From 7e722c4088f3e61ce175bd89f1a1ef26fd857189 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 5 Mar 2023 18:42:57 +0100 Subject: [PATCH] Remove outdated patches We currently carry some horrendously outdated patches, that nobody should use any more. Old kernel sources will still be kept in the respective branches at https://github.com/linux-surface/kernel/ if you need to have a look at them. --- patches/4.19/0001-surface3-power.patch | 657 - .../0002-surface3-touchscreen-dma-fix.patch | 89 - patches/4.19/0003-surface3-oemb.patch | 101 - patches/4.19/0004-surface-buttons.patch | 440 - patches/4.19/0005-suspend.patch | 334 - patches/4.19/0006-ipts.patch | 7314 - patches/4.19/0007-wifi.patch | 2571 - patches/4.19/0008-surface-gpe.patch | 394 - patches/4.19/0009-surface-sam-over-hid.patch | 334 - patches/4.19/0010-surface-sam.patch | 20918 -- patches/4.19/0011-surface-hotplug.patch | 4293 - patches/4.19/0012-surface-typecover.patch | 233 - .../4.19/0013-surface-go-touchscreen.patch | 39 - .../4.19/0014-ath10k-firmware-override.patch | 121 - patches/5.10/0001-surface3-oemb.patch | 101 - patches/5.10/0002-wifi.patch | 2520 - patches/5.10/0003-ipts.patch | 1534 - patches/5.10/0004-surface-gpe.patch | 401 - patches/5.10/0005-surface-sam-over-hid.patch | 335 - patches/5.10/0006-surface-sam.patch | 20758 -- patches/5.10/0007-surface-hotplug.patch | 470 - patches/5.10/0008-surface-typecover.patch | 233 - .../5.10/0009-surface-go-touchscreen.patch | 39 - patches/5.10/0010-surface-sensors.patch | 53 - patches/5.10/0011-cameras.patch | 11662 - .../5.10/0012-ath10k-firmware-override.patch | 121 - patches/5.11/0001-surface3-oemb.patch | 101 - patches/5.11/0002-wifi.patch | 2520 - patches/5.11/0003-ipts.patch | 1534 - patches/5.11/0004-surface-sam-over-hid.patch | 334 - patches/5.11/0005-surface-sam.patch | 23461 -- patches/5.11/0006-surface-hotplug.patch | 386 - patches/5.11/0007-surface-typecover.patch | 233 - patches/5.11/0008-surface-sensors.patch | 53 - patches/5.11/0009-cameras.patch | 11127 - .../5.11/0010-ath10k-firmware-override.patch | 121 - patches/5.12/0001-surface3-oemb.patch | 101 - patches/5.12/0002-mwifiex.patch | 2622 - patches/5.12/0003-ath10k.patch | 121 - patches/5.12/0004-ipts.patch | 1503 - patches/5.12/0005-surface-sam-over-hid.patch | 335 - patches/5.12/0006-surface-sam.patch | 10239 - patches/5.12/0007-surface-hotplug.patch | 44 - patches/5.12/0008-surface-typecover.patch | 233 - .../5.12/0009-surface-go-touchscreen.patch | 39 - patches/5.12/0010-cameras.patch | 4047 - patches/5.12/0011-s0ix-amd.patch | 1228 - patches/5.13/0001-surface3-oemb.patch | 101 - patches/5.13/0002-mwifiex.patch | 2520 - patches/5.13/0003-ath10k.patch | 121 - patches/5.13/0004-ipts.patch | 1503 - patches/5.13/0005-surface-sam-over-hid.patch | 335 - patches/5.13/0006-surface-sam.patch | 2324 - patches/5.13/0007-surface-hotplug.patch | 44 - patches/5.13/0008-surface-typecover.patch | 233 - patches/5.13/0009-cameras.patch | 5713 - patches/5.13/0010-amd-gpio.patch | 109 - patches/5.13/0011-amd-s0ix.patch | 905 - patches/5.14/0001-surface3-oemb.patch | 101 - patches/5.14/0002-mwifiex.patch | 2217 - patches/5.14/0003-ath10k.patch | 121 - patches/5.14/0004-ipts.patch | 1503 - patches/5.14/0005-surface-sam.patch | 2265 - patches/5.14/0006-surface-sam-over-hid.patch | 335 - patches/5.14/0007-surface-gpe.patch | 85 - patches/5.14/0008-surface-button.patch | 149 - patches/5.14/0009-surface-typecover.patch | 233 - patches/5.14/0010-cameras.patch | 5357 - patches/5.14/0011-amd-gpio.patch | 109 - patches/5.14/0012-misc-fixes.patch | 70 - patches/5.15/0001-surface3-oemb.patch | 101 - patches/5.15/0002-mwifiex.patch | 1862 - patches/5.15/0003-ath10k.patch | 121 - patches/5.15/0004-ipts.patch | 1503 - patches/5.15/0005-surface-sam.patch | 1816 - patches/5.15/0006-surface-sam-over-hid.patch | 335 - patches/5.15/0007-surface-gpe.patch | 85 - patches/5.15/0008-surface-button.patch | 149 - patches/5.15/0009-surface-typecover.patch | 233 - patches/5.15/0010-cameras.patch | 5414 - patches/5.15/0011-amd-gpio.patch | 109 - patches/5.15/0012-misc-fixes.patch | 53 - patches/5.16/0001-surface3-oemb.patch | 101 - patches/5.16/0002-mwifiex.patch | 845 - patches/5.16/0003-ath10k.patch | 121 - patches/5.16/0004-ipts.patch | 1503 - patches/5.16/0005-surface-sam.patch | 1756 - patches/5.16/0006-surface-sam-over-hid.patch | 335 - patches/5.16/0007-surface-gpe.patch | 37 - patches/5.16/0008-surface-button.patch | 557 - patches/5.16/0009-surface-typecover.patch | 233 - patches/5.16/0010-cameras.patch | 5039 - patches/5.16/0011-amd-gpio.patch | 109 - patches/5.16/0012-misc-fixes.patch | 111 - patches/5.17/0001-surface3-oemb.patch | 101 - patches/5.17/0002-mwifiex.patch | 704 - patches/5.17/0003-ath10k.patch | 121 - patches/5.17/0004-ipts.patch | 1603 - patches/5.17/0005-surface-sam.patch | 3567 - patches/5.17/0006-surface-sam-over-hid.patch | 335 - patches/5.17/0007-surface-button.patch | 596 - patches/5.17/0008-surface-typecover.patch | 533 - patches/5.17/0009-surface-battery.patch | 131 - patches/5.17/0010-cameras.patch | 1218 - patches/5.17/0011-amd-gpio.patch | 109 - patches/5.18/0001-surface3-oemb.patch | 101 - patches/5.18/0002-mwifiex.patch | 704 - patches/5.18/0003-ath10k.patch | 121 - patches/5.18/0004-ipts.patch | 1603 - patches/5.18/0005-surface-sam.patch | 4445 - patches/5.18/0006-surface-sam-over-hid.patch | 335 - patches/5.18/0007-surface-button.patch | 149 - patches/5.18/0008-surface-typecover.patch | 533 - patches/5.18/0009-surface-battery.patch | 83 - patches/5.18/0010-surface-gpe.patch | 41 - patches/5.18/0011-cameras.patch | 1247 - patches/5.18/0012-amd-gpio.patch | 109 - patches/5.19/0001-surface3-oemb.patch | 101 - patches/5.19/0002-mwifiex.patch | 704 - patches/5.19/0003-ath10k.patch | 121 - patches/5.19/0004-ipts.patch | 1603 - patches/5.19/0005-surface-sam.patch | 4703 - patches/5.19/0006-surface-sam-over-hid.patch | 335 - patches/5.19/0007-surface-button.patch | 149 - patches/5.19/0008-surface-typecover.patch | 533 - patches/5.19/0009-surface-gpe.patch | 43 - patches/5.19/0010-cameras.patch | 1135 - patches/5.19/0011-amd-gpio.patch | 109 - patches/5.2/0001-surface-acpi.patch | 4350 - patches/5.2/0002-suspend.patch | 281 - patches/5.2/0003-buttons.patch | 274 - patches/5.2/0004-cameras.patch | 2753 - patches/5.2/0005-ipts.patch | 6854 - patches/5.2/0006-hid.patch | 151 - patches/5.2/0007-sdcard-reader.patch | 26 - patches/5.2/0008-wifi.patch | 270 - patches/5.2/0009-surface3-power.patch | 655 - patches/5.2/0010-mwlwifi.patch | 19753 -- patches/5.2/0012-surfacebook2-dgpu.patch | 359 - patches/5.3/0001-surface-acpi.patch | 7590 - patches/5.3/0002-buttons.patch | 274 - patches/5.3/0003-hid.patch | 27 - patches/5.3/0004-surface3-power.patch | 655 - patches/5.3/0006-wifi.patch | 171 - patches/5.3/0007-legacy-i915.patch | 247070 --------------- patches/5.3/0008-ipts.patch | 7408 - patches/5.3/0009-ioremap_uc.patch | 97 - patches/5.3/0010-surface3-spi-dma.patch | 63 - patches/5.4/0001-surface3-power.patch | 657 - patches/5.4/0002-surface3-oemb.patch | 101 - patches/5.4/0003-wifi.patch | 1299 - patches/5.4/0004-ipts.patch | 2089 - patches/5.4/0005-surface-gpe.patch | 402 - patches/5.4/0006-surface-sam-over-hid.patch | 334 - patches/5.4/0007-surface-sam.patch | 20116 -- patches/5.4/0008-surface-hotplug.patch | 487 - patches/5.4/0009-surface-typecover.patch | 233 - patches/5.4/0010-surface-sensors.patch | 54 - patches/5.5/0001-surface3-power.patch | 655 - patches/5.5/0002-surface3-spi.patch | 63 - patches/5.5/0003-surface3-oemb.patch | 71 - patches/5.5/0004-surface-sam.patch | 7375 - patches/5.5/0006-wifi.patch | 231 - patches/5.5/0007-ipts.patch | 2074 - patches/5.6/0001-surface3-power.patch | 655 - patches/5.6/0002-surface3-spi.patch | 63 - patches/5.6/0003-surface3-oemb.patch | 71 - patches/5.6/0004-surface-sam.patch | 7328 - patches/5.6/0006-wifi.patch | 255 - patches/5.6/0007-ipts.patch | 2061 - patches/5.7/0001-surface3-oemb.patch | 71 - patches/5.7/0002-wifi.patch | 292 - patches/5.7/0003-ipts.patch | 2061 - patches/5.7/0004-surface-sam.patch | 15673 - patches/5.7/0005-surface-sam-over-hid.patch | 65 - patches/5.7/0006-surface-gpe.patch | 355 - patches/5.8/0001-surface3-oemb.patch | 101 - patches/5.8/0002-wifi.patch | 1396 - patches/5.8/0003-ipts.patch | 1448 - patches/5.8/0004-surface-gpe.patch | 401 - patches/5.8/0005-surface-sam-over-hid.patch | 334 - patches/5.8/0006-surface-sam.patch | 19594 -- patches/5.8/0007-surface-typecover.patch | 233 - patches/5.9/0001-surface3-oemb.patch | 101 - patches/5.9/0002-wifi.patch | 1376 - patches/5.9/0003-ipts.patch | 1416 - patches/5.9/0004-surface-gpe.patch | 401 - patches/5.9/0005-surface-sam-over-hid.patch | 334 - patches/5.9/0006-surface-sam.patch | 19718 -- patches/5.9/0007-surface-hotplug.patch | 548 - patches/5.9/0008-surface-typecover.patch | 233 - patches/6.0/0001-surface3-oemb.patch | 101 - patches/6.0/0002-mwifiex.patch | 400 - patches/6.0/0003-ath10k.patch | 121 - patches/6.0/0004-ipts.patch | 1606 - patches/6.0/0005-surface-sam.patch | 1024 - patches/6.0/0006-surface-sam-over-hid.patch | 335 - patches/6.0/0007-surface-button.patch | 149 - patches/6.0/0008-surface-typecover.patch | 533 - patches/6.0/0009-cameras.patch | 1054 - patches/6.0/0010-amd-gpio.patch | 109 - patches/6.0/0011-rtc.patch | 109 - 202 files changed, 630900 deletions(-) delete mode 100644 patches/4.19/0001-surface3-power.patch delete mode 100644 patches/4.19/0002-surface3-touchscreen-dma-fix.patch delete mode 100644 patches/4.19/0003-surface3-oemb.patch delete mode 100644 patches/4.19/0004-surface-buttons.patch delete mode 100644 patches/4.19/0005-suspend.patch delete mode 100644 patches/4.19/0006-ipts.patch delete mode 100644 patches/4.19/0007-wifi.patch delete mode 100644 patches/4.19/0008-surface-gpe.patch delete mode 100644 patches/4.19/0009-surface-sam-over-hid.patch delete mode 100644 patches/4.19/0010-surface-sam.patch delete mode 100644 patches/4.19/0011-surface-hotplug.patch delete mode 100644 patches/4.19/0012-surface-typecover.patch delete mode 100644 patches/4.19/0013-surface-go-touchscreen.patch delete mode 100644 patches/4.19/0014-ath10k-firmware-override.patch delete mode 100644 patches/5.10/0001-surface3-oemb.patch delete mode 100644 patches/5.10/0002-wifi.patch delete mode 100644 patches/5.10/0003-ipts.patch delete mode 100644 patches/5.10/0004-surface-gpe.patch delete mode 100644 patches/5.10/0005-surface-sam-over-hid.patch delete mode 100644 patches/5.10/0006-surface-sam.patch delete mode 100644 patches/5.10/0007-surface-hotplug.patch delete mode 100644 patches/5.10/0008-surface-typecover.patch delete mode 100644 patches/5.10/0009-surface-go-touchscreen.patch delete mode 100644 patches/5.10/0010-surface-sensors.patch delete mode 100644 patches/5.10/0011-cameras.patch delete mode 100644 patches/5.10/0012-ath10k-firmware-override.patch delete mode 100644 patches/5.11/0001-surface3-oemb.patch delete mode 100644 patches/5.11/0002-wifi.patch delete mode 100644 patches/5.11/0003-ipts.patch delete mode 100644 patches/5.11/0004-surface-sam-over-hid.patch delete mode 100644 patches/5.11/0005-surface-sam.patch delete mode 100644 patches/5.11/0006-surface-hotplug.patch delete mode 100644 patches/5.11/0007-surface-typecover.patch delete mode 100644 patches/5.11/0008-surface-sensors.patch delete mode 100644 patches/5.11/0009-cameras.patch delete mode 100644 patches/5.11/0010-ath10k-firmware-override.patch delete mode 100644 patches/5.12/0001-surface3-oemb.patch delete mode 100644 patches/5.12/0002-mwifiex.patch delete mode 100644 patches/5.12/0003-ath10k.patch delete mode 100644 patches/5.12/0004-ipts.patch delete mode 100644 patches/5.12/0005-surface-sam-over-hid.patch delete mode 100644 patches/5.12/0006-surface-sam.patch delete mode 100644 patches/5.12/0007-surface-hotplug.patch delete mode 100644 patches/5.12/0008-surface-typecover.patch delete mode 100644 patches/5.12/0009-surface-go-touchscreen.patch delete mode 100644 patches/5.12/0010-cameras.patch delete mode 100644 patches/5.12/0011-s0ix-amd.patch delete mode 100644 patches/5.13/0001-surface3-oemb.patch delete mode 100644 patches/5.13/0002-mwifiex.patch delete mode 100644 patches/5.13/0003-ath10k.patch delete mode 100644 patches/5.13/0004-ipts.patch delete mode 100644 patches/5.13/0005-surface-sam-over-hid.patch delete mode 100644 patches/5.13/0006-surface-sam.patch delete mode 100644 patches/5.13/0007-surface-hotplug.patch delete mode 100644 patches/5.13/0008-surface-typecover.patch delete mode 100644 patches/5.13/0009-cameras.patch delete mode 100644 patches/5.13/0010-amd-gpio.patch delete mode 100644 patches/5.13/0011-amd-s0ix.patch delete mode 100644 patches/5.14/0001-surface3-oemb.patch delete mode 100644 patches/5.14/0002-mwifiex.patch delete mode 100644 patches/5.14/0003-ath10k.patch delete mode 100644 patches/5.14/0004-ipts.patch delete mode 100644 patches/5.14/0005-surface-sam.patch delete mode 100644 patches/5.14/0006-surface-sam-over-hid.patch delete mode 100644 patches/5.14/0007-surface-gpe.patch delete mode 100644 patches/5.14/0008-surface-button.patch delete mode 100644 patches/5.14/0009-surface-typecover.patch delete mode 100644 patches/5.14/0010-cameras.patch delete mode 100644 patches/5.14/0011-amd-gpio.patch delete mode 100644 patches/5.14/0012-misc-fixes.patch delete mode 100644 patches/5.15/0001-surface3-oemb.patch delete mode 100644 patches/5.15/0002-mwifiex.patch delete mode 100644 patches/5.15/0003-ath10k.patch delete mode 100644 patches/5.15/0004-ipts.patch delete mode 100644 patches/5.15/0005-surface-sam.patch delete mode 100644 patches/5.15/0006-surface-sam-over-hid.patch delete mode 100644 patches/5.15/0007-surface-gpe.patch delete mode 100644 patches/5.15/0008-surface-button.patch delete mode 100644 patches/5.15/0009-surface-typecover.patch delete mode 100644 patches/5.15/0010-cameras.patch delete mode 100644 patches/5.15/0011-amd-gpio.patch delete mode 100644 patches/5.15/0012-misc-fixes.patch delete mode 100644 patches/5.16/0001-surface3-oemb.patch delete mode 100644 patches/5.16/0002-mwifiex.patch delete mode 100644 patches/5.16/0003-ath10k.patch delete mode 100644 patches/5.16/0004-ipts.patch delete mode 100644 patches/5.16/0005-surface-sam.patch delete mode 100644 patches/5.16/0006-surface-sam-over-hid.patch delete mode 100644 patches/5.16/0007-surface-gpe.patch delete mode 100644 patches/5.16/0008-surface-button.patch delete mode 100644 patches/5.16/0009-surface-typecover.patch delete mode 100644 patches/5.16/0010-cameras.patch delete mode 100644 patches/5.16/0011-amd-gpio.patch delete mode 100644 patches/5.16/0012-misc-fixes.patch delete mode 100644 patches/5.17/0001-surface3-oemb.patch delete mode 100644 patches/5.17/0002-mwifiex.patch delete mode 100644 patches/5.17/0003-ath10k.patch delete mode 100644 patches/5.17/0004-ipts.patch delete mode 100644 patches/5.17/0005-surface-sam.patch delete mode 100644 patches/5.17/0006-surface-sam-over-hid.patch delete mode 100644 patches/5.17/0007-surface-button.patch delete mode 100644 patches/5.17/0008-surface-typecover.patch delete mode 100644 patches/5.17/0009-surface-battery.patch delete mode 100644 patches/5.17/0010-cameras.patch delete mode 100644 patches/5.17/0011-amd-gpio.patch delete mode 100644 patches/5.18/0001-surface3-oemb.patch delete mode 100644 patches/5.18/0002-mwifiex.patch delete mode 100644 patches/5.18/0003-ath10k.patch delete mode 100644 patches/5.18/0004-ipts.patch delete mode 100644 patches/5.18/0005-surface-sam.patch delete mode 100644 patches/5.18/0006-surface-sam-over-hid.patch delete mode 100644 patches/5.18/0007-surface-button.patch delete mode 100644 patches/5.18/0008-surface-typecover.patch delete mode 100644 patches/5.18/0009-surface-battery.patch delete mode 100644 patches/5.18/0010-surface-gpe.patch delete mode 100644 patches/5.18/0011-cameras.patch delete mode 100644 patches/5.18/0012-amd-gpio.patch delete mode 100644 patches/5.19/0001-surface3-oemb.patch delete mode 100644 patches/5.19/0002-mwifiex.patch delete mode 100644 patches/5.19/0003-ath10k.patch delete mode 100644 patches/5.19/0004-ipts.patch delete mode 100644 patches/5.19/0005-surface-sam.patch delete mode 100644 patches/5.19/0006-surface-sam-over-hid.patch delete mode 100644 patches/5.19/0007-surface-button.patch delete mode 100644 patches/5.19/0008-surface-typecover.patch delete mode 100644 patches/5.19/0009-surface-gpe.patch delete mode 100644 patches/5.19/0010-cameras.patch delete mode 100644 patches/5.19/0011-amd-gpio.patch delete mode 100644 patches/5.2/0001-surface-acpi.patch delete mode 100644 patches/5.2/0002-suspend.patch delete mode 100644 patches/5.2/0003-buttons.patch delete mode 100644 patches/5.2/0004-cameras.patch delete mode 100644 patches/5.2/0005-ipts.patch delete mode 100644 patches/5.2/0006-hid.patch delete mode 100644 patches/5.2/0007-sdcard-reader.patch delete mode 100644 patches/5.2/0008-wifi.patch delete mode 100644 patches/5.2/0009-surface3-power.patch delete mode 100644 patches/5.2/0010-mwlwifi.patch delete mode 100644 patches/5.2/0012-surfacebook2-dgpu.patch delete mode 100644 patches/5.3/0001-surface-acpi.patch delete mode 100644 patches/5.3/0002-buttons.patch delete mode 100644 patches/5.3/0003-hid.patch delete mode 100644 patches/5.3/0004-surface3-power.patch delete mode 100644 patches/5.3/0006-wifi.patch delete mode 100644 patches/5.3/0007-legacy-i915.patch delete mode 100644 patches/5.3/0008-ipts.patch delete mode 100644 patches/5.3/0009-ioremap_uc.patch delete mode 100644 patches/5.3/0010-surface3-spi-dma.patch delete mode 100644 patches/5.4/0001-surface3-power.patch delete mode 100644 patches/5.4/0002-surface3-oemb.patch delete mode 100644 patches/5.4/0003-wifi.patch delete mode 100644 patches/5.4/0004-ipts.patch delete mode 100644 patches/5.4/0005-surface-gpe.patch delete mode 100644 patches/5.4/0006-surface-sam-over-hid.patch delete mode 100644 patches/5.4/0007-surface-sam.patch delete mode 100644 patches/5.4/0008-surface-hotplug.patch delete mode 100644 patches/5.4/0009-surface-typecover.patch delete mode 100644 patches/5.4/0010-surface-sensors.patch delete mode 100644 patches/5.5/0001-surface3-power.patch delete mode 100644 patches/5.5/0002-surface3-spi.patch delete mode 100644 patches/5.5/0003-surface3-oemb.patch delete mode 100644 patches/5.5/0004-surface-sam.patch delete mode 100644 patches/5.5/0006-wifi.patch delete mode 100644 patches/5.5/0007-ipts.patch delete mode 100644 patches/5.6/0001-surface3-power.patch delete mode 100644 patches/5.6/0002-surface3-spi.patch delete mode 100644 patches/5.6/0003-surface3-oemb.patch delete mode 100644 patches/5.6/0004-surface-sam.patch delete mode 100644 patches/5.6/0006-wifi.patch delete mode 100644 patches/5.6/0007-ipts.patch delete mode 100644 patches/5.7/0001-surface3-oemb.patch delete mode 100644 patches/5.7/0002-wifi.patch delete mode 100644 patches/5.7/0003-ipts.patch delete mode 100644 patches/5.7/0004-surface-sam.patch delete mode 100644 patches/5.7/0005-surface-sam-over-hid.patch delete mode 100644 patches/5.7/0006-surface-gpe.patch delete mode 100644 patches/5.8/0001-surface3-oemb.patch delete mode 100644 patches/5.8/0002-wifi.patch delete mode 100644 patches/5.8/0003-ipts.patch delete mode 100644 patches/5.8/0004-surface-gpe.patch delete mode 100644 patches/5.8/0005-surface-sam-over-hid.patch delete mode 100644 patches/5.8/0006-surface-sam.patch delete mode 100644 patches/5.8/0007-surface-typecover.patch delete mode 100644 patches/5.9/0001-surface3-oemb.patch delete mode 100644 patches/5.9/0002-wifi.patch delete mode 100644 patches/5.9/0003-ipts.patch delete mode 100644 patches/5.9/0004-surface-gpe.patch delete mode 100644 patches/5.9/0005-surface-sam-over-hid.patch delete mode 100644 patches/5.9/0006-surface-sam.patch delete mode 100644 patches/5.9/0007-surface-hotplug.patch delete mode 100644 patches/5.9/0008-surface-typecover.patch delete mode 100644 patches/6.0/0001-surface3-oemb.patch delete mode 100644 patches/6.0/0002-mwifiex.patch delete mode 100644 patches/6.0/0003-ath10k.patch delete mode 100644 patches/6.0/0004-ipts.patch delete mode 100644 patches/6.0/0005-surface-sam.patch delete mode 100644 patches/6.0/0006-surface-sam-over-hid.patch delete mode 100644 patches/6.0/0007-surface-button.patch delete mode 100644 patches/6.0/0008-surface-typecover.patch delete mode 100644 patches/6.0/0009-cameras.patch delete mode 100644 patches/6.0/0010-amd-gpio.patch delete mode 100644 patches/6.0/0011-rtc.patch diff --git a/patches/4.19/0001-surface3-power.patch b/patches/4.19/0001-surface3-power.patch deleted file mode 100644 index 79a8a85bf..000000000 --- a/patches/4.19/0001-surface3-power.patch +++ /dev/null @@ -1,657 +0,0 @@ -From a9919478a89a940a0e8a546c369c6d7c2559ee63 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 28 Sep 2019 18:00:43 +0200 -Subject: [PATCH] platform/x86: Surface 3 battery platform operation region - support - -Patchset: surface3-power ---- - drivers/platform/x86/Kconfig | 7 + - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/surface3_power.c | 604 ++++++++++++++++++++++++++ - 3 files changed, 612 insertions(+) - create mode 100644 drivers/platform/x86/surface3_power.c - -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index a13bb4ddd0cf..0d20ffdb5a67 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -1161,6 +1161,13 @@ config SURFACE_3_BUTTON - ---help--- - This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet. - -+config SURFACE_3_POWER_OPREGION -+ tristate "Surface 3 battery platform operation region support" -+ depends on ACPI && I2C -+ help -+ Select this option to enable support for ACPI operation -+ region of the Surface 3 battery platform driver. -+ - config INTEL_PUNIT_IPC - tristate "Intel P-Unit IPC Driver" - ---help--- -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index dc29af4d8e2f..2ea90039a3e4 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -81,6 +81,7 @@ obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o - obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o - obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o -+obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o - obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o - obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ -diff --git a/drivers/platform/x86/surface3_power.c b/drivers/platform/x86/surface3_power.c -new file mode 100644 -index 000000000000..e0af01a60302 ---- /dev/null -+++ b/drivers/platform/x86/surface3_power.c -@@ -0,0 +1,604 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+ -+/* -+ * Supports for the power IC on the Surface 3 tablet. -+ * -+ * (C) Copyright 2016-2018 Red Hat, Inc -+ * (C) Copyright 2016-2018 Benjamin Tissoires -+ * (C) Copyright 2016 Stephen Just -+ * -+ */ -+ -+/* -+ * This driver has been reverse-engineered by parsing the DSDT of the Surface 3 -+ * and looking at the registers of the chips. -+ * -+ * The DSDT allowed to find out that: -+ * - the driver is required for the ACPI BAT0 device to communicate to the chip -+ * through an operation region. -+ * - the various defines for the operation region functions to communicate with -+ * this driver -+ * - the DSM 3f99e367-6220-4955-8b0f-06ef2ae79412 allows to trigger ACPI -+ * events to BAT0 (the code is all available in the DSDT). -+ * -+ * Further findings regarding the 2 chips declared in the MSHW0011 are: -+ * - there are 2 chips declared: -+ * . 0x22 seems to control the ADP1 line status (and probably the charger) -+ * . 0x55 controls the battery directly -+ * - the battery chip uses a SMBus protocol (using plain SMBus allows non -+ * destructive commands): -+ * . the commands/registers used are in the range 0x00..0x7F -+ * . if bit 8 (0x80) is set in the SMBus command, the returned value is the -+ * same as when it is not set. There is a high chance this bit is the -+ * read/write -+ * . the various registers semantic as been deduced by observing the register -+ * dumps. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define POLL_INTERVAL (2 * HZ) -+ -+struct mshw0011_data { -+ struct i2c_client *adp1; -+ struct i2c_client *bat0; -+ unsigned short notify_mask; -+ struct task_struct *poll_task; -+ bool kthread_running; -+ -+ bool charging; -+ bool bat_charging; -+ u8 trip_point; -+ s32 full_capacity; -+}; -+ -+struct mshw0011_lookup { -+ struct mshw0011_data *cdata; -+ unsigned int n; -+ unsigned int index; -+ int addr; -+}; -+ -+struct mshw0011_handler_data { -+ struct acpi_connection_info info; -+ struct i2c_client *client; -+}; -+ -+struct bix { -+ u32 revision; -+ u32 power_unit; -+ u32 design_capacity; -+ u32 last_full_charg_capacity; -+ u32 battery_technology; -+ u32 design_voltage; -+ u32 design_capacity_of_warning; -+ u32 design_capacity_of_low; -+ u32 cycle_count; -+ u32 measurement_accuracy; -+ u32 max_sampling_time; -+ u32 min_sampling_time; -+ u32 max_average_interval; -+ u32 min_average_interval; -+ u32 battery_capacity_granularity_1; -+ u32 battery_capacity_granularity_2; -+ char model[10]; -+ char serial[10]; -+ char type[10]; -+ char OEM[10]; -+} __packed; -+ -+struct bst { -+ u32 battery_state; -+ s32 battery_present_rate; -+ u32 battery_remaining_capacity; -+ u32 battery_present_voltage; -+} __packed; -+ -+struct gsb_command { -+ u8 arg0; -+ u8 arg1; -+ u8 arg2; -+} __packed; -+ -+struct gsb_buffer { -+ u8 status; -+ u8 len; -+ u8 ret; -+ union { -+ struct gsb_command cmd; -+ struct bst bst; -+ struct bix bix; -+ } __packed; -+} __packed; -+ -+ -+#define ACPI_BATTERY_STATE_DISCHARGING BIT(0) -+#define ACPI_BATTERY_STATE_CHARGING BIT(1) -+#define ACPI_BATTERY_STATE_CRITICAL BIT(2) -+ -+#define MSHW0011_CMD_DEST_BAT0 0x01 -+#define MSHW0011_CMD_DEST_ADP1 0x03 -+ -+#define MSHW0011_CMD_BAT0_STA 0x01 -+#define MSHW0011_CMD_BAT0_BIX 0x02 -+#define MSHW0011_CMD_BAT0_BCT 0x03 -+#define MSHW0011_CMD_BAT0_BTM 0x04 -+#define MSHW0011_CMD_BAT0_BST 0x05 -+#define MSHW0011_CMD_BAT0_BTP 0x06 -+#define MSHW0011_CMD_ADP1_PSR 0x07 -+#define MSHW0011_CMD_BAT0_PSOC 0x09 -+#define MSHW0011_CMD_BAT0_PMAX 0x0a -+#define MSHW0011_CMD_BAT0_PSRC 0x0b -+#define MSHW0011_CMD_BAT0_CHGI 0x0c -+#define MSHW0011_CMD_BAT0_ARTG 0x0d -+ -+#define MSHW0011_NOTIFY_GET_VERSION 0x00 -+#define MSHW0011_NOTIFY_ADP1 0x01 -+#define MSHW0011_NOTIFY_BAT0_BST 0x02 -+#define MSHW0011_NOTIFY_BAT0_BIX 0x05 -+ -+#define MSHW0011_ADP1_REG_PSR 0x04 -+ -+#define MSHW0011_BAT0_REG_CAPACITY 0x0c -+#define MSHW0011_BAT0_REG_FULL_CHG_CAPACITY 0x0e -+#define MSHW0011_BAT0_REG_DESIGN_CAPACITY 0x40 -+#define MSHW0011_BAT0_REG_VOLTAGE 0x08 -+#define MSHW0011_BAT0_REG_RATE 0x14 -+#define MSHW0011_BAT0_REG_OEM 0x45 -+#define MSHW0011_BAT0_REG_TYPE 0x4e -+#define MSHW0011_BAT0_REG_SERIAL_NO 0x56 -+#define MSHW0011_BAT0_REG_CYCLE_CNT 0x6e -+ -+#define MSHW0011_EV_2_5 0x1ff -+ -+static int -+mshw0011_notify(struct mshw0011_data *cdata, u8 arg1, u8 arg2, -+ unsigned int *ret_value) -+{ -+ static const guid_t mshw0011_guid = -+ GUID_INIT(0x3F99E367, 0x6220, 0x4955, -+ 0x8B, 0x0F, 0x06, 0xEF, 0x2A, 0xE7, 0x94, 0x12); -+ union acpi_object *obj; -+ struct acpi_device *adev; -+ acpi_handle handle; -+ unsigned int i; -+ -+ handle = ACPI_HANDLE(&cdata->adp1->dev); -+ if (!handle || acpi_bus_get_device(handle, &adev)) -+ return -ENODEV; -+ -+ obj = acpi_evaluate_dsm_typed(handle, &mshw0011_guid, arg1, arg2, NULL, -+ ACPI_TYPE_BUFFER); -+ if (!obj) { -+ dev_err(&cdata->adp1->dev, "device _DSM execution failed\n"); -+ return -ENODEV; -+ } -+ -+ *ret_value = 0; -+ for (i = 0; i < obj->buffer.length; i++) -+ *ret_value |= obj->buffer.pointer[i] << (i * 8); -+ -+ ACPI_FREE(obj); -+ return 0; -+} -+ -+static const struct bix default_bix = { -+ .revision = 0x00, -+ .power_unit = 0x01, -+ .design_capacity = 0x1dca, -+ .last_full_charg_capacity = 0x1dca, -+ .battery_technology = 0x01, -+ .design_voltage = 0x10df, -+ .design_capacity_of_warning = 0x8f, -+ .design_capacity_of_low = 0x47, -+ .cycle_count = 0xffffffff, -+ .measurement_accuracy = 0x00015f90, -+ .max_sampling_time = 0x03e8, -+ .min_sampling_time = 0x03e8, -+ .max_average_interval = 0x03e8, -+ .min_average_interval = 0x03e8, -+ .battery_capacity_granularity_1 = 0x45, -+ .battery_capacity_granularity_2 = 0x11, -+ .model = "P11G8M", -+ .serial = "", -+ .type = "LION", -+ .OEM = "", -+}; -+ -+static int mshw0011_bix(struct mshw0011_data *cdata, struct bix *bix) -+{ -+ struct i2c_client *client = cdata->bat0; -+ char buf[10]; -+ int ret; -+ -+ *bix = default_bix; -+ -+ /* get design capacity */ -+ ret = i2c_smbus_read_word_data(client, -+ MSHW0011_BAT0_REG_DESIGN_CAPACITY); -+ if (ret < 0) { -+ dev_err(&client->dev, "Error reading design capacity: %d\n", -+ ret); -+ return ret; -+ } -+ bix->design_capacity = ret; -+ -+ /* get last full charge capacity */ -+ ret = i2c_smbus_read_word_data(client, -+ MSHW0011_BAT0_REG_FULL_CHG_CAPACITY); -+ if (ret < 0) { -+ dev_err(&client->dev, -+ "Error reading last full charge capacity: %d\n", ret); -+ return ret; -+ } -+ bix->last_full_charg_capacity = ret; -+ -+ /* get serial number */ -+ ret = i2c_smbus_read_i2c_block_data(client, MSHW0011_BAT0_REG_SERIAL_NO, -+ 10, buf); -+ if (ret != 10) { -+ dev_err(&client->dev, "Error reading serial no: %d\n", ret); -+ return ret; -+ } -+ snprintf(bix->serial, ARRAY_SIZE(bix->serial), -+ "%*pE%*pE", 3, buf + 7, 6, buf); -+ -+ /* get cycle count */ -+ ret = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_CYCLE_CNT); -+ if (ret < 0) { -+ dev_err(&client->dev, "Error reading cycle count: %d\n", ret); -+ return ret; -+ } -+ bix->cycle_count = ret; -+ -+ /* get OEM name */ -+ ret = i2c_smbus_read_i2c_block_data(client, MSHW0011_BAT0_REG_OEM, -+ 4, buf); -+ if (ret != 4) { -+ dev_err(&client->dev, "Error reading cycle count: %d\n", ret); -+ return ret; -+ } -+ snprintf(bix->OEM, ARRAY_SIZE(bix->OEM), "%*pE", 3, buf); -+ -+ return 0; -+} -+ -+static int mshw0011_bst(struct mshw0011_data *cdata, struct bst *bst) -+{ -+ struct i2c_client *client = cdata->bat0; -+ int rate, capacity, voltage, state; -+ s16 tmp; -+ -+ rate = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_RATE); -+ if (rate < 0) -+ return rate; -+ -+ capacity = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_CAPACITY); -+ if (capacity < 0) -+ return capacity; -+ -+ voltage = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_VOLTAGE); -+ if (voltage < 0) -+ return voltage; -+ -+ tmp = rate; -+ bst->battery_present_rate = abs((s32)tmp); -+ -+ state = 0; -+ if ((s32) tmp > 0) -+ state |= ACPI_BATTERY_STATE_CHARGING; -+ else if ((s32) tmp < 0) -+ state |= ACPI_BATTERY_STATE_DISCHARGING; -+ bst->battery_state = state; -+ -+ bst->battery_remaining_capacity = capacity; -+ bst->battery_present_voltage = voltage; -+ -+ return 0; -+} -+ -+static int mshw0011_adp_psr(struct mshw0011_data *cdata) -+{ -+ struct i2c_client *client = cdata->adp1; -+ int ret; -+ -+ ret = i2c_smbus_read_byte_data(client, MSHW0011_ADP1_REG_PSR); -+ if (ret < 0) -+ return ret; -+ -+ return ret; -+} -+ -+static int mshw0011_isr(struct mshw0011_data *cdata) -+{ -+ struct bst bst; -+ struct bix bix; -+ int ret; -+ bool status, bat_status; -+ -+ ret = mshw0011_adp_psr(cdata); -+ if (ret < 0) -+ return ret; -+ -+ status = ret; -+ -+ if (status != cdata->charging) -+ mshw0011_notify(cdata, cdata->notify_mask, -+ MSHW0011_NOTIFY_ADP1, &ret); -+ -+ cdata->charging = status; -+ -+ ret = mshw0011_bst(cdata, &bst); -+ if (ret < 0) -+ return ret; -+ -+ bat_status = bst.battery_state; -+ -+ if (bat_status != cdata->bat_charging) -+ mshw0011_notify(cdata, cdata->notify_mask, -+ MSHW0011_NOTIFY_BAT0_BST, &ret); -+ -+ cdata->bat_charging = bat_status; -+ -+ ret = mshw0011_bix(cdata, &bix); -+ if (ret < 0) -+ return ret; -+ if (bix.last_full_charg_capacity != cdata->full_capacity) -+ mshw0011_notify(cdata, cdata->notify_mask, -+ MSHW0011_NOTIFY_BAT0_BIX, &ret); -+ -+ cdata->full_capacity = bix.last_full_charg_capacity; -+ -+ return 0; -+} -+ -+static int mshw0011_poll_task(void *data) -+{ -+ struct mshw0011_data *cdata = data; -+ int ret = 0; -+ -+ cdata->kthread_running = true; -+ -+ set_freezable(); -+ -+ while (!kthread_should_stop()) { -+ schedule_timeout_interruptible(POLL_INTERVAL); -+ try_to_freeze(); -+ ret = mshw0011_isr(data); -+ if (ret) -+ break; -+ } -+ -+ cdata->kthread_running = false; -+ return ret; -+} -+ -+static acpi_status -+mshw0011_space_handler(u32 function, acpi_physical_address command, -+ u32 bits, u64 *value64, -+ void *handler_context, void *region_context) -+{ -+ struct gsb_buffer *gsb = (struct gsb_buffer *)value64; -+ struct mshw0011_handler_data *data = handler_context; -+ struct acpi_connection_info *info = &data->info; -+ struct acpi_resource_i2c_serialbus *sb; -+ struct i2c_client *client = data->client; -+ struct mshw0011_data *cdata = i2c_get_clientdata(client); -+ struct acpi_resource *ares; -+ u32 accessor_type = function >> 16; -+ acpi_status ret; -+ int status = 1; -+ -+ ret = acpi_buffer_to_resource(info->connection, info->length, &ares); -+ if (ACPI_FAILURE(ret)) -+ return ret; -+ -+ if (!value64 || ares->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) { -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ sb = &ares->data.i2c_serial_bus; -+ if (sb->type != ACPI_RESOURCE_SERIAL_TYPE_I2C) { -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ if (gsb->cmd.arg0 == MSHW0011_CMD_DEST_ADP1 && -+ gsb->cmd.arg1 == MSHW0011_CMD_ADP1_PSR) { -+ ret = mshw0011_adp_psr(cdata); -+ if (ret >= 0) { -+ status = ret; -+ ret = 0; -+ } -+ goto out; -+ } -+ -+ if (gsb->cmd.arg0 != MSHW0011_CMD_DEST_BAT0) { -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ switch (gsb->cmd.arg1) { -+ case MSHW0011_CMD_BAT0_STA: -+ break; -+ case MSHW0011_CMD_BAT0_BIX: -+ ret = mshw0011_bix(cdata, &gsb->bix); -+ break; -+ case MSHW0011_CMD_BAT0_BTP: -+ cdata->trip_point = gsb->cmd.arg2; -+ break; -+ case MSHW0011_CMD_BAT0_BST: -+ ret = mshw0011_bst(cdata, &gsb->bst); -+ break; -+ default: -+ pr_info("command(0x%02x) is not supported.\n", gsb->cmd.arg1); -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ out: -+ gsb->ret = status; -+ gsb->status = 0; -+ -+ err: -+ ACPI_FREE(ares); -+ return ret; -+} -+ -+static int mshw0011_install_space_handler(struct i2c_client *client) -+{ -+ acpi_handle handle; -+ struct mshw0011_handler_data *data; -+ acpi_status status; -+ -+ handle = ACPI_HANDLE(&client->dev); -+ -+ if (!handle) -+ return -ENODEV; -+ -+ data = kzalloc(sizeof(struct mshw0011_handler_data), -+ GFP_KERNEL); -+ if (!data) -+ return -ENOMEM; -+ -+ data->client = client; -+ status = acpi_bus_attach_private_data(handle, (void *)data); -+ if (ACPI_FAILURE(status)) { -+ kfree(data); -+ return -ENOMEM; -+ } -+ -+ status = acpi_install_address_space_handler(handle, -+ ACPI_ADR_SPACE_GSBUS, -+ &mshw0011_space_handler, -+ NULL, -+ data); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&client->dev, "Error installing i2c space handler\n"); -+ acpi_bus_detach_private_data(handle); -+ kfree(data); -+ return -ENOMEM; -+ } -+ -+ acpi_walk_dep_device_list(handle); -+ return 0; -+} -+ -+static void mshw0011_remove_space_handler(struct i2c_client *client) -+{ -+ acpi_handle handle = ACPI_HANDLE(&client->dev); -+ struct mshw0011_handler_data *data; -+ acpi_status status; -+ -+ if (!handle) -+ return; -+ -+ acpi_remove_address_space_handler(handle, -+ ACPI_ADR_SPACE_GSBUS, -+ &mshw0011_space_handler); -+ -+ status = acpi_bus_get_private_data(handle, (void **)&data); -+ if (ACPI_SUCCESS(status)) -+ kfree(data); -+ -+ acpi_bus_detach_private_data(handle); -+} -+ -+static int mshw0011_probe(struct i2c_client *client) -+{ -+ struct i2c_board_info board_info; -+ struct device *dev = &client->dev; -+ struct i2c_client *bat0; -+ -+ struct mshw0011_data *data; -+ int error, mask; -+ -+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); -+ if (!data) -+ return -ENOMEM; -+ -+ data->adp1 = client; -+ i2c_set_clientdata(client, data); -+ -+ memset(&board_info, 0, sizeof(board_info)); -+ strlcpy(board_info.type, "MSHW0011-bat0", I2C_NAME_SIZE); -+ -+ bat0 = i2c_acpi_new_device(dev, 1, &board_info); -+ if (!bat0) -+ return -ENOMEM; -+ -+ data->bat0 = bat0; -+ i2c_set_clientdata(bat0, data); -+ -+ error = mshw0011_notify(data, 1, MSHW0011_NOTIFY_GET_VERSION, &mask); -+ if (error) -+ goto out_err; -+ -+ data->notify_mask = mask == MSHW0011_EV_2_5; -+ -+ data->poll_task = kthread_run(mshw0011_poll_task, data, "mshw0011_adp"); -+ if (IS_ERR(data->poll_task)) { -+ error = PTR_ERR(data->poll_task); -+ dev_err(&client->dev, "Unable to run kthread err %d\n", error); -+ goto out_err; -+ } -+ -+ error = mshw0011_install_space_handler(client); -+ if (error) -+ goto out_err; -+ -+ return 0; -+ -+out_err: -+ if (data->kthread_running) -+ kthread_stop(data->poll_task); -+ i2c_unregister_device(data->bat0); -+ return error; -+} -+ -+static int mshw0011_remove(struct i2c_client *client) -+{ -+ struct mshw0011_data *cdata = i2c_get_clientdata(client); -+ -+ mshw0011_remove_space_handler(client); -+ -+ if (cdata->kthread_running) -+ kthread_stop(cdata->poll_task); -+ -+ i2c_unregister_device(cdata->bat0); -+ -+ return 0; -+} -+ -+static const struct acpi_device_id mshw0011_acpi_match[] = { -+ { "MSHW0011", 0 }, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, mshw0011_acpi_match); -+ -+static struct i2c_driver mshw0011_driver = { -+ .probe_new = mshw0011_probe, -+ .remove = mshw0011_remove, -+ .driver = { -+ .name = "mshw0011", -+ .acpi_match_table = ACPI_PTR(mshw0011_acpi_match), -+ }, -+}; -+module_i2c_driver(mshw0011_driver); -+ -+MODULE_AUTHOR("Benjamin Tissoires "); -+MODULE_DESCRIPTION("mshw0011 driver"); -+MODULE_LICENSE("GPL v2"); --- -2.33.0 - diff --git a/patches/4.19/0002-surface3-touchscreen-dma-fix.patch b/patches/4.19/0002-surface3-touchscreen-dma-fix.patch deleted file mode 100644 index 220d6f165..000000000 --- a/patches/4.19/0002-surface3-touchscreen-dma-fix.patch +++ /dev/null @@ -1,89 +0,0 @@ -From a6ecb122a7a0d8ad93890cd3a1e5bd5f44de614a Mon Sep 17 00:00:00 2001 -From: Andy Shevchenko -Date: Sun, 5 Jul 2020 14:56:20 +0300 -Subject: [PATCH] dmaengine: dw: Initialize channel before each transfer - -In some cases DMA can be used only with a consumer which does runtime power -management and on the platforms, that have DMA auto power gating logic -(see comments in the drivers/acpi/acpi_lpss.c), may result in DMA losing -its context. Simple mitigation of this issue is to initialize channel -each time the consumer initiates a transfer. - -Fixes: cfdf5b6cc598 ("dw_dmac: add support for Lynxpoint DMA controllers") -Reported-by: Tsuchiya Yuto -Signed-off-by: Andy Shevchenko -Acked-by: Viresh Kumar -BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=206403 -Link: https://lore.kernel.org/r/20200705115620.51929-1-andriy.shevchenko@linux.intel.com -Signed-off-by: Vinod Koul - -(cherry picked from commit 99ba8b9b0d9780e9937eb1d488d120e9e5c2533d) -[Reason for cherry-picking this commit: - This commit fixes touch input when using DMA mode on Surface 3's - touchscreen. - Note: this commit was not backported to v4.19 by upstream. For now, - backport this patch ourselves.] -[ Conflicts: - drivers/dma/dw/core.c - Resolved conflict by accepting current change then removed - DW_DMA_IS_INITIALIZED lines] -Signed-off-by: Tsuchiya Yuto -Patchset: surface3-touchscreen-dma-fix ---- - drivers/dma/dw/core.c | 12 ------------ - 1 file changed, 12 deletions(-) - -diff --git a/drivers/dma/dw/core.c b/drivers/dma/dw/core.c -index 055d83b6cb68..acf64302a2b2 100644 ---- a/drivers/dma/dw/core.c -+++ b/drivers/dma/dw/core.c -@@ -180,9 +180,6 @@ static void dwc_initialize(struct dw_dma_chan *dwc) - { - struct dw_dma *dw = to_dw_dma(dwc->chan.device); - -- if (test_bit(DW_DMA_IS_INITIALIZED, &dwc->flags)) -- return; -- - if (dw->pdata->is_idma32) - dwc_initialize_chan_idma32(dwc); - else -@@ -191,8 +188,6 @@ static void dwc_initialize(struct dw_dma_chan *dwc) - /* Enable interrupts */ - channel_set_bit(dw, MASK.XFER, dwc->mask); - channel_set_bit(dw, MASK.ERROR, dwc->mask); -- -- set_bit(DW_DMA_IS_INITIALIZED, &dwc->flags); - } - - /*----------------------------------------------------------------------*/ -@@ -1091,8 +1086,6 @@ static void idma32_fifo_partition(struct dw_dma *dw) - - static void dw_dma_off(struct dw_dma *dw) - { -- unsigned int i; -- - dma_writel(dw, CFG, 0); - - channel_clear_bit(dw, MASK.XFER, dw->all_chan_mask); -@@ -1103,9 +1096,6 @@ static void dw_dma_off(struct dw_dma *dw) - - while (dma_readl(dw, CFG) & DW_CFG_DMA_EN) - cpu_relax(); -- -- for (i = 0; i < dw->dma.chancnt; i++) -- clear_bit(DW_DMA_IS_INITIALIZED, &dw->chan[i].flags); - } - - static void dw_dma_on(struct dw_dma *dw) -@@ -1170,8 +1160,6 @@ static void dwc_free_chan_resources(struct dma_chan *chan) - /* Clear custom channel configuration */ - memset(&dwc->dws, 0, sizeof(struct dw_dma_slave)); - -- clear_bit(DW_DMA_IS_INITIALIZED, &dwc->flags); -- - /* Disable interrupts */ - channel_clear_bit(dw, MASK.XFER, dwc->mask); - channel_clear_bit(dw, MASK.BLOCK, dwc->mask); --- -2.33.0 - diff --git a/patches/4.19/0003-surface3-oemb.patch b/patches/4.19/0003-surface3-oemb.patch deleted file mode 100644 index 786616660..000000000 --- a/patches/4.19/0003-surface3-oemb.patch +++ /dev/null @@ -1,101 +0,0 @@ -From f2ac73a1a6535648da848404aa571779dfcf66e4 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 18 Oct 2020 16:42:44 +0900 -Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI - table - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI data into dmi_system_id tables used -for quirks so that each driver can enable quirks even on the affected -systems. - -On affected systems, DMI data will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: surface3-oemb ---- - drivers/platform/x86/surface3-wmi.c | 7 +++++++ - sound/soc/codecs/rt5645.c | 9 +++++++++ - sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ - 3 files changed, 24 insertions(+) - -diff --git a/drivers/platform/x86/surface3-wmi.c b/drivers/platform/x86/surface3-wmi.c -index 25b176996cb7..58d11877677f 100644 ---- a/drivers/platform/x86/surface3-wmi.c -+++ b/drivers/platform/x86/surface3-wmi.c -@@ -41,6 +41,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - #endif - { } - }; -diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c -index 9185bd7c5a6d..a514d03ae58f 100644 ---- a/sound/soc/codecs/rt5645.c -+++ b/sound/soc/codecs/rt5645.c -@@ -3712,6 +3712,15 @@ static const struct dmi_system_id dmi_platform_data[] = { - }, - .driver_data = (void *)&intel_braswell_platform_data, - }, -+ { -+ .ident = "Microsoft Surface 3", -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)&intel_braswell_platform_data, -+ }, - { - /* - * Match for the GPDwin which unfortunately uses somewhat -diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -index 91bb99b69601..8418938b32ad 100644 ---- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c -+++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -@@ -36,6 +36,14 @@ static const struct dmi_system_id cht_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .callback = cht_surface_quirk_cb, -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - { } - }; - --- -2.33.0 - diff --git a/patches/4.19/0004-surface-buttons.patch b/patches/4.19/0004-surface-buttons.patch deleted file mode 100644 index 215c69e88..000000000 --- a/patches/4.19/0004-surface-buttons.patch +++ /dev/null @@ -1,440 +0,0 @@ -From be8bb5f93d32d3ac7f918cdbfb04b412673ff80b Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 27 Jul 2019 17:51:37 +0200 -Subject: [PATCH] platform/x86: surfacepro3_button: Fix device check - -Do not use the surfacepro3_button driver on newer Microsoft Surface -models, only use it on the Surface Pro 3 and 4. Newer models (5th, 6th -and possibly future generations) use the same device as the Surface Pro -4 to represent their volume and power buttons (MSHW0040), but their -actual implementation is significantly different. This patch ensures -that the surfacepro3_button driver is only used on the Pro 3 and 4 -models, allowing a different driver to bind on other models. - -Signed-off-by: Maximilian Luz -Patchset: surface-buttons ---- - drivers/platform/x86/surfacepro3_button.c | 47 +++++++++++++++++++++++ - 1 file changed, 47 insertions(+) - -diff --git a/drivers/platform/x86/surfacepro3_button.c b/drivers/platform/x86/surfacepro3_button.c -index 1b491690ce07..96627627060e 100644 ---- a/drivers/platform/x86/surfacepro3_button.c -+++ b/drivers/platform/x86/surfacepro3_button.c -@@ -24,6 +24,12 @@ - #define SURFACE_BUTTON_OBJ_NAME "VGBI" - #define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons" - -+#define MSHW0040_DSM_REVISION 0x01 -+#define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision -+static const guid_t MSHW0040_DSM_UUID = -+ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65, -+ 0x49, 0x80, 0x35); -+ - #define SURFACE_BUTTON_NOTIFY_TABLET_MODE 0xc8 - - #define SURFACE_BUTTON_NOTIFY_PRESS_POWER 0xc6 -@@ -146,6 +152,44 @@ static int surface_button_resume(struct device *dev) - } - #endif - -+/* -+ * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device -+ * ID (MSHW0040) for the power/volume buttons. Make sure this is the right -+ * device by checking for the _DSM method and OEM Platform Revision. -+ * -+ * Returns true if the driver should bind to this device, i.e. the device is -+ * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. -+ */ -+static bool surface_button_check_MSHW0040(struct acpi_device *dev) -+{ -+ acpi_handle handle = dev->handle; -+ union acpi_object *result; -+ u64 oem_platform_rev = 0; // valid revisions are nonzero -+ -+ // get OEM platform revision -+ result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ MSHW0040_DSM_GET_OMPR, -+ NULL, ACPI_TYPE_INTEGER); -+ -+ /* -+ * If evaluating the _DSM fails, the method is not present. This means -+ * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we -+ * should use this driver. We use revision 0 indicating it is -+ * unavailable. -+ */ -+ -+ if (result) { -+ oem_platform_rev = result->integer.value; -+ ACPI_FREE(result); -+ } -+ -+ dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); -+ -+ return oem_platform_rev == 0; -+} -+ -+ - static int surface_button_add(struct acpi_device *device) - { - struct surface_button *button; -@@ -158,6 +202,9 @@ static int surface_button_add(struct acpi_device *device) - strlen(SURFACE_BUTTON_OBJ_NAME))) - return -ENODEV; - -+ if (!surface_button_check_MSHW0040(device)) -+ return -ENODEV; -+ - button = kzalloc(sizeof(struct surface_button), GFP_KERNEL); - if (!button) - return -ENOMEM; --- -2.33.0 - -From dc6c755a5867401c03cb7ca17396f8d9b05df58d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 27 Jul 2019 17:52:01 +0200 -Subject: [PATCH] Input: soc_button_array - Add support for newer surface - devices - -Power and volume button support for 5th and 6th generation Microsoft -Surface devices via soc_button_array. - -Note that these devices use the same MSHW0040 device as on the Surface -Pro 4, however the implementation is different (GPIOs vs. ACPI -notifications). Thus some checking is required to ensure we only load -this driver on the correct devices. - -Signed-off-by: Maximilian Luz -Patchset: surface-buttons ---- - drivers/input/misc/Kconfig | 6 +- - drivers/input/misc/soc_button_array.c | 105 +++++++++++++++++++++++--- - 2 files changed, 96 insertions(+), 15 deletions(-) - -diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig -index ca59a2be9bc5..ea69610370e8 100644 ---- a/drivers/input/misc/Kconfig -+++ b/drivers/input/misc/Kconfig -@@ -781,10 +781,10 @@ config INPUT_IDEAPAD_SLIDEBAR - - config INPUT_SOC_BUTTON_ARRAY - tristate "Windows-compatible SoC Button Array" -- depends on KEYBOARD_GPIO -+ depends on KEYBOARD_GPIO && ACPI - help -- Say Y here if you have a SoC-based tablet that originally -- runs Windows 8. -+ Say Y here if you have a SoC-based tablet that originally runs -+ Windows 8 or a Microsoft Surface Book 2, Pro 5, Laptop 1 or later. - - To compile this driver as a module, choose M here: the - module will be called soc_button_array. -diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c -index 55cd6e0b409c..8f21c062c85d 100644 ---- a/drivers/input/misc/soc_button_array.c -+++ b/drivers/input/misc/soc_button_array.c -@@ -29,6 +29,11 @@ struct soc_button_info { - bool wakeup; - }; - -+struct soc_device_data { -+ const struct soc_button_info *button_info; -+ int (*check)(struct device *dev); -+}; -+ - /* - * Some of the buttons like volume up/down are auto repeat, while others - * are not. To support both, we register two platform devices, and put -@@ -91,8 +96,13 @@ soc_button_device_create(struct platform_device *pdev, - continue; - - gpio = soc_button_lookup_gpio(&pdev->dev, info->acpi_index); -- if (!gpio_is_valid(gpio)) -+ if (gpio < 0 && gpio != -ENOENT) { -+ error = gpio; -+ goto err_free_mem; -+ } else if (!gpio_is_valid(gpio)) { -+ /* Skip GPIO if not present */ - continue; -+ } - - gpio_keys[n_buttons].type = info->event_type; - gpio_keys[n_buttons].code = info->event_code; -@@ -309,23 +319,26 @@ static int soc_button_remove(struct platform_device *pdev) - static int soc_button_probe(struct platform_device *pdev) - { - struct device *dev = &pdev->dev; -- const struct acpi_device_id *id; -- struct soc_button_info *button_info; -+ const struct soc_device_data *device_data; -+ const struct soc_button_info *button_info; - struct soc_button_data *priv; - struct platform_device *pd; - int i; - int error; - -- id = acpi_match_device(dev->driver->acpi_match_table, dev); -- if (!id) -- return -ENODEV; -+ device_data = acpi_device_get_match_data(dev); -+ if (device_data && device_data->check) { -+ error = device_data->check(dev); -+ if (error) -+ return error; -+ } - -- if (!id->driver_data) { -+ if (device_data && device_data->button_info) { -+ button_info = device_data->button_info; -+ } else { - button_info = soc_button_get_button_info(dev); - if (IS_ERR(button_info)) - return PTR_ERR(button_info); -- } else { -- button_info = (struct soc_button_info *)id->driver_data; - } - - error = gpiod_count(dev, NULL); -@@ -357,7 +370,7 @@ static int soc_button_probe(struct platform_device *pdev) - if (!priv->children[0] && !priv->children[1]) - return -ENODEV; - -- if (!id->driver_data) -+ if (!device_data || !device_data->button_info) - devm_kfree(dev, button_info); - - return 0; -@@ -368,7 +381,7 @@ static int soc_button_probe(struct platform_device *pdev) - * is defined in section 2.8.7.2 of "Windows ACPI Design Guide for SoC - * Platforms" - */ --static struct soc_button_info soc_button_PNP0C40[] = { -+static const struct soc_button_info soc_button_PNP0C40[] = { - { "power", 0, EV_KEY, KEY_POWER, false, true }, - { "home", 1, EV_KEY, KEY_LEFTMETA, false, true }, - { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false }, -@@ -377,9 +390,77 @@ static struct soc_button_info soc_button_PNP0C40[] = { - { } - }; - -+static const struct soc_device_data soc_device_PNP0C40 = { -+ .button_info = soc_button_PNP0C40, -+}; -+ -+/* -+ * Special device check for Surface Book 2 and Surface Pro (2017). -+ * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned -+ * devices use MSHW0040 for power and volume buttons, however the way they -+ * have to be addressed differs. Make sure that we only load this drivers -+ * for the correct devices by checking the OEM Platform Revision provided by -+ * the _DSM method. -+ */ -+#define MSHW0040_DSM_REVISION 0x01 -+#define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision -+static const guid_t MSHW0040_DSM_UUID = -+ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65, -+ 0x49, 0x80, 0x35); -+ -+static int soc_device_check_MSHW0040(struct device *dev) -+{ -+ acpi_handle handle = ACPI_HANDLE(dev); -+ union acpi_object *result; -+ u64 oem_platform_rev = 0; // valid revisions are nonzero -+ -+ // get OEM platform revision -+ result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ MSHW0040_DSM_GET_OMPR, NULL, -+ ACPI_TYPE_INTEGER); -+ -+ if (result) { -+ oem_platform_rev = result->integer.value; -+ ACPI_FREE(result); -+ } -+ -+ /* -+ * If the revision is zero here, the _DSM evaluation has failed. This -+ * indicates that we have a Pro 4 or Book 1 and this driver should not -+ * be used. -+ */ -+ if (oem_platform_rev == 0) -+ return -ENODEV; -+ -+ dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); -+ -+ return 0; -+} -+ -+/* -+ * Button infos for Microsoft Surface Book 2 and Surface Pro (2017). -+ * Obtained from DSDT/testing. -+ */ -+static const struct soc_button_info soc_button_MSHW0040[] = { -+ { "power", 0, EV_KEY, KEY_POWER, false, true }, -+ { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false }, -+ { "volume_down", 4, EV_KEY, KEY_VOLUMEDOWN, true, false }, -+ { } -+}; -+ -+static const struct soc_device_data soc_device_MSHW0040 = { -+ .button_info = soc_button_MSHW0040, -+ .check = soc_device_check_MSHW0040, -+}; -+ - static const struct acpi_device_id soc_button_acpi_match[] = { -- { "PNP0C40", (unsigned long)soc_button_PNP0C40 }, -+ { "PNP0C40", (unsigned long)&soc_device_PNP0C40 }, - { "ACPI0011", 0 }, -+ -+ /* Microsoft Surface Devices (5th and 6th generation) */ -+ { "MSHW0040", (unsigned long)&soc_device_MSHW0040 }, -+ - { } - }; - --- -2.33.0 - -From 3495e149d8dc07d035fc87d0820b2e0938ed097b Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sat, 5 Oct 2019 14:11:58 +0200 -Subject: [PATCH] Input: soc_button_array - partial revert of support for newer - surface devices - -Commit c394159310d0 ("Input: soc_button_array - add support for newer -surface devices") not only added support for the MSHW0040 ACPI HID, -but for some reason it also makes changes to the error handling of the -soc_button_lookup_gpio() call in soc_button_device_create(). Note ideally -this seamingly unrelated change would have been made in a separate commit, -with a message explaining the what and why of this change. - -I guess this change may have been added to deal with -EPROBE_DEFER errors, -but in case of the existing support for PNP0C40 devices, treating --EPROBE_DEFER as any other error is deliberate, see the comment this -commit adds for why. - -The actual returning of -EPROBE_DEFER to the caller of soc_button_probe() -introduced by the new error checking causes a serious regression: - -On devices with so called virtual GPIOs soc_button_lookup_gpio() will -always return -EPROBE_DEFER for these fake GPIOs, when this happens -during the second call of soc_button_device_create() we already have -successfully registered our first child. This causes the kernel to think -we are making progress with probing things even though we unregister the -child before again before we return the -EPROBE_DEFER. Since we are making -progress the kernel will retry deferred-probes again immediately ending -up stuck in a loop with the following showing in dmesg: - -[ 124.022697] input: gpio-keys as /devices/platform/INTCFD9:00/gpio-keys.0.auto/input/input6537 -[ 124.040764] input: gpio-keys as /devices/platform/INTCFD9:00/gpio-keys.0.auto/input/input6538 -[ 124.056967] input: gpio-keys as /devices/platform/INTCFD9:00/gpio-keys.0.auto/input/input6539 -[ 124.072143] input: gpio-keys as /devices/platform/INTCFD9:00/gpio-keys.0.auto/input/input6540 -[ 124.092373] input: gpio-keys as /devices/platform/INTCFD9:00/gpio-keys.0.auto/input/input6541 -[ 124.108065] input: gpio-keys as /devices/platform/INTCFD9:00/gpio-keys.0.auto/input/input6542 -[ 124.128483] input: gpio-keys as /devices/platform/INTCFD9:00/gpio-keys.0.auto/input/input6543 -[ 124.147141] input: gpio-keys as /devices/platform/INTCFD9:00/gpio-keys.0.auto/input/input6544 -[ 124.165070] input: gpio-keys as /devices/platform/INTCFD9:00/gpio-keys.0.auto/input/input6545 -[ 124.179775] input: gpio-keys as /devices/platform/INTCFD9:00/gpio-keys.0.auto/input/input6546 -[ 124.202726] input: gpio-keys as /devices/platform/INTCFD9:00/gpio-keys.0.auto/input/input6547 - - -And 1 CPU core being stuck at 100% and udev hanging since it is waiting -for the modprobe of soc_button_array to return. - -This patch reverts the soc_button_lookup_gpio() error handling changes, -fixing this regression. - -Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") -BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=205031 -Cc: Maximilian Luz -Signed-off-by: Hans de Goede -Patchset: surface-buttons ---- - drivers/input/misc/soc_button_array.c | 17 ++++++++++++----- - 1 file changed, 12 insertions(+), 5 deletions(-) - -diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c -index 8f21c062c85d..5983733d78dd 100644 ---- a/drivers/input/misc/soc_button_array.c -+++ b/drivers/input/misc/soc_button_array.c -@@ -96,11 +96,18 @@ soc_button_device_create(struct platform_device *pdev, - continue; - - gpio = soc_button_lookup_gpio(&pdev->dev, info->acpi_index); -- if (gpio < 0 && gpio != -ENOENT) { -- error = gpio; -- goto err_free_mem; -- } else if (!gpio_is_valid(gpio)) { -- /* Skip GPIO if not present */ -+ if (!gpio_is_valid(gpio)) { -+ /* -+ * Skip GPIO if not present. Note we deliberately -+ * ignore -EPROBE_DEFER errors here. On some devices -+ * Intel is using so called virtual GPIOs which are not -+ * GPIOs at all but some way for AML code to check some -+ * random status bits without need a custom opregion. -+ * In some cases the resources table we parse points to -+ * such a virtual GPIO, since these are not real GPIOs -+ * we do not have a driver for these so they will never -+ * show up, therefor we ignore -EPROBE_DEFER. -+ */ - continue; - } - --- -2.33.0 - -From 97852be9f07614c8f49c9aaa7308d550d952207d Mon Sep 17 00:00:00 2001 -From: "Tsuchiya Yuto (kitakar5525)" -Date: Mon, 11 May 2020 17:40:21 +0900 -Subject: [PATCH] Input: soc_button_array - fix Wdiscarded-qualifiers for - kernels below 4.20 -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -There is a warning from compiler when building v4.19-surface kernel that -backported button patches from newer kernels. - - drivers/input/misc/soc_button_array.c: In function ‘soc_button_probe’: - drivers/input/misc/soc_button_array.c:381:19: warning: passing argument 2 of ‘devm_kfree’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] - 381 | devm_kfree(dev, button_info); - | ^~~~~~~~~~~ - In file included from ./include/linux/input.h:22, - from drivers/input/misc/soc_button_array.c:14: - ./include/linux/device.h:695:50: note: expected ‘void *’ but argument is of type ‘const struct soc_button_info *’ - 695 | extern void devm_kfree(struct device *dev, void *p); - | ~~~~~~^ - -This warning happens bacause commit 0571967dfb5d25 ("devres: constify p -in devm_kfree()") has not been applied to v4.19 series (available after -v4.20-rc1). - -This commit casts button_info to (void *) when calling devm_kfree() to -avoid compiler warning. - -Fixes: b892fc124285ba ("Input: soc_button_array - Add support for newer surface devices") -Signed-off-by: Tsuchiya Yuto (kitakar5525) -Patchset: surface-buttons ---- - drivers/input/misc/soc_button_array.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c -index 5983733d78dd..c564ea99f47d 100644 ---- a/drivers/input/misc/soc_button_array.c -+++ b/drivers/input/misc/soc_button_array.c -@@ -378,7 +378,7 @@ static int soc_button_probe(struct platform_device *pdev) - return -ENODEV; - - if (!device_data || !device_data->button_info) -- devm_kfree(dev, button_info); -+ devm_kfree(dev, (void *)button_info); - - return 0; - } --- -2.33.0 - diff --git a/patches/4.19/0005-suspend.patch b/patches/4.19/0005-suspend.patch deleted file mode 100644 index 3421ec121..000000000 --- a/patches/4.19/0005-suspend.patch +++ /dev/null @@ -1,334 +0,0 @@ -From 393a9f9995f490d4b17ef4713aa370111b67efeb Mon Sep 17 00:00:00 2001 -From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com> -Date: Sat, 28 Sep 2019 17:48:21 +0200 -Subject: [PATCH] nvme: Backport changes for suspend - -Backported commits are: - -- torvalds/linux@4eaefe8c621c6195c91044396ed8060c179f7aae - (nvme-pci: Allow PCI bus-level PM to be used if ASPM is disabled) - -- torvalds/linux@accd2dd72c8f087441d725dd916688171519e4e6 - (PCI/ASPM: Add pcie_aspm_enabled()) - -- torvalds/linux@d916b1be94b6dc8d293abed2451f3062f6af7551 - (nvme-pci: use host managed power state for suspend) - -- torvalds/linux@1a87ee657c530bb2f3e39e4ac184d48f5f959cda - (nvme: export get and set features) - -- torvalds/linux@d6135c3a1ec0cddda7b8b8e1b5b4abeeafd98289 - (nvme-pci: Sync queues on reset) - -Patchset: suspend ---- - drivers/nvme/host/core.c | 36 ++++++++++++-- - drivers/nvme/host/nvme.h | 7 +++ - drivers/nvme/host/pci.c | 103 +++++++++++++++++++++++++++++++++++++-- - drivers/pci/pcie/aspm.c | 20 ++++++++ - include/linux/pci.h | 2 + - 5 files changed, 162 insertions(+), 6 deletions(-) - -diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c -index e64310f2296f..2a46419c5820 100644 ---- a/drivers/nvme/host/core.c -+++ b/drivers/nvme/host/core.c -@@ -1065,15 +1065,15 @@ static struct nvme_id_ns *nvme_identify_ns(struct nvme_ctrl *ctrl, - return id; - } - --static int nvme_set_features(struct nvme_ctrl *dev, unsigned fid, unsigned dword11, -- void *buffer, size_t buflen, u32 *result) -+static int nvme_features(struct nvme_ctrl *dev, u8 op, unsigned int fid, -+ unsigned int dword11, void *buffer, size_t buflen, u32 *result) - { - union nvme_result res = { 0 }; - struct nvme_command c; - int ret; - - memset(&c, 0, sizeof(c)); -- c.features.opcode = nvme_admin_set_features; -+ c.features.opcode = op; - c.features.fid = cpu_to_le32(fid); - c.features.dword11 = cpu_to_le32(dword11); - -@@ -1084,6 +1084,24 @@ static int nvme_set_features(struct nvme_ctrl *dev, unsigned fid, unsigned dword - return ret; - } - -+int nvme_set_features(struct nvme_ctrl *dev, unsigned int fid, -+ unsigned int dword11, void *buffer, size_t buflen, -+ u32 *result) -+{ -+ return nvme_features(dev, nvme_admin_set_features, fid, dword11, buffer, -+ buflen, result); -+} -+EXPORT_SYMBOL_GPL(nvme_set_features); -+ -+int nvme_get_features(struct nvme_ctrl *dev, unsigned int fid, -+ unsigned int dword11, void *buffer, size_t buflen, -+ u32 *result) -+{ -+ return nvme_features(dev, nvme_admin_get_features, fid, dword11, buffer, -+ buflen, result); -+} -+EXPORT_SYMBOL_GPL(nvme_get_features); -+ - int nvme_set_queue_count(struct nvme_ctrl *ctrl, int *count) - { - u32 q_count = (*count - 1) | ((*count - 1) << 16); -@@ -3630,6 +3648,18 @@ static void nvme_free_ctrl(struct device *dev) - nvme_put_subsystem(subsys); - } - -+ -+void nvme_sync_queues(struct nvme_ctrl *ctrl) -+{ -+ struct nvme_ns *ns; -+ -+ down_read(&ctrl->namespaces_rwsem); -+ list_for_each_entry(ns, &ctrl->namespaces, list) -+ blk_sync_queue(ns->queue); -+ up_read(&ctrl->namespaces_rwsem); -+} -+EXPORT_SYMBOL_GPL(nvme_sync_queues); -+ - /* - * Initialize a NVMe controller structures. This needs to be called during - * earliest initialization so that we have the initialized structured around -diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h -index 276975506709..29e062d29c04 100644 ---- a/drivers/nvme/host/nvme.h -+++ b/drivers/nvme/host/nvme.h -@@ -437,6 +437,7 @@ void nvme_complete_async_event(struct nvme_ctrl *ctrl, __le16 status, - void nvme_stop_queues(struct nvme_ctrl *ctrl); - void nvme_start_queues(struct nvme_ctrl *ctrl); - void nvme_kill_queues(struct nvme_ctrl *ctrl); -+void nvme_sync_queues(struct nvme_ctrl *ctrl); - void nvme_unfreeze(struct nvme_ctrl *ctrl); - void nvme_wait_freeze(struct nvme_ctrl *ctrl); - void nvme_wait_freeze_timeout(struct nvme_ctrl *ctrl, long timeout); -@@ -454,6 +455,12 @@ int __nvme_submit_sync_cmd(struct request_queue *q, struct nvme_command *cmd, - union nvme_result *result, void *buffer, unsigned bufflen, - unsigned timeout, int qid, int at_head, - blk_mq_req_flags_t flags); -+int nvme_set_features(struct nvme_ctrl *dev, unsigned int fid, -+ unsigned int dword11, void *buffer, size_t buflen, -+ u32 *result); -+int nvme_get_features(struct nvme_ctrl *dev, unsigned int fid, -+ unsigned int dword11, void *buffer, size_t buflen, -+ u32 *result); - int nvme_set_queue_count(struct nvme_ctrl *ctrl, int *count); - void nvme_stop_keep_alive(struct nvme_ctrl *ctrl); - int nvme_reset_ctrl(struct nvme_ctrl *ctrl); -diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c -index d7cf3202cdd3..bdafe953c9a9 100644 ---- a/drivers/nvme/host/pci.c -+++ b/drivers/nvme/host/pci.c -@@ -26,6 +26,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -106,6 +107,7 @@ struct nvme_dev { - u32 cmbloc; - struct nvme_ctrl ctrl; - struct completion ioq_wait; -+ u32 last_ps; - - mempool_t *iod_mempool; - -@@ -2286,6 +2288,7 @@ static void nvme_reset_work(struct work_struct *work) - */ - if (dev->ctrl.ctrl_config & NVME_CC_ENABLE) - nvme_dev_disable(dev, false); -+ nvme_sync_queues(&dev->ctrl); - - mutex_lock(&dev->shutdown_lock); - result = nvme_pci_enable(dev); -@@ -2624,16 +2627,101 @@ static void nvme_remove(struct pci_dev *pdev) - } - - #ifdef CONFIG_PM_SLEEP -+static int nvme_get_power_state(struct nvme_ctrl *ctrl, u32 *ps) -+{ -+ return nvme_get_features(ctrl, NVME_FEAT_POWER_MGMT, 0, NULL, 0, ps); -+} -+ -+static int nvme_set_power_state(struct nvme_ctrl *ctrl, u32 ps) -+{ -+ return nvme_set_features(ctrl, NVME_FEAT_POWER_MGMT, ps, NULL, 0, NULL); -+} -+ -+static int nvme_resume(struct device *dev) -+{ -+ struct nvme_dev *ndev = pci_get_drvdata(to_pci_dev(dev)); -+ struct nvme_ctrl *ctrl = &ndev->ctrl; -+ -+ if (ndev->last_ps == U32_MAX || -+ nvme_set_power_state(ctrl, ndev->last_ps) != 0) -+ nvme_reset_ctrl(ctrl); -+ return 0; -+} -+ - static int nvme_suspend(struct device *dev) - { - struct pci_dev *pdev = to_pci_dev(dev); - struct nvme_dev *ndev = pci_get_drvdata(pdev); -+ struct nvme_ctrl *ctrl = &ndev->ctrl; -+ int ret = -EBUSY; -+ -+ ndev->last_ps = U32_MAX; -+ -+ /* -+ * The platform does not remove power for a kernel managed suspend so -+ * use host managed nvme power settings for lowest idle power if -+ * possible. This should have quicker resume latency than a full device -+ * shutdown. But if the firmware is involved after the suspend or the -+ * device does not support any non-default power states, shut down the -+ * device fully. -+ * -+ * If ASPM is not enabled for the device, shut down the device and allow -+ * the PCI bus layer to put it into D3 in order to take the PCIe link -+ * down, so as to allow the platform to achieve its minimum low-power -+ * state (which may not be possible if the link is up). -+ */ -+ if (pm_suspend_via_firmware() || !ctrl->npss || -+ !pcie_aspm_enabled(pdev)) { -+ nvme_dev_disable(ndev, true); -+ return 0; -+ } -+ -+ nvme_start_freeze(ctrl); -+ nvme_wait_freeze(ctrl); -+ nvme_sync_queues(ctrl); -+ -+ if (ctrl->state != NVME_CTRL_LIVE && -+ ctrl->state != NVME_CTRL_ADMIN_ONLY) -+ goto unfreeze; -+ -+ ret = nvme_get_power_state(ctrl, &ndev->last_ps); -+ if (ret < 0) -+ goto unfreeze; -+ -+ ret = nvme_set_power_state(ctrl, ctrl->npss); -+ if (ret < 0) -+ goto unfreeze; -+ -+ if (ret) { -+ /* -+ * Clearing npss forces a controller reset on resume. The -+ * correct value will be resdicovered then. -+ */ -+ nvme_dev_disable(ndev, true); -+ ctrl->npss = 0; -+ ret = 0; -+ goto unfreeze; -+ } -+ /* -+ * A saved state prevents pci pm from generically controlling the -+ * device's power. If we're using protocol specific settings, we don't -+ * want pci interfering. -+ */ -+ pci_save_state(pdev); -+unfreeze: -+ nvme_unfreeze(ctrl); -+ return ret; -+} -+ -+static int nvme_simple_suspend(struct device *dev) -+{ -+ struct nvme_dev *ndev = pci_get_drvdata(to_pci_dev(dev)); - - nvme_dev_disable(ndev, true); - return 0; - } - --static int nvme_resume(struct device *dev) -+static int nvme_simple_resume(struct device *dev) - { - struct pci_dev *pdev = to_pci_dev(dev); - struct nvme_dev *ndev = pci_get_drvdata(pdev); -@@ -2641,9 +2729,16 @@ static int nvme_resume(struct device *dev) - nvme_reset_ctrl(&ndev->ctrl); - return 0; - } --#endif - --static SIMPLE_DEV_PM_OPS(nvme_dev_pm_ops, nvme_suspend, nvme_resume); -+const struct dev_pm_ops nvme_dev_pm_ops = { -+ .suspend = nvme_suspend, -+ .resume = nvme_resume, -+ .freeze = nvme_simple_suspend, -+ .thaw = nvme_simple_resume, -+ .poweroff = nvme_simple_suspend, -+ .restore = nvme_simple_resume, -+}; -+#endif /* CONFIG_PM_SLEEP */ - - static pci_ers_result_t nvme_error_detected(struct pci_dev *pdev, - pci_channel_state_t state) -@@ -2748,9 +2843,11 @@ static struct pci_driver nvme_driver = { - .probe = nvme_probe, - .remove = nvme_remove, - .shutdown = nvme_shutdown, -+#ifdef CONFIG_PM_SLEEP - .driver = { - .pm = &nvme_dev_pm_ops, - }, -+#endif - .sriov_configure = pci_sriov_configure_simple, - .err_handler = &nvme_err_handler, - }; -diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c -index 279f9f0197b0..cb474338f39d 100644 ---- a/drivers/pci/pcie/aspm.c -+++ b/drivers/pci/pcie/aspm.c -@@ -1171,6 +1171,26 @@ static int pcie_aspm_get_policy(char *buffer, const struct kernel_param *kp) - module_param_call(policy, pcie_aspm_set_policy, pcie_aspm_get_policy, - NULL, 0644); - -+/** -+ * pcie_aspm_enabled - Check if PCIe ASPM has been enabled for a device. -+ * @pdev: Target device. -+ */ -+bool pcie_aspm_enabled(struct pci_dev *pdev) -+{ -+ struct pci_dev *bridge = pci_upstream_bridge(pdev); -+ bool ret; -+ -+ if (!bridge) -+ return false; -+ -+ mutex_lock(&aspm_lock); -+ ret = bridge->link_state ? !!bridge->link_state->aspm_enabled : false; -+ mutex_unlock(&aspm_lock); -+ -+ return ret; -+} -+EXPORT_SYMBOL_GPL(pcie_aspm_enabled); -+ - #ifdef CONFIG_PCIEASPM_DEBUG - static ssize_t link_state_show(struct device *dev, - struct device_attribute *attr, -diff --git a/include/linux/pci.h b/include/linux/pci.h -index 2fda9893962d..ec6c48ecd7d5 100644 ---- a/include/linux/pci.h -+++ b/include/linux/pci.h -@@ -1488,8 +1488,10 @@ extern bool pcie_ports_native; - - #ifdef CONFIG_PCIEASPM - bool pcie_aspm_support_enabled(void); -+bool pcie_aspm_enabled(struct pci_dev *pdev); - #else - static inline bool pcie_aspm_support_enabled(void) { return false; } -+static inline bool pcie_aspm_enabled(struct pci_dev *pdev) { return false; } - #endif - - #ifdef CONFIG_PCIEAER --- -2.33.0 - diff --git a/patches/4.19/0006-ipts.patch b/patches/4.19/0006-ipts.patch deleted file mode 100644 index d4b496ed2..000000000 --- a/patches/4.19/0006-ipts.patch +++ /dev/null @@ -1,7314 +0,0 @@ -From ab9cccbae943c29bf47ae3522da72af934cca6f8 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 28 Sep 2019 17:58:17 +0200 -Subject: [PATCH] Add support for Intel IPTS touch devices - -Patchset: ipts ---- - drivers/gpu/drm/i915/Makefile | 3 + - drivers/gpu/drm/i915/i915_debugfs.c | 63 +- - drivers/gpu/drm/i915/i915_drv.c | 9 +- - drivers/gpu/drm/i915/i915_drv.h | 3 + - drivers/gpu/drm/i915/i915_gem_context.c | 12 + - drivers/gpu/drm/i915/i915_irq.c | 7 +- - drivers/gpu/drm/i915/i915_params.c | 5 +- - drivers/gpu/drm/i915/i915_params.h | 5 +- - drivers/gpu/drm/i915/intel_guc.h | 1 + - drivers/gpu/drm/i915/intel_guc_submission.c | 89 +- - drivers/gpu/drm/i915/intel_guc_submission.h | 4 + - drivers/gpu/drm/i915/intel_ipts.c | 650 ++++++++++++ - drivers/gpu/drm/i915/intel_ipts.h | 34 + - drivers/gpu/drm/i915/intel_lrc.c | 12 +- - drivers/gpu/drm/i915/intel_lrc.h | 8 + - drivers/gpu/drm/i915/intel_panel.c | 7 + - drivers/misc/Kconfig | 1 + - drivers/misc/Makefile | 1 + - drivers/misc/ipts/Kconfig | 12 + - drivers/misc/ipts/Makefile | 19 + - drivers/misc/ipts/companion.c | 211 ++++ - drivers/misc/ipts/companion.h | 25 + - drivers/misc/ipts/companion/Kconfig | 8 + - drivers/misc/ipts/companion/Makefile | 2 + - drivers/misc/ipts/companion/ipts-surface.c | 157 +++ - drivers/misc/ipts/dbgfs.c | 277 +++++ - drivers/misc/ipts/gfx.c | 180 ++++ - drivers/misc/ipts/gfx.h | 25 + - drivers/misc/ipts/hid.c | 469 +++++++++ - drivers/misc/ipts/hid.h | 21 + - drivers/misc/ipts/ipts.c | 62 ++ - drivers/misc/ipts/ipts.h | 172 +++ - drivers/misc/ipts/kernel.c | 1047 +++++++++++++++++++ - drivers/misc/ipts/kernel.h | 17 + - drivers/misc/ipts/mei-msgs.h | 901 ++++++++++++++++ - drivers/misc/ipts/mei.c | 238 +++++ - drivers/misc/ipts/msg-handler.c | 405 +++++++ - drivers/misc/ipts/msg-handler.h | 28 + - drivers/misc/ipts/params.c | 42 + - drivers/misc/ipts/params.h | 25 + - drivers/misc/ipts/resource.c | 291 ++++++ - drivers/misc/ipts/resource.h | 26 + - drivers/misc/ipts/sensor-regs.h | 834 +++++++++++++++ - drivers/misc/ipts/state.h | 22 + - drivers/misc/mei/hw-me-regs.h | 1 + - drivers/misc/mei/pci-me.c | 1 + - include/linux/ipts-binary.h | 140 +++ - include/linux/ipts-companion.h | 29 + - include/linux/ipts-gfx.h | 86 ++ - include/linux/ipts.h | 19 + - 50 files changed, 6684 insertions(+), 22 deletions(-) - create mode 100644 drivers/gpu/drm/i915/intel_ipts.c - create mode 100644 drivers/gpu/drm/i915/intel_ipts.h - create mode 100644 drivers/misc/ipts/Kconfig - create mode 100644 drivers/misc/ipts/Makefile - create mode 100644 drivers/misc/ipts/companion.c - create mode 100644 drivers/misc/ipts/companion.h - create mode 100644 drivers/misc/ipts/companion/Kconfig - create mode 100644 drivers/misc/ipts/companion/Makefile - create mode 100644 drivers/misc/ipts/companion/ipts-surface.c - create mode 100644 drivers/misc/ipts/dbgfs.c - create mode 100644 drivers/misc/ipts/gfx.c - create mode 100644 drivers/misc/ipts/gfx.h - create mode 100644 drivers/misc/ipts/hid.c - create mode 100644 drivers/misc/ipts/hid.h - create mode 100644 drivers/misc/ipts/ipts.c - create mode 100644 drivers/misc/ipts/ipts.h - create mode 100644 drivers/misc/ipts/kernel.c - create mode 100644 drivers/misc/ipts/kernel.h - create mode 100644 drivers/misc/ipts/mei-msgs.h - create mode 100644 drivers/misc/ipts/mei.c - create mode 100644 drivers/misc/ipts/msg-handler.c - create mode 100644 drivers/misc/ipts/msg-handler.h - create mode 100644 drivers/misc/ipts/params.c - create mode 100644 drivers/misc/ipts/params.h - create mode 100644 drivers/misc/ipts/resource.c - create mode 100644 drivers/misc/ipts/resource.h - create mode 100644 drivers/misc/ipts/sensor-regs.h - create mode 100644 drivers/misc/ipts/state.h - create mode 100644 include/linux/ipts-binary.h - create mode 100644 include/linux/ipts-companion.h - create mode 100644 include/linux/ipts-gfx.h - create mode 100644 include/linux/ipts.h - -diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile -index 5794f102f9b8..6ae0e91a213a 100644 ---- a/drivers/gpu/drm/i915/Makefile -+++ b/drivers/gpu/drm/i915/Makefile -@@ -155,6 +155,9 @@ i915-y += dvo_ch7017.o \ - vlv_dsi.o \ - vlv_dsi_pll.o - -+# intel precise touch & stylus -+i915-y += intel_ipts.o -+ - # Post-mortem debug and GPU hang state capture - i915-$(CONFIG_DRM_I915_CAPTURE_ERROR) += i915_gpu_error.o - i915-$(CONFIG_DRM_I915_SELFTEST) += \ -diff --git a/drivers/gpu/drm/i915/i915_debugfs.c b/drivers/gpu/drm/i915/i915_debugfs.c -index e063e98d1e82..99becb6aed68 100644 ---- a/drivers/gpu/drm/i915/i915_debugfs.c -+++ b/drivers/gpu/drm/i915/i915_debugfs.c -@@ -31,6 +31,7 @@ - #include - #include "intel_drv.h" - #include "intel_guc_submission.h" -+#include "intel_ipts.h" - - static inline struct drm_i915_private *node_to_i915(struct drm_info_node *node) - { -@@ -4695,6 +4696,64 @@ static const struct file_operations i915_fifo_underrun_reset_ops = { - .llseek = default_llseek, - }; - -+static ssize_t -+i915_ipts_cleanup_write(struct file *filp, -+ const char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ struct drm_i915_private *dev_priv = filp->private_data; -+ struct drm_device *dev = &dev_priv->drm; -+ int ret; -+ bool flag; -+ -+ ret = kstrtobool_from_user(ubuf, cnt, &flag); -+ if (ret) -+ return ret; -+ -+ if (!flag) -+ return cnt; -+ -+ ipts_cleanup(dev); -+ -+ return cnt; -+} -+ -+static const struct file_operations i915_ipts_cleanup_ops = { -+ .owner = THIS_MODULE, -+ .open = simple_open, -+ .write = i915_ipts_cleanup_write, -+ .llseek = default_llseek, -+}; -+ -+static ssize_t -+i915_ipts_init_write(struct file *filp, -+ const char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ struct drm_i915_private *dev_priv = filp->private_data; -+ struct drm_device *dev = &dev_priv->drm; -+ int ret; -+ bool flag; -+ -+ ret = kstrtobool_from_user(ubuf, cnt, &flag); -+ if (ret) -+ return ret; -+ -+ if (!flag) -+ return cnt; -+ -+ ipts_init(dev); -+ -+ return cnt; -+} -+ -+static const struct file_operations i915_ipts_init_ops = { -+ .owner = THIS_MODULE, -+ .open = simple_open, -+ .write = i915_ipts_init_write, -+ .llseek = default_llseek, -+}; -+ - static const struct drm_info_list i915_debugfs_list[] = { - {"i915_capabilities", i915_capabilities, 0}, - {"i915_gem_objects", i915_gem_object_info, 0}, -@@ -4773,7 +4832,9 @@ static const struct i915_debugfs_files { - {"i915_hpd_storm_ctl", &i915_hpd_storm_ctl_fops}, - {"i915_ipc_status", &i915_ipc_status_fops}, - {"i915_drrs_ctl", &i915_drrs_ctl_fops}, -- {"i915_edp_psr_debug", &i915_edp_psr_debug_fops} -+ {"i915_edp_psr_debug", &i915_edp_psr_debug_fops}, -+ {"i915_ipts_cleanup", &i915_ipts_cleanup_ops}, -+ {"i915_ipts_init", &i915_ipts_init_ops}, - }; - - int i915_debugfs_register(struct drm_i915_private *dev_priv) -diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c -index b0d76a7a0946..81fba8e5ab05 100644 ---- a/drivers/gpu/drm/i915/i915_drv.c -+++ b/drivers/gpu/drm/i915/i915_drv.c -@@ -47,11 +47,12 @@ - #include - - #include "i915_drv.h" --#include "i915_trace.h" - #include "i915_pmu.h" - #include "i915_query.h" -+#include "i915_trace.h" - #include "i915_vgpu.h" - #include "intel_drv.h" -+#include "intel_ipts.h" - #include "intel_uc.h" - - static struct drm_driver driver; -@@ -696,6 +697,9 @@ static int i915_load_modeset_init(struct drm_device *dev) - /* Only enable hotplug handling once the fbdev is fully set up. */ - intel_hpd_init(dev_priv); - -+ if (INTEL_GEN(dev_priv) >= 9 && i915_modparams.enable_guc && i915_modparams.enable_ipts) -+ ipts_init(dev); -+ - return 0; - - cleanup_gem: -@@ -1438,6 +1442,9 @@ void i915_driver_unload(struct drm_device *dev) - struct drm_i915_private *dev_priv = to_i915(dev); - struct pci_dev *pdev = dev_priv->drm.pdev; - -+ if (INTEL_GEN(dev_priv) >= 9 && i915_modparams.enable_guc && i915_modparams.enable_ipts) -+ ipts_cleanup(dev); -+ - i915_driver_unregister(dev_priv); - - if (i915_gem_suspend(dev_priv)) -diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h -index 37c80cfecd09..948eb874342d 100644 ---- a/drivers/gpu/drm/i915/i915_drv.h -+++ b/drivers/gpu/drm/i915/i915_drv.h -@@ -3236,6 +3236,9 @@ void i915_gem_object_do_bit_17_swizzle(struct drm_i915_gem_object *obj, - void i915_gem_object_save_bit_17_swizzle(struct drm_i915_gem_object *obj, - struct sg_table *pages); - -+struct i915_gem_context * -+i915_gem_context_create_ipts(struct drm_device *dev); -+ - static inline struct i915_gem_context * - __i915_gem_context_lookup_rcu(struct drm_i915_file_private *file_priv, u32 id) - { -diff --git a/drivers/gpu/drm/i915/i915_gem_context.c b/drivers/gpu/drm/i915/i915_gem_context.c -index ef383fd42988..89da4ff09431 100644 ---- a/drivers/gpu/drm/i915/i915_gem_context.c -+++ b/drivers/gpu/drm/i915/i915_gem_context.c -@@ -472,6 +472,18 @@ static bool needs_preempt_context(struct drm_i915_private *i915) - return HAS_LOGICAL_RING_PREEMPTION(i915); - } - -+struct i915_gem_context *i915_gem_context_create_ipts(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct i915_gem_context *ctx; -+ -+ BUG_ON(!mutex_is_locked(&dev->struct_mutex)); -+ -+ ctx = i915_gem_create_context(dev_priv, NULL); -+ -+ return ctx; -+} -+ - int i915_gem_contexts_init(struct drm_i915_private *dev_priv) - { - struct i915_gem_context *ctx; -diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c -index b7c398232136..adf168aed2fe 100644 ---- a/drivers/gpu/drm/i915/i915_irq.c -+++ b/drivers/gpu/drm/i915/i915_irq.c -@@ -36,6 +36,7 @@ - #include "i915_drv.h" - #include "i915_trace.h" - #include "intel_drv.h" -+#include "intel_ipts.h" - - /** - * DOC: interrupt handling -@@ -1503,6 +1504,9 @@ gen8_cs_irq_handler(struct intel_engine_cs *engine, u32 iir) - tasklet |= USES_GUC_SUBMISSION(engine->i915); - } - -+ if (iir & GT_RENDER_PIPECTL_NOTIFY_INTERRUPT && i915_modparams.enable_ipts) -+ ipts_notify_complete(); -+ - if (tasklet) - tasklet_hi_schedule(&engine->execlists.tasklet); - } -@@ -4123,7 +4127,8 @@ static void gen8_gt_irq_postinstall(struct drm_i915_private *dev_priv) - { - /* These are interrupts we'll toggle with the ring mask register */ - uint32_t gt_interrupts[] = { -- GT_RENDER_USER_INTERRUPT << GEN8_RCS_IRQ_SHIFT | -+ GT_RENDER_PIPECTL_NOTIFY_INTERRUPT << GEN8_RCS_IRQ_SHIFT | -+ GT_RENDER_USER_INTERRUPT << GEN8_RCS_IRQ_SHIFT | - GT_CONTEXT_SWITCH_INTERRUPT << GEN8_RCS_IRQ_SHIFT | - GT_RENDER_USER_INTERRUPT << GEN8_BCS_IRQ_SHIFT | - GT_CONTEXT_SWITCH_INTERRUPT << GEN8_BCS_IRQ_SHIFT, -diff --git a/drivers/gpu/drm/i915/i915_params.c b/drivers/gpu/drm/i915/i915_params.c -index 295e981e4a39..84415814c007 100644 ---- a/drivers/gpu/drm/i915/i915_params.c -+++ b/drivers/gpu/drm/i915/i915_params.c -@@ -145,7 +145,10 @@ i915_param_named_unsafe(edp_vswing, int, 0400, - i915_param_named_unsafe(enable_guc, int, 0400, - "Enable GuC load for GuC submission and/or HuC load. " - "Required functionality can be selected using bitmask values. " -- "(-1=auto, 0=disable [default], 1=GuC submission, 2=HuC load)"); -+ "(-1=auto [default], 0=disable, 1=GuC submission, 2=HuC load)"); -+ -+i915_param_named_unsafe(enable_ipts, int, 0400, -+ "Enable IPTS Touchscreen and Pen support (default: 1)"); - - i915_param_named(guc_log_level, int, 0400, - "GuC firmware logging level. Requires GuC to be loaded. " -diff --git a/drivers/gpu/drm/i915/i915_params.h b/drivers/gpu/drm/i915/i915_params.h -index 6c4d4a21474b..4ab800c3de6d 100644 ---- a/drivers/gpu/drm/i915/i915_params.h -+++ b/drivers/gpu/drm/i915/i915_params.h -@@ -46,7 +46,7 @@ struct drm_printer; - param(int, disable_power_well, -1) \ - param(int, enable_ips, 1) \ - param(int, invert_brightness, 0) \ -- param(int, enable_guc, 0) \ -+ param(int, enable_guc, -1) \ - param(int, guc_log_level, -1) \ - param(char *, guc_firmware_path, NULL) \ - param(char *, huc_firmware_path, NULL) \ -@@ -68,7 +68,8 @@ struct drm_printer; - param(bool, nuclear_pageflip, false) \ - param(bool, enable_dp_mst, true) \ - param(bool, enable_dpcd_backlight, false) \ -- param(bool, enable_gvt, false) -+ param(bool, enable_gvt, false) \ -+ param(int, enable_ipts, 1) - - #define MEMBER(T, member, ...) T member; - struct i915_params { -diff --git a/drivers/gpu/drm/i915/intel_guc.h b/drivers/gpu/drm/i915/intel_guc.h -index 4121928a495e..8967376accf3 100644 ---- a/drivers/gpu/drm/i915/intel_guc.h -+++ b/drivers/gpu/drm/i915/intel_guc.h -@@ -69,6 +69,7 @@ struct intel_guc { - - struct intel_guc_client *execbuf_client; - struct intel_guc_client *preempt_client; -+ struct intel_guc_client *ipts_client; - - struct guc_preempt_work preempt_work[I915_NUM_ENGINES]; - struct workqueue_struct *preempt_wq; -diff --git a/drivers/gpu/drm/i915/intel_guc_submission.c b/drivers/gpu/drm/i915/intel_guc_submission.c -index 4aa5e6463e7b..da80c5f17fee 100644 ---- a/drivers/gpu/drm/i915/intel_guc_submission.c -+++ b/drivers/gpu/drm/i915/intel_guc_submission.c -@@ -88,12 +88,17 @@ static inline struct i915_priolist *to_priolist(struct rb_node *rb) - - static inline bool is_high_priority(struct intel_guc_client *client) - { -- return (client->priority == GUC_CLIENT_PRIORITY_KMD_HIGH || -- client->priority == GUC_CLIENT_PRIORITY_HIGH); -+ return (client->priority == GUC_CLIENT_PRIORITY_HIGH); -+} -+ -+static inline bool is_high_priority_kmd(struct intel_guc_client *client) -+{ -+ return (client->priority == GUC_CLIENT_PRIORITY_KMD_HIGH); - } - - static int reserve_doorbell(struct intel_guc_client *client) - { -+ struct drm_i915_private *dev_priv = guc_to_i915(client->guc); - unsigned long offset; - unsigned long end; - u16 id; -@@ -106,10 +111,14 @@ static int reserve_doorbell(struct intel_guc_client *client) - * priority contexts, the second half for high-priority ones. - */ - offset = 0; -- end = GUC_NUM_DOORBELLS / 2; -- if (is_high_priority(client)) { -- offset = end; -- end += offset; -+ if (IS_SKYLAKE(dev_priv) || IS_KABYLAKE(dev_priv)) { -+ end = GUC_NUM_DOORBELLS; -+ } else { -+ end = GUC_NUM_DOORBELLS/2; -+ if (is_high_priority(client)) { -+ offset = end; -+ end += offset; -+ } - } - - id = find_next_zero_bit(client->guc->doorbell_bitmap, end, offset); -@@ -355,9 +364,15 @@ static void guc_stage_desc_init(struct intel_guc *guc, - desc = __get_stage_desc(client); - memset(desc, 0, sizeof(*desc)); - -- desc->attribute = GUC_STAGE_DESC_ATTR_ACTIVE | -- GUC_STAGE_DESC_ATTR_KERNEL; -- if (is_high_priority(client)) -+ desc->attribute = GUC_STAGE_DESC_ATTR_ACTIVE; -+ if ((client->priority == GUC_CLIENT_PRIORITY_KMD_NORMAL) || -+ (client->priority == GUC_CLIENT_PRIORITY_KMD_HIGH)) { -+ desc->attribute |= GUC_STAGE_DESC_ATTR_KERNEL; -+ } else { -+ desc->attribute |= GUC_STAGE_DESC_ATTR_PCH; -+ } -+ -+ if (is_high_priority_kmd(client)) - desc->attribute |= GUC_STAGE_DESC_ATTR_PREEMPT; - desc->stage_id = client->stage_id; - desc->priority = client->priority; -@@ -1204,7 +1219,8 @@ static void guc_interrupts_capture(struct drm_i915_private *dev_priv) - I915_WRITE(RING_MODE_GEN7(engine), irqs); - - /* route USER_INTERRUPT to Host, all others are sent to GuC. */ -- irqs = GT_RENDER_USER_INTERRUPT << GEN8_RCS_IRQ_SHIFT | -+ irqs = (GT_RENDER_USER_INTERRUPT | GT_RENDER_PIPECTL_NOTIFY_INTERRUPT) -+ << GEN8_RCS_IRQ_SHIFT | - GT_RENDER_USER_INTERRUPT << GEN8_BCS_IRQ_SHIFT; - /* These three registers have the same bit definitions */ - I915_WRITE(GUC_BCS_RCS_IER, ~irqs); -@@ -1349,6 +1365,59 @@ void intel_guc_submission_disable(struct intel_guc *guc) - guc_clients_doorbell_fini(guc); - } - -+int i915_guc_ipts_submission_enable(struct drm_i915_private *dev_priv, -+ struct i915_gem_context *ctx) -+{ -+ struct intel_guc *guc = &dev_priv->guc; -+ struct intel_guc_client *client; -+ int err; -+ int ret; -+ -+ /* client for execbuf submission */ -+ client = guc_client_alloc(dev_priv, -+ INTEL_INFO(dev_priv)->ring_mask, -+ IS_SKYLAKE(dev_priv) || IS_KABYLAKE(dev_priv) ? GUC_CLIENT_PRIORITY_HIGH : GUC_CLIENT_PRIORITY_NORMAL, -+ ctx); -+ if (IS_ERR(client)) { -+ DRM_ERROR("Failed to create normal GuC client!\n"); -+ return -ENOMEM; -+ } -+ -+ guc->ipts_client = client; -+ -+ err = intel_guc_sample_forcewake(guc); -+ if (err) -+ return err; -+ -+ ret = create_doorbell(guc->ipts_client); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+void i915_guc_ipts_submission_disable(struct drm_i915_private *dev_priv) -+{ -+ struct intel_guc *guc = &dev_priv->guc; -+ -+ if (!guc->ipts_client) -+ return; -+ -+ destroy_doorbell(guc->ipts_client); -+ guc_client_free(guc->ipts_client); -+ guc->ipts_client = NULL; -+} -+ -+void i915_guc_ipts_reacquire_doorbell(struct drm_i915_private *dev_priv) -+{ -+ struct intel_guc *guc = &dev_priv->guc; -+ -+ int err = __guc_allocate_doorbell(guc, guc->ipts_client->stage_id); -+ -+ if (err) -+ DRM_ERROR("Not able to reacquire IPTS doorbell\n"); -+} -+ - #if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) - #include "selftests/intel_guc.c" - #endif -diff --git a/drivers/gpu/drm/i915/intel_guc_submission.h b/drivers/gpu/drm/i915/intel_guc_submission.h -index fb081cefef93..71fc7986585a 100644 ---- a/drivers/gpu/drm/i915/intel_guc_submission.h -+++ b/drivers/gpu/drm/i915/intel_guc_submission.h -@@ -79,5 +79,9 @@ void intel_guc_submission_disable(struct intel_guc *guc); - void intel_guc_submission_fini(struct intel_guc *guc); - int intel_guc_preempt_work_create(struct intel_guc *guc); - void intel_guc_preempt_work_destroy(struct intel_guc *guc); -+int i915_guc_ipts_submission_enable(struct drm_i915_private *dev_priv, -+ struct i915_gem_context *ctx); -+void i915_guc_ipts_submission_disable(struct drm_i915_private *dev_priv); -+void i915_guc_ipts_reacquire_doorbell(struct drm_i915_private *dev_priv); - - #endif -diff --git a/drivers/gpu/drm/i915/intel_ipts.c b/drivers/gpu/drm/i915/intel_ipts.c -new file mode 100644 -index 000000000000..c1199074924a ---- /dev/null -+++ b/drivers/gpu/drm/i915/intel_ipts.c -@@ -0,0 +1,650 @@ -+/* -+ * Copyright 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "intel_guc_submission.h" -+#include "i915_drv.h" -+ -+#define SUPPORTED_IPTS_INTERFACE_VERSION 1 -+ -+#define REACQUIRE_DB_THRESHOLD 10 -+ -+#define DB_LOST_CHECK_STEP1_INTERVAL 2500 // ms -+#define DB_LOST_CHECK_STEP2_INTERVAL 1000 // ms -+ -+// CTX for ipts support -+struct ipts { -+ struct drm_device *dev; -+ struct i915_gem_context *ipts_context; -+ struct ipts_callback ipts_clbks; -+ -+ // buffers' list -+ struct { -+ spinlock_t lock; -+ struct list_head list; -+ } buffers; -+ -+ void *data; -+ -+ struct delayed_work reacquire_db_work; -+ struct ipts_wq_info wq_info; -+ u32 old_tail; -+ u32 old_head; -+ bool need_reacquire_db; -+ -+ bool connected; -+ bool initialized; -+}; -+ -+struct ipts ipts; -+ -+struct ipts_object { -+ struct list_head list; -+ struct drm_i915_gem_object *gem_obj; -+ void *cpu_addr; -+}; -+ -+static struct ipts_object *ipts_object_create(size_t size, u32 flags) -+{ -+ struct drm_i915_private *dev_priv = to_i915(ipts.dev); -+ struct ipts_object *obj = NULL; -+ struct drm_i915_gem_object *gem_obj = NULL; -+ int ret = 0; -+ -+ obj = kzalloc(sizeof(*obj), GFP_KERNEL); -+ if (!obj) -+ return NULL; -+ -+ size = roundup(size, PAGE_SIZE); -+ if (size == 0) { -+ ret = -EINVAL; -+ goto err_out; -+ } -+ -+ // Allocate the new object -+ gem_obj = i915_gem_object_create(dev_priv, size); -+ if (gem_obj == NULL) { -+ ret = -ENOMEM; -+ goto err_out; -+ } -+ -+ if (flags & IPTS_BUF_FLAG_CONTIGUOUS) { -+ ret = i915_gem_object_attach_phys(gem_obj, PAGE_SIZE); -+ if (ret) { -+ pr_info(">> ipts no contiguous : %d\n", ret); -+ goto err_out; -+ } -+ } -+ -+ obj->gem_obj = gem_obj; -+ -+ spin_lock(&ipts.buffers.lock); -+ list_add_tail(&obj->list, &ipts.buffers.list); -+ spin_unlock(&ipts.buffers.lock); -+ -+ return obj; -+ -+err_out: -+ -+ if (gem_obj) -+ i915_gem_free_object(&gem_obj->base); -+ -+ kfree(obj); -+ -+ return NULL; -+} -+ -+static void ipts_object_free(struct ipts_object *obj) -+{ -+ spin_lock(&ipts.buffers.lock); -+ list_del(&obj->list); -+ spin_unlock(&ipts.buffers.lock); -+ -+ i915_gem_free_object(&obj->gem_obj->base); -+ kfree(obj); -+} -+ -+static int ipts_object_pin(struct ipts_object *obj, -+ struct i915_gem_context *ipts_ctx) -+{ -+ struct i915_address_space *vm = NULL; -+ struct i915_vma *vma = NULL; -+ struct drm_i915_private *dev_priv = to_i915(ipts.dev); -+ int ret = 0; -+ -+ if (ipts_ctx->ppgtt) -+ vm = &ipts_ctx->ppgtt->vm; -+ else -+ vm = &dev_priv->ggtt.vm; -+ -+ vma = i915_vma_instance(obj->gem_obj, vm, NULL); -+ if (IS_ERR(vma)) { -+ DRM_ERROR("cannot find or create vma\n"); -+ return -1; -+ } -+ -+ ret = i915_vma_pin(vma, 0, PAGE_SIZE, PIN_USER); -+ -+ return ret; -+} -+ -+static void ipts_object_unpin(struct ipts_object *obj) -+{ -+ // TODO: Add support -+} -+ -+static void *ipts_object_map(struct ipts_object *obj) -+{ -+ return i915_gem_object_pin_map(obj->gem_obj, I915_MAP_WB); -+} -+ -+static void ipts_object_unmap(struct ipts_object *obj) -+{ -+ i915_gem_object_unpin_map(obj->gem_obj); -+ obj->cpu_addr = NULL; -+} -+ -+static int create_ipts_context(void) -+{ -+ struct i915_gem_context *ipts_ctx = NULL; -+ struct drm_i915_private *dev_priv = to_i915(ipts.dev); -+ struct intel_context *ce = NULL; -+ struct intel_context *pin_ret; -+ int ret = 0; -+ -+ // Initialize the context right away. -+ ret = i915_mutex_lock_interruptible(ipts.dev); -+ if (ret) { -+ DRM_ERROR("i915_mutex_lock_interruptible failed\n"); -+ return ret; -+ } -+ -+ ipts_ctx = i915_gem_context_create_ipts(ipts.dev); -+ if (IS_ERR(ipts_ctx)) { -+ DRM_ERROR("Failed to create IPTS context (error %ld)\n", -+ PTR_ERR(ipts_ctx)); -+ ret = PTR_ERR(ipts_ctx); -+ goto err_unlock; -+ } -+ -+ ce = to_intel_context(ipts_ctx, dev_priv->engine[RCS]); -+ if (IS_ERR(ce)) { -+ DRM_ERROR("Failed to create intel context (error %ld)\n", -+ PTR_ERR(ce)); -+ ret = PTR_ERR(ce); -+ goto err_unlock; -+ } -+ -+ ret = execlists_context_deferred_alloc(ipts_ctx, dev_priv->engine[RCS], ce); -+ if (ret) { -+ DRM_DEBUG("lr context allocation failed: %d\n", ret); -+ goto err_ctx; -+ } -+ -+ pin_ret = execlists_context_pin(dev_priv->engine[RCS], ipts_ctx); -+ if (IS_ERR(pin_ret)) { -+ DRM_DEBUG("lr context pinning failed: %ld\n", PTR_ERR(pin_ret)); -+ goto err_ctx; -+ } -+ -+ // Release the mutex -+ mutex_unlock(&ipts.dev->struct_mutex); -+ -+ spin_lock_init(&ipts.buffers.lock); -+ INIT_LIST_HEAD(&ipts.buffers.list); -+ -+ ipts.ipts_context = ipts_ctx; -+ -+ return 0; -+ -+err_ctx: -+ if (ipts_ctx) -+ i915_gem_context_put(ipts_ctx); -+ -+err_unlock: -+ mutex_unlock(&ipts.dev->struct_mutex); -+ -+ return ret; -+} -+ -+static void destroy_ipts_context(void) -+{ -+ struct i915_gem_context *ipts_ctx = NULL; -+ struct drm_i915_private *dev_priv = to_i915(ipts.dev); -+ struct intel_context *ce = NULL; -+ int ret = 0; -+ -+ ipts_ctx = ipts.ipts_context; -+ -+ ce = to_intel_context(ipts_ctx, dev_priv->engine[RCS]); -+ -+ // Initialize the context right away. -+ ret = i915_mutex_lock_interruptible(ipts.dev); -+ if (ret) { -+ DRM_ERROR("i915_mutex_lock_interruptible failed\n"); -+ return; -+ } -+ -+ execlists_context_unpin(ce); -+ i915_gem_context_put(ipts_ctx); -+ -+ mutex_unlock(&ipts.dev->struct_mutex); -+} -+ -+int ipts_notify_complete(void) -+{ -+ if (ipts.ipts_clbks.workload_complete) -+ ipts.ipts_clbks.workload_complete(ipts.data); -+ -+ return 0; -+} -+ -+int ipts_notify_backlight_status(bool backlight_on) -+{ -+ if (ipts.ipts_clbks.notify_gfx_status) { -+ if (backlight_on) { -+ ipts.ipts_clbks.notify_gfx_status( -+ IPTS_NOTIFY_STA_BACKLIGHT_ON, ipts.data); -+ schedule_delayed_work(&ipts.reacquire_db_work, -+ msecs_to_jiffies(DB_LOST_CHECK_STEP1_INTERVAL)); -+ } else { -+ ipts.ipts_clbks.notify_gfx_status( -+ IPTS_NOTIFY_STA_BACKLIGHT_OFF, ipts.data); -+ cancel_delayed_work(&ipts.reacquire_db_work); -+ } -+ } -+ -+ return 0; -+} -+ -+static void ipts_reacquire_db(struct ipts *ipts_p) -+{ -+ int ret = 0; -+ -+ ret = i915_mutex_lock_interruptible(ipts_p->dev); -+ if (ret) { -+ DRM_ERROR("i915_mutex_lock_interruptible failed\n"); -+ return; -+ } -+ -+ // Reacquire the doorbell -+ i915_guc_ipts_reacquire_doorbell(ipts_p->dev->dev_private); -+ -+ mutex_unlock(&ipts_p->dev->struct_mutex); -+} -+ -+static int ipts_get_wq_info(uint64_t gfx_handle, -+ struct ipts_wq_info *wq_info) -+{ -+ if (gfx_handle != (uint64_t)&ipts) { -+ DRM_ERROR("invalid gfx handle\n"); -+ return -EINVAL; -+ } -+ -+ *wq_info = ipts.wq_info; -+ -+ ipts_reacquire_db(&ipts); -+ schedule_delayed_work(&ipts.reacquire_db_work, -+ msecs_to_jiffies(DB_LOST_CHECK_STEP1_INTERVAL)); -+ -+ return 0; -+} -+ -+static int set_wq_info(void) -+{ -+ struct drm_i915_private *dev_priv = to_i915(ipts.dev); -+ struct intel_guc *guc = &dev_priv->guc; -+ struct intel_guc_client *client; -+ struct guc_process_desc *desc; -+ struct ipts_wq_info *wq_info; -+ void *base = NULL; -+ u64 phy_base = 0; -+ -+ wq_info = &ipts.wq_info; -+ -+ client = guc->ipts_client; -+ if (!client) { -+ DRM_ERROR("IPTS GuC client is NOT available\n"); -+ return -EINVAL; -+ } -+ -+ base = client->vaddr; -+ desc = (struct guc_process_desc *) -+ ((u64)base + client->proc_desc_offset); -+ -+ desc->wq_base_addr = (u64)base + GUC_DB_SIZE; -+ desc->db_base_addr = (u64)base + client->doorbell_offset; -+ -+ // IPTS expects physical addresses to pass it to ME -+ phy_base = sg_dma_address(client->vma->pages->sgl); -+ -+ wq_info->db_addr = desc->db_base_addr; -+ wq_info->db_phy_addr = phy_base + client->doorbell_offset; -+ wq_info->db_cookie_offset = offsetof(struct guc_doorbell_info, cookie); -+ wq_info->wq_addr = desc->wq_base_addr; -+ wq_info->wq_phy_addr = phy_base + GUC_DB_SIZE; -+ wq_info->wq_head_addr = (u64)&desc->head; -+ wq_info->wq_tail_addr = (u64)&desc->tail; -+ wq_info->wq_size = desc->wq_size_bytes; -+ -+ wq_info->wq_head_phy_addr = phy_base + client->proc_desc_offset + -+ offsetof(struct guc_process_desc, head); -+ -+ wq_info->wq_tail_phy_addr = phy_base + client->proc_desc_offset + -+ offsetof(struct guc_process_desc, tail); -+ -+ return 0; -+} -+ -+static int ipts_init_wq(void) -+{ -+ int ret = 0; -+ -+ ret = i915_mutex_lock_interruptible(ipts.dev); -+ if (ret) { -+ DRM_ERROR("i915_mutex_lock_interruptible failed\n"); -+ return ret; -+ } -+ -+ // disable IPTS submission -+ i915_guc_ipts_submission_disable(ipts.dev->dev_private); -+ -+ // enable IPTS submission -+ ret = i915_guc_ipts_submission_enable(ipts.dev->dev_private, -+ ipts.ipts_context); -+ if (ret) { -+ DRM_ERROR("i915_guc_ipts_submission_enable failed: %d\n", ret); -+ goto out; -+ } -+ -+ ret = set_wq_info(); -+ if (ret) { -+ DRM_ERROR("set_wq_info failed\n"); -+ goto out; -+ } -+ -+out: -+ mutex_unlock(&ipts.dev->struct_mutex); -+ -+ return ret; -+} -+ -+static void ipts_release_wq(void) -+{ -+ int ret = 0; -+ -+ ret = i915_mutex_lock_interruptible(ipts.dev); -+ if (ret) { -+ DRM_ERROR("i915_mutex_lock_interruptible failed\n"); -+ return; -+ } -+ -+ // disable IPTS submission -+ i915_guc_ipts_submission_disable(ipts.dev->dev_private); -+ -+ mutex_unlock(&ipts.dev->struct_mutex); -+} -+ -+static int ipts_map_buffer(u64 gfx_handle, struct ipts_mapbuffer *mapbuf) -+{ -+ struct ipts_object *obj; -+ struct i915_gem_context *ipts_ctx = NULL; -+ struct drm_i915_private *dev_priv = to_i915(ipts.dev); -+ struct i915_address_space *vm = NULL; -+ struct i915_vma *vma = NULL; -+ int ret = 0; -+ -+ if (gfx_handle != (uint64_t)&ipts) { -+ DRM_ERROR("invalid gfx handle\n"); -+ return -EINVAL; -+ } -+ -+ // Acquire mutex first -+ ret = i915_mutex_lock_interruptible(ipts.dev); -+ if (ret) { -+ DRM_ERROR("i915_mutex_lock_interruptible failed\n"); -+ return -EINVAL; -+ } -+ -+ obj = ipts_object_create(mapbuf->size, mapbuf->flags); -+ if (!obj) -+ return -ENOMEM; -+ -+ ipts_ctx = ipts.ipts_context; -+ ret = ipts_object_pin(obj, ipts_ctx); -+ if (ret) { -+ DRM_ERROR("Not able to pin iTouch obj\n"); -+ ipts_object_free(obj); -+ mutex_unlock(&ipts.dev->struct_mutex); -+ return -ENOMEM; -+ } -+ -+ if (mapbuf->flags & IPTS_BUF_FLAG_CONTIGUOUS) -+ obj->cpu_addr = obj->gem_obj->phys_handle->vaddr; -+ else -+ obj->cpu_addr = ipts_object_map(obj); -+ -+ if (ipts_ctx->ppgtt) -+ vm = &ipts_ctx->ppgtt->vm; -+ else -+ vm = &dev_priv->ggtt.vm; -+ -+ vma = i915_vma_instance(obj->gem_obj, vm, NULL); -+ if (IS_ERR(vma)) { -+ DRM_ERROR("cannot find or create vma\n"); -+ return -EINVAL; -+ } -+ -+ mapbuf->gfx_addr = (void *)vma->node.start; -+ mapbuf->cpu_addr = (void *)obj->cpu_addr; -+ mapbuf->buf_handle = (u64)obj; -+ if (mapbuf->flags & IPTS_BUF_FLAG_CONTIGUOUS) -+ mapbuf->phy_addr = (u64)obj->gem_obj->phys_handle->busaddr; -+ -+ // Release the mutex -+ mutex_unlock(&ipts.dev->struct_mutex); -+ -+ return 0; -+} -+ -+static int ipts_unmap_buffer(uint64_t gfx_handle, uint64_t buf_handle) -+{ -+ struct ipts_object *obj = (struct ipts_object *)buf_handle; -+ -+ if (gfx_handle != (uint64_t)&ipts) { -+ DRM_ERROR("invalid gfx handle\n"); -+ return -EINVAL; -+ } -+ -+ if (!obj->gem_obj->phys_handle) -+ ipts_object_unmap(obj); -+ -+ ipts_object_unpin(obj); -+ ipts_object_free(obj); -+ -+ return 0; -+} -+ -+int ipts_connect(struct ipts_connect *ipts_connect) -+{ -+ u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; -+ struct drm_i915_private *dev_priv = to_i915(ipts.dev); -+ -+ if (!ipts.initialized) -+ return -EIO; -+ -+ if (!ipts_connect) -+ return -EINVAL; -+ -+ if (ipts_connect->if_version > SUPPORTED_IPTS_INTERFACE_VERSION) -+ return -EINVAL; -+ -+ // set up device-link for PM -+ if (!device_link_add(ipts_connect->client, ipts.dev->dev, flags)) -+ return -EFAULT; -+ -+ // return gpu operations for ipts -+ ipts_connect->ipts_ops.get_wq_info = ipts_get_wq_info; -+ ipts_connect->ipts_ops.map_buffer = ipts_map_buffer; -+ ipts_connect->ipts_ops.unmap_buffer = ipts_unmap_buffer; -+ ipts_connect->gfx_version = INTEL_INFO(dev_priv)->gen; -+ ipts_connect->gfx_handle = (uint64_t)&ipts; -+ -+ // save callback and data -+ ipts.data = ipts_connect->data; -+ ipts.ipts_clbks = ipts_connect->ipts_cb; -+ -+ ipts.connected = true; -+ -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ipts_connect); -+ -+void ipts_disconnect(uint64_t gfx_handle) -+{ -+ if (!ipts.initialized) -+ return; -+ -+ if (gfx_handle != (uint64_t)&ipts || !ipts.connected) { -+ DRM_ERROR("invalid gfx handle\n"); -+ return; -+ } -+ -+ ipts.data = 0; -+ memset(&ipts.ipts_clbks, 0, sizeof(struct ipts_callback)); -+ -+ ipts.connected = false; -+} -+EXPORT_SYMBOL_GPL(ipts_disconnect); -+ -+static void reacquire_db_work_func(struct work_struct *work) -+{ -+ struct delayed_work *d_work = container_of(work, -+ struct delayed_work, work); -+ struct ipts *ipts_p = container_of(d_work, -+ struct ipts, reacquire_db_work); -+ u32 head; -+ u32 tail; -+ u32 size; -+ u32 load; -+ -+ head = *(u32 *)ipts_p->wq_info.wq_head_addr; -+ tail = *(u32 *)ipts_p->wq_info.wq_tail_addr; -+ size = ipts_p->wq_info.wq_size; -+ -+ if (head >= tail) -+ load = head - tail; -+ else -+ load = head + size - tail; -+ -+ if (load < REACQUIRE_DB_THRESHOLD) { -+ ipts_p->need_reacquire_db = false; -+ goto reschedule_work; -+ } -+ -+ if (ipts_p->need_reacquire_db) { -+ if (ipts_p->old_head == head && -+ ipts_p->old_tail == tail) -+ ipts_reacquire_db(ipts_p); -+ ipts_p->need_reacquire_db = false; -+ } else { -+ ipts_p->old_head = head; -+ ipts_p->old_tail = tail; -+ ipts_p->need_reacquire_db = true; -+ -+ // recheck -+ schedule_delayed_work(&ipts_p->reacquire_db_work, -+ msecs_to_jiffies(DB_LOST_CHECK_STEP2_INTERVAL)); -+ return; -+ } -+ -+reschedule_work: -+ schedule_delayed_work(&ipts_p->reacquire_db_work, -+ msecs_to_jiffies(DB_LOST_CHECK_STEP1_INTERVAL)); -+} -+ -+/** -+ * ipts_init - Initialize ipts support -+ * @dev: drm device -+ * -+ * Setup the required structures for ipts. -+ */ -+int ipts_init(struct drm_device *dev) -+{ -+ int ret = 0; -+ -+ pr_info("ipts: initializing ipts\n"); -+ -+ ipts.dev = dev; -+ INIT_DELAYED_WORK(&ipts.reacquire_db_work, -+ reacquire_db_work_func); -+ -+ ret = create_ipts_context(); -+ if (ret) -+ return -ENOMEM; -+ -+ ret = ipts_init_wq(); -+ if (ret) -+ return ret; -+ -+ ipts.initialized = true; -+ pr_info("ipts: Intel iTouch framework initialized\n"); -+ -+ return ret; -+} -+ -+void ipts_cleanup(struct drm_device *dev) -+{ -+ struct ipts_object *obj, *n; -+ -+ if (ipts.dev != dev) -+ return; -+ -+ list_for_each_entry_safe(obj, n, &ipts.buffers.list, list) { -+ struct i915_vma *vma, *vn; -+ -+ list_for_each_entry_safe(vma, vn, &obj->list, obj_link) { -+ vma->flags &= ~I915_VMA_PIN_MASK; -+ i915_vma_destroy(vma); -+ } -+ -+ list_del(&obj->list); -+ -+ if (!obj->gem_obj->phys_handle) -+ ipts_object_unmap(obj); -+ -+ ipts_object_unpin(obj); -+ i915_gem_free_object(&obj->gem_obj->base); -+ kfree(obj); -+ } -+ -+ ipts_release_wq(); -+ destroy_ipts_context(); -+ cancel_delayed_work(&ipts.reacquire_db_work); -+} -diff --git a/drivers/gpu/drm/i915/intel_ipts.h b/drivers/gpu/drm/i915/intel_ipts.h -new file mode 100644 -index 000000000000..67f90b72f237 ---- /dev/null -+++ b/drivers/gpu/drm/i915/intel_ipts.h -@@ -0,0 +1,34 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+#ifndef _INTEL_IPTS_H_ -+#define _INTEL_IPTS_H_ -+ -+#include -+ -+int ipts_init(struct drm_device *dev); -+void ipts_cleanup(struct drm_device *dev); -+int ipts_notify_backlight_status(bool backlight_on); -+int ipts_notify_complete(void); -+ -+#endif //_INTEL_IPTS_H_ -diff --git a/drivers/gpu/drm/i915/intel_lrc.c b/drivers/gpu/drm/i915/intel_lrc.c -index 13e97faabaa7..a4af67d3d6ff 100644 ---- a/drivers/gpu/drm/i915/intel_lrc.c -+++ b/drivers/gpu/drm/i915/intel_lrc.c -@@ -164,9 +164,6 @@ - #define WA_TAIL_DWORDS 2 - #define WA_TAIL_BYTES (sizeof(u32) * WA_TAIL_DWORDS) - --static int execlists_context_deferred_alloc(struct i915_gem_context *ctx, -- struct intel_engine_cs *engine, -- struct intel_context *ce); - static void execlists_init_reg_state(u32 *reg_state, - struct i915_gem_context *ctx, - struct intel_engine_cs *engine, -@@ -1292,7 +1289,7 @@ static void execlists_context_destroy(struct intel_context *ce) - i915_gem_object_put(ce->state->obj); - } - --static void execlists_context_unpin(struct intel_context *ce) -+void execlists_context_unpin(struct intel_context *ce) - { - intel_ring_unpin(ce->ring); - -@@ -1379,7 +1376,7 @@ static const struct intel_context_ops execlists_context_ops = { - .destroy = execlists_context_destroy, - }; - --static struct intel_context * -+struct intel_context * - execlists_context_pin(struct intel_engine_cs *engine, - struct i915_gem_context *ctx) - { -@@ -2479,6 +2476,9 @@ int logical_render_ring_init(struct intel_engine_cs *engine) - - logical_ring_setup(engine); - -+ engine->irq_keep_mask |= GT_RENDER_PIPECTL_NOTIFY_INTERRUPT -+ << GEN8_RCS_IRQ_SHIFT; -+ - if (HAS_L3_DPF(dev_priv)) - engine->irq_keep_mask |= GT_RENDER_L3_PARITY_ERROR_INTERRUPT; - -@@ -2743,7 +2743,7 @@ populate_lr_context(struct i915_gem_context *ctx, - return ret; - } - --static int execlists_context_deferred_alloc(struct i915_gem_context *ctx, -+int execlists_context_deferred_alloc(struct i915_gem_context *ctx, - struct intel_engine_cs *engine, - struct intel_context *ce) - { -diff --git a/drivers/gpu/drm/i915/intel_lrc.h b/drivers/gpu/drm/i915/intel_lrc.h -index 4dfb78e3ec7e..32159231a16e 100644 ---- a/drivers/gpu/drm/i915/intel_lrc.h -+++ b/drivers/gpu/drm/i915/intel_lrc.h -@@ -106,4 +106,12 @@ void intel_lr_context_resume(struct drm_i915_private *dev_priv); - - void intel_execlists_set_default_submission(struct intel_engine_cs *engine); - -+struct intel_context * -+execlists_context_pin(struct intel_engine_cs *engine, -+ struct i915_gem_context *ctx); -+void execlists_context_unpin(struct intel_context *ce); -+int execlists_context_deferred_alloc(struct i915_gem_context *ctx, -+ struct intel_engine_cs *engine, -+ struct intel_context *ce); -+ - #endif /* _INTEL_LRC_H_ */ -diff --git a/drivers/gpu/drm/i915/intel_panel.c b/drivers/gpu/drm/i915/intel_panel.c -index 4a9f139e7b73..c137a57f6702 100644 ---- a/drivers/gpu/drm/i915/intel_panel.c -+++ b/drivers/gpu/drm/i915/intel_panel.c -@@ -34,6 +34,7 @@ - #include - #include - #include "intel_drv.h" -+#include "intel_ipts.h" - - #define CRC_PMIC_PWM_PERIOD_NS 21333 - -@@ -659,6 +660,9 @@ static void lpt_disable_backlight(const struct drm_connector_state *old_conn_sta - struct drm_i915_private *dev_priv = to_i915(connector->base.dev); - u32 tmp; - -+ if (INTEL_GEN(dev_priv) >= 9 && i915_modparams.enable_guc && i915_modparams.enable_ipts) -+ ipts_notify_backlight_status(false); -+ - intel_panel_actually_set_backlight(old_conn_state, 0); - - /* -@@ -846,6 +850,9 @@ static void lpt_enable_backlight(const struct intel_crtc_state *crtc_state, - - /* This won't stick until the above enable. */ - intel_panel_actually_set_backlight(conn_state, panel->backlight.level); -+ -+ if (INTEL_GEN(dev_priv) >= 9 && i915_modparams.enable_guc && i915_modparams.enable_ipts) -+ ipts_notify_backlight_status(true); - } - - static void pch_enable_backlight(const struct intel_crtc_state *crtc_state, -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index 3726eacdf65d..77263b5f5915 100644 ---- a/drivers/misc/Kconfig -+++ b/drivers/misc/Kconfig -@@ -520,6 +520,7 @@ source "drivers/misc/ti-st/Kconfig" - source "drivers/misc/lis3lv02d/Kconfig" - source "drivers/misc/altera-stapl/Kconfig" - source "drivers/misc/mei/Kconfig" -+source "drivers/misc/ipts/Kconfig" - source "drivers/misc/vmw_vmci/Kconfig" - source "drivers/misc/mic/Kconfig" - source "drivers/misc/genwqe/Kconfig" -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index af22bbc3d00c..eb1eb0d58c32 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -44,6 +44,7 @@ obj-y += lis3lv02d/ - obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o - obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/ - obj-$(CONFIG_INTEL_MEI) += mei/ -+obj-$(CONFIG_INTEL_IPTS) += ipts/ - obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/ - obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o - obj-$(CONFIG_SRAM) += sram.o -diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig -new file mode 100644 -index 000000000000..900d2c58ca74 ---- /dev/null -+++ b/drivers/misc/ipts/Kconfig -@@ -0,0 +1,12 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+config INTEL_IPTS -+ tristate "Intel Precise Touch & Stylus" -+ select INTEL_MEI -+ depends on X86 && PCI && HID && DRM_I915 -+ help -+ Intel Precise Touch & Stylus support -+ Supported SoCs: -+ Intel Skylake -+ Intel Kabylake -+ -+source "drivers/misc/ipts/companion/Kconfig" -diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile -new file mode 100644 -index 000000000000..bb3982f48afc ---- /dev/null -+++ b/drivers/misc/ipts/Makefile -@@ -0,0 +1,19 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+# -+# Makefile - Intel Precise Touch & Stylus device driver -+# Copyright (c) 2016 Intel Corporation -+# -+ -+obj-$(CONFIG_INTEL_IPTS)+= intel-ipts.o -+intel-ipts-objs += companion.o -+intel-ipts-objs += ipts.o -+intel-ipts-objs += mei.o -+intel-ipts-objs += hid.o -+intel-ipts-objs += msg-handler.o -+intel-ipts-objs += kernel.o -+intel-ipts-objs += params.o -+intel-ipts-objs += resource.o -+intel-ipts-objs += gfx.o -+intel-ipts-$(CONFIG_DEBUG_FS) += dbgfs.o -+ -+obj-y += companion/ -diff --git a/drivers/misc/ipts/companion.c b/drivers/misc/ipts/companion.c -new file mode 100644 -index 000000000000..8f66b852f137 ---- /dev/null -+++ b/drivers/misc/ipts/companion.c -@@ -0,0 +1,211 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "companion.h" -+#include "ipts.h" -+#include "params.h" -+ -+#define IPTS_FW_PATH_FMT "intel/ipts/%s" -+#define IPTS_FW_CONFIG_FILE "ipts_fw_config.bin" -+ -+struct ipts_companion *ipts_companion; -+DEFINE_MUTEX(ipts_companion_lock); -+ -+bool ipts_companion_available(void) -+{ -+ bool ret; -+ -+ mutex_lock(&ipts_companion_lock); -+ -+ ret = ipts_companion != NULL; -+ -+ mutex_unlock(&ipts_companion_lock); -+ -+ return ret; -+} -+ -+/* -+ * General purpose API for adding or removing a companion driver -+ * A companion driver is a driver that implements hardware specific -+ * behaviour into IPTS, so it doesn't have to be hardcoded into the -+ * main driver. All requests to the companion driver should be wrapped, -+ * with a fallback in case a companion driver cannot be found. -+ */ -+ -+int ipts_add_companion(struct ipts_companion *companion) -+{ -+ int ret; -+ -+ // Make sure that access to the companion is synchronized -+ mutex_lock(&ipts_companion_lock); -+ -+ if (ipts_companion == NULL) { -+ ret = 0; -+ ipts_companion = companion; -+ } else { -+ ret = -EBUSY; -+ } -+ -+ mutex_unlock(&ipts_companion_lock); -+ -+ return ret; -+} -+EXPORT_SYMBOL_GPL(ipts_add_companion); -+ -+int ipts_remove_companion(struct ipts_companion *companion) -+{ -+ int ret; -+ -+ // Make sure that access to the companion is synchronized -+ mutex_lock(&ipts_companion_lock); -+ -+ if (ipts_companion != NULL && companion != NULL && -+ ipts_companion->name != companion->name) { -+ ret = -EPERM; -+ } else { -+ ret = 0; -+ ipts_companion = NULL; -+ } -+ -+ mutex_unlock(&ipts_companion_lock); -+ return ret; -+} -+EXPORT_SYMBOL_GPL(ipts_remove_companion); -+ -+/* -+ * Utility functions for IPTS. These functions replace codepaths in the IPTS -+ * driver, and redirect them to the companion driver, if one was found. -+ * Otherwise the legacy code gets executed as a fallback. -+ */ -+ -+int ipts_request_firmware(const struct firmware **fw, const char *name, -+ struct device *device) -+{ -+ int ret = 0; -+ char fw_path[MAX_IOCL_FILE_PATH_LEN]; -+ -+ // Make sure that access to the companion is synchronized -+ mutex_lock(&ipts_companion_lock); -+ -+ // Check if a companion was registered. If not, skip -+ // forward and try to load the firmware from the legacy path -+ if (ipts_companion == NULL || ipts_modparams.ignore_companion) -+ goto request_firmware_fallback; -+ -+ ret = ipts_companion->firmware_request(ipts_companion, fw, -+ name, device); -+ if (!ret) -+ goto request_firmware_return; -+ -+request_firmware_fallback: -+ -+ // If fallback loading for firmware was disabled, abort. -+ // Return -ENOENT as no firmware file was found. -+ if (ipts_modparams.ignore_fw_fallback) { -+ ret = -ENOENT; -+ goto request_firmware_return; -+ } -+ -+ // No firmware was found by the companion driver, try the generic path. -+ snprintf(fw_path, MAX_IOCL_FILE_PATH_LEN, IPTS_FW_PATH_FMT, name); -+ ret = request_firmware(fw, fw_path, device); -+ -+request_firmware_return: -+ -+ mutex_unlock(&ipts_companion_lock); -+ -+ return ret; -+} -+ -+static struct ipts_bin_fw_list *ipts_alloc_fw_list( -+ struct ipts_bin_fw_info **fw) -+{ -+ int size, len, i, j; -+ struct ipts_bin_fw_list *fw_list; -+ char *itr; -+ -+ // Figure out the amount of firmware files inside of the array -+ len = 0; -+ while (fw[len] != NULL) -+ len++; -+ -+ // Determine the size that the final list will need in memory -+ size = sizeof(struct ipts_bin_fw_list); -+ for (i = 0; i < len; i++) { -+ size += sizeof(struct ipts_bin_fw_info); -+ size += sizeof(struct ipts_bin_data_file_info) * -+ fw[i]->num_of_data_files; -+ } -+ -+ fw_list = kmalloc(size, GFP_KERNEL); -+ fw_list->num_of_fws = len; -+ -+ itr = (char *)fw_list->fw_info; -+ for (i = 0; i < len; i++) { -+ *(struct ipts_bin_fw_info *)itr = *fw[i]; -+ -+ itr += sizeof(struct ipts_bin_fw_info); -+ -+ for (j = 0; j < fw[i]->num_of_data_files; j++) { -+ *(struct ipts_bin_data_file_info *)itr = -+ fw[i]->data_file[j]; -+ -+ itr += sizeof(struct ipts_bin_data_file_info); -+ } -+ } -+ -+ return fw_list; -+} -+ -+int ipts_request_firmware_config(struct ipts_info *ipts, -+ struct ipts_bin_fw_list **cfg) -+{ -+ int ret; -+ const struct firmware *config_fw = NULL; -+ -+ // Make sure that access to the companion is synchronized -+ mutex_lock(&ipts_companion_lock); -+ -+ // Check if a companion was registered. If not, skip -+ // forward and try to load the firmware config from a file -+ if (ipts_modparams.ignore_companion || ipts_companion == NULL) { -+ mutex_unlock(&ipts_companion_lock); -+ goto config_fallback; -+ } -+ -+ if (ipts_companion->firmware_config != NULL) { -+ *cfg = ipts_alloc_fw_list(ipts_companion->firmware_config); -+ mutex_unlock(&ipts_companion_lock); -+ return 0; -+ } -+ -+config_fallback: -+ -+ // If fallback loading for the firmware config was disabled, abort. -+ // Return -ENOENT as no config file was found. -+ if (ipts_modparams.ignore_config_fallback) -+ return -ENOENT; -+ -+ // No firmware config was found by the companion driver, -+ // try loading it from a file now -+ ret = ipts_request_firmware(&config_fw, IPTS_FW_CONFIG_FILE, -+ &ipts->cldev->dev); -+ if (!ret) -+ *cfg = (struct ipts_bin_fw_list *)config_fw->data; -+ else -+ release_firmware(config_fw); -+ -+ return ret; -+ -+} -diff --git a/drivers/misc/ipts/companion.h b/drivers/misc/ipts/companion.h -new file mode 100644 -index 000000000000..7a1e4b388c40 ---- /dev/null -+++ b/drivers/misc/ipts/companion.h -@@ -0,0 +1,25 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#ifndef _IPTS_COMPANION_H_ -+#define _IPTS_COMPANION_H_ -+ -+#include -+#include -+ -+#include "ipts.h" -+ -+bool ipts_companion_available(void); -+ -+int ipts_request_firmware(const struct firmware **fw, const char *name, -+ struct device *device); -+ -+int ipts_request_firmware_config(struct ipts_info *ipts, -+ struct ipts_bin_fw_list **firmware_config); -+ -+#endif // _IPTS_COMPANION_H_ -diff --git a/drivers/misc/ipts/companion/Kconfig b/drivers/misc/ipts/companion/Kconfig -new file mode 100644 -index 000000000000..ef17d9bb5242 ---- /dev/null -+++ b/drivers/misc/ipts/companion/Kconfig -@@ -0,0 +1,8 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+config INTEL_IPTS_SURFACE -+ tristate "IPTS companion driver for Microsoft Surface" -+ depends on INTEL_IPTS && ACPI -+ help -+ IPTS companion driver for Microsoft Surface. This driver is -+ responsible for loading firmware using surface-specific hardware IDs. -+ If you have a Microsoft Surface using IPTS, select y or m here. -diff --git a/drivers/misc/ipts/companion/Makefile b/drivers/misc/ipts/companion/Makefile -new file mode 100644 -index 000000000000..b37f2f59937a ---- /dev/null -+++ b/drivers/misc/ipts/companion/Makefile -@@ -0,0 +1,2 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+obj-$(CONFIG_INTEL_IPTS_SURFACE)+= ipts-surface.o -diff --git a/drivers/misc/ipts/companion/ipts-surface.c b/drivers/misc/ipts/companion/ipts-surface.c -new file mode 100644 -index 000000000000..a717dfcdfeba ---- /dev/null -+++ b/drivers/misc/ipts/companion/ipts-surface.c -@@ -0,0 +1,157 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2019 Dorian Stoll -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define IPTS_SURFACE_FW_PATH_FMT "intel/ipts/%s/%s" -+ -+/* -+ * checkpatch complains about this and wants it wrapped with do { } while(0); -+ * Since this would absolutely not work, just ignore checkpatch in this case. -+ */ -+#define IPTS_SURFACE_FIRMWARE(X) \ -+ MODULE_FIRMWARE("intel/ipts/" X "/config.bin"); \ -+ MODULE_FIRMWARE("intel/ipts/" X "/intel_desc.bin"); \ -+ MODULE_FIRMWARE("intel/ipts/" X "/vendor_desc.bin"); \ -+ MODULE_FIRMWARE("intel/ipts/" X "/vendor_kernel.bin") -+ -+/* -+ * Checkpatch complains about the following lines because it sees them as -+ * header files mixed with .c files. However, forward declaration is perfectly -+ * fine in C, and this allows us to seperate the companion data from the -+ * functions for the companion. -+ */ -+int ipts_surface_request_firmware(struct ipts_companion *companion, -+ const struct firmware **fw, const char *name, -+ struct device *device); -+ -+unsigned int ipts_surface_get_quirks(struct ipts_companion *companion); -+ -+static struct ipts_bin_fw_info ipts_surface_vendor_kernel = { -+ .fw_name = "vendor_kernel.bin", -+ .vendor_output = -1, -+ .num_of_data_files = 3, -+ .data_file = { -+ { -+ .io_buffer_type = IPTS_CONFIGURATION, -+ .flags = IPTS_DATA_FILE_FLAG_NONE, -+ .file_name = "config.bin", -+ }, -+ -+ // The following files are part of the config, but they don't -+ // exist, and the driver never requests them. -+ { -+ .io_buffer_type = IPTS_CALIBRATION, -+ .flags = IPTS_DATA_FILE_FLAG_NONE, -+ .file_name = "calib.bin", -+ }, -+ { -+ .io_buffer_type = IPTS_FEATURE, -+ .flags = IPTS_DATA_FILE_FLAG_SHARE, -+ .file_name = "feature.bin", -+ }, -+ }, -+}; -+ -+static struct ipts_bin_fw_info *ipts_surface_fw_config[] = { -+ &ipts_surface_vendor_kernel, -+ NULL, -+}; -+ -+static struct ipts_companion ipts_surface_companion = { -+ .firmware_request = &ipts_surface_request_firmware, -+ .firmware_config = ipts_surface_fw_config, -+ .name = "ipts_surface", -+}; -+ -+int ipts_surface_request_firmware(struct ipts_companion *companion, -+ const struct firmware **fw, const char *name, -+ struct device *device) -+{ -+ char fw_path[MAX_IOCL_FILE_PATH_LEN]; -+ -+ if (companion == NULL || companion->data == NULL) -+ return -ENOENT; -+ -+ snprintf(fw_path, MAX_IOCL_FILE_PATH_LEN, IPTS_SURFACE_FW_PATH_FMT, -+ (const char *)companion->data, name); -+ return request_firmware(fw, fw_path, device); -+} -+ -+static int ipts_surface_probe(struct platform_device *pdev) -+{ -+ int r; -+ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); -+ -+ if (!adev) { -+ dev_err(&pdev->dev, "Unable to find ACPI info for device\n"); -+ return -ENODEV; -+ } -+ -+ ipts_surface_companion.data = (void *)acpi_device_hid(adev); -+ -+ r = ipts_add_companion(&ipts_surface_companion); -+ if (r) { -+ dev_warn(&pdev->dev, "Adding IPTS companion failed: %d\n", r); -+ return r; -+ } -+ -+ return 0; -+} -+ -+static int ipts_surface_remove(struct platform_device *pdev) -+{ -+ int r = ipts_remove_companion(&ipts_surface_companion); -+ -+ if (r) { -+ dev_warn(&pdev->dev, "Removing IPTS companion failed: %d\n", r); -+ return r; -+ } -+ -+ return 0; -+} -+ -+static const struct acpi_device_id ipts_surface_acpi_match[] = { -+ { "MSHW0076", 0 }, // Surface Book 1 / Surface Studio -+ { "MSHW0078", 0 }, // some Surface Pro 4 -+ { "MSHW0079", 0 }, // Surface Laptop 1 / 2 -+ { "MSHW0101", 0 }, // Surface Book 2 15" -+ { "MSHW0102", 0 }, // Surface Pro 5 / 6 -+ { "MSHW0103", 0 }, // some Surface Pro 4 -+ { "MSHW0137", 0 }, // Surface Book 2 -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, ipts_surface_acpi_match); -+ -+static struct platform_driver ipts_surface_driver = { -+ .probe = ipts_surface_probe, -+ .remove = ipts_surface_remove, -+ .driver = { -+ .name = "ipts_surface", -+ .acpi_match_table = ACPI_PTR(ipts_surface_acpi_match), -+ }, -+}; -+module_platform_driver(ipts_surface_driver); -+ -+MODULE_AUTHOR("Dorian Stoll "); -+MODULE_DESCRIPTION("IPTS companion driver for Microsoft Surface"); -+MODULE_LICENSE("GPL v2"); -+ -+IPTS_SURFACE_FIRMWARE("MSHW0076"); -+IPTS_SURFACE_FIRMWARE("MSHW0078"); -+IPTS_SURFACE_FIRMWARE("MSHW0079"); -+IPTS_SURFACE_FIRMWARE("MSHW0101"); -+IPTS_SURFACE_FIRMWARE("MSHW0102"); -+IPTS_SURFACE_FIRMWARE("MSHW0103"); -+IPTS_SURFACE_FIRMWARE("MSHW0137"); -diff --git a/drivers/misc/ipts/dbgfs.c b/drivers/misc/ipts/dbgfs.c -new file mode 100644 -index 000000000000..fd9388de17e7 ---- /dev/null -+++ b/drivers/misc/ipts/dbgfs.c -@@ -0,0 +1,277 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#include -+#include -+#include -+ -+#include "ipts.h" -+#include "msg-handler.h" -+#include "sensor-regs.h" -+#include "state.h" -+#include "../mei/mei_dev.h" -+ -+static const char ipts_status_fmt[] = "ipts state : %01d\n"; -+static const char ipts_debug_fmt[] = ">> tdt : fw status : %s\n" -+ ">> == Doorbell status:%x, count:%x ==\n" -+ ">> == Workqueue head:%u, tail:%u ==\n"; -+ -+static ssize_t ipts_dbgfs_status_read(struct file *fp, char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ struct ipts_info *ipts = fp->private_data; -+ char status[256]; -+ int len = 0; -+ -+ if (cnt < sizeof(ipts_status_fmt) - 3) -+ return -EINVAL; -+ -+ len = scnprintf(status, 256, ipts_status_fmt, ipts->state); -+ if (len < 0) -+ return -EIO; -+ -+ return simple_read_from_buffer(ubuf, cnt, ppos, status, len); -+} -+ -+static const struct file_operations ipts_status_dbgfs_fops = { -+ .open = simple_open, -+ .read = ipts_dbgfs_status_read, -+ .llseek = generic_file_llseek, -+}; -+ -+static ssize_t ipts_dbgfs_quiesce_io_cmd_write(struct file *fp, -+ const char __user *ubuf, size_t cnt, loff_t *ppos) -+{ -+ struct ipts_info *ipts = fp->private_data; -+ bool result; -+ int rc; -+ -+ rc = kstrtobool_from_user(ubuf, cnt, &result); -+ if (rc) -+ return rc; -+ -+ if (!result) -+ return -EINVAL; -+ -+ ipts_send_sensor_quiesce_io_cmd(ipts); -+ return cnt; -+} -+ -+static const struct file_operations ipts_quiesce_io_cmd_dbgfs_fops = { -+ .open = simple_open, -+ .write = ipts_dbgfs_quiesce_io_cmd_write, -+ .llseek = generic_file_llseek, -+}; -+ -+static ssize_t ipts_dbgfs_clear_mem_window_cmd_write(struct file *fp, -+ const char __user *ubuf, size_t cnt, loff_t *ppos) -+{ -+ struct ipts_info *ipts = fp->private_data; -+ bool result; -+ int rc; -+ -+ rc = kstrtobool_from_user(ubuf, cnt, &result); -+ if (rc) -+ return rc; -+ -+ if (!result) -+ return -EINVAL; -+ -+ ipts_send_sensor_clear_mem_window_cmd(ipts); -+ -+ return cnt; -+} -+ -+static const struct file_operations ipts_clear_mem_window_cmd_dbgfs_fops = { -+ .open = simple_open, -+ .write = ipts_dbgfs_clear_mem_window_cmd_write, -+ .llseek = generic_file_llseek, -+}; -+ -+static ssize_t ipts_dbgfs_debug_read(struct file *fp, char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ struct ipts_info *ipts = fp->private_data; -+ char dbg_info[1024]; -+ int len = 0; -+ -+ char fw_sts_str[MEI_FW_STATUS_STR_SZ]; -+ u32 *db, *head, *tail; -+ struct ipts_wq_info *wq_info; -+ -+ wq_info = &ipts->resource.wq_info; -+ mei_fw_status_str(ipts->cldev->bus, fw_sts_str, MEI_FW_STATUS_STR_SZ); -+ -+ db = (u32 *)wq_info->db_addr; -+ head = (u32 *)wq_info->wq_head_addr; -+ tail = (u32 *)wq_info->wq_tail_addr; -+ -+ if (cnt < sizeof(ipts_debug_fmt) - 3) -+ return -EINVAL; -+ -+ len = scnprintf(dbg_info, 1024, ipts_debug_fmt, -+ fw_sts_str, *db, *(db+1), *head, *tail); -+ -+ if (len < 0) -+ return -EIO; -+ -+ return simple_read_from_buffer(ubuf, cnt, ppos, dbg_info, len); -+} -+ -+static const struct file_operations ipts_debug_dbgfs_fops = { -+ .open = simple_open, -+ .read = ipts_dbgfs_debug_read, -+ .llseek = generic_file_llseek, -+}; -+ -+static ssize_t ipts_dbgfs_ipts_restart_write(struct file *fp, -+ const char __user *ubuf, size_t cnt, loff_t *ppos) -+{ -+ struct ipts_info *ipts = fp->private_data; -+ bool result; -+ int rc; -+ -+ rc = kstrtobool_from_user(ubuf, cnt, &result); -+ if (rc) -+ return rc; -+ if (!result) -+ return -EINVAL; -+ -+ ipts_restart(ipts); -+ return cnt; -+} -+ -+static const struct file_operations ipts_ipts_restart_dbgfs_fops = { -+ .open = simple_open, -+ .write = ipts_dbgfs_ipts_restart_write, -+ .llseek = generic_file_llseek, -+}; -+ -+static ssize_t ipts_dbgfs_ipts_stop_write(struct file *fp, -+ const char __user *ubuf, size_t cnt, loff_t *ppos) -+{ -+ struct ipts_info *ipts = fp->private_data; -+ bool result; -+ int rc; -+ -+ rc = kstrtobool_from_user(ubuf, cnt, &result); -+ if (rc) -+ return rc; -+ -+ if (!result) -+ return -EINVAL; -+ -+ ipts_stop(ipts); -+ return cnt; -+} -+ -+static const struct file_operations ipts_ipts_stop_dbgfs_fops = { -+ .open = simple_open, -+ .write = ipts_dbgfs_ipts_stop_write, -+ .llseek = generic_file_llseek, -+}; -+ -+static ssize_t ipts_dbgfs_ipts_start_write(struct file *fp, -+ const char __user *ubuf, size_t cnt, loff_t *ppos) -+{ -+ struct ipts_info *ipts = fp->private_data; -+ bool result; -+ int rc; -+ -+ rc = kstrtobool_from_user(ubuf, cnt, &result); -+ if (rc) -+ return rc; -+ -+ if (!result) -+ return -EINVAL; -+ -+ ipts_start(ipts); -+ return cnt; -+} -+ -+static const struct file_operations ipts_ipts_start_dbgfs_fops = { -+ .open = simple_open, -+ .write = ipts_dbgfs_ipts_start_write, -+ .llseek = generic_file_llseek, -+}; -+ -+void ipts_dbgfs_deregister(struct ipts_info *ipts) -+{ -+ if (!ipts->dbgfs_dir) -+ return; -+ -+ debugfs_remove_recursive(ipts->dbgfs_dir); -+ ipts->dbgfs_dir = NULL; -+} -+ -+int ipts_dbgfs_register(struct ipts_info *ipts, const char *name) -+{ -+ struct dentry *dir, *f; -+ -+ dir = debugfs_create_dir(name, NULL); -+ if (!dir) -+ return -ENOMEM; -+ -+ f = debugfs_create_file("status", 0200, dir, ipts, -+ &ipts_status_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs status creation failed\n"); -+ goto err; -+ } -+ -+ f = debugfs_create_file("quiesce_io_cmd", 0200, dir, ipts, -+ &ipts_quiesce_io_cmd_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs quiesce_io_cmd creation failed\n"); -+ goto err; -+ } -+ -+ f = debugfs_create_file("clear_mem_window_cmd", 0200, dir, ipts, -+ &ipts_clear_mem_window_cmd_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs clear_mem_window_cmd creation failed\n"); -+ goto err; -+ } -+ -+ f = debugfs_create_file("debug", 0200, dir, ipts, -+ &ipts_debug_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs debug creation failed\n"); -+ goto err; -+ } -+ -+ f = debugfs_create_file("ipts_restart", 0200, dir, ipts, -+ &ipts_ipts_restart_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs ipts_restart creation failed\n"); -+ goto err; -+ } -+ -+ f = debugfs_create_file("ipts_stop", 0200, dir, ipts, -+ &ipts_ipts_stop_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs ipts_stop creation failed\n"); -+ goto err; -+ } -+ -+ f = debugfs_create_file("ipts_start", 0200, dir, ipts, -+ &ipts_ipts_start_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs ipts_start creation failed\n"); -+ goto err; -+ } -+ -+ ipts->dbgfs_dir = dir; -+ -+ return 0; -+ -+err: -+ ipts_dbgfs_deregister(ipts); -+ -+ return -ENODEV; -+} -diff --git a/drivers/misc/ipts/gfx.c b/drivers/misc/ipts/gfx.c -new file mode 100644 -index 000000000000..b8900f514c75 ---- /dev/null -+++ b/drivers/misc/ipts/gfx.c -@@ -0,0 +1,180 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#include -+#include -+ -+#include "ipts.h" -+#include "msg-handler.h" -+#include "params.h" -+#include "state.h" -+#include "../mei/mei_dev.h" -+ -+static void gfx_processing_complete(void *data) -+{ -+ struct ipts_info *ipts = data; -+ -+ if (ipts_get_state(ipts) == IPTS_STA_RAW_DATA_STARTED) { -+ schedule_work(&ipts->raw_data_work); -+ return; -+ } -+ -+ ipts_dbg(ipts, "not ready to handle gfx event\n"); -+} -+ -+static void notify_gfx_status(u32 status, void *data) -+{ -+ struct ipts_info *ipts = data; -+ -+ ipts->gfx_status = status; -+ schedule_work(&ipts->gfx_status_work); -+} -+ -+static int connect_gfx(struct ipts_info *ipts) -+{ -+ int ret = 0; -+ struct ipts_connect connect; -+ -+ connect.client = ipts->cldev->dev.parent; -+ connect.if_version = IPTS_INTERFACE_V1; -+ connect.ipts_cb.workload_complete = gfx_processing_complete; -+ connect.ipts_cb.notify_gfx_status = notify_gfx_status; -+ connect.data = (void *)ipts; -+ -+ ret = ipts_connect(&connect); -+ if (ret) -+ return ret; -+ -+ // TODO: GFX version check -+ ipts->gfx_info.gfx_handle = connect.gfx_handle; -+ ipts->gfx_info.ipts_ops = connect.ipts_ops; -+ -+ return ret; -+} -+ -+static void disconnect_gfx(struct ipts_info *ipts) -+{ -+ ipts_disconnect(ipts->gfx_info.gfx_handle); -+} -+ -+static struct task_struct *dbg_thread; -+ -+static void ipts_print_dbg_info(struct ipts_info *ipts) -+{ -+ char fw_sts_str[MEI_FW_STATUS_STR_SZ]; -+ u32 *db, *head, *tail; -+ struct ipts_wq_info *wq_info; -+ -+ wq_info = &ipts->resource.wq_info; -+ -+ mei_fw_status_str(ipts->cldev->bus, fw_sts_str, MEI_FW_STATUS_STR_SZ); -+ pr_info(">> tdt : fw status : %s\n", fw_sts_str); -+ -+ db = (u32 *)wq_info->db_addr; -+ head = (u32 *)wq_info->wq_head_addr; -+ tail = (u32 *)wq_info->wq_tail_addr; -+ -+ // Every time the ME has filled up the touch input buffer, and the GuC -+ // doorbell is rang, the doorbell count will increase by one -+ // The workqueue is the queue of touch events that the GuC has to -+ // process. Head is the currently processed event, while tail is -+ // the last one that is currently available. If head and tail are -+ // not equal, this can be an indicator for GuC / GPU hang. -+ pr_info(">> == Doorbell status:%x, count:%x ==\n", *db, *(db+1)); -+ pr_info(">> == Workqueue head:%u, tail:%u ==\n", *head, *tail); -+} -+ -+static int ipts_dbg_thread(void *data) -+{ -+ struct ipts_info *ipts = (struct ipts_info *)data; -+ -+ pr_info(">> start debug thread\n"); -+ -+ while (!kthread_should_stop()) { -+ if (ipts_get_state(ipts) != IPTS_STA_RAW_DATA_STARTED) { -+ pr_info("state is not IPTS_STA_RAW_DATA_STARTED : %d\n", -+ ipts_get_state(ipts)); -+ -+ msleep(5000); -+ continue; -+ } -+ -+ ipts_print_dbg_info(ipts); -+ msleep(3000); -+ } -+ -+ return 0; -+} -+ -+int ipts_open_gpu(struct ipts_info *ipts) -+{ -+ int ret = 0; -+ -+ ret = connect_gfx(ipts); -+ if (ret) { -+ ipts_dbg(ipts, "cannot connect GPU\n"); -+ return ret; -+ } -+ -+ ret = ipts->gfx_info.ipts_ops.get_wq_info(ipts->gfx_info.gfx_handle, -+ &ipts->resource.wq_info); -+ if (ret) { -+ ipts_dbg(ipts, "error in get_wq_info\n"); -+ return ret; -+ } -+ -+ if (ipts_modparams.debug_thread) -+ dbg_thread = kthread_run( -+ ipts_dbg_thread, (void *)ipts, "ipts_debug"); -+ -+ return 0; -+} -+ -+void ipts_close_gpu(struct ipts_info *ipts) -+{ -+ disconnect_gfx(ipts); -+ -+ if (ipts_modparams.debug_thread) -+ kthread_stop(dbg_thread); -+} -+ -+struct ipts_mapbuffer *ipts_map_buffer(struct ipts_info *ipts, -+ u32 size, u32 flags) -+{ -+ struct ipts_mapbuffer *buf; -+ u64 handle; -+ int ret; -+ -+ buf = devm_kzalloc(&ipts->cldev->dev, sizeof(*buf), GFP_KERNEL); -+ if (!buf) -+ return NULL; -+ -+ buf->size = size; -+ buf->flags = flags; -+ -+ handle = ipts->gfx_info.gfx_handle; -+ ret = ipts->gfx_info.ipts_ops.map_buffer(handle, buf); -+ if (ret) { -+ devm_kfree(&ipts->cldev->dev, buf); -+ return NULL; -+ } -+ -+ return buf; -+} -+ -+void ipts_unmap_buffer(struct ipts_info *ipts, struct ipts_mapbuffer *buf) -+{ -+ u64 handle; -+ -+ if (!buf) -+ return; -+ -+ handle = ipts->gfx_info.gfx_handle; -+ ipts->gfx_info.ipts_ops.unmap_buffer(handle, buf->buf_handle); -+ devm_kfree(&ipts->cldev->dev, buf); -+} -diff --git a/drivers/misc/ipts/gfx.h b/drivers/misc/ipts/gfx.h -new file mode 100644 -index 000000000000..2880e122e9f9 ---- /dev/null -+++ b/drivers/misc/ipts/gfx.h -@@ -0,0 +1,25 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#ifndef _IPTS_GFX_H_ -+#define _IPTS_GFX_H_ -+ -+#include -+ -+#include "ipts.h" -+ -+int ipts_open_gpu(struct ipts_info *ipts); -+void ipts_close_gpu(struct ipts_info *ipts); -+ -+struct ipts_mapbuffer *ipts_map_buffer(struct ipts_info *ipts, -+ u32 size, u32 flags); -+ -+void ipts_unmap_buffer(struct ipts_info *ipts, -+ struct ipts_mapbuffer *buf); -+ -+#endif // _IPTS_GFX_H_ -diff --git a/drivers/misc/ipts/hid.c b/drivers/misc/ipts/hid.c -new file mode 100644 -index 000000000000..1b7ad2a774a8 ---- /dev/null -+++ b/drivers/misc/ipts/hid.c -@@ -0,0 +1,469 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "companion.h" -+#include "hid.h" -+#include "ipts.h" -+#include "msg-handler.h" -+#include "params.h" -+#include "resource.h" -+#include "sensor-regs.h" -+ -+#define HID_DESC_INTEL "intel_desc.bin" -+#define HID_DESC_VENDOR "vendor_desc.bin" -+ -+enum output_buffer_payload_type { -+ OUTPUT_BUFFER_PAYLOAD_ERROR = 0, -+ OUTPUT_BUFFER_PAYLOAD_HID_INPUT_REPORT, -+ OUTPUT_BUFFER_PAYLOAD_HID_FEATURE_REPORT, -+ OUTPUT_BUFFER_PAYLOAD_KERNEL_LOAD, -+ OUTPUT_BUFFER_PAYLOAD_FEEDBACK_BUFFER -+}; -+ -+struct kernel_output_buffer_header { -+ u16 length; -+ u8 payload_type; -+ u8 reserved1; -+ struct touch_hid_private_data hid_private_data; -+ u8 reserved2[28]; -+ u8 data[0]; -+}; -+ -+struct kernel_output_payload_error { -+ u16 severity; -+ u16 source; -+ u8 code[4]; -+ char string[128]; -+}; -+ -+static int ipts_hid_get_descriptor(struct ipts_info *ipts, -+ u8 **desc, int *size) -+{ -+ u8 *buf; -+ int hid_size = 0, ret = 0; -+ const struct firmware *intel_desc = NULL; -+ const struct firmware *vendor_desc = NULL; -+ -+ ret = ipts_request_firmware(&intel_desc, HID_DESC_INTEL, -+ &ipts->cldev->dev); -+ if (ret) -+ goto no_hid; -+ -+ hid_size = intel_desc->size; -+ -+ ret = ipts_request_firmware(&vendor_desc, HID_DESC_VENDOR, -+ &ipts->cldev->dev); -+ if (ret) -+ ipts_dbg(ipts, "error in reading HID Vendor Descriptor\n"); -+ else -+ hid_size += vendor_desc->size; -+ -+ ipts_dbg(ipts, "HID descriptor size = %d\n", hid_size); -+ -+ buf = vmalloc(hid_size); -+ if (buf == NULL) { -+ ret = -ENOMEM; -+ goto no_mem; -+ } -+ -+ memcpy(buf, intel_desc->data, intel_desc->size); -+ if (vendor_desc) { -+ memcpy(&buf[intel_desc->size], vendor_desc->data, -+ vendor_desc->size); -+ release_firmware(vendor_desc); -+ } -+ release_firmware(intel_desc); -+ -+ *desc = buf; -+ *size = hid_size; -+ -+ return 0; -+ -+no_mem: -+ if (vendor_desc) -+ release_firmware(vendor_desc); -+ -+ release_firmware(intel_desc); -+ -+no_hid: -+ return ret; -+} -+ -+static int ipts_hid_parse(struct hid_device *hid) -+{ -+ struct ipts_info *ipts = hid->driver_data; -+ int ret = 0, size; -+ u8 *buf; -+ -+ ipts_dbg(ipts, "%s() start\n", __func__); -+ -+ ret = ipts_hid_get_descriptor(ipts, &buf, &size); -+ if (ret != 0) { -+ ipts_dbg(ipts, "ipts_hid_get_descriptor: %d\n", -+ ret); -+ return -EIO; -+ } -+ -+ ret = hid_parse_report(hid, buf, size); -+ vfree(buf); -+ if (ret) { -+ ipts_err(ipts, "hid_parse_report error : %d\n", ret); -+ return ret; -+ } -+ -+ ipts->hid_desc_ready = true; -+ -+ return 0; -+} -+ -+static int ipts_hid_start(struct hid_device *hid) -+{ -+ return 0; -+} -+ -+static void ipts_hid_stop(struct hid_device *hid) -+{ -+ -+} -+ -+static int ipts_hid_open(struct hid_device *hid) -+{ -+ return 0; -+} -+ -+static void ipts_hid_close(struct hid_device *hid) -+{ -+ struct ipts_info *ipts = hid->driver_data; -+ -+ ipts->hid_desc_ready = false; -+} -+ -+static int ipts_hid_send_hid2me_feedback(struct ipts_info *ipts, -+ u32 fb_data_type, __u8 *buf, size_t count) -+{ -+ struct ipts_buffer_info *fb_buf; -+ struct touch_feedback_hdr *feedback; -+ enum ipts_state state; -+ u8 *payload; -+ int header_size; -+ -+ header_size = sizeof(struct touch_feedback_hdr); -+ -+ if (count > ipts->resource.hid2me_buffer_size - header_size) -+ return -EINVAL; -+ -+ state = ipts_get_state(ipts); -+ if (state != IPTS_STA_RAW_DATA_STARTED && -+ state != IPTS_STA_HID_STARTED) -+ return 0; -+ -+ fb_buf = ipts_get_hid2me_buffer(ipts); -+ feedback = (struct touch_feedback_hdr *)fb_buf->addr; -+ payload = fb_buf->addr + header_size; -+ memset(feedback, 0, header_size); -+ -+ feedback->feedback_data_type = fb_data_type; -+ feedback->feedback_cmd_type = TOUCH_FEEDBACK_CMD_TYPE_NONE; -+ feedback->payload_size_bytes = count; -+ feedback->buffer_id = TOUCH_HID_2_ME_BUFFER_ID; -+ feedback->protocol_ver = 0; -+ feedback->reserved[0] = 0xAC; -+ -+ // copy payload -+ memcpy(payload, buf, count); -+ -+ ipts_send_feedback(ipts, TOUCH_HID_2_ME_BUFFER_ID, 0); -+ -+ return 0; -+} -+ -+static int ipts_hid_raw_request(struct hid_device *hid, -+ unsigned char report_number, __u8 *buf, size_t count, -+ unsigned char report_type, int reqtype) -+{ -+ struct ipts_info *ipts = hid->driver_data; -+ u32 fb_data_type; -+ -+ ipts_dbg(ipts, "hid raw request => report %d, request %d\n", -+ (int)report_type, reqtype); -+ -+ if (report_type != HID_FEATURE_REPORT) -+ return 0; -+ -+ switch (reqtype) { -+ case HID_REQ_GET_REPORT: -+ fb_data_type = TOUCH_FEEDBACK_DATA_TYPE_GET_FEATURES; -+ break; -+ case HID_REQ_SET_REPORT: -+ fb_data_type = TOUCH_FEEDBACK_DATA_TYPE_SET_FEATURES; -+ break; -+ default: -+ ipts_err(ipts, "raw request not supprted: %d\n", reqtype); -+ return -EIO; -+ } -+ -+ return ipts_hid_send_hid2me_feedback(ipts, fb_data_type, buf, count); -+} -+ -+static int ipts_hid_output_report(struct hid_device *hid, -+ __u8 *buf, size_t count) -+{ -+ struct ipts_info *ipts = hid->driver_data; -+ u32 fb_data_type; -+ -+ ipts_dbg(ipts, "hid output report\n"); -+ -+ fb_data_type = TOUCH_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; -+ -+ return ipts_hid_send_hid2me_feedback(ipts, fb_data_type, buf, count); -+} -+ -+static struct hid_ll_driver ipts_hid_ll_driver = { -+ .parse = ipts_hid_parse, -+ .start = ipts_hid_start, -+ .stop = ipts_hid_stop, -+ .open = ipts_hid_open, -+ .close = ipts_hid_close, -+ .raw_request = ipts_hid_raw_request, -+ .output_report = ipts_hid_output_report, -+}; -+ -+int ipts_hid_init(struct ipts_info *ipts) -+{ -+ int ret = 0; -+ struct hid_device *hid; -+ -+ hid = hid_allocate_device(); -+ if (IS_ERR(hid)) -+ return PTR_ERR(hid); -+ -+ hid->driver_data = ipts; -+ hid->ll_driver = &ipts_hid_ll_driver; -+ hid->dev.parent = &ipts->cldev->dev; -+ hid->bus = BUS_MEI; -+ hid->version = ipts->device_info.fw_rev; -+ hid->vendor = ipts->device_info.vendor_id; -+ hid->product = ipts->device_info.device_id; -+ -+ snprintf(hid->phys, sizeof(hid->phys), "heci3"); -+ snprintf(hid->name, sizeof(hid->name), -+ "ipts %04hX:%04hX", hid->vendor, hid->product); -+ -+ ret = hid_add_device(hid); -+ if (ret) { -+ if (ret != -ENODEV) -+ ipts_err(ipts, "can't add hid device: %d\n", ret); -+ -+ hid_destroy_device(hid); -+ -+ return ret; -+ } -+ -+ ipts->hid = hid; -+ -+ return 0; -+} -+ -+void ipts_hid_release(struct ipts_info *ipts) -+{ -+ if (!ipts->hid) -+ return; -+ -+ hid_destroy_device(ipts->hid); -+} -+ -+int ipts_handle_hid_data(struct ipts_info *ipts, -+ struct touch_sensor_hid_ready_for_data_rsp_data *hid_rsp) -+{ -+ struct touch_raw_data_hdr *raw_header; -+ struct ipts_buffer_info *buffer_info; -+ struct touch_feedback_hdr *feedback; -+ u8 *raw_data; -+ int touch_data_buffer_index; -+ int transaction_id; -+ int ret = 0; -+ -+ touch_data_buffer_index = (int)hid_rsp->touch_data_buffer_index; -+ buffer_info = ipts_get_touch_data_buffer_hid(ipts); -+ raw_header = (struct touch_raw_data_hdr *)buffer_info->addr; -+ transaction_id = raw_header->hid_private_data.transaction_id; -+ raw_data = (u8 *)raw_header + sizeof(struct touch_raw_data_hdr); -+ -+ switch (raw_header->data_type) { -+ case TOUCH_RAW_DATA_TYPE_HID_REPORT: { -+ if (raw_header->raw_data_size_bytes > HID_MAX_BUFFER_SIZE) { -+ ipts_err(ipts, "input report too large (%u bytes), skipping", -+ raw_header->raw_data_size_bytes); -+ break; -+ } -+ -+ memcpy(ipts->hid_input_report, raw_data, -+ raw_header->raw_data_size_bytes); -+ -+ ret = hid_input_report(ipts->hid, HID_INPUT_REPORT, -+ (u8 *)ipts->hid_input_report, -+ raw_header->raw_data_size_bytes, 1); -+ if (ret) -+ ipts_err(ipts, "error in hid_input_report: %d\n", ret); -+ -+ break; -+ } -+ case TOUCH_RAW_DATA_TYPE_GET_FEATURES: { -+ // TODO: implement together with "get feature ioctl" -+ break; -+ } -+ case TOUCH_RAW_DATA_TYPE_ERROR: { -+ struct touch_error *touch_err = (struct touch_error *)raw_data; -+ -+ ipts_err(ipts, "error type: %d, me error: %x, err reg: %x\n", -+ touch_err->touch_error_type, -+ touch_err->touch_me_fw_error.value, -+ touch_err->touch_error_register.reg_value); -+ -+ break; -+ } -+ default: -+ break; -+ } -+ -+ // send feedback data for HID mode -+ buffer_info = ipts_get_feedback_buffer(ipts, touch_data_buffer_index); -+ feedback = (struct touch_feedback_hdr *)buffer_info->addr; -+ memset(feedback, 0, sizeof(struct touch_feedback_hdr)); -+ feedback->feedback_cmd_type = TOUCH_FEEDBACK_CMD_TYPE_NONE; -+ feedback->payload_size_bytes = 0; -+ feedback->buffer_id = touch_data_buffer_index; -+ feedback->protocol_ver = 0; -+ feedback->reserved[0] = 0xAC; -+ -+ ret = ipts_send_feedback(ipts, touch_data_buffer_index, transaction_id); -+ -+ return ret; -+} -+ -+static int handle_outputs(struct ipts_info *ipts, int parallel_idx) -+{ -+ struct kernel_output_buffer_header *out_buf_hdr; -+ struct ipts_buffer_info *output_buf; -+ u8 *input_report, *payload; -+ u8 tr_id; -+ int i, payload_size, header_size; -+ bool send_feedback = false; -+ -+ header_size = sizeof(struct kernel_output_buffer_header); -+ output_buf = ipts_get_output_buffers_by_parallel_id(ipts, -+ parallel_idx); -+ -+ for (i = 0; i < ipts->resource.num_of_outputs; i++) { -+ out_buf_hdr = (struct kernel_output_buffer_header *) -+ output_buf[i].addr; -+ -+ if (out_buf_hdr->length < header_size) -+ continue; -+ -+ tr_id = *(u8 *)&out_buf_hdr->hid_private_data.transaction_id; -+ send_feedback = true; -+ -+ payload_size = out_buf_hdr->length - header_size; -+ payload = out_buf_hdr->data; -+ -+ switch (out_buf_hdr->payload_type) { -+ case OUTPUT_BUFFER_PAYLOAD_HID_INPUT_REPORT: { -+ input_report = ipts->hid_input_report; -+ memcpy(input_report, payload, payload_size); -+ -+ hid_input_report(ipts->hid, HID_INPUT_REPORT, -+ input_report, payload_size, 1); -+ -+ break; -+ } -+ case OUTPUT_BUFFER_PAYLOAD_HID_FEATURE_REPORT: { -+ ipts_dbg(ipts, "output hid feature report\n"); -+ break; -+ } -+ case OUTPUT_BUFFER_PAYLOAD_KERNEL_LOAD: { -+ ipts_dbg(ipts, "output kernel load\n"); -+ break; -+ } -+ case OUTPUT_BUFFER_PAYLOAD_FEEDBACK_BUFFER: { -+ // Ignored -+ break; -+ } -+ case OUTPUT_BUFFER_PAYLOAD_ERROR: { -+ struct kernel_output_payload_error *err_payload; -+ -+ if (payload_size == 0) -+ break; -+ -+ err_payload = (struct kernel_output_payload_error *) -+ payload; -+ -+ ipts_err(ipts, "severity: %d, source: %d ", -+ err_payload->severity, -+ err_payload->source); -+ ipts_err(ipts, "code : %d:%d:%d:%d\nstring %s\n", -+ err_payload->code[0], -+ err_payload->code[1], -+ err_payload->code[2], -+ err_payload->code[3], -+ err_payload->string); -+ -+ break; -+ } -+ default: -+ ipts_err(ipts, "invalid output buffer payload\n"); -+ break; -+ } -+ } -+ -+ -+ -+ if (send_feedback) -+ return ipts_send_feedback(ipts, parallel_idx, tr_id); -+ -+ return 0; -+} -+ -+static int handle_output_buffers(struct ipts_info *ipts, -+ int cur_idx, int end_idx) -+{ -+ int max_num_of_buffers = ipts_get_num_of_parallel_buffers(ipts); -+ -+ do { -+ cur_idx++; // cur_idx has last completed so starts with +1 -+ cur_idx %= max_num_of_buffers; -+ handle_outputs(ipts, cur_idx); -+ } while (cur_idx != end_idx); -+ -+ return 0; -+} -+ -+int ipts_handle_processed_data(struct ipts_info *ipts) -+{ -+ int ret = 0; -+ int current_buffer_idx; -+ int last_buffer_idx; -+ -+ current_buffer_idx = *ipts->last_submitted_id; -+ last_buffer_idx = ipts->last_buffer_completed; -+ -+ if (current_buffer_idx == last_buffer_idx) -+ return 0; -+ -+ ipts->last_buffer_completed = current_buffer_idx; -+ handle_output_buffers(ipts, last_buffer_idx, current_buffer_idx); -+ -+ return ret; -+} -diff --git a/drivers/misc/ipts/hid.h b/drivers/misc/ipts/hid.h -new file mode 100644 -index 000000000000..c943979e0198 ---- /dev/null -+++ b/drivers/misc/ipts/hid.h -@@ -0,0 +1,21 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#ifndef _IPTS_HID_H_ -+#define _IPTS_HID_H_ -+ -+#include "ipts.h" -+ -+#define BUS_MEI 0x44 -+ -+int ipts_hid_init(struct ipts_info *ipts); -+void ipts_hid_release(struct ipts_info *ipts); -+int ipts_handle_hid_data(struct ipts_info *ipts, -+ struct touch_sensor_hid_ready_for_data_rsp_data *hid_rsp); -+ -+#endif // _IPTS_HID_H_ -diff --git a/drivers/misc/ipts/ipts.c b/drivers/misc/ipts/ipts.c -new file mode 100644 -index 000000000000..dfafabf8dd94 ---- /dev/null -+++ b/drivers/misc/ipts/ipts.c -@@ -0,0 +1,62 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#include -+#include -+ -+#include "ipts.h" -+#include "params.h" -+ -+static void ipts_printk(const char *level, const struct device *dev, -+ struct va_format *vaf) -+{ -+ if (dev) { -+ dev_printk_emit(level[1] - '0', dev, "%s %s: %pV", -+ dev_driver_string(dev), dev_name(dev), vaf); -+ } else { -+ // checkpatch wants this to be prefixed with KERN_*, but -+ // since the level is passed as a parameter, ignore it -+ printk("%s(NULL device *): %pV", level, vaf); -+ } -+} -+ -+void ipts_info(struct ipts_info *ipts, const char *fmt, ...) -+{ -+ va_list args; -+ struct va_format vaf; -+ -+ if (!ipts_modparams.debug) -+ return; -+ -+ va_start(args, fmt); -+ -+ vaf.fmt = fmt; -+ vaf.va = &args; -+ -+ ipts_printk(KERN_INFO, &ipts->cldev->dev, &vaf); -+ -+ va_end(args); -+} -+ -+void ipts_dbg(struct ipts_info *ipts, const char *fmt, ...) -+{ -+ va_list args; -+ struct va_format vaf; -+ -+ if (!ipts_modparams.debug) -+ return; -+ -+ va_start(args, fmt); -+ -+ vaf.fmt = fmt; -+ vaf.va = &args; -+ -+ ipts_printk(KERN_DEBUG, &ipts->cldev->dev, &vaf); -+ -+ va_end(args); -+} -diff --git a/drivers/misc/ipts/ipts.h b/drivers/misc/ipts/ipts.h -new file mode 100644 -index 000000000000..32eb3ffd68a3 ---- /dev/null -+++ b/drivers/misc/ipts/ipts.h -@@ -0,0 +1,172 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#ifndef _IPTS_H_ -+#define _IPTS_H_ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "mei-msgs.h" -+#include "state.h" -+ -+#define HID_PARALLEL_DATA_BUFFERS TOUCH_SENSOR_MAX_DATA_BUFFERS -+ -+#define IPTS_MAX_RETRY 3 -+ -+struct ipts_buffer_info { -+ char *addr; -+ dma_addr_t dma_addr; -+}; -+ -+struct ipts_gfx_info { -+ u64 gfx_handle; -+ struct ipts_ops ipts_ops; -+}; -+ -+struct ipts_resource { -+ // ME & GFX resource -+ struct ipts_buffer_info touch_data_buffer_raw -+ [HID_PARALLEL_DATA_BUFFERS]; -+ struct ipts_buffer_info touch_data_buffer_hid; -+ struct ipts_buffer_info feedback_buffer[HID_PARALLEL_DATA_BUFFERS]; -+ struct ipts_buffer_info hid2me_buffer; -+ u32 hid2me_buffer_size; -+ -+ u8 wq_item_size; -+ struct ipts_wq_info wq_info; -+ -+ // ME2HID buffer -+ char *me2hid_buffer; -+ -+ // GFX specific resource -+ struct ipts_buffer_info raw_data_mode_output_buffer -+ [HID_PARALLEL_DATA_BUFFERS][MAX_NUM_OUTPUT_BUFFERS]; -+ -+ int num_of_outputs; -+ bool default_resource_ready; -+ bool raw_data_resource_ready; -+}; -+ -+struct ipts_info { -+ struct mei_cl_device *cldev; -+ struct hid_device *hid; -+ -+ struct work_struct init_work; -+ struct work_struct raw_data_work; -+ struct work_struct gfx_status_work; -+ -+ struct task_struct *event_loop; -+ -+#if IS_ENABLED(CONFIG_DEBUG_FS) -+ struct dentry *dbgfs_dir; -+#endif -+ -+ enum ipts_state state; -+ -+ enum touch_sensor_mode sensor_mode; -+ struct touch_sensor_get_device_info_rsp_data device_info; -+ struct ipts_resource resource; -+ u8 hid_input_report[HID_MAX_BUFFER_SIZE]; -+ int num_of_parallel_data_buffers; -+ bool hid_desc_ready; -+ -+ int current_buffer_index; -+ int last_buffer_completed; -+ int *last_submitted_id; -+ -+ struct ipts_gfx_info gfx_info; -+ u64 kernel_handle; -+ int gfx_status; -+ bool display_status; -+ -+ bool restart; -+}; -+ -+#if IS_ENABLED(CONFIG_DEBUG_FS) -+int ipts_dbgfs_register(struct ipts_info *ipts, const char *name); -+void ipts_dbgfs_deregister(struct ipts_info *ipts); -+#else -+static int ipts_dbgfs_register(struct ipts_info *ipts, const char *name); -+static void ipts_dbgfs_deregister(struct ipts_info *ipts); -+#endif -+ -+void ipts_info(struct ipts_info *ipts, const char *fmt, ...); -+void ipts_dbg(struct ipts_info *ipts, const char *fmt, ...); -+ -+// Because ipts_err is unconditional, this can stay a macro for now -+#define ipts_err(ipts, format, arg...) \ -+ dev_err(&ipts->cldev->dev, format, ##arg) -+ -+/* -+ * Inline functions -+ */ -+static inline void ipts_set_state(struct ipts_info *ipts, -+ enum ipts_state state) -+{ -+ ipts->state = state; -+} -+ -+static inline enum ipts_state ipts_get_state(const struct ipts_info *ipts) -+{ -+ return ipts->state; -+} -+ -+static inline bool ipts_is_default_resource_ready(const struct ipts_info *ipts) -+{ -+ return ipts->resource.default_resource_ready; -+} -+ -+static inline bool ipts_is_raw_data_resource_ready(const struct ipts_info *ipts) -+{ -+ return ipts->resource.raw_data_resource_ready; -+} -+ -+static inline struct ipts_buffer_info *ipts_get_feedback_buffer( -+ struct ipts_info *ipts, int buffer_idx) -+{ -+ return &ipts->resource.feedback_buffer[buffer_idx]; -+} -+ -+static inline struct ipts_buffer_info *ipts_get_touch_data_buffer_hid( -+ struct ipts_info *ipts) -+{ -+ return &ipts->resource.touch_data_buffer_hid; -+} -+ -+static inline struct ipts_buffer_info *ipts_get_output_buffers_by_parallel_id( -+ struct ipts_info *ipts, int parallel_idx) -+{ -+ return &ipts->resource.raw_data_mode_output_buffer[parallel_idx][0]; -+} -+ -+static inline struct ipts_buffer_info *ipts_get_hid2me_buffer( -+ struct ipts_info *ipts) -+{ -+ return &ipts->resource.hid2me_buffer; -+} -+ -+static inline void ipts_set_wq_item_size(struct ipts_info *ipts, u8 size) -+{ -+ ipts->resource.wq_item_size = size; -+} -+ -+static inline u8 ipts_get_wq_item_size(const struct ipts_info *ipts) -+{ -+ return ipts->resource.wq_item_size; -+} -+ -+static inline int ipts_get_num_of_parallel_buffers(const struct ipts_info *ipts) -+{ -+ return ipts->num_of_parallel_data_buffers; -+} -+ -+#endif // _IPTS_H_ -diff --git a/drivers/misc/ipts/kernel.c b/drivers/misc/ipts/kernel.c -new file mode 100644 -index 000000000000..a2c43228e2c7 ---- /dev/null -+++ b/drivers/misc/ipts/kernel.c -@@ -0,0 +1,1047 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "companion.h" -+#include "gfx.h" -+#include "ipts.h" -+#include "msg-handler.h" -+#include "resource.h" -+#include "state.h" -+ -+#define BDW_SURFACE_BASE_ADDRESS 0x6101000e -+#define SURFACE_STATE_OFFSET_WORD 4 -+#define SBA_OFFSET_BYTES 16384 -+#define LASTSUBMITID_DEFAULT_VALUE -1 -+ -+#define IPTS_INPUT_ON ((u32)1 << IPTS_INPUT) -+#define IPTS_OUTPUT_ON ((u32)1 << IPTS_OUTPUT) -+#define IPTS_CONFIGURATION_ON ((u32)1 << IPTS_CONFIGURATION) -+#define IPTS_CALIBRATION_ON ((u32)1 << IPTS_CALIBRATION) -+#define IPTS_FEATURE_ON ((u32)1 << IPTS_FEATURE) -+ -+// OpenCL kernel -+struct bin_workload { -+ int cmdbuf_index; -+ int iobuf_input; -+ int iobuf_output[MAX_NUM_OUTPUT_BUFFERS]; -+}; -+ -+struct bin_buffer { -+ unsigned int handle; -+ struct ipts_mapbuffer *buf; -+ -+ // only releasing vendor kernel unmaps output buffers -+ bool no_unmap; -+}; -+ -+struct bin_alloc_info { -+ struct bin_buffer *buffs; -+ int num_of_allocations; -+ int num_of_outputs; -+ -+ int num_of_buffers; -+}; -+ -+struct bin_guc_wq_item { -+ unsigned int batch_offset; -+ unsigned int size; -+ char data[]; -+}; -+ -+struct bin_kernel_info { -+ struct bin_workload *wl; -+ struct bin_alloc_info *alloc_info; -+ struct bin_guc_wq_item *guc_wq_item; -+ struct ipts_bin_bufid_patch bufid_patch; -+ -+ // 1: vendor, 0: postprocessing -+ bool is_vendor; -+}; -+ -+struct bin_kernel_list { -+ struct ipts_mapbuffer *bufid_buf; -+ int num_of_kernels; -+ struct bin_kernel_info kernels[]; -+}; -+ -+struct bin_parse_info { -+ u8 *data; -+ int size; -+ int parsed; -+ -+ struct ipts_bin_fw_info *fw_info; -+ -+ // only used by postprocessing -+ struct bin_kernel_info *vendor_kernel; -+ -+ // interested vendor output index -+ u32 interested_vendor_output; -+}; -+ -+static int bin_read_fw(struct ipts_info *ipts, const char *fw_name, -+ u8 *data, int size) -+{ -+ const struct firmware *fw = NULL; -+ int ret = 0; -+ -+ ret = ipts_request_firmware(&fw, fw_name, &ipts->cldev->dev); -+ if (ret) { -+ ipts_err(ipts, "cannot read fw %s\n", fw_name); -+ return ret; -+ } -+ -+ if (fw->size > size) { -+ ipts_dbg(ipts, "too small buffer to contain fw data\n"); -+ ret = -EINVAL; -+ } else { -+ memcpy(data, fw->data, fw->size); -+ } -+ -+ release_firmware(fw); -+ -+ return ret; -+} -+ -+ -+static struct ipts_bin_data_file_info *bin_get_data_file_info( -+ struct ipts_bin_fw_info *fw_info, u32 io_buffer_type) -+{ -+ int i; -+ -+ for (i = 0; i < fw_info->num_of_data_files; i++) { -+ if (fw_info->data_file[i].io_buffer_type == io_buffer_type) -+ break; -+ } -+ -+ if (i == fw_info->num_of_data_files) -+ return NULL; -+ -+ return &fw_info->data_file[i]; -+} -+ -+static inline bool is_shared_data( -+ const struct ipts_bin_data_file_info *data_file) -+{ -+ if (!data_file) -+ return false; -+ -+ return (!!(data_file->flags & IPTS_DATA_FILE_FLAG_SHARE)); -+} -+ -+static inline bool is_alloc_cont_data( -+ const struct ipts_bin_data_file_info *data_file) -+{ -+ if (!data_file) -+ return false; -+ -+ return (!!(data_file->flags & IPTS_DATA_FILE_FLAG_ALLOC_CONTIGUOUS)); -+} -+ -+static inline bool is_parsing_vendor_kernel( -+ const struct bin_parse_info *parse_info) -+{ -+ // vendor_kernel == null while loading itself -+ return parse_info->vendor_kernel == NULL; -+} -+ -+static int bin_read_allocation_list(struct ipts_info *ipts, -+ struct bin_parse_info *parse_info, -+ struct bin_alloc_info *alloc_info) -+{ -+ struct ipts_bin_alloc_list *alloc_list; -+ int aidx, pidx, num_of_parallels, bidx, num_of_buffers; -+ int parsed, size; -+ -+ parsed = parse_info->parsed; -+ size = parse_info->size; -+ -+ alloc_list = (struct ipts_bin_alloc_list *)&parse_info->data[parsed]; -+ -+ // validation check -+ if (sizeof(alloc_list->num) > size - parsed) -+ return -EINVAL; -+ -+ // read the number of aloocations -+ parsed += sizeof(alloc_list->num); -+ -+ // validation check -+ if (sizeof(alloc_list->alloc[0]) * alloc_list->num > size - parsed) -+ return -EINVAL; -+ -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ num_of_buffers = num_of_parallels * alloc_list->num + num_of_parallels; -+ alloc_info->buffs = vmalloc(sizeof(struct bin_buffer) * -+ num_of_buffers); -+ -+ if (alloc_info->buffs == NULL) -+ return -ENOMEM; -+ -+ memset(alloc_info->buffs, 0, sizeof(struct bin_buffer) * -+ num_of_buffers); -+ -+ for (aidx = 0; aidx < alloc_list->num; aidx++) { -+ for (pidx = 0; pidx < num_of_parallels; pidx++) { -+ bidx = aidx + (pidx * alloc_list->num); -+ alloc_info->buffs[bidx].handle = -+ alloc_list->alloc[aidx].handle; -+ } -+ -+ parsed += sizeof(alloc_list->alloc[0]); -+ } -+ -+ parse_info->parsed = parsed; -+ alloc_info->num_of_allocations = alloc_list->num; -+ alloc_info->num_of_buffers = num_of_buffers; -+ -+ ipts_dbg(ipts, "number of allocations = %d, buffers = %d\n", -+ alloc_info->num_of_allocations, -+ alloc_info->num_of_buffers); -+ -+ return 0; -+} -+ -+static void patch_SBA(u32 *buf_addr, u64 gpu_addr, int size) -+{ -+ u64 *stateBase; -+ u64 SBA; -+ u32 inst; -+ int i; -+ -+ SBA = gpu_addr + SBA_OFFSET_BYTES; -+ -+ for (i = 0; i < size / 4; i++) { -+ inst = buf_addr[i]; -+ if (inst == BDW_SURFACE_BASE_ADDRESS) { -+ stateBase = (u64 *)&buf_addr -+ [i + SURFACE_STATE_OFFSET_WORD]; -+ *stateBase |= SBA; -+ *stateBase |= 0x01; // enable -+ break; -+ } -+ } -+} -+ -+static int bin_read_cmd_buffer(struct ipts_info *ipts, -+ struct bin_parse_info *parse_info, -+ struct bin_alloc_info *alloc_info, struct bin_workload *wl) -+{ -+ struct ipts_bin_cmdbuf *cmd; -+ struct ipts_mapbuffer *buf; -+ int cidx, size, parsed, pidx, num_of_parallels; -+ -+ size = parse_info->size; -+ parsed = parse_info->parsed; -+ -+ cmd = (struct ipts_bin_cmdbuf *)&parse_info->data[parsed]; -+ -+ if (sizeof(cmd->size) > size - parsed) -+ return -EINVAL; -+ -+ parsed += sizeof(cmd->size); -+ if (cmd->size > size - parsed) -+ return -EINVAL; -+ -+ ipts_dbg(ipts, "cmd buf size = %d\n", cmd->size); -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ -+ // command buffers are located after the other allocations -+ cidx = num_of_parallels * alloc_info->num_of_allocations; -+ for (pidx = 0; pidx < num_of_parallels; pidx++) { -+ buf = ipts_map_buffer(ipts, cmd->size, 0); -+ -+ if (buf == NULL) -+ return -ENOMEM; -+ -+ ipts_dbg(ipts, "cmd_idx[%d] = %d, g:0x%p, c:0x%p\n", pidx, -+ cidx, buf->gfx_addr, buf->cpu_addr); -+ -+ memcpy((void *)buf->cpu_addr, &(cmd->data[0]), cmd->size); -+ patch_SBA(buf->cpu_addr, (u64)buf->gfx_addr, cmd->size); -+ -+ alloc_info->buffs[cidx].buf = buf; -+ wl[pidx].cmdbuf_index = cidx; -+ cidx++; -+ } -+ -+ parsed += cmd->size; -+ parse_info->parsed = parsed; -+ -+ return 0; -+} -+ -+static int bin_find_alloc(struct ipts_info *ipts, -+ struct bin_alloc_info *alloc_info, u32 handle) -+{ -+ int i; -+ -+ for (i = 0; i < alloc_info->num_of_allocations; i++) { -+ if (alloc_info->buffs[i].handle == handle) -+ return i; -+ } -+ -+ return -1; -+} -+ -+static struct ipts_mapbuffer *bin_get_vendor_kernel_output( -+ struct bin_parse_info *parse_info, int pidx) -+{ -+ struct bin_kernel_info *vendor = parse_info->vendor_kernel; -+ struct bin_alloc_info *alloc_info; -+ int bidx, vidx; -+ -+ alloc_info = vendor->alloc_info; -+ vidx = parse_info->interested_vendor_output; -+ -+ if (vidx >= alloc_info->num_of_outputs) -+ return NULL; -+ -+ bidx = vendor->wl[pidx].iobuf_output[vidx]; -+ -+ return alloc_info->buffs[bidx].buf; -+} -+ -+static int bin_read_res_list(struct ipts_info *ipts, -+ struct bin_parse_info *parse_info, -+ struct bin_alloc_info *alloc_info, struct bin_workload *wl) -+{ -+ struct ipts_bin_res_list *res_list; -+ struct ipts_bin_res *res; -+ struct ipts_mapbuffer *buf; -+ struct ipts_bin_data_file_info *data_file; -+ u8 *bin_data; -+ int i, size, parsed, pidx, num_of_parallels, oidx = -1; -+ int bidx, num_of_alloc; -+ u32 buf_size, flags, io_buf_type; -+ bool initialize; -+ -+ parsed = parse_info->parsed; -+ size = parse_info->size; -+ bin_data = parse_info->data; -+ -+ res_list = (struct ipts_bin_res_list *)&parse_info->data[parsed]; -+ -+ if (sizeof(res_list->num) > (size - parsed)) -+ return -EINVAL; -+ -+ parsed += sizeof(res_list->num); -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ -+ ipts_dbg(ipts, "number of resources %u\n", res_list->num); -+ -+ for (i = 0; i < res_list->num; i++) { -+ struct ipts_bin_io_header *io_hdr; -+ -+ initialize = false; -+ io_buf_type = 0; -+ flags = 0; -+ -+ // initial data -+ data_file = NULL; -+ -+ res = (struct ipts_bin_res *)(&(bin_data[parsed])); -+ if (sizeof(res[0]) > (size - parsed)) -+ return -EINVAL; -+ -+ ipts_dbg(ipts, "Resource(%d): handle 0x%08x type %u init %u size %u alsigned %u\n", -+ i, res->handle, res->type, res->initialize, -+ res->size, res->aligned_size); -+ -+ parsed += sizeof(res[0]); -+ -+ if (res->initialize) { -+ if (res->size > (size - parsed)) -+ return -EINVAL; -+ parsed += res->size; -+ } -+ -+ initialize = res->initialize; -+ if (!initialize || res->size <= -+ sizeof(struct ipts_bin_io_header)) -+ goto read_res_list_no_init; -+ -+ io_hdr = (struct ipts_bin_io_header *)(&res->data[0]); -+ -+ if (strncmp(io_hdr->str, "INTELTOUCH", 10) != 0) -+ goto read_res_list_no_init; -+ -+ data_file = bin_get_data_file_info(parse_info->fw_info, -+ (u32)io_hdr->type); -+ -+ switch (io_hdr->type) { -+ case IPTS_INPUT: { -+ ipts_dbg(ipts, "input detected\n"); -+ io_buf_type = IPTS_INPUT_ON; -+ flags = IPTS_BUF_FLAG_CONTIGUOUS; -+ break; -+ } -+ case IPTS_OUTPUT: { -+ ipts_dbg(ipts, "output detected\n"); -+ io_buf_type = IPTS_OUTPUT_ON; -+ oidx++; -+ break; -+ } -+ default: { -+ if ((u32)io_hdr->type > 31) { -+ ipts_err(ipts, "invalid io buffer : %u\n", -+ (u32)io_hdr->type); -+ continue; -+ } -+ -+ if (is_alloc_cont_data(data_file)) -+ flags = IPTS_BUF_FLAG_CONTIGUOUS; -+ -+ io_buf_type = ((u32)1 << (u32)io_hdr->type); -+ ipts_dbg(ipts, "special io buffer %u\n", -+ io_hdr->type); -+ -+ break; -+ } -+ } -+ -+ initialize = false; -+ -+read_res_list_no_init: -+ num_of_alloc = alloc_info->num_of_allocations; -+ bidx = bin_find_alloc(ipts, alloc_info, res->handle); -+ -+ if (bidx == -1) { -+ ipts_dbg(ipts, "cannot find alloc info\n"); -+ return -EINVAL; -+ } -+ -+ for (pidx = 0; pidx < num_of_parallels; pidx++, -+ bidx += num_of_alloc) { -+ if (!res->aligned_size) -+ continue; -+ -+ if (!(pidx == 0 || (io_buf_type && -+ !is_shared_data(data_file)))) -+ continue; -+ -+ buf_size = res->aligned_size; -+ if (io_buf_type & IPTS_INPUT_ON) { -+ buf_size = max_t(u32, buf_size, -+ ipts->device_info.frame_size); -+ -+ wl[pidx].iobuf_input = bidx; -+ } else if (io_buf_type & IPTS_OUTPUT_ON) { -+ wl[pidx].iobuf_output[oidx] = bidx; -+ -+ if (is_parsing_vendor_kernel(parse_info) || -+ oidx == 0) -+ goto read_res_list_no_inout_err; -+ -+ ipts_err(ipts, "postproc with >1 inout is not supported: %d\n", -+ oidx); -+ -+ return -EINVAL; -+ } -+ -+read_res_list_no_inout_err: -+ if (!is_parsing_vendor_kernel(parse_info) && -+ io_buf_type & IPTS_OUTPUT_ON) { -+ buf = bin_get_vendor_kernel_output( -+ parse_info, pidx); -+ -+ alloc_info->buffs[bidx].no_unmap = true; -+ } else { -+ buf = ipts_map_buffer(ipts, buf_size, flags); -+ } -+ -+ if (buf == NULL) { -+ ipts_dbg(ipts, "ipts_map_buffer failed\n"); -+ return -ENOMEM; -+ } -+ -+ if (initialize) { -+ memcpy((void *)buf->cpu_addr, &(res->data[0]), -+ res->size); -+ } else if (data_file && strlen(data_file->file_name)) { -+ bin_read_fw(ipts, data_file->file_name, -+ buf->cpu_addr, buf_size); -+ } else if (is_parsing_vendor_kernel(parse_info) || -+ !(io_buf_type & IPTS_OUTPUT_ON)) { -+ memset((void *)buf->cpu_addr, 0, res->size); -+ } -+ -+ alloc_info->buffs[bidx].buf = buf; -+ } -+ } -+ -+ alloc_info->num_of_outputs = oidx + 1; -+ parse_info->parsed = parsed; -+ -+ return 0; -+} -+ -+static int bin_read_patch_list(struct ipts_info *ipts, -+ struct bin_parse_info *parse_info, -+ struct bin_alloc_info *alloc_info, struct bin_workload *wl) -+{ -+ struct ipts_bin_patch_list *patch_list; -+ struct ipts_bin_patch *patch; -+ struct ipts_mapbuffer *cmd = NULL; -+ u8 *batch; -+ int parsed, size, i, pidx, num_of_parallels, cidx, bidx; -+ unsigned int gtt_offset; -+ -+ parsed = parse_info->parsed; -+ size = parse_info->size; -+ patch_list = (struct ipts_bin_patch_list *)&parse_info->data[parsed]; -+ -+ if (sizeof(patch_list->num) > (size - parsed)) -+ return -EFAULT; -+ parsed += sizeof(patch_list->num); -+ -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ patch = (struct ipts_bin_patch *)(&patch_list->patch[0]); -+ -+ for (i = 0; i < patch_list->num; i++) { -+ if (sizeof(patch_list->patch[0]) > (size - parsed)) -+ return -EFAULT; -+ -+ for (pidx = 0; pidx < num_of_parallels; pidx++) { -+ cidx = wl[pidx].cmdbuf_index; -+ bidx = patch[i].index + pidx * -+ alloc_info->num_of_allocations; -+ -+ // buffer is shared -+ if (alloc_info->buffs[bidx].buf == NULL) -+ bidx = patch[i].index; -+ -+ cmd = alloc_info->buffs[cidx].buf; -+ batch = (char *)(u64)cmd->cpu_addr; -+ -+ gtt_offset = 0; -+ if (alloc_info->buffs[bidx].buf != NULL) { -+ gtt_offset = (u32)(u64)alloc_info->buffs -+ [bidx].buf->gfx_addr; -+ } -+ gtt_offset += patch[i].alloc_offset; -+ -+ batch += patch[i].patch_offset; -+ *(u32 *)batch = gtt_offset; -+ } -+ -+ parsed += sizeof(patch_list->patch[0]); -+ } -+ -+ parse_info->parsed = parsed; -+ -+ return 0; -+} -+ -+static int bin_read_guc_wq_item(struct ipts_info *ipts, -+ struct bin_parse_info *parse_info, -+ struct bin_guc_wq_item **guc_wq_item) -+{ -+ struct ipts_bin_guc_wq_info *bin_guc_wq; -+ struct bin_guc_wq_item *item; -+ u8 *wi_data; -+ int size, parsed, hdr_size, wi_size; -+ int i, batch_offset; -+ -+ parsed = parse_info->parsed; -+ size = parse_info->size; -+ bin_guc_wq = (struct ipts_bin_guc_wq_info *)&parse_info->data[parsed]; -+ -+ wi_size = bin_guc_wq->size; -+ wi_data = bin_guc_wq->data; -+ batch_offset = bin_guc_wq->batch_offset; -+ -+ ipts_dbg(ipts, "wi size = %d, bt offset = %d\n", wi_size, batch_offset); -+ -+ for (i = 0; i < wi_size / sizeof(u32); i++) -+ ipts_dbg(ipts, "wi[%d] = 0x%08x\n", i, *((u32 *)wi_data + i)); -+ -+ hdr_size = sizeof(bin_guc_wq->size) + sizeof(bin_guc_wq->batch_offset); -+ -+ if (hdr_size > (size - parsed)) -+ return -EINVAL; -+ -+ parsed += hdr_size; -+ item = vmalloc(sizeof(struct bin_guc_wq_item) + wi_size); -+ -+ if (item == NULL) -+ return -ENOMEM; -+ -+ item->size = wi_size; -+ item->batch_offset = batch_offset; -+ memcpy(item->data, wi_data, wi_size); -+ -+ *guc_wq_item = item; -+ -+ parsed += wi_size; -+ parse_info->parsed = parsed; -+ -+ return 0; -+} -+ -+static int bin_setup_guc_workqueue(struct ipts_info *ipts, -+ struct bin_kernel_list *kernel_list) -+{ -+ struct bin_alloc_info *alloc_info; -+ struct bin_workload *wl; -+ struct bin_kernel_info *kernel; -+ struct bin_buffer *bin_buf; -+ u8 *wq_start, *wq_addr, *wi_data; -+ int wq_size, wi_size, pidx, cidx, kidx, iter_size; -+ int i, num_of_parallels, batch_offset, k_num, total_workload; -+ -+ wq_addr = (u8 *)ipts->resource.wq_info.wq_addr; -+ wq_size = ipts->resource.wq_info.wq_size; -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ total_workload = ipts_get_wq_item_size(ipts); -+ k_num = kernel_list->num_of_kernels; -+ -+ iter_size = total_workload * num_of_parallels; -+ if (wq_size % iter_size) { -+ ipts_err(ipts, "wq item cannot fit into wq\n"); -+ return -EINVAL; -+ } -+ -+ wq_start = wq_addr; -+ for (pidx = 0; pidx < num_of_parallels; pidx++) { -+ kernel = &kernel_list->kernels[0]; -+ -+ for (kidx = 0; kidx < k_num; kidx++, kernel++) { -+ wl = kernel->wl; -+ alloc_info = kernel->alloc_info; -+ -+ batch_offset = kernel->guc_wq_item->batch_offset; -+ wi_size = kernel->guc_wq_item->size; -+ wi_data = &kernel->guc_wq_item->data[0]; -+ -+ cidx = wl[pidx].cmdbuf_index; -+ bin_buf = &alloc_info->buffs[cidx]; -+ -+ // Patch the WQ Data with proper batch buffer offset -+ *(u32 *)(wi_data + batch_offset) = -+ (u32)(unsigned long)(bin_buf->buf->gfx_addr); -+ -+ memcpy(wq_addr, wi_data, wi_size); -+ wq_addr += wi_size; -+ } -+ } -+ -+ for (i = 0; i < (wq_size / iter_size) - 1; i++) { -+ memcpy(wq_addr, wq_start, iter_size); -+ wq_addr += iter_size; -+ } -+ -+ return 0; -+} -+ -+static int bin_read_bufid_patch(struct ipts_info *ipts, -+ struct bin_parse_info *parse_info, -+ struct ipts_bin_bufid_patch *bufid_patch) -+{ -+ struct ipts_bin_bufid_patch *patch; -+ int size, parsed; -+ -+ parsed = parse_info->parsed; -+ size = parse_info->size; -+ patch = (struct ipts_bin_bufid_patch *)&parse_info->data[parsed]; -+ -+ if (sizeof(struct ipts_bin_bufid_patch) > (size - parsed)) { -+ ipts_dbg(ipts, "invalid bufid info\n"); -+ return -EINVAL; -+ } -+ -+ parsed += sizeof(struct ipts_bin_bufid_patch); -+ parse_info->parsed = parsed; -+ -+ memcpy(bufid_patch, patch, sizeof(struct ipts_bin_bufid_patch)); -+ -+ return 0; -+} -+ -+static int bin_setup_bufid_buffer(struct ipts_info *ipts, -+ struct bin_kernel_list *kernel_list) -+{ -+ struct ipts_mapbuffer *buf, *cmd_buf; -+ struct bin_kernel_info *last_kernel; -+ struct bin_alloc_info *alloc_info; -+ struct bin_workload *wl; -+ u8 *batch; -+ int pidx, num_of_parallels, cidx; -+ u32 mem_offset, imm_offset; -+ -+ buf = ipts_map_buffer(ipts, PAGE_SIZE, 0); -+ if (!buf) -+ return -ENOMEM; -+ -+ last_kernel = &kernel_list->kernels[kernel_list->num_of_kernels - 1]; -+ -+ mem_offset = last_kernel->bufid_patch.mem_offset; -+ imm_offset = last_kernel->bufid_patch.imm_offset; -+ wl = last_kernel->wl; -+ alloc_info = last_kernel->alloc_info; -+ -+ // Initialize the buffer with default value -+ *((u32 *)buf->cpu_addr) = LASTSUBMITID_DEFAULT_VALUE; -+ ipts->current_buffer_index = LASTSUBMITID_DEFAULT_VALUE; -+ ipts->last_buffer_completed = LASTSUBMITID_DEFAULT_VALUE; -+ ipts->last_submitted_id = (int *)buf->cpu_addr; -+ -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ -+ for (pidx = 0; pidx < num_of_parallels; pidx++) { -+ cidx = wl[pidx].cmdbuf_index; -+ cmd_buf = alloc_info->buffs[cidx].buf; -+ batch = (u8 *)(u64)cmd_buf->cpu_addr; -+ -+ *((u32 *)(batch + mem_offset)) = (u32)(u64)(buf->gfx_addr); -+ *((u32 *)(batch + imm_offset)) = pidx; -+ } -+ -+ kernel_list->bufid_buf = buf; -+ -+ return 0; -+} -+ -+static void unmap_buffers(struct ipts_info *ipts, -+ struct bin_alloc_info *alloc_info) -+{ -+ struct bin_buffer *buffs; -+ int i, num_of_buffers; -+ -+ num_of_buffers = alloc_info->num_of_buffers; -+ buffs = &alloc_info->buffs[0]; -+ -+ for (i = 0; i < num_of_buffers; i++) { -+ if (buffs[i].no_unmap != true && buffs[i].buf != NULL) -+ ipts_unmap_buffer(ipts, buffs[i].buf); -+ } -+} -+ -+static int load_kernel(struct ipts_info *ipts, -+ struct bin_parse_info *parse_info, -+ struct bin_kernel_info *kernel) -+{ -+ struct ipts_bin_header *hdr; -+ struct bin_workload *wl; -+ struct bin_alloc_info *alloc_info; -+ struct bin_guc_wq_item *guc_wq_item = NULL; -+ struct ipts_bin_bufid_patch bufid_patch; -+ int num_of_parallels, ret; -+ -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ -+ // check header version and magic numbers -+ hdr = (struct ipts_bin_header *)parse_info->data; -+ if (hdr->version != IPTS_BIN_HEADER_VERSION || -+ strncmp(hdr->str, "IOCL", 4) != 0) { -+ ipts_err(ipts, "binary header is not correct version = %d, ", -+ hdr->version); -+ -+ ipts_err(ipts, "string = %c%c%c%c\n", hdr->str[0], hdr->str[1], -+ hdr->str[2], hdr->str[3]); -+ -+ return -EINVAL; -+ } -+ -+ parse_info->parsed = sizeof(struct ipts_bin_header); -+ wl = vmalloc(sizeof(struct bin_workload) * num_of_parallels); -+ -+ if (wl == NULL) -+ return -ENOMEM; -+ -+ memset(wl, 0, sizeof(struct bin_workload) * num_of_parallels); -+ alloc_info = vmalloc(sizeof(struct bin_alloc_info)); -+ -+ if (alloc_info == NULL) { -+ vfree(wl); -+ return -ENOMEM; -+ } -+ -+ memset(alloc_info, 0, sizeof(struct bin_alloc_info)); -+ -+ ipts_dbg(ipts, "kernel setup(size : %d)\n", parse_info->size); -+ -+ ret = bin_read_allocation_list(ipts, parse_info, alloc_info); -+ if (ret) { -+ ipts_dbg(ipts, "error read_allocation_list\n"); -+ goto setup_error; -+ } -+ -+ ret = bin_read_cmd_buffer(ipts, parse_info, alloc_info, wl); -+ if (ret) { -+ ipts_dbg(ipts, "error read_cmd_buffer\n"); -+ goto setup_error; -+ } -+ -+ ret = bin_read_res_list(ipts, parse_info, alloc_info, wl); -+ if (ret) { -+ ipts_dbg(ipts, "error read_res_list\n"); -+ goto setup_error; -+ } -+ -+ ret = bin_read_patch_list(ipts, parse_info, alloc_info, wl); -+ if (ret) { -+ ipts_dbg(ipts, "error read_patch_list\n"); -+ goto setup_error; -+ } -+ -+ ret = bin_read_guc_wq_item(ipts, parse_info, &guc_wq_item); -+ if (ret) { -+ ipts_dbg(ipts, "error read_guc_workqueue\n"); -+ goto setup_error; -+ } -+ -+ memset(&bufid_patch, 0, sizeof(bufid_patch)); -+ -+ ret = bin_read_bufid_patch(ipts, parse_info, &bufid_patch); -+ if (ret) { -+ ipts_dbg(ipts, "error read_bufid_patch\n"); -+ goto setup_error; -+ } -+ -+ kernel->wl = wl; -+ kernel->alloc_info = alloc_info; -+ kernel->is_vendor = is_parsing_vendor_kernel(parse_info); -+ kernel->guc_wq_item = guc_wq_item; -+ -+ memcpy(&kernel->bufid_patch, &bufid_patch, sizeof(bufid_patch)); -+ -+ return 0; -+ -+setup_error: -+ vfree(guc_wq_item); -+ -+ unmap_buffers(ipts, alloc_info); -+ -+ vfree(alloc_info->buffs); -+ vfree(alloc_info); -+ vfree(wl); -+ -+ return ret; -+} -+ -+void bin_setup_input_output(struct ipts_info *ipts, -+ struct bin_kernel_list *kernel_list) -+{ -+ struct bin_kernel_info *vendor_kernel; -+ struct bin_workload *wl; -+ struct ipts_mapbuffer *buf; -+ struct bin_alloc_info *alloc_info; -+ int pidx, num_of_parallels, i, bidx; -+ -+ vendor_kernel = &kernel_list->kernels[0]; -+ -+ wl = vendor_kernel->wl; -+ alloc_info = vendor_kernel->alloc_info; -+ ipts->resource.num_of_outputs = alloc_info->num_of_outputs; -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ -+ for (pidx = 0; pidx < num_of_parallels; pidx++) { -+ bidx = wl[pidx].iobuf_input; -+ buf = alloc_info->buffs[bidx].buf; -+ -+ ipts_dbg(ipts, "in_buf[%d](%d) c:%p, p:%p, g:%p\n", -+ pidx, bidx, (void *)buf->cpu_addr, -+ (void *)buf->phy_addr, (void *)buf->gfx_addr); -+ -+ ipts_set_input_buffer(ipts, pidx, buf->cpu_addr, buf->phy_addr); -+ -+ for (i = 0; i < alloc_info->num_of_outputs; i++) { -+ bidx = wl[pidx].iobuf_output[i]; -+ buf = alloc_info->buffs[bidx].buf; -+ -+ ipts_dbg(ipts, "out_buf[%d][%d] c:%p, p:%p, g:%p\n", -+ pidx, i, (void *)buf->cpu_addr, -+ (void *)buf->phy_addr, (void *)buf->gfx_addr); -+ -+ ipts_set_output_buffer(ipts, pidx, i, -+ buf->cpu_addr, buf->phy_addr); -+ } -+ } -+} -+ -+static void unload_kernel(struct ipts_info *ipts, -+ struct bin_kernel_info *kernel) -+{ -+ struct bin_alloc_info *alloc_info = kernel->alloc_info; -+ struct bin_guc_wq_item *guc_wq_item = kernel->guc_wq_item; -+ -+ if (guc_wq_item) -+ vfree(guc_wq_item); -+ -+ if (alloc_info) { -+ unmap_buffers(ipts, alloc_info); -+ -+ vfree(alloc_info->buffs); -+ vfree(alloc_info); -+ } -+} -+ -+static int setup_kernel(struct ipts_info *ipts, -+ struct ipts_bin_fw_list *fw_list) -+{ -+ struct bin_kernel_list *kernel_list = NULL; -+ struct bin_kernel_info *kernel = NULL; -+ const struct firmware *fw = NULL; -+ struct bin_workload *wl; -+ struct ipts_bin_fw_info *fw_info; -+ char *fw_name, *fw_data; -+ struct bin_parse_info parse_info; -+ int ret = 0, kidx = 0, num_of_kernels = 0; -+ int vidx, total_workload = 0; -+ -+ num_of_kernels = fw_list->num_of_fws; -+ kernel_list = vmalloc(sizeof(*kernel) * -+ num_of_kernels + sizeof(*kernel_list)); -+ -+ if (kernel_list == NULL) -+ return -ENOMEM; -+ -+ memset(kernel_list, 0, sizeof(*kernel) * -+ num_of_kernels + sizeof(*kernel_list)); -+ -+ kernel_list->num_of_kernels = num_of_kernels; -+ kernel = &kernel_list->kernels[0]; -+ -+ fw_data = (char *)&fw_list->fw_info[0]; -+ for (kidx = 0; kidx < num_of_kernels; kidx++) { -+ fw_info = (struct ipts_bin_fw_info *)fw_data; -+ fw_name = &fw_info->fw_name[0]; -+ vidx = fw_info->vendor_output; -+ -+ ret = ipts_request_firmware(&fw, fw_name, &ipts->cldev->dev); -+ if (ret) { -+ ipts_err(ipts, "cannot read fw %s\n", fw_name); -+ goto error_exit; -+ } -+ -+ parse_info.data = (u8 *)fw->data; -+ parse_info.size = fw->size; -+ parse_info.parsed = 0; -+ parse_info.fw_info = fw_info; -+ parse_info.vendor_kernel = (kidx == 0) ? NULL : &kernel[0]; -+ parse_info.interested_vendor_output = vidx; -+ -+ ret = load_kernel(ipts, &parse_info, &kernel[kidx]); -+ if (ret) { -+ ipts_err(ipts, "do_setup_kernel error : %d\n", ret); -+ release_firmware(fw); -+ goto error_exit; -+ } -+ -+ release_firmware(fw); -+ -+ total_workload += kernel[kidx].guc_wq_item->size; -+ -+ // advance to the next kernel -+ fw_data += sizeof(struct ipts_bin_fw_info); -+ fw_data += sizeof(struct ipts_bin_data_file_info) * -+ fw_info->num_of_data_files; -+ } -+ -+ ipts_set_wq_item_size(ipts, total_workload); -+ -+ ret = bin_setup_guc_workqueue(ipts, kernel_list); -+ if (ret) { -+ ipts_dbg(ipts, "error setup_guc_workqueue\n"); -+ goto error_exit; -+ } -+ -+ ret = bin_setup_bufid_buffer(ipts, kernel_list); -+ if (ret) { -+ ipts_dbg(ipts, "error setup_lastbubmit_buffer\n"); -+ goto error_exit; -+ } -+ -+ bin_setup_input_output(ipts, kernel_list); -+ -+ // workload is not needed during run-time so free them -+ for (kidx = 0; kidx < num_of_kernels; kidx++) { -+ wl = kernel[kidx].wl; -+ vfree(wl); -+ } -+ -+ ipts->kernel_handle = (u64)kernel_list; -+ -+ return 0; -+ -+error_exit: -+ -+ for (kidx = 0; kidx < num_of_kernels; kidx++) { -+ wl = kernel[kidx].wl; -+ vfree(wl); -+ unload_kernel(ipts, &kernel[kidx]); -+ } -+ -+ vfree(kernel_list); -+ -+ return ret; -+} -+ -+ -+static void release_kernel(struct ipts_info *ipts) -+{ -+ struct bin_kernel_list *kernel_list; -+ struct bin_kernel_info *kernel; -+ int kidx, knum; -+ -+ kernel_list = (struct bin_kernel_list *)ipts->kernel_handle; -+ knum = kernel_list->num_of_kernels; -+ kernel = &kernel_list->kernels[0]; -+ -+ for (kidx = 0; kidx < knum; kidx++) { -+ unload_kernel(ipts, kernel); -+ kernel++; -+ } -+ -+ ipts_unmap_buffer(ipts, kernel_list->bufid_buf); -+ -+ vfree(kernel_list); -+ ipts->kernel_handle = 0; -+} -+ -+int ipts_init_kernels(struct ipts_info *ipts) -+{ -+ struct ipts_bin_fw_list *fw_list; -+ int ret; -+ -+ ret = ipts_open_gpu(ipts); -+ if (ret) { -+ ipts_err(ipts, "open gpu error : %d\n", ret); -+ return ret; -+ } -+ -+ ret = ipts_request_firmware_config(ipts, &fw_list); -+ if (ret) { -+ ipts_err(ipts, "request firmware config error : %d\n", ret); -+ goto close_gpu; -+ } -+ -+ ret = setup_kernel(ipts, fw_list); -+ if (ret) { -+ ipts_err(ipts, "setup kernel error : %d\n", ret); -+ goto close_gpu; -+ } -+ -+ return ret; -+ -+close_gpu: -+ ipts_close_gpu(ipts); -+ -+ return ret; -+} -+ -+void ipts_release_kernels(struct ipts_info *ipts) -+{ -+ release_kernel(ipts); -+ ipts_close_gpu(ipts); -+} -diff --git a/drivers/misc/ipts/kernel.h b/drivers/misc/ipts/kernel.h -new file mode 100644 -index 000000000000..7be45da01cfc ---- /dev/null -+++ b/drivers/misc/ipts/kernel.h -@@ -0,0 +1,17 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#ifndef _IPTS_KERNEL_H_ -+#define _IPTS_KERNEL_H_ -+ -+#include "ipts.h" -+ -+int ipts_init_kernels(struct ipts_info *ipts); -+void ipts_release_kernels(struct ipts_info *ipts); -+ -+#endif // _IPTS_KERNEL_H_ -diff --git a/drivers/misc/ipts/mei-msgs.h b/drivers/misc/ipts/mei-msgs.h -new file mode 100644 -index 000000000000..036b74f7234e ---- /dev/null -+++ b/drivers/misc/ipts/mei-msgs.h -@@ -0,0 +1,901 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2013-2016 Intel Corporation -+ * -+ */ -+ -+#ifndef _IPTS_MEI_MSGS_H_ -+#define _IPTS_MEI_MSGS_H_ -+ -+#include -+ -+#include "sensor-regs.h" -+ -+#pragma pack(1) -+ -+// Define static_assert macro (which will be available after 5.1 -+// and not available on 4.19 yet) to check structure size and fail -+// compile for unexpected mismatch. -+// Taken from upstream commit 6bab69c65013bed5fce9f101a64a84d0385b3946. -+#define static_assert(expr, ...) __static_assert(expr, ##__VA_ARGS__, #expr) -+#define __static_assert(expr, msg, ...) _Static_assert(expr, msg) -+ -+// Initial protocol version -+#define TOUCH_HECI_CLIENT_PROTOCOL_VERSION 10 -+ -+// GUID that identifies the Touch HECI client. -+#define TOUCH_HECI_CLIENT_GUID \ -+ {0x3e8d0870, 0x271a, 0x4208, \ -+ {0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04} } -+ -+#define TOUCH_SENSOR_GET_DEVICE_INFO_CMD 0x00000001 -+#define TOUCH_SENSOR_GET_DEVICE_INFO_RSP 0x80000001 -+ -+#define TOUCH_SENSOR_SET_MODE_CMD 0x00000002 -+#define TOUCH_SENSOR_SET_MODE_RSP 0x80000002 -+ -+#define TOUCH_SENSOR_SET_MEM_WINDOW_CMD 0x00000003 -+#define TOUCH_SENSOR_SET_MEM_WINDOW_RSP 0x80000003 -+ -+#define TOUCH_SENSOR_QUIESCE_IO_CMD 0x00000004 -+#define TOUCH_SENSOR_QUIESCE_IO_RSP 0x80000004 -+ -+#define TOUCH_SENSOR_HID_READY_FOR_DATA_CMD 0x00000005 -+#define TOUCH_SENSOR_HID_READY_FOR_DATA_RSP 0x80000005 -+ -+#define TOUCH_SENSOR_FEEDBACK_READY_CMD 0x00000006 -+#define TOUCH_SENSOR_FEEDBACK_READY_RSP 0x80000006 -+ -+#define TOUCH_SENSOR_CLEAR_MEM_WINDOW_CMD 0x00000007 -+#define TOUCH_SENSOR_CLEAR_MEM_WINDOW_RSP 0x80000007 -+ -+#define TOUCH_SENSOR_NOTIFY_DEV_READY_CMD 0x00000008 -+#define TOUCH_SENSOR_NOTIFY_DEV_READY_RSP 0x80000008 -+ -+#define TOUCH_SENSOR_SET_POLICIES_CMD 0x00000009 -+#define TOUCH_SENSOR_SET_POLICIES_RSP 0x80000009 -+ -+#define TOUCH_SENSOR_GET_POLICIES_CMD 0x0000000A -+#define TOUCH_SENSOR_GET_POLICIES_RSP 0x8000000A -+ -+#define TOUCH_SENSOR_RESET_CMD 0x0000000B -+#define TOUCH_SENSOR_RESET_RSP 0x8000000B -+ -+#define TOUCH_SENSOR_READ_ALL_REGS_CMD 0x0000000C -+#define TOUCH_SENSOR_READ_ALL_REGS_RSP 0x8000000C -+ -+// ME sends this message to indicate previous command was unrecognized -+#define TOUCH_SENSOR_CMD_ERROR_RSP 0x8FFFFFFF -+ -+#define TOUCH_SENSOR_MAX_DATA_BUFFERS 16 -+#define TOUCH_HID_2_ME_BUFFER_ID TOUCH_SENSOR_MAX_DATA_BUFFERS -+#define TOUCH_HID_2_ME_BUFFER_SIZE_MAX 1024 -+#define TOUCH_INVALID_BUFFER_ID 0xFF -+ -+#define TOUCH_DEFAULT_DOZE_TIMER_SECONDS 30 -+ -+#define TOUCH_MSG_SIZE_MAX_BYTES \ -+ (MAX(sizeof(struct touch_sensor_msg_m2h), \ -+ sizeof(struct touch_sensor_msg_h2m))) -+ -+// indicates GuC got reset and ME must re-read GuC data such as -+// TailOffset and Doorbell Cookie values -+#define TOUCH_SENSOR_QUIESCE_FLAG_GUC_RESET BIT(0) -+ -+/* -+ * Debug Policy bits used by TOUCH_POLICY_DATA.DebugOverride -+ */ -+ -+// Disable sensor startup timer -+#define TOUCH_DBG_POLICY_OVERRIDE_STARTUP_TIMER_DIS BIT(0) -+ -+// Disable Sync Byte check -+#define TOUCH_DBG_POLICY_OVERRIDE_SYNC_BYTE_DIS BIT(1) -+ -+// Disable error resets -+#define TOUCH_DBG_POLICY_OVERRIDE_ERR_RESET_DIS BIT(2) -+ -+/* -+ * Touch Sensor Status Codes -+ */ -+enum touch_status { -+ // Requested operation was successful -+ TOUCH_STATUS_SUCCESS = 0, -+ -+ // Invalid parameter(s) sent -+ TOUCH_STATUS_INVALID_PARAMS, -+ -+ // Unable to validate address range -+ TOUCH_STATUS_ACCESS_DENIED, -+ -+ // HECI message incorrect size for specified command -+ TOUCH_STATUS_CMD_SIZE_ERROR, -+ -+ // Memory window not set or device is not armed for operation -+ TOUCH_STATUS_NOT_READY, -+ -+ // There is already an outstanding message of the same type, must -+ // wait for response before sending another request of that type -+ TOUCH_STATUS_REQUEST_OUTSTANDING, -+ -+ // Sensor could not be found. Either no sensor is connected, -+ // the sensor has not yet initialized, or the system is -+ // improperly configured. -+ TOUCH_STATUS_NO_SENSOR_FOUND, -+ -+ // Not enough memory/storage for requested operation -+ TOUCH_STATUS_OUT_OF_MEMORY, -+ -+ // Unexpected error occurred -+ TOUCH_STATUS_INTERNAL_ERROR, -+ -+ // Used in TOUCH_SENSOR_HID_READY_FOR_DATA_RSP to indicate sensor -+ // has been disabled or reset and must be reinitialized. -+ TOUCH_STATUS_SENSOR_DISABLED, -+ -+ // Used to indicate compatibility revision check between sensor and ME -+ // failed, or protocol ver between ME/HID/Kernels failed. -+ TOUCH_STATUS_COMPAT_CHECK_FAIL, -+ -+ // Indicates sensor went through a reset initiated by ME -+ TOUCH_STATUS_SENSOR_EXPECTED_RESET, -+ -+ // Indicates sensor went through an unexpected reset -+ TOUCH_STATUS_SENSOR_UNEXPECTED_RESET, -+ -+ // Requested sensor reset failed to complete -+ TOUCH_STATUS_RESET_FAILED, -+ -+ // Operation timed out -+ TOUCH_STATUS_TIMEOUT, -+ -+ // Test mode pattern did not match expected values -+ TOUCH_STATUS_TEST_MODE_FAIL, -+ -+ // Indicates sensor reported fatal error during reset sequence. -+ // Further progress is not possible. -+ TOUCH_STATUS_SENSOR_FAIL_FATAL, -+ -+ // Indicates sensor reported non-fatal error during reset sequence. -+ // HID/BIOS logs error and attempts to continue. -+ TOUCH_STATUS_SENSOR_FAIL_NONFATAL, -+ -+ // Indicates sensor reported invalid capabilities, such as not -+ // supporting required minimum frequency or I/O mode. -+ TOUCH_STATUS_INVALID_DEVICE_CAPS, -+ -+ // Indicates that command cannot be complete until ongoing Quiesce I/O -+ // flow has completed. -+ TOUCH_STATUS_QUIESCE_IO_IN_PROGRESS, -+ -+ // Invalid value, never returned -+ TOUCH_STATUS_MAX -+}; -+static_assert(sizeof(enum touch_status) == 4); -+ -+/* -+ * Defines for message structures used for Host to ME communication -+ */ -+enum touch_sensor_mode { -+ // Set mode to HID mode -+ TOUCH_SENSOR_MODE_HID = 0, -+ -+ // Set mode to Raw Data mode -+ TOUCH_SENSOR_MODE_RAW_DATA, -+ -+ // Used like TOUCH_SENSOR_MODE_HID but data coming from sensor is -+ // not necessarily a HID packet. -+ TOUCH_SENSOR_MODE_SENSOR_DEBUG = 4, -+ -+ // Invalid value -+ TOUCH_SENSOR_MODE_MAX -+}; -+static_assert(sizeof(enum touch_sensor_mode) == 4); -+ -+struct touch_sensor_set_mode_cmd_data { -+ // Indicate desired sensor mode -+ enum touch_sensor_mode sensor_mode; -+ -+ // For future expansion -+ u32 Reserved[3]; -+}; -+static_assert(sizeof(struct touch_sensor_set_mode_cmd_data) == 16); -+ -+struct touch_sensor_set_mem_window_cmd_data { -+ // Lower 32 bits of Touch Data Buffer physical address. Size of each -+ // buffer should be TOUCH_SENSOR_GET_DEVICE_INFO_RSP_DATA.FrameSize -+ u32 touch_data_buffer_addr_lower[TOUCH_SENSOR_MAX_DATA_BUFFERS]; -+ -+ // Upper 32 bits of Touch Data Buffer physical address. Size of each -+ // buffer should be TOUCH_SENSOR_GET_DEVICE_INFO_RSP_DATA.FrameSize -+ u32 touch_data_buffer_addr_upper[TOUCH_SENSOR_MAX_DATA_BUFFERS]; -+ -+ // Lower 32 bits of Tail Offset physical address -+ u32 tail_offset_addr_lower; -+ -+ // Upper 32 bits of Tail Offset physical address, always 32 bit, -+ // increment by WorkQueueItemSize -+ u32 tail_offset_addr_upper; -+ -+ // Lower 32 bits of Doorbell register physical address -+ u32 doorbell_cookie_addr_lower; -+ -+ // Upper 32 bits of Doorbell register physical address, always 32 bit, -+ // increment as integer, rollover to 1 -+ u32 doorbell_cookie_addr_upper; -+ -+ // Lower 32 bits of Feedback Buffer physical address. Size of each -+ // buffer should be TOUCH_SENSOR_GET_DEVICE_INFO_RSP_DATA.FeedbackSize -+ u32 feedback_buffer_addr_lower[TOUCH_SENSOR_MAX_DATA_BUFFERS]; -+ -+ // Upper 32 bits of Feedback Buffer physical address. Size of each -+ // buffer should be TOUCH_SENSOR_GET_DEVICE_INFO_RSP_DATA.FeedbackSize -+ u32 feedback_buffer_addr_upper[TOUCH_SENSOR_MAX_DATA_BUFFERS]; -+ -+ // Lower 32 bits of dedicated HID to ME communication buffer. -+ // Size is Hid2MeBufferSize. -+ u32 hid2me_buffer_addr_lower; -+ -+ // Upper 32 bits of dedicated HID to ME communication buffer. -+ // Size is Hid2MeBufferSize. -+ u32 hid2me_buffer_addr_upper; -+ -+ // Size in bytes of Hid2MeBuffer, can be no bigger than -+ // TOUCH_HID_2_ME_BUFFER_SIZE_MAX -+ u32 hid2me_buffer_size; -+ -+ // For future expansion -+ u8 reserved1; -+ -+ // Size in bytes of the GuC Work Queue Item pointed to by TailOffset -+ u8 work_queue_item_size; -+ -+ // Size in bytes of the entire GuC Work Queue -+ u16 work_queue_size; -+ -+ // For future expansion -+ u32 reserved[8]; -+}; -+static_assert(sizeof(struct touch_sensor_set_mem_window_cmd_data) == 320); -+ -+struct touch_sensor_quiesce_io_cmd_data { -+ // Optionally set TOUCH_SENSOR_QUIESCE_FLAG_GUC_RESET -+ u32 quiesce_flags; -+ u32 reserved[2]; -+}; -+static_assert(sizeof(struct touch_sensor_quiesce_io_cmd_data) == 12); -+ -+struct touch_sensor_feedback_ready_cmd_data { -+ // Index value from 0 to TOUCH_HID_2_ME_BUFFER_ID used to indicate -+ // which Feedback Buffer to use. Using special value -+ // TOUCH_HID_2_ME_BUFFER_ID is an indication to ME to -+ // get feedback data from the Hid2Me buffer instead of one -+ // of the standard Feedback buffers. -+ u8 feedback_index; -+ -+ // For future expansion -+ u8 reserved1[3]; -+ -+ // Transaction ID that was originally passed to host in -+ // TOUCH_HID_PRIVATE_DATA. Used to track round trip of a given -+ // transaction for performance measurements. -+ u32 transaction_id; -+ -+ // For future expansion -+ u32 reserved2[2]; -+}; -+static_assert(sizeof(struct touch_sensor_feedback_ready_cmd_data) == 16); -+ -+enum touch_freq_override { -+ // Do not apply any override -+ TOUCH_FREQ_OVERRIDE_NONE, -+ -+ // Force frequency to 10MHz (not currently supported) -+ TOUCH_FREQ_OVERRIDE_10MHZ, -+ -+ // Force frequency to 17MHz -+ TOUCH_FREQ_OVERRIDE_17MHZ, -+ -+ // Force frequency to 30MHz -+ TOUCH_FREQ_OVERRIDE_30MHZ, -+ -+ // Force frequency to 50MHz (not currently supported) -+ TOUCH_FREQ_OVERRIDE_50MHZ, -+ -+ // Invalid value -+ TOUCH_FREQ_OVERRIDE_MAX -+}; -+static_assert(sizeof(enum touch_freq_override) == 4); -+ -+enum touch_spi_io_mode_override { -+ // Do not apply any override -+ TOUCH_SPI_IO_MODE_OVERRIDE_NONE, -+ -+ // Force Single I/O -+ TOUCH_SPI_IO_MODE_OVERRIDE_SINGLE, -+ -+ // Force Dual I/O -+ TOUCH_SPI_IO_MODE_OVERRIDE_DUAL, -+ -+ // Force Quad I/O -+ TOUCH_SPI_IO_MODE_OVERRIDE_QUAD, -+ -+ // Invalid value -+ TOUCH_SPI_IO_MODE_OVERRIDE_MAX -+}; -+static_assert(sizeof(enum touch_spi_io_mode_override) == 4); -+ -+struct touch_policy_data { -+ // For future expansion. -+ u32 reserved0; -+ -+ // Value in seconds, after which ME will put the sensor into Doze power -+ // state if no activity occurs. Set to 0 to disable Doze mode -+ // (not recommended). Value will be set to -+ // TOUCH_DEFAULT_DOZE_TIMER_SECONDS by default -+ u32 doze_timer:16; -+ -+ // Override frequency requested by sensor -+ enum touch_freq_override freq_override:3; -+ -+ // Override IO mode requested by sensor -+ enum touch_spi_io_mode_override spi_io_override :3; -+ -+ // For future expansion -+ u32 reserved1:10; -+ -+ // For future expansion -+ u32 reserved2; -+ -+ // Normally all bits will be zero. Bits will be defined as needed -+ // for enabling special debug features -+ u32 debug_override; -+}; -+static_assert(sizeof(struct touch_policy_data) == 16); -+ -+struct touch_sensor_set_policies_cmd_data { -+ // Contains the desired policy to be set -+ struct touch_policy_data policy_data; -+}; -+static_assert(sizeof(struct touch_sensor_set_policies_cmd_data) == 16); -+ -+enum touch_sensor_reset_type { -+ // Hardware Reset using dedicated GPIO pin -+ TOUCH_SENSOR_RESET_TYPE_HARD, -+ -+ // Software Reset using command written over SPI interface -+ TOUCH_SENSOR_RESET_TYPE_SOFT, -+ -+ // Invalid value -+ TOUCH_SENSOR_RESET_TYPE_MAX -+}; -+static_assert(sizeof(enum touch_sensor_reset_type) == 4); -+ -+struct touch_sensor_reset_cmd_data { -+ // Indicate desired reset type -+ enum touch_sensor_reset_type reset_type; -+ -+ // For future expansion -+ u32 reserved; -+}; -+static_assert(sizeof(struct touch_sensor_reset_cmd_data) == 8); -+ -+/* -+ * Host to ME message -+ */ -+union touch_sensor_data_h2m { -+ struct touch_sensor_set_mode_cmd_data set_mode_cmd_data; -+ struct touch_sensor_set_mem_window_cmd_data set_window_cmd_data; -+ struct touch_sensor_quiesce_io_cmd_data quiesce_io_cmd_data; -+ struct touch_sensor_feedback_ready_cmd_data feedback_ready_cmd_data; -+ struct touch_sensor_set_policies_cmd_data set_policies_cmd_data; -+ struct touch_sensor_reset_cmd_data reset_cmd_data; -+}; -+struct touch_sensor_msg_h2m { -+ u32 command_code; -+ union touch_sensor_data_h2m h2m_data; -+}; -+static_assert(sizeof(struct touch_sensor_msg_h2m) == 324); -+ -+/* -+ * Message structures used for ME to Host communication -+ */ -+ -+// I/O mode values used by TOUCH_SENSOR_GET_DEVICE_INFO_RSP_DATA. -+enum touch_spi_io_mode { -+ // Sensor set for Single I/O SPI -+ TOUCH_SPI_IO_MODE_SINGLE = 0, -+ -+ // Sensor set for Dual I/O SPI -+ TOUCH_SPI_IO_MODE_DUAL, -+ -+ // Sensor set for Quad I/O SPI -+ TOUCH_SPI_IO_MODE_QUAD, -+ -+ // Invalid value -+ TOUCH_SPI_IO_MODE_MAX -+}; -+static_assert(sizeof(enum touch_spi_io_mode) == 4); -+ -+/* -+ * TOUCH_SENSOR_GET_DEVICE_INFO_RSP code is sent in response to -+ * TOUCH_SENSOR_GET_DEVICE_INFO_CMD. This code will be followed by -+ * TOUCH_SENSOR_GET_DEVICE_INFO_RSP_DATA. -+ * -+ * Possible Status values: -+ * TOUCH_STATUS_SUCCESS: -+ * Command was processed successfully and sensor -+ * details are reported. -+ * -+ * TOUCH_STATUS_CMD_SIZE_ERROR: -+ * Command sent did not match expected size. Other fields will -+ * not contain valid data. -+ * -+ * TOUCH_STATUS_NO_SENSOR_FOUND: -+ * Sensor has not yet been detected. Other fields will -+ * not contain valid data. -+ * -+ * TOUCH_STATUS_INVALID_DEVICE_CAPS: -+ * Indicates sensor does not support minimum required Frequency -+ * or I/O Mode. ME firmware will choose best possible option for -+ * the errant field. Caller should attempt to continue. -+ * -+ * TOUCH_STATUS_COMPAT_CHECK_FAIL: -+ * Indicates TouchIC/ME compatibility mismatch. Caller should -+ * attempt to continue. -+ */ -+struct touch_sensor_get_device_info_rsp_data { -+ // Touch Sensor vendor ID -+ u16 vendor_id; -+ -+ // Touch Sensor device ID -+ u16 device_id; -+ -+ // Touch Sensor Hardware Revision -+ u32 hw_rev; -+ -+ // Touch Sensor Firmware Revision -+ u32 fw_rev; -+ -+ // Max size of one frame returned by Touch IC in bytes. This data -+ // will be TOUCH_RAW_DATA_HDR followed by a payload. The payload can be -+ // raw data or a HID structure depending on mode. -+ u32 frame_size; -+ -+ // Max size of one Feedback structure in bytes -+ u32 feedback_size; -+ -+ // Current operating mode of the sensor -+ enum touch_sensor_mode sensor_mode; -+ -+ // Maximum number of simultaneous touch points that -+ // can be reported by sensor -+ u32 max_touch_points:8; -+ -+ // SPI bus Frequency supported by sensor and ME firmware -+ enum touch_freq spi_frequency:8; -+ -+ // SPI bus I/O Mode supported by sensor and ME firmware -+ enum touch_spi_io_mode spi_io_mode:8; -+ -+ // For future expansion -+ u32 reserved0:8; -+ -+ // Minor version number of EDS spec supported by -+ // sensor (from Compat Rev ID Reg) -+ u8 sensor_minor_eds_rev; -+ -+ // Major version number of EDS spec supported by -+ // sensor (from Compat Rev ID Reg) -+ u8 sensor_major_eds_rev; -+ -+ // Minor version number of EDS spec supported by ME -+ u8 me_minor_eds_rev; -+ -+ // Major version number of EDS spec supported by ME -+ u8 me_major_eds_rev; -+ -+ // EDS Interface Revision Number supported by -+ // sensor (from Compat Rev ID Reg) -+ u8 sensor_eds_intf_rev; -+ -+ // EDS Interface Revision Number supported by ME -+ u8 me_eds_intf_rev; -+ -+ // EU Kernel Compatibility Version (from Compat Rev ID Reg) -+ u8 kernel_compat_ver; -+ -+ // For future expansion -+ u8 reserved1; -+ -+ // For future expansion -+ u32 reserved2[2]; -+}; -+static_assert(sizeof(struct touch_sensor_get_device_info_rsp_data) == 44); -+ -+/* -+ * TOUCH_SENSOR_SET_MODE_RSP code is sent in response to -+ * TOUCH_SENSOR_SET_MODE_CMD. This code will be followed by -+ * TOUCH_SENSOR_SET_MODE_RSP_DATA. -+ * -+ * Possible Status values: -+ * TOUCH_STATUS_SUCCESS: -+ * Command was processed successfully and mode was set. -+ * -+ * TOUCH_STATUS_CMD_SIZE_ERROR: -+ * Command sent did not match expected size. Other fields will -+ * not contain valid data. -+ * -+ * TOUCH_STATUS_INVALID_PARAMS: -+ * Input parameters are out of range. -+ */ -+struct touch_sensor_set_mode_rsp_data { -+ // For future expansion -+ u32 reserved[3]; -+}; -+static_assert(sizeof(struct touch_sensor_set_mode_rsp_data) == 12); -+ -+/* -+ * TOUCH_SENSOR_SET_MEM_WINDOW_RSP code is sent in response to -+ * TOUCH_SENSOR_SET_MEM_WINDOW_CMD. This code will be followed -+ * by TOUCH_SENSOR_SET_MEM_WINDOW_RSP_DATA. -+ * -+ * Possible Status values: -+ * TOUCH_STATUS_SUCCESS: -+ * Command was processed successfully and memory window was set. -+ * -+ * TOUCH_STATUS_CMD_SIZE_ERROR: -+ * Command sent did not match expected size. Other fields will -+ * not contain valid data. -+ * -+ * TOUCH_STATUS_INVALID_PARAMS: -+ * Input parameters are out of range. -+ * -+ * TOUCH_STATUS_ACCESS_DENIED: -+ * Unable to map host address ranges for DMA. -+ * -+ * TOUCH_STATUS_OUT_OF_MEMORY: -+ * Unable to allocate enough space for needed buffers. -+ */ -+struct touch_sensor_set_mem_window_rsp_data { -+ // For future expansion -+ u32 reserved[3]; -+}; -+static_assert(sizeof(struct touch_sensor_set_mem_window_rsp_data) == 12); -+ -+/* -+ * TOUCH_SENSOR_QUIESCE_IO_RSP code is sent in response to -+ * TOUCH_SENSOR_QUIESCE_IO_CMD. This code will be followed -+ * by TOUCH_SENSOR_QUIESCE_IO_RSP_DATA. -+ * -+ * Possible Status values: -+ * TOUCH_STATUS_SUCCESS: -+ * Command was processed successfully and touch flow has stopped. -+ * -+ * TOUCH_STATUS_CMD_SIZE_ERROR: -+ * Command sent did not match expected size. Other fields will -+ * not contain valid data. -+ * -+ * TOUCH_STATUS_QUIESCE_IO_IN_PROGRESS: -+ * Indicates that Quiesce I/O is already in progress and this -+ * command cannot be accepted at this time. -+ * -+ * TOUCH_STATIS_TIMEOUT: -+ * Indicates ME timed out waiting for Quiesce I/O flow to complete. -+ */ -+struct touch_sensor_quiesce_io_rsp_data { -+ // For future expansion -+ u32 reserved[3]; -+}; -+static_assert(sizeof(struct touch_sensor_quiesce_io_rsp_data) == 12); -+ -+// Reset Reason values used in TOUCH_SENSOR_HID_READY_FOR_DATA_RSP_DATA -+enum touch_reset_reason { -+ // Reason for sensor reset is not known -+ TOUCH_RESET_REASON_UNKNOWN = 0, -+ -+ // Reset was requested as part of TOUCH_SENSOR_FEEDBACK_READY_CMD -+ TOUCH_RESET_REASON_FEEDBACK_REQUEST, -+ -+ // Reset was requested via TOUCH_SENSOR_RESET_CMD -+ TOUCH_RESET_REASON_HECI_REQUEST, -+ -+ TOUCH_RESET_REASON_MAX -+}; -+static_assert(sizeof(enum touch_reset_reason) == 4); -+ -+/* -+ * TOUCH_SENSOR_HID_READY_FOR_DATA_RSP code is sent in response to -+ * TOUCH_SENSOR_HID_READY_FOR_DATA_CMD. This code will be followed -+ * by TOUCH_SENSOR_HID_READY_FOR_DATA_RSP_DATA. -+ * -+ * Possible Status values: -+ * TOUCH_STATUS_SUCCESS: -+ * Command was processed successfully and HID data was sent by DMA. -+ * This will only be sent in HID mode. -+ * -+ * TOUCH_STATUS_CMD_SIZE_ERROR: -+ * Command sent did not match expected size. Other fields will -+ * not contain valid data. -+ * -+ * TOUCH_STATUS_REQUEST_OUTSTANDING: -+ * Previous request is still outstanding, ME FW cannot handle -+ * another request for the same command. -+ * -+ * TOUCH_STATUS_NOT_READY: -+ * Indicates memory window has not yet been set by BIOS/HID. -+ * -+ * TOUCH_STATUS_SENSOR_DISABLED: -+ * Indicates that ME to HID communication has been stopped either -+ * by TOUCH_SENSOR_QUIESCE_IO_CMD or -+ * TOUCH_SENSOR_CLEAR_MEM_WINDOW_CMD. -+ * -+ * TOUCH_STATUS_SENSOR_UNEXPECTED_RESET: -+ * Sensor signaled a Reset Interrupt. ME did not expect this and -+ * has no info about why this occurred. -+ * -+ * TOUCH_STATUS_SENSOR_EXPECTED_RESET: -+ * Sensor signaled a Reset Interrupt. ME either directly requested -+ * this reset, or it was expected as part of a defined flow -+ * in the EDS. -+ * -+ * TOUCH_STATUS_QUIESCE_IO_IN_PROGRESS: -+ * Indicates that Quiesce I/O is already in progress and this -+ * command cannot be accepted at this time. -+ * -+ * TOUCH_STATUS_TIMEOUT: -+ * Sensor did not generate a reset interrupt in the time allotted. -+ * Could indicate sensor is not connected or malfunctioning. -+ */ -+struct touch_sensor_hid_ready_for_data_rsp_data { -+ // Size of the data the ME DMA'd into a RawDataBuffer. -+ // Valid only when Status == TOUCH_STATUS_SUCCESS -+ u32 data_size; -+ -+ // Index to indicate which RawDataBuffer was used. -+ // Valid only when Status == TOUCH_STATUS_SUCCESS -+ u8 touch_data_buffer_index; -+ -+ // If Status is TOUCH_STATUS_SENSOR_EXPECTED_RESET, ME will provide -+ // the cause. See TOUCH_RESET_REASON. -+ u8 reset_reason; -+ -+ // For future expansion -+ u8 reserved1[2]; -+ u32 reserved2[5]; -+}; -+static_assert(sizeof(struct touch_sensor_hid_ready_for_data_rsp_data) == 28); -+ -+/* -+ * TOUCH_SENSOR_FEEDBACK_READY_RSP code is sent in response to -+ * TOUCH_SENSOR_FEEDBACK_READY_CMD. This code will be followed -+ * by TOUCH_SENSOR_FEEDBACK_READY_RSP_DATA. -+ * -+ * Possible Status values: -+ * TOUCH_STATUS_SUCCESS: -+ * Command was processed successfully and any feedback or -+ * commands were sent to sensor. -+ * -+ * TOUCH_STATUS_CMD_SIZE_ERROR: -+ * Command sent did not match expected size. Other fields will -+ * not contain valid data. -+ * -+ * TOUCH_STATUS_INVALID_PARAMS: -+ * Input parameters are out of range. -+ * -+ * TOUCH_STATUS_COMPAT_CHECK_FAIL: -+ * Indicates ProtocolVer does not match ME supported -+ * version. (non-fatal error) -+ * -+ * TOUCH_STATUS_INTERNAL_ERROR: -+ * Unexpected error occurred. This should not normally be seen. -+ * -+ * TOUCH_STATUS_OUT_OF_MEMORY: -+ * Insufficient space to store Calibration Data -+ */ -+struct touch_sensor_feedback_ready_rsp_data { -+ // Index value from 0 to TOUCH_SENSOR_MAX_DATA_BUFFERS used -+ // to indicate which Feedback Buffer to use -+ u8 feedback_index; -+ -+ // For future expansion -+ u8 reserved1[3]; -+ u32 reserved2[6]; -+}; -+static_assert(sizeof(struct touch_sensor_feedback_ready_rsp_data) == 28); -+ -+/* -+ * TOUCH_SENSOR_CLEAR_MEM_WINDOW_RSP code is sent in response to -+ * TOUCH_SENSOR_CLEAR_MEM_WINDOW_CMD. This code will be followed -+ * by TOUCH_SENSOR_CLEAR_MEM_WINDOW_RSP_DATA. -+ * -+ * Possible Status values: -+ * TOUCH_STATUS_SUCCESS: -+ * Command was processed successfully and memory window was set. -+ * -+ * TOUCH_STATUS_CMD_SIZE_ERROR: -+ * Command sent did not match expected size. Other fields will -+ * not contain valid data. -+ * -+ * TOUCH_STATUS_INVALID_PARAMS: -+ * Input parameters are out of range. -+ * -+ * TOUCH_STATUS_QUIESCE_IO_IN_PROGRESS: -+ * Indicates that Quiesce I/O is already in progress and this -+ * command cannot be accepted at this time. -+ */ -+struct touch_sensor_clear_mem_window_rsp_data { -+ // For future expansion -+ u32 reserved[3]; -+}; -+static_assert(sizeof(struct touch_sensor_clear_mem_window_rsp_data) == 12); -+ -+/* -+ * TOUCH_SENSOR_NOTIFY_DEV_READY_RSP code is sent in response to -+ * TOUCH_SENSOR_NOTIFY_DEV_READY_CMD. This code will be followed -+ * by TOUCH_SENSOR_NOTIFY_DEV_READY_RSP_DATA. -+ * -+ * Possible Status values: -+ * TOUCH_STATUS_SUCCESS: -+ * Command was processed successfully and sensor has -+ * been detected by ME FW. -+ * -+ * TOUCH_STATUS_CMD_SIZE_ERROR: -+ * Command sent did not match expected size. -+ * -+ * TOUCH_STATUS_REQUEST_OUTSTANDING: -+ * Previous request is still outstanding, ME FW cannot handle -+ * another request for the same command. -+ * -+ * TOUCH_STATUS_TIMEOUT: -+ * Sensor did not generate a reset interrupt in the time allotted. -+ * Could indicate sensor is not connected or malfunctioning. -+ * -+ * TOUCH_STATUS_SENSOR_FAIL_FATAL: -+ * Sensor indicated a fatal error, further operation is not -+ * possible. Error details can be found in ErrReg. -+ * -+ * TOUCH_STATUS_SENSOR_FAIL_NONFATAL: -+ * Sensor indicated a non-fatal error. Error should be logged by -+ * caller and init flow can continue. Error details can be found -+ * in ErrReg. -+ */ -+struct touch_sensor_notify_dev_ready_rsp_data { -+ // Value of sensor Error Register, field is only valid for -+ // Status == TOUCH_STATUS_SENSOR_FAIL_FATAL or -+ // TOUCH_STATUS_SENSOR_FAIL_NONFATAL -+ union touch_err_reg err_reg; -+ -+ // For future expansion -+ u32 reserved[2]; -+}; -+static_assert(sizeof(struct touch_sensor_notify_dev_ready_rsp_data) == 12); -+ -+/* -+ * TOUCH_SENSOR_SET_POLICIES_RSP code is sent in response to -+ * TOUCH_SENSOR_SET_POLICIES_CMD. This code will be followed -+ * by TOUCH_SENSOR_SET_POLICIES_RSP_DATA. -+ * -+ * Possible Status values: -+ * TOUCH_STATUS_SUCCESS: -+ * Command was processed successfully and new policies were set. -+ * -+ * TOUCH_STATUS_CMD_SIZE_ERROR: -+ * Command sent did not match expected size. Other fields will -+ * not contain valid data. -+ * -+ * TOUCH_STATUS_INVALID_PARAMS: -+ * Input parameters are out of range. -+ */ -+struct touch_sensor_set_policies_rsp_data { -+ // For future expansion -+ u32 reserved[3]; -+}; -+static_assert(sizeof(struct touch_sensor_set_policies_rsp_data) == 12); -+ -+/* -+ * TOUCH_SENSOR_GET_POLICIES_RSP code is sent in response to -+ * TOUCH_SENSOR_GET_POLICIES_CMD. This code will be followed -+ * by TOUCH_SENSOR_GET_POLICIES_RSP_DATA. -+ * -+ * Possible Status values: -+ * TOUCH_STATUS_SUCCESS: -+ * Command was processed successfully and new policies were set. -+ * -+ * TOUCH_STATUS_CMD_SIZE_ERROR: -+ * Command sent did not match expected size. Other fields will -+ * not contain valid data. -+ */ -+struct touch_sensor_get_policies_rsp_data { -+ // Contains the current policy -+ struct touch_policy_data policy_data; -+}; -+static_assert(sizeof(struct touch_sensor_get_policies_rsp_data) == 16); -+ -+ -+/* -+ * TOUCH_SENSOR_RESET_RSP code is sent in response to -+ * TOUCH_SENSOR_RESET_CMD. This code will be followed -+ * by TOUCH_SENSOR_RESET_RSP_DATA. -+ * -+ * Possible Status values: -+ * TOUCH_STATUS_SUCCESS: -+ * Command was processed successfully and -+ * sensor reset was completed. -+ * -+ * TOUCH_STATUS_CMD_SIZE_ERROR: -+ * Command sent did not match expected size. Other fields will -+ * not contain valid data. -+ * -+ * TOUCH_STATUS_INVALID_PARAMS: -+ * Input parameters are out of range. -+ * -+ * TOUCH_STATUS_TIMEOUT: -+ * Sensor did not generate a reset interrupt in the time allotted. -+ * Could indicate sensor is not connected or malfunctioning. -+ * -+ * TOUCH_STATUS_RESET_FAILED: -+ * Sensor generated an invalid or unexpected interrupt. -+ * -+ * TOUCH_STATUS_QUIESCE_IO_IN_PROGRESS: -+ * Indicates that Quiesce I/O is already in progress and this -+ * command cannot be accepted at this time. -+ */ -+struct touch_sensor_reset_rsp_data { -+ // For future expansion -+ u32 reserved[3]; -+}; -+static_assert(sizeof(struct touch_sensor_reset_rsp_data) == 12); -+ -+/* -+ * TOUCH_SENSOR_READ_ALL_REGS_RSP code is sent in response to -+ * TOUCH_SENSOR_READ_ALL_REGS_CMD. This code will be followed -+ * by TOUCH_SENSOR_READ_ALL_REGS_RSP_DATA. -+ * -+ * Possible Status values: -+ * TOUCH_STATUS_SUCCESS: -+ * Command was processed successfully and new policies were set. -+ * TOUCH_STATUS_CMD_SIZE_ERROR: -+ * Command sent did not match expected size. Other fields will -+ * not contain valid data. -+ */ -+struct touch_sensor_read_all_regs_rsp_data { -+ // Returns first 64 bytes of register space used for normal -+ // touch operation. Does not include test mode register. -+ struct touch_reg_block sensor_regs; -+ u32 reserved[4]; -+}; -+static_assert(sizeof(struct touch_sensor_read_all_regs_rsp_data) == 80); -+ -+/* -+ * ME to Host Message -+ */ -+union touch_sensor_data_m2h { -+ struct touch_sensor_get_device_info_rsp_data device_info_rsp_data; -+ struct touch_sensor_set_mode_rsp_data set_mode_rsp_data; -+ struct touch_sensor_set_mem_window_rsp_data set_mem_window_rsp_data; -+ struct touch_sensor_quiesce_io_rsp_data quiesce_io_rsp_data; -+ -+ struct touch_sensor_hid_ready_for_data_rsp_data -+ hid_ready_for_data_rsp_data; -+ -+ struct touch_sensor_feedback_ready_rsp_data feedback_ready_rsp_data; -+ struct touch_sensor_clear_mem_window_rsp_data clear_mem_window_rsp_data; -+ struct touch_sensor_notify_dev_ready_rsp_data notify_dev_ready_rsp_data; -+ struct touch_sensor_set_policies_rsp_data set_policies_rsp_data; -+ struct touch_sensor_get_policies_rsp_data get_policies_rsp_data; -+ struct touch_sensor_reset_rsp_data reset_rsp_data; -+ struct touch_sensor_read_all_regs_rsp_data read_all_regs_rsp_data; -+}; -+struct touch_sensor_msg_m2h { -+ u32 command_code; -+ enum touch_status status; -+ union touch_sensor_data_m2h m2h_data; -+}; -+static_assert(sizeof(struct touch_sensor_msg_m2h) == 88); -+ -+#pragma pack() -+ -+#endif // _IPTS_MEI_MSGS_H_ -diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c -new file mode 100644 -index 000000000000..03b5d747a728 ---- /dev/null -+++ b/drivers/misc/ipts/mei.c -@@ -0,0 +1,238 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "companion.h" -+#include "hid.h" -+#include "ipts.h" -+#include "params.h" -+#include "msg-handler.h" -+#include "mei-msgs.h" -+#include "state.h" -+ -+#define IPTS_DRIVER_NAME "ipts" -+#define IPTS_MEI_UUID UUID_LE(0x3e8d0870, 0x271a, 0x4208, \ -+ 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) -+ -+static struct mei_cl_device_id ipts_mei_cl_tbl[] = { -+ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, -+ { } -+}; -+MODULE_DEVICE_TABLE(mei, ipts_mei_cl_tbl); -+ -+static ssize_t device_info_show(struct device *dev, -+ struct device_attribute *attr, char *buf) -+{ -+ struct ipts_info *ipts; -+ -+ ipts = dev_get_drvdata(dev); -+ return sprintf(buf, "vendor id = 0x%04hX\ndevice id = 0x%04hX\n" -+ "HW rev = 0x%08X\nfirmware rev = 0x%08X\n", -+ ipts->device_info.vendor_id, ipts->device_info.device_id, -+ ipts->device_info.hw_rev, ipts->device_info.fw_rev); -+} -+static DEVICE_ATTR_RO(device_info); -+ -+static struct attribute *ipts_attrs[] = { -+ &dev_attr_device_info.attr, -+ NULL -+}; -+ -+static const struct attribute_group ipts_grp = { -+ .attrs = ipts_attrs, -+}; -+ -+static void raw_data_work_func(struct work_struct *work) -+{ -+ struct ipts_info *ipts = container_of(work, -+ struct ipts_info, raw_data_work); -+ -+ ipts_handle_processed_data(ipts); -+} -+ -+static void gfx_status_work_func(struct work_struct *work) -+{ -+ struct ipts_info *ipts = container_of(work, struct ipts_info, -+ gfx_status_work); -+ enum ipts_state state; -+ int status = ipts->gfx_status; -+ -+ ipts_dbg(ipts, "notify gfx status : %d\n", status); -+ -+ state = ipts_get_state(ipts); -+ -+ if (state != IPTS_STA_RAW_DATA_STARTED && state != IPTS_STA_HID_STARTED) -+ return; -+ -+ if (status == IPTS_NOTIFY_STA_BACKLIGHT_ON && !ipts->display_status) { -+ ipts_send_sensor_clear_mem_window_cmd(ipts); -+ ipts->display_status = true; -+ } -+ -+ if (status == IPTS_NOTIFY_STA_BACKLIGHT_OFF && ipts->display_status) { -+ ipts_send_sensor_quiesce_io_cmd(ipts); -+ ipts->display_status = false; -+ } -+} -+ -+// event loop -+static int ipts_mei_cl_event_thread(void *data) -+{ -+ struct ipts_info *ipts = (struct ipts_info *)data; -+ struct mei_cl_device *cldev = ipts->cldev; -+ ssize_t msg_len; -+ struct touch_sensor_msg_m2h m2h_msg; -+ -+ while (!kthread_should_stop()) { -+ msg_len = mei_cldev_recv(cldev, -+ (u8 *)&m2h_msg, sizeof(m2h_msg)); -+ if (msg_len <= 0) { -+ ipts_err(ipts, "error in reading m2h msg\n"); -+ continue; -+ } -+ -+ if (ipts_handle_resp(ipts, &m2h_msg, msg_len) != 0) -+ ipts_err(ipts, "error in handling resp msg\n"); -+ } -+ -+ ipts_dbg(ipts, "!! end event loop !!\n"); -+ -+ return 0; -+} -+ -+static void init_work_func(struct work_struct *work) -+{ -+ struct ipts_info *ipts = container_of(work, -+ struct ipts_info, init_work); -+ -+ ipts->sensor_mode = TOUCH_SENSOR_MODE_RAW_DATA; -+ ipts->display_status = true; -+ -+ ipts_start(ipts); -+} -+ -+static int ipts_mei_cl_probe(struct mei_cl_device *cldev, -+ const struct mei_cl_device_id *id) -+{ -+ int ret = 0; -+ struct ipts_info *ipts = NULL; -+ -+ // Check if a companion driver for firmware loading was registered -+ // If not, defer probing until it was properly registered -+ if (!ipts_companion_available() && !ipts_modparams.ignore_companion) -+ return -EPROBE_DEFER; -+ -+ pr_info("probing Intel Precise Touch & Stylus\n"); -+ -+ // setup the DMA BIT mask, the system will choose the best possible -+ if (dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)) == 0) { -+ pr_info("IPTS using DMA_BIT_MASK(64)\n"); -+ } else if (dma_coerce_mask_and_coherent(&cldev->dev, -+ DMA_BIT_MASK(32)) == 0) { -+ pr_info("IPTS using DMA_BIT_MASK(32)\n"); -+ } else { -+ pr_err("IPTS: No suitable DMA available\n"); -+ return -EFAULT; -+ } -+ -+ ret = mei_cldev_enable(cldev); -+ if (ret < 0) { -+ pr_err("cannot enable IPTS\n"); -+ return ret; -+ } -+ -+ ipts = devm_kzalloc(&cldev->dev, sizeof(struct ipts_info), GFP_KERNEL); -+ if (ipts == NULL) { -+ ret = -ENOMEM; -+ goto disable_mei; -+ } -+ -+ ipts->cldev = cldev; -+ mei_cldev_set_drvdata(cldev, ipts); -+ ipts->event_loop = kthread_run(ipts_mei_cl_event_thread, (void *)ipts, -+ "ipts_event_thread"); -+ -+ if (ipts_dbgfs_register(ipts, "ipts")) -+ pr_debug("cannot register debugfs for IPTS\n"); -+ -+ INIT_WORK(&ipts->init_work, init_work_func); -+ INIT_WORK(&ipts->raw_data_work, raw_data_work_func); -+ INIT_WORK(&ipts->gfx_status_work, gfx_status_work_func); -+ -+ ret = sysfs_create_group(&cldev->dev.kobj, &ipts_grp); -+ if (ret != 0) -+ pr_debug("cannot create sysfs for IPTS\n"); -+ -+ schedule_work(&ipts->init_work); -+ -+ return 0; -+ -+disable_mei: -+ mei_cldev_disable(cldev); -+ -+ return ret; -+} -+ -+static int ipts_mei_cl_remove(struct mei_cl_device *cldev) -+{ -+ struct ipts_info *ipts = mei_cldev_get_drvdata(cldev); -+ -+ ipts_stop(ipts); -+ -+ sysfs_remove_group(&cldev->dev.kobj, &ipts_grp); -+ ipts_hid_release(ipts); -+ ipts_dbgfs_deregister(ipts); -+ mei_cldev_disable(cldev); -+ -+ kthread_stop(ipts->event_loop); -+ -+ pr_info("IPTS removed\n"); -+ -+ return 0; -+} -+ -+static struct mei_cl_driver ipts_mei_cl_driver = { -+ .id_table = ipts_mei_cl_tbl, -+ .name = IPTS_DRIVER_NAME, -+ .probe = ipts_mei_cl_probe, -+ .remove = ipts_mei_cl_remove, -+}; -+ -+static int ipts_mei_cl_init(void) -+{ -+ int ret; -+ -+ pr_info("IPTS %s() is called\n", __func__); -+ -+ ret = mei_cldev_driver_register(&ipts_mei_cl_driver); -+ if (ret) { -+ pr_err("unable to register IPTS mei client driver\n"); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static void __exit ipts_mei_cl_exit(void) -+{ -+ pr_info("IPTS %s() is called\n", __func__); -+ mei_cldev_driver_unregister(&ipts_mei_cl_driver); -+} -+ -+module_init(ipts_mei_cl_init); -+module_exit(ipts_mei_cl_exit); -+ -+MODULE_DESCRIPTION("Intel(R) ME Interface Client Driver for IPTS"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/ipts/msg-handler.c b/drivers/misc/ipts/msg-handler.c -new file mode 100644 -index 000000000000..9431b1dfc6e0 ---- /dev/null -+++ b/drivers/misc/ipts/msg-handler.c -@@ -0,0 +1,405 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#include -+ -+#include "hid.h" -+#include "ipts.h" -+#include "mei-msgs.h" -+#include "resource.h" -+ -+#define rsp_failed(ipts, cmd, status) \ -+ ipts_err(ipts, "0x%08x failed status = %d\n", cmd, status) -+ -+int ipts_handle_cmd(struct ipts_info *ipts, u32 cmd, void *data, int data_size) -+{ -+ int ret = 0; -+ int len = 0; -+ struct touch_sensor_msg_h2m h2m_msg; -+ -+ memset(&h2m_msg, 0, sizeof(h2m_msg)); -+ -+ h2m_msg.command_code = cmd; -+ len = sizeof(h2m_msg.command_code) + data_size; -+ -+ if (data != NULL && data_size != 0) -+ memcpy(&h2m_msg.h2m_data, data, data_size); // copy payload -+ -+ ret = mei_cldev_send(ipts->cldev, (u8 *)&h2m_msg, len); -+ if (ret < 0) { -+ ipts_err(ipts, "mei_cldev_send() error 0x%X:%d\n", cmd, ret); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+int ipts_send_feedback(struct ipts_info *ipts, int buffer_idx, -+ u32 transaction_id) -+{ -+ struct ipts_buffer_info feedback_buffer; -+ struct touch_feedback_hdr *feedback; -+ struct touch_sensor_feedback_ready_cmd_data cmd; -+ -+ feedback_buffer = ipts->resource.feedback_buffer[buffer_idx]; -+ feedback = (struct touch_feedback_hdr *)feedback_buffer.addr; -+ -+ memset(feedback, 0, sizeof(struct touch_feedback_hdr)); -+ memset(&cmd, 0, sizeof(struct touch_sensor_feedback_ready_cmd_data)); -+ -+ feedback->feedback_cmd_type = TOUCH_FEEDBACK_CMD_TYPE_NONE; -+ feedback->buffer_id = transaction_id; -+ -+ cmd.feedback_index = buffer_idx; -+ cmd.transaction_id = transaction_id; -+ -+ return ipts_handle_cmd(ipts, TOUCH_SENSOR_FEEDBACK_READY_CMD, -+ &cmd, sizeof(struct touch_sensor_feedback_ready_cmd_data)); -+} -+ -+int ipts_send_sensor_quiesce_io_cmd(struct ipts_info *ipts) -+{ -+ int cmd_len = sizeof(struct touch_sensor_quiesce_io_cmd_data); -+ struct touch_sensor_quiesce_io_cmd_data quiesce_io_cmd; -+ -+ memset(&quiesce_io_cmd, 0, cmd_len); -+ -+ return ipts_handle_cmd(ipts, TOUCH_SENSOR_QUIESCE_IO_CMD, -+ &quiesce_io_cmd, cmd_len); -+} -+ -+int ipts_send_sensor_hid_ready_for_data_cmd(struct ipts_info *ipts) -+{ -+ return ipts_handle_cmd(ipts, -+ TOUCH_SENSOR_HID_READY_FOR_DATA_CMD, NULL, 0); -+} -+ -+int ipts_send_sensor_clear_mem_window_cmd(struct ipts_info *ipts) -+{ -+ return ipts_handle_cmd(ipts, -+ TOUCH_SENSOR_CLEAR_MEM_WINDOW_CMD, NULL, 0); -+} -+ -+static int check_validity(struct touch_sensor_msg_m2h *m2h_msg, u32 msg_len) -+{ -+ int ret = 0; -+ int valid_msg_len = sizeof(m2h_msg->command_code); -+ u32 cmd_code = m2h_msg->command_code; -+ -+ switch (cmd_code) { -+ case TOUCH_SENSOR_SET_MODE_RSP: -+ valid_msg_len += -+ sizeof(struct touch_sensor_set_mode_rsp_data); -+ break; -+ case TOUCH_SENSOR_SET_MEM_WINDOW_RSP: -+ valid_msg_len += -+ sizeof(struct touch_sensor_set_mem_window_rsp_data); -+ break; -+ case TOUCH_SENSOR_QUIESCE_IO_RSP: -+ valid_msg_len += -+ sizeof(struct touch_sensor_quiesce_io_rsp_data); -+ break; -+ case TOUCH_SENSOR_HID_READY_FOR_DATA_RSP: -+ valid_msg_len += -+ sizeof(struct touch_sensor_hid_ready_for_data_rsp_data); -+ break; -+ case TOUCH_SENSOR_FEEDBACK_READY_RSP: -+ valid_msg_len += -+ sizeof(struct touch_sensor_feedback_ready_rsp_data); -+ break; -+ case TOUCH_SENSOR_CLEAR_MEM_WINDOW_RSP: -+ valid_msg_len += -+ sizeof(struct touch_sensor_clear_mem_window_rsp_data); -+ break; -+ case TOUCH_SENSOR_NOTIFY_DEV_READY_RSP: -+ valid_msg_len += -+ sizeof(struct touch_sensor_notify_dev_ready_rsp_data); -+ break; -+ case TOUCH_SENSOR_SET_POLICIES_RSP: -+ valid_msg_len += -+ sizeof(struct touch_sensor_set_policies_rsp_data); -+ break; -+ case TOUCH_SENSOR_GET_POLICIES_RSP: -+ valid_msg_len += -+ sizeof(struct touch_sensor_get_policies_rsp_data); -+ break; -+ case TOUCH_SENSOR_RESET_RSP: -+ valid_msg_len += -+ sizeof(struct touch_sensor_reset_rsp_data); -+ break; -+ } -+ -+ if (valid_msg_len != msg_len) -+ return -EINVAL; -+ return ret; -+} -+ -+int ipts_start(struct ipts_info *ipts) -+{ -+ /* -+ * TODO: check if we need to do SET_POLICIES_CMD we need to do this -+ * when protocol version doesn't match with reported one how we keep -+ * vendor specific data is the first thing to solve. -+ */ -+ ipts_set_state(ipts, IPTS_STA_INIT); -+ ipts->num_of_parallel_data_buffers = TOUCH_SENSOR_MAX_DATA_BUFFERS; -+ -+ // start with RAW_DATA -+ ipts->sensor_mode = TOUCH_SENSOR_MODE_RAW_DATA; -+ -+ return ipts_handle_cmd(ipts, -+ TOUCH_SENSOR_NOTIFY_DEV_READY_CMD, NULL, 0); -+} -+ -+void ipts_stop(struct ipts_info *ipts) -+{ -+ enum ipts_state old_state = ipts_get_state(ipts); -+ -+ ipts_set_state(ipts, IPTS_STA_STOPPING); -+ -+ ipts_send_sensor_quiesce_io_cmd(ipts); -+ ipts_send_sensor_clear_mem_window_cmd(ipts); -+ -+ if (old_state < IPTS_STA_RESOURCE_READY) -+ return; -+ -+ if (old_state == IPTS_STA_RAW_DATA_STARTED || -+ old_state == IPTS_STA_HID_STARTED) { -+ ipts_free_default_resource(ipts); -+ ipts_free_raw_data_resource(ipts); -+ } -+} -+ -+int ipts_restart(struct ipts_info *ipts) -+{ -+ ipts_dbg(ipts, "ipts restart\n"); -+ ipts_stop(ipts); -+ -+ ipts_send_sensor_quiesce_io_cmd(ipts); -+ ipts->restart = true; -+ -+ return 0; -+} -+ -+int ipts_handle_resp(struct ipts_info *ipts, -+ struct touch_sensor_msg_m2h *m2h_msg, u32 msg_len) -+{ -+ int ret = 0; -+ int rsp_status = 0; -+ int cmd_status = 0; -+ int cmd_len = 0; -+ u32 cmd; -+ -+ if (!check_validity(m2h_msg, msg_len)) { -+ ipts_err(ipts, "wrong rsp\n"); -+ return -EINVAL; -+ } -+ -+ rsp_status = m2h_msg->status; -+ cmd = m2h_msg->command_code; -+ -+ switch (cmd) { -+ case TOUCH_SENSOR_NOTIFY_DEV_READY_RSP: { -+ if (rsp_status != TOUCH_STATUS_SENSOR_FAIL_NONFATAL && -+ rsp_status != 0) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ cmd_status = ipts_handle_cmd(ipts, -+ TOUCH_SENSOR_GET_DEVICE_INFO_CMD, NULL, 0); -+ -+ break; -+ } -+ case TOUCH_SENSOR_GET_DEVICE_INFO_RSP: { -+ if (rsp_status != TOUCH_STATUS_COMPAT_CHECK_FAIL && -+ rsp_status != 0) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ memcpy(&ipts->device_info, -+ &m2h_msg->m2h_data.device_info_rsp_data, -+ sizeof(struct touch_sensor_get_device_info_rsp_data)); -+ -+ /* -+ * TODO: support raw_request during HID init. Although HID -+ * init happens here, technically most of reports -+ * (for both direction) can be issued only after -+ * SET_MEM_WINDOWS_CMD since they may require ME or touch IC. -+ * If ipts vendor requires raw_request during HID init, we -+ * need to consider to move HID init. -+ */ -+ if (ipts->hid_desc_ready == false) { -+ ret = ipts_hid_init(ipts); -+ if (ret) -+ break; -+ } -+ -+ cmd_status = ipts_send_sensor_clear_mem_window_cmd(ipts); -+ -+ break; -+ } -+ case TOUCH_SENSOR_CLEAR_MEM_WINDOW_RSP: { -+ struct touch_sensor_set_mode_cmd_data sensor_mode_cmd; -+ -+ if (rsp_status != TOUCH_STATUS_TIMEOUT && rsp_status != 0) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ if (ipts_get_state(ipts) == IPTS_STA_STOPPING) -+ break; -+ -+ // allocate default resource: common & hid only -+ if (!ipts_is_default_resource_ready(ipts)) { -+ ret = ipts_allocate_default_resource(ipts); -+ if (ret) -+ break; -+ } -+ -+ if (ipts->sensor_mode == TOUCH_SENSOR_MODE_RAW_DATA && -+ !ipts_is_raw_data_resource_ready(ipts)) { -+ ret = ipts_allocate_raw_data_resource(ipts); -+ if (ret) { -+ ipts_free_default_resource(ipts); -+ break; -+ } -+ } -+ -+ ipts_set_state(ipts, IPTS_STA_RESOURCE_READY); -+ -+ cmd_len = sizeof(struct touch_sensor_set_mode_cmd_data); -+ memset(&sensor_mode_cmd, 0, cmd_len); -+ -+ sensor_mode_cmd.sensor_mode = ipts->sensor_mode; -+ cmd_status = ipts_handle_cmd(ipts, TOUCH_SENSOR_SET_MODE_CMD, -+ &sensor_mode_cmd, cmd_len); -+ -+ break; -+ } -+ case TOUCH_SENSOR_SET_MODE_RSP: { -+ struct touch_sensor_set_mem_window_cmd_data smw_cmd; -+ -+ if (rsp_status != 0) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ cmd_len = sizeof(struct touch_sensor_set_mem_window_cmd_data); -+ memset(&smw_cmd, 0, cmd_len); -+ -+ ipts_get_set_mem_window_cmd_data(ipts, &smw_cmd); -+ cmd_status = ipts_handle_cmd(ipts, -+ TOUCH_SENSOR_SET_MEM_WINDOW_CMD, &smw_cmd, cmd_len); -+ -+ break; -+ } -+ case TOUCH_SENSOR_SET_MEM_WINDOW_RSP: { -+ if (rsp_status != 0) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ cmd_status = ipts_send_sensor_hid_ready_for_data_cmd(ipts); -+ if (cmd_status) -+ break; -+ -+ if (ipts->sensor_mode == TOUCH_SENSOR_MODE_HID) -+ ipts_set_state(ipts, IPTS_STA_HID_STARTED); -+ else if (ipts->sensor_mode == TOUCH_SENSOR_MODE_RAW_DATA) -+ ipts_set_state(ipts, IPTS_STA_RAW_DATA_STARTED); -+ -+ ipts_dbg(ipts, "touch enabled %d\n", ipts_get_state(ipts)); -+ -+ break; -+ } -+ case TOUCH_SENSOR_HID_READY_FOR_DATA_RSP: { -+ struct touch_sensor_hid_ready_for_data_rsp_data *hid_data; -+ enum ipts_state state; -+ -+ if (rsp_status != TOUCH_STATUS_SENSOR_DISABLED && -+ rsp_status != 0) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ state = ipts_get_state(ipts); -+ if (ipts->sensor_mode == TOUCH_SENSOR_MODE_HID && -+ state == IPTS_STA_HID_STARTED) { -+ hid_data = -+ &m2h_msg->m2h_data.hid_ready_for_data_rsp_data; -+ -+ // HID mode only uses buffer 0 -+ if (hid_data->touch_data_buffer_index != 0) -+ break; -+ -+ // handle hid data -+ ipts_handle_hid_data(ipts, hid_data); -+ } -+ -+ break; -+ } -+ case TOUCH_SENSOR_FEEDBACK_READY_RSP: { -+ if (rsp_status != TOUCH_STATUS_COMPAT_CHECK_FAIL && -+ rsp_status != TOUCH_STATUS_INVALID_PARAMS && -+ rsp_status != 0) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ if (m2h_msg->m2h_data.feedback_ready_rsp_data.feedback_index -+ == TOUCH_HID_2_ME_BUFFER_ID) -+ break; -+ -+ if (ipts->sensor_mode == TOUCH_SENSOR_MODE_HID) -+ cmd_status = ipts_handle_cmd(ipts, -+ TOUCH_SENSOR_HID_READY_FOR_DATA_CMD, NULL, 0); -+ -+ break; -+ } -+ case TOUCH_SENSOR_QUIESCE_IO_RSP: { -+ enum ipts_state state; -+ -+ if (rsp_status != 0) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ state = ipts_get_state(ipts); -+ if (state == IPTS_STA_STOPPING && ipts->restart) { -+ ipts_dbg(ipts, "restart\n"); -+ ipts_start(ipts); -+ ipts->restart = 0; -+ break; -+ } -+ -+ break; -+ } -+ } -+ -+ // handle error in rsp_status -+ if (rsp_status != 0) { -+ switch (rsp_status) { -+ case TOUCH_STATUS_SENSOR_EXPECTED_RESET: -+ case TOUCH_STATUS_SENSOR_UNEXPECTED_RESET: -+ ipts_dbg(ipts, "sensor reset %d\n", rsp_status); -+ ipts_restart(ipts); -+ break; -+ default: -+ ipts_dbg(ipts, "cmd : 0x%08x, status %d\n", -+ cmd, rsp_status); -+ break; -+ } -+ } -+ -+ if (cmd_status) -+ ipts_restart(ipts); -+ -+ return ret; -+} -diff --git a/drivers/misc/ipts/msg-handler.h b/drivers/misc/ipts/msg-handler.h -new file mode 100644 -index 000000000000..eca4238adf4b ---- /dev/null -+++ b/drivers/misc/ipts/msg-handler.h -@@ -0,0 +1,28 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#ifndef _IPTS_MSG_HANDLER_H_ -+#define _IPTS_MSG_HANDLER_H_ -+ -+int ipts_start(struct ipts_info *ipts); -+void ipts_stop(struct ipts_info *ipts); -+int ipts_handle_cmd(struct ipts_info *ipts, u32 cmd, void *data, int data_size); -+ -+int ipts_handle_resp(struct ipts_info *ipts, -+ struct touch_sensor_msg_m2h *m2h_msg, u32 msg_len); -+ -+int ipts_send_feedback(struct ipts_info *ipts, -+ int buffer_idx, u32 transaction_id); -+ -+int ipts_handle_processed_data(struct ipts_info *ipts); -+int ipts_send_sensor_quiesce_io_cmd(struct ipts_info *ipts); -+int ipts_send_sensor_hid_ready_for_data_cmd(struct ipts_info *ipts); -+int ipts_send_sensor_clear_mem_window_cmd(struct ipts_info *ipts); -+int ipts_restart(struct ipts_info *ipts); -+ -+#endif /* _IPTS_MSG_HANDLER_H */ -diff --git a/drivers/misc/ipts/params.c b/drivers/misc/ipts/params.c -new file mode 100644 -index 000000000000..3ea76ca8342a ---- /dev/null -+++ b/drivers/misc/ipts/params.c -@@ -0,0 +1,42 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#include -+ -+#include "params.h" -+ -+#define IPTS_PARAM(NAME, TYPE, PERM, DESC) \ -+ module_param_named(NAME, ipts_modparams.NAME, TYPE, PERM); \ -+ MODULE_PARM_DESC(NAME, DESC) -+ -+struct ipts_params ipts_modparams = { -+ .ignore_fw_fallback = false, -+ .ignore_config_fallback = false, -+ .ignore_companion = false, -+ -+ .debug = false, -+ .debug_thread = false, -+}; -+ -+IPTS_PARAM(ignore_fw_fallback, bool, 0400, -+ "Don't use the IPTS firmware fallback path. (default: false)" -+); -+IPTS_PARAM(ignore_config_fallback, bool, 0400, -+ "Don't try to load the IPTS firmware config from a file. (default: false)" -+); -+IPTS_PARAM(ignore_companion, bool, 0400, -+ "Don't use a companion driver to load firmware. (default: false)" -+); -+ -+IPTS_PARAM(debug, bool, 0400, -+ "Enable IPTS debugging output. (default: false)" -+); -+IPTS_PARAM(debug_thread, bool, 0400, -+ "Periodically print the ME status into the kernel log. (default: false)" -+); -+ -diff --git a/drivers/misc/ipts/params.h b/drivers/misc/ipts/params.h -new file mode 100644 -index 000000000000..c20546bacb08 ---- /dev/null -+++ b/drivers/misc/ipts/params.h -@@ -0,0 +1,25 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#ifndef _IPTS_PARAMS_H_ -+#define _IPTS_PARAMS_H_ -+ -+#include -+ -+struct ipts_params { -+ bool ignore_fw_fallback; -+ bool ignore_config_fallback; -+ bool ignore_companion; -+ -+ bool debug; -+ bool debug_thread; -+}; -+ -+extern struct ipts_params ipts_modparams; -+ -+#endif // _IPTS_PARAMS_H_ -diff --git a/drivers/misc/ipts/resource.c b/drivers/misc/ipts/resource.c -new file mode 100644 -index 000000000000..cfd212f2cac0 ---- /dev/null -+++ b/drivers/misc/ipts/resource.c -@@ -0,0 +1,291 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#include -+ -+#include "ipts.h" -+#include "kernel.h" -+#include "mei-msgs.h" -+ -+static void free_common_resource(struct ipts_info *ipts) -+{ -+ char *addr; -+ struct ipts_buffer_info *feedback_buffer; -+ dma_addr_t dma_addr; -+ u32 buffer_size; -+ int i, num_of_parallels; -+ -+ if (ipts->resource.me2hid_buffer) { -+ devm_kfree(&ipts->cldev->dev, ipts->resource.me2hid_buffer); -+ ipts->resource.me2hid_buffer = 0; -+ } -+ -+ addr = ipts->resource.hid2me_buffer.addr; -+ dma_addr = ipts->resource.hid2me_buffer.dma_addr; -+ buffer_size = ipts->resource.hid2me_buffer_size; -+ -+ if (ipts->resource.hid2me_buffer.addr) { -+ dmam_free_coherent(&ipts->cldev->dev, buffer_size, -+ addr, dma_addr); -+ -+ ipts->resource.hid2me_buffer.addr = 0; -+ ipts->resource.hid2me_buffer.dma_addr = 0; -+ ipts->resource.hid2me_buffer_size = 0; -+ } -+ -+ feedback_buffer = ipts->resource.feedback_buffer; -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ for (i = 0; i < num_of_parallels; i++) { -+ -+ if (!feedback_buffer[i].addr) -+ continue; -+ -+ dmam_free_coherent(&ipts->cldev->dev, -+ ipts->device_info.feedback_size, -+ feedback_buffer[i].addr, feedback_buffer[i].dma_addr); -+ -+ feedback_buffer[i].addr = 0; -+ feedback_buffer[i].dma_addr = 0; -+ } -+} -+ -+static int allocate_common_resource(struct ipts_info *ipts) -+{ -+ char *addr, *me2hid_addr; -+ struct ipts_buffer_info *feedback_buffer; -+ dma_addr_t dma_addr; -+ int i, ret = 0, num_of_parallels; -+ u32 buffer_size; -+ -+ buffer_size = ipts->device_info.feedback_size; -+ -+ addr = dmam_alloc_coherent(&ipts->cldev->dev, buffer_size, &dma_addr, -+ GFP_ATOMIC | __GFP_ZERO); -+ if (addr == NULL) -+ return -ENOMEM; -+ -+ me2hid_addr = devm_kzalloc(&ipts->cldev->dev, buffer_size, GFP_KERNEL); -+ if (me2hid_addr == NULL) { -+ ret = -ENOMEM; -+ goto release_resource; -+ } -+ -+ ipts->resource.hid2me_buffer.addr = addr; -+ ipts->resource.hid2me_buffer.dma_addr = dma_addr; -+ ipts->resource.hid2me_buffer_size = buffer_size; -+ ipts->resource.me2hid_buffer = me2hid_addr; -+ -+ feedback_buffer = ipts->resource.feedback_buffer; -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ -+ for (i = 0; i < num_of_parallels; i++) { -+ feedback_buffer[i].addr = dmam_alloc_coherent(&ipts->cldev->dev, -+ ipts->device_info.feedback_size, -+ &feedback_buffer[i].dma_addr, GFP_ATOMIC|__GFP_ZERO); -+ -+ if (feedback_buffer[i].addr == NULL) { -+ ret = -ENOMEM; -+ goto release_resource; -+ } -+ } -+ -+ return 0; -+ -+release_resource: -+ free_common_resource(ipts); -+ -+ return ret; -+} -+ -+void ipts_free_raw_data_resource(struct ipts_info *ipts) -+{ -+ if (ipts_is_raw_data_resource_ready(ipts)) { -+ ipts->resource.raw_data_resource_ready = false; -+ ipts_release_kernels(ipts); -+ } -+} -+ -+static int allocate_hid_resource(struct ipts_info *ipts) -+{ -+ struct ipts_buffer_info *buffer_hid; -+ -+ // hid mode uses only one touch data buffer -+ buffer_hid = &ipts->resource.touch_data_buffer_hid; -+ buffer_hid->addr = dmam_alloc_coherent(&ipts->cldev->dev, -+ ipts->device_info.frame_size, &buffer_hid->dma_addr, -+ GFP_ATOMIC|__GFP_ZERO); -+ -+ if (buffer_hid->addr == NULL) -+ return -ENOMEM; -+ -+ return 0; -+} -+ -+static void free_hid_resource(struct ipts_info *ipts) -+{ -+ struct ipts_buffer_info *buffer_hid; -+ -+ buffer_hid = &ipts->resource.touch_data_buffer_hid; -+ if (buffer_hid->addr) { -+ dmam_free_coherent(&ipts->cldev->dev, -+ ipts->device_info.frame_size, -+ buffer_hid->addr, buffer_hid->dma_addr); -+ -+ buffer_hid->addr = 0; -+ buffer_hid->dma_addr = 0; -+ } -+} -+ -+int ipts_allocate_default_resource(struct ipts_info *ipts) -+{ -+ int ret; -+ -+ ret = allocate_common_resource(ipts); -+ if (ret) { -+ ipts_dbg(ipts, "cannot allocate common resource\n"); -+ return ret; -+ } -+ -+ ret = allocate_hid_resource(ipts); -+ if (ret) { -+ ipts_dbg(ipts, "cannot allocate hid resource\n"); -+ free_common_resource(ipts); -+ return ret; -+ } -+ -+ ipts->resource.default_resource_ready = true; -+ -+ return 0; -+} -+ -+void ipts_free_default_resource(struct ipts_info *ipts) -+{ -+ if (ipts_is_default_resource_ready(ipts)) { -+ ipts->resource.default_resource_ready = false; -+ free_hid_resource(ipts); -+ free_common_resource(ipts); -+ } -+} -+ -+int ipts_allocate_raw_data_resource(struct ipts_info *ipts) -+{ -+ int ret = 0; -+ -+ ret = ipts_init_kernels(ipts); -+ if (ret) -+ return ret; -+ -+ ipts->resource.raw_data_resource_ready = true; -+ return 0; -+} -+ -+static void get_hid_only_smw_cmd_data(struct ipts_info *ipts, -+ struct touch_sensor_set_mem_window_cmd_data *data, -+ struct ipts_resource *resrc) -+{ -+ struct ipts_buffer_info *touch_buf; -+ struct ipts_buffer_info *feedback_buf; -+ -+ touch_buf = &resrc->touch_data_buffer_hid; -+ feedback_buf = &resrc->feedback_buffer[0]; -+ -+ data->touch_data_buffer_addr_lower[0] = -+ lower_32_bits(touch_buf->dma_addr); -+ -+ data->touch_data_buffer_addr_upper[0] = -+ upper_32_bits(touch_buf->dma_addr); -+ -+ data->feedback_buffer_addr_lower[0] = -+ lower_32_bits(feedback_buf->dma_addr); -+ -+ data->feedback_buffer_addr_upper[0] = -+ upper_32_bits(feedback_buf->dma_addr); -+} -+ -+static void get_raw_data_only_smw_cmd_data(struct ipts_info *ipts, -+ struct touch_sensor_set_mem_window_cmd_data *data, -+ struct ipts_resource *resrc) -+{ -+ u64 wq_tail_phy_addr; -+ u64 cookie_phy_addr; -+ struct ipts_buffer_info *touch_buf; -+ struct ipts_buffer_info *feedback_buf; -+ int i, num_of_parallels; -+ -+ touch_buf = resrc->touch_data_buffer_raw; -+ feedback_buf = resrc->feedback_buffer; -+ -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ for (i = 0; i < num_of_parallels; i++) { -+ data->touch_data_buffer_addr_lower[i] = -+ lower_32_bits(touch_buf[i].dma_addr); -+ -+ data->touch_data_buffer_addr_upper[i] = -+ upper_32_bits(touch_buf[i].dma_addr); -+ -+ data->feedback_buffer_addr_lower[i] = -+ lower_32_bits(feedback_buf[i].dma_addr); -+ -+ data->feedback_buffer_addr_upper[i] = -+ upper_32_bits(feedback_buf[i].dma_addr); -+ } -+ -+ wq_tail_phy_addr = resrc->wq_info.wq_tail_phy_addr; -+ data->tail_offset_addr_lower = lower_32_bits(wq_tail_phy_addr); -+ data->tail_offset_addr_upper = upper_32_bits(wq_tail_phy_addr); -+ -+ cookie_phy_addr = resrc->wq_info.db_phy_addr + -+ resrc->wq_info.db_cookie_offset; -+ -+ data->doorbell_cookie_addr_lower = lower_32_bits(cookie_phy_addr); -+ data->doorbell_cookie_addr_upper = upper_32_bits(cookie_phy_addr); -+ data->work_queue_size = resrc->wq_info.wq_size; -+ data->work_queue_item_size = resrc->wq_item_size; -+} -+ -+void ipts_get_set_mem_window_cmd_data(struct ipts_info *ipts, -+ struct touch_sensor_set_mem_window_cmd_data *data) -+{ -+ struct ipts_resource *resrc = &ipts->resource; -+ -+ if (ipts->sensor_mode == TOUCH_SENSOR_MODE_RAW_DATA) -+ get_raw_data_only_smw_cmd_data(ipts, data, resrc); -+ else if (ipts->sensor_mode == TOUCH_SENSOR_MODE_HID) -+ get_hid_only_smw_cmd_data(ipts, data, resrc); -+ -+ // hid2me is common for "raw data" and "hid" -+ data->hid2me_buffer_addr_lower = -+ lower_32_bits(resrc->hid2me_buffer.dma_addr); -+ -+ data->hid2me_buffer_addr_upper = -+ upper_32_bits(resrc->hid2me_buffer.dma_addr); -+ -+ data->hid2me_buffer_size = resrc->hid2me_buffer_size; -+} -+ -+void ipts_set_input_buffer(struct ipts_info *ipts, int parallel_idx, -+ u8 *cpu_addr, u64 dma_addr) -+{ -+ struct ipts_buffer_info *touch_buf; -+ -+ touch_buf = ipts->resource.touch_data_buffer_raw; -+ touch_buf[parallel_idx].dma_addr = dma_addr; -+ touch_buf[parallel_idx].addr = cpu_addr; -+} -+ -+void ipts_set_output_buffer(struct ipts_info *ipts, int parallel_idx, -+ int output_idx, u8 *cpu_addr, u64 dma_addr) -+{ -+ struct ipts_buffer_info *output_buf; -+ -+ output_buf = &ipts->resource.raw_data_mode_output_buffer -+ [parallel_idx][output_idx]; -+ -+ output_buf->dma_addr = dma_addr; -+ output_buf->addr = cpu_addr; -+} -diff --git a/drivers/misc/ipts/resource.h b/drivers/misc/ipts/resource.h -new file mode 100644 -index 000000000000..27b9c17fcb89 ---- /dev/null -+++ b/drivers/misc/ipts/resource.h -@@ -0,0 +1,26 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#ifndef _IPTS_RESOURCE_H_ -+#define _IPTS_RESOURCE_H_ -+ -+int ipts_allocate_default_resource(struct ipts_info *ipts); -+void ipts_free_default_resource(struct ipts_info *ipts); -+int ipts_allocate_raw_data_resource(struct ipts_info *ipts); -+void ipts_free_raw_data_resource(struct ipts_info *ipts); -+ -+void ipts_get_set_mem_window_cmd_data(struct ipts_info *ipts, -+ struct touch_sensor_set_mem_window_cmd_data *data); -+ -+void ipts_set_input_buffer(struct ipts_info *ipts, int parallel_idx, -+ u8 *cpu_addr, u64 dma_addr); -+ -+void ipts_set_output_buffer(struct ipts_info *ipts, int parallel_idx, -+ int output_idx, u8 *cpu_addr, u64 dma_addr); -+ -+#endif // _IPTS_RESOURCE_H_ -diff --git a/drivers/misc/ipts/sensor-regs.h b/drivers/misc/ipts/sensor-regs.h -new file mode 100644 -index 000000000000..c1afab48249b ---- /dev/null -+++ b/drivers/misc/ipts/sensor-regs.h -@@ -0,0 +1,834 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2013-2016 Intel Corporation -+ * -+ */ -+ -+#ifndef _IPTS_SENSOR_REGS_H_ -+#define _IPTS_SENSOR_REGS_H_ -+ -+#include -+ -+#pragma pack(1) -+ -+// Define static_assert macro (which will be available after 5.1 -+// and not available on 4.19 yet) to check structure size and fail -+// compile for unexpected mismatch. -+// Taken from upstream commit 6bab69c65013bed5fce9f101a64a84d0385b3946. -+#define static_assert(expr, ...) __static_assert(expr, ##__VA_ARGS__, #expr) -+#define __static_assert(expr, msg, ...) _Static_assert(expr, msg) -+ -+/* -+ * Compatibility versions for this header file -+ */ -+#define TOUCH_EDS_REV_MINOR 0 -+#define TOUCH_EDS_REV_MAJOR 1 -+#define TOUCH_EDS_INTF_REV 1 -+#define TOUCH_PROTOCOL_VER 0 -+ -+/* -+ * Offset 00h: TOUCH_STS: Status Register -+ * This register is read by the SPI Controller immediately following -+ * an interrupt. -+ */ -+#define TOUCH_STS_REG_OFFSET 0x00 -+ -+#define TOUCH_SYNC_BYTE_VALUE 0x5A -+ -+/* -+ * Offset 04h: TOUCH_FRAME_CHAR: Frame Characteristics Register -+ * This registers describes the characteristics of each data frame read by the -+ * SPI Controller in response to a touch interrupt. -+ */ -+#define TOUCH_FRAME_CHAR_REG_OFFSET 0x04 -+ -+/* -+ * Offset 08h: Touch Error Register -+ */ -+#define TOUCH_ERR_REG_OFFSET 0x08 -+ -+/* -+ * Offset 10h: Touch Identification Register -+ */ -+#define TOUCH_ID_REG_OFFSET 0x10 -+#define TOUCH_ID_REG_VALUE 0x43495424 -+ -+/* -+ * Offset 14h: TOUCH_DATA_SZ: Touch Data Size Register -+ * This register describes the maximum size of frames and feedback data -+ */ -+#define TOUCH_DATA_SZ_REG_OFFSET 0x14 -+ -+#define TOUCH_MAX_FRAME_SIZE_INCREMENT 64 -+#define TOUCH_MAX_FEEDBACK_SIZE_INCREMENT 64 -+ -+/* -+ * Max allowed frame size 32KB -+ * Max allowed feedback size 16KB -+ */ -+#define TOUCH_SENSOR_MAX_FRAME_SIZE (32 * 1024) -+#define TOUCH_SENSOR_MAX_FEEDBACK_SIZE (16 * 1024) -+ -+/* -+ * Offset 18h: TOUCH_CAPABILITIES: Touch Capabilities Register -+ * This register informs the host as to the capabilities of the touch IC. -+ */ -+#define TOUCH_CAPS_REG_OFFSET 0x18 -+ -+#define TOUCH_BULK_DATA_MAX_WRITE_INCREMENT 64 -+ -+/* -+ * Offset 1Ch: TOUCH_CFG: Touch Configuration Register -+ * This register allows the SPI Controller to configure the touch sensor as -+ * needed during touch operations. -+ */ -+#define TOUCH_CFG_REG_OFFSET 0x1C -+ -+/* -+ * Offset 20h: TOUCH_CMD: Touch Command Register -+ * This register is used for sending commands to the Touch IC. -+ */ -+#define TOUCH_CMD_REG_OFFSET 0x20 -+ -+/* -+ * Offset 24h: Power Management Control -+ * This register is used for active power management. The Touch IC is allowed -+ * to mover from Doze or Armed to Sensing after a touch has occurred. All other -+ * transitions will be made at the request of the SPI Controller. -+ */ -+#define TOUCH_PWR_MGMT_CTRL_REG_OFFSET 0x24 -+ -+/* -+ * Offset 28h: Vendor HW Information Register -+ * This register is used to relay Intel-assigned vendor ID information to the -+ * SPI Controller, which may be forwarded to SW running on the host CPU. -+ */ -+#define TOUCH_VEN_HW_INFO_REG_OFFSET 0x28 -+ -+/* -+ * Offset 2Ch: HW Revision ID Register -+ * This register is used to relay vendor HW revision information to the SPI -+ * Controller which may be forwarded to SW running on the host CPU. -+ */ -+#define TOUCH_HW_REV_REG_OFFSET 0x2C -+ -+/* -+ * Offset 30h: FW Revision ID Register -+ * This register is used to relay vendor FW revision information to the SPI -+ * Controller which may be forwarded to SW running on the host CPU. -+ */ -+#define TOUCH_FW_REV_REG_OFFSET 0x30 -+ -+/* -+ * Offset 34h: Compatibility Revision ID Register -+ * This register is used to relay vendor compatibility information to the SPI -+ * Controller which may be forwarded to SW running on the host CPU. -+ * Compatibility Information is a numeric value given by Intel to the Touch IC -+ * vendor based on the major and minor revision of the EDS supported. From a -+ * nomenclature point of view in an x.y revision number of the EDS, the major -+ * version is the value of x and the minor version is the value of y. For -+ * example, a Touch IC supporting an EDS version of 0.61 would contain a major -+ * version of 0 and a minor version of 61 in the register. -+ */ -+#define TOUCH_COMPAT_REV_REG_OFFSET 0x34 -+ -+/* -+ * Touch Register Block is the full set of registers from offset 0x00h to 0x3F -+ * This is the entire set of registers needed for normal touch operation. It -+ * does not include test registers such as TOUCH_TEST_CTRL_REG -+ */ -+#define TOUCH_REG_BLOCK_OFFSET TOUCH_STS_REG_OFFSET -+ -+/* -+ * Offset 40h: Test Control Register -+ * This register -+ */ -+#define TOUCH_TEST_CTRL_REG_OFFSET 0x40 -+ -+/* -+ * Offsets 0x000 to 0xFFF are reserved for Intel-defined Registers -+ */ -+#define TOUCH_REGISTER_LIMIT 0xFFF -+ -+/* -+ * Data Window: Address 0x1000-0x1FFFF -+ * The data window is reserved for writing and reading large quantities of -+ * data to and from the sensor. -+ */ -+#define TOUCH_DATA_WINDOW_OFFSET 0x1000 -+#define TOUCH_DATA_WINDOW_LIMIT 0x1FFFF -+ -+#define TOUCH_SENSOR_MAX_OFFSET TOUCH_DATA_WINDOW_LIMIT -+ -+enum touch_sts_reg_int_type { -+ // Touch Data Available -+ TOUCH_STS_REG_INT_TYPE_DATA_AVAIL = 0, -+ -+ // Reset Occurred -+ TOUCH_STS_REG_INT_TYPE_RESET_OCCURRED, -+ -+ // Error Occurred -+ TOUCH_STS_REG_INT_TYPE_ERROR_OCCURRED, -+ -+ // Vendor specific data, treated same as raw frame -+ TOUCH_STS_REG_INT_TYPE_VENDOR_DATA, -+ -+ // Get Features response data available -+ TOUCH_STS_REG_INT_TYPE_GET_FEATURES, -+ -+ TOUCH_STS_REG_INT_TYPE_MAX -+}; -+static_assert(sizeof(enum touch_sts_reg_int_type) == 4); -+ -+enum touch_sts_reg_pwr_state { -+ // Sleep -+ TOUCH_STS_REG_PWR_STATE_SLEEP = 0, -+ -+ // Doze -+ TOUCH_STS_REG_PWR_STATE_DOZE, -+ -+ // Armed -+ TOUCH_STS_REG_PWR_STATE_ARMED, -+ -+ // Sensing -+ TOUCH_STS_REG_PWR_STATE_SENSING, -+ -+ TOUCH_STS_REG_PWR_STATE_MAX -+}; -+static_assert(sizeof(enum touch_sts_reg_pwr_state) == 4); -+ -+enum touch_sts_reg_init_state { -+ // Ready for normal operation -+ TOUCH_STS_REG_INIT_STATE_READY_FOR_OP = 0, -+ -+ // Touch IC needs its Firmware loaded -+ TOUCH_STS_REG_INIT_STATE_FW_NEEDED, -+ -+ // Touch IC needs its Data loaded -+ TOUCH_STS_REG_INIT_STATE_DATA_NEEDED, -+ -+ // Error info in TOUCH_ERR_REG -+ TOUCH_STS_REG_INIT_STATE_INIT_ERROR, -+ -+ TOUCH_STS_REG_INIT_STATE_MAX -+}; -+static_assert(sizeof(enum touch_sts_reg_init_state) == 4); -+ -+union touch_sts_reg { -+ u32 reg_value; -+ struct { -+ // When set, this indicates the hardware has data -+ // that needs to be read. -+ u32 int_status:1; -+ -+ // see TOUCH_STS_REG_INT_TYPE -+ u32 int_type:4; -+ -+ // see TOUCH_STS_REG_PWR_STATE -+ u32 pwr_state:2; -+ -+ // see TOUCH_STS_REG_INIT_STATE -+ u32 init_state:2; -+ -+ // Busy bit indicates that sensor cannot -+ // accept writes at this time -+ u32 busy:1; -+ -+ // Reserved -+ u32 reserved:14; -+ -+ // Synchronization bit, should always be TOUCH_SYNC_BYTE_VALUE -+ u32 sync_byte:8; -+ } fields; -+}; -+static_assert(sizeof(union touch_sts_reg) == 4); -+ -+union touch_frame_char_reg { -+ u32 reg_value; -+ struct { -+ // Micro-Frame Size (MFS): Indicates the size of a touch -+ // micro-frame in byte increments. When a micro-frame is to be -+ // read for processing (in data mode), this is the total number -+ // of bytes that must be read per interrupt, split into -+ // multiple read commands no longer than RPS. -+ // Maximum micro-frame size is 256KB. -+ u32 microframe_size:18; -+ -+ // Micro-Frames per Frame (MFPF): Indicates the number of -+ // micro-frames per frame. If a sensor's frame does not contain -+ // micro-frames this value will be 1. Valid values are 1-31. -+ u32 microframes_per_frame:5; -+ -+ // Micro-Frame Index (MFI): Indicates the index of the -+ // micro-frame within a frame. This allows the SPI Controller -+ // to maintain synchronization with the sensor and determine -+ // when the final micro-frame has arrived. -+ // Valid values are 1-31. -+ u32 microframe_index:5; -+ -+ // HID/Raw Data: This bit describes whether the data from the -+ // sensor is Raw data or a HID report. When set, the data -+ // is a HID report. -+ u32 hid_report:1; -+ -+ // Reserved -+ u32 reserved:3; -+ } fields; -+}; -+static_assert(sizeof(union touch_frame_char_reg) == 4); -+ -+// bit definition is vendor specific -+union touch_err_reg { -+ u32 reg_value; -+ struct { -+ u32 invalid_fw:1; -+ u32 invalid_data:1; -+ u32 self_test_failed:1; -+ u32 reserved:12; -+ u32 fatal_error:1; -+ u32 vendor_errors:16; -+ } fields; -+}; -+static_assert(sizeof(union touch_err_reg) == 4); -+ -+union touch_data_sz_reg { -+ u32 reg_value; -+ struct { -+ // This value describes the maximum frame size in -+ // 64byte increments. -+ u32 max_frame_size:12; -+ -+ // This value describes the maximum feedback size in -+ // 64byte increments. -+ u32 max_feedback_size:8; -+ -+ // Reserved -+ u32 reserved:12; -+ } fields; -+}; -+static_assert(sizeof(union touch_data_sz_reg) == 4); -+ -+enum touch_caps_reg_read_delay_time { -+ TOUCH_CAPS_REG_READ_DELAY_TIME_0, -+ TOUCH_CAPS_REG_READ_DELAY_TIME_10uS, -+ TOUCH_CAPS_REG_READ_DELAY_TIME_50uS, -+ TOUCH_CAPS_REG_READ_DELAY_TIME_100uS, -+ TOUCH_CAPS_REG_READ_DELAY_TIME_150uS, -+ TOUCH_CAPS_REG_READ_DELAY_TIME_250uS, -+ TOUCH_CAPS_REG_READ_DELAY_TIME_500uS, -+ TOUCH_CAPS_REG_READ_DELAY_TIME_1mS, -+}; -+static_assert(sizeof(enum touch_caps_reg_read_delay_time) == 4); -+ -+union touch_caps_reg { -+ u32 reg_value; -+ struct { -+ // Reserved for future frequency -+ u32 reserved0:1; -+ -+ // 17 MHz (14 MHz on Atom) Supported -+ // 0b - Not supported, 1b - Supported -+ u32 supported_17Mhz:1; -+ -+ // 30 MHz (25MHz on Atom) Supported -+ // 0b - Not supported, 1b - Supported -+ u32 supported_30Mhz:1; -+ -+ // 50 MHz Supported -+ // 0b - Not supported, 1b - Supported -+ u32 supported_50Mhz:1; -+ -+ // Reserved -+ u32 reserved1:4; -+ -+ // Single I/O Supported -+ // 0b - Not supported, 1b - Supported -+ u32 supported_single_io:1; -+ -+ // Dual I/O Supported -+ // 0b - Not supported, 1b - Supported -+ u32 supported_dual_io:1; -+ -+ // Quad I/O Supported -+ // 0b - Not supported, 1b - Supported -+ u32 supported_quad_io:1; -+ -+ // Bulk Data Area Max Write Size: The amount of data the SPI -+ // Controller can write to the bulk data area before it has to -+ // poll the busy bit. This field is in multiples of 64 bytes. -+ // The SPI Controller will write the amount of data specified -+ // in this field, then check and wait for the Status.Busy bit -+ // to be zero before writing the next data chunk. This field is -+ // 6 bits long, allowing for 4KB of contiguous writes w/o a -+ // poll of the busy bit. If this field is 0x00 the Touch IC has -+ // no limit in the amount of data the SPI Controller can write -+ // to the bulk data area. -+ u32 bulk_data_max_write:6; -+ -+ // Read Delay Timer Value: This field describes the delay the -+ // SPI Controller will initiate when a read interrupt follows -+ // a write data command. Uses values from -+ // TOUCH_CAPS_REG_READ_DELAY_TIME -+ u32 read_delay_timer_value:3; -+ -+ // Reserved -+ u32 reserved2:4; -+ -+ // Maximum Touch Points: A byte value based on the -+ // HID descriptor definition. -+ u32 max_touch_points:8; -+ } fields; -+}; -+static_assert(sizeof(union touch_caps_reg) == 4); -+ -+enum touch_cfg_reg_bulk_xfer_size { -+ // Bulk Data Transfer Size is 4 bytes -+ TOUCH_CFG_REG_BULK_XFER_SIZE_4B = 0, -+ -+ // Bulk Data Transfer Size is 8 bytes -+ TOUCH_CFG_REG_BULK_XFER_SIZE_8B, -+ -+ // Bulk Data Transfer Size is 16 bytes -+ TOUCH_CFG_REG_BULK_XFER_SIZE_16B, -+ -+ // Bulk Data Transfer Size is 32 bytes -+ TOUCH_CFG_REG_BULK_XFER_SIZE_32B, -+ -+ // Bulk Data Transfer Size is 64 bytes -+ TOUCH_CFG_REG_BULK_XFER_SIZE_64B, -+ -+ TOUCH_CFG_REG_BULK_XFER_SIZE_MAX -+}; -+static_assert(sizeof(enum touch_cfg_reg_bulk_xfer_size) == 4); -+ -+/* -+ * Frequency values used by TOUCH_CFG_REG -+ * and TOUCH_SENSOR_GET_DEVICE_INFO_RSP_DATA. -+ */ -+enum touch_freq { -+ // Reserved value -+ TOUCH_FREQ_RSVD = 0, -+ -+ // Sensor set for 17MHz operation (14MHz on Atom) -+ TOUCH_FREQ_17MHZ, -+ -+ // Sensor set for 30MHz operation (25MHz on Atom) -+ TOUCH_FREQ_30MHZ, -+ -+ // Invalid value -+ TOUCH_FREQ_MAX -+}; -+static_assert(sizeof(enum touch_freq) == 4); -+ -+union touch_cfg_reg { -+ u32 reg_value; -+ struct { -+ // Touch Enable (TE): This bit is used as a HW semaphore for -+ // the Touch IC to guarantee to the SPI Controller to that -+ // (when 0) no sensing operations will occur and only the Reset -+ // interrupt will be generated. -+ // -+ // When TE is cleared by the SPI -+ // Controller: -+ // - TICs must flush all output buffers -+ // - TICs must De-assert any pending interrupt -+ // - ME must throw away any partial frame and pending -+ // interrupt must be cleared/not serviced. -+ // -+ // The SPI Controller will only modify the configuration of the -+ // TIC when TE is cleared. -+ // TE is defaulted to 0h on a power-on reset. -+ u32 touch_enable:1; -+ -+ // Data/HID Packet Mode (DHPM) -+ // Raw Data Mode: 0h, HID Packet Mode: 1h -+ u32 dhpm:1; -+ -+ // Bulk Data Transfer Size: This field represents the amount -+ // of data written to the Bulk Data Area -+ // (SPI Offset 0x1000-0x2FFF) in a single SPI write protocol -+ u32 bulk_xfer_size:4; -+ -+ // Frequency Select: Frequency for the TouchIC to run at. -+ // Use values from TOUCH_FREQ -+ u32 freq_select:3; -+ -+ // Reserved -+ u32 reserved:23; -+ } fields; -+}; -+static_assert(sizeof(union touch_cfg_reg) == 4); -+ -+enum touch_cmd_reg_code { -+ // No Operation -+ TOUCH_CMD_REG_CODE_NOP = 0, -+ -+ // Soft Reset -+ TOUCH_CMD_REG_CODE_SOFT_RESET, -+ -+ // Prepare All Registers for Read -+ TOUCH_CMD_REG_CODE_PREP_4_READ, -+ -+ // Generate Test Packets according to value in TOUCH_TEST_CTRL_REG -+ TOUCH_CMD_REG_CODE_GEN_TEST_PACKETS, -+ -+ TOUCH_CMD_REG_CODE_MAX -+}; -+static_assert(sizeof(enum touch_cmd_reg_code) == 4); -+ -+union touch_cmd_reg { -+ u32 reg_value; -+ struct { -+ // Command Code: See TOUCH_CMD_REG_CODE -+ u32 command_code:8; -+ -+ // Reserved -+ u32 reserved:24; -+ } fields; -+}; -+static_assert(sizeof(union touch_cmd_reg) == 4); -+ -+enum touch_pwr_mgmt_ctrl_reg_cmd { -+ // No change to power state -+ TOUCH_PWR_MGMT_CTRL_REG_CMD_NOP = 0, -+ -+ // Sleep - set when the system goes into connected standby -+ TOUCH_PWR_MGMT_CTRL_REG_CMD_SLEEP, -+ -+ // Doze - set after 300 seconds of inactivity -+ TOUCH_PWR_MGMT_CTRL_REG_CMD_DOZE, -+ -+ // Armed - Set by FW when a "finger off" message is -+ // received from the EUs -+ TOUCH_PWR_MGMT_CTRL_REG_CMD_ARMED, -+ -+ // Sensing - not typically set by FW -+ TOUCH_PWR_MGMT_CTRL_REG_CMD_SENSING, -+ -+ // Values will result in no change to the power state of the Touch IC -+ TOUCH_PWR_MGMT_CTRL_REG_CMD_MAX -+}; -+static_assert(sizeof(enum touch_pwr_mgmt_ctrl_reg_cmd) == 4); -+ -+union touch_pwr_mgmt_ctrl_reg { -+ u32 reg_value; -+ struct { -+ // Power State Command: See TOUCH_PWR_MGMT_CTRL_REG_CMD -+ u32 pwr_state_cmd:3; -+ -+ // Reserved -+ u32 reserved:29; -+ } fields; -+}; -+static_assert(sizeof(union touch_pwr_mgmt_ctrl_reg) == 4); -+ -+union touch_ven_hw_info_reg { -+ u32 reg_value; -+ struct { -+ // Touch Sensor Vendor ID -+ u32 vendor_id:16; -+ -+ // Touch Sensor Device ID -+ u32 device_id:16; -+ } fields; -+}; -+static_assert(sizeof(union touch_ven_hw_info_reg) == 4); -+ -+union touch_compat_rev_reg { -+ u32 reg_value; -+ -+ struct { -+ // EDS Minor Revision -+ u8 minor; -+ -+ // EDS Major Revision -+ u8 major; -+ -+ // Interface Revision Number (from EDS) -+ u8 intf_rev; -+ -+ // EU Kernel Compatibility Version - vendor specific value -+ u8 kernel_compat_ver; -+ } fields; -+}; -+static_assert(sizeof(union touch_compat_rev_reg) == 4); -+ -+struct touch_reg_block { -+ // 0x00 -+ union touch_sts_reg sts_reg; -+ -+ // 0x04 -+ union touch_frame_char_reg frame_char_reg; -+ -+ // 0x08 -+ union touch_err_reg error_reg; -+ -+ // 0x0C -+ u32 reserved0; -+ -+ // 0x10 - expected value is "$TIC" or 0x43495424 -+ u32 id_reg; -+ -+ // 0x14 -+ union touch_data_sz_reg data_size_reg; -+ -+ // 0x18 -+ union touch_caps_reg caps_reg; -+ -+ // 0x1C -+ union touch_cfg_reg cfg_reg; -+ -+ // 0x20 -+ union touch_cmd_reg cmd_reg; -+ -+ // 0x24 -+ union touch_pwr_mgmt_ctrl_reg pwm_mgme_ctrl_reg; -+ -+ // 0x28 -+ union touch_ven_hw_info_reg ven_hw_info_reg; -+ -+ // 0x2C -+ u32 hw_rev_reg; -+ -+ // 0x30 -+ u32 fw_rev_reg; -+ -+ // 0x34 -+ union touch_compat_rev_reg compat_rev_reg; -+ -+ // 0x38 -+ u32 reserved1; -+ -+ // 0x3C -+ u32 reserved2; -+}; -+static_assert(sizeof(struct touch_reg_block) == 64); -+ -+union touch_test_ctrl_reg { -+ u32 reg_value; -+ struct { -+ // Size of Test Frame in Raw Data Mode: This field specifies -+ // the test frame size in raw data mode in multiple of 64 bytes. -+ // For example, if this field value is 16, the test frame size -+ // will be 16x64 = 1K. -+ u32 raw_test_frame_size:16; -+ -+ // Number of Raw Data Frames or HID Report Packets Generation. -+ // This field represents the number of test frames or HID -+ // reports to be generated when test mode is enabled. When -+ // multiple packets/frames are generated, they need be -+ // generated at 100 Hz frequency, i.e. 10ms per packet/frame. -+ u32 num_test_frames:16; -+ } fields; -+}; -+static_assert(sizeof(union touch_test_ctrl_reg) == 4); -+ -+/* -+ * The following data structures represent the headers defined in the Data -+ * Structures chapter of the Intel Integrated Touch EDS -+ */ -+ -+// Enumeration used in TOUCH_RAW_DATA_HDR -+enum touch_raw_data_types { -+ TOUCH_RAW_DATA_TYPE_FRAME = 0, -+ -+ // RawData will be the TOUCH_ERROR struct below -+ TOUCH_RAW_DATA_TYPE_ERROR, -+ -+ // Set when InterruptType is Vendor Data -+ TOUCH_RAW_DATA_TYPE_VENDOR_DATA, -+ -+ TOUCH_RAW_DATA_TYPE_HID_REPORT, -+ TOUCH_RAW_DATA_TYPE_GET_FEATURES, -+ TOUCH_RAW_DATA_TYPE_MAX -+}; -+static_assert(sizeof(enum touch_raw_data_types) == 4); -+ -+// Private data structure. Kernels must copy to HID driver buffer -+struct touch_hid_private_data { -+ u32 transaction_id; -+ u8 reserved[28]; -+}; -+static_assert(sizeof(struct touch_hid_private_data) == 32); -+ -+// This is the data structure sent from the PCH FW to the EU kernel -+struct touch_raw_data_hdr { -+ // use values from TOUCH_RAW_DATA_TYPES -+ u32 data_type; -+ -+ // The size in bytes of the raw data read from the sensor, does not -+ // include TOUCH_RAW_DATA_HDR. Will be the sum of all uFrames, or size -+ // of TOUCH_ERROR for if DataType is TOUCH_RAW_DATA_TYPE_ERROR -+ u32 raw_data_size_bytes; -+ -+ // An ID to qualify with the feedback data to track buffer usage -+ u32 buffer_id; -+ -+ // Must match protocol version of the EDS -+ u32 protocol_ver; -+ -+ // Copied from the Compatibility Revision ID Reg -+ u8 kernel_compat_id; -+ -+ // Padding to extend header to full 64 bytes and allow for growth -+ u8 reserved[15]; -+ -+ // Private data structure. Kernels must copy to HID driver buffer -+ struct touch_hid_private_data hid_private_data; -+}; -+static_assert(sizeof(struct touch_raw_data_hdr) == 64); -+ -+struct touch_raw_data { -+ struct touch_raw_data_hdr header; -+ -+ // used to access the raw data as an array and keep the compilers -+ // happy. Actual size of this array is Header.RawDataSizeBytes -+ u8 raw_data[1]; -+}; -+ -+/* -+ * The following section describes the data passed in TOUCH_RAW_DATA.RawData -+ * when DataType equals TOUCH_RAW_DATA_TYPE_ERROR -+ * Note: This data structure is also applied to HID mode -+ */ -+enum touch_err_types { -+ TOUCH_RAW_DATA_ERROR = 0, -+ TOUCH_RAW_ERROR_MAX -+}; -+static_assert(sizeof(enum touch_err_types) == 4); -+ -+union touch_me_fw_error { -+ u32 value; -+ struct { -+ u32 invalid_frame_characteristics:1; -+ u32 microframe_index_invalid:1; -+ u32 reserved:30; -+ } fields; -+}; -+static_assert(sizeof(union touch_me_fw_error) == 4); -+ -+struct touch_error { -+ // This must be a value from TOUCH_ERROR_TYPES -+ u8 touch_error_type; -+ u8 reserved[3]; -+ union touch_me_fw_error touch_me_fw_error; -+ -+ // Contains the value copied from the Touch Error Reg -+ union touch_err_reg touch_error_register; -+}; -+static_assert(sizeof(struct touch_error) == 12); -+ -+// Enumeration used in TOUCH_FEEDBACK_BUFFER -+enum touch_feedback_cmd_types { -+ TOUCH_FEEDBACK_CMD_TYPE_NONE = 0, -+ TOUCH_FEEDBACK_CMD_TYPE_SOFT_RESET, -+ TOUCH_FEEDBACK_CMD_TYPE_GOTO_ARMED, -+ TOUCH_FEEDBACK_CMD_TYPE_GOTO_SENSING, -+ TOUCH_FEEDBACK_CMD_TYPE_GOTO_SLEEP, -+ TOUCH_FEEDBACK_CMD_TYPE_GOTO_DOZE, -+ TOUCH_FEEDBACK_CMD_TYPE_HARD_RESET, -+ TOUCH_FEEDBACK_CMD_TYPE_MAX -+}; -+static_assert(sizeof(enum touch_feedback_cmd_types) == 4); -+ -+// Enumeration used in TOUCH_FEEDBACK_HDR -+enum touch_feedback_data_types { -+ // This is vendor specific feedback to be written to the sensor -+ TOUCH_FEEDBACK_DATA_TYPE_FEEDBACK = 0, -+ -+ // This is a set features command to be written to the sensor -+ TOUCH_FEEDBACK_DATA_TYPE_SET_FEATURES, -+ -+ // This is a get features command to be written to the sensor -+ TOUCH_FEEDBACK_DATA_TYPE_GET_FEATURES, -+ -+ // This is a HID output report to be written to the sensor -+ TOUCH_FEEDBACK_DATA_TYPE_OUTPUT_REPORT, -+ -+ // This is calibration data to be written to system flash -+ TOUCH_FEEDBACK_DATA_TYPE_STORE_DATA, -+ -+ TOUCH_FEEDBACK_DATA_TYPE_MAX -+}; -+static_assert(sizeof(enum touch_feedback_data_types) == 4); -+ -+/* -+ * This is the data structure sent from the EU kernels back to the ME FW. -+ * In addition to "feedback" data, the FW can execute a "command" described by -+ * the command type parameter. Any payload data will always be sent to the TIC -+ * first, then any command will be issued. -+ */ -+struct touch_feedback_hdr { -+ // use values from TOUCH_FEEDBACK_CMD_TYPES -+ u32 feedback_cmd_type; -+ -+ // The amount of data to be written to the sensor, -+ // not including the header -+ u32 payload_size_bytes; -+ -+ // The ID of the raw data buffer that generated this feedback data -+ u32 buffer_id; -+ -+ // Must match protocol version of the EDS -+ u32 protocol_ver; -+ -+ // use values from TOUCH_FEEDBACK_DATA_TYPES. This is not relevant -+ // if PayloadSizeBytes is 0 -+ u32 feedback_data_type; -+ -+ // The offset from TOUCH_DATA_WINDOW_OFFSET at which to write the -+ // Payload data. Maximum offset is 0x1EFFF. -+ u32 spi_offest; -+ -+ // Padding to extend header to full 64 bytes and allow for growth -+ u8 reserved[40]; -+}; -+static_assert(sizeof(struct touch_feedback_hdr) == 64); -+ -+struct touch_feedback_buffer { -+ struct touch_feedback_hdr Header; -+ -+ // used to access the feedback data as an array and keep the compilers -+ // happy. Actual size of this array is Header.PayloadSizeBytes -+ u8 feedback_data[1]; -+}; -+ -+/* -+ * This data structure describes the header prepended to all data -+ * written to the touch IC at the bulk data write -+ * (TOUCH_DATA_WINDOW_OFFSET + TOUCH_FEEDBACK_HDR.SpiOffest) address. -+ */ -+enum touch_write_data_type { -+ TOUCH_WRITE_DATA_TYPE_FW_LOAD = 0, -+ TOUCH_WRITE_DATA_TYPE_DATA_LOAD, -+ TOUCH_WRITE_DATA_TYPE_FEEDBACK, -+ TOUCH_WRITE_DATA_TYPE_SET_FEATURES, -+ TOUCH_WRITE_DATA_TYPE_GET_FEATURES, -+ TOUCH_WRITE_DATA_TYPE_OUTPUT_REPORT, -+ TOUCH_WRITE_DATA_TYPE_NO_DATA_USE_DEFAULTS, -+ TOUCH_WRITE_DATA_TYPE_MAX -+}; -+static_assert(sizeof(enum touch_write_data_type) == 4); -+ -+struct touch_write_hdr { -+ // Use values from TOUCH_WRITE_DATA_TYPE -+ u32 write_data_type; -+ -+ // This field designates the amount of data to follow -+ u32 write_data_len; -+}; -+static_assert(sizeof(struct touch_write_hdr) == 8); -+ -+struct touch_write_data { -+ struct touch_write_hdr header; -+ -+ // used to access the write data as an array and keep the compilers -+ // happy. Actual size of this array is Header.WriteDataLen -+ u8 write_data[1]; -+}; -+ -+#pragma pack() -+ -+#endif // _IPTS_SENSOR_REGS_H_ -diff --git a/drivers/misc/ipts/state.h b/drivers/misc/ipts/state.h -new file mode 100644 -index 000000000000..ef73d28db47c ---- /dev/null -+++ b/drivers/misc/ipts/state.h -@@ -0,0 +1,22 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#ifndef _IPTS_STATE_H_ -+#define _IPTS_STATE_H_ -+ -+// IPTS driver states -+enum ipts_state { -+ IPTS_STA_NONE, -+ IPTS_STA_INIT, -+ IPTS_STA_RESOURCE_READY, -+ IPTS_STA_HID_STARTED, -+ IPTS_STA_RAW_DATA_STARTED, -+ IPTS_STA_STOPPING -+}; -+ -+#endif // _IPTS_STATE_H_ -diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h -index 2ac1dc5104b7..5daa857a4938 100644 ---- a/drivers/misc/mei/hw-me-regs.h -+++ b/drivers/misc/mei/hw-me-regs.h -@@ -119,6 +119,7 @@ - - #define MEI_DEV_ID_SPT 0x9D3A /* Sunrise Point */ - #define MEI_DEV_ID_SPT_2 0x9D3B /* Sunrise Point 2 */ -+#define MEI_DEV_ID_SPT_4 0x9D3E /* Sunrise Point 4 */ - #define MEI_DEV_ID_SPT_H 0xA13A /* Sunrise Point H */ - #define MEI_DEV_ID_SPT_H_2 0xA13B /* Sunrise Point H 2 */ - -diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c -index b4bf12f27caf..34f4338fa641 100644 ---- a/drivers/misc/mei/pci-me.c -+++ b/drivers/misc/mei/pci-me.c -@@ -86,6 +86,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { - - {MEI_PCI_DEVICE(MEI_DEV_ID_SPT, MEI_ME_PCH8_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_2, MEI_ME_PCH8_CFG)}, -+ {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_4, MEI_ME_PCH8_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H, MEI_ME_PCH8_SPS_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H_2, MEI_ME_PCH8_SPS_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_LBG, MEI_ME_PCH12_CFG)}, -diff --git a/include/linux/ipts-binary.h b/include/linux/ipts-binary.h -new file mode 100644 -index 000000000000..98b54d74ff88 ---- /dev/null -+++ b/include/linux/ipts-binary.h -@@ -0,0 +1,140 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#ifndef IPTS_BINARY_H -+#define IPTS_BINARY_H -+ -+#include -+#include -+ -+#define IPTS_BIN_HEADER_VERSION 2 -+ -+#pragma pack(1) -+ -+// we support 16 output buffers (1:feedback, 15:HID) -+#define MAX_NUM_OUTPUT_BUFFERS 16 -+ -+enum ipts_bin_res_type { -+ IPTS_BIN_KERNEL, -+ IPTS_BIN_RO_DATA, -+ IPTS_BIN_RW_DATA, -+ IPTS_BIN_SENSOR_FRAME, -+ IPTS_BIN_OUTPUT, -+ IPTS_BIN_DYNAMIC_STATE_HEAP, -+ IPTS_BIN_PATCH_LOCATION_LIST, -+ IPTS_BIN_ALLOCATION_LIST, -+ IPTS_BIN_COMMAND_BUFFER_PACKET, -+ IPTS_BIN_TAG, -+}; -+ -+struct ipts_bin_header { -+ char str[4]; -+ u32 version; -+ -+#if IPTS_BIN_HEADER_VERSION > 1 -+ u32 gfxcore; -+ u32 revid; -+#endif -+}; -+ -+struct ipts_bin_alloc { -+ u32 handle; -+ u32 reserved; -+}; -+ -+struct ipts_bin_alloc_list { -+ u32 num; -+ struct ipts_bin_alloc alloc[]; -+}; -+ -+struct ipts_bin_cmdbuf { -+ u32 size; -+ char data[]; -+}; -+ -+struct ipts_bin_res { -+ u32 handle; -+ enum ipts_bin_res_type type; -+ u32 initialize; -+ u32 aligned_size; -+ u32 size; -+ char data[]; -+}; -+ -+enum ipts_bin_io_buffer_type { -+ IPTS_INPUT, -+ IPTS_OUTPUT, -+ IPTS_CONFIGURATION, -+ IPTS_CALIBRATION, -+ IPTS_FEATURE, -+}; -+ -+struct ipts_bin_io_header { -+ char str[10]; -+ u16 type; -+}; -+ -+struct ipts_bin_res_list { -+ u32 num; -+ struct ipts_bin_res res[]; -+}; -+ -+struct ipts_bin_patch { -+ u32 index; -+ u32 reserved1[2]; -+ u32 alloc_offset; -+ u32 patch_offset; -+ u32 reserved2; -+}; -+ -+struct ipts_bin_patch_list { -+ u32 num; -+ struct ipts_bin_patch patch[]; -+}; -+ -+struct ipts_bin_guc_wq_info { -+ u32 batch_offset; -+ u32 size; -+ char data[]; -+}; -+ -+struct ipts_bin_bufid_patch { -+ u32 imm_offset; -+ u32 mem_offset; -+}; -+ -+enum ipts_bin_data_file_flags { -+ IPTS_DATA_FILE_FLAG_NONE = 0, -+ IPTS_DATA_FILE_FLAG_SHARE = 1, -+ IPTS_DATA_FILE_FLAG_ALLOC_CONTIGUOUS = 2, -+}; -+ -+struct ipts_bin_data_file_info { -+ u32 io_buffer_type; -+ u32 flags; -+ char file_name[MAX_IOCL_FILE_NAME_LEN]; -+}; -+ -+struct ipts_bin_fw_info { -+ char fw_name[MAX_IOCL_FILE_NAME_LEN]; -+ -+ // output index. -1 for no use -+ s32 vendor_output; -+ -+ u32 num_of_data_files; -+ struct ipts_bin_data_file_info data_file[]; -+}; -+ -+struct ipts_bin_fw_list { -+ u32 num_of_fws; -+ struct ipts_bin_fw_info fw_info[]; -+}; -+ -+#pragma pack() -+ -+#endif // IPTS_BINARY_H -diff --git a/include/linux/ipts-companion.h b/include/linux/ipts-companion.h -new file mode 100644 -index 000000000000..de31f5e0b186 ---- /dev/null -+++ b/include/linux/ipts-companion.h -@@ -0,0 +1,29 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2019 Dorian Stoll -+ * -+ */ -+ -+#ifndef IPTS_COMPANION_H -+#define IPTS_COMPANION_H -+ -+#include -+#include -+ -+struct ipts_companion { -+ int (*firmware_request)(struct ipts_companion *companion, -+ const struct firmware **fw, -+ const char *name, struct device *device); -+ -+ struct ipts_bin_fw_info **firmware_config; -+ void *data; -+ const char *name; -+}; -+ -+int ipts_add_companion(struct ipts_companion *companion); -+int ipts_remove_companion(struct ipts_companion *companion); -+ -+#endif // IPTS_COMPANION_H -diff --git a/include/linux/ipts-gfx.h b/include/linux/ipts-gfx.h -new file mode 100644 -index 000000000000..cb9d98fe96e4 ---- /dev/null -+++ b/include/linux/ipts-gfx.h -@@ -0,0 +1,86 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#ifndef IPTS_GFX_H -+#define IPTS_GFX_H -+ -+enum { -+ IPTS_INTERFACE_V1 = 1, -+}; -+ -+#define IPTS_BUF_FLAG_CONTIGUOUS 0x01 -+ -+#define IPTS_NOTIFY_STA_BACKLIGHT_OFF 0x00 -+#define IPTS_NOTIFY_STA_BACKLIGHT_ON 0x01 -+ -+struct ipts_mapbuffer { -+ u32 size; -+ u32 flags; -+ void *gfx_addr; -+ void *cpu_addr; -+ u64 buf_handle; -+ u64 phy_addr; -+}; -+ -+struct ipts_wq_info { -+ u64 db_addr; -+ u64 db_phy_addr; -+ u32 db_cookie_offset; -+ u32 wq_size; -+ u64 wq_addr; -+ u64 wq_phy_addr; -+ -+ // head of wq is managed by GPU -+ u64 wq_head_addr; -+ u64 wq_head_phy_addr; -+ -+ // tail of wq is managed by CSME -+ u64 wq_tail_addr; -+ u64 wq_tail_phy_addr; -+}; -+ -+struct ipts_ops { -+ int (*get_wq_info)(uint64_t gfx_handle, -+ struct ipts_wq_info *wq_info); -+ int (*map_buffer)(uint64_t gfx_handle, -+ struct ipts_mapbuffer *mapbuffer); -+ int (*unmap_buffer)(uint64_t gfx_handle, uint64_t buf_handle); -+}; -+ -+struct ipts_callback { -+ void (*workload_complete)(void *data); -+ void (*notify_gfx_status)(u32 status, void *data); -+}; -+ -+struct ipts_connect { -+ // input: Client device for PM setup -+ struct device *client; -+ -+ // input: Callback addresses -+ struct ipts_callback ipts_cb; -+ -+ // input: Callback data -+ void *data; -+ -+ // input: interface version -+ u32 if_version; -+ -+ // output: GFX version -+ u32 gfx_version; -+ -+ // output: GFX handle -+ u64 gfx_handle; -+ -+ // output: GFX ops for IPTS -+ struct ipts_ops ipts_ops; -+}; -+ -+int ipts_connect(struct ipts_connect *ipts_connect); -+void ipts_disconnect(uint64_t gfx_handle); -+ -+#endif // IPTS_GFX_H -diff --git a/include/linux/ipts.h b/include/linux/ipts.h -new file mode 100644 -index 000000000000..f229a3436851 ---- /dev/null -+++ b/include/linux/ipts.h -@@ -0,0 +1,19 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * -+ * Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation -+ * -+ */ -+ -+#ifndef IPTS_H -+#define IPTS_H -+ -+#include -+ -+#define MAX_IOCL_FILE_NAME_LEN 80 -+#define MAX_IOCL_FILE_PATH_LEN 256 -+ -+#define IPTS_QUIRK_NONE 0 -+ -+#endif // IPTS_H --- -2.33.0 - diff --git a/patches/4.19/0007-wifi.patch b/patches/4.19/0007-wifi.patch deleted file mode 100644 index fa62a1a6b..000000000 --- a/patches/4.19/0007-wifi.patch +++ /dev/null @@ -1,2571 +0,0 @@ -From 537f98362192d5812c1192e5707f3151307ae062 Mon Sep 17 00:00:00 2001 -From: Chuhong Yuan -Date: Wed, 24 Jul 2019 19:27:45 +0800 -Subject: [PATCH] mwifiex: pcie: Use dev_get_drvdata - -Instead of using to_pci_dev + pci_get_drvdata, -use dev_get_drvdata to make code simpler. - -Signed-off-by: Chuhong Yuan -Signed-off-by: Kalle Valo - -(cherry picked from commit ffa4d78cbc2644b4867b8129b3fbb5ddcdfcdba2) -Reason for cherry-picking this commit: - to avoid conflicts when backporting incoming commits -Signed-off-by: Tsuchiya Yuto (kitakar5525) -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 8 ++------ - 1 file changed, 2 insertions(+), 6 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 2f0141c964e2..a822f8524737 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -150,10 +150,8 @@ static bool mwifiex_pcie_ok_to_access_hw(struct mwifiex_adapter *adapter) - static int mwifiex_pcie_suspend(struct device *dev) - { - struct mwifiex_adapter *adapter; -- struct pcie_service_card *card; -- struct pci_dev *pdev = to_pci_dev(dev); -+ struct pcie_service_card *card = dev_get_drvdata(dev); - -- card = pci_get_drvdata(pdev); - - /* Might still be loading firmware */ - wait_for_completion(&card->fw_done); -@@ -195,10 +193,8 @@ static int mwifiex_pcie_suspend(struct device *dev) - static int mwifiex_pcie_resume(struct device *dev) - { - struct mwifiex_adapter *adapter; -- struct pcie_service_card *card; -- struct pci_dev *pdev = to_pci_dev(dev); -+ struct pcie_service_card *card = dev_get_drvdata(dev); - -- card = pci_get_drvdata(pdev); - - if (!card->adapter) { - dev_err(dev, "adapter structure is not valid\n"); --- -2.33.0 - -From 08e679300b1d7825da5667bc8d1bbd024d64adaf Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Mon, 28 Sep 2020 17:46:49 +0900 -Subject: [PATCH] mwifiex: pcie: add DMI-based quirk impl for Surface devices - -This commit adds quirk implementation based on DMI matching with DMI -table for Surface devices. - -This implementation can be used for quirks later. - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/Makefile | 1 + - drivers/net/wireless/marvell/mwifiex/pcie.c | 4 + - drivers/net/wireless/marvell/mwifiex/pcie.h | 1 + - .../wireless/marvell/mwifiex/pcie_quirks.c | 114 ++++++++++++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.h | 11 ++ - 5 files changed, 131 insertions(+) - create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.c - create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.h - -diff --git a/drivers/net/wireless/marvell/mwifiex/Makefile b/drivers/net/wireless/marvell/mwifiex/Makefile -index fdfd9bf15ed4..8a1e7c5b9c6e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/Makefile -+++ b/drivers/net/wireless/marvell/mwifiex/Makefile -@@ -49,6 +49,7 @@ mwifiex_sdio-y += sdio.o - obj-$(CONFIG_MWIFIEX_SDIO) += mwifiex_sdio.o - - mwifiex_pcie-y += pcie.o -+mwifiex_pcie-y += pcie_quirks.o - obj-$(CONFIG_MWIFIEX_PCIE) += mwifiex_pcie.o - - mwifiex_usb-y += usb.o -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index a822f8524737..9d12a0b726a3 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -27,6 +27,7 @@ - #include "wmm.h" - #include "11n.h" - #include "pcie.h" -+#include "pcie_quirks.h" - - #define PCIE_VERSION "1.0" - #define DRV_NAME "Marvell mwifiex PCIe" -@@ -261,6 +262,9 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return ret; - } - -+ /* check quirks */ -+ mwifiex_initialize_quirks(card); -+ - if (mwifiex_add_card(card, &card->fw_done, &pcie_ops, - MWIFIEX_PCIE, &pdev->dev)) { - pr_err("%s failed\n", __func__); -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.h b/drivers/net/wireless/marvell/mwifiex/pcie.h -index 72d0c01ff359..f7e968306a0c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.h -@@ -393,6 +393,7 @@ struct pcie_service_card { - unsigned long work_flags; - - bool pci_reset_ongoing; -+ unsigned long quirks; - }; - - static inline int -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -new file mode 100644 -index 000000000000..929aee2b0a60 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -0,0 +1,114 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * File for PCIe quirks. -+ */ -+ -+/* The low-level PCI operations will be performed in this file. Therefore, -+ * let's use dev_*() instead of mwifiex_dbg() here to avoid troubles (e.g. -+ * to avoid using mwifiex_adapter struct before init or wifi is powered -+ * down, or causes NULL ptr deref). -+ */ -+ -+#include -+ -+#include "pcie_quirks.h" -+ -+/* quirk table based on DMI matching */ -+static const struct dmi_system_id mwifiex_quirk_table[] = { -+ { -+ .ident = "Surface Pro 4", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 5", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 5 (LTE)", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 6", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Book 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Book 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Laptop 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Laptop 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 3"), -+ }, -+ .driver_data = 0, -+ }, -+ {} -+}; -+ -+void mwifiex_initialize_quirks(struct pcie_service_card *card) -+{ -+ struct pci_dev *pdev = card->dev; -+ const struct dmi_system_id *dmi_id; -+ -+ dmi_id = dmi_first_match(mwifiex_quirk_table); -+ if (dmi_id) -+ card->quirks = (uintptr_t)dmi_id->driver_data; -+ -+ if (!card->quirks) -+ dev_info(&pdev->dev, "no quirks enabled\n"); -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -new file mode 100644 -index 000000000000..5326ae7e5671 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -0,0 +1,11 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* -+ * Header file for PCIe quirks. -+ */ -+ -+#include "pcie.h" -+ -+/* quirks */ -+// quirk flags can be added here -+ -+void mwifiex_initialize_quirks(struct pcie_service_card *card); --- -2.33.0 - -From 663283113cc8744300c2113b7e1777ba57da49be Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:25:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_d3cold quirk for Surface gen4+ - devices - -To reset mwifiex on Surface gen4+ (Pro 4 or later gen) devices, it -seems that putting the wifi device into D3cold is required according -to errata.inf file on Windows installation (Windows/INF/errata.inf). - -This patch adds a function that performs power-cycle (put into D3cold -then D0) and call the function at the end of reset_prepare(). - -Note: Need to also reset the parent device (bridge) of wifi on SB1; -it might be because the bridge of wifi always reports it's in D3hot. -When I tried to reset only the wifi device (not touching parent), it gave -the following error and the reset failed: - - acpi device:4b: Cannot transition to power state D0 for parent in D3hot - mwifiex_pcie 0000:03:00.0: can't change power state from D3cold to D0 (config space inaccessible) - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 ++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 73 +++++++++++++++++-- - .../wireless/marvell/mwifiex/pcie_quirks.h | 3 +- - 3 files changed, 74 insertions(+), 9 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 9d12a0b726a3..4613e8cb2431 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -380,6 +380,13 @@ static void mwifiex_pcie_reset_prepare(struct pci_dev *pdev) - mwifiex_shutdown_sw(adapter); - clear_bit(MWIFIEX_IFACE_WORK_DEVICE_DUMP, &card->work_flags); - clear_bit(MWIFIEX_IFACE_WORK_CARD_RESET, &card->work_flags); -+ -+ /* For Surface gen4+ devices, we need to put wifi into D3cold right -+ * before performing FLR -+ */ -+ if (card->quirks & QUIRK_FW_RST_D3COLD) -+ mwifiex_pcie_reset_d3cold_quirk(pdev); -+ - mwifiex_dbg(adapter, INFO, "%s, successful\n", __func__); - - card->pci_reset_ongoing = true; -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 929aee2b0a60..edc739c542fe 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -21,7 +21,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 5", -@@ -30,7 +30,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -39,7 +39,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 6", -@@ -47,7 +47,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Book 1", -@@ -55,7 +55,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Book 2", -@@ -63,7 +63,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Laptop 1", -@@ -71,7 +71,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Laptop 2", -@@ -79,7 +79,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface 3", -@@ -111,4 +111,61 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - - if (!card->quirks) - dev_info(&pdev->dev, "no quirks enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_D3COLD) -+ dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+} -+ -+static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -+{ -+ dev_info(&pdev->dev, "putting into D3cold...\n"); -+ -+ pci_save_state(pdev); -+ if (pci_is_enabled(pdev)) -+ pci_disable_device(pdev); -+ pci_set_power_state(pdev, PCI_D3cold); -+} -+ -+static int mwifiex_pcie_set_power_d0(struct pci_dev *pdev) -+{ -+ int ret; -+ -+ dev_info(&pdev->dev, "putting into D0...\n"); -+ -+ pci_set_power_state(pdev, PCI_D0); -+ ret = pci_enable_device(pdev); -+ if (ret) { -+ dev_err(&pdev->dev, "pci_enable_device failed\n"); -+ return ret; -+ } -+ pci_restore_state(pdev); -+ -+ return 0; -+} -+ -+int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) -+{ -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); -+ int ret; -+ -+ /* Power-cycle (put into D3cold then D0) */ -+ dev_info(&pdev->dev, "Using reset_d3cold quirk to perform FW reset\n"); -+ -+ /* We need to perform power-cycle also for bridge of wifi because -+ * on some devices (e.g. Surface Book 1), the OS for some reasons -+ * can't know the real power state of the bridge. -+ * When tried to power-cycle only wifi, the reset failed with the -+ * following dmesg log: -+ * "Cannot transition to power state D0 for parent in D3hot". -+ */ -+ mwifiex_pcie_set_power_d3cold(pdev); -+ mwifiex_pcie_set_power_d3cold(parent_pdev); -+ -+ ret = mwifiex_pcie_set_power_d0(parent_pdev); -+ if (ret) -+ return ret; -+ ret = mwifiex_pcie_set_power_d0(pdev); -+ if (ret) -+ return ret; -+ -+ return 0; - } -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 5326ae7e5671..8b9dcb5070d8 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -6,6 +6,7 @@ - #include "pcie.h" - - /* quirks */ --// quirk flags can be added here -+#define QUIRK_FW_RST_D3COLD BIT(0) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); -+int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.33.0 - -From f12e03a06906f7992f03b992796c6ce19b75a215 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:32:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_wsid quirk for Surface 3 - -This commit adds reset_wsid quirk and uses this quirk for Surface 3 on -card reset. - -To reset mwifiex on Surface 3, it seems that calling the _DSM method -exists in \_SB.WSID [1] device is required. - -On Surface 3, calling the _DSM method removes/re-probes the card by -itself. So, need to place the reset function before performing FLR and -skip performing any other reset-related works. - -Note that Surface Pro 3 also has the WSID device [2], but it seems to need -more work. This commit only supports Surface 3 yet. - -[1] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_3/dsdt.dsl#L11947-L12011 -[2] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_pro_3/dsdt.dsl#L12164-L12216 - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 10 +++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 77 ++++++++++++++++++- - .../wireless/marvell/mwifiex/pcie_quirks.h | 5 ++ - 3 files changed, 91 insertions(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 4613e8cb2431..8b1412c49bc2 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -2815,6 +2815,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - -+ /* On Surface 3, reset_wsid method removes then re-probes card by -+ * itself. So, need to place it here and skip performing any other -+ * reset-related works. -+ */ -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) { -+ mwifiex_pcie_reset_wsid_quirk(card->dev); -+ /* skip performing any other reset-related works */ -+ return; -+ } -+ - /* We can't afford to wait here; remove() might be waiting on us. If we - * can't grab the device lock, maybe we'll get another chance later. - */ -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index edc739c542fe..f0a6fa0a7ae5 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -9,10 +9,21 @@ - * down, or causes NULL ptr deref). - */ - -+#include - #include - - #include "pcie_quirks.h" - -+/* For reset_wsid quirk */ -+#define ACPI_WSID_PATH "\\_SB.WSID" -+#define WSID_REV 0x0 -+#define WSID_FUNC_WIFI_PWR_OFF 0x1 -+#define WSID_FUNC_WIFI_PWR_ON 0x2 -+/* WSID _DSM UUID: "534ea3bf-fcc2-4e7a-908f-a13978f0c7ef" */ -+static const guid_t wsid_dsm_guid = -+ GUID_INIT(0x534ea3bf, 0xfcc2, 0x4e7a, -+ 0x90, 0x8f, 0xa1, 0x39, 0x78, 0xf0, 0xc7, 0xef); -+ - /* quirk table based on DMI matching */ - static const struct dmi_system_id mwifiex_quirk_table[] = { - { -@@ -87,7 +98,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, - { - .ident = "Surface Pro 3", -@@ -113,6 +124,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - dev_info(&pdev->dev, "no quirks enabled\n"); - if (card->quirks & QUIRK_FW_RST_D3COLD) - dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) -+ dev_info(&pdev->dev, -+ "quirk reset_wsid for Surface 3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -@@ -169,3 +183,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) - - return 0; - } -+ -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev) -+{ -+ acpi_handle handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n"); -+ -+ status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&pdev->dev, "No ACPI handle for path %s\n", -+ ACPI_WSID_PATH); -+ return -ENODEV; -+ } -+ -+ if (!acpi_has_method(handle, "_DSM")) { -+ dev_err(&pdev->dev, "_DSM method not found\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power off func\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power on func\n"); -+ return -ENODEV; -+ } -+ -+ /* card will be removed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi off...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi off\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ /* card will be re-probed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi on...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi on\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ return 0; -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 8b9dcb5070d8..3ef7440418e3 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -7,6 +7,11 @@ - - /* quirks */ - #define QUIRK_FW_RST_D3COLD BIT(0) -+/* Surface 3 and Surface Pro 3 have the same _DSM method but need to -+ * be handled differently. Currently, only S3 is supported. -+ */ -+#define QUIRK_FW_RST_WSID_S3 BIT(1) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); --- -2.33.0 - -From 1792b38ea0d695b96ddd03ef2385b1b39052ca7a Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Wed, 30 Sep 2020 18:08:24 +0900 -Subject: [PATCH] mwifiex: pcie: (OEMB) add quirk for Surface 3 with broken DMI - table - -(made referring to http://git.osdn.net/view?p=android-x86/kernel.git;a=commitdiff;h=18e2e857c57633b25b3b4120f212224a108cd883) - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI info for the affected Surface 3. - -On affected systems, DMI info will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie_quirks.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index f0a6fa0a7ae5..34dcd84f02a6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -100,6 +100,15 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - { - .ident = "Surface Pro 3", - .matches = { --- -2.33.0 - -From f9db59aedf1fab30389305390e9d691f782b5521 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 4 Oct 2020 00:11:49 +0900 -Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ - -Currently, mwifiex fw will crash after suspend on recent kernel series. -On Windows, it seems that the root port of wifi will never enter D3 state -(stay on D0 state). And on Linux, disabling the D3 state for the -bridge fixes fw crashing after suspend. - -This commit disables the D3 state of root port on driver initialization -and fixes fw crashing after suspend. - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 27 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 8b1412c49bc2..21f7a913978d 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -230,6 +230,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) - { - struct pcie_service_card *card; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - int ret; - - pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", -@@ -271,6 +272,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return -1; - } - -+ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing -+ * after suspend -+ */ -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ parent_pdev->bridge_d3 = false; -+ - return 0; - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 34dcd84f02a6..a2aeb2af907e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -32,7 +32,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5", -@@ -41,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -50,7 +52,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 6", -@@ -58,7 +61,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 1", -@@ -66,7 +70,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 2", -@@ -74,7 +79,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 1", -@@ -82,7 +88,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 2", -@@ -90,7 +97,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface 3", -@@ -136,6 +144,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_FW_RST_WSID_S3) - dev_info(&pdev->dev, - "quirk reset_wsid for Surface 3 enabled\n"); -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ dev_info(&pdev->dev, -+ "quirk no_brigde_d3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 3ef7440418e3..a95ebac06e13 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -11,6 +11,7 @@ - * be handled differently. Currently, only S3 is supported. - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) -+#define QUIRK_NO_BRIDGE_D3 BIT(2) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.33.0 - -From 652e4e9d07e50238a2af5f17eb44881f2eca2daa Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 10 Nov 2020 12:49:56 +0100 -Subject: [PATCH] mwifiex: Use non-posted PCI register writes - -On the 88W8897 card it's very important the TX ring write pointer is -updated correctly to its new value before setting the TX ready -interrupt, otherwise the firmware appears to crash (probably because -it's trying to DMA-read from the wrong place). - -Since PCI uses "posted writes" when writing to a register, it's not -guaranteed that a write will happen immediately. That means the pointer -might be outdated when setting the TX ready interrupt, leading to -firmware crashes especially when ASPM L1 and L1 substates are enabled -(because of the higher link latency, the write will probably take -longer). - -So fix those firmware crashes by always forcing non-posted writes. We do -that by simply reading back the register after writing it, just as a lot -of other drivers do. - -There are two reproducers that are fixed with this patch: - -1) During rx/tx traffic and with ASPM L1 substates enabled (the enabled -substates are platform dependent), the firmware crashes and eventually a -command timeout appears in the logs. That crash is fixed by using a -non-posted write in mwifiex_pcie_send_data(). - -2) When sending lots of commands to the card, waking it up from sleep in -very quick intervals, the firmware eventually crashes. That crash -appears to be fixed by some other non-posted write included here. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 21f7a913978d..0488b9610fa3 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -88,6 +88,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) - - iowrite32(data, card->pci_mmap1 + reg); - -+ /* Do a read-back, which makes the write non-posted, ensuring the -+ * completion before returning. -+ * The firmware of the 88W8897 card is buggy and this avoids crashes. -+ */ -+ ioread32(card->pci_mmap1 + reg); -+ - return 0; - } - --- -2.33.0 - -From 13a1cd06e88b37cebf30ba50445f9bda87032dc5 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 3 Nov 2020 13:28:04 +0100 -Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface - devices - -The most recent firmware of the 88W8897 card reports a hardcoded LTR -value to the system during initialization, probably as an (unsuccessful) -attempt of the developers to fix firmware crashes. This LTR value -prevents most of the Microsoft Surface devices from entering deep -powersaving states (either platform C-State 10 or S0ix state), because -the exit latency of that state would be higher than what the card can -tolerate. - -Turns out the card works just the same (including the firmware crashes) -no matter if that hardcoded LTR value is reported or not, so it's kind -of useless and only prevents us from saving power. - -To get rid of those hardcoded LTR reports, it's possible to reset the -PCI bridge device after initializing the cards firmware. I'm not exactly -sure why that works, maybe the power management subsystem of the PCH -resets its stored LTR values when doing a function level reset of the -bridge device. Doing the reset once after starting the wifi firmware -works very well, probably because the firmware only reports that LTR -value a single time during firmware startup. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 31 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 0488b9610fa3..1ad41c6e14ff 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -1608,9 +1608,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) - static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; -+ struct pci_dev *pdev = card->dev; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; - int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; - -+ /* Trigger a function level reset of the PCI bridge device, this makes -+ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value -+ * that prevents the system from entering package C10 and S0ix powersaving -+ * states. -+ * We need to do it here because it must happen after firmware -+ * initialization and this function is called after that is done. -+ */ -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ pci_reset_function(parent_pdev); -+ - /* Write the RX ring read pointer in to reg->rx_rdptr */ - if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | - tx_wrap)) { -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index a2aeb2af907e..6885575826a6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -33,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5", -@@ -43,7 +44,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -53,7 +55,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 6", -@@ -62,7 +65,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 1", -@@ -71,7 +75,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 2", -@@ -80,7 +85,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 1", -@@ -89,7 +95,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 2", -@@ -98,7 +105,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface 3", -@@ -147,6 +155,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_NO_BRIDGE_D3) - dev_info(&pdev->dev, - "quirk no_brigde_d3 enabled\n"); -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index a95ebac06e13..4ec2ae72f632 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -12,6 +12,7 @@ - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) - #define QUIRK_NO_BRIDGE_D3 BIT(2) -+#define QUIRK_DO_FLR_ON_BRIDGE BIT(3) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.33.0 - -From cec09dc4302c324e652452b81b8609a044b3fc39 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sun, 28 Mar 2021 21:10:06 +0200 -Subject: [PATCH] mwifiex: Try waking the firmware until we get an interrupt - -It seems that the firmware of the 88W8897 card sometimes ignores or -misses when we try to wake it up by reading the firmware status -register. This leads to the firmware wakeup timeout expiring and the -driver resetting the card because we assume the firmware has hung up or -crashed (unfortunately that's not unlikely with this card). - -Turns out that most of the time the firmware actually didn't hang up, -but simply "missed" our wakeup request and doesn't send us an AWAKE -event. - -Trying again to read the firmware status register after a short timeout -usually makes the firmware wake we up as expected, so add a small retry -loop to mwifiex_pm_wakeup_card() that looks at the interrupt status to -check whether the card woke up. - -The number of tries and timeout lengths for this were determined -experimentally: The firmware usually takes about 500 us to wake up -after we attempt to read the status register. In some cases where the -firmware is very busy (for example while doing a bluetooth scan) it -might even miss our requests for multiple milliseconds, which is why -after 15 tries the waiting time gets increased to 10 ms. The maximum -number of tries it took to wake the firmware when testing this was -around 20, so a maximum number of 50 tries should give us plenty of -safety margin. - -A good reproducer for this issue is letting the firmware sleep and wake -up in very short intervals, for example by pinging an device on the -network every 0.1 seconds. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 29 ++++++++++++++++----- - 1 file changed, 23 insertions(+), 6 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 1ad41c6e14ff..cf1304eb9b8c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -518,6 +518,7 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; -+ int n_tries = 0; - - mwifiex_dbg(adapter, EVENT, - "event: Wakeup device...\n"); -@@ -525,12 +526,28 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) - if (reg->sleep_cookie) - mwifiex_pcie_dev_wakeup_delay(adapter); - -- /* Accessing fw_status register will wakeup device */ -- if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) { -- mwifiex_dbg(adapter, ERROR, -- "Writing fw_status register failed\n"); -- return -1; -- } -+ /* Access the fw_status register to wake up the device. -+ * Since the 88W8897 firmware sometimes appears to ignore or miss -+ * that wakeup request, we continue trying until we receive an -+ * interrupt from the card. -+ */ -+ do { -+ if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) { -+ mwifiex_dbg(adapter, ERROR, -+ "Writing fw_status register failed\n"); -+ return -1; -+ } -+ -+ n_tries++; -+ -+ if (n_tries <= 15) -+ usleep_range(400, 700); -+ else -+ msleep(10); -+ } while (n_tries <= 50 && READ_ONCE(adapter->int_status) == 0); -+ -+ mwifiex_dbg(adapter, EVENT, -+ "event: Tried %d times until firmware woke up\n", n_tries); - - if (reg->sleep_cookie) { - mwifiex_pcie_dev_wakeup_delay(adapter); --- -2.33.0 - -From 178ace012f8cf67a6461843fb3378d1382b0f81d Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 25 Mar 2021 11:33:02 +0100 -Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell - 88W8897 - -The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) -is used in a lot of Microsoft Surface devices, and all those devices -suffer from very low 2.4GHz wifi connection speeds while bluetooth is -enabled. The reason for that is that the default passive scanning -interval for Bluetooth Low Energy devices is quite high in Linux -(interval of 60 msec and scan window of 30 msec, see hci_core.c), and -the Marvell chip is known for its bad bt+wifi coexisting performance. - -So decrease that passive scan interval and make the scan window shorter -on this particular device to allow for spending more time transmitting -wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and -the new scan window is 6.25 msec (0xa * 0,625 msec). - -This change has a very large impact on the 2.4GHz wifi speeds and gets -it up to performance comparable with the Windows driver, which seems to -apply a similar quirk. - -The interval and window length were tested and found to work very well -with a lot of Bluetooth Low Energy devices, including the Surface Pen, a -Bluetooth Speaker and two modern Bluetooth headphones. All devices were -discovered immediately after turning them on. Even lower values were -also tested, but they introduced longer delays until devices get -discovered. - -Patchset: wifi ---- - drivers/bluetooth/btusb.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index 7188f0fb2e05..1fda8be4e8e1 100644 ---- a/drivers/bluetooth/btusb.c -+++ b/drivers/bluetooth/btusb.c -@@ -69,6 +69,7 @@ static struct usb_driver btusb_driver; - #define BTUSB_BCM2045 0x40000 - #define BTUSB_IFNUM_2 0x80000 - #define BTUSB_CW6622 0x100000 -+#define BTUSB_LOWER_LESCAN_INTERVAL 0x200000 - - static const struct usb_device_id btusb_table[] = { - /* Generic Bluetooth USB device */ -@@ -341,6 +342,7 @@ static const struct usb_device_id blacklist_table[] = { - { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, -+ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, - - /* Intel Bluetooth devices */ - { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_NEW }, -@@ -3110,6 +3112,19 @@ static int btusb_probe(struct usb_interface *intf, - if (id->driver_info & BTUSB_MARVELL) - hdev->set_bdaddr = btusb_set_bdaddr_marvell; - -+ /* The Marvell 88W8897 combined wifi and bluetooth card is known for -+ * very bad bt+wifi coexisting performance. -+ * -+ * Decrease the passive BT Low Energy scan interval a bit -+ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter -+ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly -+ * higher wifi throughput while passively scanning for BT LE devices. -+ */ -+ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { -+ hdev->le_scan_interval = 0x0190; -+ hdev->le_scan_window = 0x000a; -+ } -+ - if (id->driver_info & BTUSB_SWAVE) { - set_bit(HCI_QUIRK_FIXUP_INQUIRY_MODE, &hdev->quirks); - set_bit(HCI_QUIRK_BROKEN_LOCAL_COMMANDS, &hdev->quirks); --- -2.33.0 - -From 44fcb8da980dce251bb3705b204a916f2e749a2b Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 12:31:26 +0100 -Subject: [PATCH] mwifiex: Small cleanup for handling virtual interface type - changes - -Handle the obvious invalid virtual interface type changes with a general -check instead of looking at the individual change. - -For type changes from P2P_CLIENT to P2P_GO and the other way round, this -changes the behavior slightly: We now still do nothing, but return --EOPNOTSUPP instead of 0. Now that behavior was incorrect before and -still is, because type changes between these two types are actually -possible and supported, which we'll fix in a following commit. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 39 +++++++------------ - 1 file changed, 14 insertions(+), 25 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 650191db25cb..5badf7fef37e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1145,6 +1145,20 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return -EBUSY; - } - -+ if (type == NL80211_IFTYPE_UNSPECIFIED) { -+ mwifiex_dbg(priv->adapter, INFO, -+ "%s: no new type specified, keeping old type %d\n", -+ dev->name, curr_iftype); -+ return 0; -+ } -+ -+ if (curr_iftype == type) { -+ mwifiex_dbg(priv->adapter, INFO, -+ "%s: interface already is of type %d\n", -+ dev->name, curr_iftype); -+ return 0; -+ } -+ - switch (curr_iftype) { - case NL80211_IFTYPE_ADHOC: - switch (type) { -@@ -1164,12 +1178,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as IBSS\n", dev->name); -- /* fall through */ -- case NL80211_IFTYPE_ADHOC: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1195,12 +1203,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as STA\n", dev->name); -- /* fall through */ -- case NL80211_IFTYPE_STATION: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1218,12 +1220,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_P2P_GO: - return mwifiex_change_vif_to_p2p(dev, curr_iftype, - type, params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as AP\n", dev->name); -- /* fall through */ -- case NL80211_IFTYPE_AP: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1258,13 +1254,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return -EFAULT; - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as P2P\n", dev->name); -- /* fall through */ -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", --- -2.33.0 - -From 9dfc8a4c16f19c30ba54538f153041b103429a10 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 12:44:39 +0100 -Subject: [PATCH] mwifiex: Use function to check whether interface type change - is allowed - -Instead of bailing out in the function which is supposed to do the type -change, detect invalid changes beforehand using a generic function and -return an error if the change is not allowed. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 139 ++++++++++++------ - 1 file changed, 92 insertions(+), 47 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 5badf7fef37e..e73334679992 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -943,6 +943,76 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, - return 0; - } - -+static bool -+is_vif_type_change_allowed(struct mwifiex_adapter *adapter, -+ enum nl80211_iftype old_iftype, -+ enum nl80211_iftype new_iftype) -+{ -+ switch (old_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_STATION: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_AP: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return adapter->curr_iface_comb.sta_intf != -+ adapter->iface_limit.sta_intf; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ default: -+ break; -+ } -+ -+ return false; -+} -+ - static int - mwifiex_change_vif_to_p2p(struct net_device *dev, - enum nl80211_iftype curr_iftype, -@@ -959,13 +1029,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - - adapter = priv->adapter; - -- if (adapter->curr_iface_comb.p2p_intf == -- adapter->iface_limit.p2p_intf) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple P2P ifaces\n"); -- return -1; -- } -- - mwifiex_dbg(adapter, INFO, - "%s: changing role to p2p\n", dev->name); - -@@ -1031,15 +1094,6 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - - adapter = priv->adapter; - -- if ((curr_iftype != NL80211_IFTYPE_P2P_CLIENT && -- curr_iftype != NL80211_IFTYPE_P2P_GO) && -- (adapter->curr_iface_comb.sta_intf == -- adapter->iface_limit.sta_intf)) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple station/adhoc ifaces\n"); -- return -1; -- } -- - if (type == NL80211_IFTYPE_STATION) - mwifiex_dbg(adapter, INFO, - "%s: changing role to station\n", dev->name); -@@ -1090,13 +1144,6 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - - adapter = priv->adapter; - -- if (adapter->curr_iface_comb.uap_intf == -- adapter->iface_limit.uap_intf) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple AP ifaces\n"); -- return -1; -- } -- - mwifiex_dbg(adapter, INFO, - "%s: changing role to AP\n", dev->name); - -@@ -1159,6 +1206,13 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return 0; - } - -+ if (!is_vif_type_change_allowed(priv->adapter, curr_iftype, type)) { -+ mwifiex_dbg(priv->adapter, ERROR, -+ "%s: change from type %d to %d is not allowed\n", -+ dev->name, curr_iftype, type); -+ return -EOPNOTSUPP; -+ } -+ - switch (curr_iftype) { - case NL80211_IFTYPE_ADHOC: - switch (type) { -@@ -1179,12 +1233,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_STATION: - switch (type) { - case NL80211_IFTYPE_ADHOC: -@@ -1204,12 +1255,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_AP: - switch (type) { - case NL80211_IFTYPE_ADHOC: -@@ -1221,12 +1269,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_p2p(dev, curr_iftype, - type, params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_P2P_CLIENT: - case NL80211_IFTYPE_P2P_GO: - switch (type) { -@@ -1255,21 +1300,21 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: unknown iftype: %d\n", -- dev->name, dev->ieee80211_ptr->iftype); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } - - - return 0; -+ -+errnotsupp: -+ mwifiex_dbg(priv->adapter, ERROR, -+ "unsupported interface type transition: %d to %d\n", -+ curr_iftype, type); -+ return -EOPNOTSUPP; - } - - static void --- -2.33.0 - -From c7538183a79e680bdfa8236049128e9c88af8d21 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 13:33:04 +0100 -Subject: [PATCH] mwifiex: Run SET_BSS_MODE when changing from P2P to STATION - vif-type - -We currently handle changing from the P2P to the STATION virtual -interface type slightly different than changing from P2P to ADHOC: When -changing to STATION, we don't send the SET_BSS_MODE command. We do send -that command on all other type-changes though, and it probably makes -sense to send the command since after all we just changed our BSS_MODE. -Looking at prior changes to this part of the code, it seems that this is -simply a leftover from old refactorings. - -Since sending the SET_BSS_MODE command is the only difference between -mwifiex_change_vif_to_sta_adhoc() and the current code, we can now use -mwifiex_change_vif_to_sta_adhoc() for both switching to ADHOC and -STATION interface type. - -This does not fix any particular bug and just "looked right", so there's -a small chance it might be a regression. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 22 ++++--------------- - 1 file changed, 4 insertions(+), 18 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index e73334679992..99da637692cc 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1274,29 +1274,15 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - - case NL80211_IFTYPE_P2P_CLIENT: - case NL80211_IFTYPE_P2P_GO: -+ if (mwifiex_cfg80211_deinit_p2p(priv)) -+ return -EFAULT; -+ - switch (type) { -- case NL80211_IFTYPE_STATION: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; -- priv->adapter->curr_iface_comb.p2p_intf--; -- priv->adapter->curr_iface_comb.sta_intf++; -- dev->ieee80211_ptr->iftype = type; -- if (mwifiex_deinit_priv_params(priv)) -- return -1; -- if (mwifiex_init_new_priv_params(priv, dev, type)) -- return -1; -- if (mwifiex_sta_init_cmd(priv, false, false)) -- return -1; -- break; - case NL80211_IFTYPE_ADHOC: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; -+ case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); -- break; - case NL80211_IFTYPE_AP: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: --- -2.33.0 - -From 58da22d55a0a6a15b4243a75dfb4f8ea4fc4e8a8 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 14:42:54 +0100 -Subject: [PATCH] mwifiex: Use helper function for counting interface types - -Use a small helper function to increment and decrement the counter of -the interface types we currently manage. This makes the code that -actually changes and sets up the interface type a bit less messy and -also helps avoiding mistakes in case someone increments/decrements a -counter wrongly. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 110 ++++++------------ - 1 file changed, 35 insertions(+), 75 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 99da637692cc..feb3a858d8c1 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1013,6 +1013,32 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, - return false; - } - -+static void -+update_vif_type_counter(struct mwifiex_adapter *adapter, -+ enum nl80211_iftype iftype, -+ int change) -+{ -+ switch (iftype) { -+ case NL80211_IFTYPE_UNSPECIFIED: -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ adapter->curr_iface_comb.sta_intf += change; -+ break; -+ case NL80211_IFTYPE_AP: -+ adapter->curr_iface_comb.uap_intf += change; -+ break; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ adapter->curr_iface_comb.p2p_intf += change; -+ break; -+ default: -+ mwifiex_dbg(adapter, ERROR, -+ "%s: Unsupported iftype passed: %d\n", -+ __func__, iftype); -+ break; -+ } -+} -+ - static int - mwifiex_change_vif_to_p2p(struct net_device *dev, - enum nl80211_iftype curr_iftype, -@@ -1060,19 +1086,8 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.p2p_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; - - return 0; -@@ -1111,20 +1126,10 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.sta_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; -+ - return 0; - } - -@@ -1157,20 +1162,8 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.uap_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; - return 0; - } -@@ -3091,23 +3084,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, - mwifiex_dev_debugfs_init(priv); - #endif - -- switch (type) { -- case NL80211_IFTYPE_UNSPECIFIED: -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf++; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf++; -- break; -- case NL80211_IFTYPE_P2P_CLIENT: -- adapter->curr_iface_comb.p2p_intf++; -- break; -- default: -- /* This should be dead code; checked above */ -- mwifiex_dbg(adapter, ERROR, "type not supported\n"); -- return ERR_PTR(-EINVAL); -- } -+ update_vif_type_counter(adapter, type, +1); - - return &priv->wdev; - -@@ -3173,24 +3150,7 @@ int mwifiex_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) - /* Clear the priv in adapter */ - priv->netdev = NULL; - -- switch (priv->bss_mode) { -- case NL80211_IFTYPE_UNSPECIFIED: -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- default: -- mwifiex_dbg(adapter, ERROR, -- "del_virtual_intf: type not supported\n"); -- break; -- } -+ update_vif_type_counter(adapter, priv->bss_mode, -1); - - priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; - --- -2.33.0 - -From a963cff901b373ee52aa99f96acd034ad7697637 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:56:58 +0100 -Subject: [PATCH] mwifiex: Update virtual interface counters right after - setting bss_type - -In mwifiex_init_new_priv_params() we update our private driver state to -reflect the currently selected virtual interface type. Most notably we -set the bss_mode to the mode we're going to put the firmware in. - -Now after we updated the driver state we actually start talking to the -firmware and instruct it to set up the new mode. Those commands can and -will sometimes fail, in which case we return with an error from -mwifiex_change_vif_to_*. We currently update our virtual interface type -counters after this return, which means the code is never reached when a -firmware error happens and we never update the counters. Since we have -updated our bss_mode earlier though, the counters now no longer reflect -the actual state of the driver. - -This will break things on the next virtual interface change, because the -virtual interface type we're switching away from didn't get its counter -incremented, and we end up decrementing a 0-counter. - -To fix this, simply update the virtual interface type counters right -after updating our driver structures, so that they are always in sync. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 25 +++++++++++-------- - 1 file changed, 14 insertions(+), 11 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index feb3a858d8c1..54d9e789aa14 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1063,6 +1063,10 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; - -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - switch (type) { - case NL80211_IFTYPE_P2P_CLIENT: - if (mwifiex_cfg80211_init_p2p_client(priv)) -@@ -1086,10 +1090,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; -- - return 0; - } - -@@ -1120,16 +1120,17 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - return -1; - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; -+ -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, - HostCmd_ACT_GEN_SET, 0, NULL, true)) - return -1; - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; -- - return 0; - } - -@@ -1156,15 +1157,17 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - return -1; - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; -+ -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, - HostCmd_ACT_GEN_SET, 0, NULL, true)) - return -1; - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; - return 0; - } - /* --- -2.33.0 - -From 8eb7471eb2fe6e028ff6ddd218f593cf3def7886 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 13:42:40 +0100 -Subject: [PATCH] mwifiex: Allow switching interface type from P2P_CLIENT to - P2P_GO - -It's possible to change virtual interface type between P2P_CLIENT and -P2P_GO, the card supports that just fine, and it happens for example -when using miracast with the miraclecast software. - -So allow type changes between P2P_CLIENT and P2P_GO and simply call into -mwifiex_change_vif_to_p2p(), which handles this just fine. We have to -call mwifiex_cfg80211_deinit_p2p() before though to make sure the old -p2p mode is properly uninitialized. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 36 +++++++++++++++++++ - 1 file changed, 36 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 54d9e789aa14..ec5ed00b4b89 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -994,11 +994,26 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, - } - - case NL80211_IFTYPE_P2P_CLIENT: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_P2P_GO: -+ return true; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ - case NL80211_IFTYPE_P2P_GO: - switch (new_iftype) { - case NL80211_IFTYPE_ADHOC: - case NL80211_IFTYPE_STATION: - return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ return true; - case NL80211_IFTYPE_AP: - return adapter->curr_iface_comb.uap_intf != - adapter->iface_limit.uap_intf; -@@ -1269,6 +1284,24 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - } - - case NL80211_IFTYPE_P2P_CLIENT: -+ if (mwifiex_cfg80211_deinit_p2p(priv)) -+ return -EFAULT; -+ -+ switch (type) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, -+ type, params); -+ case NL80211_IFTYPE_P2P_GO: -+ return mwifiex_change_vif_to_p2p(dev, curr_iftype, -+ type, params); -+ case NL80211_IFTYPE_AP: -+ return mwifiex_change_vif_to_ap(dev, curr_iftype, type, -+ params); -+ default: -+ goto errnotsupp; -+ } -+ - case NL80211_IFTYPE_P2P_GO: - if (mwifiex_cfg80211_deinit_p2p(priv)) - return -EFAULT; -@@ -1278,6 +1311,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); -+ case NL80211_IFTYPE_P2P_CLIENT: -+ return mwifiex_change_vif_to_p2p(dev, curr_iftype, -+ type, params); - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); --- -2.33.0 - -From 35e241e203f53307a21d57ea1a73fa17dd93b49e Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:31:08 +0100 -Subject: [PATCH] mwifiex: Handle interface type changes from AP to STATION - -Looks like this case was simply overseen, so handle it, too. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index ec5ed00b4b89..0fc554abfea3 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1272,6 +1272,7 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - switch (type) { - case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); - break; --- -2.33.0 - -From 34f1e5ab4f15920960db9a68864cb66ba20a6f2d Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:32:16 +0100 -Subject: [PATCH] mwifiex: Properly initialize private structure on interface - type changes - -When creating a new virtual interface in mwifiex_add_virtual_intf(), we -update our internal driver states like bss_type, bss_priority, bss_role -and bss_mode to reflect the mode the firmware will be set to. - -When switching virtual interface mode using -mwifiex_init_new_priv_params() though, we currently only update bss_mode -and bss_role. In order for the interface mode switch to actually work, -we also need to update bss_type to its proper value, so do that. - -This fixes a crash of the firmware (because the driver tries to execute -commands that are invalid in AP mode) when switching from station mode -to AP mode. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 +++++++--- - 1 file changed, 7 insertions(+), 3 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 0fc554abfea3..7c6d31eb058c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -912,16 +912,20 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, - switch (type) { - case NL80211_IFTYPE_STATION: - case NL80211_IFTYPE_ADHOC: -- priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_type = MWIFIEX_BSS_TYPE_STA; - break; - case NL80211_IFTYPE_P2P_CLIENT: -- priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - break; - case NL80211_IFTYPE_P2P_GO: -- priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - break; - case NL80211_IFTYPE_AP: - priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_type = MWIFIEX_BSS_TYPE_UAP; - break; - default: - mwifiex_dbg(adapter, ERROR, --- -2.33.0 - -From 92ba5d5ddc96e1ea6321352ff1194ac93ef3a846 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sat, 27 Mar 2021 12:19:14 +0100 -Subject: [PATCH] mwifiex: Fix copy-paste mistake when creating virtual - interface - -The BSS priority here for a new P2P_CLIENT device was accidentally set -to an enum that's certainly not meant for this. Since -MWIFIEX_BSS_ROLE_STA is 0 anyway, we can just set the bss_priority to 0 -instead here. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 7c6d31eb058c..16a94f06a518 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -3017,7 +3017,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, - priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - - priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II; -- priv->bss_priority = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_priority = 0; - priv->bss_role = MWIFIEX_BSS_ROLE_STA; - priv->bss_started = 0; - --- -2.33.0 - -From a20c86dd42f62e6ef2338e5bfa0ac636bbe927e9 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:30:28 +0200 -Subject: [PATCH] mwifiex: Deactive host sleep using HSCFG after it was - activated manually - -When powersaving (so either wifi powersaving or deep sleep, depending on -which state the firmware is in) is disabled, the way the firmware goes -into host sleep is different: Usually the firmware implicitely enters -host sleep on the next SLEEP event we get when we configured host sleep -via HSCFG before. When powersaving is disabled though, there are no -SLEEP events, the way we enter host sleep in that case is different: The -firmware will send us a HS_ACT_REQ event and after that we "manually" -make the firmware enter host sleep by sending it another HSCFG command -with the action HS_ACTIVATE. - -Now waking up from host sleep appears to be different depending on -whether powersaving is enabled again: When powersaving is enabled, the -firmware implicitely leaves host sleep as soon as it wakes up and sends -us an AWAKE event. When powersaving is disabled though, it apparently -doesn't implicitely leave host sleep, but instead we need to send it a -HSCFG command with the HS_CONFIGURE action and the HS_CFG_CANCEL -condition. We didn't do that so far, which is why waking up from host -sleep was broken when powersaving is disabled. - -So add some additional state to mwifiex_adapter where we keep track of -whether host sleep was activated manually via HS_ACTIVATE, and if that -was the case, deactivate it manually again via HS_CFG_CANCEL. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cmdevt.c | 21 +++++++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.c | 18 ++++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmd.c | 4 ++++ - 4 files changed, 44 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cmdevt.c b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -index 60db2b969e20..c7db969aa11d 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cmdevt.c -+++ b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -@@ -604,6 +604,11 @@ int mwifiex_send_cmd(struct mwifiex_private *priv, u16 cmd_no, - return -1; - } - -+ if (priv->adapter->hs_activated_manually && -+ cmd_no != HostCmd_CMD_802_11_HS_CFG_ENH) { -+ mwifiex_cancel_hs(priv, MWIFIEX_ASYNC_CMD); -+ priv->adapter->hs_activated_manually = false; -+ } - - /* Get a new command node */ - cmd_node = mwifiex_get_cmd_node(adapter); -@@ -711,6 +716,15 @@ mwifiex_insert_cmd_to_pending_q(struct mwifiex_adapter *adapter, - } - } - -+ /* Same with exit host sleep cmd, luckily that can't happen at the same time as EXIT_PS */ -+ if (command == HostCmd_CMD_802_11_HS_CFG_ENH) { -+ struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = -+ &host_cmd->params.opt_hs_cfg; -+ -+ if (le16_to_cpu(hs_cfg->action) == HS_ACTIVATE) -+ add_tail = false; -+ } -+ - spin_lock_irqsave(&adapter->cmd_pending_q_lock, flags); - if (add_tail) - list_add_tail(&cmd_node->list, &adapter->cmd_pending_q); -@@ -1215,6 +1229,13 @@ mwifiex_process_hs_config(struct mwifiex_adapter *adapter) - __func__); - - adapter->if_ops.wakeup(adapter); -+ -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv (adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - adapter->hs_activated = false; - clear_bit(MWIFIEX_IS_HS_CONFIGURED, &adapter->work_flags); - clear_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags); -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index ceac611ef086..fd55f502dd2e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -402,6 +402,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !adapter->scan_processing) && - !adapter->data_sent && - !skb_queue_empty(&adapter->tx_data_q)) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_process_tx_queue(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -@@ -419,6 +425,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !mwifiex_bypass_txlist_empty(adapter) && - !mwifiex_is_tdls_chan_switching - (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_process_bypass_tx(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -@@ -435,6 +447,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !adapter->data_sent && !mwifiex_wmm_lists_empty(adapter) && - !mwifiex_is_tdls_chan_switching - (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_wmm_process_tx(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 7e526014b638..96395854c085 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -986,6 +986,7 @@ struct mwifiex_adapter { - struct timer_list wakeup_timer; - struct mwifiex_hs_config_param hs_cfg; - u8 hs_activated; -+ u8 hs_activated_manually; - u16 hs_activate_wait_q_woken; - wait_queue_head_t hs_activate_wait_q; - u8 event_body[MAX_EVENT_SIZE]; -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -index 4ed10cf82f9a..057c810a9ef7 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -@@ -396,6 +396,10 @@ mwifiex_cmd_802_11_hs_cfg(struct mwifiex_private *priv, - if (hs_activate) { - hs_cfg->action = cpu_to_le16(HS_ACTIVATE); - hs_cfg->params.hs_activate.resp_ctrl = cpu_to_le16(RESP_NEEDED); -+ -+ adapter->hs_activated_manually = true; -+ mwifiex_dbg(priv->adapter, CMD, -+ "cmd: Activating host sleep manually\n"); - } else { - hs_cfg->action = cpu_to_le16(HS_CONFIGURE); - hs_cfg->params.hs_config.conditions = hscfg_param->conditions; --- -2.33.0 - -From 9d470202ac7c2a374da22666b7a0d1135cb99995 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:23:05 +0200 -Subject: [PATCH] mwifiex: Add quirk to disable deep sleep with certain - hardware revision - -The 88W8897 pcie card with the hardware revision 20 apparently has a -hardware issue where the card wakes up from deep sleep randomly and very -often, somewhat depending on the card activity, maybe the hardware has a -floating wakeup pin or something. - -Those continuous wakeups prevent the card from entering host sleep when -the computer suspends. And because the host won't answer to events from -the card anymore while it's suspended, the firmwares internal -powersaving state machine seems to get confused and the card can't sleep -anymore at all after that. - -Since we can't work around that hardware bug in the firmware, let's -get the hardware revision string from the firmware and match it with -known bad revisions. Then disable auto deep sleep for those revisions, -which makes sure we no longer get those spurious wakeups. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/main.c | 14 ++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmdresp.c | 16 ++++++++++++++++ - 3 files changed, 31 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index fd55f502dd2e..7159033fcc2e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -227,6 +227,19 @@ static int mwifiex_process_rx(struct mwifiex_adapter *adapter) - return 0; - } - -+static void maybe_quirk_fw_disable_ds(struct mwifiex_adapter *adapter) -+{ -+ struct mwifiex_private *priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA); -+ struct mwifiex_ver_ext ver_ext; -+ -+ set_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &adapter->work_flags); -+ -+ memset(&ver_ext, 0, sizeof(ver_ext)); -+ ver_ext.version_str_sel = 1; -+ mwifiex_send_cmd(priv, HostCmd_CMD_VERSION_EXT, -+ HostCmd_ACT_GEN_GET, 0, &ver_ext, false); -+} -+ - /* - * The main process. - * -@@ -357,6 +370,7 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - if (adapter->hw_status == MWIFIEX_HW_STATUS_INIT_DONE) { - adapter->hw_status = MWIFIEX_HW_STATUS_READY; - mwifiex_init_fw_complete(adapter); -+ maybe_quirk_fw_disable_ds(adapter); - } - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 96395854c085..fa84258f042a 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -524,6 +524,7 @@ enum mwifiex_adapter_work_flags { - MWIFIEX_IS_SUSPENDED, - MWIFIEX_IS_HS_CONFIGURED, - MWIFIEX_IS_HS_ENABLING, -+ MWIFIEX_IS_REQUESTING_FW_VEREXT, - }; - - struct mwifiex_band_config { -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -index 7003767eef42..3079ca3e3fdc 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -@@ -708,6 +708,22 @@ static int mwifiex_ret_ver_ext(struct mwifiex_private *priv, - { - struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext; - -+ if (test_and_clear_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &priv->adapter->work_flags)) { -+ if (strncmp(ver_ext->version_str, "ChipRev:20, BB:9b(10.00), RF:40(21)", 128) == 0) { -+ struct mwifiex_ds_auto_ds auto_ds = { -+ .auto_ds = DEEP_SLEEP_OFF, -+ }; -+ -+ mwifiex_dbg(priv->adapter, MSG, -+ "Bad HW revision detected, disabling deep sleep\n"); -+ -+ mwifiex_send_cmd(priv, HostCmd_CMD_802_11_PS_MODE_ENH, -+ DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, false); -+ } -+ -+ return 0; -+ } -+ - if (version_ext) { - version_ext->version_str_sel = ver_ext->version_str_sel; - memcpy(version_ext->version_str, ver_ext->version_str, --- -2.33.0 - -From 8ab64537c8da107d3803bba61e904269ac0e265a Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 15:17:07 +0100 -Subject: [PATCH] mwifiex: Don't log error on suspend if wake-on-wlan is - disabled - -It's not an error if someone chooses to put their computer to sleep, not -wanting it to wake up because the person next door has just discovered -what a magic packet is. So change the loglevel of this annoying message -from ERROR to INFO. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 16a94f06a518..92d5c9aa5ec7 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -3457,7 +3457,7 @@ static int mwifiex_cfg80211_suspend(struct wiphy *wiphy, - } - - if (!wowlan) { -- mwifiex_dbg(adapter, ERROR, -+ mwifiex_dbg(adapter, INFO, - "None of the WOWLAN triggers enabled\n"); - ret = 0; - goto done; --- -2.33.0 - -From e3854660e8665e0bb7f2a9601094e41a720de905 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sun, 28 Mar 2021 21:42:54 +0200 -Subject: [PATCH] mwifiex: Log an error on command failure during key-material - upload - -Sometimes the KEY_MATERIAL command can fail with the 88W8897 firmware -(when this happens exactly seems pretty random). This appears to prevent -the access point from starting, so it seems like a good idea to log an -error in that case. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 ++++++++-- - 1 file changed, 8 insertions(+), 2 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 92d5c9aa5ec7..5786dcea79cc 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -523,8 +523,14 @@ mwifiex_cfg80211_set_default_mgmt_key(struct wiphy *wiphy, - encrypt_key.is_igtk_def_key = true; - eth_broadcast_addr(encrypt_key.mac_addr); - -- return mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, -- HostCmd_ACT_GEN_SET, true, &encrypt_key, true); -+ if (mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, -+ HostCmd_ACT_GEN_SET, true, &encrypt_key, true)) { -+ mwifiex_dbg(priv->adapter, ERROR, -+ "Sending KEY_MATERIAL command failed\n"); -+ return -1; -+ } -+ -+ return 0; - } - - /* --- -2.33.0 - -From 7a49607e0d149fa55126a3e86b9487d807647d7c Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:44:03 +0200 -Subject: [PATCH] mwifiex: Fix an incorrect comment - -We're sending DELBA requests here, not ADDBA requests. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/11n.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c -index 5d75c971004b..b36b2103d555 100644 ---- a/drivers/net/wireless/marvell/mwifiex/11n.c -+++ b/drivers/net/wireless/marvell/mwifiex/11n.c -@@ -127,7 +127,7 @@ int mwifiex_ret_11n_delba(struct mwifiex_private *priv, - tx_ba_tbl->ra); - } else { /* - * In case of failure, recreate the deleted stream in case -- * we initiated the ADDBA -+ * we initiated the DELBA - */ - if (!INITIATOR_BIT(del_ba_param_set)) - return 0; --- -2.33.0 - -From 35223ec3a57e7f5db3a1f592f44951b4ba3f9a8e Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:45:59 +0200 -Subject: [PATCH] mwifiex: Send DELBA requests according to spec - -We're currently failing to set the initiator bit for DELBA requests: -While we set the bit on our del_ba_param_set bitmask, we forget to -actually copy that bitmask over to the command struct, which means we -never actually set the initiator bit. - -Fix that and copy the bitmask over to the host_cmd_ds_11n_delba command -struct. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/11n.c | 5 +++-- - 1 file changed, 3 insertions(+), 2 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c -index b36b2103d555..4ed6ae8a96f1 100644 ---- a/drivers/net/wireless/marvell/mwifiex/11n.c -+++ b/drivers/net/wireless/marvell/mwifiex/11n.c -@@ -664,14 +664,15 @@ int mwifiex_send_delba(struct mwifiex_private *priv, int tid, u8 *peer_mac, - uint16_t del_ba_param_set; - - memset(&delba, 0, sizeof(delba)); -- delba.del_ba_param_set = cpu_to_le16(tid << DELBA_TID_POS); - -- del_ba_param_set = le16_to_cpu(delba.del_ba_param_set); -+ del_ba_param_set = tid << DELBA_TID_POS; -+ - if (initiator) - del_ba_param_set |= IEEE80211_DELBA_PARAM_INITIATOR_MASK; - else - del_ba_param_set &= ~IEEE80211_DELBA_PARAM_INITIATOR_MASK; - -+ delba.del_ba_param_set = cpu_to_le16(del_ba_param_set); - memcpy(&delba.peer_mac_addr, peer_mac, ETH_ALEN); - - /* We don't wait for the response of this command */ --- -2.33.0 - -From 64af2962611212536a1189a51934c720e373b51d Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:57:41 +0200 -Subject: [PATCH] mwifiex: Ignore BTCOEX events from the firmware - -The firmware of the pcie 88W8897 chip sends those events very -unreliably, which means we sometimes end up actually capping the window -size while bluetooth is disabled, artifically limiting wifi speeds even -though it's not needed. - -Since we can't fix the firmware, let's just ignore those events, it -seems that the Windows driver also doesn't change the rx/tx block ack -buffer sizes when bluetooth gets enabled or disabled, so this is -consistent with the Windows driver. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/sta_event.c | 4 +--- - 1 file changed, 1 insertion(+), 3 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_event.c b/drivers/net/wireless/marvell/mwifiex/sta_event.c -index a327fc5b36e3..6228971d9b8b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_event.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_event.c -@@ -1058,9 +1058,7 @@ int mwifiex_process_sta_event(struct mwifiex_private *priv) - adapter->event_skb); - break; - case EVENT_BT_COEX_WLAN_PARA_CHANGE: -- dev_dbg(adapter->dev, "EVENT: BT coex wlan param update\n"); -- mwifiex_bt_coex_wlan_param_update_event(priv, -- adapter->event_skb); -+ dev_dbg(adapter->dev, "EVENT: ignoring BT coex wlan param update\n"); - break; - case EVENT_RXBA_SYNC: - dev_dbg(adapter->dev, "EVENT: RXBA_SYNC\n"); --- -2.33.0 - diff --git a/patches/4.19/0008-surface-gpe.patch b/patches/4.19/0008-surface-gpe.patch deleted file mode 100644 index 22d6e0fc6..000000000 --- a/patches/4.19/0008-surface-gpe.patch +++ /dev/null @@ -1,394 +0,0 @@ -From 92deae5f20757e35b3ce78a80845649bb880d2cf Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 16 Aug 2020 23:39:56 +0200 -Subject: [PATCH] platform/x86: Add Driver to set up lid GPEs on MS Surface - device - -Conventionally, wake-up events for a specific device, in our case the -lid device, are managed via the ACPI _PRW field. While this does not -seem strictly necessary based on ACPI spec, the kernel disables GPE -wakeups to avoid non-wakeup interrupts preventing suspend by default and -only enables GPEs associated via the _PRW field with a wake-up capable -device. This behavior has been introduced in commit - - f941d3e41da7f86bdb9dcc1977c2bcc6b89bfe47 - ACPI: EC / PM: Disable non-wakeup GPEs for suspend-to-idle - -and is described in more detail in its commit message. - -Unfortunately, on MS Surface devices, there is no _PRW field present on -the lid device, thus no GPE is associated with it, and therefore the GPE -responsible for sending the status-change notification to the lid gets -disabled during suspend, making it impossible to wake the device via the -lid. - -This patch introduces a pseudo-device and respective driver which, based -on some DMI matching, mark the corresponding GPE of the lid device for -wake and enable it during suspend. The behavior of this driver models -the behavior of the ACPI/PM core for normal wakeup GPEs, properly -declared via the _PRW field. - -Signed-off-by: Maximilian Luz -Patchset: surface-gpe ---- - drivers/platform/x86/Kconfig | 10 + - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/surface_gpe.c | 313 +++++++++++++++++++++++++++++ - 3 files changed, 324 insertions(+) - create mode 100644 drivers/platform/x86/surface_gpe.c - -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 0d20ffdb5a67..4321ec171bcd 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -1168,6 +1168,16 @@ config SURFACE_3_POWER_OPREGION - Select this option to enable support for ACPI operation - region of the Surface 3 battery platform driver. - -+config SURFACE_GPE -+ tristate "Surface GPE/Lid Support Driver" -+ depends on ACPI -+ depends on DMI -+ help -+ This driver marks the GPEs related to the ACPI lid device found on -+ Microsoft Surface devices as wakeup sources and prepares them -+ accordingly. It is required on those devices to allow wake-ups from -+ suspend by opening the lid. -+ - config INTEL_PUNIT_IPC - tristate "Intel P-Unit IPC Driver" - ---help--- -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 2ea90039a3e4..49238e9d4abf 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -82,6 +82,7 @@ obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o - obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o - obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o -+obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o - obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o - obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ -diff --git a/drivers/platform/x86/surface_gpe.c b/drivers/platform/x86/surface_gpe.c -new file mode 100644 -index 000000000000..7eaaeacbf408 ---- /dev/null -+++ b/drivers/platform/x86/surface_gpe.c -@@ -0,0 +1,313 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Surface GPE/Lid driver to enable wakeup from suspend via the lid by -+ * properly configuring the respective GPEs. Required for wakeup via lid on -+ * newer Intel-based Microsoft Surface devices. -+ * -+ * Copyright (C) 2020 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+ -+struct surface_lid_device { -+ u32 gpe_number; -+}; -+ -+static const struct surface_lid_device lid_device_l17 = { -+ .gpe_number = 0x17, -+}; -+ -+static const struct surface_lid_device lid_device_l4D = { -+ .gpe_number = 0x4D, -+}; -+ -+static const struct surface_lid_device lid_device_l4F = { -+ .gpe_number = 0x4F, -+}; -+ -+static const struct surface_lid_device lid_device_l57 = { -+ .gpe_number = 0x57, -+}; -+ -+ -+// Note: When changing this don't forget to change the MODULE_ALIAS below. -+static const struct dmi_system_id dmi_lid_device_table[] = { -+ { -+ .ident = "Surface Pro 4", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), -+ }, -+ .driver_data = (void *)&lid_device_l17, -+ }, -+ { -+ .ident = "Surface Pro 5", -+ .matches = { -+ /* -+ * We match for SKU here due to generic product name -+ * "Surface Pro". -+ */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), -+ }, -+ .driver_data = (void *)&lid_device_l4F, -+ }, -+ { -+ .ident = "Surface Pro 5 (LTE)", -+ .matches = { -+ /* -+ * We match for SKU here due to generic product name -+ * "Surface Pro" -+ */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), -+ }, -+ .driver_data = (void *)&lid_device_l4F, -+ }, -+ { -+ .ident = "Surface Pro 6", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), -+ }, -+ .driver_data = (void *)&lid_device_l4F, -+ }, -+ { -+ .ident = "Surface Pro 7", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"), -+ }, -+ .driver_data = (void *)&lid_device_l4D, -+ }, -+ { -+ .ident = "Surface Book 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), -+ }, -+ .driver_data = (void *)&lid_device_l17, -+ }, -+ { -+ .ident = "Surface Book 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), -+ }, -+ .driver_data = (void *)&lid_device_l17, -+ }, -+ { -+ .ident = "Surface Book 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 3"), -+ }, -+ .driver_data = (void *)&lid_device_l4D, -+ }, -+ { -+ .ident = "Surface Laptop 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), -+ }, -+ .driver_data = (void *)&lid_device_l57, -+ }, -+ { -+ .ident = "Surface Laptop 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), -+ }, -+ .driver_data = (void *)&lid_device_l57, -+ }, -+ { -+ .ident = "Surface Laptop 3 (Intel 13\")", -+ .matches = { -+ /* -+ * We match for SKU here due to different variants: The -+ * AMD (15") version does not rely on GPEs. -+ */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"), -+ }, -+ .driver_data = (void *)&lid_device_l4D, -+ }, -+ { -+ .ident = "Surface Laptop 3 (Intel 15\")", -+ .matches = { -+ /* -+ * We match for SKU here due to different variants: The -+ * AMD (15") version does not rely on GPEs. -+ */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1872"), -+ }, -+ .driver_data = (void *)&lid_device_l4D, -+ }, -+ { } -+}; -+ -+ -+static int surface_lid_enable_wakeup(struct device *dev, -+ const struct surface_lid_device *lid, -+ bool enable) -+{ -+ int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE; -+ acpi_status status; -+ -+ status = acpi_set_gpe_wake_mask(NULL, lid->gpe_number, action); -+ if (status) { -+ dev_err(dev, "failed to set GPE wake mask: %d\n", status); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int __maybe_unused surface_gpe_suspend(struct device *dev) -+{ -+ const struct surface_lid_device *lid; -+ -+ lid = dev_get_platdata(dev); -+ return surface_lid_enable_wakeup(dev, lid, true); -+} -+ -+static int __maybe_unused surface_gpe_resume(struct device *dev) -+{ -+ const struct surface_lid_device *lid; -+ -+ lid = dev_get_platdata(dev); -+ return surface_lid_enable_wakeup(dev, lid, false); -+} -+ -+static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume); -+ -+ -+static int surface_gpe_probe(struct platform_device *pdev) -+{ -+ const struct surface_lid_device *lid; -+ int status; -+ -+ lid = dev_get_platdata(&pdev->dev); -+ if (!lid) -+ return -ENODEV; -+ -+ status = acpi_mark_gpe_for_wake(NULL, lid->gpe_number); -+ if (status) { -+ dev_err(&pdev->dev, "failed to mark GPE for wake: %d\n", status); -+ return -EINVAL; -+ } -+ -+ status = acpi_enable_gpe(NULL, lid->gpe_number); -+ if (status) { -+ dev_err(&pdev->dev, "failed to enable GPE: %d\n", status); -+ return -EINVAL; -+ } -+ -+ status = surface_lid_enable_wakeup(&pdev->dev, lid, false); -+ if (status) { -+ acpi_disable_gpe(NULL, lid->gpe_number); -+ return status; -+ } -+ -+ return 0; -+} -+ -+static int surface_gpe_remove(struct platform_device *pdev) -+{ -+ struct surface_lid_device *lid = dev_get_platdata(&pdev->dev); -+ -+ /* restore default behavior without this module */ -+ surface_lid_enable_wakeup(&pdev->dev, lid, false); -+ acpi_disable_gpe(NULL, lid->gpe_number); -+ -+ return 0; -+} -+ -+static struct platform_driver surface_gpe_driver = { -+ .probe = surface_gpe_probe, -+ .remove = surface_gpe_remove, -+ .driver = { -+ .name = "surface_gpe", -+ .pm = &surface_gpe_pm, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+static struct platform_device *surface_gpe_device; -+ -+static int __init surface_gpe_init(void) -+{ -+ const struct dmi_system_id *match; -+ const struct surface_lid_device *lid; -+ struct platform_device *pdev; -+ int status; -+ -+ match = dmi_first_match(dmi_lid_device_table); -+ if (!match) { -+ pr_info(KBUILD_MODNAME": no device detected, exiting\n"); -+ return 0; -+ } -+ -+ lid = match->driver_data; -+ -+ status = platform_driver_register(&surface_gpe_driver); -+ if (status) -+ return status; -+ -+ pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE); -+ if (!pdev) { -+ platform_driver_unregister(&surface_gpe_driver); -+ return -ENOMEM; -+ } -+ -+ status = platform_device_add_data(pdev, lid, sizeof(*lid)); -+ if (status) { -+ platform_device_put(pdev); -+ platform_driver_unregister(&surface_gpe_driver); -+ return status; -+ } -+ -+ status = platform_device_add(pdev); -+ if (status) { -+ platform_device_put(pdev); -+ platform_driver_unregister(&surface_gpe_driver); -+ return status; -+ } -+ -+ surface_gpe_device = pdev; -+ return 0; -+} -+ -+static void __exit surface_gpe_exit(void) -+{ -+ if (!surface_gpe_device) -+ return; -+ -+ platform_device_unregister(surface_gpe_device); -+ platform_driver_unregister(&surface_gpe_driver); -+} -+ -+module_init(surface_gpe_init); -+module_exit(surface_gpe_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface GPE/Lid Driver"); -+MODULE_LICENSE("GPL"); -+ -+MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfacePro:*"); -+MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfacePro4:*"); -+MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfacePro6:*"); -+MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfacePro7:*"); -+MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceBook:*"); -+MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceBook2:*"); -+MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceBook3:*"); -+MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceLaptop:*"); -+MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceLaptop2:*"); -+MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceLaptop3:*"); --- -2.33.0 - diff --git a/patches/4.19/0009-surface-sam-over-hid.patch b/patches/4.19/0009-surface-sam-over-hid.patch deleted file mode 100644 index a277d2d5a..000000000 --- a/patches/4.19/0009-surface-sam-over-hid.patch +++ /dev/null @@ -1,334 +0,0 @@ -From 8cacfd20df4e1f52549e7a24c4eafca9f000486b Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jul 2020 17:19:53 +0200 -Subject: [PATCH] i2c: acpi: Implement RawBytes read access - -Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C -device via a generic serial bus operation region and RawBytes read -access. On the Surface Book 1, this access is required to turn on (and -off) the discrete GPU. - -Multiple things are to note here: - -a) The RawBytes access is device/driver dependent. The ACPI - specification states: - - > Raw accesses assume that the writer has knowledge of the bus that - > the access is made over and the device that is being accessed. The - > protocol may only ensure that the buffer is transmitted to the - > appropriate driver, but the driver must be able to interpret the - > buffer to communicate to a register. - - Thus this implementation may likely not work on other devices - accessing I2C via the RawBytes accessor type. - -b) The MSHW0030 I2C device is an HID-over-I2C device which seems to - serve multiple functions: - - 1. It is the main access point for the legacy-type Surface Aggregator - Module (also referred to as SAM-over-HID, as opposed to the newer - SAM-over-SSH/UART). It has currently not been determined on how - support for the legacy SAM should be implemented. Likely via a - custom HID driver. - - 2. It seems to serve as the HID device for the Integrated Sensor Hub. - This might complicate matters with regards to implementing a - SAM-over-HID driver required by legacy SAM. - -In light of this, the simplest approach has been chosen for now. -However, it may make more sense regarding breakage and compatibility to -either provide functionality for replacing or enhancing the default -operation region handler via some additional API functions, or even to -completely blacklist MSHW0030 from the I2C core and provide a custom -driver for it. - -Replacing/enhancing the default operation region handler would, however, -either require some sort of secondary driver and access point for it, -from which the new API functions would be called and the new handler -(part) would be installed, or hard-coding them via some sort of -quirk-like interface into the I2C core. - -Patchset: surface-sam-over-hid ---- - drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ - 1 file changed, 35 insertions(+) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index 8ba4122fb340..f9a24b56fec0 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -542,6 +542,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, - return (ret == 1) ? 0 : -EIO; - } - -+static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, -+ u8 *data, u8 data_len) -+{ -+ struct i2c_msg msgs[1]; -+ int ret = AE_OK; -+ -+ msgs[0].addr = client->addr; -+ msgs[0].flags = client->flags; -+ msgs[0].len = data_len + 1; -+ msgs[0].buf = data; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ -+ if (ret < 0) { -+ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); -+ return ret; -+ } -+ -+ /* 1 transfer must have completed successfully */ -+ return (ret == 1) ? 0 : -EIO; -+} -+ - static acpi_status - i2c_acpi_space_handler(u32 function, acpi_physical_address command, - u32 bits, u64 *value64, -@@ -649,6 +671,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, - } - break; - -+ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: -+ if (action == ACPI_READ) { -+ dev_warn(&adapter->dev, -+ "protocol 0x%02x not supported for client 0x%02x\n", -+ accessor_type, client->addr); -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } else { -+ status = acpi_gsb_i2c_write_raw_bytes(client, -+ gsb->data, info->access_length); -+ } -+ break; -+ - default: - dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", - accessor_type, client->addr); --- -2.33.0 - -From d624ae9e8be201ef02ccd2656acba516ccffd2fa Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 6 Sep 2020 04:01:19 +0200 -Subject: [PATCH] platform/x86: Add driver for Surface Book 1 dGPU switch - -Add driver exposing the discrete GPU power-switch of the Microsoft -Surface Book 1 to user-space. - -On the Surface Book 1, the dGPU power is controlled via the Surface -System Aggregator Module (SAM). The specific SAM-over-HID command for -this is exposed via ACPI. This module provides a simple driver exposing -the ACPI call via a sysfs parameter to user-space, so that users can -easily power-on/-off the dGPU. - -Patchset: surface-sam-over-hid ---- - drivers/platform/x86/Kconfig | 7 ++ - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/sb1_dgpu_sw.c | 162 +++++++++++++++++++++++++++++ - 3 files changed, 170 insertions(+) - create mode 100644 drivers/platform/x86/sb1_dgpu_sw.c - -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 4321ec171bcd..d1d9ebaecf1c 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -1178,6 +1178,13 @@ config SURFACE_GPE - accordingly. It is required on those devices to allow wake-ups from - suspend by opening the lid. - -+config SURFACE_BOOK1_DGPU_SWITCH -+ tristate "Surface Book 1 dGPU Switch Driver" -+ depends on ACPI && SYSFS -+ help -+ This driver provides a sysfs switch to set the power-state of the -+ discrete GPU found on the Microsoft Surface Book 1. -+ - config INTEL_PUNIT_IPC - tristate "Intel P-Unit IPC Driver" - ---help--- -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 49238e9d4abf..6b028d1ee802 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -83,6 +83,7 @@ obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o - obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o - obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o -+obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += sb1_dgpu_sw.o - obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o - obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o - obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ -diff --git a/drivers/platform/x86/sb1_dgpu_sw.c b/drivers/platform/x86/sb1_dgpu_sw.c -new file mode 100644 -index 000000000000..8c66ed5110fd ---- /dev/null -+++ b/drivers/platform/x86/sb1_dgpu_sw.c -@@ -0,0 +1,162 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+ -+#include -+#include -+#include -+#include -+ -+ -+#ifdef pr_fmt -+#undef pr_fmt -+#endif -+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ -+ -+ -+static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, -+ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); -+ -+#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" -+#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" -+#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" -+ -+ -+static int sb1_dgpu_sw_dsmcall(void) -+{ -+ union acpi_object *ret; -+ acpi_handle handle; -+ acpi_status status; -+ -+ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); -+ if (status) -+ return -EINVAL; -+ -+ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); -+ if (!ret) -+ return -EINVAL; -+ -+ ACPI_FREE(ret); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgon(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGON: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-on dGPU via HGON\n"); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgof(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGOF: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-off dGPU via HGOF\n"); -+ return 0; -+} -+ -+ -+static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ int status, value; -+ -+ status = kstrtoint(buf, 0, &value); -+ if (status < 0) -+ return status; -+ -+ if (value != 1) -+ return -EINVAL; -+ -+ status = sb1_dgpu_sw_dsmcall(); -+ -+ return status < 0 ? status : len; -+} -+ -+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ bool power; -+ int status; -+ -+ status = kstrtobool(buf, &power); -+ if (status < 0) -+ return status; -+ -+ if (power) -+ status = sb1_dgpu_sw_hgon(); -+ else -+ status = sb1_dgpu_sw_hgof(); -+ -+ return status < 0 ? status : len; -+} -+ -+static DEVICE_ATTR_WO(dgpu_dsmcall); -+static DEVICE_ATTR_WO(dgpu_power); -+ -+static struct attribute *sb1_dgpu_sw_attrs[] = { -+ &dev_attr_dgpu_dsmcall.attr, -+ &dev_attr_dgpu_power.attr, -+ NULL, -+}; -+ -+static const struct attribute_group sb1_dgpu_sw_attr_group = { -+ .attrs = sb1_dgpu_sw_attrs, -+}; -+ -+ -+static int sb1_dgpu_sw_probe(struct platform_device *pdev) -+{ -+ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+} -+ -+static int sb1_dgpu_sw_remove(struct platform_device *pdev) -+{ -+ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+ return 0; -+} -+ -+/* -+ * The dGPU power seems to be actually handled by MSHW0040. However, that is -+ * also the power-/volume-button device with a mainline driver. So let's use -+ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. -+ */ -+static const struct acpi_device_id sb1_dgpu_sw_match[] = { -+ { "MSHW0041", }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); -+ -+static struct platform_driver sb1_dgpu_sw = { -+ .probe = sb1_dgpu_sw_probe, -+ .remove = sb1_dgpu_sw_remove, -+ .driver = { -+ .name = "sb1_dgpu_sw", -+ .acpi_match_table = sb1_dgpu_sw_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(sb1_dgpu_sw); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); -+MODULE_LICENSE("GPL"); --- -2.33.0 - diff --git a/patches/4.19/0010-surface-sam.patch b/patches/4.19/0010-surface-sam.patch deleted file mode 100644 index 64492c85e..000000000 --- a/patches/4.19/0010-surface-sam.patch +++ /dev/null @@ -1,20918 +0,0 @@ -From a3e9a6d1ac5954c21a42b7b252942bc08ddbaacc Mon Sep 17 00:00:00 2001 -From: qzed -Date: Mon, 26 Aug 2019 01:15:40 +0200 -Subject: [PATCH] ACPI: Fix buffer/integer type mismatch - -This is actually not a bug in the kernel, but rather Microsoft not -conforming with the ACPI specification. - -Patchset: surface-sam ---- - drivers/acpi/acpica/dsopcode.c | 2 +- - drivers/acpi/acpica/exfield.c | 26 ++++++++++---------------- - 2 files changed, 11 insertions(+), 17 deletions(-) - -diff --git a/drivers/acpi/acpica/dsopcode.c b/drivers/acpi/acpica/dsopcode.c -index 2f4641e5ecde..beb22d7e245e 100644 ---- a/drivers/acpi/acpica/dsopcode.c -+++ b/drivers/acpi/acpica/dsopcode.c -@@ -123,7 +123,7 @@ acpi_ds_init_buffer_field(u16 aml_opcode, - - /* Offset is in bits, count is in bits */ - -- field_flags = AML_FIELD_ACCESS_BYTE; -+ field_flags = AML_FIELD_ACCESS_BUFFER; - bit_offset = offset; - bit_count = (u32) length_desc->integer.value; - -diff --git a/drivers/acpi/acpica/exfield.c b/drivers/acpi/acpica/exfield.c -index b272c329d45d..cf547883a993 100644 ---- a/drivers/acpi/acpica/exfield.c -+++ b/drivers/acpi/acpica/exfield.c -@@ -102,6 +102,7 @@ acpi_ex_read_data_from_field(struct acpi_walk_state *walk_state, - void *buffer; - u32 function; - u16 accessor_type; -+ u8 field_flags; - - ACPI_FUNCTION_TRACE_PTR(ex_read_data_from_field, obj_desc); - -@@ -199,11 +200,16 @@ acpi_ex_read_data_from_field(struct acpi_walk_state *walk_state, - * Note: Field.length is in bits. - */ - length = -- (acpi_size)ACPI_ROUND_BITS_UP_TO_BYTES(obj_desc->field.bit_length); -+ (acpi_size)ACPI_ROUND_BITS_UP_TO_BYTES(obj_desc->common_field.bit_length); -+ field_flags = obj_desc->common_field.field_flags; - -- if (length > acpi_gbl_integer_byte_width) { -+ if (length > acpi_gbl_integer_byte_width || -+ (field_flags & AML_FIELD_ACCESS_TYPE_MASK) == AML_FIELD_ACCESS_BUFFER) { - -- /* Field is too large for an Integer, create a Buffer instead */ -+ /* -+ * Field is either too large for an Integer, or a actually of type -+ * buffer, so create a Buffer. -+ */ - - buffer_desc = acpi_ut_create_buffer_object(length); - if (!buffer_desc) { -@@ -366,19 +372,7 @@ acpi_ex_write_data_to_field(union acpi_operand_object *source_desc, - } else if (obj_desc->field.region_obj->region.space_id == - ACPI_ADR_SPACE_GSBUS) { - accessor_type = obj_desc->field.attribute; -- length = -- acpi_ex_get_serial_access_length(accessor_type, -- obj_desc->field. -- access_length); -- -- /* -- * Add additional 2 bytes for the generic_serial_bus data buffer: -- * -- * Status; (Byte 0 of the data buffer) -- * Length; (Byte 1 of the data buffer) -- * Data[x-1]: (Bytes 2-x of the arbitrary length data buffer) -- */ -- length += 2; -+ length = source_desc->buffer.length; - function = ACPI_WRITE | (accessor_type << 16); - } else { /* IPMI */ - --- -2.33.0 - -From e1162dd41c2e7a84804013cdc46a02f155da5047 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 24 Sep 2019 17:38:12 +0200 -Subject: [PATCH] serdev: Add ACPI devices by ResourceSource field - -When registering a serdev controller, ACPI needs to be checked for -devices attached to it. Currently, all immediate children of the ACPI -node of the controller are assumed to be UART client devices for this -controller. Furthermore, these devices are not searched elsewhere. - -This is incorrect: Similar to SPI and I2C devices, the UART client -device definition (via UARTSerialBusV2) can reside anywhere in the ACPI -namespace as resource definition inside the _CRS method and points to -the controller via its ResourceSource field. This field may either -contain a fully qualified or relative path, indicating the controller -device. To address this, we need to walk over the whole ACPI namespace, -looking at each resource definition, and match the client device to the -controller via this field. - -This patch is based on the existing acpi serial bus implementations in -drivers/i2c/i2c-core-acpi.c and drivers/spi/spi.c, specifically commit -4c3c59544f33e97cf8557f27e05a9904ead16363 ("spi/acpi: enumerate all SPI -slaves in the namespace"). - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/tty/serdev/core.c | 111 +++++++++++++++++++++++++++++++++----- - 1 file changed, 99 insertions(+), 12 deletions(-) - -diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c -index c66a04d24f1d..1b18d12d217f 100644 ---- a/drivers/tty/serdev/core.c -+++ b/drivers/tty/serdev/core.c -@@ -496,16 +496,97 @@ static int of_serdev_register_devices(struct serdev_controller *ctrl) - } - - #ifdef CONFIG_ACPI -+ -+#define SERDEV_ACPI_MAX_SCAN_DEPTH 32 -+ -+struct acpi_serdev_lookup { -+ acpi_handle device_handle; -+ acpi_handle controller_handle; -+ int n; -+ int index; -+}; -+ -+static int acpi_serdev_parse_resource(struct acpi_resource *ares, void *data) -+{ -+ struct acpi_serdev_lookup *lookup = data; -+ struct acpi_resource_uart_serialbus *sb; -+ acpi_status status; -+ -+ if (ares->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) -+ return 1; -+ -+ if (ares->data.common_serial_bus.type != ACPI_RESOURCE_SERIAL_TYPE_UART) -+ return 1; -+ -+ if (lookup->index != -1 && lookup->n++ != lookup->index) -+ return 1; -+ -+ sb = &ares->data.uart_serial_bus; -+ -+ status = acpi_get_handle(lookup->device_handle, -+ sb->resource_source.string_ptr, -+ &lookup->controller_handle); -+ if (ACPI_FAILURE(status)) -+ return 1; -+ -+ /* -+ * NOTE: Ideally, we would also want to retreive other properties here, -+ * once setting them before opening the device is supported by serdev. -+ */ -+ -+ return 1; -+} -+ -+static int acpi_serdev_do_lookup(struct acpi_device *adev, -+ struct acpi_serdev_lookup *lookup) -+{ -+ struct list_head resource_list; -+ int ret; -+ -+ lookup->device_handle = acpi_device_handle(adev); -+ lookup->controller_handle = NULL; -+ lookup->n = 0; -+ -+ INIT_LIST_HEAD(&resource_list); -+ ret = acpi_dev_get_resources(adev, &resource_list, -+ acpi_serdev_parse_resource, lookup); -+ acpi_dev_free_resource_list(&resource_list); -+ -+ if (ret < 0) -+ return -EINVAL; -+ -+ return 0; -+} -+ -+static int acpi_serdev_check_resources(struct serdev_controller *ctrl, -+ struct acpi_device *adev) -+{ -+ struct acpi_serdev_lookup lookup; -+ int ret; -+ -+ if (acpi_bus_get_status(adev) || !adev->status.present) -+ return -EINVAL; -+ -+ /* Look for UARTSerialBusV2 resource */ -+ lookup.index = -1; // we only care for the last device -+ -+ ret = acpi_serdev_do_lookup(adev, &lookup); -+ if (ret) -+ return ret; -+ -+ /* Make sure controller and ResourceSource handle match */ -+ if (ACPI_HANDLE(ctrl->dev.parent) != lookup.controller_handle) -+ return -ENODEV; -+ -+ return 0; -+} -+ - static acpi_status acpi_serdev_register_device(struct serdev_controller *ctrl, -- struct acpi_device *adev) -+ struct acpi_device *adev) - { -- struct serdev_device *serdev = NULL; -+ struct serdev_device *serdev; - int err; - -- if (acpi_bus_get_status(adev) || !adev->status.present || -- acpi_device_enumerated(adev)) -- return AE_OK; -- - serdev = serdev_device_alloc(ctrl); - if (!serdev) { - dev_err(&ctrl->dev, "failed to allocate serdev device for %s\n", -@@ -533,7 +614,7 @@ static const struct acpi_device_id serdev_acpi_devices_blacklist[] = { - }; - - static acpi_status acpi_serdev_add_device(acpi_handle handle, u32 level, -- void *data, void **return_value) -+ void *data, void **return_value) - { - struct serdev_controller *ctrl = data; - struct acpi_device *adev; -@@ -541,26 +622,32 @@ static acpi_status acpi_serdev_add_device(acpi_handle handle, u32 level, - if (acpi_bus_get_device(handle, &adev)) - return AE_OK; - -+ if (acpi_device_enumerated(adev)) -+ return AE_OK; -+ - /* Skip if black listed */ - if (!acpi_match_device_ids(adev, serdev_acpi_devices_blacklist)) - return AE_OK; - -+ if (acpi_serdev_check_resources(ctrl, adev)) -+ return AE_OK; -+ - return acpi_serdev_register_device(ctrl, adev); - } - -+ - static int acpi_serdev_register_devices(struct serdev_controller *ctrl) - { - acpi_status status; -- acpi_handle handle; - -- handle = ACPI_HANDLE(ctrl->dev.parent); -- if (!handle) -+ if (!has_acpi_companion(ctrl->dev.parent)) - return -ENODEV; - -- status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1, -+ status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, -+ SERDEV_ACPI_MAX_SCAN_DEPTH, - acpi_serdev_add_device, NULL, ctrl, NULL); - if (ACPI_FAILURE(status)) -- dev_dbg(&ctrl->dev, "failed to enumerate serdev slaves\n"); -+ dev_warn(&ctrl->dev, "failed to enumerate serdev slaves\n"); - - if (!ctrl->serdev) - return -ENODEV; --- -2.33.0 - -From 92a7407ceae7b34677e6fdef8c3d44bd95911c63 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 17 Aug 2020 01:23:20 +0200 -Subject: [PATCH] Add file2alias support for Surface Aggregator devices - -Implement file2alias support for Surface System Aggregator Module (SSAM) -devices. This allows modules to be auto-loaded for specific devices via -their respective module alias. - -Patchset: surface-sam ---- - include/linux/mod_devicetable.h | 17 +++++++++++++++++ - scripts/mod/devicetable-offsets.c | 7 +++++++ - scripts/mod/file2alias.c | 21 +++++++++++++++++++++ - 3 files changed, 45 insertions(+) - -diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h -index 610cdf8082f2..69f4527315e7 100644 ---- a/include/linux/mod_devicetable.h -+++ b/include/linux/mod_devicetable.h -@@ -768,4 +768,21 @@ struct typec_device_id { - kernel_ulong_t driver_data; - }; - -+/* Surface System Aggregator Module */ -+ -+#define SSAM_MATCH_CHANNEL 0x1 -+#define SSAM_MATCH_INSTANCE 0x2 -+#define SSAM_MATCH_FUNCTION 0x4 -+ -+struct ssam_device_id { -+ __u8 match_flags; -+ -+ __u8 category; -+ __u8 channel; -+ __u8 instance; -+ __u8 function; -+ -+ kernel_ulong_t driver_data; -+}; -+ - #endif /* LINUX_MOD_DEVICETABLE_H */ -diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c -index 293004499b4d..13acbf55c6fd 100644 ---- a/scripts/mod/devicetable-offsets.c -+++ b/scripts/mod/devicetable-offsets.c -@@ -225,5 +225,12 @@ int main(void) - DEVID_FIELD(typec_device_id, svid); - DEVID_FIELD(typec_device_id, mode); - -+ DEVID(ssam_device_id); -+ DEVID_FIELD(ssam_device_id, match_flags); -+ DEVID_FIELD(ssam_device_id, category); -+ DEVID_FIELD(ssam_device_id, channel); -+ DEVID_FIELD(ssam_device_id, instance); -+ DEVID_FIELD(ssam_device_id, function); -+ - return 0; - } -diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c -index 7f40b6aab689..76e3b1d7db45 100644 ---- a/scripts/mod/file2alias.c -+++ b/scripts/mod/file2alias.c -@@ -1276,6 +1276,26 @@ static int do_typec_entry(const char *filename, void *symval, char *alias) - return 1; - } - -+/* Looks like: ssam:cNtNiNfN -+ * -+ * N is exactly 2 digits, where each is an upper-case hex digit. -+ */ -+static int do_ssam_entry(const char *filename, void *symval, char *alias) -+{ -+ DEF_FIELD(symval, ssam_device_id, match_flags); -+ DEF_FIELD(symval, ssam_device_id, category); -+ DEF_FIELD(symval, ssam_device_id, channel); -+ DEF_FIELD(symval, ssam_device_id, instance); -+ DEF_FIELD(symval, ssam_device_id, function); -+ -+ sprintf(alias, "ssam:c%02X", category); -+ ADD(alias, "t", match_flags & SSAM_MATCH_CHANNEL, channel); -+ ADD(alias, "i", match_flags & SSAM_MATCH_INSTANCE, instance); -+ ADD(alias, "f", match_flags & SSAM_MATCH_FUNCTION, function); -+ -+ return 1; -+} -+ - /* Does namelen bytes of name exactly match the symbol? */ - static bool sym_is(const char *name, unsigned namelen, const char *symbol) - { -@@ -1346,6 +1366,7 @@ static const struct devtable devtable[] = { - {"fslmc", SIZE_fsl_mc_device_id, do_fsl_mc_entry}, - {"tbsvc", SIZE_tb_service_id, do_tbsvc_entry}, - {"typec", SIZE_typec_device_id, do_typec_entry}, -+ {"ssam", SIZE_ssam_device_id, do_ssam_entry}, - }; - - /* Create MODULE_ALIAS() statements. --- -2.33.0 - -From e2854086b083f87547d7737556ddbcfa3a76bd6a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 17 Aug 2020 01:44:30 +0200 -Subject: [PATCH] platform/x86: Add support for Surface System Aggregator - Module - -Add support for the Surface System Aggregator Module (SSAM), an embedded -controller that can be found on 5th and later generation Microsoft -Surface devices. The responsibilities of this EC vary from device to -device. It provides battery information on all 5th and later generation -devices, temperature sensor and cooling capability access, functionality -for clipboard detaching on the Surface Books (2 and 3), as well as -HID-over-SSAM input devices, including keyboard on the Surface Laptop 1 -and 2, and keyboard as well as touchpad input on the Surface Laptop 3 -and Surface Book 3. - -Patchset: surface-sam ---- - Documentation/driver-api/index.rst | 1 + - .../surface_aggregator/client-api.rst | 38 + - .../driver-api/surface_aggregator/client.rst | 393 +++ - .../surface_aggregator/clients/cdev.rst | 204 ++ - .../surface_aggregator/clients/dtx.rst | 712 +++++ - .../surface_aggregator/clients/index.rst | 22 + - .../surface_aggregator/clients/san.rst | 44 + - .../driver-api/surface_aggregator/index.rst | 21 + - .../surface_aggregator/internal-api.rst | 67 + - .../surface_aggregator/internal.rst | 577 ++++ - .../surface_aggregator/overview.rst | 77 + - .../driver-api/surface_aggregator/ssh.rst | 344 ++ - drivers/hid/Kconfig | 2 + - drivers/hid/Makefile | 2 + - drivers/hid/surface-hid/Kconfig | 42 + - drivers/hid/surface-hid/Makefile | 7 + - drivers/hid/surface-hid/surface_hid.c | 251 ++ - drivers/hid/surface-hid/surface_hid_core.c | 272 ++ - drivers/hid/surface-hid/surface_hid_core.h | 73 + - drivers/hid/surface-hid/surface_kbd.c | 300 ++ - drivers/platform/x86/Kconfig | 102 + - drivers/platform/x86/Makefile | 6 + - drivers/platform/x86/surface_acpi_notify.c | 886 ++++++ - .../platform/x86/surface_aggregator/Kconfig | 69 + - .../platform/x86/surface_aggregator/Makefile | 17 + - drivers/platform/x86/surface_aggregator/bus.c | 415 +++ - drivers/platform/x86/surface_aggregator/bus.h | 27 + - .../x86/surface_aggregator/controller.c | 2780 +++++++++++++++++ - .../x86/surface_aggregator/controller.h | 285 ++ - .../platform/x86/surface_aggregator/core.c | 850 +++++ - .../x86/surface_aggregator/ssh_msgb.h | 205 ++ - .../x86/surface_aggregator/ssh_packet_layer.c | 2074 ++++++++++++ - .../x86/surface_aggregator/ssh_packet_layer.h | 190 ++ - .../x86/surface_aggregator/ssh_parser.c | 228 ++ - .../x86/surface_aggregator/ssh_parser.h | 154 + - .../surface_aggregator/ssh_request_layer.c | 1263 ++++++++ - .../surface_aggregator/ssh_request_layer.h | 143 + - .../platform/x86/surface_aggregator/trace.h | 632 ++++ - .../platform/x86/surface_aggregator_cdev.c | 810 +++++ - .../x86/surface_aggregator_registry.c | 618 ++++ - drivers/platform/x86/surface_dtx.c | 1281 ++++++++ - drivers/platform/x86/surface_perfmode.c | 122 + - drivers/power/supply/Kconfig | 32 + - drivers/power/supply/Makefile | 2 + - drivers/power/supply/surface_battery.c | 816 +++++ - drivers/power/supply/surface_charger.c | 282 ++ - include/linux/mod_devicetable.h | 5 +- - include/linux/surface_acpi_notify.h | 39 + - include/linux/surface_aggregator/controller.h | 849 +++++ - include/linux/surface_aggregator/device.h | 423 +++ - include/linux/surface_aggregator/serial_hub.h | 668 ++++ - include/uapi/linux/surface_aggregator/cdev.h | 147 + - include/uapi/linux/surface_aggregator/dtx.h | 146 + - scripts/mod/devicetable-offsets.c | 3 +- - scripts/mod/file2alias.c | 10 +- - 55 files changed, 20021 insertions(+), 7 deletions(-) - create mode 100644 Documentation/driver-api/surface_aggregator/client-api.rst - create mode 100644 Documentation/driver-api/surface_aggregator/client.rst - create mode 100644 Documentation/driver-api/surface_aggregator/clients/cdev.rst - create mode 100644 Documentation/driver-api/surface_aggregator/clients/dtx.rst - create mode 100644 Documentation/driver-api/surface_aggregator/clients/index.rst - create mode 100644 Documentation/driver-api/surface_aggregator/clients/san.rst - create mode 100644 Documentation/driver-api/surface_aggregator/index.rst - create mode 100644 Documentation/driver-api/surface_aggregator/internal-api.rst - create mode 100644 Documentation/driver-api/surface_aggregator/internal.rst - create mode 100644 Documentation/driver-api/surface_aggregator/overview.rst - create mode 100644 Documentation/driver-api/surface_aggregator/ssh.rst - create mode 100644 drivers/hid/surface-hid/Kconfig - create mode 100644 drivers/hid/surface-hid/Makefile - create mode 100644 drivers/hid/surface-hid/surface_hid.c - create mode 100644 drivers/hid/surface-hid/surface_hid_core.c - create mode 100644 drivers/hid/surface-hid/surface_hid_core.h - create mode 100644 drivers/hid/surface-hid/surface_kbd.c - create mode 100644 drivers/platform/x86/surface_acpi_notify.c - create mode 100644 drivers/platform/x86/surface_aggregator/Kconfig - create mode 100644 drivers/platform/x86/surface_aggregator/Makefile - create mode 100644 drivers/platform/x86/surface_aggregator/bus.c - create mode 100644 drivers/platform/x86/surface_aggregator/bus.h - create mode 100644 drivers/platform/x86/surface_aggregator/controller.c - create mode 100644 drivers/platform/x86/surface_aggregator/controller.h - create mode 100644 drivers/platform/x86/surface_aggregator/core.c - create mode 100644 drivers/platform/x86/surface_aggregator/ssh_msgb.h - create mode 100644 drivers/platform/x86/surface_aggregator/ssh_packet_layer.c - create mode 100644 drivers/platform/x86/surface_aggregator/ssh_packet_layer.h - create mode 100644 drivers/platform/x86/surface_aggregator/ssh_parser.c - create mode 100644 drivers/platform/x86/surface_aggregator/ssh_parser.h - create mode 100644 drivers/platform/x86/surface_aggregator/ssh_request_layer.c - create mode 100644 drivers/platform/x86/surface_aggregator/ssh_request_layer.h - create mode 100644 drivers/platform/x86/surface_aggregator/trace.h - create mode 100644 drivers/platform/x86/surface_aggregator_cdev.c - create mode 100644 drivers/platform/x86/surface_aggregator_registry.c - create mode 100644 drivers/platform/x86/surface_dtx.c - create mode 100644 drivers/platform/x86/surface_perfmode.c - create mode 100644 drivers/power/supply/surface_battery.c - create mode 100644 drivers/power/supply/surface_charger.c - create mode 100644 include/linux/surface_acpi_notify.h - create mode 100644 include/linux/surface_aggregator/controller.h - create mode 100644 include/linux/surface_aggregator/device.h - create mode 100644 include/linux/surface_aggregator/serial_hub.h - create mode 100644 include/uapi/linux/surface_aggregator/cdev.h - create mode 100644 include/uapi/linux/surface_aggregator/dtx.h - -diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst -index 6d9f2f9fe20e..e36fbb60c676 100644 ---- a/Documentation/driver-api/index.rst -+++ b/Documentation/driver-api/index.rst -@@ -53,6 +53,7 @@ available subsections can be seen below. - slimbus - soundwire/index - fpga/index -+ surface_aggregator/index - - .. only:: subproject and html - -diff --git a/Documentation/driver-api/surface_aggregator/client-api.rst b/Documentation/driver-api/surface_aggregator/client-api.rst -new file mode 100644 -index 000000000000..a1117d57036a ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/client-api.rst -@@ -0,0 +1,38 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+=============================== -+Client Driver API Documentation -+=============================== -+ -+.. contents:: -+ :depth: 2 -+ -+ -+Serial Hub Communication -+======================== -+ -+.. kernel-doc:: include/linux/surface_aggregator/serial_hub.h -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_packet_layer.c -+ :export: -+ -+ -+Controller and Core Interface -+============================= -+ -+.. kernel-doc:: include/linux/surface_aggregator/controller.h -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/controller.c -+ :export: -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/core.c -+ :export: -+ -+ -+Client Bus and Client Device API -+================================ -+ -+.. kernel-doc:: include/linux/surface_aggregator/device.h -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/bus.c -+ :export: -diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst -new file mode 100644 -index 000000000000..26d13085a117 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/client.rst -@@ -0,0 +1,393 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |ssam_controller| replace:: :c:type:`struct ssam_controller ` -+.. |ssam_device| replace:: :c:type:`struct ssam_device ` -+.. |ssam_device_driver| replace:: :c:type:`struct ssam_device_driver ` -+.. |ssam_client_bind| replace:: :c:func:`ssam_client_bind` -+.. |ssam_client_link| replace:: :c:func:`ssam_client_link` -+.. |ssam_get_controller| replace:: :c:func:`ssam_get_controller` -+.. |ssam_controller_get| replace:: :c:func:`ssam_controller_get` -+.. |ssam_controller_put| replace:: :c:func:`ssam_controller_put` -+.. |ssam_device_alloc| replace:: :c:func:`ssam_device_alloc` -+.. |ssam_device_add| replace:: :c:func:`ssam_device_add` -+.. |ssam_device_remove| replace:: :c:func:`ssam_device_remove` -+.. |ssam_device_driver_register| replace:: :c:func:`ssam_device_driver_register` -+.. |ssam_device_driver_unregister| replace:: :c:func:`ssam_device_driver_unregister` -+.. |module_ssam_device_driver| replace:: :c:func:`module_ssam_device_driver` -+.. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` -+.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` -+.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` -+.. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` -+.. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` -+ -+ -+====================== -+Writing Client Drivers -+====================== -+ -+For the API documentation, refer to: -+ -+.. toctree:: -+ :maxdepth: 2 -+ -+ client-api -+ -+ -+Overview -+======== -+ -+Client drivers can be set up in two main ways, depending on how the -+corresponding device is made available to the system. We specifically -+differentiate between devices that are presented to the system via one of -+the conventional ways, e.g. as platform devices via ACPI, and devices that -+are non-discoverable and instead need to be explicitly provided by some -+other mechanism, as discussed further below. -+ -+ -+Non-SSAM Client Drivers -+======================= -+ -+All communication with the SAM EC is handled via the |ssam_controller| -+representing that EC to the kernel. Drivers targeting a non-SSAM device (and -+thus not being a |ssam_device_driver|) need to explicitly establish a -+connection/relation to that controller. This can be done via the -+|ssam_client_bind| function. Said function returns a reference to the SSAM -+controller, but, more importantly, also establishes a device link between -+client device and controller (this can also be done separate via -+|ssam_client_link|). It is important to do this, as it, first, guarantees -+that the returned controller is valid for use in the client driver for as -+long as this driver is bound to its device, i.e. that the driver gets -+unbound before the controller ever becomes invalid, and, second, as it -+ensures correct suspend/resume ordering. This setup should be done in the -+driver's probe function, and may be used to defer probing in case the SSAM -+subsystem is not ready yet, for example: -+ -+.. code-block:: c -+ -+ static int client_driver_probe(struct platform_device *pdev) -+ { -+ struct ssam_controller *ctrl; -+ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ // ... -+ -+ return 0; -+ } -+ -+The controller may be separately obtained via |ssam_get_controller| and its -+lifetime be guaranteed via |ssam_controller_get| and |ssam_controller_put|. -+Note that none of these functions, however, guarantee that the controller -+will not be shut down or suspended. These functions essentially only operate -+on the reference, i.e. only guarantee a bare minimum of accessibility -+without any guarantees at all on practical operability. -+ -+ -+Adding SSAM Devices -+=================== -+ -+If a device does not already exist/is not already provided via conventional -+means, it should be provided as |ssam_device| via the SSAM client device -+hub. New devices can be added to this hub by entering their UID into the -+corresponding registry. SSAM devices can also be manually allocated via -+|ssam_device_alloc|, subsequently to which they have to be added via -+|ssam_device_add| and eventually removed via |ssam_device_remove|. By -+default, the parent of the device is set to the controller device provided -+for allocation, however this may be changed before the device is added. Note -+that, when changing the parent device, care must be taken to ensure that the -+controller lifetime and suspend/resume ordering guarantees, in the default -+setup provided through the parent-child relation, are preserved. If -+necessary, by use of |ssam_client_link| as is done for non-SSAM client -+drivers and described in more detail above. -+ -+A client device must always be removed by the party which added the -+respective device before the controller shuts down. Such removal can be -+guaranteed by linking the driver providing the SSAM device to the controller -+via |ssam_client_link|, causing it to unbind before the controller driver -+unbinds. Client devices registered with the controller as parent are -+automatically removed when the controller shuts down, but this should not be -+relied upon, especially as this does not extend to client devices with a -+different parent. -+ -+ -+SSAM Client Drivers -+=================== -+ -+SSAM client device drivers are, in essence, no different than other device -+driver types. They are represented via |ssam_device_driver| and bind to a -+|ssam_device| via its UID (:c:type:`struct ssam_device.uid `) -+member and the match table -+(:c:type:`struct ssam_device_driver.match_table `), -+which should be set when declaring the driver struct instance. Refer to the -+|SSAM_DEVICE| macro documentation for more details on how to define members -+of the driver's match table. -+ -+The UID for SSAM client devices consists of a ``domain``, a ``category``, -+a ``target``, an ``instance``, and a ``function``. The ``domain`` is used -+differentiate between physical SAM devices -+(:c:type:`SSAM_DOMAIN_SERIALHUB `), i.e. devices that can -+be accessed via the Surface Serial Hub, and virtual ones -+(:c:type:`SSAM_DOMAIN_VIRTUAL `), such as client-device -+hubs, that have no real representation on the SAM EC and are solely used on -+the kernel/driver-side. For physical devices, ``category`` represents the -+target category, ``target`` the target ID, and ``instance`` the instance ID -+used to access the physical SAM device. In addition, ``function`` references -+a specific device functionality, but has no meaning to the SAM EC. The -+(default) name of a client device is generated based on its UID. -+ -+A driver instance can be registered via |ssam_device_driver_register| and -+unregistered via |ssam_device_driver_unregister|. For convenience, the -+|module_ssam_device_driver| macro may be used to define module init- and -+exit-functions registering the driver. -+ -+The controller associated with a SSAM client device can be found in its -+:c:type:`struct ssam_device.ctrl ` member. This reference is -+guaranteed to be valid for at least as long as the client driver is bound, -+but should also be valid for as long as the client device exists. Note, -+however, that access outside of the bound client driver must ensure that the -+controller device is not suspended while making any requests or -+(un-)registering event notifiers (and thus should generally be avoided). This -+is guaranteed when the controller is accessed from inside the bound client -+driver. -+ -+ -+Making Synchronous Requests -+=========================== -+ -+Synchronous requests are (currently) the main form of host-initiated -+communication with the EC. There are a couple of ways to define and execute -+such requests, however, most of them boil down to something similar as shown -+in the example below. This example defines a write-read request, meaning -+that the caller provides an argument to the SAM EC and receives a response. -+The caller needs to know the (maximum) length of the response payload and -+provide a buffer for it. -+ -+Care must be taken to ensure that any command payload data passed to the SAM -+EC is provided in little-endian format and, similarly, any response payload -+data received from it is converted from little-endian to host endianness. -+ -+.. code-block:: c -+ -+ int perform_request(struct ssam_controller *ctrl, u32 arg, u32 *ret) -+ { -+ struct ssam_request rqst; -+ struct ssam_response resp; -+ int status; -+ -+ /* Convert request argument to little-endian. */ -+ __le32 arg_le = cpu_to_le32(arg); -+ __le32 ret_le = cpu_to_le32(0); -+ -+ /* -+ * Initialize request specification. Replace this with your values. -+ * The rqst.payload field may be NULL if rqst.length is zero, -+ * indicating that the request does not have any argument. -+ * -+ * Note: The request parameters used here are not valid, i.e. -+ * they do not correspond to an actual SAM/EC request. -+ */ -+ rqst.target_category = SSAM_SSH_TC_SAM; -+ rqst.target_id = 0x01; -+ rqst.command_id = 0x02; -+ rqst.instance_id = 0x03; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(arg_le); -+ rqst.payload = (u8 *)&arg_le; -+ -+ /* Initialize request response. */ -+ resp.capacity = sizeof(ret_le); -+ resp.length = 0; -+ resp.pointer = (u8 *)&ret_le; -+ -+ /* -+ * Perform actual request. The response pointer may be null in case -+ * the request does not have any response. This must be consistent -+ * with the SSAM_REQUEST_HAS_RESPONSE flag set in the specification -+ * above. -+ */ -+ status = ssam_request_sync(ctrl, &rqst, &resp); -+ -+ /* -+ * Alternatively use -+ * -+ * ssam_request_sync_onstack(ctrl, &rqst, &resp, sizeof(arg_le)); -+ * -+ * to perform the request, allocating the message buffer directly -+ * on the stack as opposed to allocation via kzalloc(). -+ */ -+ -+ /* -+ * Convert request response back to native format. Note that in the -+ * error case, this value is not touched by the SSAM core, i.e. -+ * 'ret_le' will be zero as specified in its initialization. -+ */ -+ *ret = le32_to_cpu(ret_le); -+ -+ return status; -+ } -+ -+Note that |ssam_request_sync| in its essence is a wrapper over lower-level -+request primitives, which may also be used to perform requests. Refer to its -+implementation and documentation for more details. -+ -+An arguably more user-friendly way of defining such functions is by using -+one of the generator macros, for example via: -+ -+.. code-block:: c -+ -+ SSAM_DEFINE_SYNC_REQUEST_W(__ssam_tmp_perf_mode_set, __le32, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .target_id = 0x01, -+ .command_id = 0x03, -+ .instance_id = 0x00, -+ }); -+ -+This example defines a function -+ -+.. code-block:: c -+ -+ int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg); -+ -+executing the specified request, with the controller passed in when calling -+said function. In this example, the argument is provided via the ``arg`` -+pointer. Note that the generated function allocates the message buffer on -+the stack. Thus, if the argument provided via the request is large, these -+kinds of macros should be avoided. Also note that, in contrast to the -+previous non-macro example, this function does not do any endianness -+conversion, which has to be handled by the caller. Apart from those -+differences the function generated by the macro is similar to the one -+provided in the non-macro example above. -+ -+The full list of such function-generating macros is -+ -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_N` for requests without return value and -+ without argument. -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_R` for requests with return value but no -+ argument. -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_W` for requests without return value but -+ with argument. -+ -+Refer to their respective documentation for more details. For each one of -+these macros, a special variant is provided, which targets request types -+applicable to multiple instances of the same device type: -+ -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_N` -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_R` -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_W` -+ -+The difference of those macros to the previously mentioned versions is, that -+the device target and instance IDs are not fixed for the generated function, -+but instead have to be provided by the caller of said function. -+ -+Additionally, variants for direct use with client devices, i.e. -+|ssam_device|, are also provided. These can, for example, be used as -+follows: -+ -+.. code-block:: c -+ -+ SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x01, -+ }); -+ -+This invocation of the macro defines a function -+ -+.. code-block:: c -+ -+ int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret); -+ -+executing the specified request, using the device IDs and controller given -+in the client device. The full list of such macros for client devices is: -+ -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_N` -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_R` -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_W` -+ -+ -+Handling Events -+=============== -+ -+To receive events from the SAM EC, an event notifier must be registered for -+the desired event via |ssam_notifier_register|. The notifier must be -+unregistered via |ssam_notifier_unregister| once it is not required any -+more. -+ -+Event notifiers are registered by providing (at minimum) a callback to call -+in case an event has been received, the registry specifying how the event -+should be enabled, an event ID specifying for which target category and, -+optionally and depending on the registry used, for which instance ID events -+should be enabled, and finally, flags describing how the EC will send these -+events. If the specific registry does not enable events by instance ID, the -+instance ID must be set to zero. Additionally, a priority for the respective -+notifier may be specified, which determines its order in relation to any -+other notifier registered for the same target category. -+ -+By default, event notifiers will receive all events for the specific target -+category, regardless of the instance ID specified when registering the -+notifier. The core may be instructed to only call a notifier if the target -+ID or instance ID (or both) of the event match the ones implied by the -+notifier IDs (in case of target ID, the target ID of the registry), by -+providing an event mask (see |ssam_event_mask|). -+ -+In general, the target ID of the registry is also the target ID of the -+enabled event (with the notable exception being keyboard input events on the -+Surface Laptop 1 and 2, which are enabled via a registry with target ID 1, -+but provide events with target ID 2). -+ -+A full example for registering an event notifier and handling received -+events is provided below: -+ -+.. code-block:: c -+ -+ u32 notifier_callback(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+ { -+ int status = ... -+ -+ /* Handle the event here ... */ -+ -+ /* Convert return value and indicate that we handled the event. */ -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+ } -+ -+ int setup_notifier(struct ssam_device *sdev, -+ struct ssam_event_notifier *nf) -+ { -+ /* Set priority wrt. other handlers of same target category. */ -+ nf->base.priority = 1; -+ -+ /* Set event/notifier callback. */ -+ nf->base.fn = notifier_callback; -+ -+ /* Specify event registry, i.e. how events get enabled/disabled. */ -+ nf->event.reg = SSAM_EVENT_REGISTRY_KIP; -+ -+ /* Specify which event to enable/disable */ -+ nf->event.id.target_category = sdev->uid.category; -+ nf->event.id.instance = sdev->uid.instance; -+ -+ /* -+ * Specify for which events the notifier callback gets executed. -+ * This essentially tells the core if it can skip notifiers that -+ * don't have target or instance IDs matching those of the event. -+ */ -+ nf->event.mask = SSAM_EVENT_MASK_STRICT; -+ -+ /* Specify event flags. */ -+ nf->event.flags = SSAM_EVENT_SEQUENCED; -+ -+ return ssam_notifier_register(sdev->ctrl, nf); -+ } -+ -+Multiple event notifiers can be registered for the same event. The event -+handler core takes care of enabling and disabling events when notifiers are -+registered and unregistered, by keeping track of how many notifiers for a -+specific event (combination of registry, event target category, and event -+instance ID) are currently registered. This means that a specific event will -+be enabled when the first notifier for it is being registered and disabled -+when the last notifier for it is being unregistered. Note that the event -+flags are therefore only used on the first registered notifier, however, one -+should take care that notifiers for a specific event are always registered -+with the same flag and it is considered a bug to do otherwise. -diff --git a/Documentation/driver-api/surface_aggregator/clients/cdev.rst b/Documentation/driver-api/surface_aggregator/clients/cdev.rst -new file mode 100644 -index 000000000000..0134a841a079 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/clients/cdev.rst -@@ -0,0 +1,204 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request ` -+.. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags ` -+.. |ssam_cdev_event| replace:: :c:type:`struct ssam_cdev_event ` -+ -+============================== -+User-Space EC Interface (cdev) -+============================== -+ -+The ``surface_aggregator_cdev`` module provides a misc-device for the SSAM -+controller to allow for a (more or less) direct connection from user-space to -+the SAM EC. It is intended to be used for development and debugging, and -+therefore should not be used or relied upon in any other way. Note that this -+module is not loaded automatically, but instead must be loaded manually. -+ -+The provided interface is accessible through the ``/dev/surface/aggregator`` -+device-file. All functionality of this interface is provided via IOCTLs. -+These IOCTLs and their respective input/output parameter structs are defined in -+``include/uapi/linux/surface_aggregator/cdev.h``. -+ -+A small python library and scripts for accessing this interface can be found -+at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam. -+ -+.. contents:: -+ -+ -+Receiving Events -+================ -+ -+Events can be received by reading from the device-file. The are represented by -+the |ssam_cdev_event| datatype. -+ -+Before events are available to be read, however, the desired notifiers must be -+registered via the ``SSAM_CDEV_NOTIF_REGISTER`` IOCTL. Notifiers are, in -+essence, callbacks, called when the EC sends an event. They are, in this -+interface, associated with a specific target category and device-file-instance. -+They forward any event of this category to the buffer of the corresponding -+instance, from which it can then be read. -+ -+Notifiers themselves do not enable events on the EC. Thus, it may additionally -+be necessary to enable events via the ``SSAM_CDEV_EVENT_ENABLE`` IOCTL. While -+notifiers work per-client (i.e. per-device-file-instance), events are enabled -+globally, for the EC and all of its clients (regardless of userspace or -+non-userspace). The ``SSAM_CDEV_EVENT_ENABLE`` and ``SSAM_CDEV_EVENT_DISABLE`` -+IOCTLs take care of reference counting the events, such that an event is -+enabled as long as there is a client that has requested it. -+ -+Note that enabled events are not automatically disabled once the client -+instance is closed. Therefore any client process (or group of processes) should -+balance their event enable calls with the corresponding event disable calls. It -+is, however, perfectly valid to enable and disable events on different client -+instances. For example, it is valid to set up notifiers and read events on -+client instance ``A``, enable those events on instance ``B`` (note that these -+will also be received by A since events are enabled/disabled globally), and -+after no more events are desired, disable the previously enabled events via -+instance ``C``. -+ -+ -+Controller IOCTLs -+================= -+ -+The following IOCTLs are provided: -+ -+.. flat-table:: Controller IOCTLs -+ :widths: 1 1 1 1 4 -+ :header-rows: 1 -+ -+ * - Type -+ - Number -+ - Direction -+ - Name -+ - Description -+ -+ * - ``0xA5`` -+ - ``1`` -+ - ``WR`` -+ - ``REQUEST`` -+ - Perform synchronous SAM request. -+ -+ * - ``0xA5`` -+ - ``2`` -+ - ``W`` -+ - ``NOTIF_REGISTER`` -+ - Register event notifier. -+ -+ * - ``0xA5`` -+ - ``3`` -+ - ``W`` -+ - ``NOTIF_UNREGISTER`` -+ - Unregister event notifier. -+ -+ * - ``0xA5`` -+ - ``4`` -+ - ``W`` -+ - ``EVENT_ENABLE`` -+ - Enable event source. -+ -+ * - ``0xA5`` -+ - ``5`` -+ - ``W`` -+ - ``EVENT_DISABLE`` -+ - Disable event source. -+ -+ -+``SSAM_CDEV_REQUEST`` -+--------------------- -+ -+Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``. -+ -+Executes a synchronous SAM request. The request specification is passed in -+as argument of type |ssam_cdev_request|, which is then written to/modified -+by the IOCTL to return status and result of the request. -+ -+Request payload data must be allocated separately and is passed in via the -+``payload.data`` and ``payload.length`` members. If a response is required, -+the response buffer must be allocated by the caller and passed in via the -+``response.data`` member. The ``response.length`` member must be set to the -+capacity of this buffer, or if no response is required, zero. Upon -+completion of the request, the call will write the response to the response -+buffer (if its capacity allows it) and overwrite the length field with the -+actual size of the response, in bytes. -+ -+Additionally, if the request has a response, this must be indicated via the -+request flags, as is done with in-kernel requests. Request flags can be set -+via the ``flags`` member and the values correspond to the values found in -+|ssam_cdev_request_flags|. -+ -+Finally, the status of the request itself is returned in the ``status`` -+member (a negative errno value indicating failure). Note that failure -+indication of the IOCTL is separated from failure indication of the request: -+The IOCTL returns a negative status code if anything failed during setup of -+the request (``-EFAULT``) or if the provided argument or any of its fields -+are invalid (``-EINVAL``). In this case, the status value of the request -+argument may be set, providing more detail on what went wrong (e.g. -+``-ENOMEM`` for out-of-memory), but this value may also be zero. The IOCTL -+will return with a zero status code in case the request has been set up, -+submitted, and completed (i.e. handed back to user-space) successfully from -+inside the IOCTL, but the request ``status`` member may still be negative in -+case the actual execution of the request failed after it has been submitted. -+ -+A full definition of the argument struct is provided below. -+ -+``SSAM_CDEV_NOTIF_REGISTER`` -+---------------------------- -+ -+Defined as ``_IOW(0xA5, 2, struct ssam_cdev_notifier_desc)``. -+ -+Register a notifier for the event target category specified in the given -+notifier description with the specified priority. Notifiers registration is -+required to receive events, but does not enable events themselves. After a -+notifier for a specific target category has been registered, all events of that -+category will be forwarded to the userspace client and can then be read from -+the device file instance. Note that events may have to be enabled, e.g. via the -+``SSAM_CDEV_EVENT_ENABLE`` IOCTL, before the EC will send them. -+ -+Only one notifier can be registered per target category and client instance. If -+a notifier has already been registered, this IOCTL will fail with ``-EEXIST``. -+ -+Notifiers will automatically be removed when the device file instance is -+closed. -+ -+``SSAM_CDEV_NOTIF_UNREGISTER`` -+------------------------------ -+ -+Defined as ``_IOW(0xA5, 3, struct ssam_cdev_notifier_desc)``. -+ -+Unregisters the notifier associated with the specified target category. The -+priority field will be ignored by this IOCTL. If no notifier has been -+registered for this client instance and the given category, this IOCTL will -+fail with ``-ENOENT``. -+ -+``SSAM_CDEV_EVENT_ENABLE`` -+-------------------------- -+ -+Defined as ``_IOW(0xA5, 4, struct ssam_cdev_event_desc)``. -+ -+Enable the event associated with the given event descriptor. -+ -+Note that this call will not register a notifier itself, it will only enable -+events on the controller. If you want to receive events by reading from the -+device file, you will need to register the corresponding notifier(s) on that -+instance. -+ -+Events are not automatically disabled when the device file is closed. This must -+be done manually, via a call to the ``SSAM_CDEV_EVENT_DISABLE`` IOCTL. -+ -+``SSAM_CDEV_EVENT_DISABLE`` -+--------------------------- -+ -+Defined as ``_IOW(0xA5, 5, struct ssam_cdev_event_desc)``. -+ -+Disable the event associated with the given event descriptor. -+ -+Note that this will not unregister any notifiers. Events may still be received -+and forwarded to user-space after this call. The only safe way of stopping -+events from being received is unregistering all previously registered -+notifiers. -+ -+ -+Structures and Enums -+==================== -+ -+.. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h -diff --git a/Documentation/driver-api/surface_aggregator/clients/dtx.rst b/Documentation/driver-api/surface_aggregator/clients/dtx.rst -new file mode 100644 -index 000000000000..e974c2b04e9f ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/clients/dtx.rst -@@ -0,0 +1,712 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |__u16| replace:: :c:type:`__u16 <__u16>` -+.. |sdtx_event| replace:: :c:type:`struct sdtx_event ` -+.. |sdtx_event_code| replace:: :c:type:`enum sdtx_event_code ` -+.. |sdtx_base_info| replace:: :c:type:`struct sdtx_base_info ` -+.. |sdtx_device_mode| replace:: :c:type:`struct sdtx_device_mode ` -+ -+====================================================== -+User-Space DTX (Clipboard Detachment System) Interface -+====================================================== -+ -+The ``surface_dtx`` driver is responsible for proper clipboard detachment -+and re-attachment handling. To this end, it provides the ``/dev/surface/dtx`` -+device file, through which it can interface with a user-space daemon. This -+daemon is then ultimately responsible for determining and taking necessary -+actions, such as unmounting devices attached to the base, -+unloading/reloading the graphics-driver, user-notifications, etc. -+ -+There are two basic communication principles used in this driver: Commands -+(in other parts of the documentation also referred to as requests) and -+events. Commands are sent to the EC and may have a different implications in -+different contexts. Events are sent by the EC upon some internal state -+change. Commands are always driver-initiated, whereas events are always -+initiated by the EC. -+ -+.. contents:: -+ -+Nomenclature -+============ -+ -+* **Clipboard:** -+ The detachable upper part of the Surface Book, housing the screen and CPU. -+ -+* **Base:** -+ The lower part of the Surface Book from which the clipboard can be -+ detached, optionally (model dependent) housing the discrete GPU (dGPU). -+ -+* **Latch:** -+ The mechanism keeping the clipboard attached to the base in normal -+ operation and allowing it to be detached when requested. -+ -+* **Silently ignored commands:** -+ The command is accepted by the EC as a valid command and acknowledged -+ (following the standard communication protocol), but the EC does not act -+ upon it, i.e. ignores it.e upper part of the -+ -+ -+Detachment Process -+================== -+ -+Warning: This part of the documentation is based on reverse engineering and -+testing and thus may contain errors or be incomplete. -+ -+Latch States -+------------ -+ -+The latch mechanism has two major states: *open* and *closed*. In the -+*closed* state (default), the clipboard is secured to the base, whereas in -+the *open* state, the clipboard can be removed by a user. -+ -+The latch can additionally be locked and, correspondingly, unlocked, which -+can influence the detachment procedure. Specifically, this locking mechanism -+is intended to prevent the the dGPU, positioned in the base of the device, -+from being hot-unplugged while in use. More details can be found in the -+documentation for the detachment procedure below. By default, the latch is -+unlocked. -+ -+Detachment Procedure -+-------------------- -+ -+Note that the detachment process is governed fully by the EC. The -+``surface_dtx`` driver only relays events from the EC to user-space and -+commands from user-space to the EC, i.e. it does not influence this process. -+ -+The detachment process is started with the user pressing the *detach* button -+on the base of the device or executing the ``SDTX_IOCTL_LATCH_REQUEST`` IOCTL. -+Following that: -+ -+1. The EC turns on the indicator led on the detach-button, sends a -+ *detach-request* event (``SDTX_EVENT_REQUEST``), and awaits further -+ instructions/commands. In case the latch is unlocked, the led will flash -+ green. If the latch has been locked, the led will be solid red -+ -+2. The event is, via the ``surface_dtx`` driver, relayed to user-space, where -+ an appropriate user-space daemon can handle it and send instructions back -+ to the EC via IOCTLs provided by this driver. -+ -+3. The EC waits for instructions from user-space and acts according to them. -+ If the EC does not receive any instructions in a given period, it will -+ time out and continue as follows: -+ -+ - If the latch is unlocked, the EC will open the latch and the clipboard -+ can be detached from the base. This is the exact behavior as without -+ this driver or any user-space daemon. See the ``SDTX_IOCTL_LATCH_CONFIRM`` -+ description below for more details on the follow-up behavior of the EC. -+ -+ - If the latch is locked, the EC will *not* open the latch, meaning the -+ clipboard cannot be detached from the base. Furthermore, the EC sends -+ an cancel event (``SDTX_EVENT_CANCEL``) detailing this with the cancel -+ reason ``SDTX_DETACH_TIMEDOUT`` (see :ref:`events` for details). -+ -+Valid responses by a user-space daemon to a detachment request event are: -+ -+- Execute ``SDTX_IOCTL_LATCH_REQUEST``. This will immediately abort the -+ detachment process. Furthermore, the EC will send a detach-request event, -+ similar to the user pressing the detach-button to cancel said process (see -+ below). -+ -+- Execute ``SDTX_IOCTL_LATCH_CONFIRM``. This will cause the EC to open the -+ latch, after which the user can separate clipboard and base. -+ -+ As this changes the latch state, a *latch-status* event -+ (``SDTX_EVENT_LATCH_STATUS``) will be sent once the latch has been opened -+ successfully. If the EC fails to open the latch, e.g. due to hardware -+ error or low battery, a latch-cancel event (``SDTX_EVENT_CANCEL``) will be -+ sent with the cancel reason indicating the specific failure. -+ -+ If the latch is currently locked, the latch will automatically be -+ unlocked before it is opened. -+ -+- Execute ``SDTX_IOCTL_LATCH_HEARTBEAT``. This will reset the internal timeout. -+ No other actions will be performed, i.e. the detachment process will neither -+ be completed nor canceled, and the EC will still be waiting for further -+ responses. -+ -+- Execute ``SDTX_IOCTL_LATCH_CANCEL``. This will abort the detachment process, -+ similar to ``SDTX_IOCTL_LATCH_REQUEST``, described above, or the button -+ press, described below. A *generic request* event (``SDTX_EVENT_REQUEST``) -+ is send in response to this. In contrast to those, however, this command -+ does not trigger a new detachment process if none is currently in -+ progress. -+ -+- Do nothing. The detachment process eventually times out as described in -+ point 3. -+ -+See :ref:`ioctls` for more details on these responses. -+ -+It is important to note that, if the user presses the detach button at any -+point when a detachment operation is in progress (i.e. after the the EC has -+sent the initial *detach-request* event (``SDTX_EVENT_REQUEST``) and before -+it received the corresponding response concluding the process), the -+detachment process is canceled on the EC-level and an identical event is -+being sent. Thus a *detach-request* event, by itself, does not signal the -+start of the detachment process. -+ -+The detachment process may further be canceled by the EC due to hardware -+failures or a low clipboard battery. This is done via a cancel event -+(``SDTX_EVENT_CANCEL``) with the corresponding cancel reason. -+ -+ -+User-Space Interface Documentation -+================================== -+ -+Error Codes and Status Values -+----------------------------- -+ -+Error and status codes are divided into different categories, which can be -+used to determine if the status code is an error, and, if it is, the -+severity and type of that error. The current categories are: -+ -+.. flat-table:: Overview of Status/Error Categories. -+ :widths: 2 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Value -+ - Short Description -+ -+ * - ``STATUS`` -+ - ``0x0000`` -+ - Non-error status codes. -+ -+ * - ``RUNTIME_ERROR`` -+ - ``0x1000`` -+ - Non-critical runtime errors. -+ -+ * - ``HARDWARE_ERROR`` -+ - ``0x2000`` -+ - Critical hardware failures. -+ -+ * - ``UNKNOWN`` -+ - ``0xF000`` -+ - Unknown error codes. -+ -+Other categories are reserved for future use. The ``SDTX_CATEGORY()`` macro -+can be used to determine the category of any status value. The -+``SDTX_SUCCESS()`` macro can be used to check if the status value is a -+success value (``SDTX_CATEGORY_STATUS``) or if it indicates a failure. -+ -+Unknown status or error codes sent by the EC are assigned to the ``UNKNOWN`` -+category by the driver and may be implemented via their own code in the -+future. -+ -+Currently used error codes are: -+ -+.. flat-table:: Overview of Error Codes. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Category -+ - Value -+ - Short Description -+ -+ * - ``SDTX_DETACH_NOT_FEASIBLE`` -+ - ``RUNTIME`` -+ - ``0x1001`` -+ - Detachment not feasible due to low clipboard battery. -+ -+ * - ``SDTX_DETACH_TIMEDOUT`` -+ - ``RUNTIME`` -+ - ``0x1002`` -+ - Detachment process timed out while the latch was locked. -+ -+ * - ``SDTX_ERR_FAILED_TO_OPEN`` -+ - ``HARDWARE`` -+ - ``0x2001`` -+ - Failed to open latch. -+ -+ * - ``SDTX_ERR_FAILED_TO_REMAIN_OPEN`` -+ - ``HARDWARE`` -+ - ``0x2002`` -+ - Failed to keep latch open. -+ -+ * - ``SDTX_ERR_FAILED_TO_CLOSE`` -+ - ``HARDWARE`` -+ - ``0x2003`` -+ - Failed to close latch. -+ -+Other error codes are reserved for future use. Non-error status codes may -+overlap and are generally only unique within their use-case: -+ -+.. flat-table:: Latch Status Codes. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Category -+ - Value -+ - Short Description -+ -+ * - ``SDTX_LATCH_CLOSED`` -+ - ``STATUS`` -+ - ``0x0000`` -+ - Latch is closed/has been closed. -+ -+ * - ``SDTX_LATCH_OPENED`` -+ - ``STATUS`` -+ - ``0x0001`` -+ - Latch is open/has been opened. -+ -+.. flat-table:: Base State Codes. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Category -+ - Value -+ - Short Description -+ -+ * - ``SDTX_BASE_DETACHED`` -+ - ``STATUS`` -+ - ``0x0000`` -+ - Base has been detached/is not present. -+ -+ * - ``SDTX_BASE_ATTACHED`` -+ - ``STATUS`` -+ - ``0x0001`` -+ - Base has been attached/is present. -+ -+Again, other codes are reserved for future use. -+ -+.. _events: -+ -+Events -+------ -+ -+Events can be received by reading from the device file. They are disabled by -+default and have to be enabled by executing ``SDTX_IOCTL_EVENTS_ENABLE`` -+first. All events follow the layout prescribed by |sdtx_event|. Specific -+event types can be identified by their event code, described in -+|sdtx_event_code|. Note that other event codes are reserved for future use, -+thus an event parser must be able to handle any unknown/unsupported event -+types gracefully, by relying on the payload length given in the event header. -+ -+Currently provided event types are: -+ -+.. flat-table:: Overview of DTX events. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Code -+ - Payload -+ - Short Description -+ -+ * - ``SDTX_EVENT_REQUEST`` -+ - ``1`` -+ - ``0`` bytes -+ - Detachment process initiated/aborted. -+ -+ * - ``SDTX_EVENT_CANCEL`` -+ - ``2`` -+ - ``2`` bytes -+ - EC canceled detachment process. -+ -+ * - ``SDTX_EVENT_BASE_CONNECTION`` -+ - ``3`` -+ - ``4`` bytes -+ - Base connection state changed. -+ -+ * - ``SDTX_EVENT_LATCH_STATUS`` -+ - ``4`` -+ - ``2`` bytes -+ - Latch status changed. -+ -+ * - ``SDTX_EVENT_DEVICE_MODE`` -+ - ``5`` -+ - ``2`` bytes -+ - Device mode changed. -+ -+Individual events in more detail: -+ -+``SDTX_EVENT_REQUEST`` -+^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when a detachment process is started or, if in progress, aborted by the -+user, either via a detach button press or a detach request -+(``SDTX_IOCTL_LATCH_REQUEST``) being sent from user-space. -+ -+Does not have any payload. -+ -+``SDTX_EVENT_CANCEL`` -+^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when a detachment process is canceled by the EC due to unfulfilled -+preconditions (e.g. clipboard battery too low to detach) or hardware -+failure. The reason for cancellation is given in the event payload detailed -+below and can be one of -+ -+* ``SDTX_DETACH_TIMEDOUT``: Detachment timed out while the latch was locked. -+ The latch has neither been opened nor unlocked. -+ -+* ``SDTX_DETACH_NOT_FEASIBLE``: Detachment not feasible due to low clipboard -+ battery. -+ -+* ``SDTX_ERR_FAILED_TO_OPEN``: Could not open the latch (hardware failure). -+ -+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``: Could not keep the latch open (hardware -+ failure). -+ -+* ``SDTX_ERR_FAILED_TO_CLOSE``: Could not close the latch (hardware failure). -+ -+Other error codes in this context are reserved for future use. -+ -+These codes can be classified via the ``SDTX_CATEGORY()`` macro to discern -+between critical hardware errors (``SDTX_CATEGORY_HARDWARE_ERROR``) or -+runtime errors (``SDTX_CATEGORY_RUNTIME_ERROR``), the latter of which may -+happen during normal operation if certain preconditions for detachment are -+not given. -+ -+.. flat-table:: Detachment Cancel Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``reason`` -+ - |__u16| -+ - Reason for cancellation. -+ -+``SDTX_EVENT_BASE_CONNECTION`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when the base connection state has changed, i.e. when the base has been -+attached, detached, or detachment has become infeasible due to low clipboard -+battery. The new state and, if a base is connected, ID of the base is -+provided as payload of type |sdtx_base_info| with its layout presented -+below: -+ -+.. flat-table:: Base-Connection-Change Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``state`` -+ - |__u16| -+ - Base connection state. -+ -+ * - ``base_id`` -+ - |__u16| -+ - Type of base connected (zero if none). -+ -+Possible values for ``state`` are: -+ -+* ``SDTX_BASE_DETACHED``, -+* ``SDTX_BASE_ATTACHED``, and -+* ``SDTX_DETACH_NOT_FEASIBLE``. -+ -+Other values are reserved for future use. -+ -+``SDTX_EVENT_LATCH_STATUS`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when the latch status has changed, i.e. when the latch has been opened, -+closed, or an error occurred. The current status is provided as payload: -+ -+.. flat-table:: Latch-Status-Change Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``status`` -+ - |__u16| -+ - Latch status. -+ -+Possible values for ``status`` are: -+ -+* ``SDTX_LATCH_CLOSED``, -+* ``SDTX_LATCH_OPENED``, -+* ``SDTX_ERR_FAILED_TO_OPEN``, -+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and -+* ``SDTX_ERR_FAILED_TO_CLOSE``. -+ -+Other values are reserved for future use. -+ -+``SDTX_EVENT_DEVICE_MODE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when the device mode has changed. The new device mode is provided as -+payload: -+ -+.. flat-table:: Device-Mode-Change Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``mode`` -+ - |__u16| -+ - Device operation mode. -+ -+Possible values for ``mode`` are: -+ -+* ``SDTX_DEVICE_MODE_TABLET``, -+* ``SDTX_DEVICE_MODE_LAPTOP``, and -+* ``SDTX_DEVICE_MODE_STUDIO``. -+ -+Other values are reserved for future use. -+ -+.. _ioctls: -+ -+IOCTLs -+------ -+ -+The following IOCTLs are provided: -+ -+.. flat-table:: Overview of DTX IOCTLs -+ :widths: 1 1 1 1 4 -+ :header-rows: 1 -+ -+ * - Type -+ - Number -+ - Direction -+ - Name -+ - Description -+ -+ * - ``0xA5`` -+ - ``0x21`` -+ - ``-`` -+ - ``EVENTS_ENABLE`` -+ - Enable events for the current file descriptor. -+ -+ * - ``0xA5`` -+ - ``0x22`` -+ - ``-`` -+ - ``EVENTS_DISABLE`` -+ - Disable events for the current file descriptor. -+ -+ * - ``0xA5`` -+ - ``0x23`` -+ - ``-`` -+ - ``LATCH_LOCK`` -+ - Lock the latch. -+ -+ * - ``0xA5`` -+ - ``0x24`` -+ - ``-`` -+ - ``LATCH_UNLOCK`` -+ - Unlock the latch. -+ -+ * - ``0xA5`` -+ - ``0x25`` -+ - ``-`` -+ - ``LATCH_REQUEST`` -+ - Request clipboard detachment. -+ -+ * - ``0xA5`` -+ - ``0x26`` -+ - ``-`` -+ - ``LATCH_CONFIRM`` -+ - Confirm clipboard detachment request. -+ -+ * - ``0xA5`` -+ - ``0x27`` -+ - ``-`` -+ - ``LATCH_HEARTBEAT`` -+ - Send heartbeat signal to EC. -+ -+ * - ``0xA5`` -+ - ``0x28`` -+ - ``-`` -+ - ``LATCH_CANCEL`` -+ - Cancel detachment process. -+ -+ * - ``0xA5`` -+ - ``0x29`` -+ - ``R`` -+ - ``GET_BASE_INFO`` -+ - Get current base/connection information. -+ -+ * - ``0xA5`` -+ - ``0x2A`` -+ - ``R`` -+ - ``GET_DEVICE_MODE`` -+ - Get current device operation mode. -+ -+ * - ``0xA5`` -+ - ``0x2B`` -+ - ``R`` -+ - ``GET_LATCH_STATUS`` -+ - Get current device latch status. -+ -+``SDTX_IOCTL_EVENTS_ENABLE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x22)``. -+ -+Enable events for the current file descriptor. Events can be obtained by -+reading from the device, if enabled. Events are disabled by default. -+ -+``SDTX_IOCTL_EVENTS_DISABLE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x22)``. -+ -+Disable events for the current file descriptor. Events can be obtained by -+reading from the device, if enabled. Events are disabled by default. -+ -+``SDTX_IOCTL_LATCH_LOCK`` -+^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x23)``. -+ -+Locks the latch, causing the detachment procedure to abort without opening -+the latch on timeout. The latch is unlocked by default. This command will be -+silently ignored if the latch is already locked. -+ -+``SDTX_IOCTL_LATCH_UNLOCK`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x24)``. -+ -+Unlocks the latch, causing the detachment procedure to open the latch on -+timeout. The latch is unlocked by default. This command will not open the -+latch when sent during an ongoing detachment process. It will be silently -+ignored if the latch is already unlocked. -+ -+``SDTX_IOCTL_LATCH_REQUEST`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x25)``. -+ -+Generic latch request. Behavior depends on the context: If no -+detachment-process is active, detachment is requested. Otherwise the -+currently active detachment-process will be aborted. -+ -+If a detachment process is canceled by this operation, a generic detachment -+request event (``SDTX_EVENT_REQUEST``) will be sent. -+ -+This essentially behaves the same as a detachment button press. -+ -+``SDTX_IOCTL_LATCH_CONFIRM`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x26)``. -+ -+Acknowledges and confirms a latch request. If sent during an ongoing -+detachment process, this command causes the latch to be opened immediately. -+The latch will also be opened if it has been locked. In this case, the latch -+lock is reset to the unlocked state. -+ -+This command will be silently ignored if there is currently no detachment -+procedure in progress. -+ -+``SDTX_IOCTL_LATCH_HEARTBEAT`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x27)``. -+ -+Sends a heartbeat, essentially resetting the detachment timeout. This -+command can be used to keep the detachment process alive while work required -+for the detachment to succeed is still in progress. -+ -+This command will be silently ignored if there is currently no detachment -+procedure in progress. -+ -+``SDTX_IOCTL_LATCH_CANCEL`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x28)``. -+ -+Cancels detachment in progress (if any). If a detachment process is canceled -+by this operation, a generic detachment request event -+(``SDTX_EVENT_REQUEST``) will be sent. -+ -+This command will be silently ignored if there is currently no detachment -+procedure in progress. -+ -+``SDTX_IOCTL_GET_BASE_INFO`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IOR(0xA5, 0x29, struct sdtx_base_info)``. -+ -+Get the current base connection state (i.e. attached/detached) and the type -+of the base connected to the clipboard. This is command essentially provides -+a way to query the information provided by the base connection change event -+(``SDTX_EVENT_BASE_CONNECTION``). -+ -+Possible values for ``struct sdtx_base_info.state`` are: -+ -+* ``SDTX_BASE_DETACHED``, -+* ``SDTX_BASE_ATTACHED``, and -+* ``SDTX_DETACH_NOT_FEASIBLE``. -+ -+Other values are reserved for future use. -+ -+``SDTX_IOCTL_GET_DEVICE_MODE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IOR(0xA5, 0x2A, __u16)``. -+ -+Returns the device operation mode, indicating if and how the base is -+attached to the clipboard. This is command essentially provides a way to -+query the information provided by the device mode change event -+(``SDTX_EVENT_DEVICE_MODE``). -+ -+Returned values are: -+ -+* ``SDTX_DEVICE_MODE_LAPTOP`` -+* ``SDTX_DEVICE_MODE_TABLET`` -+* ``SDTX_DEVICE_MODE_STUDIO`` -+ -+See |sdtx_device_mode| for details. Other values are reserved for future -+use. -+ -+ -+``SDTX_IOCTL_GET_LATCH_STATUS`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IOR(0xA5, 0x2B, __u16)``. -+ -+Get the current latch status or (presumably) the last error encountered when -+trying to open/close the latch. This is command essentially provides a way -+to query the information provided by the latch status change event -+(``SDTX_EVENT_LATCH_STATUS``). -+ -+Returned values are: -+ -+* ``SDTX_LATCH_CLOSED``, -+* ``SDTX_LATCH_OPENED``, -+* ``SDTX_ERR_FAILED_TO_OPEN``, -+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and -+* ``SDTX_ERR_FAILED_TO_CLOSE``. -+ -+Other values are reserved for future use. -+ -+A Note on Base IDs -+------------------ -+ -+Base types/IDs provided via ``SDTX_EVENT_BASE_CONNECTION`` or -+``SDTX_IOCTL_GET_BASE_INFO`` are directly forwarded from from the EC in the -+lower byte of the combined |__u16| value, with the driver storing the EC -+type from which this ID comes in the high byte (without this, base IDs over -+different types of ECs may be overlapping). -+ -+The ``SDTX_DEVICE_TYPE()`` macro can be used to determine the EC device -+type. This can be one of -+ -+* ``SDTX_DEVICE_TYPE_HID``, for Surface Aggregator Module over HID, and -+ -+* ``SDTX_DEVICE_TYPE_SSH``, for Surface Aggregator Module over Surface Serial -+ Hub. -+ -+Note that currently only the ``SSH`` type EC is supported, however ``HID`` -+type is reserved for future use. -+ -+Structures and Enums -+-------------------- -+ -+.. kernel-doc:: include/uapi/linux/surface_aggregator/dtx.h -diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst -new file mode 100644 -index 000000000000..98ea9946b8a2 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/clients/index.rst -@@ -0,0 +1,22 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+=========================== -+Client Driver Documentation -+=========================== -+ -+This is the documentation for client drivers themselves. Refer to -+:doc:`../client` for documentation on how to write client drivers. -+ -+.. toctree:: -+ :maxdepth: 1 -+ -+ cdev -+ dtx -+ san -+ -+.. only:: subproject and html -+ -+ Indices -+ ======= -+ -+ * :ref:`genindex` -diff --git a/Documentation/driver-api/surface_aggregator/clients/san.rst b/Documentation/driver-api/surface_aggregator/clients/san.rst -new file mode 100644 -index 000000000000..1bf830ad367d ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/clients/san.rst -@@ -0,0 +1,44 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |san_client_link| replace:: :c:func:`san_client_link` -+.. |san_dgpu_notifier_register| replace:: :c:func:`san_dgpu_notifier_register` -+.. |san_dgpu_notifier_unregister| replace:: :c:func:`san_dgpu_notifier_unregister` -+ -+=================== -+Surface ACPI Notify -+=================== -+ -+The Surface ACPI Notify (SAN) device provides the bridge between ACPI and -+SAM controller. Specifically, ACPI code can execute requests and handle -+battery and thermal events via this interface. In addition to this, events -+relating to the discrete GPU (dGPU) of the Surface Book 2 can be sent from -+ACPI code (note: the Surface Book 3 uses a different method for this). The -+only currently known event sent via this interface is a dGPU power-on -+notification. While this driver handles the former part internally, it only -+relays the dGPU events to any other driver interested via its public API and -+does not handle them. -+ -+The public interface of this driver is split into two parts: Client -+registration and notifier-block registration. -+ -+A client to the SAN interface can be linked as consumer to the SAN device -+via |san_client_link|. This can be used to ensure that the a client -+receiving dGPU events does not miss any events due to the SAN interface not -+being set up as this forces the client driver to unbind once the SAN driver -+is unbound. -+ -+Notifier-blocks can be registered by any device for as long as the module is -+loaded, regardless of being linked as client or not. Registration is done -+with |san_dgpu_notifier_register|. If the notifier is not needed any more, it -+should be unregistered via |san_dgpu_notifier_unregister|. -+ -+Consult the API documentation below for more details. -+ -+ -+API Documentation -+================= -+ -+.. kernel-doc:: include/linux/surface_acpi_notify.h -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/clients/surface_acpi_notify.c -+ :export: -diff --git a/Documentation/driver-api/surface_aggregator/index.rst b/Documentation/driver-api/surface_aggregator/index.rst -new file mode 100644 -index 000000000000..6f3e1094904d ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/index.rst -@@ -0,0 +1,21 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+======================================= -+Surface System Aggregator Module (SSAM) -+======================================= -+ -+.. toctree:: -+ :maxdepth: 2 -+ -+ overview -+ client -+ clients/index -+ ssh -+ internal -+ -+.. only:: subproject and html -+ -+ Indices -+ ======= -+ -+ * :ref:`genindex` -diff --git a/Documentation/driver-api/surface_aggregator/internal-api.rst b/Documentation/driver-api/surface_aggregator/internal-api.rst -new file mode 100644 -index 000000000000..db6a70119f49 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/internal-api.rst -@@ -0,0 +1,67 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+========================== -+Internal API Documentation -+========================== -+ -+.. contents:: -+ :depth: 2 -+ -+ -+Packet Transport Layer -+====================== -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_parser.h -+ :internal: -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_parser.c -+ :internal: -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_msgb.h -+ :internal: -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_packet_layer.h -+ :internal: -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_packet_layer.c -+ :internal: -+ -+ -+Request Transport Layer -+======================= -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_request_layer.h -+ :internal: -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_request_layer.c -+ :internal: -+ -+ -+Controller -+========== -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/controller.h -+ :internal: -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/controller.c -+ :internal: -+ -+ -+Client Device Bus -+================= -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/bus.c -+ :internal: -+ -+ -+Core -+==== -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/core.c -+ :internal: -+ -+ -+Trace Helpers -+============= -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/trace.h -diff --git a/Documentation/driver-api/surface_aggregator/internal.rst b/Documentation/driver-api/surface_aggregator/internal.rst -new file mode 100644 -index 000000000000..72704734982a ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/internal.rst -@@ -0,0 +1,577 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |ssh_ptl| replace:: :c:type:`struct ssh_ptl ` -+.. |ssh_ptl_submit| replace:: :c:func:`ssh_ptl_submit` -+.. |ssh_ptl_cancel| replace:: :c:func:`ssh_ptl_cancel` -+.. |ssh_ptl_shutdown| replace:: :c:func:`ssh_ptl_shutdown` -+.. |ssh_ptl_rx_rcvbuf| replace:: :c:func:`ssh_ptl_rx_rcvbuf` -+.. |ssh_rtl| replace:: :c:type:`struct ssh_rtl ` -+.. |ssh_rtl_submit| replace:: :c:func:`ssh_rtl_submit` -+.. |ssh_rtl_cancel| replace:: :c:func:`ssh_rtl_cancel` -+.. |ssh_rtl_shutdown| replace:: :c:func:`ssh_rtl_shutdown` -+.. |ssh_packet| replace:: :c:type:`struct ssh_packet ` -+.. |ssh_packet_get| replace:: :c:func:`ssh_packet_get` -+.. |ssh_packet_put| replace:: :c:func:`ssh_packet_put` -+.. |ssh_packet_ops| replace:: :c:type:`struct ssh_packet_ops ` -+.. |ssh_packet_base_priority| replace:: :c:type:`enum ssh_packet_base_priority ` -+.. |ssh_packet_flags| replace:: :c:type:`enum ssh_packet_flags ` -+.. |SSH_PACKET_PRIORITY| replace:: :c:func:`SSH_PACKET_PRIORITY` -+.. |ssh_frame| replace:: :c:type:`struct ssh_frame ` -+.. |ssh_command| replace:: :c:type:`struct ssh_command ` -+.. |ssh_request| replace:: :c:type:`struct ssh_request ` -+.. |ssh_request_get| replace:: :c:func:`ssh_request_get` -+.. |ssh_request_put| replace:: :c:func:`ssh_request_put` -+.. |ssh_request_ops| replace:: :c:type:`struct ssh_request_ops ` -+.. |ssh_request_init| replace:: :c:func:`ssh_request_init` -+.. |ssh_request_flags| replace:: :c:type:`enum ssh_request_flags ` -+.. |ssam_controller| replace:: :c:type:`struct ssam_controller ` -+.. |ssam_device| replace:: :c:type:`struct ssam_device ` -+.. |ssam_device_driver| replace:: :c:type:`struct ssam_device_driver ` -+.. |ssam_client_bind| replace:: :c:func:`ssam_client_bind` -+.. |ssam_client_link| replace:: :c:func:`ssam_client_link` -+.. |ssam_request_sync| replace:: :c:type:`struct ssam_request_sync ` -+.. |ssam_event_registry| replace:: :c:type:`struct ssam_event_registry ` -+.. |ssam_event_id| replace:: :c:type:`struct ssam_event_id ` -+.. |ssam_nf| replace:: :c:type:`struct ssam_nf ` -+.. |ssam_nf_refcount_inc| replace:: :c:func:`ssam_nf_refcount_inc` -+.. |ssam_nf_refcount_dec| replace:: :c:func:`ssam_nf_refcount_dec` -+.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` -+.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` -+.. |ssam_cplt| replace:: :c:type:`struct ssam_cplt ` -+.. |ssam_event_queue| replace:: :c:type:`struct ssam_event_queue ` -+.. |ssam_request_sync_submit| replace:: :c:func:`ssam_request_sync_submit` -+ -+===================== -+Core Driver Internals -+===================== -+ -+Architectural overview of the Surface System Aggregator Module (SSAM) core -+and Surface Serial Hub (SSH) driver. For the API documentation, refer to: -+ -+.. toctree:: -+ :maxdepth: 2 -+ -+ internal-api -+ -+ -+Overview -+======== -+ -+The SSAM core implementation is structured in layers, somewhat following the -+SSH protocol structure: -+ -+Lower-level packet transport is implemented in the *packet transport layer -+(PTL)*, directly building on top of the serial device (serdev) -+infrastructure of the kernel. As the name indicates, this layer deals with -+the packet transport logic and handles things like packet validation, packet -+acknowledgment (ACKing), packet (retransmission) timeouts, and relaying -+packet payloads to higher-level layers. -+ -+Above this sits the *request transport layer (RTL)*. This layer is centered -+around command-type packet payloads, i.e. requests (sent from host to EC), -+responses of the EC to those requests, and events (sent from EC to host). -+It, specifically, distinguishes events from request responses, matches -+responses to their corresponding requests, and implements request timeouts. -+ -+The *controller* layer is building on top of this and essentially decides -+how request responses and, especially, events are dealt with. It provides an -+event notifier system, handles event activation/deactivation, provides a -+workqueue for event and asynchronous request completion, and also manages -+the message counters required for building command messages (``SEQ``, -+``RQID``). This layer basically provides a fundamental interface to the SAM -+EC for use in other kernel drivers. -+ -+While the controller layer already provides an interface for other kernel -+drivers, the client *bus* extends this interface to provide support for -+native SSAM devices, i.e. devices that are not defined in ACPI and not -+implemented as platform devices, via |ssam_device| and |ssam_device_driver| -+simplify management of client devices and client drivers. -+ -+Refer to :doc:`client` for documentation regarding the client device/driver -+API and interface options for other kernel drivers. It is recommended to -+familiarize oneself with that chapter and the :doc:`ssh` before continuing -+with the architectural overview below. -+ -+ -+Packet Transport Layer -+====================== -+ -+The packet transport layer is represented via |ssh_ptl| and is structured -+around the following key concepts: -+ -+Packets -+------- -+ -+Packets are the fundamental transmission unit of the SSH protocol. They are -+managed by the packet transport layer, which is essentially the lowest layer -+of the driver and is built upon by other components of the SSAM core. -+Packets to be transmitted by the SSAM core are represented via |ssh_packet| -+(in contrast, packets received by the core do not have any specific -+structure and are managed entirely via the raw |ssh_frame|). -+ -+This structure contains the required fields to manage the packet inside the -+transport layer, as well as a reference to the buffer containing the data to -+be transmitted (i.e. the message wrapped in |ssh_frame|). Most notably, it -+contains an internal reference count, which is used for managing its -+lifetime (accessible via |ssh_packet_get| and |ssh_packet_put|). When this -+counter reaches zero, the ``release()`` callback provided to the packet via -+its |ssh_packet_ops| reference is executed, which may then deallocate the -+packet or its enclosing structure (e.g. |ssh_request|). -+ -+In addition to the ``release`` callback, the |ssh_packet_ops| reference also -+provides a ``complete()`` callback, which is run once the packet has been -+completed and provides the status of this completion, i.e. zero on success -+or a negative errno value in case of an error. Once the packet has been -+submitted to the packet transport layer, the ``complete()`` callback is -+always guaranteed to be executed before the ``release()`` callback, i.e. the -+packet will always be completed, either successfully, with an error, or due -+to cancellation, before it will be released. -+ -+The state of a packet is managed via its ``state`` flags -+(|ssh_packet_flags|), which also contains the packet type. In particular, -+the following bits are noteworthy: -+ -+* ``SSH_PACKET_SF_LOCKED_BIT``: This bit is set when completion, either -+ through error or success, is imminent. It indicates that no further -+ references of the packet should be taken and any existing references -+ should be dropped as soon as possible. The process setting this bit is -+ responsible for removing any references to this packet from the packet -+ queue and pending set. -+ -+* ``SSH_PACKET_SF_COMPLETED_BIT``: This bit is set by the process running the -+ ``complete()`` callback and is used to ensure that this callback only runs -+ once. -+ -+* ``SSH_PACKET_SF_QUEUED_BIT``: This bit is set when the packet is queued on -+ the packet queue and cleared when it is dequeued. -+ -+* ``SSH_PACKET_SF_PENDING_BIT``: This bit is set when the packet is added to -+ the pending set and cleared when it is removed from it. -+ -+Packet Queue -+------------ -+ -+The packet queue is the first of the two fundamental collections in the -+packet transport layer. It is a priority queue, with priority of the -+respective packets based on the packet type (major) and number of tries -+(minor). See |SSH_PACKET_PRIORITY| for more details on the priority value. -+ -+All packets to be transmitted by the transport layer must be submitted to -+this queue via |ssh_ptl_submit|. Note that this includes control packets -+sent by the transport layer itself. Internally, data packets can be -+re-submitted to this queue due to timeouts or NAK packets sent by the EC. -+ -+Pending Set -+----------- -+ -+The pending set is the second of the two fundamental collections in the -+packet transport layer. It stores references to packets that have already -+been transmitted, but wait for acknowledgment (e.g. the corresponding ACK -+packet) by the EC. -+ -+Note that a packet may both be pending and queued if it has been -+re-submitted due to a packet acknowledgment timeout or NAK. On such a -+re-submission, packets are not removed from the pending set. -+ -+Transmitter Thread -+------------------ -+ -+The transmitter thread is responsible for most of the actual work regarding -+packet transmission. In each iteration, it (waits for and) checks if the -+next packet on the queue (if any) can be transmitted and, if so, removes it -+from the queue and increments its counter for the number of transmission -+attempts, i.e. tries. If the packet is sequenced, i.e. requires an ACK by -+the EC, the packet is added to the pending set. Next, the packet's data is -+submitted to the serdev subsystem. In case of an error or timeout during -+this submission, the packet is completed by the transmitter thread with the -+status value of the callback set accordingly. In case the packet is -+unsequenced, i.e. does not require an ACK by the EC, the packet is completed -+with success on the transmitter thread. -+ -+Transmission of sequenced packets is limited by the number of concurrently -+pending packets, i.e. a limit on how many packets may be waiting for an ACK -+from the EC in parallel. This limit is currently set to one (see :doc:`ssh` -+for the reasoning behind this). Control packets (i.e. ACK and NAK) can -+always be transmitted. -+ -+Receiver Thread -+--------------- -+ -+Any data received from the EC is put into a FIFO buffer for further -+processing. This processing happens on the receiver thread. The receiver -+thread parses and validates the received message into its |ssh_frame| and -+corresponding payload. It prepares and submits the necessary ACK (and on -+validation error or invalid data NAK) packets for the received messages. -+ -+This thread also handles further processing, such as matching ACK messages -+to the corresponding pending packet (via sequence ID) and completing it, as -+well as initiating re-submission of all currently pending packets on -+receival of a NAK message (re-submission in case of a NAK is similar to -+re-submission due to timeout, see below for more details on that). Note that -+the successful completion of a sequenced packet will always run on the -+receiver thread (whereas any failure-indicating completion will run on the -+process where the failure occurred). -+ -+Any payload data is forwarded via a callback to the next upper layer, i.e. -+the request transport layer. -+ -+Timeout Reaper -+-------------- -+ -+The packet acknowledgment timeout is a per-packet timeout for sequenced -+packets, started when the respective packet begins (re-)transmission (i.e. -+this timeout is armed once per transmission attempt on the transmitter -+thread). It is used to trigger re-submission or, when the number of tries -+has been exceeded, cancellation of the packet in question. -+ -+This timeout is handled via a dedicated reaper task, which is essentially a -+work item (re-)scheduled to run when the next packet is set to time out. The -+work item then checks the set of pending packets for any packets that have -+exceeded the timeout and, if there are any remaining packets, re-schedules -+itself to the next appropriate point in time. -+ -+If a timeout has been detected by the reaper, the packet will either be -+re-submitted if it still has some remaining tries left, or completed with -+``-ETIMEDOUT`` as status if not. Note that re-submission, in this case and -+triggered by receival of a NAK, means that the packet is added to the queue -+with a now incremented number of tries, yielding a higher priority. The -+timeout for the packet will be disabled until the next transmission attempt -+and the packet remains on the pending set. -+ -+Note that due to transmission and packet acknowledgment timeouts, the packet -+transport layer is always guaranteed to make progress, if only through -+timing out packets, and will never fully block. -+ -+Concurrency and Locking -+----------------------- -+ -+There are two main locks in the packet transport layer: One guarding access -+to the packet queue and one guarding access to the pending set. These -+collections may only be accessed and modified under the respective lock. If -+access to both collections is needed, the pending lock must be acquired -+before the queue lock to avoid deadlocks. -+ -+In addition to guarding the collections, after initial packet submission -+certain packet fields may only be accessed under one of the locks. -+Specifically, the packet priority must only be accessed while holding the -+queue lock and the packet timestamp must only be accessed while holding the -+pending lock. -+ -+Other parts of the packet transport layer are guarded independently. State -+flags are managed by atomic bit operations and, if necessary, memory -+barriers. Modifications to the timeout reaper work item and expiration date -+are guarded by their own lock. -+ -+The reference of the packet to the packet transport layer (``ptl``) is -+somewhat special. It is either set when the upper layer request is submitted -+or, if there is none, when the packet is first submitted. After it is set, -+it will not change its value. Functions that may run concurrently with -+submission, i.e. cancellation, can not rely on the ``ptl`` reference to be -+set. Access to it in these functions is guarded by ``READ_ONCE()``, whereas -+setting ``ptl`` is equally guarded with ``WRITE_ONCE()`` for symmetry. -+ -+Some packet fields may be read outside of the respective locks guarding -+them, specifically priority and state for tracing. In those cases, proper -+access is ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. Such -+read-only access is only allowed when stale values are not critical. -+ -+With respect to the interface for higher layers, packet submission -+(|ssh_ptl_submit|), packet cancellation (|ssh_ptl_cancel|), data receival -+(|ssh_ptl_rx_rcvbuf|), and layer shutdown (|ssh_ptl_shutdown|) may always be -+executed concurrently with respect to each other. Note that packet -+submission may not run concurrently with itself for the same packet. -+Equally, shutdown and data receival may also not run concurrently with -+themselves (but may run concurrently with each other). -+ -+ -+Request Transport Layer -+======================= -+ -+The request transport layer is represented via |ssh_rtl| and builds on top -+of the packet transport layer. It deals with requests, i.e. SSH packets sent -+by the host containing a |ssh_command| as frame payload. This layer -+separates responses to requests from events, which are also sent by the EC -+via a |ssh_command| payload. While responses are handled in this layer, -+events are relayed to the next upper layer, i.e. the controller layer, via -+the corresponding callback. The request transport layer is structured around -+the following key concepts: -+ -+Request -+------- -+ -+Requests are packets with a command-type payload, sent from host to EC to -+query data from or trigger an action on it (or both simultaneously). They -+are represented by |ssh_request|, wrapping the underlying |ssh_packet| -+storing its message data (i.e. SSH frame with command payload). Note that -+all top-level representations, e.g. |ssam_request_sync| are built upon this -+struct. -+ -+As |ssh_request| extends |ssh_packet|, its lifetime is also managed by the -+reference counter inside the packet struct (which can be accessed via -+|ssh_request_get| and |ssh_request_put|). Once the counter reaches zero, the -+``release()`` callback of the |ssh_request_ops| reference of the request is -+called. -+ -+Requests can have an optional response that is equally sent via a SSH -+message with command-type payload (from EC to host). The party constructing -+the request must know if a response is expected and mark this in the request -+flags provided to |ssh_request_init|, so that the request transport layer -+can wait for this response. -+ -+Similar to |ssh_packet|, |ssh_request| also has a ``complete()`` callback -+provided via its request ops reference and is guaranteed to be completed -+before it is released once it has been submitted to the request transport -+layer via |ssh_rtl_submit|. For a request without a response, successful -+completion will occur once the underlying packet has been successfully -+transmitted by the packet transport layer (i.e. from within the packet -+completion callback). For a request with response, successful completion -+will occur once the response has been received and matched to the request -+via its request ID (which happens on the packet layer's data-received -+callback running on the receiver thread). If the request is completed with -+an error, the status value will be set to the corresponding (negative) errno -+value. -+ -+The state of a request is again managed via its ``state`` flags -+(|ssh_request_flags|), which also encode the request type. In particular, -+the following bits are noteworthy: -+ -+* ``SSH_REQUEST_SF_LOCKED_BIT``: This bit is set when completion, either -+ through error or success, is imminent. It indicates that no further -+ references of the request should be taken and any existing references -+ should be dropped as soon as possible. The process setting this bit is -+ responsible for removing any references to this request from the request -+ queue and pending set. -+ -+* ``SSH_REQUEST_SF_COMPLETED_BIT``: This bit is set by the process running the -+ ``complete()`` callback and is used to ensure that this callback only runs -+ once. -+ -+* ``SSH_REQUEST_SF_QUEUED_BIT``: This bit is set when the request is queued on -+ the request queue and cleared when it is dequeued. -+ -+* ``SSH_REQUEST_SF_PENDING_BIT``: This bit is set when the request is added to -+ the pending set and cleared when it is removed from it. -+ -+Request Queue -+------------- -+ -+The request queue is the first of the two fundamental collections in the -+request transport layer. In contrast to the packet queue of the packet -+transport layer, it is not a priority queue and the simple first come first -+serve principle applies. -+ -+All requests to be transmitted by the request transport layer must be -+submitted to this queue via |ssh_rtl_submit|. Once submitted, requests may -+not be re-submitted, and will not be re-submitted automatically on timeout. -+Instead, the request is completed with a timeout error. If desired, the -+caller can create and submit a new request for another try, but it must not -+submit the same request again. -+ -+Pending Set -+----------- -+ -+The pending set is the second of the two fundamental collections in the -+request transport layer. This collection stores references to all pending -+requests, i.e. requests awaiting a response from the EC (similar to what the -+pending set of the packet transport layer does for packets). -+ -+Transmitter Task -+---------------- -+ -+The transmitter task is scheduled when a new request is available for -+transmission. It checks if the next request on the request queue can be -+transmitted and, if so, submits its underlying packet to the packet -+transport layer. This check ensures that only a limited number of -+requests can be pending, i.e. waiting for a response, at the same time. If -+the request requires a response, the request is added to the pending set -+before its packet is submitted. -+ -+Packet Completion Callback -+-------------------------- -+ -+The packet completion callback is executed once the underlying packet of a -+request has been completed. In case of an error completion, the -+corresponding request is completed with the error value provided in this -+callback. -+ -+On successful packet completion, further processing depends on the request. -+If the request expects a response, it is marked as transmitted and the -+request timeout is started. If the request does not expect a response, it is -+completed with success. -+ -+Data-Received Callback -+---------------------- -+ -+The data received callback notifies the request transport layer of data -+being received by the underlying packet transport layer via a data-type -+frame. In general, this is expected to be a command-type payload. -+ -+If the request ID of the command is one of the request IDs reserved for -+events (one to ``SSH_NUM_EVENTS``, inclusively), it is forwarded to the -+event callback registered in the request transport layer. If the request ID -+indicates a response to a request, the respective request is looked up in -+the pending set and, if found and marked as transmitted, completed with -+success. -+ -+Timeout Reaper -+-------------- -+ -+The request-response-timeout is a per-request timeout for requests expecting -+a response. It is used to ensure that a request does not wait indefinitely -+on a response from the EC and is started after the underlying packet has -+been successfully completed. -+ -+This timeout is, similar to the packet acknowledgment timeout on the packet -+transport layer, handled via a dedicated reaper task. This task is -+essentially a work-item (re-)scheduled to run when the next request is set -+to time out. The work item then scans the set of pending requests for any -+requests that have timed out and completes them with ``-ETIMEDOUT`` as -+status. Requests will not be re-submitted automatically. Instead, the issuer -+of the request must construct and submit a new request, if so desired. -+ -+Note that this timeout, in combination with packet transmission and -+acknowledgment timeouts, guarantees that the request layer will always make -+progress, even if only through timing out packets, and never fully block. -+ -+Concurrency and Locking -+----------------------- -+ -+Similar to the packet transport layer, there are two main locks in the -+request transport layer: One guarding access to the request queue and one -+guarding access to the pending set. These collections may only be accessed -+and modified under the respective lock. -+ -+Other parts of the request transport layer are guarded independently. State -+flags are (again) managed by atomic bit operations and, if necessary, memory -+barriers. Modifications to the timeout reaper work item and expiration date -+are guarded by their own lock. -+ -+Some request fields may be read outside of the respective locks guarding -+them, specifically the state for tracing. In those cases, proper access is -+ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. Such read-only -+access is only allowed when stale values are not critical. -+ -+With respect to the interface for higher layers, request submission -+(|ssh_rtl_submit|), request cancellation (|ssh_rtl_cancel|), and layer -+shutdown (|ssh_rtl_shutdown|) may always be executed concurrently with -+respect to each other. Note that request submission may not run concurrently -+with itself for the same request (and also may only be called once per -+request). Equally, shutdown may also not run concurrently with itself. -+ -+ -+Controller Layer -+================ -+ -+The controller layer extends on the request transport layer to provide an -+easy-to-use interface for client drivers. It is represented by -+|ssam_controller| and the SSH driver. While the lower level transport layers -+take care of transmitting and handling packets and requests, the controller -+layer takes on more of a management role. Specifically, it handles device -+initialization, power management, and event handling, including event -+delivery and registration via the (event) completion system (|ssam_cplt|). -+ -+Event Registration -+------------------ -+ -+In general, an event (or rather a class of events) has to be explicitly -+requested by the host before the EC will send it (HID input events seem to -+be the exception). This is done via an event-enable request (similarly, -+events should be disabled via an event-disable request once no longer -+desired). -+ -+The specific request used to enable (or disable) an event is given via an -+event registry, i.e. the governing authority of this event (so to speak), -+represented by |ssam_event_registry|. As parameters to this request, the -+target category and, depending on the event registry, instance ID of the -+event to be enabled must be provided. This (optional) instance ID must be -+zero if the registry does not use it. Together, target category and instance -+ID form the event ID, represented by |ssam_event_id|. In short, both, event -+registry and event ID, are required to uniquely identify a respective class -+of events. -+ -+Note that a further *request ID* parameter must be provided for the -+enable-event request. This parameter does not influence the class of events -+being enabled, but instead is set as the request ID (RQID) on each event of -+this class sent by the EC. It is used to identify events (as a limited -+number of request IDs is reserved for use in events only, specifically one -+to ``SSH_NUM_EVENTS`` inclusively) and also map events to their specific -+class. Currently, the controller always sets this parameter to the target -+category specified in |ssam_event_id|. -+ -+As multiple client drivers may rely on the same (or overlapping) classes of -+events and enable/disable calls are strictly binary (i.e. on/off), the -+controller has to manage access to these events. It does so via reference -+counting, storing the counter inside an RB-tree based mapping with event -+registry and ID as key (there is no known list of valid event registry and -+event ID combinations). See |ssam_nf|, |ssam_nf_refcount_inc|, and -+|ssam_nf_refcount_dec| for details. -+ -+This management is done together with notifier registration (described in -+the next section) via the top-level |ssam_notifier_register| and -+|ssam_notifier_unregister| functions. -+ -+Event Delivery -+-------------- -+ -+To receive events, a client driver has to register an event notifier via -+|ssam_notifier_register|. This increments the reference counter for that -+specific class of events (as detailed in the previous section), enables the -+class on the EC (if it has not been enabled already), and installs the -+provided notifier callback. -+ -+Notifier callbacks are stored in lists, with one (RCU) list per target -+category (provided via the event ID; NB: there is a fixed known number of -+target categories). There is no known association from the combination of -+event registry and event ID to the command data (target ID, target category, -+command ID, and instance ID) that can be provided by an event class, apart -+from target category and instance ID given via the event ID. -+ -+Note that due to the way notifiers are (or rather have to be) stored, client -+drivers may receive events that they have not requested and need to account -+for them. Specifically, they will, by default, receive all events from the -+same target category. To simplify dealing with this, filtering of events by -+target ID (provided via the event registry) and instance ID (provided via -+the event ID) can be requested when registering a notifier. This filtering -+is applied when iterating over the notifiers at the time they are executed. -+ -+All notifier callbacks are executed on a dedicated workqueue, the so-called -+completion workqueue. After an event has been received via the callback -+installed in the request layer (running on the receiver thread of the packet -+transport layer), it will be put on its respective event queue -+(|ssam_event_queue|). From this event queue the completion work item of that -+queue (running on the completion workqueue) will pick up the event and -+execute the notifier callback. This is done to avoid blocking on the -+receiver thread. -+ -+There is one event queue per combination of target ID and target category. -+This is done to ensure that notifier callbacks are executed in sequence for -+events of the same target ID and target category. Callbacks can be executed -+in parallel for events with a different combination of target ID and target -+category. -+ -+Concurrency and Locking -+----------------------- -+ -+Most of the concurrency related safety guarantees of the controller are -+provided by the lower-level request transport layer. In addition to this, -+event (un-)registration is guarded by its own lock. -+ -+Access to the controller state is guarded by the state lock. This lock is a -+read/write semaphore. The reader part can be used to ensure that the state -+does not change while functions depending on the state to stay the same -+(e.g. |ssam_notifier_register|, |ssam_notifier_unregister|, -+|ssam_request_sync_submit|, and derivatives) are executed and this guarantee -+is not already provided otherwise (e.g. through |ssam_client_bind| or -+|ssam_client_link|). The writer part guards any transitions that will change -+the state, i.e. initialization, destruction, suspension, and resumption. -+ -+The controller state may be accessed (read-only) outside the state lock for -+smoke-testing against invalid API usage (e.g. in |ssam_request_sync_submit|). -+Note that such checks are not supposed to (and will not) protect against all -+invalid usages, but rather aim to help catch them. In those cases, proper -+variable access is ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. -+ -+Assuming any preconditions on the state not changing have been satisfied, -+all non-initialization and non-shutdown functions may run concurrently with -+each other. This includes |ssam_notifier_register|, |ssam_notifier_unregister|, -+|ssam_request_sync_submit|, as well as all functions building on top of those. -diff --git a/Documentation/driver-api/surface_aggregator/overview.rst b/Documentation/driver-api/surface_aggregator/overview.rst -new file mode 100644 -index 000000000000..1e9d57e50063 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/overview.rst -@@ -0,0 +1,77 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+======== -+Overview -+======== -+ -+The Surface/System Aggregator Module (SAM, SSAM) is an (arguably *the*) -+embedded controller (EC) on Microsoft Surface devices. It has been originally -+introduced on 4th generation devices (Surface Pro 4, Surface Book 1), but -+its responsibilities and feature-set have since been expanded significantly -+with the following generations. -+ -+ -+Features and Integration -+======================== -+ -+Not much is currently known about SAM on 4th generation devices (Surface Pro -+4, Surface Book 1), due to the use of a different communication interface -+between host and EC (as detailed below). On 5th (Surface Pro 2017, Surface -+Book 2, Surface Laptop 1) and later generation devices, SAM is responsible -+for providing battery information (both current status and static values, -+such as maximum capacity etc.), as well as an assortment of temperature -+sensors (e.g. skin temperature) and cooling/performance-mode setting to the -+host. On the Surface Book 2, specifically, it additionally provides an -+interface for properly handling clipboard detachment (i.e. separating the -+display part from the keyboard part of the device), on the Surface Laptop 1 -+and 2 it is required for keyboard HID input. This HID subsystem has been -+restructured for 7th generation devices and on those, specifically Surface -+Laptop 3 and Surface Book 3, is responsible for all major HID input (i.e. -+keyboard and touchpad). -+ -+While features have not changed much on a coarse level since the 5th -+generation, internal interfaces have undergone some rather large changes. On -+5th and 6th generation devices, both battery and temperature information is -+exposed to ACPI via a shim driver (referred to as Surface ACPI Notify, or -+SAN), translating ACPI generic serial bus write-/read-accesses to SAM -+requests. On 7th generation devices, this additional layer is gone and these -+devices require a driver hooking directly into the SAM interface. Equally, -+on newer generations, less devices are declared in ACPI, making them a bit -+harder to discover and requiring us to hard-code a sort of device registry. -+Due to this, a SSAM bus and subsystem with client devices -+(:c:type:`struct ssam_device `) has been implemented. -+ -+ -+Communication -+============= -+ -+The type of communication interface between host and EC depends on the -+generation of the Surface device. On 4th generation devices, host and EC -+communicate via HID, specifically using a HID-over-I2C device, whereas on -+5th and later generations, communication takes place via a USART serial -+device. In accordance to the drivers found on other operating systems, we -+refer to the serial device and its driver as Surface Serial Hub (SSH). When -+needed, we differentiate between both types of SAM by referring to them as -+SAM-over-SSH and SAM-over-HID. -+ -+Currently, this subsystem only supports SAM-over-SSH. The SSH communication -+interface is described in more detail below. The HID interface has not been -+reverse engineered yet and it is, at the moment, unclear how many (and -+which) concepts of the SSH interface detailed below can be transferred to -+it. -+ -+Surface Serial Hub -+------------------ -+ -+As already elaborated above, the Surface Serial Hub (SSH) is the -+communication interface for SAM on 5th- and all later-generation Surface -+devices. On the highest level, communication can be separated into two main -+types: Requests, messages sent from host to EC that may trigger a direct -+response from the EC (explicitly associated with the request), and events -+(sometimes also referred to as notifications), sent from EC to host without -+being a direct response to a previous request. We may also refer to requests -+without response as commands. In general, events need to be enabled via one -+of multiple dedicated requests before they are sent by the EC. -+ -+See :doc:`ssh` for a more technical protocol documentation and -+:doc:`internal` for an overview of the internal driver architecture. -diff --git a/Documentation/driver-api/surface_aggregator/ssh.rst b/Documentation/driver-api/surface_aggregator/ssh.rst -new file mode 100644 -index 000000000000..bf007d6c9873 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/ssh.rst -@@ -0,0 +1,344 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |u8| replace:: :c:type:`u8 ` -+.. |u16| replace:: :c:type:`u16 ` -+.. |TYPE| replace:: ``TYPE`` -+.. |LEN| replace:: ``LEN`` -+.. |SEQ| replace:: ``SEQ`` -+.. |SYN| replace:: ``SYN`` -+.. |NAK| replace:: ``NAK`` -+.. |ACK| replace:: ``ACK`` -+.. |DATA| replace:: ``DATA`` -+.. |DATA_SEQ| replace:: ``DATA_SEQ`` -+.. |DATA_NSQ| replace:: ``DATA_NSQ`` -+.. |TC| replace:: ``TC`` -+.. |TID| replace:: ``TID`` -+.. |IID| replace:: ``IID`` -+.. |RQID| replace:: ``RQID`` -+.. |CID| replace:: ``CID`` -+ -+=========================== -+Surface Serial Hub Protocol -+=========================== -+ -+The Surface Serial Hub (SSH) is the central communication interface for the -+embedded Surface Aggregator Module controller (SAM or EC), found on newer -+Surface generations. We will refer to this protocol and interface as -+SAM-over-SSH, as opposed to SAM-over-HID for the older generations. -+ -+On Surface devices with SAM-over-SSH, SAM is connected to the host via UART -+and defined in ACPI as device with ID ``MSHW0084``. On these devices, -+significant functionality is provided via SAM, including access to battery -+and power information and events, thermal read-outs and events, and many -+more. For Surface Laptops, keyboard input is handled via HID directed -+through SAM, on the Surface Laptop 3 and Surface Book 3 this also includes -+touchpad input. -+ -+Note that the standard disclaimer for this subsystem also applies to this -+document: All of this has been reverse-engineered and may thus be erroneous -+and/or incomplete. -+ -+All CRCs used in the following are two-byte ``crc_ccitt_false(0xffff, ...)``. -+All multi-byte values are little-endian, there is no implicit padding between -+values. -+ -+ -+SSH Packet Protocol: Definitions -+================================ -+ -+The fundamental communication unit of the SSH protocol is a frame -+(:c:type:`struct ssh_frame `). A frame consists of the following -+fields, packed together and in order: -+ -+.. flat-table:: SSH Frame -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - |TYPE| -+ - |u8| -+ - Type identifier of the frame. -+ -+ * - |LEN| -+ - |u16| -+ - Length of the payload associated with the frame. -+ -+ * - |SEQ| -+ - |u8| -+ - Sequence ID (see explanation below). -+ -+Each frame structure is followed by a CRC over this structure. The CRC over -+the frame structure (|TYPE|, |LEN|, and |SEQ| fields) is placed directly -+after the frame structure and before the payload. The payload is followed by -+its own CRC (over all payload bytes). If the payload is not present (i.e. -+the frame has ``LEN=0``), the CRC of the payload is still present and will -+evaluate to ``0xffff``. The |LEN| field does not include any of the CRCs, it -+equals the number of bytes inbetween the CRC of the frame and the CRC of the -+payload. -+ -+Additionally, the following fixed two-byte sequences are used: -+ -+.. flat-table:: SSH Byte Sequences -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Name -+ - Value -+ - Description -+ -+ * - |SYN| -+ - ``[0xAA, 0x55]`` -+ - Synchronization bytes. -+ -+A message consists of |SYN|, followed by the frame (|TYPE|, |LEN|, |SEQ| and -+CRC) and, if specified in the frame (i.e. ``LEN > 0``), payload bytes, -+followed finally, regardless if the payload is present, the payload CRC. The -+messages corresponding to an exchange are, in part, identified by having the -+same sequence ID (|SEQ|), stored inside the frame (more on this in the next -+section). The sequence ID is a wrapping counter. -+ -+A frame can have the following types -+(:c:type:`enum ssh_frame_type `): -+ -+.. flat-table:: SSH Frame Types -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Name -+ - Value -+ - Short Description -+ -+ * - |NAK| -+ - ``0x04`` -+ - Sent on error in previously received message. -+ -+ * - |ACK| -+ - ``0x40`` -+ - Sent to acknowledge receival of |DATA| frame. -+ -+ * - |DATA_SEQ| -+ - ``0x80`` -+ - Sent to transfer data. Sequenced. -+ -+ * - |DATA_NSQ| -+ - ``0x00`` -+ - Same as |DATA_SEQ|, but does not need to be ACKed. -+ -+Both |NAK|- and |ACK|-type frames are used to control flow of messages and -+thus do not carry a payload. |DATA_SEQ|- and |DATA_NSQ|-type frames on the -+other hand must carry a payload. The flow sequence and interaction of -+different frame types will be described in more depth in the next section. -+ -+ -+SSH Packet Protocol: Flow Sequence -+================================== -+ -+Each exchange begins with |SYN|, followed by a |DATA_SEQ|- or -+|DATA_NSQ|-type frame, followed by its CRC, payload, and payload CRC. In -+case of a |DATA_NSQ|-type frame, the exchange is then finished. In case of a -+|DATA_SEQ|-type frame, the receiving party has to acknowledge receival of -+the frame by responding with a message containing an |ACK|-type frame with -+the same sequence ID of the |DATA| frame. In other words, the sequence ID of -+the |ACK| frame specifies the |DATA| frame to be acknowledged. In case of an -+error, e.g. an invalid CRC, the receiving party responds with a message -+containing an |NAK|-type frame. As the sequence ID of the previous data -+frame, for which an error is indicated via the |NAK| frame, cannot be relied -+upon, the sequence ID of the |NAK| frame should not be used and is set to -+zero. After receival of an |NAK| frame, the sending party should re-send all -+outstanding (non-ACKed) messages. -+ -+Sequence IDs are not synchronized between the two parties, meaning that they -+are managed independently for each party. Identifying the messages -+corresponding to a single exchange thus relies on the sequence ID as well as -+the type of the message, and the context. Specifically, the sequence ID is -+used to associate an ``ACK`` with its ``DATA_SEQ``-type frame, but not -+``DATA_SEQ``- or ``DATA_NSQ``-type frames with other ``DATA``- type frames. -+ -+An example exchange might look like this: -+ -+:: -+ -+ tx: -- SYN FRAME(D) CRC(F) PAYLOAD CRC(P) ----------------------------- -+ rx: ------------------------------------- SYN FRAME(A) CRC(F) CRC(P) -- -+ -+where both frames have the same sequence ID (``SEQ``). Here, ``FRAME(D)`` -+indicates a |DATA_SEQ|-type frame, ``FRAME(A)`` an ``ACK``-type frame, -+``CRC(F)`` the CRC over the previous frame, ``CRC(P)`` the CRC over the -+previous payload. In case of an error, the exchange would look like this: -+ -+:: -+ -+ tx: -- SYN FRAME(D) CRC(F) PAYLOAD CRC(P) ----------------------------- -+ rx: ------------------------------------- SYN FRAME(N) CRC(F) CRC(P) -- -+ -+upon which the sender should re-send the message. ``FRAME(N)`` indicates an -+|NAK|-type frame. Note that the sequence ID of the |NAK|-type frame is fixed -+to zero. For |DATA_NSQ|-type frames, both exchanges are the same: -+ -+:: -+ -+ tx: -- SYN FRAME(DATA_NSQ) CRC(F) PAYLOAD CRC(P) ---------------------- -+ rx: ------------------------------------------------------------------- -+ -+Here, an error can be detected, but not corrected or indicated to the -+sending party. These exchanges are symmetric, i.e. switching ``rx`` and -+``tx`` results again in a valid exchange. Currently, no longer exchanges are -+known. -+ -+ -+Commands: Requests, Responses, and Events -+========================================= -+ -+Commands are sent as payload inside a data frame. Currently, this is the -+only known payload type of |DATA| frames, with a payload-type value of -+``0x80`` (:c:type:`SSH_PLD_TYPE_CMD `). -+ -+The command-type payload (:c:type:`struct ssh_command `) -+consists of an eight-byte command structure, followed by optional and -+variable length command data. The length of this optional data is derived -+from the frame payload length given in the corresponding frame, i.e. it is -+``frame.len - sizeof(struct ssh_command)``. The command struct contains the -+following fields, packed together and in order: -+ -+.. flat-table:: SSH Command -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - |TYPE| -+ - |u8| -+ - Type of the payload. For commands always ``0x80``. -+ -+ * - |TC| -+ - |u8| -+ - Target category. -+ -+ * - |TID| (out) -+ - |u8| -+ - Target ID for outgoing (host to EC) commands. -+ -+ * - |TID| (in) -+ - |u8| -+ - Target ID for incoming (EC to host) commands. -+ -+ * - |IID| -+ - |u8| -+ - Instance ID. -+ -+ * - |RQID| -+ - |u16| -+ - Request ID. -+ -+ * - |CID| -+ - |u8| -+ - Command ID. -+ -+The command struct and data, in general, does not contain any failure -+detection mechanism (e.g. CRCs), this is solely done on the frame level. -+ -+Command-type payloads are used by the host to send commands and requests to -+the EC as well as by the EC to send responses and events back to the host. -+We differentiate between requests (sent by the host), responses (sent by the -+EC in response to a request), and events (sent by the EC without a preceding -+request). -+ -+Commands and events are uniquely identified by their target category -+(``TC``) and command ID (``CID``). The target category specifies a general -+category for the command (e.g. system in general, vs. battery and AC, vs. -+temperature, and so on), while the command ID specifies the command inside -+that category. Only the combination of |TC| + |CID| is unique. Additionally, -+commands have an instance ID (``IID``), which is used to differentiate -+between different sub-devices. For example ``TC=3`` ``CID=1`` is a -+request to get the temperature on a thermal sensor, where |IID| specifies -+the respective sensor. If the instance ID is not used, it should be set to -+zero. If instance IDs are used, they, in general, start with a value of one, -+whereas zero may be used for instance independent queries, if applicable. A -+response to a request should have the same target category, command ID, and -+instance ID as the corresponding request. -+ -+Responses are matched to their corresponding request via the request ID -+(``RQID``) field. This is a 16 bit wrapping counter similar to the sequence -+ID on the frames. Note that the sequence ID of the frames for a -+request-response pair does not match. Only the request ID has to match. -+Frame-protocol wise these are two separate exchanges, and may even be -+separated, e.g. by an event being sent after the request but before the -+response. Not all commands produce a response, and this is not detectable by -+|TC| + |CID|. It is the responsibility of the issuing party to wait for a -+response (or signal this to the communication framework, as is done in -+SAN/ACPI via the ``SNC`` flag). -+ -+Events are identified by unique and reserved request IDs. These IDs should -+not be used by the host when sending a new request. They are used on the -+host to, first, detect events and, second, match them with a registered -+event handler. Request IDs for events are chosen by the host and directed to -+the EC when setting up and enabling an event source (via the -+enable-event-source request). The EC then uses the specified request ID for -+events sent from the respective source. Note that an event should still be -+identified by its target category, command ID, and, if applicable, instance -+ID, as a single event source can send multiple different event types. In -+general, however, a single target category should map to a single reserved -+event request ID. -+ -+Furthermore, requests, responses, and events have an associated target ID -+(``TID``). This target ID is split into output (host to EC) and input (EC to -+host) fields, with the respecting other field (e.g. output field on incoming -+messages) set to zero. Two ``TID`` values are known: Primary (``0x01``) and -+secondary (``0x02``). In general, the response to a request should have the -+same ``TID`` value, however, the field (output vs. input) should be used in -+accordance to the direction in which the response is sent (i.e. on the input -+field, as responses are generally sent from the EC to the host). -+ -+Note that, even though requests and events should be uniquely identifiable -+by target category and command ID alone, the EC may require specific -+target ID and instance ID values to accept a command. A command that is -+accepted for ``TID=1``, for example, may not be accepted for ``TID=2`` -+and vice versa. -+ -+ -+Limitations and Observations -+============================ -+ -+The protocol can, in theory, handle up to ``U8_MAX`` frames in parallel, -+with up to ``U16_MAX`` pending requests (neglecting request IDs reserved for -+events). In practice, however, this is more limited. From our testing -+(although via a python and thus a user-space program), it seems that the EC -+can handle up to four requests (mostly) reliably in parallel at a certain -+time. With five or more requests in parallel, consistent discarding of -+commands (ACKed frame but no command response) has been observed. For five -+simultaneous commands, this reproducibly resulted in one command being -+dropped and four commands being handled. -+ -+However, it has also been noted that, even with three requests in parallel, -+occasional frame drops happen. Apart from this, with a limit of three -+pending requests, no dropped commands (i.e. command being dropped but frame -+carrying command being ACKed) have been observed. In any case, frames (and -+possibly also commands) should be re-sent by the host if a certain timeout -+is exceeded. This is done by the EC for frames with a timeout of one second, -+up to two re-tries (i.e. three transmissions in total). The limit of -+re-tries also applies to received NAKs, and, in a worst case scenario, can -+lead to entire messages being dropped. -+ -+While this also seems to work fine for pending data frames as long as no -+transmission failures occur, implementation and handling of these seems to -+depend on the assumption that there is only one non-acknowledged data frame. -+In particular, the detection of repeated frames relies on the last sequence -+number. This means that, if a frame that has been successfully received by -+the EC is sent again, e.g. due to the host not receiving an |ACK|, the EC -+will only detect this if it has the sequence ID of the last frame received -+by the EC. As an example: Sending two frames with ``SEQ=0`` and ``SEQ=1`` -+followed by a repetition of ``SEQ=0`` will not detect the second ``SEQ=0`` -+frame as such, and thus execute the command in this frame each time it has -+been received, i.e. twice in this example. Sending ``SEQ=0``, ``SEQ=1`` and -+then repeating ``SEQ=1`` will detect the second ``SEQ=1`` as repetition of -+the first one and ignore it, thus executing the contained command only once. -+ -+In conclusion, this suggests a limit of at most one pending un-ACKed frame -+(per party, effectively leading to synchronous communication regarding -+frames) and at most three pending commands. The limit to synchronous frame -+transfers seems to be consistent with behavior observed on Windows. -diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig -index 61e1953ff921..3610c379b939 100644 ---- a/drivers/hid/Kconfig -+++ b/drivers/hid/Kconfig -@@ -1089,4 +1089,6 @@ source "drivers/hid/i2c-hid/Kconfig" - - source "drivers/hid/intel-ish-hid/Kconfig" - -+source "drivers/hid/surface-hid/Kconfig" -+ - endmenu -diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile -index bd7ac53b75c5..1f23e09049e1 100644 ---- a/drivers/hid/Makefile -+++ b/drivers/hid/Makefile -@@ -128,3 +128,5 @@ obj-$(CONFIG_USB_KBD) += usbhid/ - obj-$(CONFIG_I2C_HID) += i2c-hid/ - - obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ -+ -+obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ -diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig -new file mode 100644 -index 000000000000..7ce9b5d641eb ---- /dev/null -+++ b/drivers/hid/surface-hid/Kconfig -@@ -0,0 +1,42 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+menu "Surface System Aggregator Module HID support" -+ depends on SURFACE_AGGREGATOR -+ depends on INPUT -+ -+config SURFACE_HID -+ tristate "HID transport driver for Surface System Aggregator Module" -+ depends on SURFACE_AGGREGATOR_REGISTRY -+ select SURFACE_HID_CORE -+ help -+ Driver to support integrated HID devices on newer Microsoft Surface -+ models. -+ -+ This driver provides support for the HID transport protocol provided -+ by the Surface Aggregator Module (i.e. the embedded controller) on -+ 7th-generation Microsoft Surface devices, i.e. Surface Book 3 and -+ Surface Laptop 3. On those models, it is mainly used to connect the -+ integrated touchpad and keyboard. -+ -+ Say M or Y here, if you want support for integrated HID devices, i.e. -+ integrated touchpad and keyboard, on 7th generation Microsoft Surface -+ models. -+ -+config SURFACE_KBD -+ tristate "HID keyboard transport driver for Surface System Aggregator Module" -+ select SURFACE_HID_CORE -+ help -+ Driver to support HID keyboards on Surface Laptop 1 and 2 devices. -+ -+ This driver provides support for the HID transport protocol provided -+ by the Surface Aggregator Module (i.e. the embedded controller) on -+ Microsoft Surface Laptops 1 and 2. It is used to connect the -+ integrated keyboard on those devices. -+ -+ Say M or Y here, if you want support for the integrated keyboard on -+ Microsoft Surface Laptops 1 and 2. -+ -+endmenu -+ -+config SURFACE_HID_CORE -+ tristate -+ select HID -diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile -new file mode 100644 -index 000000000000..4ae11cf09b25 ---- /dev/null -+++ b/drivers/hid/surface-hid/Makefile -@@ -0,0 +1,7 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+# -+# Makefile - Surface System Aggregator Module (SSAM) HID transport driver. -+# -+obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o -+obj-$(CONFIG_SURFACE_HID) += surface_hid.o -+obj-$(CONFIG_SURFACE_KBD) += surface_kbd.o -diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c -new file mode 100644 -index 000000000000..82767dd3e088 ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_hid.c -@@ -0,0 +1,251 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) HID transport driver for the -+ * generic HID interface (HID/TC=0x15 subsystem). Provides support for -+ * integrated HID devices on Surface Laptop 3, Book 3, and later. -+ * -+ * Copyright (C) 2019-2021 Blaž Hrastnik , -+ * Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "surface_hid_core.h" -+ -+ -+/* -- SAM interface. -------------------------------------------------------- */ -+ -+struct surface_hid_buffer_slice { -+ __u8 entry; -+ __le32 offset; -+ __le32 length; -+ __u8 end; -+ __u8 data[]; -+} __packed; -+ -+enum surface_hid_cid { -+ SURFACE_HID_CID_OUTPUT_REPORT = 0x01, -+ SURFACE_HID_CID_GET_FEATURE_REPORT = 0x02, -+ SURFACE_HID_CID_SET_FEATURE_REPORT = 0x03, -+ SURFACE_HID_CID_GET_DESCRIPTOR = 0x04, -+}; -+ -+static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len) -+{ -+ u8 buffer[sizeof(struct surface_hid_buffer_slice) + 0x76]; -+ struct surface_hid_buffer_slice *slice; -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ u32 buffer_len, offset, length; -+ int status; -+ -+ /* -+ * Note: The 0x76 above has been chosen because that's what's used by -+ * the Windows driver. Together with the header, this leads to a 128 -+ * byte payload in total. -+ */ -+ -+ buffer_len = ARRAY_SIZE(buffer) - sizeof(struct surface_hid_buffer_slice); -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_HID_CID_GET_DESCRIPTOR; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(struct surface_hid_buffer_slice); -+ rqst.payload = buffer; -+ -+ rsp.capacity = ARRAY_SIZE(buffer); -+ rsp.pointer = buffer; -+ -+ slice = (struct surface_hid_buffer_slice *)buffer; -+ slice->entry = entry; -+ slice->end = 0; -+ -+ offset = 0; -+ length = buffer_len; -+ -+ while (!slice->end && offset < len) { -+ put_unaligned_le32(offset, &slice->offset); -+ put_unaligned_le32(length, &slice->length); -+ -+ rsp.length = 0; -+ -+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, -+ sizeof(*slice)); -+ if (status) -+ return status; -+ -+ offset = get_unaligned_le32(&slice->offset); -+ length = get_unaligned_le32(&slice->length); -+ -+ /* Don't mess stuff up in case we receive garbage. */ -+ if (length > buffer_len || offset > len) -+ return -EPROTO; -+ -+ if (offset + length > len) -+ length = len - offset; -+ -+ memcpy(buf + offset, &slice->data[0], length); -+ -+ offset += length; -+ length = buffer_len; -+ } -+ -+ if (offset != len) { -+ dev_err(shid->dev, "unexpected descriptor length: got %u, expected %zu\n", -+ offset, len); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id, bool feature, -+ u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ u8 cid; -+ -+ if (feature) -+ cid = SURFACE_HID_CID_SET_FEATURE_REPORT; -+ else -+ cid = SURFACE_HID_CID_OUTPUT_REPORT; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.instance_id = shid->uid.instance; -+ rqst.command_id = cid; -+ rqst.flags = 0; -+ rqst.length = len; -+ rqst.payload = buf; -+ -+ buf[0] = rprt_id; -+ -+ return ssam_retry(ssam_request_sync, shid->ctrl, &rqst, NULL); -+} -+ -+static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.instance_id = shid->uid.instance; -+ rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(rprt_id); -+ rqst.payload = &rprt_id; -+ -+ rsp.capacity = len; -+ rsp.length = 0; -+ rsp.pointer = buf; -+ -+ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id)); -+} -+ -+static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif); -+ -+ if (event->command_id != 0x00) -+ return 0; -+ -+ hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+ -+/* -- Transport driver. ----------------------------------------------------- */ -+ -+static int shid_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_set_raw_report(shid, rprt_id, false, buf, len); -+ return status >= 0 ? len : status; -+} -+ -+static int shid_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_get_raw_report(shid, rprt_id, buf, len); -+ return status >= 0 ? len : status; -+} -+ -+static int shid_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_set_raw_report(shid, rprt_id, true, buf, len); -+ return status >= 0 ? len : status; -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int surface_hid_probe(struct ssam_device *sdev) -+{ -+ struct surface_hid_device *shid; -+ -+ shid = devm_kzalloc(&sdev->dev, sizeof(*shid), GFP_KERNEL); -+ if (!shid) -+ return -ENOMEM; -+ -+ shid->dev = &sdev->dev; -+ shid->ctrl = sdev->ctrl; -+ shid->uid = sdev->uid; -+ -+ shid->notif.base.priority = 1; -+ shid->notif.base.fn = ssam_hid_event_fn; -+ shid->notif.event.reg = SSAM_EVENT_REGISTRY_REG; -+ shid->notif.event.id.target_category = sdev->uid.category; -+ shid->notif.event.id.instance = sdev->uid.instance; -+ shid->notif.event.mask = SSAM_EVENT_MASK_STRICT; -+ shid->notif.event.flags = 0; -+ -+ shid->ops.get_descriptor = ssam_hid_get_descriptor; -+ shid->ops.output_report = shid_output_report; -+ shid->ops.get_feature_report = shid_get_feature_report; -+ shid->ops.set_feature_report = shid_set_feature_report; -+ -+ ssam_device_set_drvdata(sdev, shid); -+ return surface_hid_device_add(shid); -+} -+ -+static void surface_hid_remove(struct ssam_device *sdev) -+{ -+ surface_hid_device_destroy(ssam_device_get_drvdata(sdev)); -+} -+ -+static const struct ssam_device_id surface_hid_match[] = { -+ { SSAM_SDEV(HID, 0x02, SSAM_ANY_IID, 0x00) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_hid_match); -+ -+static struct ssam_device_driver surface_hid_driver = { -+ .probe = surface_hid_probe, -+ .remove = surface_hid_remove, -+ .match_table = surface_hid_match, -+ .driver = { -+ .name = "surface_hid", -+ .pm = &surface_hid_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_hid_driver); -+ -+MODULE_AUTHOR("Blaž Hrastnik "); -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("HID transport driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c -new file mode 100644 -index 000000000000..5571e74abe91 ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_hid_core.c -@@ -0,0 +1,272 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Common/core components for the Surface System Aggregator Module (SSAM) HID -+ * transport driver. Provides support for integrated HID devices on Microsoft -+ * Surface models. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "surface_hid_core.h" -+ -+ -+/* -- Device descriptor access. --------------------------------------------- */ -+ -+static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) -+{ -+ int status; -+ -+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, -+ (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); -+ if (status) -+ return status; -+ -+ if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) { -+ dev_err(shid->dev, "unexpected HID descriptor length: got %u, expected %zu\n", -+ shid->hid_desc.desc_len, sizeof(shid->hid_desc)); -+ return -EPROTO; -+ } -+ -+ if (shid->hid_desc.desc_type != HID_DT_HID) { -+ dev_err(shid->dev, "unexpected HID descriptor type: got %#04x, expected %#04x\n", -+ shid->hid_desc.desc_type, HID_DT_HID); -+ return -EPROTO; -+ } -+ -+ if (shid->hid_desc.num_descriptors != 1) { -+ dev_err(shid->dev, "unexpected number of descriptors: got %u, expected 1\n", -+ shid->hid_desc.num_descriptors); -+ return -EPROTO; -+ } -+ -+ if (shid->hid_desc.report_desc_type != HID_DT_REPORT) { -+ dev_err(shid->dev, "unexpected report descriptor type: got %#04x, expected %#04x\n", -+ shid->hid_desc.report_desc_type, HID_DT_REPORT); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static int surface_hid_load_device_attributes(struct surface_hid_device *shid) -+{ -+ int status; -+ -+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, -+ (u8 *)&shid->attrs, sizeof(shid->attrs)); -+ if (status) -+ return status; -+ -+ if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) { -+ dev_err(shid->dev, "unexpected attribute length: got %u, expected %zu\n", -+ get_unaligned_le32(&shid->attrs.length), sizeof(shid->attrs)); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+ -+/* -- Transport driver (common). -------------------------------------------- */ -+ -+static int surface_hid_start(struct hid_device *hid) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ -+ return ssam_notifier_register(shid->ctrl, &shid->notif); -+} -+ -+static void surface_hid_stop(struct hid_device *hid) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ -+ /* Note: This call will log errors for us, so ignore them here. */ -+ ssam_notifier_unregister(shid->ctrl, &shid->notif); -+} -+ -+static int surface_hid_open(struct hid_device *hid) -+{ -+ return 0; -+} -+ -+static void surface_hid_close(struct hid_device *hid) -+{ -+} -+ -+static int surface_hid_parse(struct hid_device *hid) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len); -+ u8 *buf; -+ int status; -+ -+ buf = kzalloc(len, GFP_KERNEL); -+ if (!buf) -+ return -ENOMEM; -+ -+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len); -+ if (!status) -+ status = hid_parse_report(hid, buf, len); -+ -+ kfree(buf); -+ return status; -+} -+ -+static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportnum, u8 *buf, -+ size_t len, unsigned char rtype, int reqtype) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ -+ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) -+ return shid->ops.output_report(shid, reportnum, buf, len); -+ -+ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) -+ return shid->ops.get_feature_report(shid, reportnum, buf, len); -+ -+ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) -+ return shid->ops.set_feature_report(shid, reportnum, buf, len); -+ -+ return -EIO; -+} -+ -+static struct hid_ll_driver surface_hid_ll_driver = { -+ .start = surface_hid_start, -+ .stop = surface_hid_stop, -+ .open = surface_hid_open, -+ .close = surface_hid_close, -+ .parse = surface_hid_parse, -+ .raw_request = surface_hid_raw_request, -+}; -+ -+ -+/* -- Common device setup. -------------------------------------------------- */ -+ -+int surface_hid_device_add(struct surface_hid_device *shid) -+{ -+ int status; -+ -+ status = surface_hid_load_hid_descriptor(shid); -+ if (status) -+ return status; -+ -+ status = surface_hid_load_device_attributes(shid); -+ if (status) -+ return status; -+ -+ shid->hid = hid_allocate_device(); -+ if (IS_ERR(shid->hid)) -+ return PTR_ERR(shid->hid); -+ -+ shid->hid->dev.parent = shid->dev; -+ shid->hid->bus = BUS_HOST; -+ shid->hid->vendor = get_unaligned_le16(&shid->attrs.vendor); -+ shid->hid->product = get_unaligned_le16(&shid->attrs.product); -+ shid->hid->version = get_unaligned_le16(&shid->hid_desc.hid_version); -+ shid->hid->country = shid->hid_desc.country_code; -+ -+ snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X", -+ shid->hid->vendor, shid->hid->product); -+ -+ strscpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys)); -+ -+ shid->hid->driver_data = shid; -+ shid->hid->ll_driver = &surface_hid_ll_driver; -+ -+ status = hid_add_device(shid->hid); -+ if (status) -+ hid_destroy_device(shid->hid); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(surface_hid_device_add); -+ -+void surface_hid_device_destroy(struct surface_hid_device *shid) -+{ -+ hid_destroy_device(shid->hid); -+} -+EXPORT_SYMBOL_GPL(surface_hid_device_destroy); -+ -+ -+/* -- PM ops. --------------------------------------------------------------- */ -+ -+#ifdef CONFIG_PM_SLEEP -+ -+static int surface_hid_suspend(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_SUSPEND); -+ -+ return 0; -+} -+ -+static int surface_hid_resume(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->resume) -+ return d->hid->driver->resume(d->hid); -+ -+ return 0; -+} -+ -+static int surface_hid_freeze(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_FREEZE); -+ -+ return 0; -+} -+ -+static int surface_hid_poweroff(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE); -+ -+ return 0; -+} -+ -+static int surface_hid_restore(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->reset_resume) -+ return d->hid->driver->reset_resume(d->hid); -+ -+ return 0; -+} -+ -+const struct dev_pm_ops surface_hid_pm_ops = { -+ .freeze = surface_hid_freeze, -+ .thaw = surface_hid_resume, -+ .suspend = surface_hid_suspend, -+ .resume = surface_hid_resume, -+ .poweroff = surface_hid_poweroff, -+ .restore = surface_hid_restore, -+}; -+EXPORT_SYMBOL_GPL(surface_hid_pm_ops); -+ -+#else /* CONFIG_PM_SLEEP */ -+ -+const struct dev_pm_ops surface_hid_pm_ops = { }; -+EXPORT_SYMBOL_GPL(surface_hid_pm_ops); -+ -+#endif /* CONFIG_PM_SLEEP */ -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/hid/surface-hid/surface_hid_core.h b/drivers/hid/surface-hid/surface_hid_core.h -new file mode 100644 -index 000000000000..56fb9e8c5466 ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_hid_core.h -@@ -0,0 +1,73 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Common/core components for the Surface System Aggregator Module (SSAM) HID -+ * transport driver. Provides support for integrated HID devices on Microsoft -+ * Surface models. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef SURFACE_HID_CORE_H -+#define SURFACE_HID_CORE_H -+ -+#include -+#include -+#include -+ -+#include -+#include -+ -+enum surface_hid_descriptor_entry { -+ SURFACE_HID_DESC_HID = 0, -+ SURFACE_HID_DESC_REPORT = 1, -+ SURFACE_HID_DESC_ATTRS = 2, -+}; -+ -+struct surface_hid_descriptor { -+ __u8 desc_len; /* = 9 */ -+ __u8 desc_type; /* = HID_DT_HID */ -+ __le16 hid_version; -+ __u8 country_code; -+ __u8 num_descriptors; /* = 1 */ -+ -+ __u8 report_desc_type; /* = HID_DT_REPORT */ -+ __le16 report_desc_len; -+} __packed; -+ -+struct surface_hid_attributes { -+ __le32 length; -+ __le16 vendor; -+ __le16 product; -+ __le16 version; -+ __u8 _unknown[22]; -+} __packed; -+ -+struct surface_hid_device; -+ -+struct surface_hid_device_ops { -+ int (*get_descriptor)(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len); -+ int (*output_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); -+ int (*get_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); -+ int (*set_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); -+}; -+ -+struct surface_hid_device { -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ struct ssam_device_uid uid; -+ -+ struct surface_hid_descriptor hid_desc; -+ struct surface_hid_attributes attrs; -+ -+ struct ssam_event_notifier notif; -+ struct hid_device *hid; -+ -+ struct surface_hid_device_ops ops; -+}; -+ -+int surface_hid_device_add(struct surface_hid_device *shid); -+void surface_hid_device_destroy(struct surface_hid_device *shid); -+ -+extern const struct dev_pm_ops surface_hid_pm_ops; -+ -+#endif /* SURFACE_HID_CORE_H */ -diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c -new file mode 100644 -index 000000000000..0635341bc517 ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_kbd.c -@@ -0,0 +1,300 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) HID transport driver for the legacy -+ * keyboard interface (KBD/TC=0x08 subsystem). Provides support for the -+ * integrated HID keyboard on Surface Laptops 1 and 2. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "surface_hid_core.h" -+ -+ -+/* -- SAM interface (KBD). -------------------------------------------------- */ -+ -+#define KBD_FEATURE_REPORT_SIZE 7 /* 6 + report ID */ -+ -+enum surface_kbd_cid { -+ SURFACE_KBD_CID_GET_DESCRIPTOR = 0x00, -+ SURFACE_KBD_CID_SET_CAPSLOCK_LED = 0x01, -+ SURFACE_KBD_CID_EVT_INPUT_GENERIC = 0x03, -+ SURFACE_KBD_CID_EVT_INPUT_HOTKEYS = 0x04, -+ SURFACE_KBD_CID_GET_FEATURE_REPORT = 0x0b, -+}; -+ -+static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ int status; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(entry); -+ rqst.payload = &entry; -+ -+ rsp.capacity = len; -+ rsp.length = 0; -+ rsp.pointer = buf; -+ -+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry)); -+ if (status) -+ return status; -+ -+ if (rsp.length != len) { -+ dev_err(shid->dev, "invalid descriptor length: got %zu, expected, %zu\n", -+ rsp.length, len); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value) -+{ -+ struct ssam_request rqst; -+ u8 value_u8 = value; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = 0; -+ rqst.length = sizeof(value_u8); -+ rqst.payload = &value_u8; -+ -+ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8)); -+} -+ -+static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ u8 payload = 0; -+ int status; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(payload); -+ rqst.payload = &payload; -+ -+ rsp.capacity = len; -+ rsp.length = 0; -+ rsp.pointer = buf; -+ -+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload)); -+ if (status) -+ return status; -+ -+ if (rsp.length != len) { -+ dev_err(shid->dev, "invalid feature report length: got %zu, expected, %zu\n", -+ rsp.length, len); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static bool ssam_kbd_is_input_event(const struct ssam_event *event) -+{ -+ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC) -+ return true; -+ -+ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS) -+ return true; -+ -+ return false; -+} -+ -+static u32 ssam_kbd_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif); -+ -+ /* -+ * Check against device UID manually, as registry and device target -+ * category doesn't line up. -+ */ -+ -+ if (shid->uid.category != event->target_category) -+ return 0; -+ -+ if (shid->uid.target != event->target_id) -+ return 0; -+ -+ if (shid->uid.instance != event->instance_id) -+ return 0; -+ -+ if (!ssam_kbd_is_input_event(event)) -+ return 0; -+ -+ hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+ -+/* -- Transport driver (KBD). ----------------------------------------------- */ -+ -+static int skbd_get_caps_led_value(struct hid_device *hid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ struct hid_field *field; -+ unsigned int offset, size; -+ int i; -+ -+ /* Get LED field. */ -+ field = hidinput_get_led_field(hid); -+ if (!field) -+ return -ENOENT; -+ -+ /* Check if we got the correct report. */ -+ if (len != hid_report_len(field->report)) -+ return -ENOENT; -+ -+ if (rprt_id != field->report->id) -+ return -ENOENT; -+ -+ /* Get caps lock LED index. */ -+ for (i = 0; i < field->report_count; i++) -+ if ((field->usage[i].hid & 0xffff) == 0x02) -+ break; -+ -+ if (i == field->report_count) -+ return -ENOENT; -+ -+ /* Extract value. */ -+ size = field->report_size; -+ offset = field->report_offset + i * size; -+ return !!hid_field_extract(hid, buf + 1, size, offset); -+} -+ -+static int skbd_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int caps_led; -+ int status; -+ -+ caps_led = skbd_get_caps_led_value(shid->hid, rprt_id, buf, len); -+ if (caps_led < 0) -+ return -EIO; /* Only caps LED output reports are supported. */ -+ -+ status = ssam_kbd_set_caps_led(shid, caps_led); -+ if (status < 0) -+ return status; -+ -+ return len; -+} -+ -+static int skbd_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ u8 report[KBD_FEATURE_REPORT_SIZE]; -+ int status; -+ -+ /* -+ * The keyboard only has a single hard-coded read-only feature report -+ * of size KBD_FEATURE_REPORT_SIZE. Try to load it and compare its -+ * report ID against the requested one. -+ */ -+ -+ if (len < ARRAY_SIZE(report)) -+ return -ENOSPC; -+ -+ status = ssam_kbd_get_feature_report(shid, report, ARRAY_SIZE(report)); -+ if (status < 0) -+ return status; -+ -+ if (rprt_id != report[0]) -+ return -ENOENT; -+ -+ memcpy(buf, report, ARRAY_SIZE(report)); -+ return len; -+} -+ -+static int skbd_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ /* Not supported. See skbd_get_feature_report() for details. */ -+ return -EIO; -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int surface_kbd_probe(struct platform_device *pdev) -+{ -+ struct ssam_controller *ctrl; -+ struct surface_hid_device *shid; -+ -+ /* Add device link to EC. */ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL); -+ if (!shid) -+ return -ENOMEM; -+ -+ shid->dev = &pdev->dev; -+ shid->ctrl = ctrl; -+ -+ shid->uid.domain = SSAM_DOMAIN_SERIALHUB; -+ shid->uid.category = SSAM_SSH_TC_KBD; -+ shid->uid.target = 2; -+ shid->uid.instance = 0; -+ shid->uid.function = 0; -+ -+ shid->notif.base.priority = 1; -+ shid->notif.base.fn = ssam_kbd_event_fn; -+ shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ shid->notif.event.id.target_category = shid->uid.category; -+ shid->notif.event.id.instance = shid->uid.instance; -+ shid->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ shid->notif.event.flags = 0; -+ -+ shid->ops.get_descriptor = ssam_kbd_get_descriptor; -+ shid->ops.output_report = skbd_output_report; -+ shid->ops.get_feature_report = skbd_get_feature_report; -+ shid->ops.set_feature_report = skbd_set_feature_report; -+ -+ platform_set_drvdata(pdev, shid); -+ return surface_hid_device_add(shid); -+} -+ -+static int surface_kbd_remove(struct platform_device *pdev) -+{ -+ surface_hid_device_destroy(platform_get_drvdata(pdev)); -+ return 0; -+} -+ -+static const struct acpi_device_id surface_kbd_match[] = { -+ { "MSHW0096" }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_kbd_match); -+ -+static struct platform_driver surface_kbd_driver = { -+ .probe = surface_kbd_probe, -+ .remove = surface_kbd_remove, -+ .driver = { -+ .name = "surface_keyboard", -+ .acpi_match_table = surface_kbd_match, -+ .pm = &surface_hid_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_kbd_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index d1d9ebaecf1c..9aed8f6941cc 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -1185,6 +1185,106 @@ config SURFACE_BOOK1_DGPU_SWITCH - This driver provides a sysfs switch to set the power-state of the - discrete GPU found on the Microsoft Surface Book 1. - -+config SURFACE_ACPI_NOTIFY -+ tristate "Surface ACPI Notify Driver" -+ depends on SURFACE_AGGREGATOR -+ help -+ Surface ACPI Notify (SAN) driver for Microsoft Surface devices. -+ -+ This driver provides support for the ACPI interface (called SAN) of -+ the Surface System Aggregator Module (SSAM) EC. This interface is used -+ on 5th- and 6th-generation Microsoft Surface devices (including -+ Surface Pro 5 and 6, Surface Book 2, Surface Laptops 1 and 2, and in -+ reduced functionality on the Surface Laptop 3) to execute SSAM -+ requests directly from ACPI code, as well as receive SSAM events and -+ turn them into ACPI notifications. It essentially acts as a -+ translation layer between the SSAM controller and ACPI. -+ -+ Specifically, this driver may be needed for battery status reporting, -+ thermal sensor access, and real-time clock information, depending on -+ the Surface device in question. -+ -+config SURFACE_AGGREGATOR_CDEV -+ tristate "Surface System Aggregator Module User-Space Interface" -+ depends on SURFACE_AGGREGATOR -+ help -+ Provides a misc-device interface to the Surface System Aggregator -+ Module (SSAM) controller. -+ -+ This option provides a module (called surface_aggregator_cdev), that, -+ when loaded, will add a client device (and its respective driver) to -+ the SSAM controller. Said client device manages a misc-device -+ interface (/dev/surface/aggregator), which can be used by user-space -+ tools to directly communicate with the SSAM EC by sending requests and -+ receiving the corresponding responses. -+ -+ The provided interface is intended for debugging and development only, -+ and should not be used otherwise. -+ -+config SURFACE_AGGREGATOR_REGISTRY -+ tristate "Surface System Aggregator Module Device Registry" -+ depends on SURFACE_AGGREGATOR -+ depends on SURFACE_AGGREGATOR_BUS -+ help -+ Device-registry and device-hubs for Surface System Aggregator Module -+ (SSAM) devices. -+ -+ Provides a module and driver which act as a device-registry for SSAM -+ client devices that cannot be detected automatically, e.g. via ACPI. -+ Such devices are instead provided via this registry and attached via -+ device hubs, also provided in this module. -+ -+ Devices provided via this registry are: -+ - Platform profile (performance-/cooling-mode) device (5th- and later -+ generations). -+ - Battery/AC devices (7th-generation). -+ - HID input devices (7th-generation). -+ -+ Select M (recommended) or Y here if you want support for the above -+ mentioned devices on the corresponding Surface models. Without this -+ module, the respective devices will not be instantiated and thus any -+ functionality provided by them will be missing, even when drivers for -+ these devices are present. In other words, this module only provides -+ the respective client devices. Drivers for these devices still need to -+ be selected via the other options. -+ -+config SURFACE_DTX -+ tristate "Surface DTX (Detachment System) Driver" -+ depends on SURFACE_AGGREGATOR -+ depends on INPUT -+ help -+ Driver for the Surface Book clipboard detachment system (DTX). -+ -+ On the Surface Book series devices, the display part containing the -+ CPU (called the clipboard) can be detached from the base (containing a -+ battery, the keyboard, and, optionally, a discrete GPU) by (if -+ necessary) unlocking and opening the latch connecting both parts. -+ -+ This driver provides a user-space interface that can influence the -+ behavior of this process, which includes the option to abort it in -+ case the base is still in use or speed it up in case it is not. -+ -+ Note that this module can be built without support for the Surface -+ Aggregator Bus (i.e. CONFIG_SURFACE_AGGREGATOR_BUS=n). In that case, -+ some devices, specifically the Surface Book 3, will not be supported. -+ -+config SURFACE_PERFMODE -+ tristate "Surface Performance-Mode Driver" -+ depends on SURFACE_AGGREGATOR_BUS -+ depends on SYSFS -+ help -+ Driver for the performance-/cooling-mode interface of Microsoft -+ Surface devices. -+ -+ Microsoft Surface devices using the Surface System Aggregator Module -+ (SSAM) can be switched between different performance modes. This, -+ depending on the device, can influence their cooling behavior and may -+ influence power limits, allowing users to choose between performance -+ and higher power-draw, or lower power-draw and more silent operation. -+ -+ This driver provides a user-space interface (via sysfs) for -+ controlling said mode via the corresponding client device. -+ - config INTEL_PUNIT_IPC - tristate "Intel P-Unit IPC Driver" - ---help--- -@@ -1268,6 +1368,8 @@ config INTEL_ATOMISP2_PM - To compile this driver as a module, choose M here: the module - will be called intel_atomisp2_pm. - -+source "drivers/platform/x86/surface_aggregator/Kconfig" -+ - endif # X86_PLATFORM_DEVICES - - config PMC_ATOM -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 6b028d1ee802..2737a78616c8 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -79,6 +79,12 @@ obj-$(CONFIG_PVPANIC) += pvpanic.o - obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o - obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o - obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o -+obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator/ -+obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o -+obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o -+obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o -+obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o -+obj-$(CONFIG_SURFACE_PERFMODE) += surface_perfmode.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o - obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o - obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o -diff --git a/drivers/platform/x86/surface_acpi_notify.c b/drivers/platform/x86/surface_acpi_notify.c -new file mode 100644 -index 000000000000..ef9c1f8e8336 ---- /dev/null -+++ b/drivers/platform/x86/surface_acpi_notify.c -@@ -0,0 +1,886 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Driver for the Surface ACPI Notify (SAN) interface/shim. -+ * -+ * Translates communication from ACPI to Surface System Aggregator Module -+ * (SSAM/SAM) requests and back, specifically SAM-over-SSH. Translates SSAM -+ * events back to ACPI notifications. Allows handling of discrete GPU -+ * notifications sent from ACPI via the SAN interface by providing them to any -+ * registered external driver. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+struct san_data { -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ -+ struct acpi_connection_info info; -+ -+ struct ssam_event_notifier nf_bat; -+ struct ssam_event_notifier nf_tmp; -+}; -+ -+#define to_san_data(ptr, member) \ -+ container_of(ptr, struct san_data, member) -+ -+ -+/* -- dGPU notifier interface. ---------------------------------------------- */ -+ -+struct san_rqsg_if { -+ struct rw_semaphore lock; -+ struct device *dev; -+ struct blocking_notifier_head nh; -+}; -+ -+static struct san_rqsg_if san_rqsg_if = { -+ .lock = __RWSEM_INITIALIZER(san_rqsg_if.lock), -+ .dev = NULL, -+ .nh = BLOCKING_NOTIFIER_INIT(san_rqsg_if.nh), -+}; -+ -+static int san_set_rqsg_interface_device(struct device *dev) -+{ -+ int status = 0; -+ -+ down_write(&san_rqsg_if.lock); -+ if (!san_rqsg_if.dev && dev) -+ san_rqsg_if.dev = dev; -+ else -+ status = -EBUSY; -+ up_write(&san_rqsg_if.lock); -+ -+ return status; -+} -+ -+/** -+ * san_client_link() - Link client as consumer to SAN device. -+ * @client: The client to link. -+ * -+ * Sets up a device link between the provided client device as consumer and -+ * the SAN device as provider. This function can be used to ensure that the -+ * SAN interface has been set up and will be set up for as long as the driver -+ * of the client device is bound. This guarantees that, during that time, all -+ * dGPU events will be received by any registered notifier. -+ * -+ * The link will be automatically removed once the client device's driver is -+ * unbound. -+ * -+ * Return: Returns zero on success, %-ENXIO if the SAN interface has not been -+ * set up yet, and %-ENOMEM if device link creation failed. -+ */ -+int san_client_link(struct device *client) -+{ -+ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; -+ struct device_link *link; -+ -+ down_read(&san_rqsg_if.lock); -+ -+ if (!san_rqsg_if.dev) { -+ up_read(&san_rqsg_if.lock); -+ return -ENXIO; -+ } -+ -+ link = device_link_add(client, san_rqsg_if.dev, flags); -+ if (!link) { -+ up_read(&san_rqsg_if.lock); -+ return -ENOMEM; -+ } -+ -+ if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) { -+ up_read(&san_rqsg_if.lock); -+ return -ENXIO; -+ } -+ -+ up_read(&san_rqsg_if.lock); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(san_client_link); -+ -+/** -+ * san_dgpu_notifier_register() - Register a SAN dGPU notifier. -+ * @nb: The notifier-block to register. -+ * -+ * Registers a SAN dGPU notifier, receiving any new SAN dGPU events sent from -+ * ACPI. The registered notifier will be called with &struct san_dgpu_event -+ * as notifier data and the command ID of that event as notifier action. -+ */ -+int san_dgpu_notifier_register(struct notifier_block *nb) -+{ -+ return blocking_notifier_chain_register(&san_rqsg_if.nh, nb); -+} -+EXPORT_SYMBOL_GPL(san_dgpu_notifier_register); -+ -+/** -+ * san_dgpu_notifier_unregister() - Unregister a SAN dGPU notifier. -+ * @nb: The notifier-block to unregister. -+ */ -+int san_dgpu_notifier_unregister(struct notifier_block *nb) -+{ -+ return blocking_notifier_chain_unregister(&san_rqsg_if.nh, nb); -+} -+EXPORT_SYMBOL_GPL(san_dgpu_notifier_unregister); -+ -+static int san_dgpu_notifier_call(struct san_dgpu_event *evt) -+{ -+ int ret; -+ -+ ret = blocking_notifier_call_chain(&san_rqsg_if.nh, evt->command, evt); -+ return notifier_to_errno(ret); -+} -+ -+ -+/* -- ACPI _DSM event relay. ------------------------------------------------ */ -+ -+#define SAN_DSM_REVISION 0 -+ -+/* 93b666c5-70c6-469f-a215-3d487c91ab3c */ -+static const guid_t SAN_DSM_UUID = -+ GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d, -+ 0x48, 0x7c, 0x91, 0xab, 0x3c); -+ -+enum san_dsm_event_fn { -+ SAN_DSM_EVENT_FN_BAT1_STAT = 0x03, -+ SAN_DSM_EVENT_FN_BAT1_INFO = 0x04, -+ SAN_DSM_EVENT_FN_ADP1_STAT = 0x05, -+ SAN_DSM_EVENT_FN_ADP1_INFO = 0x06, -+ SAN_DSM_EVENT_FN_BAT2_STAT = 0x07, -+ SAN_DSM_EVENT_FN_BAT2_INFO = 0x08, -+ SAN_DSM_EVENT_FN_THERMAL = 0x09, -+ SAN_DSM_EVENT_FN_DPTF = 0x0a, -+}; -+ -+enum sam_event_cid_bat { -+ SAM_EVENT_CID_BAT_BIX = 0x15, -+ SAM_EVENT_CID_BAT_BST = 0x16, -+ SAM_EVENT_CID_BAT_ADP = 0x17, -+ SAM_EVENT_CID_BAT_PROT = 0x18, -+ SAM_EVENT_CID_BAT_DPTF = 0x4f, -+}; -+ -+enum sam_event_cid_tmp { -+ SAM_EVENT_CID_TMP_TRIP = 0x0b, -+}; -+ -+struct san_event_work { -+ struct delayed_work work; -+ struct device *dev; -+ struct ssam_event event; /* must be last */ -+}; -+ -+static int san_acpi_notify_event(struct device *dev, u64 func, -+ union acpi_object *param) -+{ -+ acpi_handle san = ACPI_HANDLE(dev); -+ union acpi_object *obj; -+ int status = 0; -+ -+ if (!acpi_check_dsm(san, &SAN_DSM_UUID, SAN_DSM_REVISION, BIT_ULL(func))) -+ return 0; -+ -+ dev_dbg(dev, "notify event %#04llx\n", func); -+ -+ obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION, -+ func, param, ACPI_TYPE_BUFFER); -+ if (!obj) -+ return -EFAULT; -+ -+ if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) { -+ dev_err(dev, "got unexpected result from _DSM\n"); -+ status = -EPROTO; -+ } -+ -+ ACPI_FREE(obj); -+ return status; -+} -+ -+static int san_evt_bat_adp(struct device *dev, const struct ssam_event *event) -+{ -+ int status; -+ -+ status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_ADP1_STAT, NULL); -+ if (status) -+ return status; -+ -+ /* -+ * Ensure that the battery states get updated correctly. When the -+ * battery is fully charged and an adapter is plugged in, it sometimes -+ * is not updated correctly, instead showing it as charging. -+ * Explicitly trigger battery updates to fix this. -+ */ -+ -+ status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT1_STAT, NULL); -+ if (status) -+ return status; -+ -+ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT2_STAT, NULL); -+} -+ -+static int san_evt_bat_bix(struct device *dev, const struct ssam_event *event) -+{ -+ enum san_dsm_event_fn fn; -+ -+ if (event->instance_id == 0x02) -+ fn = SAN_DSM_EVENT_FN_BAT2_INFO; -+ else -+ fn = SAN_DSM_EVENT_FN_BAT1_INFO; -+ -+ return san_acpi_notify_event(dev, fn, NULL); -+} -+ -+static int san_evt_bat_bst(struct device *dev, const struct ssam_event *event) -+{ -+ enum san_dsm_event_fn fn; -+ -+ if (event->instance_id == 0x02) -+ fn = SAN_DSM_EVENT_FN_BAT2_STAT; -+ else -+ fn = SAN_DSM_EVENT_FN_BAT1_STAT; -+ -+ return san_acpi_notify_event(dev, fn, NULL); -+} -+ -+static int san_evt_bat_dptf(struct device *dev, const struct ssam_event *event) -+{ -+ union acpi_object payload; -+ -+ /* -+ * The Surface ACPI expects a buffer and not a package. It specifically -+ * checks for ObjectType (Arg3) == 0x03. This will cause a warning in -+ * acpica/nsarguments.c, but that warning can be safely ignored. -+ */ -+ payload.type = ACPI_TYPE_BUFFER; -+ payload.buffer.length = event->length; -+ payload.buffer.pointer = (u8 *)&event->data[0]; -+ -+ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_DPTF, &payload); -+} -+ -+static unsigned long san_evt_bat_delay(u8 cid) -+{ -+ switch (cid) { -+ case SAM_EVENT_CID_BAT_ADP: -+ /* -+ * Wait for battery state to update before signaling adapter -+ * change. -+ */ -+ return msecs_to_jiffies(5000); -+ -+ case SAM_EVENT_CID_BAT_BST: -+ /* Ensure we do not miss anything important due to caching. */ -+ return msecs_to_jiffies(2000); -+ -+ default: -+ return 0; -+ } -+} -+ -+static bool san_evt_bat(const struct ssam_event *event, struct device *dev) -+{ -+ int status; -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_BAT_BIX: -+ status = san_evt_bat_bix(dev, event); -+ break; -+ -+ case SAM_EVENT_CID_BAT_BST: -+ status = san_evt_bat_bst(dev, event); -+ break; -+ -+ case SAM_EVENT_CID_BAT_ADP: -+ status = san_evt_bat_adp(dev, event); -+ break; -+ -+ case SAM_EVENT_CID_BAT_PROT: -+ /* -+ * TODO: Implement support for battery protection status change -+ * event. -+ */ -+ return true; -+ -+ case SAM_EVENT_CID_BAT_DPTF: -+ status = san_evt_bat_dptf(dev, event); -+ break; -+ -+ default: -+ return false; -+ } -+ -+ if (status) { -+ dev_err(dev, "error handling power event (cid = %#04x)\n", -+ event->command_id); -+ } -+ -+ return true; -+} -+ -+static void san_evt_bat_workfn(struct work_struct *work) -+{ -+ struct san_event_work *ev; -+ -+ ev = container_of(work, struct san_event_work, work.work); -+ san_evt_bat(&ev->event, ev->dev); -+ kfree(ev); -+} -+ -+static u32 san_evt_bat_nf(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+{ -+ struct san_data *d = to_san_data(nf, nf_bat); -+ struct san_event_work *work; -+ unsigned long delay = san_evt_bat_delay(event->command_id); -+ -+ if (delay == 0) -+ return san_evt_bat(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; -+ -+ work = kzalloc(sizeof(*work) + event->length, GFP_KERNEL); -+ if (!work) -+ return ssam_notifier_from_errno(-ENOMEM); -+ -+ INIT_DELAYED_WORK(&work->work, san_evt_bat_workfn); -+ work->dev = d->dev; -+ -+ memcpy(&work->event, event, sizeof(struct ssam_event) + event->length); -+ -+ schedule_delayed_work(&work->work, delay); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static int san_evt_tmp_trip(struct device *dev, const struct ssam_event *event) -+{ -+ union acpi_object param; -+ -+ /* -+ * The Surface ACPI expects an integer and not a package. This will -+ * cause a warning in acpica/nsarguments.c, but that warning can be -+ * safely ignored. -+ */ -+ param.type = ACPI_TYPE_INTEGER; -+ param.integer.value = event->instance_id; -+ -+ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_THERMAL, ¶m); -+} -+ -+static bool san_evt_tmp(const struct ssam_event *event, struct device *dev) -+{ -+ int status; -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_TMP_TRIP: -+ status = san_evt_tmp_trip(dev, event); -+ break; -+ -+ default: -+ return false; -+ } -+ -+ if (status) { -+ dev_err(dev, "error handling thermal event (cid = %#04x)\n", -+ event->command_id); -+ } -+ -+ return true; -+} -+ -+static u32 san_evt_tmp_nf(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+{ -+ struct san_data *d = to_san_data(nf, nf_tmp); -+ -+ return san_evt_tmp(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; -+} -+ -+ -+/* -- ACPI GSB OperationRegion handler -------------------------------------- */ -+ -+struct gsb_data_in { -+ u8 cv; -+} __packed; -+ -+struct gsb_data_rqsx { -+ u8 cv; /* Command value (san_gsb_request_cv). */ -+ u8 tc; /* Target category. */ -+ u8 tid; /* Target ID. */ -+ u8 iid; /* Instance ID. */ -+ u8 snc; /* Expect-response-flag. */ -+ u8 cid; /* Command ID. */ -+ u16 cdl; /* Payload length. */ -+ u8 pld[]; /* Payload. */ -+} __packed; -+ -+struct gsb_data_etwl { -+ u8 cv; /* Command value (should be 0x02). */ -+ u8 etw3; /* Unknown. */ -+ u8 etw4; /* Unknown. */ -+ u8 msg[]; /* Error message (ASCIIZ). */ -+} __packed; -+ -+struct gsb_data_out { -+ u8 status; /* _SSH communication status. */ -+ u8 len; /* _SSH payload length. */ -+ u8 pld[]; /* _SSH payload. */ -+} __packed; -+ -+union gsb_buffer_data { -+ struct gsb_data_in in; /* Common input. */ -+ struct gsb_data_rqsx rqsx; /* RQSX input. */ -+ struct gsb_data_etwl etwl; /* ETWL input. */ -+ struct gsb_data_out out; /* Output. */ -+}; -+ -+struct gsb_buffer { -+ u8 status; /* GSB AttribRawProcess status. */ -+ u8 len; /* GSB AttribRawProcess length. */ -+ union gsb_buffer_data data; -+} __packed; -+ -+#define SAN_GSB_MAX_RQSX_PAYLOAD (U8_MAX - 2 - sizeof(struct gsb_data_rqsx)) -+#define SAN_GSB_MAX_RESPONSE (U8_MAX - 2 - sizeof(struct gsb_data_out)) -+ -+#define SAN_GSB_COMMAND 0 -+ -+enum san_gsb_request_cv { -+ SAN_GSB_REQUEST_CV_RQST = 0x01, -+ SAN_GSB_REQUEST_CV_ETWL = 0x02, -+ SAN_GSB_REQUEST_CV_RQSG = 0x03, -+}; -+ -+#define SAN_REQUEST_NUM_TRIES 5 -+ -+static acpi_status san_etwl(struct san_data *d, struct gsb_buffer *b) -+{ -+ struct gsb_data_etwl *etwl = &b->data.etwl; -+ -+ if (b->len < sizeof(struct gsb_data_etwl)) { -+ dev_err(d->dev, "invalid ETWL package (len = %d)\n", b->len); -+ return AE_OK; -+ } -+ -+ dev_err(d->dev, "ETWL(%#04x, %#04x): %.*s\n", etwl->etw3, etwl->etw4, -+ (unsigned int)(b->len - sizeof(struct gsb_data_etwl)), -+ (char *)etwl->msg); -+ -+ /* Indicate success. */ -+ b->status = 0x00; -+ b->len = 0x00; -+ -+ return AE_OK; -+} -+ -+static -+struct gsb_data_rqsx *san_validate_rqsx(struct device *dev, const char *type, -+ struct gsb_buffer *b) -+{ -+ struct gsb_data_rqsx *rqsx = &b->data.rqsx; -+ -+ if (b->len < sizeof(struct gsb_data_rqsx)) { -+ dev_err(dev, "invalid %s package (len = %d)\n", type, b->len); -+ return NULL; -+ } -+ -+ if (get_unaligned(&rqsx->cdl) != b->len - sizeof(struct gsb_data_rqsx)) { -+ dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n", -+ type, b->len, get_unaligned(&rqsx->cdl)); -+ return NULL; -+ } -+ -+ if (get_unaligned(&rqsx->cdl) > SAN_GSB_MAX_RQSX_PAYLOAD) { -+ dev_err(dev, "payload for %s package too large (cdl = %d)\n", -+ type, get_unaligned(&rqsx->cdl)); -+ return NULL; -+ } -+ -+ return rqsx; -+} -+ -+static void gsb_rqsx_response_error(struct gsb_buffer *gsb, int status) -+{ -+ gsb->status = 0x00; -+ gsb->len = 0x02; -+ gsb->data.out.status = (u8)(-status); -+ gsb->data.out.len = 0x00; -+} -+ -+static void gsb_rqsx_response_success(struct gsb_buffer *gsb, u8 *ptr, size_t len) -+{ -+ gsb->status = 0x00; -+ gsb->len = len + 2; -+ gsb->data.out.status = 0x00; -+ gsb->data.out.len = len; -+ -+ if (len) -+ memcpy(&gsb->data.out.pld[0], ptr, len); -+} -+ -+static acpi_status san_rqst_fixup_suspended(struct san_data *d, -+ struct ssam_request *rqst, -+ struct gsb_buffer *gsb) -+{ -+ if (rqst->target_category == SSAM_SSH_TC_BAS && rqst->command_id == 0x0D) { -+ u8 base_state = 1; -+ -+ /* Base state quirk: -+ * The base state may be queried from ACPI when the EC is still -+ * suspended. In this case it will return '-EPERM'. This query -+ * will only be triggered from the ACPI lid GPE interrupt, thus -+ * we are either in laptop or studio mode (base status 0x01 or -+ * 0x02). Furthermore, we will only get here if the device (and -+ * EC) have been suspended. -+ * -+ * We now assume that the device is in laptop mode (0x01). This -+ * has the drawback that it will wake the device when unfolding -+ * it in studio mode, but it also allows us to avoid actively -+ * waiting for the EC to wake up, which may incur a notable -+ * delay. -+ */ -+ -+ dev_dbg(d->dev, "rqst: fixup: base-state quirk\n"); -+ -+ gsb_rqsx_response_success(gsb, &base_state, sizeof(base_state)); -+ return AE_OK; -+ } -+ -+ gsb_rqsx_response_error(gsb, -ENXIO); -+ return AE_OK; -+} -+ -+static acpi_status san_rqst(struct san_data *d, struct gsb_buffer *buffer) -+{ -+ u8 rspbuf[SAN_GSB_MAX_RESPONSE]; -+ struct gsb_data_rqsx *gsb_rqst; -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ int status = 0; -+ -+ gsb_rqst = san_validate_rqsx(d->dev, "RQST", buffer); -+ if (!gsb_rqst) -+ return AE_OK; -+ -+ rqst.target_category = gsb_rqst->tc; -+ rqst.target_id = gsb_rqst->tid; -+ rqst.command_id = gsb_rqst->cid; -+ rqst.instance_id = gsb_rqst->iid; -+ rqst.flags = gsb_rqst->snc ? SSAM_REQUEST_HAS_RESPONSE : 0; -+ rqst.length = get_unaligned(&gsb_rqst->cdl); -+ rqst.payload = &gsb_rqst->pld[0]; -+ -+ rsp.capacity = ARRAY_SIZE(rspbuf); -+ rsp.length = 0; -+ rsp.pointer = &rspbuf[0]; -+ -+ /* Handle suspended device. */ -+ if (d->dev->power.is_suspended) { -+ dev_warn(d->dev, "rqst: device is suspended, not executing\n"); -+ return san_rqst_fixup_suspended(d, &rqst, buffer); -+ } -+ -+ status = __ssam_retry(ssam_request_sync_onstack, SAN_REQUEST_NUM_TRIES, -+ d->ctrl, &rqst, &rsp, SAN_GSB_MAX_RQSX_PAYLOAD); -+ -+ if (!status) { -+ gsb_rqsx_response_success(buffer, rsp.pointer, rsp.length); -+ } else { -+ dev_err(d->dev, "rqst: failed with error %d\n", status); -+ gsb_rqsx_response_error(buffer, status); -+ } -+ -+ return AE_OK; -+} -+ -+static acpi_status san_rqsg(struct san_data *d, struct gsb_buffer *buffer) -+{ -+ struct gsb_data_rqsx *gsb_rqsg; -+ struct san_dgpu_event evt; -+ int status; -+ -+ gsb_rqsg = san_validate_rqsx(d->dev, "RQSG", buffer); -+ if (!gsb_rqsg) -+ return AE_OK; -+ -+ evt.category = gsb_rqsg->tc; -+ evt.target = gsb_rqsg->tid; -+ evt.command = gsb_rqsg->cid; -+ evt.instance = gsb_rqsg->iid; -+ evt.length = get_unaligned(&gsb_rqsg->cdl); -+ evt.payload = &gsb_rqsg->pld[0]; -+ -+ status = san_dgpu_notifier_call(&evt); -+ if (!status) { -+ gsb_rqsx_response_success(buffer, NULL, 0); -+ } else { -+ dev_err(d->dev, "rqsg: failed with error %d\n", status); -+ gsb_rqsx_response_error(buffer, status); -+ } -+ -+ return AE_OK; -+} -+ -+static acpi_status san_opreg_handler(u32 function, acpi_physical_address command, -+ u32 bits, u64 *value64, void *opreg_context, -+ void *region_context) -+{ -+ struct san_data *d = to_san_data(opreg_context, info); -+ struct gsb_buffer *buffer = (struct gsb_buffer *)value64; -+ int accessor_type = (function & 0xFFFF0000) >> 16; -+ -+ if (command != SAN_GSB_COMMAND) { -+ dev_warn(d->dev, "unsupported command: %#04llx\n", command); -+ return AE_OK; -+ } -+ -+ if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { -+ dev_err(d->dev, "invalid access type: %#04x\n", accessor_type); -+ return AE_OK; -+ } -+ -+ /* Buffer must have at least contain the command-value. */ -+ if (buffer->len == 0) { -+ dev_err(d->dev, "request-package too small\n"); -+ return AE_OK; -+ } -+ -+ switch (buffer->data.in.cv) { -+ case SAN_GSB_REQUEST_CV_RQST: -+ return san_rqst(d, buffer); -+ -+ case SAN_GSB_REQUEST_CV_ETWL: -+ return san_etwl(d, buffer); -+ -+ case SAN_GSB_REQUEST_CV_RQSG: -+ return san_rqsg(d, buffer); -+ -+ default: -+ dev_warn(d->dev, "unsupported SAN0 request (cv: %#04x)\n", -+ buffer->data.in.cv); -+ return AE_OK; -+ } -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int san_events_register(struct platform_device *pdev) -+{ -+ struct san_data *d = platform_get_drvdata(pdev); -+ int status; -+ -+ d->nf_bat.base.priority = 1; -+ d->nf_bat.base.fn = san_evt_bat_nf; -+ d->nf_bat.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ d->nf_bat.event.id.target_category = SSAM_SSH_TC_BAT; -+ d->nf_bat.event.id.instance = 0; -+ d->nf_bat.event.mask = SSAM_EVENT_MASK_TARGET; -+ d->nf_bat.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ d->nf_tmp.base.priority = 1; -+ d->nf_tmp.base.fn = san_evt_tmp_nf; -+ d->nf_tmp.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ d->nf_tmp.event.id.target_category = SSAM_SSH_TC_TMP; -+ d->nf_tmp.event.id.instance = 0; -+ d->nf_tmp.event.mask = SSAM_EVENT_MASK_TARGET; -+ d->nf_tmp.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ status = ssam_notifier_register(d->ctrl, &d->nf_bat); -+ if (status) -+ return status; -+ -+ status = ssam_notifier_register(d->ctrl, &d->nf_tmp); -+ if (status) -+ ssam_notifier_unregister(d->ctrl, &d->nf_bat); -+ -+ return status; -+} -+ -+static void san_events_unregister(struct platform_device *pdev) -+{ -+ struct san_data *d = platform_get_drvdata(pdev); -+ -+ ssam_notifier_unregister(d->ctrl, &d->nf_bat); -+ ssam_notifier_unregister(d->ctrl, &d->nf_tmp); -+} -+ -+#define san_consumer_printk(level, dev, handle, fmt, ...) \ -+do { \ -+ char *path = ""; \ -+ struct acpi_buffer buffer = { \ -+ .length = ACPI_ALLOCATE_BUFFER, \ -+ .pointer = NULL, \ -+ }; \ -+ \ -+ if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer))) \ -+ path = buffer.pointer; \ -+ \ -+ dev_##level(dev, "[%s]: " fmt, path, ##__VA_ARGS__); \ -+ kfree(buffer.pointer); \ -+} while (0) -+ -+#define san_consumer_dbg(dev, handle, fmt, ...) \ -+ san_consumer_printk(dbg, dev, handle, fmt, ##__VA_ARGS__) -+ -+#define san_consumer_warn(dev, handle, fmt, ...) \ -+ san_consumer_printk(warn, dev, handle, fmt, ##__VA_ARGS__) -+ -+static bool is_san_consumer(struct platform_device *pdev, acpi_handle handle) -+{ -+ struct acpi_handle_list dep_devices; -+ acpi_handle supplier = ACPI_HANDLE(&pdev->dev); -+ acpi_status status; -+ int i; -+ -+ if (!acpi_has_method(handle, "_DEP")) -+ return false; -+ -+ status = acpi_evaluate_reference(handle, "_DEP", NULL, &dep_devices); -+ if (ACPI_FAILURE(status)) { -+ san_consumer_dbg(&pdev->dev, handle, "failed to evaluate _DEP\n"); -+ return false; -+ } -+ -+ for (i = 0; i < dep_devices.count; i++) { -+ if (dep_devices.handles[i] == supplier) -+ return true; -+ } -+ -+ return false; -+} -+ -+static acpi_status san_consumer_setup(acpi_handle handle, u32 lvl, -+ void *context, void **rv) -+{ -+ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER; -+ struct platform_device *pdev = context; -+ struct acpi_device *adev; -+ struct device_link *link; -+ -+ if (!is_san_consumer(pdev, handle)) -+ return AE_OK; -+ -+ /* Ignore ACPI devices that are not present. */ -+ if (acpi_bus_get_device(handle, &adev) != 0) -+ return AE_OK; -+ -+ san_consumer_dbg(&pdev->dev, handle, "creating device link\n"); -+ -+ /* Try to set up device links, ignore but log errors. */ -+ link = device_link_add(&adev->dev, &pdev->dev, flags); -+ if (!link) { -+ san_consumer_warn(&pdev->dev, handle, "failed to create device link\n"); -+ return AE_OK; -+ } -+ -+ return AE_OK; -+} -+ -+static int san_consumer_links_setup(struct platform_device *pdev) -+{ -+ acpi_status status; -+ -+ status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, -+ ACPI_UINT32_MAX, san_consumer_setup, NULL, -+ pdev, NULL); -+ -+ return status ? -EFAULT : 0; -+} -+ -+static int san_probe(struct platform_device *pdev) -+{ -+ acpi_handle san = ACPI_HANDLE(&pdev->dev); -+ struct ssam_controller *ctrl; -+ struct san_data *data; -+ acpi_status astatus; -+ int status; -+ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ status = san_consumer_links_setup(pdev); -+ if (status) -+ return status; -+ -+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); -+ if (!data) -+ return -ENOMEM; -+ -+ data->dev = &pdev->dev; -+ data->ctrl = ctrl; -+ -+ platform_set_drvdata(pdev, data); -+ -+ astatus = acpi_install_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, -+ &san_opreg_handler, NULL, -+ &data->info); -+ if (ACPI_FAILURE(astatus)) -+ return -ENXIO; -+ -+ status = san_events_register(pdev); -+ if (status) -+ goto err_enable_events; -+ -+ status = san_set_rqsg_interface_device(&pdev->dev); -+ if (status) -+ goto err_install_dev; -+ -+ acpi_walk_dep_device_list(san); -+ return 0; -+ -+err_install_dev: -+ san_events_unregister(pdev); -+err_enable_events: -+ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, -+ &san_opreg_handler); -+ return status; -+} -+ -+static int san_remove(struct platform_device *pdev) -+{ -+ acpi_handle san = ACPI_HANDLE(&pdev->dev); -+ -+ san_set_rqsg_interface_device(NULL); -+ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, -+ &san_opreg_handler); -+ san_events_unregister(pdev); -+ -+ /* -+ * We have unregistered our event sources. Now we need to ensure that -+ * all delayed works they may have spawned are run to completion. -+ */ -+ flush_scheduled_work(); -+ -+ return 0; -+} -+ -+static const struct acpi_device_id san_match[] = { -+ { "MSHW0091" }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, san_match); -+ -+static struct platform_driver surface_acpi_notify = { -+ .probe = san_probe, -+ .remove = san_remove, -+ .driver = { -+ .name = "surface_acpi_notify", -+ .acpi_match_table = san_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_acpi_notify); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_aggregator/Kconfig b/drivers/platform/x86/surface_aggregator/Kconfig -new file mode 100644 -index 000000000000..cab020324256 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/Kconfig -@@ -0,0 +1,69 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+# Copyright (C) 2019-2021 Maximilian Luz -+ -+menuconfig SURFACE_AGGREGATOR -+ tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" -+ depends on SERIAL_DEV_BUS -+ depends on ACPI -+ select CRC_CCITT -+ help -+ The Surface System Aggregator Module (Surface SAM or SSAM) is an -+ embedded controller (EC) found on 5th- and later-generation Microsoft -+ Surface devices (i.e. Surface Pro 5, Surface Book 2, Surface Laptop, -+ and newer, with exception of Surface Go series devices). -+ -+ Depending on the device in question, this EC provides varying -+ functionality, including: -+ - EC access from ACPI via Surface ACPI Notify (5th- and 6th-generation) -+ - battery status information (all devices) -+ - thermal sensor access (all devices) -+ - performance mode / cooling mode control (all devices) -+ - clipboard detachment system control (Surface Book 2 and 3) -+ - HID / keyboard input (Surface Laptops, Surface Book 3) -+ -+ This option controls whether the Surface SAM subsystem core will be -+ built. This includes a driver for the Surface Serial Hub (SSH), which -+ is the device responsible for the communication with the EC, and a -+ basic kernel interface exposing the EC functionality to other client -+ drivers, i.e. allowing them to make requests to the EC and receive -+ events from it. Selecting this option alone will not provide any -+ client drivers and therefore no functionality beyond the in-kernel -+ interface. Said functionality is the responsibility of the respective -+ client drivers. -+ -+ Note: While 4th-generation Surface devices also make use of a SAM EC, -+ due to a difference in the communication interface of the controller, -+ only 5th and later generations are currently supported. Specifically, -+ devices using SAM-over-SSH are supported, whereas devices using -+ SAM-over-HID, which is used on the 4th generation, are currently not -+ supported. -+ -+ Choose m if you want to build the SAM subsystem core and SSH driver as -+ module, y if you want to build it into the kernel and n if you don't -+ want it at all. -+ -+config SURFACE_AGGREGATOR_BUS -+ bool "Surface System Aggregator Module Bus" -+ depends on SURFACE_AGGREGATOR -+ default y -+ help -+ Expands the Surface System Aggregator Module (SSAM) core driver by -+ providing a dedicated bus and client-device type. -+ -+ This bus and device type are intended to provide and simplify support -+ for non-platform and non-ACPI SSAM devices, i.e. SSAM devices that are -+ not auto-detectable via the conventional means (e.g. ACPI). -+ -+config SURFACE_AGGREGATOR_ERROR_INJECTION -+ bool "Surface System Aggregator Module Error Injection Capabilities" -+ depends on SURFACE_AGGREGATOR -+ depends on FUNCTION_ERROR_INJECTION -+ help -+ Provides error-injection capabilities for the Surface System -+ Aggregator Module subsystem and Surface Serial Hub driver. -+ -+ Specifically, exports error injection hooks to be used with the -+ kernel's function error injection capabilities to simulate underlying -+ transport and communication problems, such as invalid data sent to or -+ received from the EC, dropped data, and communication timeouts. -+ Intended for development and debugging. -diff --git a/drivers/platform/x86/surface_aggregator/Makefile b/drivers/platform/x86/surface_aggregator/Makefile -new file mode 100644 -index 000000000000..c8498c41e758 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/Makefile -@@ -0,0 +1,17 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+# Copyright (C) 2019-2021 Maximilian Luz -+ -+# For include/trace/define_trace.h to include trace.h -+CFLAGS_core.o = -I$(src) -+ -+obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator.o -+ -+surface_aggregator-objs := core.o -+surface_aggregator-objs += ssh_parser.o -+surface_aggregator-objs += ssh_packet_layer.o -+surface_aggregator-objs += ssh_request_layer.o -+surface_aggregator-objs += controller.o -+ -+ifeq ($(CONFIG_SURFACE_AGGREGATOR_BUS),y) -+surface_aggregator-objs += bus.o -+endif -diff --git a/drivers/platform/x86/surface_aggregator/bus.c b/drivers/platform/x86/surface_aggregator/bus.c -new file mode 100644 -index 000000000000..e525f34eb92c ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/bus.c -@@ -0,0 +1,415 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module bus and device integration. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+ -+#include -+#include -+ -+#include "bus.h" -+#include "controller.h" -+ -+static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, -+ char *buf) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ return scnprintf(buf, PAGE_SIZE, "ssam:d%02Xc%02Xt%02Xi%02Xf%02X\n", -+ sdev->uid.domain, sdev->uid.category, sdev->uid.target, -+ sdev->uid.instance, sdev->uid.function); -+} -+static DEVICE_ATTR_RO(modalias); -+ -+static struct attribute *ssam_device_attrs[] = { -+ &dev_attr_modalias.attr, -+ NULL, -+}; -+ATTRIBUTE_GROUPS(ssam_device); -+ -+static int ssam_device_uevent(struct device *dev, struct kobj_uevent_env *env) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ return add_uevent_var(env, "MODALIAS=ssam:d%02Xc%02Xt%02Xi%02Xf%02X", -+ sdev->uid.domain, sdev->uid.category, -+ sdev->uid.target, sdev->uid.instance, -+ sdev->uid.function); -+} -+ -+static void ssam_device_release(struct device *dev) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ ssam_controller_put(sdev->ctrl); -+ kfree(sdev); -+} -+ -+const struct device_type ssam_device_type = { -+ .name = "surface_aggregator_device", -+ .groups = ssam_device_groups, -+ .uevent = ssam_device_uevent, -+ .release = ssam_device_release, -+}; -+EXPORT_SYMBOL_GPL(ssam_device_type); -+ -+/** -+ * ssam_device_alloc() - Allocate and initialize a SSAM client device. -+ * @ctrl: The controller under which the device should be added. -+ * @uid: The UID of the device to be added. -+ * -+ * Allocates and initializes a new client device. The parent of the device -+ * will be set to the controller device and the name will be set based on the -+ * UID. Note that the device still has to be added via ssam_device_add(). -+ * Refer to that function for more details. -+ * -+ * Return: Returns the newly allocated and initialized SSAM client device, or -+ * %NULL if it could not be allocated. -+ */ -+struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, -+ struct ssam_device_uid uid) -+{ -+ struct ssam_device *sdev; -+ -+ sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); -+ if (!sdev) -+ return NULL; -+ -+ device_initialize(&sdev->dev); -+ sdev->dev.bus = &ssam_bus_type; -+ sdev->dev.type = &ssam_device_type; -+ sdev->dev.parent = ssam_controller_device(ctrl); -+ sdev->ctrl = ssam_controller_get(ctrl); -+ sdev->uid = uid; -+ -+ dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x:%02x", -+ sdev->uid.domain, sdev->uid.category, sdev->uid.target, -+ sdev->uid.instance, sdev->uid.function); -+ -+ return sdev; -+} -+EXPORT_SYMBOL_GPL(ssam_device_alloc); -+ -+/** -+ * ssam_device_add() - Add a SSAM client device. -+ * @sdev: The SSAM client device to be added. -+ * -+ * Added client devices must be guaranteed to always have a valid and active -+ * controller. Thus, this function will fail with %-ENODEV if the controller -+ * of the device has not been initialized yet, has been suspended, or has been -+ * shut down. -+ * -+ * The caller of this function should ensure that the corresponding call to -+ * ssam_device_remove() is issued before the controller is shut down. If the -+ * added device is a direct child of the controller device (default), it will -+ * be automatically removed when the controller is shut down. -+ * -+ * By default, the controller device will become the parent of the newly -+ * created client device. The parent may be changed before ssam_device_add is -+ * called, but care must be taken that a) the correct suspend/resume ordering -+ * is guaranteed and b) the client device does not outlive the controller, -+ * i.e. that the device is removed before the controller is being shut down. -+ * In case these guarantees have to be manually enforced, please refer to the -+ * ssam_client_link() and ssam_client_bind() functions, which are intended to -+ * set up device-links for this purpose. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssam_device_add(struct ssam_device *sdev) -+{ -+ int status; -+ -+ /* -+ * Ensure that we can only add new devices to a controller if it has -+ * been started and is not going away soon. This works in combination -+ * with ssam_controller_remove_clients to ensure driver presence for the -+ * controller device, i.e. it ensures that the controller (sdev->ctrl) -+ * is always valid and can be used for requests as long as the client -+ * device we add here is registered as child under it. This essentially -+ * guarantees that the client driver can always expect the preconditions -+ * for functions like ssam_request_sync (controller has to be started -+ * and is not suspended) to hold and thus does not have to check for -+ * them. -+ * -+ * Note that for this to work, the controller has to be a parent device. -+ * If it is not a direct parent, care has to be taken that the device is -+ * removed via ssam_device_remove(), as device_unregister does not -+ * remove child devices recursively. -+ */ -+ ssam_controller_statelock(sdev->ctrl); -+ -+ if (sdev->ctrl->state != SSAM_CONTROLLER_STARTED) { -+ ssam_controller_stateunlock(sdev->ctrl); -+ return -ENODEV; -+ } -+ -+ status = device_add(&sdev->dev); -+ -+ ssam_controller_stateunlock(sdev->ctrl); -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_device_add); -+ -+/** -+ * ssam_device_remove() - Remove a SSAM client device. -+ * @sdev: The device to remove. -+ * -+ * Removes and unregisters the provided SSAM client device. -+ */ -+void ssam_device_remove(struct ssam_device *sdev) -+{ -+ device_unregister(&sdev->dev); -+} -+EXPORT_SYMBOL_GPL(ssam_device_remove); -+ -+/** -+ * ssam_device_id_compatible() - Check if a device ID matches a UID. -+ * @id: The device ID as potential match. -+ * @uid: The device UID matching against. -+ * -+ * Check if the given ID is a match for the given UID, i.e. if a device with -+ * the provided UID is compatible to the given ID following the match rules -+ * described in its &ssam_device_id.match_flags member. -+ * -+ * Return: Returns %true if the given UID is compatible to the match rule -+ * described by the given ID, %false otherwise. -+ */ -+static bool ssam_device_id_compatible(const struct ssam_device_id *id, -+ struct ssam_device_uid uid) -+{ -+ if (id->domain != uid.domain || id->category != uid.category) -+ return false; -+ -+ if ((id->match_flags & SSAM_MATCH_TARGET) && id->target != uid.target) -+ return false; -+ -+ if ((id->match_flags & SSAM_MATCH_INSTANCE) && id->instance != uid.instance) -+ return false; -+ -+ if ((id->match_flags & SSAM_MATCH_FUNCTION) && id->function != uid.function) -+ return false; -+ -+ return true; -+} -+ -+/** -+ * ssam_device_id_is_null() - Check if a device ID is null. -+ * @id: The device ID to check. -+ * -+ * Check if a given device ID is null, i.e. all zeros. Used to check for the -+ * end of ``MODULE_DEVICE_TABLE(ssam, ...)`` or similar lists. -+ * -+ * Return: Returns %true if the given ID represents a null ID, %false -+ * otherwise. -+ */ -+static bool ssam_device_id_is_null(const struct ssam_device_id *id) -+{ -+ return id->match_flags == 0 && -+ id->domain == 0 && -+ id->category == 0 && -+ id->target == 0 && -+ id->instance == 0 && -+ id->function == 0 && -+ id->driver_data == 0; -+} -+ -+/** -+ * ssam_device_id_match() - Find the matching ID table entry for the given UID. -+ * @table: The table to search in. -+ * @uid: The UID to matched against the individual table entries. -+ * -+ * Find the first match for the provided device UID in the provided ID table -+ * and return it. Returns %NULL if no match could be found. -+ */ -+const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table, -+ const struct ssam_device_uid uid) -+{ -+ const struct ssam_device_id *id; -+ -+ for (id = table; !ssam_device_id_is_null(id); ++id) -+ if (ssam_device_id_compatible(id, uid)) -+ return id; -+ -+ return NULL; -+} -+EXPORT_SYMBOL_GPL(ssam_device_id_match); -+ -+/** -+ * ssam_device_get_match() - Find and return the ID matching the device in the -+ * ID table of the bound driver. -+ * @dev: The device for which to get the matching ID table entry. -+ * -+ * Find the fist match for the UID of the device in the ID table of the -+ * currently bound driver and return it. Returns %NULL if the device does not -+ * have a driver bound to it, the driver does not have match_table (i.e. it is -+ * %NULL), or there is no match in the driver's match_table. -+ * -+ * This function essentially calls ssam_device_id_match() with the ID table of -+ * the bound device driver and the UID of the device. -+ * -+ * Return: Returns the first match for the UID of the device in the device -+ * driver's match table, or %NULL if no such match could be found. -+ */ -+const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev) -+{ -+ const struct ssam_device_driver *sdrv; -+ -+ sdrv = to_ssam_device_driver(dev->dev.driver); -+ if (!sdrv) -+ return NULL; -+ -+ if (!sdrv->match_table) -+ return NULL; -+ -+ return ssam_device_id_match(sdrv->match_table, dev->uid); -+} -+EXPORT_SYMBOL_GPL(ssam_device_get_match); -+ -+/** -+ * ssam_device_get_match_data() - Find the ID matching the device in the -+ * ID table of the bound driver and return its ``driver_data`` member. -+ * @dev: The device for which to get the match data. -+ * -+ * Find the fist match for the UID of the device in the ID table of the -+ * corresponding driver and return its driver_data. Returns %NULL if the -+ * device does not have a driver bound to it, the driver does not have -+ * match_table (i.e. it is %NULL), there is no match in the driver's -+ * match_table, or the match does not have any driver_data. -+ * -+ * This function essentially calls ssam_device_get_match() and, if any match -+ * could be found, returns its ``struct ssam_device_id.driver_data`` member. -+ * -+ * Return: Returns the driver data associated with the first match for the UID -+ * of the device in the device driver's match table, or %NULL if no such match -+ * could be found. -+ */ -+const void *ssam_device_get_match_data(const struct ssam_device *dev) -+{ -+ const struct ssam_device_id *id; -+ -+ id = ssam_device_get_match(dev); -+ if (!id) -+ return NULL; -+ -+ return (const void *)id->driver_data; -+} -+EXPORT_SYMBOL_GPL(ssam_device_get_match_data); -+ -+static int ssam_bus_match(struct device *dev, struct device_driver *drv) -+{ -+ struct ssam_device_driver *sdrv = to_ssam_device_driver(drv); -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (!is_ssam_device(dev)) -+ return 0; -+ -+ return !!ssam_device_id_match(sdrv->match_table, sdev->uid); -+} -+ -+static int ssam_bus_probe(struct device *dev) -+{ -+ return to_ssam_device_driver(dev->driver) -+ ->probe(to_ssam_device(dev)); -+} -+ -+static int ssam_bus_remove(struct device *dev) -+{ -+ struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver); -+ -+ if (sdrv->remove) -+ sdrv->remove(to_ssam_device(dev)); -+ -+ return 0; -+} -+ -+struct bus_type ssam_bus_type = { -+ .name = "surface_aggregator", -+ .match = ssam_bus_match, -+ .probe = ssam_bus_probe, -+ .remove = ssam_bus_remove, -+}; -+EXPORT_SYMBOL_GPL(ssam_bus_type); -+ -+/** -+ * __ssam_device_driver_register() - Register a SSAM client device driver. -+ * @sdrv: The driver to register. -+ * @owner: The module owning the provided driver. -+ * -+ * Please refer to the ssam_device_driver_register() macro for the normal way -+ * to register a driver from inside its owning module. -+ */ -+int __ssam_device_driver_register(struct ssam_device_driver *sdrv, -+ struct module *owner) -+{ -+ sdrv->driver.owner = owner; -+ sdrv->driver.bus = &ssam_bus_type; -+ -+ /* force drivers to async probe so I/O is possible in probe */ -+ sdrv->driver.probe_type = PROBE_PREFER_ASYNCHRONOUS; -+ -+ return driver_register(&sdrv->driver); -+} -+EXPORT_SYMBOL_GPL(__ssam_device_driver_register); -+ -+/** -+ * ssam_device_driver_unregister - Unregister a SSAM device driver. -+ * @sdrv: The driver to unregister. -+ */ -+void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) -+{ -+ driver_unregister(&sdrv->driver); -+} -+EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); -+ -+static int ssam_remove_device(struct device *dev, void *_data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (is_ssam_device(dev)) -+ ssam_device_remove(sdev); -+ -+ return 0; -+} -+ -+/** -+ * ssam_controller_remove_clients() - Remove SSAM client devices registered as -+ * direct children under the given controller. -+ * @ctrl: The controller to remove all direct clients for. -+ * -+ * Remove all SSAM client devices registered as direct children under the -+ * given controller. Note that this only accounts for direct children of the -+ * controller device. This does not take care of any client devices where the -+ * parent device has been manually set before calling ssam_device_add. Refer -+ * to ssam_device_add()/ssam_device_remove() for more details on those cases. -+ * -+ * To avoid new devices being added in parallel to this call, the main -+ * controller lock (not statelock) must be held during this (and if -+ * necessary, any subsequent deinitialization) call. -+ */ -+void ssam_controller_remove_clients(struct ssam_controller *ctrl) -+{ -+ struct device *dev; -+ -+ dev = ssam_controller_device(ctrl); -+ device_for_each_child_reverse(dev, NULL, ssam_remove_device); -+} -+ -+/** -+ * ssam_bus_register() - Register and set-up the SSAM client device bus. -+ */ -+int ssam_bus_register(void) -+{ -+ return bus_register(&ssam_bus_type); -+} -+ -+/** -+ * ssam_bus_unregister() - Unregister the SSAM client device bus. -+ */ -+void ssam_bus_unregister(void) -+{ -+ return bus_unregister(&ssam_bus_type); -+} -diff --git a/drivers/platform/x86/surface_aggregator/bus.h b/drivers/platform/x86/surface_aggregator/bus.h -new file mode 100644 -index 000000000000..ed032c2cbdb2 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/bus.h -@@ -0,0 +1,27 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Surface System Aggregator Module bus and device integration. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_BUS_H -+#define _SURFACE_AGGREGATOR_BUS_H -+ -+#include -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+ -+void ssam_controller_remove_clients(struct ssam_controller *ctrl); -+ -+int ssam_bus_register(void); -+void ssam_bus_unregister(void); -+ -+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+static inline void ssam_controller_remove_clients(struct ssam_controller *ctrl) {} -+static inline int ssam_bus_register(void) { return 0; } -+static inline void ssam_bus_unregister(void) {} -+ -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+#endif /* _SURFACE_AGGREGATOR_BUS_H */ -diff --git a/drivers/platform/x86/surface_aggregator/controller.c b/drivers/platform/x86/surface_aggregator/controller.c -new file mode 100644 -index 000000000000..caf76333f8b3 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/controller.c -@@ -0,0 +1,2780 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Main SSAM/SSH controller structure and functionality. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "controller.h" -+#include "ssh_msgb.h" -+#include "ssh_request_layer.h" -+ -+#include "trace.h" -+ -+ -+/* -- Safe counters. -------------------------------------------------------- */ -+ -+/** -+ * ssh_seq_reset() - Reset/initialize sequence ID counter. -+ * @c: The counter to reset. -+ */ -+static void ssh_seq_reset(struct ssh_seq_counter *c) -+{ -+ WRITE_ONCE(c->value, 0); -+} -+ -+/** -+ * ssh_seq_next() - Get next sequence ID. -+ * @c: The counter providing the sequence IDs. -+ * -+ * Return: Returns the next sequence ID of the counter. -+ */ -+static u8 ssh_seq_next(struct ssh_seq_counter *c) -+{ -+ u8 old = READ_ONCE(c->value); -+ u8 new = old + 1; -+ u8 ret; -+ -+ while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { -+ old = ret; -+ new = old + 1; -+ } -+ -+ return old; -+} -+ -+/** -+ * ssh_rqid_reset() - Reset/initialize request ID counter. -+ * @c: The counter to reset. -+ */ -+static void ssh_rqid_reset(struct ssh_rqid_counter *c) -+{ -+ WRITE_ONCE(c->value, 0); -+} -+ -+/** -+ * ssh_rqid_next() - Get next request ID. -+ * @c: The counter providing the request IDs. -+ * -+ * Return: Returns the next request ID of the counter, skipping any reserved -+ * request IDs. -+ */ -+static u16 ssh_rqid_next(struct ssh_rqid_counter *c) -+{ -+ u16 old = READ_ONCE(c->value); -+ u16 new = ssh_rqid_next_valid(old); -+ u16 ret; -+ -+ while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { -+ old = ret; -+ new = ssh_rqid_next_valid(old); -+ } -+ -+ return old; -+} -+ -+ -+/* -- Event notifier/callbacks. --------------------------------------------- */ -+/* -+ * The notifier system is based on linux/notifier.h, specifically the SRCU -+ * implementation. The difference to that is, that some bits of the notifier -+ * call return value can be tracked across multiple calls. This is done so -+ * that handling of events can be tracked and a warning can be issued in case -+ * an event goes unhandled. The idea of that warning is that it should help -+ * discover and identify new/currently unimplemented features. -+ */ -+ -+/** -+ * ssam_event_matches_notifier() - Test if an event matches a notifier. -+ * @n: The event notifier to test against. -+ * @event: The event to test. -+ * -+ * Return: Returns %true if the given event matches the given notifier -+ * according to the rules set in the notifier's event mask, %false otherwise. -+ */ -+static bool ssam_event_matches_notifier(const struct ssam_event_notifier *n, -+ const struct ssam_event *event) -+{ -+ bool match = n->event.id.target_category == event->target_category; -+ -+ if (n->event.mask & SSAM_EVENT_MASK_TARGET) -+ match &= n->event.reg.target_id == event->target_id; -+ -+ if (n->event.mask & SSAM_EVENT_MASK_INSTANCE) -+ match &= n->event.id.instance == event->instance_id; -+ -+ return match; -+} -+ -+/** -+ * ssam_nfblk_call_chain() - Call event notifier callbacks of the given chain. -+ * @nh: The notifier head for which the notifier callbacks should be called. -+ * @event: The event data provided to the callbacks. -+ * -+ * Call all registered notifier callbacks in order of their priority until -+ * either no notifier is left or a notifier returns a value with the -+ * %SSAM_NOTIF_STOP bit set. Note that this bit is automatically set via -+ * ssam_notifier_from_errno() on any non-zero error value. -+ * -+ * Return: Returns the notifier status value, which contains the notifier -+ * status bits (%SSAM_NOTIF_HANDLED and %SSAM_NOTIF_STOP) as well as a -+ * potential error value returned from the last executed notifier callback. -+ * Use ssam_notifier_to_errno() to convert this value to the original error -+ * value. -+ */ -+static int ssam_nfblk_call_chain(struct ssam_nf_head *nh, struct ssam_event *event) -+{ -+ struct ssam_event_notifier *nf; -+ int ret = 0, idx; -+ -+ idx = srcu_read_lock(&nh->srcu); -+ -+ list_for_each_entry_rcu(nf, &nh->head, base.node) { -+ if (ssam_event_matches_notifier(nf, event)) { -+ ret = (ret & SSAM_NOTIF_STATE_MASK) | nf->base.fn(nf, event); -+ if (ret & SSAM_NOTIF_STOP) -+ break; -+ } -+ } -+ -+ srcu_read_unlock(&nh->srcu, idx); -+ return ret; -+} -+ -+/** -+ * ssam_nfblk_insert() - Insert a new notifier block into the given notifier -+ * list. -+ * @nh: The notifier head into which the block should be inserted. -+ * @nb: The notifier block to add. -+ * -+ * Note: This function must be synchronized by the caller with respect to other -+ * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. -+ * -+ * Return: Returns zero on success, %-EEXIST if the notifier block has already -+ * been registered. -+ */ -+static int ssam_nfblk_insert(struct ssam_nf_head *nh, struct ssam_notifier_block *nb) -+{ -+ struct ssam_notifier_block *p; -+ struct list_head *h; -+ -+ /* Runs under lock, no need for RCU variant. */ -+ list_for_each(h, &nh->head) { -+ p = list_entry(h, struct ssam_notifier_block, node); -+ -+ if (unlikely(p == nb)) { -+ WARN(1, "double register detected"); -+ return -EEXIST; -+ } -+ -+ if (nb->priority > p->priority) -+ break; -+ } -+ -+ list_add_tail_rcu(&nb->node, h); -+ return 0; -+} -+ -+/** -+ * ssam_nfblk_find() - Check if a notifier block is registered on the given -+ * notifier head. -+ * list. -+ * @nh: The notifier head on which to search. -+ * @nb: The notifier block to search for. -+ * -+ * Note: This function must be synchronized by the caller with respect to other -+ * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. -+ * -+ * Return: Returns true if the given notifier block is registered on the given -+ * notifier head, false otherwise. -+ */ -+static bool ssam_nfblk_find(struct ssam_nf_head *nh, struct ssam_notifier_block *nb) -+{ -+ struct ssam_notifier_block *p; -+ -+ /* Runs under lock, no need for RCU variant. */ -+ list_for_each_entry(p, &nh->head, node) { -+ if (p == nb) -+ return true; -+ } -+ -+ return false; -+} -+ -+/** -+ * ssam_nfblk_remove() - Remove a notifier block from its notifier list. -+ * @nb: The notifier block to be removed. -+ * -+ * Note: This function must be synchronized by the caller with respect to -+ * other insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. -+ * Furthermore, the caller _must_ ensure SRCU synchronization by calling -+ * synchronize_srcu() with ``nh->srcu`` after leaving the critical section, to -+ * ensure that the removed notifier block is not in use any more. -+ */ -+static void ssam_nfblk_remove(struct ssam_notifier_block *nb) -+{ -+ list_del_rcu(&nb->node); -+} -+ -+/** -+ * ssam_nf_head_init() - Initialize the given notifier head. -+ * @nh: The notifier head to initialize. -+ */ -+static int ssam_nf_head_init(struct ssam_nf_head *nh) -+{ -+ int status; -+ -+ status = init_srcu_struct(&nh->srcu); -+ if (status) -+ return status; -+ -+ INIT_LIST_HEAD(&nh->head); -+ return 0; -+} -+ -+/** -+ * ssam_nf_head_destroy() - Deinitialize the given notifier head. -+ * @nh: The notifier head to deinitialize. -+ */ -+static void ssam_nf_head_destroy(struct ssam_nf_head *nh) -+{ -+ cleanup_srcu_struct(&nh->srcu); -+} -+ -+ -+/* -- Event/notification registry. ------------------------------------------ */ -+ -+/** -+ * struct ssam_nf_refcount_key - Key used for event activation reference -+ * counting. -+ * @reg: The registry via which the event is enabled/disabled. -+ * @id: The ID uniquely describing the event. -+ */ -+struct ssam_nf_refcount_key { -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+}; -+ -+/** -+ * struct ssam_nf_refcount_entry - RB-tree entry for reference counting event -+ * activations. -+ * @node: The node of this entry in the rb-tree. -+ * @key: The key of the event. -+ * @refcount: The reference-count of the event. -+ * @flags: The flags used when enabling the event. -+ */ -+struct ssam_nf_refcount_entry { -+ struct rb_node node; -+ struct ssam_nf_refcount_key key; -+ int refcount; -+ u8 flags; -+}; -+ -+/** -+ * ssam_nf_refcount_inc() - Increment reference-/activation-count of the given -+ * event. -+ * @nf: The notifier system reference. -+ * @reg: The registry used to enable/disable the event. -+ * @id: The event ID. -+ * -+ * Increments the reference-/activation-count associated with the specified -+ * event type/ID, allocating a new entry for this event ID if necessary. A -+ * newly allocated entry will have a refcount of one. -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ * -+ * Return: Returns the refcount entry on success. Returns an error pointer -+ * with %-ENOSPC if there have already been %INT_MAX events of the specified -+ * ID and type registered, or %-ENOMEM if the entry could not be allocated. -+ */ -+static struct ssam_nf_refcount_entry * -+ssam_nf_refcount_inc(struct ssam_nf *nf, struct ssam_event_registry reg, -+ struct ssam_event_id id) -+{ -+ struct ssam_nf_refcount_entry *entry; -+ struct ssam_nf_refcount_key key; -+ struct rb_node **link = &nf->refcount.rb_node; -+ struct rb_node *parent = NULL; -+ int cmp; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ key.reg = reg; -+ key.id = id; -+ -+ while (*link) { -+ entry = rb_entry(*link, struct ssam_nf_refcount_entry, node); -+ parent = *link; -+ -+ cmp = memcmp(&key, &entry->key, sizeof(key)); -+ if (cmp < 0) { -+ link = &(*link)->rb_left; -+ } else if (cmp > 0) { -+ link = &(*link)->rb_right; -+ } else if (entry->refcount < INT_MAX) { -+ entry->refcount++; -+ return entry; -+ } else { -+ WARN_ON(1); -+ return ERR_PTR(-ENOSPC); -+ } -+ } -+ -+ entry = kzalloc(sizeof(*entry), GFP_KERNEL); -+ if (!entry) -+ return ERR_PTR(-ENOMEM); -+ -+ entry->key = key; -+ entry->refcount = 1; -+ -+ rb_link_node(&entry->node, parent, link); -+ rb_insert_color(&entry->node, &nf->refcount); -+ -+ return entry; -+} -+ -+/** -+ * ssam_nf_refcount_dec() - Decrement reference-/activation-count of the given -+ * event. -+ * @nf: The notifier system reference. -+ * @reg: The registry used to enable/disable the event. -+ * @id: The event ID. -+ * -+ * Decrements the reference-/activation-count of the specified event, -+ * returning its entry. If the returned entry has a refcount of zero, the -+ * caller is responsible for freeing it using kfree(). -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ * -+ * Return: Returns the refcount entry on success or %NULL if the entry has not -+ * been found. -+ */ -+static struct ssam_nf_refcount_entry * -+ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg, -+ struct ssam_event_id id) -+{ -+ struct ssam_nf_refcount_entry *entry; -+ struct ssam_nf_refcount_key key; -+ struct rb_node *node = nf->refcount.rb_node; -+ int cmp; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ key.reg = reg; -+ key.id = id; -+ -+ while (node) { -+ entry = rb_entry(node, struct ssam_nf_refcount_entry, node); -+ -+ cmp = memcmp(&key, &entry->key, sizeof(key)); -+ if (cmp < 0) { -+ node = node->rb_left; -+ } else if (cmp > 0) { -+ node = node->rb_right; -+ } else { -+ entry->refcount--; -+ if (entry->refcount == 0) -+ rb_erase(&entry->node, &nf->refcount); -+ -+ return entry; -+ } -+ } -+ -+ return NULL; -+} -+ -+/** -+ * ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the -+ * given event and free its entry if the reference count reaches zero. -+ * @nf: The notifier system reference. -+ * @reg: The registry used to enable/disable the event. -+ * @id: The event ID. -+ * -+ * Decrements the reference-/activation-count of the specified event, freeing -+ * its entry if it reaches zero. -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ */ -+static void ssam_nf_refcount_dec_free(struct ssam_nf *nf, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id) -+{ -+ struct ssam_nf_refcount_entry *entry; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ entry = ssam_nf_refcount_dec(nf, reg, id); -+ if (entry && entry->refcount == 0) -+ kfree(entry); -+} -+ -+/** -+ * ssam_nf_refcount_empty() - Test if the notification system has any -+ * enabled/active events. -+ * @nf: The notification system. -+ */ -+static bool ssam_nf_refcount_empty(struct ssam_nf *nf) -+{ -+ return RB_EMPTY_ROOT(&nf->refcount); -+} -+ -+/** -+ * ssam_nf_call() - Call notification callbacks for the provided event. -+ * @nf: The notifier system -+ * @dev: The associated device, only used for logging. -+ * @rqid: The request ID of the event. -+ * @event: The event provided to the callbacks. -+ * -+ * Execute registered callbacks in order of their priority until either no -+ * callback is left or a callback returns a value with the %SSAM_NOTIF_STOP -+ * bit set. Note that this bit is set automatically when converting non-zero -+ * error values via ssam_notifier_from_errno() to notifier values. -+ * -+ * Also note that any callback that could handle an event should return a value -+ * with bit %SSAM_NOTIF_HANDLED set, indicating that the event does not go -+ * unhandled/ignored. In case no registered callback could handle an event, -+ * this function will emit a warning. -+ * -+ * In case a callback failed, this function will emit an error message. -+ */ -+static void ssam_nf_call(struct ssam_nf *nf, struct device *dev, u16 rqid, -+ struct ssam_event *event) -+{ -+ struct ssam_nf_head *nf_head; -+ int status, nf_ret; -+ -+ if (!ssh_rqid_is_event(rqid)) { -+ dev_warn(dev, "event: unsupported rqid: %#06x\n", rqid); -+ return; -+ } -+ -+ nf_head = &nf->head[ssh_rqid_to_event(rqid)]; -+ nf_ret = ssam_nfblk_call_chain(nf_head, event); -+ status = ssam_notifier_to_errno(nf_ret); -+ -+ if (status < 0) { -+ dev_err(dev, -+ "event: error handling event: %d (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", -+ status, event->target_category, event->target_id, -+ event->command_id, event->instance_id); -+ } else if (!(nf_ret & SSAM_NOTIF_HANDLED)) { -+ dev_warn(dev, -+ "event: unhandled event (rqid: %#04x, tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", -+ rqid, event->target_category, event->target_id, -+ event->command_id, event->instance_id); -+ } -+} -+ -+/** -+ * ssam_nf_init() - Initialize the notifier system. -+ * @nf: The notifier system to initialize. -+ */ -+static int ssam_nf_init(struct ssam_nf *nf) -+{ -+ int i, status; -+ -+ for (i = 0; i < SSH_NUM_EVENTS; i++) { -+ status = ssam_nf_head_init(&nf->head[i]); -+ if (status) -+ break; -+ } -+ -+ if (status) { -+ while (i--) -+ ssam_nf_head_destroy(&nf->head[i]); -+ -+ return status; -+ } -+ -+ mutex_init(&nf->lock); -+ return 0; -+} -+ -+/** -+ * ssam_nf_destroy() - Deinitialize the notifier system. -+ * @nf: The notifier system to deinitialize. -+ */ -+static void ssam_nf_destroy(struct ssam_nf *nf) -+{ -+ int i; -+ -+ for (i = 0; i < SSH_NUM_EVENTS; i++) -+ ssam_nf_head_destroy(&nf->head[i]); -+ -+ mutex_destroy(&nf->lock); -+} -+ -+ -+/* -- Event/async request completion system. -------------------------------- */ -+ -+#define SSAM_CPLT_WQ_NAME "ssam_cpltq" -+ -+/* -+ * SSAM_CPLT_WQ_BATCH - Maximum number of event item completions executed per -+ * work execution. Used to prevent livelocking of the workqueue. Value chosen -+ * via educated guess, may be adjusted. -+ */ -+#define SSAM_CPLT_WQ_BATCH 10 -+ -+/* -+ * SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN - Maximum payload length for a cached -+ * &struct ssam_event_item. -+ * -+ * This length has been chosen to be accommodate standard touchpad and -+ * keyboard input events. Events with larger payloads will be allocated -+ * separately. -+ */ -+#define SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN 32 -+ -+static struct kmem_cache *ssam_event_item_cache; -+ -+/** -+ * ssam_event_item_cache_init() - Initialize the event item cache. -+ */ -+int ssam_event_item_cache_init(void) -+{ -+ const unsigned int size = sizeof(struct ssam_event_item) -+ + SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN; -+ const unsigned int align = __alignof__(struct ssam_event_item); -+ struct kmem_cache *cache; -+ -+ cache = kmem_cache_create("ssam_event_item", size, align, 0, NULL); -+ if (!cache) -+ return -ENOMEM; -+ -+ ssam_event_item_cache = cache; -+ return 0; -+} -+ -+/** -+ * ssam_event_item_cache_destroy() - Deinitialize the event item cache. -+ */ -+void ssam_event_item_cache_destroy(void) -+{ -+ kmem_cache_destroy(ssam_event_item_cache); -+ ssam_event_item_cache = NULL; -+} -+ -+static void __ssam_event_item_free_cached(struct ssam_event_item *item) -+{ -+ kmem_cache_free(ssam_event_item_cache, item); -+} -+ -+static void __ssam_event_item_free_generic(struct ssam_event_item *item) -+{ -+ kfree(item); -+} -+ -+/** -+ * ssam_event_item_free() - Free the provided event item. -+ * @item: The event item to free. -+ */ -+static void ssam_event_item_free(struct ssam_event_item *item) -+{ -+ trace_ssam_event_item_free(item); -+ item->ops.free(item); -+} -+ -+/** -+ * ssam_event_item_alloc() - Allocate an event item with the given payload size. -+ * @len: The event payload length. -+ * @flags: The flags used for allocation. -+ * -+ * Allocate an event item with the given payload size, preferring allocation -+ * from the event item cache if the payload is small enough (i.e. smaller than -+ * %SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN). Sets the item operations and payload -+ * length values. The item free callback (``ops.free``) should not be -+ * overwritten after this call. -+ * -+ * Return: Returns the newly allocated event item. -+ */ -+static struct ssam_event_item *ssam_event_item_alloc(size_t len, gfp_t flags) -+{ -+ struct ssam_event_item *item; -+ -+ if (len <= SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN) { -+ item = kmem_cache_alloc(ssam_event_item_cache, flags); -+ if (!item) -+ return NULL; -+ -+ item->ops.free = __ssam_event_item_free_cached; -+ } else { -+ item = kzalloc(struct_size(item, event.data, len), flags); -+ if (!item) -+ return NULL; -+ -+ item->ops.free = __ssam_event_item_free_generic; -+ } -+ -+ item->event.length = len; -+ -+ trace_ssam_event_item_alloc(item, len); -+ return item; -+} -+ -+/** -+ * ssam_event_queue_push() - Push an event item to the event queue. -+ * @q: The event queue. -+ * @item: The item to add. -+ */ -+static void ssam_event_queue_push(struct ssam_event_queue *q, -+ struct ssam_event_item *item) -+{ -+ spin_lock(&q->lock); -+ list_add_tail(&item->node, &q->head); -+ spin_unlock(&q->lock); -+} -+ -+/** -+ * ssam_event_queue_pop() - Pop the next event item from the event queue. -+ * @q: The event queue. -+ * -+ * Returns and removes the next event item from the queue. Returns %NULL If -+ * there is no event item left. -+ */ -+static struct ssam_event_item *ssam_event_queue_pop(struct ssam_event_queue *q) -+{ -+ struct ssam_event_item *item; -+ -+ spin_lock(&q->lock); -+ item = list_first_entry_or_null(&q->head, struct ssam_event_item, node); -+ if (item) -+ list_del(&item->node); -+ spin_unlock(&q->lock); -+ -+ return item; -+} -+ -+/** -+ * ssam_event_queue_is_empty() - Check if the event queue is empty. -+ * @q: The event queue. -+ */ -+static bool ssam_event_queue_is_empty(struct ssam_event_queue *q) -+{ -+ bool empty; -+ -+ spin_lock(&q->lock); -+ empty = list_empty(&q->head); -+ spin_unlock(&q->lock); -+ -+ return empty; -+} -+ -+/** -+ * ssam_cplt_get_event_queue() - Get the event queue for the given parameters. -+ * @cplt: The completion system on which to look for the queue. -+ * @tid: The target ID of the queue. -+ * @rqid: The request ID representing the event ID for which to get the queue. -+ * -+ * Return: Returns the event queue corresponding to the event type described -+ * by the given parameters. If the request ID does not represent an event, -+ * this function returns %NULL. If the target ID is not supported, this -+ * function will fall back to the default target ID (``tid = 1``). -+ */ -+static -+struct ssam_event_queue *ssam_cplt_get_event_queue(struct ssam_cplt *cplt, -+ u8 tid, u16 rqid) -+{ -+ u16 event = ssh_rqid_to_event(rqid); -+ u16 tidx = ssh_tid_to_index(tid); -+ -+ if (!ssh_rqid_is_event(rqid)) { -+ dev_err(cplt->dev, "event: unsupported request ID: %#06x\n", rqid); -+ return NULL; -+ } -+ -+ if (!ssh_tid_is_valid(tid)) { -+ dev_warn(cplt->dev, "event: unsupported target ID: %u\n", tid); -+ tidx = 0; -+ } -+ -+ return &cplt->event.target[tidx].queue[event]; -+} -+ -+/** -+ * ssam_cplt_submit() - Submit a work item to the completion system workqueue. -+ * @cplt: The completion system. -+ * @work: The work item to submit. -+ */ -+static bool ssam_cplt_submit(struct ssam_cplt *cplt, struct work_struct *work) -+{ -+ return queue_work(cplt->wq, work); -+} -+ -+/** -+ * ssam_cplt_submit_event() - Submit an event to the completion system. -+ * @cplt: The completion system. -+ * @item: The event item to submit. -+ * -+ * Submits the event to the completion system by queuing it on the event item -+ * queue and queuing the respective event queue work item on the completion -+ * workqueue, which will eventually complete the event. -+ * -+ * Return: Returns zero on success, %-EINVAL if there is no event queue that -+ * can handle the given event item. -+ */ -+static int ssam_cplt_submit_event(struct ssam_cplt *cplt, -+ struct ssam_event_item *item) -+{ -+ struct ssam_event_queue *evq; -+ -+ evq = ssam_cplt_get_event_queue(cplt, item->event.target_id, item->rqid); -+ if (!evq) -+ return -EINVAL; -+ -+ ssam_event_queue_push(evq, item); -+ ssam_cplt_submit(cplt, &evq->work); -+ return 0; -+} -+ -+/** -+ * ssam_cplt_flush() - Flush the completion system. -+ * @cplt: The completion system. -+ * -+ * Flush the completion system by waiting until all currently submitted work -+ * items have been completed. -+ * -+ * Note: This function does not guarantee that all events will have been -+ * handled once this call terminates. In case of a larger number of -+ * to-be-completed events, the event queue work function may re-schedule its -+ * work item, which this flush operation will ignore. -+ * -+ * This operation is only intended to, during normal operation prior to -+ * shutdown, try to complete most events and requests to get them out of the -+ * system while the system is still fully operational. It does not aim to -+ * provide any guarantee that all of them have been handled. -+ */ -+static void ssam_cplt_flush(struct ssam_cplt *cplt) -+{ -+ flush_workqueue(cplt->wq); -+} -+ -+static void ssam_event_queue_work_fn(struct work_struct *work) -+{ -+ struct ssam_event_queue *queue; -+ struct ssam_event_item *item; -+ struct ssam_nf *nf; -+ struct device *dev; -+ unsigned int iterations = SSAM_CPLT_WQ_BATCH; -+ -+ queue = container_of(work, struct ssam_event_queue, work); -+ nf = &queue->cplt->event.notif; -+ dev = queue->cplt->dev; -+ -+ /* Limit number of processed events to avoid livelocking. */ -+ do { -+ item = ssam_event_queue_pop(queue); -+ if (!item) -+ return; -+ -+ ssam_nf_call(nf, dev, item->rqid, &item->event); -+ ssam_event_item_free(item); -+ } while (--iterations); -+ -+ if (!ssam_event_queue_is_empty(queue)) -+ ssam_cplt_submit(queue->cplt, &queue->work); -+} -+ -+/** -+ * ssam_event_queue_init() - Initialize an event queue. -+ * @cplt: The completion system on which the queue resides. -+ * @evq: The event queue to initialize. -+ */ -+static void ssam_event_queue_init(struct ssam_cplt *cplt, -+ struct ssam_event_queue *evq) -+{ -+ evq->cplt = cplt; -+ spin_lock_init(&evq->lock); -+ INIT_LIST_HEAD(&evq->head); -+ INIT_WORK(&evq->work, ssam_event_queue_work_fn); -+} -+ -+/** -+ * ssam_cplt_init() - Initialize completion system. -+ * @cplt: The completion system to initialize. -+ * @dev: The device used for logging. -+ */ -+static int ssam_cplt_init(struct ssam_cplt *cplt, struct device *dev) -+{ -+ struct ssam_event_target *target; -+ int status, c, i; -+ -+ cplt->dev = dev; -+ -+ cplt->wq = create_workqueue(SSAM_CPLT_WQ_NAME); -+ if (!cplt->wq) -+ return -ENOMEM; -+ -+ for (c = 0; c < ARRAY_SIZE(cplt->event.target); c++) { -+ target = &cplt->event.target[c]; -+ -+ for (i = 0; i < ARRAY_SIZE(target->queue); i++) -+ ssam_event_queue_init(cplt, &target->queue[i]); -+ } -+ -+ status = ssam_nf_init(&cplt->event.notif); -+ if (status) -+ destroy_workqueue(cplt->wq); -+ -+ return status; -+} -+ -+/** -+ * ssam_cplt_destroy() - Deinitialize the completion system. -+ * @cplt: The completion system to deinitialize. -+ * -+ * Deinitialize the given completion system and ensure that all pending, i.e. -+ * yet-to-be-completed, event items and requests have been handled. -+ */ -+static void ssam_cplt_destroy(struct ssam_cplt *cplt) -+{ -+ /* -+ * Note: destroy_workqueue ensures that all currently queued work will -+ * be fully completed and the workqueue drained. This means that this -+ * call will inherently also free any queued ssam_event_items, thus we -+ * don't have to take care of that here explicitly. -+ */ -+ destroy_workqueue(cplt->wq); -+ ssam_nf_destroy(&cplt->event.notif); -+} -+ -+ -+/* -- Main SSAM device structures. ------------------------------------------ */ -+ -+/** -+ * ssam_controller_device() - Get the &struct device associated with this -+ * controller. -+ * @c: The controller for which to get the device. -+ * -+ * Return: Returns the &struct device associated with this controller, -+ * providing its lower-level transport. -+ */ -+struct device *ssam_controller_device(struct ssam_controller *c) -+{ -+ return ssh_rtl_get_device(&c->rtl); -+} -+EXPORT_SYMBOL_GPL(ssam_controller_device); -+ -+static void __ssam_controller_release(struct kref *kref) -+{ -+ struct ssam_controller *ctrl = to_ssam_controller(kref, kref); -+ -+ /* -+ * The lock-call here is to satisfy lockdep. At this point we really -+ * expect this to be the last remaining reference to the controller. -+ * Anything else is a bug. -+ */ -+ ssam_controller_lock(ctrl); -+ ssam_controller_destroy(ctrl); -+ ssam_controller_unlock(ctrl); -+ -+ kfree(ctrl); -+} -+ -+/** -+ * ssam_controller_get() - Increment reference count of controller. -+ * @c: The controller. -+ * -+ * Return: Returns the controller provided as input. -+ */ -+struct ssam_controller *ssam_controller_get(struct ssam_controller *c) -+{ -+ if (c) -+ kref_get(&c->kref); -+ return c; -+} -+EXPORT_SYMBOL_GPL(ssam_controller_get); -+ -+/** -+ * ssam_controller_put() - Decrement reference count of controller. -+ * @c: The controller. -+ */ -+void ssam_controller_put(struct ssam_controller *c) -+{ -+ if (c) -+ kref_put(&c->kref, __ssam_controller_release); -+} -+EXPORT_SYMBOL_GPL(ssam_controller_put); -+ -+/** -+ * ssam_controller_statelock() - Lock the controller against state transitions. -+ * @c: The controller to lock. -+ * -+ * Lock the controller against state transitions. Holding this lock guarantees -+ * that the controller will not transition between states, i.e. if the -+ * controller is in state "started", when this lock has been acquired, it will -+ * remain in this state at least until the lock has been released. -+ * -+ * Multiple clients may concurrently hold this lock. In other words: The -+ * ``statelock`` functions represent the read-lock part of a r/w-semaphore. -+ * Actions causing state transitions of the controller must be executed while -+ * holding the write-part of this r/w-semaphore (see ssam_controller_lock() -+ * and ssam_controller_unlock() for that). -+ * -+ * See ssam_controller_stateunlock() for the corresponding unlock function. -+ */ -+void ssam_controller_statelock(struct ssam_controller *c) -+{ -+ down_read(&c->lock); -+} -+EXPORT_SYMBOL_GPL(ssam_controller_statelock); -+ -+/** -+ * ssam_controller_stateunlock() - Unlock controller state transitions. -+ * @c: The controller to unlock. -+ * -+ * See ssam_controller_statelock() for the corresponding lock function. -+ */ -+void ssam_controller_stateunlock(struct ssam_controller *c) -+{ -+ up_read(&c->lock); -+} -+EXPORT_SYMBOL_GPL(ssam_controller_stateunlock); -+ -+/** -+ * ssam_controller_lock() - Acquire the main controller lock. -+ * @c: The controller to lock. -+ * -+ * This lock must be held for any state transitions, including transition to -+ * suspend/resumed states and during shutdown. See ssam_controller_statelock() -+ * for more details on controller locking. -+ * -+ * See ssam_controller_unlock() for the corresponding unlock function. -+ */ -+void ssam_controller_lock(struct ssam_controller *c) -+{ -+ down_write(&c->lock); -+} -+ -+/* -+ * ssam_controller_unlock() - Release the main controller lock. -+ * @c: The controller to unlock. -+ * -+ * See ssam_controller_lock() for the corresponding lock function. -+ */ -+void ssam_controller_unlock(struct ssam_controller *c) -+{ -+ up_write(&c->lock); -+} -+ -+static void ssam_handle_event(struct ssh_rtl *rtl, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data) -+{ -+ struct ssam_controller *ctrl = to_ssam_controller(rtl, rtl); -+ struct ssam_event_item *item; -+ -+ item = ssam_event_item_alloc(data->len, GFP_KERNEL); -+ if (!item) -+ return; -+ -+ item->rqid = get_unaligned_le16(&cmd->rqid); -+ item->event.target_category = cmd->tc; -+ item->event.target_id = cmd->tid_in; -+ item->event.command_id = cmd->cid; -+ item->event.instance_id = cmd->iid; -+ memcpy(&item->event.data[0], data->ptr, data->len); -+ -+ if (WARN_ON(ssam_cplt_submit_event(&ctrl->cplt, item))) -+ ssam_event_item_free(item); -+} -+ -+static const struct ssh_rtl_ops ssam_rtl_ops = { -+ .handle_event = ssam_handle_event, -+}; -+ -+static bool ssam_notifier_is_empty(struct ssam_controller *ctrl); -+static void ssam_notifier_unregister_all(struct ssam_controller *ctrl); -+ -+#define SSAM_SSH_DSM_REVISION 0 -+ -+/* d5e383e1-d892-4a76-89fc-f6aaae7ed5b5 */ -+static const guid_t SSAM_SSH_DSM_GUID = -+ GUID_INIT(0xd5e383e1, 0xd892, 0x4a76, -+ 0x89, 0xfc, 0xf6, 0xaa, 0xae, 0x7e, 0xd5, 0xb5); -+ -+enum ssh_dsm_fn { -+ SSH_DSM_FN_SSH_POWER_PROFILE = 0x05, -+ SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT = 0x06, -+ SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT = 0x07, -+ SSH_DSM_FN_D3_CLOSES_HANDLE = 0x08, -+ SSH_DSM_FN_SSH_BUFFER_SIZE = 0x09, -+}; -+ -+static int ssam_dsm_get_functions(acpi_handle handle, u64 *funcs) -+{ -+ union acpi_object *obj; -+ u64 mask = 0; -+ int i; -+ -+ *funcs = 0; -+ -+ /* -+ * The _DSM function is only present on newer models. It is not -+ * present on 5th and 6th generation devices (i.e. up to and including -+ * Surface Pro 6, Surface Laptop 2, Surface Book 2). -+ * -+ * If the _DSM is not present, indicate that no function is supported. -+ * This will result in default values being set. -+ */ -+ if (!acpi_has_method(handle, "_DSM")) -+ return 0; -+ -+ obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, -+ SSAM_SSH_DSM_REVISION, 0, NULL, -+ ACPI_TYPE_BUFFER); -+ if (!obj) -+ return -EIO; -+ -+ for (i = 0; i < obj->buffer.length && i < 8; i++) -+ mask |= (((u64)obj->buffer.pointer[i]) << (i * 8)); -+ -+ if (mask & BIT(0)) -+ *funcs = mask; -+ -+ ACPI_FREE(obj); -+ return 0; -+} -+ -+static int ssam_dsm_load_u32(acpi_handle handle, u64 funcs, u64 func, u32 *ret) -+{ -+ union acpi_object *obj; -+ u64 val; -+ -+ if (!(funcs & BIT_ULL(func))) -+ return 0; /* Not supported, leave *ret at its default value */ -+ -+ obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, -+ SSAM_SSH_DSM_REVISION, func, NULL, -+ ACPI_TYPE_INTEGER); -+ if (!obj) -+ return -EIO; -+ -+ val = obj->integer.value; -+ ACPI_FREE(obj); -+ -+ if (val > U32_MAX) -+ return -ERANGE; -+ -+ *ret = val; -+ return 0; -+} -+ -+/** -+ * ssam_controller_caps_load_from_acpi() - Load controller capabilities from -+ * ACPI _DSM. -+ * @handle: The handle of the ACPI controller/SSH device. -+ * @caps: Where to store the capabilities in. -+ * -+ * Initializes the given controller capabilities with default values, then -+ * checks and, if the respective _DSM functions are available, loads the -+ * actual capabilities from the _DSM. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+static -+int ssam_controller_caps_load_from_acpi(acpi_handle handle, -+ struct ssam_controller_caps *caps) -+{ -+ u32 d3_closes_handle = false; -+ u64 funcs; -+ int status; -+ -+ /* Set defaults. */ -+ caps->ssh_power_profile = U32_MAX; -+ caps->screen_on_sleep_idle_timeout = U32_MAX; -+ caps->screen_off_sleep_idle_timeout = U32_MAX; -+ caps->d3_closes_handle = false; -+ caps->ssh_buffer_size = U32_MAX; -+ -+ /* Pre-load supported DSM functions. */ -+ status = ssam_dsm_get_functions(handle, &funcs); -+ if (status) -+ return status; -+ -+ /* Load actual values from ACPI, if present. */ -+ status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_POWER_PROFILE, -+ &caps->ssh_power_profile); -+ if (status) -+ return status; -+ -+ status = ssam_dsm_load_u32(handle, funcs, -+ SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT, -+ &caps->screen_on_sleep_idle_timeout); -+ if (status) -+ return status; -+ -+ status = ssam_dsm_load_u32(handle, funcs, -+ SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT, -+ &caps->screen_off_sleep_idle_timeout); -+ if (status) -+ return status; -+ -+ status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_D3_CLOSES_HANDLE, -+ &d3_closes_handle); -+ if (status) -+ return status; -+ -+ caps->d3_closes_handle = !!d3_closes_handle; -+ -+ status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_BUFFER_SIZE, -+ &caps->ssh_buffer_size); -+ if (status) -+ return status; -+ -+ return 0; -+} -+ -+/** -+ * ssam_controller_init() - Initialize SSAM controller. -+ * @ctrl: The controller to initialize. -+ * @serdev: The serial device representing the underlying data transport. -+ * -+ * Initializes the given controller. Does neither start receiver nor -+ * transmitter threads. After this call, the controller has to be hooked up to -+ * the serdev core separately via &struct serdev_device_ops, relaying calls to -+ * ssam_controller_receive_buf() and ssam_controller_write_wakeup(). Once the -+ * controller has been hooked up, transmitter and receiver threads may be -+ * started via ssam_controller_start(). These setup steps need to be completed -+ * before controller can be used for requests. -+ */ -+int ssam_controller_init(struct ssam_controller *ctrl, -+ struct serdev_device *serdev) -+{ -+ acpi_handle handle = ACPI_HANDLE(&serdev->dev); -+ int status; -+ -+ init_rwsem(&ctrl->lock); -+ kref_init(&ctrl->kref); -+ -+ status = ssam_controller_caps_load_from_acpi(handle, &ctrl->caps); -+ if (status) -+ return status; -+ -+ dev_dbg(&serdev->dev, -+ "device capabilities:\n" -+ " ssh_power_profile: %u\n" -+ " ssh_buffer_size: %u\n" -+ " screen_on_sleep_idle_timeout: %u\n" -+ " screen_off_sleep_idle_timeout: %u\n" -+ " d3_closes_handle: %u\n", -+ ctrl->caps.ssh_power_profile, -+ ctrl->caps.ssh_buffer_size, -+ ctrl->caps.screen_on_sleep_idle_timeout, -+ ctrl->caps.screen_off_sleep_idle_timeout, -+ ctrl->caps.d3_closes_handle); -+ -+ ssh_seq_reset(&ctrl->counter.seq); -+ ssh_rqid_reset(&ctrl->counter.rqid); -+ -+ /* Initialize event/request completion system. */ -+ status = ssam_cplt_init(&ctrl->cplt, &serdev->dev); -+ if (status) -+ return status; -+ -+ /* Initialize request and packet transport layers. */ -+ status = ssh_rtl_init(&ctrl->rtl, serdev, &ssam_rtl_ops); -+ if (status) { -+ ssam_cplt_destroy(&ctrl->cplt); -+ return status; -+ } -+ -+ /* -+ * Set state via write_once even though we expect to be in an -+ * exclusive context, due to smoke-testing in -+ * ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_INITIALIZED); -+ return 0; -+} -+ -+/** -+ * ssam_controller_start() - Start the receiver and transmitter threads of the -+ * controller. -+ * @ctrl: The controller. -+ * -+ * Note: When this function is called, the controller should be properly -+ * hooked up to the serdev core via &struct serdev_device_ops. Please refer -+ * to ssam_controller_init() for more details on controller initialization. -+ * -+ * This function must be called with the main controller lock held (i.e. by -+ * calling ssam_controller_lock()). -+ */ -+int ssam_controller_start(struct ssam_controller *ctrl) -+{ -+ int status; -+ -+ if (ctrl->state != SSAM_CONTROLLER_INITIALIZED) -+ return -EINVAL; -+ -+ status = ssh_rtl_start(&ctrl->rtl); -+ if (status) -+ return status; -+ -+ /* -+ * Set state via write_once even though we expect to be locked/in an -+ * exclusive context, due to smoke-testing in -+ * ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED); -+ return 0; -+} -+ -+/* -+ * SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT - Timeout for flushing requests during -+ * shutdown. -+ * -+ * Chosen to be larger than one full request timeout, including packets timing -+ * out. This value should give ample time to complete any outstanding requests -+ * during normal operation and account for the odd package timeout. -+ */ -+#define SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT msecs_to_jiffies(5000) -+ -+/** -+ * ssam_controller_shutdown() - Shut down the controller. -+ * @ctrl: The controller. -+ * -+ * Shuts down the controller by flushing all pending requests and stopping the -+ * transmitter and receiver threads. All requests submitted after this call -+ * will fail with %-ESHUTDOWN. While it is discouraged to do so, this function -+ * is safe to use in parallel with ongoing request submission. -+ * -+ * In the course of this shutdown procedure, all currently registered -+ * notifiers will be unregistered. It is, however, strongly recommended to not -+ * rely on this behavior, and instead the party registering the notifier -+ * should unregister it before the controller gets shut down, e.g. via the -+ * SSAM bus which guarantees client devices to be removed before a shutdown. -+ * -+ * Note that events may still be pending after this call, but, due to the -+ * notifiers being unregistered, these events will be dropped when the -+ * controller is subsequently destroyed via ssam_controller_destroy(). -+ * -+ * This function must be called with the main controller lock held (i.e. by -+ * calling ssam_controller_lock()). -+ */ -+void ssam_controller_shutdown(struct ssam_controller *ctrl) -+{ -+ enum ssam_controller_state s = ctrl->state; -+ int status; -+ -+ if (s == SSAM_CONTROLLER_UNINITIALIZED || s == SSAM_CONTROLLER_STOPPED) -+ return; -+ -+ /* -+ * Try to flush pending events and requests while everything still -+ * works. Note: There may still be packets and/or requests in the -+ * system after this call (e.g. via control packets submitted by the -+ * packet transport layer or flush timeout / failure, ...). Those will -+ * be handled with the ssh_rtl_shutdown() call below. -+ */ -+ status = ssh_rtl_flush(&ctrl->rtl, SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT); -+ if (status) { -+ ssam_err(ctrl, "failed to flush request transport layer: %d\n", -+ status); -+ } -+ -+ /* Try to flush all currently completing requests and events. */ -+ ssam_cplt_flush(&ctrl->cplt); -+ -+ /* -+ * We expect all notifiers to have been removed by the respective client -+ * driver that set them up at this point. If this warning occurs, some -+ * client driver has not done that... -+ */ -+ WARN_ON(!ssam_notifier_is_empty(ctrl)); -+ -+ /* -+ * Nevertheless, we should still take care of drivers that don't behave -+ * well. Thus disable all enabled events, unregister all notifiers. -+ */ -+ ssam_notifier_unregister_all(ctrl); -+ -+ /* -+ * Cancel remaining requests. Ensure no new ones can be queued and stop -+ * threads. -+ */ -+ ssh_rtl_shutdown(&ctrl->rtl); -+ -+ /* -+ * Set state via write_once even though we expect to be locked/in an -+ * exclusive context, due to smoke-testing in -+ * ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STOPPED); -+ ctrl->rtl.ptl.serdev = NULL; -+} -+ -+/** -+ * ssam_controller_destroy() - Destroy the controller and free its resources. -+ * @ctrl: The controller. -+ * -+ * Ensures that all resources associated with the controller get freed. This -+ * function should only be called after the controller has been stopped via -+ * ssam_controller_shutdown(). In general, this function should not be called -+ * directly. The only valid place to call this function directly is during -+ * initialization, before the controller has been fully initialized and passed -+ * to other processes. This function is called automatically when the -+ * reference count of the controller reaches zero. -+ * -+ * This function must be called with the main controller lock held (i.e. by -+ * calling ssam_controller_lock()). -+ */ -+void ssam_controller_destroy(struct ssam_controller *ctrl) -+{ -+ if (ctrl->state == SSAM_CONTROLLER_UNINITIALIZED) -+ return; -+ -+ WARN_ON(ctrl->state != SSAM_CONTROLLER_STOPPED); -+ -+ /* -+ * Note: New events could still have been received after the previous -+ * flush in ssam_controller_shutdown, before the request transport layer -+ * has been shut down. At this point, after the shutdown, we can be sure -+ * that no new events will be queued. The call to ssam_cplt_destroy will -+ * ensure that those remaining are being completed and freed. -+ */ -+ -+ /* Actually free resources. */ -+ ssam_cplt_destroy(&ctrl->cplt); -+ ssh_rtl_destroy(&ctrl->rtl); -+ -+ /* -+ * Set state via write_once even though we expect to be locked/in an -+ * exclusive context, due to smoke-testing in -+ * ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_UNINITIALIZED); -+} -+ -+/** -+ * ssam_controller_suspend() - Suspend the controller. -+ * @ctrl: The controller to suspend. -+ * -+ * Marks the controller as suspended. Note that display-off and D0-exit -+ * notifications have to be sent manually before transitioning the controller -+ * into the suspended state via this function. -+ * -+ * See ssam_controller_resume() for the corresponding resume function. -+ * -+ * Return: Returns %-EINVAL if the controller is currently not in the -+ * "started" state. -+ */ -+int ssam_controller_suspend(struct ssam_controller *ctrl) -+{ -+ ssam_controller_lock(ctrl); -+ -+ if (ctrl->state != SSAM_CONTROLLER_STARTED) { -+ ssam_controller_unlock(ctrl); -+ return -EINVAL; -+ } -+ -+ ssam_dbg(ctrl, "pm: suspending controller\n"); -+ -+ /* -+ * Set state via write_once even though we're locked, due to -+ * smoke-testing in ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_SUSPENDED); -+ -+ ssam_controller_unlock(ctrl); -+ return 0; -+} -+ -+/** -+ * ssam_controller_resume() - Resume the controller from suspend. -+ * @ctrl: The controller to resume. -+ * -+ * Resume the controller from the suspended state it was put into via -+ * ssam_controller_suspend(). This function does not issue display-on and -+ * D0-entry notifications. If required, those have to be sent manually after -+ * this call. -+ * -+ * Return: Returns %-EINVAL if the controller is currently not suspended. -+ */ -+int ssam_controller_resume(struct ssam_controller *ctrl) -+{ -+ ssam_controller_lock(ctrl); -+ -+ if (ctrl->state != SSAM_CONTROLLER_SUSPENDED) { -+ ssam_controller_unlock(ctrl); -+ return -EINVAL; -+ } -+ -+ ssam_dbg(ctrl, "pm: resuming controller\n"); -+ -+ /* -+ * Set state via write_once even though we're locked, due to -+ * smoke-testing in ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED); -+ -+ ssam_controller_unlock(ctrl); -+ return 0; -+} -+ -+ -+/* -- Top-level request interface ------------------------------------------- */ -+ -+/** -+ * ssam_request_write_data() - Construct and write SAM request message to -+ * buffer. -+ * @buf: The buffer to write the data to. -+ * @ctrl: The controller via which the request will be sent. -+ * @spec: The request data and specification. -+ * -+ * Constructs a SAM/SSH request message and writes it to the provided buffer. -+ * The request and transport counters, specifically RQID and SEQ, will be set -+ * in this call. These counters are obtained from the controller. It is thus -+ * only valid to send the resulting message via the controller specified here. -+ * -+ * For calculation of the required buffer size, refer to the -+ * SSH_COMMAND_MESSAGE_LENGTH() macro. -+ * -+ * Return: Returns the number of bytes used in the buffer on success. Returns -+ * %-EINVAL if the payload length provided in the request specification is too -+ * large (larger than %SSH_COMMAND_MAX_PAYLOAD_SIZE) or if the provided buffer -+ * is too small. -+ */ -+ssize_t ssam_request_write_data(struct ssam_span *buf, -+ struct ssam_controller *ctrl, -+ const struct ssam_request *spec) -+{ -+ struct msgbuf msgb; -+ u16 rqid; -+ u8 seq; -+ -+ if (spec->length > SSH_COMMAND_MAX_PAYLOAD_SIZE) -+ return -EINVAL; -+ -+ if (SSH_COMMAND_MESSAGE_LENGTH(spec->length) > buf->len) -+ return -EINVAL; -+ -+ msgb_init(&msgb, buf->ptr, buf->len); -+ seq = ssh_seq_next(&ctrl->counter.seq); -+ rqid = ssh_rqid_next(&ctrl->counter.rqid); -+ msgb_push_cmd(&msgb, seq, rqid, spec); -+ -+ return msgb_bytes_used(&msgb); -+} -+EXPORT_SYMBOL_GPL(ssam_request_write_data); -+ -+static void ssam_request_sync_complete(struct ssh_request *rqst, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data, int status) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ struct ssam_request_sync *r; -+ -+ r = container_of(rqst, struct ssam_request_sync, base); -+ r->status = status; -+ -+ if (r->resp) -+ r->resp->length = 0; -+ -+ if (status) { -+ rtl_dbg_cond(rtl, "rsp: request failed: %d\n", status); -+ return; -+ } -+ -+ if (!data) /* Handle requests without a response. */ -+ return; -+ -+ if (!r->resp || !r->resp->pointer) { -+ if (data->len) -+ rtl_warn(rtl, "rsp: no response buffer provided, dropping data\n"); -+ return; -+ } -+ -+ if (data->len > r->resp->capacity) { -+ rtl_err(rtl, -+ "rsp: response buffer too small, capacity: %zu bytes, got: %zu bytes\n", -+ r->resp->capacity, data->len); -+ r->status = -ENOSPC; -+ return; -+ } -+ -+ r->resp->length = data->len; -+ memcpy(r->resp->pointer, data->ptr, data->len); -+} -+ -+static void ssam_request_sync_release(struct ssh_request *rqst) -+{ -+ complete_all(&container_of(rqst, struct ssam_request_sync, base)->comp); -+} -+ -+static const struct ssh_request_ops ssam_request_sync_ops = { -+ .release = ssam_request_sync_release, -+ .complete = ssam_request_sync_complete, -+}; -+ -+/** -+ * ssam_request_sync_alloc() - Allocate a synchronous request. -+ * @payload_len: The length of the request payload. -+ * @flags: Flags used for allocation. -+ * @rqst: Where to store the pointer to the allocated request. -+ * @buffer: Where to store the buffer descriptor for the message buffer of -+ * the request. -+ * -+ * Allocates a synchronous request with corresponding message buffer. The -+ * request still needs to be initialized ssam_request_sync_init() before -+ * it can be submitted, and the message buffer data must still be set to the -+ * returned buffer via ssam_request_sync_set_data() after it has been filled, -+ * if need be with adjusted message length. -+ * -+ * After use, the request and its corresponding message buffer should be freed -+ * via ssam_request_sync_free(). The buffer must not be freed separately. -+ * -+ * Return: Returns zero on success, %-ENOMEM if the request could not be -+ * allocated. -+ */ -+int ssam_request_sync_alloc(size_t payload_len, gfp_t flags, -+ struct ssam_request_sync **rqst, -+ struct ssam_span *buffer) -+{ -+ size_t msglen = SSH_COMMAND_MESSAGE_LENGTH(payload_len); -+ -+ *rqst = kzalloc(sizeof(**rqst) + msglen, flags); -+ if (!*rqst) -+ return -ENOMEM; -+ -+ buffer->ptr = (u8 *)(*rqst + 1); -+ buffer->len = msglen; -+ -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_alloc); -+ -+/** -+ * ssam_request_sync_free() - Free a synchronous request. -+ * @rqst: The request to be freed. -+ * -+ * Free a synchronous request and its corresponding buffer allocated with -+ * ssam_request_sync_alloc(). Do not use for requests allocated on the stack -+ * or via any other function. -+ * -+ * Warning: The caller must ensure that the request is not in use any more. -+ * I.e. the caller must ensure that it has the only reference to the request -+ * and the request is not currently pending. This means that the caller has -+ * either never submitted the request, request submission has failed, or the -+ * caller has waited until the submitted request has been completed via -+ * ssam_request_sync_wait(). -+ */ -+void ssam_request_sync_free(struct ssam_request_sync *rqst) -+{ -+ kfree(rqst); -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_free); -+ -+/** -+ * ssam_request_sync_init() - Initialize a synchronous request struct. -+ * @rqst: The request to initialize. -+ * @flags: The request flags. -+ * -+ * Initializes the given request struct. Does not initialize the request -+ * message data. This has to be done explicitly after this call via -+ * ssam_request_sync_set_data() and the actual message data has to be written -+ * via ssam_request_write_data(). -+ * -+ * Return: Returns zero on success or %-EINVAL if the given flags are invalid. -+ */ -+int ssam_request_sync_init(struct ssam_request_sync *rqst, -+ enum ssam_request_flags flags) -+{ -+ int status; -+ -+ status = ssh_request_init(&rqst->base, flags, &ssam_request_sync_ops); -+ if (status) -+ return status; -+ -+ init_completion(&rqst->comp); -+ rqst->resp = NULL; -+ rqst->status = 0; -+ -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_init); -+ -+/** -+ * ssam_request_sync_submit() - Submit a synchronous request. -+ * @ctrl: The controller with which to submit the request. -+ * @rqst: The request to submit. -+ * -+ * Submit a synchronous request. The request has to be initialized and -+ * properly set up, including response buffer (may be %NULL if no response is -+ * expected) and command message data. This function does not wait for the -+ * request to be completed. -+ * -+ * If this function succeeds, ssam_request_sync_wait() must be used to ensure -+ * that the request has been completed before the response data can be -+ * accessed and/or the request can be freed. On failure, the request may -+ * immediately be freed. -+ * -+ * This function may only be used if the controller is active, i.e. has been -+ * initialized and not suspended. -+ */ -+int ssam_request_sync_submit(struct ssam_controller *ctrl, -+ struct ssam_request_sync *rqst) -+{ -+ int status; -+ -+ /* -+ * This is only a superficial check. In general, the caller needs to -+ * ensure that the controller is initialized and is not (and does not -+ * get) suspended during use, i.e. until the request has been completed -+ * (if _absolutely_ necessary, by use of ssam_controller_statelock/ -+ * ssam_controller_stateunlock, but something like ssam_client_link -+ * should be preferred as this needs to last until the request has been -+ * completed). -+ * -+ * Note that it is actually safe to use this function while the -+ * controller is in the process of being shut down (as ssh_rtl_submit -+ * is safe with regards to this), but it is generally discouraged to do -+ * so. -+ */ -+ if (WARN_ON(READ_ONCE(ctrl->state) != SSAM_CONTROLLER_STARTED)) { -+ ssh_request_put(&rqst->base); -+ return -ENODEV; -+ } -+ -+ status = ssh_rtl_submit(&ctrl->rtl, &rqst->base); -+ ssh_request_put(&rqst->base); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_submit); -+ -+/** -+ * ssam_request_sync() - Execute a synchronous request. -+ * @ctrl: The controller via which the request will be submitted. -+ * @spec: The request specification and payload. -+ * @rsp: The response buffer. -+ * -+ * Allocates a synchronous request with its message data buffer on the heap -+ * via ssam_request_sync_alloc(), fully initializes it via the provided -+ * request specification, submits it, and finally waits for its completion -+ * before freeing it and returning its status. -+ * -+ * Return: Returns the status of the request or any failure during setup. -+ */ -+int ssam_request_sync(struct ssam_controller *ctrl, -+ const struct ssam_request *spec, -+ struct ssam_response *rsp) -+{ -+ struct ssam_request_sync *rqst; -+ struct ssam_span buf; -+ ssize_t len; -+ int status; -+ -+ status = ssam_request_sync_alloc(spec->length, GFP_KERNEL, &rqst, &buf); -+ if (status) -+ return status; -+ -+ status = ssam_request_sync_init(rqst, spec->flags); -+ if (status) -+ return status; -+ -+ ssam_request_sync_set_resp(rqst, rsp); -+ -+ len = ssam_request_write_data(&buf, ctrl, spec); -+ if (len < 0) { -+ ssam_request_sync_free(rqst); -+ return len; -+ } -+ -+ ssam_request_sync_set_data(rqst, buf.ptr, len); -+ -+ status = ssam_request_sync_submit(ctrl, rqst); -+ if (!status) -+ status = ssam_request_sync_wait(rqst); -+ -+ ssam_request_sync_free(rqst); -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync); -+ -+/** -+ * ssam_request_sync_with_buffer() - Execute a synchronous request with the -+ * provided buffer as back-end for the message buffer. -+ * @ctrl: The controller via which the request will be submitted. -+ * @spec: The request specification and payload. -+ * @rsp: The response buffer. -+ * @buf: The buffer for the request message data. -+ * -+ * Allocates a synchronous request struct on the stack, fully initializes it -+ * using the provided buffer as message data buffer, submits it, and then -+ * waits for its completion before returning its status. The -+ * SSH_COMMAND_MESSAGE_LENGTH() macro can be used to compute the required -+ * message buffer size. -+ * -+ * This function does essentially the same as ssam_request_sync(), but instead -+ * of dynamically allocating the request and message data buffer, it uses the -+ * provided message data buffer and stores the (small) request struct on the -+ * heap. -+ * -+ * Return: Returns the status of the request or any failure during setup. -+ */ -+int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, -+ const struct ssam_request *spec, -+ struct ssam_response *rsp, -+ struct ssam_span *buf) -+{ -+ struct ssam_request_sync rqst; -+ ssize_t len; -+ int status; -+ -+ status = ssam_request_sync_init(&rqst, spec->flags); -+ if (status) -+ return status; -+ -+ ssam_request_sync_set_resp(&rqst, rsp); -+ -+ len = ssam_request_write_data(buf, ctrl, spec); -+ if (len < 0) -+ return len; -+ -+ ssam_request_sync_set_data(&rqst, buf->ptr, len); -+ -+ status = ssam_request_sync_submit(ctrl, &rqst); -+ if (!status) -+ status = ssam_request_sync_wait(&rqst); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer); -+ -+ -+/* -- Internal SAM requests. ------------------------------------------------ */ -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x13, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x15, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x16, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x33, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x34, -+ .instance_id = 0x00, -+}); -+ -+/** -+ * struct ssh_notification_params - Command payload to enable/disable SSH -+ * notifications. -+ * @target_category: The target category for which notifications should be -+ * enabled/disabled. -+ * @flags: Flags determining how notifications are being sent. -+ * @request_id: The request ID that is used to send these notifications. -+ * @instance_id: The specific instance in the given target category for -+ * which notifications should be enabled. -+ */ -+struct ssh_notification_params { -+ u8 target_category; -+ u8 flags; -+ __le16 request_id; -+ u8 instance_id; -+} __packed; -+ -+static int __ssam_ssh_event_request(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, u8 cid, -+ struct ssam_event_id id, u8 flags) -+{ -+ struct ssh_notification_params params; -+ struct ssam_request rqst; -+ struct ssam_response result; -+ int status; -+ -+ u16 rqid = ssh_tc_to_rqid(id.target_category); -+ u8 buf = 0; -+ -+ /* Only allow RQIDs that lie within the event spectrum. */ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ params.target_category = id.target_category; -+ params.instance_id = id.instance; -+ params.flags = flags; -+ put_unaligned_le16(rqid, ¶ms.request_id); -+ -+ rqst.target_category = reg.target_category; -+ rqst.target_id = reg.target_id; -+ rqst.command_id = cid; -+ rqst.instance_id = 0x00; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(params); -+ rqst.payload = (u8 *)¶ms; -+ -+ result.capacity = sizeof(buf); -+ result.length = 0; -+ result.pointer = &buf; -+ -+ status = ssam_retry(ssam_request_sync_onstack, ctrl, &rqst, &result, -+ sizeof(params)); -+ -+ return status < 0 ? status : buf; -+} -+ -+/** -+ * ssam_ssh_event_enable() - Enable SSH event. -+ * @ctrl: The controller for which to enable the event. -+ * @reg: The event registry describing what request to use for enabling and -+ * disabling the event. -+ * @id: The event identifier. -+ * @flags: The event flags. -+ * -+ * Enables the specified event on the EC. This function does not manage -+ * reference counting of enabled events and is basically only a wrapper for -+ * the raw EC request. If the specified event is already enabled, the EC will -+ * ignore this request. -+ * -+ * Return: Returns the status of the executed SAM request (zero on success and -+ * negative on direct failure) or %-EPROTO if the request response indicates a -+ * failure. -+ */ -+static int ssam_ssh_event_enable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags) -+{ -+ int status; -+ -+ status = __ssam_ssh_event_request(ctrl, reg, reg.cid_enable, id, flags); -+ -+ if (status < 0 && status != -EINVAL) { -+ ssam_err(ctrl, -+ "failed to enable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n", -+ id.target_category, id.instance, reg.target_category); -+ } -+ -+ if (status > 0) { -+ ssam_err(ctrl, -+ "unexpected result while enabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n", -+ status, id.target_category, id.instance, reg.target_category); -+ return -EPROTO; -+ } -+ -+ return status; -+} -+ -+/** -+ * ssam_ssh_event_disable() - Disable SSH event. -+ * @ctrl: The controller for which to disable the event. -+ * @reg: The event registry describing what request to use for enabling and -+ * disabling the event (must be same as used when enabling the event). -+ * @id: The event identifier. -+ * @flags: The event flags (likely ignored for disabling of events). -+ * -+ * Disables the specified event on the EC. This function does not manage -+ * reference counting of enabled events and is basically only a wrapper for -+ * the raw EC request. If the specified event is already disabled, the EC will -+ * ignore this request. -+ * -+ * Return: Returns the status of the executed SAM request (zero on success and -+ * negative on direct failure) or %-EPROTO if the request response indicates a -+ * failure. -+ */ -+static int ssam_ssh_event_disable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags) -+{ -+ int status; -+ -+ status = __ssam_ssh_event_request(ctrl, reg, reg.cid_disable, id, flags); -+ -+ if (status < 0 && status != -EINVAL) { -+ ssam_err(ctrl, -+ "failed to disable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n", -+ id.target_category, id.instance, reg.target_category); -+ } -+ -+ if (status > 0) { -+ ssam_err(ctrl, -+ "unexpected result while disabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n", -+ status, id.target_category, id.instance, reg.target_category); -+ return -EPROTO; -+ } -+ -+ return status; -+} -+ -+ -+/* -- Wrappers for internal SAM requests. ----------------------------------- */ -+ -+/** -+ * ssam_get_firmware_version() - Get the SAM/EC firmware version. -+ * @ctrl: The controller. -+ * @version: Where to store the version number. -+ * -+ * Return: Returns zero on success or the status of the executed SAM request -+ * if that request failed. -+ */ -+int ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version) -+{ -+ __le32 __version; -+ int status; -+ -+ status = ssam_retry(ssam_ssh_get_firmware_version, ctrl, &__version); -+ if (status) -+ return status; -+ -+ *version = le32_to_cpu(__version); -+ return 0; -+} -+ -+/** -+ * ssam_ctrl_notif_display_off() - Notify EC that the display has been turned -+ * off. -+ * @ctrl: The controller. -+ * -+ * Notify the EC that the display has been turned off and the driver may enter -+ * a lower-power state. This will prevent events from being sent directly. -+ * Rather, the EC signals an event by pulling the wakeup GPIO high for as long -+ * as there are pending events. The events then need to be manually released, -+ * one by one, via the GPIO callback request. All pending events accumulated -+ * during this state can also be released by issuing the display-on -+ * notification, e.g. via ssam_ctrl_notif_display_on(), which will also reset -+ * the GPIO. -+ * -+ * On some devices, specifically ones with an integrated keyboard, the keyboard -+ * backlight will be turned off by this call. -+ * -+ * This function will only send the display-off notification command if -+ * display notifications are supported by the EC. Currently all known devices -+ * support these notifications. -+ * -+ * Use ssam_ctrl_notif_display_on() to reverse the effects of this function. -+ * -+ * Return: Returns zero on success or if no request has been executed, the -+ * status of the executed SAM request if that request failed, or %-EPROTO if -+ * an unexpected response has been received. -+ */ -+int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl) -+{ -+ int status; -+ u8 response; -+ -+ ssam_dbg(ctrl, "pm: notifying display off\n"); -+ -+ status = ssam_retry(ssam_ssh_notif_display_off, ctrl, &response); -+ if (status) -+ return status; -+ -+ if (response != 0) { -+ ssam_err(ctrl, "unexpected response from display-off notification: %#04x\n", -+ response); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_ctrl_notif_display_on() - Notify EC that the display has been turned on. -+ * @ctrl: The controller. -+ * -+ * Notify the EC that the display has been turned back on and the driver has -+ * exited its lower-power state. This notification is the counterpart to the -+ * display-off notification sent via ssam_ctrl_notif_display_off() and will -+ * reverse its effects, including resetting events to their default behavior. -+ * -+ * This function will only send the display-on notification command if display -+ * notifications are supported by the EC. Currently all known devices support -+ * these notifications. -+ * -+ * See ssam_ctrl_notif_display_off() for more details. -+ * -+ * Return: Returns zero on success or if no request has been executed, the -+ * status of the executed SAM request if that request failed, or %-EPROTO if -+ * an unexpected response has been received. -+ */ -+int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl) -+{ -+ int status; -+ u8 response; -+ -+ ssam_dbg(ctrl, "pm: notifying display on\n"); -+ -+ status = ssam_retry(ssam_ssh_notif_display_on, ctrl, &response); -+ if (status) -+ return status; -+ -+ if (response != 0) { -+ ssam_err(ctrl, "unexpected response from display-on notification: %#04x\n", -+ response); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_ctrl_notif_d0_exit() - Notify EC that the driver/device exits the D0 -+ * power state. -+ * @ctrl: The controller -+ * -+ * Notifies the EC that the driver prepares to exit the D0 power state in -+ * favor of a lower-power state. Exact effects of this function related to the -+ * EC are currently unknown. -+ * -+ * This function will only send the D0-exit notification command if D0-state -+ * notifications are supported by the EC. Only newer Surface generations -+ * support these notifications. -+ * -+ * Use ssam_ctrl_notif_d0_entry() to reverse the effects of this function. -+ * -+ * Return: Returns zero on success or if no request has been executed, the -+ * status of the executed SAM request if that request failed, or %-EPROTO if -+ * an unexpected response has been received. -+ */ -+int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl) -+{ -+ int status; -+ u8 response; -+ -+ if (!ctrl->caps.d3_closes_handle) -+ return 0; -+ -+ ssam_dbg(ctrl, "pm: notifying D0 exit\n"); -+ -+ status = ssam_retry(ssam_ssh_notif_d0_exit, ctrl, &response); -+ if (status) -+ return status; -+ -+ if (response != 0) { -+ ssam_err(ctrl, "unexpected response from D0-exit notification: %#04x\n", -+ response); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_ctrl_notif_d0_entry() - Notify EC that the driver/device enters the D0 -+ * power state. -+ * @ctrl: The controller -+ * -+ * Notifies the EC that the driver has exited a lower-power state and entered -+ * the D0 power state. Exact effects of this function related to the EC are -+ * currently unknown. -+ * -+ * This function will only send the D0-entry notification command if D0-state -+ * notifications are supported by the EC. Only newer Surface generations -+ * support these notifications. -+ * -+ * See ssam_ctrl_notif_d0_exit() for more details. -+ * -+ * Return: Returns zero on success or if no request has been executed, the -+ * status of the executed SAM request if that request failed, or %-EPROTO if -+ * an unexpected response has been received. -+ */ -+int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) -+{ -+ int status; -+ u8 response; -+ -+ if (!ctrl->caps.d3_closes_handle) -+ return 0; -+ -+ ssam_dbg(ctrl, "pm: notifying D0 entry\n"); -+ -+ status = ssam_retry(ssam_ssh_notif_d0_entry, ctrl, &response); -+ if (status) -+ return status; -+ -+ if (response != 0) { -+ ssam_err(ctrl, "unexpected response from D0-entry notification: %#04x\n", -+ response); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+ -+/* -- Top-level event registry interface. ----------------------------------- */ -+ -+/** -+ * ssam_nf_refcount_enable() - Enable event for reference count entry if it has -+ * not already been enabled. -+ * @ctrl: The controller to enable the event on. -+ * @entry: The reference count entry for the event to be enabled. -+ * @flags: The flags used for enabling the event on the EC. -+ * -+ * Enable the event associated with the given reference count entry if the -+ * reference count equals one, i.e. the event has not previously been enabled. -+ * If the event has already been enabled (i.e. reference count not equal to -+ * one), check that the flags used for enabling match and warn about this if -+ * they do not. -+ * -+ * This does not modify the reference count itself, which is done with -+ * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ * -+ * Return: Returns zero on success. If the event is enabled by this call, -+ * returns the status of the event-enable EC command. -+ */ -+static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, -+ struct ssam_nf_refcount_entry *entry, u8 flags) -+{ -+ const struct ssam_event_registry reg = entry->key.reg; -+ const struct ssam_event_id id = entry->key.id; -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ int status; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ reg.target_category, id.target_category, id.instance, entry->refcount); -+ -+ if (entry->refcount == 1) { -+ status = ssam_ssh_event_enable(ctrl, reg, id, flags); -+ if (status) -+ return status; -+ -+ entry->flags = flags; -+ -+ } else if (entry->flags != flags) { -+ ssam_warn(ctrl, -+ "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -+ flags, entry->flags, reg.target_category, id.target_category, -+ id.instance); -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is -+ * no longer in use and free the corresponding entry. -+ * @ctrl: The controller to disable the event on. -+ * @entry: The reference count entry for the event to be disabled. -+ * @flags: The flags used for enabling the event on the EC. -+ * -+ * If the reference count equals zero, i.e. the event is no longer requested by -+ * any client, the event will be disabled and the corresponding reference count -+ * entry freed. The reference count entry must not be used any more after a -+ * call to this function. -+ * -+ * Also checks if the flags used for disabling the event match the flags used -+ * for enabling the event and warns if they do not (regardless of reference -+ * count). -+ * -+ * This does not modify the reference count itself, which is done with -+ * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ * -+ * Return: Returns zero on success. If the event is disabled by this call, -+ * returns the status of the event-enable EC command. -+ */ -+static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, -+ struct ssam_nf_refcount_entry *entry, u8 flags) -+{ -+ const struct ssam_event_registry reg = entry->key.reg; -+ const struct ssam_event_id id = entry->key.id; -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ int status = 0; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ reg.target_category, id.target_category, id.instance, entry->refcount); -+ -+ if (entry->flags != flags) { -+ ssam_warn(ctrl, -+ "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -+ flags, entry->flags, reg.target_category, id.target_category, -+ id.instance); -+ } -+ -+ if (entry->refcount == 0) { -+ status = ssam_ssh_event_disable(ctrl, reg, id, flags); -+ kfree(entry); -+ } -+ -+ return status; -+} -+ -+/** -+ * ssam_notifier_register() - Register an event notifier. -+ * @ctrl: The controller to register the notifier on. -+ * @n: The event notifier to register. -+ * -+ * Register an event notifier. Increment the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the event is not -+ * marked as an observer and is currently not enabled, it will be enabled -+ * during this call. If the notifier is marked as an observer, no attempt will -+ * be made at enabling any event and no reference count will be modified. -+ * -+ * Notifiers marked as observers do not need to be associated with one specific -+ * event, i.e. as long as no event matching is performed, only the event target -+ * category needs to be set. -+ * -+ * Return: Returns zero on success, %-ENOSPC if there have already been -+ * %INT_MAX notifiers for the event ID/type associated with the notifier block -+ * registered, %-ENOMEM if the corresponding event entry could not be -+ * allocated. If this is the first time that a notifier block is registered -+ * for the specific associated event, returns the status of the event-enable -+ * EC-command. -+ */ -+int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n) -+{ -+ u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); -+ struct ssam_nf_refcount_entry *entry = NULL; -+ struct ssam_nf_head *nf_head; -+ struct ssam_nf *nf; -+ int status; -+ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ nf = &ctrl->cplt.event.notif; -+ nf_head = &nf->head[ssh_rqid_to_event(rqid)]; -+ -+ mutex_lock(&nf->lock); -+ -+ if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { -+ entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); -+ if (IS_ERR(entry)) { -+ mutex_unlock(&nf->lock); -+ return PTR_ERR(entry); -+ } -+ } -+ -+ status = ssam_nfblk_insert(nf_head, &n->base); -+ if (status) { -+ if (entry) -+ ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); -+ -+ mutex_unlock(&nf->lock); -+ return status; -+ } -+ -+ if (entry) { -+ status = ssam_nf_refcount_enable(ctrl, entry, n->event.flags); -+ if (status) { -+ ssam_nfblk_remove(&n->base); -+ ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); -+ mutex_unlock(&nf->lock); -+ synchronize_srcu(&nf_head->srcu); -+ return status; -+ } -+ } -+ -+ mutex_unlock(&nf->lock); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_notifier_register); -+ -+/** -+ * ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) -+{ -+ u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); -+ struct ssam_nf_refcount_entry *entry; -+ struct ssam_nf_head *nf_head; -+ struct ssam_nf *nf; -+ int status = 0; -+ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ nf = &ctrl->cplt.event.notif; -+ nf_head = &nf->head[ssh_rqid_to_event(rqid)]; -+ -+ mutex_lock(&nf->lock); -+ -+ if (!ssam_nfblk_find(nf_head, &n->base)) { -+ mutex_unlock(&nf->lock); -+ return -ENOENT; -+ } -+ -+ /* -+ * If this is an observer notifier, do not attempt to disable the -+ * event, just remove it. -+ */ -+ if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { -+ entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); -+ if (WARN_ON(!entry)) { -+ /* -+ * If this does not return an entry, there's a logic -+ * error somewhere: The notifier block is registered, -+ * but the event refcount entry is not there. Remove -+ * the notifier block anyways. -+ */ -+ status = -ENOENT; -+ goto remove; -+ } -+ -+ status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); -+ } -+ -+remove: -+ ssam_nfblk_remove(&n->base); -+ mutex_unlock(&nf->lock); -+ synchronize_srcu(&nf_head->srcu); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_notifier_unregister); -+ -+/** -+ * ssam_controller_event_enable() - Enable the specified event. -+ * @ctrl: The controller to enable the event for. -+ * @reg: The event registry to use for enabling the event. -+ * @id: The event ID specifying the event to be enabled. -+ * @flags: The SAM event flags used for enabling the event. -+ * -+ * Increment the event reference count of the specified event. If the event has -+ * not been enabled previously, it will be enabled by this call. -+ * -+ * Note: In general, ssam_notifier_register() with a non-observer notifier -+ * should be preferred for enabling/disabling events, as this will guarantee -+ * proper ordering and event forwarding in case of errors during event -+ * enabling/disabling. -+ * -+ * Return: Returns zero on success, %-ENOSPC if the reference count for the -+ * specified event has reached its maximum, %-ENOMEM if the corresponding event -+ * entry could not be allocated. If this is the first time that this event has -+ * been enabled (i.e. the reference count was incremented from zero to one by -+ * this call), returns the status of the event-enable EC-command. -+ */ -+int ssam_controller_event_enable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags) -+{ -+ u16 rqid = ssh_tc_to_rqid(id.target_category); -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct ssam_nf_refcount_entry *entry; -+ int status; -+ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ mutex_lock(&nf->lock); -+ -+ entry = ssam_nf_refcount_inc(nf, reg, id); -+ if (IS_ERR(entry)) { -+ mutex_unlock(&nf->lock); -+ return PTR_ERR(entry); -+ } -+ -+ status = ssam_nf_refcount_enable(ctrl, entry, flags); -+ if (status) { -+ ssam_nf_refcount_dec_free(nf, reg, id); -+ mutex_unlock(&nf->lock); -+ return status; -+ } -+ -+ mutex_unlock(&nf->lock); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_controller_event_enable); -+ -+/** -+ * ssam_controller_event_disable() - Disable the specified event. -+ * @ctrl: The controller to disable the event for. -+ * @reg: The event registry to use for disabling the event. -+ * @id: The event ID specifying the event to be disabled. -+ * @flags: The flags used when enabling the event. -+ * -+ * Decrement the reference count of the specified event. If the reference count -+ * reaches zero, the event will be disabled. -+ * -+ * Note: In general, ssam_notifier_register()/ssam_notifier_unregister() with a -+ * non-observer notifier should be preferred for enabling/disabling events, as -+ * this will guarantee proper ordering and event forwarding in case of errors -+ * during event enabling/disabling. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given event has not been -+ * enabled on the controller. If the reference count of the event reaches zero -+ * during this call, returns the status of the event-disable EC-command. -+ */ -+int ssam_controller_event_disable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags) -+{ -+ u16 rqid = ssh_tc_to_rqid(id.target_category); -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct ssam_nf_refcount_entry *entry; -+ int status; -+ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ mutex_lock(&nf->lock); -+ -+ entry = ssam_nf_refcount_dec(nf, reg, id); -+ if (!entry) { -+ mutex_unlock(&nf->lock); -+ return -ENOENT; -+ } -+ -+ status = ssam_nf_refcount_disable_free(ctrl, entry, flags); -+ -+ mutex_unlock(&nf->lock); -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_controller_event_disable); -+ -+/** -+ * ssam_notifier_disable_registered() - Disable events for all registered -+ * notifiers. -+ * @ctrl: The controller for which to disable the notifiers/events. -+ * -+ * Disables events for all currently registered notifiers. In case of an error -+ * (EC command failing), all previously disabled events will be restored and -+ * the error code returned. -+ * -+ * This function is intended to disable all events prior to hibernation entry. -+ * See ssam_notifier_restore_registered() to restore/re-enable all events -+ * disabled with this function. -+ * -+ * Note that this function will not disable events for notifiers registered -+ * after calling this function. It should thus be made sure that no new -+ * notifiers are going to be added after this call and before the corresponding -+ * call to ssam_notifier_restore_registered(). -+ * -+ * Return: Returns zero on success. In case of failure returns the error code -+ * returned by the failed EC command to disable an event. -+ */ -+int ssam_notifier_disable_registered(struct ssam_controller *ctrl) -+{ -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct rb_node *n; -+ int status; -+ -+ mutex_lock(&nf->lock); -+ for (n = rb_first(&nf->refcount); n; n = rb_next(n)) { -+ struct ssam_nf_refcount_entry *e; -+ -+ e = rb_entry(n, struct ssam_nf_refcount_entry, node); -+ status = ssam_ssh_event_disable(ctrl, e->key.reg, -+ e->key.id, e->flags); -+ if (status) -+ goto err; -+ } -+ mutex_unlock(&nf->lock); -+ -+ return 0; -+ -+err: -+ for (n = rb_prev(n); n; n = rb_prev(n)) { -+ struct ssam_nf_refcount_entry *e; -+ -+ e = rb_entry(n, struct ssam_nf_refcount_entry, node); -+ ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags); -+ } -+ mutex_unlock(&nf->lock); -+ -+ return status; -+} -+ -+/** -+ * ssam_notifier_restore_registered() - Restore/re-enable events for all -+ * registered notifiers. -+ * @ctrl: The controller for which to restore the notifiers/events. -+ * -+ * Restores/re-enables all events for which notifiers have been registered on -+ * the given controller. In case of a failure, the error is logged and the -+ * function continues to try and enable the remaining events. -+ * -+ * This function is intended to restore/re-enable all registered events after -+ * hibernation. See ssam_notifier_disable_registered() for the counter part -+ * disabling the events and more details. -+ */ -+void ssam_notifier_restore_registered(struct ssam_controller *ctrl) -+{ -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct rb_node *n; -+ -+ mutex_lock(&nf->lock); -+ for (n = rb_first(&nf->refcount); n; n = rb_next(n)) { -+ struct ssam_nf_refcount_entry *e; -+ -+ e = rb_entry(n, struct ssam_nf_refcount_entry, node); -+ -+ /* Ignore errors, will get logged in call. */ -+ ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags); -+ } -+ mutex_unlock(&nf->lock); -+} -+ -+/** -+ * ssam_notifier_is_empty() - Check if there are any registered notifiers. -+ * @ctrl: The controller to check on. -+ * -+ * Return: Returns %true if there are currently no notifiers registered on the -+ * controller, %false otherwise. -+ */ -+static bool ssam_notifier_is_empty(struct ssam_controller *ctrl) -+{ -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ bool result; -+ -+ mutex_lock(&nf->lock); -+ result = ssam_nf_refcount_empty(nf); -+ mutex_unlock(&nf->lock); -+ -+ return result; -+} -+ -+/** -+ * ssam_notifier_unregister_all() - Unregister all currently registered -+ * notifiers. -+ * @ctrl: The controller to unregister the notifiers on. -+ * -+ * Unregisters all currently registered notifiers. This function is used to -+ * ensure that all notifiers will be unregistered and associated -+ * entries/resources freed when the controller is being shut down. -+ */ -+static void ssam_notifier_unregister_all(struct ssam_controller *ctrl) -+{ -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct ssam_nf_refcount_entry *e, *n; -+ -+ mutex_lock(&nf->lock); -+ rbtree_postorder_for_each_entry_safe(e, n, &nf->refcount, node) { -+ /* Ignore errors, will get logged in call. */ -+ ssam_ssh_event_disable(ctrl, e->key.reg, e->key.id, e->flags); -+ kfree(e); -+ } -+ nf->refcount = RB_ROOT; -+ mutex_unlock(&nf->lock); -+} -+ -+ -+/* -- Wakeup IRQ. ----------------------------------------------------------- */ -+ -+static irqreturn_t ssam_irq_handle(int irq, void *dev_id) -+{ -+ struct ssam_controller *ctrl = dev_id; -+ -+ ssam_dbg(ctrl, "pm: wake irq triggered\n"); -+ -+ /* -+ * Note: Proper wakeup detection is currently unimplemented. -+ * When the EC is in display-off or any other non-D0 state, it -+ * does not send events/notifications to the host. Instead it -+ * signals that there are events available via the wakeup IRQ. -+ * This driver is responsible for calling back to the EC to -+ * release these events one-by-one. -+ * -+ * This IRQ should not cause a full system resume by its own. -+ * Instead, events should be handled by their respective subsystem -+ * drivers, which in turn should signal whether a full system -+ * resume should be performed. -+ * -+ * TODO: Send GPIO callback command repeatedly to EC until callback -+ * returns 0x00. Return flag of callback is "has more events". -+ * Each time the command is sent, one event is "released". Once -+ * all events have been released (return = 0x00), the GPIO is -+ * re-armed. Detect wakeup events during this process, go back to -+ * sleep if no wakeup event has been received. -+ */ -+ -+ return IRQ_HANDLED; -+} -+ -+/** -+ * ssam_irq_setup() - Set up SAM EC wakeup-GPIO interrupt. -+ * @ctrl: The controller for which the IRQ should be set up. -+ * -+ * Set up an IRQ for the wakeup-GPIO pin of the SAM EC. This IRQ can be used -+ * to wake the device from a low power state. -+ * -+ * Note that this IRQ can only be triggered while the EC is in the display-off -+ * state. In this state, events are not sent to the host in the usual way. -+ * Instead the wakeup-GPIO gets pulled to "high" as long as there are pending -+ * events and these events need to be released one-by-one via the GPIO -+ * callback request, either until there are no events left and the GPIO is -+ * reset, or all at once by transitioning the EC out of the display-off state, -+ * which will also clear the GPIO. -+ * -+ * Not all events, however, should trigger a full system wakeup. Instead the -+ * driver should, if necessary, inspect and forward each event to the -+ * corresponding subsystem, which in turn should decide if the system needs to -+ * be woken up. This logic has not been implemented yet, thus wakeup by this -+ * IRQ should be disabled by default to avoid spurious wake-ups, caused, for -+ * example, by the remaining battery percentage changing. Refer to comments in -+ * this function and comments in the corresponding IRQ handler for more -+ * details on how this should be implemented. -+ * -+ * See also ssam_ctrl_notif_display_off() and ssam_ctrl_notif_display_off() -+ * for functions to transition the EC into and out of the display-off state as -+ * well as more details on it. -+ * -+ * The IRQ is disabled by default and has to be enabled before it can wake up -+ * the device from suspend via ssam_irq_arm_for_wakeup(). On teardown, the IRQ -+ * should be freed via ssam_irq_free(). -+ */ -+int ssam_irq_setup(struct ssam_controller *ctrl) -+{ -+ struct device *dev = ssam_controller_device(ctrl); -+ struct gpio_desc *gpiod; -+ int irq; -+ int status; -+ -+ /* -+ * The actual GPIO interrupt is declared in ACPI as TRIGGER_HIGH. -+ * However, the GPIO line only gets reset by sending the GPIO callback -+ * command to SAM (or alternatively the display-on notification). As -+ * proper handling for this interrupt is not implemented yet, leaving -+ * the IRQ at TRIGGER_HIGH would cause an IRQ storm (as the callback -+ * never gets sent and thus the line never gets reset). To avoid this, -+ * mark the IRQ as TRIGGER_RISING for now, only creating a single -+ * interrupt, and let the SAM resume callback during the controller -+ * resume process clear it. -+ */ -+ const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING; -+ -+ gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS); -+ if (IS_ERR(gpiod)) -+ return PTR_ERR(gpiod); -+ -+ irq = gpiod_to_irq(gpiod); -+ gpiod_put(gpiod); -+ -+ if (irq < 0) -+ return irq; -+ -+ status = request_threaded_irq(irq, NULL, ssam_irq_handle, irqf, -+ "ssam_wakeup", ctrl); -+ if (status) -+ return status; -+ -+ ctrl->irq.num = irq; -+ disable_irq(ctrl->irq.num); -+ return 0; -+} -+ -+/** -+ * ssam_irq_free() - Free SAM EC wakeup-GPIO interrupt. -+ * @ctrl: The controller for which the IRQ should be freed. -+ * -+ * Free the wakeup-GPIO IRQ previously set-up via ssam_irq_setup(). -+ */ -+void ssam_irq_free(struct ssam_controller *ctrl) -+{ -+ free_irq(ctrl->irq.num, ctrl); -+ ctrl->irq.num = -1; -+} -+ -+/** -+ * ssam_irq_arm_for_wakeup() - Arm the EC IRQ for wakeup, if enabled. -+ * @ctrl: The controller for which the IRQ should be armed. -+ * -+ * Sets up the IRQ so that it can be used to wake the device. Specifically, -+ * this function enables the irq and then, if the device is allowed to wake up -+ * the system, calls enable_irq_wake(). See ssam_irq_disarm_wakeup() for the -+ * corresponding function to disable the IRQ. -+ * -+ * This function is intended to arm the IRQ before entering S2idle suspend. -+ * -+ * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must -+ * be balanced. -+ */ -+int ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl) -+{ -+ struct device *dev = ssam_controller_device(ctrl); -+ int status; -+ -+ enable_irq(ctrl->irq.num); -+ if (device_may_wakeup(dev)) { -+ status = enable_irq_wake(ctrl->irq.num); -+ if (status) { -+ ssam_err(ctrl, "failed to enable wake IRQ: %d\n", status); -+ disable_irq(ctrl->irq.num); -+ return status; -+ } -+ -+ ctrl->irq.wakeup_enabled = true; -+ } else { -+ ctrl->irq.wakeup_enabled = false; -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_irq_disarm_wakeup() - Disarm the wakeup IRQ. -+ * @ctrl: The controller for which the IRQ should be disarmed. -+ * -+ * Disarm the IRQ previously set up for wake via ssam_irq_arm_for_wakeup(). -+ * -+ * This function is intended to disarm the IRQ after exiting S2idle suspend. -+ * -+ * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must -+ * be balanced. -+ */ -+void ssam_irq_disarm_wakeup(struct ssam_controller *ctrl) -+{ -+ int status; -+ -+ if (ctrl->irq.wakeup_enabled) { -+ status = disable_irq_wake(ctrl->irq.num); -+ if (status) -+ ssam_err(ctrl, "failed to disable wake IRQ: %d\n", status); -+ -+ ctrl->irq.wakeup_enabled = false; -+ } -+ disable_irq(ctrl->irq.num); -+} -diff --git a/drivers/platform/x86/surface_aggregator/controller.h b/drivers/platform/x86/surface_aggregator/controller.h -new file mode 100644 -index 000000000000..a0963c3562ff ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/controller.h -@@ -0,0 +1,285 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Main SSAM/SSH controller structure and functionality. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_CONTROLLER_H -+#define _SURFACE_AGGREGATOR_CONTROLLER_H -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "ssh_request_layer.h" -+ -+ -+/* -- Safe counters. -------------------------------------------------------- */ -+ -+/** -+ * struct ssh_seq_counter - Safe counter for SSH sequence IDs. -+ * @value: The current counter value. -+ */ -+struct ssh_seq_counter { -+ u8 value; -+}; -+ -+/** -+ * struct ssh_rqid_counter - Safe counter for SSH request IDs. -+ * @value: The current counter value. -+ */ -+struct ssh_rqid_counter { -+ u16 value; -+}; -+ -+ -+/* -- Event/notification system. -------------------------------------------- */ -+ -+/** -+ * struct ssam_nf_head - Notifier head for SSAM events. -+ * @srcu: The SRCU struct for synchronization. -+ * @head: List-head for notifier blocks registered under this head. -+ */ -+struct ssam_nf_head { -+ struct srcu_struct srcu; -+ struct list_head head; -+}; -+ -+/** -+ * struct ssam_nf - Notifier callback- and activation-registry for SSAM events. -+ * @lock: Lock guarding (de-)registration of notifier blocks. Note: This -+ * lock does not need to be held for notifier calls, only -+ * registration and deregistration. -+ * @refcount: The root of the RB-tree used for reference-counting enabled -+ * events/notifications. -+ * @head: The list of notifier heads for event/notification callbacks. -+ */ -+struct ssam_nf { -+ struct mutex lock; -+ struct rb_root refcount; -+ struct ssam_nf_head head[SSH_NUM_EVENTS]; -+}; -+ -+ -+/* -- Event/async request completion system. -------------------------------- */ -+ -+struct ssam_cplt; -+ -+/** -+ * struct ssam_event_item - Struct for event queuing and completion. -+ * @node: The node in the queue. -+ * @rqid: The request ID of the event. -+ * @ops: Instance specific functions. -+ * @ops.free: Callback for freeing this event item. -+ * @event: Actual event data. -+ */ -+struct ssam_event_item { -+ struct list_head node; -+ u16 rqid; -+ -+ struct { -+ void (*free)(struct ssam_event_item *event); -+ } ops; -+ -+ struct ssam_event event; /* must be last */ -+}; -+ -+/** -+ * struct ssam_event_queue - Queue for completing received events. -+ * @cplt: Reference to the completion system on which this queue is active. -+ * @lock: The lock for any operation on the queue. -+ * @head: The list-head of the queue. -+ * @work: The &struct work_struct performing completion work for this queue. -+ */ -+struct ssam_event_queue { -+ struct ssam_cplt *cplt; -+ -+ spinlock_t lock; -+ struct list_head head; -+ struct work_struct work; -+}; -+ -+/** -+ * struct ssam_event_target - Set of queues for a single SSH target ID. -+ * @queue: The array of queues, one queue per event ID. -+ */ -+struct ssam_event_target { -+ struct ssam_event_queue queue[SSH_NUM_EVENTS]; -+}; -+ -+/** -+ * struct ssam_cplt - SSAM event/async request completion system. -+ * @dev: The device with which this system is associated. Only used -+ * for logging. -+ * @wq: The &struct workqueue_struct on which all completion work -+ * items are queued. -+ * @event: Event completion management. -+ * @event.target: Array of &struct ssam_event_target, one for each target. -+ * @event.notif: Notifier callbacks and event activation reference counting. -+ */ -+struct ssam_cplt { -+ struct device *dev; -+ struct workqueue_struct *wq; -+ -+ struct { -+ struct ssam_event_target target[SSH_NUM_TARGETS]; -+ struct ssam_nf notif; -+ } event; -+}; -+ -+ -+/* -- Main SSAM device structures. ------------------------------------------ */ -+ -+/** -+ * enum ssam_controller_state - State values for &struct ssam_controller. -+ * @SSAM_CONTROLLER_UNINITIALIZED: -+ * The controller has not been initialized yet or has been deinitialized. -+ * @SSAM_CONTROLLER_INITIALIZED: -+ * The controller is initialized, but has not been started yet. -+ * @SSAM_CONTROLLER_STARTED: -+ * The controller has been started and is ready to use. -+ * @SSAM_CONTROLLER_STOPPED: -+ * The controller has been stopped. -+ * @SSAM_CONTROLLER_SUSPENDED: -+ * The controller has been suspended. -+ */ -+enum ssam_controller_state { -+ SSAM_CONTROLLER_UNINITIALIZED, -+ SSAM_CONTROLLER_INITIALIZED, -+ SSAM_CONTROLLER_STARTED, -+ SSAM_CONTROLLER_STOPPED, -+ SSAM_CONTROLLER_SUSPENDED, -+}; -+ -+/** -+ * struct ssam_controller_caps - Controller device capabilities. -+ * @ssh_power_profile: SSH power profile. -+ * @ssh_buffer_size: SSH driver UART buffer size. -+ * @screen_on_sleep_idle_timeout: SAM UART screen-on sleep idle timeout. -+ * @screen_off_sleep_idle_timeout: SAM UART screen-off sleep idle timeout. -+ * @d3_closes_handle: SAM closes UART handle in D3. -+ * -+ * Controller and SSH device capabilities found in ACPI. -+ */ -+struct ssam_controller_caps { -+ u32 ssh_power_profile; -+ u32 ssh_buffer_size; -+ u32 screen_on_sleep_idle_timeout; -+ u32 screen_off_sleep_idle_timeout; -+ u32 d3_closes_handle:1; -+}; -+ -+/** -+ * struct ssam_controller - SSAM controller device. -+ * @kref: Reference count of the controller. -+ * @lock: Main lock for the controller, used to guard state changes. -+ * @state: Controller state. -+ * @rtl: Request transport layer for SSH I/O. -+ * @cplt: Completion system for SSH/SSAM events and asynchronous requests. -+ * @counter: Safe SSH message ID counters. -+ * @counter.seq: Sequence ID counter. -+ * @counter.rqid: Request ID counter. -+ * @irq: Wakeup IRQ resources. -+ * @irq.num: The wakeup IRQ number. -+ * @irq.wakeup_enabled: Whether wakeup by IRQ is enabled during suspend. -+ * @caps: The controller device capabilities. -+ */ -+struct ssam_controller { -+ struct kref kref; -+ -+ struct rw_semaphore lock; -+ enum ssam_controller_state state; -+ -+ struct ssh_rtl rtl; -+ struct ssam_cplt cplt; -+ -+ struct { -+ struct ssh_seq_counter seq; -+ struct ssh_rqid_counter rqid; -+ } counter; -+ -+ struct { -+ int num; -+ bool wakeup_enabled; -+ } irq; -+ -+ struct ssam_controller_caps caps; -+}; -+ -+#define to_ssam_controller(ptr, member) \ -+ container_of(ptr, struct ssam_controller, member) -+ -+#define ssam_dbg(ctrl, fmt, ...) rtl_dbg(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+#define ssam_info(ctrl, fmt, ...) rtl_info(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+#define ssam_warn(ctrl, fmt, ...) rtl_warn(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+#define ssam_err(ctrl, fmt, ...) rtl_err(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+ -+/** -+ * ssam_controller_receive_buf() - Provide input-data to the controller. -+ * @ctrl: The controller. -+ * @buf: The input buffer. -+ * @n: The number of bytes in the input buffer. -+ * -+ * Provide input data to be evaluated by the controller, which has been -+ * received via the lower-level transport. -+ * -+ * Return: Returns the number of bytes consumed, or, if the packet transport -+ * layer of the controller has been shut down, %-ESHUTDOWN. -+ */ -+static inline -+int ssam_controller_receive_buf(struct ssam_controller *ctrl, -+ const unsigned char *buf, size_t n) -+{ -+ return ssh_ptl_rx_rcvbuf(&ctrl->rtl.ptl, buf, n); -+} -+ -+/** -+ * ssam_controller_write_wakeup() - Notify the controller that the underlying -+ * device has space available for data to be written. -+ * @ctrl: The controller. -+ */ -+static inline void ssam_controller_write_wakeup(struct ssam_controller *ctrl) -+{ -+ ssh_ptl_tx_wakeup_transfer(&ctrl->rtl.ptl); -+} -+ -+int ssam_controller_init(struct ssam_controller *ctrl, struct serdev_device *s); -+int ssam_controller_start(struct ssam_controller *ctrl); -+void ssam_controller_shutdown(struct ssam_controller *ctrl); -+void ssam_controller_destroy(struct ssam_controller *ctrl); -+ -+int ssam_notifier_disable_registered(struct ssam_controller *ctrl); -+void ssam_notifier_restore_registered(struct ssam_controller *ctrl); -+ -+int ssam_irq_setup(struct ssam_controller *ctrl); -+void ssam_irq_free(struct ssam_controller *ctrl); -+int ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl); -+void ssam_irq_disarm_wakeup(struct ssam_controller *ctrl); -+ -+void ssam_controller_lock(struct ssam_controller *c); -+void ssam_controller_unlock(struct ssam_controller *c); -+ -+int ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version); -+int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl); -+int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl); -+int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl); -+int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl); -+ -+int ssam_controller_suspend(struct ssam_controller *ctrl); -+int ssam_controller_resume(struct ssam_controller *ctrl); -+ -+int ssam_event_item_cache_init(void); -+void ssam_event_item_cache_destroy(void); -+ -+#endif /* _SURFACE_AGGREGATOR_CONTROLLER_H */ -diff --git a/drivers/platform/x86/surface_aggregator/core.c b/drivers/platform/x86/surface_aggregator/core.c -new file mode 100644 -index 000000000000..698be0e24e9e ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/core.c -@@ -0,0 +1,850 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface Serial Hub (SSH) driver for communication with the Surface/System -+ * Aggregator Module (SSAM/SAM). -+ * -+ * Provides access to a SAM-over-SSH connected EC via a controller device. -+ * Handles communication via requests as well as enabling, disabling, and -+ * relaying of events. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "bus.h" -+#include "controller.h" -+ -+#define CREATE_TRACE_POINTS -+#include "trace.h" -+ -+ -+/* -- Static controller reference. ------------------------------------------ */ -+ -+/* -+ * Main controller reference. The corresponding lock must be held while -+ * accessing (reading/writing) the reference. -+ */ -+static struct ssam_controller *__ssam_controller; -+static DEFINE_SPINLOCK(__ssam_controller_lock); -+ -+/** -+ * ssam_get_controller() - Get reference to SSAM controller. -+ * -+ * Returns a reference to the SSAM controller of the system or %NULL if there -+ * is none, it hasn't been set up yet, or it has already been unregistered. -+ * This function automatically increments the reference count of the -+ * controller, thus the calling party must ensure that ssam_controller_put() -+ * is called when it doesn't need the controller any more. -+ */ -+struct ssam_controller *ssam_get_controller(void) -+{ -+ struct ssam_controller *ctrl; -+ -+ spin_lock(&__ssam_controller_lock); -+ -+ ctrl = __ssam_controller; -+ if (!ctrl) -+ goto out; -+ -+ if (WARN_ON(!kref_get_unless_zero(&ctrl->kref))) -+ ctrl = NULL; -+ -+out: -+ spin_unlock(&__ssam_controller_lock); -+ return ctrl; -+} -+EXPORT_SYMBOL_GPL(ssam_get_controller); -+ -+/** -+ * ssam_try_set_controller() - Try to set the main controller reference. -+ * @ctrl: The controller to which the reference should point. -+ * -+ * Set the main controller reference to the given pointer if the reference -+ * hasn't been set already. -+ * -+ * Return: Returns zero on success or %-EEXIST if the reference has already -+ * been set. -+ */ -+static int ssam_try_set_controller(struct ssam_controller *ctrl) -+{ -+ int status = 0; -+ -+ spin_lock(&__ssam_controller_lock); -+ if (!__ssam_controller) -+ __ssam_controller = ctrl; -+ else -+ status = -EEXIST; -+ spin_unlock(&__ssam_controller_lock); -+ -+ return status; -+} -+ -+/** -+ * ssam_clear_controller() - Remove/clear the main controller reference. -+ * -+ * Clears the main controller reference, i.e. sets it to %NULL. This function -+ * should be called before the controller is shut down. -+ */ -+static void ssam_clear_controller(void) -+{ -+ spin_lock(&__ssam_controller_lock); -+ __ssam_controller = NULL; -+ spin_unlock(&__ssam_controller_lock); -+} -+ -+/** -+ * ssam_client_link() - Link an arbitrary client device to the controller. -+ * @c: The controller to link to. -+ * @client: The client device. -+ * -+ * Link an arbitrary client device to the controller by creating a device link -+ * between it as consumer and the controller device as provider. This function -+ * can be used for non-SSAM devices (or SSAM devices not registered as child -+ * under the controller) to guarantee that the controller is valid for as long -+ * as the driver of the client device is bound, and that proper suspend and -+ * resume ordering is guaranteed. -+ * -+ * The device link does not have to be destructed manually. It is removed -+ * automatically once the driver of the client device unbinds. -+ * -+ * Return: Returns zero on success, %-ENODEV if the controller is not ready or -+ * going to be removed soon, or %-ENOMEM if the device link could not be -+ * created for other reasons. -+ */ -+int ssam_client_link(struct ssam_controller *c, struct device *client) -+{ -+ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; -+ struct device_link *link; -+ struct device *ctrldev; -+ -+ ssam_controller_statelock(c); -+ -+ if (c->state != SSAM_CONTROLLER_STARTED) { -+ ssam_controller_stateunlock(c); -+ return -ENODEV; -+ } -+ -+ ctrldev = ssam_controller_device(c); -+ if (!ctrldev) { -+ ssam_controller_stateunlock(c); -+ return -ENODEV; -+ } -+ -+ link = device_link_add(client, ctrldev, flags); -+ if (!link) { -+ ssam_controller_stateunlock(c); -+ return -ENOMEM; -+ } -+ -+ /* -+ * Return -ENODEV if supplier driver is on its way to be removed. In -+ * this case, the controller won't be around for much longer and the -+ * device link is not going to save us any more, as unbinding is -+ * already in progress. -+ */ -+ if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) { -+ ssam_controller_stateunlock(c); -+ return -ENODEV; -+ } -+ -+ ssam_controller_stateunlock(c); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_client_link); -+ -+/** -+ * ssam_client_bind() - Bind an arbitrary client device to the controller. -+ * @client: The client device. -+ * -+ * Link an arbitrary client device to the controller by creating a device link -+ * between it as consumer and the main controller device as provider. This -+ * function can be used for non-SSAM devices to guarantee that the controller -+ * returned by this function is valid for as long as the driver of the client -+ * device is bound, and that proper suspend and resume ordering is guaranteed. -+ * -+ * This function does essentially the same as ssam_client_link(), except that -+ * it first fetches the main controller reference, then creates the link, and -+ * finally returns this reference. Note that this function does not increment -+ * the reference counter of the controller, as, due to the link, the -+ * controller lifetime is assured as long as the driver of the client device -+ * is bound. -+ * -+ * It is not valid to use the controller reference obtained by this method -+ * outside of the driver bound to the client device at the time of calling -+ * this function, without first incrementing the reference count of the -+ * controller via ssam_controller_get(). Even after doing this, care must be -+ * taken that requests are only submitted and notifiers are only -+ * (un-)registered when the controller is active and not suspended. In other -+ * words: The device link only lives as long as the client driver is bound and -+ * any guarantees enforced by this link (e.g. active controller state) can -+ * only be relied upon as long as this link exists and may need to be enforced -+ * in other ways afterwards. -+ * -+ * The created device link does not have to be destructed manually. It is -+ * removed automatically once the driver of the client device unbinds. -+ * -+ * Return: Returns the controller on success, an error pointer with %-ENODEV -+ * if the controller is not present, not ready or going to be removed soon, or -+ * %-ENOMEM if the device link could not be created for other reasons. -+ */ -+struct ssam_controller *ssam_client_bind(struct device *client) -+{ -+ struct ssam_controller *c; -+ int status; -+ -+ c = ssam_get_controller(); -+ if (!c) -+ return ERR_PTR(-ENODEV); -+ -+ status = ssam_client_link(c, client); -+ -+ /* -+ * Note that we can drop our controller reference in both success and -+ * failure cases: On success, we have bound the controller lifetime -+ * inherently to the client driver lifetime, i.e. it the controller is -+ * now guaranteed to outlive the client driver. On failure, we're not -+ * going to use the controller any more. -+ */ -+ ssam_controller_put(c); -+ -+ return status >= 0 ? c : ERR_PTR(status); -+} -+EXPORT_SYMBOL_GPL(ssam_client_bind); -+ -+ -+/* -- Glue layer (serdev_device -> ssam_controller). ------------------------ */ -+ -+static int ssam_receive_buf(struct serdev_device *dev, const unsigned char *buf, -+ size_t n) -+{ -+ struct ssam_controller *ctrl; -+ -+ ctrl = serdev_device_get_drvdata(dev); -+ return ssam_controller_receive_buf(ctrl, buf, n); -+} -+ -+static void ssam_write_wakeup(struct serdev_device *dev) -+{ -+ ssam_controller_write_wakeup(serdev_device_get_drvdata(dev)); -+} -+ -+static const struct serdev_device_ops ssam_serdev_ops = { -+ .receive_buf = ssam_receive_buf, -+ .write_wakeup = ssam_write_wakeup, -+}; -+ -+ -+/* -- SysFS and misc. ------------------------------------------------------- */ -+ -+static int ssam_log_firmware_version(struct ssam_controller *ctrl) -+{ -+ u32 version, a, b, c; -+ int status; -+ -+ status = ssam_get_firmware_version(ctrl, &version); -+ if (status) -+ return status; -+ -+ a = (version >> 24) & 0xff; -+ b = ((version >> 8) & 0xffff); -+ c = version & 0xff; -+ -+ ssam_info(ctrl, "SAM firmware version: %u.%u.%u\n", a, b, c); -+ return 0; -+} -+ -+static ssize_t firmware_version_show(struct device *dev, -+ struct device_attribute *attr, char *buf) -+{ -+ struct ssam_controller *ctrl = dev_get_drvdata(dev); -+ u32 version, a, b, c; -+ int status; -+ -+ status = ssam_get_firmware_version(ctrl, &version); -+ if (status < 0) -+ return status; -+ -+ a = (version >> 24) & 0xff; -+ b = ((version >> 8) & 0xffff); -+ c = version & 0xff; -+ -+ return scnprintf(buf, PAGE_SIZE, "%u.%u.%u\n", a, b, c); -+} -+static DEVICE_ATTR_RO(firmware_version); -+ -+static struct attribute *ssam_sam_attrs[] = { -+ &dev_attr_firmware_version.attr, -+ NULL -+}; -+ -+static const struct attribute_group ssam_sam_group = { -+ .name = "sam", -+ .attrs = ssam_sam_attrs, -+}; -+ -+ -+/* -- ACPI based device setup. ---------------------------------------------- */ -+ -+static acpi_status ssam_serdev_setup_via_acpi_crs(struct acpi_resource *rsc, -+ void *ctx) -+{ -+ struct serdev_device *serdev = ctx; -+ struct acpi_resource_common_serialbus *serial; -+ struct acpi_resource_uart_serialbus *uart; -+ bool flow_control; -+ int status = 0; -+ -+ if (rsc->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) -+ return AE_OK; -+ -+ serial = &rsc->data.common_serial_bus; -+ if (serial->type != ACPI_RESOURCE_SERIAL_TYPE_UART) -+ return AE_OK; -+ -+ uart = &rsc->data.uart_serial_bus; -+ -+ /* Set up serdev device. */ -+ serdev_device_set_baudrate(serdev, uart->default_baud_rate); -+ -+ /* serdev currently only supports RTSCTS flow control. */ -+ if (uart->flow_control & (~((u8)ACPI_UART_FLOW_CONTROL_HW))) { -+ dev_warn(&serdev->dev, "setup: unsupported flow control (value: %#04x)\n", -+ uart->flow_control); -+ } -+ -+ /* Set RTSCTS flow control. */ -+ flow_control = uart->flow_control & ACPI_UART_FLOW_CONTROL_HW; -+ serdev_device_set_flow_control(serdev, flow_control); -+ -+ /* serdev currently only supports EVEN/ODD parity. */ -+ switch (uart->parity) { -+ case ACPI_UART_PARITY_NONE: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); -+ break; -+ case ACPI_UART_PARITY_EVEN: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_EVEN); -+ break; -+ case ACPI_UART_PARITY_ODD: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_ODD); -+ break; -+ default: -+ dev_warn(&serdev->dev, "setup: unsupported parity (value: %#04x)\n", -+ uart->parity); -+ break; -+ } -+ -+ if (status) { -+ dev_err(&serdev->dev, "setup: failed to set parity (value: %#04x, error: %d)\n", -+ uart->parity, status); -+ return AE_ERROR; -+ } -+ -+ /* We've found the resource and are done. */ -+ return AE_CTRL_TERMINATE; -+} -+ -+static acpi_status ssam_serdev_setup_via_acpi(acpi_handle handle, -+ struct serdev_device *serdev) -+{ -+ return acpi_walk_resources(handle, METHOD_NAME__CRS, -+ ssam_serdev_setup_via_acpi_crs, serdev); -+} -+ -+ -+/* -- Power management. ----------------------------------------------------- */ -+ -+static void ssam_serial_hub_shutdown(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Try to disable notifiers, signal display-off and D0-exit, ignore any -+ * errors. -+ * -+ * Note: It has not been established yet if this is actually -+ * necessary/useful for shutdown. -+ */ -+ -+ status = ssam_notifier_disable_registered(c); -+ if (status) { -+ ssam_err(c, "pm: failed to disable notifiers for shutdown: %d\n", -+ status); -+ } -+ -+ status = ssam_ctrl_notif_display_off(c); -+ if (status) -+ ssam_err(c, "pm: display-off notification failed: %d\n", status); -+ -+ status = ssam_ctrl_notif_d0_exit(c); -+ if (status) -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+} -+ -+#ifdef CONFIG_PM_SLEEP -+ -+static int ssam_serial_hub_pm_prepare(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Try to signal display-off, This will quiesce events. -+ * -+ * Note: Signaling display-off/display-on should normally be done from -+ * some sort of display state notifier. As that is not available, -+ * signal it here. -+ */ -+ -+ status = ssam_ctrl_notif_display_off(c); -+ if (status) -+ ssam_err(c, "pm: display-off notification failed: %d\n", status); -+ -+ return status; -+} -+ -+static void ssam_serial_hub_pm_complete(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Try to signal display-on. This will restore events. -+ * -+ * Note: Signaling display-off/display-on should normally be done from -+ * some sort of display state notifier. As that is not available, -+ * signal it here. -+ */ -+ -+ status = ssam_ctrl_notif_display_on(c); -+ if (status) -+ ssam_err(c, "pm: display-on notification failed: %d\n", status); -+} -+ -+static int ssam_serial_hub_pm_suspend(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Try to signal D0-exit, enable IRQ wakeup if specified. Abort on -+ * error. -+ */ -+ -+ status = ssam_ctrl_notif_d0_exit(c); -+ if (status) { -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+ goto err_notif; -+ } -+ -+ status = ssam_irq_arm_for_wakeup(c); -+ if (status) -+ goto err_irq; -+ -+ WARN_ON(ssam_controller_suspend(c)); -+ return 0; -+ -+err_irq: -+ ssam_ctrl_notif_d0_entry(c); -+err_notif: -+ ssam_ctrl_notif_display_on(c); -+ return status; -+} -+ -+static int ssam_serial_hub_pm_resume(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ WARN_ON(ssam_controller_resume(c)); -+ -+ /* -+ * Try to disable IRQ wakeup (if specified) and signal D0-entry. In -+ * case of errors, log them and try to restore normal operation state -+ * as far as possible. -+ * -+ * Note: Signaling display-off/display-on should normally be done from -+ * some sort of display state notifier. As that is not available, -+ * signal it here. -+ */ -+ -+ ssam_irq_disarm_wakeup(c); -+ -+ status = ssam_ctrl_notif_d0_entry(c); -+ if (status) -+ ssam_err(c, "pm: D0-entry notification failed: %d\n", status); -+ -+ return 0; -+} -+ -+static int ssam_serial_hub_pm_freeze(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * During hibernation image creation, we only have to ensure that the -+ * EC doesn't send us any events. This is done via the display-off -+ * and D0-exit notifications. Note that this sets up the wakeup IRQ -+ * on the EC side, however, we have disabled it by default on our side -+ * and won't enable it here. -+ * -+ * See ssam_serial_hub_poweroff() for more details on the hibernation -+ * process. -+ */ -+ -+ status = ssam_ctrl_notif_d0_exit(c); -+ if (status) { -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+ ssam_ctrl_notif_display_on(c); -+ return status; -+ } -+ -+ WARN_ON(ssam_controller_suspend(c)); -+ return 0; -+} -+ -+static int ssam_serial_hub_pm_thaw(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ WARN_ON(ssam_controller_resume(c)); -+ -+ status = ssam_ctrl_notif_d0_entry(c); -+ if (status) -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+ -+ return status; -+} -+ -+static int ssam_serial_hub_pm_poweroff(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * When entering hibernation and powering off the system, the EC, at -+ * least on some models, may disable events. Without us taking care of -+ * that, this leads to events not being enabled/restored when the -+ * system resumes from hibernation, resulting SAM-HID subsystem devices -+ * (i.e. keyboard, touchpad) not working, AC-plug/AC-unplug events being -+ * gone, etc. -+ * -+ * To avoid these issues, we disable all registered events here (this is -+ * likely not actually required) and restore them during the drivers PM -+ * restore callback. -+ * -+ * Wakeup from the EC interrupt is not supported during hibernation, -+ * so don't arm the IRQ here. -+ */ -+ -+ status = ssam_notifier_disable_registered(c); -+ if (status) { -+ ssam_err(c, "pm: failed to disable notifiers for hibernation: %d\n", -+ status); -+ return status; -+ } -+ -+ status = ssam_ctrl_notif_d0_exit(c); -+ if (status) { -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+ ssam_notifier_restore_registered(c); -+ return status; -+ } -+ -+ WARN_ON(ssam_controller_suspend(c)); -+ return 0; -+} -+ -+static int ssam_serial_hub_pm_restore(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Ignore but log errors, try to restore state as much as possible in -+ * case of failures. See ssam_serial_hub_poweroff() for more details on -+ * the hibernation process. -+ */ -+ -+ WARN_ON(ssam_controller_resume(c)); -+ -+ status = ssam_ctrl_notif_d0_entry(c); -+ if (status) -+ ssam_err(c, "pm: D0-entry notification failed: %d\n", status); -+ -+ ssam_notifier_restore_registered(c); -+ return 0; -+} -+ -+static const struct dev_pm_ops ssam_serial_hub_pm_ops = { -+ .prepare = ssam_serial_hub_pm_prepare, -+ .complete = ssam_serial_hub_pm_complete, -+ .suspend = ssam_serial_hub_pm_suspend, -+ .resume = ssam_serial_hub_pm_resume, -+ .freeze = ssam_serial_hub_pm_freeze, -+ .thaw = ssam_serial_hub_pm_thaw, -+ .poweroff = ssam_serial_hub_pm_poweroff, -+ .restore = ssam_serial_hub_pm_restore, -+}; -+ -+#else /* CONFIG_PM_SLEEP */ -+ -+static const struct dev_pm_ops ssam_serial_hub_pm_ops = { }; -+ -+#endif /* CONFIG_PM_SLEEP */ -+ -+ -+/* -- Device/driver setup. -------------------------------------------------- */ -+ -+static const struct acpi_gpio_params gpio_ssam_wakeup_int = { 0, 0, false }; -+static const struct acpi_gpio_params gpio_ssam_wakeup = { 1, 0, false }; -+ -+static const struct acpi_gpio_mapping ssam_acpi_gpios[] = { -+ { "ssam_wakeup-int-gpio", &gpio_ssam_wakeup_int, 1 }, -+ { "ssam_wakeup-gpio", &gpio_ssam_wakeup, 1 }, -+ { }, -+}; -+ -+static int ssam_serial_hub_probe(struct serdev_device *serdev) -+{ -+ struct ssam_controller *ctrl; -+ acpi_handle *ssh = ACPI_HANDLE(&serdev->dev); -+ acpi_status astatus; -+ int status; -+ -+ if (gpiod_count(&serdev->dev, NULL) < 0) -+ return -ENODEV; -+ -+ status = devm_acpi_dev_add_driver_gpios(&serdev->dev, ssam_acpi_gpios); -+ if (status) -+ return status; -+ -+ /* Allocate controller. */ -+ ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); -+ if (!ctrl) -+ return -ENOMEM; -+ -+ /* Initialize controller. */ -+ status = ssam_controller_init(ctrl, serdev); -+ if (status) -+ goto err_ctrl_init; -+ -+ ssam_controller_lock(ctrl); -+ -+ /* Set up serdev device. */ -+ serdev_device_set_drvdata(serdev, ctrl); -+ serdev_device_set_client_ops(serdev, &ssam_serdev_ops); -+ status = serdev_device_open(serdev); -+ if (status) -+ goto err_devopen; -+ -+ astatus = ssam_serdev_setup_via_acpi(ssh, serdev); -+ if (ACPI_FAILURE(astatus)) { -+ status = -ENXIO; -+ goto err_devinit; -+ } -+ -+ /* Start controller. */ -+ status = ssam_controller_start(ctrl); -+ if (status) -+ goto err_devinit; -+ -+ ssam_controller_unlock(ctrl); -+ -+ /* -+ * Initial SAM requests: Log version and notify default/init power -+ * states. -+ */ -+ status = ssam_log_firmware_version(ctrl); -+ if (status) -+ goto err_initrq; -+ -+ status = ssam_ctrl_notif_d0_entry(ctrl); -+ if (status) -+ goto err_initrq; -+ -+ status = ssam_ctrl_notif_display_on(ctrl); -+ if (status) -+ goto err_initrq; -+ -+ status = sysfs_create_group(&serdev->dev.kobj, &ssam_sam_group); -+ if (status) -+ goto err_initrq; -+ -+ /* Set up IRQ. */ -+ status = ssam_irq_setup(ctrl); -+ if (status) -+ goto err_irq; -+ -+ /* Finally, set main controller reference. */ -+ status = ssam_try_set_controller(ctrl); -+ if (WARN_ON(status)) /* Currently, we're the only provider. */ -+ goto err_mainref; -+ -+ /* -+ * TODO: The EC can wake up the system via the associated GPIO interrupt -+ * in multiple situations. One of which is the remaining battery -+ * capacity falling below a certain threshold. Normally, we should -+ * use the device_init_wakeup function, however, the EC also seems -+ * to have other reasons for waking up the system and it seems -+ * that Windows has additional checks whether the system should be -+ * resumed. In short, this causes some spurious unwanted wake-ups. -+ * For now let's thus default power/wakeup to false. -+ */ -+ device_set_wakeup_capable(&serdev->dev, true); -+ acpi_walk_dep_device_list(ssh); -+ -+ return 0; -+ -+err_mainref: -+ ssam_irq_free(ctrl); -+err_irq: -+ sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); -+err_initrq: -+ ssam_controller_lock(ctrl); -+ ssam_controller_shutdown(ctrl); -+err_devinit: -+ serdev_device_close(serdev); -+err_devopen: -+ ssam_controller_destroy(ctrl); -+ ssam_controller_unlock(ctrl); -+err_ctrl_init: -+ kfree(ctrl); -+ return status; -+} -+ -+static void ssam_serial_hub_remove(struct serdev_device *serdev) -+{ -+ struct ssam_controller *ctrl = serdev_device_get_drvdata(serdev); -+ int status; -+ -+ /* Clear static reference so that no one else can get a new one. */ -+ ssam_clear_controller(); -+ -+ /* Disable and free IRQ. */ -+ ssam_irq_free(ctrl); -+ -+ sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); -+ ssam_controller_lock(ctrl); -+ -+ /* Remove all client devices. */ -+ ssam_controller_remove_clients(ctrl); -+ -+ /* Act as if suspending to silence events. */ -+ status = ssam_ctrl_notif_display_off(ctrl); -+ if (status) { -+ dev_err(&serdev->dev, "display-off notification failed: %d\n", -+ status); -+ } -+ -+ status = ssam_ctrl_notif_d0_exit(ctrl); -+ if (status) { -+ dev_err(&serdev->dev, "D0-exit notification failed: %d\n", -+ status); -+ } -+ -+ /* Shut down controller and remove serdev device reference from it. */ -+ ssam_controller_shutdown(ctrl); -+ -+ /* Shut down actual transport. */ -+ serdev_device_wait_until_sent(serdev, 0); -+ serdev_device_close(serdev); -+ -+ /* Drop our controller reference. */ -+ ssam_controller_unlock(ctrl); -+ ssam_controller_put(ctrl); -+ -+ device_set_wakeup_capable(&serdev->dev, false); -+} -+ -+static const struct acpi_device_id ssam_serial_hub_match[] = { -+ { "MSHW0084", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, ssam_serial_hub_match); -+ -+static struct serdev_device_driver ssam_serial_hub = { -+ .probe = ssam_serial_hub_probe, -+ .remove = ssam_serial_hub_remove, -+ .driver = { -+ .name = "surface_serial_hub", -+ .acpi_match_table = ssam_serial_hub_match, -+ .pm = &ssam_serial_hub_pm_ops, -+ .shutdown = ssam_serial_hub_shutdown, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+/* -- Module setup. --------------------------------------------------------- */ -+ -+static int __init ssam_core_init(void) -+{ -+ int status; -+ -+ status = ssam_bus_register(); -+ if (status) -+ goto err_bus; -+ -+ status = ssh_ctrl_packet_cache_init(); -+ if (status) -+ goto err_cpkg; -+ -+ status = ssam_event_item_cache_init(); -+ if (status) -+ goto err_evitem; -+ -+ status = serdev_device_driver_register(&ssam_serial_hub); -+ if (status) -+ goto err_register; -+ -+ return 0; -+ -+err_register: -+ ssam_event_item_cache_destroy(); -+err_evitem: -+ ssh_ctrl_packet_cache_destroy(); -+err_cpkg: -+ ssam_bus_unregister(); -+err_bus: -+ return status; -+} -+ -+static void __exit ssam_core_exit(void) -+{ -+ serdev_device_driver_unregister(&ssam_serial_hub); -+ ssam_event_item_cache_destroy(); -+ ssh_ctrl_packet_cache_destroy(); -+ ssam_bus_unregister(); -+} -+ -+/* -+ * Ensure that the driver is loaded late due to some issues with the UART -+ * communication. Specifically, we want to ensure that DMA is ready and being -+ * used. Not using DMA can result in spurious communication failures, -+ * especially during boot, which among other things will result in wrong -+ * battery information (via ACPI _BIX) being displayed. Using a late init_call -+ * instead of the normal module_init gives the DMA subsystem time to -+ * initialize and via that results in a more stable communication, avoiding -+ * such failures. -+ */ -+late_initcall(ssam_core_init); -+module_exit(ssam_core_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Subsystem and Surface Serial Hub driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_aggregator/ssh_msgb.h b/drivers/platform/x86/surface_aggregator/ssh_msgb.h -new file mode 100644 -index 000000000000..e562958ffdf0 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/ssh_msgb.h -@@ -0,0 +1,205 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * SSH message builder functions. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H -+#define _SURFACE_AGGREGATOR_SSH_MSGB_H -+ -+#include -+#include -+ -+#include -+#include -+ -+/** -+ * struct msgbuf - Buffer struct to construct SSH messages. -+ * @begin: Pointer to the beginning of the allocated buffer space. -+ * @end: Pointer to the end (one past last element) of the allocated buffer -+ * space. -+ * @ptr: Pointer to the first free element in the buffer. -+ */ -+struct msgbuf { -+ u8 *begin; -+ u8 *end; -+ u8 *ptr; -+}; -+ -+/** -+ * msgb_init() - Initialize the given message buffer struct. -+ * @msgb: The buffer struct to initialize -+ * @ptr: Pointer to the underlying memory by which the buffer will be backed. -+ * @cap: Size of the underlying memory. -+ * -+ * Initialize the given message buffer struct using the provided memory as -+ * backing. -+ */ -+static inline void msgb_init(struct msgbuf *msgb, u8 *ptr, size_t cap) -+{ -+ msgb->begin = ptr; -+ msgb->end = ptr + cap; -+ msgb->ptr = ptr; -+} -+ -+/** -+ * msgb_bytes_used() - Return the current number of bytes used in the buffer. -+ * @msgb: The message buffer. -+ */ -+static inline size_t msgb_bytes_used(const struct msgbuf *msgb) -+{ -+ return msgb->ptr - msgb->begin; -+} -+ -+static inline void __msgb_push_u8(struct msgbuf *msgb, u8 value) -+{ -+ *msgb->ptr = value; -+ msgb->ptr += sizeof(u8); -+} -+ -+static inline void __msgb_push_u16(struct msgbuf *msgb, u16 value) -+{ -+ put_unaligned_le16(value, msgb->ptr); -+ msgb->ptr += sizeof(u16); -+} -+ -+/** -+ * msgb_push_u16() - Push a u16 value to the buffer. -+ * @msgb: The message buffer. -+ * @value: The value to push to the buffer. -+ */ -+static inline void msgb_push_u16(struct msgbuf *msgb, u16 value) -+{ -+ if (WARN_ON(msgb->ptr + sizeof(u16) > msgb->end)) -+ return; -+ -+ __msgb_push_u16(msgb, value); -+} -+ -+/** -+ * msgb_push_syn() - Push SSH SYN bytes to the buffer. -+ * @msgb: The message buffer. -+ */ -+static inline void msgb_push_syn(struct msgbuf *msgb) -+{ -+ msgb_push_u16(msgb, SSH_MSG_SYN); -+} -+ -+/** -+ * msgb_push_buf() - Push raw data to the buffer. -+ * @msgb: The message buffer. -+ * @buf: The data to push to the buffer. -+ * @len: The length of the data to push to the buffer. -+ */ -+static inline void msgb_push_buf(struct msgbuf *msgb, const u8 *buf, size_t len) -+{ -+ msgb->ptr = memcpy(msgb->ptr, buf, len) + len; -+} -+ -+/** -+ * msgb_push_crc() - Compute CRC and push it to the buffer. -+ * @msgb: The message buffer. -+ * @buf: The data for which the CRC should be computed. -+ * @len: The length of the data for which the CRC should be computed. -+ */ -+static inline void msgb_push_crc(struct msgbuf *msgb, const u8 *buf, size_t len) -+{ -+ msgb_push_u16(msgb, ssh_crc(buf, len)); -+} -+ -+/** -+ * msgb_push_frame() - Push a SSH message frame header to the buffer. -+ * @msgb: The message buffer -+ * @ty: The type of the frame. -+ * @len: The length of the payload of the frame. -+ * @seq: The sequence ID of the frame/packet. -+ */ -+static inline void msgb_push_frame(struct msgbuf *msgb, u8 ty, u16 len, u8 seq) -+{ -+ u8 *const begin = msgb->ptr; -+ -+ if (WARN_ON(msgb->ptr + sizeof(struct ssh_frame) > msgb->end)) -+ return; -+ -+ __msgb_push_u8(msgb, ty); /* Frame type. */ -+ __msgb_push_u16(msgb, len); /* Frame payload length. */ -+ __msgb_push_u8(msgb, seq); /* Frame sequence ID. */ -+ -+ msgb_push_crc(msgb, begin, msgb->ptr - begin); -+} -+ -+/** -+ * msgb_push_ack() - Push a SSH ACK frame to the buffer. -+ * @msgb: The message buffer -+ * @seq: The sequence ID of the frame/packet to be ACKed. -+ */ -+static inline void msgb_push_ack(struct msgbuf *msgb, u8 seq) -+{ -+ /* SYN. */ -+ msgb_push_syn(msgb); -+ -+ /* ACK-type frame + CRC. */ -+ msgb_push_frame(msgb, SSH_FRAME_TYPE_ACK, 0x00, seq); -+ -+ /* Payload CRC (ACK-type frames do not have a payload). */ -+ msgb_push_crc(msgb, msgb->ptr, 0); -+} -+ -+/** -+ * msgb_push_nak() - Push a SSH NAK frame to the buffer. -+ * @msgb: The message buffer -+ */ -+static inline void msgb_push_nak(struct msgbuf *msgb) -+{ -+ /* SYN. */ -+ msgb_push_syn(msgb); -+ -+ /* NAK-type frame + CRC. */ -+ msgb_push_frame(msgb, SSH_FRAME_TYPE_NAK, 0x00, 0x00); -+ -+ /* Payload CRC (ACK-type frames do not have a payload). */ -+ msgb_push_crc(msgb, msgb->ptr, 0); -+} -+ -+/** -+ * msgb_push_cmd() - Push a SSH command frame with payload to the buffer. -+ * @msgb: The message buffer. -+ * @seq: The sequence ID (SEQ) of the frame/packet. -+ * @rqid: The request ID (RQID) of the request contained in the frame. -+ * @rqst: The request to wrap in the frame. -+ */ -+static inline void msgb_push_cmd(struct msgbuf *msgb, u8 seq, u16 rqid, -+ const struct ssam_request *rqst) -+{ -+ const u8 type = SSH_FRAME_TYPE_DATA_SEQ; -+ u8 *cmd; -+ -+ /* SYN. */ -+ msgb_push_syn(msgb); -+ -+ /* Command frame + CRC. */ -+ msgb_push_frame(msgb, type, sizeof(struct ssh_command) + rqst->length, seq); -+ -+ /* Frame payload: Command struct + payload. */ -+ if (WARN_ON(msgb->ptr + sizeof(struct ssh_command) > msgb->end)) -+ return; -+ -+ cmd = msgb->ptr; -+ -+ __msgb_push_u8(msgb, SSH_PLD_TYPE_CMD); /* Payload type. */ -+ __msgb_push_u8(msgb, rqst->target_category); /* Target category. */ -+ __msgb_push_u8(msgb, rqst->target_id); /* Target ID (out). */ -+ __msgb_push_u8(msgb, 0x00); /* Target ID (in). */ -+ __msgb_push_u8(msgb, rqst->instance_id); /* Instance ID. */ -+ __msgb_push_u16(msgb, rqid); /* Request ID. */ -+ __msgb_push_u8(msgb, rqst->command_id); /* Command ID. */ -+ -+ /* Command payload. */ -+ msgb_push_buf(msgb, rqst->payload, rqst->length); -+ -+ /* CRC for command struct + payload. */ -+ msgb_push_crc(msgb, cmd, msgb->ptr - cmd); -+} -+ -+#endif /* _SURFACE_AGGREGATOR_SSH_MSGB_H */ -diff --git a/drivers/platform/x86/surface_aggregator/ssh_packet_layer.c b/drivers/platform/x86/surface_aggregator/ssh_packet_layer.c -new file mode 100644 -index 000000000000..8a06beb39aee ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/ssh_packet_layer.c -@@ -0,0 +1,2074 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * SSH packet transport layer. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "ssh_msgb.h" -+#include "ssh_packet_layer.h" -+#include "ssh_parser.h" -+ -+#include "trace.h" -+ -+/* -+ * To simplify reasoning about the code below, we define a few concepts. The -+ * system below is similar to a state-machine for packets, however, there are -+ * too many states to explicitly write them down. To (somewhat) manage the -+ * states and packets we rely on flags, reference counting, and some simple -+ * concepts. State transitions are triggered by actions. -+ * -+ * >> Actions << -+ * -+ * - submit -+ * - transmission start (process next item in queue) -+ * - transmission finished (guaranteed to never be parallel to transmission -+ * start) -+ * - ACK received -+ * - NAK received (this is equivalent to issuing re-submit for all pending -+ * packets) -+ * - timeout (this is equivalent to re-issuing a submit or canceling) -+ * - cancel (non-pending and pending) -+ * -+ * >> Data Structures, Packet Ownership, General Overview << -+ * -+ * The code below employs two main data structures: The packet queue, -+ * containing all packets scheduled for transmission, and the set of pending -+ * packets, containing all packets awaiting an ACK. -+ * -+ * Shared ownership of a packet is controlled via reference counting. Inside -+ * the transport system are a total of five packet owners: -+ * -+ * - the packet queue, -+ * - the pending set, -+ * - the transmitter thread, -+ * - the receiver thread (via ACKing), and -+ * - the timeout work item. -+ * -+ * Normal operation is as follows: The initial reference of the packet is -+ * obtained by submitting the packet and queuing it. The receiver thread takes -+ * packets from the queue. By doing this, it does not increment the refcount -+ * but takes over the reference (removing it from the queue). If the packet is -+ * sequenced (i.e. needs to be ACKed by the client), the transmitter thread -+ * sets-up the timeout and adds the packet to the pending set before starting -+ * to transmit it. As the timeout is handled by a reaper task, no additional -+ * reference for it is needed. After the transmit is done, the reference held -+ * by the transmitter thread is dropped. If the packet is unsequenced (i.e. -+ * does not need an ACK), the packet is completed by the transmitter thread -+ * before dropping that reference. -+ * -+ * On receival of an ACK, the receiver thread removes and obtains the -+ * reference to the packet from the pending set. The receiver thread will then -+ * complete the packet and drop its reference. -+ * -+ * On receival of a NAK, the receiver thread re-submits all currently pending -+ * packets. -+ * -+ * Packet timeouts are detected by the timeout reaper. This is a task, -+ * scheduled depending on the earliest packet timeout expiration date, -+ * checking all currently pending packets if their timeout has expired. If the -+ * timeout of a packet has expired, it is re-submitted and the number of tries -+ * of this packet is incremented. If this number reaches its limit, the packet -+ * will be completed with a failure. -+ * -+ * On transmission failure (such as repeated packet timeouts), the completion -+ * callback is immediately run by on thread on which the error was detected. -+ * -+ * To ensure that a packet eventually leaves the system it is marked as -+ * "locked" directly before it is going to be completed or when it is -+ * canceled. Marking a packet as "locked" has the effect that passing and -+ * creating new references of the packet is disallowed. This means that the -+ * packet cannot be added to the queue, the pending set, and the timeout, or -+ * be picked up by the transmitter thread or receiver thread. To remove a -+ * packet from the system it has to be marked as locked and subsequently all -+ * references from the data structures (queue, pending) have to be removed. -+ * References held by threads will eventually be dropped automatically as -+ * their execution progresses. -+ * -+ * Note that the packet completion callback is, in case of success and for a -+ * sequenced packet, guaranteed to run on the receiver thread, thus providing -+ * a way to reliably identify responses to the packet. The packet completion -+ * callback is only run once and it does not indicate that the packet has -+ * fully left the system (for this, one should rely on the release method, -+ * triggered when the reference count of the packet reaches zero). In case of -+ * re-submission (and with somewhat unlikely timing), it may be possible that -+ * the packet is being re-transmitted while the completion callback runs. -+ * Completion will occur both on success and internal error, as well as when -+ * the packet is canceled. -+ * -+ * >> Flags << -+ * -+ * Flags are used to indicate the state and progression of a packet. Some flags -+ * have stricter guarantees than other: -+ * -+ * - locked -+ * Indicates if the packet is locked. If the packet is locked, passing and/or -+ * creating additional references to the packet is forbidden. The packet thus -+ * may not be queued, dequeued, or removed or added to the pending set. Note -+ * that the packet state flags may still change (e.g. it may be marked as -+ * ACKed, transmitted, ...). -+ * -+ * - completed -+ * Indicates if the packet completion callback has been executed or is about -+ * to be executed. This flag is used to ensure that the packet completion -+ * callback is only run once. -+ * -+ * - queued -+ * Indicates if a packet is present in the submission queue or not. This flag -+ * must only be modified with the queue lock held, and must be coherent to the -+ * presence of the packet in the queue. -+ * -+ * - pending -+ * Indicates if a packet is present in the set of pending packets or not. -+ * This flag must only be modified with the pending lock held, and must be -+ * coherent to the presence of the packet in the pending set. -+ * -+ * - transmitting -+ * Indicates if the packet is currently transmitting. In case of -+ * re-transmissions, it is only safe to wait on the "transmitted" completion -+ * after this flag has been set. The completion will be set both in success -+ * and error case. -+ * -+ * - transmitted -+ * Indicates if the packet has been transmitted. This flag is not cleared by -+ * the system, thus it indicates the first transmission only. -+ * -+ * - acked -+ * Indicates if the packet has been acknowledged by the client. There are no -+ * other guarantees given. For example, the packet may still be canceled -+ * and/or the completion may be triggered an error even though this bit is -+ * set. Rely on the status provided to the completion callback instead. -+ * -+ * - canceled -+ * Indicates if the packet has been canceled from the outside. There are no -+ * other guarantees given. Specifically, the packet may be completed by -+ * another part of the system before the cancellation attempts to complete it. -+ * -+ * >> General Notes << -+ * -+ * - To avoid deadlocks, if both queue and pending locks are required, the -+ * pending lock must be acquired before the queue lock. -+ * -+ * - The packet priority must be accessed only while holding the queue lock. -+ * -+ * - The packet timestamp must be accessed only while holding the pending -+ * lock. -+ */ -+ -+/* -+ * SSH_PTL_MAX_PACKET_TRIES - Maximum transmission attempts for packet. -+ * -+ * Maximum number of transmission attempts per sequenced packet in case of -+ * time-outs. Must be smaller than 16. If the packet times out after this -+ * amount of tries, the packet will be completed with %-ETIMEDOUT as status -+ * code. -+ */ -+#define SSH_PTL_MAX_PACKET_TRIES 3 -+ -+/* -+ * SSH_PTL_TX_TIMEOUT - Packet transmission timeout. -+ * -+ * Timeout in jiffies for packet transmission via the underlying serial -+ * device. If transmitting the packet takes longer than this timeout, the -+ * packet will be completed with -ETIMEDOUT. It will not be re-submitted. -+ */ -+#define SSH_PTL_TX_TIMEOUT HZ -+ -+/* -+ * SSH_PTL_PACKET_TIMEOUT - Packet response timeout. -+ * -+ * Timeout as ktime_t delta for ACKs. If we have not received an ACK in this -+ * time-frame after starting transmission, the packet will be re-submitted. -+ */ -+#define SSH_PTL_PACKET_TIMEOUT ms_to_ktime(1000) -+ -+/* -+ * SSH_PTL_PACKET_TIMEOUT_RESOLUTION - Packet timeout granularity. -+ * -+ * Time-resolution for timeouts. Should be larger than one jiffy to avoid -+ * direct re-scheduling of reaper work_struct. -+ */ -+#define SSH_PTL_PACKET_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) -+ -+/* -+ * SSH_PTL_MAX_PENDING - Maximum number of pending packets. -+ * -+ * Maximum number of sequenced packets concurrently waiting for an ACK. -+ * Packets marked as blocking will not be transmitted while this limit is -+ * reached. -+ */ -+#define SSH_PTL_MAX_PENDING 1 -+ -+/* -+ * SSH_PTL_RX_BUF_LEN - Evaluation-buffer size in bytes. -+ */ -+#define SSH_PTL_RX_BUF_LEN 4096 -+ -+/* -+ * SSH_PTL_RX_FIFO_LEN - Fifo input-buffer size in bytes. -+ */ -+#define SSH_PTL_RX_FIFO_LEN 4096 -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION -+ -+/** -+ * ssh_ptl_should_drop_ack_packet() - Error injection hook to drop ACK packets. -+ * -+ * Useful to test detection and handling of automated re-transmits by the EC. -+ * Specifically of packets that the EC considers not-ACKed but the driver -+ * already considers ACKed (due to dropped ACK). In this case, the EC -+ * re-transmits the packet-to-be-ACKed and the driver should detect it as -+ * duplicate/already handled. Note that the driver should still send an ACK -+ * for the re-transmitted packet. -+ */ -+static noinline bool ssh_ptl_should_drop_ack_packet(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_ack_packet, TRUE); -+ -+/** -+ * ssh_ptl_should_drop_nak_packet() - Error injection hook to drop NAK packets. -+ * -+ * Useful to test/force automated (timeout-based) re-transmit by the EC. -+ * Specifically, packets that have not reached the driver completely/with valid -+ * checksums. Only useful in combination with receival of (injected) bad data. -+ */ -+static noinline bool ssh_ptl_should_drop_nak_packet(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_nak_packet, TRUE); -+ -+/** -+ * ssh_ptl_should_drop_dsq_packet() - Error injection hook to drop sequenced -+ * data packet. -+ * -+ * Useful to test re-transmit timeout of the driver. If the data packet has not -+ * been ACKed after a certain time, the driver should re-transmit the packet up -+ * to limited number of times defined in SSH_PTL_MAX_PACKET_TRIES. -+ */ -+static noinline bool ssh_ptl_should_drop_dsq_packet(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_dsq_packet, TRUE); -+ -+/** -+ * ssh_ptl_should_fail_write() - Error injection hook to make -+ * serdev_device_write() fail. -+ * -+ * Hook to simulate errors in serdev_device_write when transmitting packets. -+ */ -+static noinline int ssh_ptl_should_fail_write(void) -+{ -+ return 0; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_fail_write, ERRNO); -+ -+/** -+ * ssh_ptl_should_corrupt_tx_data() - Error injection hook to simulate invalid -+ * data being sent to the EC. -+ * -+ * Hook to simulate corrupt/invalid data being sent from host (driver) to EC. -+ * Causes the packet data to be actively corrupted by overwriting it with -+ * pre-defined values, such that it becomes invalid, causing the EC to respond -+ * with a NAK packet. Useful to test handling of NAK packets received by the -+ * driver. -+ */ -+static noinline bool ssh_ptl_should_corrupt_tx_data(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_tx_data, TRUE); -+ -+/** -+ * ssh_ptl_should_corrupt_rx_syn() - Error injection hook to simulate invalid -+ * data being sent by the EC. -+ * -+ * Hook to simulate invalid SYN bytes, i.e. an invalid start of messages and -+ * test handling thereof in the driver. -+ */ -+static noinline bool ssh_ptl_should_corrupt_rx_syn(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_syn, TRUE); -+ -+/** -+ * ssh_ptl_should_corrupt_rx_data() - Error injection hook to simulate invalid -+ * data being sent by the EC. -+ * -+ * Hook to simulate invalid data/checksum of the message frame and test handling -+ * thereof in the driver. -+ */ -+static noinline bool ssh_ptl_should_corrupt_rx_data(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_data, TRUE); -+ -+static bool __ssh_ptl_should_drop_ack_packet(struct ssh_packet *packet) -+{ -+ if (likely(!ssh_ptl_should_drop_ack_packet())) -+ return false; -+ -+ trace_ssam_ei_tx_drop_ack_packet(packet); -+ ptl_info(packet->ptl, "packet error injection: dropping ACK packet %p\n", -+ packet); -+ -+ return true; -+} -+ -+static bool __ssh_ptl_should_drop_nak_packet(struct ssh_packet *packet) -+{ -+ if (likely(!ssh_ptl_should_drop_nak_packet())) -+ return false; -+ -+ trace_ssam_ei_tx_drop_nak_packet(packet); -+ ptl_info(packet->ptl, "packet error injection: dropping NAK packet %p\n", -+ packet); -+ -+ return true; -+} -+ -+static bool __ssh_ptl_should_drop_dsq_packet(struct ssh_packet *packet) -+{ -+ if (likely(!ssh_ptl_should_drop_dsq_packet())) -+ return false; -+ -+ trace_ssam_ei_tx_drop_dsq_packet(packet); -+ ptl_info(packet->ptl, -+ "packet error injection: dropping sequenced data packet %p\n", -+ packet); -+ -+ return true; -+} -+ -+static bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) -+{ -+ /* Ignore packets that don't carry any data (i.e. flush). */ -+ if (!packet->data.ptr || !packet->data.len) -+ return false; -+ -+ switch (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)]) { -+ case SSH_FRAME_TYPE_ACK: -+ return __ssh_ptl_should_drop_ack_packet(packet); -+ -+ case SSH_FRAME_TYPE_NAK: -+ return __ssh_ptl_should_drop_nak_packet(packet); -+ -+ case SSH_FRAME_TYPE_DATA_SEQ: -+ return __ssh_ptl_should_drop_dsq_packet(packet); -+ -+ default: -+ return false; -+ } -+} -+ -+static int ssh_ptl_write_buf(struct ssh_ptl *ptl, struct ssh_packet *packet, -+ const unsigned char *buf, size_t count) -+{ -+ int status; -+ -+ status = ssh_ptl_should_fail_write(); -+ if (unlikely(status)) { -+ trace_ssam_ei_tx_fail_write(packet, status); -+ ptl_info(packet->ptl, -+ "packet error injection: simulating transmit error %d, packet %p\n", -+ status, packet); -+ -+ return status; -+ } -+ -+ return serdev_device_write_buf(ptl->serdev, buf, count); -+} -+ -+static void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) -+{ -+ /* Ignore packets that don't carry any data (i.e. flush). */ -+ if (!packet->data.ptr || !packet->data.len) -+ return; -+ -+ /* Only allow sequenced data packets to be modified. */ -+ if (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)] != SSH_FRAME_TYPE_DATA_SEQ) -+ return; -+ -+ if (likely(!ssh_ptl_should_corrupt_tx_data())) -+ return; -+ -+ trace_ssam_ei_tx_corrupt_data(packet); -+ ptl_info(packet->ptl, -+ "packet error injection: simulating invalid transmit data on packet %p\n", -+ packet); -+ -+ /* -+ * NB: The value 0xb3 has been chosen more or less randomly so that it -+ * doesn't have any (major) overlap with the SYN bytes (aa 55) and is -+ * non-trivial (i.e. non-zero, non-0xff). -+ */ -+ memset(packet->data.ptr, 0xb3, packet->data.len); -+} -+ -+static void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, -+ struct ssam_span *data) -+{ -+ struct ssam_span frame; -+ -+ /* Check if there actually is something to corrupt. */ -+ if (!sshp_find_syn(data, &frame)) -+ return; -+ -+ if (likely(!ssh_ptl_should_corrupt_rx_syn())) -+ return; -+ -+ trace_ssam_ei_rx_corrupt_syn(data->len); -+ -+ data->ptr[1] = 0xb3; /* Set second byte of SYN to "random" value. */ -+} -+ -+static void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, -+ struct ssam_span *frame) -+{ -+ size_t payload_len, message_len; -+ struct ssh_frame *sshf; -+ -+ /* Ignore incomplete messages, will get handled once it's complete. */ -+ if (frame->len < SSH_MESSAGE_LENGTH(0)) -+ return; -+ -+ /* Ignore incomplete messages, part 2. */ -+ payload_len = get_unaligned_le16(&frame->ptr[SSH_MSGOFFSET_FRAME(len)]); -+ message_len = SSH_MESSAGE_LENGTH(payload_len); -+ if (frame->len < message_len) -+ return; -+ -+ if (likely(!ssh_ptl_should_corrupt_rx_data())) -+ return; -+ -+ sshf = (struct ssh_frame *)&frame->ptr[SSH_MSGOFFSET_FRAME(type)]; -+ trace_ssam_ei_rx_corrupt_data(sshf); -+ -+ /* -+ * Flip bits in first byte of payload checksum. This is basically -+ * equivalent to a payload/frame data error without us having to worry -+ * about (the, arguably pretty small, probability of) accidental -+ * checksum collisions. -+ */ -+ frame->ptr[frame->len - 2] = ~frame->ptr[frame->len - 2]; -+} -+ -+#else /* CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION */ -+ -+static inline bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) -+{ -+ return false; -+} -+ -+static inline int ssh_ptl_write_buf(struct ssh_ptl *ptl, -+ struct ssh_packet *packet, -+ const unsigned char *buf, -+ size_t count) -+{ -+ return serdev_device_write_buf(ptl->serdev, buf, count); -+} -+ -+static inline void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) -+{ -+} -+ -+static inline void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, -+ struct ssam_span *data) -+{ -+} -+ -+static inline void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, -+ struct ssam_span *frame) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION */ -+ -+static void __ssh_ptl_packet_release(struct kref *kref) -+{ -+ struct ssh_packet *p = container_of(kref, struct ssh_packet, refcnt); -+ -+ trace_ssam_packet_release(p); -+ -+ ptl_dbg_cond(p->ptl, "ptl: releasing packet %p\n", p); -+ p->ops->release(p); -+} -+ -+/** -+ * ssh_packet_get() - Increment reference count of packet. -+ * @packet: The packet to increment the reference count of. -+ * -+ * Increments the reference count of the given packet. See ssh_packet_put() -+ * for the counter-part of this function. -+ * -+ * Return: Returns the packet provided as input. -+ */ -+struct ssh_packet *ssh_packet_get(struct ssh_packet *packet) -+{ -+ if (packet) -+ kref_get(&packet->refcnt); -+ return packet; -+} -+EXPORT_SYMBOL_GPL(ssh_packet_get); -+ -+/** -+ * ssh_packet_put() - Decrement reference count of packet. -+ * @packet: The packet to decrement the reference count of. -+ * -+ * If the reference count reaches zero, the ``release`` callback specified in -+ * the packet's &struct ssh_packet_ops, i.e. ``packet->ops->release``, will be -+ * called. -+ * -+ * See ssh_packet_get() for the counter-part of this function. -+ */ -+void ssh_packet_put(struct ssh_packet *packet) -+{ -+ if (packet) -+ kref_put(&packet->refcnt, __ssh_ptl_packet_release); -+} -+EXPORT_SYMBOL_GPL(ssh_packet_put); -+ -+static u8 ssh_packet_get_seq(struct ssh_packet *packet) -+{ -+ return packet->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; -+} -+ -+/** -+ * ssh_packet_init() - Initialize SSH packet. -+ * @packet: The packet to initialize. -+ * @type: Type-flags of the packet. -+ * @priority: Priority of the packet. See SSH_PACKET_PRIORITY() for details. -+ * @ops: Packet operations. -+ * -+ * Initializes the given SSH packet. Sets the transmission buffer pointer to -+ * %NULL and the transmission buffer length to zero. For data-type packets, -+ * this buffer has to be set separately via ssh_packet_set_data() before -+ * submission, and must contain a valid SSH message, i.e. frame with optional -+ * payload of any type. -+ */ -+void ssh_packet_init(struct ssh_packet *packet, unsigned long type, -+ u8 priority, const struct ssh_packet_ops *ops) -+{ -+ kref_init(&packet->refcnt); -+ -+ packet->ptl = NULL; -+ INIT_LIST_HEAD(&packet->queue_node); -+ INIT_LIST_HEAD(&packet->pending_node); -+ -+ packet->state = type & SSH_PACKET_FLAGS_TY_MASK; -+ packet->priority = priority; -+ packet->timestamp = KTIME_MAX; -+ -+ packet->data.ptr = NULL; -+ packet->data.len = 0; -+ -+ packet->ops = ops; -+} -+ -+static struct kmem_cache *ssh_ctrl_packet_cache; -+ -+/** -+ * ssh_ctrl_packet_cache_init() - Initialize the control packet cache. -+ */ -+int ssh_ctrl_packet_cache_init(void) -+{ -+ const unsigned int size = sizeof(struct ssh_packet) + SSH_MSG_LEN_CTRL; -+ const unsigned int align = __alignof__(struct ssh_packet); -+ struct kmem_cache *cache; -+ -+ cache = kmem_cache_create("ssam_ctrl_packet", size, align, 0, NULL); -+ if (!cache) -+ return -ENOMEM; -+ -+ ssh_ctrl_packet_cache = cache; -+ return 0; -+} -+ -+/** -+ * ssh_ctrl_packet_cache_destroy() - Deinitialize the control packet cache. -+ */ -+void ssh_ctrl_packet_cache_destroy(void) -+{ -+ kmem_cache_destroy(ssh_ctrl_packet_cache); -+ ssh_ctrl_packet_cache = NULL; -+} -+ -+/** -+ * ssh_ctrl_packet_alloc() - Allocate packet from control packet cache. -+ * @packet: Where the pointer to the newly allocated packet should be stored. -+ * @buffer: The buffer corresponding to this packet. -+ * @flags: Flags used for allocation. -+ * -+ * Allocates a packet and corresponding transport buffer from the control -+ * packet cache. Sets the packet's buffer reference to the allocated buffer. -+ * The packet must be freed via ssh_ctrl_packet_free(), which will also free -+ * the corresponding buffer. The corresponding buffer must not be freed -+ * separately. Intended to be used with %ssh_ptl_ctrl_packet_ops as packet -+ * operations. -+ * -+ * Return: Returns zero on success, %-ENOMEM if the allocation failed. -+ */ -+static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, -+ struct ssam_span *buffer, gfp_t flags) -+{ -+ *packet = kmem_cache_alloc(ssh_ctrl_packet_cache, flags); -+ if (!*packet) -+ return -ENOMEM; -+ -+ buffer->ptr = (u8 *)(*packet + 1); -+ buffer->len = SSH_MSG_LEN_CTRL; -+ -+ trace_ssam_ctrl_packet_alloc(*packet, buffer->len); -+ return 0; -+} -+ -+/** -+ * ssh_ctrl_packet_free() - Free packet allocated from control packet cache. -+ * @p: The packet to free. -+ */ -+static void ssh_ctrl_packet_free(struct ssh_packet *p) -+{ -+ trace_ssam_ctrl_packet_free(p); -+ kmem_cache_free(ssh_ctrl_packet_cache, p); -+} -+ -+static const struct ssh_packet_ops ssh_ptl_ctrl_packet_ops = { -+ .complete = NULL, -+ .release = ssh_ctrl_packet_free, -+}; -+ -+static void ssh_ptl_timeout_reaper_mod(struct ssh_ptl *ptl, ktime_t now, -+ ktime_t expires) -+{ -+ unsigned long delta = msecs_to_jiffies(ktime_ms_delta(expires, now)); -+ ktime_t aexp = ktime_add(expires, SSH_PTL_PACKET_TIMEOUT_RESOLUTION); -+ -+ spin_lock(&ptl->rtx_timeout.lock); -+ -+ /* Re-adjust / schedule reaper only if it is above resolution delta. */ -+ if (ktime_before(aexp, ptl->rtx_timeout.expires)) { -+ ptl->rtx_timeout.expires = expires; -+ mod_delayed_work(system_wq, &ptl->rtx_timeout.reaper, delta); -+ } -+ -+ spin_unlock(&ptl->rtx_timeout.lock); -+} -+ -+/* Must be called with queue lock held. */ -+static void ssh_packet_next_try(struct ssh_packet *p) -+{ -+ u8 base = ssh_packet_priority_get_base(p->priority); -+ u8 try = ssh_packet_priority_get_try(p->priority); -+ -+ lockdep_assert_held(&p->ptl->queue.lock); -+ -+ /* -+ * Ensure that we write the priority in one go via WRITE_ONCE() so we -+ * can access it via READ_ONCE() for tracing. Note that other access -+ * is guarded by the queue lock, so no need to use READ_ONCE() there. -+ */ -+ WRITE_ONCE(p->priority, __SSH_PACKET_PRIORITY(base, try + 1)); -+} -+ -+/* Must be called with queue lock held. */ -+static struct list_head *__ssh_ptl_queue_find_entrypoint(struct ssh_packet *p) -+{ -+ struct list_head *head; -+ struct ssh_packet *q; -+ -+ lockdep_assert_held(&p->ptl->queue.lock); -+ -+ /* -+ * We generally assume that there are less control (ACK/NAK) packets -+ * and re-submitted data packets as there are normal data packets (at -+ * least in situations in which many packets are queued; if there -+ * aren't many packets queued the decision on how to iterate should be -+ * basically irrelevant; the number of control/data packets is more or -+ * less limited via the maximum number of pending packets). Thus, when -+ * inserting a control or re-submitted data packet, (determined by -+ * their priority), we search from front to back. Normal data packets -+ * are, usually queued directly at the tail of the queue, so for those -+ * search from back to front. -+ */ -+ -+ if (p->priority > SSH_PACKET_PRIORITY(DATA, 0)) { -+ list_for_each(head, &p->ptl->queue.head) { -+ q = list_entry(head, struct ssh_packet, queue_node); -+ -+ if (q->priority < p->priority) -+ break; -+ } -+ } else { -+ list_for_each_prev(head, &p->ptl->queue.head) { -+ q = list_entry(head, struct ssh_packet, queue_node); -+ -+ if (q->priority >= p->priority) { -+ head = head->next; -+ break; -+ } -+ } -+ } -+ -+ return head; -+} -+ -+/* Must be called with queue lock held. */ -+static int __ssh_ptl_queue_push(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ struct list_head *head; -+ -+ lockdep_assert_held(&ptl->queue.lock); -+ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) -+ return -ESHUTDOWN; -+ -+ /* Avoid further transitions when canceling/completing. */ -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)) -+ return -EINVAL; -+ -+ /* If this packet has already been queued, do not add it. */ -+ if (test_and_set_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state)) -+ return -EALREADY; -+ -+ head = __ssh_ptl_queue_find_entrypoint(packet); -+ -+ list_add_tail(&ssh_packet_get(packet)->queue_node, head); -+ return 0; -+} -+ -+static int ssh_ptl_queue_push(struct ssh_packet *packet) -+{ -+ int status; -+ -+ spin_lock(&packet->ptl->queue.lock); -+ status = __ssh_ptl_queue_push(packet); -+ spin_unlock(&packet->ptl->queue.lock); -+ -+ return status; -+} -+ -+static void ssh_ptl_queue_remove(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ spin_lock(&ptl->queue.lock); -+ -+ if (!test_and_clear_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state)) { -+ spin_unlock(&ptl->queue.lock); -+ return; -+ } -+ -+ list_del(&packet->queue_node); -+ -+ spin_unlock(&ptl->queue.lock); -+ ssh_packet_put(packet); -+} -+ -+static void ssh_ptl_pending_push(struct ssh_packet *p) -+{ -+ struct ssh_ptl *ptl = p->ptl; -+ const ktime_t timestamp = ktime_get_coarse_boottime(); -+ const ktime_t timeout = ptl->rtx_timeout.timeout; -+ -+ /* -+ * Note: We can get the time for the timestamp before acquiring the -+ * lock as this is the only place we're setting it and this function -+ * is called only from the transmitter thread. Thus it is not possible -+ * to overwrite the timestamp with an outdated value below. -+ */ -+ -+ spin_lock(&ptl->pending.lock); -+ -+ /* If we are canceling/completing this packet, do not add it. */ -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) { -+ spin_unlock(&ptl->pending.lock); -+ return; -+ } -+ -+ /* -+ * On re-submission, the packet has already been added the pending -+ * set. We still need to update the timestamp as the packet timeout is -+ * reset for each (re-)submission. -+ */ -+ p->timestamp = timestamp; -+ -+ /* In case it is already pending (e.g. re-submission), do not add it. */ -+ if (!test_and_set_bit(SSH_PACKET_SF_PENDING_BIT, &p->state)) { -+ atomic_inc(&ptl->pending.count); -+ list_add_tail(&ssh_packet_get(p)->pending_node, &ptl->pending.head); -+ } -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ /* Arm/update timeout reaper. */ -+ ssh_ptl_timeout_reaper_mod(ptl, timestamp, timestamp + timeout); -+} -+ -+static void ssh_ptl_pending_remove(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ spin_lock(&ptl->pending.lock); -+ -+ if (!test_and_clear_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state)) { -+ spin_unlock(&ptl->pending.lock); -+ return; -+ } -+ -+ list_del(&packet->pending_node); -+ atomic_dec(&ptl->pending.count); -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ ssh_packet_put(packet); -+} -+ -+/* Warning: Does not check/set "completed" bit. */ -+static void __ssh_ptl_complete(struct ssh_packet *p, int status) -+{ -+ struct ssh_ptl *ptl = READ_ONCE(p->ptl); -+ -+ trace_ssam_packet_complete(p, status); -+ ptl_dbg_cond(ptl, "ptl: completing packet %p (status: %d)\n", p, status); -+ -+ if (p->ops->complete) -+ p->ops->complete(p, status); -+} -+ -+static void ssh_ptl_remove_and_complete(struct ssh_packet *p, int status) -+{ -+ /* -+ * A call to this function should in general be preceded by -+ * set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->flags) to avoid re-adding the -+ * packet to the structures it's going to be removed from. -+ * -+ * The set_bit call does not need explicit memory barriers as the -+ * implicit barrier of the test_and_set_bit() call below ensure that the -+ * flag is visible before we actually attempt to remove the packet. -+ */ -+ -+ if (test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) -+ return; -+ -+ ssh_ptl_queue_remove(p); -+ ssh_ptl_pending_remove(p); -+ -+ __ssh_ptl_complete(p, status); -+} -+ -+static bool ssh_ptl_tx_can_process(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &packet->state)) -+ return !atomic_read(&ptl->pending.count); -+ -+ /* We can always process non-blocking packets. */ -+ if (!test_bit(SSH_PACKET_TY_BLOCKING_BIT, &packet->state)) -+ return true; -+ -+ /* If we are already waiting for this packet, send it again. */ -+ if (test_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state)) -+ return true; -+ -+ /* Otherwise: Check if we have the capacity to send. */ -+ return atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING; -+} -+ -+static struct ssh_packet *ssh_ptl_tx_pop(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *packet = ERR_PTR(-ENOENT); -+ struct ssh_packet *p, *n; -+ -+ spin_lock(&ptl->queue.lock); -+ list_for_each_entry_safe(p, n, &ptl->queue.head, queue_node) { -+ /* -+ * If we are canceling or completing this packet, ignore it. -+ * It's going to be removed from this queue shortly. -+ */ -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) -+ continue; -+ -+ /* -+ * Packets should be ordered non-blocking/to-be-resent first. -+ * If we cannot process this packet, assume that we can't -+ * process any following packet either and abort. -+ */ -+ if (!ssh_ptl_tx_can_process(p)) { -+ packet = ERR_PTR(-EBUSY); -+ break; -+ } -+ -+ /* -+ * We are allowed to change the state now. Remove it from the -+ * queue and mark it as being transmitted. -+ */ -+ -+ list_del(&p->queue_node); -+ -+ set_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &p->state); -+ /* Ensure that state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); -+ -+ /* -+ * Update number of tries. This directly influences the -+ * priority in case the packet is re-submitted (e.g. via -+ * timeout/NAK). Note that all reads and writes to the -+ * priority after the first submission are guarded by the -+ * queue lock. -+ */ -+ ssh_packet_next_try(p); -+ -+ packet = p; -+ break; -+ } -+ spin_unlock(&ptl->queue.lock); -+ -+ return packet; -+} -+ -+static struct ssh_packet *ssh_ptl_tx_next(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *p; -+ -+ p = ssh_ptl_tx_pop(ptl); -+ if (IS_ERR(p)) -+ return p; -+ -+ if (test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) { -+ ptl_dbg(ptl, "ptl: transmitting sequenced packet %p\n", p); -+ ssh_ptl_pending_push(p); -+ } else { -+ ptl_dbg(ptl, "ptl: transmitting non-sequenced packet %p\n", p); -+ } -+ -+ return p; -+} -+ -+static void ssh_ptl_tx_compl_success(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ ptl_dbg(ptl, "ptl: successfully transmitted packet %p\n", packet); -+ -+ /* Transition state to "transmitted". */ -+ set_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &packet->state); -+ /* Ensure that state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &packet->state); -+ -+ /* If the packet is unsequenced, we're done: Lock and complete. */ -+ if (!test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &packet->state)) { -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state); -+ ssh_ptl_remove_and_complete(packet, 0); -+ } -+ -+ /* -+ * Notify that a packet transmission has finished. In general we're only -+ * waiting for one packet (if any), so wake_up_all should be fine. -+ */ -+ wake_up_all(&ptl->tx.packet_wq); -+} -+ -+static void ssh_ptl_tx_compl_error(struct ssh_packet *packet, int status) -+{ -+ /* Transmission failure: Lock the packet and try to complete it. */ -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state); -+ /* Ensure that state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &packet->state); -+ -+ ptl_err(packet->ptl, "ptl: transmission error: %d\n", status); -+ ptl_dbg(packet->ptl, "ptl: failed to transmit packet: %p\n", packet); -+ -+ ssh_ptl_remove_and_complete(packet, status); -+ -+ /* -+ * Notify that a packet transmission has finished. In general we're only -+ * waiting for one packet (if any), so wake_up_all should be fine. -+ */ -+ wake_up_all(&packet->ptl->tx.packet_wq); -+} -+ -+static long ssh_ptl_tx_wait_packet(struct ssh_ptl *ptl) -+{ -+ int status; -+ -+ status = wait_for_completion_interruptible(&ptl->tx.thread_cplt_pkt); -+ reinit_completion(&ptl->tx.thread_cplt_pkt); -+ -+ /* -+ * Ensure completion is cleared before continuing to avoid lost update -+ * problems. -+ */ -+ smp_mb__after_atomic(); -+ -+ return status; -+} -+ -+static long ssh_ptl_tx_wait_transfer(struct ssh_ptl *ptl, long timeout) -+{ -+ long status; -+ -+ status = wait_for_completion_interruptible_timeout(&ptl->tx.thread_cplt_tx, -+ timeout); -+ reinit_completion(&ptl->tx.thread_cplt_tx); -+ -+ /* -+ * Ensure completion is cleared before continuing to avoid lost update -+ * problems. -+ */ -+ smp_mb__after_atomic(); -+ -+ return status; -+} -+ -+static int ssh_ptl_tx_packet(struct ssh_ptl *ptl, struct ssh_packet *packet) -+{ -+ long timeout = SSH_PTL_TX_TIMEOUT; -+ size_t offset = 0; -+ -+ /* Note: Flush-packets don't have any data. */ -+ if (unlikely(!packet->data.ptr)) -+ return 0; -+ -+ /* Error injection: drop packet to simulate transmission problem. */ -+ if (ssh_ptl_should_drop_packet(packet)) -+ return 0; -+ -+ /* Error injection: simulate invalid packet data. */ -+ ssh_ptl_tx_inject_invalid_data(packet); -+ -+ ptl_dbg(ptl, "tx: sending data (length: %zu)\n", packet->data.len); -+ print_hex_dump_debug("tx: ", DUMP_PREFIX_OFFSET, 16, 1, -+ packet->data.ptr, packet->data.len, false); -+ -+ do { -+ ssize_t status, len; -+ u8 *buf; -+ -+ buf = packet->data.ptr + offset; -+ len = packet->data.len - offset; -+ -+ status = ssh_ptl_write_buf(ptl, packet, buf, len); -+ if (status < 0) -+ return status; -+ -+ if (status == len) -+ return 0; -+ -+ offset += status; -+ -+ timeout = ssh_ptl_tx_wait_transfer(ptl, timeout); -+ if (kthread_should_stop() || !atomic_read(&ptl->tx.running)) -+ return -ESHUTDOWN; -+ -+ if (timeout < 0) -+ return -EINTR; -+ -+ if (timeout == 0) -+ return -ETIMEDOUT; -+ } while (true); -+} -+ -+static int ssh_ptl_tx_threadfn(void *data) -+{ -+ struct ssh_ptl *ptl = data; -+ -+ while (!kthread_should_stop() && atomic_read(&ptl->tx.running)) { -+ struct ssh_packet *packet; -+ int status; -+ -+ /* Try to get the next packet. */ -+ packet = ssh_ptl_tx_next(ptl); -+ -+ /* If no packet can be processed, we are done. */ -+ if (IS_ERR(packet)) { -+ ssh_ptl_tx_wait_packet(ptl); -+ continue; -+ } -+ -+ /* Transfer and complete packet. */ -+ status = ssh_ptl_tx_packet(ptl, packet); -+ if (status) -+ ssh_ptl_tx_compl_error(packet, status); -+ else -+ ssh_ptl_tx_compl_success(packet); -+ -+ ssh_packet_put(packet); -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssh_ptl_tx_wakeup_packet() - Wake up packet transmitter thread for new -+ * packet. -+ * @ptl: The packet transport layer. -+ * -+ * Wakes up the packet transmitter thread, notifying it that a new packet has -+ * arrived and is ready for transfer. If the packet transport layer has been -+ * shut down, calls to this function will be ignored. -+ */ -+static void ssh_ptl_tx_wakeup_packet(struct ssh_ptl *ptl) -+{ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) -+ return; -+ -+ complete(&ptl->tx.thread_cplt_pkt); -+} -+ -+/** -+ * ssh_ptl_tx_start() - Start packet transmitter thread. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_ptl_tx_start(struct ssh_ptl *ptl) -+{ -+ atomic_set_release(&ptl->tx.running, 1); -+ -+ ptl->tx.thread = kthread_run(ssh_ptl_tx_threadfn, ptl, "ssam_serial_hub-tx"); -+ if (IS_ERR(ptl->tx.thread)) -+ return PTR_ERR(ptl->tx.thread); -+ -+ return 0; -+} -+ -+/** -+ * ssh_ptl_tx_stop() - Stop packet transmitter thread. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_ptl_tx_stop(struct ssh_ptl *ptl) -+{ -+ int status = 0; -+ -+ if (!IS_ERR_OR_NULL(ptl->tx.thread)) { -+ /* Tell thread to stop. */ -+ atomic_set_release(&ptl->tx.running, 0); -+ -+ /* -+ * Wake up thread in case it is paused. Do not use wakeup -+ * helpers as this may be called when the shutdown bit has -+ * already been set. -+ */ -+ complete(&ptl->tx.thread_cplt_pkt); -+ complete(&ptl->tx.thread_cplt_tx); -+ -+ /* Finally, wait for thread to stop. */ -+ status = kthread_stop(ptl->tx.thread); -+ ptl->tx.thread = NULL; -+ } -+ -+ return status; -+} -+ -+static struct ssh_packet *ssh_ptl_ack_pop(struct ssh_ptl *ptl, u8 seq_id) -+{ -+ struct ssh_packet *packet = ERR_PTR(-ENOENT); -+ struct ssh_packet *p, *n; -+ -+ spin_lock(&ptl->pending.lock); -+ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { -+ /* -+ * We generally expect packets to be in order, so first packet -+ * to be added to pending is first to be sent, is first to be -+ * ACKed. -+ */ -+ if (unlikely(ssh_packet_get_seq(p) != seq_id)) -+ continue; -+ -+ /* -+ * In case we receive an ACK while handling a transmission -+ * error completion. The packet will be removed shortly. -+ */ -+ if (unlikely(test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state))) { -+ packet = ERR_PTR(-EPERM); -+ break; -+ } -+ -+ /* -+ * Mark the packet as ACKed and remove it from pending by -+ * removing its node and decrementing the pending counter. -+ */ -+ set_bit(SSH_PACKET_SF_ACKED_BIT, &p->state); -+ /* Ensure that state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); -+ -+ atomic_dec(&ptl->pending.count); -+ list_del(&p->pending_node); -+ packet = p; -+ -+ break; -+ } -+ spin_unlock(&ptl->pending.lock); -+ -+ return packet; -+} -+ -+static void ssh_ptl_wait_until_transmitted(struct ssh_packet *packet) -+{ -+ wait_event(packet->ptl->tx.packet_wq, -+ test_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &packet->state) || -+ test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)); -+} -+ -+static void ssh_ptl_acknowledge(struct ssh_ptl *ptl, u8 seq) -+{ -+ struct ssh_packet *p; -+ -+ p = ssh_ptl_ack_pop(ptl, seq); -+ if (IS_ERR(p)) { -+ if (PTR_ERR(p) == -ENOENT) { -+ /* -+ * The packet has not been found in the set of pending -+ * packets. -+ */ -+ ptl_warn(ptl, "ptl: received ACK for non-pending packet\n"); -+ } else { -+ /* -+ * The packet is pending, but we are not allowed to take -+ * it because it has been locked. -+ */ -+ WARN_ON(PTR_ERR(p) != -EPERM); -+ } -+ return; -+ } -+ -+ ptl_dbg(ptl, "ptl: received ACK for packet %p\n", p); -+ -+ /* -+ * It is possible that the packet has been transmitted, but the state -+ * has not been updated from "transmitting" to "transmitted" yet. -+ * In that case, we need to wait for this transition to occur in order -+ * to determine between success or failure. -+ * -+ * On transmission failure, the packet will be locked after this call. -+ * On success, the transmitted bit will be set. -+ */ -+ ssh_ptl_wait_until_transmitted(p); -+ -+ /* -+ * The packet will already be locked in case of a transmission error or -+ * cancellation. Let the transmitter or cancellation issuer complete the -+ * packet. -+ */ -+ if (unlikely(test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state))) { -+ if (unlikely(!test_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &p->state))) -+ ptl_err(ptl, "ptl: received ACK before packet had been fully transmitted\n"); -+ -+ ssh_packet_put(p); -+ return; -+ } -+ -+ ssh_ptl_remove_and_complete(p, 0); -+ ssh_packet_put(p); -+ -+ if (atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING) -+ ssh_ptl_tx_wakeup_packet(ptl); -+} -+ -+/** -+ * ssh_ptl_submit() - Submit a packet to the transport layer. -+ * @ptl: The packet transport layer to submit the packet to. -+ * @p: The packet to submit. -+ * -+ * Submits a new packet to the transport layer, queuing it to be sent. This -+ * function should not be used for re-submission. -+ * -+ * Return: Returns zero on success, %-EINVAL if a packet field is invalid or -+ * the packet has been canceled prior to submission, %-EALREADY if the packet -+ * has already been submitted, or %-ESHUTDOWN if the packet transport layer -+ * has been shut down. -+ */ -+int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p) -+{ -+ struct ssh_ptl *ptl_old; -+ int status; -+ -+ trace_ssam_packet_submit(p); -+ -+ /* Validate packet fields. */ -+ if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &p->state)) { -+ if (p->data.ptr || test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) -+ return -EINVAL; -+ } else if (!p->data.ptr) { -+ return -EINVAL; -+ } -+ -+ /* -+ * The ptl reference only gets set on or before the first submission. -+ * After the first submission, it has to be read-only. -+ * -+ * Note that ptl may already be set from upper-layer request -+ * submission, thus we cannot expect it to be NULL. -+ */ -+ ptl_old = READ_ONCE(p->ptl); -+ if (!ptl_old) -+ WRITE_ONCE(p->ptl, ptl); -+ else if (WARN_ON(ptl_old != ptl)) -+ return -EALREADY; /* Submitted on different PTL. */ -+ -+ status = ssh_ptl_queue_push(p); -+ if (status) -+ return status; -+ -+ if (!test_bit(SSH_PACKET_TY_BLOCKING_BIT, &p->state) || -+ (atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING)) -+ ssh_ptl_tx_wakeup_packet(ptl); -+ -+ return 0; -+} -+ -+/* -+ * __ssh_ptl_resubmit() - Re-submit a packet to the transport layer. -+ * @packet: The packet to re-submit. -+ * -+ * Re-submits the given packet: Checks if it can be re-submitted and queues it -+ * if it can, resetting the packet timestamp in the process. Must be called -+ * with the pending lock held. -+ * -+ * Return: Returns %-ECANCELED if the packet has exceeded its number of tries, -+ * %-EINVAL if the packet has been locked, %-EALREADY if the packet is already -+ * on the queue, and %-ESHUTDOWN if the transmission layer has been shut down. -+ */ -+static int __ssh_ptl_resubmit(struct ssh_packet *packet) -+{ -+ int status; -+ u8 try; -+ -+ lockdep_assert_held(&packet->ptl->pending.lock); -+ -+ trace_ssam_packet_resubmit(packet); -+ -+ spin_lock(&packet->ptl->queue.lock); -+ -+ /* Check if the packet is out of tries. */ -+ try = ssh_packet_priority_get_try(packet->priority); -+ if (try >= SSH_PTL_MAX_PACKET_TRIES) { -+ spin_unlock(&packet->ptl->queue.lock); -+ return -ECANCELED; -+ } -+ -+ status = __ssh_ptl_queue_push(packet); -+ if (status) { -+ /* -+ * An error here indicates that the packet has either already -+ * been queued, been locked, or the transport layer is being -+ * shut down. In all cases: Ignore the error. -+ */ -+ spin_unlock(&packet->ptl->queue.lock); -+ return status; -+ } -+ -+ packet->timestamp = KTIME_MAX; -+ -+ spin_unlock(&packet->ptl->queue.lock); -+ return 0; -+} -+ -+static void ssh_ptl_resubmit_pending(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *p; -+ bool resub = false; -+ -+ /* -+ * Note: We deliberately do not remove/attempt to cancel and complete -+ * packets that are out of tires in this function. The packet will be -+ * eventually canceled and completed by the timeout. Removing the packet -+ * here could lead to overly eager cancellation if the packet has not -+ * been re-transmitted yet but the tries-counter already updated (i.e -+ * ssh_ptl_tx_next() removed the packet from the queue and updated the -+ * counter, but re-transmission for the last try has not actually -+ * started yet). -+ */ -+ -+ spin_lock(&ptl->pending.lock); -+ -+ /* Re-queue all pending packets. */ -+ list_for_each_entry(p, &ptl->pending.head, pending_node) { -+ /* -+ * Re-submission fails if the packet is out of tries, has been -+ * locked, is already queued, or the layer is being shut down. -+ * No need to re-schedule tx-thread in those cases. -+ */ -+ if (!__ssh_ptl_resubmit(p)) -+ resub = true; -+ } -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ if (resub) -+ ssh_ptl_tx_wakeup_packet(ptl); -+} -+ -+/** -+ * ssh_ptl_cancel() - Cancel a packet. -+ * @p: The packet to cancel. -+ * -+ * Cancels a packet. There are no guarantees on when completion and release -+ * callbacks will be called. This may occur during execution of this function -+ * or may occur at any point later. -+ * -+ * Note that it is not guaranteed that the packet will actually be canceled if -+ * the packet is concurrently completed by another process. The only guarantee -+ * of this function is that the packet will be completed (with success, -+ * failure, or cancellation) and released from the transport layer in a -+ * reasonable time-frame. -+ * -+ * May be called before the packet has been submitted, in which case any later -+ * packet submission fails. -+ */ -+void ssh_ptl_cancel(struct ssh_packet *p) -+{ -+ if (test_and_set_bit(SSH_PACKET_SF_CANCELED_BIT, &p->state)) -+ return; -+ -+ trace_ssam_packet_cancel(p); -+ -+ /* -+ * Lock packet and commit with memory barrier. If this packet has -+ * already been locked, it's going to be removed and completed by -+ * another party, which should have precedence. -+ */ -+ if (test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) -+ return; -+ -+ /* -+ * By marking the packet as locked and employing the implicit memory -+ * barrier of test_and_set_bit, we have guaranteed that, at this point, -+ * the packet cannot be added to the queue any more. -+ * -+ * In case the packet has never been submitted, packet->ptl is NULL. If -+ * the packet is currently being submitted, packet->ptl may be NULL or -+ * non-NULL. Due marking the packet as locked above and committing with -+ * the memory barrier, we have guaranteed that, if packet->ptl is NULL, -+ * the packet will never be added to the queue. If packet->ptl is -+ * non-NULL, we don't have any guarantees. -+ */ -+ -+ if (READ_ONCE(p->ptl)) { -+ ssh_ptl_remove_and_complete(p, -ECANCELED); -+ -+ if (atomic_read(&p->ptl->pending.count) < SSH_PTL_MAX_PENDING) -+ ssh_ptl_tx_wakeup_packet(p->ptl); -+ -+ } else if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) { -+ __ssh_ptl_complete(p, -ECANCELED); -+ } -+} -+ -+/* Must be called with pending lock held */ -+static ktime_t ssh_packet_get_expiration(struct ssh_packet *p, ktime_t timeout) -+{ -+ lockdep_assert_held(&p->ptl->pending.lock); -+ -+ if (p->timestamp != KTIME_MAX) -+ return ktime_add(p->timestamp, timeout); -+ else -+ return KTIME_MAX; -+} -+ -+static void ssh_ptl_timeout_reap(struct work_struct *work) -+{ -+ struct ssh_ptl *ptl = to_ssh_ptl(work, rtx_timeout.reaper.work); -+ struct ssh_packet *p, *n; -+ LIST_HEAD(claimed); -+ ktime_t now = ktime_get_coarse_boottime(); -+ ktime_t timeout = ptl->rtx_timeout.timeout; -+ ktime_t next = KTIME_MAX; -+ bool resub = false; -+ int status; -+ -+ trace_ssam_ptl_timeout_reap(atomic_read(&ptl->pending.count)); -+ -+ /* -+ * Mark reaper as "not pending". This is done before checking any -+ * packets to avoid lost-update type problems. -+ */ -+ spin_lock(&ptl->rtx_timeout.lock); -+ ptl->rtx_timeout.expires = KTIME_MAX; -+ spin_unlock(&ptl->rtx_timeout.lock); -+ -+ spin_lock(&ptl->pending.lock); -+ -+ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { -+ ktime_t expires = ssh_packet_get_expiration(p, timeout); -+ -+ /* -+ * Check if the timeout hasn't expired yet. Find out next -+ * expiration date to be handled after this run. -+ */ -+ if (ktime_after(expires, now)) { -+ next = ktime_before(expires, next) ? expires : next; -+ continue; -+ } -+ -+ trace_ssam_packet_timeout(p); -+ -+ status = __ssh_ptl_resubmit(p); -+ -+ /* -+ * Re-submission fails if the packet is out of tries, has been -+ * locked, is already queued, or the layer is being shut down. -+ * No need to re-schedule tx-thread in those cases. -+ */ -+ if (!status) -+ resub = true; -+ -+ /* Go to next packet if this packet is not out of tries. */ -+ if (status != -ECANCELED) -+ continue; -+ -+ /* No more tries left: Cancel the packet. */ -+ -+ /* -+ * If someone else has locked the packet already, don't use it -+ * and let the other party complete it. -+ */ -+ if (test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) -+ continue; -+ -+ /* -+ * We have now marked the packet as locked. Thus it cannot be -+ * added to the pending list again after we've removed it here. -+ * We can therefore re-use the pending_node of this packet -+ * temporarily. -+ */ -+ -+ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); -+ -+ atomic_dec(&ptl->pending.count); -+ list_del(&p->pending_node); -+ -+ list_add_tail(&p->pending_node, &claimed); -+ } -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ /* Cancel and complete the packet. */ -+ list_for_each_entry_safe(p, n, &claimed, pending_node) { -+ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) { -+ ssh_ptl_queue_remove(p); -+ __ssh_ptl_complete(p, -ETIMEDOUT); -+ } -+ -+ /* -+ * Drop the reference we've obtained by removing it from -+ * the pending set. -+ */ -+ list_del(&p->pending_node); -+ ssh_packet_put(p); -+ } -+ -+ /* Ensure that reaper doesn't run again immediately. */ -+ next = max(next, ktime_add(now, SSH_PTL_PACKET_TIMEOUT_RESOLUTION)); -+ if (next != KTIME_MAX) -+ ssh_ptl_timeout_reaper_mod(ptl, now, next); -+ -+ if (resub) -+ ssh_ptl_tx_wakeup_packet(ptl); -+} -+ -+static bool ssh_ptl_rx_retransmit_check(struct ssh_ptl *ptl, u8 seq) -+{ -+ int i; -+ -+ /* -+ * Check if SEQ has been seen recently (i.e. packet was -+ * re-transmitted and we should ignore it). -+ */ -+ for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) { -+ if (likely(ptl->rx.blocked.seqs[i] != seq)) -+ continue; -+ -+ ptl_dbg(ptl, "ptl: ignoring repeated data packet\n"); -+ return true; -+ } -+ -+ /* Update list of blocked sequence IDs. */ -+ ptl->rx.blocked.seqs[ptl->rx.blocked.offset] = seq; -+ ptl->rx.blocked.offset = (ptl->rx.blocked.offset + 1) -+ % ARRAY_SIZE(ptl->rx.blocked.seqs); -+ -+ return false; -+} -+ -+static void ssh_ptl_rx_dataframe(struct ssh_ptl *ptl, -+ const struct ssh_frame *frame, -+ const struct ssam_span *payload) -+{ -+ if (ssh_ptl_rx_retransmit_check(ptl, frame->seq)) -+ return; -+ -+ ptl->ops.data_received(ptl, payload); -+} -+ -+static void ssh_ptl_send_ack(struct ssh_ptl *ptl, u8 seq) -+{ -+ struct ssh_packet *packet; -+ struct ssam_span buf; -+ struct msgbuf msgb; -+ int status; -+ -+ status = ssh_ctrl_packet_alloc(&packet, &buf, GFP_KERNEL); -+ if (status) { -+ ptl_err(ptl, "ptl: failed to allocate ACK packet\n"); -+ return; -+ } -+ -+ ssh_packet_init(packet, 0, SSH_PACKET_PRIORITY(ACK, 0), -+ &ssh_ptl_ctrl_packet_ops); -+ -+ msgb_init(&msgb, buf.ptr, buf.len); -+ msgb_push_ack(&msgb, seq); -+ ssh_packet_set_data(packet, msgb.begin, msgb_bytes_used(&msgb)); -+ -+ ssh_ptl_submit(ptl, packet); -+ ssh_packet_put(packet); -+} -+ -+static void ssh_ptl_send_nak(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *packet; -+ struct ssam_span buf; -+ struct msgbuf msgb; -+ int status; -+ -+ status = ssh_ctrl_packet_alloc(&packet, &buf, GFP_KERNEL); -+ if (status) { -+ ptl_err(ptl, "ptl: failed to allocate NAK packet\n"); -+ return; -+ } -+ -+ ssh_packet_init(packet, 0, SSH_PACKET_PRIORITY(NAK, 0), -+ &ssh_ptl_ctrl_packet_ops); -+ -+ msgb_init(&msgb, buf.ptr, buf.len); -+ msgb_push_nak(&msgb); -+ ssh_packet_set_data(packet, msgb.begin, msgb_bytes_used(&msgb)); -+ -+ ssh_ptl_submit(ptl, packet); -+ ssh_packet_put(packet); -+} -+ -+static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) -+{ -+ struct ssh_frame *frame; -+ struct ssam_span payload; -+ struct ssam_span aligned; -+ bool syn_found; -+ int status; -+ -+ /* Error injection: Modify data to simulate corrupt SYN bytes. */ -+ ssh_ptl_rx_inject_invalid_syn(ptl, source); -+ -+ /* Find SYN. */ -+ syn_found = sshp_find_syn(source, &aligned); -+ -+ if (unlikely(aligned.ptr != source->ptr)) { -+ /* -+ * We expect aligned.ptr == source->ptr. If this is not the -+ * case, then aligned.ptr > source->ptr and we've encountered -+ * some unexpected data where we'd expect the start of a new -+ * message (i.e. the SYN sequence). -+ * -+ * This can happen when a CRC check for the previous message -+ * failed and we start actively searching for the next one -+ * (via the call to sshp_find_syn() above), or the first bytes -+ * of a message got dropped or corrupted. -+ * -+ * In any case, we issue a warning, send a NAK to the EC to -+ * request re-transmission of any data we haven't acknowledged -+ * yet, and finally, skip everything up to the next SYN -+ * sequence. -+ */ -+ -+ ptl_warn(ptl, "rx: parser: invalid start of frame, skipping\n"); -+ -+ /* -+ * Notes: -+ * - This might send multiple NAKs in case the communication -+ * starts with an invalid SYN and is broken down into multiple -+ * pieces. This should generally be handled fine, we just -+ * might receive duplicate data in this case, which is -+ * detected when handling data frames. -+ * - This path will also be executed on invalid CRCs: When an -+ * invalid CRC is encountered, the code below will skip data -+ * until directly after the SYN. This causes the search for -+ * the next SYN, which is generally not placed directly after -+ * the last one. -+ * -+ * Open question: Should we send this in case of invalid -+ * payload CRCs if the frame-type is non-sequential (current -+ * implementation) or should we drop that frame without -+ * telling the EC? -+ */ -+ ssh_ptl_send_nak(ptl); -+ } -+ -+ if (unlikely(!syn_found)) -+ return aligned.ptr - source->ptr; -+ -+ /* Error injection: Modify data to simulate corruption. */ -+ ssh_ptl_rx_inject_invalid_data(ptl, &aligned); -+ -+ /* Parse and validate frame. */ -+ status = sshp_parse_frame(&ptl->serdev->dev, &aligned, &frame, &payload, -+ SSH_PTL_RX_BUF_LEN); -+ if (status) /* Invalid frame: skip to next SYN. */ -+ return aligned.ptr - source->ptr + sizeof(u16); -+ if (!frame) /* Not enough data. */ -+ return aligned.ptr - source->ptr; -+ -+ trace_ssam_rx_frame_received(frame); -+ -+ switch (frame->type) { -+ case SSH_FRAME_TYPE_ACK: -+ ssh_ptl_acknowledge(ptl, frame->seq); -+ break; -+ -+ case SSH_FRAME_TYPE_NAK: -+ ssh_ptl_resubmit_pending(ptl); -+ break; -+ -+ case SSH_FRAME_TYPE_DATA_SEQ: -+ ssh_ptl_send_ack(ptl, frame->seq); -+ /* fallthrough */ -+ -+ case SSH_FRAME_TYPE_DATA_NSQ: -+ ssh_ptl_rx_dataframe(ptl, frame, &payload); -+ break; -+ -+ default: -+ ptl_warn(ptl, "ptl: received frame with unknown type %#04x\n", -+ frame->type); -+ break; -+ } -+ -+ return aligned.ptr - source->ptr + SSH_MESSAGE_LENGTH(payload.len); -+} -+ -+static int ssh_ptl_rx_threadfn(void *data) -+{ -+ struct ssh_ptl *ptl = data; -+ -+ while (true) { -+ struct ssam_span span; -+ size_t offs = 0; -+ size_t n; -+ -+ wait_event_interruptible(ptl->rx.wq, -+ !kfifo_is_empty(&ptl->rx.fifo) || -+ kthread_should_stop()); -+ if (kthread_should_stop()) -+ break; -+ -+ /* Copy from fifo to evaluation buffer. */ -+ n = sshp_buf_read_from_fifo(&ptl->rx.buf, &ptl->rx.fifo); -+ -+ ptl_dbg(ptl, "rx: received data (size: %zu)\n", n); -+ print_hex_dump_debug("rx: ", DUMP_PREFIX_OFFSET, 16, 1, -+ ptl->rx.buf.ptr + ptl->rx.buf.len - n, -+ n, false); -+ -+ /* Parse until we need more bytes or buffer is empty. */ -+ while (offs < ptl->rx.buf.len) { -+ sshp_buf_span_from(&ptl->rx.buf, offs, &span); -+ n = ssh_ptl_rx_eval(ptl, &span); -+ if (n == 0) -+ break; /* Need more bytes. */ -+ -+ offs += n; -+ } -+ -+ /* Throw away the evaluated parts. */ -+ sshp_buf_drop(&ptl->rx.buf, offs); -+ } -+ -+ return 0; -+} -+ -+static void ssh_ptl_rx_wakeup(struct ssh_ptl *ptl) -+{ -+ wake_up(&ptl->rx.wq); -+} -+ -+/** -+ * ssh_ptl_rx_start() - Start packet transport layer receiver thread. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_ptl_rx_start(struct ssh_ptl *ptl) -+{ -+ if (ptl->rx.thread) -+ return 0; -+ -+ ptl->rx.thread = kthread_run(ssh_ptl_rx_threadfn, ptl, -+ "ssam_serial_hub-rx"); -+ if (IS_ERR(ptl->rx.thread)) -+ return PTR_ERR(ptl->rx.thread); -+ -+ return 0; -+} -+ -+/** -+ * ssh_ptl_rx_stop() - Stop packet transport layer receiver thread. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_ptl_rx_stop(struct ssh_ptl *ptl) -+{ -+ int status = 0; -+ -+ if (ptl->rx.thread) { -+ status = kthread_stop(ptl->rx.thread); -+ ptl->rx.thread = NULL; -+ } -+ -+ return status; -+} -+ -+/** -+ * ssh_ptl_rx_rcvbuf() - Push data from lower-layer transport to the packet -+ * layer. -+ * @ptl: The packet transport layer. -+ * @buf: Pointer to the data to push to the layer. -+ * @n: Size of the data to push to the layer, in bytes. -+ * -+ * Pushes data from a lower-layer transport to the receiver fifo buffer of the -+ * packet layer and notifies the receiver thread. Calls to this function are -+ * ignored once the packet layer has been shut down. -+ * -+ * Return: Returns the number of bytes transferred (positive or zero) on -+ * success. Returns %-ESHUTDOWN if the packet layer has been shut down. -+ */ -+int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n) -+{ -+ int used; -+ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) -+ return -ESHUTDOWN; -+ -+ used = kfifo_in(&ptl->rx.fifo, buf, n); -+ if (used) -+ ssh_ptl_rx_wakeup(ptl); -+ -+ return used; -+} -+ -+/** -+ * ssh_ptl_shutdown() - Shut down the packet transport layer. -+ * @ptl: The packet transport layer. -+ * -+ * Shuts down the packet transport layer, removing and canceling all queued -+ * and pending packets. Packets canceled by this operation will be completed -+ * with %-ESHUTDOWN as status. Receiver and transmitter threads will be -+ * stopped. -+ * -+ * As a result of this function, the transport layer will be marked as shut -+ * down. Submission of packets after the transport layer has been shut down -+ * will fail with %-ESHUTDOWN. -+ */ -+void ssh_ptl_shutdown(struct ssh_ptl *ptl) -+{ -+ LIST_HEAD(complete_q); -+ LIST_HEAD(complete_p); -+ struct ssh_packet *p, *n; -+ int status; -+ -+ /* Ensure that no new packets (including ACK/NAK) can be submitted. */ -+ set_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state); -+ /* -+ * Ensure that the layer gets marked as shut-down before actually -+ * stopping it. In combination with the check in ssh_ptl_queue_push(), -+ * this guarantees that no new packets can be added and all already -+ * queued packets are properly canceled. In combination with the check -+ * in ssh_ptl_rx_rcvbuf(), this guarantees that received data is -+ * properly cut off. -+ */ -+ smp_mb__after_atomic(); -+ -+ status = ssh_ptl_rx_stop(ptl); -+ if (status) -+ ptl_err(ptl, "ptl: failed to stop receiver thread\n"); -+ -+ status = ssh_ptl_tx_stop(ptl); -+ if (status) -+ ptl_err(ptl, "ptl: failed to stop transmitter thread\n"); -+ -+ cancel_delayed_work_sync(&ptl->rtx_timeout.reaper); -+ -+ /* -+ * At this point, all threads have been stopped. This means that the -+ * only references to packets from inside the system are in the queue -+ * and pending set. -+ * -+ * Note: We still need locks here because someone could still be -+ * canceling packets. -+ * -+ * Note 2: We can re-use queue_node (or pending_node) if we mark the -+ * packet as locked an then remove it from the queue (or pending set -+ * respectively). Marking the packet as locked avoids re-queuing -+ * (which should already be prevented by having stopped the treads...) -+ * and not setting QUEUED_BIT (or PENDING_BIT) prevents removal from a -+ * new list via other threads (e.g. cancellation). -+ * -+ * Note 3: There may be overlap between complete_p and complete_q. -+ * This is handled via test_and_set_bit() on the "completed" flag -+ * (also handles cancellation). -+ */ -+ -+ /* Mark queued packets as locked and move them to complete_q. */ -+ spin_lock(&ptl->queue.lock); -+ list_for_each_entry_safe(p, n, &ptl->queue.head, queue_node) { -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state); -+ /* Ensure that state does not get zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); -+ -+ list_del(&p->queue_node); -+ list_add_tail(&p->queue_node, &complete_q); -+ } -+ spin_unlock(&ptl->queue.lock); -+ -+ /* Mark pending packets as locked and move them to complete_p. */ -+ spin_lock(&ptl->pending.lock); -+ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state); -+ /* Ensure that state does not get zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); -+ -+ list_del(&p->pending_node); -+ list_add_tail(&p->pending_node, &complete_q); -+ } -+ atomic_set(&ptl->pending.count, 0); -+ spin_unlock(&ptl->pending.lock); -+ -+ /* Complete and drop packets on complete_q. */ -+ list_for_each_entry(p, &complete_q, queue_node) { -+ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) -+ __ssh_ptl_complete(p, -ESHUTDOWN); -+ -+ ssh_packet_put(p); -+ } -+ -+ /* Complete and drop packets on complete_p. */ -+ list_for_each_entry(p, &complete_p, pending_node) { -+ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) -+ __ssh_ptl_complete(p, -ESHUTDOWN); -+ -+ ssh_packet_put(p); -+ } -+ -+ /* -+ * At this point we have guaranteed that the system doesn't reference -+ * any packets any more. -+ */ -+} -+ -+/** -+ * ssh_ptl_init() - Initialize packet transport layer. -+ * @ptl: The packet transport layer to initialize. -+ * @serdev: The underlying serial device, i.e. the lower-level transport. -+ * @ops: Packet layer operations. -+ * -+ * Initializes the given packet transport layer. Transmitter and receiver -+ * threads must be started separately via ssh_ptl_tx_start() and -+ * ssh_ptl_rx_start(), after the packet-layer has been initialized and the -+ * lower-level transport layer has been set up. -+ * -+ * Return: Returns zero on success and a nonzero error code on failure. -+ */ -+int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev, -+ struct ssh_ptl_ops *ops) -+{ -+ int i, status; -+ -+ ptl->serdev = serdev; -+ ptl->state = 0; -+ -+ spin_lock_init(&ptl->queue.lock); -+ INIT_LIST_HEAD(&ptl->queue.head); -+ -+ spin_lock_init(&ptl->pending.lock); -+ INIT_LIST_HEAD(&ptl->pending.head); -+ atomic_set_release(&ptl->pending.count, 0); -+ -+ ptl->tx.thread = NULL; -+ atomic_set(&ptl->tx.running, 0); -+ init_completion(&ptl->tx.thread_cplt_pkt); -+ init_completion(&ptl->tx.thread_cplt_tx); -+ init_waitqueue_head(&ptl->tx.packet_wq); -+ -+ ptl->rx.thread = NULL; -+ init_waitqueue_head(&ptl->rx.wq); -+ -+ spin_lock_init(&ptl->rtx_timeout.lock); -+ ptl->rtx_timeout.timeout = SSH_PTL_PACKET_TIMEOUT; -+ ptl->rtx_timeout.expires = KTIME_MAX; -+ INIT_DELAYED_WORK(&ptl->rtx_timeout.reaper, ssh_ptl_timeout_reap); -+ -+ ptl->ops = *ops; -+ -+ /* Initialize list of recent/blocked SEQs with invalid sequence IDs. */ -+ for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) -+ ptl->rx.blocked.seqs[i] = U16_MAX; -+ ptl->rx.blocked.offset = 0; -+ -+ status = kfifo_alloc(&ptl->rx.fifo, SSH_PTL_RX_FIFO_LEN, GFP_KERNEL); -+ if (status) -+ return status; -+ -+ status = sshp_buf_alloc(&ptl->rx.buf, SSH_PTL_RX_BUF_LEN, GFP_KERNEL); -+ if (status) -+ kfifo_free(&ptl->rx.fifo); -+ -+ return status; -+} -+ -+/** -+ * ssh_ptl_destroy() - Deinitialize packet transport layer. -+ * @ptl: The packet transport layer to deinitialize. -+ * -+ * Deinitializes the given packet transport layer and frees resources -+ * associated with it. If receiver and/or transmitter threads have been -+ * started, the layer must first be shut down via ssh_ptl_shutdown() before -+ * this function can be called. -+ */ -+void ssh_ptl_destroy(struct ssh_ptl *ptl) -+{ -+ kfifo_free(&ptl->rx.fifo); -+ sshp_buf_free(&ptl->rx.buf); -+} -diff --git a/drivers/platform/x86/surface_aggregator/ssh_packet_layer.h b/drivers/platform/x86/surface_aggregator/ssh_packet_layer.h -new file mode 100644 -index 000000000000..2eb329f0b91a ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/ssh_packet_layer.h -@@ -0,0 +1,190 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * SSH packet transport layer. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H -+#define _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include "ssh_parser.h" -+ -+/** -+ * enum ssh_ptl_state_flags - State-flags for &struct ssh_ptl. -+ * -+ * @SSH_PTL_SF_SHUTDOWN_BIT: -+ * Indicates that the packet transport layer has been shut down or is -+ * being shut down and should not accept any new packets/data. -+ */ -+enum ssh_ptl_state_flags { -+ SSH_PTL_SF_SHUTDOWN_BIT, -+}; -+ -+/** -+ * struct ssh_ptl_ops - Callback operations for packet transport layer. -+ * @data_received: Function called when a data-packet has been received. Both, -+ * the packet layer on which the packet has been received and -+ * the packet's payload data are provided to this function. -+ */ -+struct ssh_ptl_ops { -+ void (*data_received)(struct ssh_ptl *p, const struct ssam_span *data); -+}; -+ -+/** -+ * struct ssh_ptl - SSH packet transport layer. -+ * @serdev: Serial device providing the underlying data transport. -+ * @state: State(-flags) of the transport layer. -+ * @queue: Packet submission queue. -+ * @queue.lock: Lock for modifying the packet submission queue. -+ * @queue.head: List-head of the packet submission queue. -+ * @pending: Set/list of pending packets. -+ * @pending.lock: Lock for modifying the pending set. -+ * @pending.head: List-head of the pending set/list. -+ * @pending.count: Number of currently pending packets. -+ * @tx: Transmitter subsystem. -+ * @tx.running: Flag indicating (desired) transmitter thread state. -+ * @tx.thread: Transmitter thread. -+ * @tx.thread_cplt_tx: Completion for transmitter thread waiting on transfer. -+ * @tx.thread_cplt_pkt: Completion for transmitter thread waiting on packets. -+ * @tx.packet_wq: Waitqueue-head for packet transmit completion. -+ * @rx: Receiver subsystem. -+ * @rx.thread: Receiver thread. -+ * @rx.wq: Waitqueue-head for receiver thread. -+ * @rx.fifo: Buffer for receiving data/pushing data to receiver thread. -+ * @rx.buf: Buffer for evaluating data on receiver thread. -+ * @rx.blocked: List of recent/blocked sequence IDs to detect retransmission. -+ * @rx.blocked.seqs: Array of blocked sequence IDs. -+ * @rx.blocked.offset: Offset indicating where a new ID should be inserted. -+ * @rtx_timeout: Retransmission timeout subsystem. -+ * @rtx_timeout.lock: Lock for modifying the retransmission timeout reaper. -+ * @rtx_timeout.timeout: Timeout interval for retransmission. -+ * @rtx_timeout.expires: Time specifying when the reaper work is next scheduled. -+ * @rtx_timeout.reaper: Work performing timeout checks and subsequent actions. -+ * @ops: Packet layer operations. -+ */ -+struct ssh_ptl { -+ struct serdev_device *serdev; -+ unsigned long state; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ } queue; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ atomic_t count; -+ } pending; -+ -+ struct { -+ atomic_t running; -+ struct task_struct *thread; -+ struct completion thread_cplt_tx; -+ struct completion thread_cplt_pkt; -+ struct wait_queue_head packet_wq; -+ } tx; -+ -+ struct { -+ struct task_struct *thread; -+ struct wait_queue_head wq; -+ struct kfifo fifo; -+ struct sshp_buf buf; -+ -+ struct { -+ u16 seqs[8]; -+ u16 offset; -+ } blocked; -+ } rx; -+ -+ struct { -+ spinlock_t lock; -+ ktime_t timeout; -+ ktime_t expires; -+ struct delayed_work reaper; -+ } rtx_timeout; -+ -+ struct ssh_ptl_ops ops; -+}; -+ -+#define __ssam_prcond(func, p, fmt, ...) \ -+ do { \ -+ typeof(p) __p = (p); \ -+ \ -+ if (__p) \ -+ func(__p, fmt, ##__VA_ARGS__); \ -+ } while (0) -+ -+#define ptl_dbg(p, fmt, ...) dev_dbg(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_info(p, fmt, ...) dev_info(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_warn(p, fmt, ...) dev_warn(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_err(p, fmt, ...) dev_err(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_dbg_cond(p, fmt, ...) __ssam_prcond(ptl_dbg, p, fmt, ##__VA_ARGS__) -+ -+#define to_ssh_ptl(ptr, member) \ -+ container_of(ptr, struct ssh_ptl, member) -+ -+int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev, -+ struct ssh_ptl_ops *ops); -+ -+void ssh_ptl_destroy(struct ssh_ptl *ptl); -+ -+/** -+ * ssh_ptl_get_device() - Get device associated with packet transport layer. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns the device on which the given packet transport layer builds -+ * upon. -+ */ -+static inline struct device *ssh_ptl_get_device(struct ssh_ptl *ptl) -+{ -+ return ptl->serdev ? &ptl->serdev->dev : NULL; -+} -+ -+int ssh_ptl_tx_start(struct ssh_ptl *ptl); -+int ssh_ptl_tx_stop(struct ssh_ptl *ptl); -+int ssh_ptl_rx_start(struct ssh_ptl *ptl); -+int ssh_ptl_rx_stop(struct ssh_ptl *ptl); -+void ssh_ptl_shutdown(struct ssh_ptl *ptl); -+ -+int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p); -+void ssh_ptl_cancel(struct ssh_packet *p); -+ -+int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n); -+ -+/** -+ * ssh_ptl_tx_wakeup_transfer() - Wake up packet transmitter thread for -+ * transfer. -+ * @ptl: The packet transport layer. -+ * -+ * Wakes up the packet transmitter thread, notifying it that the underlying -+ * transport has more space for data to be transmitted. If the packet -+ * transport layer has been shut down, calls to this function will be ignored. -+ */ -+static inline void ssh_ptl_tx_wakeup_transfer(struct ssh_ptl *ptl) -+{ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) -+ return; -+ -+ complete(&ptl->tx.thread_cplt_tx); -+} -+ -+void ssh_packet_init(struct ssh_packet *packet, unsigned long type, -+ u8 priority, const struct ssh_packet_ops *ops); -+ -+int ssh_ctrl_packet_cache_init(void); -+void ssh_ctrl_packet_cache_destroy(void); -+ -+#endif /* _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H */ -diff --git a/drivers/platform/x86/surface_aggregator/ssh_parser.c b/drivers/platform/x86/surface_aggregator/ssh_parser.c -new file mode 100644 -index 000000000000..b77912f8f13b ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/ssh_parser.c -@@ -0,0 +1,228 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * SSH message parser. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+ -+#include -+#include "ssh_parser.h" -+ -+/** -+ * sshp_validate_crc() - Validate a CRC in raw message data. -+ * @src: The span of data over which the CRC should be computed. -+ * @crc: The pointer to the expected u16 CRC value. -+ * -+ * Computes the CRC of the provided data span (@src), compares it to the CRC -+ * stored at the given address (@crc), and returns the result of this -+ * comparison, i.e. %true if equal. This function is intended to run on raw -+ * input/message data. -+ * -+ * Return: Returns %true if the computed CRC matches the stored CRC, %false -+ * otherwise. -+ */ -+static bool sshp_validate_crc(const struct ssam_span *src, const u8 *crc) -+{ -+ u16 actual = ssh_crc(src->ptr, src->len); -+ u16 expected = get_unaligned_le16(crc); -+ -+ return actual == expected; -+} -+ -+/** -+ * sshp_starts_with_syn() - Check if the given data starts with SSH SYN bytes. -+ * @src: The data span to check the start of. -+ */ -+static bool sshp_starts_with_syn(const struct ssam_span *src) -+{ -+ return src->len >= 2 && get_unaligned_le16(src->ptr) == SSH_MSG_SYN; -+} -+ -+/** -+ * sshp_find_syn() - Find SSH SYN bytes in the given data span. -+ * @src: The data span to search in. -+ * @rem: The span (output) indicating the remaining data, starting with SSH -+ * SYN bytes, if found. -+ * -+ * Search for SSH SYN bytes in the given source span. If found, set the @rem -+ * span to the remaining data, starting with the first SYN bytes and capped by -+ * the source span length, and return %true. This function does not copy any -+ * data, but rather only sets pointers to the respective start addresses and -+ * length values. -+ * -+ * If no SSH SYN bytes could be found, set the @rem span to the zero-length -+ * span at the end of the source span and return %false. -+ * -+ * If partial SSH SYN bytes could be found at the end of the source span, set -+ * the @rem span to cover these partial SYN bytes, capped by the end of the -+ * source span, and return %false. This function should then be re-run once -+ * more data is available. -+ * -+ * Return: Returns %true if a complete SSH SYN sequence could be found, -+ * %false otherwise. -+ */ -+bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem) -+{ -+ size_t i; -+ -+ for (i = 0; i < src->len - 1; i++) { -+ if (likely(get_unaligned_le16(src->ptr + i) == SSH_MSG_SYN)) { -+ rem->ptr = src->ptr + i; -+ rem->len = src->len - i; -+ return true; -+ } -+ } -+ -+ if (unlikely(src->ptr[src->len - 1] == (SSH_MSG_SYN & 0xff))) { -+ rem->ptr = src->ptr + src->len - 1; -+ rem->len = 1; -+ return false; -+ } -+ -+ rem->ptr = src->ptr + src->len; -+ rem->len = 0; -+ return false; -+} -+ -+/** -+ * sshp_parse_frame() - Parse SSH frame. -+ * @dev: The device used for logging. -+ * @source: The source to parse from. -+ * @frame: The parsed frame (output). -+ * @payload: The parsed payload (output). -+ * @maxlen: The maximum supported message length. -+ * -+ * Parses and validates a SSH frame, including its payload, from the given -+ * source. Sets the provided @frame pointer to the start of the frame and -+ * writes the limits of the frame payload to the provided @payload span -+ * pointer. -+ * -+ * This function does not copy any data, but rather only validates the message -+ * data and sets pointers (and length values) to indicate the respective parts. -+ * -+ * If no complete SSH frame could be found, the frame pointer will be set to -+ * the %NULL pointer and the payload span will be set to the null span (start -+ * pointer %NULL, size zero). -+ * -+ * Return: Returns zero on success or if the frame is incomplete, %-ENOMSG if -+ * the start of the message is invalid, %-EBADMSG if any (frame-header or -+ * payload) CRC is invalid, or %-EMSGSIZE if the SSH message is bigger than -+ * the maximum message length specified in the @maxlen parameter. -+ */ -+int sshp_parse_frame(const struct device *dev, const struct ssam_span *source, -+ struct ssh_frame **frame, struct ssam_span *payload, -+ size_t maxlen) -+{ -+ struct ssam_span sf; -+ struct ssam_span sp; -+ -+ /* Initialize output. */ -+ *frame = NULL; -+ payload->ptr = NULL; -+ payload->len = 0; -+ -+ if (!sshp_starts_with_syn(source)) { -+ dev_warn(dev, "rx: parser: invalid start of frame\n"); -+ return -ENOMSG; -+ } -+ -+ /* Check for minimum packet length. */ -+ if (unlikely(source->len < SSH_MESSAGE_LENGTH(0))) { -+ dev_dbg(dev, "rx: parser: not enough data for frame\n"); -+ return 0; -+ } -+ -+ /* Pin down frame. */ -+ sf.ptr = source->ptr + sizeof(u16); -+ sf.len = sizeof(struct ssh_frame); -+ -+ /* Validate frame CRC. */ -+ if (unlikely(!sshp_validate_crc(&sf, sf.ptr + sf.len))) { -+ dev_warn(dev, "rx: parser: invalid frame CRC\n"); -+ return -EBADMSG; -+ } -+ -+ /* Ensure packet does not exceed maximum length. */ -+ sp.len = get_unaligned_le16(&((struct ssh_frame *)sf.ptr)->len); -+ if (unlikely(SSH_MESSAGE_LENGTH(sp.len) > maxlen)) { -+ dev_warn(dev, "rx: parser: frame too large: %llu bytes\n", -+ SSH_MESSAGE_LENGTH(sp.len)); -+ return -EMSGSIZE; -+ } -+ -+ /* Pin down payload. */ -+ sp.ptr = sf.ptr + sf.len + sizeof(u16); -+ -+ /* Check for frame + payload length. */ -+ if (source->len < SSH_MESSAGE_LENGTH(sp.len)) { -+ dev_dbg(dev, "rx: parser: not enough data for payload\n"); -+ return 0; -+ } -+ -+ /* Validate payload CRC. */ -+ if (unlikely(!sshp_validate_crc(&sp, sp.ptr + sp.len))) { -+ dev_warn(dev, "rx: parser: invalid payload CRC\n"); -+ return -EBADMSG; -+ } -+ -+ *frame = (struct ssh_frame *)sf.ptr; -+ *payload = sp; -+ -+ dev_dbg(dev, "rx: parser: valid frame found (type: %#04x, len: %u)\n", -+ (*frame)->type, (*frame)->len); -+ -+ return 0; -+} -+ -+/** -+ * sshp_parse_command() - Parse SSH command frame payload. -+ * @dev: The device used for logging. -+ * @source: The source to parse from. -+ * @command: The parsed command (output). -+ * @command_data: The parsed command data/payload (output). -+ * -+ * Parses and validates a SSH command frame payload. Sets the @command pointer -+ * to the command header and the @command_data span to the command data (i.e. -+ * payload of the command). This will result in a zero-length span if the -+ * command does not have any associated data/payload. This function does not -+ * check the frame-payload-type field, which should be checked by the caller -+ * before calling this function. -+ * -+ * The @source parameter should be the complete frame payload, e.g. returned -+ * by the sshp_parse_frame() command. -+ * -+ * This function does not copy any data, but rather only validates the frame -+ * payload data and sets pointers (and length values) to indicate the -+ * respective parts. -+ * -+ * Return: Returns zero on success or %-ENOMSG if @source does not represent a -+ * valid command-type frame payload, i.e. is too short. -+ */ -+int sshp_parse_command(const struct device *dev, const struct ssam_span *source, -+ struct ssh_command **command, -+ struct ssam_span *command_data) -+{ -+ /* Check for minimum length. */ -+ if (unlikely(source->len < sizeof(struct ssh_command))) { -+ *command = NULL; -+ command_data->ptr = NULL; -+ command_data->len = 0; -+ -+ dev_err(dev, "rx: parser: command payload is too short\n"); -+ return -ENOMSG; -+ } -+ -+ *command = (struct ssh_command *)source->ptr; -+ command_data->ptr = source->ptr + sizeof(struct ssh_command); -+ command_data->len = source->len - sizeof(struct ssh_command); -+ -+ dev_dbg(dev, "rx: parser: valid command found (tc: %#04x, cid: %#04x)\n", -+ (*command)->tc, (*command)->cid); -+ -+ return 0; -+} -diff --git a/drivers/platform/x86/surface_aggregator/ssh_parser.h b/drivers/platform/x86/surface_aggregator/ssh_parser.h -new file mode 100644 -index 000000000000..3bd6e180fd16 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/ssh_parser.h -@@ -0,0 +1,154 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * SSH message parser. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H -+#define _SURFACE_AGGREGATOR_SSH_PARSER_H -+ -+#include -+#include -+#include -+#include -+ -+#include -+ -+/** -+ * struct sshp_buf - Parser buffer for SSH messages. -+ * @ptr: Pointer to the beginning of the buffer. -+ * @len: Number of bytes used in the buffer. -+ * @cap: Maximum capacity of the buffer. -+ */ -+struct sshp_buf { -+ u8 *ptr; -+ size_t len; -+ size_t cap; -+}; -+ -+/** -+ * sshp_buf_init() - Initialize a SSH parser buffer. -+ * @buf: The buffer to initialize. -+ * @ptr: The memory backing the buffer. -+ * @cap: The length of the memory backing the buffer, i.e. its capacity. -+ * -+ * Initializes the buffer with the given memory as backing and set its used -+ * length to zero. -+ */ -+static inline void sshp_buf_init(struct sshp_buf *buf, u8 *ptr, size_t cap) -+{ -+ buf->ptr = ptr; -+ buf->len = 0; -+ buf->cap = cap; -+} -+ -+/** -+ * sshp_buf_alloc() - Allocate and initialize a SSH parser buffer. -+ * @buf: The buffer to initialize/allocate to. -+ * @cap: The desired capacity of the buffer. -+ * @flags: The flags used for allocating the memory. -+ * -+ * Allocates @cap bytes and initializes the provided buffer struct with the -+ * allocated memory. -+ * -+ * Return: Returns zero on success and %-ENOMEM if allocation failed. -+ */ -+static inline int sshp_buf_alloc(struct sshp_buf *buf, size_t cap, gfp_t flags) -+{ -+ u8 *ptr; -+ -+ ptr = kzalloc(cap, flags); -+ if (!ptr) -+ return -ENOMEM; -+ -+ sshp_buf_init(buf, ptr, cap); -+ return 0; -+} -+ -+/** -+ * sshp_buf_free() - Free a SSH parser buffer. -+ * @buf: The buffer to free. -+ * -+ * Frees a SSH parser buffer by freeing the memory backing it and then -+ * resetting its pointer to %NULL and length and capacity to zero. Intended to -+ * free a buffer previously allocated with sshp_buf_alloc(). -+ */ -+static inline void sshp_buf_free(struct sshp_buf *buf) -+{ -+ kfree(buf->ptr); -+ buf->ptr = NULL; -+ buf->len = 0; -+ buf->cap = 0; -+} -+ -+/** -+ * sshp_buf_drop() - Drop data from the beginning of the buffer. -+ * @buf: The buffer to drop data from. -+ * @n: The number of bytes to drop. -+ * -+ * Drops the first @n bytes from the buffer. Re-aligns any remaining data to -+ * the beginning of the buffer. -+ */ -+static inline void sshp_buf_drop(struct sshp_buf *buf, size_t n) -+{ -+ memmove(buf->ptr, buf->ptr + n, buf->len - n); -+ buf->len -= n; -+} -+ -+/** -+ * sshp_buf_read_from_fifo() - Transfer data from a fifo to the buffer. -+ * @buf: The buffer to write the data into. -+ * @fifo: The fifo to read the data from. -+ * -+ * Transfers the data contained in the fifo to the buffer, removing it from -+ * the fifo. This function will try to transfer as much data as possible, -+ * limited either by the remaining space in the buffer or by the number of -+ * bytes available in the fifo. -+ * -+ * Return: Returns the number of bytes transferred. -+ */ -+static inline size_t sshp_buf_read_from_fifo(struct sshp_buf *buf, -+ struct kfifo *fifo) -+{ -+ size_t n; -+ -+ n = kfifo_out(fifo, buf->ptr + buf->len, buf->cap - buf->len); -+ buf->len += n; -+ -+ return n; -+} -+ -+/** -+ * sshp_buf_span_from() - Initialize a span from the given buffer and offset. -+ * @buf: The buffer to create the span from. -+ * @offset: The offset in the buffer at which the span should start. -+ * @span: The span to initialize (output). -+ * -+ * Initializes the provided span to point to the memory at the given offset in -+ * the buffer, with the length of the span being capped by the number of bytes -+ * used in the buffer after the offset (i.e. bytes remaining after the -+ * offset). -+ * -+ * Warning: This function does not validate that @offset is less than or equal -+ * to the number of bytes used in the buffer or the buffer capacity. This must -+ * be guaranteed by the caller. -+ */ -+static inline void sshp_buf_span_from(struct sshp_buf *buf, size_t offset, -+ struct ssam_span *span) -+{ -+ span->ptr = buf->ptr + offset; -+ span->len = buf->len - offset; -+} -+ -+bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem); -+ -+int sshp_parse_frame(const struct device *dev, const struct ssam_span *source, -+ struct ssh_frame **frame, struct ssam_span *payload, -+ size_t maxlen); -+ -+int sshp_parse_command(const struct device *dev, const struct ssam_span *source, -+ struct ssh_command **command, -+ struct ssam_span *command_data); -+ -+#endif /* _SURFACE_AGGREGATOR_SSH_PARSER_h */ -diff --git a/drivers/platform/x86/surface_aggregator/ssh_request_layer.c b/drivers/platform/x86/surface_aggregator/ssh_request_layer.c -new file mode 100644 -index 000000000000..bfe1aaf38065 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/ssh_request_layer.c -@@ -0,0 +1,1263 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * SSH request transport layer. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "ssh_packet_layer.h" -+#include "ssh_request_layer.h" -+ -+#include "trace.h" -+ -+/* -+ * SSH_RTL_REQUEST_TIMEOUT - Request timeout. -+ * -+ * Timeout as ktime_t delta for request responses. If we have not received a -+ * response in this time-frame after finishing the underlying packet -+ * transmission, the request will be completed with %-ETIMEDOUT as status -+ * code. -+ */ -+#define SSH_RTL_REQUEST_TIMEOUT ms_to_ktime(3000) -+ -+/* -+ * SSH_RTL_REQUEST_TIMEOUT_RESOLUTION - Request timeout granularity. -+ * -+ * Time-resolution for timeouts. Should be larger than one jiffy to avoid -+ * direct re-scheduling of reaper work_struct. -+ */ -+#define SSH_RTL_REQUEST_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) -+ -+/* -+ * SSH_RTL_MAX_PENDING - Maximum number of pending requests. -+ * -+ * Maximum number of requests concurrently waiting to be completed (i.e. -+ * waiting for the corresponding packet transmission to finish if they don't -+ * have a response or waiting for a response if they have one). -+ */ -+#define SSH_RTL_MAX_PENDING 3 -+ -+/* -+ * SSH_RTL_TX_BATCH - Maximum number of requests processed per work execution. -+ * Used to prevent livelocking of the workqueue. Value chosen via educated -+ * guess, may be adjusted. -+ */ -+#define SSH_RTL_TX_BATCH 10 -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION -+ -+/** -+ * ssh_rtl_should_drop_response() - Error injection hook to drop request -+ * responses. -+ * -+ * Useful to cause request transmission timeouts in the driver by dropping the -+ * response to a request. -+ */ -+static noinline bool ssh_rtl_should_drop_response(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_rtl_should_drop_response, TRUE); -+ -+#else -+ -+static inline bool ssh_rtl_should_drop_response(void) -+{ -+ return false; -+} -+ -+#endif -+ -+static u16 ssh_request_get_rqid(struct ssh_request *rqst) -+{ -+ return get_unaligned_le16(rqst->packet.data.ptr -+ + SSH_MSGOFFSET_COMMAND(rqid)); -+} -+ -+static u32 ssh_request_get_rqid_safe(struct ssh_request *rqst) -+{ -+ if (!rqst->packet.data.ptr) -+ return U32_MAX; -+ -+ return ssh_request_get_rqid(rqst); -+} -+ -+static void ssh_rtl_queue_remove(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ spin_lock(&rtl->queue.lock); -+ -+ if (!test_and_clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &rqst->state)) { -+ spin_unlock(&rtl->queue.lock); -+ return; -+ } -+ -+ list_del(&rqst->node); -+ -+ spin_unlock(&rtl->queue.lock); -+ ssh_request_put(rqst); -+} -+ -+static bool ssh_rtl_queue_empty(struct ssh_rtl *rtl) -+{ -+ bool empty; -+ -+ spin_lock(&rtl->queue.lock); -+ empty = list_empty(&rtl->queue.head); -+ spin_unlock(&rtl->queue.lock); -+ -+ return empty; -+} -+ -+static void ssh_rtl_pending_remove(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ spin_lock(&rtl->pending.lock); -+ -+ if (!test_and_clear_bit(SSH_REQUEST_SF_PENDING_BIT, &rqst->state)) { -+ spin_unlock(&rtl->pending.lock); -+ return; -+ } -+ -+ atomic_dec(&rtl->pending.count); -+ list_del(&rqst->node); -+ -+ spin_unlock(&rtl->pending.lock); -+ -+ ssh_request_put(rqst); -+} -+ -+static int ssh_rtl_tx_pending_push(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ spin_lock(&rtl->pending.lock); -+ -+ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) { -+ spin_unlock(&rtl->pending.lock); -+ return -EINVAL; -+ } -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_PENDING_BIT, &rqst->state)) { -+ spin_unlock(&rtl->pending.lock); -+ return -EALREADY; -+ } -+ -+ atomic_inc(&rtl->pending.count); -+ list_add_tail(&ssh_request_get(rqst)->node, &rtl->pending.head); -+ -+ spin_unlock(&rtl->pending.lock); -+ return 0; -+} -+ -+static void ssh_rtl_complete_with_status(struct ssh_request *rqst, int status) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ trace_ssam_request_complete(rqst, status); -+ -+ /* rtl/ptl may not be set if we're canceling before submitting. */ -+ rtl_dbg_cond(rtl, "rtl: completing request (rqid: %#06x, status: %d)\n", -+ ssh_request_get_rqid_safe(rqst), status); -+ -+ rqst->ops->complete(rqst, NULL, NULL, status); -+} -+ -+static void ssh_rtl_complete_with_rsp(struct ssh_request *rqst, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ trace_ssam_request_complete(rqst, 0); -+ -+ rtl_dbg(rtl, "rtl: completing request with response (rqid: %#06x)\n", -+ ssh_request_get_rqid(rqst)); -+ -+ rqst->ops->complete(rqst, cmd, data, 0); -+} -+ -+static bool ssh_rtl_tx_can_process(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ if (test_bit(SSH_REQUEST_TY_FLUSH_BIT, &rqst->state)) -+ return !atomic_read(&rtl->pending.count); -+ -+ return atomic_read(&rtl->pending.count) < SSH_RTL_MAX_PENDING; -+} -+ -+static struct ssh_request *ssh_rtl_tx_next(struct ssh_rtl *rtl) -+{ -+ struct ssh_request *rqst = ERR_PTR(-ENOENT); -+ struct ssh_request *p, *n; -+ -+ spin_lock(&rtl->queue.lock); -+ -+ /* Find first non-locked request and remove it. */ -+ list_for_each_entry_safe(p, n, &rtl->queue.head, node) { -+ if (unlikely(test_bit(SSH_REQUEST_SF_LOCKED_BIT, &p->state))) -+ continue; -+ -+ if (!ssh_rtl_tx_can_process(p)) { -+ rqst = ERR_PTR(-EBUSY); -+ break; -+ } -+ -+ /* Remove from queue and mark as transmitting. */ -+ set_bit(SSH_REQUEST_SF_TRANSMITTING_BIT, &p->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &p->state); -+ -+ list_del(&p->node); -+ -+ rqst = p; -+ break; -+ } -+ -+ spin_unlock(&rtl->queue.lock); -+ return rqst; -+} -+ -+static int ssh_rtl_tx_try_process_one(struct ssh_rtl *rtl) -+{ -+ struct ssh_request *rqst; -+ int status; -+ -+ /* Get and prepare next request for transmit. */ -+ rqst = ssh_rtl_tx_next(rtl); -+ if (IS_ERR(rqst)) -+ return PTR_ERR(rqst); -+ -+ /* Add it to/mark it as pending. */ -+ status = ssh_rtl_tx_pending_push(rqst); -+ if (status) { -+ ssh_request_put(rqst); -+ return -EAGAIN; -+ } -+ -+ /* Submit packet. */ -+ status = ssh_ptl_submit(&rtl->ptl, &rqst->packet); -+ if (status == -ESHUTDOWN) { -+ /* -+ * Packet has been refused due to the packet layer shutting -+ * down. Complete it here. -+ */ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state); -+ /* -+ * Note: A barrier is not required here, as there are only two -+ * references in the system at this point: The one that we have, -+ * and the other one that belongs to the pending set. Due to the -+ * request being marked as "transmitting", our process is the -+ * only one allowed to remove the pending node and change the -+ * state. Normally, the task would fall to the packet callback, -+ * but as this is a path where submission failed, this callback -+ * will never be executed. -+ */ -+ -+ ssh_rtl_pending_remove(rqst); -+ ssh_rtl_complete_with_status(rqst, -ESHUTDOWN); -+ -+ ssh_request_put(rqst); -+ return -ESHUTDOWN; -+ -+ } else if (status) { -+ /* -+ * If submitting the packet failed and the packet layer isn't -+ * shutting down, the packet has either been submitted/queued -+ * before (-EALREADY, which cannot happen as we have -+ * guaranteed that requests cannot be re-submitted), or the -+ * packet was marked as locked (-EINVAL). To mark the packet -+ * locked at this stage, the request, and thus the packets -+ * itself, had to have been canceled. Simply drop the -+ * reference. Cancellation itself will remove it from the set -+ * of pending requests. -+ */ -+ -+ WARN_ON(status != -EINVAL); -+ -+ ssh_request_put(rqst); -+ return -EAGAIN; -+ } -+ -+ ssh_request_put(rqst); -+ return 0; -+} -+ -+static bool ssh_rtl_tx_schedule(struct ssh_rtl *rtl) -+{ -+ if (atomic_read(&rtl->pending.count) >= SSH_RTL_MAX_PENDING) -+ return false; -+ -+ if (ssh_rtl_queue_empty(rtl)) -+ return false; -+ -+ return schedule_work(&rtl->tx.work); -+} -+ -+static void ssh_rtl_tx_work_fn(struct work_struct *work) -+{ -+ struct ssh_rtl *rtl = to_ssh_rtl(work, tx.work); -+ unsigned int iterations = SSH_RTL_TX_BATCH; -+ int status; -+ -+ /* -+ * Try to be nice and not block/live-lock the workqueue: Run a maximum -+ * of 10 tries, then re-submit if necessary. This should not be -+ * necessary for normal execution, but guarantee it anyway. -+ */ -+ do { -+ status = ssh_rtl_tx_try_process_one(rtl); -+ if (status == -ENOENT || status == -EBUSY) -+ return; /* No more requests to process. */ -+ -+ if (status == -ESHUTDOWN) { -+ /* -+ * Packet system shutting down. No new packets can be -+ * transmitted. Return silently, the party initiating -+ * the shutdown should handle the rest. -+ */ -+ return; -+ } -+ -+ WARN_ON(status != 0 && status != -EAGAIN); -+ } while (--iterations); -+ -+ /* Out of tries, reschedule. */ -+ ssh_rtl_tx_schedule(rtl); -+} -+ -+/** -+ * ssh_rtl_submit() - Submit a request to the transport layer. -+ * @rtl: The request transport layer. -+ * @rqst: The request to submit. -+ * -+ * Submits a request to the transport layer. A single request may not be -+ * submitted multiple times without reinitializing it. -+ * -+ * Return: Returns zero on success, %-EINVAL if the request type is invalid or -+ * the request has been canceled prior to submission, %-EALREADY if the -+ * request has already been submitted, or %-ESHUTDOWN in case the request -+ * transport layer has been shut down. -+ */ -+int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst) -+{ -+ trace_ssam_request_submit(rqst); -+ -+ /* -+ * Ensure that requests expecting a response are sequenced. If this -+ * invariant ever changes, see the comment in ssh_rtl_complete() on what -+ * is required to be changed in the code. -+ */ -+ if (test_bit(SSH_REQUEST_TY_HAS_RESPONSE_BIT, &rqst->state)) -+ if (!test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &rqst->packet.state)) -+ return -EINVAL; -+ -+ spin_lock(&rtl->queue.lock); -+ -+ /* -+ * Try to set ptl and check if this request has already been submitted. -+ * -+ * Must be inside lock as we might run into a lost update problem -+ * otherwise: If this were outside of the lock, cancellation in -+ * ssh_rtl_cancel_nonpending() may run after we've set the ptl -+ * reference but before we enter the lock. In that case, we'd detect -+ * that the request is being added to the queue and would try to remove -+ * it from that, but removal might fail because it hasn't actually been -+ * added yet. By putting this cmpxchg in the critical section, we -+ * ensure that the queuing detection only triggers when we are already -+ * in the critical section and the remove process will wait until the -+ * push operation has been completed (via lock) due to that. Only then, -+ * we can safely try to remove it. -+ */ -+ if (cmpxchg(&rqst->packet.ptl, NULL, &rtl->ptl)) { -+ spin_unlock(&rtl->queue.lock); -+ return -EALREADY; -+ } -+ -+ /* -+ * Ensure that we set ptl reference before we continue modifying state. -+ * This is required for non-pending cancellation. This barrier is paired -+ * with the one in ssh_rtl_cancel_nonpending(). -+ * -+ * By setting the ptl reference before we test for "locked", we can -+ * check if the "locked" test may have already run. See comments in -+ * ssh_rtl_cancel_nonpending() for more detail. -+ */ -+ smp_mb__after_atomic(); -+ -+ if (test_bit(SSH_RTL_SF_SHUTDOWN_BIT, &rtl->state)) { -+ spin_unlock(&rtl->queue.lock); -+ return -ESHUTDOWN; -+ } -+ -+ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) { -+ spin_unlock(&rtl->queue.lock); -+ return -EINVAL; -+ } -+ -+ set_bit(SSH_REQUEST_SF_QUEUED_BIT, &rqst->state); -+ list_add_tail(&ssh_request_get(rqst)->node, &rtl->queue.head); -+ -+ spin_unlock(&rtl->queue.lock); -+ -+ ssh_rtl_tx_schedule(rtl); -+ return 0; -+} -+ -+static void ssh_rtl_timeout_reaper_mod(struct ssh_rtl *rtl, ktime_t now, -+ ktime_t expires) -+{ -+ unsigned long delta = msecs_to_jiffies(ktime_ms_delta(expires, now)); -+ ktime_t aexp = ktime_add(expires, SSH_RTL_REQUEST_TIMEOUT_RESOLUTION); -+ -+ spin_lock(&rtl->rtx_timeout.lock); -+ -+ /* Re-adjust / schedule reaper only if it is above resolution delta. */ -+ if (ktime_before(aexp, rtl->rtx_timeout.expires)) { -+ rtl->rtx_timeout.expires = expires; -+ mod_delayed_work(system_wq, &rtl->rtx_timeout.reaper, delta); -+ } -+ -+ spin_unlock(&rtl->rtx_timeout.lock); -+} -+ -+static void ssh_rtl_timeout_start(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ ktime_t timestamp = ktime_get_coarse_boottime(); -+ ktime_t timeout = rtl->rtx_timeout.timeout; -+ -+ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) -+ return; -+ -+ /* -+ * Note: The timestamp gets set only once. This happens on the packet -+ * callback. All other access to it is read-only. -+ */ -+ WRITE_ONCE(rqst->timestamp, timestamp); -+ /* -+ * Ensure timestamp is set before starting the reaper. Paired with -+ * implicit barrier following check on ssh_request_get_expiration() in -+ * ssh_rtl_timeout_reap. -+ */ -+ smp_mb__after_atomic(); -+ -+ ssh_rtl_timeout_reaper_mod(rtl, timestamp, timestamp + timeout); -+} -+ -+static void ssh_rtl_complete(struct ssh_rtl *rtl, -+ const struct ssh_command *command, -+ const struct ssam_span *command_data) -+{ -+ struct ssh_request *r = NULL; -+ struct ssh_request *p, *n; -+ u16 rqid = get_unaligned_le16(&command->rqid); -+ -+ trace_ssam_rx_response_received(command, command_data->len); -+ -+ /* -+ * Get request from pending based on request ID and mark it as response -+ * received and locked. -+ */ -+ spin_lock(&rtl->pending.lock); -+ list_for_each_entry_safe(p, n, &rtl->pending.head, node) { -+ /* We generally expect requests to be processed in order. */ -+ if (unlikely(ssh_request_get_rqid(p) != rqid)) -+ continue; -+ -+ /* Simulate response timeout. */ -+ if (ssh_rtl_should_drop_response()) { -+ spin_unlock(&rtl->pending.lock); -+ -+ trace_ssam_ei_rx_drop_response(p); -+ rtl_info(rtl, "request error injection: dropping response for request %p\n", -+ &p->packet); -+ return; -+ } -+ -+ /* -+ * Mark as "response received" and "locked" as we're going to -+ * complete it. -+ */ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &p->state); -+ set_bit(SSH_REQUEST_SF_RSPRCVD_BIT, &p->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &p->state); -+ -+ atomic_dec(&rtl->pending.count); -+ list_del(&p->node); -+ -+ r = p; -+ break; -+ } -+ spin_unlock(&rtl->pending.lock); -+ -+ if (!r) { -+ rtl_warn(rtl, "rtl: dropping unexpected command message (rqid = %#06x)\n", -+ rqid); -+ return; -+ } -+ -+ /* If the request hasn't been completed yet, we will do this now. */ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) { -+ ssh_request_put(r); -+ ssh_rtl_tx_schedule(rtl); -+ return; -+ } -+ -+ /* -+ * Make sure the request has been transmitted. In case of a sequenced -+ * request, we are guaranteed that the completion callback will run on -+ * the receiver thread directly when the ACK for the packet has been -+ * received. Similarly, this function is guaranteed to run on the -+ * receiver thread. Thus we are guaranteed that if the packet has been -+ * successfully transmitted and received an ACK, the transmitted flag -+ * has been set and is visible here. -+ * -+ * We are currently not handling unsequenced packets here, as those -+ * should never expect a response as ensured in ssh_rtl_submit. If this -+ * ever changes, one would have to test for -+ * -+ * (r->state & (transmitting | transmitted)) -+ * -+ * on unsequenced packets to determine if they could have been -+ * transmitted. There are no synchronization guarantees as in the -+ * sequenced case, since, in this case, the callback function will not -+ * run on the same thread. Thus an exact determination is impossible. -+ */ -+ if (!test_bit(SSH_REQUEST_SF_TRANSMITTED_BIT, &r->state)) { -+ rtl_err(rtl, "rtl: received response before ACK for request (rqid = %#06x)\n", -+ rqid); -+ -+ /* -+ * NB: Timeout has already been canceled, request already been -+ * removed from pending and marked as locked and completed. As -+ * we receive a "false" response, the packet might still be -+ * queued though. -+ */ -+ ssh_rtl_queue_remove(r); -+ -+ ssh_rtl_complete_with_status(r, -EREMOTEIO); -+ ssh_request_put(r); -+ -+ ssh_rtl_tx_schedule(rtl); -+ return; -+ } -+ -+ /* -+ * NB: Timeout has already been canceled, request already been -+ * removed from pending and marked as locked and completed. The request -+ * can also not be queued any more, as it has been marked as -+ * transmitting and later transmitted. Thus no need to remove it from -+ * anywhere. -+ */ -+ -+ ssh_rtl_complete_with_rsp(r, command, command_data); -+ ssh_request_put(r); -+ -+ ssh_rtl_tx_schedule(rtl); -+} -+ -+static bool ssh_rtl_cancel_nonpending(struct ssh_request *r) -+{ -+ struct ssh_rtl *rtl; -+ unsigned long flags, fixed; -+ bool remove; -+ -+ /* -+ * Handle unsubmitted request: Try to mark the packet as locked, -+ * expecting the state to be zero (i.e. unsubmitted). Note that, if -+ * setting the state worked, we might still be adding the packet to the -+ * queue in a currently executing submit call. In that case, however, -+ * ptl reference must have been set previously, as locked is checked -+ * after setting ptl. Furthermore, when the ptl reference is set, the -+ * submission process is guaranteed to have entered the critical -+ * section. Thus only if we successfully locked this request and ptl is -+ * NULL, we have successfully removed the request, i.e. we are -+ * guaranteed that, due to the "locked" check in ssh_rtl_submit(), the -+ * packet will never be added. Otherwise, we need to try and grab it -+ * from the queue, where we are now guaranteed that the packet is or has -+ * been due to the critical section. -+ * -+ * Note that if the cmpxchg() fails, we are guaranteed that ptl has -+ * been set and is non-NULL, as states can only be nonzero after this -+ * has been set. Also note that we need to fetch the static (type) -+ * flags to ensure that they don't cause the cmpxchg() to fail. -+ */ -+ fixed = READ_ONCE(r->state) & SSH_REQUEST_FLAGS_TY_MASK; -+ flags = cmpxchg(&r->state, fixed, SSH_REQUEST_SF_LOCKED_BIT); -+ -+ /* -+ * Force correct ordering with regards to state and ptl reference access -+ * to safe-guard cancellation to concurrent submission against a -+ * lost-update problem. First try to exchange state, then also check -+ * ptl if that worked. This barrier is paired with the -+ * one in ssh_rtl_submit(). -+ */ -+ smp_mb__after_atomic(); -+ -+ if (flags == fixed && !READ_ONCE(r->packet.ptl)) { -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ return true; -+ } -+ -+ rtl = ssh_request_rtl(r); -+ spin_lock(&rtl->queue.lock); -+ -+ /* -+ * Note: 1) Requests cannot be re-submitted. 2) If a request is -+ * queued, it cannot be "transmitting"/"pending" yet. Thus, if we -+ * successfully remove the request here, we have removed all its -+ * occurrences in the system. -+ */ -+ -+ remove = test_and_clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); -+ if (!remove) { -+ spin_unlock(&rtl->queue.lock); -+ return false; -+ } -+ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ list_del(&r->node); -+ -+ spin_unlock(&rtl->queue.lock); -+ -+ ssh_request_put(r); /* Drop reference obtained from queue. */ -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ return true; -+} -+ -+static bool ssh_rtl_cancel_pending(struct ssh_request *r) -+{ -+ /* If the packet is already locked, it's going to be removed shortly. */ -+ if (test_and_set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state)) -+ return true; -+ -+ /* -+ * Now that we have locked the packet, we have guaranteed that it can't -+ * be added to the system any more. If ptl is NULL, the locked -+ * check in ssh_rtl_submit() has not been run and any submission, -+ * currently in progress or called later, won't add the packet. Thus we -+ * can directly complete it. -+ * -+ * The implicit memory barrier of test_and_set_bit() should be enough -+ * to ensure that the correct order (first lock, then check ptl) is -+ * ensured. This is paired with the barrier in ssh_rtl_submit(). -+ */ -+ if (!READ_ONCE(r->packet.ptl)) { -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ return true; -+ } -+ -+ /* -+ * Try to cancel the packet. If the packet has not been completed yet, -+ * this will subsequently (and synchronously) call the completion -+ * callback of the packet, which will complete the request. -+ */ -+ ssh_ptl_cancel(&r->packet); -+ -+ /* -+ * If the packet has been completed with success, i.e. has not been -+ * canceled by the above call, the request may not have been completed -+ * yet (may be waiting for a response). Check if we need to do this -+ * here. -+ */ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_queue_remove(r); -+ ssh_rtl_pending_remove(r); -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ -+ return true; -+} -+ -+/** -+ * ssh_rtl_cancel() - Cancel request. -+ * @rqst: The request to cancel. -+ * @pending: Whether to also cancel pending requests. -+ * -+ * Cancels the given request. If @pending is %false, this will not cancel -+ * pending requests, i.e. requests that have already been submitted to the -+ * packet layer but not been completed yet. If @pending is %true, this will -+ * cancel the given request regardless of the state it is in. -+ * -+ * If the request has been canceled by calling this function, both completion -+ * and release callbacks of the request will be executed in a reasonable -+ * time-frame. This may happen during execution of this function, however, -+ * there is no guarantee for this. For example, a request currently -+ * transmitting will be canceled/completed only after transmission has -+ * completed, and the respective callbacks will be executed on the transmitter -+ * thread, which may happen during, but also some time after execution of the -+ * cancel function. -+ * -+ * Return: Returns %true if the given request has been canceled or completed, -+ * either by this function or prior to calling this function, %false -+ * otherwise. If @pending is %true, this function will always return %true. -+ */ -+bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending) -+{ -+ struct ssh_rtl *rtl; -+ bool canceled; -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_CANCELED_BIT, &rqst->state)) -+ return true; -+ -+ trace_ssam_request_cancel(rqst); -+ -+ if (pending) -+ canceled = ssh_rtl_cancel_pending(rqst); -+ else -+ canceled = ssh_rtl_cancel_nonpending(rqst); -+ -+ /* Note: rtl may be NULL if request has not been submitted yet. */ -+ rtl = ssh_request_rtl(rqst); -+ if (canceled && rtl) -+ ssh_rtl_tx_schedule(rtl); -+ -+ return canceled; -+} -+ -+static void ssh_rtl_packet_callback(struct ssh_packet *p, int status) -+{ -+ struct ssh_request *r = to_ssh_request(p); -+ -+ if (unlikely(status)) { -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return; -+ -+ /* -+ * The packet may get canceled even though it has not been -+ * submitted yet. The request may still be queued. Check the -+ * queue and remove it if necessary. As the timeout would have -+ * been started in this function on success, there's no need -+ * to cancel it here. -+ */ -+ ssh_rtl_queue_remove(r); -+ ssh_rtl_pending_remove(r); -+ ssh_rtl_complete_with_status(r, status); -+ -+ ssh_rtl_tx_schedule(ssh_request_rtl(r)); -+ return; -+ } -+ -+ /* Update state: Mark as transmitted and clear transmitting. */ -+ set_bit(SSH_REQUEST_SF_TRANSMITTED_BIT, &r->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_TRANSMITTING_BIT, &r->state); -+ -+ /* If we expect a response, we just need to start the timeout. */ -+ if (test_bit(SSH_REQUEST_TY_HAS_RESPONSE_BIT, &r->state)) { -+ /* -+ * Note: This is the only place where the timestamp gets set, -+ * all other access to it is read-only. -+ */ -+ ssh_rtl_timeout_start(r); -+ return; -+ } -+ -+ /* -+ * If we don't expect a response, lock, remove, and complete the -+ * request. Note that, at this point, the request is guaranteed to have -+ * left the queue and no timeout has been started. Thus we only need to -+ * remove it from pending. If the request has already been completed (it -+ * may have been canceled) return. -+ */ -+ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return; -+ -+ ssh_rtl_pending_remove(r); -+ ssh_rtl_complete_with_status(r, 0); -+ -+ ssh_rtl_tx_schedule(ssh_request_rtl(r)); -+} -+ -+static ktime_t ssh_request_get_expiration(struct ssh_request *r, ktime_t timeout) -+{ -+ ktime_t timestamp = READ_ONCE(r->timestamp); -+ -+ if (timestamp != KTIME_MAX) -+ return ktime_add(timestamp, timeout); -+ else -+ return KTIME_MAX; -+} -+ -+static void ssh_rtl_timeout_reap(struct work_struct *work) -+{ -+ struct ssh_rtl *rtl = to_ssh_rtl(work, rtx_timeout.reaper.work); -+ struct ssh_request *r, *n; -+ LIST_HEAD(claimed); -+ ktime_t now = ktime_get_coarse_boottime(); -+ ktime_t timeout = rtl->rtx_timeout.timeout; -+ ktime_t next = KTIME_MAX; -+ -+ trace_ssam_rtl_timeout_reap(atomic_read(&rtl->pending.count)); -+ -+ /* -+ * Mark reaper as "not pending". This is done before checking any -+ * requests to avoid lost-update type problems. -+ */ -+ spin_lock(&rtl->rtx_timeout.lock); -+ rtl->rtx_timeout.expires = KTIME_MAX; -+ spin_unlock(&rtl->rtx_timeout.lock); -+ -+ spin_lock(&rtl->pending.lock); -+ list_for_each_entry_safe(r, n, &rtl->pending.head, node) { -+ ktime_t expires = ssh_request_get_expiration(r, timeout); -+ -+ /* -+ * Check if the timeout hasn't expired yet. Find out next -+ * expiration date to be handled after this run. -+ */ -+ if (ktime_after(expires, now)) { -+ next = ktime_before(expires, next) ? expires : next; -+ continue; -+ } -+ -+ /* Avoid further transitions if locked. */ -+ if (test_and_set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state)) -+ continue; -+ -+ /* -+ * We have now marked the packet as locked. Thus it cannot be -+ * added to the pending or queued lists again after we've -+ * removed it here. We can therefore re-use the node of this -+ * packet temporarily. -+ */ -+ -+ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); -+ -+ atomic_dec(&rtl->pending.count); -+ list_del(&r->node); -+ -+ list_add_tail(&r->node, &claimed); -+ } -+ spin_unlock(&rtl->pending.lock); -+ -+ /* Cancel and complete the request. */ -+ list_for_each_entry_safe(r, n, &claimed, node) { -+ trace_ssam_request_timeout(r); -+ -+ /* -+ * At this point we've removed the packet from pending. This -+ * means that we've obtained the last (only) reference of the -+ * system to it. Thus we can just complete it. -+ */ -+ if (!test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ ssh_rtl_complete_with_status(r, -ETIMEDOUT); -+ -+ /* -+ * Drop the reference we've obtained by removing it from the -+ * pending set. -+ */ -+ list_del(&r->node); -+ ssh_request_put(r); -+ } -+ -+ /* Ensure that the reaper doesn't run again immediately. */ -+ next = max(next, ktime_add(now, SSH_RTL_REQUEST_TIMEOUT_RESOLUTION)); -+ if (next != KTIME_MAX) -+ ssh_rtl_timeout_reaper_mod(rtl, now, next); -+ -+ ssh_rtl_tx_schedule(rtl); -+} -+ -+static void ssh_rtl_rx_event(struct ssh_rtl *rtl, const struct ssh_command *cmd, -+ const struct ssam_span *data) -+{ -+ trace_ssam_rx_event_received(cmd, data->len); -+ -+ rtl_dbg(rtl, "rtl: handling event (rqid: %#06x)\n", -+ get_unaligned_le16(&cmd->rqid)); -+ -+ rtl->ops.handle_event(rtl, cmd, data); -+} -+ -+static void ssh_rtl_rx_command(struct ssh_ptl *p, const struct ssam_span *data) -+{ -+ struct ssh_rtl *rtl = to_ssh_rtl(p, ptl); -+ struct device *dev = &p->serdev->dev; -+ struct ssh_command *command; -+ struct ssam_span command_data; -+ -+ if (sshp_parse_command(dev, data, &command, &command_data)) -+ return; -+ -+ if (ssh_rqid_is_event(get_unaligned_le16(&command->rqid))) -+ ssh_rtl_rx_event(rtl, command, &command_data); -+ else -+ ssh_rtl_complete(rtl, command, &command_data); -+} -+ -+static void ssh_rtl_rx_data(struct ssh_ptl *p, const struct ssam_span *data) -+{ -+ if (!data->len) { -+ ptl_err(p, "rtl: rx: no data frame payload\n"); -+ return; -+ } -+ -+ switch (data->ptr[0]) { -+ case SSH_PLD_TYPE_CMD: -+ ssh_rtl_rx_command(p, data); -+ break; -+ -+ default: -+ ptl_err(p, "rtl: rx: unknown frame payload type (type: %#04x)\n", -+ data->ptr[0]); -+ break; -+ } -+} -+ -+static void ssh_rtl_packet_release(struct ssh_packet *p) -+{ -+ struct ssh_request *rqst; -+ -+ rqst = to_ssh_request(p); -+ rqst->ops->release(rqst); -+} -+ -+static const struct ssh_packet_ops ssh_rtl_packet_ops = { -+ .complete = ssh_rtl_packet_callback, -+ .release = ssh_rtl_packet_release, -+}; -+ -+/** -+ * ssh_request_init() - Initialize SSH request. -+ * @rqst: The request to initialize. -+ * @flags: Request flags, determining the type of the request. -+ * @ops: Request operations. -+ * -+ * Initializes the given SSH request and underlying packet. Sets the message -+ * buffer pointer to %NULL and the message buffer length to zero. This buffer -+ * has to be set separately via ssh_request_set_data() before submission and -+ * must contain a valid SSH request message. -+ * -+ * Return: Returns zero on success or %-EINVAL if the given flags are invalid. -+ */ -+int ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags, -+ const struct ssh_request_ops *ops) -+{ -+ unsigned long type = BIT(SSH_PACKET_TY_BLOCKING_BIT); -+ -+ /* Unsequenced requests cannot have a response. */ -+ if (flags & SSAM_REQUEST_UNSEQUENCED && flags & SSAM_REQUEST_HAS_RESPONSE) -+ return -EINVAL; -+ -+ if (!(flags & SSAM_REQUEST_UNSEQUENCED)) -+ type |= BIT(SSH_PACKET_TY_SEQUENCED_BIT); -+ -+ ssh_packet_init(&rqst->packet, type, SSH_PACKET_PRIORITY(DATA, 0), -+ &ssh_rtl_packet_ops); -+ -+ INIT_LIST_HEAD(&rqst->node); -+ -+ rqst->state = 0; -+ if (flags & SSAM_REQUEST_HAS_RESPONSE) -+ rqst->state |= BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT); -+ -+ rqst->timestamp = KTIME_MAX; -+ rqst->ops = ops; -+ -+ return 0; -+} -+ -+/** -+ * ssh_rtl_init() - Initialize request transport layer. -+ * @rtl: The request transport layer to initialize. -+ * @serdev: The underlying serial device, i.e. the lower-level transport. -+ * @ops: Request transport layer operations. -+ * -+ * Initializes the given request transport layer and associated packet -+ * transport layer. Transmitter and receiver threads must be started -+ * separately via ssh_rtl_start(), after the request-layer has been -+ * initialized and the lower-level serial device layer has been set up. -+ * -+ * Return: Returns zero on success and a nonzero error code on failure. -+ */ -+int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev, -+ const struct ssh_rtl_ops *ops) -+{ -+ struct ssh_ptl_ops ptl_ops; -+ int status; -+ -+ ptl_ops.data_received = ssh_rtl_rx_data; -+ -+ status = ssh_ptl_init(&rtl->ptl, serdev, &ptl_ops); -+ if (status) -+ return status; -+ -+ spin_lock_init(&rtl->queue.lock); -+ INIT_LIST_HEAD(&rtl->queue.head); -+ -+ spin_lock_init(&rtl->pending.lock); -+ INIT_LIST_HEAD(&rtl->pending.head); -+ atomic_set_release(&rtl->pending.count, 0); -+ -+ INIT_WORK(&rtl->tx.work, ssh_rtl_tx_work_fn); -+ -+ spin_lock_init(&rtl->rtx_timeout.lock); -+ rtl->rtx_timeout.timeout = SSH_RTL_REQUEST_TIMEOUT; -+ rtl->rtx_timeout.expires = KTIME_MAX; -+ INIT_DELAYED_WORK(&rtl->rtx_timeout.reaper, ssh_rtl_timeout_reap); -+ -+ rtl->ops = *ops; -+ -+ return 0; -+} -+ -+/** -+ * ssh_rtl_destroy() - Deinitialize request transport layer. -+ * @rtl: The request transport layer to deinitialize. -+ * -+ * Deinitializes the given request transport layer and frees resources -+ * associated with it. If receiver and/or transmitter threads have been -+ * started, the layer must first be shut down via ssh_rtl_shutdown() before -+ * this function can be called. -+ */ -+void ssh_rtl_destroy(struct ssh_rtl *rtl) -+{ -+ ssh_ptl_destroy(&rtl->ptl); -+} -+ -+/** -+ * ssh_rtl_start() - Start request transmitter and receiver. -+ * @rtl: The request transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_rtl_start(struct ssh_rtl *rtl) -+{ -+ int status; -+ -+ status = ssh_ptl_tx_start(&rtl->ptl); -+ if (status) -+ return status; -+ -+ ssh_rtl_tx_schedule(rtl); -+ -+ status = ssh_ptl_rx_start(&rtl->ptl); -+ if (status) { -+ ssh_rtl_flush(rtl, msecs_to_jiffies(5000)); -+ ssh_ptl_tx_stop(&rtl->ptl); -+ return status; -+ } -+ -+ return 0; -+} -+ -+struct ssh_flush_request { -+ struct ssh_request base; -+ struct completion completion; -+ int status; -+}; -+ -+static void ssh_rtl_flush_request_complete(struct ssh_request *r, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data, -+ int status) -+{ -+ struct ssh_flush_request *rqst; -+ -+ rqst = container_of(r, struct ssh_flush_request, base); -+ rqst->status = status; -+} -+ -+static void ssh_rtl_flush_request_release(struct ssh_request *r) -+{ -+ struct ssh_flush_request *rqst; -+ -+ rqst = container_of(r, struct ssh_flush_request, base); -+ complete_all(&rqst->completion); -+} -+ -+static const struct ssh_request_ops ssh_rtl_flush_request_ops = { -+ .complete = ssh_rtl_flush_request_complete, -+ .release = ssh_rtl_flush_request_release, -+}; -+ -+/** -+ * ssh_rtl_flush() - Flush the request transport layer. -+ * @rtl: request transport layer -+ * @timeout: timeout for the flush operation in jiffies -+ * -+ * Queue a special flush request and wait for its completion. This request -+ * will be completed after all other currently queued and pending requests -+ * have been completed. Instead of a normal data packet, this request submits -+ * a special flush packet, meaning that upon completion, also the underlying -+ * packet transport layer has been flushed. -+ * -+ * Flushing the request layer guarantees that all previously submitted -+ * requests have been fully completed before this call returns. Additionally, -+ * flushing blocks execution of all later submitted requests until the flush -+ * has been completed. -+ * -+ * If the caller ensures that no new requests are submitted after a call to -+ * this function, the request transport layer is guaranteed to have no -+ * remaining requests when this call returns. The same guarantee does not hold -+ * for the packet layer, on which control packets may still be queued after -+ * this call. -+ * -+ * Return: Returns zero on success, %-ETIMEDOUT if the flush timed out and has -+ * been canceled as a result of the timeout, or %-ESHUTDOWN if the packet -+ * and/or request transport layer has been shut down before this call. May -+ * also return %-EINTR if the underlying packet transmission has been -+ * interrupted. -+ */ -+int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout) -+{ -+ const unsigned int init_flags = SSAM_REQUEST_UNSEQUENCED; -+ struct ssh_flush_request rqst; -+ int status; -+ -+ ssh_request_init(&rqst.base, init_flags, &ssh_rtl_flush_request_ops); -+ rqst.base.packet.state |= BIT(SSH_PACKET_TY_FLUSH_BIT); -+ rqst.base.packet.priority = SSH_PACKET_PRIORITY(FLUSH, 0); -+ rqst.base.state |= BIT(SSH_REQUEST_TY_FLUSH_BIT); -+ -+ init_completion(&rqst.completion); -+ -+ status = ssh_rtl_submit(rtl, &rqst.base); -+ if (status) -+ return status; -+ -+ ssh_request_put(&rqst.base); -+ -+ if (!wait_for_completion_timeout(&rqst.completion, timeout)) { -+ ssh_rtl_cancel(&rqst.base, true); -+ wait_for_completion(&rqst.completion); -+ } -+ -+ WARN_ON(rqst.status != 0 && rqst.status != -ECANCELED && -+ rqst.status != -ESHUTDOWN && rqst.status != -EINTR); -+ -+ return rqst.status == -ECANCELED ? -ETIMEDOUT : rqst.status; -+} -+ -+/** -+ * ssh_rtl_shutdown() - Shut down request transport layer. -+ * @rtl: The request transport layer. -+ * -+ * Shuts down the request transport layer, removing and canceling all queued -+ * and pending requests. Requests canceled by this operation will be completed -+ * with %-ESHUTDOWN as status. Receiver and transmitter threads will be -+ * stopped, the lower-level packet layer will be shutdown. -+ * -+ * As a result of this function, the transport layer will be marked as shut -+ * down. Submission of requests after the transport layer has been shut down -+ * will fail with %-ESHUTDOWN. -+ */ -+void ssh_rtl_shutdown(struct ssh_rtl *rtl) -+{ -+ struct ssh_request *r, *n; -+ LIST_HEAD(claimed); -+ int pending; -+ -+ set_bit(SSH_RTL_SF_SHUTDOWN_BIT, &rtl->state); -+ /* -+ * Ensure that the layer gets marked as shut-down before actually -+ * stopping it. In combination with the check in ssh_rtl_submit(), -+ * this guarantees that no new requests can be added and all already -+ * queued requests are properly canceled. -+ */ -+ smp_mb__after_atomic(); -+ -+ /* Remove requests from queue. */ -+ spin_lock(&rtl->queue.lock); -+ list_for_each_entry_safe(r, n, &rtl->queue.head, node) { -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); -+ -+ list_del(&r->node); -+ list_add_tail(&r->node, &claimed); -+ } -+ spin_unlock(&rtl->queue.lock); -+ -+ /* -+ * We have now guaranteed that the queue is empty and no more new -+ * requests can be submitted (i.e. it will stay empty). This means that -+ * calling ssh_rtl_tx_schedule() will not schedule tx.work any more. So -+ * we can simply call cancel_work_sync() on tx.work here and when that -+ * returns, we've locked it down. This also means that after this call, -+ * we don't submit any more packets to the underlying packet layer, so -+ * we can also shut that down. -+ */ -+ -+ cancel_work_sync(&rtl->tx.work); -+ ssh_ptl_shutdown(&rtl->ptl); -+ cancel_delayed_work_sync(&rtl->rtx_timeout.reaper); -+ -+ /* -+ * Shutting down the packet layer should also have canceled all -+ * requests. Thus the pending set should be empty. Attempt to handle -+ * this gracefully anyways, even though this should be dead code. -+ */ -+ -+ pending = atomic_read(&rtl->pending.count); -+ if (WARN_ON(pending)) { -+ spin_lock(&rtl->pending.lock); -+ list_for_each_entry_safe(r, n, &rtl->pending.head, node) { -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); -+ -+ list_del(&r->node); -+ list_add_tail(&r->node, &claimed); -+ } -+ spin_unlock(&rtl->pending.lock); -+ } -+ -+ /* Finally, cancel and complete the requests we claimed before. */ -+ list_for_each_entry_safe(r, n, &claimed, node) { -+ /* -+ * We need test_and_set() because we still might compete with -+ * cancellation. -+ */ -+ if (!test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ ssh_rtl_complete_with_status(r, -ESHUTDOWN); -+ -+ /* -+ * Drop the reference we've obtained by removing it from the -+ * lists. -+ */ -+ list_del(&r->node); -+ ssh_request_put(r); -+ } -+} -diff --git a/drivers/platform/x86/surface_aggregator/ssh_request_layer.h b/drivers/platform/x86/surface_aggregator/ssh_request_layer.h -new file mode 100644 -index 000000000000..9c3cbae2d4bd ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/ssh_request_layer.h -@@ -0,0 +1,143 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * SSH request transport layer. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H -+#define _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "ssh_packet_layer.h" -+ -+/** -+ * enum ssh_rtl_state_flags - State-flags for &struct ssh_rtl. -+ * -+ * @SSH_RTL_SF_SHUTDOWN_BIT: -+ * Indicates that the request transport layer has been shut down or is -+ * being shut down and should not accept any new requests. -+ */ -+enum ssh_rtl_state_flags { -+ SSH_RTL_SF_SHUTDOWN_BIT, -+}; -+ -+/** -+ * struct ssh_rtl_ops - Callback operations for request transport layer. -+ * @handle_event: Function called when a SSH event has been received. The -+ * specified function takes the request layer, received command -+ * struct, and corresponding payload as arguments. If the event -+ * has no payload, the payload span is empty (not %NULL). -+ */ -+struct ssh_rtl_ops { -+ void (*handle_event)(struct ssh_rtl *rtl, const struct ssh_command *cmd, -+ const struct ssam_span *data); -+}; -+ -+/** -+ * struct ssh_rtl - SSH request transport layer. -+ * @ptl: Underlying packet transport layer. -+ * @state: State(-flags) of the transport layer. -+ * @queue: Request submission queue. -+ * @queue.lock: Lock for modifying the request submission queue. -+ * @queue.head: List-head of the request submission queue. -+ * @pending: Set/list of pending requests. -+ * @pending.lock: Lock for modifying the request set. -+ * @pending.head: List-head of the pending set/list. -+ * @pending.count: Number of currently pending requests. -+ * @tx: Transmitter subsystem. -+ * @tx.work: Transmitter work item. -+ * @rtx_timeout: Retransmission timeout subsystem. -+ * @rtx_timeout.lock: Lock for modifying the retransmission timeout reaper. -+ * @rtx_timeout.timeout: Timeout interval for retransmission. -+ * @rtx_timeout.expires: Time specifying when the reaper work is next scheduled. -+ * @rtx_timeout.reaper: Work performing timeout checks and subsequent actions. -+ * @ops: Request layer operations. -+ */ -+struct ssh_rtl { -+ struct ssh_ptl ptl; -+ unsigned long state; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ } queue; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ atomic_t count; -+ } pending; -+ -+ struct { -+ struct work_struct work; -+ } tx; -+ -+ struct { -+ spinlock_t lock; -+ ktime_t timeout; -+ ktime_t expires; -+ struct delayed_work reaper; -+ } rtx_timeout; -+ -+ struct ssh_rtl_ops ops; -+}; -+ -+#define rtl_dbg(r, fmt, ...) ptl_dbg(&(r)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_info(p, fmt, ...) ptl_info(&(p)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_warn(r, fmt, ...) ptl_warn(&(r)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_err(r, fmt, ...) ptl_err(&(r)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_dbg_cond(r, fmt, ...) __ssam_prcond(rtl_dbg, r, fmt, ##__VA_ARGS__) -+ -+#define to_ssh_rtl(ptr, member) \ -+ container_of(ptr, struct ssh_rtl, member) -+ -+/** -+ * ssh_rtl_get_device() - Get device associated with request transport layer. -+ * @rtl: The request transport layer. -+ * -+ * Return: Returns the device on which the given request transport layer -+ * builds upon. -+ */ -+static inline struct device *ssh_rtl_get_device(struct ssh_rtl *rtl) -+{ -+ return ssh_ptl_get_device(&rtl->ptl); -+} -+ -+/** -+ * ssh_request_rtl() - Get request transport layer associated with request. -+ * @rqst: The request to get the request transport layer reference for. -+ * -+ * Return: Returns the &struct ssh_rtl associated with the given SSH request. -+ */ -+static inline struct ssh_rtl *ssh_request_rtl(struct ssh_request *rqst) -+{ -+ struct ssh_ptl *ptl; -+ -+ ptl = READ_ONCE(rqst->packet.ptl); -+ return likely(ptl) ? to_ssh_rtl(ptl, ptl) : NULL; -+} -+ -+int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst); -+bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending); -+ -+int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev, -+ const struct ssh_rtl_ops *ops); -+ -+int ssh_rtl_start(struct ssh_rtl *rtl); -+int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout); -+void ssh_rtl_shutdown(struct ssh_rtl *rtl); -+void ssh_rtl_destroy(struct ssh_rtl *rtl); -+ -+int ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags, -+ const struct ssh_request_ops *ops); -+ -+#endif /* _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H */ -diff --git a/drivers/platform/x86/surface_aggregator/trace.h b/drivers/platform/x86/surface_aggregator/trace.h -new file mode 100644 -index 000000000000..de64cf169060 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/trace.h -@@ -0,0 +1,632 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Trace points for SSAM/SSH. -+ * -+ * Copyright (C) 2020-2021 Maximilian Luz -+ */ -+ -+#undef TRACE_SYSTEM -+#define TRACE_SYSTEM surface_aggregator -+ -+#if !defined(_SURFACE_AGGREGATOR_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) -+#define _SURFACE_AGGREGATOR_TRACE_H -+ -+#include -+ -+#include -+#include -+ -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_SEQ); -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_NSQ); -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_ACK); -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_NAK); -+ -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_LOCKED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_QUEUED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_PENDING_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTING_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_ACKED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_CANCELED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_COMPLETED_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_PACKET_TY_FLUSH_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_TY_SEQUENCED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_TY_BLOCKING_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_SF_MASK); -+TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_TY_MASK); -+ -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_LOCKED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_QUEUED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_PENDING_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTING_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_RSPRCVD_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_CANCELED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_COMPLETED_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_REQUEST_TY_FLUSH_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_TY_HAS_RESPONSE_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_SF_MASK); -+TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_TY_MASK); -+ -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SAM); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAT); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TMP); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_PMC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_FAN); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_PoM); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_DBG); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_KBD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_FWU); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_UNI); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_LPC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCL); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SFL); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_KIP); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_EXT); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BLD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAS); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SEN); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SRQ); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_MCU); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_VID); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG); -+ -+#define SSAM_PTR_UID_LEN 9 -+#define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1) -+#define SSAM_SEQ_NOT_APPLICABLE ((u16)-1) -+#define SSAM_RQID_NOT_APPLICABLE ((u32)-1) -+#define SSAM_SSH_TC_NOT_APPLICABLE 0 -+ -+#ifndef _SURFACE_AGGREGATOR_TRACE_HELPERS -+#define _SURFACE_AGGREGATOR_TRACE_HELPERS -+ -+/** -+ * ssam_trace_ptr_uid() - Convert the pointer to a non-pointer UID string. -+ * @ptr: The pointer to convert. -+ * @uid_str: A buffer of length SSAM_PTR_UID_LEN where the UID will be stored. -+ * -+ * Converts the given pointer into a UID string that is safe to be shared -+ * with userspace and logs, i.e. doesn't give away the real memory location. -+ */ -+static inline void ssam_trace_ptr_uid(const void *ptr, char *uid_str) -+{ -+ char buf[2 * sizeof(void *) + 1]; -+ -+ BUILD_BUG_ON(ARRAY_SIZE(buf) < SSAM_PTR_UID_LEN); -+ -+ snprintf(buf, ARRAY_SIZE(buf), "%p", ptr); -+ memcpy(uid_str, &buf[ARRAY_SIZE(buf) - SSAM_PTR_UID_LEN], -+ SSAM_PTR_UID_LEN); -+} -+ -+/** -+ * ssam_trace_get_packet_seq() - Read the packet's sequence ID. -+ * @p: The packet. -+ * -+ * Return: Returns the packet's sequence ID (SEQ) field if present, or -+ * %SSAM_SEQ_NOT_APPLICABLE if not (e.g. flush packet). -+ */ -+static inline u16 ssam_trace_get_packet_seq(const struct ssh_packet *p) -+{ -+ if (!p->data.ptr || p->data.len < SSH_MESSAGE_LENGTH(0)) -+ return SSAM_SEQ_NOT_APPLICABLE; -+ -+ return p->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; -+} -+ -+/** -+ * ssam_trace_get_request_id() - Read the packet's request ID. -+ * @p: The packet. -+ * -+ * Return: Returns the packet's request ID (RQID) field if the packet -+ * represents a request with command data, or %SSAM_RQID_NOT_APPLICABLE if not -+ * (e.g. flush request, control packet). -+ */ -+static inline u32 ssam_trace_get_request_id(const struct ssh_packet *p) -+{ -+ if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) -+ return SSAM_RQID_NOT_APPLICABLE; -+ -+ return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(rqid)]); -+} -+ -+/** -+ * ssam_trace_get_request_tc() - Read the packet's request target category. -+ * @p: The packet. -+ * -+ * Return: Returns the packet's request target category (TC) field if the -+ * packet represents a request with command data, or %SSAM_TC_NOT_APPLICABLE -+ * if not (e.g. flush request, control packet). -+ */ -+static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) -+{ -+ if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) -+ return SSAM_SSH_TC_NOT_APPLICABLE; -+ -+ return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(tc)]); -+} -+ -+#endif /* _SURFACE_AGGREGATOR_TRACE_HELPERS */ -+ -+#define ssam_trace_get_command_field_u8(packet, field) \ -+ ((!(packet) || (packet)->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) \ -+ ? 0 : (packet)->data.ptr[SSH_MSGOFFSET_COMMAND(field)]) -+ -+#define ssam_show_generic_u8_field(value) \ -+ __print_symbolic(value, \ -+ { SSAM_U8_FIELD_NOT_APPLICABLE, "N/A" } \ -+ ) -+ -+#define ssam_show_frame_type(ty) \ -+ __print_symbolic(ty, \ -+ { SSH_FRAME_TYPE_DATA_SEQ, "DSEQ" }, \ -+ { SSH_FRAME_TYPE_DATA_NSQ, "DNSQ" }, \ -+ { SSH_FRAME_TYPE_ACK, "ACK" }, \ -+ { SSH_FRAME_TYPE_NAK, "NAK" } \ -+ ) -+ -+#define ssam_show_packet_type(type) \ -+ __print_flags(flags & SSH_PACKET_FLAGS_TY_MASK, "", \ -+ { BIT(SSH_PACKET_TY_FLUSH_BIT), "F" }, \ -+ { BIT(SSH_PACKET_TY_SEQUENCED_BIT), "S" }, \ -+ { BIT(SSH_PACKET_TY_BLOCKING_BIT), "B" } \ -+ ) -+ -+#define ssam_show_packet_state(state) \ -+ __print_flags(flags & SSH_PACKET_FLAGS_SF_MASK, "", \ -+ { BIT(SSH_PACKET_SF_LOCKED_BIT), "L" }, \ -+ { BIT(SSH_PACKET_SF_QUEUED_BIT), "Q" }, \ -+ { BIT(SSH_PACKET_SF_PENDING_BIT), "P" }, \ -+ { BIT(SSH_PACKET_SF_TRANSMITTING_BIT), "S" }, \ -+ { BIT(SSH_PACKET_SF_TRANSMITTED_BIT), "T" }, \ -+ { BIT(SSH_PACKET_SF_ACKED_BIT), "A" }, \ -+ { BIT(SSH_PACKET_SF_CANCELED_BIT), "C" }, \ -+ { BIT(SSH_PACKET_SF_COMPLETED_BIT), "F" } \ -+ ) -+ -+#define ssam_show_packet_seq(seq) \ -+ __print_symbolic(seq, \ -+ { SSAM_SEQ_NOT_APPLICABLE, "N/A" } \ -+ ) -+ -+#define ssam_show_request_type(flags) \ -+ __print_flags((flags) & SSH_REQUEST_FLAGS_TY_MASK, "", \ -+ { BIT(SSH_REQUEST_TY_FLUSH_BIT), "F" }, \ -+ { BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT), "R" } \ -+ ) -+ -+#define ssam_show_request_state(flags) \ -+ __print_flags((flags) & SSH_REQUEST_FLAGS_SF_MASK, "", \ -+ { BIT(SSH_REQUEST_SF_LOCKED_BIT), "L" }, \ -+ { BIT(SSH_REQUEST_SF_QUEUED_BIT), "Q" }, \ -+ { BIT(SSH_REQUEST_SF_PENDING_BIT), "P" }, \ -+ { BIT(SSH_REQUEST_SF_TRANSMITTING_BIT), "S" }, \ -+ { BIT(SSH_REQUEST_SF_TRANSMITTED_BIT), "T" }, \ -+ { BIT(SSH_REQUEST_SF_RSPRCVD_BIT), "A" }, \ -+ { BIT(SSH_REQUEST_SF_CANCELED_BIT), "C" }, \ -+ { BIT(SSH_REQUEST_SF_COMPLETED_BIT), "F" } \ -+ ) -+ -+#define ssam_show_request_id(rqid) \ -+ __print_symbolic(rqid, \ -+ { SSAM_RQID_NOT_APPLICABLE, "N/A" } \ -+ ) -+ -+#define ssam_show_ssh_tc(rqid) \ -+ __print_symbolic(rqid, \ -+ { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ -+ { SSAM_SSH_TC_SAM, "SAM" }, \ -+ { SSAM_SSH_TC_BAT, "BAT" }, \ -+ { SSAM_SSH_TC_TMP, "TMP" }, \ -+ { SSAM_SSH_TC_PMC, "PMC" }, \ -+ { SSAM_SSH_TC_FAN, "FAN" }, \ -+ { SSAM_SSH_TC_PoM, "PoM" }, \ -+ { SSAM_SSH_TC_DBG, "DBG" }, \ -+ { SSAM_SSH_TC_KBD, "KBD" }, \ -+ { SSAM_SSH_TC_FWU, "FWU" }, \ -+ { SSAM_SSH_TC_UNI, "UNI" }, \ -+ { SSAM_SSH_TC_LPC, "LPC" }, \ -+ { SSAM_SSH_TC_TCL, "TCL" }, \ -+ { SSAM_SSH_TC_SFL, "SFL" }, \ -+ { SSAM_SSH_TC_KIP, "KIP" }, \ -+ { SSAM_SSH_TC_EXT, "EXT" }, \ -+ { SSAM_SSH_TC_BLD, "BLD" }, \ -+ { SSAM_SSH_TC_BAS, "BAS" }, \ -+ { SSAM_SSH_TC_SEN, "SEN" }, \ -+ { SSAM_SSH_TC_SRQ, "SRQ" }, \ -+ { SSAM_SSH_TC_MCU, "MCU" }, \ -+ { SSAM_SSH_TC_HID, "HID" }, \ -+ { SSAM_SSH_TC_TCH, "TCH" }, \ -+ { SSAM_SSH_TC_BKL, "BKL" }, \ -+ { SSAM_SSH_TC_TAM, "TAM" }, \ -+ { SSAM_SSH_TC_ACC, "ACC" }, \ -+ { SSAM_SSH_TC_UFI, "UFI" }, \ -+ { SSAM_SSH_TC_USC, "USC" }, \ -+ { SSAM_SSH_TC_PEN, "PEN" }, \ -+ { SSAM_SSH_TC_VID, "VID" }, \ -+ { SSAM_SSH_TC_AUD, "AUD" }, \ -+ { SSAM_SSH_TC_SMC, "SMC" }, \ -+ { SSAM_SSH_TC_KPD, "KPD" }, \ -+ { SSAM_SSH_TC_REG, "REG" } \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_frame_class, -+ TP_PROTO(const struct ssh_frame *frame), -+ -+ TP_ARGS(frame), -+ -+ TP_STRUCT__entry( -+ __field(u8, type) -+ __field(u8, seq) -+ __field(u16, len) -+ ), -+ -+ TP_fast_assign( -+ __entry->type = frame->type; -+ __entry->seq = frame->seq; -+ __entry->len = get_unaligned_le16(&frame->len); -+ ), -+ -+ TP_printk("ty=%s, seq=%#04x, len=%u", -+ ssam_show_frame_type(__entry->type), -+ __entry->seq, -+ __entry->len -+ ) -+); -+ -+#define DEFINE_SSAM_FRAME_EVENT(name) \ -+ DEFINE_EVENT(ssam_frame_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_frame *frame), \ -+ TP_ARGS(frame) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_command_class, -+ TP_PROTO(const struct ssh_command *cmd, u16 len), -+ -+ TP_ARGS(cmd, len), -+ -+ TP_STRUCT__entry( -+ __field(u16, rqid) -+ __field(u16, len) -+ __field(u8, tc) -+ __field(u8, cid) -+ __field(u8, iid) -+ ), -+ -+ TP_fast_assign( -+ __entry->rqid = get_unaligned_le16(&cmd->rqid); -+ __entry->tc = cmd->tc; -+ __entry->cid = cmd->cid; -+ __entry->iid = cmd->iid; -+ __entry->len = len; -+ ), -+ -+ TP_printk("rqid=%#06x, tc=%s, cid=%#04x, iid=%#04x, len=%u", -+ __entry->rqid, -+ ssam_show_ssh_tc(__entry->tc), -+ __entry->cid, -+ __entry->iid, -+ __entry->len -+ ) -+); -+ -+#define DEFINE_SSAM_COMMAND_EVENT(name) \ -+ DEFINE_EVENT(ssam_command_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_command *cmd, u16 len), \ -+ TP_ARGS(cmd, len) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_packet_class, -+ TP_PROTO(const struct ssh_packet *packet), -+ -+ TP_ARGS(packet), -+ -+ TP_STRUCT__entry( -+ __field(unsigned long, state) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(u8, priority) -+ __field(u16, length) -+ __field(u16, seq) -+ ), -+ -+ TP_fast_assign( -+ __entry->state = READ_ONCE(packet->state); -+ ssam_trace_ptr_uid(packet, __entry->uid); -+ __entry->priority = READ_ONCE(packet->priority); -+ __entry->length = packet->data.len; -+ __entry->seq = ssam_trace_get_packet_seq(packet); -+ ), -+ -+ TP_printk("uid=%s, seq=%s, ty=%s, pri=%#04x, len=%u, sta=%s", -+ __entry->uid, -+ ssam_show_packet_seq(__entry->seq), -+ ssam_show_packet_type(__entry->state), -+ __entry->priority, -+ __entry->length, -+ ssam_show_packet_state(__entry->state) -+ ) -+); -+ -+#define DEFINE_SSAM_PACKET_EVENT(name) \ -+ DEFINE_EVENT(ssam_packet_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_packet *packet), \ -+ TP_ARGS(packet) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_packet_status_class, -+ TP_PROTO(const struct ssh_packet *packet, int status), -+ -+ TP_ARGS(packet, status), -+ -+ TP_STRUCT__entry( -+ __field(unsigned long, state) -+ __field(int, status) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(u8, priority) -+ __field(u16, length) -+ __field(u16, seq) -+ ), -+ -+ TP_fast_assign( -+ __entry->state = READ_ONCE(packet->state); -+ __entry->status = status; -+ ssam_trace_ptr_uid(packet, __entry->uid); -+ __entry->priority = READ_ONCE(packet->priority); -+ __entry->length = packet->data.len; -+ __entry->seq = ssam_trace_get_packet_seq(packet); -+ ), -+ -+ TP_printk("uid=%s, seq=%s, ty=%s, pri=%#04x, len=%u, sta=%s, status=%d", -+ __entry->uid, -+ ssam_show_packet_seq(__entry->seq), -+ ssam_show_packet_type(__entry->state), -+ __entry->priority, -+ __entry->length, -+ ssam_show_packet_state(__entry->state), -+ __entry->status -+ ) -+); -+ -+#define DEFINE_SSAM_PACKET_STATUS_EVENT(name) \ -+ DEFINE_EVENT(ssam_packet_status_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_packet *packet, int status), \ -+ TP_ARGS(packet, status) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_request_class, -+ TP_PROTO(const struct ssh_request *request), -+ -+ TP_ARGS(request), -+ -+ TP_STRUCT__entry( -+ __field(unsigned long, state) -+ __field(u32, rqid) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(u8, tc) -+ __field(u16, cid) -+ __field(u16, iid) -+ ), -+ -+ TP_fast_assign( -+ const struct ssh_packet *p = &request->packet; -+ -+ /* Use packet for UID so we can match requests to packets. */ -+ __entry->state = READ_ONCE(request->state); -+ __entry->rqid = ssam_trace_get_request_id(p); -+ ssam_trace_ptr_uid(p, __entry->uid); -+ __entry->tc = ssam_trace_get_request_tc(p); -+ __entry->cid = ssam_trace_get_command_field_u8(p, cid); -+ __entry->iid = ssam_trace_get_command_field_u8(p, iid); -+ ), -+ -+ TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s", -+ __entry->uid, -+ ssam_show_request_id(__entry->rqid), -+ ssam_show_request_type(__entry->state), -+ ssam_show_request_state(__entry->state), -+ ssam_show_ssh_tc(__entry->tc), -+ ssam_show_generic_u8_field(__entry->cid), -+ ssam_show_generic_u8_field(__entry->iid) -+ ) -+); -+ -+#define DEFINE_SSAM_REQUEST_EVENT(name) \ -+ DEFINE_EVENT(ssam_request_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_request *request), \ -+ TP_ARGS(request) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_request_status_class, -+ TP_PROTO(const struct ssh_request *request, int status), -+ -+ TP_ARGS(request, status), -+ -+ TP_STRUCT__entry( -+ __field(unsigned long, state) -+ __field(u32, rqid) -+ __field(int, status) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(u8, tc) -+ __field(u16, cid) -+ __field(u16, iid) -+ ), -+ -+ TP_fast_assign( -+ const struct ssh_packet *p = &request->packet; -+ -+ /* Use packet for UID so we can match requests to packets. */ -+ __entry->state = READ_ONCE(request->state); -+ __entry->rqid = ssam_trace_get_request_id(p); -+ __entry->status = status; -+ ssam_trace_ptr_uid(p, __entry->uid); -+ __entry->tc = ssam_trace_get_request_tc(p); -+ __entry->cid = ssam_trace_get_command_field_u8(p, cid); -+ __entry->iid = ssam_trace_get_command_field_u8(p, iid); -+ ), -+ -+ TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s, status=%d", -+ __entry->uid, -+ ssam_show_request_id(__entry->rqid), -+ ssam_show_request_type(__entry->state), -+ ssam_show_request_state(__entry->state), -+ ssam_show_ssh_tc(__entry->tc), -+ ssam_show_generic_u8_field(__entry->cid), -+ ssam_show_generic_u8_field(__entry->iid), -+ __entry->status -+ ) -+); -+ -+#define DEFINE_SSAM_REQUEST_STATUS_EVENT(name) \ -+ DEFINE_EVENT(ssam_request_status_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_request *request, int status),\ -+ TP_ARGS(request, status) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_alloc_class, -+ TP_PROTO(void *ptr, size_t len), -+ -+ TP_ARGS(ptr, len), -+ -+ TP_STRUCT__entry( -+ __field(size_t, len) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ ), -+ -+ TP_fast_assign( -+ __entry->len = len; -+ ssam_trace_ptr_uid(ptr, __entry->uid); -+ ), -+ -+ TP_printk("uid=%s, len=%zu", __entry->uid, __entry->len) -+); -+ -+#define DEFINE_SSAM_ALLOC_EVENT(name) \ -+ DEFINE_EVENT(ssam_alloc_class, ssam_##name, \ -+ TP_PROTO(void *ptr, size_t len), \ -+ TP_ARGS(ptr, len) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_free_class, -+ TP_PROTO(void *ptr), -+ -+ TP_ARGS(ptr), -+ -+ TP_STRUCT__entry( -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ ), -+ -+ TP_fast_assign( -+ ssam_trace_ptr_uid(ptr, __entry->uid); -+ ), -+ -+ TP_printk("uid=%s", __entry->uid) -+); -+ -+#define DEFINE_SSAM_FREE_EVENT(name) \ -+ DEFINE_EVENT(ssam_free_class, ssam_##name, \ -+ TP_PROTO(void *ptr), \ -+ TP_ARGS(ptr) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_pending_class, -+ TP_PROTO(unsigned int pending), -+ -+ TP_ARGS(pending), -+ -+ TP_STRUCT__entry( -+ __field(unsigned int, pending) -+ ), -+ -+ TP_fast_assign( -+ __entry->pending = pending; -+ ), -+ -+ TP_printk("pending=%u", __entry->pending) -+); -+ -+#define DEFINE_SSAM_PENDING_EVENT(name) \ -+ DEFINE_EVENT(ssam_pending_class, ssam_##name, \ -+ TP_PROTO(unsigned int pending), \ -+ TP_ARGS(pending) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_data_class, -+ TP_PROTO(size_t length), -+ -+ TP_ARGS(length), -+ -+ TP_STRUCT__entry( -+ __field(size_t, length) -+ ), -+ -+ TP_fast_assign( -+ __entry->length = length; -+ ), -+ -+ TP_printk("length=%zu", __entry->length) -+); -+ -+#define DEFINE_SSAM_DATA_EVENT(name) \ -+ DEFINE_EVENT(ssam_data_class, ssam_##name, \ -+ TP_PROTO(size_t length), \ -+ TP_ARGS(length) \ -+ ) -+ -+DEFINE_SSAM_FRAME_EVENT(rx_frame_received); -+DEFINE_SSAM_COMMAND_EVENT(rx_response_received); -+DEFINE_SSAM_COMMAND_EVENT(rx_event_received); -+ -+DEFINE_SSAM_PACKET_EVENT(packet_release); -+DEFINE_SSAM_PACKET_EVENT(packet_submit); -+DEFINE_SSAM_PACKET_EVENT(packet_resubmit); -+DEFINE_SSAM_PACKET_EVENT(packet_timeout); -+DEFINE_SSAM_PACKET_EVENT(packet_cancel); -+DEFINE_SSAM_PACKET_STATUS_EVENT(packet_complete); -+DEFINE_SSAM_PENDING_EVENT(ptl_timeout_reap); -+ -+DEFINE_SSAM_REQUEST_EVENT(request_submit); -+DEFINE_SSAM_REQUEST_EVENT(request_timeout); -+DEFINE_SSAM_REQUEST_EVENT(request_cancel); -+DEFINE_SSAM_REQUEST_STATUS_EVENT(request_complete); -+DEFINE_SSAM_PENDING_EVENT(rtl_timeout_reap); -+ -+DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_ack_packet); -+DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_nak_packet); -+DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_dsq_packet); -+DEFINE_SSAM_PACKET_STATUS_EVENT(ei_tx_fail_write); -+DEFINE_SSAM_PACKET_EVENT(ei_tx_corrupt_data); -+DEFINE_SSAM_DATA_EVENT(ei_rx_corrupt_syn); -+DEFINE_SSAM_FRAME_EVENT(ei_rx_corrupt_data); -+DEFINE_SSAM_REQUEST_EVENT(ei_rx_drop_response); -+ -+DEFINE_SSAM_ALLOC_EVENT(ctrl_packet_alloc); -+DEFINE_SSAM_FREE_EVENT(ctrl_packet_free); -+ -+DEFINE_SSAM_ALLOC_EVENT(event_item_alloc); -+DEFINE_SSAM_FREE_EVENT(event_item_free); -+ -+#endif /* _SURFACE_AGGREGATOR_TRACE_H */ -+ -+/* This part must be outside protection */ -+#undef TRACE_INCLUDE_PATH -+#undef TRACE_INCLUDE_FILE -+ -+#define TRACE_INCLUDE_PATH . -+#define TRACE_INCLUDE_FILE trace -+ -+#include -diff --git a/drivers/platform/x86/surface_aggregator_cdev.c b/drivers/platform/x86/surface_aggregator_cdev.c -new file mode 100644 -index 000000000000..bbc0fc57ba13 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator_cdev.c -@@ -0,0 +1,810 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Provides user-space access to the SSAM EC via the /dev/surface/aggregator -+ * misc device. Intended for debugging and development. -+ * -+ * Copyright (C) 2020-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+#define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" -+ -+ -+/* -- Main structures. ------------------------------------------------------ */ -+ -+enum ssam_cdev_device_state { -+ SSAM_CDEV_DEVICE_SHUTDOWN_BIT = BIT(0), -+}; -+ -+struct ssam_cdev { -+ struct kref kref; -+ struct rw_semaphore lock; -+ -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ struct miscdevice mdev; -+ unsigned long flags; -+ -+ struct rw_semaphore client_lock; /* Guards client list. */ -+ struct list_head client_list; -+}; -+ -+struct ssam_cdev_client; -+ -+struct ssam_cdev_notifier { -+ struct ssam_cdev_client *client; -+ struct ssam_event_notifier nf; -+}; -+ -+struct ssam_cdev_client { -+ struct ssam_cdev *cdev; -+ struct list_head node; -+ -+ struct mutex notifier_lock; /* Guards notifier access for registration */ -+ struct ssam_cdev_notifier *notifier[SSH_NUM_EVENTS]; -+ -+ struct mutex read_lock; /* Guards FIFO buffer read access */ -+ struct mutex write_lock; /* Guards FIFO buffer write access */ -+ DECLARE_KFIFO(buffer, u8, 4096); -+ -+ wait_queue_head_t waitq; -+ struct fasync_struct *fasync; -+}; -+ -+static void __ssam_cdev_release(struct kref *kref) -+{ -+ kfree(container_of(kref, struct ssam_cdev, kref)); -+} -+ -+static struct ssam_cdev *ssam_cdev_get(struct ssam_cdev *cdev) -+{ -+ if (cdev) -+ kref_get(&cdev->kref); -+ -+ return cdev; -+} -+ -+static void ssam_cdev_put(struct ssam_cdev *cdev) -+{ -+ if (cdev) -+ kref_put(&cdev->kref, __ssam_cdev_release); -+} -+ -+ -+/* -- Notifier handling. ---------------------------------------------------- */ -+ -+static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in) -+{ -+ struct ssam_cdev_notifier *cdev_nf = container_of(nf, struct ssam_cdev_notifier, nf); -+ struct ssam_cdev_client *client = cdev_nf->client; -+ struct ssam_cdev_event event; -+ size_t n = struct_size(&event, data, in->length); -+ -+ /* Translate event. */ -+ event.target_category = in->target_category; -+ event.target_id = in->target_id; -+ event.command_id = in->command_id; -+ event.instance_id = in->instance_id; -+ event.length = in->length; -+ -+ mutex_lock(&client->write_lock); -+ -+ /* Make sure we have enough space. */ -+ if (kfifo_avail(&client->buffer) < n) { -+ dev_warn(client->cdev->dev, -+ "buffer full, dropping event (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", -+ in->target_category, in->target_id, in->command_id, in->instance_id); -+ mutex_unlock(&client->write_lock); -+ return 0; -+ } -+ -+ /* Copy event header and payload. */ -+ kfifo_in(&client->buffer, (const u8 *)&event, struct_size(&event, data, 0)); -+ kfifo_in(&client->buffer, &in->data[0], in->length); -+ -+ mutex_unlock(&client->write_lock); -+ -+ /* Notify waiting readers. */ -+ kill_fasync(&client->fasync, SIGIO, POLL_IN); -+ wake_up_interruptible(&client->waitq); -+ -+ /* -+ * Don't mark events as handled, this is the job of a proper driver and -+ * not the debugging interface. -+ */ -+ return 0; -+} -+ -+static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, int priority) -+{ -+ const u16 rqid = ssh_tc_to_rqid(tc); -+ const u16 event = ssh_rqid_to_event(rqid); -+ struct ssam_cdev_notifier *nf; -+ int status; -+ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ /* Validate notifier target category. */ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ mutex_lock(&client->notifier_lock); -+ -+ /* Check if the notifier has already been registered. */ -+ if (client->notifier[event]) { -+ mutex_unlock(&client->notifier_lock); -+ return -EEXIST; -+ } -+ -+ /* Allocate new notifier. */ -+ nf = kzalloc(sizeof(*nf), GFP_KERNEL); -+ if (!nf) { -+ mutex_unlock(&client->notifier_lock); -+ return -ENOMEM; -+ } -+ -+ /* -+ * Create a dummy notifier with the minimal required fields for -+ * observer registration. Note that we can skip fully specifying event -+ * and registry here as we do not need any matching and use silent -+ * registration, which does not enable the corresponding event. -+ */ -+ nf->client = client; -+ nf->nf.base.fn = ssam_cdev_notifier; -+ nf->nf.base.priority = priority; -+ nf->nf.event.id.target_category = tc; -+ nf->nf.event.mask = 0; /* Do not do any matching. */ -+ nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER; -+ -+ /* Register notifier. */ -+ status = ssam_notifier_register(client->cdev->ctrl, &nf->nf); -+ if (status) -+ kfree(nf); -+ else -+ client->notifier[event] = nf; -+ -+ mutex_unlock(&client->notifier_lock); -+ return status; -+} -+ -+static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc) -+{ -+ const u16 rqid = ssh_tc_to_rqid(tc); -+ const u16 event = ssh_rqid_to_event(rqid); -+ int status; -+ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ /* Validate notifier target category. */ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ mutex_lock(&client->notifier_lock); -+ -+ /* Check if the notifier is currently registered. */ -+ if (!client->notifier[event]) { -+ mutex_unlock(&client->notifier_lock); -+ return -ENOENT; -+ } -+ -+ /* Unregister and free notifier. */ -+ status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[event]->nf); -+ kfree(client->notifier[event]); -+ client->notifier[event] = NULL; -+ -+ mutex_unlock(&client->notifier_lock); -+ return status; -+} -+ -+static void ssam_cdev_notifier_unregister_all(struct ssam_cdev_client *client) -+{ -+ int i; -+ -+ down_read(&client->cdev->lock); -+ -+ /* -+ * This function may be used during shutdown, thus we need to test for -+ * cdev->ctrl instead of the SSAM_CDEV_DEVICE_SHUTDOWN_BIT bit. -+ */ -+ if (client->cdev->ctrl) { -+ for (i = 0; i < SSH_NUM_EVENTS; i++) -+ ssam_cdev_notifier_unregister(client, i + 1); -+ -+ } else { -+ int count = 0; -+ -+ /* -+ * Device has been shut down. Any notifier remaining is a bug, -+ * so warn about that as this would otherwise hardly be -+ * noticeable. Nevertheless, free them as well. -+ */ -+ mutex_lock(&client->notifier_lock); -+ for (i = 0; i < SSH_NUM_EVENTS; i++) { -+ count += !!(client->notifier[i]); -+ kfree(client->notifier[i]); -+ client->notifier[i] = NULL; -+ } -+ mutex_unlock(&client->notifier_lock); -+ -+ WARN_ON(count > 0); -+ } -+ -+ up_read(&client->cdev->lock); -+} -+ -+ -+/* -- IOCTL functions. ------------------------------------------------------ */ -+ -+static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_request __user *r) -+{ -+ struct ssam_cdev_request rqst; -+ struct ssam_request spec = {}; -+ struct ssam_response rsp = {}; -+ const void __user *plddata; -+ void __user *rspdata; -+ int status = 0, ret = 0, tmp; -+ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ ret = copy_from_user(&rqst, r, sizeof(struct ssam_cdev_request)); -+ if (ret) -+ goto out; -+ -+ plddata = u64_to_user_ptr(rqst.payload.data); -+ rspdata = u64_to_user_ptr(rqst.response.data); -+ -+ /* Setup basic request fields. */ -+ spec.target_category = rqst.target_category; -+ spec.target_id = rqst.target_id; -+ spec.command_id = rqst.command_id; -+ spec.instance_id = rqst.instance_id; -+ spec.flags = 0; -+ spec.length = rqst.payload.length; -+ spec.payload = NULL; -+ -+ if (rqst.flags & SSAM_CDEV_REQUEST_HAS_RESPONSE) -+ spec.flags |= SSAM_REQUEST_HAS_RESPONSE; -+ -+ if (rqst.flags & SSAM_CDEV_REQUEST_UNSEQUENCED) -+ spec.flags |= SSAM_REQUEST_UNSEQUENCED; -+ -+ rsp.capacity = rqst.response.length; -+ rsp.length = 0; -+ rsp.pointer = NULL; -+ -+ /* Get request payload from user-space. */ -+ if (spec.length) { -+ if (!plddata) { -+ ret = -EINVAL; -+ goto out; -+ } -+ -+ /* -+ * Note: spec.length is limited to U16_MAX bytes via struct -+ * ssam_cdev_request. This is slightly larger than the -+ * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the -+ * underlying protocol (note that nothing remotely this size -+ * should ever be allocated in any normal case). This size is -+ * validated later in ssam_request_sync(), for allocation the -+ * bound imposed by u16 should be enough. -+ */ -+ spec.payload = kzalloc(spec.length, GFP_KERNEL); -+ if (!spec.payload) { -+ ret = -ENOMEM; -+ goto out; -+ } -+ -+ if (copy_from_user((void *)spec.payload, plddata, spec.length)) { -+ ret = -EFAULT; -+ goto out; -+ } -+ } -+ -+ /* Allocate response buffer. */ -+ if (rsp.capacity) { -+ if (!rspdata) { -+ ret = -EINVAL; -+ goto out; -+ } -+ -+ /* -+ * Note: rsp.capacity is limited to U16_MAX bytes via struct -+ * ssam_cdev_request. This is slightly larger than the -+ * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the -+ * underlying protocol (note that nothing remotely this size -+ * should ever be allocated in any normal case). In later use, -+ * this capacity does not have to be strictly bounded, as it -+ * is only used as an output buffer to be written to. For -+ * allocation the bound imposed by u16 should be enough. -+ */ -+ rsp.pointer = kzalloc(rsp.capacity, GFP_KERNEL); -+ if (!rsp.pointer) { -+ ret = -ENOMEM; -+ goto out; -+ } -+ } -+ -+ /* Perform request. */ -+ status = ssam_request_sync(client->cdev->ctrl, &spec, &rsp); -+ if (status) -+ goto out; -+ -+ /* Copy response to user-space. */ -+ if (rsp.length && copy_to_user(rspdata, rsp.pointer, rsp.length)) -+ ret = -EFAULT; -+ -+out: -+ /* Always try to set response-length and status. */ -+ tmp = put_user(rsp.length, &r->response.length); -+ if (tmp) -+ ret = tmp; -+ -+ tmp = put_user(status, &r->status); -+ if (tmp) -+ ret = tmp; -+ -+ /* Cleanup. */ -+ kfree(spec.payload); -+ kfree(rsp.pointer); -+ -+ return ret; -+} -+ -+static long ssam_cdev_notif_register(struct ssam_cdev_client *client, -+ const struct ssam_cdev_notifier_desc __user *d) -+{ -+ struct ssam_cdev_notifier_desc desc; -+ long ret; -+ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ ret = copy_from_user(&desc, d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ return ssam_cdev_notifier_register(client, desc.target_category, desc.priority); -+} -+ -+static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, -+ const struct ssam_cdev_notifier_desc __user *d) -+{ -+ struct ssam_cdev_notifier_desc desc; -+ long ret; -+ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ ret = copy_from_user(&desc, d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ return ssam_cdev_notifier_unregister(client, desc.target_category); -+} -+ -+static long ssam_cdev_event_enable(struct ssam_cdev_client *client, -+ const struct ssam_cdev_event_desc __user *d) -+{ -+ struct ssam_cdev_event_desc desc; -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ long ret; -+ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ /* Read descriptor from user-space. */ -+ ret = copy_from_user(&desc, d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ /* Translate descriptor. */ -+ reg.target_category = desc.reg.target_category; -+ reg.target_id = desc.reg.target_id; -+ reg.cid_enable = desc.reg.cid_enable; -+ reg.cid_disable = desc.reg.cid_disable; -+ -+ id.target_category = desc.id.target_category; -+ id.instance = desc.id.instance; -+ -+ /* Disable event. */ -+ return ssam_controller_event_enable(client->cdev->ctrl, reg, id, desc.flags); -+} -+ -+static long ssam_cdev_event_disable(struct ssam_cdev_client *client, -+ const struct ssam_cdev_event_desc __user *d) -+{ -+ struct ssam_cdev_event_desc desc; -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ long ret; -+ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ /* Read descriptor from user-space. */ -+ ret = copy_from_user(&desc, d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ /* Translate descriptor. */ -+ reg.target_category = desc.reg.target_category; -+ reg.target_id = desc.reg.target_id; -+ reg.cid_enable = desc.reg.cid_enable; -+ reg.cid_disable = desc.reg.cid_disable; -+ -+ id.target_category = desc.id.target_category; -+ id.instance = desc.id.instance; -+ -+ /* Disable event. */ -+ return ssam_controller_event_disable(client->cdev->ctrl, reg, id, desc.flags); -+} -+ -+ -+/* -- File operations. ------------------------------------------------------ */ -+ -+static int ssam_cdev_device_open(struct inode *inode, struct file *filp) -+{ -+ struct miscdevice *mdev = filp->private_data; -+ struct ssam_cdev_client *client; -+ struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); -+ -+ /* Initialize client */ -+ client = vzalloc(sizeof(*client)); -+ if (!client) -+ return -ENOMEM; -+ -+ client->cdev = ssam_cdev_get(cdev); -+ -+ INIT_LIST_HEAD(&client->node); -+ -+ mutex_init(&client->notifier_lock); -+ -+ mutex_init(&client->read_lock); -+ mutex_init(&client->write_lock); -+ INIT_KFIFO(client->buffer); -+ init_waitqueue_head(&client->waitq); -+ -+ filp->private_data = client; -+ -+ /* Attach client. */ -+ down_write(&cdev->client_lock); -+ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { -+ up_write(&cdev->client_lock); -+ mutex_destroy(&client->write_lock); -+ mutex_destroy(&client->read_lock); -+ mutex_destroy(&client->notifier_lock); -+ ssam_cdev_put(client->cdev); -+ vfree(client); -+ return -ENODEV; -+ } -+ list_add_tail(&client->node, &cdev->client_list); -+ -+ up_write(&cdev->client_lock); -+ -+ stream_open(inode, filp); -+ return 0; -+} -+ -+static int ssam_cdev_device_release(struct inode *inode, struct file *filp) -+{ -+ struct ssam_cdev_client *client = filp->private_data; -+ -+ /* Force-unregister all remaining notifiers of this client. */ -+ ssam_cdev_notifier_unregister_all(client); -+ -+ /* Detach client. */ -+ down_write(&client->cdev->client_lock); -+ list_del(&client->node); -+ up_write(&client->cdev->client_lock); -+ -+ /* Free client. */ -+ mutex_destroy(&client->write_lock); -+ mutex_destroy(&client->read_lock); -+ -+ mutex_destroy(&client->notifier_lock); -+ -+ ssam_cdev_put(client->cdev); -+ vfree(client); -+ -+ return 0; -+} -+ -+static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd, -+ unsigned long arg) -+{ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ switch (cmd) { -+ case SSAM_CDEV_REQUEST: -+ return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg); -+ -+ case SSAM_CDEV_NOTIF_REGISTER: -+ return ssam_cdev_notif_register(client, -+ (struct ssam_cdev_notifier_desc __user *)arg); -+ -+ case SSAM_CDEV_NOTIF_UNREGISTER: -+ return ssam_cdev_notif_unregister(client, -+ (struct ssam_cdev_notifier_desc __user *)arg); -+ -+ case SSAM_CDEV_EVENT_ENABLE: -+ return ssam_cdev_event_enable(client, (struct ssam_cdev_event_desc __user *)arg); -+ -+ case SSAM_CDEV_EVENT_DISABLE: -+ return ssam_cdev_event_disable(client, (struct ssam_cdev_event_desc __user *)arg); -+ -+ default: -+ return -ENOTTY; -+ } -+} -+ -+static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -+{ -+ struct ssam_cdev_client *client = file->private_data; -+ long status; -+ -+ /* Ensure that controller is valid for as long as we need it. */ -+ if (down_read_killable(&client->cdev->lock)) -+ return -ERESTARTSYS; -+ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) { -+ up_read(&client->cdev->lock); -+ return -ENODEV; -+ } -+ -+ status = __ssam_cdev_device_ioctl(client, cmd, arg); -+ -+ up_read(&client->cdev->lock); -+ return status; -+} -+ -+static ssize_t ssam_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offs) -+{ -+ struct ssam_cdev_client *client = file->private_data; -+ struct ssam_cdev *cdev = client->cdev; -+ unsigned int copied; -+ int status = 0; -+ -+ if (down_read_killable(&cdev->lock)) -+ return -ERESTARTSYS; -+ -+ /* Make sure we're not shut down. */ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { -+ up_read(&cdev->lock); -+ return -ENODEV; -+ } -+ -+ do { -+ /* Check availability, wait if necessary. */ -+ if (kfifo_is_empty(&client->buffer)) { -+ up_read(&cdev->lock); -+ -+ if (file->f_flags & O_NONBLOCK) -+ return -EAGAIN; -+ -+ status = wait_event_interruptible(client->waitq, -+ !kfifo_is_empty(&client->buffer) || -+ test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, -+ &cdev->flags)); -+ if (status < 0) -+ return status; -+ -+ if (down_read_killable(&cdev->lock)) -+ return -ERESTARTSYS; -+ -+ /* Need to check that we're not shut down again. */ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { -+ up_read(&cdev->lock); -+ return -ENODEV; -+ } -+ } -+ -+ /* Try to read from FIFO. */ -+ if (mutex_lock_interruptible(&client->read_lock)) { -+ up_read(&cdev->lock); -+ return -ERESTARTSYS; -+ } -+ -+ status = kfifo_to_user(&client->buffer, buf, count, &copied); -+ mutex_unlock(&client->read_lock); -+ -+ if (status < 0) { -+ up_read(&cdev->lock); -+ return status; -+ } -+ -+ /* We might not have gotten anything, check this here. */ -+ if (copied == 0 && (file->f_flags & O_NONBLOCK)) { -+ up_read(&cdev->lock); -+ return -EAGAIN; -+ } -+ } while (copied == 0); -+ -+ up_read(&cdev->lock); -+ return copied; -+} -+ -+static __poll_t ssam_cdev_poll(struct file *file, struct poll_table_struct *pt) -+{ -+ struct ssam_cdev_client *client = file->private_data; -+ __poll_t events = 0; -+ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) -+ return EPOLLHUP | EPOLLERR; -+ -+ poll_wait(file, &client->waitq, pt); -+ -+ if (!kfifo_is_empty(&client->buffer)) -+ events |= EPOLLIN | EPOLLRDNORM; -+ -+ return events; -+} -+ -+static int ssam_cdev_fasync(int fd, struct file *file, int on) -+{ -+ struct ssam_cdev_client *client = file->private_data; -+ -+ return fasync_helper(fd, file, on, &client->fasync); -+} -+ -+static const struct file_operations ssam_controller_fops = { -+ .owner = THIS_MODULE, -+ .open = ssam_cdev_device_open, -+ .release = ssam_cdev_device_release, -+ .read = ssam_cdev_read, -+ .poll = ssam_cdev_poll, -+ .fasync = ssam_cdev_fasync, -+ .unlocked_ioctl = ssam_cdev_device_ioctl, -+ .compat_ioctl = ssam_cdev_device_ioctl, -+ .llseek = no_llseek, -+}; -+ -+ -+/* -- Device and driver setup ----------------------------------------------- */ -+ -+static int ssam_dbg_device_probe(struct platform_device *pdev) -+{ -+ struct ssam_controller *ctrl; -+ struct ssam_cdev *cdev; -+ int status; -+ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); -+ if (!cdev) -+ return -ENOMEM; -+ -+ kref_init(&cdev->kref); -+ init_rwsem(&cdev->lock); -+ cdev->ctrl = ctrl; -+ cdev->dev = &pdev->dev; -+ -+ cdev->mdev.parent = &pdev->dev; -+ cdev->mdev.minor = MISC_DYNAMIC_MINOR; -+ cdev->mdev.name = "surface_aggregator"; -+ cdev->mdev.nodename = "surface/aggregator"; -+ cdev->mdev.fops = &ssam_controller_fops; -+ -+ init_rwsem(&cdev->client_lock); -+ INIT_LIST_HEAD(&cdev->client_list); -+ -+ status = misc_register(&cdev->mdev); -+ if (status) { -+ kfree(cdev); -+ return status; -+ } -+ -+ platform_set_drvdata(pdev, cdev); -+ return 0; -+} -+ -+static int ssam_dbg_device_remove(struct platform_device *pdev) -+{ -+ struct ssam_cdev *cdev = platform_get_drvdata(pdev); -+ struct ssam_cdev_client *client; -+ -+ /* -+ * Mark device as shut-down. Prevent new clients from being added and -+ * new operations from being executed. -+ */ -+ set_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags); -+ -+ down_write(&cdev->client_lock); -+ -+ /* Remove all notifiers registered by us. */ -+ list_for_each_entry(client, &cdev->client_list, node) { -+ ssam_cdev_notifier_unregister_all(client); -+ } -+ -+ /* Wake up async clients. */ -+ list_for_each_entry(client, &cdev->client_list, node) { -+ kill_fasync(&client->fasync, SIGIO, POLL_HUP); -+ } -+ -+ /* Wake up blocking clients. */ -+ list_for_each_entry(client, &cdev->client_list, node) { -+ wake_up_interruptible(&client->waitq); -+ } -+ -+ up_write(&cdev->client_lock); -+ -+ /* -+ * The controller is only guaranteed to be valid for as long as the -+ * driver is bound. Remove controller so that any lingering open files -+ * cannot access it any more after we're gone. -+ */ -+ down_write(&cdev->lock); -+ cdev->ctrl = NULL; -+ cdev->dev = NULL; -+ up_write(&cdev->lock); -+ -+ misc_deregister(&cdev->mdev); -+ -+ ssam_cdev_put(cdev); -+ return 0; -+} -+ -+static struct platform_device *ssam_cdev_device; -+ -+static struct platform_driver ssam_cdev_driver = { -+ .probe = ssam_dbg_device_probe, -+ .remove = ssam_dbg_device_remove, -+ .driver = { -+ .name = SSAM_CDEV_DEVICE_NAME, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+static int __init ssam_debug_init(void) -+{ -+ int status; -+ -+ ssam_cdev_device = platform_device_alloc(SSAM_CDEV_DEVICE_NAME, -+ PLATFORM_DEVID_NONE); -+ if (!ssam_cdev_device) -+ return -ENOMEM; -+ -+ status = platform_device_add(ssam_cdev_device); -+ if (status) -+ goto err_device; -+ -+ status = platform_driver_register(&ssam_cdev_driver); -+ if (status) -+ goto err_driver; -+ -+ return 0; -+ -+err_driver: -+ platform_device_del(ssam_cdev_device); -+err_device: -+ platform_device_put(ssam_cdev_device); -+ return status; -+} -+module_init(ssam_debug_init); -+ -+static void __exit ssam_debug_exit(void) -+{ -+ platform_driver_unregister(&ssam_cdev_driver); -+ platform_device_unregister(ssam_cdev_device); -+} -+module_exit(ssam_debug_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("User-space interface for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_aggregator_registry.c b/drivers/platform/x86/surface_aggregator_registry.c -new file mode 100644 -index 000000000000..1b87bdd6dd1e ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator_registry.c -@@ -0,0 +1,618 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) client device registry. -+ * -+ * Registry for non-platform/non-ACPI SSAM client devices, i.e. devices that -+ * cannot be auto-detected. Provides device-hubs for these devices. -+ * -+ * Copyright (C) 2020 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+ -+/* -- Device registry structures. ------------------------------------------- */ -+ -+struct ssam_hub_cell { -+ struct ssam_device_uid uid; -+ void *data; -+}; -+ -+struct ssam_hub_desc { -+ const struct ssam_hub_cell *cells; -+ unsigned int num_cells; -+}; -+ -+ -+#define SSAM_DUID(cat, tid, iid, fun) \ -+ ((struct ssam_device_uid) { \ -+ .domain = SSAM_DOMAIN_SERIALHUB, \ -+ .category = SSAM_SSH_TC_##cat, \ -+ .target = (tid), \ -+ .instance = (iid), \ -+ .function = (fun) \ -+ }) -+ -+#define SSAM_VDUID(cat, tid, iid, fun) \ -+ ((struct ssam_device_uid) { \ -+ .domain = SSAM_DOMAIN_VIRTUAL, \ -+ .category = SSAM_VIRTUAL_TC_##cat, \ -+ .target = (tid), \ -+ .instance = (iid), \ -+ .function = (fun) \ -+ }) -+ -+#define SSAM_DUID_HUB_MAIN SSAM_VDUID(HUB, 0x01, 0x00, 0x00) -+#define SSAM_DUID_HUB_BASE SSAM_VDUID(HUB, 0x02, 0x00, 0x00) -+ -+#define SSAM_DEFINE_HUB_DESC(__name, __cells) \ -+ struct ssam_hub_desc __name = { \ -+ .cells = __cells, \ -+ .num_cells = ARRAY_SIZE(__cells), \ -+ }; -+ -+#define SSAM_DEFINE_PLATFORM_HUB(__suffix) \ -+ static const SSAM_DEFINE_HUB_DESC(ssam_device_hub_##__suffix, \ -+ ssam_devices_##__suffix); \ -+ static const struct ssam_hub_cell ssam_platform_hubs_##__suffix[] = { \ -+ { SSAM_DUID_HUB_MAIN, (void *)&ssam_device_hub_##__suffix }, \ -+ }; \ -+ static const SSAM_DEFINE_HUB_DESC(ssam_platform_hub_##__suffix, \ -+ ssam_platform_hubs_##__suffix); \ -+ -+#define SSAM_DEFINE_PLATFORM_HUB_WITH_BASE(__suffix) \ -+ static const SSAM_DEFINE_HUB_DESC(ssam_device_hub_##__suffix, \ -+ ssam_devices_##__suffix); \ -+ static const SSAM_DEFINE_HUB_DESC(ssam_device_hub_##__suffix##_base, \ -+ ssam_devices_##__suffix##_base); \ -+ static const struct ssam_hub_cell ssam_platform_hubs_##__suffix[] = { \ -+ { SSAM_DUID_HUB_MAIN, (void *)&ssam_device_hub_##__suffix }, \ -+ { SSAM_DUID_HUB_BASE, (void *)&ssam_device_hub_##__suffix##_base },\ -+ }; \ -+ static const SSAM_DEFINE_HUB_DESC(ssam_platform_hub_##__suffix, \ -+ ssam_platform_hubs_##__suffix); \ -+ -+ -+/* -- Device registry. ------------------------------------------------------ */ -+ -+#define SSAM_DUID_BAT_AC SSAM_DUID(BAT, 0x01, 0x01, 0x01) -+#define SSAM_DUID_BAT_MAIN SSAM_DUID(BAT, 0x01, 0x01, 0x00) -+#define SSAM_DUID_BAT_SB3BASE SSAM_DUID(BAT, 0x02, 0x01, 0x00) -+ -+#define SSAM_DUID_TMP_PERF SSAM_DUID(TMP, 0x01, 0x00, 0x01) -+ -+#define SSAM_DUID_BAS_DTX SSAM_DUID(BAS, 0x01, 0x00, 0x00) -+ -+#define SSAM_DUID_HID_KEYBOARD SSAM_DUID(HID, 0x02, 0x01, 0x00) -+#define SSAM_DUID_HID_TOUCHPAD SSAM_DUID(HID, 0x02, 0x03, 0x00) -+#define SSAM_DUID_HID_IID5 SSAM_DUID(HID, 0x02, 0x05, 0x00) -+#define SSAM_DUID_HID_IID6 SSAM_DUID(HID, 0x02, 0x06, 0x00) -+ -+ -+static const struct ssam_hub_cell ssam_devices_sb2[] = { -+ { SSAM_DUID_TMP_PERF }, -+}; -+ -+static const struct ssam_hub_cell ssam_devices_sb3[] = { -+ { SSAM_DUID_TMP_PERF }, -+ { SSAM_DUID_BAT_AC }, -+ { SSAM_DUID_BAT_MAIN }, -+ { SSAM_DUID_BAS_DTX }, -+}; -+ -+static const struct ssam_hub_cell ssam_devices_sb3_base[] = { -+ { SSAM_DUID_BAT_SB3BASE }, -+ { SSAM_DUID_HID_KEYBOARD }, -+ { SSAM_DUID_HID_TOUCHPAD }, -+ { SSAM_DUID_HID_IID5 }, -+ { SSAM_DUID_HID_IID6 }, -+}; -+ -+static const struct ssam_hub_cell ssam_devices_sl1[] = { -+ { SSAM_DUID_TMP_PERF }, -+}; -+ -+static const struct ssam_hub_cell ssam_devices_sl2[] = { -+ { SSAM_DUID_TMP_PERF }, -+}; -+ -+static const struct ssam_hub_cell ssam_devices_sl3[] = { -+ { SSAM_DUID_TMP_PERF }, -+ { SSAM_DUID_BAT_AC }, -+ { SSAM_DUID_BAT_MAIN }, -+ { SSAM_DUID_HID_KEYBOARD }, -+ { SSAM_DUID_HID_TOUCHPAD }, -+ { SSAM_DUID_HID_IID5 }, -+}; -+ -+static const struct ssam_hub_cell ssam_devices_slg1[] = { -+ { SSAM_DUID_TMP_PERF }, -+ { SSAM_DUID_BAT_AC }, -+ { SSAM_DUID_BAT_MAIN }, -+}; -+ -+static const struct ssam_hub_cell ssam_devices_sp5[] = { -+ { SSAM_DUID_TMP_PERF }, -+}; -+ -+static const struct ssam_hub_cell ssam_devices_sp6[] = { -+ { SSAM_DUID_TMP_PERF }, -+}; -+ -+static const struct ssam_hub_cell ssam_devices_sp7[] = { -+ { SSAM_DUID_TMP_PERF }, -+ { SSAM_DUID_BAT_AC }, -+ { SSAM_DUID_BAT_MAIN }, -+}; -+ -+SSAM_DEFINE_PLATFORM_HUB(sb2); -+SSAM_DEFINE_PLATFORM_HUB_WITH_BASE(sb3); -+SSAM_DEFINE_PLATFORM_HUB(sl1); -+SSAM_DEFINE_PLATFORM_HUB(sl2); -+SSAM_DEFINE_PLATFORM_HUB(sl3); -+SSAM_DEFINE_PLATFORM_HUB(slg1); -+SSAM_DEFINE_PLATFORM_HUB(sp5); -+SSAM_DEFINE_PLATFORM_HUB(sp6); -+SSAM_DEFINE_PLATFORM_HUB(sp7); -+ -+ -+/* -- Device registry helper functions. ------------------------------------- */ -+ -+static int ssam_hub_remove_devices_fn(struct device *dev, void *data) -+{ -+ if (!is_ssam_device(dev)) -+ return 0; -+ -+ ssam_device_remove(to_ssam_device(dev)); -+ return 0; -+} -+ -+static void ssam_hub_remove_devices(struct device *parent) -+{ -+ device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn); -+} -+ -+static int ssam_hub_add_device(struct device *parent, -+ struct ssam_controller *ctrl, -+ const struct ssam_hub_cell *cell) -+{ -+ struct ssam_device *sdev; -+ int status; -+ -+ sdev = ssam_device_alloc(ctrl, cell->uid); -+ if (!sdev) -+ return -ENOMEM; -+ -+ sdev->dev.parent = parent; -+ sdev->dev.platform_data = cell->data; -+ -+ status = ssam_device_add(sdev); -+ if (status) -+ ssam_device_put(sdev); -+ -+ return status; -+} -+ -+static int ssam_hub_add_devices(struct device *parent, -+ struct ssam_controller *ctrl, -+ const struct ssam_hub_desc *desc) -+{ -+ int status, i; -+ -+ for (i = 0; i < desc->num_cells; i++) { -+ status = ssam_hub_add_device(parent, ctrl, &desc->cells[i]); -+ if (status) -+ goto err; -+ } -+ -+ return 0; -+err: -+ ssam_hub_remove_devices(parent); -+ return status; -+} -+ -+ -+/* -- SSAM main-hub driver. ------------------------------------------------- */ -+ -+static int ssam_hub_probe(struct ssam_device *sdev) -+{ -+ const struct ssam_hub_desc *desc = dev_get_platdata(&sdev->dev); -+ -+ if (!desc) -+ return -ENODEV; -+ -+ return ssam_hub_add_devices(&sdev->dev, sdev->ctrl, desc); -+} -+ -+static void ssam_hub_remove(struct ssam_device *sdev) -+{ -+ ssam_hub_remove_devices(&sdev->dev); -+} -+ -+static const struct ssam_device_id ssam_hub_match[] = { -+ { SSAM_VDEV(HUB, 0x01, 0x00, 0x00) }, -+ { }, -+}; -+ -+static struct ssam_device_driver ssam_hub_driver = { -+ .probe = ssam_hub_probe, -+ .remove = ssam_hub_remove, -+ .match_table = ssam_hub_match, -+ .driver = { -+ .name = "surface_aggregator_device_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+/* -- SSAM base-hub driver. ------------------------------------------------- */ -+ -+enum ssam_base_hub_state { -+ SSAM_BASE_HUB_UNINITIALIZED, -+ SSAM_BASE_HUB_CONNECTED, -+ SSAM_BASE_HUB_DISCONNECTED, -+}; -+ -+struct ssam_base_hub { -+ struct ssam_device *sdev; -+ const struct ssam_hub_desc *devices; -+ -+ struct mutex lock; -+ enum ssam_base_hub_state state; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0d, -+ .instance_id = 0x00, -+}); -+ -+#define SSAM_BAS_OPMODE_TABLET 0x00 -+#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c -+ -+static int ssam_base_hub_query_state(struct ssam_device *sdev, -+ enum ssam_base_hub_state *state) -+{ -+ u8 opmode; -+ int status; -+ -+ status = ssam_retry(ssam_bas_query_opmode, sdev->ctrl, &opmode); -+ if (status < 0) { -+ dev_err(&sdev->dev, "failed to query base state: %d\n", status); -+ return status; -+ } -+ -+ if (opmode != SSAM_BAS_OPMODE_TABLET) -+ *state = SSAM_BASE_HUB_CONNECTED; -+ else -+ *state = SSAM_BASE_HUB_DISCONNECTED; -+ -+ return 0; -+} -+ -+static ssize_t ssam_base_hub_state_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); -+ bool connected; -+ -+ mutex_lock(&hub->lock); -+ connected = hub->state == SSAM_BASE_HUB_CONNECTED; -+ mutex_unlock(&hub->lock); -+ -+ return scnprintf(buf, PAGE_SIZE, "%d\n", connected); -+} -+ -+static struct device_attribute ssam_base_hub_attr_state = -+ __ATTR(state, S_IRUGO, ssam_base_hub_state_show, NULL); -+ -+static struct attribute *ssam_base_hub_attrs[] = { -+ &ssam_base_hub_attr_state.attr, -+ NULL, -+}; -+ -+static const struct attribute_group ssam_base_hub_group = { -+ .attrs = ssam_base_hub_attrs, -+}; -+ -+static int ssam_base_hub_update(struct ssam_device *sdev, -+ enum ssam_base_hub_state new) -+{ -+ struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); -+ int status = 0; -+ -+ mutex_lock(&hub->lock); -+ if (hub->state == new) { -+ mutex_unlock(&hub->lock); -+ return 0; -+ } -+ hub->state = new; -+ -+ if (hub->state == SSAM_BASE_HUB_CONNECTED) -+ status = ssam_hub_add_devices(&sdev->dev, sdev->ctrl, hub->devices); -+ -+ if (hub->state != SSAM_BASE_HUB_CONNECTED || status) -+ ssam_hub_remove_devices(&sdev->dev); -+ -+ mutex_unlock(&hub->lock); -+ -+ if (status) { -+ dev_err(&sdev->dev, "failed to update base-hub devices: %d\n", -+ status); -+ } -+ -+ return status; -+} -+ -+static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+{ -+ struct ssam_base_hub *hub; -+ struct ssam_device *sdev; -+ enum ssam_base_hub_state new; -+ -+ hub = container_of(nf, struct ssam_base_hub, notif); -+ sdev = hub->sdev; -+ -+ if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) -+ return 0; -+ -+ if (event->length < 1) { -+ dev_err(&sdev->dev, "unexpected payload size: %u\n", -+ event->length); -+ return 0; -+ } -+ -+ if (event->data[0]) -+ new = SSAM_BASE_HUB_CONNECTED; -+ else -+ new = SSAM_BASE_HUB_DISCONNECTED; -+ -+ ssam_base_hub_update(sdev, new); -+ -+ /* -+ * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -+ * consumed by the detachment system driver. We're just a (more or less) -+ * silent observer. -+ */ -+ return 0; -+} -+ -+static int __maybe_unused ssam_base_hub_resume(struct device *dev) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ enum ssam_base_hub_state state; -+ int status; -+ -+ status = ssam_base_hub_query_state(sdev, &state); -+ if (status) -+ return status; -+ -+ return ssam_base_hub_update(sdev, state); -+} -+static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); -+ -+static int ssam_base_hub_probe(struct ssam_device *sdev) -+{ -+ const struct ssam_hub_desc *desc = dev_get_platdata(&sdev->dev); -+ const struct ssam_device_id *match; -+ enum ssam_base_hub_state state; -+ struct ssam_base_hub *hub; -+ int status; -+ -+ if (!desc) -+ return -ENODEV; -+ -+ match = ssam_device_get_match(sdev); -+ if (!match) -+ return -ENODEV; -+ -+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -+ if (!hub) -+ return -ENOMEM; -+ -+ mutex_init(&hub->lock); -+ -+ hub->sdev = sdev; -+ hub->devices = desc; -+ hub->state = SSAM_BASE_HUB_UNINITIALIZED; -+ -+ hub->notif.base.priority = 1000; /* This notifier should run first. */ -+ hub->notif.base.fn = ssam_base_hub_notif; -+ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ hub->notif.event.id.target_category = SSAM_SSH_TC_BAS, -+ hub->notif.event.id.instance = 0, -+ hub->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ status = ssam_notifier_register(sdev->ctrl, &hub->notif); -+ if (status) -+ return status; -+ -+ ssam_device_set_drvdata(sdev, hub); -+ -+ status = ssam_base_hub_query_state(sdev, &state); -+ if (status) { -+ ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ return status; -+ } -+ -+ status = ssam_base_hub_update(sdev, state); -+ if (status) { -+ ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ return status; -+ } -+ -+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); -+ if (status) { -+ ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_hub_remove_devices(&sdev->dev); -+ } -+ -+ return status; -+} -+ -+static void ssam_base_hub_remove(struct ssam_device *sdev) -+{ -+ struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); -+ -+ sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); -+ -+ ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_hub_remove_devices(&sdev->dev); -+ -+ mutex_destroy(&hub->lock); -+} -+ -+static const struct ssam_device_id ssam_base_hub_match[] = { -+ { SSAM_VDEV(HUB, 0x02, 0x00, 0x00) }, -+ { }, -+}; -+ -+static struct ssam_device_driver ssam_base_hub_driver = { -+ .probe = ssam_base_hub_probe, -+ .remove = ssam_base_hub_remove, -+ .match_table = ssam_base_hub_match, -+ .driver = { -+ .name = "surface_aggregator_base_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_base_hub_pm_ops, -+ }, -+}; -+ -+ -+/* -- SSAM platform/meta-hub driver. ---------------------------------------- */ -+ -+static const struct acpi_device_id ssam_platform_hub_match[] = { -+ /* Surface Pro 4, 5, and 6 */ -+ { "MSHW0081", (unsigned long)&ssam_platform_hub_sp5 }, -+ -+ /* Surface Pro 6 (OMBR >= 0x10) */ -+ { "MSHW0111", (unsigned long)&ssam_platform_hub_sp6 }, -+ -+ /* Surface Pro 7 */ -+ { "MSHW0116", (unsigned long)&ssam_platform_hub_sp7 }, -+ -+ /* Surface Pro 7+ */ -+ { "MSHW0119", (unsigned long)&ssam_platform_hub_sp7 }, -+ -+ /* Surface Book 2 */ -+ { "MSHW0107", (unsigned long)&ssam_platform_hub_sb2 }, -+ -+ /* Surface Book 3 */ -+ { "MSHW0117", (unsigned long)&ssam_platform_hub_sb3 }, -+ -+ /* Surface Laptop 1 */ -+ { "MSHW0086", (unsigned long)&ssam_platform_hub_sl1 }, -+ -+ /* Surface Laptop 2 */ -+ { "MSHW0112", (unsigned long)&ssam_platform_hub_sl2 }, -+ -+ /* Surface Laptop 3 (13", Intel) */ -+ { "MSHW0114", (unsigned long)&ssam_platform_hub_sl3 }, -+ -+ /* Surface Laptop 3 (15", AMD) */ -+ { "MSHW0110", (unsigned long)&ssam_platform_hub_sl3 }, -+ -+ /* Surface Laptop Go 1 */ -+ { "MSHW0118", (unsigned long)&ssam_platform_hub_slg1 }, -+ -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match); -+ -+static int ssam_platform_hub_probe(struct platform_device *pdev) -+{ -+ const struct ssam_hub_desc *desc; -+ struct ssam_controller *ctrl; -+ -+ desc = acpi_device_get_match_data(&pdev->dev); -+ if (!desc) -+ return -ENODEV; -+ -+ /* -+ * As we're adding the SSAM client devices as children under this device -+ * and not the SSAM controller, we need to add a device link to the -+ * controller to ensure that we remove all of our devices before the -+ * controller is removed. This also guarantees proper ordering for -+ * suspend/resume of the devices on this hub. -+ */ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ return ssam_hub_add_devices(&pdev->dev, ctrl, desc); -+} -+ -+static int ssam_platform_hub_remove(struct platform_device *pdev) -+{ -+ ssam_hub_remove_devices(&pdev->dev); -+ return 0; -+} -+ -+static struct platform_driver ssam_platform_hub_driver = { -+ .probe = ssam_platform_hub_probe, -+ .remove = ssam_platform_hub_remove, -+ .driver = { -+ .name = "surface_aggregator_platform_hub", -+ .acpi_match_table = ssam_platform_hub_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+/* -- Module initialization. ------------------------------------------------ */ -+ -+static int __init ssam_device_hub_init(void) -+{ -+ int status; -+ -+ status = platform_driver_register(&ssam_platform_hub_driver); -+ if (status) -+ goto err_platform; -+ -+ status = ssam_device_driver_register(&ssam_hub_driver); -+ if (status) -+ goto err_main; -+ -+ status = ssam_device_driver_register(&ssam_base_hub_driver); -+ if (status) -+ goto err_base; -+ -+ return 0; -+ -+err_base: -+ ssam_device_driver_unregister(&ssam_hub_driver); -+err_main: -+ platform_driver_unregister(&ssam_platform_hub_driver); -+err_platform: -+ return status; -+} -+ -+static void __exit ssam_device_hub_exit(void) -+{ -+ ssam_device_driver_unregister(&ssam_base_hub_driver); -+ ssam_device_driver_unregister(&ssam_hub_driver); -+ platform_driver_unregister(&ssam_platform_hub_driver); -+} -+ -+module_init(ssam_device_hub_init); -+module_exit(ssam_device_hub_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_dtx.c b/drivers/platform/x86/surface_dtx.c -new file mode 100644 -index 000000000000..bbbdffc5bf8f ---- /dev/null -+++ b/drivers/platform/x86/surface_dtx.c -@@ -0,0 +1,1281 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface Book (gen. 2 and later) detachment system (DTX) driver. -+ * -+ * Provides a user-space interface to properly handle clipboard/tablet -+ * (containing screen and processor) detachment from the base of the device -+ * (containing the keyboard and optionally a discrete GPU). Allows to -+ * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in -+ * use), or request detachment via user-space. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+ -+/* -- SSAM interface. ------------------------------------------------------- */ -+ -+enum sam_event_cid_bas { -+ SAM_EVENT_CID_DTX_CONNECTION = 0x0c, -+ SAM_EVENT_CID_DTX_REQUEST = 0x0e, -+ SAM_EVENT_CID_DTX_CANCEL = 0x0f, -+ SAM_EVENT_CID_DTX_LATCH_STATUS = 0x11, -+}; -+ -+enum ssam_bas_base_state { -+ SSAM_BAS_BASE_STATE_DETACH_SUCCESS = 0x00, -+ SSAM_BAS_BASE_STATE_ATTACHED = 0x01, -+ SSAM_BAS_BASE_STATE_NOT_FEASIBLE = 0x02, -+}; -+ -+enum ssam_bas_latch_status { -+ SSAM_BAS_LATCH_STATUS_CLOSED = 0x00, -+ SSAM_BAS_LATCH_STATUS_OPENED = 0x01, -+ SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN = 0x02, -+ SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN = 0x03, -+ SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE = 0x04, -+}; -+ -+enum ssam_bas_cancel_reason { -+ SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE = 0x00, /* Low battery. */ -+ SSAM_BAS_CANCEL_REASON_TIMEOUT = 0x02, -+ SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN = 0x03, -+ SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN = 0x04, -+ SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE = 0x05, -+}; -+ -+struct ssam_bas_base_info { -+ u8 state; -+ u8 base_id; -+} __packed; -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x06, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x07, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x08, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x09, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0a, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0b, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0c, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0d, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x11, -+ .instance_id = 0x00, -+}); -+ -+ -+/* -- Main structures. ------------------------------------------------------ */ -+ -+enum sdtx_device_state { -+ SDTX_DEVICE_SHUTDOWN_BIT = BIT(0), -+ SDTX_DEVICE_DIRTY_BASE_BIT = BIT(1), -+ SDTX_DEVICE_DIRTY_MODE_BIT = BIT(2), -+ SDTX_DEVICE_DIRTY_LATCH_BIT = BIT(3), -+}; -+ -+struct sdtx_device { -+ struct kref kref; -+ struct rw_semaphore lock; /* Guards device and controller reference. */ -+ -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ unsigned long flags; -+ -+ struct miscdevice mdev; -+ wait_queue_head_t waitq; -+ struct mutex write_lock; /* Guards order of events/notifications. */ -+ struct rw_semaphore client_lock; /* Guards client list. */ -+ struct list_head client_list; -+ -+ struct delayed_work state_work; -+ struct { -+ struct ssam_bas_base_info base; -+ u8 device_mode; -+ u8 latch_status; -+ } state; -+ -+ struct delayed_work mode_work; -+ struct input_dev *mode_switch; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+enum sdtx_client_state { -+ SDTX_CLIENT_EVENTS_ENABLED_BIT = BIT(0), -+}; -+ -+struct sdtx_client { -+ struct sdtx_device *ddev; -+ struct list_head node; -+ unsigned long flags; -+ -+ struct fasync_struct *fasync; -+ -+ struct mutex read_lock; /* Guards FIFO buffer read access. */ -+ DECLARE_KFIFO(buffer, u8, 512); -+}; -+ -+static void __sdtx_device_release(struct kref *kref) -+{ -+ struct sdtx_device *ddev = container_of(kref, struct sdtx_device, kref); -+ -+ mutex_destroy(&ddev->write_lock); -+ kfree(ddev); -+} -+ -+static struct sdtx_device *sdtx_device_get(struct sdtx_device *ddev) -+{ -+ if (ddev) -+ kref_get(&ddev->kref); -+ -+ return ddev; -+} -+ -+static void sdtx_device_put(struct sdtx_device *ddev) -+{ -+ if (ddev) -+ kref_put(&ddev->kref, __sdtx_device_release); -+} -+ -+ -+/* -- Firmware value translations. ------------------------------------------ */ -+ -+static u16 sdtx_translate_base_state(struct sdtx_device *ddev, u8 state) -+{ -+ switch (state) { -+ case SSAM_BAS_BASE_STATE_ATTACHED: -+ return SDTX_BASE_ATTACHED; -+ -+ case SSAM_BAS_BASE_STATE_DETACH_SUCCESS: -+ return SDTX_BASE_DETACHED; -+ -+ case SSAM_BAS_BASE_STATE_NOT_FEASIBLE: -+ return SDTX_DETACH_NOT_FEASIBLE; -+ -+ default: -+ dev_err(ddev->dev, "unknown base state: %#04x\n", state); -+ return SDTX_UNKNOWN(state); -+ } -+} -+ -+static u16 sdtx_translate_latch_status(struct sdtx_device *ddev, u8 status) -+{ -+ switch (status) { -+ case SSAM_BAS_LATCH_STATUS_CLOSED: -+ return SDTX_LATCH_CLOSED; -+ -+ case SSAM_BAS_LATCH_STATUS_OPENED: -+ return SDTX_LATCH_OPENED; -+ -+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN: -+ return SDTX_ERR_FAILED_TO_OPEN; -+ -+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN: -+ return SDTX_ERR_FAILED_TO_REMAIN_OPEN; -+ -+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE: -+ return SDTX_ERR_FAILED_TO_CLOSE; -+ -+ default: -+ dev_err(ddev->dev, "unknown latch status: %#04x\n", status); -+ return SDTX_UNKNOWN(status); -+ } -+} -+ -+static u16 sdtx_translate_cancel_reason(struct sdtx_device *ddev, u8 reason) -+{ -+ switch (reason) { -+ case SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE: -+ return SDTX_DETACH_NOT_FEASIBLE; -+ -+ case SSAM_BAS_CANCEL_REASON_TIMEOUT: -+ return SDTX_DETACH_TIMEDOUT; -+ -+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN: -+ return SDTX_ERR_FAILED_TO_OPEN; -+ -+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN: -+ return SDTX_ERR_FAILED_TO_REMAIN_OPEN; -+ -+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE: -+ return SDTX_ERR_FAILED_TO_CLOSE; -+ -+ default: -+ dev_err(ddev->dev, "unknown cancel reason: %#04x\n", reason); -+ return SDTX_UNKNOWN(reason); -+ } -+} -+ -+ -+/* -- IOCTLs. --------------------------------------------------------------- */ -+ -+static int sdtx_ioctl_get_base_info(struct sdtx_device *ddev, -+ struct sdtx_base_info __user *buf) -+{ -+ struct ssam_bas_base_info raw; -+ struct sdtx_base_info info; -+ int status; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &raw); -+ if (status < 0) -+ return status; -+ -+ info.state = sdtx_translate_base_state(ddev, raw.state); -+ info.base_id = SDTX_BASE_TYPE_SSH(raw.base_id); -+ -+ if (copy_to_user(buf, &info, sizeof(info))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static int sdtx_ioctl_get_device_mode(struct sdtx_device *ddev, u16 __user *buf) -+{ -+ u8 mode; -+ int status; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); -+ if (status < 0) -+ return status; -+ -+ return put_user(mode, buf); -+} -+ -+static int sdtx_ioctl_get_latch_status(struct sdtx_device *ddev, u16 __user *buf) -+{ -+ u8 latch; -+ int status; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch); -+ if (status < 0) -+ return status; -+ -+ return put_user(sdtx_translate_latch_status(ddev, latch), buf); -+} -+ -+static long __surface_dtx_ioctl(struct sdtx_client *client, unsigned int cmd, unsigned long arg) -+{ -+ struct sdtx_device *ddev = client->ddev; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ switch (cmd) { -+ case SDTX_IOCTL_EVENTS_ENABLE: -+ set_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags); -+ return 0; -+ -+ case SDTX_IOCTL_EVENTS_DISABLE: -+ clear_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags); -+ return 0; -+ -+ case SDTX_IOCTL_LATCH_LOCK: -+ return ssam_retry(ssam_bas_latch_lock, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_UNLOCK: -+ return ssam_retry(ssam_bas_latch_unlock, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_REQUEST: -+ return ssam_retry(ssam_bas_latch_request, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_CONFIRM: -+ return ssam_retry(ssam_bas_latch_confirm, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_HEARTBEAT: -+ return ssam_retry(ssam_bas_latch_heartbeat, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_CANCEL: -+ return ssam_retry(ssam_bas_latch_cancel, ddev->ctrl); -+ -+ case SDTX_IOCTL_GET_BASE_INFO: -+ return sdtx_ioctl_get_base_info(ddev, (struct sdtx_base_info __user *)arg); -+ -+ case SDTX_IOCTL_GET_DEVICE_MODE: -+ return sdtx_ioctl_get_device_mode(ddev, (u16 __user *)arg); -+ -+ case SDTX_IOCTL_GET_LATCH_STATUS: -+ return sdtx_ioctl_get_latch_status(ddev, (u16 __user *)arg); -+ -+ default: -+ return -EINVAL; -+ } -+} -+ -+static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -+{ -+ struct sdtx_client *client = file->private_data; -+ long status; -+ -+ if (down_read_killable(&client->ddev->lock)) -+ return -ERESTARTSYS; -+ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) { -+ up_read(&client->ddev->lock); -+ return -ENODEV; -+ } -+ -+ status = __surface_dtx_ioctl(client, cmd, arg); -+ -+ up_read(&client->ddev->lock); -+ return status; -+} -+ -+ -+/* -- File operations. ------------------------------------------------------ */ -+ -+static int surface_dtx_open(struct inode *inode, struct file *file) -+{ -+ struct sdtx_device *ddev = container_of(file->private_data, struct sdtx_device, mdev); -+ struct sdtx_client *client; -+ -+ /* Initialize client. */ -+ client = kzalloc(sizeof(*client), GFP_KERNEL); -+ if (!client) -+ return -ENOMEM; -+ -+ client->ddev = sdtx_device_get(ddev); -+ -+ INIT_LIST_HEAD(&client->node); -+ -+ mutex_init(&client->read_lock); -+ INIT_KFIFO(client->buffer); -+ -+ file->private_data = client; -+ -+ /* Attach client. */ -+ down_write(&ddev->client_lock); -+ -+ /* -+ * Do not add a new client if the device has been shut down. Note that -+ * it's enough to hold the client_lock here as, during shutdown, we -+ * only acquire that lock and remove clients after marking the device -+ * as shut down. -+ */ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { -+ up_write(&ddev->client_lock); -+ sdtx_device_put(client->ddev); -+ kfree(client); -+ return -ENODEV; -+ } -+ -+ list_add_tail(&client->node, &ddev->client_list); -+ up_write(&ddev->client_lock); -+ -+ stream_open(inode, file); -+ return 0; -+} -+ -+static int surface_dtx_release(struct inode *inode, struct file *file) -+{ -+ struct sdtx_client *client = file->private_data; -+ -+ /* Detach client. */ -+ down_write(&client->ddev->client_lock); -+ list_del(&client->node); -+ up_write(&client->ddev->client_lock); -+ -+ /* Free client. */ -+ sdtx_device_put(client->ddev); -+ mutex_destroy(&client->read_lock); -+ kfree(client); -+ -+ return 0; -+} -+ -+static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs) -+{ -+ struct sdtx_client *client = file->private_data; -+ struct sdtx_device *ddev = client->ddev; -+ unsigned int copied; -+ int status = 0; -+ -+ if (down_read_killable(&ddev->lock)) -+ return -ERESTARTSYS; -+ -+ /* Make sure we're not shut down. */ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { -+ up_read(&ddev->lock); -+ return -ENODEV; -+ } -+ -+ do { -+ /* Check availability, wait if necessary. */ -+ if (kfifo_is_empty(&client->buffer)) { -+ up_read(&ddev->lock); -+ -+ if (file->f_flags & O_NONBLOCK) -+ return -EAGAIN; -+ -+ status = wait_event_interruptible(ddev->waitq, -+ !kfifo_is_empty(&client->buffer) || -+ test_bit(SDTX_DEVICE_SHUTDOWN_BIT, -+ &ddev->flags)); -+ if (status < 0) -+ return status; -+ -+ if (down_read_killable(&client->ddev->lock)) -+ return -ERESTARTSYS; -+ -+ /* Need to check that we're not shut down again. */ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { -+ up_read(&ddev->lock); -+ return -ENODEV; -+ } -+ } -+ -+ /* Try to read from FIFO. */ -+ if (mutex_lock_interruptible(&client->read_lock)) { -+ up_read(&ddev->lock); -+ return -ERESTARTSYS; -+ } -+ -+ status = kfifo_to_user(&client->buffer, buf, count, &copied); -+ mutex_unlock(&client->read_lock); -+ -+ if (status < 0) { -+ up_read(&ddev->lock); -+ return status; -+ } -+ -+ /* We might not have gotten anything, check this here. */ -+ if (copied == 0 && (file->f_flags & O_NONBLOCK)) { -+ up_read(&ddev->lock); -+ return -EAGAIN; -+ } -+ } while (copied == 0); -+ -+ up_read(&ddev->lock); -+ return copied; -+} -+ -+static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt) -+{ -+ struct sdtx_client *client = file->private_data; -+ __poll_t events = 0; -+ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) -+ return EPOLLHUP | EPOLLERR; -+ -+ poll_wait(file, &client->ddev->waitq, pt); -+ -+ if (!kfifo_is_empty(&client->buffer)) -+ events |= EPOLLIN | EPOLLRDNORM; -+ -+ return events; -+} -+ -+static int surface_dtx_fasync(int fd, struct file *file, int on) -+{ -+ struct sdtx_client *client = file->private_data; -+ -+ return fasync_helper(fd, file, on, &client->fasync); -+} -+ -+static const struct file_operations surface_dtx_fops = { -+ .owner = THIS_MODULE, -+ .open = surface_dtx_open, -+ .release = surface_dtx_release, -+ .read = surface_dtx_read, -+ .poll = surface_dtx_poll, -+ .fasync = surface_dtx_fasync, -+ .unlocked_ioctl = surface_dtx_ioctl, -+ .compat_ioctl = surface_dtx_ioctl, -+ .llseek = no_llseek, -+}; -+ -+ -+/* -- Event handling/forwarding. -------------------------------------------- */ -+ -+/* -+ * The device operation mode is not immediately updated on the EC when the -+ * base has been connected, i.e. querying the device mode inside the -+ * connection event callback yields an outdated value. Thus, we can only -+ * determine the new tablet-mode switch and device mode values after some -+ * time. -+ * -+ * These delays have been chosen by experimenting. We first delay on connect -+ * events, then check and validate the device mode against the base state and -+ * if invalid delay again by the "recheck" delay. -+ */ -+#define SDTX_DEVICE_MODE_DELAY_CONNECT msecs_to_jiffies(100) -+#define SDTX_DEVICE_MODE_DELAY_RECHECK msecs_to_jiffies(100) -+ -+struct sdtx_status_event { -+ struct sdtx_event e; -+ __u16 v; -+} __packed; -+ -+struct sdtx_base_info_event { -+ struct sdtx_event e; -+ struct sdtx_base_info v; -+} __packed; -+ -+union sdtx_generic_event { -+ struct sdtx_event common; -+ struct sdtx_status_event status; -+ struct sdtx_base_info_event base; -+}; -+ -+static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay); -+ -+/* Must be executed with ddev->write_lock held. */ -+static void sdtx_push_event(struct sdtx_device *ddev, struct sdtx_event *evt) -+{ -+ const size_t len = sizeof(struct sdtx_event) + evt->length; -+ struct sdtx_client *client; -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ down_read(&ddev->client_lock); -+ list_for_each_entry(client, &ddev->client_list, node) { -+ if (!test_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags)) -+ continue; -+ -+ if (likely(kfifo_avail(&client->buffer) >= len)) -+ kfifo_in(&client->buffer, (const u8 *)evt, len); -+ else -+ dev_warn(ddev->dev, "event buffer overrun\n"); -+ -+ kill_fasync(&client->fasync, SIGIO, POLL_IN); -+ } -+ up_read(&ddev->client_lock); -+ -+ wake_up_interruptible(&ddev->waitq); -+} -+ -+static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in) -+{ -+ struct sdtx_device *ddev = container_of(nf, struct sdtx_device, notif); -+ union sdtx_generic_event event; -+ size_t len; -+ -+ /* Validate event payload length. */ -+ switch (in->command_id) { -+ case SAM_EVENT_CID_DTX_CONNECTION: -+ len = 2 * sizeof(u8); -+ break; -+ -+ case SAM_EVENT_CID_DTX_REQUEST: -+ len = 0; -+ break; -+ -+ case SAM_EVENT_CID_DTX_CANCEL: -+ len = sizeof(u8); -+ break; -+ -+ case SAM_EVENT_CID_DTX_LATCH_STATUS: -+ len = sizeof(u8); -+ break; -+ -+ default: -+ return 0; -+ } -+ -+ if (in->length != len) { -+ dev_err(ddev->dev, -+ "unexpected payload size for event %#04x: got %u, expected %zu\n", -+ in->command_id, in->length, len); -+ return 0; -+ } -+ -+ mutex_lock(&ddev->write_lock); -+ -+ /* Translate event. */ -+ switch (in->command_id) { -+ case SAM_EVENT_CID_DTX_CONNECTION: -+ clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags); -+ -+ /* If state has not changed: do not send new event. */ -+ if (ddev->state.base.state == in->data[0] && -+ ddev->state.base.base_id == in->data[1]) -+ goto out; -+ -+ ddev->state.base.state = in->data[0]; -+ ddev->state.base.base_id = in->data[1]; -+ -+ event.base.e.length = sizeof(struct sdtx_base_info); -+ event.base.e.code = SDTX_EVENT_BASE_CONNECTION; -+ event.base.v.state = sdtx_translate_base_state(ddev, in->data[0]); -+ event.base.v.base_id = SDTX_BASE_TYPE_SSH(in->data[1]); -+ break; -+ -+ case SAM_EVENT_CID_DTX_REQUEST: -+ event.common.code = SDTX_EVENT_REQUEST; -+ event.common.length = 0; -+ break; -+ -+ case SAM_EVENT_CID_DTX_CANCEL: -+ event.status.e.length = sizeof(u16); -+ event.status.e.code = SDTX_EVENT_CANCEL; -+ event.status.v = sdtx_translate_cancel_reason(ddev, in->data[0]); -+ break; -+ -+ case SAM_EVENT_CID_DTX_LATCH_STATUS: -+ clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags); -+ -+ /* If state has not changed: do not send new event. */ -+ if (ddev->state.latch_status == in->data[0]) -+ goto out; -+ -+ ddev->state.latch_status = in->data[0]; -+ -+ event.status.e.length = sizeof(u16); -+ event.status.e.code = SDTX_EVENT_LATCH_STATUS; -+ event.status.v = sdtx_translate_latch_status(ddev, in->data[0]); -+ break; -+ } -+ -+ sdtx_push_event(ddev, &event.common); -+ -+ /* Update device mode on base connection change. */ -+ if (in->command_id == SAM_EVENT_CID_DTX_CONNECTION) { -+ unsigned long delay; -+ -+ delay = in->data[0] ? SDTX_DEVICE_MODE_DELAY_CONNECT : 0; -+ sdtx_update_device_mode(ddev, delay); -+ } -+ -+out: -+ mutex_unlock(&ddev->write_lock); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+ -+/* -- State update functions. ----------------------------------------------- */ -+ -+static bool sdtx_device_mode_invalid(u8 mode, u8 base_state) -+{ -+ return ((base_state == SSAM_BAS_BASE_STATE_ATTACHED) && -+ (mode == SDTX_DEVICE_MODE_TABLET)) || -+ ((base_state == SSAM_BAS_BASE_STATE_DETACH_SUCCESS) && -+ (mode != SDTX_DEVICE_MODE_TABLET)); -+} -+ -+static void sdtx_device_mode_workfn(struct work_struct *work) -+{ -+ struct sdtx_device *ddev = container_of(work, struct sdtx_device, mode_work.work); -+ struct sdtx_status_event event; -+ struct ssam_bas_base_info base; -+ int status, tablet; -+ u8 mode; -+ -+ /* Get operation mode. */ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); -+ if (status) { -+ dev_err(ddev->dev, "failed to get device mode: %d\n", status); -+ return; -+ } -+ -+ /* Get base info. */ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base); -+ if (status) { -+ dev_err(ddev->dev, "failed to get base info: %d\n", status); -+ return; -+ } -+ -+ /* -+ * In some cases (specifically when attaching the base), the device -+ * mode isn't updated right away. Thus we check if the device mode -+ * makes sense for the given base state and try again later if it -+ * doesn't. -+ */ -+ if (sdtx_device_mode_invalid(mode, base.state)) { -+ dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); -+ sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); -+ return; -+ } -+ -+ mutex_lock(&ddev->write_lock); -+ clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags); -+ -+ /* Avoid sending duplicate device-mode events. */ -+ if (ddev->state.device_mode == mode) { -+ mutex_unlock(&ddev->write_lock); -+ return; -+ } -+ -+ ddev->state.device_mode = mode; -+ -+ event.e.length = sizeof(u16); -+ event.e.code = SDTX_EVENT_DEVICE_MODE; -+ event.v = mode; -+ -+ sdtx_push_event(ddev, &event.e); -+ -+ /* Send SW_TABLET_MODE event. */ -+ tablet = mode != SDTX_DEVICE_MODE_LAPTOP; -+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); -+ input_sync(ddev->mode_switch); -+ -+ mutex_unlock(&ddev->write_lock); -+} -+ -+static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay) -+{ -+ schedule_delayed_work(&ddev->mode_work, delay); -+} -+ -+/* Must be executed with ddev->write_lock held. */ -+static void __sdtx_device_state_update_base(struct sdtx_device *ddev, -+ struct ssam_bas_base_info info) -+{ -+ struct sdtx_base_info_event event; -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ /* Prevent duplicate events. */ -+ if (ddev->state.base.state == info.state && -+ ddev->state.base.base_id == info.base_id) -+ return; -+ -+ ddev->state.base = info; -+ -+ event.e.length = sizeof(struct sdtx_base_info); -+ event.e.code = SDTX_EVENT_BASE_CONNECTION; -+ event.v.state = sdtx_translate_base_state(ddev, info.state); -+ event.v.base_id = SDTX_BASE_TYPE_SSH(info.base_id); -+ -+ sdtx_push_event(ddev, &event.e); -+} -+ -+/* Must be executed with ddev->write_lock held. */ -+static void __sdtx_device_state_update_mode(struct sdtx_device *ddev, u8 mode) -+{ -+ struct sdtx_status_event event; -+ int tablet; -+ -+ /* -+ * Note: This function must be called after updating the base state -+ * via __sdtx_device_state_update_base(), as we rely on the updated -+ * base state value in the validity check below. -+ */ -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ if (sdtx_device_mode_invalid(mode, ddev->state.base.state)) { -+ dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); -+ sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); -+ return; -+ } -+ -+ /* Prevent duplicate events. */ -+ if (ddev->state.device_mode == mode) -+ return; -+ -+ ddev->state.device_mode = mode; -+ -+ /* Send event. */ -+ event.e.length = sizeof(u16); -+ event.e.code = SDTX_EVENT_DEVICE_MODE; -+ event.v = mode; -+ -+ sdtx_push_event(ddev, &event.e); -+ -+ /* Send SW_TABLET_MODE event. */ -+ tablet = mode != SDTX_DEVICE_MODE_LAPTOP; -+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); -+ input_sync(ddev->mode_switch); -+} -+ -+/* Must be executed with ddev->write_lock held. */ -+static void __sdtx_device_state_update_latch(struct sdtx_device *ddev, u8 status) -+{ -+ struct sdtx_status_event event; -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ /* Prevent duplicate events. */ -+ if (ddev->state.latch_status == status) -+ return; -+ -+ ddev->state.latch_status = status; -+ -+ event.e.length = sizeof(struct sdtx_base_info); -+ event.e.code = SDTX_EVENT_BASE_CONNECTION; -+ event.v = sdtx_translate_latch_status(ddev, status); -+ -+ sdtx_push_event(ddev, &event.e); -+} -+ -+static void sdtx_device_state_workfn(struct work_struct *work) -+{ -+ struct sdtx_device *ddev = container_of(work, struct sdtx_device, state_work.work); -+ struct ssam_bas_base_info base; -+ u8 mode, latch; -+ int status; -+ -+ /* Mark everything as dirty. */ -+ set_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags); -+ set_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags); -+ set_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags); -+ -+ /* -+ * Ensure that the state gets marked as dirty before continuing to -+ * query it. Necessary to ensure that clear_bit() calls in -+ * sdtx_notifier() and sdtx_device_mode_workfn() actually clear these -+ * bits if an event is received while updating the state here. -+ */ -+ smp_mb__after_atomic(); -+ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base); -+ if (status) { -+ dev_err(ddev->dev, "failed to get base state: %d\n", status); -+ return; -+ } -+ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); -+ if (status) { -+ dev_err(ddev->dev, "failed to get device mode: %d\n", status); -+ return; -+ } -+ -+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch); -+ if (status) { -+ dev_err(ddev->dev, "failed to get latch status: %d\n", status); -+ return; -+ } -+ -+ mutex_lock(&ddev->write_lock); -+ -+ /* -+ * If the respective dirty-bit has been cleared, an event has been -+ * received, updating this state. The queried state may thus be out of -+ * date. At this point, we can safely assume that the state provided -+ * by the event is either up to date, or we're about to receive -+ * another event updating it. -+ */ -+ -+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags)) -+ __sdtx_device_state_update_base(ddev, base); -+ -+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags)) -+ __sdtx_device_state_update_mode(ddev, mode); -+ -+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags)) -+ __sdtx_device_state_update_latch(ddev, latch); -+ -+ mutex_unlock(&ddev->write_lock); -+} -+ -+static void sdtx_update_device_state(struct sdtx_device *ddev, unsigned long delay) -+{ -+ schedule_delayed_work(&ddev->state_work, delay); -+} -+ -+ -+/* -- Common device initialization. ----------------------------------------- */ -+ -+static int sdtx_device_init(struct sdtx_device *ddev, struct device *dev, -+ struct ssam_controller *ctrl) -+{ -+ int status, tablet_mode; -+ -+ /* Basic initialization. */ -+ kref_init(&ddev->kref); -+ init_rwsem(&ddev->lock); -+ ddev->dev = dev; -+ ddev->ctrl = ctrl; -+ -+ ddev->mdev.minor = MISC_DYNAMIC_MINOR; -+ ddev->mdev.name = "surface_dtx"; -+ ddev->mdev.nodename = "surface/dtx"; -+ ddev->mdev.fops = &surface_dtx_fops; -+ -+ ddev->notif.base.priority = 1; -+ ddev->notif.base.fn = sdtx_notifier; -+ ddev->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ ddev->notif.event.id.target_category = SSAM_SSH_TC_BAS; -+ ddev->notif.event.id.instance = 0; -+ ddev->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ ddev->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ init_waitqueue_head(&ddev->waitq); -+ mutex_init(&ddev->write_lock); -+ init_rwsem(&ddev->client_lock); -+ INIT_LIST_HEAD(&ddev->client_list); -+ -+ INIT_DELAYED_WORK(&ddev->mode_work, sdtx_device_mode_workfn); -+ INIT_DELAYED_WORK(&ddev->state_work, sdtx_device_state_workfn); -+ -+ /* -+ * Get current device state. We want to guarantee that events are only -+ * sent when state actually changes. Thus we cannot use special -+ * "uninitialized" values, as that would cause problems when manually -+ * querying the state in surface_dtx_pm_complete(). I.e. we would not -+ * be able to detect state changes there if no change event has been -+ * received between driver initialization and first device suspension. -+ * -+ * Note that we also need to do this before registering the event -+ * notifier, as that may access the state values. -+ */ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &ddev->state.base); -+ if (status) -+ return status; -+ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &ddev->state.device_mode); -+ if (status) -+ return status; -+ -+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &ddev->state.latch_status); -+ if (status) -+ return status; -+ -+ /* Set up tablet mode switch. */ -+ ddev->mode_switch = input_allocate_device(); -+ if (!ddev->mode_switch) -+ return -ENOMEM; -+ -+ ddev->mode_switch->name = "Microsoft Surface DTX Device Mode Switch"; -+ ddev->mode_switch->phys = "ssam/01:11:01:00:00/input0"; -+ ddev->mode_switch->id.bustype = BUS_HOST; -+ ddev->mode_switch->dev.parent = ddev->dev; -+ -+ tablet_mode = (ddev->state.device_mode != SDTX_DEVICE_MODE_LAPTOP); -+ input_set_capability(ddev->mode_switch, EV_SW, SW_TABLET_MODE); -+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet_mode); -+ -+ status = input_register_device(ddev->mode_switch); -+ if (status) { -+ input_free_device(ddev->mode_switch); -+ return status; -+ } -+ -+ /* Set up event notifier. */ -+ status = ssam_notifier_register(ddev->ctrl, &ddev->notif); -+ if (status) -+ goto err_notif; -+ -+ /* Register miscdevice. */ -+ status = misc_register(&ddev->mdev); -+ if (status) -+ goto err_mdev; -+ -+ /* -+ * Update device state in case it has changed between getting the -+ * initial mode and registering the event notifier. -+ */ -+ sdtx_update_device_state(ddev, 0); -+ return 0; -+ -+err_notif: -+ ssam_notifier_unregister(ddev->ctrl, &ddev->notif); -+ cancel_delayed_work_sync(&ddev->mode_work); -+err_mdev: -+ input_unregister_device(ddev->mode_switch); -+ return status; -+} -+ -+static struct sdtx_device *sdtx_device_create(struct device *dev, struct ssam_controller *ctrl) -+{ -+ struct sdtx_device *ddev; -+ int status; -+ -+ ddev = kzalloc(sizeof(*ddev), GFP_KERNEL); -+ if (!ddev) -+ return ERR_PTR(-ENOMEM); -+ -+ status = sdtx_device_init(ddev, dev, ctrl); -+ if (status) { -+ sdtx_device_put(ddev); -+ return ERR_PTR(status); -+ } -+ -+ return ddev; -+} -+ -+static void sdtx_device_destroy(struct sdtx_device *ddev) -+{ -+ struct sdtx_client *client; -+ -+ /* -+ * Mark device as shut-down. Prevent new clients from being added and -+ * new operations from being executed. -+ */ -+ set_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags); -+ -+ /* Disable notifiers, prevent new events from arriving. */ -+ ssam_notifier_unregister(ddev->ctrl, &ddev->notif); -+ -+ /* Stop mode_work, prevent access to mode_switch. */ -+ cancel_delayed_work_sync(&ddev->mode_work); -+ -+ /* Stop state_work. */ -+ cancel_delayed_work_sync(&ddev->state_work); -+ -+ /* With mode_work canceled, we can unregister the mode_switch. */ -+ input_unregister_device(ddev->mode_switch); -+ -+ /* Wake up async clients. */ -+ down_write(&ddev->client_lock); -+ list_for_each_entry(client, &ddev->client_list, node) { -+ kill_fasync(&client->fasync, SIGIO, POLL_HUP); -+ } -+ up_write(&ddev->client_lock); -+ -+ /* Wake up blocking clients. */ -+ wake_up_interruptible(&ddev->waitq); -+ -+ /* -+ * Wait for clients to finish their current operation. After this, the -+ * controller and device references are guaranteed to be no longer in -+ * use. -+ */ -+ down_write(&ddev->lock); -+ ddev->dev = NULL; -+ ddev->ctrl = NULL; -+ up_write(&ddev->lock); -+ -+ /* Finally remove the misc-device. */ -+ misc_deregister(&ddev->mdev); -+ -+ /* -+ * We're now guaranteed that sdtx_device_open() won't be called any -+ * more, so we can now drop out reference. -+ */ -+ sdtx_device_put(ddev); -+} -+ -+ -+/* -- PM ops. --------------------------------------------------------------- */ -+ -+#ifdef CONFIG_PM_SLEEP -+ -+static void surface_dtx_pm_complete(struct device *dev) -+{ -+ struct sdtx_device *ddev = dev_get_drvdata(dev); -+ -+ /* -+ * Normally, the EC will store events while suspended (i.e. in -+ * display-off state) and release them when resumed (i.e. transitioned -+ * to display-on state). During hibernation, however, the EC will be -+ * shut down and does not store events. Furthermore, events might be -+ * dropped during prolonged suspension (it is currently unknown how -+ * big this event buffer is and how it behaves on overruns). -+ * -+ * To prevent any problems, we update the device state here. We do -+ * this delayed to ensure that any events sent by the EC directly -+ * after resuming will be handled first. The delay below has been -+ * chosen (experimentally), so that there should be ample time for -+ * these events to be handled, before we check and, if necessary, -+ * update the state. -+ */ -+ sdtx_update_device_state(ddev, msecs_to_jiffies(1000)); -+} -+ -+static const struct dev_pm_ops surface_dtx_pm_ops = { -+ .complete = surface_dtx_pm_complete, -+}; -+ -+#else /* CONFIG_PM_SLEEP */ -+ -+static const struct dev_pm_ops surface_dtx_pm_ops = {}; -+ -+#endif /* CONFIG_PM_SLEEP */ -+ -+ -+/* -- Platform driver. ------------------------------------------------------ */ -+ -+static int surface_dtx_platform_probe(struct platform_device *pdev) -+{ -+ struct ssam_controller *ctrl; -+ struct sdtx_device *ddev; -+ -+ /* Link to EC. */ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ ddev = sdtx_device_create(&pdev->dev, ctrl); -+ if (IS_ERR(ddev)) -+ return PTR_ERR(ddev); -+ -+ platform_set_drvdata(pdev, ddev); -+ return 0; -+} -+ -+static int surface_dtx_platform_remove(struct platform_device *pdev) -+{ -+ sdtx_device_destroy(platform_get_drvdata(pdev)); -+ return 0; -+} -+ -+static const struct acpi_device_id surface_dtx_acpi_match[] = { -+ { "MSHW0133", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_dtx_acpi_match); -+ -+static struct platform_driver surface_dtx_platform_driver = { -+ .probe = surface_dtx_platform_probe, -+ .remove = surface_dtx_platform_remove, -+ .driver = { -+ .name = "surface_dtx_pltf", -+ .acpi_match_table = surface_dtx_acpi_match, -+ .pm = &surface_dtx_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+/* -- SSAM device driver. --------------------------------------------------- */ -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+ -+static int surface_dtx_ssam_probe(struct ssam_device *sdev) -+{ -+ struct sdtx_device *ddev; -+ -+ ddev = sdtx_device_create(&sdev->dev, sdev->ctrl); -+ if (IS_ERR(ddev)) -+ return PTR_ERR(ddev); -+ -+ ssam_device_set_drvdata(sdev, ddev); -+ return 0; -+} -+ -+static void surface_dtx_ssam_remove(struct ssam_device *sdev) -+{ -+ sdtx_device_destroy(ssam_device_get_drvdata(sdev)); -+} -+ -+static const struct ssam_device_id surface_dtx_ssam_match[] = { -+ { SSAM_SDEV(BAS, 0x01, 0x00, 0x00) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match); -+ -+static struct ssam_device_driver surface_dtx_ssam_driver = { -+ .probe = surface_dtx_ssam_probe, -+ .remove = surface_dtx_ssam_remove, -+ .match_table = surface_dtx_ssam_match, -+ .driver = { -+ .name = "surface_dtx", -+ .pm = &surface_dtx_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+static int ssam_dtx_driver_register(void) -+{ -+ return ssam_device_driver_register(&surface_dtx_ssam_driver); -+} -+ -+static void ssam_dtx_driver_unregister(void) -+{ -+ ssam_device_driver_unregister(&surface_dtx_ssam_driver); -+} -+ -+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+static int ssam_dtx_driver_register(void) -+{ -+ return 0; -+} -+ -+static void ssam_dtx_driver_unregister(void) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+ -+/* -- Module setup. --------------------------------------------------------- */ -+ -+static int __init surface_dtx_init(void) -+{ -+ int status; -+ -+ status = ssam_dtx_driver_register(); -+ if (status) -+ return status; -+ -+ status = platform_driver_register(&surface_dtx_platform_driver); -+ if (status) -+ ssam_dtx_driver_unregister(); -+ -+ return status; -+} -+module_init(surface_dtx_init); -+ -+static void __exit surface_dtx_exit(void) -+{ -+ platform_driver_unregister(&surface_dtx_platform_driver); -+ ssam_dtx_driver_unregister(); -+} -+module_exit(surface_dtx_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_perfmode.c b/drivers/platform/x86/surface_perfmode.c -new file mode 100644 -index 000000000000..3b92a43f8606 ---- /dev/null -+++ b/drivers/platform/x86/surface_perfmode.c -@@ -0,0 +1,122 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface performance-mode driver. -+ * -+ * Provides a user-space interface for the performance mode control provided -+ * by the Surface System Aggregator Module (SSAM), influencing cooling -+ * behavior of the device and potentially managing power limits. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+enum sam_perf_mode { -+ SAM_PERF_MODE_NORMAL = 1, -+ SAM_PERF_MODE_BATTERY = 2, -+ SAM_PERF_MODE_PERF1 = 3, -+ SAM_PERF_MODE_PERF2 = 4, -+ -+ __SAM_PERF_MODE__MIN = 1, -+ __SAM_PERF_MODE__MAX = 4, -+}; -+ -+struct ssam_perf_info { -+ __le32 mode; -+ __le16 unknown1; -+ __le16 unknown2; -+} __packed; -+ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_tmp_perf_mode_get, struct ssam_perf_info, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .command_id = 0x02, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_perf_mode_set, __le32, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .command_id = 0x03, -+}); -+ -+static int ssam_tmp_perf_mode_set(struct ssam_device *sdev, u32 mode) -+{ -+ __le32 mode_le = cpu_to_le32(mode); -+ -+ if (mode < __SAM_PERF_MODE__MIN || mode > __SAM_PERF_MODE__MAX) -+ return -EINVAL; -+ -+ return ssam_retry(__ssam_tmp_perf_mode_set, sdev, &mode_le); -+} -+ -+static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, -+ char *data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ struct ssam_perf_info info; -+ int status; -+ -+ status = ssam_retry(ssam_tmp_perf_mode_get, sdev, &info); -+ if (status) { -+ dev_err(dev, "failed to get current performance mode: %d\n", -+ status); -+ return -EIO; -+ } -+ -+ return sprintf(data, "%d\n", le32_to_cpu(info.mode)); -+} -+ -+static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr, -+ const char *data, size_t count) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ int perf_mode; -+ int status; -+ -+ status = kstrtoint(data, 0, &perf_mode); -+ if (status < 0) -+ return status; -+ -+ status = ssam_tmp_perf_mode_set(sdev, perf_mode); -+ if (status < 0) -+ return status; -+ -+ return count; -+} -+ -+static const DEVICE_ATTR_RW(perf_mode); -+ -+static int surface_sam_sid_perfmode_probe(struct ssam_device *sdev) -+{ -+ return sysfs_create_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); -+} -+ -+static void surface_sam_sid_perfmode_remove(struct ssam_device *sdev) -+{ -+ sysfs_remove_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); -+} -+ -+static const struct ssam_device_id ssam_perfmode_match[] = { -+ { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, ssam_perfmode_match); -+ -+static struct ssam_device_driver surface_sam_sid_perfmode = { -+ .probe = surface_sam_sid_perfmode_probe, -+ .remove = surface_sam_sid_perfmode_remove, -+ .match_table = ssam_perfmode_match, -+ .driver = { -+ .name = "surface_performance_mode", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_sam_sid_perfmode); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Performance mode interface for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig -index 1d656aa2c6d6..6876d5b4d6ac 100644 ---- a/drivers/power/supply/Kconfig -+++ b/drivers/power/supply/Kconfig -@@ -646,4 +646,36 @@ config CHARGER_CROS_USBPD - what is connected to USB PD ports from the EC and converts - that into power_supply properties. - -+config BATTERY_SURFACE -+ tristate "Battery driver for 7th-generation Microsoft Surface devices" -+ depends on SURFACE_AGGREGATOR_REGISTRY -+ help -+ Driver for battery devices connected via/managed by the Surface System -+ Aggregator Module (SSAM). -+ -+ This driver provides battery-information and -status support for -+ Surface devices where said data is not exposed via the standard ACPI -+ devices. On those models (7th-generation), battery-information is -+ instead handled directly via SSAM client devices and this driver. -+ -+ Say M or Y here to include battery status support for 7th-generation -+ Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, -+ Surface Book 3, and Surface Laptop Go. -+ -+config CHARGER_SURFACE -+ tristate "AC driver for 7th-generation Microsoft Surface devices" -+ depends on SURFACE_AGGREGATOR_REGISTRY -+ help -+ Driver for AC devices connected via/managed by the Surface System -+ Aggregator Module (SSAM). -+ -+ This driver provides AC-information and -status support for Surface -+ devices where said data is not exposed via the standard ACPI devices. -+ On those models (7th-generation), AC-information is instead handled -+ directly via a SSAM client device and this driver. -+ -+ Say M or Y here to include AC status support for 7th-generation -+ Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, -+ Surface Book 3, and Surface Laptop Go. -+ - endif # POWER_SUPPLY -diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile -index a26b402c45d9..c8dd853ee382 100644 ---- a/drivers/power/supply/Makefile -+++ b/drivers/power/supply/Makefile -@@ -85,3 +85,5 @@ obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o - obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o - obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o - obj-$(CONFIG_CHARGER_CROS_USBPD) += cros_usbpd-charger.o -+obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o -+obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -new file mode 100644 -index 000000000000..1e48b2363f23 ---- /dev/null -+++ b/drivers/power/supply/surface_battery.c -@@ -0,0 +1,816 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Battery driver for 7th-generation Microsoft Surface devices via Surface -+ * System Aggregator Module (SSAM). -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- SAM interface. -------------------------------------------------------- */ -+ -+enum sam_event_cid_bat { -+ SAM_EVENT_CID_BAT_BIX = 0x15, -+ SAM_EVENT_CID_BAT_BST = 0x16, -+ SAM_EVENT_CID_BAT_ADP = 0x17, -+ SAM_EVENT_CID_BAT_PROT = 0x18, -+ SAM_EVENT_CID_BAT_DPTF = 0x53, -+}; -+ -+enum sam_battery_sta { -+ SAM_BATTERY_STA_OK = 0x0f, -+ SAM_BATTERY_STA_PRESENT = 0x10, -+}; -+ -+enum sam_battery_state { -+ SAM_BATTERY_STATE_DISCHARGING = BIT(0), -+ SAM_BATTERY_STATE_CHARGING = BIT(1), -+ SAM_BATTERY_STATE_CRITICAL = BIT(2), -+}; -+ -+enum sam_battery_power_unit { -+ SAM_BATTERY_POWER_UNIT_mW = 0, -+ SAM_BATTERY_POWER_UNIT_mA = 1, -+}; -+ -+/* Equivalent to data returned in ACPI _BIX method, revision 0. */ -+struct spwr_bix { -+ u8 revision; -+ __le32 power_unit; -+ __le32 design_cap; -+ __le32 last_full_charge_cap; -+ __le32 technology; -+ __le32 design_voltage; -+ __le32 design_cap_warn; -+ __le32 design_cap_low; -+ __le32 cycle_count; -+ __le32 measurement_accuracy; -+ __le32 max_sampling_time; -+ __le32 min_sampling_time; -+ __le32 max_avg_interval; -+ __le32 min_avg_interval; -+ __le32 bat_cap_granularity_1; -+ __le32 bat_cap_granularity_2; -+ __u8 model[21]; -+ __u8 serial[11]; -+ __u8 type[5]; -+ __u8 oem_info[21]; -+} __packed; -+ -+/* Equivalent to data returned in ACPI _BST method. */ -+struct spwr_bst { -+ __le32 state; -+ __le32 present_rate; -+ __le32 remaining_cap; -+ __le32 present_voltage; -+} __packed; -+ -+#define SPWR_BIX_REVISION 0 -+#define SPWR_BATTERY_VALUE_UNKNOWN 0xffffffff -+ -+/* Get battery status (_STA) */ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x01, -+}); -+ -+/* Get battery static information (_BIX). */ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bix, struct spwr_bix, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x02, -+}); -+ -+/* Get battery dynamic information (_BST). */ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bst, struct spwr_bst, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x03, -+}); -+ -+/* Set battery trip point (_BTP). */ -+static SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x04, -+}); -+ -+ -+/* -- Device structures. ---------------------------------------------------- */ -+ -+struct spwr_psy_properties { -+ const char *name; -+ struct ssam_event_registry registry; -+}; -+ -+struct spwr_battery_device { -+ struct ssam_device *sdev; -+ -+ char name[32]; -+ struct power_supply *psy; -+ struct power_supply_desc psy_desc; -+ -+ struct delayed_work update_work; -+ -+ struct ssam_event_notifier notif; -+ -+ struct mutex lock; /* Guards access to state data below. */ -+ unsigned long timestamp; -+ -+ __le32 sta; -+ struct spwr_bix bix; -+ struct spwr_bst bst; -+ u32 alarm; -+}; -+ -+ -+/* -- Module parameters. ---------------------------------------------------- */ -+ -+static unsigned int cache_time = 1000; -+module_param(cache_time, uint, 0644); -+MODULE_PARM_DESC(cache_time, "battery state caching time in milliseconds [default: 1000]"); -+ -+ -+/* -- State management. ----------------------------------------------------- */ -+ -+/* -+ * Delay for battery update quirk. See spwr_external_power_changed() below -+ * for more details. -+ */ -+#define SPWR_AC_BAT_UPDATE_DELAY msecs_to_jiffies(5000) -+ -+static bool spwr_battery_present(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT; -+} -+ -+static int spwr_battery_load_sta(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ return ssam_retry(ssam_bat_get_sta, bat->sdev, &bat->sta); -+} -+ -+static int spwr_battery_load_bix(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (!spwr_battery_present(bat)) -+ return 0; -+ -+ status = ssam_retry(ssam_bat_get_bix, bat->sdev, &bat->bix); -+ -+ /* Enforce NULL terminated strings in case anything goes wrong... */ -+ bat->bix.model[ARRAY_SIZE(bat->bix.model) - 1] = 0; -+ bat->bix.serial[ARRAY_SIZE(bat->bix.serial) - 1] = 0; -+ bat->bix.type[ARRAY_SIZE(bat->bix.type) - 1] = 0; -+ bat->bix.oem_info[ARRAY_SIZE(bat->bix.oem_info) - 1] = 0; -+ -+ return status; -+} -+ -+static int spwr_battery_load_bst(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ if (!spwr_battery_present(bat)) -+ return 0; -+ -+ return ssam_retry(ssam_bat_get_bst, bat->sdev, &bat->bst); -+} -+ -+static int spwr_battery_set_alarm_unlocked(struct spwr_battery_device *bat, u32 value) -+{ -+ __le32 value_le = cpu_to_le32(value); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ bat->alarm = value; -+ return ssam_retry(ssam_bat_set_btp, bat->sdev, &value_le); -+} -+ -+static int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, bool cached) -+{ -+ unsigned long cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time); -+ int status; -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (cached && bat->timestamp && time_is_after_jiffies(cache_deadline)) -+ return 0; -+ -+ status = spwr_battery_load_sta(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bst(bat); -+ if (status) -+ return status; -+ -+ bat->timestamp = jiffies; -+ return 0; -+} -+ -+static int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached) -+{ -+ int status; -+ -+ mutex_lock(&bat->lock); -+ status = spwr_battery_update_bst_unlocked(bat, cached); -+ mutex_unlock(&bat->lock); -+ -+ return status; -+} -+ -+static int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ lockdep_assert_held(&bat->lock); -+ -+ status = spwr_battery_load_sta(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bix(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bst(bat); -+ if (status) -+ return status; -+ -+ if (bat->bix.revision != SPWR_BIX_REVISION) -+ dev_warn(&bat->sdev->dev, "unsupported battery revision: %u\n", bat->bix.revision); -+ -+ bat->timestamp = jiffies; -+ return 0; -+} -+ -+static u32 sprw_battery_get_full_cap_safe(struct spwr_battery_device *bat) -+{ -+ u32 full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN) -+ full_cap = get_unaligned_le32(&bat->bix.design_cap); -+ -+ return full_cap; -+} -+ -+static bool spwr_battery_is_full(struct spwr_battery_device *bat) -+{ -+ u32 state = get_unaligned_le32(&bat->bst.state); -+ u32 full_cap = sprw_battery_get_full_cap_safe(bat); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ return full_cap != SPWR_BATTERY_VALUE_UNKNOWN && full_cap != 0 && -+ remaining_cap != SPWR_BATTERY_VALUE_UNKNOWN && -+ remaining_cap >= full_cap && -+ state == 0; -+} -+ -+static int spwr_battery_recheck_full(struct spwr_battery_device *bat) -+{ -+ bool present; -+ u32 unit; -+ int status; -+ -+ mutex_lock(&bat->lock); -+ unit = get_unaligned_le32(&bat->bix.power_unit); -+ present = spwr_battery_present(bat); -+ -+ status = spwr_battery_update_bix_unlocked(bat); -+ if (status) -+ goto out; -+ -+ /* If battery has been attached, (re-)initialize alarm. */ -+ if (!present && spwr_battery_present(bat)) { -+ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); -+ -+ status = spwr_battery_set_alarm_unlocked(bat, cap_warn); -+ if (status) -+ goto out; -+ } -+ -+ /* -+ * Warn if the unit has changed. This is something we genuinely don't -+ * expect to happen, so make this a big warning. If it does, we'll -+ * need to add support for it. -+ */ -+ WARN_ON(unit != get_unaligned_le32(&bat->bix.power_unit)); -+ -+out: -+ mutex_unlock(&bat->lock); -+ -+ if (!status) -+ power_supply_changed(bat->psy); -+ -+ return status; -+} -+ -+static int spwr_battery_recheck_status(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ status = spwr_battery_update_bst(bat, false); -+ if (!status) -+ power_supply_changed(bat->psy); -+ -+ return status; -+} -+ -+static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif); -+ int status; -+ -+ /* -+ * We cannot use strict matching when registering the notifier as the -+ * EC expects us to register it against instance ID 0. Strict matching -+ * would thus drop events, as those may have non-zero instance IDs in -+ * this subsystem. So we need to check the instance ID of the event -+ * here manually. -+ */ -+ if (event->instance_id != bat->sdev->uid.instance) -+ return 0; -+ -+ dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", -+ event->command_id, event->instance_id, event->target_id); -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_BAT_BIX: -+ status = spwr_battery_recheck_full(bat); -+ break; -+ -+ case SAM_EVENT_CID_BAT_BST: -+ status = spwr_battery_recheck_status(bat); -+ break; -+ -+ case SAM_EVENT_CID_BAT_PROT: -+ /* -+ * TODO: Implement support for battery protection status change -+ * event. -+ */ -+ status = 0; -+ break; -+ -+ case SAM_EVENT_CID_BAT_DPTF: -+ /* -+ * TODO: Implement support for DPTF event. -+ */ -+ status = 0; -+ break; -+ -+ default: -+ return 0; -+ } -+ -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+} -+ -+static void spwr_battery_update_bst_workfn(struct work_struct *work) -+{ -+ struct delayed_work *dwork = to_delayed_work(work); -+ struct spwr_battery_device *bat; -+ int status; -+ -+ bat = container_of(dwork, struct spwr_battery_device, update_work); -+ -+ status = spwr_battery_update_bst(bat, false); -+ if (status) { -+ dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", status); -+ return; -+ } -+ -+ power_supply_changed(bat->psy); -+} -+ -+static void spwr_external_power_changed(struct power_supply *psy) -+{ -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ -+ /* -+ * Handle battery update quirk: When the battery is fully charged (or -+ * charged up to the limit imposed by the UEFI battery limit) and the -+ * adapter is plugged in or removed, the EC does not send a separate -+ * event for the state (charging/discharging) change. Furthermore it -+ * may take some time until the state is updated on the battery. -+ * Schedule an update to solve this. -+ */ -+ -+ schedule_delayed_work(&bat->update_work, SPWR_AC_BAT_UPDATE_DELAY); -+} -+ -+ -+/* -- Properties. ----------------------------------------------------------- */ -+ -+static enum power_supply_property spwr_battery_props_chg[] = { -+ POWER_SUPPLY_PROP_STATUS, -+ POWER_SUPPLY_PROP_PRESENT, -+ POWER_SUPPLY_PROP_TECHNOLOGY, -+ POWER_SUPPLY_PROP_CYCLE_COUNT, -+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, -+ POWER_SUPPLY_PROP_VOLTAGE_NOW, -+ POWER_SUPPLY_PROP_CURRENT_NOW, -+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, -+ POWER_SUPPLY_PROP_CHARGE_FULL, -+ POWER_SUPPLY_PROP_CHARGE_NOW, -+ POWER_SUPPLY_PROP_CAPACITY, -+ POWER_SUPPLY_PROP_CAPACITY_LEVEL, -+ POWER_SUPPLY_PROP_MODEL_NAME, -+ POWER_SUPPLY_PROP_MANUFACTURER, -+ POWER_SUPPLY_PROP_SERIAL_NUMBER, -+}; -+ -+static enum power_supply_property spwr_battery_props_eng[] = { -+ POWER_SUPPLY_PROP_STATUS, -+ POWER_SUPPLY_PROP_PRESENT, -+ POWER_SUPPLY_PROP_TECHNOLOGY, -+ POWER_SUPPLY_PROP_CYCLE_COUNT, -+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, -+ POWER_SUPPLY_PROP_VOLTAGE_NOW, -+ POWER_SUPPLY_PROP_POWER_NOW, -+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, -+ POWER_SUPPLY_PROP_ENERGY_FULL, -+ POWER_SUPPLY_PROP_ENERGY_NOW, -+ POWER_SUPPLY_PROP_CAPACITY, -+ POWER_SUPPLY_PROP_CAPACITY_LEVEL, -+ POWER_SUPPLY_PROP_MODEL_NAME, -+ POWER_SUPPLY_PROP_MANUFACTURER, -+ POWER_SUPPLY_PROP_SERIAL_NUMBER, -+}; -+ -+static int spwr_battery_prop_status(struct spwr_battery_device *bat) -+{ -+ u32 state = get_unaligned_le32(&bat->bst.state); -+ u32 present_rate = get_unaligned_le32(&bat->bst.present_rate); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (state & SAM_BATTERY_STATE_DISCHARGING) -+ return POWER_SUPPLY_STATUS_DISCHARGING; -+ -+ if (state & SAM_BATTERY_STATE_CHARGING) -+ return POWER_SUPPLY_STATUS_CHARGING; -+ -+ if (spwr_battery_is_full(bat)) -+ return POWER_SUPPLY_STATUS_FULL; -+ -+ if (present_rate == 0) -+ return POWER_SUPPLY_STATUS_NOT_CHARGING; -+ -+ return POWER_SUPPLY_STATUS_UNKNOWN; -+} -+ -+static int spwr_battery_prop_technology(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ if (!strcasecmp("NiCd", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_NiCd; -+ -+ if (!strcasecmp("NiMH", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_NiMH; -+ -+ if (!strcasecmp("LION", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_LION; -+ -+ if (!strncasecmp("LI-ION", bat->bix.type, 6)) -+ return POWER_SUPPLY_TECHNOLOGY_LION; -+ -+ if (!strcasecmp("LiP", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_LIPO; -+ -+ return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; -+} -+ -+static int spwr_battery_prop_capacity(struct spwr_battery_device *bat) -+{ -+ u32 full_cap = sprw_battery_get_full_cap_safe(bat); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN) -+ return -ENODATA; -+ -+ if (remaining_cap == SPWR_BATTERY_VALUE_UNKNOWN) -+ return -ENODATA; -+ -+ return remaining_cap * 100 / full_cap; -+} -+ -+static int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat) -+{ -+ u32 state = get_unaligned_le32(&bat->bst.state); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (state & SAM_BATTERY_STATE_CRITICAL) -+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; -+ -+ if (spwr_battery_is_full(bat)) -+ return POWER_SUPPLY_CAPACITY_LEVEL_FULL; -+ -+ if (remaining_cap <= bat->alarm) -+ return POWER_SUPPLY_CAPACITY_LEVEL_LOW; -+ -+ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; -+} -+ -+static int spwr_battery_get_property(struct power_supply *psy, enum power_supply_property psp, -+ union power_supply_propval *val) -+{ -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ u32 value; -+ int status; -+ -+ mutex_lock(&bat->lock); -+ -+ status = spwr_battery_update_bst_unlocked(bat, true); -+ if (status) -+ goto out; -+ -+ /* Abort if battery is not present. */ -+ if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) { -+ status = -ENODEV; -+ goto out; -+ } -+ -+ switch (psp) { -+ case POWER_SUPPLY_PROP_STATUS: -+ val->intval = spwr_battery_prop_status(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_PRESENT: -+ val->intval = spwr_battery_present(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_TECHNOLOGY: -+ val->intval = spwr_battery_prop_technology(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_CYCLE_COUNT: -+ value = get_unaligned_le32(&bat->bix.cycle_count); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: -+ value = get_unaligned_le32(&bat->bix.design_voltage); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_VOLTAGE_NOW: -+ value = get_unaligned_le32(&bat->bst.present_voltage); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CURRENT_NOW: -+ case POWER_SUPPLY_PROP_POWER_NOW: -+ value = get_unaligned_le32(&bat->bst.present_rate); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: -+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: -+ value = get_unaligned_le32(&bat->bix.design_cap); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_FULL: -+ case POWER_SUPPLY_PROP_ENERGY_FULL: -+ value = get_unaligned_le32(&bat->bix.last_full_charge_cap); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_NOW: -+ case POWER_SUPPLY_PROP_ENERGY_NOW: -+ value = get_unaligned_le32(&bat->bst.remaining_cap); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CAPACITY: -+ val->intval = spwr_battery_prop_capacity(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL: -+ val->intval = spwr_battery_prop_capacity_level(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_MODEL_NAME: -+ val->strval = bat->bix.model; -+ break; -+ -+ case POWER_SUPPLY_PROP_MANUFACTURER: -+ val->strval = bat->bix.oem_info; -+ break; -+ -+ case POWER_SUPPLY_PROP_SERIAL_NUMBER: -+ val->strval = bat->bix.serial; -+ break; -+ -+ default: -+ status = -EINVAL; -+ break; -+ } -+ -+out: -+ mutex_unlock(&bat->lock); -+ return status; -+} -+ -+ -+/* -- Device setup. --------------------------------------------------------- */ -+ -+static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_device *sdev, -+ struct ssam_event_registry registry, const char *name) -+{ -+ mutex_init(&bat->lock); -+ strncpy(bat->name, name, ARRAY_SIZE(bat->name) - 1); -+ -+ bat->sdev = sdev; -+ -+ bat->notif.base.priority = 1; -+ bat->notif.base.fn = spwr_notify_bat; -+ bat->notif.event.reg = registry; -+ bat->notif.event.id.target_category = sdev->uid.category; -+ bat->notif.event.id.instance = 0; /* need to register with instance 0 */ -+ bat->notif.event.mask = SSAM_EVENT_MASK_TARGET; -+ bat->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ bat->psy_desc.name = bat->name; -+ bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; -+ bat->psy_desc.get_property = spwr_battery_get_property; -+ -+ INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn); -+} -+ -+static int spwr_battery_register(struct spwr_battery_device *bat) -+{ -+ struct power_supply_config psy_cfg = {}; -+ __le32 sta; -+ int status; -+ -+ /* Make sure the device is there and functioning properly. */ -+ status = ssam_retry(ssam_bat_get_sta, bat->sdev, &sta); -+ if (status) -+ return status; -+ -+ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) -+ return -ENODEV; -+ -+ /* Satisfy lockdep although we are in an exclusive context here. */ -+ mutex_lock(&bat->lock); -+ -+ status = spwr_battery_update_bix_unlocked(bat); -+ if (status) { -+ mutex_unlock(&bat->lock); -+ return status; -+ } -+ -+ if (spwr_battery_present(bat)) { -+ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); -+ -+ status = spwr_battery_set_alarm_unlocked(bat, cap_warn); -+ if (status) { -+ mutex_unlock(&bat->lock); -+ return status; -+ } -+ } -+ -+ mutex_unlock(&bat->lock); -+ -+ bat->psy_desc.external_power_changed = spwr_external_power_changed; -+ -+ switch (get_unaligned_le32(&bat->bix.power_unit)) { -+ case SAM_BATTERY_POWER_UNIT_mW: -+ bat->psy_desc.properties = spwr_battery_props_eng; -+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng); -+ break; -+ -+ case SAM_BATTERY_POWER_UNIT_mA: -+ bat->psy_desc.properties = spwr_battery_props_chg; -+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg); -+ break; -+ -+ default: -+ dev_err(&bat->sdev->dev, "unsupported battery power unit: %u\n", -+ get_unaligned_le32(&bat->bix.power_unit)); -+ return -EINVAL; -+ } -+ -+ psy_cfg.drv_data = bat; -+ bat->psy = devm_power_supply_register(&bat->sdev->dev, &bat->psy_desc, &psy_cfg); -+ if (IS_ERR(bat->psy)) -+ return PTR_ERR(bat->psy); -+ -+ return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int __maybe_unused surface_battery_resume(struct device *dev) -+{ -+ return spwr_battery_recheck_full(dev_get_drvdata(dev)); -+} -+static SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume); -+ -+static int surface_battery_probe(struct ssam_device *sdev) -+{ -+ const struct spwr_psy_properties *p; -+ struct spwr_battery_device *bat; -+ -+ p = ssam_device_get_match_data(sdev); -+ if (!p) -+ return -ENODEV; -+ -+ bat = devm_kzalloc(&sdev->dev, sizeof(*bat), GFP_KERNEL); -+ if (!bat) -+ return -ENOMEM; -+ -+ spwr_battery_init(bat, sdev, p->registry, p->name); -+ ssam_device_set_drvdata(sdev, bat); -+ -+ return spwr_battery_register(bat); -+} -+ -+static void surface_battery_remove(struct ssam_device *sdev) -+{ -+ struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); -+ -+ ssam_notifier_unregister(sdev->ctrl, &bat->notif); -+ cancel_delayed_work_sync(&bat->update_work); -+} -+ -+static const struct spwr_psy_properties spwr_psy_props_bat1 = { -+ .name = "BAT1", -+ .registry = SSAM_EVENT_REGISTRY_SAM, -+}; -+ -+static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { -+ .name = "BAT2", -+ .registry = SSAM_EVENT_REGISTRY_KIP, -+}; -+ -+static const struct ssam_device_id surface_battery_match[] = { -+ { SSAM_SDEV(BAT, 0x01, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, -+ { SSAM_SDEV(BAT, 0x02, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_battery_match); -+ -+static struct ssam_device_driver surface_battery_driver = { -+ .probe = surface_battery_probe, -+ .remove = surface_battery_remove, -+ .match_table = surface_battery_match, -+ .driver = { -+ .name = "surface_battery", -+ .pm = &surface_battery_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_battery_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Battery driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -new file mode 100644 -index 000000000000..1b759c2a31ce ---- /dev/null -+++ b/drivers/power/supply/surface_charger.c -@@ -0,0 +1,282 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * AC driver for 7th-generation Microsoft Surface devices via Surface System -+ * Aggregator Module (SSAM). -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- SAM interface. -------------------------------------------------------- */ -+ -+enum sam_event_cid_bat { -+ SAM_EVENT_CID_BAT_ADP = 0x17, -+}; -+ -+enum sam_battery_sta { -+ SAM_BATTERY_STA_OK = 0x0f, -+ SAM_BATTERY_STA_PRESENT = 0x10, -+}; -+ -+/* Get battery status (_STA). */ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x01, -+}); -+ -+/* Get platform power source for battery (_PSR / DPTF PSRC). */ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x0d, -+}); -+ -+ -+/* -- Device structures. ---------------------------------------------------- */ -+ -+struct spwr_psy_properties { -+ const char *name; -+ struct ssam_event_registry registry; -+}; -+ -+struct spwr_ac_device { -+ struct ssam_device *sdev; -+ -+ char name[32]; -+ struct power_supply *psy; -+ struct power_supply_desc psy_desc; -+ -+ struct ssam_event_notifier notif; -+ -+ struct mutex lock; /* Guards access to state below. */ -+ -+ __le32 state; -+}; -+ -+ -+/* -- State management. ----------------------------------------------------- */ -+ -+static int spwr_ac_update_unlocked(struct spwr_ac_device *ac) -+{ -+ __le32 old = ac->state; -+ int status; -+ -+ lockdep_assert_held(&ac->lock); -+ -+ status = ssam_retry(ssam_bat_get_psrc, ac->sdev, &ac->state); -+ if (status < 0) -+ return status; -+ -+ return old != ac->state; -+} -+ -+static int spwr_ac_update(struct spwr_ac_device *ac) -+{ -+ int status; -+ -+ mutex_lock(&ac->lock); -+ status = spwr_ac_update_unlocked(ac); -+ mutex_unlock(&ac->lock); -+ -+ return status; -+} -+ -+static int spwr_ac_recheck(struct spwr_ac_device *ac) -+{ -+ int status; -+ -+ status = spwr_ac_update(ac); -+ if (status > 0) -+ power_supply_changed(ac->psy); -+ -+ return status >= 0 ? 0 : status; -+} -+ -+static u32 spwr_notify_ac(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct spwr_ac_device *ac; -+ int status; -+ -+ ac = container_of(nf, struct spwr_ac_device, notif); -+ -+ dev_dbg(&ac->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", -+ event->command_id, event->instance_id, event->target_id); -+ -+ /* -+ * Allow events of all targets/instances here. Global adapter status -+ * seems to be handled via target=1 and instance=1, but events are -+ * reported on all targets/instances in use. -+ * -+ * While it should be enough to just listen on 1/1, listen everywhere to -+ * make sure we don't miss anything. -+ */ -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_BAT_ADP: -+ status = spwr_ac_recheck(ac); -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+ -+ default: -+ return 0; -+ } -+} -+ -+ -+/* -- Properties. ----------------------------------------------------------- */ -+ -+static enum power_supply_property spwr_ac_props[] = { -+ POWER_SUPPLY_PROP_ONLINE, -+}; -+ -+static int spwr_ac_get_property(struct power_supply *psy, enum power_supply_property psp, -+ union power_supply_propval *val) -+{ -+ struct spwr_ac_device *ac = power_supply_get_drvdata(psy); -+ int status; -+ -+ mutex_lock(&ac->lock); -+ -+ status = spwr_ac_update_unlocked(ac); -+ if (status) -+ goto out; -+ -+ switch (psp) { -+ case POWER_SUPPLY_PROP_ONLINE: -+ val->intval = !!le32_to_cpu(ac->state); -+ break; -+ -+ default: -+ status = -EINVAL; -+ goto out; -+ } -+ -+out: -+ mutex_unlock(&ac->lock); -+ return status; -+} -+ -+ -+/* -- Device setup. --------------------------------------------------------- */ -+ -+static char *battery_supplied_to[] = { -+ "BAT1", -+ "BAT2", -+}; -+ -+static void spwr_ac_init(struct spwr_ac_device *ac, struct ssam_device *sdev, -+ struct ssam_event_registry registry, const char *name) -+{ -+ mutex_init(&ac->lock); -+ strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1); -+ -+ ac->sdev = sdev; -+ -+ ac->notif.base.priority = 1; -+ ac->notif.base.fn = spwr_notify_ac; -+ ac->notif.event.reg = registry; -+ ac->notif.event.id.target_category = sdev->uid.category; -+ ac->notif.event.id.instance = 0; -+ ac->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ ac->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ ac->psy_desc.name = ac->name; -+ ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; -+ ac->psy_desc.properties = spwr_ac_props; -+ ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props); -+ ac->psy_desc.get_property = spwr_ac_get_property; -+} -+ -+static int spwr_ac_register(struct spwr_ac_device *ac) -+{ -+ struct power_supply_config psy_cfg = {}; -+ __le32 sta; -+ int status; -+ -+ /* Make sure the device is there and functioning properly. */ -+ status = ssam_retry(ssam_bat_get_sta, ac->sdev, &sta); -+ if (status) -+ return status; -+ -+ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) -+ return -ENODEV; -+ -+ psy_cfg.drv_data = ac; -+ psy_cfg.supplied_to = battery_supplied_to; -+ psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); -+ -+ ac->psy = devm_power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg); -+ if (IS_ERR(ac->psy)) -+ return PTR_ERR(ac->psy); -+ -+ return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int __maybe_unused surface_ac_resume(struct device *dev) -+{ -+ return spwr_ac_recheck(dev_get_drvdata(dev)); -+} -+static SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); -+ -+static int surface_ac_probe(struct ssam_device *sdev) -+{ -+ const struct spwr_psy_properties *p; -+ struct spwr_ac_device *ac; -+ -+ p = ssam_device_get_match_data(sdev); -+ if (!p) -+ return -ENODEV; -+ -+ ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL); -+ if (!ac) -+ return -ENOMEM; -+ -+ spwr_ac_init(ac, sdev, p->registry, p->name); -+ ssam_device_set_drvdata(sdev, ac); -+ -+ return spwr_ac_register(ac); -+} -+ -+static void surface_ac_remove(struct ssam_device *sdev) -+{ -+ struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); -+ -+ ssam_notifier_unregister(sdev->ctrl, &ac->notif); -+} -+ -+static const struct spwr_psy_properties spwr_psy_props_adp1 = { -+ .name = "ADP1", -+ .registry = SSAM_EVENT_REGISTRY_SAM, -+}; -+ -+static const struct ssam_device_id surface_ac_match[] = { -+ { SSAM_SDEV(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_ac_match); -+ -+static struct ssam_device_driver surface_ac_driver = { -+ .probe = surface_ac_probe, -+ .remove = surface_ac_remove, -+ .match_table = surface_ac_match, -+ .driver = { -+ .name = "surface_ac", -+ .pm = &surface_ac_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_ac_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("AC driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h -index 69f4527315e7..2c8dd2abbd04 100644 ---- a/include/linux/mod_devicetable.h -+++ b/include/linux/mod_devicetable.h -@@ -770,15 +770,16 @@ struct typec_device_id { - - /* Surface System Aggregator Module */ - --#define SSAM_MATCH_CHANNEL 0x1 -+#define SSAM_MATCH_TARGET 0x1 - #define SSAM_MATCH_INSTANCE 0x2 - #define SSAM_MATCH_FUNCTION 0x4 - - struct ssam_device_id { - __u8 match_flags; - -+ __u8 domain; - __u8 category; -- __u8 channel; -+ __u8 target; - __u8 instance; - __u8 function; - -diff --git a/include/linux/surface_acpi_notify.h b/include/linux/surface_acpi_notify.h -new file mode 100644 -index 000000000000..8e3e86c7d78c ---- /dev/null -+++ b/include/linux/surface_acpi_notify.h -@@ -0,0 +1,39 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Interface for Surface ACPI Notify (SAN) driver. -+ * -+ * Provides access to discrete GPU notifications sent from ACPI via the SAN -+ * driver, which are not handled by this driver directly. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#ifndef _LINUX_SURFACE_ACPI_NOTIFY_H -+#define _LINUX_SURFACE_ACPI_NOTIFY_H -+ -+#include -+#include -+ -+/** -+ * struct san_dgpu_event - Discrete GPU ACPI event. -+ * @category: Category of the event. -+ * @target: Target ID of the event source. -+ * @command: Command ID of the event. -+ * @instance: Instance ID of the event source. -+ * @length: Length of the event's payload data (in bytes). -+ * @payload: Pointer to the event's payload data. -+ */ -+struct san_dgpu_event { -+ u8 category; -+ u8 target; -+ u8 command; -+ u8 instance; -+ u16 length; -+ u8 *payload; -+}; -+ -+int san_client_link(struct device *client); -+int san_dgpu_notifier_register(struct notifier_block *nb); -+int san_dgpu_notifier_unregister(struct notifier_block *nb); -+ -+#endif /* _LINUX_SURFACE_ACPI_NOTIFY_H */ -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -new file mode 100644 -index 000000000000..8cee730e01f3 ---- /dev/null -+++ b/include/linux/surface_aggregator/controller.h -@@ -0,0 +1,849 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Surface System Aggregator Module (SSAM) controller interface. -+ * -+ * Main communication interface for the SSAM EC. Provides a controller -+ * managing access and communication to and from the SSAM EC, as well as main -+ * communication structures and definitions. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H -+#define _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H -+ -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- Main data types and definitions --------------------------------------- */ -+ -+/** -+ * enum ssam_event_flags - Flags for enabling/disabling SAM events -+ * @SSAM_EVENT_SEQUENCED: The event will be sent via a sequenced data frame. -+ */ -+enum ssam_event_flags { -+ SSAM_EVENT_SEQUENCED = BIT(0), -+}; -+ -+/** -+ * struct ssam_event - SAM event sent from the EC to the host. -+ * @target_category: Target category of the event source. See &enum ssam_ssh_tc. -+ * @target_id: Target ID of the event source. -+ * @command_id: Command ID of the event. -+ * @instance_id: Instance ID of the event source. -+ * @length: Length of the event payload in bytes. -+ * @data: Event payload data. -+ */ -+struct ssam_event { -+ u8 target_category; -+ u8 target_id; -+ u8 command_id; -+ u8 instance_id; -+ u16 length; -+ u8 data[]; -+}; -+ -+/** -+ * enum ssam_request_flags - Flags for SAM requests. -+ * -+ * @SSAM_REQUEST_HAS_RESPONSE: -+ * Specifies that the request expects a response. If not set, the request -+ * will be directly completed after its underlying packet has been -+ * transmitted. If set, the request transport system waits for a response -+ * of the request. -+ * -+ * @SSAM_REQUEST_UNSEQUENCED: -+ * Specifies that the request should be transmitted via an unsequenced -+ * packet. If set, the request must not have a response, meaning that this -+ * flag and the %SSAM_REQUEST_HAS_RESPONSE flag are mutually exclusive. -+ */ -+enum ssam_request_flags { -+ SSAM_REQUEST_HAS_RESPONSE = BIT(0), -+ SSAM_REQUEST_UNSEQUENCED = BIT(1), -+}; -+ -+/** -+ * struct ssam_request - SAM request description. -+ * @target_category: Category of the request's target. See &enum ssam_ssh_tc. -+ * @target_id: ID of the request's target. -+ * @command_id: Command ID of the request. -+ * @instance_id: Instance ID of the request's target. -+ * @flags: Flags for the request. See &enum ssam_request_flags. -+ * @length: Length of the request payload in bytes. -+ * @payload: Request payload data. -+ * -+ * This struct fully describes a SAM request with payload. It is intended to -+ * help set up the actual transport struct, e.g. &struct ssam_request_sync, -+ * and specifically its raw message data via ssam_request_write_data(). -+ */ -+struct ssam_request { -+ u8 target_category; -+ u8 target_id; -+ u8 command_id; -+ u8 instance_id; -+ u16 flags; -+ u16 length; -+ const u8 *payload; -+}; -+ -+/** -+ * struct ssam_response - Response buffer for SAM request. -+ * @capacity: Capacity of the buffer, in bytes. -+ * @length: Length of the actual data stored in the memory pointed to by -+ * @pointer, in bytes. Set by the transport system. -+ * @pointer: Pointer to the buffer's memory, storing the response payload data. -+ */ -+struct ssam_response { -+ size_t capacity; -+ size_t length; -+ u8 *pointer; -+}; -+ -+struct ssam_controller; -+ -+struct ssam_controller *ssam_get_controller(void); -+struct ssam_controller *ssam_client_bind(struct device *client); -+int ssam_client_link(struct ssam_controller *ctrl, struct device *client); -+ -+struct device *ssam_controller_device(struct ssam_controller *c); -+ -+struct ssam_controller *ssam_controller_get(struct ssam_controller *c); -+void ssam_controller_put(struct ssam_controller *c); -+ -+void ssam_controller_statelock(struct ssam_controller *c); -+void ssam_controller_stateunlock(struct ssam_controller *c); -+ -+ssize_t ssam_request_write_data(struct ssam_span *buf, -+ struct ssam_controller *ctrl, -+ const struct ssam_request *spec); -+ -+ -+/* -- Synchronous request interface. ---------------------------------------- */ -+ -+/** -+ * struct ssam_request_sync - Synchronous SAM request struct. -+ * @base: Underlying SSH request. -+ * @comp: Completion used to signal full completion of the request. After the -+ * request has been submitted, this struct may only be modified or -+ * deallocated after the completion has been signaled. -+ * request has been submitted, -+ * @resp: Buffer to store the response. -+ * @status: Status of the request, set after the base request has been -+ * completed or has failed. -+ */ -+struct ssam_request_sync { -+ struct ssh_request base; -+ struct completion comp; -+ struct ssam_response *resp; -+ int status; -+}; -+ -+int ssam_request_sync_alloc(size_t payload_len, gfp_t flags, -+ struct ssam_request_sync **rqst, -+ struct ssam_span *buffer); -+ -+void ssam_request_sync_free(struct ssam_request_sync *rqst); -+ -+int ssam_request_sync_init(struct ssam_request_sync *rqst, -+ enum ssam_request_flags flags); -+ -+/** -+ * ssam_request_sync_set_data - Set message data of a synchronous request. -+ * @rqst: The request. -+ * @ptr: Pointer to the request message data. -+ * @len: Length of the request message data. -+ * -+ * Set the request message data of a synchronous request. The provided buffer -+ * needs to live until the request has been completed. -+ */ -+static inline void ssam_request_sync_set_data(struct ssam_request_sync *rqst, -+ u8 *ptr, size_t len) -+{ -+ ssh_request_set_data(&rqst->base, ptr, len); -+} -+ -+/** -+ * ssam_request_sync_set_resp - Set response buffer of a synchronous request. -+ * @rqst: The request. -+ * @resp: The response buffer. -+ * -+ * Sets the response buffer of a synchronous request. This buffer will store -+ * the response of the request after it has been completed. May be %NULL if no -+ * response is expected. -+ */ -+static inline void ssam_request_sync_set_resp(struct ssam_request_sync *rqst, -+ struct ssam_response *resp) -+{ -+ rqst->resp = resp; -+} -+ -+int ssam_request_sync_submit(struct ssam_controller *ctrl, -+ struct ssam_request_sync *rqst); -+ -+/** -+ * ssam_request_sync_wait - Wait for completion of a synchronous request. -+ * @rqst: The request to wait for. -+ * -+ * Wait for completion and release of a synchronous request. After this -+ * function terminates, the request is guaranteed to have left the transport -+ * system. After successful submission of a request, this function must be -+ * called before accessing the response of the request, freeing the request, -+ * or freeing any of the buffers associated with the request. -+ * -+ * This function must not be called if the request has not been submitted yet -+ * and may lead to a deadlock/infinite wait if a subsequent request submission -+ * fails in that case, due to the completion never triggering. -+ * -+ * Return: Returns the status of the given request, which is set on completion -+ * of the packet. This value is zero on success and negative on failure. -+ */ -+static inline int ssam_request_sync_wait(struct ssam_request_sync *rqst) -+{ -+ wait_for_completion(&rqst->comp); -+ return rqst->status; -+} -+ -+int ssam_request_sync(struct ssam_controller *ctrl, -+ const struct ssam_request *spec, -+ struct ssam_response *rsp); -+ -+int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, -+ const struct ssam_request *spec, -+ struct ssam_response *rsp, -+ struct ssam_span *buf); -+ -+/** -+ * ssam_request_sync_onstack - Execute a synchronous request on the stack. -+ * @ctrl: The controller via which the request is submitted. -+ * @rqst: The request specification. -+ * @rsp: The response buffer. -+ * @payload_len: The (maximum) request payload length. -+ * -+ * Allocates a synchronous request with specified payload length on the stack, -+ * fully initializes it via the provided request specification, submits it, -+ * and finally waits for its completion before returning its status. This -+ * helper macro essentially allocates the request message buffer on the stack -+ * and then calls ssam_request_sync_with_buffer(). -+ * -+ * Note: The @payload_len parameter specifies the maximum payload length, used -+ * for buffer allocation. The actual payload length may be smaller. -+ * -+ * Return: Returns the status of the request or any failure during setup, i.e. -+ * zero on success and a negative value on failure. -+ */ -+#define ssam_request_sync_onstack(ctrl, rqst, rsp, payload_len) \ -+ ({ \ -+ u8 __data[SSH_COMMAND_MESSAGE_LENGTH(payload_len)]; \ -+ struct ssam_span __buf = { &__data[0], ARRAY_SIZE(__data) }; \ -+ \ -+ ssam_request_sync_with_buffer(ctrl, rqst, rsp, &__buf); \ -+ }) -+ -+/** -+ * __ssam_retry - Retry request in case of I/O errors or timeouts. -+ * @request: The request function to execute. Must return an integer. -+ * @n: Number of tries. -+ * @args: Arguments for the request function. -+ * -+ * Executes the given request function, i.e. calls @request. In case the -+ * request returns %-EREMOTEIO (indicates I/O error) or %-ETIMEDOUT (request -+ * or underlying packet timed out), @request will be re-executed again, up to -+ * @n times in total. -+ * -+ * Return: Returns the return value of the last execution of @request. -+ */ -+#define __ssam_retry(request, n, args...) \ -+ ({ \ -+ int __i, __s = 0; \ -+ \ -+ for (__i = (n); __i > 0; __i--) { \ -+ __s = request(args); \ -+ if (__s != -ETIMEDOUT && __s != -EREMOTEIO) \ -+ break; \ -+ } \ -+ __s; \ -+ }) -+ -+/** -+ * ssam_retry - Retry request in case of I/O errors or timeouts up to three -+ * times in total. -+ * @request: The request function to execute. Must return an integer. -+ * @args: Arguments for the request function. -+ * -+ * Executes the given request function, i.e. calls @request. In case the -+ * request returns %-EREMOTEIO (indicates I/O error) or -%ETIMEDOUT (request -+ * or underlying packet timed out), @request will be re-executed again, up to -+ * three times in total. -+ * -+ * See __ssam_retry() for a more generic macro for this purpose. -+ * -+ * Return: Returns the return value of the last execution of @request. -+ */ -+#define ssam_retry(request, args...) \ -+ __ssam_retry(request, 3, args) -+ -+/** -+ * struct ssam_request_spec - Blue-print specification of SAM request. -+ * @target_category: Category of the request's target. See &enum ssam_ssh_tc. -+ * @target_id: ID of the request's target. -+ * @command_id: Command ID of the request. -+ * @instance_id: Instance ID of the request's target. -+ * @flags: Flags for the request. See &enum ssam_request_flags. -+ * -+ * Blue-print specification for a SAM request. This struct describes the -+ * unique static parameters of a request (i.e. type) without specifying any of -+ * its instance-specific data (e.g. payload). It is intended to be used as base -+ * for defining simple request functions via the -+ * ``SSAM_DEFINE_SYNC_REQUEST_x()`` family of macros. -+ */ -+struct ssam_request_spec { -+ u8 target_category; -+ u8 target_id; -+ u8 command_id; -+ u8 instance_id; -+ u8 flags; -+}; -+ -+/** -+ * struct ssam_request_spec_md - Blue-print specification for multi-device SAM -+ * request. -+ * @target_category: Category of the request's target. See &enum ssam_ssh_tc. -+ * @command_id: Command ID of the request. -+ * @flags: Flags for the request. See &enum ssam_request_flags. -+ * -+ * Blue-print specification for a multi-device SAM request, i.e. a request -+ * that is applicable to multiple device instances, described by their -+ * individual target and instance IDs. This struct describes the unique static -+ * parameters of a request (i.e. type) without specifying any of its -+ * instance-specific data (e.g. payload) and without specifying any of its -+ * device specific IDs (i.e. target and instance ID). It is intended to be -+ * used as base for defining simple multi-device request functions via the -+ * ``SSAM_DEFINE_SYNC_REQUEST_MD_x()`` and ``SSAM_DEFINE_SYNC_REQUEST_CL_x()`` -+ * families of macros. -+ */ -+struct ssam_request_spec_md { -+ u8 target_category; -+ u8 command_id; -+ u8 flags; -+}; -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_N() - Define synchronous SAM request function -+ * with neither argument nor return value. -+ * @name: Name of the generated function. -+ * @spec: Specification (&struct ssam_request_spec) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request having neither argument nor return value. The -+ * generated function takes care of setting up the request struct and buffer -+ * allocation, as well as execution of the request itself, returning once the -+ * request has been fully completed. The required transport buffer will be -+ * allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_controller -+ * *ctrl)``, returning the status of the request, which is zero on success and -+ * negative on failure. The ``ctrl`` parameter is the controller via which the -+ * request is being sent. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_N(name, spec...) \ -+ int name(struct ssam_controller *ctrl) \ -+ { \ -+ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = s.target_id; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = s.instance_id; \ -+ rqst.flags = s.flags; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, 0); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_W() - Define synchronous SAM request function with -+ * argument. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @spec: Specification (&struct ssam_request_spec) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking an argument of type @atype and having no -+ * return value. The generated function takes care of setting up the request -+ * struct, buffer allocation, as well as execution of the request itself, -+ * returning once the request has been fully completed. The required transport -+ * buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_controller -+ * *ctrl, const atype *arg)``, returning the status of the request, which is -+ * zero on success and negative on failure. The ``ctrl`` parameter is the -+ * controller via which the request is sent. The request argument is specified -+ * via the ``arg`` pointer. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_W(name, atype, spec...) \ -+ int name(struct ssam_controller *ctrl, const atype *arg) \ -+ { \ -+ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = s.target_id; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = s.instance_id; \ -+ rqst.flags = s.flags; \ -+ rqst.length = sizeof(atype); \ -+ rqst.payload = (u8 *)arg; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, \ -+ sizeof(atype)); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_R() - Define synchronous SAM request function with -+ * return value. -+ * @name: Name of the generated function. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking no argument but having a return value of -+ * type @rtype. The generated function takes care of setting up the request -+ * and response structs, buffer allocation, as well as execution of the -+ * request itself, returning once the request has been fully completed. The -+ * required transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_controller -+ * *ctrl, rtype *ret)``, returning the status of the request, which is zero on -+ * success and negative on failure. The ``ctrl`` parameter is the controller -+ * via which the request is sent. The request's return value is written to the -+ * memory pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_R(name, rtype, spec...) \ -+ int name(struct ssam_controller *ctrl, rtype *ret) \ -+ { \ -+ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ -+ struct ssam_request rqst; \ -+ struct ssam_response rsp; \ -+ int status; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = s.target_id; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = s.instance_id; \ -+ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ rsp.capacity = sizeof(rtype); \ -+ rsp.length = 0; \ -+ rsp.pointer = (u8 *)ret; \ -+ \ -+ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, 0); \ -+ if (status) \ -+ return status; \ -+ \ -+ if (rsp.length != sizeof(rtype)) { \ -+ struct device *dev = ssam_controller_device(ctrl); \ -+ dev_err(dev, \ -+ "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ -+ sizeof(rtype), rsp.length, rqst.target_category,\ -+ rqst.command_id); \ -+ return -EIO; \ -+ } \ -+ \ -+ return 0; \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_MD_N() - Define synchronous multi-device SAM -+ * request function with neither argument nor return value. -+ * @name: Name of the generated function. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request having neither argument nor return value. Device -+ * specifying parameters are not hard-coded, but instead must be provided to -+ * the function. The generated function takes care of setting up the request -+ * struct, buffer allocation, as well as execution of the request itself, -+ * returning once the request has been fully completed. The required transport -+ * buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_controller -+ * *ctrl, u8 tid, u8 iid)``, returning the status of the request, which is -+ * zero on success and negative on failure. The ``ctrl`` parameter is the -+ * controller via which the request is sent, ``tid`` the target ID for the -+ * request, and ``iid`` the instance ID. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_MD_N(name, spec...) \ -+ int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \ -+ { \ -+ struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = tid; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = iid; \ -+ rqst.flags = s.flags; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, 0); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_MD_W() - Define synchronous multi-device SAM -+ * request function with argument. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking an argument of type @atype and having no -+ * return value. Device specifying parameters are not hard-coded, but instead -+ * must be provided to the function. The generated function takes care of -+ * setting up the request struct, buffer allocation, as well as execution of -+ * the request itself, returning once the request has been fully completed. -+ * The required transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_controller -+ * *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the status of the -+ * request, which is zero on success and negative on failure. The ``ctrl`` -+ * parameter is the controller via which the request is sent, ``tid`` the -+ * target ID for the request, and ``iid`` the instance ID. The request argument -+ * is specified via the ``arg`` pointer. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_MD_W(name, atype, spec...) \ -+ int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)\ -+ { \ -+ struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = tid; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = iid; \ -+ rqst.flags = s.flags; \ -+ rqst.length = sizeof(atype); \ -+ rqst.payload = (u8 *)arg; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, \ -+ sizeof(atype)); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_MD_R() - Define synchronous multi-device SAM -+ * request function with return value. -+ * @name: Name of the generated function. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking no argument but having a return value of -+ * type @rtype. Device specifying parameters are not hard-coded, but instead -+ * must be provided to the function. The generated function takes care of -+ * setting up the request and response structs, buffer allocation, as well as -+ * execution of the request itself, returning once the request has been fully -+ * completed. The required transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_controller -+ * *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status of the request, -+ * which is zero on success and negative on failure. The ``ctrl`` parameter is -+ * the controller via which the request is sent, ``tid`` the target ID for the -+ * request, and ``iid`` the instance ID. The request's return value is written -+ * to the memory pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_MD_R(name, rtype, spec...) \ -+ int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \ -+ { \ -+ struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ -+ struct ssam_request rqst; \ -+ struct ssam_response rsp; \ -+ int status; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = tid; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = iid; \ -+ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ rsp.capacity = sizeof(rtype); \ -+ rsp.length = 0; \ -+ rsp.pointer = (u8 *)ret; \ -+ \ -+ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, 0); \ -+ if (status) \ -+ return status; \ -+ \ -+ if (rsp.length != sizeof(rtype)) { \ -+ struct device *dev = ssam_controller_device(ctrl); \ -+ dev_err(dev, \ -+ "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ -+ sizeof(rtype), rsp.length, rqst.target_category,\ -+ rqst.command_id); \ -+ return -EIO; \ -+ } \ -+ \ -+ return 0; \ -+ } -+ -+ -+/* -- Event notifier/callbacks. --------------------------------------------- */ -+ -+#define SSAM_NOTIF_STATE_SHIFT 2 -+#define SSAM_NOTIF_STATE_MASK ((1 << SSAM_NOTIF_STATE_SHIFT) - 1) -+ -+/** -+ * enum ssam_notif_flags - Flags used in return values from SSAM notifier -+ * callback functions. -+ * -+ * @SSAM_NOTIF_HANDLED: -+ * Indicates that the notification has been handled. This flag should be -+ * set by the handler if the handler can act/has acted upon the event -+ * provided to it. This flag should not be set if the handler is not a -+ * primary handler intended for the provided event. -+ * -+ * If this flag has not been set by any handler after the notifier chain -+ * has been traversed, a warning will be emitted, stating that the event -+ * has not been handled. -+ * -+ * @SSAM_NOTIF_STOP: -+ * Indicates that the notifier traversal should stop. If this flag is -+ * returned from a notifier callback, notifier chain traversal will -+ * immediately stop and any remaining notifiers will not be called. This -+ * flag is automatically set when ssam_notifier_from_errno() is called -+ * with a negative error value. -+ */ -+enum ssam_notif_flags { -+ SSAM_NOTIF_HANDLED = BIT(0), -+ SSAM_NOTIF_STOP = BIT(1), -+}; -+ -+struct ssam_event_notifier; -+ -+typedef u32 (*ssam_notifier_fn_t)(struct ssam_event_notifier *nf, -+ const struct ssam_event *event); -+ -+/** -+ * struct ssam_notifier_block - Base notifier block for SSAM event -+ * notifications. -+ * @node: The node for the list of notifiers. -+ * @fn: The callback function of this notifier. This function takes the -+ * respective notifier block and event as input and should return -+ * a notifier value, which can either be obtained from the flags -+ * provided in &enum ssam_notif_flags, converted from a standard -+ * error value via ssam_notifier_from_errno(), or a combination of -+ * both (e.g. ``ssam_notifier_from_errno(e) | SSAM_NOTIF_HANDLED``). -+ * @priority: Priority value determining the order in which notifier callbacks -+ * will be called. A higher value means higher priority, i.e. the -+ * associated callback will be executed earlier than other (lower -+ * priority) callbacks. -+ */ -+struct ssam_notifier_block { -+ struct list_head node; -+ ssam_notifier_fn_t fn; -+ int priority; -+}; -+ -+/** -+ * ssam_notifier_from_errno() - Convert standard error value to notifier -+ * return code. -+ * @err: The error code to convert, must be negative (in case of failure) or -+ * zero (in case of success). -+ * -+ * Return: Returns the notifier return value obtained by converting the -+ * specified @err value. In case @err is negative, the %SSAM_NOTIF_STOP flag -+ * will be set, causing notifier call chain traversal to abort. -+ */ -+static inline u32 ssam_notifier_from_errno(int err) -+{ -+ if (WARN_ON(err > 0) || err == 0) -+ return 0; -+ else -+ return ((-err) << SSAM_NOTIF_STATE_SHIFT) | SSAM_NOTIF_STOP; -+} -+ -+/** -+ * ssam_notifier_to_errno() - Convert notifier return code to standard error -+ * value. -+ * @ret: The notifier return value to convert. -+ * -+ * Return: Returns the negative error value encoded in @ret or zero if @ret -+ * indicates success. -+ */ -+static inline int ssam_notifier_to_errno(u32 ret) -+{ -+ return -(ret >> SSAM_NOTIF_STATE_SHIFT); -+} -+ -+ -+/* -- Event/notification registry. ------------------------------------------ */ -+ -+/** -+ * struct ssam_event_registry - Registry specification used for enabling events. -+ * @target_category: Target category for the event registry requests. -+ * @target_id: Target ID for the event registry requests. -+ * @cid_enable: Command ID for the event-enable request. -+ * @cid_disable: Command ID for the event-disable request. -+ * -+ * This struct describes a SAM event registry via the minimal collection of -+ * SAM IDs specifying the requests to use for enabling and disabling an event. -+ * The individual event to be enabled/disabled itself is specified via &struct -+ * ssam_event_id. -+ */ -+struct ssam_event_registry { -+ u8 target_category; -+ u8 target_id; -+ u8 cid_enable; -+ u8 cid_disable; -+}; -+ -+/** -+ * struct ssam_event_id - Unique event ID used for enabling events. -+ * @target_category: Target category of the event source. -+ * @instance: Instance ID of the event source. -+ * -+ * This struct specifies the event to be enabled/disabled via an externally -+ * provided registry. It does not specify the registry to be used itself, this -+ * is done via &struct ssam_event_registry. -+ */ -+struct ssam_event_id { -+ u8 target_category; -+ u8 instance; -+}; -+ -+/** -+ * enum ssam_event_mask - Flags specifying how events are matched to notifiers. -+ * -+ * @SSAM_EVENT_MASK_NONE: -+ * Run the callback for any event with matching target category. Do not -+ * do any additional filtering. -+ * -+ * @SSAM_EVENT_MASK_TARGET: -+ * In addition to filtering by target category, only execute the notifier -+ * callback for events with a target ID matching to the one of the -+ * registry used for enabling/disabling the event. -+ * -+ * @SSAM_EVENT_MASK_INSTANCE: -+ * In addition to filtering by target category, only execute the notifier -+ * callback for events with an instance ID matching to the instance ID -+ * used when enabling the event. -+ * -+ * @SSAM_EVENT_MASK_STRICT: -+ * Do all the filtering above. -+ */ -+enum ssam_event_mask { -+ SSAM_EVENT_MASK_TARGET = BIT(0), -+ SSAM_EVENT_MASK_INSTANCE = BIT(1), -+ -+ SSAM_EVENT_MASK_NONE = 0, -+ SSAM_EVENT_MASK_STRICT = -+ SSAM_EVENT_MASK_TARGET -+ | SSAM_EVENT_MASK_INSTANCE, -+}; -+ -+/** -+ * SSAM_EVENT_REGISTRY() - Define a new event registry. -+ * @tc: Target category for the event registry requests. -+ * @tid: Target ID for the event registry requests. -+ * @cid_en: Command ID for the event-enable request. -+ * @cid_dis: Command ID for the event-disable request. -+ * -+ * Return: Returns the &struct ssam_event_registry specified by the given -+ * parameters. -+ */ -+#define SSAM_EVENT_REGISTRY(tc, tid, cid_en, cid_dis) \ -+ ((struct ssam_event_registry) { \ -+ .target_category = (tc), \ -+ .target_id = (tid), \ -+ .cid_enable = (cid_en), \ -+ .cid_disable = (cid_dis), \ -+ }) -+ -+#define SSAM_EVENT_REGISTRY_SAM \ -+ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_SAM, 0x01, 0x0b, 0x0c) -+ -+#define SSAM_EVENT_REGISTRY_KIP \ -+ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_KIP, 0x02, 0x27, 0x28) -+ -+#define SSAM_EVENT_REGISTRY_REG \ -+ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02) -+ -+/** -+ * enum ssam_event_notifier_flags - Flags for event notifiers. -+ * @SSAM_EVENT_NOTIFIER_OBSERVER: -+ * The corresponding notifier acts as observer. Registering a notifier -+ * with this flag set will not attempt to enable any event. Equally, -+ * unregistering will not attempt to disable any event. Note that a -+ * notifier with this flag may not even correspond to a certain event at -+ * all, only to a specific event target category. Event matching will not -+ * be influenced by this flag. -+ */ -+enum ssam_event_notifier_flags { -+ SSAM_EVENT_NOTIFIER_OBSERVER = BIT(0), -+}; -+ -+/** -+ * struct ssam_event_notifier - Notifier block for SSAM events. -+ * @base: The base notifier block with callback function and priority. -+ * @event: The event for which this block will receive notifications. -+ * @event.reg: Registry via which the event will be enabled/disabled. -+ * @event.id: ID specifying the event. -+ * @event.mask: Flags determining how events are matched to the notifier. -+ * @event.flags: Flags used for enabling the event. -+ * @flags: Notifier flags (see &enum ssam_event_notifier_flags). -+ */ -+struct ssam_event_notifier { -+ struct ssam_notifier_block base; -+ -+ struct { -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ enum ssam_event_mask mask; -+ u8 flags; -+ } event; -+ -+ unsigned long flags; -+}; -+ -+int ssam_notifier_register(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n); -+ -+int ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n); -+ -+int ssam_controller_event_enable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags); -+ -+int ssam_controller_event_disable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags); -+ -+#endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */ -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -new file mode 100644 -index 000000000000..c092211a154d ---- /dev/null -+++ b/include/linux/surface_aggregator/device.h -@@ -0,0 +1,423 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Surface System Aggregator Module (SSAM) bus and client-device subsystem. -+ * -+ * Main interface for the surface-aggregator bus, surface-aggregator client -+ * devices, and respective drivers building on top of the SSAM controller. -+ * Provides support for non-platform/non-ACPI SSAM clients via dedicated -+ * subsystem. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H -+#define _LINUX_SURFACE_AGGREGATOR_DEVICE_H -+ -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- Surface System Aggregator Module bus. --------------------------------- */ -+ -+/** -+ * enum ssam_device_domain - SAM device domain. -+ * @SSAM_DOMAIN_VIRTUAL: Virtual device. -+ * @SSAM_DOMAIN_SERIALHUB: Physical device connected via Surface Serial Hub. -+ */ -+enum ssam_device_domain { -+ SSAM_DOMAIN_VIRTUAL = 0x00, -+ SSAM_DOMAIN_SERIALHUB = 0x01, -+}; -+ -+/** -+ * enum ssam_virtual_tc - Target categories for the virtual SAM domain. -+ * @SSAM_VIRTUAL_TC_HUB: Device hub category. -+ */ -+enum ssam_virtual_tc { -+ SSAM_VIRTUAL_TC_HUB = 0x00, -+}; -+ -+/** -+ * struct ssam_device_uid - Unique identifier for SSAM device. -+ * @domain: Domain of the device. -+ * @category: Target category of the device. -+ * @target: Target ID of the device. -+ * @instance: Instance ID of the device. -+ * @function: Sub-function of the device. This field can be used to split a -+ * single SAM device into multiple virtual subdevices to separate -+ * different functionality of that device and allow one driver per -+ * such functionality. -+ */ -+struct ssam_device_uid { -+ u8 domain; -+ u8 category; -+ u8 target; -+ u8 instance; -+ u8 function; -+}; -+ -+/* -+ * Special values for device matching. -+ * -+ * These values are intended to be used with SSAM_DEVICE(), SSAM_VDEV(), and -+ * SSAM_SDEV() exclusively. Specifically, they are used to initialize the -+ * match_flags member of the device ID structure. Do not use them directly -+ * with struct ssam_device_id or struct ssam_device_uid. -+ */ -+#define SSAM_ANY_TID 0xffff -+#define SSAM_ANY_IID 0xffff -+#define SSAM_ANY_FUN 0xffff -+ -+/** -+ * SSAM_DEVICE() - Initialize a &struct ssam_device_id with the given -+ * parameters. -+ * @d: Domain of the device. -+ * @cat: Target category of the device. -+ * @tid: Target ID of the device. -+ * @iid: Instance ID of the device. -+ * @fun: Sub-function of the device. -+ * -+ * Initializes a &struct ssam_device_id with the given parameters. See &struct -+ * ssam_device_uid for details regarding the parameters. The special values -+ * %SSAM_ANY_TID, %SSAM_ANY_IID, and %SSAM_ANY_FUN can be used to specify that -+ * matching should ignore target ID, instance ID, and/or sub-function, -+ * respectively. This macro initializes the ``match_flags`` field based on the -+ * given parameters. -+ * -+ * Note: The parameters @d and @cat must be valid &u8 values, the parameters -+ * @tid, @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, -+ * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not -+ * allowed. -+ */ -+#define SSAM_DEVICE(d, cat, tid, iid, fun) \ -+ .match_flags = (((tid) != SSAM_ANY_TID) ? SSAM_MATCH_TARGET : 0) \ -+ | (((iid) != SSAM_ANY_IID) ? SSAM_MATCH_INSTANCE : 0) \ -+ | (((fun) != SSAM_ANY_FUN) ? SSAM_MATCH_FUNCTION : 0), \ -+ .domain = d, \ -+ .category = cat, \ -+ .target = __builtin_choose_expr((tid) != SSAM_ANY_TID, (tid), 0), \ -+ .instance = __builtin_choose_expr((iid) != SSAM_ANY_IID, (iid), 0), \ -+ .function = __builtin_choose_expr((fun) != SSAM_ANY_FUN, (fun), 0) -+ -+/** -+ * SSAM_VDEV() - Initialize a &struct ssam_device_id as virtual device with -+ * the given parameters. -+ * @cat: Target category of the device. -+ * @tid: Target ID of the device. -+ * @iid: Instance ID of the device. -+ * @fun: Sub-function of the device. -+ * -+ * Initializes a &struct ssam_device_id with the given parameters in the -+ * virtual domain. See &struct ssam_device_uid for details regarding the -+ * parameters. The special values %SSAM_ANY_TID, %SSAM_ANY_IID, and -+ * %SSAM_ANY_FUN can be used to specify that matching should ignore target ID, -+ * instance ID, and/or sub-function, respectively. This macro initializes the -+ * ``match_flags`` field based on the given parameters. -+ * -+ * Note: The parameter @cat must be a valid &u8 value, the parameters @tid, -+ * @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, -+ * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not -+ * allowed. -+ */ -+#define SSAM_VDEV(cat, tid, iid, fun) \ -+ SSAM_DEVICE(SSAM_DOMAIN_VIRTUAL, SSAM_VIRTUAL_TC_##cat, tid, iid, fun) -+ -+/** -+ * SSAM_SDEV() - Initialize a &struct ssam_device_id as physical SSH device -+ * with the given parameters. -+ * @cat: Target category of the device. -+ * @tid: Target ID of the device. -+ * @iid: Instance ID of the device. -+ * @fun: Sub-function of the device. -+ * -+ * Initializes a &struct ssam_device_id with the given parameters in the SSH -+ * domain. See &struct ssam_device_uid for details regarding the parameters. -+ * The special values %SSAM_ANY_TID, %SSAM_ANY_IID, and %SSAM_ANY_FUN can be -+ * used to specify that matching should ignore target ID, instance ID, and/or -+ * sub-function, respectively. This macro initializes the ``match_flags`` -+ * field based on the given parameters. -+ * -+ * Note: The parameter @cat must be a valid &u8 value, the parameters @tid, -+ * @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, -+ * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not -+ * allowed. -+ */ -+#define SSAM_SDEV(cat, tid, iid, fun) \ -+ SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) -+ -+/** -+ * struct ssam_device - SSAM client device. -+ * @dev: Driver model representation of the device. -+ * @ctrl: SSAM controller managing this device. -+ * @uid: UID identifying the device. -+ */ -+struct ssam_device { -+ struct device dev; -+ struct ssam_controller *ctrl; -+ -+ struct ssam_device_uid uid; -+}; -+ -+/** -+ * struct ssam_device_driver - SSAM client device driver. -+ * @driver: Base driver model structure. -+ * @match_table: Match table specifying which devices the driver should bind to. -+ * @probe: Called when the driver is being bound to a device. -+ * @remove: Called when the driver is being unbound from the device. -+ */ -+struct ssam_device_driver { -+ struct device_driver driver; -+ -+ const struct ssam_device_id *match_table; -+ -+ int (*probe)(struct ssam_device *sdev); -+ void (*remove)(struct ssam_device *sdev); -+}; -+ -+extern struct bus_type ssam_bus_type; -+extern const struct device_type ssam_device_type; -+ -+/** -+ * is_ssam_device() - Check if the given device is a SSAM client device. -+ * @d: The device to test the type of. -+ * -+ * Return: Returns %true if the specified device is of type &struct -+ * ssam_device, i.e. the device type points to %ssam_device_type, and %false -+ * otherwise. -+ */ -+static inline bool is_ssam_device(struct device *d) -+{ -+ return d->type == &ssam_device_type; -+} -+ -+/** -+ * to_ssam_device() - Casts the given device to a SSAM client device. -+ * @d: The device to cast. -+ * -+ * Casts the given &struct device to a &struct ssam_device. The caller has to -+ * ensure that the given device is actually enclosed in a &struct ssam_device, -+ * e.g. by calling is_ssam_device(). -+ * -+ * Return: Returns a pointer to the &struct ssam_device wrapping the given -+ * device @d. -+ */ -+static inline struct ssam_device *to_ssam_device(struct device *d) -+{ -+ return container_of(d, struct ssam_device, dev); -+} -+ -+/** -+ * to_ssam_device_driver() - Casts the given device driver to a SSAM client -+ * device driver. -+ * @d: The driver to cast. -+ * -+ * Casts the given &struct device_driver to a &struct ssam_device_driver. The -+ * caller has to ensure that the given driver is actually enclosed in a -+ * &struct ssam_device_driver. -+ * -+ * Return: Returns the pointer to the &struct ssam_device_driver wrapping the -+ * given device driver @d. -+ */ -+static inline -+struct ssam_device_driver *to_ssam_device_driver(struct device_driver *d) -+{ -+ return container_of(d, struct ssam_device_driver, driver); -+} -+ -+const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table, -+ const struct ssam_device_uid uid); -+ -+const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev); -+ -+const void *ssam_device_get_match_data(const struct ssam_device *dev); -+ -+struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, -+ struct ssam_device_uid uid); -+ -+int ssam_device_add(struct ssam_device *sdev); -+void ssam_device_remove(struct ssam_device *sdev); -+ -+/** -+ * ssam_device_get() - Increment reference count of SSAM client device. -+ * @sdev: The device to increment the reference count of. -+ * -+ * Increments the reference count of the given SSAM client device by -+ * incrementing the reference count of the enclosed &struct device via -+ * get_device(). -+ * -+ * See ssam_device_put() for the counter-part of this function. -+ * -+ * Return: Returns the device provided as input. -+ */ -+static inline struct ssam_device *ssam_device_get(struct ssam_device *sdev) -+{ -+ return sdev ? to_ssam_device(get_device(&sdev->dev)) : NULL; -+} -+ -+/** -+ * ssam_device_put() - Decrement reference count of SSAM client device. -+ * @sdev: The device to decrement the reference count of. -+ * -+ * Decrements the reference count of the given SSAM client device by -+ * decrementing the reference count of the enclosed &struct device via -+ * put_device(). -+ * -+ * See ssam_device_get() for the counter-part of this function. -+ */ -+static inline void ssam_device_put(struct ssam_device *sdev) -+{ -+ if (sdev) -+ put_device(&sdev->dev); -+} -+ -+/** -+ * ssam_device_get_drvdata() - Get driver-data of SSAM client device. -+ * @sdev: The device to get the driver-data from. -+ * -+ * Return: Returns the driver-data of the given device, previously set via -+ * ssam_device_set_drvdata(). -+ */ -+static inline void *ssam_device_get_drvdata(struct ssam_device *sdev) -+{ -+ return dev_get_drvdata(&sdev->dev); -+} -+ -+/** -+ * ssam_device_set_drvdata() - Set driver-data of SSAM client device. -+ * @sdev: The device to set the driver-data of. -+ * @data: The data to set the device's driver-data pointer to. -+ */ -+static inline void ssam_device_set_drvdata(struct ssam_device *sdev, void *data) -+{ -+ dev_set_drvdata(&sdev->dev, data); -+} -+ -+int __ssam_device_driver_register(struct ssam_device_driver *d, struct module *o); -+void ssam_device_driver_unregister(struct ssam_device_driver *d); -+ -+/** -+ * ssam_device_driver_register() - Register a SSAM client device driver. -+ * @drv: The driver to register. -+ */ -+#define ssam_device_driver_register(drv) \ -+ __ssam_device_driver_register(drv, THIS_MODULE) -+ -+/** -+ * module_ssam_device_driver() - Helper macro for SSAM device driver -+ * registration. -+ * @drv: The driver managed by this module. -+ * -+ * Helper macro to register a SSAM device driver via module_init() and -+ * module_exit(). This macro may only be used once per module and replaces the -+ * aforementioned definitions. -+ */ -+#define module_ssam_device_driver(drv) \ -+ module_driver(drv, ssam_device_driver_register, \ -+ ssam_device_driver_unregister) -+ -+ -+/* -- Helpers for client-device requests. ----------------------------------- */ -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_CL_N() - Define synchronous client-device SAM -+ * request function with neither argument nor return value. -+ * @name: Name of the generated function. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request having neither argument nor return value. Device -+ * specifying parameters are not hard-coded, but instead are provided via the -+ * client device, specifically its UID, supplied when calling this function. -+ * The generated function takes care of setting up the request struct, buffer -+ * allocation, as well as execution of the request itself, returning once the -+ * request has been fully completed. The required transport buffer will be -+ * allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_device *sdev)``, -+ * returning the status of the request, which is zero on success and negative -+ * on failure. The ``sdev`` parameter specifies both the target device of the -+ * request and by association the controller via which the request is sent. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_CL_N(name, spec...) \ -+ SSAM_DEFINE_SYNC_REQUEST_MD_N(__raw_##name, spec) \ -+ int name(struct ssam_device *sdev) \ -+ { \ -+ return __raw_##name(sdev->ctrl, sdev->uid.target, \ -+ sdev->uid.instance); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_CL_W() - Define synchronous client-device SAM -+ * request function with argument. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking an argument of type @atype and having no -+ * return value. Device specifying parameters are not hard-coded, but instead -+ * are provided via the client device, specifically its UID, supplied when -+ * calling this function. The generated function takes care of setting up the -+ * request struct, buffer allocation, as well as execution of the request -+ * itself, returning once the request has been fully completed. The required -+ * transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_device *sdev, -+ * const atype *arg)``, returning the status of the request, which is zero on -+ * success and negative on failure. The ``sdev`` parameter specifies both the -+ * target device of the request and by association the controller via which -+ * the request is sent. The request's argument is specified via the ``arg`` -+ * pointer. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_CL_W(name, atype, spec...) \ -+ SSAM_DEFINE_SYNC_REQUEST_MD_W(__raw_##name, atype, spec) \ -+ int name(struct ssam_device *sdev, const atype *arg) \ -+ { \ -+ return __raw_##name(sdev->ctrl, sdev->uid.target, \ -+ sdev->uid.instance, arg); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_CL_R() - Define synchronous client-device SAM -+ * request function with return value. -+ * @name: Name of the generated function. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking no argument but having a return value of -+ * type @rtype. Device specifying parameters are not hard-coded, but instead -+ * are provided via the client device, specifically its UID, supplied when -+ * calling this function. The generated function takes care of setting up the -+ * request struct, buffer allocation, as well as execution of the request -+ * itself, returning once the request has been fully completed. The required -+ * transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_device *sdev, -+ * rtype *ret)``, returning the status of the request, which is zero on -+ * success and negative on failure. The ``sdev`` parameter specifies both the -+ * target device of the request and by association the controller via which -+ * the request is sent. The request's return value is written to the memory -+ * pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \ -+ SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \ -+ int name(struct ssam_device *sdev, rtype *ret) \ -+ { \ -+ return __raw_##name(sdev->ctrl, sdev->uid.target, \ -+ sdev->uid.instance, ret); \ -+ } -+ -+#endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ -diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h -new file mode 100644 -index 000000000000..f02d89168533 ---- /dev/null -+++ b/include/linux/surface_aggregator/serial_hub.h -@@ -0,0 +1,668 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Surface Serial Hub (SSH) protocol and communication interface. -+ * -+ * Lower-level communication layers and SSH protocol definitions for the -+ * Surface System Aggregator Module (SSAM). Provides the interface for basic -+ * packet- and request-based communication with the SSAM EC via SSH. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H -+#define _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H -+ -+#include -+#include -+#include -+#include -+#include -+ -+ -+/* -- Data structures for SAM-over-SSH communication. ----------------------- */ -+ -+/** -+ * enum ssh_frame_type - Frame types for SSH frames. -+ * -+ * @SSH_FRAME_TYPE_DATA_SEQ: -+ * Indicates a data frame, followed by a payload with the length specified -+ * in the ``struct ssh_frame.len`` field. This frame is sequenced, meaning -+ * that an ACK is required. -+ * -+ * @SSH_FRAME_TYPE_DATA_NSQ: -+ * Same as %SSH_FRAME_TYPE_DATA_SEQ, but unsequenced, meaning that the -+ * message does not have to be ACKed. -+ * -+ * @SSH_FRAME_TYPE_ACK: -+ * Indicates an ACK message. -+ * -+ * @SSH_FRAME_TYPE_NAK: -+ * Indicates an error response for previously sent frame. In general, this -+ * means that the frame and/or payload is malformed, e.g. a CRC is wrong. -+ * For command-type payloads, this can also mean that the command is -+ * invalid. -+ */ -+enum ssh_frame_type { -+ SSH_FRAME_TYPE_DATA_SEQ = 0x80, -+ SSH_FRAME_TYPE_DATA_NSQ = 0x00, -+ SSH_FRAME_TYPE_ACK = 0x40, -+ SSH_FRAME_TYPE_NAK = 0x04, -+}; -+ -+/** -+ * struct ssh_frame - SSH communication frame. -+ * @type: The type of the frame. See &enum ssh_frame_type. -+ * @len: The length of the frame payload directly following the CRC for this -+ * frame. Does not include the final CRC for that payload. -+ * @seq: The sequence number for this message/exchange. -+ */ -+struct ssh_frame { -+ u8 type; -+ __le16 len; -+ u8 seq; -+} __packed; -+ -+/* -+ * SSH_FRAME_MAX_PAYLOAD_SIZE - Maximum SSH frame payload length in bytes. -+ * -+ * This is the physical maximum length of the protocol. Implementations may -+ * set a more constrained limit. -+ */ -+#define SSH_FRAME_MAX_PAYLOAD_SIZE U16_MAX -+ -+/** -+ * enum ssh_payload_type - Type indicator for the SSH payload. -+ * @SSH_PLD_TYPE_CMD: The payload is a command structure with optional command -+ * payload. -+ */ -+enum ssh_payload_type { -+ SSH_PLD_TYPE_CMD = 0x80, -+}; -+ -+/** -+ * struct ssh_command - Payload of a command-type frame. -+ * @type: The type of the payload. See &enum ssh_payload_type. Should be -+ * SSH_PLD_TYPE_CMD for this struct. -+ * @tc: Command target category. -+ * @tid_out: Output target ID. Should be zero if this an incoming (EC to host) -+ * message. -+ * @tid_in: Input target ID. Should be zero if this is an outgoing (host to -+ * EC) message. -+ * @iid: Instance ID. -+ * @rqid: Request ID. Used to match requests with responses and differentiate -+ * between responses and events. -+ * @cid: Command ID. -+ */ -+struct ssh_command { -+ u8 type; -+ u8 tc; -+ u8 tid_out; -+ u8 tid_in; -+ u8 iid; -+ __le16 rqid; -+ u8 cid; -+} __packed; -+ -+/* -+ * SSH_COMMAND_MAX_PAYLOAD_SIZE - Maximum SSH command payload length in bytes. -+ * -+ * This is the physical maximum length of the protocol. Implementations may -+ * set a more constrained limit. -+ */ -+#define SSH_COMMAND_MAX_PAYLOAD_SIZE \ -+ (SSH_FRAME_MAX_PAYLOAD_SIZE - sizeof(struct ssh_command)) -+ -+/* -+ * SSH_MSG_LEN_BASE - Base-length of a SSH message. -+ * -+ * This is the minimum number of bytes required to form a message. The actual -+ * message length is SSH_MSG_LEN_BASE plus the length of the frame payload. -+ */ -+#define SSH_MSG_LEN_BASE (sizeof(struct ssh_frame) + 3ull * sizeof(u16)) -+ -+/* -+ * SSH_MSG_LEN_CTRL - Length of a SSH control message. -+ * -+ * This is the length of a SSH control message, which is equal to a SSH -+ * message without any payload. -+ */ -+#define SSH_MSG_LEN_CTRL SSH_MSG_LEN_BASE -+ -+/** -+ * SSH_MESSAGE_LENGTH() - Compute length of SSH message. -+ * @payload_size: Length of the payload inside the SSH frame. -+ * -+ * Return: Returns the length of a SSH message with payload of specified size. -+ */ -+#define SSH_MESSAGE_LENGTH(payload_size) (SSH_MSG_LEN_BASE + (payload_size)) -+ -+/** -+ * SSH_COMMAND_MESSAGE_LENGTH() - Compute length of SSH command message. -+ * @payload_size: Length of the command payload. -+ * -+ * Return: Returns the length of a SSH command message with command payload of -+ * specified size. -+ */ -+#define SSH_COMMAND_MESSAGE_LENGTH(payload_size) \ -+ SSH_MESSAGE_LENGTH(sizeof(struct ssh_command) + (payload_size)) -+ -+/** -+ * SSH_MSGOFFSET_FRAME() - Compute offset in SSH message to specified field in -+ * frame. -+ * @field: The field for which the offset should be computed. -+ * -+ * Return: Returns the offset of the specified &struct ssh_frame field in the -+ * raw SSH message data as. Takes SYN bytes (u16) preceding the frame into -+ * account. -+ */ -+#define SSH_MSGOFFSET_FRAME(field) \ -+ (sizeof(u16) + offsetof(struct ssh_frame, field)) -+ -+/** -+ * SSH_MSGOFFSET_COMMAND() - Compute offset in SSH message to specified field -+ * in command. -+ * @field: The field for which the offset should be computed. -+ * -+ * Return: Returns the offset of the specified &struct ssh_command field in -+ * the raw SSH message data. Takes SYN bytes (u16) preceding the frame and the -+ * frame CRC (u16) between frame and command into account. -+ */ -+#define SSH_MSGOFFSET_COMMAND(field) \ -+ (2ull * sizeof(u16) + sizeof(struct ssh_frame) \ -+ + offsetof(struct ssh_command, field)) -+ -+/* -+ * SSH_MSG_SYN - SSH message synchronization (SYN) bytes as u16. -+ */ -+#define SSH_MSG_SYN ((u16)0x55aa) -+ -+/** -+ * ssh_crc() - Compute CRC for SSH messages. -+ * @buf: The pointer pointing to the data for which the CRC should be computed. -+ * @len: The length of the data for which the CRC should be computed. -+ * -+ * Return: Returns the CRC computed on the provided data, as used for SSH -+ * messages. -+ */ -+static inline u16 ssh_crc(const u8 *buf, size_t len) -+{ -+ return crc_ccitt_false(0xffff, buf, len); -+} -+ -+/* -+ * SSH_NUM_EVENTS - The number of reserved event IDs. -+ * -+ * The number of reserved event IDs, used for registering an SSH event -+ * handler. Valid event IDs are numbers below or equal to this value, with -+ * exception of zero, which is not an event ID. Thus, this is also the -+ * absolute maximum number of event handlers that can be registered. -+ */ -+#define SSH_NUM_EVENTS 34 -+ -+/* -+ * SSH_NUM_TARGETS - The number of communication targets used in the protocol. -+ */ -+#define SSH_NUM_TARGETS 2 -+ -+/** -+ * ssh_rqid_next_valid() - Return the next valid request ID. -+ * @rqid: The current request ID. -+ * -+ * Return: Returns the next valid request ID, following the current request ID -+ * provided to this function. This function skips any request IDs reserved for -+ * events. -+ */ -+static inline u16 ssh_rqid_next_valid(u16 rqid) -+{ -+ return rqid > 0 ? rqid + 1u : rqid + SSH_NUM_EVENTS + 1u; -+} -+ -+/** -+ * ssh_rqid_to_event() - Convert request ID to its corresponding event ID. -+ * @rqid: The request ID to convert. -+ */ -+static inline u16 ssh_rqid_to_event(u16 rqid) -+{ -+ return rqid - 1u; -+} -+ -+/** -+ * ssh_rqid_is_event() - Check if given request ID is a valid event ID. -+ * @rqid: The request ID to check. -+ */ -+static inline bool ssh_rqid_is_event(u16 rqid) -+{ -+ return ssh_rqid_to_event(rqid) < SSH_NUM_EVENTS; -+} -+ -+/** -+ * ssh_tc_to_rqid() - Convert target category to its corresponding request ID. -+ * @tc: The target category to convert. -+ */ -+static inline u16 ssh_tc_to_rqid(u8 tc) -+{ -+ return tc; -+} -+ -+/** -+ * ssh_tid_to_index() - Convert target ID to its corresponding target index. -+ * @tid: The target ID to convert. -+ */ -+static inline u8 ssh_tid_to_index(u8 tid) -+{ -+ return tid - 1u; -+} -+ -+/** -+ * ssh_tid_is_valid() - Check if target ID is valid/supported. -+ * @tid: The target ID to check. -+ */ -+static inline bool ssh_tid_is_valid(u8 tid) -+{ -+ return ssh_tid_to_index(tid) < SSH_NUM_TARGETS; -+} -+ -+/** -+ * struct ssam_span - Reference to a buffer region. -+ * @ptr: Pointer to the buffer region. -+ * @len: Length of the buffer region. -+ * -+ * A reference to a (non-owned) buffer segment, consisting of pointer and -+ * length. Use of this struct indicates non-owned data, i.e. data of which the -+ * life-time is managed (i.e. it is allocated/freed) via another pointer. -+ */ -+struct ssam_span { -+ u8 *ptr; -+ size_t len; -+}; -+ -+/* -+ * Known SSH/EC target categories. -+ * -+ * List of currently known target category values; "Known" as in we know they -+ * exist and are valid on at least some device/model. Detailed functionality -+ * or the full category name is only known for some of these categories and -+ * is detailed in the respective comment below. -+ * -+ * These values and abbreviations have been extracted from strings inside the -+ * Windows driver. -+ */ -+enum ssam_ssh_tc { -+ /* Category 0x00 is invalid for EC use. */ -+ SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */ -+ SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */ -+ SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */ -+ SSAM_SSH_TC_PMC = 0x04, -+ SSAM_SSH_TC_FAN = 0x05, -+ SSAM_SSH_TC_PoM = 0x06, -+ SSAM_SSH_TC_DBG = 0x07, -+ SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */ -+ SSAM_SSH_TC_FWU = 0x09, -+ SSAM_SSH_TC_UNI = 0x0a, -+ SSAM_SSH_TC_LPC = 0x0b, -+ SSAM_SSH_TC_TCL = 0x0c, -+ SSAM_SSH_TC_SFL = 0x0d, -+ SSAM_SSH_TC_KIP = 0x0e, -+ SSAM_SSH_TC_EXT = 0x0f, -+ SSAM_SSH_TC_BLD = 0x10, -+ SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ -+ SSAM_SSH_TC_SEN = 0x12, -+ SSAM_SSH_TC_SRQ = 0x13, -+ SSAM_SSH_TC_MCU = 0x14, -+ SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */ -+ SSAM_SSH_TC_TCH = 0x16, -+ SSAM_SSH_TC_BKL = 0x17, -+ SSAM_SSH_TC_TAM = 0x18, -+ SSAM_SSH_TC_ACC = 0x19, -+ SSAM_SSH_TC_UFI = 0x1a, -+ SSAM_SSH_TC_USC = 0x1b, -+ SSAM_SSH_TC_PEN = 0x1c, -+ SSAM_SSH_TC_VID = 0x1d, -+ SSAM_SSH_TC_AUD = 0x1e, -+ SSAM_SSH_TC_SMC = 0x1f, -+ SSAM_SSH_TC_KPD = 0x20, -+ SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */ -+}; -+ -+ -+/* -- Packet transport layer (ptl). ----------------------------------------- */ -+ -+/** -+ * enum ssh_packet_base_priority - Base priorities for &struct ssh_packet. -+ * @SSH_PACKET_PRIORITY_FLUSH: Base priority for flush packets. -+ * @SSH_PACKET_PRIORITY_DATA: Base priority for normal data packets. -+ * @SSH_PACKET_PRIORITY_NAK: Base priority for NAK packets. -+ * @SSH_PACKET_PRIORITY_ACK: Base priority for ACK packets. -+ */ -+enum ssh_packet_base_priority { -+ SSH_PACKET_PRIORITY_FLUSH = 0, /* same as DATA to sequence flush */ -+ SSH_PACKET_PRIORITY_DATA = 0, -+ SSH_PACKET_PRIORITY_NAK = 1, -+ SSH_PACKET_PRIORITY_ACK = 2, -+}; -+ -+/* -+ * Same as SSH_PACKET_PRIORITY() below, only with actual values. -+ */ -+#define __SSH_PACKET_PRIORITY(base, try) \ -+ (((base) << 4) | ((try) & 0x0f)) -+ -+/** -+ * SSH_PACKET_PRIORITY() - Compute packet priority from base priority and -+ * number of tries. -+ * @base: The base priority as suffix of &enum ssh_packet_base_priority, e.g. -+ * ``FLUSH``, ``DATA``, ``ACK``, or ``NAK``. -+ * @try: The number of tries (must be less than 16). -+ * -+ * Compute the combined packet priority. The combined priority is dominated by -+ * the base priority, whereas the number of (re-)tries decides the precedence -+ * of packets with the same base priority, giving higher priority to packets -+ * that already have more tries. -+ * -+ * Return: Returns the computed priority as value fitting inside a &u8. A -+ * higher number means a higher priority. -+ */ -+#define SSH_PACKET_PRIORITY(base, try) \ -+ __SSH_PACKET_PRIORITY(SSH_PACKET_PRIORITY_##base, (try)) -+ -+/** -+ * ssh_packet_priority_get_try() - Get number of tries from packet priority. -+ * @priority: The packet priority. -+ * -+ * Return: Returns the number of tries encoded in the specified packet -+ * priority. -+ */ -+static inline u8 ssh_packet_priority_get_try(u8 priority) -+{ -+ return priority & 0x0f; -+} -+ -+/** -+ * ssh_packet_priority_get_base - Get base priority from packet priority. -+ * @priority: The packet priority. -+ * -+ * Return: Returns the base priority encoded in the given packet priority. -+ */ -+static inline u8 ssh_packet_priority_get_base(u8 priority) -+{ -+ return (priority & 0xf0) >> 4; -+} -+ -+enum ssh_packet_flags { -+ /* state flags */ -+ SSH_PACKET_SF_LOCKED_BIT, -+ SSH_PACKET_SF_QUEUED_BIT, -+ SSH_PACKET_SF_PENDING_BIT, -+ SSH_PACKET_SF_TRANSMITTING_BIT, -+ SSH_PACKET_SF_TRANSMITTED_BIT, -+ SSH_PACKET_SF_ACKED_BIT, -+ SSH_PACKET_SF_CANCELED_BIT, -+ SSH_PACKET_SF_COMPLETED_BIT, -+ -+ /* type flags */ -+ SSH_PACKET_TY_FLUSH_BIT, -+ SSH_PACKET_TY_SEQUENCED_BIT, -+ SSH_PACKET_TY_BLOCKING_BIT, -+ -+ /* mask for state flags */ -+ SSH_PACKET_FLAGS_SF_MASK = -+ BIT(SSH_PACKET_SF_LOCKED_BIT) -+ | BIT(SSH_PACKET_SF_QUEUED_BIT) -+ | BIT(SSH_PACKET_SF_PENDING_BIT) -+ | BIT(SSH_PACKET_SF_TRANSMITTING_BIT) -+ | BIT(SSH_PACKET_SF_TRANSMITTED_BIT) -+ | BIT(SSH_PACKET_SF_ACKED_BIT) -+ | BIT(SSH_PACKET_SF_CANCELED_BIT) -+ | BIT(SSH_PACKET_SF_COMPLETED_BIT), -+ -+ /* mask for type flags */ -+ SSH_PACKET_FLAGS_TY_MASK = -+ BIT(SSH_PACKET_TY_FLUSH_BIT) -+ | BIT(SSH_PACKET_TY_SEQUENCED_BIT) -+ | BIT(SSH_PACKET_TY_BLOCKING_BIT), -+}; -+ -+struct ssh_ptl; -+struct ssh_packet; -+ -+/** -+ * struct ssh_packet_ops - Callback operations for a SSH packet. -+ * @release: Function called when the packet reference count reaches zero. -+ * This callback must be relied upon to ensure that the packet has -+ * left the transport system(s). -+ * @complete: Function called when the packet is completed, either with -+ * success or failure. In case of failure, the reason for the -+ * failure is indicated by the value of the provided status code -+ * argument. This value will be zero in case of success. Note that -+ * a call to this callback does not guarantee that the packet is -+ * not in use by the transport system any more. -+ */ -+struct ssh_packet_ops { -+ void (*release)(struct ssh_packet *p); -+ void (*complete)(struct ssh_packet *p, int status); -+}; -+ -+/** -+ * struct ssh_packet - SSH transport packet. -+ * @ptl: Pointer to the packet transport layer. May be %NULL if the packet -+ * (or enclosing request) has not been submitted yet. -+ * @refcnt: Reference count of the packet. -+ * @priority: Priority of the packet. Must be computed via -+ * SSH_PACKET_PRIORITY(). Must only be accessed while holding the -+ * queue lock after first submission. -+ * @data: Raw message data. -+ * @data.len: Length of the raw message data. -+ * @data.ptr: Pointer to the raw message data buffer. -+ * @state: State and type flags describing current packet state (dynamic) -+ * and type (static). See &enum ssh_packet_flags for possible -+ * options. -+ * @timestamp: Timestamp specifying when the latest transmission of a -+ * currently pending packet has been started. May be %KTIME_MAX -+ * before or in-between transmission attempts. Used for the packet -+ * timeout implementation. Must only be accessed while holding the -+ * pending lock after first submission. -+ * @queue_node: The list node for the packet queue. -+ * @pending_node: The list node for the set of pending packets. -+ * @ops: Packet operations. -+ */ -+struct ssh_packet { -+ struct ssh_ptl *ptl; -+ struct kref refcnt; -+ -+ u8 priority; -+ -+ struct { -+ size_t len; -+ u8 *ptr; -+ } data; -+ -+ unsigned long state; -+ ktime_t timestamp; -+ -+ struct list_head queue_node; -+ struct list_head pending_node; -+ -+ const struct ssh_packet_ops *ops; -+}; -+ -+struct ssh_packet *ssh_packet_get(struct ssh_packet *p); -+void ssh_packet_put(struct ssh_packet *p); -+ -+/** -+ * ssh_packet_set_data() - Set raw message data of packet. -+ * @p: The packet for which the message data should be set. -+ * @ptr: Pointer to the memory holding the message data. -+ * @len: Length of the message data. -+ * -+ * Sets the raw message data buffer of the packet to the provided memory. The -+ * memory is not copied. Instead, the caller is responsible for management -+ * (i.e. allocation and deallocation) of the memory. The caller must ensure -+ * that the provided memory is valid and contains a valid SSH message, -+ * starting from the time of submission of the packet until the ``release`` -+ * callback has been called. During this time, the memory may not be altered -+ * in any way. -+ */ -+static inline void ssh_packet_set_data(struct ssh_packet *p, u8 *ptr, size_t len) -+{ -+ p->data.ptr = ptr; -+ p->data.len = len; -+} -+ -+ -+/* -- Request transport layer (rtl). ---------------------------------------- */ -+ -+enum ssh_request_flags { -+ /* state flags */ -+ SSH_REQUEST_SF_LOCKED_BIT, -+ SSH_REQUEST_SF_QUEUED_BIT, -+ SSH_REQUEST_SF_PENDING_BIT, -+ SSH_REQUEST_SF_TRANSMITTING_BIT, -+ SSH_REQUEST_SF_TRANSMITTED_BIT, -+ SSH_REQUEST_SF_RSPRCVD_BIT, -+ SSH_REQUEST_SF_CANCELED_BIT, -+ SSH_REQUEST_SF_COMPLETED_BIT, -+ -+ /* type flags */ -+ SSH_REQUEST_TY_FLUSH_BIT, -+ SSH_REQUEST_TY_HAS_RESPONSE_BIT, -+ -+ /* mask for state flags */ -+ SSH_REQUEST_FLAGS_SF_MASK = -+ BIT(SSH_REQUEST_SF_LOCKED_BIT) -+ | BIT(SSH_REQUEST_SF_QUEUED_BIT) -+ | BIT(SSH_REQUEST_SF_PENDING_BIT) -+ | BIT(SSH_REQUEST_SF_TRANSMITTING_BIT) -+ | BIT(SSH_REQUEST_SF_TRANSMITTED_BIT) -+ | BIT(SSH_REQUEST_SF_RSPRCVD_BIT) -+ | BIT(SSH_REQUEST_SF_CANCELED_BIT) -+ | BIT(SSH_REQUEST_SF_COMPLETED_BIT), -+ -+ /* mask for type flags */ -+ SSH_REQUEST_FLAGS_TY_MASK = -+ BIT(SSH_REQUEST_TY_FLUSH_BIT) -+ | BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT), -+}; -+ -+struct ssh_rtl; -+struct ssh_request; -+ -+/** -+ * struct ssh_request_ops - Callback operations for a SSH request. -+ * @release: Function called when the request's reference count reaches zero. -+ * This callback must be relied upon to ensure that the request has -+ * left the transport systems (both, packet an request systems). -+ * @complete: Function called when the request is completed, either with -+ * success or failure. The command data for the request response -+ * is provided via the &struct ssh_command parameter (``cmd``), -+ * the command payload of the request response via the &struct -+ * ssh_span parameter (``data``). -+ * -+ * If the request does not have any response or has not been -+ * completed with success, both ``cmd`` and ``data`` parameters will -+ * be NULL. If the request response does not have any command -+ * payload, the ``data`` span will be an empty (zero-length) span. -+ * -+ * In case of failure, the reason for the failure is indicated by -+ * the value of the provided status code argument (``status``). This -+ * value will be zero in case of success and a regular errno -+ * otherwise. -+ * -+ * Note that a call to this callback does not guarantee that the -+ * request is not in use by the transport systems any more. -+ */ -+struct ssh_request_ops { -+ void (*release)(struct ssh_request *rqst); -+ void (*complete)(struct ssh_request *rqst, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data, int status); -+}; -+ -+/** -+ * struct ssh_request - SSH transport request. -+ * @packet: The underlying SSH transport packet. -+ * @node: List node for the request queue and pending set. -+ * @state: State and type flags describing current request state (dynamic) -+ * and type (static). See &enum ssh_request_flags for possible -+ * options. -+ * @timestamp: Timestamp specifying when we start waiting on the response of -+ * the request. This is set once the underlying packet has been -+ * completed and may be %KTIME_MAX before that, or when the request -+ * does not expect a response. Used for the request timeout -+ * implementation. -+ * @ops: Request Operations. -+ */ -+struct ssh_request { -+ struct ssh_packet packet; -+ struct list_head node; -+ -+ unsigned long state; -+ ktime_t timestamp; -+ -+ const struct ssh_request_ops *ops; -+}; -+ -+/** -+ * to_ssh_request() - Cast a SSH packet to its enclosing SSH request. -+ * @p: The packet to cast. -+ * -+ * Casts the given &struct ssh_packet to its enclosing &struct ssh_request. -+ * The caller is responsible for making sure that the packet is actually -+ * wrapped in a &struct ssh_request. -+ * -+ * Return: Returns the &struct ssh_request wrapping the provided packet. -+ */ -+static inline struct ssh_request *to_ssh_request(struct ssh_packet *p) -+{ -+ return container_of(p, struct ssh_request, packet); -+} -+ -+/** -+ * ssh_request_get() - Increment reference count of request. -+ * @r: The request to increment the reference count of. -+ * -+ * Increments the reference count of the given request by incrementing the -+ * reference count of the underlying &struct ssh_packet, enclosed in it. -+ * -+ * See also ssh_request_put(), ssh_packet_get(). -+ * -+ * Return: Returns the request provided as input. -+ */ -+static inline struct ssh_request *ssh_request_get(struct ssh_request *r) -+{ -+ return r ? to_ssh_request(ssh_packet_get(&r->packet)) : NULL; -+} -+ -+/** -+ * ssh_request_put() - Decrement reference count of request. -+ * @r: The request to decrement the reference count of. -+ * -+ * Decrements the reference count of the given request by decrementing the -+ * reference count of the underlying &struct ssh_packet, enclosed in it. If -+ * the reference count reaches zero, the ``release`` callback specified in the -+ * request's &struct ssh_request_ops, i.e. ``r->ops->release``, will be -+ * called. -+ * -+ * See also ssh_request_get(), ssh_packet_put(). -+ */ -+static inline void ssh_request_put(struct ssh_request *r) -+{ -+ if (r) -+ ssh_packet_put(&r->packet); -+} -+ -+/** -+ * ssh_request_set_data() - Set raw message data of request. -+ * @r: The request for which the message data should be set. -+ * @ptr: Pointer to the memory holding the message data. -+ * @len: Length of the message data. -+ * -+ * Sets the raw message data buffer of the underlying packet to the specified -+ * buffer. Does not copy the actual message data, just sets the buffer pointer -+ * and length. Refer to ssh_packet_set_data() for more details. -+ */ -+static inline void ssh_request_set_data(struct ssh_request *r, u8 *ptr, size_t len) -+{ -+ ssh_packet_set_data(&r->packet, ptr, len); -+} -+ -+#endif /* _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H */ -diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h -new file mode 100644 -index 000000000000..08f46b60b151 ---- /dev/null -+++ b/include/uapi/linux/surface_aggregator/cdev.h -@@ -0,0 +1,147 @@ -+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ -+/* -+ * Surface System Aggregator Module (SSAM) user-space EC interface. -+ * -+ * Definitions, structs, and IOCTLs for the /dev/surface/aggregator misc -+ * device. This device provides direct user-space access to the SSAM EC. -+ * Intended for debugging and development. -+ * -+ * Copyright (C) 2020-2021 Maximilian Luz -+ */ -+ -+#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H -+#define _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H -+ -+#include -+#include -+ -+/** -+ * enum ssam_cdev_request_flags - Request flags for SSAM cdev request IOCTL. -+ * -+ * @SSAM_CDEV_REQUEST_HAS_RESPONSE: -+ * Specifies that the request expects a response. If not set, the request -+ * will be directly completed after its underlying packet has been -+ * transmitted. If set, the request transport system waits for a response -+ * of the request. -+ * -+ * @SSAM_CDEV_REQUEST_UNSEQUENCED: -+ * Specifies that the request should be transmitted via an unsequenced -+ * packet. If set, the request must not have a response, meaning that this -+ * flag and the %SSAM_CDEV_REQUEST_HAS_RESPONSE flag are mutually -+ * exclusive. -+ */ -+enum ssam_cdev_request_flags { -+ SSAM_CDEV_REQUEST_HAS_RESPONSE = 0x01, -+ SSAM_CDEV_REQUEST_UNSEQUENCED = 0x02, -+}; -+ -+/** -+ * struct ssam_cdev_request - Controller request IOCTL argument. -+ * @target_category: Target category of the SAM request. -+ * @target_id: Target ID of the SAM request. -+ * @command_id: Command ID of the SAM request. -+ * @instance_id: Instance ID of the SAM request. -+ * @flags: Request flags (see &enum ssam_cdev_request_flags). -+ * @status: Request status (output). -+ * @payload: Request payload (input data). -+ * @payload.data: Pointer to request payload data. -+ * @payload.length: Length of request payload data (in bytes). -+ * @response: Request response (output data). -+ * @response.data: Pointer to response buffer. -+ * @response.length: On input: Capacity of response buffer (in bytes). -+ * On output: Length of request response (number of bytes -+ * in the buffer that are actually used). -+ */ -+struct ssam_cdev_request { -+ __u8 target_category; -+ __u8 target_id; -+ __u8 command_id; -+ __u8 instance_id; -+ __u16 flags; -+ __s16 status; -+ -+ struct { -+ __u64 data; -+ __u16 length; -+ __u8 __pad[6]; -+ } payload; -+ -+ struct { -+ __u64 data; -+ __u16 length; -+ __u8 __pad[6]; -+ } response; -+} __attribute__((__packed__)); -+ -+/** -+ * struct ssam_cdev_notifier_desc - Notifier descriptor. -+ * @priority: Priority value determining the order in which notifier -+ * callbacks will be called. A higher value means higher -+ * priority, i.e. the associated callback will be executed -+ * earlier than other (lower priority) callbacks. -+ * @target_category: The event target category for which this notifier should -+ * receive events. -+ * -+ * Specifies the notifier that should be registered or unregistered, -+ * specifically with which priority and for which target category of events. -+ */ -+struct ssam_cdev_notifier_desc { -+ __s32 priority; -+ __u8 target_category; -+} __attribute__((__packed__)); -+ -+/** -+ * struct ssam_cdev_event_desc - Event descriptor. -+ * @reg: Registry via which the event will be enabled/disabled. -+ * @reg.target_category: Target category for the event registry requests. -+ * @reg.target_id: Target ID for the event registry requests. -+ * @reg.cid_enable: Command ID for the event-enable request. -+ * @reg.cid_disable: Command ID for the event-disable request. -+ * @id: ID specifying the event. -+ * @id.target_category: Target category of the event source. -+ * @id.instance: Instance ID of the event source. -+ * @flags: Flags used for enabling the event. -+ * -+ * Specifies which event should be enabled/disabled and how to do that. -+ */ -+struct ssam_cdev_event_desc { -+ struct { -+ __u8 target_category; -+ __u8 target_id; -+ __u8 cid_enable; -+ __u8 cid_disable; -+ } reg; -+ -+ struct { -+ __u8 target_category; -+ __u8 instance; -+ } id; -+ -+ __u8 flags; -+} __attribute__((__packed__)); -+ -+/** -+ * struct ssam_cdev_event - SSAM event sent by the EC. -+ * @target_category: Target category of the event source. See &enum ssam_ssh_tc. -+ * @target_id: Target ID of the event source. -+ * @command_id: Command ID of the event. -+ * @instance_id: Instance ID of the event source. -+ * @length: Length of the event payload in bytes. -+ * @data: Event payload data. -+ */ -+struct ssam_cdev_event { -+ __u8 target_category; -+ __u8 target_id; -+ __u8 command_id; -+ __u8 instance_id; -+ __u16 length; -+ __u8 data[]; -+} __attribute__((__packed__)); -+ -+#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) -+#define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc) -+#define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc) -+#define SSAM_CDEV_EVENT_ENABLE _IOW(0xA5, 4, struct ssam_cdev_event_desc) -+#define SSAM_CDEV_EVENT_DISABLE _IOW(0xA5, 5, struct ssam_cdev_event_desc) -+ -+#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ -diff --git a/include/uapi/linux/surface_aggregator/dtx.h b/include/uapi/linux/surface_aggregator/dtx.h -new file mode 100644 -index 000000000000..fc0ba6cbe3e8 ---- /dev/null -+++ b/include/uapi/linux/surface_aggregator/dtx.h -@@ -0,0 +1,146 @@ -+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ -+/* -+ * Surface DTX (clipboard detachment system driver) user-space interface. -+ * -+ * Definitions, structs, and IOCTLs for the /dev/surface/dtx misc device. This -+ * device allows user-space to control the clipboard detachment process on -+ * Surface Book series devices. -+ * -+ * Copyright (C) 2020 Maximilian Luz -+ */ -+ -+#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H -+#define _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H -+ -+#include -+#include -+ -+/* Status/error categories */ -+#define SDTX_CATEGORY_STATUS 0x0000 -+#define SDTX_CATEGORY_RUNTIME_ERROR 0x1000 -+#define SDTX_CATEGORY_HARDWARE_ERROR 0x2000 -+#define SDTX_CATEGORY_UNKNOWN 0xf000 -+ -+#define SDTX_CATEGORY_MASK 0xf000 -+#define SDTX_CATEGORY(value) ((value) & SDTX_CATEGORY_MASK) -+ -+#define SDTX_STATUS(code) ((code) | SDTX_CATEGORY_STATUS) -+#define SDTX_ERR_RT(code) ((code) | SDTX_CATEGORY_RUNTIME_ERROR) -+#define SDTX_ERR_HW(code) ((code) | SDTX_CATEGORY_HARDWARE_ERROR) -+#define SDTX_UNKNOWN(code) ((code) | SDTX_CATEGORY_UNKNOWN) -+ -+#define SDTX_SUCCESS(value) (SDTX_CATEGORY(value) == SDTX_CATEGORY_STATUS) -+ -+/* Latch status values */ -+#define SDTX_LATCH_CLOSED SDTX_STATUS(0x00) -+#define SDTX_LATCH_OPENED SDTX_STATUS(0x01) -+ -+/* Base state values */ -+#define SDTX_BASE_DETACHED SDTX_STATUS(0x00) -+#define SDTX_BASE_ATTACHED SDTX_STATUS(0x01) -+ -+/* Runtime errors (non-critical) */ -+#define SDTX_DETACH_NOT_FEASIBLE SDTX_ERR_RT(0x01) -+#define SDTX_DETACH_TIMEDOUT SDTX_ERR_RT(0x02) -+ -+/* Hardware errors (critical) */ -+#define SDTX_ERR_FAILED_TO_OPEN SDTX_ERR_HW(0x01) -+#define SDTX_ERR_FAILED_TO_REMAIN_OPEN SDTX_ERR_HW(0x02) -+#define SDTX_ERR_FAILED_TO_CLOSE SDTX_ERR_HW(0x03) -+ -+/* Base types */ -+#define SDTX_DEVICE_TYPE_HID 0x0100 -+#define SDTX_DEVICE_TYPE_SSH 0x0200 -+ -+#define SDTX_DEVICE_TYPE_MASK 0x0f00 -+#define SDTX_DEVICE_TYPE(value) ((value) & SDTX_DEVICE_TYPE_MASK) -+ -+#define SDTX_BASE_TYPE_HID(id) ((id) | SDTX_DEVICE_TYPE_HID) -+#define SDTX_BASE_TYPE_SSH(id) ((id) | SDTX_DEVICE_TYPE_SSH) -+ -+/** -+ * enum sdtx_device_mode - Mode describing how (and if) the clipboard is -+ * attached to the base of the device. -+ * @SDTX_DEVICE_MODE_TABLET: The clipboard is detached from the base and the -+ * device operates as tablet. -+ * @SDTX_DEVICE_MODE_LAPTOP: The clipboard is attached normally to the base -+ * and the device operates as laptop. -+ * @SDTX_DEVICE_MODE_STUDIO: The clipboard is attached to the base in reverse. -+ * The device operates as tablet with keyboard and -+ * touchpad deactivated, however, the base battery -+ * and, if present in the specific device model, dGPU -+ * are available to the system. -+ */ -+enum sdtx_device_mode { -+ SDTX_DEVICE_MODE_TABLET = 0x00, -+ SDTX_DEVICE_MODE_LAPTOP = 0x01, -+ SDTX_DEVICE_MODE_STUDIO = 0x02, -+}; -+ -+/** -+ * struct sdtx_event - Event provided by reading from the DTX device file. -+ * @length: Length of the event payload, in bytes. -+ * @code: Event code, detailing what type of event this is. -+ * @data: Payload of the event, containing @length bytes. -+ * -+ * See &enum sdtx_event_code for currently valid event codes. -+ */ -+struct sdtx_event { -+ __u16 length; -+ __u16 code; -+ __u8 data[]; -+} __attribute__((__packed__)); -+ -+/** -+ * enum sdtx_event_code - Code describing the type of an event. -+ * @SDTX_EVENT_REQUEST: Detachment request event type. -+ * @SDTX_EVENT_CANCEL: Cancel detachment process event type. -+ * @SDTX_EVENT_BASE_CONNECTION: Base/clipboard connection change event type. -+ * @SDTX_EVENT_LATCH_STATUS: Latch status change event type. -+ * @SDTX_EVENT_DEVICE_MODE: Device mode change event type. -+ * -+ * Used in @struct sdtx_event to describe the type of the event. Further event -+ * codes are reserved for future use. Any event parser should be able to -+ * gracefully handle unknown events, i.e. by simply skipping them. -+ * -+ * Consult the DTX user-space interface documentation for details regarding -+ * the individual event types. -+ */ -+enum sdtx_event_code { -+ SDTX_EVENT_REQUEST = 1, -+ SDTX_EVENT_CANCEL = 2, -+ SDTX_EVENT_BASE_CONNECTION = 3, -+ SDTX_EVENT_LATCH_STATUS = 4, -+ SDTX_EVENT_DEVICE_MODE = 5, -+}; -+ -+/** -+ * struct sdtx_base_info - Describes if and what type of base is connected. -+ * @state: The state of the connection. Valid values are %SDTX_BASE_DETACHED, -+ * %SDTX_BASE_ATTACHED, and %SDTX_DETACH_NOT_FEASIBLE (in case a base -+ * is attached but low clipboard battery prevents detachment). Other -+ * values are currently reserved. -+ * @base_id: The type of base connected. Zero if no base is connected. -+ */ -+struct sdtx_base_info { -+ __u16 state; -+ __u16 base_id; -+} __attribute__((__packed__)); -+ -+/* IOCTLs */ -+#define SDTX_IOCTL_EVENTS_ENABLE _IO(0xa5, 0x21) -+#define SDTX_IOCTL_EVENTS_DISABLE _IO(0xa5, 0x22) -+ -+#define SDTX_IOCTL_LATCH_LOCK _IO(0xa5, 0x23) -+#define SDTX_IOCTL_LATCH_UNLOCK _IO(0xa5, 0x24) -+ -+#define SDTX_IOCTL_LATCH_REQUEST _IO(0xa5, 0x25) -+#define SDTX_IOCTL_LATCH_CONFIRM _IO(0xa5, 0x26) -+#define SDTX_IOCTL_LATCH_HEARTBEAT _IO(0xa5, 0x27) -+#define SDTX_IOCTL_LATCH_CANCEL _IO(0xa5, 0x28) -+ -+#define SDTX_IOCTL_GET_BASE_INFO _IOR(0xa5, 0x29, struct sdtx_base_info) -+#define SDTX_IOCTL_GET_DEVICE_MODE _IOR(0xa5, 0x2a, __u16) -+#define SDTX_IOCTL_GET_LATCH_STATUS _IOR(0xa5, 0x2b, __u16) -+ -+#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H */ -diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c -index 13acbf55c6fd..6a319852083e 100644 ---- a/scripts/mod/devicetable-offsets.c -+++ b/scripts/mod/devicetable-offsets.c -@@ -227,8 +227,9 @@ int main(void) - - DEVID(ssam_device_id); - DEVID_FIELD(ssam_device_id, match_flags); -+ DEVID_FIELD(ssam_device_id, domain); - DEVID_FIELD(ssam_device_id, category); -- DEVID_FIELD(ssam_device_id, channel); -+ DEVID_FIELD(ssam_device_id, target); - DEVID_FIELD(ssam_device_id, instance); - DEVID_FIELD(ssam_device_id, function); - -diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c -index 76e3b1d7db45..f171616ab318 100644 ---- a/scripts/mod/file2alias.c -+++ b/scripts/mod/file2alias.c -@@ -1276,20 +1276,22 @@ static int do_typec_entry(const char *filename, void *symval, char *alias) - return 1; - } - --/* Looks like: ssam:cNtNiNfN -+/* -+ * Looks like: ssam:dNcNtNiNfN - * - * N is exactly 2 digits, where each is an upper-case hex digit. - */ - static int do_ssam_entry(const char *filename, void *symval, char *alias) - { - DEF_FIELD(symval, ssam_device_id, match_flags); -+ DEF_FIELD(symval, ssam_device_id, domain); - DEF_FIELD(symval, ssam_device_id, category); -- DEF_FIELD(symval, ssam_device_id, channel); -+ DEF_FIELD(symval, ssam_device_id, target); - DEF_FIELD(symval, ssam_device_id, instance); - DEF_FIELD(symval, ssam_device_id, function); - -- sprintf(alias, "ssam:c%02X", category); -- ADD(alias, "t", match_flags & SSAM_MATCH_CHANNEL, channel); -+ sprintf(alias, "ssam:d%02Xc%02X", domain, category); -+ ADD(alias, "t", match_flags & SSAM_MATCH_TARGET, target); - ADD(alias, "i", match_flags & SSAM_MATCH_INSTANCE, instance); - ADD(alias, "f", match_flags & SSAM_MATCH_FUNCTION, function); - --- -2.33.0 - diff --git a/patches/4.19/0011-surface-hotplug.patch b/patches/4.19/0011-surface-hotplug.patch deleted file mode 100644 index 3853604eb..000000000 --- a/patches/4.19/0011-surface-hotplug.patch +++ /dev/null @@ -1,4293 +0,0 @@ -From 66b504d8006984efb0bf1e5537366239dadec10c Mon Sep 17 00:00:00 2001 -From: Lukas Wunner -Date: Tue, 31 Jul 2018 07:50:37 +0200 -Subject: [PATCH] PCI: pciehp: Differentiate between surprise and safe removal - -When removing PCI devices below a hotplug bridge, pciehp marks them as -disconnected if the card is no longer present in the slot or it quiesces -them if the card is still present (by disabling INTx interrupts, bus -mastering and SERR# reporting). - -To detect whether the card is still present, pciehp checks the Presence -Detect State bit in the Slot Status register. The problem with this -approach is that even if the card is present, the link to it may be -down, and it that case it would be better to mark the devices as -disconnected instead of trying to quiesce them. Moreover, if the card -in the slot was quickly replaced by another one, the Presence Detect -State bit would be set, yet trying to quiesce the new card's devices -would be wrong and the correct thing to do is to mark the previous -card's devices as disconnected. - -Instead of looking at the Presence Detect State bit, it is better to -differentiate whether the card was surprise removed versus safely -removed (via sysfs or an Attention Button press). On surprise removal, -the devices should be marked as disconnected, whereas on safe removal it -is correct to quiesce the devices. - -The knowledge whether a surprise removal or a safe removal is at hand -does exist further up in the call stack: A surprise removal is -initiated by pciehp_handle_presence_or_link_change(), a safe removal by -pciehp_handle_disable_request(). - -Pass that information down to pciehp_unconfigure_device() and use it in -lieu of the Presence Detect State bit. While there, add kernel-doc to -pciehp_unconfigure_device() and pciehp_configure_device(). - -Tested-by: Alexandru Gagniuc -Signed-off-by: Lukas Wunner -Signed-off-by: Bjorn Helgaas -Cc: Keith Busch - -Patchset: surface-hotplug ---- - drivers/pci/hotplug/pciehp.h | 2 +- - drivers/pci/hotplug/pciehp_ctrl.c | 22 +++++++++++++--------- - drivers/pci/hotplug/pciehp_pci.c | 23 ++++++++++++++++++++--- - 3 files changed, 34 insertions(+), 13 deletions(-) - -diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h -index ef6071807072..13379bc6a466 100644 ---- a/drivers/pci/hotplug/pciehp.h -+++ b/drivers/pci/hotplug/pciehp.h -@@ -183,7 +183,7 @@ void pciehp_handle_button_press(struct slot *slot); - void pciehp_handle_disable_request(struct slot *slot); - void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events); - int pciehp_configure_device(struct slot *p_slot); --void pciehp_unconfigure_device(struct slot *p_slot); -+void pciehp_unconfigure_device(struct slot *p_slot, bool presence); - void pciehp_queue_pushbutton_work(struct work_struct *work); - struct controller *pcie_init(struct pcie_device *dev); - int pcie_init_notification(struct controller *ctrl); -diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c -index c71964e29b01..60a925066b0a 100644 ---- a/drivers/pci/hotplug/pciehp_ctrl.c -+++ b/drivers/pci/hotplug/pciehp_ctrl.c -@@ -26,6 +26,9 @@ - hotplug controller logic - */ - -+#define SAFE_REMOVAL true -+#define SURPRISE_REMOVAL false -+ - static void set_slot_off(struct controller *ctrl, struct slot *pslot) - { - /* turn off slot, turn on Amber LED, turn off Green LED if supported*/ -@@ -101,12 +104,13 @@ static int board_added(struct slot *p_slot) - /** - * remove_board - Turns off slot and LEDs - * @p_slot: slot where board is being removed -+ * @safe_removal: whether the board is safely removed (versus surprise removed) - */ --static void remove_board(struct slot *p_slot) -+static void remove_board(struct slot *p_slot, bool safe_removal) - { - struct controller *ctrl = p_slot->ctrl; - -- pciehp_unconfigure_device(p_slot); -+ pciehp_unconfigure_device(p_slot, safe_removal); - - if (POWER_CTRL(ctrl)) { - pciehp_power_off_slot(p_slot); -@@ -128,7 +132,7 @@ static void remove_board(struct slot *p_slot) - } - - static int pciehp_enable_slot(struct slot *slot); --static int pciehp_disable_slot(struct slot *slot); -+static int pciehp_disable_slot(struct slot *slot, bool safe_removal); - - void pciehp_request(struct controller *ctrl, int action) - { -@@ -220,7 +224,7 @@ void pciehp_handle_disable_request(struct slot *slot) - slot->state = POWEROFF_STATE; - mutex_unlock(&slot->lock); - -- ctrl->request_result = pciehp_disable_slot(slot); -+ ctrl->request_result = pciehp_disable_slot(slot, SAFE_REMOVAL); - } - - void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) -@@ -247,7 +251,7 @@ void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) - if (events & PCI_EXP_SLTSTA_PDC) - ctrl_info(ctrl, "Slot(%s): Card not present\n", - slot_name(slot)); -- pciehp_disable_slot(slot); -+ pciehp_disable_slot(slot, SURPRISE_REMOVAL); - break; - default: - mutex_unlock(&slot->lock); -@@ -333,7 +337,7 @@ static int pciehp_enable_slot(struct slot *slot) - return ret; - } - --static int __pciehp_disable_slot(struct slot *p_slot) -+static int __pciehp_disable_slot(struct slot *p_slot, bool safe_removal) - { - u8 getstatus = 0; - struct controller *ctrl = p_slot->ctrl; -@@ -347,17 +351,17 @@ static int __pciehp_disable_slot(struct slot *p_slot) - } - } - -- remove_board(p_slot); -+ remove_board(p_slot, safe_removal); - return 0; - } - --static int pciehp_disable_slot(struct slot *slot) -+static int pciehp_disable_slot(struct slot *slot, bool safe_removal) - { - struct controller *ctrl = slot->ctrl; - int ret; - - pm_runtime_get_sync(&ctrl->pcie->port->dev); -- ret = __pciehp_disable_slot(slot); -+ ret = __pciehp_disable_slot(slot, safe_removal); - pm_runtime_put(&ctrl->pcie->port->dev); - - mutex_lock(&slot->lock); -diff --git a/drivers/pci/hotplug/pciehp_pci.c b/drivers/pci/hotplug/pciehp_pci.c -index 5c58c22e0c08..18f83e554c73 100644 ---- a/drivers/pci/hotplug/pciehp_pci.c -+++ b/drivers/pci/hotplug/pciehp_pci.c -@@ -20,6 +20,14 @@ - #include "../pci.h" - #include "pciehp.h" - -+/** -+ * pciehp_configure_device() - enumerate PCI devices below a hotplug bridge -+ * @p_slot: PCIe hotplug slot -+ * -+ * Enumerate PCI devices below a hotplug bridge and add them to the system. -+ * Return 0 on success, %-EEXIST if the devices are already enumerated or -+ * %-ENODEV if enumeration failed. -+ */ - int pciehp_configure_device(struct slot *p_slot) - { - struct pci_dev *dev; -@@ -62,9 +70,19 @@ int pciehp_configure_device(struct slot *p_slot) - return ret; - } - --void pciehp_unconfigure_device(struct slot *p_slot) -+/** -+ * pciehp_unconfigure_device() - remove PCI devices below a hotplug bridge -+ * @p_slot: PCIe hotplug slot -+ * @presence: whether the card is still present in the slot; -+ * true for safe removal via sysfs or an Attention Button press, -+ * false for surprise removal -+ * -+ * Unbind PCI devices below a hotplug bridge from their drivers and remove -+ * them from the system. Safely removed devices are quiesced. Surprise -+ * removed devices are marked as such to prevent further accesses. -+ */ -+void pciehp_unconfigure_device(struct slot *p_slot, bool presence) - { -- u8 presence = 0; - struct pci_dev *dev, *temp; - struct pci_bus *parent = p_slot->ctrl->pcie->port->subordinate; - u16 command; -@@ -72,7 +90,6 @@ void pciehp_unconfigure_device(struct slot *p_slot) - - ctrl_dbg(ctrl, "%s: domain:bus:dev = %04x:%02x:00\n", - __func__, pci_domain_nr(parent), parent->number); -- pciehp_get_adapter_status(p_slot, &presence); - - pci_lock_rescan_remove(); - --- -2.33.0 - -From 3b713ee79a44cf32d640b89a815a87c66ad12b64 Mon Sep 17 00:00:00 2001 -From: Lukas Wunner -Date: Sun, 19 Aug 2018 16:29:00 +0200 -Subject: [PATCH] PCI: pciehp: Drop unnecessary includes - -Drop the following includes from pciehp source files which no longer use -any of the included symbols: - -* in pciehp.h - in pciehp_hpc.c - Added by commit de25968cc87c ("fix more missing includes") to - accommodate for a call to signal_pending(). - The call was removed by commit 262303fe329a ("pciehp: fix wait command - completion"). - -* in pciehp_core.c - Added by historic commit f308a2dfbe63 ("PCI: add PCI Express Port Bus - Driver subsystem") to accommodate for a call to free_irq(): - https://git.kernel.org/tglx/history/c/f308a2dfbe63 - The call was removed by commit 407f452b05f9 ("pciehp: remove - unnecessary free_irq"). - -* in pciehp_core.c and pciehp_hpc.c - Added by commit 34d03419f03b ("PCIEHP: Add Electro Mechanical - Interlock (EMI) support to the PCIE hotplug driver."), - which was reverted by commit bd3d99c17039 ("PCI: Remove untested - Electromechanical Interlock (EMI) support in pciehp."). - -* in pciehp_ctrl.c, pciehp_hpc.c and pciehp_pci.c - Added by historic commit c16b4b14d980 ("PCI Hotplug: Add SHPC and PCI - Express hot-plug drivers"): - https://git.kernel.org/tglx/history/c/c16b4b14d980 - Module-related symbols were neither used back then in those files, - nor are they used today. - -* in pciehp_ctrl.c - Added by commit 5a0e3ad6af86 ("include cleanup: Update gfp.h and - slab.h includes to prepare for breaking implicit slab.h inclusion from - percpu.h") to accommodate for calls to kmalloc(). - The calls were removed by commit 0e94916e6091 ("PCI: pciehp: Handle - events synchronously"). - -* "../pci.h" in pciehp_ctrl.c - Added by historic commit 67f4660b72f2 ("PCI: ASPM patch for") to - accommodate for usage of the global variable pcie_mch_quirk: - https://git.kernel.org/tglx/history/c/67f4660b72f2 - The global variable was removed by commit 0ba379ec0fb1 ("PCI: Simplify - hotplug mch quirk"). - -Signed-off-by: Lukas Wunner -Signed-off-by: Bjorn Helgaas - -Patchset: surface-hotplug ---- - drivers/pci/hotplug/pciehp.h | 1 - - drivers/pci/hotplug/pciehp_core.c | 2 -- - drivers/pci/hotplug/pciehp_ctrl.c | 3 --- - drivers/pci/hotplug/pciehp_hpc.c | 3 --- - drivers/pci/hotplug/pciehp_pci.c | 1 - - 5 files changed, 10 deletions(-) - -diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h -index 13379bc6a466..48d138c59f5d 100644 ---- a/drivers/pci/hotplug/pciehp.h -+++ b/drivers/pci/hotplug/pciehp.h -@@ -19,7 +19,6 @@ - #include - #include - #include --#include /* signal_pending() */ - #include - #include - #include -diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c -index 518c46f8e63b..8e5b0bdb16b6 100644 ---- a/drivers/pci/hotplug/pciehp_core.c -+++ b/drivers/pci/hotplug/pciehp_core.c -@@ -23,8 +23,6 @@ - #include - #include - #include "pciehp.h" --#include --#include - - #include "../pci.h" - -diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c -index 60a925066b0a..10e46e3cdcea 100644 ---- a/drivers/pci/hotplug/pciehp_ctrl.c -+++ b/drivers/pci/hotplug/pciehp_ctrl.c -@@ -13,13 +13,10 @@ - * - */ - --#include - #include - #include --#include - #include - #include --#include "../pci.h" - #include "pciehp.h" - - /* The following routines constitute the bulk of the -diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c -index 005817e40ad3..d4b7049cbc70 100644 ---- a/drivers/pci/hotplug/pciehp_hpc.c -+++ b/drivers/pci/hotplug/pciehp_hpc.c -@@ -13,15 +13,12 @@ - */ - - #include --#include - #include --#include - #include - #include - #include - #include - #include --#include - #include - - #include "../pci.h" -diff --git a/drivers/pci/hotplug/pciehp_pci.c b/drivers/pci/hotplug/pciehp_pci.c -index 18f83e554c73..c512b2ed85ed 100644 ---- a/drivers/pci/hotplug/pciehp_pci.c -+++ b/drivers/pci/hotplug/pciehp_pci.c -@@ -13,7 +13,6 @@ - * - */ - --#include - #include - #include - #include --- -2.33.0 - -From 750b78bfc3145422f62dae68f35834c59aa553fe Mon Sep 17 00:00:00 2001 -From: Lukas Wunner -Date: Sun, 19 Aug 2018 16:29:00 +0200 -Subject: [PATCH] PCI: pciehp: Drop hotplug_slot_ops wrappers - -pciehp's ->enable_slot, ->disable_slot, ->get_attention_status and -->reset_slot callbacks are currently implemented by wrapper functions -that do nothing else but call down to a backend function. The backends -are not called from anywhere else, so drop the wrappers and use the -backends directly as callbacks, thereby shaving off a few lines of -unnecessary code. - -No functional change intended. - -Signed-off-by: Lukas Wunner -Signed-off-by: Bjorn Helgaas - -Patchset: surface-hotplug ---- - drivers/pci/hotplug/pciehp.h | 8 +++--- - drivers/pci/hotplug/pciehp_core.c | 43 +++---------------------------- - drivers/pci/hotplug/pciehp_ctrl.c | 6 +++-- - drivers/pci/hotplug/pciehp_hpc.c | 8 ++++-- - 4 files changed, 18 insertions(+), 47 deletions(-) - -diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h -index 48d138c59f5d..79feeb0a6716 100644 ---- a/drivers/pci/hotplug/pciehp.h -+++ b/drivers/pci/hotplug/pciehp.h -@@ -175,8 +175,6 @@ struct controller { - #define NO_CMD_CMPL(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_NCCS) - #define PSN(ctrl) (((ctrl)->slot_cap & PCI_EXP_SLTCAP_PSN) >> 19) - --int pciehp_sysfs_enable_slot(struct slot *slot); --int pciehp_sysfs_disable_slot(struct slot *slot); - void pciehp_request(struct controller *ctrl, int action); - void pciehp_handle_button_press(struct slot *slot); - void pciehp_handle_disable_request(struct slot *slot); -@@ -191,7 +189,6 @@ void pcie_clear_hotplug_events(struct controller *ctrl); - int pciehp_power_on_slot(struct slot *slot); - void pciehp_power_off_slot(struct slot *slot); - void pciehp_get_power_status(struct slot *slot, u8 *status); --void pciehp_get_attention_status(struct slot *slot, u8 *status); - - void pciehp_set_attention_status(struct slot *slot, u8 status); - void pciehp_get_latch_status(struct slot *slot, u8 *status); -@@ -203,8 +200,11 @@ void pciehp_green_led_blink(struct slot *slot); - int pciehp_check_link_status(struct controller *ctrl); - bool pciehp_check_link_active(struct controller *ctrl); - void pciehp_release_ctrl(struct controller *ctrl); --int pciehp_reset_slot(struct slot *slot, int probe); - -+int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot); -+int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot); -+int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, int probe); -+int pciehp_get_attention_status(struct hotplug_slot *hotplug_slot, u8 *status); - int pciehp_set_raw_indicator_status(struct hotplug_slot *h_slot, u8 status); - int pciehp_get_raw_indicator_status(struct hotplug_slot *h_slot, u8 *status); - -diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c -index 8e5b0bdb16b6..c796c4643bbc 100644 ---- a/drivers/pci/hotplug/pciehp_core.c -+++ b/drivers/pci/hotplug/pciehp_core.c -@@ -45,13 +45,9 @@ MODULE_PARM_DESC(pciehp_poll_time, "Polling mechanism frequency, in seconds"); - #define PCIE_MODULE_NAME "pciehp" - - static int set_attention_status(struct hotplug_slot *slot, u8 value); --static int enable_slot(struct hotplug_slot *slot); --static int disable_slot(struct hotplug_slot *slot); - static int get_power_status(struct hotplug_slot *slot, u8 *value); --static int get_attention_status(struct hotplug_slot *slot, u8 *value); - static int get_latch_status(struct hotplug_slot *slot, u8 *value); - static int get_adapter_status(struct hotplug_slot *slot, u8 *value); --static int reset_slot(struct hotplug_slot *slot, int probe); - - static int init_slot(struct controller *ctrl) - { -@@ -75,15 +71,15 @@ static int init_slot(struct controller *ctrl) - if (!ops) - goto out; - -- ops->enable_slot = enable_slot; -- ops->disable_slot = disable_slot; -+ ops->enable_slot = pciehp_sysfs_enable_slot; -+ ops->disable_slot = pciehp_sysfs_disable_slot; - ops->get_power_status = get_power_status; - ops->get_adapter_status = get_adapter_status; -- ops->reset_slot = reset_slot; -+ ops->reset_slot = pciehp_reset_slot; - if (MRL_SENS(ctrl)) - ops->get_latch_status = get_latch_status; - if (ATTN_LED(ctrl)) { -- ops->get_attention_status = get_attention_status; -+ ops->get_attention_status = pciehp_get_attention_status; - ops->set_attention_status = set_attention_status; - } else if (ctrl->pcie->port->hotplug_user_indicators) { - ops->get_attention_status = pciehp_get_raw_indicator_status; -@@ -134,22 +130,6 @@ static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) - return 0; - } - -- --static int enable_slot(struct hotplug_slot *hotplug_slot) --{ -- struct slot *slot = hotplug_slot->private; -- -- return pciehp_sysfs_enable_slot(slot); --} -- -- --static int disable_slot(struct hotplug_slot *hotplug_slot) --{ -- struct slot *slot = hotplug_slot->private; -- -- return pciehp_sysfs_disable_slot(slot); --} -- - static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) - { - struct slot *slot = hotplug_slot->private; -@@ -161,14 +141,6 @@ static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) - return 0; - } - --static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value) --{ -- struct slot *slot = hotplug_slot->private; -- -- pciehp_get_attention_status(slot, value); -- return 0; --} -- - static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) - { - struct slot *slot = hotplug_slot->private; -@@ -191,13 +163,6 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) - return 0; - } - --static int reset_slot(struct hotplug_slot *hotplug_slot, int probe) --{ -- struct slot *slot = hotplug_slot->private; -- -- return pciehp_reset_slot(slot, probe); --} -- - /** - * pciehp_check_presence() - synthesize event if presence has changed - * -diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c -index 10e46e3cdcea..b101f179147e 100644 ---- a/drivers/pci/hotplug/pciehp_ctrl.c -+++ b/drivers/pci/hotplug/pciehp_ctrl.c -@@ -368,8 +368,9 @@ static int pciehp_disable_slot(struct slot *slot, bool safe_removal) - return ret; - } - --int pciehp_sysfs_enable_slot(struct slot *p_slot) -+int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot) - { -+ struct slot *p_slot = hotplug_slot->private; - struct controller *ctrl = p_slot->ctrl; - - mutex_lock(&p_slot->lock); -@@ -407,8 +408,9 @@ int pciehp_sysfs_enable_slot(struct slot *p_slot) - return -ENODEV; - } - --int pciehp_sysfs_disable_slot(struct slot *p_slot) -+int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot) - { -+ struct slot *p_slot = hotplug_slot->private; - struct controller *ctrl = p_slot->ctrl; - - mutex_lock(&p_slot->lock); -diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c -index d4b7049cbc70..576362d0b1cd 100644 ---- a/drivers/pci/hotplug/pciehp_hpc.c -+++ b/drivers/pci/hotplug/pciehp_hpc.c -@@ -326,8 +326,9 @@ int pciehp_get_raw_indicator_status(struct hotplug_slot *hotplug_slot, - return 0; - } - --void pciehp_get_attention_status(struct slot *slot, u8 *status) -+int pciehp_get_attention_status(struct hotplug_slot *hotplug_slot, u8 *status) - { -+ struct slot *slot = hotplug_slot->private; - struct controller *ctrl = slot->ctrl; - struct pci_dev *pdev = ctrl_dev(ctrl); - u16 slot_ctrl; -@@ -352,6 +353,8 @@ void pciehp_get_attention_status(struct slot *slot, u8 *status) - *status = 0xFF; - break; - } -+ -+ return 0; - } - - void pciehp_get_power_status(struct slot *slot, u8 *status) -@@ -769,8 +772,9 @@ void pcie_clear_hotplug_events(struct controller *ctrl) - * momentarily, if we see that they could interfere. Also, clear any spurious - * events after. - */ --int pciehp_reset_slot(struct slot *slot, int probe) -+int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, int probe) - { -+ struct slot *slot = hotplug_slot->private; - struct controller *ctrl = slot->ctrl; - struct pci_dev *pdev = ctrl_dev(ctrl); - u16 stat_mask = 0, ctrl_mask = 0; --- -2.33.0 - -From 01519bdc9263ba3ee3c2ddf2866e07f39cd345e3 Mon Sep 17 00:00:00 2001 -From: Lukas Wunner -Date: Sat, 8 Sep 2018 09:59:01 +0200 -Subject: [PATCH] PCI: pciehp: Tolerate Presence Detect hardwired to zero - -The WiGig Bus Extension (WBE) specification allows tunneling PCIe over -IEEE 802.11. A product implementing this spec is the wil6210 from -Wilocity (now part of Qualcomm Atheros). It integrates a PCIe switch -with a wireless network adapter: - - 00.0-+ [1ae9:0101] Upstream Port - +-00.0-+ [1ae9:0200] Downstream Port - | +-00.0 [168c:0034] Atheros AR9462 Wireless Network Adapter - +-02.0 [1ae9:0201] Downstream Port - +-03.0 [1ae9:0201] Downstream Port - -Wirelessly attached devices presumably appear below the hotplug ports -with device ID [1ae9:0201]. Oddly, the Downstream Port [1ae9:0200] -leading to the wireless network adapter is likewise Hotplug Capable, -but has its Presence Detect State bit hardwired to zero. Even if the -Link Active bit is set, Presence Detect is zero, so this cannot be -caused by in-band presence detection but only by broken hardware. - -pciehp assumes an empty slot if Presence Detect State is zero, -regardless of Link Active being one. Consequently, up until v4.18 it -removes the wireless network adapter in pciehp_resume(). From v4.19 it -already does so in pciehp_probe(). - -Be lenient towards broken hardware and assume the slot is occupied if -Link Active is set: Introduce pciehp_card_present_or_link_active() -and use it in lieu of pciehp_get_adapter_status() everywhere, except -in pciehp_handle_presence_or_link_change() whose log messages depend -on which of Presence Detect State or Link Active is set. - -Remove the Presence Detect State check from __pciehp_enable_slot() -because it is only called if either of Presence Detect State or Link -Active is set. - -Caution: There is a possibility that broken hardware exists which has -working Presence Detect but hardwires Link Active to one. On such -hardware the slot will now incorrectly be considered always occupied. -If such hardware is discovered, this commit can be rolled back and a -quirk can be added which sets is_hotplug_bridge = 0 for [1ae9:0200]. - -Link: https://bugzilla.kernel.org/show_bug.cgi?id=200839 -Reported-and-tested-by: David Yang -Signed-off-by: Lukas Wunner -Signed-off-by: Bjorn Helgaas -Cc: Rajat Jain -Cc: Ashok Raj - -Patchset: surface-hotplug ---- - drivers/pci/hotplug/pciehp.h | 3 ++- - drivers/pci/hotplug/pciehp_core.c | 6 +++--- - drivers/pci/hotplug/pciehp_ctrl.c | 10 ++-------- - drivers/pci/hotplug/pciehp_hpc.c | 25 +++++++++++++++++++------ - 4 files changed, 26 insertions(+), 18 deletions(-) - -diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h -index 79feeb0a6716..d1fa2ce71ad9 100644 ---- a/drivers/pci/hotplug/pciehp.h -+++ b/drivers/pci/hotplug/pciehp.h -@@ -192,11 +192,12 @@ void pciehp_get_power_status(struct slot *slot, u8 *status); - - void pciehp_set_attention_status(struct slot *slot, u8 status); - void pciehp_get_latch_status(struct slot *slot, u8 *status); --void pciehp_get_adapter_status(struct slot *slot, u8 *status); - int pciehp_query_power_fault(struct slot *slot); - void pciehp_green_led_on(struct slot *slot); - void pciehp_green_led_off(struct slot *slot); - void pciehp_green_led_blink(struct slot *slot); -+bool pciehp_card_present(struct controller *ctrl); -+bool pciehp_card_present_or_link_active(struct controller *ctrl); - int pciehp_check_link_status(struct controller *ctrl); - bool pciehp_check_link_active(struct controller *ctrl); - void pciehp_release_ctrl(struct controller *ctrl); -diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c -index c796c4643bbc..c052efe421f3 100644 ---- a/drivers/pci/hotplug/pciehp_core.c -+++ b/drivers/pci/hotplug/pciehp_core.c -@@ -158,7 +158,7 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) - struct pci_dev *pdev = slot->ctrl->pcie->port; - - pci_config_pm_runtime_get(pdev); -- pciehp_get_adapter_status(slot, value); -+ *value = pciehp_card_present_or_link_active(slot->ctrl); - pci_config_pm_runtime_put(pdev); - return 0; - } -@@ -176,12 +176,12 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) - static void pciehp_check_presence(struct controller *ctrl) - { - struct slot *slot = ctrl->slot; -- u8 occupied; -+ bool occupied; - - down_read(&ctrl->reset_lock); - mutex_lock(&slot->lock); - -- pciehp_get_adapter_status(slot, &occupied); -+ occupied = pciehp_card_present_or_link_active(ctrl); - if ((occupied && (slot->state == OFF_STATE || - slot->state == BLINKINGON_STATE)) || - (!occupied && (slot->state == ON_STATE || -diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c -index b101f179147e..97fa865717ec 100644 ---- a/drivers/pci/hotplug/pciehp_ctrl.c -+++ b/drivers/pci/hotplug/pciehp_ctrl.c -@@ -227,8 +227,7 @@ void pciehp_handle_disable_request(struct slot *slot) - void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) - { - struct controller *ctrl = slot->ctrl; -- bool link_active; -- u8 present; -+ bool present, link_active; - - /* - * If the slot is on and presence or link has changed, turn it off. -@@ -257,7 +256,7 @@ void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) - - /* Turn the slot on if it's occupied or link is up */ - mutex_lock(&slot->lock); -- pciehp_get_adapter_status(slot, &present); -+ present = pciehp_card_present(ctrl); - link_active = pciehp_check_link_active(ctrl); - if (!present && !link_active) { - mutex_unlock(&slot->lock); -@@ -290,11 +289,6 @@ static int __pciehp_enable_slot(struct slot *p_slot) - u8 getstatus = 0; - struct controller *ctrl = p_slot->ctrl; - -- pciehp_get_adapter_status(p_slot, &getstatus); -- if (!getstatus) { -- ctrl_info(ctrl, "Slot(%s): No adapter\n", slot_name(p_slot)); -- return -ENODEV; -- } - if (MRL_SENS(p_slot->ctrl)) { - pciehp_get_latch_status(p_slot, &getstatus); - if (getstatus) { -diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c -index 576362d0b1cd..7f4173d6771a 100644 ---- a/drivers/pci/hotplug/pciehp_hpc.c -+++ b/drivers/pci/hotplug/pciehp_hpc.c -@@ -389,13 +389,27 @@ void pciehp_get_latch_status(struct slot *slot, u8 *status) - *status = !!(slot_status & PCI_EXP_SLTSTA_MRLSS); - } - --void pciehp_get_adapter_status(struct slot *slot, u8 *status) -+bool pciehp_card_present(struct controller *ctrl) - { -- struct pci_dev *pdev = ctrl_dev(slot->ctrl); -+ struct pci_dev *pdev = ctrl_dev(ctrl); - u16 slot_status; - - pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); -- *status = !!(slot_status & PCI_EXP_SLTSTA_PDS); -+ return slot_status & PCI_EXP_SLTSTA_PDS; -+} -+ -+/** -+ * pciehp_card_present_or_link_active() - whether given slot is occupied -+ * @ctrl: PCIe hotplug controller -+ * -+ * Unlike pciehp_card_present(), which determines presence solely from the -+ * Presence Detect State bit, this helper also returns true if the Link Active -+ * bit is set. This is a concession to broken hotplug ports which hardwire -+ * Presence Detect State to zero, such as Wilocity's [1ae9:0200]. -+ */ -+bool pciehp_card_present_or_link_active(struct controller *ctrl) -+{ -+ return pciehp_card_present(ctrl) || pciehp_check_link_active(ctrl); - } - - int pciehp_query_power_fault(struct slot *slot) -@@ -874,7 +888,7 @@ struct controller *pcie_init(struct pcie_device *dev) - { - struct controller *ctrl; - u32 slot_cap, link_cap; -- u8 occupied, poweron; -+ u8 poweron; - struct pci_dev *pdev = dev->port; - - ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); -@@ -934,9 +948,8 @@ struct controller *pcie_init(struct pcie_device *dev) - * requested yet, so avoid triggering a notification with this command. - */ - if (POWER_CTRL(ctrl)) { -- pciehp_get_adapter_status(ctrl->slot, &occupied); - pciehp_get_power_status(ctrl->slot, &poweron); -- if (!occupied && poweron) { -+ if (!pciehp_card_present_or_link_active(ctrl) && poweron) { - pcie_disable_notification(ctrl); - pciehp_power_off_slot(ctrl->slot); - } --- -2.33.0 - -From 50a34b9ff91d80b612c35fad1f8dd50b845c7c7e Mon Sep 17 00:00:00 2001 -From: Patrick Talbert -Date: Wed, 5 Sep 2018 09:12:53 +0200 -Subject: [PATCH] PCI/ASPM: Do not initialize link state when aspm_disabled is - set - -Now that ASPM is configured for *all* PCIe devices at boot, a problem is -seen with systems that set the FADT NO_ASPM bit. This bit indicates that -the OS should not alter the ASPM state, but when -pcie_aspm_init_link_state() runs it only checks for !aspm_support_enabled. -This misses the ACPI_FADT_NO_ASPM case because that is setting -aspm_disabled. - -The result is systems may hang at boot after 1302fcf; avoidable if they -boot with pcie_aspm=off (sets !aspm_support_enabled). - -Fix this by having aspm_init_link_state() check for either -!aspm_support_enabled or acpm_disabled. - -Link: https://bugzilla.kernel.org/show_bug.cgi?id=201001 -Fixes: 1302fcf0d03e ("PCI: Configure *all* devices, not just hot-added ones") -Signed-off-by: Patrick Talbert -Signed-off-by: Bjorn Helgaas - -Patchset: surface-hotplug ---- - drivers/pci/pcie/aspm.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c -index cb474338f39d..18aa830e79e4 100644 ---- a/drivers/pci/pcie/aspm.c -+++ b/drivers/pci/pcie/aspm.c -@@ -907,7 +907,7 @@ void pcie_aspm_init_link_state(struct pci_dev *pdev) - struct pcie_link_state *link; - int blacklist = !!pcie_aspm_sanity_check(pdev); - -- if (!aspm_support_enabled) -+ if (!aspm_support_enabled || aspm_disabled) - return; - - if (pdev->link_state) --- -2.33.0 - -From 8e7c633b192cda8a5d12be0edc6c5083e49b57d6 Mon Sep 17 00:00:00 2001 -From: Lukas Wunner -Date: Sun, 19 Aug 2018 16:29:00 +0200 -Subject: [PATCH] PCI: Simplify disconnected marking - -Commit 89ee9f768003 ("PCI: Add device disconnected state") iterates over -the devices on a parent bus, marks each as disconnected, then marks -each device's children as disconnected using pci_walk_bus(). - -The same can be achieved more succinctly by calling pci_walk_bus() on -the parent bus. Moreover, this does not need to wait until acquiring -pci_lock_rescan_remove(), so move it out of that critical section. - -The critical section in err.c contains a pci_dev_get() / pci_dev_put() -pair which was apparently copy-pasted from pciehp_pci.c. In the latter -it serves the purpose of holding the struct pci_dev in place until the -Command register is updated. err.c doesn't do anything like that, hence -the pair is unnecessary. Remove it. - -Signed-off-by: Lukas Wunner -Signed-off-by: Bjorn Helgaas -Cc: Keith Busch -Cc: Oza Pawandeep -Cc: Sinan Kaya -Cc: Benjamin Herrenschmidt - -Patchset: surface-hotplug ---- - drivers/pci/hotplug/pciehp_pci.c | 9 +++------ - drivers/pci/pcie/err.c | 8 ++------ - 2 files changed, 5 insertions(+), 12 deletions(-) - -diff --git a/drivers/pci/hotplug/pciehp_pci.c b/drivers/pci/hotplug/pciehp_pci.c -index c512b2ed85ed..8da87931bd45 100644 ---- a/drivers/pci/hotplug/pciehp_pci.c -+++ b/drivers/pci/hotplug/pciehp_pci.c -@@ -90,6 +90,9 @@ void pciehp_unconfigure_device(struct slot *p_slot, bool presence) - ctrl_dbg(ctrl, "%s: domain:bus:dev = %04x:%02x:00\n", - __func__, pci_domain_nr(parent), parent->number); - -+ if (!presence) -+ pci_walk_bus(parent, pci_dev_set_disconnected, NULL); -+ - pci_lock_rescan_remove(); - - /* -@@ -101,12 +104,6 @@ void pciehp_unconfigure_device(struct slot *p_slot, bool presence) - list_for_each_entry_safe_reverse(dev, temp, &parent->devices, - bus_list) { - pci_dev_get(dev); -- if (!presence) { -- pci_dev_set_disconnected(dev, NULL); -- if (pci_has_subordinate(dev)) -- pci_walk_bus(dev->subordinate, -- pci_dev_set_disconnected, NULL); -- } - pci_stop_and_remove_bus_device(dev); - /* - * Ensure that no new Requests will be generated from -diff --git a/drivers/pci/pcie/err.c b/drivers/pci/pcie/err.c -index 2c3b5bd59b18..dee5a7507403 100644 ---- a/drivers/pci/pcie/err.c -+++ b/drivers/pci/pcie/err.c -@@ -239,17 +239,13 @@ void pcie_do_fatal_recovery(struct pci_dev *dev, u32 service) - udev = dev->bus->self; - - parent = udev->subordinate; -+ pci_walk_bus(parent, pci_dev_set_disconnected, NULL); -+ - pci_lock_rescan_remove(); - pci_dev_get(dev); - list_for_each_entry_safe_reverse(pdev, temp, &parent->devices, - bus_list) { -- pci_dev_get(pdev); -- pci_dev_set_disconnected(pdev, NULL); -- if (pci_has_subordinate(pdev)) -- pci_walk_bus(pdev->subordinate, -- pci_dev_set_disconnected, NULL); - pci_stop_and_remove_bus_device(pdev); -- pci_dev_put(pdev); - } - - result = reset_link(udev, service); --- -2.33.0 - -From 4a7b80f9dcd4d105ff29f98071e19107d9bb2cec Mon Sep 17 00:00:00 2001 -From: Lukas Wunner -Date: Tue, 18 Sep 2018 21:46:17 +0200 -Subject: [PATCH] PCI: pciehp: Unify controller and slot structs - -pciehp was originally introduced together with shpchp in a single -commit, c16b4b14d980 ("PCI Hotplug: Add SHPC and PCI Express hot-plug -drivers"): -https://git.kernel.org/tglx/history/c/c16b4b14d980 - -shpchp supports up to 31 slots per controller, hence uses separate slot -and controller structs. pciehp has a 1:1 relationship between slot and -controller and therefore never required this separation. Nevertheless, -because much of the code had been copy-pasted between the two drivers, -pciehp likewise uses separate structs to this very day. - -The artificial separation of data structures adds unnecessary complexity -and bloat to pciehp and requires constantly chasing pointers at runtime. - -Simplify the driver by merging struct slot into struct controller. -Merge the slot constructor pcie_init_slot() and the destructor -pcie_cleanup_slot() into the controller counterparts. - -No functional change intended. - -Signed-off-by: Lukas Wunner -Signed-off-by: Bjorn Helgaas - -Patchset: surface-hotplug ---- - drivers/pci/hotplug/pciehp.h | 67 ++++---- - drivers/pci/hotplug/pciehp_core.c | 53 +++---- - drivers/pci/hotplug/pciehp_ctrl.c | 244 ++++++++++++++---------------- - drivers/pci/hotplug/pciehp_hpc.c | 114 +++++--------- - drivers/pci/hotplug/pciehp_pci.c | 14 +- - 5 files changed, 210 insertions(+), 282 deletions(-) - -diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h -index d1fa2ce71ad9..bf1c9d3a64a5 100644 ---- a/drivers/pci/hotplug/pciehp.h -+++ b/drivers/pci/hotplug/pciehp.h -@@ -58,24 +58,6 @@ do { \ - - #define SLOT_NAME_SIZE 10 - --/** -- * struct slot - PCIe hotplug slot -- * @state: current state machine position -- * @ctrl: pointer to the slot's controller structure -- * @hotplug_slot: pointer to the structure registered with the PCI hotplug core -- * @work: work item to turn the slot on or off after 5 seconds in response to -- * an Attention Button press -- * @lock: protects reads and writes of @state; -- * protects scheduling, execution and cancellation of @work -- */ --struct slot { -- u8 state; -- struct controller *ctrl; -- struct hotplug_slot *hotplug_slot; -- struct delayed_work work; -- struct mutex lock; --}; -- - /** - * struct controller - PCIe hotplug controller - * @ctrl_lock: serializes writes to the Slot Control register -@@ -83,7 +65,6 @@ struct slot { - * @reset_lock: prevents access to the Data Link Layer Link Active bit in the - * Link Status register and to the Presence Detect State bit in the Slot - * Status register during a slot reset which may cause them to flap -- * @slot: pointer to the controller's slot structure - * @queue: wait queue to wake up on reception of a Command Completed event, - * used for synchronous writes to the Slot Control register - * @slot_cap: cached copy of the Slot Capabilities register -@@ -105,16 +86,24 @@ struct slot { - * that has not yet been cleared by the user - * @pending_events: used by the IRQ handler to save events retrieved from the - * Slot Status register for later consumption by the IRQ thread -+ * @state: current state machine position -+ * @lock: protects reads and writes of @state; -+ * protects scheduling, execution and cancellation of @work -+ * @work: work item to turn the slot on or off after 5 seconds -+ * in response to an Attention Button press -+ * @hotplug_slot: pointer to the structure registered with the PCI hotplug core - * @ist_running: flag to keep user request waiting while IRQ thread is running - * @request_result: result of last user request submitted to the IRQ thread - * @requester: wait queue to wake up on completion of user request, - * used for synchronous slot enable/disable request via sysfs -+ * -+ * PCIe hotplug has a 1:1 relationship between controller and slot, hence -+ * unlike other drivers, the two aren't represented by separate structures. - */ - struct controller { - struct mutex ctrl_lock; - struct pcie_device *pcie; - struct rw_semaphore reset_lock; -- struct slot *slot; - wait_queue_head_t queue; - u32 slot_cap; - u16 slot_ctrl; -@@ -125,6 +114,10 @@ struct controller { - unsigned int notification_enabled:1; - unsigned int power_fault_detected; - atomic_t pending_events; -+ u8 state; -+ struct mutex lock; -+ struct delayed_work work; -+ struct hotplug_slot *hotplug_slot; - unsigned int ist_running; - int request_result; - wait_queue_head_t requester; -@@ -176,26 +169,26 @@ struct controller { - #define PSN(ctrl) (((ctrl)->slot_cap & PCI_EXP_SLTCAP_PSN) >> 19) - - void pciehp_request(struct controller *ctrl, int action); --void pciehp_handle_button_press(struct slot *slot); --void pciehp_handle_disable_request(struct slot *slot); --void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events); --int pciehp_configure_device(struct slot *p_slot); --void pciehp_unconfigure_device(struct slot *p_slot, bool presence); -+void pciehp_handle_button_press(struct controller *ctrl); -+void pciehp_handle_disable_request(struct controller *ctrl); -+void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events); -+int pciehp_configure_device(struct controller *ctrl); -+void pciehp_unconfigure_device(struct controller *ctrl, bool presence); - void pciehp_queue_pushbutton_work(struct work_struct *work); - struct controller *pcie_init(struct pcie_device *dev); - int pcie_init_notification(struct controller *ctrl); - void pcie_shutdown_notification(struct controller *ctrl); - void pcie_clear_hotplug_events(struct controller *ctrl); --int pciehp_power_on_slot(struct slot *slot); --void pciehp_power_off_slot(struct slot *slot); --void pciehp_get_power_status(struct slot *slot, u8 *status); -- --void pciehp_set_attention_status(struct slot *slot, u8 status); --void pciehp_get_latch_status(struct slot *slot, u8 *status); --int pciehp_query_power_fault(struct slot *slot); --void pciehp_green_led_on(struct slot *slot); --void pciehp_green_led_off(struct slot *slot); --void pciehp_green_led_blink(struct slot *slot); -+int pciehp_power_on_slot(struct controller *ctrl); -+void pciehp_power_off_slot(struct controller *ctrl); -+void pciehp_get_power_status(struct controller *ctrl, u8 *status); -+ -+void pciehp_set_attention_status(struct controller *ctrl, u8 status); -+void pciehp_get_latch_status(struct controller *ctrl, u8 *status); -+int pciehp_query_power_fault(struct controller *ctrl); -+void pciehp_green_led_on(struct controller *ctrl); -+void pciehp_green_led_off(struct controller *ctrl); -+void pciehp_green_led_blink(struct controller *ctrl); - bool pciehp_card_present(struct controller *ctrl); - bool pciehp_card_present_or_link_active(struct controller *ctrl); - int pciehp_check_link_status(struct controller *ctrl); -@@ -209,9 +202,9 @@ int pciehp_get_attention_status(struct hotplug_slot *hotplug_slot, u8 *status); - int pciehp_set_raw_indicator_status(struct hotplug_slot *h_slot, u8 status); - int pciehp_get_raw_indicator_status(struct hotplug_slot *h_slot, u8 *status); - --static inline const char *slot_name(struct slot *slot) -+static inline const char *slot_name(struct controller *ctrl) - { -- return hotplug_slot_name(slot->hotplug_slot); -+ return hotplug_slot_name(ctrl->hotplug_slot); - } - - #endif /* _PCIEHP_H */ -diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c -index c052efe421f3..307099c720ec 100644 ---- a/drivers/pci/hotplug/pciehp_core.c -+++ b/drivers/pci/hotplug/pciehp_core.c -@@ -51,7 +51,6 @@ static int get_adapter_status(struct hotplug_slot *slot, u8 *value); - - static int init_slot(struct controller *ctrl) - { -- struct slot *slot = ctrl->slot; - struct hotplug_slot *hotplug = NULL; - struct hotplug_slot_info *info = NULL; - struct hotplug_slot_ops *ops = NULL; -@@ -88,9 +87,9 @@ static int init_slot(struct controller *ctrl) - - /* register this slot with the hotplug pci core */ - hotplug->info = info; -- hotplug->private = slot; -+ hotplug->private = ctrl; - hotplug->ops = ops; -- slot->hotplug_slot = hotplug; -+ ctrl->hotplug_slot = hotplug; - snprintf(name, SLOT_NAME_SIZE, "%u", PSN(ctrl)); - - retval = pci_hp_initialize(hotplug, -@@ -108,7 +107,7 @@ static int init_slot(struct controller *ctrl) - - static void cleanup_slot(struct controller *ctrl) - { -- struct hotplug_slot *hotplug_slot = ctrl->slot->hotplug_slot; -+ struct hotplug_slot *hotplug_slot = ctrl->hotplug_slot; - - pci_hp_destroy(hotplug_slot); - kfree(hotplug_slot->ops); -@@ -121,44 +120,44 @@ static void cleanup_slot(struct controller *ctrl) - */ - static int set_attention_status(struct hotplug_slot *hotplug_slot, u8 status) - { -- struct slot *slot = hotplug_slot->private; -- struct pci_dev *pdev = slot->ctrl->pcie->port; -+ struct controller *ctrl = hotplug_slot->private; -+ struct pci_dev *pdev = ctrl->pcie->port; - - pci_config_pm_runtime_get(pdev); -- pciehp_set_attention_status(slot, status); -+ pciehp_set_attention_status(ctrl, status); - pci_config_pm_runtime_put(pdev); - return 0; - } - - static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) - { -- struct slot *slot = hotplug_slot->private; -- struct pci_dev *pdev = slot->ctrl->pcie->port; -+ struct controller *ctrl = hotplug_slot->private; -+ struct pci_dev *pdev = ctrl->pcie->port; - - pci_config_pm_runtime_get(pdev); -- pciehp_get_power_status(slot, value); -+ pciehp_get_power_status(ctrl, value); - pci_config_pm_runtime_put(pdev); - return 0; - } - - static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value) - { -- struct slot *slot = hotplug_slot->private; -- struct pci_dev *pdev = slot->ctrl->pcie->port; -+ struct controller *ctrl = hotplug_slot->private; -+ struct pci_dev *pdev = ctrl->pcie->port; - - pci_config_pm_runtime_get(pdev); -- pciehp_get_latch_status(slot, value); -+ pciehp_get_latch_status(ctrl, value); - pci_config_pm_runtime_put(pdev); - return 0; - } - - static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) - { -- struct slot *slot = hotplug_slot->private; -- struct pci_dev *pdev = slot->ctrl->pcie->port; -+ struct controller *ctrl = hotplug_slot->private; -+ struct pci_dev *pdev = ctrl->pcie->port; - - pci_config_pm_runtime_get(pdev); -- *value = pciehp_card_present_or_link_active(slot->ctrl); -+ *value = pciehp_card_present_or_link_active(ctrl); - pci_config_pm_runtime_put(pdev); - return 0; - } -@@ -175,20 +174,19 @@ static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value) - */ - static void pciehp_check_presence(struct controller *ctrl) - { -- struct slot *slot = ctrl->slot; - bool occupied; - - down_read(&ctrl->reset_lock); -- mutex_lock(&slot->lock); -+ mutex_lock(&ctrl->lock); - - occupied = pciehp_card_present_or_link_active(ctrl); -- if ((occupied && (slot->state == OFF_STATE || -- slot->state == BLINKINGON_STATE)) || -- (!occupied && (slot->state == ON_STATE || -- slot->state == BLINKINGOFF_STATE))) -+ if ((occupied && (ctrl->state == OFF_STATE || -+ ctrl->state == BLINKINGON_STATE)) || -+ (!occupied && (ctrl->state == ON_STATE || -+ ctrl->state == BLINKINGOFF_STATE))) - pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); - -- mutex_unlock(&slot->lock); -+ mutex_unlock(&ctrl->lock); - up_read(&ctrl->reset_lock); - } - -@@ -196,7 +194,6 @@ static int pciehp_probe(struct pcie_device *dev) - { - int rc; - struct controller *ctrl; -- struct slot *slot; - - /* If this is not a "hotplug" service, we have no business here. */ - if (dev->service != PCIE_PORT_SERVICE_HP) -@@ -234,8 +231,7 @@ static int pciehp_probe(struct pcie_device *dev) - } - - /* Publish to user space */ -- slot = ctrl->slot; -- rc = pci_hp_add(slot->hotplug_slot); -+ rc = pci_hp_add(ctrl->hotplug_slot); - if (rc) { - ctrl_err(ctrl, "Publication to user space failed (%d)\n", rc); - goto err_out_shutdown_notification; -@@ -258,7 +254,7 @@ static void pciehp_remove(struct pcie_device *dev) - { - struct controller *ctrl = get_service_data(dev); - -- pci_hp_del(ctrl->slot->hotplug_slot); -+ pci_hp_del(ctrl->hotplug_slot); - pcie_shutdown_notification(ctrl); - cleanup_slot(ctrl); - pciehp_release_ctrl(ctrl); -@@ -273,14 +269,13 @@ static int pciehp_suspend(struct pcie_device *dev) - static int pciehp_resume_noirq(struct pcie_device *dev) - { - struct controller *ctrl = get_service_data(dev); -- struct slot *slot = ctrl->slot; - - /* pci_restore_state() just wrote to the Slot Control register */ - ctrl->cmd_started = jiffies; - ctrl->cmd_busy = true; - - /* clear spurious events from rediscovery of inserted card */ -- if (slot->state == ON_STATE || slot->state == BLINKINGOFF_STATE) -+ if (ctrl->state == ON_STATE || ctrl->state == BLINKINGOFF_STATE) - pcie_clear_hotplug_events(ctrl); - - return 0; -diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c -index 97fa865717ec..dd6533a094a8 100644 ---- a/drivers/pci/hotplug/pciehp_ctrl.c -+++ b/drivers/pci/hotplug/pciehp_ctrl.c -@@ -26,11 +26,11 @@ - #define SAFE_REMOVAL true - #define SURPRISE_REMOVAL false - --static void set_slot_off(struct controller *ctrl, struct slot *pslot) -+static void set_slot_off(struct controller *ctrl) - { - /* turn off slot, turn on Amber LED, turn off Green LED if supported*/ - if (POWER_CTRL(ctrl)) { -- pciehp_power_off_slot(pslot); -+ pciehp_power_off_slot(ctrl); - - /* - * After turning power off, we must wait for at least 1 second -@@ -40,31 +40,30 @@ static void set_slot_off(struct controller *ctrl, struct slot *pslot) - msleep(1000); - } - -- pciehp_green_led_off(pslot); -- pciehp_set_attention_status(pslot, 1); -+ pciehp_green_led_off(ctrl); -+ pciehp_set_attention_status(ctrl, 1); - } - - /** - * board_added - Called after a board has been added to the system. -- * @p_slot: &slot where board is added -+ * @ctrl: PCIe hotplug controller where board is added - * - * Turns power on for the board. - * Configures board. - */ --static int board_added(struct slot *p_slot) -+static int board_added(struct controller *ctrl) - { - int retval = 0; -- struct controller *ctrl = p_slot->ctrl; - struct pci_bus *parent = ctrl->pcie->port->subordinate; - - if (POWER_CTRL(ctrl)) { - /* Power on slot */ -- retval = pciehp_power_on_slot(p_slot); -+ retval = pciehp_power_on_slot(ctrl); - if (retval) - return retval; - } - -- pciehp_green_led_blink(p_slot); -+ pciehp_green_led_blink(ctrl); - - /* Check link training status */ - retval = pciehp_check_link_status(ctrl); -@@ -74,13 +73,13 @@ static int board_added(struct slot *p_slot) - } - - /* Check for a power fault */ -- if (ctrl->power_fault_detected || pciehp_query_power_fault(p_slot)) { -- ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(p_slot)); -+ if (ctrl->power_fault_detected || pciehp_query_power_fault(ctrl)) { -+ ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(ctrl)); - retval = -EIO; - goto err_exit; - } - -- retval = pciehp_configure_device(p_slot); -+ retval = pciehp_configure_device(ctrl); - if (retval) { - if (retval != -EEXIST) { - ctrl_err(ctrl, "Cannot add device at %04x:%02x:00\n", -@@ -89,28 +88,26 @@ static int board_added(struct slot *p_slot) - } - } - -- pciehp_green_led_on(p_slot); -- pciehp_set_attention_status(p_slot, 0); -+ pciehp_green_led_on(ctrl); -+ pciehp_set_attention_status(ctrl, 0); - return 0; - - err_exit: -- set_slot_off(ctrl, p_slot); -+ set_slot_off(ctrl); - return retval; - } - - /** - * remove_board - Turns off slot and LEDs -- * @p_slot: slot where board is being removed -+ * @ctrl: PCIe hotplug controller where board is being removed - * @safe_removal: whether the board is safely removed (versus surprise removed) - */ --static void remove_board(struct slot *p_slot, bool safe_removal) -+static void remove_board(struct controller *ctrl, bool safe_removal) - { -- struct controller *ctrl = p_slot->ctrl; -- -- pciehp_unconfigure_device(p_slot, safe_removal); -+ pciehp_unconfigure_device(ctrl, safe_removal); - - if (POWER_CTRL(ctrl)) { -- pciehp_power_off_slot(p_slot); -+ pciehp_power_off_slot(ctrl); - - /* - * After turning power off, we must wait for at least 1 second -@@ -125,11 +122,11 @@ static void remove_board(struct slot *p_slot, bool safe_removal) - } - - /* turn off Green LED */ -- pciehp_green_led_off(p_slot); -+ pciehp_green_led_off(ctrl); - } - --static int pciehp_enable_slot(struct slot *slot); --static int pciehp_disable_slot(struct slot *slot, bool safe_removal); -+static int pciehp_enable_slot(struct controller *ctrl); -+static int pciehp_disable_slot(struct controller *ctrl, bool safe_removal); - - void pciehp_request(struct controller *ctrl, int action) - { -@@ -140,11 +137,11 @@ void pciehp_request(struct controller *ctrl, int action) - - void pciehp_queue_pushbutton_work(struct work_struct *work) - { -- struct slot *p_slot = container_of(work, struct slot, work.work); -- struct controller *ctrl = p_slot->ctrl; -+ struct controller *ctrl = container_of(work, struct controller, -+ work.work); - -- mutex_lock(&p_slot->lock); -- switch (p_slot->state) { -+ mutex_lock(&ctrl->lock); -+ switch (ctrl->state) { - case BLINKINGOFF_STATE: - pciehp_request(ctrl, DISABLE_SLOT); - break; -@@ -154,30 +151,28 @@ void pciehp_queue_pushbutton_work(struct work_struct *work) - default: - break; - } -- mutex_unlock(&p_slot->lock); -+ mutex_unlock(&ctrl->lock); - } - --void pciehp_handle_button_press(struct slot *p_slot) -+void pciehp_handle_button_press(struct controller *ctrl) - { -- struct controller *ctrl = p_slot->ctrl; -- -- mutex_lock(&p_slot->lock); -- switch (p_slot->state) { -+ mutex_lock(&ctrl->lock); -+ switch (ctrl->state) { - case OFF_STATE: - case ON_STATE: -- if (p_slot->state == ON_STATE) { -- p_slot->state = BLINKINGOFF_STATE; -+ if (ctrl->state == ON_STATE) { -+ ctrl->state = BLINKINGOFF_STATE; - ctrl_info(ctrl, "Slot(%s): Powering off due to button press\n", -- slot_name(p_slot)); -+ slot_name(ctrl)); - } else { -- p_slot->state = BLINKINGON_STATE; -+ ctrl->state = BLINKINGON_STATE; - ctrl_info(ctrl, "Slot(%s) Powering on due to button press\n", -- slot_name(p_slot)); -+ slot_name(ctrl)); - } - /* blink green LED and turn off amber */ -- pciehp_green_led_blink(p_slot); -- pciehp_set_attention_status(p_slot, 0); -- schedule_delayed_work(&p_slot->work, 5 * HZ); -+ pciehp_green_led_blink(ctrl); -+ pciehp_set_attention_status(ctrl, 0); -+ schedule_delayed_work(&ctrl->work, 5 * HZ); - break; - case BLINKINGOFF_STATE: - case BLINKINGON_STATE: -@@ -186,192 +181,184 @@ void pciehp_handle_button_press(struct slot *p_slot) - * press the attention again before the 5 sec. limit - * expires to cancel hot-add or hot-remove - */ -- ctrl_info(ctrl, "Slot(%s): Button cancel\n", slot_name(p_slot)); -- cancel_delayed_work(&p_slot->work); -- if (p_slot->state == BLINKINGOFF_STATE) { -- p_slot->state = ON_STATE; -- pciehp_green_led_on(p_slot); -+ ctrl_info(ctrl, "Slot(%s): Button cancel\n", slot_name(ctrl)); -+ cancel_delayed_work(&ctrl->work); -+ if (ctrl->state == BLINKINGOFF_STATE) { -+ ctrl->state = ON_STATE; -+ pciehp_green_led_on(ctrl); - } else { -- p_slot->state = OFF_STATE; -- pciehp_green_led_off(p_slot); -+ ctrl->state = OFF_STATE; -+ pciehp_green_led_off(ctrl); - } -- pciehp_set_attention_status(p_slot, 0); -+ pciehp_set_attention_status(ctrl, 0); - ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n", -- slot_name(p_slot)); -+ slot_name(ctrl)); - break; - default: - ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n", -- slot_name(p_slot), p_slot->state); -+ slot_name(ctrl), ctrl->state); - break; - } -- mutex_unlock(&p_slot->lock); -+ mutex_unlock(&ctrl->lock); - } - --void pciehp_handle_disable_request(struct slot *slot) -+void pciehp_handle_disable_request(struct controller *ctrl) - { -- struct controller *ctrl = slot->ctrl; -- -- mutex_lock(&slot->lock); -- switch (slot->state) { -+ mutex_lock(&ctrl->lock); -+ switch (ctrl->state) { - case BLINKINGON_STATE: - case BLINKINGOFF_STATE: -- cancel_delayed_work(&slot->work); -+ cancel_delayed_work(&ctrl->work); - break; - } -- slot->state = POWEROFF_STATE; -- mutex_unlock(&slot->lock); -+ ctrl->state = POWEROFF_STATE; -+ mutex_unlock(&ctrl->lock); - -- ctrl->request_result = pciehp_disable_slot(slot, SAFE_REMOVAL); -+ ctrl->request_result = pciehp_disable_slot(ctrl, SAFE_REMOVAL); - } - --void pciehp_handle_presence_or_link_change(struct slot *slot, u32 events) -+void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events) - { -- struct controller *ctrl = slot->ctrl; - bool present, link_active; - - /* - * If the slot is on and presence or link has changed, turn it off. - * Even if it's occupied again, we cannot assume the card is the same. - */ -- mutex_lock(&slot->lock); -- switch (slot->state) { -+ mutex_lock(&ctrl->lock); -+ switch (ctrl->state) { - case BLINKINGOFF_STATE: -- cancel_delayed_work(&slot->work); -+ cancel_delayed_work(&ctrl->work); - /* fall through */ - case ON_STATE: -- slot->state = POWEROFF_STATE; -- mutex_unlock(&slot->lock); -+ ctrl->state = POWEROFF_STATE; -+ mutex_unlock(&ctrl->lock); - if (events & PCI_EXP_SLTSTA_DLLSC) - ctrl_info(ctrl, "Slot(%s): Link Down\n", -- slot_name(slot)); -+ slot_name(ctrl)); - if (events & PCI_EXP_SLTSTA_PDC) - ctrl_info(ctrl, "Slot(%s): Card not present\n", -- slot_name(slot)); -- pciehp_disable_slot(slot, SURPRISE_REMOVAL); -+ slot_name(ctrl)); -+ pciehp_disable_slot(ctrl, SURPRISE_REMOVAL); - break; - default: -- mutex_unlock(&slot->lock); -+ mutex_unlock(&ctrl->lock); - break; - } - - /* Turn the slot on if it's occupied or link is up */ -- mutex_lock(&slot->lock); -+ mutex_lock(&ctrl->lock); - present = pciehp_card_present(ctrl); - link_active = pciehp_check_link_active(ctrl); - if (!present && !link_active) { -- mutex_unlock(&slot->lock); -+ mutex_unlock(&ctrl->lock); - return; - } - -- switch (slot->state) { -+ switch (ctrl->state) { - case BLINKINGON_STATE: -- cancel_delayed_work(&slot->work); -+ cancel_delayed_work(&ctrl->work); - /* fall through */ - case OFF_STATE: -- slot->state = POWERON_STATE; -- mutex_unlock(&slot->lock); -+ ctrl->state = POWERON_STATE; -+ mutex_unlock(&ctrl->lock); - if (present) - ctrl_info(ctrl, "Slot(%s): Card present\n", -- slot_name(slot)); -+ slot_name(ctrl)); - if (link_active) - ctrl_info(ctrl, "Slot(%s): Link Up\n", -- slot_name(slot)); -- ctrl->request_result = pciehp_enable_slot(slot); -+ slot_name(ctrl)); -+ ctrl->request_result = pciehp_enable_slot(ctrl); - break; - default: -- mutex_unlock(&slot->lock); -+ mutex_unlock(&ctrl->lock); - break; - } - } - --static int __pciehp_enable_slot(struct slot *p_slot) -+static int __pciehp_enable_slot(struct controller *ctrl) - { - u8 getstatus = 0; -- struct controller *ctrl = p_slot->ctrl; - -- if (MRL_SENS(p_slot->ctrl)) { -- pciehp_get_latch_status(p_slot, &getstatus); -+ if (MRL_SENS(ctrl)) { -+ pciehp_get_latch_status(ctrl, &getstatus); - if (getstatus) { - ctrl_info(ctrl, "Slot(%s): Latch open\n", -- slot_name(p_slot)); -+ slot_name(ctrl)); - return -ENODEV; - } - } - -- if (POWER_CTRL(p_slot->ctrl)) { -- pciehp_get_power_status(p_slot, &getstatus); -+ if (POWER_CTRL(ctrl)) { -+ pciehp_get_power_status(ctrl, &getstatus); - if (getstatus) { - ctrl_info(ctrl, "Slot(%s): Already enabled\n", -- slot_name(p_slot)); -+ slot_name(ctrl)); - return 0; - } - } - -- return board_added(p_slot); -+ return board_added(ctrl); - } - --static int pciehp_enable_slot(struct slot *slot) -+static int pciehp_enable_slot(struct controller *ctrl) - { -- struct controller *ctrl = slot->ctrl; - int ret; - - pm_runtime_get_sync(&ctrl->pcie->port->dev); -- ret = __pciehp_enable_slot(slot); -+ ret = __pciehp_enable_slot(ctrl); - if (ret && ATTN_BUTTN(ctrl)) -- pciehp_green_led_off(slot); /* may be blinking */ -+ pciehp_green_led_off(ctrl); /* may be blinking */ - pm_runtime_put(&ctrl->pcie->port->dev); - -- mutex_lock(&slot->lock); -- slot->state = ret ? OFF_STATE : ON_STATE; -- mutex_unlock(&slot->lock); -+ mutex_lock(&ctrl->lock); -+ ctrl->state = ret ? OFF_STATE : ON_STATE; -+ mutex_unlock(&ctrl->lock); - - return ret; - } - --static int __pciehp_disable_slot(struct slot *p_slot, bool safe_removal) -+static int __pciehp_disable_slot(struct controller *ctrl, bool safe_removal) - { - u8 getstatus = 0; -- struct controller *ctrl = p_slot->ctrl; - -- if (POWER_CTRL(p_slot->ctrl)) { -- pciehp_get_power_status(p_slot, &getstatus); -+ if (POWER_CTRL(ctrl)) { -+ pciehp_get_power_status(ctrl, &getstatus); - if (!getstatus) { - ctrl_info(ctrl, "Slot(%s): Already disabled\n", -- slot_name(p_slot)); -+ slot_name(ctrl)); - return -EINVAL; - } - } - -- remove_board(p_slot, safe_removal); -+ remove_board(ctrl, safe_removal); - return 0; - } - --static int pciehp_disable_slot(struct slot *slot, bool safe_removal) -+static int pciehp_disable_slot(struct controller *ctrl, bool safe_removal) - { -- struct controller *ctrl = slot->ctrl; - int ret; - - pm_runtime_get_sync(&ctrl->pcie->port->dev); -- ret = __pciehp_disable_slot(slot, safe_removal); -+ ret = __pciehp_disable_slot(ctrl, safe_removal); - pm_runtime_put(&ctrl->pcie->port->dev); - -- mutex_lock(&slot->lock); -- slot->state = OFF_STATE; -- mutex_unlock(&slot->lock); -+ mutex_lock(&ctrl->lock); -+ ctrl->state = OFF_STATE; -+ mutex_unlock(&ctrl->lock); - - return ret; - } - - int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot) - { -- struct slot *p_slot = hotplug_slot->private; -- struct controller *ctrl = p_slot->ctrl; -+ struct controller *ctrl = hotplug_slot->private; - -- mutex_lock(&p_slot->lock); -- switch (p_slot->state) { -+ mutex_lock(&ctrl->lock); -+ switch (ctrl->state) { - case BLINKINGON_STATE: - case OFF_STATE: -- mutex_unlock(&p_slot->lock); -+ mutex_unlock(&ctrl->lock); - /* - * The IRQ thread becomes a no-op if the user pulls out the - * card before the thread wakes up, so initialize to -ENODEV. -@@ -384,34 +371,33 @@ int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot) - return ctrl->request_result; - case POWERON_STATE: - ctrl_info(ctrl, "Slot(%s): Already in powering on state\n", -- slot_name(p_slot)); -+ slot_name(ctrl)); - break; - case BLINKINGOFF_STATE: - case ON_STATE: - case POWEROFF_STATE: - ctrl_info(ctrl, "Slot(%s): Already enabled\n", -- slot_name(p_slot)); -+ slot_name(ctrl)); - break; - default: - ctrl_err(ctrl, "Slot(%s): Invalid state %#x\n", -- slot_name(p_slot), p_slot->state); -+ slot_name(ctrl), ctrl->state); - break; - } -- mutex_unlock(&p_slot->lock); -+ mutex_unlock(&ctrl->lock); - - return -ENODEV; - } - - int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot) - { -- struct slot *p_slot = hotplug_slot->private; -- struct controller *ctrl = p_slot->ctrl; -+ struct controller *ctrl = hotplug_slot->private; - -- mutex_lock(&p_slot->lock); -- switch (p_slot->state) { -+ mutex_lock(&ctrl->lock); -+ switch (ctrl->state) { - case BLINKINGOFF_STATE: - case ON_STATE: -- mutex_unlock(&p_slot->lock); -+ mutex_unlock(&ctrl->lock); - pciehp_request(ctrl, DISABLE_SLOT); - wait_event(ctrl->requester, - !atomic_read(&ctrl->pending_events) && -@@ -419,20 +405,20 @@ int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot) - return ctrl->request_result; - case POWEROFF_STATE: - ctrl_info(ctrl, "Slot(%s): Already in powering off state\n", -- slot_name(p_slot)); -+ slot_name(ctrl)); - break; - case BLINKINGON_STATE: - case OFF_STATE: - case POWERON_STATE: - ctrl_info(ctrl, "Slot(%s): Already disabled\n", -- slot_name(p_slot)); -+ slot_name(ctrl)); - break; - default: - ctrl_err(ctrl, "Slot(%s): Invalid state %#x\n", -- slot_name(p_slot), p_slot->state); -+ slot_name(ctrl), ctrl->state); - break; - } -- mutex_unlock(&p_slot->lock); -+ mutex_unlock(&ctrl->lock); - - return -ENODEV; - } -diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c -index 7f4173d6771a..4a17d71e15d3 100644 ---- a/drivers/pci/hotplug/pciehp_hpc.c -+++ b/drivers/pci/hotplug/pciehp_hpc.c -@@ -40,7 +40,7 @@ static inline int pciehp_request_irq(struct controller *ctrl) - if (pciehp_poll_mode) { - ctrl->poll_thread = kthread_run(&pciehp_poll, ctrl, - "pciehp_poll-%s", -- slot_name(ctrl->slot)); -+ slot_name(ctrl)); - return PTR_ERR_OR_ZERO(ctrl->poll_thread); - } - -@@ -315,8 +315,8 @@ static int pciehp_link_enable(struct controller *ctrl) - int pciehp_get_raw_indicator_status(struct hotplug_slot *hotplug_slot, - u8 *status) - { -- struct slot *slot = hotplug_slot->private; -- struct pci_dev *pdev = ctrl_dev(slot->ctrl); -+ struct controller *ctrl = hotplug_slot->private; -+ struct pci_dev *pdev = ctrl_dev(ctrl); - u16 slot_ctrl; - - pci_config_pm_runtime_get(pdev); -@@ -328,8 +328,7 @@ int pciehp_get_raw_indicator_status(struct hotplug_slot *hotplug_slot, - - int pciehp_get_attention_status(struct hotplug_slot *hotplug_slot, u8 *status) - { -- struct slot *slot = hotplug_slot->private; -- struct controller *ctrl = slot->ctrl; -+ struct controller *ctrl = hotplug_slot->private; - struct pci_dev *pdev = ctrl_dev(ctrl); - u16 slot_ctrl; - -@@ -357,9 +356,8 @@ int pciehp_get_attention_status(struct hotplug_slot *hotplug_slot, u8 *status) - return 0; - } - --void pciehp_get_power_status(struct slot *slot, u8 *status) -+void pciehp_get_power_status(struct controller *ctrl, u8 *status) - { -- struct controller *ctrl = slot->ctrl; - struct pci_dev *pdev = ctrl_dev(ctrl); - u16 slot_ctrl; - -@@ -380,9 +378,9 @@ void pciehp_get_power_status(struct slot *slot, u8 *status) - } - } - --void pciehp_get_latch_status(struct slot *slot, u8 *status) -+void pciehp_get_latch_status(struct controller *ctrl, u8 *status) - { -- struct pci_dev *pdev = ctrl_dev(slot->ctrl); -+ struct pci_dev *pdev = ctrl_dev(ctrl); - u16 slot_status; - - pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); -@@ -412,9 +410,9 @@ bool pciehp_card_present_or_link_active(struct controller *ctrl) - return pciehp_card_present(ctrl) || pciehp_check_link_active(ctrl); - } - --int pciehp_query_power_fault(struct slot *slot) -+int pciehp_query_power_fault(struct controller *ctrl) - { -- struct pci_dev *pdev = ctrl_dev(slot->ctrl); -+ struct pci_dev *pdev = ctrl_dev(ctrl); - u16 slot_status; - - pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); -@@ -424,8 +422,7 @@ int pciehp_query_power_fault(struct slot *slot) - int pciehp_set_raw_indicator_status(struct hotplug_slot *hotplug_slot, - u8 status) - { -- struct slot *slot = hotplug_slot->private; -- struct controller *ctrl = slot->ctrl; -+ struct controller *ctrl = hotplug_slot->private; - struct pci_dev *pdev = ctrl_dev(ctrl); - - pci_config_pm_runtime_get(pdev); -@@ -435,9 +432,8 @@ int pciehp_set_raw_indicator_status(struct hotplug_slot *hotplug_slot, - return 0; - } - --void pciehp_set_attention_status(struct slot *slot, u8 value) -+void pciehp_set_attention_status(struct controller *ctrl, u8 value) - { -- struct controller *ctrl = slot->ctrl; - u16 slot_cmd; - - if (!ATTN_LED(ctrl)) -@@ -461,10 +457,8 @@ void pciehp_set_attention_status(struct slot *slot, u8 value) - pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, slot_cmd); - } - --void pciehp_green_led_on(struct slot *slot) -+void pciehp_green_led_on(struct controller *ctrl) - { -- struct controller *ctrl = slot->ctrl; -- - if (!PWR_LED(ctrl)) - return; - -@@ -475,10 +469,8 @@ void pciehp_green_led_on(struct slot *slot) - PCI_EXP_SLTCTL_PWR_IND_ON); - } - --void pciehp_green_led_off(struct slot *slot) -+void pciehp_green_led_off(struct controller *ctrl) - { -- struct controller *ctrl = slot->ctrl; -- - if (!PWR_LED(ctrl)) - return; - -@@ -489,10 +481,8 @@ void pciehp_green_led_off(struct slot *slot) - PCI_EXP_SLTCTL_PWR_IND_OFF); - } - --void pciehp_green_led_blink(struct slot *slot) -+void pciehp_green_led_blink(struct controller *ctrl) - { -- struct controller *ctrl = slot->ctrl; -- - if (!PWR_LED(ctrl)) - return; - -@@ -503,9 +493,8 @@ void pciehp_green_led_blink(struct slot *slot) - PCI_EXP_SLTCTL_PWR_IND_BLINK); - } - --int pciehp_power_on_slot(struct slot *slot) -+int pciehp_power_on_slot(struct controller *ctrl) - { -- struct controller *ctrl = slot->ctrl; - struct pci_dev *pdev = ctrl_dev(ctrl); - u16 slot_status; - int retval; -@@ -529,10 +518,8 @@ int pciehp_power_on_slot(struct slot *slot) - return retval; - } - --void pciehp_power_off_slot(struct slot *slot) -+void pciehp_power_off_slot(struct controller *ctrl) - { -- struct controller *ctrl = slot->ctrl; -- - pcie_write_cmd(ctrl, PCI_EXP_SLTCTL_PWR_OFF, PCI_EXP_SLTCTL_PCC); - ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__, - pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, -@@ -644,7 +631,6 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id) - { - struct controller *ctrl = (struct controller *)dev_id; - struct pci_dev *pdev = ctrl_dev(ctrl); -- struct slot *slot = ctrl->slot; - irqreturn_t ret; - u32 events; - -@@ -669,16 +655,16 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id) - /* Check Attention Button Pressed */ - if (events & PCI_EXP_SLTSTA_ABP) { - ctrl_info(ctrl, "Slot(%s): Attention button pressed\n", -- slot_name(slot)); -- pciehp_handle_button_press(slot); -+ slot_name(ctrl)); -+ pciehp_handle_button_press(ctrl); - } - - /* Check Power Fault Detected */ - if ((events & PCI_EXP_SLTSTA_PFD) && !ctrl->power_fault_detected) { - ctrl->power_fault_detected = 1; -- ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(slot)); -- pciehp_set_attention_status(slot, 1); -- pciehp_green_led_off(slot); -+ ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(ctrl)); -+ pciehp_set_attention_status(ctrl, 1); -+ pciehp_green_led_off(ctrl); - } - - /* -@@ -687,9 +673,9 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id) - */ - down_read(&ctrl->reset_lock); - if (events & DISABLE_SLOT) -- pciehp_handle_disable_request(slot); -+ pciehp_handle_disable_request(ctrl); - else if (events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) -- pciehp_handle_presence_or_link_change(slot, events); -+ pciehp_handle_presence_or_link_change(ctrl, events); - up_read(&ctrl->reset_lock); - - ret = IRQ_HANDLED; -@@ -788,8 +774,7 @@ void pcie_clear_hotplug_events(struct controller *ctrl) - */ - int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, int probe) - { -- struct slot *slot = hotplug_slot->private; -- struct controller *ctrl = slot->ctrl; -+ struct controller *ctrl = hotplug_slot->private; - struct pci_dev *pdev = ctrl_dev(ctrl); - u16 stat_mask = 0, ctrl_mask = 0; - int rc; -@@ -839,34 +824,6 @@ void pcie_shutdown_notification(struct controller *ctrl) - } - } - --static int pcie_init_slot(struct controller *ctrl) --{ -- struct pci_bus *subordinate = ctrl_dev(ctrl)->subordinate; -- struct slot *slot; -- -- slot = kzalloc(sizeof(*slot), GFP_KERNEL); -- if (!slot) -- return -ENOMEM; -- -- down_read(&pci_bus_sem); -- slot->state = list_empty(&subordinate->devices) ? OFF_STATE : ON_STATE; -- up_read(&pci_bus_sem); -- -- slot->ctrl = ctrl; -- mutex_init(&slot->lock); -- INIT_DELAYED_WORK(&slot->work, pciehp_queue_pushbutton_work); -- ctrl->slot = slot; -- return 0; --} -- --static void pcie_cleanup_slot(struct controller *ctrl) --{ -- struct slot *slot = ctrl->slot; -- -- cancel_delayed_work_sync(&slot->work); -- kfree(slot); --} -- - static inline void dbg_ctrl(struct controller *ctrl) - { - struct pci_dev *pdev = ctrl->pcie->port; -@@ -890,10 +847,11 @@ struct controller *pcie_init(struct pcie_device *dev) - u32 slot_cap, link_cap; - u8 poweron; - struct pci_dev *pdev = dev->port; -+ struct pci_bus *subordinate = pdev->subordinate; - - ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); - if (!ctrl) -- goto abort; -+ return NULL; - - ctrl->pcie = dev; - pcie_capability_read_dword(pdev, PCI_EXP_SLTCAP, &slot_cap); -@@ -910,11 +868,17 @@ struct controller *pcie_init(struct pcie_device *dev) - - ctrl->slot_cap = slot_cap; - mutex_init(&ctrl->ctrl_lock); -+ mutex_init(&ctrl->lock); - init_rwsem(&ctrl->reset_lock); - init_waitqueue_head(&ctrl->requester); - init_waitqueue_head(&ctrl->queue); -+ INIT_DELAYED_WORK(&ctrl->work, pciehp_queue_pushbutton_work); - dbg_ctrl(ctrl); - -+ down_read(&pci_bus_sem); -+ ctrl->state = list_empty(&subordinate->devices) ? OFF_STATE : ON_STATE; -+ up_read(&pci_bus_sem); -+ - /* Check if Data Link Layer Link Active Reporting is implemented */ - pcie_capability_read_dword(pdev, PCI_EXP_LNKCAP, &link_cap); - if (link_cap & PCI_EXP_LNKCAP_DLLLARC) -@@ -940,32 +904,24 @@ struct controller *pcie_init(struct pcie_device *dev) - FLAG(link_cap, PCI_EXP_LNKCAP_DLLLARC), - pdev->broken_cmd_compl ? " (with Cmd Compl erratum)" : ""); - -- if (pcie_init_slot(ctrl)) -- goto abort_ctrl; -- - /* - * If empty slot's power status is on, turn power off. The IRQ isn't - * requested yet, so avoid triggering a notification with this command. - */ - if (POWER_CTRL(ctrl)) { -- pciehp_get_power_status(ctrl->slot, &poweron); -+ pciehp_get_power_status(ctrl, &poweron); - if (!pciehp_card_present_or_link_active(ctrl) && poweron) { - pcie_disable_notification(ctrl); -- pciehp_power_off_slot(ctrl->slot); -+ pciehp_power_off_slot(ctrl); - } - } - - return ctrl; -- --abort_ctrl: -- kfree(ctrl); --abort: -- return NULL; - } - - void pciehp_release_ctrl(struct controller *ctrl) - { -- pcie_cleanup_slot(ctrl); -+ cancel_delayed_work_sync(&ctrl->work); - kfree(ctrl); - } - -diff --git a/drivers/pci/hotplug/pciehp_pci.c b/drivers/pci/hotplug/pciehp_pci.c -index 8da87931bd45..b9c1396db6fe 100644 ---- a/drivers/pci/hotplug/pciehp_pci.c -+++ b/drivers/pci/hotplug/pciehp_pci.c -@@ -21,19 +21,18 @@ - - /** - * pciehp_configure_device() - enumerate PCI devices below a hotplug bridge -- * @p_slot: PCIe hotplug slot -+ * @ctrl: PCIe hotplug controller - * - * Enumerate PCI devices below a hotplug bridge and add them to the system. - * Return 0 on success, %-EEXIST if the devices are already enumerated or - * %-ENODEV if enumeration failed. - */ --int pciehp_configure_device(struct slot *p_slot) -+int pciehp_configure_device(struct controller *ctrl) - { - struct pci_dev *dev; -- struct pci_dev *bridge = p_slot->ctrl->pcie->port; -+ struct pci_dev *bridge = ctrl->pcie->port; - struct pci_bus *parent = bridge->subordinate; - int num, ret = 0; -- struct controller *ctrl = p_slot->ctrl; - - pci_lock_rescan_remove(); - -@@ -71,7 +70,7 @@ int pciehp_configure_device(struct slot *p_slot) - - /** - * pciehp_unconfigure_device() - remove PCI devices below a hotplug bridge -- * @p_slot: PCIe hotplug slot -+ * @ctrl: PCIe hotplug controller - * @presence: whether the card is still present in the slot; - * true for safe removal via sysfs or an Attention Button press, - * false for surprise removal -@@ -80,12 +79,11 @@ int pciehp_configure_device(struct slot *p_slot) - * them from the system. Safely removed devices are quiesced. Surprise - * removed devices are marked as such to prevent further accesses. - */ --void pciehp_unconfigure_device(struct slot *p_slot, bool presence) -+void pciehp_unconfigure_device(struct controller *ctrl, bool presence) - { - struct pci_dev *dev, *temp; -- struct pci_bus *parent = p_slot->ctrl->pcie->port->subordinate; -+ struct pci_bus *parent = ctrl->pcie->port->subordinate; - u16 command; -- struct controller *ctrl = p_slot->ctrl; - - ctrl_dbg(ctrl, "%s: domain:bus:dev = %04x:%02x:00\n", - __func__, pci_domain_nr(parent), parent->number); --- -2.33.0 - -From 532c70e6fb9e11ac2c19df26803ea69cebc4d26f Mon Sep 17 00:00:00 2001 -From: Lukas Wunner -Date: Sat, 8 Sep 2018 09:59:01 +0200 -Subject: [PATCH] PCI: pciehp: Rename controller struct members for clarity - -Of the members which were just moved from pciehp's slot struct to the -controller struct, rename "lock" to "state_lock" and rename "work" to -"button_work" for clarity. Perform the rename separately to the -unification of the two structs per Sinan's request. - -No functional change intended. - -Signed-off-by: Lukas Wunner -Signed-off-by: Bjorn Helgaas -Cc: Sinan Kaya - -Patchset: surface-hotplug ---- - drivers/pci/hotplug/pciehp.h | 10 +++--- - drivers/pci/hotplug/pciehp_core.c | 4 +-- - drivers/pci/hotplug/pciehp_ctrl.c | 58 +++++++++++++++---------------- - drivers/pci/hotplug/pciehp_hpc.c | 6 ++-- - 4 files changed, 39 insertions(+), 39 deletions(-) - -diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h -index bf1c9d3a64a5..2499489158cc 100644 ---- a/drivers/pci/hotplug/pciehp.h -+++ b/drivers/pci/hotplug/pciehp.h -@@ -87,9 +87,9 @@ do { \ - * @pending_events: used by the IRQ handler to save events retrieved from the - * Slot Status register for later consumption by the IRQ thread - * @state: current state machine position -- * @lock: protects reads and writes of @state; -- * protects scheduling, execution and cancellation of @work -- * @work: work item to turn the slot on or off after 5 seconds -+ * @state_lock: protects reads and writes of @state; -+ * protects scheduling, execution and cancellation of @button_work -+ * @button_work: work item to turn the slot on or off after 5 seconds - * in response to an Attention Button press - * @hotplug_slot: pointer to the structure registered with the PCI hotplug core - * @ist_running: flag to keep user request waiting while IRQ thread is running -@@ -115,8 +115,8 @@ struct controller { - unsigned int power_fault_detected; - atomic_t pending_events; - u8 state; -- struct mutex lock; -- struct delayed_work work; -+ struct mutex state_lock; -+ struct delayed_work button_work; - struct hotplug_slot *hotplug_slot; - unsigned int ist_running; - int request_result; -diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c -index 307099c720ec..7810000522dd 100644 ---- a/drivers/pci/hotplug/pciehp_core.c -+++ b/drivers/pci/hotplug/pciehp_core.c -@@ -177,7 +177,7 @@ static void pciehp_check_presence(struct controller *ctrl) - bool occupied; - - down_read(&ctrl->reset_lock); -- mutex_lock(&ctrl->lock); -+ mutex_lock(&ctrl->state_lock); - - occupied = pciehp_card_present_or_link_active(ctrl); - if ((occupied && (ctrl->state == OFF_STATE || -@@ -186,7 +186,7 @@ static void pciehp_check_presence(struct controller *ctrl) - ctrl->state == BLINKINGOFF_STATE))) - pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC); - -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - up_read(&ctrl->reset_lock); - } - -diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c -index dd6533a094a8..6513dd13b786 100644 ---- a/drivers/pci/hotplug/pciehp_ctrl.c -+++ b/drivers/pci/hotplug/pciehp_ctrl.c -@@ -138,9 +138,9 @@ void pciehp_request(struct controller *ctrl, int action) - void pciehp_queue_pushbutton_work(struct work_struct *work) - { - struct controller *ctrl = container_of(work, struct controller, -- work.work); -+ button_work.work); - -- mutex_lock(&ctrl->lock); -+ mutex_lock(&ctrl->state_lock); - switch (ctrl->state) { - case BLINKINGOFF_STATE: - pciehp_request(ctrl, DISABLE_SLOT); -@@ -151,12 +151,12 @@ void pciehp_queue_pushbutton_work(struct work_struct *work) - default: - break; - } -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - } - - void pciehp_handle_button_press(struct controller *ctrl) - { -- mutex_lock(&ctrl->lock); -+ mutex_lock(&ctrl->state_lock); - switch (ctrl->state) { - case OFF_STATE: - case ON_STATE: -@@ -172,7 +172,7 @@ void pciehp_handle_button_press(struct controller *ctrl) - /* blink green LED and turn off amber */ - pciehp_green_led_blink(ctrl); - pciehp_set_attention_status(ctrl, 0); -- schedule_delayed_work(&ctrl->work, 5 * HZ); -+ schedule_delayed_work(&ctrl->button_work, 5 * HZ); - break; - case BLINKINGOFF_STATE: - case BLINKINGON_STATE: -@@ -182,7 +182,7 @@ void pciehp_handle_button_press(struct controller *ctrl) - * expires to cancel hot-add or hot-remove - */ - ctrl_info(ctrl, "Slot(%s): Button cancel\n", slot_name(ctrl)); -- cancel_delayed_work(&ctrl->work); -+ cancel_delayed_work(&ctrl->button_work); - if (ctrl->state == BLINKINGOFF_STATE) { - ctrl->state = ON_STATE; - pciehp_green_led_on(ctrl); -@@ -199,20 +199,20 @@ void pciehp_handle_button_press(struct controller *ctrl) - slot_name(ctrl), ctrl->state); - break; - } -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - } - - void pciehp_handle_disable_request(struct controller *ctrl) - { -- mutex_lock(&ctrl->lock); -+ mutex_lock(&ctrl->state_lock); - switch (ctrl->state) { - case BLINKINGON_STATE: - case BLINKINGOFF_STATE: -- cancel_delayed_work(&ctrl->work); -+ cancel_delayed_work(&ctrl->button_work); - break; - } - ctrl->state = POWEROFF_STATE; -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - - ctrl->request_result = pciehp_disable_slot(ctrl, SAFE_REMOVAL); - } -@@ -225,14 +225,14 @@ void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events) - * If the slot is on and presence or link has changed, turn it off. - * Even if it's occupied again, we cannot assume the card is the same. - */ -- mutex_lock(&ctrl->lock); -+ mutex_lock(&ctrl->state_lock); - switch (ctrl->state) { - case BLINKINGOFF_STATE: -- cancel_delayed_work(&ctrl->work); -+ cancel_delayed_work(&ctrl->button_work); - /* fall through */ - case ON_STATE: - ctrl->state = POWEROFF_STATE; -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - if (events & PCI_EXP_SLTSTA_DLLSC) - ctrl_info(ctrl, "Slot(%s): Link Down\n", - slot_name(ctrl)); -@@ -242,26 +242,26 @@ void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events) - pciehp_disable_slot(ctrl, SURPRISE_REMOVAL); - break; - default: -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - break; - } - - /* Turn the slot on if it's occupied or link is up */ -- mutex_lock(&ctrl->lock); -+ mutex_lock(&ctrl->state_lock); - present = pciehp_card_present(ctrl); - link_active = pciehp_check_link_active(ctrl); - if (!present && !link_active) { -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - return; - } - - switch (ctrl->state) { - case BLINKINGON_STATE: -- cancel_delayed_work(&ctrl->work); -+ cancel_delayed_work(&ctrl->button_work); - /* fall through */ - case OFF_STATE: - ctrl->state = POWERON_STATE; -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - if (present) - ctrl_info(ctrl, "Slot(%s): Card present\n", - slot_name(ctrl)); -@@ -271,7 +271,7 @@ void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events) - ctrl->request_result = pciehp_enable_slot(ctrl); - break; - default: -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - break; - } - } -@@ -311,9 +311,9 @@ static int pciehp_enable_slot(struct controller *ctrl) - pciehp_green_led_off(ctrl); /* may be blinking */ - pm_runtime_put(&ctrl->pcie->port->dev); - -- mutex_lock(&ctrl->lock); -+ mutex_lock(&ctrl->state_lock); - ctrl->state = ret ? OFF_STATE : ON_STATE; -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - - return ret; - } -@@ -343,9 +343,9 @@ static int pciehp_disable_slot(struct controller *ctrl, bool safe_removal) - ret = __pciehp_disable_slot(ctrl, safe_removal); - pm_runtime_put(&ctrl->pcie->port->dev); - -- mutex_lock(&ctrl->lock); -+ mutex_lock(&ctrl->state_lock); - ctrl->state = OFF_STATE; -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - - return ret; - } -@@ -354,11 +354,11 @@ int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot) - { - struct controller *ctrl = hotplug_slot->private; - -- mutex_lock(&ctrl->lock); -+ mutex_lock(&ctrl->state_lock); - switch (ctrl->state) { - case BLINKINGON_STATE: - case OFF_STATE: -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - /* - * The IRQ thread becomes a no-op if the user pulls out the - * card before the thread wakes up, so initialize to -ENODEV. -@@ -384,7 +384,7 @@ int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot) - slot_name(ctrl), ctrl->state); - break; - } -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - - return -ENODEV; - } -@@ -393,11 +393,11 @@ int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot) - { - struct controller *ctrl = hotplug_slot->private; - -- mutex_lock(&ctrl->lock); -+ mutex_lock(&ctrl->state_lock); - switch (ctrl->state) { - case BLINKINGOFF_STATE: - case ON_STATE: -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - pciehp_request(ctrl, DISABLE_SLOT); - wait_event(ctrl->requester, - !atomic_read(&ctrl->pending_events) && -@@ -418,7 +418,7 @@ int pciehp_sysfs_disable_slot(struct hotplug_slot *hotplug_slot) - slot_name(ctrl), ctrl->state); - break; - } -- mutex_unlock(&ctrl->lock); -+ mutex_unlock(&ctrl->state_lock); - - return -ENODEV; - } -diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c -index 4a17d71e15d3..e4d6ec960630 100644 ---- a/drivers/pci/hotplug/pciehp_hpc.c -+++ b/drivers/pci/hotplug/pciehp_hpc.c -@@ -868,11 +868,11 @@ struct controller *pcie_init(struct pcie_device *dev) - - ctrl->slot_cap = slot_cap; - mutex_init(&ctrl->ctrl_lock); -- mutex_init(&ctrl->lock); -+ mutex_init(&ctrl->state_lock); - init_rwsem(&ctrl->reset_lock); - init_waitqueue_head(&ctrl->requester); - init_waitqueue_head(&ctrl->queue); -- INIT_DELAYED_WORK(&ctrl->work, pciehp_queue_pushbutton_work); -+ INIT_DELAYED_WORK(&ctrl->button_work, pciehp_queue_pushbutton_work); - dbg_ctrl(ctrl); - - down_read(&pci_bus_sem); -@@ -921,7 +921,7 @@ struct controller *pcie_init(struct pcie_device *dev) - - void pciehp_release_ctrl(struct controller *ctrl) - { -- cancel_delayed_work_sync(&ctrl->work); -+ cancel_delayed_work_sync(&ctrl->button_work); - kfree(ctrl); - } - --- -2.33.0 - -From 30ce4d14051de5bcad38cd796e9527d2f74f8e20 Mon Sep 17 00:00:00 2001 -From: Lukas Wunner -Date: Sat, 8 Sep 2018 09:59:01 +0200 -Subject: [PATCH] PCI: pciehp: Reshuffle controller struct for clarity - -The members in pciehp's controller struct are arranged in a seemingly -arbitrary order and have grown to an amount that I no longer consider -easily graspable by contributors. - -Sort the members into 5 rubrics: -* Slot Capabilities register and quirks -* Slot Control register access -* Slot Status register event handling -* state machine -* hotplug core interface - -Obviously, this is just my personal bikeshed color and if anyone has a -better idea, please come forward. Any ordering will do as long as the -information is presented in a manageable manner. - -No functional change intended. - -Signed-off-by: Lukas Wunner -Signed-off-by: Bjorn Helgaas - -Patchset: surface-hotplug ---- - drivers/pci/hotplug/pciehp.h | 57 ++++++++++++++++++++---------------- - 1 file changed, 31 insertions(+), 26 deletions(-) - -diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h -index 2499489158cc..df82a0335515 100644 ---- a/drivers/pci/hotplug/pciehp.h -+++ b/drivers/pci/hotplug/pciehp.h -@@ -60,38 +60,38 @@ do { \ - - /** - * struct controller - PCIe hotplug controller -- * @ctrl_lock: serializes writes to the Slot Control register - * @pcie: pointer to the controller's PCIe port service device -- * @reset_lock: prevents access to the Data Link Layer Link Active bit in the -- * Link Status register and to the Presence Detect State bit in the Slot -- * Status register during a slot reset which may cause them to flap -- * @queue: wait queue to wake up on reception of a Command Completed event, -- * used for synchronous writes to the Slot Control register - * @slot_cap: cached copy of the Slot Capabilities register -+ * @link_active_reporting: cached copy of Data Link Layer Link Active Reporting -+ * Capable bit in Link Capabilities register; if this bit is zero, the -+ * Data Link Layer Link Active bit in the Link Status register will never -+ * be set and the driver is thus confined to wait 1 second before assuming -+ * the link to a hotplugged device is up and accessing it - * @slot_ctrl: cached copy of the Slot Control register -- * @poll_thread: thread to poll for slot events if no IRQ is available, -- * enabled with pciehp_poll_mode module parameter -+ * @ctrl_lock: serializes writes to the Slot Control register - * @cmd_started: jiffies when the Slot Control register was last written; - * the next write is allowed 1 second later, absent a Command Completed - * interrupt (PCIe r4.0, sec 6.7.3.2) - * @cmd_busy: flag set on Slot Control register write, cleared by IRQ handler - * on reception of a Command Completed event -- * @link_active_reporting: cached copy of Data Link Layer Link Active Reporting -- * Capable bit in Link Capabilities register; if this bit is zero, the -- * Data Link Layer Link Active bit in the Link Status register will never -- * be set and the driver is thus confined to wait 1 second before assuming -- * the link to a hotplugged device is up and accessing it -+ * @queue: wait queue to wake up on reception of a Command Completed event, -+ * used for synchronous writes to the Slot Control register -+ * @pending_events: used by the IRQ handler to save events retrieved from the -+ * Slot Status register for later consumption by the IRQ thread - * @notification_enabled: whether the IRQ was requested successfully - * @power_fault_detected: whether a power fault was detected by the hardware - * that has not yet been cleared by the user -- * @pending_events: used by the IRQ handler to save events retrieved from the -- * Slot Status register for later consumption by the IRQ thread -+ * @poll_thread: thread to poll for slot events if no IRQ is available, -+ * enabled with pciehp_poll_mode module parameter - * @state: current state machine position - * @state_lock: protects reads and writes of @state; - * protects scheduling, execution and cancellation of @button_work - * @button_work: work item to turn the slot on or off after 5 seconds - * in response to an Attention Button press - * @hotplug_slot: pointer to the structure registered with the PCI hotplug core -+ * @reset_lock: prevents access to the Data Link Layer Link Active bit in the -+ * Link Status register and to the Presence Detect State bit in the Slot -+ * Status register during a slot reset which may cause them to flap - * @ist_running: flag to keep user request waiting while IRQ thread is running - * @request_result: result of last user request submitted to the IRQ thread - * @requester: wait queue to wake up on completion of user request, -@@ -101,23 +101,28 @@ do { \ - * unlike other drivers, the two aren't represented by separate structures. - */ - struct controller { -- struct mutex ctrl_lock; - struct pcie_device *pcie; -- struct rw_semaphore reset_lock; -- wait_queue_head_t queue; -- u32 slot_cap; -- u16 slot_ctrl; -- struct task_struct *poll_thread; -- unsigned long cmd_started; /* jiffies */ -- unsigned int cmd_busy:1; -+ -+ u32 slot_cap; /* capabilities and quirks */ - unsigned int link_active_reporting:1; -+ -+ u16 slot_ctrl; /* control register access */ -+ struct mutex ctrl_lock; -+ unsigned long cmd_started; -+ unsigned int cmd_busy:1; -+ wait_queue_head_t queue; -+ -+ atomic_t pending_events; /* event handling */ - unsigned int notification_enabled:1; - unsigned int power_fault_detected; -- atomic_t pending_events; -- u8 state; -+ struct task_struct *poll_thread; -+ -+ u8 state; /* state machine */ - struct mutex state_lock; - struct delayed_work button_work; -- struct hotplug_slot *hotplug_slot; -+ -+ struct hotplug_slot *hotplug_slot; /* hotplug core interface */ -+ struct rw_semaphore reset_lock; - unsigned int ist_running; - int request_result; - wait_queue_head_t requester; --- -2.33.0 - -From 7ad8cfaf3e1081427d1bf7ab95c7878fce30f18d Mon Sep 17 00:00:00 2001 -From: Keith Busch -Date: Thu, 20 Sep 2018 10:27:17 -0600 -Subject: [PATCH] PCI: Make link active reporting detection generic - -The spec has timing requirements when waiting for a link to become active -after a conventional reset. Implement those hard delays when waiting for -an active link so pciehp and dpc drivers don't need to duplicate this. - -For devices that don't support data link layer active reporting, wait the -fixed time recommended by the PCIe spec. - -Signed-off-by: Keith Busch -[bhelgaas: changelog] -Signed-off-by: Bjorn Helgaas -Reviewed-by: Sinan Kaya - -Patchset: surface-hotplug ---- - drivers/pci/hotplug/pciehp.h | 6 ------ - drivers/pci/hotplug/pciehp_hpc.c | 22 ++------------------- - drivers/pci/pci.c | 33 ++++++++++++++++++++++++++------ - drivers/pci/pcie/dpc.c | 4 +++- - drivers/pci/probe.c | 1 + - include/linux/pci.h | 1 + - 6 files changed, 34 insertions(+), 33 deletions(-) - -diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h -index df82a0335515..4a6f46ca3b03 100644 ---- a/drivers/pci/hotplug/pciehp.h -+++ b/drivers/pci/hotplug/pciehp.h -@@ -62,11 +62,6 @@ do { \ - * struct controller - PCIe hotplug controller - * @pcie: pointer to the controller's PCIe port service device - * @slot_cap: cached copy of the Slot Capabilities register -- * @link_active_reporting: cached copy of Data Link Layer Link Active Reporting -- * Capable bit in Link Capabilities register; if this bit is zero, the -- * Data Link Layer Link Active bit in the Link Status register will never -- * be set and the driver is thus confined to wait 1 second before assuming -- * the link to a hotplugged device is up and accessing it - * @slot_ctrl: cached copy of the Slot Control register - * @ctrl_lock: serializes writes to the Slot Control register - * @cmd_started: jiffies when the Slot Control register was last written; -@@ -104,7 +99,6 @@ struct controller { - struct pcie_device *pcie; - - u32 slot_cap; /* capabilities and quirks */ -- unsigned int link_active_reporting:1; - - u16 slot_ctrl; /* control register access */ - struct mutex ctrl_lock; -diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c -index e4d6ec960630..0693870a9e24 100644 ---- a/drivers/pci/hotplug/pciehp_hpc.c -+++ b/drivers/pci/hotplug/pciehp_hpc.c -@@ -214,13 +214,6 @@ bool pciehp_check_link_active(struct controller *ctrl) - return ret; - } - --static void pcie_wait_link_active(struct controller *ctrl) --{ -- struct pci_dev *pdev = ctrl_dev(ctrl); -- -- pcie_wait_for_link(pdev, true); --} -- - static bool pci_bus_check_dev(struct pci_bus *bus, int devfn) - { - u32 l; -@@ -253,18 +246,9 @@ int pciehp_check_link_status(struct controller *ctrl) - bool found; - u16 lnk_status; - -- /* -- * Data Link Layer Link Active Reporting must be capable for -- * hot-plug capable downstream port. But old controller might -- * not implement it. In this case, we wait for 1000 ms. -- */ -- if (ctrl->link_active_reporting) -- pcie_wait_link_active(ctrl); -- else -- msleep(1000); -+ if (!pcie_wait_for_link(pdev, true)) -+ return -1; - -- /* wait 100ms before read pci conf, and try in 1s */ -- msleep(100); - found = pci_bus_check_dev(ctrl->pcie->port->subordinate, - PCI_DEVFN(0, 0)); - -@@ -881,8 +865,6 @@ struct controller *pcie_init(struct pcie_device *dev) - - /* Check if Data Link Layer Link Active Reporting is implemented */ - pcie_capability_read_dword(pdev, PCI_EXP_LNKCAP, &link_cap); -- if (link_cap & PCI_EXP_LNKCAP_DLLLARC) -- ctrl->link_active_reporting = 1; - - /* Clear all remaining event bits in Slot Status register. */ - pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, -diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c -index 9ebf32de8575..2d437c40f83b 100644 ---- a/drivers/pci/pci.c -+++ b/drivers/pci/pci.c -@@ -4536,21 +4536,42 @@ bool pcie_wait_for_link(struct pci_dev *pdev, bool active) - bool ret; - u16 lnk_status; - -+ /* -+ * Some controllers might not implement link active reporting. In this -+ * case, we wait for 1000 + 100 ms. -+ */ -+ if (!pdev->link_active_reporting) { -+ msleep(1100); -+ return true; -+ } -+ -+ /* -+ * PCIe r4.0 sec 6.6.1, a component must enter LTSSM Detect within 20ms, -+ * after which we should expect an link active if the reset was -+ * successful. If so, software must wait a minimum 100ms before sending -+ * configuration requests to devices downstream this port. -+ * -+ * If the link fails to activate, either the device was physically -+ * removed or the link is permanently failed. -+ */ -+ if (active) -+ msleep(20); - for (;;) { - pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status); - ret = !!(lnk_status & PCI_EXP_LNKSTA_DLLLA); - if (ret == active) -- return true; -+ break; - if (timeout <= 0) - break; - msleep(10); - timeout -= 10; - } -- -- pci_info(pdev, "Data Link Layer Link Active not %s in 1000 msec\n", -- active ? "set" : "cleared"); -- -- return false; -+ if (active && ret) -+ msleep(100); -+ else if (ret != active) -+ pci_info(pdev, "Data Link Layer Link Active not %s in 1000 msec\n", -+ active ? "set" : "cleared"); -+ return ret == active; - } - - void pci_reset_secondary_bus(struct pci_dev *dev) -diff --git a/drivers/pci/pcie/dpc.c b/drivers/pci/pcie/dpc.c -index 118b5bcae42e..87f8d8628d94 100644 ---- a/drivers/pci/pcie/dpc.c -+++ b/drivers/pci/pcie/dpc.c -@@ -93,10 +93,12 @@ static pci_ers_result_t dpc_reset_link(struct pci_dev *pdev) - pci_write_config_word(pdev, cap + PCI_EXP_DPC_STATUS, - PCI_EXP_DPC_STATUS_TRIGGER); - -+ if (!pcie_wait_for_link(pdev, true)) -+ return PCI_ERS_RESULT_DISCONNECT; -+ - return PCI_ERS_RESULT_RECOVERED; - } - -- - static void dpc_process_rp_pio_error(struct dpc_dev *dpc) - { - struct device *dev = &dpc->dev->device; -diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c -index 113b7bdf86dd..79ca1a28b991 100644 ---- a/drivers/pci/probe.c -+++ b/drivers/pci/probe.c -@@ -769,6 +769,7 @@ static void pci_set_bus_speed(struct pci_bus *bus) - - pcie_capability_read_dword(bridge, PCI_EXP_LNKCAP, &linkcap); - bus->max_bus_speed = pcie_link_speed[linkcap & PCI_EXP_LNKCAP_SLS]; -+ bridge->link_active_reporting = !!(linkcap & PCI_EXP_LNKCAP_DLLLARC); - - pcie_capability_read_word(bridge, PCI_EXP_LNKSTA, &linksta); - pcie_update_link_speed(bus, linksta); -diff --git a/include/linux/pci.h b/include/linux/pci.h -index ec6c48ecd7d5..74c8e9190fed 100644 ---- a/include/linux/pci.h -+++ b/include/linux/pci.h -@@ -407,6 +407,7 @@ struct pci_dev { - unsigned int has_secondary_link:1; - unsigned int non_compliant_bars:1; /* Broken BARs; ignore them */ - unsigned int is_probed:1; /* Device probing in progress */ -+ unsigned int link_active_reporting:1;/* Device capable of reporting link active */ - pci_dev_flags_t dev_flags; - atomic_t enable_cnt; /* pci_enable_device has been called */ - --- -2.33.0 - -From 9f416805546783005a9a0ee5762cbf9e7e5f0f22 Mon Sep 17 00:00:00 2001 -From: Mika Westerberg -Date: Thu, 27 Sep 2018 16:53:53 -0500 -Subject: [PATCH] PCI: Do not skip power-managed bridges in pci_enable_wake() - -Commit baecc470d5fd ("PCI / PM: Skip bridges in pci_enable_wake()") changed -pci_enable_wake() so that all bridges are skipped when wakeup is enabled -(or disabled) with the reasoning that bridges can only signal wakeup on -behalf of their subordinate devices. - -However, there are bridges that can signal wakeup themselves. For example -PCIe downstream and root ports supporting hotplug may signal wakeup upon -hotplug event. - -For this reason change pci_enable_wake() so that it skips all bridges -except those that we power manage (->bridge_d3 is set). Those are the ones -that can go into low power states and may need to signal wakeup. - -Signed-off-by: Mika Westerberg -Signed-off-by: Bjorn Helgaas -Reviewed-by: Rafael J. Wysocki - -Patchset: surface-hotplug ---- - drivers/pci/pci.c | 9 ++++++--- - 1 file changed, 6 insertions(+), 3 deletions(-) - -diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c -index 2d437c40f83b..4a874e8fa1d5 100644 ---- a/drivers/pci/pci.c -+++ b/drivers/pci/pci.c -@@ -2151,10 +2151,13 @@ static int __pci_enable_wake(struct pci_dev *dev, pci_power_t state, bool enable - int ret = 0; - - /* -- * Bridges can only signal wakeup on behalf of subordinate devices, -- * but that is set up elsewhere, so skip them. -+ * Bridges that are not power-manageable directly only signal -+ * wakeup on behalf of subordinate devices which is set up -+ * elsewhere, so skip them. However, bridges that are -+ * power-manageable may signal wakeup for themselves (for example, -+ * on a hotplug event) and they need to be covered here. - */ -- if (pci_has_subordinate(dev)) -+ if (!pci_power_manageable(dev)) - return 0; - - /* Don't do the same thing twice in a row for one device. */ --- -2.33.0 - -From 0cda3ae7ae8ec40180f6019552e95d7ce4900089 Mon Sep 17 00:00:00 2001 -From: Mika Westerberg -Date: Thu, 27 Sep 2018 16:38:19 -0500 -Subject: [PATCH] PCI: pciehp: Disable hotplug interrupt during suspend - -When PCIe hotplug port is transitioned into D3hot, the link to the -downstream component will go down. If hotplug interrupt generation is -enabled when that happens, it will trigger immediately, waking up the -system and bringing the link back up. - -To prevent this, disable hotplug interrupt generation when system suspend -is entered. This does not prevent wakeup from low power states according -to PCIe 4.0 spec section 6.7.3.4: - - Software enables a hot-plug event to generate a wakeup event by - enabling software notification of the event as described in Section - 6.7.3.1. Note that in order for software to disable interrupt generation - while keeping wakeup generation enabled, the Hot-Plug Interrupt Enable - bit must be cleared. - -So as long as we have set the slot event mask accordingly, wakeup should -work even if slot interrupt is disabled. The port should trigger wake and -then send PME to the root port when the PCIe hierarchy is brought back up. - -Limit this to systems using native PME mechanism to make sure older Apple -systems depending on commit e3354628c376 ("PCI: pciehp: Support interrupts -sent from D3hot") still continue working. - -Signed-off-by: Mika Westerberg -Signed-off-by: Bjorn Helgaas -Reviewed-by: Rafael J. Wysocki - -Patchset: surface-hotplug ---- - drivers/pci/hotplug/pciehp.h | 2 ++ - drivers/pci/hotplug/pciehp_core.c | 18 ++++++++++++++++++ - drivers/pci/hotplug/pciehp_hpc.c | 10 ++++++++++ - 3 files changed, 30 insertions(+) - -diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h -index 4a6f46ca3b03..80d3f76c1193 100644 ---- a/drivers/pci/hotplug/pciehp.h -+++ b/drivers/pci/hotplug/pciehp.h -@@ -178,6 +178,8 @@ struct controller *pcie_init(struct pcie_device *dev); - int pcie_init_notification(struct controller *ctrl); - void pcie_shutdown_notification(struct controller *ctrl); - void pcie_clear_hotplug_events(struct controller *ctrl); -+void pcie_enable_interrupt(struct controller *ctrl); -+void pcie_disable_interrupt(struct controller *ctrl); - int pciehp_power_on_slot(struct controller *ctrl); - void pciehp_power_off_slot(struct controller *ctrl); - void pciehp_get_power_status(struct controller *ctrl, u8 *status); -diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c -index 7810000522dd..8e6e4ce869fb 100644 ---- a/drivers/pci/hotplug/pciehp_core.c -+++ b/drivers/pci/hotplug/pciehp_core.c -@@ -261,8 +261,23 @@ static void pciehp_remove(struct pcie_device *dev) - } - - #ifdef CONFIG_PM -+static bool pme_is_native(struct pcie_device *dev) -+{ -+ const struct pci_host_bridge *host; -+ -+ host = pci_find_host_bridge(dev->port->bus); -+ return pcie_ports_native || host->native_pme; -+} -+ - static int pciehp_suspend(struct pcie_device *dev) - { -+ /* -+ * Disable hotplug interrupt so that it does not trigger -+ * immediately when the downstream link goes down. -+ */ -+ if (pme_is_native(dev)) -+ pcie_disable_interrupt(get_service_data(dev)); -+ - return 0; - } - -@@ -285,6 +300,9 @@ static int pciehp_resume(struct pcie_device *dev) - { - struct controller *ctrl = get_service_data(dev); - -+ if (pme_is_native(dev)) -+ pcie_enable_interrupt(ctrl); -+ - pciehp_check_presence(ctrl); - - return 0; -diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c -index 0693870a9e24..b5c7f5ef597a 100644 ---- a/drivers/pci/hotplug/pciehp_hpc.c -+++ b/drivers/pci/hotplug/pciehp_hpc.c -@@ -748,6 +748,16 @@ void pcie_clear_hotplug_events(struct controller *ctrl) - PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); - } - -+void pcie_enable_interrupt(struct controller *ctrl) -+{ -+ pcie_write_cmd(ctrl, PCI_EXP_SLTCTL_HPIE, PCI_EXP_SLTCTL_HPIE); -+} -+ -+void pcie_disable_interrupt(struct controller *ctrl) -+{ -+ pcie_write_cmd(ctrl, 0, PCI_EXP_SLTCTL_HPIE); -+} -+ - /* - * pciehp has a 1:1 bus:slot relationship so we ultimately want a secondary - * bus reset of the bridge, but at the same time we want to ensure that it is --- -2.33.0 - -From 13e6690919312b21b54bef2585e94d57b9dd6a75 Mon Sep 17 00:00:00 2001 -From: Mika Westerberg -Date: Thu, 27 Sep 2018 16:41:46 -0500 -Subject: [PATCH] PCI: pciehp: Do not handle events if interrupts are masked - -PCIe native hotplug shares MSI vector with native PME so the interrupt -handler might get called even the hotplug interrupt is masked. In that case -we should not handle any events because the interrupt was not meant for us. - -Modify the PCIe hotplug interrupt handler to check this accordingly and -bail out if it finds out that the interrupt was not about hotplug. - -Signed-off-by: Mika Westerberg -Signed-off-by: Bjorn Helgaas -Reviewed-by: Lukas Wunner - -Patchset: surface-hotplug ---- - drivers/pci/hotplug/pciehp_hpc.c | 6 ++++-- - 1 file changed, 4 insertions(+), 2 deletions(-) - -diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c -index b5c7f5ef597a..242b9f30210a 100644 ---- a/drivers/pci/hotplug/pciehp_hpc.c -+++ b/drivers/pci/hotplug/pciehp_hpc.c -@@ -518,9 +518,11 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id) - u16 status, events = 0; - - /* -- * Interrupts only occur in D3hot or shallower (PCIe r4.0, sec 6.7.3.4). -+ * Interrupts only occur in D3hot or shallower and only if enabled -+ * in the Slot Control register (PCIe r4.0, sec 6.7.3.4). - */ -- if (pdev->current_state == PCI_D3cold) -+ if (pdev->current_state == PCI_D3cold || -+ (!(ctrl->slot_ctrl & PCI_EXP_SLTCTL_HPIE) && !pciehp_poll_mode)) - return IRQ_NONE; - - /* --- -2.33.0 - -From 07af128ff0d589536dbd523b944ec4942b1380d0 Mon Sep 17 00:00:00 2001 -From: Mika Westerberg -Date: Thu, 27 Sep 2018 16:41:47 -0500 -Subject: [PATCH] PCI/portdrv: Resume upon exit from system suspend if left - runtime suspended - -Currently we try to keep PCIe ports runtime suspended over system suspend -if possible. This mostly happens when entering suspend-to-idle because -there is no need to re-configure wake settings. - -This causes problems if the parent port goes into D3cold and it gets -resumed upon exit from system suspend. This may happen for example if the -port is part of PCIe switch and the same switch is connected to a PCIe -endpoint that needs to be resumed. The way exit from D3cold works according -PCIe 4.0 spec 5.3.1.4.2 is that power is restored and cold reset is -signaled. After this the device is in D0unitialized state keeping PME -context if it supports wake from D3cold. - -The problem occurs when a PCIe hotplug port is left suspended and the -parent port goes into D3cold and back to D0: the port keeps its PME context -but since everything else is reset back to defaults (D0unitialized) it is -not set to detect hotplug events anymore. - -For this reason change the PCIe portdrv power management logic so that it -is fine to keep the port runtime suspended over system suspend but it needs -to be resumed upon exit to make sure it gets properly re-initialized. - -Signed-off-by: Mika Westerberg -Signed-off-by: Bjorn Helgaas - -Patchset: surface-hotplug ---- - drivers/pci/pcie/portdrv_pci.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c -index 23a5a0c2c3fe..5badf8a1ce0a 100644 ---- a/drivers/pci/pcie/portdrv_pci.c -+++ b/drivers/pci/pcie/portdrv_pci.c -@@ -109,8 +109,8 @@ static int pcie_portdrv_probe(struct pci_dev *dev, - - pci_save_state(dev); - -- dev_pm_set_driver_flags(&dev->dev, DPM_FLAG_SMART_SUSPEND | -- DPM_FLAG_LEAVE_SUSPENDED); -+ dev_pm_set_driver_flags(&dev->dev, DPM_FLAG_NEVER_SKIP | -+ DPM_FLAG_SMART_SUSPEND); - - if (pci_bridge_d3_possible(dev)) { - /* --- -2.33.0 - -From a0c6895a935960370f4109ac525bd5691e160dd5 Mon Sep 17 00:00:00 2001 -From: Mika Westerberg -Date: Thu, 27 Sep 2018 16:41:48 -0500 -Subject: [PATCH] PCI/portdrv: Add runtime PM hooks for port service drivers - -When PCIe port is runtime suspended/resumed some extra steps might be -needed to be executed from the port service driver side. For instance we -may need to disable PCIe hotplug interrupt to prevent it from triggering -immediately when PCIe link to the downstream component goes down. - -To make the above possible add optional ->runtime_suspend() and -->runtime_resume() callbacks to struct pcie_port_service_driver and call -them for each port service in runtime suspend/resume callbacks of portdrv. - -Signed-off-by: Mika Westerberg -[bhelgaas: adjust "slot->state" for 5790a9c78e78 ("PCI: pciehp: Unify -controller and slot structs")] -Signed-off-by: Bjorn Helgaas -Reviewed-by: Rafael J. Wysocki - -Patchset: surface-hotplug ---- - drivers/pci/pcie/portdrv.h | 4 ++++ - drivers/pci/pcie/portdrv_core.c | 20 ++++++++++++++++++++ - drivers/pci/pcie/portdrv_pci.c | 10 ++++------ - 3 files changed, 28 insertions(+), 6 deletions(-) - -diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h -index 2498b2d34009..abfdc2ae7979 100644 ---- a/drivers/pci/pcie/portdrv.h -+++ b/drivers/pci/pcie/portdrv.h -@@ -76,6 +76,8 @@ struct pcie_port_service_driver { - int (*suspend) (struct pcie_device *dev); - int (*resume_noirq) (struct pcie_device *dev); - int (*resume) (struct pcie_device *dev); -+ int (*runtime_suspend) (struct pcie_device *dev); -+ int (*runtime_resume) (struct pcie_device *dev); - - /* Device driver may resume normal operations */ - void (*error_resume)(struct pci_dev *dev); -@@ -109,6 +111,8 @@ int pcie_port_device_register(struct pci_dev *dev); - int pcie_port_device_suspend(struct device *dev); - int pcie_port_device_resume_noirq(struct device *dev); - int pcie_port_device_resume(struct device *dev); -+int pcie_port_device_runtime_suspend(struct device *dev); -+int pcie_port_device_runtime_resume(struct device *dev); - #endif - void pcie_port_device_remove(struct pci_dev *dev); - int __must_check pcie_port_bus_register(void); -diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c -index 7c37d815229e..6542c48c7f59 100644 ---- a/drivers/pci/pcie/portdrv_core.c -+++ b/drivers/pci/pcie/portdrv_core.c -@@ -395,6 +395,26 @@ int pcie_port_device_resume(struct device *dev) - size_t off = offsetof(struct pcie_port_service_driver, resume); - return device_for_each_child(dev, &off, pm_iter); - } -+ -+/** -+ * pcie_port_device_runtime_suspend - runtime suspend port services -+ * @dev: PCI Express port to handle -+ */ -+int pcie_port_device_runtime_suspend(struct device *dev) -+{ -+ size_t off = offsetof(struct pcie_port_service_driver, runtime_suspend); -+ return device_for_each_child(dev, &off, pm_iter); -+} -+ -+/** -+ * pcie_port_device_runtime_resume - runtime resume port services -+ * @dev: PCI Express port to handle -+ */ -+int pcie_port_device_runtime_resume(struct device *dev) -+{ -+ size_t off = offsetof(struct pcie_port_service_driver, runtime_resume); -+ return device_for_each_child(dev, &off, pm_iter); -+} - #endif /* PM */ - - static int remove_iter(struct device *dev, void *data) -diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c -index 5badf8a1ce0a..59d2567e2db2 100644 ---- a/drivers/pci/pcie/portdrv_pci.c -+++ b/drivers/pci/pcie/portdrv_pci.c -@@ -45,12 +45,10 @@ __setup("pcie_ports=", pcie_port_setup); - #ifdef CONFIG_PM - static int pcie_port_runtime_suspend(struct device *dev) - { -- return to_pci_dev(dev)->bridge_d3 ? 0 : -EBUSY; --} -+ if (!to_pci_dev(dev)->bridge_d3) -+ return -EBUSY; - --static int pcie_port_runtime_resume(struct device *dev) --{ -- return 0; -+ return pcie_port_device_runtime_suspend(dev); - } - - static int pcie_port_runtime_idle(struct device *dev) -@@ -73,7 +71,7 @@ static const struct dev_pm_ops pcie_portdrv_pm_ops = { - .restore_noirq = pcie_port_device_resume_noirq, - .restore = pcie_port_device_resume, - .runtime_suspend = pcie_port_runtime_suspend, -- .runtime_resume = pcie_port_runtime_resume, -+ .runtime_resume = pcie_port_device_runtime_resume, - .runtime_idle = pcie_port_runtime_idle, - }; - --- -2.33.0 - -From 4f1cb78419da17be25ef12927a7d36e6e88ac5c2 Mon Sep 17 00:00:00 2001 -From: Mika Westerberg -Date: Thu, 27 Sep 2018 16:41:49 -0500 -Subject: [PATCH] PCI: pciehp: Implement runtime PM callbacks - -Basically we need to do the same thing when runtime suspending than with -system sleep so re-use those operations here. This makes sure hotplug -interrupt does not trigger immediately when the link goes down. - -Signed-off-by: Mika Westerberg -Signed-off-by: Bjorn Helgaas -Reviewed-by: Rafael J. Wysocki - -Patchset: surface-hotplug ---- - drivers/pci/hotplug/pciehp_core.c | 18 ++++++++++++++++++ - 1 file changed, 18 insertions(+) - -diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c -index 8e6e4ce869fb..e5de25ebc4cf 100644 ---- a/drivers/pci/hotplug/pciehp_core.c -+++ b/drivers/pci/hotplug/pciehp_core.c -@@ -307,6 +307,22 @@ static int pciehp_resume(struct pcie_device *dev) - - return 0; - } -+ -+static int pciehp_runtime_resume(struct pcie_device *dev) -+{ -+ struct controller *ctrl = get_service_data(dev); -+ -+ /* pci_restore_state() just wrote to the Slot Control register */ -+ ctrl->cmd_started = jiffies; -+ ctrl->cmd_busy = true; -+ -+ /* clear spurious events from rediscovery of inserted card */ -+ if ((ctrl->state == ON_STATE || ctrl->state == BLINKINGOFF_STATE) && -+ pme_is_native(dev)) -+ pcie_clear_hotplug_events(ctrl); -+ -+ return pciehp_resume(dev); -+} - #endif /* PM */ - - static struct pcie_port_service_driver hpdriver_portdrv = { -@@ -321,6 +337,8 @@ static struct pcie_port_service_driver hpdriver_portdrv = { - .suspend = pciehp_suspend, - .resume_noirq = pciehp_resume_noirq, - .resume = pciehp_resume, -+ .runtime_suspend = pciehp_suspend, -+ .runtime_resume = pciehp_runtime_resume, - #endif /* PM */ - }; - --- -2.33.0 - -From 9541f17a98ab418042a5a4053b6ac3cf2299390d Mon Sep 17 00:00:00 2001 -From: Mika Westerberg -Date: Thu, 27 Sep 2018 16:57:05 -0500 -Subject: [PATCH] ACPI / property: Allow multiple property compatible _DSD - entries - -It is possible to have _DSD entries where the data is compatible with -device properties format but are using different GUID for various reasons. -In addition to that there can be many such _DSD entries for a single device -such as for PCIe root port used to host a Thunderbolt hierarchy: - - Scope (\_SB.PCI0.RP21) - { - Name (_DSD, Package () { - ToUUID ("6211e2c0-58a3-4af3-90e1-927a4e0c55a4"), - Package () { - Package () {"HotPlugSupportInD3", 1} - }, - - ToUUID ("efcc06cc-73ac-4bc3-bff0-76143807c389"), - Package () { - Package () {"ExternalFacingPort", 1}, - Package () {"UID", 0 } - } - }) - } - -More information about these new _DSD entries can be found in: - - https://docs.microsoft.com/en-us/windows-hardware/drivers/pci/dsd-for-pcie-root-ports - -To make these available for drivers via unified device property APIs, -modify ACPI property core so that it supports multiple _DSD entries -organized in a linked list. We also store GUID of each _DSD entry in struct -acpi_device_properties in case there is need to differentiate between -entries. The supported GUIDs are then listed in prp_guids array. - -Signed-off-by: Mika Westerberg -Signed-off-by: Bjorn Helgaas -Reviewed-by: Rafael J. Wysocki -Acked-by: Sakari Ailus - -Patchset: surface-hotplug ---- - drivers/acpi/property.c | 94 +++++++++++++++++++++++++++---------- - drivers/acpi/x86/apple.c | 2 +- - drivers/gpio/gpiolib-acpi.c | 2 +- - include/acpi/acpi_bus.h | 8 +++- - include/linux/acpi.h | 9 ++++ - 5 files changed, 86 insertions(+), 29 deletions(-) - -diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c -index 27db1a968241..9a679ae50089 100644 ---- a/drivers/acpi/property.c -+++ b/drivers/acpi/property.c -@@ -24,11 +24,12 @@ static int acpi_data_get_property_array(const struct acpi_device_data *data, - acpi_object_type type, - const union acpi_object **obj); - --/* ACPI _DSD device properties GUID: daffd814-6eba-4d8c-8a91-bc9bbf4aa301 */ --static const guid_t prp_guid = -+static const guid_t prp_guids[] = { -+ /* ACPI _DSD device properties GUID: daffd814-6eba-4d8c-8a91-bc9bbf4aa301 */ - GUID_INIT(0xdaffd814, 0x6eba, 0x4d8c, -- 0x8a, 0x91, 0xbc, 0x9b, 0xbf, 0x4a, 0xa3, 0x01); --/* ACPI _DSD data subnodes GUID: dbb8e3e6-5886-4ba6-8795-1319f52a966b */ -+ 0x8a, 0x91, 0xbc, 0x9b, 0xbf, 0x4a, 0xa3, 0x01), -+}; -+ - static const guid_t ads_guid = - GUID_INIT(0xdbb8e3e6, 0x5886, 0x4ba6, - 0x87, 0x95, 0x13, 0x19, 0xf5, 0x2a, 0x96, 0x6b); -@@ -56,6 +57,7 @@ static bool acpi_nondev_subnode_extract(const union acpi_object *desc, - dn->name = link->package.elements[0].string.pointer; - dn->fwnode.ops = &acpi_data_fwnode_ops; - dn->parent = parent; -+ INIT_LIST_HEAD(&dn->data.properties); - INIT_LIST_HEAD(&dn->data.subnodes); - - result = acpi_extract_properties(desc, &dn->data); -@@ -288,6 +290,35 @@ static void acpi_init_of_compatible(struct acpi_device *adev) - adev->flags.of_compatible_ok = 1; - } - -+static bool acpi_is_property_guid(const guid_t *guid) -+{ -+ int i; -+ -+ for (i = 0; i < ARRAY_SIZE(prp_guids); i++) { -+ if (guid_equal(guid, &prp_guids[i])) -+ return true; -+ } -+ -+ return false; -+} -+ -+struct acpi_device_properties * -+acpi_data_add_props(struct acpi_device_data *data, const guid_t *guid, -+ const union acpi_object *properties) -+{ -+ struct acpi_device_properties *props; -+ -+ props = kzalloc(sizeof(*props), GFP_KERNEL); -+ if (props) { -+ INIT_LIST_HEAD(&props->list); -+ props->guid = guid; -+ props->properties = properties; -+ list_add_tail(&props->list, &data->properties); -+ } -+ -+ return props; -+} -+ - static bool acpi_extract_properties(const union acpi_object *desc, - struct acpi_device_data *data) - { -@@ -312,7 +343,7 @@ static bool acpi_extract_properties(const union acpi_object *desc, - properties->type != ACPI_TYPE_PACKAGE) - break; - -- if (!guid_equal((guid_t *)guid->buffer.pointer, &prp_guid)) -+ if (!acpi_is_property_guid((guid_t *)guid->buffer.pointer)) - continue; - - /* -@@ -320,13 +351,13 @@ static bool acpi_extract_properties(const union acpi_object *desc, - * package immediately following it. - */ - if (!acpi_properties_format_valid(properties)) -- break; -+ continue; - -- data->properties = properties; -- return true; -+ acpi_data_add_props(data, (const guid_t *)guid->buffer.pointer, -+ properties); - } - -- return false; -+ return !list_empty(&data->properties); - } - - void acpi_init_properties(struct acpi_device *adev) -@@ -336,6 +367,7 @@ void acpi_init_properties(struct acpi_device *adev) - acpi_status status; - bool acpi_of = false; - -+ INIT_LIST_HEAD(&adev->data.properties); - INIT_LIST_HEAD(&adev->data.subnodes); - - if (!adev->handle) -@@ -398,11 +430,16 @@ static void acpi_destroy_nondev_subnodes(struct list_head *list) - - void acpi_free_properties(struct acpi_device *adev) - { -+ struct acpi_device_properties *props, *tmp; -+ - acpi_destroy_nondev_subnodes(&adev->data.subnodes); - ACPI_FREE((void *)adev->data.pointer); - adev->data.of_compatible = NULL; - adev->data.pointer = NULL; -- adev->data.properties = NULL; -+ list_for_each_entry_safe(props, tmp, &adev->data.properties, list) { -+ list_del(&props->list); -+ kfree(props); -+ } - } - - /** -@@ -427,32 +464,37 @@ static int acpi_data_get_property(const struct acpi_device_data *data, - const char *name, acpi_object_type type, - const union acpi_object **obj) - { -- const union acpi_object *properties; -- int i; -+ const struct acpi_device_properties *props; - - if (!data || !name) - return -EINVAL; - -- if (!data->pointer || !data->properties) -+ if (!data->pointer || list_empty(&data->properties)) - return -EINVAL; - -- properties = data->properties; -- for (i = 0; i < properties->package.count; i++) { -- const union acpi_object *propname, *propvalue; -- const union acpi_object *property; -+ list_for_each_entry(props, &data->properties, list) { -+ const union acpi_object *properties; -+ unsigned int i; - -- property = &properties->package.elements[i]; -+ properties = props->properties; -+ for (i = 0; i < properties->package.count; i++) { -+ const union acpi_object *propname, *propvalue; -+ const union acpi_object *property; - -- propname = &property->package.elements[0]; -- propvalue = &property->package.elements[1]; -+ property = &properties->package.elements[i]; - -- if (!strcmp(name, propname->string.pointer)) { -- if (type != ACPI_TYPE_ANY && propvalue->type != type) -- return -EPROTO; -- if (obj) -- *obj = propvalue; -+ propname = &property->package.elements[0]; -+ propvalue = &property->package.elements[1]; - -- return 0; -+ if (!strcmp(name, propname->string.pointer)) { -+ if (type != ACPI_TYPE_ANY && -+ propvalue->type != type) -+ return -EPROTO; -+ if (obj) -+ *obj = propvalue; -+ -+ return 0; -+ } - } - } - return -EINVAL; -diff --git a/drivers/acpi/x86/apple.c b/drivers/acpi/x86/apple.c -index 51b4cf9f25da..130df1c8ed7d 100644 ---- a/drivers/acpi/x86/apple.c -+++ b/drivers/acpi/x86/apple.c -@@ -132,8 +132,8 @@ void acpi_extract_apple_properties(struct acpi_device *adev) - } - WARN_ON(free_space != (void *)newprops + newsize); - -- adev->data.properties = newprops; - adev->data.pointer = newprops; -+ acpi_data_add_props(&adev->data, &apple_prp_guid, newprops); - - out_free: - ACPI_FREE(props); -diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c -index b018909a4e46..ebe5245ed6c3 100644 ---- a/drivers/gpio/gpiolib-acpi.c -+++ b/drivers/gpio/gpiolib-acpi.c -@@ -1300,7 +1300,7 @@ int acpi_gpio_count(struct device *dev, const char *con_id) - bool acpi_can_fallback_to_crs(struct acpi_device *adev, const char *con_id) - { - /* Never allow fallback if the device has properties */ -- if (adev->data.properties || adev->driver_gpios) -+ if (acpi_dev_has_props(adev) || adev->driver_gpios) - return false; - - return con_id == NULL; -diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h -index 8b19618bad0a..af01d052f736 100644 ---- a/include/acpi/acpi_bus.h -+++ b/include/acpi/acpi_bus.h -@@ -347,10 +347,16 @@ struct acpi_device_physical_node { - bool put_online:1; - }; - -+struct acpi_device_properties { -+ const guid_t *guid; -+ const union acpi_object *properties; -+ struct list_head list; -+}; -+ - /* ACPI Device Specific Data (_DSD) */ - struct acpi_device_data { - const union acpi_object *pointer; -- const union acpi_object *properties; -+ struct list_head properties; - const union acpi_object *of_compatible; - struct list_head subnodes; - }; -diff --git a/include/linux/acpi.h b/include/linux/acpi.h -index 1a37748766b7..50a09003bb43 100644 ---- a/include/linux/acpi.h -+++ b/include/linux/acpi.h -@@ -1083,6 +1083,15 @@ static inline int acpi_node_get_property_reference( - NR_FWNODE_REFERENCE_ARGS, args); - } - -+static inline bool acpi_dev_has_props(const struct acpi_device *adev) -+{ -+ return !list_empty(&adev->data.properties); -+} -+ -+struct acpi_device_properties * -+acpi_data_add_props(struct acpi_device_data *data, const guid_t *guid, -+ const union acpi_object *properties); -+ - int acpi_node_prop_get(const struct fwnode_handle *fwnode, const char *propname, - void **valptr); - int acpi_dev_prop_read_single(struct acpi_device *adev, --- -2.33.0 - -From eb0dcd2b98c1bf73188056ec38f1d959bb43c55a Mon Sep 17 00:00:00 2001 -From: Mika Westerberg -Date: Thu, 27 Sep 2018 16:57:14 -0500 -Subject: [PATCH] PCI / ACPI: Whitelist D3 for more PCIe hotplug ports - -In order to have better power management for Thunderbolt PCIe chains, -Windows enables power management for native PCIe hotplug ports if there is -the following ACPI _DSD attached to the root port: - - Name (_DSD, Package () { - ToUUID ("6211e2c0-58a3-4af3-90e1-927a4e0c55a4"), - Package () { - Package () {"HotPlugSupportInD3", 1} - } - }) - -This is also documented in: - - https://docs.microsoft.com/en-us/windows-hardware/drivers/pci/dsd-for-pcie-root-ports#identifying-pcie-root-ports-supporting-hot-plug-in-d3 - -Do the same in Linux by introducing new firmware PM callback -(->bridge_d3()) and then implement it for ACPI based systems so that the -above property is checked. - -There is one catch, though. The initial pci_dev->bridge_d3 is set before -the root port has ACPI companion bound (the device is not added to the PCI -bus either) so we need to look up the ACPI companion manually in that case -in acpi_pci_bridge_d3(). - -Signed-off-by: Mika Westerberg -Signed-off-by: Bjorn Helgaas -Reviewed-by: Rafael J. Wysocki - -Patchset: surface-hotplug ---- - drivers/acpi/property.c | 3 +++ - drivers/pci/pci-acpi.c | 41 +++++++++++++++++++++++++++++++++++++++++ - drivers/pci/pci.c | 9 +++++++++ - drivers/pci/pci.h | 3 +++ - 4 files changed, 56 insertions(+) - -diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c -index 9a679ae50089..6497f04f9551 100644 ---- a/drivers/acpi/property.c -+++ b/drivers/acpi/property.c -@@ -28,6 +28,9 @@ static const guid_t prp_guids[] = { - /* ACPI _DSD device properties GUID: daffd814-6eba-4d8c-8a91-bc9bbf4aa301 */ - GUID_INIT(0xdaffd814, 0x6eba, 0x4d8c, - 0x8a, 0x91, 0xbc, 0x9b, 0xbf, 0x4a, 0xa3, 0x01), -+ /* Hotplug in D3 GUID: 6211e2c0-58a3-4af3-90e1-927a4e0c55a4 */ -+ GUID_INIT(0x6211e2c0, 0x58a3, 0x4af3, -+ 0x90, 0xe1, 0x92, 0x7a, 0x4e, 0x0c, 0x55, 0xa4), - }; - - static const guid_t ads_guid = -diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c -index 2c46f7dcd2f5..1516327a8459 100644 ---- a/drivers/pci/pci-acpi.c -+++ b/drivers/pci/pci-acpi.c -@@ -519,6 +519,46 @@ static pci_power_t acpi_pci_choose_state(struct pci_dev *pdev) - return PCI_POWER_ERROR; - } - -+static struct acpi_device *acpi_pci_find_companion(struct device *dev); -+ -+static bool acpi_pci_bridge_d3(struct pci_dev *dev) -+{ -+ const struct fwnode_handle *fwnode; -+ struct acpi_device *adev; -+ struct pci_dev *root; -+ u8 val; -+ -+ if (!dev->is_hotplug_bridge) -+ return false; -+ -+ /* -+ * Look for a special _DSD property for the root port and if it -+ * is set we know the hierarchy behind it supports D3 just fine. -+ */ -+ root = pci_find_pcie_root_port(dev); -+ if (!root) -+ return false; -+ -+ adev = ACPI_COMPANION(&root->dev); -+ if (root == dev) { -+ /* -+ * It is possible that the ACPI companion is not yet bound -+ * for the root port so look it up manually here. -+ */ -+ if (!adev && !pci_dev_is_added(root)) -+ adev = acpi_pci_find_companion(&root->dev); -+ } -+ -+ if (!adev) -+ return false; -+ -+ fwnode = acpi_fwnode_handle(adev); -+ if (fwnode_property_read_u8(fwnode, "HotPlugSupportInD3", &val)) -+ return false; -+ -+ return val == 1; -+} -+ - static bool acpi_pci_power_manageable(struct pci_dev *dev) - { - struct acpi_device *adev = ACPI_COMPANION(&dev->dev); -@@ -636,6 +676,7 @@ static bool acpi_pci_need_resume(struct pci_dev *dev) - } - - static const struct pci_platform_pm_ops acpi_pci_platform_pm = { -+ .bridge_d3 = acpi_pci_bridge_d3, - .is_manageable = acpi_pci_power_manageable, - .set_state = acpi_pci_set_power_state, - .get_state = acpi_pci_get_power_state, -diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c -index 4a874e8fa1d5..a60eb5780cc0 100644 ---- a/drivers/pci/pci.c -+++ b/drivers/pci/pci.c -@@ -793,6 +793,11 @@ static inline bool platform_pci_need_resume(struct pci_dev *dev) - return pci_platform_pm ? pci_platform_pm->need_resume(dev) : false; - } - -+static inline bool platform_pci_bridge_d3(struct pci_dev *dev) -+{ -+ return pci_platform_pm ? pci_platform_pm->bridge_d3(dev) : false; -+} -+ - /** - * pci_raw_set_power_state - Use PCI PM registers to set the power state of - * given PCI device -@@ -2551,6 +2556,10 @@ bool pci_bridge_d3_possible(struct pci_dev *bridge) - if (bridge->is_thunderbolt) - return true; - -+ /* Platform might know better if the bridge supports D3 */ -+ if (platform_pci_bridge_d3(bridge)) -+ return true; -+ - /* - * Hotplug ports handled natively by the OS were not validated - * by vendors for runtime D3 at least until 2018 because there -diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h -index 39725b71300f..ee3e94c6ea6c 100644 ---- a/drivers/pci/pci.h -+++ b/drivers/pci/pci.h -@@ -40,6 +40,8 @@ int pci_bus_error_reset(struct pci_dev *dev); - /** - * struct pci_platform_pm_ops - Firmware PM callbacks - * -+ * @bridge_d3: Does the bridge allow entering into D3 -+ * - * @is_manageable: returns 'true' if given device is power manageable by the - * platform firmware - * -@@ -61,6 +63,7 @@ int pci_bus_error_reset(struct pci_dev *dev); - * these callbacks are mandatory. - */ - struct pci_platform_pm_ops { -+ bool (*bridge_d3)(struct pci_dev *dev); - bool (*is_manageable)(struct pci_dev *dev); - int (*set_state)(struct pci_dev *dev, pci_power_t state); - pci_power_t (*get_state)(struct pci_dev *dev); --- -2.33.0 - -From 3072251f32aaa54852d19bff828f6deaad80679d Mon Sep 17 00:00:00 2001 -From: Mika Westerberg -Date: Mon, 7 Jan 2019 16:09:40 +0300 -Subject: [PATCH] PCI: pciehp: Assign ctrl->slot_ctrl before writing it to - hardware - -Shameerali reported that running v4.20-rc1 as QEMU guest, the PCIe hotplug -port times out during boot: - - pciehp 0000:00:01.0:pcie004: Timeout on hotplug command 0x03f1 (issued 1016 msec ago) - pciehp 0000:00:01.0:pcie004: Timeout on hotplug command 0x03f1 (issued 1024 msec ago) - pciehp 0000:00:01.0:pcie004: Failed to check link status - pciehp 0000:00:01.0:pcie004: Timeout on hotplug command 0x02f1 (issued 2520 msec ago) - -The issue was bisected down to commit 720d6a671a6e ("PCI: pciehp: Do not -handle events if interrupts are masked") and was further analyzed by the -reporter to be caused by the fact that pciehp first updates the hardware -and only then cache the ctrl->slot_ctrl in pcie_do_write_cmd(). If the -interrupt happens before we cache the value, pciehp_isr() reads value 0 and -decides that the interrupt was not meant for it causing the above timeout -to trigger. - -Fix by moving ctrl->slot_ctrl assignment to happen before it is written to -the hardware. - -Fixes: 720d6a671a6e ("PCI: pciehp: Do not handle events if interrupts are masked") -Link: https://lore.kernel.org/linux-pci/5FC3163CFD30C246ABAA99954A238FA8387DD344@FRAEML521-MBX.china.huawei.com -Reported-by: Shameerali Kolothum Thodi -Signed-off-by: Mika Westerberg -Signed-off-by: Bjorn Helgaas - -Patchset: surface-hotplug ---- - drivers/pci/hotplug/pciehp_hpc.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c -index 242b9f30210a..7074d4923811 100644 ---- a/drivers/pci/hotplug/pciehp_hpc.c -+++ b/drivers/pci/hotplug/pciehp_hpc.c -@@ -156,9 +156,9 @@ static void pcie_do_write_cmd(struct controller *ctrl, u16 cmd, - slot_ctrl |= (cmd & mask); - ctrl->cmd_busy = 1; - smp_mb(); -+ ctrl->slot_ctrl = slot_ctrl; - pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, slot_ctrl); - ctrl->cmd_started = jiffies; -- ctrl->slot_ctrl = slot_ctrl; - - /* - * Controllers with the Intel CF118 and similar errata advertise --- -2.33.0 - -From 9f934ab11c0f6078709402a2af415916d73e6dd9 Mon Sep 17 00:00:00 2001 -From: Mika Westerberg -Date: Thu, 31 Jan 2019 20:07:46 +0300 -Subject: [PATCH] PCI: pciehp: Disable Data Link Layer State Changed event on - suspend - -Commit 0e157e528604 ("PCI/PME: Implement runtime PM callbacks") tried to -solve an issue where the hierarchy immediately wakes up when it is -transitioned into D3cold. However, it turns out to prevent PME -propagation on some systems that do not support D3cold. - -I looked more closely at what might cause the immediate wakeup. It happens -when the ACPI power resource of the root port is turned off. The AML code -associated with the _OFF() method of the ACPI power resource starts a PCIe -L2/L3 Ready transition and waits for it to complete. Right after the L2/L3 -Ready transition is started the root port receives a PME from the -downstream port. - -The simplest hierarchy where this happens looks like this: - - 00:1d.0 PCIe Root Port - ^ - | - v - 05:00.0 PCIe switch #1 upstream port - 06:01.0 PCIe switch #1 downstream hotplug port - ^ - | - v - 08:00.0 PCIe switch #2 upstream port - -It seems that the PCIe link between the two switches, before -PME_Turn_Off/PME_TO_Ack is complete for the whole hierarchy, goes -inactive and triggers PME towards the root port bringing it back to D0. -The L2/L3 Ready sequence is described in PCIe r4.0 spec sections 5.2 and -5.3.3 but unfortunately they do not state what happens if DLLSCE is -enabled during the sequence. - -Disabling Data Link Layer State Changed event (DLLSCE) seems to prevent -the issue and still allows the downstream hotplug port to notice when a -device is plugged/unplugged. - -Link: https://bugzilla.kernel.org/show_bug.cgi?id=202593 -Fixes: 0e157e528604 ("PCI/PME: Implement runtime PM callbacks") -Signed-off-by: Mika Westerberg -Signed-off-by: Bjorn Helgaas -Reviewed-by: Rafael J. Wysocki -CC: stable@vger.kernel.org # v4.20+ - -Patchset: surface-hotplug ---- - drivers/pci/hotplug/pciehp_hpc.c | 17 +++++++++++++++-- - 1 file changed, 15 insertions(+), 2 deletions(-) - -diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c -index 7074d4923811..a37ff79a6e9e 100644 ---- a/drivers/pci/hotplug/pciehp_hpc.c -+++ b/drivers/pci/hotplug/pciehp_hpc.c -@@ -752,12 +752,25 @@ void pcie_clear_hotplug_events(struct controller *ctrl) - - void pcie_enable_interrupt(struct controller *ctrl) - { -- pcie_write_cmd(ctrl, PCI_EXP_SLTCTL_HPIE, PCI_EXP_SLTCTL_HPIE); -+ u16 mask; -+ -+ mask = PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_DLLSCE; -+ pcie_write_cmd(ctrl, mask, mask); - } - - void pcie_disable_interrupt(struct controller *ctrl) - { -- pcie_write_cmd(ctrl, 0, PCI_EXP_SLTCTL_HPIE); -+ u16 mask; -+ -+ /* -+ * Mask hot-plug interrupt to prevent it triggering immediately -+ * when the link goes inactive (we still get PME when any of the -+ * enabled events is detected). Same goes with Link Layer State -+ * changed event which generates PME immediately when the link goes -+ * inactive so mask it as well. -+ */ -+ mask = PCI_EXP_SLTCTL_HPIE | PCI_EXP_SLTCTL_DLLSCE; -+ pcie_write_cmd(ctrl, 0, mask); - } - - /* --- -2.33.0 - -From 5e8db923ba9b2b98164029d3212206338c539ac0 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 29 Oct 2020 22:04:38 +0100 -Subject: [PATCH] PCI: Allow D3cold for hot-plug ports on Surface Books - -The Microsoft Surface Book series of devices have a tablet part (so -called clipboard) that can be detached from the base of the device. -While the clipboard contains the CPU, the base can contain a discrete -GPU (dGPU). This dGPU is connected via a PCIe hot-plug port. - -Currently D3cold is disallowed for all hot-plug ports. On the Surface -Book 2 and 3, this leads to increased power consumption during suspend -and when the dGPU is not used (i.e. runtime suspended). This can be -observed not only in battery drain, but also by the dGPU getting notably -warm while suspended and not in D3cold. - -Testing shows that the Surface Books behave well with D3cold enabled for -hot-plug ports, alleviating the aforementioned issues. Thus white-list -D3cold for hot-plug ports on those devices. - -Note: PCIe hot-plug signalling while the device is in D3cold is handled -via ACPI, out-of-band interrupts, and the surface_hotplug driver -(combined). The device will work without the surface_hotplug driver, -however, device removal/addition will only be detected on device resume. - -Signed-off-by: Maximilian Luz -Patchset: surface-hotplug ---- - drivers/pci/pci.c | 31 +++++++++++++++++++++++++++++-- - 1 file changed, 29 insertions(+), 2 deletions(-) - -diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c -index a60eb5780cc0..65bb9c2c1a5b 100644 ---- a/drivers/pci/pci.c -+++ b/drivers/pci/pci.c -@@ -2523,6 +2523,32 @@ static const struct dmi_system_id bridge_d3_blacklist[] = { - { } - }; - -+static const struct dmi_system_id bridge_d3_hotplug_whitelist[] = { -+#ifdef CONFIG_X86 -+ { -+ /* -+ * Microsoft Surface Books have a hot-plug root port for the -+ * discrete GPU (the device containing it can be detached form -+ * the top-part, containing the cpu). -+ * -+ * If this discrete GPU is not transitioned into D3cold for -+ * suspend, the device will become notably warm and also -+ * consume a lot more power than desirable. -+ * -+ * We assume that since those devices have been confirmed -+ * working with D3, future Surface devices will too. So let's -+ * keep this match generic. -+ */ -+ .ident = "Microsoft Surface", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "Surface"), -+ }, -+ }, -+#endif -+ { } -+}; -+ - /** - * pci_bridge_d3_possible - Is it possible to put the bridge into D3 - * @bridge: Bridge to check -@@ -2563,10 +2589,11 @@ bool pci_bridge_d3_possible(struct pci_dev *bridge) - /* - * Hotplug ports handled natively by the OS were not validated - * by vendors for runtime D3 at least until 2018 because there -- * was no OS support. -+ * was no OS support. Explicitly whitelist systems that have -+ * been confirmed working. - */ - if (bridge->is_hotplug_bridge) -- return false; -+ return dmi_check_system(bridge_d3_hotplug_whitelist); - - if (dmi_check_system(bridge_d3_blacklist)) - return false; --- -2.33.0 - -From 122c5495b34f3c6eb16e4a8c6736ae4c110d6da7 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 31 Oct 2020 20:46:33 +0100 -Subject: [PATCH] PCI: Add sysfs attribute for PCI device power state - -While most PCI power-states can be queried from user-space via lspci, -this has some limits. Specifically, lspci fails to provide an accurate -value when the device is in D3cold as it has to resume the device before -it can access its power state via the configuration space, leading to it -reporting D0 or another on-state. Thus lspci can, for example, not be -used to diagnose power-consumption issues for devices that can enter -D3cold or to ensure that devices properly enter D3cold at all. - -To alleviate this issue, introduce a new sysfs device attribute for the -PCI power state, showing the current power state as seen by the kernel. - -Signed-off-by: Maximilian Luz -Patchset: surface-hotplug ---- - Documentation/ABI/testing/sysfs-bus-pci | 9 +++++++++ - drivers/pci/pci-sysfs.c | 12 ++++++++++++ - 2 files changed, 21 insertions(+) - -diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci -index 44d4b2be92fd..daded06ae017 100644 ---- a/Documentation/ABI/testing/sysfs-bus-pci -+++ b/Documentation/ABI/testing/sysfs-bus-pci -@@ -323,3 +323,12 @@ Description: - - This is similar to /sys/bus/pci/drivers_autoprobe, but - affects only the VFs associated with a specific PF. -+ -+What: /sys/bus/pci/devices/.../power_state -+Date: November 2020 -+Contact: Linux PCI developers -+Description: -+ This file contains the current PCI power state of the device. -+ The value comes from the PCI kernel device state and can be one -+ of: "unknown", "error", "D0", D1", "D2", "D3hot", "D3cold". -+ The file is read only. -diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c -index 1edf5a1836ea..ee1518650d55 100644 ---- a/drivers/pci/pci-sysfs.c -+++ b/drivers/pci/pci-sysfs.c -@@ -124,6 +124,17 @@ static ssize_t cpulistaffinity_show(struct device *dev, - } - static DEVICE_ATTR_RO(cpulistaffinity); - -+/* PCI power state */ -+static ssize_t power_state_show(struct device *dev, -+ struct device_attribute *attr, char *buf) -+{ -+ struct pci_dev *pci_dev = to_pci_dev(dev); -+ pci_power_t state = READ_ONCE(pci_dev->current_state); -+ -+ return sprintf(buf, "%s\n", pci_power_name(state)); -+} -+static DEVICE_ATTR_RO(power_state); -+ - /* show resources */ - static ssize_t resource_show(struct device *dev, struct device_attribute *attr, - char *buf) -@@ -745,6 +756,7 @@ static ssize_t driver_override_show(struct device *dev, - static DEVICE_ATTR_RW(driver_override); - - static struct attribute *pci_dev_attrs[] = { -+ &dev_attr_power_state.attr, - &dev_attr_resource.attr, - &dev_attr_vendor.attr, - &dev_attr_device.attr, --- -2.33.0 - -From d264fe11e99e5209c7f876691e41872f9fa3e4d5 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 14 Dec 2020 20:50:59 +0100 -Subject: [PATCH] platform/x86: Add Surface Hotplug driver - -Add a driver to handle out-of-band hot-plug signaling for the discrete -GPU (dGPU) on Microsoft Surface Book 2 and 3 devices. This driver is -required to properly detect hot-plugging of the dGPU and relay the -appropriate signal to the PCIe hot-plug driver core. - -Signed-off-by: Maximilian Luz -Patchset: surface-hotplug ---- - drivers/platform/x86/Kconfig | 19 ++ - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/surface_hotplug.c | 282 +++++++++++++++++++++++++ - 3 files changed, 302 insertions(+) - create mode 100644 drivers/platform/x86/surface_hotplug.c - -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 9aed8f6941cc..d48bf3b2fdfd 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -708,6 +708,25 @@ config ACPI_WMI - It is safe to enable this driver even if your DSDT doesn't define - any ACPI-WMI devices. - -+config SURFACE_HOTPLUG -+ tristate "Surface Hot-Plug Driver" -+ depends on GPIOLIB -+ help -+ Driver for out-of-band hot-plug event signaling on Microsoft Surface -+ devices with hot-pluggable PCIe cards. -+ -+ This driver is used on Surface Book (2 and 3) devices with a -+ hot-pluggable discrete GPU (dGPU). When not in use, the dGPU on those -+ devices can enter D3cold, which prevents in-band (standard) PCIe -+ hot-plug signaling. Thus, without this driver, detaching the base -+ containing the dGPU will not correctly update the state of the -+ corresponding PCIe device if it is in D3cold. This driver adds support -+ for out-of-band hot-plug notifications, ensuring that the device state -+ is properly updated even when the device in question is in D3cold. -+ -+ Select M or Y here, if you want to (fully) support hot-plugging of -+ dGPU devices on the Surface Book 2 and/or 3 during D3cold. -+ - config WMI_BMOF - tristate "WMI embedded Binary MOF driver" - depends on ACPI_WMI -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 2737a78616c8..d08f7e254d47 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -89,6 +89,7 @@ obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o - obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o - obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o -+obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o - obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += sb1_dgpu_sw.o - obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o - obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o -diff --git a/drivers/platform/x86/surface_hotplug.c b/drivers/platform/x86/surface_hotplug.c -new file mode 100644 -index 000000000000..cfcc15cfbacb ---- /dev/null -+++ b/drivers/platform/x86/surface_hotplug.c -@@ -0,0 +1,282 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface Book (2 and later) hot-plug driver. -+ * -+ * Surface Book devices (can) have a hot-pluggable discrete GPU (dGPU). This -+ * driver is responsible for out-of-band hot-plug event signaling on these -+ * devices. It is specifically required when the hot-plug device is in D3cold -+ * and can thus not generate PCIe hot-plug events itself. -+ * -+ * Event signaling is handled via ACPI, which will generate the appropriate -+ * device-check notifications to be picked up by the PCIe hot-plug driver. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+static const struct acpi_gpio_params shps_base_presence_int = { 0, 0, false }; -+static const struct acpi_gpio_params shps_base_presence = { 1, 0, false }; -+static const struct acpi_gpio_params shps_device_power_int = { 2, 0, false }; -+static const struct acpi_gpio_params shps_device_power = { 3, 0, false }; -+static const struct acpi_gpio_params shps_device_presence_int = { 4, 0, false }; -+static const struct acpi_gpio_params shps_device_presence = { 5, 0, false }; -+ -+static const struct acpi_gpio_mapping shps_acpi_gpios[] = { -+ { "base_presence-int-gpio", &shps_base_presence_int, 1 }, -+ { "base_presence-gpio", &shps_base_presence, 1 }, -+ { "device_power-int-gpio", &shps_device_power_int, 1 }, -+ { "device_power-gpio", &shps_device_power, 1 }, -+ { "device_presence-int-gpio", &shps_device_presence_int, 1 }, -+ { "device_presence-gpio", &shps_device_presence, 1 }, -+ { }, -+}; -+ -+/* 5515a847-ed55-4b27-8352-cd320e10360a */ -+static const guid_t shps_dsm_guid = -+ GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, 0x32, 0x0e, 0x10, 0x36, 0x0a); -+ -+#define SHPS_DSM_REVISION 1 -+ -+enum shps_dsm_fn { -+ SHPS_DSM_FN_PCI_NUM_ENTRIES = 0x01, -+ SHPS_DSM_FN_PCI_GET_ENTRIES = 0x02, -+ SHPS_DSM_FN_IRQ_BASE_PRESENCE = 0x03, -+ SHPS_DSM_FN_IRQ_DEVICE_POWER = 0x04, -+ SHPS_DSM_FN_IRQ_DEVICE_PRESENCE = 0x05, -+}; -+ -+enum shps_irq_type { -+ /* NOTE: Must be in order of enum shps_dsm_fn above. */ -+ SHPS_IRQ_TYPE_BASE_PRESENCE = 0, -+ SHPS_IRQ_TYPE_DEVICE_POWER = 1, -+ SHPS_IRQ_TYPE_DEVICE_PRESENCE = 2, -+ SHPS_NUM_IRQS, -+}; -+ -+static const char *const shps_gpio_names[] = { -+ [SHPS_IRQ_TYPE_BASE_PRESENCE] = "base_presence", -+ [SHPS_IRQ_TYPE_DEVICE_POWER] = "device_power", -+ [SHPS_IRQ_TYPE_DEVICE_PRESENCE] = "device_presence", -+}; -+ -+struct shps_device { -+ struct mutex lock[SHPS_NUM_IRQS]; /* Protects update in shps_dsm_notify_irq() */ -+ struct gpio_desc *gpio[SHPS_NUM_IRQS]; -+ unsigned int irq[SHPS_NUM_IRQS]; -+}; -+ -+#define SHPS_IRQ_NOT_PRESENT ((unsigned int)-1) -+ -+static enum shps_dsm_fn shps_dsm_fn_for_irq(enum shps_irq_type type) -+{ -+ return SHPS_DSM_FN_IRQ_BASE_PRESENCE + type; -+} -+ -+static void shps_dsm_notify_irq(struct platform_device *pdev, enum shps_irq_type type) -+{ -+ struct shps_device *sdev = platform_get_drvdata(pdev); -+ acpi_handle handle = ACPI_HANDLE(&pdev->dev); -+ union acpi_object *result; -+ union acpi_object param; -+ int value; -+ -+ mutex_lock(&sdev->lock[type]); -+ -+ value = gpiod_get_value_cansleep(sdev->gpio[type]); -+ if (value < 0) { -+ mutex_unlock(&sdev->lock[type]); -+ dev_err(&pdev->dev, "failed to get gpio: %d (irq=%d)\n", type, value); -+ return; -+ } -+ -+ dev_dbg(&pdev->dev, "IRQ notification via DSM (irq=%d, value=%d)\n", type, value); -+ -+ param.type = ACPI_TYPE_INTEGER; -+ param.integer.value = value; -+ -+ result = acpi_evaluate_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, -+ shps_dsm_fn_for_irq(type), ¶m); -+ -+ if (!result) { -+ dev_err(&pdev->dev, "IRQ notification via DSM failed (irq=%d, gpio=%d)\n", -+ type, value); -+ -+ } else if (result->type != ACPI_TYPE_BUFFER) { -+ dev_err(&pdev->dev, -+ "IRQ notification via DSM failed: unexpected result type (irq=%d, gpio=%d)\n", -+ type, value); -+ -+ } else if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) { -+ dev_err(&pdev->dev, -+ "IRQ notification via DSM failed: unexpected result value (irq=%d, gpio=%d)\n", -+ type, value); -+ } -+ -+ mutex_unlock(&sdev->lock[type]); -+ -+ if (result) -+ ACPI_FREE(result); -+} -+ -+static irqreturn_t shps_handle_irq(int irq, void *data) -+{ -+ struct platform_device *pdev = data; -+ struct shps_device *sdev = platform_get_drvdata(pdev); -+ int type; -+ -+ /* Figure out which IRQ we're handling. */ -+ for (type = 0; type < SHPS_NUM_IRQS; type++) -+ if (irq == sdev->irq[type]) -+ break; -+ -+ /* We should have found our interrupt, if not: this is a bug. */ -+ if (WARN(type >= SHPS_NUM_IRQS, "invalid IRQ number: %d\n", irq)) -+ return IRQ_HANDLED; -+ -+ /* Forward interrupt to ACPI via DSM. */ -+ shps_dsm_notify_irq(pdev, type); -+ return IRQ_HANDLED; -+} -+ -+static int shps_setup_irq(struct platform_device *pdev, enum shps_irq_type type) -+{ -+ unsigned long flags = IRQF_ONESHOT | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING; -+ struct shps_device *sdev = platform_get_drvdata(pdev); -+ struct gpio_desc *gpiod; -+ acpi_handle handle = ACPI_HANDLE(&pdev->dev); -+ const char *irq_name; -+ const int dsm = shps_dsm_fn_for_irq(type); -+ int status, irq; -+ -+ /* -+ * Only set up interrupts that we actually need: The Surface Book 3 -+ * does not have a DSM for base presence, so don't set up an interrupt -+ * for that. -+ */ -+ if (!acpi_check_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, BIT(dsm))) { -+ dev_dbg(&pdev->dev, "IRQ notification via DSM not present (irq=%d)\n", type); -+ return 0; -+ } -+ -+ gpiod = devm_gpiod_get(&pdev->dev, shps_gpio_names[type], GPIOD_ASIS); -+ if (IS_ERR(gpiod)) -+ return PTR_ERR(gpiod); -+ -+ irq = gpiod_to_irq(gpiod); -+ if (irq < 0) -+ return irq; -+ -+ irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "shps-irq-%d", type); -+ if (!irq_name) -+ return -ENOMEM; -+ -+ status = devm_request_threaded_irq(&pdev->dev, irq, NULL, shps_handle_irq, -+ flags, irq_name, pdev); -+ if (status) -+ return status; -+ -+ dev_dbg(&pdev->dev, "set up irq %d as type %d\n", irq, type); -+ -+ sdev->gpio[type] = gpiod; -+ sdev->irq[type] = irq; -+ -+ return 0; -+} -+ -+static int surface_hotplug_remove(struct platform_device *pdev) -+{ -+ struct shps_device *sdev = platform_get_drvdata(pdev); -+ int i; -+ -+ /* Ensure that IRQs have been fully handled and won't trigger any more. */ -+ for (i = 0; i < SHPS_NUM_IRQS; i++) { -+ if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT) -+ disable_irq(sdev->irq[i]); -+ -+ mutex_destroy(&sdev->lock[i]); -+ } -+ -+ return 0; -+} -+ -+static int surface_hotplug_probe(struct platform_device *pdev) -+{ -+ struct shps_device *sdev; -+ int status, i; -+ -+ /* -+ * The MSHW0153 device is also present on the Surface Laptop 3, -+ * however that doesn't have a hot-pluggable PCIe device. It also -+ * doesn't have any GPIO interrupts/pins under the MSHW0153, so filter -+ * it out here. -+ */ -+ if (gpiod_count(&pdev->dev, NULL) < 0) -+ return -ENODEV; -+ -+ status = devm_acpi_dev_add_driver_gpios(&pdev->dev, shps_acpi_gpios); -+ if (status) -+ return status; -+ -+ sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL); -+ if (!sdev) -+ return -ENOMEM; -+ -+ platform_set_drvdata(pdev, sdev); -+ -+ /* -+ * Initialize IRQs so that we can safely call surface_hotplug_remove() -+ * on errors. -+ */ -+ for (i = 0; i < SHPS_NUM_IRQS; i++) -+ sdev->irq[i] = SHPS_IRQ_NOT_PRESENT; -+ -+ /* Set up IRQs. */ -+ for (i = 0; i < SHPS_NUM_IRQS; i++) { -+ mutex_init(&sdev->lock[i]); -+ -+ status = shps_setup_irq(pdev, i); -+ if (status) { -+ dev_err(&pdev->dev, "failed to set up IRQ %d: %d\n", i, status); -+ goto err; -+ } -+ } -+ -+ /* Ensure everything is up-to-date. */ -+ for (i = 0; i < SHPS_NUM_IRQS; i++) -+ if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT) -+ shps_dsm_notify_irq(pdev, i); -+ -+ return 0; -+ -+err: -+ surface_hotplug_remove(pdev); -+ return status; -+} -+ -+static const struct acpi_device_id surface_hotplug_acpi_match[] = { -+ { "MSHW0153", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_hotplug_acpi_match); -+ -+static struct platform_driver surface_hotplug_driver = { -+ .probe = surface_hotplug_probe, -+ .remove = surface_hotplug_remove, -+ .driver = { -+ .name = "surface_hotplug", -+ .acpi_match_table = surface_hotplug_acpi_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_hotplug_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Hot-Plug Signaling Driver for Surface Book Devices"); -+MODULE_LICENSE("GPL"); --- -2.33.0 - -From 655eceec8421630a914f5edd1d433e9770c01be9 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 2 Jul 2021 14:41:34 +0200 -Subject: [PATCH] Revert "Revert "PCI: PM: Do not read power state in - pci_enable_device_flags()"" - -This reverts commit 2bf73bce3df9517c4144f05ea0a80dbfeaa36933. - -Patchset: surface-hotplug ---- - drivers/pci/pci.c | 16 +++------------- - 1 file changed, 3 insertions(+), 13 deletions(-) - -diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c -index 65bb9c2c1a5b..5f3f35d314c3 100644 ---- a/drivers/pci/pci.c -+++ b/drivers/pci/pci.c -@@ -1590,20 +1590,10 @@ static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags) - int err; - int i, bars = 0; - -- /* -- * Power state could be unknown at this point, either due to a fresh -- * boot or a device removal call. So get the current power state -- * so that things like MSI message writing will behave as expected -- * (e.g. if the device really is in D0 at enable time). -- */ -- if (dev->pm_cap) { -- u16 pmcsr; -- pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); -- dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK); -- } -- -- if (atomic_inc_return(&dev->enable_cnt) > 1) -+ if (atomic_inc_return(&dev->enable_cnt) > 1) { -+ pci_update_current_state(dev, dev->current_state); - return 0; /* already enabled */ -+ } - - bridge = pci_upstream_bridge(dev); - if (bridge) --- -2.33.0 - diff --git a/patches/4.19/0012-surface-typecover.patch b/patches/4.19/0012-surface-typecover.patch deleted file mode 100644 index 3adfd8b85..000000000 --- a/patches/4.19/0012-surface-typecover.patch +++ /dev/null @@ -1,233 +0,0 @@ -From 9fd224cfa2a52560fed23cbdf9272ac08d9c2e0f Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 5 Nov 2020 13:09:45 +0100 -Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when - suspending - -The Type Cover for Microsoft Surface devices supports a special usb -control request to disable or enable the built-in keyboard backlight. -On Windows, this request happens when putting the device into suspend or -resuming it, without it the backlight of the Type Cover will remain -enabled for some time even though the computer is suspended, which looks -weird to the user. - -So add support for this special usb control request to hid-multitouch, -which is the driver that's handling the Type Cover. - -The reason we have to use a pm_notifier for this instead of the usual -suspend/resume methods is that those won't get called in case the usb -device is already autosuspended. - -Also, if the device is autosuspended, we have to briefly autoresume it -in order to send the request. Doing that should be fine, the usb-core -driver does something similar during suspend inside choose_wakeup(). - -To make sure we don't send that request to every device but only to -devices which support it, add a new quirk -MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk -is only enabled for the usb id of the Surface Pro 2017 Type Cover, which -is where I confirmed that it's working. - -Patchset: surface-typecover ---- - drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- - 1 file changed, 98 insertions(+), 2 deletions(-) - -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index c20945ed1dc1..93a93f991649 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -38,7 +38,10 @@ - #include - #include - #include -+#include - #include -+#include -+#include - #include - #include - #include -@@ -51,6 +54,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); - MODULE_LICENSE("GPL"); - - #include "hid-ids.h" -+#include "usbhid/usbhid.h" - - /* quirks to control the device */ - #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) -@@ -72,12 +76,15 @@ MODULE_LICENSE("GPL"); - #define MT_QUIRK_STICKY_FINGERS BIT(16) - #define MT_QUIRK_ASUS_CUSTOM_UP BIT(17) - #define MT_QUIRK_WIN8_PTP_BUTTONS BIT(18) -+#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(21) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 - - #define MT_BUTTONTYPE_CLICKPAD 0 - -+#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+ - enum latency_mode { - HID_LATENCY_NORMAL = 0, - HID_LATENCY_HIGH = 1, -@@ -168,6 +175,8 @@ struct mt_device { - - struct list_head applications; - struct list_head reports; -+ -+ struct notifier_block pm_notifier; - }; - - static void mt_post_parse_default_settings(struct mt_device *td, -@@ -207,6 +216,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); - #define MT_CLS_VTL 0x0110 - #define MT_CLS_GOOGLE 0x0111 - #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 -+#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 - - #define MT_DEFAULT_MAXCONTACT 10 - #define MT_MAX_MAXCONTACT 250 -@@ -357,6 +367,16 @@ static const struct mt_class mt_classes[] = { - MT_QUIRK_CONTACT_CNT_ACCURATE | - MT_QUIRK_WIN8_PTP_BUTTONS, - }, -+ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_ALWAYS_VALID | -+ MT_QUIRK_IGNORE_DUPLICATES | -+ MT_QUIRK_HOVERING | -+ MT_QUIRK_CONTACT_CNT_ACCURATE | -+ MT_QUIRK_STICKY_FINGERS | -+ MT_QUIRK_WIN8_PTP_BUTTONS, -+ .export_all_inputs = true -+ }, - { } - }; - -@@ -1669,6 +1689,69 @@ static void mt_expired_timeout(struct timer_list *t) - clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - -+static void get_type_cover_backlight_field(struct hid_device *hdev, -+ struct hid_field **field) -+{ -+ struct hid_report_enum *rep_enum; -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid -+ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { -+ *field = cur_field; -+ return; -+ } -+ } -+ } -+ } -+} -+ -+static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) -+{ -+ struct usb_device *udev = hid_to_usb_dev(hdev); -+ struct hid_field *field = NULL; -+ -+ /* Wake up the device in case it's already suspended */ -+ pm_runtime_get_sync(&udev->dev); -+ -+ get_type_cover_backlight_field(hdev, &field); -+ if (!field) { -+ hid_err(hdev, "couldn't find backlight field\n"); -+ goto out; -+ } -+ -+ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; -+ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); -+ -+out: -+ pm_runtime_put_sync(&udev->dev); -+} -+ -+static int mt_pm_notifier(struct notifier_block *notifier, -+ unsigned long pm_event, -+ void *unused) -+{ -+ struct mt_device *td = -+ container_of(notifier, struct mt_device, pm_notifier); -+ struct hid_device *hdev = td->hdev; -+ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { -+ if (pm_event == PM_SUSPEND_PREPARE) -+ update_keyboard_backlight(hdev, 0); -+ else if (pm_event == PM_POST_SUSPEND) -+ update_keyboard_backlight(hdev, 1); -+ } -+ -+ return NOTIFY_DONE; -+} -+ - static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - { - int ret, i; -@@ -1692,6 +1775,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; - hid_set_drvdata(hdev, td); - -+ td->pm_notifier.notifier_call = mt_pm_notifier; -+ register_pm_notifier(&td->pm_notifier); -+ - INIT_LIST_HEAD(&td->applications); - INIT_LIST_HEAD(&td->reports); - -@@ -1716,15 +1802,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - timer_setup(&td->release_timer, mt_expired_timeout, 0); - - ret = hid_parse(hdev); -- if (ret != 0) -+ if (ret != 0) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) - mt_fix_const_fields(hdev, HID_DG_CONTACTID); - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); -- if (ret) -+ if (ret) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); - if (ret) -@@ -1760,6 +1850,7 @@ static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); - -+ unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); - - sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); -@@ -2107,6 +2198,11 @@ static const struct hid_device_id mt_devices[] = { - MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, - USB_DEVICE_ID_XIROKU_CSR2) }, - -+ /* Microsoft Surface type cover */ -+ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, -+ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, -+ - /* Google MT devices */ - { .driver_data = MT_CLS_GOOGLE, - HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, --- -2.33.0 - diff --git a/patches/4.19/0013-surface-go-touchscreen.patch b/patches/4.19/0013-surface-go-touchscreen.patch deleted file mode 100644 index 9a9a2b145..000000000 --- a/patches/4.19/0013-surface-go-touchscreen.patch +++ /dev/null @@ -1,39 +0,0 @@ -From c90c887eb22ee2a3cc30abb4ca87e41bc45614a9 Mon Sep 17 00:00:00 2001 -From: Zoltan Tamas Vajda -Date: Thu, 3 Jun 2021 10:50:55 +0200 -Subject: [PATCH] Added quirk for Surface Go touchscreen - -Patchset: surface-go-touchscreen ---- - drivers/hid/hid-ids.h | 1 + - drivers/hid/hid-input.c | 2 ++ - 2 files changed, 3 insertions(+) - -diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h -index ee5dce862a21..17a27c2389ff 100644 ---- a/drivers/hid/hid-ids.h -+++ b/drivers/hid/hid-ids.h -@@ -388,6 +388,7 @@ - #define USB_DEVICE_ID_HP_X2 0x074d - #define USB_DEVICE_ID_HP_X2_10_COVER 0x0755 - #define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN 0x2706 -+#define I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN 0x261A - - #define USB_VENDOR_ID_ELECOM 0x056e - #define USB_DEVICE_ID_ELECOM_BM084 0x0061 -diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c -index 4dd151b2924e..4f5e58a9b19e 100644 ---- a/drivers/hid/hid-input.c -+++ b/drivers/hid/hid-input.c -@@ -336,6 +336,8 @@ static const struct hid_device_id hid_battery_quirks[] = { - HID_BATTERY_QUIRK_IGNORE }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN), - HID_BATTERY_QUIRK_IGNORE }, -+ { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN), -+ HID_BATTERY_QUIRK_IGNORE }, - {} - }; - --- -2.33.0 - diff --git a/patches/4.19/0014-ath10k-firmware-override.patch b/patches/4.19/0014-ath10k-firmware-override.patch deleted file mode 100644 index d4fccfcb9..000000000 --- a/patches/4.19/0014-ath10k-firmware-override.patch +++ /dev/null @@ -1,121 +0,0 @@ -From df9ba74e96a8e1818028ea6f4a066a2388075377 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 27 Feb 2021 00:45:52 +0100 -Subject: [PATCH] ath10k: Add module parameters to override board files - -Some Surface devices, specifically the Surface Go and AMD version of the -Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better -with a different board file, as it seems that the firmeware included -upstream is buggy. - -As it is generally not a good idea to randomly overwrite files, let -alone doing so via packages, we add module parameters to override those -file names in the driver. This allows us to package/deploy the override -via a modprobe.d config. - -Signed-off-by: Maximilian Luz -Patchset: ath10k-firmware-override ---- - drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++ - 1 file changed, 58 insertions(+) - -diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c -index 436eac342b62..c9028d59bfe2 100644 ---- a/drivers/net/wireless/ath/ath10k/core.c -+++ b/drivers/net/wireless/ath/ath10k/core.c -@@ -41,6 +41,9 @@ static bool uart_print; - static bool skip_otp; - static bool rawmode; - -+static char *override_board = ""; -+static char *override_board2 = ""; -+ - unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | - BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); - -@@ -52,6 +55,9 @@ module_param(skip_otp, bool, 0644); - module_param(rawmode, bool, 0644); - module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); - -+module_param(override_board, charp, 0644); -+module_param(override_board2, charp, 0644); -+ - MODULE_PARM_DESC(debug_mask, "Debugging mask"); - MODULE_PARM_DESC(uart_print, "Uart target debugging"); - MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); -@@ -59,6 +65,9 @@ MODULE_PARM_DESC(cryptmode, "Crypto mode: 0-hardware, 1-software"); - MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); - MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); - -+MODULE_PARM_DESC(override_board, "Override for board.bin file"); -+MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); -+ - static const struct ath10k_hw_params ath10k_hw_params_list[] = { - { - .id = QCA988X_HW_2_0_VERSION, -@@ -705,6 +714,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) - return 0; - } - -+static const char *ath10k_override_board_fw_file(struct ath10k *ar, -+ const char *file) -+{ -+ if (strcmp(file, "board.bin") == 0) { -+ if (strcmp(override_board, "") == 0) -+ return file; -+ -+ if (strcmp(override_board, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", -+ override_board); -+ -+ return override_board; -+ } -+ -+ if (strcmp(file, "board-2.bin") == 0) { -+ if (strcmp(override_board2, "") == 0) -+ return file; -+ -+ if (strcmp(override_board2, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", -+ override_board2); -+ -+ return override_board2; -+ } -+ -+ return file; -+} -+ - static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - const char *dir, - const char *file) -@@ -719,6 +764,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - if (dir == NULL) - dir = "."; - -+ /* HACK: Override board.bin and board-2.bin files if specified. -+ * -+ * Some Surface devices perform better with a different board -+ * configuration. To this end, one would need to replace the board.bin -+ * file with the modified config and remove the board-2.bin file. -+ * Unfortunately, that's not a solution that we can easily package. So -+ * we add module options to perform these overrides here. -+ */ -+ -+ file = ath10k_override_board_fw_file(ar, file); -+ if (!file) -+ return ERR_PTR(-ENOENT); -+ - snprintf(filename, sizeof(filename), "%s/%s", dir, file); - ret = firmware_request_nowarn(&fw, filename, ar->dev); - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", --- -2.33.0 - diff --git a/patches/5.10/0001-surface3-oemb.patch b/patches/5.10/0001-surface3-oemb.patch deleted file mode 100644 index cda5617fc..000000000 --- a/patches/5.10/0001-surface3-oemb.patch +++ /dev/null @@ -1,101 +0,0 @@ -From 06b4da28555fb1498f0c657190f9453841d096bb Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 18 Oct 2020 16:42:44 +0900 -Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI - table - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI data into dmi_system_id tables used -for quirks so that each driver can enable quirks even on the affected -systems. - -On affected systems, DMI data will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: surface3-oemb ---- - drivers/platform/x86/surface3-wmi.c | 7 +++++++ - sound/soc/codecs/rt5645.c | 9 +++++++++ - sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ - 3 files changed, 24 insertions(+) - -diff --git a/drivers/platform/x86/surface3-wmi.c b/drivers/platform/x86/surface3-wmi.c -index 130b6f52a600..801083aa56d6 100644 ---- a/drivers/platform/x86/surface3-wmi.c -+++ b/drivers/platform/x86/surface3-wmi.c -@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - #endif - { } - }; -diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c -index 420003d062c7..217e488cd4fa 100644 ---- a/sound/soc/codecs/rt5645.c -+++ b/sound/soc/codecs/rt5645.c -@@ -3687,6 +3687,15 @@ static const struct dmi_system_id dmi_platform_data[] = { - }, - .driver_data = (void *)&intel_braswell_platform_data, - }, -+ { -+ .ident = "Microsoft Surface 3", -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)&intel_braswell_platform_data, -+ }, - { - /* - * Match for the GPDwin which unfortunately uses somewhat -diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -index 2752dc955733..ef36a316e2ed 100644 ---- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c -+++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .callback = cht_surface_quirk_cb, -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - { } - }; - --- -2.33.0 - diff --git a/patches/5.10/0002-wifi.patch b/patches/5.10/0002-wifi.patch deleted file mode 100644 index 430d3fb66..000000000 --- a/patches/5.10/0002-wifi.patch +++ /dev/null @@ -1,2520 +0,0 @@ -From e963d9406f7c618d9210c45a901b284cf07c8379 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Mon, 28 Sep 2020 17:46:49 +0900 -Subject: [PATCH] mwifiex: pcie: add DMI-based quirk impl for Surface devices - -This commit adds quirk implementation based on DMI matching with DMI -table for Surface devices. - -This implementation can be used for quirks later. - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/Makefile | 1 + - drivers/net/wireless/marvell/mwifiex/pcie.c | 4 + - drivers/net/wireless/marvell/mwifiex/pcie.h | 1 + - .../wireless/marvell/mwifiex/pcie_quirks.c | 114 ++++++++++++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.h | 11 ++ - 5 files changed, 131 insertions(+) - create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.c - create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.h - -diff --git a/drivers/net/wireless/marvell/mwifiex/Makefile b/drivers/net/wireless/marvell/mwifiex/Makefile -index fdfd9bf15ed4..8a1e7c5b9c6e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/Makefile -+++ b/drivers/net/wireless/marvell/mwifiex/Makefile -@@ -49,6 +49,7 @@ mwifiex_sdio-y += sdio.o - obj-$(CONFIG_MWIFIEX_SDIO) += mwifiex_sdio.o - - mwifiex_pcie-y += pcie.o -+mwifiex_pcie-y += pcie_quirks.o - obj-$(CONFIG_MWIFIEX_PCIE) += mwifiex_pcie.o - - mwifiex_usb-y += usb.o -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index b2de8d03c5fa..4c375ad6a078 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -27,6 +27,7 @@ - #include "wmm.h" - #include "11n.h" - #include "pcie.h" -+#include "pcie_quirks.h" - - #define PCIE_VERSION "1.0" - #define DRV_NAME "Marvell mwifiex PCIe" -@@ -410,6 +411,9 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return ret; - } - -+ /* check quirks */ -+ mwifiex_initialize_quirks(card); -+ - if (mwifiex_add_card(card, &card->fw_done, &pcie_ops, - MWIFIEX_PCIE, &pdev->dev)) { - pr_err("%s failed\n", __func__); -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.h b/drivers/net/wireless/marvell/mwifiex/pcie.h -index 5ed613d65709..981e330c77d7 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.h -@@ -244,6 +244,7 @@ struct pcie_service_card { - unsigned long work_flags; - - bool pci_reset_ongoing; -+ unsigned long quirks; - }; - - static inline int -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -new file mode 100644 -index 000000000000..929aee2b0a60 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -0,0 +1,114 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * File for PCIe quirks. -+ */ -+ -+/* The low-level PCI operations will be performed in this file. Therefore, -+ * let's use dev_*() instead of mwifiex_dbg() here to avoid troubles (e.g. -+ * to avoid using mwifiex_adapter struct before init or wifi is powered -+ * down, or causes NULL ptr deref). -+ */ -+ -+#include -+ -+#include "pcie_quirks.h" -+ -+/* quirk table based on DMI matching */ -+static const struct dmi_system_id mwifiex_quirk_table[] = { -+ { -+ .ident = "Surface Pro 4", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 5", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 5 (LTE)", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 6", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Book 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Book 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Laptop 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Laptop 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 3"), -+ }, -+ .driver_data = 0, -+ }, -+ {} -+}; -+ -+void mwifiex_initialize_quirks(struct pcie_service_card *card) -+{ -+ struct pci_dev *pdev = card->dev; -+ const struct dmi_system_id *dmi_id; -+ -+ dmi_id = dmi_first_match(mwifiex_quirk_table); -+ if (dmi_id) -+ card->quirks = (uintptr_t)dmi_id->driver_data; -+ -+ if (!card->quirks) -+ dev_info(&pdev->dev, "no quirks enabled\n"); -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -new file mode 100644 -index 000000000000..5326ae7e5671 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -0,0 +1,11 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* -+ * Header file for PCIe quirks. -+ */ -+ -+#include "pcie.h" -+ -+/* quirks */ -+// quirk flags can be added here -+ -+void mwifiex_initialize_quirks(struct pcie_service_card *card); --- -2.33.0 - -From 6d8f2fa840634998f2baf1a9ab17dbc175e80d54 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:25:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_d3cold quirk for Surface gen4+ - devices - -To reset mwifiex on Surface gen4+ (Pro 4 or later gen) devices, it -seems that putting the wifi device into D3cold is required according -to errata.inf file on Windows installation (Windows/INF/errata.inf). - -This patch adds a function that performs power-cycle (put into D3cold -then D0) and call the function at the end of reset_prepare(). - -Note: Need to also reset the parent device (bridge) of wifi on SB1; -it might be because the bridge of wifi always reports it's in D3hot. -When I tried to reset only the wifi device (not touching parent), it gave -the following error and the reset failed: - - acpi device:4b: Cannot transition to power state D0 for parent in D3hot - mwifiex_pcie 0000:03:00.0: can't change power state from D3cold to D0 (config space inaccessible) - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 ++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 73 +++++++++++++++++-- - .../wireless/marvell/mwifiex/pcie_quirks.h | 3 +- - 3 files changed, 74 insertions(+), 9 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 4c375ad6a078..ccb4b54c9067 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -529,6 +529,13 @@ static void mwifiex_pcie_reset_prepare(struct pci_dev *pdev) - mwifiex_shutdown_sw(adapter); - clear_bit(MWIFIEX_IFACE_WORK_DEVICE_DUMP, &card->work_flags); - clear_bit(MWIFIEX_IFACE_WORK_CARD_RESET, &card->work_flags); -+ -+ /* For Surface gen4+ devices, we need to put wifi into D3cold right -+ * before performing FLR -+ */ -+ if (card->quirks & QUIRK_FW_RST_D3COLD) -+ mwifiex_pcie_reset_d3cold_quirk(pdev); -+ - mwifiex_dbg(adapter, INFO, "%s, successful\n", __func__); - - card->pci_reset_ongoing = true; -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 929aee2b0a60..edc739c542fe 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -21,7 +21,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 5", -@@ -30,7 +30,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -39,7 +39,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 6", -@@ -47,7 +47,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Book 1", -@@ -55,7 +55,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Book 2", -@@ -63,7 +63,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Laptop 1", -@@ -71,7 +71,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Laptop 2", -@@ -79,7 +79,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface 3", -@@ -111,4 +111,61 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - - if (!card->quirks) - dev_info(&pdev->dev, "no quirks enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_D3COLD) -+ dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+} -+ -+static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -+{ -+ dev_info(&pdev->dev, "putting into D3cold...\n"); -+ -+ pci_save_state(pdev); -+ if (pci_is_enabled(pdev)) -+ pci_disable_device(pdev); -+ pci_set_power_state(pdev, PCI_D3cold); -+} -+ -+static int mwifiex_pcie_set_power_d0(struct pci_dev *pdev) -+{ -+ int ret; -+ -+ dev_info(&pdev->dev, "putting into D0...\n"); -+ -+ pci_set_power_state(pdev, PCI_D0); -+ ret = pci_enable_device(pdev); -+ if (ret) { -+ dev_err(&pdev->dev, "pci_enable_device failed\n"); -+ return ret; -+ } -+ pci_restore_state(pdev); -+ -+ return 0; -+} -+ -+int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) -+{ -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); -+ int ret; -+ -+ /* Power-cycle (put into D3cold then D0) */ -+ dev_info(&pdev->dev, "Using reset_d3cold quirk to perform FW reset\n"); -+ -+ /* We need to perform power-cycle also for bridge of wifi because -+ * on some devices (e.g. Surface Book 1), the OS for some reasons -+ * can't know the real power state of the bridge. -+ * When tried to power-cycle only wifi, the reset failed with the -+ * following dmesg log: -+ * "Cannot transition to power state D0 for parent in D3hot". -+ */ -+ mwifiex_pcie_set_power_d3cold(pdev); -+ mwifiex_pcie_set_power_d3cold(parent_pdev); -+ -+ ret = mwifiex_pcie_set_power_d0(parent_pdev); -+ if (ret) -+ return ret; -+ ret = mwifiex_pcie_set_power_d0(pdev); -+ if (ret) -+ return ret; -+ -+ return 0; - } -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 5326ae7e5671..8b9dcb5070d8 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -6,6 +6,7 @@ - #include "pcie.h" - - /* quirks */ --// quirk flags can be added here -+#define QUIRK_FW_RST_D3COLD BIT(0) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); -+int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.33.0 - -From eeb7f040697ceefc7b98f09fb0d3918bd82bcd02 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:32:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_wsid quirk for Surface 3 - -This commit adds reset_wsid quirk and uses this quirk for Surface 3 on -card reset. - -To reset mwifiex on Surface 3, it seems that calling the _DSM method -exists in \_SB.WSID [1] device is required. - -On Surface 3, calling the _DSM method removes/re-probes the card by -itself. So, need to place the reset function before performing FLR and -skip performing any other reset-related works. - -Note that Surface Pro 3 also has the WSID device [2], but it seems to need -more work. This commit only supports Surface 3 yet. - -[1] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_3/dsdt.dsl#L11947-L12011 -[2] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_pro_3/dsdt.dsl#L12164-L12216 - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 10 +++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 77 ++++++++++++++++++- - .../wireless/marvell/mwifiex/pcie_quirks.h | 5 ++ - 3 files changed, 91 insertions(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index ccb4b54c9067..8d5cc3c1cf38 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -2968,6 +2968,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - -+ /* On Surface 3, reset_wsid method removes then re-probes card by -+ * itself. So, need to place it here and skip performing any other -+ * reset-related works. -+ */ -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) { -+ mwifiex_pcie_reset_wsid_quirk(card->dev); -+ /* skip performing any other reset-related works */ -+ return; -+ } -+ - /* We can't afford to wait here; remove() might be waiting on us. If we - * can't grab the device lock, maybe we'll get another chance later. - */ -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index edc739c542fe..f0a6fa0a7ae5 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -9,10 +9,21 @@ - * down, or causes NULL ptr deref). - */ - -+#include - #include - - #include "pcie_quirks.h" - -+/* For reset_wsid quirk */ -+#define ACPI_WSID_PATH "\\_SB.WSID" -+#define WSID_REV 0x0 -+#define WSID_FUNC_WIFI_PWR_OFF 0x1 -+#define WSID_FUNC_WIFI_PWR_ON 0x2 -+/* WSID _DSM UUID: "534ea3bf-fcc2-4e7a-908f-a13978f0c7ef" */ -+static const guid_t wsid_dsm_guid = -+ GUID_INIT(0x534ea3bf, 0xfcc2, 0x4e7a, -+ 0x90, 0x8f, 0xa1, 0x39, 0x78, 0xf0, 0xc7, 0xef); -+ - /* quirk table based on DMI matching */ - static const struct dmi_system_id mwifiex_quirk_table[] = { - { -@@ -87,7 +98,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, - { - .ident = "Surface Pro 3", -@@ -113,6 +124,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - dev_info(&pdev->dev, "no quirks enabled\n"); - if (card->quirks & QUIRK_FW_RST_D3COLD) - dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) -+ dev_info(&pdev->dev, -+ "quirk reset_wsid for Surface 3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -@@ -169,3 +183,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) - - return 0; - } -+ -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev) -+{ -+ acpi_handle handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n"); -+ -+ status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&pdev->dev, "No ACPI handle for path %s\n", -+ ACPI_WSID_PATH); -+ return -ENODEV; -+ } -+ -+ if (!acpi_has_method(handle, "_DSM")) { -+ dev_err(&pdev->dev, "_DSM method not found\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power off func\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power on func\n"); -+ return -ENODEV; -+ } -+ -+ /* card will be removed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi off...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi off\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ /* card will be re-probed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi on...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi on\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ return 0; -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 8b9dcb5070d8..3ef7440418e3 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -7,6 +7,11 @@ - - /* quirks */ - #define QUIRK_FW_RST_D3COLD BIT(0) -+/* Surface 3 and Surface Pro 3 have the same _DSM method but need to -+ * be handled differently. Currently, only S3 is supported. -+ */ -+#define QUIRK_FW_RST_WSID_S3 BIT(1) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); --- -2.33.0 - -From 6e4d13d95aa201fbb6b307b67b7620f7c0c58111 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Wed, 30 Sep 2020 18:08:24 +0900 -Subject: [PATCH] mwifiex: pcie: (OEMB) add quirk for Surface 3 with broken DMI - table - -(made referring to http://git.osdn.net/view?p=android-x86/kernel.git;a=commitdiff;h=18e2e857c57633b25b3b4120f212224a108cd883) - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI info for the affected Surface 3. - -On affected systems, DMI info will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie_quirks.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index f0a6fa0a7ae5..34dcd84f02a6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -100,6 +100,15 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - { - .ident = "Surface Pro 3", - .matches = { --- -2.33.0 - -From 172acc5e1534698958af941a22fc8815486b5e4b Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 4 Oct 2020 00:11:49 +0900 -Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ - -Currently, mwifiex fw will crash after suspend on recent kernel series. -On Windows, it seems that the root port of wifi will never enter D3 state -(stay on D0 state). And on Linux, disabling the D3 state for the -bridge fixes fw crashing after suspend. - -This commit disables the D3 state of root port on driver initialization -and fixes fw crashing after suspend. - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 27 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 8d5cc3c1cf38..0627a37deac8 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -379,6 +379,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) - { - struct pcie_service_card *card; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - int ret; - - pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", -@@ -420,6 +421,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return -1; - } - -+ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing -+ * after suspend -+ */ -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ parent_pdev->bridge_d3 = false; -+ - return 0; - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 34dcd84f02a6..a2aeb2af907e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -32,7 +32,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5", -@@ -41,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -50,7 +52,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 6", -@@ -58,7 +61,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 1", -@@ -66,7 +70,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 2", -@@ -74,7 +79,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 1", -@@ -82,7 +88,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 2", -@@ -90,7 +97,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface 3", -@@ -136,6 +144,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_FW_RST_WSID_S3) - dev_info(&pdev->dev, - "quirk reset_wsid for Surface 3 enabled\n"); -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ dev_info(&pdev->dev, -+ "quirk no_brigde_d3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 3ef7440418e3..a95ebac06e13 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -11,6 +11,7 @@ - * be handled differently. Currently, only S3 is supported. - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) -+#define QUIRK_NO_BRIDGE_D3 BIT(2) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.33.0 - -From 4e5394f910d705c8d749a15610f07224fcbe8383 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 10 Nov 2020 12:49:56 +0100 -Subject: [PATCH] mwifiex: Use non-posted PCI register writes - -On the 88W8897 card it's very important the TX ring write pointer is -updated correctly to its new value before setting the TX ready -interrupt, otherwise the firmware appears to crash (probably because -it's trying to DMA-read from the wrong place). - -Since PCI uses "posted writes" when writing to a register, it's not -guaranteed that a write will happen immediately. That means the pointer -might be outdated when setting the TX ready interrupt, leading to -firmware crashes especially when ASPM L1 and L1 substates are enabled -(because of the higher link latency, the write will probably take -longer). - -So fix those firmware crashes by always forcing non-posted writes. We do -that by simply reading back the register after writing it, just as a lot -of other drivers do. - -There are two reproducers that are fixed with this patch: - -1) During rx/tx traffic and with ASPM L1 substates enabled (the enabled -substates are platform dependent), the firmware crashes and eventually a -command timeout appears in the logs. That crash is fixed by using a -non-posted write in mwifiex_pcie_send_data(). - -2) When sending lots of commands to the card, waking it up from sleep in -very quick intervals, the firmware eventually crashes. That crash -appears to be fixed by some other non-posted write included here. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 0627a37deac8..95a0c436bc1c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -237,6 +237,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) - - iowrite32(data, card->pci_mmap1 + reg); - -+ /* Do a read-back, which makes the write non-posted, ensuring the -+ * completion before returning. -+ * The firmware of the 88W8897 card is buggy and this avoids crashes. -+ */ -+ ioread32(card->pci_mmap1 + reg); -+ - return 0; - } - --- -2.33.0 - -From 031afca1cdca9266d6a61d361bc14b8b4b7834e5 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 3 Nov 2020 13:28:04 +0100 -Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface - devices - -The most recent firmware of the 88W8897 card reports a hardcoded LTR -value to the system during initialization, probably as an (unsuccessful) -attempt of the developers to fix firmware crashes. This LTR value -prevents most of the Microsoft Surface devices from entering deep -powersaving states (either platform C-State 10 or S0ix state), because -the exit latency of that state would be higher than what the card can -tolerate. - -Turns out the card works just the same (including the firmware crashes) -no matter if that hardcoded LTR value is reported or not, so it's kind -of useless and only prevents us from saving power. - -To get rid of those hardcoded LTR reports, it's possible to reset the -PCI bridge device after initializing the cards firmware. I'm not exactly -sure why that works, maybe the power management subsystem of the PCH -resets its stored LTR values when doing a function level reset of the -bridge device. Doing the reset once after starting the wifi firmware -works very well, probably because the firmware only reports that LTR -value a single time during firmware startup. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 31 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 95a0c436bc1c..97e7787e88b0 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -1762,9 +1762,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) - static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; -+ struct pci_dev *pdev = card->dev; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; - int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; - -+ /* Trigger a function level reset of the PCI bridge device, this makes -+ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value -+ * that prevents the system from entering package C10 and S0ix powersaving -+ * states. -+ * We need to do it here because it must happen after firmware -+ * initialization and this function is called after that is done. -+ */ -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ pci_reset_function(parent_pdev); -+ - /* Write the RX ring read pointer in to reg->rx_rdptr */ - if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | - tx_wrap)) { -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index a2aeb2af907e..6885575826a6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -33,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5", -@@ -43,7 +44,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -53,7 +55,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 6", -@@ -62,7 +65,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 1", -@@ -71,7 +75,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 2", -@@ -80,7 +85,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 1", -@@ -89,7 +95,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 2", -@@ -98,7 +105,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface 3", -@@ -147,6 +155,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_NO_BRIDGE_D3) - dev_info(&pdev->dev, - "quirk no_brigde_d3 enabled\n"); -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index a95ebac06e13..4ec2ae72f632 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -12,6 +12,7 @@ - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) - #define QUIRK_NO_BRIDGE_D3 BIT(2) -+#define QUIRK_DO_FLR_ON_BRIDGE BIT(3) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.33.0 - -From 432d62d512afb247aedd76f15e7e9f125b1b4dd5 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sun, 28 Mar 2021 21:10:06 +0200 -Subject: [PATCH] mwifiex: Try waking the firmware until we get an interrupt - -It seems that the firmware of the 88W8897 card sometimes ignores or -misses when we try to wake it up by reading the firmware status -register. This leads to the firmware wakeup timeout expiring and the -driver resetting the card because we assume the firmware has hung up or -crashed (unfortunately that's not unlikely with this card). - -Turns out that most of the time the firmware actually didn't hang up, -but simply "missed" our wakeup request and doesn't send us an AWAKE -event. - -Trying again to read the firmware status register after a short timeout -usually makes the firmware wake we up as expected, so add a small retry -loop to mwifiex_pm_wakeup_card() that looks at the interrupt status to -check whether the card woke up. - -The number of tries and timeout lengths for this were determined -experimentally: The firmware usually takes about 500 us to wake up -after we attempt to read the status register. In some cases where the -firmware is very busy (for example while doing a bluetooth scan) it -might even miss our requests for multiple milliseconds, which is why -after 15 tries the waiting time gets increased to 10 ms. The maximum -number of tries it took to wake the firmware when testing this was -around 20, so a maximum number of 50 tries should give us plenty of -safety margin. - -A good reproducer for this issue is letting the firmware sleep and wake -up in very short intervals, for example by pinging an device on the -network every 0.1 seconds. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 29 ++++++++++++++++----- - 1 file changed, 23 insertions(+), 6 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 97e7787e88b0..a4713019b226 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -666,6 +666,7 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; -+ int n_tries = 0; - - mwifiex_dbg(adapter, EVENT, - "event: Wakeup device...\n"); -@@ -673,12 +674,28 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) - if (reg->sleep_cookie) - mwifiex_pcie_dev_wakeup_delay(adapter); - -- /* Accessing fw_status register will wakeup device */ -- if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) { -- mwifiex_dbg(adapter, ERROR, -- "Writing fw_status register failed\n"); -- return -1; -- } -+ /* Access the fw_status register to wake up the device. -+ * Since the 88W8897 firmware sometimes appears to ignore or miss -+ * that wakeup request, we continue trying until we receive an -+ * interrupt from the card. -+ */ -+ do { -+ if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) { -+ mwifiex_dbg(adapter, ERROR, -+ "Writing fw_status register failed\n"); -+ return -1; -+ } -+ -+ n_tries++; -+ -+ if (n_tries <= 15) -+ usleep_range(400, 700); -+ else -+ msleep(10); -+ } while (n_tries <= 50 && READ_ONCE(adapter->int_status) == 0); -+ -+ mwifiex_dbg(adapter, EVENT, -+ "event: Tried %d times until firmware woke up\n", n_tries); - - if (reg->sleep_cookie) { - mwifiex_pcie_dev_wakeup_delay(adapter); --- -2.33.0 - -From bb21b6f05a27b98ea3212239a3d8ec1401448c0d Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 25 Mar 2021 11:33:02 +0100 -Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell - 88W8897 - -The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) -is used in a lot of Microsoft Surface devices, and all those devices -suffer from very low 2.4GHz wifi connection speeds while bluetooth is -enabled. The reason for that is that the default passive scanning -interval for Bluetooth Low Energy devices is quite high in Linux -(interval of 60 msec and scan window of 30 msec, see hci_core.c), and -the Marvell chip is known for its bad bt+wifi coexisting performance. - -So decrease that passive scan interval and make the scan window shorter -on this particular device to allow for spending more time transmitting -wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and -the new scan window is 6.25 msec (0xa * 0,625 msec). - -This change has a very large impact on the 2.4GHz wifi speeds and gets -it up to performance comparable with the Windows driver, which seems to -apply a similar quirk. - -The interval and window length were tested and found to work very well -with a lot of Bluetooth Low Energy devices, including the Surface Pen, a -Bluetooth Speaker and two modern Bluetooth headphones. All devices were -discovered immediately after turning them on. Even lower values were -also tested, but they introduced longer delays until devices get -discovered. - -Patchset: wifi ---- - drivers/bluetooth/btusb.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index e0859f4e2807..a6a3298c0178 100644 ---- a/drivers/bluetooth/btusb.c -+++ b/drivers/bluetooth/btusb.c -@@ -60,6 +60,7 @@ static struct usb_driver btusb_driver; - #define BTUSB_WIDEBAND_SPEECH 0x400000 - #define BTUSB_VALID_LE_STATES 0x800000 - #define BTUSB_QCA_WCN6855 0x1000000 -+#define BTUSB_LOWER_LESCAN_INTERVAL 0x2000000 - - static const struct usb_device_id btusb_table[] = { - /* Generic Bluetooth USB device */ -@@ -358,6 +359,7 @@ static const struct usb_device_id blacklist_table[] = { - { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, -+ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, - - /* Intel Bluetooth devices */ - { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_NEW | -@@ -4115,6 +4117,19 @@ static int btusb_probe(struct usb_interface *intf, - if (id->driver_info & BTUSB_MARVELL) - hdev->set_bdaddr = btusb_set_bdaddr_marvell; - -+ /* The Marvell 88W8897 combined wifi and bluetooth card is known for -+ * very bad bt+wifi coexisting performance. -+ * -+ * Decrease the passive BT Low Energy scan interval a bit -+ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter -+ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly -+ * higher wifi throughput while passively scanning for BT LE devices. -+ */ -+ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { -+ hdev->le_scan_interval = 0x0190; -+ hdev->le_scan_window = 0x000a; -+ } -+ - if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && - (id->driver_info & BTUSB_MEDIATEK)) { - hdev->setup = btusb_mtk_setup; --- -2.33.0 - -From 7763c0ba58baca69827db1e6e85a739562ecd017 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 12:31:26 +0100 -Subject: [PATCH] mwifiex: Small cleanup for handling virtual interface type - changes - -Handle the obvious invalid virtual interface type changes with a general -check instead of looking at the individual change. - -For type changes from P2P_CLIENT to P2P_GO and the other way round, this -changes the behavior slightly: We now still do nothing, but return --EOPNOTSUPP instead of 0. Now that behavior was incorrect before and -still is, because type changes between these two types are actually -possible and supported, which we'll fix in a following commit. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 39 +++++++------------ - 1 file changed, 14 insertions(+), 25 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index a6b9dc6700b1..d50fd8570475 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1141,6 +1141,20 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return -EBUSY; - } - -+ if (type == NL80211_IFTYPE_UNSPECIFIED) { -+ mwifiex_dbg(priv->adapter, INFO, -+ "%s: no new type specified, keeping old type %d\n", -+ dev->name, curr_iftype); -+ return 0; -+ } -+ -+ if (curr_iftype == type) { -+ mwifiex_dbg(priv->adapter, INFO, -+ "%s: interface already is of type %d\n", -+ dev->name, curr_iftype); -+ return 0; -+ } -+ - switch (curr_iftype) { - case NL80211_IFTYPE_ADHOC: - switch (type) { -@@ -1160,12 +1174,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as IBSS\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_ADHOC: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1191,12 +1199,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as STA\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_STATION: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1214,12 +1216,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_P2P_GO: - return mwifiex_change_vif_to_p2p(dev, curr_iftype, - type, params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as AP\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_AP: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1254,13 +1250,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return -EFAULT; - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as P2P\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", --- -2.33.0 - -From e34bf670e3ce80661536cc88049724f9a58dba2f Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 12:44:39 +0100 -Subject: [PATCH] mwifiex: Use function to check whether interface type change - is allowed - -Instead of bailing out in the function which is supposed to do the type -change, detect invalid changes beforehand using a generic function and -return an error if the change is not allowed. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 139 ++++++++++++------ - 1 file changed, 92 insertions(+), 47 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index d50fd8570475..3a79a55bbfd2 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -939,6 +939,76 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, - return 0; - } - -+static bool -+is_vif_type_change_allowed(struct mwifiex_adapter *adapter, -+ enum nl80211_iftype old_iftype, -+ enum nl80211_iftype new_iftype) -+{ -+ switch (old_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_STATION: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_AP: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return adapter->curr_iface_comb.sta_intf != -+ adapter->iface_limit.sta_intf; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ default: -+ break; -+ } -+ -+ return false; -+} -+ - static int - mwifiex_change_vif_to_p2p(struct net_device *dev, - enum nl80211_iftype curr_iftype, -@@ -955,13 +1025,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - - adapter = priv->adapter; - -- if (adapter->curr_iface_comb.p2p_intf == -- adapter->iface_limit.p2p_intf) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple P2P ifaces\n"); -- return -1; -- } -- - mwifiex_dbg(adapter, INFO, - "%s: changing role to p2p\n", dev->name); - -@@ -1027,15 +1090,6 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - - adapter = priv->adapter; - -- if ((curr_iftype != NL80211_IFTYPE_P2P_CLIENT && -- curr_iftype != NL80211_IFTYPE_P2P_GO) && -- (adapter->curr_iface_comb.sta_intf == -- adapter->iface_limit.sta_intf)) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple station/adhoc ifaces\n"); -- return -1; -- } -- - if (type == NL80211_IFTYPE_STATION) - mwifiex_dbg(adapter, INFO, - "%s: changing role to station\n", dev->name); -@@ -1086,13 +1140,6 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - - adapter = priv->adapter; - -- if (adapter->curr_iface_comb.uap_intf == -- adapter->iface_limit.uap_intf) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple AP ifaces\n"); -- return -1; -- } -- - mwifiex_dbg(adapter, INFO, - "%s: changing role to AP\n", dev->name); - -@@ -1155,6 +1202,13 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return 0; - } - -+ if (!is_vif_type_change_allowed(priv->adapter, curr_iftype, type)) { -+ mwifiex_dbg(priv->adapter, ERROR, -+ "%s: change from type %d to %d is not allowed\n", -+ dev->name, curr_iftype, type); -+ return -EOPNOTSUPP; -+ } -+ - switch (curr_iftype) { - case NL80211_IFTYPE_ADHOC: - switch (type) { -@@ -1175,12 +1229,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_STATION: - switch (type) { - case NL80211_IFTYPE_ADHOC: -@@ -1200,12 +1251,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_AP: - switch (type) { - case NL80211_IFTYPE_ADHOC: -@@ -1217,12 +1265,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_p2p(dev, curr_iftype, - type, params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_P2P_CLIENT: - case NL80211_IFTYPE_P2P_GO: - switch (type) { -@@ -1251,21 +1296,21 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: unknown iftype: %d\n", -- dev->name, dev->ieee80211_ptr->iftype); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } - - - return 0; -+ -+errnotsupp: -+ mwifiex_dbg(priv->adapter, ERROR, -+ "unsupported interface type transition: %d to %d\n", -+ curr_iftype, type); -+ return -EOPNOTSUPP; - } - - static void --- -2.33.0 - -From ede9773f9d8d9a0f65b5e17a4421e7f8862e5832 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 13:33:04 +0100 -Subject: [PATCH] mwifiex: Run SET_BSS_MODE when changing from P2P to STATION - vif-type - -We currently handle changing from the P2P to the STATION virtual -interface type slightly different than changing from P2P to ADHOC: When -changing to STATION, we don't send the SET_BSS_MODE command. We do send -that command on all other type-changes though, and it probably makes -sense to send the command since after all we just changed our BSS_MODE. -Looking at prior changes to this part of the code, it seems that this is -simply a leftover from old refactorings. - -Since sending the SET_BSS_MODE command is the only difference between -mwifiex_change_vif_to_sta_adhoc() and the current code, we can now use -mwifiex_change_vif_to_sta_adhoc() for both switching to ADHOC and -STATION interface type. - -This does not fix any particular bug and just "looked right", so there's -a small chance it might be a regression. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 22 ++++--------------- - 1 file changed, 4 insertions(+), 18 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 3a79a55bbfd2..66e978088061 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1270,29 +1270,15 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - - case NL80211_IFTYPE_P2P_CLIENT: - case NL80211_IFTYPE_P2P_GO: -+ if (mwifiex_cfg80211_deinit_p2p(priv)) -+ return -EFAULT; -+ - switch (type) { -- case NL80211_IFTYPE_STATION: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; -- priv->adapter->curr_iface_comb.p2p_intf--; -- priv->adapter->curr_iface_comb.sta_intf++; -- dev->ieee80211_ptr->iftype = type; -- if (mwifiex_deinit_priv_params(priv)) -- return -1; -- if (mwifiex_init_new_priv_params(priv, dev, type)) -- return -1; -- if (mwifiex_sta_init_cmd(priv, false, false)) -- return -1; -- break; - case NL80211_IFTYPE_ADHOC: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; -+ case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); -- break; - case NL80211_IFTYPE_AP: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: --- -2.33.0 - -From 5f322ab7ea0d62a876f50ef4ca6f40b1b9c10a74 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 14:42:54 +0100 -Subject: [PATCH] mwifiex: Use helper function for counting interface types - -Use a small helper function to increment and decrement the counter of -the interface types we currently manage. This makes the code that -actually changes and sets up the interface type a bit less messy and -also helps avoiding mistakes in case someone increments/decrements a -counter wrongly. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 110 ++++++------------ - 1 file changed, 35 insertions(+), 75 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 66e978088061..db30f595e9f9 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1009,6 +1009,32 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, - return false; - } - -+static void -+update_vif_type_counter(struct mwifiex_adapter *adapter, -+ enum nl80211_iftype iftype, -+ int change) -+{ -+ switch (iftype) { -+ case NL80211_IFTYPE_UNSPECIFIED: -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ adapter->curr_iface_comb.sta_intf += change; -+ break; -+ case NL80211_IFTYPE_AP: -+ adapter->curr_iface_comb.uap_intf += change; -+ break; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ adapter->curr_iface_comb.p2p_intf += change; -+ break; -+ default: -+ mwifiex_dbg(adapter, ERROR, -+ "%s: Unsupported iftype passed: %d\n", -+ __func__, iftype); -+ break; -+ } -+} -+ - static int - mwifiex_change_vif_to_p2p(struct net_device *dev, - enum nl80211_iftype curr_iftype, -@@ -1056,19 +1082,8 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.p2p_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; - - return 0; -@@ -1107,20 +1122,10 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.sta_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; -+ - return 0; - } - -@@ -1153,20 +1158,8 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.uap_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; - return 0; - } -@@ -3114,23 +3107,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, - mwifiex_dev_debugfs_init(priv); - #endif - -- switch (type) { -- case NL80211_IFTYPE_UNSPECIFIED: -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf++; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf++; -- break; -- case NL80211_IFTYPE_P2P_CLIENT: -- adapter->curr_iface_comb.p2p_intf++; -- break; -- default: -- /* This should be dead code; checked above */ -- mwifiex_dbg(adapter, ERROR, "type not supported\n"); -- return ERR_PTR(-EINVAL); -- } -+ update_vif_type_counter(adapter, type, +1); - - return &priv->wdev; - -@@ -3196,24 +3173,7 @@ int mwifiex_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) - /* Clear the priv in adapter */ - priv->netdev = NULL; - -- switch (priv->bss_mode) { -- case NL80211_IFTYPE_UNSPECIFIED: -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- default: -- mwifiex_dbg(adapter, ERROR, -- "del_virtual_intf: type not supported\n"); -- break; -- } -+ update_vif_type_counter(adapter, priv->bss_mode, -1); - - priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; - --- -2.33.0 - -From ccf1a0c04da4ef3eec5848dd00ee71ecf70cd4c0 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:56:58 +0100 -Subject: [PATCH] mwifiex: Update virtual interface counters right after - setting bss_type - -In mwifiex_init_new_priv_params() we update our private driver state to -reflect the currently selected virtual interface type. Most notably we -set the bss_mode to the mode we're going to put the firmware in. - -Now after we updated the driver state we actually start talking to the -firmware and instruct it to set up the new mode. Those commands can and -will sometimes fail, in which case we return with an error from -mwifiex_change_vif_to_*. We currently update our virtual interface type -counters after this return, which means the code is never reached when a -firmware error happens and we never update the counters. Since we have -updated our bss_mode earlier though, the counters now no longer reflect -the actual state of the driver. - -This will break things on the next virtual interface change, because the -virtual interface type we're switching away from didn't get its counter -incremented, and we end up decrementing a 0-counter. - -To fix this, simply update the virtual interface type counters right -after updating our driver structures, so that they are always in sync. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 25 +++++++++++-------- - 1 file changed, 14 insertions(+), 11 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index db30f595e9f9..60de1cec77c7 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1059,6 +1059,10 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; - -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - switch (type) { - case NL80211_IFTYPE_P2P_CLIENT: - if (mwifiex_cfg80211_init_p2p_client(priv)) -@@ -1082,10 +1086,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; -- - return 0; - } - -@@ -1116,16 +1116,17 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - return -1; - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; -+ -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, - HostCmd_ACT_GEN_SET, 0, NULL, true)) - return -1; - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; -- - return 0; - } - -@@ -1152,15 +1153,17 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - return -1; - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; -+ -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, - HostCmd_ACT_GEN_SET, 0, NULL, true)) - return -1; - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; - return 0; - } - /* --- -2.33.0 - -From 4ed65a1a9ca878178854a444ddf8afeb1f5892c7 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 13:42:40 +0100 -Subject: [PATCH] mwifiex: Allow switching interface type from P2P_CLIENT to - P2P_GO - -It's possible to change virtual interface type between P2P_CLIENT and -P2P_GO, the card supports that just fine, and it happens for example -when using miracast with the miraclecast software. - -So allow type changes between P2P_CLIENT and P2P_GO and simply call into -mwifiex_change_vif_to_p2p(), which handles this just fine. We have to -call mwifiex_cfg80211_deinit_p2p() before though to make sure the old -p2p mode is properly uninitialized. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 36 +++++++++++++++++++ - 1 file changed, 36 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 60de1cec77c7..a37b504bd084 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -990,11 +990,26 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, - } - - case NL80211_IFTYPE_P2P_CLIENT: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_P2P_GO: -+ return true; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ - case NL80211_IFTYPE_P2P_GO: - switch (new_iftype) { - case NL80211_IFTYPE_ADHOC: - case NL80211_IFTYPE_STATION: - return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ return true; - case NL80211_IFTYPE_AP: - return adapter->curr_iface_comb.uap_intf != - adapter->iface_limit.uap_intf; -@@ -1265,6 +1280,24 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - } - - case NL80211_IFTYPE_P2P_CLIENT: -+ if (mwifiex_cfg80211_deinit_p2p(priv)) -+ return -EFAULT; -+ -+ switch (type) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, -+ type, params); -+ case NL80211_IFTYPE_P2P_GO: -+ return mwifiex_change_vif_to_p2p(dev, curr_iftype, -+ type, params); -+ case NL80211_IFTYPE_AP: -+ return mwifiex_change_vif_to_ap(dev, curr_iftype, type, -+ params); -+ default: -+ goto errnotsupp; -+ } -+ - case NL80211_IFTYPE_P2P_GO: - if (mwifiex_cfg80211_deinit_p2p(priv)) - return -EFAULT; -@@ -1274,6 +1307,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); -+ case NL80211_IFTYPE_P2P_CLIENT: -+ return mwifiex_change_vif_to_p2p(dev, curr_iftype, -+ type, params); - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); --- -2.33.0 - -From 76192487da2c919208f751c7f527f3f30c0dffc0 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:31:08 +0100 -Subject: [PATCH] mwifiex: Handle interface type changes from AP to STATION - -Looks like this case was simply overseen, so handle it, too. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index a37b504bd084..e65f285e3efe 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1268,6 +1268,7 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - switch (type) { - case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); - break; --- -2.33.0 - -From 11436598ffa34d032944ba65666ff8dfb8759228 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:32:16 +0100 -Subject: [PATCH] mwifiex: Properly initialize private structure on interface - type changes - -When creating a new virtual interface in mwifiex_add_virtual_intf(), we -update our internal driver states like bss_type, bss_priority, bss_role -and bss_mode to reflect the mode the firmware will be set to. - -When switching virtual interface mode using -mwifiex_init_new_priv_params() though, we currently only update bss_mode -and bss_role. In order for the interface mode switch to actually work, -we also need to update bss_type to its proper value, so do that. - -This fixes a crash of the firmware (because the driver tries to execute -commands that are invalid in AP mode) when switching from station mode -to AP mode. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 +++++++--- - 1 file changed, 7 insertions(+), 3 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index e65f285e3efe..a290312313f3 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -908,16 +908,20 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, - switch (type) { - case NL80211_IFTYPE_STATION: - case NL80211_IFTYPE_ADHOC: -- priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_type = MWIFIEX_BSS_TYPE_STA; - break; - case NL80211_IFTYPE_P2P_CLIENT: -- priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - break; - case NL80211_IFTYPE_P2P_GO: -- priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - break; - case NL80211_IFTYPE_AP: - priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_type = MWIFIEX_BSS_TYPE_UAP; - break; - default: - mwifiex_dbg(adapter, ERROR, --- -2.33.0 - -From 9146bb7cf0770adeaa38ab1e456ca69466814e5e Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sat, 27 Mar 2021 12:19:14 +0100 -Subject: [PATCH] mwifiex: Fix copy-paste mistake when creating virtual - interface - -The BSS priority here for a new P2P_CLIENT device was accidentally set -to an enum that's certainly not meant for this. Since -MWIFIEX_BSS_ROLE_STA is 0 anyway, we can just set the bss_priority to 0 -instead here. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index a290312313f3..1e1cf523e228 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -3040,7 +3040,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, - priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - - priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II; -- priv->bss_priority = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_priority = 0; - priv->bss_role = MWIFIEX_BSS_ROLE_STA; - priv->bss_started = 0; - --- -2.33.0 - -From 641ae9461195af00e029d5efbec0b22813ff15d2 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:30:28 +0200 -Subject: [PATCH] mwifiex: Deactive host sleep using HSCFG after it was - activated manually - -When powersaving (so either wifi powersaving or deep sleep, depending on -which state the firmware is in) is disabled, the way the firmware goes -into host sleep is different: Usually the firmware implicitely enters -host sleep on the next SLEEP event we get when we configured host sleep -via HSCFG before. When powersaving is disabled though, there are no -SLEEP events, the way we enter host sleep in that case is different: The -firmware will send us a HS_ACT_REQ event and after that we "manually" -make the firmware enter host sleep by sending it another HSCFG command -with the action HS_ACTIVATE. - -Now waking up from host sleep appears to be different depending on -whether powersaving is enabled again: When powersaving is enabled, the -firmware implicitely leaves host sleep as soon as it wakes up and sends -us an AWAKE event. When powersaving is disabled though, it apparently -doesn't implicitely leave host sleep, but instead we need to send it a -HSCFG command with the HS_CONFIGURE action and the HS_CFG_CANCEL -condition. We didn't do that so far, which is why waking up from host -sleep was broken when powersaving is disabled. - -So add some additional state to mwifiex_adapter where we keep track of -whether host sleep was activated manually via HS_ACTIVATE, and if that -was the case, deactivate it manually again via HS_CFG_CANCEL. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cmdevt.c | 21 +++++++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.c | 18 ++++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmd.c | 4 ++++ - 4 files changed, 44 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cmdevt.c b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -index 3a11342a6bde..5487df8f994d 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cmdevt.c -+++ b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -@@ -608,6 +608,11 @@ int mwifiex_send_cmd(struct mwifiex_private *priv, u16 cmd_no, - return -1; - } - -+ if (priv->adapter->hs_activated_manually && -+ cmd_no != HostCmd_CMD_802_11_HS_CFG_ENH) { -+ mwifiex_cancel_hs(priv, MWIFIEX_ASYNC_CMD); -+ priv->adapter->hs_activated_manually = false; -+ } - - /* Get a new command node */ - cmd_node = mwifiex_get_cmd_node(adapter); -@@ -714,6 +719,15 @@ mwifiex_insert_cmd_to_pending_q(struct mwifiex_adapter *adapter, - } - } - -+ /* Same with exit host sleep cmd, luckily that can't happen at the same time as EXIT_PS */ -+ if (command == HostCmd_CMD_802_11_HS_CFG_ENH) { -+ struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = -+ &host_cmd->params.opt_hs_cfg; -+ -+ if (le16_to_cpu(hs_cfg->action) == HS_ACTIVATE) -+ add_tail = false; -+ } -+ - spin_lock_bh(&adapter->cmd_pending_q_lock); - if (add_tail) - list_add_tail(&cmd_node->list, &adapter->cmd_pending_q); -@@ -1216,6 +1230,13 @@ mwifiex_process_hs_config(struct mwifiex_adapter *adapter) - __func__); - - adapter->if_ops.wakeup(adapter); -+ -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv (adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - adapter->hs_activated = false; - clear_bit(MWIFIEX_IS_HS_CONFIGURED, &adapter->work_flags); - clear_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags); -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index 6283df5aaaf8..b30547d1f153 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -401,6 +401,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !adapter->scan_processing) && - !adapter->data_sent && - !skb_queue_empty(&adapter->tx_data_q)) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_process_tx_queue(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -@@ -418,6 +424,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !mwifiex_bypass_txlist_empty(adapter) && - !mwifiex_is_tdls_chan_switching - (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_process_bypass_tx(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -@@ -434,6 +446,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !adapter->data_sent && !mwifiex_wmm_lists_empty(adapter) && - !mwifiex_is_tdls_chan_switching - (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_wmm_process_tx(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 5923c5c14c8d..90012cbcfd15 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -986,6 +986,7 @@ struct mwifiex_adapter { - struct timer_list wakeup_timer; - struct mwifiex_hs_config_param hs_cfg; - u8 hs_activated; -+ u8 hs_activated_manually; - u16 hs_activate_wait_q_woken; - wait_queue_head_t hs_activate_wait_q; - u8 event_body[MAX_EVENT_SIZE]; -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -index d3a968ef21ef..76db9a7b8199 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -@@ -396,6 +396,10 @@ mwifiex_cmd_802_11_hs_cfg(struct mwifiex_private *priv, - if (hs_activate) { - hs_cfg->action = cpu_to_le16(HS_ACTIVATE); - hs_cfg->params.hs_activate.resp_ctrl = cpu_to_le16(RESP_NEEDED); -+ -+ adapter->hs_activated_manually = true; -+ mwifiex_dbg(priv->adapter, CMD, -+ "cmd: Activating host sleep manually\n"); - } else { - hs_cfg->action = cpu_to_le16(HS_CONFIGURE); - hs_cfg->params.hs_config.conditions = hscfg_param->conditions; --- -2.33.0 - -From 0b983e2872f4416a627caa305cc0276443555c9c Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:23:05 +0200 -Subject: [PATCH] mwifiex: Add quirk to disable deep sleep with certain - hardware revision - -The 88W8897 pcie card with the hardware revision 20 apparently has a -hardware issue where the card wakes up from deep sleep randomly and very -often, somewhat depending on the card activity, maybe the hardware has a -floating wakeup pin or something. - -Those continuous wakeups prevent the card from entering host sleep when -the computer suspends. And because the host won't answer to events from -the card anymore while it's suspended, the firmwares internal -powersaving state machine seems to get confused and the card can't sleep -anymore at all after that. - -Since we can't work around that hardware bug in the firmware, let's -get the hardware revision string from the firmware and match it with -known bad revisions. Then disable auto deep sleep for those revisions, -which makes sure we no longer get those spurious wakeups. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/main.c | 14 ++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmdresp.c | 16 ++++++++++++++++ - 3 files changed, 31 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index b30547d1f153..347d29fd92c0 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -226,6 +226,19 @@ static int mwifiex_process_rx(struct mwifiex_adapter *adapter) - return 0; - } - -+static void maybe_quirk_fw_disable_ds(struct mwifiex_adapter *adapter) -+{ -+ struct mwifiex_private *priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA); -+ struct mwifiex_ver_ext ver_ext; -+ -+ set_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &adapter->work_flags); -+ -+ memset(&ver_ext, 0, sizeof(ver_ext)); -+ ver_ext.version_str_sel = 1; -+ mwifiex_send_cmd(priv, HostCmd_CMD_VERSION_EXT, -+ HostCmd_ACT_GEN_GET, 0, &ver_ext, false); -+} -+ - /* - * The main process. - * -@@ -356,6 +369,7 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - if (adapter->hw_status == MWIFIEX_HW_STATUS_INIT_DONE) { - adapter->hw_status = MWIFIEX_HW_STATUS_READY; - mwifiex_init_fw_complete(adapter); -+ maybe_quirk_fw_disable_ds(adapter); - } - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 90012cbcfd15..1e829d84b1f6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -524,6 +524,7 @@ enum mwifiex_adapter_work_flags { - MWIFIEX_IS_SUSPENDED, - MWIFIEX_IS_HS_CONFIGURED, - MWIFIEX_IS_HS_ENABLING, -+ MWIFIEX_IS_REQUESTING_FW_VEREXT, - }; - - struct mwifiex_band_config { -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -index 119ccacd1fcc..fb245adba19f 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -@@ -707,6 +707,22 @@ static int mwifiex_ret_ver_ext(struct mwifiex_private *priv, - { - struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext; - -+ if (test_and_clear_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &priv->adapter->work_flags)) { -+ if (strncmp(ver_ext->version_str, "ChipRev:20, BB:9b(10.00), RF:40(21)", 128) == 0) { -+ struct mwifiex_ds_auto_ds auto_ds = { -+ .auto_ds = DEEP_SLEEP_OFF, -+ }; -+ -+ mwifiex_dbg(priv->adapter, MSG, -+ "Bad HW revision detected, disabling deep sleep\n"); -+ -+ mwifiex_send_cmd(priv, HostCmd_CMD_802_11_PS_MODE_ENH, -+ DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, false); -+ } -+ -+ return 0; -+ } -+ - if (version_ext) { - version_ext->version_str_sel = ver_ext->version_str_sel; - memcpy(version_ext->version_str, ver_ext->version_str, --- -2.33.0 - -From 72db0d979c61e664beb338aff7558a40bd50c585 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 15:17:07 +0100 -Subject: [PATCH] mwifiex: Don't log error on suspend if wake-on-wlan is - disabled - -It's not an error if someone chooses to put their computer to sleep, not -wanting it to wake up because the person next door has just discovered -what a magic packet is. So change the loglevel of this annoying message -from ERROR to INFO. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 1e1cf523e228..1cdd66c37cfc 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -3480,7 +3480,7 @@ static int mwifiex_cfg80211_suspend(struct wiphy *wiphy, - } - - if (!wowlan) { -- mwifiex_dbg(adapter, ERROR, -+ mwifiex_dbg(adapter, INFO, - "None of the WOWLAN triggers enabled\n"); - ret = 0; - goto done; --- -2.33.0 - -From 8f21280f53919658a62bddd3074dff3149d5ee0f Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sun, 28 Mar 2021 21:42:54 +0200 -Subject: [PATCH] mwifiex: Log an error on command failure during key-material - upload - -Sometimes the KEY_MATERIAL command can fail with the 88W8897 firmware -(when this happens exactly seems pretty random). This appears to prevent -the access point from starting, so it seems like a good idea to log an -error in that case. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 ++++++++-- - 1 file changed, 8 insertions(+), 2 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 1cdd66c37cfc..6ad935c1bb47 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -519,8 +519,14 @@ mwifiex_cfg80211_set_default_mgmt_key(struct wiphy *wiphy, - encrypt_key.is_igtk_def_key = true; - eth_broadcast_addr(encrypt_key.mac_addr); - -- return mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, -- HostCmd_ACT_GEN_SET, true, &encrypt_key, true); -+ if (mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, -+ HostCmd_ACT_GEN_SET, true, &encrypt_key, true)) { -+ mwifiex_dbg(priv->adapter, ERROR, -+ "Sending KEY_MATERIAL command failed\n"); -+ return -1; -+ } -+ -+ return 0; - } - - /* --- -2.33.0 - -From f04acb15a376038a69aa59e71a69641b6f26ab41 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:44:03 +0200 -Subject: [PATCH] mwifiex: Fix an incorrect comment - -We're sending DELBA requests here, not ADDBA requests. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/11n.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c -index 6696bce56178..b0695432b26a 100644 ---- a/drivers/net/wireless/marvell/mwifiex/11n.c -+++ b/drivers/net/wireless/marvell/mwifiex/11n.c -@@ -125,7 +125,7 @@ int mwifiex_ret_11n_delba(struct mwifiex_private *priv, - tx_ba_tbl->ra); - } else { /* - * In case of failure, recreate the deleted stream in case -- * we initiated the ADDBA -+ * we initiated the DELBA - */ - if (!INITIATOR_BIT(del_ba_param_set)) - return 0; --- -2.33.0 - -From 7e38959b0035469e0e21c1013978325c01c3b311 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:45:59 +0200 -Subject: [PATCH] mwifiex: Send DELBA requests according to spec - -We're currently failing to set the initiator bit for DELBA requests: -While we set the bit on our del_ba_param_set bitmask, we forget to -actually copy that bitmask over to the command struct, which means we -never actually set the initiator bit. - -Fix that and copy the bitmask over to the host_cmd_ds_11n_delba command -struct. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/11n.c | 5 +++-- - 1 file changed, 3 insertions(+), 2 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c -index b0695432b26a..9ff2058bcd7e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/11n.c -+++ b/drivers/net/wireless/marvell/mwifiex/11n.c -@@ -657,14 +657,15 @@ int mwifiex_send_delba(struct mwifiex_private *priv, int tid, u8 *peer_mac, - uint16_t del_ba_param_set; - - memset(&delba, 0, sizeof(delba)); -- delba.del_ba_param_set = cpu_to_le16(tid << DELBA_TID_POS); - -- del_ba_param_set = le16_to_cpu(delba.del_ba_param_set); -+ del_ba_param_set = tid << DELBA_TID_POS; -+ - if (initiator) - del_ba_param_set |= IEEE80211_DELBA_PARAM_INITIATOR_MASK; - else - del_ba_param_set &= ~IEEE80211_DELBA_PARAM_INITIATOR_MASK; - -+ delba.del_ba_param_set = cpu_to_le16(del_ba_param_set); - memcpy(&delba.peer_mac_addr, peer_mac, ETH_ALEN); - - /* We don't wait for the response of this command */ --- -2.33.0 - -From b326904b93eebe73866ce9ca95bb20e83571333e Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:57:41 +0200 -Subject: [PATCH] mwifiex: Ignore BTCOEX events from the firmware - -The firmware of the pcie 88W8897 chip sends those events very -unreliably, which means we sometimes end up actually capping the window -size while bluetooth is disabled, artifically limiting wifi speeds even -though it's not needed. - -Since we can't fix the firmware, let's just ignore those events, it -seems that the Windows driver also doesn't change the rx/tx block ack -buffer sizes when bluetooth gets enabled or disabled, so this is -consistent with the Windows driver. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/sta_event.c | 4 +--- - 1 file changed, 1 insertion(+), 3 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_event.c b/drivers/net/wireless/marvell/mwifiex/sta_event.c -index bc79ca4cb803..5e9e67d97857 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_event.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_event.c -@@ -1056,9 +1056,7 @@ int mwifiex_process_sta_event(struct mwifiex_private *priv) - adapter->event_skb); - break; - case EVENT_BT_COEX_WLAN_PARA_CHANGE: -- dev_dbg(adapter->dev, "EVENT: BT coex wlan param update\n"); -- mwifiex_bt_coex_wlan_param_update_event(priv, -- adapter->event_skb); -+ dev_dbg(adapter->dev, "EVENT: ignoring BT coex wlan param update\n"); - break; - case EVENT_RXBA_SYNC: - dev_dbg(adapter->dev, "EVENT: RXBA_SYNC\n"); --- -2.33.0 - diff --git a/patches/5.10/0003-ipts.patch b/patches/5.10/0003-ipts.patch deleted file mode 100644 index 9a2cc88cc..000000000 --- a/patches/5.10/0003-ipts.patch +++ /dev/null @@ -1,1534 +0,0 @@ -From 19d7a04915b579648b560f0bcd82f2c583446f73 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 30 Jul 2020 13:21:53 +0200 -Subject: [PATCH] misc: mei: Add missing IPTS device IDs - -Patchset: ipts ---- - drivers/misc/mei/hw-me-regs.h | 1 + - drivers/misc/mei/pci-me.c | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h -index cb34925e10f1..2b3f8073a3ec 100644 ---- a/drivers/misc/mei/hw-me-regs.h -+++ b/drivers/misc/mei/hw-me-regs.h -@@ -92,6 +92,7 @@ - #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ - - #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ -+#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ - - #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ - -diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c -index c3393b383e59..0098f98426c1 100644 ---- a/drivers/misc/mei/pci-me.c -+++ b/drivers/misc/mei/pci-me.c -@@ -96,6 +96,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { - {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, -+ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_H, MEI_ME_PCH15_SPS_CFG)}, --- -2.33.0 - -From 2b81528640fa54949d13417c2dcba4886c333328 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 25 Feb 2021 09:37:47 +0100 -Subject: [PATCH] misc: mei: Remove client devices before shutting down bus - -Patchset: ipts ---- - drivers/misc/mei/init.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c -index bcee77768b91..21ed765003e1 100644 ---- a/drivers/misc/mei/init.c -+++ b/drivers/misc/mei/init.c -@@ -302,10 +302,10 @@ void mei_stop(struct mei_device *dev) - { - dev_dbg(dev->dev, "stopping the device.\n"); - -+ mei_cl_bus_remove_devices(dev); - mutex_lock(&dev->device_lock); - mei_set_devstate(dev, MEI_DEV_POWER_DOWN); - mutex_unlock(&dev->device_lock); -- mei_cl_bus_remove_devices(dev); - - mei_cancel_work(dev); - --- -2.33.0 - -From 42fa4d55a93cbcebdfa6cb7b18896f594baee444 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 6 Aug 2020 11:20:41 +0200 -Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus - -Based on linux-surface/intel-precise-touch@3f362c - -Signed-off-by: Dorian Stoll -Patchset: ipts ---- - drivers/misc/Kconfig | 1 + - drivers/misc/Makefile | 1 + - drivers/misc/ipts/Kconfig | 17 ++ - drivers/misc/ipts/Makefile | 12 ++ - drivers/misc/ipts/context.h | 47 +++++ - drivers/misc/ipts/control.c | 113 +++++++++++ - drivers/misc/ipts/control.h | 24 +++ - drivers/misc/ipts/mei.c | 127 +++++++++++++ - drivers/misc/ipts/protocol.h | 347 ++++++++++++++++++++++++++++++++++ - drivers/misc/ipts/receiver.c | 224 ++++++++++++++++++++++ - drivers/misc/ipts/receiver.h | 16 ++ - drivers/misc/ipts/resources.c | 128 +++++++++++++ - drivers/misc/ipts/resources.h | 17 ++ - drivers/misc/ipts/uapi.c | 208 ++++++++++++++++++++ - drivers/misc/ipts/uapi.h | 47 +++++ - 15 files changed, 1329 insertions(+) - create mode 100644 drivers/misc/ipts/Kconfig - create mode 100644 drivers/misc/ipts/Makefile - create mode 100644 drivers/misc/ipts/context.h - create mode 100644 drivers/misc/ipts/control.c - create mode 100644 drivers/misc/ipts/control.h - create mode 100644 drivers/misc/ipts/mei.c - create mode 100644 drivers/misc/ipts/protocol.h - create mode 100644 drivers/misc/ipts/receiver.c - create mode 100644 drivers/misc/ipts/receiver.h - create mode 100644 drivers/misc/ipts/resources.c - create mode 100644 drivers/misc/ipts/resources.h - create mode 100644 drivers/misc/ipts/uapi.c - create mode 100644 drivers/misc/ipts/uapi.h - -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index fafa8b0d8099..c795c56e8d42 100644 ---- a/drivers/misc/Kconfig -+++ b/drivers/misc/Kconfig -@@ -481,4 +481,5 @@ source "drivers/misc/ocxl/Kconfig" - source "drivers/misc/cardreader/Kconfig" - source "drivers/misc/habanalabs/Kconfig" - source "drivers/misc/uacce/Kconfig" -+source "drivers/misc/ipts/Kconfig" - endmenu -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index d23231e73330..9e6e3e2f2ea9 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -57,3 +57,4 @@ obj-$(CONFIG_HABANA_AI) += habanalabs/ - obj-$(CONFIG_UACCE) += uacce/ - obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o - obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o -+obj-$(CONFIG_MISC_IPTS) += ipts/ -diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig -new file mode 100644 -index 000000000000..83e2a930c396 ---- /dev/null -+++ b/drivers/misc/ipts/Kconfig -@@ -0,0 +1,17 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+ -+config MISC_IPTS -+ tristate "Intel Precise Touch & Stylus" -+ depends on INTEL_MEI -+ help -+ Say Y here if your system has a touchscreen using Intels -+ Precise Touch & Stylus (IPTS) technology. -+ -+ If unsure say N. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ipts. -+ -+ Building this driver alone will not give you a working touchscreen. -+ It only exposed a userspace API that can be used by a daemon to -+ receive and process data from the touchscreen hardware. -diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile -new file mode 100644 -index 000000000000..8f58b9adbc94 ---- /dev/null -+++ b/drivers/misc/ipts/Makefile -@@ -0,0 +1,12 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+# -+# Makefile for the IPTS touchscreen driver -+# -+ -+obj-$(CONFIG_MISC_IPTS) += ipts.o -+ipts-objs := control.o -+ipts-objs += mei.o -+ipts-objs += receiver.o -+ipts-objs += resources.o -+ipts-objs += uapi.o -+ -diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h -new file mode 100644 -index 000000000000..f4b06a2d3f72 ---- /dev/null -+++ b/drivers/misc/ipts/context.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTEXT_H_ -+#define _IPTS_CONTEXT_H_ -+ -+#include -+#include -+#include -+#include -+ -+#include "protocol.h" -+ -+enum ipts_host_status { -+ IPTS_HOST_STATUS_STARTING, -+ IPTS_HOST_STATUS_STARTED, -+ IPTS_HOST_STATUS_STOPPING, -+ IPTS_HOST_STATUS_STOPPED, -+}; -+ -+struct ipts_buffer_info { -+ u8 *address; -+ dma_addr_t dma_address; -+}; -+ -+struct ipts_context { -+ struct mei_cl_device *cldev; -+ struct device *dev; -+ -+ bool restart; -+ enum ipts_host_status status; -+ struct ipts_get_device_info_rsp device_info; -+ -+ struct ipts_buffer_info data[IPTS_BUFFERS]; -+ struct ipts_buffer_info doorbell; -+ -+ struct ipts_buffer_info feedback[IPTS_BUFFERS]; -+ struct ipts_buffer_info workqueue; -+ struct ipts_buffer_info host2me; -+}; -+ -+#endif /* _IPTS_CONTEXT_H_ */ -diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c -new file mode 100644 -index 000000000000..a1d1f97a13d7 ---- /dev/null -+++ b/drivers/misc/ipts/control.c -@@ -0,0 +1,113 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+#include "protocol.h" -+#include "resources.h" -+#include "uapi.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload, -+ size_t size) -+{ -+ int ret; -+ struct ipts_command cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_command)); -+ cmd.code = code; -+ -+ if (payload && size > 0) -+ memcpy(&cmd.payload, payload, size); -+ -+ ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size); -+ if (ret >= 0) -+ return 0; -+ -+ /* -+ * During shutdown the device might get pulled away from below our feet. -+ * Dont log an error in this case, because it will confuse people. -+ */ -+ if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING) -+ dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret); -+ -+ return ret; -+} -+ -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) -+{ -+ struct ipts_feedback_cmd cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); -+ cmd.buffer = buffer; -+ -+ return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, -+ sizeof(struct ipts_feedback_cmd)); -+} -+ -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value) -+{ -+ struct ipts_feedback_buffer *feedback; -+ -+ memset(ipts->host2me.address, 0, ipts->device_info.feedback_size); -+ feedback = (struct ipts_feedback_buffer *)ipts->host2me.address; -+ -+ feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; -+ feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; -+ feedback->buffer = IPTS_HOST2ME_BUFFER; -+ feedback->size = 2; -+ feedback->payload[0] = report; -+ feedback->payload[1] = value; -+ -+ return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER); -+} -+ -+int ipts_control_start(struct ipts_context *ipts) -+{ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Starting IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STARTING; -+ ipts->restart = false; -+ -+ ipts_uapi_link(ipts); -+ return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); -+} -+ -+int ipts_control_stop(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPING) -+ return -EBUSY; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Stopping IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STOPPING; -+ -+ ipts_uapi_unlink(); -+ ipts_resources_free(ipts); -+ -+ ret = ipts_control_send_feedback(ipts, 0); -+ if (ret == -ENODEV) -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ return ret; -+} -+ -+int ipts_control_restart(struct ipts_context *ipts) -+{ -+ if (ipts->restart) -+ return -EBUSY; -+ -+ ipts->restart = true; -+ return ipts_control_stop(ipts); -+} -diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h -new file mode 100644 -index 000000000000..2c44e9e0e99f ---- /dev/null -+++ b/drivers/misc/ipts/control.h -@@ -0,0 +1,24 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTROL_H_ -+#define _IPTS_CONTROL_H_ -+ -+#include -+ -+#include "context.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, -+ size_t size); -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value); -+int ipts_control_start(struct ipts_context *ipts); -+int ipts_control_restart(struct ipts_context *ipts); -+int ipts_control_stop(struct ipts_context *ipts); -+ -+#endif /* _IPTS_CONTROL_H_ */ -diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c -new file mode 100644 -index 000000000000..b3805a91383d ---- /dev/null -+++ b/drivers/misc/ipts/mei.c -@@ -0,0 +1,127 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "receiver.h" -+#include "uapi.h" -+ -+static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev) -+{ -+ int ret; -+ -+ ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)); -+ if (!ret) -+ return 0; -+ -+ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); -+} -+ -+static int ipts_mei_probe(struct mei_cl_device *cldev, -+ const struct mei_cl_device_id *id) -+{ -+ int ret; -+ struct ipts_context *ipts; -+ -+ if (ipts_mei_set_dma_mask(cldev)) { -+ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n"); -+ return -EFAULT; -+ } -+ -+ ret = mei_cldev_enable(cldev); -+ if (ret) { -+ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); -+ return ret; -+ } -+ -+ ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); -+ if (!ipts) { -+ mei_cldev_disable(cldev); -+ return -ENOMEM; -+ } -+ -+ ipts->cldev = cldev; -+ ipts->dev = &cldev->dev; -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ mei_cldev_set_drvdata(cldev, ipts); -+ mei_cldev_register_rx_cb(cldev, ipts_receiver_callback); -+ -+ return ipts_control_start(ipts); -+} -+ -+static int ipts_mei_remove(struct mei_cl_device *cldev) -+{ -+ int i; -+ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); -+ -+ ipts_control_stop(ipts); -+ -+ for (i = 0; i < 20; i++) { -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ break; -+ -+ msleep(25); -+ } -+ -+ mei_cldev_disable(cldev); -+ kfree(ipts); -+ -+ return 0; -+} -+ -+static struct mei_cl_device_id ipts_mei_device_id_table[] = { -+ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, -+ {}, -+}; -+MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); -+ -+static struct mei_cl_driver ipts_mei_driver = { -+ .id_table = ipts_mei_device_id_table, -+ .name = "ipts", -+ .probe = ipts_mei_probe, -+ .remove = ipts_mei_remove, -+}; -+ -+static int __init ipts_mei_init(void) -+{ -+ int ret; -+ -+ ret = ipts_uapi_init(); -+ if (ret) -+ return ret; -+ -+ ret = mei_cldev_driver_register(&ipts_mei_driver); -+ if (ret) { -+ ipts_uapi_free(); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static void __exit ipts_mei_exit(void) -+{ -+ mei_cldev_driver_unregister(&ipts_mei_driver); -+ ipts_uapi_free(); -+} -+ -+MODULE_DESCRIPTION("IPTS touchscreen driver"); -+MODULE_AUTHOR("Dorian Stoll "); -+MODULE_LICENSE("GPL"); -+ -+module_init(ipts_mei_init); -+module_exit(ipts_mei_exit); -diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h -new file mode 100644 -index 000000000000..c3458904a94d ---- /dev/null -+++ b/drivers/misc/ipts/protocol.h -@@ -0,0 +1,347 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_PROTOCOL_H_ -+#define _IPTS_PROTOCOL_H_ -+ -+#include -+ -+/* -+ * The MEI client ID for IPTS functionality. -+ */ -+#define IPTS_MEI_UUID \ -+ UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, \ -+ 0x02, 0xae, 0x04) -+ -+/* -+ * Queries the device for vendor specific information. -+ * -+ * The command must not contain any payload. -+ * The response will contain struct ipts_get_device_info_rsp as payload. -+ */ -+#define IPTS_CMD_GET_DEVICE_INFO 0x00000001 -+#define IPTS_RSP_GET_DEVICE_INFO 0x80000001 -+ -+/* -+ * Sets the mode that IPTS will operate in. -+ * -+ * The command must contain struct ipts_set_mode_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MODE 0x00000002 -+#define IPTS_RSP_SET_MODE 0x80000002 -+ -+/* -+ * Configures the memory buffers that the ME will use -+ * for passing data to the host. -+ * -+ * The command must contain struct ipts_set_mem_window_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MEM_WINDOW 0x00000003 -+#define IPTS_RSP_SET_MEM_WINDOW 0x80000003 -+ -+/* -+ * Signals that the host is ready to receive data to the ME. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_READY_FOR_DATA 0x00000005 -+#define IPTS_RSP_READY_FOR_DATA 0x80000005 -+ -+/* -+ * Signals that a buffer can be refilled to the ME. -+ * -+ * The command must contain struct ipts_feedback_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_FEEDBACK 0x00000006 -+#define IPTS_RSP_FEEDBACK 0x80000006 -+ -+/* -+ * Resets the data flow from the ME to the hosts and -+ * clears the buffers that were set with SET_MEM_WINDOW. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007 -+#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007 -+ -+/* -+ * Instructs the ME to reset the touch sensor. -+ * -+ * The command must contain struct ipts_reset_sensor_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_RESET_SENSOR 0x0000000B -+#define IPTS_RSP_RESET_SENSOR 0x8000000B -+ -+/** -+ * enum ipts_status - Possible status codes returned by IPTS commands. -+ * @IPTS_STATUS_SUCCESS: Operation completed successfully. -+ * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. -+ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. -+ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. -+ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. -+ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. -+ * The host must wait for a response before sending another -+ * command of the same type. -+ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it -+ * has not been initialized yet, or the system is improperly -+ * configured. -+ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. -+ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. -+ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. -+ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. -+ * The host can ignore this error and attempt to continue. -+ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. -+ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. -+ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. -+ * @IPTS_STATUS_TIMEOUT: The operation timed out. -+ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. -+ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. -+ * Further progress is not possible. -+ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. -+ * The host can attempt to continue. -+ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. -+ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. -+ */ -+enum ipts_status { -+ IPTS_STATUS_SUCCESS = 0, -+ IPTS_STATUS_INVALID_PARAMS = 1, -+ IPTS_STATUS_ACCESS_DENIED = 2, -+ IPTS_STATUS_CMD_SIZE_ERROR = 3, -+ IPTS_STATUS_NOT_READY = 4, -+ IPTS_STATUS_REQUEST_OUTSTANDING = 5, -+ IPTS_STATUS_NO_SENSOR_FOUND = 6, -+ IPTS_STATUS_OUT_OF_MEMORY = 7, -+ IPTS_STATUS_INTERNAL_ERROR = 8, -+ IPTS_STATUS_SENSOR_DISABLED = 9, -+ IPTS_STATUS_COMPAT_CHECK_FAIL = 10, -+ IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, -+ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, -+ IPTS_STATUS_RESET_FAILED = 13, -+ IPTS_STATUS_TIMEOUT = 14, -+ IPTS_STATUS_TEST_MODE_FAIL = 15, -+ IPTS_STATUS_SENSOR_FAIL_FATAL = 16, -+ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, -+ IPTS_STATUS_INVALID_DEVICE_CAPS = 18, -+ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, -+}; -+ -+/* -+ * The amount of buffers that is used for IPTS -+ */ -+#define IPTS_BUFFERS 16 -+ -+/* -+ * The special buffer ID that is used for direct host2me feedback. -+ */ -+#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS -+ -+/** -+ * enum ipts_mode - Operation mode for IPTS hardware -+ * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. -+ * The data is received as a HID report with ID 64. -+ * @IPTS_MODE_MULTITOUCH: The "proper" operation mode for IPTS. It will return -+ * stylus data as well as capacitive heatmap touch data. -+ * This data needs to be processed in userspace. -+ */ -+enum ipts_mode { -+ IPTS_MODE_SINGLETOUCH = 0, -+ IPTS_MODE_MULTITOUCH = 1, -+}; -+ -+/** -+ * struct ipts_set_mode_cmd - Payload for the SET_MODE command. -+ * @mode: The mode that IPTS should operate in. -+ */ -+struct ipts_set_mode_cmd { -+ enum ipts_mode mode; -+ u8 reserved[12]; -+} __packed; -+ -+#define IPTS_WORKQUEUE_SIZE 8192 -+#define IPTS_WORKQUEUE_ITEM_SIZE 16 -+ -+/** -+ * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command. -+ * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses. -+ * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses. -+ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. -+ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. -+ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. -+ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. -+ * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses. -+ * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses. -+ * @host2me_addr_lower: Lower 32 bits of the host2me buffer address. -+ * @host2me_addr_upper: Upper 32 bits of the host2me buffer address. -+ * @workqueue_item_size: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) -+ * @workqueue_size: Magic value. (IPTS_WORKQUEUE_SIZE) -+ * -+ * The data buffers are buffers that get filled with touch data by the ME. -+ * The doorbell buffer is a u32 that gets incremented by the ME once a data -+ * buffer has been filled with new data. -+ * -+ * The other buffers are required for using GuC submission with binary -+ * firmware. Since support for GuC submission has been dropped from i915, -+ * they are not used anymore, but they need to be allocated and passed, -+ * otherwise the hardware will refuse to start. -+ */ -+struct ipts_set_mem_window_cmd { -+ u32 data_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 data_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 workqueue_addr_lower; -+ u32 workqueue_addr_upper; -+ u32 doorbell_addr_lower; -+ u32 doorbell_addr_upper; -+ u32 feedback_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 feedback_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 host2me_addr_lower; -+ u32 host2me_addr_upper; -+ u32 host2me_size; -+ u8 reserved1; -+ u8 workqueue_item_size; -+ u16 workqueue_size; -+ u8 reserved[32]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_cmd - Payload for the FEEDBACK command. -+ * @buffer: The buffer that the ME should refill. -+ */ -+struct ipts_feedback_cmd { -+ u32 buffer; -+ u8 reserved[12]; -+} __packed; -+ -+/** -+ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. -+ */ -+enum ipts_feedback_cmd_type { -+ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, -+ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, -+ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, -+}; -+ -+/** -+ * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains. -+ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. -+ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. -+ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. -+ */ -+enum ipts_feedback_data_type { -+ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, -+ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, -+ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, -+ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, -+ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, -+}; -+ -+/** -+ * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer. -+ * @cmd_type: A command that should be executed on the sensor. -+ * @size: The size of the payload to be written. -+ * @buffer: The ID of the buffer that contains this feedback data. -+ * @protocol: The protocol version of the EDS. -+ * @data_type: The type of payload that the buffer contains. -+ * @spi_offset: The offset at which to write the payload data. -+ * @payload: Payload for the feedback command, or 0 if no payload is sent. -+ */ -+struct ipts_feedback_buffer { -+ enum ipts_feedback_cmd_type cmd_type; -+ u32 size; -+ u32 buffer; -+ u32 protocol; -+ enum ipts_feedback_data_type data_type; -+ u32 spi_offset; -+ u8 reserved[40]; -+ u8 payload[]; -+} __packed; -+ -+/** -+ * enum ipts_reset_type - Possible ways of resetting the touch sensor -+ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. -+ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. -+ */ -+enum ipts_reset_type { -+ IPTS_RESET_TYPE_HARD = 0, -+ IPTS_RESET_TYPE_SOFT = 1, -+}; -+ -+/** -+ * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. -+ * @type: What type of reset should be performed. -+ */ -+struct ipts_reset_sensor_cmd { -+ enum ipts_reset_type type; -+ u8 reserved[4]; -+} __packed; -+ -+/** -+ * struct ipts_command - A message sent from the host to the ME. -+ * @code: The message code describing the command. (see IPTS_CMD_*) -+ * @payload: Payload for the command, or 0 if no payload is required. -+ */ -+struct ipts_command { -+ u32 code; -+ u8 payload[320]; -+} __packed; -+ -+/** -+ * struct ipts_device_info - Payload for the GET_DEVICE_INFO response. -+ * @vendor_id: Vendor ID of the touch sensor. -+ * @device_id: Device ID of the touch sensor. -+ * @hw_rev: Hardware revision of the touch sensor. -+ * @fw_rev: Firmware revision of the touch sensor. -+ * @data_size: Required size of one data buffer. -+ * @feedback_size: Required size of one feedback buffer. -+ * @mode: Current operation mode of IPTS. -+ * @max_contacts: The amount of concurrent touches supported by the sensor. -+ */ -+struct ipts_get_device_info_rsp { -+ u16 vendor_id; -+ u16 device_id; -+ u32 hw_rev; -+ u32 fw_rev; -+ u32 data_size; -+ u32 feedback_size; -+ enum ipts_mode mode; -+ u8 max_contacts; -+ u8 reserved[19]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_rsp - Payload for the FEEDBACK response. -+ * @buffer: The buffer that has received feedback. -+ */ -+struct ipts_feedback_rsp { -+ u32 buffer; -+} __packed; -+ -+/** -+ * struct ipts_response - A message sent from the ME to the host. -+ * @code: The message code describing the response. (see IPTS_RSP_*) -+ * @status: The status code returned by the command. -+ * @payload: Payload returned by the command. -+ */ -+struct ipts_response { -+ u32 code; -+ enum ipts_status status; -+ u8 payload[80]; -+} __packed; -+ -+#endif /* _IPTS_PROTOCOL_H_ */ -diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c -new file mode 100644 -index 000000000000..23dca13c2139 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.c -@@ -0,0 +1,224 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "resources.h" -+ -+/* -+ * Temporary parameter to guard gen7 multitouch mode. -+ * Remove once gen7 has stable iptsd support. -+ */ -+static bool gen7mt; -+module_param(gen7mt, bool, 0644); -+ -+static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_set_mode_cmd cmd; -+ -+ memcpy(&ipts->device_info, rsp->payload, -+ sizeof(struct ipts_get_device_info_rsp)); -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd)); -+ cmd.mode = IPTS_MODE_MULTITOUCH; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd, -+ sizeof(struct ipts_set_mode_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mode(struct ipts_context *ipts) -+{ -+ int i, ret; -+ struct ipts_set_mem_window_cmd cmd; -+ -+ ret = ipts_resources_alloc(ipts); -+ if (ret) { -+ dev_err(ipts->dev, "Failed to allocate resources\n"); -+ return ret; -+ } -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ cmd.data_buffer_addr_lower[i] = -+ lower_32_bits(ipts->data[i].dma_address); -+ -+ cmd.data_buffer_addr_upper[i] = -+ upper_32_bits(ipts->data[i].dma_address); -+ -+ cmd.feedback_buffer_addr_lower[i] = -+ lower_32_bits(ipts->feedback[i].dma_address); -+ -+ cmd.feedback_buffer_addr_upper[i] = -+ upper_32_bits(ipts->feedback[i].dma_address); -+ } -+ -+ cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); -+ cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); -+ -+ cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); -+ cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); -+ -+ cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); -+ cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); -+ -+ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; -+ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, -+ sizeof(struct ipts_set_mem_window_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ dev_info(ipts->dev, "Device %04hX:%04hX ready\n", -+ ipts->device_info.vendor_id, ipts->device_info.device_id); -+ ipts->status = IPTS_HOST_STATUS_STARTED; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); -+ if (ret) -+ return ret; -+ -+ if (!gen7mt) -+ return 0; -+ -+ return ipts_control_set_feature(ipts, 0x5, 0x1); -+} -+ -+static int ipts_receiver_handle_feedback(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_feedback_rsp feedback; -+ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPING) -+ return 0; -+ -+ memcpy(&feedback, rsp->payload, sizeof(feedback)); -+ -+ if (feedback.buffer < IPTS_BUFFERS - 1) -+ return ipts_control_send_feedback(ipts, feedback.buffer + 1); -+ -+ return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); -+} -+ -+static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) -+{ -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ if (ipts->restart) -+ return ipts_control_start(ipts); -+ -+ return 0; -+} -+ -+static bool ipts_receiver_sensor_was_reset(u32 status) -+{ -+ return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || -+ status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; -+} -+ -+static bool ipts_receiver_handle_error(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ bool error; -+ -+ switch (rsp->status) { -+ case IPTS_STATUS_SUCCESS: -+ case IPTS_STATUS_COMPAT_CHECK_FAIL: -+ error = false; -+ break; -+ case IPTS_STATUS_INVALID_PARAMS: -+ error = rsp->code != IPTS_RSP_FEEDBACK; -+ break; -+ case IPTS_STATUS_SENSOR_DISABLED: -+ error = ipts->status != IPTS_HOST_STATUS_STOPPING; -+ break; -+ default: -+ error = true; -+ break; -+ } -+ -+ if (!error) -+ return false; -+ -+ dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code, -+ rsp->status); -+ -+ if (ipts_receiver_sensor_was_reset(rsp->status)) { -+ dev_err(ipts->dev, "Sensor was reset\n"); -+ -+ if (ipts_control_restart(ipts)) -+ dev_err(ipts->dev, "Failed to restart IPTS\n"); -+ } -+ -+ return true; -+} -+ -+static void ipts_receiver_handle_response(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ int ret; -+ -+ if (ipts_receiver_handle_error(ipts, rsp)) -+ return; -+ -+ switch (rsp->code) { -+ case IPTS_RSP_GET_DEVICE_INFO: -+ ret = ipts_receiver_handle_get_device_info(ipts, rsp); -+ break; -+ case IPTS_RSP_SET_MODE: -+ ret = ipts_receiver_handle_set_mode(ipts); -+ break; -+ case IPTS_RSP_SET_MEM_WINDOW: -+ ret = ipts_receiver_handle_set_mem_window(ipts); -+ break; -+ case IPTS_RSP_FEEDBACK: -+ ret = ipts_receiver_handle_feedback(ipts, rsp); -+ break; -+ case IPTS_RSP_CLEAR_MEM_WINDOW: -+ ret = ipts_receiver_handle_clear_mem_window(ipts); -+ break; -+ default: -+ ret = 0; -+ break; -+ } -+ -+ if (!ret) -+ return; -+ -+ dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n", -+ rsp->code, ret); -+ -+ if (ipts_control_stop(ipts)) -+ dev_err(ipts->dev, "Failed to stop IPTS\n"); -+} -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev) -+{ -+ int ret; -+ struct ipts_response rsp; -+ struct ipts_context *ipts; -+ -+ ipts = mei_cldev_get_drvdata(cldev); -+ -+ ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response)); -+ if (ret <= 0) { -+ dev_err(ipts->dev, "Error while reading response: %d\n", ret); -+ return; -+ } -+ -+ ipts_receiver_handle_response(ipts, &rsp); -+} -diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h -new file mode 100644 -index 000000000000..7f075afa7ef8 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.h -@@ -0,0 +1,16 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RECEIVER_H_ -+#define _IPTS_RECEIVER_H_ -+ -+#include -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev); -+ -+#endif /* _IPTS_RECEIVER_H_ */ -diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c -new file mode 100644 -index 000000000000..8e3a2409e438 ---- /dev/null -+++ b/drivers/misc/ipts/resources.c -@@ -0,0 +1,128 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+ -+void ipts_resources_free(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, data_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ if (ipts->doorbell.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->doorbell.address, -+ ipts->doorbell.dma_address); -+ -+ ipts->doorbell.address = NULL; -+ ipts->doorbell.dma_address = 0; -+ } -+ -+ if (ipts->workqueue.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->workqueue.address, -+ ipts->workqueue.dma_address); -+ -+ ipts->workqueue.address = NULL; -+ ipts->workqueue.dma_address = 0; -+ } -+ -+ if (ipts->host2me.address) { -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ ipts->host2me.address, -+ ipts->host2me.dma_address); -+ -+ ipts->host2me.address = NULL; -+ ipts->host2me.dma_address = 0; -+ } -+} -+ -+int ipts_resources_alloc(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, data_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ ipts->doorbell.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->doorbell.dma_address, GFP_KERNEL); -+ -+ if (!ipts->doorbell.address) -+ goto release_resources; -+ -+ ipts->workqueue.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->workqueue.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ ipts->host2me.address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &ipts->host2me.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ return 0; -+ -+release_resources: -+ -+ ipts_resources_free(ipts); -+ return -ENOMEM; -+} -diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h -new file mode 100644 -index 000000000000..fdac0eee9156 ---- /dev/null -+++ b/drivers/misc/ipts/resources.h -@@ -0,0 +1,17 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RESOURCES_H_ -+#define _IPTS_RESOURCES_H_ -+ -+#include "context.h" -+ -+int ipts_resources_alloc(struct ipts_context *ipts); -+void ipts_resources_free(struct ipts_context *ipts); -+ -+#endif /* _IPTS_RESOURCES_H_ */ -diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c -new file mode 100644 -index 000000000000..598f0710ad64 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.c -@@ -0,0 +1,208 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "uapi.h" -+ -+struct ipts_uapi uapi; -+ -+static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count, -+ loff_t *offset) -+{ -+ int buffer; -+ int maxbytes; -+ struct ipts_context *ipts = uapi.ipts; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ maxbytes = ipts->device_info.data_size - *offset; -+ if (maxbytes <= 0 || count > maxbytes) -+ return -EINVAL; -+ -+ if (copy_to_user(buf, ipts->data[buffer].address + *offset, count)) -+ return -EFAULT; -+ -+ return count; -+} -+ -+static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ u8 ready = 0; -+ -+ if (ipts) -+ ready = ipts->status == IPTS_HOST_STATUS_STARTED; -+ -+ if (copy_to_user(buffer, &ready, sizeof(u8))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ struct ipts_device_info info; -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ info.vendor = ipts->device_info.vendor_id; -+ info.product = ipts->device_info.device_id; -+ info.version = ipts->device_info.fw_rev; -+ info.buffer_size = ipts->device_info.data_size; -+ info.max_contacts = ipts->device_info.max_contacts; -+ -+ if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts, -+ struct file *file) -+{ -+ int ret; -+ u32 buffer; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ ret = ipts_control_send_feedback(ipts, buffer); -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) -+{ -+ int ret; -+ struct ipts_reset_sensor_cmd cmd; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); -+ cmd.type = IPTS_RESET_TYPE_SOFT; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, -+ sizeof(struct ipts_reset_sensor_cmd)); -+ -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) -+{ -+ struct ipts_context *ipts = uapi.ipts; -+ -+ switch (cmd) { -+ case IPTS_IOCTL_GET_DEVICE_READY: -+ return ipts_uapi_ioctl_get_device_ready(ipts, arg); -+ case IPTS_IOCTL_GET_DEVICE_INFO: -+ return ipts_uapi_ioctl_get_device_info(ipts, arg); -+ case IPTS_IOCTL_GET_DOORBELL: -+ return ipts_uapi_ioctl_get_doorbell(ipts, arg); -+ case IPTS_IOCTL_SEND_FEEDBACK: -+ return ipts_uapi_ioctl_send_feedback(ipts, file); -+ case IPTS_IOCTL_SEND_RESET: -+ return ipts_uapi_ioctl_send_reset(ipts); -+ default: -+ return -ENOTTY; -+ } -+} -+ -+static const struct file_operations ipts_uapi_fops = { -+ .owner = THIS_MODULE, -+ .read = ipts_uapi_read, -+ .unlocked_ioctl = ipts_uapi_ioctl, -+#ifdef CONFIG_COMPAT -+ .compat_ioctl = ipts_uapi_ioctl, -+#endif -+}; -+ -+void ipts_uapi_link(struct ipts_context *ipts) -+{ -+ uapi.ipts = ipts; -+} -+ -+void ipts_uapi_unlink(void) -+{ -+ uapi.ipts = NULL; -+} -+ -+int ipts_uapi_init(void) -+{ -+ int i, major; -+ -+ alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts"); -+ uapi.class = class_create(THIS_MODULE, "ipts"); -+ -+ major = MAJOR(uapi.dev); -+ -+ cdev_init(&uapi.cdev, &ipts_uapi_fops); -+ uapi.cdev.owner = THIS_MODULE; -+ cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ device_create(uapi.class, NULL, MKDEV(major, i), NULL, -+ "ipts/%d", i); -+ } -+ -+ return 0; -+} -+ -+void ipts_uapi_free(void) -+{ -+ int i; -+ int major; -+ -+ major = MAJOR(uapi.dev); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) -+ device_destroy(uapi.class, MKDEV(major, i)); -+ -+ cdev_del(&uapi.cdev); -+ -+ unregister_chrdev_region(MKDEV(major, 0), MINORMASK); -+ class_destroy(uapi.class); -+} -diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h -new file mode 100644 -index 000000000000..53fb86a88f97 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_UAPI_H_ -+#define _IPTS_UAPI_H_ -+ -+#include -+ -+#include "context.h" -+ -+struct ipts_uapi { -+ dev_t dev; -+ struct class *class; -+ struct cdev cdev; -+ -+ struct ipts_context *ipts; -+}; -+ -+struct ipts_device_info { -+ __u16 vendor; -+ __u16 product; -+ __u32 version; -+ __u32 buffer_size; -+ __u8 max_contacts; -+ -+ /* For future expansion */ -+ __u8 reserved[19]; -+}; -+ -+#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8) -+#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info) -+#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) -+#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04) -+#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) -+ -+void ipts_uapi_link(struct ipts_context *ipts); -+void ipts_uapi_unlink(void); -+ -+int ipts_uapi_init(void); -+void ipts_uapi_free(void); -+ -+#endif /* _IPTS_UAPI_H_ */ --- -2.33.0 - diff --git a/patches/5.10/0004-surface-gpe.patch b/patches/5.10/0004-surface-gpe.patch deleted file mode 100644 index b275b23fc..000000000 --- a/patches/5.10/0004-surface-gpe.patch +++ /dev/null @@ -1,401 +0,0 @@ -From c8e815dba673cb2bee346f332dc1ddd5aae837e3 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 16 Aug 2020 23:39:56 +0200 -Subject: [PATCH] platform/x86: Add Driver to set up lid GPEs on MS Surface - device - -Conventionally, wake-up events for a specific device, in our case the -lid device, are managed via the ACPI _PRW field. While this does not -seem strictly necessary based on ACPI spec, the kernel disables GPE -wakeups to avoid non-wakeup interrupts preventing suspend by default and -only enables GPEs associated via the _PRW field with a wake-up capable -device. This behavior has been introduced in commit - - f941d3e41da7f86bdb9dcc1977c2bcc6b89bfe47 - ACPI: EC / PM: Disable non-wakeup GPEs for suspend-to-idle - -and is described in more detail in its commit message. - -Unfortunately, on MS Surface devices, there is no _PRW field present on -the lid device, thus no GPE is associated with it, and therefore the GPE -responsible for sending the status-change notification to the lid gets -disabled during suspend, making it impossible to wake the device via the -lid. - -This patch introduces a pseudo-device and respective driver which, based -on some DMI matching, mark the corresponding GPE of the lid device for -wake and enable it during suspend. The behavior of this driver models -the behavior of the ACPI/PM core for normal wakeup GPEs, properly -declared via the _PRW field. - -Signed-off-by: Maximilian Luz -Patchset: surface-gpe ---- - drivers/platform/x86/Kconfig | 9 + - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/surface_gpe.c | 321 +++++++++++++++++++++++++++++ - 3 files changed, 331 insertions(+) - create mode 100644 drivers/platform/x86/surface_gpe.c - -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index a1858689d6e1..11327030b721 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -901,6 +901,15 @@ config SURFACE_PRO3_BUTTON - help - This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet. - -+config SURFACE_GPE -+ tristate "Surface GPE/Lid Driver" -+ depends on ACPI -+ help -+ This driver marks the GPEs related to the ACPI lid device found on -+ Microsoft Surface devices as wakeup sources and prepares them -+ accordingly. It is required on those devices to allow wake-ups from -+ suspend by opening the lid. -+ - config MSI_LAPTOP - tristate "MSI Laptop Extras" - depends on ACPI -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 5f823f7eff45..c0d1c753eb3c 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -86,6 +86,7 @@ obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o - obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o - obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o -+obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - - # MSI - obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o -diff --git a/drivers/platform/x86/surface_gpe.c b/drivers/platform/x86/surface_gpe.c -new file mode 100644 -index 000000000000..86f6991b1215 ---- /dev/null -+++ b/drivers/platform/x86/surface_gpe.c -@@ -0,0 +1,321 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Surface GPE/Lid driver to enable wakeup from suspend via the lid by -+ * properly configuring the respective GPEs. Required for wakeup via lid on -+ * newer Intel-based Microsoft Surface devices. -+ * -+ * Copyright (C) 2020 Maximilian Luz -+ */ -+ -+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -+ -+#include -+#include -+#include -+#include -+#include -+ -+/* -+ * Note: The GPE numbers for the lid devices found below have been obtained -+ * from ACPI/the DSDT table, specifically from the GPE handler for the -+ * lid. -+ */ -+ -+static const struct property_entry lid_device_props_l17[] = { -+ PROPERTY_ENTRY_U32("gpe", 0x17), -+ {}, -+}; -+ -+static const struct property_entry lid_device_props_l4D[] = { -+ PROPERTY_ENTRY_U32("gpe", 0x4D), -+ {}, -+}; -+ -+static const struct property_entry lid_device_props_l4F[] = { -+ PROPERTY_ENTRY_U32("gpe", 0x4F), -+ {}, -+}; -+ -+static const struct property_entry lid_device_props_l57[] = { -+ PROPERTY_ENTRY_U32("gpe", 0x57), -+ {}, -+}; -+ -+/* -+ * Note: When changing this, don't forget to check that the MODULE_ALIAS below -+ * still fits. -+ */ -+static const struct dmi_system_id dmi_lid_device_table[] = { -+ { -+ .ident = "Surface Pro 4", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), -+ }, -+ .driver_data = (void *)lid_device_props_l17, -+ }, -+ { -+ .ident = "Surface Pro 5", -+ .matches = { -+ /* -+ * We match for SKU here due to generic product name -+ * "Surface Pro". -+ */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), -+ }, -+ .driver_data = (void *)lid_device_props_l4F, -+ }, -+ { -+ .ident = "Surface Pro 5 (LTE)", -+ .matches = { -+ /* -+ * We match for SKU here due to generic product name -+ * "Surface Pro" -+ */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), -+ }, -+ .driver_data = (void *)lid_device_props_l4F, -+ }, -+ { -+ .ident = "Surface Pro 6", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), -+ }, -+ .driver_data = (void *)lid_device_props_l4F, -+ }, -+ { -+ .ident = "Surface Pro 7", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"), -+ }, -+ .driver_data = (void *)lid_device_props_l4D, -+ }, -+ { -+ .ident = "Surface Book 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), -+ }, -+ .driver_data = (void *)lid_device_props_l17, -+ }, -+ { -+ .ident = "Surface Book 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), -+ }, -+ .driver_data = (void *)lid_device_props_l17, -+ }, -+ { -+ .ident = "Surface Book 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 3"), -+ }, -+ .driver_data = (void *)lid_device_props_l4D, -+ }, -+ { -+ .ident = "Surface Laptop 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), -+ }, -+ .driver_data = (void *)lid_device_props_l57, -+ }, -+ { -+ .ident = "Surface Laptop 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), -+ }, -+ .driver_data = (void *)lid_device_props_l57, -+ }, -+ { -+ .ident = "Surface Laptop 3 (Intel 13\")", -+ .matches = { -+ /* -+ * We match for SKU here due to different variants: The -+ * AMD (15") version does not rely on GPEs. -+ */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"), -+ }, -+ .driver_data = (void *)lid_device_props_l4D, -+ }, -+ { -+ .ident = "Surface Laptop 3 (Intel 15\")", -+ .matches = { -+ /* -+ * We match for SKU here due to different variants: The -+ * AMD (15") version does not rely on GPEs. -+ */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1872"), -+ }, -+ .driver_data = (void *)lid_device_props_l4D, -+ }, -+ { } -+}; -+ -+struct surface_lid_device { -+ u32 gpe_number; -+}; -+ -+static int surface_lid_enable_wakeup(struct device *dev, bool enable) -+{ -+ const struct surface_lid_device *lid = dev_get_drvdata(dev); -+ int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE; -+ acpi_status status; -+ -+ status = acpi_set_gpe_wake_mask(NULL, lid->gpe_number, action); -+ if (ACPI_FAILURE(status)) { -+ dev_err(dev, "failed to set GPE wake mask: %s\n", -+ acpi_format_exception(status)); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int __maybe_unused surface_gpe_suspend(struct device *dev) -+{ -+ return surface_lid_enable_wakeup(dev, true); -+} -+ -+static int __maybe_unused surface_gpe_resume(struct device *dev) -+{ -+ return surface_lid_enable_wakeup(dev, false); -+} -+ -+static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume); -+ -+static int surface_gpe_probe(struct platform_device *pdev) -+{ -+ struct surface_lid_device *lid; -+ u32 gpe_number; -+ acpi_status status; -+ int ret; -+ -+ ret = device_property_read_u32(&pdev->dev, "gpe", &gpe_number); -+ if (ret) { -+ dev_err(&pdev->dev, "failed to read 'gpe' property: %d\n", ret); -+ return ret; -+ } -+ -+ lid = devm_kzalloc(&pdev->dev, sizeof(*lid), GFP_KERNEL); -+ if (!lid) -+ return -ENOMEM; -+ -+ lid->gpe_number = gpe_number; -+ platform_set_drvdata(pdev, lid); -+ -+ status = acpi_mark_gpe_for_wake(NULL, gpe_number); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&pdev->dev, "failed to mark GPE for wake: %s\n", -+ acpi_format_exception(status)); -+ return -EINVAL; -+ } -+ -+ status = acpi_enable_gpe(NULL, gpe_number); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&pdev->dev, "failed to enable GPE: %s\n", -+ acpi_format_exception(status)); -+ return -EINVAL; -+ } -+ -+ ret = surface_lid_enable_wakeup(&pdev->dev, false); -+ if (ret) -+ acpi_disable_gpe(NULL, gpe_number); -+ -+ return ret; -+} -+ -+static int surface_gpe_remove(struct platform_device *pdev) -+{ -+ struct surface_lid_device *lid = dev_get_drvdata(&pdev->dev); -+ -+ /* restore default behavior without this module */ -+ surface_lid_enable_wakeup(&pdev->dev, false); -+ acpi_disable_gpe(NULL, lid->gpe_number); -+ -+ return 0; -+} -+ -+static struct platform_driver surface_gpe_driver = { -+ .probe = surface_gpe_probe, -+ .remove = surface_gpe_remove, -+ .driver = { -+ .name = "surface_gpe", -+ .pm = &surface_gpe_pm, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+static struct platform_device *surface_gpe_device; -+ -+static int __init surface_gpe_init(void) -+{ -+ const struct dmi_system_id *match; -+ struct platform_device *pdev; -+ struct fwnode_handle *fwnode; -+ int status; -+ -+ match = dmi_first_match(dmi_lid_device_table); -+ if (!match) { -+ pr_info("no compatible Microsoft Surface device found, exiting\n"); -+ return -ENODEV; -+ } -+ -+ status = platform_driver_register(&surface_gpe_driver); -+ if (status) -+ return status; -+ -+ fwnode = fwnode_create_software_node(match->driver_data, NULL); -+ if (IS_ERR(fwnode)) { -+ status = PTR_ERR(fwnode); -+ goto err_node; -+ } -+ -+ pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE); -+ if (!pdev) { -+ status = -ENOMEM; -+ goto err_alloc; -+ } -+ -+ pdev->dev.fwnode = fwnode; -+ -+ status = platform_device_add(pdev); -+ if (status) -+ goto err_add; -+ -+ surface_gpe_device = pdev; -+ return 0; -+ -+err_add: -+ platform_device_put(pdev); -+err_alloc: -+ fwnode_remove_software_node(fwnode); -+err_node: -+ platform_driver_unregister(&surface_gpe_driver); -+ return status; -+} -+module_init(surface_gpe_init); -+ -+static void __exit surface_gpe_exit(void) -+{ -+ struct fwnode_handle *fwnode = surface_gpe_device->dev.fwnode; -+ -+ platform_device_unregister(surface_gpe_device); -+ platform_driver_unregister(&surface_gpe_driver); -+ fwnode_remove_software_node(fwnode); -+} -+module_exit(surface_gpe_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface GPE/Lid Driver"); -+MODULE_LICENSE("GPL"); -+MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurface*:*"); --- -2.33.0 - diff --git a/patches/5.10/0005-surface-sam-over-hid.patch b/patches/5.10/0005-surface-sam-over-hid.patch deleted file mode 100644 index dd9a84c19..000000000 --- a/patches/5.10/0005-surface-sam-over-hid.patch +++ /dev/null @@ -1,335 +0,0 @@ -From 4d4a140ae77897688918e39798fbb9745f653155 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jul 2020 17:19:53 +0200 -Subject: [PATCH] i2c: acpi: Implement RawBytes read access - -Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C -device via a generic serial bus operation region and RawBytes read -access. On the Surface Book 1, this access is required to turn on (and -off) the discrete GPU. - -Multiple things are to note here: - -a) The RawBytes access is device/driver dependent. The ACPI - specification states: - - > Raw accesses assume that the writer has knowledge of the bus that - > the access is made over and the device that is being accessed. The - > protocol may only ensure that the buffer is transmitted to the - > appropriate driver, but the driver must be able to interpret the - > buffer to communicate to a register. - - Thus this implementation may likely not work on other devices - accessing I2C via the RawBytes accessor type. - -b) The MSHW0030 I2C device is an HID-over-I2C device which seems to - serve multiple functions: - - 1. It is the main access point for the legacy-type Surface Aggregator - Module (also referred to as SAM-over-HID, as opposed to the newer - SAM-over-SSH/UART). It has currently not been determined on how - support for the legacy SAM should be implemented. Likely via a - custom HID driver. - - 2. It seems to serve as the HID device for the Integrated Sensor Hub. - This might complicate matters with regards to implementing a - SAM-over-HID driver required by legacy SAM. - -In light of this, the simplest approach has been chosen for now. -However, it may make more sense regarding breakage and compatibility to -either provide functionality for replacing or enhancing the default -operation region handler via some additional API functions, or even to -completely blacklist MSHW0030 from the I2C core and provide a custom -driver for it. - -Replacing/enhancing the default operation region handler would, however, -either require some sort of secondary driver and access point for it, -from which the new API functions would be called and the new handler -(part) would be installed, or hard-coding them via some sort of -quirk-like interface into the I2C core. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam-over-hid ---- - drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ - 1 file changed, 35 insertions(+) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index 37c510d9347a..aed579942436 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -574,6 +574,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, - return (ret == 1) ? 0 : -EIO; - } - -+static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, -+ u8 *data, u8 data_len) -+{ -+ struct i2c_msg msgs[1]; -+ int ret = AE_OK; -+ -+ msgs[0].addr = client->addr; -+ msgs[0].flags = client->flags; -+ msgs[0].len = data_len + 1; -+ msgs[0].buf = data; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ -+ if (ret < 0) { -+ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); -+ return ret; -+ } -+ -+ /* 1 transfer must have completed successfully */ -+ return (ret == 1) ? 0 : -EIO; -+} -+ - static acpi_status - i2c_acpi_space_handler(u32 function, acpi_physical_address command, - u32 bits, u64 *value64, -@@ -675,6 +697,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, - } - break; - -+ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: -+ if (action == ACPI_READ) { -+ dev_warn(&adapter->dev, -+ "protocol 0x%02x not supported for client 0x%02x\n", -+ accessor_type, client->addr); -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } else { -+ status = acpi_gsb_i2c_write_raw_bytes(client, -+ gsb->data, info->access_length); -+ } -+ break; -+ - default: - dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", - accessor_type, client->addr); --- -2.33.0 - -From d3d2e299bab6c49cd10a49c4f745277a9247dd7a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 6 Sep 2020 04:01:19 +0200 -Subject: [PATCH] platform/x86: Add driver for Surface Book 1 dGPU switch - -Add driver exposing the discrete GPU power-switch of the Microsoft -Surface Book 1 to user-space. - -On the Surface Book 1, the dGPU power is controlled via the Surface -System Aggregator Module (SAM). The specific SAM-over-HID command for -this is exposed via ACPI. This module provides a simple driver exposing -the ACPI call via a sysfs parameter to user-space, so that users can -easily power-on/-off the dGPU. - -Patchset: surface-sam-over-hid ---- - drivers/platform/x86/Kconfig | 7 ++ - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/sb1_dgpu_sw.c | 162 +++++++++++++++++++++++++++++ - 3 files changed, 170 insertions(+) - create mode 100644 drivers/platform/x86/sb1_dgpu_sw.c - -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 11327030b721..6a53f63bde62 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -910,6 +910,13 @@ config SURFACE_GPE - accordingly. It is required on those devices to allow wake-ups from - suspend by opening the lid. - -+config SURFACE_BOOK1_DGPU_SWITCH -+ tristate "Surface Book 1 dGPU Switch Driver" -+ depends on ACPI && SYSFS -+ help -+ This driver provides a sysfs switch to set the power-state of the -+ discrete GPU found on the Microsoft Surface Book 1. -+ - config MSI_LAPTOP - tristate "MSI Laptop Extras" - depends on ACPI -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index c0d1c753eb3c..562d83940e7b 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -87,6 +87,7 @@ obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o - obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o -+obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += sb1_dgpu_sw.o - - # MSI - obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o -diff --git a/drivers/platform/x86/sb1_dgpu_sw.c b/drivers/platform/x86/sb1_dgpu_sw.c -new file mode 100644 -index 000000000000..8c66ed5110fd ---- /dev/null -+++ b/drivers/platform/x86/sb1_dgpu_sw.c -@@ -0,0 +1,162 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+ -+#include -+#include -+#include -+#include -+ -+ -+#ifdef pr_fmt -+#undef pr_fmt -+#endif -+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ -+ -+ -+static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, -+ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); -+ -+#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" -+#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" -+#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" -+ -+ -+static int sb1_dgpu_sw_dsmcall(void) -+{ -+ union acpi_object *ret; -+ acpi_handle handle; -+ acpi_status status; -+ -+ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); -+ if (status) -+ return -EINVAL; -+ -+ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); -+ if (!ret) -+ return -EINVAL; -+ -+ ACPI_FREE(ret); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgon(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGON: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-on dGPU via HGON\n"); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgof(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGOF: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-off dGPU via HGOF\n"); -+ return 0; -+} -+ -+ -+static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ int status, value; -+ -+ status = kstrtoint(buf, 0, &value); -+ if (status < 0) -+ return status; -+ -+ if (value != 1) -+ return -EINVAL; -+ -+ status = sb1_dgpu_sw_dsmcall(); -+ -+ return status < 0 ? status : len; -+} -+ -+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ bool power; -+ int status; -+ -+ status = kstrtobool(buf, &power); -+ if (status < 0) -+ return status; -+ -+ if (power) -+ status = sb1_dgpu_sw_hgon(); -+ else -+ status = sb1_dgpu_sw_hgof(); -+ -+ return status < 0 ? status : len; -+} -+ -+static DEVICE_ATTR_WO(dgpu_dsmcall); -+static DEVICE_ATTR_WO(dgpu_power); -+ -+static struct attribute *sb1_dgpu_sw_attrs[] = { -+ &dev_attr_dgpu_dsmcall.attr, -+ &dev_attr_dgpu_power.attr, -+ NULL, -+}; -+ -+static const struct attribute_group sb1_dgpu_sw_attr_group = { -+ .attrs = sb1_dgpu_sw_attrs, -+}; -+ -+ -+static int sb1_dgpu_sw_probe(struct platform_device *pdev) -+{ -+ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+} -+ -+static int sb1_dgpu_sw_remove(struct platform_device *pdev) -+{ -+ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+ return 0; -+} -+ -+/* -+ * The dGPU power seems to be actually handled by MSHW0040. However, that is -+ * also the power-/volume-button device with a mainline driver. So let's use -+ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. -+ */ -+static const struct acpi_device_id sb1_dgpu_sw_match[] = { -+ { "MSHW0041", }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); -+ -+static struct platform_driver sb1_dgpu_sw = { -+ .probe = sb1_dgpu_sw_probe, -+ .remove = sb1_dgpu_sw_remove, -+ .driver = { -+ .name = "sb1_dgpu_sw", -+ .acpi_match_table = sb1_dgpu_sw_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(sb1_dgpu_sw); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); -+MODULE_LICENSE("GPL"); --- -2.33.0 - diff --git a/patches/5.10/0006-surface-sam.patch b/patches/5.10/0006-surface-sam.patch deleted file mode 100644 index a334acfbf..000000000 --- a/patches/5.10/0006-surface-sam.patch +++ /dev/null @@ -1,20758 +0,0 @@ -From 4625c51cd75374e658046b43f26607bff6030a5d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 17 Aug 2020 01:23:20 +0200 -Subject: [PATCH] Add file2alias support for Surface Aggregator Module devices - -Implement file2alias support for Surface System Aggregator Module (SSAM) -devices. This allows modules to be auto-loaded for specific devices via -their respective module alias. - -Patchset: surface-sam ---- - include/linux/mod_devicetable.h | 17 +++++++++++++++++ - scripts/mod/devicetable-offsets.c | 7 +++++++ - scripts/mod/file2alias.c | 21 +++++++++++++++++++++ - 3 files changed, 45 insertions(+) - -diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h -index 5b08a473cdba..ef64063fac30 100644 ---- a/include/linux/mod_devicetable.h -+++ b/include/linux/mod_devicetable.h -@@ -838,4 +838,21 @@ struct mhi_device_id { - kernel_ulong_t driver_data; - }; - -+/* Surface System Aggregator Module */ -+ -+#define SSAM_MATCH_CHANNEL 0x1 -+#define SSAM_MATCH_INSTANCE 0x2 -+#define SSAM_MATCH_FUNCTION 0x4 -+ -+struct ssam_device_id { -+ __u8 match_flags; -+ -+ __u8 category; -+ __u8 channel; -+ __u8 instance; -+ __u8 function; -+ -+ kernel_ulong_t driver_data; -+}; -+ - #endif /* LINUX_MOD_DEVICETABLE_H */ -diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c -index 27007c18e754..bcff122d0dc8 100644 ---- a/scripts/mod/devicetable-offsets.c -+++ b/scripts/mod/devicetable-offsets.c -@@ -243,5 +243,12 @@ int main(void) - DEVID(mhi_device_id); - DEVID_FIELD(mhi_device_id, chan); - -+ DEVID(ssam_device_id); -+ DEVID_FIELD(ssam_device_id, match_flags); -+ DEVID_FIELD(ssam_device_id, category); -+ DEVID_FIELD(ssam_device_id, channel); -+ DEVID_FIELD(ssam_device_id, instance); -+ DEVID_FIELD(ssam_device_id, function); -+ - return 0; - } -diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c -index 2417dd1dee33..a6c583362b92 100644 ---- a/scripts/mod/file2alias.c -+++ b/scripts/mod/file2alias.c -@@ -1368,6 +1368,26 @@ static int do_mhi_entry(const char *filename, void *symval, char *alias) - return 1; - } - -+/* Looks like: ssam:cNtNiNfN -+ * -+ * N is exactly 2 digits, where each is an upper-case hex digit. -+ */ -+static int do_ssam_entry(const char *filename, void *symval, char *alias) -+{ -+ DEF_FIELD(symval, ssam_device_id, match_flags); -+ DEF_FIELD(symval, ssam_device_id, category); -+ DEF_FIELD(symval, ssam_device_id, channel); -+ DEF_FIELD(symval, ssam_device_id, instance); -+ DEF_FIELD(symval, ssam_device_id, function); -+ -+ sprintf(alias, "ssam:c%02X", category); -+ ADD(alias, "t", match_flags & SSAM_MATCH_CHANNEL, channel); -+ ADD(alias, "i", match_flags & SSAM_MATCH_INSTANCE, instance); -+ ADD(alias, "f", match_flags & SSAM_MATCH_FUNCTION, function); -+ -+ return 1; -+} -+ - /* Does namelen bytes of name exactly match the symbol? */ - static bool sym_is(const char *name, unsigned namelen, const char *symbol) - { -@@ -1442,6 +1462,7 @@ static const struct devtable devtable[] = { - {"tee", SIZE_tee_client_device_id, do_tee_entry}, - {"wmi", SIZE_wmi_device_id, do_wmi_entry}, - {"mhi", SIZE_mhi_device_id, do_mhi_entry}, -+ {"ssam", SIZE_ssam_device_id, do_ssam_entry}, - }; - - /* Create MODULE_ALIAS() statements. --- -2.33.0 - -From e9a441abff000491b2839085ab2237849b070be2 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 17 Aug 2020 01:44:30 +0200 -Subject: [PATCH] platform/x86: Add support for Surface System Aggregator - Module - -Add support for the Surface System Aggregator Module (SSAM), an embedded -controller that can be found on 5th and later generation Microsoft -Surface devices. The responsibilities of this EC vary from device to -device. It provides battery information on all 5th and later generation -devices, temperature sensor and cooling capability access, functionality -for clipboard detaching on the Surface Books (2 and 3), as well as -HID-over-SSAM input devices, including keyboard on the Surface Laptop 1 -and 2, and keyboard as well as touchpad input on the Surface Laptop 3 -and Surface Book 3. - -Patchset: surface-sam ---- - Documentation/driver-api/index.rst | 1 + - .../surface_aggregator/client-api.rst | 38 + - .../driver-api/surface_aggregator/client.rst | 393 +++ - .../surface_aggregator/clients/cdev.rst | 204 ++ - .../surface_aggregator/clients/dtx.rst | 712 +++++ - .../surface_aggregator/clients/index.rst | 22 + - .../surface_aggregator/clients/san.rst | 44 + - .../driver-api/surface_aggregator/index.rst | 21 + - .../surface_aggregator/internal-api.rst | 67 + - .../surface_aggregator/internal.rst | 577 ++++ - .../surface_aggregator/overview.rst | 77 + - .../driver-api/surface_aggregator/ssh.rst | 344 ++ - arch/x86/kernel/acpi/boot.c | 24 + - drivers/hid/Kconfig | 4 +- - drivers/hid/Makefile | 2 + - drivers/hid/surface-hid/Kconfig | 42 + - drivers/hid/surface-hid/Makefile | 7 + - drivers/hid/surface-hid/surface_hid.c | 253 ++ - drivers/hid/surface-hid/surface_hid_core.c | 272 ++ - drivers/hid/surface-hid/surface_hid_core.h | 77 + - drivers/hid/surface-hid/surface_kbd.c | 300 ++ - drivers/platform/x86/Kconfig | 102 + - drivers/platform/x86/Makefile | 7 + - drivers/platform/x86/surface_acpi_notify.c | 886 ++++++ - .../platform/x86/surface_aggregator/Kconfig | 69 + - .../platform/x86/surface_aggregator/Makefile | 17 + - drivers/platform/x86/surface_aggregator/bus.c | 415 +++ - drivers/platform/x86/surface_aggregator/bus.h | 27 + - .../x86/surface_aggregator/controller.c | 2789 +++++++++++++++++ - .../x86/surface_aggregator/controller.h | 285 ++ - .../platform/x86/surface_aggregator/core.c | 839 +++++ - .../x86/surface_aggregator/ssh_msgb.h | 205 ++ - .../x86/surface_aggregator/ssh_packet_layer.c | 2074 ++++++++++++ - .../x86/surface_aggregator/ssh_packet_layer.h | 190 ++ - .../x86/surface_aggregator/ssh_parser.c | 228 ++ - .../x86/surface_aggregator/ssh_parser.h | 154 + - .../surface_aggregator/ssh_request_layer.c | 1263 ++++++++ - .../surface_aggregator/ssh_request_layer.h | 143 + - .../platform/x86/surface_aggregator/trace.h | 632 ++++ - .../platform/x86/surface_aggregator_cdev.c | 810 +++++ - .../x86/surface_aggregator_registry.c | 606 ++++ - drivers/platform/x86/surface_dtx.c | 1283 ++++++++ - drivers/platform/x86/surface_perfmode.c | 122 + - drivers/power/supply/Kconfig | 32 + - drivers/power/supply/Makefile | 2 + - drivers/power/supply/surface_battery.c | 875 ++++++ - drivers/power/supply/surface_charger.c | 282 ++ - include/linux/mod_devicetable.h | 5 +- - include/linux/surface_acpi_notify.h | 39 + - include/linux/surface_aggregator/controller.h | 849 +++++ - include/linux/surface_aggregator/device.h | 424 +++ - include/linux/surface_aggregator/serial_hub.h | 672 ++++ - include/uapi/linux/surface_aggregator/cdev.h | 147 + - include/uapi/linux/surface_aggregator/dtx.h | 146 + - scripts/mod/devicetable-offsets.c | 3 +- - scripts/mod/file2alias.c | 10 +- - 56 files changed, 20105 insertions(+), 8 deletions(-) - create mode 100644 Documentation/driver-api/surface_aggregator/client-api.rst - create mode 100644 Documentation/driver-api/surface_aggregator/client.rst - create mode 100644 Documentation/driver-api/surface_aggregator/clients/cdev.rst - create mode 100644 Documentation/driver-api/surface_aggregator/clients/dtx.rst - create mode 100644 Documentation/driver-api/surface_aggregator/clients/index.rst - create mode 100644 Documentation/driver-api/surface_aggregator/clients/san.rst - create mode 100644 Documentation/driver-api/surface_aggregator/index.rst - create mode 100644 Documentation/driver-api/surface_aggregator/internal-api.rst - create mode 100644 Documentation/driver-api/surface_aggregator/internal.rst - create mode 100644 Documentation/driver-api/surface_aggregator/overview.rst - create mode 100644 Documentation/driver-api/surface_aggregator/ssh.rst - create mode 100644 drivers/hid/surface-hid/Kconfig - create mode 100644 drivers/hid/surface-hid/Makefile - create mode 100644 drivers/hid/surface-hid/surface_hid.c - create mode 100644 drivers/hid/surface-hid/surface_hid_core.c - create mode 100644 drivers/hid/surface-hid/surface_hid_core.h - create mode 100644 drivers/hid/surface-hid/surface_kbd.c - create mode 100644 drivers/platform/x86/surface_acpi_notify.c - create mode 100644 drivers/platform/x86/surface_aggregator/Kconfig - create mode 100644 drivers/platform/x86/surface_aggregator/Makefile - create mode 100644 drivers/platform/x86/surface_aggregator/bus.c - create mode 100644 drivers/platform/x86/surface_aggregator/bus.h - create mode 100644 drivers/platform/x86/surface_aggregator/controller.c - create mode 100644 drivers/platform/x86/surface_aggregator/controller.h - create mode 100644 drivers/platform/x86/surface_aggregator/core.c - create mode 100644 drivers/platform/x86/surface_aggregator/ssh_msgb.h - create mode 100644 drivers/platform/x86/surface_aggregator/ssh_packet_layer.c - create mode 100644 drivers/platform/x86/surface_aggregator/ssh_packet_layer.h - create mode 100644 drivers/platform/x86/surface_aggregator/ssh_parser.c - create mode 100644 drivers/platform/x86/surface_aggregator/ssh_parser.h - create mode 100644 drivers/platform/x86/surface_aggregator/ssh_request_layer.c - create mode 100644 drivers/platform/x86/surface_aggregator/ssh_request_layer.h - create mode 100644 drivers/platform/x86/surface_aggregator/trace.h - create mode 100644 drivers/platform/x86/surface_aggregator_cdev.c - create mode 100644 drivers/platform/x86/surface_aggregator_registry.c - create mode 100644 drivers/platform/x86/surface_dtx.c - create mode 100644 drivers/platform/x86/surface_perfmode.c - create mode 100644 drivers/power/supply/surface_battery.c - create mode 100644 drivers/power/supply/surface_charger.c - create mode 100644 include/linux/surface_acpi_notify.h - create mode 100644 include/linux/surface_aggregator/controller.h - create mode 100644 include/linux/surface_aggregator/device.h - create mode 100644 include/linux/surface_aggregator/serial_hub.h - create mode 100644 include/uapi/linux/surface_aggregator/cdev.h - create mode 100644 include/uapi/linux/surface_aggregator/dtx.h - -diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst -index f357f3eb400c..699dc7cac0fb 100644 ---- a/Documentation/driver-api/index.rst -+++ b/Documentation/driver-api/index.rst -@@ -97,6 +97,7 @@ available subsections can be seen below. - rfkill - serial/index - sm501 -+ surface_aggregator/index - switchtec - sync_file - vfio-mediated-device -diff --git a/Documentation/driver-api/surface_aggregator/client-api.rst b/Documentation/driver-api/surface_aggregator/client-api.rst -new file mode 100644 -index 000000000000..a1117d57036a ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/client-api.rst -@@ -0,0 +1,38 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+=============================== -+Client Driver API Documentation -+=============================== -+ -+.. contents:: -+ :depth: 2 -+ -+ -+Serial Hub Communication -+======================== -+ -+.. kernel-doc:: include/linux/surface_aggregator/serial_hub.h -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_packet_layer.c -+ :export: -+ -+ -+Controller and Core Interface -+============================= -+ -+.. kernel-doc:: include/linux/surface_aggregator/controller.h -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/controller.c -+ :export: -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/core.c -+ :export: -+ -+ -+Client Bus and Client Device API -+================================ -+ -+.. kernel-doc:: include/linux/surface_aggregator/device.h -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/bus.c -+ :export: -diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst -new file mode 100644 -index 000000000000..e519d374c378 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/client.rst -@@ -0,0 +1,393 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |ssam_controller| replace:: :c:type:`struct ssam_controller ` -+.. |ssam_device| replace:: :c:type:`struct ssam_device ` -+.. |ssam_device_driver| replace:: :c:type:`struct ssam_device_driver ` -+.. |ssam_client_bind| replace:: :c:func:`ssam_client_bind` -+.. |ssam_client_link| replace:: :c:func:`ssam_client_link` -+.. |ssam_get_controller| replace:: :c:func:`ssam_get_controller` -+.. |ssam_controller_get| replace:: :c:func:`ssam_controller_get` -+.. |ssam_controller_put| replace:: :c:func:`ssam_controller_put` -+.. |ssam_device_alloc| replace:: :c:func:`ssam_device_alloc` -+.. |ssam_device_add| replace:: :c:func:`ssam_device_add` -+.. |ssam_device_remove| replace:: :c:func:`ssam_device_remove` -+.. |ssam_device_driver_register| replace:: :c:func:`ssam_device_driver_register` -+.. |ssam_device_driver_unregister| replace:: :c:func:`ssam_device_driver_unregister` -+.. |module_ssam_device_driver| replace:: :c:func:`module_ssam_device_driver` -+.. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` -+.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` -+.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` -+.. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` -+.. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` -+ -+ -+====================== -+Writing Client Drivers -+====================== -+ -+For the API documentation, refer to: -+ -+.. toctree:: -+ :maxdepth: 2 -+ -+ client-api -+ -+ -+Overview -+======== -+ -+Client drivers can be set up in two main ways, depending on how the -+corresponding device is made available to the system. We specifically -+differentiate between devices that are presented to the system via one of -+the conventional ways, e.g. as platform devices via ACPI, and devices that -+are non-discoverable and instead need to be explicitly provided by some -+other mechanism, as discussed further below. -+ -+ -+Non-SSAM Client Drivers -+======================= -+ -+All communication with the SAM EC is handled via the |ssam_controller| -+representing that EC to the kernel. Drivers targeting a non-SSAM device (and -+thus not being a |ssam_device_driver|) need to explicitly establish a -+connection/relation to that controller. This can be done via the -+|ssam_client_bind| function. Said function returns a reference to the SSAM -+controller, but, more importantly, also establishes a device link between -+client device and controller (this can also be done separate via -+|ssam_client_link|). It is important to do this, as it, first, guarantees -+that the returned controller is valid for use in the client driver for as -+long as this driver is bound to its device, i.e. that the driver gets -+unbound before the controller ever becomes invalid, and, second, as it -+ensures correct suspend/resume ordering. This setup should be done in the -+driver's probe function, and may be used to defer probing in case the SSAM -+subsystem is not ready yet, for example: -+ -+.. code-block:: c -+ -+ static int client_driver_probe(struct platform_device *pdev) -+ { -+ struct ssam_controller *ctrl; -+ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ // ... -+ -+ return 0; -+ } -+ -+The controller may be separately obtained via |ssam_get_controller| and its -+lifetime be guaranteed via |ssam_controller_get| and |ssam_controller_put|. -+Note that none of these functions, however, guarantee that the controller -+will not be shut down or suspended. These functions essentially only operate -+on the reference, i.e. only guarantee a bare minimum of accessibility -+without any guarantees at all on practical operability. -+ -+ -+Adding SSAM Devices -+=================== -+ -+If a device does not already exist/is not already provided via conventional -+means, it should be provided as |ssam_device| via the SSAM client device -+hub. New devices can be added to this hub by entering their UID into the -+corresponding registry. SSAM devices can also be manually allocated via -+|ssam_device_alloc|, subsequently to which they have to be added via -+|ssam_device_add| and eventually removed via |ssam_device_remove|. By -+default, the parent of the device is set to the controller device provided -+for allocation, however this may be changed before the device is added. Note -+that, when changing the parent device, care must be taken to ensure that the -+controller lifetime and suspend/resume ordering guarantees, in the default -+setup provided through the parent-child relation, are preserved. If -+necessary, by use of |ssam_client_link| as is done for non-SSAM client -+drivers and described in more detail above. -+ -+A client device must always be removed by the party which added the -+respective device before the controller shuts down. Such removal can be -+guaranteed by linking the driver providing the SSAM device to the controller -+via |ssam_client_link|, causing it to unbind before the controller driver -+unbinds. Client devices registered with the controller as parent are -+automatically removed when the controller shuts down, but this should not be -+relied upon, especially as this does not extend to client devices with a -+different parent. -+ -+ -+SSAM Client Drivers -+=================== -+ -+SSAM client device drivers are, in essence, no different than other device -+driver types. They are represented via |ssam_device_driver| and bind to a -+|ssam_device| via its UID (:c:type:`struct ssam_device.uid `) -+member and the match table -+(:c:type:`struct ssam_device_driver.match_table `), -+which should be set when declaring the driver struct instance. Refer to the -+|SSAM_DEVICE| macro documentation for more details on how to define members -+of the driver's match table. -+ -+The UID for SSAM client devices consists of a ``domain``, a ``category``, -+a ``target``, an ``instance``, and a ``function``. The ``domain`` is used -+differentiate between physical SAM devices -+(:c:type:`SSAM_DOMAIN_SERIALHUB `), i.e. devices that can -+be accessed via the Surface Serial Hub, and virtual ones -+(:c:type:`SSAM_DOMAIN_VIRTUAL `), such as client-device -+hubs, that have no real representation on the SAM EC and are solely used on -+the kernel/driver-side. For physical devices, ``category`` represents the -+target category, ``target`` the target ID, and ``instance`` the instance ID -+used to access the physical SAM device. In addition, ``function`` references -+a specific device functionality, but has no meaning to the SAM EC. The -+(default) name of a client device is generated based on its UID. -+ -+A driver instance can be registered via |ssam_device_driver_register| and -+unregistered via |ssam_device_driver_unregister|. For convenience, the -+|module_ssam_device_driver| macro may be used to define module init- and -+exit-functions registering the driver. -+ -+The controller associated with a SSAM client device can be found in its -+:c:type:`struct ssam_device.ctrl ` member. This reference is -+guaranteed to be valid for at least as long as the client driver is bound, -+but should also be valid for as long as the client device exists. Note, -+however, that access outside of the bound client driver must ensure that the -+controller device is not suspended while making any requests or -+(un-)registering event notifiers (and thus should generally be avoided). This -+is guaranteed when the controller is accessed from inside the bound client -+driver. -+ -+ -+Making Synchronous Requests -+=========================== -+ -+Synchronous requests are (currently) the main form of host-initiated -+communication with the EC. There are a couple of ways to define and execute -+such requests, however, most of them boil down to something similar as shown -+in the example below. This example defines a write-read request, meaning -+that the caller provides an argument to the SAM EC and receives a response. -+The caller needs to know the (maximum) length of the response payload and -+provide a buffer for it. -+ -+Care must be taken to ensure that any command payload data passed to the SAM -+EC is provided in little-endian format and, similarly, any response payload -+data received from it is converted from little-endian to host endianness. -+ -+.. code-block:: c -+ -+ int perform_request(struct ssam_controller *ctrl, u32 arg, u32 *ret) -+ { -+ struct ssam_request rqst; -+ struct ssam_response resp; -+ int status; -+ -+ /* Convert request argument to little-endian. */ -+ __le32 arg_le = cpu_to_le32(arg); -+ __le32 ret_le = cpu_to_le32(0); -+ -+ /* -+ * Initialize request specification. Replace this with your values. -+ * The rqst.payload field may be NULL if rqst.length is zero, -+ * indicating that the request does not have any argument. -+ * -+ * Note: The request parameters used here are not valid, i.e. -+ * they do not correspond to an actual SAM/EC request. -+ */ -+ rqst.target_category = SSAM_SSH_TC_SAM; -+ rqst.target_id = 0x01; -+ rqst.command_id = 0x02; -+ rqst.instance_id = 0x03; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(arg_le); -+ rqst.payload = (u8 *)&arg_le; -+ -+ /* Initialize request response. */ -+ resp.capacity = sizeof(ret_le); -+ resp.length = 0; -+ resp.pointer = (u8 *)&ret_le; -+ -+ /* -+ * Perform actual request. The response pointer may be null in case -+ * the request does not have any response. This must be consistent -+ * with the SSAM_REQUEST_HAS_RESPONSE flag set in the specification -+ * above. -+ */ -+ status = ssam_request_sync(ctrl, &rqst, &resp); -+ -+ /* -+ * Alternatively use -+ * -+ * ssam_request_sync_onstack(ctrl, &rqst, &resp, sizeof(arg_le)); -+ * -+ * to perform the request, allocating the message buffer directly -+ * on the stack as opposed to allocation via kzalloc(). -+ */ -+ -+ /* -+ * Convert request response back to native format. Note that in the -+ * error case, this value is not touched by the SSAM core, i.e. -+ * 'ret_le' will be zero as specified in its initialization. -+ */ -+ *ret = le32_to_cpu(ret_le); -+ -+ return status; -+ } -+ -+Note that |ssam_request_sync| in its essence is a wrapper over lower-level -+request primitives, which may also be used to perform requests. Refer to its -+implementation and documentation for more details. -+ -+An arguably more user-friendly way of defining such functions is by using -+one of the generator macros, for example via: -+ -+.. code-block:: c -+ -+ SSAM_DEFINE_SYNC_REQUEST_W(__ssam_tmp_perf_mode_set, __le32, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .target_id = 0x01, -+ .command_id = 0x03, -+ .instance_id = 0x00, -+ }); -+ -+This example defines a function -+ -+.. code-block:: c -+ -+ static int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg); -+ -+executing the specified request, with the controller passed in when calling -+said function. In this example, the argument is provided via the ``arg`` -+pointer. Note that the generated function allocates the message buffer on -+the stack. Thus, if the argument provided via the request is large, these -+kinds of macros should be avoided. Also note that, in contrast to the -+previous non-macro example, this function does not do any endianness -+conversion, which has to be handled by the caller. Apart from those -+differences the function generated by the macro is similar to the one -+provided in the non-macro example above. -+ -+The full list of such function-generating macros is -+ -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_N` for requests without return value and -+ without argument. -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_R` for requests with return value but no -+ argument. -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_W` for requests without return value but -+ with argument. -+ -+Refer to their respective documentation for more details. For each one of -+these macros, a special variant is provided, which targets request types -+applicable to multiple instances of the same device type: -+ -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_N` -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_R` -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_W` -+ -+The difference of those macros to the previously mentioned versions is, that -+the device target and instance IDs are not fixed for the generated function, -+but instead have to be provided by the caller of said function. -+ -+Additionally, variants for direct use with client devices, i.e. -+|ssam_device|, are also provided. These can, for example, be used as -+follows: -+ -+.. code-block:: c -+ -+ SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x01, -+ }); -+ -+This invocation of the macro defines a function -+ -+.. code-block:: c -+ -+ static int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret); -+ -+executing the specified request, using the device IDs and controller given -+in the client device. The full list of such macros for client devices is: -+ -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_N` -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_R` -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_W` -+ -+ -+Handling Events -+=============== -+ -+To receive events from the SAM EC, an event notifier must be registered for -+the desired event via |ssam_notifier_register|. The notifier must be -+unregistered via |ssam_notifier_unregister| once it is not required any -+more. -+ -+Event notifiers are registered by providing (at minimum) a callback to call -+in case an event has been received, the registry specifying how the event -+should be enabled, an event ID specifying for which target category and, -+optionally and depending on the registry used, for which instance ID events -+should be enabled, and finally, flags describing how the EC will send these -+events. If the specific registry does not enable events by instance ID, the -+instance ID must be set to zero. Additionally, a priority for the respective -+notifier may be specified, which determines its order in relation to any -+other notifier registered for the same target category. -+ -+By default, event notifiers will receive all events for the specific target -+category, regardless of the instance ID specified when registering the -+notifier. The core may be instructed to only call a notifier if the target -+ID or instance ID (or both) of the event match the ones implied by the -+notifier IDs (in case of target ID, the target ID of the registry), by -+providing an event mask (see |ssam_event_mask|). -+ -+In general, the target ID of the registry is also the target ID of the -+enabled event (with the notable exception being keyboard input events on the -+Surface Laptop 1 and 2, which are enabled via a registry with target ID 1, -+but provide events with target ID 2). -+ -+A full example for registering an event notifier and handling received -+events is provided below: -+ -+.. code-block:: c -+ -+ u32 notifier_callback(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+ { -+ int status = ... -+ -+ /* Handle the event here ... */ -+ -+ /* Convert return value and indicate that we handled the event. */ -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+ } -+ -+ int setup_notifier(struct ssam_device *sdev, -+ struct ssam_event_notifier *nf) -+ { -+ /* Set priority wrt. other handlers of same target category. */ -+ nf->base.priority = 1; -+ -+ /* Set event/notifier callback. */ -+ nf->base.fn = notifier_callback; -+ -+ /* Specify event registry, i.e. how events get enabled/disabled. */ -+ nf->event.reg = SSAM_EVENT_REGISTRY_KIP; -+ -+ /* Specify which event to enable/disable */ -+ nf->event.id.target_category = sdev->uid.category; -+ nf->event.id.instance = sdev->uid.instance; -+ -+ /* -+ * Specify for which events the notifier callback gets executed. -+ * This essentially tells the core if it can skip notifiers that -+ * don't have target or instance IDs matching those of the event. -+ */ -+ nf->event.mask = SSAM_EVENT_MASK_STRICT; -+ -+ /* Specify event flags. */ -+ nf->event.flags = SSAM_EVENT_SEQUENCED; -+ -+ return ssam_notifier_register(sdev->ctrl, nf); -+ } -+ -+Multiple event notifiers can be registered for the same event. The event -+handler core takes care of enabling and disabling events when notifiers are -+registered and unregistered, by keeping track of how many notifiers for a -+specific event (combination of registry, event target category, and event -+instance ID) are currently registered. This means that a specific event will -+be enabled when the first notifier for it is being registered and disabled -+when the last notifier for it is being unregistered. Note that the event -+flags are therefore only used on the first registered notifier, however, one -+should take care that notifiers for a specific event are always registered -+with the same flag and it is considered a bug to do otherwise. -diff --git a/Documentation/driver-api/surface_aggregator/clients/cdev.rst b/Documentation/driver-api/surface_aggregator/clients/cdev.rst -new file mode 100644 -index 000000000000..0134a841a079 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/clients/cdev.rst -@@ -0,0 +1,204 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request ` -+.. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags ` -+.. |ssam_cdev_event| replace:: :c:type:`struct ssam_cdev_event ` -+ -+============================== -+User-Space EC Interface (cdev) -+============================== -+ -+The ``surface_aggregator_cdev`` module provides a misc-device for the SSAM -+controller to allow for a (more or less) direct connection from user-space to -+the SAM EC. It is intended to be used for development and debugging, and -+therefore should not be used or relied upon in any other way. Note that this -+module is not loaded automatically, but instead must be loaded manually. -+ -+The provided interface is accessible through the ``/dev/surface/aggregator`` -+device-file. All functionality of this interface is provided via IOCTLs. -+These IOCTLs and their respective input/output parameter structs are defined in -+``include/uapi/linux/surface_aggregator/cdev.h``. -+ -+A small python library and scripts for accessing this interface can be found -+at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam. -+ -+.. contents:: -+ -+ -+Receiving Events -+================ -+ -+Events can be received by reading from the device-file. The are represented by -+the |ssam_cdev_event| datatype. -+ -+Before events are available to be read, however, the desired notifiers must be -+registered via the ``SSAM_CDEV_NOTIF_REGISTER`` IOCTL. Notifiers are, in -+essence, callbacks, called when the EC sends an event. They are, in this -+interface, associated with a specific target category and device-file-instance. -+They forward any event of this category to the buffer of the corresponding -+instance, from which it can then be read. -+ -+Notifiers themselves do not enable events on the EC. Thus, it may additionally -+be necessary to enable events via the ``SSAM_CDEV_EVENT_ENABLE`` IOCTL. While -+notifiers work per-client (i.e. per-device-file-instance), events are enabled -+globally, for the EC and all of its clients (regardless of userspace or -+non-userspace). The ``SSAM_CDEV_EVENT_ENABLE`` and ``SSAM_CDEV_EVENT_DISABLE`` -+IOCTLs take care of reference counting the events, such that an event is -+enabled as long as there is a client that has requested it. -+ -+Note that enabled events are not automatically disabled once the client -+instance is closed. Therefore any client process (or group of processes) should -+balance their event enable calls with the corresponding event disable calls. It -+is, however, perfectly valid to enable and disable events on different client -+instances. For example, it is valid to set up notifiers and read events on -+client instance ``A``, enable those events on instance ``B`` (note that these -+will also be received by A since events are enabled/disabled globally), and -+after no more events are desired, disable the previously enabled events via -+instance ``C``. -+ -+ -+Controller IOCTLs -+================= -+ -+The following IOCTLs are provided: -+ -+.. flat-table:: Controller IOCTLs -+ :widths: 1 1 1 1 4 -+ :header-rows: 1 -+ -+ * - Type -+ - Number -+ - Direction -+ - Name -+ - Description -+ -+ * - ``0xA5`` -+ - ``1`` -+ - ``WR`` -+ - ``REQUEST`` -+ - Perform synchronous SAM request. -+ -+ * - ``0xA5`` -+ - ``2`` -+ - ``W`` -+ - ``NOTIF_REGISTER`` -+ - Register event notifier. -+ -+ * - ``0xA5`` -+ - ``3`` -+ - ``W`` -+ - ``NOTIF_UNREGISTER`` -+ - Unregister event notifier. -+ -+ * - ``0xA5`` -+ - ``4`` -+ - ``W`` -+ - ``EVENT_ENABLE`` -+ - Enable event source. -+ -+ * - ``0xA5`` -+ - ``5`` -+ - ``W`` -+ - ``EVENT_DISABLE`` -+ - Disable event source. -+ -+ -+``SSAM_CDEV_REQUEST`` -+--------------------- -+ -+Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``. -+ -+Executes a synchronous SAM request. The request specification is passed in -+as argument of type |ssam_cdev_request|, which is then written to/modified -+by the IOCTL to return status and result of the request. -+ -+Request payload data must be allocated separately and is passed in via the -+``payload.data`` and ``payload.length`` members. If a response is required, -+the response buffer must be allocated by the caller and passed in via the -+``response.data`` member. The ``response.length`` member must be set to the -+capacity of this buffer, or if no response is required, zero. Upon -+completion of the request, the call will write the response to the response -+buffer (if its capacity allows it) and overwrite the length field with the -+actual size of the response, in bytes. -+ -+Additionally, if the request has a response, this must be indicated via the -+request flags, as is done with in-kernel requests. Request flags can be set -+via the ``flags`` member and the values correspond to the values found in -+|ssam_cdev_request_flags|. -+ -+Finally, the status of the request itself is returned in the ``status`` -+member (a negative errno value indicating failure). Note that failure -+indication of the IOCTL is separated from failure indication of the request: -+The IOCTL returns a negative status code if anything failed during setup of -+the request (``-EFAULT``) or if the provided argument or any of its fields -+are invalid (``-EINVAL``). In this case, the status value of the request -+argument may be set, providing more detail on what went wrong (e.g. -+``-ENOMEM`` for out-of-memory), but this value may also be zero. The IOCTL -+will return with a zero status code in case the request has been set up, -+submitted, and completed (i.e. handed back to user-space) successfully from -+inside the IOCTL, but the request ``status`` member may still be negative in -+case the actual execution of the request failed after it has been submitted. -+ -+A full definition of the argument struct is provided below. -+ -+``SSAM_CDEV_NOTIF_REGISTER`` -+---------------------------- -+ -+Defined as ``_IOW(0xA5, 2, struct ssam_cdev_notifier_desc)``. -+ -+Register a notifier for the event target category specified in the given -+notifier description with the specified priority. Notifiers registration is -+required to receive events, but does not enable events themselves. After a -+notifier for a specific target category has been registered, all events of that -+category will be forwarded to the userspace client and can then be read from -+the device file instance. Note that events may have to be enabled, e.g. via the -+``SSAM_CDEV_EVENT_ENABLE`` IOCTL, before the EC will send them. -+ -+Only one notifier can be registered per target category and client instance. If -+a notifier has already been registered, this IOCTL will fail with ``-EEXIST``. -+ -+Notifiers will automatically be removed when the device file instance is -+closed. -+ -+``SSAM_CDEV_NOTIF_UNREGISTER`` -+------------------------------ -+ -+Defined as ``_IOW(0xA5, 3, struct ssam_cdev_notifier_desc)``. -+ -+Unregisters the notifier associated with the specified target category. The -+priority field will be ignored by this IOCTL. If no notifier has been -+registered for this client instance and the given category, this IOCTL will -+fail with ``-ENOENT``. -+ -+``SSAM_CDEV_EVENT_ENABLE`` -+-------------------------- -+ -+Defined as ``_IOW(0xA5, 4, struct ssam_cdev_event_desc)``. -+ -+Enable the event associated with the given event descriptor. -+ -+Note that this call will not register a notifier itself, it will only enable -+events on the controller. If you want to receive events by reading from the -+device file, you will need to register the corresponding notifier(s) on that -+instance. -+ -+Events are not automatically disabled when the device file is closed. This must -+be done manually, via a call to the ``SSAM_CDEV_EVENT_DISABLE`` IOCTL. -+ -+``SSAM_CDEV_EVENT_DISABLE`` -+--------------------------- -+ -+Defined as ``_IOW(0xA5, 5, struct ssam_cdev_event_desc)``. -+ -+Disable the event associated with the given event descriptor. -+ -+Note that this will not unregister any notifiers. Events may still be received -+and forwarded to user-space after this call. The only safe way of stopping -+events from being received is unregistering all previously registered -+notifiers. -+ -+ -+Structures and Enums -+==================== -+ -+.. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h -diff --git a/Documentation/driver-api/surface_aggregator/clients/dtx.rst b/Documentation/driver-api/surface_aggregator/clients/dtx.rst -new file mode 100644 -index 000000000000..e974c2b04e9f ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/clients/dtx.rst -@@ -0,0 +1,712 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |__u16| replace:: :c:type:`__u16 <__u16>` -+.. |sdtx_event| replace:: :c:type:`struct sdtx_event ` -+.. |sdtx_event_code| replace:: :c:type:`enum sdtx_event_code ` -+.. |sdtx_base_info| replace:: :c:type:`struct sdtx_base_info ` -+.. |sdtx_device_mode| replace:: :c:type:`struct sdtx_device_mode ` -+ -+====================================================== -+User-Space DTX (Clipboard Detachment System) Interface -+====================================================== -+ -+The ``surface_dtx`` driver is responsible for proper clipboard detachment -+and re-attachment handling. To this end, it provides the ``/dev/surface/dtx`` -+device file, through which it can interface with a user-space daemon. This -+daemon is then ultimately responsible for determining and taking necessary -+actions, such as unmounting devices attached to the base, -+unloading/reloading the graphics-driver, user-notifications, etc. -+ -+There are two basic communication principles used in this driver: Commands -+(in other parts of the documentation also referred to as requests) and -+events. Commands are sent to the EC and may have a different implications in -+different contexts. Events are sent by the EC upon some internal state -+change. Commands are always driver-initiated, whereas events are always -+initiated by the EC. -+ -+.. contents:: -+ -+Nomenclature -+============ -+ -+* **Clipboard:** -+ The detachable upper part of the Surface Book, housing the screen and CPU. -+ -+* **Base:** -+ The lower part of the Surface Book from which the clipboard can be -+ detached, optionally (model dependent) housing the discrete GPU (dGPU). -+ -+* **Latch:** -+ The mechanism keeping the clipboard attached to the base in normal -+ operation and allowing it to be detached when requested. -+ -+* **Silently ignored commands:** -+ The command is accepted by the EC as a valid command and acknowledged -+ (following the standard communication protocol), but the EC does not act -+ upon it, i.e. ignores it.e upper part of the -+ -+ -+Detachment Process -+================== -+ -+Warning: This part of the documentation is based on reverse engineering and -+testing and thus may contain errors or be incomplete. -+ -+Latch States -+------------ -+ -+The latch mechanism has two major states: *open* and *closed*. In the -+*closed* state (default), the clipboard is secured to the base, whereas in -+the *open* state, the clipboard can be removed by a user. -+ -+The latch can additionally be locked and, correspondingly, unlocked, which -+can influence the detachment procedure. Specifically, this locking mechanism -+is intended to prevent the the dGPU, positioned in the base of the device, -+from being hot-unplugged while in use. More details can be found in the -+documentation for the detachment procedure below. By default, the latch is -+unlocked. -+ -+Detachment Procedure -+-------------------- -+ -+Note that the detachment process is governed fully by the EC. The -+``surface_dtx`` driver only relays events from the EC to user-space and -+commands from user-space to the EC, i.e. it does not influence this process. -+ -+The detachment process is started with the user pressing the *detach* button -+on the base of the device or executing the ``SDTX_IOCTL_LATCH_REQUEST`` IOCTL. -+Following that: -+ -+1. The EC turns on the indicator led on the detach-button, sends a -+ *detach-request* event (``SDTX_EVENT_REQUEST``), and awaits further -+ instructions/commands. In case the latch is unlocked, the led will flash -+ green. If the latch has been locked, the led will be solid red -+ -+2. The event is, via the ``surface_dtx`` driver, relayed to user-space, where -+ an appropriate user-space daemon can handle it and send instructions back -+ to the EC via IOCTLs provided by this driver. -+ -+3. The EC waits for instructions from user-space and acts according to them. -+ If the EC does not receive any instructions in a given period, it will -+ time out and continue as follows: -+ -+ - If the latch is unlocked, the EC will open the latch and the clipboard -+ can be detached from the base. This is the exact behavior as without -+ this driver or any user-space daemon. See the ``SDTX_IOCTL_LATCH_CONFIRM`` -+ description below for more details on the follow-up behavior of the EC. -+ -+ - If the latch is locked, the EC will *not* open the latch, meaning the -+ clipboard cannot be detached from the base. Furthermore, the EC sends -+ an cancel event (``SDTX_EVENT_CANCEL``) detailing this with the cancel -+ reason ``SDTX_DETACH_TIMEDOUT`` (see :ref:`events` for details). -+ -+Valid responses by a user-space daemon to a detachment request event are: -+ -+- Execute ``SDTX_IOCTL_LATCH_REQUEST``. This will immediately abort the -+ detachment process. Furthermore, the EC will send a detach-request event, -+ similar to the user pressing the detach-button to cancel said process (see -+ below). -+ -+- Execute ``SDTX_IOCTL_LATCH_CONFIRM``. This will cause the EC to open the -+ latch, after which the user can separate clipboard and base. -+ -+ As this changes the latch state, a *latch-status* event -+ (``SDTX_EVENT_LATCH_STATUS``) will be sent once the latch has been opened -+ successfully. If the EC fails to open the latch, e.g. due to hardware -+ error or low battery, a latch-cancel event (``SDTX_EVENT_CANCEL``) will be -+ sent with the cancel reason indicating the specific failure. -+ -+ If the latch is currently locked, the latch will automatically be -+ unlocked before it is opened. -+ -+- Execute ``SDTX_IOCTL_LATCH_HEARTBEAT``. This will reset the internal timeout. -+ No other actions will be performed, i.e. the detachment process will neither -+ be completed nor canceled, and the EC will still be waiting for further -+ responses. -+ -+- Execute ``SDTX_IOCTL_LATCH_CANCEL``. This will abort the detachment process, -+ similar to ``SDTX_IOCTL_LATCH_REQUEST``, described above, or the button -+ press, described below. A *generic request* event (``SDTX_EVENT_REQUEST``) -+ is send in response to this. In contrast to those, however, this command -+ does not trigger a new detachment process if none is currently in -+ progress. -+ -+- Do nothing. The detachment process eventually times out as described in -+ point 3. -+ -+See :ref:`ioctls` for more details on these responses. -+ -+It is important to note that, if the user presses the detach button at any -+point when a detachment operation is in progress (i.e. after the the EC has -+sent the initial *detach-request* event (``SDTX_EVENT_REQUEST``) and before -+it received the corresponding response concluding the process), the -+detachment process is canceled on the EC-level and an identical event is -+being sent. Thus a *detach-request* event, by itself, does not signal the -+start of the detachment process. -+ -+The detachment process may further be canceled by the EC due to hardware -+failures or a low clipboard battery. This is done via a cancel event -+(``SDTX_EVENT_CANCEL``) with the corresponding cancel reason. -+ -+ -+User-Space Interface Documentation -+================================== -+ -+Error Codes and Status Values -+----------------------------- -+ -+Error and status codes are divided into different categories, which can be -+used to determine if the status code is an error, and, if it is, the -+severity and type of that error. The current categories are: -+ -+.. flat-table:: Overview of Status/Error Categories. -+ :widths: 2 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Value -+ - Short Description -+ -+ * - ``STATUS`` -+ - ``0x0000`` -+ - Non-error status codes. -+ -+ * - ``RUNTIME_ERROR`` -+ - ``0x1000`` -+ - Non-critical runtime errors. -+ -+ * - ``HARDWARE_ERROR`` -+ - ``0x2000`` -+ - Critical hardware failures. -+ -+ * - ``UNKNOWN`` -+ - ``0xF000`` -+ - Unknown error codes. -+ -+Other categories are reserved for future use. The ``SDTX_CATEGORY()`` macro -+can be used to determine the category of any status value. The -+``SDTX_SUCCESS()`` macro can be used to check if the status value is a -+success value (``SDTX_CATEGORY_STATUS``) or if it indicates a failure. -+ -+Unknown status or error codes sent by the EC are assigned to the ``UNKNOWN`` -+category by the driver and may be implemented via their own code in the -+future. -+ -+Currently used error codes are: -+ -+.. flat-table:: Overview of Error Codes. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Category -+ - Value -+ - Short Description -+ -+ * - ``SDTX_DETACH_NOT_FEASIBLE`` -+ - ``RUNTIME`` -+ - ``0x1001`` -+ - Detachment not feasible due to low clipboard battery. -+ -+ * - ``SDTX_DETACH_TIMEDOUT`` -+ - ``RUNTIME`` -+ - ``0x1002`` -+ - Detachment process timed out while the latch was locked. -+ -+ * - ``SDTX_ERR_FAILED_TO_OPEN`` -+ - ``HARDWARE`` -+ - ``0x2001`` -+ - Failed to open latch. -+ -+ * - ``SDTX_ERR_FAILED_TO_REMAIN_OPEN`` -+ - ``HARDWARE`` -+ - ``0x2002`` -+ - Failed to keep latch open. -+ -+ * - ``SDTX_ERR_FAILED_TO_CLOSE`` -+ - ``HARDWARE`` -+ - ``0x2003`` -+ - Failed to close latch. -+ -+Other error codes are reserved for future use. Non-error status codes may -+overlap and are generally only unique within their use-case: -+ -+.. flat-table:: Latch Status Codes. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Category -+ - Value -+ - Short Description -+ -+ * - ``SDTX_LATCH_CLOSED`` -+ - ``STATUS`` -+ - ``0x0000`` -+ - Latch is closed/has been closed. -+ -+ * - ``SDTX_LATCH_OPENED`` -+ - ``STATUS`` -+ - ``0x0001`` -+ - Latch is open/has been opened. -+ -+.. flat-table:: Base State Codes. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Category -+ - Value -+ - Short Description -+ -+ * - ``SDTX_BASE_DETACHED`` -+ - ``STATUS`` -+ - ``0x0000`` -+ - Base has been detached/is not present. -+ -+ * - ``SDTX_BASE_ATTACHED`` -+ - ``STATUS`` -+ - ``0x0001`` -+ - Base has been attached/is present. -+ -+Again, other codes are reserved for future use. -+ -+.. _events: -+ -+Events -+------ -+ -+Events can be received by reading from the device file. They are disabled by -+default and have to be enabled by executing ``SDTX_IOCTL_EVENTS_ENABLE`` -+first. All events follow the layout prescribed by |sdtx_event|. Specific -+event types can be identified by their event code, described in -+|sdtx_event_code|. Note that other event codes are reserved for future use, -+thus an event parser must be able to handle any unknown/unsupported event -+types gracefully, by relying on the payload length given in the event header. -+ -+Currently provided event types are: -+ -+.. flat-table:: Overview of DTX events. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Code -+ - Payload -+ - Short Description -+ -+ * - ``SDTX_EVENT_REQUEST`` -+ - ``1`` -+ - ``0`` bytes -+ - Detachment process initiated/aborted. -+ -+ * - ``SDTX_EVENT_CANCEL`` -+ - ``2`` -+ - ``2`` bytes -+ - EC canceled detachment process. -+ -+ * - ``SDTX_EVENT_BASE_CONNECTION`` -+ - ``3`` -+ - ``4`` bytes -+ - Base connection state changed. -+ -+ * - ``SDTX_EVENT_LATCH_STATUS`` -+ - ``4`` -+ - ``2`` bytes -+ - Latch status changed. -+ -+ * - ``SDTX_EVENT_DEVICE_MODE`` -+ - ``5`` -+ - ``2`` bytes -+ - Device mode changed. -+ -+Individual events in more detail: -+ -+``SDTX_EVENT_REQUEST`` -+^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when a detachment process is started or, if in progress, aborted by the -+user, either via a detach button press or a detach request -+(``SDTX_IOCTL_LATCH_REQUEST``) being sent from user-space. -+ -+Does not have any payload. -+ -+``SDTX_EVENT_CANCEL`` -+^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when a detachment process is canceled by the EC due to unfulfilled -+preconditions (e.g. clipboard battery too low to detach) or hardware -+failure. The reason for cancellation is given in the event payload detailed -+below and can be one of -+ -+* ``SDTX_DETACH_TIMEDOUT``: Detachment timed out while the latch was locked. -+ The latch has neither been opened nor unlocked. -+ -+* ``SDTX_DETACH_NOT_FEASIBLE``: Detachment not feasible due to low clipboard -+ battery. -+ -+* ``SDTX_ERR_FAILED_TO_OPEN``: Could not open the latch (hardware failure). -+ -+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``: Could not keep the latch open (hardware -+ failure). -+ -+* ``SDTX_ERR_FAILED_TO_CLOSE``: Could not close the latch (hardware failure). -+ -+Other error codes in this context are reserved for future use. -+ -+These codes can be classified via the ``SDTX_CATEGORY()`` macro to discern -+between critical hardware errors (``SDTX_CATEGORY_HARDWARE_ERROR``) or -+runtime errors (``SDTX_CATEGORY_RUNTIME_ERROR``), the latter of which may -+happen during normal operation if certain preconditions for detachment are -+not given. -+ -+.. flat-table:: Detachment Cancel Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``reason`` -+ - |__u16| -+ - Reason for cancellation. -+ -+``SDTX_EVENT_BASE_CONNECTION`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when the base connection state has changed, i.e. when the base has been -+attached, detached, or detachment has become infeasible due to low clipboard -+battery. The new state and, if a base is connected, ID of the base is -+provided as payload of type |sdtx_base_info| with its layout presented -+below: -+ -+.. flat-table:: Base-Connection-Change Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``state`` -+ - |__u16| -+ - Base connection state. -+ -+ * - ``base_id`` -+ - |__u16| -+ - Type of base connected (zero if none). -+ -+Possible values for ``state`` are: -+ -+* ``SDTX_BASE_DETACHED``, -+* ``SDTX_BASE_ATTACHED``, and -+* ``SDTX_DETACH_NOT_FEASIBLE``. -+ -+Other values are reserved for future use. -+ -+``SDTX_EVENT_LATCH_STATUS`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when the latch status has changed, i.e. when the latch has been opened, -+closed, or an error occurred. The current status is provided as payload: -+ -+.. flat-table:: Latch-Status-Change Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``status`` -+ - |__u16| -+ - Latch status. -+ -+Possible values for ``status`` are: -+ -+* ``SDTX_LATCH_CLOSED``, -+* ``SDTX_LATCH_OPENED``, -+* ``SDTX_ERR_FAILED_TO_OPEN``, -+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and -+* ``SDTX_ERR_FAILED_TO_CLOSE``. -+ -+Other values are reserved for future use. -+ -+``SDTX_EVENT_DEVICE_MODE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when the device mode has changed. The new device mode is provided as -+payload: -+ -+.. flat-table:: Device-Mode-Change Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``mode`` -+ - |__u16| -+ - Device operation mode. -+ -+Possible values for ``mode`` are: -+ -+* ``SDTX_DEVICE_MODE_TABLET``, -+* ``SDTX_DEVICE_MODE_LAPTOP``, and -+* ``SDTX_DEVICE_MODE_STUDIO``. -+ -+Other values are reserved for future use. -+ -+.. _ioctls: -+ -+IOCTLs -+------ -+ -+The following IOCTLs are provided: -+ -+.. flat-table:: Overview of DTX IOCTLs -+ :widths: 1 1 1 1 4 -+ :header-rows: 1 -+ -+ * - Type -+ - Number -+ - Direction -+ - Name -+ - Description -+ -+ * - ``0xA5`` -+ - ``0x21`` -+ - ``-`` -+ - ``EVENTS_ENABLE`` -+ - Enable events for the current file descriptor. -+ -+ * - ``0xA5`` -+ - ``0x22`` -+ - ``-`` -+ - ``EVENTS_DISABLE`` -+ - Disable events for the current file descriptor. -+ -+ * - ``0xA5`` -+ - ``0x23`` -+ - ``-`` -+ - ``LATCH_LOCK`` -+ - Lock the latch. -+ -+ * - ``0xA5`` -+ - ``0x24`` -+ - ``-`` -+ - ``LATCH_UNLOCK`` -+ - Unlock the latch. -+ -+ * - ``0xA5`` -+ - ``0x25`` -+ - ``-`` -+ - ``LATCH_REQUEST`` -+ - Request clipboard detachment. -+ -+ * - ``0xA5`` -+ - ``0x26`` -+ - ``-`` -+ - ``LATCH_CONFIRM`` -+ - Confirm clipboard detachment request. -+ -+ * - ``0xA5`` -+ - ``0x27`` -+ - ``-`` -+ - ``LATCH_HEARTBEAT`` -+ - Send heartbeat signal to EC. -+ -+ * - ``0xA5`` -+ - ``0x28`` -+ - ``-`` -+ - ``LATCH_CANCEL`` -+ - Cancel detachment process. -+ -+ * - ``0xA5`` -+ - ``0x29`` -+ - ``R`` -+ - ``GET_BASE_INFO`` -+ - Get current base/connection information. -+ -+ * - ``0xA5`` -+ - ``0x2A`` -+ - ``R`` -+ - ``GET_DEVICE_MODE`` -+ - Get current device operation mode. -+ -+ * - ``0xA5`` -+ - ``0x2B`` -+ - ``R`` -+ - ``GET_LATCH_STATUS`` -+ - Get current device latch status. -+ -+``SDTX_IOCTL_EVENTS_ENABLE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x22)``. -+ -+Enable events for the current file descriptor. Events can be obtained by -+reading from the device, if enabled. Events are disabled by default. -+ -+``SDTX_IOCTL_EVENTS_DISABLE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x22)``. -+ -+Disable events for the current file descriptor. Events can be obtained by -+reading from the device, if enabled. Events are disabled by default. -+ -+``SDTX_IOCTL_LATCH_LOCK`` -+^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x23)``. -+ -+Locks the latch, causing the detachment procedure to abort without opening -+the latch on timeout. The latch is unlocked by default. This command will be -+silently ignored if the latch is already locked. -+ -+``SDTX_IOCTL_LATCH_UNLOCK`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x24)``. -+ -+Unlocks the latch, causing the detachment procedure to open the latch on -+timeout. The latch is unlocked by default. This command will not open the -+latch when sent during an ongoing detachment process. It will be silently -+ignored if the latch is already unlocked. -+ -+``SDTX_IOCTL_LATCH_REQUEST`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x25)``. -+ -+Generic latch request. Behavior depends on the context: If no -+detachment-process is active, detachment is requested. Otherwise the -+currently active detachment-process will be aborted. -+ -+If a detachment process is canceled by this operation, a generic detachment -+request event (``SDTX_EVENT_REQUEST``) will be sent. -+ -+This essentially behaves the same as a detachment button press. -+ -+``SDTX_IOCTL_LATCH_CONFIRM`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x26)``. -+ -+Acknowledges and confirms a latch request. If sent during an ongoing -+detachment process, this command causes the latch to be opened immediately. -+The latch will also be opened if it has been locked. In this case, the latch -+lock is reset to the unlocked state. -+ -+This command will be silently ignored if there is currently no detachment -+procedure in progress. -+ -+``SDTX_IOCTL_LATCH_HEARTBEAT`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x27)``. -+ -+Sends a heartbeat, essentially resetting the detachment timeout. This -+command can be used to keep the detachment process alive while work required -+for the detachment to succeed is still in progress. -+ -+This command will be silently ignored if there is currently no detachment -+procedure in progress. -+ -+``SDTX_IOCTL_LATCH_CANCEL`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x28)``. -+ -+Cancels detachment in progress (if any). If a detachment process is canceled -+by this operation, a generic detachment request event -+(``SDTX_EVENT_REQUEST``) will be sent. -+ -+This command will be silently ignored if there is currently no detachment -+procedure in progress. -+ -+``SDTX_IOCTL_GET_BASE_INFO`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IOR(0xA5, 0x29, struct sdtx_base_info)``. -+ -+Get the current base connection state (i.e. attached/detached) and the type -+of the base connected to the clipboard. This is command essentially provides -+a way to query the information provided by the base connection change event -+(``SDTX_EVENT_BASE_CONNECTION``). -+ -+Possible values for ``struct sdtx_base_info.state`` are: -+ -+* ``SDTX_BASE_DETACHED``, -+* ``SDTX_BASE_ATTACHED``, and -+* ``SDTX_DETACH_NOT_FEASIBLE``. -+ -+Other values are reserved for future use. -+ -+``SDTX_IOCTL_GET_DEVICE_MODE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IOR(0xA5, 0x2A, __u16)``. -+ -+Returns the device operation mode, indicating if and how the base is -+attached to the clipboard. This is command essentially provides a way to -+query the information provided by the device mode change event -+(``SDTX_EVENT_DEVICE_MODE``). -+ -+Returned values are: -+ -+* ``SDTX_DEVICE_MODE_LAPTOP`` -+* ``SDTX_DEVICE_MODE_TABLET`` -+* ``SDTX_DEVICE_MODE_STUDIO`` -+ -+See |sdtx_device_mode| for details. Other values are reserved for future -+use. -+ -+ -+``SDTX_IOCTL_GET_LATCH_STATUS`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IOR(0xA5, 0x2B, __u16)``. -+ -+Get the current latch status or (presumably) the last error encountered when -+trying to open/close the latch. This is command essentially provides a way -+to query the information provided by the latch status change event -+(``SDTX_EVENT_LATCH_STATUS``). -+ -+Returned values are: -+ -+* ``SDTX_LATCH_CLOSED``, -+* ``SDTX_LATCH_OPENED``, -+* ``SDTX_ERR_FAILED_TO_OPEN``, -+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and -+* ``SDTX_ERR_FAILED_TO_CLOSE``. -+ -+Other values are reserved for future use. -+ -+A Note on Base IDs -+------------------ -+ -+Base types/IDs provided via ``SDTX_EVENT_BASE_CONNECTION`` or -+``SDTX_IOCTL_GET_BASE_INFO`` are directly forwarded from from the EC in the -+lower byte of the combined |__u16| value, with the driver storing the EC -+type from which this ID comes in the high byte (without this, base IDs over -+different types of ECs may be overlapping). -+ -+The ``SDTX_DEVICE_TYPE()`` macro can be used to determine the EC device -+type. This can be one of -+ -+* ``SDTX_DEVICE_TYPE_HID``, for Surface Aggregator Module over HID, and -+ -+* ``SDTX_DEVICE_TYPE_SSH``, for Surface Aggregator Module over Surface Serial -+ Hub. -+ -+Note that currently only the ``SSH`` type EC is supported, however ``HID`` -+type is reserved for future use. -+ -+Structures and Enums -+-------------------- -+ -+.. kernel-doc:: include/uapi/linux/surface_aggregator/dtx.h -diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst -new file mode 100644 -index 000000000000..98ea9946b8a2 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/clients/index.rst -@@ -0,0 +1,22 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+=========================== -+Client Driver Documentation -+=========================== -+ -+This is the documentation for client drivers themselves. Refer to -+:doc:`../client` for documentation on how to write client drivers. -+ -+.. toctree:: -+ :maxdepth: 1 -+ -+ cdev -+ dtx -+ san -+ -+.. only:: subproject and html -+ -+ Indices -+ ======= -+ -+ * :ref:`genindex` -diff --git a/Documentation/driver-api/surface_aggregator/clients/san.rst b/Documentation/driver-api/surface_aggregator/clients/san.rst -new file mode 100644 -index 000000000000..1bf830ad367d ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/clients/san.rst -@@ -0,0 +1,44 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |san_client_link| replace:: :c:func:`san_client_link` -+.. |san_dgpu_notifier_register| replace:: :c:func:`san_dgpu_notifier_register` -+.. |san_dgpu_notifier_unregister| replace:: :c:func:`san_dgpu_notifier_unregister` -+ -+=================== -+Surface ACPI Notify -+=================== -+ -+The Surface ACPI Notify (SAN) device provides the bridge between ACPI and -+SAM controller. Specifically, ACPI code can execute requests and handle -+battery and thermal events via this interface. In addition to this, events -+relating to the discrete GPU (dGPU) of the Surface Book 2 can be sent from -+ACPI code (note: the Surface Book 3 uses a different method for this). The -+only currently known event sent via this interface is a dGPU power-on -+notification. While this driver handles the former part internally, it only -+relays the dGPU events to any other driver interested via its public API and -+does not handle them. -+ -+The public interface of this driver is split into two parts: Client -+registration and notifier-block registration. -+ -+A client to the SAN interface can be linked as consumer to the SAN device -+via |san_client_link|. This can be used to ensure that the a client -+receiving dGPU events does not miss any events due to the SAN interface not -+being set up as this forces the client driver to unbind once the SAN driver -+is unbound. -+ -+Notifier-blocks can be registered by any device for as long as the module is -+loaded, regardless of being linked as client or not. Registration is done -+with |san_dgpu_notifier_register|. If the notifier is not needed any more, it -+should be unregistered via |san_dgpu_notifier_unregister|. -+ -+Consult the API documentation below for more details. -+ -+ -+API Documentation -+================= -+ -+.. kernel-doc:: include/linux/surface_acpi_notify.h -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/clients/surface_acpi_notify.c -+ :export: -diff --git a/Documentation/driver-api/surface_aggregator/index.rst b/Documentation/driver-api/surface_aggregator/index.rst -new file mode 100644 -index 000000000000..6f3e1094904d ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/index.rst -@@ -0,0 +1,21 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+======================================= -+Surface System Aggregator Module (SSAM) -+======================================= -+ -+.. toctree:: -+ :maxdepth: 2 -+ -+ overview -+ client -+ clients/index -+ ssh -+ internal -+ -+.. only:: subproject and html -+ -+ Indices -+ ======= -+ -+ * :ref:`genindex` -diff --git a/Documentation/driver-api/surface_aggregator/internal-api.rst b/Documentation/driver-api/surface_aggregator/internal-api.rst -new file mode 100644 -index 000000000000..db6a70119f49 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/internal-api.rst -@@ -0,0 +1,67 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+========================== -+Internal API Documentation -+========================== -+ -+.. contents:: -+ :depth: 2 -+ -+ -+Packet Transport Layer -+====================== -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_parser.h -+ :internal: -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_parser.c -+ :internal: -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_msgb.h -+ :internal: -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_packet_layer.h -+ :internal: -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_packet_layer.c -+ :internal: -+ -+ -+Request Transport Layer -+======================= -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_request_layer.h -+ :internal: -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/ssh_request_layer.c -+ :internal: -+ -+ -+Controller -+========== -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/controller.h -+ :internal: -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/controller.c -+ :internal: -+ -+ -+Client Device Bus -+================= -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/bus.c -+ :internal: -+ -+ -+Core -+==== -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/core.c -+ :internal: -+ -+ -+Trace Helpers -+============= -+ -+.. kernel-doc:: drivers/misc/surface_aggregator/trace.h -diff --git a/Documentation/driver-api/surface_aggregator/internal.rst b/Documentation/driver-api/surface_aggregator/internal.rst -new file mode 100644 -index 000000000000..72704734982a ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/internal.rst -@@ -0,0 +1,577 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |ssh_ptl| replace:: :c:type:`struct ssh_ptl ` -+.. |ssh_ptl_submit| replace:: :c:func:`ssh_ptl_submit` -+.. |ssh_ptl_cancel| replace:: :c:func:`ssh_ptl_cancel` -+.. |ssh_ptl_shutdown| replace:: :c:func:`ssh_ptl_shutdown` -+.. |ssh_ptl_rx_rcvbuf| replace:: :c:func:`ssh_ptl_rx_rcvbuf` -+.. |ssh_rtl| replace:: :c:type:`struct ssh_rtl ` -+.. |ssh_rtl_submit| replace:: :c:func:`ssh_rtl_submit` -+.. |ssh_rtl_cancel| replace:: :c:func:`ssh_rtl_cancel` -+.. |ssh_rtl_shutdown| replace:: :c:func:`ssh_rtl_shutdown` -+.. |ssh_packet| replace:: :c:type:`struct ssh_packet ` -+.. |ssh_packet_get| replace:: :c:func:`ssh_packet_get` -+.. |ssh_packet_put| replace:: :c:func:`ssh_packet_put` -+.. |ssh_packet_ops| replace:: :c:type:`struct ssh_packet_ops ` -+.. |ssh_packet_base_priority| replace:: :c:type:`enum ssh_packet_base_priority ` -+.. |ssh_packet_flags| replace:: :c:type:`enum ssh_packet_flags ` -+.. |SSH_PACKET_PRIORITY| replace:: :c:func:`SSH_PACKET_PRIORITY` -+.. |ssh_frame| replace:: :c:type:`struct ssh_frame ` -+.. |ssh_command| replace:: :c:type:`struct ssh_command ` -+.. |ssh_request| replace:: :c:type:`struct ssh_request ` -+.. |ssh_request_get| replace:: :c:func:`ssh_request_get` -+.. |ssh_request_put| replace:: :c:func:`ssh_request_put` -+.. |ssh_request_ops| replace:: :c:type:`struct ssh_request_ops ` -+.. |ssh_request_init| replace:: :c:func:`ssh_request_init` -+.. |ssh_request_flags| replace:: :c:type:`enum ssh_request_flags ` -+.. |ssam_controller| replace:: :c:type:`struct ssam_controller ` -+.. |ssam_device| replace:: :c:type:`struct ssam_device ` -+.. |ssam_device_driver| replace:: :c:type:`struct ssam_device_driver ` -+.. |ssam_client_bind| replace:: :c:func:`ssam_client_bind` -+.. |ssam_client_link| replace:: :c:func:`ssam_client_link` -+.. |ssam_request_sync| replace:: :c:type:`struct ssam_request_sync ` -+.. |ssam_event_registry| replace:: :c:type:`struct ssam_event_registry ` -+.. |ssam_event_id| replace:: :c:type:`struct ssam_event_id ` -+.. |ssam_nf| replace:: :c:type:`struct ssam_nf ` -+.. |ssam_nf_refcount_inc| replace:: :c:func:`ssam_nf_refcount_inc` -+.. |ssam_nf_refcount_dec| replace:: :c:func:`ssam_nf_refcount_dec` -+.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` -+.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` -+.. |ssam_cplt| replace:: :c:type:`struct ssam_cplt ` -+.. |ssam_event_queue| replace:: :c:type:`struct ssam_event_queue ` -+.. |ssam_request_sync_submit| replace:: :c:func:`ssam_request_sync_submit` -+ -+===================== -+Core Driver Internals -+===================== -+ -+Architectural overview of the Surface System Aggregator Module (SSAM) core -+and Surface Serial Hub (SSH) driver. For the API documentation, refer to: -+ -+.. toctree:: -+ :maxdepth: 2 -+ -+ internal-api -+ -+ -+Overview -+======== -+ -+The SSAM core implementation is structured in layers, somewhat following the -+SSH protocol structure: -+ -+Lower-level packet transport is implemented in the *packet transport layer -+(PTL)*, directly building on top of the serial device (serdev) -+infrastructure of the kernel. As the name indicates, this layer deals with -+the packet transport logic and handles things like packet validation, packet -+acknowledgment (ACKing), packet (retransmission) timeouts, and relaying -+packet payloads to higher-level layers. -+ -+Above this sits the *request transport layer (RTL)*. This layer is centered -+around command-type packet payloads, i.e. requests (sent from host to EC), -+responses of the EC to those requests, and events (sent from EC to host). -+It, specifically, distinguishes events from request responses, matches -+responses to their corresponding requests, and implements request timeouts. -+ -+The *controller* layer is building on top of this and essentially decides -+how request responses and, especially, events are dealt with. It provides an -+event notifier system, handles event activation/deactivation, provides a -+workqueue for event and asynchronous request completion, and also manages -+the message counters required for building command messages (``SEQ``, -+``RQID``). This layer basically provides a fundamental interface to the SAM -+EC for use in other kernel drivers. -+ -+While the controller layer already provides an interface for other kernel -+drivers, the client *bus* extends this interface to provide support for -+native SSAM devices, i.e. devices that are not defined in ACPI and not -+implemented as platform devices, via |ssam_device| and |ssam_device_driver| -+simplify management of client devices and client drivers. -+ -+Refer to :doc:`client` for documentation regarding the client device/driver -+API and interface options for other kernel drivers. It is recommended to -+familiarize oneself with that chapter and the :doc:`ssh` before continuing -+with the architectural overview below. -+ -+ -+Packet Transport Layer -+====================== -+ -+The packet transport layer is represented via |ssh_ptl| and is structured -+around the following key concepts: -+ -+Packets -+------- -+ -+Packets are the fundamental transmission unit of the SSH protocol. They are -+managed by the packet transport layer, which is essentially the lowest layer -+of the driver and is built upon by other components of the SSAM core. -+Packets to be transmitted by the SSAM core are represented via |ssh_packet| -+(in contrast, packets received by the core do not have any specific -+structure and are managed entirely via the raw |ssh_frame|). -+ -+This structure contains the required fields to manage the packet inside the -+transport layer, as well as a reference to the buffer containing the data to -+be transmitted (i.e. the message wrapped in |ssh_frame|). Most notably, it -+contains an internal reference count, which is used for managing its -+lifetime (accessible via |ssh_packet_get| and |ssh_packet_put|). When this -+counter reaches zero, the ``release()`` callback provided to the packet via -+its |ssh_packet_ops| reference is executed, which may then deallocate the -+packet or its enclosing structure (e.g. |ssh_request|). -+ -+In addition to the ``release`` callback, the |ssh_packet_ops| reference also -+provides a ``complete()`` callback, which is run once the packet has been -+completed and provides the status of this completion, i.e. zero on success -+or a negative errno value in case of an error. Once the packet has been -+submitted to the packet transport layer, the ``complete()`` callback is -+always guaranteed to be executed before the ``release()`` callback, i.e. the -+packet will always be completed, either successfully, with an error, or due -+to cancellation, before it will be released. -+ -+The state of a packet is managed via its ``state`` flags -+(|ssh_packet_flags|), which also contains the packet type. In particular, -+the following bits are noteworthy: -+ -+* ``SSH_PACKET_SF_LOCKED_BIT``: This bit is set when completion, either -+ through error or success, is imminent. It indicates that no further -+ references of the packet should be taken and any existing references -+ should be dropped as soon as possible. The process setting this bit is -+ responsible for removing any references to this packet from the packet -+ queue and pending set. -+ -+* ``SSH_PACKET_SF_COMPLETED_BIT``: This bit is set by the process running the -+ ``complete()`` callback and is used to ensure that this callback only runs -+ once. -+ -+* ``SSH_PACKET_SF_QUEUED_BIT``: This bit is set when the packet is queued on -+ the packet queue and cleared when it is dequeued. -+ -+* ``SSH_PACKET_SF_PENDING_BIT``: This bit is set when the packet is added to -+ the pending set and cleared when it is removed from it. -+ -+Packet Queue -+------------ -+ -+The packet queue is the first of the two fundamental collections in the -+packet transport layer. It is a priority queue, with priority of the -+respective packets based on the packet type (major) and number of tries -+(minor). See |SSH_PACKET_PRIORITY| for more details on the priority value. -+ -+All packets to be transmitted by the transport layer must be submitted to -+this queue via |ssh_ptl_submit|. Note that this includes control packets -+sent by the transport layer itself. Internally, data packets can be -+re-submitted to this queue due to timeouts or NAK packets sent by the EC. -+ -+Pending Set -+----------- -+ -+The pending set is the second of the two fundamental collections in the -+packet transport layer. It stores references to packets that have already -+been transmitted, but wait for acknowledgment (e.g. the corresponding ACK -+packet) by the EC. -+ -+Note that a packet may both be pending and queued if it has been -+re-submitted due to a packet acknowledgment timeout or NAK. On such a -+re-submission, packets are not removed from the pending set. -+ -+Transmitter Thread -+------------------ -+ -+The transmitter thread is responsible for most of the actual work regarding -+packet transmission. In each iteration, it (waits for and) checks if the -+next packet on the queue (if any) can be transmitted and, if so, removes it -+from the queue and increments its counter for the number of transmission -+attempts, i.e. tries. If the packet is sequenced, i.e. requires an ACK by -+the EC, the packet is added to the pending set. Next, the packet's data is -+submitted to the serdev subsystem. In case of an error or timeout during -+this submission, the packet is completed by the transmitter thread with the -+status value of the callback set accordingly. In case the packet is -+unsequenced, i.e. does not require an ACK by the EC, the packet is completed -+with success on the transmitter thread. -+ -+Transmission of sequenced packets is limited by the number of concurrently -+pending packets, i.e. a limit on how many packets may be waiting for an ACK -+from the EC in parallel. This limit is currently set to one (see :doc:`ssh` -+for the reasoning behind this). Control packets (i.e. ACK and NAK) can -+always be transmitted. -+ -+Receiver Thread -+--------------- -+ -+Any data received from the EC is put into a FIFO buffer for further -+processing. This processing happens on the receiver thread. The receiver -+thread parses and validates the received message into its |ssh_frame| and -+corresponding payload. It prepares and submits the necessary ACK (and on -+validation error or invalid data NAK) packets for the received messages. -+ -+This thread also handles further processing, such as matching ACK messages -+to the corresponding pending packet (via sequence ID) and completing it, as -+well as initiating re-submission of all currently pending packets on -+receival of a NAK message (re-submission in case of a NAK is similar to -+re-submission due to timeout, see below for more details on that). Note that -+the successful completion of a sequenced packet will always run on the -+receiver thread (whereas any failure-indicating completion will run on the -+process where the failure occurred). -+ -+Any payload data is forwarded via a callback to the next upper layer, i.e. -+the request transport layer. -+ -+Timeout Reaper -+-------------- -+ -+The packet acknowledgment timeout is a per-packet timeout for sequenced -+packets, started when the respective packet begins (re-)transmission (i.e. -+this timeout is armed once per transmission attempt on the transmitter -+thread). It is used to trigger re-submission or, when the number of tries -+has been exceeded, cancellation of the packet in question. -+ -+This timeout is handled via a dedicated reaper task, which is essentially a -+work item (re-)scheduled to run when the next packet is set to time out. The -+work item then checks the set of pending packets for any packets that have -+exceeded the timeout and, if there are any remaining packets, re-schedules -+itself to the next appropriate point in time. -+ -+If a timeout has been detected by the reaper, the packet will either be -+re-submitted if it still has some remaining tries left, or completed with -+``-ETIMEDOUT`` as status if not. Note that re-submission, in this case and -+triggered by receival of a NAK, means that the packet is added to the queue -+with a now incremented number of tries, yielding a higher priority. The -+timeout for the packet will be disabled until the next transmission attempt -+and the packet remains on the pending set. -+ -+Note that due to transmission and packet acknowledgment timeouts, the packet -+transport layer is always guaranteed to make progress, if only through -+timing out packets, and will never fully block. -+ -+Concurrency and Locking -+----------------------- -+ -+There are two main locks in the packet transport layer: One guarding access -+to the packet queue and one guarding access to the pending set. These -+collections may only be accessed and modified under the respective lock. If -+access to both collections is needed, the pending lock must be acquired -+before the queue lock to avoid deadlocks. -+ -+In addition to guarding the collections, after initial packet submission -+certain packet fields may only be accessed under one of the locks. -+Specifically, the packet priority must only be accessed while holding the -+queue lock and the packet timestamp must only be accessed while holding the -+pending lock. -+ -+Other parts of the packet transport layer are guarded independently. State -+flags are managed by atomic bit operations and, if necessary, memory -+barriers. Modifications to the timeout reaper work item and expiration date -+are guarded by their own lock. -+ -+The reference of the packet to the packet transport layer (``ptl``) is -+somewhat special. It is either set when the upper layer request is submitted -+or, if there is none, when the packet is first submitted. After it is set, -+it will not change its value. Functions that may run concurrently with -+submission, i.e. cancellation, can not rely on the ``ptl`` reference to be -+set. Access to it in these functions is guarded by ``READ_ONCE()``, whereas -+setting ``ptl`` is equally guarded with ``WRITE_ONCE()`` for symmetry. -+ -+Some packet fields may be read outside of the respective locks guarding -+them, specifically priority and state for tracing. In those cases, proper -+access is ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. Such -+read-only access is only allowed when stale values are not critical. -+ -+With respect to the interface for higher layers, packet submission -+(|ssh_ptl_submit|), packet cancellation (|ssh_ptl_cancel|), data receival -+(|ssh_ptl_rx_rcvbuf|), and layer shutdown (|ssh_ptl_shutdown|) may always be -+executed concurrently with respect to each other. Note that packet -+submission may not run concurrently with itself for the same packet. -+Equally, shutdown and data receival may also not run concurrently with -+themselves (but may run concurrently with each other). -+ -+ -+Request Transport Layer -+======================= -+ -+The request transport layer is represented via |ssh_rtl| and builds on top -+of the packet transport layer. It deals with requests, i.e. SSH packets sent -+by the host containing a |ssh_command| as frame payload. This layer -+separates responses to requests from events, which are also sent by the EC -+via a |ssh_command| payload. While responses are handled in this layer, -+events are relayed to the next upper layer, i.e. the controller layer, via -+the corresponding callback. The request transport layer is structured around -+the following key concepts: -+ -+Request -+------- -+ -+Requests are packets with a command-type payload, sent from host to EC to -+query data from or trigger an action on it (or both simultaneously). They -+are represented by |ssh_request|, wrapping the underlying |ssh_packet| -+storing its message data (i.e. SSH frame with command payload). Note that -+all top-level representations, e.g. |ssam_request_sync| are built upon this -+struct. -+ -+As |ssh_request| extends |ssh_packet|, its lifetime is also managed by the -+reference counter inside the packet struct (which can be accessed via -+|ssh_request_get| and |ssh_request_put|). Once the counter reaches zero, the -+``release()`` callback of the |ssh_request_ops| reference of the request is -+called. -+ -+Requests can have an optional response that is equally sent via a SSH -+message with command-type payload (from EC to host). The party constructing -+the request must know if a response is expected and mark this in the request -+flags provided to |ssh_request_init|, so that the request transport layer -+can wait for this response. -+ -+Similar to |ssh_packet|, |ssh_request| also has a ``complete()`` callback -+provided via its request ops reference and is guaranteed to be completed -+before it is released once it has been submitted to the request transport -+layer via |ssh_rtl_submit|. For a request without a response, successful -+completion will occur once the underlying packet has been successfully -+transmitted by the packet transport layer (i.e. from within the packet -+completion callback). For a request with response, successful completion -+will occur once the response has been received and matched to the request -+via its request ID (which happens on the packet layer's data-received -+callback running on the receiver thread). If the request is completed with -+an error, the status value will be set to the corresponding (negative) errno -+value. -+ -+The state of a request is again managed via its ``state`` flags -+(|ssh_request_flags|), which also encode the request type. In particular, -+the following bits are noteworthy: -+ -+* ``SSH_REQUEST_SF_LOCKED_BIT``: This bit is set when completion, either -+ through error or success, is imminent. It indicates that no further -+ references of the request should be taken and any existing references -+ should be dropped as soon as possible. The process setting this bit is -+ responsible for removing any references to this request from the request -+ queue and pending set. -+ -+* ``SSH_REQUEST_SF_COMPLETED_BIT``: This bit is set by the process running the -+ ``complete()`` callback and is used to ensure that this callback only runs -+ once. -+ -+* ``SSH_REQUEST_SF_QUEUED_BIT``: This bit is set when the request is queued on -+ the request queue and cleared when it is dequeued. -+ -+* ``SSH_REQUEST_SF_PENDING_BIT``: This bit is set when the request is added to -+ the pending set and cleared when it is removed from it. -+ -+Request Queue -+------------- -+ -+The request queue is the first of the two fundamental collections in the -+request transport layer. In contrast to the packet queue of the packet -+transport layer, it is not a priority queue and the simple first come first -+serve principle applies. -+ -+All requests to be transmitted by the request transport layer must be -+submitted to this queue via |ssh_rtl_submit|. Once submitted, requests may -+not be re-submitted, and will not be re-submitted automatically on timeout. -+Instead, the request is completed with a timeout error. If desired, the -+caller can create and submit a new request for another try, but it must not -+submit the same request again. -+ -+Pending Set -+----------- -+ -+The pending set is the second of the two fundamental collections in the -+request transport layer. This collection stores references to all pending -+requests, i.e. requests awaiting a response from the EC (similar to what the -+pending set of the packet transport layer does for packets). -+ -+Transmitter Task -+---------------- -+ -+The transmitter task is scheduled when a new request is available for -+transmission. It checks if the next request on the request queue can be -+transmitted and, if so, submits its underlying packet to the packet -+transport layer. This check ensures that only a limited number of -+requests can be pending, i.e. waiting for a response, at the same time. If -+the request requires a response, the request is added to the pending set -+before its packet is submitted. -+ -+Packet Completion Callback -+-------------------------- -+ -+The packet completion callback is executed once the underlying packet of a -+request has been completed. In case of an error completion, the -+corresponding request is completed with the error value provided in this -+callback. -+ -+On successful packet completion, further processing depends on the request. -+If the request expects a response, it is marked as transmitted and the -+request timeout is started. If the request does not expect a response, it is -+completed with success. -+ -+Data-Received Callback -+---------------------- -+ -+The data received callback notifies the request transport layer of data -+being received by the underlying packet transport layer via a data-type -+frame. In general, this is expected to be a command-type payload. -+ -+If the request ID of the command is one of the request IDs reserved for -+events (one to ``SSH_NUM_EVENTS``, inclusively), it is forwarded to the -+event callback registered in the request transport layer. If the request ID -+indicates a response to a request, the respective request is looked up in -+the pending set and, if found and marked as transmitted, completed with -+success. -+ -+Timeout Reaper -+-------------- -+ -+The request-response-timeout is a per-request timeout for requests expecting -+a response. It is used to ensure that a request does not wait indefinitely -+on a response from the EC and is started after the underlying packet has -+been successfully completed. -+ -+This timeout is, similar to the packet acknowledgment timeout on the packet -+transport layer, handled via a dedicated reaper task. This task is -+essentially a work-item (re-)scheduled to run when the next request is set -+to time out. The work item then scans the set of pending requests for any -+requests that have timed out and completes them with ``-ETIMEDOUT`` as -+status. Requests will not be re-submitted automatically. Instead, the issuer -+of the request must construct and submit a new request, if so desired. -+ -+Note that this timeout, in combination with packet transmission and -+acknowledgment timeouts, guarantees that the request layer will always make -+progress, even if only through timing out packets, and never fully block. -+ -+Concurrency and Locking -+----------------------- -+ -+Similar to the packet transport layer, there are two main locks in the -+request transport layer: One guarding access to the request queue and one -+guarding access to the pending set. These collections may only be accessed -+and modified under the respective lock. -+ -+Other parts of the request transport layer are guarded independently. State -+flags are (again) managed by atomic bit operations and, if necessary, memory -+barriers. Modifications to the timeout reaper work item and expiration date -+are guarded by their own lock. -+ -+Some request fields may be read outside of the respective locks guarding -+them, specifically the state for tracing. In those cases, proper access is -+ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. Such read-only -+access is only allowed when stale values are not critical. -+ -+With respect to the interface for higher layers, request submission -+(|ssh_rtl_submit|), request cancellation (|ssh_rtl_cancel|), and layer -+shutdown (|ssh_rtl_shutdown|) may always be executed concurrently with -+respect to each other. Note that request submission may not run concurrently -+with itself for the same request (and also may only be called once per -+request). Equally, shutdown may also not run concurrently with itself. -+ -+ -+Controller Layer -+================ -+ -+The controller layer extends on the request transport layer to provide an -+easy-to-use interface for client drivers. It is represented by -+|ssam_controller| and the SSH driver. While the lower level transport layers -+take care of transmitting and handling packets and requests, the controller -+layer takes on more of a management role. Specifically, it handles device -+initialization, power management, and event handling, including event -+delivery and registration via the (event) completion system (|ssam_cplt|). -+ -+Event Registration -+------------------ -+ -+In general, an event (or rather a class of events) has to be explicitly -+requested by the host before the EC will send it (HID input events seem to -+be the exception). This is done via an event-enable request (similarly, -+events should be disabled via an event-disable request once no longer -+desired). -+ -+The specific request used to enable (or disable) an event is given via an -+event registry, i.e. the governing authority of this event (so to speak), -+represented by |ssam_event_registry|. As parameters to this request, the -+target category and, depending on the event registry, instance ID of the -+event to be enabled must be provided. This (optional) instance ID must be -+zero if the registry does not use it. Together, target category and instance -+ID form the event ID, represented by |ssam_event_id|. In short, both, event -+registry and event ID, are required to uniquely identify a respective class -+of events. -+ -+Note that a further *request ID* parameter must be provided for the -+enable-event request. This parameter does not influence the class of events -+being enabled, but instead is set as the request ID (RQID) on each event of -+this class sent by the EC. It is used to identify events (as a limited -+number of request IDs is reserved for use in events only, specifically one -+to ``SSH_NUM_EVENTS`` inclusively) and also map events to their specific -+class. Currently, the controller always sets this parameter to the target -+category specified in |ssam_event_id|. -+ -+As multiple client drivers may rely on the same (or overlapping) classes of -+events and enable/disable calls are strictly binary (i.e. on/off), the -+controller has to manage access to these events. It does so via reference -+counting, storing the counter inside an RB-tree based mapping with event -+registry and ID as key (there is no known list of valid event registry and -+event ID combinations). See |ssam_nf|, |ssam_nf_refcount_inc|, and -+|ssam_nf_refcount_dec| for details. -+ -+This management is done together with notifier registration (described in -+the next section) via the top-level |ssam_notifier_register| and -+|ssam_notifier_unregister| functions. -+ -+Event Delivery -+-------------- -+ -+To receive events, a client driver has to register an event notifier via -+|ssam_notifier_register|. This increments the reference counter for that -+specific class of events (as detailed in the previous section), enables the -+class on the EC (if it has not been enabled already), and installs the -+provided notifier callback. -+ -+Notifier callbacks are stored in lists, with one (RCU) list per target -+category (provided via the event ID; NB: there is a fixed known number of -+target categories). There is no known association from the combination of -+event registry and event ID to the command data (target ID, target category, -+command ID, and instance ID) that can be provided by an event class, apart -+from target category and instance ID given via the event ID. -+ -+Note that due to the way notifiers are (or rather have to be) stored, client -+drivers may receive events that they have not requested and need to account -+for them. Specifically, they will, by default, receive all events from the -+same target category. To simplify dealing with this, filtering of events by -+target ID (provided via the event registry) and instance ID (provided via -+the event ID) can be requested when registering a notifier. This filtering -+is applied when iterating over the notifiers at the time they are executed. -+ -+All notifier callbacks are executed on a dedicated workqueue, the so-called -+completion workqueue. After an event has been received via the callback -+installed in the request layer (running on the receiver thread of the packet -+transport layer), it will be put on its respective event queue -+(|ssam_event_queue|). From this event queue the completion work item of that -+queue (running on the completion workqueue) will pick up the event and -+execute the notifier callback. This is done to avoid blocking on the -+receiver thread. -+ -+There is one event queue per combination of target ID and target category. -+This is done to ensure that notifier callbacks are executed in sequence for -+events of the same target ID and target category. Callbacks can be executed -+in parallel for events with a different combination of target ID and target -+category. -+ -+Concurrency and Locking -+----------------------- -+ -+Most of the concurrency related safety guarantees of the controller are -+provided by the lower-level request transport layer. In addition to this, -+event (un-)registration is guarded by its own lock. -+ -+Access to the controller state is guarded by the state lock. This lock is a -+read/write semaphore. The reader part can be used to ensure that the state -+does not change while functions depending on the state to stay the same -+(e.g. |ssam_notifier_register|, |ssam_notifier_unregister|, -+|ssam_request_sync_submit|, and derivatives) are executed and this guarantee -+is not already provided otherwise (e.g. through |ssam_client_bind| or -+|ssam_client_link|). The writer part guards any transitions that will change -+the state, i.e. initialization, destruction, suspension, and resumption. -+ -+The controller state may be accessed (read-only) outside the state lock for -+smoke-testing against invalid API usage (e.g. in |ssam_request_sync_submit|). -+Note that such checks are not supposed to (and will not) protect against all -+invalid usages, but rather aim to help catch them. In those cases, proper -+variable access is ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. -+ -+Assuming any preconditions on the state not changing have been satisfied, -+all non-initialization and non-shutdown functions may run concurrently with -+each other. This includes |ssam_notifier_register|, |ssam_notifier_unregister|, -+|ssam_request_sync_submit|, as well as all functions building on top of those. -diff --git a/Documentation/driver-api/surface_aggregator/overview.rst b/Documentation/driver-api/surface_aggregator/overview.rst -new file mode 100644 -index 000000000000..1e9d57e50063 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/overview.rst -@@ -0,0 +1,77 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+======== -+Overview -+======== -+ -+The Surface/System Aggregator Module (SAM, SSAM) is an (arguably *the*) -+embedded controller (EC) on Microsoft Surface devices. It has been originally -+introduced on 4th generation devices (Surface Pro 4, Surface Book 1), but -+its responsibilities and feature-set have since been expanded significantly -+with the following generations. -+ -+ -+Features and Integration -+======================== -+ -+Not much is currently known about SAM on 4th generation devices (Surface Pro -+4, Surface Book 1), due to the use of a different communication interface -+between host and EC (as detailed below). On 5th (Surface Pro 2017, Surface -+Book 2, Surface Laptop 1) and later generation devices, SAM is responsible -+for providing battery information (both current status and static values, -+such as maximum capacity etc.), as well as an assortment of temperature -+sensors (e.g. skin temperature) and cooling/performance-mode setting to the -+host. On the Surface Book 2, specifically, it additionally provides an -+interface for properly handling clipboard detachment (i.e. separating the -+display part from the keyboard part of the device), on the Surface Laptop 1 -+and 2 it is required for keyboard HID input. This HID subsystem has been -+restructured for 7th generation devices and on those, specifically Surface -+Laptop 3 and Surface Book 3, is responsible for all major HID input (i.e. -+keyboard and touchpad). -+ -+While features have not changed much on a coarse level since the 5th -+generation, internal interfaces have undergone some rather large changes. On -+5th and 6th generation devices, both battery and temperature information is -+exposed to ACPI via a shim driver (referred to as Surface ACPI Notify, or -+SAN), translating ACPI generic serial bus write-/read-accesses to SAM -+requests. On 7th generation devices, this additional layer is gone and these -+devices require a driver hooking directly into the SAM interface. Equally, -+on newer generations, less devices are declared in ACPI, making them a bit -+harder to discover and requiring us to hard-code a sort of device registry. -+Due to this, a SSAM bus and subsystem with client devices -+(:c:type:`struct ssam_device `) has been implemented. -+ -+ -+Communication -+============= -+ -+The type of communication interface between host and EC depends on the -+generation of the Surface device. On 4th generation devices, host and EC -+communicate via HID, specifically using a HID-over-I2C device, whereas on -+5th and later generations, communication takes place via a USART serial -+device. In accordance to the drivers found on other operating systems, we -+refer to the serial device and its driver as Surface Serial Hub (SSH). When -+needed, we differentiate between both types of SAM by referring to them as -+SAM-over-SSH and SAM-over-HID. -+ -+Currently, this subsystem only supports SAM-over-SSH. The SSH communication -+interface is described in more detail below. The HID interface has not been -+reverse engineered yet and it is, at the moment, unclear how many (and -+which) concepts of the SSH interface detailed below can be transferred to -+it. -+ -+Surface Serial Hub -+------------------ -+ -+As already elaborated above, the Surface Serial Hub (SSH) is the -+communication interface for SAM on 5th- and all later-generation Surface -+devices. On the highest level, communication can be separated into two main -+types: Requests, messages sent from host to EC that may trigger a direct -+response from the EC (explicitly associated with the request), and events -+(sometimes also referred to as notifications), sent from EC to host without -+being a direct response to a previous request. We may also refer to requests -+without response as commands. In general, events need to be enabled via one -+of multiple dedicated requests before they are sent by the EC. -+ -+See :doc:`ssh` for a more technical protocol documentation and -+:doc:`internal` for an overview of the internal driver architecture. -diff --git a/Documentation/driver-api/surface_aggregator/ssh.rst b/Documentation/driver-api/surface_aggregator/ssh.rst -new file mode 100644 -index 000000000000..bf007d6c9873 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/ssh.rst -@@ -0,0 +1,344 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |u8| replace:: :c:type:`u8 ` -+.. |u16| replace:: :c:type:`u16 ` -+.. |TYPE| replace:: ``TYPE`` -+.. |LEN| replace:: ``LEN`` -+.. |SEQ| replace:: ``SEQ`` -+.. |SYN| replace:: ``SYN`` -+.. |NAK| replace:: ``NAK`` -+.. |ACK| replace:: ``ACK`` -+.. |DATA| replace:: ``DATA`` -+.. |DATA_SEQ| replace:: ``DATA_SEQ`` -+.. |DATA_NSQ| replace:: ``DATA_NSQ`` -+.. |TC| replace:: ``TC`` -+.. |TID| replace:: ``TID`` -+.. |IID| replace:: ``IID`` -+.. |RQID| replace:: ``RQID`` -+.. |CID| replace:: ``CID`` -+ -+=========================== -+Surface Serial Hub Protocol -+=========================== -+ -+The Surface Serial Hub (SSH) is the central communication interface for the -+embedded Surface Aggregator Module controller (SAM or EC), found on newer -+Surface generations. We will refer to this protocol and interface as -+SAM-over-SSH, as opposed to SAM-over-HID for the older generations. -+ -+On Surface devices with SAM-over-SSH, SAM is connected to the host via UART -+and defined in ACPI as device with ID ``MSHW0084``. On these devices, -+significant functionality is provided via SAM, including access to battery -+and power information and events, thermal read-outs and events, and many -+more. For Surface Laptops, keyboard input is handled via HID directed -+through SAM, on the Surface Laptop 3 and Surface Book 3 this also includes -+touchpad input. -+ -+Note that the standard disclaimer for this subsystem also applies to this -+document: All of this has been reverse-engineered and may thus be erroneous -+and/or incomplete. -+ -+All CRCs used in the following are two-byte ``crc_ccitt_false(0xffff, ...)``. -+All multi-byte values are little-endian, there is no implicit padding between -+values. -+ -+ -+SSH Packet Protocol: Definitions -+================================ -+ -+The fundamental communication unit of the SSH protocol is a frame -+(:c:type:`struct ssh_frame `). A frame consists of the following -+fields, packed together and in order: -+ -+.. flat-table:: SSH Frame -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - |TYPE| -+ - |u8| -+ - Type identifier of the frame. -+ -+ * - |LEN| -+ - |u16| -+ - Length of the payload associated with the frame. -+ -+ * - |SEQ| -+ - |u8| -+ - Sequence ID (see explanation below). -+ -+Each frame structure is followed by a CRC over this structure. The CRC over -+the frame structure (|TYPE|, |LEN|, and |SEQ| fields) is placed directly -+after the frame structure and before the payload. The payload is followed by -+its own CRC (over all payload bytes). If the payload is not present (i.e. -+the frame has ``LEN=0``), the CRC of the payload is still present and will -+evaluate to ``0xffff``. The |LEN| field does not include any of the CRCs, it -+equals the number of bytes inbetween the CRC of the frame and the CRC of the -+payload. -+ -+Additionally, the following fixed two-byte sequences are used: -+ -+.. flat-table:: SSH Byte Sequences -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Name -+ - Value -+ - Description -+ -+ * - |SYN| -+ - ``[0xAA, 0x55]`` -+ - Synchronization bytes. -+ -+A message consists of |SYN|, followed by the frame (|TYPE|, |LEN|, |SEQ| and -+CRC) and, if specified in the frame (i.e. ``LEN > 0``), payload bytes, -+followed finally, regardless if the payload is present, the payload CRC. The -+messages corresponding to an exchange are, in part, identified by having the -+same sequence ID (|SEQ|), stored inside the frame (more on this in the next -+section). The sequence ID is a wrapping counter. -+ -+A frame can have the following types -+(:c:type:`enum ssh_frame_type `): -+ -+.. flat-table:: SSH Frame Types -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Name -+ - Value -+ - Short Description -+ -+ * - |NAK| -+ - ``0x04`` -+ - Sent on error in previously received message. -+ -+ * - |ACK| -+ - ``0x40`` -+ - Sent to acknowledge receival of |DATA| frame. -+ -+ * - |DATA_SEQ| -+ - ``0x80`` -+ - Sent to transfer data. Sequenced. -+ -+ * - |DATA_NSQ| -+ - ``0x00`` -+ - Same as |DATA_SEQ|, but does not need to be ACKed. -+ -+Both |NAK|- and |ACK|-type frames are used to control flow of messages and -+thus do not carry a payload. |DATA_SEQ|- and |DATA_NSQ|-type frames on the -+other hand must carry a payload. The flow sequence and interaction of -+different frame types will be described in more depth in the next section. -+ -+ -+SSH Packet Protocol: Flow Sequence -+================================== -+ -+Each exchange begins with |SYN|, followed by a |DATA_SEQ|- or -+|DATA_NSQ|-type frame, followed by its CRC, payload, and payload CRC. In -+case of a |DATA_NSQ|-type frame, the exchange is then finished. In case of a -+|DATA_SEQ|-type frame, the receiving party has to acknowledge receival of -+the frame by responding with a message containing an |ACK|-type frame with -+the same sequence ID of the |DATA| frame. In other words, the sequence ID of -+the |ACK| frame specifies the |DATA| frame to be acknowledged. In case of an -+error, e.g. an invalid CRC, the receiving party responds with a message -+containing an |NAK|-type frame. As the sequence ID of the previous data -+frame, for which an error is indicated via the |NAK| frame, cannot be relied -+upon, the sequence ID of the |NAK| frame should not be used and is set to -+zero. After receival of an |NAK| frame, the sending party should re-send all -+outstanding (non-ACKed) messages. -+ -+Sequence IDs are not synchronized between the two parties, meaning that they -+are managed independently for each party. Identifying the messages -+corresponding to a single exchange thus relies on the sequence ID as well as -+the type of the message, and the context. Specifically, the sequence ID is -+used to associate an ``ACK`` with its ``DATA_SEQ``-type frame, but not -+``DATA_SEQ``- or ``DATA_NSQ``-type frames with other ``DATA``- type frames. -+ -+An example exchange might look like this: -+ -+:: -+ -+ tx: -- SYN FRAME(D) CRC(F) PAYLOAD CRC(P) ----------------------------- -+ rx: ------------------------------------- SYN FRAME(A) CRC(F) CRC(P) -- -+ -+where both frames have the same sequence ID (``SEQ``). Here, ``FRAME(D)`` -+indicates a |DATA_SEQ|-type frame, ``FRAME(A)`` an ``ACK``-type frame, -+``CRC(F)`` the CRC over the previous frame, ``CRC(P)`` the CRC over the -+previous payload. In case of an error, the exchange would look like this: -+ -+:: -+ -+ tx: -- SYN FRAME(D) CRC(F) PAYLOAD CRC(P) ----------------------------- -+ rx: ------------------------------------- SYN FRAME(N) CRC(F) CRC(P) -- -+ -+upon which the sender should re-send the message. ``FRAME(N)`` indicates an -+|NAK|-type frame. Note that the sequence ID of the |NAK|-type frame is fixed -+to zero. For |DATA_NSQ|-type frames, both exchanges are the same: -+ -+:: -+ -+ tx: -- SYN FRAME(DATA_NSQ) CRC(F) PAYLOAD CRC(P) ---------------------- -+ rx: ------------------------------------------------------------------- -+ -+Here, an error can be detected, but not corrected or indicated to the -+sending party. These exchanges are symmetric, i.e. switching ``rx`` and -+``tx`` results again in a valid exchange. Currently, no longer exchanges are -+known. -+ -+ -+Commands: Requests, Responses, and Events -+========================================= -+ -+Commands are sent as payload inside a data frame. Currently, this is the -+only known payload type of |DATA| frames, with a payload-type value of -+``0x80`` (:c:type:`SSH_PLD_TYPE_CMD `). -+ -+The command-type payload (:c:type:`struct ssh_command `) -+consists of an eight-byte command structure, followed by optional and -+variable length command data. The length of this optional data is derived -+from the frame payload length given in the corresponding frame, i.e. it is -+``frame.len - sizeof(struct ssh_command)``. The command struct contains the -+following fields, packed together and in order: -+ -+.. flat-table:: SSH Command -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - |TYPE| -+ - |u8| -+ - Type of the payload. For commands always ``0x80``. -+ -+ * - |TC| -+ - |u8| -+ - Target category. -+ -+ * - |TID| (out) -+ - |u8| -+ - Target ID for outgoing (host to EC) commands. -+ -+ * - |TID| (in) -+ - |u8| -+ - Target ID for incoming (EC to host) commands. -+ -+ * - |IID| -+ - |u8| -+ - Instance ID. -+ -+ * - |RQID| -+ - |u16| -+ - Request ID. -+ -+ * - |CID| -+ - |u8| -+ - Command ID. -+ -+The command struct and data, in general, does not contain any failure -+detection mechanism (e.g. CRCs), this is solely done on the frame level. -+ -+Command-type payloads are used by the host to send commands and requests to -+the EC as well as by the EC to send responses and events back to the host. -+We differentiate between requests (sent by the host), responses (sent by the -+EC in response to a request), and events (sent by the EC without a preceding -+request). -+ -+Commands and events are uniquely identified by their target category -+(``TC``) and command ID (``CID``). The target category specifies a general -+category for the command (e.g. system in general, vs. battery and AC, vs. -+temperature, and so on), while the command ID specifies the command inside -+that category. Only the combination of |TC| + |CID| is unique. Additionally, -+commands have an instance ID (``IID``), which is used to differentiate -+between different sub-devices. For example ``TC=3`` ``CID=1`` is a -+request to get the temperature on a thermal sensor, where |IID| specifies -+the respective sensor. If the instance ID is not used, it should be set to -+zero. If instance IDs are used, they, in general, start with a value of one, -+whereas zero may be used for instance independent queries, if applicable. A -+response to a request should have the same target category, command ID, and -+instance ID as the corresponding request. -+ -+Responses are matched to their corresponding request via the request ID -+(``RQID``) field. This is a 16 bit wrapping counter similar to the sequence -+ID on the frames. Note that the sequence ID of the frames for a -+request-response pair does not match. Only the request ID has to match. -+Frame-protocol wise these are two separate exchanges, and may even be -+separated, e.g. by an event being sent after the request but before the -+response. Not all commands produce a response, and this is not detectable by -+|TC| + |CID|. It is the responsibility of the issuing party to wait for a -+response (or signal this to the communication framework, as is done in -+SAN/ACPI via the ``SNC`` flag). -+ -+Events are identified by unique and reserved request IDs. These IDs should -+not be used by the host when sending a new request. They are used on the -+host to, first, detect events and, second, match them with a registered -+event handler. Request IDs for events are chosen by the host and directed to -+the EC when setting up and enabling an event source (via the -+enable-event-source request). The EC then uses the specified request ID for -+events sent from the respective source. Note that an event should still be -+identified by its target category, command ID, and, if applicable, instance -+ID, as a single event source can send multiple different event types. In -+general, however, a single target category should map to a single reserved -+event request ID. -+ -+Furthermore, requests, responses, and events have an associated target ID -+(``TID``). This target ID is split into output (host to EC) and input (EC to -+host) fields, with the respecting other field (e.g. output field on incoming -+messages) set to zero. Two ``TID`` values are known: Primary (``0x01``) and -+secondary (``0x02``). In general, the response to a request should have the -+same ``TID`` value, however, the field (output vs. input) should be used in -+accordance to the direction in which the response is sent (i.e. on the input -+field, as responses are generally sent from the EC to the host). -+ -+Note that, even though requests and events should be uniquely identifiable -+by target category and command ID alone, the EC may require specific -+target ID and instance ID values to accept a command. A command that is -+accepted for ``TID=1``, for example, may not be accepted for ``TID=2`` -+and vice versa. -+ -+ -+Limitations and Observations -+============================ -+ -+The protocol can, in theory, handle up to ``U8_MAX`` frames in parallel, -+with up to ``U16_MAX`` pending requests (neglecting request IDs reserved for -+events). In practice, however, this is more limited. From our testing -+(although via a python and thus a user-space program), it seems that the EC -+can handle up to four requests (mostly) reliably in parallel at a certain -+time. With five or more requests in parallel, consistent discarding of -+commands (ACKed frame but no command response) has been observed. For five -+simultaneous commands, this reproducibly resulted in one command being -+dropped and four commands being handled. -+ -+However, it has also been noted that, even with three requests in parallel, -+occasional frame drops happen. Apart from this, with a limit of three -+pending requests, no dropped commands (i.e. command being dropped but frame -+carrying command being ACKed) have been observed. In any case, frames (and -+possibly also commands) should be re-sent by the host if a certain timeout -+is exceeded. This is done by the EC for frames with a timeout of one second, -+up to two re-tries (i.e. three transmissions in total). The limit of -+re-tries also applies to received NAKs, and, in a worst case scenario, can -+lead to entire messages being dropped. -+ -+While this also seems to work fine for pending data frames as long as no -+transmission failures occur, implementation and handling of these seems to -+depend on the assumption that there is only one non-acknowledged data frame. -+In particular, the detection of repeated frames relies on the last sequence -+number. This means that, if a frame that has been successfully received by -+the EC is sent again, e.g. due to the host not receiving an |ACK|, the EC -+will only detect this if it has the sequence ID of the last frame received -+by the EC. As an example: Sending two frames with ``SEQ=0`` and ``SEQ=1`` -+followed by a repetition of ``SEQ=0`` will not detect the second ``SEQ=0`` -+frame as such, and thus execute the command in this frame each time it has -+been received, i.e. twice in this example. Sending ``SEQ=0``, ``SEQ=1`` and -+then repeating ``SEQ=1`` will detect the second ``SEQ=1`` as repetition of -+the first one and ignore it, thus executing the contained command only once. -+ -+In conclusion, this suggests a limit of at most one pending un-ACKed frame -+(per party, effectively leading to synchronous communication regarding -+frames) and at most three pending commands. The limit to synchronous frame -+transfers seems to be consistent with behavior observed on Windows. -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 14cd3186dc77..fa1dcdd119e5 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -21,6 +21,7 @@ - #include - #include - #include -+#include - - #include - #include -@@ -1155,6 +1156,24 @@ static void __init mp_config_acpi_legacy_irqs(void) - } - } - -+static const struct dmi_system_id surface_quirk[] __initconst = { -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") -+ }, -+ }, -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") -+ }, -+ }, -+ {} -+}; -+ - /* - * Parse IOAPIC related entries in MADT - * returns 0 on success, < 0 on error -@@ -1212,6 +1231,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) - acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, - acpi_gbl_FADT.sci_interrupt); - -+ if (dmi_check_system(surface_quirk)) { -+ pr_warn("Surface hack: Override irq 7\n"); -+ mp_override_legacy_irq(7, 3, 3, 7); -+ } -+ - /* Fill in identity legacy mappings where no override */ - mp_config_acpi_legacy_irqs(); - -diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig -index 54bc563a8dff..30b142b86bf5 100644 ---- a/drivers/hid/Kconfig -+++ b/drivers/hid/Kconfig -@@ -910,7 +910,7 @@ config HID_SONY - * Logitech Harmony adapter for Sony Playstation 3 (Bluetooth) - - config SONY_FF -- bool "Sony PS2/3/4 accessories force feedback support" -+ bool "Sony PS2/3/4 accessories force feedback support" - depends on HID_SONY - select INPUT_FF_MEMLESS - help -@@ -1184,4 +1184,6 @@ source "drivers/hid/i2c-hid/Kconfig" - - source "drivers/hid/intel-ish-hid/Kconfig" - -+source "drivers/hid/surface-hid/Kconfig" -+ - endmenu -diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile -index 4acb583c92a6..a13d89358b0c 100644 ---- a/drivers/hid/Makefile -+++ b/drivers/hid/Makefile -@@ -142,3 +142,5 @@ obj-$(CONFIG_I2C_HID) += i2c-hid/ - - obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ - obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ -+ -+obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ -diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig -new file mode 100644 -index 000000000000..7ce9b5d641eb ---- /dev/null -+++ b/drivers/hid/surface-hid/Kconfig -@@ -0,0 +1,42 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+menu "Surface System Aggregator Module HID support" -+ depends on SURFACE_AGGREGATOR -+ depends on INPUT -+ -+config SURFACE_HID -+ tristate "HID transport driver for Surface System Aggregator Module" -+ depends on SURFACE_AGGREGATOR_REGISTRY -+ select SURFACE_HID_CORE -+ help -+ Driver to support integrated HID devices on newer Microsoft Surface -+ models. -+ -+ This driver provides support for the HID transport protocol provided -+ by the Surface Aggregator Module (i.e. the embedded controller) on -+ 7th-generation Microsoft Surface devices, i.e. Surface Book 3 and -+ Surface Laptop 3. On those models, it is mainly used to connect the -+ integrated touchpad and keyboard. -+ -+ Say M or Y here, if you want support for integrated HID devices, i.e. -+ integrated touchpad and keyboard, on 7th generation Microsoft Surface -+ models. -+ -+config SURFACE_KBD -+ tristate "HID keyboard transport driver for Surface System Aggregator Module" -+ select SURFACE_HID_CORE -+ help -+ Driver to support HID keyboards on Surface Laptop 1 and 2 devices. -+ -+ This driver provides support for the HID transport protocol provided -+ by the Surface Aggregator Module (i.e. the embedded controller) on -+ Microsoft Surface Laptops 1 and 2. It is used to connect the -+ integrated keyboard on those devices. -+ -+ Say M or Y here, if you want support for the integrated keyboard on -+ Microsoft Surface Laptops 1 and 2. -+ -+endmenu -+ -+config SURFACE_HID_CORE -+ tristate -+ select HID -diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile -new file mode 100644 -index 000000000000..4ae11cf09b25 ---- /dev/null -+++ b/drivers/hid/surface-hid/Makefile -@@ -0,0 +1,7 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+# -+# Makefile - Surface System Aggregator Module (SSAM) HID transport driver. -+# -+obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o -+obj-$(CONFIG_SURFACE_HID) += surface_hid.o -+obj-$(CONFIG_SURFACE_KBD) += surface_kbd.o -diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c -new file mode 100644 -index 000000000000..a3a70e4f3f6c ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_hid.c -@@ -0,0 +1,253 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) HID transport driver for the -+ * generic HID interface (HID/TC=0x15 subsystem). Provides support for -+ * integrated HID devices on Surface Laptop 3, Book 3, and later. -+ * -+ * Copyright (C) 2019-2021 Blaž Hrastnik , -+ * Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "surface_hid_core.h" -+ -+ -+/* -- SAM interface. -------------------------------------------------------- */ -+ -+struct surface_hid_buffer_slice { -+ __u8 entry; -+ __le32 offset; -+ __le32 length; -+ __u8 end; -+ __u8 data[]; -+} __packed; -+ -+static_assert(sizeof(struct surface_hid_buffer_slice) == 10); -+ -+enum surface_hid_cid { -+ SURFACE_HID_CID_OUTPUT_REPORT = 0x01, -+ SURFACE_HID_CID_GET_FEATURE_REPORT = 0x02, -+ SURFACE_HID_CID_SET_FEATURE_REPORT = 0x03, -+ SURFACE_HID_CID_GET_DESCRIPTOR = 0x04, -+}; -+ -+static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len) -+{ -+ u8 buffer[sizeof(struct surface_hid_buffer_slice) + 0x76]; -+ struct surface_hid_buffer_slice *slice; -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ u32 buffer_len, offset, length; -+ int status; -+ -+ /* -+ * Note: The 0x76 above has been chosen because that's what's used by -+ * the Windows driver. Together with the header, this leads to a 128 -+ * byte payload in total. -+ */ -+ -+ buffer_len = ARRAY_SIZE(buffer) - sizeof(struct surface_hid_buffer_slice); -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_HID_CID_GET_DESCRIPTOR; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(struct surface_hid_buffer_slice); -+ rqst.payload = buffer; -+ -+ rsp.capacity = ARRAY_SIZE(buffer); -+ rsp.pointer = buffer; -+ -+ slice = (struct surface_hid_buffer_slice *)buffer; -+ slice->entry = entry; -+ slice->end = 0; -+ -+ offset = 0; -+ length = buffer_len; -+ -+ while (!slice->end && offset < len) { -+ put_unaligned_le32(offset, &slice->offset); -+ put_unaligned_le32(length, &slice->length); -+ -+ rsp.length = 0; -+ -+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, -+ sizeof(*slice)); -+ if (status) -+ return status; -+ -+ offset = get_unaligned_le32(&slice->offset); -+ length = get_unaligned_le32(&slice->length); -+ -+ /* Don't mess stuff up in case we receive garbage. */ -+ if (length > buffer_len || offset > len) -+ return -EPROTO; -+ -+ if (offset + length > len) -+ length = len - offset; -+ -+ memcpy(buf + offset, &slice->data[0], length); -+ -+ offset += length; -+ length = buffer_len; -+ } -+ -+ if (offset != len) { -+ dev_err(shid->dev, "unexpected descriptor length: got %u, expected %zu\n", -+ offset, len); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id, bool feature, -+ u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ u8 cid; -+ -+ if (feature) -+ cid = SURFACE_HID_CID_SET_FEATURE_REPORT; -+ else -+ cid = SURFACE_HID_CID_OUTPUT_REPORT; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.instance_id = shid->uid.instance; -+ rqst.command_id = cid; -+ rqst.flags = 0; -+ rqst.length = len; -+ rqst.payload = buf; -+ -+ buf[0] = rprt_id; -+ -+ return ssam_retry(ssam_request_sync, shid->ctrl, &rqst, NULL); -+} -+ -+static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.instance_id = shid->uid.instance; -+ rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(rprt_id); -+ rqst.payload = &rprt_id; -+ -+ rsp.capacity = len; -+ rsp.length = 0; -+ rsp.pointer = buf; -+ -+ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id)); -+} -+ -+static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif); -+ -+ if (event->command_id != 0x00) -+ return 0; -+ -+ hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+ -+/* -- Transport driver. ----------------------------------------------------- */ -+ -+static int shid_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_set_raw_report(shid, rprt_id, false, buf, len); -+ return status >= 0 ? len : status; -+} -+ -+static int shid_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_get_raw_report(shid, rprt_id, buf, len); -+ return status >= 0 ? len : status; -+} -+ -+static int shid_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_set_raw_report(shid, rprt_id, true, buf, len); -+ return status >= 0 ? len : status; -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int surface_hid_probe(struct ssam_device *sdev) -+{ -+ struct surface_hid_device *shid; -+ -+ shid = devm_kzalloc(&sdev->dev, sizeof(*shid), GFP_KERNEL); -+ if (!shid) -+ return -ENOMEM; -+ -+ shid->dev = &sdev->dev; -+ shid->ctrl = sdev->ctrl; -+ shid->uid = sdev->uid; -+ -+ shid->notif.base.priority = 1; -+ shid->notif.base.fn = ssam_hid_event_fn; -+ shid->notif.event.reg = SSAM_EVENT_REGISTRY_REG; -+ shid->notif.event.id.target_category = sdev->uid.category; -+ shid->notif.event.id.instance = sdev->uid.instance; -+ shid->notif.event.mask = SSAM_EVENT_MASK_STRICT; -+ shid->notif.event.flags = 0; -+ -+ shid->ops.get_descriptor = ssam_hid_get_descriptor; -+ shid->ops.output_report = shid_output_report; -+ shid->ops.get_feature_report = shid_get_feature_report; -+ shid->ops.set_feature_report = shid_set_feature_report; -+ -+ ssam_device_set_drvdata(sdev, shid); -+ return surface_hid_device_add(shid); -+} -+ -+static void surface_hid_remove(struct ssam_device *sdev) -+{ -+ surface_hid_device_destroy(ssam_device_get_drvdata(sdev)); -+} -+ -+static const struct ssam_device_id surface_hid_match[] = { -+ { SSAM_SDEV(HID, 0x02, SSAM_ANY_IID, 0x00) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_hid_match); -+ -+static struct ssam_device_driver surface_hid_driver = { -+ .probe = surface_hid_probe, -+ .remove = surface_hid_remove, -+ .match_table = surface_hid_match, -+ .driver = { -+ .name = "surface_hid", -+ .pm = &surface_hid_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_hid_driver); -+ -+MODULE_AUTHOR("Blaž Hrastnik "); -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("HID transport driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c -new file mode 100644 -index 000000000000..5571e74abe91 ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_hid_core.c -@@ -0,0 +1,272 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Common/core components for the Surface System Aggregator Module (SSAM) HID -+ * transport driver. Provides support for integrated HID devices on Microsoft -+ * Surface models. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "surface_hid_core.h" -+ -+ -+/* -- Device descriptor access. --------------------------------------------- */ -+ -+static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) -+{ -+ int status; -+ -+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, -+ (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); -+ if (status) -+ return status; -+ -+ if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) { -+ dev_err(shid->dev, "unexpected HID descriptor length: got %u, expected %zu\n", -+ shid->hid_desc.desc_len, sizeof(shid->hid_desc)); -+ return -EPROTO; -+ } -+ -+ if (shid->hid_desc.desc_type != HID_DT_HID) { -+ dev_err(shid->dev, "unexpected HID descriptor type: got %#04x, expected %#04x\n", -+ shid->hid_desc.desc_type, HID_DT_HID); -+ return -EPROTO; -+ } -+ -+ if (shid->hid_desc.num_descriptors != 1) { -+ dev_err(shid->dev, "unexpected number of descriptors: got %u, expected 1\n", -+ shid->hid_desc.num_descriptors); -+ return -EPROTO; -+ } -+ -+ if (shid->hid_desc.report_desc_type != HID_DT_REPORT) { -+ dev_err(shid->dev, "unexpected report descriptor type: got %#04x, expected %#04x\n", -+ shid->hid_desc.report_desc_type, HID_DT_REPORT); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static int surface_hid_load_device_attributes(struct surface_hid_device *shid) -+{ -+ int status; -+ -+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, -+ (u8 *)&shid->attrs, sizeof(shid->attrs)); -+ if (status) -+ return status; -+ -+ if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) { -+ dev_err(shid->dev, "unexpected attribute length: got %u, expected %zu\n", -+ get_unaligned_le32(&shid->attrs.length), sizeof(shid->attrs)); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+ -+/* -- Transport driver (common). -------------------------------------------- */ -+ -+static int surface_hid_start(struct hid_device *hid) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ -+ return ssam_notifier_register(shid->ctrl, &shid->notif); -+} -+ -+static void surface_hid_stop(struct hid_device *hid) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ -+ /* Note: This call will log errors for us, so ignore them here. */ -+ ssam_notifier_unregister(shid->ctrl, &shid->notif); -+} -+ -+static int surface_hid_open(struct hid_device *hid) -+{ -+ return 0; -+} -+ -+static void surface_hid_close(struct hid_device *hid) -+{ -+} -+ -+static int surface_hid_parse(struct hid_device *hid) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len); -+ u8 *buf; -+ int status; -+ -+ buf = kzalloc(len, GFP_KERNEL); -+ if (!buf) -+ return -ENOMEM; -+ -+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len); -+ if (!status) -+ status = hid_parse_report(hid, buf, len); -+ -+ kfree(buf); -+ return status; -+} -+ -+static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportnum, u8 *buf, -+ size_t len, unsigned char rtype, int reqtype) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ -+ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) -+ return shid->ops.output_report(shid, reportnum, buf, len); -+ -+ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) -+ return shid->ops.get_feature_report(shid, reportnum, buf, len); -+ -+ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) -+ return shid->ops.set_feature_report(shid, reportnum, buf, len); -+ -+ return -EIO; -+} -+ -+static struct hid_ll_driver surface_hid_ll_driver = { -+ .start = surface_hid_start, -+ .stop = surface_hid_stop, -+ .open = surface_hid_open, -+ .close = surface_hid_close, -+ .parse = surface_hid_parse, -+ .raw_request = surface_hid_raw_request, -+}; -+ -+ -+/* -- Common device setup. -------------------------------------------------- */ -+ -+int surface_hid_device_add(struct surface_hid_device *shid) -+{ -+ int status; -+ -+ status = surface_hid_load_hid_descriptor(shid); -+ if (status) -+ return status; -+ -+ status = surface_hid_load_device_attributes(shid); -+ if (status) -+ return status; -+ -+ shid->hid = hid_allocate_device(); -+ if (IS_ERR(shid->hid)) -+ return PTR_ERR(shid->hid); -+ -+ shid->hid->dev.parent = shid->dev; -+ shid->hid->bus = BUS_HOST; -+ shid->hid->vendor = get_unaligned_le16(&shid->attrs.vendor); -+ shid->hid->product = get_unaligned_le16(&shid->attrs.product); -+ shid->hid->version = get_unaligned_le16(&shid->hid_desc.hid_version); -+ shid->hid->country = shid->hid_desc.country_code; -+ -+ snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X", -+ shid->hid->vendor, shid->hid->product); -+ -+ strscpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys)); -+ -+ shid->hid->driver_data = shid; -+ shid->hid->ll_driver = &surface_hid_ll_driver; -+ -+ status = hid_add_device(shid->hid); -+ if (status) -+ hid_destroy_device(shid->hid); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(surface_hid_device_add); -+ -+void surface_hid_device_destroy(struct surface_hid_device *shid) -+{ -+ hid_destroy_device(shid->hid); -+} -+EXPORT_SYMBOL_GPL(surface_hid_device_destroy); -+ -+ -+/* -- PM ops. --------------------------------------------------------------- */ -+ -+#ifdef CONFIG_PM_SLEEP -+ -+static int surface_hid_suspend(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_SUSPEND); -+ -+ return 0; -+} -+ -+static int surface_hid_resume(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->resume) -+ return d->hid->driver->resume(d->hid); -+ -+ return 0; -+} -+ -+static int surface_hid_freeze(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_FREEZE); -+ -+ return 0; -+} -+ -+static int surface_hid_poweroff(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE); -+ -+ return 0; -+} -+ -+static int surface_hid_restore(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->reset_resume) -+ return d->hid->driver->reset_resume(d->hid); -+ -+ return 0; -+} -+ -+const struct dev_pm_ops surface_hid_pm_ops = { -+ .freeze = surface_hid_freeze, -+ .thaw = surface_hid_resume, -+ .suspend = surface_hid_suspend, -+ .resume = surface_hid_resume, -+ .poweroff = surface_hid_poweroff, -+ .restore = surface_hid_restore, -+}; -+EXPORT_SYMBOL_GPL(surface_hid_pm_ops); -+ -+#else /* CONFIG_PM_SLEEP */ -+ -+const struct dev_pm_ops surface_hid_pm_ops = { }; -+EXPORT_SYMBOL_GPL(surface_hid_pm_ops); -+ -+#endif /* CONFIG_PM_SLEEP */ -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/hid/surface-hid/surface_hid_core.h b/drivers/hid/surface-hid/surface_hid_core.h -new file mode 100644 -index 000000000000..4b1a7b57e035 ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_hid_core.h -@@ -0,0 +1,77 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Common/core components for the Surface System Aggregator Module (SSAM) HID -+ * transport driver. Provides support for integrated HID devices on Microsoft -+ * Surface models. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef SURFACE_HID_CORE_H -+#define SURFACE_HID_CORE_H -+ -+#include -+#include -+#include -+ -+#include -+#include -+ -+enum surface_hid_descriptor_entry { -+ SURFACE_HID_DESC_HID = 0, -+ SURFACE_HID_DESC_REPORT = 1, -+ SURFACE_HID_DESC_ATTRS = 2, -+}; -+ -+struct surface_hid_descriptor { -+ __u8 desc_len; /* = 9 */ -+ __u8 desc_type; /* = HID_DT_HID */ -+ __le16 hid_version; -+ __u8 country_code; -+ __u8 num_descriptors; /* = 1 */ -+ -+ __u8 report_desc_type; /* = HID_DT_REPORT */ -+ __le16 report_desc_len; -+} __packed; -+ -+static_assert(sizeof(struct surface_hid_descriptor) == 9); -+ -+struct surface_hid_attributes { -+ __le32 length; -+ __le16 vendor; -+ __le16 product; -+ __le16 version; -+ __u8 _unknown[22]; -+} __packed; -+ -+static_assert(sizeof(struct surface_hid_attributes) == 32); -+ -+struct surface_hid_device; -+ -+struct surface_hid_device_ops { -+ int (*get_descriptor)(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len); -+ int (*output_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); -+ int (*get_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); -+ int (*set_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); -+}; -+ -+struct surface_hid_device { -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ struct ssam_device_uid uid; -+ -+ struct surface_hid_descriptor hid_desc; -+ struct surface_hid_attributes attrs; -+ -+ struct ssam_event_notifier notif; -+ struct hid_device *hid; -+ -+ struct surface_hid_device_ops ops; -+}; -+ -+int surface_hid_device_add(struct surface_hid_device *shid); -+void surface_hid_device_destroy(struct surface_hid_device *shid); -+ -+extern const struct dev_pm_ops surface_hid_pm_ops; -+ -+#endif /* SURFACE_HID_CORE_H */ -diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c -new file mode 100644 -index 000000000000..0635341bc517 ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_kbd.c -@@ -0,0 +1,300 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) HID transport driver for the legacy -+ * keyboard interface (KBD/TC=0x08 subsystem). Provides support for the -+ * integrated HID keyboard on Surface Laptops 1 and 2. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "surface_hid_core.h" -+ -+ -+/* -- SAM interface (KBD). -------------------------------------------------- */ -+ -+#define KBD_FEATURE_REPORT_SIZE 7 /* 6 + report ID */ -+ -+enum surface_kbd_cid { -+ SURFACE_KBD_CID_GET_DESCRIPTOR = 0x00, -+ SURFACE_KBD_CID_SET_CAPSLOCK_LED = 0x01, -+ SURFACE_KBD_CID_EVT_INPUT_GENERIC = 0x03, -+ SURFACE_KBD_CID_EVT_INPUT_HOTKEYS = 0x04, -+ SURFACE_KBD_CID_GET_FEATURE_REPORT = 0x0b, -+}; -+ -+static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ int status; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(entry); -+ rqst.payload = &entry; -+ -+ rsp.capacity = len; -+ rsp.length = 0; -+ rsp.pointer = buf; -+ -+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry)); -+ if (status) -+ return status; -+ -+ if (rsp.length != len) { -+ dev_err(shid->dev, "invalid descriptor length: got %zu, expected, %zu\n", -+ rsp.length, len); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value) -+{ -+ struct ssam_request rqst; -+ u8 value_u8 = value; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = 0; -+ rqst.length = sizeof(value_u8); -+ rqst.payload = &value_u8; -+ -+ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8)); -+} -+ -+static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ u8 payload = 0; -+ int status; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(payload); -+ rqst.payload = &payload; -+ -+ rsp.capacity = len; -+ rsp.length = 0; -+ rsp.pointer = buf; -+ -+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload)); -+ if (status) -+ return status; -+ -+ if (rsp.length != len) { -+ dev_err(shid->dev, "invalid feature report length: got %zu, expected, %zu\n", -+ rsp.length, len); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static bool ssam_kbd_is_input_event(const struct ssam_event *event) -+{ -+ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC) -+ return true; -+ -+ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS) -+ return true; -+ -+ return false; -+} -+ -+static u32 ssam_kbd_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif); -+ -+ /* -+ * Check against device UID manually, as registry and device target -+ * category doesn't line up. -+ */ -+ -+ if (shid->uid.category != event->target_category) -+ return 0; -+ -+ if (shid->uid.target != event->target_id) -+ return 0; -+ -+ if (shid->uid.instance != event->instance_id) -+ return 0; -+ -+ if (!ssam_kbd_is_input_event(event)) -+ return 0; -+ -+ hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+ -+/* -- Transport driver (KBD). ----------------------------------------------- */ -+ -+static int skbd_get_caps_led_value(struct hid_device *hid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ struct hid_field *field; -+ unsigned int offset, size; -+ int i; -+ -+ /* Get LED field. */ -+ field = hidinput_get_led_field(hid); -+ if (!field) -+ return -ENOENT; -+ -+ /* Check if we got the correct report. */ -+ if (len != hid_report_len(field->report)) -+ return -ENOENT; -+ -+ if (rprt_id != field->report->id) -+ return -ENOENT; -+ -+ /* Get caps lock LED index. */ -+ for (i = 0; i < field->report_count; i++) -+ if ((field->usage[i].hid & 0xffff) == 0x02) -+ break; -+ -+ if (i == field->report_count) -+ return -ENOENT; -+ -+ /* Extract value. */ -+ size = field->report_size; -+ offset = field->report_offset + i * size; -+ return !!hid_field_extract(hid, buf + 1, size, offset); -+} -+ -+static int skbd_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int caps_led; -+ int status; -+ -+ caps_led = skbd_get_caps_led_value(shid->hid, rprt_id, buf, len); -+ if (caps_led < 0) -+ return -EIO; /* Only caps LED output reports are supported. */ -+ -+ status = ssam_kbd_set_caps_led(shid, caps_led); -+ if (status < 0) -+ return status; -+ -+ return len; -+} -+ -+static int skbd_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ u8 report[KBD_FEATURE_REPORT_SIZE]; -+ int status; -+ -+ /* -+ * The keyboard only has a single hard-coded read-only feature report -+ * of size KBD_FEATURE_REPORT_SIZE. Try to load it and compare its -+ * report ID against the requested one. -+ */ -+ -+ if (len < ARRAY_SIZE(report)) -+ return -ENOSPC; -+ -+ status = ssam_kbd_get_feature_report(shid, report, ARRAY_SIZE(report)); -+ if (status < 0) -+ return status; -+ -+ if (rprt_id != report[0]) -+ return -ENOENT; -+ -+ memcpy(buf, report, ARRAY_SIZE(report)); -+ return len; -+} -+ -+static int skbd_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ /* Not supported. See skbd_get_feature_report() for details. */ -+ return -EIO; -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int surface_kbd_probe(struct platform_device *pdev) -+{ -+ struct ssam_controller *ctrl; -+ struct surface_hid_device *shid; -+ -+ /* Add device link to EC. */ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL); -+ if (!shid) -+ return -ENOMEM; -+ -+ shid->dev = &pdev->dev; -+ shid->ctrl = ctrl; -+ -+ shid->uid.domain = SSAM_DOMAIN_SERIALHUB; -+ shid->uid.category = SSAM_SSH_TC_KBD; -+ shid->uid.target = 2; -+ shid->uid.instance = 0; -+ shid->uid.function = 0; -+ -+ shid->notif.base.priority = 1; -+ shid->notif.base.fn = ssam_kbd_event_fn; -+ shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ shid->notif.event.id.target_category = shid->uid.category; -+ shid->notif.event.id.instance = shid->uid.instance; -+ shid->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ shid->notif.event.flags = 0; -+ -+ shid->ops.get_descriptor = ssam_kbd_get_descriptor; -+ shid->ops.output_report = skbd_output_report; -+ shid->ops.get_feature_report = skbd_get_feature_report; -+ shid->ops.set_feature_report = skbd_set_feature_report; -+ -+ platform_set_drvdata(pdev, shid); -+ return surface_hid_device_add(shid); -+} -+ -+static int surface_kbd_remove(struct platform_device *pdev) -+{ -+ surface_hid_device_destroy(platform_get_drvdata(pdev)); -+ return 0; -+} -+ -+static const struct acpi_device_id surface_kbd_match[] = { -+ { "MSHW0096" }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_kbd_match); -+ -+static struct platform_driver surface_kbd_driver = { -+ .probe = surface_kbd_probe, -+ .remove = surface_kbd_remove, -+ .driver = { -+ .name = "surface_keyboard", -+ .acpi_match_table = surface_kbd_match, -+ .pm = &surface_hid_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_kbd_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 6a53f63bde62..d44d3fb9ca72 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -870,6 +870,108 @@ config INTEL_VBTN - To compile this driver as a module, choose M here: the module will - be called intel_vbtn. - -+source "drivers/platform/x86/surface_aggregator/Kconfig" -+ -+config SURFACE_AGGREGATOR_REGISTRY -+ tristate "Surface System Aggregator Module Device Registry" -+ depends on SURFACE_AGGREGATOR -+ depends on SURFACE_AGGREGATOR_BUS -+ help -+ Device-registry and device-hubs for Surface System Aggregator Module -+ (SSAM) devices. -+ -+ Provides a module and driver which act as a device-registry for SSAM -+ client devices that cannot be detected automatically, e.g. via ACPI. -+ Such devices are instead provided via this registry and attached via -+ device hubs, also provided in this module. -+ -+ Devices provided via this registry are: -+ - Platform profile (performance-/cooling-mode) device (5th- and later -+ generations). -+ - Battery/AC devices (7th-generation). -+ - HID input devices (7th-generation). -+ -+ Select M (recommended) or Y here if you want support for the above -+ mentioned devices on the corresponding Surface models. Without this -+ module, the respective devices will not be instantiated and thus any -+ functionality provided by them will be missing, even when drivers for -+ these devices are present. In other words, this module only provides -+ the respective client devices. Drivers for these devices still need to -+ be selected via the other options. -+ -+config SURFACE_AGGREGATOR_CDEV -+ tristate "Surface System Aggregator Module User-Space Interface" -+ depends on SURFACE_AGGREGATOR -+ help -+ Provides a misc-device interface to the Surface System Aggregator -+ Module (SSAM) controller. -+ -+ This option provides a module (called surface_aggregator_cdev), that, -+ when loaded, will add a client device (and its respective driver) to -+ the SSAM controller. Said client device manages a misc-device -+ interface (/dev/surface/aggregator), which can be used by user-space -+ tools to directly communicate with the SSAM EC by sending requests and -+ receiving the corresponding responses. -+ -+ The provided interface is intended for debugging and development only, -+ and should not be used otherwise. -+ -+config SURFACE_ACPI_NOTIFY -+ tristate "Surface ACPI Notify Driver" -+ depends on SURFACE_AGGREGATOR -+ help -+ Surface ACPI Notify (SAN) driver for Microsoft Surface devices. -+ -+ This driver provides support for the ACPI interface (called SAN) of -+ the Surface System Aggregator Module (SSAM) EC. This interface is used -+ on 5th- and 6th-generation Microsoft Surface devices (including -+ Surface Pro 5 and 6, Surface Book 2, Surface Laptops 1 and 2, and in -+ reduced functionality on the Surface Laptop 3) to execute SSAM -+ requests directly from ACPI code, as well as receive SSAM events and -+ turn them into ACPI notifications. It essentially acts as a -+ translation layer between the SSAM controller and ACPI. -+ -+ Specifically, this driver may be needed for battery status reporting, -+ thermal sensor access, and real-time clock information, depending on -+ the Surface device in question. -+ -+config SURFACE_PERFMODE -+ tristate "Surface Performance-Mode Driver" -+ depends on SURFACE_AGGREGATOR_BUS -+ depends on SYSFS -+ help -+ Driver for the performance-/cooling-mode interface of Microsoft -+ Surface devices. -+ -+ Microsoft Surface devices using the Surface System Aggregator Module -+ (SSAM) can be switched between different performance modes. This, -+ depending on the device, can influence their cooling behavior and may -+ influence power limits, allowing users to choose between performance -+ and higher power-draw, or lower power-draw and more silent operation. -+ -+ This driver provides a user-space interface (via sysfs) for -+ controlling said mode via the corresponding client device. -+ -+config SURFACE_DTX -+ tristate "Surface DTX (Detachment System) Driver" -+ depends on SURFACE_AGGREGATOR -+ depends on INPUT -+ help -+ Driver for the Surface Book clipboard detachment system (DTX). -+ -+ On the Surface Book series devices, the display part containing the -+ CPU (called the clipboard) can be detached from the base (containing a -+ battery, the keyboard, and, optionally, a discrete GPU) by (if -+ necessary) unlocking and opening the latch connecting both parts. -+ -+ This driver provides a user-space interface that can influence the -+ behavior of this process, which includes the option to abort it in -+ case the base is still in use or speed it up in case it is not. -+ -+ Note that this module can be built without support for the Surface -+ Aggregator Bus (i.e. CONFIG_SURFACE_AGGREGATOR_BUS=n). In that case, -+ some devices, specifically the Surface Book 3, will not be supported. -+ - config SURFACE3_WMI - tristate "Surface 3 WMI Driver" - depends on ACPI_WMI -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 562d83940e7b..2e0a2896c78d 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -82,6 +82,13 @@ obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o - obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o - - # Microsoft -+obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator/ -+obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o -+obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o -+obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o -+obj-$(CONFIG_SURFACE_PERFMODE) += surface_perfmode.o -+obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o -+ - obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o - obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o - obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o -diff --git a/drivers/platform/x86/surface_acpi_notify.c b/drivers/platform/x86/surface_acpi_notify.c -new file mode 100644 -index 000000000000..ef9c1f8e8336 ---- /dev/null -+++ b/drivers/platform/x86/surface_acpi_notify.c -@@ -0,0 +1,886 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Driver for the Surface ACPI Notify (SAN) interface/shim. -+ * -+ * Translates communication from ACPI to Surface System Aggregator Module -+ * (SSAM/SAM) requests and back, specifically SAM-over-SSH. Translates SSAM -+ * events back to ACPI notifications. Allows handling of discrete GPU -+ * notifications sent from ACPI via the SAN interface by providing them to any -+ * registered external driver. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+struct san_data { -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ -+ struct acpi_connection_info info; -+ -+ struct ssam_event_notifier nf_bat; -+ struct ssam_event_notifier nf_tmp; -+}; -+ -+#define to_san_data(ptr, member) \ -+ container_of(ptr, struct san_data, member) -+ -+ -+/* -- dGPU notifier interface. ---------------------------------------------- */ -+ -+struct san_rqsg_if { -+ struct rw_semaphore lock; -+ struct device *dev; -+ struct blocking_notifier_head nh; -+}; -+ -+static struct san_rqsg_if san_rqsg_if = { -+ .lock = __RWSEM_INITIALIZER(san_rqsg_if.lock), -+ .dev = NULL, -+ .nh = BLOCKING_NOTIFIER_INIT(san_rqsg_if.nh), -+}; -+ -+static int san_set_rqsg_interface_device(struct device *dev) -+{ -+ int status = 0; -+ -+ down_write(&san_rqsg_if.lock); -+ if (!san_rqsg_if.dev && dev) -+ san_rqsg_if.dev = dev; -+ else -+ status = -EBUSY; -+ up_write(&san_rqsg_if.lock); -+ -+ return status; -+} -+ -+/** -+ * san_client_link() - Link client as consumer to SAN device. -+ * @client: The client to link. -+ * -+ * Sets up a device link between the provided client device as consumer and -+ * the SAN device as provider. This function can be used to ensure that the -+ * SAN interface has been set up and will be set up for as long as the driver -+ * of the client device is bound. This guarantees that, during that time, all -+ * dGPU events will be received by any registered notifier. -+ * -+ * The link will be automatically removed once the client device's driver is -+ * unbound. -+ * -+ * Return: Returns zero on success, %-ENXIO if the SAN interface has not been -+ * set up yet, and %-ENOMEM if device link creation failed. -+ */ -+int san_client_link(struct device *client) -+{ -+ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; -+ struct device_link *link; -+ -+ down_read(&san_rqsg_if.lock); -+ -+ if (!san_rqsg_if.dev) { -+ up_read(&san_rqsg_if.lock); -+ return -ENXIO; -+ } -+ -+ link = device_link_add(client, san_rqsg_if.dev, flags); -+ if (!link) { -+ up_read(&san_rqsg_if.lock); -+ return -ENOMEM; -+ } -+ -+ if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) { -+ up_read(&san_rqsg_if.lock); -+ return -ENXIO; -+ } -+ -+ up_read(&san_rqsg_if.lock); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(san_client_link); -+ -+/** -+ * san_dgpu_notifier_register() - Register a SAN dGPU notifier. -+ * @nb: The notifier-block to register. -+ * -+ * Registers a SAN dGPU notifier, receiving any new SAN dGPU events sent from -+ * ACPI. The registered notifier will be called with &struct san_dgpu_event -+ * as notifier data and the command ID of that event as notifier action. -+ */ -+int san_dgpu_notifier_register(struct notifier_block *nb) -+{ -+ return blocking_notifier_chain_register(&san_rqsg_if.nh, nb); -+} -+EXPORT_SYMBOL_GPL(san_dgpu_notifier_register); -+ -+/** -+ * san_dgpu_notifier_unregister() - Unregister a SAN dGPU notifier. -+ * @nb: The notifier-block to unregister. -+ */ -+int san_dgpu_notifier_unregister(struct notifier_block *nb) -+{ -+ return blocking_notifier_chain_unregister(&san_rqsg_if.nh, nb); -+} -+EXPORT_SYMBOL_GPL(san_dgpu_notifier_unregister); -+ -+static int san_dgpu_notifier_call(struct san_dgpu_event *evt) -+{ -+ int ret; -+ -+ ret = blocking_notifier_call_chain(&san_rqsg_if.nh, evt->command, evt); -+ return notifier_to_errno(ret); -+} -+ -+ -+/* -- ACPI _DSM event relay. ------------------------------------------------ */ -+ -+#define SAN_DSM_REVISION 0 -+ -+/* 93b666c5-70c6-469f-a215-3d487c91ab3c */ -+static const guid_t SAN_DSM_UUID = -+ GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d, -+ 0x48, 0x7c, 0x91, 0xab, 0x3c); -+ -+enum san_dsm_event_fn { -+ SAN_DSM_EVENT_FN_BAT1_STAT = 0x03, -+ SAN_DSM_EVENT_FN_BAT1_INFO = 0x04, -+ SAN_DSM_EVENT_FN_ADP1_STAT = 0x05, -+ SAN_DSM_EVENT_FN_ADP1_INFO = 0x06, -+ SAN_DSM_EVENT_FN_BAT2_STAT = 0x07, -+ SAN_DSM_EVENT_FN_BAT2_INFO = 0x08, -+ SAN_DSM_EVENT_FN_THERMAL = 0x09, -+ SAN_DSM_EVENT_FN_DPTF = 0x0a, -+}; -+ -+enum sam_event_cid_bat { -+ SAM_EVENT_CID_BAT_BIX = 0x15, -+ SAM_EVENT_CID_BAT_BST = 0x16, -+ SAM_EVENT_CID_BAT_ADP = 0x17, -+ SAM_EVENT_CID_BAT_PROT = 0x18, -+ SAM_EVENT_CID_BAT_DPTF = 0x4f, -+}; -+ -+enum sam_event_cid_tmp { -+ SAM_EVENT_CID_TMP_TRIP = 0x0b, -+}; -+ -+struct san_event_work { -+ struct delayed_work work; -+ struct device *dev; -+ struct ssam_event event; /* must be last */ -+}; -+ -+static int san_acpi_notify_event(struct device *dev, u64 func, -+ union acpi_object *param) -+{ -+ acpi_handle san = ACPI_HANDLE(dev); -+ union acpi_object *obj; -+ int status = 0; -+ -+ if (!acpi_check_dsm(san, &SAN_DSM_UUID, SAN_DSM_REVISION, BIT_ULL(func))) -+ return 0; -+ -+ dev_dbg(dev, "notify event %#04llx\n", func); -+ -+ obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION, -+ func, param, ACPI_TYPE_BUFFER); -+ if (!obj) -+ return -EFAULT; -+ -+ if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) { -+ dev_err(dev, "got unexpected result from _DSM\n"); -+ status = -EPROTO; -+ } -+ -+ ACPI_FREE(obj); -+ return status; -+} -+ -+static int san_evt_bat_adp(struct device *dev, const struct ssam_event *event) -+{ -+ int status; -+ -+ status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_ADP1_STAT, NULL); -+ if (status) -+ return status; -+ -+ /* -+ * Ensure that the battery states get updated correctly. When the -+ * battery is fully charged and an adapter is plugged in, it sometimes -+ * is not updated correctly, instead showing it as charging. -+ * Explicitly trigger battery updates to fix this. -+ */ -+ -+ status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT1_STAT, NULL); -+ if (status) -+ return status; -+ -+ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT2_STAT, NULL); -+} -+ -+static int san_evt_bat_bix(struct device *dev, const struct ssam_event *event) -+{ -+ enum san_dsm_event_fn fn; -+ -+ if (event->instance_id == 0x02) -+ fn = SAN_DSM_EVENT_FN_BAT2_INFO; -+ else -+ fn = SAN_DSM_EVENT_FN_BAT1_INFO; -+ -+ return san_acpi_notify_event(dev, fn, NULL); -+} -+ -+static int san_evt_bat_bst(struct device *dev, const struct ssam_event *event) -+{ -+ enum san_dsm_event_fn fn; -+ -+ if (event->instance_id == 0x02) -+ fn = SAN_DSM_EVENT_FN_BAT2_STAT; -+ else -+ fn = SAN_DSM_EVENT_FN_BAT1_STAT; -+ -+ return san_acpi_notify_event(dev, fn, NULL); -+} -+ -+static int san_evt_bat_dptf(struct device *dev, const struct ssam_event *event) -+{ -+ union acpi_object payload; -+ -+ /* -+ * The Surface ACPI expects a buffer and not a package. It specifically -+ * checks for ObjectType (Arg3) == 0x03. This will cause a warning in -+ * acpica/nsarguments.c, but that warning can be safely ignored. -+ */ -+ payload.type = ACPI_TYPE_BUFFER; -+ payload.buffer.length = event->length; -+ payload.buffer.pointer = (u8 *)&event->data[0]; -+ -+ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_DPTF, &payload); -+} -+ -+static unsigned long san_evt_bat_delay(u8 cid) -+{ -+ switch (cid) { -+ case SAM_EVENT_CID_BAT_ADP: -+ /* -+ * Wait for battery state to update before signaling adapter -+ * change. -+ */ -+ return msecs_to_jiffies(5000); -+ -+ case SAM_EVENT_CID_BAT_BST: -+ /* Ensure we do not miss anything important due to caching. */ -+ return msecs_to_jiffies(2000); -+ -+ default: -+ return 0; -+ } -+} -+ -+static bool san_evt_bat(const struct ssam_event *event, struct device *dev) -+{ -+ int status; -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_BAT_BIX: -+ status = san_evt_bat_bix(dev, event); -+ break; -+ -+ case SAM_EVENT_CID_BAT_BST: -+ status = san_evt_bat_bst(dev, event); -+ break; -+ -+ case SAM_EVENT_CID_BAT_ADP: -+ status = san_evt_bat_adp(dev, event); -+ break; -+ -+ case SAM_EVENT_CID_BAT_PROT: -+ /* -+ * TODO: Implement support for battery protection status change -+ * event. -+ */ -+ return true; -+ -+ case SAM_EVENT_CID_BAT_DPTF: -+ status = san_evt_bat_dptf(dev, event); -+ break; -+ -+ default: -+ return false; -+ } -+ -+ if (status) { -+ dev_err(dev, "error handling power event (cid = %#04x)\n", -+ event->command_id); -+ } -+ -+ return true; -+} -+ -+static void san_evt_bat_workfn(struct work_struct *work) -+{ -+ struct san_event_work *ev; -+ -+ ev = container_of(work, struct san_event_work, work.work); -+ san_evt_bat(&ev->event, ev->dev); -+ kfree(ev); -+} -+ -+static u32 san_evt_bat_nf(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+{ -+ struct san_data *d = to_san_data(nf, nf_bat); -+ struct san_event_work *work; -+ unsigned long delay = san_evt_bat_delay(event->command_id); -+ -+ if (delay == 0) -+ return san_evt_bat(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; -+ -+ work = kzalloc(sizeof(*work) + event->length, GFP_KERNEL); -+ if (!work) -+ return ssam_notifier_from_errno(-ENOMEM); -+ -+ INIT_DELAYED_WORK(&work->work, san_evt_bat_workfn); -+ work->dev = d->dev; -+ -+ memcpy(&work->event, event, sizeof(struct ssam_event) + event->length); -+ -+ schedule_delayed_work(&work->work, delay); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static int san_evt_tmp_trip(struct device *dev, const struct ssam_event *event) -+{ -+ union acpi_object param; -+ -+ /* -+ * The Surface ACPI expects an integer and not a package. This will -+ * cause a warning in acpica/nsarguments.c, but that warning can be -+ * safely ignored. -+ */ -+ param.type = ACPI_TYPE_INTEGER; -+ param.integer.value = event->instance_id; -+ -+ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_THERMAL, ¶m); -+} -+ -+static bool san_evt_tmp(const struct ssam_event *event, struct device *dev) -+{ -+ int status; -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_TMP_TRIP: -+ status = san_evt_tmp_trip(dev, event); -+ break; -+ -+ default: -+ return false; -+ } -+ -+ if (status) { -+ dev_err(dev, "error handling thermal event (cid = %#04x)\n", -+ event->command_id); -+ } -+ -+ return true; -+} -+ -+static u32 san_evt_tmp_nf(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+{ -+ struct san_data *d = to_san_data(nf, nf_tmp); -+ -+ return san_evt_tmp(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; -+} -+ -+ -+/* -- ACPI GSB OperationRegion handler -------------------------------------- */ -+ -+struct gsb_data_in { -+ u8 cv; -+} __packed; -+ -+struct gsb_data_rqsx { -+ u8 cv; /* Command value (san_gsb_request_cv). */ -+ u8 tc; /* Target category. */ -+ u8 tid; /* Target ID. */ -+ u8 iid; /* Instance ID. */ -+ u8 snc; /* Expect-response-flag. */ -+ u8 cid; /* Command ID. */ -+ u16 cdl; /* Payload length. */ -+ u8 pld[]; /* Payload. */ -+} __packed; -+ -+struct gsb_data_etwl { -+ u8 cv; /* Command value (should be 0x02). */ -+ u8 etw3; /* Unknown. */ -+ u8 etw4; /* Unknown. */ -+ u8 msg[]; /* Error message (ASCIIZ). */ -+} __packed; -+ -+struct gsb_data_out { -+ u8 status; /* _SSH communication status. */ -+ u8 len; /* _SSH payload length. */ -+ u8 pld[]; /* _SSH payload. */ -+} __packed; -+ -+union gsb_buffer_data { -+ struct gsb_data_in in; /* Common input. */ -+ struct gsb_data_rqsx rqsx; /* RQSX input. */ -+ struct gsb_data_etwl etwl; /* ETWL input. */ -+ struct gsb_data_out out; /* Output. */ -+}; -+ -+struct gsb_buffer { -+ u8 status; /* GSB AttribRawProcess status. */ -+ u8 len; /* GSB AttribRawProcess length. */ -+ union gsb_buffer_data data; -+} __packed; -+ -+#define SAN_GSB_MAX_RQSX_PAYLOAD (U8_MAX - 2 - sizeof(struct gsb_data_rqsx)) -+#define SAN_GSB_MAX_RESPONSE (U8_MAX - 2 - sizeof(struct gsb_data_out)) -+ -+#define SAN_GSB_COMMAND 0 -+ -+enum san_gsb_request_cv { -+ SAN_GSB_REQUEST_CV_RQST = 0x01, -+ SAN_GSB_REQUEST_CV_ETWL = 0x02, -+ SAN_GSB_REQUEST_CV_RQSG = 0x03, -+}; -+ -+#define SAN_REQUEST_NUM_TRIES 5 -+ -+static acpi_status san_etwl(struct san_data *d, struct gsb_buffer *b) -+{ -+ struct gsb_data_etwl *etwl = &b->data.etwl; -+ -+ if (b->len < sizeof(struct gsb_data_etwl)) { -+ dev_err(d->dev, "invalid ETWL package (len = %d)\n", b->len); -+ return AE_OK; -+ } -+ -+ dev_err(d->dev, "ETWL(%#04x, %#04x): %.*s\n", etwl->etw3, etwl->etw4, -+ (unsigned int)(b->len - sizeof(struct gsb_data_etwl)), -+ (char *)etwl->msg); -+ -+ /* Indicate success. */ -+ b->status = 0x00; -+ b->len = 0x00; -+ -+ return AE_OK; -+} -+ -+static -+struct gsb_data_rqsx *san_validate_rqsx(struct device *dev, const char *type, -+ struct gsb_buffer *b) -+{ -+ struct gsb_data_rqsx *rqsx = &b->data.rqsx; -+ -+ if (b->len < sizeof(struct gsb_data_rqsx)) { -+ dev_err(dev, "invalid %s package (len = %d)\n", type, b->len); -+ return NULL; -+ } -+ -+ if (get_unaligned(&rqsx->cdl) != b->len - sizeof(struct gsb_data_rqsx)) { -+ dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n", -+ type, b->len, get_unaligned(&rqsx->cdl)); -+ return NULL; -+ } -+ -+ if (get_unaligned(&rqsx->cdl) > SAN_GSB_MAX_RQSX_PAYLOAD) { -+ dev_err(dev, "payload for %s package too large (cdl = %d)\n", -+ type, get_unaligned(&rqsx->cdl)); -+ return NULL; -+ } -+ -+ return rqsx; -+} -+ -+static void gsb_rqsx_response_error(struct gsb_buffer *gsb, int status) -+{ -+ gsb->status = 0x00; -+ gsb->len = 0x02; -+ gsb->data.out.status = (u8)(-status); -+ gsb->data.out.len = 0x00; -+} -+ -+static void gsb_rqsx_response_success(struct gsb_buffer *gsb, u8 *ptr, size_t len) -+{ -+ gsb->status = 0x00; -+ gsb->len = len + 2; -+ gsb->data.out.status = 0x00; -+ gsb->data.out.len = len; -+ -+ if (len) -+ memcpy(&gsb->data.out.pld[0], ptr, len); -+} -+ -+static acpi_status san_rqst_fixup_suspended(struct san_data *d, -+ struct ssam_request *rqst, -+ struct gsb_buffer *gsb) -+{ -+ if (rqst->target_category == SSAM_SSH_TC_BAS && rqst->command_id == 0x0D) { -+ u8 base_state = 1; -+ -+ /* Base state quirk: -+ * The base state may be queried from ACPI when the EC is still -+ * suspended. In this case it will return '-EPERM'. This query -+ * will only be triggered from the ACPI lid GPE interrupt, thus -+ * we are either in laptop or studio mode (base status 0x01 or -+ * 0x02). Furthermore, we will only get here if the device (and -+ * EC) have been suspended. -+ * -+ * We now assume that the device is in laptop mode (0x01). This -+ * has the drawback that it will wake the device when unfolding -+ * it in studio mode, but it also allows us to avoid actively -+ * waiting for the EC to wake up, which may incur a notable -+ * delay. -+ */ -+ -+ dev_dbg(d->dev, "rqst: fixup: base-state quirk\n"); -+ -+ gsb_rqsx_response_success(gsb, &base_state, sizeof(base_state)); -+ return AE_OK; -+ } -+ -+ gsb_rqsx_response_error(gsb, -ENXIO); -+ return AE_OK; -+} -+ -+static acpi_status san_rqst(struct san_data *d, struct gsb_buffer *buffer) -+{ -+ u8 rspbuf[SAN_GSB_MAX_RESPONSE]; -+ struct gsb_data_rqsx *gsb_rqst; -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ int status = 0; -+ -+ gsb_rqst = san_validate_rqsx(d->dev, "RQST", buffer); -+ if (!gsb_rqst) -+ return AE_OK; -+ -+ rqst.target_category = gsb_rqst->tc; -+ rqst.target_id = gsb_rqst->tid; -+ rqst.command_id = gsb_rqst->cid; -+ rqst.instance_id = gsb_rqst->iid; -+ rqst.flags = gsb_rqst->snc ? SSAM_REQUEST_HAS_RESPONSE : 0; -+ rqst.length = get_unaligned(&gsb_rqst->cdl); -+ rqst.payload = &gsb_rqst->pld[0]; -+ -+ rsp.capacity = ARRAY_SIZE(rspbuf); -+ rsp.length = 0; -+ rsp.pointer = &rspbuf[0]; -+ -+ /* Handle suspended device. */ -+ if (d->dev->power.is_suspended) { -+ dev_warn(d->dev, "rqst: device is suspended, not executing\n"); -+ return san_rqst_fixup_suspended(d, &rqst, buffer); -+ } -+ -+ status = __ssam_retry(ssam_request_sync_onstack, SAN_REQUEST_NUM_TRIES, -+ d->ctrl, &rqst, &rsp, SAN_GSB_MAX_RQSX_PAYLOAD); -+ -+ if (!status) { -+ gsb_rqsx_response_success(buffer, rsp.pointer, rsp.length); -+ } else { -+ dev_err(d->dev, "rqst: failed with error %d\n", status); -+ gsb_rqsx_response_error(buffer, status); -+ } -+ -+ return AE_OK; -+} -+ -+static acpi_status san_rqsg(struct san_data *d, struct gsb_buffer *buffer) -+{ -+ struct gsb_data_rqsx *gsb_rqsg; -+ struct san_dgpu_event evt; -+ int status; -+ -+ gsb_rqsg = san_validate_rqsx(d->dev, "RQSG", buffer); -+ if (!gsb_rqsg) -+ return AE_OK; -+ -+ evt.category = gsb_rqsg->tc; -+ evt.target = gsb_rqsg->tid; -+ evt.command = gsb_rqsg->cid; -+ evt.instance = gsb_rqsg->iid; -+ evt.length = get_unaligned(&gsb_rqsg->cdl); -+ evt.payload = &gsb_rqsg->pld[0]; -+ -+ status = san_dgpu_notifier_call(&evt); -+ if (!status) { -+ gsb_rqsx_response_success(buffer, NULL, 0); -+ } else { -+ dev_err(d->dev, "rqsg: failed with error %d\n", status); -+ gsb_rqsx_response_error(buffer, status); -+ } -+ -+ return AE_OK; -+} -+ -+static acpi_status san_opreg_handler(u32 function, acpi_physical_address command, -+ u32 bits, u64 *value64, void *opreg_context, -+ void *region_context) -+{ -+ struct san_data *d = to_san_data(opreg_context, info); -+ struct gsb_buffer *buffer = (struct gsb_buffer *)value64; -+ int accessor_type = (function & 0xFFFF0000) >> 16; -+ -+ if (command != SAN_GSB_COMMAND) { -+ dev_warn(d->dev, "unsupported command: %#04llx\n", command); -+ return AE_OK; -+ } -+ -+ if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { -+ dev_err(d->dev, "invalid access type: %#04x\n", accessor_type); -+ return AE_OK; -+ } -+ -+ /* Buffer must have at least contain the command-value. */ -+ if (buffer->len == 0) { -+ dev_err(d->dev, "request-package too small\n"); -+ return AE_OK; -+ } -+ -+ switch (buffer->data.in.cv) { -+ case SAN_GSB_REQUEST_CV_RQST: -+ return san_rqst(d, buffer); -+ -+ case SAN_GSB_REQUEST_CV_ETWL: -+ return san_etwl(d, buffer); -+ -+ case SAN_GSB_REQUEST_CV_RQSG: -+ return san_rqsg(d, buffer); -+ -+ default: -+ dev_warn(d->dev, "unsupported SAN0 request (cv: %#04x)\n", -+ buffer->data.in.cv); -+ return AE_OK; -+ } -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int san_events_register(struct platform_device *pdev) -+{ -+ struct san_data *d = platform_get_drvdata(pdev); -+ int status; -+ -+ d->nf_bat.base.priority = 1; -+ d->nf_bat.base.fn = san_evt_bat_nf; -+ d->nf_bat.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ d->nf_bat.event.id.target_category = SSAM_SSH_TC_BAT; -+ d->nf_bat.event.id.instance = 0; -+ d->nf_bat.event.mask = SSAM_EVENT_MASK_TARGET; -+ d->nf_bat.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ d->nf_tmp.base.priority = 1; -+ d->nf_tmp.base.fn = san_evt_tmp_nf; -+ d->nf_tmp.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ d->nf_tmp.event.id.target_category = SSAM_SSH_TC_TMP; -+ d->nf_tmp.event.id.instance = 0; -+ d->nf_tmp.event.mask = SSAM_EVENT_MASK_TARGET; -+ d->nf_tmp.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ status = ssam_notifier_register(d->ctrl, &d->nf_bat); -+ if (status) -+ return status; -+ -+ status = ssam_notifier_register(d->ctrl, &d->nf_tmp); -+ if (status) -+ ssam_notifier_unregister(d->ctrl, &d->nf_bat); -+ -+ return status; -+} -+ -+static void san_events_unregister(struct platform_device *pdev) -+{ -+ struct san_data *d = platform_get_drvdata(pdev); -+ -+ ssam_notifier_unregister(d->ctrl, &d->nf_bat); -+ ssam_notifier_unregister(d->ctrl, &d->nf_tmp); -+} -+ -+#define san_consumer_printk(level, dev, handle, fmt, ...) \ -+do { \ -+ char *path = ""; \ -+ struct acpi_buffer buffer = { \ -+ .length = ACPI_ALLOCATE_BUFFER, \ -+ .pointer = NULL, \ -+ }; \ -+ \ -+ if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer))) \ -+ path = buffer.pointer; \ -+ \ -+ dev_##level(dev, "[%s]: " fmt, path, ##__VA_ARGS__); \ -+ kfree(buffer.pointer); \ -+} while (0) -+ -+#define san_consumer_dbg(dev, handle, fmt, ...) \ -+ san_consumer_printk(dbg, dev, handle, fmt, ##__VA_ARGS__) -+ -+#define san_consumer_warn(dev, handle, fmt, ...) \ -+ san_consumer_printk(warn, dev, handle, fmt, ##__VA_ARGS__) -+ -+static bool is_san_consumer(struct platform_device *pdev, acpi_handle handle) -+{ -+ struct acpi_handle_list dep_devices; -+ acpi_handle supplier = ACPI_HANDLE(&pdev->dev); -+ acpi_status status; -+ int i; -+ -+ if (!acpi_has_method(handle, "_DEP")) -+ return false; -+ -+ status = acpi_evaluate_reference(handle, "_DEP", NULL, &dep_devices); -+ if (ACPI_FAILURE(status)) { -+ san_consumer_dbg(&pdev->dev, handle, "failed to evaluate _DEP\n"); -+ return false; -+ } -+ -+ for (i = 0; i < dep_devices.count; i++) { -+ if (dep_devices.handles[i] == supplier) -+ return true; -+ } -+ -+ return false; -+} -+ -+static acpi_status san_consumer_setup(acpi_handle handle, u32 lvl, -+ void *context, void **rv) -+{ -+ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER; -+ struct platform_device *pdev = context; -+ struct acpi_device *adev; -+ struct device_link *link; -+ -+ if (!is_san_consumer(pdev, handle)) -+ return AE_OK; -+ -+ /* Ignore ACPI devices that are not present. */ -+ if (acpi_bus_get_device(handle, &adev) != 0) -+ return AE_OK; -+ -+ san_consumer_dbg(&pdev->dev, handle, "creating device link\n"); -+ -+ /* Try to set up device links, ignore but log errors. */ -+ link = device_link_add(&adev->dev, &pdev->dev, flags); -+ if (!link) { -+ san_consumer_warn(&pdev->dev, handle, "failed to create device link\n"); -+ return AE_OK; -+ } -+ -+ return AE_OK; -+} -+ -+static int san_consumer_links_setup(struct platform_device *pdev) -+{ -+ acpi_status status; -+ -+ status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, -+ ACPI_UINT32_MAX, san_consumer_setup, NULL, -+ pdev, NULL); -+ -+ return status ? -EFAULT : 0; -+} -+ -+static int san_probe(struct platform_device *pdev) -+{ -+ acpi_handle san = ACPI_HANDLE(&pdev->dev); -+ struct ssam_controller *ctrl; -+ struct san_data *data; -+ acpi_status astatus; -+ int status; -+ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ status = san_consumer_links_setup(pdev); -+ if (status) -+ return status; -+ -+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); -+ if (!data) -+ return -ENOMEM; -+ -+ data->dev = &pdev->dev; -+ data->ctrl = ctrl; -+ -+ platform_set_drvdata(pdev, data); -+ -+ astatus = acpi_install_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, -+ &san_opreg_handler, NULL, -+ &data->info); -+ if (ACPI_FAILURE(astatus)) -+ return -ENXIO; -+ -+ status = san_events_register(pdev); -+ if (status) -+ goto err_enable_events; -+ -+ status = san_set_rqsg_interface_device(&pdev->dev); -+ if (status) -+ goto err_install_dev; -+ -+ acpi_walk_dep_device_list(san); -+ return 0; -+ -+err_install_dev: -+ san_events_unregister(pdev); -+err_enable_events: -+ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, -+ &san_opreg_handler); -+ return status; -+} -+ -+static int san_remove(struct platform_device *pdev) -+{ -+ acpi_handle san = ACPI_HANDLE(&pdev->dev); -+ -+ san_set_rqsg_interface_device(NULL); -+ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, -+ &san_opreg_handler); -+ san_events_unregister(pdev); -+ -+ /* -+ * We have unregistered our event sources. Now we need to ensure that -+ * all delayed works they may have spawned are run to completion. -+ */ -+ flush_scheduled_work(); -+ -+ return 0; -+} -+ -+static const struct acpi_device_id san_match[] = { -+ { "MSHW0091" }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, san_match); -+ -+static struct platform_driver surface_acpi_notify = { -+ .probe = san_probe, -+ .remove = san_remove, -+ .driver = { -+ .name = "surface_acpi_notify", -+ .acpi_match_table = san_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_acpi_notify); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_aggregator/Kconfig b/drivers/platform/x86/surface_aggregator/Kconfig -new file mode 100644 -index 000000000000..cab020324256 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/Kconfig -@@ -0,0 +1,69 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+# Copyright (C) 2019-2021 Maximilian Luz -+ -+menuconfig SURFACE_AGGREGATOR -+ tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" -+ depends on SERIAL_DEV_BUS -+ depends on ACPI -+ select CRC_CCITT -+ help -+ The Surface System Aggregator Module (Surface SAM or SSAM) is an -+ embedded controller (EC) found on 5th- and later-generation Microsoft -+ Surface devices (i.e. Surface Pro 5, Surface Book 2, Surface Laptop, -+ and newer, with exception of Surface Go series devices). -+ -+ Depending on the device in question, this EC provides varying -+ functionality, including: -+ - EC access from ACPI via Surface ACPI Notify (5th- and 6th-generation) -+ - battery status information (all devices) -+ - thermal sensor access (all devices) -+ - performance mode / cooling mode control (all devices) -+ - clipboard detachment system control (Surface Book 2 and 3) -+ - HID / keyboard input (Surface Laptops, Surface Book 3) -+ -+ This option controls whether the Surface SAM subsystem core will be -+ built. This includes a driver for the Surface Serial Hub (SSH), which -+ is the device responsible for the communication with the EC, and a -+ basic kernel interface exposing the EC functionality to other client -+ drivers, i.e. allowing them to make requests to the EC and receive -+ events from it. Selecting this option alone will not provide any -+ client drivers and therefore no functionality beyond the in-kernel -+ interface. Said functionality is the responsibility of the respective -+ client drivers. -+ -+ Note: While 4th-generation Surface devices also make use of a SAM EC, -+ due to a difference in the communication interface of the controller, -+ only 5th and later generations are currently supported. Specifically, -+ devices using SAM-over-SSH are supported, whereas devices using -+ SAM-over-HID, which is used on the 4th generation, are currently not -+ supported. -+ -+ Choose m if you want to build the SAM subsystem core and SSH driver as -+ module, y if you want to build it into the kernel and n if you don't -+ want it at all. -+ -+config SURFACE_AGGREGATOR_BUS -+ bool "Surface System Aggregator Module Bus" -+ depends on SURFACE_AGGREGATOR -+ default y -+ help -+ Expands the Surface System Aggregator Module (SSAM) core driver by -+ providing a dedicated bus and client-device type. -+ -+ This bus and device type are intended to provide and simplify support -+ for non-platform and non-ACPI SSAM devices, i.e. SSAM devices that are -+ not auto-detectable via the conventional means (e.g. ACPI). -+ -+config SURFACE_AGGREGATOR_ERROR_INJECTION -+ bool "Surface System Aggregator Module Error Injection Capabilities" -+ depends on SURFACE_AGGREGATOR -+ depends on FUNCTION_ERROR_INJECTION -+ help -+ Provides error-injection capabilities for the Surface System -+ Aggregator Module subsystem and Surface Serial Hub driver. -+ -+ Specifically, exports error injection hooks to be used with the -+ kernel's function error injection capabilities to simulate underlying -+ transport and communication problems, such as invalid data sent to or -+ received from the EC, dropped data, and communication timeouts. -+ Intended for development and debugging. -diff --git a/drivers/platform/x86/surface_aggregator/Makefile b/drivers/platform/x86/surface_aggregator/Makefile -new file mode 100644 -index 000000000000..c8498c41e758 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/Makefile -@@ -0,0 +1,17 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+# Copyright (C) 2019-2021 Maximilian Luz -+ -+# For include/trace/define_trace.h to include trace.h -+CFLAGS_core.o = -I$(src) -+ -+obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator.o -+ -+surface_aggregator-objs := core.o -+surface_aggregator-objs += ssh_parser.o -+surface_aggregator-objs += ssh_packet_layer.o -+surface_aggregator-objs += ssh_request_layer.o -+surface_aggregator-objs += controller.o -+ -+ifeq ($(CONFIG_SURFACE_AGGREGATOR_BUS),y) -+surface_aggregator-objs += bus.o -+endif -diff --git a/drivers/platform/x86/surface_aggregator/bus.c b/drivers/platform/x86/surface_aggregator/bus.c -new file mode 100644 -index 000000000000..0169677c243e ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/bus.c -@@ -0,0 +1,415 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module bus and device integration. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+ -+#include -+#include -+ -+#include "bus.h" -+#include "controller.h" -+ -+static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, -+ char *buf) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ return sysfs_emit(buf, "ssam:d%02Xc%02Xt%02Xi%02Xf%02X\n", -+ sdev->uid.domain, sdev->uid.category, sdev->uid.target, -+ sdev->uid.instance, sdev->uid.function); -+} -+static DEVICE_ATTR_RO(modalias); -+ -+static struct attribute *ssam_device_attrs[] = { -+ &dev_attr_modalias.attr, -+ NULL, -+}; -+ATTRIBUTE_GROUPS(ssam_device); -+ -+static int ssam_device_uevent(struct device *dev, struct kobj_uevent_env *env) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ return add_uevent_var(env, "MODALIAS=ssam:d%02Xc%02Xt%02Xi%02Xf%02X", -+ sdev->uid.domain, sdev->uid.category, -+ sdev->uid.target, sdev->uid.instance, -+ sdev->uid.function); -+} -+ -+static void ssam_device_release(struct device *dev) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ ssam_controller_put(sdev->ctrl); -+ kfree(sdev); -+} -+ -+const struct device_type ssam_device_type = { -+ .name = "surface_aggregator_device", -+ .groups = ssam_device_groups, -+ .uevent = ssam_device_uevent, -+ .release = ssam_device_release, -+}; -+EXPORT_SYMBOL_GPL(ssam_device_type); -+ -+/** -+ * ssam_device_alloc() - Allocate and initialize a SSAM client device. -+ * @ctrl: The controller under which the device should be added. -+ * @uid: The UID of the device to be added. -+ * -+ * Allocates and initializes a new client device. The parent of the device -+ * will be set to the controller device and the name will be set based on the -+ * UID. Note that the device still has to be added via ssam_device_add(). -+ * Refer to that function for more details. -+ * -+ * Return: Returns the newly allocated and initialized SSAM client device, or -+ * %NULL if it could not be allocated. -+ */ -+struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, -+ struct ssam_device_uid uid) -+{ -+ struct ssam_device *sdev; -+ -+ sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); -+ if (!sdev) -+ return NULL; -+ -+ device_initialize(&sdev->dev); -+ sdev->dev.bus = &ssam_bus_type; -+ sdev->dev.type = &ssam_device_type; -+ sdev->dev.parent = ssam_controller_device(ctrl); -+ sdev->ctrl = ssam_controller_get(ctrl); -+ sdev->uid = uid; -+ -+ dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x:%02x", -+ sdev->uid.domain, sdev->uid.category, sdev->uid.target, -+ sdev->uid.instance, sdev->uid.function); -+ -+ return sdev; -+} -+EXPORT_SYMBOL_GPL(ssam_device_alloc); -+ -+/** -+ * ssam_device_add() - Add a SSAM client device. -+ * @sdev: The SSAM client device to be added. -+ * -+ * Added client devices must be guaranteed to always have a valid and active -+ * controller. Thus, this function will fail with %-ENODEV if the controller -+ * of the device has not been initialized yet, has been suspended, or has been -+ * shut down. -+ * -+ * The caller of this function should ensure that the corresponding call to -+ * ssam_device_remove() is issued before the controller is shut down. If the -+ * added device is a direct child of the controller device (default), it will -+ * be automatically removed when the controller is shut down. -+ * -+ * By default, the controller device will become the parent of the newly -+ * created client device. The parent may be changed before ssam_device_add is -+ * called, but care must be taken that a) the correct suspend/resume ordering -+ * is guaranteed and b) the client device does not outlive the controller, -+ * i.e. that the device is removed before the controller is being shut down. -+ * In case these guarantees have to be manually enforced, please refer to the -+ * ssam_client_link() and ssam_client_bind() functions, which are intended to -+ * set up device-links for this purpose. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssam_device_add(struct ssam_device *sdev) -+{ -+ int status; -+ -+ /* -+ * Ensure that we can only add new devices to a controller if it has -+ * been started and is not going away soon. This works in combination -+ * with ssam_controller_remove_clients to ensure driver presence for the -+ * controller device, i.e. it ensures that the controller (sdev->ctrl) -+ * is always valid and can be used for requests as long as the client -+ * device we add here is registered as child under it. This essentially -+ * guarantees that the client driver can always expect the preconditions -+ * for functions like ssam_request_sync (controller has to be started -+ * and is not suspended) to hold and thus does not have to check for -+ * them. -+ * -+ * Note that for this to work, the controller has to be a parent device. -+ * If it is not a direct parent, care has to be taken that the device is -+ * removed via ssam_device_remove(), as device_unregister does not -+ * remove child devices recursively. -+ */ -+ ssam_controller_statelock(sdev->ctrl); -+ -+ if (sdev->ctrl->state != SSAM_CONTROLLER_STARTED) { -+ ssam_controller_stateunlock(sdev->ctrl); -+ return -ENODEV; -+ } -+ -+ status = device_add(&sdev->dev); -+ -+ ssam_controller_stateunlock(sdev->ctrl); -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_device_add); -+ -+/** -+ * ssam_device_remove() - Remove a SSAM client device. -+ * @sdev: The device to remove. -+ * -+ * Removes and unregisters the provided SSAM client device. -+ */ -+void ssam_device_remove(struct ssam_device *sdev) -+{ -+ device_unregister(&sdev->dev); -+} -+EXPORT_SYMBOL_GPL(ssam_device_remove); -+ -+/** -+ * ssam_device_id_compatible() - Check if a device ID matches a UID. -+ * @id: The device ID as potential match. -+ * @uid: The device UID matching against. -+ * -+ * Check if the given ID is a match for the given UID, i.e. if a device with -+ * the provided UID is compatible to the given ID following the match rules -+ * described in its &ssam_device_id.match_flags member. -+ * -+ * Return: Returns %true if the given UID is compatible to the match rule -+ * described by the given ID, %false otherwise. -+ */ -+static bool ssam_device_id_compatible(const struct ssam_device_id *id, -+ struct ssam_device_uid uid) -+{ -+ if (id->domain != uid.domain || id->category != uid.category) -+ return false; -+ -+ if ((id->match_flags & SSAM_MATCH_TARGET) && id->target != uid.target) -+ return false; -+ -+ if ((id->match_flags & SSAM_MATCH_INSTANCE) && id->instance != uid.instance) -+ return false; -+ -+ if ((id->match_flags & SSAM_MATCH_FUNCTION) && id->function != uid.function) -+ return false; -+ -+ return true; -+} -+ -+/** -+ * ssam_device_id_is_null() - Check if a device ID is null. -+ * @id: The device ID to check. -+ * -+ * Check if a given device ID is null, i.e. all zeros. Used to check for the -+ * end of ``MODULE_DEVICE_TABLE(ssam, ...)`` or similar lists. -+ * -+ * Return: Returns %true if the given ID represents a null ID, %false -+ * otherwise. -+ */ -+static bool ssam_device_id_is_null(const struct ssam_device_id *id) -+{ -+ return id->match_flags == 0 && -+ id->domain == 0 && -+ id->category == 0 && -+ id->target == 0 && -+ id->instance == 0 && -+ id->function == 0 && -+ id->driver_data == 0; -+} -+ -+/** -+ * ssam_device_id_match() - Find the matching ID table entry for the given UID. -+ * @table: The table to search in. -+ * @uid: The UID to matched against the individual table entries. -+ * -+ * Find the first match for the provided device UID in the provided ID table -+ * and return it. Returns %NULL if no match could be found. -+ */ -+const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table, -+ const struct ssam_device_uid uid) -+{ -+ const struct ssam_device_id *id; -+ -+ for (id = table; !ssam_device_id_is_null(id); ++id) -+ if (ssam_device_id_compatible(id, uid)) -+ return id; -+ -+ return NULL; -+} -+EXPORT_SYMBOL_GPL(ssam_device_id_match); -+ -+/** -+ * ssam_device_get_match() - Find and return the ID matching the device in the -+ * ID table of the bound driver. -+ * @dev: The device for which to get the matching ID table entry. -+ * -+ * Find the fist match for the UID of the device in the ID table of the -+ * currently bound driver and return it. Returns %NULL if the device does not -+ * have a driver bound to it, the driver does not have match_table (i.e. it is -+ * %NULL), or there is no match in the driver's match_table. -+ * -+ * This function essentially calls ssam_device_id_match() with the ID table of -+ * the bound device driver and the UID of the device. -+ * -+ * Return: Returns the first match for the UID of the device in the device -+ * driver's match table, or %NULL if no such match could be found. -+ */ -+const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev) -+{ -+ const struct ssam_device_driver *sdrv; -+ -+ sdrv = to_ssam_device_driver(dev->dev.driver); -+ if (!sdrv) -+ return NULL; -+ -+ if (!sdrv->match_table) -+ return NULL; -+ -+ return ssam_device_id_match(sdrv->match_table, dev->uid); -+} -+EXPORT_SYMBOL_GPL(ssam_device_get_match); -+ -+/** -+ * ssam_device_get_match_data() - Find the ID matching the device in the -+ * ID table of the bound driver and return its ``driver_data`` member. -+ * @dev: The device for which to get the match data. -+ * -+ * Find the fist match for the UID of the device in the ID table of the -+ * corresponding driver and return its driver_data. Returns %NULL if the -+ * device does not have a driver bound to it, the driver does not have -+ * match_table (i.e. it is %NULL), there is no match in the driver's -+ * match_table, or the match does not have any driver_data. -+ * -+ * This function essentially calls ssam_device_get_match() and, if any match -+ * could be found, returns its ``struct ssam_device_id.driver_data`` member. -+ * -+ * Return: Returns the driver data associated with the first match for the UID -+ * of the device in the device driver's match table, or %NULL if no such match -+ * could be found. -+ */ -+const void *ssam_device_get_match_data(const struct ssam_device *dev) -+{ -+ const struct ssam_device_id *id; -+ -+ id = ssam_device_get_match(dev); -+ if (!id) -+ return NULL; -+ -+ return (const void *)id->driver_data; -+} -+EXPORT_SYMBOL_GPL(ssam_device_get_match_data); -+ -+static int ssam_bus_match(struct device *dev, struct device_driver *drv) -+{ -+ struct ssam_device_driver *sdrv = to_ssam_device_driver(drv); -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (!is_ssam_device(dev)) -+ return 0; -+ -+ return !!ssam_device_id_match(sdrv->match_table, sdev->uid); -+} -+ -+static int ssam_bus_probe(struct device *dev) -+{ -+ return to_ssam_device_driver(dev->driver) -+ ->probe(to_ssam_device(dev)); -+} -+ -+static int ssam_bus_remove(struct device *dev) -+{ -+ struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver); -+ -+ if (sdrv->remove) -+ sdrv->remove(to_ssam_device(dev)); -+ -+ return 0; -+} -+ -+struct bus_type ssam_bus_type = { -+ .name = "surface_aggregator", -+ .match = ssam_bus_match, -+ .probe = ssam_bus_probe, -+ .remove = ssam_bus_remove, -+}; -+EXPORT_SYMBOL_GPL(ssam_bus_type); -+ -+/** -+ * __ssam_device_driver_register() - Register a SSAM client device driver. -+ * @sdrv: The driver to register. -+ * @owner: The module owning the provided driver. -+ * -+ * Please refer to the ssam_device_driver_register() macro for the normal way -+ * to register a driver from inside its owning module. -+ */ -+int __ssam_device_driver_register(struct ssam_device_driver *sdrv, -+ struct module *owner) -+{ -+ sdrv->driver.owner = owner; -+ sdrv->driver.bus = &ssam_bus_type; -+ -+ /* force drivers to async probe so I/O is possible in probe */ -+ sdrv->driver.probe_type = PROBE_PREFER_ASYNCHRONOUS; -+ -+ return driver_register(&sdrv->driver); -+} -+EXPORT_SYMBOL_GPL(__ssam_device_driver_register); -+ -+/** -+ * ssam_device_driver_unregister - Unregister a SSAM device driver. -+ * @sdrv: The driver to unregister. -+ */ -+void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) -+{ -+ driver_unregister(&sdrv->driver); -+} -+EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); -+ -+static int ssam_remove_device(struct device *dev, void *_data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (is_ssam_device(dev)) -+ ssam_device_remove(sdev); -+ -+ return 0; -+} -+ -+/** -+ * ssam_controller_remove_clients() - Remove SSAM client devices registered as -+ * direct children under the given controller. -+ * @ctrl: The controller to remove all direct clients for. -+ * -+ * Remove all SSAM client devices registered as direct children under the -+ * given controller. Note that this only accounts for direct children of the -+ * controller device. This does not take care of any client devices where the -+ * parent device has been manually set before calling ssam_device_add. Refer -+ * to ssam_device_add()/ssam_device_remove() for more details on those cases. -+ * -+ * To avoid new devices being added in parallel to this call, the main -+ * controller lock (not statelock) must be held during this (and if -+ * necessary, any subsequent deinitialization) call. -+ */ -+void ssam_controller_remove_clients(struct ssam_controller *ctrl) -+{ -+ struct device *dev; -+ -+ dev = ssam_controller_device(ctrl); -+ device_for_each_child_reverse(dev, NULL, ssam_remove_device); -+} -+ -+/** -+ * ssam_bus_register() - Register and set-up the SSAM client device bus. -+ */ -+int ssam_bus_register(void) -+{ -+ return bus_register(&ssam_bus_type); -+} -+ -+/** -+ * ssam_bus_unregister() - Unregister the SSAM client device bus. -+ */ -+void ssam_bus_unregister(void) -+{ -+ return bus_unregister(&ssam_bus_type); -+} -diff --git a/drivers/platform/x86/surface_aggregator/bus.h b/drivers/platform/x86/surface_aggregator/bus.h -new file mode 100644 -index 000000000000..ed032c2cbdb2 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/bus.h -@@ -0,0 +1,27 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Surface System Aggregator Module bus and device integration. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_BUS_H -+#define _SURFACE_AGGREGATOR_BUS_H -+ -+#include -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+ -+void ssam_controller_remove_clients(struct ssam_controller *ctrl); -+ -+int ssam_bus_register(void); -+void ssam_bus_unregister(void); -+ -+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+static inline void ssam_controller_remove_clients(struct ssam_controller *ctrl) {} -+static inline int ssam_bus_register(void) { return 0; } -+static inline void ssam_bus_unregister(void) {} -+ -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+#endif /* _SURFACE_AGGREGATOR_BUS_H */ -diff --git a/drivers/platform/x86/surface_aggregator/controller.c b/drivers/platform/x86/surface_aggregator/controller.c -new file mode 100644 -index 000000000000..68585c966de5 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/controller.c -@@ -0,0 +1,2789 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Main SSAM/SSH controller structure and functionality. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "controller.h" -+#include "ssh_msgb.h" -+#include "ssh_request_layer.h" -+ -+#include "trace.h" -+ -+ -+/* -- Safe counters. -------------------------------------------------------- */ -+ -+/** -+ * ssh_seq_reset() - Reset/initialize sequence ID counter. -+ * @c: The counter to reset. -+ */ -+static void ssh_seq_reset(struct ssh_seq_counter *c) -+{ -+ WRITE_ONCE(c->value, 0); -+} -+ -+/** -+ * ssh_seq_next() - Get next sequence ID. -+ * @c: The counter providing the sequence IDs. -+ * -+ * Return: Returns the next sequence ID of the counter. -+ */ -+static u8 ssh_seq_next(struct ssh_seq_counter *c) -+{ -+ u8 old = READ_ONCE(c->value); -+ u8 new = old + 1; -+ u8 ret; -+ -+ while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { -+ old = ret; -+ new = old + 1; -+ } -+ -+ return old; -+} -+ -+/** -+ * ssh_rqid_reset() - Reset/initialize request ID counter. -+ * @c: The counter to reset. -+ */ -+static void ssh_rqid_reset(struct ssh_rqid_counter *c) -+{ -+ WRITE_ONCE(c->value, 0); -+} -+ -+/** -+ * ssh_rqid_next() - Get next request ID. -+ * @c: The counter providing the request IDs. -+ * -+ * Return: Returns the next request ID of the counter, skipping any reserved -+ * request IDs. -+ */ -+static u16 ssh_rqid_next(struct ssh_rqid_counter *c) -+{ -+ u16 old = READ_ONCE(c->value); -+ u16 new = ssh_rqid_next_valid(old); -+ u16 ret; -+ -+ while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { -+ old = ret; -+ new = ssh_rqid_next_valid(old); -+ } -+ -+ return old; -+} -+ -+ -+/* -- Event notifier/callbacks. --------------------------------------------- */ -+/* -+ * The notifier system is based on linux/notifier.h, specifically the SRCU -+ * implementation. The difference to that is, that some bits of the notifier -+ * call return value can be tracked across multiple calls. This is done so -+ * that handling of events can be tracked and a warning can be issued in case -+ * an event goes unhandled. The idea of that warning is that it should help -+ * discover and identify new/currently unimplemented features. -+ */ -+ -+/** -+ * ssam_event_matches_notifier() - Test if an event matches a notifier. -+ * @n: The event notifier to test against. -+ * @event: The event to test. -+ * -+ * Return: Returns %true if the given event matches the given notifier -+ * according to the rules set in the notifier's event mask, %false otherwise. -+ */ -+static bool ssam_event_matches_notifier(const struct ssam_event_notifier *n, -+ const struct ssam_event *event) -+{ -+ bool match = n->event.id.target_category == event->target_category; -+ -+ if (n->event.mask & SSAM_EVENT_MASK_TARGET) -+ match &= n->event.reg.target_id == event->target_id; -+ -+ if (n->event.mask & SSAM_EVENT_MASK_INSTANCE) -+ match &= n->event.id.instance == event->instance_id; -+ -+ return match; -+} -+ -+/** -+ * ssam_nfblk_call_chain() - Call event notifier callbacks of the given chain. -+ * @nh: The notifier head for which the notifier callbacks should be called. -+ * @event: The event data provided to the callbacks. -+ * -+ * Call all registered notifier callbacks in order of their priority until -+ * either no notifier is left or a notifier returns a value with the -+ * %SSAM_NOTIF_STOP bit set. Note that this bit is automatically set via -+ * ssam_notifier_from_errno() on any non-zero error value. -+ * -+ * Return: Returns the notifier status value, which contains the notifier -+ * status bits (%SSAM_NOTIF_HANDLED and %SSAM_NOTIF_STOP) as well as a -+ * potential error value returned from the last executed notifier callback. -+ * Use ssam_notifier_to_errno() to convert this value to the original error -+ * value. -+ */ -+static int ssam_nfblk_call_chain(struct ssam_nf_head *nh, struct ssam_event *event) -+{ -+ struct ssam_event_notifier *nf; -+ int ret = 0, idx; -+ -+ idx = srcu_read_lock(&nh->srcu); -+ -+ list_for_each_entry_rcu(nf, &nh->head, base.node, -+ srcu_read_lock_held(&nh->srcu)) { -+ if (ssam_event_matches_notifier(nf, event)) { -+ ret = (ret & SSAM_NOTIF_STATE_MASK) | nf->base.fn(nf, event); -+ if (ret & SSAM_NOTIF_STOP) -+ break; -+ } -+ } -+ -+ srcu_read_unlock(&nh->srcu, idx); -+ return ret; -+} -+ -+/** -+ * ssam_nfblk_insert() - Insert a new notifier block into the given notifier -+ * list. -+ * @nh: The notifier head into which the block should be inserted. -+ * @nb: The notifier block to add. -+ * -+ * Note: This function must be synchronized by the caller with respect to other -+ * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. -+ * -+ * Return: Returns zero on success, %-EEXIST if the notifier block has already -+ * been registered. -+ */ -+static int ssam_nfblk_insert(struct ssam_nf_head *nh, struct ssam_notifier_block *nb) -+{ -+ struct ssam_notifier_block *p; -+ struct list_head *h; -+ -+ /* Runs under lock, no need for RCU variant. */ -+ list_for_each(h, &nh->head) { -+ p = list_entry(h, struct ssam_notifier_block, node); -+ -+ if (unlikely(p == nb)) { -+ WARN(1, "double register detected"); -+ return -EEXIST; -+ } -+ -+ if (nb->priority > p->priority) -+ break; -+ } -+ -+ list_add_tail_rcu(&nb->node, h); -+ return 0; -+} -+ -+/** -+ * ssam_nfblk_find() - Check if a notifier block is registered on the given -+ * notifier head. -+ * list. -+ * @nh: The notifier head on which to search. -+ * @nb: The notifier block to search for. -+ * -+ * Note: This function must be synchronized by the caller with respect to other -+ * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. -+ * -+ * Return: Returns true if the given notifier block is registered on the given -+ * notifier head, false otherwise. -+ */ -+static bool ssam_nfblk_find(struct ssam_nf_head *nh, struct ssam_notifier_block *nb) -+{ -+ struct ssam_notifier_block *p; -+ -+ /* Runs under lock, no need for RCU variant. */ -+ list_for_each_entry(p, &nh->head, node) { -+ if (p == nb) -+ return true; -+ } -+ -+ return false; -+} -+ -+/** -+ * ssam_nfblk_remove() - Remove a notifier block from its notifier list. -+ * @nb: The notifier block to be removed. -+ * -+ * Note: This function must be synchronized by the caller with respect to -+ * other insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. -+ * Furthermore, the caller _must_ ensure SRCU synchronization by calling -+ * synchronize_srcu() with ``nh->srcu`` after leaving the critical section, to -+ * ensure that the removed notifier block is not in use any more. -+ */ -+static void ssam_nfblk_remove(struct ssam_notifier_block *nb) -+{ -+ list_del_rcu(&nb->node); -+} -+ -+/** -+ * ssam_nf_head_init() - Initialize the given notifier head. -+ * @nh: The notifier head to initialize. -+ */ -+static int ssam_nf_head_init(struct ssam_nf_head *nh) -+{ -+ int status; -+ -+ status = init_srcu_struct(&nh->srcu); -+ if (status) -+ return status; -+ -+ INIT_LIST_HEAD(&nh->head); -+ return 0; -+} -+ -+/** -+ * ssam_nf_head_destroy() - Deinitialize the given notifier head. -+ * @nh: The notifier head to deinitialize. -+ */ -+static void ssam_nf_head_destroy(struct ssam_nf_head *nh) -+{ -+ cleanup_srcu_struct(&nh->srcu); -+} -+ -+ -+/* -- Event/notification registry. ------------------------------------------ */ -+ -+/** -+ * struct ssam_nf_refcount_key - Key used for event activation reference -+ * counting. -+ * @reg: The registry via which the event is enabled/disabled. -+ * @id: The ID uniquely describing the event. -+ */ -+struct ssam_nf_refcount_key { -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+}; -+ -+/** -+ * struct ssam_nf_refcount_entry - RB-tree entry for reference counting event -+ * activations. -+ * @node: The node of this entry in the rb-tree. -+ * @key: The key of the event. -+ * @refcount: The reference-count of the event. -+ * @flags: The flags used when enabling the event. -+ */ -+struct ssam_nf_refcount_entry { -+ struct rb_node node; -+ struct ssam_nf_refcount_key key; -+ int refcount; -+ u8 flags; -+}; -+ -+/** -+ * ssam_nf_refcount_inc() - Increment reference-/activation-count of the given -+ * event. -+ * @nf: The notifier system reference. -+ * @reg: The registry used to enable/disable the event. -+ * @id: The event ID. -+ * -+ * Increments the reference-/activation-count associated with the specified -+ * event type/ID, allocating a new entry for this event ID if necessary. A -+ * newly allocated entry will have a refcount of one. -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ * -+ * Return: Returns the refcount entry on success. Returns an error pointer -+ * with %-ENOSPC if there have already been %INT_MAX events of the specified -+ * ID and type registered, or %-ENOMEM if the entry could not be allocated. -+ */ -+static struct ssam_nf_refcount_entry * -+ssam_nf_refcount_inc(struct ssam_nf *nf, struct ssam_event_registry reg, -+ struct ssam_event_id id) -+{ -+ struct ssam_nf_refcount_entry *entry; -+ struct ssam_nf_refcount_key key; -+ struct rb_node **link = &nf->refcount.rb_node; -+ struct rb_node *parent = NULL; -+ int cmp; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ key.reg = reg; -+ key.id = id; -+ -+ while (*link) { -+ entry = rb_entry(*link, struct ssam_nf_refcount_entry, node); -+ parent = *link; -+ -+ cmp = memcmp(&key, &entry->key, sizeof(key)); -+ if (cmp < 0) { -+ link = &(*link)->rb_left; -+ } else if (cmp > 0) { -+ link = &(*link)->rb_right; -+ } else if (entry->refcount < INT_MAX) { -+ entry->refcount++; -+ return entry; -+ } else { -+ WARN_ON(1); -+ return ERR_PTR(-ENOSPC); -+ } -+ } -+ -+ entry = kzalloc(sizeof(*entry), GFP_KERNEL); -+ if (!entry) -+ return ERR_PTR(-ENOMEM); -+ -+ entry->key = key; -+ entry->refcount = 1; -+ -+ rb_link_node(&entry->node, parent, link); -+ rb_insert_color(&entry->node, &nf->refcount); -+ -+ return entry; -+} -+ -+/** -+ * ssam_nf_refcount_dec() - Decrement reference-/activation-count of the given -+ * event. -+ * @nf: The notifier system reference. -+ * @reg: The registry used to enable/disable the event. -+ * @id: The event ID. -+ * -+ * Decrements the reference-/activation-count of the specified event, -+ * returning its entry. If the returned entry has a refcount of zero, the -+ * caller is responsible for freeing it using kfree(). -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ * -+ * Return: Returns the refcount entry on success or %NULL if the entry has not -+ * been found. -+ */ -+static struct ssam_nf_refcount_entry * -+ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg, -+ struct ssam_event_id id) -+{ -+ struct ssam_nf_refcount_entry *entry; -+ struct ssam_nf_refcount_key key; -+ struct rb_node *node = nf->refcount.rb_node; -+ int cmp; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ key.reg = reg; -+ key.id = id; -+ -+ while (node) { -+ entry = rb_entry(node, struct ssam_nf_refcount_entry, node); -+ -+ cmp = memcmp(&key, &entry->key, sizeof(key)); -+ if (cmp < 0) { -+ node = node->rb_left; -+ } else if (cmp > 0) { -+ node = node->rb_right; -+ } else { -+ entry->refcount--; -+ if (entry->refcount == 0) -+ rb_erase(&entry->node, &nf->refcount); -+ -+ return entry; -+ } -+ } -+ -+ return NULL; -+} -+ -+/** -+ * ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the -+ * given event and free its entry if the reference count reaches zero. -+ * @nf: The notifier system reference. -+ * @reg: The registry used to enable/disable the event. -+ * @id: The event ID. -+ * -+ * Decrements the reference-/activation-count of the specified event, freeing -+ * its entry if it reaches zero. -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ */ -+static void ssam_nf_refcount_dec_free(struct ssam_nf *nf, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id) -+{ -+ struct ssam_nf_refcount_entry *entry; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ entry = ssam_nf_refcount_dec(nf, reg, id); -+ if (entry && entry->refcount == 0) -+ kfree(entry); -+} -+ -+/** -+ * ssam_nf_refcount_empty() - Test if the notification system has any -+ * enabled/active events. -+ * @nf: The notification system. -+ */ -+static bool ssam_nf_refcount_empty(struct ssam_nf *nf) -+{ -+ return RB_EMPTY_ROOT(&nf->refcount); -+} -+ -+/** -+ * ssam_nf_call() - Call notification callbacks for the provided event. -+ * @nf: The notifier system -+ * @dev: The associated device, only used for logging. -+ * @rqid: The request ID of the event. -+ * @event: The event provided to the callbacks. -+ * -+ * Execute registered callbacks in order of their priority until either no -+ * callback is left or a callback returns a value with the %SSAM_NOTIF_STOP -+ * bit set. Note that this bit is set automatically when converting non-zero -+ * error values via ssam_notifier_from_errno() to notifier values. -+ * -+ * Also note that any callback that could handle an event should return a value -+ * with bit %SSAM_NOTIF_HANDLED set, indicating that the event does not go -+ * unhandled/ignored. In case no registered callback could handle an event, -+ * this function will emit a warning. -+ * -+ * In case a callback failed, this function will emit an error message. -+ */ -+static void ssam_nf_call(struct ssam_nf *nf, struct device *dev, u16 rqid, -+ struct ssam_event *event) -+{ -+ struct ssam_nf_head *nf_head; -+ int status, nf_ret; -+ -+ if (!ssh_rqid_is_event(rqid)) { -+ dev_warn(dev, "event: unsupported rqid: %#06x\n", rqid); -+ return; -+ } -+ -+ nf_head = &nf->head[ssh_rqid_to_event(rqid)]; -+ nf_ret = ssam_nfblk_call_chain(nf_head, event); -+ status = ssam_notifier_to_errno(nf_ret); -+ -+ if (status < 0) { -+ dev_err(dev, -+ "event: error handling event: %d (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", -+ status, event->target_category, event->target_id, -+ event->command_id, event->instance_id); -+ } else if (!(nf_ret & SSAM_NOTIF_HANDLED)) { -+ dev_warn(dev, -+ "event: unhandled event (rqid: %#04x, tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", -+ rqid, event->target_category, event->target_id, -+ event->command_id, event->instance_id); -+ } -+} -+ -+/** -+ * ssam_nf_init() - Initialize the notifier system. -+ * @nf: The notifier system to initialize. -+ */ -+static int ssam_nf_init(struct ssam_nf *nf) -+{ -+ int i, status; -+ -+ for (i = 0; i < SSH_NUM_EVENTS; i++) { -+ status = ssam_nf_head_init(&nf->head[i]); -+ if (status) -+ break; -+ } -+ -+ if (status) { -+ while (i--) -+ ssam_nf_head_destroy(&nf->head[i]); -+ -+ return status; -+ } -+ -+ mutex_init(&nf->lock); -+ return 0; -+} -+ -+/** -+ * ssam_nf_destroy() - Deinitialize the notifier system. -+ * @nf: The notifier system to deinitialize. -+ */ -+static void ssam_nf_destroy(struct ssam_nf *nf) -+{ -+ int i; -+ -+ for (i = 0; i < SSH_NUM_EVENTS; i++) -+ ssam_nf_head_destroy(&nf->head[i]); -+ -+ mutex_destroy(&nf->lock); -+} -+ -+ -+/* -- Event/async request completion system. -------------------------------- */ -+ -+#define SSAM_CPLT_WQ_NAME "ssam_cpltq" -+ -+/* -+ * SSAM_CPLT_WQ_BATCH - Maximum number of event item completions executed per -+ * work execution. Used to prevent livelocking of the workqueue. Value chosen -+ * via educated guess, may be adjusted. -+ */ -+#define SSAM_CPLT_WQ_BATCH 10 -+ -+/* -+ * SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN - Maximum payload length for a cached -+ * &struct ssam_event_item. -+ * -+ * This length has been chosen to be accommodate standard touchpad and -+ * keyboard input events. Events with larger payloads will be allocated -+ * separately. -+ */ -+#define SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN 32 -+ -+static struct kmem_cache *ssam_event_item_cache; -+ -+/** -+ * ssam_event_item_cache_init() - Initialize the event item cache. -+ */ -+int ssam_event_item_cache_init(void) -+{ -+ const unsigned int size = sizeof(struct ssam_event_item) -+ + SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN; -+ const unsigned int align = __alignof__(struct ssam_event_item); -+ struct kmem_cache *cache; -+ -+ cache = kmem_cache_create("ssam_event_item", size, align, 0, NULL); -+ if (!cache) -+ return -ENOMEM; -+ -+ ssam_event_item_cache = cache; -+ return 0; -+} -+ -+/** -+ * ssam_event_item_cache_destroy() - Deinitialize the event item cache. -+ */ -+void ssam_event_item_cache_destroy(void) -+{ -+ kmem_cache_destroy(ssam_event_item_cache); -+ ssam_event_item_cache = NULL; -+} -+ -+static void __ssam_event_item_free_cached(struct ssam_event_item *item) -+{ -+ kmem_cache_free(ssam_event_item_cache, item); -+} -+ -+static void __ssam_event_item_free_generic(struct ssam_event_item *item) -+{ -+ kfree(item); -+} -+ -+/** -+ * ssam_event_item_free() - Free the provided event item. -+ * @item: The event item to free. -+ */ -+static void ssam_event_item_free(struct ssam_event_item *item) -+{ -+ trace_ssam_event_item_free(item); -+ item->ops.free(item); -+} -+ -+/** -+ * ssam_event_item_alloc() - Allocate an event item with the given payload size. -+ * @len: The event payload length. -+ * @flags: The flags used for allocation. -+ * -+ * Allocate an event item with the given payload size, preferring allocation -+ * from the event item cache if the payload is small enough (i.e. smaller than -+ * %SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN). Sets the item operations and payload -+ * length values. The item free callback (``ops.free``) should not be -+ * overwritten after this call. -+ * -+ * Return: Returns the newly allocated event item. -+ */ -+static struct ssam_event_item *ssam_event_item_alloc(size_t len, gfp_t flags) -+{ -+ struct ssam_event_item *item; -+ -+ if (len <= SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN) { -+ item = kmem_cache_alloc(ssam_event_item_cache, flags); -+ if (!item) -+ return NULL; -+ -+ item->ops.free = __ssam_event_item_free_cached; -+ } else { -+ item = kzalloc(struct_size(item, event.data, len), flags); -+ if (!item) -+ return NULL; -+ -+ item->ops.free = __ssam_event_item_free_generic; -+ } -+ -+ item->event.length = len; -+ -+ trace_ssam_event_item_alloc(item, len); -+ return item; -+} -+ -+/** -+ * ssam_event_queue_push() - Push an event item to the event queue. -+ * @q: The event queue. -+ * @item: The item to add. -+ */ -+static void ssam_event_queue_push(struct ssam_event_queue *q, -+ struct ssam_event_item *item) -+{ -+ spin_lock(&q->lock); -+ list_add_tail(&item->node, &q->head); -+ spin_unlock(&q->lock); -+} -+ -+/** -+ * ssam_event_queue_pop() - Pop the next event item from the event queue. -+ * @q: The event queue. -+ * -+ * Returns and removes the next event item from the queue. Returns %NULL If -+ * there is no event item left. -+ */ -+static struct ssam_event_item *ssam_event_queue_pop(struct ssam_event_queue *q) -+{ -+ struct ssam_event_item *item; -+ -+ spin_lock(&q->lock); -+ item = list_first_entry_or_null(&q->head, struct ssam_event_item, node); -+ if (item) -+ list_del(&item->node); -+ spin_unlock(&q->lock); -+ -+ return item; -+} -+ -+/** -+ * ssam_event_queue_is_empty() - Check if the event queue is empty. -+ * @q: The event queue. -+ */ -+static bool ssam_event_queue_is_empty(struct ssam_event_queue *q) -+{ -+ bool empty; -+ -+ spin_lock(&q->lock); -+ empty = list_empty(&q->head); -+ spin_unlock(&q->lock); -+ -+ return empty; -+} -+ -+/** -+ * ssam_cplt_get_event_queue() - Get the event queue for the given parameters. -+ * @cplt: The completion system on which to look for the queue. -+ * @tid: The target ID of the queue. -+ * @rqid: The request ID representing the event ID for which to get the queue. -+ * -+ * Return: Returns the event queue corresponding to the event type described -+ * by the given parameters. If the request ID does not represent an event, -+ * this function returns %NULL. If the target ID is not supported, this -+ * function will fall back to the default target ID (``tid = 1``). -+ */ -+static -+struct ssam_event_queue *ssam_cplt_get_event_queue(struct ssam_cplt *cplt, -+ u8 tid, u16 rqid) -+{ -+ u16 event = ssh_rqid_to_event(rqid); -+ u16 tidx = ssh_tid_to_index(tid); -+ -+ if (!ssh_rqid_is_event(rqid)) { -+ dev_err(cplt->dev, "event: unsupported request ID: %#06x\n", rqid); -+ return NULL; -+ } -+ -+ if (!ssh_tid_is_valid(tid)) { -+ dev_warn(cplt->dev, "event: unsupported target ID: %u\n", tid); -+ tidx = 0; -+ } -+ -+ return &cplt->event.target[tidx].queue[event]; -+} -+ -+/** -+ * ssam_cplt_submit() - Submit a work item to the completion system workqueue. -+ * @cplt: The completion system. -+ * @work: The work item to submit. -+ */ -+static bool ssam_cplt_submit(struct ssam_cplt *cplt, struct work_struct *work) -+{ -+ return queue_work(cplt->wq, work); -+} -+ -+/** -+ * ssam_cplt_submit_event() - Submit an event to the completion system. -+ * @cplt: The completion system. -+ * @item: The event item to submit. -+ * -+ * Submits the event to the completion system by queuing it on the event item -+ * queue and queuing the respective event queue work item on the completion -+ * workqueue, which will eventually complete the event. -+ * -+ * Return: Returns zero on success, %-EINVAL if there is no event queue that -+ * can handle the given event item. -+ */ -+static int ssam_cplt_submit_event(struct ssam_cplt *cplt, -+ struct ssam_event_item *item) -+{ -+ struct ssam_event_queue *evq; -+ -+ evq = ssam_cplt_get_event_queue(cplt, item->event.target_id, item->rqid); -+ if (!evq) -+ return -EINVAL; -+ -+ ssam_event_queue_push(evq, item); -+ ssam_cplt_submit(cplt, &evq->work); -+ return 0; -+} -+ -+/** -+ * ssam_cplt_flush() - Flush the completion system. -+ * @cplt: The completion system. -+ * -+ * Flush the completion system by waiting until all currently submitted work -+ * items have been completed. -+ * -+ * Note: This function does not guarantee that all events will have been -+ * handled once this call terminates. In case of a larger number of -+ * to-be-completed events, the event queue work function may re-schedule its -+ * work item, which this flush operation will ignore. -+ * -+ * This operation is only intended to, during normal operation prior to -+ * shutdown, try to complete most events and requests to get them out of the -+ * system while the system is still fully operational. It does not aim to -+ * provide any guarantee that all of them have been handled. -+ */ -+static void ssam_cplt_flush(struct ssam_cplt *cplt) -+{ -+ flush_workqueue(cplt->wq); -+} -+ -+static void ssam_event_queue_work_fn(struct work_struct *work) -+{ -+ struct ssam_event_queue *queue; -+ struct ssam_event_item *item; -+ struct ssam_nf *nf; -+ struct device *dev; -+ unsigned int iterations = SSAM_CPLT_WQ_BATCH; -+ -+ queue = container_of(work, struct ssam_event_queue, work); -+ nf = &queue->cplt->event.notif; -+ dev = queue->cplt->dev; -+ -+ /* Limit number of processed events to avoid livelocking. */ -+ do { -+ item = ssam_event_queue_pop(queue); -+ if (!item) -+ return; -+ -+ ssam_nf_call(nf, dev, item->rqid, &item->event); -+ ssam_event_item_free(item); -+ } while (--iterations); -+ -+ if (!ssam_event_queue_is_empty(queue)) -+ ssam_cplt_submit(queue->cplt, &queue->work); -+} -+ -+/** -+ * ssam_event_queue_init() - Initialize an event queue. -+ * @cplt: The completion system on which the queue resides. -+ * @evq: The event queue to initialize. -+ */ -+static void ssam_event_queue_init(struct ssam_cplt *cplt, -+ struct ssam_event_queue *evq) -+{ -+ evq->cplt = cplt; -+ spin_lock_init(&evq->lock); -+ INIT_LIST_HEAD(&evq->head); -+ INIT_WORK(&evq->work, ssam_event_queue_work_fn); -+} -+ -+/** -+ * ssam_cplt_init() - Initialize completion system. -+ * @cplt: The completion system to initialize. -+ * @dev: The device used for logging. -+ */ -+static int ssam_cplt_init(struct ssam_cplt *cplt, struct device *dev) -+{ -+ struct ssam_event_target *target; -+ int status, c, i; -+ -+ cplt->dev = dev; -+ -+ cplt->wq = create_workqueue(SSAM_CPLT_WQ_NAME); -+ if (!cplt->wq) -+ return -ENOMEM; -+ -+ for (c = 0; c < ARRAY_SIZE(cplt->event.target); c++) { -+ target = &cplt->event.target[c]; -+ -+ for (i = 0; i < ARRAY_SIZE(target->queue); i++) -+ ssam_event_queue_init(cplt, &target->queue[i]); -+ } -+ -+ status = ssam_nf_init(&cplt->event.notif); -+ if (status) -+ destroy_workqueue(cplt->wq); -+ -+ return status; -+} -+ -+/** -+ * ssam_cplt_destroy() - Deinitialize the completion system. -+ * @cplt: The completion system to deinitialize. -+ * -+ * Deinitialize the given completion system and ensure that all pending, i.e. -+ * yet-to-be-completed, event items and requests have been handled. -+ */ -+static void ssam_cplt_destroy(struct ssam_cplt *cplt) -+{ -+ /* -+ * Note: destroy_workqueue ensures that all currently queued work will -+ * be fully completed and the workqueue drained. This means that this -+ * call will inherently also free any queued ssam_event_items, thus we -+ * don't have to take care of that here explicitly. -+ */ -+ destroy_workqueue(cplt->wq); -+ ssam_nf_destroy(&cplt->event.notif); -+} -+ -+ -+/* -- Main SSAM device structures. ------------------------------------------ */ -+ -+/** -+ * ssam_controller_device() - Get the &struct device associated with this -+ * controller. -+ * @c: The controller for which to get the device. -+ * -+ * Return: Returns the &struct device associated with this controller, -+ * providing its lower-level transport. -+ */ -+struct device *ssam_controller_device(struct ssam_controller *c) -+{ -+ return ssh_rtl_get_device(&c->rtl); -+} -+EXPORT_SYMBOL_GPL(ssam_controller_device); -+ -+static void __ssam_controller_release(struct kref *kref) -+{ -+ struct ssam_controller *ctrl = to_ssam_controller(kref, kref); -+ -+ /* -+ * The lock-call here is to satisfy lockdep. At this point we really -+ * expect this to be the last remaining reference to the controller. -+ * Anything else is a bug. -+ */ -+ ssam_controller_lock(ctrl); -+ ssam_controller_destroy(ctrl); -+ ssam_controller_unlock(ctrl); -+ -+ kfree(ctrl); -+} -+ -+/** -+ * ssam_controller_get() - Increment reference count of controller. -+ * @c: The controller. -+ * -+ * Return: Returns the controller provided as input. -+ */ -+struct ssam_controller *ssam_controller_get(struct ssam_controller *c) -+{ -+ if (c) -+ kref_get(&c->kref); -+ return c; -+} -+EXPORT_SYMBOL_GPL(ssam_controller_get); -+ -+/** -+ * ssam_controller_put() - Decrement reference count of controller. -+ * @c: The controller. -+ */ -+void ssam_controller_put(struct ssam_controller *c) -+{ -+ if (c) -+ kref_put(&c->kref, __ssam_controller_release); -+} -+EXPORT_SYMBOL_GPL(ssam_controller_put); -+ -+/** -+ * ssam_controller_statelock() - Lock the controller against state transitions. -+ * @c: The controller to lock. -+ * -+ * Lock the controller against state transitions. Holding this lock guarantees -+ * that the controller will not transition between states, i.e. if the -+ * controller is in state "started", when this lock has been acquired, it will -+ * remain in this state at least until the lock has been released. -+ * -+ * Multiple clients may concurrently hold this lock. In other words: The -+ * ``statelock`` functions represent the read-lock part of a r/w-semaphore. -+ * Actions causing state transitions of the controller must be executed while -+ * holding the write-part of this r/w-semaphore (see ssam_controller_lock() -+ * and ssam_controller_unlock() for that). -+ * -+ * See ssam_controller_stateunlock() for the corresponding unlock function. -+ */ -+void ssam_controller_statelock(struct ssam_controller *c) -+{ -+ down_read(&c->lock); -+} -+EXPORT_SYMBOL_GPL(ssam_controller_statelock); -+ -+/** -+ * ssam_controller_stateunlock() - Unlock controller state transitions. -+ * @c: The controller to unlock. -+ * -+ * See ssam_controller_statelock() for the corresponding lock function. -+ */ -+void ssam_controller_stateunlock(struct ssam_controller *c) -+{ -+ up_read(&c->lock); -+} -+EXPORT_SYMBOL_GPL(ssam_controller_stateunlock); -+ -+/** -+ * ssam_controller_lock() - Acquire the main controller lock. -+ * @c: The controller to lock. -+ * -+ * This lock must be held for any state transitions, including transition to -+ * suspend/resumed states and during shutdown. See ssam_controller_statelock() -+ * for more details on controller locking. -+ * -+ * See ssam_controller_unlock() for the corresponding unlock function. -+ */ -+void ssam_controller_lock(struct ssam_controller *c) -+{ -+ down_write(&c->lock); -+} -+ -+/* -+ * ssam_controller_unlock() - Release the main controller lock. -+ * @c: The controller to unlock. -+ * -+ * See ssam_controller_lock() for the corresponding lock function. -+ */ -+void ssam_controller_unlock(struct ssam_controller *c) -+{ -+ up_write(&c->lock); -+} -+ -+static void ssam_handle_event(struct ssh_rtl *rtl, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data) -+{ -+ struct ssam_controller *ctrl = to_ssam_controller(rtl, rtl); -+ struct ssam_event_item *item; -+ -+ item = ssam_event_item_alloc(data->len, GFP_KERNEL); -+ if (!item) -+ return; -+ -+ item->rqid = get_unaligned_le16(&cmd->rqid); -+ item->event.target_category = cmd->tc; -+ item->event.target_id = cmd->tid_in; -+ item->event.command_id = cmd->cid; -+ item->event.instance_id = cmd->iid; -+ memcpy(&item->event.data[0], data->ptr, data->len); -+ -+ if (WARN_ON(ssam_cplt_submit_event(&ctrl->cplt, item))) -+ ssam_event_item_free(item); -+} -+ -+static const struct ssh_rtl_ops ssam_rtl_ops = { -+ .handle_event = ssam_handle_event, -+}; -+ -+static bool ssam_notifier_is_empty(struct ssam_controller *ctrl); -+static void ssam_notifier_unregister_all(struct ssam_controller *ctrl); -+ -+#define SSAM_SSH_DSM_REVISION 0 -+ -+/* d5e383e1-d892-4a76-89fc-f6aaae7ed5b5 */ -+static const guid_t SSAM_SSH_DSM_GUID = -+ GUID_INIT(0xd5e383e1, 0xd892, 0x4a76, -+ 0x89, 0xfc, 0xf6, 0xaa, 0xae, 0x7e, 0xd5, 0xb5); -+ -+enum ssh_dsm_fn { -+ SSH_DSM_FN_SSH_POWER_PROFILE = 0x05, -+ SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT = 0x06, -+ SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT = 0x07, -+ SSH_DSM_FN_D3_CLOSES_HANDLE = 0x08, -+ SSH_DSM_FN_SSH_BUFFER_SIZE = 0x09, -+}; -+ -+static int ssam_dsm_get_functions(acpi_handle handle, u64 *funcs) -+{ -+ union acpi_object *obj; -+ u64 mask = 0; -+ int i; -+ -+ *funcs = 0; -+ -+ /* -+ * The _DSM function is only present on newer models. It is not -+ * present on 5th and 6th generation devices (i.e. up to and including -+ * Surface Pro 6, Surface Laptop 2, Surface Book 2). -+ * -+ * If the _DSM is not present, indicate that no function is supported. -+ * This will result in default values being set. -+ */ -+ if (!acpi_has_method(handle, "_DSM")) -+ return 0; -+ -+ obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, -+ SSAM_SSH_DSM_REVISION, 0, NULL, -+ ACPI_TYPE_BUFFER); -+ if (!obj) -+ return -EIO; -+ -+ for (i = 0; i < obj->buffer.length && i < 8; i++) -+ mask |= (((u64)obj->buffer.pointer[i]) << (i * 8)); -+ -+ if (mask & BIT(0)) -+ *funcs = mask; -+ -+ ACPI_FREE(obj); -+ return 0; -+} -+ -+static int ssam_dsm_load_u32(acpi_handle handle, u64 funcs, u64 func, u32 *ret) -+{ -+ union acpi_object *obj; -+ u64 val; -+ -+ if (!(funcs & BIT_ULL(func))) -+ return 0; /* Not supported, leave *ret at its default value */ -+ -+ obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, -+ SSAM_SSH_DSM_REVISION, func, NULL, -+ ACPI_TYPE_INTEGER); -+ if (!obj) -+ return -EIO; -+ -+ val = obj->integer.value; -+ ACPI_FREE(obj); -+ -+ if (val > U32_MAX) -+ return -ERANGE; -+ -+ *ret = val; -+ return 0; -+} -+ -+/** -+ * ssam_controller_caps_load_from_acpi() - Load controller capabilities from -+ * ACPI _DSM. -+ * @handle: The handle of the ACPI controller/SSH device. -+ * @caps: Where to store the capabilities in. -+ * -+ * Initializes the given controller capabilities with default values, then -+ * checks and, if the respective _DSM functions are available, loads the -+ * actual capabilities from the _DSM. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+static -+int ssam_controller_caps_load_from_acpi(acpi_handle handle, -+ struct ssam_controller_caps *caps) -+{ -+ u32 d3_closes_handle = false; -+ u64 funcs; -+ int status; -+ -+ /* Set defaults. */ -+ caps->ssh_power_profile = U32_MAX; -+ caps->screen_on_sleep_idle_timeout = U32_MAX; -+ caps->screen_off_sleep_idle_timeout = U32_MAX; -+ caps->d3_closes_handle = false; -+ caps->ssh_buffer_size = U32_MAX; -+ -+ /* Pre-load supported DSM functions. */ -+ status = ssam_dsm_get_functions(handle, &funcs); -+ if (status) -+ return status; -+ -+ /* Load actual values from ACPI, if present. */ -+ status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_POWER_PROFILE, -+ &caps->ssh_power_profile); -+ if (status) -+ return status; -+ -+ status = ssam_dsm_load_u32(handle, funcs, -+ SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT, -+ &caps->screen_on_sleep_idle_timeout); -+ if (status) -+ return status; -+ -+ status = ssam_dsm_load_u32(handle, funcs, -+ SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT, -+ &caps->screen_off_sleep_idle_timeout); -+ if (status) -+ return status; -+ -+ status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_D3_CLOSES_HANDLE, -+ &d3_closes_handle); -+ if (status) -+ return status; -+ -+ caps->d3_closes_handle = !!d3_closes_handle; -+ -+ status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_BUFFER_SIZE, -+ &caps->ssh_buffer_size); -+ if (status) -+ return status; -+ -+ return 0; -+} -+ -+/** -+ * ssam_controller_init() - Initialize SSAM controller. -+ * @ctrl: The controller to initialize. -+ * @serdev: The serial device representing the underlying data transport. -+ * -+ * Initializes the given controller. Does neither start receiver nor -+ * transmitter threads. After this call, the controller has to be hooked up to -+ * the serdev core separately via &struct serdev_device_ops, relaying calls to -+ * ssam_controller_receive_buf() and ssam_controller_write_wakeup(). Once the -+ * controller has been hooked up, transmitter and receiver threads may be -+ * started via ssam_controller_start(). These setup steps need to be completed -+ * before controller can be used for requests. -+ */ -+int ssam_controller_init(struct ssam_controller *ctrl, -+ struct serdev_device *serdev) -+{ -+ acpi_handle handle = ACPI_HANDLE(&serdev->dev); -+ int status; -+ -+ init_rwsem(&ctrl->lock); -+ kref_init(&ctrl->kref); -+ -+ status = ssam_controller_caps_load_from_acpi(handle, &ctrl->caps); -+ if (status) -+ return status; -+ -+ dev_dbg(&serdev->dev, -+ "device capabilities:\n" -+ " ssh_power_profile: %u\n" -+ " ssh_buffer_size: %u\n" -+ " screen_on_sleep_idle_timeout: %u\n" -+ " screen_off_sleep_idle_timeout: %u\n" -+ " d3_closes_handle: %u\n", -+ ctrl->caps.ssh_power_profile, -+ ctrl->caps.ssh_buffer_size, -+ ctrl->caps.screen_on_sleep_idle_timeout, -+ ctrl->caps.screen_off_sleep_idle_timeout, -+ ctrl->caps.d3_closes_handle); -+ -+ ssh_seq_reset(&ctrl->counter.seq); -+ ssh_rqid_reset(&ctrl->counter.rqid); -+ -+ /* Initialize event/request completion system. */ -+ status = ssam_cplt_init(&ctrl->cplt, &serdev->dev); -+ if (status) -+ return status; -+ -+ /* Initialize request and packet transport layers. */ -+ status = ssh_rtl_init(&ctrl->rtl, serdev, &ssam_rtl_ops); -+ if (status) { -+ ssam_cplt_destroy(&ctrl->cplt); -+ return status; -+ } -+ -+ /* -+ * Set state via write_once even though we expect to be in an -+ * exclusive context, due to smoke-testing in -+ * ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_INITIALIZED); -+ return 0; -+} -+ -+/** -+ * ssam_controller_start() - Start the receiver and transmitter threads of the -+ * controller. -+ * @ctrl: The controller. -+ * -+ * Note: When this function is called, the controller should be properly -+ * hooked up to the serdev core via &struct serdev_device_ops. Please refer -+ * to ssam_controller_init() for more details on controller initialization. -+ * -+ * This function must be called with the main controller lock held (i.e. by -+ * calling ssam_controller_lock()). -+ */ -+int ssam_controller_start(struct ssam_controller *ctrl) -+{ -+ int status; -+ -+ lockdep_assert_held_write(&ctrl->lock); -+ -+ if (ctrl->state != SSAM_CONTROLLER_INITIALIZED) -+ return -EINVAL; -+ -+ status = ssh_rtl_start(&ctrl->rtl); -+ if (status) -+ return status; -+ -+ /* -+ * Set state via write_once even though we expect to be locked/in an -+ * exclusive context, due to smoke-testing in -+ * ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED); -+ return 0; -+} -+ -+/* -+ * SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT - Timeout for flushing requests during -+ * shutdown. -+ * -+ * Chosen to be larger than one full request timeout, including packets timing -+ * out. This value should give ample time to complete any outstanding requests -+ * during normal operation and account for the odd package timeout. -+ */ -+#define SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT msecs_to_jiffies(5000) -+ -+/** -+ * ssam_controller_shutdown() - Shut down the controller. -+ * @ctrl: The controller. -+ * -+ * Shuts down the controller by flushing all pending requests and stopping the -+ * transmitter and receiver threads. All requests submitted after this call -+ * will fail with %-ESHUTDOWN. While it is discouraged to do so, this function -+ * is safe to use in parallel with ongoing request submission. -+ * -+ * In the course of this shutdown procedure, all currently registered -+ * notifiers will be unregistered. It is, however, strongly recommended to not -+ * rely on this behavior, and instead the party registering the notifier -+ * should unregister it before the controller gets shut down, e.g. via the -+ * SSAM bus which guarantees client devices to be removed before a shutdown. -+ * -+ * Note that events may still be pending after this call, but, due to the -+ * notifiers being unregistered, these events will be dropped when the -+ * controller is subsequently destroyed via ssam_controller_destroy(). -+ * -+ * This function must be called with the main controller lock held (i.e. by -+ * calling ssam_controller_lock()). -+ */ -+void ssam_controller_shutdown(struct ssam_controller *ctrl) -+{ -+ enum ssam_controller_state s = ctrl->state; -+ int status; -+ -+ lockdep_assert_held_write(&ctrl->lock); -+ -+ if (s == SSAM_CONTROLLER_UNINITIALIZED || s == SSAM_CONTROLLER_STOPPED) -+ return; -+ -+ /* -+ * Try to flush pending events and requests while everything still -+ * works. Note: There may still be packets and/or requests in the -+ * system after this call (e.g. via control packets submitted by the -+ * packet transport layer or flush timeout / failure, ...). Those will -+ * be handled with the ssh_rtl_shutdown() call below. -+ */ -+ status = ssh_rtl_flush(&ctrl->rtl, SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT); -+ if (status) { -+ ssam_err(ctrl, "failed to flush request transport layer: %d\n", -+ status); -+ } -+ -+ /* Try to flush all currently completing requests and events. */ -+ ssam_cplt_flush(&ctrl->cplt); -+ -+ /* -+ * We expect all notifiers to have been removed by the respective client -+ * driver that set them up at this point. If this warning occurs, some -+ * client driver has not done that... -+ */ -+ WARN_ON(!ssam_notifier_is_empty(ctrl)); -+ -+ /* -+ * Nevertheless, we should still take care of drivers that don't behave -+ * well. Thus disable all enabled events, unregister all notifiers. -+ */ -+ ssam_notifier_unregister_all(ctrl); -+ -+ /* -+ * Cancel remaining requests. Ensure no new ones can be queued and stop -+ * threads. -+ */ -+ ssh_rtl_shutdown(&ctrl->rtl); -+ -+ /* -+ * Set state via write_once even though we expect to be locked/in an -+ * exclusive context, due to smoke-testing in -+ * ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STOPPED); -+ ctrl->rtl.ptl.serdev = NULL; -+} -+ -+/** -+ * ssam_controller_destroy() - Destroy the controller and free its resources. -+ * @ctrl: The controller. -+ * -+ * Ensures that all resources associated with the controller get freed. This -+ * function should only be called after the controller has been stopped via -+ * ssam_controller_shutdown(). In general, this function should not be called -+ * directly. The only valid place to call this function directly is during -+ * initialization, before the controller has been fully initialized and passed -+ * to other processes. This function is called automatically when the -+ * reference count of the controller reaches zero. -+ * -+ * This function must be called with the main controller lock held (i.e. by -+ * calling ssam_controller_lock()). -+ */ -+void ssam_controller_destroy(struct ssam_controller *ctrl) -+{ -+ lockdep_assert_held_write(&ctrl->lock); -+ -+ if (ctrl->state == SSAM_CONTROLLER_UNINITIALIZED) -+ return; -+ -+ WARN_ON(ctrl->state != SSAM_CONTROLLER_STOPPED); -+ -+ /* -+ * Note: New events could still have been received after the previous -+ * flush in ssam_controller_shutdown, before the request transport layer -+ * has been shut down. At this point, after the shutdown, we can be sure -+ * that no new events will be queued. The call to ssam_cplt_destroy will -+ * ensure that those remaining are being completed and freed. -+ */ -+ -+ /* Actually free resources. */ -+ ssam_cplt_destroy(&ctrl->cplt); -+ ssh_rtl_destroy(&ctrl->rtl); -+ -+ /* -+ * Set state via write_once even though we expect to be locked/in an -+ * exclusive context, due to smoke-testing in -+ * ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_UNINITIALIZED); -+} -+ -+/** -+ * ssam_controller_suspend() - Suspend the controller. -+ * @ctrl: The controller to suspend. -+ * -+ * Marks the controller as suspended. Note that display-off and D0-exit -+ * notifications have to be sent manually before transitioning the controller -+ * into the suspended state via this function. -+ * -+ * See ssam_controller_resume() for the corresponding resume function. -+ * -+ * Return: Returns %-EINVAL if the controller is currently not in the -+ * "started" state. -+ */ -+int ssam_controller_suspend(struct ssam_controller *ctrl) -+{ -+ ssam_controller_lock(ctrl); -+ -+ if (ctrl->state != SSAM_CONTROLLER_STARTED) { -+ ssam_controller_unlock(ctrl); -+ return -EINVAL; -+ } -+ -+ ssam_dbg(ctrl, "pm: suspending controller\n"); -+ -+ /* -+ * Set state via write_once even though we're locked, due to -+ * smoke-testing in ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_SUSPENDED); -+ -+ ssam_controller_unlock(ctrl); -+ return 0; -+} -+ -+/** -+ * ssam_controller_resume() - Resume the controller from suspend. -+ * @ctrl: The controller to resume. -+ * -+ * Resume the controller from the suspended state it was put into via -+ * ssam_controller_suspend(). This function does not issue display-on and -+ * D0-entry notifications. If required, those have to be sent manually after -+ * this call. -+ * -+ * Return: Returns %-EINVAL if the controller is currently not suspended. -+ */ -+int ssam_controller_resume(struct ssam_controller *ctrl) -+{ -+ ssam_controller_lock(ctrl); -+ -+ if (ctrl->state != SSAM_CONTROLLER_SUSPENDED) { -+ ssam_controller_unlock(ctrl); -+ return -EINVAL; -+ } -+ -+ ssam_dbg(ctrl, "pm: resuming controller\n"); -+ -+ /* -+ * Set state via write_once even though we're locked, due to -+ * smoke-testing in ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED); -+ -+ ssam_controller_unlock(ctrl); -+ return 0; -+} -+ -+ -+/* -- Top-level request interface ------------------------------------------- */ -+ -+/** -+ * ssam_request_write_data() - Construct and write SAM request message to -+ * buffer. -+ * @buf: The buffer to write the data to. -+ * @ctrl: The controller via which the request will be sent. -+ * @spec: The request data and specification. -+ * -+ * Constructs a SAM/SSH request message and writes it to the provided buffer. -+ * The request and transport counters, specifically RQID and SEQ, will be set -+ * in this call. These counters are obtained from the controller. It is thus -+ * only valid to send the resulting message via the controller specified here. -+ * -+ * For calculation of the required buffer size, refer to the -+ * SSH_COMMAND_MESSAGE_LENGTH() macro. -+ * -+ * Return: Returns the number of bytes used in the buffer on success. Returns -+ * %-EINVAL if the payload length provided in the request specification is too -+ * large (larger than %SSH_COMMAND_MAX_PAYLOAD_SIZE) or if the provided buffer -+ * is too small. -+ */ -+ssize_t ssam_request_write_data(struct ssam_span *buf, -+ struct ssam_controller *ctrl, -+ const struct ssam_request *spec) -+{ -+ struct msgbuf msgb; -+ u16 rqid; -+ u8 seq; -+ -+ if (spec->length > SSH_COMMAND_MAX_PAYLOAD_SIZE) -+ return -EINVAL; -+ -+ if (SSH_COMMAND_MESSAGE_LENGTH(spec->length) > buf->len) -+ return -EINVAL; -+ -+ msgb_init(&msgb, buf->ptr, buf->len); -+ seq = ssh_seq_next(&ctrl->counter.seq); -+ rqid = ssh_rqid_next(&ctrl->counter.rqid); -+ msgb_push_cmd(&msgb, seq, rqid, spec); -+ -+ return msgb_bytes_used(&msgb); -+} -+EXPORT_SYMBOL_GPL(ssam_request_write_data); -+ -+static void ssam_request_sync_complete(struct ssh_request *rqst, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data, int status) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ struct ssam_request_sync *r; -+ -+ r = container_of(rqst, struct ssam_request_sync, base); -+ r->status = status; -+ -+ if (r->resp) -+ r->resp->length = 0; -+ -+ if (status) { -+ rtl_dbg_cond(rtl, "rsp: request failed: %d\n", status); -+ return; -+ } -+ -+ if (!data) /* Handle requests without a response. */ -+ return; -+ -+ if (!r->resp || !r->resp->pointer) { -+ if (data->len) -+ rtl_warn(rtl, "rsp: no response buffer provided, dropping data\n"); -+ return; -+ } -+ -+ if (data->len > r->resp->capacity) { -+ rtl_err(rtl, -+ "rsp: response buffer too small, capacity: %zu bytes, got: %zu bytes\n", -+ r->resp->capacity, data->len); -+ r->status = -ENOSPC; -+ return; -+ } -+ -+ r->resp->length = data->len; -+ memcpy(r->resp->pointer, data->ptr, data->len); -+} -+ -+static void ssam_request_sync_release(struct ssh_request *rqst) -+{ -+ complete_all(&container_of(rqst, struct ssam_request_sync, base)->comp); -+} -+ -+static const struct ssh_request_ops ssam_request_sync_ops = { -+ .release = ssam_request_sync_release, -+ .complete = ssam_request_sync_complete, -+}; -+ -+/** -+ * ssam_request_sync_alloc() - Allocate a synchronous request. -+ * @payload_len: The length of the request payload. -+ * @flags: Flags used for allocation. -+ * @rqst: Where to store the pointer to the allocated request. -+ * @buffer: Where to store the buffer descriptor for the message buffer of -+ * the request. -+ * -+ * Allocates a synchronous request with corresponding message buffer. The -+ * request still needs to be initialized ssam_request_sync_init() before -+ * it can be submitted, and the message buffer data must still be set to the -+ * returned buffer via ssam_request_sync_set_data() after it has been filled, -+ * if need be with adjusted message length. -+ * -+ * After use, the request and its corresponding message buffer should be freed -+ * via ssam_request_sync_free(). The buffer must not be freed separately. -+ * -+ * Return: Returns zero on success, %-ENOMEM if the request could not be -+ * allocated. -+ */ -+int ssam_request_sync_alloc(size_t payload_len, gfp_t flags, -+ struct ssam_request_sync **rqst, -+ struct ssam_span *buffer) -+{ -+ size_t msglen = SSH_COMMAND_MESSAGE_LENGTH(payload_len); -+ -+ *rqst = kzalloc(sizeof(**rqst) + msglen, flags); -+ if (!*rqst) -+ return -ENOMEM; -+ -+ buffer->ptr = (u8 *)(*rqst + 1); -+ buffer->len = msglen; -+ -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_alloc); -+ -+/** -+ * ssam_request_sync_free() - Free a synchronous request. -+ * @rqst: The request to be freed. -+ * -+ * Free a synchronous request and its corresponding buffer allocated with -+ * ssam_request_sync_alloc(). Do not use for requests allocated on the stack -+ * or via any other function. -+ * -+ * Warning: The caller must ensure that the request is not in use any more. -+ * I.e. the caller must ensure that it has the only reference to the request -+ * and the request is not currently pending. This means that the caller has -+ * either never submitted the request, request submission has failed, or the -+ * caller has waited until the submitted request has been completed via -+ * ssam_request_sync_wait(). -+ */ -+void ssam_request_sync_free(struct ssam_request_sync *rqst) -+{ -+ kfree(rqst); -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_free); -+ -+/** -+ * ssam_request_sync_init() - Initialize a synchronous request struct. -+ * @rqst: The request to initialize. -+ * @flags: The request flags. -+ * -+ * Initializes the given request struct. Does not initialize the request -+ * message data. This has to be done explicitly after this call via -+ * ssam_request_sync_set_data() and the actual message data has to be written -+ * via ssam_request_write_data(). -+ * -+ * Return: Returns zero on success or %-EINVAL if the given flags are invalid. -+ */ -+int ssam_request_sync_init(struct ssam_request_sync *rqst, -+ enum ssam_request_flags flags) -+{ -+ int status; -+ -+ status = ssh_request_init(&rqst->base, flags, &ssam_request_sync_ops); -+ if (status) -+ return status; -+ -+ init_completion(&rqst->comp); -+ rqst->resp = NULL; -+ rqst->status = 0; -+ -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_init); -+ -+/** -+ * ssam_request_sync_submit() - Submit a synchronous request. -+ * @ctrl: The controller with which to submit the request. -+ * @rqst: The request to submit. -+ * -+ * Submit a synchronous request. The request has to be initialized and -+ * properly set up, including response buffer (may be %NULL if no response is -+ * expected) and command message data. This function does not wait for the -+ * request to be completed. -+ * -+ * If this function succeeds, ssam_request_sync_wait() must be used to ensure -+ * that the request has been completed before the response data can be -+ * accessed and/or the request can be freed. On failure, the request may -+ * immediately be freed. -+ * -+ * This function may only be used if the controller is active, i.e. has been -+ * initialized and not suspended. -+ */ -+int ssam_request_sync_submit(struct ssam_controller *ctrl, -+ struct ssam_request_sync *rqst) -+{ -+ int status; -+ -+ /* -+ * This is only a superficial check. In general, the caller needs to -+ * ensure that the controller is initialized and is not (and does not -+ * get) suspended during use, i.e. until the request has been completed -+ * (if _absolutely_ necessary, by use of ssam_controller_statelock/ -+ * ssam_controller_stateunlock, but something like ssam_client_link -+ * should be preferred as this needs to last until the request has been -+ * completed). -+ * -+ * Note that it is actually safe to use this function while the -+ * controller is in the process of being shut down (as ssh_rtl_submit -+ * is safe with regards to this), but it is generally discouraged to do -+ * so. -+ */ -+ if (WARN_ON(READ_ONCE(ctrl->state) != SSAM_CONTROLLER_STARTED)) { -+ ssh_request_put(&rqst->base); -+ return -ENODEV; -+ } -+ -+ status = ssh_rtl_submit(&ctrl->rtl, &rqst->base); -+ ssh_request_put(&rqst->base); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_submit); -+ -+/** -+ * ssam_request_sync() - Execute a synchronous request. -+ * @ctrl: The controller via which the request will be submitted. -+ * @spec: The request specification and payload. -+ * @rsp: The response buffer. -+ * -+ * Allocates a synchronous request with its message data buffer on the heap -+ * via ssam_request_sync_alloc(), fully initializes it via the provided -+ * request specification, submits it, and finally waits for its completion -+ * before freeing it and returning its status. -+ * -+ * Return: Returns the status of the request or any failure during setup. -+ */ -+int ssam_request_sync(struct ssam_controller *ctrl, -+ const struct ssam_request *spec, -+ struct ssam_response *rsp) -+{ -+ struct ssam_request_sync *rqst; -+ struct ssam_span buf; -+ ssize_t len; -+ int status; -+ -+ status = ssam_request_sync_alloc(spec->length, GFP_KERNEL, &rqst, &buf); -+ if (status) -+ return status; -+ -+ status = ssam_request_sync_init(rqst, spec->flags); -+ if (status) -+ return status; -+ -+ ssam_request_sync_set_resp(rqst, rsp); -+ -+ len = ssam_request_write_data(&buf, ctrl, spec); -+ if (len < 0) { -+ ssam_request_sync_free(rqst); -+ return len; -+ } -+ -+ ssam_request_sync_set_data(rqst, buf.ptr, len); -+ -+ status = ssam_request_sync_submit(ctrl, rqst); -+ if (!status) -+ status = ssam_request_sync_wait(rqst); -+ -+ ssam_request_sync_free(rqst); -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync); -+ -+/** -+ * ssam_request_sync_with_buffer() - Execute a synchronous request with the -+ * provided buffer as back-end for the message buffer. -+ * @ctrl: The controller via which the request will be submitted. -+ * @spec: The request specification and payload. -+ * @rsp: The response buffer. -+ * @buf: The buffer for the request message data. -+ * -+ * Allocates a synchronous request struct on the stack, fully initializes it -+ * using the provided buffer as message data buffer, submits it, and then -+ * waits for its completion before returning its status. The -+ * SSH_COMMAND_MESSAGE_LENGTH() macro can be used to compute the required -+ * message buffer size. -+ * -+ * This function does essentially the same as ssam_request_sync(), but instead -+ * of dynamically allocating the request and message data buffer, it uses the -+ * provided message data buffer and stores the (small) request struct on the -+ * heap. -+ * -+ * Return: Returns the status of the request or any failure during setup. -+ */ -+int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, -+ const struct ssam_request *spec, -+ struct ssam_response *rsp, -+ struct ssam_span *buf) -+{ -+ struct ssam_request_sync rqst; -+ ssize_t len; -+ int status; -+ -+ status = ssam_request_sync_init(&rqst, spec->flags); -+ if (status) -+ return status; -+ -+ ssam_request_sync_set_resp(&rqst, rsp); -+ -+ len = ssam_request_write_data(buf, ctrl, spec); -+ if (len < 0) -+ return len; -+ -+ ssam_request_sync_set_data(&rqst, buf->ptr, len); -+ -+ status = ssam_request_sync_submit(ctrl, &rqst); -+ if (!status) -+ status = ssam_request_sync_wait(&rqst); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer); -+ -+ -+/* -- Internal SAM requests. ------------------------------------------------ */ -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x13, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x15, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x16, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x33, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x34, -+ .instance_id = 0x00, -+}); -+ -+/** -+ * struct ssh_notification_params - Command payload to enable/disable SSH -+ * notifications. -+ * @target_category: The target category for which notifications should be -+ * enabled/disabled. -+ * @flags: Flags determining how notifications are being sent. -+ * @request_id: The request ID that is used to send these notifications. -+ * @instance_id: The specific instance in the given target category for -+ * which notifications should be enabled. -+ */ -+struct ssh_notification_params { -+ u8 target_category; -+ u8 flags; -+ __le16 request_id; -+ u8 instance_id; -+} __packed; -+ -+static_assert(sizeof(struct ssh_notification_params) == 5); -+ -+static int __ssam_ssh_event_request(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, u8 cid, -+ struct ssam_event_id id, u8 flags) -+{ -+ struct ssh_notification_params params; -+ struct ssam_request rqst; -+ struct ssam_response result; -+ int status; -+ -+ u16 rqid = ssh_tc_to_rqid(id.target_category); -+ u8 buf = 0; -+ -+ /* Only allow RQIDs that lie within the event spectrum. */ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ params.target_category = id.target_category; -+ params.instance_id = id.instance; -+ params.flags = flags; -+ put_unaligned_le16(rqid, ¶ms.request_id); -+ -+ rqst.target_category = reg.target_category; -+ rqst.target_id = reg.target_id; -+ rqst.command_id = cid; -+ rqst.instance_id = 0x00; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(params); -+ rqst.payload = (u8 *)¶ms; -+ -+ result.capacity = sizeof(buf); -+ result.length = 0; -+ result.pointer = &buf; -+ -+ status = ssam_retry(ssam_request_sync_onstack, ctrl, &rqst, &result, -+ sizeof(params)); -+ -+ return status < 0 ? status : buf; -+} -+ -+/** -+ * ssam_ssh_event_enable() - Enable SSH event. -+ * @ctrl: The controller for which to enable the event. -+ * @reg: The event registry describing what request to use for enabling and -+ * disabling the event. -+ * @id: The event identifier. -+ * @flags: The event flags. -+ * -+ * Enables the specified event on the EC. This function does not manage -+ * reference counting of enabled events and is basically only a wrapper for -+ * the raw EC request. If the specified event is already enabled, the EC will -+ * ignore this request. -+ * -+ * Return: Returns the status of the executed SAM request (zero on success and -+ * negative on direct failure) or %-EPROTO if the request response indicates a -+ * failure. -+ */ -+static int ssam_ssh_event_enable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags) -+{ -+ int status; -+ -+ status = __ssam_ssh_event_request(ctrl, reg, reg.cid_enable, id, flags); -+ -+ if (status < 0 && status != -EINVAL) { -+ ssam_err(ctrl, -+ "failed to enable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n", -+ id.target_category, id.instance, reg.target_category); -+ } -+ -+ if (status > 0) { -+ ssam_err(ctrl, -+ "unexpected result while enabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n", -+ status, id.target_category, id.instance, reg.target_category); -+ return -EPROTO; -+ } -+ -+ return status; -+} -+ -+/** -+ * ssam_ssh_event_disable() - Disable SSH event. -+ * @ctrl: The controller for which to disable the event. -+ * @reg: The event registry describing what request to use for enabling and -+ * disabling the event (must be same as used when enabling the event). -+ * @id: The event identifier. -+ * @flags: The event flags (likely ignored for disabling of events). -+ * -+ * Disables the specified event on the EC. This function does not manage -+ * reference counting of enabled events and is basically only a wrapper for -+ * the raw EC request. If the specified event is already disabled, the EC will -+ * ignore this request. -+ * -+ * Return: Returns the status of the executed SAM request (zero on success and -+ * negative on direct failure) or %-EPROTO if the request response indicates a -+ * failure. -+ */ -+static int ssam_ssh_event_disable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags) -+{ -+ int status; -+ -+ status = __ssam_ssh_event_request(ctrl, reg, reg.cid_disable, id, flags); -+ -+ if (status < 0 && status != -EINVAL) { -+ ssam_err(ctrl, -+ "failed to disable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n", -+ id.target_category, id.instance, reg.target_category); -+ } -+ -+ if (status > 0) { -+ ssam_err(ctrl, -+ "unexpected result while disabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n", -+ status, id.target_category, id.instance, reg.target_category); -+ return -EPROTO; -+ } -+ -+ return status; -+} -+ -+ -+/* -- Wrappers for internal SAM requests. ----------------------------------- */ -+ -+/** -+ * ssam_get_firmware_version() - Get the SAM/EC firmware version. -+ * @ctrl: The controller. -+ * @version: Where to store the version number. -+ * -+ * Return: Returns zero on success or the status of the executed SAM request -+ * if that request failed. -+ */ -+int ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version) -+{ -+ __le32 __version; -+ int status; -+ -+ status = ssam_retry(ssam_ssh_get_firmware_version, ctrl, &__version); -+ if (status) -+ return status; -+ -+ *version = le32_to_cpu(__version); -+ return 0; -+} -+ -+/** -+ * ssam_ctrl_notif_display_off() - Notify EC that the display has been turned -+ * off. -+ * @ctrl: The controller. -+ * -+ * Notify the EC that the display has been turned off and the driver may enter -+ * a lower-power state. This will prevent events from being sent directly. -+ * Rather, the EC signals an event by pulling the wakeup GPIO high for as long -+ * as there are pending events. The events then need to be manually released, -+ * one by one, via the GPIO callback request. All pending events accumulated -+ * during this state can also be released by issuing the display-on -+ * notification, e.g. via ssam_ctrl_notif_display_on(), which will also reset -+ * the GPIO. -+ * -+ * On some devices, specifically ones with an integrated keyboard, the keyboard -+ * backlight will be turned off by this call. -+ * -+ * This function will only send the display-off notification command if -+ * display notifications are supported by the EC. Currently all known devices -+ * support these notifications. -+ * -+ * Use ssam_ctrl_notif_display_on() to reverse the effects of this function. -+ * -+ * Return: Returns zero on success or if no request has been executed, the -+ * status of the executed SAM request if that request failed, or %-EPROTO if -+ * an unexpected response has been received. -+ */ -+int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl) -+{ -+ int status; -+ u8 response; -+ -+ ssam_dbg(ctrl, "pm: notifying display off\n"); -+ -+ status = ssam_retry(ssam_ssh_notif_display_off, ctrl, &response); -+ if (status) -+ return status; -+ -+ if (response != 0) { -+ ssam_err(ctrl, "unexpected response from display-off notification: %#04x\n", -+ response); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_ctrl_notif_display_on() - Notify EC that the display has been turned on. -+ * @ctrl: The controller. -+ * -+ * Notify the EC that the display has been turned back on and the driver has -+ * exited its lower-power state. This notification is the counterpart to the -+ * display-off notification sent via ssam_ctrl_notif_display_off() and will -+ * reverse its effects, including resetting events to their default behavior. -+ * -+ * This function will only send the display-on notification command if display -+ * notifications are supported by the EC. Currently all known devices support -+ * these notifications. -+ * -+ * See ssam_ctrl_notif_display_off() for more details. -+ * -+ * Return: Returns zero on success or if no request has been executed, the -+ * status of the executed SAM request if that request failed, or %-EPROTO if -+ * an unexpected response has been received. -+ */ -+int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl) -+{ -+ int status; -+ u8 response; -+ -+ ssam_dbg(ctrl, "pm: notifying display on\n"); -+ -+ status = ssam_retry(ssam_ssh_notif_display_on, ctrl, &response); -+ if (status) -+ return status; -+ -+ if (response != 0) { -+ ssam_err(ctrl, "unexpected response from display-on notification: %#04x\n", -+ response); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_ctrl_notif_d0_exit() - Notify EC that the driver/device exits the D0 -+ * power state. -+ * @ctrl: The controller -+ * -+ * Notifies the EC that the driver prepares to exit the D0 power state in -+ * favor of a lower-power state. Exact effects of this function related to the -+ * EC are currently unknown. -+ * -+ * This function will only send the D0-exit notification command if D0-state -+ * notifications are supported by the EC. Only newer Surface generations -+ * support these notifications. -+ * -+ * Use ssam_ctrl_notif_d0_entry() to reverse the effects of this function. -+ * -+ * Return: Returns zero on success or if no request has been executed, the -+ * status of the executed SAM request if that request failed, or %-EPROTO if -+ * an unexpected response has been received. -+ */ -+int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl) -+{ -+ int status; -+ u8 response; -+ -+ if (!ctrl->caps.d3_closes_handle) -+ return 0; -+ -+ ssam_dbg(ctrl, "pm: notifying D0 exit\n"); -+ -+ status = ssam_retry(ssam_ssh_notif_d0_exit, ctrl, &response); -+ if (status) -+ return status; -+ -+ if (response != 0) { -+ ssam_err(ctrl, "unexpected response from D0-exit notification: %#04x\n", -+ response); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_ctrl_notif_d0_entry() - Notify EC that the driver/device enters the D0 -+ * power state. -+ * @ctrl: The controller -+ * -+ * Notifies the EC that the driver has exited a lower-power state and entered -+ * the D0 power state. Exact effects of this function related to the EC are -+ * currently unknown. -+ * -+ * This function will only send the D0-entry notification command if D0-state -+ * notifications are supported by the EC. Only newer Surface generations -+ * support these notifications. -+ * -+ * See ssam_ctrl_notif_d0_exit() for more details. -+ * -+ * Return: Returns zero on success or if no request has been executed, the -+ * status of the executed SAM request if that request failed, or %-EPROTO if -+ * an unexpected response has been received. -+ */ -+int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) -+{ -+ int status; -+ u8 response; -+ -+ if (!ctrl->caps.d3_closes_handle) -+ return 0; -+ -+ ssam_dbg(ctrl, "pm: notifying D0 entry\n"); -+ -+ status = ssam_retry(ssam_ssh_notif_d0_entry, ctrl, &response); -+ if (status) -+ return status; -+ -+ if (response != 0) { -+ ssam_err(ctrl, "unexpected response from D0-entry notification: %#04x\n", -+ response); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+ -+/* -- Top-level event registry interface. ----------------------------------- */ -+ -+/** -+ * ssam_nf_refcount_enable() - Enable event for reference count entry if it has -+ * not already been enabled. -+ * @ctrl: The controller to enable the event on. -+ * @entry: The reference count entry for the event to be enabled. -+ * @flags: The flags used for enabling the event on the EC. -+ * -+ * Enable the event associated with the given reference count entry if the -+ * reference count equals one, i.e. the event has not previously been enabled. -+ * If the event has already been enabled (i.e. reference count not equal to -+ * one), check that the flags used for enabling match and warn about this if -+ * they do not. -+ * -+ * This does not modify the reference count itself, which is done with -+ * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ * -+ * Return: Returns zero on success. If the event is enabled by this call, -+ * returns the status of the event-enable EC command. -+ */ -+static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, -+ struct ssam_nf_refcount_entry *entry, u8 flags) -+{ -+ const struct ssam_event_registry reg = entry->key.reg; -+ const struct ssam_event_id id = entry->key.id; -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ int status; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ reg.target_category, id.target_category, id.instance, entry->refcount); -+ -+ if (entry->refcount == 1) { -+ status = ssam_ssh_event_enable(ctrl, reg, id, flags); -+ if (status) -+ return status; -+ -+ entry->flags = flags; -+ -+ } else if (entry->flags != flags) { -+ ssam_warn(ctrl, -+ "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -+ flags, entry->flags, reg.target_category, id.target_category, -+ id.instance); -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is -+ * no longer in use and free the corresponding entry. -+ * @ctrl: The controller to disable the event on. -+ * @entry: The reference count entry for the event to be disabled. -+ * @flags: The flags used for enabling the event on the EC. -+ * -+ * If the reference count equals zero, i.e. the event is no longer requested by -+ * any client, the event will be disabled and the corresponding reference count -+ * entry freed. The reference count entry must not be used any more after a -+ * call to this function. -+ * -+ * Also checks if the flags used for disabling the event match the flags used -+ * for enabling the event and warns if they do not (regardless of reference -+ * count). -+ * -+ * This does not modify the reference count itself, which is done with -+ * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ * -+ * Return: Returns zero on success. If the event is disabled by this call, -+ * returns the status of the event-enable EC command. -+ */ -+static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, -+ struct ssam_nf_refcount_entry *entry, u8 flags) -+{ -+ const struct ssam_event_registry reg = entry->key.reg; -+ const struct ssam_event_id id = entry->key.id; -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ int status = 0; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ reg.target_category, id.target_category, id.instance, entry->refcount); -+ -+ if (entry->flags != flags) { -+ ssam_warn(ctrl, -+ "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -+ flags, entry->flags, reg.target_category, id.target_category, -+ id.instance); -+ } -+ -+ if (entry->refcount == 0) { -+ status = ssam_ssh_event_disable(ctrl, reg, id, flags); -+ kfree(entry); -+ } -+ -+ return status; -+} -+ -+/** -+ * ssam_notifier_register() - Register an event notifier. -+ * @ctrl: The controller to register the notifier on. -+ * @n: The event notifier to register. -+ * -+ * Register an event notifier. Increment the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the event is not -+ * marked as an observer and is currently not enabled, it will be enabled -+ * during this call. If the notifier is marked as an observer, no attempt will -+ * be made at enabling any event and no reference count will be modified. -+ * -+ * Notifiers marked as observers do not need to be associated with one specific -+ * event, i.e. as long as no event matching is performed, only the event target -+ * category needs to be set. -+ * -+ * Return: Returns zero on success, %-ENOSPC if there have already been -+ * %INT_MAX notifiers for the event ID/type associated with the notifier block -+ * registered, %-ENOMEM if the corresponding event entry could not be -+ * allocated. If this is the first time that a notifier block is registered -+ * for the specific associated event, returns the status of the event-enable -+ * EC-command. -+ */ -+int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n) -+{ -+ u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); -+ struct ssam_nf_refcount_entry *entry = NULL; -+ struct ssam_nf_head *nf_head; -+ struct ssam_nf *nf; -+ int status; -+ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ nf = &ctrl->cplt.event.notif; -+ nf_head = &nf->head[ssh_rqid_to_event(rqid)]; -+ -+ mutex_lock(&nf->lock); -+ -+ if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { -+ entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); -+ if (IS_ERR(entry)) { -+ mutex_unlock(&nf->lock); -+ return PTR_ERR(entry); -+ } -+ } -+ -+ status = ssam_nfblk_insert(nf_head, &n->base); -+ if (status) { -+ if (entry) -+ ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); -+ -+ mutex_unlock(&nf->lock); -+ return status; -+ } -+ -+ if (entry) { -+ status = ssam_nf_refcount_enable(ctrl, entry, n->event.flags); -+ if (status) { -+ ssam_nfblk_remove(&n->base); -+ ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); -+ mutex_unlock(&nf->lock); -+ synchronize_srcu(&nf_head->srcu); -+ return status; -+ } -+ } -+ -+ mutex_unlock(&nf->lock); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_notifier_register); -+ -+/** -+ * ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) -+{ -+ u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); -+ struct ssam_nf_refcount_entry *entry; -+ struct ssam_nf_head *nf_head; -+ struct ssam_nf *nf; -+ int status = 0; -+ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ nf = &ctrl->cplt.event.notif; -+ nf_head = &nf->head[ssh_rqid_to_event(rqid)]; -+ -+ mutex_lock(&nf->lock); -+ -+ if (!ssam_nfblk_find(nf_head, &n->base)) { -+ mutex_unlock(&nf->lock); -+ return -ENOENT; -+ } -+ -+ /* -+ * If this is an observer notifier, do not attempt to disable the -+ * event, just remove it. -+ */ -+ if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { -+ entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); -+ if (WARN_ON(!entry)) { -+ /* -+ * If this does not return an entry, there's a logic -+ * error somewhere: The notifier block is registered, -+ * but the event refcount entry is not there. Remove -+ * the notifier block anyways. -+ */ -+ status = -ENOENT; -+ goto remove; -+ } -+ -+ status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); -+ } -+ -+remove: -+ ssam_nfblk_remove(&n->base); -+ mutex_unlock(&nf->lock); -+ synchronize_srcu(&nf_head->srcu); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_notifier_unregister); -+ -+/** -+ * ssam_controller_event_enable() - Enable the specified event. -+ * @ctrl: The controller to enable the event for. -+ * @reg: The event registry to use for enabling the event. -+ * @id: The event ID specifying the event to be enabled. -+ * @flags: The SAM event flags used for enabling the event. -+ * -+ * Increment the event reference count of the specified event. If the event has -+ * not been enabled previously, it will be enabled by this call. -+ * -+ * Note: In general, ssam_notifier_register() with a non-observer notifier -+ * should be preferred for enabling/disabling events, as this will guarantee -+ * proper ordering and event forwarding in case of errors during event -+ * enabling/disabling. -+ * -+ * Return: Returns zero on success, %-ENOSPC if the reference count for the -+ * specified event has reached its maximum, %-ENOMEM if the corresponding event -+ * entry could not be allocated. If this is the first time that this event has -+ * been enabled (i.e. the reference count was incremented from zero to one by -+ * this call), returns the status of the event-enable EC-command. -+ */ -+int ssam_controller_event_enable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags) -+{ -+ u16 rqid = ssh_tc_to_rqid(id.target_category); -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct ssam_nf_refcount_entry *entry; -+ int status; -+ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ mutex_lock(&nf->lock); -+ -+ entry = ssam_nf_refcount_inc(nf, reg, id); -+ if (IS_ERR(entry)) { -+ mutex_unlock(&nf->lock); -+ return PTR_ERR(entry); -+ } -+ -+ status = ssam_nf_refcount_enable(ctrl, entry, flags); -+ if (status) { -+ ssam_nf_refcount_dec_free(nf, reg, id); -+ mutex_unlock(&nf->lock); -+ return status; -+ } -+ -+ mutex_unlock(&nf->lock); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_controller_event_enable); -+ -+/** -+ * ssam_controller_event_disable() - Disable the specified event. -+ * @ctrl: The controller to disable the event for. -+ * @reg: The event registry to use for disabling the event. -+ * @id: The event ID specifying the event to be disabled. -+ * @flags: The flags used when enabling the event. -+ * -+ * Decrement the reference count of the specified event. If the reference count -+ * reaches zero, the event will be disabled. -+ * -+ * Note: In general, ssam_notifier_register()/ssam_notifier_unregister() with a -+ * non-observer notifier should be preferred for enabling/disabling events, as -+ * this will guarantee proper ordering and event forwarding in case of errors -+ * during event enabling/disabling. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given event has not been -+ * enabled on the controller. If the reference count of the event reaches zero -+ * during this call, returns the status of the event-disable EC-command. -+ */ -+int ssam_controller_event_disable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags) -+{ -+ u16 rqid = ssh_tc_to_rqid(id.target_category); -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct ssam_nf_refcount_entry *entry; -+ int status; -+ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ mutex_lock(&nf->lock); -+ -+ entry = ssam_nf_refcount_dec(nf, reg, id); -+ if (!entry) { -+ mutex_unlock(&nf->lock); -+ return -ENOENT; -+ } -+ -+ status = ssam_nf_refcount_disable_free(ctrl, entry, flags); -+ -+ mutex_unlock(&nf->lock); -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_controller_event_disable); -+ -+/** -+ * ssam_notifier_disable_registered() - Disable events for all registered -+ * notifiers. -+ * @ctrl: The controller for which to disable the notifiers/events. -+ * -+ * Disables events for all currently registered notifiers. In case of an error -+ * (EC command failing), all previously disabled events will be restored and -+ * the error code returned. -+ * -+ * This function is intended to disable all events prior to hibernation entry. -+ * See ssam_notifier_restore_registered() to restore/re-enable all events -+ * disabled with this function. -+ * -+ * Note that this function will not disable events for notifiers registered -+ * after calling this function. It should thus be made sure that no new -+ * notifiers are going to be added after this call and before the corresponding -+ * call to ssam_notifier_restore_registered(). -+ * -+ * Return: Returns zero on success. In case of failure returns the error code -+ * returned by the failed EC command to disable an event. -+ */ -+int ssam_notifier_disable_registered(struct ssam_controller *ctrl) -+{ -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct rb_node *n; -+ int status; -+ -+ mutex_lock(&nf->lock); -+ for (n = rb_first(&nf->refcount); n; n = rb_next(n)) { -+ struct ssam_nf_refcount_entry *e; -+ -+ e = rb_entry(n, struct ssam_nf_refcount_entry, node); -+ status = ssam_ssh_event_disable(ctrl, e->key.reg, -+ e->key.id, e->flags); -+ if (status) -+ goto err; -+ } -+ mutex_unlock(&nf->lock); -+ -+ return 0; -+ -+err: -+ for (n = rb_prev(n); n; n = rb_prev(n)) { -+ struct ssam_nf_refcount_entry *e; -+ -+ e = rb_entry(n, struct ssam_nf_refcount_entry, node); -+ ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags); -+ } -+ mutex_unlock(&nf->lock); -+ -+ return status; -+} -+ -+/** -+ * ssam_notifier_restore_registered() - Restore/re-enable events for all -+ * registered notifiers. -+ * @ctrl: The controller for which to restore the notifiers/events. -+ * -+ * Restores/re-enables all events for which notifiers have been registered on -+ * the given controller. In case of a failure, the error is logged and the -+ * function continues to try and enable the remaining events. -+ * -+ * This function is intended to restore/re-enable all registered events after -+ * hibernation. See ssam_notifier_disable_registered() for the counter part -+ * disabling the events and more details. -+ */ -+void ssam_notifier_restore_registered(struct ssam_controller *ctrl) -+{ -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct rb_node *n; -+ -+ mutex_lock(&nf->lock); -+ for (n = rb_first(&nf->refcount); n; n = rb_next(n)) { -+ struct ssam_nf_refcount_entry *e; -+ -+ e = rb_entry(n, struct ssam_nf_refcount_entry, node); -+ -+ /* Ignore errors, will get logged in call. */ -+ ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags); -+ } -+ mutex_unlock(&nf->lock); -+} -+ -+/** -+ * ssam_notifier_is_empty() - Check if there are any registered notifiers. -+ * @ctrl: The controller to check on. -+ * -+ * Return: Returns %true if there are currently no notifiers registered on the -+ * controller, %false otherwise. -+ */ -+static bool ssam_notifier_is_empty(struct ssam_controller *ctrl) -+{ -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ bool result; -+ -+ mutex_lock(&nf->lock); -+ result = ssam_nf_refcount_empty(nf); -+ mutex_unlock(&nf->lock); -+ -+ return result; -+} -+ -+/** -+ * ssam_notifier_unregister_all() - Unregister all currently registered -+ * notifiers. -+ * @ctrl: The controller to unregister the notifiers on. -+ * -+ * Unregisters all currently registered notifiers. This function is used to -+ * ensure that all notifiers will be unregistered and associated -+ * entries/resources freed when the controller is being shut down. -+ */ -+static void ssam_notifier_unregister_all(struct ssam_controller *ctrl) -+{ -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct ssam_nf_refcount_entry *e, *n; -+ -+ mutex_lock(&nf->lock); -+ rbtree_postorder_for_each_entry_safe(e, n, &nf->refcount, node) { -+ /* Ignore errors, will get logged in call. */ -+ ssam_ssh_event_disable(ctrl, e->key.reg, e->key.id, e->flags); -+ kfree(e); -+ } -+ nf->refcount = RB_ROOT; -+ mutex_unlock(&nf->lock); -+} -+ -+ -+/* -- Wakeup IRQ. ----------------------------------------------------------- */ -+ -+static irqreturn_t ssam_irq_handle(int irq, void *dev_id) -+{ -+ struct ssam_controller *ctrl = dev_id; -+ -+ ssam_dbg(ctrl, "pm: wake irq triggered\n"); -+ -+ /* -+ * Note: Proper wakeup detection is currently unimplemented. -+ * When the EC is in display-off or any other non-D0 state, it -+ * does not send events/notifications to the host. Instead it -+ * signals that there are events available via the wakeup IRQ. -+ * This driver is responsible for calling back to the EC to -+ * release these events one-by-one. -+ * -+ * This IRQ should not cause a full system resume by its own. -+ * Instead, events should be handled by their respective subsystem -+ * drivers, which in turn should signal whether a full system -+ * resume should be performed. -+ * -+ * TODO: Send GPIO callback command repeatedly to EC until callback -+ * returns 0x00. Return flag of callback is "has more events". -+ * Each time the command is sent, one event is "released". Once -+ * all events have been released (return = 0x00), the GPIO is -+ * re-armed. Detect wakeup events during this process, go back to -+ * sleep if no wakeup event has been received. -+ */ -+ -+ return IRQ_HANDLED; -+} -+ -+/** -+ * ssam_irq_setup() - Set up SAM EC wakeup-GPIO interrupt. -+ * @ctrl: The controller for which the IRQ should be set up. -+ * -+ * Set up an IRQ for the wakeup-GPIO pin of the SAM EC. This IRQ can be used -+ * to wake the device from a low power state. -+ * -+ * Note that this IRQ can only be triggered while the EC is in the display-off -+ * state. In this state, events are not sent to the host in the usual way. -+ * Instead the wakeup-GPIO gets pulled to "high" as long as there are pending -+ * events and these events need to be released one-by-one via the GPIO -+ * callback request, either until there are no events left and the GPIO is -+ * reset, or all at once by transitioning the EC out of the display-off state, -+ * which will also clear the GPIO. -+ * -+ * Not all events, however, should trigger a full system wakeup. Instead the -+ * driver should, if necessary, inspect and forward each event to the -+ * corresponding subsystem, which in turn should decide if the system needs to -+ * be woken up. This logic has not been implemented yet, thus wakeup by this -+ * IRQ should be disabled by default to avoid spurious wake-ups, caused, for -+ * example, by the remaining battery percentage changing. Refer to comments in -+ * this function and comments in the corresponding IRQ handler for more -+ * details on how this should be implemented. -+ * -+ * See also ssam_ctrl_notif_display_off() and ssam_ctrl_notif_display_off() -+ * for functions to transition the EC into and out of the display-off state as -+ * well as more details on it. -+ * -+ * The IRQ is disabled by default and has to be enabled before it can wake up -+ * the device from suspend via ssam_irq_arm_for_wakeup(). On teardown, the IRQ -+ * should be freed via ssam_irq_free(). -+ */ -+int ssam_irq_setup(struct ssam_controller *ctrl) -+{ -+ struct device *dev = ssam_controller_device(ctrl); -+ struct gpio_desc *gpiod; -+ int irq; -+ int status; -+ -+ /* -+ * The actual GPIO interrupt is declared in ACPI as TRIGGER_HIGH. -+ * However, the GPIO line only gets reset by sending the GPIO callback -+ * command to SAM (or alternatively the display-on notification). As -+ * proper handling for this interrupt is not implemented yet, leaving -+ * the IRQ at TRIGGER_HIGH would cause an IRQ storm (as the callback -+ * never gets sent and thus the line never gets reset). To avoid this, -+ * mark the IRQ as TRIGGER_RISING for now, only creating a single -+ * interrupt, and let the SAM resume callback during the controller -+ * resume process clear it. -+ */ -+ const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING; -+ -+ gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS); -+ if (IS_ERR(gpiod)) -+ return PTR_ERR(gpiod); -+ -+ irq = gpiod_to_irq(gpiod); -+ gpiod_put(gpiod); -+ -+ if (irq < 0) -+ return irq; -+ -+ status = request_threaded_irq(irq, NULL, ssam_irq_handle, irqf, -+ "ssam_wakeup", ctrl); -+ if (status) -+ return status; -+ -+ ctrl->irq.num = irq; -+ disable_irq(ctrl->irq.num); -+ return 0; -+} -+ -+/** -+ * ssam_irq_free() - Free SAM EC wakeup-GPIO interrupt. -+ * @ctrl: The controller for which the IRQ should be freed. -+ * -+ * Free the wakeup-GPIO IRQ previously set-up via ssam_irq_setup(). -+ */ -+void ssam_irq_free(struct ssam_controller *ctrl) -+{ -+ free_irq(ctrl->irq.num, ctrl); -+ ctrl->irq.num = -1; -+} -+ -+/** -+ * ssam_irq_arm_for_wakeup() - Arm the EC IRQ for wakeup, if enabled. -+ * @ctrl: The controller for which the IRQ should be armed. -+ * -+ * Sets up the IRQ so that it can be used to wake the device. Specifically, -+ * this function enables the irq and then, if the device is allowed to wake up -+ * the system, calls enable_irq_wake(). See ssam_irq_disarm_wakeup() for the -+ * corresponding function to disable the IRQ. -+ * -+ * This function is intended to arm the IRQ before entering S2idle suspend. -+ * -+ * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must -+ * be balanced. -+ */ -+int ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl) -+{ -+ struct device *dev = ssam_controller_device(ctrl); -+ int status; -+ -+ enable_irq(ctrl->irq.num); -+ if (device_may_wakeup(dev)) { -+ status = enable_irq_wake(ctrl->irq.num); -+ if (status) { -+ ssam_err(ctrl, "failed to enable wake IRQ: %d\n", status); -+ disable_irq(ctrl->irq.num); -+ return status; -+ } -+ -+ ctrl->irq.wakeup_enabled = true; -+ } else { -+ ctrl->irq.wakeup_enabled = false; -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_irq_disarm_wakeup() - Disarm the wakeup IRQ. -+ * @ctrl: The controller for which the IRQ should be disarmed. -+ * -+ * Disarm the IRQ previously set up for wake via ssam_irq_arm_for_wakeup(). -+ * -+ * This function is intended to disarm the IRQ after exiting S2idle suspend. -+ * -+ * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must -+ * be balanced. -+ */ -+void ssam_irq_disarm_wakeup(struct ssam_controller *ctrl) -+{ -+ int status; -+ -+ if (ctrl->irq.wakeup_enabled) { -+ status = disable_irq_wake(ctrl->irq.num); -+ if (status) -+ ssam_err(ctrl, "failed to disable wake IRQ: %d\n", status); -+ -+ ctrl->irq.wakeup_enabled = false; -+ } -+ disable_irq(ctrl->irq.num); -+} -diff --git a/drivers/platform/x86/surface_aggregator/controller.h b/drivers/platform/x86/surface_aggregator/controller.h -new file mode 100644 -index 000000000000..a0963c3562ff ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/controller.h -@@ -0,0 +1,285 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Main SSAM/SSH controller structure and functionality. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_CONTROLLER_H -+#define _SURFACE_AGGREGATOR_CONTROLLER_H -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "ssh_request_layer.h" -+ -+ -+/* -- Safe counters. -------------------------------------------------------- */ -+ -+/** -+ * struct ssh_seq_counter - Safe counter for SSH sequence IDs. -+ * @value: The current counter value. -+ */ -+struct ssh_seq_counter { -+ u8 value; -+}; -+ -+/** -+ * struct ssh_rqid_counter - Safe counter for SSH request IDs. -+ * @value: The current counter value. -+ */ -+struct ssh_rqid_counter { -+ u16 value; -+}; -+ -+ -+/* -- Event/notification system. -------------------------------------------- */ -+ -+/** -+ * struct ssam_nf_head - Notifier head for SSAM events. -+ * @srcu: The SRCU struct for synchronization. -+ * @head: List-head for notifier blocks registered under this head. -+ */ -+struct ssam_nf_head { -+ struct srcu_struct srcu; -+ struct list_head head; -+}; -+ -+/** -+ * struct ssam_nf - Notifier callback- and activation-registry for SSAM events. -+ * @lock: Lock guarding (de-)registration of notifier blocks. Note: This -+ * lock does not need to be held for notifier calls, only -+ * registration and deregistration. -+ * @refcount: The root of the RB-tree used for reference-counting enabled -+ * events/notifications. -+ * @head: The list of notifier heads for event/notification callbacks. -+ */ -+struct ssam_nf { -+ struct mutex lock; -+ struct rb_root refcount; -+ struct ssam_nf_head head[SSH_NUM_EVENTS]; -+}; -+ -+ -+/* -- Event/async request completion system. -------------------------------- */ -+ -+struct ssam_cplt; -+ -+/** -+ * struct ssam_event_item - Struct for event queuing and completion. -+ * @node: The node in the queue. -+ * @rqid: The request ID of the event. -+ * @ops: Instance specific functions. -+ * @ops.free: Callback for freeing this event item. -+ * @event: Actual event data. -+ */ -+struct ssam_event_item { -+ struct list_head node; -+ u16 rqid; -+ -+ struct { -+ void (*free)(struct ssam_event_item *event); -+ } ops; -+ -+ struct ssam_event event; /* must be last */ -+}; -+ -+/** -+ * struct ssam_event_queue - Queue for completing received events. -+ * @cplt: Reference to the completion system on which this queue is active. -+ * @lock: The lock for any operation on the queue. -+ * @head: The list-head of the queue. -+ * @work: The &struct work_struct performing completion work for this queue. -+ */ -+struct ssam_event_queue { -+ struct ssam_cplt *cplt; -+ -+ spinlock_t lock; -+ struct list_head head; -+ struct work_struct work; -+}; -+ -+/** -+ * struct ssam_event_target - Set of queues for a single SSH target ID. -+ * @queue: The array of queues, one queue per event ID. -+ */ -+struct ssam_event_target { -+ struct ssam_event_queue queue[SSH_NUM_EVENTS]; -+}; -+ -+/** -+ * struct ssam_cplt - SSAM event/async request completion system. -+ * @dev: The device with which this system is associated. Only used -+ * for logging. -+ * @wq: The &struct workqueue_struct on which all completion work -+ * items are queued. -+ * @event: Event completion management. -+ * @event.target: Array of &struct ssam_event_target, one for each target. -+ * @event.notif: Notifier callbacks and event activation reference counting. -+ */ -+struct ssam_cplt { -+ struct device *dev; -+ struct workqueue_struct *wq; -+ -+ struct { -+ struct ssam_event_target target[SSH_NUM_TARGETS]; -+ struct ssam_nf notif; -+ } event; -+}; -+ -+ -+/* -- Main SSAM device structures. ------------------------------------------ */ -+ -+/** -+ * enum ssam_controller_state - State values for &struct ssam_controller. -+ * @SSAM_CONTROLLER_UNINITIALIZED: -+ * The controller has not been initialized yet or has been deinitialized. -+ * @SSAM_CONTROLLER_INITIALIZED: -+ * The controller is initialized, but has not been started yet. -+ * @SSAM_CONTROLLER_STARTED: -+ * The controller has been started and is ready to use. -+ * @SSAM_CONTROLLER_STOPPED: -+ * The controller has been stopped. -+ * @SSAM_CONTROLLER_SUSPENDED: -+ * The controller has been suspended. -+ */ -+enum ssam_controller_state { -+ SSAM_CONTROLLER_UNINITIALIZED, -+ SSAM_CONTROLLER_INITIALIZED, -+ SSAM_CONTROLLER_STARTED, -+ SSAM_CONTROLLER_STOPPED, -+ SSAM_CONTROLLER_SUSPENDED, -+}; -+ -+/** -+ * struct ssam_controller_caps - Controller device capabilities. -+ * @ssh_power_profile: SSH power profile. -+ * @ssh_buffer_size: SSH driver UART buffer size. -+ * @screen_on_sleep_idle_timeout: SAM UART screen-on sleep idle timeout. -+ * @screen_off_sleep_idle_timeout: SAM UART screen-off sleep idle timeout. -+ * @d3_closes_handle: SAM closes UART handle in D3. -+ * -+ * Controller and SSH device capabilities found in ACPI. -+ */ -+struct ssam_controller_caps { -+ u32 ssh_power_profile; -+ u32 ssh_buffer_size; -+ u32 screen_on_sleep_idle_timeout; -+ u32 screen_off_sleep_idle_timeout; -+ u32 d3_closes_handle:1; -+}; -+ -+/** -+ * struct ssam_controller - SSAM controller device. -+ * @kref: Reference count of the controller. -+ * @lock: Main lock for the controller, used to guard state changes. -+ * @state: Controller state. -+ * @rtl: Request transport layer for SSH I/O. -+ * @cplt: Completion system for SSH/SSAM events and asynchronous requests. -+ * @counter: Safe SSH message ID counters. -+ * @counter.seq: Sequence ID counter. -+ * @counter.rqid: Request ID counter. -+ * @irq: Wakeup IRQ resources. -+ * @irq.num: The wakeup IRQ number. -+ * @irq.wakeup_enabled: Whether wakeup by IRQ is enabled during suspend. -+ * @caps: The controller device capabilities. -+ */ -+struct ssam_controller { -+ struct kref kref; -+ -+ struct rw_semaphore lock; -+ enum ssam_controller_state state; -+ -+ struct ssh_rtl rtl; -+ struct ssam_cplt cplt; -+ -+ struct { -+ struct ssh_seq_counter seq; -+ struct ssh_rqid_counter rqid; -+ } counter; -+ -+ struct { -+ int num; -+ bool wakeup_enabled; -+ } irq; -+ -+ struct ssam_controller_caps caps; -+}; -+ -+#define to_ssam_controller(ptr, member) \ -+ container_of(ptr, struct ssam_controller, member) -+ -+#define ssam_dbg(ctrl, fmt, ...) rtl_dbg(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+#define ssam_info(ctrl, fmt, ...) rtl_info(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+#define ssam_warn(ctrl, fmt, ...) rtl_warn(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+#define ssam_err(ctrl, fmt, ...) rtl_err(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+ -+/** -+ * ssam_controller_receive_buf() - Provide input-data to the controller. -+ * @ctrl: The controller. -+ * @buf: The input buffer. -+ * @n: The number of bytes in the input buffer. -+ * -+ * Provide input data to be evaluated by the controller, which has been -+ * received via the lower-level transport. -+ * -+ * Return: Returns the number of bytes consumed, or, if the packet transport -+ * layer of the controller has been shut down, %-ESHUTDOWN. -+ */ -+static inline -+int ssam_controller_receive_buf(struct ssam_controller *ctrl, -+ const unsigned char *buf, size_t n) -+{ -+ return ssh_ptl_rx_rcvbuf(&ctrl->rtl.ptl, buf, n); -+} -+ -+/** -+ * ssam_controller_write_wakeup() - Notify the controller that the underlying -+ * device has space available for data to be written. -+ * @ctrl: The controller. -+ */ -+static inline void ssam_controller_write_wakeup(struct ssam_controller *ctrl) -+{ -+ ssh_ptl_tx_wakeup_transfer(&ctrl->rtl.ptl); -+} -+ -+int ssam_controller_init(struct ssam_controller *ctrl, struct serdev_device *s); -+int ssam_controller_start(struct ssam_controller *ctrl); -+void ssam_controller_shutdown(struct ssam_controller *ctrl); -+void ssam_controller_destroy(struct ssam_controller *ctrl); -+ -+int ssam_notifier_disable_registered(struct ssam_controller *ctrl); -+void ssam_notifier_restore_registered(struct ssam_controller *ctrl); -+ -+int ssam_irq_setup(struct ssam_controller *ctrl); -+void ssam_irq_free(struct ssam_controller *ctrl); -+int ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl); -+void ssam_irq_disarm_wakeup(struct ssam_controller *ctrl); -+ -+void ssam_controller_lock(struct ssam_controller *c); -+void ssam_controller_unlock(struct ssam_controller *c); -+ -+int ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version); -+int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl); -+int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl); -+int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl); -+int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl); -+ -+int ssam_controller_suspend(struct ssam_controller *ctrl); -+int ssam_controller_resume(struct ssam_controller *ctrl); -+ -+int ssam_event_item_cache_init(void); -+void ssam_event_item_cache_destroy(void); -+ -+#endif /* _SURFACE_AGGREGATOR_CONTROLLER_H */ -diff --git a/drivers/platform/x86/surface_aggregator/core.c b/drivers/platform/x86/surface_aggregator/core.c -new file mode 100644 -index 000000000000..5d780e55f4a1 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/core.c -@@ -0,0 +1,839 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface Serial Hub (SSH) driver for communication with the Surface/System -+ * Aggregator Module (SSAM/SAM). -+ * -+ * Provides access to a SAM-over-SSH connected EC via a controller device. -+ * Handles communication via requests as well as enabling, disabling, and -+ * relaying of events. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "bus.h" -+#include "controller.h" -+ -+#define CREATE_TRACE_POINTS -+#include "trace.h" -+ -+ -+/* -- Static controller reference. ------------------------------------------ */ -+ -+/* -+ * Main controller reference. The corresponding lock must be held while -+ * accessing (reading/writing) the reference. -+ */ -+static struct ssam_controller *__ssam_controller; -+static DEFINE_SPINLOCK(__ssam_controller_lock); -+ -+/** -+ * ssam_get_controller() - Get reference to SSAM controller. -+ * -+ * Returns a reference to the SSAM controller of the system or %NULL if there -+ * is none, it hasn't been set up yet, or it has already been unregistered. -+ * This function automatically increments the reference count of the -+ * controller, thus the calling party must ensure that ssam_controller_put() -+ * is called when it doesn't need the controller any more. -+ */ -+struct ssam_controller *ssam_get_controller(void) -+{ -+ struct ssam_controller *ctrl; -+ -+ spin_lock(&__ssam_controller_lock); -+ -+ ctrl = __ssam_controller; -+ if (!ctrl) -+ goto out; -+ -+ if (WARN_ON(!kref_get_unless_zero(&ctrl->kref))) -+ ctrl = NULL; -+ -+out: -+ spin_unlock(&__ssam_controller_lock); -+ return ctrl; -+} -+EXPORT_SYMBOL_GPL(ssam_get_controller); -+ -+/** -+ * ssam_try_set_controller() - Try to set the main controller reference. -+ * @ctrl: The controller to which the reference should point. -+ * -+ * Set the main controller reference to the given pointer if the reference -+ * hasn't been set already. -+ * -+ * Return: Returns zero on success or %-EEXIST if the reference has already -+ * been set. -+ */ -+static int ssam_try_set_controller(struct ssam_controller *ctrl) -+{ -+ int status = 0; -+ -+ spin_lock(&__ssam_controller_lock); -+ if (!__ssam_controller) -+ __ssam_controller = ctrl; -+ else -+ status = -EEXIST; -+ spin_unlock(&__ssam_controller_lock); -+ -+ return status; -+} -+ -+/** -+ * ssam_clear_controller() - Remove/clear the main controller reference. -+ * -+ * Clears the main controller reference, i.e. sets it to %NULL. This function -+ * should be called before the controller is shut down. -+ */ -+static void ssam_clear_controller(void) -+{ -+ spin_lock(&__ssam_controller_lock); -+ __ssam_controller = NULL; -+ spin_unlock(&__ssam_controller_lock); -+} -+ -+/** -+ * ssam_client_link() - Link an arbitrary client device to the controller. -+ * @c: The controller to link to. -+ * @client: The client device. -+ * -+ * Link an arbitrary client device to the controller by creating a device link -+ * between it as consumer and the controller device as provider. This function -+ * can be used for non-SSAM devices (or SSAM devices not registered as child -+ * under the controller) to guarantee that the controller is valid for as long -+ * as the driver of the client device is bound, and that proper suspend and -+ * resume ordering is guaranteed. -+ * -+ * The device link does not have to be destructed manually. It is removed -+ * automatically once the driver of the client device unbinds. -+ * -+ * Return: Returns zero on success, %-ENODEV if the controller is not ready or -+ * going to be removed soon, or %-ENOMEM if the device link could not be -+ * created for other reasons. -+ */ -+int ssam_client_link(struct ssam_controller *c, struct device *client) -+{ -+ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; -+ struct device_link *link; -+ struct device *ctrldev; -+ -+ ssam_controller_statelock(c); -+ -+ if (c->state != SSAM_CONTROLLER_STARTED) { -+ ssam_controller_stateunlock(c); -+ return -ENODEV; -+ } -+ -+ ctrldev = ssam_controller_device(c); -+ if (!ctrldev) { -+ ssam_controller_stateunlock(c); -+ return -ENODEV; -+ } -+ -+ link = device_link_add(client, ctrldev, flags); -+ if (!link) { -+ ssam_controller_stateunlock(c); -+ return -ENOMEM; -+ } -+ -+ /* -+ * Return -ENODEV if supplier driver is on its way to be removed. In -+ * this case, the controller won't be around for much longer and the -+ * device link is not going to save us any more, as unbinding is -+ * already in progress. -+ */ -+ if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) { -+ ssam_controller_stateunlock(c); -+ return -ENODEV; -+ } -+ -+ ssam_controller_stateunlock(c); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_client_link); -+ -+/** -+ * ssam_client_bind() - Bind an arbitrary client device to the controller. -+ * @client: The client device. -+ * -+ * Link an arbitrary client device to the controller by creating a device link -+ * between it as consumer and the main controller device as provider. This -+ * function can be used for non-SSAM devices to guarantee that the controller -+ * returned by this function is valid for as long as the driver of the client -+ * device is bound, and that proper suspend and resume ordering is guaranteed. -+ * -+ * This function does essentially the same as ssam_client_link(), except that -+ * it first fetches the main controller reference, then creates the link, and -+ * finally returns this reference. Note that this function does not increment -+ * the reference counter of the controller, as, due to the link, the -+ * controller lifetime is assured as long as the driver of the client device -+ * is bound. -+ * -+ * It is not valid to use the controller reference obtained by this method -+ * outside of the driver bound to the client device at the time of calling -+ * this function, without first incrementing the reference count of the -+ * controller via ssam_controller_get(). Even after doing this, care must be -+ * taken that requests are only submitted and notifiers are only -+ * (un-)registered when the controller is active and not suspended. In other -+ * words: The device link only lives as long as the client driver is bound and -+ * any guarantees enforced by this link (e.g. active controller state) can -+ * only be relied upon as long as this link exists and may need to be enforced -+ * in other ways afterwards. -+ * -+ * The created device link does not have to be destructed manually. It is -+ * removed automatically once the driver of the client device unbinds. -+ * -+ * Return: Returns the controller on success, an error pointer with %-ENODEV -+ * if the controller is not present, not ready or going to be removed soon, or -+ * %-ENOMEM if the device link could not be created for other reasons. -+ */ -+struct ssam_controller *ssam_client_bind(struct device *client) -+{ -+ struct ssam_controller *c; -+ int status; -+ -+ c = ssam_get_controller(); -+ if (!c) -+ return ERR_PTR(-ENODEV); -+ -+ status = ssam_client_link(c, client); -+ -+ /* -+ * Note that we can drop our controller reference in both success and -+ * failure cases: On success, we have bound the controller lifetime -+ * inherently to the client driver lifetime, i.e. it the controller is -+ * now guaranteed to outlive the client driver. On failure, we're not -+ * going to use the controller any more. -+ */ -+ ssam_controller_put(c); -+ -+ return status >= 0 ? c : ERR_PTR(status); -+} -+EXPORT_SYMBOL_GPL(ssam_client_bind); -+ -+ -+/* -- Glue layer (serdev_device -> ssam_controller). ------------------------ */ -+ -+static int ssam_receive_buf(struct serdev_device *dev, const unsigned char *buf, -+ size_t n) -+{ -+ struct ssam_controller *ctrl; -+ -+ ctrl = serdev_device_get_drvdata(dev); -+ return ssam_controller_receive_buf(ctrl, buf, n); -+} -+ -+static void ssam_write_wakeup(struct serdev_device *dev) -+{ -+ ssam_controller_write_wakeup(serdev_device_get_drvdata(dev)); -+} -+ -+static const struct serdev_device_ops ssam_serdev_ops = { -+ .receive_buf = ssam_receive_buf, -+ .write_wakeup = ssam_write_wakeup, -+}; -+ -+ -+/* -- SysFS and misc. ------------------------------------------------------- */ -+ -+static int ssam_log_firmware_version(struct ssam_controller *ctrl) -+{ -+ u32 version, a, b, c; -+ int status; -+ -+ status = ssam_get_firmware_version(ctrl, &version); -+ if (status) -+ return status; -+ -+ a = (version >> 24) & 0xff; -+ b = ((version >> 8) & 0xffff); -+ c = version & 0xff; -+ -+ ssam_info(ctrl, "SAM firmware version: %u.%u.%u\n", a, b, c); -+ return 0; -+} -+ -+static ssize_t firmware_version_show(struct device *dev, -+ struct device_attribute *attr, char *buf) -+{ -+ struct ssam_controller *ctrl = dev_get_drvdata(dev); -+ u32 version, a, b, c; -+ int status; -+ -+ status = ssam_get_firmware_version(ctrl, &version); -+ if (status < 0) -+ return status; -+ -+ a = (version >> 24) & 0xff; -+ b = ((version >> 8) & 0xffff); -+ c = version & 0xff; -+ -+ return sysfs_emit(buf, "%u.%u.%u\n", a, b, c); -+} -+static DEVICE_ATTR_RO(firmware_version); -+ -+static struct attribute *ssam_sam_attrs[] = { -+ &dev_attr_firmware_version.attr, -+ NULL -+}; -+ -+static const struct attribute_group ssam_sam_group = { -+ .name = "sam", -+ .attrs = ssam_sam_attrs, -+}; -+ -+ -+/* -- ACPI based device setup. ---------------------------------------------- */ -+ -+static acpi_status ssam_serdev_setup_via_acpi_crs(struct acpi_resource *rsc, -+ void *ctx) -+{ -+ struct serdev_device *serdev = ctx; -+ struct acpi_resource_common_serialbus *serial; -+ struct acpi_resource_uart_serialbus *uart; -+ bool flow_control; -+ int status = 0; -+ -+ if (rsc->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) -+ return AE_OK; -+ -+ serial = &rsc->data.common_serial_bus; -+ if (serial->type != ACPI_RESOURCE_SERIAL_TYPE_UART) -+ return AE_OK; -+ -+ uart = &rsc->data.uart_serial_bus; -+ -+ /* Set up serdev device. */ -+ serdev_device_set_baudrate(serdev, uart->default_baud_rate); -+ -+ /* serdev currently only supports RTSCTS flow control. */ -+ if (uart->flow_control & (~((u8)ACPI_UART_FLOW_CONTROL_HW))) { -+ dev_warn(&serdev->dev, "setup: unsupported flow control (value: %#04x)\n", -+ uart->flow_control); -+ } -+ -+ /* Set RTSCTS flow control. */ -+ flow_control = uart->flow_control & ACPI_UART_FLOW_CONTROL_HW; -+ serdev_device_set_flow_control(serdev, flow_control); -+ -+ /* serdev currently only supports EVEN/ODD parity. */ -+ switch (uart->parity) { -+ case ACPI_UART_PARITY_NONE: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); -+ break; -+ case ACPI_UART_PARITY_EVEN: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_EVEN); -+ break; -+ case ACPI_UART_PARITY_ODD: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_ODD); -+ break; -+ default: -+ dev_warn(&serdev->dev, "setup: unsupported parity (value: %#04x)\n", -+ uart->parity); -+ break; -+ } -+ -+ if (status) { -+ dev_err(&serdev->dev, "setup: failed to set parity (value: %#04x, error: %d)\n", -+ uart->parity, status); -+ return AE_ERROR; -+ } -+ -+ /* We've found the resource and are done. */ -+ return AE_CTRL_TERMINATE; -+} -+ -+static acpi_status ssam_serdev_setup_via_acpi(acpi_handle handle, -+ struct serdev_device *serdev) -+{ -+ return acpi_walk_resources(handle, METHOD_NAME__CRS, -+ ssam_serdev_setup_via_acpi_crs, serdev); -+} -+ -+ -+/* -- Power management. ----------------------------------------------------- */ -+ -+static void ssam_serial_hub_shutdown(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Try to disable notifiers, signal display-off and D0-exit, ignore any -+ * errors. -+ * -+ * Note: It has not been established yet if this is actually -+ * necessary/useful for shutdown. -+ */ -+ -+ status = ssam_notifier_disable_registered(c); -+ if (status) { -+ ssam_err(c, "pm: failed to disable notifiers for shutdown: %d\n", -+ status); -+ } -+ -+ status = ssam_ctrl_notif_display_off(c); -+ if (status) -+ ssam_err(c, "pm: display-off notification failed: %d\n", status); -+ -+ status = ssam_ctrl_notif_d0_exit(c); -+ if (status) -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+} -+ -+#ifdef CONFIG_PM_SLEEP -+ -+static int ssam_serial_hub_pm_prepare(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Try to signal display-off, This will quiesce events. -+ * -+ * Note: Signaling display-off/display-on should normally be done from -+ * some sort of display state notifier. As that is not available, -+ * signal it here. -+ */ -+ -+ status = ssam_ctrl_notif_display_off(c); -+ if (status) -+ ssam_err(c, "pm: display-off notification failed: %d\n", status); -+ -+ return status; -+} -+ -+static void ssam_serial_hub_pm_complete(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Try to signal display-on. This will restore events. -+ * -+ * Note: Signaling display-off/display-on should normally be done from -+ * some sort of display state notifier. As that is not available, -+ * signal it here. -+ */ -+ -+ status = ssam_ctrl_notif_display_on(c); -+ if (status) -+ ssam_err(c, "pm: display-on notification failed: %d\n", status); -+} -+ -+static int ssam_serial_hub_pm_suspend(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Try to signal D0-exit, enable IRQ wakeup if specified. Abort on -+ * error. -+ */ -+ -+ status = ssam_ctrl_notif_d0_exit(c); -+ if (status) { -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+ goto err_notif; -+ } -+ -+ status = ssam_irq_arm_for_wakeup(c); -+ if (status) -+ goto err_irq; -+ -+ WARN_ON(ssam_controller_suspend(c)); -+ return 0; -+ -+err_irq: -+ ssam_ctrl_notif_d0_entry(c); -+err_notif: -+ ssam_ctrl_notif_display_on(c); -+ return status; -+} -+ -+static int ssam_serial_hub_pm_resume(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ WARN_ON(ssam_controller_resume(c)); -+ -+ /* -+ * Try to disable IRQ wakeup (if specified) and signal D0-entry. In -+ * case of errors, log them and try to restore normal operation state -+ * as far as possible. -+ * -+ * Note: Signaling display-off/display-on should normally be done from -+ * some sort of display state notifier. As that is not available, -+ * signal it here. -+ */ -+ -+ ssam_irq_disarm_wakeup(c); -+ -+ status = ssam_ctrl_notif_d0_entry(c); -+ if (status) -+ ssam_err(c, "pm: D0-entry notification failed: %d\n", status); -+ -+ return 0; -+} -+ -+static int ssam_serial_hub_pm_freeze(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * During hibernation image creation, we only have to ensure that the -+ * EC doesn't send us any events. This is done via the display-off -+ * and D0-exit notifications. Note that this sets up the wakeup IRQ -+ * on the EC side, however, we have disabled it by default on our side -+ * and won't enable it here. -+ * -+ * See ssam_serial_hub_poweroff() for more details on the hibernation -+ * process. -+ */ -+ -+ status = ssam_ctrl_notif_d0_exit(c); -+ if (status) { -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+ ssam_ctrl_notif_display_on(c); -+ return status; -+ } -+ -+ WARN_ON(ssam_controller_suspend(c)); -+ return 0; -+} -+ -+static int ssam_serial_hub_pm_thaw(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ WARN_ON(ssam_controller_resume(c)); -+ -+ status = ssam_ctrl_notif_d0_entry(c); -+ if (status) -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+ -+ return status; -+} -+ -+static int ssam_serial_hub_pm_poweroff(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * When entering hibernation and powering off the system, the EC, at -+ * least on some models, may disable events. Without us taking care of -+ * that, this leads to events not being enabled/restored when the -+ * system resumes from hibernation, resulting SAM-HID subsystem devices -+ * (i.e. keyboard, touchpad) not working, AC-plug/AC-unplug events being -+ * gone, etc. -+ * -+ * To avoid these issues, we disable all registered events here (this is -+ * likely not actually required) and restore them during the drivers PM -+ * restore callback. -+ * -+ * Wakeup from the EC interrupt is not supported during hibernation, -+ * so don't arm the IRQ here. -+ */ -+ -+ status = ssam_notifier_disable_registered(c); -+ if (status) { -+ ssam_err(c, "pm: failed to disable notifiers for hibernation: %d\n", -+ status); -+ return status; -+ } -+ -+ status = ssam_ctrl_notif_d0_exit(c); -+ if (status) { -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+ ssam_notifier_restore_registered(c); -+ return status; -+ } -+ -+ WARN_ON(ssam_controller_suspend(c)); -+ return 0; -+} -+ -+static int ssam_serial_hub_pm_restore(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Ignore but log errors, try to restore state as much as possible in -+ * case of failures. See ssam_serial_hub_poweroff() for more details on -+ * the hibernation process. -+ */ -+ -+ WARN_ON(ssam_controller_resume(c)); -+ -+ status = ssam_ctrl_notif_d0_entry(c); -+ if (status) -+ ssam_err(c, "pm: D0-entry notification failed: %d\n", status); -+ -+ ssam_notifier_restore_registered(c); -+ return 0; -+} -+ -+static const struct dev_pm_ops ssam_serial_hub_pm_ops = { -+ .prepare = ssam_serial_hub_pm_prepare, -+ .complete = ssam_serial_hub_pm_complete, -+ .suspend = ssam_serial_hub_pm_suspend, -+ .resume = ssam_serial_hub_pm_resume, -+ .freeze = ssam_serial_hub_pm_freeze, -+ .thaw = ssam_serial_hub_pm_thaw, -+ .poweroff = ssam_serial_hub_pm_poweroff, -+ .restore = ssam_serial_hub_pm_restore, -+}; -+ -+#else /* CONFIG_PM_SLEEP */ -+ -+static const struct dev_pm_ops ssam_serial_hub_pm_ops = { }; -+ -+#endif /* CONFIG_PM_SLEEP */ -+ -+ -+/* -- Device/driver setup. -------------------------------------------------- */ -+ -+static const struct acpi_gpio_params gpio_ssam_wakeup_int = { 0, 0, false }; -+static const struct acpi_gpio_params gpio_ssam_wakeup = { 1, 0, false }; -+ -+static const struct acpi_gpio_mapping ssam_acpi_gpios[] = { -+ { "ssam_wakeup-int-gpio", &gpio_ssam_wakeup_int, 1 }, -+ { "ssam_wakeup-gpio", &gpio_ssam_wakeup, 1 }, -+ { }, -+}; -+ -+static int ssam_serial_hub_probe(struct serdev_device *serdev) -+{ -+ struct ssam_controller *ctrl; -+ acpi_handle *ssh = ACPI_HANDLE(&serdev->dev); -+ acpi_status astatus; -+ int status; -+ -+ if (gpiod_count(&serdev->dev, NULL) < 0) -+ return -ENODEV; -+ -+ status = devm_acpi_dev_add_driver_gpios(&serdev->dev, ssam_acpi_gpios); -+ if (status) -+ return status; -+ -+ /* Allocate controller. */ -+ ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); -+ if (!ctrl) -+ return -ENOMEM; -+ -+ /* Initialize controller. */ -+ status = ssam_controller_init(ctrl, serdev); -+ if (status) -+ goto err_ctrl_init; -+ -+ ssam_controller_lock(ctrl); -+ -+ /* Set up serdev device. */ -+ serdev_device_set_drvdata(serdev, ctrl); -+ serdev_device_set_client_ops(serdev, &ssam_serdev_ops); -+ status = serdev_device_open(serdev); -+ if (status) -+ goto err_devopen; -+ -+ astatus = ssam_serdev_setup_via_acpi(ssh, serdev); -+ if (ACPI_FAILURE(astatus)) { -+ status = -ENXIO; -+ goto err_devinit; -+ } -+ -+ /* Start controller. */ -+ status = ssam_controller_start(ctrl); -+ if (status) -+ goto err_devinit; -+ -+ ssam_controller_unlock(ctrl); -+ -+ /* -+ * Initial SAM requests: Log version and notify default/init power -+ * states. -+ */ -+ status = ssam_log_firmware_version(ctrl); -+ if (status) -+ goto err_initrq; -+ -+ status = ssam_ctrl_notif_d0_entry(ctrl); -+ if (status) -+ goto err_initrq; -+ -+ status = ssam_ctrl_notif_display_on(ctrl); -+ if (status) -+ goto err_initrq; -+ -+ status = sysfs_create_group(&serdev->dev.kobj, &ssam_sam_group); -+ if (status) -+ goto err_initrq; -+ -+ /* Set up IRQ. */ -+ status = ssam_irq_setup(ctrl); -+ if (status) -+ goto err_irq; -+ -+ /* Finally, set main controller reference. */ -+ status = ssam_try_set_controller(ctrl); -+ if (WARN_ON(status)) /* Currently, we're the only provider. */ -+ goto err_mainref; -+ -+ /* -+ * TODO: The EC can wake up the system via the associated GPIO interrupt -+ * in multiple situations. One of which is the remaining battery -+ * capacity falling below a certain threshold. Normally, we should -+ * use the device_init_wakeup function, however, the EC also seems -+ * to have other reasons for waking up the system and it seems -+ * that Windows has additional checks whether the system should be -+ * resumed. In short, this causes some spurious unwanted wake-ups. -+ * For now let's thus default power/wakeup to false. -+ */ -+ device_set_wakeup_capable(&serdev->dev, true); -+ acpi_walk_dep_device_list(ssh); -+ -+ return 0; -+ -+err_mainref: -+ ssam_irq_free(ctrl); -+err_irq: -+ sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); -+err_initrq: -+ ssam_controller_lock(ctrl); -+ ssam_controller_shutdown(ctrl); -+err_devinit: -+ serdev_device_close(serdev); -+err_devopen: -+ ssam_controller_destroy(ctrl); -+ ssam_controller_unlock(ctrl); -+err_ctrl_init: -+ kfree(ctrl); -+ return status; -+} -+ -+static void ssam_serial_hub_remove(struct serdev_device *serdev) -+{ -+ struct ssam_controller *ctrl = serdev_device_get_drvdata(serdev); -+ int status; -+ -+ /* Clear static reference so that no one else can get a new one. */ -+ ssam_clear_controller(); -+ -+ /* Disable and free IRQ. */ -+ ssam_irq_free(ctrl); -+ -+ sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); -+ ssam_controller_lock(ctrl); -+ -+ /* Remove all client devices. */ -+ ssam_controller_remove_clients(ctrl); -+ -+ /* Act as if suspending to silence events. */ -+ status = ssam_ctrl_notif_display_off(ctrl); -+ if (status) { -+ dev_err(&serdev->dev, "display-off notification failed: %d\n", -+ status); -+ } -+ -+ status = ssam_ctrl_notif_d0_exit(ctrl); -+ if (status) { -+ dev_err(&serdev->dev, "D0-exit notification failed: %d\n", -+ status); -+ } -+ -+ /* Shut down controller and remove serdev device reference from it. */ -+ ssam_controller_shutdown(ctrl); -+ -+ /* Shut down actual transport. */ -+ serdev_device_wait_until_sent(serdev, 0); -+ serdev_device_close(serdev); -+ -+ /* Drop our controller reference. */ -+ ssam_controller_unlock(ctrl); -+ ssam_controller_put(ctrl); -+ -+ device_set_wakeup_capable(&serdev->dev, false); -+} -+ -+static const struct acpi_device_id ssam_serial_hub_match[] = { -+ { "MSHW0084", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, ssam_serial_hub_match); -+ -+static struct serdev_device_driver ssam_serial_hub = { -+ .probe = ssam_serial_hub_probe, -+ .remove = ssam_serial_hub_remove, -+ .driver = { -+ .name = "surface_serial_hub", -+ .acpi_match_table = ssam_serial_hub_match, -+ .pm = &ssam_serial_hub_pm_ops, -+ .shutdown = ssam_serial_hub_shutdown, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+/* -- Module setup. --------------------------------------------------------- */ -+ -+static int __init ssam_core_init(void) -+{ -+ int status; -+ -+ status = ssam_bus_register(); -+ if (status) -+ goto err_bus; -+ -+ status = ssh_ctrl_packet_cache_init(); -+ if (status) -+ goto err_cpkg; -+ -+ status = ssam_event_item_cache_init(); -+ if (status) -+ goto err_evitem; -+ -+ status = serdev_device_driver_register(&ssam_serial_hub); -+ if (status) -+ goto err_register; -+ -+ return 0; -+ -+err_register: -+ ssam_event_item_cache_destroy(); -+err_evitem: -+ ssh_ctrl_packet_cache_destroy(); -+err_cpkg: -+ ssam_bus_unregister(); -+err_bus: -+ return status; -+} -+module_init(ssam_core_init); -+ -+static void __exit ssam_core_exit(void) -+{ -+ serdev_device_driver_unregister(&ssam_serial_hub); -+ ssam_event_item_cache_destroy(); -+ ssh_ctrl_packet_cache_destroy(); -+ ssam_bus_unregister(); -+} -+module_exit(ssam_core_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Subsystem and Surface Serial Hub driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_aggregator/ssh_msgb.h b/drivers/platform/x86/surface_aggregator/ssh_msgb.h -new file mode 100644 -index 000000000000..e562958ffdf0 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/ssh_msgb.h -@@ -0,0 +1,205 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * SSH message builder functions. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H -+#define _SURFACE_AGGREGATOR_SSH_MSGB_H -+ -+#include -+#include -+ -+#include -+#include -+ -+/** -+ * struct msgbuf - Buffer struct to construct SSH messages. -+ * @begin: Pointer to the beginning of the allocated buffer space. -+ * @end: Pointer to the end (one past last element) of the allocated buffer -+ * space. -+ * @ptr: Pointer to the first free element in the buffer. -+ */ -+struct msgbuf { -+ u8 *begin; -+ u8 *end; -+ u8 *ptr; -+}; -+ -+/** -+ * msgb_init() - Initialize the given message buffer struct. -+ * @msgb: The buffer struct to initialize -+ * @ptr: Pointer to the underlying memory by which the buffer will be backed. -+ * @cap: Size of the underlying memory. -+ * -+ * Initialize the given message buffer struct using the provided memory as -+ * backing. -+ */ -+static inline void msgb_init(struct msgbuf *msgb, u8 *ptr, size_t cap) -+{ -+ msgb->begin = ptr; -+ msgb->end = ptr + cap; -+ msgb->ptr = ptr; -+} -+ -+/** -+ * msgb_bytes_used() - Return the current number of bytes used in the buffer. -+ * @msgb: The message buffer. -+ */ -+static inline size_t msgb_bytes_used(const struct msgbuf *msgb) -+{ -+ return msgb->ptr - msgb->begin; -+} -+ -+static inline void __msgb_push_u8(struct msgbuf *msgb, u8 value) -+{ -+ *msgb->ptr = value; -+ msgb->ptr += sizeof(u8); -+} -+ -+static inline void __msgb_push_u16(struct msgbuf *msgb, u16 value) -+{ -+ put_unaligned_le16(value, msgb->ptr); -+ msgb->ptr += sizeof(u16); -+} -+ -+/** -+ * msgb_push_u16() - Push a u16 value to the buffer. -+ * @msgb: The message buffer. -+ * @value: The value to push to the buffer. -+ */ -+static inline void msgb_push_u16(struct msgbuf *msgb, u16 value) -+{ -+ if (WARN_ON(msgb->ptr + sizeof(u16) > msgb->end)) -+ return; -+ -+ __msgb_push_u16(msgb, value); -+} -+ -+/** -+ * msgb_push_syn() - Push SSH SYN bytes to the buffer. -+ * @msgb: The message buffer. -+ */ -+static inline void msgb_push_syn(struct msgbuf *msgb) -+{ -+ msgb_push_u16(msgb, SSH_MSG_SYN); -+} -+ -+/** -+ * msgb_push_buf() - Push raw data to the buffer. -+ * @msgb: The message buffer. -+ * @buf: The data to push to the buffer. -+ * @len: The length of the data to push to the buffer. -+ */ -+static inline void msgb_push_buf(struct msgbuf *msgb, const u8 *buf, size_t len) -+{ -+ msgb->ptr = memcpy(msgb->ptr, buf, len) + len; -+} -+ -+/** -+ * msgb_push_crc() - Compute CRC and push it to the buffer. -+ * @msgb: The message buffer. -+ * @buf: The data for which the CRC should be computed. -+ * @len: The length of the data for which the CRC should be computed. -+ */ -+static inline void msgb_push_crc(struct msgbuf *msgb, const u8 *buf, size_t len) -+{ -+ msgb_push_u16(msgb, ssh_crc(buf, len)); -+} -+ -+/** -+ * msgb_push_frame() - Push a SSH message frame header to the buffer. -+ * @msgb: The message buffer -+ * @ty: The type of the frame. -+ * @len: The length of the payload of the frame. -+ * @seq: The sequence ID of the frame/packet. -+ */ -+static inline void msgb_push_frame(struct msgbuf *msgb, u8 ty, u16 len, u8 seq) -+{ -+ u8 *const begin = msgb->ptr; -+ -+ if (WARN_ON(msgb->ptr + sizeof(struct ssh_frame) > msgb->end)) -+ return; -+ -+ __msgb_push_u8(msgb, ty); /* Frame type. */ -+ __msgb_push_u16(msgb, len); /* Frame payload length. */ -+ __msgb_push_u8(msgb, seq); /* Frame sequence ID. */ -+ -+ msgb_push_crc(msgb, begin, msgb->ptr - begin); -+} -+ -+/** -+ * msgb_push_ack() - Push a SSH ACK frame to the buffer. -+ * @msgb: The message buffer -+ * @seq: The sequence ID of the frame/packet to be ACKed. -+ */ -+static inline void msgb_push_ack(struct msgbuf *msgb, u8 seq) -+{ -+ /* SYN. */ -+ msgb_push_syn(msgb); -+ -+ /* ACK-type frame + CRC. */ -+ msgb_push_frame(msgb, SSH_FRAME_TYPE_ACK, 0x00, seq); -+ -+ /* Payload CRC (ACK-type frames do not have a payload). */ -+ msgb_push_crc(msgb, msgb->ptr, 0); -+} -+ -+/** -+ * msgb_push_nak() - Push a SSH NAK frame to the buffer. -+ * @msgb: The message buffer -+ */ -+static inline void msgb_push_nak(struct msgbuf *msgb) -+{ -+ /* SYN. */ -+ msgb_push_syn(msgb); -+ -+ /* NAK-type frame + CRC. */ -+ msgb_push_frame(msgb, SSH_FRAME_TYPE_NAK, 0x00, 0x00); -+ -+ /* Payload CRC (ACK-type frames do not have a payload). */ -+ msgb_push_crc(msgb, msgb->ptr, 0); -+} -+ -+/** -+ * msgb_push_cmd() - Push a SSH command frame with payload to the buffer. -+ * @msgb: The message buffer. -+ * @seq: The sequence ID (SEQ) of the frame/packet. -+ * @rqid: The request ID (RQID) of the request contained in the frame. -+ * @rqst: The request to wrap in the frame. -+ */ -+static inline void msgb_push_cmd(struct msgbuf *msgb, u8 seq, u16 rqid, -+ const struct ssam_request *rqst) -+{ -+ const u8 type = SSH_FRAME_TYPE_DATA_SEQ; -+ u8 *cmd; -+ -+ /* SYN. */ -+ msgb_push_syn(msgb); -+ -+ /* Command frame + CRC. */ -+ msgb_push_frame(msgb, type, sizeof(struct ssh_command) + rqst->length, seq); -+ -+ /* Frame payload: Command struct + payload. */ -+ if (WARN_ON(msgb->ptr + sizeof(struct ssh_command) > msgb->end)) -+ return; -+ -+ cmd = msgb->ptr; -+ -+ __msgb_push_u8(msgb, SSH_PLD_TYPE_CMD); /* Payload type. */ -+ __msgb_push_u8(msgb, rqst->target_category); /* Target category. */ -+ __msgb_push_u8(msgb, rqst->target_id); /* Target ID (out). */ -+ __msgb_push_u8(msgb, 0x00); /* Target ID (in). */ -+ __msgb_push_u8(msgb, rqst->instance_id); /* Instance ID. */ -+ __msgb_push_u16(msgb, rqid); /* Request ID. */ -+ __msgb_push_u8(msgb, rqst->command_id); /* Command ID. */ -+ -+ /* Command payload. */ -+ msgb_push_buf(msgb, rqst->payload, rqst->length); -+ -+ /* CRC for command struct + payload. */ -+ msgb_push_crc(msgb, cmd, msgb->ptr - cmd); -+} -+ -+#endif /* _SURFACE_AGGREGATOR_SSH_MSGB_H */ -diff --git a/drivers/platform/x86/surface_aggregator/ssh_packet_layer.c b/drivers/platform/x86/surface_aggregator/ssh_packet_layer.c -new file mode 100644 -index 000000000000..5e08049fc3ac ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/ssh_packet_layer.c -@@ -0,0 +1,2074 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * SSH packet transport layer. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "ssh_msgb.h" -+#include "ssh_packet_layer.h" -+#include "ssh_parser.h" -+ -+#include "trace.h" -+ -+/* -+ * To simplify reasoning about the code below, we define a few concepts. The -+ * system below is similar to a state-machine for packets, however, there are -+ * too many states to explicitly write them down. To (somewhat) manage the -+ * states and packets we rely on flags, reference counting, and some simple -+ * concepts. State transitions are triggered by actions. -+ * -+ * >> Actions << -+ * -+ * - submit -+ * - transmission start (process next item in queue) -+ * - transmission finished (guaranteed to never be parallel to transmission -+ * start) -+ * - ACK received -+ * - NAK received (this is equivalent to issuing re-submit for all pending -+ * packets) -+ * - timeout (this is equivalent to re-issuing a submit or canceling) -+ * - cancel (non-pending and pending) -+ * -+ * >> Data Structures, Packet Ownership, General Overview << -+ * -+ * The code below employs two main data structures: The packet queue, -+ * containing all packets scheduled for transmission, and the set of pending -+ * packets, containing all packets awaiting an ACK. -+ * -+ * Shared ownership of a packet is controlled via reference counting. Inside -+ * the transport system are a total of five packet owners: -+ * -+ * - the packet queue, -+ * - the pending set, -+ * - the transmitter thread, -+ * - the receiver thread (via ACKing), and -+ * - the timeout work item. -+ * -+ * Normal operation is as follows: The initial reference of the packet is -+ * obtained by submitting the packet and queuing it. The receiver thread takes -+ * packets from the queue. By doing this, it does not increment the refcount -+ * but takes over the reference (removing it from the queue). If the packet is -+ * sequenced (i.e. needs to be ACKed by the client), the transmitter thread -+ * sets-up the timeout and adds the packet to the pending set before starting -+ * to transmit it. As the timeout is handled by a reaper task, no additional -+ * reference for it is needed. After the transmit is done, the reference held -+ * by the transmitter thread is dropped. If the packet is unsequenced (i.e. -+ * does not need an ACK), the packet is completed by the transmitter thread -+ * before dropping that reference. -+ * -+ * On receival of an ACK, the receiver thread removes and obtains the -+ * reference to the packet from the pending set. The receiver thread will then -+ * complete the packet and drop its reference. -+ * -+ * On receival of a NAK, the receiver thread re-submits all currently pending -+ * packets. -+ * -+ * Packet timeouts are detected by the timeout reaper. This is a task, -+ * scheduled depending on the earliest packet timeout expiration date, -+ * checking all currently pending packets if their timeout has expired. If the -+ * timeout of a packet has expired, it is re-submitted and the number of tries -+ * of this packet is incremented. If this number reaches its limit, the packet -+ * will be completed with a failure. -+ * -+ * On transmission failure (such as repeated packet timeouts), the completion -+ * callback is immediately run by on thread on which the error was detected. -+ * -+ * To ensure that a packet eventually leaves the system it is marked as -+ * "locked" directly before it is going to be completed or when it is -+ * canceled. Marking a packet as "locked" has the effect that passing and -+ * creating new references of the packet is disallowed. This means that the -+ * packet cannot be added to the queue, the pending set, and the timeout, or -+ * be picked up by the transmitter thread or receiver thread. To remove a -+ * packet from the system it has to be marked as locked and subsequently all -+ * references from the data structures (queue, pending) have to be removed. -+ * References held by threads will eventually be dropped automatically as -+ * their execution progresses. -+ * -+ * Note that the packet completion callback is, in case of success and for a -+ * sequenced packet, guaranteed to run on the receiver thread, thus providing -+ * a way to reliably identify responses to the packet. The packet completion -+ * callback is only run once and it does not indicate that the packet has -+ * fully left the system (for this, one should rely on the release method, -+ * triggered when the reference count of the packet reaches zero). In case of -+ * re-submission (and with somewhat unlikely timing), it may be possible that -+ * the packet is being re-transmitted while the completion callback runs. -+ * Completion will occur both on success and internal error, as well as when -+ * the packet is canceled. -+ * -+ * >> Flags << -+ * -+ * Flags are used to indicate the state and progression of a packet. Some flags -+ * have stricter guarantees than other: -+ * -+ * - locked -+ * Indicates if the packet is locked. If the packet is locked, passing and/or -+ * creating additional references to the packet is forbidden. The packet thus -+ * may not be queued, dequeued, or removed or added to the pending set. Note -+ * that the packet state flags may still change (e.g. it may be marked as -+ * ACKed, transmitted, ...). -+ * -+ * - completed -+ * Indicates if the packet completion callback has been executed or is about -+ * to be executed. This flag is used to ensure that the packet completion -+ * callback is only run once. -+ * -+ * - queued -+ * Indicates if a packet is present in the submission queue or not. This flag -+ * must only be modified with the queue lock held, and must be coherent to the -+ * presence of the packet in the queue. -+ * -+ * - pending -+ * Indicates if a packet is present in the set of pending packets or not. -+ * This flag must only be modified with the pending lock held, and must be -+ * coherent to the presence of the packet in the pending set. -+ * -+ * - transmitting -+ * Indicates if the packet is currently transmitting. In case of -+ * re-transmissions, it is only safe to wait on the "transmitted" completion -+ * after this flag has been set. The completion will be set both in success -+ * and error case. -+ * -+ * - transmitted -+ * Indicates if the packet has been transmitted. This flag is not cleared by -+ * the system, thus it indicates the first transmission only. -+ * -+ * - acked -+ * Indicates if the packet has been acknowledged by the client. There are no -+ * other guarantees given. For example, the packet may still be canceled -+ * and/or the completion may be triggered an error even though this bit is -+ * set. Rely on the status provided to the completion callback instead. -+ * -+ * - canceled -+ * Indicates if the packet has been canceled from the outside. There are no -+ * other guarantees given. Specifically, the packet may be completed by -+ * another part of the system before the cancellation attempts to complete it. -+ * -+ * >> General Notes << -+ * -+ * - To avoid deadlocks, if both queue and pending locks are required, the -+ * pending lock must be acquired before the queue lock. -+ * -+ * - The packet priority must be accessed only while holding the queue lock. -+ * -+ * - The packet timestamp must be accessed only while holding the pending -+ * lock. -+ */ -+ -+/* -+ * SSH_PTL_MAX_PACKET_TRIES - Maximum transmission attempts for packet. -+ * -+ * Maximum number of transmission attempts per sequenced packet in case of -+ * time-outs. Must be smaller than 16. If the packet times out after this -+ * amount of tries, the packet will be completed with %-ETIMEDOUT as status -+ * code. -+ */ -+#define SSH_PTL_MAX_PACKET_TRIES 3 -+ -+/* -+ * SSH_PTL_TX_TIMEOUT - Packet transmission timeout. -+ * -+ * Timeout in jiffies for packet transmission via the underlying serial -+ * device. If transmitting the packet takes longer than this timeout, the -+ * packet will be completed with -ETIMEDOUT. It will not be re-submitted. -+ */ -+#define SSH_PTL_TX_TIMEOUT HZ -+ -+/* -+ * SSH_PTL_PACKET_TIMEOUT - Packet response timeout. -+ * -+ * Timeout as ktime_t delta for ACKs. If we have not received an ACK in this -+ * time-frame after starting transmission, the packet will be re-submitted. -+ */ -+#define SSH_PTL_PACKET_TIMEOUT ms_to_ktime(1000) -+ -+/* -+ * SSH_PTL_PACKET_TIMEOUT_RESOLUTION - Packet timeout granularity. -+ * -+ * Time-resolution for timeouts. Should be larger than one jiffy to avoid -+ * direct re-scheduling of reaper work_struct. -+ */ -+#define SSH_PTL_PACKET_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) -+ -+/* -+ * SSH_PTL_MAX_PENDING - Maximum number of pending packets. -+ * -+ * Maximum number of sequenced packets concurrently waiting for an ACK. -+ * Packets marked as blocking will not be transmitted while this limit is -+ * reached. -+ */ -+#define SSH_PTL_MAX_PENDING 1 -+ -+/* -+ * SSH_PTL_RX_BUF_LEN - Evaluation-buffer size in bytes. -+ */ -+#define SSH_PTL_RX_BUF_LEN 4096 -+ -+/* -+ * SSH_PTL_RX_FIFO_LEN - Fifo input-buffer size in bytes. -+ */ -+#define SSH_PTL_RX_FIFO_LEN 4096 -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION -+ -+/** -+ * ssh_ptl_should_drop_ack_packet() - Error injection hook to drop ACK packets. -+ * -+ * Useful to test detection and handling of automated re-transmits by the EC. -+ * Specifically of packets that the EC considers not-ACKed but the driver -+ * already considers ACKed (due to dropped ACK). In this case, the EC -+ * re-transmits the packet-to-be-ACKed and the driver should detect it as -+ * duplicate/already handled. Note that the driver should still send an ACK -+ * for the re-transmitted packet. -+ */ -+static noinline bool ssh_ptl_should_drop_ack_packet(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_ack_packet, TRUE); -+ -+/** -+ * ssh_ptl_should_drop_nak_packet() - Error injection hook to drop NAK packets. -+ * -+ * Useful to test/force automated (timeout-based) re-transmit by the EC. -+ * Specifically, packets that have not reached the driver completely/with valid -+ * checksums. Only useful in combination with receival of (injected) bad data. -+ */ -+static noinline bool ssh_ptl_should_drop_nak_packet(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_nak_packet, TRUE); -+ -+/** -+ * ssh_ptl_should_drop_dsq_packet() - Error injection hook to drop sequenced -+ * data packet. -+ * -+ * Useful to test re-transmit timeout of the driver. If the data packet has not -+ * been ACKed after a certain time, the driver should re-transmit the packet up -+ * to limited number of times defined in SSH_PTL_MAX_PACKET_TRIES. -+ */ -+static noinline bool ssh_ptl_should_drop_dsq_packet(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_dsq_packet, TRUE); -+ -+/** -+ * ssh_ptl_should_fail_write() - Error injection hook to make -+ * serdev_device_write() fail. -+ * -+ * Hook to simulate errors in serdev_device_write when transmitting packets. -+ */ -+static noinline int ssh_ptl_should_fail_write(void) -+{ -+ return 0; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_fail_write, ERRNO); -+ -+/** -+ * ssh_ptl_should_corrupt_tx_data() - Error injection hook to simulate invalid -+ * data being sent to the EC. -+ * -+ * Hook to simulate corrupt/invalid data being sent from host (driver) to EC. -+ * Causes the packet data to be actively corrupted by overwriting it with -+ * pre-defined values, such that it becomes invalid, causing the EC to respond -+ * with a NAK packet. Useful to test handling of NAK packets received by the -+ * driver. -+ */ -+static noinline bool ssh_ptl_should_corrupt_tx_data(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_tx_data, TRUE); -+ -+/** -+ * ssh_ptl_should_corrupt_rx_syn() - Error injection hook to simulate invalid -+ * data being sent by the EC. -+ * -+ * Hook to simulate invalid SYN bytes, i.e. an invalid start of messages and -+ * test handling thereof in the driver. -+ */ -+static noinline bool ssh_ptl_should_corrupt_rx_syn(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_syn, TRUE); -+ -+/** -+ * ssh_ptl_should_corrupt_rx_data() - Error injection hook to simulate invalid -+ * data being sent by the EC. -+ * -+ * Hook to simulate invalid data/checksum of the message frame and test handling -+ * thereof in the driver. -+ */ -+static noinline bool ssh_ptl_should_corrupt_rx_data(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_data, TRUE); -+ -+static bool __ssh_ptl_should_drop_ack_packet(struct ssh_packet *packet) -+{ -+ if (likely(!ssh_ptl_should_drop_ack_packet())) -+ return false; -+ -+ trace_ssam_ei_tx_drop_ack_packet(packet); -+ ptl_info(packet->ptl, "packet error injection: dropping ACK packet %p\n", -+ packet); -+ -+ return true; -+} -+ -+static bool __ssh_ptl_should_drop_nak_packet(struct ssh_packet *packet) -+{ -+ if (likely(!ssh_ptl_should_drop_nak_packet())) -+ return false; -+ -+ trace_ssam_ei_tx_drop_nak_packet(packet); -+ ptl_info(packet->ptl, "packet error injection: dropping NAK packet %p\n", -+ packet); -+ -+ return true; -+} -+ -+static bool __ssh_ptl_should_drop_dsq_packet(struct ssh_packet *packet) -+{ -+ if (likely(!ssh_ptl_should_drop_dsq_packet())) -+ return false; -+ -+ trace_ssam_ei_tx_drop_dsq_packet(packet); -+ ptl_info(packet->ptl, -+ "packet error injection: dropping sequenced data packet %p\n", -+ packet); -+ -+ return true; -+} -+ -+static bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) -+{ -+ /* Ignore packets that don't carry any data (i.e. flush). */ -+ if (!packet->data.ptr || !packet->data.len) -+ return false; -+ -+ switch (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)]) { -+ case SSH_FRAME_TYPE_ACK: -+ return __ssh_ptl_should_drop_ack_packet(packet); -+ -+ case SSH_FRAME_TYPE_NAK: -+ return __ssh_ptl_should_drop_nak_packet(packet); -+ -+ case SSH_FRAME_TYPE_DATA_SEQ: -+ return __ssh_ptl_should_drop_dsq_packet(packet); -+ -+ default: -+ return false; -+ } -+} -+ -+static int ssh_ptl_write_buf(struct ssh_ptl *ptl, struct ssh_packet *packet, -+ const unsigned char *buf, size_t count) -+{ -+ int status; -+ -+ status = ssh_ptl_should_fail_write(); -+ if (unlikely(status)) { -+ trace_ssam_ei_tx_fail_write(packet, status); -+ ptl_info(packet->ptl, -+ "packet error injection: simulating transmit error %d, packet %p\n", -+ status, packet); -+ -+ return status; -+ } -+ -+ return serdev_device_write_buf(ptl->serdev, buf, count); -+} -+ -+static void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) -+{ -+ /* Ignore packets that don't carry any data (i.e. flush). */ -+ if (!packet->data.ptr || !packet->data.len) -+ return; -+ -+ /* Only allow sequenced data packets to be modified. */ -+ if (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)] != SSH_FRAME_TYPE_DATA_SEQ) -+ return; -+ -+ if (likely(!ssh_ptl_should_corrupt_tx_data())) -+ return; -+ -+ trace_ssam_ei_tx_corrupt_data(packet); -+ ptl_info(packet->ptl, -+ "packet error injection: simulating invalid transmit data on packet %p\n", -+ packet); -+ -+ /* -+ * NB: The value 0xb3 has been chosen more or less randomly so that it -+ * doesn't have any (major) overlap with the SYN bytes (aa 55) and is -+ * non-trivial (i.e. non-zero, non-0xff). -+ */ -+ memset(packet->data.ptr, 0xb3, packet->data.len); -+} -+ -+static void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, -+ struct ssam_span *data) -+{ -+ struct ssam_span frame; -+ -+ /* Check if there actually is something to corrupt. */ -+ if (!sshp_find_syn(data, &frame)) -+ return; -+ -+ if (likely(!ssh_ptl_should_corrupt_rx_syn())) -+ return; -+ -+ trace_ssam_ei_rx_corrupt_syn(data->len); -+ -+ data->ptr[1] = 0xb3; /* Set second byte of SYN to "random" value. */ -+} -+ -+static void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, -+ struct ssam_span *frame) -+{ -+ size_t payload_len, message_len; -+ struct ssh_frame *sshf; -+ -+ /* Ignore incomplete messages, will get handled once it's complete. */ -+ if (frame->len < SSH_MESSAGE_LENGTH(0)) -+ return; -+ -+ /* Ignore incomplete messages, part 2. */ -+ payload_len = get_unaligned_le16(&frame->ptr[SSH_MSGOFFSET_FRAME(len)]); -+ message_len = SSH_MESSAGE_LENGTH(payload_len); -+ if (frame->len < message_len) -+ return; -+ -+ if (likely(!ssh_ptl_should_corrupt_rx_data())) -+ return; -+ -+ sshf = (struct ssh_frame *)&frame->ptr[SSH_MSGOFFSET_FRAME(type)]; -+ trace_ssam_ei_rx_corrupt_data(sshf); -+ -+ /* -+ * Flip bits in first byte of payload checksum. This is basically -+ * equivalent to a payload/frame data error without us having to worry -+ * about (the, arguably pretty small, probability of) accidental -+ * checksum collisions. -+ */ -+ frame->ptr[frame->len - 2] = ~frame->ptr[frame->len - 2]; -+} -+ -+#else /* CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION */ -+ -+static inline bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) -+{ -+ return false; -+} -+ -+static inline int ssh_ptl_write_buf(struct ssh_ptl *ptl, -+ struct ssh_packet *packet, -+ const unsigned char *buf, -+ size_t count) -+{ -+ return serdev_device_write_buf(ptl->serdev, buf, count); -+} -+ -+static inline void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) -+{ -+} -+ -+static inline void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, -+ struct ssam_span *data) -+{ -+} -+ -+static inline void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, -+ struct ssam_span *frame) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION */ -+ -+static void __ssh_ptl_packet_release(struct kref *kref) -+{ -+ struct ssh_packet *p = container_of(kref, struct ssh_packet, refcnt); -+ -+ trace_ssam_packet_release(p); -+ -+ ptl_dbg_cond(p->ptl, "ptl: releasing packet %p\n", p); -+ p->ops->release(p); -+} -+ -+/** -+ * ssh_packet_get() - Increment reference count of packet. -+ * @packet: The packet to increment the reference count of. -+ * -+ * Increments the reference count of the given packet. See ssh_packet_put() -+ * for the counter-part of this function. -+ * -+ * Return: Returns the packet provided as input. -+ */ -+struct ssh_packet *ssh_packet_get(struct ssh_packet *packet) -+{ -+ if (packet) -+ kref_get(&packet->refcnt); -+ return packet; -+} -+EXPORT_SYMBOL_GPL(ssh_packet_get); -+ -+/** -+ * ssh_packet_put() - Decrement reference count of packet. -+ * @packet: The packet to decrement the reference count of. -+ * -+ * If the reference count reaches zero, the ``release`` callback specified in -+ * the packet's &struct ssh_packet_ops, i.e. ``packet->ops->release``, will be -+ * called. -+ * -+ * See ssh_packet_get() for the counter-part of this function. -+ */ -+void ssh_packet_put(struct ssh_packet *packet) -+{ -+ if (packet) -+ kref_put(&packet->refcnt, __ssh_ptl_packet_release); -+} -+EXPORT_SYMBOL_GPL(ssh_packet_put); -+ -+static u8 ssh_packet_get_seq(struct ssh_packet *packet) -+{ -+ return packet->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; -+} -+ -+/** -+ * ssh_packet_init() - Initialize SSH packet. -+ * @packet: The packet to initialize. -+ * @type: Type-flags of the packet. -+ * @priority: Priority of the packet. See SSH_PACKET_PRIORITY() for details. -+ * @ops: Packet operations. -+ * -+ * Initializes the given SSH packet. Sets the transmission buffer pointer to -+ * %NULL and the transmission buffer length to zero. For data-type packets, -+ * this buffer has to be set separately via ssh_packet_set_data() before -+ * submission, and must contain a valid SSH message, i.e. frame with optional -+ * payload of any type. -+ */ -+void ssh_packet_init(struct ssh_packet *packet, unsigned long type, -+ u8 priority, const struct ssh_packet_ops *ops) -+{ -+ kref_init(&packet->refcnt); -+ -+ packet->ptl = NULL; -+ INIT_LIST_HEAD(&packet->queue_node); -+ INIT_LIST_HEAD(&packet->pending_node); -+ -+ packet->state = type & SSH_PACKET_FLAGS_TY_MASK; -+ packet->priority = priority; -+ packet->timestamp = KTIME_MAX; -+ -+ packet->data.ptr = NULL; -+ packet->data.len = 0; -+ -+ packet->ops = ops; -+} -+ -+static struct kmem_cache *ssh_ctrl_packet_cache; -+ -+/** -+ * ssh_ctrl_packet_cache_init() - Initialize the control packet cache. -+ */ -+int ssh_ctrl_packet_cache_init(void) -+{ -+ const unsigned int size = sizeof(struct ssh_packet) + SSH_MSG_LEN_CTRL; -+ const unsigned int align = __alignof__(struct ssh_packet); -+ struct kmem_cache *cache; -+ -+ cache = kmem_cache_create("ssam_ctrl_packet", size, align, 0, NULL); -+ if (!cache) -+ return -ENOMEM; -+ -+ ssh_ctrl_packet_cache = cache; -+ return 0; -+} -+ -+/** -+ * ssh_ctrl_packet_cache_destroy() - Deinitialize the control packet cache. -+ */ -+void ssh_ctrl_packet_cache_destroy(void) -+{ -+ kmem_cache_destroy(ssh_ctrl_packet_cache); -+ ssh_ctrl_packet_cache = NULL; -+} -+ -+/** -+ * ssh_ctrl_packet_alloc() - Allocate packet from control packet cache. -+ * @packet: Where the pointer to the newly allocated packet should be stored. -+ * @buffer: The buffer corresponding to this packet. -+ * @flags: Flags used for allocation. -+ * -+ * Allocates a packet and corresponding transport buffer from the control -+ * packet cache. Sets the packet's buffer reference to the allocated buffer. -+ * The packet must be freed via ssh_ctrl_packet_free(), which will also free -+ * the corresponding buffer. The corresponding buffer must not be freed -+ * separately. Intended to be used with %ssh_ptl_ctrl_packet_ops as packet -+ * operations. -+ * -+ * Return: Returns zero on success, %-ENOMEM if the allocation failed. -+ */ -+static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, -+ struct ssam_span *buffer, gfp_t flags) -+{ -+ *packet = kmem_cache_alloc(ssh_ctrl_packet_cache, flags); -+ if (!*packet) -+ return -ENOMEM; -+ -+ buffer->ptr = (u8 *)(*packet + 1); -+ buffer->len = SSH_MSG_LEN_CTRL; -+ -+ trace_ssam_ctrl_packet_alloc(*packet, buffer->len); -+ return 0; -+} -+ -+/** -+ * ssh_ctrl_packet_free() - Free packet allocated from control packet cache. -+ * @p: The packet to free. -+ */ -+static void ssh_ctrl_packet_free(struct ssh_packet *p) -+{ -+ trace_ssam_ctrl_packet_free(p); -+ kmem_cache_free(ssh_ctrl_packet_cache, p); -+} -+ -+static const struct ssh_packet_ops ssh_ptl_ctrl_packet_ops = { -+ .complete = NULL, -+ .release = ssh_ctrl_packet_free, -+}; -+ -+static void ssh_ptl_timeout_reaper_mod(struct ssh_ptl *ptl, ktime_t now, -+ ktime_t expires) -+{ -+ unsigned long delta = msecs_to_jiffies(ktime_ms_delta(expires, now)); -+ ktime_t aexp = ktime_add(expires, SSH_PTL_PACKET_TIMEOUT_RESOLUTION); -+ -+ spin_lock(&ptl->rtx_timeout.lock); -+ -+ /* Re-adjust / schedule reaper only if it is above resolution delta. */ -+ if (ktime_before(aexp, ptl->rtx_timeout.expires)) { -+ ptl->rtx_timeout.expires = expires; -+ mod_delayed_work(system_wq, &ptl->rtx_timeout.reaper, delta); -+ } -+ -+ spin_unlock(&ptl->rtx_timeout.lock); -+} -+ -+/* Must be called with queue lock held. */ -+static void ssh_packet_next_try(struct ssh_packet *p) -+{ -+ u8 base = ssh_packet_priority_get_base(p->priority); -+ u8 try = ssh_packet_priority_get_try(p->priority); -+ -+ lockdep_assert_held(&p->ptl->queue.lock); -+ -+ /* -+ * Ensure that we write the priority in one go via WRITE_ONCE() so we -+ * can access it via READ_ONCE() for tracing. Note that other access -+ * is guarded by the queue lock, so no need to use READ_ONCE() there. -+ */ -+ WRITE_ONCE(p->priority, __SSH_PACKET_PRIORITY(base, try + 1)); -+} -+ -+/* Must be called with queue lock held. */ -+static struct list_head *__ssh_ptl_queue_find_entrypoint(struct ssh_packet *p) -+{ -+ struct list_head *head; -+ struct ssh_packet *q; -+ -+ lockdep_assert_held(&p->ptl->queue.lock); -+ -+ /* -+ * We generally assume that there are less control (ACK/NAK) packets -+ * and re-submitted data packets as there are normal data packets (at -+ * least in situations in which many packets are queued; if there -+ * aren't many packets queued the decision on how to iterate should be -+ * basically irrelevant; the number of control/data packets is more or -+ * less limited via the maximum number of pending packets). Thus, when -+ * inserting a control or re-submitted data packet, (determined by -+ * their priority), we search from front to back. Normal data packets -+ * are, usually queued directly at the tail of the queue, so for those -+ * search from back to front. -+ */ -+ -+ if (p->priority > SSH_PACKET_PRIORITY(DATA, 0)) { -+ list_for_each(head, &p->ptl->queue.head) { -+ q = list_entry(head, struct ssh_packet, queue_node); -+ -+ if (q->priority < p->priority) -+ break; -+ } -+ } else { -+ list_for_each_prev(head, &p->ptl->queue.head) { -+ q = list_entry(head, struct ssh_packet, queue_node); -+ -+ if (q->priority >= p->priority) { -+ head = head->next; -+ break; -+ } -+ } -+ } -+ -+ return head; -+} -+ -+/* Must be called with queue lock held. */ -+static int __ssh_ptl_queue_push(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ struct list_head *head; -+ -+ lockdep_assert_held(&ptl->queue.lock); -+ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) -+ return -ESHUTDOWN; -+ -+ /* Avoid further transitions when canceling/completing. */ -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)) -+ return -EINVAL; -+ -+ /* If this packet has already been queued, do not add it. */ -+ if (test_and_set_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state)) -+ return -EALREADY; -+ -+ head = __ssh_ptl_queue_find_entrypoint(packet); -+ -+ list_add_tail(&ssh_packet_get(packet)->queue_node, head); -+ return 0; -+} -+ -+static int ssh_ptl_queue_push(struct ssh_packet *packet) -+{ -+ int status; -+ -+ spin_lock(&packet->ptl->queue.lock); -+ status = __ssh_ptl_queue_push(packet); -+ spin_unlock(&packet->ptl->queue.lock); -+ -+ return status; -+} -+ -+static void ssh_ptl_queue_remove(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ spin_lock(&ptl->queue.lock); -+ -+ if (!test_and_clear_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state)) { -+ spin_unlock(&ptl->queue.lock); -+ return; -+ } -+ -+ list_del(&packet->queue_node); -+ -+ spin_unlock(&ptl->queue.lock); -+ ssh_packet_put(packet); -+} -+ -+static void ssh_ptl_pending_push(struct ssh_packet *p) -+{ -+ struct ssh_ptl *ptl = p->ptl; -+ const ktime_t timestamp = ktime_get_coarse_boottime(); -+ const ktime_t timeout = ptl->rtx_timeout.timeout; -+ -+ /* -+ * Note: We can get the time for the timestamp before acquiring the -+ * lock as this is the only place we're setting it and this function -+ * is called only from the transmitter thread. Thus it is not possible -+ * to overwrite the timestamp with an outdated value below. -+ */ -+ -+ spin_lock(&ptl->pending.lock); -+ -+ /* If we are canceling/completing this packet, do not add it. */ -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) { -+ spin_unlock(&ptl->pending.lock); -+ return; -+ } -+ -+ /* -+ * On re-submission, the packet has already been added the pending -+ * set. We still need to update the timestamp as the packet timeout is -+ * reset for each (re-)submission. -+ */ -+ p->timestamp = timestamp; -+ -+ /* In case it is already pending (e.g. re-submission), do not add it. */ -+ if (!test_and_set_bit(SSH_PACKET_SF_PENDING_BIT, &p->state)) { -+ atomic_inc(&ptl->pending.count); -+ list_add_tail(&ssh_packet_get(p)->pending_node, &ptl->pending.head); -+ } -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ /* Arm/update timeout reaper. */ -+ ssh_ptl_timeout_reaper_mod(ptl, timestamp, timestamp + timeout); -+} -+ -+static void ssh_ptl_pending_remove(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ spin_lock(&ptl->pending.lock); -+ -+ if (!test_and_clear_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state)) { -+ spin_unlock(&ptl->pending.lock); -+ return; -+ } -+ -+ list_del(&packet->pending_node); -+ atomic_dec(&ptl->pending.count); -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ ssh_packet_put(packet); -+} -+ -+/* Warning: Does not check/set "completed" bit. */ -+static void __ssh_ptl_complete(struct ssh_packet *p, int status) -+{ -+ struct ssh_ptl *ptl = READ_ONCE(p->ptl); -+ -+ trace_ssam_packet_complete(p, status); -+ ptl_dbg_cond(ptl, "ptl: completing packet %p (status: %d)\n", p, status); -+ -+ if (p->ops->complete) -+ p->ops->complete(p, status); -+} -+ -+static void ssh_ptl_remove_and_complete(struct ssh_packet *p, int status) -+{ -+ /* -+ * A call to this function should in general be preceded by -+ * set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->flags) to avoid re-adding the -+ * packet to the structures it's going to be removed from. -+ * -+ * The set_bit call does not need explicit memory barriers as the -+ * implicit barrier of the test_and_set_bit() call below ensure that the -+ * flag is visible before we actually attempt to remove the packet. -+ */ -+ -+ if (test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) -+ return; -+ -+ ssh_ptl_queue_remove(p); -+ ssh_ptl_pending_remove(p); -+ -+ __ssh_ptl_complete(p, status); -+} -+ -+static bool ssh_ptl_tx_can_process(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &packet->state)) -+ return !atomic_read(&ptl->pending.count); -+ -+ /* We can always process non-blocking packets. */ -+ if (!test_bit(SSH_PACKET_TY_BLOCKING_BIT, &packet->state)) -+ return true; -+ -+ /* If we are already waiting for this packet, send it again. */ -+ if (test_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state)) -+ return true; -+ -+ /* Otherwise: Check if we have the capacity to send. */ -+ return atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING; -+} -+ -+static struct ssh_packet *ssh_ptl_tx_pop(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *packet = ERR_PTR(-ENOENT); -+ struct ssh_packet *p, *n; -+ -+ spin_lock(&ptl->queue.lock); -+ list_for_each_entry_safe(p, n, &ptl->queue.head, queue_node) { -+ /* -+ * If we are canceling or completing this packet, ignore it. -+ * It's going to be removed from this queue shortly. -+ */ -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) -+ continue; -+ -+ /* -+ * Packets should be ordered non-blocking/to-be-resent first. -+ * If we cannot process this packet, assume that we can't -+ * process any following packet either and abort. -+ */ -+ if (!ssh_ptl_tx_can_process(p)) { -+ packet = ERR_PTR(-EBUSY); -+ break; -+ } -+ -+ /* -+ * We are allowed to change the state now. Remove it from the -+ * queue and mark it as being transmitted. -+ */ -+ -+ list_del(&p->queue_node); -+ -+ set_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &p->state); -+ /* Ensure that state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); -+ -+ /* -+ * Update number of tries. This directly influences the -+ * priority in case the packet is re-submitted (e.g. via -+ * timeout/NAK). Note that all reads and writes to the -+ * priority after the first submission are guarded by the -+ * queue lock. -+ */ -+ ssh_packet_next_try(p); -+ -+ packet = p; -+ break; -+ } -+ spin_unlock(&ptl->queue.lock); -+ -+ return packet; -+} -+ -+static struct ssh_packet *ssh_ptl_tx_next(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *p; -+ -+ p = ssh_ptl_tx_pop(ptl); -+ if (IS_ERR(p)) -+ return p; -+ -+ if (test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) { -+ ptl_dbg(ptl, "ptl: transmitting sequenced packet %p\n", p); -+ ssh_ptl_pending_push(p); -+ } else { -+ ptl_dbg(ptl, "ptl: transmitting non-sequenced packet %p\n", p); -+ } -+ -+ return p; -+} -+ -+static void ssh_ptl_tx_compl_success(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ ptl_dbg(ptl, "ptl: successfully transmitted packet %p\n", packet); -+ -+ /* Transition state to "transmitted". */ -+ set_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &packet->state); -+ /* Ensure that state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &packet->state); -+ -+ /* If the packet is unsequenced, we're done: Lock and complete. */ -+ if (!test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &packet->state)) { -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state); -+ ssh_ptl_remove_and_complete(packet, 0); -+ } -+ -+ /* -+ * Notify that a packet transmission has finished. In general we're only -+ * waiting for one packet (if any), so wake_up_all should be fine. -+ */ -+ wake_up_all(&ptl->tx.packet_wq); -+} -+ -+static void ssh_ptl_tx_compl_error(struct ssh_packet *packet, int status) -+{ -+ /* Transmission failure: Lock the packet and try to complete it. */ -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state); -+ /* Ensure that state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &packet->state); -+ -+ ptl_err(packet->ptl, "ptl: transmission error: %d\n", status); -+ ptl_dbg(packet->ptl, "ptl: failed to transmit packet: %p\n", packet); -+ -+ ssh_ptl_remove_and_complete(packet, status); -+ -+ /* -+ * Notify that a packet transmission has finished. In general we're only -+ * waiting for one packet (if any), so wake_up_all should be fine. -+ */ -+ wake_up_all(&packet->ptl->tx.packet_wq); -+} -+ -+static long ssh_ptl_tx_wait_packet(struct ssh_ptl *ptl) -+{ -+ int status; -+ -+ status = wait_for_completion_interruptible(&ptl->tx.thread_cplt_pkt); -+ reinit_completion(&ptl->tx.thread_cplt_pkt); -+ -+ /* -+ * Ensure completion is cleared before continuing to avoid lost update -+ * problems. -+ */ -+ smp_mb__after_atomic(); -+ -+ return status; -+} -+ -+static long ssh_ptl_tx_wait_transfer(struct ssh_ptl *ptl, long timeout) -+{ -+ long status; -+ -+ status = wait_for_completion_interruptible_timeout(&ptl->tx.thread_cplt_tx, -+ timeout); -+ reinit_completion(&ptl->tx.thread_cplt_tx); -+ -+ /* -+ * Ensure completion is cleared before continuing to avoid lost update -+ * problems. -+ */ -+ smp_mb__after_atomic(); -+ -+ return status; -+} -+ -+static int ssh_ptl_tx_packet(struct ssh_ptl *ptl, struct ssh_packet *packet) -+{ -+ long timeout = SSH_PTL_TX_TIMEOUT; -+ size_t offset = 0; -+ -+ /* Note: Flush-packets don't have any data. */ -+ if (unlikely(!packet->data.ptr)) -+ return 0; -+ -+ /* Error injection: drop packet to simulate transmission problem. */ -+ if (ssh_ptl_should_drop_packet(packet)) -+ return 0; -+ -+ /* Error injection: simulate invalid packet data. */ -+ ssh_ptl_tx_inject_invalid_data(packet); -+ -+ ptl_dbg(ptl, "tx: sending data (length: %zu)\n", packet->data.len); -+ print_hex_dump_debug("tx: ", DUMP_PREFIX_OFFSET, 16, 1, -+ packet->data.ptr, packet->data.len, false); -+ -+ do { -+ ssize_t status, len; -+ u8 *buf; -+ -+ buf = packet->data.ptr + offset; -+ len = packet->data.len - offset; -+ -+ status = ssh_ptl_write_buf(ptl, packet, buf, len); -+ if (status < 0) -+ return status; -+ -+ if (status == len) -+ return 0; -+ -+ offset += status; -+ -+ timeout = ssh_ptl_tx_wait_transfer(ptl, timeout); -+ if (kthread_should_stop() || !atomic_read(&ptl->tx.running)) -+ return -ESHUTDOWN; -+ -+ if (timeout < 0) -+ return -EINTR; -+ -+ if (timeout == 0) -+ return -ETIMEDOUT; -+ } while (true); -+} -+ -+static int ssh_ptl_tx_threadfn(void *data) -+{ -+ struct ssh_ptl *ptl = data; -+ -+ while (!kthread_should_stop() && atomic_read(&ptl->tx.running)) { -+ struct ssh_packet *packet; -+ int status; -+ -+ /* Try to get the next packet. */ -+ packet = ssh_ptl_tx_next(ptl); -+ -+ /* If no packet can be processed, we are done. */ -+ if (IS_ERR(packet)) { -+ ssh_ptl_tx_wait_packet(ptl); -+ continue; -+ } -+ -+ /* Transfer and complete packet. */ -+ status = ssh_ptl_tx_packet(ptl, packet); -+ if (status) -+ ssh_ptl_tx_compl_error(packet, status); -+ else -+ ssh_ptl_tx_compl_success(packet); -+ -+ ssh_packet_put(packet); -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssh_ptl_tx_wakeup_packet() - Wake up packet transmitter thread for new -+ * packet. -+ * @ptl: The packet transport layer. -+ * -+ * Wakes up the packet transmitter thread, notifying it that a new packet has -+ * arrived and is ready for transfer. If the packet transport layer has been -+ * shut down, calls to this function will be ignored. -+ */ -+static void ssh_ptl_tx_wakeup_packet(struct ssh_ptl *ptl) -+{ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) -+ return; -+ -+ complete(&ptl->tx.thread_cplt_pkt); -+} -+ -+/** -+ * ssh_ptl_tx_start() - Start packet transmitter thread. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_ptl_tx_start(struct ssh_ptl *ptl) -+{ -+ atomic_set_release(&ptl->tx.running, 1); -+ -+ ptl->tx.thread = kthread_run(ssh_ptl_tx_threadfn, ptl, "ssam_serial_hub-tx"); -+ if (IS_ERR(ptl->tx.thread)) -+ return PTR_ERR(ptl->tx.thread); -+ -+ return 0; -+} -+ -+/** -+ * ssh_ptl_tx_stop() - Stop packet transmitter thread. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_ptl_tx_stop(struct ssh_ptl *ptl) -+{ -+ int status = 0; -+ -+ if (!IS_ERR_OR_NULL(ptl->tx.thread)) { -+ /* Tell thread to stop. */ -+ atomic_set_release(&ptl->tx.running, 0); -+ -+ /* -+ * Wake up thread in case it is paused. Do not use wakeup -+ * helpers as this may be called when the shutdown bit has -+ * already been set. -+ */ -+ complete(&ptl->tx.thread_cplt_pkt); -+ complete(&ptl->tx.thread_cplt_tx); -+ -+ /* Finally, wait for thread to stop. */ -+ status = kthread_stop(ptl->tx.thread); -+ ptl->tx.thread = NULL; -+ } -+ -+ return status; -+} -+ -+static struct ssh_packet *ssh_ptl_ack_pop(struct ssh_ptl *ptl, u8 seq_id) -+{ -+ struct ssh_packet *packet = ERR_PTR(-ENOENT); -+ struct ssh_packet *p, *n; -+ -+ spin_lock(&ptl->pending.lock); -+ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { -+ /* -+ * We generally expect packets to be in order, so first packet -+ * to be added to pending is first to be sent, is first to be -+ * ACKed. -+ */ -+ if (unlikely(ssh_packet_get_seq(p) != seq_id)) -+ continue; -+ -+ /* -+ * In case we receive an ACK while handling a transmission -+ * error completion. The packet will be removed shortly. -+ */ -+ if (unlikely(test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state))) { -+ packet = ERR_PTR(-EPERM); -+ break; -+ } -+ -+ /* -+ * Mark the packet as ACKed and remove it from pending by -+ * removing its node and decrementing the pending counter. -+ */ -+ set_bit(SSH_PACKET_SF_ACKED_BIT, &p->state); -+ /* Ensure that state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); -+ -+ atomic_dec(&ptl->pending.count); -+ list_del(&p->pending_node); -+ packet = p; -+ -+ break; -+ } -+ spin_unlock(&ptl->pending.lock); -+ -+ return packet; -+} -+ -+static void ssh_ptl_wait_until_transmitted(struct ssh_packet *packet) -+{ -+ wait_event(packet->ptl->tx.packet_wq, -+ test_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &packet->state) || -+ test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)); -+} -+ -+static void ssh_ptl_acknowledge(struct ssh_ptl *ptl, u8 seq) -+{ -+ struct ssh_packet *p; -+ -+ p = ssh_ptl_ack_pop(ptl, seq); -+ if (IS_ERR(p)) { -+ if (PTR_ERR(p) == -ENOENT) { -+ /* -+ * The packet has not been found in the set of pending -+ * packets. -+ */ -+ ptl_warn(ptl, "ptl: received ACK for non-pending packet\n"); -+ } else { -+ /* -+ * The packet is pending, but we are not allowed to take -+ * it because it has been locked. -+ */ -+ WARN_ON(PTR_ERR(p) != -EPERM); -+ } -+ return; -+ } -+ -+ ptl_dbg(ptl, "ptl: received ACK for packet %p\n", p); -+ -+ /* -+ * It is possible that the packet has been transmitted, but the state -+ * has not been updated from "transmitting" to "transmitted" yet. -+ * In that case, we need to wait for this transition to occur in order -+ * to determine between success or failure. -+ * -+ * On transmission failure, the packet will be locked after this call. -+ * On success, the transmitted bit will be set. -+ */ -+ ssh_ptl_wait_until_transmitted(p); -+ -+ /* -+ * The packet will already be locked in case of a transmission error or -+ * cancellation. Let the transmitter or cancellation issuer complete the -+ * packet. -+ */ -+ if (unlikely(test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state))) { -+ if (unlikely(!test_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &p->state))) -+ ptl_err(ptl, "ptl: received ACK before packet had been fully transmitted\n"); -+ -+ ssh_packet_put(p); -+ return; -+ } -+ -+ ssh_ptl_remove_and_complete(p, 0); -+ ssh_packet_put(p); -+ -+ if (atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING) -+ ssh_ptl_tx_wakeup_packet(ptl); -+} -+ -+/** -+ * ssh_ptl_submit() - Submit a packet to the transport layer. -+ * @ptl: The packet transport layer to submit the packet to. -+ * @p: The packet to submit. -+ * -+ * Submits a new packet to the transport layer, queuing it to be sent. This -+ * function should not be used for re-submission. -+ * -+ * Return: Returns zero on success, %-EINVAL if a packet field is invalid or -+ * the packet has been canceled prior to submission, %-EALREADY if the packet -+ * has already been submitted, or %-ESHUTDOWN if the packet transport layer -+ * has been shut down. -+ */ -+int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p) -+{ -+ struct ssh_ptl *ptl_old; -+ int status; -+ -+ trace_ssam_packet_submit(p); -+ -+ /* Validate packet fields. */ -+ if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &p->state)) { -+ if (p->data.ptr || test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) -+ return -EINVAL; -+ } else if (!p->data.ptr) { -+ return -EINVAL; -+ } -+ -+ /* -+ * The ptl reference only gets set on or before the first submission. -+ * After the first submission, it has to be read-only. -+ * -+ * Note that ptl may already be set from upper-layer request -+ * submission, thus we cannot expect it to be NULL. -+ */ -+ ptl_old = READ_ONCE(p->ptl); -+ if (!ptl_old) -+ WRITE_ONCE(p->ptl, ptl); -+ else if (WARN_ON(ptl_old != ptl)) -+ return -EALREADY; /* Submitted on different PTL. */ -+ -+ status = ssh_ptl_queue_push(p); -+ if (status) -+ return status; -+ -+ if (!test_bit(SSH_PACKET_TY_BLOCKING_BIT, &p->state) || -+ (atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING)) -+ ssh_ptl_tx_wakeup_packet(ptl); -+ -+ return 0; -+} -+ -+/* -+ * __ssh_ptl_resubmit() - Re-submit a packet to the transport layer. -+ * @packet: The packet to re-submit. -+ * -+ * Re-submits the given packet: Checks if it can be re-submitted and queues it -+ * if it can, resetting the packet timestamp in the process. Must be called -+ * with the pending lock held. -+ * -+ * Return: Returns %-ECANCELED if the packet has exceeded its number of tries, -+ * %-EINVAL if the packet has been locked, %-EALREADY if the packet is already -+ * on the queue, and %-ESHUTDOWN if the transmission layer has been shut down. -+ */ -+static int __ssh_ptl_resubmit(struct ssh_packet *packet) -+{ -+ int status; -+ u8 try; -+ -+ lockdep_assert_held(&packet->ptl->pending.lock); -+ -+ trace_ssam_packet_resubmit(packet); -+ -+ spin_lock(&packet->ptl->queue.lock); -+ -+ /* Check if the packet is out of tries. */ -+ try = ssh_packet_priority_get_try(packet->priority); -+ if (try >= SSH_PTL_MAX_PACKET_TRIES) { -+ spin_unlock(&packet->ptl->queue.lock); -+ return -ECANCELED; -+ } -+ -+ status = __ssh_ptl_queue_push(packet); -+ if (status) { -+ /* -+ * An error here indicates that the packet has either already -+ * been queued, been locked, or the transport layer is being -+ * shut down. In all cases: Ignore the error. -+ */ -+ spin_unlock(&packet->ptl->queue.lock); -+ return status; -+ } -+ -+ packet->timestamp = KTIME_MAX; -+ -+ spin_unlock(&packet->ptl->queue.lock); -+ return 0; -+} -+ -+static void ssh_ptl_resubmit_pending(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *p; -+ bool resub = false; -+ -+ /* -+ * Note: We deliberately do not remove/attempt to cancel and complete -+ * packets that are out of tires in this function. The packet will be -+ * eventually canceled and completed by the timeout. Removing the packet -+ * here could lead to overly eager cancellation if the packet has not -+ * been re-transmitted yet but the tries-counter already updated (i.e -+ * ssh_ptl_tx_next() removed the packet from the queue and updated the -+ * counter, but re-transmission for the last try has not actually -+ * started yet). -+ */ -+ -+ spin_lock(&ptl->pending.lock); -+ -+ /* Re-queue all pending packets. */ -+ list_for_each_entry(p, &ptl->pending.head, pending_node) { -+ /* -+ * Re-submission fails if the packet is out of tries, has been -+ * locked, is already queued, or the layer is being shut down. -+ * No need to re-schedule tx-thread in those cases. -+ */ -+ if (!__ssh_ptl_resubmit(p)) -+ resub = true; -+ } -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ if (resub) -+ ssh_ptl_tx_wakeup_packet(ptl); -+} -+ -+/** -+ * ssh_ptl_cancel() - Cancel a packet. -+ * @p: The packet to cancel. -+ * -+ * Cancels a packet. There are no guarantees on when completion and release -+ * callbacks will be called. This may occur during execution of this function -+ * or may occur at any point later. -+ * -+ * Note that it is not guaranteed that the packet will actually be canceled if -+ * the packet is concurrently completed by another process. The only guarantee -+ * of this function is that the packet will be completed (with success, -+ * failure, or cancellation) and released from the transport layer in a -+ * reasonable time-frame. -+ * -+ * May be called before the packet has been submitted, in which case any later -+ * packet submission fails. -+ */ -+void ssh_ptl_cancel(struct ssh_packet *p) -+{ -+ if (test_and_set_bit(SSH_PACKET_SF_CANCELED_BIT, &p->state)) -+ return; -+ -+ trace_ssam_packet_cancel(p); -+ -+ /* -+ * Lock packet and commit with memory barrier. If this packet has -+ * already been locked, it's going to be removed and completed by -+ * another party, which should have precedence. -+ */ -+ if (test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) -+ return; -+ -+ /* -+ * By marking the packet as locked and employing the implicit memory -+ * barrier of test_and_set_bit, we have guaranteed that, at this point, -+ * the packet cannot be added to the queue any more. -+ * -+ * In case the packet has never been submitted, packet->ptl is NULL. If -+ * the packet is currently being submitted, packet->ptl may be NULL or -+ * non-NULL. Due marking the packet as locked above and committing with -+ * the memory barrier, we have guaranteed that, if packet->ptl is NULL, -+ * the packet will never be added to the queue. If packet->ptl is -+ * non-NULL, we don't have any guarantees. -+ */ -+ -+ if (READ_ONCE(p->ptl)) { -+ ssh_ptl_remove_and_complete(p, -ECANCELED); -+ -+ if (atomic_read(&p->ptl->pending.count) < SSH_PTL_MAX_PENDING) -+ ssh_ptl_tx_wakeup_packet(p->ptl); -+ -+ } else if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) { -+ __ssh_ptl_complete(p, -ECANCELED); -+ } -+} -+ -+/* Must be called with pending lock held */ -+static ktime_t ssh_packet_get_expiration(struct ssh_packet *p, ktime_t timeout) -+{ -+ lockdep_assert_held(&p->ptl->pending.lock); -+ -+ if (p->timestamp != KTIME_MAX) -+ return ktime_add(p->timestamp, timeout); -+ else -+ return KTIME_MAX; -+} -+ -+static void ssh_ptl_timeout_reap(struct work_struct *work) -+{ -+ struct ssh_ptl *ptl = to_ssh_ptl(work, rtx_timeout.reaper.work); -+ struct ssh_packet *p, *n; -+ LIST_HEAD(claimed); -+ ktime_t now = ktime_get_coarse_boottime(); -+ ktime_t timeout = ptl->rtx_timeout.timeout; -+ ktime_t next = KTIME_MAX; -+ bool resub = false; -+ int status; -+ -+ trace_ssam_ptl_timeout_reap(atomic_read(&ptl->pending.count)); -+ -+ /* -+ * Mark reaper as "not pending". This is done before checking any -+ * packets to avoid lost-update type problems. -+ */ -+ spin_lock(&ptl->rtx_timeout.lock); -+ ptl->rtx_timeout.expires = KTIME_MAX; -+ spin_unlock(&ptl->rtx_timeout.lock); -+ -+ spin_lock(&ptl->pending.lock); -+ -+ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { -+ ktime_t expires = ssh_packet_get_expiration(p, timeout); -+ -+ /* -+ * Check if the timeout hasn't expired yet. Find out next -+ * expiration date to be handled after this run. -+ */ -+ if (ktime_after(expires, now)) { -+ next = ktime_before(expires, next) ? expires : next; -+ continue; -+ } -+ -+ trace_ssam_packet_timeout(p); -+ -+ status = __ssh_ptl_resubmit(p); -+ -+ /* -+ * Re-submission fails if the packet is out of tries, has been -+ * locked, is already queued, or the layer is being shut down. -+ * No need to re-schedule tx-thread in those cases. -+ */ -+ if (!status) -+ resub = true; -+ -+ /* Go to next packet if this packet is not out of tries. */ -+ if (status != -ECANCELED) -+ continue; -+ -+ /* No more tries left: Cancel the packet. */ -+ -+ /* -+ * If someone else has locked the packet already, don't use it -+ * and let the other party complete it. -+ */ -+ if (test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) -+ continue; -+ -+ /* -+ * We have now marked the packet as locked. Thus it cannot be -+ * added to the pending list again after we've removed it here. -+ * We can therefore re-use the pending_node of this packet -+ * temporarily. -+ */ -+ -+ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); -+ -+ atomic_dec(&ptl->pending.count); -+ list_del(&p->pending_node); -+ -+ list_add_tail(&p->pending_node, &claimed); -+ } -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ /* Cancel and complete the packet. */ -+ list_for_each_entry_safe(p, n, &claimed, pending_node) { -+ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) { -+ ssh_ptl_queue_remove(p); -+ __ssh_ptl_complete(p, -ETIMEDOUT); -+ } -+ -+ /* -+ * Drop the reference we've obtained by removing it from -+ * the pending set. -+ */ -+ list_del(&p->pending_node); -+ ssh_packet_put(p); -+ } -+ -+ /* Ensure that reaper doesn't run again immediately. */ -+ next = max(next, ktime_add(now, SSH_PTL_PACKET_TIMEOUT_RESOLUTION)); -+ if (next != KTIME_MAX) -+ ssh_ptl_timeout_reaper_mod(ptl, now, next); -+ -+ if (resub) -+ ssh_ptl_tx_wakeup_packet(ptl); -+} -+ -+static bool ssh_ptl_rx_retransmit_check(struct ssh_ptl *ptl, u8 seq) -+{ -+ int i; -+ -+ /* -+ * Check if SEQ has been seen recently (i.e. packet was -+ * re-transmitted and we should ignore it). -+ */ -+ for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) { -+ if (likely(ptl->rx.blocked.seqs[i] != seq)) -+ continue; -+ -+ ptl_dbg(ptl, "ptl: ignoring repeated data packet\n"); -+ return true; -+ } -+ -+ /* Update list of blocked sequence IDs. */ -+ ptl->rx.blocked.seqs[ptl->rx.blocked.offset] = seq; -+ ptl->rx.blocked.offset = (ptl->rx.blocked.offset + 1) -+ % ARRAY_SIZE(ptl->rx.blocked.seqs); -+ -+ return false; -+} -+ -+static void ssh_ptl_rx_dataframe(struct ssh_ptl *ptl, -+ const struct ssh_frame *frame, -+ const struct ssam_span *payload) -+{ -+ if (ssh_ptl_rx_retransmit_check(ptl, frame->seq)) -+ return; -+ -+ ptl->ops.data_received(ptl, payload); -+} -+ -+static void ssh_ptl_send_ack(struct ssh_ptl *ptl, u8 seq) -+{ -+ struct ssh_packet *packet; -+ struct ssam_span buf; -+ struct msgbuf msgb; -+ int status; -+ -+ status = ssh_ctrl_packet_alloc(&packet, &buf, GFP_KERNEL); -+ if (status) { -+ ptl_err(ptl, "ptl: failed to allocate ACK packet\n"); -+ return; -+ } -+ -+ ssh_packet_init(packet, 0, SSH_PACKET_PRIORITY(ACK, 0), -+ &ssh_ptl_ctrl_packet_ops); -+ -+ msgb_init(&msgb, buf.ptr, buf.len); -+ msgb_push_ack(&msgb, seq); -+ ssh_packet_set_data(packet, msgb.begin, msgb_bytes_used(&msgb)); -+ -+ ssh_ptl_submit(ptl, packet); -+ ssh_packet_put(packet); -+} -+ -+static void ssh_ptl_send_nak(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *packet; -+ struct ssam_span buf; -+ struct msgbuf msgb; -+ int status; -+ -+ status = ssh_ctrl_packet_alloc(&packet, &buf, GFP_KERNEL); -+ if (status) { -+ ptl_err(ptl, "ptl: failed to allocate NAK packet\n"); -+ return; -+ } -+ -+ ssh_packet_init(packet, 0, SSH_PACKET_PRIORITY(NAK, 0), -+ &ssh_ptl_ctrl_packet_ops); -+ -+ msgb_init(&msgb, buf.ptr, buf.len); -+ msgb_push_nak(&msgb); -+ ssh_packet_set_data(packet, msgb.begin, msgb_bytes_used(&msgb)); -+ -+ ssh_ptl_submit(ptl, packet); -+ ssh_packet_put(packet); -+} -+ -+static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) -+{ -+ struct ssh_frame *frame; -+ struct ssam_span payload; -+ struct ssam_span aligned; -+ bool syn_found; -+ int status; -+ -+ /* Error injection: Modify data to simulate corrupt SYN bytes. */ -+ ssh_ptl_rx_inject_invalid_syn(ptl, source); -+ -+ /* Find SYN. */ -+ syn_found = sshp_find_syn(source, &aligned); -+ -+ if (unlikely(aligned.ptr != source->ptr)) { -+ /* -+ * We expect aligned.ptr == source->ptr. If this is not the -+ * case, then aligned.ptr > source->ptr and we've encountered -+ * some unexpected data where we'd expect the start of a new -+ * message (i.e. the SYN sequence). -+ * -+ * This can happen when a CRC check for the previous message -+ * failed and we start actively searching for the next one -+ * (via the call to sshp_find_syn() above), or the first bytes -+ * of a message got dropped or corrupted. -+ * -+ * In any case, we issue a warning, send a NAK to the EC to -+ * request re-transmission of any data we haven't acknowledged -+ * yet, and finally, skip everything up to the next SYN -+ * sequence. -+ */ -+ -+ ptl_warn(ptl, "rx: parser: invalid start of frame, skipping\n"); -+ -+ /* -+ * Notes: -+ * - This might send multiple NAKs in case the communication -+ * starts with an invalid SYN and is broken down into multiple -+ * pieces. This should generally be handled fine, we just -+ * might receive duplicate data in this case, which is -+ * detected when handling data frames. -+ * - This path will also be executed on invalid CRCs: When an -+ * invalid CRC is encountered, the code below will skip data -+ * until directly after the SYN. This causes the search for -+ * the next SYN, which is generally not placed directly after -+ * the last one. -+ * -+ * Open question: Should we send this in case of invalid -+ * payload CRCs if the frame-type is non-sequential (current -+ * implementation) or should we drop that frame without -+ * telling the EC? -+ */ -+ ssh_ptl_send_nak(ptl); -+ } -+ -+ if (unlikely(!syn_found)) -+ return aligned.ptr - source->ptr; -+ -+ /* Error injection: Modify data to simulate corruption. */ -+ ssh_ptl_rx_inject_invalid_data(ptl, &aligned); -+ -+ /* Parse and validate frame. */ -+ status = sshp_parse_frame(&ptl->serdev->dev, &aligned, &frame, &payload, -+ SSH_PTL_RX_BUF_LEN); -+ if (status) /* Invalid frame: skip to next SYN. */ -+ return aligned.ptr - source->ptr + sizeof(u16); -+ if (!frame) /* Not enough data. */ -+ return aligned.ptr - source->ptr; -+ -+ trace_ssam_rx_frame_received(frame); -+ -+ switch (frame->type) { -+ case SSH_FRAME_TYPE_ACK: -+ ssh_ptl_acknowledge(ptl, frame->seq); -+ break; -+ -+ case SSH_FRAME_TYPE_NAK: -+ ssh_ptl_resubmit_pending(ptl); -+ break; -+ -+ case SSH_FRAME_TYPE_DATA_SEQ: -+ ssh_ptl_send_ack(ptl, frame->seq); -+ fallthrough; -+ -+ case SSH_FRAME_TYPE_DATA_NSQ: -+ ssh_ptl_rx_dataframe(ptl, frame, &payload); -+ break; -+ -+ default: -+ ptl_warn(ptl, "ptl: received frame with unknown type %#04x\n", -+ frame->type); -+ break; -+ } -+ -+ return aligned.ptr - source->ptr + SSH_MESSAGE_LENGTH(payload.len); -+} -+ -+static int ssh_ptl_rx_threadfn(void *data) -+{ -+ struct ssh_ptl *ptl = data; -+ -+ while (true) { -+ struct ssam_span span; -+ size_t offs = 0; -+ size_t n; -+ -+ wait_event_interruptible(ptl->rx.wq, -+ !kfifo_is_empty(&ptl->rx.fifo) || -+ kthread_should_stop()); -+ if (kthread_should_stop()) -+ break; -+ -+ /* Copy from fifo to evaluation buffer. */ -+ n = sshp_buf_read_from_fifo(&ptl->rx.buf, &ptl->rx.fifo); -+ -+ ptl_dbg(ptl, "rx: received data (size: %zu)\n", n); -+ print_hex_dump_debug("rx: ", DUMP_PREFIX_OFFSET, 16, 1, -+ ptl->rx.buf.ptr + ptl->rx.buf.len - n, -+ n, false); -+ -+ /* Parse until we need more bytes or buffer is empty. */ -+ while (offs < ptl->rx.buf.len) { -+ sshp_buf_span_from(&ptl->rx.buf, offs, &span); -+ n = ssh_ptl_rx_eval(ptl, &span); -+ if (n == 0) -+ break; /* Need more bytes. */ -+ -+ offs += n; -+ } -+ -+ /* Throw away the evaluated parts. */ -+ sshp_buf_drop(&ptl->rx.buf, offs); -+ } -+ -+ return 0; -+} -+ -+static void ssh_ptl_rx_wakeup(struct ssh_ptl *ptl) -+{ -+ wake_up(&ptl->rx.wq); -+} -+ -+/** -+ * ssh_ptl_rx_start() - Start packet transport layer receiver thread. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_ptl_rx_start(struct ssh_ptl *ptl) -+{ -+ if (ptl->rx.thread) -+ return 0; -+ -+ ptl->rx.thread = kthread_run(ssh_ptl_rx_threadfn, ptl, -+ "ssam_serial_hub-rx"); -+ if (IS_ERR(ptl->rx.thread)) -+ return PTR_ERR(ptl->rx.thread); -+ -+ return 0; -+} -+ -+/** -+ * ssh_ptl_rx_stop() - Stop packet transport layer receiver thread. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_ptl_rx_stop(struct ssh_ptl *ptl) -+{ -+ int status = 0; -+ -+ if (ptl->rx.thread) { -+ status = kthread_stop(ptl->rx.thread); -+ ptl->rx.thread = NULL; -+ } -+ -+ return status; -+} -+ -+/** -+ * ssh_ptl_rx_rcvbuf() - Push data from lower-layer transport to the packet -+ * layer. -+ * @ptl: The packet transport layer. -+ * @buf: Pointer to the data to push to the layer. -+ * @n: Size of the data to push to the layer, in bytes. -+ * -+ * Pushes data from a lower-layer transport to the receiver fifo buffer of the -+ * packet layer and notifies the receiver thread. Calls to this function are -+ * ignored once the packet layer has been shut down. -+ * -+ * Return: Returns the number of bytes transferred (positive or zero) on -+ * success. Returns %-ESHUTDOWN if the packet layer has been shut down. -+ */ -+int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n) -+{ -+ int used; -+ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) -+ return -ESHUTDOWN; -+ -+ used = kfifo_in(&ptl->rx.fifo, buf, n); -+ if (used) -+ ssh_ptl_rx_wakeup(ptl); -+ -+ return used; -+} -+ -+/** -+ * ssh_ptl_shutdown() - Shut down the packet transport layer. -+ * @ptl: The packet transport layer. -+ * -+ * Shuts down the packet transport layer, removing and canceling all queued -+ * and pending packets. Packets canceled by this operation will be completed -+ * with %-ESHUTDOWN as status. Receiver and transmitter threads will be -+ * stopped. -+ * -+ * As a result of this function, the transport layer will be marked as shut -+ * down. Submission of packets after the transport layer has been shut down -+ * will fail with %-ESHUTDOWN. -+ */ -+void ssh_ptl_shutdown(struct ssh_ptl *ptl) -+{ -+ LIST_HEAD(complete_q); -+ LIST_HEAD(complete_p); -+ struct ssh_packet *p, *n; -+ int status; -+ -+ /* Ensure that no new packets (including ACK/NAK) can be submitted. */ -+ set_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state); -+ /* -+ * Ensure that the layer gets marked as shut-down before actually -+ * stopping it. In combination with the check in ssh_ptl_queue_push(), -+ * this guarantees that no new packets can be added and all already -+ * queued packets are properly canceled. In combination with the check -+ * in ssh_ptl_rx_rcvbuf(), this guarantees that received data is -+ * properly cut off. -+ */ -+ smp_mb__after_atomic(); -+ -+ status = ssh_ptl_rx_stop(ptl); -+ if (status) -+ ptl_err(ptl, "ptl: failed to stop receiver thread\n"); -+ -+ status = ssh_ptl_tx_stop(ptl); -+ if (status) -+ ptl_err(ptl, "ptl: failed to stop transmitter thread\n"); -+ -+ cancel_delayed_work_sync(&ptl->rtx_timeout.reaper); -+ -+ /* -+ * At this point, all threads have been stopped. This means that the -+ * only references to packets from inside the system are in the queue -+ * and pending set. -+ * -+ * Note: We still need locks here because someone could still be -+ * canceling packets. -+ * -+ * Note 2: We can re-use queue_node (or pending_node) if we mark the -+ * packet as locked an then remove it from the queue (or pending set -+ * respectively). Marking the packet as locked avoids re-queuing -+ * (which should already be prevented by having stopped the treads...) -+ * and not setting QUEUED_BIT (or PENDING_BIT) prevents removal from a -+ * new list via other threads (e.g. cancellation). -+ * -+ * Note 3: There may be overlap between complete_p and complete_q. -+ * This is handled via test_and_set_bit() on the "completed" flag -+ * (also handles cancellation). -+ */ -+ -+ /* Mark queued packets as locked and move them to complete_q. */ -+ spin_lock(&ptl->queue.lock); -+ list_for_each_entry_safe(p, n, &ptl->queue.head, queue_node) { -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state); -+ /* Ensure that state does not get zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); -+ -+ list_del(&p->queue_node); -+ list_add_tail(&p->queue_node, &complete_q); -+ } -+ spin_unlock(&ptl->queue.lock); -+ -+ /* Mark pending packets as locked and move them to complete_p. */ -+ spin_lock(&ptl->pending.lock); -+ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state); -+ /* Ensure that state does not get zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); -+ -+ list_del(&p->pending_node); -+ list_add_tail(&p->pending_node, &complete_q); -+ } -+ atomic_set(&ptl->pending.count, 0); -+ spin_unlock(&ptl->pending.lock); -+ -+ /* Complete and drop packets on complete_q. */ -+ list_for_each_entry(p, &complete_q, queue_node) { -+ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) -+ __ssh_ptl_complete(p, -ESHUTDOWN); -+ -+ ssh_packet_put(p); -+ } -+ -+ /* Complete and drop packets on complete_p. */ -+ list_for_each_entry(p, &complete_p, pending_node) { -+ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) -+ __ssh_ptl_complete(p, -ESHUTDOWN); -+ -+ ssh_packet_put(p); -+ } -+ -+ /* -+ * At this point we have guaranteed that the system doesn't reference -+ * any packets any more. -+ */ -+} -+ -+/** -+ * ssh_ptl_init() - Initialize packet transport layer. -+ * @ptl: The packet transport layer to initialize. -+ * @serdev: The underlying serial device, i.e. the lower-level transport. -+ * @ops: Packet layer operations. -+ * -+ * Initializes the given packet transport layer. Transmitter and receiver -+ * threads must be started separately via ssh_ptl_tx_start() and -+ * ssh_ptl_rx_start(), after the packet-layer has been initialized and the -+ * lower-level transport layer has been set up. -+ * -+ * Return: Returns zero on success and a nonzero error code on failure. -+ */ -+int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev, -+ struct ssh_ptl_ops *ops) -+{ -+ int i, status; -+ -+ ptl->serdev = serdev; -+ ptl->state = 0; -+ -+ spin_lock_init(&ptl->queue.lock); -+ INIT_LIST_HEAD(&ptl->queue.head); -+ -+ spin_lock_init(&ptl->pending.lock); -+ INIT_LIST_HEAD(&ptl->pending.head); -+ atomic_set_release(&ptl->pending.count, 0); -+ -+ ptl->tx.thread = NULL; -+ atomic_set(&ptl->tx.running, 0); -+ init_completion(&ptl->tx.thread_cplt_pkt); -+ init_completion(&ptl->tx.thread_cplt_tx); -+ init_waitqueue_head(&ptl->tx.packet_wq); -+ -+ ptl->rx.thread = NULL; -+ init_waitqueue_head(&ptl->rx.wq); -+ -+ spin_lock_init(&ptl->rtx_timeout.lock); -+ ptl->rtx_timeout.timeout = SSH_PTL_PACKET_TIMEOUT; -+ ptl->rtx_timeout.expires = KTIME_MAX; -+ INIT_DELAYED_WORK(&ptl->rtx_timeout.reaper, ssh_ptl_timeout_reap); -+ -+ ptl->ops = *ops; -+ -+ /* Initialize list of recent/blocked SEQs with invalid sequence IDs. */ -+ for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) -+ ptl->rx.blocked.seqs[i] = U16_MAX; -+ ptl->rx.blocked.offset = 0; -+ -+ status = kfifo_alloc(&ptl->rx.fifo, SSH_PTL_RX_FIFO_LEN, GFP_KERNEL); -+ if (status) -+ return status; -+ -+ status = sshp_buf_alloc(&ptl->rx.buf, SSH_PTL_RX_BUF_LEN, GFP_KERNEL); -+ if (status) -+ kfifo_free(&ptl->rx.fifo); -+ -+ return status; -+} -+ -+/** -+ * ssh_ptl_destroy() - Deinitialize packet transport layer. -+ * @ptl: The packet transport layer to deinitialize. -+ * -+ * Deinitializes the given packet transport layer and frees resources -+ * associated with it. If receiver and/or transmitter threads have been -+ * started, the layer must first be shut down via ssh_ptl_shutdown() before -+ * this function can be called. -+ */ -+void ssh_ptl_destroy(struct ssh_ptl *ptl) -+{ -+ kfifo_free(&ptl->rx.fifo); -+ sshp_buf_free(&ptl->rx.buf); -+} -diff --git a/drivers/platform/x86/surface_aggregator/ssh_packet_layer.h b/drivers/platform/x86/surface_aggregator/ssh_packet_layer.h -new file mode 100644 -index 000000000000..2eb329f0b91a ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/ssh_packet_layer.h -@@ -0,0 +1,190 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * SSH packet transport layer. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H -+#define _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include "ssh_parser.h" -+ -+/** -+ * enum ssh_ptl_state_flags - State-flags for &struct ssh_ptl. -+ * -+ * @SSH_PTL_SF_SHUTDOWN_BIT: -+ * Indicates that the packet transport layer has been shut down or is -+ * being shut down and should not accept any new packets/data. -+ */ -+enum ssh_ptl_state_flags { -+ SSH_PTL_SF_SHUTDOWN_BIT, -+}; -+ -+/** -+ * struct ssh_ptl_ops - Callback operations for packet transport layer. -+ * @data_received: Function called when a data-packet has been received. Both, -+ * the packet layer on which the packet has been received and -+ * the packet's payload data are provided to this function. -+ */ -+struct ssh_ptl_ops { -+ void (*data_received)(struct ssh_ptl *p, const struct ssam_span *data); -+}; -+ -+/** -+ * struct ssh_ptl - SSH packet transport layer. -+ * @serdev: Serial device providing the underlying data transport. -+ * @state: State(-flags) of the transport layer. -+ * @queue: Packet submission queue. -+ * @queue.lock: Lock for modifying the packet submission queue. -+ * @queue.head: List-head of the packet submission queue. -+ * @pending: Set/list of pending packets. -+ * @pending.lock: Lock for modifying the pending set. -+ * @pending.head: List-head of the pending set/list. -+ * @pending.count: Number of currently pending packets. -+ * @tx: Transmitter subsystem. -+ * @tx.running: Flag indicating (desired) transmitter thread state. -+ * @tx.thread: Transmitter thread. -+ * @tx.thread_cplt_tx: Completion for transmitter thread waiting on transfer. -+ * @tx.thread_cplt_pkt: Completion for transmitter thread waiting on packets. -+ * @tx.packet_wq: Waitqueue-head for packet transmit completion. -+ * @rx: Receiver subsystem. -+ * @rx.thread: Receiver thread. -+ * @rx.wq: Waitqueue-head for receiver thread. -+ * @rx.fifo: Buffer for receiving data/pushing data to receiver thread. -+ * @rx.buf: Buffer for evaluating data on receiver thread. -+ * @rx.blocked: List of recent/blocked sequence IDs to detect retransmission. -+ * @rx.blocked.seqs: Array of blocked sequence IDs. -+ * @rx.blocked.offset: Offset indicating where a new ID should be inserted. -+ * @rtx_timeout: Retransmission timeout subsystem. -+ * @rtx_timeout.lock: Lock for modifying the retransmission timeout reaper. -+ * @rtx_timeout.timeout: Timeout interval for retransmission. -+ * @rtx_timeout.expires: Time specifying when the reaper work is next scheduled. -+ * @rtx_timeout.reaper: Work performing timeout checks and subsequent actions. -+ * @ops: Packet layer operations. -+ */ -+struct ssh_ptl { -+ struct serdev_device *serdev; -+ unsigned long state; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ } queue; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ atomic_t count; -+ } pending; -+ -+ struct { -+ atomic_t running; -+ struct task_struct *thread; -+ struct completion thread_cplt_tx; -+ struct completion thread_cplt_pkt; -+ struct wait_queue_head packet_wq; -+ } tx; -+ -+ struct { -+ struct task_struct *thread; -+ struct wait_queue_head wq; -+ struct kfifo fifo; -+ struct sshp_buf buf; -+ -+ struct { -+ u16 seqs[8]; -+ u16 offset; -+ } blocked; -+ } rx; -+ -+ struct { -+ spinlock_t lock; -+ ktime_t timeout; -+ ktime_t expires; -+ struct delayed_work reaper; -+ } rtx_timeout; -+ -+ struct ssh_ptl_ops ops; -+}; -+ -+#define __ssam_prcond(func, p, fmt, ...) \ -+ do { \ -+ typeof(p) __p = (p); \ -+ \ -+ if (__p) \ -+ func(__p, fmt, ##__VA_ARGS__); \ -+ } while (0) -+ -+#define ptl_dbg(p, fmt, ...) dev_dbg(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_info(p, fmt, ...) dev_info(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_warn(p, fmt, ...) dev_warn(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_err(p, fmt, ...) dev_err(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_dbg_cond(p, fmt, ...) __ssam_prcond(ptl_dbg, p, fmt, ##__VA_ARGS__) -+ -+#define to_ssh_ptl(ptr, member) \ -+ container_of(ptr, struct ssh_ptl, member) -+ -+int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev, -+ struct ssh_ptl_ops *ops); -+ -+void ssh_ptl_destroy(struct ssh_ptl *ptl); -+ -+/** -+ * ssh_ptl_get_device() - Get device associated with packet transport layer. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns the device on which the given packet transport layer builds -+ * upon. -+ */ -+static inline struct device *ssh_ptl_get_device(struct ssh_ptl *ptl) -+{ -+ return ptl->serdev ? &ptl->serdev->dev : NULL; -+} -+ -+int ssh_ptl_tx_start(struct ssh_ptl *ptl); -+int ssh_ptl_tx_stop(struct ssh_ptl *ptl); -+int ssh_ptl_rx_start(struct ssh_ptl *ptl); -+int ssh_ptl_rx_stop(struct ssh_ptl *ptl); -+void ssh_ptl_shutdown(struct ssh_ptl *ptl); -+ -+int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p); -+void ssh_ptl_cancel(struct ssh_packet *p); -+ -+int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n); -+ -+/** -+ * ssh_ptl_tx_wakeup_transfer() - Wake up packet transmitter thread for -+ * transfer. -+ * @ptl: The packet transport layer. -+ * -+ * Wakes up the packet transmitter thread, notifying it that the underlying -+ * transport has more space for data to be transmitted. If the packet -+ * transport layer has been shut down, calls to this function will be ignored. -+ */ -+static inline void ssh_ptl_tx_wakeup_transfer(struct ssh_ptl *ptl) -+{ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) -+ return; -+ -+ complete(&ptl->tx.thread_cplt_tx); -+} -+ -+void ssh_packet_init(struct ssh_packet *packet, unsigned long type, -+ u8 priority, const struct ssh_packet_ops *ops); -+ -+int ssh_ctrl_packet_cache_init(void); -+void ssh_ctrl_packet_cache_destroy(void); -+ -+#endif /* _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H */ -diff --git a/drivers/platform/x86/surface_aggregator/ssh_parser.c b/drivers/platform/x86/surface_aggregator/ssh_parser.c -new file mode 100644 -index 000000000000..b77912f8f13b ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/ssh_parser.c -@@ -0,0 +1,228 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * SSH message parser. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+ -+#include -+#include "ssh_parser.h" -+ -+/** -+ * sshp_validate_crc() - Validate a CRC in raw message data. -+ * @src: The span of data over which the CRC should be computed. -+ * @crc: The pointer to the expected u16 CRC value. -+ * -+ * Computes the CRC of the provided data span (@src), compares it to the CRC -+ * stored at the given address (@crc), and returns the result of this -+ * comparison, i.e. %true if equal. This function is intended to run on raw -+ * input/message data. -+ * -+ * Return: Returns %true if the computed CRC matches the stored CRC, %false -+ * otherwise. -+ */ -+static bool sshp_validate_crc(const struct ssam_span *src, const u8 *crc) -+{ -+ u16 actual = ssh_crc(src->ptr, src->len); -+ u16 expected = get_unaligned_le16(crc); -+ -+ return actual == expected; -+} -+ -+/** -+ * sshp_starts_with_syn() - Check if the given data starts with SSH SYN bytes. -+ * @src: The data span to check the start of. -+ */ -+static bool sshp_starts_with_syn(const struct ssam_span *src) -+{ -+ return src->len >= 2 && get_unaligned_le16(src->ptr) == SSH_MSG_SYN; -+} -+ -+/** -+ * sshp_find_syn() - Find SSH SYN bytes in the given data span. -+ * @src: The data span to search in. -+ * @rem: The span (output) indicating the remaining data, starting with SSH -+ * SYN bytes, if found. -+ * -+ * Search for SSH SYN bytes in the given source span. If found, set the @rem -+ * span to the remaining data, starting with the first SYN bytes and capped by -+ * the source span length, and return %true. This function does not copy any -+ * data, but rather only sets pointers to the respective start addresses and -+ * length values. -+ * -+ * If no SSH SYN bytes could be found, set the @rem span to the zero-length -+ * span at the end of the source span and return %false. -+ * -+ * If partial SSH SYN bytes could be found at the end of the source span, set -+ * the @rem span to cover these partial SYN bytes, capped by the end of the -+ * source span, and return %false. This function should then be re-run once -+ * more data is available. -+ * -+ * Return: Returns %true if a complete SSH SYN sequence could be found, -+ * %false otherwise. -+ */ -+bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem) -+{ -+ size_t i; -+ -+ for (i = 0; i < src->len - 1; i++) { -+ if (likely(get_unaligned_le16(src->ptr + i) == SSH_MSG_SYN)) { -+ rem->ptr = src->ptr + i; -+ rem->len = src->len - i; -+ return true; -+ } -+ } -+ -+ if (unlikely(src->ptr[src->len - 1] == (SSH_MSG_SYN & 0xff))) { -+ rem->ptr = src->ptr + src->len - 1; -+ rem->len = 1; -+ return false; -+ } -+ -+ rem->ptr = src->ptr + src->len; -+ rem->len = 0; -+ return false; -+} -+ -+/** -+ * sshp_parse_frame() - Parse SSH frame. -+ * @dev: The device used for logging. -+ * @source: The source to parse from. -+ * @frame: The parsed frame (output). -+ * @payload: The parsed payload (output). -+ * @maxlen: The maximum supported message length. -+ * -+ * Parses and validates a SSH frame, including its payload, from the given -+ * source. Sets the provided @frame pointer to the start of the frame and -+ * writes the limits of the frame payload to the provided @payload span -+ * pointer. -+ * -+ * This function does not copy any data, but rather only validates the message -+ * data and sets pointers (and length values) to indicate the respective parts. -+ * -+ * If no complete SSH frame could be found, the frame pointer will be set to -+ * the %NULL pointer and the payload span will be set to the null span (start -+ * pointer %NULL, size zero). -+ * -+ * Return: Returns zero on success or if the frame is incomplete, %-ENOMSG if -+ * the start of the message is invalid, %-EBADMSG if any (frame-header or -+ * payload) CRC is invalid, or %-EMSGSIZE if the SSH message is bigger than -+ * the maximum message length specified in the @maxlen parameter. -+ */ -+int sshp_parse_frame(const struct device *dev, const struct ssam_span *source, -+ struct ssh_frame **frame, struct ssam_span *payload, -+ size_t maxlen) -+{ -+ struct ssam_span sf; -+ struct ssam_span sp; -+ -+ /* Initialize output. */ -+ *frame = NULL; -+ payload->ptr = NULL; -+ payload->len = 0; -+ -+ if (!sshp_starts_with_syn(source)) { -+ dev_warn(dev, "rx: parser: invalid start of frame\n"); -+ return -ENOMSG; -+ } -+ -+ /* Check for minimum packet length. */ -+ if (unlikely(source->len < SSH_MESSAGE_LENGTH(0))) { -+ dev_dbg(dev, "rx: parser: not enough data for frame\n"); -+ return 0; -+ } -+ -+ /* Pin down frame. */ -+ sf.ptr = source->ptr + sizeof(u16); -+ sf.len = sizeof(struct ssh_frame); -+ -+ /* Validate frame CRC. */ -+ if (unlikely(!sshp_validate_crc(&sf, sf.ptr + sf.len))) { -+ dev_warn(dev, "rx: parser: invalid frame CRC\n"); -+ return -EBADMSG; -+ } -+ -+ /* Ensure packet does not exceed maximum length. */ -+ sp.len = get_unaligned_le16(&((struct ssh_frame *)sf.ptr)->len); -+ if (unlikely(SSH_MESSAGE_LENGTH(sp.len) > maxlen)) { -+ dev_warn(dev, "rx: parser: frame too large: %llu bytes\n", -+ SSH_MESSAGE_LENGTH(sp.len)); -+ return -EMSGSIZE; -+ } -+ -+ /* Pin down payload. */ -+ sp.ptr = sf.ptr + sf.len + sizeof(u16); -+ -+ /* Check for frame + payload length. */ -+ if (source->len < SSH_MESSAGE_LENGTH(sp.len)) { -+ dev_dbg(dev, "rx: parser: not enough data for payload\n"); -+ return 0; -+ } -+ -+ /* Validate payload CRC. */ -+ if (unlikely(!sshp_validate_crc(&sp, sp.ptr + sp.len))) { -+ dev_warn(dev, "rx: parser: invalid payload CRC\n"); -+ return -EBADMSG; -+ } -+ -+ *frame = (struct ssh_frame *)sf.ptr; -+ *payload = sp; -+ -+ dev_dbg(dev, "rx: parser: valid frame found (type: %#04x, len: %u)\n", -+ (*frame)->type, (*frame)->len); -+ -+ return 0; -+} -+ -+/** -+ * sshp_parse_command() - Parse SSH command frame payload. -+ * @dev: The device used for logging. -+ * @source: The source to parse from. -+ * @command: The parsed command (output). -+ * @command_data: The parsed command data/payload (output). -+ * -+ * Parses and validates a SSH command frame payload. Sets the @command pointer -+ * to the command header and the @command_data span to the command data (i.e. -+ * payload of the command). This will result in a zero-length span if the -+ * command does not have any associated data/payload. This function does not -+ * check the frame-payload-type field, which should be checked by the caller -+ * before calling this function. -+ * -+ * The @source parameter should be the complete frame payload, e.g. returned -+ * by the sshp_parse_frame() command. -+ * -+ * This function does not copy any data, but rather only validates the frame -+ * payload data and sets pointers (and length values) to indicate the -+ * respective parts. -+ * -+ * Return: Returns zero on success or %-ENOMSG if @source does not represent a -+ * valid command-type frame payload, i.e. is too short. -+ */ -+int sshp_parse_command(const struct device *dev, const struct ssam_span *source, -+ struct ssh_command **command, -+ struct ssam_span *command_data) -+{ -+ /* Check for minimum length. */ -+ if (unlikely(source->len < sizeof(struct ssh_command))) { -+ *command = NULL; -+ command_data->ptr = NULL; -+ command_data->len = 0; -+ -+ dev_err(dev, "rx: parser: command payload is too short\n"); -+ return -ENOMSG; -+ } -+ -+ *command = (struct ssh_command *)source->ptr; -+ command_data->ptr = source->ptr + sizeof(struct ssh_command); -+ command_data->len = source->len - sizeof(struct ssh_command); -+ -+ dev_dbg(dev, "rx: parser: valid command found (tc: %#04x, cid: %#04x)\n", -+ (*command)->tc, (*command)->cid); -+ -+ return 0; -+} -diff --git a/drivers/platform/x86/surface_aggregator/ssh_parser.h b/drivers/platform/x86/surface_aggregator/ssh_parser.h -new file mode 100644 -index 000000000000..3bd6e180fd16 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/ssh_parser.h -@@ -0,0 +1,154 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * SSH message parser. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H -+#define _SURFACE_AGGREGATOR_SSH_PARSER_H -+ -+#include -+#include -+#include -+#include -+ -+#include -+ -+/** -+ * struct sshp_buf - Parser buffer for SSH messages. -+ * @ptr: Pointer to the beginning of the buffer. -+ * @len: Number of bytes used in the buffer. -+ * @cap: Maximum capacity of the buffer. -+ */ -+struct sshp_buf { -+ u8 *ptr; -+ size_t len; -+ size_t cap; -+}; -+ -+/** -+ * sshp_buf_init() - Initialize a SSH parser buffer. -+ * @buf: The buffer to initialize. -+ * @ptr: The memory backing the buffer. -+ * @cap: The length of the memory backing the buffer, i.e. its capacity. -+ * -+ * Initializes the buffer with the given memory as backing and set its used -+ * length to zero. -+ */ -+static inline void sshp_buf_init(struct sshp_buf *buf, u8 *ptr, size_t cap) -+{ -+ buf->ptr = ptr; -+ buf->len = 0; -+ buf->cap = cap; -+} -+ -+/** -+ * sshp_buf_alloc() - Allocate and initialize a SSH parser buffer. -+ * @buf: The buffer to initialize/allocate to. -+ * @cap: The desired capacity of the buffer. -+ * @flags: The flags used for allocating the memory. -+ * -+ * Allocates @cap bytes and initializes the provided buffer struct with the -+ * allocated memory. -+ * -+ * Return: Returns zero on success and %-ENOMEM if allocation failed. -+ */ -+static inline int sshp_buf_alloc(struct sshp_buf *buf, size_t cap, gfp_t flags) -+{ -+ u8 *ptr; -+ -+ ptr = kzalloc(cap, flags); -+ if (!ptr) -+ return -ENOMEM; -+ -+ sshp_buf_init(buf, ptr, cap); -+ return 0; -+} -+ -+/** -+ * sshp_buf_free() - Free a SSH parser buffer. -+ * @buf: The buffer to free. -+ * -+ * Frees a SSH parser buffer by freeing the memory backing it and then -+ * resetting its pointer to %NULL and length and capacity to zero. Intended to -+ * free a buffer previously allocated with sshp_buf_alloc(). -+ */ -+static inline void sshp_buf_free(struct sshp_buf *buf) -+{ -+ kfree(buf->ptr); -+ buf->ptr = NULL; -+ buf->len = 0; -+ buf->cap = 0; -+} -+ -+/** -+ * sshp_buf_drop() - Drop data from the beginning of the buffer. -+ * @buf: The buffer to drop data from. -+ * @n: The number of bytes to drop. -+ * -+ * Drops the first @n bytes from the buffer. Re-aligns any remaining data to -+ * the beginning of the buffer. -+ */ -+static inline void sshp_buf_drop(struct sshp_buf *buf, size_t n) -+{ -+ memmove(buf->ptr, buf->ptr + n, buf->len - n); -+ buf->len -= n; -+} -+ -+/** -+ * sshp_buf_read_from_fifo() - Transfer data from a fifo to the buffer. -+ * @buf: The buffer to write the data into. -+ * @fifo: The fifo to read the data from. -+ * -+ * Transfers the data contained in the fifo to the buffer, removing it from -+ * the fifo. This function will try to transfer as much data as possible, -+ * limited either by the remaining space in the buffer or by the number of -+ * bytes available in the fifo. -+ * -+ * Return: Returns the number of bytes transferred. -+ */ -+static inline size_t sshp_buf_read_from_fifo(struct sshp_buf *buf, -+ struct kfifo *fifo) -+{ -+ size_t n; -+ -+ n = kfifo_out(fifo, buf->ptr + buf->len, buf->cap - buf->len); -+ buf->len += n; -+ -+ return n; -+} -+ -+/** -+ * sshp_buf_span_from() - Initialize a span from the given buffer and offset. -+ * @buf: The buffer to create the span from. -+ * @offset: The offset in the buffer at which the span should start. -+ * @span: The span to initialize (output). -+ * -+ * Initializes the provided span to point to the memory at the given offset in -+ * the buffer, with the length of the span being capped by the number of bytes -+ * used in the buffer after the offset (i.e. bytes remaining after the -+ * offset). -+ * -+ * Warning: This function does not validate that @offset is less than or equal -+ * to the number of bytes used in the buffer or the buffer capacity. This must -+ * be guaranteed by the caller. -+ */ -+static inline void sshp_buf_span_from(struct sshp_buf *buf, size_t offset, -+ struct ssam_span *span) -+{ -+ span->ptr = buf->ptr + offset; -+ span->len = buf->len - offset; -+} -+ -+bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem); -+ -+int sshp_parse_frame(const struct device *dev, const struct ssam_span *source, -+ struct ssh_frame **frame, struct ssam_span *payload, -+ size_t maxlen); -+ -+int sshp_parse_command(const struct device *dev, const struct ssam_span *source, -+ struct ssh_command **command, -+ struct ssam_span *command_data); -+ -+#endif /* _SURFACE_AGGREGATOR_SSH_PARSER_h */ -diff --git a/drivers/platform/x86/surface_aggregator/ssh_request_layer.c b/drivers/platform/x86/surface_aggregator/ssh_request_layer.c -new file mode 100644 -index 000000000000..bfe1aaf38065 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/ssh_request_layer.c -@@ -0,0 +1,1263 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * SSH request transport layer. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "ssh_packet_layer.h" -+#include "ssh_request_layer.h" -+ -+#include "trace.h" -+ -+/* -+ * SSH_RTL_REQUEST_TIMEOUT - Request timeout. -+ * -+ * Timeout as ktime_t delta for request responses. If we have not received a -+ * response in this time-frame after finishing the underlying packet -+ * transmission, the request will be completed with %-ETIMEDOUT as status -+ * code. -+ */ -+#define SSH_RTL_REQUEST_TIMEOUT ms_to_ktime(3000) -+ -+/* -+ * SSH_RTL_REQUEST_TIMEOUT_RESOLUTION - Request timeout granularity. -+ * -+ * Time-resolution for timeouts. Should be larger than one jiffy to avoid -+ * direct re-scheduling of reaper work_struct. -+ */ -+#define SSH_RTL_REQUEST_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) -+ -+/* -+ * SSH_RTL_MAX_PENDING - Maximum number of pending requests. -+ * -+ * Maximum number of requests concurrently waiting to be completed (i.e. -+ * waiting for the corresponding packet transmission to finish if they don't -+ * have a response or waiting for a response if they have one). -+ */ -+#define SSH_RTL_MAX_PENDING 3 -+ -+/* -+ * SSH_RTL_TX_BATCH - Maximum number of requests processed per work execution. -+ * Used to prevent livelocking of the workqueue. Value chosen via educated -+ * guess, may be adjusted. -+ */ -+#define SSH_RTL_TX_BATCH 10 -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION -+ -+/** -+ * ssh_rtl_should_drop_response() - Error injection hook to drop request -+ * responses. -+ * -+ * Useful to cause request transmission timeouts in the driver by dropping the -+ * response to a request. -+ */ -+static noinline bool ssh_rtl_should_drop_response(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_rtl_should_drop_response, TRUE); -+ -+#else -+ -+static inline bool ssh_rtl_should_drop_response(void) -+{ -+ return false; -+} -+ -+#endif -+ -+static u16 ssh_request_get_rqid(struct ssh_request *rqst) -+{ -+ return get_unaligned_le16(rqst->packet.data.ptr -+ + SSH_MSGOFFSET_COMMAND(rqid)); -+} -+ -+static u32 ssh_request_get_rqid_safe(struct ssh_request *rqst) -+{ -+ if (!rqst->packet.data.ptr) -+ return U32_MAX; -+ -+ return ssh_request_get_rqid(rqst); -+} -+ -+static void ssh_rtl_queue_remove(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ spin_lock(&rtl->queue.lock); -+ -+ if (!test_and_clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &rqst->state)) { -+ spin_unlock(&rtl->queue.lock); -+ return; -+ } -+ -+ list_del(&rqst->node); -+ -+ spin_unlock(&rtl->queue.lock); -+ ssh_request_put(rqst); -+} -+ -+static bool ssh_rtl_queue_empty(struct ssh_rtl *rtl) -+{ -+ bool empty; -+ -+ spin_lock(&rtl->queue.lock); -+ empty = list_empty(&rtl->queue.head); -+ spin_unlock(&rtl->queue.lock); -+ -+ return empty; -+} -+ -+static void ssh_rtl_pending_remove(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ spin_lock(&rtl->pending.lock); -+ -+ if (!test_and_clear_bit(SSH_REQUEST_SF_PENDING_BIT, &rqst->state)) { -+ spin_unlock(&rtl->pending.lock); -+ return; -+ } -+ -+ atomic_dec(&rtl->pending.count); -+ list_del(&rqst->node); -+ -+ spin_unlock(&rtl->pending.lock); -+ -+ ssh_request_put(rqst); -+} -+ -+static int ssh_rtl_tx_pending_push(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ spin_lock(&rtl->pending.lock); -+ -+ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) { -+ spin_unlock(&rtl->pending.lock); -+ return -EINVAL; -+ } -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_PENDING_BIT, &rqst->state)) { -+ spin_unlock(&rtl->pending.lock); -+ return -EALREADY; -+ } -+ -+ atomic_inc(&rtl->pending.count); -+ list_add_tail(&ssh_request_get(rqst)->node, &rtl->pending.head); -+ -+ spin_unlock(&rtl->pending.lock); -+ return 0; -+} -+ -+static void ssh_rtl_complete_with_status(struct ssh_request *rqst, int status) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ trace_ssam_request_complete(rqst, status); -+ -+ /* rtl/ptl may not be set if we're canceling before submitting. */ -+ rtl_dbg_cond(rtl, "rtl: completing request (rqid: %#06x, status: %d)\n", -+ ssh_request_get_rqid_safe(rqst), status); -+ -+ rqst->ops->complete(rqst, NULL, NULL, status); -+} -+ -+static void ssh_rtl_complete_with_rsp(struct ssh_request *rqst, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ trace_ssam_request_complete(rqst, 0); -+ -+ rtl_dbg(rtl, "rtl: completing request with response (rqid: %#06x)\n", -+ ssh_request_get_rqid(rqst)); -+ -+ rqst->ops->complete(rqst, cmd, data, 0); -+} -+ -+static bool ssh_rtl_tx_can_process(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ if (test_bit(SSH_REQUEST_TY_FLUSH_BIT, &rqst->state)) -+ return !atomic_read(&rtl->pending.count); -+ -+ return atomic_read(&rtl->pending.count) < SSH_RTL_MAX_PENDING; -+} -+ -+static struct ssh_request *ssh_rtl_tx_next(struct ssh_rtl *rtl) -+{ -+ struct ssh_request *rqst = ERR_PTR(-ENOENT); -+ struct ssh_request *p, *n; -+ -+ spin_lock(&rtl->queue.lock); -+ -+ /* Find first non-locked request and remove it. */ -+ list_for_each_entry_safe(p, n, &rtl->queue.head, node) { -+ if (unlikely(test_bit(SSH_REQUEST_SF_LOCKED_BIT, &p->state))) -+ continue; -+ -+ if (!ssh_rtl_tx_can_process(p)) { -+ rqst = ERR_PTR(-EBUSY); -+ break; -+ } -+ -+ /* Remove from queue and mark as transmitting. */ -+ set_bit(SSH_REQUEST_SF_TRANSMITTING_BIT, &p->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &p->state); -+ -+ list_del(&p->node); -+ -+ rqst = p; -+ break; -+ } -+ -+ spin_unlock(&rtl->queue.lock); -+ return rqst; -+} -+ -+static int ssh_rtl_tx_try_process_one(struct ssh_rtl *rtl) -+{ -+ struct ssh_request *rqst; -+ int status; -+ -+ /* Get and prepare next request for transmit. */ -+ rqst = ssh_rtl_tx_next(rtl); -+ if (IS_ERR(rqst)) -+ return PTR_ERR(rqst); -+ -+ /* Add it to/mark it as pending. */ -+ status = ssh_rtl_tx_pending_push(rqst); -+ if (status) { -+ ssh_request_put(rqst); -+ return -EAGAIN; -+ } -+ -+ /* Submit packet. */ -+ status = ssh_ptl_submit(&rtl->ptl, &rqst->packet); -+ if (status == -ESHUTDOWN) { -+ /* -+ * Packet has been refused due to the packet layer shutting -+ * down. Complete it here. -+ */ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state); -+ /* -+ * Note: A barrier is not required here, as there are only two -+ * references in the system at this point: The one that we have, -+ * and the other one that belongs to the pending set. Due to the -+ * request being marked as "transmitting", our process is the -+ * only one allowed to remove the pending node and change the -+ * state. Normally, the task would fall to the packet callback, -+ * but as this is a path where submission failed, this callback -+ * will never be executed. -+ */ -+ -+ ssh_rtl_pending_remove(rqst); -+ ssh_rtl_complete_with_status(rqst, -ESHUTDOWN); -+ -+ ssh_request_put(rqst); -+ return -ESHUTDOWN; -+ -+ } else if (status) { -+ /* -+ * If submitting the packet failed and the packet layer isn't -+ * shutting down, the packet has either been submitted/queued -+ * before (-EALREADY, which cannot happen as we have -+ * guaranteed that requests cannot be re-submitted), or the -+ * packet was marked as locked (-EINVAL). To mark the packet -+ * locked at this stage, the request, and thus the packets -+ * itself, had to have been canceled. Simply drop the -+ * reference. Cancellation itself will remove it from the set -+ * of pending requests. -+ */ -+ -+ WARN_ON(status != -EINVAL); -+ -+ ssh_request_put(rqst); -+ return -EAGAIN; -+ } -+ -+ ssh_request_put(rqst); -+ return 0; -+} -+ -+static bool ssh_rtl_tx_schedule(struct ssh_rtl *rtl) -+{ -+ if (atomic_read(&rtl->pending.count) >= SSH_RTL_MAX_PENDING) -+ return false; -+ -+ if (ssh_rtl_queue_empty(rtl)) -+ return false; -+ -+ return schedule_work(&rtl->tx.work); -+} -+ -+static void ssh_rtl_tx_work_fn(struct work_struct *work) -+{ -+ struct ssh_rtl *rtl = to_ssh_rtl(work, tx.work); -+ unsigned int iterations = SSH_RTL_TX_BATCH; -+ int status; -+ -+ /* -+ * Try to be nice and not block/live-lock the workqueue: Run a maximum -+ * of 10 tries, then re-submit if necessary. This should not be -+ * necessary for normal execution, but guarantee it anyway. -+ */ -+ do { -+ status = ssh_rtl_tx_try_process_one(rtl); -+ if (status == -ENOENT || status == -EBUSY) -+ return; /* No more requests to process. */ -+ -+ if (status == -ESHUTDOWN) { -+ /* -+ * Packet system shutting down. No new packets can be -+ * transmitted. Return silently, the party initiating -+ * the shutdown should handle the rest. -+ */ -+ return; -+ } -+ -+ WARN_ON(status != 0 && status != -EAGAIN); -+ } while (--iterations); -+ -+ /* Out of tries, reschedule. */ -+ ssh_rtl_tx_schedule(rtl); -+} -+ -+/** -+ * ssh_rtl_submit() - Submit a request to the transport layer. -+ * @rtl: The request transport layer. -+ * @rqst: The request to submit. -+ * -+ * Submits a request to the transport layer. A single request may not be -+ * submitted multiple times without reinitializing it. -+ * -+ * Return: Returns zero on success, %-EINVAL if the request type is invalid or -+ * the request has been canceled prior to submission, %-EALREADY if the -+ * request has already been submitted, or %-ESHUTDOWN in case the request -+ * transport layer has been shut down. -+ */ -+int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst) -+{ -+ trace_ssam_request_submit(rqst); -+ -+ /* -+ * Ensure that requests expecting a response are sequenced. If this -+ * invariant ever changes, see the comment in ssh_rtl_complete() on what -+ * is required to be changed in the code. -+ */ -+ if (test_bit(SSH_REQUEST_TY_HAS_RESPONSE_BIT, &rqst->state)) -+ if (!test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &rqst->packet.state)) -+ return -EINVAL; -+ -+ spin_lock(&rtl->queue.lock); -+ -+ /* -+ * Try to set ptl and check if this request has already been submitted. -+ * -+ * Must be inside lock as we might run into a lost update problem -+ * otherwise: If this were outside of the lock, cancellation in -+ * ssh_rtl_cancel_nonpending() may run after we've set the ptl -+ * reference but before we enter the lock. In that case, we'd detect -+ * that the request is being added to the queue and would try to remove -+ * it from that, but removal might fail because it hasn't actually been -+ * added yet. By putting this cmpxchg in the critical section, we -+ * ensure that the queuing detection only triggers when we are already -+ * in the critical section and the remove process will wait until the -+ * push operation has been completed (via lock) due to that. Only then, -+ * we can safely try to remove it. -+ */ -+ if (cmpxchg(&rqst->packet.ptl, NULL, &rtl->ptl)) { -+ spin_unlock(&rtl->queue.lock); -+ return -EALREADY; -+ } -+ -+ /* -+ * Ensure that we set ptl reference before we continue modifying state. -+ * This is required for non-pending cancellation. This barrier is paired -+ * with the one in ssh_rtl_cancel_nonpending(). -+ * -+ * By setting the ptl reference before we test for "locked", we can -+ * check if the "locked" test may have already run. See comments in -+ * ssh_rtl_cancel_nonpending() for more detail. -+ */ -+ smp_mb__after_atomic(); -+ -+ if (test_bit(SSH_RTL_SF_SHUTDOWN_BIT, &rtl->state)) { -+ spin_unlock(&rtl->queue.lock); -+ return -ESHUTDOWN; -+ } -+ -+ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) { -+ spin_unlock(&rtl->queue.lock); -+ return -EINVAL; -+ } -+ -+ set_bit(SSH_REQUEST_SF_QUEUED_BIT, &rqst->state); -+ list_add_tail(&ssh_request_get(rqst)->node, &rtl->queue.head); -+ -+ spin_unlock(&rtl->queue.lock); -+ -+ ssh_rtl_tx_schedule(rtl); -+ return 0; -+} -+ -+static void ssh_rtl_timeout_reaper_mod(struct ssh_rtl *rtl, ktime_t now, -+ ktime_t expires) -+{ -+ unsigned long delta = msecs_to_jiffies(ktime_ms_delta(expires, now)); -+ ktime_t aexp = ktime_add(expires, SSH_RTL_REQUEST_TIMEOUT_RESOLUTION); -+ -+ spin_lock(&rtl->rtx_timeout.lock); -+ -+ /* Re-adjust / schedule reaper only if it is above resolution delta. */ -+ if (ktime_before(aexp, rtl->rtx_timeout.expires)) { -+ rtl->rtx_timeout.expires = expires; -+ mod_delayed_work(system_wq, &rtl->rtx_timeout.reaper, delta); -+ } -+ -+ spin_unlock(&rtl->rtx_timeout.lock); -+} -+ -+static void ssh_rtl_timeout_start(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ ktime_t timestamp = ktime_get_coarse_boottime(); -+ ktime_t timeout = rtl->rtx_timeout.timeout; -+ -+ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) -+ return; -+ -+ /* -+ * Note: The timestamp gets set only once. This happens on the packet -+ * callback. All other access to it is read-only. -+ */ -+ WRITE_ONCE(rqst->timestamp, timestamp); -+ /* -+ * Ensure timestamp is set before starting the reaper. Paired with -+ * implicit barrier following check on ssh_request_get_expiration() in -+ * ssh_rtl_timeout_reap. -+ */ -+ smp_mb__after_atomic(); -+ -+ ssh_rtl_timeout_reaper_mod(rtl, timestamp, timestamp + timeout); -+} -+ -+static void ssh_rtl_complete(struct ssh_rtl *rtl, -+ const struct ssh_command *command, -+ const struct ssam_span *command_data) -+{ -+ struct ssh_request *r = NULL; -+ struct ssh_request *p, *n; -+ u16 rqid = get_unaligned_le16(&command->rqid); -+ -+ trace_ssam_rx_response_received(command, command_data->len); -+ -+ /* -+ * Get request from pending based on request ID and mark it as response -+ * received and locked. -+ */ -+ spin_lock(&rtl->pending.lock); -+ list_for_each_entry_safe(p, n, &rtl->pending.head, node) { -+ /* We generally expect requests to be processed in order. */ -+ if (unlikely(ssh_request_get_rqid(p) != rqid)) -+ continue; -+ -+ /* Simulate response timeout. */ -+ if (ssh_rtl_should_drop_response()) { -+ spin_unlock(&rtl->pending.lock); -+ -+ trace_ssam_ei_rx_drop_response(p); -+ rtl_info(rtl, "request error injection: dropping response for request %p\n", -+ &p->packet); -+ return; -+ } -+ -+ /* -+ * Mark as "response received" and "locked" as we're going to -+ * complete it. -+ */ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &p->state); -+ set_bit(SSH_REQUEST_SF_RSPRCVD_BIT, &p->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &p->state); -+ -+ atomic_dec(&rtl->pending.count); -+ list_del(&p->node); -+ -+ r = p; -+ break; -+ } -+ spin_unlock(&rtl->pending.lock); -+ -+ if (!r) { -+ rtl_warn(rtl, "rtl: dropping unexpected command message (rqid = %#06x)\n", -+ rqid); -+ return; -+ } -+ -+ /* If the request hasn't been completed yet, we will do this now. */ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) { -+ ssh_request_put(r); -+ ssh_rtl_tx_schedule(rtl); -+ return; -+ } -+ -+ /* -+ * Make sure the request has been transmitted. In case of a sequenced -+ * request, we are guaranteed that the completion callback will run on -+ * the receiver thread directly when the ACK for the packet has been -+ * received. Similarly, this function is guaranteed to run on the -+ * receiver thread. Thus we are guaranteed that if the packet has been -+ * successfully transmitted and received an ACK, the transmitted flag -+ * has been set and is visible here. -+ * -+ * We are currently not handling unsequenced packets here, as those -+ * should never expect a response as ensured in ssh_rtl_submit. If this -+ * ever changes, one would have to test for -+ * -+ * (r->state & (transmitting | transmitted)) -+ * -+ * on unsequenced packets to determine if they could have been -+ * transmitted. There are no synchronization guarantees as in the -+ * sequenced case, since, in this case, the callback function will not -+ * run on the same thread. Thus an exact determination is impossible. -+ */ -+ if (!test_bit(SSH_REQUEST_SF_TRANSMITTED_BIT, &r->state)) { -+ rtl_err(rtl, "rtl: received response before ACK for request (rqid = %#06x)\n", -+ rqid); -+ -+ /* -+ * NB: Timeout has already been canceled, request already been -+ * removed from pending and marked as locked and completed. As -+ * we receive a "false" response, the packet might still be -+ * queued though. -+ */ -+ ssh_rtl_queue_remove(r); -+ -+ ssh_rtl_complete_with_status(r, -EREMOTEIO); -+ ssh_request_put(r); -+ -+ ssh_rtl_tx_schedule(rtl); -+ return; -+ } -+ -+ /* -+ * NB: Timeout has already been canceled, request already been -+ * removed from pending and marked as locked and completed. The request -+ * can also not be queued any more, as it has been marked as -+ * transmitting and later transmitted. Thus no need to remove it from -+ * anywhere. -+ */ -+ -+ ssh_rtl_complete_with_rsp(r, command, command_data); -+ ssh_request_put(r); -+ -+ ssh_rtl_tx_schedule(rtl); -+} -+ -+static bool ssh_rtl_cancel_nonpending(struct ssh_request *r) -+{ -+ struct ssh_rtl *rtl; -+ unsigned long flags, fixed; -+ bool remove; -+ -+ /* -+ * Handle unsubmitted request: Try to mark the packet as locked, -+ * expecting the state to be zero (i.e. unsubmitted). Note that, if -+ * setting the state worked, we might still be adding the packet to the -+ * queue in a currently executing submit call. In that case, however, -+ * ptl reference must have been set previously, as locked is checked -+ * after setting ptl. Furthermore, when the ptl reference is set, the -+ * submission process is guaranteed to have entered the critical -+ * section. Thus only if we successfully locked this request and ptl is -+ * NULL, we have successfully removed the request, i.e. we are -+ * guaranteed that, due to the "locked" check in ssh_rtl_submit(), the -+ * packet will never be added. Otherwise, we need to try and grab it -+ * from the queue, where we are now guaranteed that the packet is or has -+ * been due to the critical section. -+ * -+ * Note that if the cmpxchg() fails, we are guaranteed that ptl has -+ * been set and is non-NULL, as states can only be nonzero after this -+ * has been set. Also note that we need to fetch the static (type) -+ * flags to ensure that they don't cause the cmpxchg() to fail. -+ */ -+ fixed = READ_ONCE(r->state) & SSH_REQUEST_FLAGS_TY_MASK; -+ flags = cmpxchg(&r->state, fixed, SSH_REQUEST_SF_LOCKED_BIT); -+ -+ /* -+ * Force correct ordering with regards to state and ptl reference access -+ * to safe-guard cancellation to concurrent submission against a -+ * lost-update problem. First try to exchange state, then also check -+ * ptl if that worked. This barrier is paired with the -+ * one in ssh_rtl_submit(). -+ */ -+ smp_mb__after_atomic(); -+ -+ if (flags == fixed && !READ_ONCE(r->packet.ptl)) { -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ return true; -+ } -+ -+ rtl = ssh_request_rtl(r); -+ spin_lock(&rtl->queue.lock); -+ -+ /* -+ * Note: 1) Requests cannot be re-submitted. 2) If a request is -+ * queued, it cannot be "transmitting"/"pending" yet. Thus, if we -+ * successfully remove the request here, we have removed all its -+ * occurrences in the system. -+ */ -+ -+ remove = test_and_clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); -+ if (!remove) { -+ spin_unlock(&rtl->queue.lock); -+ return false; -+ } -+ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ list_del(&r->node); -+ -+ spin_unlock(&rtl->queue.lock); -+ -+ ssh_request_put(r); /* Drop reference obtained from queue. */ -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ return true; -+} -+ -+static bool ssh_rtl_cancel_pending(struct ssh_request *r) -+{ -+ /* If the packet is already locked, it's going to be removed shortly. */ -+ if (test_and_set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state)) -+ return true; -+ -+ /* -+ * Now that we have locked the packet, we have guaranteed that it can't -+ * be added to the system any more. If ptl is NULL, the locked -+ * check in ssh_rtl_submit() has not been run and any submission, -+ * currently in progress or called later, won't add the packet. Thus we -+ * can directly complete it. -+ * -+ * The implicit memory barrier of test_and_set_bit() should be enough -+ * to ensure that the correct order (first lock, then check ptl) is -+ * ensured. This is paired with the barrier in ssh_rtl_submit(). -+ */ -+ if (!READ_ONCE(r->packet.ptl)) { -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ return true; -+ } -+ -+ /* -+ * Try to cancel the packet. If the packet has not been completed yet, -+ * this will subsequently (and synchronously) call the completion -+ * callback of the packet, which will complete the request. -+ */ -+ ssh_ptl_cancel(&r->packet); -+ -+ /* -+ * If the packet has been completed with success, i.e. has not been -+ * canceled by the above call, the request may not have been completed -+ * yet (may be waiting for a response). Check if we need to do this -+ * here. -+ */ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_queue_remove(r); -+ ssh_rtl_pending_remove(r); -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ -+ return true; -+} -+ -+/** -+ * ssh_rtl_cancel() - Cancel request. -+ * @rqst: The request to cancel. -+ * @pending: Whether to also cancel pending requests. -+ * -+ * Cancels the given request. If @pending is %false, this will not cancel -+ * pending requests, i.e. requests that have already been submitted to the -+ * packet layer but not been completed yet. If @pending is %true, this will -+ * cancel the given request regardless of the state it is in. -+ * -+ * If the request has been canceled by calling this function, both completion -+ * and release callbacks of the request will be executed in a reasonable -+ * time-frame. This may happen during execution of this function, however, -+ * there is no guarantee for this. For example, a request currently -+ * transmitting will be canceled/completed only after transmission has -+ * completed, and the respective callbacks will be executed on the transmitter -+ * thread, which may happen during, but also some time after execution of the -+ * cancel function. -+ * -+ * Return: Returns %true if the given request has been canceled or completed, -+ * either by this function or prior to calling this function, %false -+ * otherwise. If @pending is %true, this function will always return %true. -+ */ -+bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending) -+{ -+ struct ssh_rtl *rtl; -+ bool canceled; -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_CANCELED_BIT, &rqst->state)) -+ return true; -+ -+ trace_ssam_request_cancel(rqst); -+ -+ if (pending) -+ canceled = ssh_rtl_cancel_pending(rqst); -+ else -+ canceled = ssh_rtl_cancel_nonpending(rqst); -+ -+ /* Note: rtl may be NULL if request has not been submitted yet. */ -+ rtl = ssh_request_rtl(rqst); -+ if (canceled && rtl) -+ ssh_rtl_tx_schedule(rtl); -+ -+ return canceled; -+} -+ -+static void ssh_rtl_packet_callback(struct ssh_packet *p, int status) -+{ -+ struct ssh_request *r = to_ssh_request(p); -+ -+ if (unlikely(status)) { -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return; -+ -+ /* -+ * The packet may get canceled even though it has not been -+ * submitted yet. The request may still be queued. Check the -+ * queue and remove it if necessary. As the timeout would have -+ * been started in this function on success, there's no need -+ * to cancel it here. -+ */ -+ ssh_rtl_queue_remove(r); -+ ssh_rtl_pending_remove(r); -+ ssh_rtl_complete_with_status(r, status); -+ -+ ssh_rtl_tx_schedule(ssh_request_rtl(r)); -+ return; -+ } -+ -+ /* Update state: Mark as transmitted and clear transmitting. */ -+ set_bit(SSH_REQUEST_SF_TRANSMITTED_BIT, &r->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_TRANSMITTING_BIT, &r->state); -+ -+ /* If we expect a response, we just need to start the timeout. */ -+ if (test_bit(SSH_REQUEST_TY_HAS_RESPONSE_BIT, &r->state)) { -+ /* -+ * Note: This is the only place where the timestamp gets set, -+ * all other access to it is read-only. -+ */ -+ ssh_rtl_timeout_start(r); -+ return; -+ } -+ -+ /* -+ * If we don't expect a response, lock, remove, and complete the -+ * request. Note that, at this point, the request is guaranteed to have -+ * left the queue and no timeout has been started. Thus we only need to -+ * remove it from pending. If the request has already been completed (it -+ * may have been canceled) return. -+ */ -+ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return; -+ -+ ssh_rtl_pending_remove(r); -+ ssh_rtl_complete_with_status(r, 0); -+ -+ ssh_rtl_tx_schedule(ssh_request_rtl(r)); -+} -+ -+static ktime_t ssh_request_get_expiration(struct ssh_request *r, ktime_t timeout) -+{ -+ ktime_t timestamp = READ_ONCE(r->timestamp); -+ -+ if (timestamp != KTIME_MAX) -+ return ktime_add(timestamp, timeout); -+ else -+ return KTIME_MAX; -+} -+ -+static void ssh_rtl_timeout_reap(struct work_struct *work) -+{ -+ struct ssh_rtl *rtl = to_ssh_rtl(work, rtx_timeout.reaper.work); -+ struct ssh_request *r, *n; -+ LIST_HEAD(claimed); -+ ktime_t now = ktime_get_coarse_boottime(); -+ ktime_t timeout = rtl->rtx_timeout.timeout; -+ ktime_t next = KTIME_MAX; -+ -+ trace_ssam_rtl_timeout_reap(atomic_read(&rtl->pending.count)); -+ -+ /* -+ * Mark reaper as "not pending". This is done before checking any -+ * requests to avoid lost-update type problems. -+ */ -+ spin_lock(&rtl->rtx_timeout.lock); -+ rtl->rtx_timeout.expires = KTIME_MAX; -+ spin_unlock(&rtl->rtx_timeout.lock); -+ -+ spin_lock(&rtl->pending.lock); -+ list_for_each_entry_safe(r, n, &rtl->pending.head, node) { -+ ktime_t expires = ssh_request_get_expiration(r, timeout); -+ -+ /* -+ * Check if the timeout hasn't expired yet. Find out next -+ * expiration date to be handled after this run. -+ */ -+ if (ktime_after(expires, now)) { -+ next = ktime_before(expires, next) ? expires : next; -+ continue; -+ } -+ -+ /* Avoid further transitions if locked. */ -+ if (test_and_set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state)) -+ continue; -+ -+ /* -+ * We have now marked the packet as locked. Thus it cannot be -+ * added to the pending or queued lists again after we've -+ * removed it here. We can therefore re-use the node of this -+ * packet temporarily. -+ */ -+ -+ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); -+ -+ atomic_dec(&rtl->pending.count); -+ list_del(&r->node); -+ -+ list_add_tail(&r->node, &claimed); -+ } -+ spin_unlock(&rtl->pending.lock); -+ -+ /* Cancel and complete the request. */ -+ list_for_each_entry_safe(r, n, &claimed, node) { -+ trace_ssam_request_timeout(r); -+ -+ /* -+ * At this point we've removed the packet from pending. This -+ * means that we've obtained the last (only) reference of the -+ * system to it. Thus we can just complete it. -+ */ -+ if (!test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ ssh_rtl_complete_with_status(r, -ETIMEDOUT); -+ -+ /* -+ * Drop the reference we've obtained by removing it from the -+ * pending set. -+ */ -+ list_del(&r->node); -+ ssh_request_put(r); -+ } -+ -+ /* Ensure that the reaper doesn't run again immediately. */ -+ next = max(next, ktime_add(now, SSH_RTL_REQUEST_TIMEOUT_RESOLUTION)); -+ if (next != KTIME_MAX) -+ ssh_rtl_timeout_reaper_mod(rtl, now, next); -+ -+ ssh_rtl_tx_schedule(rtl); -+} -+ -+static void ssh_rtl_rx_event(struct ssh_rtl *rtl, const struct ssh_command *cmd, -+ const struct ssam_span *data) -+{ -+ trace_ssam_rx_event_received(cmd, data->len); -+ -+ rtl_dbg(rtl, "rtl: handling event (rqid: %#06x)\n", -+ get_unaligned_le16(&cmd->rqid)); -+ -+ rtl->ops.handle_event(rtl, cmd, data); -+} -+ -+static void ssh_rtl_rx_command(struct ssh_ptl *p, const struct ssam_span *data) -+{ -+ struct ssh_rtl *rtl = to_ssh_rtl(p, ptl); -+ struct device *dev = &p->serdev->dev; -+ struct ssh_command *command; -+ struct ssam_span command_data; -+ -+ if (sshp_parse_command(dev, data, &command, &command_data)) -+ return; -+ -+ if (ssh_rqid_is_event(get_unaligned_le16(&command->rqid))) -+ ssh_rtl_rx_event(rtl, command, &command_data); -+ else -+ ssh_rtl_complete(rtl, command, &command_data); -+} -+ -+static void ssh_rtl_rx_data(struct ssh_ptl *p, const struct ssam_span *data) -+{ -+ if (!data->len) { -+ ptl_err(p, "rtl: rx: no data frame payload\n"); -+ return; -+ } -+ -+ switch (data->ptr[0]) { -+ case SSH_PLD_TYPE_CMD: -+ ssh_rtl_rx_command(p, data); -+ break; -+ -+ default: -+ ptl_err(p, "rtl: rx: unknown frame payload type (type: %#04x)\n", -+ data->ptr[0]); -+ break; -+ } -+} -+ -+static void ssh_rtl_packet_release(struct ssh_packet *p) -+{ -+ struct ssh_request *rqst; -+ -+ rqst = to_ssh_request(p); -+ rqst->ops->release(rqst); -+} -+ -+static const struct ssh_packet_ops ssh_rtl_packet_ops = { -+ .complete = ssh_rtl_packet_callback, -+ .release = ssh_rtl_packet_release, -+}; -+ -+/** -+ * ssh_request_init() - Initialize SSH request. -+ * @rqst: The request to initialize. -+ * @flags: Request flags, determining the type of the request. -+ * @ops: Request operations. -+ * -+ * Initializes the given SSH request and underlying packet. Sets the message -+ * buffer pointer to %NULL and the message buffer length to zero. This buffer -+ * has to be set separately via ssh_request_set_data() before submission and -+ * must contain a valid SSH request message. -+ * -+ * Return: Returns zero on success or %-EINVAL if the given flags are invalid. -+ */ -+int ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags, -+ const struct ssh_request_ops *ops) -+{ -+ unsigned long type = BIT(SSH_PACKET_TY_BLOCKING_BIT); -+ -+ /* Unsequenced requests cannot have a response. */ -+ if (flags & SSAM_REQUEST_UNSEQUENCED && flags & SSAM_REQUEST_HAS_RESPONSE) -+ return -EINVAL; -+ -+ if (!(flags & SSAM_REQUEST_UNSEQUENCED)) -+ type |= BIT(SSH_PACKET_TY_SEQUENCED_BIT); -+ -+ ssh_packet_init(&rqst->packet, type, SSH_PACKET_PRIORITY(DATA, 0), -+ &ssh_rtl_packet_ops); -+ -+ INIT_LIST_HEAD(&rqst->node); -+ -+ rqst->state = 0; -+ if (flags & SSAM_REQUEST_HAS_RESPONSE) -+ rqst->state |= BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT); -+ -+ rqst->timestamp = KTIME_MAX; -+ rqst->ops = ops; -+ -+ return 0; -+} -+ -+/** -+ * ssh_rtl_init() - Initialize request transport layer. -+ * @rtl: The request transport layer to initialize. -+ * @serdev: The underlying serial device, i.e. the lower-level transport. -+ * @ops: Request transport layer operations. -+ * -+ * Initializes the given request transport layer and associated packet -+ * transport layer. Transmitter and receiver threads must be started -+ * separately via ssh_rtl_start(), after the request-layer has been -+ * initialized and the lower-level serial device layer has been set up. -+ * -+ * Return: Returns zero on success and a nonzero error code on failure. -+ */ -+int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev, -+ const struct ssh_rtl_ops *ops) -+{ -+ struct ssh_ptl_ops ptl_ops; -+ int status; -+ -+ ptl_ops.data_received = ssh_rtl_rx_data; -+ -+ status = ssh_ptl_init(&rtl->ptl, serdev, &ptl_ops); -+ if (status) -+ return status; -+ -+ spin_lock_init(&rtl->queue.lock); -+ INIT_LIST_HEAD(&rtl->queue.head); -+ -+ spin_lock_init(&rtl->pending.lock); -+ INIT_LIST_HEAD(&rtl->pending.head); -+ atomic_set_release(&rtl->pending.count, 0); -+ -+ INIT_WORK(&rtl->tx.work, ssh_rtl_tx_work_fn); -+ -+ spin_lock_init(&rtl->rtx_timeout.lock); -+ rtl->rtx_timeout.timeout = SSH_RTL_REQUEST_TIMEOUT; -+ rtl->rtx_timeout.expires = KTIME_MAX; -+ INIT_DELAYED_WORK(&rtl->rtx_timeout.reaper, ssh_rtl_timeout_reap); -+ -+ rtl->ops = *ops; -+ -+ return 0; -+} -+ -+/** -+ * ssh_rtl_destroy() - Deinitialize request transport layer. -+ * @rtl: The request transport layer to deinitialize. -+ * -+ * Deinitializes the given request transport layer and frees resources -+ * associated with it. If receiver and/or transmitter threads have been -+ * started, the layer must first be shut down via ssh_rtl_shutdown() before -+ * this function can be called. -+ */ -+void ssh_rtl_destroy(struct ssh_rtl *rtl) -+{ -+ ssh_ptl_destroy(&rtl->ptl); -+} -+ -+/** -+ * ssh_rtl_start() - Start request transmitter and receiver. -+ * @rtl: The request transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_rtl_start(struct ssh_rtl *rtl) -+{ -+ int status; -+ -+ status = ssh_ptl_tx_start(&rtl->ptl); -+ if (status) -+ return status; -+ -+ ssh_rtl_tx_schedule(rtl); -+ -+ status = ssh_ptl_rx_start(&rtl->ptl); -+ if (status) { -+ ssh_rtl_flush(rtl, msecs_to_jiffies(5000)); -+ ssh_ptl_tx_stop(&rtl->ptl); -+ return status; -+ } -+ -+ return 0; -+} -+ -+struct ssh_flush_request { -+ struct ssh_request base; -+ struct completion completion; -+ int status; -+}; -+ -+static void ssh_rtl_flush_request_complete(struct ssh_request *r, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data, -+ int status) -+{ -+ struct ssh_flush_request *rqst; -+ -+ rqst = container_of(r, struct ssh_flush_request, base); -+ rqst->status = status; -+} -+ -+static void ssh_rtl_flush_request_release(struct ssh_request *r) -+{ -+ struct ssh_flush_request *rqst; -+ -+ rqst = container_of(r, struct ssh_flush_request, base); -+ complete_all(&rqst->completion); -+} -+ -+static const struct ssh_request_ops ssh_rtl_flush_request_ops = { -+ .complete = ssh_rtl_flush_request_complete, -+ .release = ssh_rtl_flush_request_release, -+}; -+ -+/** -+ * ssh_rtl_flush() - Flush the request transport layer. -+ * @rtl: request transport layer -+ * @timeout: timeout for the flush operation in jiffies -+ * -+ * Queue a special flush request and wait for its completion. This request -+ * will be completed after all other currently queued and pending requests -+ * have been completed. Instead of a normal data packet, this request submits -+ * a special flush packet, meaning that upon completion, also the underlying -+ * packet transport layer has been flushed. -+ * -+ * Flushing the request layer guarantees that all previously submitted -+ * requests have been fully completed before this call returns. Additionally, -+ * flushing blocks execution of all later submitted requests until the flush -+ * has been completed. -+ * -+ * If the caller ensures that no new requests are submitted after a call to -+ * this function, the request transport layer is guaranteed to have no -+ * remaining requests when this call returns. The same guarantee does not hold -+ * for the packet layer, on which control packets may still be queued after -+ * this call. -+ * -+ * Return: Returns zero on success, %-ETIMEDOUT if the flush timed out and has -+ * been canceled as a result of the timeout, or %-ESHUTDOWN if the packet -+ * and/or request transport layer has been shut down before this call. May -+ * also return %-EINTR if the underlying packet transmission has been -+ * interrupted. -+ */ -+int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout) -+{ -+ const unsigned int init_flags = SSAM_REQUEST_UNSEQUENCED; -+ struct ssh_flush_request rqst; -+ int status; -+ -+ ssh_request_init(&rqst.base, init_flags, &ssh_rtl_flush_request_ops); -+ rqst.base.packet.state |= BIT(SSH_PACKET_TY_FLUSH_BIT); -+ rqst.base.packet.priority = SSH_PACKET_PRIORITY(FLUSH, 0); -+ rqst.base.state |= BIT(SSH_REQUEST_TY_FLUSH_BIT); -+ -+ init_completion(&rqst.completion); -+ -+ status = ssh_rtl_submit(rtl, &rqst.base); -+ if (status) -+ return status; -+ -+ ssh_request_put(&rqst.base); -+ -+ if (!wait_for_completion_timeout(&rqst.completion, timeout)) { -+ ssh_rtl_cancel(&rqst.base, true); -+ wait_for_completion(&rqst.completion); -+ } -+ -+ WARN_ON(rqst.status != 0 && rqst.status != -ECANCELED && -+ rqst.status != -ESHUTDOWN && rqst.status != -EINTR); -+ -+ return rqst.status == -ECANCELED ? -ETIMEDOUT : rqst.status; -+} -+ -+/** -+ * ssh_rtl_shutdown() - Shut down request transport layer. -+ * @rtl: The request transport layer. -+ * -+ * Shuts down the request transport layer, removing and canceling all queued -+ * and pending requests. Requests canceled by this operation will be completed -+ * with %-ESHUTDOWN as status. Receiver and transmitter threads will be -+ * stopped, the lower-level packet layer will be shutdown. -+ * -+ * As a result of this function, the transport layer will be marked as shut -+ * down. Submission of requests after the transport layer has been shut down -+ * will fail with %-ESHUTDOWN. -+ */ -+void ssh_rtl_shutdown(struct ssh_rtl *rtl) -+{ -+ struct ssh_request *r, *n; -+ LIST_HEAD(claimed); -+ int pending; -+ -+ set_bit(SSH_RTL_SF_SHUTDOWN_BIT, &rtl->state); -+ /* -+ * Ensure that the layer gets marked as shut-down before actually -+ * stopping it. In combination with the check in ssh_rtl_submit(), -+ * this guarantees that no new requests can be added and all already -+ * queued requests are properly canceled. -+ */ -+ smp_mb__after_atomic(); -+ -+ /* Remove requests from queue. */ -+ spin_lock(&rtl->queue.lock); -+ list_for_each_entry_safe(r, n, &rtl->queue.head, node) { -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); -+ -+ list_del(&r->node); -+ list_add_tail(&r->node, &claimed); -+ } -+ spin_unlock(&rtl->queue.lock); -+ -+ /* -+ * We have now guaranteed that the queue is empty and no more new -+ * requests can be submitted (i.e. it will stay empty). This means that -+ * calling ssh_rtl_tx_schedule() will not schedule tx.work any more. So -+ * we can simply call cancel_work_sync() on tx.work here and when that -+ * returns, we've locked it down. This also means that after this call, -+ * we don't submit any more packets to the underlying packet layer, so -+ * we can also shut that down. -+ */ -+ -+ cancel_work_sync(&rtl->tx.work); -+ ssh_ptl_shutdown(&rtl->ptl); -+ cancel_delayed_work_sync(&rtl->rtx_timeout.reaper); -+ -+ /* -+ * Shutting down the packet layer should also have canceled all -+ * requests. Thus the pending set should be empty. Attempt to handle -+ * this gracefully anyways, even though this should be dead code. -+ */ -+ -+ pending = atomic_read(&rtl->pending.count); -+ if (WARN_ON(pending)) { -+ spin_lock(&rtl->pending.lock); -+ list_for_each_entry_safe(r, n, &rtl->pending.head, node) { -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); -+ -+ list_del(&r->node); -+ list_add_tail(&r->node, &claimed); -+ } -+ spin_unlock(&rtl->pending.lock); -+ } -+ -+ /* Finally, cancel and complete the requests we claimed before. */ -+ list_for_each_entry_safe(r, n, &claimed, node) { -+ /* -+ * We need test_and_set() because we still might compete with -+ * cancellation. -+ */ -+ if (!test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ ssh_rtl_complete_with_status(r, -ESHUTDOWN); -+ -+ /* -+ * Drop the reference we've obtained by removing it from the -+ * lists. -+ */ -+ list_del(&r->node); -+ ssh_request_put(r); -+ } -+} -diff --git a/drivers/platform/x86/surface_aggregator/ssh_request_layer.h b/drivers/platform/x86/surface_aggregator/ssh_request_layer.h -new file mode 100644 -index 000000000000..9c3cbae2d4bd ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/ssh_request_layer.h -@@ -0,0 +1,143 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * SSH request transport layer. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H -+#define _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "ssh_packet_layer.h" -+ -+/** -+ * enum ssh_rtl_state_flags - State-flags for &struct ssh_rtl. -+ * -+ * @SSH_RTL_SF_SHUTDOWN_BIT: -+ * Indicates that the request transport layer has been shut down or is -+ * being shut down and should not accept any new requests. -+ */ -+enum ssh_rtl_state_flags { -+ SSH_RTL_SF_SHUTDOWN_BIT, -+}; -+ -+/** -+ * struct ssh_rtl_ops - Callback operations for request transport layer. -+ * @handle_event: Function called when a SSH event has been received. The -+ * specified function takes the request layer, received command -+ * struct, and corresponding payload as arguments. If the event -+ * has no payload, the payload span is empty (not %NULL). -+ */ -+struct ssh_rtl_ops { -+ void (*handle_event)(struct ssh_rtl *rtl, const struct ssh_command *cmd, -+ const struct ssam_span *data); -+}; -+ -+/** -+ * struct ssh_rtl - SSH request transport layer. -+ * @ptl: Underlying packet transport layer. -+ * @state: State(-flags) of the transport layer. -+ * @queue: Request submission queue. -+ * @queue.lock: Lock for modifying the request submission queue. -+ * @queue.head: List-head of the request submission queue. -+ * @pending: Set/list of pending requests. -+ * @pending.lock: Lock for modifying the request set. -+ * @pending.head: List-head of the pending set/list. -+ * @pending.count: Number of currently pending requests. -+ * @tx: Transmitter subsystem. -+ * @tx.work: Transmitter work item. -+ * @rtx_timeout: Retransmission timeout subsystem. -+ * @rtx_timeout.lock: Lock for modifying the retransmission timeout reaper. -+ * @rtx_timeout.timeout: Timeout interval for retransmission. -+ * @rtx_timeout.expires: Time specifying when the reaper work is next scheduled. -+ * @rtx_timeout.reaper: Work performing timeout checks and subsequent actions. -+ * @ops: Request layer operations. -+ */ -+struct ssh_rtl { -+ struct ssh_ptl ptl; -+ unsigned long state; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ } queue; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ atomic_t count; -+ } pending; -+ -+ struct { -+ struct work_struct work; -+ } tx; -+ -+ struct { -+ spinlock_t lock; -+ ktime_t timeout; -+ ktime_t expires; -+ struct delayed_work reaper; -+ } rtx_timeout; -+ -+ struct ssh_rtl_ops ops; -+}; -+ -+#define rtl_dbg(r, fmt, ...) ptl_dbg(&(r)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_info(p, fmt, ...) ptl_info(&(p)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_warn(r, fmt, ...) ptl_warn(&(r)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_err(r, fmt, ...) ptl_err(&(r)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_dbg_cond(r, fmt, ...) __ssam_prcond(rtl_dbg, r, fmt, ##__VA_ARGS__) -+ -+#define to_ssh_rtl(ptr, member) \ -+ container_of(ptr, struct ssh_rtl, member) -+ -+/** -+ * ssh_rtl_get_device() - Get device associated with request transport layer. -+ * @rtl: The request transport layer. -+ * -+ * Return: Returns the device on which the given request transport layer -+ * builds upon. -+ */ -+static inline struct device *ssh_rtl_get_device(struct ssh_rtl *rtl) -+{ -+ return ssh_ptl_get_device(&rtl->ptl); -+} -+ -+/** -+ * ssh_request_rtl() - Get request transport layer associated with request. -+ * @rqst: The request to get the request transport layer reference for. -+ * -+ * Return: Returns the &struct ssh_rtl associated with the given SSH request. -+ */ -+static inline struct ssh_rtl *ssh_request_rtl(struct ssh_request *rqst) -+{ -+ struct ssh_ptl *ptl; -+ -+ ptl = READ_ONCE(rqst->packet.ptl); -+ return likely(ptl) ? to_ssh_rtl(ptl, ptl) : NULL; -+} -+ -+int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst); -+bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending); -+ -+int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev, -+ const struct ssh_rtl_ops *ops); -+ -+int ssh_rtl_start(struct ssh_rtl *rtl); -+int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout); -+void ssh_rtl_shutdown(struct ssh_rtl *rtl); -+void ssh_rtl_destroy(struct ssh_rtl *rtl); -+ -+int ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags, -+ const struct ssh_request_ops *ops); -+ -+#endif /* _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H */ -diff --git a/drivers/platform/x86/surface_aggregator/trace.h b/drivers/platform/x86/surface_aggregator/trace.h -new file mode 100644 -index 000000000000..de64cf169060 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator/trace.h -@@ -0,0 +1,632 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Trace points for SSAM/SSH. -+ * -+ * Copyright (C) 2020-2021 Maximilian Luz -+ */ -+ -+#undef TRACE_SYSTEM -+#define TRACE_SYSTEM surface_aggregator -+ -+#if !defined(_SURFACE_AGGREGATOR_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) -+#define _SURFACE_AGGREGATOR_TRACE_H -+ -+#include -+ -+#include -+#include -+ -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_SEQ); -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_NSQ); -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_ACK); -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_NAK); -+ -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_LOCKED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_QUEUED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_PENDING_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTING_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_ACKED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_CANCELED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_COMPLETED_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_PACKET_TY_FLUSH_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_TY_SEQUENCED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_TY_BLOCKING_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_SF_MASK); -+TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_TY_MASK); -+ -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_LOCKED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_QUEUED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_PENDING_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTING_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_RSPRCVD_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_CANCELED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_COMPLETED_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_REQUEST_TY_FLUSH_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_TY_HAS_RESPONSE_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_SF_MASK); -+TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_TY_MASK); -+ -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SAM); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAT); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TMP); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_PMC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_FAN); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_PoM); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_DBG); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_KBD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_FWU); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_UNI); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_LPC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCL); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SFL); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_KIP); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_EXT); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BLD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAS); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SEN); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SRQ); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_MCU); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_VID); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG); -+ -+#define SSAM_PTR_UID_LEN 9 -+#define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1) -+#define SSAM_SEQ_NOT_APPLICABLE ((u16)-1) -+#define SSAM_RQID_NOT_APPLICABLE ((u32)-1) -+#define SSAM_SSH_TC_NOT_APPLICABLE 0 -+ -+#ifndef _SURFACE_AGGREGATOR_TRACE_HELPERS -+#define _SURFACE_AGGREGATOR_TRACE_HELPERS -+ -+/** -+ * ssam_trace_ptr_uid() - Convert the pointer to a non-pointer UID string. -+ * @ptr: The pointer to convert. -+ * @uid_str: A buffer of length SSAM_PTR_UID_LEN where the UID will be stored. -+ * -+ * Converts the given pointer into a UID string that is safe to be shared -+ * with userspace and logs, i.e. doesn't give away the real memory location. -+ */ -+static inline void ssam_trace_ptr_uid(const void *ptr, char *uid_str) -+{ -+ char buf[2 * sizeof(void *) + 1]; -+ -+ BUILD_BUG_ON(ARRAY_SIZE(buf) < SSAM_PTR_UID_LEN); -+ -+ snprintf(buf, ARRAY_SIZE(buf), "%p", ptr); -+ memcpy(uid_str, &buf[ARRAY_SIZE(buf) - SSAM_PTR_UID_LEN], -+ SSAM_PTR_UID_LEN); -+} -+ -+/** -+ * ssam_trace_get_packet_seq() - Read the packet's sequence ID. -+ * @p: The packet. -+ * -+ * Return: Returns the packet's sequence ID (SEQ) field if present, or -+ * %SSAM_SEQ_NOT_APPLICABLE if not (e.g. flush packet). -+ */ -+static inline u16 ssam_trace_get_packet_seq(const struct ssh_packet *p) -+{ -+ if (!p->data.ptr || p->data.len < SSH_MESSAGE_LENGTH(0)) -+ return SSAM_SEQ_NOT_APPLICABLE; -+ -+ return p->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; -+} -+ -+/** -+ * ssam_trace_get_request_id() - Read the packet's request ID. -+ * @p: The packet. -+ * -+ * Return: Returns the packet's request ID (RQID) field if the packet -+ * represents a request with command data, or %SSAM_RQID_NOT_APPLICABLE if not -+ * (e.g. flush request, control packet). -+ */ -+static inline u32 ssam_trace_get_request_id(const struct ssh_packet *p) -+{ -+ if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) -+ return SSAM_RQID_NOT_APPLICABLE; -+ -+ return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(rqid)]); -+} -+ -+/** -+ * ssam_trace_get_request_tc() - Read the packet's request target category. -+ * @p: The packet. -+ * -+ * Return: Returns the packet's request target category (TC) field if the -+ * packet represents a request with command data, or %SSAM_TC_NOT_APPLICABLE -+ * if not (e.g. flush request, control packet). -+ */ -+static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) -+{ -+ if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) -+ return SSAM_SSH_TC_NOT_APPLICABLE; -+ -+ return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(tc)]); -+} -+ -+#endif /* _SURFACE_AGGREGATOR_TRACE_HELPERS */ -+ -+#define ssam_trace_get_command_field_u8(packet, field) \ -+ ((!(packet) || (packet)->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) \ -+ ? 0 : (packet)->data.ptr[SSH_MSGOFFSET_COMMAND(field)]) -+ -+#define ssam_show_generic_u8_field(value) \ -+ __print_symbolic(value, \ -+ { SSAM_U8_FIELD_NOT_APPLICABLE, "N/A" } \ -+ ) -+ -+#define ssam_show_frame_type(ty) \ -+ __print_symbolic(ty, \ -+ { SSH_FRAME_TYPE_DATA_SEQ, "DSEQ" }, \ -+ { SSH_FRAME_TYPE_DATA_NSQ, "DNSQ" }, \ -+ { SSH_FRAME_TYPE_ACK, "ACK" }, \ -+ { SSH_FRAME_TYPE_NAK, "NAK" } \ -+ ) -+ -+#define ssam_show_packet_type(type) \ -+ __print_flags(flags & SSH_PACKET_FLAGS_TY_MASK, "", \ -+ { BIT(SSH_PACKET_TY_FLUSH_BIT), "F" }, \ -+ { BIT(SSH_PACKET_TY_SEQUENCED_BIT), "S" }, \ -+ { BIT(SSH_PACKET_TY_BLOCKING_BIT), "B" } \ -+ ) -+ -+#define ssam_show_packet_state(state) \ -+ __print_flags(flags & SSH_PACKET_FLAGS_SF_MASK, "", \ -+ { BIT(SSH_PACKET_SF_LOCKED_BIT), "L" }, \ -+ { BIT(SSH_PACKET_SF_QUEUED_BIT), "Q" }, \ -+ { BIT(SSH_PACKET_SF_PENDING_BIT), "P" }, \ -+ { BIT(SSH_PACKET_SF_TRANSMITTING_BIT), "S" }, \ -+ { BIT(SSH_PACKET_SF_TRANSMITTED_BIT), "T" }, \ -+ { BIT(SSH_PACKET_SF_ACKED_BIT), "A" }, \ -+ { BIT(SSH_PACKET_SF_CANCELED_BIT), "C" }, \ -+ { BIT(SSH_PACKET_SF_COMPLETED_BIT), "F" } \ -+ ) -+ -+#define ssam_show_packet_seq(seq) \ -+ __print_symbolic(seq, \ -+ { SSAM_SEQ_NOT_APPLICABLE, "N/A" } \ -+ ) -+ -+#define ssam_show_request_type(flags) \ -+ __print_flags((flags) & SSH_REQUEST_FLAGS_TY_MASK, "", \ -+ { BIT(SSH_REQUEST_TY_FLUSH_BIT), "F" }, \ -+ { BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT), "R" } \ -+ ) -+ -+#define ssam_show_request_state(flags) \ -+ __print_flags((flags) & SSH_REQUEST_FLAGS_SF_MASK, "", \ -+ { BIT(SSH_REQUEST_SF_LOCKED_BIT), "L" }, \ -+ { BIT(SSH_REQUEST_SF_QUEUED_BIT), "Q" }, \ -+ { BIT(SSH_REQUEST_SF_PENDING_BIT), "P" }, \ -+ { BIT(SSH_REQUEST_SF_TRANSMITTING_BIT), "S" }, \ -+ { BIT(SSH_REQUEST_SF_TRANSMITTED_BIT), "T" }, \ -+ { BIT(SSH_REQUEST_SF_RSPRCVD_BIT), "A" }, \ -+ { BIT(SSH_REQUEST_SF_CANCELED_BIT), "C" }, \ -+ { BIT(SSH_REQUEST_SF_COMPLETED_BIT), "F" } \ -+ ) -+ -+#define ssam_show_request_id(rqid) \ -+ __print_symbolic(rqid, \ -+ { SSAM_RQID_NOT_APPLICABLE, "N/A" } \ -+ ) -+ -+#define ssam_show_ssh_tc(rqid) \ -+ __print_symbolic(rqid, \ -+ { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ -+ { SSAM_SSH_TC_SAM, "SAM" }, \ -+ { SSAM_SSH_TC_BAT, "BAT" }, \ -+ { SSAM_SSH_TC_TMP, "TMP" }, \ -+ { SSAM_SSH_TC_PMC, "PMC" }, \ -+ { SSAM_SSH_TC_FAN, "FAN" }, \ -+ { SSAM_SSH_TC_PoM, "PoM" }, \ -+ { SSAM_SSH_TC_DBG, "DBG" }, \ -+ { SSAM_SSH_TC_KBD, "KBD" }, \ -+ { SSAM_SSH_TC_FWU, "FWU" }, \ -+ { SSAM_SSH_TC_UNI, "UNI" }, \ -+ { SSAM_SSH_TC_LPC, "LPC" }, \ -+ { SSAM_SSH_TC_TCL, "TCL" }, \ -+ { SSAM_SSH_TC_SFL, "SFL" }, \ -+ { SSAM_SSH_TC_KIP, "KIP" }, \ -+ { SSAM_SSH_TC_EXT, "EXT" }, \ -+ { SSAM_SSH_TC_BLD, "BLD" }, \ -+ { SSAM_SSH_TC_BAS, "BAS" }, \ -+ { SSAM_SSH_TC_SEN, "SEN" }, \ -+ { SSAM_SSH_TC_SRQ, "SRQ" }, \ -+ { SSAM_SSH_TC_MCU, "MCU" }, \ -+ { SSAM_SSH_TC_HID, "HID" }, \ -+ { SSAM_SSH_TC_TCH, "TCH" }, \ -+ { SSAM_SSH_TC_BKL, "BKL" }, \ -+ { SSAM_SSH_TC_TAM, "TAM" }, \ -+ { SSAM_SSH_TC_ACC, "ACC" }, \ -+ { SSAM_SSH_TC_UFI, "UFI" }, \ -+ { SSAM_SSH_TC_USC, "USC" }, \ -+ { SSAM_SSH_TC_PEN, "PEN" }, \ -+ { SSAM_SSH_TC_VID, "VID" }, \ -+ { SSAM_SSH_TC_AUD, "AUD" }, \ -+ { SSAM_SSH_TC_SMC, "SMC" }, \ -+ { SSAM_SSH_TC_KPD, "KPD" }, \ -+ { SSAM_SSH_TC_REG, "REG" } \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_frame_class, -+ TP_PROTO(const struct ssh_frame *frame), -+ -+ TP_ARGS(frame), -+ -+ TP_STRUCT__entry( -+ __field(u8, type) -+ __field(u8, seq) -+ __field(u16, len) -+ ), -+ -+ TP_fast_assign( -+ __entry->type = frame->type; -+ __entry->seq = frame->seq; -+ __entry->len = get_unaligned_le16(&frame->len); -+ ), -+ -+ TP_printk("ty=%s, seq=%#04x, len=%u", -+ ssam_show_frame_type(__entry->type), -+ __entry->seq, -+ __entry->len -+ ) -+); -+ -+#define DEFINE_SSAM_FRAME_EVENT(name) \ -+ DEFINE_EVENT(ssam_frame_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_frame *frame), \ -+ TP_ARGS(frame) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_command_class, -+ TP_PROTO(const struct ssh_command *cmd, u16 len), -+ -+ TP_ARGS(cmd, len), -+ -+ TP_STRUCT__entry( -+ __field(u16, rqid) -+ __field(u16, len) -+ __field(u8, tc) -+ __field(u8, cid) -+ __field(u8, iid) -+ ), -+ -+ TP_fast_assign( -+ __entry->rqid = get_unaligned_le16(&cmd->rqid); -+ __entry->tc = cmd->tc; -+ __entry->cid = cmd->cid; -+ __entry->iid = cmd->iid; -+ __entry->len = len; -+ ), -+ -+ TP_printk("rqid=%#06x, tc=%s, cid=%#04x, iid=%#04x, len=%u", -+ __entry->rqid, -+ ssam_show_ssh_tc(__entry->tc), -+ __entry->cid, -+ __entry->iid, -+ __entry->len -+ ) -+); -+ -+#define DEFINE_SSAM_COMMAND_EVENT(name) \ -+ DEFINE_EVENT(ssam_command_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_command *cmd, u16 len), \ -+ TP_ARGS(cmd, len) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_packet_class, -+ TP_PROTO(const struct ssh_packet *packet), -+ -+ TP_ARGS(packet), -+ -+ TP_STRUCT__entry( -+ __field(unsigned long, state) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(u8, priority) -+ __field(u16, length) -+ __field(u16, seq) -+ ), -+ -+ TP_fast_assign( -+ __entry->state = READ_ONCE(packet->state); -+ ssam_trace_ptr_uid(packet, __entry->uid); -+ __entry->priority = READ_ONCE(packet->priority); -+ __entry->length = packet->data.len; -+ __entry->seq = ssam_trace_get_packet_seq(packet); -+ ), -+ -+ TP_printk("uid=%s, seq=%s, ty=%s, pri=%#04x, len=%u, sta=%s", -+ __entry->uid, -+ ssam_show_packet_seq(__entry->seq), -+ ssam_show_packet_type(__entry->state), -+ __entry->priority, -+ __entry->length, -+ ssam_show_packet_state(__entry->state) -+ ) -+); -+ -+#define DEFINE_SSAM_PACKET_EVENT(name) \ -+ DEFINE_EVENT(ssam_packet_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_packet *packet), \ -+ TP_ARGS(packet) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_packet_status_class, -+ TP_PROTO(const struct ssh_packet *packet, int status), -+ -+ TP_ARGS(packet, status), -+ -+ TP_STRUCT__entry( -+ __field(unsigned long, state) -+ __field(int, status) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(u8, priority) -+ __field(u16, length) -+ __field(u16, seq) -+ ), -+ -+ TP_fast_assign( -+ __entry->state = READ_ONCE(packet->state); -+ __entry->status = status; -+ ssam_trace_ptr_uid(packet, __entry->uid); -+ __entry->priority = READ_ONCE(packet->priority); -+ __entry->length = packet->data.len; -+ __entry->seq = ssam_trace_get_packet_seq(packet); -+ ), -+ -+ TP_printk("uid=%s, seq=%s, ty=%s, pri=%#04x, len=%u, sta=%s, status=%d", -+ __entry->uid, -+ ssam_show_packet_seq(__entry->seq), -+ ssam_show_packet_type(__entry->state), -+ __entry->priority, -+ __entry->length, -+ ssam_show_packet_state(__entry->state), -+ __entry->status -+ ) -+); -+ -+#define DEFINE_SSAM_PACKET_STATUS_EVENT(name) \ -+ DEFINE_EVENT(ssam_packet_status_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_packet *packet, int status), \ -+ TP_ARGS(packet, status) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_request_class, -+ TP_PROTO(const struct ssh_request *request), -+ -+ TP_ARGS(request), -+ -+ TP_STRUCT__entry( -+ __field(unsigned long, state) -+ __field(u32, rqid) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(u8, tc) -+ __field(u16, cid) -+ __field(u16, iid) -+ ), -+ -+ TP_fast_assign( -+ const struct ssh_packet *p = &request->packet; -+ -+ /* Use packet for UID so we can match requests to packets. */ -+ __entry->state = READ_ONCE(request->state); -+ __entry->rqid = ssam_trace_get_request_id(p); -+ ssam_trace_ptr_uid(p, __entry->uid); -+ __entry->tc = ssam_trace_get_request_tc(p); -+ __entry->cid = ssam_trace_get_command_field_u8(p, cid); -+ __entry->iid = ssam_trace_get_command_field_u8(p, iid); -+ ), -+ -+ TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s", -+ __entry->uid, -+ ssam_show_request_id(__entry->rqid), -+ ssam_show_request_type(__entry->state), -+ ssam_show_request_state(__entry->state), -+ ssam_show_ssh_tc(__entry->tc), -+ ssam_show_generic_u8_field(__entry->cid), -+ ssam_show_generic_u8_field(__entry->iid) -+ ) -+); -+ -+#define DEFINE_SSAM_REQUEST_EVENT(name) \ -+ DEFINE_EVENT(ssam_request_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_request *request), \ -+ TP_ARGS(request) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_request_status_class, -+ TP_PROTO(const struct ssh_request *request, int status), -+ -+ TP_ARGS(request, status), -+ -+ TP_STRUCT__entry( -+ __field(unsigned long, state) -+ __field(u32, rqid) -+ __field(int, status) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(u8, tc) -+ __field(u16, cid) -+ __field(u16, iid) -+ ), -+ -+ TP_fast_assign( -+ const struct ssh_packet *p = &request->packet; -+ -+ /* Use packet for UID so we can match requests to packets. */ -+ __entry->state = READ_ONCE(request->state); -+ __entry->rqid = ssam_trace_get_request_id(p); -+ __entry->status = status; -+ ssam_trace_ptr_uid(p, __entry->uid); -+ __entry->tc = ssam_trace_get_request_tc(p); -+ __entry->cid = ssam_trace_get_command_field_u8(p, cid); -+ __entry->iid = ssam_trace_get_command_field_u8(p, iid); -+ ), -+ -+ TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s, status=%d", -+ __entry->uid, -+ ssam_show_request_id(__entry->rqid), -+ ssam_show_request_type(__entry->state), -+ ssam_show_request_state(__entry->state), -+ ssam_show_ssh_tc(__entry->tc), -+ ssam_show_generic_u8_field(__entry->cid), -+ ssam_show_generic_u8_field(__entry->iid), -+ __entry->status -+ ) -+); -+ -+#define DEFINE_SSAM_REQUEST_STATUS_EVENT(name) \ -+ DEFINE_EVENT(ssam_request_status_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_request *request, int status),\ -+ TP_ARGS(request, status) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_alloc_class, -+ TP_PROTO(void *ptr, size_t len), -+ -+ TP_ARGS(ptr, len), -+ -+ TP_STRUCT__entry( -+ __field(size_t, len) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ ), -+ -+ TP_fast_assign( -+ __entry->len = len; -+ ssam_trace_ptr_uid(ptr, __entry->uid); -+ ), -+ -+ TP_printk("uid=%s, len=%zu", __entry->uid, __entry->len) -+); -+ -+#define DEFINE_SSAM_ALLOC_EVENT(name) \ -+ DEFINE_EVENT(ssam_alloc_class, ssam_##name, \ -+ TP_PROTO(void *ptr, size_t len), \ -+ TP_ARGS(ptr, len) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_free_class, -+ TP_PROTO(void *ptr), -+ -+ TP_ARGS(ptr), -+ -+ TP_STRUCT__entry( -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ ), -+ -+ TP_fast_assign( -+ ssam_trace_ptr_uid(ptr, __entry->uid); -+ ), -+ -+ TP_printk("uid=%s", __entry->uid) -+); -+ -+#define DEFINE_SSAM_FREE_EVENT(name) \ -+ DEFINE_EVENT(ssam_free_class, ssam_##name, \ -+ TP_PROTO(void *ptr), \ -+ TP_ARGS(ptr) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_pending_class, -+ TP_PROTO(unsigned int pending), -+ -+ TP_ARGS(pending), -+ -+ TP_STRUCT__entry( -+ __field(unsigned int, pending) -+ ), -+ -+ TP_fast_assign( -+ __entry->pending = pending; -+ ), -+ -+ TP_printk("pending=%u", __entry->pending) -+); -+ -+#define DEFINE_SSAM_PENDING_EVENT(name) \ -+ DEFINE_EVENT(ssam_pending_class, ssam_##name, \ -+ TP_PROTO(unsigned int pending), \ -+ TP_ARGS(pending) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_data_class, -+ TP_PROTO(size_t length), -+ -+ TP_ARGS(length), -+ -+ TP_STRUCT__entry( -+ __field(size_t, length) -+ ), -+ -+ TP_fast_assign( -+ __entry->length = length; -+ ), -+ -+ TP_printk("length=%zu", __entry->length) -+); -+ -+#define DEFINE_SSAM_DATA_EVENT(name) \ -+ DEFINE_EVENT(ssam_data_class, ssam_##name, \ -+ TP_PROTO(size_t length), \ -+ TP_ARGS(length) \ -+ ) -+ -+DEFINE_SSAM_FRAME_EVENT(rx_frame_received); -+DEFINE_SSAM_COMMAND_EVENT(rx_response_received); -+DEFINE_SSAM_COMMAND_EVENT(rx_event_received); -+ -+DEFINE_SSAM_PACKET_EVENT(packet_release); -+DEFINE_SSAM_PACKET_EVENT(packet_submit); -+DEFINE_SSAM_PACKET_EVENT(packet_resubmit); -+DEFINE_SSAM_PACKET_EVENT(packet_timeout); -+DEFINE_SSAM_PACKET_EVENT(packet_cancel); -+DEFINE_SSAM_PACKET_STATUS_EVENT(packet_complete); -+DEFINE_SSAM_PENDING_EVENT(ptl_timeout_reap); -+ -+DEFINE_SSAM_REQUEST_EVENT(request_submit); -+DEFINE_SSAM_REQUEST_EVENT(request_timeout); -+DEFINE_SSAM_REQUEST_EVENT(request_cancel); -+DEFINE_SSAM_REQUEST_STATUS_EVENT(request_complete); -+DEFINE_SSAM_PENDING_EVENT(rtl_timeout_reap); -+ -+DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_ack_packet); -+DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_nak_packet); -+DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_dsq_packet); -+DEFINE_SSAM_PACKET_STATUS_EVENT(ei_tx_fail_write); -+DEFINE_SSAM_PACKET_EVENT(ei_tx_corrupt_data); -+DEFINE_SSAM_DATA_EVENT(ei_rx_corrupt_syn); -+DEFINE_SSAM_FRAME_EVENT(ei_rx_corrupt_data); -+DEFINE_SSAM_REQUEST_EVENT(ei_rx_drop_response); -+ -+DEFINE_SSAM_ALLOC_EVENT(ctrl_packet_alloc); -+DEFINE_SSAM_FREE_EVENT(ctrl_packet_free); -+ -+DEFINE_SSAM_ALLOC_EVENT(event_item_alloc); -+DEFINE_SSAM_FREE_EVENT(event_item_free); -+ -+#endif /* _SURFACE_AGGREGATOR_TRACE_H */ -+ -+/* This part must be outside protection */ -+#undef TRACE_INCLUDE_PATH -+#undef TRACE_INCLUDE_FILE -+ -+#define TRACE_INCLUDE_PATH . -+#define TRACE_INCLUDE_FILE trace -+ -+#include -diff --git a/drivers/platform/x86/surface_aggregator_cdev.c b/drivers/platform/x86/surface_aggregator_cdev.c -new file mode 100644 -index 000000000000..30fb50fde450 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator_cdev.c -@@ -0,0 +1,810 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Provides user-space access to the SSAM EC via the /dev/surface/aggregator -+ * misc device. Intended for debugging and development. -+ * -+ * Copyright (C) 2020-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+#define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" -+ -+ -+/* -- Main structures. ------------------------------------------------------ */ -+ -+enum ssam_cdev_device_state { -+ SSAM_CDEV_DEVICE_SHUTDOWN_BIT = BIT(0), -+}; -+ -+struct ssam_cdev { -+ struct kref kref; -+ struct rw_semaphore lock; -+ -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ struct miscdevice mdev; -+ unsigned long flags; -+ -+ struct rw_semaphore client_lock; /* Guards client list. */ -+ struct list_head client_list; -+}; -+ -+struct ssam_cdev_client; -+ -+struct ssam_cdev_notifier { -+ struct ssam_cdev_client *client; -+ struct ssam_event_notifier nf; -+}; -+ -+struct ssam_cdev_client { -+ struct ssam_cdev *cdev; -+ struct list_head node; -+ -+ struct mutex notifier_lock; /* Guards notifier access for registration */ -+ struct ssam_cdev_notifier *notifier[SSH_NUM_EVENTS]; -+ -+ struct mutex read_lock; /* Guards FIFO buffer read access */ -+ struct mutex write_lock; /* Guards FIFO buffer write access */ -+ DECLARE_KFIFO(buffer, u8, 4096); -+ -+ wait_queue_head_t waitq; -+ struct fasync_struct *fasync; -+}; -+ -+static void __ssam_cdev_release(struct kref *kref) -+{ -+ kfree(container_of(kref, struct ssam_cdev, kref)); -+} -+ -+static struct ssam_cdev *ssam_cdev_get(struct ssam_cdev *cdev) -+{ -+ if (cdev) -+ kref_get(&cdev->kref); -+ -+ return cdev; -+} -+ -+static void ssam_cdev_put(struct ssam_cdev *cdev) -+{ -+ if (cdev) -+ kref_put(&cdev->kref, __ssam_cdev_release); -+} -+ -+ -+/* -- Notifier handling. ---------------------------------------------------- */ -+ -+static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in) -+{ -+ struct ssam_cdev_notifier *cdev_nf = container_of(nf, struct ssam_cdev_notifier, nf); -+ struct ssam_cdev_client *client = cdev_nf->client; -+ struct ssam_cdev_event event; -+ size_t n = struct_size(&event, data, in->length); -+ -+ /* Translate event. */ -+ event.target_category = in->target_category; -+ event.target_id = in->target_id; -+ event.command_id = in->command_id; -+ event.instance_id = in->instance_id; -+ event.length = in->length; -+ -+ mutex_lock(&client->write_lock); -+ -+ /* Make sure we have enough space. */ -+ if (kfifo_avail(&client->buffer) < n) { -+ dev_warn(client->cdev->dev, -+ "buffer full, dropping event (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", -+ in->target_category, in->target_id, in->command_id, in->instance_id); -+ mutex_unlock(&client->write_lock); -+ return 0; -+ } -+ -+ /* Copy event header and payload. */ -+ kfifo_in(&client->buffer, (const u8 *)&event, struct_size(&event, data, 0)); -+ kfifo_in(&client->buffer, &in->data[0], in->length); -+ -+ mutex_unlock(&client->write_lock); -+ -+ /* Notify waiting readers. */ -+ kill_fasync(&client->fasync, SIGIO, POLL_IN); -+ wake_up_interruptible(&client->waitq); -+ -+ /* -+ * Don't mark events as handled, this is the job of a proper driver and -+ * not the debugging interface. -+ */ -+ return 0; -+} -+ -+static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, int priority) -+{ -+ const u16 rqid = ssh_tc_to_rqid(tc); -+ const u16 event = ssh_rqid_to_event(rqid); -+ struct ssam_cdev_notifier *nf; -+ int status; -+ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ /* Validate notifier target category. */ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ mutex_lock(&client->notifier_lock); -+ -+ /* Check if the notifier has already been registered. */ -+ if (client->notifier[event]) { -+ mutex_unlock(&client->notifier_lock); -+ return -EEXIST; -+ } -+ -+ /* Allocate new notifier. */ -+ nf = kzalloc(sizeof(*nf), GFP_KERNEL); -+ if (!nf) { -+ mutex_unlock(&client->notifier_lock); -+ return -ENOMEM; -+ } -+ -+ /* -+ * Create a dummy notifier with the minimal required fields for -+ * observer registration. Note that we can skip fully specifying event -+ * and registry here as we do not need any matching and use silent -+ * registration, which does not enable the corresponding event. -+ */ -+ nf->client = client; -+ nf->nf.base.fn = ssam_cdev_notifier; -+ nf->nf.base.priority = priority; -+ nf->nf.event.id.target_category = tc; -+ nf->nf.event.mask = 0; /* Do not do any matching. */ -+ nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER; -+ -+ /* Register notifier. */ -+ status = ssam_notifier_register(client->cdev->ctrl, &nf->nf); -+ if (status) -+ kfree(nf); -+ else -+ client->notifier[event] = nf; -+ -+ mutex_unlock(&client->notifier_lock); -+ return status; -+} -+ -+static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc) -+{ -+ const u16 rqid = ssh_tc_to_rqid(tc); -+ const u16 event = ssh_rqid_to_event(rqid); -+ int status; -+ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ /* Validate notifier target category. */ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ mutex_lock(&client->notifier_lock); -+ -+ /* Check if the notifier is currently registered. */ -+ if (!client->notifier[event]) { -+ mutex_unlock(&client->notifier_lock); -+ return -ENOENT; -+ } -+ -+ /* Unregister and free notifier. */ -+ status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[event]->nf); -+ kfree(client->notifier[event]); -+ client->notifier[event] = NULL; -+ -+ mutex_unlock(&client->notifier_lock); -+ return status; -+} -+ -+static void ssam_cdev_notifier_unregister_all(struct ssam_cdev_client *client) -+{ -+ int i; -+ -+ down_read(&client->cdev->lock); -+ -+ /* -+ * This function may be used during shutdown, thus we need to test for -+ * cdev->ctrl instead of the SSAM_CDEV_DEVICE_SHUTDOWN_BIT bit. -+ */ -+ if (client->cdev->ctrl) { -+ for (i = 0; i < SSH_NUM_EVENTS; i++) -+ ssam_cdev_notifier_unregister(client, i + 1); -+ -+ } else { -+ int count = 0; -+ -+ /* -+ * Device has been shut down. Any notifier remaining is a bug, -+ * so warn about that as this would otherwise hardly be -+ * noticeable. Nevertheless, free them as well. -+ */ -+ mutex_lock(&client->notifier_lock); -+ for (i = 0; i < SSH_NUM_EVENTS; i++) { -+ count += !!(client->notifier[i]); -+ kfree(client->notifier[i]); -+ client->notifier[i] = NULL; -+ } -+ mutex_unlock(&client->notifier_lock); -+ -+ WARN_ON(count > 0); -+ } -+ -+ up_read(&client->cdev->lock); -+} -+ -+ -+/* -- IOCTL functions. ------------------------------------------------------ */ -+ -+static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_request __user *r) -+{ -+ struct ssam_cdev_request rqst; -+ struct ssam_request spec = {}; -+ struct ssam_response rsp = {}; -+ const void __user *plddata; -+ void __user *rspdata; -+ int status = 0, ret = 0, tmp; -+ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); -+ if (ret) -+ goto out; -+ -+ plddata = u64_to_user_ptr(rqst.payload.data); -+ rspdata = u64_to_user_ptr(rqst.response.data); -+ -+ /* Setup basic request fields. */ -+ spec.target_category = rqst.target_category; -+ spec.target_id = rqst.target_id; -+ spec.command_id = rqst.command_id; -+ spec.instance_id = rqst.instance_id; -+ spec.flags = 0; -+ spec.length = rqst.payload.length; -+ spec.payload = NULL; -+ -+ if (rqst.flags & SSAM_CDEV_REQUEST_HAS_RESPONSE) -+ spec.flags |= SSAM_REQUEST_HAS_RESPONSE; -+ -+ if (rqst.flags & SSAM_CDEV_REQUEST_UNSEQUENCED) -+ spec.flags |= SSAM_REQUEST_UNSEQUENCED; -+ -+ rsp.capacity = rqst.response.length; -+ rsp.length = 0; -+ rsp.pointer = NULL; -+ -+ /* Get request payload from user-space. */ -+ if (spec.length) { -+ if (!plddata) { -+ ret = -EINVAL; -+ goto out; -+ } -+ -+ /* -+ * Note: spec.length is limited to U16_MAX bytes via struct -+ * ssam_cdev_request. This is slightly larger than the -+ * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the -+ * underlying protocol (note that nothing remotely this size -+ * should ever be allocated in any normal case). This size is -+ * validated later in ssam_request_sync(), for allocation the -+ * bound imposed by u16 should be enough. -+ */ -+ spec.payload = kzalloc(spec.length, GFP_KERNEL); -+ if (!spec.payload) { -+ ret = -ENOMEM; -+ goto out; -+ } -+ -+ if (copy_from_user((void *)spec.payload, plddata, spec.length)) { -+ ret = -EFAULT; -+ goto out; -+ } -+ } -+ -+ /* Allocate response buffer. */ -+ if (rsp.capacity) { -+ if (!rspdata) { -+ ret = -EINVAL; -+ goto out; -+ } -+ -+ /* -+ * Note: rsp.capacity is limited to U16_MAX bytes via struct -+ * ssam_cdev_request. This is slightly larger than the -+ * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the -+ * underlying protocol (note that nothing remotely this size -+ * should ever be allocated in any normal case). In later use, -+ * this capacity does not have to be strictly bounded, as it -+ * is only used as an output buffer to be written to. For -+ * allocation the bound imposed by u16 should be enough. -+ */ -+ rsp.pointer = kzalloc(rsp.capacity, GFP_KERNEL); -+ if (!rsp.pointer) { -+ ret = -ENOMEM; -+ goto out; -+ } -+ } -+ -+ /* Perform request. */ -+ status = ssam_request_sync(client->cdev->ctrl, &spec, &rsp); -+ if (status) -+ goto out; -+ -+ /* Copy response to user-space. */ -+ if (rsp.length && copy_to_user(rspdata, rsp.pointer, rsp.length)) -+ ret = -EFAULT; -+ -+out: -+ /* Always try to set response-length and status. */ -+ tmp = put_user(rsp.length, &r->response.length); -+ if (tmp) -+ ret = tmp; -+ -+ tmp = put_user(status, &r->status); -+ if (tmp) -+ ret = tmp; -+ -+ /* Cleanup. */ -+ kfree(spec.payload); -+ kfree(rsp.pointer); -+ -+ return ret; -+} -+ -+static long ssam_cdev_notif_register(struct ssam_cdev_client *client, -+ const struct ssam_cdev_notifier_desc __user *d) -+{ -+ struct ssam_cdev_notifier_desc desc; -+ long ret; -+ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ return ssam_cdev_notifier_register(client, desc.target_category, desc.priority); -+} -+ -+static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, -+ const struct ssam_cdev_notifier_desc __user *d) -+{ -+ struct ssam_cdev_notifier_desc desc; -+ long ret; -+ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ return ssam_cdev_notifier_unregister(client, desc.target_category); -+} -+ -+static long ssam_cdev_event_enable(struct ssam_cdev_client *client, -+ const struct ssam_cdev_event_desc __user *d) -+{ -+ struct ssam_cdev_event_desc desc; -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ long ret; -+ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ /* Read descriptor from user-space. */ -+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ /* Translate descriptor. */ -+ reg.target_category = desc.reg.target_category; -+ reg.target_id = desc.reg.target_id; -+ reg.cid_enable = desc.reg.cid_enable; -+ reg.cid_disable = desc.reg.cid_disable; -+ -+ id.target_category = desc.id.target_category; -+ id.instance = desc.id.instance; -+ -+ /* Disable event. */ -+ return ssam_controller_event_enable(client->cdev->ctrl, reg, id, desc.flags); -+} -+ -+static long ssam_cdev_event_disable(struct ssam_cdev_client *client, -+ const struct ssam_cdev_event_desc __user *d) -+{ -+ struct ssam_cdev_event_desc desc; -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ long ret; -+ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ /* Read descriptor from user-space. */ -+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ /* Translate descriptor. */ -+ reg.target_category = desc.reg.target_category; -+ reg.target_id = desc.reg.target_id; -+ reg.cid_enable = desc.reg.cid_enable; -+ reg.cid_disable = desc.reg.cid_disable; -+ -+ id.target_category = desc.id.target_category; -+ id.instance = desc.id.instance; -+ -+ /* Disable event. */ -+ return ssam_controller_event_disable(client->cdev->ctrl, reg, id, desc.flags); -+} -+ -+ -+/* -- File operations. ------------------------------------------------------ */ -+ -+static int ssam_cdev_device_open(struct inode *inode, struct file *filp) -+{ -+ struct miscdevice *mdev = filp->private_data; -+ struct ssam_cdev_client *client; -+ struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); -+ -+ /* Initialize client */ -+ client = vzalloc(sizeof(*client)); -+ if (!client) -+ return -ENOMEM; -+ -+ client->cdev = ssam_cdev_get(cdev); -+ -+ INIT_LIST_HEAD(&client->node); -+ -+ mutex_init(&client->notifier_lock); -+ -+ mutex_init(&client->read_lock); -+ mutex_init(&client->write_lock); -+ INIT_KFIFO(client->buffer); -+ init_waitqueue_head(&client->waitq); -+ -+ filp->private_data = client; -+ -+ /* Attach client. */ -+ down_write(&cdev->client_lock); -+ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { -+ up_write(&cdev->client_lock); -+ mutex_destroy(&client->write_lock); -+ mutex_destroy(&client->read_lock); -+ mutex_destroy(&client->notifier_lock); -+ ssam_cdev_put(client->cdev); -+ vfree(client); -+ return -ENODEV; -+ } -+ list_add_tail(&client->node, &cdev->client_list); -+ -+ up_write(&cdev->client_lock); -+ -+ stream_open(inode, filp); -+ return 0; -+} -+ -+static int ssam_cdev_device_release(struct inode *inode, struct file *filp) -+{ -+ struct ssam_cdev_client *client = filp->private_data; -+ -+ /* Force-unregister all remaining notifiers of this client. */ -+ ssam_cdev_notifier_unregister_all(client); -+ -+ /* Detach client. */ -+ down_write(&client->cdev->client_lock); -+ list_del(&client->node); -+ up_write(&client->cdev->client_lock); -+ -+ /* Free client. */ -+ mutex_destroy(&client->write_lock); -+ mutex_destroy(&client->read_lock); -+ -+ mutex_destroy(&client->notifier_lock); -+ -+ ssam_cdev_put(client->cdev); -+ vfree(client); -+ -+ return 0; -+} -+ -+static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd, -+ unsigned long arg) -+{ -+ lockdep_assert_held_read(&client->cdev->lock); -+ -+ switch (cmd) { -+ case SSAM_CDEV_REQUEST: -+ return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg); -+ -+ case SSAM_CDEV_NOTIF_REGISTER: -+ return ssam_cdev_notif_register(client, -+ (struct ssam_cdev_notifier_desc __user *)arg); -+ -+ case SSAM_CDEV_NOTIF_UNREGISTER: -+ return ssam_cdev_notif_unregister(client, -+ (struct ssam_cdev_notifier_desc __user *)arg); -+ -+ case SSAM_CDEV_EVENT_ENABLE: -+ return ssam_cdev_event_enable(client, (struct ssam_cdev_event_desc __user *)arg); -+ -+ case SSAM_CDEV_EVENT_DISABLE: -+ return ssam_cdev_event_disable(client, (struct ssam_cdev_event_desc __user *)arg); -+ -+ default: -+ return -ENOTTY; -+ } -+} -+ -+static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -+{ -+ struct ssam_cdev_client *client = file->private_data; -+ long status; -+ -+ /* Ensure that controller is valid for as long as we need it. */ -+ if (down_read_killable(&client->cdev->lock)) -+ return -ERESTARTSYS; -+ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) { -+ up_read(&client->cdev->lock); -+ return -ENODEV; -+ } -+ -+ status = __ssam_cdev_device_ioctl(client, cmd, arg); -+ -+ up_read(&client->cdev->lock); -+ return status; -+} -+ -+static ssize_t ssam_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offs) -+{ -+ struct ssam_cdev_client *client = file->private_data; -+ struct ssam_cdev *cdev = client->cdev; -+ unsigned int copied; -+ int status = 0; -+ -+ if (down_read_killable(&cdev->lock)) -+ return -ERESTARTSYS; -+ -+ /* Make sure we're not shut down. */ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { -+ up_read(&cdev->lock); -+ return -ENODEV; -+ } -+ -+ do { -+ /* Check availability, wait if necessary. */ -+ if (kfifo_is_empty(&client->buffer)) { -+ up_read(&cdev->lock); -+ -+ if (file->f_flags & O_NONBLOCK) -+ return -EAGAIN; -+ -+ status = wait_event_interruptible(client->waitq, -+ !kfifo_is_empty(&client->buffer) || -+ test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, -+ &cdev->flags)); -+ if (status < 0) -+ return status; -+ -+ if (down_read_killable(&cdev->lock)) -+ return -ERESTARTSYS; -+ -+ /* Need to check that we're not shut down again. */ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { -+ up_read(&cdev->lock); -+ return -ENODEV; -+ } -+ } -+ -+ /* Try to read from FIFO. */ -+ if (mutex_lock_interruptible(&client->read_lock)) { -+ up_read(&cdev->lock); -+ return -ERESTARTSYS; -+ } -+ -+ status = kfifo_to_user(&client->buffer, buf, count, &copied); -+ mutex_unlock(&client->read_lock); -+ -+ if (status < 0) { -+ up_read(&cdev->lock); -+ return status; -+ } -+ -+ /* We might not have gotten anything, check this here. */ -+ if (copied == 0 && (file->f_flags & O_NONBLOCK)) { -+ up_read(&cdev->lock); -+ return -EAGAIN; -+ } -+ } while (copied == 0); -+ -+ up_read(&cdev->lock); -+ return copied; -+} -+ -+static __poll_t ssam_cdev_poll(struct file *file, struct poll_table_struct *pt) -+{ -+ struct ssam_cdev_client *client = file->private_data; -+ __poll_t events = 0; -+ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) -+ return EPOLLHUP | EPOLLERR; -+ -+ poll_wait(file, &client->waitq, pt); -+ -+ if (!kfifo_is_empty(&client->buffer)) -+ events |= EPOLLIN | EPOLLRDNORM; -+ -+ return events; -+} -+ -+static int ssam_cdev_fasync(int fd, struct file *file, int on) -+{ -+ struct ssam_cdev_client *client = file->private_data; -+ -+ return fasync_helper(fd, file, on, &client->fasync); -+} -+ -+static const struct file_operations ssam_controller_fops = { -+ .owner = THIS_MODULE, -+ .open = ssam_cdev_device_open, -+ .release = ssam_cdev_device_release, -+ .read = ssam_cdev_read, -+ .poll = ssam_cdev_poll, -+ .fasync = ssam_cdev_fasync, -+ .unlocked_ioctl = ssam_cdev_device_ioctl, -+ .compat_ioctl = ssam_cdev_device_ioctl, -+ .llseek = no_llseek, -+}; -+ -+ -+/* -- Device and driver setup ----------------------------------------------- */ -+ -+static int ssam_dbg_device_probe(struct platform_device *pdev) -+{ -+ struct ssam_controller *ctrl; -+ struct ssam_cdev *cdev; -+ int status; -+ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); -+ if (!cdev) -+ return -ENOMEM; -+ -+ kref_init(&cdev->kref); -+ init_rwsem(&cdev->lock); -+ cdev->ctrl = ctrl; -+ cdev->dev = &pdev->dev; -+ -+ cdev->mdev.parent = &pdev->dev; -+ cdev->mdev.minor = MISC_DYNAMIC_MINOR; -+ cdev->mdev.name = "surface_aggregator"; -+ cdev->mdev.nodename = "surface/aggregator"; -+ cdev->mdev.fops = &ssam_controller_fops; -+ -+ init_rwsem(&cdev->client_lock); -+ INIT_LIST_HEAD(&cdev->client_list); -+ -+ status = misc_register(&cdev->mdev); -+ if (status) { -+ kfree(cdev); -+ return status; -+ } -+ -+ platform_set_drvdata(pdev, cdev); -+ return 0; -+} -+ -+static int ssam_dbg_device_remove(struct platform_device *pdev) -+{ -+ struct ssam_cdev *cdev = platform_get_drvdata(pdev); -+ struct ssam_cdev_client *client; -+ -+ /* -+ * Mark device as shut-down. Prevent new clients from being added and -+ * new operations from being executed. -+ */ -+ set_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags); -+ -+ down_write(&cdev->client_lock); -+ -+ /* Remove all notifiers registered by us. */ -+ list_for_each_entry(client, &cdev->client_list, node) { -+ ssam_cdev_notifier_unregister_all(client); -+ } -+ -+ /* Wake up async clients. */ -+ list_for_each_entry(client, &cdev->client_list, node) { -+ kill_fasync(&client->fasync, SIGIO, POLL_HUP); -+ } -+ -+ /* Wake up blocking clients. */ -+ list_for_each_entry(client, &cdev->client_list, node) { -+ wake_up_interruptible(&client->waitq); -+ } -+ -+ up_write(&cdev->client_lock); -+ -+ /* -+ * The controller is only guaranteed to be valid for as long as the -+ * driver is bound. Remove controller so that any lingering open files -+ * cannot access it any more after we're gone. -+ */ -+ down_write(&cdev->lock); -+ cdev->ctrl = NULL; -+ cdev->dev = NULL; -+ up_write(&cdev->lock); -+ -+ misc_deregister(&cdev->mdev); -+ -+ ssam_cdev_put(cdev); -+ return 0; -+} -+ -+static struct platform_device *ssam_cdev_device; -+ -+static struct platform_driver ssam_cdev_driver = { -+ .probe = ssam_dbg_device_probe, -+ .remove = ssam_dbg_device_remove, -+ .driver = { -+ .name = SSAM_CDEV_DEVICE_NAME, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+static int __init ssam_debug_init(void) -+{ -+ int status; -+ -+ ssam_cdev_device = platform_device_alloc(SSAM_CDEV_DEVICE_NAME, -+ PLATFORM_DEVID_NONE); -+ if (!ssam_cdev_device) -+ return -ENOMEM; -+ -+ status = platform_device_add(ssam_cdev_device); -+ if (status) -+ goto err_device; -+ -+ status = platform_driver_register(&ssam_cdev_driver); -+ if (status) -+ goto err_driver; -+ -+ return 0; -+ -+err_driver: -+ platform_device_del(ssam_cdev_device); -+err_device: -+ platform_device_put(ssam_cdev_device); -+ return status; -+} -+module_init(ssam_debug_init); -+ -+static void __exit ssam_debug_exit(void) -+{ -+ platform_driver_unregister(&ssam_cdev_driver); -+ platform_device_unregister(ssam_cdev_device); -+} -+module_exit(ssam_debug_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("User-space interface for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_aggregator_registry.c b/drivers/platform/x86/surface_aggregator_registry.c -new file mode 100644 -index 000000000000..4428c4330229 ---- /dev/null -+++ b/drivers/platform/x86/surface_aggregator_registry.c -@@ -0,0 +1,606 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) client device registry. -+ * -+ * Registry for non-platform/non-ACPI SSAM client devices, i.e. devices that -+ * cannot be auto-detected. Provides device-hubs and performs instantiation -+ * for these devices. -+ * -+ * Copyright (C) 2020-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+ -+/* -- Device registry. ------------------------------------------------------ */ -+ -+/* -+ * SSAM device names follow the SSAM module alias, meaning they are prefixed -+ * with 'ssam:', followed by domain, category, target ID, instance ID, and -+ * function, each encoded as two-digit hexadecimal, separated by ':'. In other -+ * words, it follows the scheme -+ * -+ * ssam:dd:cc:tt:ii:ff -+ * -+ * Where, 'dd', 'cc', 'tt', 'ii', and 'ff' are the two-digit hexadecimal -+ * values mentioned above, respectively. -+ */ -+ -+/* Root node. */ -+static const struct software_node ssam_node_root = { -+ .name = "ssam_platform_hub", -+}; -+ -+/* Base device hub (devices attached to Surface Book 3 base). */ -+static const struct software_node ssam_node_hub_base = { -+ .name = "ssam:00:00:02:00:00", -+ .parent = &ssam_node_root, -+}; -+ -+/* AC adapter. */ -+static const struct software_node ssam_node_bat_ac = { -+ .name = "ssam:01:02:01:01:01", -+ .parent = &ssam_node_root, -+}; -+ -+/* Primary battery. */ -+static const struct software_node ssam_node_bat_main = { -+ .name = "ssam:01:02:01:01:00", -+ .parent = &ssam_node_root, -+}; -+ -+/* Secondary battery (Surface Book 3). */ -+static const struct software_node ssam_node_bat_sb3base = { -+ .name = "ssam:01:02:02:01:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+/* Platform profile / performance-mode device. */ -+static const struct software_node ssam_node_tmp_pprof = { -+ .name = "ssam:01:03:01:00:01", -+ .parent = &ssam_node_root, -+}; -+ -+/* DTX / detachment-system device (Surface Book 3). */ -+static const struct software_node ssam_node_bas_dtx = { -+ .name = "ssam:01:11:01:00:00", -+ .parent = &ssam_node_root, -+}; -+ -+/* HID keyboard. */ -+static const struct software_node ssam_node_hid_main_keyboard = { -+ .name = "ssam:01:15:02:01:00", -+ .parent = &ssam_node_root, -+}; -+ -+/* HID touchpad. */ -+static const struct software_node ssam_node_hid_main_touchpad = { -+ .name = "ssam:01:15:02:03:00", -+ .parent = &ssam_node_root, -+}; -+ -+/* HID device instance 5 (unknown HID device). */ -+static const struct software_node ssam_node_hid_main_iid5 = { -+ .name = "ssam:01:15:02:05:00", -+ .parent = &ssam_node_root, -+}; -+ -+/* HID keyboard (base hub). */ -+static const struct software_node ssam_node_hid_base_keyboard = { -+ .name = "ssam:01:15:02:01:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+/* HID touchpad (base hub). */ -+static const struct software_node ssam_node_hid_base_touchpad = { -+ .name = "ssam:01:15:02:03:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+/* HID device instance 5 (unknown HID device, base hub). */ -+static const struct software_node ssam_node_hid_base_iid5 = { -+ .name = "ssam:01:15:02:05:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+/* HID device instance 6 (unknown HID device, base hub). */ -+static const struct software_node ssam_node_hid_base_iid6 = { -+ .name = "ssam:01:15:02:06:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+/* -+ * Devices for 5th- and 6th-generations models: -+ * - Surface Book 2, -+ * - Surface Laptop 1 and 2, -+ * - Surface Pro 5 and 6. -+ */ -+static const struct software_node *ssam_node_group_gen5[] = { -+ &ssam_node_root, -+ &ssam_node_tmp_pprof, -+ NULL, -+}; -+ -+/* Devices for Surface Book 3. */ -+static const struct software_node *ssam_node_group_sb3[] = { -+ &ssam_node_root, -+ &ssam_node_hub_base, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, -+ &ssam_node_bat_sb3base, -+ &ssam_node_tmp_pprof, -+ &ssam_node_bas_dtx, -+ &ssam_node_hid_base_keyboard, -+ &ssam_node_hid_base_touchpad, -+ &ssam_node_hid_base_iid5, -+ &ssam_node_hid_base_iid6, -+ NULL, -+}; -+ -+/* Devices for Surface Laptop 3 and 4. */ -+static const struct software_node *ssam_node_group_sl3[] = { -+ &ssam_node_root, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, -+ &ssam_node_tmp_pprof, -+ &ssam_node_hid_main_keyboard, -+ &ssam_node_hid_main_touchpad, -+ &ssam_node_hid_main_iid5, -+ NULL, -+}; -+ -+/* Devices for Surface Laptop Go. */ -+static const struct software_node *ssam_node_group_slg1[] = { -+ &ssam_node_root, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, -+ &ssam_node_tmp_pprof, -+ NULL, -+}; -+ -+/* Devices for Surface Pro 7 and Surface Pro 7+. */ -+static const struct software_node *ssam_node_group_sp7[] = { -+ &ssam_node_root, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, -+ &ssam_node_tmp_pprof, -+ NULL, -+}; -+ -+ -+/* -- Device registry helper functions. ------------------------------------- */ -+ -+static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) -+{ -+ u8 d, tc, tid, iid, fn; -+ int n; -+ -+ n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); -+ if (n != 5) -+ return -EINVAL; -+ -+ uid->domain = d; -+ uid->category = tc; -+ uid->target = tid; -+ uid->instance = iid; -+ uid->function = fn; -+ -+ return 0; -+} -+ -+static int ssam_hub_remove_devices_fn(struct device *dev, void *data) -+{ -+ if (!is_ssam_device(dev)) -+ return 0; -+ -+ ssam_device_remove(to_ssam_device(dev)); -+ return 0; -+} -+ -+static void ssam_hub_remove_devices(struct device *parent) -+{ -+ device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn); -+} -+ -+static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ struct ssam_device_uid uid; -+ struct ssam_device *sdev; -+ int status; -+ -+ status = ssam_uid_from_string(fwnode_get_name(node), &uid); -+ if (status) -+ return status; -+ -+ sdev = ssam_device_alloc(ctrl, uid); -+ if (!sdev) -+ return -ENOMEM; -+ -+ sdev->dev.parent = parent; -+ sdev->dev.fwnode = node; -+ -+ status = ssam_device_add(sdev); -+ if (status) -+ ssam_device_put(sdev); -+ -+ return status; -+} -+ -+static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ struct fwnode_handle *child; -+ int status; -+ -+ fwnode_for_each_child_node(node, child) { -+ /* -+ * Try to add the device specified in the firmware node. If -+ * this fails with -EINVAL, the node does not specify any SSAM -+ * device, so ignore it and continue with the next one. -+ */ -+ -+ status = ssam_hub_add_device(parent, ctrl, child); -+ if (status && status != -EINVAL) -+ goto err; -+ } -+ -+ return 0; -+err: -+ ssam_hub_remove_devices(parent); -+ return status; -+} -+ -+ -+/* -- SSAM base-hub driver. ------------------------------------------------- */ -+ -+/* -+ * Some devices (especially battery) may need a bit of time to be fully usable -+ * after being (re-)connected. This delay has been determined via -+ * experimentation. -+ */ -+#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) -+ -+enum ssam_base_hub_state { -+ SSAM_BASE_HUB_UNINITIALIZED, -+ SSAM_BASE_HUB_CONNECTED, -+ SSAM_BASE_HUB_DISCONNECTED, -+}; -+ -+struct ssam_base_hub { -+ struct ssam_device *sdev; -+ -+ enum ssam_base_hub_state state; -+ struct delayed_work update_work; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0d, -+ .instance_id = 0x00, -+}); -+ -+#define SSAM_BAS_OPMODE_TABLET 0x00 -+#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c -+ -+static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state) -+{ -+ u8 opmode; -+ int status; -+ -+ status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); -+ return status; -+ } -+ -+ if (opmode != SSAM_BAS_OPMODE_TABLET) -+ *state = SSAM_BASE_HUB_CONNECTED; -+ else -+ *state = SSAM_BASE_HUB_DISCONNECTED; -+ -+ return 0; -+} -+ -+static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr, -+ char *buf) -+{ -+ struct ssam_base_hub *hub = dev_get_drvdata(dev); -+ bool connected = hub->state == SSAM_BASE_HUB_CONNECTED; -+ -+ return sysfs_emit(buf, "%d\n", connected); -+} -+ -+static struct device_attribute ssam_base_hub_attr_state = -+ __ATTR(state, 0444, ssam_base_hub_state_show, NULL); -+ -+static struct attribute *ssam_base_hub_attrs[] = { -+ &ssam_base_hub_attr_state.attr, -+ NULL, -+}; -+ -+static const struct attribute_group ssam_base_hub_group = { -+ .attrs = ssam_base_hub_attrs, -+}; -+ -+static void ssam_base_hub_update_workfn(struct work_struct *work) -+{ -+ struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work); -+ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -+ enum ssam_base_hub_state state; -+ int status = 0; -+ -+ status = ssam_base_hub_query_state(hub, &state); -+ if (status) -+ return; -+ -+ if (hub->state == state) -+ return; -+ hub->state = state; -+ -+ if (hub->state == SSAM_BASE_HUB_CONNECTED) -+ status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); -+ else -+ ssam_hub_remove_devices(&hub->sdev->dev); -+ -+ if (status) -+ dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); -+} -+ -+static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif); -+ unsigned long delay; -+ -+ if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) -+ return 0; -+ -+ if (event->length < 1) { -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ /* -+ * Delay update when the base is being connected to give devices/EC -+ * some time to set up. -+ */ -+ delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0; -+ -+ schedule_delayed_work(&hub->update_work, delay); -+ -+ /* -+ * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -+ * consumed by the detachment system driver. We're just a (more or less) -+ * silent observer. -+ */ -+ return 0; -+} -+ -+static int __maybe_unused ssam_base_hub_resume(struct device *dev) -+{ -+ struct ssam_base_hub *hub = dev_get_drvdata(dev); -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); -+ -+static int ssam_base_hub_probe(struct ssam_device *sdev) -+{ -+ struct ssam_base_hub *hub; -+ int status; -+ -+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -+ if (!hub) -+ return -ENOMEM; -+ -+ hub->sdev = sdev; -+ hub->state = SSAM_BASE_HUB_UNINITIALIZED; -+ -+ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -+ hub->notif.base.fn = ssam_base_hub_notif; -+ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ hub->notif.event.id.target_category = SSAM_SSH_TC_BAS, -+ hub->notif.event.id.instance = 0, -+ hub->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, hub); -+ -+ status = ssam_notifier_register(sdev->ctrl, &hub->notif); -+ if (status) -+ return status; -+ -+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); -+ if (status) -+ goto err; -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+ -+err: -+ ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); -+ ssam_hub_remove_devices(&sdev->dev); -+ return status; -+} -+ -+static void ssam_base_hub_remove(struct ssam_device *sdev) -+{ -+ struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); -+ -+ sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); -+ -+ ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); -+ ssam_hub_remove_devices(&sdev->dev); -+} -+ -+static const struct ssam_device_id ssam_base_hub_match[] = { -+ { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) }, -+ { }, -+}; -+ -+static struct ssam_device_driver ssam_base_hub_driver = { -+ .probe = ssam_base_hub_probe, -+ .remove = ssam_base_hub_remove, -+ .match_table = ssam_base_hub_match, -+ .driver = { -+ .name = "surface_aggregator_base_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_base_hub_pm_ops, -+ }, -+}; -+ -+ -+/* -- SSAM platform/meta-hub driver. ---------------------------------------- */ -+ -+static const struct acpi_device_id ssam_platform_hub_match[] = { -+ /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ -+ { "MSHW0081", (unsigned long)ssam_node_group_gen5 }, -+ -+ /* Surface Pro 6 (OMBR >= 0x10) */ -+ { "MSHW0111", (unsigned long)ssam_node_group_gen5 }, -+ -+ /* Surface Pro 7 */ -+ { "MSHW0116", (unsigned long)ssam_node_group_sp7 }, -+ -+ /* Surface Pro 7+ */ -+ { "MSHW0119", (unsigned long)ssam_node_group_sp7 }, -+ -+ /* Surface Book 2 */ -+ { "MSHW0107", (unsigned long)ssam_node_group_gen5 }, -+ -+ /* Surface Book 3 */ -+ { "MSHW0117", (unsigned long)ssam_node_group_sb3 }, -+ -+ /* Surface Laptop 1 */ -+ { "MSHW0086", (unsigned long)ssam_node_group_gen5 }, -+ -+ /* Surface Laptop 2 */ -+ { "MSHW0112", (unsigned long)ssam_node_group_gen5 }, -+ -+ /* Surface Laptop 3 (13", Intel) */ -+ { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, -+ -+ /* Surface Laptop 3 (15", AMD) and 4 (15", AMD) */ -+ { "MSHW0110", (unsigned long)ssam_node_group_sl3 }, -+ -+ /* Surface Laptop 4 (13", Intel) */ -+ { "MSHW0250", (unsigned long)ssam_node_group_sl3 }, -+ -+ /* Surface Laptop Go 1 */ -+ { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, -+ -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match); -+ -+static int ssam_platform_hub_probe(struct platform_device *pdev) -+{ -+ const struct software_node **nodes; -+ struct ssam_controller *ctrl; -+ struct fwnode_handle *root; -+ int status; -+ -+ nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev); -+ if (!nodes) -+ return -ENODEV; -+ -+ /* -+ * As we're adding the SSAM client devices as children under this device -+ * and not the SSAM controller, we need to add a device link to the -+ * controller to ensure that we remove all of our devices before the -+ * controller is removed. This also guarantees proper ordering for -+ * suspend/resume of the devices on this hub. -+ */ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ status = software_node_register_node_group(nodes); -+ if (status) -+ return status; -+ -+ root = software_node_fwnode(&ssam_node_root); -+ if (!root) { -+ software_node_unregister_node_group(nodes); -+ return -ENOENT; -+ } -+ -+ set_secondary_fwnode(&pdev->dev, root); -+ -+ status = ssam_hub_add_devices(&pdev->dev, ctrl, root); -+ if (status) { -+ set_secondary_fwnode(&pdev->dev, NULL); -+ software_node_unregister_node_group(nodes); -+ } -+ -+ platform_set_drvdata(pdev, nodes); -+ return status; -+} -+ -+static int ssam_platform_hub_remove(struct platform_device *pdev) -+{ -+ const struct software_node **nodes = platform_get_drvdata(pdev); -+ -+ ssam_hub_remove_devices(&pdev->dev); -+ set_secondary_fwnode(&pdev->dev, NULL); -+ software_node_unregister_node_group(nodes); -+ return 0; -+} -+ -+static struct platform_driver ssam_platform_hub_driver = { -+ .probe = ssam_platform_hub_probe, -+ .remove = ssam_platform_hub_remove, -+ .driver = { -+ .name = "surface_aggregator_platform_hub", -+ .acpi_match_table = ssam_platform_hub_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+/* -- Module initialization. ------------------------------------------------ */ -+ -+static int __init ssam_device_hub_init(void) -+{ -+ int status; -+ -+ status = platform_driver_register(&ssam_platform_hub_driver); -+ if (status) -+ return status; -+ -+ status = ssam_device_driver_register(&ssam_base_hub_driver); -+ if (status) -+ platform_driver_unregister(&ssam_platform_hub_driver); -+ -+ return status; -+} -+module_init(ssam_device_hub_init); -+ -+static void __exit ssam_device_hub_exit(void) -+{ -+ ssam_device_driver_unregister(&ssam_base_hub_driver); -+ platform_driver_unregister(&ssam_platform_hub_driver); -+} -+module_exit(ssam_device_hub_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_dtx.c b/drivers/platform/x86/surface_dtx.c -new file mode 100644 -index 000000000000..f486fabf9319 ---- /dev/null -+++ b/drivers/platform/x86/surface_dtx.c -@@ -0,0 +1,1283 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface Book (gen. 2 and later) detachment system (DTX) driver. -+ * -+ * Provides a user-space interface to properly handle clipboard/tablet -+ * (containing screen and processor) detachment from the base of the device -+ * (containing the keyboard and optionally a discrete GPU). Allows to -+ * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in -+ * use), or request detachment via user-space. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+ -+/* -- SSAM interface. ------------------------------------------------------- */ -+ -+enum sam_event_cid_bas { -+ SAM_EVENT_CID_DTX_CONNECTION = 0x0c, -+ SAM_EVENT_CID_DTX_REQUEST = 0x0e, -+ SAM_EVENT_CID_DTX_CANCEL = 0x0f, -+ SAM_EVENT_CID_DTX_LATCH_STATUS = 0x11, -+}; -+ -+enum ssam_bas_base_state { -+ SSAM_BAS_BASE_STATE_DETACH_SUCCESS = 0x00, -+ SSAM_BAS_BASE_STATE_ATTACHED = 0x01, -+ SSAM_BAS_BASE_STATE_NOT_FEASIBLE = 0x02, -+}; -+ -+enum ssam_bas_latch_status { -+ SSAM_BAS_LATCH_STATUS_CLOSED = 0x00, -+ SSAM_BAS_LATCH_STATUS_OPENED = 0x01, -+ SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN = 0x02, -+ SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN = 0x03, -+ SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE = 0x04, -+}; -+ -+enum ssam_bas_cancel_reason { -+ SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE = 0x00, /* Low battery. */ -+ SSAM_BAS_CANCEL_REASON_TIMEOUT = 0x02, -+ SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN = 0x03, -+ SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN = 0x04, -+ SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE = 0x05, -+}; -+ -+struct ssam_bas_base_info { -+ u8 state; -+ u8 base_id; -+} __packed; -+ -+static_assert(sizeof(struct ssam_bas_base_info) == 2); -+ -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x06, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x07, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x08, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x09, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0a, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0b, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0c, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0d, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x11, -+ .instance_id = 0x00, -+}); -+ -+ -+/* -- Main structures. ------------------------------------------------------ */ -+ -+enum sdtx_device_state { -+ SDTX_DEVICE_SHUTDOWN_BIT = BIT(0), -+ SDTX_DEVICE_DIRTY_BASE_BIT = BIT(1), -+ SDTX_DEVICE_DIRTY_MODE_BIT = BIT(2), -+ SDTX_DEVICE_DIRTY_LATCH_BIT = BIT(3), -+}; -+ -+struct sdtx_device { -+ struct kref kref; -+ struct rw_semaphore lock; /* Guards device and controller reference. */ -+ -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ unsigned long flags; -+ -+ struct miscdevice mdev; -+ wait_queue_head_t waitq; -+ struct mutex write_lock; /* Guards order of events/notifications. */ -+ struct rw_semaphore client_lock; /* Guards client list. */ -+ struct list_head client_list; -+ -+ struct delayed_work state_work; -+ struct { -+ struct ssam_bas_base_info base; -+ u8 device_mode; -+ u8 latch_status; -+ } state; -+ -+ struct delayed_work mode_work; -+ struct input_dev *mode_switch; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+enum sdtx_client_state { -+ SDTX_CLIENT_EVENTS_ENABLED_BIT = BIT(0), -+}; -+ -+struct sdtx_client { -+ struct sdtx_device *ddev; -+ struct list_head node; -+ unsigned long flags; -+ -+ struct fasync_struct *fasync; -+ -+ struct mutex read_lock; /* Guards FIFO buffer read access. */ -+ DECLARE_KFIFO(buffer, u8, 512); -+}; -+ -+static void __sdtx_device_release(struct kref *kref) -+{ -+ struct sdtx_device *ddev = container_of(kref, struct sdtx_device, kref); -+ -+ mutex_destroy(&ddev->write_lock); -+ kfree(ddev); -+} -+ -+static struct sdtx_device *sdtx_device_get(struct sdtx_device *ddev) -+{ -+ if (ddev) -+ kref_get(&ddev->kref); -+ -+ return ddev; -+} -+ -+static void sdtx_device_put(struct sdtx_device *ddev) -+{ -+ if (ddev) -+ kref_put(&ddev->kref, __sdtx_device_release); -+} -+ -+ -+/* -- Firmware value translations. ------------------------------------------ */ -+ -+static u16 sdtx_translate_base_state(struct sdtx_device *ddev, u8 state) -+{ -+ switch (state) { -+ case SSAM_BAS_BASE_STATE_ATTACHED: -+ return SDTX_BASE_ATTACHED; -+ -+ case SSAM_BAS_BASE_STATE_DETACH_SUCCESS: -+ return SDTX_BASE_DETACHED; -+ -+ case SSAM_BAS_BASE_STATE_NOT_FEASIBLE: -+ return SDTX_DETACH_NOT_FEASIBLE; -+ -+ default: -+ dev_err(ddev->dev, "unknown base state: %#04x\n", state); -+ return SDTX_UNKNOWN(state); -+ } -+} -+ -+static u16 sdtx_translate_latch_status(struct sdtx_device *ddev, u8 status) -+{ -+ switch (status) { -+ case SSAM_BAS_LATCH_STATUS_CLOSED: -+ return SDTX_LATCH_CLOSED; -+ -+ case SSAM_BAS_LATCH_STATUS_OPENED: -+ return SDTX_LATCH_OPENED; -+ -+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN: -+ return SDTX_ERR_FAILED_TO_OPEN; -+ -+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN: -+ return SDTX_ERR_FAILED_TO_REMAIN_OPEN; -+ -+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE: -+ return SDTX_ERR_FAILED_TO_CLOSE; -+ -+ default: -+ dev_err(ddev->dev, "unknown latch status: %#04x\n", status); -+ return SDTX_UNKNOWN(status); -+ } -+} -+ -+static u16 sdtx_translate_cancel_reason(struct sdtx_device *ddev, u8 reason) -+{ -+ switch (reason) { -+ case SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE: -+ return SDTX_DETACH_NOT_FEASIBLE; -+ -+ case SSAM_BAS_CANCEL_REASON_TIMEOUT: -+ return SDTX_DETACH_TIMEDOUT; -+ -+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN: -+ return SDTX_ERR_FAILED_TO_OPEN; -+ -+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN: -+ return SDTX_ERR_FAILED_TO_REMAIN_OPEN; -+ -+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE: -+ return SDTX_ERR_FAILED_TO_CLOSE; -+ -+ default: -+ dev_err(ddev->dev, "unknown cancel reason: %#04x\n", reason); -+ return SDTX_UNKNOWN(reason); -+ } -+} -+ -+ -+/* -- IOCTLs. --------------------------------------------------------------- */ -+ -+static int sdtx_ioctl_get_base_info(struct sdtx_device *ddev, -+ struct sdtx_base_info __user *buf) -+{ -+ struct ssam_bas_base_info raw; -+ struct sdtx_base_info info; -+ int status; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &raw); -+ if (status < 0) -+ return status; -+ -+ info.state = sdtx_translate_base_state(ddev, raw.state); -+ info.base_id = SDTX_BASE_TYPE_SSH(raw.base_id); -+ -+ if (copy_to_user(buf, &info, sizeof(info))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static int sdtx_ioctl_get_device_mode(struct sdtx_device *ddev, u16 __user *buf) -+{ -+ u8 mode; -+ int status; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); -+ if (status < 0) -+ return status; -+ -+ return put_user(mode, buf); -+} -+ -+static int sdtx_ioctl_get_latch_status(struct sdtx_device *ddev, u16 __user *buf) -+{ -+ u8 latch; -+ int status; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch); -+ if (status < 0) -+ return status; -+ -+ return put_user(sdtx_translate_latch_status(ddev, latch), buf); -+} -+ -+static long __surface_dtx_ioctl(struct sdtx_client *client, unsigned int cmd, unsigned long arg) -+{ -+ struct sdtx_device *ddev = client->ddev; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ switch (cmd) { -+ case SDTX_IOCTL_EVENTS_ENABLE: -+ set_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags); -+ return 0; -+ -+ case SDTX_IOCTL_EVENTS_DISABLE: -+ clear_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags); -+ return 0; -+ -+ case SDTX_IOCTL_LATCH_LOCK: -+ return ssam_retry(ssam_bas_latch_lock, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_UNLOCK: -+ return ssam_retry(ssam_bas_latch_unlock, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_REQUEST: -+ return ssam_retry(ssam_bas_latch_request, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_CONFIRM: -+ return ssam_retry(ssam_bas_latch_confirm, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_HEARTBEAT: -+ return ssam_retry(ssam_bas_latch_heartbeat, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_CANCEL: -+ return ssam_retry(ssam_bas_latch_cancel, ddev->ctrl); -+ -+ case SDTX_IOCTL_GET_BASE_INFO: -+ return sdtx_ioctl_get_base_info(ddev, (struct sdtx_base_info __user *)arg); -+ -+ case SDTX_IOCTL_GET_DEVICE_MODE: -+ return sdtx_ioctl_get_device_mode(ddev, (u16 __user *)arg); -+ -+ case SDTX_IOCTL_GET_LATCH_STATUS: -+ return sdtx_ioctl_get_latch_status(ddev, (u16 __user *)arg); -+ -+ default: -+ return -EINVAL; -+ } -+} -+ -+static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -+{ -+ struct sdtx_client *client = file->private_data; -+ long status; -+ -+ if (down_read_killable(&client->ddev->lock)) -+ return -ERESTARTSYS; -+ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) { -+ up_read(&client->ddev->lock); -+ return -ENODEV; -+ } -+ -+ status = __surface_dtx_ioctl(client, cmd, arg); -+ -+ up_read(&client->ddev->lock); -+ return status; -+} -+ -+ -+/* -- File operations. ------------------------------------------------------ */ -+ -+static int surface_dtx_open(struct inode *inode, struct file *file) -+{ -+ struct sdtx_device *ddev = container_of(file->private_data, struct sdtx_device, mdev); -+ struct sdtx_client *client; -+ -+ /* Initialize client. */ -+ client = kzalloc(sizeof(*client), GFP_KERNEL); -+ if (!client) -+ return -ENOMEM; -+ -+ client->ddev = sdtx_device_get(ddev); -+ -+ INIT_LIST_HEAD(&client->node); -+ -+ mutex_init(&client->read_lock); -+ INIT_KFIFO(client->buffer); -+ -+ file->private_data = client; -+ -+ /* Attach client. */ -+ down_write(&ddev->client_lock); -+ -+ /* -+ * Do not add a new client if the device has been shut down. Note that -+ * it's enough to hold the client_lock here as, during shutdown, we -+ * only acquire that lock and remove clients after marking the device -+ * as shut down. -+ */ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { -+ up_write(&ddev->client_lock); -+ sdtx_device_put(client->ddev); -+ kfree(client); -+ return -ENODEV; -+ } -+ -+ list_add_tail(&client->node, &ddev->client_list); -+ up_write(&ddev->client_lock); -+ -+ stream_open(inode, file); -+ return 0; -+} -+ -+static int surface_dtx_release(struct inode *inode, struct file *file) -+{ -+ struct sdtx_client *client = file->private_data; -+ -+ /* Detach client. */ -+ down_write(&client->ddev->client_lock); -+ list_del(&client->node); -+ up_write(&client->ddev->client_lock); -+ -+ /* Free client. */ -+ sdtx_device_put(client->ddev); -+ mutex_destroy(&client->read_lock); -+ kfree(client); -+ -+ return 0; -+} -+ -+static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs) -+{ -+ struct sdtx_client *client = file->private_data; -+ struct sdtx_device *ddev = client->ddev; -+ unsigned int copied; -+ int status = 0; -+ -+ if (down_read_killable(&ddev->lock)) -+ return -ERESTARTSYS; -+ -+ /* Make sure we're not shut down. */ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { -+ up_read(&ddev->lock); -+ return -ENODEV; -+ } -+ -+ do { -+ /* Check availability, wait if necessary. */ -+ if (kfifo_is_empty(&client->buffer)) { -+ up_read(&ddev->lock); -+ -+ if (file->f_flags & O_NONBLOCK) -+ return -EAGAIN; -+ -+ status = wait_event_interruptible(ddev->waitq, -+ !kfifo_is_empty(&client->buffer) || -+ test_bit(SDTX_DEVICE_SHUTDOWN_BIT, -+ &ddev->flags)); -+ if (status < 0) -+ return status; -+ -+ if (down_read_killable(&client->ddev->lock)) -+ return -ERESTARTSYS; -+ -+ /* Need to check that we're not shut down again. */ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { -+ up_read(&ddev->lock); -+ return -ENODEV; -+ } -+ } -+ -+ /* Try to read from FIFO. */ -+ if (mutex_lock_interruptible(&client->read_lock)) { -+ up_read(&ddev->lock); -+ return -ERESTARTSYS; -+ } -+ -+ status = kfifo_to_user(&client->buffer, buf, count, &copied); -+ mutex_unlock(&client->read_lock); -+ -+ if (status < 0) { -+ up_read(&ddev->lock); -+ return status; -+ } -+ -+ /* We might not have gotten anything, check this here. */ -+ if (copied == 0 && (file->f_flags & O_NONBLOCK)) { -+ up_read(&ddev->lock); -+ return -EAGAIN; -+ } -+ } while (copied == 0); -+ -+ up_read(&ddev->lock); -+ return copied; -+} -+ -+static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt) -+{ -+ struct sdtx_client *client = file->private_data; -+ __poll_t events = 0; -+ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) -+ return EPOLLHUP | EPOLLERR; -+ -+ poll_wait(file, &client->ddev->waitq, pt); -+ -+ if (!kfifo_is_empty(&client->buffer)) -+ events |= EPOLLIN | EPOLLRDNORM; -+ -+ return events; -+} -+ -+static int surface_dtx_fasync(int fd, struct file *file, int on) -+{ -+ struct sdtx_client *client = file->private_data; -+ -+ return fasync_helper(fd, file, on, &client->fasync); -+} -+ -+static const struct file_operations surface_dtx_fops = { -+ .owner = THIS_MODULE, -+ .open = surface_dtx_open, -+ .release = surface_dtx_release, -+ .read = surface_dtx_read, -+ .poll = surface_dtx_poll, -+ .fasync = surface_dtx_fasync, -+ .unlocked_ioctl = surface_dtx_ioctl, -+ .compat_ioctl = surface_dtx_ioctl, -+ .llseek = no_llseek, -+}; -+ -+ -+/* -- Event handling/forwarding. -------------------------------------------- */ -+ -+/* -+ * The device operation mode is not immediately updated on the EC when the -+ * base has been connected, i.e. querying the device mode inside the -+ * connection event callback yields an outdated value. Thus, we can only -+ * determine the new tablet-mode switch and device mode values after some -+ * time. -+ * -+ * These delays have been chosen by experimenting. We first delay on connect -+ * events, then check and validate the device mode against the base state and -+ * if invalid delay again by the "recheck" delay. -+ */ -+#define SDTX_DEVICE_MODE_DELAY_CONNECT msecs_to_jiffies(100) -+#define SDTX_DEVICE_MODE_DELAY_RECHECK msecs_to_jiffies(100) -+ -+struct sdtx_status_event { -+ struct sdtx_event e; -+ __u16 v; -+} __packed; -+ -+struct sdtx_base_info_event { -+ struct sdtx_event e; -+ struct sdtx_base_info v; -+} __packed; -+ -+union sdtx_generic_event { -+ struct sdtx_event common; -+ struct sdtx_status_event status; -+ struct sdtx_base_info_event base; -+}; -+ -+static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay); -+ -+/* Must be executed with ddev->write_lock held. */ -+static void sdtx_push_event(struct sdtx_device *ddev, struct sdtx_event *evt) -+{ -+ const size_t len = sizeof(struct sdtx_event) + evt->length; -+ struct sdtx_client *client; -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ down_read(&ddev->client_lock); -+ list_for_each_entry(client, &ddev->client_list, node) { -+ if (!test_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags)) -+ continue; -+ -+ if (likely(kfifo_avail(&client->buffer) >= len)) -+ kfifo_in(&client->buffer, (const u8 *)evt, len); -+ else -+ dev_warn(ddev->dev, "event buffer overrun\n"); -+ -+ kill_fasync(&client->fasync, SIGIO, POLL_IN); -+ } -+ up_read(&ddev->client_lock); -+ -+ wake_up_interruptible(&ddev->waitq); -+} -+ -+static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in) -+{ -+ struct sdtx_device *ddev = container_of(nf, struct sdtx_device, notif); -+ union sdtx_generic_event event; -+ size_t len; -+ -+ /* Validate event payload length. */ -+ switch (in->command_id) { -+ case SAM_EVENT_CID_DTX_CONNECTION: -+ len = 2 * sizeof(u8); -+ break; -+ -+ case SAM_EVENT_CID_DTX_REQUEST: -+ len = 0; -+ break; -+ -+ case SAM_EVENT_CID_DTX_CANCEL: -+ len = sizeof(u8); -+ break; -+ -+ case SAM_EVENT_CID_DTX_LATCH_STATUS: -+ len = sizeof(u8); -+ break; -+ -+ default: -+ return 0; -+ } -+ -+ if (in->length != len) { -+ dev_err(ddev->dev, -+ "unexpected payload size for event %#04x: got %u, expected %zu\n", -+ in->command_id, in->length, len); -+ return 0; -+ } -+ -+ mutex_lock(&ddev->write_lock); -+ -+ /* Translate event. */ -+ switch (in->command_id) { -+ case SAM_EVENT_CID_DTX_CONNECTION: -+ clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags); -+ -+ /* If state has not changed: do not send new event. */ -+ if (ddev->state.base.state == in->data[0] && -+ ddev->state.base.base_id == in->data[1]) -+ goto out; -+ -+ ddev->state.base.state = in->data[0]; -+ ddev->state.base.base_id = in->data[1]; -+ -+ event.base.e.length = sizeof(struct sdtx_base_info); -+ event.base.e.code = SDTX_EVENT_BASE_CONNECTION; -+ event.base.v.state = sdtx_translate_base_state(ddev, in->data[0]); -+ event.base.v.base_id = SDTX_BASE_TYPE_SSH(in->data[1]); -+ break; -+ -+ case SAM_EVENT_CID_DTX_REQUEST: -+ event.common.code = SDTX_EVENT_REQUEST; -+ event.common.length = 0; -+ break; -+ -+ case SAM_EVENT_CID_DTX_CANCEL: -+ event.status.e.length = sizeof(u16); -+ event.status.e.code = SDTX_EVENT_CANCEL; -+ event.status.v = sdtx_translate_cancel_reason(ddev, in->data[0]); -+ break; -+ -+ case SAM_EVENT_CID_DTX_LATCH_STATUS: -+ clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags); -+ -+ /* If state has not changed: do not send new event. */ -+ if (ddev->state.latch_status == in->data[0]) -+ goto out; -+ -+ ddev->state.latch_status = in->data[0]; -+ -+ event.status.e.length = sizeof(u16); -+ event.status.e.code = SDTX_EVENT_LATCH_STATUS; -+ event.status.v = sdtx_translate_latch_status(ddev, in->data[0]); -+ break; -+ } -+ -+ sdtx_push_event(ddev, &event.common); -+ -+ /* Update device mode on base connection change. */ -+ if (in->command_id == SAM_EVENT_CID_DTX_CONNECTION) { -+ unsigned long delay; -+ -+ delay = in->data[0] ? SDTX_DEVICE_MODE_DELAY_CONNECT : 0; -+ sdtx_update_device_mode(ddev, delay); -+ } -+ -+out: -+ mutex_unlock(&ddev->write_lock); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+ -+/* -- State update functions. ----------------------------------------------- */ -+ -+static bool sdtx_device_mode_invalid(u8 mode, u8 base_state) -+{ -+ return ((base_state == SSAM_BAS_BASE_STATE_ATTACHED) && -+ (mode == SDTX_DEVICE_MODE_TABLET)) || -+ ((base_state == SSAM_BAS_BASE_STATE_DETACH_SUCCESS) && -+ (mode != SDTX_DEVICE_MODE_TABLET)); -+} -+ -+static void sdtx_device_mode_workfn(struct work_struct *work) -+{ -+ struct sdtx_device *ddev = container_of(work, struct sdtx_device, mode_work.work); -+ struct sdtx_status_event event; -+ struct ssam_bas_base_info base; -+ int status, tablet; -+ u8 mode; -+ -+ /* Get operation mode. */ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); -+ if (status) { -+ dev_err(ddev->dev, "failed to get device mode: %d\n", status); -+ return; -+ } -+ -+ /* Get base info. */ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base); -+ if (status) { -+ dev_err(ddev->dev, "failed to get base info: %d\n", status); -+ return; -+ } -+ -+ /* -+ * In some cases (specifically when attaching the base), the device -+ * mode isn't updated right away. Thus we check if the device mode -+ * makes sense for the given base state and try again later if it -+ * doesn't. -+ */ -+ if (sdtx_device_mode_invalid(mode, base.state)) { -+ dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); -+ sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); -+ return; -+ } -+ -+ mutex_lock(&ddev->write_lock); -+ clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags); -+ -+ /* Avoid sending duplicate device-mode events. */ -+ if (ddev->state.device_mode == mode) { -+ mutex_unlock(&ddev->write_lock); -+ return; -+ } -+ -+ ddev->state.device_mode = mode; -+ -+ event.e.length = sizeof(u16); -+ event.e.code = SDTX_EVENT_DEVICE_MODE; -+ event.v = mode; -+ -+ sdtx_push_event(ddev, &event.e); -+ -+ /* Send SW_TABLET_MODE event. */ -+ tablet = mode != SDTX_DEVICE_MODE_LAPTOP; -+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); -+ input_sync(ddev->mode_switch); -+ -+ mutex_unlock(&ddev->write_lock); -+} -+ -+static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay) -+{ -+ schedule_delayed_work(&ddev->mode_work, delay); -+} -+ -+/* Must be executed with ddev->write_lock held. */ -+static void __sdtx_device_state_update_base(struct sdtx_device *ddev, -+ struct ssam_bas_base_info info) -+{ -+ struct sdtx_base_info_event event; -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ /* Prevent duplicate events. */ -+ if (ddev->state.base.state == info.state && -+ ddev->state.base.base_id == info.base_id) -+ return; -+ -+ ddev->state.base = info; -+ -+ event.e.length = sizeof(struct sdtx_base_info); -+ event.e.code = SDTX_EVENT_BASE_CONNECTION; -+ event.v.state = sdtx_translate_base_state(ddev, info.state); -+ event.v.base_id = SDTX_BASE_TYPE_SSH(info.base_id); -+ -+ sdtx_push_event(ddev, &event.e); -+} -+ -+/* Must be executed with ddev->write_lock held. */ -+static void __sdtx_device_state_update_mode(struct sdtx_device *ddev, u8 mode) -+{ -+ struct sdtx_status_event event; -+ int tablet; -+ -+ /* -+ * Note: This function must be called after updating the base state -+ * via __sdtx_device_state_update_base(), as we rely on the updated -+ * base state value in the validity check below. -+ */ -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ if (sdtx_device_mode_invalid(mode, ddev->state.base.state)) { -+ dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); -+ sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); -+ return; -+ } -+ -+ /* Prevent duplicate events. */ -+ if (ddev->state.device_mode == mode) -+ return; -+ -+ ddev->state.device_mode = mode; -+ -+ /* Send event. */ -+ event.e.length = sizeof(u16); -+ event.e.code = SDTX_EVENT_DEVICE_MODE; -+ event.v = mode; -+ -+ sdtx_push_event(ddev, &event.e); -+ -+ /* Send SW_TABLET_MODE event. */ -+ tablet = mode != SDTX_DEVICE_MODE_LAPTOP; -+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); -+ input_sync(ddev->mode_switch); -+} -+ -+/* Must be executed with ddev->write_lock held. */ -+static void __sdtx_device_state_update_latch(struct sdtx_device *ddev, u8 status) -+{ -+ struct sdtx_status_event event; -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ /* Prevent duplicate events. */ -+ if (ddev->state.latch_status == status) -+ return; -+ -+ ddev->state.latch_status = status; -+ -+ event.e.length = sizeof(struct sdtx_base_info); -+ event.e.code = SDTX_EVENT_BASE_CONNECTION; -+ event.v = sdtx_translate_latch_status(ddev, status); -+ -+ sdtx_push_event(ddev, &event.e); -+} -+ -+static void sdtx_device_state_workfn(struct work_struct *work) -+{ -+ struct sdtx_device *ddev = container_of(work, struct sdtx_device, state_work.work); -+ struct ssam_bas_base_info base; -+ u8 mode, latch; -+ int status; -+ -+ /* Mark everything as dirty. */ -+ set_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags); -+ set_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags); -+ set_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags); -+ -+ /* -+ * Ensure that the state gets marked as dirty before continuing to -+ * query it. Necessary to ensure that clear_bit() calls in -+ * sdtx_notifier() and sdtx_device_mode_workfn() actually clear these -+ * bits if an event is received while updating the state here. -+ */ -+ smp_mb__after_atomic(); -+ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base); -+ if (status) { -+ dev_err(ddev->dev, "failed to get base state: %d\n", status); -+ return; -+ } -+ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); -+ if (status) { -+ dev_err(ddev->dev, "failed to get device mode: %d\n", status); -+ return; -+ } -+ -+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch); -+ if (status) { -+ dev_err(ddev->dev, "failed to get latch status: %d\n", status); -+ return; -+ } -+ -+ mutex_lock(&ddev->write_lock); -+ -+ /* -+ * If the respective dirty-bit has been cleared, an event has been -+ * received, updating this state. The queried state may thus be out of -+ * date. At this point, we can safely assume that the state provided -+ * by the event is either up to date, or we're about to receive -+ * another event updating it. -+ */ -+ -+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags)) -+ __sdtx_device_state_update_base(ddev, base); -+ -+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags)) -+ __sdtx_device_state_update_mode(ddev, mode); -+ -+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags)) -+ __sdtx_device_state_update_latch(ddev, latch); -+ -+ mutex_unlock(&ddev->write_lock); -+} -+ -+static void sdtx_update_device_state(struct sdtx_device *ddev, unsigned long delay) -+{ -+ schedule_delayed_work(&ddev->state_work, delay); -+} -+ -+ -+/* -- Common device initialization. ----------------------------------------- */ -+ -+static int sdtx_device_init(struct sdtx_device *ddev, struct device *dev, -+ struct ssam_controller *ctrl) -+{ -+ int status, tablet_mode; -+ -+ /* Basic initialization. */ -+ kref_init(&ddev->kref); -+ init_rwsem(&ddev->lock); -+ ddev->dev = dev; -+ ddev->ctrl = ctrl; -+ -+ ddev->mdev.minor = MISC_DYNAMIC_MINOR; -+ ddev->mdev.name = "surface_dtx"; -+ ddev->mdev.nodename = "surface/dtx"; -+ ddev->mdev.fops = &surface_dtx_fops; -+ -+ ddev->notif.base.priority = 1; -+ ddev->notif.base.fn = sdtx_notifier; -+ ddev->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ ddev->notif.event.id.target_category = SSAM_SSH_TC_BAS; -+ ddev->notif.event.id.instance = 0; -+ ddev->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ ddev->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ init_waitqueue_head(&ddev->waitq); -+ mutex_init(&ddev->write_lock); -+ init_rwsem(&ddev->client_lock); -+ INIT_LIST_HEAD(&ddev->client_list); -+ -+ INIT_DELAYED_WORK(&ddev->mode_work, sdtx_device_mode_workfn); -+ INIT_DELAYED_WORK(&ddev->state_work, sdtx_device_state_workfn); -+ -+ /* -+ * Get current device state. We want to guarantee that events are only -+ * sent when state actually changes. Thus we cannot use special -+ * "uninitialized" values, as that would cause problems when manually -+ * querying the state in surface_dtx_pm_complete(). I.e. we would not -+ * be able to detect state changes there if no change event has been -+ * received between driver initialization and first device suspension. -+ * -+ * Note that we also need to do this before registering the event -+ * notifier, as that may access the state values. -+ */ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &ddev->state.base); -+ if (status) -+ return status; -+ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &ddev->state.device_mode); -+ if (status) -+ return status; -+ -+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &ddev->state.latch_status); -+ if (status) -+ return status; -+ -+ /* Set up tablet mode switch. */ -+ ddev->mode_switch = input_allocate_device(); -+ if (!ddev->mode_switch) -+ return -ENOMEM; -+ -+ ddev->mode_switch->name = "Microsoft Surface DTX Device Mode Switch"; -+ ddev->mode_switch->phys = "ssam/01:11:01:00:00/input0"; -+ ddev->mode_switch->id.bustype = BUS_HOST; -+ ddev->mode_switch->dev.parent = ddev->dev; -+ -+ tablet_mode = (ddev->state.device_mode != SDTX_DEVICE_MODE_LAPTOP); -+ input_set_capability(ddev->mode_switch, EV_SW, SW_TABLET_MODE); -+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet_mode); -+ -+ status = input_register_device(ddev->mode_switch); -+ if (status) { -+ input_free_device(ddev->mode_switch); -+ return status; -+ } -+ -+ /* Set up event notifier. */ -+ status = ssam_notifier_register(ddev->ctrl, &ddev->notif); -+ if (status) -+ goto err_notif; -+ -+ /* Register miscdevice. */ -+ status = misc_register(&ddev->mdev); -+ if (status) -+ goto err_mdev; -+ -+ /* -+ * Update device state in case it has changed between getting the -+ * initial mode and registering the event notifier. -+ */ -+ sdtx_update_device_state(ddev, 0); -+ return 0; -+ -+err_notif: -+ ssam_notifier_unregister(ddev->ctrl, &ddev->notif); -+ cancel_delayed_work_sync(&ddev->mode_work); -+err_mdev: -+ input_unregister_device(ddev->mode_switch); -+ return status; -+} -+ -+static struct sdtx_device *sdtx_device_create(struct device *dev, struct ssam_controller *ctrl) -+{ -+ struct sdtx_device *ddev; -+ int status; -+ -+ ddev = kzalloc(sizeof(*ddev), GFP_KERNEL); -+ if (!ddev) -+ return ERR_PTR(-ENOMEM); -+ -+ status = sdtx_device_init(ddev, dev, ctrl); -+ if (status) { -+ sdtx_device_put(ddev); -+ return ERR_PTR(status); -+ } -+ -+ return ddev; -+} -+ -+static void sdtx_device_destroy(struct sdtx_device *ddev) -+{ -+ struct sdtx_client *client; -+ -+ /* -+ * Mark device as shut-down. Prevent new clients from being added and -+ * new operations from being executed. -+ */ -+ set_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags); -+ -+ /* Disable notifiers, prevent new events from arriving. */ -+ ssam_notifier_unregister(ddev->ctrl, &ddev->notif); -+ -+ /* Stop mode_work, prevent access to mode_switch. */ -+ cancel_delayed_work_sync(&ddev->mode_work); -+ -+ /* Stop state_work. */ -+ cancel_delayed_work_sync(&ddev->state_work); -+ -+ /* With mode_work canceled, we can unregister the mode_switch. */ -+ input_unregister_device(ddev->mode_switch); -+ -+ /* Wake up async clients. */ -+ down_write(&ddev->client_lock); -+ list_for_each_entry(client, &ddev->client_list, node) { -+ kill_fasync(&client->fasync, SIGIO, POLL_HUP); -+ } -+ up_write(&ddev->client_lock); -+ -+ /* Wake up blocking clients. */ -+ wake_up_interruptible(&ddev->waitq); -+ -+ /* -+ * Wait for clients to finish their current operation. After this, the -+ * controller and device references are guaranteed to be no longer in -+ * use. -+ */ -+ down_write(&ddev->lock); -+ ddev->dev = NULL; -+ ddev->ctrl = NULL; -+ up_write(&ddev->lock); -+ -+ /* Finally remove the misc-device. */ -+ misc_deregister(&ddev->mdev); -+ -+ /* -+ * We're now guaranteed that sdtx_device_open() won't be called any -+ * more, so we can now drop out reference. -+ */ -+ sdtx_device_put(ddev); -+} -+ -+ -+/* -- PM ops. --------------------------------------------------------------- */ -+ -+#ifdef CONFIG_PM_SLEEP -+ -+static void surface_dtx_pm_complete(struct device *dev) -+{ -+ struct sdtx_device *ddev = dev_get_drvdata(dev); -+ -+ /* -+ * Normally, the EC will store events while suspended (i.e. in -+ * display-off state) and release them when resumed (i.e. transitioned -+ * to display-on state). During hibernation, however, the EC will be -+ * shut down and does not store events. Furthermore, events might be -+ * dropped during prolonged suspension (it is currently unknown how -+ * big this event buffer is and how it behaves on overruns). -+ * -+ * To prevent any problems, we update the device state here. We do -+ * this delayed to ensure that any events sent by the EC directly -+ * after resuming will be handled first. The delay below has been -+ * chosen (experimentally), so that there should be ample time for -+ * these events to be handled, before we check and, if necessary, -+ * update the state. -+ */ -+ sdtx_update_device_state(ddev, msecs_to_jiffies(1000)); -+} -+ -+static const struct dev_pm_ops surface_dtx_pm_ops = { -+ .complete = surface_dtx_pm_complete, -+}; -+ -+#else /* CONFIG_PM_SLEEP */ -+ -+static const struct dev_pm_ops surface_dtx_pm_ops = {}; -+ -+#endif /* CONFIG_PM_SLEEP */ -+ -+ -+/* -- Platform driver. ------------------------------------------------------ */ -+ -+static int surface_dtx_platform_probe(struct platform_device *pdev) -+{ -+ struct ssam_controller *ctrl; -+ struct sdtx_device *ddev; -+ -+ /* Link to EC. */ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ ddev = sdtx_device_create(&pdev->dev, ctrl); -+ if (IS_ERR(ddev)) -+ return PTR_ERR(ddev); -+ -+ platform_set_drvdata(pdev, ddev); -+ return 0; -+} -+ -+static int surface_dtx_platform_remove(struct platform_device *pdev) -+{ -+ sdtx_device_destroy(platform_get_drvdata(pdev)); -+ return 0; -+} -+ -+static const struct acpi_device_id surface_dtx_acpi_match[] = { -+ { "MSHW0133", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_dtx_acpi_match); -+ -+static struct platform_driver surface_dtx_platform_driver = { -+ .probe = surface_dtx_platform_probe, -+ .remove = surface_dtx_platform_remove, -+ .driver = { -+ .name = "surface_dtx_pltf", -+ .acpi_match_table = surface_dtx_acpi_match, -+ .pm = &surface_dtx_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+/* -- SSAM device driver. --------------------------------------------------- */ -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+ -+static int surface_dtx_ssam_probe(struct ssam_device *sdev) -+{ -+ struct sdtx_device *ddev; -+ -+ ddev = sdtx_device_create(&sdev->dev, sdev->ctrl); -+ if (IS_ERR(ddev)) -+ return PTR_ERR(ddev); -+ -+ ssam_device_set_drvdata(sdev, ddev); -+ return 0; -+} -+ -+static void surface_dtx_ssam_remove(struct ssam_device *sdev) -+{ -+ sdtx_device_destroy(ssam_device_get_drvdata(sdev)); -+} -+ -+static const struct ssam_device_id surface_dtx_ssam_match[] = { -+ { SSAM_SDEV(BAS, 0x01, 0x00, 0x00) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match); -+ -+static struct ssam_device_driver surface_dtx_ssam_driver = { -+ .probe = surface_dtx_ssam_probe, -+ .remove = surface_dtx_ssam_remove, -+ .match_table = surface_dtx_ssam_match, -+ .driver = { -+ .name = "surface_dtx", -+ .pm = &surface_dtx_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+static int ssam_dtx_driver_register(void) -+{ -+ return ssam_device_driver_register(&surface_dtx_ssam_driver); -+} -+ -+static void ssam_dtx_driver_unregister(void) -+{ -+ ssam_device_driver_unregister(&surface_dtx_ssam_driver); -+} -+ -+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+static int ssam_dtx_driver_register(void) -+{ -+ return 0; -+} -+ -+static void ssam_dtx_driver_unregister(void) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+ -+/* -- Module setup. --------------------------------------------------------- */ -+ -+static int __init surface_dtx_init(void) -+{ -+ int status; -+ -+ status = ssam_dtx_driver_register(); -+ if (status) -+ return status; -+ -+ status = platform_driver_register(&surface_dtx_platform_driver); -+ if (status) -+ ssam_dtx_driver_unregister(); -+ -+ return status; -+} -+module_init(surface_dtx_init); -+ -+static void __exit surface_dtx_exit(void) -+{ -+ platform_driver_unregister(&surface_dtx_platform_driver); -+ ssam_dtx_driver_unregister(); -+} -+module_exit(surface_dtx_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_perfmode.c b/drivers/platform/x86/surface_perfmode.c -new file mode 100644 -index 000000000000..a9114e001d0d ---- /dev/null -+++ b/drivers/platform/x86/surface_perfmode.c -@@ -0,0 +1,122 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface performance-mode driver. -+ * -+ * Provides a user-space interface for the performance mode control provided -+ * by the Surface System Aggregator Module (SSAM), influencing cooling -+ * behavior of the device and potentially managing power limits. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+enum sam_perf_mode { -+ SAM_PERF_MODE_NORMAL = 1, -+ SAM_PERF_MODE_BATTERY = 2, -+ SAM_PERF_MODE_PERF1 = 3, -+ SAM_PERF_MODE_PERF2 = 4, -+ -+ __SAM_PERF_MODE__MIN = 1, -+ __SAM_PERF_MODE__MAX = 4, -+}; -+ -+struct ssam_perf_info { -+ __le32 mode; -+ __le16 unknown1; -+ __le16 unknown2; -+} __packed; -+ -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_tmp_perf_mode_get, struct ssam_perf_info, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .command_id = 0x02, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_perf_mode_set, __le32, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .command_id = 0x03, -+}); -+ -+static int ssam_tmp_perf_mode_set(struct ssam_device *sdev, u32 mode) -+{ -+ __le32 mode_le = cpu_to_le32(mode); -+ -+ if (mode < __SAM_PERF_MODE__MIN || mode > __SAM_PERF_MODE__MAX) -+ return -EINVAL; -+ -+ return ssam_retry(__ssam_tmp_perf_mode_set, sdev, &mode_le); -+} -+ -+static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, -+ char *data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ struct ssam_perf_info info; -+ int status; -+ -+ status = ssam_retry(ssam_tmp_perf_mode_get, sdev, &info); -+ if (status) { -+ dev_err(dev, "failed to get current performance mode: %d\n", -+ status); -+ return -EIO; -+ } -+ -+ return sprintf(data, "%d\n", le32_to_cpu(info.mode)); -+} -+ -+static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr, -+ const char *data, size_t count) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ int perf_mode; -+ int status; -+ -+ status = kstrtoint(data, 0, &perf_mode); -+ if (status < 0) -+ return status; -+ -+ status = ssam_tmp_perf_mode_set(sdev, perf_mode); -+ if (status < 0) -+ return status; -+ -+ return count; -+} -+ -+static const DEVICE_ATTR_RW(perf_mode); -+ -+static int surface_sam_sid_perfmode_probe(struct ssam_device *sdev) -+{ -+ return sysfs_create_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); -+} -+ -+static void surface_sam_sid_perfmode_remove(struct ssam_device *sdev) -+{ -+ sysfs_remove_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); -+} -+ -+static const struct ssam_device_id ssam_perfmode_match[] = { -+ { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, ssam_perfmode_match); -+ -+static struct ssam_device_driver surface_sam_sid_perfmode = { -+ .probe = surface_sam_sid_perfmode_probe, -+ .remove = surface_sam_sid_perfmode_remove, -+ .match_table = ssam_perfmode_match, -+ .driver = { -+ .name = "surface_performance_mode", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_sam_sid_perfmode); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Performance mode interface for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig -index 0aa46b451017..e96e136c60b4 100644 ---- a/drivers/power/supply/Kconfig -+++ b/drivers/power/supply/Kconfig -@@ -776,4 +776,36 @@ config RN5T618_POWER - This driver can also be built as a module. If so, the module will be - called rn5t618_power. - -+config BATTERY_SURFACE -+ tristate "Battery driver for 7th-generation Microsoft Surface devices" -+ depends on SURFACE_AGGREGATOR_REGISTRY -+ help -+ Driver for battery devices connected via/managed by the Surface System -+ Aggregator Module (SSAM). -+ -+ This driver provides battery-information and -status support for -+ Surface devices where said data is not exposed via the standard ACPI -+ devices. On those models (7th-generation), battery-information is -+ instead handled directly via SSAM client devices and this driver. -+ -+ Say M or Y here to include battery status support for 7th-generation -+ Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, -+ Surface Book 3, and Surface Laptop Go. -+ -+config CHARGER_SURFACE -+ tristate "AC driver for 7th-generation Microsoft Surface devices" -+ depends on SURFACE_AGGREGATOR_REGISTRY -+ help -+ Driver for AC devices connected via/managed by the Surface System -+ Aggregator Module (SSAM). -+ -+ This driver provides AC-information and -status support for Surface -+ devices where said data is not exposed via the standard ACPI devices. -+ On those models (7th-generation), AC-information is instead handled -+ directly via a SSAM client device and this driver. -+ -+ Say M or Y here to include AC status support for 7th-generation -+ Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, -+ Surface Book 3, and Surface Laptop Go. -+ - endif # POWER_SUPPLY -diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile -index dd4b86318cd9..9fdd34956153 100644 ---- a/drivers/power/supply/Makefile -+++ b/drivers/power/supply/Makefile -@@ -98,3 +98,5 @@ obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o - obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o - obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o - obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o -+obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o -+obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -new file mode 100644 -index 000000000000..5ec2e6bb2465 ---- /dev/null -+++ b/drivers/power/supply/surface_battery.c -@@ -0,0 +1,875 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Battery driver for 7th-generation Microsoft Surface devices via Surface -+ * System Aggregator Module (SSAM). -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- SAM interface. -------------------------------------------------------- */ -+ -+enum sam_event_cid_bat { -+ SAM_EVENT_CID_BAT_BIX = 0x15, -+ SAM_EVENT_CID_BAT_BST = 0x16, -+ SAM_EVENT_CID_BAT_ADP = 0x17, -+ SAM_EVENT_CID_BAT_PROT = 0x18, -+ SAM_EVENT_CID_BAT_DPTF = 0x53, -+}; -+ -+enum sam_battery_sta { -+ SAM_BATTERY_STA_OK = 0x0f, -+ SAM_BATTERY_STA_PRESENT = 0x10, -+}; -+ -+enum sam_battery_state { -+ SAM_BATTERY_STATE_DISCHARGING = BIT(0), -+ SAM_BATTERY_STATE_CHARGING = BIT(1), -+ SAM_BATTERY_STATE_CRITICAL = BIT(2), -+}; -+ -+enum sam_battery_power_unit { -+ SAM_BATTERY_POWER_UNIT_mW = 0, -+ SAM_BATTERY_POWER_UNIT_mA = 1, -+}; -+ -+/* Equivalent to data returned in ACPI _BIX method, revision 0. */ -+struct spwr_bix { -+ u8 revision; -+ __le32 power_unit; -+ __le32 design_cap; -+ __le32 last_full_charge_cap; -+ __le32 technology; -+ __le32 design_voltage; -+ __le32 design_cap_warn; -+ __le32 design_cap_low; -+ __le32 cycle_count; -+ __le32 measurement_accuracy; -+ __le32 max_sampling_time; -+ __le32 min_sampling_time; -+ __le32 max_avg_interval; -+ __le32 min_avg_interval; -+ __le32 bat_cap_granularity_1; -+ __le32 bat_cap_granularity_2; -+ __u8 model[21]; -+ __u8 serial[11]; -+ __u8 type[5]; -+ __u8 oem_info[21]; -+} __packed; -+ -+static_assert(sizeof(struct spwr_bix) == 119); -+ -+/* Equivalent to data returned in ACPI _BST method. */ -+struct spwr_bst { -+ __le32 state; -+ __le32 present_rate; -+ __le32 remaining_cap; -+ __le32 present_voltage; -+} __packed; -+ -+static_assert(sizeof(struct spwr_bst) == 16); -+ -+#define SPWR_BIX_REVISION 0 -+#define SPWR_BATTERY_VALUE_UNKNOWN 0xffffffff -+ -+/* Get battery status (_STA) */ -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x01, -+}); -+ -+/* Get battery static information (_BIX). */ -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bix, struct spwr_bix, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x02, -+}); -+ -+/* Get battery dynamic information (_BST). */ -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bst, struct spwr_bst, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x03, -+}); -+ -+/* Set battery trip point (_BTP). */ -+SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x04, -+}); -+ -+ -+/* -- Device structures. ---------------------------------------------------- */ -+ -+struct spwr_psy_properties { -+ const char *name; -+ struct ssam_event_registry registry; -+}; -+ -+struct spwr_battery_device { -+ struct ssam_device *sdev; -+ -+ char name[32]; -+ struct power_supply *psy; -+ struct power_supply_desc psy_desc; -+ -+ struct delayed_work update_work; -+ -+ struct ssam_event_notifier notif; -+ -+ struct mutex lock; /* Guards access to state data below. */ -+ unsigned long timestamp; -+ -+ __le32 sta; -+ struct spwr_bix bix; -+ struct spwr_bst bst; -+ u32 alarm; -+}; -+ -+ -+/* -- Module parameters. ---------------------------------------------------- */ -+ -+static unsigned int cache_time = 1000; -+module_param(cache_time, uint, 0644); -+MODULE_PARM_DESC(cache_time, "battery state caching time in milliseconds [default: 1000]"); -+ -+ -+/* -- State management. ----------------------------------------------------- */ -+ -+/* -+ * Delay for battery update quirk. See spwr_external_power_changed() below -+ * for more details. -+ */ -+#define SPWR_AC_BAT_UPDATE_DELAY msecs_to_jiffies(5000) -+ -+static bool spwr_battery_present(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT; -+} -+ -+static int spwr_battery_load_sta(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ return ssam_retry(ssam_bat_get_sta, bat->sdev, &bat->sta); -+} -+ -+static int spwr_battery_load_bix(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (!spwr_battery_present(bat)) -+ return 0; -+ -+ status = ssam_retry(ssam_bat_get_bix, bat->sdev, &bat->bix); -+ -+ /* Enforce NULL terminated strings in case anything goes wrong... */ -+ bat->bix.model[ARRAY_SIZE(bat->bix.model) - 1] = 0; -+ bat->bix.serial[ARRAY_SIZE(bat->bix.serial) - 1] = 0; -+ bat->bix.type[ARRAY_SIZE(bat->bix.type) - 1] = 0; -+ bat->bix.oem_info[ARRAY_SIZE(bat->bix.oem_info) - 1] = 0; -+ -+ return status; -+} -+ -+static int spwr_battery_load_bst(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ if (!spwr_battery_present(bat)) -+ return 0; -+ -+ return ssam_retry(ssam_bat_get_bst, bat->sdev, &bat->bst); -+} -+ -+static int spwr_battery_set_alarm_unlocked(struct spwr_battery_device *bat, u32 value) -+{ -+ __le32 value_le = cpu_to_le32(value); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ bat->alarm = value; -+ return ssam_retry(ssam_bat_set_btp, bat->sdev, &value_le); -+} -+ -+static int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, bool cached) -+{ -+ unsigned long cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time); -+ int status; -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (cached && bat->timestamp && time_is_after_jiffies(cache_deadline)) -+ return 0; -+ -+ status = spwr_battery_load_sta(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bst(bat); -+ if (status) -+ return status; -+ -+ bat->timestamp = jiffies; -+ return 0; -+} -+ -+static int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached) -+{ -+ int status; -+ -+ mutex_lock(&bat->lock); -+ status = spwr_battery_update_bst_unlocked(bat, cached); -+ mutex_unlock(&bat->lock); -+ -+ return status; -+} -+ -+static int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ lockdep_assert_held(&bat->lock); -+ -+ status = spwr_battery_load_sta(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bix(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bst(bat); -+ if (status) -+ return status; -+ -+ if (bat->bix.revision != SPWR_BIX_REVISION) -+ dev_warn(&bat->sdev->dev, "unsupported battery revision: %u\n", bat->bix.revision); -+ -+ bat->timestamp = jiffies; -+ return 0; -+} -+ -+static u32 sprw_battery_get_full_cap_safe(struct spwr_battery_device *bat) -+{ -+ u32 full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN) -+ full_cap = get_unaligned_le32(&bat->bix.design_cap); -+ -+ return full_cap; -+} -+ -+static bool spwr_battery_is_full(struct spwr_battery_device *bat) -+{ -+ u32 state = get_unaligned_le32(&bat->bst.state); -+ u32 full_cap = sprw_battery_get_full_cap_safe(bat); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ return full_cap != SPWR_BATTERY_VALUE_UNKNOWN && full_cap != 0 && -+ remaining_cap != SPWR_BATTERY_VALUE_UNKNOWN && -+ remaining_cap >= full_cap && -+ state == 0; -+} -+ -+static int spwr_battery_recheck_full(struct spwr_battery_device *bat) -+{ -+ bool present; -+ u32 unit; -+ int status; -+ -+ mutex_lock(&bat->lock); -+ unit = get_unaligned_le32(&bat->bix.power_unit); -+ present = spwr_battery_present(bat); -+ -+ status = spwr_battery_update_bix_unlocked(bat); -+ if (status) -+ goto out; -+ -+ /* If battery has been attached, (re-)initialize alarm. */ -+ if (!present && spwr_battery_present(bat)) { -+ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); -+ -+ status = spwr_battery_set_alarm_unlocked(bat, cap_warn); -+ if (status) -+ goto out; -+ } -+ -+ /* -+ * Warn if the unit has changed. This is something we genuinely don't -+ * expect to happen, so make this a big warning. If it does, we'll -+ * need to add support for it. -+ */ -+ WARN_ON(unit != get_unaligned_le32(&bat->bix.power_unit)); -+ -+out: -+ mutex_unlock(&bat->lock); -+ -+ if (!status) -+ power_supply_changed(bat->psy); -+ -+ return status; -+} -+ -+static int spwr_battery_recheck_status(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ status = spwr_battery_update_bst(bat, false); -+ if (!status) -+ power_supply_changed(bat->psy); -+ -+ return status; -+} -+ -+static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif); -+ int status; -+ -+ /* -+ * We cannot use strict matching when registering the notifier as the -+ * EC expects us to register it against instance ID 0. Strict matching -+ * would thus drop events, as those may have non-zero instance IDs in -+ * this subsystem. So we need to check the instance ID of the event -+ * here manually. -+ */ -+ if (event->instance_id != bat->sdev->uid.instance) -+ return 0; -+ -+ dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", -+ event->command_id, event->instance_id, event->target_id); -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_BAT_BIX: -+ status = spwr_battery_recheck_full(bat); -+ break; -+ -+ case SAM_EVENT_CID_BAT_BST: -+ status = spwr_battery_recheck_status(bat); -+ break; -+ -+ case SAM_EVENT_CID_BAT_PROT: -+ /* -+ * TODO: Implement support for battery protection status change -+ * event. -+ */ -+ status = 0; -+ break; -+ -+ case SAM_EVENT_CID_BAT_DPTF: -+ /* -+ * TODO: Implement support for DPTF event. -+ */ -+ status = 0; -+ break; -+ -+ default: -+ return 0; -+ } -+ -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+} -+ -+static void spwr_battery_update_bst_workfn(struct work_struct *work) -+{ -+ struct delayed_work *dwork = to_delayed_work(work); -+ struct spwr_battery_device *bat; -+ int status; -+ -+ bat = container_of(dwork, struct spwr_battery_device, update_work); -+ -+ status = spwr_battery_update_bst(bat, false); -+ if (status) { -+ dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", status); -+ return; -+ } -+ -+ power_supply_changed(bat->psy); -+} -+ -+static void spwr_external_power_changed(struct power_supply *psy) -+{ -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ -+ /* -+ * Handle battery update quirk: When the battery is fully charged (or -+ * charged up to the limit imposed by the UEFI battery limit) and the -+ * adapter is plugged in or removed, the EC does not send a separate -+ * event for the state (charging/discharging) change. Furthermore it -+ * may take some time until the state is updated on the battery. -+ * Schedule an update to solve this. -+ */ -+ -+ schedule_delayed_work(&bat->update_work, SPWR_AC_BAT_UPDATE_DELAY); -+} -+ -+ -+/* -- Properties. ----------------------------------------------------------- */ -+ -+static const enum power_supply_property spwr_battery_props_chg[] = { -+ POWER_SUPPLY_PROP_STATUS, -+ POWER_SUPPLY_PROP_PRESENT, -+ POWER_SUPPLY_PROP_TECHNOLOGY, -+ POWER_SUPPLY_PROP_CYCLE_COUNT, -+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, -+ POWER_SUPPLY_PROP_VOLTAGE_NOW, -+ POWER_SUPPLY_PROP_CURRENT_NOW, -+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, -+ POWER_SUPPLY_PROP_CHARGE_FULL, -+ POWER_SUPPLY_PROP_CHARGE_NOW, -+ POWER_SUPPLY_PROP_CAPACITY, -+ POWER_SUPPLY_PROP_CAPACITY_LEVEL, -+ POWER_SUPPLY_PROP_MODEL_NAME, -+ POWER_SUPPLY_PROP_MANUFACTURER, -+ POWER_SUPPLY_PROP_SERIAL_NUMBER, -+}; -+ -+static const enum power_supply_property spwr_battery_props_eng[] = { -+ POWER_SUPPLY_PROP_STATUS, -+ POWER_SUPPLY_PROP_PRESENT, -+ POWER_SUPPLY_PROP_TECHNOLOGY, -+ POWER_SUPPLY_PROP_CYCLE_COUNT, -+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, -+ POWER_SUPPLY_PROP_VOLTAGE_NOW, -+ POWER_SUPPLY_PROP_POWER_NOW, -+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, -+ POWER_SUPPLY_PROP_ENERGY_FULL, -+ POWER_SUPPLY_PROP_ENERGY_NOW, -+ POWER_SUPPLY_PROP_CAPACITY, -+ POWER_SUPPLY_PROP_CAPACITY_LEVEL, -+ POWER_SUPPLY_PROP_MODEL_NAME, -+ POWER_SUPPLY_PROP_MANUFACTURER, -+ POWER_SUPPLY_PROP_SERIAL_NUMBER, -+}; -+ -+static int spwr_battery_prop_status(struct spwr_battery_device *bat) -+{ -+ u32 state = get_unaligned_le32(&bat->bst.state); -+ u32 present_rate = get_unaligned_le32(&bat->bst.present_rate); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (state & SAM_BATTERY_STATE_DISCHARGING) -+ return POWER_SUPPLY_STATUS_DISCHARGING; -+ -+ if (state & SAM_BATTERY_STATE_CHARGING) -+ return POWER_SUPPLY_STATUS_CHARGING; -+ -+ if (spwr_battery_is_full(bat)) -+ return POWER_SUPPLY_STATUS_FULL; -+ -+ if (present_rate == 0) -+ return POWER_SUPPLY_STATUS_NOT_CHARGING; -+ -+ return POWER_SUPPLY_STATUS_UNKNOWN; -+} -+ -+static int spwr_battery_prop_technology(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ if (!strcasecmp("NiCd", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_NiCd; -+ -+ if (!strcasecmp("NiMH", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_NiMH; -+ -+ if (!strcasecmp("LION", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_LION; -+ -+ if (!strncasecmp("LI-ION", bat->bix.type, 6)) -+ return POWER_SUPPLY_TECHNOLOGY_LION; -+ -+ if (!strcasecmp("LiP", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_LIPO; -+ -+ return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; -+} -+ -+static int spwr_battery_prop_capacity(struct spwr_battery_device *bat) -+{ -+ u32 full_cap = sprw_battery_get_full_cap_safe(bat); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN) -+ return -ENODATA; -+ -+ if (remaining_cap == SPWR_BATTERY_VALUE_UNKNOWN) -+ return -ENODATA; -+ -+ return remaining_cap * 100 / full_cap; -+} -+ -+static int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat) -+{ -+ u32 state = get_unaligned_le32(&bat->bst.state); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (state & SAM_BATTERY_STATE_CRITICAL) -+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; -+ -+ if (spwr_battery_is_full(bat)) -+ return POWER_SUPPLY_CAPACITY_LEVEL_FULL; -+ -+ if (remaining_cap <= bat->alarm) -+ return POWER_SUPPLY_CAPACITY_LEVEL_LOW; -+ -+ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; -+} -+ -+static int spwr_battery_get_property(struct power_supply *psy, enum power_supply_property psp, -+ union power_supply_propval *val) -+{ -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ u32 value; -+ int status; -+ -+ mutex_lock(&bat->lock); -+ -+ status = spwr_battery_update_bst_unlocked(bat, true); -+ if (status) -+ goto out; -+ -+ /* Abort if battery is not present. */ -+ if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) { -+ status = -ENODEV; -+ goto out; -+ } -+ -+ switch (psp) { -+ case POWER_SUPPLY_PROP_STATUS: -+ val->intval = spwr_battery_prop_status(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_PRESENT: -+ val->intval = spwr_battery_present(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_TECHNOLOGY: -+ val->intval = spwr_battery_prop_technology(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_CYCLE_COUNT: -+ value = get_unaligned_le32(&bat->bix.cycle_count); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: -+ value = get_unaligned_le32(&bat->bix.design_voltage); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_VOLTAGE_NOW: -+ value = get_unaligned_le32(&bat->bst.present_voltage); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CURRENT_NOW: -+ case POWER_SUPPLY_PROP_POWER_NOW: -+ value = get_unaligned_le32(&bat->bst.present_rate); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: -+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: -+ value = get_unaligned_le32(&bat->bix.design_cap); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_FULL: -+ case POWER_SUPPLY_PROP_ENERGY_FULL: -+ value = get_unaligned_le32(&bat->bix.last_full_charge_cap); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_NOW: -+ case POWER_SUPPLY_PROP_ENERGY_NOW: -+ value = get_unaligned_le32(&bat->bst.remaining_cap); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CAPACITY: -+ val->intval = spwr_battery_prop_capacity(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL: -+ val->intval = spwr_battery_prop_capacity_level(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_MODEL_NAME: -+ val->strval = bat->bix.model; -+ break; -+ -+ case POWER_SUPPLY_PROP_MANUFACTURER: -+ val->strval = bat->bix.oem_info; -+ break; -+ -+ case POWER_SUPPLY_PROP_SERIAL_NUMBER: -+ val->strval = bat->bix.serial; -+ break; -+ -+ default: -+ status = -EINVAL; -+ break; -+ } -+ -+out: -+ mutex_unlock(&bat->lock); -+ return status; -+} -+ -+ -+/* -- Alarm attribute. ------------------------------------------------------ */ -+ -+static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct power_supply *psy = dev_get_drvdata(dev); -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ int status; -+ -+ mutex_lock(&bat->lock); -+ status = sysfs_emit(buf, "%d\n", bat->alarm * 1000); -+ mutex_unlock(&bat->lock); -+ -+ return status; -+} -+ -+static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, const char *buf, -+ size_t count) -+{ -+ struct power_supply *psy = dev_get_drvdata(dev); -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ unsigned long value; -+ int status; -+ -+ status = kstrtoul(buf, 0, &value); -+ if (status) -+ return status; -+ -+ mutex_lock(&bat->lock); -+ -+ if (!spwr_battery_present(bat)) { -+ mutex_unlock(&bat->lock); -+ return -ENODEV; -+ } -+ -+ status = spwr_battery_set_alarm_unlocked(bat, value / 1000); -+ if (status) { -+ mutex_unlock(&bat->lock); -+ return status; -+ } -+ -+ mutex_unlock(&bat->lock); -+ return count; -+} -+ -+static DEVICE_ATTR_RW(alarm); -+ -+static struct attribute *spwr_battery_attrs[] = { -+ &dev_attr_alarm.attr, -+ NULL, -+}; -+ATTRIBUTE_GROUPS(spwr_battery); -+ -+ -+/* -- Device setup. --------------------------------------------------------- */ -+ -+static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_device *sdev, -+ struct ssam_event_registry registry, const char *name) -+{ -+ mutex_init(&bat->lock); -+ strncpy(bat->name, name, ARRAY_SIZE(bat->name) - 1); -+ -+ bat->sdev = sdev; -+ -+ bat->notif.base.priority = 1; -+ bat->notif.base.fn = spwr_notify_bat; -+ bat->notif.event.reg = registry; -+ bat->notif.event.id.target_category = sdev->uid.category; -+ bat->notif.event.id.instance = 0; /* need to register with instance 0 */ -+ bat->notif.event.mask = SSAM_EVENT_MASK_TARGET; -+ bat->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ bat->psy_desc.name = bat->name; -+ bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; -+ bat->psy_desc.get_property = spwr_battery_get_property; -+ -+ INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn); -+} -+ -+static int spwr_battery_register(struct spwr_battery_device *bat) -+{ -+ struct power_supply_config psy_cfg = {}; -+ __le32 sta; -+ int status; -+ -+ /* Make sure the device is there and functioning properly. */ -+ status = ssam_retry(ssam_bat_get_sta, bat->sdev, &sta); -+ if (status) -+ return status; -+ -+ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) -+ return -ENODEV; -+ -+ /* Satisfy lockdep although we are in an exclusive context here. */ -+ mutex_lock(&bat->lock); -+ -+ status = spwr_battery_update_bix_unlocked(bat); -+ if (status) { -+ mutex_unlock(&bat->lock); -+ return status; -+ } -+ -+ if (spwr_battery_present(bat)) { -+ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); -+ -+ status = spwr_battery_set_alarm_unlocked(bat, cap_warn); -+ if (status) { -+ mutex_unlock(&bat->lock); -+ return status; -+ } -+ } -+ -+ mutex_unlock(&bat->lock); -+ -+ bat->psy_desc.external_power_changed = spwr_external_power_changed; -+ -+ switch (get_unaligned_le32(&bat->bix.power_unit)) { -+ case SAM_BATTERY_POWER_UNIT_mW: -+ bat->psy_desc.properties = spwr_battery_props_eng; -+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng); -+ break; -+ -+ case SAM_BATTERY_POWER_UNIT_mA: -+ bat->psy_desc.properties = spwr_battery_props_chg; -+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg); -+ break; -+ -+ default: -+ dev_err(&bat->sdev->dev, "unsupported battery power unit: %u\n", -+ get_unaligned_le32(&bat->bix.power_unit)); -+ return -EINVAL; -+ } -+ -+ psy_cfg.drv_data = bat; -+ psy_cfg.attr_grp = spwr_battery_groups; -+ -+ bat->psy = devm_power_supply_register(&bat->sdev->dev, &bat->psy_desc, &psy_cfg); -+ if (IS_ERR(bat->psy)) -+ return PTR_ERR(bat->psy); -+ -+ return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int __maybe_unused surface_battery_resume(struct device *dev) -+{ -+ return spwr_battery_recheck_full(dev_get_drvdata(dev)); -+} -+static SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume); -+ -+static int surface_battery_probe(struct ssam_device *sdev) -+{ -+ const struct spwr_psy_properties *p; -+ struct spwr_battery_device *bat; -+ -+ p = ssam_device_get_match_data(sdev); -+ if (!p) -+ return -ENODEV; -+ -+ bat = devm_kzalloc(&sdev->dev, sizeof(*bat), GFP_KERNEL); -+ if (!bat) -+ return -ENOMEM; -+ -+ spwr_battery_init(bat, sdev, p->registry, p->name); -+ ssam_device_set_drvdata(sdev, bat); -+ -+ return spwr_battery_register(bat); -+} -+ -+static void surface_battery_remove(struct ssam_device *sdev) -+{ -+ struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); -+ -+ ssam_notifier_unregister(sdev->ctrl, &bat->notif); -+ cancel_delayed_work_sync(&bat->update_work); -+} -+ -+static const struct spwr_psy_properties spwr_psy_props_bat1 = { -+ .name = "BAT1", -+ .registry = SSAM_EVENT_REGISTRY_SAM, -+}; -+ -+static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { -+ .name = "BAT2", -+ .registry = SSAM_EVENT_REGISTRY_KIP, -+}; -+ -+static const struct ssam_device_id surface_battery_match[] = { -+ { SSAM_SDEV(BAT, 0x01, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, -+ { SSAM_SDEV(BAT, 0x02, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_battery_match); -+ -+static struct ssam_device_driver surface_battery_driver = { -+ .probe = surface_battery_probe, -+ .remove = surface_battery_remove, -+ .match_table = surface_battery_match, -+ .driver = { -+ .name = "surface_battery", -+ .pm = &surface_battery_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_battery_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Battery driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -new file mode 100644 -index 000000000000..a060c36c7766 ---- /dev/null -+++ b/drivers/power/supply/surface_charger.c -@@ -0,0 +1,282 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * AC driver for 7th-generation Microsoft Surface devices via Surface System -+ * Aggregator Module (SSAM). -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- SAM interface. -------------------------------------------------------- */ -+ -+enum sam_event_cid_bat { -+ SAM_EVENT_CID_BAT_ADP = 0x17, -+}; -+ -+enum sam_battery_sta { -+ SAM_BATTERY_STA_OK = 0x0f, -+ SAM_BATTERY_STA_PRESENT = 0x10, -+}; -+ -+/* Get battery status (_STA). */ -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x01, -+}); -+ -+/* Get platform power source for battery (_PSR / DPTF PSRC). */ -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x0d, -+}); -+ -+ -+/* -- Device structures. ---------------------------------------------------- */ -+ -+struct spwr_psy_properties { -+ const char *name; -+ struct ssam_event_registry registry; -+}; -+ -+struct spwr_ac_device { -+ struct ssam_device *sdev; -+ -+ char name[32]; -+ struct power_supply *psy; -+ struct power_supply_desc psy_desc; -+ -+ struct ssam_event_notifier notif; -+ -+ struct mutex lock; /* Guards access to state below. */ -+ -+ __le32 state; -+}; -+ -+ -+/* -- State management. ----------------------------------------------------- */ -+ -+static int spwr_ac_update_unlocked(struct spwr_ac_device *ac) -+{ -+ __le32 old = ac->state; -+ int status; -+ -+ lockdep_assert_held(&ac->lock); -+ -+ status = ssam_retry(ssam_bat_get_psrc, ac->sdev, &ac->state); -+ if (status < 0) -+ return status; -+ -+ return old != ac->state; -+} -+ -+static int spwr_ac_update(struct spwr_ac_device *ac) -+{ -+ int status; -+ -+ mutex_lock(&ac->lock); -+ status = spwr_ac_update_unlocked(ac); -+ mutex_unlock(&ac->lock); -+ -+ return status; -+} -+ -+static int spwr_ac_recheck(struct spwr_ac_device *ac) -+{ -+ int status; -+ -+ status = spwr_ac_update(ac); -+ if (status > 0) -+ power_supply_changed(ac->psy); -+ -+ return status >= 0 ? 0 : status; -+} -+ -+static u32 spwr_notify_ac(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct spwr_ac_device *ac; -+ int status; -+ -+ ac = container_of(nf, struct spwr_ac_device, notif); -+ -+ dev_dbg(&ac->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", -+ event->command_id, event->instance_id, event->target_id); -+ -+ /* -+ * Allow events of all targets/instances here. Global adapter status -+ * seems to be handled via target=1 and instance=1, but events are -+ * reported on all targets/instances in use. -+ * -+ * While it should be enough to just listen on 1/1, listen everywhere to -+ * make sure we don't miss anything. -+ */ -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_BAT_ADP: -+ status = spwr_ac_recheck(ac); -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+ -+ default: -+ return 0; -+ } -+} -+ -+ -+/* -- Properties. ----------------------------------------------------------- */ -+ -+static const enum power_supply_property spwr_ac_props[] = { -+ POWER_SUPPLY_PROP_ONLINE, -+}; -+ -+static int spwr_ac_get_property(struct power_supply *psy, enum power_supply_property psp, -+ union power_supply_propval *val) -+{ -+ struct spwr_ac_device *ac = power_supply_get_drvdata(psy); -+ int status; -+ -+ mutex_lock(&ac->lock); -+ -+ status = spwr_ac_update_unlocked(ac); -+ if (status) -+ goto out; -+ -+ switch (psp) { -+ case POWER_SUPPLY_PROP_ONLINE: -+ val->intval = !!le32_to_cpu(ac->state); -+ break; -+ -+ default: -+ status = -EINVAL; -+ goto out; -+ } -+ -+out: -+ mutex_unlock(&ac->lock); -+ return status; -+} -+ -+ -+/* -- Device setup. --------------------------------------------------------- */ -+ -+static char *battery_supplied_to[] = { -+ "BAT1", -+ "BAT2", -+}; -+ -+static void spwr_ac_init(struct spwr_ac_device *ac, struct ssam_device *sdev, -+ struct ssam_event_registry registry, const char *name) -+{ -+ mutex_init(&ac->lock); -+ strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1); -+ -+ ac->sdev = sdev; -+ -+ ac->notif.base.priority = 1; -+ ac->notif.base.fn = spwr_notify_ac; -+ ac->notif.event.reg = registry; -+ ac->notif.event.id.target_category = sdev->uid.category; -+ ac->notif.event.id.instance = 0; -+ ac->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ ac->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ ac->psy_desc.name = ac->name; -+ ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; -+ ac->psy_desc.properties = spwr_ac_props; -+ ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props); -+ ac->psy_desc.get_property = spwr_ac_get_property; -+} -+ -+static int spwr_ac_register(struct spwr_ac_device *ac) -+{ -+ struct power_supply_config psy_cfg = {}; -+ __le32 sta; -+ int status; -+ -+ /* Make sure the device is there and functioning properly. */ -+ status = ssam_retry(ssam_bat_get_sta, ac->sdev, &sta); -+ if (status) -+ return status; -+ -+ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) -+ return -ENODEV; -+ -+ psy_cfg.drv_data = ac; -+ psy_cfg.supplied_to = battery_supplied_to; -+ psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); -+ -+ ac->psy = devm_power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg); -+ if (IS_ERR(ac->psy)) -+ return PTR_ERR(ac->psy); -+ -+ return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int __maybe_unused surface_ac_resume(struct device *dev) -+{ -+ return spwr_ac_recheck(dev_get_drvdata(dev)); -+} -+static SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); -+ -+static int surface_ac_probe(struct ssam_device *sdev) -+{ -+ const struct spwr_psy_properties *p; -+ struct spwr_ac_device *ac; -+ -+ p = ssam_device_get_match_data(sdev); -+ if (!p) -+ return -ENODEV; -+ -+ ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL); -+ if (!ac) -+ return -ENOMEM; -+ -+ spwr_ac_init(ac, sdev, p->registry, p->name); -+ ssam_device_set_drvdata(sdev, ac); -+ -+ return spwr_ac_register(ac); -+} -+ -+static void surface_ac_remove(struct ssam_device *sdev) -+{ -+ struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); -+ -+ ssam_notifier_unregister(sdev->ctrl, &ac->notif); -+} -+ -+static const struct spwr_psy_properties spwr_psy_props_adp1 = { -+ .name = "ADP1", -+ .registry = SSAM_EVENT_REGISTRY_SAM, -+}; -+ -+static const struct ssam_device_id surface_ac_match[] = { -+ { SSAM_SDEV(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_ac_match); -+ -+static struct ssam_device_driver surface_ac_driver = { -+ .probe = surface_ac_probe, -+ .remove = surface_ac_remove, -+ .match_table = surface_ac_match, -+ .driver = { -+ .name = "surface_ac", -+ .pm = &surface_ac_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_ac_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("AC driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h -index ef64063fac30..0b8f1feefe0e 100644 ---- a/include/linux/mod_devicetable.h -+++ b/include/linux/mod_devicetable.h -@@ -840,15 +840,16 @@ struct mhi_device_id { - - /* Surface System Aggregator Module */ - --#define SSAM_MATCH_CHANNEL 0x1 -+#define SSAM_MATCH_TARGET 0x1 - #define SSAM_MATCH_INSTANCE 0x2 - #define SSAM_MATCH_FUNCTION 0x4 - - struct ssam_device_id { - __u8 match_flags; - -+ __u8 domain; - __u8 category; -- __u8 channel; -+ __u8 target; - __u8 instance; - __u8 function; - -diff --git a/include/linux/surface_acpi_notify.h b/include/linux/surface_acpi_notify.h -new file mode 100644 -index 000000000000..8e3e86c7d78c ---- /dev/null -+++ b/include/linux/surface_acpi_notify.h -@@ -0,0 +1,39 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Interface for Surface ACPI Notify (SAN) driver. -+ * -+ * Provides access to discrete GPU notifications sent from ACPI via the SAN -+ * driver, which are not handled by this driver directly. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#ifndef _LINUX_SURFACE_ACPI_NOTIFY_H -+#define _LINUX_SURFACE_ACPI_NOTIFY_H -+ -+#include -+#include -+ -+/** -+ * struct san_dgpu_event - Discrete GPU ACPI event. -+ * @category: Category of the event. -+ * @target: Target ID of the event source. -+ * @command: Command ID of the event. -+ * @instance: Instance ID of the event source. -+ * @length: Length of the event's payload data (in bytes). -+ * @payload: Pointer to the event's payload data. -+ */ -+struct san_dgpu_event { -+ u8 category; -+ u8 target; -+ u8 command; -+ u8 instance; -+ u16 length; -+ u8 *payload; -+}; -+ -+int san_client_link(struct device *client); -+int san_dgpu_notifier_register(struct notifier_block *nb); -+int san_dgpu_notifier_unregister(struct notifier_block *nb); -+ -+#endif /* _LINUX_SURFACE_ACPI_NOTIFY_H */ -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -new file mode 100644 -index 000000000000..068e1982ad37 ---- /dev/null -+++ b/include/linux/surface_aggregator/controller.h -@@ -0,0 +1,849 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Surface System Aggregator Module (SSAM) controller interface. -+ * -+ * Main communication interface for the SSAM EC. Provides a controller -+ * managing access and communication to and from the SSAM EC, as well as main -+ * communication structures and definitions. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H -+#define _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H -+ -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- Main data types and definitions --------------------------------------- */ -+ -+/** -+ * enum ssam_event_flags - Flags for enabling/disabling SAM events -+ * @SSAM_EVENT_SEQUENCED: The event will be sent via a sequenced data frame. -+ */ -+enum ssam_event_flags { -+ SSAM_EVENT_SEQUENCED = BIT(0), -+}; -+ -+/** -+ * struct ssam_event - SAM event sent from the EC to the host. -+ * @target_category: Target category of the event source. See &enum ssam_ssh_tc. -+ * @target_id: Target ID of the event source. -+ * @command_id: Command ID of the event. -+ * @instance_id: Instance ID of the event source. -+ * @length: Length of the event payload in bytes. -+ * @data: Event payload data. -+ */ -+struct ssam_event { -+ u8 target_category; -+ u8 target_id; -+ u8 command_id; -+ u8 instance_id; -+ u16 length; -+ u8 data[]; -+}; -+ -+/** -+ * enum ssam_request_flags - Flags for SAM requests. -+ * -+ * @SSAM_REQUEST_HAS_RESPONSE: -+ * Specifies that the request expects a response. If not set, the request -+ * will be directly completed after its underlying packet has been -+ * transmitted. If set, the request transport system waits for a response -+ * of the request. -+ * -+ * @SSAM_REQUEST_UNSEQUENCED: -+ * Specifies that the request should be transmitted via an unsequenced -+ * packet. If set, the request must not have a response, meaning that this -+ * flag and the %SSAM_REQUEST_HAS_RESPONSE flag are mutually exclusive. -+ */ -+enum ssam_request_flags { -+ SSAM_REQUEST_HAS_RESPONSE = BIT(0), -+ SSAM_REQUEST_UNSEQUENCED = BIT(1), -+}; -+ -+/** -+ * struct ssam_request - SAM request description. -+ * @target_category: Category of the request's target. See &enum ssam_ssh_tc. -+ * @target_id: ID of the request's target. -+ * @command_id: Command ID of the request. -+ * @instance_id: Instance ID of the request's target. -+ * @flags: Flags for the request. See &enum ssam_request_flags. -+ * @length: Length of the request payload in bytes. -+ * @payload: Request payload data. -+ * -+ * This struct fully describes a SAM request with payload. It is intended to -+ * help set up the actual transport struct, e.g. &struct ssam_request_sync, -+ * and specifically its raw message data via ssam_request_write_data(). -+ */ -+struct ssam_request { -+ u8 target_category; -+ u8 target_id; -+ u8 command_id; -+ u8 instance_id; -+ u16 flags; -+ u16 length; -+ const u8 *payload; -+}; -+ -+/** -+ * struct ssam_response - Response buffer for SAM request. -+ * @capacity: Capacity of the buffer, in bytes. -+ * @length: Length of the actual data stored in the memory pointed to by -+ * @pointer, in bytes. Set by the transport system. -+ * @pointer: Pointer to the buffer's memory, storing the response payload data. -+ */ -+struct ssam_response { -+ size_t capacity; -+ size_t length; -+ u8 *pointer; -+}; -+ -+struct ssam_controller; -+ -+struct ssam_controller *ssam_get_controller(void); -+struct ssam_controller *ssam_client_bind(struct device *client); -+int ssam_client_link(struct ssam_controller *ctrl, struct device *client); -+ -+struct device *ssam_controller_device(struct ssam_controller *c); -+ -+struct ssam_controller *ssam_controller_get(struct ssam_controller *c); -+void ssam_controller_put(struct ssam_controller *c); -+ -+void ssam_controller_statelock(struct ssam_controller *c); -+void ssam_controller_stateunlock(struct ssam_controller *c); -+ -+ssize_t ssam_request_write_data(struct ssam_span *buf, -+ struct ssam_controller *ctrl, -+ const struct ssam_request *spec); -+ -+ -+/* -- Synchronous request interface. ---------------------------------------- */ -+ -+/** -+ * struct ssam_request_sync - Synchronous SAM request struct. -+ * @base: Underlying SSH request. -+ * @comp: Completion used to signal full completion of the request. After the -+ * request has been submitted, this struct may only be modified or -+ * deallocated after the completion has been signaled. -+ * request has been submitted, -+ * @resp: Buffer to store the response. -+ * @status: Status of the request, set after the base request has been -+ * completed or has failed. -+ */ -+struct ssam_request_sync { -+ struct ssh_request base; -+ struct completion comp; -+ struct ssam_response *resp; -+ int status; -+}; -+ -+int ssam_request_sync_alloc(size_t payload_len, gfp_t flags, -+ struct ssam_request_sync **rqst, -+ struct ssam_span *buffer); -+ -+void ssam_request_sync_free(struct ssam_request_sync *rqst); -+ -+int ssam_request_sync_init(struct ssam_request_sync *rqst, -+ enum ssam_request_flags flags); -+ -+/** -+ * ssam_request_sync_set_data - Set message data of a synchronous request. -+ * @rqst: The request. -+ * @ptr: Pointer to the request message data. -+ * @len: Length of the request message data. -+ * -+ * Set the request message data of a synchronous request. The provided buffer -+ * needs to live until the request has been completed. -+ */ -+static inline void ssam_request_sync_set_data(struct ssam_request_sync *rqst, -+ u8 *ptr, size_t len) -+{ -+ ssh_request_set_data(&rqst->base, ptr, len); -+} -+ -+/** -+ * ssam_request_sync_set_resp - Set response buffer of a synchronous request. -+ * @rqst: The request. -+ * @resp: The response buffer. -+ * -+ * Sets the response buffer of a synchronous request. This buffer will store -+ * the response of the request after it has been completed. May be %NULL if no -+ * response is expected. -+ */ -+static inline void ssam_request_sync_set_resp(struct ssam_request_sync *rqst, -+ struct ssam_response *resp) -+{ -+ rqst->resp = resp; -+} -+ -+int ssam_request_sync_submit(struct ssam_controller *ctrl, -+ struct ssam_request_sync *rqst); -+ -+/** -+ * ssam_request_sync_wait - Wait for completion of a synchronous request. -+ * @rqst: The request to wait for. -+ * -+ * Wait for completion and release of a synchronous request. After this -+ * function terminates, the request is guaranteed to have left the transport -+ * system. After successful submission of a request, this function must be -+ * called before accessing the response of the request, freeing the request, -+ * or freeing any of the buffers associated with the request. -+ * -+ * This function must not be called if the request has not been submitted yet -+ * and may lead to a deadlock/infinite wait if a subsequent request submission -+ * fails in that case, due to the completion never triggering. -+ * -+ * Return: Returns the status of the given request, which is set on completion -+ * of the packet. This value is zero on success and negative on failure. -+ */ -+static inline int ssam_request_sync_wait(struct ssam_request_sync *rqst) -+{ -+ wait_for_completion(&rqst->comp); -+ return rqst->status; -+} -+ -+int ssam_request_sync(struct ssam_controller *ctrl, -+ const struct ssam_request *spec, -+ struct ssam_response *rsp); -+ -+int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, -+ const struct ssam_request *spec, -+ struct ssam_response *rsp, -+ struct ssam_span *buf); -+ -+/** -+ * ssam_request_sync_onstack - Execute a synchronous request on the stack. -+ * @ctrl: The controller via which the request is submitted. -+ * @rqst: The request specification. -+ * @rsp: The response buffer. -+ * @payload_len: The (maximum) request payload length. -+ * -+ * Allocates a synchronous request with specified payload length on the stack, -+ * fully initializes it via the provided request specification, submits it, -+ * and finally waits for its completion before returning its status. This -+ * helper macro essentially allocates the request message buffer on the stack -+ * and then calls ssam_request_sync_with_buffer(). -+ * -+ * Note: The @payload_len parameter specifies the maximum payload length, used -+ * for buffer allocation. The actual payload length may be smaller. -+ * -+ * Return: Returns the status of the request or any failure during setup, i.e. -+ * zero on success and a negative value on failure. -+ */ -+#define ssam_request_sync_onstack(ctrl, rqst, rsp, payload_len) \ -+ ({ \ -+ u8 __data[SSH_COMMAND_MESSAGE_LENGTH(payload_len)]; \ -+ struct ssam_span __buf = { &__data[0], ARRAY_SIZE(__data) }; \ -+ \ -+ ssam_request_sync_with_buffer(ctrl, rqst, rsp, &__buf); \ -+ }) -+ -+/** -+ * __ssam_retry - Retry request in case of I/O errors or timeouts. -+ * @request: The request function to execute. Must return an integer. -+ * @n: Number of tries. -+ * @args: Arguments for the request function. -+ * -+ * Executes the given request function, i.e. calls @request. In case the -+ * request returns %-EREMOTEIO (indicates I/O error) or %-ETIMEDOUT (request -+ * or underlying packet timed out), @request will be re-executed again, up to -+ * @n times in total. -+ * -+ * Return: Returns the return value of the last execution of @request. -+ */ -+#define __ssam_retry(request, n, args...) \ -+ ({ \ -+ int __i, __s = 0; \ -+ \ -+ for (__i = (n); __i > 0; __i--) { \ -+ __s = request(args); \ -+ if (__s != -ETIMEDOUT && __s != -EREMOTEIO) \ -+ break; \ -+ } \ -+ __s; \ -+ }) -+ -+/** -+ * ssam_retry - Retry request in case of I/O errors or timeouts up to three -+ * times in total. -+ * @request: The request function to execute. Must return an integer. -+ * @args: Arguments for the request function. -+ * -+ * Executes the given request function, i.e. calls @request. In case the -+ * request returns %-EREMOTEIO (indicates I/O error) or -%ETIMEDOUT (request -+ * or underlying packet timed out), @request will be re-executed again, up to -+ * three times in total. -+ * -+ * See __ssam_retry() for a more generic macro for this purpose. -+ * -+ * Return: Returns the return value of the last execution of @request. -+ */ -+#define ssam_retry(request, args...) \ -+ __ssam_retry(request, 3, args) -+ -+/** -+ * struct ssam_request_spec - Blue-print specification of SAM request. -+ * @target_category: Category of the request's target. See &enum ssam_ssh_tc. -+ * @target_id: ID of the request's target. -+ * @command_id: Command ID of the request. -+ * @instance_id: Instance ID of the request's target. -+ * @flags: Flags for the request. See &enum ssam_request_flags. -+ * -+ * Blue-print specification for a SAM request. This struct describes the -+ * unique static parameters of a request (i.e. type) without specifying any of -+ * its instance-specific data (e.g. payload). It is intended to be used as base -+ * for defining simple request functions via the -+ * ``SSAM_DEFINE_SYNC_REQUEST_x()`` family of macros. -+ */ -+struct ssam_request_spec { -+ u8 target_category; -+ u8 target_id; -+ u8 command_id; -+ u8 instance_id; -+ u8 flags; -+}; -+ -+/** -+ * struct ssam_request_spec_md - Blue-print specification for multi-device SAM -+ * request. -+ * @target_category: Category of the request's target. See &enum ssam_ssh_tc. -+ * @command_id: Command ID of the request. -+ * @flags: Flags for the request. See &enum ssam_request_flags. -+ * -+ * Blue-print specification for a multi-device SAM request, i.e. a request -+ * that is applicable to multiple device instances, described by their -+ * individual target and instance IDs. This struct describes the unique static -+ * parameters of a request (i.e. type) without specifying any of its -+ * instance-specific data (e.g. payload) and without specifying any of its -+ * device specific IDs (i.e. target and instance ID). It is intended to be -+ * used as base for defining simple multi-device request functions via the -+ * ``SSAM_DEFINE_SYNC_REQUEST_MD_x()`` and ``SSAM_DEFINE_SYNC_REQUEST_CL_x()`` -+ * families of macros. -+ */ -+struct ssam_request_spec_md { -+ u8 target_category; -+ u8 command_id; -+ u8 flags; -+}; -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_N() - Define synchronous SAM request function -+ * with neither argument nor return value. -+ * @name: Name of the generated function. -+ * @spec: Specification (&struct ssam_request_spec) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request having neither argument nor return value. The -+ * generated function takes care of setting up the request struct and buffer -+ * allocation, as well as execution of the request itself, returning once the -+ * request has been fully completed. The required transport buffer will be -+ * allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl)``, returning the status of the request, which is -+ * zero on success and negative on failure. The ``ctrl`` parameter is the -+ * controller via which the request is being sent. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_N(name, spec...) \ -+ static int name(struct ssam_controller *ctrl) \ -+ { \ -+ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = s.target_id; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = s.instance_id; \ -+ rqst.flags = s.flags; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, 0); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_W() - Define synchronous SAM request function with -+ * argument. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @spec: Specification (&struct ssam_request_spec) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking an argument of type @atype and having no -+ * return value. The generated function takes care of setting up the request -+ * struct, buffer allocation, as well as execution of the request itself, -+ * returning once the request has been fully completed. The required transport -+ * buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, const atype *arg)``, returning the status of the -+ * request, which is zero on success and negative on failure. The ``ctrl`` -+ * parameter is the controller via which the request is sent. The request -+ * argument is specified via the ``arg`` pointer. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_W(name, atype, spec...) \ -+ static int name(struct ssam_controller *ctrl, const atype *arg) \ -+ { \ -+ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = s.target_id; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = s.instance_id; \ -+ rqst.flags = s.flags; \ -+ rqst.length = sizeof(atype); \ -+ rqst.payload = (u8 *)arg; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, \ -+ sizeof(atype)); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_R() - Define synchronous SAM request function with -+ * return value. -+ * @name: Name of the generated function. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking no argument but having a return value of -+ * type @rtype. The generated function takes care of setting up the request -+ * and response structs, buffer allocation, as well as execution of the -+ * request itself, returning once the request has been fully completed. The -+ * required transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, rtype *ret)``, returning the status of the request, -+ * which is zero on success and negative on failure. The ``ctrl`` parameter is -+ * the controller via which the request is sent. The request's return value is -+ * written to the memory pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_R(name, rtype, spec...) \ -+ static int name(struct ssam_controller *ctrl, rtype *ret) \ -+ { \ -+ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ -+ struct ssam_request rqst; \ -+ struct ssam_response rsp; \ -+ int status; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = s.target_id; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = s.instance_id; \ -+ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ rsp.capacity = sizeof(rtype); \ -+ rsp.length = 0; \ -+ rsp.pointer = (u8 *)ret; \ -+ \ -+ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, 0); \ -+ if (status) \ -+ return status; \ -+ \ -+ if (rsp.length != sizeof(rtype)) { \ -+ struct device *dev = ssam_controller_device(ctrl); \ -+ dev_err(dev, \ -+ "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ -+ sizeof(rtype), rsp.length, rqst.target_category,\ -+ rqst.command_id); \ -+ return -EIO; \ -+ } \ -+ \ -+ return 0; \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_MD_N() - Define synchronous multi-device SAM -+ * request function with neither argument nor return value. -+ * @name: Name of the generated function. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request having neither argument nor return value. Device -+ * specifying parameters are not hard-coded, but instead must be provided to -+ * the function. The generated function takes care of setting up the request -+ * struct, buffer allocation, as well as execution of the request itself, -+ * returning once the request has been fully completed. The required transport -+ * buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, u8 tid, u8 iid)``, returning the status of the -+ * request, which is zero on success and negative on failure. The ``ctrl`` -+ * parameter is the controller via which the request is sent, ``tid`` the -+ * target ID for the request, and ``iid`` the instance ID. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_MD_N(name, spec...) \ -+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \ -+ { \ -+ struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = tid; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = iid; \ -+ rqst.flags = s.flags; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, 0); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_MD_W() - Define synchronous multi-device SAM -+ * request function with argument. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking an argument of type @atype and having no -+ * return value. Device specifying parameters are not hard-coded, but instead -+ * must be provided to the function. The generated function takes care of -+ * setting up the request struct, buffer allocation, as well as execution of -+ * the request itself, returning once the request has been fully completed. -+ * The required transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the -+ * status of the request, which is zero on success and negative on failure. -+ * The ``ctrl`` parameter is the controller via which the request is sent, -+ * ``tid`` the target ID for the request, and ``iid`` the instance ID. The -+ * request argument is specified via the ``arg`` pointer. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_MD_W(name, atype, spec...) \ -+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg) \ -+ { \ -+ struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = tid; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = iid; \ -+ rqst.flags = s.flags; \ -+ rqst.length = sizeof(atype); \ -+ rqst.payload = (u8 *)arg; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, \ -+ sizeof(atype)); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_MD_R() - Define synchronous multi-device SAM -+ * request function with return value. -+ * @name: Name of the generated function. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking no argument but having a return value of -+ * type @rtype. Device specifying parameters are not hard-coded, but instead -+ * must be provided to the function. The generated function takes care of -+ * setting up the request and response structs, buffer allocation, as well as -+ * execution of the request itself, returning once the request has been fully -+ * completed. The required transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status -+ * of the request, which is zero on success and negative on failure. The -+ * ``ctrl`` parameter is the controller via which the request is sent, ``tid`` -+ * the target ID for the request, and ``iid`` the instance ID. The request's -+ * return value is written to the memory pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_MD_R(name, rtype, spec...) \ -+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \ -+ { \ -+ struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ -+ struct ssam_request rqst; \ -+ struct ssam_response rsp; \ -+ int status; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = tid; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = iid; \ -+ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ rsp.capacity = sizeof(rtype); \ -+ rsp.length = 0; \ -+ rsp.pointer = (u8 *)ret; \ -+ \ -+ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, 0); \ -+ if (status) \ -+ return status; \ -+ \ -+ if (rsp.length != sizeof(rtype)) { \ -+ struct device *dev = ssam_controller_device(ctrl); \ -+ dev_err(dev, \ -+ "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ -+ sizeof(rtype), rsp.length, rqst.target_category,\ -+ rqst.command_id); \ -+ return -EIO; \ -+ } \ -+ \ -+ return 0; \ -+ } -+ -+ -+/* -- Event notifier/callbacks. --------------------------------------------- */ -+ -+#define SSAM_NOTIF_STATE_SHIFT 2 -+#define SSAM_NOTIF_STATE_MASK ((1 << SSAM_NOTIF_STATE_SHIFT) - 1) -+ -+/** -+ * enum ssam_notif_flags - Flags used in return values from SSAM notifier -+ * callback functions. -+ * -+ * @SSAM_NOTIF_HANDLED: -+ * Indicates that the notification has been handled. This flag should be -+ * set by the handler if the handler can act/has acted upon the event -+ * provided to it. This flag should not be set if the handler is not a -+ * primary handler intended for the provided event. -+ * -+ * If this flag has not been set by any handler after the notifier chain -+ * has been traversed, a warning will be emitted, stating that the event -+ * has not been handled. -+ * -+ * @SSAM_NOTIF_STOP: -+ * Indicates that the notifier traversal should stop. If this flag is -+ * returned from a notifier callback, notifier chain traversal will -+ * immediately stop and any remaining notifiers will not be called. This -+ * flag is automatically set when ssam_notifier_from_errno() is called -+ * with a negative error value. -+ */ -+enum ssam_notif_flags { -+ SSAM_NOTIF_HANDLED = BIT(0), -+ SSAM_NOTIF_STOP = BIT(1), -+}; -+ -+struct ssam_event_notifier; -+ -+typedef u32 (*ssam_notifier_fn_t)(struct ssam_event_notifier *nf, -+ const struct ssam_event *event); -+ -+/** -+ * struct ssam_notifier_block - Base notifier block for SSAM event -+ * notifications. -+ * @node: The node for the list of notifiers. -+ * @fn: The callback function of this notifier. This function takes the -+ * respective notifier block and event as input and should return -+ * a notifier value, which can either be obtained from the flags -+ * provided in &enum ssam_notif_flags, converted from a standard -+ * error value via ssam_notifier_from_errno(), or a combination of -+ * both (e.g. ``ssam_notifier_from_errno(e) | SSAM_NOTIF_HANDLED``). -+ * @priority: Priority value determining the order in which notifier callbacks -+ * will be called. A higher value means higher priority, i.e. the -+ * associated callback will be executed earlier than other (lower -+ * priority) callbacks. -+ */ -+struct ssam_notifier_block { -+ struct list_head node; -+ ssam_notifier_fn_t fn; -+ int priority; -+}; -+ -+/** -+ * ssam_notifier_from_errno() - Convert standard error value to notifier -+ * return code. -+ * @err: The error code to convert, must be negative (in case of failure) or -+ * zero (in case of success). -+ * -+ * Return: Returns the notifier return value obtained by converting the -+ * specified @err value. In case @err is negative, the %SSAM_NOTIF_STOP flag -+ * will be set, causing notifier call chain traversal to abort. -+ */ -+static inline u32 ssam_notifier_from_errno(int err) -+{ -+ if (WARN_ON(err > 0) || err == 0) -+ return 0; -+ else -+ return ((-err) << SSAM_NOTIF_STATE_SHIFT) | SSAM_NOTIF_STOP; -+} -+ -+/** -+ * ssam_notifier_to_errno() - Convert notifier return code to standard error -+ * value. -+ * @ret: The notifier return value to convert. -+ * -+ * Return: Returns the negative error value encoded in @ret or zero if @ret -+ * indicates success. -+ */ -+static inline int ssam_notifier_to_errno(u32 ret) -+{ -+ return -(ret >> SSAM_NOTIF_STATE_SHIFT); -+} -+ -+ -+/* -- Event/notification registry. ------------------------------------------ */ -+ -+/** -+ * struct ssam_event_registry - Registry specification used for enabling events. -+ * @target_category: Target category for the event registry requests. -+ * @target_id: Target ID for the event registry requests. -+ * @cid_enable: Command ID for the event-enable request. -+ * @cid_disable: Command ID for the event-disable request. -+ * -+ * This struct describes a SAM event registry via the minimal collection of -+ * SAM IDs specifying the requests to use for enabling and disabling an event. -+ * The individual event to be enabled/disabled itself is specified via &struct -+ * ssam_event_id. -+ */ -+struct ssam_event_registry { -+ u8 target_category; -+ u8 target_id; -+ u8 cid_enable; -+ u8 cid_disable; -+}; -+ -+/** -+ * struct ssam_event_id - Unique event ID used for enabling events. -+ * @target_category: Target category of the event source. -+ * @instance: Instance ID of the event source. -+ * -+ * This struct specifies the event to be enabled/disabled via an externally -+ * provided registry. It does not specify the registry to be used itself, this -+ * is done via &struct ssam_event_registry. -+ */ -+struct ssam_event_id { -+ u8 target_category; -+ u8 instance; -+}; -+ -+/** -+ * enum ssam_event_mask - Flags specifying how events are matched to notifiers. -+ * -+ * @SSAM_EVENT_MASK_NONE: -+ * Run the callback for any event with matching target category. Do not -+ * do any additional filtering. -+ * -+ * @SSAM_EVENT_MASK_TARGET: -+ * In addition to filtering by target category, only execute the notifier -+ * callback for events with a target ID matching to the one of the -+ * registry used for enabling/disabling the event. -+ * -+ * @SSAM_EVENT_MASK_INSTANCE: -+ * In addition to filtering by target category, only execute the notifier -+ * callback for events with an instance ID matching to the instance ID -+ * used when enabling the event. -+ * -+ * @SSAM_EVENT_MASK_STRICT: -+ * Do all the filtering above. -+ */ -+enum ssam_event_mask { -+ SSAM_EVENT_MASK_TARGET = BIT(0), -+ SSAM_EVENT_MASK_INSTANCE = BIT(1), -+ -+ SSAM_EVENT_MASK_NONE = 0, -+ SSAM_EVENT_MASK_STRICT = -+ SSAM_EVENT_MASK_TARGET -+ | SSAM_EVENT_MASK_INSTANCE, -+}; -+ -+/** -+ * SSAM_EVENT_REGISTRY() - Define a new event registry. -+ * @tc: Target category for the event registry requests. -+ * @tid: Target ID for the event registry requests. -+ * @cid_en: Command ID for the event-enable request. -+ * @cid_dis: Command ID for the event-disable request. -+ * -+ * Return: Returns the &struct ssam_event_registry specified by the given -+ * parameters. -+ */ -+#define SSAM_EVENT_REGISTRY(tc, tid, cid_en, cid_dis) \ -+ ((struct ssam_event_registry) { \ -+ .target_category = (tc), \ -+ .target_id = (tid), \ -+ .cid_enable = (cid_en), \ -+ .cid_disable = (cid_dis), \ -+ }) -+ -+#define SSAM_EVENT_REGISTRY_SAM \ -+ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_SAM, 0x01, 0x0b, 0x0c) -+ -+#define SSAM_EVENT_REGISTRY_KIP \ -+ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_KIP, 0x02, 0x27, 0x28) -+ -+#define SSAM_EVENT_REGISTRY_REG \ -+ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02) -+ -+/** -+ * enum ssam_event_notifier_flags - Flags for event notifiers. -+ * @SSAM_EVENT_NOTIFIER_OBSERVER: -+ * The corresponding notifier acts as observer. Registering a notifier -+ * with this flag set will not attempt to enable any event. Equally, -+ * unregistering will not attempt to disable any event. Note that a -+ * notifier with this flag may not even correspond to a certain event at -+ * all, only to a specific event target category. Event matching will not -+ * be influenced by this flag. -+ */ -+enum ssam_event_notifier_flags { -+ SSAM_EVENT_NOTIFIER_OBSERVER = BIT(0), -+}; -+ -+/** -+ * struct ssam_event_notifier - Notifier block for SSAM events. -+ * @base: The base notifier block with callback function and priority. -+ * @event: The event for which this block will receive notifications. -+ * @event.reg: Registry via which the event will be enabled/disabled. -+ * @event.id: ID specifying the event. -+ * @event.mask: Flags determining how events are matched to the notifier. -+ * @event.flags: Flags used for enabling the event. -+ * @flags: Notifier flags (see &enum ssam_event_notifier_flags). -+ */ -+struct ssam_event_notifier { -+ struct ssam_notifier_block base; -+ -+ struct { -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ enum ssam_event_mask mask; -+ u8 flags; -+ } event; -+ -+ unsigned long flags; -+}; -+ -+int ssam_notifier_register(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n); -+ -+int ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n); -+ -+int ssam_controller_event_enable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags); -+ -+int ssam_controller_event_disable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags); -+ -+#endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */ -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -new file mode 100644 -index 000000000000..f636c5310321 ---- /dev/null -+++ b/include/linux/surface_aggregator/device.h -@@ -0,0 +1,424 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Surface System Aggregator Module (SSAM) bus and client-device subsystem. -+ * -+ * Main interface for the surface-aggregator bus, surface-aggregator client -+ * devices, and respective drivers building on top of the SSAM controller. -+ * Provides support for non-platform/non-ACPI SSAM clients via dedicated -+ * subsystem. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H -+#define _LINUX_SURFACE_AGGREGATOR_DEVICE_H -+ -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- Surface System Aggregator Module bus. --------------------------------- */ -+ -+/** -+ * enum ssam_device_domain - SAM device domain. -+ * @SSAM_DOMAIN_VIRTUAL: Virtual device. -+ * @SSAM_DOMAIN_SERIALHUB: Physical device connected via Surface Serial Hub. -+ */ -+enum ssam_device_domain { -+ SSAM_DOMAIN_VIRTUAL = 0x00, -+ SSAM_DOMAIN_SERIALHUB = 0x01, -+}; -+ -+/** -+ * enum ssam_virtual_tc - Target categories for the virtual SAM domain. -+ * @SSAM_VIRTUAL_TC_HUB: Device hub category. -+ */ -+enum ssam_virtual_tc { -+ SSAM_VIRTUAL_TC_HUB = 0x00, -+}; -+ -+/** -+ * struct ssam_device_uid - Unique identifier for SSAM device. -+ * @domain: Domain of the device. -+ * @category: Target category of the device. -+ * @target: Target ID of the device. -+ * @instance: Instance ID of the device. -+ * @function: Sub-function of the device. This field can be used to split a -+ * single SAM device into multiple virtual subdevices to separate -+ * different functionality of that device and allow one driver per -+ * such functionality. -+ */ -+struct ssam_device_uid { -+ u8 domain; -+ u8 category; -+ u8 target; -+ u8 instance; -+ u8 function; -+}; -+ -+/* -+ * Special values for device matching. -+ * -+ * These values are intended to be used with SSAM_DEVICE(), SSAM_VDEV(), and -+ * SSAM_SDEV() exclusively. Specifically, they are used to initialize the -+ * match_flags member of the device ID structure. Do not use them directly -+ * with struct ssam_device_id or struct ssam_device_uid. -+ */ -+#define SSAM_ANY_TID 0xffff -+#define SSAM_ANY_IID 0xffff -+#define SSAM_ANY_FUN 0xffff -+ -+/** -+ * SSAM_DEVICE() - Initialize a &struct ssam_device_id with the given -+ * parameters. -+ * @d: Domain of the device. -+ * @cat: Target category of the device. -+ * @tid: Target ID of the device. -+ * @iid: Instance ID of the device. -+ * @fun: Sub-function of the device. -+ * -+ * Initializes a &struct ssam_device_id with the given parameters. See &struct -+ * ssam_device_uid for details regarding the parameters. The special values -+ * %SSAM_ANY_TID, %SSAM_ANY_IID, and %SSAM_ANY_FUN can be used to specify that -+ * matching should ignore target ID, instance ID, and/or sub-function, -+ * respectively. This macro initializes the ``match_flags`` field based on the -+ * given parameters. -+ * -+ * Note: The parameters @d and @cat must be valid &u8 values, the parameters -+ * @tid, @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, -+ * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not -+ * allowed. -+ */ -+#define SSAM_DEVICE(d, cat, tid, iid, fun) \ -+ .match_flags = (((tid) != SSAM_ANY_TID) ? SSAM_MATCH_TARGET : 0) \ -+ | (((iid) != SSAM_ANY_IID) ? SSAM_MATCH_INSTANCE : 0) \ -+ | (((fun) != SSAM_ANY_FUN) ? SSAM_MATCH_FUNCTION : 0), \ -+ .domain = d, \ -+ .category = cat, \ -+ .target = __builtin_choose_expr((tid) != SSAM_ANY_TID, (tid), 0), \ -+ .instance = __builtin_choose_expr((iid) != SSAM_ANY_IID, (iid), 0), \ -+ .function = __builtin_choose_expr((fun) != SSAM_ANY_FUN, (fun), 0) -+ -+/** -+ * SSAM_VDEV() - Initialize a &struct ssam_device_id as virtual device with -+ * the given parameters. -+ * @cat: Target category of the device. -+ * @tid: Target ID of the device. -+ * @iid: Instance ID of the device. -+ * @fun: Sub-function of the device. -+ * -+ * Initializes a &struct ssam_device_id with the given parameters in the -+ * virtual domain. See &struct ssam_device_uid for details regarding the -+ * parameters. The special values %SSAM_ANY_TID, %SSAM_ANY_IID, and -+ * %SSAM_ANY_FUN can be used to specify that matching should ignore target ID, -+ * instance ID, and/or sub-function, respectively. This macro initializes the -+ * ``match_flags`` field based on the given parameters. -+ * -+ * Note: The parameter @cat must be a valid &u8 value, the parameters @tid, -+ * @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, -+ * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not -+ * allowed. -+ */ -+#define SSAM_VDEV(cat, tid, iid, fun) \ -+ SSAM_DEVICE(SSAM_DOMAIN_VIRTUAL, SSAM_VIRTUAL_TC_##cat, tid, iid, fun) -+ -+/** -+ * SSAM_SDEV() - Initialize a &struct ssam_device_id as physical SSH device -+ * with the given parameters. -+ * @cat: Target category of the device. -+ * @tid: Target ID of the device. -+ * @iid: Instance ID of the device. -+ * @fun: Sub-function of the device. -+ * -+ * Initializes a &struct ssam_device_id with the given parameters in the SSH -+ * domain. See &struct ssam_device_uid for details regarding the parameters. -+ * The special values %SSAM_ANY_TID, %SSAM_ANY_IID, and %SSAM_ANY_FUN can be -+ * used to specify that matching should ignore target ID, instance ID, and/or -+ * sub-function, respectively. This macro initializes the ``match_flags`` -+ * field based on the given parameters. -+ * -+ * Note: The parameter @cat must be a valid &u8 value, the parameters @tid, -+ * @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, -+ * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not -+ * allowed. -+ */ -+#define SSAM_SDEV(cat, tid, iid, fun) \ -+ SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) -+ -+/** -+ * struct ssam_device - SSAM client device. -+ * @dev: Driver model representation of the device. -+ * @ctrl: SSAM controller managing this device. -+ * @uid: UID identifying the device. -+ */ -+struct ssam_device { -+ struct device dev; -+ struct ssam_controller *ctrl; -+ -+ struct ssam_device_uid uid; -+}; -+ -+/** -+ * struct ssam_device_driver - SSAM client device driver. -+ * @driver: Base driver model structure. -+ * @match_table: Match table specifying which devices the driver should bind to. -+ * @probe: Called when the driver is being bound to a device. -+ * @remove: Called when the driver is being unbound from the device. -+ */ -+struct ssam_device_driver { -+ struct device_driver driver; -+ -+ const struct ssam_device_id *match_table; -+ -+ int (*probe)(struct ssam_device *sdev); -+ void (*remove)(struct ssam_device *sdev); -+}; -+ -+extern struct bus_type ssam_bus_type; -+extern const struct device_type ssam_device_type; -+ -+/** -+ * is_ssam_device() - Check if the given device is a SSAM client device. -+ * @d: The device to test the type of. -+ * -+ * Return: Returns %true if the specified device is of type &struct -+ * ssam_device, i.e. the device type points to %ssam_device_type, and %false -+ * otherwise. -+ */ -+static inline bool is_ssam_device(struct device *d) -+{ -+ return d->type == &ssam_device_type; -+} -+ -+/** -+ * to_ssam_device() - Casts the given device to a SSAM client device. -+ * @d: The device to cast. -+ * -+ * Casts the given &struct device to a &struct ssam_device. The caller has to -+ * ensure that the given device is actually enclosed in a &struct ssam_device, -+ * e.g. by calling is_ssam_device(). -+ * -+ * Return: Returns a pointer to the &struct ssam_device wrapping the given -+ * device @d. -+ */ -+static inline struct ssam_device *to_ssam_device(struct device *d) -+{ -+ return container_of(d, struct ssam_device, dev); -+} -+ -+/** -+ * to_ssam_device_driver() - Casts the given device driver to a SSAM client -+ * device driver. -+ * @d: The driver to cast. -+ * -+ * Casts the given &struct device_driver to a &struct ssam_device_driver. The -+ * caller has to ensure that the given driver is actually enclosed in a -+ * &struct ssam_device_driver. -+ * -+ * Return: Returns the pointer to the &struct ssam_device_driver wrapping the -+ * given device driver @d. -+ */ -+static inline -+struct ssam_device_driver *to_ssam_device_driver(struct device_driver *d) -+{ -+ return container_of(d, struct ssam_device_driver, driver); -+} -+ -+const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table, -+ const struct ssam_device_uid uid); -+ -+const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev); -+ -+const void *ssam_device_get_match_data(const struct ssam_device *dev); -+ -+struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, -+ struct ssam_device_uid uid); -+ -+int ssam_device_add(struct ssam_device *sdev); -+void ssam_device_remove(struct ssam_device *sdev); -+ -+/** -+ * ssam_device_get() - Increment reference count of SSAM client device. -+ * @sdev: The device to increment the reference count of. -+ * -+ * Increments the reference count of the given SSAM client device by -+ * incrementing the reference count of the enclosed &struct device via -+ * get_device(). -+ * -+ * See ssam_device_put() for the counter-part of this function. -+ * -+ * Return: Returns the device provided as input. -+ */ -+static inline struct ssam_device *ssam_device_get(struct ssam_device *sdev) -+{ -+ return sdev ? to_ssam_device(get_device(&sdev->dev)) : NULL; -+} -+ -+/** -+ * ssam_device_put() - Decrement reference count of SSAM client device. -+ * @sdev: The device to decrement the reference count of. -+ * -+ * Decrements the reference count of the given SSAM client device by -+ * decrementing the reference count of the enclosed &struct device via -+ * put_device(). -+ * -+ * See ssam_device_get() for the counter-part of this function. -+ */ -+static inline void ssam_device_put(struct ssam_device *sdev) -+{ -+ if (sdev) -+ put_device(&sdev->dev); -+} -+ -+/** -+ * ssam_device_get_drvdata() - Get driver-data of SSAM client device. -+ * @sdev: The device to get the driver-data from. -+ * -+ * Return: Returns the driver-data of the given device, previously set via -+ * ssam_device_set_drvdata(). -+ */ -+static inline void *ssam_device_get_drvdata(struct ssam_device *sdev) -+{ -+ return dev_get_drvdata(&sdev->dev); -+} -+ -+/** -+ * ssam_device_set_drvdata() - Set driver-data of SSAM client device. -+ * @sdev: The device to set the driver-data of. -+ * @data: The data to set the device's driver-data pointer to. -+ */ -+static inline void ssam_device_set_drvdata(struct ssam_device *sdev, void *data) -+{ -+ dev_set_drvdata(&sdev->dev, data); -+} -+ -+int __ssam_device_driver_register(struct ssam_device_driver *d, struct module *o); -+void ssam_device_driver_unregister(struct ssam_device_driver *d); -+ -+/** -+ * ssam_device_driver_register() - Register a SSAM client device driver. -+ * @drv: The driver to register. -+ */ -+#define ssam_device_driver_register(drv) \ -+ __ssam_device_driver_register(drv, THIS_MODULE) -+ -+/** -+ * module_ssam_device_driver() - Helper macro for SSAM device driver -+ * registration. -+ * @drv: The driver managed by this module. -+ * -+ * Helper macro to register a SSAM device driver via module_init() and -+ * module_exit(). This macro may only be used once per module and replaces the -+ * aforementioned definitions. -+ */ -+#define module_ssam_device_driver(drv) \ -+ module_driver(drv, ssam_device_driver_register, \ -+ ssam_device_driver_unregister) -+ -+ -+/* -- Helpers for client-device requests. ----------------------------------- */ -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_CL_N() - Define synchronous client-device SAM -+ * request function with neither argument nor return value. -+ * @name: Name of the generated function. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request having neither argument nor return value. Device -+ * specifying parameters are not hard-coded, but instead are provided via the -+ * client device, specifically its UID, supplied when calling this function. -+ * The generated function takes care of setting up the request struct, buffer -+ * allocation, as well as execution of the request itself, returning once the -+ * request has been fully completed. The required transport buffer will be -+ * allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct ssam_device -+ * *sdev)``, returning the status of the request, which is zero on success and -+ * negative on failure. The ``sdev`` parameter specifies both the target -+ * device of the request and by association the controller via which the -+ * request is sent. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_CL_N(name, spec...) \ -+ SSAM_DEFINE_SYNC_REQUEST_MD_N(__raw_##name, spec) \ -+ static int name(struct ssam_device *sdev) \ -+ { \ -+ return __raw_##name(sdev->ctrl, sdev->uid.target, \ -+ sdev->uid.instance); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_CL_W() - Define synchronous client-device SAM -+ * request function with argument. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking an argument of type @atype and having no -+ * return value. Device specifying parameters are not hard-coded, but instead -+ * are provided via the client device, specifically its UID, supplied when -+ * calling this function. The generated function takes care of setting up the -+ * request struct, buffer allocation, as well as execution of the request -+ * itself, returning once the request has been fully completed. The required -+ * transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct ssam_device -+ * *sdev, const atype *arg)``, returning the status of the request, which is -+ * zero on success and negative on failure. The ``sdev`` parameter specifies -+ * both the target device of the request and by association the controller via -+ * which the request is sent. The request's argument is specified via the -+ * ``arg`` pointer. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_CL_W(name, atype, spec...) \ -+ SSAM_DEFINE_SYNC_REQUEST_MD_W(__raw_##name, atype, spec) \ -+ static int name(struct ssam_device *sdev, const atype *arg) \ -+ { \ -+ return __raw_##name(sdev->ctrl, sdev->uid.target, \ -+ sdev->uid.instance, arg); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_CL_R() - Define synchronous client-device SAM -+ * request function with return value. -+ * @name: Name of the generated function. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking no argument but having a return value of -+ * type @rtype. Device specifying parameters are not hard-coded, but instead -+ * are provided via the client device, specifically its UID, supplied when -+ * calling this function. The generated function takes care of setting up the -+ * request struct, buffer allocation, as well as execution of the request -+ * itself, returning once the request has been fully completed. The required -+ * transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct ssam_device -+ * *sdev, rtype *ret)``, returning the status of the request, which is zero on -+ * success and negative on failure. The ``sdev`` parameter specifies both the -+ * target device of the request and by association the controller via which -+ * the request is sent. The request's return value is written to the memory -+ * pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \ -+ SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \ -+ static int name(struct ssam_device *sdev, rtype *ret) \ -+ { \ -+ return __raw_##name(sdev->ctrl, sdev->uid.target, \ -+ sdev->uid.instance, ret); \ -+ } -+ -+#endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ -diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h -new file mode 100644 -index 000000000000..c3de43edcffa ---- /dev/null -+++ b/include/linux/surface_aggregator/serial_hub.h -@@ -0,0 +1,672 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Surface Serial Hub (SSH) protocol and communication interface. -+ * -+ * Lower-level communication layers and SSH protocol definitions for the -+ * Surface System Aggregator Module (SSAM). Provides the interface for basic -+ * packet- and request-based communication with the SSAM EC via SSH. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H -+#define _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H -+ -+#include -+#include -+#include -+#include -+#include -+ -+ -+/* -- Data structures for SAM-over-SSH communication. ----------------------- */ -+ -+/** -+ * enum ssh_frame_type - Frame types for SSH frames. -+ * -+ * @SSH_FRAME_TYPE_DATA_SEQ: -+ * Indicates a data frame, followed by a payload with the length specified -+ * in the ``struct ssh_frame.len`` field. This frame is sequenced, meaning -+ * that an ACK is required. -+ * -+ * @SSH_FRAME_TYPE_DATA_NSQ: -+ * Same as %SSH_FRAME_TYPE_DATA_SEQ, but unsequenced, meaning that the -+ * message does not have to be ACKed. -+ * -+ * @SSH_FRAME_TYPE_ACK: -+ * Indicates an ACK message. -+ * -+ * @SSH_FRAME_TYPE_NAK: -+ * Indicates an error response for previously sent frame. In general, this -+ * means that the frame and/or payload is malformed, e.g. a CRC is wrong. -+ * For command-type payloads, this can also mean that the command is -+ * invalid. -+ */ -+enum ssh_frame_type { -+ SSH_FRAME_TYPE_DATA_SEQ = 0x80, -+ SSH_FRAME_TYPE_DATA_NSQ = 0x00, -+ SSH_FRAME_TYPE_ACK = 0x40, -+ SSH_FRAME_TYPE_NAK = 0x04, -+}; -+ -+/** -+ * struct ssh_frame - SSH communication frame. -+ * @type: The type of the frame. See &enum ssh_frame_type. -+ * @len: The length of the frame payload directly following the CRC for this -+ * frame. Does not include the final CRC for that payload. -+ * @seq: The sequence number for this message/exchange. -+ */ -+struct ssh_frame { -+ u8 type; -+ __le16 len; -+ u8 seq; -+} __packed; -+ -+static_assert(sizeof(struct ssh_frame) == 4); -+ -+/* -+ * SSH_FRAME_MAX_PAYLOAD_SIZE - Maximum SSH frame payload length in bytes. -+ * -+ * This is the physical maximum length of the protocol. Implementations may -+ * set a more constrained limit. -+ */ -+#define SSH_FRAME_MAX_PAYLOAD_SIZE U16_MAX -+ -+/** -+ * enum ssh_payload_type - Type indicator for the SSH payload. -+ * @SSH_PLD_TYPE_CMD: The payload is a command structure with optional command -+ * payload. -+ */ -+enum ssh_payload_type { -+ SSH_PLD_TYPE_CMD = 0x80, -+}; -+ -+/** -+ * struct ssh_command - Payload of a command-type frame. -+ * @type: The type of the payload. See &enum ssh_payload_type. Should be -+ * SSH_PLD_TYPE_CMD for this struct. -+ * @tc: Command target category. -+ * @tid_out: Output target ID. Should be zero if this an incoming (EC to host) -+ * message. -+ * @tid_in: Input target ID. Should be zero if this is an outgoing (host to -+ * EC) message. -+ * @iid: Instance ID. -+ * @rqid: Request ID. Used to match requests with responses and differentiate -+ * between responses and events. -+ * @cid: Command ID. -+ */ -+struct ssh_command { -+ u8 type; -+ u8 tc; -+ u8 tid_out; -+ u8 tid_in; -+ u8 iid; -+ __le16 rqid; -+ u8 cid; -+} __packed; -+ -+static_assert(sizeof(struct ssh_command) == 8); -+ -+/* -+ * SSH_COMMAND_MAX_PAYLOAD_SIZE - Maximum SSH command payload length in bytes. -+ * -+ * This is the physical maximum length of the protocol. Implementations may -+ * set a more constrained limit. -+ */ -+#define SSH_COMMAND_MAX_PAYLOAD_SIZE \ -+ (SSH_FRAME_MAX_PAYLOAD_SIZE - sizeof(struct ssh_command)) -+ -+/* -+ * SSH_MSG_LEN_BASE - Base-length of a SSH message. -+ * -+ * This is the minimum number of bytes required to form a message. The actual -+ * message length is SSH_MSG_LEN_BASE plus the length of the frame payload. -+ */ -+#define SSH_MSG_LEN_BASE (sizeof(struct ssh_frame) + 3ull * sizeof(u16)) -+ -+/* -+ * SSH_MSG_LEN_CTRL - Length of a SSH control message. -+ * -+ * This is the length of a SSH control message, which is equal to a SSH -+ * message without any payload. -+ */ -+#define SSH_MSG_LEN_CTRL SSH_MSG_LEN_BASE -+ -+/** -+ * SSH_MESSAGE_LENGTH() - Compute length of SSH message. -+ * @payload_size: Length of the payload inside the SSH frame. -+ * -+ * Return: Returns the length of a SSH message with payload of specified size. -+ */ -+#define SSH_MESSAGE_LENGTH(payload_size) (SSH_MSG_LEN_BASE + (payload_size)) -+ -+/** -+ * SSH_COMMAND_MESSAGE_LENGTH() - Compute length of SSH command message. -+ * @payload_size: Length of the command payload. -+ * -+ * Return: Returns the length of a SSH command message with command payload of -+ * specified size. -+ */ -+#define SSH_COMMAND_MESSAGE_LENGTH(payload_size) \ -+ SSH_MESSAGE_LENGTH(sizeof(struct ssh_command) + (payload_size)) -+ -+/** -+ * SSH_MSGOFFSET_FRAME() - Compute offset in SSH message to specified field in -+ * frame. -+ * @field: The field for which the offset should be computed. -+ * -+ * Return: Returns the offset of the specified &struct ssh_frame field in the -+ * raw SSH message data as. Takes SYN bytes (u16) preceding the frame into -+ * account. -+ */ -+#define SSH_MSGOFFSET_FRAME(field) \ -+ (sizeof(u16) + offsetof(struct ssh_frame, field)) -+ -+/** -+ * SSH_MSGOFFSET_COMMAND() - Compute offset in SSH message to specified field -+ * in command. -+ * @field: The field for which the offset should be computed. -+ * -+ * Return: Returns the offset of the specified &struct ssh_command field in -+ * the raw SSH message data. Takes SYN bytes (u16) preceding the frame and the -+ * frame CRC (u16) between frame and command into account. -+ */ -+#define SSH_MSGOFFSET_COMMAND(field) \ -+ (2ull * sizeof(u16) + sizeof(struct ssh_frame) \ -+ + offsetof(struct ssh_command, field)) -+ -+/* -+ * SSH_MSG_SYN - SSH message synchronization (SYN) bytes as u16. -+ */ -+#define SSH_MSG_SYN ((u16)0x55aa) -+ -+/** -+ * ssh_crc() - Compute CRC for SSH messages. -+ * @buf: The pointer pointing to the data for which the CRC should be computed. -+ * @len: The length of the data for which the CRC should be computed. -+ * -+ * Return: Returns the CRC computed on the provided data, as used for SSH -+ * messages. -+ */ -+static inline u16 ssh_crc(const u8 *buf, size_t len) -+{ -+ return crc_ccitt_false(0xffff, buf, len); -+} -+ -+/* -+ * SSH_NUM_EVENTS - The number of reserved event IDs. -+ * -+ * The number of reserved event IDs, used for registering an SSH event -+ * handler. Valid event IDs are numbers below or equal to this value, with -+ * exception of zero, which is not an event ID. Thus, this is also the -+ * absolute maximum number of event handlers that can be registered. -+ */ -+#define SSH_NUM_EVENTS 34 -+ -+/* -+ * SSH_NUM_TARGETS - The number of communication targets used in the protocol. -+ */ -+#define SSH_NUM_TARGETS 2 -+ -+/** -+ * ssh_rqid_next_valid() - Return the next valid request ID. -+ * @rqid: The current request ID. -+ * -+ * Return: Returns the next valid request ID, following the current request ID -+ * provided to this function. This function skips any request IDs reserved for -+ * events. -+ */ -+static inline u16 ssh_rqid_next_valid(u16 rqid) -+{ -+ return rqid > 0 ? rqid + 1u : rqid + SSH_NUM_EVENTS + 1u; -+} -+ -+/** -+ * ssh_rqid_to_event() - Convert request ID to its corresponding event ID. -+ * @rqid: The request ID to convert. -+ */ -+static inline u16 ssh_rqid_to_event(u16 rqid) -+{ -+ return rqid - 1u; -+} -+ -+/** -+ * ssh_rqid_is_event() - Check if given request ID is a valid event ID. -+ * @rqid: The request ID to check. -+ */ -+static inline bool ssh_rqid_is_event(u16 rqid) -+{ -+ return ssh_rqid_to_event(rqid) < SSH_NUM_EVENTS; -+} -+ -+/** -+ * ssh_tc_to_rqid() - Convert target category to its corresponding request ID. -+ * @tc: The target category to convert. -+ */ -+static inline u16 ssh_tc_to_rqid(u8 tc) -+{ -+ return tc; -+} -+ -+/** -+ * ssh_tid_to_index() - Convert target ID to its corresponding target index. -+ * @tid: The target ID to convert. -+ */ -+static inline u8 ssh_tid_to_index(u8 tid) -+{ -+ return tid - 1u; -+} -+ -+/** -+ * ssh_tid_is_valid() - Check if target ID is valid/supported. -+ * @tid: The target ID to check. -+ */ -+static inline bool ssh_tid_is_valid(u8 tid) -+{ -+ return ssh_tid_to_index(tid) < SSH_NUM_TARGETS; -+} -+ -+/** -+ * struct ssam_span - Reference to a buffer region. -+ * @ptr: Pointer to the buffer region. -+ * @len: Length of the buffer region. -+ * -+ * A reference to a (non-owned) buffer segment, consisting of pointer and -+ * length. Use of this struct indicates non-owned data, i.e. data of which the -+ * life-time is managed (i.e. it is allocated/freed) via another pointer. -+ */ -+struct ssam_span { -+ u8 *ptr; -+ size_t len; -+}; -+ -+/* -+ * Known SSH/EC target categories. -+ * -+ * List of currently known target category values; "Known" as in we know they -+ * exist and are valid on at least some device/model. Detailed functionality -+ * or the full category name is only known for some of these categories and -+ * is detailed in the respective comment below. -+ * -+ * These values and abbreviations have been extracted from strings inside the -+ * Windows driver. -+ */ -+enum ssam_ssh_tc { -+ /* Category 0x00 is invalid for EC use. */ -+ SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */ -+ SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */ -+ SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */ -+ SSAM_SSH_TC_PMC = 0x04, -+ SSAM_SSH_TC_FAN = 0x05, -+ SSAM_SSH_TC_PoM = 0x06, -+ SSAM_SSH_TC_DBG = 0x07, -+ SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */ -+ SSAM_SSH_TC_FWU = 0x09, -+ SSAM_SSH_TC_UNI = 0x0a, -+ SSAM_SSH_TC_LPC = 0x0b, -+ SSAM_SSH_TC_TCL = 0x0c, -+ SSAM_SSH_TC_SFL = 0x0d, -+ SSAM_SSH_TC_KIP = 0x0e, -+ SSAM_SSH_TC_EXT = 0x0f, -+ SSAM_SSH_TC_BLD = 0x10, -+ SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ -+ SSAM_SSH_TC_SEN = 0x12, -+ SSAM_SSH_TC_SRQ = 0x13, -+ SSAM_SSH_TC_MCU = 0x14, -+ SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */ -+ SSAM_SSH_TC_TCH = 0x16, -+ SSAM_SSH_TC_BKL = 0x17, -+ SSAM_SSH_TC_TAM = 0x18, -+ SSAM_SSH_TC_ACC = 0x19, -+ SSAM_SSH_TC_UFI = 0x1a, -+ SSAM_SSH_TC_USC = 0x1b, -+ SSAM_SSH_TC_PEN = 0x1c, -+ SSAM_SSH_TC_VID = 0x1d, -+ SSAM_SSH_TC_AUD = 0x1e, -+ SSAM_SSH_TC_SMC = 0x1f, -+ SSAM_SSH_TC_KPD = 0x20, -+ SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */ -+}; -+ -+ -+/* -- Packet transport layer (ptl). ----------------------------------------- */ -+ -+/** -+ * enum ssh_packet_base_priority - Base priorities for &struct ssh_packet. -+ * @SSH_PACKET_PRIORITY_FLUSH: Base priority for flush packets. -+ * @SSH_PACKET_PRIORITY_DATA: Base priority for normal data packets. -+ * @SSH_PACKET_PRIORITY_NAK: Base priority for NAK packets. -+ * @SSH_PACKET_PRIORITY_ACK: Base priority for ACK packets. -+ */ -+enum ssh_packet_base_priority { -+ SSH_PACKET_PRIORITY_FLUSH = 0, /* same as DATA to sequence flush */ -+ SSH_PACKET_PRIORITY_DATA = 0, -+ SSH_PACKET_PRIORITY_NAK = 1, -+ SSH_PACKET_PRIORITY_ACK = 2, -+}; -+ -+/* -+ * Same as SSH_PACKET_PRIORITY() below, only with actual values. -+ */ -+#define __SSH_PACKET_PRIORITY(base, try) \ -+ (((base) << 4) | ((try) & 0x0f)) -+ -+/** -+ * SSH_PACKET_PRIORITY() - Compute packet priority from base priority and -+ * number of tries. -+ * @base: The base priority as suffix of &enum ssh_packet_base_priority, e.g. -+ * ``FLUSH``, ``DATA``, ``ACK``, or ``NAK``. -+ * @try: The number of tries (must be less than 16). -+ * -+ * Compute the combined packet priority. The combined priority is dominated by -+ * the base priority, whereas the number of (re-)tries decides the precedence -+ * of packets with the same base priority, giving higher priority to packets -+ * that already have more tries. -+ * -+ * Return: Returns the computed priority as value fitting inside a &u8. A -+ * higher number means a higher priority. -+ */ -+#define SSH_PACKET_PRIORITY(base, try) \ -+ __SSH_PACKET_PRIORITY(SSH_PACKET_PRIORITY_##base, (try)) -+ -+/** -+ * ssh_packet_priority_get_try() - Get number of tries from packet priority. -+ * @priority: The packet priority. -+ * -+ * Return: Returns the number of tries encoded in the specified packet -+ * priority. -+ */ -+static inline u8 ssh_packet_priority_get_try(u8 priority) -+{ -+ return priority & 0x0f; -+} -+ -+/** -+ * ssh_packet_priority_get_base - Get base priority from packet priority. -+ * @priority: The packet priority. -+ * -+ * Return: Returns the base priority encoded in the given packet priority. -+ */ -+static inline u8 ssh_packet_priority_get_base(u8 priority) -+{ -+ return (priority & 0xf0) >> 4; -+} -+ -+enum ssh_packet_flags { -+ /* state flags */ -+ SSH_PACKET_SF_LOCKED_BIT, -+ SSH_PACKET_SF_QUEUED_BIT, -+ SSH_PACKET_SF_PENDING_BIT, -+ SSH_PACKET_SF_TRANSMITTING_BIT, -+ SSH_PACKET_SF_TRANSMITTED_BIT, -+ SSH_PACKET_SF_ACKED_BIT, -+ SSH_PACKET_SF_CANCELED_BIT, -+ SSH_PACKET_SF_COMPLETED_BIT, -+ -+ /* type flags */ -+ SSH_PACKET_TY_FLUSH_BIT, -+ SSH_PACKET_TY_SEQUENCED_BIT, -+ SSH_PACKET_TY_BLOCKING_BIT, -+ -+ /* mask for state flags */ -+ SSH_PACKET_FLAGS_SF_MASK = -+ BIT(SSH_PACKET_SF_LOCKED_BIT) -+ | BIT(SSH_PACKET_SF_QUEUED_BIT) -+ | BIT(SSH_PACKET_SF_PENDING_BIT) -+ | BIT(SSH_PACKET_SF_TRANSMITTING_BIT) -+ | BIT(SSH_PACKET_SF_TRANSMITTED_BIT) -+ | BIT(SSH_PACKET_SF_ACKED_BIT) -+ | BIT(SSH_PACKET_SF_CANCELED_BIT) -+ | BIT(SSH_PACKET_SF_COMPLETED_BIT), -+ -+ /* mask for type flags */ -+ SSH_PACKET_FLAGS_TY_MASK = -+ BIT(SSH_PACKET_TY_FLUSH_BIT) -+ | BIT(SSH_PACKET_TY_SEQUENCED_BIT) -+ | BIT(SSH_PACKET_TY_BLOCKING_BIT), -+}; -+ -+struct ssh_ptl; -+struct ssh_packet; -+ -+/** -+ * struct ssh_packet_ops - Callback operations for a SSH packet. -+ * @release: Function called when the packet reference count reaches zero. -+ * This callback must be relied upon to ensure that the packet has -+ * left the transport system(s). -+ * @complete: Function called when the packet is completed, either with -+ * success or failure. In case of failure, the reason for the -+ * failure is indicated by the value of the provided status code -+ * argument. This value will be zero in case of success. Note that -+ * a call to this callback does not guarantee that the packet is -+ * not in use by the transport system any more. -+ */ -+struct ssh_packet_ops { -+ void (*release)(struct ssh_packet *p); -+ void (*complete)(struct ssh_packet *p, int status); -+}; -+ -+/** -+ * struct ssh_packet - SSH transport packet. -+ * @ptl: Pointer to the packet transport layer. May be %NULL if the packet -+ * (or enclosing request) has not been submitted yet. -+ * @refcnt: Reference count of the packet. -+ * @priority: Priority of the packet. Must be computed via -+ * SSH_PACKET_PRIORITY(). Must only be accessed while holding the -+ * queue lock after first submission. -+ * @data: Raw message data. -+ * @data.len: Length of the raw message data. -+ * @data.ptr: Pointer to the raw message data buffer. -+ * @state: State and type flags describing current packet state (dynamic) -+ * and type (static). See &enum ssh_packet_flags for possible -+ * options. -+ * @timestamp: Timestamp specifying when the latest transmission of a -+ * currently pending packet has been started. May be %KTIME_MAX -+ * before or in-between transmission attempts. Used for the packet -+ * timeout implementation. Must only be accessed while holding the -+ * pending lock after first submission. -+ * @queue_node: The list node for the packet queue. -+ * @pending_node: The list node for the set of pending packets. -+ * @ops: Packet operations. -+ */ -+struct ssh_packet { -+ struct ssh_ptl *ptl; -+ struct kref refcnt; -+ -+ u8 priority; -+ -+ struct { -+ size_t len; -+ u8 *ptr; -+ } data; -+ -+ unsigned long state; -+ ktime_t timestamp; -+ -+ struct list_head queue_node; -+ struct list_head pending_node; -+ -+ const struct ssh_packet_ops *ops; -+}; -+ -+struct ssh_packet *ssh_packet_get(struct ssh_packet *p); -+void ssh_packet_put(struct ssh_packet *p); -+ -+/** -+ * ssh_packet_set_data() - Set raw message data of packet. -+ * @p: The packet for which the message data should be set. -+ * @ptr: Pointer to the memory holding the message data. -+ * @len: Length of the message data. -+ * -+ * Sets the raw message data buffer of the packet to the provided memory. The -+ * memory is not copied. Instead, the caller is responsible for management -+ * (i.e. allocation and deallocation) of the memory. The caller must ensure -+ * that the provided memory is valid and contains a valid SSH message, -+ * starting from the time of submission of the packet until the ``release`` -+ * callback has been called. During this time, the memory may not be altered -+ * in any way. -+ */ -+static inline void ssh_packet_set_data(struct ssh_packet *p, u8 *ptr, size_t len) -+{ -+ p->data.ptr = ptr; -+ p->data.len = len; -+} -+ -+ -+/* -- Request transport layer (rtl). ---------------------------------------- */ -+ -+enum ssh_request_flags { -+ /* state flags */ -+ SSH_REQUEST_SF_LOCKED_BIT, -+ SSH_REQUEST_SF_QUEUED_BIT, -+ SSH_REQUEST_SF_PENDING_BIT, -+ SSH_REQUEST_SF_TRANSMITTING_BIT, -+ SSH_REQUEST_SF_TRANSMITTED_BIT, -+ SSH_REQUEST_SF_RSPRCVD_BIT, -+ SSH_REQUEST_SF_CANCELED_BIT, -+ SSH_REQUEST_SF_COMPLETED_BIT, -+ -+ /* type flags */ -+ SSH_REQUEST_TY_FLUSH_BIT, -+ SSH_REQUEST_TY_HAS_RESPONSE_BIT, -+ -+ /* mask for state flags */ -+ SSH_REQUEST_FLAGS_SF_MASK = -+ BIT(SSH_REQUEST_SF_LOCKED_BIT) -+ | BIT(SSH_REQUEST_SF_QUEUED_BIT) -+ | BIT(SSH_REQUEST_SF_PENDING_BIT) -+ | BIT(SSH_REQUEST_SF_TRANSMITTING_BIT) -+ | BIT(SSH_REQUEST_SF_TRANSMITTED_BIT) -+ | BIT(SSH_REQUEST_SF_RSPRCVD_BIT) -+ | BIT(SSH_REQUEST_SF_CANCELED_BIT) -+ | BIT(SSH_REQUEST_SF_COMPLETED_BIT), -+ -+ /* mask for type flags */ -+ SSH_REQUEST_FLAGS_TY_MASK = -+ BIT(SSH_REQUEST_TY_FLUSH_BIT) -+ | BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT), -+}; -+ -+struct ssh_rtl; -+struct ssh_request; -+ -+/** -+ * struct ssh_request_ops - Callback operations for a SSH request. -+ * @release: Function called when the request's reference count reaches zero. -+ * This callback must be relied upon to ensure that the request has -+ * left the transport systems (both, packet an request systems). -+ * @complete: Function called when the request is completed, either with -+ * success or failure. The command data for the request response -+ * is provided via the &struct ssh_command parameter (``cmd``), -+ * the command payload of the request response via the &struct -+ * ssh_span parameter (``data``). -+ * -+ * If the request does not have any response or has not been -+ * completed with success, both ``cmd`` and ``data`` parameters will -+ * be NULL. If the request response does not have any command -+ * payload, the ``data`` span will be an empty (zero-length) span. -+ * -+ * In case of failure, the reason for the failure is indicated by -+ * the value of the provided status code argument (``status``). This -+ * value will be zero in case of success and a regular errno -+ * otherwise. -+ * -+ * Note that a call to this callback does not guarantee that the -+ * request is not in use by the transport systems any more. -+ */ -+struct ssh_request_ops { -+ void (*release)(struct ssh_request *rqst); -+ void (*complete)(struct ssh_request *rqst, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data, int status); -+}; -+ -+/** -+ * struct ssh_request - SSH transport request. -+ * @packet: The underlying SSH transport packet. -+ * @node: List node for the request queue and pending set. -+ * @state: State and type flags describing current request state (dynamic) -+ * and type (static). See &enum ssh_request_flags for possible -+ * options. -+ * @timestamp: Timestamp specifying when we start waiting on the response of -+ * the request. This is set once the underlying packet has been -+ * completed and may be %KTIME_MAX before that, or when the request -+ * does not expect a response. Used for the request timeout -+ * implementation. -+ * @ops: Request Operations. -+ */ -+struct ssh_request { -+ struct ssh_packet packet; -+ struct list_head node; -+ -+ unsigned long state; -+ ktime_t timestamp; -+ -+ const struct ssh_request_ops *ops; -+}; -+ -+/** -+ * to_ssh_request() - Cast a SSH packet to its enclosing SSH request. -+ * @p: The packet to cast. -+ * -+ * Casts the given &struct ssh_packet to its enclosing &struct ssh_request. -+ * The caller is responsible for making sure that the packet is actually -+ * wrapped in a &struct ssh_request. -+ * -+ * Return: Returns the &struct ssh_request wrapping the provided packet. -+ */ -+static inline struct ssh_request *to_ssh_request(struct ssh_packet *p) -+{ -+ return container_of(p, struct ssh_request, packet); -+} -+ -+/** -+ * ssh_request_get() - Increment reference count of request. -+ * @r: The request to increment the reference count of. -+ * -+ * Increments the reference count of the given request by incrementing the -+ * reference count of the underlying &struct ssh_packet, enclosed in it. -+ * -+ * See also ssh_request_put(), ssh_packet_get(). -+ * -+ * Return: Returns the request provided as input. -+ */ -+static inline struct ssh_request *ssh_request_get(struct ssh_request *r) -+{ -+ return r ? to_ssh_request(ssh_packet_get(&r->packet)) : NULL; -+} -+ -+/** -+ * ssh_request_put() - Decrement reference count of request. -+ * @r: The request to decrement the reference count of. -+ * -+ * Decrements the reference count of the given request by decrementing the -+ * reference count of the underlying &struct ssh_packet, enclosed in it. If -+ * the reference count reaches zero, the ``release`` callback specified in the -+ * request's &struct ssh_request_ops, i.e. ``r->ops->release``, will be -+ * called. -+ * -+ * See also ssh_request_get(), ssh_packet_put(). -+ */ -+static inline void ssh_request_put(struct ssh_request *r) -+{ -+ if (r) -+ ssh_packet_put(&r->packet); -+} -+ -+/** -+ * ssh_request_set_data() - Set raw message data of request. -+ * @r: The request for which the message data should be set. -+ * @ptr: Pointer to the memory holding the message data. -+ * @len: Length of the message data. -+ * -+ * Sets the raw message data buffer of the underlying packet to the specified -+ * buffer. Does not copy the actual message data, just sets the buffer pointer -+ * and length. Refer to ssh_packet_set_data() for more details. -+ */ -+static inline void ssh_request_set_data(struct ssh_request *r, u8 *ptr, size_t len) -+{ -+ ssh_packet_set_data(&r->packet, ptr, len); -+} -+ -+#endif /* _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H */ -diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h -new file mode 100644 -index 000000000000..08f46b60b151 ---- /dev/null -+++ b/include/uapi/linux/surface_aggregator/cdev.h -@@ -0,0 +1,147 @@ -+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ -+/* -+ * Surface System Aggregator Module (SSAM) user-space EC interface. -+ * -+ * Definitions, structs, and IOCTLs for the /dev/surface/aggregator misc -+ * device. This device provides direct user-space access to the SSAM EC. -+ * Intended for debugging and development. -+ * -+ * Copyright (C) 2020-2021 Maximilian Luz -+ */ -+ -+#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H -+#define _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H -+ -+#include -+#include -+ -+/** -+ * enum ssam_cdev_request_flags - Request flags for SSAM cdev request IOCTL. -+ * -+ * @SSAM_CDEV_REQUEST_HAS_RESPONSE: -+ * Specifies that the request expects a response. If not set, the request -+ * will be directly completed after its underlying packet has been -+ * transmitted. If set, the request transport system waits for a response -+ * of the request. -+ * -+ * @SSAM_CDEV_REQUEST_UNSEQUENCED: -+ * Specifies that the request should be transmitted via an unsequenced -+ * packet. If set, the request must not have a response, meaning that this -+ * flag and the %SSAM_CDEV_REQUEST_HAS_RESPONSE flag are mutually -+ * exclusive. -+ */ -+enum ssam_cdev_request_flags { -+ SSAM_CDEV_REQUEST_HAS_RESPONSE = 0x01, -+ SSAM_CDEV_REQUEST_UNSEQUENCED = 0x02, -+}; -+ -+/** -+ * struct ssam_cdev_request - Controller request IOCTL argument. -+ * @target_category: Target category of the SAM request. -+ * @target_id: Target ID of the SAM request. -+ * @command_id: Command ID of the SAM request. -+ * @instance_id: Instance ID of the SAM request. -+ * @flags: Request flags (see &enum ssam_cdev_request_flags). -+ * @status: Request status (output). -+ * @payload: Request payload (input data). -+ * @payload.data: Pointer to request payload data. -+ * @payload.length: Length of request payload data (in bytes). -+ * @response: Request response (output data). -+ * @response.data: Pointer to response buffer. -+ * @response.length: On input: Capacity of response buffer (in bytes). -+ * On output: Length of request response (number of bytes -+ * in the buffer that are actually used). -+ */ -+struct ssam_cdev_request { -+ __u8 target_category; -+ __u8 target_id; -+ __u8 command_id; -+ __u8 instance_id; -+ __u16 flags; -+ __s16 status; -+ -+ struct { -+ __u64 data; -+ __u16 length; -+ __u8 __pad[6]; -+ } payload; -+ -+ struct { -+ __u64 data; -+ __u16 length; -+ __u8 __pad[6]; -+ } response; -+} __attribute__((__packed__)); -+ -+/** -+ * struct ssam_cdev_notifier_desc - Notifier descriptor. -+ * @priority: Priority value determining the order in which notifier -+ * callbacks will be called. A higher value means higher -+ * priority, i.e. the associated callback will be executed -+ * earlier than other (lower priority) callbacks. -+ * @target_category: The event target category for which this notifier should -+ * receive events. -+ * -+ * Specifies the notifier that should be registered or unregistered, -+ * specifically with which priority and for which target category of events. -+ */ -+struct ssam_cdev_notifier_desc { -+ __s32 priority; -+ __u8 target_category; -+} __attribute__((__packed__)); -+ -+/** -+ * struct ssam_cdev_event_desc - Event descriptor. -+ * @reg: Registry via which the event will be enabled/disabled. -+ * @reg.target_category: Target category for the event registry requests. -+ * @reg.target_id: Target ID for the event registry requests. -+ * @reg.cid_enable: Command ID for the event-enable request. -+ * @reg.cid_disable: Command ID for the event-disable request. -+ * @id: ID specifying the event. -+ * @id.target_category: Target category of the event source. -+ * @id.instance: Instance ID of the event source. -+ * @flags: Flags used for enabling the event. -+ * -+ * Specifies which event should be enabled/disabled and how to do that. -+ */ -+struct ssam_cdev_event_desc { -+ struct { -+ __u8 target_category; -+ __u8 target_id; -+ __u8 cid_enable; -+ __u8 cid_disable; -+ } reg; -+ -+ struct { -+ __u8 target_category; -+ __u8 instance; -+ } id; -+ -+ __u8 flags; -+} __attribute__((__packed__)); -+ -+/** -+ * struct ssam_cdev_event - SSAM event sent by the EC. -+ * @target_category: Target category of the event source. See &enum ssam_ssh_tc. -+ * @target_id: Target ID of the event source. -+ * @command_id: Command ID of the event. -+ * @instance_id: Instance ID of the event source. -+ * @length: Length of the event payload in bytes. -+ * @data: Event payload data. -+ */ -+struct ssam_cdev_event { -+ __u8 target_category; -+ __u8 target_id; -+ __u8 command_id; -+ __u8 instance_id; -+ __u16 length; -+ __u8 data[]; -+} __attribute__((__packed__)); -+ -+#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) -+#define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc) -+#define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc) -+#define SSAM_CDEV_EVENT_ENABLE _IOW(0xA5, 4, struct ssam_cdev_event_desc) -+#define SSAM_CDEV_EVENT_DISABLE _IOW(0xA5, 5, struct ssam_cdev_event_desc) -+ -+#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ -diff --git a/include/uapi/linux/surface_aggregator/dtx.h b/include/uapi/linux/surface_aggregator/dtx.h -new file mode 100644 -index 000000000000..fc0ba6cbe3e8 ---- /dev/null -+++ b/include/uapi/linux/surface_aggregator/dtx.h -@@ -0,0 +1,146 @@ -+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ -+/* -+ * Surface DTX (clipboard detachment system driver) user-space interface. -+ * -+ * Definitions, structs, and IOCTLs for the /dev/surface/dtx misc device. This -+ * device allows user-space to control the clipboard detachment process on -+ * Surface Book series devices. -+ * -+ * Copyright (C) 2020 Maximilian Luz -+ */ -+ -+#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H -+#define _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H -+ -+#include -+#include -+ -+/* Status/error categories */ -+#define SDTX_CATEGORY_STATUS 0x0000 -+#define SDTX_CATEGORY_RUNTIME_ERROR 0x1000 -+#define SDTX_CATEGORY_HARDWARE_ERROR 0x2000 -+#define SDTX_CATEGORY_UNKNOWN 0xf000 -+ -+#define SDTX_CATEGORY_MASK 0xf000 -+#define SDTX_CATEGORY(value) ((value) & SDTX_CATEGORY_MASK) -+ -+#define SDTX_STATUS(code) ((code) | SDTX_CATEGORY_STATUS) -+#define SDTX_ERR_RT(code) ((code) | SDTX_CATEGORY_RUNTIME_ERROR) -+#define SDTX_ERR_HW(code) ((code) | SDTX_CATEGORY_HARDWARE_ERROR) -+#define SDTX_UNKNOWN(code) ((code) | SDTX_CATEGORY_UNKNOWN) -+ -+#define SDTX_SUCCESS(value) (SDTX_CATEGORY(value) == SDTX_CATEGORY_STATUS) -+ -+/* Latch status values */ -+#define SDTX_LATCH_CLOSED SDTX_STATUS(0x00) -+#define SDTX_LATCH_OPENED SDTX_STATUS(0x01) -+ -+/* Base state values */ -+#define SDTX_BASE_DETACHED SDTX_STATUS(0x00) -+#define SDTX_BASE_ATTACHED SDTX_STATUS(0x01) -+ -+/* Runtime errors (non-critical) */ -+#define SDTX_DETACH_NOT_FEASIBLE SDTX_ERR_RT(0x01) -+#define SDTX_DETACH_TIMEDOUT SDTX_ERR_RT(0x02) -+ -+/* Hardware errors (critical) */ -+#define SDTX_ERR_FAILED_TO_OPEN SDTX_ERR_HW(0x01) -+#define SDTX_ERR_FAILED_TO_REMAIN_OPEN SDTX_ERR_HW(0x02) -+#define SDTX_ERR_FAILED_TO_CLOSE SDTX_ERR_HW(0x03) -+ -+/* Base types */ -+#define SDTX_DEVICE_TYPE_HID 0x0100 -+#define SDTX_DEVICE_TYPE_SSH 0x0200 -+ -+#define SDTX_DEVICE_TYPE_MASK 0x0f00 -+#define SDTX_DEVICE_TYPE(value) ((value) & SDTX_DEVICE_TYPE_MASK) -+ -+#define SDTX_BASE_TYPE_HID(id) ((id) | SDTX_DEVICE_TYPE_HID) -+#define SDTX_BASE_TYPE_SSH(id) ((id) | SDTX_DEVICE_TYPE_SSH) -+ -+/** -+ * enum sdtx_device_mode - Mode describing how (and if) the clipboard is -+ * attached to the base of the device. -+ * @SDTX_DEVICE_MODE_TABLET: The clipboard is detached from the base and the -+ * device operates as tablet. -+ * @SDTX_DEVICE_MODE_LAPTOP: The clipboard is attached normally to the base -+ * and the device operates as laptop. -+ * @SDTX_DEVICE_MODE_STUDIO: The clipboard is attached to the base in reverse. -+ * The device operates as tablet with keyboard and -+ * touchpad deactivated, however, the base battery -+ * and, if present in the specific device model, dGPU -+ * are available to the system. -+ */ -+enum sdtx_device_mode { -+ SDTX_DEVICE_MODE_TABLET = 0x00, -+ SDTX_DEVICE_MODE_LAPTOP = 0x01, -+ SDTX_DEVICE_MODE_STUDIO = 0x02, -+}; -+ -+/** -+ * struct sdtx_event - Event provided by reading from the DTX device file. -+ * @length: Length of the event payload, in bytes. -+ * @code: Event code, detailing what type of event this is. -+ * @data: Payload of the event, containing @length bytes. -+ * -+ * See &enum sdtx_event_code for currently valid event codes. -+ */ -+struct sdtx_event { -+ __u16 length; -+ __u16 code; -+ __u8 data[]; -+} __attribute__((__packed__)); -+ -+/** -+ * enum sdtx_event_code - Code describing the type of an event. -+ * @SDTX_EVENT_REQUEST: Detachment request event type. -+ * @SDTX_EVENT_CANCEL: Cancel detachment process event type. -+ * @SDTX_EVENT_BASE_CONNECTION: Base/clipboard connection change event type. -+ * @SDTX_EVENT_LATCH_STATUS: Latch status change event type. -+ * @SDTX_EVENT_DEVICE_MODE: Device mode change event type. -+ * -+ * Used in @struct sdtx_event to describe the type of the event. Further event -+ * codes are reserved for future use. Any event parser should be able to -+ * gracefully handle unknown events, i.e. by simply skipping them. -+ * -+ * Consult the DTX user-space interface documentation for details regarding -+ * the individual event types. -+ */ -+enum sdtx_event_code { -+ SDTX_EVENT_REQUEST = 1, -+ SDTX_EVENT_CANCEL = 2, -+ SDTX_EVENT_BASE_CONNECTION = 3, -+ SDTX_EVENT_LATCH_STATUS = 4, -+ SDTX_EVENT_DEVICE_MODE = 5, -+}; -+ -+/** -+ * struct sdtx_base_info - Describes if and what type of base is connected. -+ * @state: The state of the connection. Valid values are %SDTX_BASE_DETACHED, -+ * %SDTX_BASE_ATTACHED, and %SDTX_DETACH_NOT_FEASIBLE (in case a base -+ * is attached but low clipboard battery prevents detachment). Other -+ * values are currently reserved. -+ * @base_id: The type of base connected. Zero if no base is connected. -+ */ -+struct sdtx_base_info { -+ __u16 state; -+ __u16 base_id; -+} __attribute__((__packed__)); -+ -+/* IOCTLs */ -+#define SDTX_IOCTL_EVENTS_ENABLE _IO(0xa5, 0x21) -+#define SDTX_IOCTL_EVENTS_DISABLE _IO(0xa5, 0x22) -+ -+#define SDTX_IOCTL_LATCH_LOCK _IO(0xa5, 0x23) -+#define SDTX_IOCTL_LATCH_UNLOCK _IO(0xa5, 0x24) -+ -+#define SDTX_IOCTL_LATCH_REQUEST _IO(0xa5, 0x25) -+#define SDTX_IOCTL_LATCH_CONFIRM _IO(0xa5, 0x26) -+#define SDTX_IOCTL_LATCH_HEARTBEAT _IO(0xa5, 0x27) -+#define SDTX_IOCTL_LATCH_CANCEL _IO(0xa5, 0x28) -+ -+#define SDTX_IOCTL_GET_BASE_INFO _IOR(0xa5, 0x29, struct sdtx_base_info) -+#define SDTX_IOCTL_GET_DEVICE_MODE _IOR(0xa5, 0x2a, __u16) -+#define SDTX_IOCTL_GET_LATCH_STATUS _IOR(0xa5, 0x2b, __u16) -+ -+#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H */ -diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c -index bcff122d0dc8..4339377ad929 100644 ---- a/scripts/mod/devicetable-offsets.c -+++ b/scripts/mod/devicetable-offsets.c -@@ -245,8 +245,9 @@ int main(void) - - DEVID(ssam_device_id); - DEVID_FIELD(ssam_device_id, match_flags); -+ DEVID_FIELD(ssam_device_id, domain); - DEVID_FIELD(ssam_device_id, category); -- DEVID_FIELD(ssam_device_id, channel); -+ DEVID_FIELD(ssam_device_id, target); - DEVID_FIELD(ssam_device_id, instance); - DEVID_FIELD(ssam_device_id, function); - -diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c -index a6c583362b92..5b79fdc42641 100644 ---- a/scripts/mod/file2alias.c -+++ b/scripts/mod/file2alias.c -@@ -1368,20 +1368,22 @@ static int do_mhi_entry(const char *filename, void *symval, char *alias) - return 1; - } - --/* Looks like: ssam:cNtNiNfN -+/* -+ * Looks like: ssam:dNcNtNiNfN - * - * N is exactly 2 digits, where each is an upper-case hex digit. - */ - static int do_ssam_entry(const char *filename, void *symval, char *alias) - { - DEF_FIELD(symval, ssam_device_id, match_flags); -+ DEF_FIELD(symval, ssam_device_id, domain); - DEF_FIELD(symval, ssam_device_id, category); -- DEF_FIELD(symval, ssam_device_id, channel); -+ DEF_FIELD(symval, ssam_device_id, target); - DEF_FIELD(symval, ssam_device_id, instance); - DEF_FIELD(symval, ssam_device_id, function); - -- sprintf(alias, "ssam:c%02X", category); -- ADD(alias, "t", match_flags & SSAM_MATCH_CHANNEL, channel); -+ sprintf(alias, "ssam:d%02Xc%02X", domain, category); -+ ADD(alias, "t", match_flags & SSAM_MATCH_TARGET, target); - ADD(alias, "i", match_flags & SSAM_MATCH_INSTANCE, instance); - ADD(alias, "f", match_flags & SSAM_MATCH_FUNCTION, function); - --- -2.33.0 - diff --git a/patches/5.10/0007-surface-hotplug.patch b/patches/5.10/0007-surface-hotplug.patch deleted file mode 100644 index f8234ad00..000000000 --- a/patches/5.10/0007-surface-hotplug.patch +++ /dev/null @@ -1,470 +0,0 @@ -From f255f201f64e16da9651393a9906956d6dcc89d7 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 31 Oct 2020 20:46:33 +0100 -Subject: [PATCH] PCI: Add sysfs attribute for PCI device power state - -While most PCI power-states can be queried from user-space via lspci, -this has some limits. Specifically, lspci fails to provide an accurate -value when the device is in D3cold as it has to resume the device before -it can access its power state via the configuration space, leading to it -reporting D0 or another on-state. Thus lspci can, for example, not be -used to diagnose power-consumption issues for devices that can enter -D3cold or to ensure that devices properly enter D3cold at all. - -To alleviate this issue, introduce a new sysfs device attribute for the -PCI power state, showing the current power state as seen by the kernel. - -Signed-off-by: Maximilian Luz -Patchset: surface-hotplug ---- - Documentation/ABI/testing/sysfs-bus-pci | 9 +++++++++ - drivers/pci/pci-sysfs.c | 12 ++++++++++++ - 2 files changed, 21 insertions(+) - -diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci -index 77ad9ec3c801..25c9c39770c6 100644 ---- a/Documentation/ABI/testing/sysfs-bus-pci -+++ b/Documentation/ABI/testing/sysfs-bus-pci -@@ -366,3 +366,12 @@ Contact: Heiner Kallweit - Description: If ASPM is supported for an endpoint, these files can be - used to disable or enable the individual power management - states. Write y/1/on to enable, n/0/off to disable. -+ -+What: /sys/bus/pci/devices/.../power_state -+Date: November 2020 -+Contact: Linux PCI developers -+Description: -+ This file contains the current PCI power state of the device. -+ The value comes from the PCI kernel device state and can be one -+ of: "unknown", "error", "D0", D1", "D2", "D3hot", "D3cold". -+ The file is read only. -diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c -index d15c881e2e7e..b15f754e6346 100644 ---- a/drivers/pci/pci-sysfs.c -+++ b/drivers/pci/pci-sysfs.c -@@ -124,6 +124,17 @@ static ssize_t cpulistaffinity_show(struct device *dev, - } - static DEVICE_ATTR_RO(cpulistaffinity); - -+/* PCI power state */ -+static ssize_t power_state_show(struct device *dev, -+ struct device_attribute *attr, char *buf) -+{ -+ struct pci_dev *pci_dev = to_pci_dev(dev); -+ pci_power_t state = READ_ONCE(pci_dev->current_state); -+ -+ return sprintf(buf, "%s\n", pci_power_name(state)); -+} -+static DEVICE_ATTR_RO(power_state); -+ - /* show resources */ - static ssize_t resource_show(struct device *dev, struct device_attribute *attr, - char *buf) -@@ -581,6 +592,7 @@ static ssize_t driver_override_show(struct device *dev, - static DEVICE_ATTR_RW(driver_override); - - static struct attribute *pci_dev_attrs[] = { -+ &dev_attr_power_state.attr, - &dev_attr_resource.attr, - &dev_attr_vendor.attr, - &dev_attr_device.attr, --- -2.33.0 - -From cd1bedb6ac1c78f328d474774dcdaabdc385c409 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 14 Dec 2020 20:50:59 +0100 -Subject: [PATCH] platform/x86: Add Surface Hotplug driver - -Add a driver to handle out-of-band hot-plug signaling for the discrete -GPU (dGPU) on Microsoft Surface Book 2 and 3 devices. This driver is -required to properly detect hot-plugging of the dGPU and relay the -appropriate signal to the PCIe hot-plug driver core. - -Signed-off-by: Maximilian Luz -Patchset: surface-hotplug ---- - drivers/platform/x86/Kconfig | 20 ++ - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/surface_hotplug.c | 282 +++++++++++++++++++++++++ - 3 files changed, 303 insertions(+) - create mode 100644 drivers/platform/x86/surface_hotplug.c - -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index d44d3fb9ca72..533998040530 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -1012,6 +1012,26 @@ config SURFACE_GPE - accordingly. It is required on those devices to allow wake-ups from - suspend by opening the lid. - -+config SURFACE_HOTPLUG -+ tristate "Surface Hot-Plug System Driver" -+ depends on ACPI -+ default m -+ help -+ Driver for out-of-band hot-plug event signaling on Microsoft Surface -+ devices with hot-pluggable PCIe cards. -+ -+ This driver is used on Surface Book (2 and 3) devices with a -+ hot-pluggable discrete GPU (dGPU). When not in use, the dGPU on those -+ devices can enter D3cold, which prevents in-band (standard) PCIe -+ hot-plug signaling. Thus, without this driver, detaching the base -+ containing the dGPU will not correctly update the state of the -+ corresponding PCIe device if it is in D3cold. This driver adds support -+ for out-of-band hot-plug notifications, ensuring that the device state -+ is properly updated even when the device in question is in D3cold. -+ -+ Select M or Y here, if you want to (fully) support hot-plugging of -+ dGPU devices on the Surface Book 2 and/or 3 during D3cold. -+ - config SURFACE_BOOK1_DGPU_SWITCH - tristate "Surface Book 1 dGPU Switch Driver" - depends on ACPI && SYSFS -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 2e0a2896c78d..f552cbfb7914 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -94,6 +94,7 @@ obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o - obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o -+obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o - obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += sb1_dgpu_sw.o - - # MSI -diff --git a/drivers/platform/x86/surface_hotplug.c b/drivers/platform/x86/surface_hotplug.c -new file mode 100644 -index 000000000000..cfcc15cfbacb ---- /dev/null -+++ b/drivers/platform/x86/surface_hotplug.c -@@ -0,0 +1,282 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface Book (2 and later) hot-plug driver. -+ * -+ * Surface Book devices (can) have a hot-pluggable discrete GPU (dGPU). This -+ * driver is responsible for out-of-band hot-plug event signaling on these -+ * devices. It is specifically required when the hot-plug device is in D3cold -+ * and can thus not generate PCIe hot-plug events itself. -+ * -+ * Event signaling is handled via ACPI, which will generate the appropriate -+ * device-check notifications to be picked up by the PCIe hot-plug driver. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+static const struct acpi_gpio_params shps_base_presence_int = { 0, 0, false }; -+static const struct acpi_gpio_params shps_base_presence = { 1, 0, false }; -+static const struct acpi_gpio_params shps_device_power_int = { 2, 0, false }; -+static const struct acpi_gpio_params shps_device_power = { 3, 0, false }; -+static const struct acpi_gpio_params shps_device_presence_int = { 4, 0, false }; -+static const struct acpi_gpio_params shps_device_presence = { 5, 0, false }; -+ -+static const struct acpi_gpio_mapping shps_acpi_gpios[] = { -+ { "base_presence-int-gpio", &shps_base_presence_int, 1 }, -+ { "base_presence-gpio", &shps_base_presence, 1 }, -+ { "device_power-int-gpio", &shps_device_power_int, 1 }, -+ { "device_power-gpio", &shps_device_power, 1 }, -+ { "device_presence-int-gpio", &shps_device_presence_int, 1 }, -+ { "device_presence-gpio", &shps_device_presence, 1 }, -+ { }, -+}; -+ -+/* 5515a847-ed55-4b27-8352-cd320e10360a */ -+static const guid_t shps_dsm_guid = -+ GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, 0x32, 0x0e, 0x10, 0x36, 0x0a); -+ -+#define SHPS_DSM_REVISION 1 -+ -+enum shps_dsm_fn { -+ SHPS_DSM_FN_PCI_NUM_ENTRIES = 0x01, -+ SHPS_DSM_FN_PCI_GET_ENTRIES = 0x02, -+ SHPS_DSM_FN_IRQ_BASE_PRESENCE = 0x03, -+ SHPS_DSM_FN_IRQ_DEVICE_POWER = 0x04, -+ SHPS_DSM_FN_IRQ_DEVICE_PRESENCE = 0x05, -+}; -+ -+enum shps_irq_type { -+ /* NOTE: Must be in order of enum shps_dsm_fn above. */ -+ SHPS_IRQ_TYPE_BASE_PRESENCE = 0, -+ SHPS_IRQ_TYPE_DEVICE_POWER = 1, -+ SHPS_IRQ_TYPE_DEVICE_PRESENCE = 2, -+ SHPS_NUM_IRQS, -+}; -+ -+static const char *const shps_gpio_names[] = { -+ [SHPS_IRQ_TYPE_BASE_PRESENCE] = "base_presence", -+ [SHPS_IRQ_TYPE_DEVICE_POWER] = "device_power", -+ [SHPS_IRQ_TYPE_DEVICE_PRESENCE] = "device_presence", -+}; -+ -+struct shps_device { -+ struct mutex lock[SHPS_NUM_IRQS]; /* Protects update in shps_dsm_notify_irq() */ -+ struct gpio_desc *gpio[SHPS_NUM_IRQS]; -+ unsigned int irq[SHPS_NUM_IRQS]; -+}; -+ -+#define SHPS_IRQ_NOT_PRESENT ((unsigned int)-1) -+ -+static enum shps_dsm_fn shps_dsm_fn_for_irq(enum shps_irq_type type) -+{ -+ return SHPS_DSM_FN_IRQ_BASE_PRESENCE + type; -+} -+ -+static void shps_dsm_notify_irq(struct platform_device *pdev, enum shps_irq_type type) -+{ -+ struct shps_device *sdev = platform_get_drvdata(pdev); -+ acpi_handle handle = ACPI_HANDLE(&pdev->dev); -+ union acpi_object *result; -+ union acpi_object param; -+ int value; -+ -+ mutex_lock(&sdev->lock[type]); -+ -+ value = gpiod_get_value_cansleep(sdev->gpio[type]); -+ if (value < 0) { -+ mutex_unlock(&sdev->lock[type]); -+ dev_err(&pdev->dev, "failed to get gpio: %d (irq=%d)\n", type, value); -+ return; -+ } -+ -+ dev_dbg(&pdev->dev, "IRQ notification via DSM (irq=%d, value=%d)\n", type, value); -+ -+ param.type = ACPI_TYPE_INTEGER; -+ param.integer.value = value; -+ -+ result = acpi_evaluate_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, -+ shps_dsm_fn_for_irq(type), ¶m); -+ -+ if (!result) { -+ dev_err(&pdev->dev, "IRQ notification via DSM failed (irq=%d, gpio=%d)\n", -+ type, value); -+ -+ } else if (result->type != ACPI_TYPE_BUFFER) { -+ dev_err(&pdev->dev, -+ "IRQ notification via DSM failed: unexpected result type (irq=%d, gpio=%d)\n", -+ type, value); -+ -+ } else if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) { -+ dev_err(&pdev->dev, -+ "IRQ notification via DSM failed: unexpected result value (irq=%d, gpio=%d)\n", -+ type, value); -+ } -+ -+ mutex_unlock(&sdev->lock[type]); -+ -+ if (result) -+ ACPI_FREE(result); -+} -+ -+static irqreturn_t shps_handle_irq(int irq, void *data) -+{ -+ struct platform_device *pdev = data; -+ struct shps_device *sdev = platform_get_drvdata(pdev); -+ int type; -+ -+ /* Figure out which IRQ we're handling. */ -+ for (type = 0; type < SHPS_NUM_IRQS; type++) -+ if (irq == sdev->irq[type]) -+ break; -+ -+ /* We should have found our interrupt, if not: this is a bug. */ -+ if (WARN(type >= SHPS_NUM_IRQS, "invalid IRQ number: %d\n", irq)) -+ return IRQ_HANDLED; -+ -+ /* Forward interrupt to ACPI via DSM. */ -+ shps_dsm_notify_irq(pdev, type); -+ return IRQ_HANDLED; -+} -+ -+static int shps_setup_irq(struct platform_device *pdev, enum shps_irq_type type) -+{ -+ unsigned long flags = IRQF_ONESHOT | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING; -+ struct shps_device *sdev = platform_get_drvdata(pdev); -+ struct gpio_desc *gpiod; -+ acpi_handle handle = ACPI_HANDLE(&pdev->dev); -+ const char *irq_name; -+ const int dsm = shps_dsm_fn_for_irq(type); -+ int status, irq; -+ -+ /* -+ * Only set up interrupts that we actually need: The Surface Book 3 -+ * does not have a DSM for base presence, so don't set up an interrupt -+ * for that. -+ */ -+ if (!acpi_check_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, BIT(dsm))) { -+ dev_dbg(&pdev->dev, "IRQ notification via DSM not present (irq=%d)\n", type); -+ return 0; -+ } -+ -+ gpiod = devm_gpiod_get(&pdev->dev, shps_gpio_names[type], GPIOD_ASIS); -+ if (IS_ERR(gpiod)) -+ return PTR_ERR(gpiod); -+ -+ irq = gpiod_to_irq(gpiod); -+ if (irq < 0) -+ return irq; -+ -+ irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "shps-irq-%d", type); -+ if (!irq_name) -+ return -ENOMEM; -+ -+ status = devm_request_threaded_irq(&pdev->dev, irq, NULL, shps_handle_irq, -+ flags, irq_name, pdev); -+ if (status) -+ return status; -+ -+ dev_dbg(&pdev->dev, "set up irq %d as type %d\n", irq, type); -+ -+ sdev->gpio[type] = gpiod; -+ sdev->irq[type] = irq; -+ -+ return 0; -+} -+ -+static int surface_hotplug_remove(struct platform_device *pdev) -+{ -+ struct shps_device *sdev = platform_get_drvdata(pdev); -+ int i; -+ -+ /* Ensure that IRQs have been fully handled and won't trigger any more. */ -+ for (i = 0; i < SHPS_NUM_IRQS; i++) { -+ if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT) -+ disable_irq(sdev->irq[i]); -+ -+ mutex_destroy(&sdev->lock[i]); -+ } -+ -+ return 0; -+} -+ -+static int surface_hotplug_probe(struct platform_device *pdev) -+{ -+ struct shps_device *sdev; -+ int status, i; -+ -+ /* -+ * The MSHW0153 device is also present on the Surface Laptop 3, -+ * however that doesn't have a hot-pluggable PCIe device. It also -+ * doesn't have any GPIO interrupts/pins under the MSHW0153, so filter -+ * it out here. -+ */ -+ if (gpiod_count(&pdev->dev, NULL) < 0) -+ return -ENODEV; -+ -+ status = devm_acpi_dev_add_driver_gpios(&pdev->dev, shps_acpi_gpios); -+ if (status) -+ return status; -+ -+ sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL); -+ if (!sdev) -+ return -ENOMEM; -+ -+ platform_set_drvdata(pdev, sdev); -+ -+ /* -+ * Initialize IRQs so that we can safely call surface_hotplug_remove() -+ * on errors. -+ */ -+ for (i = 0; i < SHPS_NUM_IRQS; i++) -+ sdev->irq[i] = SHPS_IRQ_NOT_PRESENT; -+ -+ /* Set up IRQs. */ -+ for (i = 0; i < SHPS_NUM_IRQS; i++) { -+ mutex_init(&sdev->lock[i]); -+ -+ status = shps_setup_irq(pdev, i); -+ if (status) { -+ dev_err(&pdev->dev, "failed to set up IRQ %d: %d\n", i, status); -+ goto err; -+ } -+ } -+ -+ /* Ensure everything is up-to-date. */ -+ for (i = 0; i < SHPS_NUM_IRQS; i++) -+ if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT) -+ shps_dsm_notify_irq(pdev, i); -+ -+ return 0; -+ -+err: -+ surface_hotplug_remove(pdev); -+ return status; -+} -+ -+static const struct acpi_device_id surface_hotplug_acpi_match[] = { -+ { "MSHW0153", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_hotplug_acpi_match); -+ -+static struct platform_driver surface_hotplug_driver = { -+ .probe = surface_hotplug_probe, -+ .remove = surface_hotplug_remove, -+ .driver = { -+ .name = "surface_hotplug", -+ .acpi_match_table = surface_hotplug_acpi_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_hotplug_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Hot-Plug Signaling Driver for Surface Book Devices"); -+MODULE_LICENSE("GPL"); --- -2.33.0 - -From 4a19147e3089c339a35adf8babad555cda52cb2e Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 2 Jul 2021 14:35:43 +0200 -Subject: [PATCH] Revert "Revert "PCI: PM: Do not read power state in - pci_enable_device_flags()"" - -This reverts commit f11f9ff8a7c97b2a3990c7322304627d9b58d362. - -Patchset: surface-hotplug ---- - drivers/pci/pci.c | 16 +++------------- - 1 file changed, 3 insertions(+), 13 deletions(-) - -diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c -index 29f5d699fa06..c73fcdff0c16 100644 ---- a/drivers/pci/pci.c -+++ b/drivers/pci/pci.c -@@ -1874,20 +1874,10 @@ static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags) - int err; - int i, bars = 0; - -- /* -- * Power state could be unknown at this point, either due to a fresh -- * boot or a device removal call. So get the current power state -- * so that things like MSI message writing will behave as expected -- * (e.g. if the device really is in D0 at enable time). -- */ -- if (dev->pm_cap) { -- u16 pmcsr; -- pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); -- dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK); -- } -- -- if (atomic_inc_return(&dev->enable_cnt) > 1) -+ if (atomic_inc_return(&dev->enable_cnt) > 1) { -+ pci_update_current_state(dev, dev->current_state); - return 0; /* already enabled */ -+ } - - bridge = pci_upstream_bridge(dev); - if (bridge) --- -2.33.0 - diff --git a/patches/5.10/0008-surface-typecover.patch b/patches/5.10/0008-surface-typecover.patch deleted file mode 100644 index f21369b5a..000000000 --- a/patches/5.10/0008-surface-typecover.patch +++ /dev/null @@ -1,233 +0,0 @@ -From 780663211d7d594890a6d386e40ce8d11b98cc9c Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 5 Nov 2020 13:09:45 +0100 -Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when - suspending - -The Type Cover for Microsoft Surface devices supports a special usb -control request to disable or enable the built-in keyboard backlight. -On Windows, this request happens when putting the device into suspend or -resuming it, without it the backlight of the Type Cover will remain -enabled for some time even though the computer is suspended, which looks -weird to the user. - -So add support for this special usb control request to hid-multitouch, -which is the driver that's handling the Type Cover. - -The reason we have to use a pm_notifier for this instead of the usual -suspend/resume methods is that those won't get called in case the usb -device is already autosuspended. - -Also, if the device is autosuspended, we have to briefly autoresume it -in order to send the request. Doing that should be fine, the usb-core -driver does something similar during suspend inside choose_wakeup(). - -To make sure we don't send that request to every device but only to -devices which support it, add a new quirk -MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk -is only enabled for the usb id of the Surface Pro 2017 Type Cover, which -is where I confirmed that it's working. - -Patchset: surface-typecover ---- - drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- - 1 file changed, 98 insertions(+), 2 deletions(-) - -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index e5a3704b9fe8..14910d6b7e24 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -34,7 +34,10 @@ - #include - #include - #include -+#include - #include -+#include -+#include - #include - #include - #include -@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); - MODULE_LICENSE("GPL"); - - #include "hid-ids.h" -+#include "usbhid/usbhid.h" - - /* quirks to control the device */ - #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) -@@ -70,12 +74,15 @@ MODULE_LICENSE("GPL"); - #define MT_QUIRK_WIN8_PTP_BUTTONS BIT(18) - #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) - #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) -+#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(21) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 - - #define MT_BUTTONTYPE_CLICKPAD 0 - -+#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+ - enum latency_mode { - HID_LATENCY_NORMAL = 0, - HID_LATENCY_HIGH = 1, -@@ -167,6 +174,8 @@ struct mt_device { - - struct list_head applications; - struct list_head reports; -+ -+ struct notifier_block pm_notifier; - }; - - static void mt_post_parse_default_settings(struct mt_device *td, -@@ -208,6 +217,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); - #define MT_CLS_GOOGLE 0x0111 - #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 - #define MT_CLS_SMART_TECH 0x0113 -+#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 - - #define MT_DEFAULT_MAXCONTACT 10 - #define MT_MAX_MAXCONTACT 250 -@@ -367,6 +377,16 @@ static const struct mt_class mt_classes[] = { - MT_QUIRK_CONTACT_CNT_ACCURATE | - MT_QUIRK_SEPARATE_APP_REPORT, - }, -+ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_ALWAYS_VALID | -+ MT_QUIRK_IGNORE_DUPLICATES | -+ MT_QUIRK_HOVERING | -+ MT_QUIRK_CONTACT_CNT_ACCURATE | -+ MT_QUIRK_STICKY_FINGERS | -+ MT_QUIRK_WIN8_PTP_BUTTONS, -+ .export_all_inputs = true -+ }, - { } - }; - -@@ -1678,6 +1698,69 @@ static void mt_expired_timeout(struct timer_list *t) - clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - -+static void get_type_cover_backlight_field(struct hid_device *hdev, -+ struct hid_field **field) -+{ -+ struct hid_report_enum *rep_enum; -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid -+ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { -+ *field = cur_field; -+ return; -+ } -+ } -+ } -+ } -+} -+ -+static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) -+{ -+ struct usb_device *udev = hid_to_usb_dev(hdev); -+ struct hid_field *field = NULL; -+ -+ /* Wake up the device in case it's already suspended */ -+ pm_runtime_get_sync(&udev->dev); -+ -+ get_type_cover_backlight_field(hdev, &field); -+ if (!field) { -+ hid_err(hdev, "couldn't find backlight field\n"); -+ goto out; -+ } -+ -+ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; -+ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); -+ -+out: -+ pm_runtime_put_sync(&udev->dev); -+} -+ -+static int mt_pm_notifier(struct notifier_block *notifier, -+ unsigned long pm_event, -+ void *unused) -+{ -+ struct mt_device *td = -+ container_of(notifier, struct mt_device, pm_notifier); -+ struct hid_device *hdev = td->hdev; -+ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { -+ if (pm_event == PM_SUSPEND_PREPARE) -+ update_keyboard_backlight(hdev, 0); -+ else if (pm_event == PM_POST_SUSPEND) -+ update_keyboard_backlight(hdev, 1); -+ } -+ -+ return NOTIFY_DONE; -+} -+ - static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - { - int ret, i; -@@ -1701,6 +1784,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; - hid_set_drvdata(hdev, td); - -+ td->pm_notifier.notifier_call = mt_pm_notifier; -+ register_pm_notifier(&td->pm_notifier); -+ - INIT_LIST_HEAD(&td->applications); - INIT_LIST_HEAD(&td->reports); - -@@ -1730,15 +1816,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - timer_setup(&td->release_timer, mt_expired_timeout, 0); - - ret = hid_parse(hdev); -- if (ret != 0) -+ if (ret != 0) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) - mt_fix_const_fields(hdev, HID_DG_CONTACTID); - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); -- if (ret) -+ if (ret) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); - if (ret) -@@ -1774,6 +1864,7 @@ static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); - -+ unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); - - sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); -@@ -2125,6 +2216,11 @@ static const struct hid_device_id mt_devices[] = { - MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, - USB_DEVICE_ID_XIROKU_CSR2) }, - -+ /* Microsoft Surface type cover */ -+ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, -+ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, -+ - /* Google MT devices */ - { .driver_data = MT_CLS_GOOGLE, - HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, --- -2.33.0 - diff --git a/patches/5.10/0009-surface-go-touchscreen.patch b/patches/5.10/0009-surface-go-touchscreen.patch deleted file mode 100644 index e5dbbbabd..000000000 --- a/patches/5.10/0009-surface-go-touchscreen.patch +++ /dev/null @@ -1,39 +0,0 @@ -From f1dc20f48a1c7581e9b48b3d606356ce7516d9ce Mon Sep 17 00:00:00 2001 -From: Zoltan Tamas Vajda -Date: Thu, 3 Jun 2021 10:50:55 +0200 -Subject: [PATCH] Added quirk for Surface Go touchscreen - -Patchset: surface-go-touchscreen ---- - drivers/hid/hid-ids.h | 1 + - drivers/hid/hid-input.c | 2 ++ - 2 files changed, 3 insertions(+) - -diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h -index 136b58a91c04..28150a359970 100644 ---- a/drivers/hid/hid-ids.h -+++ b/drivers/hid/hid-ids.h -@@ -390,6 +390,7 @@ - #define USB_DEVICE_ID_HP_X2 0x074d - #define USB_DEVICE_ID_HP_X2_10_COVER 0x0755 - #define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN 0x2706 -+#define I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN 0x261A - - #define USB_VENDOR_ID_ELECOM 0x056e - #define USB_DEVICE_ID_ELECOM_BM084 0x0061 -diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c -index d1ab2dccf6fd..4548eace4d9f 100644 ---- a/drivers/hid/hid-input.c -+++ b/drivers/hid/hid-input.c -@@ -324,6 +324,8 @@ static const struct hid_device_id hid_battery_quirks[] = { - HID_BATTERY_QUIRK_IGNORE }, - { HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN), - HID_BATTERY_QUIRK_IGNORE }, -+ { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN), -+ HID_BATTERY_QUIRK_IGNORE }, - {} - }; - --- -2.33.0 - diff --git a/patches/5.10/0010-surface-sensors.patch b/patches/5.10/0010-surface-sensors.patch deleted file mode 100644 index 4589ef6c8..000000000 --- a/patches/5.10/0010-surface-sensors.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 76b74003480901a745b19c4a28ccd4dcabdbe42f Mon Sep 17 00:00:00 2001 -From: Max Leiter -Date: Sat, 19 Dec 2020 17:50:55 -0800 -Subject: [PATCH] iio:light:apds9960 add detection for MSHW0184 ACPI device in - apds9960 driver - -The device is used in the Microsoft Surface Book 3 and Surface Pro 7 - -Signed-off-by: Max Leiter -Reviewed-by: Matt Ranostay -Link: https://lore.kernel.org/r/20201220015057.107246-1-maxwell.leiter@gmail.com -Signed-off-by: Jonathan Cameron -Patchset: surface-sensors ---- - drivers/iio/light/apds9960.c | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/drivers/iio/light/apds9960.c b/drivers/iio/light/apds9960.c -index 9afb3fcc74e6..20719141c03a 100644 ---- a/drivers/iio/light/apds9960.c -+++ b/drivers/iio/light/apds9960.c -@@ -8,6 +8,7 @@ - * TODO: gesture + proximity calib offsets - */ - -+#include - #include - #include - #include -@@ -1113,6 +1114,12 @@ static const struct i2c_device_id apds9960_id[] = { - }; - MODULE_DEVICE_TABLE(i2c, apds9960_id); - -+static const struct acpi_device_id apds9960_acpi_match[] = { -+ { "MSHW0184" }, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, apds9960_acpi_match); -+ - static const struct of_device_id apds9960_of_match[] = { - { .compatible = "avago,apds9960" }, - { } -@@ -1124,6 +1131,7 @@ static struct i2c_driver apds9960_driver = { - .name = APDS9960_DRV_NAME, - .of_match_table = apds9960_of_match, - .pm = &apds9960_pm_ops, -+ .acpi_match_table = apds9960_acpi_match, - }, - .probe = apds9960_probe, - .remove = apds9960_remove, --- -2.33.0 - diff --git a/patches/5.10/0011-cameras.patch b/patches/5.10/0011-cameras.patch deleted file mode 100644 index 5019c701a..000000000 --- a/patches/5.10/0011-cameras.patch +++ /dev/null @@ -1,11662 +0,0 @@ -From 2474b15f441fc37985fac9a9458c9b5135f20907 Mon Sep 17 00:00:00 2001 -From: Sakari Ailus -Date: Mon, 12 Oct 2020 21:04:11 +0300 -Subject: [PATCH] ipu3-cio2: Use unsigned values where appropriate - -Use unsigned values for width, height, bit shifts and registers, -effectively for all definitions that are not signed. - -Signed-off-by: Sakari Ailus -Reviewed-by: Andy Shevchenko -Acked-by: Laurent Pinchart -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2.h | 156 +++++++++++------------ - 1 file changed, 78 insertions(+), 78 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.h b/drivers/media/pci/intel/ipu3/ipu3-cio2.h -index 146492383aa5..7650d7998a3f 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2.h -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.h -@@ -13,20 +13,20 @@ - #define CIO2_PCI_BAR 0 - #define CIO2_DMA_MASK DMA_BIT_MASK(39) - --#define CIO2_IMAGE_MAX_WIDTH 4224 --#define CIO2_IMAGE_MAX_LENGTH 3136 -+#define CIO2_IMAGE_MAX_WIDTH 4224U -+#define CIO2_IMAGE_MAX_LENGTH 3136U - - /* 32MB = 8xFBPT_entry */ - #define CIO2_MAX_LOPS 8 - #define CIO2_MAX_BUFFERS (PAGE_SIZE / 16 / CIO2_MAX_LOPS) - #define CIO2_LOP_ENTRIES (PAGE_SIZE / sizeof(u32)) - --#define CIO2_PAD_SINK 0 --#define CIO2_PAD_SOURCE 1 --#define CIO2_PADS 2 -+#define CIO2_PAD_SINK 0U -+#define CIO2_PAD_SOURCE 1U -+#define CIO2_PADS 2U - --#define CIO2_NUM_DMA_CHAN 20 --#define CIO2_NUM_PORTS 4 /* DPHYs */ -+#define CIO2_NUM_DMA_CHAN 20U -+#define CIO2_NUM_PORTS 4U /* DPHYs */ - - /* 1 for each sensor */ - #define CIO2_QUEUES CIO2_NUM_PORTS -@@ -66,12 +66,12 @@ - #define CIO2_REG_MIPIBE_FORCE_RAW8 (CIO2_REG_MIPIBE_BASE + 0x20) - #define CIO2_REG_MIPIBE_FORCE_RAW8_ENABLE BIT(0) - #define CIO2_REG_MIPIBE_FORCE_RAW8_USE_TYPEID BIT(1) --#define CIO2_REG_MIPIBE_FORCE_RAW8_TYPEID_SHIFT 2 -+#define CIO2_REG_MIPIBE_FORCE_RAW8_TYPEID_SHIFT 2U - - #define CIO2_REG_MIPIBE_IRQ_STATUS (CIO2_REG_MIPIBE_BASE + 0x24) - #define CIO2_REG_MIPIBE_IRQ_CLEAR (CIO2_REG_MIPIBE_BASE + 0x28) - #define CIO2_REG_MIPIBE_GLOBAL_LUT_DISREGARD (CIO2_REG_MIPIBE_BASE + 0x68) --#define CIO2_MIPIBE_GLOBAL_LUT_DISREGARD 1 -+#define CIO2_MIPIBE_GLOBAL_LUT_DISREGARD 1U - #define CIO2_REG_MIPIBE_PKT_STALL_STATUS (CIO2_REG_MIPIBE_BASE + 0x6c) - #define CIO2_REG_MIPIBE_PARSE_GSP_THROUGH_LP_LUT_REG_IDX \ - (CIO2_REG_MIPIBE_BASE + 0x70) -@@ -79,10 +79,10 @@ - (CIO2_REG_MIPIBE_BASE + 0x74 + 4 * (vc)) - #define CIO2_REG_MIPIBE_LP_LUT_ENTRY(m) /* m = 0..15 */ \ - (CIO2_REG_MIPIBE_BASE + 0x84 + 4 * (m)) --#define CIO2_MIPIBE_LP_LUT_ENTRY_DISREGARD 1 --#define CIO2_MIPIBE_LP_LUT_ENTRY_SID_SHIFT 1 --#define CIO2_MIPIBE_LP_LUT_ENTRY_VC_SHIFT 5 --#define CIO2_MIPIBE_LP_LUT_ENTRY_FORMAT_TYPE_SHIFT 7 -+#define CIO2_MIPIBE_LP_LUT_ENTRY_DISREGARD 1U -+#define CIO2_MIPIBE_LP_LUT_ENTRY_SID_SHIFT 1U -+#define CIO2_MIPIBE_LP_LUT_ENTRY_VC_SHIFT 5U -+#define CIO2_MIPIBE_LP_LUT_ENTRY_FORMAT_TYPE_SHIFT 7U - - /* base register: CIO2_REG_PIPE_BASE(pipe) * CIO2_REG_IRQCTRL_BASE */ - /* IRQ registers are 18-bit wide, see cio2_irq_error for bit definitions */ -@@ -113,31 +113,31 @@ - #define CIO2_CGC_ROSC_DCGE BIT(12) - #define CIO2_CGC_XOSC_DCGE BIT(13) - #define CIO2_CGC_FLIS_DCGE BIT(14) --#define CIO2_CGC_CLKGATE_HOLDOFF_SHIFT 20 --#define CIO2_CGC_CSI_CLKGATE_HOLDOFF_SHIFT 24 -+#define CIO2_CGC_CLKGATE_HOLDOFF_SHIFT 20U -+#define CIO2_CGC_CSI_CLKGATE_HOLDOFF_SHIFT 24U - #define CIO2_REG_D0I3C 0x1408 - #define CIO2_D0I3C_I3 BIT(2) /* Set D0I3 */ - #define CIO2_D0I3C_RR BIT(3) /* Restore? */ - #define CIO2_REG_SWRESET 0x140c --#define CIO2_SWRESET_SWRESET 1 -+#define CIO2_SWRESET_SWRESET 1U - #define CIO2_REG_SENSOR_ACTIVE 0x1410 - #define CIO2_REG_INT_STS 0x1414 - #define CIO2_REG_INT_STS_EXT_OE 0x1418 --#define CIO2_INT_EXT_OE_DMAOE_SHIFT 0 -+#define CIO2_INT_EXT_OE_DMAOE_SHIFT 0U - #define CIO2_INT_EXT_OE_DMAOE_MASK 0x7ffff --#define CIO2_INT_EXT_OE_OES_SHIFT 24 -+#define CIO2_INT_EXT_OE_OES_SHIFT 24U - #define CIO2_INT_EXT_OE_OES_MASK (0xf << CIO2_INT_EXT_OE_OES_SHIFT) - #define CIO2_REG_INT_EN 0x1420 - #define CIO2_REG_INT_EN_IRQ (1 << 24) --#define CIO2_REG_INT_EN_IOS(dma) (1 << (((dma) >> 1) + 12)) -+#define CIO2_REG_INT_EN_IOS(dma) (1U << (((dma) >> 1U) + 12U)) - /* - * Interrupt on completion bit, Eg. DMA 0-3 maps to bit 0-3, - * DMA4 & DMA5 map to bit 4 ... DMA18 & DMA19 map to bit 11 Et cetera - */ --#define CIO2_INT_IOC(dma) (1 << ((dma) < 4 ? (dma) : ((dma) >> 1) + 2)) -+#define CIO2_INT_IOC(dma) (1U << ((dma) < 4U ? (dma) : ((dma) >> 1U) + 2U)) - #define CIO2_INT_IOC_SHIFT 0 - #define CIO2_INT_IOC_MASK (0x7ff << CIO2_INT_IOC_SHIFT) --#define CIO2_INT_IOS_IOLN(dma) (1 << (((dma) >> 1) + 12)) -+#define CIO2_INT_IOS_IOLN(dma) (1U << (((dma) >> 1U) + 12U)) - #define CIO2_INT_IOS_IOLN_SHIFT 12 - #define CIO2_INT_IOS_IOLN_MASK (0x3ff << CIO2_INT_IOS_IOLN_SHIFT) - #define CIO2_INT_IOIE BIT(22) -@@ -145,32 +145,32 @@ - #define CIO2_INT_IOIRQ BIT(24) - #define CIO2_REG_INT_EN_EXT_OE 0x1424 - #define CIO2_REG_DMA_DBG 0x1448 --#define CIO2_REG_DMA_DBG_DMA_INDEX_SHIFT 0 -+#define CIO2_REG_DMA_DBG_DMA_INDEX_SHIFT 0U - #define CIO2_REG_PBM_ARB_CTRL 0x1460 --#define CIO2_PBM_ARB_CTRL_LANES_DIV 0 /* 4-4-2-2 lanes */ --#define CIO2_PBM_ARB_CTRL_LANES_DIV_SHIFT 0 -+#define CIO2_PBM_ARB_CTRL_LANES_DIV 0U /* 4-4-2-2 lanes */ -+#define CIO2_PBM_ARB_CTRL_LANES_DIV_SHIFT 0U - #define CIO2_PBM_ARB_CTRL_LE_EN BIT(7) --#define CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN 2 --#define CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN_SHIFT 8 --#define CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP 480 --#define CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP_SHIFT 16 -+#define CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN 2U -+#define CIO2_PBM_ARB_CTRL_PLL_POST_SHTDN_SHIFT 8U -+#define CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP 480U -+#define CIO2_PBM_ARB_CTRL_PLL_AHD_WK_UP_SHIFT 16U - #define CIO2_REG_PBM_WMCTRL1 0x1464 --#define CIO2_PBM_WMCTRL1_MIN_2CK_SHIFT 0 --#define CIO2_PBM_WMCTRL1_MID1_2CK_SHIFT 8 --#define CIO2_PBM_WMCTRL1_MID2_2CK_SHIFT 16 -+#define CIO2_PBM_WMCTRL1_MIN_2CK_SHIFT 0U -+#define CIO2_PBM_WMCTRL1_MID1_2CK_SHIFT 8U -+#define CIO2_PBM_WMCTRL1_MID2_2CK_SHIFT 16U - #define CIO2_PBM_WMCTRL1_TS_COUNT_DISABLE BIT(31) - #define CIO2_PBM_WMCTRL1_MIN_2CK (4 << CIO2_PBM_WMCTRL1_MIN_2CK_SHIFT) - #define CIO2_PBM_WMCTRL1_MID1_2CK (16 << CIO2_PBM_WMCTRL1_MID1_2CK_SHIFT) - #define CIO2_PBM_WMCTRL1_MID2_2CK (21 << CIO2_PBM_WMCTRL1_MID2_2CK_SHIFT) - #define CIO2_REG_PBM_WMCTRL2 0x1468 --#define CIO2_PBM_WMCTRL2_HWM_2CK 40 --#define CIO2_PBM_WMCTRL2_HWM_2CK_SHIFT 0 --#define CIO2_PBM_WMCTRL2_LWM_2CK 22 --#define CIO2_PBM_WMCTRL2_LWM_2CK_SHIFT 8 --#define CIO2_PBM_WMCTRL2_OBFFWM_2CK 2 --#define CIO2_PBM_WMCTRL2_OBFFWM_2CK_SHIFT 16 --#define CIO2_PBM_WMCTRL2_TRANSDYN 1 --#define CIO2_PBM_WMCTRL2_TRANSDYN_SHIFT 24 -+#define CIO2_PBM_WMCTRL2_HWM_2CK 40U -+#define CIO2_PBM_WMCTRL2_HWM_2CK_SHIFT 0U -+#define CIO2_PBM_WMCTRL2_LWM_2CK 22U -+#define CIO2_PBM_WMCTRL2_LWM_2CK_SHIFT 8U -+#define CIO2_PBM_WMCTRL2_OBFFWM_2CK 2U -+#define CIO2_PBM_WMCTRL2_OBFFWM_2CK_SHIFT 16U -+#define CIO2_PBM_WMCTRL2_TRANSDYN 1U -+#define CIO2_PBM_WMCTRL2_TRANSDYN_SHIFT 24U - #define CIO2_PBM_WMCTRL2_DYNWMEN BIT(28) - #define CIO2_PBM_WMCTRL2_OBFF_MEM_EN BIT(29) - #define CIO2_PBM_WMCTRL2_OBFF_CPU_EN BIT(30) -@@ -178,12 +178,12 @@ - #define CIO2_REG_PBM_TS_COUNT 0x146c - #define CIO2_REG_PBM_FOPN_ABORT 0x1474 - /* below n = 0..3 */ --#define CIO2_PBM_FOPN_ABORT(n) (0x1 << 8 * (n)) --#define CIO2_PBM_FOPN_FORCE_ABORT(n) (0x2 << 8 * (n)) --#define CIO2_PBM_FOPN_FRAMEOPEN(n) (0x8 << 8 * (n)) -+#define CIO2_PBM_FOPN_ABORT(n) (0x1 << 8U * (n)) -+#define CIO2_PBM_FOPN_FORCE_ABORT(n) (0x2 << 8U * (n)) -+#define CIO2_PBM_FOPN_FRAMEOPEN(n) (0x8 << 8U * (n)) - #define CIO2_REG_LTRCTRL 0x1480 - #define CIO2_LTRCTRL_LTRDYNEN BIT(16) --#define CIO2_LTRCTRL_LTRSTABLETIME_SHIFT 8 -+#define CIO2_LTRCTRL_LTRSTABLETIME_SHIFT 8U - #define CIO2_LTRCTRL_LTRSTABLETIME_MASK 0xff - #define CIO2_LTRCTRL_LTRSEL1S3 BIT(7) - #define CIO2_LTRCTRL_LTRSEL1S2 BIT(6) -@@ -195,28 +195,28 @@ - #define CIO2_LTRCTRL_LTRSEL2S0 BIT(0) - #define CIO2_REG_LTRVAL23 0x1484 - #define CIO2_REG_LTRVAL01 0x1488 --#define CIO2_LTRVAL02_VAL_SHIFT 0 --#define CIO2_LTRVAL02_SCALE_SHIFT 10 --#define CIO2_LTRVAL13_VAL_SHIFT 16 --#define CIO2_LTRVAL13_SCALE_SHIFT 26 -+#define CIO2_LTRVAL02_VAL_SHIFT 0U -+#define CIO2_LTRVAL02_SCALE_SHIFT 10U -+#define CIO2_LTRVAL13_VAL_SHIFT 16U -+#define CIO2_LTRVAL13_SCALE_SHIFT 26U - --#define CIO2_LTRVAL0_VAL 175 -+#define CIO2_LTRVAL0_VAL 175U - /* Value times 1024 ns */ --#define CIO2_LTRVAL0_SCALE 2 --#define CIO2_LTRVAL1_VAL 90 --#define CIO2_LTRVAL1_SCALE 2 --#define CIO2_LTRVAL2_VAL 90 --#define CIO2_LTRVAL2_SCALE 2 --#define CIO2_LTRVAL3_VAL 90 --#define CIO2_LTRVAL3_SCALE 2 -+#define CIO2_LTRVAL0_SCALE 2U -+#define CIO2_LTRVAL1_VAL 90U -+#define CIO2_LTRVAL1_SCALE 2U -+#define CIO2_LTRVAL2_VAL 90U -+#define CIO2_LTRVAL2_SCALE 2U -+#define CIO2_LTRVAL3_VAL 90U -+#define CIO2_LTRVAL3_SCALE 2U - - #define CIO2_REG_CDMABA(n) (0x1500 + 0x10 * (n)) /* n = 0..19 */ - #define CIO2_REG_CDMARI(n) (0x1504 + 0x10 * (n)) --#define CIO2_CDMARI_FBPT_RP_SHIFT 0 -+#define CIO2_CDMARI_FBPT_RP_SHIFT 0U - #define CIO2_CDMARI_FBPT_RP_MASK 0xff - #define CIO2_REG_CDMAC0(n) (0x1508 + 0x10 * (n)) --#define CIO2_CDMAC0_FBPT_LEN_SHIFT 0 --#define CIO2_CDMAC0_FBPT_WIDTH_SHIFT 8 -+#define CIO2_CDMAC0_FBPT_LEN_SHIFT 0U -+#define CIO2_CDMAC0_FBPT_WIDTH_SHIFT 8U - #define CIO2_CDMAC0_FBPT_NS BIT(25) - #define CIO2_CDMAC0_DMA_INTR_ON_FS BIT(26) - #define CIO2_CDMAC0_DMA_INTR_ON_FE BIT(27) -@@ -225,12 +225,12 @@ - #define CIO2_CDMAC0_DMA_EN BIT(30) - #define CIO2_CDMAC0_DMA_HALTED BIT(31) - #define CIO2_REG_CDMAC1(n) (0x150c + 0x10 * (n)) --#define CIO2_CDMAC1_LINENUMINT_SHIFT 0 --#define CIO2_CDMAC1_LINENUMUPDATE_SHIFT 16 -+#define CIO2_CDMAC1_LINENUMINT_SHIFT 0U -+#define CIO2_CDMAC1_LINENUMUPDATE_SHIFT 16U - /* n = 0..3 */ - #define CIO2_REG_PXM_PXF_FMT_CFG0(n) (0x1700 + 0x30 * (n)) --#define CIO2_PXM_PXF_FMT_CFG_SID0_SHIFT 0 --#define CIO2_PXM_PXF_FMT_CFG_SID1_SHIFT 16 -+#define CIO2_PXM_PXF_FMT_CFG_SID0_SHIFT 0U -+#define CIO2_PXM_PXF_FMT_CFG_SID1_SHIFT 16U - #define CIO2_PXM_PXF_FMT_CFG_PCK_64B (0 << 0) - #define CIO2_PXM_PXF_FMT_CFG_PCK_32B (1 << 0) - #define CIO2_PXM_PXF_FMT_CFG_BPP_08 (0 << 2) -@@ -249,27 +249,27 @@ - #define CIO2_PXM_PXF_FMT_CFG_PSWAP4_2ND_BD (1 << 10) - #define CIO2_REG_INT_STS_EXT_IE 0x17e4 - #define CIO2_REG_INT_EN_EXT_IE 0x17e8 --#define CIO2_INT_EXT_IE_ECC_RE(n) (0x01 << (8 * (n))) --#define CIO2_INT_EXT_IE_DPHY_NR(n) (0x02 << (8 * (n))) --#define CIO2_INT_EXT_IE_ECC_NR(n) (0x04 << (8 * (n))) --#define CIO2_INT_EXT_IE_CRCERR(n) (0x08 << (8 * (n))) --#define CIO2_INT_EXT_IE_INTERFRAMEDATA(n) (0x10 << (8 * (n))) --#define CIO2_INT_EXT_IE_PKT2SHORT(n) (0x20 << (8 * (n))) --#define CIO2_INT_EXT_IE_PKT2LONG(n) (0x40 << (8 * (n))) --#define CIO2_INT_EXT_IE_IRQ(n) (0x80 << (8 * (n))) -+#define CIO2_INT_EXT_IE_ECC_RE(n) (0x01 << (8U * (n))) -+#define CIO2_INT_EXT_IE_DPHY_NR(n) (0x02 << (8U * (n))) -+#define CIO2_INT_EXT_IE_ECC_NR(n) (0x04 << (8U * (n))) -+#define CIO2_INT_EXT_IE_CRCERR(n) (0x08 << (8U * (n))) -+#define CIO2_INT_EXT_IE_INTERFRAMEDATA(n) (0x10 << (8U * (n))) -+#define CIO2_INT_EXT_IE_PKT2SHORT(n) (0x20 << (8U * (n))) -+#define CIO2_INT_EXT_IE_PKT2LONG(n) (0x40 << (8U * (n))) -+#define CIO2_INT_EXT_IE_IRQ(n) (0x80 << (8U * (n))) - #define CIO2_REG_PXM_FRF_CFG(n) (0x1720 + 0x30 * (n)) - #define CIO2_PXM_FRF_CFG_FNSEL BIT(0) - #define CIO2_PXM_FRF_CFG_FN_RST BIT(1) - #define CIO2_PXM_FRF_CFG_ABORT BIT(2) --#define CIO2_PXM_FRF_CFG_CRC_TH_SHIFT 3 -+#define CIO2_PXM_FRF_CFG_CRC_TH_SHIFT 3U - #define CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NR BIT(8) - #define CIO2_PXM_FRF_CFG_MSK_ECC_RE BIT(9) - #define CIO2_PXM_FRF_CFG_MSK_ECC_DPHY_NE BIT(10) --#define CIO2_PXM_FRF_CFG_EVEN_ODD_MODE_SHIFT 11 -+#define CIO2_PXM_FRF_CFG_EVEN_ODD_MODE_SHIFT 11U - #define CIO2_PXM_FRF_CFG_MASK_CRC_THRES BIT(13) - #define CIO2_PXM_FRF_CFG_MASK_CSI_ACCEPT BIT(14) - #define CIO2_PXM_FRF_CFG_CIOHC_FS_MODE BIT(15) --#define CIO2_PXM_FRF_CFG_CIOHC_FRST_FRM_SHIFT 16 -+#define CIO2_PXM_FRF_CFG_CIOHC_FRST_FRM_SHIFT 16U - #define CIO2_REG_PXM_SID2BID0(n) (0x1724 + 0x30 * (n)) - #define CIO2_FB_HPLL_FREQ 0x2 - #define CIO2_ISCLK_RATIO 0xc -@@ -278,14 +278,14 @@ - - #define CIO2_INT_EN_EXT_OE_MASK 0x8f0fffff - --#define CIO2_CGC_CLKGATE_HOLDOFF 3 --#define CIO2_CGC_CSI_CLKGATE_HOLDOFF 5 -+#define CIO2_CGC_CLKGATE_HOLDOFF 3U -+#define CIO2_CGC_CSI_CLKGATE_HOLDOFF 5U - - #define CIO2_PXM_FRF_CFG_CRC_TH 16 - - #define CIO2_INT_EN_EXT_IE_MASK 0xffffffff - --#define CIO2_DMA_CHAN 0 -+#define CIO2_DMA_CHAN 0U - - #define CIO2_CSIRX_DLY_CNT_CLANE_IDX -1 - -@@ -302,8 +302,8 @@ - #define CIO2_CSIRX_DLY_CNT_TERMEN_DEFAULT 0x4 - #define CIO2_CSIRX_DLY_CNT_SETTLE_DEFAULT 0x570 - --#define CIO2_PMCSR_OFFSET 4 --#define CIO2_PMCSR_D0D3_SHIFT 2 -+#define CIO2_PMCSR_OFFSET 4U -+#define CIO2_PMCSR_D0D3_SHIFT 2U - #define CIO2_PMCSR_D3 0x3 - - struct cio2_csi2_timing { --- -2.33.0 - -From 80cc88e57e3b5b737e093f1a0b42a2f6da577ae6 Mon Sep 17 00:00:00 2001 -From: Sakari Ailus -Date: Mon, 12 Oct 2020 21:04:12 +0300 -Subject: [PATCH] ipu3-cio2: Remove explicit type from frame size checks - -Now that the values are unsigned, we can remove explicit cast to u32. - -Signed-off-by: Sakari Ailus -Reviewed-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2.c | 5 ++--- - 1 file changed, 2 insertions(+), 3 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2.c -index 2fe4a0bd0284..2061d7a50700 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.c -@@ -1283,9 +1283,8 @@ static int cio2_subdev_set_fmt(struct v4l2_subdev *sd, - } - } - -- fmt->format.width = min_t(u32, fmt->format.width, CIO2_IMAGE_MAX_WIDTH); -- fmt->format.height = min_t(u32, fmt->format.height, -- CIO2_IMAGE_MAX_LENGTH); -+ fmt->format.width = min(fmt->format.width, CIO2_IMAGE_MAX_WIDTH); -+ fmt->format.height = min(fmt->format.height, CIO2_IMAGE_MAX_LENGTH); - fmt->format.field = V4L2_FIELD_NONE; - - mutex_lock(&q->subdev_lock); --- -2.33.0 - -From dc8f0b080a1f793d85e993e1796883614e9ce6cc Mon Sep 17 00:00:00 2001 -From: Sakari Ailus -Date: Mon, 12 Oct 2020 21:04:13 +0300 -Subject: [PATCH] ipu3-cio2: Rename CIO2_IMAGE_MAX_LENGTH as - CIO2_IMAGE_MAX_HEIGHT - -CIO2_IMAGE_MAX_LENGTH is the maximum width of the image. Rename it as -"CIO2_IMAGE_MAX_HEIGHT" in order to better describe what it is. - -Suggested-by: Laurent Pinchart -Signed-off-by: Sakari Ailus -Reviewed-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2.c | 6 +++--- - drivers/media/pci/intel/ipu3/ipu3-cio2.h | 2 +- - 2 files changed, 4 insertions(+), 4 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2.c -index 2061d7a50700..a0202166e2b0 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.c -@@ -1095,8 +1095,8 @@ static int cio2_v4l2_try_fmt(struct file *file, void *fh, struct v4l2_format *f) - /* Only supports up to 4224x3136 */ - if (mpix->width > CIO2_IMAGE_MAX_WIDTH) - mpix->width = CIO2_IMAGE_MAX_WIDTH; -- if (mpix->height > CIO2_IMAGE_MAX_LENGTH) -- mpix->height = CIO2_IMAGE_MAX_LENGTH; -+ if (mpix->height > CIO2_IMAGE_MAX_HEIGHT) -+ mpix->height = CIO2_IMAGE_MAX_HEIGHT; - - mpix->num_planes = 1; - mpix->pixelformat = fmt->fourcc; -@@ -1284,7 +1284,7 @@ static int cio2_subdev_set_fmt(struct v4l2_subdev *sd, - } - - fmt->format.width = min(fmt->format.width, CIO2_IMAGE_MAX_WIDTH); -- fmt->format.height = min(fmt->format.height, CIO2_IMAGE_MAX_LENGTH); -+ fmt->format.height = min(fmt->format.height, CIO2_IMAGE_MAX_HEIGHT); - fmt->format.field = V4L2_FIELD_NONE; - - mutex_lock(&q->subdev_lock); -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.h b/drivers/media/pci/intel/ipu3/ipu3-cio2.h -index 7650d7998a3f..ccf0b85ae36f 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2.h -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.h -@@ -14,7 +14,7 @@ - #define CIO2_DMA_MASK DMA_BIT_MASK(39) - - #define CIO2_IMAGE_MAX_WIDTH 4224U --#define CIO2_IMAGE_MAX_LENGTH 3136U -+#define CIO2_IMAGE_MAX_HEIGHT 3136U - - /* 32MB = 8xFBPT_entry */ - #define CIO2_MAX_LOPS 8 --- -2.33.0 - -From 60dce090c91b1919a00bc95124dd008d049aa3d4 Mon Sep 17 00:00:00 2001 -From: Sakari Ailus -Date: Tue, 13 Oct 2020 17:25:35 +0300 -Subject: [PATCH] ipu3-cio2: Check receved the size against payload size, not - buffer size - -Compare the received size of the payload size, not the allocated size of -the buffer that is page aligned. This way also images that aren't aligned -to page size are not warned about. - -Also wrap a line over 80 characters. - -Suggested-by: Laurent Pinchart -Signed-off-by: Sakari Ailus -Reviewed-by: Laurent Pinchart -Tested-by: Jean-Michel Hautbois -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2.c | 12 +++++++----- - 1 file changed, 7 insertions(+), 5 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2.c -index a0202166e2b0..c0cd4606810c 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.c -@@ -561,7 +561,9 @@ static void cio2_buffer_done(struct cio2_device *cio2, unsigned int dma_chan) - - b = q->bufs[q->bufs_first]; - if (b) { -- unsigned int bytes = entry[1].second_entry.num_of_bytes; -+ unsigned int received = entry[1].second_entry.num_of_bytes; -+ unsigned long payload = -+ vb2_get_plane_payload(&b->vbb.vb2_buf, 0); - - q->bufs[q->bufs_first] = NULL; - atomic_dec(&q->bufs_queued); -@@ -571,10 +573,10 @@ static void cio2_buffer_done(struct cio2_device *cio2, unsigned int dma_chan) - b->vbb.vb2_buf.timestamp = ns; - b->vbb.field = V4L2_FIELD_NONE; - b->vbb.sequence = atomic_read(&q->frame_sequence); -- if (b->vbb.vb2_buf.planes[0].length != bytes) -- dev_warn(dev, "buffer length is %d received %d\n", -- b->vbb.vb2_buf.planes[0].length, -- bytes); -+ if (payload != received) -+ dev_warn(dev, -+ "payload length is %lu, received %u\n", -+ payload, received); - vb2_buffer_done(&b->vbb.vb2_buf, VB2_BUF_STATE_DONE); - } - atomic_inc(&q->frame_sequence); --- -2.33.0 - -From 77c2761d168b618b79be1dc908a649f2f3ba6e00 Mon Sep 17 00:00:00 2001 -From: Andy Shevchenko -Date: Wed, 30 Dec 2020 22:44:05 +0200 -Subject: [PATCH] media: ipu3-cio2: Add headers that ipu3-cio2.h is direct user - of - -Add headers that ipu3-cio2.h is direct user of. - -Signed-off-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Reviewed-by: Daniel Scally -Tested-by: Daniel Scally -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2.h | 18 ++++++++++++++++++ - 1 file changed, 18 insertions(+) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.h b/drivers/media/pci/intel/ipu3/ipu3-cio2.h -index ccf0b85ae36f..62187ab5ae43 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2.h -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.h -@@ -4,8 +4,26 @@ - #ifndef __IPU3_CIO2_H - #define __IPU3_CIO2_H - -+#include -+#include -+#include -+#include - #include - -+#include -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+struct cio2_fbpt_entry; /* defined here, after the first usage */ -+struct pci_dev; -+ - #define CIO2_NAME "ipu3-cio2" - #define CIO2_DEVICE_NAME "Intel IPU3 CIO2" - #define CIO2_ENTITY_NAME "ipu3-csi2" --- -2.33.0 - -From 8baab94b24db7550d05fa3360efc24fb1cad79e9 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 24 Oct 2020 22:42:28 +0100 -Subject: [PATCH] device property: Return true in fwnode_device_is_available - for NULL ops - -Some types of fwnode_handle do not implement the device_is_available() -check, such as those created by software_nodes. There isn't really a -meaningful way to check for the availability of a device that doesn't -actually exist, so if the check isn't implemented just assume that the -"device" is present. - -Suggested-by: Laurent Pinchart -Reviewed-by: Laurent Pinchart -Reviewed-by: Andy Shevchenko -Acked-by: Sakari Ailus -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/base/property.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/base/property.c b/drivers/base/property.c -index 4c43d30145c6..bc9c634df6df 100644 ---- a/drivers/base/property.c -+++ b/drivers/base/property.c -@@ -785,9 +785,15 @@ EXPORT_SYMBOL_GPL(fwnode_handle_put); - /** - * fwnode_device_is_available - check if a device is available for use - * @fwnode: Pointer to the fwnode of the device. -+ * -+ * For fwnode node types that don't implement the .device_is_available() -+ * operation, this function returns true. - */ - bool fwnode_device_is_available(const struct fwnode_handle *fwnode) - { -+ if (!fwnode_has_op(fwnode, device_is_available)) -+ return true; -+ - return fwnode_call_bool_op(fwnode, device_is_available); - } - EXPORT_SYMBOL_GPL(fwnode_device_is_available); --- -2.33.0 - -From ea49a99ed515607bdb999f35233be6c651492e4a Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 21 Nov 2020 22:06:38 +0000 -Subject: [PATCH] device property: Call fwnode_graph_get_endpoint_by_id() for - fwnode->secondary - -This function is used to find fwnode endpoints against a device. In -some instances those endpoints are software nodes which are children of -fwnode->secondary. Add support to fwnode_graph_get_endpoint_by_id() to -find those endpoints by recursively calling itself passing the ptr to -fwnode->secondary in the event no endpoint is found for the primary. - -Reviewed-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Acked-by: Sakari Ailus -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/base/property.c | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/drivers/base/property.c b/drivers/base/property.c -index bc9c634df6df..ddba75d90af2 100644 ---- a/drivers/base/property.c -+++ b/drivers/base/property.c -@@ -1163,7 +1163,14 @@ fwnode_graph_get_endpoint_by_id(const struct fwnode_handle *fwnode, - best_ep_id = fwnode_ep.id; - } - -- return best_ep; -+ if (best_ep) -+ return best_ep; -+ -+ if (fwnode && !IS_ERR_OR_NULL(fwnode->secondary)) -+ return fwnode_graph_get_endpoint_by_id(fwnode->secondary, port, -+ endpoint, flags); -+ -+ return NULL; - } - EXPORT_SYMBOL_GPL(fwnode_graph_get_endpoint_by_id); - --- -2.33.0 - -From 87fef2d943894c2b371af910231d00c662179442 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 25 Oct 2020 22:49:08 +0000 -Subject: [PATCH] software_node: Enforce parent before child ordering of nodes - arrays - -Registering software_nodes with the .parent member set to point to a -currently unregistered software_node has the potential for problems, -so enforce parent -> child ordering in arrays passed in to -software_node_register_nodes(). - -Software nodes that are children of another software node should be -unregistered before their parent. To allow easy unregistering of an array -of software_nodes ordered parent to child, reverse the order in which -software_node_unregister_nodes() unregisters software_nodes. - -Suggested-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/base/swnode.c | 42 ++++++++++++++++++++++++++++++------------ - 1 file changed, 30 insertions(+), 12 deletions(-) - -diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c -index 206bd4d7d7e2..eb89bdb9232c 100644 ---- a/drivers/base/swnode.c -+++ b/drivers/base/swnode.c -@@ -692,7 +692,11 @@ swnode_register(const struct software_node *node, struct swnode *parent, - * software_node_register_nodes - Register an array of software nodes - * @nodes: Zero terminated array of software nodes to be registered - * -- * Register multiple software nodes at once. -+ * Register multiple software nodes at once. If any node in the array -+ * has its .parent pointer set (which can only be to another software_node), -+ * then its parent **must** have been registered before it is; either outside -+ * of this function or by ordering the array such that parent comes before -+ * child. - */ - int software_node_register_nodes(const struct software_node *nodes) - { -@@ -700,14 +704,23 @@ int software_node_register_nodes(const struct software_node *nodes) - int i; - - for (i = 0; nodes[i].name; i++) { -- ret = software_node_register(&nodes[i]); -- if (ret) { -- software_node_unregister_nodes(nodes); -- return ret; -+ const struct software_node *parent = nodes[i].parent; -+ -+ if (parent && !software_node_to_swnode(parent)) { -+ ret = -EINVAL; -+ goto err_unregister_nodes; - } -+ -+ ret = software_node_register(&nodes[i]); -+ if (ret) -+ goto err_unregister_nodes; - } - - return 0; -+ -+err_unregister_nodes: -+ software_node_unregister_nodes(nodes); -+ return ret; - } - EXPORT_SYMBOL_GPL(software_node_register_nodes); - -@@ -715,18 +728,23 @@ EXPORT_SYMBOL_GPL(software_node_register_nodes); - * software_node_unregister_nodes - Unregister an array of software nodes - * @nodes: Zero terminated array of software nodes to be unregistered - * -- * Unregister multiple software nodes at once. -+ * Unregister multiple software nodes at once. If parent pointers are set up -+ * in any of the software nodes then the array **must** be ordered such that -+ * parents come before their children. - * -- * NOTE: Be careful using this call if the nodes had parent pointers set up in -- * them before registering. If so, it is wiser to remove the nodes -- * individually, in the correct order (child before parent) instead of relying -- * on the sequential order of the list of nodes in the array. -+ * NOTE: If you are uncertain whether the array is ordered such that -+ * parents will be unregistered before their children, it is wiser to -+ * remove the nodes individually, in the correct order (child before -+ * parent). - */ - void software_node_unregister_nodes(const struct software_node *nodes) - { -- int i; -+ unsigned int i = 0; -+ -+ while (nodes[i].name) -+ i++; - -- for (i = 0; nodes[i].name; i++) -+ while (i--) - software_node_unregister(&nodes[i]); - } - EXPORT_SYMBOL_GPL(software_node_unregister_nodes); --- -2.33.0 - -From e32a376cde000010f438c6fa0068e56765ac6eca Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 21 Oct 2020 22:25:03 +0100 -Subject: [PATCH] software_node: unregister software_nodes in reverse order - -To maintain consistency with software_node_unregister_nodes(), reverse -the order in which the software_node_unregister_node_group() function -unregisters nodes. - -Reported-by: kernel test robot -Reported-by: Dan Carpenter -Reviewed-by: Laurent Pinchart -Reviewed-by: Sakari Ailus -Suggested-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/base/swnode.c | 15 +++++++++++---- - 1 file changed, 11 insertions(+), 4 deletions(-) - -diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c -index eb89bdb9232c..032b24f60c78 100644 ---- a/drivers/base/swnode.c -+++ b/drivers/base/swnode.c -@@ -779,16 +779,23 @@ EXPORT_SYMBOL_GPL(software_node_register_node_group); - * software_node_unregister_node_group - Unregister a group of software nodes - * @node_group: NULL terminated array of software node pointers to be unregistered - * -- * Unregister multiple software nodes at once. -+ * Unregister multiple software nodes at once. The array will be unwound in -+ * reverse order (i.e. last entry first) and thus if any members of the array are -+ * children of another member then the children must appear later in the list such -+ * that they are unregistered first. - */ --void software_node_unregister_node_group(const struct software_node **node_group) -+void software_node_unregister_node_group( -+ const struct software_node **node_group) - { -- unsigned int i; -+ unsigned int i = 0; - - if (!node_group) - return; - -- for (i = 0; node_group[i]; i++) -+ while (node_group[i]) -+ i++; -+ -+ while (i--) - software_node_unregister(node_group[i]); - } - EXPORT_SYMBOL_GPL(software_node_unregister_node_group); --- -2.33.0 - -From 8d589207e02ba1733fc4d9b95eb6529871a8a291 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 22 Dec 2020 13:09:05 +0000 -Subject: [PATCH] device property: Define format macros for ports and endpoints - -OF, ACPI and software_nodes all implement graphs including nodes for ports -and endpoints. These are all intended to be named with a common schema, -as "port@n" and "endpoint@n" where n is an unsigned int representing the -index of the node. To ensure commonality across the subsystems, provide a -set of macros to define the format. - -Suggested-by: Andy Shevchenko -Reviewed-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - include/linux/fwnode.h | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/include/linux/fwnode.h b/include/linux/fwnode.h -index 9506f8ec0974..72d36d46287d 100644 ---- a/include/linux/fwnode.h -+++ b/include/linux/fwnode.h -@@ -32,6 +32,13 @@ struct fwnode_endpoint { - const struct fwnode_handle *local_fwnode; - }; - -+/* -+ * ports and endpoints defined as software_nodes should all follow a common -+ * naming scheme; use these macros to ensure commonality. -+ */ -+#define SWNODE_GRAPH_PORT_NAME_FMT "port@%u" -+#define SWNODE_GRAPH_ENDPOINT_NAME_FMT "endpoint@%u" -+ - #define NR_FWNODE_REFERENCE_ARGS 8 - - /** --- -2.33.0 - -From b68f8d0583a0da5e4a766953b524016295d5df4b Mon Sep 17 00:00:00 2001 -From: Heikki Krogerus -Date: Tue, 15 Sep 2020 15:47:46 +0100 -Subject: [PATCH] software_node: Add support for fwnode_graph*() family of - functions - -This implements the remaining .graph_*() callbacks in the fwnode -operations structure for the software nodes. That makes the -fwnode_graph_*() functions available in the drivers also when software -nodes are used. - -The implementation tries to mimic the "OF graph" as much as possible, but -there is no support for the "reg" device property. The ports will need to -have the index in their name which starts with "port@" (for example -"port@0", "port@1", ...) and endpoints will use the index of the software -node that is given to them during creation. The port nodes can also be -grouped under a specially named "ports" subnode, just like in DT, if -necessary. - -The remote-endpoints are reference properties under the endpoint nodes -that are named "remote-endpoint". - -Reviewed-by: Laurent Pinchart -Reviewed-by: Andy Shevchenko -Signed-off-by: Heikki Krogerus -Co-developed-by: Daniel Scally -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/base/swnode.c | 115 +++++++++++++++++++++++++++++++++++++++++- - 1 file changed, 114 insertions(+), 1 deletion(-) - -diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c -index 032b24f60c78..7f056c5e0ed3 100644 ---- a/drivers/base/swnode.c -+++ b/drivers/base/swnode.c -@@ -540,6 +540,115 @@ software_node_get_reference_args(const struct fwnode_handle *fwnode, - return 0; - } - -+static struct fwnode_handle * -+swnode_graph_find_next_port(const struct fwnode_handle *parent, -+ struct fwnode_handle *port) -+{ -+ struct fwnode_handle *old = port; -+ -+ while ((port = software_node_get_next_child(parent, old))) { -+ /* -+ * fwnode ports have naming style "port@", so we search for any -+ * children that follow that convention. -+ */ -+ if (!strncmp(to_swnode(port)->node->name, "port@", -+ strlen("port@"))) -+ return port; -+ old = port; -+ } -+ -+ return NULL; -+} -+ -+static struct fwnode_handle * -+software_node_graph_get_next_endpoint(const struct fwnode_handle *fwnode, -+ struct fwnode_handle *endpoint) -+{ -+ struct swnode *swnode = to_swnode(fwnode); -+ struct fwnode_handle *parent; -+ struct fwnode_handle *port; -+ -+ if (!swnode) -+ return NULL; -+ -+ if (endpoint) { -+ port = software_node_get_parent(endpoint); -+ parent = software_node_get_parent(port); -+ } else { -+ parent = software_node_get_named_child_node(fwnode, "ports"); -+ if (!parent) -+ parent = software_node_get(&swnode->fwnode); -+ -+ port = swnode_graph_find_next_port(parent, NULL); -+ } -+ -+ for (; port; port = swnode_graph_find_next_port(parent, port)) { -+ endpoint = software_node_get_next_child(port, endpoint); -+ if (endpoint) { -+ fwnode_handle_put(port); -+ break; -+ } -+ } -+ -+ fwnode_handle_put(parent); -+ -+ return endpoint; -+} -+ -+static struct fwnode_handle * -+software_node_graph_get_remote_endpoint(const struct fwnode_handle *fwnode) -+{ -+ struct swnode *swnode = to_swnode(fwnode); -+ const struct software_node_ref_args *ref; -+ const struct property_entry *prop; -+ -+ if (!swnode) -+ return NULL; -+ -+ prop = property_entry_get(swnode->node->properties, "remote-endpoint"); -+ if (!prop || prop->type != DEV_PROP_REF || prop->is_inline) -+ return NULL; -+ -+ ref = prop->pointer; -+ -+ return software_node_get(software_node_fwnode(ref[0].node)); -+} -+ -+static struct fwnode_handle * -+software_node_graph_get_port_parent(struct fwnode_handle *fwnode) -+{ -+ struct swnode *swnode = to_swnode(fwnode); -+ -+ swnode = swnode->parent; -+ if (swnode && !strcmp(swnode->node->name, "ports")) -+ swnode = swnode->parent; -+ -+ return swnode ? software_node_get(&swnode->fwnode) : NULL; -+} -+ -+static int -+software_node_graph_parse_endpoint(const struct fwnode_handle *fwnode, -+ struct fwnode_endpoint *endpoint) -+{ -+ struct swnode *swnode = to_swnode(fwnode); -+ const char *parent_name = swnode->parent->node->name; -+ int ret; -+ -+ if (strlen("port@") >= strlen(parent_name) || -+ strncmp(parent_name, "port@", strlen("port@"))) -+ return -EINVAL; -+ -+ /* Ports have naming style "port@n", we need to select the n */ -+ ret = kstrtou32(parent_name + strlen("port@"), 10, &endpoint->port); -+ if (ret) -+ return ret; -+ -+ endpoint->id = swnode->id; -+ endpoint->local_fwnode = fwnode; -+ -+ return 0; -+} -+ - static const struct fwnode_operations software_node_ops = { - .get = software_node_get, - .put = software_node_put, -@@ -551,7 +660,11 @@ static const struct fwnode_operations software_node_ops = { - .get_parent = software_node_get_parent, - .get_next_child_node = software_node_get_next_child, - .get_named_child_node = software_node_get_named_child_node, -- .get_reference_args = software_node_get_reference_args -+ .get_reference_args = software_node_get_reference_args, -+ .graph_get_next_endpoint = software_node_graph_get_next_endpoint, -+ .graph_get_remote_endpoint = software_node_graph_get_remote_endpoint, -+ .graph_get_port_parent = software_node_graph_get_port_parent, -+ .graph_parse_endpoint = software_node_graph_parse_endpoint, - }; - - /* -------------------------------------------------------------------------- */ --- -2.33.0 - -From 2074d259d4e0e363320583ce0eab59821fa2509a Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Oct 2020 23:07:22 +0100 -Subject: [PATCH] lib/test_printf.c: Use helper function to unwind array of - software_nodes - -Use the software_node_unregister_nodes() helper function to unwind this -array in a cleaner way. - -Acked-by: Petr Mladek -Reviewed-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Reviewed-by: Sergey Senozhatsky -Suggested-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - lib/test_printf.c | 4 +--- - 1 file changed, 1 insertion(+), 3 deletions(-) - -diff --git a/lib/test_printf.c b/lib/test_printf.c -index 7ac87f18a10f..7d60f24240a4 100644 ---- a/lib/test_printf.c -+++ b/lib/test_printf.c -@@ -644,9 +644,7 @@ static void __init fwnode_pointer(void) - test(second_name, "%pfwP", software_node_fwnode(&softnodes[1])); - test(third_name, "%pfwP", software_node_fwnode(&softnodes[2])); - -- software_node_unregister(&softnodes[2]); -- software_node_unregister(&softnodes[1]); -- software_node_unregister(&softnodes[0]); -+ software_node_unregister_nodes(softnodes); - } - - static void __init --- -2.33.0 - -From 8aef347cd8368f0c56d6f26c5d8349d8c2321117 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Oct 2020 23:11:36 +0100 -Subject: [PATCH] ipu3-cio2: Add T: entry to MAINTAINERS - -Development for the ipu3-cio2 driver is taking place in media_tree, but -there's no T: entry in MAINTAINERS to denote that - rectify that oversight - -Reviewed-by: Laurent Pinchart -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/MAINTAINERS b/MAINTAINERS -index 4fef10dd2975..7ac7d1ae8764 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -8938,6 +8938,7 @@ M: Bingbu Cao - R: Tianshu Qiu - L: linux-media@vger.kernel.org - S: Maintained -+T: git git://linuxtv.org/media_tree.git - F: Documentation/userspace-api/media/v4l/pixfmt-srggb10-ipu3.rst - F: drivers/media/pci/intel/ipu3/ - --- -2.33.0 - -From bc6f5e14d0a9b9926678e7baf0b75bfd09278ddc Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Oct 2020 22:47:21 +0100 -Subject: [PATCH] ipu3-cio2: Rename ipu3-cio2.c - -ipu3-cio2 driver needs extending with multiple files; rename the main -source file and specify the renamed file in Makefile to accommodate that. - -Suggested-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/Makefile | 2 ++ - drivers/media/pci/intel/ipu3/{ipu3-cio2.c => ipu3-cio2-main.c} | 0 - 2 files changed, 2 insertions(+) - rename drivers/media/pci/intel/ipu3/{ipu3-cio2.c => ipu3-cio2-main.c} (100%) - -diff --git a/drivers/media/pci/intel/ipu3/Makefile b/drivers/media/pci/intel/ipu3/Makefile -index 98ddd5beafe0..429d516452e4 100644 ---- a/drivers/media/pci/intel/ipu3/Makefile -+++ b/drivers/media/pci/intel/ipu3/Makefile -@@ -1,2 +1,4 @@ - # SPDX-License-Identifier: GPL-2.0-only - obj-$(CONFIG_VIDEO_IPU3_CIO2) += ipu3-cio2.o -+ -+ipu3-cio2-y += ipu3-cio2-main.o -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -similarity index 100% -rename from drivers/media/pci/intel/ipu3/ipu3-cio2.c -rename to drivers/media/pci/intel/ipu3/ipu3-cio2-main.c --- -2.33.0 - -From ce657d720915cf99a81a1c66e0331fbe0e1e1c2c Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 21 Oct 2020 21:53:05 +0100 -Subject: [PATCH] media: v4l2-core: v4l2-async: Check sd->fwnode->secondary in - match_fwnode() - -Where the fwnode graph is comprised of software_nodes, these will be -assigned as the secondary to dev->fwnode. Check the v4l2_subdev's fwnode -for a secondary and attempt to match against it during match_fwnode() to -accommodate that possibility. - -Reviewed-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/v4l2-core/v4l2-async.c | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c -index 33babe6e8b3a..d23dff76da3d 100644 ---- a/drivers/media/v4l2-core/v4l2-async.c -+++ b/drivers/media/v4l2-core/v4l2-async.c -@@ -87,6 +87,14 @@ static bool match_fwnode(struct v4l2_async_notifier *notifier, - if (sd->fwnode == asd->match.fwnode) - return true; - -+ /* -+ * Check the same situation for any possible secondary assigned to the -+ * subdev's fwnode -+ */ -+ if (!IS_ERR_OR_NULL(sd->fwnode->secondary) && -+ sd->fwnode->secondary == asd->match.fwnode) -+ return true; -+ - /* - * Otherwise, check if the sd fwnode and the asd fwnode refer to an - * endpoint or a device. If they're of the same type, there's no match. --- -2.33.0 - -From cbb823597d62725a97cc98633d97b7145e13d6c2 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 15 Nov 2020 08:15:34 +0000 -Subject: [PATCH] ACPI / bus: Add acpi_dev_get_next_match_dev() and helper - macro - -To ensure we handle situations in which multiple sensors of the same -model (and therefore _HID) are present in a system, we need to be able -to iterate over devices matching a known _HID but unknown _UID and _HRV - - add acpi_dev_get_next_match_dev() to accommodate that possibility and -change acpi_dev_get_first_match_dev() to simply call the new function -with a NULL starting point. Add an iterator macro for convenience. - -Reviewed-by: Andy Shevchenko -Reviewed-by: Sakari Ailus -Suggested-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/acpi/utils.c | 30 ++++++++++++++++++++++++++---- - include/acpi/acpi_bus.h | 7 +++++++ - 2 files changed, 33 insertions(+), 4 deletions(-) - -diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c -index d5411a166685..ddca1550cce6 100644 ---- a/drivers/acpi/utils.c -+++ b/drivers/acpi/utils.c -@@ -843,12 +843,13 @@ bool acpi_dev_present(const char *hid, const char *uid, s64 hrv) - EXPORT_SYMBOL(acpi_dev_present); - - /** -- * acpi_dev_get_first_match_dev - Return the first match of ACPI device -+ * acpi_dev_get_next_match_dev - Return the next match of ACPI device -+ * @adev: Pointer to the previous acpi_device matching this @hid, @uid and @hrv - * @hid: Hardware ID of the device. - * @uid: Unique ID of the device, pass NULL to not check _UID - * @hrv: Hardware Revision of the device, pass -1 to not check _HRV - * -- * Return the first match of ACPI device if a matching device was present -+ * Return the next match of ACPI device if another matching device was present - * at the moment of invocation, or NULL otherwise. - * - * The caller is responsible to call put_device() on the returned device. -@@ -856,8 +857,9 @@ EXPORT_SYMBOL(acpi_dev_present); - * See additional information in acpi_dev_present() as well. - */ - struct acpi_device * --acpi_dev_get_first_match_dev(const char *hid, const char *uid, s64 hrv) -+acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv) - { -+ struct device *start = adev ? &adev->dev : NULL; - struct acpi_dev_match_info match = {}; - struct device *dev; - -@@ -865,9 +867,29 @@ acpi_dev_get_first_match_dev(const char *hid, const char *uid, s64 hrv) - match.uid = uid; - match.hrv = hrv; - -- dev = bus_find_device(&acpi_bus_type, NULL, &match, acpi_dev_match_cb); -+ dev = bus_find_device(&acpi_bus_type, start, &match, acpi_dev_match_cb); - return dev ? to_acpi_device(dev) : NULL; - } -+EXPORT_SYMBOL(acpi_dev_get_next_match_dev); -+ -+/** -+ * acpi_dev_get_first_match_dev - Return the first match of ACPI device -+ * @hid: Hardware ID of the device. -+ * @uid: Unique ID of the device, pass NULL to not check _UID -+ * @hrv: Hardware Revision of the device, pass -1 to not check _HRV -+ * -+ * Return the first match of ACPI device if a matching device was present -+ * at the moment of invocation, or NULL otherwise. -+ * -+ * The caller is responsible to call put_device() on the returned device. -+ * -+ * See additional information in acpi_dev_present() as well. -+ */ -+struct acpi_device * -+acpi_dev_get_first_match_dev(const char *hid, const char *uid, s64 hrv) -+{ -+ return acpi_dev_get_next_match_dev(NULL, hid, uid, hrv); -+} - EXPORT_SYMBOL(acpi_dev_get_first_match_dev); - - /* -diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h -index 6ad3b89a8a2e..7295aa50120a 100644 ---- a/include/acpi/acpi_bus.h -+++ b/include/acpi/acpi_bus.h -@@ -684,9 +684,16 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) - - bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); - -+struct acpi_device * -+acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); - struct acpi_device * - acpi_dev_get_first_match_dev(const char *hid, const char *uid, s64 hrv); - -+#define for_each_acpi_dev_match(adev, hid, uid, hrv) \ -+ for (adev = acpi_dev_get_first_match_dev(hid, uid, hrv); \ -+ adev; \ -+ adev = acpi_dev_get_next_match_dev(adev, hid, uid, hrv)) -+ - static inline void acpi_dev_put(struct acpi_device *adev) - { - if (adev) --- -2.33.0 - -From 4c4ecb2b29db4aebb8a247643cf131b13a15b332 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 19 Dec 2020 23:55:04 +0000 -Subject: [PATCH] media: v4l2-fwnode: Include v4l2_fwnode_bus_type - -V4L2 fwnode bus types are enumerated in v4l2-fwnode.c, meaning they aren't -available to the rest of the kernel. Move the enum to the corresponding -header so that I can use the label to refer to those values. - -Suggested-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/v4l2-core/v4l2-fwnode.c | 11 ----------- - include/media/v4l2-fwnode.h | 22 ++++++++++++++++++++++ - 2 files changed, 22 insertions(+), 11 deletions(-) - -diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c -index dfc53d11053f..fe01aeb59fd4 100644 ---- a/drivers/media/v4l2-core/v4l2-fwnode.c -+++ b/drivers/media/v4l2-core/v4l2-fwnode.c -@@ -28,17 +28,6 @@ - #include - #include - --enum v4l2_fwnode_bus_type { -- V4L2_FWNODE_BUS_TYPE_GUESS = 0, -- V4L2_FWNODE_BUS_TYPE_CSI2_CPHY, -- V4L2_FWNODE_BUS_TYPE_CSI1, -- V4L2_FWNODE_BUS_TYPE_CCP2, -- V4L2_FWNODE_BUS_TYPE_CSI2_DPHY, -- V4L2_FWNODE_BUS_TYPE_PARALLEL, -- V4L2_FWNODE_BUS_TYPE_BT656, -- NR_OF_V4L2_FWNODE_BUS_TYPE, --}; -- - static const struct v4l2_fwnode_bus_conv { - enum v4l2_fwnode_bus_type fwnode_bus_type; - enum v4l2_mbus_type mbus_type; -diff --git a/include/media/v4l2-fwnode.h b/include/media/v4l2-fwnode.h -index ed0840f3d5df..6ca337c28b3c 100644 ---- a/include/media/v4l2-fwnode.h -+++ b/include/media/v4l2-fwnode.h -@@ -213,6 +213,28 @@ struct v4l2_fwnode_connector { - } connector; - }; - -+/** -+ * enum v4l2_fwnode_bus_type - Video bus types defined by firmware properties -+ * @V4L2_FWNODE_BUS_TYPE_GUESS: Default value if no bus-type fwnode property -+ * @V4L2_FWNODE_BUS_TYPE_CSI2_CPHY: MIPI CSI-2 bus, C-PHY physical layer -+ * @V4L2_FWNODE_BUS_TYPE_CSI1: MIPI CSI-1 bus -+ * @V4L2_FWNODE_BUS_TYPE_CCP2: SMIA Compact Camera Port 2 bus -+ * @V4L2_FWNODE_BUS_TYPE_CSI2_DPHY: MIPI CSI-2 bus, D-PHY physical layer -+ * @V4L2_FWNODE_BUS_TYPE_PARALLEL: Camera Parallel Interface bus -+ * @V4L2_FWNODE_BUS_TYPE_BT656: BT.656 video format bus-type -+ * @NR_OF_V4L2_FWNODE_BUS_TYPE: Number of bus-types -+ */ -+enum v4l2_fwnode_bus_type { -+ V4L2_FWNODE_BUS_TYPE_GUESS = 0, -+ V4L2_FWNODE_BUS_TYPE_CSI2_CPHY, -+ V4L2_FWNODE_BUS_TYPE_CSI1, -+ V4L2_FWNODE_BUS_TYPE_CCP2, -+ V4L2_FWNODE_BUS_TYPE_CSI2_DPHY, -+ V4L2_FWNODE_BUS_TYPE_PARALLEL, -+ V4L2_FWNODE_BUS_TYPE_BT656, -+ NR_OF_V4L2_FWNODE_BUS_TYPE -+}; -+ - /** - * v4l2_fwnode_endpoint_parse() - parse all fwnode node properties - * @fwnode: pointer to the endpoint's fwnode handle --- -2.33.0 - -From 0392fb1e916582a46fe6396ce6db45f1b7b1b2aa Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 21 Oct 2020 21:53:44 +0100 -Subject: [PATCH] ipu3-cio2: Add cio2-bridge to ipu3-cio2 driver - -Currently on platforms designed for Windows, connections between CIO2 and -sensors are not properly defined in DSDT. This patch extends the ipu3-cio2 -driver to compensate by building software_node connections, parsing the -connection properties from the sensor's SSDB buffer. - -Suggested-by: Jordan Hand -Reviewed-by: Laurent Pinchart -Reviewed-by: Andy Shevchenko -Reviewed-by: Kieran Bingham -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 1 + - drivers/media/pci/intel/ipu3/Kconfig | 18 + - drivers/media/pci/intel/ipu3/Makefile | 1 + - drivers/media/pci/intel/ipu3/cio2-bridge.c | 311 ++++++++++++++++++ - drivers/media/pci/intel/ipu3/cio2-bridge.h | 125 +++++++ - drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 34 ++ - drivers/media/pci/intel/ipu3/ipu3-cio2.h | 6 + - 7 files changed, 496 insertions(+) - create mode 100644 drivers/media/pci/intel/ipu3/cio2-bridge.c - create mode 100644 drivers/media/pci/intel/ipu3/cio2-bridge.h - -diff --git a/MAINTAINERS b/MAINTAINERS -index 7ac7d1ae8764..f313ba49c2b8 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -8935,6 +8935,7 @@ INTEL IPU3 CSI-2 CIO2 DRIVER - M: Yong Zhi - M: Sakari Ailus - M: Bingbu Cao -+M: Dan Scally - R: Tianshu Qiu - L: linux-media@vger.kernel.org - S: Maintained -diff --git a/drivers/media/pci/intel/ipu3/Kconfig b/drivers/media/pci/intel/ipu3/Kconfig -index 7a805201034b..24f4e79fe0cb 100644 ---- a/drivers/media/pci/intel/ipu3/Kconfig -+++ b/drivers/media/pci/intel/ipu3/Kconfig -@@ -17,3 +17,21 @@ config VIDEO_IPU3_CIO2 - Say Y or M here if you have a Skylake/Kaby Lake SoC with MIPI CSI-2 - connected camera. - The module will be called ipu3-cio2. -+ -+config CIO2_BRIDGE -+ bool "IPU3 CIO2 Sensors Bridge" -+ depends on VIDEO_IPU3_CIO2 -+ help -+ This extension provides an API for the ipu3-cio2 driver to create -+ connections to cameras that are hidden in the SSDB buffer in ACPI. -+ It can be used to enable support for cameras in detachable / hybrid -+ devices that ship with Windows. -+ -+ Say Y here if your device is a detachable / hybrid laptop that comes -+ with Windows installed by the OEM, for example: -+ -+ - Microsoft Surface models (except Surface Pro 3) -+ - The Lenovo Miix line (for example the 510, 520, 710 and 720) -+ - Dell 7285 -+ -+ If in doubt, say N here. -diff --git a/drivers/media/pci/intel/ipu3/Makefile b/drivers/media/pci/intel/ipu3/Makefile -index 429d516452e4..933777e6ea8a 100644 ---- a/drivers/media/pci/intel/ipu3/Makefile -+++ b/drivers/media/pci/intel/ipu3/Makefile -@@ -2,3 +2,4 @@ - obj-$(CONFIG_VIDEO_IPU3_CIO2) += ipu3-cio2.o - - ipu3-cio2-y += ipu3-cio2-main.o -+ipu3-cio2-$(CONFIG_CIO2_BRIDGE) += cio2-bridge.o -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -new file mode 100644 -index 000000000000..143f3c0f445e ---- /dev/null -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -0,0 +1,311 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "cio2-bridge.h" -+ -+/* -+ * Extend this array with ACPI Hardware IDs of devices known to be working -+ * plus the number of link-frequencies expected by their drivers, along with -+ * the frequency values in hertz. This is somewhat opportunistic way of adding -+ * support for this for now in the hopes of a better source for the information -+ * (possibly some encoded value in the SSDB buffer that we're unaware of) -+ * becoming apparent in the future. -+ * -+ * Do not add an entry for a sensor that is not actually supported. -+ */ -+static const struct cio2_sensor_config cio2_supported_sensors[] = { -+ /* Omnivision OV5693 */ -+ CIO2_SENSOR_CONFIG("INT33BE", 0), -+ /* Omnivision OV2680 */ -+ CIO2_SENSOR_CONFIG("OVTI2680", 0), -+}; -+ -+static const struct cio2_property_names prop_names = { -+ .clock_frequency = "clock-frequency", -+ .rotation = "rotation", -+ .bus_type = "bus-type", -+ .data_lanes = "data-lanes", -+ .remote_endpoint = "remote-endpoint", -+ .link_frequencies = "link-frequencies", -+}; -+ -+static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, -+ void *data, u32 size) -+{ -+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; -+ union acpi_object *obj; -+ acpi_status status; -+ int ret = 0; -+ -+ status = acpi_evaluate_object(adev->handle, id, NULL, &buffer); -+ if (ACPI_FAILURE(status)) -+ return -ENODEV; -+ -+ obj = buffer.pointer; -+ if (!obj) { -+ dev_err(&adev->dev, "Couldn't locate ACPI buffer\n"); -+ return -ENODEV; -+ } -+ -+ if (obj->type != ACPI_TYPE_BUFFER) { -+ dev_err(&adev->dev, "Not an ACPI buffer\n"); -+ ret = -ENODEV; -+ goto out_free_buff; -+ } -+ -+ if (obj->buffer.length > size) { -+ dev_err(&adev->dev, "Given buffer is too small\n"); -+ ret = -EINVAL; -+ goto out_free_buff; -+ } -+ -+ memcpy(data, obj->buffer.pointer, obj->buffer.length); -+ -+out_free_buff: -+ kfree(buffer.pointer); -+ return ret; -+} -+ -+static void cio2_bridge_create_fwnode_properties( -+ struct cio2_sensor *sensor, -+ struct cio2_bridge *bridge, -+ const struct cio2_sensor_config *cfg) -+{ -+ sensor->prop_names = prop_names; -+ -+ sensor->local_ref[0].node = &sensor->swnodes[SWNODE_CIO2_ENDPOINT]; -+ sensor->remote_ref[0].node = &sensor->swnodes[SWNODE_SENSOR_ENDPOINT]; -+ -+ sensor->dev_properties[0] = PROPERTY_ENTRY_U32( -+ sensor->prop_names.clock_frequency, -+ sensor->ssdb.mclkspeed); -+ sensor->dev_properties[1] = PROPERTY_ENTRY_U8( -+ sensor->prop_names.rotation, -+ sensor->ssdb.degree); -+ -+ sensor->ep_properties[0] = PROPERTY_ENTRY_U32( -+ sensor->prop_names.bus_type, -+ V4L2_FWNODE_BUS_TYPE_CSI2_DPHY); -+ sensor->ep_properties[1] = PROPERTY_ENTRY_U32_ARRAY_LEN( -+ sensor->prop_names.data_lanes, -+ bridge->data_lanes, -+ sensor->ssdb.lanes); -+ sensor->ep_properties[2] = PROPERTY_ENTRY_REF_ARRAY( -+ sensor->prop_names.remote_endpoint, -+ sensor->local_ref); -+ -+ if (cfg->nr_link_freqs > 0) -+ sensor->ep_properties[3] = PROPERTY_ENTRY_U64_ARRAY_LEN( -+ sensor->prop_names.link_frequencies, -+ cfg->link_freqs, -+ cfg->nr_link_freqs); -+ -+ sensor->cio2_properties[0] = PROPERTY_ENTRY_U32_ARRAY_LEN( -+ sensor->prop_names.data_lanes, -+ bridge->data_lanes, -+ sensor->ssdb.lanes); -+ sensor->cio2_properties[1] = PROPERTY_ENTRY_REF_ARRAY( -+ sensor->prop_names.remote_endpoint, -+ sensor->remote_ref); -+} -+ -+static void cio2_bridge_init_swnode_names(struct cio2_sensor *sensor) -+{ -+ snprintf(sensor->node_names.remote_port, -+ sizeof(sensor->node_names.remote_port), -+ SWNODE_GRAPH_PORT_NAME_FMT, sensor->ssdb.link); -+ snprintf(sensor->node_names.port, -+ sizeof(sensor->node_names.port), -+ SWNODE_GRAPH_PORT_NAME_FMT, 0); /* Always port 0 */ -+ snprintf(sensor->node_names.endpoint, -+ sizeof(sensor->node_names.endpoint), -+ SWNODE_GRAPH_ENDPOINT_NAME_FMT, 0); /* And endpoint 0 */ -+} -+ -+static void cio2_bridge_create_connection_swnodes(struct cio2_bridge *bridge, -+ struct cio2_sensor *sensor) -+{ -+ struct software_node *nodes = sensor->swnodes; -+ -+ cio2_bridge_init_swnode_names(sensor); -+ -+ nodes[SWNODE_SENSOR_HID] = NODE_SENSOR(sensor->name, -+ sensor->dev_properties); -+ nodes[SWNODE_SENSOR_PORT] = NODE_PORT(sensor->node_names.port, -+ &nodes[SWNODE_SENSOR_HID]); -+ nodes[SWNODE_SENSOR_ENDPOINT] = NODE_ENDPOINT( -+ sensor->node_names.endpoint, -+ &nodes[SWNODE_SENSOR_PORT], -+ sensor->ep_properties); -+ nodes[SWNODE_CIO2_PORT] = NODE_PORT(sensor->node_names.remote_port, -+ &bridge->cio2_hid_node); -+ nodes[SWNODE_CIO2_ENDPOINT] = NODE_ENDPOINT( -+ sensor->node_names.endpoint, -+ &nodes[SWNODE_CIO2_PORT], -+ sensor->cio2_properties); -+} -+ -+static void cio2_bridge_unregister_sensors(struct cio2_bridge *bridge) -+{ -+ struct cio2_sensor *sensor; -+ unsigned int i; -+ -+ for (i = 0; i < bridge->n_sensors; i++) { -+ sensor = &bridge->sensors[i]; -+ software_node_unregister_nodes(sensor->swnodes); -+ acpi_dev_put(sensor->adev); -+ } -+} -+ -+static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, -+ struct cio2_bridge *bridge, -+ struct pci_dev *cio2) -+{ -+ struct fwnode_handle *fwnode; -+ struct cio2_sensor *sensor; -+ struct acpi_device *adev; -+ int ret; -+ -+ for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { -+ if (!adev->status.enabled) -+ continue; -+ -+ if (bridge->n_sensors >= CIO2_NUM_PORTS) { -+ dev_err(&cio2->dev, "Exceeded available CIO2 ports\n"); -+ cio2_bridge_unregister_sensors(bridge); -+ ret = -EINVAL; -+ goto err_out; -+ } -+ -+ sensor = &bridge->sensors[bridge->n_sensors]; -+ sensor->adev = adev; -+ strscpy(sensor->name, cfg->hid, sizeof(sensor->name)); -+ -+ ret = cio2_bridge_read_acpi_buffer(adev, "SSDB", -+ &sensor->ssdb, -+ sizeof(sensor->ssdb)); -+ if (ret) -+ goto err_put_adev; -+ -+ if (sensor->ssdb.lanes > CIO2_MAX_LANES) { -+ dev_err(&adev->dev, -+ "Number of lanes in SSDB is invalid\n"); -+ ret = -EINVAL; -+ goto err_put_adev; -+ } -+ -+ cio2_bridge_create_fwnode_properties(sensor, bridge, cfg); -+ cio2_bridge_create_connection_swnodes(bridge, sensor); -+ -+ ret = software_node_register_nodes(sensor->swnodes); -+ if (ret) -+ goto err_put_adev; -+ -+ fwnode = software_node_fwnode(&sensor->swnodes[SWNODE_SENSOR_HID]); -+ if (!fwnode) { -+ ret = -ENODEV; -+ goto err_free_swnodes; -+ } -+ -+ adev->fwnode.secondary = fwnode; -+ -+ dev_info(&cio2->dev, "Found supported sensor %s\n", -+ acpi_dev_name(adev)); -+ -+ bridge->n_sensors++; -+ } -+ -+ return 0; -+ -+err_free_swnodes: -+ software_node_unregister_nodes(sensor->swnodes); -+err_put_adev: -+ acpi_dev_put(sensor->adev); -+err_out: -+ return ret; -+} -+ -+static int cio2_bridge_connect_sensors(struct cio2_bridge *bridge, -+ struct pci_dev *cio2) -+{ -+ unsigned int i; -+ int ret; -+ -+ for (i = 0; i < ARRAY_SIZE(cio2_supported_sensors); i++) { -+ const struct cio2_sensor_config *cfg = &cio2_supported_sensors[i]; -+ -+ ret = cio2_bridge_connect_sensor(cfg, bridge, cio2); -+ if (ret) -+ goto err_unregister_sensors; -+ } -+ -+ return 0; -+ -+err_unregister_sensors: -+ cio2_bridge_unregister_sensors(bridge); -+ return ret; -+} -+ -+int cio2_bridge_init(struct pci_dev *cio2) -+{ -+ struct device *dev = &cio2->dev; -+ struct fwnode_handle *fwnode; -+ struct cio2_bridge *bridge; -+ unsigned int i; -+ int ret; -+ -+ bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); -+ if (!bridge) -+ return -ENOMEM; -+ -+ strscpy(bridge->cio2_node_name, CIO2_HID, sizeof(bridge->cio2_node_name)); -+ bridge->cio2_hid_node.name = bridge->cio2_node_name; -+ -+ ret = software_node_register(&bridge->cio2_hid_node); -+ if (ret < 0) { -+ dev_err(dev, "Failed to register the CIO2 HID node\n"); -+ goto err_free_bridge; -+ } -+ -+ /* -+ * Map the lane arrangement, which is fixed for the IPU3 (meaning we -+ * only need one, rather than one per sensor). We include it as a -+ * member of the struct cio2_bridge rather than a global variable so -+ * that it survives if the module is unloaded along with the rest of -+ * the struct. -+ */ -+ for (i = 0; i < CIO2_MAX_LANES; i++) -+ bridge->data_lanes[i] = i + 1; -+ -+ ret = cio2_bridge_connect_sensors(bridge, cio2); -+ if (ret || bridge->n_sensors == 0) -+ goto err_unregister_cio2; -+ -+ dev_info(dev, "Connected %d cameras\n", bridge->n_sensors); -+ -+ fwnode = software_node_fwnode(&bridge->cio2_hid_node); -+ if (!fwnode) { -+ dev_err(dev, "Error getting fwnode from cio2 software_node\n"); -+ ret = -ENODEV; -+ goto err_unregister_sensors; -+ } -+ -+ set_secondary_fwnode(dev, fwnode); -+ -+ return 0; -+ -+err_unregister_sensors: -+ cio2_bridge_unregister_sensors(bridge); -+err_unregister_cio2: -+ software_node_unregister(&bridge->cio2_hid_node); -+err_free_bridge: -+ kfree(bridge); -+ -+ return ret; -+} -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -new file mode 100644 -index 000000000000..dd0ffcafa489 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -0,0 +1,125 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* Author: Dan Scally */ -+#ifndef __CIO2_BRIDGE_H -+#define __CIO2_BRIDGE_H -+ -+#include -+#include -+ -+#include "ipu3-cio2.h" -+ -+#define CIO2_HID "INT343E" -+#define CIO2_MAX_LANES 4 -+#define MAX_NUM_LINK_FREQS 3 -+ -+#define CIO2_SENSOR_CONFIG(_HID, _NR, ...) \ -+ (const struct cio2_sensor_config) { \ -+ .hid = _HID, \ -+ .nr_link_freqs = _NR, \ -+ .link_freqs = { __VA_ARGS__ } \ -+ } -+ -+#define NODE_SENSOR(_HID, _PROPS) \ -+ (const struct software_node) { \ -+ .name = _HID, \ -+ .properties = _PROPS, \ -+ } -+ -+#define NODE_PORT(_PORT, _SENSOR_NODE) \ -+ (const struct software_node) { \ -+ .name = _PORT, \ -+ .parent = _SENSOR_NODE, \ -+ } -+ -+#define NODE_ENDPOINT(_EP, _PORT, _PROPS) \ -+ (const struct software_node) { \ -+ .name = _EP, \ -+ .parent = _PORT, \ -+ .properties = _PROPS, \ -+ } -+ -+enum cio2_sensor_swnodes { -+ SWNODE_SENSOR_HID, -+ SWNODE_SENSOR_PORT, -+ SWNODE_SENSOR_ENDPOINT, -+ SWNODE_CIO2_PORT, -+ SWNODE_CIO2_ENDPOINT, -+ SWNODE_COUNT -+}; -+ -+/* Data representation as it is in ACPI SSDB buffer */ -+struct cio2_sensor_ssdb { -+ u8 version; -+ u8 sku; -+ u8 guid_csi2[16]; -+ u8 devfunction; -+ u8 bus; -+ u32 dphylinkenfuses; -+ u32 clockdiv; -+ u8 link; -+ u8 lanes; -+ u32 csiparams[10]; -+ u32 maxlanespeed; -+ u8 sensorcalibfileidx; -+ u8 sensorcalibfileidxInMBZ[3]; -+ u8 romtype; -+ u8 vcmtype; -+ u8 platforminfo; -+ u8 platformsubinfo; -+ u8 flash; -+ u8 privacyled; -+ u8 degree; -+ u8 mipilinkdefined; -+ u32 mclkspeed; -+ u8 controllogicid; -+ u8 reserved1[3]; -+ u8 mclkport; -+ u8 reserved2[13]; -+} __packed; -+ -+struct cio2_property_names { -+ char clock_frequency[16]; -+ char rotation[9]; -+ char bus_type[9]; -+ char data_lanes[11]; -+ char remote_endpoint[16]; -+ char link_frequencies[17]; -+}; -+ -+struct cio2_node_names { -+ char port[7]; -+ char endpoint[11]; -+ char remote_port[7]; -+}; -+ -+struct cio2_sensor_config { -+ const char *hid; -+ const u8 nr_link_freqs; -+ const u64 link_freqs[MAX_NUM_LINK_FREQS]; -+}; -+ -+struct cio2_sensor { -+ char name[ACPI_ID_LEN]; -+ struct acpi_device *adev; -+ -+ struct software_node swnodes[6]; -+ struct cio2_node_names node_names; -+ -+ struct cio2_sensor_ssdb ssdb; -+ struct cio2_property_names prop_names; -+ struct property_entry ep_properties[5]; -+ struct property_entry dev_properties[3]; -+ struct property_entry cio2_properties[3]; -+ struct software_node_ref_args local_ref[1]; -+ struct software_node_ref_args remote_ref[1]; -+}; -+ -+struct cio2_bridge { -+ char cio2_node_name[ACPI_ID_LEN]; -+ struct software_node cio2_hid_node; -+ u32 data_lanes[4]; -+ unsigned int n_sensors; -+ struct cio2_sensor sensors[CIO2_NUM_PORTS]; -+}; -+ -+#endif -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -index c0cd4606810c..77cea941fd8d 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -@@ -1709,11 +1709,28 @@ static void cio2_queues_exit(struct cio2_device *cio2) - cio2_queue_exit(cio2, &cio2->queue[i]); - } - -+static int cio2_check_fwnode_graph(struct fwnode_handle *fwnode) -+{ -+ struct fwnode_handle *endpoint; -+ -+ if (IS_ERR_OR_NULL(fwnode)) -+ return -EINVAL; -+ -+ endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); -+ if (endpoint) { -+ fwnode_handle_put(endpoint); -+ return 0; -+ } -+ -+ return cio2_check_fwnode_graph(fwnode->secondary); -+} -+ - /**************** PCI interface ****************/ - - static int cio2_pci_probe(struct pci_dev *pci_dev, - const struct pci_device_id *id) - { -+ struct fwnode_handle *fwnode = dev_fwnode(&pci_dev->dev); - struct cio2_device *cio2; - int r; - -@@ -1722,6 +1739,23 @@ static int cio2_pci_probe(struct pci_dev *pci_dev, - return -ENOMEM; - cio2->pci_dev = pci_dev; - -+ /* -+ * On some platforms no connections to sensors are defined in firmware, -+ * if the device has no endpoints then we can try to build those as -+ * software_nodes parsed from SSDB. -+ */ -+ r = cio2_check_fwnode_graph(fwnode); -+ if (r) { -+ if (fwnode && !IS_ERR_OR_NULL(fwnode->secondary)) { -+ dev_err(&pci_dev->dev, "fwnode graph has no endpoints connected\n"); -+ return -EINVAL; -+ } -+ -+ r = cio2_bridge_init(pci_dev); -+ if (r) -+ return r; -+ } -+ - r = pcim_enable_device(pci_dev); - if (r) { - dev_err(&pci_dev->dev, "failed to enable device (%d)\n", r); -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.h b/drivers/media/pci/intel/ipu3/ipu3-cio2.h -index 62187ab5ae43..dc3e343a37fb 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2.h -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.h -@@ -455,4 +455,10 @@ static inline struct cio2_queue *vb2q_to_cio2_queue(struct vb2_queue *vq) - return container_of(vq, struct cio2_queue, vbq); - } - -+#if IS_ENABLED(CONFIG_CIO2_BRIDGE) -+int cio2_bridge_init(struct pci_dev *cio2); -+#else -+int cio2_bridge_init(struct pci_dev *cio2) { return 0; } -+#endif -+ - #endif --- -2.33.0 - -From d870071b5f738817441b184b82b5b919b1515fae Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 2 Dec 2020 12:38:10 +0000 -Subject: [PATCH] acpi: utils: move acpi_lpss_dep() to utils - -I need to be able to identify devices which declare themselves to be -dependent on other devices through _DEP; add this function to utils.c -and export it to the rest of the ACPI layer. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/acpi/acpi_lpss.c | 24 ------------------------ - drivers/acpi/internal.h | 1 + - drivers/acpi/utils.c | 24 ++++++++++++++++++++++++ - 3 files changed, 25 insertions(+), 24 deletions(-) - -diff --git a/drivers/acpi/acpi_lpss.c b/drivers/acpi/acpi_lpss.c -index be73974ce449..70c7d9a3f715 100644 ---- a/drivers/acpi/acpi_lpss.c -+++ b/drivers/acpi/acpi_lpss.c -@@ -543,30 +543,6 @@ static struct device *acpi_lpss_find_device(const char *hid, const char *uid) - return bus_find_device(&pci_bus_type, NULL, &data, match_hid_uid); - } - --static bool acpi_lpss_dep(struct acpi_device *adev, acpi_handle handle) --{ -- struct acpi_handle_list dep_devices; -- acpi_status status; -- int i; -- -- if (!acpi_has_method(adev->handle, "_DEP")) -- return false; -- -- status = acpi_evaluate_reference(adev->handle, "_DEP", NULL, -- &dep_devices); -- if (ACPI_FAILURE(status)) { -- dev_dbg(&adev->dev, "Failed to evaluate _DEP.\n"); -- return false; -- } -- -- for (i = 0; i < dep_devices.count; i++) { -- if (dep_devices.handles[i] == handle) -- return true; -- } -- -- return false; --} -- - static void acpi_lpss_link_consumer(struct device *dev1, - const struct lpss_device_links *link) - { -diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h -index a958ad60a339..3901a251c998 100644 ---- a/drivers/acpi/internal.h -+++ b/drivers/acpi/internal.h -@@ -81,6 +81,7 @@ static inline void acpi_lpss_init(void) {} - #endif - - void acpi_apd_init(void); -+bool acpi_lpss_dep(struct acpi_device *adev, acpi_handle handle); - - acpi_status acpi_hotplug_schedule(struct acpi_device *adev, u32 src); - bool acpi_queue_hotplug_work(struct work_struct *work); -diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c -index ddca1550cce6..78b38775f18b 100644 ---- a/drivers/acpi/utils.c -+++ b/drivers/acpi/utils.c -@@ -807,6 +807,30 @@ static int acpi_dev_match_cb(struct device *dev, const void *data) - return hrv == match->hrv; - } - -+bool acpi_lpss_dep(struct acpi_device *adev, acpi_handle handle) -+{ -+ struct acpi_handle_list dep_devices; -+ acpi_status status; -+ int i; -+ -+ if (!acpi_has_method(adev->handle, "_DEP")) -+ return false; -+ -+ status = acpi_evaluate_reference(adev->handle, "_DEP", NULL, -+ &dep_devices); -+ if (ACPI_FAILURE(status)) { -+ dev_dbg(&adev->dev, "Failed to evaluate _DEP.\n"); -+ return false; -+ } -+ -+ for (i = 0; i < dep_devices.count; i++) { -+ if (dep_devices.handles[i] == handle) -+ return true; -+ } -+ -+ return false; -+} -+ - /** - * acpi_dev_present - Detect that a given ACPI device is present - * @hid: Hardware ID of the device. --- -2.33.0 - -From a659546aa2ac175585041d0cea87aa970ec7585c Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 26 Nov 2020 21:12:41 +0000 -Subject: [PATCH] acpi: utils: Add function to fetch dependent acpi_devices - -In some ACPI tables we encounter, devices use the _DEP method to assert -a dependence on other ACPI devices as opposed to the OpRegions that the -specification intends. We need to be able to find those devices "from" -the dependee, so add a function to parse all ACPI Devices and check if -the include the handle of the dependee device in their _DEP buffer. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/acpi/utils.c | 34 ++++++++++++++++++++++++++++++++++ - include/acpi/acpi_bus.h | 2 ++ - 2 files changed, 36 insertions(+) - -diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c -index 78b38775f18b..ec6a2406a886 100644 ---- a/drivers/acpi/utils.c -+++ b/drivers/acpi/utils.c -@@ -831,6 +831,18 @@ bool acpi_lpss_dep(struct acpi_device *adev, acpi_handle handle) - return false; - } - -+static int acpi_dev_match_by_dep(struct device *dev, const void *data) -+{ -+ struct acpi_device *adev = to_acpi_device(dev); -+ const struct acpi_device *dependee = data; -+ acpi_handle handle = dependee->handle; -+ -+ if (acpi_lpss_dep(adev, handle)) -+ return 1; -+ -+ return 0; -+} -+ - /** - * acpi_dev_present - Detect that a given ACPI device is present - * @hid: Hardware ID of the device. -@@ -866,6 +878,28 @@ bool acpi_dev_present(const char *hid, const char *uid, s64 hrv) - } - EXPORT_SYMBOL(acpi_dev_present); - -+/** -+ * acpi_dev_get_next_dep_dev - Return next ACPI device dependent on input dev -+ * @adev: Pointer to the dependee device -+ * @prev: Pointer to the previous dependent device (or NULL for first match) -+ * -+ * Return the next ACPI device which declares itself dependent on @adev in -+ * the _DEP buffer. -+ * -+ * The caller is responsible to call put_device() on the returned device. -+ */ -+struct acpi_device *acpi_dev_get_next_dep_dev(struct acpi_device *adev, -+ struct acpi_device *prev) -+{ -+ struct device *start = prev ? &prev->dev : NULL; -+ struct device *dev; -+ -+ dev = bus_find_device(&acpi_bus_type, start, adev, acpi_dev_match_by_dep); -+ -+ return dev ? to_acpi_device(dev) : NULL; -+} -+EXPORT_SYMBOL(acpi_dev_get_next_dep_dev); -+ - /** - * acpi_dev_get_next_match_dev - Return the next match of ACPI device - * @adev: Pointer to the previous acpi_device matching this @hid, @uid and @hrv -diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h -index 7295aa50120a..58f964471da5 100644 ---- a/include/acpi/acpi_bus.h -+++ b/include/acpi/acpi_bus.h -@@ -684,6 +684,8 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) - - bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); - -+struct acpi_device * -+acpi_dev_get_next_dep_dev(struct acpi_device *adev, struct acpi_device *prev); - struct acpi_device * - acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); - struct acpi_device * --- -2.33.0 - -From d7f298e244aba7f166774f34339fd5d884fa7114 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 16 Nov 2020 21:38:49 +0000 -Subject: [PATCH] i2c: i2c-core-base: Use format macro in i2c_dev_set_name() - -Some places in the kernel allow users to map resources to a device -using device name (for example, gpiod_lookup_table). Currently -this involves waiting for the i2c_client to have been registered so we -can use dev_name(&client->dev). We want to add a function to allow users -to refer to an i2c device by name before it has been instantiated, so -create a macro for the format that's accessible outside the i2c layer -and use it in i2c_dev_set_name() - -Suggested-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/i2c/i2c-core-base.c | 4 ++-- - include/linux/i2c.h | 7 +++++++ - 2 files changed, 9 insertions(+), 2 deletions(-) - -diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c -index bdce6d3e5327..f3ecc78dca4e 100644 ---- a/drivers/i2c/i2c-core-base.c -+++ b/drivers/i2c/i2c-core-base.c -@@ -813,12 +813,12 @@ static void i2c_dev_set_name(struct i2c_adapter *adap, - struct acpi_device *adev = ACPI_COMPANION(&client->dev); - - if (info && info->dev_name) { -- dev_set_name(&client->dev, "i2c-%s", info->dev_name); -+ dev_set_name(&client->dev, I2C_DEV_NAME_FORMAT, info->dev_name); - return; - } - - if (adev) { -- dev_set_name(&client->dev, "i2c-%s", acpi_dev_name(adev)); -+ dev_set_name(&client->dev, I2C_DEV_NAME_FORMAT, acpi_dev_name(adev)); - return; - } - -diff --git a/include/linux/i2c.h b/include/linux/i2c.h -index a670ae129f4b..b18172f240af 100644 ---- a/include/linux/i2c.h -+++ b/include/linux/i2c.h -@@ -39,6 +39,9 @@ enum i2c_slave_event; - typedef int (*i2c_slave_cb_t)(struct i2c_client *client, - enum i2c_slave_event event, u8 *val); - -+/* I2C Device Name Format - to maintain consistency outside the i2c layer */ -+#define I2C_DEV_NAME_FORMAT "i2c-%s" -+ - /* I2C Frequency Modes */ - #define I2C_MAX_STANDARD_MODE_FREQ 100000 - #define I2C_MAX_FAST_MODE_FREQ 400000 -@@ -1013,6 +1016,10 @@ static inline struct i2c_client *i2c_acpi_new_device(struct device *dev, - { - return ERR_PTR(-ENODEV); - } -+static inline char *i2c_acpi_dev_name(struct acpi_device *adev) -+{ -+ return NULL; -+} - static inline struct i2c_adapter *i2c_acpi_find_adapter_by_handle(acpi_handle handle) - { - return NULL; --- -2.33.0 - -From b39615bf56e9af6f6f601c992d48b2001c71f50b Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 2 Dec 2020 16:41:42 +0000 -Subject: [PATCH] i2c: i2c-core-acpi: Add i2c_acpi_dev_name() - -We want to refer to an i2c device by name before it has been -created by the kernel; add a function that constructs the name -from the acpi device instead. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/i2c/i2c-core-acpi.c | 16 ++++++++++++++++ - include/linux/i2c.h | 1 + - 2 files changed, 17 insertions(+) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index aed579942436..89751415b69b 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -497,6 +497,22 @@ struct i2c_client *i2c_acpi_new_device(struct device *dev, int index, - } - EXPORT_SYMBOL_GPL(i2c_acpi_new_device); - -+/** -+ * i2c_acpi_dev_name - Construct i2c device name for devs sourced from ACPI -+ * @adev: ACPI device to construct the name for -+ * -+ * Constructs the name of an i2c device matching the format used by -+ * i2c_dev_set_name() to allow users to refer to an i2c device by name even -+ * before they have been instantiated. -+ * -+ * The caller is responsible for freeing the returned pointer. -+ */ -+char *i2c_acpi_dev_name(struct acpi_device *adev) -+{ -+ return kasprintf(GFP_KERNEL, I2C_DEV_NAME_FORMAT, acpi_dev_name(adev)); -+} -+EXPORT_SYMBOL_GPL(i2c_acpi_dev_name); -+ - #ifdef CONFIG_ACPI_I2C_OPREGION - static int acpi_gsb_i2c_read_bytes(struct i2c_client *client, - u8 cmd, u8 *data, u8 data_len) -diff --git a/include/linux/i2c.h b/include/linux/i2c.h -index b18172f240af..269a2009080c 100644 ---- a/include/linux/i2c.h -+++ b/include/linux/i2c.h -@@ -1000,6 +1000,7 @@ bool i2c_acpi_get_i2c_resource(struct acpi_resource *ares, - u32 i2c_acpi_find_bus_speed(struct device *dev); - struct i2c_client *i2c_acpi_new_device(struct device *dev, int index, - struct i2c_board_info *info); -+char *i2c_acpi_dev_name(struct acpi_device *adev); - struct i2c_adapter *i2c_acpi_find_adapter_by_handle(acpi_handle handle); - #else - static inline bool i2c_acpi_get_i2c_resource(struct acpi_resource *ares, --- -2.33.0 - -From 93e728bae8143ccdc46578ce93feec0c40172d85 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 16 Nov 2020 00:16:56 +0000 -Subject: [PATCH] gpio: gpiolib-acpi: Export acpi_get_gpiod() - -I need to be able to translate GPIO resources in an acpi_device's _CRS -into gpio_descs. Those are represented in _CRS as a pathname to a GPIO -device plus the pin's index number: this function is perfect for that -purpose. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/gpio/gpiolib-acpi.c | 3 ++- - include/linux/acpi.h | 5 +++++ - 2 files changed, 7 insertions(+), 1 deletion(-) - -diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c -index 6f11714ce023..4ae6fd33b783 100644 ---- a/drivers/gpio/gpiolib-acpi.c -+++ b/drivers/gpio/gpiolib-acpi.c -@@ -111,7 +111,7 @@ static int acpi_gpiochip_find(struct gpio_chip *gc, void *data) - * controller does not have GPIO chip registered at the moment. This is to - * support probe deferral. - */ --static struct gpio_desc *acpi_get_gpiod(char *path, int pin) -+struct gpio_desc *acpi_get_gpiod(char *path, int pin) - { - struct gpio_chip *chip; - acpi_handle handle; -@@ -127,6 +127,7 @@ static struct gpio_desc *acpi_get_gpiod(char *path, int pin) - - return gpiochip_get_desc(chip, pin); - } -+EXPORT_SYMBOL_GPL(acpi_get_gpiod); - - static irqreturn_t acpi_gpio_irq_handler(int irq, void *data) - { -diff --git a/include/linux/acpi.h b/include/linux/acpi.h -index fdb1d5262ce8..817f53506cfe 100644 ---- a/include/linux/acpi.h -+++ b/include/linux/acpi.h -@@ -1080,6 +1080,7 @@ void __acpi_handle_debug(struct _ddebug *descriptor, acpi_handle handle, const c - bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, - struct acpi_resource_gpio **agpio); - int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, const char *name, int index); -+struct gpio_desc *acpi_get_gpiod(char *path, int pin); - #else - static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, - struct acpi_resource_gpio **agpio) -@@ -1091,6 +1092,10 @@ static inline int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, - { - return -ENXIO; - } -+struct gpio_desc *acpi_get_gpiod(char *path, int pin) -+{ -+ return NULL; -+} - #endif - - static inline int acpi_dev_gpio_irq_get(struct acpi_device *adev, int index) --- -2.33.0 - -From f47c23f06fd6656dcc585ad8bcf6223b73f28e49 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 12 Dec 2020 23:56:59 +0000 -Subject: [PATCH] mfd: Remove tps68470 MFD driver - -This driver only covered one scenario in which ACPI devices with _HID -INT3472 are found, and its functionality has been taken over by the -intel-skl-int3472 module, so remove it. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/acpi/pmic/Kconfig | 1 - - drivers/gpio/Kconfig | 1 - - drivers/mfd/Kconfig | 18 -------- - drivers/mfd/Makefile | 1 - - drivers/mfd/tps68470.c | 97 --------------------------------------- - 5 files changed, 118 deletions(-) - delete mode 100644 drivers/mfd/tps68470.c - -diff --git a/drivers/acpi/pmic/Kconfig b/drivers/acpi/pmic/Kconfig -index 56bbcb2ce61b..e27d8ef3a32c 100644 ---- a/drivers/acpi/pmic/Kconfig -+++ b/drivers/acpi/pmic/Kconfig -@@ -52,7 +52,6 @@ endif # PMIC_OPREGION - - config TPS68470_PMIC_OPREGION - bool "ACPI operation region support for TPS68470 PMIC" -- depends on MFD_TPS68470 - help - This config adds ACPI operation region support for TI TPS68470 PMIC. - TPS68470 device is an advanced power management unit that powers -diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig -index d1300fc003ed..923768bf2719 100644 ---- a/drivers/gpio/Kconfig -+++ b/drivers/gpio/Kconfig -@@ -1321,7 +1321,6 @@ config GPIO_TPS65912 - - config GPIO_TPS68470 - bool "TPS68470 GPIO" -- depends on MFD_TPS68470 - help - Select this option to enable GPIO driver for the TPS68470 - chip family. -diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig -index b8847ae04d93..bd655a383cee 100644 ---- a/drivers/mfd/Kconfig -+++ b/drivers/mfd/Kconfig -@@ -1534,24 +1534,6 @@ config MFD_TPS65217 - This driver can also be built as a module. If so, the module - will be called tps65217. - --config MFD_TPS68470 -- bool "TI TPS68470 Power Management / LED chips" -- depends on ACPI && PCI && I2C=y -- depends on I2C_DESIGNWARE_PLATFORM=y -- select MFD_CORE -- select REGMAP_I2C -- help -- If you say yes here you get support for the TPS68470 series of -- Power Management / LED chips. -- -- These include voltage regulators, LEDs and other features -- that are often used in portable devices. -- -- This option is a bool as it provides an ACPI operation -- region, which must be available before any of the devices -- using this are probed. This option also configures the -- designware-i2c driver to be built-in, for the same reason. -- - config MFD_TI_LP873X - tristate "TI LP873X Power Management IC" - depends on I2C -diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile -index 1780019d2474..a23f38c4c38a 100644 ---- a/drivers/mfd/Makefile -+++ b/drivers/mfd/Makefile -@@ -105,7 +105,6 @@ obj-$(CONFIG_MFD_TPS65910) += tps65910.o - obj-$(CONFIG_MFD_TPS65912) += tps65912-core.o - obj-$(CONFIG_MFD_TPS65912_I2C) += tps65912-i2c.o - obj-$(CONFIG_MFD_TPS65912_SPI) += tps65912-spi.o --obj-$(CONFIG_MFD_TPS68470) += tps68470.o - obj-$(CONFIG_MFD_TPS80031) += tps80031.o - obj-$(CONFIG_MENELAUS) += menelaus.o - -diff --git a/drivers/mfd/tps68470.c b/drivers/mfd/tps68470.c -deleted file mode 100644 -index 4a4df4ffd18c..000000000000 ---- a/drivers/mfd/tps68470.c -+++ /dev/null -@@ -1,97 +0,0 @@ --// SPDX-License-Identifier: GPL-2.0 --/* -- * TPS68470 chip Parent driver -- * -- * Copyright (C) 2017 Intel Corporation -- * -- * Authors: -- * Rajmohan Mani -- * Tianshu Qiu -- * Jian Xu Zheng -- * Yuning Pu -- */ -- --#include --#include --#include --#include --#include --#include --#include -- --static const struct mfd_cell tps68470s[] = { -- { .name = "tps68470-gpio" }, -- { .name = "tps68470_pmic_opregion" }, --}; -- --static const struct regmap_config tps68470_regmap_config = { -- .reg_bits = 8, -- .val_bits = 8, -- .max_register = TPS68470_REG_MAX, --}; -- --static int tps68470_chip_init(struct device *dev, struct regmap *regmap) --{ -- unsigned int version; -- int ret; -- -- /* Force software reset */ -- ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); -- if (ret) -- return ret; -- -- ret = regmap_read(regmap, TPS68470_REG_REVID, &version); -- if (ret) { -- dev_err(dev, "Failed to read revision register: %d\n", ret); -- return ret; -- } -- -- dev_info(dev, "TPS68470 REVID: 0x%x\n", version); -- -- return 0; --} -- --static int tps68470_probe(struct i2c_client *client) --{ -- struct device *dev = &client->dev; -- struct regmap *regmap; -- int ret; -- -- regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); -- if (IS_ERR(regmap)) { -- dev_err(dev, "devm_regmap_init_i2c Error %ld\n", -- PTR_ERR(regmap)); -- return PTR_ERR(regmap); -- } -- -- i2c_set_clientdata(client, regmap); -- -- ret = tps68470_chip_init(dev, regmap); -- if (ret < 0) { -- dev_err(dev, "TPS68470 Init Error %d\n", ret); -- return ret; -- } -- -- ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, tps68470s, -- ARRAY_SIZE(tps68470s), NULL, 0, NULL); -- if (ret < 0) { -- dev_err(dev, "devm_mfd_add_devices failed: %d\n", ret); -- return ret; -- } -- -- return 0; --} -- --static const struct acpi_device_id tps68470_acpi_ids[] = { -- {"INT3472"}, -- {}, --}; -- --static struct i2c_driver tps68470_driver = { -- .driver = { -- .name = "tps68470", -- .acpi_match_table = tps68470_acpi_ids, -- }, -- .probe_new = tps68470_probe, --}; --builtin_i2c_driver(tps68470_driver); --- -2.33.0 - -From 8fe7e113fe28d240202bf5047de448b5cd86a6de Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 15 Jan 2021 12:37:31 +0000 -Subject: [PATCH] platform: x86: Add intel_skl_int3472 driver - -ACPI devices with _HID INT3472 are currently matched to the tps68470 -driver, however this does not cover all situations in which that _HID -occurs. We've encountered three possibilities: - -1. On Chrome OS devices, an ACPI device with _HID INT3472 (representing -a physical tps68470 device) that requires a GPIO and OpRegion driver -2. On devices designed for Windows, an ACPI device with _HID INT3472 -(again representing a physical tps68470 device) which requires GPIO, -Clock and Regulator drivers. -3. On other devices designed for Windows, an ACPI device with _HID -INT3472 which does NOT represent a physical tps68470, and is instead -used as a dummy device to group some system GPIO lines which are meant -to be consumed by the sensor that is dependent on this entry. - -This commit adds a new module, registering a platform driver to deal -with the 3rd scenario plus an i2c-driver to deal with #1 and #2, by -querying the CLDB buffer found against INT3472 entries to determine -which is most appropriate. - -Suggested-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 5 + - drivers/platform/x86/Kconfig | 25 + - drivers/platform/x86/Makefile | 5 + - .../platform/x86/intel_skl_int3472_common.c | 100 ++++ - .../platform/x86/intel_skl_int3472_common.h | 99 ++++ - .../platform/x86/intel_skl_int3472_discrete.c | 489 ++++++++++++++++++ - .../platform/x86/intel_skl_int3472_tps68470.c | 145 ++++++ - 7 files changed, 868 insertions(+) - create mode 100644 drivers/platform/x86/intel_skl_int3472_common.c - create mode 100644 drivers/platform/x86/intel_skl_int3472_common.h - create mode 100644 drivers/platform/x86/intel_skl_int3472_discrete.c - create mode 100644 drivers/platform/x86/intel_skl_int3472_tps68470.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index f313ba49c2b8..7bec93adaae5 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -9045,6 +9045,11 @@ S: Maintained - F: arch/x86/include/asm/intel_scu_ipc.h - F: drivers/platform/x86/intel_scu_* - -+INTEL SKL INT3472 ACPI DEVICE DRIVER -+M: Daniel Scally -+S: Maintained -+F: drivers/platform/x86/intel_skl_int3472_* -+ - INTEL SPEED SELECT TECHNOLOGY - M: Srinivas Pandruvada - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 533998040530..ab8324034c76 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -807,6 +807,31 @@ config INTEL_CHT_INT33FE - device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m - for Type-C device. - -+config INTEL_SKL_INT3472 -+ tristate "Intel SkyLake ACPI INT3472 Driver" -+ depends on X86 && ACPI -+ select REGMAP_I2C -+ help -+ This driver adds support for the INT3472 ACPI devices found on some -+ Intel SkyLake devices. -+ -+ There are 3 kinds of INT3472 ACPI device possible; two for devices -+ designed for Windows (either with or without a physical tps68470 -+ PMIC) and one designed for Chrome OS. This driver handles all three -+ situations by discovering information it needs to discern them at -+ runtime. -+ -+ If your device was designed for Chrome OS, this driver will provide -+ an ACPI operation region, which must be available before any of the -+ devices using this are probed. For this reason, you should select Y -+ if your device was designed for ChromeOS. This option also configures -+ the designware-i2c driver to be built-in, for the same reason. -+ -+ Say Y or M here if you have a SkyLake device designed for use -+ with Windows or ChromeOS. Say N here if you are not sure. -+ -+ The module will be named "intel-skl-int3472" -+ - config INTEL_HID_EVENT - tristate "INTEL HID Event" - depends on ACPI -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index f552cbfb7914..dd8fc06b224c 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -79,6 +79,11 @@ obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o - obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o - obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o - obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o -+obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o -+intel_skl_int3472-objs := intel_skl_int3472_common.o \ -+ intel_skl_int3472_discrete.o \ -+ intel_skl_int3472_tps68470.o -+ - obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o - - # Microsoft -diff --git a/drivers/platform/x86/intel_skl_int3472_common.c b/drivers/platform/x86/intel_skl_int3472_common.c -new file mode 100644 -index 000000000000..08cb9d3c06aa ---- /dev/null -+++ b/drivers/platform/x86/intel_skl_int3472_common.c -@@ -0,0 +1,100 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+ -+#include "intel_skl_int3472_common.h" -+ -+int skl_int3472_get_cldb_buffer(struct acpi_device *adev, -+ struct int3472_cldb *cldb) -+{ -+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; -+ acpi_handle handle = adev->handle; -+ union acpi_object *obj; -+ acpi_status status; -+ int ret = 0; -+ -+ status = acpi_evaluate_object(handle, "CLDB", NULL, &buffer); -+ if (ACPI_FAILURE(status)) -+ return -ENODEV; -+ -+ obj = buffer.pointer; -+ if (!obj) { -+ dev_err(&adev->dev, "ACPI device has no CLDB object\n"); -+ return -ENODEV; -+ } -+ -+ if (obj->type != ACPI_TYPE_BUFFER) { -+ dev_err(&adev->dev, "CLDB object is not an ACPI buffer\n"); -+ ret = -EINVAL; -+ goto out_free_buff; -+ } -+ -+ if (obj->buffer.length > sizeof(*cldb)) { -+ dev_err(&adev->dev, "The CLDB buffer is too large\n"); -+ ret = -EINVAL; -+ goto out_free_buff; -+ } -+ -+ memcpy(cldb, obj->buffer.pointer, obj->buffer.length); -+ -+out_free_buff: -+ kfree(buffer.pointer); -+ return ret; -+} -+ -+static const struct acpi_device_id int3472_device_id[] = { -+ { "INT3472", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, int3472_device_id); -+ -+static struct platform_driver int3472_discrete = { -+ .driver = { -+ .name = "int3472-discrete", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe = skl_int3472_discrete_probe, -+ .remove = skl_int3472_discrete_remove, -+}; -+ -+static struct i2c_driver int3472_tps68470 = { -+ .driver = { -+ .name = "int3472-tps68470", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe_new = skl_int3472_tps68470_probe, -+}; -+ -+static int skl_int3472_init(void) -+{ -+ int ret = 0; -+ -+ ret = platform_driver_register(&int3472_discrete); -+ if (ret) -+ return ret; -+ -+ ret = i2c_register_driver(THIS_MODULE, &int3472_tps68470); -+ if (ret) -+ goto err_unregister_plat_drv; -+ -+ return 0; -+ -+err_unregister_plat_drv: -+ platform_driver_unregister(&int3472_discrete); -+ return ret; -+} -+module_init(skl_int3472_init); -+ -+static void skl_int3472_exit(void) -+{ -+ platform_driver_unregister(&int3472_discrete); -+ i2c_del_driver(&int3472_tps68470); -+} -+module_exit(skl_int3472_exit); -+ -+MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver"); -+MODULE_AUTHOR("Daniel Scally "); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/platform/x86/intel_skl_int3472_common.h b/drivers/platform/x86/intel_skl_int3472_common.h -new file mode 100644 -index 000000000000..4ac6bb2b223f ---- /dev/null -+++ b/drivers/platform/x86/intel_skl_int3472_common.h -@@ -0,0 +1,99 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* Author: Dan Scally */ -+#include -+#include -+#include -+#include -+#include -+ -+/* PMIC GPIO Types */ -+#define INT3472_GPIO_TYPE_RESET 0x00 -+#define INT3472_GPIO_TYPE_POWERDOWN 0x01 -+#define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c -+#define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b -+#define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d -+#define INT3472_PDEV_MAX_NAME_LEN 23 -+#define INT3472_MAX_SENSOR_GPIOS 3 -+#define GPIO_REGULATOR_NAME_LENGTH 27 -+#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9 -+ -+#define INT3472_REGULATOR(_NAME, _SUPPLY, _OPS) \ -+ (const struct regulator_desc) { \ -+ .name = _NAME, \ -+ .supply_name = _SUPPLY, \ -+ .id = 0, \ -+ .type = REGULATOR_VOLTAGE, \ -+ .ops = _OPS, \ -+ .owner = THIS_MODULE, \ -+ } -+ -+#define INT3472_GPIO_FUNCTION_REMAP(_PIN, _FUNCTION) \ -+ (const struct int3472_gpio_function_remap) { \ -+ .documented = _PIN, \ -+ .actual = _FUNCTION \ -+ } -+ -+#define to_int3472_clk(hw) \ -+ container_of(hw, struct int3472_gpio_clock, clk_hw) -+ -+struct int3472_cldb { -+ u8 version; -+ /* -+ * control logic type -+ * 0: UNKNOWN -+ * 1: DISCRETE(CRD-D) -+ * 2: PMIC TPS68470 -+ * 3: PMIC uP6641 -+ */ -+ u8 control_logic_type; -+ u8 control_logic_id; -+ u8 sensor_card_sku; -+ u8 reserved[28]; -+}; -+ -+struct int3472_gpio_regulator { -+ char regulator_name[GPIO_REGULATOR_NAME_LENGTH]; -+ char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH]; -+ struct gpio_desc *gpio; -+ struct regulator_dev *rdev; -+ struct regulator_desc rdesc; -+}; -+ -+struct int3472_gpio_clock { -+ struct clk *clk; -+ struct clk_hw clk_hw; -+ struct gpio_desc *gpio; -+}; -+ -+struct int3472_device { -+ struct acpi_device *adev; -+ struct platform_device *pdev; -+ struct acpi_device *sensor; -+ char *sensor_name; -+ -+ unsigned int n_gpios; /* how many GPIOs have we seen */ -+ -+ struct int3472_gpio_regulator regulator; -+ struct int3472_gpio_clock clock; -+ -+ unsigned int n_sensor_gpios; /* how many have we mapped to sensor */ -+ bool gpios_mapped; -+ struct gpiod_lookup_table gpios; -+}; -+ -+struct int3472_gpio_function_remap { -+ char *documented; -+ char *actual; -+}; -+ -+struct int3472_sensor_config { -+ char *sensor_module_name; -+ struct regulator_consumer_supply supply_map; -+ const struct int3472_gpio_function_remap *function_maps; -+}; -+ -+int skl_int3472_discrete_probe(struct platform_device *pdev); -+int skl_int3472_discrete_remove(struct platform_device *pdev); -+int skl_int3472_tps68470_probe(struct i2c_client *client); -+int skl_int3472_get_cldb_buffer(struct acpi_device *adev, -+ struct int3472_cldb *cldb); -diff --git a/drivers/platform/x86/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel_skl_int3472_discrete.c -new file mode 100644 -index 000000000000..ea7e57f3e3f0 ---- /dev/null -+++ b/drivers/platform/x86/intel_skl_int3472_discrete.c -@@ -0,0 +1,489 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "intel_skl_int3472_common.h" -+ -+/* 79234640-9e10-4fea-a5c1b5aa8b19756f */ -+static const guid_t int3472_gpio_guid = -+ GUID_INIT(0x79234640, 0x9e10, 0x4fea, -+ 0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f); -+ -+/* 822ace8f-2814-4174-a56b5f029fe079ee */ -+static const guid_t cio2_sensor_module_guid = -+ GUID_INIT(0x822ace8f, 0x2814, 0x4174, -+ 0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee); -+ -+/* -+ * Here follows platform specific mapping information that we can pass to -+ * the functions mapping resources to the sensors. Where the sensors have -+ * a power enable pin defined in DSDT we need to provide a supply name so -+ * the sensor drivers can find the regulator. Optionally, we can provide a -+ * NULL terminated array of function name mappings to deal with any platform -+ * specific deviations from the documented behaviour of GPIOs. -+ * -+ * Map a GPIO function name to NULL to prevent the driver from mapping that -+ * GPIO at all. -+ */ -+ -+static const struct int3472_gpio_function_remap ov2680_gpio_function_remaps[] = { -+ INT3472_GPIO_FUNCTION_REMAP("reset", NULL), -+ INT3472_GPIO_FUNCTION_REMAP("powerdown", "reset"), -+ { } -+}; -+ -+static struct int3472_sensor_config int3472_sensor_configs[] = { -+ /* Lenovo Miix 510-12ISK - OV2680, Front */ -+ { "GNDF140809R", { 0 }, ov2680_gpio_function_remaps}, -+ /* Lenovo Miix 510-12ISK - OV5648, Rear */ -+ { "GEFF150023R", REGULATOR_SUPPLY("avdd", "i2c-OVTI5648:00"), NULL}, -+ /* Surface Go 1&2 - OV5693, Front */ -+ { "YHCU", REGULATOR_SUPPLY("avdd", "i2c-INT33BE:00"), NULL}, -+}; -+ -+/* -+ * The regulators have to have .ops to be valid, but the only ops we actually -+ * support are .enable and .disable which are handled via .ena_gpiod. Pass an -+ * empty struct to clear the check without lying about capabilities. -+ */ -+static const struct regulator_ops int3472_gpio_regulator_ops = { 0 }; -+ -+static int skl_int3472_clk_enable(struct clk_hw *hw) -+{ -+ struct int3472_gpio_clock *clk = to_int3472_clk(hw); -+ -+ gpiod_set_value(clk->gpio, 1); -+ -+ return 0; -+} -+ -+static void skl_int3472_clk_disable(struct clk_hw *hw) -+{ -+ struct int3472_gpio_clock *clk = to_int3472_clk(hw); -+ -+ gpiod_set_value(clk->gpio, 0); -+} -+ -+static int skl_int3472_clk_prepare(struct clk_hw *hw) -+{ -+ /* -+ * We're just turning a GPIO on to enable, so nothing to do here, but -+ * we want to provide the op so prepare_enable() works. -+ */ -+ return 0; -+} -+ -+static void skl_int3472_clk_unprepare(struct clk_hw *hw) -+{ -+ /* Likewise, nothing to do here... */ -+} -+ -+static const struct clk_ops skl_int3472_clock_ops = { -+ .prepare = skl_int3472_clk_prepare, -+ .unprepare = skl_int3472_clk_unprepare, -+ .enable = skl_int3472_clk_enable, -+ .disable = skl_int3472_clk_disable, -+}; -+ -+static struct int3472_sensor_config * -+int3472_get_sensor_module_config(struct int3472_device *int3472) -+{ -+ unsigned int i = ARRAY_SIZE(int3472_sensor_configs); -+ struct int3472_sensor_config *ret; -+ union acpi_object *obj; -+ -+ obj = acpi_evaluate_dsm_typed(int3472->sensor->handle, -+ &cio2_sensor_module_guid, 0x00, -+ 0x01, NULL, ACPI_TYPE_STRING); -+ -+ if (!obj) { -+ dev_err(&int3472->pdev->dev, -+ "Failed to get sensor module string from _DSM\n"); -+ return ERR_PTR(-ENODEV); -+ } -+ -+ if (obj->string.type != ACPI_TYPE_STRING) { -+ dev_err(&int3472->pdev->dev, -+ "Sensor _DSM returned a non-string value\n"); -+ ret = ERR_PTR(-EINVAL); -+ goto out_free_obj; -+ } -+ -+ ret = ERR_PTR(-ENODEV); -+ while (i--) { -+ if (!strcmp(int3472_sensor_configs[i].sensor_module_name, -+ obj->string.pointer)) { -+ ret = &int3472_sensor_configs[i]; -+ goto out_free_obj; -+ } -+ } -+ -+out_free_obj: -+ ACPI_FREE(obj); -+ return ret; -+} -+ -+static int int3472_map_gpio_to_sensor(struct int3472_device *int3472, -+ struct acpi_resource *ares, -+ char *func, u32 polarity) -+{ -+ char *path = ares->data.gpio.resource_source.string_ptr; -+ struct int3472_sensor_config *sensor_config; -+ struct gpiod_lookup table_entry; -+ struct acpi_device *adev; -+ acpi_handle handle; -+ acpi_status status; -+ int ret; -+ -+ sensor_config = int3472_get_sensor_module_config(int3472); -+ if (!IS_ERR(sensor_config) && sensor_config->function_maps) { -+ unsigned int i = 0; -+ -+ while (sensor_config->function_maps[i].documented) { -+ if (!strcmp(func, sensor_config->function_maps[i].documented)) { -+ func = sensor_config->function_maps[i].actual; -+ -+ break; -+ } -+ -+ i++; -+ } -+ } -+ -+ if (!func) -+ return 0; -+ -+ if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { -+ dev_warn(&int3472->pdev->dev, "Too many GPIOs mapped\n"); -+ return -EINVAL; -+ } -+ -+ status = acpi_get_handle(NULL, path, &handle); -+ if (ACPI_FAILURE(status)) -+ return -EINVAL; -+ -+ ret = acpi_bus_get_device(handle, &adev); -+ if (ret) -+ return -ENODEV; -+ -+ table_entry = (struct gpiod_lookup)GPIO_LOOKUP_IDX(acpi_dev_name(adev), -+ ares->data.gpio.pin_table[0], -+ func, 0, polarity); -+ -+ memcpy(&int3472->gpios.table[int3472->n_sensor_gpios], &table_entry, -+ sizeof(table_entry)); -+ -+ int3472->n_sensor_gpios++; -+ -+ return 0; -+} -+ -+static int int3472_register_clock(struct int3472_device *int3472, -+ struct acpi_resource *ares) -+{ -+ char *path = ares->data.gpio.resource_source.string_ptr; -+ struct clk_init_data init = { }; -+ int ret = 0; -+ -+ init.name = kasprintf(GFP_KERNEL, "%s-clk", acpi_dev_name(int3472->adev)); -+ init.ops = &skl_int3472_clock_ops; -+ -+ int3472->clock.gpio = acpi_get_gpiod(path, ares->data.gpio.pin_table[0]); -+ if (IS_ERR(int3472->clock.gpio)) { -+ ret = PTR_ERR(int3472->clock.gpio); -+ goto out_free_init_name; -+ } -+ -+ int3472->clock.clk_hw.init = &init; -+ int3472->clock.clk = clk_register(&int3472->adev->dev, -+ &int3472->clock.clk_hw); -+ if (IS_ERR(int3472->clock.clk)) { -+ ret = PTR_ERR(int3472->clock.clk); -+ goto err_put_gpio; -+ } -+ -+ ret = clk_register_clkdev(int3472->clock.clk, "xvclk", int3472->sensor_name); -+ if (ret) -+ goto err_unregister_clk; -+ -+ goto out_free_init_name; -+ -+err_unregister_clk: -+ clk_unregister(int3472->clock.clk); -+err_put_gpio: -+ gpiod_put(int3472->clock.gpio); -+out_free_init_name: -+ kfree(init.name); -+ -+ return ret; -+} -+ -+static int int3472_register_regulator(struct int3472_device *int3472, -+ struct acpi_resource *ares) -+{ -+ char *path = ares->data.gpio.resource_source.string_ptr; -+ struct int3472_sensor_config *sensor_config; -+ struct regulator_init_data init_data = { }; -+ struct int3472_gpio_regulator *regulator; -+ struct regulator_config cfg = { }; -+ int ret; -+ -+ sensor_config = int3472_get_sensor_module_config(int3472); -+ if (IS_ERR_OR_NULL(sensor_config)) { -+ dev_err(&int3472->pdev->dev, "No sensor module config\n"); -+ return PTR_ERR(sensor_config); -+ } -+ -+ if (!sensor_config->supply_map.supply) { -+ dev_err(&int3472->pdev->dev, "No supply name defined\n"); -+ return -ENODEV; -+ } -+ -+ init_data.supply_regulator = NULL; -+ init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; -+ init_data.num_consumer_supplies = 1; -+ init_data.consumer_supplies = &sensor_config->supply_map; -+ -+ snprintf(int3472->regulator.regulator_name, GPIO_REGULATOR_NAME_LENGTH, -+ "int3472-discrete-regulator"); -+ snprintf(int3472->regulator.supply_name, GPIO_REGULATOR_SUPPLY_NAME_LENGTH, -+ "supply-0"); -+ -+ int3472->regulator.rdesc = INT3472_REGULATOR(int3472->regulator.regulator_name, -+ int3472->regulator.supply_name, -+ &int3472_gpio_regulator_ops); -+ -+ int3472->regulator.gpio = acpi_get_gpiod(path, ares->data.gpio.pin_table[0]); -+ if (IS_ERR(int3472->regulator.gpio)) { -+ ret = PTR_ERR(int3472->regulator.gpio); -+ goto err_free_regulator; -+ } -+ -+ cfg.dev = &int3472->adev->dev; -+ cfg.init_data = &init_data; -+ cfg.ena_gpiod = int3472->regulator.gpio; -+ -+ int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc, &cfg); -+ if (IS_ERR(int3472->regulator.rdev)) { -+ ret = PTR_ERR(int3472->regulator.rdev); -+ goto err_free_gpio; -+ } -+ -+ return 0; -+ -+err_free_gpio: -+ gpiod_put(regulator->gpio); -+err_free_regulator: -+ kfree(regulator); -+ -+ return ret; -+} -+ -+/** -+ * int3472_handle_gpio_resources: maps PMIC resources to consuming sensor -+ * @ares: A pointer to a &struct acpi_resource -+ * @data: A pointer to a &struct int3472_device -+ * -+ * This function handles GPIO resources that are against an INT3472 -+ * ACPI device, by checking the value of the corresponding _DSM entry. -+ * This will return a 32bit int, where the lowest byte represents the -+ * function of the GPIO pin: -+ * -+ * 0x00 Reset -+ * 0x01 Power down -+ * 0x0b Power enable -+ * 0x0c Clock enable -+ * 0x0d Privacy LED -+ * -+ * There are some known platform specific quirks where that does not quite -+ * hold up; for example where a pin with type 0x01 (Power down) is mapped to -+ * a sensor pin that performs a reset function. These will be handled by the -+ * mapping sub-functions. -+ * -+ * GPIOs will either be mapped directly to the sensor device or else used -+ * to create clocks and regulators via the usual frameworks. -+ * -+ * Return: -+ * * 0 - When all resources found are handled properly. -+ * * -EINVAL - If the resource is not a GPIO IO resource -+ * * -ENODEV - If the resource has no corresponding _DSM entry -+ * * -Other - Errors propagated from one of the sub-functions. -+ */ -+static int int3472_handle_gpio_resources(struct acpi_resource *ares, -+ void *data) -+{ -+ struct int3472_device *int3472 = data; -+ union acpi_object *obj; -+ int ret = 0; -+ -+ if (ares->type != ACPI_RESOURCE_TYPE_GPIO || -+ ares->data.gpio.connection_type != ACPI_RESOURCE_GPIO_TYPE_IO) -+ return EINVAL; /* Deliberately positive so parsing continues */ -+ -+ /* -+ * n_gpios + 2 because the index of this _DSM function is 1-based and -+ * the first function is just a count. -+ */ -+ obj = acpi_evaluate_dsm_typed(int3472->adev->handle, -+ &int3472_gpio_guid, 0x00, -+ int3472->n_gpios + 2, -+ NULL, ACPI_TYPE_INTEGER); -+ -+ if (!obj) { -+ dev_warn(&int3472->pdev->dev, -+ "No _DSM entry for this GPIO pin\n"); -+ return ENODEV; -+ } -+ -+ switch (obj->integer.value & 0xff) { -+ case INT3472_GPIO_TYPE_RESET: -+ ret = int3472_map_gpio_to_sensor(int3472, ares, "reset", -+ GPIO_ACTIVE_LOW); -+ if (ret) -+ dev_err(&int3472->pdev->dev, -+ "Failed to map reset pin to sensor\n"); -+ -+ break; -+ case INT3472_GPIO_TYPE_POWERDOWN: -+ ret = int3472_map_gpio_to_sensor(int3472, ares, "powerdown", -+ GPIO_ACTIVE_LOW); -+ if (ret) -+ dev_err(&int3472->pdev->dev, -+ "Failed to map powerdown pin to sensor\n"); -+ -+ break; -+ case INT3472_GPIO_TYPE_CLK_ENABLE: -+ ret = int3472_register_clock(int3472, ares); -+ if (ret) -+ dev_err(&int3472->pdev->dev, -+ "Failed to map clock to sensor\n"); -+ -+ break; -+ case INT3472_GPIO_TYPE_POWER_ENABLE: -+ ret = int3472_register_regulator(int3472, ares); -+ if (ret) { -+ dev_err(&int3472->pdev->dev, -+ "Failed to map regulator to sensor\n"); -+ } -+ -+ break; -+ case INT3472_GPIO_TYPE_PRIVACY_LED: -+ ret = int3472_map_gpio_to_sensor(int3472, ares, "indicator-led", -+ GPIO_ACTIVE_HIGH); -+ if (ret) -+ dev_err(&int3472->pdev->dev, -+ "Failed to map indicator led to sensor\n"); -+ -+ break; -+ default: -+ dev_warn(&int3472->pdev->dev, -+ "GPIO type 0x%llx unknown; the sensor may not work\n", -+ (obj->integer.value & 0xff)); -+ ret = EINVAL; -+ } -+ -+ int3472->n_gpios++; -+ ACPI_FREE(obj); -+ -+ return ret; -+} -+ -+static int int3472_parse_crs(struct int3472_device *int3472) -+{ -+ struct list_head resource_list; -+ int ret = 0; -+ -+ INIT_LIST_HEAD(&resource_list); -+ -+ ret = acpi_dev_get_resources(int3472->adev, &resource_list, -+ int3472_handle_gpio_resources, int3472); -+ -+ if (!ret) { -+ gpiod_add_lookup_table(&int3472->gpios); -+ int3472->gpios_mapped = true; -+ } -+ -+ acpi_dev_free_resource_list(&resource_list); -+ -+ return ret; -+} -+ -+int skl_int3472_discrete_probe(struct platform_device *pdev) -+{ -+ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); -+ struct int3472_device *int3472; -+ struct int3472_cldb cldb; -+ int ret = 0; -+ -+ ret = skl_int3472_get_cldb_buffer(adev, &cldb); -+ if (ret || cldb.control_logic_type != 1) -+ return -EINVAL; -+ -+ int3472 = kzalloc(sizeof(*int3472) + -+ ((INT3472_MAX_SENSOR_GPIOS + 1) * sizeof(struct gpiod_lookup)), -+ GFP_KERNEL); -+ if (!int3472) -+ return -ENOMEM; -+ -+ int3472->adev = adev; -+ int3472->pdev = pdev; -+ platform_set_drvdata(pdev, int3472); -+ -+ int3472->sensor = acpi_dev_get_next_dep_dev(adev, NULL); -+ if (!int3472->sensor) { -+ dev_err(&pdev->dev, -+ "This INT3472 entry seems to have no dependents.\n"); -+ ret = -ENODEV; -+ goto err_free_int3472; -+ } -+ int3472->sensor_name = i2c_acpi_dev_name(int3472->sensor); -+ int3472->gpios.dev_id = int3472->sensor_name; -+ -+ ret = int3472_parse_crs(int3472); -+ if (ret) { -+ skl_int3472_discrete_remove(pdev); -+ goto err_return_ret; -+ } -+ -+ return 0; -+ -+err_free_int3472: -+ kfree(int3472); -+err_return_ret: -+ return ret; -+} -+ -+int skl_int3472_discrete_remove(struct platform_device *pdev) -+{ -+ struct int3472_device *int3472; -+ -+ int3472 = platform_get_drvdata(pdev); -+ -+ if (int3472->gpios_mapped) -+ gpiod_remove_lookup_table(&int3472->gpios); -+ -+ if (!IS_ERR_OR_NULL(int3472->regulator.rdev)) { -+ gpiod_put(int3472->regulator.gpio); -+ regulator_unregister(int3472->regulator.rdev); -+ } -+ -+ if (!IS_ERR_OR_NULL(int3472->clock.clk)) { -+ gpiod_put(int3472->clock.gpio); -+ clk_unregister(int3472->clock.clk); -+ } -+ -+ acpi_dev_put(int3472->sensor); -+ -+ kfree(int3472->sensor_name); -+ kfree(int3472); -+ -+ return 0; -+} -diff --git a/drivers/platform/x86/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel_skl_int3472_tps68470.c -new file mode 100644 -index 000000000000..3fe27ec0caff ---- /dev/null -+++ b/drivers/platform/x86/intel_skl_int3472_tps68470.c -@@ -0,0 +1,145 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+#include -+ -+#include "intel_skl_int3472_common.h" -+ -+static const struct regmap_config tps68470_regmap_config = { -+ .reg_bits = 8, -+ .val_bits = 8, -+ .max_register = TPS68470_REG_MAX, -+}; -+ -+static int tps68470_chip_init(struct device *dev, struct regmap *regmap) -+{ -+ unsigned int version; -+ int ret; -+ -+ /* Force software reset */ -+ ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); -+ if (ret) -+ return ret; -+ -+ ret = regmap_read(regmap, TPS68470_REG_REVID, &version); -+ if (ret) { -+ dev_err(dev, "Failed to read revision register: %d\n", ret); -+ return ret; -+ } -+ -+ dev_info(dev, "TPS68470 REVID: 0x%x\n", version); -+ -+ return 0; -+} -+ -+static struct platform_device * -+skl_int3472_register_pdev(const char *name, struct device *parent) -+{ -+ struct platform_device *pdev; -+ int ret; -+ -+ pdev = platform_device_alloc(name, PLATFORM_DEVID_NONE); -+ if (IS_ERR_OR_NULL(pdev)) -+ return ERR_PTR(-ENOMEM); -+ -+ pdev->dev.parent = parent; -+ pdev->driver_override = kstrndup(pdev->name, INT3472_PDEV_MAX_NAME_LEN, -+ GFP_KERNEL); -+ -+ ret = platform_device_add(pdev); -+ if (ret) { -+ platform_device_put(pdev); -+ return ERR_PTR(ret); -+ } -+ -+ return pdev; -+} -+ -+int skl_int3472_tps68470_probe(struct i2c_client *client) -+{ -+ struct acpi_device *adev = ACPI_COMPANION(&client->dev); -+ struct platform_device *regulator_dev; -+ struct platform_device *opregion_dev; -+ struct platform_device *gpio_dev; -+ struct int3472_cldb cldb = { 0 }; -+ struct platform_device *clk_dev; -+ bool cldb_present = true; -+ struct regmap *regmap; -+ int ret = 0; -+ -+ regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); -+ if (IS_ERR(regmap)) { -+ dev_err(&client->dev, "devm_regmap_init_i2c Error %ld\n", -+ PTR_ERR(regmap)); -+ return PTR_ERR(regmap); -+ } -+ -+ i2c_set_clientdata(client, regmap); -+ -+ ret = tps68470_chip_init(&client->dev, regmap); -+ if (ret < 0) { -+ dev_err(&client->dev, "TPS68470 Init Error %d\n", ret); -+ return ret; -+ } -+ -+ /* -+ * Check CLDB buffer against the PMIC's adev. If present, then we check -+ * the value of control_logic_type field and follow one of the following -+ * scenarios: -+ * -+ * 1. No CLDB - likely ACPI tables designed for ChromeOS. We create -+ * platform devices for the GPIOs and OpRegion drivers. -+ * -+ * 2. CLDB, with control_logic_type = 2 - probably ACPI tables made -+ * for Windows 2-in-1 platforms. Register pdevs for GPIO, Clock and -+ * Regulator drivers to bind to. -+ * -+ * 3. Any other value in control_logic_type, we should never have -+ * gotten to this point; crash and burn. -+ */ -+ ret = skl_int3472_get_cldb_buffer(adev, &cldb); -+ if (!ret && cldb.control_logic_type != 2) -+ return -EINVAL; -+ -+ if (ret) -+ cldb_present = false; -+ -+ gpio_dev = skl_int3472_register_pdev("tps68470-gpio", &client->dev); -+ if (IS_ERR(gpio_dev)) -+ return PTR_ERR(gpio_dev); -+ -+ if (cldb_present) { -+ clk_dev = skl_int3472_register_pdev("tps68470-clk", -+ &client->dev); -+ if (IS_ERR(clk_dev)) { -+ ret = PTR_ERR(clk_dev); -+ goto err_free_gpio; -+ } -+ -+ regulator_dev = skl_int3472_register_pdev("tps68470-regulator", -+ &client->dev); -+ if (IS_ERR(regulator_dev)) { -+ ret = PTR_ERR(regulator_dev); -+ goto err_free_clk; -+ } -+ } else { -+ opregion_dev = skl_int3472_register_pdev("tps68470_pmic_opregion", -+ &client->dev); -+ if (IS_ERR(opregion_dev)) { -+ ret = PTR_ERR(opregion_dev); -+ goto err_free_gpio; -+ } -+ } -+ -+ return 0; -+ -+err_free_clk: -+ platform_device_put(clk_dev); -+err_free_gpio: -+ platform_device_put(gpio_dev); -+ -+ return ret; -+} --- -2.33.0 - -From dc854a10270465727e4ec5e9c25a2c88b55539b2 Mon Sep 17 00:00:00 2001 -From: "Rafael J. Wysocki" -Date: Fri, 11 Dec 2020 21:17:35 +0100 -Subject: [PATCH] PCI: ACPI: Fix up ACPI companion lookup for device 0 on the - root bus - -In some cases acpi_pci_find_companion() returns an incorrect device -object as the ACPI companion for device 0 on the root bus (bus 0). - -On the affected systems that device is the PCI interface to the -host bridge and the "ACPI companion" returned for it corresponds -to a non-PCI device located in the SoC (e.g. a sensor on an I2C -bus). As a result of this, the ACPI device object "attached" -to PCI device 00:00.0 cannot be used for enumerating the device -that is really represented by it which (of course) is problematic. - -Address that issue by preventing acpi_pci_find_companion() from -returning a device object with a valid _HID (which by the spec -should not be present uder ACPI device objects corresponding to -PCI devices) for PCI device 00:00.0. - -Link: https://lore.kernel.org/linux-acpi/1409ba0c-1580-dc09-e6fe-a0c9bcda6462@gmail.com/ -Reported-by: Daniel Scally -Signed-off-by: Rafael J. Wysocki -Patchset: cameras ---- - drivers/pci/pci-acpi.c | 20 +++++++++++++++++++- - 1 file changed, 19 insertions(+), 1 deletion(-) - -diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c -index 745a4e0c4994..87e45a800919 100644 ---- a/drivers/pci/pci-acpi.c -+++ b/drivers/pci/pci-acpi.c -@@ -1162,14 +1162,32 @@ void acpi_pci_remove_bus(struct pci_bus *bus) - static struct acpi_device *acpi_pci_find_companion(struct device *dev) - { - struct pci_dev *pci_dev = to_pci_dev(dev); -+ struct acpi_device *adev; - bool check_children; - u64 addr; - - check_children = pci_is_bridge(pci_dev); - /* Please ref to ACPI spec for the syntax of _ADR */ - addr = (PCI_SLOT(pci_dev->devfn) << 16) | PCI_FUNC(pci_dev->devfn); -- return acpi_find_child_device(ACPI_COMPANION(dev->parent), addr, -+ adev = acpi_find_child_device(ACPI_COMPANION(dev->parent), addr, - check_children); -+ /* -+ * There may be ACPI device objects in the ACPI namespace that are -+ * children of the device object representing the host bridge, but don't -+ * represent PCI devices. Both _HID and _ADR may be present for them, -+ * even though that is against the specification (for example, see -+ * Section 6.1 of ACPI 6.3), but in many cases the _ADR returns 0 which -+ * appears to indicate that they should not be taken into consideration -+ * as potential companions of PCI devices on the root bus. -+ * -+ * To catch this special case, disregard the returned device object if -+ * it has a valid _HID, addr is 0 and the PCI device at hand is on the -+ * root bus. -+ */ -+ if (adev && adev->pnp.type.platform_id && !addr && !pci_dev->bus->parent) -+ return NULL; -+ -+ return adev; - } - - /** --- -2.33.0 - -From 1e350fd1a4d00b6b9fd895b017daece0db39380a Mon Sep 17 00:00:00 2001 -From: Jake Day -Date: Fri, 25 Sep 2020 10:24:53 -0400 -Subject: [PATCH] media: i2c: Add support for the OV5693 image sensor - -The OV5693 is a 5 Mpx CMOS image sensor, connected via MIPI CSI-2 -in a one or two lane configuration. - -Signed-off-by: Jean-Michel Hautbois -Patchset: cameras ---- - drivers/media/i2c/Kconfig | 11 + - drivers/media/i2c/Makefile | 1 + - drivers/media/i2c/ad5823.h | 63 ++ - drivers/media/i2c/ov5693.c | 1788 ++++++++++++++++++++++++++++++++++++ - drivers/media/i2c/ov5693.h | 1430 ++++++++++++++++++++++++++++ - 5 files changed, 3293 insertions(+) - create mode 100644 drivers/media/i2c/ad5823.h - create mode 100644 drivers/media/i2c/ov5693.c - create mode 100644 drivers/media/i2c/ov5693.h - -diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig -index 878f66ef2719..10bbc85d0aba 100644 ---- a/drivers/media/i2c/Kconfig -+++ b/drivers/media/i2c/Kconfig -@@ -958,6 +958,17 @@ config VIDEO_OV5675 - To compile this driver as a module, choose M here: the - module will be called ov5675. - -+config VIDEO_OV5693 -+ tristate "OmniVision OV5693 sensor support" -+ depends on I2C && VIDEO_V4L2 -+ select V4L2_FWNODE -+ help -+ This is a Video4Linux2 sensor driver for the OmniVision -+ OV5693 camera. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ov5693. -+ - config VIDEO_OV5695 - tristate "OmniVision OV5695 sensor support" - depends on I2C && VIDEO_V4L2 -diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile -index f0a77473979d..2bc1dd3e791b 100644 ---- a/drivers/media/i2c/Makefile -+++ b/drivers/media/i2c/Makefile -@@ -73,6 +73,7 @@ obj-$(CONFIG_VIDEO_OV5645) += ov5645.o - obj-$(CONFIG_VIDEO_OV5647) += ov5647.o - obj-$(CONFIG_VIDEO_OV5670) += ov5670.o - obj-$(CONFIG_VIDEO_OV5675) += ov5675.o -+obj-$(CONFIG_VIDEO_OV5693) += ov5693.o - obj-$(CONFIG_VIDEO_OV5695) += ov5695.o - obj-$(CONFIG_VIDEO_OV6650) += ov6650.o - obj-$(CONFIG_VIDEO_OV7251) += ov7251.o -diff --git a/drivers/media/i2c/ad5823.h b/drivers/media/i2c/ad5823.h -new file mode 100644 -index 000000000000..f1362cd69f6e ---- /dev/null -+++ b/drivers/media/i2c/ad5823.h -@@ -0,0 +1,63 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* -+ * Support for AD5823 VCM. -+ * -+ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. -+ * -+ * 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 __AD5823_H__ -+#define __AD5823_H__ -+ -+#include -+ -+#define AD5823_VCM_ADDR 0x0c -+ -+#define AD5823_REG_RESET 0x01 -+#define AD5823_REG_MODE 0x02 -+#define AD5823_REG_VCM_MOVE_TIME 0x03 -+#define AD5823_REG_VCM_CODE_MSB 0x04 -+#define AD5823_REG_VCM_CODE_LSB 0x05 -+#define AD5823_REG_VCM_THRESHOLD_MSB 0x06 -+#define AD5823_REG_VCM_THRESHOLD_LSB 0x07 -+ -+#define AD5823_REG_LENGTH 0x1 -+ -+#define AD5823_RING_CTRL_ENABLE 0x04 -+#define AD5823_RING_CTRL_DISABLE 0x00 -+ -+#define AD5823_RESONANCE_PERIOD 100000 -+#define AD5823_RESONANCE_COEF 512 -+#define AD5823_HIGH_FREQ_RANGE 0x80 -+ -+#define VCM_CODE_MSB_MASK 0xfc -+#define AD5823_INIT_FOCUS_POS 350 -+ -+enum ad5823_tok_type { -+ AD5823_8BIT = 0x1, -+ AD5823_16BIT = 0x2, -+}; -+ -+enum ad5823_vcm_mode { -+ AD5823_ARC_RES0 = 0x0, /* Actuator response control RES1 */ -+ AD5823_ARC_RES1 = 0x1, /* Actuator response control RES0.5 */ -+ AD5823_ARC_RES2 = 0x2, /* Actuator response control RES2 */ -+ AD5823_ESRC = 0x3, /* Enhanced slew rate control */ -+ AD5823_DIRECT = 0x4, /* Direct control */ -+}; -+ -+#define AD5823_INVALID_CONFIG 0xffffffff -+#define AD5823_MAX_FOCUS_POS 1023 -+#define DELAY_PER_STEP_NS 1000000 -+#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) -+#endif -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -new file mode 100644 -index 000000000000..32485e4ed42b ---- /dev/null -+++ b/drivers/media/i2c/ov5693.c -@@ -0,0 +1,1788 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Support for OmniVision OV5693 1080p HD camera sensor. -+ * -+ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. -+ * -+ * This program is free software; you can redistribute it and/or -+ * modify it under the terms of the GNU General Public License version -+ * 2 as published by the Free Software Foundation. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "ov5693.h" -+#include "ad5823.h" -+ -+#define __cci_delay(t) \ -+ do { \ -+ if ((t) < 10) { \ -+ usleep_range((t) * 1000, ((t) + 1) * 1000); \ -+ } else { \ -+ msleep((t)); \ -+ } \ -+ } while (0) -+ -+/* Value 30ms reached through experimentation on byt ecs. -+ * The DS specifies a much lower value but when using a smaller value -+ * the I2C bus sometimes locks up permanently when starting the camera. -+ * This issue could not be reproduced on cht, so we can reduce the -+ * delay value to a lower value when insmod. -+ */ -+static uint up_delay = 30; -+module_param(up_delay, uint, 0644); -+MODULE_PARM_DESC(up_delay, -+ "Delay prior to the first CCI transaction for ov5693"); -+ -+ -+/* Exposure/gain */ -+ -+#define OV5693_EXPOSURE_CTRL_HH_REG 0x3500 -+#define OV5693_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(18, 16)) >> 16) -+#define OV5693_EXPOSURE_CTRL_H_REG 0x3501 -+#define OV5693_EXPOSURE_CTRL_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_EXPOSURE_CTRL_L_REG 0x3502 -+#define OV5693_EXPOSURE_CTRL_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_EXPOSURE_GAIN_MANUAL_REG 0x3509 -+ -+#define OV5693_GAIN_CTRL_H_REG 0x3504 -+#define OV5693_GAIN_CTRL_H(v) (((v) & GENMASK(9, 8)) >> 8) -+#define OV5693_GAIN_CTRL_L_REG 0x3505 -+#define OV5693_GAIN_CTRL_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_FORMAT1_REG 0x3820 -+#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -+#define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) -+#define OV5693_FORMAT2_REG 0x3821 -+#define OV5693_FORMAT2_HSYNC_EN BIT(6) -+#define OV5693_FORMAT2_FST_VBIN_EN BIT(5) -+#define OV5693_FORMAT2_FST_HBIN_EN BIT(4) -+#define OV5693_FORMAT2_ISP_HORZ_VAR2_EN BIT(3) -+#define OV5693_FORMAT2_FLIP_HORZ_ISP_EN BIT(2) -+#define OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN BIT(1) -+#define OV5693_FORMAT2_SYNC_HBIN_EN BIT(0) -+ -+/* ISP */ -+ -+#define OV5693_ISP_CTRL0_REG 0x5000 -+#define OV5693_ISP_CTRL0_LENC_EN BIT(7) -+#define OV5693_ISP_CTRL0_WHITE_BALANCE_EN BIT(4) -+#define OV5693_ISP_CTRL0_DPC_BLACK_EN BIT(2) -+#define OV5693_ISP_CTRL0_DPC_WHITE_EN BIT(1) -+#define OV5693_ISP_CTRL1_REG 0x5001 -+#define OV5693_ISP_CTRL1_BLC_EN BIT(0) -+ -+/* native and active pixel array size. */ -+#define OV5693_NATIVE_WIDTH 2688U -+#define OV5693_NATIVE_HEIGHT 1984U -+#define OV5693_PIXEL_ARRAY_LEFT 48U -+#define OV5693_PIXEL_ARRAY_TOP 20U -+#define OV5693_PIXEL_ARRAY_WIDTH 2592U -+#define OV5693_PIXEL_ARRAY_HEIGHT 1944U -+ -+#define OV5693_PPL_DEFAULT 2800 -+ -+static int vcm_ad_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) -+{ -+ int err; -+ struct i2c_msg msg; -+ u8 buf[2]; -+ -+ buf[0] = reg; -+ buf[1] = val; -+ -+ msg.addr = VCM_ADDR; -+ msg.flags = 0; -+ msg.len = 2; -+ msg.buf = &buf[0]; -+ -+ err = i2c_transfer(client->adapter, &msg, 1); -+ if (err != 1) { -+ dev_err(&client->dev, "%s: vcm i2c fail, err code = %d\n", -+ __func__, err); -+ return -EIO; -+ } -+ return 0; -+} -+ -+static int ad5823_i2c_write(struct i2c_client *client, u8 reg, u8 val) -+{ -+ struct i2c_msg msg; -+ u8 buf[2]; -+ -+ buf[0] = reg; -+ buf[1] = val; -+ msg.addr = AD5823_VCM_ADDR; -+ msg.flags = 0; -+ msg.len = 0x02; -+ msg.buf = &buf[0]; -+ -+ if (i2c_transfer(client->adapter, &msg, 1) != 1) -+ return -EIO; -+ return 0; -+} -+ -+static int ad5823_i2c_read(struct i2c_client *client, u8 reg, u8 *val) -+{ -+ struct i2c_msg msg[2]; -+ u8 buf[2]; -+ -+ buf[0] = reg; -+ buf[1] = 0; -+ -+ msg[0].addr = AD5823_VCM_ADDR; -+ msg[0].flags = 0; -+ msg[0].len = 0x01; -+ msg[0].buf = &buf[0]; -+ -+ msg[1].addr = 0x0c; -+ msg[1].flags = I2C_M_RD; -+ msg[1].len = 0x01; -+ msg[1].buf = &buf[1]; -+ *val = 0; -+ if (i2c_transfer(client->adapter, msg, 2) != 2) -+ return -EIO; -+ *val = buf[1]; -+ return 0; -+} -+ -+static const u32 ov5693_embedded_effective_size = 28; -+ -+/* i2c read/write stuff */ -+static int ov5693_read_reg(struct i2c_client *client, -+ u16 data_length, u16 reg, u16 *val) -+{ -+ int err; -+ struct i2c_msg msg[2]; -+ unsigned char data[6]; -+ -+ if (!client->adapter) { -+ dev_err(&client->dev, "%s error, no client->adapter\n", -+ __func__); -+ return -ENODEV; -+ } -+ -+ if (data_length != OV5693_8BIT && data_length != OV5693_16BIT -+ && data_length != OV5693_32BIT) { -+ dev_err(&client->dev, "%s error, invalid data length\n", -+ __func__); -+ return -EINVAL; -+ } -+ -+ memset(msg, 0, sizeof(msg)); -+ -+ msg[0].addr = client->addr; -+ msg[0].flags = 0; -+ msg[0].len = I2C_MSG_LENGTH; -+ msg[0].buf = data; -+ -+ /* high byte goes out first */ -+ data[0] = (u8)(reg >> 8); -+ data[1] = (u8)(reg & 0xff); -+ -+ msg[1].addr = client->addr; -+ msg[1].len = data_length; -+ msg[1].flags = I2C_M_RD; -+ msg[1].buf = data; -+ -+ err = i2c_transfer(client->adapter, msg, 2); -+ if (err != 2) { -+ if (err >= 0) -+ err = -EIO; -+ dev_err(&client->dev, -+ "read from offset 0x%x error %d", reg, err); -+ return err; -+ } -+ -+ *val = 0; -+ /* high byte comes first */ -+ if (data_length == OV5693_8BIT) -+ *val = (u8)data[0]; -+ else if (data_length == OV5693_16BIT) -+ *val = be16_to_cpu(*(__be16 *)&data[0]); -+ else -+ *val = be32_to_cpu(*(__be32 *)&data[0]); -+ -+ return 0; -+} -+ -+static int ov5693_i2c_write(struct i2c_client *client, u16 len, u8 *data) -+{ -+ struct i2c_msg msg; -+ const int num_msg = 1; -+ int ret; -+ -+ msg.addr = client->addr; -+ msg.flags = 0; -+ msg.len = len; -+ msg.buf = data; -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ return ret == num_msg ? 0 : -EIO; -+} -+ -+static int vcm_dw_i2c_write(struct i2c_client *client, u16 data) -+{ -+ struct i2c_msg msg; -+ const int num_msg = 1; -+ int ret; -+ __be16 val; -+ -+ val = cpu_to_be16(data); -+ msg.addr = VCM_ADDR; -+ msg.flags = 0; -+ msg.len = OV5693_16BIT; -+ msg.buf = (void *)&val; -+ -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ return ret == num_msg ? 0 : -EIO; -+} -+ -+/* -+ * Theory: per datasheet, the two VCMs both allow for a 2-byte read. -+ * The DW9714 doesn't actually specify what this does (it has a -+ * two-byte write-only protocol, but specifies the read sequence as -+ * legal), but it returns the same data (zeroes) always, after an -+ * undocumented initial NAK. The AD5823 has a one-byte address -+ * register to which all writes go, and subsequent reads will cycle -+ * through the 8 bytes of registers. Notably, the default values (the -+ * device is always power-cycled affirmatively, so we can rely on -+ * these) in AD5823 are not pairwise repetitions of the same 16 bit -+ * word. So all we have to do is sequentially read two bytes at a -+ * time and see if we detect a difference in any of the first four -+ * pairs. -+ */ -+static int vcm_detect(struct i2c_client *client) -+{ -+ int i, ret; -+ struct i2c_msg msg; -+ u16 data0 = 0, data; -+ -+ for (i = 0; i < 4; i++) { -+ msg.addr = VCM_ADDR; -+ msg.flags = I2C_M_RD; -+ msg.len = sizeof(data); -+ msg.buf = (u8 *)&data; -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ /* -+ * DW9714 always fails the first read and returns -+ * zeroes for subsequent ones -+ */ -+ if (i == 0 && ret == -EREMOTEIO) { -+ data0 = 0; -+ continue; -+ } -+ -+ if (i == 0) -+ data0 = data; -+ -+ if (data != data0) -+ return VCM_AD5823; -+ } -+ return ret == 1 ? VCM_DW9714 : ret; -+} -+ -+static int ov5693_write_reg(struct i2c_client *client, u16 data_length, -+ u16 reg, u16 val) -+{ -+ int ret; -+ unsigned char data[4] = {0}; -+ __be16 *wreg = (void *)data; -+ const u16 len = data_length + sizeof(u16); /* 16-bit address + data */ -+ -+ if (data_length != OV5693_8BIT && data_length != OV5693_16BIT) { -+ dev_err(&client->dev, -+ "%s error, invalid data_length\n", __func__); -+ return -EINVAL; -+ } -+ -+ /* high byte goes out first */ -+ *wreg = cpu_to_be16(reg); -+ -+ if (data_length == OV5693_8BIT) { -+ data[2] = (u8)(val); -+ } else { -+ /* OV5693_16BIT */ -+ __be16 *wdata = (void *)&data[2]; -+ -+ *wdata = cpu_to_be16(val); -+ } -+ -+ ret = ov5693_i2c_write(client, len, data); -+ if (ret) -+ dev_err(&client->dev, -+ "write error: wrote 0x%x to offset 0x%x error %d", -+ val, reg, ret); -+ -+ return ret; -+} -+ -+/* -+ * ov5693_write_reg_array - Initializes a list of OV5693 registers -+ * @client: i2c driver client structure -+ * @reglist: list of registers to be written -+ * -+ * This function initializes a list of registers. When consecutive addresses -+ * are found in a row on the list, this function creates a buffer and sends -+ * consecutive data in a single i2c_transfer(). -+ * -+ * __ov5693_flush_reg_array, __ov5693_buf_reg_array() and -+ * __ov5693_write_reg_is_consecutive() are internal functions to -+ * ov5693_write_reg_array_fast() and should be not used anywhere else. -+ * -+ */ -+ -+static int __ov5693_flush_reg_array(struct i2c_client *client, -+ struct ov5693_write_ctrl *ctrl) -+{ -+ u16 size; -+ __be16 *reg = (void *)&ctrl->buffer.addr; -+ -+ if (ctrl->index == 0) -+ return 0; -+ -+ size = sizeof(u16) + ctrl->index; /* 16-bit address + data */ -+ -+ *reg = cpu_to_be16(ctrl->buffer.addr); -+ ctrl->index = 0; -+ -+ return ov5693_i2c_write(client, size, (u8 *)reg); -+} -+ -+static int __ov5693_buf_reg_array(struct i2c_client *client, -+ struct ov5693_write_ctrl *ctrl, -+ const struct ov5693_reg *next) -+{ -+ int size; -+ __be16 *data16; -+ -+ switch (next->type) { -+ case OV5693_8BIT: -+ size = 1; -+ ctrl->buffer.data[ctrl->index] = (u8)next->val; -+ break; -+ case OV5693_16BIT: -+ size = 2; -+ -+ data16 = (void *)&ctrl->buffer.data[ctrl->index]; -+ *data16 = cpu_to_be16((u16)next->val); -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ /* When first item is added, we need to store its starting address */ -+ if (ctrl->index == 0) -+ ctrl->buffer.addr = next->reg; -+ -+ ctrl->index += size; -+ -+ /* -+ * Buffer cannot guarantee free space for u32? Better flush it to avoid -+ * possible lack of memory for next item. -+ */ -+ if (ctrl->index + sizeof(u16) >= OV5693_MAX_WRITE_BUF_SIZE) -+ return __ov5693_flush_reg_array(client, ctrl); -+ -+ return 0; -+} -+ -+static int __ov5693_write_reg_is_consecutive(struct i2c_client *client, -+ struct ov5693_write_ctrl *ctrl, -+ const struct ov5693_reg *next) -+{ -+ if (ctrl->index == 0) -+ return 1; -+ -+ return ctrl->buffer.addr + ctrl->index == next->reg; -+} -+ -+static int ov5693_write_reg_array(struct i2c_client *client, -+ const struct ov5693_reg *reglist) -+{ -+ const struct ov5693_reg *next = reglist; -+ struct ov5693_write_ctrl ctrl; -+ int err; -+ -+ ctrl.index = 0; -+ for (; next->type != OV5693_TOK_TERM; next++) { -+ switch (next->type & OV5693_TOK_MASK) { -+ case OV5693_TOK_DELAY: -+ err = __ov5693_flush_reg_array(client, &ctrl); -+ if (err) -+ return err; -+ msleep(next->val); -+ break; -+ default: -+ /* -+ * If next address is not consecutive, data needs to be -+ * flushed before proceed. -+ */ -+ if (!__ov5693_write_reg_is_consecutive(client, &ctrl, -+ next)) { -+ err = __ov5693_flush_reg_array(client, &ctrl); -+ if (err) -+ return err; -+ } -+ err = __ov5693_buf_reg_array(client, &ctrl, next); -+ if (err) { -+ dev_err(&client->dev, -+ "%s: write error, aborted\n", -+ __func__); -+ return err; -+ } -+ break; -+ } -+ } -+ -+ return __ov5693_flush_reg_array(client, &ctrl); -+} -+ -+static int ov5693_read_otp_reg_array(struct i2c_client *client, u16 size, -+ u16 addr, u8 *buf) -+{ -+ u16 index; -+ int ret; -+ u16 *pVal = NULL; -+ -+ for (index = 0; index <= size; index++) { -+ pVal = (u16 *)(buf + index); -+ ret = -+ ov5693_read_reg(client, OV5693_8BIT, addr + index, -+ pVal); -+ if (ret) -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static int __ov5693_otp_read(struct v4l2_subdev *sd, u8 *buf) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ int ret; -+ int i; -+ u8 *b = buf; -+ -+ dev->otp_size = 0; -+ for (i = 1; i < OV5693_OTP_BANK_MAX; i++) { -+ /*set bank NO and OTP read mode. */ -+ ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_OTP_BANK_REG, -+ (i | 0xc0)); //[7:6] 2'b11 [5:0] bank no -+ if (ret) { -+ dev_err(&client->dev, "failed to prepare OTP page\n"); -+ return ret; -+ } -+ //dev_dbg(&client->dev, "write 0x%x->0x%x\n",OV5693_OTP_BANK_REG,(i|0xc0)); -+ -+ /*enable read */ -+ ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_OTP_READ_REG, -+ OV5693_OTP_MODE_READ); // enable :1 -+ if (ret) { -+ dev_err(&client->dev, -+ "failed to set OTP reading mode page"); -+ return ret; -+ } -+ //dev_dbg(&client->dev, "write 0x%x->0x%x\n", -+ // OV5693_OTP_READ_REG,OV5693_OTP_MODE_READ); -+ -+ /* Reading the OTP data array */ -+ ret = ov5693_read_otp_reg_array(client, OV5693_OTP_BANK_SIZE, -+ OV5693_OTP_START_ADDR, -+ b); -+ if (ret) { -+ dev_err(&client->dev, "failed to read OTP data\n"); -+ return ret; -+ } -+ -+ //dev_dbg(&client->dev, -+ // "BANK[%2d] %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", -+ // i, *b, *(b+1), *(b+2), *(b+3), *(b+4), *(b+5), *(b+6), *(b+7), -+ // *(b+8), *(b+9), *(b+10), *(b+11), *(b+12), *(b+13), *(b+14), *(b+15)); -+ -+ //Intel OTP map, try to read 320byts first. -+ if (i == 21) { -+ if ((*b) == 0) { -+ dev->otp_size = 320; -+ break; -+ } -+ /* (*b) != 0 */ -+ b = buf; -+ continue; -+ } else if (i == -+ 24) { //if the first 320bytes data doesn't not exist, try to read the next 32bytes data. -+ if ((*b) == 0) { -+ dev->otp_size = 32; -+ break; -+ } -+ /* (*b) != 0 */ -+ b = buf; -+ continue; -+ } else if (i == -+ 27) { //if the prvious 32bytes data doesn't exist, try to read the next 32bytes data again. -+ if ((*b) == 0) { -+ dev->otp_size = 32; -+ break; -+ } -+ /* (*b) != 0 */ -+ dev->otp_size = 0; // no OTP data. -+ break; -+ } -+ -+ b = b + OV5693_OTP_BANK_SIZE; -+ } -+ return 0; -+} -+ -+/* -+ * Read otp data and store it into a kmalloced buffer. -+ * The caller must kfree the buffer when no more needed. -+ * @size: set to the size of the returned otp data. -+ */ -+static void *ov5693_otp_read(struct v4l2_subdev *sd) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ u8 *buf; -+ int ret; -+ -+ buf = devm_kzalloc(&client->dev, (OV5693_OTP_DATA_SIZE + 16), GFP_KERNEL); -+ if (!buf) -+ return ERR_PTR(-ENOMEM); -+ -+ //otp valid after mipi on and sw stream on -+ ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_FRAME_OFF_NUM, 0x00); -+ -+ ret = ov5693_write_reg(client, OV5693_8BIT, -+ OV5693_SW_STREAM, OV5693_START_STREAMING); -+ -+ ret = __ov5693_otp_read(sd, buf); -+ -+ //mipi off and sw stream off after otp read -+ ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_FRAME_OFF_NUM, 0x0f); -+ -+ ret = ov5693_write_reg(client, OV5693_8BIT, -+ OV5693_SW_STREAM, OV5693_STOP_STREAMING); -+ -+ /* Driver has failed to find valid data */ -+ if (ret) { -+ dev_err(&client->dev, "sensor found no valid OTP data\n"); -+ return ERR_PTR(ret); -+ } -+ -+ return buf; -+} -+ -+static int ov5693_update_bits(struct ov5693_device *sensor, u16 address, -+ u16 mask, u16 bits) -+{ -+ u16 value = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, address, &value); -+ if (ret) -+ return ret; -+ -+ value &= ~mask; -+ value |= bits; -+ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, address, value); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+/* Flip */ -+ -+static int ov5693_flip_vert_configure(struct ov5693_device *sensor, bool enable) -+{ -+ u8 bits = OV5693_FORMAT1_FLIP_VERT_ISP_EN | -+ OV5693_FORMAT1_FLIP_VERT_SENSOR_EN; -+ int ret; -+ -+ ret = ov5693_update_bits(sensor, OV5693_FORMAT1_REG, bits, -+ enable ? bits : 0); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int ov5693_flip_horz_configure(struct ov5693_device *sensor, bool enable) -+{ -+ u8 bits = OV5693_FORMAT2_FLIP_HORZ_ISP_EN | -+ OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN; -+ int ret; -+ -+ ret = ov5693_update_bits(sensor, OV5693_FORMAT2_REG, bits, -+ enable ? bits : 0); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+/* -+ * This returns the exposure time being used. This should only be used -+ * for filling in EXIF data, not for actual image processing. -+ */ -+static int ov5693_q_exposure(struct v4l2_subdev *sd, s32 *value) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ u16 reg_v, reg_v2; -+ int ret; -+ -+ /* get exposure */ -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_EXPOSURE_L, -+ ®_v); -+ if (ret) -+ goto err; -+ -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_EXPOSURE_M, -+ ®_v2); -+ if (ret) -+ goto err; -+ -+ reg_v += reg_v2 << 8; -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_EXPOSURE_H, -+ ®_v2); -+ if (ret) -+ goto err; -+ -+ *value = reg_v + (((u32)reg_v2 << 16)); -+err: -+ return ret; -+} -+ -+static int ad5823_t_focus_vcm(struct v4l2_subdev *sd, u16 val) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret = -EINVAL; -+ u8 vcm_code; -+ -+ ret = ad5823_i2c_read(client, AD5823_REG_VCM_CODE_MSB, &vcm_code); -+ if (ret) -+ return ret; -+ -+ /* set reg VCM_CODE_MSB Bit[1:0] */ -+ vcm_code = (vcm_code & VCM_CODE_MSB_MASK) | -+ ((val >> 8) & ~VCM_CODE_MSB_MASK); -+ ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_MSB, vcm_code); -+ if (ret) -+ return ret; -+ -+ /* set reg VCM_CODE_LSB Bit[7:0] */ -+ ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_LSB, (val & 0xff)); -+ if (ret) -+ return ret; -+ -+ /* set required vcm move time */ -+ vcm_code = AD5823_RESONANCE_PERIOD / AD5823_RESONANCE_COEF -+ - AD5823_HIGH_FREQ_RANGE; -+ ret = ad5823_i2c_write(client, AD5823_REG_VCM_MOVE_TIME, vcm_code); -+ -+ return ret; -+} -+ -+static int ad5823_t_focus_abs(struct v4l2_subdev *sd, s32 value) -+{ -+ value = min(value, AD5823_MAX_FOCUS_POS); -+ return ad5823_t_focus_vcm(sd, value); -+} -+ -+static int ov5693_t_focus_abs(struct v4l2_subdev *sd, s32 value) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret = 0; -+ -+ dev_dbg(&client->dev, "%s: FOCUS_POS: 0x%x\n", __func__, value); -+ value = clamp(value, 0, OV5693_VCM_MAX_FOCUS_POS); -+ if (dev->vcm == VCM_DW9714) { -+ if (dev->vcm_update) { -+ ret = vcm_dw_i2c_write(client, VCM_PROTECTION_OFF); -+ if (ret) -+ return ret; -+ ret = vcm_dw_i2c_write(client, DIRECT_VCM); -+ if (ret) -+ return ret; -+ ret = vcm_dw_i2c_write(client, VCM_PROTECTION_ON); -+ if (ret) -+ return ret; -+ dev->vcm_update = false; -+ } -+ ret = vcm_dw_i2c_write(client, -+ vcm_val(value, VCM_DEFAULT_S)); -+ } else if (dev->vcm == VCM_AD5823) { -+ ad5823_t_focus_abs(sd, value); -+ } -+ if (ret == 0) { -+ dev->number_of_steps = value - dev->focus; -+ dev->focus = value; -+ dev->timestamp_t_focus_abs = ktime_get(); -+ } else -+ dev_err(&client->dev, -+ "%s: i2c failed. ret %d\n", __func__, ret); -+ -+ return ret; -+} -+ -+static int ov5693_t_focus_rel(struct v4l2_subdev *sd, s32 value) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ -+ return ov5693_t_focus_abs(sd, dev->focus + value); -+} -+ -+#define DELAY_PER_STEP_NS 1000000 -+#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) -+ -+/* Exposure */ -+ -+static int ov5693_get_exposure(struct ov5693_device *sensor) -+{ -+ u16 reg_v, reg_v2; -+ int ret = 0; -+ -+ /* get exposure */ -+ ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_EXPOSURE_L, -+ ®_v); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_EXPOSURE_M, -+ ®_v2); -+ if (ret) -+ return ret; -+ -+ reg_v += reg_v2 << 8; -+ ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_EXPOSURE_H, -+ ®_v2); -+ if (ret) -+ return ret; -+ -+ printk("exposure set to: %u\n", reg_v + (((u32)reg_v2 << 16))); -+ return ret; -+} -+ -+static int ov5693_exposure_configure(struct ov5693_device *sensor, u32 exposure) -+{ -+ int ret; -+ -+ ov5693_get_exposure(sensor); -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_EXPOSURE_CTRL_HH_REG, OV5693_EXPOSURE_CTRL_HH(exposure)); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_EXPOSURE_CTRL_H_REG, OV5693_EXPOSURE_CTRL_H(exposure)); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_EXPOSURE_CTRL_L_REG, OV5693_EXPOSURE_CTRL_L(exposure)); -+ if (ret) -+ return ret; -+ ov5693_get_exposure(sensor); -+ -+ return 0; -+} -+ -+/* Gain */ -+ -+static int ov5693_get_gain(struct ov5693_device *sensor, u32 *gain) -+{ -+ u16 gain_l, gain_h; -+ int ret = 0; -+ -+ ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_GAIN_CTRL_L_REG, -+ &gain_l); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_GAIN_CTRL_H_REG, -+ &gain_h); -+ if (ret) -+ return ret; -+ -+ *gain = (u32)(((gain_h >> 8) & 0x03) | -+ (gain_l & 0xff)); -+ -+ return ret; -+} -+static int ov5693_gain_configure(struct ov5693_device *sensor, u32 gain) -+{ -+ int ret; -+ -+ /* A 1.0 gain is 0x400 */ -+ gain = (gain * 1024)/1000; -+ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_16BIT, -+ OV5693_MWB_RED_GAIN_H, gain); -+ if (ret) { -+ dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ __func__, OV5693_MWB_RED_GAIN_H); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_16BIT, -+ OV5693_MWB_GREEN_GAIN_H, gain); -+ if (ret) { -+ dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ __func__, OV5693_MWB_RED_GAIN_H); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_16BIT, -+ OV5693_MWB_BLUE_GAIN_H, gain); -+ if (ret) { -+ dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ __func__, OV5693_MWB_RED_GAIN_H); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static int ov5693_analog_gain_configure(struct ov5693_device *sensor, u32 gain) -+{ -+ int ret; -+ -+ /* Analog gain */ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_AGC_L, gain & 0xff); -+ if (ret) { -+ dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ __func__, OV5693_AGC_L); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_AGC_H, (gain >> 8) & 0xff); -+ if (ret) { -+ dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ __func__, OV5693_AGC_H); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *dev = -+ container_of(ctrl->handler, struct ov5693_device, ctrl_handler); -+ struct i2c_client *client = v4l2_get_subdevdata(&dev->sd); -+ int ret = 0; -+ -+ switch (ctrl->id) { -+ case V4L2_CID_FOCUS_ABSOLUTE: -+ dev_dbg(&client->dev, "%s: CID_FOCUS_ABSOLUTE:%d.\n", -+ __func__, ctrl->val); -+ ret = ov5693_t_focus_abs(&dev->sd, ctrl->val); -+ break; -+ case V4L2_CID_FOCUS_RELATIVE: -+ dev_dbg(&client->dev, "%s: CID_FOCUS_RELATIVE:%d.\n", -+ __func__, ctrl->val); -+ ret = ov5693_t_focus_rel(&dev->sd, ctrl->val); -+ break; -+ case V4L2_CID_EXPOSURE: -+ dev_dbg(&client->dev, "%s: CID_EXPOSURE:%d.\n", -+ __func__, ctrl->val); -+ ret = ov5693_exposure_configure(dev, ctrl->val); -+ if (ret) -+ return ret; -+ break; -+ case V4L2_CID_ANALOGUE_GAIN: -+ dev_dbg(&client->dev, "%s: CID_ANALOGUE_GAIN:%d.\n", -+ __func__, ctrl->val); -+ ret = ov5693_analog_gain_configure(dev, ctrl->val); -+ if (ret) -+ return ret; -+ break; -+ case V4L2_CID_DIGITAL_GAIN: -+ dev_dbg(&client->dev, "%s: CID_DIGITAL_GAIN:%d.\n", -+ __func__, ctrl->val); -+ ret = ov5693_gain_configure(dev, ctrl->val); -+ if (ret) -+ return ret; -+ break; -+ case V4L2_CID_HFLIP: -+ return ov5693_flip_horz_configure(dev, !!ctrl->val); -+ case V4L2_CID_VFLIP: -+ return ov5693_flip_vert_configure(dev, !!ctrl->val); -+ default: -+ ret = -EINVAL; -+ } -+ return ret; -+} -+ -+static int ov5693_g_volatile_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *dev = -+ container_of(ctrl->handler, struct ov5693_device, ctrl_handler); -+ int ret = 0; -+ -+ switch (ctrl->id) { -+ case V4L2_CID_EXPOSURE_ABSOLUTE: -+ ret = ov5693_q_exposure(&dev->sd, &ctrl->val); -+ break; -+ case V4L2_CID_AUTOGAIN: -+ ret = ov5693_get_gain(dev, &ctrl->val); -+ break; -+ case V4L2_CID_FOCUS_ABSOLUTE: -+ /* NOTE: there was atomisp-specific function ov5693_q_focus_abs() */ -+ break; -+ default: -+ ret = -EINVAL; -+ } -+ -+ return ret; -+} -+ -+static const struct v4l2_ctrl_ops ov5693_ctrl_ops = { -+ .s_ctrl = ov5693_s_ctrl, -+ .g_volatile_ctrl = ov5693_g_volatile_ctrl -+}; -+ -+static const struct v4l2_ctrl_config ov5693_controls[] = { -+ { -+ .ops = &ov5693_ctrl_ops, -+ .id = V4L2_CID_FOCUS_ABSOLUTE, -+ .type = V4L2_CTRL_TYPE_INTEGER, -+ .name = "focus move absolute", -+ .min = 0, -+ .max = OV5693_VCM_MAX_FOCUS_POS, -+ .step = 1, -+ .def = 0, -+ .flags = 0, -+ }, -+ { -+ .ops = &ov5693_ctrl_ops, -+ .id = V4L2_CID_FOCUS_RELATIVE, -+ .type = V4L2_CTRL_TYPE_INTEGER, -+ .name = "focus move relative", -+ .min = OV5693_VCM_MAX_FOCUS_NEG, -+ .max = OV5693_VCM_MAX_FOCUS_POS, -+ .step = 1, -+ .def = 0, -+ .flags = 0, -+ }, -+}; -+ -+static int ov5693_isp_configure(struct ov5693_device *sensor) -+{ -+ int ret; -+ -+ /* Enable lens correction. */ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_ISP_CTRL0_REG, 0x86); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int ov5693_init(struct v4l2_subdev *sd) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret; -+ -+ if (!dev->has_vcm) -+ return 0; -+ -+ dev_info(&client->dev, "%s\n", __func__); -+ mutex_lock(&dev->input_lock); -+ dev->vcm_update = false; -+ -+ if (dev->vcm == VCM_AD5823) { -+ ret = vcm_ad_i2c_wr8(client, 0x01, 0x01); /* vcm init test */ -+ if (ret) -+ dev_err(&client->dev, -+ "vcm reset failed\n"); -+ /*change the mode*/ -+ ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_MSB, -+ AD5823_RING_CTRL_ENABLE); -+ if (ret) -+ dev_err(&client->dev, -+ "vcm enable ringing failed\n"); -+ ret = ad5823_i2c_write(client, AD5823_REG_MODE, -+ AD5823_ARC_RES1); -+ if (ret) -+ dev_err(&client->dev, -+ "vcm change mode failed\n"); -+ } -+ -+ /*change initial focus value for ad5823*/ -+ if (dev->vcm == VCM_AD5823) { -+ dev->focus = AD5823_INIT_FOCUS_POS; -+ ov5693_t_focus_abs(sd, AD5823_INIT_FOCUS_POS); -+ } else { -+ dev->focus = 0; -+ ov5693_t_focus_abs(sd, 0); -+ } -+ -+ ov5693_isp_configure(dev); -+ mutex_unlock(&dev->input_lock); -+ -+ return 0; -+} -+ -+static int __power_up(struct v4l2_subdev *sd) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ struct ov5693_device *sensor = to_ov5693_sensor(sd); -+ int ret; -+ -+ ret = clk_prepare_enable(sensor->clk); -+ if (ret) { -+ dev_err(&client->dev, "Error enabling clock\n"); -+ return -EINVAL; -+ } -+ -+ if (sensor->indicator_led) -+ gpiod_set_value_cansleep(sensor->indicator_led, 1); -+ -+ ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, -+ sensor->supplies); -+ if (ret) -+ goto fail_power; -+ -+ __cci_delay(up_delay); -+ -+ return 0; -+ -+fail_power: -+ if (sensor->indicator_led) -+ gpiod_set_value_cansleep(sensor->indicator_led, 0); -+ dev_err(&client->dev, "sensor power-up failed\n"); -+ -+ return ret; -+} -+ -+static int power_down(struct v4l2_subdev *sd) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ -+ dev->focus = OV5693_INVALID_CONFIG; -+ -+ clk_disable_unprepare(dev->clk); -+ -+ if (dev->indicator_led) -+ gpiod_set_value_cansleep(dev->indicator_led, 0); -+ return regulator_bulk_disable(OV5693_NUM_SUPPLIES, dev->supplies); -+} -+ -+static int power_up(struct v4l2_subdev *sd) -+{ -+ static const int retry_count = 4; -+ int i, ret; -+ -+ for (i = 0; i < retry_count; i++) { -+ ret = __power_up(sd); -+ if (!ret) -+ return 0; -+ -+ power_down(sd); -+ } -+ return ret; -+} -+ -+static int ov5693_s_power(struct v4l2_subdev *sd, int on) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret; -+ -+ dev_info(&client->dev, "%s: on %d\n", __func__, on); -+ -+ if (on == 0) -+ return power_down(sd); -+ -+ /* on == 1 */ -+ ret = power_up(sd); -+ if (!ret) { -+ ret = ov5693_init(sd); -+ /* restore settings */ -+ ov5693_res = ov5693_res_video; -+ N_RES = N_RES_VIDEO; -+ } -+ -+ return ret; -+} -+ -+/* -+ * distance - calculate the distance -+ * @res: resolution -+ * @w: width -+ * @h: height -+ * -+ * Get the gap between res_w/res_h and w/h. -+ * distance = (res_w/res_h - w/h) / (w/h) * 8192 -+ * res->width/height smaller than w/h wouldn't be considered. -+ * The gap of ratio larger than 1/8 wouldn't be considered. -+ * Returns the value of gap or -1 if fail. -+ */ -+#define LARGEST_ALLOWED_RATIO_MISMATCH 1024 -+static int distance(struct ov5693_resolution *res, u32 w, u32 h) -+{ -+ int ratio; -+ int distance; -+ -+ if (w == 0 || h == 0 || -+ res->width < w || res->height < h) -+ return -1; -+ -+ ratio = res->width << 13; -+ ratio /= w; -+ ratio *= h; -+ ratio /= res->height; -+ -+ distance = abs(ratio - 8192); -+ -+ if (distance > LARGEST_ALLOWED_RATIO_MISMATCH) -+ return -1; -+ -+ return distance; -+} -+ -+/* Return the nearest higher resolution index -+ * Firstly try to find the approximate aspect ratio resolution -+ * If we find multiple same AR resolutions, choose the -+ * minimal size. -+ */ -+static int nearest_resolution_index(int w, int h) -+{ -+ int i; -+ int idx = -1; -+ int dist; -+ int min_dist = INT_MAX; -+ int min_res_w = INT_MAX; -+ struct ov5693_resolution *tmp_res = NULL; -+ -+ for (i = 0; i < N_RES; i++) { -+ tmp_res = &ov5693_res[i]; -+ dist = distance(tmp_res, w, h); -+ if (dist == -1) -+ continue; -+ if (dist < min_dist) { -+ min_dist = dist; -+ idx = i; -+ min_res_w = ov5693_res[i].width; -+ continue; -+ } -+ if (dist == min_dist && ov5693_res[i].width < min_res_w) -+ idx = i; -+ } -+ -+ return idx; -+} -+ -+static int get_resolution_index(int w, int h) -+{ -+ int i; -+ -+ for (i = 0; i < N_RES; i++) { -+ if (w != ov5693_res[i].width) -+ continue; -+ if (h != ov5693_res[i].height) -+ continue; -+ -+ return i; -+ } -+ -+ return -1; -+} -+ -+/* TODO: remove it. */ -+static int startup(struct v4l2_subdev *sd) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret = 0; -+ -+ ret = ov5693_write_reg(client, OV5693_8BIT, -+ OV5693_SW_RESET, 0x01); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 reset err.\n"); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg_array(client, ov5693_global_setting); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 write register err.\n"); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg_array(client, ov5693_res[dev->fmt_idx].regs); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 write register err.\n"); -+ return ret; -+ } -+ -+ return ret; -+} -+ -+static int ov5693_set_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_format *format) -+{ -+ struct v4l2_mbus_framefmt *fmt = &format->format; -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret = 0; -+ int idx; -+ int cnt; -+ -+ if (format->pad) -+ return -EINVAL; -+ if (!fmt) -+ return -EINVAL; -+ -+ mutex_lock(&dev->input_lock); -+ idx = nearest_resolution_index(fmt->width, fmt->height); -+ if (idx == -1) { -+ /* return the largest resolution */ -+ fmt->width = ov5693_res[N_RES - 1].width; -+ fmt->height = ov5693_res[N_RES - 1].height; -+ } else { -+ fmt->width = ov5693_res[idx].width; -+ fmt->height = ov5693_res[idx].height; -+ } -+ -+ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) { -+ cfg->try_fmt = *fmt; -+ ret = 0; -+ goto mutex_unlock; -+ } -+ -+ dev->fmt_idx = get_resolution_index(fmt->width, fmt->height); -+ if (dev->fmt_idx == -1) { -+ dev_err(&client->dev, "get resolution fail\n"); -+ ret = -EINVAL; -+ goto mutex_unlock; -+ } -+ -+ for (cnt = 0; cnt < OV5693_POWER_UP_RETRY_NUM; cnt++) { -+ ret = power_up(sd); -+ if (ret) { -+ dev_err(&client->dev, "power up failed\n"); -+ continue; -+ } -+ -+ mutex_unlock(&dev->input_lock); -+ ov5693_init(sd); -+ mutex_lock(&dev->input_lock); -+ ret = startup(sd); -+ if (ret) -+ dev_err(&client->dev, " startup() FAILED!\n"); -+ else -+ break; -+ } -+ if (cnt == OV5693_POWER_UP_RETRY_NUM) { -+ dev_err(&client->dev, "power up failed, gave up\n"); -+ goto mutex_unlock; -+ } -+ -+ -+ -+ /* -+ * After sensor settings are set to HW, sometimes stream is started. -+ * This would cause ISP timeout because ISP is not ready to receive -+ * data yet. So add stop streaming here. -+ */ -+ ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_SW_STREAM, -+ OV5693_STOP_STREAMING); -+ if (ret) -+ dev_warn(&client->dev, "ov5693 stream off err\n"); -+ -+mutex_unlock: -+ mutex_unlock(&dev->input_lock); -+ return ret; -+} -+ -+static const struct v4l2_rect * -+__ov5693_get_pad_crop(struct ov5693_device *dev, struct v4l2_subdev_pad_config *cfg, -+ unsigned int pad, enum v4l2_subdev_format_whence which) -+{ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ return v4l2_subdev_get_try_crop(&dev->sd, cfg, pad); -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ return &dev->mode->crop; -+ } -+ -+ return NULL; -+} -+static int ov5693_get_selection(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_selection *sel) -+{ -+ switch (sel->target) { -+ case V4L2_SEL_TGT_CROP: { -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ -+ mutex_lock(&dev->input_lock); -+ sel->r = *__ov5693_get_pad_crop(dev, cfg, sel->pad, -+ sel->which); -+ mutex_unlock(&dev->input_lock); -+ -+ return 0; -+ } -+ -+ case V4L2_SEL_TGT_NATIVE_SIZE: -+ sel->r.top = 0; -+ sel->r.left = 0; -+ sel->r.width = OV5693_NATIVE_WIDTH; -+ sel->r.height = OV5693_NATIVE_HEIGHT; -+ -+ return 0; -+ -+ case V4L2_SEL_TGT_CROP_DEFAULT: -+ sel->r.top = OV5693_PIXEL_ARRAY_TOP; -+ sel->r.left = OV5693_PIXEL_ARRAY_LEFT; -+ sel->r.width = OV5693_PIXEL_ARRAY_WIDTH; -+ sel->r.height = OV5693_PIXEL_ARRAY_HEIGHT; -+ -+ return 0; -+ } -+ -+ return -EINVAL; -+} -+ -+static int ov5693_get_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_format *format) -+{ -+ struct v4l2_mbus_framefmt *fmt = &format->format; -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ -+ if (format->pad) -+ return -EINVAL; -+ -+ if (!fmt) -+ return -EINVAL; -+ -+ fmt->width = ov5693_res[dev->fmt_idx].width; -+ fmt->height = ov5693_res[dev->fmt_idx].height; -+ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ -+ return 0; -+} -+ -+static int ov5693_detect(struct i2c_client *client) -+{ -+ struct i2c_adapter *adapter = client->adapter; -+ u16 high, low; -+ int ret; -+ u16 id; -+ u8 revision; -+ -+ if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) -+ return -ENODEV; -+ -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_SC_CMMN_CHIP_ID_H, &high); -+ if (ret) { -+ dev_err(&client->dev, "sensor_id_high = 0x%x\n", high); -+ return -ENODEV; -+ } -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_SC_CMMN_CHIP_ID_L, &low); -+ id = ((((u16)high) << 8) | (u16)low); -+ -+ if (id != OV5693_ID) { -+ dev_err(&client->dev, "sensor ID error 0x%x\n", id); -+ return -ENODEV; -+ } -+ -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_SC_CMMN_SUB_ID, &high); -+ revision = (u8)high & 0x0f; -+ -+ dev_info(&client->dev, "sensor_revision = 0x%x\n", revision); -+ dev_info(&client->dev, "sensor_address = 0x%02x\n", client->addr); -+ dev_info(&client->dev, "detect ov5693 success\n"); -+ return 0; -+} -+ -+static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret; -+ -+ mutex_lock(&dev->input_lock); -+ -+ /* power_on() here before streaming for regular PCs. */ -+ if (enable) { -+ ret = power_up(sd); -+ if (ret) { -+ dev_err(&client->dev, "sensor power-up error\n"); -+ goto out; -+ } -+ } -+ -+ ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_SW_STREAM, -+ enable ? OV5693_START_STREAMING : -+ OV5693_STOP_STREAMING); -+ -+ /* power_off() here after streaming for regular PCs. */ -+ if (!enable) -+ power_down(sd); -+ -+out: -+ mutex_unlock(&dev->input_lock); -+ -+ return ret; -+} -+ -+static int ov5693_s_config(struct v4l2_subdev *sd, int irq) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret = 0; -+ -+ mutex_lock(&dev->input_lock); -+ ret = power_up(sd); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 power-up err.\n"); -+ goto fail_power_on; -+ } -+ -+ if (!dev->vcm) -+ dev->vcm = vcm_detect(client); -+ -+ /* config & detect sensor */ -+ ret = ov5693_detect(client); -+ if (ret) { -+ dev_err(&client->dev, "ov5693_detect err s_config.\n"); -+ goto fail_power_on; -+ } -+ -+ dev->otp_data = ov5693_otp_read(sd); -+ -+ /* turn off sensor, after probed */ -+ ret = power_down(sd); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 power-off err.\n"); -+ goto fail_power_on; -+ } -+ mutex_unlock(&dev->input_lock); -+ -+ return ret; -+ -+fail_power_on: -+ power_down(sd); -+ dev_err(&client->dev, "sensor power-gating failed\n"); -+ mutex_unlock(&dev->input_lock); -+ return ret; -+} -+ -+static int ov5693_g_frame_interval(struct v4l2_subdev *sd, -+ struct v4l2_subdev_frame_interval *interval) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ -+ interval->interval.numerator = 1; -+ interval->interval.denominator = ov5693_res[dev->fmt_idx].fps; -+ -+ return 0; -+} -+ -+static int ov5693_enum_mbus_code(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_mbus_code_enum *code) -+{ -+ if (code->index >= MAX_FMTS) -+ return -EINVAL; -+ -+ code->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ return 0; -+} -+ -+static int ov5693_enum_frame_size(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_frame_size_enum *fse) -+{ -+ int index = fse->index; -+ -+ if (index >= N_RES) -+ return -EINVAL; -+ -+ fse->min_width = ov5693_res[index].width; -+ fse->min_height = ov5693_res[index].height; -+ fse->max_width = ov5693_res[index].width; -+ fse->max_height = ov5693_res[index].height; -+ -+ return 0; -+} -+ -+static const struct v4l2_subdev_video_ops ov5693_video_ops = { -+ .s_stream = ov5693_s_stream, -+ .g_frame_interval = ov5693_g_frame_interval, -+}; -+ -+static const struct v4l2_subdev_core_ops ov5693_core_ops = { -+ .s_power = ov5693_s_power, -+}; -+ -+static const struct v4l2_subdev_pad_ops ov5693_pad_ops = { -+ .enum_mbus_code = ov5693_enum_mbus_code, -+ .enum_frame_size = ov5693_enum_frame_size, -+ .get_fmt = ov5693_get_fmt, -+ .set_fmt = ov5693_set_fmt, -+ .get_selection = ov5693_get_selection, -+}; -+ -+static const struct v4l2_subdev_ops ov5693_ops = { -+ .core = &ov5693_core_ops, -+ .video = &ov5693_video_ops, -+ .pad = &ov5693_pad_ops, -+}; -+ -+static int ov5693_remove(struct i2c_client *client) -+{ -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ unsigned int i = OV5693_NUM_SUPPLIES; -+ -+ dev_info(&client->dev, "%s...\n", __func__); -+ -+ gpiod_put(ov5693->reset); -+ gpiod_put(ov5693->indicator_led); -+ while (i--) -+ regulator_put(ov5693->supplies[i].consumer); -+ -+ v4l2_async_unregister_subdev(sd); -+ -+ media_entity_cleanup(&ov5693->sd.entity); -+ v4l2_ctrl_handler_free(&ov5693->ctrl_handler); -+ kfree(ov5693); -+ -+ return 0; -+} -+ -+static int ov5693_init_controls(struct ov5693_device *ov5693) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(&ov5693->sd); -+ const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; -+ struct v4l2_ctrl *ctrl; -+ unsigned int i; -+ int ret; -+ int hblank; -+ -+ ret = v4l2_ctrl_handler_init(&ov5693->ctrl_handler, -+ ARRAY_SIZE(ov5693_controls)); -+ if (ret) { -+ ov5693_remove(client); -+ return ret; -+ } -+ -+ for (i = 0; i < ARRAY_SIZE(ov5693_controls); i++) -+ v4l2_ctrl_new_custom(&ov5693->ctrl_handler, -+ &ov5693_controls[i], -+ NULL); -+ -+ /* link freq */ -+ ctrl = v4l2_ctrl_new_int_menu(&ov5693->ctrl_handler, NULL, -+ V4L2_CID_LINK_FREQ, -+ 0, 0, link_freq_menu_items); -+ if (ctrl) -+ ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ -+ /* pixel rate */ -+ v4l2_ctrl_new_std(&ov5693->ctrl_handler, NULL, V4L2_CID_PIXEL_RATE, -+ 0, OV5693_PIXEL_RATE, 1, OV5693_PIXEL_RATE); -+ -+ if (ov5693->ctrl_handler.error) { -+ ov5693_remove(client); -+ return ov5693->ctrl_handler.error; -+ } -+ -+ /* Exposure */ -+ -+ v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_EXPOSURE, 16, 1048575, 16, -+ 512); -+ -+ /* Gain */ -+ -+ v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_ANALOGUE_GAIN, 1, 1023, 1, 128); -+ v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_DIGITAL_GAIN, 1, 3999, 1, 1000); -+ -+ /* Flip */ -+ -+ v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_HFLIP, 0, 1, 1, 0); -+ v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_VFLIP, 0, 1, 1, 0); -+ -+ hblank = OV5693_PPL_DEFAULT - ov5693->mode->width; -+ ov5693->hblank = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -+ V4L2_CID_HBLANK, hblank, hblank, -+ 1, hblank); -+ if (ov5693->hblank) -+ ov5693->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ -+ /* Use same lock for controls as for everything else. */ -+ ov5693->ctrl_handler.lock = &ov5693->input_lock; -+ ov5693->sd.ctrl_handler = &ov5693->ctrl_handler; -+ -+ return 0; -+} -+ -+static int ov5693_configure_gpios(struct ov5693_device *ov5693) -+{ -+ ov5693->reset = gpiod_get_index(&ov5693->i2c_client->dev, "reset", 0, -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->reset)) { -+ dev_err(&ov5693->i2c_client->dev, "Couldn't find reset GPIO\n"); -+ return -EINVAL; -+ } -+ -+ ov5693->indicator_led = gpiod_get_index_optional(&ov5693->i2c_client->dev, "indicator-led", 0, -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->indicator_led)) { -+ dev_err(&ov5693->i2c_client->dev, "Couldn't find indicator-led GPIO\n"); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int ov5693_get_regulators(struct ov5693_device *ov5693) -+{ -+ unsigned int i; -+ -+ for (i = 0; i < OV5693_NUM_SUPPLIES; i++) -+ ov5693->supplies[i].supply = ov5693_supply_names[i]; -+ -+ return regulator_bulk_get(&ov5693->i2c_client->dev, -+ OV5693_NUM_SUPPLIES, -+ ov5693->supplies); -+} -+ -+static int ov5693_probe(struct i2c_client *client) -+{ -+ struct ov5693_device *ov5693; -+ int ret = 0; -+ -+ dev_info(&client->dev, "%s() called", __func__); -+ -+ ov5693 = kzalloc(sizeof(*ov5693), GFP_KERNEL); -+ if (!ov5693) -+ return -ENOMEM; -+ -+ ov5693->i2c_client = client; -+ -+ /* check if VCM device exists */ -+ /* TODO: read from SSDB */ -+ ov5693->has_vcm = false; -+ -+ mutex_init(&ov5693->input_lock); -+ -+ v4l2_i2c_subdev_init(&ov5693->sd, client, &ov5693_ops); -+ -+ ov5693->clk = devm_clk_get(&client->dev, "xvclk"); -+ if (IS_ERR(ov5693->clk)) { -+ dev_err(&client->dev, "Error getting clock\n"); -+ return -EINVAL; -+ } -+ -+ ret = ov5693_configure_gpios(ov5693); -+ if (ret) -+ goto out_free; -+ -+ ret = ov5693_get_regulators(ov5693); -+ if (ret) -+ goto out_put_reset; -+ -+ ret = ov5693_s_config(&ov5693->sd, client->irq); -+ if (ret) -+ goto out_put_reset; -+ -+ ov5693->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; -+ ov5693->pad.flags = MEDIA_PAD_FL_SOURCE; -+ ov5693->format.code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ ov5693->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; -+ ov5693->mode = &ov5693_res_video[N_RES_VIDEO-1]; -+ -+ ret = ov5693_init_controls(ov5693); -+ if (ret) -+ ov5693_remove(client); -+ -+ ret = media_entity_pads_init(&ov5693->sd.entity, 1, &ov5693->pad); -+ if (ret) -+ ov5693_remove(client); -+ -+ ret = v4l2_async_register_subdev_sensor_common(&ov5693->sd); -+ if (ret) { -+ dev_err(&client->dev, "failed to register V4L2 subdev: %d", ret); -+ goto media_entity_cleanup; -+ } -+ -+ return ret; -+ -+media_entity_cleanup: -+ media_entity_cleanup(&ov5693->sd.entity); -+out_put_reset: -+ gpiod_put(ov5693->reset); -+out_free: -+ v4l2_device_unregister_subdev(&ov5693->sd); -+ kfree(ov5693); -+ return ret; -+} -+ -+static const struct acpi_device_id ov5693_acpi_match[] = { -+ {"INT33BE"}, -+ {}, -+}; -+MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); -+ -+static struct i2c_driver ov5693_driver = { -+ .driver = { -+ .name = "ov5693", -+ .acpi_match_table = ov5693_acpi_match, -+ }, -+ .probe_new = ov5693_probe, -+ .remove = ov5693_remove, -+}; -+module_i2c_driver(ov5693_driver); -+ -+MODULE_DESCRIPTION("A low-level driver for OmniVision 5693 sensors"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -new file mode 100644 -index 000000000000..9a508e1f3624 ---- /dev/null -+++ b/drivers/media/i2c/ov5693.h -@@ -0,0 +1,1430 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* -+ * Support for OmniVision OV5693 5M camera sensor. -+ * -+ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. -+ * -+ * 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 __OV5693_H__ -+#define __OV5693_H__ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define OV5693_HID "INT33BE" -+ -+/* -+ * FIXME: non-preview resolutions are currently broken -+ */ -+#define ENABLE_NON_PREVIEW 1 -+ -+#define OV5693_POWER_UP_RETRY_NUM 5 -+ -+/* Defines for register writes and register array processing */ -+#define I2C_MSG_LENGTH 0x2 -+#define I2C_RETRY_COUNT 5 -+ -+#define OV5693_FOCAL_LENGTH_NUM 334 /*3.34mm*/ -+#define OV5693_FOCAL_LENGTH_DEM 100 -+#define OV5693_F_NUMBER_DEFAULT_NUM 24 -+#define OV5693_F_NUMBER_DEM 10 -+ -+#define MAX_FMTS 1 -+ -+/* sensor_mode_data read_mode adaptation */ -+#define OV5693_READ_MODE_BINNING_ON 0x0400 -+#define OV5693_READ_MODE_BINNING_OFF 0x00 -+#define OV5693_INTEGRATION_TIME_MARGIN 8 -+ -+#define OV5693_MAX_EXPOSURE_VALUE 0xFFF1 -+#define OV5693_MAX_GAIN_VALUE 0xFF -+ -+/* -+ * focal length bits definition: -+ * bits 31-16: numerator, bits 15-0: denominator -+ */ -+#define OV5693_FOCAL_LENGTH_DEFAULT 0x1B70064 -+ -+/* -+ * current f-number bits definition: -+ * bits 31-16: numerator, bits 15-0: denominator -+ */ -+#define OV5693_F_NUMBER_DEFAULT 0x18000a -+ -+/* -+ * f-number range bits definition: -+ * bits 31-24: max f-number numerator -+ * bits 23-16: max f-number denominator -+ * bits 15-8: min f-number numerator -+ * bits 7-0: min f-number denominator -+ */ -+#define OV5693_F_NUMBER_RANGE 0x180a180a -+#define OV5693_ID 0x5690 -+ -+#define OV5693_FINE_INTG_TIME_MIN 0 -+#define OV5693_FINE_INTG_TIME_MAX_MARGIN 0 -+#define OV5693_COARSE_INTG_TIME_MIN 1 -+#define OV5693_COARSE_INTG_TIME_MAX_MARGIN 6 -+ -+#define OV5693_BIN_FACTOR_MAX 4 -+/* -+ * OV5693 System control registers -+ */ -+#define OV5693_SW_SLEEP 0x0100 -+#define OV5693_SW_RESET 0x0103 -+#define OV5693_SW_STREAM 0x0100 -+ -+#define OV5693_SC_CMMN_CHIP_ID_H 0x300A -+#define OV5693_SC_CMMN_CHIP_ID_L 0x300B -+#define OV5693_SC_CMMN_SCCB_ID 0x300C -+#define OV5693_SC_CMMN_SUB_ID 0x302A /* process, version*/ -+/*Bit[7:4] Group control, Bit[3:0] Group ID*/ -+#define OV5693_GROUP_ACCESS 0x3208 -+/* -+*Bit[3:0] Bit[19:16] of exposure, -+*remaining 16 bits lies in Reg0x3501&Reg0x3502 -+*/ -+#define OV5693_EXPOSURE_H 0x3500 -+#define OV5693_EXPOSURE_M 0x3501 -+#define OV5693_EXPOSURE_L 0x3502 -+/*Bit[1:0] means Bit[9:8] of gain*/ -+#define OV5693_AGC_H 0x350A -+#define OV5693_AGC_L 0x350B /*Bit[7:0] of gain*/ -+ -+#define OV5693_HORIZONTAL_START_H 0x3800 /*Bit[11:8]*/ -+#define OV5693_HORIZONTAL_START_L 0x3801 /*Bit[7:0]*/ -+#define OV5693_VERTICAL_START_H 0x3802 /*Bit[11:8]*/ -+#define OV5693_VERTICAL_START_L 0x3803 /*Bit[7:0]*/ -+#define OV5693_HORIZONTAL_END_H 0x3804 /*Bit[11:8]*/ -+#define OV5693_HORIZONTAL_END_L 0x3805 /*Bit[7:0]*/ -+#define OV5693_VERTICAL_END_H 0x3806 /*Bit[11:8]*/ -+#define OV5693_VERTICAL_END_L 0x3807 /*Bit[7:0]*/ -+#define OV5693_HORIZONTAL_OUTPUT_SIZE_H 0x3808 /*Bit[3:0]*/ -+#define OV5693_HORIZONTAL_OUTPUT_SIZE_L 0x3809 /*Bit[7:0]*/ -+#define OV5693_VERTICAL_OUTPUT_SIZE_H 0x380a /*Bit[3:0]*/ -+#define OV5693_VERTICAL_OUTPUT_SIZE_L 0x380b /*Bit[7:0]*/ -+/*High 8-bit, and low 8-bit HTS address is 0x380d*/ -+#define OV5693_TIMING_HTS_H 0x380C -+/*High 8-bit, and low 8-bit HTS address is 0x380d*/ -+#define OV5693_TIMING_HTS_L 0x380D -+/*High 8-bit, and low 8-bit HTS address is 0x380f*/ -+#define OV5693_TIMING_VTS_H 0x380e -+/*High 8-bit, and low 8-bit HTS address is 0x380f*/ -+#define OV5693_TIMING_VTS_L 0x380f -+ -+#define OV5693_MWB_RED_GAIN_H 0x3400 -+#define OV5693_MWB_GREEN_GAIN_H 0x3402 -+#define OV5693_MWB_BLUE_GAIN_H 0x3404 -+#define OV5693_MWB_GAIN_MAX 0x0fff -+ -+#define OV5693_START_STREAMING 0x01 -+#define OV5693_STOP_STREAMING 0x00 -+ -+#define VCM_ADDR 0x0c -+#define VCM_CODE_MSB 0x04 -+ -+#define OV5693_INVALID_CONFIG 0xffffffff -+ -+#define OV5693_VCM_SLEW_STEP 0x30F0 -+#define OV5693_VCM_SLEW_STEP_MAX 0x7 -+#define OV5693_VCM_SLEW_STEP_MASK 0x7 -+#define OV5693_VCM_CODE 0x30F2 -+#define OV5693_VCM_SLEW_TIME 0x30F4 -+#define OV5693_VCM_SLEW_TIME_MAX 0xffff -+#define OV5693_VCM_ENABLE 0x8000 -+ -+#define OV5693_VCM_MAX_FOCUS_NEG -1023 -+#define OV5693_VCM_MAX_FOCUS_POS 1023 -+ -+#define DLC_ENABLE 1 -+#define DLC_DISABLE 0 -+#define VCM_PROTECTION_OFF 0xeca3 -+#define VCM_PROTECTION_ON 0xdc51 -+#define VCM_DEFAULT_S 0x0 -+#define vcm_step_s(a) (u8)(a & 0xf) -+#define vcm_step_mclk(a) (u8)((a >> 4) & 0x3) -+#define vcm_dlc_mclk(dlc, mclk) (u16)((dlc << 3) | mclk | 0xa104) -+#define vcm_tsrc(tsrc) (u16)(tsrc << 3 | 0xf200) -+#define vcm_val(data, s) (u16)(data << 4 | s) -+#define DIRECT_VCM vcm_dlc_mclk(0, 0) -+ -+/* Defines for OTP Data Registers */ -+#define OV5693_FRAME_OFF_NUM 0x4202 -+#define OV5693_OTP_BYTE_MAX 32 //change to 32 as needed by otpdata -+#define OV5693_OTP_SHORT_MAX 16 -+#define OV5693_OTP_START_ADDR 0x3D00 -+#define OV5693_OTP_END_ADDR 0x3D0F -+#define OV5693_OTP_DATA_SIZE 320 -+#define OV5693_OTP_PROGRAM_REG 0x3D80 -+#define OV5693_OTP_READ_REG 0x3D81 // 1:Enable 0:disable -+#define OV5693_OTP_BANK_REG 0x3D84 //otp bank and mode -+#define OV5693_OTP_READY_REG_DONE 1 -+#define OV5693_OTP_BANK_MAX 28 -+#define OV5693_OTP_BANK_SIZE 16 //16 bytes per bank -+#define OV5693_OTP_READ_ONETIME 16 -+#define OV5693_OTP_MODE_READ 1 -+ -+/* link freq and pixel rate required for IPU3 */ -+#define OV5693_LINK_FREQ_640MHZ 640000000 -+/* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample -+ * To avoid integer overflow, dividing by bits_per_sample first. -+ */ -+#define OV5693_PIXEL_RATE (OV5693_LINK_FREQ_640MHZ / 10) * 2 * 2 -+static const s64 link_freq_menu_items[] = { -+ OV5693_LINK_FREQ_640MHZ -+}; -+ -+#define OV5693_NUM_SUPPLIES 2 -+static const char * const ov5693_supply_names[] = { -+ "avdd", -+ "dovdd", -+}; -+ -+struct regval_list { -+ u16 reg_num; -+ u8 value; -+}; -+ -+struct ov5693_resolution { -+ u8 *desc; -+ const struct ov5693_reg *regs; -+ int res; -+ int width; -+ int height; -+ int fps; -+ int pix_clk_freq; -+ u16 pixels_per_line; -+ u16 lines_per_frame; -+ u8 bin_factor_x; -+ u8 bin_factor_y; -+ u8 bin_mode; -+ bool used; -+ -+ /* Analog crop rectangle. */ -+ struct v4l2_rect crop; -+}; -+ -+struct ov5693_format { -+ u8 *desc; -+ u32 pixelformat; -+ struct ov5693_reg *regs; -+}; -+ -+enum vcm_type { -+ VCM_UNKNOWN, -+ VCM_AD5823, -+ VCM_DW9714, -+}; -+ -+/* -+ * ov5693 device structure. -+ */ -+struct ov5693_device { -+ struct i2c_client *i2c_client; -+ struct v4l2_subdev sd; -+ struct media_pad pad; -+ struct v4l2_mbus_framefmt format; -+ struct mutex input_lock; -+ struct v4l2_ctrl_handler ctrl_handler; -+ -+ struct gpio_desc *reset; -+ struct gpio_desc *indicator_led; -+ struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; -+ struct clk *clk; -+ -+ /* Current mode */ -+ const struct ov5693_resolution *mode; -+ -+ struct camera_sensor_platform_data *platform_data; -+ ktime_t timestamp_t_focus_abs; -+ int vt_pix_clk_freq_mhz; -+ int fmt_idx; -+ int run_mode; -+ int otp_size; -+ u8 *otp_data; -+ u32 focus; -+ s16 number_of_steps; -+ u8 res; -+ u8 type; -+ bool vcm_update; -+ enum vcm_type vcm; -+ -+ bool has_vcm; -+ -+ struct v4l2_ctrl *hblank; -+}; -+ -+enum ov5693_tok_type { -+ OV5693_8BIT = 0x0001, -+ OV5693_16BIT = 0x0002, -+ OV5693_32BIT = 0x0004, -+ OV5693_TOK_TERM = 0xf000, /* terminating token for reg list */ -+ OV5693_TOK_DELAY = 0xfe00, /* delay token for reg list */ -+ OV5693_TOK_MASK = 0xfff0 -+}; -+ -+/** -+ * struct ov5693_reg - MI sensor register format -+ * @type: type of the register -+ * @reg: 16-bit offset to register -+ * @val: 8/16/32-bit register value -+ * -+ * Define a structure for sensor register initialization values -+ */ -+struct ov5693_reg { -+ enum ov5693_tok_type type; -+ u16 reg; -+ u32 val; /* @set value for read/mod/write, @mask */ -+}; -+ -+#define to_ov5693_sensor(x) container_of(x, struct ov5693_device, sd) -+ -+#define OV5693_MAX_WRITE_BUF_SIZE 30 -+ -+struct ov5693_write_buffer { -+ u16 addr; -+ u8 data[OV5693_MAX_WRITE_BUF_SIZE]; -+}; -+ -+struct ov5693_write_ctrl { -+ int index; -+ struct ov5693_write_buffer buffer; -+}; -+ -+static struct ov5693_reg const ov5693_global_setting[] = { -+ {OV5693_8BIT, 0x0103, 0x01}, -+ {OV5693_8BIT, 0x3001, 0x0a}, -+ {OV5693_8BIT, 0x3002, 0x80}, -+ {OV5693_8BIT, 0x3006, 0x00}, -+ {OV5693_8BIT, 0x3011, 0x21}, -+ {OV5693_8BIT, 0x3012, 0x09}, -+ {OV5693_8BIT, 0x3013, 0x10}, -+ {OV5693_8BIT, 0x3014, 0x00}, -+ {OV5693_8BIT, 0x3015, 0x08}, -+ {OV5693_8BIT, 0x3016, 0xf0}, -+ {OV5693_8BIT, 0x3017, 0xf0}, -+ {OV5693_8BIT, 0x3018, 0xf0}, -+ {OV5693_8BIT, 0x301b, 0xb4}, -+ {OV5693_8BIT, 0x301d, 0x02}, -+ {OV5693_8BIT, 0x3021, 0x00}, -+ {OV5693_8BIT, 0x3022, 0x01}, -+ {OV5693_8BIT, 0x3028, 0x44}, -+ {OV5693_8BIT, 0x3098, 0x02}, -+ {OV5693_8BIT, 0x3099, 0x19}, -+ {OV5693_8BIT, 0x309a, 0x02}, -+ {OV5693_8BIT, 0x309b, 0x01}, -+ {OV5693_8BIT, 0x309c, 0x00}, -+ {OV5693_8BIT, 0x30a0, 0xd2}, -+ {OV5693_8BIT, 0x30a2, 0x01}, -+ {OV5693_8BIT, 0x30b2, 0x00}, -+ {OV5693_8BIT, 0x30b3, 0x7d}, -+ {OV5693_8BIT, 0x30b4, 0x03}, -+ {OV5693_8BIT, 0x30b5, 0x04}, -+ {OV5693_8BIT, 0x30b6, 0x01}, -+ {OV5693_8BIT, 0x3104, 0x21}, -+ {OV5693_8BIT, 0x3106, 0x00}, -+ {OV5693_8BIT, 0x3400, 0x04}, -+ {OV5693_8BIT, 0x3401, 0x00}, -+ {OV5693_8BIT, 0x3402, 0x04}, -+ {OV5693_8BIT, 0x3403, 0x00}, -+ {OV5693_8BIT, 0x3404, 0x04}, -+ {OV5693_8BIT, 0x3405, 0x00}, -+ {OV5693_8BIT, 0x3406, 0x01}, -+ {OV5693_8BIT, 0x3500, 0x00}, -+ {OV5693_8BIT, 0x3503, 0x07}, -+ {OV5693_8BIT, 0x3504, 0x00}, -+ {OV5693_8BIT, 0x3505, 0x00}, -+ {OV5693_8BIT, 0x3506, 0x00}, -+ {OV5693_8BIT, 0x3507, 0x02}, -+ {OV5693_8BIT, 0x3508, 0x00}, -+ {OV5693_8BIT, 0x3509, 0x10}, -+ {OV5693_8BIT, 0x350a, 0x00}, -+ {OV5693_8BIT, 0x350b, 0x40}, -+ {OV5693_8BIT, 0x3601, 0x0a}, -+ {OV5693_8BIT, 0x3602, 0x38}, -+ {OV5693_8BIT, 0x3612, 0x80}, -+ {OV5693_8BIT, 0x3620, 0x54}, -+ {OV5693_8BIT, 0x3621, 0xc7}, -+ {OV5693_8BIT, 0x3622, 0x0f}, -+ {OV5693_8BIT, 0x3625, 0x10}, -+ {OV5693_8BIT, 0x3630, 0x55}, -+ {OV5693_8BIT, 0x3631, 0xf4}, -+ {OV5693_8BIT, 0x3632, 0x00}, -+ {OV5693_8BIT, 0x3633, 0x34}, -+ {OV5693_8BIT, 0x3634, 0x02}, -+ {OV5693_8BIT, 0x364d, 0x0d}, -+ {OV5693_8BIT, 0x364f, 0xdd}, -+ {OV5693_8BIT, 0x3660, 0x04}, -+ {OV5693_8BIT, 0x3662, 0x10}, -+ {OV5693_8BIT, 0x3663, 0xf1}, -+ {OV5693_8BIT, 0x3665, 0x00}, -+ {OV5693_8BIT, 0x3666, 0x20}, -+ {OV5693_8BIT, 0x3667, 0x00}, -+ {OV5693_8BIT, 0x366a, 0x80}, -+ {OV5693_8BIT, 0x3680, 0xe0}, -+ {OV5693_8BIT, 0x3681, 0x00}, -+ {OV5693_8BIT, 0x3700, 0x42}, -+ {OV5693_8BIT, 0x3701, 0x14}, -+ {OV5693_8BIT, 0x3702, 0xa0}, -+ {OV5693_8BIT, 0x3703, 0xd8}, -+ {OV5693_8BIT, 0x3704, 0x78}, -+ {OV5693_8BIT, 0x3705, 0x02}, -+ {OV5693_8BIT, 0x370a, 0x00}, -+ {OV5693_8BIT, 0x370b, 0x20}, -+ {OV5693_8BIT, 0x370c, 0x0c}, -+ {OV5693_8BIT, 0x370d, 0x11}, -+ {OV5693_8BIT, 0x370e, 0x00}, -+ {OV5693_8BIT, 0x370f, 0x40}, -+ {OV5693_8BIT, 0x3710, 0x00}, -+ {OV5693_8BIT, 0x371a, 0x1c}, -+ {OV5693_8BIT, 0x371b, 0x05}, -+ {OV5693_8BIT, 0x371c, 0x01}, -+ {OV5693_8BIT, 0x371e, 0xa1}, -+ {OV5693_8BIT, 0x371f, 0x0c}, -+ {OV5693_8BIT, 0x3721, 0x00}, -+ {OV5693_8BIT, 0x3724, 0x10}, -+ {OV5693_8BIT, 0x3726, 0x00}, -+ {OV5693_8BIT, 0x372a, 0x01}, -+ {OV5693_8BIT, 0x3730, 0x10}, -+ {OV5693_8BIT, 0x3738, 0x22}, -+ {OV5693_8BIT, 0x3739, 0xe5}, -+ {OV5693_8BIT, 0x373a, 0x50}, -+ {OV5693_8BIT, 0x373b, 0x02}, -+ {OV5693_8BIT, 0x373c, 0x41}, -+ {OV5693_8BIT, 0x373f, 0x02}, -+ {OV5693_8BIT, 0x3740, 0x42}, -+ {OV5693_8BIT, 0x3741, 0x02}, -+ {OV5693_8BIT, 0x3742, 0x18}, -+ {OV5693_8BIT, 0x3743, 0x01}, -+ {OV5693_8BIT, 0x3744, 0x02}, -+ {OV5693_8BIT, 0x3747, 0x10}, -+ {OV5693_8BIT, 0x374c, 0x04}, -+ {OV5693_8BIT, 0x3751, 0xf0}, -+ {OV5693_8BIT, 0x3752, 0x00}, -+ {OV5693_8BIT, 0x3753, 0x00}, -+ {OV5693_8BIT, 0x3754, 0xc0}, -+ {OV5693_8BIT, 0x3755, 0x00}, -+ {OV5693_8BIT, 0x3756, 0x1a}, -+ {OV5693_8BIT, 0x3758, 0x00}, -+ {OV5693_8BIT, 0x3759, 0x0f}, -+ {OV5693_8BIT, 0x376b, 0x44}, -+ {OV5693_8BIT, 0x375c, 0x04}, -+ {OV5693_8BIT, 0x3774, 0x10}, -+ {OV5693_8BIT, 0x3776, 0x00}, -+ {OV5693_8BIT, 0x377f, 0x08}, -+ {OV5693_8BIT, 0x3780, 0x22}, -+ {OV5693_8BIT, 0x3781, 0x0c}, -+ {OV5693_8BIT, 0x3784, 0x2c}, -+ {OV5693_8BIT, 0x3785, 0x1e}, -+ {OV5693_8BIT, 0x378f, 0xf5}, -+ {OV5693_8BIT, 0x3791, 0xb0}, -+ {OV5693_8BIT, 0x3795, 0x00}, -+ {OV5693_8BIT, 0x3796, 0x64}, -+ {OV5693_8BIT, 0x3797, 0x11}, -+ {OV5693_8BIT, 0x3798, 0x30}, -+ {OV5693_8BIT, 0x3799, 0x41}, -+ {OV5693_8BIT, 0x379a, 0x07}, -+ {OV5693_8BIT, 0x379b, 0xb0}, -+ {OV5693_8BIT, 0x379c, 0x0c}, -+ {OV5693_8BIT, 0x37c5, 0x00}, -+ {OV5693_8BIT, 0x37c6, 0x00}, -+ {OV5693_8BIT, 0x37c7, 0x00}, -+ {OV5693_8BIT, 0x37c9, 0x00}, -+ {OV5693_8BIT, 0x37ca, 0x00}, -+ {OV5693_8BIT, 0x37cb, 0x00}, -+ {OV5693_8BIT, 0x37de, 0x00}, -+ {OV5693_8BIT, 0x37df, 0x00}, -+ {OV5693_8BIT, 0x3800, 0x00}, -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, -+ {OV5693_8BIT, 0x3804, 0x0a}, -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3810, 0x00}, -+ {OV5693_8BIT, 0x3812, 0x00}, -+ {OV5693_8BIT, 0x3823, 0x00}, -+ {OV5693_8BIT, 0x3824, 0x00}, -+ {OV5693_8BIT, 0x3825, 0x00}, -+ {OV5693_8BIT, 0x3826, 0x00}, -+ {OV5693_8BIT, 0x3827, 0x00}, -+ {OV5693_8BIT, 0x382a, 0x04}, -+ {OV5693_8BIT, 0x3a04, 0x06}, -+ {OV5693_8BIT, 0x3a05, 0x14}, -+ {OV5693_8BIT, 0x3a06, 0x00}, -+ {OV5693_8BIT, 0x3a07, 0xfe}, -+ {OV5693_8BIT, 0x3b00, 0x00}, -+ {OV5693_8BIT, 0x3b02, 0x00}, -+ {OV5693_8BIT, 0x3b03, 0x00}, -+ {OV5693_8BIT, 0x3b04, 0x00}, -+ {OV5693_8BIT, 0x3b05, 0x00}, -+ {OV5693_8BIT, 0x3e07, 0x20}, -+ {OV5693_8BIT, 0x4000, 0x08}, -+ {OV5693_8BIT, 0x4001, 0x04}, -+ {OV5693_8BIT, 0x4002, 0x45}, -+ {OV5693_8BIT, 0x4004, 0x08}, -+ {OV5693_8BIT, 0x4005, 0x18}, -+ {OV5693_8BIT, 0x4006, 0x20}, -+ {OV5693_8BIT, 0x4008, 0x24}, -+ {OV5693_8BIT, 0x4009, 0x10}, -+ {OV5693_8BIT, 0x400c, 0x00}, -+ {OV5693_8BIT, 0x400d, 0x00}, -+ {OV5693_8BIT, 0x4058, 0x00}, -+ {OV5693_8BIT, 0x404e, 0x37}, -+ {OV5693_8BIT, 0x404f, 0x8f}, -+ {OV5693_8BIT, 0x4058, 0x00}, -+ {OV5693_8BIT, 0x4101, 0xb2}, -+ {OV5693_8BIT, 0x4303, 0x00}, -+ {OV5693_8BIT, 0x4304, 0x08}, -+ {OV5693_8BIT, 0x4307, 0x31}, -+ {OV5693_8BIT, 0x4311, 0x04}, -+ {OV5693_8BIT, 0x4315, 0x01}, -+ {OV5693_8BIT, 0x4511, 0x05}, -+ {OV5693_8BIT, 0x4512, 0x01}, -+ {OV5693_8BIT, 0x4806, 0x00}, -+ {OV5693_8BIT, 0x4816, 0x52}, -+ {OV5693_8BIT, 0x481f, 0x30}, -+ {OV5693_8BIT, 0x4826, 0x2c}, -+ {OV5693_8BIT, 0x4831, 0x64}, -+ {OV5693_8BIT, 0x4d00, 0x04}, -+ {OV5693_8BIT, 0x4d01, 0x71}, -+ {OV5693_8BIT, 0x4d02, 0xfd}, -+ {OV5693_8BIT, 0x4d03, 0xf5}, -+ {OV5693_8BIT, 0x4d04, 0x0c}, -+ {OV5693_8BIT, 0x4d05, 0xcc}, -+ {OV5693_8BIT, 0x4837, 0x0a}, -+ {OV5693_8BIT, 0x5000, 0x06}, -+ {OV5693_8BIT, 0x5001, 0x01}, -+ {OV5693_8BIT, 0x5003, 0x20}, -+ {OV5693_8BIT, 0x5046, 0x0a}, -+ {OV5693_8BIT, 0x5013, 0x00}, -+ {OV5693_8BIT, 0x5046, 0x0a}, -+ {OV5693_8BIT, 0x5780, 0x1c}, -+ {OV5693_8BIT, 0x5786, 0x20}, -+ {OV5693_8BIT, 0x5787, 0x10}, -+ {OV5693_8BIT, 0x5788, 0x18}, -+ {OV5693_8BIT, 0x578a, 0x04}, -+ {OV5693_8BIT, 0x578b, 0x02}, -+ {OV5693_8BIT, 0x578c, 0x02}, -+ {OV5693_8BIT, 0x578e, 0x06}, -+ {OV5693_8BIT, 0x578f, 0x02}, -+ {OV5693_8BIT, 0x5790, 0x02}, -+ {OV5693_8BIT, 0x5791, 0xff}, -+ {OV5693_8BIT, 0x5842, 0x01}, -+ {OV5693_8BIT, 0x5843, 0x2b}, -+ {OV5693_8BIT, 0x5844, 0x01}, -+ {OV5693_8BIT, 0x5845, 0x92}, -+ {OV5693_8BIT, 0x5846, 0x01}, -+ {OV5693_8BIT, 0x5847, 0x8f}, -+ {OV5693_8BIT, 0x5848, 0x01}, -+ {OV5693_8BIT, 0x5849, 0x0c}, -+ {OV5693_8BIT, 0x5e00, 0x00}, -+ {OV5693_8BIT, 0x5e10, 0x0c}, -+ {OV5693_8BIT, 0x0100, 0x00}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+#if ENABLE_NON_PREVIEW -+/* -+ * 654x496 30fps 17ms VBlanking 2lane 10Bit (Scaling) -+ */ -+static struct ov5693_reg const ov5693_654x496[] = { -+ {OV5693_8BIT, 0x3501, 0x3d}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe6}, -+ {OV5693_8BIT, 0x3709, 0xc7}, -+ {OV5693_8BIT, 0x3803, 0x00}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xa3}, -+ {OV5693_8BIT, 0x3808, 0x02}, -+ {OV5693_8BIT, 0x3809, 0x90}, -+ {OV5693_8BIT, 0x380a, 0x01}, -+ {OV5693_8BIT, 0x380b, 0xf0}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x08}, -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x04}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+ * 1296x976 30fps 17ms VBlanking 2lane 10Bit (Scaling) -+*DS from 2592x1952 -+*/ -+static struct ov5693_reg const ov5693_1296x976[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ -+ {OV5693_8BIT, 0x3800, 0x00}, -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, -+ {OV5693_8BIT, 0x3803, 0x00}, -+ -+ {OV5693_8BIT, 0x3804, 0x0a}, -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xA3}, -+ -+ {OV5693_8BIT, 0x3808, 0x05}, -+ {OV5693_8BIT, 0x3809, 0x10}, -+ {OV5693_8BIT, 0x380a, 0x03}, -+ {OV5693_8BIT, 0x380b, 0xD0}, -+ -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ -+ {OV5693_8BIT, 0x3810, 0x00}, -+ {OV5693_8BIT, 0x3811, 0x10}, -+ {OV5693_8BIT, 0x3812, 0x00}, -+ {OV5693_8BIT, 0x3813, 0x02}, -+ -+ {OV5693_8BIT, 0x3814, 0x11}, /*X subsample control*/ -+ {OV5693_8BIT, 0x3815, 0x11}, /*Y subsample control*/ -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+ -+}; -+ -+/* -+ * 336x256 30fps 17ms VBlanking 2lane 10Bit (Scaling) -+ DS from 2564x1956 -+ */ -+static struct ov5693_reg const ov5693_336x256[] = { -+ {OV5693_8BIT, 0x3501, 0x3d}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe6}, -+ {OV5693_8BIT, 0x3709, 0xc7}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xa3}, -+ {OV5693_8BIT, 0x3808, 0x01}, -+ {OV5693_8BIT, 0x3809, 0x50}, -+ {OV5693_8BIT, 0x380a, 0x01}, -+ {OV5693_8BIT, 0x380b, 0x00}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x1E}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x04}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+ * 336x256 30fps 17ms VBlanking 2lane 10Bit (Scaling) -+ DS from 2368x1956 -+ */ -+static struct ov5693_reg const ov5693_368x304[] = { -+ {OV5693_8BIT, 0x3501, 0x3d}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe6}, -+ {OV5693_8BIT, 0x3709, 0xc7}, -+ {OV5693_8BIT, 0x3808, 0x01}, -+ {OV5693_8BIT, 0x3809, 0x70}, -+ {OV5693_8BIT, 0x380a, 0x01}, -+ {OV5693_8BIT, 0x380b, 0x30}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x80}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x04}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+ * ov5693_192x160 30fps 17ms VBlanking 2lane 10Bit (Scaling) -+ DS from 2460x1956 -+ */ -+static struct ov5693_reg const ov5693_192x160[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x80}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3804, 0x0a}, -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xA3}, -+ {OV5693_8BIT, 0x3808, 0x00}, -+ {OV5693_8BIT, 0x3809, 0xC0}, -+ {OV5693_8BIT, 0x380a, 0x00}, -+ {OV5693_8BIT, 0x380b, 0xA0}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x40}, -+ {OV5693_8BIT, 0x3813, 0x00}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x04}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+static struct ov5693_reg const ov5693_736x496[] = { -+ {OV5693_8BIT, 0x3501, 0x3d}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe6}, -+ {OV5693_8BIT, 0x3709, 0xc7}, -+ {OV5693_8BIT, 0x3803, 0x68}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0x3b}, -+ {OV5693_8BIT, 0x3808, 0x02}, -+ {OV5693_8BIT, 0x3809, 0xe0}, -+ {OV5693_8BIT, 0x380a, 0x01}, -+ {OV5693_8BIT, 0x380b, 0xf0}, -+ {OV5693_8BIT, 0x380c, 0x0a}, /*hts*/ -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, /*vts*/ -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x08}, -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x04}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+#endif -+ -+/* -+static struct ov5693_reg const ov5693_736x496[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe6}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3803, 0x00}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xa3}, -+ {OV5693_8BIT, 0x3808, 0x02}, -+ {OV5693_8BIT, 0x3809, 0xe0}, -+ {OV5693_8BIT, 0x380a, 0x01}, -+ {OV5693_8BIT, 0x380b, 0xf0}, -+ {OV5693_8BIT, 0x380c, 0x0d}, -+ {OV5693_8BIT, 0x380d, 0xb0}, -+ {OV5693_8BIT, 0x380e, 0x05}, -+ {OV5693_8BIT, 0x380f, 0xf2}, -+ {OV5693_8BIT, 0x3811, 0x08}, -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x01}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+*/ -+/* -+ * 976x556 30fps 8.8ms VBlanking 2lane 10Bit (Scaling) -+ */ -+#if ENABLE_NON_PREVIEW -+static struct ov5693_reg const ov5693_976x556[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3803, 0xf0}, -+ {OV5693_8BIT, 0x3806, 0x06}, -+ {OV5693_8BIT, 0x3807, 0xa7}, -+ {OV5693_8BIT, 0x3808, 0x03}, -+ {OV5693_8BIT, 0x3809, 0xd0}, -+ {OV5693_8BIT, 0x380a, 0x02}, -+ {OV5693_8BIT, 0x380b, 0x2C}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x10}, -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/*DS from 2624x1492*/ -+static struct ov5693_reg const ov5693_1296x736[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ -+ {OV5693_8BIT, 0x3800, 0x00}, -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, -+ {OV5693_8BIT, 0x3803, 0x00}, -+ -+ {OV5693_8BIT, 0x3804, 0x0a}, -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xA3}, -+ -+ {OV5693_8BIT, 0x3808, 0x05}, -+ {OV5693_8BIT, 0x3809, 0x10}, -+ {OV5693_8BIT, 0x380a, 0x02}, -+ {OV5693_8BIT, 0x380b, 0xe0}, -+ -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ -+ {OV5693_8BIT, 0x3813, 0xE8}, -+ -+ {OV5693_8BIT, 0x3814, 0x11}, /*X subsample control*/ -+ {OV5693_8BIT, 0x3815, 0x11}, /*Y subsample control*/ -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+static struct ov5693_reg const ov5693_1636p_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3803, 0xf0}, -+ {OV5693_8BIT, 0x3806, 0x06}, -+ {OV5693_8BIT, 0x3807, 0xa7}, -+ {OV5693_8BIT, 0x3808, 0x06}, -+ {OV5693_8BIT, 0x3809, 0x64}, -+ {OV5693_8BIT, 0x380a, 0x04}, -+ {OV5693_8BIT, 0x380b, 0x48}, -+ {OV5693_8BIT, 0x380c, 0x0a}, /*hts*/ -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, /*vts*/ -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x02}, -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+#endif -+ -+static struct ov5693_reg const ov5693_1616x1216_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x80}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3800, 0x00}, /*{3800,3801} Array X start*/ -+ {OV5693_8BIT, 0x3801, 0x08}, /* 04 //{3800,3801} Array X start*/ -+ {OV5693_8BIT, 0x3802, 0x00}, /*{3802,3803} Array Y start*/ -+ {OV5693_8BIT, 0x3803, 0x04}, /* 00 //{3802,3803} Array Y start*/ -+ {OV5693_8BIT, 0x3804, 0x0a}, /*{3804,3805} Array X end*/ -+ {OV5693_8BIT, 0x3805, 0x37}, /* 3b //{3804,3805} Array X end*/ -+ {OV5693_8BIT, 0x3806, 0x07}, /*{3806,3807} Array Y end*/ -+ {OV5693_8BIT, 0x3807, 0x9f}, /* a3 //{3806,3807} Array Y end*/ -+ {OV5693_8BIT, 0x3808, 0x06}, /*{3808,3809} Final output H size*/ -+ {OV5693_8BIT, 0x3809, 0x50}, /*{3808,3809} Final output H size*/ -+ {OV5693_8BIT, 0x380a, 0x04}, /*{380a,380b} Final output V size*/ -+ {OV5693_8BIT, 0x380b, 0xc0}, /*{380a,380b} Final output V size*/ -+ {OV5693_8BIT, 0x380c, 0x0a}, /*{380c,380d} HTS*/ -+ {OV5693_8BIT, 0x380d, 0x80}, /*{380c,380d} HTS*/ -+ {OV5693_8BIT, 0x380e, 0x07}, /*{380e,380f} VTS*/ -+ {OV5693_8BIT, 0x380f, 0xc0}, /* bc //{380e,380f} VTS*/ -+ {OV5693_8BIT, 0x3810, 0x00}, /*{3810,3811} windowing X offset*/ -+ {OV5693_8BIT, 0x3811, 0x10}, /*{3810,3811} windowing X offset*/ -+ {OV5693_8BIT, 0x3812, 0x00}, /*{3812,3813} windowing Y offset*/ -+ {OV5693_8BIT, 0x3813, 0x06}, /*{3812,3813} windowing Y offset*/ -+ {OV5693_8BIT, 0x3814, 0x11}, /*X subsample control*/ -+ {OV5693_8BIT, 0x3815, 0x11}, /*Y subsample control*/ -+ {OV5693_8BIT, 0x3820, 0x00}, /*FLIP/Binnning control*/ -+ {OV5693_8BIT, 0x3821, 0x1e}, /*MIRROR control*/ -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x5041, 0x84}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+ * 1940x1096 30fps 8.8ms VBlanking 2lane 10bit (Scaling) -+ */ -+#if ENABLE_NON_PREVIEW -+static struct ov5693_reg const ov5693_1940x1096[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3803, 0xf0}, -+ {OV5693_8BIT, 0x3806, 0x06}, -+ {OV5693_8BIT, 0x3807, 0xa7}, -+ {OV5693_8BIT, 0x3808, 0x07}, -+ {OV5693_8BIT, 0x3809, 0x94}, -+ {OV5693_8BIT, 0x380a, 0x04}, -+ {OV5693_8BIT, 0x380b, 0x48}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x02}, -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+static struct ov5693_reg const ov5693_2592x1456_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3800, 0x00}, -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, -+ {OV5693_8BIT, 0x3803, 0xf0}, -+ {OV5693_8BIT, 0x3804, 0x0a}, -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3806, 0x06}, -+ {OV5693_8BIT, 0x3807, 0xa4}, -+ {OV5693_8BIT, 0x3808, 0x0a}, -+ {OV5693_8BIT, 0x3809, 0x20}, -+ {OV5693_8BIT, 0x380a, 0x05}, -+ {OV5693_8BIT, 0x380b, 0xb0}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x10}, -+ {OV5693_8BIT, 0x3813, 0x00}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+#endif -+ -+static struct ov5693_reg const ov5693_2576x1456_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3800, 0x00}, -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, -+ {OV5693_8BIT, 0x3803, 0xf0}, -+ {OV5693_8BIT, 0x3804, 0x0a}, -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3806, 0x06}, -+ {OV5693_8BIT, 0x3807, 0xa4}, -+ {OV5693_8BIT, 0x3808, 0x0a}, -+ {OV5693_8BIT, 0x3809, 0x10}, -+ {OV5693_8BIT, 0x380a, 0x05}, -+ {OV5693_8BIT, 0x380b, 0xb0}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x18}, -+ {OV5693_8BIT, 0x3813, 0x00}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+ * 2592x1944 30fps 0.6ms VBlanking 2lane 10Bit -+ */ -+#if ENABLE_NON_PREVIEW -+static struct ov5693_reg const ov5693_2592x1944_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3803, 0x00}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xa3}, -+ {OV5693_8BIT, 0x3808, 0x0a}, -+ {OV5693_8BIT, 0x3809, 0x20}, -+ {OV5693_8BIT, 0x380a, 0x07}, -+ {OV5693_8BIT, 0x380b, 0x98}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x10}, -+ {OV5693_8BIT, 0x3813, 0x00}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+#endif -+ -+/* -+ * 11:9 Full FOV Output, expected FOV Res: 2346x1920 -+ * ISP Effect Res: 1408x1152 -+ * Sensor out: 1424x1168, DS From: 2380x1952 -+ * -+ * WA: Left Offset: 8, Hor scal: 64 -+ */ -+#if ENABLE_NON_PREVIEW -+static struct ov5693_reg const ov5693_1424x1168_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x3b}, /* long exposure[15:8] */ -+ {OV5693_8BIT, 0x3502, 0x80}, /* long exposure[7:0] */ -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3800, 0x00}, /* TIMING_X_ADDR_START */ -+ {OV5693_8BIT, 0x3801, 0x50}, /* 80 */ -+ {OV5693_8BIT, 0x3802, 0x00}, /* TIMING_Y_ADDR_START */ -+ {OV5693_8BIT, 0x3803, 0x02}, /* 2 */ -+ {OV5693_8BIT, 0x3804, 0x09}, /* TIMING_X_ADDR_END */ -+ {OV5693_8BIT, 0x3805, 0xdd}, /* 2525 */ -+ {OV5693_8BIT, 0x3806, 0x07}, /* TIMING_Y_ADDR_END */ -+ {OV5693_8BIT, 0x3807, 0xa1}, /* 1953 */ -+ {OV5693_8BIT, 0x3808, 0x05}, /* TIMING_X_OUTPUT_SIZE */ -+ {OV5693_8BIT, 0x3809, 0x90}, /* 1424 */ -+ {OV5693_8BIT, 0x380a, 0x04}, /* TIMING_Y_OUTPUT_SIZE */ -+ {OV5693_8BIT, 0x380b, 0x90}, /* 1168 */ -+ {OV5693_8BIT, 0x380c, 0x0a}, /* TIMING_HTS */ -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, /* TIMING_VTS */ -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3810, 0x00}, /* TIMING_ISP_X_WIN */ -+ {OV5693_8BIT, 0x3811, 0x02}, /* 2 */ -+ {OV5693_8BIT, 0x3812, 0x00}, /* TIMING_ISP_Y_WIN */ -+ {OV5693_8BIT, 0x3813, 0x00}, /* 0 */ -+ {OV5693_8BIT, 0x3814, 0x11}, /* TIME_X_INC */ -+ {OV5693_8BIT, 0x3815, 0x11}, /* TIME_Y_INC */ -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+#endif -+ -+/* -+ * 3:2 Full FOV Output, expected FOV Res: 2560x1706 -+ * ISP Effect Res: 720x480 -+ * Sensor out: 736x496, DS From 2616x1764 -+ */ -+static struct ov5693_reg const ov5693_736x496_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x3b}, /* long exposure[15:8] */ -+ {OV5693_8BIT, 0x3502, 0x80}, /* long exposure[7:0] */ -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3800, 0x00}, /* TIMING_X_ADDR_START */ -+ {OV5693_8BIT, 0x3801, 0x02}, /* 2 */ -+ {OV5693_8BIT, 0x3802, 0x00}, /* TIMING_Y_ADDR_START */ -+ {OV5693_8BIT, 0x3803, 0x62}, /* 98 */ -+ {OV5693_8BIT, 0x3804, 0x0a}, /* TIMING_X_ADDR_END */ -+ {OV5693_8BIT, 0x3805, 0x3b}, /* 2619 */ -+ {OV5693_8BIT, 0x3806, 0x07}, /* TIMING_Y_ADDR_END */ -+ {OV5693_8BIT, 0x3807, 0x43}, /* 1859 */ -+ {OV5693_8BIT, 0x3808, 0x02}, /* TIMING_X_OUTPUT_SIZE */ -+ {OV5693_8BIT, 0x3809, 0xe0}, /* 736 */ -+ {OV5693_8BIT, 0x380a, 0x01}, /* TIMING_Y_OUTPUT_SIZE */ -+ {OV5693_8BIT, 0x380b, 0xf0}, /* 496 */ -+ {OV5693_8BIT, 0x380c, 0x0a}, /* TIMING_HTS */ -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, /* TIMING_VTS */ -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3810, 0x00}, /* TIMING_ISP_X_WIN */ -+ {OV5693_8BIT, 0x3811, 0x02}, /* 2 */ -+ {OV5693_8BIT, 0x3812, 0x00}, /* TIMING_ISP_Y_WIN */ -+ {OV5693_8BIT, 0x3813, 0x00}, /* 0 */ -+ {OV5693_8BIT, 0x3814, 0x11}, /* TIME_X_INC */ -+ {OV5693_8BIT, 0x3815, 0x11}, /* TIME_Y_INC */ -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+static struct ov5693_reg const ov5693_2576x1936_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3803, 0x00}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xa3}, -+ {OV5693_8BIT, 0x3808, 0x0a}, -+ {OV5693_8BIT, 0x3809, 0x10}, -+ {OV5693_8BIT, 0x380a, 0x07}, -+ {OV5693_8BIT, 0x380b, 0x90}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x18}, -+ {OV5693_8BIT, 0x3813, 0x00}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+static struct ov5693_resolution ov5693_res_preview[] = { -+ { -+ .desc = "ov5693_736x496_30fps", -+ .width = 736, -+ .height = 496, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_736x496_30fps, -+ }, -+ { -+ .desc = "ov5693_1616x1216_30fps", -+ .width = 1616, -+ .height = 1216, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_1616x1216_30fps, -+ }, -+ { -+ .desc = "ov5693_5M_30fps", -+ .width = 2576, -+ .height = 1456, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_2576x1456_30fps, -+ }, -+ { -+ .desc = "ov5693_5M_30fps", -+ .width = 2576, -+ .height = 1936, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_2576x1936_30fps, -+ }, -+}; -+ -+#define N_RES_PREVIEW (ARRAY_SIZE(ov5693_res_preview)) -+ -+/* -+ * Disable non-preview configurations until the configuration selection is -+ * improved. -+ */ -+#if ENABLE_NON_PREVIEW -+struct ov5693_resolution ov5693_res_still[] = { -+ { -+ .desc = "ov5693_736x496_30fps", -+ .width = 736, -+ .height = 496, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_736x496_30fps, -+ }, -+ { -+ .desc = "ov5693_1424x1168_30fps", -+ .width = 1424, -+ .height = 1168, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_1424x1168_30fps, -+ }, -+ { -+ .desc = "ov5693_1616x1216_30fps", -+ .width = 1616, -+ .height = 1216, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_1616x1216_30fps, -+ }, -+ { -+ .desc = "ov5693_5M_30fps", -+ .width = 2592, -+ .height = 1456, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_2592x1456_30fps, -+ }, -+ { -+ .desc = "ov5693_5M_30fps", -+ .width = 2592, -+ .height = 1944, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_2592x1944_30fps, -+ }, -+}; -+ -+#define N_RES_STILL (ARRAY_SIZE(ov5693_res_still)) -+ -+struct ov5693_resolution ov5693_res_video[] = { -+ { -+ .desc = "ov5693_736x496_30fps", -+ .width = 736, -+ .height = 496, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 1, -+ .regs = ov5693_736x496, -+ }, -+ { -+ .desc = "ov5693_336x256_30fps", -+ .width = 336, -+ .height = 256, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 1, -+ .regs = ov5693_336x256, -+ }, -+ { -+ .desc = "ov5693_368x304_30fps", -+ .width = 368, -+ .height = 304, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 1, -+ .regs = ov5693_368x304, -+ }, -+ { -+ .desc = "ov5693_192x160_30fps", -+ .width = 192, -+ .height = 160, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 1, -+ .regs = ov5693_192x160, -+ }, -+ { -+ .desc = "ov5693_1296x736_30fps", -+ .width = 1296, -+ .height = 736, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 0, -+ .regs = ov5693_1296x736, -+ }, -+ { -+ .desc = "ov5693_1296x976_30fps", -+ .width = 1296, -+ .height = 976, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 0, -+ .regs = ov5693_1296x976, -+ }, -+ { -+ .desc = "ov5693_1636P_30fps", -+ .width = 1636, -+ .height = 1096, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_1636p_30fps, -+ }, -+ { -+ .desc = "ov5693_1080P_30fps", -+ .width = 1940, -+ .height = 1096, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_1940x1096, -+ }, -+ { -+ .desc = "ov5693_5M_30fps", -+ .width = 2592, -+ .height = 1456, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_2592x1456_30fps, -+ }, -+ { -+ .desc = "ov5693_5M_30fps", -+ .width = 2592, -+ .height = 1944, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_2592x1944_30fps, -+ .crop = { -+ .left = 0, -+ .top = 0, -+ .width = 2592, -+ .height = 1944 -+ }, -+ }, -+}; -+ -+#define N_RES_VIDEO (ARRAY_SIZE(ov5693_res_video)) -+#endif -+ -+static struct ov5693_resolution *ov5693_res = ov5693_res_video; -+static unsigned long N_RES = N_RES_VIDEO; -+#endif --- -2.33.0 - -From 636227d49ba696aefb0608471380d2d7306fdb8c Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 17 Jan 2021 19:08:18 +0000 -Subject: [PATCH] media: i2c: Add reset pin toggling to ov5693 - -The ov5693 has an xshutdown pin which can be present and, if so, needs -toggling as part of power on sequence. - -Add calls to handle the reset GPIO - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 32485e4ed42b..f9ced52ad37a 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1085,6 +1085,8 @@ static int __power_up(struct v4l2_subdev *sd) - if (ret) - goto fail_power; - -+ gpiod_set_value_cansleep(sensor->reset, 0); -+ - __cci_delay(up_delay); - - return 0; -@@ -1103,6 +1105,8 @@ static int power_down(struct v4l2_subdev *sd) - - dev->focus = OV5693_INVALID_CONFIG; - -+ gpiod_set_value_cansleep(sensor->reset, 1); -+ - clk_disable_unprepare(dev->clk); - - if (dev->indicator_led) --- -2.33.0 - -From 2dffcd137b3a665ea278785f3c01c0f143e301e8 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 17 Jan 2021 21:39:15 +0000 -Subject: [PATCH] media: i2c: Fix misnamed variable in power_down() for ov5693 - -Fix the misnamed variable in gpiod_set_value_cansleep(). - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index f9ced52ad37a..9fd44a3d1d85 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1105,7 +1105,7 @@ static int power_down(struct v4l2_subdev *sd) - - dev->focus = OV5693_INVALID_CONFIG; - -- gpiod_set_value_cansleep(sensor->reset, 1); -+ gpiod_set_value_cansleep(dev->reset, 1); - - clk_disable_unprepare(dev->clk); - --- -2.33.0 - -From 238061b3ae97913f96ade9beba6c26947de52d6c Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Fri, 22 Jan 2021 20:58:13 +0100 -Subject: [PATCH] cio2-bridge: Parse sensor orientation and rotation -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -The sensor orientation is read from the _PLC ACPI buffer and converted -to a v4l2 format. - -See https://uefi.org/sites/default/files/resources/ACPI_6_3_final_Jan30.pdf -page 351 for a definition of the Panel property. - -The sensor rotation is read from the SSDB ACPI buffer and converted into -degrees. - -Signed-off-by: Fabian Wüthrich -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 45 ++++++++++++++++++++-- - drivers/media/pci/intel/ipu3/cio2-bridge.h | 3 ++ - 2 files changed, 44 insertions(+), 4 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index 143f3c0f445e..806d4e5fc177 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -29,6 +29,7 @@ static const struct cio2_sensor_config cio2_supported_sensors[] = { - static const struct cio2_property_names prop_names = { - .clock_frequency = "clock-frequency", - .rotation = "rotation", -+ .orientation = "orientation", - .bus_type = "bus-type", - .data_lanes = "data-lanes", - .remote_endpoint = "remote-endpoint", -@@ -72,11 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, - return ret; - } - -+static u32 cio2_bridge_parse_rotation(u8 rotation) -+{ -+ if (rotation == 1) -+ return 180; -+ return 0; -+} -+ -+static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) -+{ -+ switch (panel) { -+ case 4: -+ return V4L2_FWNODE_ORIENTATION_FRONT; -+ case 5: -+ return V4L2_FWNODE_ORIENTATION_BACK; -+ default: -+ return V4L2_FWNODE_ORIENTATION_EXTERNAL; -+ } -+} -+ - static void cio2_bridge_create_fwnode_properties( - struct cio2_sensor *sensor, - struct cio2_bridge *bridge, - const struct cio2_sensor_config *cfg) - { -+ u32 rotation; -+ enum v4l2_fwnode_orientation orientation; -+ -+ rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); -+ orientation = cio2_bridge_parse_orientation(sensor->pld->panel); -+ - sensor->prop_names = prop_names; - - sensor->local_ref[0].node = &sensor->swnodes[SWNODE_CIO2_ENDPOINT]; -@@ -85,9 +111,12 @@ static void cio2_bridge_create_fwnode_properties( - sensor->dev_properties[0] = PROPERTY_ENTRY_U32( - sensor->prop_names.clock_frequency, - sensor->ssdb.mclkspeed); -- sensor->dev_properties[1] = PROPERTY_ENTRY_U8( -+ sensor->dev_properties[1] = PROPERTY_ENTRY_U32( - sensor->prop_names.rotation, -- sensor->ssdb.degree); -+ rotation); -+ sensor->dev_properties[2] = PROPERTY_ENTRY_U32( -+ sensor->prop_names.orientation, -+ orientation); - - sensor->ep_properties[0] = PROPERTY_ENTRY_U32( - sensor->prop_names.bus_type, -@@ -159,6 +188,7 @@ static void cio2_bridge_unregister_sensors(struct cio2_bridge *bridge) - for (i = 0; i < bridge->n_sensors; i++) { - sensor = &bridge->sensors[i]; - software_node_unregister_nodes(sensor->swnodes); -+ ACPI_FREE(sensor->pld); - acpi_dev_put(sensor->adev); - } - } -@@ -170,6 +200,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - struct fwnode_handle *fwnode; - struct cio2_sensor *sensor; - struct acpi_device *adev; -+ acpi_status status; - int ret; - - for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { -@@ -193,11 +224,15 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - if (ret) - goto err_put_adev; - -+ status = acpi_get_physical_device_location(adev->handle, &sensor->pld); -+ if (ACPI_FAILURE(status)) -+ goto err_put_adev; -+ - if (sensor->ssdb.lanes > CIO2_MAX_LANES) { - dev_err(&adev->dev, - "Number of lanes in SSDB is invalid\n"); - ret = -EINVAL; -- goto err_put_adev; -+ goto err_free_pld; - } - - cio2_bridge_create_fwnode_properties(sensor, bridge, cfg); -@@ -205,7 +240,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - - ret = software_node_register_nodes(sensor->swnodes); - if (ret) -- goto err_put_adev; -+ goto err_free_pld; - - fwnode = software_node_fwnode(&sensor->swnodes[SWNODE_SENSOR_HID]); - if (!fwnode) { -@@ -225,6 +260,8 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - - err_free_swnodes: - software_node_unregister_nodes(sensor->swnodes); -+err_free_pld: -+ ACPI_FREE(sensor->pld); - err_put_adev: - acpi_dev_put(sensor->adev); - err_out: -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index dd0ffcafa489..924d99d20328 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -80,6 +80,7 @@ struct cio2_sensor_ssdb { - struct cio2_property_names { - char clock_frequency[16]; - char rotation[9]; -+ char orientation[12]; - char bus_type[9]; - char data_lanes[11]; - char remote_endpoint[16]; -@@ -106,6 +107,8 @@ struct cio2_sensor { - struct cio2_node_names node_names; - - struct cio2_sensor_ssdb ssdb; -+ struct acpi_pld_info *pld; -+ - struct cio2_property_names prop_names; - struct property_entry ep_properties[5]; - struct property_entry dev_properties[3]; --- -2.33.0 - -From 0c8bba6d8661cbb5f7a54ba8ccd17678ae58ebc9 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Fri, 22 Jan 2021 21:23:47 +0100 -Subject: [PATCH] ov5693: Add orientation and rotation controls -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - - Parse orientation and rotation from fwnodes and initialize the - respective controls. - -Signed-off-by: Fabian Wüthrich -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 11 +++++++++++ - 1 file changed, 11 insertions(+) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 9fd44a3d1d85..1a85800df7ed 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -31,6 +31,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -1608,6 +1609,7 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - { - struct i2c_client *client = v4l2_get_subdevdata(&ov5693->sd); - const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; -+ struct v4l2_fwnode_device_properties props; - struct v4l2_ctrl *ctrl; - unsigned int i; - int ret; -@@ -1663,6 +1665,15 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - if (ov5693->hblank) - ov5693->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; - -+ /* set properties from fwnode (e.g. rotation, orientation) */ -+ ret = v4l2_fwnode_device_parse(&client->dev, &props); -+ if (ret) -+ return ret; -+ -+ ret = v4l2_ctrl_new_fwnode_properties(&ov5693->ctrl_handler, ops, &props); -+ if (ret) -+ return ret; -+ - /* Use same lock for controls as for everything else. */ - ov5693->ctrl_handler.lock = &ov5693->input_lock; - ov5693->sd.ctrl_handler = &ov5693->ctrl_handler; --- -2.33.0 - -From 2e7dcc9a3f803030d102e9cdfba6a995311b5a55 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 23 Jan 2021 00:28:32 +0000 -Subject: [PATCH] platform: x86: Stylistic updates for intel-skl-int3472 - -This commit makes a bunch of stylistic updates, minor changes and other -stuff that's part of the improvements pass I'm doing to the code after -taking into account feedback from the list. - -It also alters the ACPI buffer fetching code to be more generalisable so -I can re-use it to fetch the clock frequency. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - .../platform/x86/intel_skl_int3472_common.c | 37 ++++--- - .../platform/x86/intel_skl_int3472_common.h | 7 +- - .../platform/x86/intel_skl_int3472_discrete.c | 101 +++++++++--------- - .../platform/x86/intel_skl_int3472_tps68470.c | 16 +-- - 4 files changed, 89 insertions(+), 72 deletions(-) - -diff --git a/drivers/platform/x86/intel_skl_int3472_common.c b/drivers/platform/x86/intel_skl_int3472_common.c -index 08cb9d3c06aa..549d211979e1 100644 ---- a/drivers/platform/x86/intel_skl_int3472_common.c -+++ b/drivers/platform/x86/intel_skl_int3472_common.c -@@ -7,41 +7,52 @@ - - #include "intel_skl_int3472_common.h" - --int skl_int3472_get_cldb_buffer(struct acpi_device *adev, -- struct int3472_cldb *cldb) -+union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, -+ char *id) - { - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - acpi_handle handle = adev->handle; - union acpi_object *obj; - acpi_status status; -- int ret = 0; - -- status = acpi_evaluate_object(handle, "CLDB", NULL, &buffer); -+ status = acpi_evaluate_object(handle, id, NULL, &buffer); - if (ACPI_FAILURE(status)) -- return -ENODEV; -+ return ERR_PTR(-ENODEV); - - obj = buffer.pointer; - if (!obj) { -- dev_err(&adev->dev, "ACPI device has no CLDB object\n"); -- return -ENODEV; -+ dev_err(&adev->dev, "ACPI device has no %s object\n", id); -+ return ERR_PTR(-ENODEV); - } - - if (obj->type != ACPI_TYPE_BUFFER) { -- dev_err(&adev->dev, "CLDB object is not an ACPI buffer\n"); -- ret = -EINVAL; -- goto out_free_buff; -+ dev_err(&adev->dev, "%s object is not an ACPI buffer\n", id); -+ kfree(obj); -+ return ERR_PTR(-EINVAL); - } - -+ return obj; -+} -+ -+int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) -+{ -+ union acpi_object *obj; -+ int ret = 0; -+ -+ obj = skl_int3472_get_acpi_buffer(adev, "CLDB"); -+ if (IS_ERR(obj)) -+ return PTR_ERR(obj); -+ - if (obj->buffer.length > sizeof(*cldb)) { - dev_err(&adev->dev, "The CLDB buffer is too large\n"); - ret = -EINVAL; -- goto out_free_buff; -+ goto out_free_obj; - } - - memcpy(cldb, obj->buffer.pointer, obj->buffer.length); - --out_free_buff: -- kfree(buffer.pointer); -+out_free_obj: -+ kfree(obj); - return ret; - } - -diff --git a/drivers/platform/x86/intel_skl_int3472_common.h b/drivers/platform/x86/intel_skl_int3472_common.h -index 4ac6bb2b223f..e1083bb67dc6 100644 ---- a/drivers/platform/x86/intel_skl_int3472_common.h -+++ b/drivers/platform/x86/intel_skl_int3472_common.h -@@ -29,7 +29,7 @@ - - #define INT3472_GPIO_FUNCTION_REMAP(_PIN, _FUNCTION) \ - (const struct int3472_gpio_function_remap) { \ -- .documented = _PIN, \ -+ .documented = _PIN, \ - .actual = _FUNCTION \ - } - -@@ -95,5 +95,6 @@ struct int3472_sensor_config { - int skl_int3472_discrete_probe(struct platform_device *pdev); - int skl_int3472_discrete_remove(struct platform_device *pdev); - int skl_int3472_tps68470_probe(struct i2c_client *client); --int skl_int3472_get_cldb_buffer(struct acpi_device *adev, -- struct int3472_cldb *cldb); -+union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, -+ char *id); -+int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); -diff --git a/drivers/platform/x86/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel_skl_int3472_discrete.c -index ea7e57f3e3f0..42ae8396eb64 100644 ---- a/drivers/platform/x86/intel_skl_int3472_discrete.c -+++ b/drivers/platform/x86/intel_skl_int3472_discrete.c -@@ -12,12 +12,12 @@ - - #include "intel_skl_int3472_common.h" - --/* 79234640-9e10-4fea-a5c1b5aa8b19756f */ -+/* 79234640-9e10-4fea-a5c1-b5aa8b19756f */ - static const guid_t int3472_gpio_guid = - GUID_INIT(0x79234640, 0x9e10, 0x4fea, - 0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f); - --/* 822ace8f-2814-4174-a56b5f029fe079ee */ -+/* 822ace8f-2814-4174-a56b-5f029fe079ee */ - static const guid_t cio2_sensor_module_guid = - GUID_INIT(0x822ace8f, 0x2814, 0x4174, - 0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee); -@@ -94,7 +94,7 @@ static const struct clk_ops skl_int3472_clock_ops = { - }; - - static struct int3472_sensor_config * --int3472_get_sensor_module_config(struct int3472_device *int3472) -+skl_int3472_get_sensor_module_config(struct int3472_device *int3472) - { - unsigned int i = ARRAY_SIZE(int3472_sensor_configs); - struct int3472_sensor_config *ret; -@@ -131,9 +131,9 @@ int3472_get_sensor_module_config(struct int3472_device *int3472) - return ret; - } - --static int int3472_map_gpio_to_sensor(struct int3472_device *int3472, -- struct acpi_resource *ares, -- char *func, u32 polarity) -+static int skl_int3472_map_gpio_to_sensor(struct int3472_device *int3472, -+ struct acpi_resource *ares, -+ char *func, u32 polarity) - { - char *path = ares->data.gpio.resource_source.string_ptr; - struct int3472_sensor_config *sensor_config; -@@ -143,7 +143,7 @@ static int int3472_map_gpio_to_sensor(struct int3472_device *int3472, - acpi_status status; - int ret; - -- sensor_config = int3472_get_sensor_module_config(int3472); -+ sensor_config = skl_int3472_get_sensor_module_config(int3472); - if (!IS_ERR(sensor_config) && sensor_config->function_maps) { - unsigned int i = 0; - -@@ -186,17 +186,19 @@ static int int3472_map_gpio_to_sensor(struct int3472_device *int3472, - return 0; - } - --static int int3472_register_clock(struct int3472_device *int3472, -- struct acpi_resource *ares) -+static int skl_int3472_register_clock(struct int3472_device *int3472, -+ struct acpi_resource *ares) - { - char *path = ares->data.gpio.resource_source.string_ptr; -- struct clk_init_data init = { }; -+ struct clk_init_data init = { 0 }; - int ret = 0; - -- init.name = kasprintf(GFP_KERNEL, "%s-clk", acpi_dev_name(int3472->adev)); -+ init.name = kasprintf(GFP_KERNEL, "%s-clk", -+ acpi_dev_name(int3472->adev)); - init.ops = &skl_int3472_clock_ops; - -- int3472->clock.gpio = acpi_get_gpiod(path, ares->data.gpio.pin_table[0]); -+ int3472->clock.gpio = acpi_get_gpiod(path, -+ ares->data.gpio.pin_table[0]); - if (IS_ERR(int3472->clock.gpio)) { - ret = PTR_ERR(int3472->clock.gpio); - goto out_free_init_name; -@@ -226,17 +228,16 @@ static int int3472_register_clock(struct int3472_device *int3472, - return ret; - } - --static int int3472_register_regulator(struct int3472_device *int3472, -- struct acpi_resource *ares) -+static int skl_int3472_register_regulator(struct int3472_device *int3472, -+ struct acpi_resource *ares) - { - char *path = ares->data.gpio.resource_source.string_ptr; - struct int3472_sensor_config *sensor_config; - struct regulator_init_data init_data = { }; -- struct int3472_gpio_regulator *regulator; - struct regulator_config cfg = { }; - int ret; - -- sensor_config = int3472_get_sensor_module_config(int3472); -+ sensor_config = skl_int3472_get_sensor_module_config(int3472); - if (IS_ERR_OR_NULL(sensor_config)) { - dev_err(&int3472->pdev->dev, "No sensor module config\n"); - return PTR_ERR(sensor_config); -@@ -252,26 +253,29 @@ static int int3472_register_regulator(struct int3472_device *int3472, - init_data.num_consumer_supplies = 1; - init_data.consumer_supplies = &sensor_config->supply_map; - -- snprintf(int3472->regulator.regulator_name, GPIO_REGULATOR_NAME_LENGTH, -- "int3472-discrete-regulator"); -- snprintf(int3472->regulator.supply_name, GPIO_REGULATOR_SUPPLY_NAME_LENGTH, -- "supply-0"); -+ snprintf(int3472->regulator.regulator_name, -+ GPIO_REGULATOR_NAME_LENGTH, "int3472-discrete-regulator"); -+ snprintf(int3472->regulator.supply_name, -+ GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0"); - -- int3472->regulator.rdesc = INT3472_REGULATOR(int3472->regulator.regulator_name, -- int3472->regulator.supply_name, -- &int3472_gpio_regulator_ops); -+ int3472->regulator.rdesc = INT3472_REGULATOR( -+ int3472->regulator.regulator_name, -+ int3472->regulator.supply_name, -+ &int3472_gpio_regulator_ops); - -- int3472->regulator.gpio = acpi_get_gpiod(path, ares->data.gpio.pin_table[0]); -+ int3472->regulator.gpio = acpi_get_gpiod(path, -+ ares->data.gpio.pin_table[0]); - if (IS_ERR(int3472->regulator.gpio)) { -- ret = PTR_ERR(int3472->regulator.gpio); -- goto err_free_regulator; -+ dev_err(&int3472->pdev->dev, "Failed to get GPIO line\n"); -+ return PTR_ERR(int3472->regulator.gpio); - } - - cfg.dev = &int3472->adev->dev; - cfg.init_data = &init_data; - cfg.ena_gpiod = int3472->regulator.gpio; - -- int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc, &cfg); -+ int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc, -+ &cfg); - if (IS_ERR(int3472->regulator.rdev)) { - ret = PTR_ERR(int3472->regulator.rdev); - goto err_free_gpio; -@@ -280,15 +284,13 @@ static int int3472_register_regulator(struct int3472_device *int3472, - return 0; - - err_free_gpio: -- gpiod_put(regulator->gpio); --err_free_regulator: -- kfree(regulator); -+ gpiod_put(int3472->regulator.gpio); - - return ret; - } - - /** -- * int3472_handle_gpio_resources: maps PMIC resources to consuming sensor -+ * skl_int3472_handle_gpio_resources: maps PMIC resources to consuming sensor - * @ares: A pointer to a &struct acpi_resource - * @data: A pointer to a &struct int3472_device - * -@@ -305,8 +307,9 @@ static int int3472_register_regulator(struct int3472_device *int3472, - * - * There are some known platform specific quirks where that does not quite - * hold up; for example where a pin with type 0x01 (Power down) is mapped to -- * a sensor pin that performs a reset function. These will be handled by the -- * mapping sub-functions. -+ * a sensor pin that performs a reset function or entries in _CRS and _DSM that -+ * do not actually correspond to a physical connection. These will be handled by -+ * the mapping sub-functions. - * - * GPIOs will either be mapped directly to the sensor device or else used - * to create clocks and regulators via the usual frameworks. -@@ -317,8 +320,8 @@ static int int3472_register_regulator(struct int3472_device *int3472, - * * -ENODEV - If the resource has no corresponding _DSM entry - * * -Other - Errors propagated from one of the sub-functions. - */ --static int int3472_handle_gpio_resources(struct acpi_resource *ares, -- void *data) -+static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, -+ void *data) - { - struct int3472_device *int3472 = data; - union acpi_object *obj; -@@ -345,30 +348,30 @@ static int int3472_handle_gpio_resources(struct acpi_resource *ares, - - switch (obj->integer.value & 0xff) { - case INT3472_GPIO_TYPE_RESET: -- ret = int3472_map_gpio_to_sensor(int3472, ares, "reset", -- GPIO_ACTIVE_LOW); -+ ret = skl_int3472_map_gpio_to_sensor(int3472, ares, "reset", -+ GPIO_ACTIVE_LOW); - if (ret) - dev_err(&int3472->pdev->dev, - "Failed to map reset pin to sensor\n"); - - break; - case INT3472_GPIO_TYPE_POWERDOWN: -- ret = int3472_map_gpio_to_sensor(int3472, ares, "powerdown", -- GPIO_ACTIVE_LOW); -+ ret = skl_int3472_map_gpio_to_sensor(int3472, ares, "powerdown", -+ GPIO_ACTIVE_LOW); - if (ret) - dev_err(&int3472->pdev->dev, - "Failed to map powerdown pin to sensor\n"); - - break; - case INT3472_GPIO_TYPE_CLK_ENABLE: -- ret = int3472_register_clock(int3472, ares); -+ ret = skl_int3472_register_clock(int3472, ares); - if (ret) - dev_err(&int3472->pdev->dev, - "Failed to map clock to sensor\n"); - - break; - case INT3472_GPIO_TYPE_POWER_ENABLE: -- ret = int3472_register_regulator(int3472, ares); -+ ret = skl_int3472_register_regulator(int3472, ares); - if (ret) { - dev_err(&int3472->pdev->dev, - "Failed to map regulator to sensor\n"); -@@ -376,8 +379,9 @@ static int int3472_handle_gpio_resources(struct acpi_resource *ares, - - break; - case INT3472_GPIO_TYPE_PRIVACY_LED: -- ret = int3472_map_gpio_to_sensor(int3472, ares, "indicator-led", -- GPIO_ACTIVE_HIGH); -+ ret = skl_int3472_map_gpio_to_sensor(int3472, ares, -+ "indicator-led", -+ GPIO_ACTIVE_HIGH); - if (ret) - dev_err(&int3472->pdev->dev, - "Failed to map indicator led to sensor\n"); -@@ -396,7 +400,7 @@ static int int3472_handle_gpio_resources(struct acpi_resource *ares, - return ret; - } - --static int int3472_parse_crs(struct int3472_device *int3472) -+static int skl_int3472_parse_crs(struct int3472_device *int3472) - { - struct list_head resource_list; - int ret = 0; -@@ -404,7 +408,8 @@ static int int3472_parse_crs(struct int3472_device *int3472) - INIT_LIST_HEAD(&resource_list); - - ret = acpi_dev_get_resources(int3472->adev, &resource_list, -- int3472_handle_gpio_resources, int3472); -+ skl_int3472_handle_gpio_resources, -+ int3472); - - if (!ret) { - gpiod_add_lookup_table(&int3472->gpios); -@@ -423,7 +428,7 @@ int skl_int3472_discrete_probe(struct platform_device *pdev) - struct int3472_cldb cldb; - int ret = 0; - -- ret = skl_int3472_get_cldb_buffer(adev, &cldb); -+ ret = skl_int3472_fill_cldb(adev, &cldb); - if (ret || cldb.control_logic_type != 1) - return -EINVAL; - -@@ -444,10 +449,10 @@ int skl_int3472_discrete_probe(struct platform_device *pdev) - ret = -ENODEV; - goto err_free_int3472; - } -- int3472->sensor_name = i2c_acpi_dev_name(int3472->sensor); -+ int3472->sensor_name = kasprintf(GFP_KERNEL, I2C_DEV_NAME_FORMAT, acpi_dev_name(int3472->sensor)); - int3472->gpios.dev_id = int3472->sensor_name; - -- ret = int3472_parse_crs(int3472); -+ ret = skl_int3472_parse_crs(int3472); - if (ret) { - skl_int3472_discrete_remove(pdev); - goto err_return_ret; -diff --git a/drivers/platform/x86/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel_skl_int3472_tps68470.c -index 3fe27ec0caff..40629291b339 100644 ---- a/drivers/platform/x86/intel_skl_int3472_tps68470.c -+++ b/drivers/platform/x86/intel_skl_int3472_tps68470.c -@@ -87,20 +87,20 @@ int skl_int3472_tps68470_probe(struct i2c_client *client) - - /* - * Check CLDB buffer against the PMIC's adev. If present, then we check -- * the value of control_logic_type field and follow one of the following -- * scenarios: -+ * the value of control_logic_type field and follow one of the -+ * following scenarios: - * -- * 1. No CLDB - likely ACPI tables designed for ChromeOS. We create -- * platform devices for the GPIOs and OpRegion drivers. -+ * 1. No CLDB - likely ACPI tables designed for ChromeOS. We -+ * create platform devices for the GPIOs and OpRegion drivers. - * -- * 2. CLDB, with control_logic_type = 2 - probably ACPI tables made -- * for Windows 2-in-1 platforms. Register pdevs for GPIO, Clock and -- * Regulator drivers to bind to. -+ * 2. CLDB, with control_logic_type = 2 - probably ACPI tables -+ * made for Windows 2-in-1 platforms. Register pdevs for GPIO, -+ * Clock and Regulator drivers to bind to. - * - * 3. Any other value in control_logic_type, we should never have - * gotten to this point; crash and burn. - */ -- ret = skl_int3472_get_cldb_buffer(adev, &cldb); -+ ret = skl_int3472_fill_cldb(adev, &cldb); - if (!ret && cldb.control_logic_type != 2) - return -EINVAL; - --- -2.33.0 - -From 44e995a18984d3b9406e53da163ec61619c56d9a Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 23 Jan 2021 00:30:15 +0000 -Subject: [PATCH] platform: x86: Add recalc_rate opp to int3472-discrete clock - -This commit adds the recalc_rate opp to the clock registered by -int3472-discrete so that sensor drivers calling clk_get_rate() will get a -valid value returned. - -The value is simply read from the sensor's SSDB buffer, and so we pass -CLK_GET_RATE_NOCACHE - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - .../platform/x86/intel_skl_int3472_common.h | 6 +++ - .../platform/x86/intel_skl_int3472_discrete.c | 37 ++++++++++++++++++- - 2 files changed, 41 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/x86/intel_skl_int3472_common.h b/drivers/platform/x86/intel_skl_int3472_common.h -index e1083bb67dc6..860c849b7769 100644 ---- a/drivers/platform/x86/intel_skl_int3472_common.h -+++ b/drivers/platform/x86/intel_skl_int3472_common.h -@@ -17,6 +17,8 @@ - #define GPIO_REGULATOR_NAME_LENGTH 27 - #define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9 - -+#define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86 -+ - #define INT3472_REGULATOR(_NAME, _SUPPLY, _OPS) \ - (const struct regulator_desc) { \ - .name = _NAME, \ -@@ -36,6 +38,9 @@ - #define to_int3472_clk(hw) \ - container_of(hw, struct int3472_gpio_clock, clk_hw) - -+#define to_int3472_device(clk) \ -+ container_of(clk, struct int3472_device, clock) -+ - struct int3472_cldb { - u8 version; - /* -@@ -62,6 +67,7 @@ struct int3472_gpio_regulator { - struct int3472_gpio_clock { - struct clk *clk; - struct clk_hw clk_hw; -+ struct clk_lookup *cl; - struct gpio_desc *gpio; - }; - -diff --git a/drivers/platform/x86/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel_skl_int3472_discrete.c -index 42ae8396eb64..98eb1ec3399e 100644 ---- a/drivers/platform/x86/intel_skl_int3472_discrete.c -+++ b/drivers/platform/x86/intel_skl_int3472_discrete.c -@@ -86,11 +86,41 @@ static void skl_int3472_clk_unprepare(struct clk_hw *hw) - /* Likewise, nothing to do here... */ - } - -+static unsigned int skl_int3472_get_clk_frequency(struct int3472_device *int3472) -+{ -+ union acpi_object *obj; -+ unsigned int ret = 0; -+ -+ obj = skl_int3472_get_acpi_buffer(int3472->sensor, "SSDB"); -+ if (IS_ERR(obj)) -+ goto out_free_buff; /* report rate as 0 on error */ -+ -+ if (obj->buffer.length < CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET + sizeof(u32)) { -+ dev_err(&int3472->pdev->dev, "The buffer is too small\n"); -+ goto out_free_buff; -+ } -+ -+ ret = *(u32*)(obj->buffer.pointer + CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET); -+ -+out_free_buff: -+ kfree(obj); -+ return ret; -+} -+ -+static unsigned long skl_int3472_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) -+{ -+ struct int3472_gpio_clock *clk = to_int3472_clk(hw); -+ struct int3472_device *int3472 = to_int3472_device(clk); -+ -+ return skl_int3472_get_clk_frequency(int3472); -+} -+ - static const struct clk_ops skl_int3472_clock_ops = { - .prepare = skl_int3472_clk_prepare, - .unprepare = skl_int3472_clk_unprepare, - .enable = skl_int3472_clk_enable, - .disable = skl_int3472_clk_disable, -+ .recalc_rate = skl_int3472_clk_recalc_rate, - }; - - static struct int3472_sensor_config * -@@ -196,6 +226,7 @@ static int skl_int3472_register_clock(struct int3472_device *int3472, - init.name = kasprintf(GFP_KERNEL, "%s-clk", - acpi_dev_name(int3472->adev)); - init.ops = &skl_int3472_clock_ops; -+ init.flags |= CLK_GET_RATE_NOCACHE; - - int3472->clock.gpio = acpi_get_gpiod(path, - ares->data.gpio.pin_table[0]); -@@ -212,8 +243,9 @@ static int skl_int3472_register_clock(struct int3472_device *int3472, - goto err_put_gpio; - } - -- ret = clk_register_clkdev(int3472->clock.clk, "xvclk", int3472->sensor_name); -- if (ret) -+ int3472->clock.cl = clkdev_create(int3472->clock.clk, "xvclk", -+ int3472->sensor_name); -+ if (IS_ERR_OR_NULL(int3472->clock.cl)) - goto err_unregister_clk; - - goto out_free_init_name; -@@ -483,6 +515,7 @@ int skl_int3472_discrete_remove(struct platform_device *pdev) - if (!IS_ERR_OR_NULL(int3472->clock.clk)) { - gpiod_put(int3472->clock.gpio); - clk_unregister(int3472->clock.clk); -+ clkdev_drop(int3472->clock.cl); - } - - acpi_dev_put(int3472->sensor); --- -2.33.0 - -From af463fa43654ac271940b47b9f9bd31a6d332750 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Sun, 24 Jan 2021 11:07:42 +0100 -Subject: [PATCH] cio2-bridge: Use macros and add warnings -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Use macros for the _PLD panel as defined in the ACPI spec 6.3 and emit -a warning if we see an unknown value. - -Signed-off-by: Fabian Wüthrich -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 33 ++++++++++++++++------ - drivers/media/pci/intel/ipu3/cio2-bridge.h | 13 +++++++++ - 2 files changed, 37 insertions(+), 9 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index 806d4e5fc177..3c373ad1c0b0 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -73,21 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, - return ret; - } - --static u32 cio2_bridge_parse_rotation(u8 rotation) -+static u32 cio2_bridge_parse_rotation(struct cio2_sensor *sensor) - { -- if (rotation == 1) -+ switch (sensor->ssdb.degree) { -+ case CIO2_SENSOR_ROTATION_NORMAL: -+ return 0; -+ case CIO2_SENSOR_ROTATION_INVERTED: - return 180; -- return 0; -+ default: -+ dev_warn(&sensor->adev->dev, -+ "Unknown rotation %d. Assume 0 degree rotation\n", -+ sensor->ssdb.degree); -+ return 0; -+ } - } - --static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) -+static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(struct cio2_sensor *sensor) - { -- switch (panel) { -- case 4: -+ switch (sensor->pld->panel) { -+ case CIO2_PLD_PANEL_FRONT: - return V4L2_FWNODE_ORIENTATION_FRONT; -- case 5: -+ case CIO2_PLD_PANEL_BACK: - return V4L2_FWNODE_ORIENTATION_BACK; -+ case CIO2_PLD_PANEL_TOP: -+ case CIO2_PLD_PANEL_LEFT: -+ case CIO2_PLD_PANEL_RIGHT: -+ case CIO2_PLD_PANEL_UNKNOWN: -+ return V4L2_FWNODE_ORIENTATION_EXTERNAL; - default: -+ dev_warn(&sensor->adev->dev, "Unknown _PLD panel value %d\n", -+ sensor->pld->panel); - return V4L2_FWNODE_ORIENTATION_EXTERNAL; - } - } -@@ -100,8 +115,8 @@ static void cio2_bridge_create_fwnode_properties( - u32 rotation; - enum v4l2_fwnode_orientation orientation; - -- rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); -- orientation = cio2_bridge_parse_orientation(sensor->pld->panel); -+ rotation = cio2_bridge_parse_rotation(sensor); -+ orientation = cio2_bridge_parse_orientation(sensor); - - sensor->prop_names = prop_names; - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index 924d99d20328..e1e388cc9f45 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -12,6 +12,19 @@ - #define CIO2_MAX_LANES 4 - #define MAX_NUM_LINK_FREQS 3 - -+/* Values are estimated guesses as we don't have a spec */ -+#define CIO2_SENSOR_ROTATION_NORMAL 0 -+#define CIO2_SENSOR_ROTATION_INVERTED 1 -+ -+/* Panel position defined in _PLD section of ACPI Specification 6.3 */ -+#define CIO2_PLD_PANEL_TOP 0 -+#define CIO2_PLD_PANEL_BOTTOM 1 -+#define CIO2_PLD_PANEL_LEFT 2 -+#define CIO2_PLD_PANEL_RIGHT 3 -+#define CIO2_PLD_PANEL_FRONT 4 -+#define CIO2_PLD_PANEL_BACK 5 -+#define CIO2_PLD_PANEL_UNKNOWN 6 -+ - #define CIO2_SENSOR_CONFIG(_HID, _NR, ...) \ - (const struct cio2_sensor_config) { \ - .hid = _HID, \ --- -2.33.0 - -From 9f5594c8807e188c8f57a84a9574be82446118fb Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 8 Feb 2021 21:44:38 +0000 -Subject: [PATCH] media: i2c: Tidy up ov5693_init_controls() - -The ov5693 driver initialises a bunch of v4l2 controls and throws away -the pointers. This seems weird, let's not do that. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 46 ++++++++++++++++++++++---------------- - drivers/media/i2c/ov5693.h | 12 +++++++++- - 2 files changed, 38 insertions(+), 20 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 1a85800df7ed..a9747ab783d7 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1610,7 +1610,6 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - struct i2c_client *client = v4l2_get_subdevdata(&ov5693->sd); - const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; - struct v4l2_fwnode_device_properties props; -- struct v4l2_ctrl *ctrl; - unsigned int i; - int ret; - int hblank; -@@ -1628,15 +1627,17 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - NULL); - - /* link freq */ -- ctrl = v4l2_ctrl_new_int_menu(&ov5693->ctrl_handler, NULL, -- V4L2_CID_LINK_FREQ, -- 0, 0, link_freq_menu_items); -- if (ctrl) -- ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ ov5693->ctrls.link_freq = v4l2_ctrl_new_int_menu(&ov5693->ctrl_handler, -+ NULL, V4L2_CID_LINK_FREQ, -+ 0, 0, link_freq_menu_items); -+ if (ov5693->ctrls.link_freq) -+ ov5693->ctrls.link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; - - /* pixel rate */ -- v4l2_ctrl_new_std(&ov5693->ctrl_handler, NULL, V4L2_CID_PIXEL_RATE, -- 0, OV5693_PIXEL_RATE, 1, OV5693_PIXEL_RATE); -+ ov5693->ctrls.pixel_rate = v4l2_ctrl_new_std(&ov5693->ctrl_handler, NULL, -+ V4L2_CID_PIXEL_RATE, 0, -+ OV5693_PIXEL_RATE, 1, -+ OV5693_PIXEL_RATE); - - if (ov5693->ctrl_handler.error) { - ov5693_remove(client); -@@ -1645,25 +1646,32 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - - /* Exposure */ - -- v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_EXPOSURE, 16, 1048575, 16, -- 512); -+ ov5693->ctrls.exposure = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -+ V4L2_CID_EXPOSURE, 16, -+ 1048575, 16, 512); - - /* Gain */ - -- v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_ANALOGUE_GAIN, 1, 1023, 1, 128); -- v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_DIGITAL_GAIN, 1, 3999, 1, 1000); -+ ov5693->ctrls.analogue_gain = v4l2_ctrl_new_std(&ov5693->ctrl_handler, -+ ops, V4L2_CID_ANALOGUE_GAIN, -+ 1, 1023, 1, 128); -+ ov5693->ctrls.digital_gain = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -+ V4L2_CID_DIGITAL_GAIN, 1, -+ 3999, 1, 1000); - - /* Flip */ - -- v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_HFLIP, 0, 1, 1, 0); -- v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_VFLIP, 0, 1, 1, 0); -+ ov5693->ctrls.hflip = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -+ V4L2_CID_HFLIP, 0, 1, 1, 0); -+ ov5693->ctrls.vflip = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -+ V4L2_CID_VFLIP, 0, 1, 1, 0); - - hblank = OV5693_PPL_DEFAULT - ov5693->mode->width; -- ov5693->hblank = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -- V4L2_CID_HBLANK, hblank, hblank, -- 1, hblank); -- if (ov5693->hblank) -- ov5693->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ ov5693->ctrls.hblank = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -+ V4L2_CID_HBLANK, hblank, hblank, -+ 1, hblank); -+ if (ov5693->ctrls.hblank) -+ ov5693->ctrls.hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; - - /* set properties from fwnode (e.g. rotation, orientation) */ - ret = v4l2_fwnode_device_parse(&client->dev, &props); -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 9a508e1f3624..26819cf3f4d2 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -270,7 +270,17 @@ struct ov5693_device { - - bool has_vcm; - -- struct v4l2_ctrl *hblank; -+ struct ov5693_v4l2_ctrls { -+ struct v4l2_ctrl *link_freq; -+ struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *exposure; -+ struct v4l2_ctrl *analogue_gain; -+ struct v4l2_ctrl *digital_gain; -+ struct v4l2_ctrl *hflip; -+ struct v4l2_ctrl *vflip; -+ struct v4l2_ctrl *hblank; -+ } ctrls; -+ - }; - - enum ov5693_tok_type { --- -2.33.0 - -From 5fc8a53c12d4549b0196f9cecac40c4a4f985873 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 8 Feb 2021 21:46:49 +0000 -Subject: [PATCH] media: i2c: Remove OV5693_PPL_DEFAULT - -No need for this macro, the PPL setting is against the mode structs. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 4 +--- - 1 file changed, 1 insertion(+), 3 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index a9747ab783d7..7fb368eec327 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -105,8 +105,6 @@ MODULE_PARM_DESC(up_delay, - #define OV5693_PIXEL_ARRAY_WIDTH 2592U - #define OV5693_PIXEL_ARRAY_HEIGHT 1944U - --#define OV5693_PPL_DEFAULT 2800 -- - static int vcm_ad_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) - { - int err; -@@ -1666,7 +1664,7 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - ov5693->ctrls.vflip = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, - V4L2_CID_VFLIP, 0, 1, 1, 0); - -- hblank = OV5693_PPL_DEFAULT - ov5693->mode->width; -+ hblank = ov5693->mode->pixels_per_line - ov5693->mode->width; - ov5693->ctrls.hblank = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, - V4L2_CID_HBLANK, hblank, hblank, - 1, hblank); --- -2.33.0 - -From 9b6b3a7cc2420c4b57591404ed70378e4cc7c237 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 8 Feb 2021 22:53:02 +0000 -Subject: [PATCH] media: i2c: Add vblank control to ov5693 driver - -The latest libcamera requires a V4L2_CID_VBLANK control in each sensor -driver; add a skeleton one to the ov5693 to fulfill the requirement. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 12 ++++++++++++ - drivers/media/i2c/ov5693.h | 3 +++ - 2 files changed, 15 insertions(+) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 7fb368eec327..1950d7ac2d54 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -946,6 +946,10 @@ static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - return ov5693_flip_horz_configure(dev, !!ctrl->val); - case V4L2_CID_VFLIP: - return ov5693_flip_vert_configure(dev, !!ctrl->val); -+ case V4L2_CID_VBLANK: -+ ret = ov5693_write_reg(client, OV5693_16BIT, OV5693_TIMING_VTS_H, -+ dev->mode->height + ctrl->val); -+ break; - default: - ret = -EINVAL; - } -@@ -1611,6 +1615,7 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - unsigned int i; - int ret; - int hblank; -+ int vblank_max, vblank_min, vblank_def; - - ret = v4l2_ctrl_handler_init(&ov5693->ctrl_handler, - ARRAY_SIZE(ov5693_controls)); -@@ -1671,6 +1676,13 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - if (ov5693->ctrls.hblank) - ov5693->ctrls.hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; - -+ vblank_max = OV5693_TIMING_MAX_VTS - ov5693->mode->height; -+ vblank_def = ov5693->mode->lines_per_frame - ov5693->mode->height; -+ vblank_min = ov5693->mode->lines_per_frame - ov5693->mode->height; -+ ov5693->ctrls.vblank = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -+ V4L2_CID_VBLANK, vblank_min, -+ vblank_max, 1, vblank_def); -+ - /* set properties from fwnode (e.g. rotation, orientation) */ - ret = v4l2_fwnode_device_parse(&client->dev, &props); - if (ret) -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 26819cf3f4d2..9d7eed97963b 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -131,6 +131,8 @@ - /*High 8-bit, and low 8-bit HTS address is 0x380f*/ - #define OV5693_TIMING_VTS_L 0x380f - -+#define OV5693_TIMING_MAX_VTS 0xffff -+ - #define OV5693_MWB_RED_GAIN_H 0x3400 - #define OV5693_MWB_GREEN_GAIN_H 0x3402 - #define OV5693_MWB_BLUE_GAIN_H 0x3404 -@@ -279,6 +281,7 @@ struct ov5693_device { - struct v4l2_ctrl *hflip; - struct v4l2_ctrl *vflip; - struct v4l2_ctrl *hblank; -+ struct v4l2_ctrl *vblank; - } ctrls; - - }; --- -2.33.0 - -From 32c9eeba5037cf58eafa718260a6e678efc52089 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 10 Feb 2021 00:36:32 +0000 -Subject: [PATCH] media: i2c: update exposure control for ov5693 - -The exposure control for ov5693 currently is in units of 1/16th of a line, -but I think the framework expects it in units of lines. Set the control to -work in lines and simply apply the multiplication when configuring the chip -registers instead. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 23 ++++++++++++++++++++--- - 1 file changed, 20 insertions(+), 3 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 1950d7ac2d54..cea767230aa9 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -801,6 +801,12 @@ static int ov5693_exposure_configure(struct ov5693_device *sensor, u32 exposure) - { - int ret; - -+ /* -+ * The control for exposure seems to be in units of lines, but the chip -+ * datasheet specifies exposure is in units of 1/16th of a line. -+ */ -+ exposure = exposure * 16; -+ - ov5693_get_exposure(sensor); - ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, - OV5693_EXPOSURE_CTRL_HH_REG, OV5693_EXPOSURE_CTRL_HH(exposure)); -@@ -910,6 +916,16 @@ static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - struct i2c_client *client = v4l2_get_subdevdata(&dev->sd); - int ret = 0; - -+ /* If VBLANK is altered we need to update exposure to compensate */ -+ if (ctrl->id == V4L2_CID_VBLANK) { -+ int exposure_max; -+ exposure_max = dev->mode->lines_per_frame - 8; -+ __v4l2_ctrl_modify_range(dev->ctrls.exposure, dev->ctrls.exposure->minimum, -+ exposure_max, dev->ctrls.exposure->step, -+ dev->ctrls.exposure->val < exposure_max ? -+ dev->ctrls.exposure->val : exposure_max); -+ } -+ - switch (ctrl->id) { - case V4L2_CID_FOCUS_ABSOLUTE: - dev_dbg(&client->dev, "%s: CID_FOCUS_ABSOLUTE:%d.\n", -@@ -1616,6 +1632,7 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - int ret; - int hblank; - int vblank_max, vblank_min, vblank_def; -+ int exposure_max; - - ret = v4l2_ctrl_handler_init(&ov5693->ctrl_handler, - ARRAY_SIZE(ov5693_controls)); -@@ -1648,10 +1665,10 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - } - - /* Exposure */ -- -+ exposure_max = ov5693->mode->lines_per_frame - 8; - ov5693->ctrls.exposure = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -- V4L2_CID_EXPOSURE, 16, -- 1048575, 16, 512); -+ V4L2_CID_EXPOSURE, 1, -+ exposure_max, 1, 123); - - /* Gain */ - --- -2.33.0 - -From 49678e1440330e97a36563a1f245fb52e7823e04 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 10 Feb 2021 00:39:42 +0000 -Subject: [PATCH] media: i2c: Fix incorrect bit-setting - -The bitmask macros to set the exposure for the ov5693 are not quite right. -Update them so that they're setting the correct bits in the registers. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index cea767230aa9..f681dbfcec56 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -63,11 +63,11 @@ MODULE_PARM_DESC(up_delay, - /* Exposure/gain */ - - #define OV5693_EXPOSURE_CTRL_HH_REG 0x3500 --#define OV5693_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(18, 16)) >> 16) -+#define OV5693_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(14, 12)) >> 12) - #define OV5693_EXPOSURE_CTRL_H_REG 0x3501 --#define OV5693_EXPOSURE_CTRL_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_EXPOSURE_CTRL_H(v) (((v) & GENMASK(11, 4)) >> 4) - #define OV5693_EXPOSURE_CTRL_L_REG 0x3502 --#define OV5693_EXPOSURE_CTRL_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_EXPOSURE_CTRL_L(v) (((v) & GENMASK(3, 0)) << 4) - #define OV5693_EXPOSURE_GAIN_MANUAL_REG 0x3509 - - #define OV5693_GAIN_CTRL_H_REG 0x3504 --- -2.33.0 - -From 0397bb78961d71bcf4cd0ff8ede80826d3ddd717 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 10 Feb 2021 16:25:48 +0000 -Subject: [PATCH] media: i2c: Don't set stream on during mode config - -Currently the register lists for the ov5693 include setting stream on. -That register shouldn't be set until the control is called, so remove -this setting from all of the modes. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.h | 16 ---------------- - 1 file changed, 16 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 9d7eed97963b..965208078c2b 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -581,7 +581,6 @@ static struct ov5693_reg const ov5693_654x496[] = { - {OV5693_8BIT, 0x3820, 0x04}, - {OV5693_8BIT, 0x3821, 0x1f}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -626,7 +625,6 @@ static struct ov5693_reg const ov5693_1296x976[] = { - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x00}, - {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - - }; -@@ -656,7 +654,6 @@ static struct ov5693_reg const ov5693_336x256[] = { - {OV5693_8BIT, 0x3820, 0x04}, - {OV5693_8BIT, 0x3821, 0x1f}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -683,7 +680,6 @@ static struct ov5693_reg const ov5693_368x304[] = { - {OV5693_8BIT, 0x3820, 0x04}, - {OV5693_8BIT, 0x3821, 0x1f}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -715,7 +711,6 @@ static struct ov5693_reg const ov5693_192x160[] = { - {OV5693_8BIT, 0x3820, 0x04}, - {OV5693_8BIT, 0x3821, 0x1f}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -742,7 +737,6 @@ static struct ov5693_reg const ov5693_736x496[] = { - {OV5693_8BIT, 0x3820, 0x04}, - {OV5693_8BIT, 0x3821, 0x1f}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - #endif -@@ -771,7 +765,6 @@ static struct ov5693_reg const ov5693_736x496[] = { - {OV5693_8BIT, 0x3820, 0x01}, - {OV5693_8BIT, 0x3821, 0x1f}, - {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - */ -@@ -802,7 +795,6 @@ static struct ov5693_reg const ov5693_976x556[] = { - {OV5693_8BIT, 0x3820, 0x00}, - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -841,7 +833,6 @@ static struct ov5693_reg const ov5693_1296x736[] = { - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x00}, - {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -868,7 +859,6 @@ static struct ov5693_reg const ov5693_1636p_30fps[] = { - {OV5693_8BIT, 0x3820, 0x00}, - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - #endif -@@ -904,7 +894,6 @@ static struct ov5693_reg const ov5693_1616x1216_30fps[] = { - {OV5693_8BIT, 0x3821, 0x1e}, /*MIRROR control*/ - {OV5693_8BIT, 0x5002, 0x00}, - {OV5693_8BIT, 0x5041, 0x84}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -935,7 +924,6 @@ static struct ov5693_reg const ov5693_1940x1096[] = { - {OV5693_8BIT, 0x3820, 0x00}, - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -1029,7 +1017,6 @@ static struct ov5693_reg const ov5693_2592x1944_30fps[] = { - {OV5693_8BIT, 0x3820, 0x00}, - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - #endif -@@ -1073,7 +1060,6 @@ static struct ov5693_reg const ov5693_1424x1168_30fps[] = { - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x00}, - {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - #endif -@@ -1114,7 +1100,6 @@ static struct ov5693_reg const ov5693_736x496_30fps[] = { - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x00}, - {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -1141,7 +1126,6 @@ static struct ov5693_reg const ov5693_2576x1936_30fps[] = { - {OV5693_8BIT, 0x3820, 0x00}, - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - --- -2.33.0 - -From 05691584e795bd16965cf2cc3b27ca0133e2ae7e Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 10 Feb 2021 16:35:24 +0000 -Subject: [PATCH] media: i2c: Update gain control for ov5693 - -The gain control of the ov5693 driver is setting the wrong bits and -defining an invalid maximum value; change (and use) the bitshifting -macros and update the control's ranges. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 18 +++++++++++------- - 1 file changed, 11 insertions(+), 7 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index f681dbfcec56..51eb3b05d121 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -71,9 +71,9 @@ MODULE_PARM_DESC(up_delay, - #define OV5693_EXPOSURE_GAIN_MANUAL_REG 0x3509 - - #define OV5693_GAIN_CTRL_H_REG 0x3504 --#define OV5693_GAIN_CTRL_H(v) (((v) & GENMASK(9, 8)) >> 8) -+#define OV5693_GAIN_CTRL_H(v) ((v >> 4) & GENMASK(2, 0)) - #define OV5693_GAIN_CTRL_L_REG 0x3505 --#define OV5693_GAIN_CTRL_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_GAIN_CTRL_L(v) ((v << 4) & GENMASK(7, 4)) - - #define OV5693_FORMAT1_REG 0x3820 - #define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -@@ -889,9 +889,13 @@ static int ov5693_analog_gain_configure(struct ov5693_device *sensor, u32 gain) - { - int ret; - -- /* Analog gain */ -+ /* -+ * As with exposure, the lowest 4 bits are fractional bits. Setting -+ * those is not supported, so we have a tiny bit of bit shifting to -+ * do. -+ */ - ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -- OV5693_AGC_L, gain & 0xff); -+ OV5693_AGC_L, OV5693_GAIN_CTRL_L(gain)); - if (ret) { - dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", - __func__, OV5693_AGC_L); -@@ -899,7 +903,7 @@ static int ov5693_analog_gain_configure(struct ov5693_device *sensor, u32 gain) - } - - ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -- OV5693_AGC_H, (gain >> 8) & 0xff); -+ OV5693_AGC_H, OV5693_GAIN_CTRL_H(gain)); - if (ret) { - dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", - __func__, OV5693_AGC_H); -@@ -1674,10 +1678,10 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - - ov5693->ctrls.analogue_gain = v4l2_ctrl_new_std(&ov5693->ctrl_handler, - ops, V4L2_CID_ANALOGUE_GAIN, -- 1, 1023, 1, 128); -+ 1, 127, 1, 8); - ov5693->ctrls.digital_gain = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, - V4L2_CID_DIGITAL_GAIN, 1, -- 3999, 1, 1000); -+ 4095, 1, 1024); - - /* Flip */ - --- -2.33.0 - -From 000757caef6c4e3162b28447cdb832659cf01b88 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 10 Feb 2021 23:44:39 +0000 -Subject: [PATCH] media: i2c: Fixup gain read - -This function reads the bits from the gain registers poorly. Update -it to do that properly (although, it probably just needs to be deleted) - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 17 +++++++++++------ - 1 file changed, 11 insertions(+), 6 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 51eb3b05d121..952558c4f33b 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -770,30 +770,35 @@ static int ov5693_t_focus_rel(struct v4l2_subdev *sd, s32 value) - - static int ov5693_get_exposure(struct ov5693_device *sensor) - { -- u16 reg_v, reg_v2; -+ u32 exposure = 0; -+ u16 tmp; - int ret = 0; - - /* get exposure */ - ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, - OV5693_EXPOSURE_L, -- ®_v); -+ &tmp); - if (ret) - return ret; - -+ exposure |= ((tmp >> 4) & 0b1111); -+ - ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, - OV5693_EXPOSURE_M, -- ®_v2); -+ &tmp); - if (ret) - return ret; - -- reg_v += reg_v2 << 8; -+ exposure |= (tmp << 4); - ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, - OV5693_EXPOSURE_H, -- ®_v2); -+ &tmp); - if (ret) - return ret; - -- printk("exposure set to: %u\n", reg_v + (((u32)reg_v2 << 16))); -+ exposure |= (tmp << 12); -+ -+ printk("exposure set to: %u\n", exposure); - return ret; - } - --- -2.33.0 - -From e34f12d5d1acd241e7b9eebd15fa0dc4c5ae692e Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 11 Feb 2021 00:40:10 +0000 -Subject: [PATCH] media: i2c: Update controls on stream - -Currently the ov5693 driver throws away control setting by simply loading -each mode's default registers. Instead, re-set the user defined controls -during stream with __v4l2_ctrl_handler_setup() - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 952558c4f33b..dd31083eeb7b 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1492,6 +1492,12 @@ static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) - } - } - -+ ret = __v4l2_ctrl_handler_setup(&dev->ctrl_handler); -+ if (ret) { -+ power_down(sd); -+ return ret; -+ } -+ - ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_SW_STREAM, - enable ? OV5693_START_STREAMING : - OV5693_STOP_STREAMING); --- -2.33.0 - -From bcf152f00576999a15a184e41818742dd01a0313 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 11 Feb 2021 23:29:15 +0000 -Subject: [PATCH] media: i2c: Correct link frequency value - -The link frequency is given by vts * hts * fps * bits / lanes / 2. In the -case of the ov5693 driver that works out to 400MHz, not 640Mhz. Correct -the macro. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.h | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 965208078c2b..7f1d31a82d3d 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -186,13 +186,13 @@ - #define OV5693_OTP_MODE_READ 1 - - /* link freq and pixel rate required for IPU3 */ --#define OV5693_LINK_FREQ_640MHZ 640000000 -+#define OV5693_LINK_FREQ_400MHZ 400000000 - /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample - * To avoid integer overflow, dividing by bits_per_sample first. - */ --#define OV5693_PIXEL_RATE (OV5693_LINK_FREQ_640MHZ / 10) * 2 * 2 -+#define OV5693_PIXEL_RATE (OV5693_LINK_FREQ_400MHZ / 10) * 2 * 2 - static const s64 link_freq_menu_items[] = { -- OV5693_LINK_FREQ_640MHZ -+ OV5693_LINK_FREQ_400MHZ - }; - - #define OV5693_NUM_SUPPLIES 2 --- -2.33.0 - -From e1d20f8442fdb17668d029b8726466810e297385 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 25 Jan 2021 23:12:09 +0000 -Subject: [PATCH] media: i2c: Cleanup ov5693 driver - -This commit performs some cleanup to the ov5693 driver: - -1. Superfluous words in variable names dropped; "i2c_client" becomes - "client", "input_lock" becomes "lock" -2. ov5693_configure_gpios() is does error handling properly, and uses - gpiod_get_optional() -3. The name of the struct ov5693_device variable in each functions, which - previously was a mix of dev, sensor or ov5693, is standardised to the - latter. -4. The list of headers is alphabetised (and probably also needs trimming) - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 309 +++++++++++++++++++------------------ - drivers/media/i2c/ov5693.h | 5 +- - 2 files changed, 165 insertions(+), 149 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index dd31083eeb7b..0643390c872a 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -16,25 +16,25 @@ - * - */ - -+#include - #include --#include --#include --#include --#include --#include -+#include -+#include - #include -+#include - #include -+#include -+#include - #include --#include --#include --#include --#include -+#include - #include -+#include -+#include -+#include -+#include -+#include - #include - #include --#include --#include --#include - - #include "ov5693.h" - #include "ad5823.h" -@@ -485,12 +485,12 @@ static int ov5693_read_otp_reg_array(struct i2c_client *client, u16 size, - static int __ov5693_otp_read(struct v4l2_subdev *sd, u8 *buf) - { - struct i2c_client *client = v4l2_get_subdevdata(sd); -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - int ret; - int i; - u8 *b = buf; - -- dev->otp_size = 0; -+ ov5693->otp_size = 0; - for (i = 1; i < OV5693_OTP_BANK_MAX; i++) { - /*set bank NO and OTP read mode. */ - ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_OTP_BANK_REG, -@@ -529,7 +529,7 @@ static int __ov5693_otp_read(struct v4l2_subdev *sd, u8 *buf) - //Intel OTP map, try to read 320byts first. - if (i == 21) { - if ((*b) == 0) { -- dev->otp_size = 320; -+ ov5693->otp_size = 320; - break; - } - /* (*b) != 0 */ -@@ -538,7 +538,7 @@ static int __ov5693_otp_read(struct v4l2_subdev *sd, u8 *buf) - } else if (i == - 24) { //if the first 320bytes data doesn't not exist, try to read the next 32bytes data. - if ((*b) == 0) { -- dev->otp_size = 32; -+ ov5693->otp_size = 32; - break; - } - /* (*b) != 0 */ -@@ -547,11 +547,11 @@ static int __ov5693_otp_read(struct v4l2_subdev *sd, u8 *buf) - } else if (i == - 27) { //if the prvious 32bytes data doesn't exist, try to read the next 32bytes data again. - if ((*b) == 0) { -- dev->otp_size = 32; -+ ov5693->otp_size = 32; - break; - } - /* (*b) != 0 */ -- dev->otp_size = 0; // no OTP data. -+ ov5693->otp_size = 0; // no OTP data. - break; - } - -@@ -598,20 +598,20 @@ static void *ov5693_otp_read(struct v4l2_subdev *sd) - return buf; - } - --static int ov5693_update_bits(struct ov5693_device *sensor, u16 address, -+static int ov5693_update_bits(struct ov5693_device *ov5693, u16 address, - u16 mask, u16 bits) - { - u16 value = 0; - int ret; - -- ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, address, &value); -+ ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, address, &value); - if (ret) - return ret; - - value &= ~mask; - value |= bits; - -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, address, value); -+ ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, address, value); - if (ret) - return ret; - -@@ -620,13 +620,13 @@ static int ov5693_update_bits(struct ov5693_device *sensor, u16 address, - - /* Flip */ - --static int ov5693_flip_vert_configure(struct ov5693_device *sensor, bool enable) -+static int ov5693_flip_vert_configure(struct ov5693_device *ov5693, bool enable) - { - u8 bits = OV5693_FORMAT1_FLIP_VERT_ISP_EN | - OV5693_FORMAT1_FLIP_VERT_SENSOR_EN; - int ret; - -- ret = ov5693_update_bits(sensor, OV5693_FORMAT1_REG, bits, -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, bits, - enable ? bits : 0); - if (ret) - return ret; -@@ -634,13 +634,13 @@ static int ov5693_flip_vert_configure(struct ov5693_device *sensor, bool enable) - return 0; - } - --static int ov5693_flip_horz_configure(struct ov5693_device *sensor, bool enable) -+static int ov5693_flip_horz_configure(struct ov5693_device *ov5693, bool enable) - { - u8 bits = OV5693_FORMAT2_FLIP_HORZ_ISP_EN | - OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN; - int ret; - -- ret = ov5693_update_bits(sensor, OV5693_FORMAT2_REG, bits, -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, bits, - enable ? bits : 0); - if (ret) - return ret; -@@ -721,14 +721,14 @@ static int ad5823_t_focus_abs(struct v4l2_subdev *sd, s32 value) - - static int ov5693_t_focus_abs(struct v4l2_subdev *sd, s32 value) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret = 0; - - dev_dbg(&client->dev, "%s: FOCUS_POS: 0x%x\n", __func__, value); - value = clamp(value, 0, OV5693_VCM_MAX_FOCUS_POS); -- if (dev->vcm == VCM_DW9714) { -- if (dev->vcm_update) { -+ if (ov5693->vcm == VCM_DW9714) { -+ if (ov5693->vcm_update) { - ret = vcm_dw_i2c_write(client, VCM_PROTECTION_OFF); - if (ret) - return ret; -@@ -738,17 +738,17 @@ static int ov5693_t_focus_abs(struct v4l2_subdev *sd, s32 value) - ret = vcm_dw_i2c_write(client, VCM_PROTECTION_ON); - if (ret) - return ret; -- dev->vcm_update = false; -+ ov5693->vcm_update = false; - } - ret = vcm_dw_i2c_write(client, - vcm_val(value, VCM_DEFAULT_S)); -- } else if (dev->vcm == VCM_AD5823) { -+ } else if (ov5693->vcm == VCM_AD5823) { - ad5823_t_focus_abs(sd, value); - } - if (ret == 0) { -- dev->number_of_steps = value - dev->focus; -- dev->focus = value; -- dev->timestamp_t_focus_abs = ktime_get(); -+ ov5693->number_of_steps = value - ov5693->focus; -+ ov5693->focus = value; -+ ov5693->timestamp_t_focus_abs = ktime_get(); - } else - dev_err(&client->dev, - "%s: i2c failed. ret %d\n", __func__, ret); -@@ -758,9 +758,9 @@ static int ov5693_t_focus_abs(struct v4l2_subdev *sd, s32 value) - - static int ov5693_t_focus_rel(struct v4l2_subdev *sd, s32 value) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - -- return ov5693_t_focus_abs(sd, dev->focus + value); -+ return ov5693_t_focus_abs(sd, ov5693->focus + value); - } - - #define DELAY_PER_STEP_NS 1000000 -@@ -768,14 +768,14 @@ static int ov5693_t_focus_rel(struct v4l2_subdev *sd, s32 value) - - /* Exposure */ - --static int ov5693_get_exposure(struct ov5693_device *sensor) -+static int ov5693_get_exposure(struct ov5693_device *ov5693) - { - u32 exposure = 0; - u16 tmp; - int ret = 0; - - /* get exposure */ -- ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, - OV5693_EXPOSURE_L, - &tmp); - if (ret) -@@ -783,14 +783,14 @@ static int ov5693_get_exposure(struct ov5693_device *sensor) - - exposure |= ((tmp >> 4) & 0b1111); - -- ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, - OV5693_EXPOSURE_M, - &tmp); - if (ret) - return ret; - - exposure |= (tmp << 4); -- ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, - OV5693_EXPOSURE_H, - &tmp); - if (ret) -@@ -802,7 +802,7 @@ static int ov5693_get_exposure(struct ov5693_device *sensor) - return ret; - } - --static int ov5693_exposure_configure(struct ov5693_device *sensor, u32 exposure) -+static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) - { - int ret; - -@@ -812,40 +812,40 @@ static int ov5693_exposure_configure(struct ov5693_device *sensor, u32 exposure) - */ - exposure = exposure * 16; - -- ov5693_get_exposure(sensor); -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ ov5693_get_exposure(ov5693); -+ ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, - OV5693_EXPOSURE_CTRL_HH_REG, OV5693_EXPOSURE_CTRL_HH(exposure)); - if (ret) - return ret; - -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, - OV5693_EXPOSURE_CTRL_H_REG, OV5693_EXPOSURE_CTRL_H(exposure)); - if (ret) - return ret; - -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, - OV5693_EXPOSURE_CTRL_L_REG, OV5693_EXPOSURE_CTRL_L(exposure)); - if (ret) - return ret; -- ov5693_get_exposure(sensor); -+ ov5693_get_exposure(ov5693); - - return 0; - } - - /* Gain */ - --static int ov5693_get_gain(struct ov5693_device *sensor, u32 *gain) -+static int ov5693_get_gain(struct ov5693_device *ov5693, u32 *gain) - { - u16 gain_l, gain_h; - int ret = 0; - -- ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, - OV5693_GAIN_CTRL_L_REG, - &gain_l); - if (ret) - return ret; - -- ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, - OV5693_GAIN_CTRL_H_REG, - &gain_h); - if (ret) -@@ -856,33 +856,33 @@ static int ov5693_get_gain(struct ov5693_device *sensor, u32 *gain) - - return ret; - } --static int ov5693_gain_configure(struct ov5693_device *sensor, u32 gain) -+static int ov5693_gain_configure(struct ov5693_device *ov5693, u32 gain) - { - int ret; - - /* A 1.0 gain is 0x400 */ - gain = (gain * 1024)/1000; - -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_16BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_16BIT, - OV5693_MWB_RED_GAIN_H, gain); - if (ret) { -- dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ dev_err(&ov5693->client->dev, "%s: write %x error, aborted\n", - __func__, OV5693_MWB_RED_GAIN_H); - return ret; - } - -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_16BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_16BIT, - OV5693_MWB_GREEN_GAIN_H, gain); - if (ret) { -- dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ dev_err(&ov5693->client->dev, "%s: write %x error, aborted\n", - __func__, OV5693_MWB_RED_GAIN_H); - return ret; - } - -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_16BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_16BIT, - OV5693_MWB_BLUE_GAIN_H, gain); - if (ret) { -- dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ dev_err(&ov5693->client->dev, "%s: write %x error, aborted\n", - __func__, OV5693_MWB_RED_GAIN_H); - return ret; - } -@@ -890,7 +890,7 @@ static int ov5693_gain_configure(struct ov5693_device *sensor, u32 gain) - return 0; - } - --static int ov5693_analog_gain_configure(struct ov5693_device *sensor, u32 gain) -+static int ov5693_analog_gain_configure(struct ov5693_device *ov5693, u32 gain) - { - int ret; - -@@ -899,18 +899,18 @@ static int ov5693_analog_gain_configure(struct ov5693_device *sensor, u32 gain) - * those is not supported, so we have a tiny bit of bit shifting to - * do. - */ -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, - OV5693_AGC_L, OV5693_GAIN_CTRL_L(gain)); - if (ret) { -- dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ dev_err(&ov5693->client->dev, "%s: write %x error, aborted\n", - __func__, OV5693_AGC_L); - return ret; - } - -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, - OV5693_AGC_H, OV5693_GAIN_CTRL_H(gain)); - if (ret) { -- dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ dev_err(&ov5693->client->dev, "%s: write %x error, aborted\n", - __func__, OV5693_AGC_H); - return ret; - } -@@ -920,60 +920,60 @@ static int ov5693_analog_gain_configure(struct ov5693_device *sensor, u32 gain) - - static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - { -- struct ov5693_device *dev = -+ struct ov5693_device *ov5693 = - container_of(ctrl->handler, struct ov5693_device, ctrl_handler); -- struct i2c_client *client = v4l2_get_subdevdata(&dev->sd); -+ struct i2c_client *client = v4l2_get_subdevdata(&ov5693->sd); - int ret = 0; - - /* If VBLANK is altered we need to update exposure to compensate */ - if (ctrl->id == V4L2_CID_VBLANK) { - int exposure_max; -- exposure_max = dev->mode->lines_per_frame - 8; -- __v4l2_ctrl_modify_range(dev->ctrls.exposure, dev->ctrls.exposure->minimum, -- exposure_max, dev->ctrls.exposure->step, -- dev->ctrls.exposure->val < exposure_max ? -- dev->ctrls.exposure->val : exposure_max); -+ exposure_max = ov5693->mode->lines_per_frame - 8; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, ov5693->ctrls.exposure->minimum, -+ exposure_max, ov5693->ctrls.exposure->step, -+ ov5693->ctrls.exposure->val < exposure_max ? -+ ov5693->ctrls.exposure->val : exposure_max); - } - - switch (ctrl->id) { - case V4L2_CID_FOCUS_ABSOLUTE: - dev_dbg(&client->dev, "%s: CID_FOCUS_ABSOLUTE:%d.\n", - __func__, ctrl->val); -- ret = ov5693_t_focus_abs(&dev->sd, ctrl->val); -+ ret = ov5693_t_focus_abs(&ov5693->sd, ctrl->val); - break; - case V4L2_CID_FOCUS_RELATIVE: - dev_dbg(&client->dev, "%s: CID_FOCUS_RELATIVE:%d.\n", - __func__, ctrl->val); -- ret = ov5693_t_focus_rel(&dev->sd, ctrl->val); -+ ret = ov5693_t_focus_rel(&ov5693->sd, ctrl->val); - break; - case V4L2_CID_EXPOSURE: - dev_dbg(&client->dev, "%s: CID_EXPOSURE:%d.\n", - __func__, ctrl->val); -- ret = ov5693_exposure_configure(dev, ctrl->val); -+ ret = ov5693_exposure_configure(ov5693, ctrl->val); - if (ret) - return ret; - break; - case V4L2_CID_ANALOGUE_GAIN: - dev_dbg(&client->dev, "%s: CID_ANALOGUE_GAIN:%d.\n", - __func__, ctrl->val); -- ret = ov5693_analog_gain_configure(dev, ctrl->val); -+ ret = ov5693_analog_gain_configure(ov5693, ctrl->val); - if (ret) - return ret; - break; - case V4L2_CID_DIGITAL_GAIN: - dev_dbg(&client->dev, "%s: CID_DIGITAL_GAIN:%d.\n", - __func__, ctrl->val); -- ret = ov5693_gain_configure(dev, ctrl->val); -+ ret = ov5693_gain_configure(ov5693, ctrl->val); - if (ret) - return ret; - break; - case V4L2_CID_HFLIP: -- return ov5693_flip_horz_configure(dev, !!ctrl->val); -+ return ov5693_flip_horz_configure(ov5693, !!ctrl->val); - case V4L2_CID_VFLIP: -- return ov5693_flip_vert_configure(dev, !!ctrl->val); -+ return ov5693_flip_vert_configure(ov5693, !!ctrl->val); - case V4L2_CID_VBLANK: - ret = ov5693_write_reg(client, OV5693_16BIT, OV5693_TIMING_VTS_H, -- dev->mode->height + ctrl->val); -+ ov5693->mode->height + ctrl->val); - break; - default: - ret = -EINVAL; -@@ -983,16 +983,16 @@ static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - - static int ov5693_g_volatile_ctrl(struct v4l2_ctrl *ctrl) - { -- struct ov5693_device *dev = -+ struct ov5693_device *ov5693 = - container_of(ctrl->handler, struct ov5693_device, ctrl_handler); - int ret = 0; - - switch (ctrl->id) { - case V4L2_CID_EXPOSURE_ABSOLUTE: -- ret = ov5693_q_exposure(&dev->sd, &ctrl->val); -+ ret = ov5693_q_exposure(&ov5693->sd, &ctrl->val); - break; - case V4L2_CID_AUTOGAIN: -- ret = ov5693_get_gain(dev, &ctrl->val); -+ ret = ov5693_get_gain(ov5693, &ctrl->val); - break; - case V4L2_CID_FOCUS_ABSOLUTE: - /* NOTE: there was atomisp-specific function ov5693_q_focus_abs() */ -@@ -1034,12 +1034,12 @@ static const struct v4l2_ctrl_config ov5693_controls[] = { - }, - }; - --static int ov5693_isp_configure(struct ov5693_device *sensor) -+static int ov5693_isp_configure(struct ov5693_device *ov5693) - { - int ret; - - /* Enable lens correction. */ -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, - OV5693_ISP_CTRL0_REG, 0x86); - if (ret) - return ret; -@@ -1049,18 +1049,18 @@ static int ov5693_isp_configure(struct ov5693_device *sensor) - - static int ov5693_init(struct v4l2_subdev *sd) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret; - -- if (!dev->has_vcm) -+ if (!ov5693->has_vcm) - return 0; - - dev_info(&client->dev, "%s\n", __func__); -- mutex_lock(&dev->input_lock); -- dev->vcm_update = false; -+ mutex_lock(&ov5693->lock); -+ ov5693->vcm_update = false; - -- if (dev->vcm == VCM_AD5823) { -+ if (ov5693->vcm == VCM_AD5823) { - ret = vcm_ad_i2c_wr8(client, 0x01, 0x01); /* vcm init test */ - if (ret) - dev_err(&client->dev, -@@ -1079,16 +1079,16 @@ static int ov5693_init(struct v4l2_subdev *sd) - } - - /*change initial focus value for ad5823*/ -- if (dev->vcm == VCM_AD5823) { -- dev->focus = AD5823_INIT_FOCUS_POS; -+ if (ov5693->vcm == VCM_AD5823) { -+ ov5693->focus = AD5823_INIT_FOCUS_POS; - ov5693_t_focus_abs(sd, AD5823_INIT_FOCUS_POS); - } else { -- dev->focus = 0; -+ ov5693->focus = 0; - ov5693_t_focus_abs(sd, 0); - } - -- ov5693_isp_configure(dev); -- mutex_unlock(&dev->input_lock); -+ ov5693_isp_configure(ov5693); -+ mutex_unlock(&ov5693->lock); - - return 0; - } -@@ -1096,32 +1096,32 @@ static int ov5693_init(struct v4l2_subdev *sd) - static int __power_up(struct v4l2_subdev *sd) - { - struct i2c_client *client = v4l2_get_subdevdata(sd); -- struct ov5693_device *sensor = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - int ret; - -- ret = clk_prepare_enable(sensor->clk); -+ ret = clk_prepare_enable(ov5693->clk); - if (ret) { - dev_err(&client->dev, "Error enabling clock\n"); - return -EINVAL; - } - -- if (sensor->indicator_led) -- gpiod_set_value_cansleep(sensor->indicator_led, 1); -+ if (ov5693->indicator_led) -+ gpiod_set_value_cansleep(ov5693->indicator_led, 1); - - ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, -- sensor->supplies); -+ ov5693->supplies); - if (ret) - goto fail_power; - -- gpiod_set_value_cansleep(sensor->reset, 0); -+ gpiod_set_value_cansleep(ov5693->reset, 0); - - __cci_delay(up_delay); - - return 0; - - fail_power: -- if (sensor->indicator_led) -- gpiod_set_value_cansleep(sensor->indicator_led, 0); -+ if (ov5693->indicator_led) -+ gpiod_set_value_cansleep(ov5693->indicator_led, 0); - dev_err(&client->dev, "sensor power-up failed\n"); - - return ret; -@@ -1129,17 +1129,17 @@ static int __power_up(struct v4l2_subdev *sd) - - static int power_down(struct v4l2_subdev *sd) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - -- dev->focus = OV5693_INVALID_CONFIG; -+ ov5693->focus = OV5693_INVALID_CONFIG; - -- gpiod_set_value_cansleep(dev->reset, 1); -+ gpiod_set_value_cansleep(ov5693->reset, 1); - -- clk_disable_unprepare(dev->clk); -+ clk_disable_unprepare(ov5693->clk); - -- if (dev->indicator_led) -- gpiod_set_value_cansleep(dev->indicator_led, 0); -- return regulator_bulk_disable(OV5693_NUM_SUPPLIES, dev->supplies); -+ if (ov5693->indicator_led) -+ gpiod_set_value_cansleep(ov5693->indicator_led, 0); -+ return regulator_bulk_disable(OV5693_NUM_SUPPLIES, ov5693->supplies); - } - - static int power_up(struct v4l2_subdev *sd) -@@ -1265,7 +1265,7 @@ static int get_resolution_index(int w, int h) - /* TODO: remove it. */ - static int startup(struct v4l2_subdev *sd) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret = 0; - -@@ -1282,7 +1282,7 @@ static int startup(struct v4l2_subdev *sd) - return ret; - } - -- ret = ov5693_write_reg_array(client, ov5693_res[dev->fmt_idx].regs); -+ ret = ov5693_write_reg_array(client, ov5693_res[ov5693->fmt_idx].regs); - if (ret) { - dev_err(&client->dev, "ov5693 write register err.\n"); - return ret; -@@ -1296,7 +1296,7 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_format *format) - { - struct v4l2_mbus_framefmt *fmt = &format->format; -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret = 0; - int idx; -@@ -1307,7 +1307,7 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - if (!fmt) - return -EINVAL; - -- mutex_lock(&dev->input_lock); -+ mutex_lock(&ov5693->lock); - idx = nearest_resolution_index(fmt->width, fmt->height); - if (idx == -1) { - /* return the largest resolution */ -@@ -1325,8 +1325,8 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - goto mutex_unlock; - } - -- dev->fmt_idx = get_resolution_index(fmt->width, fmt->height); -- if (dev->fmt_idx == -1) { -+ ov5693->fmt_idx = get_resolution_index(fmt->width, fmt->height); -+ if (ov5693->fmt_idx == -1) { - dev_err(&client->dev, "get resolution fail\n"); - ret = -EINVAL; - goto mutex_unlock; -@@ -1339,9 +1339,9 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - continue; - } - -- mutex_unlock(&dev->input_lock); -+ mutex_unlock(&ov5693->lock); - ov5693_init(sd); -- mutex_lock(&dev->input_lock); -+ mutex_lock(&ov5693->lock); - ret = startup(sd); - if (ret) - dev_err(&client->dev, " startup() FAILED!\n"); -@@ -1353,8 +1353,6 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - goto mutex_unlock; - } - -- -- - /* - * After sensor settings are set to HW, sometimes stream is started. - * This would cause ISP timeout because ISP is not ready to receive -@@ -1366,19 +1364,19 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - dev_warn(&client->dev, "ov5693 stream off err\n"); - - mutex_unlock: -- mutex_unlock(&dev->input_lock); -+ mutex_unlock(&ov5693->lock); - return ret; - } - - static const struct v4l2_rect * --__ov5693_get_pad_crop(struct ov5693_device *dev, struct v4l2_subdev_pad_config *cfg, -+__ov5693_get_pad_crop(struct ov5693_device *ov5693, struct v4l2_subdev_pad_config *cfg, - unsigned int pad, enum v4l2_subdev_format_whence which) - { - switch (which) { - case V4L2_SUBDEV_FORMAT_TRY: -- return v4l2_subdev_get_try_crop(&dev->sd, cfg, pad); -+ return v4l2_subdev_get_try_crop(&ov5693->sd, cfg, pad); - case V4L2_SUBDEV_FORMAT_ACTIVE: -- return &dev->mode->crop; -+ return &ov5693->mode->crop; - } - - return NULL; -@@ -1389,12 +1387,12 @@ static int ov5693_get_selection(struct v4l2_subdev *sd, - { - switch (sel->target) { - case V4L2_SEL_TGT_CROP: { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - -- mutex_lock(&dev->input_lock); -- sel->r = *__ov5693_get_pad_crop(dev, cfg, sel->pad, -+ mutex_lock(&ov5693->lock); -+ sel->r = *__ov5693_get_pad_crop(ov5693, cfg, sel->pad, - sel->which); -- mutex_unlock(&dev->input_lock); -+ mutex_unlock(&ov5693->lock); - - return 0; - } -@@ -1424,7 +1422,7 @@ static int ov5693_get_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_format *format) - { - struct v4l2_mbus_framefmt *fmt = &format->format; -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - - if (format->pad) - return -EINVAL; -@@ -1432,8 +1430,8 @@ static int ov5693_get_fmt(struct v4l2_subdev *sd, - if (!fmt) - return -EINVAL; - -- fmt->width = ov5693_res[dev->fmt_idx].width; -- fmt->height = ov5693_res[dev->fmt_idx].height; -+ fmt->width = ov5693_res[ov5693->fmt_idx].width; -+ fmt->height = ov5693_res[ov5693->fmt_idx].height; - fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; - - return 0; -@@ -1481,7 +1479,7 @@ static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret; - -- mutex_lock(&dev->input_lock); -+ mutex_lock(&dev->lock); - - /* power_on() here before streaming for regular PCs. */ - if (enable) { -@@ -1507,26 +1505,26 @@ static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) - power_down(sd); - - out: -- mutex_unlock(&dev->input_lock); -+ mutex_unlock(&dev->lock); - - return ret; - } - - static int ov5693_s_config(struct v4l2_subdev *sd, int irq) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret = 0; - -- mutex_lock(&dev->input_lock); -+ mutex_lock(&ov5693->lock); - ret = power_up(sd); - if (ret) { - dev_err(&client->dev, "ov5693 power-up err.\n"); - goto fail_power_on; - } - -- if (!dev->vcm) -- dev->vcm = vcm_detect(client); -+ if (!ov5693->vcm) -+ ov5693->vcm = vcm_detect(client); - - /* config & detect sensor */ - ret = ov5693_detect(client); -@@ -1535,7 +1533,7 @@ static int ov5693_s_config(struct v4l2_subdev *sd, int irq) - goto fail_power_on; - } - -- dev->otp_data = ov5693_otp_read(sd); -+ ov5693->otp_data = ov5693_otp_read(sd); - - /* turn off sensor, after probed */ - ret = power_down(sd); -@@ -1543,24 +1541,24 @@ static int ov5693_s_config(struct v4l2_subdev *sd, int irq) - dev_err(&client->dev, "ov5693 power-off err.\n"); - goto fail_power_on; - } -- mutex_unlock(&dev->input_lock); -+ mutex_unlock(&ov5693->lock); - - return ret; - - fail_power_on: - power_down(sd); - dev_err(&client->dev, "sensor power-gating failed\n"); -- mutex_unlock(&dev->input_lock); -+ mutex_unlock(&ov5693->lock); - return ret; - } - - static int ov5693_g_frame_interval(struct v4l2_subdev *sd, - struct v4l2_subdev_frame_interval *interval) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - - interval->interval.numerator = 1; -- interval->interval.denominator = ov5693_res[dev->fmt_idx].fps; -+ interval->interval.denominator = ov5693_res[ov5693->fmt_idx].fps; - - return 0; - } -@@ -1725,7 +1723,7 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - return ret; - - /* Use same lock for controls as for everything else. */ -- ov5693->ctrl_handler.lock = &ov5693->input_lock; -+ ov5693->ctrl_handler.lock = &ov5693->lock; - ov5693->sd.ctrl_handler = &ov5693->ctrl_handler; - - return 0; -@@ -1733,21 +1731,38 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - - static int ov5693_configure_gpios(struct ov5693_device *ov5693) - { -- ov5693->reset = gpiod_get_index(&ov5693->i2c_client->dev, "reset", 0, -+ int ret; -+ -+ ov5693->reset = gpiod_get_optional(&ov5693->client->dev, "reset", - GPIOD_OUT_HIGH); - if (IS_ERR(ov5693->reset)) { -- dev_err(&ov5693->i2c_client->dev, "Couldn't find reset GPIO\n"); -- return -EINVAL; -+ dev_err(&ov5693->client->dev, "Couldn't find reset GPIO\n"); -+ return PTR_ERR(ov5693->reset); -+ } -+ -+ ov5693->powerdown = gpiod_get_optional(&ov5693->client->dev, "powerdown", -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->powerdown)) { -+ dev_err(&ov5693->client->dev, "Couldn't find powerdown GPIO\n"); -+ ret = PTR_ERR(ov5693->powerdown); -+ goto err_put_reset; - } - -- ov5693->indicator_led = gpiod_get_index_optional(&ov5693->i2c_client->dev, "indicator-led", 0, -+ ov5693->indicator_led = gpiod_get_optional(&ov5693->client->dev, "indicator-led", - GPIOD_OUT_HIGH); - if (IS_ERR(ov5693->indicator_led)) { -- dev_err(&ov5693->i2c_client->dev, "Couldn't find indicator-led GPIO\n"); -- return -EINVAL; -+ dev_err(&ov5693->client->dev, "Couldn't find indicator-led GPIO\n"); -+ ret = PTR_ERR(ov5693->indicator_led); -+ goto err_put_powerdown; - } - - return 0; -+err_put_reset: -+ gpiod_put(ov5693->reset); -+err_put_powerdown: -+ gpiod_put(ov5693->powerdown); -+ -+ return ret; - } - - static int ov5693_get_regulators(struct ov5693_device *ov5693) -@@ -1757,7 +1772,7 @@ static int ov5693_get_regulators(struct ov5693_device *ov5693) - for (i = 0; i < OV5693_NUM_SUPPLIES; i++) - ov5693->supplies[i].supply = ov5693_supply_names[i]; - -- return regulator_bulk_get(&ov5693->i2c_client->dev, -+ return regulator_bulk_get(&ov5693->client->dev, - OV5693_NUM_SUPPLIES, - ov5693->supplies); - } -@@ -1773,13 +1788,13 @@ static int ov5693_probe(struct i2c_client *client) - if (!ov5693) - return -ENOMEM; - -- ov5693->i2c_client = client; -+ ov5693->client = client; - - /* check if VCM device exists */ - /* TODO: read from SSDB */ - ov5693->has_vcm = false; - -- mutex_init(&ov5693->input_lock); -+ mutex_init(&ov5693->lock); - - v4l2_i2c_subdev_init(&ov5693->sd, client, &ov5693_ops); - -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 7f1d31a82d3d..70ccb3aae4c7 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -241,14 +241,15 @@ enum vcm_type { - * ov5693 device structure. - */ - struct ov5693_device { -- struct i2c_client *i2c_client; -+ struct i2c_client *client; - struct v4l2_subdev sd; - struct media_pad pad; - struct v4l2_mbus_framefmt format; -- struct mutex input_lock; -+ struct mutex lock; - struct v4l2_ctrl_handler ctrl_handler; - - struct gpio_desc *reset; -+ struct gpio_desc *powerdown; - struct gpio_desc *indicator_led; - struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; - struct clk *clk; --- -2.33.0 - -From 00545008dfbfb2e4447504e41561beedfb9ccdf0 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 28 Jan 2021 12:04:38 +0000 -Subject: [PATCH] media: i2c: Add pm_runtime support to ov5693 driver - -The ov5693 driver currently uses hacky and horrible power up/down methods -called directly in s_stream. Add pm_runtime support and use that in -s_stream instead. Replace all other uses of the power+up/down() calls with -the single ov5693_sensor_stream() for now. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 183 +++++++++++++++++++++++++++++-------- - drivers/media/i2c/ov5693.h | 1 + - 2 files changed, 146 insertions(+), 38 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 0643390c872a..f2eaa5f71a31 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -29,6 +29,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -935,6 +936,10 @@ static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - ov5693->ctrls.exposure->val : exposure_max); - } - -+ /* Only apply changes to the controls if the device is powered up */ -+ if (!pm_runtime_get_if_in_use(&ov5693->client->dev)) -+ return 0; -+ - switch (ctrl->id) { - case V4L2_CID_FOCUS_ABSOLUTE: - dev_dbg(&client->dev, "%s: CID_FOCUS_ABSOLUTE:%d.\n", -@@ -950,27 +955,23 @@ static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - dev_dbg(&client->dev, "%s: CID_EXPOSURE:%d.\n", - __func__, ctrl->val); - ret = ov5693_exposure_configure(ov5693, ctrl->val); -- if (ret) -- return ret; - break; - case V4L2_CID_ANALOGUE_GAIN: - dev_dbg(&client->dev, "%s: CID_ANALOGUE_GAIN:%d.\n", - __func__, ctrl->val); - ret = ov5693_analog_gain_configure(ov5693, ctrl->val); -- if (ret) -- return ret; - break; - case V4L2_CID_DIGITAL_GAIN: - dev_dbg(&client->dev, "%s: CID_DIGITAL_GAIN:%d.\n", - __func__, ctrl->val); - ret = ov5693_gain_configure(ov5693, ctrl->val); -- if (ret) -- return ret; - break; - case V4L2_CID_HFLIP: -- return ov5693_flip_horz_configure(ov5693, !!ctrl->val); -+ ret = ov5693_flip_horz_configure(ov5693, !!ctrl->val); -+ break; - case V4L2_CID_VFLIP: -- return ov5693_flip_vert_configure(ov5693, !!ctrl->val); -+ ret = ov5693_flip_vert_configure(ov5693, !!ctrl->val); -+ break; - case V4L2_CID_VBLANK: - ret = ov5693_write_reg(client, OV5693_16BIT, OV5693_TIMING_VTS_H, - ov5693->mode->height + ctrl->val); -@@ -978,6 +979,9 @@ static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - default: - ret = -EINVAL; - } -+ -+ pm_runtime_put(&ov5693->client->dev); -+ - return ret; - } - -@@ -1093,6 +1097,106 @@ static int ov5693_init(struct v4l2_subdev *sd) - return 0; - } - -+static int ov5693_sw_standby(struct ov5693_device *ov5693, bool standby) -+{ -+ return ov5693_write_reg(ov5693->client, OV5693_8BIT, OV5693_SW_STREAM, -+ standby ? OV5693_STOP_STREAMING : OV5693_START_STREAMING); -+} -+ -+static void ov5693_sensor_powerdown(struct ov5693_device *ov5693) -+{ -+ gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); -+ -+ regulator_bulk_disable(OV5693_NUM_SUPPLIES, ov5693->supplies); -+ -+ clk_disable_unprepare(ov5693->clk); -+ gpiod_set_value_cansleep(ov5693->indicator_led, 0); -+} -+ -+ -+static int ov5693_sensor_powerup(struct ov5693_device *ov5693) -+{ -+ int ret = 0; -+ -+ gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); -+ -+ ret = clk_prepare_enable(ov5693->clk); -+ if (ret) { -+ dev_err(&ov5693->client->dev, "Failed to enable clk\n"); -+ goto fail_power; -+ } -+ -+ ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, ov5693->supplies); -+ if (ret) { -+ dev_err(&ov5693->client->dev, "Failed to enable regulators\n"); -+ goto fail_power; -+ } -+ -+ gpiod_set_value_cansleep(ov5693->reset, 0); -+ gpiod_set_value_cansleep(ov5693->powerdown, 0); -+ gpiod_set_value_cansleep(ov5693->indicator_led, 1); -+ -+ usleep_range(20000, 25000); -+ -+ return 0; -+ -+fail_power: -+ ov5693_sensor_powerdown(ov5693); -+ return ret; -+} -+ -+static int __maybe_unused ov5693_sensor_suspend(struct device *dev) -+{ -+ struct i2c_client *client = i2c_verify_client(dev); -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ int ret; -+ -+ mutex_lock(&ov5693->lock); -+ -+ if (ov5693->streaming) { -+ ret = ov5693_sw_standby(ov5693, true); -+ if (ret) -+ goto out_unlock; -+ } -+ -+ ov5693_sensor_powerdown(ov5693); -+ -+out_unlock: -+ mutex_unlock(&ov5693->lock); -+ return ret; -+} -+ -+static int __maybe_unused ov5693_sensor_resume(struct device *dev) -+{ -+ struct i2c_client *client = i2c_verify_client(dev); -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ int ret; -+ -+ mutex_lock(&ov5693->lock); -+ -+ ret = ov5693_sensor_powerup(ov5693); -+ if (ret) -+ goto out_unlock; -+ -+ if (ov5693->streaming) { -+ ret = ov5693_sw_standby(ov5693, false); -+ if (ret) -+ goto err_power; -+ } -+ -+ goto out_unlock; -+ -+err_power: -+ ov5693_sensor_powerdown(ov5693); -+out_unlock: -+ mutex_unlock(&ov5693->lock); -+ return ret; -+} -+ - static int __power_up(struct v4l2_subdev *sd) - { - struct i2c_client *client = v4l2_get_subdevdata(sd); -@@ -1134,6 +1238,7 @@ static int power_down(struct v4l2_subdev *sd) - ov5693->focus = OV5693_INVALID_CONFIG; - - gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); - - clk_disable_unprepare(ov5693->clk); - -@@ -1333,7 +1438,7 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - } - - for (cnt = 0; cnt < OV5693_POWER_UP_RETRY_NUM; cnt++) { -- ret = power_up(sd); -+ ret = ov5693_sensor_powerup(ov5693); - if (ret) { - dev_err(&client->dev, "power up failed\n"); - continue; -@@ -1475,38 +1580,34 @@ static int ov5693_detect(struct i2c_client *client) - - static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -- struct i2c_client *client = v4l2_get_subdevdata(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - int ret; - -- mutex_lock(&dev->lock); -- -- /* power_on() here before streaming for regular PCs. */ - if (enable) { -- ret = power_up(sd); -- if (ret) { -- dev_err(&client->dev, "sensor power-up error\n"); -- goto out; -- } -+ ret = pm_runtime_get_sync(&ov5693->client->dev); -+ if (ret < 0) -+ goto err_power_down; - } - -- ret = __v4l2_ctrl_handler_setup(&dev->ctrl_handler); -- if (ret) { -- power_down(sd); -- return ret; -- } -+ ret = __v4l2_ctrl_handler_setup(&ov5693->ctrl_handler); -+ if (ret) -+ goto err_power_down; - -- ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_SW_STREAM, -- enable ? OV5693_START_STREAMING : -- OV5693_STOP_STREAMING); -+ mutex_lock(&ov5693->lock); -+ ret = ov5693_sw_standby(ov5693, !enable); -+ mutex_unlock(&ov5693->lock); -+ -+ if (ret) -+ goto err_power_down; -+ ov5693->streaming = !!enable; - - /* power_off() here after streaming for regular PCs. */ - if (!enable) -- power_down(sd); -- --out: -- mutex_unlock(&dev->lock); -+ pm_runtime_put(&ov5693->client->dev); - -+ return 0; -+err_power_down: -+ pm_runtime_put_noidle(&ov5693->client->dev); - return ret; - } - -@@ -1517,7 +1618,7 @@ static int ov5693_s_config(struct v4l2_subdev *sd, int irq) - int ret = 0; - - mutex_lock(&ov5693->lock); -- ret = power_up(sd); -+ ret = ov5693_sensor_powerup(ov5693); - if (ret) { - dev_err(&client->dev, "ov5693 power-up err.\n"); - goto fail_power_on; -@@ -1536,17 +1637,14 @@ static int ov5693_s_config(struct v4l2_subdev *sd, int irq) - ov5693->otp_data = ov5693_otp_read(sd); - - /* turn off sensor, after probed */ -- ret = power_down(sd); -- if (ret) { -- dev_err(&client->dev, "ov5693 power-off err.\n"); -- goto fail_power_on; -- } -+ ov5693_sensor_powerdown(ov5693); -+ - mutex_unlock(&ov5693->lock); - - return ret; - - fail_power_on: -- power_down(sd); -+ ov5693_sensor_powerdown(ov5693); - dev_err(&client->dev, "sensor power-gating failed\n"); - mutex_unlock(&ov5693->lock); - return ret; -@@ -1830,6 +1928,9 @@ static int ov5693_probe(struct i2c_client *client) - if (ret) - ov5693_remove(client); - -+ pm_runtime_enable(&client->dev); -+ pm_runtime_set_suspended(&client->dev); -+ - ret = v4l2_async_register_subdev_sensor_common(&ov5693->sd); - if (ret) { - dev_err(&client->dev, "failed to register V4L2 subdev: %d", ret); -@@ -1839,6 +1940,7 @@ static int ov5693_probe(struct i2c_client *client) - return ret; - - media_entity_cleanup: -+ pm_runtime_disable(&client->dev); - media_entity_cleanup(&ov5693->sd.entity); - out_put_reset: - gpiod_put(ov5693->reset); -@@ -1848,6 +1950,10 @@ static int ov5693_probe(struct i2c_client *client) - return ret; - } - -+static const struct dev_pm_ops ov5693_pm_ops = { -+ SET_RUNTIME_PM_OPS(ov5693_sensor_suspend, ov5693_sensor_resume, NULL) -+}; -+ - static const struct acpi_device_id ov5693_acpi_match[] = { - {"INT33BE"}, - {}, -@@ -1858,6 +1964,7 @@ static struct i2c_driver ov5693_driver = { - .driver = { - .name = "ov5693", - .acpi_match_table = ov5693_acpi_match, -+ .pm = &ov5693_pm_ops, - }, - .probe_new = ov5693_probe, - .remove = ov5693_remove, -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 70ccb3aae4c7..b78d3b474a43 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -256,6 +256,7 @@ struct ov5693_device { - - /* Current mode */ - const struct ov5693_resolution *mode; -+ bool streaming; - - struct camera_sensor_platform_data *platform_data; - ktime_t timestamp_t_focus_abs; --- -2.33.0 - -From 0a057ee3ecd60dd5eb0d8ba418c226cf772cb72b Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 28 Jan 2021 12:07:36 +0000 -Subject: [PATCH] media: i2c: Remove old power methods from ov5693 - -Now that we have replaced the power_up/down() methods with a unified -function and pm_runtime support, we can remove these old methods from the -driver entirely along with some macros and a header. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 114 ------------------------------------- - 1 file changed, 114 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index f2eaa5f71a31..ce26ce86fbd5 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -27,7 +27,6 @@ - #include - #include - #include --#include - #include - #include - #include -@@ -40,27 +39,6 @@ - #include "ov5693.h" - #include "ad5823.h" - --#define __cci_delay(t) \ -- do { \ -- if ((t) < 10) { \ -- usleep_range((t) * 1000, ((t) + 1) * 1000); \ -- } else { \ -- msleep((t)); \ -- } \ -- } while (0) -- --/* Value 30ms reached through experimentation on byt ecs. -- * The DS specifies a much lower value but when using a smaller value -- * the I2C bus sometimes locks up permanently when starting the camera. -- * This issue could not be reproduced on cht, so we can reduce the -- * delay value to a lower value when insmod. -- */ --static uint up_delay = 30; --module_param(up_delay, uint, 0644); --MODULE_PARM_DESC(up_delay, -- "Delay prior to the first CCI transaction for ov5693"); -- -- - /* Exposure/gain */ - - #define OV5693_EXPOSURE_CTRL_HH_REG 0x3500 -@@ -1197,93 +1175,6 @@ static int __maybe_unused ov5693_sensor_resume(struct device *dev) - return ret; - } - --static int __power_up(struct v4l2_subdev *sd) --{ -- struct i2c_client *client = v4l2_get_subdevdata(sd); -- struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- int ret; -- -- ret = clk_prepare_enable(ov5693->clk); -- if (ret) { -- dev_err(&client->dev, "Error enabling clock\n"); -- return -EINVAL; -- } -- -- if (ov5693->indicator_led) -- gpiod_set_value_cansleep(ov5693->indicator_led, 1); -- -- ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, -- ov5693->supplies); -- if (ret) -- goto fail_power; -- -- gpiod_set_value_cansleep(ov5693->reset, 0); -- -- __cci_delay(up_delay); -- -- return 0; -- --fail_power: -- if (ov5693->indicator_led) -- gpiod_set_value_cansleep(ov5693->indicator_led, 0); -- dev_err(&client->dev, "sensor power-up failed\n"); -- -- return ret; --} -- --static int power_down(struct v4l2_subdev *sd) --{ -- struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- -- ov5693->focus = OV5693_INVALID_CONFIG; -- -- gpiod_set_value_cansleep(ov5693->reset, 1); -- gpiod_set_value_cansleep(ov5693->powerdown, 1); -- -- clk_disable_unprepare(ov5693->clk); -- -- if (ov5693->indicator_led) -- gpiod_set_value_cansleep(ov5693->indicator_led, 0); -- return regulator_bulk_disable(OV5693_NUM_SUPPLIES, ov5693->supplies); --} -- --static int power_up(struct v4l2_subdev *sd) --{ -- static const int retry_count = 4; -- int i, ret; -- -- for (i = 0; i < retry_count; i++) { -- ret = __power_up(sd); -- if (!ret) -- return 0; -- -- power_down(sd); -- } -- return ret; --} -- --static int ov5693_s_power(struct v4l2_subdev *sd, int on) --{ -- struct i2c_client *client = v4l2_get_subdevdata(sd); -- int ret; -- -- dev_info(&client->dev, "%s: on %d\n", __func__, on); -- -- if (on == 0) -- return power_down(sd); -- -- /* on == 1 */ -- ret = power_up(sd); -- if (!ret) { -- ret = ov5693_init(sd); -- /* restore settings */ -- ov5693_res = ov5693_res_video; -- N_RES = N_RES_VIDEO; -- } -- -- return ret; --} -- - /* - * distance - calculate the distance - * @res: resolution -@@ -1694,10 +1585,6 @@ static const struct v4l2_subdev_video_ops ov5693_video_ops = { - .g_frame_interval = ov5693_g_frame_interval, - }; - --static const struct v4l2_subdev_core_ops ov5693_core_ops = { -- .s_power = ov5693_s_power, --}; -- - static const struct v4l2_subdev_pad_ops ov5693_pad_ops = { - .enum_mbus_code = ov5693_enum_mbus_code, - .enum_frame_size = ov5693_enum_frame_size, -@@ -1707,7 +1594,6 @@ static const struct v4l2_subdev_pad_ops ov5693_pad_ops = { - }; - - static const struct v4l2_subdev_ops ov5693_ops = { -- .core = &ov5693_core_ops, - .video = &ov5693_video_ops, - .pad = &ov5693_pad_ops, - }; --- -2.33.0 - -From 1cce1c2b2913dde59e9c2083bdfb8fac1946df44 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 28 Jan 2021 12:14:00 +0000 -Subject: [PATCH] media: i2c: Trim unused headers from ov5693 - -The ov5693 driver includes a ton of unecessary headers, -trim the list down. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 8 -------- - 1 file changed, 8 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index ce26ce86fbd5..b3b391a49fdb 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -20,19 +20,11 @@ - #include - #include - #include --#include - #include --#include --#include --#include --#include - #include --#include - #include - #include - #include --#include --#include - #include - #include - --- -2.33.0 - -From 9a9284056a8c43d857e95e9748164a23ecd2597d Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 13 Feb 2021 21:39:35 +0000 -Subject: [PATCH] media: i2c: Remove VCM stuff - -This all needs binning, since we have no idea if it's right. It needs to -be moved to a driver for the VCM device I guess. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 325 +------------------------------------ - 1 file changed, 1 insertion(+), 324 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index b3b391a49fdb..2c82b6578de9 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -76,72 +76,6 @@ - #define OV5693_PIXEL_ARRAY_WIDTH 2592U - #define OV5693_PIXEL_ARRAY_HEIGHT 1944U - --static int vcm_ad_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) --{ -- int err; -- struct i2c_msg msg; -- u8 buf[2]; -- -- buf[0] = reg; -- buf[1] = val; -- -- msg.addr = VCM_ADDR; -- msg.flags = 0; -- msg.len = 2; -- msg.buf = &buf[0]; -- -- err = i2c_transfer(client->adapter, &msg, 1); -- if (err != 1) { -- dev_err(&client->dev, "%s: vcm i2c fail, err code = %d\n", -- __func__, err); -- return -EIO; -- } -- return 0; --} -- --static int ad5823_i2c_write(struct i2c_client *client, u8 reg, u8 val) --{ -- struct i2c_msg msg; -- u8 buf[2]; -- -- buf[0] = reg; -- buf[1] = val; -- msg.addr = AD5823_VCM_ADDR; -- msg.flags = 0; -- msg.len = 0x02; -- msg.buf = &buf[0]; -- -- if (i2c_transfer(client->adapter, &msg, 1) != 1) -- return -EIO; -- return 0; --} -- --static int ad5823_i2c_read(struct i2c_client *client, u8 reg, u8 *val) --{ -- struct i2c_msg msg[2]; -- u8 buf[2]; -- -- buf[0] = reg; -- buf[1] = 0; -- -- msg[0].addr = AD5823_VCM_ADDR; -- msg[0].flags = 0; -- msg[0].len = 0x01; -- msg[0].buf = &buf[0]; -- -- msg[1].addr = 0x0c; -- msg[1].flags = I2C_M_RD; -- msg[1].len = 0x01; -- msg[1].buf = &buf[1]; -- *val = 0; -- if (i2c_transfer(client->adapter, msg, 2) != 2) -- return -EIO; -- *val = buf[1]; -- return 0; --} -- --static const u32 ov5693_embedded_effective_size = 28; -- - /* i2c read/write stuff */ - static int ov5693_read_reg(struct i2c_client *client, - u16 data_length, u16 reg, u16 *val) -@@ -215,69 +149,6 @@ static int ov5693_i2c_write(struct i2c_client *client, u16 len, u8 *data) - return ret == num_msg ? 0 : -EIO; - } - --static int vcm_dw_i2c_write(struct i2c_client *client, u16 data) --{ -- struct i2c_msg msg; -- const int num_msg = 1; -- int ret; -- __be16 val; -- -- val = cpu_to_be16(data); -- msg.addr = VCM_ADDR; -- msg.flags = 0; -- msg.len = OV5693_16BIT; -- msg.buf = (void *)&val; -- -- ret = i2c_transfer(client->adapter, &msg, 1); -- -- return ret == num_msg ? 0 : -EIO; --} -- --/* -- * Theory: per datasheet, the two VCMs both allow for a 2-byte read. -- * The DW9714 doesn't actually specify what this does (it has a -- * two-byte write-only protocol, but specifies the read sequence as -- * legal), but it returns the same data (zeroes) always, after an -- * undocumented initial NAK. The AD5823 has a one-byte address -- * register to which all writes go, and subsequent reads will cycle -- * through the 8 bytes of registers. Notably, the default values (the -- * device is always power-cycled affirmatively, so we can rely on -- * these) in AD5823 are not pairwise repetitions of the same 16 bit -- * word. So all we have to do is sequentially read two bytes at a -- * time and see if we detect a difference in any of the first four -- * pairs. -- */ --static int vcm_detect(struct i2c_client *client) --{ -- int i, ret; -- struct i2c_msg msg; -- u16 data0 = 0, data; -- -- for (i = 0; i < 4; i++) { -- msg.addr = VCM_ADDR; -- msg.flags = I2C_M_RD; -- msg.len = sizeof(data); -- msg.buf = (u8 *)&data; -- ret = i2c_transfer(client->adapter, &msg, 1); -- -- /* -- * DW9714 always fails the first read and returns -- * zeroes for subsequent ones -- */ -- if (i == 0 && ret == -EREMOTEIO) { -- data0 = 0; -- continue; -- } -- -- if (i == 0) -- data0 = data; -- -- if (data != data0) -- return VCM_AD5823; -- } -- return ret == 1 ? VCM_DW9714 : ret; --} -- - static int ov5693_write_reg(struct i2c_client *client, u16 data_length, - u16 reg, u16 val) - { -@@ -654,89 +525,6 @@ static int ov5693_q_exposure(struct v4l2_subdev *sd, s32 *value) - return ret; - } - --static int ad5823_t_focus_vcm(struct v4l2_subdev *sd, u16 val) --{ -- struct i2c_client *client = v4l2_get_subdevdata(sd); -- int ret = -EINVAL; -- u8 vcm_code; -- -- ret = ad5823_i2c_read(client, AD5823_REG_VCM_CODE_MSB, &vcm_code); -- if (ret) -- return ret; -- -- /* set reg VCM_CODE_MSB Bit[1:0] */ -- vcm_code = (vcm_code & VCM_CODE_MSB_MASK) | -- ((val >> 8) & ~VCM_CODE_MSB_MASK); -- ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_MSB, vcm_code); -- if (ret) -- return ret; -- -- /* set reg VCM_CODE_LSB Bit[7:0] */ -- ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_LSB, (val & 0xff)); -- if (ret) -- return ret; -- -- /* set required vcm move time */ -- vcm_code = AD5823_RESONANCE_PERIOD / AD5823_RESONANCE_COEF -- - AD5823_HIGH_FREQ_RANGE; -- ret = ad5823_i2c_write(client, AD5823_REG_VCM_MOVE_TIME, vcm_code); -- -- return ret; --} -- --static int ad5823_t_focus_abs(struct v4l2_subdev *sd, s32 value) --{ -- value = min(value, AD5823_MAX_FOCUS_POS); -- return ad5823_t_focus_vcm(sd, value); --} -- --static int ov5693_t_focus_abs(struct v4l2_subdev *sd, s32 value) --{ -- struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- struct i2c_client *client = v4l2_get_subdevdata(sd); -- int ret = 0; -- -- dev_dbg(&client->dev, "%s: FOCUS_POS: 0x%x\n", __func__, value); -- value = clamp(value, 0, OV5693_VCM_MAX_FOCUS_POS); -- if (ov5693->vcm == VCM_DW9714) { -- if (ov5693->vcm_update) { -- ret = vcm_dw_i2c_write(client, VCM_PROTECTION_OFF); -- if (ret) -- return ret; -- ret = vcm_dw_i2c_write(client, DIRECT_VCM); -- if (ret) -- return ret; -- ret = vcm_dw_i2c_write(client, VCM_PROTECTION_ON); -- if (ret) -- return ret; -- ov5693->vcm_update = false; -- } -- ret = vcm_dw_i2c_write(client, -- vcm_val(value, VCM_DEFAULT_S)); -- } else if (ov5693->vcm == VCM_AD5823) { -- ad5823_t_focus_abs(sd, value); -- } -- if (ret == 0) { -- ov5693->number_of_steps = value - ov5693->focus; -- ov5693->focus = value; -- ov5693->timestamp_t_focus_abs = ktime_get(); -- } else -- dev_err(&client->dev, -- "%s: i2c failed. ret %d\n", __func__, ret); -- -- return ret; --} -- --static int ov5693_t_focus_rel(struct v4l2_subdev *sd, s32 value) --{ -- struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- -- return ov5693_t_focus_abs(sd, ov5693->focus + value); --} -- --#define DELAY_PER_STEP_NS 1000000 --#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) -- - /* Exposure */ - - static int ov5693_get_exposure(struct ov5693_device *ov5693) -@@ -911,16 +699,6 @@ static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - return 0; - - switch (ctrl->id) { -- case V4L2_CID_FOCUS_ABSOLUTE: -- dev_dbg(&client->dev, "%s: CID_FOCUS_ABSOLUTE:%d.\n", -- __func__, ctrl->val); -- ret = ov5693_t_focus_abs(&ov5693->sd, ctrl->val); -- break; -- case V4L2_CID_FOCUS_RELATIVE: -- dev_dbg(&client->dev, "%s: CID_FOCUS_RELATIVE:%d.\n", -- __func__, ctrl->val); -- ret = ov5693_t_focus_rel(&ov5693->sd, ctrl->val); -- break; - case V4L2_CID_EXPOSURE: - dev_dbg(&client->dev, "%s: CID_EXPOSURE:%d.\n", - __func__, ctrl->val); -@@ -983,90 +761,6 @@ static const struct v4l2_ctrl_ops ov5693_ctrl_ops = { - .g_volatile_ctrl = ov5693_g_volatile_ctrl - }; - --static const struct v4l2_ctrl_config ov5693_controls[] = { -- { -- .ops = &ov5693_ctrl_ops, -- .id = V4L2_CID_FOCUS_ABSOLUTE, -- .type = V4L2_CTRL_TYPE_INTEGER, -- .name = "focus move absolute", -- .min = 0, -- .max = OV5693_VCM_MAX_FOCUS_POS, -- .step = 1, -- .def = 0, -- .flags = 0, -- }, -- { -- .ops = &ov5693_ctrl_ops, -- .id = V4L2_CID_FOCUS_RELATIVE, -- .type = V4L2_CTRL_TYPE_INTEGER, -- .name = "focus move relative", -- .min = OV5693_VCM_MAX_FOCUS_NEG, -- .max = OV5693_VCM_MAX_FOCUS_POS, -- .step = 1, -- .def = 0, -- .flags = 0, -- }, --}; -- --static int ov5693_isp_configure(struct ov5693_device *ov5693) --{ -- int ret; -- -- /* Enable lens correction. */ -- ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, -- OV5693_ISP_CTRL0_REG, 0x86); -- if (ret) -- return ret; -- -- return 0; --} -- --static int ov5693_init(struct v4l2_subdev *sd) --{ -- struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- struct i2c_client *client = v4l2_get_subdevdata(sd); -- int ret; -- -- if (!ov5693->has_vcm) -- return 0; -- -- dev_info(&client->dev, "%s\n", __func__); -- mutex_lock(&ov5693->lock); -- ov5693->vcm_update = false; -- -- if (ov5693->vcm == VCM_AD5823) { -- ret = vcm_ad_i2c_wr8(client, 0x01, 0x01); /* vcm init test */ -- if (ret) -- dev_err(&client->dev, -- "vcm reset failed\n"); -- /*change the mode*/ -- ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_MSB, -- AD5823_RING_CTRL_ENABLE); -- if (ret) -- dev_err(&client->dev, -- "vcm enable ringing failed\n"); -- ret = ad5823_i2c_write(client, AD5823_REG_MODE, -- AD5823_ARC_RES1); -- if (ret) -- dev_err(&client->dev, -- "vcm change mode failed\n"); -- } -- -- /*change initial focus value for ad5823*/ -- if (ov5693->vcm == VCM_AD5823) { -- ov5693->focus = AD5823_INIT_FOCUS_POS; -- ov5693_t_focus_abs(sd, AD5823_INIT_FOCUS_POS); -- } else { -- ov5693->focus = 0; -- ov5693_t_focus_abs(sd, 0); -- } -- -- ov5693_isp_configure(ov5693); -- mutex_unlock(&ov5693->lock); -- -- return 0; --} -- - static int ov5693_sw_standby(struct ov5693_device *ov5693, bool standby) - { - return ov5693_write_reg(ov5693->client, OV5693_8BIT, OV5693_SW_STREAM, -@@ -1327,9 +1021,6 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - continue; - } - -- mutex_unlock(&ov5693->lock); -- ov5693_init(sd); -- mutex_lock(&ov5693->lock); - ret = startup(sd); - if (ret) - dev_err(&client->dev, " startup() FAILED!\n"); -@@ -1507,9 +1198,6 @@ static int ov5693_s_config(struct v4l2_subdev *sd, int irq) - goto fail_power_on; - } - -- if (!ov5693->vcm) -- ov5693->vcm = vcm_detect(client); -- - /* config & detect sensor */ - ret = ov5693_detect(client); - if (ret) { -@@ -1617,24 +1305,17 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - struct i2c_client *client = v4l2_get_subdevdata(&ov5693->sd); - const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; - struct v4l2_fwnode_device_properties props; -- unsigned int i; - int ret; - int hblank; - int vblank_max, vblank_min, vblank_def; - int exposure_max; - -- ret = v4l2_ctrl_handler_init(&ov5693->ctrl_handler, -- ARRAY_SIZE(ov5693_controls)); -+ ret = v4l2_ctrl_handler_init(&ov5693->ctrl_handler, 8); - if (ret) { - ov5693_remove(client); - return ret; - } - -- for (i = 0; i < ARRAY_SIZE(ov5693_controls); i++) -- v4l2_ctrl_new_custom(&ov5693->ctrl_handler, -- &ov5693_controls[i], -- NULL); -- - /* link freq */ - ov5693->ctrls.link_freq = v4l2_ctrl_new_int_menu(&ov5693->ctrl_handler, - NULL, V4L2_CID_LINK_FREQ, -@@ -1766,10 +1447,6 @@ static int ov5693_probe(struct i2c_client *client) - - ov5693->client = client; - -- /* check if VCM device exists */ -- /* TODO: read from SSDB */ -- ov5693->has_vcm = false; -- - mutex_init(&ov5693->lock); - - v4l2_i2c_subdev_init(&ov5693->sd, client, &ov5693_ops); --- -2.33.0 - -From b52dadcfd79b26724cf0dee8c22649dffbc766a9 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 13 Feb 2021 22:16:08 +0000 -Subject: [PATCH] media: i2c: Tidy up ov5693 sensor init - -The initialisation of a mode when the sensor is activated is a bit messy, -so lets tidy that up a bit to bring it in line with other drivers. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 100 ++++++++++++++++--------------------- - 1 file changed, 42 insertions(+), 58 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 2c82b6578de9..313bc9177328 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -767,6 +767,42 @@ static int ov5693_sw_standby(struct ov5693_device *ov5693, bool standby) - standby ? OV5693_STOP_STREAMING : OV5693_START_STREAMING); - } - -+static int ov5693_sw_reset(struct ov5693_device *ov5693) -+{ -+ return ov5693_write_reg(ov5693->client, OV5693_8BIT, OV5693_SW_RESET, -+ 0x01); -+} -+ -+static int ov5693_sensor_init(struct ov5693_device *ov5693) -+{ -+ struct i2c_client *client = ov5693->client; -+ int ret = 0; -+ -+ ret = ov5693_sw_reset(ov5693); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 reset err.\n"); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg_array(client, ov5693_global_setting); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 write register err.\n"); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg_array(client, ov5693_res[ov5693->fmt_idx].regs); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 write register err.\n"); -+ return ret; -+ } -+ -+ ret = ov5693_sw_standby(ov5693, true); -+ if (ret) -+ dev_err(&client->dev, "ov5693 stream off error\n"); -+ -+ return ret; -+} -+ - static void ov5693_sensor_powerdown(struct ov5693_device *ov5693) - { - gpiod_set_value_cansleep(ov5693->reset, 1); -@@ -846,6 +882,12 @@ static int __maybe_unused ov5693_sensor_resume(struct device *dev) - if (ret) - goto out_unlock; - -+ ret = ov5693_sensor_init(ov5693); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 sensor init failure\n"); -+ goto err_power; -+ } -+ - if (ov5693->streaming) { - ret = ov5693_sw_standby(ov5693, false); - if (ret) -@@ -944,35 +986,6 @@ static int get_resolution_index(int w, int h) - return -1; - } - --/* TODO: remove it. */ --static int startup(struct v4l2_subdev *sd) --{ -- struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- struct i2c_client *client = v4l2_get_subdevdata(sd); -- int ret = 0; -- -- ret = ov5693_write_reg(client, OV5693_8BIT, -- OV5693_SW_RESET, 0x01); -- if (ret) { -- dev_err(&client->dev, "ov5693 reset err.\n"); -- return ret; -- } -- -- ret = ov5693_write_reg_array(client, ov5693_global_setting); -- if (ret) { -- dev_err(&client->dev, "ov5693 write register err.\n"); -- return ret; -- } -- -- ret = ov5693_write_reg_array(client, ov5693_res[ov5693->fmt_idx].regs); -- if (ret) { -- dev_err(&client->dev, "ov5693 write register err.\n"); -- return ret; -- } -- -- return ret; --} -- - static int ov5693_set_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *format) -@@ -982,7 +995,6 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret = 0; - int idx; -- int cnt; - - if (format->pad) - return -EINVAL; -@@ -1014,34 +1026,6 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - goto mutex_unlock; - } - -- for (cnt = 0; cnt < OV5693_POWER_UP_RETRY_NUM; cnt++) { -- ret = ov5693_sensor_powerup(ov5693); -- if (ret) { -- dev_err(&client->dev, "power up failed\n"); -- continue; -- } -- -- ret = startup(sd); -- if (ret) -- dev_err(&client->dev, " startup() FAILED!\n"); -- else -- break; -- } -- if (cnt == OV5693_POWER_UP_RETRY_NUM) { -- dev_err(&client->dev, "power up failed, gave up\n"); -- goto mutex_unlock; -- } -- -- /* -- * After sensor settings are set to HW, sometimes stream is started. -- * This would cause ISP timeout because ISP is not ready to receive -- * data yet. So add stop streaming here. -- */ -- ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_SW_STREAM, -- OV5693_STOP_STREAMING); -- if (ret) -- dev_warn(&client->dev, "ov5693 stream off err\n"); -- - mutex_unlock: - mutex_unlock(&ov5693->lock); - return ret; --- -2.33.0 - -From b0e47f06039b2b73908b044681c9d311edf1fe5e Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 12 Feb 2021 16:14:04 +0000 -Subject: [PATCH] media: i2c: cleanup macros in ov5693.h - -Lots of orphaned or duplicated macros in this header file. Clean -those up a bit so it's less ugly. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.h | 89 +------------------------------------- - 1 file changed, 2 insertions(+), 87 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index b78d3b474a43..6502777eb5f3 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -37,68 +37,23 @@ - */ - #define ENABLE_NON_PREVIEW 1 - --#define OV5693_POWER_UP_RETRY_NUM 5 -- - /* Defines for register writes and register array processing */ --#define I2C_MSG_LENGTH 0x2 --#define I2C_RETRY_COUNT 5 -- --#define OV5693_FOCAL_LENGTH_NUM 334 /*3.34mm*/ --#define OV5693_FOCAL_LENGTH_DEM 100 --#define OV5693_F_NUMBER_DEFAULT_NUM 24 --#define OV5693_F_NUMBER_DEM 10 -+#define I2C_MSG_LENGTH 0x2 - - #define MAX_FMTS 1 - --/* sensor_mode_data read_mode adaptation */ --#define OV5693_READ_MODE_BINNING_ON 0x0400 --#define OV5693_READ_MODE_BINNING_OFF 0x00 --#define OV5693_INTEGRATION_TIME_MARGIN 8 -- --#define OV5693_MAX_EXPOSURE_VALUE 0xFFF1 --#define OV5693_MAX_GAIN_VALUE 0xFF -- --/* -- * focal length bits definition: -- * bits 31-16: numerator, bits 15-0: denominator -- */ --#define OV5693_FOCAL_LENGTH_DEFAULT 0x1B70064 -- --/* -- * current f-number bits definition: -- * bits 31-16: numerator, bits 15-0: denominator -- */ --#define OV5693_F_NUMBER_DEFAULT 0x18000a -- --/* -- * f-number range bits definition: -- * bits 31-24: max f-number numerator -- * bits 23-16: max f-number denominator -- * bits 15-8: min f-number numerator -- * bits 7-0: min f-number denominator -- */ --#define OV5693_F_NUMBER_RANGE 0x180a180a - #define OV5693_ID 0x5690 - --#define OV5693_FINE_INTG_TIME_MIN 0 --#define OV5693_FINE_INTG_TIME_MAX_MARGIN 0 --#define OV5693_COARSE_INTG_TIME_MIN 1 --#define OV5693_COARSE_INTG_TIME_MAX_MARGIN 6 -- --#define OV5693_BIN_FACTOR_MAX 4 - /* - * OV5693 System control registers - */ --#define OV5693_SW_SLEEP 0x0100 - #define OV5693_SW_RESET 0x0103 - #define OV5693_SW_STREAM 0x0100 - - #define OV5693_SC_CMMN_CHIP_ID_H 0x300A - #define OV5693_SC_CMMN_CHIP_ID_L 0x300B --#define OV5693_SC_CMMN_SCCB_ID 0x300C - #define OV5693_SC_CMMN_SUB_ID 0x302A /* process, version*/ --/*Bit[7:4] Group control, Bit[3:0] Group ID*/ --#define OV5693_GROUP_ACCESS 0x3208 -+ - /* - *Bit[3:0] Bit[19:16] of exposure, - *remaining 16 bits lies in Reg0x3501&Reg0x3502 -@@ -110,18 +65,6 @@ - #define OV5693_AGC_H 0x350A - #define OV5693_AGC_L 0x350B /*Bit[7:0] of gain*/ - --#define OV5693_HORIZONTAL_START_H 0x3800 /*Bit[11:8]*/ --#define OV5693_HORIZONTAL_START_L 0x3801 /*Bit[7:0]*/ --#define OV5693_VERTICAL_START_H 0x3802 /*Bit[11:8]*/ --#define OV5693_VERTICAL_START_L 0x3803 /*Bit[7:0]*/ --#define OV5693_HORIZONTAL_END_H 0x3804 /*Bit[11:8]*/ --#define OV5693_HORIZONTAL_END_L 0x3805 /*Bit[7:0]*/ --#define OV5693_VERTICAL_END_H 0x3806 /*Bit[11:8]*/ --#define OV5693_VERTICAL_END_L 0x3807 /*Bit[7:0]*/ --#define OV5693_HORIZONTAL_OUTPUT_SIZE_H 0x3808 /*Bit[3:0]*/ --#define OV5693_HORIZONTAL_OUTPUT_SIZE_L 0x3809 /*Bit[7:0]*/ --#define OV5693_VERTICAL_OUTPUT_SIZE_H 0x380a /*Bit[3:0]*/ --#define OV5693_VERTICAL_OUTPUT_SIZE_L 0x380b /*Bit[7:0]*/ - /*High 8-bit, and low 8-bit HTS address is 0x380d*/ - #define OV5693_TIMING_HTS_H 0x380C - /*High 8-bit, and low 8-bit HTS address is 0x380d*/ -@@ -141,34 +84,6 @@ - #define OV5693_START_STREAMING 0x01 - #define OV5693_STOP_STREAMING 0x00 - --#define VCM_ADDR 0x0c --#define VCM_CODE_MSB 0x04 -- --#define OV5693_INVALID_CONFIG 0xffffffff -- --#define OV5693_VCM_SLEW_STEP 0x30F0 --#define OV5693_VCM_SLEW_STEP_MAX 0x7 --#define OV5693_VCM_SLEW_STEP_MASK 0x7 --#define OV5693_VCM_CODE 0x30F2 --#define OV5693_VCM_SLEW_TIME 0x30F4 --#define OV5693_VCM_SLEW_TIME_MAX 0xffff --#define OV5693_VCM_ENABLE 0x8000 -- --#define OV5693_VCM_MAX_FOCUS_NEG -1023 --#define OV5693_VCM_MAX_FOCUS_POS 1023 -- --#define DLC_ENABLE 1 --#define DLC_DISABLE 0 --#define VCM_PROTECTION_OFF 0xeca3 --#define VCM_PROTECTION_ON 0xdc51 --#define VCM_DEFAULT_S 0x0 --#define vcm_step_s(a) (u8)(a & 0xf) --#define vcm_step_mclk(a) (u8)((a >> 4) & 0x3) --#define vcm_dlc_mclk(dlc, mclk) (u16)((dlc << 3) | mclk | 0xa104) --#define vcm_tsrc(tsrc) (u16)(tsrc << 3 | 0xf200) --#define vcm_val(data, s) (u16)(data << 4 | s) --#define DIRECT_VCM vcm_dlc_mclk(0, 0) -- - /* Defines for OTP Data Registers */ - #define OV5693_FRAME_OFF_NUM 0x4202 - #define OV5693_OTP_BYTE_MAX 32 //change to 32 as needed by otpdata --- -2.33.0 - -From 07688f98d5cf042a675581a0d50543379b280c18 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 12 Feb 2021 16:19:09 +0000 -Subject: [PATCH] media: i2c: use devm_kzalloc() to initialise ov5693 - -There's a memory leak in probe because we're not using devres; swtich -so that we are. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 313bc9177328..d092ed698eb3 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1425,7 +1425,7 @@ static int ov5693_probe(struct i2c_client *client) - - dev_info(&client->dev, "%s() called", __func__); - -- ov5693 = kzalloc(sizeof(*ov5693), GFP_KERNEL); -+ ov5693 = devm_kzalloc(&client->dev, sizeof(*ov5693), GFP_KERNEL); - if (!ov5693) - return -ENOMEM; - --- -2.33.0 - -From 227f50c100aa3939be34d4cee2b2eaedd8e6eb32 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 12 Feb 2021 16:26:21 +0000 -Subject: [PATCH] media: i2c: Check for supported clk rate in probe - -The ov5693 driver is configured to support a 19.2MHz external clock only. -Check that we do indeed have that value and if not, exit with -EINVAL. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 8 ++++++++ - drivers/media/i2c/ov5693.h | 2 ++ - 2 files changed, 10 insertions(+) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index d092ed698eb3..8082d37841da 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1421,6 +1421,7 @@ static int ov5693_get_regulators(struct ov5693_device *ov5693) - static int ov5693_probe(struct i2c_client *client) - { - struct ov5693_device *ov5693; -+ u32 clk_rate; - int ret = 0; - - dev_info(&client->dev, "%s() called", __func__); -@@ -1441,6 +1442,13 @@ static int ov5693_probe(struct i2c_client *client) - return -EINVAL; - } - -+ clk_rate = clk_get_rate(ov5693->clk); -+ if (clk_rate != OV5693_XVCLK_FREQ) { -+ dev_err(&client->dev, "Unsupported clk freq %u, expected %u\n", -+ clk_rate, OV5693_XVCLK_FREQ); -+ return -EINVAL; -+ } -+ - ret = ov5693_configure_gpios(ov5693); - if (ret) - goto out_free; -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 6502777eb5f3..0dfbbe9a0ff2 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -100,6 +100,8 @@ - #define OV5693_OTP_READ_ONETIME 16 - #define OV5693_OTP_MODE_READ 1 - -+#define OV5693_XVCLK_FREQ 19200000 -+ - /* link freq and pixel rate required for IPU3 */ - #define OV5693_LINK_FREQ_400MHZ 400000000 - /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample --- -2.33.0 - -From 5a0cbf1fec50b3cbcf2ffdfedb8a28c00f15310a Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 13 Feb 2021 23:17:50 +0000 -Subject: [PATCH] media: i2c: Use devres to fetch gpios - -Use devres; it'll simplify error handling through this function -and probe. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 22 +++++----------------- - 1 file changed, 5 insertions(+), 17 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 8082d37841da..c580159079d2 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1270,8 +1270,6 @@ static int ov5693_remove(struct i2c_client *client) - - dev_info(&client->dev, "%s...\n", __func__); - -- gpiod_put(ov5693->reset); -- gpiod_put(ov5693->indicator_led); - while (i--) - regulator_put(ov5693->supplies[i].consumer); - -@@ -1372,38 +1370,28 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - - static int ov5693_configure_gpios(struct ov5693_device *ov5693) - { -- int ret; -- -- ov5693->reset = gpiod_get_optional(&ov5693->client->dev, "reset", -+ ov5693->reset = devm_gpiod_get_optional(&ov5693->client->dev, "reset", - GPIOD_OUT_HIGH); - if (IS_ERR(ov5693->reset)) { - dev_err(&ov5693->client->dev, "Couldn't find reset GPIO\n"); - return PTR_ERR(ov5693->reset); - } - -- ov5693->powerdown = gpiod_get_optional(&ov5693->client->dev, "powerdown", -+ ov5693->powerdown = devm_gpiod_get_optional(&ov5693->client->dev, "powerdown", - GPIOD_OUT_HIGH); - if (IS_ERR(ov5693->powerdown)) { - dev_err(&ov5693->client->dev, "Couldn't find powerdown GPIO\n"); -- ret = PTR_ERR(ov5693->powerdown); -- goto err_put_reset; -+ return PTR_ERR(ov5693->powerdown); - } - -- ov5693->indicator_led = gpiod_get_optional(&ov5693->client->dev, "indicator-led", -+ ov5693->indicator_led = devm_gpiod_get_optional(&ov5693->client->dev, "indicator-led", - GPIOD_OUT_HIGH); - if (IS_ERR(ov5693->indicator_led)) { - dev_err(&ov5693->client->dev, "Couldn't find indicator-led GPIO\n"); -- ret = PTR_ERR(ov5693->indicator_led); -- goto err_put_powerdown; -+ return PTR_ERR(ov5693->indicator_led); - } - - return 0; --err_put_reset: -- gpiod_put(ov5693->reset); --err_put_powerdown: -- gpiod_put(ov5693->powerdown); -- -- return ret; - } - - static int ov5693_get_regulators(struct ov5693_device *ov5693) --- -2.33.0 - -From fb99f6001c7ffec79ece7a941fbc9b85acd9e180 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 13 Feb 2021 23:20:47 +0000 -Subject: [PATCH] media: i2c: Use devres to fetch regulators - -As before, use devres to simplify error handling and driver removal - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 6 +----- - 1 file changed, 1 insertion(+), 5 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index c580159079d2..9f61b470f8ba 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1266,13 +1266,9 @@ static int ov5693_remove(struct i2c_client *client) - { - struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- unsigned int i = OV5693_NUM_SUPPLIES; - - dev_info(&client->dev, "%s...\n", __func__); - -- while (i--) -- regulator_put(ov5693->supplies[i].consumer); -- - v4l2_async_unregister_subdev(sd); - - media_entity_cleanup(&ov5693->sd.entity); -@@ -1401,7 +1397,7 @@ static int ov5693_get_regulators(struct ov5693_device *ov5693) - for (i = 0; i < OV5693_NUM_SUPPLIES; i++) - ov5693->supplies[i].supply = ov5693_supply_names[i]; - -- return regulator_bulk_get(&ov5693->client->dev, -+ return devm_regulator_bulk_get(&ov5693->client->dev, - OV5693_NUM_SUPPLIES, - ov5693->supplies); - } --- -2.33.0 - -From 89bcec14cf2e9d0ee4dc3938bda2cb8377007007 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 14 Feb 2021 12:39:14 +0000 -Subject: [PATCH] media: i2c: remove debug print - -The exposure configure function has a debug print. It's working fine, -so bin it. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 37 ------------------------------------- - 1 file changed, 37 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 9f61b470f8ba..622a7ddf4063 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -526,41 +526,6 @@ static int ov5693_q_exposure(struct v4l2_subdev *sd, s32 *value) - } - - /* Exposure */ -- --static int ov5693_get_exposure(struct ov5693_device *ov5693) --{ -- u32 exposure = 0; -- u16 tmp; -- int ret = 0; -- -- /* get exposure */ -- ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, -- OV5693_EXPOSURE_L, -- &tmp); -- if (ret) -- return ret; -- -- exposure |= ((tmp >> 4) & 0b1111); -- -- ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, -- OV5693_EXPOSURE_M, -- &tmp); -- if (ret) -- return ret; -- -- exposure |= (tmp << 4); -- ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, -- OV5693_EXPOSURE_H, -- &tmp); -- if (ret) -- return ret; -- -- exposure |= (tmp << 12); -- -- printk("exposure set to: %u\n", exposure); -- return ret; --} -- - static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) - { - int ret; -@@ -571,7 +536,6 @@ static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) - */ - exposure = exposure * 16; - -- ov5693_get_exposure(ov5693); - ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, - OV5693_EXPOSURE_CTRL_HH_REG, OV5693_EXPOSURE_CTRL_HH(exposure)); - if (ret) -@@ -586,7 +550,6 @@ static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) - OV5693_EXPOSURE_CTRL_L_REG, OV5693_EXPOSURE_CTRL_L(exposure)); - if (ret) - return ret; -- ov5693_get_exposure(ov5693); - - return 0; - } --- -2.33.0 - -From 20db0005a194231baa4b02dde2133631072add3c Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 14 Feb 2021 14:32:50 +0000 -Subject: [PATCH] media: i2c: Remove unused resolutions from ov5693 - -The list of resolutions in here is really unmaintanably long. For now just -bin all of the ones that are not part of ov5693_res_video, which is the -only array of resolutions in use anyway. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.h | 357 +------------------------------------ - 1 file changed, 1 insertion(+), 356 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 0dfbbe9a0ff2..29e6735112da 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -474,34 +474,7 @@ static struct ov5693_reg const ov5693_global_setting[] = { - }; - - #if ENABLE_NON_PREVIEW --/* -- * 654x496 30fps 17ms VBlanking 2lane 10Bit (Scaling) -- */ --static struct ov5693_reg const ov5693_654x496[] = { -- {OV5693_8BIT, 0x3501, 0x3d}, -- {OV5693_8BIT, 0x3502, 0x00}, -- {OV5693_8BIT, 0x3708, 0xe6}, -- {OV5693_8BIT, 0x3709, 0xc7}, -- {OV5693_8BIT, 0x3803, 0x00}, -- {OV5693_8BIT, 0x3806, 0x07}, -- {OV5693_8BIT, 0x3807, 0xa3}, -- {OV5693_8BIT, 0x3808, 0x02}, -- {OV5693_8BIT, 0x3809, 0x90}, -- {OV5693_8BIT, 0x380a, 0x01}, -- {OV5693_8BIT, 0x380b, 0xf0}, -- {OV5693_8BIT, 0x380c, 0x0a}, -- {OV5693_8BIT, 0x380d, 0x80}, -- {OV5693_8BIT, 0x380e, 0x07}, -- {OV5693_8BIT, 0x380f, 0xc0}, -- {OV5693_8BIT, 0x3811, 0x08}, -- {OV5693_8BIT, 0x3813, 0x02}, -- {OV5693_8BIT, 0x3814, 0x31}, -- {OV5693_8BIT, 0x3815, 0x31}, -- {OV5693_8BIT, 0x3820, 0x04}, -- {OV5693_8BIT, 0x3821, 0x1f}, -- {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_TOK_TERM, 0, 0} --}; -+ - - /* - * 1296x976 30fps 17ms VBlanking 2lane 10Bit (Scaling) -@@ -660,62 +633,10 @@ static struct ov5693_reg const ov5693_736x496[] = { - }; - #endif - --/* --static struct ov5693_reg const ov5693_736x496[] = { -- {OV5693_8BIT, 0x3501, 0x7b}, -- {OV5693_8BIT, 0x3502, 0x00}, -- {OV5693_8BIT, 0x3708, 0xe6}, -- {OV5693_8BIT, 0x3709, 0xc3}, -- {OV5693_8BIT, 0x3803, 0x00}, -- {OV5693_8BIT, 0x3806, 0x07}, -- {OV5693_8BIT, 0x3807, 0xa3}, -- {OV5693_8BIT, 0x3808, 0x02}, -- {OV5693_8BIT, 0x3809, 0xe0}, -- {OV5693_8BIT, 0x380a, 0x01}, -- {OV5693_8BIT, 0x380b, 0xf0}, -- {OV5693_8BIT, 0x380c, 0x0d}, -- {OV5693_8BIT, 0x380d, 0xb0}, -- {OV5693_8BIT, 0x380e, 0x05}, -- {OV5693_8BIT, 0x380f, 0xf2}, -- {OV5693_8BIT, 0x3811, 0x08}, -- {OV5693_8BIT, 0x3813, 0x02}, -- {OV5693_8BIT, 0x3814, 0x31}, -- {OV5693_8BIT, 0x3815, 0x31}, -- {OV5693_8BIT, 0x3820, 0x01}, -- {OV5693_8BIT, 0x3821, 0x1f}, -- {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_TOK_TERM, 0, 0} --}; --*/ - /* - * 976x556 30fps 8.8ms VBlanking 2lane 10Bit (Scaling) - */ - #if ENABLE_NON_PREVIEW --static struct ov5693_reg const ov5693_976x556[] = { -- {OV5693_8BIT, 0x3501, 0x7b}, -- {OV5693_8BIT, 0x3502, 0x00}, -- {OV5693_8BIT, 0x3708, 0xe2}, -- {OV5693_8BIT, 0x3709, 0xc3}, -- {OV5693_8BIT, 0x3803, 0xf0}, -- {OV5693_8BIT, 0x3806, 0x06}, -- {OV5693_8BIT, 0x3807, 0xa7}, -- {OV5693_8BIT, 0x3808, 0x03}, -- {OV5693_8BIT, 0x3809, 0xd0}, -- {OV5693_8BIT, 0x380a, 0x02}, -- {OV5693_8BIT, 0x380b, 0x2C}, -- {OV5693_8BIT, 0x380c, 0x0a}, -- {OV5693_8BIT, 0x380d, 0x80}, -- {OV5693_8BIT, 0x380e, 0x07}, -- {OV5693_8BIT, 0x380f, 0xc0}, -- {OV5693_8BIT, 0x3811, 0x10}, -- {OV5693_8BIT, 0x3813, 0x02}, -- {OV5693_8BIT, 0x3814, 0x11}, -- {OV5693_8BIT, 0x3815, 0x11}, -- {OV5693_8BIT, 0x3820, 0x00}, -- {OV5693_8BIT, 0x3821, 0x1e}, -- {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_TOK_TERM, 0, 0} --}; - - /*DS from 2624x1492*/ - static struct ov5693_reg const ov5693_1296x736[] = { -@@ -782,40 +703,6 @@ static struct ov5693_reg const ov5693_1636p_30fps[] = { - }; - #endif - --static struct ov5693_reg const ov5693_1616x1216_30fps[] = { -- {OV5693_8BIT, 0x3501, 0x7b}, -- {OV5693_8BIT, 0x3502, 0x80}, -- {OV5693_8BIT, 0x3708, 0xe2}, -- {OV5693_8BIT, 0x3709, 0xc3}, -- {OV5693_8BIT, 0x3800, 0x00}, /*{3800,3801} Array X start*/ -- {OV5693_8BIT, 0x3801, 0x08}, /* 04 //{3800,3801} Array X start*/ -- {OV5693_8BIT, 0x3802, 0x00}, /*{3802,3803} Array Y start*/ -- {OV5693_8BIT, 0x3803, 0x04}, /* 00 //{3802,3803} Array Y start*/ -- {OV5693_8BIT, 0x3804, 0x0a}, /*{3804,3805} Array X end*/ -- {OV5693_8BIT, 0x3805, 0x37}, /* 3b //{3804,3805} Array X end*/ -- {OV5693_8BIT, 0x3806, 0x07}, /*{3806,3807} Array Y end*/ -- {OV5693_8BIT, 0x3807, 0x9f}, /* a3 //{3806,3807} Array Y end*/ -- {OV5693_8BIT, 0x3808, 0x06}, /*{3808,3809} Final output H size*/ -- {OV5693_8BIT, 0x3809, 0x50}, /*{3808,3809} Final output H size*/ -- {OV5693_8BIT, 0x380a, 0x04}, /*{380a,380b} Final output V size*/ -- {OV5693_8BIT, 0x380b, 0xc0}, /*{380a,380b} Final output V size*/ -- {OV5693_8BIT, 0x380c, 0x0a}, /*{380c,380d} HTS*/ -- {OV5693_8BIT, 0x380d, 0x80}, /*{380c,380d} HTS*/ -- {OV5693_8BIT, 0x380e, 0x07}, /*{380e,380f} VTS*/ -- {OV5693_8BIT, 0x380f, 0xc0}, /* bc //{380e,380f} VTS*/ -- {OV5693_8BIT, 0x3810, 0x00}, /*{3810,3811} windowing X offset*/ -- {OV5693_8BIT, 0x3811, 0x10}, /*{3810,3811} windowing X offset*/ -- {OV5693_8BIT, 0x3812, 0x00}, /*{3812,3813} windowing Y offset*/ -- {OV5693_8BIT, 0x3813, 0x06}, /*{3812,3813} windowing Y offset*/ -- {OV5693_8BIT, 0x3814, 0x11}, /*X subsample control*/ -- {OV5693_8BIT, 0x3815, 0x11}, /*Y subsample control*/ -- {OV5693_8BIT, 0x3820, 0x00}, /*FLIP/Binnning control*/ -- {OV5693_8BIT, 0x3821, 0x1e}, /*MIRROR control*/ -- {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_8BIT, 0x5041, 0x84}, -- {OV5693_TOK_TERM, 0, 0} --}; -- - /* - * 1940x1096 30fps 8.8ms VBlanking 2lane 10bit (Scaling) - */ -@@ -878,37 +765,6 @@ static struct ov5693_reg const ov5693_2592x1456_30fps[] = { - }; - #endif - --static struct ov5693_reg const ov5693_2576x1456_30fps[] = { -- {OV5693_8BIT, 0x3501, 0x7b}, -- {OV5693_8BIT, 0x3502, 0x00}, -- {OV5693_8BIT, 0x3708, 0xe2}, -- {OV5693_8BIT, 0x3709, 0xc3}, -- {OV5693_8BIT, 0x3800, 0x00}, -- {OV5693_8BIT, 0x3801, 0x00}, -- {OV5693_8BIT, 0x3802, 0x00}, -- {OV5693_8BIT, 0x3803, 0xf0}, -- {OV5693_8BIT, 0x3804, 0x0a}, -- {OV5693_8BIT, 0x3805, 0x3f}, -- {OV5693_8BIT, 0x3806, 0x06}, -- {OV5693_8BIT, 0x3807, 0xa4}, -- {OV5693_8BIT, 0x3808, 0x0a}, -- {OV5693_8BIT, 0x3809, 0x10}, -- {OV5693_8BIT, 0x380a, 0x05}, -- {OV5693_8BIT, 0x380b, 0xb0}, -- {OV5693_8BIT, 0x380c, 0x0a}, -- {OV5693_8BIT, 0x380d, 0x80}, -- {OV5693_8BIT, 0x380e, 0x07}, -- {OV5693_8BIT, 0x380f, 0xc0}, -- {OV5693_8BIT, 0x3811, 0x18}, -- {OV5693_8BIT, 0x3813, 0x00}, -- {OV5693_8BIT, 0x3814, 0x11}, -- {OV5693_8BIT, 0x3815, 0x11}, -- {OV5693_8BIT, 0x3820, 0x00}, -- {OV5693_8BIT, 0x3821, 0x1e}, -- {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_TOK_TERM, 0, 0} --}; -- - /* - * 2592x1944 30fps 0.6ms VBlanking 2lane 10Bit - */ -@@ -940,49 +796,6 @@ static struct ov5693_reg const ov5693_2592x1944_30fps[] = { - }; - #endif - --/* -- * 11:9 Full FOV Output, expected FOV Res: 2346x1920 -- * ISP Effect Res: 1408x1152 -- * Sensor out: 1424x1168, DS From: 2380x1952 -- * -- * WA: Left Offset: 8, Hor scal: 64 -- */ --#if ENABLE_NON_PREVIEW --static struct ov5693_reg const ov5693_1424x1168_30fps[] = { -- {OV5693_8BIT, 0x3501, 0x3b}, /* long exposure[15:8] */ -- {OV5693_8BIT, 0x3502, 0x80}, /* long exposure[7:0] */ -- {OV5693_8BIT, 0x3708, 0xe2}, -- {OV5693_8BIT, 0x3709, 0xc3}, -- {OV5693_8BIT, 0x3800, 0x00}, /* TIMING_X_ADDR_START */ -- {OV5693_8BIT, 0x3801, 0x50}, /* 80 */ -- {OV5693_8BIT, 0x3802, 0x00}, /* TIMING_Y_ADDR_START */ -- {OV5693_8BIT, 0x3803, 0x02}, /* 2 */ -- {OV5693_8BIT, 0x3804, 0x09}, /* TIMING_X_ADDR_END */ -- {OV5693_8BIT, 0x3805, 0xdd}, /* 2525 */ -- {OV5693_8BIT, 0x3806, 0x07}, /* TIMING_Y_ADDR_END */ -- {OV5693_8BIT, 0x3807, 0xa1}, /* 1953 */ -- {OV5693_8BIT, 0x3808, 0x05}, /* TIMING_X_OUTPUT_SIZE */ -- {OV5693_8BIT, 0x3809, 0x90}, /* 1424 */ -- {OV5693_8BIT, 0x380a, 0x04}, /* TIMING_Y_OUTPUT_SIZE */ -- {OV5693_8BIT, 0x380b, 0x90}, /* 1168 */ -- {OV5693_8BIT, 0x380c, 0x0a}, /* TIMING_HTS */ -- {OV5693_8BIT, 0x380d, 0x80}, -- {OV5693_8BIT, 0x380e, 0x07}, /* TIMING_VTS */ -- {OV5693_8BIT, 0x380f, 0xc0}, -- {OV5693_8BIT, 0x3810, 0x00}, /* TIMING_ISP_X_WIN */ -- {OV5693_8BIT, 0x3811, 0x02}, /* 2 */ -- {OV5693_8BIT, 0x3812, 0x00}, /* TIMING_ISP_Y_WIN */ -- {OV5693_8BIT, 0x3813, 0x00}, /* 0 */ -- {OV5693_8BIT, 0x3814, 0x11}, /* TIME_X_INC */ -- {OV5693_8BIT, 0x3815, 0x11}, /* TIME_Y_INC */ -- {OV5693_8BIT, 0x3820, 0x00}, -- {OV5693_8BIT, 0x3821, 0x1e}, -- {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -- {OV5693_TOK_TERM, 0, 0} --}; --#endif -- - /* - * 3:2 Full FOV Output, expected FOV Res: 2560x1706 - * ISP Effect Res: 720x480 -@@ -1022,173 +835,6 @@ static struct ov5693_reg const ov5693_736x496_30fps[] = { - {OV5693_TOK_TERM, 0, 0} - }; - --static struct ov5693_reg const ov5693_2576x1936_30fps[] = { -- {OV5693_8BIT, 0x3501, 0x7b}, -- {OV5693_8BIT, 0x3502, 0x00}, -- {OV5693_8BIT, 0x3708, 0xe2}, -- {OV5693_8BIT, 0x3709, 0xc3}, -- {OV5693_8BIT, 0x3803, 0x00}, -- {OV5693_8BIT, 0x3806, 0x07}, -- {OV5693_8BIT, 0x3807, 0xa3}, -- {OV5693_8BIT, 0x3808, 0x0a}, -- {OV5693_8BIT, 0x3809, 0x10}, -- {OV5693_8BIT, 0x380a, 0x07}, -- {OV5693_8BIT, 0x380b, 0x90}, -- {OV5693_8BIT, 0x380c, 0x0a}, -- {OV5693_8BIT, 0x380d, 0x80}, -- {OV5693_8BIT, 0x380e, 0x07}, -- {OV5693_8BIT, 0x380f, 0xc0}, -- {OV5693_8BIT, 0x3811, 0x18}, -- {OV5693_8BIT, 0x3813, 0x00}, -- {OV5693_8BIT, 0x3814, 0x11}, -- {OV5693_8BIT, 0x3815, 0x11}, -- {OV5693_8BIT, 0x3820, 0x00}, -- {OV5693_8BIT, 0x3821, 0x1e}, -- {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_TOK_TERM, 0, 0} --}; -- --static struct ov5693_resolution ov5693_res_preview[] = { -- { -- .desc = "ov5693_736x496_30fps", -- .width = 736, -- .height = 496, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_736x496_30fps, -- }, -- { -- .desc = "ov5693_1616x1216_30fps", -- .width = 1616, -- .height = 1216, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_1616x1216_30fps, -- }, -- { -- .desc = "ov5693_5M_30fps", -- .width = 2576, -- .height = 1456, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_2576x1456_30fps, -- }, -- { -- .desc = "ov5693_5M_30fps", -- .width = 2576, -- .height = 1936, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_2576x1936_30fps, -- }, --}; -- --#define N_RES_PREVIEW (ARRAY_SIZE(ov5693_res_preview)) -- --/* -- * Disable non-preview configurations until the configuration selection is -- * improved. -- */ --#if ENABLE_NON_PREVIEW --struct ov5693_resolution ov5693_res_still[] = { -- { -- .desc = "ov5693_736x496_30fps", -- .width = 736, -- .height = 496, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_736x496_30fps, -- }, -- { -- .desc = "ov5693_1424x1168_30fps", -- .width = 1424, -- .height = 1168, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_1424x1168_30fps, -- }, -- { -- .desc = "ov5693_1616x1216_30fps", -- .width = 1616, -- .height = 1216, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_1616x1216_30fps, -- }, -- { -- .desc = "ov5693_5M_30fps", -- .width = 2592, -- .height = 1456, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_2592x1456_30fps, -- }, -- { -- .desc = "ov5693_5M_30fps", -- .width = 2592, -- .height = 1944, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_2592x1944_30fps, -- }, --}; -- --#define N_RES_STILL (ARRAY_SIZE(ov5693_res_still)) -- - struct ov5693_resolution ov5693_res_video[] = { - { - .desc = "ov5693_736x496_30fps", -@@ -1343,4 +989,3 @@ struct ov5693_resolution ov5693_res_video[] = { - - static struct ov5693_resolution *ov5693_res = ov5693_res_video; - static unsigned long N_RES = N_RES_VIDEO; --#endif --- -2.33.0 - -From 4ddf83de46c2815e6cf66cc2121558ccba73c432 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 14 Feb 2021 14:45:58 +0000 -Subject: [PATCH] media: i2c: update set_fmt() for ov5693 - -The set_fmt() function is a bit messy still, using home grown solutions to -find the closest supported resolution instead of the v4l2 helpers. It also -fails to update control ranges to account for the new mode (though this is -moot currently as they're all the same, but the probably shouldn't be). - -Fix it up a little. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 148 ++++++++++--------------------------- - drivers/media/i2c/ov5693.h | 5 +- - 2 files changed, 40 insertions(+), 113 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 622a7ddf4063..09c84006d5c9 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -753,7 +753,7 @@ static int ov5693_sensor_init(struct ov5693_device *ov5693) - return ret; - } - -- ret = ov5693_write_reg_array(client, ov5693_res[ov5693->fmt_idx].regs); -+ ret = ov5693_write_reg_array(client, ov5693->mode->regs); - if (ret) { - dev_err(&client->dev, "ov5693 write register err.\n"); - return ret; -@@ -866,128 +866,56 @@ static int __maybe_unused ov5693_sensor_resume(struct device *dev) - return ret; - } - --/* -- * distance - calculate the distance -- * @res: resolution -- * @w: width -- * @h: height -- * -- * Get the gap between res_w/res_h and w/h. -- * distance = (res_w/res_h - w/h) / (w/h) * 8192 -- * res->width/height smaller than w/h wouldn't be considered. -- * The gap of ratio larger than 1/8 wouldn't be considered. -- * Returns the value of gap or -1 if fail. -- */ --#define LARGEST_ALLOWED_RATIO_MISMATCH 1024 --static int distance(struct ov5693_resolution *res, u32 w, u32 h) --{ -- int ratio; -- int distance; -- -- if (w == 0 || h == 0 || -- res->width < w || res->height < h) -- return -1; -- -- ratio = res->width << 13; -- ratio /= w; -- ratio *= h; -- ratio /= res->height; -- -- distance = abs(ratio - 8192); -- -- if (distance > LARGEST_ALLOWED_RATIO_MISMATCH) -- return -1; -- -- return distance; --} -- --/* Return the nearest higher resolution index -- * Firstly try to find the approximate aspect ratio resolution -- * If we find multiple same AR resolutions, choose the -- * minimal size. -- */ --static int nearest_resolution_index(int w, int h) --{ -- int i; -- int idx = -1; -- int dist; -- int min_dist = INT_MAX; -- int min_res_w = INT_MAX; -- struct ov5693_resolution *tmp_res = NULL; -- -- for (i = 0; i < N_RES; i++) { -- tmp_res = &ov5693_res[i]; -- dist = distance(tmp_res, w, h); -- if (dist == -1) -- continue; -- if (dist < min_dist) { -- min_dist = dist; -- idx = i; -- min_res_w = ov5693_res[i].width; -- continue; -- } -- if (dist == min_dist && ov5693_res[i].width < min_res_w) -- idx = i; -- } -- -- return idx; --} -- --static int get_resolution_index(int w, int h) --{ -- int i; -- -- for (i = 0; i < N_RES; i++) { -- if (w != ov5693_res[i].width) -- continue; -- if (h != ov5693_res[i].height) -- continue; -- -- return i; -- } -- -- return -1; --} -- - static int ov5693_set_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *format) - { -- struct v4l2_mbus_framefmt *fmt = &format->format; - struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- struct i2c_client *client = v4l2_get_subdevdata(sd); -+ const struct ov5693_resolution *mode; -+ int exposure_max; - int ret = 0; -- int idx; -+ int hblank; - - if (format->pad) - return -EINVAL; -- if (!fmt) -- return -EINVAL; - - mutex_lock(&ov5693->lock); -- idx = nearest_resolution_index(fmt->width, fmt->height); -- if (idx == -1) { -- /* return the largest resolution */ -- fmt->width = ov5693_res[N_RES - 1].width; -- fmt->height = ov5693_res[N_RES - 1].height; -- } else { -- fmt->width = ov5693_res[idx].width; -- fmt->height = ov5693_res[idx].height; -- } - -- fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ mode = v4l2_find_nearest_size(ov5693_res_video, ARRAY_SIZE(ov5693_res_video), -+ width, height, format->format.width, -+ format->format.height); -+ -+ if (!mode) -+ return -EINVAL; -+ -+ format->format.width = mode->width; -+ format->format.height = mode->height; -+ format->format.code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ - if (format->which == V4L2_SUBDEV_FORMAT_TRY) { -- cfg->try_fmt = *fmt; -- ret = 0; -+ *v4l2_subdev_get_try_format(sd, cfg, format->pad) = format->format; - goto mutex_unlock; - } - -- ov5693->fmt_idx = get_resolution_index(fmt->width, fmt->height); -- if (ov5693->fmt_idx == -1) { -- dev_err(&client->dev, "get resolution fail\n"); -- ret = -EINVAL; -- goto mutex_unlock; -- } -+ ov5693->mode = mode; -+ -+ /* Update limits and set FPS to default */ -+ __v4l2_ctrl_modify_range(ov5693->ctrls.vblank, -+ mode->lines_per_frame - mode->height, -+ OV5693_TIMING_MAX_VTS - mode->height, -+ 1, mode->lines_per_frame - mode->height); -+ __v4l2_ctrl_s_ctrl(ov5693->ctrls.vblank, -+ mode->lines_per_frame - mode->height); -+ -+ hblank = mode->pixels_per_line - mode->width; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.hblank, hblank, hblank, 1, hblank); -+ -+ exposure_max = mode->lines_per_frame - 8; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, -+ ov5693->ctrls.exposure->minimum, exposure_max, -+ ov5693->ctrls.exposure->step, -+ ov5693->ctrls.exposure->val < exposure_max ? -+ ov5693->ctrls.exposure->val : exposure_max); - - mutex_unlock: - mutex_unlock(&ov5693->lock); -@@ -1056,8 +984,8 @@ static int ov5693_get_fmt(struct v4l2_subdev *sd, - if (!fmt) - return -EINVAL; - -- fmt->width = ov5693_res[ov5693->fmt_idx].width; -- fmt->height = ov5693_res[ov5693->fmt_idx].height; -+ fmt->width = ov5693->mode->width; -+ fmt->height = ov5693->mode->height; - fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; - - return 0; -@@ -1174,7 +1102,7 @@ static int ov5693_g_frame_interval(struct v4l2_subdev *sd, - struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - - interval->interval.numerator = 1; -- interval->interval.denominator = ov5693_res[ov5693->fmt_idx].fps; -+ interval->interval.denominator = ov5693->mode->fps; - - return 0; - } -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 29e6735112da..0377853f8b2b 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -127,8 +127,8 @@ struct ov5693_resolution { - u8 *desc; - const struct ov5693_reg *regs; - int res; -- int width; -- int height; -+ u32 width; -+ u32 height; - int fps; - int pix_clk_freq; - u16 pixels_per_line; -@@ -178,7 +178,6 @@ struct ov5693_device { - struct camera_sensor_platform_data *platform_data; - ktime_t timestamp_t_focus_abs; - int vt_pix_clk_freq_mhz; -- int fmt_idx; - int run_mode; - int otp_size; - u8 *otp_data; --- -2.33.0 - diff --git a/patches/5.10/0012-ath10k-firmware-override.patch b/patches/5.10/0012-ath10k-firmware-override.patch deleted file mode 100644 index 6bf58ecd7..000000000 --- a/patches/5.10/0012-ath10k-firmware-override.patch +++ /dev/null @@ -1,121 +0,0 @@ -From f1a7edd0afcfe48655860b056f689dae10dd46f9 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 27 Feb 2021 00:45:52 +0100 -Subject: [PATCH] ath10k: Add module parameters to override board files - -Some Surface devices, specifically the Surface Go and AMD version of the -Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better -with a different board file, as it seems that the firmeware included -upstream is buggy. - -As it is generally not a good idea to randomly overwrite files, let -alone doing so via packages, we add module parameters to override those -file names in the driver. This allows us to package/deploy the override -via a modprobe.d config. - -Signed-off-by: Maximilian Luz -Patchset: ath10k-firmware-override ---- - drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++ - 1 file changed, 58 insertions(+) - -diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c -index d73ad60b571c..f242fd4b81a6 100644 ---- a/drivers/net/wireless/ath/ath10k/core.c -+++ b/drivers/net/wireless/ath/ath10k/core.c -@@ -35,6 +35,9 @@ static bool skip_otp; - static bool rawmode; - static bool fw_diag_log; - -+static char *override_board = ""; -+static char *override_board2 = ""; -+ - unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | - BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); - -@@ -47,6 +50,9 @@ module_param(rawmode, bool, 0644); - module_param(fw_diag_log, bool, 0644); - module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); - -+module_param(override_board, charp, 0644); -+module_param(override_board2, charp, 0644); -+ - MODULE_PARM_DESC(debug_mask, "Debugging mask"); - MODULE_PARM_DESC(uart_print, "Uart target debugging"); - MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); -@@ -55,6 +61,9 @@ MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); - MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); - MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); - -+MODULE_PARM_DESC(override_board, "Override for board.bin file"); -+MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); -+ - static const struct ath10k_hw_params ath10k_hw_params_list[] = { - { - .id = QCA988X_HW_2_0_VERSION, -@@ -809,6 +818,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) - return 0; - } - -+static const char *ath10k_override_board_fw_file(struct ath10k *ar, -+ const char *file) -+{ -+ if (strcmp(file, "board.bin") == 0) { -+ if (strcmp(override_board, "") == 0) -+ return file; -+ -+ if (strcmp(override_board, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", -+ override_board); -+ -+ return override_board; -+ } -+ -+ if (strcmp(file, "board-2.bin") == 0) { -+ if (strcmp(override_board2, "") == 0) -+ return file; -+ -+ if (strcmp(override_board2, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", -+ override_board2); -+ -+ return override_board2; -+ } -+ -+ return file; -+} -+ - static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - const char *dir, - const char *file) -@@ -823,6 +868,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - if (dir == NULL) - dir = "."; - -+ /* HACK: Override board.bin and board-2.bin files if specified. -+ * -+ * Some Surface devices perform better with a different board -+ * configuration. To this end, one would need to replace the board.bin -+ * file with the modified config and remove the board-2.bin file. -+ * Unfortunately, that's not a solution that we can easily package. So -+ * we add module options to perform these overrides here. -+ */ -+ -+ file = ath10k_override_board_fw_file(ar, file); -+ if (!file) -+ return ERR_PTR(-ENOENT); -+ - snprintf(filename, sizeof(filename), "%s/%s", dir, file); - ret = firmware_request_nowarn(&fw, filename, ar->dev); - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", --- -2.33.0 - diff --git a/patches/5.11/0001-surface3-oemb.patch b/patches/5.11/0001-surface3-oemb.patch deleted file mode 100644 index 01e621a93..000000000 --- a/patches/5.11/0001-surface3-oemb.patch +++ /dev/null @@ -1,101 +0,0 @@ -From e6d25f7538426bd45706329a13c6ec2523f5899b Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 18 Oct 2020 16:42:44 +0900 -Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI - table - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI data into dmi_system_id tables used -for quirks so that each driver can enable quirks even on the affected -systems. - -On affected systems, DMI data will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: surface3-oemb ---- - drivers/platform/surface/surface3-wmi.c | 7 +++++++ - sound/soc/codecs/rt5645.c | 9 +++++++++ - sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ - 3 files changed, 24 insertions(+) - -diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c -index 130b6f52a600..801083aa56d6 100644 ---- a/drivers/platform/surface/surface3-wmi.c -+++ b/drivers/platform/surface/surface3-wmi.c -@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - #endif - { } - }; -diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c -index 420003d062c7..217e488cd4fa 100644 ---- a/sound/soc/codecs/rt5645.c -+++ b/sound/soc/codecs/rt5645.c -@@ -3687,6 +3687,15 @@ static const struct dmi_system_id dmi_platform_data[] = { - }, - .driver_data = (void *)&intel_braswell_platform_data, - }, -+ { -+ .ident = "Microsoft Surface 3", -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)&intel_braswell_platform_data, -+ }, - { - /* - * Match for the GPDwin which unfortunately uses somewhat -diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -index 2752dc955733..ef36a316e2ed 100644 ---- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c -+++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .callback = cht_surface_quirk_cb, -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - { } - }; - --- -2.31.1 - diff --git a/patches/5.11/0002-wifi.patch b/patches/5.11/0002-wifi.patch deleted file mode 100644 index 3dada9bd4..000000000 --- a/patches/5.11/0002-wifi.patch +++ /dev/null @@ -1,2520 +0,0 @@ -From 8de29febc13795f44263a9c1826488fbe63fd4d8 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Mon, 28 Sep 2020 17:46:49 +0900 -Subject: [PATCH] mwifiex: pcie: add DMI-based quirk impl for Surface devices - -This commit adds quirk implementation based on DMI matching with DMI -table for Surface devices. - -This implementation can be used for quirks later. - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/Makefile | 1 + - drivers/net/wireless/marvell/mwifiex/pcie.c | 4 + - drivers/net/wireless/marvell/mwifiex/pcie.h | 1 + - .../wireless/marvell/mwifiex/pcie_quirks.c | 114 ++++++++++++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.h | 11 ++ - 5 files changed, 131 insertions(+) - create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.c - create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.h - -diff --git a/drivers/net/wireless/marvell/mwifiex/Makefile b/drivers/net/wireless/marvell/mwifiex/Makefile -index 162d557b78af..2bd00f40958e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/Makefile -+++ b/drivers/net/wireless/marvell/mwifiex/Makefile -@@ -49,6 +49,7 @@ mwifiex_sdio-y += sdio.o - obj-$(CONFIG_MWIFIEX_SDIO) += mwifiex_sdio.o - - mwifiex_pcie-y += pcie.o -+mwifiex_pcie-y += pcie_quirks.o - obj-$(CONFIG_MWIFIEX_PCIE) += mwifiex_pcie.o - - mwifiex_usb-y += usb.o -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 5f0a61b974ee..41c71fbea9c1 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -27,6 +27,7 @@ - #include "wmm.h" - #include "11n.h" - #include "pcie.h" -+#include "pcie_quirks.h" - - #define PCIE_VERSION "1.0" - #define DRV_NAME "Marvell mwifiex PCIe" -@@ -410,6 +411,9 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return ret; - } - -+ /* check quirks */ -+ mwifiex_initialize_quirks(card); -+ - if (mwifiex_add_card(card, &card->fw_done, &pcie_ops, - MWIFIEX_PCIE, &pdev->dev)) { - pr_err("%s failed\n", __func__); -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.h b/drivers/net/wireless/marvell/mwifiex/pcie.h -index 5ed613d65709..981e330c77d7 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.h -@@ -244,6 +244,7 @@ struct pcie_service_card { - unsigned long work_flags; - - bool pci_reset_ongoing; -+ unsigned long quirks; - }; - - static inline int -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -new file mode 100644 -index 000000000000..929aee2b0a60 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -0,0 +1,114 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * File for PCIe quirks. -+ */ -+ -+/* The low-level PCI operations will be performed in this file. Therefore, -+ * let's use dev_*() instead of mwifiex_dbg() here to avoid troubles (e.g. -+ * to avoid using mwifiex_adapter struct before init or wifi is powered -+ * down, or causes NULL ptr deref). -+ */ -+ -+#include -+ -+#include "pcie_quirks.h" -+ -+/* quirk table based on DMI matching */ -+static const struct dmi_system_id mwifiex_quirk_table[] = { -+ { -+ .ident = "Surface Pro 4", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 5", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 5 (LTE)", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 6", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Book 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Book 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Laptop 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Laptop 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 3"), -+ }, -+ .driver_data = 0, -+ }, -+ {} -+}; -+ -+void mwifiex_initialize_quirks(struct pcie_service_card *card) -+{ -+ struct pci_dev *pdev = card->dev; -+ const struct dmi_system_id *dmi_id; -+ -+ dmi_id = dmi_first_match(mwifiex_quirk_table); -+ if (dmi_id) -+ card->quirks = (uintptr_t)dmi_id->driver_data; -+ -+ if (!card->quirks) -+ dev_info(&pdev->dev, "no quirks enabled\n"); -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -new file mode 100644 -index 000000000000..5326ae7e5671 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -0,0 +1,11 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* -+ * Header file for PCIe quirks. -+ */ -+ -+#include "pcie.h" -+ -+/* quirks */ -+// quirk flags can be added here -+ -+void mwifiex_initialize_quirks(struct pcie_service_card *card); --- -2.31.1 - -From f54cbb3d064c5d408b1bbb283f03044e77e4de19 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:25:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_d3cold quirk for Surface gen4+ - devices - -To reset mwifiex on Surface gen4+ (Pro 4 or later gen) devices, it -seems that putting the wifi device into D3cold is required according -to errata.inf file on Windows installation (Windows/INF/errata.inf). - -This patch adds a function that performs power-cycle (put into D3cold -then D0) and call the function at the end of reset_prepare(). - -Note: Need to also reset the parent device (bridge) of wifi on SB1; -it might be because the bridge of wifi always reports it's in D3hot. -When I tried to reset only the wifi device (not touching parent), it gave -the following error and the reset failed: - - acpi device:4b: Cannot transition to power state D0 for parent in D3hot - mwifiex_pcie 0000:03:00.0: can't change power state from D3cold to D0 (config space inaccessible) - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 ++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 73 +++++++++++++++++-- - .../wireless/marvell/mwifiex/pcie_quirks.h | 3 +- - 3 files changed, 74 insertions(+), 9 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 41c71fbea9c1..ac749da17072 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -528,6 +528,13 @@ static void mwifiex_pcie_reset_prepare(struct pci_dev *pdev) - mwifiex_shutdown_sw(adapter); - clear_bit(MWIFIEX_IFACE_WORK_DEVICE_DUMP, &card->work_flags); - clear_bit(MWIFIEX_IFACE_WORK_CARD_RESET, &card->work_flags); -+ -+ /* For Surface gen4+ devices, we need to put wifi into D3cold right -+ * before performing FLR -+ */ -+ if (card->quirks & QUIRK_FW_RST_D3COLD) -+ mwifiex_pcie_reset_d3cold_quirk(pdev); -+ - mwifiex_dbg(adapter, INFO, "%s, successful\n", __func__); - - card->pci_reset_ongoing = true; -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 929aee2b0a60..edc739c542fe 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -21,7 +21,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 5", -@@ -30,7 +30,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -39,7 +39,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 6", -@@ -47,7 +47,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Book 1", -@@ -55,7 +55,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Book 2", -@@ -63,7 +63,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Laptop 1", -@@ -71,7 +71,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Laptop 2", -@@ -79,7 +79,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface 3", -@@ -111,4 +111,61 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - - if (!card->quirks) - dev_info(&pdev->dev, "no quirks enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_D3COLD) -+ dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+} -+ -+static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -+{ -+ dev_info(&pdev->dev, "putting into D3cold...\n"); -+ -+ pci_save_state(pdev); -+ if (pci_is_enabled(pdev)) -+ pci_disable_device(pdev); -+ pci_set_power_state(pdev, PCI_D3cold); -+} -+ -+static int mwifiex_pcie_set_power_d0(struct pci_dev *pdev) -+{ -+ int ret; -+ -+ dev_info(&pdev->dev, "putting into D0...\n"); -+ -+ pci_set_power_state(pdev, PCI_D0); -+ ret = pci_enable_device(pdev); -+ if (ret) { -+ dev_err(&pdev->dev, "pci_enable_device failed\n"); -+ return ret; -+ } -+ pci_restore_state(pdev); -+ -+ return 0; -+} -+ -+int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) -+{ -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); -+ int ret; -+ -+ /* Power-cycle (put into D3cold then D0) */ -+ dev_info(&pdev->dev, "Using reset_d3cold quirk to perform FW reset\n"); -+ -+ /* We need to perform power-cycle also for bridge of wifi because -+ * on some devices (e.g. Surface Book 1), the OS for some reasons -+ * can't know the real power state of the bridge. -+ * When tried to power-cycle only wifi, the reset failed with the -+ * following dmesg log: -+ * "Cannot transition to power state D0 for parent in D3hot". -+ */ -+ mwifiex_pcie_set_power_d3cold(pdev); -+ mwifiex_pcie_set_power_d3cold(parent_pdev); -+ -+ ret = mwifiex_pcie_set_power_d0(parent_pdev); -+ if (ret) -+ return ret; -+ ret = mwifiex_pcie_set_power_d0(pdev); -+ if (ret) -+ return ret; -+ -+ return 0; - } -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 5326ae7e5671..8b9dcb5070d8 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -6,6 +6,7 @@ - #include "pcie.h" - - /* quirks */ --// quirk flags can be added here -+#define QUIRK_FW_RST_D3COLD BIT(0) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); -+int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.31.1 - -From 1c26491c00fe075fc901d397567c7946f0aa639e Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:32:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_wsid quirk for Surface 3 - -This commit adds reset_wsid quirk and uses this quirk for Surface 3 on -card reset. - -To reset mwifiex on Surface 3, it seems that calling the _DSM method -exists in \_SB.WSID [1] device is required. - -On Surface 3, calling the _DSM method removes/re-probes the card by -itself. So, need to place the reset function before performing FLR and -skip performing any other reset-related works. - -Note that Surface Pro 3 also has the WSID device [2], but it seems to need -more work. This commit only supports Surface 3 yet. - -[1] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_3/dsdt.dsl#L11947-L12011 -[2] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_pro_3/dsdt.dsl#L12164-L12216 - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 10 +++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 77 ++++++++++++++++++- - .../wireless/marvell/mwifiex/pcie_quirks.h | 5 ++ - 3 files changed, 91 insertions(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index ac749da17072..bf9ef4ede3f1 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -2969,6 +2969,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - -+ /* On Surface 3, reset_wsid method removes then re-probes card by -+ * itself. So, need to place it here and skip performing any other -+ * reset-related works. -+ */ -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) { -+ mwifiex_pcie_reset_wsid_quirk(card->dev); -+ /* skip performing any other reset-related works */ -+ return; -+ } -+ - /* We can't afford to wait here; remove() might be waiting on us. If we - * can't grab the device lock, maybe we'll get another chance later. - */ -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index edc739c542fe..f0a6fa0a7ae5 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -9,10 +9,21 @@ - * down, or causes NULL ptr deref). - */ - -+#include - #include - - #include "pcie_quirks.h" - -+/* For reset_wsid quirk */ -+#define ACPI_WSID_PATH "\\_SB.WSID" -+#define WSID_REV 0x0 -+#define WSID_FUNC_WIFI_PWR_OFF 0x1 -+#define WSID_FUNC_WIFI_PWR_ON 0x2 -+/* WSID _DSM UUID: "534ea3bf-fcc2-4e7a-908f-a13978f0c7ef" */ -+static const guid_t wsid_dsm_guid = -+ GUID_INIT(0x534ea3bf, 0xfcc2, 0x4e7a, -+ 0x90, 0x8f, 0xa1, 0x39, 0x78, 0xf0, 0xc7, 0xef); -+ - /* quirk table based on DMI matching */ - static const struct dmi_system_id mwifiex_quirk_table[] = { - { -@@ -87,7 +98,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, - { - .ident = "Surface Pro 3", -@@ -113,6 +124,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - dev_info(&pdev->dev, "no quirks enabled\n"); - if (card->quirks & QUIRK_FW_RST_D3COLD) - dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) -+ dev_info(&pdev->dev, -+ "quirk reset_wsid for Surface 3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -@@ -169,3 +183,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) - - return 0; - } -+ -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev) -+{ -+ acpi_handle handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n"); -+ -+ status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&pdev->dev, "No ACPI handle for path %s\n", -+ ACPI_WSID_PATH); -+ return -ENODEV; -+ } -+ -+ if (!acpi_has_method(handle, "_DSM")) { -+ dev_err(&pdev->dev, "_DSM method not found\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power off func\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power on func\n"); -+ return -ENODEV; -+ } -+ -+ /* card will be removed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi off...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi off\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ /* card will be re-probed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi on...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi on\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ return 0; -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 8b9dcb5070d8..3ef7440418e3 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -7,6 +7,11 @@ - - /* quirks */ - #define QUIRK_FW_RST_D3COLD BIT(0) -+/* Surface 3 and Surface Pro 3 have the same _DSM method but need to -+ * be handled differently. Currently, only S3 is supported. -+ */ -+#define QUIRK_FW_RST_WSID_S3 BIT(1) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); --- -2.31.1 - -From 11b587191358f8b18555bf41b4958bd30131968a Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Wed, 30 Sep 2020 18:08:24 +0900 -Subject: [PATCH] mwifiex: pcie: (OEMB) add quirk for Surface 3 with broken DMI - table - -(made referring to http://git.osdn.net/view?p=android-x86/kernel.git;a=commitdiff;h=18e2e857c57633b25b3b4120f212224a108cd883) - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI info for the affected Surface 3. - -On affected systems, DMI info will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie_quirks.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index f0a6fa0a7ae5..34dcd84f02a6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -100,6 +100,15 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - { - .ident = "Surface Pro 3", - .matches = { --- -2.31.1 - -From 45c761b68885d62b7a8be4fbb8355d9fbcd9beb8 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 4 Oct 2020 00:11:49 +0900 -Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ - -Currently, mwifiex fw will crash after suspend on recent kernel series. -On Windows, it seems that the root port of wifi will never enter D3 state -(stay on D0 state). And on Linux, disabling the D3 state for the -bridge fixes fw crashing after suspend. - -This commit disables the D3 state of root port on driver initialization -and fixes fw crashing after suspend. - -Signed-off-by: Tsuchiya Yuto -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 27 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index bf9ef4ede3f1..ba6296885b82 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -379,6 +379,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) - { - struct pcie_service_card *card; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - int ret; - - pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", -@@ -420,6 +421,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return -1; - } - -+ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing -+ * after suspend -+ */ -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ parent_pdev->bridge_d3 = false; -+ - return 0; - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 34dcd84f02a6..a2aeb2af907e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -32,7 +32,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5", -@@ -41,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -50,7 +52,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 6", -@@ -58,7 +61,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 1", -@@ -66,7 +70,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 2", -@@ -74,7 +79,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 1", -@@ -82,7 +88,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 2", -@@ -90,7 +97,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface 3", -@@ -136,6 +144,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_FW_RST_WSID_S3) - dev_info(&pdev->dev, - "quirk reset_wsid for Surface 3 enabled\n"); -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ dev_info(&pdev->dev, -+ "quirk no_brigde_d3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 3ef7440418e3..a95ebac06e13 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -11,6 +11,7 @@ - * be handled differently. Currently, only S3 is supported. - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) -+#define QUIRK_NO_BRIDGE_D3 BIT(2) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.31.1 - -From 6fb483e102f4ffd6338bd94b48091a57664f8814 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 3 Nov 2020 13:28:04 +0100 -Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface - devices - -The most recent firmware of the 88W8897 card reports a hardcoded LTR -value to the system during initialization, probably as an (unsuccessful) -attempt of the developers to fix firmware crashes. This LTR value -prevents most of the Microsoft Surface devices from entering deep -powersaving states (either platform C-State 10 or S0ix state), because -the exit latency of that state would be higher than what the card can -tolerate. - -Turns out the card works just the same (including the firmware crashes) -no matter if that hardcoded LTR value is reported or not, so it's kind -of useless and only prevents us from saving power. - -To get rid of those hardcoded LTR reports, it's possible to reset the -PCI bridge device after initializing the cards firmware. I'm not exactly -sure why that works, maybe the power management subsystem of the PCH -resets its stored LTR values when doing a function level reset of the -bridge device. Doing the reset once after starting the wifi firmware -works very well, probably because the firmware only reports that LTR -value a single time during firmware startup. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 31 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index ba6296885b82..9ac12ff4a5c1 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -1757,9 +1757,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) - static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; -+ struct pci_dev *pdev = card->dev; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; - int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; - -+ /* Trigger a function level reset of the PCI bridge device, this makes -+ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value -+ * that prevents the system from entering package C10 and S0ix powersaving -+ * states. -+ * We need to do it here because it must happen after firmware -+ * initialization and this function is called after that is done. -+ */ -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ pci_reset_function(parent_pdev); -+ - /* Write the RX ring read pointer in to reg->rx_rdptr */ - if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | - tx_wrap)) { -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index a2aeb2af907e..6885575826a6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -33,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5", -@@ -43,7 +44,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -53,7 +55,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 6", -@@ -62,7 +65,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 1", -@@ -71,7 +75,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 2", -@@ -80,7 +85,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 1", -@@ -89,7 +95,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 2", -@@ -98,7 +105,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface 3", -@@ -147,6 +155,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_NO_BRIDGE_D3) - dev_info(&pdev->dev, - "quirk no_brigde_d3 enabled\n"); -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index a95ebac06e13..4ec2ae72f632 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -12,6 +12,7 @@ - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) - #define QUIRK_NO_BRIDGE_D3 BIT(2) -+#define QUIRK_DO_FLR_ON_BRIDGE BIT(3) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.31.1 - -From 0208cf5e3589f5ffd25908f2e6ef03d1a61585a5 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 25 Mar 2021 11:33:02 +0100 -Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell - 88W8897 - -The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) -is used in a lot of Microsoft Surface devices, and all those devices -suffer from very low 2.4GHz wifi connection speeds while bluetooth is -enabled. The reason for that is that the default passive scanning -interval for Bluetooth Low Energy devices is quite high in Linux -(interval of 60 msec and scan window of 30 msec, see hci_core.c), and -the Marvell chip is known for its bad bt+wifi coexisting performance. - -So decrease that passive scan interval and make the scan window shorter -on this particular device to allow for spending more time transmitting -wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and -the new scan window is 6.25 msec (0xa * 0,625 msec). - -This change has a very large impact on the 2.4GHz wifi speeds and gets -it up to performance comparable with the Windows driver, which seems to -apply a similar quirk. - -The interval and window length were tested and found to work very well -with a lot of Bluetooth Low Energy devices, including the Surface Pen, a -Bluetooth Speaker and two modern Bluetooth headphones. All devices were -discovered immediately after turning them on. Even lower values were -also tested, but they introduced longer delays until devices get -discovered. - -Patchset: wifi ---- - drivers/bluetooth/btusb.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index 3620981e8b1c..526320673fa0 100644 ---- a/drivers/bluetooth/btusb.c -+++ b/drivers/bluetooth/btusb.c -@@ -61,6 +61,7 @@ static struct usb_driver btusb_driver; - #define BTUSB_VALID_LE_STATES 0x800000 - #define BTUSB_QCA_WCN6855 0x1000000 - #define BTUSB_INTEL_NEWGEN 0x2000000 -+#define BTUSB_LOWER_LESCAN_INTERVAL BIT(26) - - static const struct usb_device_id btusb_table[] = { - /* Generic Bluetooth USB device */ -@@ -357,6 +358,7 @@ static const struct usb_device_id blacklist_table[] = { - { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, -+ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, - - /* Intel Bluetooth devices */ - { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_NEW | -@@ -4493,6 +4495,19 @@ static int btusb_probe(struct usb_interface *intf, - if (id->driver_info & BTUSB_MARVELL) - hdev->set_bdaddr = btusb_set_bdaddr_marvell; - -+ /* The Marvell 88W8897 combined wifi and bluetooth card is known for -+ * very bad bt+wifi coexisting performance. -+ * -+ * Decrease the passive BT Low Energy scan interval a bit -+ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter -+ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly -+ * higher wifi throughput while passively scanning for BT LE devices. -+ */ -+ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { -+ hdev->le_scan_interval = 0x0190; -+ hdev->le_scan_window = 0x000a; -+ } -+ - if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && - (id->driver_info & BTUSB_MEDIATEK)) { - hdev->setup = btusb_mtk_setup; --- -2.31.1 - -From 3e0eb9f427c72ccc4938a1b2cd73546c1fce2388 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 12:31:26 +0100 -Subject: [PATCH] mwifiex: Small cleanup for handling virtual interface type - changes - -Handle the obvious invalid virtual interface type changes with a general -check instead of looking at the individual change. - -For type changes from P2P_CLIENT to P2P_GO and the other way round, this -changes the behavior slightly: We now still do nothing, but return --EOPNOTSUPP instead of 0. Now that behavior was incorrect before and -still is, because type changes between these two types are actually -possible and supported, which we'll fix in a following commit. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 39 +++++++------------ - 1 file changed, 14 insertions(+), 25 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index a6b9dc6700b1..d50fd8570475 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1141,6 +1141,20 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return -EBUSY; - } - -+ if (type == NL80211_IFTYPE_UNSPECIFIED) { -+ mwifiex_dbg(priv->adapter, INFO, -+ "%s: no new type specified, keeping old type %d\n", -+ dev->name, curr_iftype); -+ return 0; -+ } -+ -+ if (curr_iftype == type) { -+ mwifiex_dbg(priv->adapter, INFO, -+ "%s: interface already is of type %d\n", -+ dev->name, curr_iftype); -+ return 0; -+ } -+ - switch (curr_iftype) { - case NL80211_IFTYPE_ADHOC: - switch (type) { -@@ -1160,12 +1174,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as IBSS\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_ADHOC: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1191,12 +1199,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as STA\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_STATION: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1214,12 +1216,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_P2P_GO: - return mwifiex_change_vif_to_p2p(dev, curr_iftype, - type, params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as AP\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_AP: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1254,13 +1250,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return -EFAULT; - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as P2P\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", --- -2.31.1 - -From e196f55feb353c9409e09f397f8918469bf4d521 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 12:44:39 +0100 -Subject: [PATCH] mwifiex: Use function to check whether interface type change - is allowed - -Instead of bailing out in the function which is supposed to do the type -change, detect invalid changes beforehand using a generic function and -return an error if the change is not allowed. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 139 ++++++++++++------ - 1 file changed, 92 insertions(+), 47 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index d50fd8570475..3a79a55bbfd2 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -939,6 +939,76 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, - return 0; - } - -+static bool -+is_vif_type_change_allowed(struct mwifiex_adapter *adapter, -+ enum nl80211_iftype old_iftype, -+ enum nl80211_iftype new_iftype) -+{ -+ switch (old_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_STATION: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_AP: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return adapter->curr_iface_comb.sta_intf != -+ adapter->iface_limit.sta_intf; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ default: -+ break; -+ } -+ -+ return false; -+} -+ - static int - mwifiex_change_vif_to_p2p(struct net_device *dev, - enum nl80211_iftype curr_iftype, -@@ -955,13 +1025,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - - adapter = priv->adapter; - -- if (adapter->curr_iface_comb.p2p_intf == -- adapter->iface_limit.p2p_intf) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple P2P ifaces\n"); -- return -1; -- } -- - mwifiex_dbg(adapter, INFO, - "%s: changing role to p2p\n", dev->name); - -@@ -1027,15 +1090,6 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - - adapter = priv->adapter; - -- if ((curr_iftype != NL80211_IFTYPE_P2P_CLIENT && -- curr_iftype != NL80211_IFTYPE_P2P_GO) && -- (adapter->curr_iface_comb.sta_intf == -- adapter->iface_limit.sta_intf)) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple station/adhoc ifaces\n"); -- return -1; -- } -- - if (type == NL80211_IFTYPE_STATION) - mwifiex_dbg(adapter, INFO, - "%s: changing role to station\n", dev->name); -@@ -1086,13 +1140,6 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - - adapter = priv->adapter; - -- if (adapter->curr_iface_comb.uap_intf == -- adapter->iface_limit.uap_intf) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple AP ifaces\n"); -- return -1; -- } -- - mwifiex_dbg(adapter, INFO, - "%s: changing role to AP\n", dev->name); - -@@ -1155,6 +1202,13 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return 0; - } - -+ if (!is_vif_type_change_allowed(priv->adapter, curr_iftype, type)) { -+ mwifiex_dbg(priv->adapter, ERROR, -+ "%s: change from type %d to %d is not allowed\n", -+ dev->name, curr_iftype, type); -+ return -EOPNOTSUPP; -+ } -+ - switch (curr_iftype) { - case NL80211_IFTYPE_ADHOC: - switch (type) { -@@ -1175,12 +1229,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_STATION: - switch (type) { - case NL80211_IFTYPE_ADHOC: -@@ -1200,12 +1251,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_AP: - switch (type) { - case NL80211_IFTYPE_ADHOC: -@@ -1217,12 +1265,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_p2p(dev, curr_iftype, - type, params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_P2P_CLIENT: - case NL80211_IFTYPE_P2P_GO: - switch (type) { -@@ -1251,21 +1296,21 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: unknown iftype: %d\n", -- dev->name, dev->ieee80211_ptr->iftype); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } - - - return 0; -+ -+errnotsupp: -+ mwifiex_dbg(priv->adapter, ERROR, -+ "unsupported interface type transition: %d to %d\n", -+ curr_iftype, type); -+ return -EOPNOTSUPP; - } - - static void --- -2.31.1 - -From 2fb8f5b8b2abe58b446a120c91034bfa118ef9d1 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 13:33:04 +0100 -Subject: [PATCH] mwifiex: Run SET_BSS_MODE when changing from P2P to STATION - vif-type - -We currently handle changing from the P2P to the STATION virtual -interface type slightly different than changing from P2P to ADHOC: When -changing to STATION, we don't send the SET_BSS_MODE command. We do send -that command on all other type-changes though, and it probably makes -sense to send the command since after all we just changed our BSS_MODE. -Looking at prior changes to this part of the code, it seems that this is -simply a leftover from old refactorings. - -Since sending the SET_BSS_MODE command is the only difference between -mwifiex_change_vif_to_sta_adhoc() and the current code, we can now use -mwifiex_change_vif_to_sta_adhoc() for both switching to ADHOC and -STATION interface type. - -This does not fix any particular bug and just "looked right", so there's -a small chance it might be a regression. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 22 ++++--------------- - 1 file changed, 4 insertions(+), 18 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 3a79a55bbfd2..66e978088061 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1270,29 +1270,15 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - - case NL80211_IFTYPE_P2P_CLIENT: - case NL80211_IFTYPE_P2P_GO: -+ if (mwifiex_cfg80211_deinit_p2p(priv)) -+ return -EFAULT; -+ - switch (type) { -- case NL80211_IFTYPE_STATION: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; -- priv->adapter->curr_iface_comb.p2p_intf--; -- priv->adapter->curr_iface_comb.sta_intf++; -- dev->ieee80211_ptr->iftype = type; -- if (mwifiex_deinit_priv_params(priv)) -- return -1; -- if (mwifiex_init_new_priv_params(priv, dev, type)) -- return -1; -- if (mwifiex_sta_init_cmd(priv, false, false)) -- return -1; -- break; - case NL80211_IFTYPE_ADHOC: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; -+ case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); -- break; - case NL80211_IFTYPE_AP: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: --- -2.31.1 - -From 7ada305aa7a3a1cdfc719f6cebc0293d29e76943 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 14:42:54 +0100 -Subject: [PATCH] mwifiex: Use helper function for counting interface types - -Use a small helper function to increment and decrement the counter of -the interface types we currently manage. This makes the code that -actually changes and sets up the interface type a bit less messy and -also helps avoiding mistakes in case someone increments/decrements a -counter wrongly. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 110 ++++++------------ - 1 file changed, 35 insertions(+), 75 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 66e978088061..db30f595e9f9 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1009,6 +1009,32 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, - return false; - } - -+static void -+update_vif_type_counter(struct mwifiex_adapter *adapter, -+ enum nl80211_iftype iftype, -+ int change) -+{ -+ switch (iftype) { -+ case NL80211_IFTYPE_UNSPECIFIED: -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ adapter->curr_iface_comb.sta_intf += change; -+ break; -+ case NL80211_IFTYPE_AP: -+ adapter->curr_iface_comb.uap_intf += change; -+ break; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ adapter->curr_iface_comb.p2p_intf += change; -+ break; -+ default: -+ mwifiex_dbg(adapter, ERROR, -+ "%s: Unsupported iftype passed: %d\n", -+ __func__, iftype); -+ break; -+ } -+} -+ - static int - mwifiex_change_vif_to_p2p(struct net_device *dev, - enum nl80211_iftype curr_iftype, -@@ -1056,19 +1082,8 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.p2p_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; - - return 0; -@@ -1107,20 +1122,10 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.sta_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; -+ - return 0; - } - -@@ -1153,20 +1158,8 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.uap_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; - return 0; - } -@@ -3114,23 +3107,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, - mwifiex_dev_debugfs_init(priv); - #endif - -- switch (type) { -- case NL80211_IFTYPE_UNSPECIFIED: -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf++; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf++; -- break; -- case NL80211_IFTYPE_P2P_CLIENT: -- adapter->curr_iface_comb.p2p_intf++; -- break; -- default: -- /* This should be dead code; checked above */ -- mwifiex_dbg(adapter, ERROR, "type not supported\n"); -- return ERR_PTR(-EINVAL); -- } -+ update_vif_type_counter(adapter, type, +1); - - return &priv->wdev; - -@@ -3196,24 +3173,7 @@ int mwifiex_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) - /* Clear the priv in adapter */ - priv->netdev = NULL; - -- switch (priv->bss_mode) { -- case NL80211_IFTYPE_UNSPECIFIED: -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- default: -- mwifiex_dbg(adapter, ERROR, -- "del_virtual_intf: type not supported\n"); -- break; -- } -+ update_vif_type_counter(adapter, priv->bss_mode, -1); - - priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; - --- -2.31.1 - -From 1de4818c6ed1fab2c4d5d71e9b5d49c9794cd1e4 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:56:58 +0100 -Subject: [PATCH] mwifiex: Update virtual interface counters right after - setting bss_type - -In mwifiex_init_new_priv_params() we update our private driver state to -reflect the currently selected virtual interface type. Most notably we -set the bss_mode to the mode we're going to put the firmware in. - -Now after we updated the driver state we actually start talking to the -firmware and instruct it to set up the new mode. Those commands can and -will sometimes fail, in which case we return with an error from -mwifiex_change_vif_to_*. We currently update our virtual interface type -counters after this return, which means the code is never reached when a -firmware error happens and we never update the counters. Since we have -updated our bss_mode earlier though, the counters now no longer reflect -the actual state of the driver. - -This will break things on the next virtual interface change, because the -virtual interface type we're switching away from didn't get its counter -incremented, and we end up decrementing a 0-counter. - -To fix this, simply update the virtual interface type counters right -after updating our driver structures, so that they are always in sync. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 25 +++++++++++-------- - 1 file changed, 14 insertions(+), 11 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index db30f595e9f9..60de1cec77c7 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1059,6 +1059,10 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; - -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - switch (type) { - case NL80211_IFTYPE_P2P_CLIENT: - if (mwifiex_cfg80211_init_p2p_client(priv)) -@@ -1082,10 +1086,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; -- - return 0; - } - -@@ -1116,16 +1116,17 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - return -1; - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; -+ -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, - HostCmd_ACT_GEN_SET, 0, NULL, true)) - return -1; - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; -- - return 0; - } - -@@ -1152,15 +1153,17 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - return -1; - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; -+ -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, - HostCmd_ACT_GEN_SET, 0, NULL, true)) - return -1; - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; - return 0; - } - /* --- -2.31.1 - -From 56e4eae3b97bd3e1790a57f6ae04f0feee126feb Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 13:42:40 +0100 -Subject: [PATCH] mwifiex: Allow switching interface type from P2P_CLIENT to - P2P_GO - -It's possible to change virtual interface type between P2P_CLIENT and -P2P_GO, the card supports that just fine, and it happens for example -when using miracast with the miraclecast software. - -So allow type changes between P2P_CLIENT and P2P_GO and simply call into -mwifiex_change_vif_to_p2p(), which handles this just fine. We have to -call mwifiex_cfg80211_deinit_p2p() before though to make sure the old -p2p mode is properly uninitialized. - -Patchset: wifi ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 36 +++++++++++++++++++ - 1 file changed, 36 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 60de1cec77c7..a37b504bd084 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -990,11 +990,26 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, - } - - case NL80211_IFTYPE_P2P_CLIENT: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_P2P_GO: -+ return true; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ - case NL80211_IFTYPE_P2P_GO: - switch (new_iftype) { - case NL80211_IFTYPE_ADHOC: - case NL80211_IFTYPE_STATION: - return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ return true; - case NL80211_IFTYPE_AP: - return adapter->curr_iface_comb.uap_intf != - adapter->iface_limit.uap_intf; -@@ -1265,6 +1280,24 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - } - - case NL80211_IFTYPE_P2P_CLIENT: -+ if (mwifiex_cfg80211_deinit_p2p(priv)) -+ return -EFAULT; -+ -+ switch (type) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, -+ type, params); -+ case NL80211_IFTYPE_P2P_GO: -+ return mwifiex_change_vif_to_p2p(dev, curr_iftype, -+ type, params); -+ case NL80211_IFTYPE_AP: -+ return mwifiex_change_vif_to_ap(dev, curr_iftype, type, -+ params); -+ default: -+ goto errnotsupp; -+ } -+ - case NL80211_IFTYPE_P2P_GO: - if (mwifiex_cfg80211_deinit_p2p(priv)) - return -EFAULT; -@@ -1274,6 +1307,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); -+ case NL80211_IFTYPE_P2P_CLIENT: -+ return mwifiex_change_vif_to_p2p(dev, curr_iftype, -+ type, params); - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); --- -2.31.1 - -From c4fe64dd8ac97b1eab109a20d2f9a71daaa5ff14 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:31:08 +0100 -Subject: [PATCH] mwifiex: Handle interface type changes from AP to STATION - -Looks like this case was simply overseen, so handle it, too. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index a37b504bd084..e65f285e3efe 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1268,6 +1268,7 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - switch (type) { - case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); - break; --- -2.31.1 - -From 4c30b5476237da4887d12e331b7228dfc07ba897 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:32:16 +0100 -Subject: [PATCH] mwifiex: Properly initialize private structure on interface - type changes - -When creating a new virtual interface in mwifiex_add_virtual_intf(), we -update our internal driver states like bss_type, bss_priority, bss_role -and bss_mode to reflect the mode the firmware will be set to. - -When switching virtual interface mode using -mwifiex_init_new_priv_params() though, we currently only update bss_mode -and bss_role. In order for the interface mode switch to actually work, -we also need to update bss_type to its proper value, so do that. - -This fixes a crash of the firmware (because the driver tries to execute -commands that are invalid in AP mode) when switching from station mode -to AP mode. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 +++++++--- - 1 file changed, 7 insertions(+), 3 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index e65f285e3efe..a290312313f3 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -908,16 +908,20 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, - switch (type) { - case NL80211_IFTYPE_STATION: - case NL80211_IFTYPE_ADHOC: -- priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_type = MWIFIEX_BSS_TYPE_STA; - break; - case NL80211_IFTYPE_P2P_CLIENT: -- priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - break; - case NL80211_IFTYPE_P2P_GO: -- priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - break; - case NL80211_IFTYPE_AP: - priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_type = MWIFIEX_BSS_TYPE_UAP; - break; - default: - mwifiex_dbg(adapter, ERROR, --- -2.31.1 - -From 5f978abee6600f8d44458a7695d2ad5bbae940fc Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sat, 27 Mar 2021 12:19:14 +0100 -Subject: [PATCH] mwifiex: Fix copy-paste mistake when creating virtual - interface - -The BSS priority here for a new P2P_CLIENT device was accidentally set -to an enum that's certainly not meant for this. Since -MWIFIEX_BSS_ROLE_STA is 0 anyway, we can just set the bss_priority to 0 -instead here. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index a290312313f3..1e1cf523e228 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -3040,7 +3040,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, - priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - - priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II; -- priv->bss_priority = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_priority = 0; - priv->bss_role = MWIFIEX_BSS_ROLE_STA; - priv->bss_started = 0; - --- -2.31.1 - -From e6626bb7402937413c4c0dfa304e6cddc620e98d Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 10 Nov 2020 12:49:56 +0100 -Subject: [PATCH] mwifiex: Use non-posted PCI register writes - -On the 88W8897 card it's very important the TX ring write pointer is -updated correctly to its new value before setting the TX ready -interrupt, otherwise the firmware appears to crash (probably because -it's trying to DMA-read from the wrong place). - -Since PCI uses "posted writes" when writing to a register, it's not -guaranteed that a write will happen immediately. That means the pointer -might be outdated when setting the TX ready interrupt, leading to -firmware crashes especially when ASPM L1 and L1 substates are enabled -(because of the higher link latency, the write will probably take -longer). - -So fix those firmware crashes by always forcing non-posted writes. We do -that by simply reading back the register after writing it, just as a lot -of other drivers do. - -There are two reproducers that are fixed with this patch: - -1) During rx/tx traffic and with ASPM L1 substates enabled (the enabled -substates are platform dependent), the firmware crashes and eventually a -command timeout appears in the logs. That crash is fixed by using a -non-posted write in mwifiex_pcie_send_data(). - -2) When sending lots of commands to the card, waking it up from sleep in -very quick intervals, the firmware eventually crashes. That crash -appears to be fixed by some other non-posted write included here. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 9ac12ff4a5c1..4123c8bb6b6f 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -237,6 +237,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) - - iowrite32(data, card->pci_mmap1 + reg); - -+ /* Do a read-back, which makes the write non-posted, ensuring the -+ * completion before returning. -+ * The firmware of the 88W8897 card is buggy and this avoids crashes. -+ */ -+ ioread32(card->pci_mmap1 + reg); -+ - return 0; - } - --- -2.31.1 - -From e14c128fc83dfb35913c8829590a99a5b761f6f2 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sun, 28 Mar 2021 21:10:06 +0200 -Subject: [PATCH] mwifiex: Try waking the firmware until we get an interrupt - -It seems that the firmware of the 88W8897 card sometimes ignores or -misses when we try to wake it up by reading the firmware status -register. This leads to the firmware wakeup timeout expiring and the -driver resetting the card because we assume the firmware has hung up or -crashed (unfortunately that's not unlikely with this card). - -Turns out that most of the time the firmware actually didn't hang up, -but simply "missed" our wakeup request and doesn't send us an AWAKE -event. - -Trying again to read the firmware status register after a short timeout -usually makes the firmware wake we up as expected, so add a small retry -loop to mwifiex_pm_wakeup_card() that looks at the interrupt status to -check whether the card woke up. - -The number of tries and timeout lengths for this were determined -experimentally: The firmware usually takes about 500 us to wake up -after we attempt to read the status register. In some cases where the -firmware is very busy (for example while doing a bluetooth scan) it -might even miss our requests for multiple milliseconds, which is why -after 15 tries the waiting time gets increased to 10 ms. The maximum -number of tries it took to wake the firmware when testing this was -around 20, so a maximum number of 50 tries should give us plenty of -safety margin. - -A good reproducer for this issue is letting the firmware sleep and wake -up in very short intervals, for example by pinging an device on the -network every 0.1 seconds. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 29 ++++++++++++++++----- - 1 file changed, 23 insertions(+), 6 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 4123c8bb6b6f..b5491509d6a2 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -665,6 +665,7 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; -+ int n_tries = 0; - - mwifiex_dbg(adapter, EVENT, - "event: Wakeup device...\n"); -@@ -672,12 +673,28 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) - if (reg->sleep_cookie) - mwifiex_pcie_dev_wakeup_delay(adapter); - -- /* Accessing fw_status register will wakeup device */ -- if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) { -- mwifiex_dbg(adapter, ERROR, -- "Writing fw_status register failed\n"); -- return -1; -- } -+ /* Access the fw_status register to wake up the device. -+ * Since the 88W8897 firmware sometimes appears to ignore or miss -+ * that wakeup request, we continue trying until we receive an -+ * interrupt from the card. -+ */ -+ do { -+ if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) { -+ mwifiex_dbg(adapter, ERROR, -+ "Writing fw_status register failed\n"); -+ return -1; -+ } -+ -+ n_tries++; -+ -+ if (n_tries <= 15) -+ usleep_range(400, 700); -+ else -+ msleep(10); -+ } while (n_tries <= 50 && READ_ONCE(adapter->int_status) == 0); -+ -+ mwifiex_dbg(adapter, EVENT, -+ "event: Tried %d times until firmware woke up\n", n_tries); - - if (reg->sleep_cookie) { - mwifiex_pcie_dev_wakeup_delay(adapter); --- -2.31.1 - -From d8bb7532e7c1930f7ce60c3023f83e68cf23646d Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:30:28 +0200 -Subject: [PATCH] mwifiex: Deactive host sleep using HSCFG after it was - activated manually - -When powersaving (so either wifi powersaving or deep sleep, depending on -which state the firmware is in) is disabled, the way the firmware goes -into host sleep is different: Usually the firmware implicitely enters -host sleep on the next SLEEP event we get when we configured host sleep -via HSCFG before. When powersaving is disabled though, there are no -SLEEP events, the way we enter host sleep in that case is different: The -firmware will send us a HS_ACT_REQ event and after that we "manually" -make the firmware enter host sleep by sending it another HSCFG command -with the action HS_ACTIVATE. - -Now waking up from host sleep appears to be different depending on -whether powersaving is enabled again: When powersaving is enabled, the -firmware implicitely leaves host sleep as soon as it wakes up and sends -us an AWAKE event. When powersaving is disabled though, it apparently -doesn't implicitely leave host sleep, but instead we need to send it a -HSCFG command with the HS_CONFIGURE action and the HS_CFG_CANCEL -condition. We didn't do that so far, which is why waking up from host -sleep was broken when powersaving is disabled. - -So add some additional state to mwifiex_adapter where we keep track of -whether host sleep was activated manually via HS_ACTIVATE, and if that -was the case, deactivate it manually again via HS_CFG_CANCEL. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cmdevt.c | 21 +++++++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.c | 18 ++++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmd.c | 4 ++++ - 4 files changed, 44 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cmdevt.c b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -index 3a11342a6bde..5487df8f994d 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cmdevt.c -+++ b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -@@ -608,6 +608,11 @@ int mwifiex_send_cmd(struct mwifiex_private *priv, u16 cmd_no, - return -1; - } - -+ if (priv->adapter->hs_activated_manually && -+ cmd_no != HostCmd_CMD_802_11_HS_CFG_ENH) { -+ mwifiex_cancel_hs(priv, MWIFIEX_ASYNC_CMD); -+ priv->adapter->hs_activated_manually = false; -+ } - - /* Get a new command node */ - cmd_node = mwifiex_get_cmd_node(adapter); -@@ -714,6 +719,15 @@ mwifiex_insert_cmd_to_pending_q(struct mwifiex_adapter *adapter, - } - } - -+ /* Same with exit host sleep cmd, luckily that can't happen at the same time as EXIT_PS */ -+ if (command == HostCmd_CMD_802_11_HS_CFG_ENH) { -+ struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = -+ &host_cmd->params.opt_hs_cfg; -+ -+ if (le16_to_cpu(hs_cfg->action) == HS_ACTIVATE) -+ add_tail = false; -+ } -+ - spin_lock_bh(&adapter->cmd_pending_q_lock); - if (add_tail) - list_add_tail(&cmd_node->list, &adapter->cmd_pending_q); -@@ -1216,6 +1230,13 @@ mwifiex_process_hs_config(struct mwifiex_adapter *adapter) - __func__); - - adapter->if_ops.wakeup(adapter); -+ -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv (adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - adapter->hs_activated = false; - clear_bit(MWIFIEX_IS_HS_CONFIGURED, &adapter->work_flags); - clear_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags); -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index ee52fb839ef7..aa44bcbebca4 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -401,6 +401,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !adapter->scan_processing) && - !adapter->data_sent && - !skb_queue_empty(&adapter->tx_data_q)) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_process_tx_queue(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -@@ -418,6 +424,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !mwifiex_bypass_txlist_empty(adapter) && - !mwifiex_is_tdls_chan_switching - (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_process_bypass_tx(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -@@ -434,6 +446,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !adapter->data_sent && !mwifiex_wmm_lists_empty(adapter) && - !mwifiex_is_tdls_chan_switching - (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_wmm_process_tx(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 5923c5c14c8d..90012cbcfd15 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -986,6 +986,7 @@ struct mwifiex_adapter { - struct timer_list wakeup_timer; - struct mwifiex_hs_config_param hs_cfg; - u8 hs_activated; -+ u8 hs_activated_manually; - u16 hs_activate_wait_q_woken; - wait_queue_head_t hs_activate_wait_q; - u8 event_body[MAX_EVENT_SIZE]; -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -index d3a968ef21ef..76db9a7b8199 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -@@ -396,6 +396,10 @@ mwifiex_cmd_802_11_hs_cfg(struct mwifiex_private *priv, - if (hs_activate) { - hs_cfg->action = cpu_to_le16(HS_ACTIVATE); - hs_cfg->params.hs_activate.resp_ctrl = cpu_to_le16(RESP_NEEDED); -+ -+ adapter->hs_activated_manually = true; -+ mwifiex_dbg(priv->adapter, CMD, -+ "cmd: Activating host sleep manually\n"); - } else { - hs_cfg->action = cpu_to_le16(HS_CONFIGURE); - hs_cfg->params.hs_config.conditions = hscfg_param->conditions; --- -2.31.1 - -From 024fa7918f896fd7611ce4cc29b93b27404f89ae Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:23:05 +0200 -Subject: [PATCH] mwifiex: Add quirk to disable deep sleep with certain - hardware revision - -The 88W8897 pcie card with the hardware revision 20 apparently has a -hardware issue where the card wakes up from deep sleep randomly and very -often, somewhat depending on the card activity, maybe the hardware has a -floating wakeup pin or something. - -Those continuous wakeups prevent the card from entering host sleep when -the computer suspends. And because the host won't answer to events from -the card anymore while it's suspended, the firmwares internal -powersaving state machine seems to get confused and the card can't sleep -anymore at all after that. - -Since we can't work around that hardware bug in the firmware, let's -get the hardware revision string from the firmware and match it with -known bad revisions. Then disable auto deep sleep for those revisions, -which makes sure we no longer get those spurious wakeups. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/main.c | 14 ++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmdresp.c | 16 ++++++++++++++++ - 3 files changed, 31 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index aa44bcbebca4..f09a39ce645f 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -226,6 +226,19 @@ static int mwifiex_process_rx(struct mwifiex_adapter *adapter) - return 0; - } - -+static void maybe_quirk_fw_disable_ds(struct mwifiex_adapter *adapter) -+{ -+ struct mwifiex_private *priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA); -+ struct mwifiex_ver_ext ver_ext; -+ -+ set_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &adapter->work_flags); -+ -+ memset(&ver_ext, 0, sizeof(ver_ext)); -+ ver_ext.version_str_sel = 1; -+ mwifiex_send_cmd(priv, HostCmd_CMD_VERSION_EXT, -+ HostCmd_ACT_GEN_GET, 0, &ver_ext, false); -+} -+ - /* - * The main process. - * -@@ -356,6 +369,7 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - if (adapter->hw_status == MWIFIEX_HW_STATUS_INIT_DONE) { - adapter->hw_status = MWIFIEX_HW_STATUS_READY; - mwifiex_init_fw_complete(adapter); -+ maybe_quirk_fw_disable_ds(adapter); - } - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 90012cbcfd15..1e829d84b1f6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -524,6 +524,7 @@ enum mwifiex_adapter_work_flags { - MWIFIEX_IS_SUSPENDED, - MWIFIEX_IS_HS_CONFIGURED, - MWIFIEX_IS_HS_ENABLING, -+ MWIFIEX_IS_REQUESTING_FW_VEREXT, - }; - - struct mwifiex_band_config { -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -index 6b5d35d9e69f..8e49ebca1847 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -@@ -708,6 +708,22 @@ static int mwifiex_ret_ver_ext(struct mwifiex_private *priv, - { - struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext; - -+ if (test_and_clear_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &priv->adapter->work_flags)) { -+ if (strncmp(ver_ext->version_str, "ChipRev:20, BB:9b(10.00), RF:40(21)", 128) == 0) { -+ struct mwifiex_ds_auto_ds auto_ds = { -+ .auto_ds = DEEP_SLEEP_OFF, -+ }; -+ -+ mwifiex_dbg(priv->adapter, MSG, -+ "Bad HW revision detected, disabling deep sleep\n"); -+ -+ mwifiex_send_cmd(priv, HostCmd_CMD_802_11_PS_MODE_ENH, -+ DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, false); -+ } -+ -+ return 0; -+ } -+ - if (version_ext) { - version_ext->version_str_sel = ver_ext->version_str_sel; - memcpy(version_ext->version_str, ver_ext->version_str, --- -2.31.1 - -From d38c6eca640053104c5992465a2a10af015fd842 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 15:17:07 +0100 -Subject: [PATCH] mwifiex: Don't log error on suspend if wake-on-wlan is - disabled - -It's not an error if someone chooses to put their computer to sleep, not -wanting it to wake up because the person next door has just discovered -what a magic packet is. So change the loglevel of this annoying message -from ERROR to INFO. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 1e1cf523e228..1cdd66c37cfc 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -3480,7 +3480,7 @@ static int mwifiex_cfg80211_suspend(struct wiphy *wiphy, - } - - if (!wowlan) { -- mwifiex_dbg(adapter, ERROR, -+ mwifiex_dbg(adapter, INFO, - "None of the WOWLAN triggers enabled\n"); - ret = 0; - goto done; --- -2.31.1 - -From 90588b13e9a4a10c396834ab8207c9f4ddd55a93 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sun, 28 Mar 2021 21:42:54 +0200 -Subject: [PATCH] mwifiex: Log an error on command failure during key-material - upload - -Sometimes the KEY_MATERIAL command can fail with the 88W8897 firmware -(when this happens exactly seems pretty random). This appears to prevent -the access point from starting, so it seems like a good idea to log an -error in that case. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 ++++++++-- - 1 file changed, 8 insertions(+), 2 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 1cdd66c37cfc..6ad935c1bb47 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -519,8 +519,14 @@ mwifiex_cfg80211_set_default_mgmt_key(struct wiphy *wiphy, - encrypt_key.is_igtk_def_key = true; - eth_broadcast_addr(encrypt_key.mac_addr); - -- return mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, -- HostCmd_ACT_GEN_SET, true, &encrypt_key, true); -+ if (mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, -+ HostCmd_ACT_GEN_SET, true, &encrypt_key, true)) { -+ mwifiex_dbg(priv->adapter, ERROR, -+ "Sending KEY_MATERIAL command failed\n"); -+ return -1; -+ } -+ -+ return 0; - } - - /* --- -2.31.1 - -From f634f27a002015e2a068b79a742860cce0a5954e Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:44:03 +0200 -Subject: [PATCH] mwifiex: Fix an incorrect comment - -We're sending DELBA requests here, not ADDBA requests. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/11n.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c -index 6696bce56178..b0695432b26a 100644 ---- a/drivers/net/wireless/marvell/mwifiex/11n.c -+++ b/drivers/net/wireless/marvell/mwifiex/11n.c -@@ -125,7 +125,7 @@ int mwifiex_ret_11n_delba(struct mwifiex_private *priv, - tx_ba_tbl->ra); - } else { /* - * In case of failure, recreate the deleted stream in case -- * we initiated the ADDBA -+ * we initiated the DELBA - */ - if (!INITIATOR_BIT(del_ba_param_set)) - return 0; --- -2.31.1 - -From c71a9203e1beff52732d78271a33893dac9d54c4 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:45:59 +0200 -Subject: [PATCH] mwifiex: Send DELBA requests according to spec - -We're currently failing to set the initiator bit for DELBA requests: -While we set the bit on our del_ba_param_set bitmask, we forget to -actually copy that bitmask over to the command struct, which means we -never actually set the initiator bit. - -Fix that and copy the bitmask over to the host_cmd_ds_11n_delba command -struct. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/11n.c | 5 +++-- - 1 file changed, 3 insertions(+), 2 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c -index b0695432b26a..9ff2058bcd7e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/11n.c -+++ b/drivers/net/wireless/marvell/mwifiex/11n.c -@@ -657,14 +657,15 @@ int mwifiex_send_delba(struct mwifiex_private *priv, int tid, u8 *peer_mac, - uint16_t del_ba_param_set; - - memset(&delba, 0, sizeof(delba)); -- delba.del_ba_param_set = cpu_to_le16(tid << DELBA_TID_POS); - -- del_ba_param_set = le16_to_cpu(delba.del_ba_param_set); -+ del_ba_param_set = tid << DELBA_TID_POS; -+ - if (initiator) - del_ba_param_set |= IEEE80211_DELBA_PARAM_INITIATOR_MASK; - else - del_ba_param_set &= ~IEEE80211_DELBA_PARAM_INITIATOR_MASK; - -+ delba.del_ba_param_set = cpu_to_le16(del_ba_param_set); - memcpy(&delba.peer_mac_addr, peer_mac, ETH_ALEN); - - /* We don't wait for the response of this command */ --- -2.31.1 - -From 4a933c6784b80c35cf4fc0c3a2aa2df6eaf3f337 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:57:41 +0200 -Subject: [PATCH] mwifiex: Ignore BTCOEX events from the firmware - -The firmware of the pcie 88W8897 chip sends those events very -unreliably, which means we sometimes end up actually capping the window -size while bluetooth is disabled, artifically limiting wifi speeds even -though it's not needed. - -Since we can't fix the firmware, let's just ignore those events, it -seems that the Windows driver also doesn't change the rx/tx block ack -buffer sizes when bluetooth gets enabled or disabled, so this is -consistent with the Windows driver. - -Patchset: wifi ---- - drivers/net/wireless/marvell/mwifiex/sta_event.c | 4 +--- - 1 file changed, 1 insertion(+), 3 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_event.c b/drivers/net/wireless/marvell/mwifiex/sta_event.c -index 68c63268e2e6..933111a3511c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_event.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_event.c -@@ -1057,9 +1057,7 @@ int mwifiex_process_sta_event(struct mwifiex_private *priv) - adapter->event_skb); - break; - case EVENT_BT_COEX_WLAN_PARA_CHANGE: -- dev_dbg(adapter->dev, "EVENT: BT coex wlan param update\n"); -- mwifiex_bt_coex_wlan_param_update_event(priv, -- adapter->event_skb); -+ dev_dbg(adapter->dev, "EVENT: ignoring BT coex wlan param update\n"); - break; - case EVENT_RXBA_SYNC: - dev_dbg(adapter->dev, "EVENT: RXBA_SYNC\n"); --- -2.31.1 - diff --git a/patches/5.11/0003-ipts.patch b/patches/5.11/0003-ipts.patch deleted file mode 100644 index 0bf380000..000000000 --- a/patches/5.11/0003-ipts.patch +++ /dev/null @@ -1,1534 +0,0 @@ -From fed94111ed1361e18c40c54dcb9795d903158090 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 30 Jul 2020 13:21:53 +0200 -Subject: [PATCH] misc: mei: Add missing IPTS device IDs - -Patchset: ipts ---- - drivers/misc/mei/hw-me-regs.h | 1 + - drivers/misc/mei/pci-me.c | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h -index cb34925e10f1..2b3f8073a3ec 100644 ---- a/drivers/misc/mei/hw-me-regs.h -+++ b/drivers/misc/mei/hw-me-regs.h -@@ -92,6 +92,7 @@ - #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ - - #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ -+#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ - - #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ - -diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c -index c3393b383e59..0098f98426c1 100644 ---- a/drivers/misc/mei/pci-me.c -+++ b/drivers/misc/mei/pci-me.c -@@ -96,6 +96,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { - {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, -+ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_H, MEI_ME_PCH15_SPS_CFG)}, --- -2.31.1 - -From 8b52473bf34de0e532f7abf95702d4ce7f3f04bc Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 25 Feb 2021 09:37:47 +0100 -Subject: [PATCH] misc: mei: Remove client devices before shutting down bus - -Patchset: ipts ---- - drivers/misc/mei/init.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c -index bcee77768b91..21ed765003e1 100644 ---- a/drivers/misc/mei/init.c -+++ b/drivers/misc/mei/init.c -@@ -302,10 +302,10 @@ void mei_stop(struct mei_device *dev) - { - dev_dbg(dev->dev, "stopping the device.\n"); - -+ mei_cl_bus_remove_devices(dev); - mutex_lock(&dev->device_lock); - mei_set_devstate(dev, MEI_DEV_POWER_DOWN); - mutex_unlock(&dev->device_lock); -- mei_cl_bus_remove_devices(dev); - - mei_cancel_work(dev); - --- -2.31.1 - -From 07754dd30f8964e74cf702d4e9778136c30cd17d Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 6 Aug 2020 11:20:41 +0200 -Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus - -Based on linux-surface/intel-precise-touch@3f362c - -Signed-off-by: Dorian Stoll -Patchset: ipts ---- - drivers/misc/Kconfig | 1 + - drivers/misc/Makefile | 1 + - drivers/misc/ipts/Kconfig | 17 ++ - drivers/misc/ipts/Makefile | 12 ++ - drivers/misc/ipts/context.h | 47 +++++ - drivers/misc/ipts/control.c | 113 +++++++++++ - drivers/misc/ipts/control.h | 24 +++ - drivers/misc/ipts/mei.c | 127 +++++++++++++ - drivers/misc/ipts/protocol.h | 347 ++++++++++++++++++++++++++++++++++ - drivers/misc/ipts/receiver.c | 224 ++++++++++++++++++++++ - drivers/misc/ipts/receiver.h | 16 ++ - drivers/misc/ipts/resources.c | 128 +++++++++++++ - drivers/misc/ipts/resources.h | 17 ++ - drivers/misc/ipts/uapi.c | 208 ++++++++++++++++++++ - drivers/misc/ipts/uapi.h | 47 +++++ - 15 files changed, 1329 insertions(+) - create mode 100644 drivers/misc/ipts/Kconfig - create mode 100644 drivers/misc/ipts/Makefile - create mode 100644 drivers/misc/ipts/context.h - create mode 100644 drivers/misc/ipts/control.c - create mode 100644 drivers/misc/ipts/control.h - create mode 100644 drivers/misc/ipts/mei.c - create mode 100644 drivers/misc/ipts/protocol.h - create mode 100644 drivers/misc/ipts/receiver.c - create mode 100644 drivers/misc/ipts/receiver.h - create mode 100644 drivers/misc/ipts/resources.c - create mode 100644 drivers/misc/ipts/resources.h - create mode 100644 drivers/misc/ipts/uapi.c - create mode 100644 drivers/misc/ipts/uapi.h - -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index fafa8b0d8099..c795c56e8d42 100644 ---- a/drivers/misc/Kconfig -+++ b/drivers/misc/Kconfig -@@ -481,4 +481,5 @@ source "drivers/misc/ocxl/Kconfig" - source "drivers/misc/cardreader/Kconfig" - source "drivers/misc/habanalabs/Kconfig" - source "drivers/misc/uacce/Kconfig" -+source "drivers/misc/ipts/Kconfig" - endmenu -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index d23231e73330..9e6e3e2f2ea9 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -57,3 +57,4 @@ obj-$(CONFIG_HABANA_AI) += habanalabs/ - obj-$(CONFIG_UACCE) += uacce/ - obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o - obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o -+obj-$(CONFIG_MISC_IPTS) += ipts/ -diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig -new file mode 100644 -index 000000000000..83e2a930c396 ---- /dev/null -+++ b/drivers/misc/ipts/Kconfig -@@ -0,0 +1,17 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+ -+config MISC_IPTS -+ tristate "Intel Precise Touch & Stylus" -+ depends on INTEL_MEI -+ help -+ Say Y here if your system has a touchscreen using Intels -+ Precise Touch & Stylus (IPTS) technology. -+ -+ If unsure say N. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ipts. -+ -+ Building this driver alone will not give you a working touchscreen. -+ It only exposed a userspace API that can be used by a daemon to -+ receive and process data from the touchscreen hardware. -diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile -new file mode 100644 -index 000000000000..8f58b9adbc94 ---- /dev/null -+++ b/drivers/misc/ipts/Makefile -@@ -0,0 +1,12 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+# -+# Makefile for the IPTS touchscreen driver -+# -+ -+obj-$(CONFIG_MISC_IPTS) += ipts.o -+ipts-objs := control.o -+ipts-objs += mei.o -+ipts-objs += receiver.o -+ipts-objs += resources.o -+ipts-objs += uapi.o -+ -diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h -new file mode 100644 -index 000000000000..f4b06a2d3f72 ---- /dev/null -+++ b/drivers/misc/ipts/context.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTEXT_H_ -+#define _IPTS_CONTEXT_H_ -+ -+#include -+#include -+#include -+#include -+ -+#include "protocol.h" -+ -+enum ipts_host_status { -+ IPTS_HOST_STATUS_STARTING, -+ IPTS_HOST_STATUS_STARTED, -+ IPTS_HOST_STATUS_STOPPING, -+ IPTS_HOST_STATUS_STOPPED, -+}; -+ -+struct ipts_buffer_info { -+ u8 *address; -+ dma_addr_t dma_address; -+}; -+ -+struct ipts_context { -+ struct mei_cl_device *cldev; -+ struct device *dev; -+ -+ bool restart; -+ enum ipts_host_status status; -+ struct ipts_get_device_info_rsp device_info; -+ -+ struct ipts_buffer_info data[IPTS_BUFFERS]; -+ struct ipts_buffer_info doorbell; -+ -+ struct ipts_buffer_info feedback[IPTS_BUFFERS]; -+ struct ipts_buffer_info workqueue; -+ struct ipts_buffer_info host2me; -+}; -+ -+#endif /* _IPTS_CONTEXT_H_ */ -diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c -new file mode 100644 -index 000000000000..a1d1f97a13d7 ---- /dev/null -+++ b/drivers/misc/ipts/control.c -@@ -0,0 +1,113 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+#include "protocol.h" -+#include "resources.h" -+#include "uapi.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload, -+ size_t size) -+{ -+ int ret; -+ struct ipts_command cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_command)); -+ cmd.code = code; -+ -+ if (payload && size > 0) -+ memcpy(&cmd.payload, payload, size); -+ -+ ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size); -+ if (ret >= 0) -+ return 0; -+ -+ /* -+ * During shutdown the device might get pulled away from below our feet. -+ * Dont log an error in this case, because it will confuse people. -+ */ -+ if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING) -+ dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret); -+ -+ return ret; -+} -+ -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) -+{ -+ struct ipts_feedback_cmd cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); -+ cmd.buffer = buffer; -+ -+ return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, -+ sizeof(struct ipts_feedback_cmd)); -+} -+ -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value) -+{ -+ struct ipts_feedback_buffer *feedback; -+ -+ memset(ipts->host2me.address, 0, ipts->device_info.feedback_size); -+ feedback = (struct ipts_feedback_buffer *)ipts->host2me.address; -+ -+ feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; -+ feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; -+ feedback->buffer = IPTS_HOST2ME_BUFFER; -+ feedback->size = 2; -+ feedback->payload[0] = report; -+ feedback->payload[1] = value; -+ -+ return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER); -+} -+ -+int ipts_control_start(struct ipts_context *ipts) -+{ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Starting IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STARTING; -+ ipts->restart = false; -+ -+ ipts_uapi_link(ipts); -+ return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); -+} -+ -+int ipts_control_stop(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPING) -+ return -EBUSY; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Stopping IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STOPPING; -+ -+ ipts_uapi_unlink(); -+ ipts_resources_free(ipts); -+ -+ ret = ipts_control_send_feedback(ipts, 0); -+ if (ret == -ENODEV) -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ return ret; -+} -+ -+int ipts_control_restart(struct ipts_context *ipts) -+{ -+ if (ipts->restart) -+ return -EBUSY; -+ -+ ipts->restart = true; -+ return ipts_control_stop(ipts); -+} -diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h -new file mode 100644 -index 000000000000..2c44e9e0e99f ---- /dev/null -+++ b/drivers/misc/ipts/control.h -@@ -0,0 +1,24 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTROL_H_ -+#define _IPTS_CONTROL_H_ -+ -+#include -+ -+#include "context.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, -+ size_t size); -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value); -+int ipts_control_start(struct ipts_context *ipts); -+int ipts_control_restart(struct ipts_context *ipts); -+int ipts_control_stop(struct ipts_context *ipts); -+ -+#endif /* _IPTS_CONTROL_H_ */ -diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c -new file mode 100644 -index 000000000000..b3805a91383d ---- /dev/null -+++ b/drivers/misc/ipts/mei.c -@@ -0,0 +1,127 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "receiver.h" -+#include "uapi.h" -+ -+static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev) -+{ -+ int ret; -+ -+ ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)); -+ if (!ret) -+ return 0; -+ -+ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); -+} -+ -+static int ipts_mei_probe(struct mei_cl_device *cldev, -+ const struct mei_cl_device_id *id) -+{ -+ int ret; -+ struct ipts_context *ipts; -+ -+ if (ipts_mei_set_dma_mask(cldev)) { -+ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n"); -+ return -EFAULT; -+ } -+ -+ ret = mei_cldev_enable(cldev); -+ if (ret) { -+ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); -+ return ret; -+ } -+ -+ ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); -+ if (!ipts) { -+ mei_cldev_disable(cldev); -+ return -ENOMEM; -+ } -+ -+ ipts->cldev = cldev; -+ ipts->dev = &cldev->dev; -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ mei_cldev_set_drvdata(cldev, ipts); -+ mei_cldev_register_rx_cb(cldev, ipts_receiver_callback); -+ -+ return ipts_control_start(ipts); -+} -+ -+static int ipts_mei_remove(struct mei_cl_device *cldev) -+{ -+ int i; -+ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); -+ -+ ipts_control_stop(ipts); -+ -+ for (i = 0; i < 20; i++) { -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ break; -+ -+ msleep(25); -+ } -+ -+ mei_cldev_disable(cldev); -+ kfree(ipts); -+ -+ return 0; -+} -+ -+static struct mei_cl_device_id ipts_mei_device_id_table[] = { -+ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, -+ {}, -+}; -+MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); -+ -+static struct mei_cl_driver ipts_mei_driver = { -+ .id_table = ipts_mei_device_id_table, -+ .name = "ipts", -+ .probe = ipts_mei_probe, -+ .remove = ipts_mei_remove, -+}; -+ -+static int __init ipts_mei_init(void) -+{ -+ int ret; -+ -+ ret = ipts_uapi_init(); -+ if (ret) -+ return ret; -+ -+ ret = mei_cldev_driver_register(&ipts_mei_driver); -+ if (ret) { -+ ipts_uapi_free(); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static void __exit ipts_mei_exit(void) -+{ -+ mei_cldev_driver_unregister(&ipts_mei_driver); -+ ipts_uapi_free(); -+} -+ -+MODULE_DESCRIPTION("IPTS touchscreen driver"); -+MODULE_AUTHOR("Dorian Stoll "); -+MODULE_LICENSE("GPL"); -+ -+module_init(ipts_mei_init); -+module_exit(ipts_mei_exit); -diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h -new file mode 100644 -index 000000000000..c3458904a94d ---- /dev/null -+++ b/drivers/misc/ipts/protocol.h -@@ -0,0 +1,347 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_PROTOCOL_H_ -+#define _IPTS_PROTOCOL_H_ -+ -+#include -+ -+/* -+ * The MEI client ID for IPTS functionality. -+ */ -+#define IPTS_MEI_UUID \ -+ UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, \ -+ 0x02, 0xae, 0x04) -+ -+/* -+ * Queries the device for vendor specific information. -+ * -+ * The command must not contain any payload. -+ * The response will contain struct ipts_get_device_info_rsp as payload. -+ */ -+#define IPTS_CMD_GET_DEVICE_INFO 0x00000001 -+#define IPTS_RSP_GET_DEVICE_INFO 0x80000001 -+ -+/* -+ * Sets the mode that IPTS will operate in. -+ * -+ * The command must contain struct ipts_set_mode_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MODE 0x00000002 -+#define IPTS_RSP_SET_MODE 0x80000002 -+ -+/* -+ * Configures the memory buffers that the ME will use -+ * for passing data to the host. -+ * -+ * The command must contain struct ipts_set_mem_window_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MEM_WINDOW 0x00000003 -+#define IPTS_RSP_SET_MEM_WINDOW 0x80000003 -+ -+/* -+ * Signals that the host is ready to receive data to the ME. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_READY_FOR_DATA 0x00000005 -+#define IPTS_RSP_READY_FOR_DATA 0x80000005 -+ -+/* -+ * Signals that a buffer can be refilled to the ME. -+ * -+ * The command must contain struct ipts_feedback_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_FEEDBACK 0x00000006 -+#define IPTS_RSP_FEEDBACK 0x80000006 -+ -+/* -+ * Resets the data flow from the ME to the hosts and -+ * clears the buffers that were set with SET_MEM_WINDOW. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007 -+#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007 -+ -+/* -+ * Instructs the ME to reset the touch sensor. -+ * -+ * The command must contain struct ipts_reset_sensor_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_RESET_SENSOR 0x0000000B -+#define IPTS_RSP_RESET_SENSOR 0x8000000B -+ -+/** -+ * enum ipts_status - Possible status codes returned by IPTS commands. -+ * @IPTS_STATUS_SUCCESS: Operation completed successfully. -+ * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. -+ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. -+ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. -+ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. -+ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. -+ * The host must wait for a response before sending another -+ * command of the same type. -+ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it -+ * has not been initialized yet, or the system is improperly -+ * configured. -+ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. -+ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. -+ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. -+ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. -+ * The host can ignore this error and attempt to continue. -+ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. -+ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. -+ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. -+ * @IPTS_STATUS_TIMEOUT: The operation timed out. -+ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. -+ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. -+ * Further progress is not possible. -+ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. -+ * The host can attempt to continue. -+ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. -+ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. -+ */ -+enum ipts_status { -+ IPTS_STATUS_SUCCESS = 0, -+ IPTS_STATUS_INVALID_PARAMS = 1, -+ IPTS_STATUS_ACCESS_DENIED = 2, -+ IPTS_STATUS_CMD_SIZE_ERROR = 3, -+ IPTS_STATUS_NOT_READY = 4, -+ IPTS_STATUS_REQUEST_OUTSTANDING = 5, -+ IPTS_STATUS_NO_SENSOR_FOUND = 6, -+ IPTS_STATUS_OUT_OF_MEMORY = 7, -+ IPTS_STATUS_INTERNAL_ERROR = 8, -+ IPTS_STATUS_SENSOR_DISABLED = 9, -+ IPTS_STATUS_COMPAT_CHECK_FAIL = 10, -+ IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, -+ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, -+ IPTS_STATUS_RESET_FAILED = 13, -+ IPTS_STATUS_TIMEOUT = 14, -+ IPTS_STATUS_TEST_MODE_FAIL = 15, -+ IPTS_STATUS_SENSOR_FAIL_FATAL = 16, -+ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, -+ IPTS_STATUS_INVALID_DEVICE_CAPS = 18, -+ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, -+}; -+ -+/* -+ * The amount of buffers that is used for IPTS -+ */ -+#define IPTS_BUFFERS 16 -+ -+/* -+ * The special buffer ID that is used for direct host2me feedback. -+ */ -+#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS -+ -+/** -+ * enum ipts_mode - Operation mode for IPTS hardware -+ * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. -+ * The data is received as a HID report with ID 64. -+ * @IPTS_MODE_MULTITOUCH: The "proper" operation mode for IPTS. It will return -+ * stylus data as well as capacitive heatmap touch data. -+ * This data needs to be processed in userspace. -+ */ -+enum ipts_mode { -+ IPTS_MODE_SINGLETOUCH = 0, -+ IPTS_MODE_MULTITOUCH = 1, -+}; -+ -+/** -+ * struct ipts_set_mode_cmd - Payload for the SET_MODE command. -+ * @mode: The mode that IPTS should operate in. -+ */ -+struct ipts_set_mode_cmd { -+ enum ipts_mode mode; -+ u8 reserved[12]; -+} __packed; -+ -+#define IPTS_WORKQUEUE_SIZE 8192 -+#define IPTS_WORKQUEUE_ITEM_SIZE 16 -+ -+/** -+ * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command. -+ * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses. -+ * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses. -+ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. -+ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. -+ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. -+ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. -+ * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses. -+ * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses. -+ * @host2me_addr_lower: Lower 32 bits of the host2me buffer address. -+ * @host2me_addr_upper: Upper 32 bits of the host2me buffer address. -+ * @workqueue_item_size: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) -+ * @workqueue_size: Magic value. (IPTS_WORKQUEUE_SIZE) -+ * -+ * The data buffers are buffers that get filled with touch data by the ME. -+ * The doorbell buffer is a u32 that gets incremented by the ME once a data -+ * buffer has been filled with new data. -+ * -+ * The other buffers are required for using GuC submission with binary -+ * firmware. Since support for GuC submission has been dropped from i915, -+ * they are not used anymore, but they need to be allocated and passed, -+ * otherwise the hardware will refuse to start. -+ */ -+struct ipts_set_mem_window_cmd { -+ u32 data_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 data_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 workqueue_addr_lower; -+ u32 workqueue_addr_upper; -+ u32 doorbell_addr_lower; -+ u32 doorbell_addr_upper; -+ u32 feedback_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 feedback_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 host2me_addr_lower; -+ u32 host2me_addr_upper; -+ u32 host2me_size; -+ u8 reserved1; -+ u8 workqueue_item_size; -+ u16 workqueue_size; -+ u8 reserved[32]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_cmd - Payload for the FEEDBACK command. -+ * @buffer: The buffer that the ME should refill. -+ */ -+struct ipts_feedback_cmd { -+ u32 buffer; -+ u8 reserved[12]; -+} __packed; -+ -+/** -+ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. -+ */ -+enum ipts_feedback_cmd_type { -+ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, -+ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, -+ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, -+}; -+ -+/** -+ * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains. -+ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. -+ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. -+ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. -+ */ -+enum ipts_feedback_data_type { -+ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, -+ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, -+ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, -+ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, -+ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, -+}; -+ -+/** -+ * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer. -+ * @cmd_type: A command that should be executed on the sensor. -+ * @size: The size of the payload to be written. -+ * @buffer: The ID of the buffer that contains this feedback data. -+ * @protocol: The protocol version of the EDS. -+ * @data_type: The type of payload that the buffer contains. -+ * @spi_offset: The offset at which to write the payload data. -+ * @payload: Payload for the feedback command, or 0 if no payload is sent. -+ */ -+struct ipts_feedback_buffer { -+ enum ipts_feedback_cmd_type cmd_type; -+ u32 size; -+ u32 buffer; -+ u32 protocol; -+ enum ipts_feedback_data_type data_type; -+ u32 spi_offset; -+ u8 reserved[40]; -+ u8 payload[]; -+} __packed; -+ -+/** -+ * enum ipts_reset_type - Possible ways of resetting the touch sensor -+ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. -+ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. -+ */ -+enum ipts_reset_type { -+ IPTS_RESET_TYPE_HARD = 0, -+ IPTS_RESET_TYPE_SOFT = 1, -+}; -+ -+/** -+ * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. -+ * @type: What type of reset should be performed. -+ */ -+struct ipts_reset_sensor_cmd { -+ enum ipts_reset_type type; -+ u8 reserved[4]; -+} __packed; -+ -+/** -+ * struct ipts_command - A message sent from the host to the ME. -+ * @code: The message code describing the command. (see IPTS_CMD_*) -+ * @payload: Payload for the command, or 0 if no payload is required. -+ */ -+struct ipts_command { -+ u32 code; -+ u8 payload[320]; -+} __packed; -+ -+/** -+ * struct ipts_device_info - Payload for the GET_DEVICE_INFO response. -+ * @vendor_id: Vendor ID of the touch sensor. -+ * @device_id: Device ID of the touch sensor. -+ * @hw_rev: Hardware revision of the touch sensor. -+ * @fw_rev: Firmware revision of the touch sensor. -+ * @data_size: Required size of one data buffer. -+ * @feedback_size: Required size of one feedback buffer. -+ * @mode: Current operation mode of IPTS. -+ * @max_contacts: The amount of concurrent touches supported by the sensor. -+ */ -+struct ipts_get_device_info_rsp { -+ u16 vendor_id; -+ u16 device_id; -+ u32 hw_rev; -+ u32 fw_rev; -+ u32 data_size; -+ u32 feedback_size; -+ enum ipts_mode mode; -+ u8 max_contacts; -+ u8 reserved[19]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_rsp - Payload for the FEEDBACK response. -+ * @buffer: The buffer that has received feedback. -+ */ -+struct ipts_feedback_rsp { -+ u32 buffer; -+} __packed; -+ -+/** -+ * struct ipts_response - A message sent from the ME to the host. -+ * @code: The message code describing the response. (see IPTS_RSP_*) -+ * @status: The status code returned by the command. -+ * @payload: Payload returned by the command. -+ */ -+struct ipts_response { -+ u32 code; -+ enum ipts_status status; -+ u8 payload[80]; -+} __packed; -+ -+#endif /* _IPTS_PROTOCOL_H_ */ -diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c -new file mode 100644 -index 000000000000..23dca13c2139 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.c -@@ -0,0 +1,224 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "resources.h" -+ -+/* -+ * Temporary parameter to guard gen7 multitouch mode. -+ * Remove once gen7 has stable iptsd support. -+ */ -+static bool gen7mt; -+module_param(gen7mt, bool, 0644); -+ -+static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_set_mode_cmd cmd; -+ -+ memcpy(&ipts->device_info, rsp->payload, -+ sizeof(struct ipts_get_device_info_rsp)); -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd)); -+ cmd.mode = IPTS_MODE_MULTITOUCH; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd, -+ sizeof(struct ipts_set_mode_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mode(struct ipts_context *ipts) -+{ -+ int i, ret; -+ struct ipts_set_mem_window_cmd cmd; -+ -+ ret = ipts_resources_alloc(ipts); -+ if (ret) { -+ dev_err(ipts->dev, "Failed to allocate resources\n"); -+ return ret; -+ } -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ cmd.data_buffer_addr_lower[i] = -+ lower_32_bits(ipts->data[i].dma_address); -+ -+ cmd.data_buffer_addr_upper[i] = -+ upper_32_bits(ipts->data[i].dma_address); -+ -+ cmd.feedback_buffer_addr_lower[i] = -+ lower_32_bits(ipts->feedback[i].dma_address); -+ -+ cmd.feedback_buffer_addr_upper[i] = -+ upper_32_bits(ipts->feedback[i].dma_address); -+ } -+ -+ cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); -+ cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); -+ -+ cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); -+ cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); -+ -+ cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); -+ cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); -+ -+ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; -+ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, -+ sizeof(struct ipts_set_mem_window_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ dev_info(ipts->dev, "Device %04hX:%04hX ready\n", -+ ipts->device_info.vendor_id, ipts->device_info.device_id); -+ ipts->status = IPTS_HOST_STATUS_STARTED; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); -+ if (ret) -+ return ret; -+ -+ if (!gen7mt) -+ return 0; -+ -+ return ipts_control_set_feature(ipts, 0x5, 0x1); -+} -+ -+static int ipts_receiver_handle_feedback(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_feedback_rsp feedback; -+ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPING) -+ return 0; -+ -+ memcpy(&feedback, rsp->payload, sizeof(feedback)); -+ -+ if (feedback.buffer < IPTS_BUFFERS - 1) -+ return ipts_control_send_feedback(ipts, feedback.buffer + 1); -+ -+ return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); -+} -+ -+static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) -+{ -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ if (ipts->restart) -+ return ipts_control_start(ipts); -+ -+ return 0; -+} -+ -+static bool ipts_receiver_sensor_was_reset(u32 status) -+{ -+ return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || -+ status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; -+} -+ -+static bool ipts_receiver_handle_error(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ bool error; -+ -+ switch (rsp->status) { -+ case IPTS_STATUS_SUCCESS: -+ case IPTS_STATUS_COMPAT_CHECK_FAIL: -+ error = false; -+ break; -+ case IPTS_STATUS_INVALID_PARAMS: -+ error = rsp->code != IPTS_RSP_FEEDBACK; -+ break; -+ case IPTS_STATUS_SENSOR_DISABLED: -+ error = ipts->status != IPTS_HOST_STATUS_STOPPING; -+ break; -+ default: -+ error = true; -+ break; -+ } -+ -+ if (!error) -+ return false; -+ -+ dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code, -+ rsp->status); -+ -+ if (ipts_receiver_sensor_was_reset(rsp->status)) { -+ dev_err(ipts->dev, "Sensor was reset\n"); -+ -+ if (ipts_control_restart(ipts)) -+ dev_err(ipts->dev, "Failed to restart IPTS\n"); -+ } -+ -+ return true; -+} -+ -+static void ipts_receiver_handle_response(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ int ret; -+ -+ if (ipts_receiver_handle_error(ipts, rsp)) -+ return; -+ -+ switch (rsp->code) { -+ case IPTS_RSP_GET_DEVICE_INFO: -+ ret = ipts_receiver_handle_get_device_info(ipts, rsp); -+ break; -+ case IPTS_RSP_SET_MODE: -+ ret = ipts_receiver_handle_set_mode(ipts); -+ break; -+ case IPTS_RSP_SET_MEM_WINDOW: -+ ret = ipts_receiver_handle_set_mem_window(ipts); -+ break; -+ case IPTS_RSP_FEEDBACK: -+ ret = ipts_receiver_handle_feedback(ipts, rsp); -+ break; -+ case IPTS_RSP_CLEAR_MEM_WINDOW: -+ ret = ipts_receiver_handle_clear_mem_window(ipts); -+ break; -+ default: -+ ret = 0; -+ break; -+ } -+ -+ if (!ret) -+ return; -+ -+ dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n", -+ rsp->code, ret); -+ -+ if (ipts_control_stop(ipts)) -+ dev_err(ipts->dev, "Failed to stop IPTS\n"); -+} -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev) -+{ -+ int ret; -+ struct ipts_response rsp; -+ struct ipts_context *ipts; -+ -+ ipts = mei_cldev_get_drvdata(cldev); -+ -+ ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response)); -+ if (ret <= 0) { -+ dev_err(ipts->dev, "Error while reading response: %d\n", ret); -+ return; -+ } -+ -+ ipts_receiver_handle_response(ipts, &rsp); -+} -diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h -new file mode 100644 -index 000000000000..7f075afa7ef8 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.h -@@ -0,0 +1,16 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RECEIVER_H_ -+#define _IPTS_RECEIVER_H_ -+ -+#include -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev); -+ -+#endif /* _IPTS_RECEIVER_H_ */ -diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c -new file mode 100644 -index 000000000000..8e3a2409e438 ---- /dev/null -+++ b/drivers/misc/ipts/resources.c -@@ -0,0 +1,128 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+ -+void ipts_resources_free(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, data_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ if (ipts->doorbell.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->doorbell.address, -+ ipts->doorbell.dma_address); -+ -+ ipts->doorbell.address = NULL; -+ ipts->doorbell.dma_address = 0; -+ } -+ -+ if (ipts->workqueue.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->workqueue.address, -+ ipts->workqueue.dma_address); -+ -+ ipts->workqueue.address = NULL; -+ ipts->workqueue.dma_address = 0; -+ } -+ -+ if (ipts->host2me.address) { -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ ipts->host2me.address, -+ ipts->host2me.dma_address); -+ -+ ipts->host2me.address = NULL; -+ ipts->host2me.dma_address = 0; -+ } -+} -+ -+int ipts_resources_alloc(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, data_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ ipts->doorbell.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->doorbell.dma_address, GFP_KERNEL); -+ -+ if (!ipts->doorbell.address) -+ goto release_resources; -+ -+ ipts->workqueue.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->workqueue.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ ipts->host2me.address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &ipts->host2me.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ return 0; -+ -+release_resources: -+ -+ ipts_resources_free(ipts); -+ return -ENOMEM; -+} -diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h -new file mode 100644 -index 000000000000..fdac0eee9156 ---- /dev/null -+++ b/drivers/misc/ipts/resources.h -@@ -0,0 +1,17 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RESOURCES_H_ -+#define _IPTS_RESOURCES_H_ -+ -+#include "context.h" -+ -+int ipts_resources_alloc(struct ipts_context *ipts); -+void ipts_resources_free(struct ipts_context *ipts); -+ -+#endif /* _IPTS_RESOURCES_H_ */ -diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c -new file mode 100644 -index 000000000000..598f0710ad64 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.c -@@ -0,0 +1,208 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "uapi.h" -+ -+struct ipts_uapi uapi; -+ -+static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count, -+ loff_t *offset) -+{ -+ int buffer; -+ int maxbytes; -+ struct ipts_context *ipts = uapi.ipts; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ maxbytes = ipts->device_info.data_size - *offset; -+ if (maxbytes <= 0 || count > maxbytes) -+ return -EINVAL; -+ -+ if (copy_to_user(buf, ipts->data[buffer].address + *offset, count)) -+ return -EFAULT; -+ -+ return count; -+} -+ -+static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ u8 ready = 0; -+ -+ if (ipts) -+ ready = ipts->status == IPTS_HOST_STATUS_STARTED; -+ -+ if (copy_to_user(buffer, &ready, sizeof(u8))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ struct ipts_device_info info; -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ info.vendor = ipts->device_info.vendor_id; -+ info.product = ipts->device_info.device_id; -+ info.version = ipts->device_info.fw_rev; -+ info.buffer_size = ipts->device_info.data_size; -+ info.max_contacts = ipts->device_info.max_contacts; -+ -+ if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts, -+ struct file *file) -+{ -+ int ret; -+ u32 buffer; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ ret = ipts_control_send_feedback(ipts, buffer); -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) -+{ -+ int ret; -+ struct ipts_reset_sensor_cmd cmd; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); -+ cmd.type = IPTS_RESET_TYPE_SOFT; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, -+ sizeof(struct ipts_reset_sensor_cmd)); -+ -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) -+{ -+ struct ipts_context *ipts = uapi.ipts; -+ -+ switch (cmd) { -+ case IPTS_IOCTL_GET_DEVICE_READY: -+ return ipts_uapi_ioctl_get_device_ready(ipts, arg); -+ case IPTS_IOCTL_GET_DEVICE_INFO: -+ return ipts_uapi_ioctl_get_device_info(ipts, arg); -+ case IPTS_IOCTL_GET_DOORBELL: -+ return ipts_uapi_ioctl_get_doorbell(ipts, arg); -+ case IPTS_IOCTL_SEND_FEEDBACK: -+ return ipts_uapi_ioctl_send_feedback(ipts, file); -+ case IPTS_IOCTL_SEND_RESET: -+ return ipts_uapi_ioctl_send_reset(ipts); -+ default: -+ return -ENOTTY; -+ } -+} -+ -+static const struct file_operations ipts_uapi_fops = { -+ .owner = THIS_MODULE, -+ .read = ipts_uapi_read, -+ .unlocked_ioctl = ipts_uapi_ioctl, -+#ifdef CONFIG_COMPAT -+ .compat_ioctl = ipts_uapi_ioctl, -+#endif -+}; -+ -+void ipts_uapi_link(struct ipts_context *ipts) -+{ -+ uapi.ipts = ipts; -+} -+ -+void ipts_uapi_unlink(void) -+{ -+ uapi.ipts = NULL; -+} -+ -+int ipts_uapi_init(void) -+{ -+ int i, major; -+ -+ alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts"); -+ uapi.class = class_create(THIS_MODULE, "ipts"); -+ -+ major = MAJOR(uapi.dev); -+ -+ cdev_init(&uapi.cdev, &ipts_uapi_fops); -+ uapi.cdev.owner = THIS_MODULE; -+ cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ device_create(uapi.class, NULL, MKDEV(major, i), NULL, -+ "ipts/%d", i); -+ } -+ -+ return 0; -+} -+ -+void ipts_uapi_free(void) -+{ -+ int i; -+ int major; -+ -+ major = MAJOR(uapi.dev); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) -+ device_destroy(uapi.class, MKDEV(major, i)); -+ -+ cdev_del(&uapi.cdev); -+ -+ unregister_chrdev_region(MKDEV(major, 0), MINORMASK); -+ class_destroy(uapi.class); -+} -diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h -new file mode 100644 -index 000000000000..53fb86a88f97 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_UAPI_H_ -+#define _IPTS_UAPI_H_ -+ -+#include -+ -+#include "context.h" -+ -+struct ipts_uapi { -+ dev_t dev; -+ struct class *class; -+ struct cdev cdev; -+ -+ struct ipts_context *ipts; -+}; -+ -+struct ipts_device_info { -+ __u16 vendor; -+ __u16 product; -+ __u32 version; -+ __u32 buffer_size; -+ __u8 max_contacts; -+ -+ /* For future expansion */ -+ __u8 reserved[19]; -+}; -+ -+#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8) -+#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info) -+#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) -+#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04) -+#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) -+ -+void ipts_uapi_link(struct ipts_context *ipts); -+void ipts_uapi_unlink(void); -+ -+int ipts_uapi_init(void); -+void ipts_uapi_free(void); -+ -+#endif /* _IPTS_UAPI_H_ */ --- -2.31.1 - diff --git a/patches/5.11/0004-surface-sam-over-hid.patch b/patches/5.11/0004-surface-sam-over-hid.patch deleted file mode 100644 index 53d05421d..000000000 --- a/patches/5.11/0004-surface-sam-over-hid.patch +++ /dev/null @@ -1,334 +0,0 @@ -From 0067d01f09367874f590189dcb2260ad9f6b4bda Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jul 2020 17:19:53 +0200 -Subject: [PATCH] i2c: acpi: Implement RawBytes read access - -Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C -device via a generic serial bus operation region and RawBytes read -access. On the Surface Book 1, this access is required to turn on (and -off) the discrete GPU. - -Multiple things are to note here: - -a) The RawBytes access is device/driver dependent. The ACPI - specification states: - - > Raw accesses assume that the writer has knowledge of the bus that - > the access is made over and the device that is being accessed. The - > protocol may only ensure that the buffer is transmitted to the - > appropriate driver, but the driver must be able to interpret the - > buffer to communicate to a register. - - Thus this implementation may likely not work on other devices - accessing I2C via the RawBytes accessor type. - -b) The MSHW0030 I2C device is an HID-over-I2C device which seems to - serve multiple functions: - - 1. It is the main access point for the legacy-type Surface Aggregator - Module (also referred to as SAM-over-HID, as opposed to the newer - SAM-over-SSH/UART). It has currently not been determined on how - support for the legacy SAM should be implemented. Likely via a - custom HID driver. - - 2. It seems to serve as the HID device for the Integrated Sensor Hub. - This might complicate matters with regards to implementing a - SAM-over-HID driver required by legacy SAM. - -In light of this, the simplest approach has been chosen for now. -However, it may make more sense regarding breakage and compatibility to -either provide functionality for replacing or enhancing the default -operation region handler via some additional API functions, or even to -completely blacklist MSHW0030 from the I2C core and provide a custom -driver for it. - -Replacing/enhancing the default operation region handler would, however, -either require some sort of secondary driver and access point for it, -from which the new API functions would be called and the new handler -(part) would be installed, or hard-coding them via some sort of -quirk-like interface into the I2C core. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam-over-hid ---- - drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ - 1 file changed, 35 insertions(+) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index 37c510d9347a..aed579942436 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -574,6 +574,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, - return (ret == 1) ? 0 : -EIO; - } - -+static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, -+ u8 *data, u8 data_len) -+{ -+ struct i2c_msg msgs[1]; -+ int ret = AE_OK; -+ -+ msgs[0].addr = client->addr; -+ msgs[0].flags = client->flags; -+ msgs[0].len = data_len + 1; -+ msgs[0].buf = data; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ -+ if (ret < 0) { -+ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); -+ return ret; -+ } -+ -+ /* 1 transfer must have completed successfully */ -+ return (ret == 1) ? 0 : -EIO; -+} -+ - static acpi_status - i2c_acpi_space_handler(u32 function, acpi_physical_address command, - u32 bits, u64 *value64, -@@ -675,6 +697,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, - } - break; - -+ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: -+ if (action == ACPI_READ) { -+ dev_warn(&adapter->dev, -+ "protocol 0x%02x not supported for client 0x%02x\n", -+ accessor_type, client->addr); -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } else { -+ status = acpi_gsb_i2c_write_raw_bytes(client, -+ gsb->data, info->access_length); -+ } -+ break; -+ - default: - dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", - accessor_type, client->addr); --- -2.31.1 - -From 51f8d840fd9411b02204f964ede35356fc4e001f Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 13 Feb 2021 16:41:18 +0100 -Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch - -Add driver exposing the discrete GPU power-switch of the Microsoft -Surface Book 1 to user-space. - -On the Surface Book 1, the dGPU power is controlled via the Surface -System Aggregator Module (SAM). The specific SAM-over-HID command for -this is exposed via ACPI. This module provides a simple driver exposing -the ACPI call via a sysfs parameter to user-space, so that users can -easily power-on/-off the dGPU. - -Patchset: surface-sam-over-hid ---- - drivers/platform/surface/Kconfig | 7 + - drivers/platform/surface/Makefile | 1 + - .../surface/surfacebook1_dgpu_switch.c | 162 ++++++++++++++++++ - 3 files changed, 170 insertions(+) - create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 2c941cdac9ee..b5dc9148066c 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -41,6 +41,13 @@ config SURFACE_3_POWER_OPREGION - This driver provides support for ACPI operation - region of the Surface 3 battery platform driver. - -+config SURFACE_BOOK1_DGPU_SWITCH -+ tristate "Surface Book 1 dGPU Switch Driver" -+ depends on SYSFS -+ help -+ This driver provides a sysfs switch to set the power-state of the -+ discrete GPU found on the Microsoft Surface Book 1. -+ - config SURFACE_GPE - tristate "Surface GPE/Lid Support Driver" - depends on DMI -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index cedfb027ded1..3d5fa0daa56b 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -7,5 +7,6 @@ - obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o - obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o - obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o -+obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o -diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c -new file mode 100644 -index 000000000000..8b816ed8f35c ---- /dev/null -+++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c -@@ -0,0 +1,162 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+ -+#include -+#include -+#include -+#include -+ -+ -+#ifdef pr_fmt -+#undef pr_fmt -+#endif -+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ -+ -+ -+static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, -+ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); -+ -+#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" -+#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" -+#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" -+ -+ -+static int sb1_dgpu_sw_dsmcall(void) -+{ -+ union acpi_object *ret; -+ acpi_handle handle; -+ acpi_status status; -+ -+ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); -+ if (status) -+ return -EINVAL; -+ -+ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); -+ if (!ret) -+ return -EINVAL; -+ -+ ACPI_FREE(ret); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgon(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGON: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-on dGPU via HGON\n"); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgof(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGOF: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-off dGPU via HGOF\n"); -+ return 0; -+} -+ -+ -+static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ int status, value; -+ -+ status = kstrtoint(buf, 0, &value); -+ if (status < 0) -+ return status; -+ -+ if (value != 1) -+ return -EINVAL; -+ -+ status = sb1_dgpu_sw_dsmcall(); -+ -+ return status < 0 ? status : len; -+} -+ -+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ bool power; -+ int status; -+ -+ status = kstrtobool(buf, &power); -+ if (status < 0) -+ return status; -+ -+ if (power) -+ status = sb1_dgpu_sw_hgon(); -+ else -+ status = sb1_dgpu_sw_hgof(); -+ -+ return status < 0 ? status : len; -+} -+ -+static DEVICE_ATTR_WO(dgpu_dsmcall); -+static DEVICE_ATTR_WO(dgpu_power); -+ -+static struct attribute *sb1_dgpu_sw_attrs[] = { -+ &dev_attr_dgpu_dsmcall.attr, -+ &dev_attr_dgpu_power.attr, -+ NULL, -+}; -+ -+static const struct attribute_group sb1_dgpu_sw_attr_group = { -+ .attrs = sb1_dgpu_sw_attrs, -+}; -+ -+ -+static int sb1_dgpu_sw_probe(struct platform_device *pdev) -+{ -+ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+} -+ -+static int sb1_dgpu_sw_remove(struct platform_device *pdev) -+{ -+ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+ return 0; -+} -+ -+/* -+ * The dGPU power seems to be actually handled by MSHW0040. However, that is -+ * also the power-/volume-button device with a mainline driver. So let's use -+ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. -+ */ -+static const struct acpi_device_id sb1_dgpu_sw_match[] = { -+ { "MSHW0041", }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); -+ -+static struct platform_driver sb1_dgpu_sw = { -+ .probe = sb1_dgpu_sw_probe, -+ .remove = sb1_dgpu_sw_remove, -+ .driver = { -+ .name = "surfacebook1_dgpu_switch", -+ .acpi_match_table = sb1_dgpu_sw_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(sb1_dgpu_sw); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); -+MODULE_LICENSE("GPL"); --- -2.31.1 - diff --git a/patches/5.11/0005-surface-sam.patch b/patches/5.11/0005-surface-sam.patch deleted file mode 100644 index 29cb6c9a1..000000000 --- a/patches/5.11/0005-surface-sam.patch +++ /dev/null @@ -1,23461 +0,0 @@ -From cf1d8358199d3211ef711dc396c94958a00bc610 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 21 Dec 2020 19:39:51 +0100 -Subject: [PATCH] platform/surface: Add Surface Aggregator subsystem - -Add Surface System Aggregator Module core and Surface Serial Hub driver, -required for the embedded controller found on Microsoft Surface devices. - -The Surface System Aggregator Module (SSAM, SAM or Surface Aggregator) -is an embedded controller (EC) found on 4th and later generation -Microsoft Surface devices, with the exception of the Surface Go series. -This EC provides various functionality, depending on the device in -question. This can include battery status and thermal reporting (5th and -later generations), but also HID keyboard (6th+) and touchpad input -(7th+) on Surface Laptop and Surface Book 3 series devices. - -This patch provides the basic necessities for communication with the SAM -EC on 5th and later generation devices. On these devices, the EC -provides an interface that acts as serial device, called the Surface -Serial Hub (SSH). 4th generation devices, on which the EC interface is -provided via an HID-over-I2C device, are not supported by this patch. - -Specifically, this patch adds a driver for the SSH device (device HID -MSHW0084 in ACPI), as well as a controller structure and associated API. -This represents the functional core of the Surface Aggregator kernel -subsystem, introduced with this patch, and will be expanded upon in -subsequent commits. - -The SSH driver acts as the main attachment point for this subsystem and -sets-up and manages the controller structure. The controller in turn -provides a basic communication interface, allowing to send requests from -host to EC and receiving the corresponding responses, as well as -managing and receiving events, sent from EC to host. It is structured -into multiple layers, with the top layer presenting the API used by -other kernel drivers and the lower layers modeled after the serial -protocol used for communication. - -Said other drivers are then responsible for providing the (Surface model -specific) functionality accessible through the EC (e.g. battery status -reporting, thermal information, ...) via said controller structure and -API, and will be added in future commits. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20201221183959.1186143-2-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - MAINTAINERS | 8 + - drivers/platform/surface/Kconfig | 2 + - drivers/platform/surface/Makefile | 1 + - drivers/platform/surface/aggregator/Kconfig | 42 + - drivers/platform/surface/aggregator/Makefile | 10 + - .../platform/surface/aggregator/controller.c | 2504 +++++++++++++++++ - .../platform/surface/aggregator/controller.h | 276 ++ - drivers/platform/surface/aggregator/core.c | 787 ++++++ - .../platform/surface/aggregator/ssh_msgb.h | 205 ++ - .../surface/aggregator/ssh_packet_layer.c | 1710 +++++++++++ - .../surface/aggregator/ssh_packet_layer.h | 187 ++ - .../platform/surface/aggregator/ssh_parser.c | 228 ++ - .../platform/surface/aggregator/ssh_parser.h | 154 + - .../surface/aggregator/ssh_request_layer.c | 1211 ++++++++ - .../surface/aggregator/ssh_request_layer.h | 143 + - include/linux/surface_aggregator/controller.h | 824 ++++++ - include/linux/surface_aggregator/serial_hub.h | 672 +++++ - 17 files changed, 8964 insertions(+) - create mode 100644 drivers/platform/surface/aggregator/Kconfig - create mode 100644 drivers/platform/surface/aggregator/Makefile - create mode 100644 drivers/platform/surface/aggregator/controller.c - create mode 100644 drivers/platform/surface/aggregator/controller.h - create mode 100644 drivers/platform/surface/aggregator/core.c - create mode 100644 drivers/platform/surface/aggregator/ssh_msgb.h - create mode 100644 drivers/platform/surface/aggregator/ssh_packet_layer.c - create mode 100644 drivers/platform/surface/aggregator/ssh_packet_layer.h - create mode 100644 drivers/platform/surface/aggregator/ssh_parser.c - create mode 100644 drivers/platform/surface/aggregator/ssh_parser.h - create mode 100644 drivers/platform/surface/aggregator/ssh_request_layer.c - create mode 100644 drivers/platform/surface/aggregator/ssh_request_layer.h - create mode 100644 include/linux/surface_aggregator/controller.h - create mode 100644 include/linux/surface_aggregator/serial_hub.h - -diff --git a/MAINTAINERS b/MAINTAINERS -index b6ab9c1a2119..530792c869c4 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11806,6 +11806,14 @@ L: platform-driver-x86@vger.kernel.org - S: Supported - F: drivers/platform/surface/surfacepro3_button.c - -+MICROSOFT SURFACE SYSTEM AGGREGATOR SUBSYSTEM -+M: Maximilian Luz -+S: Maintained -+W: https://github.com/linux-surface/surface-aggregator-module -+C: irc://chat.freenode.net/##linux-surface -+F: drivers/platform/surface/aggregator/ -+F: include/linux/surface_aggregator/ -+ - MICROTEK X6 SCANNER - M: Oliver Neukum - S: Maintained -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index b5dc9148066c..ef6b4051e7c8 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -63,4 +63,6 @@ config SURFACE_PRO3_BUTTON - help - This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet. - -+source "drivers/platform/surface/aggregator/Kconfig" -+ - endif # SURFACE_PLATFORMS -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 3d5fa0daa56b..c5392098cfb9 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -7,6 +7,7 @@ - obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o - obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o - obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o -+obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o -diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig -new file mode 100644 -index 000000000000..e9f4ad96e40a ---- /dev/null -+++ b/drivers/platform/surface/aggregator/Kconfig -@@ -0,0 +1,42 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+# Copyright (C) 2019-2020 Maximilian Luz -+ -+menuconfig SURFACE_AGGREGATOR -+ tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" -+ depends on SERIAL_DEV_BUS -+ select CRC_CCITT -+ help -+ The Surface System Aggregator Module (Surface SAM or SSAM) is an -+ embedded controller (EC) found on 5th- and later-generation Microsoft -+ Surface devices (i.e. Surface Pro 5, Surface Book 2, Surface Laptop, -+ and newer, with exception of Surface Go series devices). -+ -+ Depending on the device in question, this EC provides varying -+ functionality, including: -+ - EC access from ACPI via Surface ACPI Notify (5th- and 6th-generation) -+ - battery status information (all devices) -+ - thermal sensor access (all devices) -+ - performance mode / cooling mode control (all devices) -+ - clipboard detachment system control (Surface Book 2 and 3) -+ - HID / keyboard input (Surface Laptops, Surface Book 3) -+ -+ This option controls whether the Surface SAM subsystem core will be -+ built. This includes a driver for the Surface Serial Hub (SSH), which -+ is the device responsible for the communication with the EC, and a -+ basic kernel interface exposing the EC functionality to other client -+ drivers, i.e. allowing them to make requests to the EC and receive -+ events from it. Selecting this option alone will not provide any -+ client drivers and therefore no functionality beyond the in-kernel -+ interface. Said functionality is the responsibility of the respective -+ client drivers. -+ -+ Note: While 4th-generation Surface devices also make use of a SAM EC, -+ due to a difference in the communication interface of the controller, -+ only 5th and later generations are currently supported. Specifically, -+ devices using SAM-over-SSH are supported, whereas devices using -+ SAM-over-HID, which is used on the 4th generation, are currently not -+ supported. -+ -+ Choose m if you want to build the SAM subsystem core and SSH driver as -+ module, y if you want to build it into the kernel and n if you don't -+ want it at all. -diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile -new file mode 100644 -index 000000000000..faad18d4a7f2 ---- /dev/null -+++ b/drivers/platform/surface/aggregator/Makefile -@@ -0,0 +1,10 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+# Copyright (C) 2019-2020 Maximilian Luz -+ -+obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator.o -+ -+surface_aggregator-objs := core.o -+surface_aggregator-objs += ssh_parser.o -+surface_aggregator-objs += ssh_packet_layer.o -+surface_aggregator-objs += ssh_request_layer.o -+surface_aggregator-objs += controller.o -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -new file mode 100644 -index 000000000000..488318cf2098 ---- /dev/null -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -0,0 +1,2504 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Main SSAM/SSH controller structure and functionality. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "controller.h" -+#include "ssh_msgb.h" -+#include "ssh_request_layer.h" -+ -+ -+/* -- Safe counters. -------------------------------------------------------- */ -+ -+/** -+ * ssh_seq_reset() - Reset/initialize sequence ID counter. -+ * @c: The counter to reset. -+ */ -+static void ssh_seq_reset(struct ssh_seq_counter *c) -+{ -+ WRITE_ONCE(c->value, 0); -+} -+ -+/** -+ * ssh_seq_next() - Get next sequence ID. -+ * @c: The counter providing the sequence IDs. -+ * -+ * Return: Returns the next sequence ID of the counter. -+ */ -+static u8 ssh_seq_next(struct ssh_seq_counter *c) -+{ -+ u8 old = READ_ONCE(c->value); -+ u8 new = old + 1; -+ u8 ret; -+ -+ while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { -+ old = ret; -+ new = old + 1; -+ } -+ -+ return old; -+} -+ -+/** -+ * ssh_rqid_reset() - Reset/initialize request ID counter. -+ * @c: The counter to reset. -+ */ -+static void ssh_rqid_reset(struct ssh_rqid_counter *c) -+{ -+ WRITE_ONCE(c->value, 0); -+} -+ -+/** -+ * ssh_rqid_next() - Get next request ID. -+ * @c: The counter providing the request IDs. -+ * -+ * Return: Returns the next request ID of the counter, skipping any reserved -+ * request IDs. -+ */ -+static u16 ssh_rqid_next(struct ssh_rqid_counter *c) -+{ -+ u16 old = READ_ONCE(c->value); -+ u16 new = ssh_rqid_next_valid(old); -+ u16 ret; -+ -+ while (unlikely((ret = cmpxchg(&c->value, old, new)) != old)) { -+ old = ret; -+ new = ssh_rqid_next_valid(old); -+ } -+ -+ return old; -+} -+ -+ -+/* -- Event notifier/callbacks. --------------------------------------------- */ -+/* -+ * The notifier system is based on linux/notifier.h, specifically the SRCU -+ * implementation. The difference to that is, that some bits of the notifier -+ * call return value can be tracked across multiple calls. This is done so -+ * that handling of events can be tracked and a warning can be issued in case -+ * an event goes unhandled. The idea of that warning is that it should help -+ * discover and identify new/currently unimplemented features. -+ */ -+ -+/** -+ * ssam_event_matches_notifier() - Test if an event matches a notifier. -+ * @n: The event notifier to test against. -+ * @event: The event to test. -+ * -+ * Return: Returns %true if the given event matches the given notifier -+ * according to the rules set in the notifier's event mask, %false otherwise. -+ */ -+static bool ssam_event_matches_notifier(const struct ssam_event_notifier *n, -+ const struct ssam_event *event) -+{ -+ bool match = n->event.id.target_category == event->target_category; -+ -+ if (n->event.mask & SSAM_EVENT_MASK_TARGET) -+ match &= n->event.reg.target_id == event->target_id; -+ -+ if (n->event.mask & SSAM_EVENT_MASK_INSTANCE) -+ match &= n->event.id.instance == event->instance_id; -+ -+ return match; -+} -+ -+/** -+ * ssam_nfblk_call_chain() - Call event notifier callbacks of the given chain. -+ * @nh: The notifier head for which the notifier callbacks should be called. -+ * @event: The event data provided to the callbacks. -+ * -+ * Call all registered notifier callbacks in order of their priority until -+ * either no notifier is left or a notifier returns a value with the -+ * %SSAM_NOTIF_STOP bit set. Note that this bit is automatically set via -+ * ssam_notifier_from_errno() on any non-zero error value. -+ * -+ * Return: Returns the notifier status value, which contains the notifier -+ * status bits (%SSAM_NOTIF_HANDLED and %SSAM_NOTIF_STOP) as well as a -+ * potential error value returned from the last executed notifier callback. -+ * Use ssam_notifier_to_errno() to convert this value to the original error -+ * value. -+ */ -+static int ssam_nfblk_call_chain(struct ssam_nf_head *nh, struct ssam_event *event) -+{ -+ struct ssam_event_notifier *nf; -+ int ret = 0, idx; -+ -+ idx = srcu_read_lock(&nh->srcu); -+ -+ list_for_each_entry_rcu(nf, &nh->head, base.node, -+ srcu_read_lock_held(&nh->srcu)) { -+ if (ssam_event_matches_notifier(nf, event)) { -+ ret = (ret & SSAM_NOTIF_STATE_MASK) | nf->base.fn(nf, event); -+ if (ret & SSAM_NOTIF_STOP) -+ break; -+ } -+ } -+ -+ srcu_read_unlock(&nh->srcu, idx); -+ return ret; -+} -+ -+/** -+ * ssam_nfblk_insert() - Insert a new notifier block into the given notifier -+ * list. -+ * @nh: The notifier head into which the block should be inserted. -+ * @nb: The notifier block to add. -+ * -+ * Note: This function must be synchronized by the caller with respect to other -+ * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. -+ * -+ * Return: Returns zero on success, %-EEXIST if the notifier block has already -+ * been registered. -+ */ -+static int ssam_nfblk_insert(struct ssam_nf_head *nh, struct ssam_notifier_block *nb) -+{ -+ struct ssam_notifier_block *p; -+ struct list_head *h; -+ -+ /* Runs under lock, no need for RCU variant. */ -+ list_for_each(h, &nh->head) { -+ p = list_entry(h, struct ssam_notifier_block, node); -+ -+ if (unlikely(p == nb)) { -+ WARN(1, "double register detected"); -+ return -EEXIST; -+ } -+ -+ if (nb->priority > p->priority) -+ break; -+ } -+ -+ list_add_tail_rcu(&nb->node, h); -+ return 0; -+} -+ -+/** -+ * ssam_nfblk_find() - Check if a notifier block is registered on the given -+ * notifier head. -+ * list. -+ * @nh: The notifier head on which to search. -+ * @nb: The notifier block to search for. -+ * -+ * Note: This function must be synchronized by the caller with respect to other -+ * insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. -+ * -+ * Return: Returns true if the given notifier block is registered on the given -+ * notifier head, false otherwise. -+ */ -+static bool ssam_nfblk_find(struct ssam_nf_head *nh, struct ssam_notifier_block *nb) -+{ -+ struct ssam_notifier_block *p; -+ -+ /* Runs under lock, no need for RCU variant. */ -+ list_for_each_entry(p, &nh->head, node) { -+ if (p == nb) -+ return true; -+ } -+ -+ return false; -+} -+ -+/** -+ * ssam_nfblk_remove() - Remove a notifier block from its notifier list. -+ * @nb: The notifier block to be removed. -+ * -+ * Note: This function must be synchronized by the caller with respect to -+ * other insert, find, and/or remove calls by holding ``struct ssam_nf.lock``. -+ * Furthermore, the caller _must_ ensure SRCU synchronization by calling -+ * synchronize_srcu() with ``nh->srcu`` after leaving the critical section, to -+ * ensure that the removed notifier block is not in use any more. -+ */ -+static void ssam_nfblk_remove(struct ssam_notifier_block *nb) -+{ -+ list_del_rcu(&nb->node); -+} -+ -+/** -+ * ssam_nf_head_init() - Initialize the given notifier head. -+ * @nh: The notifier head to initialize. -+ */ -+static int ssam_nf_head_init(struct ssam_nf_head *nh) -+{ -+ int status; -+ -+ status = init_srcu_struct(&nh->srcu); -+ if (status) -+ return status; -+ -+ INIT_LIST_HEAD(&nh->head); -+ return 0; -+} -+ -+/** -+ * ssam_nf_head_destroy() - Deinitialize the given notifier head. -+ * @nh: The notifier head to deinitialize. -+ */ -+static void ssam_nf_head_destroy(struct ssam_nf_head *nh) -+{ -+ cleanup_srcu_struct(&nh->srcu); -+} -+ -+ -+/* -- Event/notification registry. ------------------------------------------ */ -+ -+/** -+ * struct ssam_nf_refcount_key - Key used for event activation reference -+ * counting. -+ * @reg: The registry via which the event is enabled/disabled. -+ * @id: The ID uniquely describing the event. -+ */ -+struct ssam_nf_refcount_key { -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+}; -+ -+/** -+ * struct ssam_nf_refcount_entry - RB-tree entry for reference counting event -+ * activations. -+ * @node: The node of this entry in the rb-tree. -+ * @key: The key of the event. -+ * @refcount: The reference-count of the event. -+ * @flags: The flags used when enabling the event. -+ */ -+struct ssam_nf_refcount_entry { -+ struct rb_node node; -+ struct ssam_nf_refcount_key key; -+ int refcount; -+ u8 flags; -+}; -+ -+/** -+ * ssam_nf_refcount_inc() - Increment reference-/activation-count of the given -+ * event. -+ * @nf: The notifier system reference. -+ * @reg: The registry used to enable/disable the event. -+ * @id: The event ID. -+ * -+ * Increments the reference-/activation-count associated with the specified -+ * event type/ID, allocating a new entry for this event ID if necessary. A -+ * newly allocated entry will have a refcount of one. -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ * -+ * Return: Returns the refcount entry on success. Returns an error pointer -+ * with %-ENOSPC if there have already been %INT_MAX events of the specified -+ * ID and type registered, or %-ENOMEM if the entry could not be allocated. -+ */ -+static struct ssam_nf_refcount_entry * -+ssam_nf_refcount_inc(struct ssam_nf *nf, struct ssam_event_registry reg, -+ struct ssam_event_id id) -+{ -+ struct ssam_nf_refcount_entry *entry; -+ struct ssam_nf_refcount_key key; -+ struct rb_node **link = &nf->refcount.rb_node; -+ struct rb_node *parent = NULL; -+ int cmp; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ key.reg = reg; -+ key.id = id; -+ -+ while (*link) { -+ entry = rb_entry(*link, struct ssam_nf_refcount_entry, node); -+ parent = *link; -+ -+ cmp = memcmp(&key, &entry->key, sizeof(key)); -+ if (cmp < 0) { -+ link = &(*link)->rb_left; -+ } else if (cmp > 0) { -+ link = &(*link)->rb_right; -+ } else if (entry->refcount < INT_MAX) { -+ entry->refcount++; -+ return entry; -+ } else { -+ WARN_ON(1); -+ return ERR_PTR(-ENOSPC); -+ } -+ } -+ -+ entry = kzalloc(sizeof(*entry), GFP_KERNEL); -+ if (!entry) -+ return ERR_PTR(-ENOMEM); -+ -+ entry->key = key; -+ entry->refcount = 1; -+ -+ rb_link_node(&entry->node, parent, link); -+ rb_insert_color(&entry->node, &nf->refcount); -+ -+ return entry; -+} -+ -+/** -+ * ssam_nf_refcount_dec() - Decrement reference-/activation-count of the given -+ * event. -+ * @nf: The notifier system reference. -+ * @reg: The registry used to enable/disable the event. -+ * @id: The event ID. -+ * -+ * Decrements the reference-/activation-count of the specified event, -+ * returning its entry. If the returned entry has a refcount of zero, the -+ * caller is responsible for freeing it using kfree(). -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ * -+ * Return: Returns the refcount entry on success or %NULL if the entry has not -+ * been found. -+ */ -+static struct ssam_nf_refcount_entry * -+ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg, -+ struct ssam_event_id id) -+{ -+ struct ssam_nf_refcount_entry *entry; -+ struct ssam_nf_refcount_key key; -+ struct rb_node *node = nf->refcount.rb_node; -+ int cmp; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ key.reg = reg; -+ key.id = id; -+ -+ while (node) { -+ entry = rb_entry(node, struct ssam_nf_refcount_entry, node); -+ -+ cmp = memcmp(&key, &entry->key, sizeof(key)); -+ if (cmp < 0) { -+ node = node->rb_left; -+ } else if (cmp > 0) { -+ node = node->rb_right; -+ } else { -+ entry->refcount--; -+ if (entry->refcount == 0) -+ rb_erase(&entry->node, &nf->refcount); -+ -+ return entry; -+ } -+ } -+ -+ return NULL; -+} -+ -+/** -+ * ssam_nf_refcount_empty() - Test if the notification system has any -+ * enabled/active events. -+ * @nf: The notification system. -+ */ -+static bool ssam_nf_refcount_empty(struct ssam_nf *nf) -+{ -+ return RB_EMPTY_ROOT(&nf->refcount); -+} -+ -+/** -+ * ssam_nf_call() - Call notification callbacks for the provided event. -+ * @nf: The notifier system -+ * @dev: The associated device, only used for logging. -+ * @rqid: The request ID of the event. -+ * @event: The event provided to the callbacks. -+ * -+ * Execute registered callbacks in order of their priority until either no -+ * callback is left or a callback returns a value with the %SSAM_NOTIF_STOP -+ * bit set. Note that this bit is set automatically when converting non-zero -+ * error values via ssam_notifier_from_errno() to notifier values. -+ * -+ * Also note that any callback that could handle an event should return a value -+ * with bit %SSAM_NOTIF_HANDLED set, indicating that the event does not go -+ * unhandled/ignored. In case no registered callback could handle an event, -+ * this function will emit a warning. -+ * -+ * In case a callback failed, this function will emit an error message. -+ */ -+static void ssam_nf_call(struct ssam_nf *nf, struct device *dev, u16 rqid, -+ struct ssam_event *event) -+{ -+ struct ssam_nf_head *nf_head; -+ int status, nf_ret; -+ -+ if (!ssh_rqid_is_event(rqid)) { -+ dev_warn(dev, "event: unsupported rqid: %#06x\n", rqid); -+ return; -+ } -+ -+ nf_head = &nf->head[ssh_rqid_to_event(rqid)]; -+ nf_ret = ssam_nfblk_call_chain(nf_head, event); -+ status = ssam_notifier_to_errno(nf_ret); -+ -+ if (status < 0) { -+ dev_err(dev, -+ "event: error handling event: %d (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", -+ status, event->target_category, event->target_id, -+ event->command_id, event->instance_id); -+ } else if (!(nf_ret & SSAM_NOTIF_HANDLED)) { -+ dev_warn(dev, -+ "event: unhandled event (rqid: %#04x, tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", -+ rqid, event->target_category, event->target_id, -+ event->command_id, event->instance_id); -+ } -+} -+ -+/** -+ * ssam_nf_init() - Initialize the notifier system. -+ * @nf: The notifier system to initialize. -+ */ -+static int ssam_nf_init(struct ssam_nf *nf) -+{ -+ int i, status; -+ -+ for (i = 0; i < SSH_NUM_EVENTS; i++) { -+ status = ssam_nf_head_init(&nf->head[i]); -+ if (status) -+ break; -+ } -+ -+ if (status) { -+ while (i--) -+ ssam_nf_head_destroy(&nf->head[i]); -+ -+ return status; -+ } -+ -+ mutex_init(&nf->lock); -+ return 0; -+} -+ -+/** -+ * ssam_nf_destroy() - Deinitialize the notifier system. -+ * @nf: The notifier system to deinitialize. -+ */ -+static void ssam_nf_destroy(struct ssam_nf *nf) -+{ -+ int i; -+ -+ for (i = 0; i < SSH_NUM_EVENTS; i++) -+ ssam_nf_head_destroy(&nf->head[i]); -+ -+ mutex_destroy(&nf->lock); -+} -+ -+ -+/* -- Event/async request completion system. -------------------------------- */ -+ -+#define SSAM_CPLT_WQ_NAME "ssam_cpltq" -+ -+/* -+ * SSAM_CPLT_WQ_BATCH - Maximum number of event item completions executed per -+ * work execution. Used to prevent livelocking of the workqueue. Value chosen -+ * via educated guess, may be adjusted. -+ */ -+#define SSAM_CPLT_WQ_BATCH 10 -+ -+/** -+ * ssam_event_item_alloc() - Allocate an event item with the given payload size. -+ * @len: The event payload length. -+ * @flags: The flags used for allocation. -+ * -+ * Allocate an event item with the given payload size. Sets the item -+ * operations and payload length values. The item free callback (``ops.free``) -+ * should not be overwritten after this call. -+ * -+ * Return: Returns the newly allocated event item. -+ */ -+static struct ssam_event_item *ssam_event_item_alloc(size_t len, gfp_t flags) -+{ -+ struct ssam_event_item *item; -+ -+ item = kzalloc(struct_size(item, event.data, len), flags); -+ if (!item) -+ return NULL; -+ -+ item->event.length = len; -+ return item; -+} -+ -+/** -+ * ssam_event_queue_push() - Push an event item to the event queue. -+ * @q: The event queue. -+ * @item: The item to add. -+ */ -+static void ssam_event_queue_push(struct ssam_event_queue *q, -+ struct ssam_event_item *item) -+{ -+ spin_lock(&q->lock); -+ list_add_tail(&item->node, &q->head); -+ spin_unlock(&q->lock); -+} -+ -+/** -+ * ssam_event_queue_pop() - Pop the next event item from the event queue. -+ * @q: The event queue. -+ * -+ * Returns and removes the next event item from the queue. Returns %NULL If -+ * there is no event item left. -+ */ -+static struct ssam_event_item *ssam_event_queue_pop(struct ssam_event_queue *q) -+{ -+ struct ssam_event_item *item; -+ -+ spin_lock(&q->lock); -+ item = list_first_entry_or_null(&q->head, struct ssam_event_item, node); -+ if (item) -+ list_del(&item->node); -+ spin_unlock(&q->lock); -+ -+ return item; -+} -+ -+/** -+ * ssam_event_queue_is_empty() - Check if the event queue is empty. -+ * @q: The event queue. -+ */ -+static bool ssam_event_queue_is_empty(struct ssam_event_queue *q) -+{ -+ bool empty; -+ -+ spin_lock(&q->lock); -+ empty = list_empty(&q->head); -+ spin_unlock(&q->lock); -+ -+ return empty; -+} -+ -+/** -+ * ssam_cplt_get_event_queue() - Get the event queue for the given parameters. -+ * @cplt: The completion system on which to look for the queue. -+ * @tid: The target ID of the queue. -+ * @rqid: The request ID representing the event ID for which to get the queue. -+ * -+ * Return: Returns the event queue corresponding to the event type described -+ * by the given parameters. If the request ID does not represent an event, -+ * this function returns %NULL. If the target ID is not supported, this -+ * function will fall back to the default target ID (``tid = 1``). -+ */ -+static -+struct ssam_event_queue *ssam_cplt_get_event_queue(struct ssam_cplt *cplt, -+ u8 tid, u16 rqid) -+{ -+ u16 event = ssh_rqid_to_event(rqid); -+ u16 tidx = ssh_tid_to_index(tid); -+ -+ if (!ssh_rqid_is_event(rqid)) { -+ dev_err(cplt->dev, "event: unsupported request ID: %#06x\n", rqid); -+ return NULL; -+ } -+ -+ if (!ssh_tid_is_valid(tid)) { -+ dev_warn(cplt->dev, "event: unsupported target ID: %u\n", tid); -+ tidx = 0; -+ } -+ -+ return &cplt->event.target[tidx].queue[event]; -+} -+ -+/** -+ * ssam_cplt_submit() - Submit a work item to the completion system workqueue. -+ * @cplt: The completion system. -+ * @work: The work item to submit. -+ */ -+static bool ssam_cplt_submit(struct ssam_cplt *cplt, struct work_struct *work) -+{ -+ return queue_work(cplt->wq, work); -+} -+ -+/** -+ * ssam_cplt_submit_event() - Submit an event to the completion system. -+ * @cplt: The completion system. -+ * @item: The event item to submit. -+ * -+ * Submits the event to the completion system by queuing it on the event item -+ * queue and queuing the respective event queue work item on the completion -+ * workqueue, which will eventually complete the event. -+ * -+ * Return: Returns zero on success, %-EINVAL if there is no event queue that -+ * can handle the given event item. -+ */ -+static int ssam_cplt_submit_event(struct ssam_cplt *cplt, -+ struct ssam_event_item *item) -+{ -+ struct ssam_event_queue *evq; -+ -+ evq = ssam_cplt_get_event_queue(cplt, item->event.target_id, item->rqid); -+ if (!evq) -+ return -EINVAL; -+ -+ ssam_event_queue_push(evq, item); -+ ssam_cplt_submit(cplt, &evq->work); -+ return 0; -+} -+ -+/** -+ * ssam_cplt_flush() - Flush the completion system. -+ * @cplt: The completion system. -+ * -+ * Flush the completion system by waiting until all currently submitted work -+ * items have been completed. -+ * -+ * Note: This function does not guarantee that all events will have been -+ * handled once this call terminates. In case of a larger number of -+ * to-be-completed events, the event queue work function may re-schedule its -+ * work item, which this flush operation will ignore. -+ * -+ * This operation is only intended to, during normal operation prior to -+ * shutdown, try to complete most events and requests to get them out of the -+ * system while the system is still fully operational. It does not aim to -+ * provide any guarantee that all of them have been handled. -+ */ -+static void ssam_cplt_flush(struct ssam_cplt *cplt) -+{ -+ flush_workqueue(cplt->wq); -+} -+ -+static void ssam_event_queue_work_fn(struct work_struct *work) -+{ -+ struct ssam_event_queue *queue; -+ struct ssam_event_item *item; -+ struct ssam_nf *nf; -+ struct device *dev; -+ unsigned int iterations = SSAM_CPLT_WQ_BATCH; -+ -+ queue = container_of(work, struct ssam_event_queue, work); -+ nf = &queue->cplt->event.notif; -+ dev = queue->cplt->dev; -+ -+ /* Limit number of processed events to avoid livelocking. */ -+ do { -+ item = ssam_event_queue_pop(queue); -+ if (!item) -+ return; -+ -+ ssam_nf_call(nf, dev, item->rqid, &item->event); -+ kfree(item); -+ } while (--iterations); -+ -+ if (!ssam_event_queue_is_empty(queue)) -+ ssam_cplt_submit(queue->cplt, &queue->work); -+} -+ -+/** -+ * ssam_event_queue_init() - Initialize an event queue. -+ * @cplt: The completion system on which the queue resides. -+ * @evq: The event queue to initialize. -+ */ -+static void ssam_event_queue_init(struct ssam_cplt *cplt, -+ struct ssam_event_queue *evq) -+{ -+ evq->cplt = cplt; -+ spin_lock_init(&evq->lock); -+ INIT_LIST_HEAD(&evq->head); -+ INIT_WORK(&evq->work, ssam_event_queue_work_fn); -+} -+ -+/** -+ * ssam_cplt_init() - Initialize completion system. -+ * @cplt: The completion system to initialize. -+ * @dev: The device used for logging. -+ */ -+static int ssam_cplt_init(struct ssam_cplt *cplt, struct device *dev) -+{ -+ struct ssam_event_target *target; -+ int status, c, i; -+ -+ cplt->dev = dev; -+ -+ cplt->wq = create_workqueue(SSAM_CPLT_WQ_NAME); -+ if (!cplt->wq) -+ return -ENOMEM; -+ -+ for (c = 0; c < ARRAY_SIZE(cplt->event.target); c++) { -+ target = &cplt->event.target[c]; -+ -+ for (i = 0; i < ARRAY_SIZE(target->queue); i++) -+ ssam_event_queue_init(cplt, &target->queue[i]); -+ } -+ -+ status = ssam_nf_init(&cplt->event.notif); -+ if (status) -+ destroy_workqueue(cplt->wq); -+ -+ return status; -+} -+ -+/** -+ * ssam_cplt_destroy() - Deinitialize the completion system. -+ * @cplt: The completion system to deinitialize. -+ * -+ * Deinitialize the given completion system and ensure that all pending, i.e. -+ * yet-to-be-completed, event items and requests have been handled. -+ */ -+static void ssam_cplt_destroy(struct ssam_cplt *cplt) -+{ -+ /* -+ * Note: destroy_workqueue ensures that all currently queued work will -+ * be fully completed and the workqueue drained. This means that this -+ * call will inherently also free any queued ssam_event_items, thus we -+ * don't have to take care of that here explicitly. -+ */ -+ destroy_workqueue(cplt->wq); -+ ssam_nf_destroy(&cplt->event.notif); -+} -+ -+ -+/* -- Main SSAM device structures. ------------------------------------------ */ -+ -+/** -+ * ssam_controller_device() - Get the &struct device associated with this -+ * controller. -+ * @c: The controller for which to get the device. -+ * -+ * Return: Returns the &struct device associated with this controller, -+ * providing its lower-level transport. -+ */ -+struct device *ssam_controller_device(struct ssam_controller *c) -+{ -+ return ssh_rtl_get_device(&c->rtl); -+} -+EXPORT_SYMBOL_GPL(ssam_controller_device); -+ -+static void __ssam_controller_release(struct kref *kref) -+{ -+ struct ssam_controller *ctrl = to_ssam_controller(kref, kref); -+ -+ /* -+ * The lock-call here is to satisfy lockdep. At this point we really -+ * expect this to be the last remaining reference to the controller. -+ * Anything else is a bug. -+ */ -+ ssam_controller_lock(ctrl); -+ ssam_controller_destroy(ctrl); -+ ssam_controller_unlock(ctrl); -+ -+ kfree(ctrl); -+} -+ -+/** -+ * ssam_controller_get() - Increment reference count of controller. -+ * @c: The controller. -+ * -+ * Return: Returns the controller provided as input. -+ */ -+struct ssam_controller *ssam_controller_get(struct ssam_controller *c) -+{ -+ if (c) -+ kref_get(&c->kref); -+ return c; -+} -+EXPORT_SYMBOL_GPL(ssam_controller_get); -+ -+/** -+ * ssam_controller_put() - Decrement reference count of controller. -+ * @c: The controller. -+ */ -+void ssam_controller_put(struct ssam_controller *c) -+{ -+ if (c) -+ kref_put(&c->kref, __ssam_controller_release); -+} -+EXPORT_SYMBOL_GPL(ssam_controller_put); -+ -+/** -+ * ssam_controller_statelock() - Lock the controller against state transitions. -+ * @c: The controller to lock. -+ * -+ * Lock the controller against state transitions. Holding this lock guarantees -+ * that the controller will not transition between states, i.e. if the -+ * controller is in state "started", when this lock has been acquired, it will -+ * remain in this state at least until the lock has been released. -+ * -+ * Multiple clients may concurrently hold this lock. In other words: The -+ * ``statelock`` functions represent the read-lock part of a r/w-semaphore. -+ * Actions causing state transitions of the controller must be executed while -+ * holding the write-part of this r/w-semaphore (see ssam_controller_lock() -+ * and ssam_controller_unlock() for that). -+ * -+ * See ssam_controller_stateunlock() for the corresponding unlock function. -+ */ -+void ssam_controller_statelock(struct ssam_controller *c) -+{ -+ down_read(&c->lock); -+} -+EXPORT_SYMBOL_GPL(ssam_controller_statelock); -+ -+/** -+ * ssam_controller_stateunlock() - Unlock controller state transitions. -+ * @c: The controller to unlock. -+ * -+ * See ssam_controller_statelock() for the corresponding lock function. -+ */ -+void ssam_controller_stateunlock(struct ssam_controller *c) -+{ -+ up_read(&c->lock); -+} -+EXPORT_SYMBOL_GPL(ssam_controller_stateunlock); -+ -+/** -+ * ssam_controller_lock() - Acquire the main controller lock. -+ * @c: The controller to lock. -+ * -+ * This lock must be held for any state transitions, including transition to -+ * suspend/resumed states and during shutdown. See ssam_controller_statelock() -+ * for more details on controller locking. -+ * -+ * See ssam_controller_unlock() for the corresponding unlock function. -+ */ -+void ssam_controller_lock(struct ssam_controller *c) -+{ -+ down_write(&c->lock); -+} -+ -+/* -+ * ssam_controller_unlock() - Release the main controller lock. -+ * @c: The controller to unlock. -+ * -+ * See ssam_controller_lock() for the corresponding lock function. -+ */ -+void ssam_controller_unlock(struct ssam_controller *c) -+{ -+ up_write(&c->lock); -+} -+ -+static void ssam_handle_event(struct ssh_rtl *rtl, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data) -+{ -+ struct ssam_controller *ctrl = to_ssam_controller(rtl, rtl); -+ struct ssam_event_item *item; -+ -+ item = ssam_event_item_alloc(data->len, GFP_KERNEL); -+ if (!item) -+ return; -+ -+ item->rqid = get_unaligned_le16(&cmd->rqid); -+ item->event.target_category = cmd->tc; -+ item->event.target_id = cmd->tid_in; -+ item->event.command_id = cmd->cid; -+ item->event.instance_id = cmd->iid; -+ memcpy(&item->event.data[0], data->ptr, data->len); -+ -+ if (WARN_ON(ssam_cplt_submit_event(&ctrl->cplt, item))) -+ kfree(item); -+} -+ -+static const struct ssh_rtl_ops ssam_rtl_ops = { -+ .handle_event = ssam_handle_event, -+}; -+ -+static bool ssam_notifier_is_empty(struct ssam_controller *ctrl); -+static void ssam_notifier_unregister_all(struct ssam_controller *ctrl); -+ -+#define SSAM_SSH_DSM_REVISION 0 -+ -+/* d5e383e1-d892-4a76-89fc-f6aaae7ed5b5 */ -+static const guid_t SSAM_SSH_DSM_GUID = -+ GUID_INIT(0xd5e383e1, 0xd892, 0x4a76, -+ 0x89, 0xfc, 0xf6, 0xaa, 0xae, 0x7e, 0xd5, 0xb5); -+ -+enum ssh_dsm_fn { -+ SSH_DSM_FN_SSH_POWER_PROFILE = 0x05, -+ SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT = 0x06, -+ SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT = 0x07, -+ SSH_DSM_FN_D3_CLOSES_HANDLE = 0x08, -+ SSH_DSM_FN_SSH_BUFFER_SIZE = 0x09, -+}; -+ -+static int ssam_dsm_get_functions(acpi_handle handle, u64 *funcs) -+{ -+ union acpi_object *obj; -+ u64 mask = 0; -+ int i; -+ -+ *funcs = 0; -+ -+ /* -+ * The _DSM function is only present on newer models. It is not -+ * present on 5th and 6th generation devices (i.e. up to and including -+ * Surface Pro 6, Surface Laptop 2, Surface Book 2). -+ * -+ * If the _DSM is not present, indicate that no function is supported. -+ * This will result in default values being set. -+ */ -+ if (!acpi_has_method(handle, "_DSM")) -+ return 0; -+ -+ obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, -+ SSAM_SSH_DSM_REVISION, 0, NULL, -+ ACPI_TYPE_BUFFER); -+ if (!obj) -+ return -EIO; -+ -+ for (i = 0; i < obj->buffer.length && i < 8; i++) -+ mask |= (((u64)obj->buffer.pointer[i]) << (i * 8)); -+ -+ if (mask & BIT(0)) -+ *funcs = mask; -+ -+ ACPI_FREE(obj); -+ return 0; -+} -+ -+static int ssam_dsm_load_u32(acpi_handle handle, u64 funcs, u64 func, u32 *ret) -+{ -+ union acpi_object *obj; -+ u64 val; -+ -+ if (!(funcs & BIT(func))) -+ return 0; /* Not supported, leave *ret at its default value */ -+ -+ obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, -+ SSAM_SSH_DSM_REVISION, func, NULL, -+ ACPI_TYPE_INTEGER); -+ if (!obj) -+ return -EIO; -+ -+ val = obj->integer.value; -+ ACPI_FREE(obj); -+ -+ if (val > U32_MAX) -+ return -ERANGE; -+ -+ *ret = val; -+ return 0; -+} -+ -+/** -+ * ssam_controller_caps_load_from_acpi() - Load controller capabilities from -+ * ACPI _DSM. -+ * @handle: The handle of the ACPI controller/SSH device. -+ * @caps: Where to store the capabilities in. -+ * -+ * Initializes the given controller capabilities with default values, then -+ * checks and, if the respective _DSM functions are available, loads the -+ * actual capabilities from the _DSM. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+static -+int ssam_controller_caps_load_from_acpi(acpi_handle handle, -+ struct ssam_controller_caps *caps) -+{ -+ u32 d3_closes_handle = false; -+ u64 funcs; -+ int status; -+ -+ /* Set defaults. */ -+ caps->ssh_power_profile = U32_MAX; -+ caps->screen_on_sleep_idle_timeout = U32_MAX; -+ caps->screen_off_sleep_idle_timeout = U32_MAX; -+ caps->d3_closes_handle = false; -+ caps->ssh_buffer_size = U32_MAX; -+ -+ /* Pre-load supported DSM functions. */ -+ status = ssam_dsm_get_functions(handle, &funcs); -+ if (status) -+ return status; -+ -+ /* Load actual values from ACPI, if present. */ -+ status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_POWER_PROFILE, -+ &caps->ssh_power_profile); -+ if (status) -+ return status; -+ -+ status = ssam_dsm_load_u32(handle, funcs, -+ SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT, -+ &caps->screen_on_sleep_idle_timeout); -+ if (status) -+ return status; -+ -+ status = ssam_dsm_load_u32(handle, funcs, -+ SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT, -+ &caps->screen_off_sleep_idle_timeout); -+ if (status) -+ return status; -+ -+ status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_D3_CLOSES_HANDLE, -+ &d3_closes_handle); -+ if (status) -+ return status; -+ -+ caps->d3_closes_handle = !!d3_closes_handle; -+ -+ status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_BUFFER_SIZE, -+ &caps->ssh_buffer_size); -+ if (status) -+ return status; -+ -+ return 0; -+} -+ -+/** -+ * ssam_controller_init() - Initialize SSAM controller. -+ * @ctrl: The controller to initialize. -+ * @serdev: The serial device representing the underlying data transport. -+ * -+ * Initializes the given controller. Does neither start receiver nor -+ * transmitter threads. After this call, the controller has to be hooked up to -+ * the serdev core separately via &struct serdev_device_ops, relaying calls to -+ * ssam_controller_receive_buf() and ssam_controller_write_wakeup(). Once the -+ * controller has been hooked up, transmitter and receiver threads may be -+ * started via ssam_controller_start(). These setup steps need to be completed -+ * before controller can be used for requests. -+ */ -+int ssam_controller_init(struct ssam_controller *ctrl, -+ struct serdev_device *serdev) -+{ -+ acpi_handle handle = ACPI_HANDLE(&serdev->dev); -+ int status; -+ -+ init_rwsem(&ctrl->lock); -+ kref_init(&ctrl->kref); -+ -+ status = ssam_controller_caps_load_from_acpi(handle, &ctrl->caps); -+ if (status) -+ return status; -+ -+ dev_dbg(&serdev->dev, -+ "device capabilities:\n" -+ " ssh_power_profile: %u\n" -+ " ssh_buffer_size: %u\n" -+ " screen_on_sleep_idle_timeout: %u\n" -+ " screen_off_sleep_idle_timeout: %u\n" -+ " d3_closes_handle: %u\n", -+ ctrl->caps.ssh_power_profile, -+ ctrl->caps.ssh_buffer_size, -+ ctrl->caps.screen_on_sleep_idle_timeout, -+ ctrl->caps.screen_off_sleep_idle_timeout, -+ ctrl->caps.d3_closes_handle); -+ -+ ssh_seq_reset(&ctrl->counter.seq); -+ ssh_rqid_reset(&ctrl->counter.rqid); -+ -+ /* Initialize event/request completion system. */ -+ status = ssam_cplt_init(&ctrl->cplt, &serdev->dev); -+ if (status) -+ return status; -+ -+ /* Initialize request and packet transport layers. */ -+ status = ssh_rtl_init(&ctrl->rtl, serdev, &ssam_rtl_ops); -+ if (status) { -+ ssam_cplt_destroy(&ctrl->cplt); -+ return status; -+ } -+ -+ /* -+ * Set state via write_once even though we expect to be in an -+ * exclusive context, due to smoke-testing in -+ * ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_INITIALIZED); -+ return 0; -+} -+ -+/** -+ * ssam_controller_start() - Start the receiver and transmitter threads of the -+ * controller. -+ * @ctrl: The controller. -+ * -+ * Note: When this function is called, the controller should be properly -+ * hooked up to the serdev core via &struct serdev_device_ops. Please refer -+ * to ssam_controller_init() for more details on controller initialization. -+ * -+ * This function must be called with the main controller lock held (i.e. by -+ * calling ssam_controller_lock()). -+ */ -+int ssam_controller_start(struct ssam_controller *ctrl) -+{ -+ int status; -+ -+ lockdep_assert_held_write(&ctrl->lock); -+ -+ if (ctrl->state != SSAM_CONTROLLER_INITIALIZED) -+ return -EINVAL; -+ -+ status = ssh_rtl_start(&ctrl->rtl); -+ if (status) -+ return status; -+ -+ /* -+ * Set state via write_once even though we expect to be locked/in an -+ * exclusive context, due to smoke-testing in -+ * ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED); -+ return 0; -+} -+ -+/* -+ * SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT - Timeout for flushing requests during -+ * shutdown. -+ * -+ * Chosen to be larger than one full request timeout, including packets timing -+ * out. This value should give ample time to complete any outstanding requests -+ * during normal operation and account for the odd package timeout. -+ */ -+#define SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT msecs_to_jiffies(5000) -+ -+/** -+ * ssam_controller_shutdown() - Shut down the controller. -+ * @ctrl: The controller. -+ * -+ * Shuts down the controller by flushing all pending requests and stopping the -+ * transmitter and receiver threads. All requests submitted after this call -+ * will fail with %-ESHUTDOWN. While it is discouraged to do so, this function -+ * is safe to use in parallel with ongoing request submission. -+ * -+ * In the course of this shutdown procedure, all currently registered -+ * notifiers will be unregistered. It is, however, strongly recommended to not -+ * rely on this behavior, and instead the party registering the notifier -+ * should unregister it before the controller gets shut down, e.g. via the -+ * SSAM bus which guarantees client devices to be removed before a shutdown. -+ * -+ * Note that events may still be pending after this call, but, due to the -+ * notifiers being unregistered, these events will be dropped when the -+ * controller is subsequently destroyed via ssam_controller_destroy(). -+ * -+ * This function must be called with the main controller lock held (i.e. by -+ * calling ssam_controller_lock()). -+ */ -+void ssam_controller_shutdown(struct ssam_controller *ctrl) -+{ -+ enum ssam_controller_state s = ctrl->state; -+ int status; -+ -+ lockdep_assert_held_write(&ctrl->lock); -+ -+ if (s == SSAM_CONTROLLER_UNINITIALIZED || s == SSAM_CONTROLLER_STOPPED) -+ return; -+ -+ /* -+ * Try to flush pending events and requests while everything still -+ * works. Note: There may still be packets and/or requests in the -+ * system after this call (e.g. via control packets submitted by the -+ * packet transport layer or flush timeout / failure, ...). Those will -+ * be handled with the ssh_rtl_shutdown() call below. -+ */ -+ status = ssh_rtl_flush(&ctrl->rtl, SSAM_CTRL_SHUTDOWN_FLUSH_TIMEOUT); -+ if (status) { -+ ssam_err(ctrl, "failed to flush request transport layer: %d\n", -+ status); -+ } -+ -+ /* Try to flush all currently completing requests and events. */ -+ ssam_cplt_flush(&ctrl->cplt); -+ -+ /* -+ * We expect all notifiers to have been removed by the respective client -+ * driver that set them up at this point. If this warning occurs, some -+ * client driver has not done that... -+ */ -+ WARN_ON(!ssam_notifier_is_empty(ctrl)); -+ -+ /* -+ * Nevertheless, we should still take care of drivers that don't behave -+ * well. Thus disable all enabled events, unregister all notifiers. -+ */ -+ ssam_notifier_unregister_all(ctrl); -+ -+ /* -+ * Cancel remaining requests. Ensure no new ones can be queued and stop -+ * threads. -+ */ -+ ssh_rtl_shutdown(&ctrl->rtl); -+ -+ /* -+ * Set state via write_once even though we expect to be locked/in an -+ * exclusive context, due to smoke-testing in -+ * ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STOPPED); -+ ctrl->rtl.ptl.serdev = NULL; -+} -+ -+/** -+ * ssam_controller_destroy() - Destroy the controller and free its resources. -+ * @ctrl: The controller. -+ * -+ * Ensures that all resources associated with the controller get freed. This -+ * function should only be called after the controller has been stopped via -+ * ssam_controller_shutdown(). In general, this function should not be called -+ * directly. The only valid place to call this function directly is during -+ * initialization, before the controller has been fully initialized and passed -+ * to other processes. This function is called automatically when the -+ * reference count of the controller reaches zero. -+ * -+ * This function must be called with the main controller lock held (i.e. by -+ * calling ssam_controller_lock()). -+ */ -+void ssam_controller_destroy(struct ssam_controller *ctrl) -+{ -+ lockdep_assert_held_write(&ctrl->lock); -+ -+ if (ctrl->state == SSAM_CONTROLLER_UNINITIALIZED) -+ return; -+ -+ WARN_ON(ctrl->state != SSAM_CONTROLLER_STOPPED); -+ -+ /* -+ * Note: New events could still have been received after the previous -+ * flush in ssam_controller_shutdown, before the request transport layer -+ * has been shut down. At this point, after the shutdown, we can be sure -+ * that no new events will be queued. The call to ssam_cplt_destroy will -+ * ensure that those remaining are being completed and freed. -+ */ -+ -+ /* Actually free resources. */ -+ ssam_cplt_destroy(&ctrl->cplt); -+ ssh_rtl_destroy(&ctrl->rtl); -+ -+ /* -+ * Set state via write_once even though we expect to be locked/in an -+ * exclusive context, due to smoke-testing in -+ * ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_UNINITIALIZED); -+} -+ -+/** -+ * ssam_controller_suspend() - Suspend the controller. -+ * @ctrl: The controller to suspend. -+ * -+ * Marks the controller as suspended. Note that display-off and D0-exit -+ * notifications have to be sent manually before transitioning the controller -+ * into the suspended state via this function. -+ * -+ * See ssam_controller_resume() for the corresponding resume function. -+ * -+ * Return: Returns %-EINVAL if the controller is currently not in the -+ * "started" state. -+ */ -+int ssam_controller_suspend(struct ssam_controller *ctrl) -+{ -+ ssam_controller_lock(ctrl); -+ -+ if (ctrl->state != SSAM_CONTROLLER_STARTED) { -+ ssam_controller_unlock(ctrl); -+ return -EINVAL; -+ } -+ -+ ssam_dbg(ctrl, "pm: suspending controller\n"); -+ -+ /* -+ * Set state via write_once even though we're locked, due to -+ * smoke-testing in ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_SUSPENDED); -+ -+ ssam_controller_unlock(ctrl); -+ return 0; -+} -+ -+/** -+ * ssam_controller_resume() - Resume the controller from suspend. -+ * @ctrl: The controller to resume. -+ * -+ * Resume the controller from the suspended state it was put into via -+ * ssam_controller_suspend(). This function does not issue display-on and -+ * D0-entry notifications. If required, those have to be sent manually after -+ * this call. -+ * -+ * Return: Returns %-EINVAL if the controller is currently not suspended. -+ */ -+int ssam_controller_resume(struct ssam_controller *ctrl) -+{ -+ ssam_controller_lock(ctrl); -+ -+ if (ctrl->state != SSAM_CONTROLLER_SUSPENDED) { -+ ssam_controller_unlock(ctrl); -+ return -EINVAL; -+ } -+ -+ ssam_dbg(ctrl, "pm: resuming controller\n"); -+ -+ /* -+ * Set state via write_once even though we're locked, due to -+ * smoke-testing in ssam_request_sync_submit(). -+ */ -+ WRITE_ONCE(ctrl->state, SSAM_CONTROLLER_STARTED); -+ -+ ssam_controller_unlock(ctrl); -+ return 0; -+} -+ -+ -+/* -- Top-level request interface ------------------------------------------- */ -+ -+/** -+ * ssam_request_write_data() - Construct and write SAM request message to -+ * buffer. -+ * @buf: The buffer to write the data to. -+ * @ctrl: The controller via which the request will be sent. -+ * @spec: The request data and specification. -+ * -+ * Constructs a SAM/SSH request message and writes it to the provided buffer. -+ * The request and transport counters, specifically RQID and SEQ, will be set -+ * in this call. These counters are obtained from the controller. It is thus -+ * only valid to send the resulting message via the controller specified here. -+ * -+ * For calculation of the required buffer size, refer to the -+ * SSH_COMMAND_MESSAGE_LENGTH() macro. -+ * -+ * Return: Returns the number of bytes used in the buffer on success. Returns -+ * %-EINVAL if the payload length provided in the request specification is too -+ * large (larger than %SSH_COMMAND_MAX_PAYLOAD_SIZE) or if the provided buffer -+ * is too small. -+ */ -+ssize_t ssam_request_write_data(struct ssam_span *buf, -+ struct ssam_controller *ctrl, -+ const struct ssam_request *spec) -+{ -+ struct msgbuf msgb; -+ u16 rqid; -+ u8 seq; -+ -+ if (spec->length > SSH_COMMAND_MAX_PAYLOAD_SIZE) -+ return -EINVAL; -+ -+ if (SSH_COMMAND_MESSAGE_LENGTH(spec->length) > buf->len) -+ return -EINVAL; -+ -+ msgb_init(&msgb, buf->ptr, buf->len); -+ seq = ssh_seq_next(&ctrl->counter.seq); -+ rqid = ssh_rqid_next(&ctrl->counter.rqid); -+ msgb_push_cmd(&msgb, seq, rqid, spec); -+ -+ return msgb_bytes_used(&msgb); -+} -+EXPORT_SYMBOL_GPL(ssam_request_write_data); -+ -+static void ssam_request_sync_complete(struct ssh_request *rqst, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data, int status) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ struct ssam_request_sync *r; -+ -+ r = container_of(rqst, struct ssam_request_sync, base); -+ r->status = status; -+ -+ if (r->resp) -+ r->resp->length = 0; -+ -+ if (status) { -+ rtl_dbg_cond(rtl, "rsp: request failed: %d\n", status); -+ return; -+ } -+ -+ if (!data) /* Handle requests without a response. */ -+ return; -+ -+ if (!r->resp || !r->resp->pointer) { -+ if (data->len) -+ rtl_warn(rtl, "rsp: no response buffer provided, dropping data\n"); -+ return; -+ } -+ -+ if (data->len > r->resp->capacity) { -+ rtl_err(rtl, -+ "rsp: response buffer too small, capacity: %zu bytes, got: %zu bytes\n", -+ r->resp->capacity, data->len); -+ r->status = -ENOSPC; -+ return; -+ } -+ -+ r->resp->length = data->len; -+ memcpy(r->resp->pointer, data->ptr, data->len); -+} -+ -+static void ssam_request_sync_release(struct ssh_request *rqst) -+{ -+ complete_all(&container_of(rqst, struct ssam_request_sync, base)->comp); -+} -+ -+static const struct ssh_request_ops ssam_request_sync_ops = { -+ .release = ssam_request_sync_release, -+ .complete = ssam_request_sync_complete, -+}; -+ -+/** -+ * ssam_request_sync_alloc() - Allocate a synchronous request. -+ * @payload_len: The length of the request payload. -+ * @flags: Flags used for allocation. -+ * @rqst: Where to store the pointer to the allocated request. -+ * @buffer: Where to store the buffer descriptor for the message buffer of -+ * the request. -+ * -+ * Allocates a synchronous request with corresponding message buffer. The -+ * request still needs to be initialized ssam_request_sync_init() before -+ * it can be submitted, and the message buffer data must still be set to the -+ * returned buffer via ssam_request_sync_set_data() after it has been filled, -+ * if need be with adjusted message length. -+ * -+ * After use, the request and its corresponding message buffer should be freed -+ * via ssam_request_sync_free(). The buffer must not be freed separately. -+ * -+ * Return: Returns zero on success, %-ENOMEM if the request could not be -+ * allocated. -+ */ -+int ssam_request_sync_alloc(size_t payload_len, gfp_t flags, -+ struct ssam_request_sync **rqst, -+ struct ssam_span *buffer) -+{ -+ size_t msglen = SSH_COMMAND_MESSAGE_LENGTH(payload_len); -+ -+ *rqst = kzalloc(sizeof(**rqst) + msglen, flags); -+ if (!*rqst) -+ return -ENOMEM; -+ -+ buffer->ptr = (u8 *)(*rqst + 1); -+ buffer->len = msglen; -+ -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_alloc); -+ -+/** -+ * ssam_request_sync_free() - Free a synchronous request. -+ * @rqst: The request to be freed. -+ * -+ * Free a synchronous request and its corresponding buffer allocated with -+ * ssam_request_sync_alloc(). Do not use for requests allocated on the stack -+ * or via any other function. -+ * -+ * Warning: The caller must ensure that the request is not in use any more. -+ * I.e. the caller must ensure that it has the only reference to the request -+ * and the request is not currently pending. This means that the caller has -+ * either never submitted the request, request submission has failed, or the -+ * caller has waited until the submitted request has been completed via -+ * ssam_request_sync_wait(). -+ */ -+void ssam_request_sync_free(struct ssam_request_sync *rqst) -+{ -+ kfree(rqst); -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_free); -+ -+/** -+ * ssam_request_sync_init() - Initialize a synchronous request struct. -+ * @rqst: The request to initialize. -+ * @flags: The request flags. -+ * -+ * Initializes the given request struct. Does not initialize the request -+ * message data. This has to be done explicitly after this call via -+ * ssam_request_sync_set_data() and the actual message data has to be written -+ * via ssam_request_write_data(). -+ * -+ * Return: Returns zero on success or %-EINVAL if the given flags are invalid. -+ */ -+int ssam_request_sync_init(struct ssam_request_sync *rqst, -+ enum ssam_request_flags flags) -+{ -+ int status; -+ -+ status = ssh_request_init(&rqst->base, flags, &ssam_request_sync_ops); -+ if (status) -+ return status; -+ -+ init_completion(&rqst->comp); -+ rqst->resp = NULL; -+ rqst->status = 0; -+ -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_init); -+ -+/** -+ * ssam_request_sync_submit() - Submit a synchronous request. -+ * @ctrl: The controller with which to submit the request. -+ * @rqst: The request to submit. -+ * -+ * Submit a synchronous request. The request has to be initialized and -+ * properly set up, including response buffer (may be %NULL if no response is -+ * expected) and command message data. This function does not wait for the -+ * request to be completed. -+ * -+ * If this function succeeds, ssam_request_sync_wait() must be used to ensure -+ * that the request has been completed before the response data can be -+ * accessed and/or the request can be freed. On failure, the request may -+ * immediately be freed. -+ * -+ * This function may only be used if the controller is active, i.e. has been -+ * initialized and not suspended. -+ */ -+int ssam_request_sync_submit(struct ssam_controller *ctrl, -+ struct ssam_request_sync *rqst) -+{ -+ int status; -+ -+ /* -+ * This is only a superficial check. In general, the caller needs to -+ * ensure that the controller is initialized and is not (and does not -+ * get) suspended during use, i.e. until the request has been completed -+ * (if _absolutely_ necessary, by use of ssam_controller_statelock/ -+ * ssam_controller_stateunlock, but something like ssam_client_link -+ * should be preferred as this needs to last until the request has been -+ * completed). -+ * -+ * Note that it is actually safe to use this function while the -+ * controller is in the process of being shut down (as ssh_rtl_submit -+ * is safe with regards to this), but it is generally discouraged to do -+ * so. -+ */ -+ if (WARN_ON(READ_ONCE(ctrl->state) != SSAM_CONTROLLER_STARTED)) { -+ ssh_request_put(&rqst->base); -+ return -ENODEV; -+ } -+ -+ status = ssh_rtl_submit(&ctrl->rtl, &rqst->base); -+ ssh_request_put(&rqst->base); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_submit); -+ -+/** -+ * ssam_request_sync() - Execute a synchronous request. -+ * @ctrl: The controller via which the request will be submitted. -+ * @spec: The request specification and payload. -+ * @rsp: The response buffer. -+ * -+ * Allocates a synchronous request with its message data buffer on the heap -+ * via ssam_request_sync_alloc(), fully initializes it via the provided -+ * request specification, submits it, and finally waits for its completion -+ * before freeing it and returning its status. -+ * -+ * Return: Returns the status of the request or any failure during setup. -+ */ -+int ssam_request_sync(struct ssam_controller *ctrl, -+ const struct ssam_request *spec, -+ struct ssam_response *rsp) -+{ -+ struct ssam_request_sync *rqst; -+ struct ssam_span buf; -+ ssize_t len; -+ int status; -+ -+ status = ssam_request_sync_alloc(spec->length, GFP_KERNEL, &rqst, &buf); -+ if (status) -+ return status; -+ -+ status = ssam_request_sync_init(rqst, spec->flags); -+ if (status) -+ return status; -+ -+ ssam_request_sync_set_resp(rqst, rsp); -+ -+ len = ssam_request_write_data(&buf, ctrl, spec); -+ if (len < 0) { -+ ssam_request_sync_free(rqst); -+ return len; -+ } -+ -+ ssam_request_sync_set_data(rqst, buf.ptr, len); -+ -+ status = ssam_request_sync_submit(ctrl, rqst); -+ if (!status) -+ status = ssam_request_sync_wait(rqst); -+ -+ ssam_request_sync_free(rqst); -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync); -+ -+/** -+ * ssam_request_sync_with_buffer() - Execute a synchronous request with the -+ * provided buffer as back-end for the message buffer. -+ * @ctrl: The controller via which the request will be submitted. -+ * @spec: The request specification and payload. -+ * @rsp: The response buffer. -+ * @buf: The buffer for the request message data. -+ * -+ * Allocates a synchronous request struct on the stack, fully initializes it -+ * using the provided buffer as message data buffer, submits it, and then -+ * waits for its completion before returning its status. The -+ * SSH_COMMAND_MESSAGE_LENGTH() macro can be used to compute the required -+ * message buffer size. -+ * -+ * This function does essentially the same as ssam_request_sync(), but instead -+ * of dynamically allocating the request and message data buffer, it uses the -+ * provided message data buffer and stores the (small) request struct on the -+ * heap. -+ * -+ * Return: Returns the status of the request or any failure during setup. -+ */ -+int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, -+ const struct ssam_request *spec, -+ struct ssam_response *rsp, -+ struct ssam_span *buf) -+{ -+ struct ssam_request_sync rqst; -+ ssize_t len; -+ int status; -+ -+ status = ssam_request_sync_init(&rqst, spec->flags); -+ if (status) -+ return status; -+ -+ ssam_request_sync_set_resp(&rqst, rsp); -+ -+ len = ssam_request_write_data(buf, ctrl, spec); -+ if (len < 0) -+ return len; -+ -+ ssam_request_sync_set_data(&rqst, buf->ptr, len); -+ -+ status = ssam_request_sync_submit(ctrl, &rqst); -+ if (!status) -+ status = ssam_request_sync_wait(&rqst); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer); -+ -+ -+/* -- Internal SAM requests. ------------------------------------------------ */ -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x13, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x15, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x16, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x33, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { -+ .target_category = SSAM_SSH_TC_SAM, -+ .target_id = 0x01, -+ .command_id = 0x34, -+ .instance_id = 0x00, -+}); -+ -+/** -+ * struct ssh_notification_params - Command payload to enable/disable SSH -+ * notifications. -+ * @target_category: The target category for which notifications should be -+ * enabled/disabled. -+ * @flags: Flags determining how notifications are being sent. -+ * @request_id: The request ID that is used to send these notifications. -+ * @instance_id: The specific instance in the given target category for -+ * which notifications should be enabled. -+ */ -+struct ssh_notification_params { -+ u8 target_category; -+ u8 flags; -+ __le16 request_id; -+ u8 instance_id; -+} __packed; -+ -+static_assert(sizeof(struct ssh_notification_params) == 5); -+ -+static int __ssam_ssh_event_request(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, u8 cid, -+ struct ssam_event_id id, u8 flags) -+{ -+ struct ssh_notification_params params; -+ struct ssam_request rqst; -+ struct ssam_response result; -+ int status; -+ -+ u16 rqid = ssh_tc_to_rqid(id.target_category); -+ u8 buf = 0; -+ -+ /* Only allow RQIDs that lie within the event spectrum. */ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ params.target_category = id.target_category; -+ params.instance_id = id.instance; -+ params.flags = flags; -+ put_unaligned_le16(rqid, ¶ms.request_id); -+ -+ rqst.target_category = reg.target_category; -+ rqst.target_id = reg.target_id; -+ rqst.command_id = cid; -+ rqst.instance_id = 0x00; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(params); -+ rqst.payload = (u8 *)¶ms; -+ -+ result.capacity = sizeof(buf); -+ result.length = 0; -+ result.pointer = &buf; -+ -+ status = ssam_retry(ssam_request_sync_onstack, ctrl, &rqst, &result, -+ sizeof(params)); -+ -+ return status < 0 ? status : buf; -+} -+ -+/** -+ * ssam_ssh_event_enable() - Enable SSH event. -+ * @ctrl: The controller for which to enable the event. -+ * @reg: The event registry describing what request to use for enabling and -+ * disabling the event. -+ * @id: The event identifier. -+ * @flags: The event flags. -+ * -+ * Enables the specified event on the EC. This function does not manage -+ * reference counting of enabled events and is basically only a wrapper for -+ * the raw EC request. If the specified event is already enabled, the EC will -+ * ignore this request. -+ * -+ * Return: Returns the status of the executed SAM request (zero on success and -+ * negative on direct failure) or %-EPROTO if the request response indicates a -+ * failure. -+ */ -+static int ssam_ssh_event_enable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags) -+{ -+ int status; -+ -+ status = __ssam_ssh_event_request(ctrl, reg, reg.cid_enable, id, flags); -+ -+ if (status < 0 && status != -EINVAL) { -+ ssam_err(ctrl, -+ "failed to enable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n", -+ id.target_category, id.instance, reg.target_category); -+ } -+ -+ if (status > 0) { -+ ssam_err(ctrl, -+ "unexpected result while enabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n", -+ status, id.target_category, id.instance, reg.target_category); -+ return -EPROTO; -+ } -+ -+ return status; -+} -+ -+/** -+ * ssam_ssh_event_disable() - Disable SSH event. -+ * @ctrl: The controller for which to disable the event. -+ * @reg: The event registry describing what request to use for enabling and -+ * disabling the event (must be same as used when enabling the event). -+ * @id: The event identifier. -+ * @flags: The event flags (likely ignored for disabling of events). -+ * -+ * Disables the specified event on the EC. This function does not manage -+ * reference counting of enabled events and is basically only a wrapper for -+ * the raw EC request. If the specified event is already disabled, the EC will -+ * ignore this request. -+ * -+ * Return: Returns the status of the executed SAM request (zero on success and -+ * negative on direct failure) or %-EPROTO if the request response indicates a -+ * failure. -+ */ -+static int ssam_ssh_event_disable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags) -+{ -+ int status; -+ -+ status = __ssam_ssh_event_request(ctrl, reg, reg.cid_enable, id, flags); -+ -+ if (status < 0 && status != -EINVAL) { -+ ssam_err(ctrl, -+ "failed to disable event source (tc: %#04x, iid: %#04x, reg: %#04x)\n", -+ id.target_category, id.instance, reg.target_category); -+ } -+ -+ if (status > 0) { -+ ssam_err(ctrl, -+ "unexpected result while disabling event source: %#04x (tc: %#04x, iid: %#04x, reg: %#04x)\n", -+ status, id.target_category, id.instance, reg.target_category); -+ return -EPROTO; -+ } -+ -+ return status; -+} -+ -+ -+/* -- Wrappers for internal SAM requests. ----------------------------------- */ -+ -+/** -+ * ssam_get_firmware_version() - Get the SAM/EC firmware version. -+ * @ctrl: The controller. -+ * @version: Where to store the version number. -+ * -+ * Return: Returns zero on success or the status of the executed SAM request -+ * if that request failed. -+ */ -+int ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version) -+{ -+ __le32 __version; -+ int status; -+ -+ status = ssam_retry(ssam_ssh_get_firmware_version, ctrl, &__version); -+ if (status) -+ return status; -+ -+ *version = le32_to_cpu(__version); -+ return 0; -+} -+ -+/** -+ * ssam_ctrl_notif_display_off() - Notify EC that the display has been turned -+ * off. -+ * @ctrl: The controller. -+ * -+ * Notify the EC that the display has been turned off and the driver may enter -+ * a lower-power state. This will prevent events from being sent directly. -+ * Rather, the EC signals an event by pulling the wakeup GPIO high for as long -+ * as there are pending events. The events then need to be manually released, -+ * one by one, via the GPIO callback request. All pending events accumulated -+ * during this state can also be released by issuing the display-on -+ * notification, e.g. via ssam_ctrl_notif_display_on(), which will also reset -+ * the GPIO. -+ * -+ * On some devices, specifically ones with an integrated keyboard, the keyboard -+ * backlight will be turned off by this call. -+ * -+ * This function will only send the display-off notification command if -+ * display notifications are supported by the EC. Currently all known devices -+ * support these notifications. -+ * -+ * Use ssam_ctrl_notif_display_on() to reverse the effects of this function. -+ * -+ * Return: Returns zero on success or if no request has been executed, the -+ * status of the executed SAM request if that request failed, or %-EPROTO if -+ * an unexpected response has been received. -+ */ -+int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl) -+{ -+ int status; -+ u8 response; -+ -+ ssam_dbg(ctrl, "pm: notifying display off\n"); -+ -+ status = ssam_retry(ssam_ssh_notif_display_off, ctrl, &response); -+ if (status) -+ return status; -+ -+ if (response != 0) { -+ ssam_err(ctrl, "unexpected response from display-off notification: %#04x\n", -+ response); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_ctrl_notif_display_on() - Notify EC that the display has been turned on. -+ * @ctrl: The controller. -+ * -+ * Notify the EC that the display has been turned back on and the driver has -+ * exited its lower-power state. This notification is the counterpart to the -+ * display-off notification sent via ssam_ctrl_notif_display_off() and will -+ * reverse its effects, including resetting events to their default behavior. -+ * -+ * This function will only send the display-on notification command if display -+ * notifications are supported by the EC. Currently all known devices support -+ * these notifications. -+ * -+ * See ssam_ctrl_notif_display_off() for more details. -+ * -+ * Return: Returns zero on success or if no request has been executed, the -+ * status of the executed SAM request if that request failed, or %-EPROTO if -+ * an unexpected response has been received. -+ */ -+int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl) -+{ -+ int status; -+ u8 response; -+ -+ ssam_dbg(ctrl, "pm: notifying display on\n"); -+ -+ status = ssam_retry(ssam_ssh_notif_display_on, ctrl, &response); -+ if (status) -+ return status; -+ -+ if (response != 0) { -+ ssam_err(ctrl, "unexpected response from display-on notification: %#04x\n", -+ response); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_ctrl_notif_d0_exit() - Notify EC that the driver/device exits the D0 -+ * power state. -+ * @ctrl: The controller -+ * -+ * Notifies the EC that the driver prepares to exit the D0 power state in -+ * favor of a lower-power state. Exact effects of this function related to the -+ * EC are currently unknown. -+ * -+ * This function will only send the D0-exit notification command if D0-state -+ * notifications are supported by the EC. Only newer Surface generations -+ * support these notifications. -+ * -+ * Use ssam_ctrl_notif_d0_entry() to reverse the effects of this function. -+ * -+ * Return: Returns zero on success or if no request has been executed, the -+ * status of the executed SAM request if that request failed, or %-EPROTO if -+ * an unexpected response has been received. -+ */ -+int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl) -+{ -+ int status; -+ u8 response; -+ -+ if (!ctrl->caps.d3_closes_handle) -+ return 0; -+ -+ ssam_dbg(ctrl, "pm: notifying D0 exit\n"); -+ -+ status = ssam_retry(ssam_ssh_notif_d0_exit, ctrl, &response); -+ if (status) -+ return status; -+ -+ if (response != 0) { -+ ssam_err(ctrl, "unexpected response from D0-exit notification: %#04x\n", -+ response); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_ctrl_notif_d0_entry() - Notify EC that the driver/device enters the D0 -+ * power state. -+ * @ctrl: The controller -+ * -+ * Notifies the EC that the driver has exited a lower-power state and entered -+ * the D0 power state. Exact effects of this function related to the EC are -+ * currently unknown. -+ * -+ * This function will only send the D0-entry notification command if D0-state -+ * notifications are supported by the EC. Only newer Surface generations -+ * support these notifications. -+ * -+ * See ssam_ctrl_notif_d0_exit() for more details. -+ * -+ * Return: Returns zero on success or if no request has been executed, the -+ * status of the executed SAM request if that request failed, or %-EPROTO if -+ * an unexpected response has been received. -+ */ -+int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) -+{ -+ int status; -+ u8 response; -+ -+ if (!ctrl->caps.d3_closes_handle) -+ return 0; -+ -+ ssam_dbg(ctrl, "pm: notifying D0 entry\n"); -+ -+ status = ssam_retry(ssam_ssh_notif_d0_entry, ctrl, &response); -+ if (status) -+ return status; -+ -+ if (response != 0) { -+ ssam_err(ctrl, "unexpected response from D0-entry notification: %#04x\n", -+ response); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+ -+/* -- Top-level event registry interface. ----------------------------------- */ -+ -+/** -+ * ssam_notifier_register() - Register an event notifier. -+ * @ctrl: The controller to register the notifier on. -+ * @n: The event notifier to register. -+ * -+ * Register an event notifier and increment the usage counter of the -+ * associated SAM event. If the event was previously not enabled, it will be -+ * enabled during this call. -+ * -+ * Return: Returns zero on success, %-ENOSPC if there have already been -+ * %INT_MAX notifiers for the event ID/type associated with the notifier block -+ * registered, %-ENOMEM if the corresponding event entry could not be -+ * allocated. If this is the first time that a notifier block is registered -+ * for the specific associated event, returns the status of the event-enable -+ * EC-command. -+ */ -+int ssam_notifier_register(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n) -+{ -+ u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); -+ struct ssam_nf_refcount_entry *entry; -+ struct ssam_nf_head *nf_head; -+ struct ssam_nf *nf; -+ int status; -+ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ nf = &ctrl->cplt.event.notif; -+ nf_head = &nf->head[ssh_rqid_to_event(rqid)]; -+ -+ mutex_lock(&nf->lock); -+ -+ entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); -+ if (IS_ERR(entry)) { -+ mutex_unlock(&nf->lock); -+ return PTR_ERR(entry); -+ } -+ -+ ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ n->event.reg.target_category, n->event.id.target_category, -+ n->event.id.instance, entry->refcount); -+ -+ status = ssam_nfblk_insert(nf_head, &n->base); -+ if (status) { -+ entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); -+ if (entry->refcount == 0) -+ kfree(entry); -+ -+ mutex_unlock(&nf->lock); -+ return status; -+ } -+ -+ if (entry->refcount == 1) { -+ status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, -+ n->event.flags); -+ if (status) { -+ ssam_nfblk_remove(&n->base); -+ kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id)); -+ mutex_unlock(&nf->lock); -+ synchronize_srcu(&nf_head->srcu); -+ return status; -+ } -+ -+ entry->flags = n->event.flags; -+ -+ } else if (entry->flags != n->event.flags) { -+ ssam_warn(ctrl, -+ "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -+ n->event.flags, entry->flags, n->event.reg.target_category, -+ n->event.id.target_category, n->event.id.instance); -+ } -+ -+ mutex_unlock(&nf->lock); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_notifier_register); -+ -+/** -+ * ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier and decrement the usage counter of the -+ * associated SAM event. If the usage counter reaches zero, the event will be -+ * disabled. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+int ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n) -+{ -+ u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); -+ struct ssam_nf_refcount_entry *entry; -+ struct ssam_nf_head *nf_head; -+ struct ssam_nf *nf; -+ int status = 0; -+ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ nf = &ctrl->cplt.event.notif; -+ nf_head = &nf->head[ssh_rqid_to_event(rqid)]; -+ -+ mutex_lock(&nf->lock); -+ -+ if (!ssam_nfblk_find(nf_head, &n->base)) { -+ mutex_unlock(&nf->lock); -+ return -ENOENT; -+ } -+ -+ entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); -+ if (WARN_ON(!entry)) { -+ /* -+ * If this does not return an entry, there's a logic error -+ * somewhere: The notifier block is registered, but the event -+ * refcount entry is not there. Remove the notifier block -+ * anyways. -+ */ -+ status = -ENOENT; -+ goto remove; -+ } -+ -+ ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ n->event.reg.target_category, n->event.id.target_category, -+ n->event.id.instance, entry->refcount); -+ -+ if (entry->flags != n->event.flags) { -+ ssam_warn(ctrl, -+ "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -+ n->event.flags, entry->flags, n->event.reg.target_category, -+ n->event.id.target_category, n->event.id.instance); -+ } -+ -+ if (entry->refcount == 0) { -+ status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, -+ n->event.flags); -+ kfree(entry); -+ } -+ -+remove: -+ ssam_nfblk_remove(&n->base); -+ mutex_unlock(&nf->lock); -+ synchronize_srcu(&nf_head->srcu); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_notifier_unregister); -+ -+/** -+ * ssam_notifier_disable_registered() - Disable events for all registered -+ * notifiers. -+ * @ctrl: The controller for which to disable the notifiers/events. -+ * -+ * Disables events for all currently registered notifiers. In case of an error -+ * (EC command failing), all previously disabled events will be restored and -+ * the error code returned. -+ * -+ * This function is intended to disable all events prior to hibernation entry. -+ * See ssam_notifier_restore_registered() to restore/re-enable all events -+ * disabled with this function. -+ * -+ * Note that this function will not disable events for notifiers registered -+ * after calling this function. It should thus be made sure that no new -+ * notifiers are going to be added after this call and before the corresponding -+ * call to ssam_notifier_restore_registered(). -+ * -+ * Return: Returns zero on success. In case of failure returns the error code -+ * returned by the failed EC command to disable an event. -+ */ -+int ssam_notifier_disable_registered(struct ssam_controller *ctrl) -+{ -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct rb_node *n; -+ int status; -+ -+ mutex_lock(&nf->lock); -+ for (n = rb_first(&nf->refcount); n; n = rb_next(n)) { -+ struct ssam_nf_refcount_entry *e; -+ -+ e = rb_entry(n, struct ssam_nf_refcount_entry, node); -+ status = ssam_ssh_event_disable(ctrl, e->key.reg, -+ e->key.id, e->flags); -+ if (status) -+ goto err; -+ } -+ mutex_unlock(&nf->lock); -+ -+ return 0; -+ -+err: -+ for (n = rb_prev(n); n; n = rb_prev(n)) { -+ struct ssam_nf_refcount_entry *e; -+ -+ e = rb_entry(n, struct ssam_nf_refcount_entry, node); -+ ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags); -+ } -+ mutex_unlock(&nf->lock); -+ -+ return status; -+} -+ -+/** -+ * ssam_notifier_restore_registered() - Restore/re-enable events for all -+ * registered notifiers. -+ * @ctrl: The controller for which to restore the notifiers/events. -+ * -+ * Restores/re-enables all events for which notifiers have been registered on -+ * the given controller. In case of a failure, the error is logged and the -+ * function continues to try and enable the remaining events. -+ * -+ * This function is intended to restore/re-enable all registered events after -+ * hibernation. See ssam_notifier_disable_registered() for the counter part -+ * disabling the events and more details. -+ */ -+void ssam_notifier_restore_registered(struct ssam_controller *ctrl) -+{ -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct rb_node *n; -+ -+ mutex_lock(&nf->lock); -+ for (n = rb_first(&nf->refcount); n; n = rb_next(n)) { -+ struct ssam_nf_refcount_entry *e; -+ -+ e = rb_entry(n, struct ssam_nf_refcount_entry, node); -+ -+ /* Ignore errors, will get logged in call. */ -+ ssam_ssh_event_enable(ctrl, e->key.reg, e->key.id, e->flags); -+ } -+ mutex_unlock(&nf->lock); -+} -+ -+/** -+ * ssam_notifier_is_empty() - Check if there are any registered notifiers. -+ * @ctrl: The controller to check on. -+ * -+ * Return: Returns %true if there are currently no notifiers registered on the -+ * controller, %false otherwise. -+ */ -+static bool ssam_notifier_is_empty(struct ssam_controller *ctrl) -+{ -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ bool result; -+ -+ mutex_lock(&nf->lock); -+ result = ssam_nf_refcount_empty(nf); -+ mutex_unlock(&nf->lock); -+ -+ return result; -+} -+ -+/** -+ * ssam_notifier_unregister_all() - Unregister all currently registered -+ * notifiers. -+ * @ctrl: The controller to unregister the notifiers on. -+ * -+ * Unregisters all currently registered notifiers. This function is used to -+ * ensure that all notifiers will be unregistered and associated -+ * entries/resources freed when the controller is being shut down. -+ */ -+static void ssam_notifier_unregister_all(struct ssam_controller *ctrl) -+{ -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct ssam_nf_refcount_entry *e, *n; -+ -+ mutex_lock(&nf->lock); -+ rbtree_postorder_for_each_entry_safe(e, n, &nf->refcount, node) { -+ /* Ignore errors, will get logged in call. */ -+ ssam_ssh_event_disable(ctrl, e->key.reg, e->key.id, e->flags); -+ kfree(e); -+ } -+ nf->refcount = RB_ROOT; -+ mutex_unlock(&nf->lock); -+} -+ -+ -+/* -- Wakeup IRQ. ----------------------------------------------------------- */ -+ -+static irqreturn_t ssam_irq_handle(int irq, void *dev_id) -+{ -+ struct ssam_controller *ctrl = dev_id; -+ -+ ssam_dbg(ctrl, "pm: wake irq triggered\n"); -+ -+ /* -+ * Note: Proper wakeup detection is currently unimplemented. -+ * When the EC is in display-off or any other non-D0 state, it -+ * does not send events/notifications to the host. Instead it -+ * signals that there are events available via the wakeup IRQ. -+ * This driver is responsible for calling back to the EC to -+ * release these events one-by-one. -+ * -+ * This IRQ should not cause a full system resume by its own. -+ * Instead, events should be handled by their respective subsystem -+ * drivers, which in turn should signal whether a full system -+ * resume should be performed. -+ * -+ * TODO: Send GPIO callback command repeatedly to EC until callback -+ * returns 0x00. Return flag of callback is "has more events". -+ * Each time the command is sent, one event is "released". Once -+ * all events have been released (return = 0x00), the GPIO is -+ * re-armed. Detect wakeup events during this process, go back to -+ * sleep if no wakeup event has been received. -+ */ -+ -+ return IRQ_HANDLED; -+} -+ -+/** -+ * ssam_irq_setup() - Set up SAM EC wakeup-GPIO interrupt. -+ * @ctrl: The controller for which the IRQ should be set up. -+ * -+ * Set up an IRQ for the wakeup-GPIO pin of the SAM EC. This IRQ can be used -+ * to wake the device from a low power state. -+ * -+ * Note that this IRQ can only be triggered while the EC is in the display-off -+ * state. In this state, events are not sent to the host in the usual way. -+ * Instead the wakeup-GPIO gets pulled to "high" as long as there are pending -+ * events and these events need to be released one-by-one via the GPIO -+ * callback request, either until there are no events left and the GPIO is -+ * reset, or all at once by transitioning the EC out of the display-off state, -+ * which will also clear the GPIO. -+ * -+ * Not all events, however, should trigger a full system wakeup. Instead the -+ * driver should, if necessary, inspect and forward each event to the -+ * corresponding subsystem, which in turn should decide if the system needs to -+ * be woken up. This logic has not been implemented yet, thus wakeup by this -+ * IRQ should be disabled by default to avoid spurious wake-ups, caused, for -+ * example, by the remaining battery percentage changing. Refer to comments in -+ * this function and comments in the corresponding IRQ handler for more -+ * details on how this should be implemented. -+ * -+ * See also ssam_ctrl_notif_display_off() and ssam_ctrl_notif_display_off() -+ * for functions to transition the EC into and out of the display-off state as -+ * well as more details on it. -+ * -+ * The IRQ is disabled by default and has to be enabled before it can wake up -+ * the device from suspend via ssam_irq_arm_for_wakeup(). On teardown, the IRQ -+ * should be freed via ssam_irq_free(). -+ */ -+int ssam_irq_setup(struct ssam_controller *ctrl) -+{ -+ struct device *dev = ssam_controller_device(ctrl); -+ struct gpio_desc *gpiod; -+ int irq; -+ int status; -+ -+ /* -+ * The actual GPIO interrupt is declared in ACPI as TRIGGER_HIGH. -+ * However, the GPIO line only gets reset by sending the GPIO callback -+ * command to SAM (or alternatively the display-on notification). As -+ * proper handling for this interrupt is not implemented yet, leaving -+ * the IRQ at TRIGGER_HIGH would cause an IRQ storm (as the callback -+ * never gets sent and thus the line never gets reset). To avoid this, -+ * mark the IRQ as TRIGGER_RISING for now, only creating a single -+ * interrupt, and let the SAM resume callback during the controller -+ * resume process clear it. -+ */ -+ const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING; -+ -+ gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS); -+ if (IS_ERR(gpiod)) -+ return PTR_ERR(gpiod); -+ -+ irq = gpiod_to_irq(gpiod); -+ gpiod_put(gpiod); -+ -+ if (irq < 0) -+ return irq; -+ -+ status = request_threaded_irq(irq, NULL, ssam_irq_handle, irqf, -+ "ssam_wakeup", ctrl); -+ if (status) -+ return status; -+ -+ ctrl->irq.num = irq; -+ disable_irq(ctrl->irq.num); -+ return 0; -+} -+ -+/** -+ * ssam_irq_free() - Free SAM EC wakeup-GPIO interrupt. -+ * @ctrl: The controller for which the IRQ should be freed. -+ * -+ * Free the wakeup-GPIO IRQ previously set-up via ssam_irq_setup(). -+ */ -+void ssam_irq_free(struct ssam_controller *ctrl) -+{ -+ free_irq(ctrl->irq.num, ctrl); -+ ctrl->irq.num = -1; -+} -+ -+/** -+ * ssam_irq_arm_for_wakeup() - Arm the EC IRQ for wakeup, if enabled. -+ * @ctrl: The controller for which the IRQ should be armed. -+ * -+ * Sets up the IRQ so that it can be used to wake the device. Specifically, -+ * this function enables the irq and then, if the device is allowed to wake up -+ * the system, calls enable_irq_wake(). See ssam_irq_disarm_wakeup() for the -+ * corresponding function to disable the IRQ. -+ * -+ * This function is intended to arm the IRQ before entering S2idle suspend. -+ * -+ * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must -+ * be balanced. -+ */ -+int ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl) -+{ -+ struct device *dev = ssam_controller_device(ctrl); -+ int status; -+ -+ enable_irq(ctrl->irq.num); -+ if (device_may_wakeup(dev)) { -+ status = enable_irq_wake(ctrl->irq.num); -+ if (status) { -+ ssam_err(ctrl, "failed to enable wake IRQ: %d\n", status); -+ disable_irq(ctrl->irq.num); -+ return status; -+ } -+ -+ ctrl->irq.wakeup_enabled = true; -+ } else { -+ ctrl->irq.wakeup_enabled = false; -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_irq_disarm_wakeup() - Disarm the wakeup IRQ. -+ * @ctrl: The controller for which the IRQ should be disarmed. -+ * -+ * Disarm the IRQ previously set up for wake via ssam_irq_arm_for_wakeup(). -+ * -+ * This function is intended to disarm the IRQ after exiting S2idle suspend. -+ * -+ * Note: calls to ssam_irq_arm_for_wakeup() and ssam_irq_disarm_wakeup() must -+ * be balanced. -+ */ -+void ssam_irq_disarm_wakeup(struct ssam_controller *ctrl) -+{ -+ int status; -+ -+ if (ctrl->irq.wakeup_enabled) { -+ status = disable_irq_wake(ctrl->irq.num); -+ if (status) -+ ssam_err(ctrl, "failed to disable wake IRQ: %d\n", status); -+ -+ ctrl->irq.wakeup_enabled = false; -+ } -+ disable_irq(ctrl->irq.num); -+} -diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h -new file mode 100644 -index 000000000000..5ee9e966f1d7 ---- /dev/null -+++ b/drivers/platform/surface/aggregator/controller.h -@@ -0,0 +1,276 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Main SSAM/SSH controller structure and functionality. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_CONTROLLER_H -+#define _SURFACE_AGGREGATOR_CONTROLLER_H -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "ssh_request_layer.h" -+ -+ -+/* -- Safe counters. -------------------------------------------------------- */ -+ -+/** -+ * struct ssh_seq_counter - Safe counter for SSH sequence IDs. -+ * @value: The current counter value. -+ */ -+struct ssh_seq_counter { -+ u8 value; -+}; -+ -+/** -+ * struct ssh_rqid_counter - Safe counter for SSH request IDs. -+ * @value: The current counter value. -+ */ -+struct ssh_rqid_counter { -+ u16 value; -+}; -+ -+ -+/* -- Event/notification system. -------------------------------------------- */ -+ -+/** -+ * struct ssam_nf_head - Notifier head for SSAM events. -+ * @srcu: The SRCU struct for synchronization. -+ * @head: List-head for notifier blocks registered under this head. -+ */ -+struct ssam_nf_head { -+ struct srcu_struct srcu; -+ struct list_head head; -+}; -+ -+/** -+ * struct ssam_nf - Notifier callback- and activation-registry for SSAM events. -+ * @lock: Lock guarding (de-)registration of notifier blocks. Note: This -+ * lock does not need to be held for notifier calls, only -+ * registration and deregistration. -+ * @refcount: The root of the RB-tree used for reference-counting enabled -+ * events/notifications. -+ * @head: The list of notifier heads for event/notification callbacks. -+ */ -+struct ssam_nf { -+ struct mutex lock; -+ struct rb_root refcount; -+ struct ssam_nf_head head[SSH_NUM_EVENTS]; -+}; -+ -+ -+/* -- Event/async request completion system. -------------------------------- */ -+ -+struct ssam_cplt; -+ -+/** -+ * struct ssam_event_item - Struct for event queuing and completion. -+ * @node: The node in the queue. -+ * @rqid: The request ID of the event. -+ * @event: Actual event data. -+ */ -+struct ssam_event_item { -+ struct list_head node; -+ u16 rqid; -+ -+ struct ssam_event event; /* must be last */ -+}; -+ -+/** -+ * struct ssam_event_queue - Queue for completing received events. -+ * @cplt: Reference to the completion system on which this queue is active. -+ * @lock: The lock for any operation on the queue. -+ * @head: The list-head of the queue. -+ * @work: The &struct work_struct performing completion work for this queue. -+ */ -+struct ssam_event_queue { -+ struct ssam_cplt *cplt; -+ -+ spinlock_t lock; -+ struct list_head head; -+ struct work_struct work; -+}; -+ -+/** -+ * struct ssam_event_target - Set of queues for a single SSH target ID. -+ * @queue: The array of queues, one queue per event ID. -+ */ -+struct ssam_event_target { -+ struct ssam_event_queue queue[SSH_NUM_EVENTS]; -+}; -+ -+/** -+ * struct ssam_cplt - SSAM event/async request completion system. -+ * @dev: The device with which this system is associated. Only used -+ * for logging. -+ * @wq: The &struct workqueue_struct on which all completion work -+ * items are queued. -+ * @event: Event completion management. -+ * @event.target: Array of &struct ssam_event_target, one for each target. -+ * @event.notif: Notifier callbacks and event activation reference counting. -+ */ -+struct ssam_cplt { -+ struct device *dev; -+ struct workqueue_struct *wq; -+ -+ struct { -+ struct ssam_event_target target[SSH_NUM_TARGETS]; -+ struct ssam_nf notif; -+ } event; -+}; -+ -+ -+/* -- Main SSAM device structures. ------------------------------------------ */ -+ -+/** -+ * enum ssam_controller_state - State values for &struct ssam_controller. -+ * @SSAM_CONTROLLER_UNINITIALIZED: -+ * The controller has not been initialized yet or has been deinitialized. -+ * @SSAM_CONTROLLER_INITIALIZED: -+ * The controller is initialized, but has not been started yet. -+ * @SSAM_CONTROLLER_STARTED: -+ * The controller has been started and is ready to use. -+ * @SSAM_CONTROLLER_STOPPED: -+ * The controller has been stopped. -+ * @SSAM_CONTROLLER_SUSPENDED: -+ * The controller has been suspended. -+ */ -+enum ssam_controller_state { -+ SSAM_CONTROLLER_UNINITIALIZED, -+ SSAM_CONTROLLER_INITIALIZED, -+ SSAM_CONTROLLER_STARTED, -+ SSAM_CONTROLLER_STOPPED, -+ SSAM_CONTROLLER_SUSPENDED, -+}; -+ -+/** -+ * struct ssam_controller_caps - Controller device capabilities. -+ * @ssh_power_profile: SSH power profile. -+ * @ssh_buffer_size: SSH driver UART buffer size. -+ * @screen_on_sleep_idle_timeout: SAM UART screen-on sleep idle timeout. -+ * @screen_off_sleep_idle_timeout: SAM UART screen-off sleep idle timeout. -+ * @d3_closes_handle: SAM closes UART handle in D3. -+ * -+ * Controller and SSH device capabilities found in ACPI. -+ */ -+struct ssam_controller_caps { -+ u32 ssh_power_profile; -+ u32 ssh_buffer_size; -+ u32 screen_on_sleep_idle_timeout; -+ u32 screen_off_sleep_idle_timeout; -+ u32 d3_closes_handle:1; -+}; -+ -+/** -+ * struct ssam_controller - SSAM controller device. -+ * @kref: Reference count of the controller. -+ * @lock: Main lock for the controller, used to guard state changes. -+ * @state: Controller state. -+ * @rtl: Request transport layer for SSH I/O. -+ * @cplt: Completion system for SSH/SSAM events and asynchronous requests. -+ * @counter: Safe SSH message ID counters. -+ * @counter.seq: Sequence ID counter. -+ * @counter.rqid: Request ID counter. -+ * @irq: Wakeup IRQ resources. -+ * @irq.num: The wakeup IRQ number. -+ * @irq.wakeup_enabled: Whether wakeup by IRQ is enabled during suspend. -+ * @caps: The controller device capabilities. -+ */ -+struct ssam_controller { -+ struct kref kref; -+ -+ struct rw_semaphore lock; -+ enum ssam_controller_state state; -+ -+ struct ssh_rtl rtl; -+ struct ssam_cplt cplt; -+ -+ struct { -+ struct ssh_seq_counter seq; -+ struct ssh_rqid_counter rqid; -+ } counter; -+ -+ struct { -+ int num; -+ bool wakeup_enabled; -+ } irq; -+ -+ struct ssam_controller_caps caps; -+}; -+ -+#define to_ssam_controller(ptr, member) \ -+ container_of(ptr, struct ssam_controller, member) -+ -+#define ssam_dbg(ctrl, fmt, ...) rtl_dbg(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+#define ssam_info(ctrl, fmt, ...) rtl_info(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+#define ssam_warn(ctrl, fmt, ...) rtl_warn(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+#define ssam_err(ctrl, fmt, ...) rtl_err(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+ -+/** -+ * ssam_controller_receive_buf() - Provide input-data to the controller. -+ * @ctrl: The controller. -+ * @buf: The input buffer. -+ * @n: The number of bytes in the input buffer. -+ * -+ * Provide input data to be evaluated by the controller, which has been -+ * received via the lower-level transport. -+ * -+ * Return: Returns the number of bytes consumed, or, if the packet transport -+ * layer of the controller has been shut down, %-ESHUTDOWN. -+ */ -+static inline -+int ssam_controller_receive_buf(struct ssam_controller *ctrl, -+ const unsigned char *buf, size_t n) -+{ -+ return ssh_ptl_rx_rcvbuf(&ctrl->rtl.ptl, buf, n); -+} -+ -+/** -+ * ssam_controller_write_wakeup() - Notify the controller that the underlying -+ * device has space available for data to be written. -+ * @ctrl: The controller. -+ */ -+static inline void ssam_controller_write_wakeup(struct ssam_controller *ctrl) -+{ -+ ssh_ptl_tx_wakeup_transfer(&ctrl->rtl.ptl); -+} -+ -+int ssam_controller_init(struct ssam_controller *ctrl, struct serdev_device *s); -+int ssam_controller_start(struct ssam_controller *ctrl); -+void ssam_controller_shutdown(struct ssam_controller *ctrl); -+void ssam_controller_destroy(struct ssam_controller *ctrl); -+ -+int ssam_notifier_disable_registered(struct ssam_controller *ctrl); -+void ssam_notifier_restore_registered(struct ssam_controller *ctrl); -+ -+int ssam_irq_setup(struct ssam_controller *ctrl); -+void ssam_irq_free(struct ssam_controller *ctrl); -+int ssam_irq_arm_for_wakeup(struct ssam_controller *ctrl); -+void ssam_irq_disarm_wakeup(struct ssam_controller *ctrl); -+ -+void ssam_controller_lock(struct ssam_controller *c); -+void ssam_controller_unlock(struct ssam_controller *c); -+ -+int ssam_get_firmware_version(struct ssam_controller *ctrl, u32 *version); -+int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl); -+int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl); -+int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl); -+int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl); -+ -+int ssam_controller_suspend(struct ssam_controller *ctrl); -+int ssam_controller_resume(struct ssam_controller *ctrl); -+ -+#endif /* _SURFACE_AGGREGATOR_CONTROLLER_H */ -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -new file mode 100644 -index 000000000000..18e0e9e34e7b ---- /dev/null -+++ b/drivers/platform/surface/aggregator/core.c -@@ -0,0 +1,787 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface Serial Hub (SSH) driver for communication with the Surface/System -+ * Aggregator Module (SSAM/SAM). -+ * -+ * Provides access to a SAM-over-SSH connected EC via a controller device. -+ * Handles communication via requests as well as enabling, disabling, and -+ * relaying of events. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include "controller.h" -+ -+ -+/* -- Static controller reference. ------------------------------------------ */ -+ -+/* -+ * Main controller reference. The corresponding lock must be held while -+ * accessing (reading/writing) the reference. -+ */ -+static struct ssam_controller *__ssam_controller; -+static DEFINE_SPINLOCK(__ssam_controller_lock); -+ -+/** -+ * ssam_get_controller() - Get reference to SSAM controller. -+ * -+ * Returns a reference to the SSAM controller of the system or %NULL if there -+ * is none, it hasn't been set up yet, or it has already been unregistered. -+ * This function automatically increments the reference count of the -+ * controller, thus the calling party must ensure that ssam_controller_put() -+ * is called when it doesn't need the controller any more. -+ */ -+struct ssam_controller *ssam_get_controller(void) -+{ -+ struct ssam_controller *ctrl; -+ -+ spin_lock(&__ssam_controller_lock); -+ -+ ctrl = __ssam_controller; -+ if (!ctrl) -+ goto out; -+ -+ if (WARN_ON(!kref_get_unless_zero(&ctrl->kref))) -+ ctrl = NULL; -+ -+out: -+ spin_unlock(&__ssam_controller_lock); -+ return ctrl; -+} -+EXPORT_SYMBOL_GPL(ssam_get_controller); -+ -+/** -+ * ssam_try_set_controller() - Try to set the main controller reference. -+ * @ctrl: The controller to which the reference should point. -+ * -+ * Set the main controller reference to the given pointer if the reference -+ * hasn't been set already. -+ * -+ * Return: Returns zero on success or %-EEXIST if the reference has already -+ * been set. -+ */ -+static int ssam_try_set_controller(struct ssam_controller *ctrl) -+{ -+ int status = 0; -+ -+ spin_lock(&__ssam_controller_lock); -+ if (!__ssam_controller) -+ __ssam_controller = ctrl; -+ else -+ status = -EEXIST; -+ spin_unlock(&__ssam_controller_lock); -+ -+ return status; -+} -+ -+/** -+ * ssam_clear_controller() - Remove/clear the main controller reference. -+ * -+ * Clears the main controller reference, i.e. sets it to %NULL. This function -+ * should be called before the controller is shut down. -+ */ -+static void ssam_clear_controller(void) -+{ -+ spin_lock(&__ssam_controller_lock); -+ __ssam_controller = NULL; -+ spin_unlock(&__ssam_controller_lock); -+} -+ -+/** -+ * ssam_client_link() - Link an arbitrary client device to the controller. -+ * @c: The controller to link to. -+ * @client: The client device. -+ * -+ * Link an arbitrary client device to the controller by creating a device link -+ * between it as consumer and the controller device as provider. This function -+ * can be used for non-SSAM devices (or SSAM devices not registered as child -+ * under the controller) to guarantee that the controller is valid for as long -+ * as the driver of the client device is bound, and that proper suspend and -+ * resume ordering is guaranteed. -+ * -+ * The device link does not have to be destructed manually. It is removed -+ * automatically once the driver of the client device unbinds. -+ * -+ * Return: Returns zero on success, %-ENODEV if the controller is not ready or -+ * going to be removed soon, or %-ENOMEM if the device link could not be -+ * created for other reasons. -+ */ -+int ssam_client_link(struct ssam_controller *c, struct device *client) -+{ -+ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; -+ struct device_link *link; -+ struct device *ctrldev; -+ -+ ssam_controller_statelock(c); -+ -+ if (c->state != SSAM_CONTROLLER_STARTED) { -+ ssam_controller_stateunlock(c); -+ return -ENODEV; -+ } -+ -+ ctrldev = ssam_controller_device(c); -+ if (!ctrldev) { -+ ssam_controller_stateunlock(c); -+ return -ENODEV; -+ } -+ -+ link = device_link_add(client, ctrldev, flags); -+ if (!link) { -+ ssam_controller_stateunlock(c); -+ return -ENOMEM; -+ } -+ -+ /* -+ * Return -ENODEV if supplier driver is on its way to be removed. In -+ * this case, the controller won't be around for much longer and the -+ * device link is not going to save us any more, as unbinding is -+ * already in progress. -+ */ -+ if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) { -+ ssam_controller_stateunlock(c); -+ return -ENODEV; -+ } -+ -+ ssam_controller_stateunlock(c); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_client_link); -+ -+/** -+ * ssam_client_bind() - Bind an arbitrary client device to the controller. -+ * @client: The client device. -+ * -+ * Link an arbitrary client device to the controller by creating a device link -+ * between it as consumer and the main controller device as provider. This -+ * function can be used for non-SSAM devices to guarantee that the controller -+ * returned by this function is valid for as long as the driver of the client -+ * device is bound, and that proper suspend and resume ordering is guaranteed. -+ * -+ * This function does essentially the same as ssam_client_link(), except that -+ * it first fetches the main controller reference, then creates the link, and -+ * finally returns this reference. Note that this function does not increment -+ * the reference counter of the controller, as, due to the link, the -+ * controller lifetime is assured as long as the driver of the client device -+ * is bound. -+ * -+ * It is not valid to use the controller reference obtained by this method -+ * outside of the driver bound to the client device at the time of calling -+ * this function, without first incrementing the reference count of the -+ * controller via ssam_controller_get(). Even after doing this, care must be -+ * taken that requests are only submitted and notifiers are only -+ * (un-)registered when the controller is active and not suspended. In other -+ * words: The device link only lives as long as the client driver is bound and -+ * any guarantees enforced by this link (e.g. active controller state) can -+ * only be relied upon as long as this link exists and may need to be enforced -+ * in other ways afterwards. -+ * -+ * The created device link does not have to be destructed manually. It is -+ * removed automatically once the driver of the client device unbinds. -+ * -+ * Return: Returns the controller on success, an error pointer with %-ENODEV -+ * if the controller is not present, not ready or going to be removed soon, or -+ * %-ENOMEM if the device link could not be created for other reasons. -+ */ -+struct ssam_controller *ssam_client_bind(struct device *client) -+{ -+ struct ssam_controller *c; -+ int status; -+ -+ c = ssam_get_controller(); -+ if (!c) -+ return ERR_PTR(-ENODEV); -+ -+ status = ssam_client_link(c, client); -+ -+ /* -+ * Note that we can drop our controller reference in both success and -+ * failure cases: On success, we have bound the controller lifetime -+ * inherently to the client driver lifetime, i.e. it the controller is -+ * now guaranteed to outlive the client driver. On failure, we're not -+ * going to use the controller any more. -+ */ -+ ssam_controller_put(c); -+ -+ return status >= 0 ? c : ERR_PTR(status); -+} -+EXPORT_SYMBOL_GPL(ssam_client_bind); -+ -+ -+/* -- Glue layer (serdev_device -> ssam_controller). ------------------------ */ -+ -+static int ssam_receive_buf(struct serdev_device *dev, const unsigned char *buf, -+ size_t n) -+{ -+ struct ssam_controller *ctrl; -+ -+ ctrl = serdev_device_get_drvdata(dev); -+ return ssam_controller_receive_buf(ctrl, buf, n); -+} -+ -+static void ssam_write_wakeup(struct serdev_device *dev) -+{ -+ ssam_controller_write_wakeup(serdev_device_get_drvdata(dev)); -+} -+ -+static const struct serdev_device_ops ssam_serdev_ops = { -+ .receive_buf = ssam_receive_buf, -+ .write_wakeup = ssam_write_wakeup, -+}; -+ -+ -+/* -- SysFS and misc. ------------------------------------------------------- */ -+ -+static int ssam_log_firmware_version(struct ssam_controller *ctrl) -+{ -+ u32 version, a, b, c; -+ int status; -+ -+ status = ssam_get_firmware_version(ctrl, &version); -+ if (status) -+ return status; -+ -+ a = (version >> 24) & 0xff; -+ b = ((version >> 8) & 0xffff); -+ c = version & 0xff; -+ -+ ssam_info(ctrl, "SAM firmware version: %u.%u.%u\n", a, b, c); -+ return 0; -+} -+ -+static ssize_t firmware_version_show(struct device *dev, -+ struct device_attribute *attr, char *buf) -+{ -+ struct ssam_controller *ctrl = dev_get_drvdata(dev); -+ u32 version, a, b, c; -+ int status; -+ -+ status = ssam_get_firmware_version(ctrl, &version); -+ if (status < 0) -+ return status; -+ -+ a = (version >> 24) & 0xff; -+ b = ((version >> 8) & 0xffff); -+ c = version & 0xff; -+ -+ return sysfs_emit(buf, "%u.%u.%u\n", a, b, c); -+} -+static DEVICE_ATTR_RO(firmware_version); -+ -+static struct attribute *ssam_sam_attrs[] = { -+ &dev_attr_firmware_version.attr, -+ NULL -+}; -+ -+static const struct attribute_group ssam_sam_group = { -+ .name = "sam", -+ .attrs = ssam_sam_attrs, -+}; -+ -+ -+/* -- ACPI based device setup. ---------------------------------------------- */ -+ -+static acpi_status ssam_serdev_setup_via_acpi_crs(struct acpi_resource *rsc, -+ void *ctx) -+{ -+ struct serdev_device *serdev = ctx; -+ struct acpi_resource_common_serialbus *serial; -+ struct acpi_resource_uart_serialbus *uart; -+ bool flow_control; -+ int status = 0; -+ -+ if (rsc->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) -+ return AE_OK; -+ -+ serial = &rsc->data.common_serial_bus; -+ if (serial->type != ACPI_RESOURCE_SERIAL_TYPE_UART) -+ return AE_OK; -+ -+ uart = &rsc->data.uart_serial_bus; -+ -+ /* Set up serdev device. */ -+ serdev_device_set_baudrate(serdev, uart->default_baud_rate); -+ -+ /* serdev currently only supports RTSCTS flow control. */ -+ if (uart->flow_control & (~((u8)ACPI_UART_FLOW_CONTROL_HW))) { -+ dev_warn(&serdev->dev, "setup: unsupported flow control (value: %#04x)\n", -+ uart->flow_control); -+ } -+ -+ /* Set RTSCTS flow control. */ -+ flow_control = uart->flow_control & ACPI_UART_FLOW_CONTROL_HW; -+ serdev_device_set_flow_control(serdev, flow_control); -+ -+ /* serdev currently only supports EVEN/ODD parity. */ -+ switch (uart->parity) { -+ case ACPI_UART_PARITY_NONE: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); -+ break; -+ case ACPI_UART_PARITY_EVEN: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_EVEN); -+ break; -+ case ACPI_UART_PARITY_ODD: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_ODD); -+ break; -+ default: -+ dev_warn(&serdev->dev, "setup: unsupported parity (value: %#04x)\n", -+ uart->parity); -+ break; -+ } -+ -+ if (status) { -+ dev_err(&serdev->dev, "setup: failed to set parity (value: %#04x, error: %d)\n", -+ uart->parity, status); -+ return AE_ERROR; -+ } -+ -+ /* We've found the resource and are done. */ -+ return AE_CTRL_TERMINATE; -+} -+ -+static acpi_status ssam_serdev_setup_via_acpi(acpi_handle handle, -+ struct serdev_device *serdev) -+{ -+ return acpi_walk_resources(handle, METHOD_NAME__CRS, -+ ssam_serdev_setup_via_acpi_crs, serdev); -+} -+ -+ -+/* -- Power management. ----------------------------------------------------- */ -+ -+static void ssam_serial_hub_shutdown(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Try to disable notifiers, signal display-off and D0-exit, ignore any -+ * errors. -+ * -+ * Note: It has not been established yet if this is actually -+ * necessary/useful for shutdown. -+ */ -+ -+ status = ssam_notifier_disable_registered(c); -+ if (status) { -+ ssam_err(c, "pm: failed to disable notifiers for shutdown: %d\n", -+ status); -+ } -+ -+ status = ssam_ctrl_notif_display_off(c); -+ if (status) -+ ssam_err(c, "pm: display-off notification failed: %d\n", status); -+ -+ status = ssam_ctrl_notif_d0_exit(c); -+ if (status) -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+} -+ -+#ifdef CONFIG_PM_SLEEP -+ -+static int ssam_serial_hub_pm_prepare(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Try to signal display-off, This will quiesce events. -+ * -+ * Note: Signaling display-off/display-on should normally be done from -+ * some sort of display state notifier. As that is not available, -+ * signal it here. -+ */ -+ -+ status = ssam_ctrl_notif_display_off(c); -+ if (status) -+ ssam_err(c, "pm: display-off notification failed: %d\n", status); -+ -+ return status; -+} -+ -+static void ssam_serial_hub_pm_complete(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Try to signal display-on. This will restore events. -+ * -+ * Note: Signaling display-off/display-on should normally be done from -+ * some sort of display state notifier. As that is not available, -+ * signal it here. -+ */ -+ -+ status = ssam_ctrl_notif_display_on(c); -+ if (status) -+ ssam_err(c, "pm: display-on notification failed: %d\n", status); -+} -+ -+static int ssam_serial_hub_pm_suspend(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Try to signal D0-exit, enable IRQ wakeup if specified. Abort on -+ * error. -+ */ -+ -+ status = ssam_ctrl_notif_d0_exit(c); -+ if (status) { -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+ goto err_notif; -+ } -+ -+ status = ssam_irq_arm_for_wakeup(c); -+ if (status) -+ goto err_irq; -+ -+ WARN_ON(ssam_controller_suspend(c)); -+ return 0; -+ -+err_irq: -+ ssam_ctrl_notif_d0_entry(c); -+err_notif: -+ ssam_ctrl_notif_display_on(c); -+ return status; -+} -+ -+static int ssam_serial_hub_pm_resume(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ WARN_ON(ssam_controller_resume(c)); -+ -+ /* -+ * Try to disable IRQ wakeup (if specified) and signal D0-entry. In -+ * case of errors, log them and try to restore normal operation state -+ * as far as possible. -+ * -+ * Note: Signaling display-off/display-on should normally be done from -+ * some sort of display state notifier. As that is not available, -+ * signal it here. -+ */ -+ -+ ssam_irq_disarm_wakeup(c); -+ -+ status = ssam_ctrl_notif_d0_entry(c); -+ if (status) -+ ssam_err(c, "pm: D0-entry notification failed: %d\n", status); -+ -+ return 0; -+} -+ -+static int ssam_serial_hub_pm_freeze(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * During hibernation image creation, we only have to ensure that the -+ * EC doesn't send us any events. This is done via the display-off -+ * and D0-exit notifications. Note that this sets up the wakeup IRQ -+ * on the EC side, however, we have disabled it by default on our side -+ * and won't enable it here. -+ * -+ * See ssam_serial_hub_poweroff() for more details on the hibernation -+ * process. -+ */ -+ -+ status = ssam_ctrl_notif_d0_exit(c); -+ if (status) { -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+ ssam_ctrl_notif_display_on(c); -+ return status; -+ } -+ -+ WARN_ON(ssam_controller_suspend(c)); -+ return 0; -+} -+ -+static int ssam_serial_hub_pm_thaw(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ WARN_ON(ssam_controller_resume(c)); -+ -+ status = ssam_ctrl_notif_d0_entry(c); -+ if (status) -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+ -+ return status; -+} -+ -+static int ssam_serial_hub_pm_poweroff(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * When entering hibernation and powering off the system, the EC, at -+ * least on some models, may disable events. Without us taking care of -+ * that, this leads to events not being enabled/restored when the -+ * system resumes from hibernation, resulting SAM-HID subsystem devices -+ * (i.e. keyboard, touchpad) not working, AC-plug/AC-unplug events being -+ * gone, etc. -+ * -+ * To avoid these issues, we disable all registered events here (this is -+ * likely not actually required) and restore them during the drivers PM -+ * restore callback. -+ * -+ * Wakeup from the EC interrupt is not supported during hibernation, -+ * so don't arm the IRQ here. -+ */ -+ -+ status = ssam_notifier_disable_registered(c); -+ if (status) { -+ ssam_err(c, "pm: failed to disable notifiers for hibernation: %d\n", -+ status); -+ return status; -+ } -+ -+ status = ssam_ctrl_notif_d0_exit(c); -+ if (status) { -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+ ssam_notifier_restore_registered(c); -+ return status; -+ } -+ -+ WARN_ON(ssam_controller_suspend(c)); -+ return 0; -+} -+ -+static int ssam_serial_hub_pm_restore(struct device *dev) -+{ -+ struct ssam_controller *c = dev_get_drvdata(dev); -+ int status; -+ -+ /* -+ * Ignore but log errors, try to restore state as much as possible in -+ * case of failures. See ssam_serial_hub_poweroff() for more details on -+ * the hibernation process. -+ */ -+ -+ WARN_ON(ssam_controller_resume(c)); -+ -+ status = ssam_ctrl_notif_d0_entry(c); -+ if (status) -+ ssam_err(c, "pm: D0-entry notification failed: %d\n", status); -+ -+ ssam_notifier_restore_registered(c); -+ return 0; -+} -+ -+static const struct dev_pm_ops ssam_serial_hub_pm_ops = { -+ .prepare = ssam_serial_hub_pm_prepare, -+ .complete = ssam_serial_hub_pm_complete, -+ .suspend = ssam_serial_hub_pm_suspend, -+ .resume = ssam_serial_hub_pm_resume, -+ .freeze = ssam_serial_hub_pm_freeze, -+ .thaw = ssam_serial_hub_pm_thaw, -+ .poweroff = ssam_serial_hub_pm_poweroff, -+ .restore = ssam_serial_hub_pm_restore, -+}; -+ -+#else /* CONFIG_PM_SLEEP */ -+ -+static const struct dev_pm_ops ssam_serial_hub_pm_ops = { }; -+ -+#endif /* CONFIG_PM_SLEEP */ -+ -+ -+/* -- Device/driver setup. -------------------------------------------------- */ -+ -+static const struct acpi_gpio_params gpio_ssam_wakeup_int = { 0, 0, false }; -+static const struct acpi_gpio_params gpio_ssam_wakeup = { 1, 0, false }; -+ -+static const struct acpi_gpio_mapping ssam_acpi_gpios[] = { -+ { "ssam_wakeup-int-gpio", &gpio_ssam_wakeup_int, 1 }, -+ { "ssam_wakeup-gpio", &gpio_ssam_wakeup, 1 }, -+ { }, -+}; -+ -+static int ssam_serial_hub_probe(struct serdev_device *serdev) -+{ -+ struct ssam_controller *ctrl; -+ acpi_handle *ssh = ACPI_HANDLE(&serdev->dev); -+ acpi_status astatus; -+ int status; -+ -+ if (gpiod_count(&serdev->dev, NULL) < 0) -+ return -ENODEV; -+ -+ status = devm_acpi_dev_add_driver_gpios(&serdev->dev, ssam_acpi_gpios); -+ if (status) -+ return status; -+ -+ /* Allocate controller. */ -+ ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); -+ if (!ctrl) -+ return -ENOMEM; -+ -+ /* Initialize controller. */ -+ status = ssam_controller_init(ctrl, serdev); -+ if (status) -+ goto err_ctrl_init; -+ -+ ssam_controller_lock(ctrl); -+ -+ /* Set up serdev device. */ -+ serdev_device_set_drvdata(serdev, ctrl); -+ serdev_device_set_client_ops(serdev, &ssam_serdev_ops); -+ status = serdev_device_open(serdev); -+ if (status) -+ goto err_devopen; -+ -+ astatus = ssam_serdev_setup_via_acpi(ssh, serdev); -+ if (ACPI_FAILURE(astatus)) { -+ status = -ENXIO; -+ goto err_devinit; -+ } -+ -+ /* Start controller. */ -+ status = ssam_controller_start(ctrl); -+ if (status) -+ goto err_devinit; -+ -+ ssam_controller_unlock(ctrl); -+ -+ /* -+ * Initial SAM requests: Log version and notify default/init power -+ * states. -+ */ -+ status = ssam_log_firmware_version(ctrl); -+ if (status) -+ goto err_initrq; -+ -+ status = ssam_ctrl_notif_d0_entry(ctrl); -+ if (status) -+ goto err_initrq; -+ -+ status = ssam_ctrl_notif_display_on(ctrl); -+ if (status) -+ goto err_initrq; -+ -+ status = sysfs_create_group(&serdev->dev.kobj, &ssam_sam_group); -+ if (status) -+ goto err_initrq; -+ -+ /* Set up IRQ. */ -+ status = ssam_irq_setup(ctrl); -+ if (status) -+ goto err_irq; -+ -+ /* Finally, set main controller reference. */ -+ status = ssam_try_set_controller(ctrl); -+ if (WARN_ON(status)) /* Currently, we're the only provider. */ -+ goto err_mainref; -+ -+ /* -+ * TODO: The EC can wake up the system via the associated GPIO interrupt -+ * in multiple situations. One of which is the remaining battery -+ * capacity falling below a certain threshold. Normally, we should -+ * use the device_init_wakeup function, however, the EC also seems -+ * to have other reasons for waking up the system and it seems -+ * that Windows has additional checks whether the system should be -+ * resumed. In short, this causes some spurious unwanted wake-ups. -+ * For now let's thus default power/wakeup to false. -+ */ -+ device_set_wakeup_capable(&serdev->dev, true); -+ acpi_walk_dep_device_list(ssh); -+ -+ return 0; -+ -+err_mainref: -+ ssam_irq_free(ctrl); -+err_irq: -+ sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); -+err_initrq: -+ ssam_controller_lock(ctrl); -+ ssam_controller_shutdown(ctrl); -+err_devinit: -+ serdev_device_close(serdev); -+err_devopen: -+ ssam_controller_destroy(ctrl); -+ ssam_controller_unlock(ctrl); -+err_ctrl_init: -+ kfree(ctrl); -+ return status; -+} -+ -+static void ssam_serial_hub_remove(struct serdev_device *serdev) -+{ -+ struct ssam_controller *ctrl = serdev_device_get_drvdata(serdev); -+ int status; -+ -+ /* Clear static reference so that no one else can get a new one. */ -+ ssam_clear_controller(); -+ -+ /* Disable and free IRQ. */ -+ ssam_irq_free(ctrl); -+ -+ sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); -+ ssam_controller_lock(ctrl); -+ -+ /* Act as if suspending to silence events. */ -+ status = ssam_ctrl_notif_display_off(ctrl); -+ if (status) { -+ dev_err(&serdev->dev, "display-off notification failed: %d\n", -+ status); -+ } -+ -+ status = ssam_ctrl_notif_d0_exit(ctrl); -+ if (status) { -+ dev_err(&serdev->dev, "D0-exit notification failed: %d\n", -+ status); -+ } -+ -+ /* Shut down controller and remove serdev device reference from it. */ -+ ssam_controller_shutdown(ctrl); -+ -+ /* Shut down actual transport. */ -+ serdev_device_wait_until_sent(serdev, 0); -+ serdev_device_close(serdev); -+ -+ /* Drop our controller reference. */ -+ ssam_controller_unlock(ctrl); -+ ssam_controller_put(ctrl); -+ -+ device_set_wakeup_capable(&serdev->dev, false); -+} -+ -+static const struct acpi_device_id ssam_serial_hub_match[] = { -+ { "MSHW0084", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, ssam_serial_hub_match); -+ -+static struct serdev_device_driver ssam_serial_hub = { -+ .probe = ssam_serial_hub_probe, -+ .remove = ssam_serial_hub_remove, -+ .driver = { -+ .name = "surface_serial_hub", -+ .acpi_match_table = ssam_serial_hub_match, -+ .pm = &ssam_serial_hub_pm_ops, -+ .shutdown = ssam_serial_hub_shutdown, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_serdev_device_driver(ssam_serial_hub); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Subsystem and Surface Serial Hub driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h -new file mode 100644 -index 000000000000..1221f642dda1 ---- /dev/null -+++ b/drivers/platform/surface/aggregator/ssh_msgb.h -@@ -0,0 +1,205 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * SSH message builder functions. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H -+#define _SURFACE_AGGREGATOR_SSH_MSGB_H -+ -+#include -+#include -+ -+#include -+#include -+ -+/** -+ * struct msgbuf - Buffer struct to construct SSH messages. -+ * @begin: Pointer to the beginning of the allocated buffer space. -+ * @end: Pointer to the end (one past last element) of the allocated buffer -+ * space. -+ * @ptr: Pointer to the first free element in the buffer. -+ */ -+struct msgbuf { -+ u8 *begin; -+ u8 *end; -+ u8 *ptr; -+}; -+ -+/** -+ * msgb_init() - Initialize the given message buffer struct. -+ * @msgb: The buffer struct to initialize -+ * @ptr: Pointer to the underlying memory by which the buffer will be backed. -+ * @cap: Size of the underlying memory. -+ * -+ * Initialize the given message buffer struct using the provided memory as -+ * backing. -+ */ -+static inline void msgb_init(struct msgbuf *msgb, u8 *ptr, size_t cap) -+{ -+ msgb->begin = ptr; -+ msgb->end = ptr + cap; -+ msgb->ptr = ptr; -+} -+ -+/** -+ * msgb_bytes_used() - Return the current number of bytes used in the buffer. -+ * @msgb: The message buffer. -+ */ -+static inline size_t msgb_bytes_used(const struct msgbuf *msgb) -+{ -+ return msgb->ptr - msgb->begin; -+} -+ -+static inline void __msgb_push_u8(struct msgbuf *msgb, u8 value) -+{ -+ *msgb->ptr = value; -+ msgb->ptr += sizeof(u8); -+} -+ -+static inline void __msgb_push_u16(struct msgbuf *msgb, u16 value) -+{ -+ put_unaligned_le16(value, msgb->ptr); -+ msgb->ptr += sizeof(u16); -+} -+ -+/** -+ * msgb_push_u16() - Push a u16 value to the buffer. -+ * @msgb: The message buffer. -+ * @value: The value to push to the buffer. -+ */ -+static inline void msgb_push_u16(struct msgbuf *msgb, u16 value) -+{ -+ if (WARN_ON(msgb->ptr + sizeof(u16) > msgb->end)) -+ return; -+ -+ __msgb_push_u16(msgb, value); -+} -+ -+/** -+ * msgb_push_syn() - Push SSH SYN bytes to the buffer. -+ * @msgb: The message buffer. -+ */ -+static inline void msgb_push_syn(struct msgbuf *msgb) -+{ -+ msgb_push_u16(msgb, SSH_MSG_SYN); -+} -+ -+/** -+ * msgb_push_buf() - Push raw data to the buffer. -+ * @msgb: The message buffer. -+ * @buf: The data to push to the buffer. -+ * @len: The length of the data to push to the buffer. -+ */ -+static inline void msgb_push_buf(struct msgbuf *msgb, const u8 *buf, size_t len) -+{ -+ msgb->ptr = memcpy(msgb->ptr, buf, len) + len; -+} -+ -+/** -+ * msgb_push_crc() - Compute CRC and push it to the buffer. -+ * @msgb: The message buffer. -+ * @buf: The data for which the CRC should be computed. -+ * @len: The length of the data for which the CRC should be computed. -+ */ -+static inline void msgb_push_crc(struct msgbuf *msgb, const u8 *buf, size_t len) -+{ -+ msgb_push_u16(msgb, ssh_crc(buf, len)); -+} -+ -+/** -+ * msgb_push_frame() - Push a SSH message frame header to the buffer. -+ * @msgb: The message buffer -+ * @ty: The type of the frame. -+ * @len: The length of the payload of the frame. -+ * @seq: The sequence ID of the frame/packet. -+ */ -+static inline void msgb_push_frame(struct msgbuf *msgb, u8 ty, u16 len, u8 seq) -+{ -+ u8 *const begin = msgb->ptr; -+ -+ if (WARN_ON(msgb->ptr + sizeof(struct ssh_frame) > msgb->end)) -+ return; -+ -+ __msgb_push_u8(msgb, ty); /* Frame type. */ -+ __msgb_push_u16(msgb, len); /* Frame payload length. */ -+ __msgb_push_u8(msgb, seq); /* Frame sequence ID. */ -+ -+ msgb_push_crc(msgb, begin, msgb->ptr - begin); -+} -+ -+/** -+ * msgb_push_ack() - Push a SSH ACK frame to the buffer. -+ * @msgb: The message buffer -+ * @seq: The sequence ID of the frame/packet to be ACKed. -+ */ -+static inline void msgb_push_ack(struct msgbuf *msgb, u8 seq) -+{ -+ /* SYN. */ -+ msgb_push_syn(msgb); -+ -+ /* ACK-type frame + CRC. */ -+ msgb_push_frame(msgb, SSH_FRAME_TYPE_ACK, 0x00, seq); -+ -+ /* Payload CRC (ACK-type frames do not have a payload). */ -+ msgb_push_crc(msgb, msgb->ptr, 0); -+} -+ -+/** -+ * msgb_push_nak() - Push a SSH NAK frame to the buffer. -+ * @msgb: The message buffer -+ */ -+static inline void msgb_push_nak(struct msgbuf *msgb) -+{ -+ /* SYN. */ -+ msgb_push_syn(msgb); -+ -+ /* NAK-type frame + CRC. */ -+ msgb_push_frame(msgb, SSH_FRAME_TYPE_NAK, 0x00, 0x00); -+ -+ /* Payload CRC (ACK-type frames do not have a payload). */ -+ msgb_push_crc(msgb, msgb->ptr, 0); -+} -+ -+/** -+ * msgb_push_cmd() - Push a SSH command frame with payload to the buffer. -+ * @msgb: The message buffer. -+ * @seq: The sequence ID (SEQ) of the frame/packet. -+ * @rqid: The request ID (RQID) of the request contained in the frame. -+ * @rqst: The request to wrap in the frame. -+ */ -+static inline void msgb_push_cmd(struct msgbuf *msgb, u8 seq, u16 rqid, -+ const struct ssam_request *rqst) -+{ -+ const u8 type = SSH_FRAME_TYPE_DATA_SEQ; -+ u8 *cmd; -+ -+ /* SYN. */ -+ msgb_push_syn(msgb); -+ -+ /* Command frame + CRC. */ -+ msgb_push_frame(msgb, type, sizeof(struct ssh_command) + rqst->length, seq); -+ -+ /* Frame payload: Command struct + payload. */ -+ if (WARN_ON(msgb->ptr + sizeof(struct ssh_command) > msgb->end)) -+ return; -+ -+ cmd = msgb->ptr; -+ -+ __msgb_push_u8(msgb, SSH_PLD_TYPE_CMD); /* Payload type. */ -+ __msgb_push_u8(msgb, rqst->target_category); /* Target category. */ -+ __msgb_push_u8(msgb, rqst->target_id); /* Target ID (out). */ -+ __msgb_push_u8(msgb, 0x00); /* Target ID (in). */ -+ __msgb_push_u8(msgb, rqst->instance_id); /* Instance ID. */ -+ __msgb_push_u16(msgb, rqid); /* Request ID. */ -+ __msgb_push_u8(msgb, rqst->command_id); /* Command ID. */ -+ -+ /* Command payload. */ -+ msgb_push_buf(msgb, rqst->payload, rqst->length); -+ -+ /* CRC for command struct + payload. */ -+ msgb_push_crc(msgb, cmd, msgb->ptr - cmd); -+} -+ -+#endif /* _SURFACE_AGGREGATOR_SSH_MSGB_H */ -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c -new file mode 100644 -index 000000000000..66e38fdc7963 ---- /dev/null -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c -@@ -0,0 +1,1710 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * SSH packet transport layer. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "ssh_msgb.h" -+#include "ssh_packet_layer.h" -+#include "ssh_parser.h" -+ -+/* -+ * To simplify reasoning about the code below, we define a few concepts. The -+ * system below is similar to a state-machine for packets, however, there are -+ * too many states to explicitly write them down. To (somewhat) manage the -+ * states and packets we rely on flags, reference counting, and some simple -+ * concepts. State transitions are triggered by actions. -+ * -+ * >> Actions << -+ * -+ * - submit -+ * - transmission start (process next item in queue) -+ * - transmission finished (guaranteed to never be parallel to transmission -+ * start) -+ * - ACK received -+ * - NAK received (this is equivalent to issuing re-submit for all pending -+ * packets) -+ * - timeout (this is equivalent to re-issuing a submit or canceling) -+ * - cancel (non-pending and pending) -+ * -+ * >> Data Structures, Packet Ownership, General Overview << -+ * -+ * The code below employs two main data structures: The packet queue, -+ * containing all packets scheduled for transmission, and the set of pending -+ * packets, containing all packets awaiting an ACK. -+ * -+ * Shared ownership of a packet is controlled via reference counting. Inside -+ * the transport system are a total of five packet owners: -+ * -+ * - the packet queue, -+ * - the pending set, -+ * - the transmitter thread, -+ * - the receiver thread (via ACKing), and -+ * - the timeout work item. -+ * -+ * Normal operation is as follows: The initial reference of the packet is -+ * obtained by submitting the packet and queuing it. The receiver thread takes -+ * packets from the queue. By doing this, it does not increment the refcount -+ * but takes over the reference (removing it from the queue). If the packet is -+ * sequenced (i.e. needs to be ACKed by the client), the transmitter thread -+ * sets-up the timeout and adds the packet to the pending set before starting -+ * to transmit it. As the timeout is handled by a reaper task, no additional -+ * reference for it is needed. After the transmit is done, the reference held -+ * by the transmitter thread is dropped. If the packet is unsequenced (i.e. -+ * does not need an ACK), the packet is completed by the transmitter thread -+ * before dropping that reference. -+ * -+ * On receival of an ACK, the receiver thread removes and obtains the -+ * reference to the packet from the pending set. The receiver thread will then -+ * complete the packet and drop its reference. -+ * -+ * On receival of a NAK, the receiver thread re-submits all currently pending -+ * packets. -+ * -+ * Packet timeouts are detected by the timeout reaper. This is a task, -+ * scheduled depending on the earliest packet timeout expiration date, -+ * checking all currently pending packets if their timeout has expired. If the -+ * timeout of a packet has expired, it is re-submitted and the number of tries -+ * of this packet is incremented. If this number reaches its limit, the packet -+ * will be completed with a failure. -+ * -+ * On transmission failure (such as repeated packet timeouts), the completion -+ * callback is immediately run by on thread on which the error was detected. -+ * -+ * To ensure that a packet eventually leaves the system it is marked as -+ * "locked" directly before it is going to be completed or when it is -+ * canceled. Marking a packet as "locked" has the effect that passing and -+ * creating new references of the packet is disallowed. This means that the -+ * packet cannot be added to the queue, the pending set, and the timeout, or -+ * be picked up by the transmitter thread or receiver thread. To remove a -+ * packet from the system it has to be marked as locked and subsequently all -+ * references from the data structures (queue, pending) have to be removed. -+ * References held by threads will eventually be dropped automatically as -+ * their execution progresses. -+ * -+ * Note that the packet completion callback is, in case of success and for a -+ * sequenced packet, guaranteed to run on the receiver thread, thus providing -+ * a way to reliably identify responses to the packet. The packet completion -+ * callback is only run once and it does not indicate that the packet has -+ * fully left the system (for this, one should rely on the release method, -+ * triggered when the reference count of the packet reaches zero). In case of -+ * re-submission (and with somewhat unlikely timing), it may be possible that -+ * the packet is being re-transmitted while the completion callback runs. -+ * Completion will occur both on success and internal error, as well as when -+ * the packet is canceled. -+ * -+ * >> Flags << -+ * -+ * Flags are used to indicate the state and progression of a packet. Some flags -+ * have stricter guarantees than other: -+ * -+ * - locked -+ * Indicates if the packet is locked. If the packet is locked, passing and/or -+ * creating additional references to the packet is forbidden. The packet thus -+ * may not be queued, dequeued, or removed or added to the pending set. Note -+ * that the packet state flags may still change (e.g. it may be marked as -+ * ACKed, transmitted, ...). -+ * -+ * - completed -+ * Indicates if the packet completion callback has been executed or is about -+ * to be executed. This flag is used to ensure that the packet completion -+ * callback is only run once. -+ * -+ * - queued -+ * Indicates if a packet is present in the submission queue or not. This flag -+ * must only be modified with the queue lock held, and must be coherent to the -+ * presence of the packet in the queue. -+ * -+ * - pending -+ * Indicates if a packet is present in the set of pending packets or not. -+ * This flag must only be modified with the pending lock held, and must be -+ * coherent to the presence of the packet in the pending set. -+ * -+ * - transmitting -+ * Indicates if the packet is currently transmitting. In case of -+ * re-transmissions, it is only safe to wait on the "transmitted" completion -+ * after this flag has been set. The completion will be set both in success -+ * and error case. -+ * -+ * - transmitted -+ * Indicates if the packet has been transmitted. This flag is not cleared by -+ * the system, thus it indicates the first transmission only. -+ * -+ * - acked -+ * Indicates if the packet has been acknowledged by the client. There are no -+ * other guarantees given. For example, the packet may still be canceled -+ * and/or the completion may be triggered an error even though this bit is -+ * set. Rely on the status provided to the completion callback instead. -+ * -+ * - canceled -+ * Indicates if the packet has been canceled from the outside. There are no -+ * other guarantees given. Specifically, the packet may be completed by -+ * another part of the system before the cancellation attempts to complete it. -+ * -+ * >> General Notes << -+ * -+ * - To avoid deadlocks, if both queue and pending locks are required, the -+ * pending lock must be acquired before the queue lock. -+ * -+ * - The packet priority must be accessed only while holding the queue lock. -+ * -+ * - The packet timestamp must be accessed only while holding the pending -+ * lock. -+ */ -+ -+/* -+ * SSH_PTL_MAX_PACKET_TRIES - Maximum transmission attempts for packet. -+ * -+ * Maximum number of transmission attempts per sequenced packet in case of -+ * time-outs. Must be smaller than 16. If the packet times out after this -+ * amount of tries, the packet will be completed with %-ETIMEDOUT as status -+ * code. -+ */ -+#define SSH_PTL_MAX_PACKET_TRIES 3 -+ -+/* -+ * SSH_PTL_TX_TIMEOUT - Packet transmission timeout. -+ * -+ * Timeout in jiffies for packet transmission via the underlying serial -+ * device. If transmitting the packet takes longer than this timeout, the -+ * packet will be completed with -ETIMEDOUT. It will not be re-submitted. -+ */ -+#define SSH_PTL_TX_TIMEOUT HZ -+ -+/* -+ * SSH_PTL_PACKET_TIMEOUT - Packet response timeout. -+ * -+ * Timeout as ktime_t delta for ACKs. If we have not received an ACK in this -+ * time-frame after starting transmission, the packet will be re-submitted. -+ */ -+#define SSH_PTL_PACKET_TIMEOUT ms_to_ktime(1000) -+ -+/* -+ * SSH_PTL_PACKET_TIMEOUT_RESOLUTION - Packet timeout granularity. -+ * -+ * Time-resolution for timeouts. Should be larger than one jiffy to avoid -+ * direct re-scheduling of reaper work_struct. -+ */ -+#define SSH_PTL_PACKET_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) -+ -+/* -+ * SSH_PTL_MAX_PENDING - Maximum number of pending packets. -+ * -+ * Maximum number of sequenced packets concurrently waiting for an ACK. -+ * Packets marked as blocking will not be transmitted while this limit is -+ * reached. -+ */ -+#define SSH_PTL_MAX_PENDING 1 -+ -+/* -+ * SSH_PTL_RX_BUF_LEN - Evaluation-buffer size in bytes. -+ */ -+#define SSH_PTL_RX_BUF_LEN 4096 -+ -+/* -+ * SSH_PTL_RX_FIFO_LEN - Fifo input-buffer size in bytes. -+ */ -+#define SSH_PTL_RX_FIFO_LEN 4096 -+ -+static void __ssh_ptl_packet_release(struct kref *kref) -+{ -+ struct ssh_packet *p = container_of(kref, struct ssh_packet, refcnt); -+ -+ ptl_dbg_cond(p->ptl, "ptl: releasing packet %p\n", p); -+ p->ops->release(p); -+} -+ -+/** -+ * ssh_packet_get() - Increment reference count of packet. -+ * @packet: The packet to increment the reference count of. -+ * -+ * Increments the reference count of the given packet. See ssh_packet_put() -+ * for the counter-part of this function. -+ * -+ * Return: Returns the packet provided as input. -+ */ -+struct ssh_packet *ssh_packet_get(struct ssh_packet *packet) -+{ -+ if (packet) -+ kref_get(&packet->refcnt); -+ return packet; -+} -+EXPORT_SYMBOL_GPL(ssh_packet_get); -+ -+/** -+ * ssh_packet_put() - Decrement reference count of packet. -+ * @packet: The packet to decrement the reference count of. -+ * -+ * If the reference count reaches zero, the ``release`` callback specified in -+ * the packet's &struct ssh_packet_ops, i.e. ``packet->ops->release``, will be -+ * called. -+ * -+ * See ssh_packet_get() for the counter-part of this function. -+ */ -+void ssh_packet_put(struct ssh_packet *packet) -+{ -+ if (packet) -+ kref_put(&packet->refcnt, __ssh_ptl_packet_release); -+} -+EXPORT_SYMBOL_GPL(ssh_packet_put); -+ -+static u8 ssh_packet_get_seq(struct ssh_packet *packet) -+{ -+ return packet->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; -+} -+ -+/** -+ * ssh_packet_init() - Initialize SSH packet. -+ * @packet: The packet to initialize. -+ * @type: Type-flags of the packet. -+ * @priority: Priority of the packet. See SSH_PACKET_PRIORITY() for details. -+ * @ops: Packet operations. -+ * -+ * Initializes the given SSH packet. Sets the transmission buffer pointer to -+ * %NULL and the transmission buffer length to zero. For data-type packets, -+ * this buffer has to be set separately via ssh_packet_set_data() before -+ * submission, and must contain a valid SSH message, i.e. frame with optional -+ * payload of any type. -+ */ -+void ssh_packet_init(struct ssh_packet *packet, unsigned long type, -+ u8 priority, const struct ssh_packet_ops *ops) -+{ -+ kref_init(&packet->refcnt); -+ -+ packet->ptl = NULL; -+ INIT_LIST_HEAD(&packet->queue_node); -+ INIT_LIST_HEAD(&packet->pending_node); -+ -+ packet->state = type & SSH_PACKET_FLAGS_TY_MASK; -+ packet->priority = priority; -+ packet->timestamp = KTIME_MAX; -+ -+ packet->data.ptr = NULL; -+ packet->data.len = 0; -+ -+ packet->ops = ops; -+} -+ -+/** -+ * ssh_ctrl_packet_alloc() - Allocate control packet. -+ * @packet: Where the pointer to the newly allocated packet should be stored. -+ * @buffer: The buffer corresponding to this packet. -+ * @flags: Flags used for allocation. -+ * -+ * Allocates a packet and corresponding transport buffer. Sets the packet's -+ * buffer reference to the allocated buffer. The packet must be freed via -+ * ssh_ctrl_packet_free(), which will also free the corresponding buffer. The -+ * corresponding buffer must not be freed separately. Intended to be used with -+ * %ssh_ptl_ctrl_packet_ops as packet operations. -+ * -+ * Return: Returns zero on success, %-ENOMEM if the allocation failed. -+ */ -+static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, -+ struct ssam_span *buffer, gfp_t flags) -+{ -+ *packet = kzalloc(sizeof(**packet) + SSH_MSG_LEN_CTRL, flags); -+ if (!*packet) -+ return -ENOMEM; -+ -+ buffer->ptr = (u8 *)(*packet + 1); -+ buffer->len = SSH_MSG_LEN_CTRL; -+ -+ return 0; -+} -+ -+/** -+ * ssh_ctrl_packet_free() - Free control packet. -+ * @p: The packet to free. -+ */ -+static void ssh_ctrl_packet_free(struct ssh_packet *p) -+{ -+ kfree(p); -+} -+ -+static const struct ssh_packet_ops ssh_ptl_ctrl_packet_ops = { -+ .complete = NULL, -+ .release = ssh_ctrl_packet_free, -+}; -+ -+static void ssh_ptl_timeout_reaper_mod(struct ssh_ptl *ptl, ktime_t now, -+ ktime_t expires) -+{ -+ unsigned long delta = msecs_to_jiffies(ktime_ms_delta(expires, now)); -+ ktime_t aexp = ktime_add(expires, SSH_PTL_PACKET_TIMEOUT_RESOLUTION); -+ -+ spin_lock(&ptl->rtx_timeout.lock); -+ -+ /* Re-adjust / schedule reaper only if it is above resolution delta. */ -+ if (ktime_before(aexp, ptl->rtx_timeout.expires)) { -+ ptl->rtx_timeout.expires = expires; -+ mod_delayed_work(system_wq, &ptl->rtx_timeout.reaper, delta); -+ } -+ -+ spin_unlock(&ptl->rtx_timeout.lock); -+} -+ -+/* Must be called with queue lock held. */ -+static void ssh_packet_next_try(struct ssh_packet *p) -+{ -+ u8 base = ssh_packet_priority_get_base(p->priority); -+ u8 try = ssh_packet_priority_get_try(p->priority); -+ -+ lockdep_assert_held(&p->ptl->queue.lock); -+ -+ p->priority = __SSH_PACKET_PRIORITY(base, try + 1); -+} -+ -+/* Must be called with queue lock held. */ -+static struct list_head *__ssh_ptl_queue_find_entrypoint(struct ssh_packet *p) -+{ -+ struct list_head *head; -+ struct ssh_packet *q; -+ -+ lockdep_assert_held(&p->ptl->queue.lock); -+ -+ /* -+ * We generally assume that there are less control (ACK/NAK) packets -+ * and re-submitted data packets as there are normal data packets (at -+ * least in situations in which many packets are queued; if there -+ * aren't many packets queued the decision on how to iterate should be -+ * basically irrelevant; the number of control/data packets is more or -+ * less limited via the maximum number of pending packets). Thus, when -+ * inserting a control or re-submitted data packet, (determined by -+ * their priority), we search from front to back. Normal data packets -+ * are, usually queued directly at the tail of the queue, so for those -+ * search from back to front. -+ */ -+ -+ if (p->priority > SSH_PACKET_PRIORITY(DATA, 0)) { -+ list_for_each(head, &p->ptl->queue.head) { -+ q = list_entry(head, struct ssh_packet, queue_node); -+ -+ if (q->priority < p->priority) -+ break; -+ } -+ } else { -+ list_for_each_prev(head, &p->ptl->queue.head) { -+ q = list_entry(head, struct ssh_packet, queue_node); -+ -+ if (q->priority >= p->priority) { -+ head = head->next; -+ break; -+ } -+ } -+ } -+ -+ return head; -+} -+ -+/* Must be called with queue lock held. */ -+static int __ssh_ptl_queue_push(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ struct list_head *head; -+ -+ lockdep_assert_held(&ptl->queue.lock); -+ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) -+ return -ESHUTDOWN; -+ -+ /* Avoid further transitions when canceling/completing. */ -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)) -+ return -EINVAL; -+ -+ /* If this packet has already been queued, do not add it. */ -+ if (test_and_set_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state)) -+ return -EALREADY; -+ -+ head = __ssh_ptl_queue_find_entrypoint(packet); -+ -+ list_add_tail(&ssh_packet_get(packet)->queue_node, head); -+ return 0; -+} -+ -+static int ssh_ptl_queue_push(struct ssh_packet *packet) -+{ -+ int status; -+ -+ spin_lock(&packet->ptl->queue.lock); -+ status = __ssh_ptl_queue_push(packet); -+ spin_unlock(&packet->ptl->queue.lock); -+ -+ return status; -+} -+ -+static void ssh_ptl_queue_remove(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ spin_lock(&ptl->queue.lock); -+ -+ if (!test_and_clear_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state)) { -+ spin_unlock(&ptl->queue.lock); -+ return; -+ } -+ -+ list_del(&packet->queue_node); -+ -+ spin_unlock(&ptl->queue.lock); -+ ssh_packet_put(packet); -+} -+ -+static void ssh_ptl_pending_push(struct ssh_packet *p) -+{ -+ struct ssh_ptl *ptl = p->ptl; -+ const ktime_t timestamp = ktime_get_coarse_boottime(); -+ const ktime_t timeout = ptl->rtx_timeout.timeout; -+ -+ /* -+ * Note: We can get the time for the timestamp before acquiring the -+ * lock as this is the only place we're setting it and this function -+ * is called only from the transmitter thread. Thus it is not possible -+ * to overwrite the timestamp with an outdated value below. -+ */ -+ -+ spin_lock(&ptl->pending.lock); -+ -+ /* If we are canceling/completing this packet, do not add it. */ -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) { -+ spin_unlock(&ptl->pending.lock); -+ return; -+ } -+ -+ /* -+ * On re-submission, the packet has already been added the pending -+ * set. We still need to update the timestamp as the packet timeout is -+ * reset for each (re-)submission. -+ */ -+ p->timestamp = timestamp; -+ -+ /* In case it is already pending (e.g. re-submission), do not add it. */ -+ if (!test_and_set_bit(SSH_PACKET_SF_PENDING_BIT, &p->state)) { -+ atomic_inc(&ptl->pending.count); -+ list_add_tail(&ssh_packet_get(p)->pending_node, &ptl->pending.head); -+ } -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ /* Arm/update timeout reaper. */ -+ ssh_ptl_timeout_reaper_mod(ptl, timestamp, timestamp + timeout); -+} -+ -+static void ssh_ptl_pending_remove(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ spin_lock(&ptl->pending.lock); -+ -+ if (!test_and_clear_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state)) { -+ spin_unlock(&ptl->pending.lock); -+ return; -+ } -+ -+ list_del(&packet->pending_node); -+ atomic_dec(&ptl->pending.count); -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ ssh_packet_put(packet); -+} -+ -+/* Warning: Does not check/set "completed" bit. */ -+static void __ssh_ptl_complete(struct ssh_packet *p, int status) -+{ -+ struct ssh_ptl *ptl = READ_ONCE(p->ptl); -+ -+ ptl_dbg_cond(ptl, "ptl: completing packet %p (status: %d)\n", p, status); -+ -+ if (p->ops->complete) -+ p->ops->complete(p, status); -+} -+ -+static void ssh_ptl_remove_and_complete(struct ssh_packet *p, int status) -+{ -+ /* -+ * A call to this function should in general be preceded by -+ * set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->flags) to avoid re-adding the -+ * packet to the structures it's going to be removed from. -+ * -+ * The set_bit call does not need explicit memory barriers as the -+ * implicit barrier of the test_and_set_bit() call below ensure that the -+ * flag is visible before we actually attempt to remove the packet. -+ */ -+ -+ if (test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) -+ return; -+ -+ ssh_ptl_queue_remove(p); -+ ssh_ptl_pending_remove(p); -+ -+ __ssh_ptl_complete(p, status); -+} -+ -+static bool ssh_ptl_tx_can_process(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &packet->state)) -+ return !atomic_read(&ptl->pending.count); -+ -+ /* We can always process non-blocking packets. */ -+ if (!test_bit(SSH_PACKET_TY_BLOCKING_BIT, &packet->state)) -+ return true; -+ -+ /* If we are already waiting for this packet, send it again. */ -+ if (test_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state)) -+ return true; -+ -+ /* Otherwise: Check if we have the capacity to send. */ -+ return atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING; -+} -+ -+static struct ssh_packet *ssh_ptl_tx_pop(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *packet = ERR_PTR(-ENOENT); -+ struct ssh_packet *p, *n; -+ -+ spin_lock(&ptl->queue.lock); -+ list_for_each_entry_safe(p, n, &ptl->queue.head, queue_node) { -+ /* -+ * If we are canceling or completing this packet, ignore it. -+ * It's going to be removed from this queue shortly. -+ */ -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) -+ continue; -+ -+ /* -+ * Packets should be ordered non-blocking/to-be-resent first. -+ * If we cannot process this packet, assume that we can't -+ * process any following packet either and abort. -+ */ -+ if (!ssh_ptl_tx_can_process(p)) { -+ packet = ERR_PTR(-EBUSY); -+ break; -+ } -+ -+ /* -+ * We are allowed to change the state now. Remove it from the -+ * queue and mark it as being transmitted. -+ */ -+ -+ list_del(&p->queue_node); -+ -+ set_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &p->state); -+ /* Ensure that state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); -+ -+ /* -+ * Update number of tries. This directly influences the -+ * priority in case the packet is re-submitted (e.g. via -+ * timeout/NAK). Note that all reads and writes to the -+ * priority after the first submission are guarded by the -+ * queue lock. -+ */ -+ ssh_packet_next_try(p); -+ -+ packet = p; -+ break; -+ } -+ spin_unlock(&ptl->queue.lock); -+ -+ return packet; -+} -+ -+static struct ssh_packet *ssh_ptl_tx_next(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *p; -+ -+ p = ssh_ptl_tx_pop(ptl); -+ if (IS_ERR(p)) -+ return p; -+ -+ if (test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) { -+ ptl_dbg(ptl, "ptl: transmitting sequenced packet %p\n", p); -+ ssh_ptl_pending_push(p); -+ } else { -+ ptl_dbg(ptl, "ptl: transmitting non-sequenced packet %p\n", p); -+ } -+ -+ return p; -+} -+ -+static void ssh_ptl_tx_compl_success(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ ptl_dbg(ptl, "ptl: successfully transmitted packet %p\n", packet); -+ -+ /* Transition state to "transmitted". */ -+ set_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &packet->state); -+ /* Ensure that state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &packet->state); -+ -+ /* If the packet is unsequenced, we're done: Lock and complete. */ -+ if (!test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &packet->state)) { -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state); -+ ssh_ptl_remove_and_complete(packet, 0); -+ } -+ -+ /* -+ * Notify that a packet transmission has finished. In general we're only -+ * waiting for one packet (if any), so wake_up_all should be fine. -+ */ -+ wake_up_all(&ptl->tx.packet_wq); -+} -+ -+static void ssh_ptl_tx_compl_error(struct ssh_packet *packet, int status) -+{ -+ /* Transmission failure: Lock the packet and try to complete it. */ -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state); -+ /* Ensure that state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &packet->state); -+ -+ ptl_err(packet->ptl, "ptl: transmission error: %d\n", status); -+ ptl_dbg(packet->ptl, "ptl: failed to transmit packet: %p\n", packet); -+ -+ ssh_ptl_remove_and_complete(packet, status); -+ -+ /* -+ * Notify that a packet transmission has finished. In general we're only -+ * waiting for one packet (if any), so wake_up_all should be fine. -+ */ -+ wake_up_all(&packet->ptl->tx.packet_wq); -+} -+ -+static long ssh_ptl_tx_wait_packet(struct ssh_ptl *ptl) -+{ -+ int status; -+ -+ status = wait_for_completion_interruptible(&ptl->tx.thread_cplt_pkt); -+ reinit_completion(&ptl->tx.thread_cplt_pkt); -+ -+ /* -+ * Ensure completion is cleared before continuing to avoid lost update -+ * problems. -+ */ -+ smp_mb__after_atomic(); -+ -+ return status; -+} -+ -+static long ssh_ptl_tx_wait_transfer(struct ssh_ptl *ptl, long timeout) -+{ -+ long status; -+ -+ status = wait_for_completion_interruptible_timeout(&ptl->tx.thread_cplt_tx, -+ timeout); -+ reinit_completion(&ptl->tx.thread_cplt_tx); -+ -+ /* -+ * Ensure completion is cleared before continuing to avoid lost update -+ * problems. -+ */ -+ smp_mb__after_atomic(); -+ -+ return status; -+} -+ -+static int ssh_ptl_tx_packet(struct ssh_ptl *ptl, struct ssh_packet *packet) -+{ -+ long timeout = SSH_PTL_TX_TIMEOUT; -+ size_t offset = 0; -+ -+ /* Note: Flush-packets don't have any data. */ -+ if (unlikely(!packet->data.ptr)) -+ return 0; -+ -+ ptl_dbg(ptl, "tx: sending data (length: %zu)\n", packet->data.len); -+ print_hex_dump_debug("tx: ", DUMP_PREFIX_OFFSET, 16, 1, -+ packet->data.ptr, packet->data.len, false); -+ -+ do { -+ ssize_t status, len; -+ u8 *buf; -+ -+ buf = packet->data.ptr + offset; -+ len = packet->data.len - offset; -+ -+ status = serdev_device_write_buf(ptl->serdev, buf, len); -+ if (status < 0) -+ return status; -+ -+ if (status == len) -+ return 0; -+ -+ offset += status; -+ -+ timeout = ssh_ptl_tx_wait_transfer(ptl, timeout); -+ if (kthread_should_stop() || !atomic_read(&ptl->tx.running)) -+ return -ESHUTDOWN; -+ -+ if (timeout < 0) -+ return -EINTR; -+ -+ if (timeout == 0) -+ return -ETIMEDOUT; -+ } while (true); -+} -+ -+static int ssh_ptl_tx_threadfn(void *data) -+{ -+ struct ssh_ptl *ptl = data; -+ -+ while (!kthread_should_stop() && atomic_read(&ptl->tx.running)) { -+ struct ssh_packet *packet; -+ int status; -+ -+ /* Try to get the next packet. */ -+ packet = ssh_ptl_tx_next(ptl); -+ -+ /* If no packet can be processed, we are done. */ -+ if (IS_ERR(packet)) { -+ ssh_ptl_tx_wait_packet(ptl); -+ continue; -+ } -+ -+ /* Transfer and complete packet. */ -+ status = ssh_ptl_tx_packet(ptl, packet); -+ if (status) -+ ssh_ptl_tx_compl_error(packet, status); -+ else -+ ssh_ptl_tx_compl_success(packet); -+ -+ ssh_packet_put(packet); -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssh_ptl_tx_wakeup_packet() - Wake up packet transmitter thread for new -+ * packet. -+ * @ptl: The packet transport layer. -+ * -+ * Wakes up the packet transmitter thread, notifying it that a new packet has -+ * arrived and is ready for transfer. If the packet transport layer has been -+ * shut down, calls to this function will be ignored. -+ */ -+static void ssh_ptl_tx_wakeup_packet(struct ssh_ptl *ptl) -+{ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) -+ return; -+ -+ complete(&ptl->tx.thread_cplt_pkt); -+} -+ -+/** -+ * ssh_ptl_tx_start() - Start packet transmitter thread. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_ptl_tx_start(struct ssh_ptl *ptl) -+{ -+ atomic_set_release(&ptl->tx.running, 1); -+ -+ ptl->tx.thread = kthread_run(ssh_ptl_tx_threadfn, ptl, "ssam_serial_hub-tx"); -+ if (IS_ERR(ptl->tx.thread)) -+ return PTR_ERR(ptl->tx.thread); -+ -+ return 0; -+} -+ -+/** -+ * ssh_ptl_tx_stop() - Stop packet transmitter thread. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_ptl_tx_stop(struct ssh_ptl *ptl) -+{ -+ int status = 0; -+ -+ if (!IS_ERR_OR_NULL(ptl->tx.thread)) { -+ /* Tell thread to stop. */ -+ atomic_set_release(&ptl->tx.running, 0); -+ -+ /* -+ * Wake up thread in case it is paused. Do not use wakeup -+ * helpers as this may be called when the shutdown bit has -+ * already been set. -+ */ -+ complete(&ptl->tx.thread_cplt_pkt); -+ complete(&ptl->tx.thread_cplt_tx); -+ -+ /* Finally, wait for thread to stop. */ -+ status = kthread_stop(ptl->tx.thread); -+ ptl->tx.thread = NULL; -+ } -+ -+ return status; -+} -+ -+static struct ssh_packet *ssh_ptl_ack_pop(struct ssh_ptl *ptl, u8 seq_id) -+{ -+ struct ssh_packet *packet = ERR_PTR(-ENOENT); -+ struct ssh_packet *p, *n; -+ -+ spin_lock(&ptl->pending.lock); -+ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { -+ /* -+ * We generally expect packets to be in order, so first packet -+ * to be added to pending is first to be sent, is first to be -+ * ACKed. -+ */ -+ if (unlikely(ssh_packet_get_seq(p) != seq_id)) -+ continue; -+ -+ /* -+ * In case we receive an ACK while handling a transmission -+ * error completion. The packet will be removed shortly. -+ */ -+ if (unlikely(test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state))) { -+ packet = ERR_PTR(-EPERM); -+ break; -+ } -+ -+ /* -+ * Mark the packet as ACKed and remove it from pending by -+ * removing its node and decrementing the pending counter. -+ */ -+ set_bit(SSH_PACKET_SF_ACKED_BIT, &p->state); -+ /* Ensure that state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); -+ -+ atomic_dec(&ptl->pending.count); -+ list_del(&p->pending_node); -+ packet = p; -+ -+ break; -+ } -+ spin_unlock(&ptl->pending.lock); -+ -+ return packet; -+} -+ -+static void ssh_ptl_wait_until_transmitted(struct ssh_packet *packet) -+{ -+ wait_event(packet->ptl->tx.packet_wq, -+ test_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &packet->state) || -+ test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)); -+} -+ -+static void ssh_ptl_acknowledge(struct ssh_ptl *ptl, u8 seq) -+{ -+ struct ssh_packet *p; -+ -+ p = ssh_ptl_ack_pop(ptl, seq); -+ if (IS_ERR(p)) { -+ if (PTR_ERR(p) == -ENOENT) { -+ /* -+ * The packet has not been found in the set of pending -+ * packets. -+ */ -+ ptl_warn(ptl, "ptl: received ACK for non-pending packet\n"); -+ } else { -+ /* -+ * The packet is pending, but we are not allowed to take -+ * it because it has been locked. -+ */ -+ WARN_ON(PTR_ERR(p) != -EPERM); -+ } -+ return; -+ } -+ -+ ptl_dbg(ptl, "ptl: received ACK for packet %p\n", p); -+ -+ /* -+ * It is possible that the packet has been transmitted, but the state -+ * has not been updated from "transmitting" to "transmitted" yet. -+ * In that case, we need to wait for this transition to occur in order -+ * to determine between success or failure. -+ * -+ * On transmission failure, the packet will be locked after this call. -+ * On success, the transmitted bit will be set. -+ */ -+ ssh_ptl_wait_until_transmitted(p); -+ -+ /* -+ * The packet will already be locked in case of a transmission error or -+ * cancellation. Let the transmitter or cancellation issuer complete the -+ * packet. -+ */ -+ if (unlikely(test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state))) { -+ if (unlikely(!test_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &p->state))) -+ ptl_err(ptl, "ptl: received ACK before packet had been fully transmitted\n"); -+ -+ ssh_packet_put(p); -+ return; -+ } -+ -+ ssh_ptl_remove_and_complete(p, 0); -+ ssh_packet_put(p); -+ -+ if (atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING) -+ ssh_ptl_tx_wakeup_packet(ptl); -+} -+ -+/** -+ * ssh_ptl_submit() - Submit a packet to the transport layer. -+ * @ptl: The packet transport layer to submit the packet to. -+ * @p: The packet to submit. -+ * -+ * Submits a new packet to the transport layer, queuing it to be sent. This -+ * function should not be used for re-submission. -+ * -+ * Return: Returns zero on success, %-EINVAL if a packet field is invalid or -+ * the packet has been canceled prior to submission, %-EALREADY if the packet -+ * has already been submitted, or %-ESHUTDOWN if the packet transport layer -+ * has been shut down. -+ */ -+int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p) -+{ -+ struct ssh_ptl *ptl_old; -+ int status; -+ -+ /* Validate packet fields. */ -+ if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &p->state)) { -+ if (p->data.ptr || test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) -+ return -EINVAL; -+ } else if (!p->data.ptr) { -+ return -EINVAL; -+ } -+ -+ /* -+ * The ptl reference only gets set on or before the first submission. -+ * After the first submission, it has to be read-only. -+ * -+ * Note that ptl may already be set from upper-layer request -+ * submission, thus we cannot expect it to be NULL. -+ */ -+ ptl_old = READ_ONCE(p->ptl); -+ if (!ptl_old) -+ WRITE_ONCE(p->ptl, ptl); -+ else if (WARN_ON(ptl_old != ptl)) -+ return -EALREADY; /* Submitted on different PTL. */ -+ -+ status = ssh_ptl_queue_push(p); -+ if (status) -+ return status; -+ -+ if (!test_bit(SSH_PACKET_TY_BLOCKING_BIT, &p->state) || -+ (atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING)) -+ ssh_ptl_tx_wakeup_packet(ptl); -+ -+ return 0; -+} -+ -+/* -+ * __ssh_ptl_resubmit() - Re-submit a packet to the transport layer. -+ * @packet: The packet to re-submit. -+ * -+ * Re-submits the given packet: Checks if it can be re-submitted and queues it -+ * if it can, resetting the packet timestamp in the process. Must be called -+ * with the pending lock held. -+ * -+ * Return: Returns %-ECANCELED if the packet has exceeded its number of tries, -+ * %-EINVAL if the packet has been locked, %-EALREADY if the packet is already -+ * on the queue, and %-ESHUTDOWN if the transmission layer has been shut down. -+ */ -+static int __ssh_ptl_resubmit(struct ssh_packet *packet) -+{ -+ int status; -+ u8 try; -+ -+ lockdep_assert_held(&packet->ptl->pending.lock); -+ -+ spin_lock(&packet->ptl->queue.lock); -+ -+ /* Check if the packet is out of tries. */ -+ try = ssh_packet_priority_get_try(packet->priority); -+ if (try >= SSH_PTL_MAX_PACKET_TRIES) { -+ spin_unlock(&packet->ptl->queue.lock); -+ return -ECANCELED; -+ } -+ -+ status = __ssh_ptl_queue_push(packet); -+ if (status) { -+ /* -+ * An error here indicates that the packet has either already -+ * been queued, been locked, or the transport layer is being -+ * shut down. In all cases: Ignore the error. -+ */ -+ spin_unlock(&packet->ptl->queue.lock); -+ return status; -+ } -+ -+ packet->timestamp = KTIME_MAX; -+ -+ spin_unlock(&packet->ptl->queue.lock); -+ return 0; -+} -+ -+static void ssh_ptl_resubmit_pending(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *p; -+ bool resub = false; -+ -+ /* -+ * Note: We deliberately do not remove/attempt to cancel and complete -+ * packets that are out of tires in this function. The packet will be -+ * eventually canceled and completed by the timeout. Removing the packet -+ * here could lead to overly eager cancellation if the packet has not -+ * been re-transmitted yet but the tries-counter already updated (i.e -+ * ssh_ptl_tx_next() removed the packet from the queue and updated the -+ * counter, but re-transmission for the last try has not actually -+ * started yet). -+ */ -+ -+ spin_lock(&ptl->pending.lock); -+ -+ /* Re-queue all pending packets. */ -+ list_for_each_entry(p, &ptl->pending.head, pending_node) { -+ /* -+ * Re-submission fails if the packet is out of tries, has been -+ * locked, is already queued, or the layer is being shut down. -+ * No need to re-schedule tx-thread in those cases. -+ */ -+ if (!__ssh_ptl_resubmit(p)) -+ resub = true; -+ } -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ if (resub) -+ ssh_ptl_tx_wakeup_packet(ptl); -+} -+ -+/** -+ * ssh_ptl_cancel() - Cancel a packet. -+ * @p: The packet to cancel. -+ * -+ * Cancels a packet. There are no guarantees on when completion and release -+ * callbacks will be called. This may occur during execution of this function -+ * or may occur at any point later. -+ * -+ * Note that it is not guaranteed that the packet will actually be canceled if -+ * the packet is concurrently completed by another process. The only guarantee -+ * of this function is that the packet will be completed (with success, -+ * failure, or cancellation) and released from the transport layer in a -+ * reasonable time-frame. -+ * -+ * May be called before the packet has been submitted, in which case any later -+ * packet submission fails. -+ */ -+void ssh_ptl_cancel(struct ssh_packet *p) -+{ -+ if (test_and_set_bit(SSH_PACKET_SF_CANCELED_BIT, &p->state)) -+ return; -+ -+ /* -+ * Lock packet and commit with memory barrier. If this packet has -+ * already been locked, it's going to be removed and completed by -+ * another party, which should have precedence. -+ */ -+ if (test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) -+ return; -+ -+ /* -+ * By marking the packet as locked and employing the implicit memory -+ * barrier of test_and_set_bit, we have guaranteed that, at this point, -+ * the packet cannot be added to the queue any more. -+ * -+ * In case the packet has never been submitted, packet->ptl is NULL. If -+ * the packet is currently being submitted, packet->ptl may be NULL or -+ * non-NULL. Due marking the packet as locked above and committing with -+ * the memory barrier, we have guaranteed that, if packet->ptl is NULL, -+ * the packet will never be added to the queue. If packet->ptl is -+ * non-NULL, we don't have any guarantees. -+ */ -+ -+ if (READ_ONCE(p->ptl)) { -+ ssh_ptl_remove_and_complete(p, -ECANCELED); -+ -+ if (atomic_read(&p->ptl->pending.count) < SSH_PTL_MAX_PENDING) -+ ssh_ptl_tx_wakeup_packet(p->ptl); -+ -+ } else if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) { -+ __ssh_ptl_complete(p, -ECANCELED); -+ } -+} -+ -+/* Must be called with pending lock held */ -+static ktime_t ssh_packet_get_expiration(struct ssh_packet *p, ktime_t timeout) -+{ -+ lockdep_assert_held(&p->ptl->pending.lock); -+ -+ if (p->timestamp != KTIME_MAX) -+ return ktime_add(p->timestamp, timeout); -+ else -+ return KTIME_MAX; -+} -+ -+static void ssh_ptl_timeout_reap(struct work_struct *work) -+{ -+ struct ssh_ptl *ptl = to_ssh_ptl(work, rtx_timeout.reaper.work); -+ struct ssh_packet *p, *n; -+ LIST_HEAD(claimed); -+ ktime_t now = ktime_get_coarse_boottime(); -+ ktime_t timeout = ptl->rtx_timeout.timeout; -+ ktime_t next = KTIME_MAX; -+ bool resub = false; -+ int status; -+ -+ /* -+ * Mark reaper as "not pending". This is done before checking any -+ * packets to avoid lost-update type problems. -+ */ -+ spin_lock(&ptl->rtx_timeout.lock); -+ ptl->rtx_timeout.expires = KTIME_MAX; -+ spin_unlock(&ptl->rtx_timeout.lock); -+ -+ spin_lock(&ptl->pending.lock); -+ -+ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { -+ ktime_t expires = ssh_packet_get_expiration(p, timeout); -+ -+ /* -+ * Check if the timeout hasn't expired yet. Find out next -+ * expiration date to be handled after this run. -+ */ -+ if (ktime_after(expires, now)) { -+ next = ktime_before(expires, next) ? expires : next; -+ continue; -+ } -+ -+ status = __ssh_ptl_resubmit(p); -+ -+ /* -+ * Re-submission fails if the packet is out of tries, has been -+ * locked, is already queued, or the layer is being shut down. -+ * No need to re-schedule tx-thread in those cases. -+ */ -+ if (!status) -+ resub = true; -+ -+ /* Go to next packet if this packet is not out of tries. */ -+ if (status != -ECANCELED) -+ continue; -+ -+ /* No more tries left: Cancel the packet. */ -+ -+ /* -+ * If someone else has locked the packet already, don't use it -+ * and let the other party complete it. -+ */ -+ if (test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) -+ continue; -+ -+ /* -+ * We have now marked the packet as locked. Thus it cannot be -+ * added to the pending list again after we've removed it here. -+ * We can therefore re-use the pending_node of this packet -+ * temporarily. -+ */ -+ -+ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); -+ -+ atomic_dec(&ptl->pending.count); -+ list_del(&p->pending_node); -+ -+ list_add_tail(&p->pending_node, &claimed); -+ } -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ /* Cancel and complete the packet. */ -+ list_for_each_entry_safe(p, n, &claimed, pending_node) { -+ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) { -+ ssh_ptl_queue_remove(p); -+ __ssh_ptl_complete(p, -ETIMEDOUT); -+ } -+ -+ /* -+ * Drop the reference we've obtained by removing it from -+ * the pending set. -+ */ -+ list_del(&p->pending_node); -+ ssh_packet_put(p); -+ } -+ -+ /* Ensure that reaper doesn't run again immediately. */ -+ next = max(next, ktime_add(now, SSH_PTL_PACKET_TIMEOUT_RESOLUTION)); -+ if (next != KTIME_MAX) -+ ssh_ptl_timeout_reaper_mod(ptl, now, next); -+ -+ if (resub) -+ ssh_ptl_tx_wakeup_packet(ptl); -+} -+ -+static bool ssh_ptl_rx_retransmit_check(struct ssh_ptl *ptl, u8 seq) -+{ -+ int i; -+ -+ /* -+ * Check if SEQ has been seen recently (i.e. packet was -+ * re-transmitted and we should ignore it). -+ */ -+ for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) { -+ if (likely(ptl->rx.blocked.seqs[i] != seq)) -+ continue; -+ -+ ptl_dbg(ptl, "ptl: ignoring repeated data packet\n"); -+ return true; -+ } -+ -+ /* Update list of blocked sequence IDs. */ -+ ptl->rx.blocked.seqs[ptl->rx.blocked.offset] = seq; -+ ptl->rx.blocked.offset = (ptl->rx.blocked.offset + 1) -+ % ARRAY_SIZE(ptl->rx.blocked.seqs); -+ -+ return false; -+} -+ -+static void ssh_ptl_rx_dataframe(struct ssh_ptl *ptl, -+ const struct ssh_frame *frame, -+ const struct ssam_span *payload) -+{ -+ if (ssh_ptl_rx_retransmit_check(ptl, frame->seq)) -+ return; -+ -+ ptl->ops.data_received(ptl, payload); -+} -+ -+static void ssh_ptl_send_ack(struct ssh_ptl *ptl, u8 seq) -+{ -+ struct ssh_packet *packet; -+ struct ssam_span buf; -+ struct msgbuf msgb; -+ int status; -+ -+ status = ssh_ctrl_packet_alloc(&packet, &buf, GFP_KERNEL); -+ if (status) { -+ ptl_err(ptl, "ptl: failed to allocate ACK packet\n"); -+ return; -+ } -+ -+ ssh_packet_init(packet, 0, SSH_PACKET_PRIORITY(ACK, 0), -+ &ssh_ptl_ctrl_packet_ops); -+ -+ msgb_init(&msgb, buf.ptr, buf.len); -+ msgb_push_ack(&msgb, seq); -+ ssh_packet_set_data(packet, msgb.begin, msgb_bytes_used(&msgb)); -+ -+ ssh_ptl_submit(ptl, packet); -+ ssh_packet_put(packet); -+} -+ -+static void ssh_ptl_send_nak(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *packet; -+ struct ssam_span buf; -+ struct msgbuf msgb; -+ int status; -+ -+ status = ssh_ctrl_packet_alloc(&packet, &buf, GFP_KERNEL); -+ if (status) { -+ ptl_err(ptl, "ptl: failed to allocate NAK packet\n"); -+ return; -+ } -+ -+ ssh_packet_init(packet, 0, SSH_PACKET_PRIORITY(NAK, 0), -+ &ssh_ptl_ctrl_packet_ops); -+ -+ msgb_init(&msgb, buf.ptr, buf.len); -+ msgb_push_nak(&msgb); -+ ssh_packet_set_data(packet, msgb.begin, msgb_bytes_used(&msgb)); -+ -+ ssh_ptl_submit(ptl, packet); -+ ssh_packet_put(packet); -+} -+ -+static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) -+{ -+ struct ssh_frame *frame; -+ struct ssam_span payload; -+ struct ssam_span aligned; -+ bool syn_found; -+ int status; -+ -+ /* Find SYN. */ -+ syn_found = sshp_find_syn(source, &aligned); -+ -+ if (unlikely(aligned.ptr - source->ptr) > 0) { -+ ptl_warn(ptl, "rx: parser: invalid start of frame, skipping\n"); -+ -+ /* -+ * Notes: -+ * - This might send multiple NAKs in case the communication -+ * starts with an invalid SYN and is broken down into multiple -+ * pieces. This should generally be handled fine, we just -+ * might receive duplicate data in this case, which is -+ * detected when handling data frames. -+ * - This path will also be executed on invalid CRCs: When an -+ * invalid CRC is encountered, the code below will skip data -+ * until directly after the SYN. This causes the search for -+ * the next SYN, which is generally not placed directly after -+ * the last one. -+ * -+ * Open question: Should we send this in case of invalid -+ * payload CRCs if the frame-type is non-sequential (current -+ * implementation) or should we drop that frame without -+ * telling the EC? -+ */ -+ ssh_ptl_send_nak(ptl); -+ } -+ -+ if (unlikely(!syn_found)) -+ return aligned.ptr - source->ptr; -+ -+ /* Parse and validate frame. */ -+ status = sshp_parse_frame(&ptl->serdev->dev, &aligned, &frame, &payload, -+ SSH_PTL_RX_BUF_LEN); -+ if (status) /* Invalid frame: skip to next SYN. */ -+ return aligned.ptr - source->ptr + sizeof(u16); -+ if (!frame) /* Not enough data. */ -+ return aligned.ptr - source->ptr; -+ -+ switch (frame->type) { -+ case SSH_FRAME_TYPE_ACK: -+ ssh_ptl_acknowledge(ptl, frame->seq); -+ break; -+ -+ case SSH_FRAME_TYPE_NAK: -+ ssh_ptl_resubmit_pending(ptl); -+ break; -+ -+ case SSH_FRAME_TYPE_DATA_SEQ: -+ ssh_ptl_send_ack(ptl, frame->seq); -+ fallthrough; -+ -+ case SSH_FRAME_TYPE_DATA_NSQ: -+ ssh_ptl_rx_dataframe(ptl, frame, &payload); -+ break; -+ -+ default: -+ ptl_warn(ptl, "ptl: received frame with unknown type %#04x\n", -+ frame->type); -+ break; -+ } -+ -+ return aligned.ptr - source->ptr + SSH_MESSAGE_LENGTH(frame->len); -+} -+ -+static int ssh_ptl_rx_threadfn(void *data) -+{ -+ struct ssh_ptl *ptl = data; -+ -+ while (true) { -+ struct ssam_span span; -+ size_t offs = 0; -+ size_t n; -+ -+ wait_event_interruptible(ptl->rx.wq, -+ !kfifo_is_empty(&ptl->rx.fifo) || -+ kthread_should_stop()); -+ if (kthread_should_stop()) -+ break; -+ -+ /* Copy from fifo to evaluation buffer. */ -+ n = sshp_buf_read_from_fifo(&ptl->rx.buf, &ptl->rx.fifo); -+ -+ ptl_dbg(ptl, "rx: received data (size: %zu)\n", n); -+ print_hex_dump_debug("rx: ", DUMP_PREFIX_OFFSET, 16, 1, -+ ptl->rx.buf.ptr + ptl->rx.buf.len - n, -+ n, false); -+ -+ /* Parse until we need more bytes or buffer is empty. */ -+ while (offs < ptl->rx.buf.len) { -+ sshp_buf_span_from(&ptl->rx.buf, offs, &span); -+ n = ssh_ptl_rx_eval(ptl, &span); -+ if (n == 0) -+ break; /* Need more bytes. */ -+ -+ offs += n; -+ } -+ -+ /* Throw away the evaluated parts. */ -+ sshp_buf_drop(&ptl->rx.buf, offs); -+ } -+ -+ return 0; -+} -+ -+static void ssh_ptl_rx_wakeup(struct ssh_ptl *ptl) -+{ -+ wake_up(&ptl->rx.wq); -+} -+ -+/** -+ * ssh_ptl_rx_start() - Start packet transport layer receiver thread. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_ptl_rx_start(struct ssh_ptl *ptl) -+{ -+ if (ptl->rx.thread) -+ return 0; -+ -+ ptl->rx.thread = kthread_run(ssh_ptl_rx_threadfn, ptl, -+ "ssam_serial_hub-rx"); -+ if (IS_ERR(ptl->rx.thread)) -+ return PTR_ERR(ptl->rx.thread); -+ -+ return 0; -+} -+ -+/** -+ * ssh_ptl_rx_stop() - Stop packet transport layer receiver thread. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_ptl_rx_stop(struct ssh_ptl *ptl) -+{ -+ int status = 0; -+ -+ if (ptl->rx.thread) { -+ status = kthread_stop(ptl->rx.thread); -+ ptl->rx.thread = NULL; -+ } -+ -+ return status; -+} -+ -+/** -+ * ssh_ptl_rx_rcvbuf() - Push data from lower-layer transport to the packet -+ * layer. -+ * @ptl: The packet transport layer. -+ * @buf: Pointer to the data to push to the layer. -+ * @n: Size of the data to push to the layer, in bytes. -+ * -+ * Pushes data from a lower-layer transport to the receiver fifo buffer of the -+ * packet layer and notifies the receiver thread. Calls to this function are -+ * ignored once the packet layer has been shut down. -+ * -+ * Return: Returns the number of bytes transferred (positive or zero) on -+ * success. Returns %-ESHUTDOWN if the packet layer has been shut down. -+ */ -+int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n) -+{ -+ int used; -+ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) -+ return -ESHUTDOWN; -+ -+ used = kfifo_in(&ptl->rx.fifo, buf, n); -+ if (used) -+ ssh_ptl_rx_wakeup(ptl); -+ -+ return used; -+} -+ -+/** -+ * ssh_ptl_shutdown() - Shut down the packet transport layer. -+ * @ptl: The packet transport layer. -+ * -+ * Shuts down the packet transport layer, removing and canceling all queued -+ * and pending packets. Packets canceled by this operation will be completed -+ * with %-ESHUTDOWN as status. Receiver and transmitter threads will be -+ * stopped. -+ * -+ * As a result of this function, the transport layer will be marked as shut -+ * down. Submission of packets after the transport layer has been shut down -+ * will fail with %-ESHUTDOWN. -+ */ -+void ssh_ptl_shutdown(struct ssh_ptl *ptl) -+{ -+ LIST_HEAD(complete_q); -+ LIST_HEAD(complete_p); -+ struct ssh_packet *p, *n; -+ int status; -+ -+ /* Ensure that no new packets (including ACK/NAK) can be submitted. */ -+ set_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state); -+ /* -+ * Ensure that the layer gets marked as shut-down before actually -+ * stopping it. In combination with the check in ssh_ptl_queue_push(), -+ * this guarantees that no new packets can be added and all already -+ * queued packets are properly canceled. In combination with the check -+ * in ssh_ptl_rx_rcvbuf(), this guarantees that received data is -+ * properly cut off. -+ */ -+ smp_mb__after_atomic(); -+ -+ status = ssh_ptl_rx_stop(ptl); -+ if (status) -+ ptl_err(ptl, "ptl: failed to stop receiver thread\n"); -+ -+ status = ssh_ptl_tx_stop(ptl); -+ if (status) -+ ptl_err(ptl, "ptl: failed to stop transmitter thread\n"); -+ -+ cancel_delayed_work_sync(&ptl->rtx_timeout.reaper); -+ -+ /* -+ * At this point, all threads have been stopped. This means that the -+ * only references to packets from inside the system are in the queue -+ * and pending set. -+ * -+ * Note: We still need locks here because someone could still be -+ * canceling packets. -+ * -+ * Note 2: We can re-use queue_node (or pending_node) if we mark the -+ * packet as locked an then remove it from the queue (or pending set -+ * respectively). Marking the packet as locked avoids re-queuing -+ * (which should already be prevented by having stopped the treads...) -+ * and not setting QUEUED_BIT (or PENDING_BIT) prevents removal from a -+ * new list via other threads (e.g. cancellation). -+ * -+ * Note 3: There may be overlap between complete_p and complete_q. -+ * This is handled via test_and_set_bit() on the "completed" flag -+ * (also handles cancellation). -+ */ -+ -+ /* Mark queued packets as locked and move them to complete_q. */ -+ spin_lock(&ptl->queue.lock); -+ list_for_each_entry_safe(p, n, &ptl->queue.head, queue_node) { -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state); -+ /* Ensure that state does not get zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); -+ -+ list_del(&p->queue_node); -+ list_add_tail(&p->queue_node, &complete_q); -+ } -+ spin_unlock(&ptl->queue.lock); -+ -+ /* Mark pending packets as locked and move them to complete_p. */ -+ spin_lock(&ptl->pending.lock); -+ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state); -+ /* Ensure that state does not get zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); -+ -+ list_del(&p->pending_node); -+ list_add_tail(&p->pending_node, &complete_q); -+ } -+ atomic_set(&ptl->pending.count, 0); -+ spin_unlock(&ptl->pending.lock); -+ -+ /* Complete and drop packets on complete_q. */ -+ list_for_each_entry(p, &complete_q, queue_node) { -+ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) -+ __ssh_ptl_complete(p, -ESHUTDOWN); -+ -+ ssh_packet_put(p); -+ } -+ -+ /* Complete and drop packets on complete_p. */ -+ list_for_each_entry(p, &complete_p, pending_node) { -+ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) -+ __ssh_ptl_complete(p, -ESHUTDOWN); -+ -+ ssh_packet_put(p); -+ } -+ -+ /* -+ * At this point we have guaranteed that the system doesn't reference -+ * any packets any more. -+ */ -+} -+ -+/** -+ * ssh_ptl_init() - Initialize packet transport layer. -+ * @ptl: The packet transport layer to initialize. -+ * @serdev: The underlying serial device, i.e. the lower-level transport. -+ * @ops: Packet layer operations. -+ * -+ * Initializes the given packet transport layer. Transmitter and receiver -+ * threads must be started separately via ssh_ptl_tx_start() and -+ * ssh_ptl_rx_start(), after the packet-layer has been initialized and the -+ * lower-level transport layer has been set up. -+ * -+ * Return: Returns zero on success and a nonzero error code on failure. -+ */ -+int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev, -+ struct ssh_ptl_ops *ops) -+{ -+ int i, status; -+ -+ ptl->serdev = serdev; -+ ptl->state = 0; -+ -+ spin_lock_init(&ptl->queue.lock); -+ INIT_LIST_HEAD(&ptl->queue.head); -+ -+ spin_lock_init(&ptl->pending.lock); -+ INIT_LIST_HEAD(&ptl->pending.head); -+ atomic_set_release(&ptl->pending.count, 0); -+ -+ ptl->tx.thread = NULL; -+ atomic_set(&ptl->tx.running, 0); -+ init_completion(&ptl->tx.thread_cplt_pkt); -+ init_completion(&ptl->tx.thread_cplt_tx); -+ init_waitqueue_head(&ptl->tx.packet_wq); -+ -+ ptl->rx.thread = NULL; -+ init_waitqueue_head(&ptl->rx.wq); -+ -+ spin_lock_init(&ptl->rtx_timeout.lock); -+ ptl->rtx_timeout.timeout = SSH_PTL_PACKET_TIMEOUT; -+ ptl->rtx_timeout.expires = KTIME_MAX; -+ INIT_DELAYED_WORK(&ptl->rtx_timeout.reaper, ssh_ptl_timeout_reap); -+ -+ ptl->ops = *ops; -+ -+ /* Initialize list of recent/blocked SEQs with invalid sequence IDs. */ -+ for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) -+ ptl->rx.blocked.seqs[i] = U16_MAX; -+ ptl->rx.blocked.offset = 0; -+ -+ status = kfifo_alloc(&ptl->rx.fifo, SSH_PTL_RX_FIFO_LEN, GFP_KERNEL); -+ if (status) -+ return status; -+ -+ status = sshp_buf_alloc(&ptl->rx.buf, SSH_PTL_RX_BUF_LEN, GFP_KERNEL); -+ if (status) -+ kfifo_free(&ptl->rx.fifo); -+ -+ return status; -+} -+ -+/** -+ * ssh_ptl_destroy() - Deinitialize packet transport layer. -+ * @ptl: The packet transport layer to deinitialize. -+ * -+ * Deinitializes the given packet transport layer and frees resources -+ * associated with it. If receiver and/or transmitter threads have been -+ * started, the layer must first be shut down via ssh_ptl_shutdown() before -+ * this function can be called. -+ */ -+void ssh_ptl_destroy(struct ssh_ptl *ptl) -+{ -+ kfifo_free(&ptl->rx.fifo); -+ sshp_buf_free(&ptl->rx.buf); -+} -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h -new file mode 100644 -index 000000000000..058f111292ca ---- /dev/null -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h -@@ -0,0 +1,187 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * SSH packet transport layer. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H -+#define _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include "ssh_parser.h" -+ -+/** -+ * enum ssh_ptl_state_flags - State-flags for &struct ssh_ptl. -+ * -+ * @SSH_PTL_SF_SHUTDOWN_BIT: -+ * Indicates that the packet transport layer has been shut down or is -+ * being shut down and should not accept any new packets/data. -+ */ -+enum ssh_ptl_state_flags { -+ SSH_PTL_SF_SHUTDOWN_BIT, -+}; -+ -+/** -+ * struct ssh_ptl_ops - Callback operations for packet transport layer. -+ * @data_received: Function called when a data-packet has been received. Both, -+ * the packet layer on which the packet has been received and -+ * the packet's payload data are provided to this function. -+ */ -+struct ssh_ptl_ops { -+ void (*data_received)(struct ssh_ptl *p, const struct ssam_span *data); -+}; -+ -+/** -+ * struct ssh_ptl - SSH packet transport layer. -+ * @serdev: Serial device providing the underlying data transport. -+ * @state: State(-flags) of the transport layer. -+ * @queue: Packet submission queue. -+ * @queue.lock: Lock for modifying the packet submission queue. -+ * @queue.head: List-head of the packet submission queue. -+ * @pending: Set/list of pending packets. -+ * @pending.lock: Lock for modifying the pending set. -+ * @pending.head: List-head of the pending set/list. -+ * @pending.count: Number of currently pending packets. -+ * @tx: Transmitter subsystem. -+ * @tx.running: Flag indicating (desired) transmitter thread state. -+ * @tx.thread: Transmitter thread. -+ * @tx.thread_cplt_tx: Completion for transmitter thread waiting on transfer. -+ * @tx.thread_cplt_pkt: Completion for transmitter thread waiting on packets. -+ * @tx.packet_wq: Waitqueue-head for packet transmit completion. -+ * @rx: Receiver subsystem. -+ * @rx.thread: Receiver thread. -+ * @rx.wq: Waitqueue-head for receiver thread. -+ * @rx.fifo: Buffer for receiving data/pushing data to receiver thread. -+ * @rx.buf: Buffer for evaluating data on receiver thread. -+ * @rx.blocked: List of recent/blocked sequence IDs to detect retransmission. -+ * @rx.blocked.seqs: Array of blocked sequence IDs. -+ * @rx.blocked.offset: Offset indicating where a new ID should be inserted. -+ * @rtx_timeout: Retransmission timeout subsystem. -+ * @rtx_timeout.lock: Lock for modifying the retransmission timeout reaper. -+ * @rtx_timeout.timeout: Timeout interval for retransmission. -+ * @rtx_timeout.expires: Time specifying when the reaper work is next scheduled. -+ * @rtx_timeout.reaper: Work performing timeout checks and subsequent actions. -+ * @ops: Packet layer operations. -+ */ -+struct ssh_ptl { -+ struct serdev_device *serdev; -+ unsigned long state; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ } queue; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ atomic_t count; -+ } pending; -+ -+ struct { -+ atomic_t running; -+ struct task_struct *thread; -+ struct completion thread_cplt_tx; -+ struct completion thread_cplt_pkt; -+ struct wait_queue_head packet_wq; -+ } tx; -+ -+ struct { -+ struct task_struct *thread; -+ struct wait_queue_head wq; -+ struct kfifo fifo; -+ struct sshp_buf buf; -+ -+ struct { -+ u16 seqs[8]; -+ u16 offset; -+ } blocked; -+ } rx; -+ -+ struct { -+ spinlock_t lock; -+ ktime_t timeout; -+ ktime_t expires; -+ struct delayed_work reaper; -+ } rtx_timeout; -+ -+ struct ssh_ptl_ops ops; -+}; -+ -+#define __ssam_prcond(func, p, fmt, ...) \ -+ do { \ -+ typeof(p) __p = (p); \ -+ \ -+ if (__p) \ -+ func(__p, fmt, ##__VA_ARGS__); \ -+ } while (0) -+ -+#define ptl_dbg(p, fmt, ...) dev_dbg(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_info(p, fmt, ...) dev_info(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_warn(p, fmt, ...) dev_warn(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_err(p, fmt, ...) dev_err(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_dbg_cond(p, fmt, ...) __ssam_prcond(ptl_dbg, p, fmt, ##__VA_ARGS__) -+ -+#define to_ssh_ptl(ptr, member) \ -+ container_of(ptr, struct ssh_ptl, member) -+ -+int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev, -+ struct ssh_ptl_ops *ops); -+ -+void ssh_ptl_destroy(struct ssh_ptl *ptl); -+ -+/** -+ * ssh_ptl_get_device() - Get device associated with packet transport layer. -+ * @ptl: The packet transport layer. -+ * -+ * Return: Returns the device on which the given packet transport layer builds -+ * upon. -+ */ -+static inline struct device *ssh_ptl_get_device(struct ssh_ptl *ptl) -+{ -+ return ptl->serdev ? &ptl->serdev->dev : NULL; -+} -+ -+int ssh_ptl_tx_start(struct ssh_ptl *ptl); -+int ssh_ptl_tx_stop(struct ssh_ptl *ptl); -+int ssh_ptl_rx_start(struct ssh_ptl *ptl); -+int ssh_ptl_rx_stop(struct ssh_ptl *ptl); -+void ssh_ptl_shutdown(struct ssh_ptl *ptl); -+ -+int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p); -+void ssh_ptl_cancel(struct ssh_packet *p); -+ -+int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n); -+ -+/** -+ * ssh_ptl_tx_wakeup_transfer() - Wake up packet transmitter thread for -+ * transfer. -+ * @ptl: The packet transport layer. -+ * -+ * Wakes up the packet transmitter thread, notifying it that the underlying -+ * transport has more space for data to be transmitted. If the packet -+ * transport layer has been shut down, calls to this function will be ignored. -+ */ -+static inline void ssh_ptl_tx_wakeup_transfer(struct ssh_ptl *ptl) -+{ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) -+ return; -+ -+ complete(&ptl->tx.thread_cplt_tx); -+} -+ -+void ssh_packet_init(struct ssh_packet *packet, unsigned long type, -+ u8 priority, const struct ssh_packet_ops *ops); -+ -+#endif /* _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H */ -diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c -new file mode 100644 -index 000000000000..e2dead8de94a ---- /dev/null -+++ b/drivers/platform/surface/aggregator/ssh_parser.c -@@ -0,0 +1,228 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * SSH message parser. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+ -+#include -+#include "ssh_parser.h" -+ -+/** -+ * sshp_validate_crc() - Validate a CRC in raw message data. -+ * @src: The span of data over which the CRC should be computed. -+ * @crc: The pointer to the expected u16 CRC value. -+ * -+ * Computes the CRC of the provided data span (@src), compares it to the CRC -+ * stored at the given address (@crc), and returns the result of this -+ * comparison, i.e. %true if equal. This function is intended to run on raw -+ * input/message data. -+ * -+ * Return: Returns %true if the computed CRC matches the stored CRC, %false -+ * otherwise. -+ */ -+static bool sshp_validate_crc(const struct ssam_span *src, const u8 *crc) -+{ -+ u16 actual = ssh_crc(src->ptr, src->len); -+ u16 expected = get_unaligned_le16(crc); -+ -+ return actual == expected; -+} -+ -+/** -+ * sshp_starts_with_syn() - Check if the given data starts with SSH SYN bytes. -+ * @src: The data span to check the start of. -+ */ -+static bool sshp_starts_with_syn(const struct ssam_span *src) -+{ -+ return src->len >= 2 && get_unaligned_le16(src->ptr) == SSH_MSG_SYN; -+} -+ -+/** -+ * sshp_find_syn() - Find SSH SYN bytes in the given data span. -+ * @src: The data span to search in. -+ * @rem: The span (output) indicating the remaining data, starting with SSH -+ * SYN bytes, if found. -+ * -+ * Search for SSH SYN bytes in the given source span. If found, set the @rem -+ * span to the remaining data, starting with the first SYN bytes and capped by -+ * the source span length, and return %true. This function does not copy any -+ * data, but rather only sets pointers to the respective start addresses and -+ * length values. -+ * -+ * If no SSH SYN bytes could be found, set the @rem span to the zero-length -+ * span at the end of the source span and return %false. -+ * -+ * If partial SSH SYN bytes could be found at the end of the source span, set -+ * the @rem span to cover these partial SYN bytes, capped by the end of the -+ * source span, and return %false. This function should then be re-run once -+ * more data is available. -+ * -+ * Return: Returns %true if a complete SSH SYN sequence could be found, -+ * %false otherwise. -+ */ -+bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem) -+{ -+ size_t i; -+ -+ for (i = 0; i < src->len - 1; i++) { -+ if (likely(get_unaligned_le16(src->ptr + i) == SSH_MSG_SYN)) { -+ rem->ptr = src->ptr + i; -+ rem->len = src->len - i; -+ return true; -+ } -+ } -+ -+ if (unlikely(src->ptr[src->len - 1] == (SSH_MSG_SYN & 0xff))) { -+ rem->ptr = src->ptr + src->len - 1; -+ rem->len = 1; -+ return false; -+ } -+ -+ rem->ptr = src->ptr + src->len; -+ rem->len = 0; -+ return false; -+} -+ -+/** -+ * sshp_parse_frame() - Parse SSH frame. -+ * @dev: The device used for logging. -+ * @source: The source to parse from. -+ * @frame: The parsed frame (output). -+ * @payload: The parsed payload (output). -+ * @maxlen: The maximum supported message length. -+ * -+ * Parses and validates a SSH frame, including its payload, from the given -+ * source. Sets the provided @frame pointer to the start of the frame and -+ * writes the limits of the frame payload to the provided @payload span -+ * pointer. -+ * -+ * This function does not copy any data, but rather only validates the message -+ * data and sets pointers (and length values) to indicate the respective parts. -+ * -+ * If no complete SSH frame could be found, the frame pointer will be set to -+ * the %NULL pointer and the payload span will be set to the null span (start -+ * pointer %NULL, size zero). -+ * -+ * Return: Returns zero on success or if the frame is incomplete, %-ENOMSG if -+ * the start of the message is invalid, %-EBADMSG if any (frame-header or -+ * payload) CRC is invalid, or %-EMSGSIZE if the SSH message is bigger than -+ * the maximum message length specified in the @maxlen parameter. -+ */ -+int sshp_parse_frame(const struct device *dev, const struct ssam_span *source, -+ struct ssh_frame **frame, struct ssam_span *payload, -+ size_t maxlen) -+{ -+ struct ssam_span sf; -+ struct ssam_span sp; -+ -+ /* Initialize output. */ -+ *frame = NULL; -+ payload->ptr = NULL; -+ payload->len = 0; -+ -+ if (!sshp_starts_with_syn(source)) { -+ dev_warn(dev, "rx: parser: invalid start of frame\n"); -+ return -ENOMSG; -+ } -+ -+ /* Check for minimum packet length. */ -+ if (unlikely(source->len < SSH_MESSAGE_LENGTH(0))) { -+ dev_dbg(dev, "rx: parser: not enough data for frame\n"); -+ return 0; -+ } -+ -+ /* Pin down frame. */ -+ sf.ptr = source->ptr + sizeof(u16); -+ sf.len = sizeof(struct ssh_frame); -+ -+ /* Validate frame CRC. */ -+ if (unlikely(!sshp_validate_crc(&sf, sf.ptr + sf.len))) { -+ dev_warn(dev, "rx: parser: invalid frame CRC\n"); -+ return -EBADMSG; -+ } -+ -+ /* Ensure packet does not exceed maximum length. */ -+ sp.len = get_unaligned_le16(&((struct ssh_frame *)sf.ptr)->len); -+ if (unlikely(SSH_MESSAGE_LENGTH(sp.len) > maxlen)) { -+ dev_warn(dev, "rx: parser: frame too large: %llu bytes\n", -+ SSH_MESSAGE_LENGTH(sp.len)); -+ return -EMSGSIZE; -+ } -+ -+ /* Pin down payload. */ -+ sp.ptr = sf.ptr + sf.len + sizeof(u16); -+ -+ /* Check for frame + payload length. */ -+ if (source->len < SSH_MESSAGE_LENGTH(sp.len)) { -+ dev_dbg(dev, "rx: parser: not enough data for payload\n"); -+ return 0; -+ } -+ -+ /* Validate payload CRC. */ -+ if (unlikely(!sshp_validate_crc(&sp, sp.ptr + sp.len))) { -+ dev_warn(dev, "rx: parser: invalid payload CRC\n"); -+ return -EBADMSG; -+ } -+ -+ *frame = (struct ssh_frame *)sf.ptr; -+ *payload = sp; -+ -+ dev_dbg(dev, "rx: parser: valid frame found (type: %#04x, len: %u)\n", -+ (*frame)->type, (*frame)->len); -+ -+ return 0; -+} -+ -+/** -+ * sshp_parse_command() - Parse SSH command frame payload. -+ * @dev: The device used for logging. -+ * @source: The source to parse from. -+ * @command: The parsed command (output). -+ * @command_data: The parsed command data/payload (output). -+ * -+ * Parses and validates a SSH command frame payload. Sets the @command pointer -+ * to the command header and the @command_data span to the command data (i.e. -+ * payload of the command). This will result in a zero-length span if the -+ * command does not have any associated data/payload. This function does not -+ * check the frame-payload-type field, which should be checked by the caller -+ * before calling this function. -+ * -+ * The @source parameter should be the complete frame payload, e.g. returned -+ * by the sshp_parse_frame() command. -+ * -+ * This function does not copy any data, but rather only validates the frame -+ * payload data and sets pointers (and length values) to indicate the -+ * respective parts. -+ * -+ * Return: Returns zero on success or %-ENOMSG if @source does not represent a -+ * valid command-type frame payload, i.e. is too short. -+ */ -+int sshp_parse_command(const struct device *dev, const struct ssam_span *source, -+ struct ssh_command **command, -+ struct ssam_span *command_data) -+{ -+ /* Check for minimum length. */ -+ if (unlikely(source->len < sizeof(struct ssh_command))) { -+ *command = NULL; -+ command_data->ptr = NULL; -+ command_data->len = 0; -+ -+ dev_err(dev, "rx: parser: command payload is too short\n"); -+ return -ENOMSG; -+ } -+ -+ *command = (struct ssh_command *)source->ptr; -+ command_data->ptr = source->ptr + sizeof(struct ssh_command); -+ command_data->len = source->len - sizeof(struct ssh_command); -+ -+ dev_dbg(dev, "rx: parser: valid command found (tc: %#04x, cid: %#04x)\n", -+ (*command)->tc, (*command)->cid); -+ -+ return 0; -+} -diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h -new file mode 100644 -index 000000000000..63c38d350988 ---- /dev/null -+++ b/drivers/platform/surface/aggregator/ssh_parser.h -@@ -0,0 +1,154 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * SSH message parser. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H -+#define _SURFACE_AGGREGATOR_SSH_PARSER_H -+ -+#include -+#include -+#include -+#include -+ -+#include -+ -+/** -+ * struct sshp_buf - Parser buffer for SSH messages. -+ * @ptr: Pointer to the beginning of the buffer. -+ * @len: Number of bytes used in the buffer. -+ * @cap: Maximum capacity of the buffer. -+ */ -+struct sshp_buf { -+ u8 *ptr; -+ size_t len; -+ size_t cap; -+}; -+ -+/** -+ * sshp_buf_init() - Initialize a SSH parser buffer. -+ * @buf: The buffer to initialize. -+ * @ptr: The memory backing the buffer. -+ * @cap: The length of the memory backing the buffer, i.e. its capacity. -+ * -+ * Initializes the buffer with the given memory as backing and set its used -+ * length to zero. -+ */ -+static inline void sshp_buf_init(struct sshp_buf *buf, u8 *ptr, size_t cap) -+{ -+ buf->ptr = ptr; -+ buf->len = 0; -+ buf->cap = cap; -+} -+ -+/** -+ * sshp_buf_alloc() - Allocate and initialize a SSH parser buffer. -+ * @buf: The buffer to initialize/allocate to. -+ * @cap: The desired capacity of the buffer. -+ * @flags: The flags used for allocating the memory. -+ * -+ * Allocates @cap bytes and initializes the provided buffer struct with the -+ * allocated memory. -+ * -+ * Return: Returns zero on success and %-ENOMEM if allocation failed. -+ */ -+static inline int sshp_buf_alloc(struct sshp_buf *buf, size_t cap, gfp_t flags) -+{ -+ u8 *ptr; -+ -+ ptr = kzalloc(cap, flags); -+ if (!ptr) -+ return -ENOMEM; -+ -+ sshp_buf_init(buf, ptr, cap); -+ return 0; -+} -+ -+/** -+ * sshp_buf_free() - Free a SSH parser buffer. -+ * @buf: The buffer to free. -+ * -+ * Frees a SSH parser buffer by freeing the memory backing it and then -+ * resetting its pointer to %NULL and length and capacity to zero. Intended to -+ * free a buffer previously allocated with sshp_buf_alloc(). -+ */ -+static inline void sshp_buf_free(struct sshp_buf *buf) -+{ -+ kfree(buf->ptr); -+ buf->ptr = NULL; -+ buf->len = 0; -+ buf->cap = 0; -+} -+ -+/** -+ * sshp_buf_drop() - Drop data from the beginning of the buffer. -+ * @buf: The buffer to drop data from. -+ * @n: The number of bytes to drop. -+ * -+ * Drops the first @n bytes from the buffer. Re-aligns any remaining data to -+ * the beginning of the buffer. -+ */ -+static inline void sshp_buf_drop(struct sshp_buf *buf, size_t n) -+{ -+ memmove(buf->ptr, buf->ptr + n, buf->len - n); -+ buf->len -= n; -+} -+ -+/** -+ * sshp_buf_read_from_fifo() - Transfer data from a fifo to the buffer. -+ * @buf: The buffer to write the data into. -+ * @fifo: The fifo to read the data from. -+ * -+ * Transfers the data contained in the fifo to the buffer, removing it from -+ * the fifo. This function will try to transfer as much data as possible, -+ * limited either by the remaining space in the buffer or by the number of -+ * bytes available in the fifo. -+ * -+ * Return: Returns the number of bytes transferred. -+ */ -+static inline size_t sshp_buf_read_from_fifo(struct sshp_buf *buf, -+ struct kfifo *fifo) -+{ -+ size_t n; -+ -+ n = kfifo_out(fifo, buf->ptr + buf->len, buf->cap - buf->len); -+ buf->len += n; -+ -+ return n; -+} -+ -+/** -+ * sshp_buf_span_from() - Initialize a span from the given buffer and offset. -+ * @buf: The buffer to create the span from. -+ * @offset: The offset in the buffer at which the span should start. -+ * @span: The span to initialize (output). -+ * -+ * Initializes the provided span to point to the memory at the given offset in -+ * the buffer, with the length of the span being capped by the number of bytes -+ * used in the buffer after the offset (i.e. bytes remaining after the -+ * offset). -+ * -+ * Warning: This function does not validate that @offset is less than or equal -+ * to the number of bytes used in the buffer or the buffer capacity. This must -+ * be guaranteed by the caller. -+ */ -+static inline void sshp_buf_span_from(struct sshp_buf *buf, size_t offset, -+ struct ssam_span *span) -+{ -+ span->ptr = buf->ptr + offset; -+ span->len = buf->len - offset; -+} -+ -+bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem); -+ -+int sshp_parse_frame(const struct device *dev, const struct ssam_span *source, -+ struct ssh_frame **frame, struct ssam_span *payload, -+ size_t maxlen); -+ -+int sshp_parse_command(const struct device *dev, const struct ssam_span *source, -+ struct ssh_command **command, -+ struct ssam_span *command_data); -+ -+#endif /* _SURFACE_AGGREGATOR_SSH_PARSER_h */ -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c -new file mode 100644 -index 000000000000..66c839a995f3 ---- /dev/null -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.c -@@ -0,0 +1,1211 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * SSH request transport layer. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "ssh_packet_layer.h" -+#include "ssh_request_layer.h" -+ -+/* -+ * SSH_RTL_REQUEST_TIMEOUT - Request timeout. -+ * -+ * Timeout as ktime_t delta for request responses. If we have not received a -+ * response in this time-frame after finishing the underlying packet -+ * transmission, the request will be completed with %-ETIMEDOUT as status -+ * code. -+ */ -+#define SSH_RTL_REQUEST_TIMEOUT ms_to_ktime(3000) -+ -+/* -+ * SSH_RTL_REQUEST_TIMEOUT_RESOLUTION - Request timeout granularity. -+ * -+ * Time-resolution for timeouts. Should be larger than one jiffy to avoid -+ * direct re-scheduling of reaper work_struct. -+ */ -+#define SSH_RTL_REQUEST_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) -+ -+/* -+ * SSH_RTL_MAX_PENDING - Maximum number of pending requests. -+ * -+ * Maximum number of requests concurrently waiting to be completed (i.e. -+ * waiting for the corresponding packet transmission to finish if they don't -+ * have a response or waiting for a response if they have one). -+ */ -+#define SSH_RTL_MAX_PENDING 3 -+ -+/* -+ * SSH_RTL_TX_BATCH - Maximum number of requests processed per work execution. -+ * Used to prevent livelocking of the workqueue. Value chosen via educated -+ * guess, may be adjusted. -+ */ -+#define SSH_RTL_TX_BATCH 10 -+ -+static u16 ssh_request_get_rqid(struct ssh_request *rqst) -+{ -+ return get_unaligned_le16(rqst->packet.data.ptr -+ + SSH_MSGOFFSET_COMMAND(rqid)); -+} -+ -+static u32 ssh_request_get_rqid_safe(struct ssh_request *rqst) -+{ -+ if (!rqst->packet.data.ptr) -+ return U32_MAX; -+ -+ return ssh_request_get_rqid(rqst); -+} -+ -+static void ssh_rtl_queue_remove(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ spin_lock(&rtl->queue.lock); -+ -+ if (!test_and_clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &rqst->state)) { -+ spin_unlock(&rtl->queue.lock); -+ return; -+ } -+ -+ list_del(&rqst->node); -+ -+ spin_unlock(&rtl->queue.lock); -+ ssh_request_put(rqst); -+} -+ -+static bool ssh_rtl_queue_empty(struct ssh_rtl *rtl) -+{ -+ bool empty; -+ -+ spin_lock(&rtl->queue.lock); -+ empty = list_empty(&rtl->queue.head); -+ spin_unlock(&rtl->queue.lock); -+ -+ return empty; -+} -+ -+static void ssh_rtl_pending_remove(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ spin_lock(&rtl->pending.lock); -+ -+ if (!test_and_clear_bit(SSH_REQUEST_SF_PENDING_BIT, &rqst->state)) { -+ spin_unlock(&rtl->pending.lock); -+ return; -+ } -+ -+ atomic_dec(&rtl->pending.count); -+ list_del(&rqst->node); -+ -+ spin_unlock(&rtl->pending.lock); -+ -+ ssh_request_put(rqst); -+} -+ -+static int ssh_rtl_tx_pending_push(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ spin_lock(&rtl->pending.lock); -+ -+ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) { -+ spin_unlock(&rtl->pending.lock); -+ return -EINVAL; -+ } -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_PENDING_BIT, &rqst->state)) { -+ spin_unlock(&rtl->pending.lock); -+ return -EALREADY; -+ } -+ -+ atomic_inc(&rtl->pending.count); -+ list_add_tail(&ssh_request_get(rqst)->node, &rtl->pending.head); -+ -+ spin_unlock(&rtl->pending.lock); -+ return 0; -+} -+ -+static void ssh_rtl_complete_with_status(struct ssh_request *rqst, int status) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ /* rtl/ptl may not be set if we're canceling before submitting. */ -+ rtl_dbg_cond(rtl, "rtl: completing request (rqid: %#06x, status: %d)\n", -+ ssh_request_get_rqid_safe(rqst), status); -+ -+ rqst->ops->complete(rqst, NULL, NULL, status); -+} -+ -+static void ssh_rtl_complete_with_rsp(struct ssh_request *rqst, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ rtl_dbg(rtl, "rtl: completing request with response (rqid: %#06x)\n", -+ ssh_request_get_rqid(rqst)); -+ -+ rqst->ops->complete(rqst, cmd, data, 0); -+} -+ -+static bool ssh_rtl_tx_can_process(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ if (test_bit(SSH_REQUEST_TY_FLUSH_BIT, &rqst->state)) -+ return !atomic_read(&rtl->pending.count); -+ -+ return atomic_read(&rtl->pending.count) < SSH_RTL_MAX_PENDING; -+} -+ -+static struct ssh_request *ssh_rtl_tx_next(struct ssh_rtl *rtl) -+{ -+ struct ssh_request *rqst = ERR_PTR(-ENOENT); -+ struct ssh_request *p, *n; -+ -+ spin_lock(&rtl->queue.lock); -+ -+ /* Find first non-locked request and remove it. */ -+ list_for_each_entry_safe(p, n, &rtl->queue.head, node) { -+ if (unlikely(test_bit(SSH_REQUEST_SF_LOCKED_BIT, &p->state))) -+ continue; -+ -+ if (!ssh_rtl_tx_can_process(p)) { -+ rqst = ERR_PTR(-EBUSY); -+ break; -+ } -+ -+ /* Remove from queue and mark as transmitting. */ -+ set_bit(SSH_REQUEST_SF_TRANSMITTING_BIT, &p->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &p->state); -+ -+ list_del(&p->node); -+ -+ rqst = p; -+ break; -+ } -+ -+ spin_unlock(&rtl->queue.lock); -+ return rqst; -+} -+ -+static int ssh_rtl_tx_try_process_one(struct ssh_rtl *rtl) -+{ -+ struct ssh_request *rqst; -+ int status; -+ -+ /* Get and prepare next request for transmit. */ -+ rqst = ssh_rtl_tx_next(rtl); -+ if (IS_ERR(rqst)) -+ return PTR_ERR(rqst); -+ -+ /* Add it to/mark it as pending. */ -+ status = ssh_rtl_tx_pending_push(rqst); -+ if (status) { -+ ssh_request_put(rqst); -+ return -EAGAIN; -+ } -+ -+ /* Submit packet. */ -+ status = ssh_ptl_submit(&rtl->ptl, &rqst->packet); -+ if (status == -ESHUTDOWN) { -+ /* -+ * Packet has been refused due to the packet layer shutting -+ * down. Complete it here. -+ */ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state); -+ /* -+ * Note: A barrier is not required here, as there are only two -+ * references in the system at this point: The one that we have, -+ * and the other one that belongs to the pending set. Due to the -+ * request being marked as "transmitting", our process is the -+ * only one allowed to remove the pending node and change the -+ * state. Normally, the task would fall to the packet callback, -+ * but as this is a path where submission failed, this callback -+ * will never be executed. -+ */ -+ -+ ssh_rtl_pending_remove(rqst); -+ ssh_rtl_complete_with_status(rqst, -ESHUTDOWN); -+ -+ ssh_request_put(rqst); -+ return -ESHUTDOWN; -+ -+ } else if (status) { -+ /* -+ * If submitting the packet failed and the packet layer isn't -+ * shutting down, the packet has either been submitted/queued -+ * before (-EALREADY, which cannot happen as we have -+ * guaranteed that requests cannot be re-submitted), or the -+ * packet was marked as locked (-EINVAL). To mark the packet -+ * locked at this stage, the request, and thus the packets -+ * itself, had to have been canceled. Simply drop the -+ * reference. Cancellation itself will remove it from the set -+ * of pending requests. -+ */ -+ -+ WARN_ON(status != -EINVAL); -+ -+ ssh_request_put(rqst); -+ return -EAGAIN; -+ } -+ -+ ssh_request_put(rqst); -+ return 0; -+} -+ -+static bool ssh_rtl_tx_schedule(struct ssh_rtl *rtl) -+{ -+ if (atomic_read(&rtl->pending.count) >= SSH_RTL_MAX_PENDING) -+ return false; -+ -+ if (ssh_rtl_queue_empty(rtl)) -+ return false; -+ -+ return schedule_work(&rtl->tx.work); -+} -+ -+static void ssh_rtl_tx_work_fn(struct work_struct *work) -+{ -+ struct ssh_rtl *rtl = to_ssh_rtl(work, tx.work); -+ unsigned int iterations = SSH_RTL_TX_BATCH; -+ int status; -+ -+ /* -+ * Try to be nice and not block/live-lock the workqueue: Run a maximum -+ * of 10 tries, then re-submit if necessary. This should not be -+ * necessary for normal execution, but guarantee it anyway. -+ */ -+ do { -+ status = ssh_rtl_tx_try_process_one(rtl); -+ if (status == -ENOENT || status == -EBUSY) -+ return; /* No more requests to process. */ -+ -+ if (status == -ESHUTDOWN) { -+ /* -+ * Packet system shutting down. No new packets can be -+ * transmitted. Return silently, the party initiating -+ * the shutdown should handle the rest. -+ */ -+ return; -+ } -+ -+ WARN_ON(status != 0 && status != -EAGAIN); -+ } while (--iterations); -+ -+ /* Out of tries, reschedule. */ -+ ssh_rtl_tx_schedule(rtl); -+} -+ -+/** -+ * ssh_rtl_submit() - Submit a request to the transport layer. -+ * @rtl: The request transport layer. -+ * @rqst: The request to submit. -+ * -+ * Submits a request to the transport layer. A single request may not be -+ * submitted multiple times without reinitializing it. -+ * -+ * Return: Returns zero on success, %-EINVAL if the request type is invalid or -+ * the request has been canceled prior to submission, %-EALREADY if the -+ * request has already been submitted, or %-ESHUTDOWN in case the request -+ * transport layer has been shut down. -+ */ -+int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst) -+{ -+ /* -+ * Ensure that requests expecting a response are sequenced. If this -+ * invariant ever changes, see the comment in ssh_rtl_complete() on what -+ * is required to be changed in the code. -+ */ -+ if (test_bit(SSH_REQUEST_TY_HAS_RESPONSE_BIT, &rqst->state)) -+ if (!test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &rqst->packet.state)) -+ return -EINVAL; -+ -+ spin_lock(&rtl->queue.lock); -+ -+ /* -+ * Try to set ptl and check if this request has already been submitted. -+ * -+ * Must be inside lock as we might run into a lost update problem -+ * otherwise: If this were outside of the lock, cancellation in -+ * ssh_rtl_cancel_nonpending() may run after we've set the ptl -+ * reference but before we enter the lock. In that case, we'd detect -+ * that the request is being added to the queue and would try to remove -+ * it from that, but removal might fail because it hasn't actually been -+ * added yet. By putting this cmpxchg in the critical section, we -+ * ensure that the queuing detection only triggers when we are already -+ * in the critical section and the remove process will wait until the -+ * push operation has been completed (via lock) due to that. Only then, -+ * we can safely try to remove it. -+ */ -+ if (cmpxchg(&rqst->packet.ptl, NULL, &rtl->ptl)) { -+ spin_unlock(&rtl->queue.lock); -+ return -EALREADY; -+ } -+ -+ /* -+ * Ensure that we set ptl reference before we continue modifying state. -+ * This is required for non-pending cancellation. This barrier is paired -+ * with the one in ssh_rtl_cancel_nonpending(). -+ * -+ * By setting the ptl reference before we test for "locked", we can -+ * check if the "locked" test may have already run. See comments in -+ * ssh_rtl_cancel_nonpending() for more detail. -+ */ -+ smp_mb__after_atomic(); -+ -+ if (test_bit(SSH_RTL_SF_SHUTDOWN_BIT, &rtl->state)) { -+ spin_unlock(&rtl->queue.lock); -+ return -ESHUTDOWN; -+ } -+ -+ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) { -+ spin_unlock(&rtl->queue.lock); -+ return -EINVAL; -+ } -+ -+ set_bit(SSH_REQUEST_SF_QUEUED_BIT, &rqst->state); -+ list_add_tail(&ssh_request_get(rqst)->node, &rtl->queue.head); -+ -+ spin_unlock(&rtl->queue.lock); -+ -+ ssh_rtl_tx_schedule(rtl); -+ return 0; -+} -+ -+static void ssh_rtl_timeout_reaper_mod(struct ssh_rtl *rtl, ktime_t now, -+ ktime_t expires) -+{ -+ unsigned long delta = msecs_to_jiffies(ktime_ms_delta(expires, now)); -+ ktime_t aexp = ktime_add(expires, SSH_RTL_REQUEST_TIMEOUT_RESOLUTION); -+ -+ spin_lock(&rtl->rtx_timeout.lock); -+ -+ /* Re-adjust / schedule reaper only if it is above resolution delta. */ -+ if (ktime_before(aexp, rtl->rtx_timeout.expires)) { -+ rtl->rtx_timeout.expires = expires; -+ mod_delayed_work(system_wq, &rtl->rtx_timeout.reaper, delta); -+ } -+ -+ spin_unlock(&rtl->rtx_timeout.lock); -+} -+ -+static void ssh_rtl_timeout_start(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ ktime_t timestamp = ktime_get_coarse_boottime(); -+ ktime_t timeout = rtl->rtx_timeout.timeout; -+ -+ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) -+ return; -+ -+ /* -+ * Note: The timestamp gets set only once. This happens on the packet -+ * callback. All other access to it is read-only. -+ */ -+ WRITE_ONCE(rqst->timestamp, timestamp); -+ /* -+ * Ensure timestamp is set before starting the reaper. Paired with -+ * implicit barrier following check on ssh_request_get_expiration() in -+ * ssh_rtl_timeout_reap. -+ */ -+ smp_mb__after_atomic(); -+ -+ ssh_rtl_timeout_reaper_mod(rtl, timestamp, timestamp + timeout); -+} -+ -+static void ssh_rtl_complete(struct ssh_rtl *rtl, -+ const struct ssh_command *command, -+ const struct ssam_span *command_data) -+{ -+ struct ssh_request *r = NULL; -+ struct ssh_request *p, *n; -+ u16 rqid = get_unaligned_le16(&command->rqid); -+ -+ /* -+ * Get request from pending based on request ID and mark it as response -+ * received and locked. -+ */ -+ spin_lock(&rtl->pending.lock); -+ list_for_each_entry_safe(p, n, &rtl->pending.head, node) { -+ /* We generally expect requests to be processed in order. */ -+ if (unlikely(ssh_request_get_rqid(p) != rqid)) -+ continue; -+ -+ /* -+ * Mark as "response received" and "locked" as we're going to -+ * complete it. -+ */ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &p->state); -+ set_bit(SSH_REQUEST_SF_RSPRCVD_BIT, &p->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &p->state); -+ -+ atomic_dec(&rtl->pending.count); -+ list_del(&p->node); -+ -+ r = p; -+ break; -+ } -+ spin_unlock(&rtl->pending.lock); -+ -+ if (!r) { -+ rtl_warn(rtl, "rtl: dropping unexpected command message (rqid = %#06x)\n", -+ rqid); -+ return; -+ } -+ -+ /* If the request hasn't been completed yet, we will do this now. */ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) { -+ ssh_request_put(r); -+ ssh_rtl_tx_schedule(rtl); -+ return; -+ } -+ -+ /* -+ * Make sure the request has been transmitted. In case of a sequenced -+ * request, we are guaranteed that the completion callback will run on -+ * the receiver thread directly when the ACK for the packet has been -+ * received. Similarly, this function is guaranteed to run on the -+ * receiver thread. Thus we are guaranteed that if the packet has been -+ * successfully transmitted and received an ACK, the transmitted flag -+ * has been set and is visible here. -+ * -+ * We are currently not handling unsequenced packets here, as those -+ * should never expect a response as ensured in ssh_rtl_submit. If this -+ * ever changes, one would have to test for -+ * -+ * (r->state & (transmitting | transmitted)) -+ * -+ * on unsequenced packets to determine if they could have been -+ * transmitted. There are no synchronization guarantees as in the -+ * sequenced case, since, in this case, the callback function will not -+ * run on the same thread. Thus an exact determination is impossible. -+ */ -+ if (!test_bit(SSH_REQUEST_SF_TRANSMITTED_BIT, &r->state)) { -+ rtl_err(rtl, "rtl: received response before ACK for request (rqid = %#06x)\n", -+ rqid); -+ -+ /* -+ * NB: Timeout has already been canceled, request already been -+ * removed from pending and marked as locked and completed. As -+ * we receive a "false" response, the packet might still be -+ * queued though. -+ */ -+ ssh_rtl_queue_remove(r); -+ -+ ssh_rtl_complete_with_status(r, -EREMOTEIO); -+ ssh_request_put(r); -+ -+ ssh_rtl_tx_schedule(rtl); -+ return; -+ } -+ -+ /* -+ * NB: Timeout has already been canceled, request already been -+ * removed from pending and marked as locked and completed. The request -+ * can also not be queued any more, as it has been marked as -+ * transmitting and later transmitted. Thus no need to remove it from -+ * anywhere. -+ */ -+ -+ ssh_rtl_complete_with_rsp(r, command, command_data); -+ ssh_request_put(r); -+ -+ ssh_rtl_tx_schedule(rtl); -+} -+ -+static bool ssh_rtl_cancel_nonpending(struct ssh_request *r) -+{ -+ struct ssh_rtl *rtl; -+ unsigned long flags, fixed; -+ bool remove; -+ -+ /* -+ * Handle unsubmitted request: Try to mark the packet as locked, -+ * expecting the state to be zero (i.e. unsubmitted). Note that, if -+ * setting the state worked, we might still be adding the packet to the -+ * queue in a currently executing submit call. In that case, however, -+ * ptl reference must have been set previously, as locked is checked -+ * after setting ptl. Furthermore, when the ptl reference is set, the -+ * submission process is guaranteed to have entered the critical -+ * section. Thus only if we successfully locked this request and ptl is -+ * NULL, we have successfully removed the request, i.e. we are -+ * guaranteed that, due to the "locked" check in ssh_rtl_submit(), the -+ * packet will never be added. Otherwise, we need to try and grab it -+ * from the queue, where we are now guaranteed that the packet is or has -+ * been due to the critical section. -+ * -+ * Note that if the cmpxchg() fails, we are guaranteed that ptl has -+ * been set and is non-NULL, as states can only be nonzero after this -+ * has been set. Also note that we need to fetch the static (type) -+ * flags to ensure that they don't cause the cmpxchg() to fail. -+ */ -+ fixed = READ_ONCE(r->state) & SSH_REQUEST_FLAGS_TY_MASK; -+ flags = cmpxchg(&r->state, fixed, SSH_REQUEST_SF_LOCKED_BIT); -+ -+ /* -+ * Force correct ordering with regards to state and ptl reference access -+ * to safe-guard cancellation to concurrent submission against a -+ * lost-update problem. First try to exchange state, then also check -+ * ptl if that worked. This barrier is paired with the -+ * one in ssh_rtl_submit(). -+ */ -+ smp_mb__after_atomic(); -+ -+ if (flags == fixed && !READ_ONCE(r->packet.ptl)) { -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ return true; -+ } -+ -+ rtl = ssh_request_rtl(r); -+ spin_lock(&rtl->queue.lock); -+ -+ /* -+ * Note: 1) Requests cannot be re-submitted. 2) If a request is -+ * queued, it cannot be "transmitting"/"pending" yet. Thus, if we -+ * successfully remove the request here, we have removed all its -+ * occurrences in the system. -+ */ -+ -+ remove = test_and_clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); -+ if (!remove) { -+ spin_unlock(&rtl->queue.lock); -+ return false; -+ } -+ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ list_del(&r->node); -+ -+ spin_unlock(&rtl->queue.lock); -+ -+ ssh_request_put(r); /* Drop reference obtained from queue. */ -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ return true; -+} -+ -+static bool ssh_rtl_cancel_pending(struct ssh_request *r) -+{ -+ /* If the packet is already locked, it's going to be removed shortly. */ -+ if (test_and_set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state)) -+ return true; -+ -+ /* -+ * Now that we have locked the packet, we have guaranteed that it can't -+ * be added to the system any more. If ptl is NULL, the locked -+ * check in ssh_rtl_submit() has not been run and any submission, -+ * currently in progress or called later, won't add the packet. Thus we -+ * can directly complete it. -+ * -+ * The implicit memory barrier of test_and_set_bit() should be enough -+ * to ensure that the correct order (first lock, then check ptl) is -+ * ensured. This is paired with the barrier in ssh_rtl_submit(). -+ */ -+ if (!READ_ONCE(r->packet.ptl)) { -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ return true; -+ } -+ -+ /* -+ * Try to cancel the packet. If the packet has not been completed yet, -+ * this will subsequently (and synchronously) call the completion -+ * callback of the packet, which will complete the request. -+ */ -+ ssh_ptl_cancel(&r->packet); -+ -+ /* -+ * If the packet has been completed with success, i.e. has not been -+ * canceled by the above call, the request may not have been completed -+ * yet (may be waiting for a response). Check if we need to do this -+ * here. -+ */ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_queue_remove(r); -+ ssh_rtl_pending_remove(r); -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ -+ return true; -+} -+ -+/** -+ * ssh_rtl_cancel() - Cancel request. -+ * @rqst: The request to cancel. -+ * @pending: Whether to also cancel pending requests. -+ * -+ * Cancels the given request. If @pending is %false, this will not cancel -+ * pending requests, i.e. requests that have already been submitted to the -+ * packet layer but not been completed yet. If @pending is %true, this will -+ * cancel the given request regardless of the state it is in. -+ * -+ * If the request has been canceled by calling this function, both completion -+ * and release callbacks of the request will be executed in a reasonable -+ * time-frame. This may happen during execution of this function, however, -+ * there is no guarantee for this. For example, a request currently -+ * transmitting will be canceled/completed only after transmission has -+ * completed, and the respective callbacks will be executed on the transmitter -+ * thread, which may happen during, but also some time after execution of the -+ * cancel function. -+ * -+ * Return: Returns %true if the given request has been canceled or completed, -+ * either by this function or prior to calling this function, %false -+ * otherwise. If @pending is %true, this function will always return %true. -+ */ -+bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending) -+{ -+ struct ssh_rtl *rtl; -+ bool canceled; -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_CANCELED_BIT, &rqst->state)) -+ return true; -+ -+ if (pending) -+ canceled = ssh_rtl_cancel_pending(rqst); -+ else -+ canceled = ssh_rtl_cancel_nonpending(rqst); -+ -+ /* Note: rtl may be NULL if request has not been submitted yet. */ -+ rtl = ssh_request_rtl(rqst); -+ if (canceled && rtl) -+ ssh_rtl_tx_schedule(rtl); -+ -+ return canceled; -+} -+ -+static void ssh_rtl_packet_callback(struct ssh_packet *p, int status) -+{ -+ struct ssh_request *r = to_ssh_request(p); -+ -+ if (unlikely(status)) { -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return; -+ -+ /* -+ * The packet may get canceled even though it has not been -+ * submitted yet. The request may still be queued. Check the -+ * queue and remove it if necessary. As the timeout would have -+ * been started in this function on success, there's no need -+ * to cancel it here. -+ */ -+ ssh_rtl_queue_remove(r); -+ ssh_rtl_pending_remove(r); -+ ssh_rtl_complete_with_status(r, status); -+ -+ ssh_rtl_tx_schedule(ssh_request_rtl(r)); -+ return; -+ } -+ -+ /* Update state: Mark as transmitted and clear transmitting. */ -+ set_bit(SSH_REQUEST_SF_TRANSMITTED_BIT, &r->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_TRANSMITTING_BIT, &r->state); -+ -+ /* If we expect a response, we just need to start the timeout. */ -+ if (test_bit(SSH_REQUEST_TY_HAS_RESPONSE_BIT, &r->state)) { -+ /* -+ * Note: This is the only place where the timestamp gets set, -+ * all other access to it is read-only. -+ */ -+ ssh_rtl_timeout_start(r); -+ return; -+ } -+ -+ /* -+ * If we don't expect a response, lock, remove, and complete the -+ * request. Note that, at this point, the request is guaranteed to have -+ * left the queue and no timeout has been started. Thus we only need to -+ * remove it from pending. If the request has already been completed (it -+ * may have been canceled) return. -+ */ -+ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return; -+ -+ ssh_rtl_pending_remove(r); -+ ssh_rtl_complete_with_status(r, 0); -+ -+ ssh_rtl_tx_schedule(ssh_request_rtl(r)); -+} -+ -+static ktime_t ssh_request_get_expiration(struct ssh_request *r, ktime_t timeout) -+{ -+ ktime_t timestamp = READ_ONCE(r->timestamp); -+ -+ if (timestamp != KTIME_MAX) -+ return ktime_add(timestamp, timeout); -+ else -+ return KTIME_MAX; -+} -+ -+static void ssh_rtl_timeout_reap(struct work_struct *work) -+{ -+ struct ssh_rtl *rtl = to_ssh_rtl(work, rtx_timeout.reaper.work); -+ struct ssh_request *r, *n; -+ LIST_HEAD(claimed); -+ ktime_t now = ktime_get_coarse_boottime(); -+ ktime_t timeout = rtl->rtx_timeout.timeout; -+ ktime_t next = KTIME_MAX; -+ -+ /* -+ * Mark reaper as "not pending". This is done before checking any -+ * requests to avoid lost-update type problems. -+ */ -+ spin_lock(&rtl->rtx_timeout.lock); -+ rtl->rtx_timeout.expires = KTIME_MAX; -+ spin_unlock(&rtl->rtx_timeout.lock); -+ -+ spin_lock(&rtl->pending.lock); -+ list_for_each_entry_safe(r, n, &rtl->pending.head, node) { -+ ktime_t expires = ssh_request_get_expiration(r, timeout); -+ -+ /* -+ * Check if the timeout hasn't expired yet. Find out next -+ * expiration date to be handled after this run. -+ */ -+ if (ktime_after(expires, now)) { -+ next = ktime_before(expires, next) ? expires : next; -+ continue; -+ } -+ -+ /* Avoid further transitions if locked. */ -+ if (test_and_set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state)) -+ continue; -+ -+ /* -+ * We have now marked the packet as locked. Thus it cannot be -+ * added to the pending or queued lists again after we've -+ * removed it here. We can therefore re-use the node of this -+ * packet temporarily. -+ */ -+ -+ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); -+ -+ atomic_dec(&rtl->pending.count); -+ list_del(&r->node); -+ -+ list_add_tail(&r->node, &claimed); -+ } -+ spin_unlock(&rtl->pending.lock); -+ -+ /* Cancel and complete the request. */ -+ list_for_each_entry_safe(r, n, &claimed, node) { -+ /* -+ * At this point we've removed the packet from pending. This -+ * means that we've obtained the last (only) reference of the -+ * system to it. Thus we can just complete it. -+ */ -+ if (!test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ ssh_rtl_complete_with_status(r, -ETIMEDOUT); -+ -+ /* -+ * Drop the reference we've obtained by removing it from the -+ * pending set. -+ */ -+ list_del(&r->node); -+ ssh_request_put(r); -+ } -+ -+ /* Ensure that the reaper doesn't run again immediately. */ -+ next = max(next, ktime_add(now, SSH_RTL_REQUEST_TIMEOUT_RESOLUTION)); -+ if (next != KTIME_MAX) -+ ssh_rtl_timeout_reaper_mod(rtl, now, next); -+ -+ ssh_rtl_tx_schedule(rtl); -+} -+ -+static void ssh_rtl_rx_event(struct ssh_rtl *rtl, const struct ssh_command *cmd, -+ const struct ssam_span *data) -+{ -+ rtl_dbg(rtl, "rtl: handling event (rqid: %#06x)\n", -+ get_unaligned_le16(&cmd->rqid)); -+ -+ rtl->ops.handle_event(rtl, cmd, data); -+} -+ -+static void ssh_rtl_rx_command(struct ssh_ptl *p, const struct ssam_span *data) -+{ -+ struct ssh_rtl *rtl = to_ssh_rtl(p, ptl); -+ struct device *dev = &p->serdev->dev; -+ struct ssh_command *command; -+ struct ssam_span command_data; -+ -+ if (sshp_parse_command(dev, data, &command, &command_data)) -+ return; -+ -+ if (ssh_rqid_is_event(get_unaligned_le16(&command->rqid))) -+ ssh_rtl_rx_event(rtl, command, &command_data); -+ else -+ ssh_rtl_complete(rtl, command, &command_data); -+} -+ -+static void ssh_rtl_rx_data(struct ssh_ptl *p, const struct ssam_span *data) -+{ -+ if (!data->len) { -+ ptl_err(p, "rtl: rx: no data frame payload\n"); -+ return; -+ } -+ -+ switch (data->ptr[0]) { -+ case SSH_PLD_TYPE_CMD: -+ ssh_rtl_rx_command(p, data); -+ break; -+ -+ default: -+ ptl_err(p, "rtl: rx: unknown frame payload type (type: %#04x)\n", -+ data->ptr[0]); -+ break; -+ } -+} -+ -+static void ssh_rtl_packet_release(struct ssh_packet *p) -+{ -+ struct ssh_request *rqst; -+ -+ rqst = to_ssh_request(p); -+ rqst->ops->release(rqst); -+} -+ -+static const struct ssh_packet_ops ssh_rtl_packet_ops = { -+ .complete = ssh_rtl_packet_callback, -+ .release = ssh_rtl_packet_release, -+}; -+ -+/** -+ * ssh_request_init() - Initialize SSH request. -+ * @rqst: The request to initialize. -+ * @flags: Request flags, determining the type of the request. -+ * @ops: Request operations. -+ * -+ * Initializes the given SSH request and underlying packet. Sets the message -+ * buffer pointer to %NULL and the message buffer length to zero. This buffer -+ * has to be set separately via ssh_request_set_data() before submission and -+ * must contain a valid SSH request message. -+ * -+ * Return: Returns zero on success or %-EINVAL if the given flags are invalid. -+ */ -+int ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags, -+ const struct ssh_request_ops *ops) -+{ -+ unsigned long type = BIT(SSH_PACKET_TY_BLOCKING_BIT); -+ -+ /* Unsequenced requests cannot have a response. */ -+ if (flags & SSAM_REQUEST_UNSEQUENCED && flags & SSAM_REQUEST_HAS_RESPONSE) -+ return -EINVAL; -+ -+ if (!(flags & SSAM_REQUEST_UNSEQUENCED)) -+ type |= BIT(SSH_PACKET_TY_SEQUENCED_BIT); -+ -+ ssh_packet_init(&rqst->packet, type, SSH_PACKET_PRIORITY(DATA, 0), -+ &ssh_rtl_packet_ops); -+ -+ INIT_LIST_HEAD(&rqst->node); -+ -+ rqst->state = 0; -+ if (flags & SSAM_REQUEST_HAS_RESPONSE) -+ rqst->state |= BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT); -+ -+ rqst->timestamp = KTIME_MAX; -+ rqst->ops = ops; -+ -+ return 0; -+} -+ -+/** -+ * ssh_rtl_init() - Initialize request transport layer. -+ * @rtl: The request transport layer to initialize. -+ * @serdev: The underlying serial device, i.e. the lower-level transport. -+ * @ops: Request transport layer operations. -+ * -+ * Initializes the given request transport layer and associated packet -+ * transport layer. Transmitter and receiver threads must be started -+ * separately via ssh_rtl_tx_start() and ssh_rtl_rx_start(), after the -+ * request-layer has been initialized and the lower-level serial device layer -+ * has been set up. -+ * -+ * Return: Returns zero on success and a nonzero error code on failure. -+ */ -+int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev, -+ const struct ssh_rtl_ops *ops) -+{ -+ struct ssh_ptl_ops ptl_ops; -+ int status; -+ -+ ptl_ops.data_received = ssh_rtl_rx_data; -+ -+ status = ssh_ptl_init(&rtl->ptl, serdev, &ptl_ops); -+ if (status) -+ return status; -+ -+ spin_lock_init(&rtl->queue.lock); -+ INIT_LIST_HEAD(&rtl->queue.head); -+ -+ spin_lock_init(&rtl->pending.lock); -+ INIT_LIST_HEAD(&rtl->pending.head); -+ atomic_set_release(&rtl->pending.count, 0); -+ -+ INIT_WORK(&rtl->tx.work, ssh_rtl_tx_work_fn); -+ -+ spin_lock_init(&rtl->rtx_timeout.lock); -+ rtl->rtx_timeout.timeout = SSH_RTL_REQUEST_TIMEOUT; -+ rtl->rtx_timeout.expires = KTIME_MAX; -+ INIT_DELAYED_WORK(&rtl->rtx_timeout.reaper, ssh_rtl_timeout_reap); -+ -+ rtl->ops = *ops; -+ -+ return 0; -+} -+ -+/** -+ * ssh_rtl_destroy() - Deinitialize request transport layer. -+ * @rtl: The request transport layer to deinitialize. -+ * -+ * Deinitializes the given request transport layer and frees resources -+ * associated with it. If receiver and/or transmitter threads have been -+ * started, the layer must first be shut down via ssh_rtl_shutdown() before -+ * this function can be called. -+ */ -+void ssh_rtl_destroy(struct ssh_rtl *rtl) -+{ -+ ssh_ptl_destroy(&rtl->ptl); -+} -+ -+/** -+ * ssh_rtl_tx_start() - Start request transmitter and receiver. -+ * @rtl: The request transport layer. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssh_rtl_start(struct ssh_rtl *rtl) -+{ -+ int status; -+ -+ status = ssh_ptl_tx_start(&rtl->ptl); -+ if (status) -+ return status; -+ -+ ssh_rtl_tx_schedule(rtl); -+ -+ status = ssh_ptl_rx_start(&rtl->ptl); -+ if (status) { -+ ssh_rtl_flush(rtl, msecs_to_jiffies(5000)); -+ ssh_ptl_tx_stop(&rtl->ptl); -+ return status; -+ } -+ -+ return 0; -+} -+ -+struct ssh_flush_request { -+ struct ssh_request base; -+ struct completion completion; -+ int status; -+}; -+ -+static void ssh_rtl_flush_request_complete(struct ssh_request *r, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data, -+ int status) -+{ -+ struct ssh_flush_request *rqst; -+ -+ rqst = container_of(r, struct ssh_flush_request, base); -+ rqst->status = status; -+} -+ -+static void ssh_rtl_flush_request_release(struct ssh_request *r) -+{ -+ struct ssh_flush_request *rqst; -+ -+ rqst = container_of(r, struct ssh_flush_request, base); -+ complete_all(&rqst->completion); -+} -+ -+static const struct ssh_request_ops ssh_rtl_flush_request_ops = { -+ .complete = ssh_rtl_flush_request_complete, -+ .release = ssh_rtl_flush_request_release, -+}; -+ -+/** -+ * ssh_rtl_flush() - Flush the request transport layer. -+ * @rtl: request transport layer -+ * @timeout: timeout for the flush operation in jiffies -+ * -+ * Queue a special flush request and wait for its completion. This request -+ * will be completed after all other currently queued and pending requests -+ * have been completed. Instead of a normal data packet, this request submits -+ * a special flush packet, meaning that upon completion, also the underlying -+ * packet transport layer has been flushed. -+ * -+ * Flushing the request layer guarantees that all previously submitted -+ * requests have been fully completed before this call returns. Additionally, -+ * flushing blocks execution of all later submitted requests until the flush -+ * has been completed. -+ * -+ * If the caller ensures that no new requests are submitted after a call to -+ * this function, the request transport layer is guaranteed to have no -+ * remaining requests when this call returns. The same guarantee does not hold -+ * for the packet layer, on which control packets may still be queued after -+ * this call. -+ * -+ * Return: Returns zero on success, %-ETIMEDOUT if the flush timed out and has -+ * been canceled as a result of the timeout, or %-ESHUTDOWN if the packet -+ * and/or request transport layer has been shut down before this call. May -+ * also return %-EINTR if the underlying packet transmission has been -+ * interrupted. -+ */ -+int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout) -+{ -+ const unsigned int init_flags = SSAM_REQUEST_UNSEQUENCED; -+ struct ssh_flush_request rqst; -+ int status; -+ -+ ssh_request_init(&rqst.base, init_flags, &ssh_rtl_flush_request_ops); -+ rqst.base.packet.state |= BIT(SSH_PACKET_TY_FLUSH_BIT); -+ rqst.base.packet.priority = SSH_PACKET_PRIORITY(FLUSH, 0); -+ rqst.base.state |= BIT(SSH_REQUEST_TY_FLUSH_BIT); -+ -+ init_completion(&rqst.completion); -+ -+ status = ssh_rtl_submit(rtl, &rqst.base); -+ if (status) -+ return status; -+ -+ ssh_request_put(&rqst.base); -+ -+ if (!wait_for_completion_timeout(&rqst.completion, timeout)) { -+ ssh_rtl_cancel(&rqst.base, true); -+ wait_for_completion(&rqst.completion); -+ } -+ -+ WARN_ON(rqst.status != 0 && rqst.status != -ECANCELED && -+ rqst.status != -ESHUTDOWN && rqst.status != -EINTR); -+ -+ return rqst.status == -ECANCELED ? -ETIMEDOUT : rqst.status; -+} -+ -+/** -+ * ssh_rtl_shutdown() - Shut down request transport layer. -+ * @rtl: The request transport layer. -+ * -+ * Shuts down the request transport layer, removing and canceling all queued -+ * and pending requests. Requests canceled by this operation will be completed -+ * with %-ESHUTDOWN as status. Receiver and transmitter threads will be -+ * stopped, the lower-level packet layer will be shutdown. -+ * -+ * As a result of this function, the transport layer will be marked as shut -+ * down. Submission of requests after the transport layer has been shut down -+ * will fail with %-ESHUTDOWN. -+ */ -+void ssh_rtl_shutdown(struct ssh_rtl *rtl) -+{ -+ struct ssh_request *r, *n; -+ LIST_HEAD(claimed); -+ int pending; -+ -+ set_bit(SSH_RTL_SF_SHUTDOWN_BIT, &rtl->state); -+ /* -+ * Ensure that the layer gets marked as shut-down before actually -+ * stopping it. In combination with the check in ssh_rtl_submit(), -+ * this guarantees that no new requests can be added and all already -+ * queued requests are properly canceled. -+ */ -+ smp_mb__after_atomic(); -+ -+ /* Remove requests from queue. */ -+ spin_lock(&rtl->queue.lock); -+ list_for_each_entry_safe(r, n, &rtl->queue.head, node) { -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); -+ -+ list_del(&r->node); -+ list_add_tail(&r->node, &claimed); -+ } -+ spin_unlock(&rtl->queue.lock); -+ -+ /* -+ * We have now guaranteed that the queue is empty and no more new -+ * requests can be submitted (i.e. it will stay empty). This means that -+ * calling ssh_rtl_tx_schedule() will not schedule tx.work any more. So -+ * we can simply call cancel_work_sync() on tx.work here and when that -+ * returns, we've locked it down. This also means that after this call, -+ * we don't submit any more packets to the underlying packet layer, so -+ * we can also shut that down. -+ */ -+ -+ cancel_work_sync(&rtl->tx.work); -+ ssh_ptl_shutdown(&rtl->ptl); -+ cancel_delayed_work_sync(&rtl->rtx_timeout.reaper); -+ -+ /* -+ * Shutting down the packet layer should also have canceled all -+ * requests. Thus the pending set should be empty. Attempt to handle -+ * this gracefully anyways, even though this should be dead code. -+ */ -+ -+ pending = atomic_read(&rtl->pending.count); -+ if (WARN_ON(pending)) { -+ spin_lock(&rtl->pending.lock); -+ list_for_each_entry_safe(r, n, &rtl->pending.head, node) { -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ /* Ensure state never gets zero. */ -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); -+ -+ list_del(&r->node); -+ list_add_tail(&r->node, &claimed); -+ } -+ spin_unlock(&rtl->pending.lock); -+ } -+ -+ /* Finally, cancel and complete the requests we claimed before. */ -+ list_for_each_entry_safe(r, n, &claimed, node) { -+ /* -+ * We need test_and_set() because we still might compete with -+ * cancellation. -+ */ -+ if (!test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ ssh_rtl_complete_with_status(r, -ESHUTDOWN); -+ -+ /* -+ * Drop the reference we've obtained by removing it from the -+ * lists. -+ */ -+ list_del(&r->node); -+ ssh_request_put(r); -+ } -+} -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h -new file mode 100644 -index 000000000000..cb35815858d1 ---- /dev/null -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.h -@@ -0,0 +1,143 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * SSH request transport layer. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H -+#define _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "ssh_packet_layer.h" -+ -+/** -+ * enum ssh_rtl_state_flags - State-flags for &struct ssh_rtl. -+ * -+ * @SSH_RTL_SF_SHUTDOWN_BIT: -+ * Indicates that the request transport layer has been shut down or is -+ * being shut down and should not accept any new requests. -+ */ -+enum ssh_rtl_state_flags { -+ SSH_RTL_SF_SHUTDOWN_BIT, -+}; -+ -+/** -+ * struct ssh_rtl_ops - Callback operations for request transport layer. -+ * @handle_event: Function called when a SSH event has been received. The -+ * specified function takes the request layer, received command -+ * struct, and corresponding payload as arguments. If the event -+ * has no payload, the payload span is empty (not %NULL). -+ */ -+struct ssh_rtl_ops { -+ void (*handle_event)(struct ssh_rtl *rtl, const struct ssh_command *cmd, -+ const struct ssam_span *data); -+}; -+ -+/** -+ * struct ssh_rtl - SSH request transport layer. -+ * @ptl: Underlying packet transport layer. -+ * @state: State(-flags) of the transport layer. -+ * @queue: Request submission queue. -+ * @queue.lock: Lock for modifying the request submission queue. -+ * @queue.head: List-head of the request submission queue. -+ * @pending: Set/list of pending requests. -+ * @pending.lock: Lock for modifying the request set. -+ * @pending.head: List-head of the pending set/list. -+ * @pending.count: Number of currently pending requests. -+ * @tx: Transmitter subsystem. -+ * @tx.work: Transmitter work item. -+ * @rtx_timeout: Retransmission timeout subsystem. -+ * @rtx_timeout.lock: Lock for modifying the retransmission timeout reaper. -+ * @rtx_timeout.timeout: Timeout interval for retransmission. -+ * @rtx_timeout.expires: Time specifying when the reaper work is next scheduled. -+ * @rtx_timeout.reaper: Work performing timeout checks and subsequent actions. -+ * @ops: Request layer operations. -+ */ -+struct ssh_rtl { -+ struct ssh_ptl ptl; -+ unsigned long state; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ } queue; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ atomic_t count; -+ } pending; -+ -+ struct { -+ struct work_struct work; -+ } tx; -+ -+ struct { -+ spinlock_t lock; -+ ktime_t timeout; -+ ktime_t expires; -+ struct delayed_work reaper; -+ } rtx_timeout; -+ -+ struct ssh_rtl_ops ops; -+}; -+ -+#define rtl_dbg(r, fmt, ...) ptl_dbg(&(r)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_info(p, fmt, ...) ptl_info(&(p)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_warn(r, fmt, ...) ptl_warn(&(r)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_err(r, fmt, ...) ptl_err(&(r)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_dbg_cond(r, fmt, ...) __ssam_prcond(rtl_dbg, r, fmt, ##__VA_ARGS__) -+ -+#define to_ssh_rtl(ptr, member) \ -+ container_of(ptr, struct ssh_rtl, member) -+ -+/** -+ * ssh_rtl_get_device() - Get device associated with request transport layer. -+ * @rtl: The request transport layer. -+ * -+ * Return: Returns the device on which the given request transport layer -+ * builds upon. -+ */ -+static inline struct device *ssh_rtl_get_device(struct ssh_rtl *rtl) -+{ -+ return ssh_ptl_get_device(&rtl->ptl); -+} -+ -+/** -+ * ssh_request_rtl() - Get request transport layer associated with request. -+ * @rqst: The request to get the request transport layer reference for. -+ * -+ * Return: Returns the &struct ssh_rtl associated with the given SSH request. -+ */ -+static inline struct ssh_rtl *ssh_request_rtl(struct ssh_request *rqst) -+{ -+ struct ssh_ptl *ptl; -+ -+ ptl = READ_ONCE(rqst->packet.ptl); -+ return likely(ptl) ? to_ssh_rtl(ptl, ptl) : NULL; -+} -+ -+int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst); -+bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending); -+ -+int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev, -+ const struct ssh_rtl_ops *ops); -+ -+int ssh_rtl_start(struct ssh_rtl *rtl); -+int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout); -+void ssh_rtl_shutdown(struct ssh_rtl *rtl); -+void ssh_rtl_destroy(struct ssh_rtl *rtl); -+ -+int ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags, -+ const struct ssh_request_ops *ops); -+ -+#endif /* _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H */ -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -new file mode 100644 -index 000000000000..f4b1ba887384 ---- /dev/null -+++ b/include/linux/surface_aggregator/controller.h -@@ -0,0 +1,824 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Surface System Aggregator Module (SSAM) controller interface. -+ * -+ * Main communication interface for the SSAM EC. Provides a controller -+ * managing access and communication to and from the SSAM EC, as well as main -+ * communication structures and definitions. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H -+#define _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H -+ -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- Main data types and definitions --------------------------------------- */ -+ -+/** -+ * enum ssam_event_flags - Flags for enabling/disabling SAM events -+ * @SSAM_EVENT_SEQUENCED: The event will be sent via a sequenced data frame. -+ */ -+enum ssam_event_flags { -+ SSAM_EVENT_SEQUENCED = BIT(0), -+}; -+ -+/** -+ * struct ssam_event - SAM event sent from the EC to the host. -+ * @target_category: Target category of the event source. See &enum ssam_ssh_tc. -+ * @target_id: Target ID of the event source. -+ * @command_id: Command ID of the event. -+ * @instance_id: Instance ID of the event source. -+ * @length: Length of the event payload in bytes. -+ * @data: Event payload data. -+ */ -+struct ssam_event { -+ u8 target_category; -+ u8 target_id; -+ u8 command_id; -+ u8 instance_id; -+ u16 length; -+ u8 data[]; -+}; -+ -+/** -+ * enum ssam_request_flags - Flags for SAM requests. -+ * -+ * @SSAM_REQUEST_HAS_RESPONSE: -+ * Specifies that the request expects a response. If not set, the request -+ * will be directly completed after its underlying packet has been -+ * transmitted. If set, the request transport system waits for a response -+ * of the request. -+ * -+ * @SSAM_REQUEST_UNSEQUENCED: -+ * Specifies that the request should be transmitted via an unsequenced -+ * packet. If set, the request must not have a response, meaning that this -+ * flag and the %SSAM_REQUEST_HAS_RESPONSE flag are mutually exclusive. -+ */ -+enum ssam_request_flags { -+ SSAM_REQUEST_HAS_RESPONSE = BIT(0), -+ SSAM_REQUEST_UNSEQUENCED = BIT(1), -+}; -+ -+/** -+ * struct ssam_request - SAM request description. -+ * @target_category: Category of the request's target. See &enum ssam_ssh_tc. -+ * @target_id: ID of the request's target. -+ * @command_id: Command ID of the request. -+ * @instance_id: Instance ID of the request's target. -+ * @flags: Flags for the request. See &enum ssam_request_flags. -+ * @length: Length of the request payload in bytes. -+ * @payload: Request payload data. -+ * -+ * This struct fully describes a SAM request with payload. It is intended to -+ * help set up the actual transport struct, e.g. &struct ssam_request_sync, -+ * and specifically its raw message data via ssam_request_write_data(). -+ */ -+struct ssam_request { -+ u8 target_category; -+ u8 target_id; -+ u8 command_id; -+ u8 instance_id; -+ u16 flags; -+ u16 length; -+ const u8 *payload; -+}; -+ -+/** -+ * struct ssam_response - Response buffer for SAM request. -+ * @capacity: Capacity of the buffer, in bytes. -+ * @length: Length of the actual data stored in the memory pointed to by -+ * @pointer, in bytes. Set by the transport system. -+ * @pointer: Pointer to the buffer's memory, storing the response payload data. -+ */ -+struct ssam_response { -+ size_t capacity; -+ size_t length; -+ u8 *pointer; -+}; -+ -+struct ssam_controller; -+ -+struct ssam_controller *ssam_get_controller(void); -+struct ssam_controller *ssam_client_bind(struct device *client); -+int ssam_client_link(struct ssam_controller *ctrl, struct device *client); -+ -+struct device *ssam_controller_device(struct ssam_controller *c); -+ -+struct ssam_controller *ssam_controller_get(struct ssam_controller *c); -+void ssam_controller_put(struct ssam_controller *c); -+ -+void ssam_controller_statelock(struct ssam_controller *c); -+void ssam_controller_stateunlock(struct ssam_controller *c); -+ -+ssize_t ssam_request_write_data(struct ssam_span *buf, -+ struct ssam_controller *ctrl, -+ const struct ssam_request *spec); -+ -+ -+/* -- Synchronous request interface. ---------------------------------------- */ -+ -+/** -+ * struct ssam_request_sync - Synchronous SAM request struct. -+ * @base: Underlying SSH request. -+ * @comp: Completion used to signal full completion of the request. After the -+ * request has been submitted, this struct may only be modified or -+ * deallocated after the completion has been signaled. -+ * request has been submitted, -+ * @resp: Buffer to store the response. -+ * @status: Status of the request, set after the base request has been -+ * completed or has failed. -+ */ -+struct ssam_request_sync { -+ struct ssh_request base; -+ struct completion comp; -+ struct ssam_response *resp; -+ int status; -+}; -+ -+int ssam_request_sync_alloc(size_t payload_len, gfp_t flags, -+ struct ssam_request_sync **rqst, -+ struct ssam_span *buffer); -+ -+void ssam_request_sync_free(struct ssam_request_sync *rqst); -+ -+int ssam_request_sync_init(struct ssam_request_sync *rqst, -+ enum ssam_request_flags flags); -+ -+/** -+ * ssam_request_sync_set_data - Set message data of a synchronous request. -+ * @rqst: The request. -+ * @ptr: Pointer to the request message data. -+ * @len: Length of the request message data. -+ * -+ * Set the request message data of a synchronous request. The provided buffer -+ * needs to live until the request has been completed. -+ */ -+static inline void ssam_request_sync_set_data(struct ssam_request_sync *rqst, -+ u8 *ptr, size_t len) -+{ -+ ssh_request_set_data(&rqst->base, ptr, len); -+} -+ -+/** -+ * ssam_request_sync_set_resp - Set response buffer of a synchronous request. -+ * @rqst: The request. -+ * @resp: The response buffer. -+ * -+ * Sets the response buffer of a synchronous request. This buffer will store -+ * the response of the request after it has been completed. May be %NULL if no -+ * response is expected. -+ */ -+static inline void ssam_request_sync_set_resp(struct ssam_request_sync *rqst, -+ struct ssam_response *resp) -+{ -+ rqst->resp = resp; -+} -+ -+int ssam_request_sync_submit(struct ssam_controller *ctrl, -+ struct ssam_request_sync *rqst); -+ -+/** -+ * ssam_request_sync_wait - Wait for completion of a synchronous request. -+ * @rqst: The request to wait for. -+ * -+ * Wait for completion and release of a synchronous request. After this -+ * function terminates, the request is guaranteed to have left the transport -+ * system. After successful submission of a request, this function must be -+ * called before accessing the response of the request, freeing the request, -+ * or freeing any of the buffers associated with the request. -+ * -+ * This function must not be called if the request has not been submitted yet -+ * and may lead to a deadlock/infinite wait if a subsequent request submission -+ * fails in that case, due to the completion never triggering. -+ * -+ * Return: Returns the status of the given request, which is set on completion -+ * of the packet. This value is zero on success and negative on failure. -+ */ -+static inline int ssam_request_sync_wait(struct ssam_request_sync *rqst) -+{ -+ wait_for_completion(&rqst->comp); -+ return rqst->status; -+} -+ -+int ssam_request_sync(struct ssam_controller *ctrl, -+ const struct ssam_request *spec, -+ struct ssam_response *rsp); -+ -+int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, -+ const struct ssam_request *spec, -+ struct ssam_response *rsp, -+ struct ssam_span *buf); -+ -+/** -+ * ssam_request_sync_onstack - Execute a synchronous request on the stack. -+ * @ctrl: The controller via which the request is submitted. -+ * @rqst: The request specification. -+ * @rsp: The response buffer. -+ * @payload_len: The (maximum) request payload length. -+ * -+ * Allocates a synchronous request with specified payload length on the stack, -+ * fully initializes it via the provided request specification, submits it, -+ * and finally waits for its completion before returning its status. This -+ * helper macro essentially allocates the request message buffer on the stack -+ * and then calls ssam_request_sync_with_buffer(). -+ * -+ * Note: The @payload_len parameter specifies the maximum payload length, used -+ * for buffer allocation. The actual payload length may be smaller. -+ * -+ * Return: Returns the status of the request or any failure during setup, i.e. -+ * zero on success and a negative value on failure. -+ */ -+#define ssam_request_sync_onstack(ctrl, rqst, rsp, payload_len) \ -+ ({ \ -+ u8 __data[SSH_COMMAND_MESSAGE_LENGTH(payload_len)]; \ -+ struct ssam_span __buf = { &__data[0], ARRAY_SIZE(__data) }; \ -+ \ -+ ssam_request_sync_with_buffer(ctrl, rqst, rsp, &__buf); \ -+ }) -+ -+/** -+ * __ssam_retry - Retry request in case of I/O errors or timeouts. -+ * @request: The request function to execute. Must return an integer. -+ * @n: Number of tries. -+ * @args: Arguments for the request function. -+ * -+ * Executes the given request function, i.e. calls @request. In case the -+ * request returns %-EREMOTEIO (indicates I/O error) or %-ETIMEDOUT (request -+ * or underlying packet timed out), @request will be re-executed again, up to -+ * @n times in total. -+ * -+ * Return: Returns the return value of the last execution of @request. -+ */ -+#define __ssam_retry(request, n, args...) \ -+ ({ \ -+ int __i, __s = 0; \ -+ \ -+ for (__i = (n); __i > 0; __i--) { \ -+ __s = request(args); \ -+ if (__s != -ETIMEDOUT && __s != -EREMOTEIO) \ -+ break; \ -+ } \ -+ __s; \ -+ }) -+ -+/** -+ * ssam_retry - Retry request in case of I/O errors or timeouts up to three -+ * times in total. -+ * @request: The request function to execute. Must return an integer. -+ * @args: Arguments for the request function. -+ * -+ * Executes the given request function, i.e. calls @request. In case the -+ * request returns %-EREMOTEIO (indicates I/O error) or -%ETIMEDOUT (request -+ * or underlying packet timed out), @request will be re-executed again, up to -+ * three times in total. -+ * -+ * See __ssam_retry() for a more generic macro for this purpose. -+ * -+ * Return: Returns the return value of the last execution of @request. -+ */ -+#define ssam_retry(request, args...) \ -+ __ssam_retry(request, 3, args) -+ -+/** -+ * struct ssam_request_spec - Blue-print specification of SAM request. -+ * @target_category: Category of the request's target. See &enum ssam_ssh_tc. -+ * @target_id: ID of the request's target. -+ * @command_id: Command ID of the request. -+ * @instance_id: Instance ID of the request's target. -+ * @flags: Flags for the request. See &enum ssam_request_flags. -+ * -+ * Blue-print specification for a SAM request. This struct describes the -+ * unique static parameters of a request (i.e. type) without specifying any of -+ * its instance-specific data (e.g. payload). It is intended to be used as base -+ * for defining simple request functions via the -+ * ``SSAM_DEFINE_SYNC_REQUEST_x()`` family of macros. -+ */ -+struct ssam_request_spec { -+ u8 target_category; -+ u8 target_id; -+ u8 command_id; -+ u8 instance_id; -+ u8 flags; -+}; -+ -+/** -+ * struct ssam_request_spec_md - Blue-print specification for multi-device SAM -+ * request. -+ * @target_category: Category of the request's target. See &enum ssam_ssh_tc. -+ * @command_id: Command ID of the request. -+ * @flags: Flags for the request. See &enum ssam_request_flags. -+ * -+ * Blue-print specification for a multi-device SAM request, i.e. a request -+ * that is applicable to multiple device instances, described by their -+ * individual target and instance IDs. This struct describes the unique static -+ * parameters of a request (i.e. type) without specifying any of its -+ * instance-specific data (e.g. payload) and without specifying any of its -+ * device specific IDs (i.e. target and instance ID). It is intended to be -+ * used as base for defining simple multi-device request functions via the -+ * ``SSAM_DEFINE_SYNC_REQUEST_MD_x()`` and ``SSAM_DEFINE_SYNC_REQUEST_CL_x()`` -+ * families of macros. -+ */ -+struct ssam_request_spec_md { -+ u8 target_category; -+ u8 command_id; -+ u8 flags; -+}; -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_N() - Define synchronous SAM request function -+ * with neither argument nor return value. -+ * @name: Name of the generated function. -+ * @spec: Specification (&struct ssam_request_spec) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request having neither argument nor return value. The -+ * generated function takes care of setting up the request struct and buffer -+ * allocation, as well as execution of the request itself, returning once the -+ * request has been fully completed. The required transport buffer will be -+ * allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_controller -+ * *ctrl)``, returning the status of the request, which is zero on success and -+ * negative on failure. The ``ctrl`` parameter is the controller via which the -+ * request is being sent. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_N(name, spec...) \ -+ int name(struct ssam_controller *ctrl) \ -+ { \ -+ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = s.target_id; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = s.instance_id; \ -+ rqst.flags = s.flags; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, 0); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_W() - Define synchronous SAM request function with -+ * argument. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @spec: Specification (&struct ssam_request_spec) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking an argument of type @atype and having no -+ * return value. The generated function takes care of setting up the request -+ * struct, buffer allocation, as well as execution of the request itself, -+ * returning once the request has been fully completed. The required transport -+ * buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_controller -+ * *ctrl, const atype *arg)``, returning the status of the request, which is -+ * zero on success and negative on failure. The ``ctrl`` parameter is the -+ * controller via which the request is sent. The request argument is specified -+ * via the ``arg`` pointer. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_W(name, atype, spec...) \ -+ int name(struct ssam_controller *ctrl, const atype *arg) \ -+ { \ -+ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = s.target_id; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = s.instance_id; \ -+ rqst.flags = s.flags; \ -+ rqst.length = sizeof(atype); \ -+ rqst.payload = (u8 *)arg; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, \ -+ sizeof(atype)); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_R() - Define synchronous SAM request function with -+ * return value. -+ * @name: Name of the generated function. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking no argument but having a return value of -+ * type @rtype. The generated function takes care of setting up the request -+ * and response structs, buffer allocation, as well as execution of the -+ * request itself, returning once the request has been fully completed. The -+ * required transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_controller -+ * *ctrl, rtype *ret)``, returning the status of the request, which is zero on -+ * success and negative on failure. The ``ctrl`` parameter is the controller -+ * via which the request is sent. The request's return value is written to the -+ * memory pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_R(name, rtype, spec...) \ -+ int name(struct ssam_controller *ctrl, rtype *ret) \ -+ { \ -+ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ -+ struct ssam_request rqst; \ -+ struct ssam_response rsp; \ -+ int status; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = s.target_id; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = s.instance_id; \ -+ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ rsp.capacity = sizeof(rtype); \ -+ rsp.length = 0; \ -+ rsp.pointer = (u8 *)ret; \ -+ \ -+ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, 0); \ -+ if (status) \ -+ return status; \ -+ \ -+ if (rsp.length != sizeof(rtype)) { \ -+ struct device *dev = ssam_controller_device(ctrl); \ -+ dev_err(dev, \ -+ "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ -+ sizeof(rtype), rsp.length, rqst.target_category,\ -+ rqst.command_id); \ -+ return -EIO; \ -+ } \ -+ \ -+ return 0; \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_MD_N() - Define synchronous multi-device SAM -+ * request function with neither argument nor return value. -+ * @name: Name of the generated function. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request having neither argument nor return value. Device -+ * specifying parameters are not hard-coded, but instead must be provided to -+ * the function. The generated function takes care of setting up the request -+ * struct, buffer allocation, as well as execution of the request itself, -+ * returning once the request has been fully completed. The required transport -+ * buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_controller -+ * *ctrl, u8 tid, u8 iid)``, returning the status of the request, which is -+ * zero on success and negative on failure. The ``ctrl`` parameter is the -+ * controller via which the request is sent, ``tid`` the target ID for the -+ * request, and ``iid`` the instance ID. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_MD_N(name, spec...) \ -+ int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \ -+ { \ -+ struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = tid; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = iid; \ -+ rqst.flags = s.flags; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, 0); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_MD_W() - Define synchronous multi-device SAM -+ * request function with argument. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking an argument of type @atype and having no -+ * return value. Device specifying parameters are not hard-coded, but instead -+ * must be provided to the function. The generated function takes care of -+ * setting up the request struct, buffer allocation, as well as execution of -+ * the request itself, returning once the request has been fully completed. -+ * The required transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_controller -+ * *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the status of the -+ * request, which is zero on success and negative on failure. The ``ctrl`` -+ * parameter is the controller via which the request is sent, ``tid`` the -+ * target ID for the request, and ``iid`` the instance ID. The request argument -+ * is specified via the ``arg`` pointer. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_MD_W(name, atype, spec...) \ -+ int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)\ -+ { \ -+ struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = tid; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = iid; \ -+ rqst.flags = s.flags; \ -+ rqst.length = sizeof(atype); \ -+ rqst.payload = (u8 *)arg; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, \ -+ sizeof(atype)); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_MD_R() - Define synchronous multi-device SAM -+ * request function with return value. -+ * @name: Name of the generated function. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking no argument but having a return value of -+ * type @rtype. Device specifying parameters are not hard-coded, but instead -+ * must be provided to the function. The generated function takes care of -+ * setting up the request and response structs, buffer allocation, as well as -+ * execution of the request itself, returning once the request has been fully -+ * completed. The required transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_controller -+ * *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status of the request, -+ * which is zero on success and negative on failure. The ``ctrl`` parameter is -+ * the controller via which the request is sent, ``tid`` the target ID for the -+ * request, and ``iid`` the instance ID. The request's return value is written -+ * to the memory pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_MD_R(name, rtype, spec...) \ -+ int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \ -+ { \ -+ struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ -+ struct ssam_request rqst; \ -+ struct ssam_response rsp; \ -+ int status; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = tid; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = iid; \ -+ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ rsp.capacity = sizeof(rtype); \ -+ rsp.length = 0; \ -+ rsp.pointer = (u8 *)ret; \ -+ \ -+ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, 0); \ -+ if (status) \ -+ return status; \ -+ \ -+ if (rsp.length != sizeof(rtype)) { \ -+ struct device *dev = ssam_controller_device(ctrl); \ -+ dev_err(dev, \ -+ "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ -+ sizeof(rtype), rsp.length, rqst.target_category,\ -+ rqst.command_id); \ -+ return -EIO; \ -+ } \ -+ \ -+ return 0; \ -+ } -+ -+ -+/* -- Event notifier/callbacks. --------------------------------------------- */ -+ -+#define SSAM_NOTIF_STATE_SHIFT 2 -+#define SSAM_NOTIF_STATE_MASK ((1 << SSAM_NOTIF_STATE_SHIFT) - 1) -+ -+/** -+ * enum ssam_notif_flags - Flags used in return values from SSAM notifier -+ * callback functions. -+ * -+ * @SSAM_NOTIF_HANDLED: -+ * Indicates that the notification has been handled. This flag should be -+ * set by the handler if the handler can act/has acted upon the event -+ * provided to it. This flag should not be set if the handler is not a -+ * primary handler intended for the provided event. -+ * -+ * If this flag has not been set by any handler after the notifier chain -+ * has been traversed, a warning will be emitted, stating that the event -+ * has not been handled. -+ * -+ * @SSAM_NOTIF_STOP: -+ * Indicates that the notifier traversal should stop. If this flag is -+ * returned from a notifier callback, notifier chain traversal will -+ * immediately stop and any remaining notifiers will not be called. This -+ * flag is automatically set when ssam_notifier_from_errno() is called -+ * with a negative error value. -+ */ -+enum ssam_notif_flags { -+ SSAM_NOTIF_HANDLED = BIT(0), -+ SSAM_NOTIF_STOP = BIT(1), -+}; -+ -+struct ssam_event_notifier; -+ -+typedef u32 (*ssam_notifier_fn_t)(struct ssam_event_notifier *nf, -+ const struct ssam_event *event); -+ -+/** -+ * struct ssam_notifier_block - Base notifier block for SSAM event -+ * notifications. -+ * @node: The node for the list of notifiers. -+ * @fn: The callback function of this notifier. This function takes the -+ * respective notifier block and event as input and should return -+ * a notifier value, which can either be obtained from the flags -+ * provided in &enum ssam_notif_flags, converted from a standard -+ * error value via ssam_notifier_from_errno(), or a combination of -+ * both (e.g. ``ssam_notifier_from_errno(e) | SSAM_NOTIF_HANDLED``). -+ * @priority: Priority value determining the order in which notifier callbacks -+ * will be called. A higher value means higher priority, i.e. the -+ * associated callback will be executed earlier than other (lower -+ * priority) callbacks. -+ */ -+struct ssam_notifier_block { -+ struct list_head node; -+ ssam_notifier_fn_t fn; -+ int priority; -+}; -+ -+/** -+ * ssam_notifier_from_errno() - Convert standard error value to notifier -+ * return code. -+ * @err: The error code to convert, must be negative (in case of failure) or -+ * zero (in case of success). -+ * -+ * Return: Returns the notifier return value obtained by converting the -+ * specified @err value. In case @err is negative, the %SSAM_NOTIF_STOP flag -+ * will be set, causing notifier call chain traversal to abort. -+ */ -+static inline u32 ssam_notifier_from_errno(int err) -+{ -+ if (WARN_ON(err > 0) || err == 0) -+ return 0; -+ else -+ return ((-err) << SSAM_NOTIF_STATE_SHIFT) | SSAM_NOTIF_STOP; -+} -+ -+/** -+ * ssam_notifier_to_errno() - Convert notifier return code to standard error -+ * value. -+ * @ret: The notifier return value to convert. -+ * -+ * Return: Returns the negative error value encoded in @ret or zero if @ret -+ * indicates success. -+ */ -+static inline int ssam_notifier_to_errno(u32 ret) -+{ -+ return -(ret >> SSAM_NOTIF_STATE_SHIFT); -+} -+ -+ -+/* -- Event/notification registry. ------------------------------------------ */ -+ -+/** -+ * struct ssam_event_registry - Registry specification used for enabling events. -+ * @target_category: Target category for the event registry requests. -+ * @target_id: Target ID for the event registry requests. -+ * @cid_enable: Command ID for the event-enable request. -+ * @cid_disable: Command ID for the event-disable request. -+ * -+ * This struct describes a SAM event registry via the minimal collection of -+ * SAM IDs specifying the requests to use for enabling and disabling an event. -+ * The individual event to be enabled/disabled itself is specified via &struct -+ * ssam_event_id. -+ */ -+struct ssam_event_registry { -+ u8 target_category; -+ u8 target_id; -+ u8 cid_enable; -+ u8 cid_disable; -+}; -+ -+/** -+ * struct ssam_event_id - Unique event ID used for enabling events. -+ * @target_category: Target category of the event source. -+ * @instance: Instance ID of the event source. -+ * -+ * This struct specifies the event to be enabled/disabled via an externally -+ * provided registry. It does not specify the registry to be used itself, this -+ * is done via &struct ssam_event_registry. -+ */ -+struct ssam_event_id { -+ u8 target_category; -+ u8 instance; -+}; -+ -+/** -+ * enum ssam_event_mask - Flags specifying how events are matched to notifiers. -+ * -+ * @SSAM_EVENT_MASK_NONE: -+ * Run the callback for any event with matching target category. Do not -+ * do any additional filtering. -+ * -+ * @SSAM_EVENT_MASK_TARGET: -+ * In addition to filtering by target category, only execute the notifier -+ * callback for events with a target ID matching to the one of the -+ * registry used for enabling/disabling the event. -+ * -+ * @SSAM_EVENT_MASK_INSTANCE: -+ * In addition to filtering by target category, only execute the notifier -+ * callback for events with an instance ID matching to the instance ID -+ * used when enabling the event. -+ * -+ * @SSAM_EVENT_MASK_STRICT: -+ * Do all the filtering above. -+ */ -+enum ssam_event_mask { -+ SSAM_EVENT_MASK_TARGET = BIT(0), -+ SSAM_EVENT_MASK_INSTANCE = BIT(1), -+ -+ SSAM_EVENT_MASK_NONE = 0, -+ SSAM_EVENT_MASK_STRICT = -+ SSAM_EVENT_MASK_TARGET -+ | SSAM_EVENT_MASK_INSTANCE, -+}; -+ -+/** -+ * SSAM_EVENT_REGISTRY() - Define a new event registry. -+ * @tc: Target category for the event registry requests. -+ * @tid: Target ID for the event registry requests. -+ * @cid_en: Command ID for the event-enable request. -+ * @cid_dis: Command ID for the event-disable request. -+ * -+ * Return: Returns the &struct ssam_event_registry specified by the given -+ * parameters. -+ */ -+#define SSAM_EVENT_REGISTRY(tc, tid, cid_en, cid_dis) \ -+ ((struct ssam_event_registry) { \ -+ .target_category = (tc), \ -+ .target_id = (tid), \ -+ .cid_enable = (cid_en), \ -+ .cid_disable = (cid_dis), \ -+ }) -+ -+#define SSAM_EVENT_REGISTRY_SAM \ -+ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_SAM, 0x01, 0x0b, 0x0c) -+ -+#define SSAM_EVENT_REGISTRY_KIP \ -+ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_KIP, 0x02, 0x27, 0x28) -+ -+#define SSAM_EVENT_REGISTRY_REG \ -+ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02) -+ -+/** -+ * struct ssam_event_notifier - Notifier block for SSAM events. -+ * @base: The base notifier block with callback function and priority. -+ * @event: The event for which this block will receive notifications. -+ * @event.reg: Registry via which the event will be enabled/disabled. -+ * @event.id: ID specifying the event. -+ * @event.mask: Flags determining how events are matched to the notifier. -+ * @event.flags: Flags used for enabling the event. -+ */ -+struct ssam_event_notifier { -+ struct ssam_notifier_block base; -+ -+ struct { -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ enum ssam_event_mask mask; -+ u8 flags; -+ } event; -+}; -+ -+int ssam_notifier_register(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n); -+ -+int ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n); -+ -+#endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */ -diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h -new file mode 100644 -index 000000000000..64276fbfa1d5 ---- /dev/null -+++ b/include/linux/surface_aggregator/serial_hub.h -@@ -0,0 +1,672 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Surface Serial Hub (SSH) protocol and communication interface. -+ * -+ * Lower-level communication layers and SSH protocol definitions for the -+ * Surface System Aggregator Module (SSAM). Provides the interface for basic -+ * packet- and request-based communication with the SSAM EC via SSH. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H -+#define _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H -+ -+#include -+#include -+#include -+#include -+#include -+ -+ -+/* -- Data structures for SAM-over-SSH communication. ----------------------- */ -+ -+/** -+ * enum ssh_frame_type - Frame types for SSH frames. -+ * -+ * @SSH_FRAME_TYPE_DATA_SEQ: -+ * Indicates a data frame, followed by a payload with the length specified -+ * in the ``struct ssh_frame.len`` field. This frame is sequenced, meaning -+ * that an ACK is required. -+ * -+ * @SSH_FRAME_TYPE_DATA_NSQ: -+ * Same as %SSH_FRAME_TYPE_DATA_SEQ, but unsequenced, meaning that the -+ * message does not have to be ACKed. -+ * -+ * @SSH_FRAME_TYPE_ACK: -+ * Indicates an ACK message. -+ * -+ * @SSH_FRAME_TYPE_NAK: -+ * Indicates an error response for previously sent frame. In general, this -+ * means that the frame and/or payload is malformed, e.g. a CRC is wrong. -+ * For command-type payloads, this can also mean that the command is -+ * invalid. -+ */ -+enum ssh_frame_type { -+ SSH_FRAME_TYPE_DATA_SEQ = 0x80, -+ SSH_FRAME_TYPE_DATA_NSQ = 0x00, -+ SSH_FRAME_TYPE_ACK = 0x40, -+ SSH_FRAME_TYPE_NAK = 0x04, -+}; -+ -+/** -+ * struct ssh_frame - SSH communication frame. -+ * @type: The type of the frame. See &enum ssh_frame_type. -+ * @len: The length of the frame payload directly following the CRC for this -+ * frame. Does not include the final CRC for that payload. -+ * @seq: The sequence number for this message/exchange. -+ */ -+struct ssh_frame { -+ u8 type; -+ __le16 len; -+ u8 seq; -+} __packed; -+ -+static_assert(sizeof(struct ssh_frame) == 4); -+ -+/* -+ * SSH_FRAME_MAX_PAYLOAD_SIZE - Maximum SSH frame payload length in bytes. -+ * -+ * This is the physical maximum length of the protocol. Implementations may -+ * set a more constrained limit. -+ */ -+#define SSH_FRAME_MAX_PAYLOAD_SIZE U16_MAX -+ -+/** -+ * enum ssh_payload_type - Type indicator for the SSH payload. -+ * @SSH_PLD_TYPE_CMD: The payload is a command structure with optional command -+ * payload. -+ */ -+enum ssh_payload_type { -+ SSH_PLD_TYPE_CMD = 0x80, -+}; -+ -+/** -+ * struct ssh_command - Payload of a command-type frame. -+ * @type: The type of the payload. See &enum ssh_payload_type. Should be -+ * SSH_PLD_TYPE_CMD for this struct. -+ * @tc: Command target category. -+ * @tid_out: Output target ID. Should be zero if this an incoming (EC to host) -+ * message. -+ * @tid_in: Input target ID. Should be zero if this is an outgoing (host to -+ * EC) message. -+ * @iid: Instance ID. -+ * @rqid: Request ID. Used to match requests with responses and differentiate -+ * between responses and events. -+ * @cid: Command ID. -+ */ -+struct ssh_command { -+ u8 type; -+ u8 tc; -+ u8 tid_out; -+ u8 tid_in; -+ u8 iid; -+ __le16 rqid; -+ u8 cid; -+} __packed; -+ -+static_assert(sizeof(struct ssh_command) == 8); -+ -+/* -+ * SSH_COMMAND_MAX_PAYLOAD_SIZE - Maximum SSH command payload length in bytes. -+ * -+ * This is the physical maximum length of the protocol. Implementations may -+ * set a more constrained limit. -+ */ -+#define SSH_COMMAND_MAX_PAYLOAD_SIZE \ -+ (SSH_FRAME_MAX_PAYLOAD_SIZE - sizeof(struct ssh_command)) -+ -+/* -+ * SSH_MSG_LEN_BASE - Base-length of a SSH message. -+ * -+ * This is the minimum number of bytes required to form a message. The actual -+ * message length is SSH_MSG_LEN_BASE plus the length of the frame payload. -+ */ -+#define SSH_MSG_LEN_BASE (sizeof(struct ssh_frame) + 3ull * sizeof(u16)) -+ -+/* -+ * SSH_MSG_LEN_CTRL - Length of a SSH control message. -+ * -+ * This is the length of a SSH control message, which is equal to a SSH -+ * message without any payload. -+ */ -+#define SSH_MSG_LEN_CTRL SSH_MSG_LEN_BASE -+ -+/** -+ * SSH_MESSAGE_LENGTH() - Compute length of SSH message. -+ * @payload_size: Length of the payload inside the SSH frame. -+ * -+ * Return: Returns the length of a SSH message with payload of specified size. -+ */ -+#define SSH_MESSAGE_LENGTH(payload_size) (SSH_MSG_LEN_BASE + (payload_size)) -+ -+/** -+ * SSH_COMMAND_MESSAGE_LENGTH() - Compute length of SSH command message. -+ * @payload_size: Length of the command payload. -+ * -+ * Return: Returns the length of a SSH command message with command payload of -+ * specified size. -+ */ -+#define SSH_COMMAND_MESSAGE_LENGTH(payload_size) \ -+ SSH_MESSAGE_LENGTH(sizeof(struct ssh_command) + (payload_size)) -+ -+/** -+ * SSH_MSGOFFSET_FRAME() - Compute offset in SSH message to specified field in -+ * frame. -+ * @field: The field for which the offset should be computed. -+ * -+ * Return: Returns the offset of the specified &struct ssh_frame field in the -+ * raw SSH message data as. Takes SYN bytes (u16) preceding the frame into -+ * account. -+ */ -+#define SSH_MSGOFFSET_FRAME(field) \ -+ (sizeof(u16) + offsetof(struct ssh_frame, field)) -+ -+/** -+ * SSH_MSGOFFSET_COMMAND() - Compute offset in SSH message to specified field -+ * in command. -+ * @field: The field for which the offset should be computed. -+ * -+ * Return: Returns the offset of the specified &struct ssh_command field in -+ * the raw SSH message data. Takes SYN bytes (u16) preceding the frame and the -+ * frame CRC (u16) between frame and command into account. -+ */ -+#define SSH_MSGOFFSET_COMMAND(field) \ -+ (2ull * sizeof(u16) + sizeof(struct ssh_frame) \ -+ + offsetof(struct ssh_command, field)) -+ -+/* -+ * SSH_MSG_SYN - SSH message synchronization (SYN) bytes as u16. -+ */ -+#define SSH_MSG_SYN ((u16)0x55aa) -+ -+/** -+ * ssh_crc() - Compute CRC for SSH messages. -+ * @buf: The pointer pointing to the data for which the CRC should be computed. -+ * @len: The length of the data for which the CRC should be computed. -+ * -+ * Return: Returns the CRC computed on the provided data, as used for SSH -+ * messages. -+ */ -+static inline u16 ssh_crc(const u8 *buf, size_t len) -+{ -+ return crc_ccitt_false(0xffff, buf, len); -+} -+ -+/* -+ * SSH_NUM_EVENTS - The number of reserved event IDs. -+ * -+ * The number of reserved event IDs, used for registering an SSH event -+ * handler. Valid event IDs are numbers below or equal to this value, with -+ * exception of zero, which is not an event ID. Thus, this is also the -+ * absolute maximum number of event handlers that can be registered. -+ */ -+#define SSH_NUM_EVENTS 34 -+ -+/* -+ * SSH_NUM_TARGETS - The number of communication targets used in the protocol. -+ */ -+#define SSH_NUM_TARGETS 2 -+ -+/** -+ * ssh_rqid_next_valid() - Return the next valid request ID. -+ * @rqid: The current request ID. -+ * -+ * Return: Returns the next valid request ID, following the current request ID -+ * provided to this function. This function skips any request IDs reserved for -+ * events. -+ */ -+static inline u16 ssh_rqid_next_valid(u16 rqid) -+{ -+ return rqid > 0 ? rqid + 1u : rqid + SSH_NUM_EVENTS + 1u; -+} -+ -+/** -+ * ssh_rqid_to_event() - Convert request ID to its corresponding event ID. -+ * @rqid: The request ID to convert. -+ */ -+static inline u16 ssh_rqid_to_event(u16 rqid) -+{ -+ return rqid - 1u; -+} -+ -+/** -+ * ssh_rqid_is_event() - Check if given request ID is a valid event ID. -+ * @rqid: The request ID to check. -+ */ -+static inline bool ssh_rqid_is_event(u16 rqid) -+{ -+ return ssh_rqid_to_event(rqid) < SSH_NUM_EVENTS; -+} -+ -+/** -+ * ssh_tc_to_rqid() - Convert target category to its corresponding request ID. -+ * @tc: The target category to convert. -+ */ -+static inline u16 ssh_tc_to_rqid(u8 tc) -+{ -+ return tc; -+} -+ -+/** -+ * ssh_tid_to_index() - Convert target ID to its corresponding target index. -+ * @tid: The target ID to convert. -+ */ -+static inline u8 ssh_tid_to_index(u8 tid) -+{ -+ return tid - 1u; -+} -+ -+/** -+ * ssh_tid_is_valid() - Check if target ID is valid/supported. -+ * @tid: The target ID to check. -+ */ -+static inline bool ssh_tid_is_valid(u8 tid) -+{ -+ return ssh_tid_to_index(tid) < SSH_NUM_TARGETS; -+} -+ -+/** -+ * struct ssam_span - Reference to a buffer region. -+ * @ptr: Pointer to the buffer region. -+ * @len: Length of the buffer region. -+ * -+ * A reference to a (non-owned) buffer segment, consisting of pointer and -+ * length. Use of this struct indicates non-owned data, i.e. data of which the -+ * life-time is managed (i.e. it is allocated/freed) via another pointer. -+ */ -+struct ssam_span { -+ u8 *ptr; -+ size_t len; -+}; -+ -+/* -+ * Known SSH/EC target categories. -+ * -+ * List of currently known target category values; "Known" as in we know they -+ * exist and are valid on at least some device/model. Detailed functionality -+ * or the full category name is only known for some of these categories and -+ * is detailed in the respective comment below. -+ * -+ * These values and abbreviations have been extracted from strings inside the -+ * Windows driver. -+ */ -+enum ssam_ssh_tc { -+ /* Category 0x00 is invalid for EC use. */ -+ SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */ -+ SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */ -+ SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */ -+ SSAM_SSH_TC_PMC = 0x04, -+ SSAM_SSH_TC_FAN = 0x05, -+ SSAM_SSH_TC_PoM = 0x06, -+ SSAM_SSH_TC_DBG = 0x07, -+ SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */ -+ SSAM_SSH_TC_FWU = 0x09, -+ SSAM_SSH_TC_UNI = 0x0a, -+ SSAM_SSH_TC_LPC = 0x0b, -+ SSAM_SSH_TC_TCL = 0x0c, -+ SSAM_SSH_TC_SFL = 0x0d, -+ SSAM_SSH_TC_KIP = 0x0e, -+ SSAM_SSH_TC_EXT = 0x0f, -+ SSAM_SSH_TC_BLD = 0x10, -+ SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ -+ SSAM_SSH_TC_SEN = 0x12, -+ SSAM_SSH_TC_SRQ = 0x13, -+ SSAM_SSH_TC_MCU = 0x14, -+ SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */ -+ SSAM_SSH_TC_TCH = 0x16, -+ SSAM_SSH_TC_BKL = 0x17, -+ SSAM_SSH_TC_TAM = 0x18, -+ SSAM_SSH_TC_ACC = 0x19, -+ SSAM_SSH_TC_UFI = 0x1a, -+ SSAM_SSH_TC_USC = 0x1b, -+ SSAM_SSH_TC_PEN = 0x1c, -+ SSAM_SSH_TC_VID = 0x1d, -+ SSAM_SSH_TC_AUD = 0x1e, -+ SSAM_SSH_TC_SMC = 0x1f, -+ SSAM_SSH_TC_KPD = 0x20, -+ SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */ -+}; -+ -+ -+/* -- Packet transport layer (ptl). ----------------------------------------- */ -+ -+/** -+ * enum ssh_packet_base_priority - Base priorities for &struct ssh_packet. -+ * @SSH_PACKET_PRIORITY_FLUSH: Base priority for flush packets. -+ * @SSH_PACKET_PRIORITY_DATA: Base priority for normal data packets. -+ * @SSH_PACKET_PRIORITY_NAK: Base priority for NAK packets. -+ * @SSH_PACKET_PRIORITY_ACK: Base priority for ACK packets. -+ */ -+enum ssh_packet_base_priority { -+ SSH_PACKET_PRIORITY_FLUSH = 0, /* same as DATA to sequence flush */ -+ SSH_PACKET_PRIORITY_DATA = 0, -+ SSH_PACKET_PRIORITY_NAK = 1, -+ SSH_PACKET_PRIORITY_ACK = 2, -+}; -+ -+/* -+ * Same as SSH_PACKET_PRIORITY() below, only with actual values. -+ */ -+#define __SSH_PACKET_PRIORITY(base, try) \ -+ (((base) << 4) | ((try) & 0x0f)) -+ -+/** -+ * SSH_PACKET_PRIORITY() - Compute packet priority from base priority and -+ * number of tries. -+ * @base: The base priority as suffix of &enum ssh_packet_base_priority, e.g. -+ * ``FLUSH``, ``DATA``, ``ACK``, or ``NAK``. -+ * @try: The number of tries (must be less than 16). -+ * -+ * Compute the combined packet priority. The combined priority is dominated by -+ * the base priority, whereas the number of (re-)tries decides the precedence -+ * of packets with the same base priority, giving higher priority to packets -+ * that already have more tries. -+ * -+ * Return: Returns the computed priority as value fitting inside a &u8. A -+ * higher number means a higher priority. -+ */ -+#define SSH_PACKET_PRIORITY(base, try) \ -+ __SSH_PACKET_PRIORITY(SSH_PACKET_PRIORITY_##base, (try)) -+ -+/** -+ * ssh_packet_priority_get_try() - Get number of tries from packet priority. -+ * @priority: The packet priority. -+ * -+ * Return: Returns the number of tries encoded in the specified packet -+ * priority. -+ */ -+static inline u8 ssh_packet_priority_get_try(u8 priority) -+{ -+ return priority & 0x0f; -+} -+ -+/** -+ * ssh_packet_priority_get_base - Get base priority from packet priority. -+ * @priority: The packet priority. -+ * -+ * Return: Returns the base priority encoded in the given packet priority. -+ */ -+static inline u8 ssh_packet_priority_get_base(u8 priority) -+{ -+ return (priority & 0xf0) >> 4; -+} -+ -+enum ssh_packet_flags { -+ /* state flags */ -+ SSH_PACKET_SF_LOCKED_BIT, -+ SSH_PACKET_SF_QUEUED_BIT, -+ SSH_PACKET_SF_PENDING_BIT, -+ SSH_PACKET_SF_TRANSMITTING_BIT, -+ SSH_PACKET_SF_TRANSMITTED_BIT, -+ SSH_PACKET_SF_ACKED_BIT, -+ SSH_PACKET_SF_CANCELED_BIT, -+ SSH_PACKET_SF_COMPLETED_BIT, -+ -+ /* type flags */ -+ SSH_PACKET_TY_FLUSH_BIT, -+ SSH_PACKET_TY_SEQUENCED_BIT, -+ SSH_PACKET_TY_BLOCKING_BIT, -+ -+ /* mask for state flags */ -+ SSH_PACKET_FLAGS_SF_MASK = -+ BIT(SSH_PACKET_SF_LOCKED_BIT) -+ | BIT(SSH_PACKET_SF_QUEUED_BIT) -+ | BIT(SSH_PACKET_SF_PENDING_BIT) -+ | BIT(SSH_PACKET_SF_TRANSMITTING_BIT) -+ | BIT(SSH_PACKET_SF_TRANSMITTED_BIT) -+ | BIT(SSH_PACKET_SF_ACKED_BIT) -+ | BIT(SSH_PACKET_SF_CANCELED_BIT) -+ | BIT(SSH_PACKET_SF_COMPLETED_BIT), -+ -+ /* mask for type flags */ -+ SSH_PACKET_FLAGS_TY_MASK = -+ BIT(SSH_PACKET_TY_FLUSH_BIT) -+ | BIT(SSH_PACKET_TY_SEQUENCED_BIT) -+ | BIT(SSH_PACKET_TY_BLOCKING_BIT), -+}; -+ -+struct ssh_ptl; -+struct ssh_packet; -+ -+/** -+ * struct ssh_packet_ops - Callback operations for a SSH packet. -+ * @release: Function called when the packet reference count reaches zero. -+ * This callback must be relied upon to ensure that the packet has -+ * left the transport system(s). -+ * @complete: Function called when the packet is completed, either with -+ * success or failure. In case of failure, the reason for the -+ * failure is indicated by the value of the provided status code -+ * argument. This value will be zero in case of success. Note that -+ * a call to this callback does not guarantee that the packet is -+ * not in use by the transport system any more. -+ */ -+struct ssh_packet_ops { -+ void (*release)(struct ssh_packet *p); -+ void (*complete)(struct ssh_packet *p, int status); -+}; -+ -+/** -+ * struct ssh_packet - SSH transport packet. -+ * @ptl: Pointer to the packet transport layer. May be %NULL if the packet -+ * (or enclosing request) has not been submitted yet. -+ * @refcnt: Reference count of the packet. -+ * @priority: Priority of the packet. Must be computed via -+ * SSH_PACKET_PRIORITY(). Must only be accessed while holding the -+ * queue lock after first submission. -+ * @data: Raw message data. -+ * @data.len: Length of the raw message data. -+ * @data.ptr: Pointer to the raw message data buffer. -+ * @state: State and type flags describing current packet state (dynamic) -+ * and type (static). See &enum ssh_packet_flags for possible -+ * options. -+ * @timestamp: Timestamp specifying when the latest transmission of a -+ * currently pending packet has been started. May be %KTIME_MAX -+ * before or in-between transmission attempts. Used for the packet -+ * timeout implementation. Must only be accessed while holding the -+ * pending lock after first submission. -+ * @queue_node: The list node for the packet queue. -+ * @pending_node: The list node for the set of pending packets. -+ * @ops: Packet operations. -+ */ -+struct ssh_packet { -+ struct ssh_ptl *ptl; -+ struct kref refcnt; -+ -+ u8 priority; -+ -+ struct { -+ size_t len; -+ u8 *ptr; -+ } data; -+ -+ unsigned long state; -+ ktime_t timestamp; -+ -+ struct list_head queue_node; -+ struct list_head pending_node; -+ -+ const struct ssh_packet_ops *ops; -+}; -+ -+struct ssh_packet *ssh_packet_get(struct ssh_packet *p); -+void ssh_packet_put(struct ssh_packet *p); -+ -+/** -+ * ssh_packet_set_data() - Set raw message data of packet. -+ * @p: The packet for which the message data should be set. -+ * @ptr: Pointer to the memory holding the message data. -+ * @len: Length of the message data. -+ * -+ * Sets the raw message data buffer of the packet to the provided memory. The -+ * memory is not copied. Instead, the caller is responsible for management -+ * (i.e. allocation and deallocation) of the memory. The caller must ensure -+ * that the provided memory is valid and contains a valid SSH message, -+ * starting from the time of submission of the packet until the ``release`` -+ * callback has been called. During this time, the memory may not be altered -+ * in any way. -+ */ -+static inline void ssh_packet_set_data(struct ssh_packet *p, u8 *ptr, size_t len) -+{ -+ p->data.ptr = ptr; -+ p->data.len = len; -+} -+ -+ -+/* -- Request transport layer (rtl). ---------------------------------------- */ -+ -+enum ssh_request_flags { -+ /* state flags */ -+ SSH_REQUEST_SF_LOCKED_BIT, -+ SSH_REQUEST_SF_QUEUED_BIT, -+ SSH_REQUEST_SF_PENDING_BIT, -+ SSH_REQUEST_SF_TRANSMITTING_BIT, -+ SSH_REQUEST_SF_TRANSMITTED_BIT, -+ SSH_REQUEST_SF_RSPRCVD_BIT, -+ SSH_REQUEST_SF_CANCELED_BIT, -+ SSH_REQUEST_SF_COMPLETED_BIT, -+ -+ /* type flags */ -+ SSH_REQUEST_TY_FLUSH_BIT, -+ SSH_REQUEST_TY_HAS_RESPONSE_BIT, -+ -+ /* mask for state flags */ -+ SSH_REQUEST_FLAGS_SF_MASK = -+ BIT(SSH_REQUEST_SF_LOCKED_BIT) -+ | BIT(SSH_REQUEST_SF_QUEUED_BIT) -+ | BIT(SSH_REQUEST_SF_PENDING_BIT) -+ | BIT(SSH_REQUEST_SF_TRANSMITTING_BIT) -+ | BIT(SSH_REQUEST_SF_TRANSMITTED_BIT) -+ | BIT(SSH_REQUEST_SF_RSPRCVD_BIT) -+ | BIT(SSH_REQUEST_SF_CANCELED_BIT) -+ | BIT(SSH_REQUEST_SF_COMPLETED_BIT), -+ -+ /* mask for type flags */ -+ SSH_REQUEST_FLAGS_TY_MASK = -+ BIT(SSH_REQUEST_TY_FLUSH_BIT) -+ | BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT), -+}; -+ -+struct ssh_rtl; -+struct ssh_request; -+ -+/** -+ * struct ssh_request_ops - Callback operations for a SSH request. -+ * @release: Function called when the request's reference count reaches zero. -+ * This callback must be relied upon to ensure that the request has -+ * left the transport systems (both, packet an request systems). -+ * @complete: Function called when the request is completed, either with -+ * success or failure. The command data for the request response -+ * is provided via the &struct ssh_command parameter (``cmd``), -+ * the command payload of the request response via the &struct -+ * ssh_span parameter (``data``). -+ * -+ * If the request does not have any response or has not been -+ * completed with success, both ``cmd`` and ``data`` parameters will -+ * be NULL. If the request response does not have any command -+ * payload, the ``data`` span will be an empty (zero-length) span. -+ * -+ * In case of failure, the reason for the failure is indicated by -+ * the value of the provided status code argument (``status``). This -+ * value will be zero in case of success and a regular errno -+ * otherwise. -+ * -+ * Note that a call to this callback does not guarantee that the -+ * request is not in use by the transport systems any more. -+ */ -+struct ssh_request_ops { -+ void (*release)(struct ssh_request *rqst); -+ void (*complete)(struct ssh_request *rqst, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data, int status); -+}; -+ -+/** -+ * struct ssh_request - SSH transport request. -+ * @packet: The underlying SSH transport packet. -+ * @node: List node for the request queue and pending set. -+ * @state: State and type flags describing current request state (dynamic) -+ * and type (static). See &enum ssh_request_flags for possible -+ * options. -+ * @timestamp: Timestamp specifying when we start waiting on the response of -+ * the request. This is set once the underlying packet has been -+ * completed and may be %KTIME_MAX before that, or when the request -+ * does not expect a response. Used for the request timeout -+ * implementation. -+ * @ops: Request Operations. -+ */ -+struct ssh_request { -+ struct ssh_packet packet; -+ struct list_head node; -+ -+ unsigned long state; -+ ktime_t timestamp; -+ -+ const struct ssh_request_ops *ops; -+}; -+ -+/** -+ * to_ssh_request() - Cast a SSH packet to its enclosing SSH request. -+ * @p: The packet to cast. -+ * -+ * Casts the given &struct ssh_packet to its enclosing &struct ssh_request. -+ * The caller is responsible for making sure that the packet is actually -+ * wrapped in a &struct ssh_request. -+ * -+ * Return: Returns the &struct ssh_request wrapping the provided packet. -+ */ -+static inline struct ssh_request *to_ssh_request(struct ssh_packet *p) -+{ -+ return container_of(p, struct ssh_request, packet); -+} -+ -+/** -+ * ssh_request_get() - Increment reference count of request. -+ * @r: The request to increment the reference count of. -+ * -+ * Increments the reference count of the given request by incrementing the -+ * reference count of the underlying &struct ssh_packet, enclosed in it. -+ * -+ * See also ssh_request_put(), ssh_packet_get(). -+ * -+ * Return: Returns the request provided as input. -+ */ -+static inline struct ssh_request *ssh_request_get(struct ssh_request *r) -+{ -+ return r ? to_ssh_request(ssh_packet_get(&r->packet)) : NULL; -+} -+ -+/** -+ * ssh_request_put() - Decrement reference count of request. -+ * @r: The request to decrement the reference count of. -+ * -+ * Decrements the reference count of the given request by decrementing the -+ * reference count of the underlying &struct ssh_packet, enclosed in it. If -+ * the reference count reaches zero, the ``release`` callback specified in the -+ * request's &struct ssh_request_ops, i.e. ``r->ops->release``, will be -+ * called. -+ * -+ * See also ssh_request_get(), ssh_packet_put(). -+ */ -+static inline void ssh_request_put(struct ssh_request *r) -+{ -+ if (r) -+ ssh_packet_put(&r->packet); -+} -+ -+/** -+ * ssh_request_set_data() - Set raw message data of request. -+ * @r: The request for which the message data should be set. -+ * @ptr: Pointer to the memory holding the message data. -+ * @len: Length of the message data. -+ * -+ * Sets the raw message data buffer of the underlying packet to the specified -+ * buffer. Does not copy the actual message data, just sets the buffer pointer -+ * and length. Refer to ssh_packet_set_data() for more details. -+ */ -+static inline void ssh_request_set_data(struct ssh_request *r, u8 *ptr, size_t len) -+{ -+ ssh_packet_set_data(&r->packet, ptr, len); -+} -+ -+#endif /* _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H */ --- -2.31.1 - -From 30a565ec13104e6b6e84541dfdff09d77d986ecc Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 21 Dec 2020 19:39:52 +0100 -Subject: [PATCH] platform/surface: aggregator: Add control packet allocation - caching - -Surface Serial Hub communication is, in its core, packet based. Each -sequenced packet requires to be acknowledged, via an ACK-type control -packet. In case invalid data has been received by the driver, a NAK-type -(not-acknowledge/negative acknowledge) control packet is sent, -triggering retransmission. - -Control packets are therefore a core communication primitive and used -frequently enough (with every sequenced packet transmission sent by the -embedded controller, including events and request responses) that it may -warrant caching their allocations to reduce possible memory -fragmentation. - -Signed-off-by: Maximilian Luz -Reviewed-by: Hans de Goede -Link: https://lore.kernel.org/r/20201221183959.1186143-3-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/core.c | 27 ++++++++++- - .../surface/aggregator/ssh_packet_layer.c | 47 +++++++++++++++---- - .../surface/aggregator/ssh_packet_layer.h | 3 ++ - 3 files changed, 67 insertions(+), 10 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -index 18e0e9e34e7b..60d312f71436 100644 ---- a/drivers/platform/surface/aggregator/core.c -+++ b/drivers/platform/surface/aggregator/core.c -@@ -780,7 +780,32 @@ static struct serdev_device_driver ssam_serial_hub = { - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - }, - }; --module_serdev_device_driver(ssam_serial_hub); -+ -+ -+/* -- Module setup. --------------------------------------------------------- */ -+ -+static int __init ssam_core_init(void) -+{ -+ int status; -+ -+ status = ssh_ctrl_packet_cache_init(); -+ if (status) -+ return status; -+ -+ status = serdev_device_driver_register(&ssam_serial_hub); -+ if (status) -+ ssh_ctrl_packet_cache_destroy(); -+ -+ return status; -+} -+module_init(ssam_core_init); -+ -+static void __exit ssam_core_exit(void) -+{ -+ serdev_device_driver_unregister(&ssam_serial_hub); -+ ssh_ctrl_packet_cache_destroy(); -+} -+module_exit(ssam_core_exit); - - MODULE_AUTHOR("Maximilian Luz "); - MODULE_DESCRIPTION("Subsystem and Surface Serial Hub driver for Surface System Aggregator Module"); -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c -index 66e38fdc7963..23c2e31e7d0e 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c -@@ -303,24 +303,53 @@ void ssh_packet_init(struct ssh_packet *packet, unsigned long type, - packet->ops = ops; - } - -+static struct kmem_cache *ssh_ctrl_packet_cache; -+ -+/** -+ * ssh_ctrl_packet_cache_init() - Initialize the control packet cache. -+ */ -+int ssh_ctrl_packet_cache_init(void) -+{ -+ const unsigned int size = sizeof(struct ssh_packet) + SSH_MSG_LEN_CTRL; -+ const unsigned int align = __alignof__(struct ssh_packet); -+ struct kmem_cache *cache; -+ -+ cache = kmem_cache_create("ssam_ctrl_packet", size, align, 0, NULL); -+ if (!cache) -+ return -ENOMEM; -+ -+ ssh_ctrl_packet_cache = cache; -+ return 0; -+} -+ -+/** -+ * ssh_ctrl_packet_cache_destroy() - Deinitialize the control packet cache. -+ */ -+void ssh_ctrl_packet_cache_destroy(void) -+{ -+ kmem_cache_destroy(ssh_ctrl_packet_cache); -+ ssh_ctrl_packet_cache = NULL; -+} -+ - /** -- * ssh_ctrl_packet_alloc() - Allocate control packet. -+ * ssh_ctrl_packet_alloc() - Allocate packet from control packet cache. - * @packet: Where the pointer to the newly allocated packet should be stored. - * @buffer: The buffer corresponding to this packet. - * @flags: Flags used for allocation. - * -- * Allocates a packet and corresponding transport buffer. Sets the packet's -- * buffer reference to the allocated buffer. The packet must be freed via -- * ssh_ctrl_packet_free(), which will also free the corresponding buffer. The -- * corresponding buffer must not be freed separately. Intended to be used with -- * %ssh_ptl_ctrl_packet_ops as packet operations. -+ * Allocates a packet and corresponding transport buffer from the control -+ * packet cache. Sets the packet's buffer reference to the allocated buffer. -+ * The packet must be freed via ssh_ctrl_packet_free(), which will also free -+ * the corresponding buffer. The corresponding buffer must not be freed -+ * separately. Intended to be used with %ssh_ptl_ctrl_packet_ops as packet -+ * operations. - * - * Return: Returns zero on success, %-ENOMEM if the allocation failed. - */ - static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, - struct ssam_span *buffer, gfp_t flags) - { -- *packet = kzalloc(sizeof(**packet) + SSH_MSG_LEN_CTRL, flags); -+ *packet = kmem_cache_alloc(ssh_ctrl_packet_cache, flags); - if (!*packet) - return -ENOMEM; - -@@ -331,12 +360,12 @@ static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, - } - - /** -- * ssh_ctrl_packet_free() - Free control packet. -+ * ssh_ctrl_packet_free() - Free packet allocated from control packet cache. - * @p: The packet to free. - */ - static void ssh_ctrl_packet_free(struct ssh_packet *p) - { -- kfree(p); -+ kmem_cache_free(ssh_ctrl_packet_cache, p); - } - - static const struct ssh_packet_ops ssh_ptl_ctrl_packet_ops = { -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h -index 058f111292ca..e8757d03f279 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.h -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h -@@ -184,4 +184,7 @@ static inline void ssh_ptl_tx_wakeup_transfer(struct ssh_ptl *ptl) - void ssh_packet_init(struct ssh_packet *packet, unsigned long type, - u8 priority, const struct ssh_packet_ops *ops); - -+int ssh_ctrl_packet_cache_init(void); -+void ssh_ctrl_packet_cache_destroy(void); -+ - #endif /* _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H */ --- -2.31.1 - -From 08f1a0cbd5de6dc7bf56931f76105b376ee7a14f Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 21 Dec 2020 19:39:53 +0100 -Subject: [PATCH] platform/surface: aggregator: Add event item allocation - caching - -Event items are used for completing Surface Aggregator EC events, i.e. -placing event command data and payload on a workqueue for later -processing to avoid doing said processing directly on the receiver -thread. This means that event items are allocated for each incoming -event, regardless of that event being transmitted via sequenced or -unsequenced packets. - -On the Surface Book 3 and Surface Laptop 3, touchpad HID input events -(unsequenced), can constitute a larger amount of traffic, and therefore -allocation of event items. This warrants caching event items to reduce -memory fragmentation. The size of the cached objects is specifically -tuned to accommodate keyboard and touchpad input events and their -payloads on those devices. As a result, this effectively also covers -most other event types. In case of a larger event payload, event item -allocation will fall back to kzalloc(). - -Signed-off-by: Maximilian Luz -Reviewed-by: Hans de Goede -Link: https://lore.kernel.org/r/20201221183959.1186143-4-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../platform/surface/aggregator/controller.c | 86 +++++++++++++++++-- - .../platform/surface/aggregator/controller.h | 9 ++ - drivers/platform/surface/aggregator/core.c | 16 +++- - 3 files changed, 101 insertions(+), 10 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index 488318cf2098..775a4509bece 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -513,14 +513,74 @@ static void ssam_nf_destroy(struct ssam_nf *nf) - */ - #define SSAM_CPLT_WQ_BATCH 10 - -+/* -+ * SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN - Maximum payload length for a cached -+ * &struct ssam_event_item. -+ * -+ * This length has been chosen to be accommodate standard touchpad and -+ * keyboard input events. Events with larger payloads will be allocated -+ * separately. -+ */ -+#define SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN 32 -+ -+static struct kmem_cache *ssam_event_item_cache; -+ -+/** -+ * ssam_event_item_cache_init() - Initialize the event item cache. -+ */ -+int ssam_event_item_cache_init(void) -+{ -+ const unsigned int size = sizeof(struct ssam_event_item) -+ + SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN; -+ const unsigned int align = __alignof__(struct ssam_event_item); -+ struct kmem_cache *cache; -+ -+ cache = kmem_cache_create("ssam_event_item", size, align, 0, NULL); -+ if (!cache) -+ return -ENOMEM; -+ -+ ssam_event_item_cache = cache; -+ return 0; -+} -+ -+/** -+ * ssam_event_item_cache_destroy() - Deinitialize the event item cache. -+ */ -+void ssam_event_item_cache_destroy(void) -+{ -+ kmem_cache_destroy(ssam_event_item_cache); -+ ssam_event_item_cache = NULL; -+} -+ -+static void __ssam_event_item_free_cached(struct ssam_event_item *item) -+{ -+ kmem_cache_free(ssam_event_item_cache, item); -+} -+ -+static void __ssam_event_item_free_generic(struct ssam_event_item *item) -+{ -+ kfree(item); -+} -+ -+/** -+ * ssam_event_item_free() - Free the provided event item. -+ * @item: The event item to free. -+ */ -+static void ssam_event_item_free(struct ssam_event_item *item) -+{ -+ item->ops.free(item); -+} -+ - /** - * ssam_event_item_alloc() - Allocate an event item with the given payload size. - * @len: The event payload length. - * @flags: The flags used for allocation. - * -- * Allocate an event item with the given payload size. Sets the item -- * operations and payload length values. The item free callback (``ops.free``) -- * should not be overwritten after this call. -+ * Allocate an event item with the given payload size, preferring allocation -+ * from the event item cache if the payload is small enough (i.e. smaller than -+ * %SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN). Sets the item operations and payload -+ * length values. The item free callback (``ops.free``) should not be -+ * overwritten after this call. - * - * Return: Returns the newly allocated event item. - */ -@@ -528,9 +588,19 @@ static struct ssam_event_item *ssam_event_item_alloc(size_t len, gfp_t flags) - { - struct ssam_event_item *item; - -- item = kzalloc(struct_size(item, event.data, len), flags); -- if (!item) -- return NULL; -+ if (len <= SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN) { -+ item = kmem_cache_alloc(ssam_event_item_cache, flags); -+ if (!item) -+ return NULL; -+ -+ item->ops.free = __ssam_event_item_free_cached; -+ } else { -+ item = kzalloc(struct_size(item, event.data, len), flags); -+ if (!item) -+ return NULL; -+ -+ item->ops.free = __ssam_event_item_free_generic; -+ } - - item->event.length = len; - return item; -@@ -692,7 +762,7 @@ static void ssam_event_queue_work_fn(struct work_struct *work) - return; - - ssam_nf_call(nf, dev, item->rqid, &item->event); -- kfree(item); -+ ssam_event_item_free(item); - } while (--iterations); - - if (!ssam_event_queue_is_empty(queue)) -@@ -900,7 +970,7 @@ static void ssam_handle_event(struct ssh_rtl *rtl, - memcpy(&item->event.data[0], data->ptr, data->len); - - if (WARN_ON(ssam_cplt_submit_event(&ctrl->cplt, item))) -- kfree(item); -+ ssam_event_item_free(item); - } - - static const struct ssh_rtl_ops ssam_rtl_ops = { -diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h -index 5ee9e966f1d7..8297d34e7489 100644 ---- a/drivers/platform/surface/aggregator/controller.h -+++ b/drivers/platform/surface/aggregator/controller.h -@@ -80,12 +80,18 @@ struct ssam_cplt; - * struct ssam_event_item - Struct for event queuing and completion. - * @node: The node in the queue. - * @rqid: The request ID of the event. -+ * @ops: Instance specific functions. -+ * @ops.free: Callback for freeing this event item. - * @event: Actual event data. - */ - struct ssam_event_item { - struct list_head node; - u16 rqid; - -+ struct { -+ void (*free)(struct ssam_event_item *event); -+ } ops; -+ - struct ssam_event event; /* must be last */ - }; - -@@ -273,4 +279,7 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl); - int ssam_controller_suspend(struct ssam_controller *ctrl); - int ssam_controller_resume(struct ssam_controller *ctrl); - -+int ssam_event_item_cache_init(void); -+void ssam_event_item_cache_destroy(void); -+ - #endif /* _SURFACE_AGGREGATOR_CONTROLLER_H */ -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -index 60d312f71436..37593234fb31 100644 ---- a/drivers/platform/surface/aggregator/core.c -+++ b/drivers/platform/surface/aggregator/core.c -@@ -790,12 +790,23 @@ static int __init ssam_core_init(void) - - status = ssh_ctrl_packet_cache_init(); - if (status) -- return status; -+ goto err_cpkg; -+ -+ status = ssam_event_item_cache_init(); -+ if (status) -+ goto err_evitem; - - status = serdev_device_driver_register(&ssam_serial_hub); - if (status) -- ssh_ctrl_packet_cache_destroy(); -+ goto err_register; - -+ return 0; -+ -+err_register: -+ ssam_event_item_cache_destroy(); -+err_evitem: -+ ssh_ctrl_packet_cache_destroy(); -+err_cpkg: - return status; - } - module_init(ssam_core_init); -@@ -803,6 +814,7 @@ module_init(ssam_core_init); - static void __exit ssam_core_exit(void) - { - serdev_device_driver_unregister(&ssam_serial_hub); -+ ssam_event_item_cache_destroy(); - ssh_ctrl_packet_cache_destroy(); - } - module_exit(ssam_core_exit); --- -2.31.1 - -From 5f3de1d75e04d4ac9e54c78026c7361261b93717 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 21 Dec 2020 19:39:54 +0100 -Subject: [PATCH] platform/surface: aggregator: Add trace points - -Add trace points to the Surface Aggregator subsystem core. These trace -points can be used to track packets, requests, and allocations. They are -further intended for debugging and testing/validation, specifically in -combination with the error injection capabilities introduced in the -subsequent commit. - -Signed-off-by: Maximilian Luz -Reviewed-by: Hans de Goede -Acked-by: Steven Rostedt (VMware) -Link: https://lore.kernel.org/r/20201221183959.1186143-5-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/Makefile | 3 + - .../platform/surface/aggregator/controller.c | 5 + - drivers/platform/surface/aggregator/core.c | 3 + - .../surface/aggregator/ssh_packet_layer.c | 26 +- - .../surface/aggregator/ssh_request_layer.c | 18 + - drivers/platform/surface/aggregator/trace.h | 601 ++++++++++++++++++ - 6 files changed, 655 insertions(+), 1 deletion(-) - create mode 100644 drivers/platform/surface/aggregator/trace.h - -diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile -index faad18d4a7f2..b8b24c8ec310 100644 ---- a/drivers/platform/surface/aggregator/Makefile -+++ b/drivers/platform/surface/aggregator/Makefile -@@ -1,6 +1,9 @@ - # SPDX-License-Identifier: GPL-2.0+ - # Copyright (C) 2019-2020 Maximilian Luz - -+# For include/trace/define_trace.h to include trace.h -+CFLAGS_core.o = -I$(src) -+ - obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator.o - - surface_aggregator-objs := core.o -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index 775a4509bece..5bcb59ed579d 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -32,6 +32,8 @@ - #include "ssh_msgb.h" - #include "ssh_request_layer.h" - -+#include "trace.h" -+ - - /* -- Safe counters. -------------------------------------------------------- */ - -@@ -568,6 +570,7 @@ static void __ssam_event_item_free_generic(struct ssam_event_item *item) - */ - static void ssam_event_item_free(struct ssam_event_item *item) - { -+ trace_ssam_event_item_free(item); - item->ops.free(item); - } - -@@ -603,6 +606,8 @@ static struct ssam_event_item *ssam_event_item_alloc(size_t len, gfp_t flags) - } - - item->event.length = len; -+ -+ trace_ssam_event_item_alloc(item, len); - return item; - } - -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -index 37593234fb31..b6a9dea53592 100644 ---- a/drivers/platform/surface/aggregator/core.c -+++ b/drivers/platform/surface/aggregator/core.c -@@ -24,6 +24,9 @@ - #include - #include "controller.h" - -+#define CREATE_TRACE_POINTS -+#include "trace.h" -+ - - /* -- Static controller reference. ------------------------------------------ */ - -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c -index 23c2e31e7d0e..c4f082e57372 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c -@@ -26,6 +26,8 @@ - #include "ssh_packet_layer.h" - #include "ssh_parser.h" - -+#include "trace.h" -+ - /* - * To simplify reasoning about the code below, we define a few concepts. The - * system below is similar to a state-machine for packets, however, there are -@@ -228,6 +230,8 @@ static void __ssh_ptl_packet_release(struct kref *kref) - { - struct ssh_packet *p = container_of(kref, struct ssh_packet, refcnt); - -+ trace_ssam_packet_release(p); -+ - ptl_dbg_cond(p->ptl, "ptl: releasing packet %p\n", p); - p->ops->release(p); - } -@@ -356,6 +360,7 @@ static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, - buffer->ptr = (u8 *)(*packet + 1); - buffer->len = SSH_MSG_LEN_CTRL; - -+ trace_ssam_ctrl_packet_alloc(*packet, buffer->len); - return 0; - } - -@@ -365,6 +370,7 @@ static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, - */ - static void ssh_ctrl_packet_free(struct ssh_packet *p) - { -+ trace_ssam_ctrl_packet_free(p); - kmem_cache_free(ssh_ctrl_packet_cache, p); - } - -@@ -398,7 +404,12 @@ static void ssh_packet_next_try(struct ssh_packet *p) - - lockdep_assert_held(&p->ptl->queue.lock); - -- p->priority = __SSH_PACKET_PRIORITY(base, try + 1); -+ /* -+ * Ensure that we write the priority in one go via WRITE_ONCE() so we -+ * can access it via READ_ONCE() for tracing. Note that other access -+ * is guarded by the queue lock, so no need to use READ_ONCE() there. -+ */ -+ WRITE_ONCE(p->priority, __SSH_PACKET_PRIORITY(base, try + 1)); - } - - /* Must be called with queue lock held. */ -@@ -560,6 +571,7 @@ static void __ssh_ptl_complete(struct ssh_packet *p, int status) - { - struct ssh_ptl *ptl = READ_ONCE(p->ptl); - -+ trace_ssam_packet_complete(p, status); - ptl_dbg_cond(ptl, "ptl: completing packet %p (status: %d)\n", p, status); - - if (p->ops->complete) -@@ -1014,6 +1026,8 @@ int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p) - struct ssh_ptl *ptl_old; - int status; - -+ trace_ssam_packet_submit(p); -+ - /* Validate packet fields. */ - if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &p->state)) { - if (p->data.ptr || test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) -@@ -1065,6 +1079,8 @@ static int __ssh_ptl_resubmit(struct ssh_packet *packet) - - lockdep_assert_held(&packet->ptl->pending.lock); - -+ trace_ssam_packet_resubmit(packet); -+ - spin_lock(&packet->ptl->queue.lock); - - /* Check if the packet is out of tries. */ -@@ -1148,6 +1164,8 @@ void ssh_ptl_cancel(struct ssh_packet *p) - if (test_and_set_bit(SSH_PACKET_SF_CANCELED_BIT, &p->state)) - return; - -+ trace_ssam_packet_cancel(p); -+ - /* - * Lock packet and commit with memory barrier. If this packet has - * already been locked, it's going to be removed and completed by -@@ -1202,6 +1220,8 @@ static void ssh_ptl_timeout_reap(struct work_struct *work) - bool resub = false; - int status; - -+ trace_ssam_ptl_timeout_reap(atomic_read(&ptl->pending.count)); -+ - /* - * Mark reaper as "not pending". This is done before checking any - * packets to avoid lost-update type problems. -@@ -1224,6 +1244,8 @@ static void ssh_ptl_timeout_reap(struct work_struct *work) - continue; - } - -+ trace_ssam_packet_timeout(p); -+ - status = __ssh_ptl_resubmit(p); - - /* -@@ -1416,6 +1438,8 @@ static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) - if (!frame) /* Not enough data. */ - return aligned.ptr - source->ptr; - -+ trace_ssam_rx_frame_received(frame); -+ - switch (frame->type) { - case SSH_FRAME_TYPE_ACK: - ssh_ptl_acknowledge(ptl, frame->seq); -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c -index 66c839a995f3..b649d71840fd 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.c -@@ -22,6 +22,8 @@ - #include "ssh_packet_layer.h" - #include "ssh_request_layer.h" - -+#include "trace.h" -+ - /* - * SSH_RTL_REQUEST_TIMEOUT - Request timeout. - * -@@ -144,6 +146,8 @@ static void ssh_rtl_complete_with_status(struct ssh_request *rqst, int status) - { - struct ssh_rtl *rtl = ssh_request_rtl(rqst); - -+ trace_ssam_request_complete(rqst, status); -+ - /* rtl/ptl may not be set if we're canceling before submitting. */ - rtl_dbg_cond(rtl, "rtl: completing request (rqid: %#06x, status: %d)\n", - ssh_request_get_rqid_safe(rqst), status); -@@ -157,6 +161,8 @@ static void ssh_rtl_complete_with_rsp(struct ssh_request *rqst, - { - struct ssh_rtl *rtl = ssh_request_rtl(rqst); - -+ trace_ssam_request_complete(rqst, 0); -+ - rtl_dbg(rtl, "rtl: completing request with response (rqid: %#06x)\n", - ssh_request_get_rqid(rqst)); - -@@ -329,6 +335,8 @@ static void ssh_rtl_tx_work_fn(struct work_struct *work) - */ - int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst) - { -+ trace_ssam_request_submit(rqst); -+ - /* - * Ensure that requests expecting a response are sequenced. If this - * invariant ever changes, see the comment in ssh_rtl_complete() on what -@@ -439,6 +447,8 @@ static void ssh_rtl_complete(struct ssh_rtl *rtl, - struct ssh_request *p, *n; - u16 rqid = get_unaligned_le16(&command->rqid); - -+ trace_ssam_rx_response_received(command, command_data->len); -+ - /* - * Get request from pending based on request ID and mark it as response - * received and locked. -@@ -688,6 +698,8 @@ bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending) - if (test_and_set_bit(SSH_REQUEST_SF_CANCELED_BIT, &rqst->state)) - return true; - -+ trace_ssam_request_cancel(rqst); -+ - if (pending) - canceled = ssh_rtl_cancel_pending(rqst); - else -@@ -779,6 +791,8 @@ static void ssh_rtl_timeout_reap(struct work_struct *work) - ktime_t timeout = rtl->rtx_timeout.timeout; - ktime_t next = KTIME_MAX; - -+ trace_ssam_rtl_timeout_reap(atomic_read(&rtl->pending.count)); -+ - /* - * Mark reaper as "not pending". This is done before checking any - * requests to avoid lost-update type problems. -@@ -822,6 +836,8 @@ static void ssh_rtl_timeout_reap(struct work_struct *work) - - /* Cancel and complete the request. */ - list_for_each_entry_safe(r, n, &claimed, node) { -+ trace_ssam_request_timeout(r); -+ - /* - * At this point we've removed the packet from pending. This - * means that we've obtained the last (only) reference of the -@@ -849,6 +865,8 @@ static void ssh_rtl_timeout_reap(struct work_struct *work) - static void ssh_rtl_rx_event(struct ssh_rtl *rtl, const struct ssh_command *cmd, - const struct ssam_span *data) - { -+ trace_ssam_rx_event_received(cmd, data->len); -+ - rtl_dbg(rtl, "rtl: handling event (rqid: %#06x)\n", - get_unaligned_le16(&cmd->rqid)); - -diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h -new file mode 100644 -index 000000000000..dcca8007d876 ---- /dev/null -+++ b/drivers/platform/surface/aggregator/trace.h -@@ -0,0 +1,601 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Trace points for SSAM/SSH. -+ * -+ * Copyright (C) 2020 Maximilian Luz -+ */ -+ -+#undef TRACE_SYSTEM -+#define TRACE_SYSTEM surface_aggregator -+ -+#if !defined(_SURFACE_AGGREGATOR_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) -+#define _SURFACE_AGGREGATOR_TRACE_H -+ -+#include -+ -+#include -+#include -+ -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_SEQ); -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_NSQ); -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_ACK); -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_NAK); -+ -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_LOCKED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_QUEUED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_PENDING_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTING_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_ACKED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_CANCELED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_COMPLETED_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_PACKET_TY_FLUSH_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_TY_SEQUENCED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_TY_BLOCKING_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_SF_MASK); -+TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_TY_MASK); -+ -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_LOCKED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_QUEUED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_PENDING_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTING_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_RSPRCVD_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_CANCELED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_COMPLETED_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_REQUEST_TY_FLUSH_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_TY_HAS_RESPONSE_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_SF_MASK); -+TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_TY_MASK); -+ -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SAM); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAT); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TMP); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_PMC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_FAN); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_PoM); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_DBG); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_KBD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_FWU); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_UNI); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_LPC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCL); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SFL); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_KIP); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_EXT); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BLD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAS); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SEN); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SRQ); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_MCU); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_VID); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG); -+ -+#define SSAM_PTR_UID_LEN 9 -+#define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1) -+#define SSAM_SEQ_NOT_APPLICABLE ((u16)-1) -+#define SSAM_RQID_NOT_APPLICABLE ((u32)-1) -+#define SSAM_SSH_TC_NOT_APPLICABLE 0 -+ -+#ifndef _SURFACE_AGGREGATOR_TRACE_HELPERS -+#define _SURFACE_AGGREGATOR_TRACE_HELPERS -+ -+/** -+ * ssam_trace_ptr_uid() - Convert the pointer to a non-pointer UID string. -+ * @ptr: The pointer to convert. -+ * @uid_str: A buffer of length SSAM_PTR_UID_LEN where the UID will be stored. -+ * -+ * Converts the given pointer into a UID string that is safe to be shared -+ * with userspace and logs, i.e. doesn't give away the real memory location. -+ */ -+static inline void ssam_trace_ptr_uid(const void *ptr, char *uid_str) -+{ -+ char buf[2 * sizeof(void *) + 1]; -+ -+ BUILD_BUG_ON(ARRAY_SIZE(buf) < SSAM_PTR_UID_LEN); -+ -+ snprintf(buf, ARRAY_SIZE(buf), "%p", ptr); -+ memcpy(uid_str, &buf[ARRAY_SIZE(buf) - SSAM_PTR_UID_LEN], -+ SSAM_PTR_UID_LEN); -+} -+ -+/** -+ * ssam_trace_get_packet_seq() - Read the packet's sequence ID. -+ * @p: The packet. -+ * -+ * Return: Returns the packet's sequence ID (SEQ) field if present, or -+ * %SSAM_SEQ_NOT_APPLICABLE if not (e.g. flush packet). -+ */ -+static inline u16 ssam_trace_get_packet_seq(const struct ssh_packet *p) -+{ -+ if (!p->data.ptr || p->data.len < SSH_MESSAGE_LENGTH(0)) -+ return SSAM_SEQ_NOT_APPLICABLE; -+ -+ return p->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; -+} -+ -+/** -+ * ssam_trace_get_request_id() - Read the packet's request ID. -+ * @p: The packet. -+ * -+ * Return: Returns the packet's request ID (RQID) field if the packet -+ * represents a request with command data, or %SSAM_RQID_NOT_APPLICABLE if not -+ * (e.g. flush request, control packet). -+ */ -+static inline u32 ssam_trace_get_request_id(const struct ssh_packet *p) -+{ -+ if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) -+ return SSAM_RQID_NOT_APPLICABLE; -+ -+ return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(rqid)]); -+} -+ -+/** -+ * ssam_trace_get_request_tc() - Read the packet's request target category. -+ * @p: The packet. -+ * -+ * Return: Returns the packet's request target category (TC) field if the -+ * packet represents a request with command data, or %SSAM_TC_NOT_APPLICABLE -+ * if not (e.g. flush request, control packet). -+ */ -+static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) -+{ -+ if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) -+ return SSAM_SSH_TC_NOT_APPLICABLE; -+ -+ return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(tc)]); -+} -+ -+#endif /* _SURFACE_AGGREGATOR_TRACE_HELPERS */ -+ -+#define ssam_trace_get_command_field_u8(packet, field) \ -+ ((!(packet) || (packet)->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) \ -+ ? 0 : (packet)->data.ptr[SSH_MSGOFFSET_COMMAND(field)]) -+ -+#define ssam_show_generic_u8_field(value) \ -+ __print_symbolic(value, \ -+ { SSAM_U8_FIELD_NOT_APPLICABLE, "N/A" } \ -+ ) -+ -+#define ssam_show_frame_type(ty) \ -+ __print_symbolic(ty, \ -+ { SSH_FRAME_TYPE_DATA_SEQ, "DSEQ" }, \ -+ { SSH_FRAME_TYPE_DATA_NSQ, "DNSQ" }, \ -+ { SSH_FRAME_TYPE_ACK, "ACK" }, \ -+ { SSH_FRAME_TYPE_NAK, "NAK" } \ -+ ) -+ -+#define ssam_show_packet_type(type) \ -+ __print_flags(flags & SSH_PACKET_FLAGS_TY_MASK, "", \ -+ { BIT(SSH_PACKET_TY_FLUSH_BIT), "F" }, \ -+ { BIT(SSH_PACKET_TY_SEQUENCED_BIT), "S" }, \ -+ { BIT(SSH_PACKET_TY_BLOCKING_BIT), "B" } \ -+ ) -+ -+#define ssam_show_packet_state(state) \ -+ __print_flags(flags & SSH_PACKET_FLAGS_SF_MASK, "", \ -+ { BIT(SSH_PACKET_SF_LOCKED_BIT), "L" }, \ -+ { BIT(SSH_PACKET_SF_QUEUED_BIT), "Q" }, \ -+ { BIT(SSH_PACKET_SF_PENDING_BIT), "P" }, \ -+ { BIT(SSH_PACKET_SF_TRANSMITTING_BIT), "S" }, \ -+ { BIT(SSH_PACKET_SF_TRANSMITTED_BIT), "T" }, \ -+ { BIT(SSH_PACKET_SF_ACKED_BIT), "A" }, \ -+ { BIT(SSH_PACKET_SF_CANCELED_BIT), "C" }, \ -+ { BIT(SSH_PACKET_SF_COMPLETED_BIT), "F" } \ -+ ) -+ -+#define ssam_show_packet_seq(seq) \ -+ __print_symbolic(seq, \ -+ { SSAM_SEQ_NOT_APPLICABLE, "N/A" } \ -+ ) -+ -+#define ssam_show_request_type(flags) \ -+ __print_flags((flags) & SSH_REQUEST_FLAGS_TY_MASK, "", \ -+ { BIT(SSH_REQUEST_TY_FLUSH_BIT), "F" }, \ -+ { BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT), "R" } \ -+ ) -+ -+#define ssam_show_request_state(flags) \ -+ __print_flags((flags) & SSH_REQUEST_FLAGS_SF_MASK, "", \ -+ { BIT(SSH_REQUEST_SF_LOCKED_BIT), "L" }, \ -+ { BIT(SSH_REQUEST_SF_QUEUED_BIT), "Q" }, \ -+ { BIT(SSH_REQUEST_SF_PENDING_BIT), "P" }, \ -+ { BIT(SSH_REQUEST_SF_TRANSMITTING_BIT), "S" }, \ -+ { BIT(SSH_REQUEST_SF_TRANSMITTED_BIT), "T" }, \ -+ { BIT(SSH_REQUEST_SF_RSPRCVD_BIT), "A" }, \ -+ { BIT(SSH_REQUEST_SF_CANCELED_BIT), "C" }, \ -+ { BIT(SSH_REQUEST_SF_COMPLETED_BIT), "F" } \ -+ ) -+ -+#define ssam_show_request_id(rqid) \ -+ __print_symbolic(rqid, \ -+ { SSAM_RQID_NOT_APPLICABLE, "N/A" } \ -+ ) -+ -+#define ssam_show_ssh_tc(rqid) \ -+ __print_symbolic(rqid, \ -+ { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ -+ { SSAM_SSH_TC_SAM, "SAM" }, \ -+ { SSAM_SSH_TC_BAT, "BAT" }, \ -+ { SSAM_SSH_TC_TMP, "TMP" }, \ -+ { SSAM_SSH_TC_PMC, "PMC" }, \ -+ { SSAM_SSH_TC_FAN, "FAN" }, \ -+ { SSAM_SSH_TC_PoM, "PoM" }, \ -+ { SSAM_SSH_TC_DBG, "DBG" }, \ -+ { SSAM_SSH_TC_KBD, "KBD" }, \ -+ { SSAM_SSH_TC_FWU, "FWU" }, \ -+ { SSAM_SSH_TC_UNI, "UNI" }, \ -+ { SSAM_SSH_TC_LPC, "LPC" }, \ -+ { SSAM_SSH_TC_TCL, "TCL" }, \ -+ { SSAM_SSH_TC_SFL, "SFL" }, \ -+ { SSAM_SSH_TC_KIP, "KIP" }, \ -+ { SSAM_SSH_TC_EXT, "EXT" }, \ -+ { SSAM_SSH_TC_BLD, "BLD" }, \ -+ { SSAM_SSH_TC_BAS, "BAS" }, \ -+ { SSAM_SSH_TC_SEN, "SEN" }, \ -+ { SSAM_SSH_TC_SRQ, "SRQ" }, \ -+ { SSAM_SSH_TC_MCU, "MCU" }, \ -+ { SSAM_SSH_TC_HID, "HID" }, \ -+ { SSAM_SSH_TC_TCH, "TCH" }, \ -+ { SSAM_SSH_TC_BKL, "BKL" }, \ -+ { SSAM_SSH_TC_TAM, "TAM" }, \ -+ { SSAM_SSH_TC_ACC, "ACC" }, \ -+ { SSAM_SSH_TC_UFI, "UFI" }, \ -+ { SSAM_SSH_TC_USC, "USC" }, \ -+ { SSAM_SSH_TC_PEN, "PEN" }, \ -+ { SSAM_SSH_TC_VID, "VID" }, \ -+ { SSAM_SSH_TC_AUD, "AUD" }, \ -+ { SSAM_SSH_TC_SMC, "SMC" }, \ -+ { SSAM_SSH_TC_KPD, "KPD" }, \ -+ { SSAM_SSH_TC_REG, "REG" } \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_frame_class, -+ TP_PROTO(const struct ssh_frame *frame), -+ -+ TP_ARGS(frame), -+ -+ TP_STRUCT__entry( -+ __field(u8, type) -+ __field(u8, seq) -+ __field(u16, len) -+ ), -+ -+ TP_fast_assign( -+ __entry->type = frame->type; -+ __entry->seq = frame->seq; -+ __entry->len = get_unaligned_le16(&frame->len); -+ ), -+ -+ TP_printk("ty=%s, seq=%#04x, len=%u", -+ ssam_show_frame_type(__entry->type), -+ __entry->seq, -+ __entry->len -+ ) -+); -+ -+#define DEFINE_SSAM_FRAME_EVENT(name) \ -+ DEFINE_EVENT(ssam_frame_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_frame *frame), \ -+ TP_ARGS(frame) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_command_class, -+ TP_PROTO(const struct ssh_command *cmd, u16 len), -+ -+ TP_ARGS(cmd, len), -+ -+ TP_STRUCT__entry( -+ __field(u16, rqid) -+ __field(u16, len) -+ __field(u8, tc) -+ __field(u8, cid) -+ __field(u8, iid) -+ ), -+ -+ TP_fast_assign( -+ __entry->rqid = get_unaligned_le16(&cmd->rqid); -+ __entry->tc = cmd->tc; -+ __entry->cid = cmd->cid; -+ __entry->iid = cmd->iid; -+ __entry->len = len; -+ ), -+ -+ TP_printk("rqid=%#06x, tc=%s, cid=%#04x, iid=%#04x, len=%u", -+ __entry->rqid, -+ ssam_show_ssh_tc(__entry->tc), -+ __entry->cid, -+ __entry->iid, -+ __entry->len -+ ) -+); -+ -+#define DEFINE_SSAM_COMMAND_EVENT(name) \ -+ DEFINE_EVENT(ssam_command_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_command *cmd, u16 len), \ -+ TP_ARGS(cmd, len) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_packet_class, -+ TP_PROTO(const struct ssh_packet *packet), -+ -+ TP_ARGS(packet), -+ -+ TP_STRUCT__entry( -+ __field(unsigned long, state) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(u8, priority) -+ __field(u16, length) -+ __field(u16, seq) -+ ), -+ -+ TP_fast_assign( -+ __entry->state = READ_ONCE(packet->state); -+ ssam_trace_ptr_uid(packet, __entry->uid); -+ __entry->priority = READ_ONCE(packet->priority); -+ __entry->length = packet->data.len; -+ __entry->seq = ssam_trace_get_packet_seq(packet); -+ ), -+ -+ TP_printk("uid=%s, seq=%s, ty=%s, pri=%#04x, len=%u, sta=%s", -+ __entry->uid, -+ ssam_show_packet_seq(__entry->seq), -+ ssam_show_packet_type(__entry->state), -+ __entry->priority, -+ __entry->length, -+ ssam_show_packet_state(__entry->state) -+ ) -+); -+ -+#define DEFINE_SSAM_PACKET_EVENT(name) \ -+ DEFINE_EVENT(ssam_packet_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_packet *packet), \ -+ TP_ARGS(packet) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_packet_status_class, -+ TP_PROTO(const struct ssh_packet *packet, int status), -+ -+ TP_ARGS(packet, status), -+ -+ TP_STRUCT__entry( -+ __field(unsigned long, state) -+ __field(int, status) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(u8, priority) -+ __field(u16, length) -+ __field(u16, seq) -+ ), -+ -+ TP_fast_assign( -+ __entry->state = READ_ONCE(packet->state); -+ __entry->status = status; -+ ssam_trace_ptr_uid(packet, __entry->uid); -+ __entry->priority = READ_ONCE(packet->priority); -+ __entry->length = packet->data.len; -+ __entry->seq = ssam_trace_get_packet_seq(packet); -+ ), -+ -+ TP_printk("uid=%s, seq=%s, ty=%s, pri=%#04x, len=%u, sta=%s, status=%d", -+ __entry->uid, -+ ssam_show_packet_seq(__entry->seq), -+ ssam_show_packet_type(__entry->state), -+ __entry->priority, -+ __entry->length, -+ ssam_show_packet_state(__entry->state), -+ __entry->status -+ ) -+); -+ -+#define DEFINE_SSAM_PACKET_STATUS_EVENT(name) \ -+ DEFINE_EVENT(ssam_packet_status_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_packet *packet, int status), \ -+ TP_ARGS(packet, status) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_request_class, -+ TP_PROTO(const struct ssh_request *request), -+ -+ TP_ARGS(request), -+ -+ TP_STRUCT__entry( -+ __field(unsigned long, state) -+ __field(u32, rqid) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(u8, tc) -+ __field(u16, cid) -+ __field(u16, iid) -+ ), -+ -+ TP_fast_assign( -+ const struct ssh_packet *p = &request->packet; -+ -+ /* Use packet for UID so we can match requests to packets. */ -+ __entry->state = READ_ONCE(request->state); -+ __entry->rqid = ssam_trace_get_request_id(p); -+ ssam_trace_ptr_uid(p, __entry->uid); -+ __entry->tc = ssam_trace_get_request_tc(p); -+ __entry->cid = ssam_trace_get_command_field_u8(p, cid); -+ __entry->iid = ssam_trace_get_command_field_u8(p, iid); -+ ), -+ -+ TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s", -+ __entry->uid, -+ ssam_show_request_id(__entry->rqid), -+ ssam_show_request_type(__entry->state), -+ ssam_show_request_state(__entry->state), -+ ssam_show_ssh_tc(__entry->tc), -+ ssam_show_generic_u8_field(__entry->cid), -+ ssam_show_generic_u8_field(__entry->iid) -+ ) -+); -+ -+#define DEFINE_SSAM_REQUEST_EVENT(name) \ -+ DEFINE_EVENT(ssam_request_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_request *request), \ -+ TP_ARGS(request) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_request_status_class, -+ TP_PROTO(const struct ssh_request *request, int status), -+ -+ TP_ARGS(request, status), -+ -+ TP_STRUCT__entry( -+ __field(unsigned long, state) -+ __field(u32, rqid) -+ __field(int, status) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(u8, tc) -+ __field(u16, cid) -+ __field(u16, iid) -+ ), -+ -+ TP_fast_assign( -+ const struct ssh_packet *p = &request->packet; -+ -+ /* Use packet for UID so we can match requests to packets. */ -+ __entry->state = READ_ONCE(request->state); -+ __entry->rqid = ssam_trace_get_request_id(p); -+ __entry->status = status; -+ ssam_trace_ptr_uid(p, __entry->uid); -+ __entry->tc = ssam_trace_get_request_tc(p); -+ __entry->cid = ssam_trace_get_command_field_u8(p, cid); -+ __entry->iid = ssam_trace_get_command_field_u8(p, iid); -+ ), -+ -+ TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s, status=%d", -+ __entry->uid, -+ ssam_show_request_id(__entry->rqid), -+ ssam_show_request_type(__entry->state), -+ ssam_show_request_state(__entry->state), -+ ssam_show_ssh_tc(__entry->tc), -+ ssam_show_generic_u8_field(__entry->cid), -+ ssam_show_generic_u8_field(__entry->iid), -+ __entry->status -+ ) -+); -+ -+#define DEFINE_SSAM_REQUEST_STATUS_EVENT(name) \ -+ DEFINE_EVENT(ssam_request_status_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_request *request, int status),\ -+ TP_ARGS(request, status) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_alloc_class, -+ TP_PROTO(void *ptr, size_t len), -+ -+ TP_ARGS(ptr, len), -+ -+ TP_STRUCT__entry( -+ __field(size_t, len) -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ ), -+ -+ TP_fast_assign( -+ __entry->len = len; -+ ssam_trace_ptr_uid(ptr, __entry->uid); -+ ), -+ -+ TP_printk("uid=%s, len=%zu", __entry->uid, __entry->len) -+); -+ -+#define DEFINE_SSAM_ALLOC_EVENT(name) \ -+ DEFINE_EVENT(ssam_alloc_class, ssam_##name, \ -+ TP_PROTO(void *ptr, size_t len), \ -+ TP_ARGS(ptr, len) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_free_class, -+ TP_PROTO(void *ptr), -+ -+ TP_ARGS(ptr), -+ -+ TP_STRUCT__entry( -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ ), -+ -+ TP_fast_assign( -+ ssam_trace_ptr_uid(ptr, __entry->uid); -+ ), -+ -+ TP_printk("uid=%s", __entry->uid) -+); -+ -+#define DEFINE_SSAM_FREE_EVENT(name) \ -+ DEFINE_EVENT(ssam_free_class, ssam_##name, \ -+ TP_PROTO(void *ptr), \ -+ TP_ARGS(ptr) \ -+ ) -+ -+DECLARE_EVENT_CLASS(ssam_pending_class, -+ TP_PROTO(unsigned int pending), -+ -+ TP_ARGS(pending), -+ -+ TP_STRUCT__entry( -+ __field(unsigned int, pending) -+ ), -+ -+ TP_fast_assign( -+ __entry->pending = pending; -+ ), -+ -+ TP_printk("pending=%u", __entry->pending) -+); -+ -+#define DEFINE_SSAM_PENDING_EVENT(name) \ -+ DEFINE_EVENT(ssam_pending_class, ssam_##name, \ -+ TP_PROTO(unsigned int pending), \ -+ TP_ARGS(pending) \ -+ ) -+ -+DEFINE_SSAM_FRAME_EVENT(rx_frame_received); -+DEFINE_SSAM_COMMAND_EVENT(rx_response_received); -+DEFINE_SSAM_COMMAND_EVENT(rx_event_received); -+ -+DEFINE_SSAM_PACKET_EVENT(packet_release); -+DEFINE_SSAM_PACKET_EVENT(packet_submit); -+DEFINE_SSAM_PACKET_EVENT(packet_resubmit); -+DEFINE_SSAM_PACKET_EVENT(packet_timeout); -+DEFINE_SSAM_PACKET_EVENT(packet_cancel); -+DEFINE_SSAM_PACKET_STATUS_EVENT(packet_complete); -+DEFINE_SSAM_PENDING_EVENT(ptl_timeout_reap); -+ -+DEFINE_SSAM_REQUEST_EVENT(request_submit); -+DEFINE_SSAM_REQUEST_EVENT(request_timeout); -+DEFINE_SSAM_REQUEST_EVENT(request_cancel); -+DEFINE_SSAM_REQUEST_STATUS_EVENT(request_complete); -+DEFINE_SSAM_PENDING_EVENT(rtl_timeout_reap); -+ -+DEFINE_SSAM_ALLOC_EVENT(ctrl_packet_alloc); -+DEFINE_SSAM_FREE_EVENT(ctrl_packet_free); -+ -+DEFINE_SSAM_ALLOC_EVENT(event_item_alloc); -+DEFINE_SSAM_FREE_EVENT(event_item_free); -+ -+#endif /* _SURFACE_AGGREGATOR_TRACE_H */ -+ -+/* This part must be outside protection */ -+#undef TRACE_INCLUDE_PATH -+#undef TRACE_INCLUDE_FILE -+ -+#define TRACE_INCLUDE_PATH . -+#define TRACE_INCLUDE_FILE trace -+ -+#include --- -2.31.1 - -From eaee81c67e0e55521adc4fa11c0268889ce96d11 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 21 Dec 2020 19:39:55 +0100 -Subject: [PATCH] platform/surface: aggregator: Add error injection - capabilities - -This commit adds error injection hooks to the Surface Serial Hub -communication protocol implementation, to: - - - simulate simple serial transmission errors, - - - drop packets, requests, and responses, simulating communication - failures and potentially trigger retransmission timeouts, as well as - - - inject invalid data into submitted and received packets. - -Together with the trace points introduced in the previous commit, these -facilities are intended to aid in testing, validation, and debugging of -the Surface Aggregator communication layer. - -Signed-off-by: Maximilian Luz -Reviewed-by: Hans de Goede -Acked-by: Steven Rostedt (VMware) -Link: https://lore.kernel.org/r/20201221183959.1186143-6-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/Kconfig | 14 + - .../surface/aggregator/ssh_packet_layer.c | 296 +++++++++++++++++- - .../surface/aggregator/ssh_request_layer.c | 35 +++ - drivers/platform/surface/aggregator/trace.h | 31 ++ - 4 files changed, 375 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig -index e9f4ad96e40a..e417bac67088 100644 ---- a/drivers/platform/surface/aggregator/Kconfig -+++ b/drivers/platform/surface/aggregator/Kconfig -@@ -40,3 +40,17 @@ menuconfig SURFACE_AGGREGATOR - Choose m if you want to build the SAM subsystem core and SSH driver as - module, y if you want to build it into the kernel and n if you don't - want it at all. -+ -+config SURFACE_AGGREGATOR_ERROR_INJECTION -+ bool "Surface System Aggregator Module Error Injection Capabilities" -+ depends on SURFACE_AGGREGATOR -+ depends on FUNCTION_ERROR_INJECTION -+ help -+ Provides error-injection capabilities for the Surface System -+ Aggregator Module subsystem and Surface Serial Hub driver. -+ -+ Specifically, exports error injection hooks to be used with the -+ kernel's function error injection capabilities to simulate underlying -+ transport and communication problems, such as invalid data sent to or -+ received from the EC, dropped data, and communication timeouts. -+ Intended for development and debugging. -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c -index c4f082e57372..74f0faaa2b27 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c -@@ -7,6 +7,7 @@ - - #include - #include -+#include - #include - #include - #include -@@ -226,6 +227,286 @@ - */ - #define SSH_PTL_RX_FIFO_LEN 4096 - -+#ifdef CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION -+ -+/** -+ * ssh_ptl_should_drop_ack_packet() - Error injection hook to drop ACK packets. -+ * -+ * Useful to test detection and handling of automated re-transmits by the EC. -+ * Specifically of packets that the EC considers not-ACKed but the driver -+ * already considers ACKed (due to dropped ACK). In this case, the EC -+ * re-transmits the packet-to-be-ACKed and the driver should detect it as -+ * duplicate/already handled. Note that the driver should still send an ACK -+ * for the re-transmitted packet. -+ */ -+static noinline bool ssh_ptl_should_drop_ack_packet(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_ack_packet, TRUE); -+ -+/** -+ * ssh_ptl_should_drop_nak_packet() - Error injection hook to drop NAK packets. -+ * -+ * Useful to test/force automated (timeout-based) re-transmit by the EC. -+ * Specifically, packets that have not reached the driver completely/with valid -+ * checksums. Only useful in combination with receival of (injected) bad data. -+ */ -+static noinline bool ssh_ptl_should_drop_nak_packet(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_nak_packet, TRUE); -+ -+/** -+ * ssh_ptl_should_drop_dsq_packet() - Error injection hook to drop sequenced -+ * data packet. -+ * -+ * Useful to test re-transmit timeout of the driver. If the data packet has not -+ * been ACKed after a certain time, the driver should re-transmit the packet up -+ * to limited number of times defined in SSH_PTL_MAX_PACKET_TRIES. -+ */ -+static noinline bool ssh_ptl_should_drop_dsq_packet(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_dsq_packet, TRUE); -+ -+/** -+ * ssh_ptl_should_fail_write() - Error injection hook to make -+ * serdev_device_write() fail. -+ * -+ * Hook to simulate errors in serdev_device_write when transmitting packets. -+ */ -+static noinline int ssh_ptl_should_fail_write(void) -+{ -+ return 0; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_fail_write, ERRNO); -+ -+/** -+ * ssh_ptl_should_corrupt_tx_data() - Error injection hook to simulate invalid -+ * data being sent to the EC. -+ * -+ * Hook to simulate corrupt/invalid data being sent from host (driver) to EC. -+ * Causes the packet data to be actively corrupted by overwriting it with -+ * pre-defined values, such that it becomes invalid, causing the EC to respond -+ * with a NAK packet. Useful to test handling of NAK packets received by the -+ * driver. -+ */ -+static noinline bool ssh_ptl_should_corrupt_tx_data(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_tx_data, TRUE); -+ -+/** -+ * ssh_ptl_should_corrupt_rx_syn() - Error injection hook to simulate invalid -+ * data being sent by the EC. -+ * -+ * Hook to simulate invalid SYN bytes, i.e. an invalid start of messages and -+ * test handling thereof in the driver. -+ */ -+static noinline bool ssh_ptl_should_corrupt_rx_syn(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_syn, TRUE); -+ -+/** -+ * ssh_ptl_should_corrupt_rx_data() - Error injection hook to simulate invalid -+ * data being sent by the EC. -+ * -+ * Hook to simulate invalid data/checksum of the message frame and test handling -+ * thereof in the driver. -+ */ -+static noinline bool ssh_ptl_should_corrupt_rx_data(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_data, TRUE); -+ -+static bool __ssh_ptl_should_drop_ack_packet(struct ssh_packet *packet) -+{ -+ if (likely(!ssh_ptl_should_drop_ack_packet())) -+ return false; -+ -+ trace_ssam_ei_tx_drop_ack_packet(packet); -+ ptl_info(packet->ptl, "packet error injection: dropping ACK packet %p\n", -+ packet); -+ -+ return true; -+} -+ -+static bool __ssh_ptl_should_drop_nak_packet(struct ssh_packet *packet) -+{ -+ if (likely(!ssh_ptl_should_drop_nak_packet())) -+ return false; -+ -+ trace_ssam_ei_tx_drop_nak_packet(packet); -+ ptl_info(packet->ptl, "packet error injection: dropping NAK packet %p\n", -+ packet); -+ -+ return true; -+} -+ -+static bool __ssh_ptl_should_drop_dsq_packet(struct ssh_packet *packet) -+{ -+ if (likely(!ssh_ptl_should_drop_dsq_packet())) -+ return false; -+ -+ trace_ssam_ei_tx_drop_dsq_packet(packet); -+ ptl_info(packet->ptl, -+ "packet error injection: dropping sequenced data packet %p\n", -+ packet); -+ -+ return true; -+} -+ -+static bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) -+{ -+ /* Ignore packets that don't carry any data (i.e. flush). */ -+ if (!packet->data.ptr || !packet->data.len) -+ return false; -+ -+ switch (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)]) { -+ case SSH_FRAME_TYPE_ACK: -+ return __ssh_ptl_should_drop_ack_packet(packet); -+ -+ case SSH_FRAME_TYPE_NAK: -+ return __ssh_ptl_should_drop_nak_packet(packet); -+ -+ case SSH_FRAME_TYPE_DATA_SEQ: -+ return __ssh_ptl_should_drop_dsq_packet(packet); -+ -+ default: -+ return false; -+ } -+} -+ -+static int ssh_ptl_write_buf(struct ssh_ptl *ptl, struct ssh_packet *packet, -+ const unsigned char *buf, size_t count) -+{ -+ int status; -+ -+ status = ssh_ptl_should_fail_write(); -+ if (unlikely(status)) { -+ trace_ssam_ei_tx_fail_write(packet, status); -+ ptl_info(packet->ptl, -+ "packet error injection: simulating transmit error %d, packet %p\n", -+ status, packet); -+ -+ return status; -+ } -+ -+ return serdev_device_write_buf(ptl->serdev, buf, count); -+} -+ -+static void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) -+{ -+ /* Ignore packets that don't carry any data (i.e. flush). */ -+ if (!packet->data.ptr || !packet->data.len) -+ return; -+ -+ /* Only allow sequenced data packets to be modified. */ -+ if (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)] != SSH_FRAME_TYPE_DATA_SEQ) -+ return; -+ -+ if (likely(!ssh_ptl_should_corrupt_tx_data())) -+ return; -+ -+ trace_ssam_ei_tx_corrupt_data(packet); -+ ptl_info(packet->ptl, -+ "packet error injection: simulating invalid transmit data on packet %p\n", -+ packet); -+ -+ /* -+ * NB: The value 0xb3 has been chosen more or less randomly so that it -+ * doesn't have any (major) overlap with the SYN bytes (aa 55) and is -+ * non-trivial (i.e. non-zero, non-0xff). -+ */ -+ memset(packet->data.ptr, 0xb3, packet->data.len); -+} -+ -+static void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, -+ struct ssam_span *data) -+{ -+ struct ssam_span frame; -+ -+ /* Check if there actually is something to corrupt. */ -+ if (!sshp_find_syn(data, &frame)) -+ return; -+ -+ if (likely(!ssh_ptl_should_corrupt_rx_syn())) -+ return; -+ -+ trace_ssam_ei_rx_corrupt_syn(data->len); -+ -+ data->ptr[1] = 0xb3; /* Set second byte of SYN to "random" value. */ -+} -+ -+static void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, -+ struct ssam_span *frame) -+{ -+ size_t payload_len, message_len; -+ struct ssh_frame *sshf; -+ -+ /* Ignore incomplete messages, will get handled once it's complete. */ -+ if (frame->len < SSH_MESSAGE_LENGTH(0)) -+ return; -+ -+ /* Ignore incomplete messages, part 2. */ -+ payload_len = get_unaligned_le16(&frame->ptr[SSH_MSGOFFSET_FRAME(len)]); -+ message_len = SSH_MESSAGE_LENGTH(payload_len); -+ if (frame->len < message_len) -+ return; -+ -+ if (likely(!ssh_ptl_should_corrupt_rx_data())) -+ return; -+ -+ sshf = (struct ssh_frame *)&frame->ptr[SSH_MSGOFFSET_FRAME(type)]; -+ trace_ssam_ei_rx_corrupt_data(sshf); -+ -+ /* -+ * Flip bits in first byte of payload checksum. This is basically -+ * equivalent to a payload/frame data error without us having to worry -+ * about (the, arguably pretty small, probability of) accidental -+ * checksum collisions. -+ */ -+ frame->ptr[frame->len - 2] = ~frame->ptr[frame->len - 2]; -+} -+ -+#else /* CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION */ -+ -+static inline bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) -+{ -+ return false; -+} -+ -+static inline int ssh_ptl_write_buf(struct ssh_ptl *ptl, -+ struct ssh_packet *packet, -+ const unsigned char *buf, -+ size_t count) -+{ -+ return serdev_device_write_buf(ptl->serdev, buf, count); -+} -+ -+static inline void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) -+{ -+} -+ -+static inline void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, -+ struct ssam_span *data) -+{ -+} -+ -+static inline void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, -+ struct ssam_span *frame) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION */ -+ - static void __ssh_ptl_packet_release(struct kref *kref) - { - struct ssh_packet *p = container_of(kref, struct ssh_packet, refcnt); -@@ -776,6 +1057,13 @@ static int ssh_ptl_tx_packet(struct ssh_ptl *ptl, struct ssh_packet *packet) - if (unlikely(!packet->data.ptr)) - return 0; - -+ /* Error injection: drop packet to simulate transmission problem. */ -+ if (ssh_ptl_should_drop_packet(packet)) -+ return 0; -+ -+ /* Error injection: simulate invalid packet data. */ -+ ssh_ptl_tx_inject_invalid_data(packet); -+ - ptl_dbg(ptl, "tx: sending data (length: %zu)\n", packet->data.len); - print_hex_dump_debug("tx: ", DUMP_PREFIX_OFFSET, 16, 1, - packet->data.ptr, packet->data.len, false); -@@ -787,7 +1075,7 @@ static int ssh_ptl_tx_packet(struct ssh_ptl *ptl, struct ssh_packet *packet) - buf = packet->data.ptr + offset; - len = packet->data.len - offset; - -- status = serdev_device_write_buf(ptl->serdev, buf, len); -+ status = ssh_ptl_write_buf(ptl, packet, buf, len); - if (status < 0) - return status; - -@@ -1400,6 +1688,9 @@ static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) - bool syn_found; - int status; - -+ /* Error injection: Modify data to simulate corrupt SYN bytes. */ -+ ssh_ptl_rx_inject_invalid_syn(ptl, source); -+ - /* Find SYN. */ - syn_found = sshp_find_syn(source, &aligned); - -@@ -1430,6 +1721,9 @@ static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) - if (unlikely(!syn_found)) - return aligned.ptr - source->ptr; - -+ /* Error injection: Modify data to simulate corruption. */ -+ ssh_ptl_rx_inject_invalid_data(ptl, &aligned); -+ - /* Parse and validate frame. */ - status = sshp_parse_frame(&ptl->serdev->dev, &aligned, &frame, &payload, - SSH_PTL_RX_BUF_LEN); -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c -index b649d71840fd..bb1c862411a2 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.c -@@ -8,6 +8,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -58,6 +59,30 @@ - */ - #define SSH_RTL_TX_BATCH 10 - -+#ifdef CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION -+ -+/** -+ * ssh_rtl_should_drop_response() - Error injection hook to drop request -+ * responses. -+ * -+ * Useful to cause request transmission timeouts in the driver by dropping the -+ * response to a request. -+ */ -+static noinline bool ssh_rtl_should_drop_response(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_rtl_should_drop_response, TRUE); -+ -+#else -+ -+static inline bool ssh_rtl_should_drop_response(void) -+{ -+ return false; -+} -+ -+#endif -+ - static u16 ssh_request_get_rqid(struct ssh_request *rqst) - { - return get_unaligned_le16(rqst->packet.data.ptr -@@ -459,6 +484,16 @@ static void ssh_rtl_complete(struct ssh_rtl *rtl, - if (unlikely(ssh_request_get_rqid(p) != rqid)) - continue; - -+ /* Simulate response timeout. */ -+ if (ssh_rtl_should_drop_response()) { -+ spin_unlock(&rtl->pending.lock); -+ -+ trace_ssam_ei_rx_drop_response(p); -+ rtl_info(rtl, "request error injection: dropping response for request %p\n", -+ &p->packet); -+ return; -+ } -+ - /* - * Mark as "response received" and "locked" as we're going to - * complete it. -diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h -index dcca8007d876..eb332bb53ae4 100644 ---- a/drivers/platform/surface/aggregator/trace.h -+++ b/drivers/platform/surface/aggregator/trace.h -@@ -565,6 +565,28 @@ DECLARE_EVENT_CLASS(ssam_pending_class, - TP_ARGS(pending) \ - ) - -+DECLARE_EVENT_CLASS(ssam_data_class, -+ TP_PROTO(size_t length), -+ -+ TP_ARGS(length), -+ -+ TP_STRUCT__entry( -+ __field(size_t, length) -+ ), -+ -+ TP_fast_assign( -+ __entry->length = length; -+ ), -+ -+ TP_printk("length=%zu", __entry->length) -+); -+ -+#define DEFINE_SSAM_DATA_EVENT(name) \ -+ DEFINE_EVENT(ssam_data_class, ssam_##name, \ -+ TP_PROTO(size_t length), \ -+ TP_ARGS(length) \ -+ ) -+ - DEFINE_SSAM_FRAME_EVENT(rx_frame_received); - DEFINE_SSAM_COMMAND_EVENT(rx_response_received); - DEFINE_SSAM_COMMAND_EVENT(rx_event_received); -@@ -583,6 +605,15 @@ DEFINE_SSAM_REQUEST_EVENT(request_cancel); - DEFINE_SSAM_REQUEST_STATUS_EVENT(request_complete); - DEFINE_SSAM_PENDING_EVENT(rtl_timeout_reap); - -+DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_ack_packet); -+DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_nak_packet); -+DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_dsq_packet); -+DEFINE_SSAM_PACKET_STATUS_EVENT(ei_tx_fail_write); -+DEFINE_SSAM_PACKET_EVENT(ei_tx_corrupt_data); -+DEFINE_SSAM_DATA_EVENT(ei_rx_corrupt_syn); -+DEFINE_SSAM_FRAME_EVENT(ei_rx_corrupt_data); -+DEFINE_SSAM_REQUEST_EVENT(ei_rx_drop_response); -+ - DEFINE_SSAM_ALLOC_EVENT(ctrl_packet_alloc); - DEFINE_SSAM_FREE_EVENT(ctrl_packet_free); - --- -2.31.1 - -From a641329806cb702fdacc704e295ef773c7afb795 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 21 Dec 2020 19:39:56 +0100 -Subject: [PATCH] platform/surface: aggregator: Add dedicated bus and device - type - -The Surface Aggregator EC provides varying functionality, depending on -the Surface device. To manage this functionality, we use dedicated -client devices for each subsystem or virtual device of the EC. While -some of these clients are described as standard devices in ACPI and the -corresponding client drivers can be implemented as platform drivers in -the kernel (making use of the controller API already present), many -devices, especially on newer Surface models, cannot be found there. - -To simplify management of these devices, we introduce a new bus and -client device type for the Surface Aggregator subsystem. The new device -type takes care of managing the controller reference, essentially -guaranteeing its validity for as long as the client device exists, thus -alleviating the need to manually establish device links for that purpose -in the client driver (as has to be done with the platform devices). - -Signed-off-by: Maximilian Luz -Reviewed-by: Hans de Goede -Link: https://lore.kernel.org/r/20201221183959.1186143-7-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/Kconfig | 12 + - drivers/platform/surface/aggregator/Makefile | 4 + - drivers/platform/surface/aggregator/bus.c | 415 ++++++++++++++++++ - drivers/platform/surface/aggregator/bus.h | 27 ++ - drivers/platform/surface/aggregator/core.c | 12 + - include/linux/mod_devicetable.h | 18 + - include/linux/surface_aggregator/device.h | 423 +++++++++++++++++++ - scripts/mod/devicetable-offsets.c | 8 + - scripts/mod/file2alias.c | 23 + - 9 files changed, 942 insertions(+) - create mode 100644 drivers/platform/surface/aggregator/bus.c - create mode 100644 drivers/platform/surface/aggregator/bus.h - create mode 100644 include/linux/surface_aggregator/device.h - -diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig -index e417bac67088..3aaeea9f0433 100644 ---- a/drivers/platform/surface/aggregator/Kconfig -+++ b/drivers/platform/surface/aggregator/Kconfig -@@ -41,6 +41,18 @@ menuconfig SURFACE_AGGREGATOR - module, y if you want to build it into the kernel and n if you don't - want it at all. - -+config SURFACE_AGGREGATOR_BUS -+ bool "Surface System Aggregator Module Bus" -+ depends on SURFACE_AGGREGATOR -+ default y -+ help -+ Expands the Surface System Aggregator Module (SSAM) core driver by -+ providing a dedicated bus and client-device type. -+ -+ This bus and device type are intended to provide and simplify support -+ for non-platform and non-ACPI SSAM devices, i.e. SSAM devices that are -+ not auto-detectable via the conventional means (e.g. ACPI). -+ - config SURFACE_AGGREGATOR_ERROR_INJECTION - bool "Surface System Aggregator Module Error Injection Capabilities" - depends on SURFACE_AGGREGATOR -diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile -index b8b24c8ec310..c112e2c7112b 100644 ---- a/drivers/platform/surface/aggregator/Makefile -+++ b/drivers/platform/surface/aggregator/Makefile -@@ -11,3 +11,7 @@ surface_aggregator-objs += ssh_parser.o - surface_aggregator-objs += ssh_packet_layer.o - surface_aggregator-objs += ssh_request_layer.o - surface_aggregator-objs += controller.o -+ -+ifeq ($(CONFIG_SURFACE_AGGREGATOR_BUS),y) -+surface_aggregator-objs += bus.o -+endif -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -new file mode 100644 -index 000000000000..a9b660af0917 ---- /dev/null -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -0,0 +1,415 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module bus and device integration. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#include -+#include -+ -+#include -+#include -+ -+#include "bus.h" -+#include "controller.h" -+ -+static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, -+ char *buf) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ return sysfs_emit(buf, "ssam:d%02Xc%02Xt%02Xi%02Xf%02X\n", -+ sdev->uid.domain, sdev->uid.category, sdev->uid.target, -+ sdev->uid.instance, sdev->uid.function); -+} -+static DEVICE_ATTR_RO(modalias); -+ -+static struct attribute *ssam_device_attrs[] = { -+ &dev_attr_modalias.attr, -+ NULL, -+}; -+ATTRIBUTE_GROUPS(ssam_device); -+ -+static int ssam_device_uevent(struct device *dev, struct kobj_uevent_env *env) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ return add_uevent_var(env, "MODALIAS=ssam:d%02Xc%02Xt%02Xi%02Xf%02X", -+ sdev->uid.domain, sdev->uid.category, -+ sdev->uid.target, sdev->uid.instance, -+ sdev->uid.function); -+} -+ -+static void ssam_device_release(struct device *dev) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ ssam_controller_put(sdev->ctrl); -+ kfree(sdev); -+} -+ -+const struct device_type ssam_device_type = { -+ .name = "surface_aggregator_device", -+ .groups = ssam_device_groups, -+ .uevent = ssam_device_uevent, -+ .release = ssam_device_release, -+}; -+EXPORT_SYMBOL_GPL(ssam_device_type); -+ -+/** -+ * ssam_device_alloc() - Allocate and initialize a SSAM client device. -+ * @ctrl: The controller under which the device should be added. -+ * @uid: The UID of the device to be added. -+ * -+ * Allocates and initializes a new client device. The parent of the device -+ * will be set to the controller device and the name will be set based on the -+ * UID. Note that the device still has to be added via ssam_device_add(). -+ * Refer to that function for more details. -+ * -+ * Return: Returns the newly allocated and initialized SSAM client device, or -+ * %NULL if it could not be allocated. -+ */ -+struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, -+ struct ssam_device_uid uid) -+{ -+ struct ssam_device *sdev; -+ -+ sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); -+ if (!sdev) -+ return NULL; -+ -+ device_initialize(&sdev->dev); -+ sdev->dev.bus = &ssam_bus_type; -+ sdev->dev.type = &ssam_device_type; -+ sdev->dev.parent = ssam_controller_device(ctrl); -+ sdev->ctrl = ssam_controller_get(ctrl); -+ sdev->uid = uid; -+ -+ dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x:%02x", -+ sdev->uid.domain, sdev->uid.category, sdev->uid.target, -+ sdev->uid.instance, sdev->uid.function); -+ -+ return sdev; -+} -+EXPORT_SYMBOL_GPL(ssam_device_alloc); -+ -+/** -+ * ssam_device_add() - Add a SSAM client device. -+ * @sdev: The SSAM client device to be added. -+ * -+ * Added client devices must be guaranteed to always have a valid and active -+ * controller. Thus, this function will fail with %-ENODEV if the controller -+ * of the device has not been initialized yet, has been suspended, or has been -+ * shut down. -+ * -+ * The caller of this function should ensure that the corresponding call to -+ * ssam_device_remove() is issued before the controller is shut down. If the -+ * added device is a direct child of the controller device (default), it will -+ * be automatically removed when the controller is shut down. -+ * -+ * By default, the controller device will become the parent of the newly -+ * created client device. The parent may be changed before ssam_device_add is -+ * called, but care must be taken that a) the correct suspend/resume ordering -+ * is guaranteed and b) the client device does not outlive the controller, -+ * i.e. that the device is removed before the controller is being shut down. -+ * In case these guarantees have to be manually enforced, please refer to the -+ * ssam_client_link() and ssam_client_bind() functions, which are intended to -+ * set up device-links for this purpose. -+ * -+ * Return: Returns zero on success, a negative error code on failure. -+ */ -+int ssam_device_add(struct ssam_device *sdev) -+{ -+ int status; -+ -+ /* -+ * Ensure that we can only add new devices to a controller if it has -+ * been started and is not going away soon. This works in combination -+ * with ssam_controller_remove_clients to ensure driver presence for the -+ * controller device, i.e. it ensures that the controller (sdev->ctrl) -+ * is always valid and can be used for requests as long as the client -+ * device we add here is registered as child under it. This essentially -+ * guarantees that the client driver can always expect the preconditions -+ * for functions like ssam_request_sync (controller has to be started -+ * and is not suspended) to hold and thus does not have to check for -+ * them. -+ * -+ * Note that for this to work, the controller has to be a parent device. -+ * If it is not a direct parent, care has to be taken that the device is -+ * removed via ssam_device_remove(), as device_unregister does not -+ * remove child devices recursively. -+ */ -+ ssam_controller_statelock(sdev->ctrl); -+ -+ if (sdev->ctrl->state != SSAM_CONTROLLER_STARTED) { -+ ssam_controller_stateunlock(sdev->ctrl); -+ return -ENODEV; -+ } -+ -+ status = device_add(&sdev->dev); -+ -+ ssam_controller_stateunlock(sdev->ctrl); -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_device_add); -+ -+/** -+ * ssam_device_remove() - Remove a SSAM client device. -+ * @sdev: The device to remove. -+ * -+ * Removes and unregisters the provided SSAM client device. -+ */ -+void ssam_device_remove(struct ssam_device *sdev) -+{ -+ device_unregister(&sdev->dev); -+} -+EXPORT_SYMBOL_GPL(ssam_device_remove); -+ -+/** -+ * ssam_device_id_compatible() - Check if a device ID matches a UID. -+ * @id: The device ID as potential match. -+ * @uid: The device UID matching against. -+ * -+ * Check if the given ID is a match for the given UID, i.e. if a device with -+ * the provided UID is compatible to the given ID following the match rules -+ * described in its &ssam_device_id.match_flags member. -+ * -+ * Return: Returns %true if the given UID is compatible to the match rule -+ * described by the given ID, %false otherwise. -+ */ -+static bool ssam_device_id_compatible(const struct ssam_device_id *id, -+ struct ssam_device_uid uid) -+{ -+ if (id->domain != uid.domain || id->category != uid.category) -+ return false; -+ -+ if ((id->match_flags & SSAM_MATCH_TARGET) && id->target != uid.target) -+ return false; -+ -+ if ((id->match_flags & SSAM_MATCH_INSTANCE) && id->instance != uid.instance) -+ return false; -+ -+ if ((id->match_flags & SSAM_MATCH_FUNCTION) && id->function != uid.function) -+ return false; -+ -+ return true; -+} -+ -+/** -+ * ssam_device_id_is_null() - Check if a device ID is null. -+ * @id: The device ID to check. -+ * -+ * Check if a given device ID is null, i.e. all zeros. Used to check for the -+ * end of ``MODULE_DEVICE_TABLE(ssam, ...)`` or similar lists. -+ * -+ * Return: Returns %true if the given ID represents a null ID, %false -+ * otherwise. -+ */ -+static bool ssam_device_id_is_null(const struct ssam_device_id *id) -+{ -+ return id->match_flags == 0 && -+ id->domain == 0 && -+ id->category == 0 && -+ id->target == 0 && -+ id->instance == 0 && -+ id->function == 0 && -+ id->driver_data == 0; -+} -+ -+/** -+ * ssam_device_id_match() - Find the matching ID table entry for the given UID. -+ * @table: The table to search in. -+ * @uid: The UID to matched against the individual table entries. -+ * -+ * Find the first match for the provided device UID in the provided ID table -+ * and return it. Returns %NULL if no match could be found. -+ */ -+const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table, -+ const struct ssam_device_uid uid) -+{ -+ const struct ssam_device_id *id; -+ -+ for (id = table; !ssam_device_id_is_null(id); ++id) -+ if (ssam_device_id_compatible(id, uid)) -+ return id; -+ -+ return NULL; -+} -+EXPORT_SYMBOL_GPL(ssam_device_id_match); -+ -+/** -+ * ssam_device_get_match() - Find and return the ID matching the device in the -+ * ID table of the bound driver. -+ * @dev: The device for which to get the matching ID table entry. -+ * -+ * Find the fist match for the UID of the device in the ID table of the -+ * currently bound driver and return it. Returns %NULL if the device does not -+ * have a driver bound to it, the driver does not have match_table (i.e. it is -+ * %NULL), or there is no match in the driver's match_table. -+ * -+ * This function essentially calls ssam_device_id_match() with the ID table of -+ * the bound device driver and the UID of the device. -+ * -+ * Return: Returns the first match for the UID of the device in the device -+ * driver's match table, or %NULL if no such match could be found. -+ */ -+const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev) -+{ -+ const struct ssam_device_driver *sdrv; -+ -+ sdrv = to_ssam_device_driver(dev->dev.driver); -+ if (!sdrv) -+ return NULL; -+ -+ if (!sdrv->match_table) -+ return NULL; -+ -+ return ssam_device_id_match(sdrv->match_table, dev->uid); -+} -+EXPORT_SYMBOL_GPL(ssam_device_get_match); -+ -+/** -+ * ssam_device_get_match_data() - Find the ID matching the device in the -+ * ID table of the bound driver and return its ``driver_data`` member. -+ * @dev: The device for which to get the match data. -+ * -+ * Find the fist match for the UID of the device in the ID table of the -+ * corresponding driver and return its driver_data. Returns %NULL if the -+ * device does not have a driver bound to it, the driver does not have -+ * match_table (i.e. it is %NULL), there is no match in the driver's -+ * match_table, or the match does not have any driver_data. -+ * -+ * This function essentially calls ssam_device_get_match() and, if any match -+ * could be found, returns its ``struct ssam_device_id.driver_data`` member. -+ * -+ * Return: Returns the driver data associated with the first match for the UID -+ * of the device in the device driver's match table, or %NULL if no such match -+ * could be found. -+ */ -+const void *ssam_device_get_match_data(const struct ssam_device *dev) -+{ -+ const struct ssam_device_id *id; -+ -+ id = ssam_device_get_match(dev); -+ if (!id) -+ return NULL; -+ -+ return (const void *)id->driver_data; -+} -+EXPORT_SYMBOL_GPL(ssam_device_get_match_data); -+ -+static int ssam_bus_match(struct device *dev, struct device_driver *drv) -+{ -+ struct ssam_device_driver *sdrv = to_ssam_device_driver(drv); -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (!is_ssam_device(dev)) -+ return 0; -+ -+ return !!ssam_device_id_match(sdrv->match_table, sdev->uid); -+} -+ -+static int ssam_bus_probe(struct device *dev) -+{ -+ return to_ssam_device_driver(dev->driver) -+ ->probe(to_ssam_device(dev)); -+} -+ -+static int ssam_bus_remove(struct device *dev) -+{ -+ struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver); -+ -+ if (sdrv->remove) -+ sdrv->remove(to_ssam_device(dev)); -+ -+ return 0; -+} -+ -+struct bus_type ssam_bus_type = { -+ .name = "surface_aggregator", -+ .match = ssam_bus_match, -+ .probe = ssam_bus_probe, -+ .remove = ssam_bus_remove, -+}; -+EXPORT_SYMBOL_GPL(ssam_bus_type); -+ -+/** -+ * __ssam_device_driver_register() - Register a SSAM client device driver. -+ * @sdrv: The driver to register. -+ * @owner: The module owning the provided driver. -+ * -+ * Please refer to the ssam_device_driver_register() macro for the normal way -+ * to register a driver from inside its owning module. -+ */ -+int __ssam_device_driver_register(struct ssam_device_driver *sdrv, -+ struct module *owner) -+{ -+ sdrv->driver.owner = owner; -+ sdrv->driver.bus = &ssam_bus_type; -+ -+ /* force drivers to async probe so I/O is possible in probe */ -+ sdrv->driver.probe_type = PROBE_PREFER_ASYNCHRONOUS; -+ -+ return driver_register(&sdrv->driver); -+} -+EXPORT_SYMBOL_GPL(__ssam_device_driver_register); -+ -+/** -+ * ssam_device_driver_unregister - Unregister a SSAM device driver. -+ * @sdrv: The driver to unregister. -+ */ -+void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) -+{ -+ driver_unregister(&sdrv->driver); -+} -+EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); -+ -+static int ssam_remove_device(struct device *dev, void *_data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (is_ssam_device(dev)) -+ ssam_device_remove(sdev); -+ -+ return 0; -+} -+ -+/** -+ * ssam_controller_remove_clients() - Remove SSAM client devices registered as -+ * direct children under the given controller. -+ * @ctrl: The controller to remove all direct clients for. -+ * -+ * Remove all SSAM client devices registered as direct children under the -+ * given controller. Note that this only accounts for direct children of the -+ * controller device. This does not take care of any client devices where the -+ * parent device has been manually set before calling ssam_device_add. Refer -+ * to ssam_device_add()/ssam_device_remove() for more details on those cases. -+ * -+ * To avoid new devices being added in parallel to this call, the main -+ * controller lock (not statelock) must be held during this (and if -+ * necessary, any subsequent deinitialization) call. -+ */ -+void ssam_controller_remove_clients(struct ssam_controller *ctrl) -+{ -+ struct device *dev; -+ -+ dev = ssam_controller_device(ctrl); -+ device_for_each_child_reverse(dev, NULL, ssam_remove_device); -+} -+ -+/** -+ * ssam_bus_register() - Register and set-up the SSAM client device bus. -+ */ -+int ssam_bus_register(void) -+{ -+ return bus_register(&ssam_bus_type); -+} -+ -+/** -+ * ssam_bus_unregister() - Unregister the SSAM client device bus. -+ */ -+void ssam_bus_unregister(void) -+{ -+ return bus_unregister(&ssam_bus_type); -+} -diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h -new file mode 100644 -index 000000000000..7712baaed6a5 ---- /dev/null -+++ b/drivers/platform/surface/aggregator/bus.h -@@ -0,0 +1,27 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Surface System Aggregator Module bus and device integration. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#ifndef _SURFACE_AGGREGATOR_BUS_H -+#define _SURFACE_AGGREGATOR_BUS_H -+ -+#include -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+ -+void ssam_controller_remove_clients(struct ssam_controller *ctrl); -+ -+int ssam_bus_register(void); -+void ssam_bus_unregister(void); -+ -+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+static inline void ssam_controller_remove_clients(struct ssam_controller *ctrl) {} -+static inline int ssam_bus_register(void) { return 0; } -+static inline void ssam_bus_unregister(void) {} -+ -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+#endif /* _SURFACE_AGGREGATOR_BUS_H */ -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -index b6a9dea53592..8dc2c267bcd6 100644 ---- a/drivers/platform/surface/aggregator/core.c -+++ b/drivers/platform/surface/aggregator/core.c -@@ -22,6 +22,8 @@ - #include - - #include -+ -+#include "bus.h" - #include "controller.h" - - #define CREATE_TRACE_POINTS -@@ -739,6 +741,9 @@ static void ssam_serial_hub_remove(struct serdev_device *serdev) - sysfs_remove_group(&serdev->dev.kobj, &ssam_sam_group); - ssam_controller_lock(ctrl); - -+ /* Remove all client devices. */ -+ ssam_controller_remove_clients(ctrl); -+ - /* Act as if suspending to silence events. */ - status = ssam_ctrl_notif_display_off(ctrl); - if (status) { -@@ -791,6 +796,10 @@ static int __init ssam_core_init(void) - { - int status; - -+ status = ssam_bus_register(); -+ if (status) -+ goto err_bus; -+ - status = ssh_ctrl_packet_cache_init(); - if (status) - goto err_cpkg; -@@ -810,6 +819,8 @@ static int __init ssam_core_init(void) - err_evitem: - ssh_ctrl_packet_cache_destroy(); - err_cpkg: -+ ssam_bus_unregister(); -+err_bus: - return status; - } - module_init(ssam_core_init); -@@ -819,6 +830,7 @@ static void __exit ssam_core_exit(void) - serdev_device_driver_unregister(&ssam_serial_hub); - ssam_event_item_cache_destroy(); - ssh_ctrl_packet_cache_destroy(); -+ ssam_bus_unregister(); - } - module_exit(ssam_core_exit); - -diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h -index c425290b21e2..935060955152 100644 ---- a/include/linux/mod_devicetable.h -+++ b/include/linux/mod_devicetable.h -@@ -846,4 +846,22 @@ struct auxiliary_device_id { - kernel_ulong_t driver_data; - }; - -+/* Surface System Aggregator Module */ -+ -+#define SSAM_MATCH_TARGET 0x1 -+#define SSAM_MATCH_INSTANCE 0x2 -+#define SSAM_MATCH_FUNCTION 0x4 -+ -+struct ssam_device_id { -+ __u8 match_flags; -+ -+ __u8 domain; -+ __u8 category; -+ __u8 target; -+ __u8 instance; -+ __u8 function; -+ -+ kernel_ulong_t driver_data; -+}; -+ - #endif /* LINUX_MOD_DEVICETABLE_H */ -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -new file mode 100644 -index 000000000000..02f3e06c0a60 ---- /dev/null -+++ b/include/linux/surface_aggregator/device.h -@@ -0,0 +1,423 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Surface System Aggregator Module (SSAM) bus and client-device subsystem. -+ * -+ * Main interface for the surface-aggregator bus, surface-aggregator client -+ * devices, and respective drivers building on top of the SSAM controller. -+ * Provides support for non-platform/non-ACPI SSAM clients via dedicated -+ * subsystem. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H -+#define _LINUX_SURFACE_AGGREGATOR_DEVICE_H -+ -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- Surface System Aggregator Module bus. --------------------------------- */ -+ -+/** -+ * enum ssam_device_domain - SAM device domain. -+ * @SSAM_DOMAIN_VIRTUAL: Virtual device. -+ * @SSAM_DOMAIN_SERIALHUB: Physical device connected via Surface Serial Hub. -+ */ -+enum ssam_device_domain { -+ SSAM_DOMAIN_VIRTUAL = 0x00, -+ SSAM_DOMAIN_SERIALHUB = 0x01, -+}; -+ -+/** -+ * enum ssam_virtual_tc - Target categories for the virtual SAM domain. -+ * @SSAM_VIRTUAL_TC_HUB: Device hub category. -+ */ -+enum ssam_virtual_tc { -+ SSAM_VIRTUAL_TC_HUB = 0x00, -+}; -+ -+/** -+ * struct ssam_device_uid - Unique identifier for SSAM device. -+ * @domain: Domain of the device. -+ * @category: Target category of the device. -+ * @target: Target ID of the device. -+ * @instance: Instance ID of the device. -+ * @function: Sub-function of the device. This field can be used to split a -+ * single SAM device into multiple virtual subdevices to separate -+ * different functionality of that device and allow one driver per -+ * such functionality. -+ */ -+struct ssam_device_uid { -+ u8 domain; -+ u8 category; -+ u8 target; -+ u8 instance; -+ u8 function; -+}; -+ -+/* -+ * Special values for device matching. -+ * -+ * These values are intended to be used with SSAM_DEVICE(), SSAM_VDEV(), and -+ * SSAM_SDEV() exclusively. Specifically, they are used to initialize the -+ * match_flags member of the device ID structure. Do not use them directly -+ * with struct ssam_device_id or struct ssam_device_uid. -+ */ -+#define SSAM_ANY_TID 0xffff -+#define SSAM_ANY_IID 0xffff -+#define SSAM_ANY_FUN 0xffff -+ -+/** -+ * SSAM_DEVICE() - Initialize a &struct ssam_device_id with the given -+ * parameters. -+ * @d: Domain of the device. -+ * @cat: Target category of the device. -+ * @tid: Target ID of the device. -+ * @iid: Instance ID of the device. -+ * @fun: Sub-function of the device. -+ * -+ * Initializes a &struct ssam_device_id with the given parameters. See &struct -+ * ssam_device_uid for details regarding the parameters. The special values -+ * %SSAM_ANY_TID, %SSAM_ANY_IID, and %SSAM_ANY_FUN can be used to specify that -+ * matching should ignore target ID, instance ID, and/or sub-function, -+ * respectively. This macro initializes the ``match_flags`` field based on the -+ * given parameters. -+ * -+ * Note: The parameters @d and @cat must be valid &u8 values, the parameters -+ * @tid, @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, -+ * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not -+ * allowed. -+ */ -+#define SSAM_DEVICE(d, cat, tid, iid, fun) \ -+ .match_flags = (((tid) != SSAM_ANY_TID) ? SSAM_MATCH_TARGET : 0) \ -+ | (((iid) != SSAM_ANY_IID) ? SSAM_MATCH_INSTANCE : 0) \ -+ | (((fun) != SSAM_ANY_FUN) ? SSAM_MATCH_FUNCTION : 0), \ -+ .domain = d, \ -+ .category = cat, \ -+ .target = ((tid) != SSAM_ANY_TID) ? (tid) : 0, \ -+ .instance = ((iid) != SSAM_ANY_IID) ? (iid) : 0, \ -+ .function = ((fun) != SSAM_ANY_FUN) ? (fun) : 0 \ -+ -+/** -+ * SSAM_VDEV() - Initialize a &struct ssam_device_id as virtual device with -+ * the given parameters. -+ * @cat: Target category of the device. -+ * @tid: Target ID of the device. -+ * @iid: Instance ID of the device. -+ * @fun: Sub-function of the device. -+ * -+ * Initializes a &struct ssam_device_id with the given parameters in the -+ * virtual domain. See &struct ssam_device_uid for details regarding the -+ * parameters. The special values %SSAM_ANY_TID, %SSAM_ANY_IID, and -+ * %SSAM_ANY_FUN can be used to specify that matching should ignore target ID, -+ * instance ID, and/or sub-function, respectively. This macro initializes the -+ * ``match_flags`` field based on the given parameters. -+ * -+ * Note: The parameter @cat must be a valid &u8 value, the parameters @tid, -+ * @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, -+ * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not -+ * allowed. -+ */ -+#define SSAM_VDEV(cat, tid, iid, fun) \ -+ SSAM_DEVICE(SSAM_DOMAIN_VIRTUAL, SSAM_VIRTUAL_TC_##cat, tid, iid, fun) -+ -+/** -+ * SSAM_SDEV() - Initialize a &struct ssam_device_id as physical SSH device -+ * with the given parameters. -+ * @cat: Target category of the device. -+ * @tid: Target ID of the device. -+ * @iid: Instance ID of the device. -+ * @fun: Sub-function of the device. -+ * -+ * Initializes a &struct ssam_device_id with the given parameters in the SSH -+ * domain. See &struct ssam_device_uid for details regarding the parameters. -+ * The special values %SSAM_ANY_TID, %SSAM_ANY_IID, and %SSAM_ANY_FUN can be -+ * used to specify that matching should ignore target ID, instance ID, and/or -+ * sub-function, respectively. This macro initializes the ``match_flags`` -+ * field based on the given parameters. -+ * -+ * Note: The parameter @cat must be a valid &u8 value, the parameters @tid, -+ * @iid, and @fun must be either valid &u8 values or %SSAM_ANY_TID, -+ * %SSAM_ANY_IID, or %SSAM_ANY_FUN, respectively. Other non-&u8 values are not -+ * allowed. -+ */ -+#define SSAM_SDEV(cat, tid, iid, fun) \ -+ SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) -+ -+/** -+ * struct ssam_device - SSAM client device. -+ * @dev: Driver model representation of the device. -+ * @ctrl: SSAM controller managing this device. -+ * @uid: UID identifying the device. -+ */ -+struct ssam_device { -+ struct device dev; -+ struct ssam_controller *ctrl; -+ -+ struct ssam_device_uid uid; -+}; -+ -+/** -+ * struct ssam_device_driver - SSAM client device driver. -+ * @driver: Base driver model structure. -+ * @match_table: Match table specifying which devices the driver should bind to. -+ * @probe: Called when the driver is being bound to a device. -+ * @remove: Called when the driver is being unbound from the device. -+ */ -+struct ssam_device_driver { -+ struct device_driver driver; -+ -+ const struct ssam_device_id *match_table; -+ -+ int (*probe)(struct ssam_device *sdev); -+ void (*remove)(struct ssam_device *sdev); -+}; -+ -+extern struct bus_type ssam_bus_type; -+extern const struct device_type ssam_device_type; -+ -+/** -+ * is_ssam_device() - Check if the given device is a SSAM client device. -+ * @d: The device to test the type of. -+ * -+ * Return: Returns %true if the specified device is of type &struct -+ * ssam_device, i.e. the device type points to %ssam_device_type, and %false -+ * otherwise. -+ */ -+static inline bool is_ssam_device(struct device *d) -+{ -+ return d->type == &ssam_device_type; -+} -+ -+/** -+ * to_ssam_device() - Casts the given device to a SSAM client device. -+ * @d: The device to cast. -+ * -+ * Casts the given &struct device to a &struct ssam_device. The caller has to -+ * ensure that the given device is actually enclosed in a &struct ssam_device, -+ * e.g. by calling is_ssam_device(). -+ * -+ * Return: Returns a pointer to the &struct ssam_device wrapping the given -+ * device @d. -+ */ -+static inline struct ssam_device *to_ssam_device(struct device *d) -+{ -+ return container_of(d, struct ssam_device, dev); -+} -+ -+/** -+ * to_ssam_device_driver() - Casts the given device driver to a SSAM client -+ * device driver. -+ * @d: The driver to cast. -+ * -+ * Casts the given &struct device_driver to a &struct ssam_device_driver. The -+ * caller has to ensure that the given driver is actually enclosed in a -+ * &struct ssam_device_driver. -+ * -+ * Return: Returns the pointer to the &struct ssam_device_driver wrapping the -+ * given device driver @d. -+ */ -+static inline -+struct ssam_device_driver *to_ssam_device_driver(struct device_driver *d) -+{ -+ return container_of(d, struct ssam_device_driver, driver); -+} -+ -+const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table, -+ const struct ssam_device_uid uid); -+ -+const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev); -+ -+const void *ssam_device_get_match_data(const struct ssam_device *dev); -+ -+struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, -+ struct ssam_device_uid uid); -+ -+int ssam_device_add(struct ssam_device *sdev); -+void ssam_device_remove(struct ssam_device *sdev); -+ -+/** -+ * ssam_device_get() - Increment reference count of SSAM client device. -+ * @sdev: The device to increment the reference count of. -+ * -+ * Increments the reference count of the given SSAM client device by -+ * incrementing the reference count of the enclosed &struct device via -+ * get_device(). -+ * -+ * See ssam_device_put() for the counter-part of this function. -+ * -+ * Return: Returns the device provided as input. -+ */ -+static inline struct ssam_device *ssam_device_get(struct ssam_device *sdev) -+{ -+ return sdev ? to_ssam_device(get_device(&sdev->dev)) : NULL; -+} -+ -+/** -+ * ssam_device_put() - Decrement reference count of SSAM client device. -+ * @sdev: The device to decrement the reference count of. -+ * -+ * Decrements the reference count of the given SSAM client device by -+ * decrementing the reference count of the enclosed &struct device via -+ * put_device(). -+ * -+ * See ssam_device_get() for the counter-part of this function. -+ */ -+static inline void ssam_device_put(struct ssam_device *sdev) -+{ -+ if (sdev) -+ put_device(&sdev->dev); -+} -+ -+/** -+ * ssam_device_get_drvdata() - Get driver-data of SSAM client device. -+ * @sdev: The device to get the driver-data from. -+ * -+ * Return: Returns the driver-data of the given device, previously set via -+ * ssam_device_set_drvdata(). -+ */ -+static inline void *ssam_device_get_drvdata(struct ssam_device *sdev) -+{ -+ return dev_get_drvdata(&sdev->dev); -+} -+ -+/** -+ * ssam_device_set_drvdata() - Set driver-data of SSAM client device. -+ * @sdev: The device to set the driver-data of. -+ * @data: The data to set the device's driver-data pointer to. -+ */ -+static inline void ssam_device_set_drvdata(struct ssam_device *sdev, void *data) -+{ -+ dev_set_drvdata(&sdev->dev, data); -+} -+ -+int __ssam_device_driver_register(struct ssam_device_driver *d, struct module *o); -+void ssam_device_driver_unregister(struct ssam_device_driver *d); -+ -+/** -+ * ssam_device_driver_register() - Register a SSAM client device driver. -+ * @drv: The driver to register. -+ */ -+#define ssam_device_driver_register(drv) \ -+ __ssam_device_driver_register(drv, THIS_MODULE) -+ -+/** -+ * module_ssam_device_driver() - Helper macro for SSAM device driver -+ * registration. -+ * @drv: The driver managed by this module. -+ * -+ * Helper macro to register a SSAM device driver via module_init() and -+ * module_exit(). This macro may only be used once per module and replaces the -+ * aforementioned definitions. -+ */ -+#define module_ssam_device_driver(drv) \ -+ module_driver(drv, ssam_device_driver_register, \ -+ ssam_device_driver_unregister) -+ -+ -+/* -- Helpers for client-device requests. ----------------------------------- */ -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_CL_N() - Define synchronous client-device SAM -+ * request function with neither argument nor return value. -+ * @name: Name of the generated function. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request having neither argument nor return value. Device -+ * specifying parameters are not hard-coded, but instead are provided via the -+ * client device, specifically its UID, supplied when calling this function. -+ * The generated function takes care of setting up the request struct, buffer -+ * allocation, as well as execution of the request itself, returning once the -+ * request has been fully completed. The required transport buffer will be -+ * allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_device *sdev)``, -+ * returning the status of the request, which is zero on success and negative -+ * on failure. The ``sdev`` parameter specifies both the target device of the -+ * request and by association the controller via which the request is sent. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_CL_N(name, spec...) \ -+ SSAM_DEFINE_SYNC_REQUEST_MD_N(__raw_##name, spec) \ -+ int name(struct ssam_device *sdev) \ -+ { \ -+ return __raw_##name(sdev->ctrl, sdev->uid.target, \ -+ sdev->uid.instance); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_CL_W() - Define synchronous client-device SAM -+ * request function with argument. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking an argument of type @atype and having no -+ * return value. Device specifying parameters are not hard-coded, but instead -+ * are provided via the client device, specifically its UID, supplied when -+ * calling this function. The generated function takes care of setting up the -+ * request struct, buffer allocation, as well as execution of the request -+ * itself, returning once the request has been fully completed. The required -+ * transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_device *sdev, -+ * const atype *arg)``, returning the status of the request, which is zero on -+ * success and negative on failure. The ``sdev`` parameter specifies both the -+ * target device of the request and by association the controller via which -+ * the request is sent. The request's argument is specified via the ``arg`` -+ * pointer. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_CL_W(name, atype, spec...) \ -+ SSAM_DEFINE_SYNC_REQUEST_MD_W(__raw_##name, atype, spec) \ -+ int name(struct ssam_device *sdev, const atype *arg) \ -+ { \ -+ return __raw_##name(sdev->ctrl, sdev->uid.target, \ -+ sdev->uid.instance, arg); \ -+ } -+ -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_CL_R() - Define synchronous client-device SAM -+ * request function with return value. -+ * @name: Name of the generated function. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by -+ * @spec, with the request taking no argument but having a return value of -+ * type @rtype. Device specifying parameters are not hard-coded, but instead -+ * are provided via the client device, specifically its UID, supplied when -+ * calling this function. The generated function takes care of setting up the -+ * request struct, buffer allocation, as well as execution of the request -+ * itself, returning once the request has been fully completed. The required -+ * transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``int name(struct ssam_device *sdev, -+ * rtype *ret)``, returning the status of the request, which is zero on -+ * success and negative on failure. The ``sdev`` parameter specifies both the -+ * target device of the request and by association the controller via which -+ * the request is sent. The request's return value is written to the memory -+ * pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \ -+ SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \ -+ int name(struct ssam_device *sdev, rtype *ret) \ -+ { \ -+ return __raw_##name(sdev->ctrl, sdev->uid.target, \ -+ sdev->uid.instance, ret); \ -+ } -+ -+#endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ -diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c -index e377f52dbfa3..f078eeb0a961 100644 ---- a/scripts/mod/devicetable-offsets.c -+++ b/scripts/mod/devicetable-offsets.c -@@ -246,5 +246,13 @@ int main(void) - DEVID(auxiliary_device_id); - DEVID_FIELD(auxiliary_device_id, name); - -+ DEVID(ssam_device_id); -+ DEVID_FIELD(ssam_device_id, match_flags); -+ DEVID_FIELD(ssam_device_id, domain); -+ DEVID_FIELD(ssam_device_id, category); -+ DEVID_FIELD(ssam_device_id, target); -+ DEVID_FIELD(ssam_device_id, instance); -+ DEVID_FIELD(ssam_device_id, function); -+ - return 0; - } -diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c -index fb4827027536..d21d2871387b 100644 ---- a/scripts/mod/file2alias.c -+++ b/scripts/mod/file2alias.c -@@ -1375,6 +1375,28 @@ static int do_auxiliary_entry(const char *filename, void *symval, char *alias) - return 1; - } - -+/* -+ * Looks like: ssam:dNcNtNiNfN -+ * -+ * N is exactly 2 digits, where each is an upper-case hex digit. -+ */ -+static int do_ssam_entry(const char *filename, void *symval, char *alias) -+{ -+ DEF_FIELD(symval, ssam_device_id, match_flags); -+ DEF_FIELD(symval, ssam_device_id, domain); -+ DEF_FIELD(symval, ssam_device_id, category); -+ DEF_FIELD(symval, ssam_device_id, target); -+ DEF_FIELD(symval, ssam_device_id, instance); -+ DEF_FIELD(symval, ssam_device_id, function); -+ -+ sprintf(alias, "ssam:d%02Xc%02X", domain, category); -+ ADD(alias, "t", match_flags & SSAM_MATCH_TARGET, target); -+ ADD(alias, "i", match_flags & SSAM_MATCH_INSTANCE, instance); -+ ADD(alias, "f", match_flags & SSAM_MATCH_FUNCTION, function); -+ -+ return 1; -+} -+ - /* Does namelen bytes of name exactly match the symbol? */ - static bool sym_is(const char *name, unsigned namelen, const char *symbol) - { -@@ -1450,6 +1472,7 @@ static const struct devtable devtable[] = { - {"wmi", SIZE_wmi_device_id, do_wmi_entry}, - {"mhi", SIZE_mhi_device_id, do_mhi_entry}, - {"auxiliary", SIZE_auxiliary_device_id, do_auxiliary_entry}, -+ {"ssam", SIZE_ssam_device_id, do_ssam_entry}, - }; - - /* Create MODULE_ALIAS() statements. --- -2.31.1 - -From 772762592f466285da77b9e4fab5d4761d6cabe8 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 21 Dec 2020 19:39:57 +0100 -Subject: [PATCH] docs: driver-api: Add Surface Aggregator subsystem - documentation - -Add documentation for the Surface Aggregator subsystem and its client -drivers, giving an overview of the subsystem, its use-cases, its -internal structure and internal API, as well as its external API for -writing client drivers. - -Signed-off-by: Maximilian Luz -Reviewed-by: Hans de Goede -Link: https://lore.kernel.org/r/20201221183959.1186143-8-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - Documentation/driver-api/index.rst | 1 + - .../surface_aggregator/client-api.rst | 38 ++ - .../driver-api/surface_aggregator/client.rst | 393 ++++++++++++ - .../surface_aggregator/clients/index.rst | 10 + - .../driver-api/surface_aggregator/index.rst | 21 + - .../surface_aggregator/internal-api.rst | 67 ++ - .../surface_aggregator/internal.rst | 577 ++++++++++++++++++ - .../surface_aggregator/overview.rst | 77 +++ - .../driver-api/surface_aggregator/ssh.rst | 344 +++++++++++ - MAINTAINERS | 1 + - 10 files changed, 1529 insertions(+) - create mode 100644 Documentation/driver-api/surface_aggregator/client-api.rst - create mode 100644 Documentation/driver-api/surface_aggregator/client.rst - create mode 100644 Documentation/driver-api/surface_aggregator/clients/index.rst - create mode 100644 Documentation/driver-api/surface_aggregator/index.rst - create mode 100644 Documentation/driver-api/surface_aggregator/internal-api.rst - create mode 100644 Documentation/driver-api/surface_aggregator/internal.rst - create mode 100644 Documentation/driver-api/surface_aggregator/overview.rst - create mode 100644 Documentation/driver-api/surface_aggregator/ssh.rst - -diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst -index 2456d0a97ed8..9d9af54d68c5 100644 ---- a/Documentation/driver-api/index.rst -+++ b/Documentation/driver-api/index.rst -@@ -99,6 +99,7 @@ available subsections can be seen below. - rfkill - serial/index - sm501 -+ surface_aggregator/index - switchtec - sync_file - vfio-mediated-device -diff --git a/Documentation/driver-api/surface_aggregator/client-api.rst b/Documentation/driver-api/surface_aggregator/client-api.rst -new file mode 100644 -index 000000000000..8e0b000d0e64 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/client-api.rst -@@ -0,0 +1,38 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+=============================== -+Client Driver API Documentation -+=============================== -+ -+.. contents:: -+ :depth: 2 -+ -+ -+Serial Hub Communication -+======================== -+ -+.. kernel-doc:: include/linux/surface_aggregator/serial_hub.h -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/ssh_packet_layer.c -+ :export: -+ -+ -+Controller and Core Interface -+============================= -+ -+.. kernel-doc:: include/linux/surface_aggregator/controller.h -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/controller.c -+ :export: -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/core.c -+ :export: -+ -+ -+Client Bus and Client Device API -+================================ -+ -+.. kernel-doc:: include/linux/surface_aggregator/device.h -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/bus.c -+ :export: -diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst -new file mode 100644 -index 000000000000..26d13085a117 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/client.rst -@@ -0,0 +1,393 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |ssam_controller| replace:: :c:type:`struct ssam_controller ` -+.. |ssam_device| replace:: :c:type:`struct ssam_device ` -+.. |ssam_device_driver| replace:: :c:type:`struct ssam_device_driver ` -+.. |ssam_client_bind| replace:: :c:func:`ssam_client_bind` -+.. |ssam_client_link| replace:: :c:func:`ssam_client_link` -+.. |ssam_get_controller| replace:: :c:func:`ssam_get_controller` -+.. |ssam_controller_get| replace:: :c:func:`ssam_controller_get` -+.. |ssam_controller_put| replace:: :c:func:`ssam_controller_put` -+.. |ssam_device_alloc| replace:: :c:func:`ssam_device_alloc` -+.. |ssam_device_add| replace:: :c:func:`ssam_device_add` -+.. |ssam_device_remove| replace:: :c:func:`ssam_device_remove` -+.. |ssam_device_driver_register| replace:: :c:func:`ssam_device_driver_register` -+.. |ssam_device_driver_unregister| replace:: :c:func:`ssam_device_driver_unregister` -+.. |module_ssam_device_driver| replace:: :c:func:`module_ssam_device_driver` -+.. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` -+.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` -+.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` -+.. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` -+.. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` -+ -+ -+====================== -+Writing Client Drivers -+====================== -+ -+For the API documentation, refer to: -+ -+.. toctree:: -+ :maxdepth: 2 -+ -+ client-api -+ -+ -+Overview -+======== -+ -+Client drivers can be set up in two main ways, depending on how the -+corresponding device is made available to the system. We specifically -+differentiate between devices that are presented to the system via one of -+the conventional ways, e.g. as platform devices via ACPI, and devices that -+are non-discoverable and instead need to be explicitly provided by some -+other mechanism, as discussed further below. -+ -+ -+Non-SSAM Client Drivers -+======================= -+ -+All communication with the SAM EC is handled via the |ssam_controller| -+representing that EC to the kernel. Drivers targeting a non-SSAM device (and -+thus not being a |ssam_device_driver|) need to explicitly establish a -+connection/relation to that controller. This can be done via the -+|ssam_client_bind| function. Said function returns a reference to the SSAM -+controller, but, more importantly, also establishes a device link between -+client device and controller (this can also be done separate via -+|ssam_client_link|). It is important to do this, as it, first, guarantees -+that the returned controller is valid for use in the client driver for as -+long as this driver is bound to its device, i.e. that the driver gets -+unbound before the controller ever becomes invalid, and, second, as it -+ensures correct suspend/resume ordering. This setup should be done in the -+driver's probe function, and may be used to defer probing in case the SSAM -+subsystem is not ready yet, for example: -+ -+.. code-block:: c -+ -+ static int client_driver_probe(struct platform_device *pdev) -+ { -+ struct ssam_controller *ctrl; -+ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ // ... -+ -+ return 0; -+ } -+ -+The controller may be separately obtained via |ssam_get_controller| and its -+lifetime be guaranteed via |ssam_controller_get| and |ssam_controller_put|. -+Note that none of these functions, however, guarantee that the controller -+will not be shut down or suspended. These functions essentially only operate -+on the reference, i.e. only guarantee a bare minimum of accessibility -+without any guarantees at all on practical operability. -+ -+ -+Adding SSAM Devices -+=================== -+ -+If a device does not already exist/is not already provided via conventional -+means, it should be provided as |ssam_device| via the SSAM client device -+hub. New devices can be added to this hub by entering their UID into the -+corresponding registry. SSAM devices can also be manually allocated via -+|ssam_device_alloc|, subsequently to which they have to be added via -+|ssam_device_add| and eventually removed via |ssam_device_remove|. By -+default, the parent of the device is set to the controller device provided -+for allocation, however this may be changed before the device is added. Note -+that, when changing the parent device, care must be taken to ensure that the -+controller lifetime and suspend/resume ordering guarantees, in the default -+setup provided through the parent-child relation, are preserved. If -+necessary, by use of |ssam_client_link| as is done for non-SSAM client -+drivers and described in more detail above. -+ -+A client device must always be removed by the party which added the -+respective device before the controller shuts down. Such removal can be -+guaranteed by linking the driver providing the SSAM device to the controller -+via |ssam_client_link|, causing it to unbind before the controller driver -+unbinds. Client devices registered with the controller as parent are -+automatically removed when the controller shuts down, but this should not be -+relied upon, especially as this does not extend to client devices with a -+different parent. -+ -+ -+SSAM Client Drivers -+=================== -+ -+SSAM client device drivers are, in essence, no different than other device -+driver types. They are represented via |ssam_device_driver| and bind to a -+|ssam_device| via its UID (:c:type:`struct ssam_device.uid `) -+member and the match table -+(:c:type:`struct ssam_device_driver.match_table `), -+which should be set when declaring the driver struct instance. Refer to the -+|SSAM_DEVICE| macro documentation for more details on how to define members -+of the driver's match table. -+ -+The UID for SSAM client devices consists of a ``domain``, a ``category``, -+a ``target``, an ``instance``, and a ``function``. The ``domain`` is used -+differentiate between physical SAM devices -+(:c:type:`SSAM_DOMAIN_SERIALHUB `), i.e. devices that can -+be accessed via the Surface Serial Hub, and virtual ones -+(:c:type:`SSAM_DOMAIN_VIRTUAL `), such as client-device -+hubs, that have no real representation on the SAM EC and are solely used on -+the kernel/driver-side. For physical devices, ``category`` represents the -+target category, ``target`` the target ID, and ``instance`` the instance ID -+used to access the physical SAM device. In addition, ``function`` references -+a specific device functionality, but has no meaning to the SAM EC. The -+(default) name of a client device is generated based on its UID. -+ -+A driver instance can be registered via |ssam_device_driver_register| and -+unregistered via |ssam_device_driver_unregister|. For convenience, the -+|module_ssam_device_driver| macro may be used to define module init- and -+exit-functions registering the driver. -+ -+The controller associated with a SSAM client device can be found in its -+:c:type:`struct ssam_device.ctrl ` member. This reference is -+guaranteed to be valid for at least as long as the client driver is bound, -+but should also be valid for as long as the client device exists. Note, -+however, that access outside of the bound client driver must ensure that the -+controller device is not suspended while making any requests or -+(un-)registering event notifiers (and thus should generally be avoided). This -+is guaranteed when the controller is accessed from inside the bound client -+driver. -+ -+ -+Making Synchronous Requests -+=========================== -+ -+Synchronous requests are (currently) the main form of host-initiated -+communication with the EC. There are a couple of ways to define and execute -+such requests, however, most of them boil down to something similar as shown -+in the example below. This example defines a write-read request, meaning -+that the caller provides an argument to the SAM EC and receives a response. -+The caller needs to know the (maximum) length of the response payload and -+provide a buffer for it. -+ -+Care must be taken to ensure that any command payload data passed to the SAM -+EC is provided in little-endian format and, similarly, any response payload -+data received from it is converted from little-endian to host endianness. -+ -+.. code-block:: c -+ -+ int perform_request(struct ssam_controller *ctrl, u32 arg, u32 *ret) -+ { -+ struct ssam_request rqst; -+ struct ssam_response resp; -+ int status; -+ -+ /* Convert request argument to little-endian. */ -+ __le32 arg_le = cpu_to_le32(arg); -+ __le32 ret_le = cpu_to_le32(0); -+ -+ /* -+ * Initialize request specification. Replace this with your values. -+ * The rqst.payload field may be NULL if rqst.length is zero, -+ * indicating that the request does not have any argument. -+ * -+ * Note: The request parameters used here are not valid, i.e. -+ * they do not correspond to an actual SAM/EC request. -+ */ -+ rqst.target_category = SSAM_SSH_TC_SAM; -+ rqst.target_id = 0x01; -+ rqst.command_id = 0x02; -+ rqst.instance_id = 0x03; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(arg_le); -+ rqst.payload = (u8 *)&arg_le; -+ -+ /* Initialize request response. */ -+ resp.capacity = sizeof(ret_le); -+ resp.length = 0; -+ resp.pointer = (u8 *)&ret_le; -+ -+ /* -+ * Perform actual request. The response pointer may be null in case -+ * the request does not have any response. This must be consistent -+ * with the SSAM_REQUEST_HAS_RESPONSE flag set in the specification -+ * above. -+ */ -+ status = ssam_request_sync(ctrl, &rqst, &resp); -+ -+ /* -+ * Alternatively use -+ * -+ * ssam_request_sync_onstack(ctrl, &rqst, &resp, sizeof(arg_le)); -+ * -+ * to perform the request, allocating the message buffer directly -+ * on the stack as opposed to allocation via kzalloc(). -+ */ -+ -+ /* -+ * Convert request response back to native format. Note that in the -+ * error case, this value is not touched by the SSAM core, i.e. -+ * 'ret_le' will be zero as specified in its initialization. -+ */ -+ *ret = le32_to_cpu(ret_le); -+ -+ return status; -+ } -+ -+Note that |ssam_request_sync| in its essence is a wrapper over lower-level -+request primitives, which may also be used to perform requests. Refer to its -+implementation and documentation for more details. -+ -+An arguably more user-friendly way of defining such functions is by using -+one of the generator macros, for example via: -+ -+.. code-block:: c -+ -+ SSAM_DEFINE_SYNC_REQUEST_W(__ssam_tmp_perf_mode_set, __le32, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .target_id = 0x01, -+ .command_id = 0x03, -+ .instance_id = 0x00, -+ }); -+ -+This example defines a function -+ -+.. code-block:: c -+ -+ int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg); -+ -+executing the specified request, with the controller passed in when calling -+said function. In this example, the argument is provided via the ``arg`` -+pointer. Note that the generated function allocates the message buffer on -+the stack. Thus, if the argument provided via the request is large, these -+kinds of macros should be avoided. Also note that, in contrast to the -+previous non-macro example, this function does not do any endianness -+conversion, which has to be handled by the caller. Apart from those -+differences the function generated by the macro is similar to the one -+provided in the non-macro example above. -+ -+The full list of such function-generating macros is -+ -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_N` for requests without return value and -+ without argument. -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_R` for requests with return value but no -+ argument. -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_W` for requests without return value but -+ with argument. -+ -+Refer to their respective documentation for more details. For each one of -+these macros, a special variant is provided, which targets request types -+applicable to multiple instances of the same device type: -+ -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_N` -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_R` -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_MD_W` -+ -+The difference of those macros to the previously mentioned versions is, that -+the device target and instance IDs are not fixed for the generated function, -+but instead have to be provided by the caller of said function. -+ -+Additionally, variants for direct use with client devices, i.e. -+|ssam_device|, are also provided. These can, for example, be used as -+follows: -+ -+.. code-block:: c -+ -+ SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x01, -+ }); -+ -+This invocation of the macro defines a function -+ -+.. code-block:: c -+ -+ int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret); -+ -+executing the specified request, using the device IDs and controller given -+in the client device. The full list of such macros for client devices is: -+ -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_N` -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_R` -+- :c:func:`SSAM_DEFINE_SYNC_REQUEST_CL_W` -+ -+ -+Handling Events -+=============== -+ -+To receive events from the SAM EC, an event notifier must be registered for -+the desired event via |ssam_notifier_register|. The notifier must be -+unregistered via |ssam_notifier_unregister| once it is not required any -+more. -+ -+Event notifiers are registered by providing (at minimum) a callback to call -+in case an event has been received, the registry specifying how the event -+should be enabled, an event ID specifying for which target category and, -+optionally and depending on the registry used, for which instance ID events -+should be enabled, and finally, flags describing how the EC will send these -+events. If the specific registry does not enable events by instance ID, the -+instance ID must be set to zero. Additionally, a priority for the respective -+notifier may be specified, which determines its order in relation to any -+other notifier registered for the same target category. -+ -+By default, event notifiers will receive all events for the specific target -+category, regardless of the instance ID specified when registering the -+notifier. The core may be instructed to only call a notifier if the target -+ID or instance ID (or both) of the event match the ones implied by the -+notifier IDs (in case of target ID, the target ID of the registry), by -+providing an event mask (see |ssam_event_mask|). -+ -+In general, the target ID of the registry is also the target ID of the -+enabled event (with the notable exception being keyboard input events on the -+Surface Laptop 1 and 2, which are enabled via a registry with target ID 1, -+but provide events with target ID 2). -+ -+A full example for registering an event notifier and handling received -+events is provided below: -+ -+.. code-block:: c -+ -+ u32 notifier_callback(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+ { -+ int status = ... -+ -+ /* Handle the event here ... */ -+ -+ /* Convert return value and indicate that we handled the event. */ -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+ } -+ -+ int setup_notifier(struct ssam_device *sdev, -+ struct ssam_event_notifier *nf) -+ { -+ /* Set priority wrt. other handlers of same target category. */ -+ nf->base.priority = 1; -+ -+ /* Set event/notifier callback. */ -+ nf->base.fn = notifier_callback; -+ -+ /* Specify event registry, i.e. how events get enabled/disabled. */ -+ nf->event.reg = SSAM_EVENT_REGISTRY_KIP; -+ -+ /* Specify which event to enable/disable */ -+ nf->event.id.target_category = sdev->uid.category; -+ nf->event.id.instance = sdev->uid.instance; -+ -+ /* -+ * Specify for which events the notifier callback gets executed. -+ * This essentially tells the core if it can skip notifiers that -+ * don't have target or instance IDs matching those of the event. -+ */ -+ nf->event.mask = SSAM_EVENT_MASK_STRICT; -+ -+ /* Specify event flags. */ -+ nf->event.flags = SSAM_EVENT_SEQUENCED; -+ -+ return ssam_notifier_register(sdev->ctrl, nf); -+ } -+ -+Multiple event notifiers can be registered for the same event. The event -+handler core takes care of enabling and disabling events when notifiers are -+registered and unregistered, by keeping track of how many notifiers for a -+specific event (combination of registry, event target category, and event -+instance ID) are currently registered. This means that a specific event will -+be enabled when the first notifier for it is being registered and disabled -+when the last notifier for it is being unregistered. Note that the event -+flags are therefore only used on the first registered notifier, however, one -+should take care that notifiers for a specific event are always registered -+with the same flag and it is considered a bug to do otherwise. -diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst -new file mode 100644 -index 000000000000..31e026d96102 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/clients/index.rst -@@ -0,0 +1,10 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+=========================== -+Client Driver Documentation -+=========================== -+ -+This is the documentation for client drivers themselves. Refer to -+:doc:`../client` for documentation on how to write client drivers. -+ -+.. Place documentation for individual client drivers here. -diff --git a/Documentation/driver-api/surface_aggregator/index.rst b/Documentation/driver-api/surface_aggregator/index.rst -new file mode 100644 -index 000000000000..6f3e1094904d ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/index.rst -@@ -0,0 +1,21 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+======================================= -+Surface System Aggregator Module (SSAM) -+======================================= -+ -+.. toctree:: -+ :maxdepth: 2 -+ -+ overview -+ client -+ clients/index -+ ssh -+ internal -+ -+.. only:: subproject and html -+ -+ Indices -+ ======= -+ -+ * :ref:`genindex` -diff --git a/Documentation/driver-api/surface_aggregator/internal-api.rst b/Documentation/driver-api/surface_aggregator/internal-api.rst -new file mode 100644 -index 000000000000..639a67b5a392 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/internal-api.rst -@@ -0,0 +1,67 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+========================== -+Internal API Documentation -+========================== -+ -+.. contents:: -+ :depth: 2 -+ -+ -+Packet Transport Layer -+====================== -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/ssh_parser.h -+ :internal: -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/ssh_parser.c -+ :internal: -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/ssh_msgb.h -+ :internal: -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/ssh_packet_layer.h -+ :internal: -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/ssh_packet_layer.c -+ :internal: -+ -+ -+Request Transport Layer -+======================= -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/ssh_request_layer.h -+ :internal: -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/ssh_request_layer.c -+ :internal: -+ -+ -+Controller -+========== -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/controller.h -+ :internal: -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/controller.c -+ :internal: -+ -+ -+Client Device Bus -+================= -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/bus.c -+ :internal: -+ -+ -+Core -+==== -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/core.c -+ :internal: -+ -+ -+Trace Helpers -+============= -+ -+.. kernel-doc:: drivers/platform/surface/aggregator/trace.h -diff --git a/Documentation/driver-api/surface_aggregator/internal.rst b/Documentation/driver-api/surface_aggregator/internal.rst -new file mode 100644 -index 000000000000..72704734982a ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/internal.rst -@@ -0,0 +1,577 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |ssh_ptl| replace:: :c:type:`struct ssh_ptl ` -+.. |ssh_ptl_submit| replace:: :c:func:`ssh_ptl_submit` -+.. |ssh_ptl_cancel| replace:: :c:func:`ssh_ptl_cancel` -+.. |ssh_ptl_shutdown| replace:: :c:func:`ssh_ptl_shutdown` -+.. |ssh_ptl_rx_rcvbuf| replace:: :c:func:`ssh_ptl_rx_rcvbuf` -+.. |ssh_rtl| replace:: :c:type:`struct ssh_rtl ` -+.. |ssh_rtl_submit| replace:: :c:func:`ssh_rtl_submit` -+.. |ssh_rtl_cancel| replace:: :c:func:`ssh_rtl_cancel` -+.. |ssh_rtl_shutdown| replace:: :c:func:`ssh_rtl_shutdown` -+.. |ssh_packet| replace:: :c:type:`struct ssh_packet ` -+.. |ssh_packet_get| replace:: :c:func:`ssh_packet_get` -+.. |ssh_packet_put| replace:: :c:func:`ssh_packet_put` -+.. |ssh_packet_ops| replace:: :c:type:`struct ssh_packet_ops ` -+.. |ssh_packet_base_priority| replace:: :c:type:`enum ssh_packet_base_priority ` -+.. |ssh_packet_flags| replace:: :c:type:`enum ssh_packet_flags ` -+.. |SSH_PACKET_PRIORITY| replace:: :c:func:`SSH_PACKET_PRIORITY` -+.. |ssh_frame| replace:: :c:type:`struct ssh_frame ` -+.. |ssh_command| replace:: :c:type:`struct ssh_command ` -+.. |ssh_request| replace:: :c:type:`struct ssh_request ` -+.. |ssh_request_get| replace:: :c:func:`ssh_request_get` -+.. |ssh_request_put| replace:: :c:func:`ssh_request_put` -+.. |ssh_request_ops| replace:: :c:type:`struct ssh_request_ops ` -+.. |ssh_request_init| replace:: :c:func:`ssh_request_init` -+.. |ssh_request_flags| replace:: :c:type:`enum ssh_request_flags ` -+.. |ssam_controller| replace:: :c:type:`struct ssam_controller ` -+.. |ssam_device| replace:: :c:type:`struct ssam_device ` -+.. |ssam_device_driver| replace:: :c:type:`struct ssam_device_driver ` -+.. |ssam_client_bind| replace:: :c:func:`ssam_client_bind` -+.. |ssam_client_link| replace:: :c:func:`ssam_client_link` -+.. |ssam_request_sync| replace:: :c:type:`struct ssam_request_sync ` -+.. |ssam_event_registry| replace:: :c:type:`struct ssam_event_registry ` -+.. |ssam_event_id| replace:: :c:type:`struct ssam_event_id ` -+.. |ssam_nf| replace:: :c:type:`struct ssam_nf ` -+.. |ssam_nf_refcount_inc| replace:: :c:func:`ssam_nf_refcount_inc` -+.. |ssam_nf_refcount_dec| replace:: :c:func:`ssam_nf_refcount_dec` -+.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` -+.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` -+.. |ssam_cplt| replace:: :c:type:`struct ssam_cplt ` -+.. |ssam_event_queue| replace:: :c:type:`struct ssam_event_queue ` -+.. |ssam_request_sync_submit| replace:: :c:func:`ssam_request_sync_submit` -+ -+===================== -+Core Driver Internals -+===================== -+ -+Architectural overview of the Surface System Aggregator Module (SSAM) core -+and Surface Serial Hub (SSH) driver. For the API documentation, refer to: -+ -+.. toctree:: -+ :maxdepth: 2 -+ -+ internal-api -+ -+ -+Overview -+======== -+ -+The SSAM core implementation is structured in layers, somewhat following the -+SSH protocol structure: -+ -+Lower-level packet transport is implemented in the *packet transport layer -+(PTL)*, directly building on top of the serial device (serdev) -+infrastructure of the kernel. As the name indicates, this layer deals with -+the packet transport logic and handles things like packet validation, packet -+acknowledgment (ACKing), packet (retransmission) timeouts, and relaying -+packet payloads to higher-level layers. -+ -+Above this sits the *request transport layer (RTL)*. This layer is centered -+around command-type packet payloads, i.e. requests (sent from host to EC), -+responses of the EC to those requests, and events (sent from EC to host). -+It, specifically, distinguishes events from request responses, matches -+responses to their corresponding requests, and implements request timeouts. -+ -+The *controller* layer is building on top of this and essentially decides -+how request responses and, especially, events are dealt with. It provides an -+event notifier system, handles event activation/deactivation, provides a -+workqueue for event and asynchronous request completion, and also manages -+the message counters required for building command messages (``SEQ``, -+``RQID``). This layer basically provides a fundamental interface to the SAM -+EC for use in other kernel drivers. -+ -+While the controller layer already provides an interface for other kernel -+drivers, the client *bus* extends this interface to provide support for -+native SSAM devices, i.e. devices that are not defined in ACPI and not -+implemented as platform devices, via |ssam_device| and |ssam_device_driver| -+simplify management of client devices and client drivers. -+ -+Refer to :doc:`client` for documentation regarding the client device/driver -+API and interface options for other kernel drivers. It is recommended to -+familiarize oneself with that chapter and the :doc:`ssh` before continuing -+with the architectural overview below. -+ -+ -+Packet Transport Layer -+====================== -+ -+The packet transport layer is represented via |ssh_ptl| and is structured -+around the following key concepts: -+ -+Packets -+------- -+ -+Packets are the fundamental transmission unit of the SSH protocol. They are -+managed by the packet transport layer, which is essentially the lowest layer -+of the driver and is built upon by other components of the SSAM core. -+Packets to be transmitted by the SSAM core are represented via |ssh_packet| -+(in contrast, packets received by the core do not have any specific -+structure and are managed entirely via the raw |ssh_frame|). -+ -+This structure contains the required fields to manage the packet inside the -+transport layer, as well as a reference to the buffer containing the data to -+be transmitted (i.e. the message wrapped in |ssh_frame|). Most notably, it -+contains an internal reference count, which is used for managing its -+lifetime (accessible via |ssh_packet_get| and |ssh_packet_put|). When this -+counter reaches zero, the ``release()`` callback provided to the packet via -+its |ssh_packet_ops| reference is executed, which may then deallocate the -+packet or its enclosing structure (e.g. |ssh_request|). -+ -+In addition to the ``release`` callback, the |ssh_packet_ops| reference also -+provides a ``complete()`` callback, which is run once the packet has been -+completed and provides the status of this completion, i.e. zero on success -+or a negative errno value in case of an error. Once the packet has been -+submitted to the packet transport layer, the ``complete()`` callback is -+always guaranteed to be executed before the ``release()`` callback, i.e. the -+packet will always be completed, either successfully, with an error, or due -+to cancellation, before it will be released. -+ -+The state of a packet is managed via its ``state`` flags -+(|ssh_packet_flags|), which also contains the packet type. In particular, -+the following bits are noteworthy: -+ -+* ``SSH_PACKET_SF_LOCKED_BIT``: This bit is set when completion, either -+ through error or success, is imminent. It indicates that no further -+ references of the packet should be taken and any existing references -+ should be dropped as soon as possible. The process setting this bit is -+ responsible for removing any references to this packet from the packet -+ queue and pending set. -+ -+* ``SSH_PACKET_SF_COMPLETED_BIT``: This bit is set by the process running the -+ ``complete()`` callback and is used to ensure that this callback only runs -+ once. -+ -+* ``SSH_PACKET_SF_QUEUED_BIT``: This bit is set when the packet is queued on -+ the packet queue and cleared when it is dequeued. -+ -+* ``SSH_PACKET_SF_PENDING_BIT``: This bit is set when the packet is added to -+ the pending set and cleared when it is removed from it. -+ -+Packet Queue -+------------ -+ -+The packet queue is the first of the two fundamental collections in the -+packet transport layer. It is a priority queue, with priority of the -+respective packets based on the packet type (major) and number of tries -+(minor). See |SSH_PACKET_PRIORITY| for more details on the priority value. -+ -+All packets to be transmitted by the transport layer must be submitted to -+this queue via |ssh_ptl_submit|. Note that this includes control packets -+sent by the transport layer itself. Internally, data packets can be -+re-submitted to this queue due to timeouts or NAK packets sent by the EC. -+ -+Pending Set -+----------- -+ -+The pending set is the second of the two fundamental collections in the -+packet transport layer. It stores references to packets that have already -+been transmitted, but wait for acknowledgment (e.g. the corresponding ACK -+packet) by the EC. -+ -+Note that a packet may both be pending and queued if it has been -+re-submitted due to a packet acknowledgment timeout or NAK. On such a -+re-submission, packets are not removed from the pending set. -+ -+Transmitter Thread -+------------------ -+ -+The transmitter thread is responsible for most of the actual work regarding -+packet transmission. In each iteration, it (waits for and) checks if the -+next packet on the queue (if any) can be transmitted and, if so, removes it -+from the queue and increments its counter for the number of transmission -+attempts, i.e. tries. If the packet is sequenced, i.e. requires an ACK by -+the EC, the packet is added to the pending set. Next, the packet's data is -+submitted to the serdev subsystem. In case of an error or timeout during -+this submission, the packet is completed by the transmitter thread with the -+status value of the callback set accordingly. In case the packet is -+unsequenced, i.e. does not require an ACK by the EC, the packet is completed -+with success on the transmitter thread. -+ -+Transmission of sequenced packets is limited by the number of concurrently -+pending packets, i.e. a limit on how many packets may be waiting for an ACK -+from the EC in parallel. This limit is currently set to one (see :doc:`ssh` -+for the reasoning behind this). Control packets (i.e. ACK and NAK) can -+always be transmitted. -+ -+Receiver Thread -+--------------- -+ -+Any data received from the EC is put into a FIFO buffer for further -+processing. This processing happens on the receiver thread. The receiver -+thread parses and validates the received message into its |ssh_frame| and -+corresponding payload. It prepares and submits the necessary ACK (and on -+validation error or invalid data NAK) packets for the received messages. -+ -+This thread also handles further processing, such as matching ACK messages -+to the corresponding pending packet (via sequence ID) and completing it, as -+well as initiating re-submission of all currently pending packets on -+receival of a NAK message (re-submission in case of a NAK is similar to -+re-submission due to timeout, see below for more details on that). Note that -+the successful completion of a sequenced packet will always run on the -+receiver thread (whereas any failure-indicating completion will run on the -+process where the failure occurred). -+ -+Any payload data is forwarded via a callback to the next upper layer, i.e. -+the request transport layer. -+ -+Timeout Reaper -+-------------- -+ -+The packet acknowledgment timeout is a per-packet timeout for sequenced -+packets, started when the respective packet begins (re-)transmission (i.e. -+this timeout is armed once per transmission attempt on the transmitter -+thread). It is used to trigger re-submission or, when the number of tries -+has been exceeded, cancellation of the packet in question. -+ -+This timeout is handled via a dedicated reaper task, which is essentially a -+work item (re-)scheduled to run when the next packet is set to time out. The -+work item then checks the set of pending packets for any packets that have -+exceeded the timeout and, if there are any remaining packets, re-schedules -+itself to the next appropriate point in time. -+ -+If a timeout has been detected by the reaper, the packet will either be -+re-submitted if it still has some remaining tries left, or completed with -+``-ETIMEDOUT`` as status if not. Note that re-submission, in this case and -+triggered by receival of a NAK, means that the packet is added to the queue -+with a now incremented number of tries, yielding a higher priority. The -+timeout for the packet will be disabled until the next transmission attempt -+and the packet remains on the pending set. -+ -+Note that due to transmission and packet acknowledgment timeouts, the packet -+transport layer is always guaranteed to make progress, if only through -+timing out packets, and will never fully block. -+ -+Concurrency and Locking -+----------------------- -+ -+There are two main locks in the packet transport layer: One guarding access -+to the packet queue and one guarding access to the pending set. These -+collections may only be accessed and modified under the respective lock. If -+access to both collections is needed, the pending lock must be acquired -+before the queue lock to avoid deadlocks. -+ -+In addition to guarding the collections, after initial packet submission -+certain packet fields may only be accessed under one of the locks. -+Specifically, the packet priority must only be accessed while holding the -+queue lock and the packet timestamp must only be accessed while holding the -+pending lock. -+ -+Other parts of the packet transport layer are guarded independently. State -+flags are managed by atomic bit operations and, if necessary, memory -+barriers. Modifications to the timeout reaper work item and expiration date -+are guarded by their own lock. -+ -+The reference of the packet to the packet transport layer (``ptl``) is -+somewhat special. It is either set when the upper layer request is submitted -+or, if there is none, when the packet is first submitted. After it is set, -+it will not change its value. Functions that may run concurrently with -+submission, i.e. cancellation, can not rely on the ``ptl`` reference to be -+set. Access to it in these functions is guarded by ``READ_ONCE()``, whereas -+setting ``ptl`` is equally guarded with ``WRITE_ONCE()`` for symmetry. -+ -+Some packet fields may be read outside of the respective locks guarding -+them, specifically priority and state for tracing. In those cases, proper -+access is ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. Such -+read-only access is only allowed when stale values are not critical. -+ -+With respect to the interface for higher layers, packet submission -+(|ssh_ptl_submit|), packet cancellation (|ssh_ptl_cancel|), data receival -+(|ssh_ptl_rx_rcvbuf|), and layer shutdown (|ssh_ptl_shutdown|) may always be -+executed concurrently with respect to each other. Note that packet -+submission may not run concurrently with itself for the same packet. -+Equally, shutdown and data receival may also not run concurrently with -+themselves (but may run concurrently with each other). -+ -+ -+Request Transport Layer -+======================= -+ -+The request transport layer is represented via |ssh_rtl| and builds on top -+of the packet transport layer. It deals with requests, i.e. SSH packets sent -+by the host containing a |ssh_command| as frame payload. This layer -+separates responses to requests from events, which are also sent by the EC -+via a |ssh_command| payload. While responses are handled in this layer, -+events are relayed to the next upper layer, i.e. the controller layer, via -+the corresponding callback. The request transport layer is structured around -+the following key concepts: -+ -+Request -+------- -+ -+Requests are packets with a command-type payload, sent from host to EC to -+query data from or trigger an action on it (or both simultaneously). They -+are represented by |ssh_request|, wrapping the underlying |ssh_packet| -+storing its message data (i.e. SSH frame with command payload). Note that -+all top-level representations, e.g. |ssam_request_sync| are built upon this -+struct. -+ -+As |ssh_request| extends |ssh_packet|, its lifetime is also managed by the -+reference counter inside the packet struct (which can be accessed via -+|ssh_request_get| and |ssh_request_put|). Once the counter reaches zero, the -+``release()`` callback of the |ssh_request_ops| reference of the request is -+called. -+ -+Requests can have an optional response that is equally sent via a SSH -+message with command-type payload (from EC to host). The party constructing -+the request must know if a response is expected and mark this in the request -+flags provided to |ssh_request_init|, so that the request transport layer -+can wait for this response. -+ -+Similar to |ssh_packet|, |ssh_request| also has a ``complete()`` callback -+provided via its request ops reference and is guaranteed to be completed -+before it is released once it has been submitted to the request transport -+layer via |ssh_rtl_submit|. For a request without a response, successful -+completion will occur once the underlying packet has been successfully -+transmitted by the packet transport layer (i.e. from within the packet -+completion callback). For a request with response, successful completion -+will occur once the response has been received and matched to the request -+via its request ID (which happens on the packet layer's data-received -+callback running on the receiver thread). If the request is completed with -+an error, the status value will be set to the corresponding (negative) errno -+value. -+ -+The state of a request is again managed via its ``state`` flags -+(|ssh_request_flags|), which also encode the request type. In particular, -+the following bits are noteworthy: -+ -+* ``SSH_REQUEST_SF_LOCKED_BIT``: This bit is set when completion, either -+ through error or success, is imminent. It indicates that no further -+ references of the request should be taken and any existing references -+ should be dropped as soon as possible. The process setting this bit is -+ responsible for removing any references to this request from the request -+ queue and pending set. -+ -+* ``SSH_REQUEST_SF_COMPLETED_BIT``: This bit is set by the process running the -+ ``complete()`` callback and is used to ensure that this callback only runs -+ once. -+ -+* ``SSH_REQUEST_SF_QUEUED_BIT``: This bit is set when the request is queued on -+ the request queue and cleared when it is dequeued. -+ -+* ``SSH_REQUEST_SF_PENDING_BIT``: This bit is set when the request is added to -+ the pending set and cleared when it is removed from it. -+ -+Request Queue -+------------- -+ -+The request queue is the first of the two fundamental collections in the -+request transport layer. In contrast to the packet queue of the packet -+transport layer, it is not a priority queue and the simple first come first -+serve principle applies. -+ -+All requests to be transmitted by the request transport layer must be -+submitted to this queue via |ssh_rtl_submit|. Once submitted, requests may -+not be re-submitted, and will not be re-submitted automatically on timeout. -+Instead, the request is completed with a timeout error. If desired, the -+caller can create and submit a new request for another try, but it must not -+submit the same request again. -+ -+Pending Set -+----------- -+ -+The pending set is the second of the two fundamental collections in the -+request transport layer. This collection stores references to all pending -+requests, i.e. requests awaiting a response from the EC (similar to what the -+pending set of the packet transport layer does for packets). -+ -+Transmitter Task -+---------------- -+ -+The transmitter task is scheduled when a new request is available for -+transmission. It checks if the next request on the request queue can be -+transmitted and, if so, submits its underlying packet to the packet -+transport layer. This check ensures that only a limited number of -+requests can be pending, i.e. waiting for a response, at the same time. If -+the request requires a response, the request is added to the pending set -+before its packet is submitted. -+ -+Packet Completion Callback -+-------------------------- -+ -+The packet completion callback is executed once the underlying packet of a -+request has been completed. In case of an error completion, the -+corresponding request is completed with the error value provided in this -+callback. -+ -+On successful packet completion, further processing depends on the request. -+If the request expects a response, it is marked as transmitted and the -+request timeout is started. If the request does not expect a response, it is -+completed with success. -+ -+Data-Received Callback -+---------------------- -+ -+The data received callback notifies the request transport layer of data -+being received by the underlying packet transport layer via a data-type -+frame. In general, this is expected to be a command-type payload. -+ -+If the request ID of the command is one of the request IDs reserved for -+events (one to ``SSH_NUM_EVENTS``, inclusively), it is forwarded to the -+event callback registered in the request transport layer. If the request ID -+indicates a response to a request, the respective request is looked up in -+the pending set and, if found and marked as transmitted, completed with -+success. -+ -+Timeout Reaper -+-------------- -+ -+The request-response-timeout is a per-request timeout for requests expecting -+a response. It is used to ensure that a request does not wait indefinitely -+on a response from the EC and is started after the underlying packet has -+been successfully completed. -+ -+This timeout is, similar to the packet acknowledgment timeout on the packet -+transport layer, handled via a dedicated reaper task. This task is -+essentially a work-item (re-)scheduled to run when the next request is set -+to time out. The work item then scans the set of pending requests for any -+requests that have timed out and completes them with ``-ETIMEDOUT`` as -+status. Requests will not be re-submitted automatically. Instead, the issuer -+of the request must construct and submit a new request, if so desired. -+ -+Note that this timeout, in combination with packet transmission and -+acknowledgment timeouts, guarantees that the request layer will always make -+progress, even if only through timing out packets, and never fully block. -+ -+Concurrency and Locking -+----------------------- -+ -+Similar to the packet transport layer, there are two main locks in the -+request transport layer: One guarding access to the request queue and one -+guarding access to the pending set. These collections may only be accessed -+and modified under the respective lock. -+ -+Other parts of the request transport layer are guarded independently. State -+flags are (again) managed by atomic bit operations and, if necessary, memory -+barriers. Modifications to the timeout reaper work item and expiration date -+are guarded by their own lock. -+ -+Some request fields may be read outside of the respective locks guarding -+them, specifically the state for tracing. In those cases, proper access is -+ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. Such read-only -+access is only allowed when stale values are not critical. -+ -+With respect to the interface for higher layers, request submission -+(|ssh_rtl_submit|), request cancellation (|ssh_rtl_cancel|), and layer -+shutdown (|ssh_rtl_shutdown|) may always be executed concurrently with -+respect to each other. Note that request submission may not run concurrently -+with itself for the same request (and also may only be called once per -+request). Equally, shutdown may also not run concurrently with itself. -+ -+ -+Controller Layer -+================ -+ -+The controller layer extends on the request transport layer to provide an -+easy-to-use interface for client drivers. It is represented by -+|ssam_controller| and the SSH driver. While the lower level transport layers -+take care of transmitting and handling packets and requests, the controller -+layer takes on more of a management role. Specifically, it handles device -+initialization, power management, and event handling, including event -+delivery and registration via the (event) completion system (|ssam_cplt|). -+ -+Event Registration -+------------------ -+ -+In general, an event (or rather a class of events) has to be explicitly -+requested by the host before the EC will send it (HID input events seem to -+be the exception). This is done via an event-enable request (similarly, -+events should be disabled via an event-disable request once no longer -+desired). -+ -+The specific request used to enable (or disable) an event is given via an -+event registry, i.e. the governing authority of this event (so to speak), -+represented by |ssam_event_registry|. As parameters to this request, the -+target category and, depending on the event registry, instance ID of the -+event to be enabled must be provided. This (optional) instance ID must be -+zero if the registry does not use it. Together, target category and instance -+ID form the event ID, represented by |ssam_event_id|. In short, both, event -+registry and event ID, are required to uniquely identify a respective class -+of events. -+ -+Note that a further *request ID* parameter must be provided for the -+enable-event request. This parameter does not influence the class of events -+being enabled, but instead is set as the request ID (RQID) on each event of -+this class sent by the EC. It is used to identify events (as a limited -+number of request IDs is reserved for use in events only, specifically one -+to ``SSH_NUM_EVENTS`` inclusively) and also map events to their specific -+class. Currently, the controller always sets this parameter to the target -+category specified in |ssam_event_id|. -+ -+As multiple client drivers may rely on the same (or overlapping) classes of -+events and enable/disable calls are strictly binary (i.e. on/off), the -+controller has to manage access to these events. It does so via reference -+counting, storing the counter inside an RB-tree based mapping with event -+registry and ID as key (there is no known list of valid event registry and -+event ID combinations). See |ssam_nf|, |ssam_nf_refcount_inc|, and -+|ssam_nf_refcount_dec| for details. -+ -+This management is done together with notifier registration (described in -+the next section) via the top-level |ssam_notifier_register| and -+|ssam_notifier_unregister| functions. -+ -+Event Delivery -+-------------- -+ -+To receive events, a client driver has to register an event notifier via -+|ssam_notifier_register|. This increments the reference counter for that -+specific class of events (as detailed in the previous section), enables the -+class on the EC (if it has not been enabled already), and installs the -+provided notifier callback. -+ -+Notifier callbacks are stored in lists, with one (RCU) list per target -+category (provided via the event ID; NB: there is a fixed known number of -+target categories). There is no known association from the combination of -+event registry and event ID to the command data (target ID, target category, -+command ID, and instance ID) that can be provided by an event class, apart -+from target category and instance ID given via the event ID. -+ -+Note that due to the way notifiers are (or rather have to be) stored, client -+drivers may receive events that they have not requested and need to account -+for them. Specifically, they will, by default, receive all events from the -+same target category. To simplify dealing with this, filtering of events by -+target ID (provided via the event registry) and instance ID (provided via -+the event ID) can be requested when registering a notifier. This filtering -+is applied when iterating over the notifiers at the time they are executed. -+ -+All notifier callbacks are executed on a dedicated workqueue, the so-called -+completion workqueue. After an event has been received via the callback -+installed in the request layer (running on the receiver thread of the packet -+transport layer), it will be put on its respective event queue -+(|ssam_event_queue|). From this event queue the completion work item of that -+queue (running on the completion workqueue) will pick up the event and -+execute the notifier callback. This is done to avoid blocking on the -+receiver thread. -+ -+There is one event queue per combination of target ID and target category. -+This is done to ensure that notifier callbacks are executed in sequence for -+events of the same target ID and target category. Callbacks can be executed -+in parallel for events with a different combination of target ID and target -+category. -+ -+Concurrency and Locking -+----------------------- -+ -+Most of the concurrency related safety guarantees of the controller are -+provided by the lower-level request transport layer. In addition to this, -+event (un-)registration is guarded by its own lock. -+ -+Access to the controller state is guarded by the state lock. This lock is a -+read/write semaphore. The reader part can be used to ensure that the state -+does not change while functions depending on the state to stay the same -+(e.g. |ssam_notifier_register|, |ssam_notifier_unregister|, -+|ssam_request_sync_submit|, and derivatives) are executed and this guarantee -+is not already provided otherwise (e.g. through |ssam_client_bind| or -+|ssam_client_link|). The writer part guards any transitions that will change -+the state, i.e. initialization, destruction, suspension, and resumption. -+ -+The controller state may be accessed (read-only) outside the state lock for -+smoke-testing against invalid API usage (e.g. in |ssam_request_sync_submit|). -+Note that such checks are not supposed to (and will not) protect against all -+invalid usages, but rather aim to help catch them. In those cases, proper -+variable access is ensured by employing ``WRITE_ONCE()`` and ``READ_ONCE()``. -+ -+Assuming any preconditions on the state not changing have been satisfied, -+all non-initialization and non-shutdown functions may run concurrently with -+each other. This includes |ssam_notifier_register|, |ssam_notifier_unregister|, -+|ssam_request_sync_submit|, as well as all functions building on top of those. -diff --git a/Documentation/driver-api/surface_aggregator/overview.rst b/Documentation/driver-api/surface_aggregator/overview.rst -new file mode 100644 -index 000000000000..1e9d57e50063 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/overview.rst -@@ -0,0 +1,77 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+======== -+Overview -+======== -+ -+The Surface/System Aggregator Module (SAM, SSAM) is an (arguably *the*) -+embedded controller (EC) on Microsoft Surface devices. It has been originally -+introduced on 4th generation devices (Surface Pro 4, Surface Book 1), but -+its responsibilities and feature-set have since been expanded significantly -+with the following generations. -+ -+ -+Features and Integration -+======================== -+ -+Not much is currently known about SAM on 4th generation devices (Surface Pro -+4, Surface Book 1), due to the use of a different communication interface -+between host and EC (as detailed below). On 5th (Surface Pro 2017, Surface -+Book 2, Surface Laptop 1) and later generation devices, SAM is responsible -+for providing battery information (both current status and static values, -+such as maximum capacity etc.), as well as an assortment of temperature -+sensors (e.g. skin temperature) and cooling/performance-mode setting to the -+host. On the Surface Book 2, specifically, it additionally provides an -+interface for properly handling clipboard detachment (i.e. separating the -+display part from the keyboard part of the device), on the Surface Laptop 1 -+and 2 it is required for keyboard HID input. This HID subsystem has been -+restructured for 7th generation devices and on those, specifically Surface -+Laptop 3 and Surface Book 3, is responsible for all major HID input (i.e. -+keyboard and touchpad). -+ -+While features have not changed much on a coarse level since the 5th -+generation, internal interfaces have undergone some rather large changes. On -+5th and 6th generation devices, both battery and temperature information is -+exposed to ACPI via a shim driver (referred to as Surface ACPI Notify, or -+SAN), translating ACPI generic serial bus write-/read-accesses to SAM -+requests. On 7th generation devices, this additional layer is gone and these -+devices require a driver hooking directly into the SAM interface. Equally, -+on newer generations, less devices are declared in ACPI, making them a bit -+harder to discover and requiring us to hard-code a sort of device registry. -+Due to this, a SSAM bus and subsystem with client devices -+(:c:type:`struct ssam_device `) has been implemented. -+ -+ -+Communication -+============= -+ -+The type of communication interface between host and EC depends on the -+generation of the Surface device. On 4th generation devices, host and EC -+communicate via HID, specifically using a HID-over-I2C device, whereas on -+5th and later generations, communication takes place via a USART serial -+device. In accordance to the drivers found on other operating systems, we -+refer to the serial device and its driver as Surface Serial Hub (SSH). When -+needed, we differentiate between both types of SAM by referring to them as -+SAM-over-SSH and SAM-over-HID. -+ -+Currently, this subsystem only supports SAM-over-SSH. The SSH communication -+interface is described in more detail below. The HID interface has not been -+reverse engineered yet and it is, at the moment, unclear how many (and -+which) concepts of the SSH interface detailed below can be transferred to -+it. -+ -+Surface Serial Hub -+------------------ -+ -+As already elaborated above, the Surface Serial Hub (SSH) is the -+communication interface for SAM on 5th- and all later-generation Surface -+devices. On the highest level, communication can be separated into two main -+types: Requests, messages sent from host to EC that may trigger a direct -+response from the EC (explicitly associated with the request), and events -+(sometimes also referred to as notifications), sent from EC to host without -+being a direct response to a previous request. We may also refer to requests -+without response as commands. In general, events need to be enabled via one -+of multiple dedicated requests before they are sent by the EC. -+ -+See :doc:`ssh` for a more technical protocol documentation and -+:doc:`internal` for an overview of the internal driver architecture. -diff --git a/Documentation/driver-api/surface_aggregator/ssh.rst b/Documentation/driver-api/surface_aggregator/ssh.rst -new file mode 100644 -index 000000000000..bf007d6c9873 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/ssh.rst -@@ -0,0 +1,344 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |u8| replace:: :c:type:`u8 ` -+.. |u16| replace:: :c:type:`u16 ` -+.. |TYPE| replace:: ``TYPE`` -+.. |LEN| replace:: ``LEN`` -+.. |SEQ| replace:: ``SEQ`` -+.. |SYN| replace:: ``SYN`` -+.. |NAK| replace:: ``NAK`` -+.. |ACK| replace:: ``ACK`` -+.. |DATA| replace:: ``DATA`` -+.. |DATA_SEQ| replace:: ``DATA_SEQ`` -+.. |DATA_NSQ| replace:: ``DATA_NSQ`` -+.. |TC| replace:: ``TC`` -+.. |TID| replace:: ``TID`` -+.. |IID| replace:: ``IID`` -+.. |RQID| replace:: ``RQID`` -+.. |CID| replace:: ``CID`` -+ -+=========================== -+Surface Serial Hub Protocol -+=========================== -+ -+The Surface Serial Hub (SSH) is the central communication interface for the -+embedded Surface Aggregator Module controller (SAM or EC), found on newer -+Surface generations. We will refer to this protocol and interface as -+SAM-over-SSH, as opposed to SAM-over-HID for the older generations. -+ -+On Surface devices with SAM-over-SSH, SAM is connected to the host via UART -+and defined in ACPI as device with ID ``MSHW0084``. On these devices, -+significant functionality is provided via SAM, including access to battery -+and power information and events, thermal read-outs and events, and many -+more. For Surface Laptops, keyboard input is handled via HID directed -+through SAM, on the Surface Laptop 3 and Surface Book 3 this also includes -+touchpad input. -+ -+Note that the standard disclaimer for this subsystem also applies to this -+document: All of this has been reverse-engineered and may thus be erroneous -+and/or incomplete. -+ -+All CRCs used in the following are two-byte ``crc_ccitt_false(0xffff, ...)``. -+All multi-byte values are little-endian, there is no implicit padding between -+values. -+ -+ -+SSH Packet Protocol: Definitions -+================================ -+ -+The fundamental communication unit of the SSH protocol is a frame -+(:c:type:`struct ssh_frame `). A frame consists of the following -+fields, packed together and in order: -+ -+.. flat-table:: SSH Frame -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - |TYPE| -+ - |u8| -+ - Type identifier of the frame. -+ -+ * - |LEN| -+ - |u16| -+ - Length of the payload associated with the frame. -+ -+ * - |SEQ| -+ - |u8| -+ - Sequence ID (see explanation below). -+ -+Each frame structure is followed by a CRC over this structure. The CRC over -+the frame structure (|TYPE|, |LEN|, and |SEQ| fields) is placed directly -+after the frame structure and before the payload. The payload is followed by -+its own CRC (over all payload bytes). If the payload is not present (i.e. -+the frame has ``LEN=0``), the CRC of the payload is still present and will -+evaluate to ``0xffff``. The |LEN| field does not include any of the CRCs, it -+equals the number of bytes inbetween the CRC of the frame and the CRC of the -+payload. -+ -+Additionally, the following fixed two-byte sequences are used: -+ -+.. flat-table:: SSH Byte Sequences -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Name -+ - Value -+ - Description -+ -+ * - |SYN| -+ - ``[0xAA, 0x55]`` -+ - Synchronization bytes. -+ -+A message consists of |SYN|, followed by the frame (|TYPE|, |LEN|, |SEQ| and -+CRC) and, if specified in the frame (i.e. ``LEN > 0``), payload bytes, -+followed finally, regardless if the payload is present, the payload CRC. The -+messages corresponding to an exchange are, in part, identified by having the -+same sequence ID (|SEQ|), stored inside the frame (more on this in the next -+section). The sequence ID is a wrapping counter. -+ -+A frame can have the following types -+(:c:type:`enum ssh_frame_type `): -+ -+.. flat-table:: SSH Frame Types -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Name -+ - Value -+ - Short Description -+ -+ * - |NAK| -+ - ``0x04`` -+ - Sent on error in previously received message. -+ -+ * - |ACK| -+ - ``0x40`` -+ - Sent to acknowledge receival of |DATA| frame. -+ -+ * - |DATA_SEQ| -+ - ``0x80`` -+ - Sent to transfer data. Sequenced. -+ -+ * - |DATA_NSQ| -+ - ``0x00`` -+ - Same as |DATA_SEQ|, but does not need to be ACKed. -+ -+Both |NAK|- and |ACK|-type frames are used to control flow of messages and -+thus do not carry a payload. |DATA_SEQ|- and |DATA_NSQ|-type frames on the -+other hand must carry a payload. The flow sequence and interaction of -+different frame types will be described in more depth in the next section. -+ -+ -+SSH Packet Protocol: Flow Sequence -+================================== -+ -+Each exchange begins with |SYN|, followed by a |DATA_SEQ|- or -+|DATA_NSQ|-type frame, followed by its CRC, payload, and payload CRC. In -+case of a |DATA_NSQ|-type frame, the exchange is then finished. In case of a -+|DATA_SEQ|-type frame, the receiving party has to acknowledge receival of -+the frame by responding with a message containing an |ACK|-type frame with -+the same sequence ID of the |DATA| frame. In other words, the sequence ID of -+the |ACK| frame specifies the |DATA| frame to be acknowledged. In case of an -+error, e.g. an invalid CRC, the receiving party responds with a message -+containing an |NAK|-type frame. As the sequence ID of the previous data -+frame, for which an error is indicated via the |NAK| frame, cannot be relied -+upon, the sequence ID of the |NAK| frame should not be used and is set to -+zero. After receival of an |NAK| frame, the sending party should re-send all -+outstanding (non-ACKed) messages. -+ -+Sequence IDs are not synchronized between the two parties, meaning that they -+are managed independently for each party. Identifying the messages -+corresponding to a single exchange thus relies on the sequence ID as well as -+the type of the message, and the context. Specifically, the sequence ID is -+used to associate an ``ACK`` with its ``DATA_SEQ``-type frame, but not -+``DATA_SEQ``- or ``DATA_NSQ``-type frames with other ``DATA``- type frames. -+ -+An example exchange might look like this: -+ -+:: -+ -+ tx: -- SYN FRAME(D) CRC(F) PAYLOAD CRC(P) ----------------------------- -+ rx: ------------------------------------- SYN FRAME(A) CRC(F) CRC(P) -- -+ -+where both frames have the same sequence ID (``SEQ``). Here, ``FRAME(D)`` -+indicates a |DATA_SEQ|-type frame, ``FRAME(A)`` an ``ACK``-type frame, -+``CRC(F)`` the CRC over the previous frame, ``CRC(P)`` the CRC over the -+previous payload. In case of an error, the exchange would look like this: -+ -+:: -+ -+ tx: -- SYN FRAME(D) CRC(F) PAYLOAD CRC(P) ----------------------------- -+ rx: ------------------------------------- SYN FRAME(N) CRC(F) CRC(P) -- -+ -+upon which the sender should re-send the message. ``FRAME(N)`` indicates an -+|NAK|-type frame. Note that the sequence ID of the |NAK|-type frame is fixed -+to zero. For |DATA_NSQ|-type frames, both exchanges are the same: -+ -+:: -+ -+ tx: -- SYN FRAME(DATA_NSQ) CRC(F) PAYLOAD CRC(P) ---------------------- -+ rx: ------------------------------------------------------------------- -+ -+Here, an error can be detected, but not corrected or indicated to the -+sending party. These exchanges are symmetric, i.e. switching ``rx`` and -+``tx`` results again in a valid exchange. Currently, no longer exchanges are -+known. -+ -+ -+Commands: Requests, Responses, and Events -+========================================= -+ -+Commands are sent as payload inside a data frame. Currently, this is the -+only known payload type of |DATA| frames, with a payload-type value of -+``0x80`` (:c:type:`SSH_PLD_TYPE_CMD `). -+ -+The command-type payload (:c:type:`struct ssh_command `) -+consists of an eight-byte command structure, followed by optional and -+variable length command data. The length of this optional data is derived -+from the frame payload length given in the corresponding frame, i.e. it is -+``frame.len - sizeof(struct ssh_command)``. The command struct contains the -+following fields, packed together and in order: -+ -+.. flat-table:: SSH Command -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - |TYPE| -+ - |u8| -+ - Type of the payload. For commands always ``0x80``. -+ -+ * - |TC| -+ - |u8| -+ - Target category. -+ -+ * - |TID| (out) -+ - |u8| -+ - Target ID for outgoing (host to EC) commands. -+ -+ * - |TID| (in) -+ - |u8| -+ - Target ID for incoming (EC to host) commands. -+ -+ * - |IID| -+ - |u8| -+ - Instance ID. -+ -+ * - |RQID| -+ - |u16| -+ - Request ID. -+ -+ * - |CID| -+ - |u8| -+ - Command ID. -+ -+The command struct and data, in general, does not contain any failure -+detection mechanism (e.g. CRCs), this is solely done on the frame level. -+ -+Command-type payloads are used by the host to send commands and requests to -+the EC as well as by the EC to send responses and events back to the host. -+We differentiate between requests (sent by the host), responses (sent by the -+EC in response to a request), and events (sent by the EC without a preceding -+request). -+ -+Commands and events are uniquely identified by their target category -+(``TC``) and command ID (``CID``). The target category specifies a general -+category for the command (e.g. system in general, vs. battery and AC, vs. -+temperature, and so on), while the command ID specifies the command inside -+that category. Only the combination of |TC| + |CID| is unique. Additionally, -+commands have an instance ID (``IID``), which is used to differentiate -+between different sub-devices. For example ``TC=3`` ``CID=1`` is a -+request to get the temperature on a thermal sensor, where |IID| specifies -+the respective sensor. If the instance ID is not used, it should be set to -+zero. If instance IDs are used, they, in general, start with a value of one, -+whereas zero may be used for instance independent queries, if applicable. A -+response to a request should have the same target category, command ID, and -+instance ID as the corresponding request. -+ -+Responses are matched to their corresponding request via the request ID -+(``RQID``) field. This is a 16 bit wrapping counter similar to the sequence -+ID on the frames. Note that the sequence ID of the frames for a -+request-response pair does not match. Only the request ID has to match. -+Frame-protocol wise these are two separate exchanges, and may even be -+separated, e.g. by an event being sent after the request but before the -+response. Not all commands produce a response, and this is not detectable by -+|TC| + |CID|. It is the responsibility of the issuing party to wait for a -+response (or signal this to the communication framework, as is done in -+SAN/ACPI via the ``SNC`` flag). -+ -+Events are identified by unique and reserved request IDs. These IDs should -+not be used by the host when sending a new request. They are used on the -+host to, first, detect events and, second, match them with a registered -+event handler. Request IDs for events are chosen by the host and directed to -+the EC when setting up and enabling an event source (via the -+enable-event-source request). The EC then uses the specified request ID for -+events sent from the respective source. Note that an event should still be -+identified by its target category, command ID, and, if applicable, instance -+ID, as a single event source can send multiple different event types. In -+general, however, a single target category should map to a single reserved -+event request ID. -+ -+Furthermore, requests, responses, and events have an associated target ID -+(``TID``). This target ID is split into output (host to EC) and input (EC to -+host) fields, with the respecting other field (e.g. output field on incoming -+messages) set to zero. Two ``TID`` values are known: Primary (``0x01``) and -+secondary (``0x02``). In general, the response to a request should have the -+same ``TID`` value, however, the field (output vs. input) should be used in -+accordance to the direction in which the response is sent (i.e. on the input -+field, as responses are generally sent from the EC to the host). -+ -+Note that, even though requests and events should be uniquely identifiable -+by target category and command ID alone, the EC may require specific -+target ID and instance ID values to accept a command. A command that is -+accepted for ``TID=1``, for example, may not be accepted for ``TID=2`` -+and vice versa. -+ -+ -+Limitations and Observations -+============================ -+ -+The protocol can, in theory, handle up to ``U8_MAX`` frames in parallel, -+with up to ``U16_MAX`` pending requests (neglecting request IDs reserved for -+events). In practice, however, this is more limited. From our testing -+(although via a python and thus a user-space program), it seems that the EC -+can handle up to four requests (mostly) reliably in parallel at a certain -+time. With five or more requests in parallel, consistent discarding of -+commands (ACKed frame but no command response) has been observed. For five -+simultaneous commands, this reproducibly resulted in one command being -+dropped and four commands being handled. -+ -+However, it has also been noted that, even with three requests in parallel, -+occasional frame drops happen. Apart from this, with a limit of three -+pending requests, no dropped commands (i.e. command being dropped but frame -+carrying command being ACKed) have been observed. In any case, frames (and -+possibly also commands) should be re-sent by the host if a certain timeout -+is exceeded. This is done by the EC for frames with a timeout of one second, -+up to two re-tries (i.e. three transmissions in total). The limit of -+re-tries also applies to received NAKs, and, in a worst case scenario, can -+lead to entire messages being dropped. -+ -+While this also seems to work fine for pending data frames as long as no -+transmission failures occur, implementation and handling of these seems to -+depend on the assumption that there is only one non-acknowledged data frame. -+In particular, the detection of repeated frames relies on the last sequence -+number. This means that, if a frame that has been successfully received by -+the EC is sent again, e.g. due to the host not receiving an |ACK|, the EC -+will only detect this if it has the sequence ID of the last frame received -+by the EC. As an example: Sending two frames with ``SEQ=0`` and ``SEQ=1`` -+followed by a repetition of ``SEQ=0`` will not detect the second ``SEQ=0`` -+frame as such, and thus execute the command in this frame each time it has -+been received, i.e. twice in this example. Sending ``SEQ=0``, ``SEQ=1`` and -+then repeating ``SEQ=1`` will detect the second ``SEQ=1`` as repetition of -+the first one and ignore it, thus executing the contained command only once. -+ -+In conclusion, this suggests a limit of at most one pending un-ACKed frame -+(per party, effectively leading to synchronous communication regarding -+frames) and at most three pending commands. The limit to synchronous frame -+transfers seems to be consistent with behavior observed on Windows. -diff --git a/MAINTAINERS b/MAINTAINERS -index 530792c869c4..8e6fe82c1072 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11811,6 +11811,7 @@ M: Maximilian Luz - S: Maintained - W: https://github.com/linux-surface/surface-aggregator-module - C: irc://chat.freenode.net/##linux-surface -+F: Documentation/driver-api/surface_aggregator/ - F: drivers/platform/surface/aggregator/ - F: include/linux/surface_aggregator/ - --- -2.31.1 - -From 3f4fe5ead2f37abfcde0feda51a6a41358b75440 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 21 Dec 2020 19:39:58 +0100 -Subject: [PATCH] platform/surface: Add Surface Aggregator user-space interface - -Add a misc-device providing user-space access to the Surface Aggregator -EC, mainly intended for debugging, testing, and reverse-engineering. -This interface gives user-space applications the ability to send -requests to the EC and receive the corresponding responses. - -The device-file is managed by a pseudo platform-device and corresponding -driver to avoid dependence on the dedicated bus, allowing it to be -loaded in a minimal configuration. - -A python library and scripts to access this device can be found at [1]. - -[1]: https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20201221183959.1186143-9-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface_aggregator/clients/cdev.rst | 87 +++++ - .../surface_aggregator/clients/index.rst | 12 +- - .../userspace-api/ioctl/ioctl-number.rst | 2 + - MAINTAINERS | 2 + - drivers/platform/surface/Kconfig | 17 + - drivers/platform/surface/Makefile | 1 + - .../surface/surface_aggregator_cdev.c | 303 ++++++++++++++++++ - include/uapi/linux/surface_aggregator/cdev.h | 78 +++++ - 8 files changed, 501 insertions(+), 1 deletion(-) - create mode 100644 Documentation/driver-api/surface_aggregator/clients/cdev.rst - create mode 100644 drivers/platform/surface/surface_aggregator_cdev.c - create mode 100644 include/uapi/linux/surface_aggregator/cdev.h - -diff --git a/Documentation/driver-api/surface_aggregator/clients/cdev.rst b/Documentation/driver-api/surface_aggregator/clients/cdev.rst -new file mode 100644 -index 000000000000..248c1372d879 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/clients/cdev.rst -@@ -0,0 +1,87 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |u8| replace:: :c:type:`u8 ` -+.. |u16| replace:: :c:type:`u16 ` -+.. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request ` -+.. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags ` -+ -+============================== -+User-Space EC Interface (cdev) -+============================== -+ -+The ``surface_aggregator_cdev`` module provides a misc-device for the SSAM -+controller to allow for a (more or less) direct connection from user-space to -+the SAM EC. It is intended to be used for development and debugging, and -+therefore should not be used or relied upon in any other way. Note that this -+module is not loaded automatically, but instead must be loaded manually. -+ -+The provided interface is accessible through the ``/dev/surface/aggregator`` -+device-file. All functionality of this interface is provided via IOCTLs. -+These IOCTLs and their respective input/output parameter structs are defined in -+``include/uapi/linux/surface_aggregator/cdev.h``. -+ -+A small python library and scripts for accessing this interface can be found -+at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam. -+ -+ -+Controller IOCTLs -+================= -+ -+The following IOCTLs are provided: -+ -+.. flat-table:: Controller IOCTLs -+ :widths: 1 1 1 1 4 -+ :header-rows: 1 -+ -+ * - Type -+ - Number -+ - Direction -+ - Name -+ - Description -+ -+ * - ``0xA5`` -+ - ``1`` -+ - ``WR`` -+ - ``REQUEST`` -+ - Perform synchronous SAM request. -+ -+ -+``REQUEST`` -+----------- -+ -+Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``. -+ -+Executes a synchronous SAM request. The request specification is passed in -+as argument of type |ssam_cdev_request|, which is then written to/modified -+by the IOCTL to return status and result of the request. -+ -+Request payload data must be allocated separately and is passed in via the -+``payload.data`` and ``payload.length`` members. If a response is required, -+the response buffer must be allocated by the caller and passed in via the -+``response.data`` member. The ``response.length`` member must be set to the -+capacity of this buffer, or if no response is required, zero. Upon -+completion of the request, the call will write the response to the response -+buffer (if its capacity allows it) and overwrite the length field with the -+actual size of the response, in bytes. -+ -+Additionally, if the request has a response, this must be indicated via the -+request flags, as is done with in-kernel requests. Request flags can be set -+via the ``flags`` member and the values correspond to the values found in -+|ssam_cdev_request_flags|. -+ -+Finally, the status of the request itself is returned in the ``status`` -+member (a negative errno value indicating failure). Note that failure -+indication of the IOCTL is separated from failure indication of the request: -+The IOCTL returns a negative status code if anything failed during setup of -+the request (``-EFAULT``) or if the provided argument or any of its fields -+are invalid (``-EINVAL``). In this case, the status value of the request -+argument may be set, providing more detail on what went wrong (e.g. -+``-ENOMEM`` for out-of-memory), but this value may also be zero. The IOCTL -+will return with a zero status code in case the request has been set up, -+submitted, and completed (i.e. handed back to user-space) successfully from -+inside the IOCTL, but the request ``status`` member may still be negative in -+case the actual execution of the request failed after it has been submitted. -+ -+A full definition of the argument struct is provided below: -+ -+.. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h -diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst -index 31e026d96102..ab260ec82cfb 100644 ---- a/Documentation/driver-api/surface_aggregator/clients/index.rst -+++ b/Documentation/driver-api/surface_aggregator/clients/index.rst -@@ -7,4 +7,14 @@ Client Driver Documentation - This is the documentation for client drivers themselves. Refer to - :doc:`../client` for documentation on how to write client drivers. - --.. Place documentation for individual client drivers here. -+.. toctree:: -+ :maxdepth: 1 -+ -+ cdev -+ -+.. only:: subproject and html -+ -+ Indices -+ ======= -+ -+ * :ref:`genindex` -diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst -index a4c75a28c839..b5231d7f9200 100644 ---- a/Documentation/userspace-api/ioctl/ioctl-number.rst -+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst -@@ -324,6 +324,8 @@ Code Seq# Include File Comments - 0xA3 90-9F linux/dtlk.h - 0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem - 0xA4 00-1F uapi/asm/sgx.h -+0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator -+ - 0xAA 00-3F linux/uapi/linux/userfaultfd.h - 0xAB 00-1F linux/nbd.h - 0xAC 00-1F linux/raw.h -diff --git a/MAINTAINERS b/MAINTAINERS -index 8e6fe82c1072..b45df8ec687f 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11813,7 +11813,9 @@ W: https://github.com/linux-surface/surface-aggregator-module - C: irc://chat.freenode.net/##linux-surface - F: Documentation/driver-api/surface_aggregator/ - F: drivers/platform/surface/aggregator/ -+F: drivers/platform/surface/surface_aggregator_cdev.c - F: include/linux/surface_aggregator/ -+F: include/uapi/linux/surface_aggregator/ - - MICROTEK X6 SCANNER - M: Oliver Neukum -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index ef6b4051e7c8..82fbcfedc6dc 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -41,6 +41,23 @@ config SURFACE_3_POWER_OPREGION - This driver provides support for ACPI operation - region of the Surface 3 battery platform driver. - -+config SURFACE_AGGREGATOR_CDEV -+ tristate "Surface System Aggregator Module User-Space Interface" -+ depends on SURFACE_AGGREGATOR -+ help -+ Provides a misc-device interface to the Surface System Aggregator -+ Module (SSAM) controller. -+ -+ This option provides a module (called surface_aggregator_cdev), that, -+ when loaded, will add a client device (and its respective driver) to -+ the SSAM controller. Said client device manages a misc-device -+ interface (/dev/surface/aggregator), which can be used by user-space -+ tools to directly communicate with the SSAM EC by sending requests and -+ receiving the corresponding responses. -+ -+ The provided interface is intended for debugging and development only, -+ and should not be used otherwise. -+ - config SURFACE_BOOK1_DGPU_SWITCH - tristate "Surface Book 1 dGPU Switch Driver" - depends on SYSFS -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index c5392098cfb9..644c7511f64d 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -8,6 +8,7 @@ obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o - obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o - obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ -+obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o - obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o -diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c -new file mode 100644 -index 000000000000..340d15b148b9 ---- /dev/null -+++ b/drivers/platform/surface/surface_aggregator_cdev.c -@@ -0,0 +1,303 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Provides user-space access to the SSAM EC via the /dev/surface/aggregator -+ * misc device. Intended for debugging and development. -+ * -+ * Copyright (C) 2020 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" -+ -+struct ssam_cdev { -+ struct kref kref; -+ struct rw_semaphore lock; -+ struct ssam_controller *ctrl; -+ struct miscdevice mdev; -+}; -+ -+static void __ssam_cdev_release(struct kref *kref) -+{ -+ kfree(container_of(kref, struct ssam_cdev, kref)); -+} -+ -+static struct ssam_cdev *ssam_cdev_get(struct ssam_cdev *cdev) -+{ -+ if (cdev) -+ kref_get(&cdev->kref); -+ -+ return cdev; -+} -+ -+static void ssam_cdev_put(struct ssam_cdev *cdev) -+{ -+ if (cdev) -+ kref_put(&cdev->kref, __ssam_cdev_release); -+} -+ -+static int ssam_cdev_device_open(struct inode *inode, struct file *filp) -+{ -+ struct miscdevice *mdev = filp->private_data; -+ struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); -+ -+ filp->private_data = ssam_cdev_get(cdev); -+ return stream_open(inode, filp); -+} -+ -+static int ssam_cdev_device_release(struct inode *inode, struct file *filp) -+{ -+ ssam_cdev_put(filp->private_data); -+ return 0; -+} -+ -+static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) -+{ -+ struct ssam_cdev_request __user *r; -+ struct ssam_cdev_request rqst; -+ struct ssam_request spec; -+ struct ssam_response rsp; -+ const void __user *plddata; -+ void __user *rspdata; -+ int status = 0, ret = 0, tmp; -+ -+ r = (struct ssam_cdev_request __user *)arg; -+ ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); -+ if (ret) -+ goto out; -+ -+ plddata = u64_to_user_ptr(rqst.payload.data); -+ rspdata = u64_to_user_ptr(rqst.response.data); -+ -+ /* Setup basic request fields. */ -+ spec.target_category = rqst.target_category; -+ spec.target_id = rqst.target_id; -+ spec.command_id = rqst.command_id; -+ spec.instance_id = rqst.instance_id; -+ spec.flags = 0; -+ spec.length = rqst.payload.length; -+ spec.payload = NULL; -+ -+ if (rqst.flags & SSAM_CDEV_REQUEST_HAS_RESPONSE) -+ spec.flags |= SSAM_REQUEST_HAS_RESPONSE; -+ -+ if (rqst.flags & SSAM_CDEV_REQUEST_UNSEQUENCED) -+ spec.flags |= SSAM_REQUEST_UNSEQUENCED; -+ -+ rsp.capacity = rqst.response.length; -+ rsp.length = 0; -+ rsp.pointer = NULL; -+ -+ /* Get request payload from user-space. */ -+ if (spec.length) { -+ if (!plddata) { -+ ret = -EINVAL; -+ goto out; -+ } -+ -+ spec.payload = kzalloc(spec.length, GFP_KERNEL); -+ if (!spec.payload) { -+ ret = -ENOMEM; -+ goto out; -+ } -+ -+ if (copy_from_user((void *)spec.payload, plddata, spec.length)) { -+ ret = -EFAULT; -+ goto out; -+ } -+ } -+ -+ /* Allocate response buffer. */ -+ if (rsp.capacity) { -+ if (!rspdata) { -+ ret = -EINVAL; -+ goto out; -+ } -+ -+ rsp.pointer = kzalloc(rsp.capacity, GFP_KERNEL); -+ if (!rsp.pointer) { -+ ret = -ENOMEM; -+ goto out; -+ } -+ } -+ -+ /* Perform request. */ -+ status = ssam_request_sync(cdev->ctrl, &spec, &rsp); -+ if (status) -+ goto out; -+ -+ /* Copy response to user-space. */ -+ if (rsp.length && copy_to_user(rspdata, rsp.pointer, rsp.length)) -+ ret = -EFAULT; -+ -+out: -+ /* Always try to set response-length and status. */ -+ tmp = put_user(rsp.length, &r->response.length); -+ if (tmp) -+ ret = tmp; -+ -+ tmp = put_user(status, &r->status); -+ if (tmp) -+ ret = tmp; -+ -+ /* Cleanup. */ -+ kfree(spec.payload); -+ kfree(rsp.pointer); -+ -+ return ret; -+} -+ -+static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd, -+ unsigned long arg) -+{ -+ switch (cmd) { -+ case SSAM_CDEV_REQUEST: -+ return ssam_cdev_request(cdev, arg); -+ -+ default: -+ return -ENOTTY; -+ } -+} -+ -+static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) -+{ -+ struct ssam_cdev *cdev = file->private_data; -+ long status; -+ -+ /* Ensure that controller is valid for as long as we need it. */ -+ if (down_read_killable(&cdev->lock)) -+ return -ERESTARTSYS; -+ -+ if (!cdev->ctrl) { -+ up_read(&cdev->lock); -+ return -ENODEV; -+ } -+ -+ status = __ssam_cdev_device_ioctl(cdev, cmd, arg); -+ -+ up_read(&cdev->lock); -+ return status; -+} -+ -+static const struct file_operations ssam_controller_fops = { -+ .owner = THIS_MODULE, -+ .open = ssam_cdev_device_open, -+ .release = ssam_cdev_device_release, -+ .unlocked_ioctl = ssam_cdev_device_ioctl, -+ .compat_ioctl = ssam_cdev_device_ioctl, -+ .llseek = noop_llseek, -+}; -+ -+static int ssam_dbg_device_probe(struct platform_device *pdev) -+{ -+ struct ssam_controller *ctrl; -+ struct ssam_cdev *cdev; -+ int status; -+ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); -+ if (!cdev) -+ return -ENOMEM; -+ -+ kref_init(&cdev->kref); -+ init_rwsem(&cdev->lock); -+ cdev->ctrl = ctrl; -+ -+ cdev->mdev.parent = &pdev->dev; -+ cdev->mdev.minor = MISC_DYNAMIC_MINOR; -+ cdev->mdev.name = "surface_aggregator"; -+ cdev->mdev.nodename = "surface/aggregator"; -+ cdev->mdev.fops = &ssam_controller_fops; -+ -+ status = misc_register(&cdev->mdev); -+ if (status) { -+ kfree(cdev); -+ return status; -+ } -+ -+ platform_set_drvdata(pdev, cdev); -+ return 0; -+} -+ -+static int ssam_dbg_device_remove(struct platform_device *pdev) -+{ -+ struct ssam_cdev *cdev = platform_get_drvdata(pdev); -+ -+ misc_deregister(&cdev->mdev); -+ -+ /* -+ * The controller is only guaranteed to be valid for as long as the -+ * driver is bound. Remove controller so that any lingering open files -+ * cannot access it any more after we're gone. -+ */ -+ down_write(&cdev->lock); -+ cdev->ctrl = NULL; -+ up_write(&cdev->lock); -+ -+ ssam_cdev_put(cdev); -+ return 0; -+} -+ -+static struct platform_device *ssam_cdev_device; -+ -+static struct platform_driver ssam_cdev_driver = { -+ .probe = ssam_dbg_device_probe, -+ .remove = ssam_dbg_device_remove, -+ .driver = { -+ .name = SSAM_CDEV_DEVICE_NAME, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+static int __init ssam_debug_init(void) -+{ -+ int status; -+ -+ ssam_cdev_device = platform_device_alloc(SSAM_CDEV_DEVICE_NAME, -+ PLATFORM_DEVID_NONE); -+ if (!ssam_cdev_device) -+ return -ENOMEM; -+ -+ status = platform_device_add(ssam_cdev_device); -+ if (status) -+ goto err_device; -+ -+ status = platform_driver_register(&ssam_cdev_driver); -+ if (status) -+ goto err_driver; -+ -+ return 0; -+ -+err_driver: -+ platform_device_del(ssam_cdev_device); -+err_device: -+ platform_device_put(ssam_cdev_device); -+ return status; -+} -+module_init(ssam_debug_init); -+ -+static void __exit ssam_debug_exit(void) -+{ -+ platform_driver_unregister(&ssam_cdev_driver); -+ platform_device_unregister(ssam_cdev_device); -+} -+module_exit(ssam_debug_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("User-space interface for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h -new file mode 100644 -index 000000000000..fbcce04abfe9 ---- /dev/null -+++ b/include/uapi/linux/surface_aggregator/cdev.h -@@ -0,0 +1,78 @@ -+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ -+/* -+ * Surface System Aggregator Module (SSAM) user-space EC interface. -+ * -+ * Definitions, structs, and IOCTLs for the /dev/surface/aggregator misc -+ * device. This device provides direct user-space access to the SSAM EC. -+ * Intended for debugging and development. -+ * -+ * Copyright (C) 2020 Maximilian Luz -+ */ -+ -+#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H -+#define _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H -+ -+#include -+#include -+ -+/** -+ * enum ssam_cdev_request_flags - Request flags for SSAM cdev request IOCTL. -+ * -+ * @SSAM_CDEV_REQUEST_HAS_RESPONSE: -+ * Specifies that the request expects a response. If not set, the request -+ * will be directly completed after its underlying packet has been -+ * transmitted. If set, the request transport system waits for a response -+ * of the request. -+ * -+ * @SSAM_CDEV_REQUEST_UNSEQUENCED: -+ * Specifies that the request should be transmitted via an unsequenced -+ * packet. If set, the request must not have a response, meaning that this -+ * flag and the %SSAM_CDEV_REQUEST_HAS_RESPONSE flag are mutually -+ * exclusive. -+ */ -+enum ssam_cdev_request_flags { -+ SSAM_CDEV_REQUEST_HAS_RESPONSE = 0x01, -+ SSAM_CDEV_REQUEST_UNSEQUENCED = 0x02, -+}; -+ -+/** -+ * struct ssam_cdev_request - Controller request IOCTL argument. -+ * @target_category: Target category of the SAM request. -+ * @target_id: Target ID of the SAM request. -+ * @command_id: Command ID of the SAM request. -+ * @instance_id: Instance ID of the SAM request. -+ * @flags: Request flags (see &enum ssam_cdev_request_flags). -+ * @status: Request status (output). -+ * @payload: Request payload (input data). -+ * @payload.data: Pointer to request payload data. -+ * @payload.length: Length of request payload data (in bytes). -+ * @response: Request response (output data). -+ * @response.data: Pointer to response buffer. -+ * @response.length: On input: Capacity of response buffer (in bytes). -+ * On output: Length of request response (number of bytes -+ * in the buffer that are actually used). -+ */ -+struct ssam_cdev_request { -+ __u8 target_category; -+ __u8 target_id; -+ __u8 command_id; -+ __u8 instance_id; -+ __u16 flags; -+ __s16 status; -+ -+ struct { -+ __u64 data; -+ __u16 length; -+ __u8 __pad[6]; -+ } payload; -+ -+ struct { -+ __u64 data; -+ __u16 length; -+ __u8 __pad[6]; -+ } response; -+} __attribute__((__packed__)); -+ -+#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) -+ -+#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ --- -2.31.1 - -From 145977952074195b91951c5b137d970025df1707 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 21 Dec 2020 19:39:59 +0100 -Subject: [PATCH] platform/surface: Add Surface ACPI Notify driver - -The Surface ACPI Notify (SAN) device provides an ACPI interface to the -Surface Aggregator EC, specifically the Surface Serial Hub interface. -This interface allows EC requests to be made from ACPI code and can -convert a subset of EC events back to ACPI notifications. - -Specifically, this interface provides a GenericSerialBus operation -region ACPI code can execute a request by writing the request command -data and payload to this operation region and reading back the -corresponding response via a write-then-read operation. Furthermore, -this interface provides a _DSM method to be called when certain events -from the EC have been received, essentially turning them into ACPI -notifications. - -The driver provided in this commit essentially takes care of translating -the request data written to the operation region, executing the request, -waiting for it to finish, and finally writing and translating back the -response (if the request has one). Furthermore, this driver takes care -of enabling the events handled via ACPI _DSM calls. Lastly, this driver -also exposes an interface providing discrete GPU (dGPU) power-on -notifications on the Surface Book 2, which are also received via the -operation region interface (but not handled by the SAN driver directly), -making them accessible to other drivers (such as a dGPU hot-plug driver -that may be added later on). - -On 5th and 6th generation Surface devices (Surface Pro 5/2017, Pro 6, -Book 2, Laptop 1 and 2), the SAN interface provides full battery and -thermal subsystem access, as well as other EC based functionality. On -those models, battery and thermal sensor devices are implemented as -standard ACPI devices of that type, however, forward ACPI calls to the -corresponding Surface Aggregator EC request via the SAN interface and -receive corresponding notifications (e.g. battery information change) -from it. This interface is therefore required to provide said -functionality on those devices. - -Signed-off-by: Maximilian Luz -Reviewed-by: Hans de Goede -Link: https://lore.kernel.org/r/20201221183959.1186143-10-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface_aggregator/clients/index.rst | 1 + - .../surface_aggregator/clients/san.rst | 44 + - MAINTAINERS | 2 + - drivers/platform/surface/Kconfig | 19 + - drivers/platform/surface/Makefile | 1 + - .../platform/surface/surface_acpi_notify.c | 886 ++++++++++++++++++ - include/linux/surface_acpi_notify.h | 39 + - 7 files changed, 992 insertions(+) - create mode 100644 Documentation/driver-api/surface_aggregator/clients/san.rst - create mode 100644 drivers/platform/surface/surface_acpi_notify.c - create mode 100644 include/linux/surface_acpi_notify.h - -diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst -index ab260ec82cfb..3ccabce23271 100644 ---- a/Documentation/driver-api/surface_aggregator/clients/index.rst -+++ b/Documentation/driver-api/surface_aggregator/clients/index.rst -@@ -11,6 +11,7 @@ This is the documentation for client drivers themselves. Refer to - :maxdepth: 1 - - cdev -+ san - - .. only:: subproject and html - -diff --git a/Documentation/driver-api/surface_aggregator/clients/san.rst b/Documentation/driver-api/surface_aggregator/clients/san.rst -new file mode 100644 -index 000000000000..38c2580e7758 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/clients/san.rst -@@ -0,0 +1,44 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |san_client_link| replace:: :c:func:`san_client_link` -+.. |san_dgpu_notifier_register| replace:: :c:func:`san_dgpu_notifier_register` -+.. |san_dgpu_notifier_unregister| replace:: :c:func:`san_dgpu_notifier_unregister` -+ -+=================== -+Surface ACPI Notify -+=================== -+ -+The Surface ACPI Notify (SAN) device provides the bridge between ACPI and -+SAM controller. Specifically, ACPI code can execute requests and handle -+battery and thermal events via this interface. In addition to this, events -+relating to the discrete GPU (dGPU) of the Surface Book 2 can be sent from -+ACPI code (note: the Surface Book 3 uses a different method for this). The -+only currently known event sent via this interface is a dGPU power-on -+notification. While this driver handles the former part internally, it only -+relays the dGPU events to any other driver interested via its public API and -+does not handle them. -+ -+The public interface of this driver is split into two parts: Client -+registration and notifier-block registration. -+ -+A client to the SAN interface can be linked as consumer to the SAN device -+via |san_client_link|. This can be used to ensure that the a client -+receiving dGPU events does not miss any events due to the SAN interface not -+being set up as this forces the client driver to unbind once the SAN driver -+is unbound. -+ -+Notifier-blocks can be registered by any device for as long as the module is -+loaded, regardless of being linked as client or not. Registration is done -+with |san_dgpu_notifier_register|. If the notifier is not needed any more, it -+should be unregistered via |san_dgpu_notifier_unregister|. -+ -+Consult the API documentation below for more details. -+ -+ -+API Documentation -+================= -+ -+.. kernel-doc:: include/linux/surface_acpi_notify.h -+ -+.. kernel-doc:: drivers/platform/surface/surface_acpi_notify.c -+ :export: -diff --git a/MAINTAINERS b/MAINTAINERS -index b45df8ec687f..dfe4f4e1da7a 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11813,7 +11813,9 @@ W: https://github.com/linux-surface/surface-aggregator-module - C: irc://chat.freenode.net/##linux-surface - F: Documentation/driver-api/surface_aggregator/ - F: drivers/platform/surface/aggregator/ -+F: drivers/platform/surface/surface_acpi_notify.c - F: drivers/platform/surface/surface_aggregator_cdev.c -+F: include/linux/surface_acpi_notify.h - F: include/linux/surface_aggregator/ - F: include/uapi/linux/surface_aggregator/ - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 82fbcfedc6dc..b0b91fa2f6a1 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -41,6 +41,25 @@ config SURFACE_3_POWER_OPREGION - This driver provides support for ACPI operation - region of the Surface 3 battery platform driver. - -+config SURFACE_ACPI_NOTIFY -+ tristate "Surface ACPI Notify Driver" -+ depends on SURFACE_AGGREGATOR -+ help -+ Surface ACPI Notify (SAN) driver for Microsoft Surface devices. -+ -+ This driver provides support for the ACPI interface (called SAN) of -+ the Surface System Aggregator Module (SSAM) EC. This interface is used -+ on 5th- and 6th-generation Microsoft Surface devices (including -+ Surface Pro 5 and 6, Surface Book 2, Surface Laptops 1 and 2, and in -+ reduced functionality on the Surface Laptop 3) to execute SSAM -+ requests directly from ACPI code, as well as receive SSAM events and -+ turn them into ACPI notifications. It essentially acts as a -+ translation layer between the SSAM controller and ACPI. -+ -+ Specifically, this driver may be needed for battery status reporting, -+ thermal sensor access, and real-time clock information, depending on -+ the Surface device in question. -+ - config SURFACE_AGGREGATOR_CDEV - tristate "Surface System Aggregator Module User-Space Interface" - depends on SURFACE_AGGREGATOR -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 644c7511f64d..72f4d9fbb6be 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -7,6 +7,7 @@ - obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o - obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o - obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o -+obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o - obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o -diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c -new file mode 100644 -index 000000000000..8cd67a669c86 ---- /dev/null -+++ b/drivers/platform/surface/surface_acpi_notify.c -@@ -0,0 +1,886 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Driver for the Surface ACPI Notify (SAN) interface/shim. -+ * -+ * Translates communication from ACPI to Surface System Aggregator Module -+ * (SSAM/SAM) requests and back, specifically SAM-over-SSH. Translates SSAM -+ * events back to ACPI notifications. Allows handling of discrete GPU -+ * notifications sent from ACPI via the SAN interface by providing them to any -+ * registered external driver. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+struct san_data { -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ -+ struct acpi_connection_info info; -+ -+ struct ssam_event_notifier nf_bat; -+ struct ssam_event_notifier nf_tmp; -+}; -+ -+#define to_san_data(ptr, member) \ -+ container_of(ptr, struct san_data, member) -+ -+ -+/* -- dGPU notifier interface. ---------------------------------------------- */ -+ -+struct san_rqsg_if { -+ struct rw_semaphore lock; -+ struct device *dev; -+ struct blocking_notifier_head nh; -+}; -+ -+static struct san_rqsg_if san_rqsg_if = { -+ .lock = __RWSEM_INITIALIZER(san_rqsg_if.lock), -+ .dev = NULL, -+ .nh = BLOCKING_NOTIFIER_INIT(san_rqsg_if.nh), -+}; -+ -+static int san_set_rqsg_interface_device(struct device *dev) -+{ -+ int status = 0; -+ -+ down_write(&san_rqsg_if.lock); -+ if (!san_rqsg_if.dev && dev) -+ san_rqsg_if.dev = dev; -+ else -+ status = -EBUSY; -+ up_write(&san_rqsg_if.lock); -+ -+ return status; -+} -+ -+/** -+ * san_client_link() - Link client as consumer to SAN device. -+ * @client: The client to link. -+ * -+ * Sets up a device link between the provided client device as consumer and -+ * the SAN device as provider. This function can be used to ensure that the -+ * SAN interface has been set up and will be set up for as long as the driver -+ * of the client device is bound. This guarantees that, during that time, all -+ * dGPU events will be received by any registered notifier. -+ * -+ * The link will be automatically removed once the client device's driver is -+ * unbound. -+ * -+ * Return: Returns zero on success, %-ENXIO if the SAN interface has not been -+ * set up yet, and %-ENOMEM if device link creation failed. -+ */ -+int san_client_link(struct device *client) -+{ -+ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; -+ struct device_link *link; -+ -+ down_read(&san_rqsg_if.lock); -+ -+ if (!san_rqsg_if.dev) { -+ up_read(&san_rqsg_if.lock); -+ return -ENXIO; -+ } -+ -+ link = device_link_add(client, san_rqsg_if.dev, flags); -+ if (!link) { -+ up_read(&san_rqsg_if.lock); -+ return -ENOMEM; -+ } -+ -+ if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) { -+ up_read(&san_rqsg_if.lock); -+ return -ENXIO; -+ } -+ -+ up_read(&san_rqsg_if.lock); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(san_client_link); -+ -+/** -+ * san_dgpu_notifier_register() - Register a SAN dGPU notifier. -+ * @nb: The notifier-block to register. -+ * -+ * Registers a SAN dGPU notifier, receiving any new SAN dGPU events sent from -+ * ACPI. The registered notifier will be called with &struct san_dgpu_event -+ * as notifier data and the command ID of that event as notifier action. -+ */ -+int san_dgpu_notifier_register(struct notifier_block *nb) -+{ -+ return blocking_notifier_chain_register(&san_rqsg_if.nh, nb); -+} -+EXPORT_SYMBOL_GPL(san_dgpu_notifier_register); -+ -+/** -+ * san_dgpu_notifier_unregister() - Unregister a SAN dGPU notifier. -+ * @nb: The notifier-block to unregister. -+ */ -+int san_dgpu_notifier_unregister(struct notifier_block *nb) -+{ -+ return blocking_notifier_chain_unregister(&san_rqsg_if.nh, nb); -+} -+EXPORT_SYMBOL_GPL(san_dgpu_notifier_unregister); -+ -+static int san_dgpu_notifier_call(struct san_dgpu_event *evt) -+{ -+ int ret; -+ -+ ret = blocking_notifier_call_chain(&san_rqsg_if.nh, evt->command, evt); -+ return notifier_to_errno(ret); -+} -+ -+ -+/* -- ACPI _DSM event relay. ------------------------------------------------ */ -+ -+#define SAN_DSM_REVISION 0 -+ -+/* 93b666c5-70c6-469f-a215-3d487c91ab3c */ -+static const guid_t SAN_DSM_UUID = -+ GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d, -+ 0x48, 0x7c, 0x91, 0xab, 0x3c); -+ -+enum san_dsm_event_fn { -+ SAN_DSM_EVENT_FN_BAT1_STAT = 0x03, -+ SAN_DSM_EVENT_FN_BAT1_INFO = 0x04, -+ SAN_DSM_EVENT_FN_ADP1_STAT = 0x05, -+ SAN_DSM_EVENT_FN_ADP1_INFO = 0x06, -+ SAN_DSM_EVENT_FN_BAT2_STAT = 0x07, -+ SAN_DSM_EVENT_FN_BAT2_INFO = 0x08, -+ SAN_DSM_EVENT_FN_THERMAL = 0x09, -+ SAN_DSM_EVENT_FN_DPTF = 0x0a, -+}; -+ -+enum sam_event_cid_bat { -+ SAM_EVENT_CID_BAT_BIX = 0x15, -+ SAM_EVENT_CID_BAT_BST = 0x16, -+ SAM_EVENT_CID_BAT_ADP = 0x17, -+ SAM_EVENT_CID_BAT_PROT = 0x18, -+ SAM_EVENT_CID_BAT_DPTF = 0x4f, -+}; -+ -+enum sam_event_cid_tmp { -+ SAM_EVENT_CID_TMP_TRIP = 0x0b, -+}; -+ -+struct san_event_work { -+ struct delayed_work work; -+ struct device *dev; -+ struct ssam_event event; /* must be last */ -+}; -+ -+static int san_acpi_notify_event(struct device *dev, u64 func, -+ union acpi_object *param) -+{ -+ acpi_handle san = ACPI_HANDLE(dev); -+ union acpi_object *obj; -+ int status = 0; -+ -+ if (!acpi_check_dsm(san, &SAN_DSM_UUID, SAN_DSM_REVISION, 1 << func)) -+ return 0; -+ -+ dev_dbg(dev, "notify event %#04llx\n", func); -+ -+ obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION, -+ func, param, ACPI_TYPE_BUFFER); -+ if (!obj) -+ return -EFAULT; -+ -+ if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) { -+ dev_err(dev, "got unexpected result from _DSM\n"); -+ status = -EPROTO; -+ } -+ -+ ACPI_FREE(obj); -+ return status; -+} -+ -+static int san_evt_bat_adp(struct device *dev, const struct ssam_event *event) -+{ -+ int status; -+ -+ status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_ADP1_STAT, NULL); -+ if (status) -+ return status; -+ -+ /* -+ * Ensure that the battery states get updated correctly. When the -+ * battery is fully charged and an adapter is plugged in, it sometimes -+ * is not updated correctly, instead showing it as charging. -+ * Explicitly trigger battery updates to fix this. -+ */ -+ -+ status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT1_STAT, NULL); -+ if (status) -+ return status; -+ -+ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT2_STAT, NULL); -+} -+ -+static int san_evt_bat_bix(struct device *dev, const struct ssam_event *event) -+{ -+ enum san_dsm_event_fn fn; -+ -+ if (event->instance_id == 0x02) -+ fn = SAN_DSM_EVENT_FN_BAT2_INFO; -+ else -+ fn = SAN_DSM_EVENT_FN_BAT1_INFO; -+ -+ return san_acpi_notify_event(dev, fn, NULL); -+} -+ -+static int san_evt_bat_bst(struct device *dev, const struct ssam_event *event) -+{ -+ enum san_dsm_event_fn fn; -+ -+ if (event->instance_id == 0x02) -+ fn = SAN_DSM_EVENT_FN_BAT2_STAT; -+ else -+ fn = SAN_DSM_EVENT_FN_BAT1_STAT; -+ -+ return san_acpi_notify_event(dev, fn, NULL); -+} -+ -+static int san_evt_bat_dptf(struct device *dev, const struct ssam_event *event) -+{ -+ union acpi_object payload; -+ -+ /* -+ * The Surface ACPI expects a buffer and not a package. It specifically -+ * checks for ObjectType (Arg3) == 0x03. This will cause a warning in -+ * acpica/nsarguments.c, but that warning can be safely ignored. -+ */ -+ payload.type = ACPI_TYPE_BUFFER; -+ payload.buffer.length = event->length; -+ payload.buffer.pointer = (u8 *)&event->data[0]; -+ -+ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_DPTF, &payload); -+} -+ -+static unsigned long san_evt_bat_delay(u8 cid) -+{ -+ switch (cid) { -+ case SAM_EVENT_CID_BAT_ADP: -+ /* -+ * Wait for battery state to update before signaling adapter -+ * change. -+ */ -+ return msecs_to_jiffies(5000); -+ -+ case SAM_EVENT_CID_BAT_BST: -+ /* Ensure we do not miss anything important due to caching. */ -+ return msecs_to_jiffies(2000); -+ -+ default: -+ return 0; -+ } -+} -+ -+static bool san_evt_bat(const struct ssam_event *event, struct device *dev) -+{ -+ int status; -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_BAT_BIX: -+ status = san_evt_bat_bix(dev, event); -+ break; -+ -+ case SAM_EVENT_CID_BAT_BST: -+ status = san_evt_bat_bst(dev, event); -+ break; -+ -+ case SAM_EVENT_CID_BAT_ADP: -+ status = san_evt_bat_adp(dev, event); -+ break; -+ -+ case SAM_EVENT_CID_BAT_PROT: -+ /* -+ * TODO: Implement support for battery protection status change -+ * event. -+ */ -+ return true; -+ -+ case SAM_EVENT_CID_BAT_DPTF: -+ status = san_evt_bat_dptf(dev, event); -+ break; -+ -+ default: -+ return false; -+ } -+ -+ if (status) { -+ dev_err(dev, "error handling power event (cid = %#04x)\n", -+ event->command_id); -+ } -+ -+ return true; -+} -+ -+static void san_evt_bat_workfn(struct work_struct *work) -+{ -+ struct san_event_work *ev; -+ -+ ev = container_of(work, struct san_event_work, work.work); -+ san_evt_bat(&ev->event, ev->dev); -+ kfree(ev); -+} -+ -+static u32 san_evt_bat_nf(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+{ -+ struct san_data *d = to_san_data(nf, nf_bat); -+ struct san_event_work *work; -+ unsigned long delay = san_evt_bat_delay(event->command_id); -+ -+ if (delay == 0) -+ return san_evt_bat(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; -+ -+ work = kzalloc(sizeof(*work) + event->length, GFP_KERNEL); -+ if (!work) -+ return ssam_notifier_from_errno(-ENOMEM); -+ -+ INIT_DELAYED_WORK(&work->work, san_evt_bat_workfn); -+ work->dev = d->dev; -+ -+ memcpy(&work->event, event, sizeof(struct ssam_event) + event->length); -+ -+ schedule_delayed_work(&work->work, delay); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static int san_evt_tmp_trip(struct device *dev, const struct ssam_event *event) -+{ -+ union acpi_object param; -+ -+ /* -+ * The Surface ACPI expects an integer and not a package. This will -+ * cause a warning in acpica/nsarguments.c, but that warning can be -+ * safely ignored. -+ */ -+ param.type = ACPI_TYPE_INTEGER; -+ param.integer.value = event->instance_id; -+ -+ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_THERMAL, ¶m); -+} -+ -+static bool san_evt_tmp(const struct ssam_event *event, struct device *dev) -+{ -+ int status; -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_TMP_TRIP: -+ status = san_evt_tmp_trip(dev, event); -+ break; -+ -+ default: -+ return false; -+ } -+ -+ if (status) { -+ dev_err(dev, "error handling thermal event (cid = %#04x)\n", -+ event->command_id); -+ } -+ -+ return true; -+} -+ -+static u32 san_evt_tmp_nf(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+{ -+ struct san_data *d = to_san_data(nf, nf_tmp); -+ -+ return san_evt_tmp(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; -+} -+ -+ -+/* -- ACPI GSB OperationRegion handler -------------------------------------- */ -+ -+struct gsb_data_in { -+ u8 cv; -+} __packed; -+ -+struct gsb_data_rqsx { -+ u8 cv; /* Command value (san_gsb_request_cv). */ -+ u8 tc; /* Target category. */ -+ u8 tid; /* Target ID. */ -+ u8 iid; /* Instance ID. */ -+ u8 snc; /* Expect-response-flag. */ -+ u8 cid; /* Command ID. */ -+ u16 cdl; /* Payload length. */ -+ u8 pld[]; /* Payload. */ -+} __packed; -+ -+struct gsb_data_etwl { -+ u8 cv; /* Command value (should be 0x02). */ -+ u8 etw3; /* Unknown. */ -+ u8 etw4; /* Unknown. */ -+ u8 msg[]; /* Error message (ASCIIZ). */ -+} __packed; -+ -+struct gsb_data_out { -+ u8 status; /* _SSH communication status. */ -+ u8 len; /* _SSH payload length. */ -+ u8 pld[]; /* _SSH payload. */ -+} __packed; -+ -+union gsb_buffer_data { -+ struct gsb_data_in in; /* Common input. */ -+ struct gsb_data_rqsx rqsx; /* RQSX input. */ -+ struct gsb_data_etwl etwl; /* ETWL input. */ -+ struct gsb_data_out out; /* Output. */ -+}; -+ -+struct gsb_buffer { -+ u8 status; /* GSB AttribRawProcess status. */ -+ u8 len; /* GSB AttribRawProcess length. */ -+ union gsb_buffer_data data; -+} __packed; -+ -+#define SAN_GSB_MAX_RQSX_PAYLOAD (U8_MAX - 2 - sizeof(struct gsb_data_rqsx)) -+#define SAN_GSB_MAX_RESPONSE (U8_MAX - 2 - sizeof(struct gsb_data_out)) -+ -+#define SAN_GSB_COMMAND 0 -+ -+enum san_gsb_request_cv { -+ SAN_GSB_REQUEST_CV_RQST = 0x01, -+ SAN_GSB_REQUEST_CV_ETWL = 0x02, -+ SAN_GSB_REQUEST_CV_RQSG = 0x03, -+}; -+ -+#define SAN_REQUEST_NUM_TRIES 5 -+ -+static acpi_status san_etwl(struct san_data *d, struct gsb_buffer *b) -+{ -+ struct gsb_data_etwl *etwl = &b->data.etwl; -+ -+ if (b->len < sizeof(struct gsb_data_etwl)) { -+ dev_err(d->dev, "invalid ETWL package (len = %d)\n", b->len); -+ return AE_OK; -+ } -+ -+ dev_err(d->dev, "ETWL(%#04x, %#04x): %.*s\n", etwl->etw3, etwl->etw4, -+ (unsigned int)(b->len - sizeof(struct gsb_data_etwl)), -+ (char *)etwl->msg); -+ -+ /* Indicate success. */ -+ b->status = 0x00; -+ b->len = 0x00; -+ -+ return AE_OK; -+} -+ -+static -+struct gsb_data_rqsx *san_validate_rqsx(struct device *dev, const char *type, -+ struct gsb_buffer *b) -+{ -+ struct gsb_data_rqsx *rqsx = &b->data.rqsx; -+ -+ if (b->len < sizeof(struct gsb_data_rqsx)) { -+ dev_err(dev, "invalid %s package (len = %d)\n", type, b->len); -+ return NULL; -+ } -+ -+ if (get_unaligned(&rqsx->cdl) != b->len - sizeof(struct gsb_data_rqsx)) { -+ dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n", -+ type, b->len, get_unaligned(&rqsx->cdl)); -+ return NULL; -+ } -+ -+ if (get_unaligned(&rqsx->cdl) > SAN_GSB_MAX_RQSX_PAYLOAD) { -+ dev_err(dev, "payload for %s package too large (cdl = %d)\n", -+ type, get_unaligned(&rqsx->cdl)); -+ return NULL; -+ } -+ -+ return rqsx; -+} -+ -+static void gsb_rqsx_response_error(struct gsb_buffer *gsb, int status) -+{ -+ gsb->status = 0x00; -+ gsb->len = 0x02; -+ gsb->data.out.status = (u8)(-status); -+ gsb->data.out.len = 0x00; -+} -+ -+static void gsb_rqsx_response_success(struct gsb_buffer *gsb, u8 *ptr, size_t len) -+{ -+ gsb->status = 0x00; -+ gsb->len = len + 2; -+ gsb->data.out.status = 0x00; -+ gsb->data.out.len = len; -+ -+ if (len) -+ memcpy(&gsb->data.out.pld[0], ptr, len); -+} -+ -+static acpi_status san_rqst_fixup_suspended(struct san_data *d, -+ struct ssam_request *rqst, -+ struct gsb_buffer *gsb) -+{ -+ if (rqst->target_category == SSAM_SSH_TC_BAS && rqst->command_id == 0x0D) { -+ u8 base_state = 1; -+ -+ /* Base state quirk: -+ * The base state may be queried from ACPI when the EC is still -+ * suspended. In this case it will return '-EPERM'. This query -+ * will only be triggered from the ACPI lid GPE interrupt, thus -+ * we are either in laptop or studio mode (base status 0x01 or -+ * 0x02). Furthermore, we will only get here if the device (and -+ * EC) have been suspended. -+ * -+ * We now assume that the device is in laptop mode (0x01). This -+ * has the drawback that it will wake the device when unfolding -+ * it in studio mode, but it also allows us to avoid actively -+ * waiting for the EC to wake up, which may incur a notable -+ * delay. -+ */ -+ -+ dev_dbg(d->dev, "rqst: fixup: base-state quirk\n"); -+ -+ gsb_rqsx_response_success(gsb, &base_state, sizeof(base_state)); -+ return AE_OK; -+ } -+ -+ gsb_rqsx_response_error(gsb, -ENXIO); -+ return AE_OK; -+} -+ -+static acpi_status san_rqst(struct san_data *d, struct gsb_buffer *buffer) -+{ -+ u8 rspbuf[SAN_GSB_MAX_RESPONSE]; -+ struct gsb_data_rqsx *gsb_rqst; -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ int status = 0; -+ -+ gsb_rqst = san_validate_rqsx(d->dev, "RQST", buffer); -+ if (!gsb_rqst) -+ return AE_OK; -+ -+ rqst.target_category = gsb_rqst->tc; -+ rqst.target_id = gsb_rqst->tid; -+ rqst.command_id = gsb_rqst->cid; -+ rqst.instance_id = gsb_rqst->iid; -+ rqst.flags = gsb_rqst->snc ? SSAM_REQUEST_HAS_RESPONSE : 0; -+ rqst.length = get_unaligned(&gsb_rqst->cdl); -+ rqst.payload = &gsb_rqst->pld[0]; -+ -+ rsp.capacity = ARRAY_SIZE(rspbuf); -+ rsp.length = 0; -+ rsp.pointer = &rspbuf[0]; -+ -+ /* Handle suspended device. */ -+ if (d->dev->power.is_suspended) { -+ dev_warn(d->dev, "rqst: device is suspended, not executing\n"); -+ return san_rqst_fixup_suspended(d, &rqst, buffer); -+ } -+ -+ status = __ssam_retry(ssam_request_sync_onstack, SAN_REQUEST_NUM_TRIES, -+ d->ctrl, &rqst, &rsp, SAN_GSB_MAX_RQSX_PAYLOAD); -+ -+ if (!status) { -+ gsb_rqsx_response_success(buffer, rsp.pointer, rsp.length); -+ } else { -+ dev_err(d->dev, "rqst: failed with error %d\n", status); -+ gsb_rqsx_response_error(buffer, status); -+ } -+ -+ return AE_OK; -+} -+ -+static acpi_status san_rqsg(struct san_data *d, struct gsb_buffer *buffer) -+{ -+ struct gsb_data_rqsx *gsb_rqsg; -+ struct san_dgpu_event evt; -+ int status; -+ -+ gsb_rqsg = san_validate_rqsx(d->dev, "RQSG", buffer); -+ if (!gsb_rqsg) -+ return AE_OK; -+ -+ evt.category = gsb_rqsg->tc; -+ evt.target = gsb_rqsg->tid; -+ evt.command = gsb_rqsg->cid; -+ evt.instance = gsb_rqsg->iid; -+ evt.length = get_unaligned(&gsb_rqsg->cdl); -+ evt.payload = &gsb_rqsg->pld[0]; -+ -+ status = san_dgpu_notifier_call(&evt); -+ if (!status) { -+ gsb_rqsx_response_success(buffer, NULL, 0); -+ } else { -+ dev_err(d->dev, "rqsg: failed with error %d\n", status); -+ gsb_rqsx_response_error(buffer, status); -+ } -+ -+ return AE_OK; -+} -+ -+static acpi_status san_opreg_handler(u32 function, acpi_physical_address command, -+ u32 bits, u64 *value64, void *opreg_context, -+ void *region_context) -+{ -+ struct san_data *d = to_san_data(opreg_context, info); -+ struct gsb_buffer *buffer = (struct gsb_buffer *)value64; -+ int accessor_type = (function & 0xFFFF0000) >> 16; -+ -+ if (command != SAN_GSB_COMMAND) { -+ dev_warn(d->dev, "unsupported command: %#04llx\n", command); -+ return AE_OK; -+ } -+ -+ if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { -+ dev_err(d->dev, "invalid access type: %#04x\n", accessor_type); -+ return AE_OK; -+ } -+ -+ /* Buffer must have at least contain the command-value. */ -+ if (buffer->len == 0) { -+ dev_err(d->dev, "request-package too small\n"); -+ return AE_OK; -+ } -+ -+ switch (buffer->data.in.cv) { -+ case SAN_GSB_REQUEST_CV_RQST: -+ return san_rqst(d, buffer); -+ -+ case SAN_GSB_REQUEST_CV_ETWL: -+ return san_etwl(d, buffer); -+ -+ case SAN_GSB_REQUEST_CV_RQSG: -+ return san_rqsg(d, buffer); -+ -+ default: -+ dev_warn(d->dev, "unsupported SAN0 request (cv: %#04x)\n", -+ buffer->data.in.cv); -+ return AE_OK; -+ } -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int san_events_register(struct platform_device *pdev) -+{ -+ struct san_data *d = platform_get_drvdata(pdev); -+ int status; -+ -+ d->nf_bat.base.priority = 1; -+ d->nf_bat.base.fn = san_evt_bat_nf; -+ d->nf_bat.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ d->nf_bat.event.id.target_category = SSAM_SSH_TC_BAT; -+ d->nf_bat.event.id.instance = 0; -+ d->nf_bat.event.mask = SSAM_EVENT_MASK_TARGET; -+ d->nf_bat.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ d->nf_tmp.base.priority = 1; -+ d->nf_tmp.base.fn = san_evt_tmp_nf; -+ d->nf_tmp.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ d->nf_tmp.event.id.target_category = SSAM_SSH_TC_TMP; -+ d->nf_tmp.event.id.instance = 0; -+ d->nf_tmp.event.mask = SSAM_EVENT_MASK_TARGET; -+ d->nf_tmp.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ status = ssam_notifier_register(d->ctrl, &d->nf_bat); -+ if (status) -+ return status; -+ -+ status = ssam_notifier_register(d->ctrl, &d->nf_tmp); -+ if (status) -+ ssam_notifier_unregister(d->ctrl, &d->nf_bat); -+ -+ return status; -+} -+ -+static void san_events_unregister(struct platform_device *pdev) -+{ -+ struct san_data *d = platform_get_drvdata(pdev); -+ -+ ssam_notifier_unregister(d->ctrl, &d->nf_bat); -+ ssam_notifier_unregister(d->ctrl, &d->nf_tmp); -+} -+ -+#define san_consumer_printk(level, dev, handle, fmt, ...) \ -+do { \ -+ char *path = ""; \ -+ struct acpi_buffer buffer = { \ -+ .length = ACPI_ALLOCATE_BUFFER, \ -+ .pointer = NULL, \ -+ }; \ -+ \ -+ if (ACPI_SUCCESS(acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer))) \ -+ path = buffer.pointer; \ -+ \ -+ dev_##level(dev, "[%s]: " fmt, path, ##__VA_ARGS__); \ -+ kfree(buffer.pointer); \ -+} while (0) -+ -+#define san_consumer_dbg(dev, handle, fmt, ...) \ -+ san_consumer_printk(dbg, dev, handle, fmt, ##__VA_ARGS__) -+ -+#define san_consumer_warn(dev, handle, fmt, ...) \ -+ san_consumer_printk(warn, dev, handle, fmt, ##__VA_ARGS__) -+ -+static bool is_san_consumer(struct platform_device *pdev, acpi_handle handle) -+{ -+ struct acpi_handle_list dep_devices; -+ acpi_handle supplier = ACPI_HANDLE(&pdev->dev); -+ acpi_status status; -+ int i; -+ -+ if (!acpi_has_method(handle, "_DEP")) -+ return false; -+ -+ status = acpi_evaluate_reference(handle, "_DEP", NULL, &dep_devices); -+ if (ACPI_FAILURE(status)) { -+ san_consumer_dbg(&pdev->dev, handle, "failed to evaluate _DEP\n"); -+ return false; -+ } -+ -+ for (i = 0; i < dep_devices.count; i++) { -+ if (dep_devices.handles[i] == supplier) -+ return true; -+ } -+ -+ return false; -+} -+ -+static acpi_status san_consumer_setup(acpi_handle handle, u32 lvl, -+ void *context, void **rv) -+{ -+ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER; -+ struct platform_device *pdev = context; -+ struct acpi_device *adev; -+ struct device_link *link; -+ -+ if (!is_san_consumer(pdev, handle)) -+ return AE_OK; -+ -+ /* Ignore ACPI devices that are not present. */ -+ if (acpi_bus_get_device(handle, &adev) != 0) -+ return AE_OK; -+ -+ san_consumer_dbg(&pdev->dev, handle, "creating device link\n"); -+ -+ /* Try to set up device links, ignore but log errors. */ -+ link = device_link_add(&adev->dev, &pdev->dev, flags); -+ if (!link) { -+ san_consumer_warn(&pdev->dev, handle, "failed to create device link\n"); -+ return AE_OK; -+ } -+ -+ return AE_OK; -+} -+ -+static int san_consumer_links_setup(struct platform_device *pdev) -+{ -+ acpi_status status; -+ -+ status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, -+ ACPI_UINT32_MAX, san_consumer_setup, NULL, -+ pdev, NULL); -+ -+ return status ? -EFAULT : 0; -+} -+ -+static int san_probe(struct platform_device *pdev) -+{ -+ acpi_handle san = ACPI_HANDLE(&pdev->dev); -+ struct ssam_controller *ctrl; -+ struct san_data *data; -+ acpi_status astatus; -+ int status; -+ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ status = san_consumer_links_setup(pdev); -+ if (status) -+ return status; -+ -+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); -+ if (!data) -+ return -ENOMEM; -+ -+ data->dev = &pdev->dev; -+ data->ctrl = ctrl; -+ -+ platform_set_drvdata(pdev, data); -+ -+ astatus = acpi_install_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, -+ &san_opreg_handler, NULL, -+ &data->info); -+ if (ACPI_FAILURE(astatus)) -+ return -ENXIO; -+ -+ status = san_events_register(pdev); -+ if (status) -+ goto err_enable_events; -+ -+ status = san_set_rqsg_interface_device(&pdev->dev); -+ if (status) -+ goto err_install_dev; -+ -+ acpi_walk_dep_device_list(san); -+ return 0; -+ -+err_install_dev: -+ san_events_unregister(pdev); -+err_enable_events: -+ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, -+ &san_opreg_handler); -+ return status; -+} -+ -+static int san_remove(struct platform_device *pdev) -+{ -+ acpi_handle san = ACPI_HANDLE(&pdev->dev); -+ -+ san_set_rqsg_interface_device(NULL); -+ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, -+ &san_opreg_handler); -+ san_events_unregister(pdev); -+ -+ /* -+ * We have unregistered our event sources. Now we need to ensure that -+ * all delayed works they may have spawned are run to completion. -+ */ -+ flush_scheduled_work(); -+ -+ return 0; -+} -+ -+static const struct acpi_device_id san_match[] = { -+ { "MSHW0091" }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, san_match); -+ -+static struct platform_driver surface_acpi_notify = { -+ .probe = san_probe, -+ .remove = san_remove, -+ .driver = { -+ .name = "surface_acpi_notify", -+ .acpi_match_table = san_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_acpi_notify); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/include/linux/surface_acpi_notify.h b/include/linux/surface_acpi_notify.h -new file mode 100644 -index 000000000000..8e3e86c7d78c ---- /dev/null -+++ b/include/linux/surface_acpi_notify.h -@@ -0,0 +1,39 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Interface for Surface ACPI Notify (SAN) driver. -+ * -+ * Provides access to discrete GPU notifications sent from ACPI via the SAN -+ * driver, which are not handled by this driver directly. -+ * -+ * Copyright (C) 2019-2020 Maximilian Luz -+ */ -+ -+#ifndef _LINUX_SURFACE_ACPI_NOTIFY_H -+#define _LINUX_SURFACE_ACPI_NOTIFY_H -+ -+#include -+#include -+ -+/** -+ * struct san_dgpu_event - Discrete GPU ACPI event. -+ * @category: Category of the event. -+ * @target: Target ID of the event source. -+ * @command: Command ID of the event. -+ * @instance: Instance ID of the event source. -+ * @length: Length of the event's payload data (in bytes). -+ * @payload: Pointer to the event's payload data. -+ */ -+struct san_dgpu_event { -+ u8 category; -+ u8 target; -+ u8 command; -+ u8 instance; -+ u16 length; -+ u8 *payload; -+}; -+ -+int san_client_link(struct device *client); -+int san_dgpu_notifier_register(struct notifier_block *nb); -+int san_dgpu_notifier_unregister(struct notifier_block *nb); -+ -+#endif /* _LINUX_SURFACE_ACPI_NOTIFY_H */ --- -2.31.1 - -From 3c956eaece7e8cbe8715c64ac20b5992bd4593a5 Mon Sep 17 00:00:00 2001 -From: Colin Ian King -Date: Mon, 11 Jan 2021 14:46:48 +0000 -Subject: [PATCH] platform/surface: fix potential integer overflow on shift of - a int - -The left shift of int 32 bit integer constant 1 is evaluated using 32 bit -arithmetic and then passed as a 64 bit function argument. In the case where -func is 32 or more this can lead to an oveflow. Avoid this by shifting -using the BIT_ULL macro instead. - -Addresses-Coverity: ("Unintentional integer overflow") -Fixes: fc00bc8ac1da ("platform/surface: Add Surface ACPI Notify driver") -Signed-off-by: Colin Ian King -Reviewed-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210111144648.20498-1-colin.king@canonical.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_acpi_notify.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c -index 8cd67a669c86..ef9c1f8e8336 100644 ---- a/drivers/platform/surface/surface_acpi_notify.c -+++ b/drivers/platform/surface/surface_acpi_notify.c -@@ -188,7 +188,7 @@ static int san_acpi_notify_event(struct device *dev, u64 func, - union acpi_object *obj; - int status = 0; - -- if (!acpi_check_dsm(san, &SAN_DSM_UUID, SAN_DSM_REVISION, 1 << func)) -+ if (!acpi_check_dsm(san, &SAN_DSM_UUID, SAN_DSM_REVISION, BIT_ULL(func))) - return 0; - - dev_dbg(dev, "notify event %#04llx\n", func); --- -2.31.1 - -From 4ef4bdd30753c3b7e346fb3b337a4674c9be23d3 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 11 Jan 2021 16:48:50 +0100 -Subject: [PATCH] platform/surface: aggregator_cdev: Fix access of - uninitialized variables - -When copy_struct_from_user() in ssam_cdev_request() fails, we directly -jump to the 'out' label. In this case, however 'spec' and 'rsp' are not -initialized, but we still access fields of those variables. Fix this by -initializing them at the time of their declaration. - -Reported-by: Colin Ian King -Fixes: 178f6ab77e61 ("platform/surface: Add Surface Aggregator user-space interface") -Addresses-Coverity: ("Uninitialized pointer read") -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210111154851.325404-2-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_cdev.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c -index 340d15b148b9..979340cdd9de 100644 ---- a/drivers/platform/surface/surface_aggregator_cdev.c -+++ b/drivers/platform/surface/surface_aggregator_cdev.c -@@ -66,8 +66,8 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) - { - struct ssam_cdev_request __user *r; - struct ssam_cdev_request rqst; -- struct ssam_request spec; -- struct ssam_response rsp; -+ struct ssam_request spec = {}; -+ struct ssam_response rsp = {}; - const void __user *plddata; - void __user *rspdata; - int status = 0, ret = 0, tmp; --- -2.31.1 - -From cd6b26f5d1f440269db33ce52308f63a6402ec8d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 11 Jan 2021 16:48:51 +0100 -Subject: [PATCH] platform/surface: aggregator_cdev: Add comments regarding - unchecked allocation size - -CI static analysis complains about the allocation size in payload and -response buffers being unchecked. In general, these allocations should -be safe as the user-input is u16 and thus limited to U16_MAX, which is -only slightly larger than the theoretical maximum imposed by the -underlying SSH protocol. - -All bounds on these values required by the underlying protocol are -enforced in ssam_request_sync() (or rather the functions called by it), -thus bounds here are only relevant for allocation. - -Add comments explaining that this should be safe. - -Reported-by: Colin Ian King -Fixes: 178f6ab77e61 ("platform/surface: Add Surface Aggregator user-space interface") -Addresses-Coverity: ("Untrusted allocation size") -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210111154851.325404-3-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface/surface_aggregator_cdev.c | 19 +++++++++++++++++++ - 1 file changed, 19 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c -index 979340cdd9de..79e28fab7e40 100644 ---- a/drivers/platform/surface/surface_aggregator_cdev.c -+++ b/drivers/platform/surface/surface_aggregator_cdev.c -@@ -106,6 +106,15 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) - goto out; - } - -+ /* -+ * Note: spec.length is limited to U16_MAX bytes via struct -+ * ssam_cdev_request. This is slightly larger than the -+ * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the -+ * underlying protocol (note that nothing remotely this size -+ * should ever be allocated in any normal case). This size is -+ * validated later in ssam_request_sync(), for allocation the -+ * bound imposed by u16 should be enough. -+ */ - spec.payload = kzalloc(spec.length, GFP_KERNEL); - if (!spec.payload) { - ret = -ENOMEM; -@@ -125,6 +134,16 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) - goto out; - } - -+ /* -+ * Note: rsp.capacity is limited to U16_MAX bytes via struct -+ * ssam_cdev_request. This is slightly larger than the -+ * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the -+ * underlying protocol (note that nothing remotely this size -+ * should ever be allocated in any normal case). In later use, -+ * this capacity does not have to be strictly bounded, as it -+ * is only used as an output buffer to be written to. For -+ * allocation the bound imposed by u16 should be enough. -+ */ - rsp.pointer = kzalloc(rsp.capacity, GFP_KERNEL); - if (!rsp.pointer) { - ret = -ENOMEM; --- -2.31.1 - -From b694c15ed9aa180aa6408d2089c4d0ac8ddbbf01 Mon Sep 17 00:00:00 2001 -From: Mauro Carvalho Chehab -Date: Thu, 14 Jan 2021 09:04:52 +0100 -Subject: [PATCH] platform/surface: aggregator: fix a kernel-doc markup - -A function has a different name between their prototype -and its kernel-doc markup: - - ../drivers/platform/surface/aggregator/ssh_request_layer.c:1065: warning: expecting prototype for ssh_rtl_tx_start(). Prototype was for ssh_rtl_start() instead - -Signed-off-by: Mauro Carvalho Chehab -Reviewed-by: Maximilian Luz -Link: https://lore.kernel.org/r/4a6bf33cfbd06654d78294127f2b6d354d073089.1610610937.git.mchehab+huawei@kernel.org -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/ssh_request_layer.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c -index bb1c862411a2..25db4d638cfa 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.c -@@ -1056,7 +1056,7 @@ void ssh_rtl_destroy(struct ssh_rtl *rtl) - } - - /** -- * ssh_rtl_tx_start() - Start request transmitter and receiver. -+ * ssh_rtl_start() - Start request transmitter and receiver. - * @rtl: The request transport layer. - * - * Return: Returns zero on success, a negative error code on failure. --- -2.31.1 - -From 7a6f7746cf7ed75744cea80ff3c22c4b3e395a7e Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 14 Jan 2021 16:08:26 +0100 -Subject: [PATCH] platform/surface: aggregator: Fix kernel-doc references - -Both, ssh_rtl_rx_start() and ssh_rtl_tx_start() functions, do not exist -and have been consolidated into ssh_rtl_start(). Nevertheless, -kernel-doc references the former functions. Replace those references -with references to ssh_rtl_start(). - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210114150826.19109-1-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/ssh_request_layer.c | 5 ++--- - 1 file changed, 2 insertions(+), 3 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c -index 25db4d638cfa..52a83a8fcf82 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.c -@@ -1004,9 +1004,8 @@ int ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags, - * - * Initializes the given request transport layer and associated packet - * transport layer. Transmitter and receiver threads must be started -- * separately via ssh_rtl_tx_start() and ssh_rtl_rx_start(), after the -- * request-layer has been initialized and the lower-level serial device layer -- * has been set up. -+ * separately via ssh_rtl_start(), after the request-layer has been -+ * initialized and the lower-level serial device layer has been set up. - * - * Return: Returns zero on success and a nonzero error code on failure. - */ --- -2.31.1 - -From 94141813173b0e7a08ebf80d5c1f1a85c6dc2c73 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 26 Jan 2021 18:22:02 +0100 -Subject: [PATCH] platform/surface: aggregator: Fix braces in if condition with - unlikely() macro - -The braces of the unlikely() macro inside the if condition only cover -the subtraction part, not the whole statement. This causes the result of -the subtraction to be converted to zero or one. While that still works -in this context, it causes static analysis tools to complain (and is -just plain wrong). - -Fix the bracket placement and, while at it, simplify the if-condition. -Also add a comment to the if-condition explaining what we expect the -result to be and what happens on the failure path, as it seems to have -caused a bit of confusion. - -This commit should not cause any difference in behavior or generated -code. - -Reported-by: Dan Carpenter -Fixes: c167b9c7e3d6 ("platform/surface: Add Surface Aggregator subsystem") -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210126172202.1428367-1-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface/aggregator/ssh_packet_layer.c | 19 ++++++++++++++++++- - 1 file changed, 18 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c -index 74f0faaa2b27..583315db8b02 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c -@@ -1694,7 +1694,24 @@ static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) - /* Find SYN. */ - syn_found = sshp_find_syn(source, &aligned); - -- if (unlikely(aligned.ptr - source->ptr) > 0) { -+ if (unlikely(aligned.ptr != source->ptr)) { -+ /* -+ * We expect aligned.ptr == source->ptr. If this is not the -+ * case, then aligned.ptr > source->ptr and we've encountered -+ * some unexpected data where we'd expect the start of a new -+ * message (i.e. the SYN sequence). -+ * -+ * This can happen when a CRC check for the previous message -+ * failed and we start actively searching for the next one -+ * (via the call to sshp_find_syn() above), or the first bytes -+ * of a message got dropped or corrupted. -+ * -+ * In any case, we issue a warning, send a NAK to the EC to -+ * request re-transmission of any data we haven't acknowledged -+ * yet, and finally, skip everything up to the next SYN -+ * sequence. -+ */ -+ - ptl_warn(ptl, "rx: parser: invalid start of frame, skipping\n"); - - /* --- -2.31.1 - -From 6678837e69c7317497fc5bbe06cf9e67f9ecbc4a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 11 Feb 2021 13:41:49 +0100 -Subject: [PATCH] platform/surface: aggregator: Fix access of unaligned value - -The raw message frame length is unaligned and explicitly marked as -little endian. It should not be accessed without the appropriate -accessor functions. Fix this. - -Note that payload.len already contains the correct length after parsing -via sshp_parse_frame(), so we can simply use that instead. - -Reported-by: kernel-test-robot -Fixes: c167b9c7e3d6 ("platform/surface: Add Surface Aggregator subsystem") -Signed-off-by: Maximilian Luz -Acked-by: Mark Gross -Reviewed-by: Andy Shevchenko -Link: https://lore.kernel.org/r/20210211124149.2439007-1-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/ssh_packet_layer.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c -index 583315db8b02..15d96eac6811 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c -@@ -1774,7 +1774,7 @@ static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) - break; - } - -- return aligned.ptr - source->ptr + SSH_MESSAGE_LENGTH(frame->len); -+ return aligned.ptr - source->ptr + SSH_MESSAGE_LENGTH(payload.len); - } - - static int ssh_ptl_rx_threadfn(void *data) --- -2.31.1 - -From c6c3789ec7a163b0dd62b90d9fd70deb71df30d9 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 7 Feb 2021 03:42:45 +0100 -Subject: [PATCH] platform/surface: Set up Surface Aggregator device registry - -The Surface System Aggregator Module (SSAM) subsystem provides various -functionalities, which are separated by spreading them across multiple -devices and corresponding drivers. Parts of that functionality / some of -those devices, however, can (as far as we currently know) not be -auto-detected by conventional means. While older (specifically 5th- and -6th-)generation models do advertise most of their functionality via -standard platform devices in ACPI, newer generations do not. - -As we are currently also not aware of any feasible way to query said -functionalities dynamically, this poses a problem. There is, however, a -device in ACPI that seems to be used by Windows for identifying -different Surface models: The Windows Surface Integration Device (WSID). -This device seems to have a HID corresponding to the overall set of -functionalities SSAM provides for the associated model. - -This commit introduces a registry providing non-detectable device -information via software nodes. In addition, a SSAM platform hub driver -is introduced, which takes care of creating and managing the SSAM -devices specified in this registry. This approach allows for a -hierarchical setup akin to ACPI and is easily extendable, e.g. via -firmware node properties. - -Note that this commit only provides the basis for the platform hub and -registry, and does not add any content to it. The registry will be -expanded in subsequent commits. - -Patchset: surface-sam ---- - MAINTAINERS | 1 + - drivers/platform/surface/Kconfig | 27 ++ - drivers/platform/surface/Makefile | 1 + - .../surface/surface_aggregator_registry.c | 284 ++++++++++++++++++ - 4 files changed, 313 insertions(+) - create mode 100644 drivers/platform/surface/surface_aggregator_registry.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index dfe4f4e1da7a..1fd2fd35d5b7 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11815,6 +11815,7 @@ F: Documentation/driver-api/surface_aggregator/ - F: drivers/platform/surface/aggregator/ - F: drivers/platform/surface/surface_acpi_notify.c - F: drivers/platform/surface/surface_aggregator_cdev.c -+F: drivers/platform/surface/surface_aggregator_registry.c - F: include/linux/surface_acpi_notify.h - F: include/linux/surface_aggregator/ - F: include/uapi/linux/surface_aggregator/ -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index b0b91fa2f6a1..97e08dd35992 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -77,6 +77,33 @@ config SURFACE_AGGREGATOR_CDEV - The provided interface is intended for debugging and development only, - and should not be used otherwise. - -+config SURFACE_AGGREGATOR_REGISTRY -+ tristate "Surface System Aggregator Module Device Registry" -+ depends on SURFACE_AGGREGATOR -+ depends on SURFACE_AGGREGATOR_BUS -+ help -+ Device-registry and device-hubs for Surface System Aggregator Module -+ (SSAM) devices. -+ -+ Provides a module and driver which act as a device-registry for SSAM -+ client devices that cannot be detected automatically, e.g. via ACPI. -+ Such devices are instead provided via this registry and attached via -+ device hubs, also provided in this module. -+ -+ Devices provided via this registry are: -+ - Platform profile (performance-/cooling-mode) device (5th- and later -+ generations). -+ - Battery/AC devices (7th-generation). -+ - HID input devices (7th-generation). -+ -+ Select M (recommended) or Y here if you want support for the above -+ mentioned devices on the corresponding Surface models. Without this -+ module, the respective devices will not be instantiated and thus any -+ functionality provided by them will be missing, even when drivers for -+ these devices are present. In other words, this module only provides -+ the respective client devices. Drivers for these devices still need to -+ be selected via the other options. -+ - config SURFACE_BOOK1_DGPU_SWITCH - tristate "Surface Book 1 dGPU Switch Driver" - depends on SYSFS -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 72f4d9fbb6be..30a212aefd35 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -10,6 +10,7 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o -+obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -new file mode 100644 -index 000000000000..a051d941ad96 ---- /dev/null -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -0,0 +1,284 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) client device registry. -+ * -+ * Registry for non-platform/non-ACPI SSAM client devices, i.e. devices that -+ * cannot be auto-detected. Provides device-hubs and performs instantiation -+ * for these devices. -+ * -+ * Copyright (C) 2020-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+ -+/* -- Device registry. ------------------------------------------------------ */ -+ -+/* -+ * SSAM device names follow the SSAM module alias, meaning they are prefixed -+ * with 'ssam:', followed by domain, category, target ID, instance ID, and -+ * function, each encoded as two-digit hexadecimal, separated by ':'. In other -+ * words, it follows the scheme -+ * -+ * ssam:dd:cc:tt:ii:ff -+ * -+ * Where, 'dd', 'cc', 'tt', 'ii', and 'ff' are the two-digit hexadecimal -+ * values mentioned above, respectively. -+ */ -+ -+/* Root node. */ -+static const struct software_node ssam_node_root = { -+ .name = "ssam_platform_hub", -+}; -+ -+/* Devices for Surface Book 2. */ -+static const struct software_node *ssam_node_group_sb2[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Book 3. */ -+static const struct software_node *ssam_node_group_sb3[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Laptop 1. */ -+static const struct software_node *ssam_node_group_sl1[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Laptop 2. */ -+static const struct software_node *ssam_node_group_sl2[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Laptop 3. */ -+static const struct software_node *ssam_node_group_sl3[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Laptop Go. */ -+static const struct software_node *ssam_node_group_slg1[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Pro 5. */ -+static const struct software_node *ssam_node_group_sp5[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Pro 6. */ -+static const struct software_node *ssam_node_group_sp6[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Pro 7. */ -+static const struct software_node *ssam_node_group_sp7[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+ -+/* -- Device registry helper functions. ------------------------------------- */ -+ -+static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) -+{ -+ u8 d, tc, tid, iid, fn; -+ int n; -+ -+ n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); -+ if (n != 5) -+ return -EINVAL; -+ -+ uid->domain = d; -+ uid->category = tc; -+ uid->target = tid; -+ uid->instance = iid; -+ uid->function = fn; -+ -+ return 0; -+} -+ -+static int ssam_hub_remove_devices_fn(struct device *dev, void *data) -+{ -+ if (!is_ssam_device(dev)) -+ return 0; -+ -+ ssam_device_remove(to_ssam_device(dev)); -+ return 0; -+} -+ -+static void ssam_hub_remove_devices(struct device *parent) -+{ -+ device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn); -+} -+ -+static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ struct ssam_device_uid uid; -+ struct ssam_device *sdev; -+ int status; -+ -+ status = ssam_uid_from_string(fwnode_get_name(node), &uid); -+ if (status) -+ return status; -+ -+ sdev = ssam_device_alloc(ctrl, uid); -+ if (!sdev) -+ return -ENOMEM; -+ -+ sdev->dev.parent = parent; -+ sdev->dev.fwnode = node; -+ -+ status = ssam_device_add(sdev); -+ if (status) -+ ssam_device_put(sdev); -+ -+ return status; -+} -+ -+static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ struct fwnode_handle *child; -+ int status; -+ -+ fwnode_for_each_child_node(node, child) { -+ /* -+ * Try to add the device specified in the firmware node. If -+ * this fails with -EINVAL, the node does not specify any SSAM -+ * device, so ignore it and continue with the next one. -+ */ -+ -+ status = ssam_hub_add_device(parent, ctrl, child); -+ if (status && status != -EINVAL) -+ goto err; -+ } -+ -+ return 0; -+err: -+ ssam_hub_remove_devices(parent); -+ return status; -+} -+ -+ -+/* -- SSAM platform/meta-hub driver. ---------------------------------------- */ -+ -+static const struct acpi_device_id ssam_platform_hub_match[] = { -+ /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ -+ { "MSHW0081", (unsigned long)ssam_node_group_sp5 }, -+ -+ /* Surface Pro 6 (OMBR >= 0x10) */ -+ { "MSHW0111", (unsigned long)ssam_node_group_sp6 }, -+ -+ /* Surface Pro 7 */ -+ { "MSHW0116", (unsigned long)ssam_node_group_sp7 }, -+ -+ /* Surface Book 2 */ -+ { "MSHW0107", (unsigned long)ssam_node_group_sb2 }, -+ -+ /* Surface Book 3 */ -+ { "MSHW0117", (unsigned long)ssam_node_group_sb3 }, -+ -+ /* Surface Laptop 1 */ -+ { "MSHW0086", (unsigned long)ssam_node_group_sl1 }, -+ -+ /* Surface Laptop 2 */ -+ { "MSHW0112", (unsigned long)ssam_node_group_sl2 }, -+ -+ /* Surface Laptop 3 (13", Intel) */ -+ { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, -+ -+ /* Surface Laptop 3 (15", AMD) */ -+ { "MSHW0110", (unsigned long)ssam_node_group_sl3 }, -+ -+ /* Surface Laptop Go 1 */ -+ { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, -+ -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match); -+ -+static int ssam_platform_hub_probe(struct platform_device *pdev) -+{ -+ const struct software_node **nodes; -+ struct ssam_controller *ctrl; -+ struct fwnode_handle *root; -+ int status; -+ -+ nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev); -+ if (!nodes) -+ return -ENODEV; -+ -+ /* -+ * As we're adding the SSAM client devices as children under this device -+ * and not the SSAM controller, we need to add a device link to the -+ * controller to ensure that we remove all of our devices before the -+ * controller is removed. This also guarantees proper ordering for -+ * suspend/resume of the devices on this hub. -+ */ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ status = software_node_register_node_group(nodes); -+ if (status) -+ return status; -+ -+ root = software_node_fwnode(&ssam_node_root); -+ if (!root) { -+ software_node_unregister_node_group(nodes); -+ return -ENOENT; -+ } -+ -+ set_secondary_fwnode(&pdev->dev, root); -+ -+ status = ssam_hub_add_devices(&pdev->dev, ctrl, root); -+ if (status) { -+ set_secondary_fwnode(&pdev->dev, NULL); -+ software_node_unregister_node_group(nodes); -+ } -+ -+ platform_set_drvdata(pdev, nodes); -+ return status; -+} -+ -+static int ssam_platform_hub_remove(struct platform_device *pdev) -+{ -+ const struct software_node **nodes = platform_get_drvdata(pdev); -+ -+ ssam_hub_remove_devices(&pdev->dev); -+ set_secondary_fwnode(&pdev->dev, NULL); -+ software_node_unregister_node_group(nodes); -+ return 0; -+} -+ -+static struct platform_driver ssam_platform_hub_driver = { -+ .probe = ssam_platform_hub_probe, -+ .remove = ssam_platform_hub_remove, -+ .driver = { -+ .name = "surface_aggregator_platform_hub", -+ .acpi_match_table = ssam_platform_hub_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(ssam_platform_hub_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); --- -2.31.1 - -From dfc9dddeddf9378ec27a99d32e03627396184b4b Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 7 Feb 2021 04:14:35 +0100 -Subject: [PATCH] platform/surface: aggregator_registry: Add base device hub - -The Surface Book 3 has a detachable base part. While the top part -(so-called clipboard) contains the CPU, touchscreen, and primary -battery, the base contains, among other things, a keyboard, touchpad, -and secondary battery. - -Those devices do not react well to being accessed when the base part is -detached and should thus be removed and added in sync with the base. To -facilitate this, we introduce a virtual base device hub, which -automatically removes or adds the devices registered under it. - -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 261 +++++++++++++++++- - 1 file changed, 260 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index a051d941ad96..6c23d75a044c 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -11,9 +11,12 @@ - - #include - #include -+#include - #include -+#include - #include - #include -+#include - - #include - #include -@@ -38,6 +41,12 @@ static const struct software_node ssam_node_root = { - .name = "ssam_platform_hub", - }; - -+/* Base device hub (devices attached to Surface Book 3 base). */ -+static const struct software_node ssam_node_hub_base = { -+ .name = "ssam:00:00:02:00:00", -+ .parent = &ssam_node_root, -+}; -+ - /* Devices for Surface Book 2. */ - static const struct software_node *ssam_node_group_sb2[] = { - &ssam_node_root, -@@ -47,6 +56,7 @@ static const struct software_node *ssam_node_group_sb2[] = { - /* Devices for Surface Book 3. */ - static const struct software_node *ssam_node_group_sb3[] = { - &ssam_node_root, -+ &ssam_node_hub_base, - NULL, - }; - -@@ -177,6 +187,230 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c - } - - -+/* -- SSAM base-hub driver. ------------------------------------------------- */ -+ -+enum ssam_base_hub_state { -+ SSAM_BASE_HUB_UNINITIALIZED, -+ SSAM_BASE_HUB_CONNECTED, -+ SSAM_BASE_HUB_DISCONNECTED, -+}; -+ -+struct ssam_base_hub { -+ struct ssam_device *sdev; -+ -+ struct mutex lock; /* Guards state update checks and transitions. */ -+ enum ssam_base_hub_state state; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0d, -+ .instance_id = 0x00, -+}); -+ -+#define SSAM_BAS_OPMODE_TABLET 0x00 -+#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c -+ -+static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state) -+{ -+ u8 opmode; -+ int status; -+ -+ status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); -+ return status; -+ } -+ -+ if (opmode != SSAM_BAS_OPMODE_TABLET) -+ *state = SSAM_BASE_HUB_CONNECTED; -+ else -+ *state = SSAM_BASE_HUB_DISCONNECTED; -+ -+ return 0; -+} -+ -+static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr, -+ char *buf) -+{ -+ struct ssam_base_hub *hub = dev_get_drvdata(dev); -+ bool connected; -+ -+ mutex_lock(&hub->lock); -+ connected = hub->state == SSAM_BASE_HUB_CONNECTED; -+ mutex_unlock(&hub->lock); -+ -+ return sysfs_emit(buf, "%d\n", connected); -+} -+ -+static struct device_attribute ssam_base_hub_attr_state = -+ __ATTR(state, 0444, ssam_base_hub_state_show, NULL); -+ -+static struct attribute *ssam_base_hub_attrs[] = { -+ &ssam_base_hub_attr_state.attr, -+ NULL, -+}; -+ -+const struct attribute_group ssam_base_hub_group = { -+ .attrs = ssam_base_hub_attrs, -+}; -+ -+static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new) -+{ -+ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -+ int status = 0; -+ -+ lockdep_assert_held(&hub->lock); -+ -+ if (hub->state == new) -+ return 0; -+ hub->state = new; -+ -+ if (hub->state == SSAM_BASE_HUB_CONNECTED) -+ status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); -+ else -+ ssam_hub_remove_devices(&hub->sdev->dev); -+ -+ if (status) -+ dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); -+ -+ return status; -+} -+ -+static int ssam_base_hub_update(struct ssam_base_hub *hub) -+{ -+ enum ssam_base_hub_state state; -+ int status; -+ -+ mutex_lock(&hub->lock); -+ -+ status = ssam_base_hub_query_state(hub, &state); -+ if (!status) -+ status = __ssam_base_hub_update(hub, state); -+ -+ mutex_unlock(&hub->lock); -+ return status; -+} -+ -+static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_base_hub *hub; -+ struct ssam_device *sdev; -+ enum ssam_base_hub_state new; -+ -+ hub = container_of(nf, struct ssam_base_hub, notif); -+ sdev = hub->sdev; -+ -+ if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) -+ return 0; -+ -+ if (event->length < 1) { -+ dev_err(&sdev->dev, "unexpected payload size: %u\n", -+ event->length); -+ return 0; -+ } -+ -+ if (event->data[0]) -+ new = SSAM_BASE_HUB_CONNECTED; -+ else -+ new = SSAM_BASE_HUB_DISCONNECTED; -+ -+ mutex_lock(&hub->lock); -+ __ssam_base_hub_update(hub, new); -+ mutex_unlock(&hub->lock); -+ -+ /* -+ * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -+ * consumed by the detachment system driver. We're just a (more or less) -+ * silent observer. -+ */ -+ return 0; -+} -+ -+static int __maybe_unused ssam_base_hub_resume(struct device *dev) -+{ -+ return ssam_base_hub_update(dev_get_drvdata(dev)); -+} -+static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); -+ -+static int ssam_base_hub_probe(struct ssam_device *sdev) -+{ -+ struct ssam_base_hub *hub; -+ int status; -+ -+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -+ if (!hub) -+ return -ENOMEM; -+ -+ mutex_init(&hub->lock); -+ -+ hub->sdev = sdev; -+ hub->state = SSAM_BASE_HUB_UNINITIALIZED; -+ -+ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -+ hub->notif.base.fn = ssam_base_hub_notif; -+ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ hub->notif.event.id.target_category = SSAM_SSH_TC_BAS, -+ hub->notif.event.id.instance = 0, -+ hub->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ ssam_device_set_drvdata(sdev, hub); -+ -+ status = ssam_notifier_register(sdev->ctrl, &hub->notif); -+ if (status) -+ goto err_register; -+ -+ status = ssam_base_hub_update(hub); -+ if (status) -+ goto err_update; -+ -+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); -+ if (status) -+ goto err_update; -+ -+ return 0; -+ -+err_update: -+ ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_hub_remove_devices(&sdev->dev); -+err_register: -+ mutex_destroy(&hub->lock); -+ return status; -+} -+ -+static void ssam_base_hub_remove(struct ssam_device *sdev) -+{ -+ struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); -+ -+ sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); -+ -+ ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_hub_remove_devices(&sdev->dev); -+ -+ mutex_destroy(&hub->lock); -+} -+ -+static const struct ssam_device_id ssam_base_hub_match[] = { -+ { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) }, -+ { }, -+}; -+ -+static struct ssam_device_driver ssam_base_hub_driver = { -+ .probe = ssam_base_hub_probe, -+ .remove = ssam_base_hub_remove, -+ .match_table = ssam_base_hub_match, -+ .driver = { -+ .name = "surface_aggregator_base_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_base_hub_pm_ops, -+ }, -+}; -+ -+ - /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ - - static const struct acpi_device_id ssam_platform_hub_match[] = { -@@ -277,7 +511,32 @@ static struct platform_driver ssam_platform_hub_driver = { - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - }, - }; --module_platform_driver(ssam_platform_hub_driver); -+ -+ -+/* -- Module initialization. ------------------------------------------------ */ -+ -+static int __init ssam_device_hub_init(void) -+{ -+ int status; -+ -+ status = platform_driver_register(&ssam_platform_hub_driver); -+ if (status) -+ return status; -+ -+ status = ssam_device_driver_register(&ssam_base_hub_driver); -+ if (status) -+ platform_driver_unregister(&ssam_platform_hub_driver); -+ -+ return status; -+} -+module_init(ssam_device_hub_init); -+ -+static void __exit ssam_device_hub_exit(void) -+{ -+ ssam_device_driver_unregister(&ssam_base_hub_driver); -+ platform_driver_unregister(&ssam_platform_hub_driver); -+} -+module_exit(ssam_device_hub_exit); - - MODULE_AUTHOR("Maximilian Luz "); - MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); --- -2.31.1 - -From 8c6120b039adf4d4a2988c0842f95e1f8e8a7d03 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 7 Feb 2021 04:55:09 +0100 -Subject: [PATCH] platform/surface: aggregator_registry: Add battery subsystem - devices - -Add battery subsystem (TC=0x02) devices (battery and AC) to the SSAM -device registry. These devices need to be registered for 7th-generation -Surface models. On 5th- and 6th-generation models, these devices are -handled via the standard ACPI battery/AC interface, which in turn -accesses the same SSAM interface via the Surface ACPI Notify (SAN) -driver. - -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 27 +++++++++++++++++++ - 1 file changed, 27 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 6c23d75a044c..cde279692842 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -47,6 +47,24 @@ static const struct software_node ssam_node_hub_base = { - .parent = &ssam_node_root, - }; - -+/* AC adapter. */ -+static const struct software_node ssam_node_bat_ac = { -+ .name = "ssam:01:02:01:01:01", -+ .parent = &ssam_node_root, -+}; -+ -+/* Primary battery. */ -+static const struct software_node ssam_node_bat_main = { -+ .name = "ssam:01:02:01:01:00", -+ .parent = &ssam_node_root, -+}; -+ -+/* Secondary battery (Surface Book 3). */ -+static const struct software_node ssam_node_bat_sb3base = { -+ .name = "ssam:01:02:02:01:00", -+ .parent = &ssam_node_hub_base, -+}; -+ - /* Devices for Surface Book 2. */ - static const struct software_node *ssam_node_group_sb2[] = { - &ssam_node_root, -@@ -57,6 +75,9 @@ static const struct software_node *ssam_node_group_sb2[] = { - static const struct software_node *ssam_node_group_sb3[] = { - &ssam_node_root, - &ssam_node_hub_base, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, -+ &ssam_node_bat_sb3base, - NULL, - }; - -@@ -75,12 +96,16 @@ static const struct software_node *ssam_node_group_sl2[] = { - /* Devices for Surface Laptop 3. */ - static const struct software_node *ssam_node_group_sl3[] = { - &ssam_node_root, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, - NULL, - }; - - /* Devices for Surface Laptop Go. */ - static const struct software_node *ssam_node_group_slg1[] = { - &ssam_node_root, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, - NULL, - }; - -@@ -99,6 +124,8 @@ static const struct software_node *ssam_node_group_sp6[] = { - /* Devices for Surface Pro 7. */ - static const struct software_node *ssam_node_group_sp7[] = { - &ssam_node_root, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, - NULL, - }; - --- -2.31.1 - -From c3a854eaad88e67fff96cecce704b7440215058b Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 7 Feb 2021 05:01:08 +0100 -Subject: [PATCH] platform/surface: aggregator_registry: Add platform profile - device - -Add the SSAM platform profile device to the SSAM device registry. This -device is accessible under the thermal subsystem (TC=0x03) and needs to -be registered for all Surface models. - -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index cde279692842..33904613dd4b 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -65,9 +65,16 @@ static const struct software_node ssam_node_bat_sb3base = { - .parent = &ssam_node_hub_base, - }; - -+/* Platform profile / performance-mode device. */ -+static const struct software_node ssam_node_tmp_pprof = { -+ .name = "ssam:01:03:01:00:01", -+ .parent = &ssam_node_root, -+}; -+ - /* Devices for Surface Book 2. */ - static const struct software_node *ssam_node_group_sb2[] = { - &ssam_node_root, -+ &ssam_node_tmp_pprof, - NULL, - }; - -@@ -78,18 +85,21 @@ static const struct software_node *ssam_node_group_sb3[] = { - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_bat_sb3base, -+ &ssam_node_tmp_pprof, - NULL, - }; - - /* Devices for Surface Laptop 1. */ - static const struct software_node *ssam_node_group_sl1[] = { - &ssam_node_root, -+ &ssam_node_tmp_pprof, - NULL, - }; - - /* Devices for Surface Laptop 2. */ - static const struct software_node *ssam_node_group_sl2[] = { - &ssam_node_root, -+ &ssam_node_tmp_pprof, - NULL, - }; - -@@ -98,6 +108,7 @@ static const struct software_node *ssam_node_group_sl3[] = { - &ssam_node_root, - &ssam_node_bat_ac, - &ssam_node_bat_main, -+ &ssam_node_tmp_pprof, - NULL, - }; - -@@ -106,18 +117,21 @@ static const struct software_node *ssam_node_group_slg1[] = { - &ssam_node_root, - &ssam_node_bat_ac, - &ssam_node_bat_main, -+ &ssam_node_tmp_pprof, - NULL, - }; - - /* Devices for Surface Pro 5. */ - static const struct software_node *ssam_node_group_sp5[] = { - &ssam_node_root, -+ &ssam_node_tmp_pprof, - NULL, - }; - - /* Devices for Surface Pro 6. */ - static const struct software_node *ssam_node_group_sp6[] = { - &ssam_node_root, -+ &ssam_node_tmp_pprof, - NULL, - }; - -@@ -126,6 +140,7 @@ static const struct software_node *ssam_node_group_sp7[] = { - &ssam_node_root, - &ssam_node_bat_ac, - &ssam_node_bat_main, -+ &ssam_node_tmp_pprof, - NULL, - }; - --- -2.31.1 - -From abd9c0325f6f26e7a3fe6075c31b678a88fcecc0 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 7 Feb 2021 05:06:41 +0100 -Subject: [PATCH] platform/surface: aggregator_registry: Add DTX device - -Add the detachment system (DTX) SSAM device for the Surface Book 3. This -device is accessible under the base (TC=0x11) subsystem. - -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 33904613dd4b..dc044d06828b 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -71,6 +71,12 @@ static const struct software_node ssam_node_tmp_pprof = { - .parent = &ssam_node_root, - }; - -+/* DTX / detachment-system device (Surface Book 3). */ -+static const struct software_node ssam_node_bas_dtx = { -+ .name = "ssam:01:11:01:00:00", -+ .parent = &ssam_node_root, -+}; -+ - /* Devices for Surface Book 2. */ - static const struct software_node *ssam_node_group_sb2[] = { - &ssam_node_root, -@@ -86,6 +92,7 @@ static const struct software_node *ssam_node_group_sb3[] = { - &ssam_node_bat_main, - &ssam_node_bat_sb3base, - &ssam_node_tmp_pprof, -+ &ssam_node_bas_dtx, - NULL, - }; - --- -2.31.1 - -From f77a2d7eb7c53a6e00d04f940f663794affedb93 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 7 Feb 2021 05:16:44 +0100 -Subject: [PATCH] platform/surface: aggregator_registry: Add HID subsystem - devices - -Add HID subsystem (TC=0x15) devices. These devices need to be registered -for 7th-generation Surface models. On previous generations, these -devices are either provided as platform devices via ACPI (Surface Laptop -1 and 2) or implemented as standard USB device. - -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 49 +++++++++++++++++++ - 1 file changed, 49 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index dc044d06828b..caee90d135c5 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -77,6 +77,48 @@ static const struct software_node ssam_node_bas_dtx = { - .parent = &ssam_node_root, - }; - -+/* HID keyboard. */ -+static const struct software_node ssam_node_hid_main_keyboard = { -+ .name = "ssam:01:15:02:01:00", -+ .parent = &ssam_node_root, -+}; -+ -+/* HID touchpad. */ -+static const struct software_node ssam_node_hid_main_touchpad = { -+ .name = "ssam:01:15:02:03:00", -+ .parent = &ssam_node_root, -+}; -+ -+/* HID device instance 5 (unknown HID device). */ -+static const struct software_node ssam_node_hid_main_iid5 = { -+ .name = "ssam:01:15:02:05:00", -+ .parent = &ssam_node_root, -+}; -+ -+/* HID keyboard (base hub). */ -+static const struct software_node ssam_node_hid_base_keyboard = { -+ .name = "ssam:01:15:02:01:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+/* HID touchpad (base hub). */ -+static const struct software_node ssam_node_hid_base_touchpad = { -+ .name = "ssam:01:15:02:03:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+/* HID device instance 5 (unknown HID device, base hub). */ -+static const struct software_node ssam_node_hid_base_iid5 = { -+ .name = "ssam:01:15:02:05:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+/* HID device instance 6 (unknown HID device, base hub). */ -+static const struct software_node ssam_node_hid_base_iid6 = { -+ .name = "ssam:01:15:02:06:00", -+ .parent = &ssam_node_hub_base, -+}; -+ - /* Devices for Surface Book 2. */ - static const struct software_node *ssam_node_group_sb2[] = { - &ssam_node_root, -@@ -93,6 +135,10 @@ static const struct software_node *ssam_node_group_sb3[] = { - &ssam_node_bat_sb3base, - &ssam_node_tmp_pprof, - &ssam_node_bas_dtx, -+ &ssam_node_hid_base_keyboard, -+ &ssam_node_hid_base_touchpad, -+ &ssam_node_hid_base_iid5, -+ &ssam_node_hid_base_iid6, - NULL, - }; - -@@ -116,6 +162,9 @@ static const struct software_node *ssam_node_group_sl3[] = { - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -+ &ssam_node_hid_main_keyboard, -+ &ssam_node_hid_main_touchpad, -+ &ssam_node_hid_main_iid5, - NULL, - }; - --- -2.31.1 - -From 7cd25ac485e645d62254795a2bc5602d37f1c07d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 9 Mar 2021 17:03:15 +0100 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for - Surface Pro 7+ - -The Surface Pro 7+ is essentially a refresh of the Surface Pro 7 with -updated hardware and a new WSID identifier. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index caee90d135c5..6de74e893d06 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -191,7 +191,7 @@ static const struct software_node *ssam_node_group_sp6[] = { - NULL, - }; - --/* Devices for Surface Pro 7. */ -+/* Devices for Surface Pro 7 and Surface Pro 7+. */ - static const struct software_node *ssam_node_group_sp7[] = { - &ssam_node_root, - &ssam_node_bat_ac, -@@ -521,6 +521,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { - /* Surface Pro 7 */ - { "MSHW0116", (unsigned long)ssam_node_group_sp7 }, - -+ /* Surface Pro 7+ */ -+ { "MSHW0119", (unsigned long)ssam_node_group_sp7 }, -+ - /* Surface Book 2 */ - { "MSHW0107", (unsigned long)ssam_node_group_sb2 }, - --- -2.31.1 - -From 97d7677ff2a364de5738e9c1211b3c87225e1d08 Mon Sep 17 00:00:00 2001 -From: Wei Yongjun -Date: Tue, 9 Mar 2021 13:15:00 +0000 -Subject: [PATCH] platform/surface: aggregator_registry: Make symbol - 'ssam_base_hub_group' static - -The sparse tool complains as follows: - -drivers/platform/surface/surface_aggregator_registry.c:355:30: warning: - symbol 'ssam_base_hub_group' was not declared. Should it be static? - -This symbol is not used outside of surface_aggregator_registry.c, so this -commit marks it static. - -Fixes: 797e78564634 ("platform/surface: aggregator_registry: Add base device hub") -Reported-by: Hulk Robot -Signed-off-by: Wei Yongjun -Reviewed-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210309131500.1885772-1-weiyongjun1@huawei.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 6de74e893d06..304d601980ed 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -352,7 +352,7 @@ static struct attribute *ssam_base_hub_attrs[] = { - NULL, - }; - --const struct attribute_group ssam_base_hub_group = { -+static const struct attribute_group ssam_base_hub_group = { - .attrs = ssam_base_hub_attrs, - }; - --- -2.31.1 - -From 02fd3e2f7c55c7020bda48af89d15101e5ef7ad9 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 7 Apr 2021 01:51:47 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Give devices time to - set up when connecting - -Sometimes, the "base connected" event that we rely on to (re-)attach the -device connected to the base is sent a bit too early. When this happens, -some devices may not be completely ready yet. - -Specifically, the battery has been observed to report zero-values for -things like full charge capacity, which, however, is only loaded once -when the driver for that device probes. This can thus result in battery -readings being unavailable. - -As we cannot easily and reliably discern between devices that are not -ready yet and devices that are not connected (i.e. will never be ready), -delay adding these devices. This should give them enough time to set up. - -The delay is set to 2.5 seconds, which should give us a good safety -margin based on testing and still be fairly responsive for users. - -To achieve that delay switch to updating via a delayed work struct, -which means that we can also get rid of some locking. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 98 ++++++++----------- - 1 file changed, 40 insertions(+), 58 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 304d601980ed..dd1d6504b004 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -13,10 +13,10 @@ - #include - #include - #include --#include - #include - #include - #include -+#include - - #include - #include -@@ -287,6 +287,13 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c - - /* -- SSAM base-hub driver. ------------------------------------------------- */ - -+/* -+ * Some devices (especially battery) may need a bit of time to be fully usable -+ * after being (re-)connected. This delay has been determined via -+ * experimentation. -+ */ -+#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) -+ - enum ssam_base_hub_state { - SSAM_BASE_HUB_UNINITIALIZED, - SSAM_BASE_HUB_CONNECTED, -@@ -296,8 +303,8 @@ enum ssam_base_hub_state { - struct ssam_base_hub { - struct ssam_device *sdev; - -- struct mutex lock; /* Guards state update checks and transitions. */ - enum ssam_base_hub_state state; -+ struct delayed_work update_work; - - struct ssam_event_notifier notif; - }; -@@ -335,11 +342,7 @@ static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attrib - char *buf) - { - struct ssam_base_hub *hub = dev_get_drvdata(dev); -- bool connected; -- -- mutex_lock(&hub->lock); -- connected = hub->state == SSAM_BASE_HUB_CONNECTED; -- mutex_unlock(&hub->lock); -+ bool connected = hub->state == SSAM_BASE_HUB_CONNECTED; - - return sysfs_emit(buf, "%d\n", connected); - } -@@ -356,16 +359,20 @@ static const struct attribute_group ssam_base_hub_group = { - .attrs = ssam_base_hub_attrs, - }; - --static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new) -+static void ssam_base_hub_update_workfn(struct work_struct *work) - { -+ struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work); - struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -+ enum ssam_base_hub_state state; - int status = 0; - -- lockdep_assert_held(&hub->lock); -+ status = ssam_base_hub_query_state(hub, &state); -+ if (status) -+ return; - -- if (hub->state == new) -- return 0; -- hub->state = new; -+ if (hub->state == state) -+ return; -+ hub->state = state; - - if (hub->state == SSAM_BASE_HUB_CONNECTED) - status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); -@@ -374,51 +381,28 @@ static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_ - - if (status) - dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); -- -- return status; --} -- --static int ssam_base_hub_update(struct ssam_base_hub *hub) --{ -- enum ssam_base_hub_state state; -- int status; -- -- mutex_lock(&hub->lock); -- -- status = ssam_base_hub_query_state(hub, &state); -- if (!status) -- status = __ssam_base_hub_update(hub, state); -- -- mutex_unlock(&hub->lock); -- return status; - } - - static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) - { -- struct ssam_base_hub *hub; -- struct ssam_device *sdev; -- enum ssam_base_hub_state new; -- -- hub = container_of(nf, struct ssam_base_hub, notif); -- sdev = hub->sdev; -+ struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif); -+ unsigned long delay; - - if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) - return 0; - - if (event->length < 1) { -- dev_err(&sdev->dev, "unexpected payload size: %u\n", -- event->length); -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); - return 0; - } - -- if (event->data[0]) -- new = SSAM_BASE_HUB_CONNECTED; -- else -- new = SSAM_BASE_HUB_DISCONNECTED; -+ /* -+ * Delay update when the base is being connected to give devices/EC -+ * some time to set up. -+ */ -+ delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0; - -- mutex_lock(&hub->lock); -- __ssam_base_hub_update(hub, new); -- mutex_unlock(&hub->lock); -+ schedule_delayed_work(&hub->update_work, delay); - - /* - * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -@@ -430,7 +414,10 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam - - static int __maybe_unused ssam_base_hub_resume(struct device *dev) - { -- return ssam_base_hub_update(dev_get_drvdata(dev)); -+ struct ssam_base_hub *hub = dev_get_drvdata(dev); -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; - } - static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); - -@@ -443,8 +430,6 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - if (!hub) - return -ENOMEM; - -- mutex_init(&hub->lock); -- - hub->sdev = sdev; - hub->state = SSAM_BASE_HUB_UNINITIALIZED; - -@@ -456,27 +441,25 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - hub->notif.event.mask = SSAM_EVENT_MASK_NONE; - hub->notif.event.flags = SSAM_EVENT_SEQUENCED; - -+ INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn); -+ - ssam_device_set_drvdata(sdev, hub); - - status = ssam_notifier_register(sdev->ctrl, &hub->notif); - if (status) -- goto err_register; -- -- status = ssam_base_hub_update(hub); -- if (status) -- goto err_update; -+ return status; - - status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); - if (status) -- goto err_update; -+ goto err; - -+ schedule_delayed_work(&hub->update_work, 0); - return 0; - --err_update: -+err: - ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); - ssam_hub_remove_devices(&sdev->dev); --err_register: -- mutex_destroy(&hub->lock); - return status; - } - -@@ -487,9 +470,8 @@ static void ssam_base_hub_remove(struct ssam_device *sdev) - sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); - - ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); - ssam_hub_remove_devices(&sdev->dev); -- -- mutex_destroy(&hub->lock); - } - - static const struct ssam_device_id ssam_base_hub_match[] = { --- -2.31.1 - -From ed6d9e1308010f74dfe2389815dc01c1d36a3cda Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 23 May 2021 14:35:37 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Update comments for - 15" AMD Surface Laptop 4 - -The 15" AMD version of the Surface Laptop 4 shares its WSID HID with the -15" AMD version of the Surface Laptop 3. Update the comments -accordingly. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index dd1d6504b004..3916df6fb517 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -156,7 +156,7 @@ static const struct software_node *ssam_node_group_sl2[] = { - NULL, - }; - --/* Devices for Surface Laptop 3. */ -+/* Devices for Surface Laptop 3 and 4. */ - static const struct software_node *ssam_node_group_sl3[] = { - &ssam_node_root, - &ssam_node_bat_ac, -@@ -521,7 +521,7 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { - /* Surface Laptop 3 (13", Intel) */ - { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, - -- /* Surface Laptop 3 (15", AMD) */ -+ /* Surface Laptop 3 (15", AMD) and 4 (15", AMD) */ - { "MSHW0110", (unsigned long)ssam_node_group_sl3 }, - - /* Surface Laptop Go 1 */ --- -2.31.1 - -From 8835f72ea04e03e7314e8f03b03232c5fac20cfd Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 23 May 2021 14:36:36 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for 13" - Intel Surface Laptop 4 - -Add support for the 13" Intel version of the Surface Laptop 4. - -Use the existing node group for the Surface Laptop 3 since the 15" AMD -version already shares its WSID HID with its predecessor and there don't -seem to be any significant differences with regards to SAM. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 3916df6fb517..c1c939625fc0 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -524,6 +524,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { - /* Surface Laptop 3 (15", AMD) and 4 (15", AMD) */ - { "MSHW0110", (unsigned long)ssam_node_group_sl3 }, - -+ /* Surface Laptop 4 (13", Intel) */ -+ { "MSHW0250", (unsigned long)ssam_node_group_sl3 }, -+ - /* Surface Laptop Go 1 */ - { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, - --- -2.31.1 - -From 7d53ad4fc1de993bc8389dcc4086d2d141e821fa Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 23 May 2021 14:09:42 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Consolidate node - groups for 5th- and 6th-gen devices - -5th- and 6th-generation Surface devices have all SAM clients defined in -ACPI, except for the platform profile/performance mode which his handled -via the WSID (Windows Surface Integration Device). Thus, the node groups -for those devices are the same and we can just use a single one instead -of re-defining the same one over and over again. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 47 +++++-------------- - 1 file changed, 12 insertions(+), 35 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index c1c939625fc0..e618bdd5cf30 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -119,8 +119,13 @@ static const struct software_node ssam_node_hid_base_iid6 = { - .parent = &ssam_node_hub_base, - }; - --/* Devices for Surface Book 2. */ --static const struct software_node *ssam_node_group_sb2[] = { -+/* -+ * Devices for 5th- and 6th-generations models: -+ * - Surface Book 2, -+ * - Surface Laptop 1 and 2, -+ * - Surface Pro 5 and 6. -+ */ -+static const struct software_node *ssam_node_group_gen5[] = { - &ssam_node_root, - &ssam_node_tmp_pprof, - NULL, -@@ -142,20 +147,6 @@ static const struct software_node *ssam_node_group_sb3[] = { - NULL, - }; - --/* Devices for Surface Laptop 1. */ --static const struct software_node *ssam_node_group_sl1[] = { -- &ssam_node_root, -- &ssam_node_tmp_pprof, -- NULL, --}; -- --/* Devices for Surface Laptop 2. */ --static const struct software_node *ssam_node_group_sl2[] = { -- &ssam_node_root, -- &ssam_node_tmp_pprof, -- NULL, --}; -- - /* Devices for Surface Laptop 3 and 4. */ - static const struct software_node *ssam_node_group_sl3[] = { - &ssam_node_root, -@@ -177,20 +168,6 @@ static const struct software_node *ssam_node_group_slg1[] = { - NULL, - }; - --/* Devices for Surface Pro 5. */ --static const struct software_node *ssam_node_group_sp5[] = { -- &ssam_node_root, -- &ssam_node_tmp_pprof, -- NULL, --}; -- --/* Devices for Surface Pro 6. */ --static const struct software_node *ssam_node_group_sp6[] = { -- &ssam_node_root, -- &ssam_node_tmp_pprof, -- NULL, --}; -- - /* Devices for Surface Pro 7 and Surface Pro 7+. */ - static const struct software_node *ssam_node_group_sp7[] = { - &ssam_node_root, -@@ -495,10 +472,10 @@ static struct ssam_device_driver ssam_base_hub_driver = { - - static const struct acpi_device_id ssam_platform_hub_match[] = { - /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ -- { "MSHW0081", (unsigned long)ssam_node_group_sp5 }, -+ { "MSHW0081", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Pro 6 (OMBR >= 0x10) */ -- { "MSHW0111", (unsigned long)ssam_node_group_sp6 }, -+ { "MSHW0111", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Pro 7 */ - { "MSHW0116", (unsigned long)ssam_node_group_sp7 }, -@@ -507,16 +484,16 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { - { "MSHW0119", (unsigned long)ssam_node_group_sp7 }, - - /* Surface Book 2 */ -- { "MSHW0107", (unsigned long)ssam_node_group_sb2 }, -+ { "MSHW0107", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Book 3 */ - { "MSHW0117", (unsigned long)ssam_node_group_sb3 }, - - /* Surface Laptop 1 */ -- { "MSHW0086", (unsigned long)ssam_node_group_sl1 }, -+ { "MSHW0086", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Laptop 2 */ -- { "MSHW0112", (unsigned long)ssam_node_group_sl2 }, -+ { "MSHW0112", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Laptop 3 (13", Intel) */ - { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, --- -2.31.1 - -From abcc82b9ec6965bbac173b9554b19b1f3ee1cd3d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 9 Feb 2021 02:46:40 +0100 -Subject: [PATCH] platform/surface: Add DTX driver - -The Microsoft Surface Book series devices consist of a so-called -clipboard part (containing the CPU, touchscreen, and primary battery) -and a base part (containing keyboard, secondary battery, and optional -discrete GPU). These parts can be separated, i.e. the clipboard can be -detached and used as tablet. - -This detachment process is initiated by pressing a button. On the -Surface Book 2 and 3 (targeted with this commit), the Surface Aggregator -Module (i.e. the embedded controller on those devices) attempts to send -a notification to any listening client driver and waits for further -instructions (i.e. whether the detachment process should continue or be -aborted). If it does not receive a response in a certain time-frame, the -detachment process (by default) continues and the clipboard can be -physically separated. In other words, (by default and) without a driver, -the detachment process takes about 10 seconds to complete. - -This commit introduces a driver for this detachment system (called DTX). -This driver allows a user-space daemon to control and influence the -detachment behavior. Specifically, it forwards any detachment requests -to user-space, allows user-space to make such requests itself, and -allows handling of those requests. Requests can be handled by either -aborting, continuing/allowing, or delaying (i.e. resetting the timeout -via a heartbeat commend). The user-space API is implemented via the -/dev/surface/dtx miscdevice. - -In addition, user-space can change the default behavior on timeout from -allowing detachment to disallowing it, which is useful if the (optional) -discrete GPU is in use. - -Furthermore, this driver allows user-space to receive notifications -about the state of the base, specifically when it is physically removed -(as opposed to detachment requested), in what manner it is connected -(i.e. in reverse-/tent-/studio- or laptop-mode), and what type of base -is connected. Based on this information, the driver also provides a -simple tablet-mode switch (aliasing all modes without keyboard access, -i.e. tablet-mode and studio-mode to its reported tablet-mode). - -An implementation of such a user-space daemon, allowing configuration of -detachment behavior via scripts (e.g. safely unmounting USB devices -connected to the base before continuing) can be found at [1]. - -[1]: https://github.com/linux-surface/surface-dtx-daemon - -Patchset: surface-sam ---- - .../userspace-api/ioctl/ioctl-number.rst | 2 + - MAINTAINERS | 7 + - drivers/platform/surface/Kconfig | 16 + - drivers/platform/surface/Makefile | 1 + - drivers/platform/surface/surface_dtx.c | 1201 +++++++++++++++++ - include/uapi/linux/surface_aggregator/dtx.h | 146 ++ - 6 files changed, 1373 insertions(+) - create mode 100644 drivers/platform/surface/surface_dtx.c - create mode 100644 include/uapi/linux/surface_aggregator/dtx.h - -diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst -index b5231d7f9200..e1dc72a8b62e 100644 ---- a/Documentation/userspace-api/ioctl/ioctl-number.rst -+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst -@@ -326,6 +326,8 @@ Code Seq# Include File Comments - 0xA4 00-1F uapi/asm/sgx.h - 0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator - -+0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver -+ - 0xAA 00-3F linux/uapi/linux/userfaultfd.h - 0xAB 00-1F linux/nbd.h - 0xAC 00-1F linux/raw.h -diff --git a/MAINTAINERS b/MAINTAINERS -index 1fd2fd35d5b7..1a60e353df38 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11785,6 +11785,13 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch] - F: include/linux/cciss*.h - F: include/uapi/linux/cciss*.h - -+MICROSOFT SURFACE DTX DRIVER -+M: Maximilian Luz -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/platform/surface/surface_dtx.c -+F: include/uapi/linux/surface_aggregator/dtx.h -+ - MICROSOFT SURFACE GPE LID SUPPORT DRIVER - M: Maximilian Luz - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 97e08dd35992..745f9d2eb6a7 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -111,6 +111,22 @@ config SURFACE_BOOK1_DGPU_SWITCH - This driver provides a sysfs switch to set the power-state of the - discrete GPU found on the Microsoft Surface Book 1. - -+config SURFACE_DTX -+ tristate "Surface DTX (Detachment System) Driver" -+ depends on SURFACE_AGGREGATOR -+ depends on INPUT -+ help -+ Driver for the Surface Book clipboard detachment system (DTX). -+ -+ On the Surface Book series devices, the display part containing the -+ CPU (called the clipboard) can be detached from the base (containing a -+ battery, the keyboard, and, optionally, a discrete GPU) by (if -+ necessary) unlocking and opening the latch connecting both parts. -+ -+ This driver provides a user-space interface that can influence the -+ behavior of this process, which includes the option to abort it in -+ case the base is still in use or speed it up in case it is not. -+ - config SURFACE_GPE - tristate "Surface GPE/Lid Support Driver" - depends on DMI -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 30a212aefd35..19b661e274c3 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -12,5 +12,6 @@ obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o -+obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o -diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c -new file mode 100644 -index 000000000000..a95adc1094aa ---- /dev/null -+++ b/drivers/platform/surface/surface_dtx.c -@@ -0,0 +1,1201 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface Book (gen. 2 and later) detachment system (DTX) driver. -+ * -+ * Provides a user-space interface to properly handle clipboard/tablet -+ * (containing screen and processor) detachment from the base of the device -+ * (containing the keyboard and optionally a discrete GPU). Allows to -+ * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in -+ * use), or request detachment via user-space. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+ -+/* -- SSAM interface. ------------------------------------------------------- */ -+ -+enum sam_event_cid_bas { -+ SAM_EVENT_CID_DTX_CONNECTION = 0x0c, -+ SAM_EVENT_CID_DTX_REQUEST = 0x0e, -+ SAM_EVENT_CID_DTX_CANCEL = 0x0f, -+ SAM_EVENT_CID_DTX_LATCH_STATUS = 0x11, -+}; -+ -+enum ssam_bas_base_state { -+ SSAM_BAS_BASE_STATE_DETACH_SUCCESS = 0x00, -+ SSAM_BAS_BASE_STATE_ATTACHED = 0x01, -+ SSAM_BAS_BASE_STATE_NOT_FEASIBLE = 0x02, -+}; -+ -+enum ssam_bas_latch_status { -+ SSAM_BAS_LATCH_STATUS_CLOSED = 0x00, -+ SSAM_BAS_LATCH_STATUS_OPENED = 0x01, -+ SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN = 0x02, -+ SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN = 0x03, -+ SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE = 0x04, -+}; -+ -+enum ssam_bas_cancel_reason { -+ SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE = 0x00, /* Low battery. */ -+ SSAM_BAS_CANCEL_REASON_TIMEOUT = 0x02, -+ SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN = 0x03, -+ SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN = 0x04, -+ SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE = 0x05, -+}; -+ -+struct ssam_bas_base_info { -+ u8 state; -+ u8 base_id; -+} __packed; -+ -+static_assert(sizeof(struct ssam_bas_base_info) == 2); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x06, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x07, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x08, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x09, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0a, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0b, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0c, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0d, -+ .instance_id = 0x00, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x11, -+ .instance_id = 0x00, -+}); -+ -+ -+/* -- Main structures. ------------------------------------------------------ */ -+ -+enum sdtx_device_state { -+ SDTX_DEVICE_SHUTDOWN_BIT = BIT(0), -+ SDTX_DEVICE_DIRTY_BASE_BIT = BIT(1), -+ SDTX_DEVICE_DIRTY_MODE_BIT = BIT(2), -+ SDTX_DEVICE_DIRTY_LATCH_BIT = BIT(3), -+}; -+ -+struct sdtx_device { -+ struct kref kref; -+ struct rw_semaphore lock; /* Guards device and controller reference. */ -+ -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ unsigned long flags; -+ -+ struct miscdevice mdev; -+ wait_queue_head_t waitq; -+ struct mutex write_lock; /* Guards order of events/notifications. */ -+ struct rw_semaphore client_lock; /* Guards client list. */ -+ struct list_head client_list; -+ -+ struct delayed_work state_work; -+ struct { -+ struct ssam_bas_base_info base; -+ u8 device_mode; -+ u8 latch_status; -+ } state; -+ -+ struct delayed_work mode_work; -+ struct input_dev *mode_switch; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+enum sdtx_client_state { -+ SDTX_CLIENT_EVENTS_ENABLED_BIT = BIT(0), -+}; -+ -+struct sdtx_client { -+ struct sdtx_device *ddev; -+ struct list_head node; -+ unsigned long flags; -+ -+ struct fasync_struct *fasync; -+ -+ struct mutex read_lock; /* Guards FIFO buffer read access. */ -+ DECLARE_KFIFO(buffer, u8, 512); -+}; -+ -+static void __sdtx_device_release(struct kref *kref) -+{ -+ struct sdtx_device *ddev = container_of(kref, struct sdtx_device, kref); -+ -+ mutex_destroy(&ddev->write_lock); -+ kfree(ddev); -+} -+ -+static struct sdtx_device *sdtx_device_get(struct sdtx_device *ddev) -+{ -+ if (ddev) -+ kref_get(&ddev->kref); -+ -+ return ddev; -+} -+ -+static void sdtx_device_put(struct sdtx_device *ddev) -+{ -+ if (ddev) -+ kref_put(&ddev->kref, __sdtx_device_release); -+} -+ -+ -+/* -- Firmware value translations. ------------------------------------------ */ -+ -+static u16 sdtx_translate_base_state(struct sdtx_device *ddev, u8 state) -+{ -+ switch (state) { -+ case SSAM_BAS_BASE_STATE_ATTACHED: -+ return SDTX_BASE_ATTACHED; -+ -+ case SSAM_BAS_BASE_STATE_DETACH_SUCCESS: -+ return SDTX_BASE_DETACHED; -+ -+ case SSAM_BAS_BASE_STATE_NOT_FEASIBLE: -+ return SDTX_DETACH_NOT_FEASIBLE; -+ -+ default: -+ dev_err(ddev->dev, "unknown base state: %#04x\n", state); -+ return SDTX_UNKNOWN(state); -+ } -+} -+ -+static u16 sdtx_translate_latch_status(struct sdtx_device *ddev, u8 status) -+{ -+ switch (status) { -+ case SSAM_BAS_LATCH_STATUS_CLOSED: -+ return SDTX_LATCH_CLOSED; -+ -+ case SSAM_BAS_LATCH_STATUS_OPENED: -+ return SDTX_LATCH_OPENED; -+ -+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN: -+ return SDTX_ERR_FAILED_TO_OPEN; -+ -+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN: -+ return SDTX_ERR_FAILED_TO_REMAIN_OPEN; -+ -+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE: -+ return SDTX_ERR_FAILED_TO_CLOSE; -+ -+ default: -+ dev_err(ddev->dev, "unknown latch status: %#04x\n", status); -+ return SDTX_UNKNOWN(status); -+ } -+} -+ -+static u16 sdtx_translate_cancel_reason(struct sdtx_device *ddev, u8 reason) -+{ -+ switch (reason) { -+ case SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE: -+ return SDTX_DETACH_NOT_FEASIBLE; -+ -+ case SSAM_BAS_CANCEL_REASON_TIMEOUT: -+ return SDTX_DETACH_TIMEDOUT; -+ -+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN: -+ return SDTX_ERR_FAILED_TO_OPEN; -+ -+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN: -+ return SDTX_ERR_FAILED_TO_REMAIN_OPEN; -+ -+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE: -+ return SDTX_ERR_FAILED_TO_CLOSE; -+ -+ default: -+ dev_err(ddev->dev, "unknown cancel reason: %#04x\n", reason); -+ return SDTX_UNKNOWN(reason); -+ } -+} -+ -+ -+/* -- IOCTLs. --------------------------------------------------------------- */ -+ -+static int sdtx_ioctl_get_base_info(struct sdtx_device *ddev, -+ struct sdtx_base_info __user *buf) -+{ -+ struct ssam_bas_base_info raw; -+ struct sdtx_base_info info; -+ int status; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &raw); -+ if (status < 0) -+ return status; -+ -+ info.state = sdtx_translate_base_state(ddev, raw.state); -+ info.base_id = SDTX_BASE_TYPE_SSH(raw.base_id); -+ -+ if (copy_to_user(buf, &info, sizeof(info))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static int sdtx_ioctl_get_device_mode(struct sdtx_device *ddev, u16 __user *buf) -+{ -+ u8 mode; -+ int status; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); -+ if (status < 0) -+ return status; -+ -+ return put_user(mode, buf); -+} -+ -+static int sdtx_ioctl_get_latch_status(struct sdtx_device *ddev, u16 __user *buf) -+{ -+ u8 latch; -+ int status; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch); -+ if (status < 0) -+ return status; -+ -+ return put_user(sdtx_translate_latch_status(ddev, latch), buf); -+} -+ -+static long __surface_dtx_ioctl(struct sdtx_client *client, unsigned int cmd, unsigned long arg) -+{ -+ struct sdtx_device *ddev = client->ddev; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ switch (cmd) { -+ case SDTX_IOCTL_EVENTS_ENABLE: -+ set_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags); -+ return 0; -+ -+ case SDTX_IOCTL_EVENTS_DISABLE: -+ clear_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags); -+ return 0; -+ -+ case SDTX_IOCTL_LATCH_LOCK: -+ return ssam_retry(ssam_bas_latch_lock, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_UNLOCK: -+ return ssam_retry(ssam_bas_latch_unlock, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_REQUEST: -+ return ssam_retry(ssam_bas_latch_request, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_CONFIRM: -+ return ssam_retry(ssam_bas_latch_confirm, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_HEARTBEAT: -+ return ssam_retry(ssam_bas_latch_heartbeat, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_CANCEL: -+ return ssam_retry(ssam_bas_latch_cancel, ddev->ctrl); -+ -+ case SDTX_IOCTL_GET_BASE_INFO: -+ return sdtx_ioctl_get_base_info(ddev, (struct sdtx_base_info __user *)arg); -+ -+ case SDTX_IOCTL_GET_DEVICE_MODE: -+ return sdtx_ioctl_get_device_mode(ddev, (u16 __user *)arg); -+ -+ case SDTX_IOCTL_GET_LATCH_STATUS: -+ return sdtx_ioctl_get_latch_status(ddev, (u16 __user *)arg); -+ -+ default: -+ return -EINVAL; -+ } -+} -+ -+static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -+{ -+ struct sdtx_client *client = file->private_data; -+ long status; -+ -+ if (down_read_killable(&client->ddev->lock)) -+ return -ERESTARTSYS; -+ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) { -+ up_read(&client->ddev->lock); -+ return -ENODEV; -+ } -+ -+ status = __surface_dtx_ioctl(client, cmd, arg); -+ -+ up_read(&client->ddev->lock); -+ return status; -+} -+ -+ -+/* -- File operations. ------------------------------------------------------ */ -+ -+static int surface_dtx_open(struct inode *inode, struct file *file) -+{ -+ struct sdtx_device *ddev = container_of(file->private_data, struct sdtx_device, mdev); -+ struct sdtx_client *client; -+ -+ /* Initialize client. */ -+ client = kzalloc(sizeof(*client), GFP_KERNEL); -+ if (!client) -+ return -ENOMEM; -+ -+ client->ddev = sdtx_device_get(ddev); -+ -+ INIT_LIST_HEAD(&client->node); -+ -+ mutex_init(&client->read_lock); -+ INIT_KFIFO(client->buffer); -+ -+ file->private_data = client; -+ -+ /* Attach client. */ -+ down_write(&ddev->client_lock); -+ -+ /* -+ * Do not add a new client if the device has been shut down. Note that -+ * it's enough to hold the client_lock here as, during shutdown, we -+ * only acquire that lock and remove clients after marking the device -+ * as shut down. -+ */ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { -+ up_write(&ddev->client_lock); -+ sdtx_device_put(client->ddev); -+ kfree(client); -+ return -ENODEV; -+ } -+ -+ list_add_tail(&client->node, &ddev->client_list); -+ up_write(&ddev->client_lock); -+ -+ stream_open(inode, file); -+ return 0; -+} -+ -+static int surface_dtx_release(struct inode *inode, struct file *file) -+{ -+ struct sdtx_client *client = file->private_data; -+ -+ /* Detach client. */ -+ down_write(&client->ddev->client_lock); -+ list_del(&client->node); -+ up_write(&client->ddev->client_lock); -+ -+ /* Free client. */ -+ sdtx_device_put(client->ddev); -+ mutex_destroy(&client->read_lock); -+ kfree(client); -+ -+ return 0; -+} -+ -+static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs) -+{ -+ struct sdtx_client *client = file->private_data; -+ struct sdtx_device *ddev = client->ddev; -+ unsigned int copied; -+ int status = 0; -+ -+ if (down_read_killable(&ddev->lock)) -+ return -ERESTARTSYS; -+ -+ /* Make sure we're not shut down. */ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { -+ up_read(&ddev->lock); -+ return -ENODEV; -+ } -+ -+ do { -+ /* Check availability, wait if necessary. */ -+ if (kfifo_is_empty(&client->buffer)) { -+ up_read(&ddev->lock); -+ -+ if (file->f_flags & O_NONBLOCK) -+ return -EAGAIN; -+ -+ status = wait_event_interruptible(ddev->waitq, -+ !kfifo_is_empty(&client->buffer) || -+ test_bit(SDTX_DEVICE_SHUTDOWN_BIT, -+ &ddev->flags)); -+ if (status < 0) -+ return status; -+ -+ if (down_read_killable(&client->ddev->lock)) -+ return -ERESTARTSYS; -+ -+ /* Need to check that we're not shut down again. */ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { -+ up_read(&ddev->lock); -+ return -ENODEV; -+ } -+ } -+ -+ /* Try to read from FIFO. */ -+ if (mutex_lock_interruptible(&client->read_lock)) { -+ up_read(&ddev->lock); -+ return -ERESTARTSYS; -+ } -+ -+ status = kfifo_to_user(&client->buffer, buf, count, &copied); -+ mutex_unlock(&client->read_lock); -+ -+ if (status < 0) { -+ up_read(&ddev->lock); -+ return status; -+ } -+ -+ /* We might not have gotten anything, check this here. */ -+ if (copied == 0 && (file->f_flags & O_NONBLOCK)) { -+ up_read(&ddev->lock); -+ return -EAGAIN; -+ } -+ } while (copied == 0); -+ -+ up_read(&ddev->lock); -+ return copied; -+} -+ -+static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt) -+{ -+ struct sdtx_client *client = file->private_data; -+ __poll_t events = 0; -+ -+ if (down_read_killable(&client->ddev->lock)) -+ return -ERESTARTSYS; -+ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) { -+ up_read(&client->ddev->lock); -+ return EPOLLHUP | EPOLLERR; -+ } -+ -+ poll_wait(file, &client->ddev->waitq, pt); -+ -+ if (!kfifo_is_empty(&client->buffer)) -+ events |= EPOLLIN | EPOLLRDNORM; -+ -+ up_read(&client->ddev->lock); -+ return events; -+} -+ -+static int surface_dtx_fasync(int fd, struct file *file, int on) -+{ -+ struct sdtx_client *client = file->private_data; -+ -+ return fasync_helper(fd, file, on, &client->fasync); -+} -+ -+static const struct file_operations surface_dtx_fops = { -+ .owner = THIS_MODULE, -+ .open = surface_dtx_open, -+ .release = surface_dtx_release, -+ .read = surface_dtx_read, -+ .poll = surface_dtx_poll, -+ .fasync = surface_dtx_fasync, -+ .unlocked_ioctl = surface_dtx_ioctl, -+ .compat_ioctl = surface_dtx_ioctl, -+ .llseek = no_llseek, -+}; -+ -+ -+/* -- Event handling/forwarding. -------------------------------------------- */ -+ -+/* -+ * The device operation mode is not immediately updated on the EC when the -+ * base has been connected, i.e. querying the device mode inside the -+ * connection event callback yields an outdated value. Thus, we can only -+ * determine the new tablet-mode switch and device mode values after some -+ * time. -+ * -+ * These delays have been chosen by experimenting. We first delay on connect -+ * events, then check and validate the device mode against the base state and -+ * if invalid delay again by the "recheck" delay. -+ */ -+#define SDTX_DEVICE_MODE_DELAY_CONNECT msecs_to_jiffies(100) -+#define SDTX_DEVICE_MODE_DELAY_RECHECK msecs_to_jiffies(100) -+ -+struct sdtx_status_event { -+ struct sdtx_event e; -+ __u16 v; -+} __packed; -+ -+struct sdtx_base_info_event { -+ struct sdtx_event e; -+ struct sdtx_base_info v; -+} __packed; -+ -+union sdtx_generic_event { -+ struct sdtx_event common; -+ struct sdtx_status_event status; -+ struct sdtx_base_info_event base; -+}; -+ -+static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay); -+ -+/* Must be executed with ddev->write_lock held. */ -+static void sdtx_push_event(struct sdtx_device *ddev, struct sdtx_event *evt) -+{ -+ const size_t len = sizeof(struct sdtx_event) + evt->length; -+ struct sdtx_client *client; -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ down_read(&ddev->client_lock); -+ list_for_each_entry(client, &ddev->client_list, node) { -+ if (!test_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags)) -+ continue; -+ -+ if (likely(kfifo_avail(&client->buffer) >= len)) -+ kfifo_in(&client->buffer, (const u8 *)evt, len); -+ else -+ dev_warn(ddev->dev, "event buffer overrun\n"); -+ -+ kill_fasync(&client->fasync, SIGIO, POLL_IN); -+ } -+ up_read(&ddev->client_lock); -+ -+ wake_up_interruptible(&ddev->waitq); -+} -+ -+static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in) -+{ -+ struct sdtx_device *ddev = container_of(nf, struct sdtx_device, notif); -+ union sdtx_generic_event event; -+ size_t len; -+ -+ /* Validate event payload length. */ -+ switch (in->command_id) { -+ case SAM_EVENT_CID_DTX_CONNECTION: -+ len = 2 * sizeof(u8); -+ break; -+ -+ case SAM_EVENT_CID_DTX_REQUEST: -+ len = 0; -+ break; -+ -+ case SAM_EVENT_CID_DTX_CANCEL: -+ len = sizeof(u8); -+ break; -+ -+ case SAM_EVENT_CID_DTX_LATCH_STATUS: -+ len = sizeof(u8); -+ break; -+ -+ default: -+ return 0; -+ }; -+ -+ if (in->length != len) { -+ dev_err(ddev->dev, -+ "unexpected payload size for event %#04x: got %u, expected %zu\n", -+ in->command_id, in->length, len); -+ return 0; -+ } -+ -+ mutex_lock(&ddev->write_lock); -+ -+ /* Translate event. */ -+ switch (in->command_id) { -+ case SAM_EVENT_CID_DTX_CONNECTION: -+ clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags); -+ -+ /* If state has not changed: do not send new event. */ -+ if (ddev->state.base.state == in->data[0] && -+ ddev->state.base.base_id == in->data[1]) -+ goto out; -+ -+ ddev->state.base.state = in->data[0]; -+ ddev->state.base.base_id = in->data[1]; -+ -+ event.base.e.length = sizeof(struct sdtx_base_info); -+ event.base.e.code = SDTX_EVENT_BASE_CONNECTION; -+ event.base.v.state = sdtx_translate_base_state(ddev, in->data[0]); -+ event.base.v.base_id = SDTX_BASE_TYPE_SSH(in->data[1]); -+ break; -+ -+ case SAM_EVENT_CID_DTX_REQUEST: -+ event.common.code = SDTX_EVENT_REQUEST; -+ event.common.length = 0; -+ break; -+ -+ case SAM_EVENT_CID_DTX_CANCEL: -+ event.status.e.length = sizeof(u16); -+ event.status.e.code = SDTX_EVENT_CANCEL; -+ event.status.v = sdtx_translate_cancel_reason(ddev, in->data[0]); -+ break; -+ -+ case SAM_EVENT_CID_DTX_LATCH_STATUS: -+ clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags); -+ -+ /* If state has not changed: do not send new event. */ -+ if (ddev->state.latch_status == in->data[0]) -+ goto out; -+ -+ ddev->state.latch_status = in->data[0]; -+ -+ event.status.e.length = sizeof(u16); -+ event.status.e.code = SDTX_EVENT_LATCH_STATUS; -+ event.status.v = sdtx_translate_latch_status(ddev, in->data[0]); -+ break; -+ } -+ -+ sdtx_push_event(ddev, &event.common); -+ -+ /* Update device mode on base connection change. */ -+ if (in->command_id == SAM_EVENT_CID_DTX_CONNECTION) { -+ unsigned long delay; -+ -+ delay = in->data[0] ? SDTX_DEVICE_MODE_DELAY_CONNECT : 0; -+ sdtx_update_device_mode(ddev, delay); -+ } -+ -+out: -+ mutex_unlock(&ddev->write_lock); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+ -+/* -- State update functions. ----------------------------------------------- */ -+ -+static bool sdtx_device_mode_invalid(u8 mode, u8 base_state) -+{ -+ return ((base_state == SSAM_BAS_BASE_STATE_ATTACHED) && -+ (mode == SDTX_DEVICE_MODE_TABLET)) || -+ ((base_state == SSAM_BAS_BASE_STATE_DETACH_SUCCESS) && -+ (mode != SDTX_DEVICE_MODE_TABLET)); -+} -+ -+static void sdtx_device_mode_workfn(struct work_struct *work) -+{ -+ struct sdtx_device *ddev = container_of(work, struct sdtx_device, mode_work.work); -+ struct sdtx_status_event event; -+ struct ssam_bas_base_info base; -+ int status, tablet; -+ u8 mode; -+ -+ /* Get operation mode. */ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); -+ if (status) { -+ dev_err(ddev->dev, "failed to get device mode: %d\n", status); -+ return; -+ } -+ -+ /* Get base info. */ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base); -+ if (status) { -+ dev_err(ddev->dev, "failed to get base info: %d\n", status); -+ return; -+ } -+ -+ /* -+ * In some cases (specifically when attaching the base), the device -+ * mode isn't updated right away. Thus we check if the device mode -+ * makes sense for the given base state and try again later if it -+ * doesn't. -+ */ -+ if (sdtx_device_mode_invalid(mode, base.state)) { -+ dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); -+ sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); -+ return; -+ } -+ -+ mutex_lock(&ddev->write_lock); -+ clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags); -+ -+ /* Avoid sending duplicate device-mode events. */ -+ if (ddev->state.device_mode == mode) { -+ mutex_unlock(&ddev->write_lock); -+ return; -+ } -+ -+ ddev->state.device_mode = mode; -+ -+ event.e.length = sizeof(u16); -+ event.e.code = SDTX_EVENT_DEVICE_MODE; -+ event.v = mode; -+ -+ sdtx_push_event(ddev, &event.e); -+ -+ /* Send SW_TABLET_MODE event. */ -+ tablet = mode != SDTX_DEVICE_MODE_LAPTOP; -+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); -+ input_sync(ddev->mode_switch); -+ -+ mutex_unlock(&ddev->write_lock); -+} -+ -+static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay) -+{ -+ schedule_delayed_work(&ddev->mode_work, delay); -+} -+ -+/* Must be executed with ddev->write_lock held. */ -+static void __sdtx_device_state_update_base(struct sdtx_device *ddev, -+ struct ssam_bas_base_info info) -+{ -+ struct sdtx_base_info_event event; -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ /* Prevent duplicate events. */ -+ if (ddev->state.base.state == info.state && -+ ddev->state.base.base_id == info.base_id) -+ return; -+ -+ ddev->state.base = info; -+ -+ event.e.length = sizeof(struct sdtx_base_info); -+ event.e.code = SDTX_EVENT_BASE_CONNECTION; -+ event.v.state = sdtx_translate_base_state(ddev, info.state); -+ event.v.base_id = SDTX_BASE_TYPE_SSH(info.base_id); -+ -+ sdtx_push_event(ddev, &event.e); -+} -+ -+/* Must be executed with ddev->write_lock held. */ -+static void __sdtx_device_state_update_mode(struct sdtx_device *ddev, u8 mode) -+{ -+ struct sdtx_status_event event; -+ int tablet; -+ -+ /* -+ * Note: This function must be called after updating the base state -+ * via __sdtx_device_state_update_base(), as we rely on the updated -+ * base state value in the validity check below. -+ */ -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ if (sdtx_device_mode_invalid(mode, ddev->state.base.state)) { -+ dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); -+ sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); -+ return; -+ } -+ -+ /* Prevent duplicate events. */ -+ if (ddev->state.device_mode == mode) -+ return; -+ -+ ddev->state.device_mode = mode; -+ -+ /* Send event. */ -+ event.e.length = sizeof(u16); -+ event.e.code = SDTX_EVENT_DEVICE_MODE; -+ event.v = mode; -+ -+ sdtx_push_event(ddev, &event.e); -+ -+ /* Send SW_TABLET_MODE event. */ -+ tablet = mode != SDTX_DEVICE_MODE_LAPTOP; -+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); -+ input_sync(ddev->mode_switch); -+} -+ -+/* Must be executed with ddev->write_lock held. */ -+static void __sdtx_device_state_update_latch(struct sdtx_device *ddev, u8 status) -+{ -+ struct sdtx_status_event event; -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ /* Prevent duplicate events. */ -+ if (ddev->state.latch_status == status) -+ return; -+ -+ ddev->state.latch_status = status; -+ -+ event.e.length = sizeof(struct sdtx_base_info); -+ event.e.code = SDTX_EVENT_BASE_CONNECTION; -+ event.v = sdtx_translate_latch_status(ddev, status); -+ -+ sdtx_push_event(ddev, &event.e); -+} -+ -+static void sdtx_device_state_workfn(struct work_struct *work) -+{ -+ struct sdtx_device *ddev = container_of(work, struct sdtx_device, state_work.work); -+ struct ssam_bas_base_info base; -+ u8 mode, latch; -+ int status; -+ -+ /* Mark everything as dirty. */ -+ set_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags); -+ set_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags); -+ set_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags); -+ -+ /* -+ * Ensure that the state gets marked as dirty before continuing to -+ * query it. Necessary to ensure that clear_bit() calls in -+ * sdtx_notifier() and sdtx_device_mode_workfn() actually clear these -+ * bits if an event is received while updating the state here. -+ */ -+ smp_mb__after_atomic(); -+ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base); -+ if (status) { -+ dev_err(ddev->dev, "failed to get base state: %d\n", status); -+ return; -+ } -+ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); -+ if (status) { -+ dev_err(ddev->dev, "failed to get device mode: %d\n", status); -+ return; -+ } -+ -+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch); -+ if (status) { -+ dev_err(ddev->dev, "failed to get latch status: %d\n", status); -+ return; -+ } -+ -+ mutex_lock(&ddev->write_lock); -+ -+ /* -+ * If the respective dirty-bit has been cleared, an event has been -+ * received, updating this state. The queried state may thus be out of -+ * date. At this point, we can safely assume that the state provided -+ * by the event is either up to date, or we're about to receive -+ * another event updating it. -+ */ -+ -+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags)) -+ __sdtx_device_state_update_base(ddev, base); -+ -+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags)) -+ __sdtx_device_state_update_mode(ddev, mode); -+ -+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags)) -+ __sdtx_device_state_update_latch(ddev, latch); -+ -+ mutex_unlock(&ddev->write_lock); -+} -+ -+static void sdtx_update_device_state(struct sdtx_device *ddev, unsigned long delay) -+{ -+ schedule_delayed_work(&ddev->state_work, delay); -+} -+ -+ -+/* -- Common device initialization. ----------------------------------------- */ -+ -+static int sdtx_device_init(struct sdtx_device *ddev, struct device *dev, -+ struct ssam_controller *ctrl) -+{ -+ int status, tablet_mode; -+ -+ /* Basic initialization. */ -+ kref_init(&ddev->kref); -+ init_rwsem(&ddev->lock); -+ ddev->dev = dev; -+ ddev->ctrl = ctrl; -+ -+ ddev->mdev.minor = MISC_DYNAMIC_MINOR; -+ ddev->mdev.name = "surface_dtx"; -+ ddev->mdev.nodename = "surface/dtx"; -+ ddev->mdev.fops = &surface_dtx_fops; -+ -+ ddev->notif.base.priority = 1; -+ ddev->notif.base.fn = sdtx_notifier; -+ ddev->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ ddev->notif.event.id.target_category = SSAM_SSH_TC_BAS; -+ ddev->notif.event.id.instance = 0; -+ ddev->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ ddev->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ init_waitqueue_head(&ddev->waitq); -+ mutex_init(&ddev->write_lock); -+ init_rwsem(&ddev->client_lock); -+ INIT_LIST_HEAD(&ddev->client_list); -+ -+ INIT_DELAYED_WORK(&ddev->mode_work, sdtx_device_mode_workfn); -+ INIT_DELAYED_WORK(&ddev->state_work, sdtx_device_state_workfn); -+ -+ /* -+ * Get current device state. We want to guarantee that events are only -+ * sent when state actually changes. Thus we cannot use special -+ * "uninitialized" values, as that would cause problems when manually -+ * querying the state in surface_dtx_pm_complete(). I.e. we would not -+ * be able to detect state changes there if no change event has been -+ * received between driver initialization and first device suspension. -+ * -+ * Note that we also need to do this before registering the event -+ * notifier, as that may access the state values. -+ */ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &ddev->state.base); -+ if (status) -+ return status; -+ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &ddev->state.device_mode); -+ if (status) -+ return status; -+ -+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &ddev->state.latch_status); -+ if (status) -+ return status; -+ -+ /* Set up tablet mode switch. */ -+ ddev->mode_switch = input_allocate_device(); -+ if (!ddev->mode_switch) -+ return -ENOMEM; -+ -+ ddev->mode_switch->name = "Microsoft Surface DTX Device Mode Switch"; -+ ddev->mode_switch->phys = "ssam/01:11:01:00:00/input0"; -+ ddev->mode_switch->id.bustype = BUS_HOST; -+ ddev->mode_switch->dev.parent = ddev->dev; -+ -+ tablet_mode = (ddev->state.device_mode != SDTX_DEVICE_MODE_LAPTOP); -+ input_set_capability(ddev->mode_switch, EV_SW, SW_TABLET_MODE); -+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet_mode); -+ -+ status = input_register_device(ddev->mode_switch); -+ if (status) { -+ input_free_device(ddev->mode_switch); -+ return status; -+ } -+ -+ /* Set up event notifier. */ -+ status = ssam_notifier_register(ddev->ctrl, &ddev->notif); -+ if (status) -+ goto err_notif; -+ -+ /* Register miscdevice. */ -+ status = misc_register(&ddev->mdev); -+ if (status) -+ goto err_mdev; -+ -+ /* -+ * Update device state in case it has changed between getting the -+ * initial mode and registering the event notifier. -+ */ -+ sdtx_update_device_state(ddev, 0); -+ return 0; -+ -+err_notif: -+ ssam_notifier_unregister(ddev->ctrl, &ddev->notif); -+ cancel_delayed_work_sync(&ddev->mode_work); -+err_mdev: -+ input_unregister_device(ddev->mode_switch); -+ return status; -+} -+ -+static struct sdtx_device *sdtx_device_create(struct device *dev, struct ssam_controller *ctrl) -+{ -+ struct sdtx_device *ddev; -+ int status; -+ -+ ddev = kzalloc(sizeof(*ddev), GFP_KERNEL); -+ if (!ddev) -+ return ERR_PTR(-ENOMEM); -+ -+ status = sdtx_device_init(ddev, dev, ctrl); -+ if (status) { -+ sdtx_device_put(ddev); -+ return ERR_PTR(status); -+ } -+ -+ return ddev; -+} -+ -+static void sdtx_device_destroy(struct sdtx_device *ddev) -+{ -+ struct sdtx_client *client; -+ -+ /* -+ * Mark device as shut-down. Prevent new clients from being added and -+ * new operations from being executed. -+ */ -+ set_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags); -+ -+ /* Disable notifiers, prevent new events from arriving. */ -+ ssam_notifier_unregister(ddev->ctrl, &ddev->notif); -+ -+ /* Stop mode_work, prevent access to mode_switch. */ -+ cancel_delayed_work_sync(&ddev->mode_work); -+ -+ /* Stop state_work. */ -+ cancel_delayed_work_sync(&ddev->state_work); -+ -+ /* With mode_work canceled, we can unregister the mode_switch. */ -+ input_unregister_device(ddev->mode_switch); -+ -+ /* Wake up async clients. */ -+ down_write(&ddev->client_lock); -+ list_for_each_entry(client, &ddev->client_list, node) { -+ kill_fasync(&client->fasync, SIGIO, POLL_HUP); -+ } -+ up_write(&ddev->client_lock); -+ -+ /* Wake up blocking clients. */ -+ wake_up_interruptible(&ddev->waitq); -+ -+ /* -+ * Wait for clients to finish their current operation. After this, the -+ * controller and device references are guaranteed to be no longer in -+ * use. -+ */ -+ down_write(&ddev->lock); -+ ddev->dev = NULL; -+ ddev->ctrl = NULL; -+ up_write(&ddev->lock); -+ -+ /* Finally remove the misc-device. */ -+ misc_deregister(&ddev->mdev); -+ -+ /* -+ * We're now guaranteed that sdtx_device_open() won't be called any -+ * more, so we can now drop out reference. -+ */ -+ sdtx_device_put(ddev); -+} -+ -+ -+/* -- PM ops. --------------------------------------------------------------- */ -+ -+#ifdef CONFIG_PM_SLEEP -+ -+static void surface_dtx_pm_complete(struct device *dev) -+{ -+ struct sdtx_device *ddev = dev_get_drvdata(dev); -+ -+ /* -+ * Normally, the EC will store events while suspended (i.e. in -+ * display-off state) and release them when resumed (i.e. transitioned -+ * to display-on state). During hibernation, however, the EC will be -+ * shut down and does not store events. Furthermore, events might be -+ * dropped during prolonged suspension (it is currently unknown how -+ * big this event buffer is and how it behaves on overruns). -+ * -+ * To prevent any problems, we update the device state here. We do -+ * this delayed to ensure that any events sent by the EC directly -+ * after resuming will be handled first. The delay below has been -+ * chosen (experimentally), so that there should be ample time for -+ * these events to be handled, before we check and, if necessary, -+ * update the state. -+ */ -+ sdtx_update_device_state(ddev, msecs_to_jiffies(1000)); -+} -+ -+static const struct dev_pm_ops surface_dtx_pm_ops = { -+ .complete = surface_dtx_pm_complete, -+}; -+ -+#else /* CONFIG_PM_SLEEP */ -+ -+static const struct dev_pm_ops surface_dtx_pm_ops = {}; -+ -+#endif /* CONFIG_PM_SLEEP */ -+ -+ -+/* -- Platform driver. ------------------------------------------------------ */ -+ -+static int surface_dtx_platform_probe(struct platform_device *pdev) -+{ -+ struct ssam_controller *ctrl; -+ struct sdtx_device *ddev; -+ -+ /* Link to EC. */ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ ddev = sdtx_device_create(&pdev->dev, ctrl); -+ if (IS_ERR(ddev)) -+ return PTR_ERR(ddev); -+ -+ platform_set_drvdata(pdev, ddev); -+ return 0; -+} -+ -+static int surface_dtx_platform_remove(struct platform_device *pdev) -+{ -+ sdtx_device_destroy(platform_get_drvdata(pdev)); -+ return 0; -+} -+ -+static const struct acpi_device_id surface_dtx_acpi_match[] = { -+ { "MSHW0133", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_dtx_acpi_match); -+ -+static struct platform_driver surface_dtx_platform_driver = { -+ .probe = surface_dtx_platform_probe, -+ .remove = surface_dtx_platform_remove, -+ .driver = { -+ .name = "surface_dtx_pltf", -+ .acpi_match_table = surface_dtx_acpi_match, -+ .pm = &surface_dtx_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_dtx_platform_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/include/uapi/linux/surface_aggregator/dtx.h b/include/uapi/linux/surface_aggregator/dtx.h -new file mode 100644 -index 000000000000..0833aab0d819 ---- /dev/null -+++ b/include/uapi/linux/surface_aggregator/dtx.h -@@ -0,0 +1,146 @@ -+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ -+/* -+ * Surface DTX (clipboard detachment system driver) user-space interface. -+ * -+ * Definitions, structs, and IOCTLs for the /dev/surface/dtx misc device. This -+ * device allows user-space to control the clipboard detachment process on -+ * Surface Book series devices. -+ * -+ * Copyright (C) 2020-2021 Maximilian Luz -+ */ -+ -+#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H -+#define _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H -+ -+#include -+#include -+ -+/* Status/error categories */ -+#define SDTX_CATEGORY_STATUS 0x0000 -+#define SDTX_CATEGORY_RUNTIME_ERROR 0x1000 -+#define SDTX_CATEGORY_HARDWARE_ERROR 0x2000 -+#define SDTX_CATEGORY_UNKNOWN 0xf000 -+ -+#define SDTX_CATEGORY_MASK 0xf000 -+#define SDTX_CATEGORY(value) ((value) & SDTX_CATEGORY_MASK) -+ -+#define SDTX_STATUS(code) ((code) | SDTX_CATEGORY_STATUS) -+#define SDTX_ERR_RT(code) ((code) | SDTX_CATEGORY_RUNTIME_ERROR) -+#define SDTX_ERR_HW(code) ((code) | SDTX_CATEGORY_HARDWARE_ERROR) -+#define SDTX_UNKNOWN(code) ((code) | SDTX_CATEGORY_UNKNOWN) -+ -+#define SDTX_SUCCESS(value) (SDTX_CATEGORY(value) == SDTX_CATEGORY_STATUS) -+ -+/* Latch status values */ -+#define SDTX_LATCH_CLOSED SDTX_STATUS(0x00) -+#define SDTX_LATCH_OPENED SDTX_STATUS(0x01) -+ -+/* Base state values */ -+#define SDTX_BASE_DETACHED SDTX_STATUS(0x00) -+#define SDTX_BASE_ATTACHED SDTX_STATUS(0x01) -+ -+/* Runtime errors (non-critical) */ -+#define SDTX_DETACH_NOT_FEASIBLE SDTX_ERR_RT(0x01) -+#define SDTX_DETACH_TIMEDOUT SDTX_ERR_RT(0x02) -+ -+/* Hardware errors (critical) */ -+#define SDTX_ERR_FAILED_TO_OPEN SDTX_ERR_HW(0x01) -+#define SDTX_ERR_FAILED_TO_REMAIN_OPEN SDTX_ERR_HW(0x02) -+#define SDTX_ERR_FAILED_TO_CLOSE SDTX_ERR_HW(0x03) -+ -+/* Base types */ -+#define SDTX_DEVICE_TYPE_HID 0x0100 -+#define SDTX_DEVICE_TYPE_SSH 0x0200 -+ -+#define SDTX_DEVICE_TYPE_MASK 0x0f00 -+#define SDTX_DEVICE_TYPE(value) ((value) & SDTX_DEVICE_TYPE_MASK) -+ -+#define SDTX_BASE_TYPE_HID(id) ((id) | SDTX_DEVICE_TYPE_HID) -+#define SDTX_BASE_TYPE_SSH(id) ((id) | SDTX_DEVICE_TYPE_SSH) -+ -+/** -+ * enum sdtx_device_mode - Mode describing how (and if) the clipboard is -+ * attached to the base of the device. -+ * @SDTX_DEVICE_MODE_TABLET: The clipboard is detached from the base and the -+ * device operates as tablet. -+ * @SDTX_DEVICE_MODE_LAPTOP: The clipboard is attached normally to the base -+ * and the device operates as laptop. -+ * @SDTX_DEVICE_MODE_STUDIO: The clipboard is attached to the base in reverse. -+ * The device operates as tablet with keyboard and -+ * touchpad deactivated, however, the base battery -+ * and, if present in the specific device model, dGPU -+ * are available to the system. -+ */ -+enum sdtx_device_mode { -+ SDTX_DEVICE_MODE_TABLET = 0x00, -+ SDTX_DEVICE_MODE_LAPTOP = 0x01, -+ SDTX_DEVICE_MODE_STUDIO = 0x02, -+}; -+ -+/** -+ * struct sdtx_event - Event provided by reading from the DTX device file. -+ * @length: Length of the event payload, in bytes. -+ * @code: Event code, detailing what type of event this is. -+ * @data: Payload of the event, containing @length bytes. -+ * -+ * See &enum sdtx_event_code for currently valid event codes. -+ */ -+struct sdtx_event { -+ __u16 length; -+ __u16 code; -+ __u8 data[]; -+} __attribute__((__packed__)); -+ -+/** -+ * enum sdtx_event_code - Code describing the type of an event. -+ * @SDTX_EVENT_REQUEST: Detachment request event type. -+ * @SDTX_EVENT_CANCEL: Cancel detachment process event type. -+ * @SDTX_EVENT_BASE_CONNECTION: Base/clipboard connection change event type. -+ * @SDTX_EVENT_LATCH_STATUS: Latch status change event type. -+ * @SDTX_EVENT_DEVICE_MODE: Device mode change event type. -+ * -+ * Used in &struct sdtx_event to describe the type of the event. Further event -+ * codes are reserved for future use. Any event parser should be able to -+ * gracefully handle unknown events, i.e. by simply skipping them. -+ * -+ * Consult the DTX user-space interface documentation for details regarding -+ * the individual event types. -+ */ -+enum sdtx_event_code { -+ SDTX_EVENT_REQUEST = 1, -+ SDTX_EVENT_CANCEL = 2, -+ SDTX_EVENT_BASE_CONNECTION = 3, -+ SDTX_EVENT_LATCH_STATUS = 4, -+ SDTX_EVENT_DEVICE_MODE = 5, -+}; -+ -+/** -+ * struct sdtx_base_info - Describes if and what type of base is connected. -+ * @state: The state of the connection. Valid values are %SDTX_BASE_DETACHED, -+ * %SDTX_BASE_ATTACHED, and %SDTX_DETACH_NOT_FEASIBLE (in case a base -+ * is attached but low clipboard battery prevents detachment). Other -+ * values are currently reserved. -+ * @base_id: The type of base connected. Zero if no base is connected. -+ */ -+struct sdtx_base_info { -+ __u16 state; -+ __u16 base_id; -+} __attribute__((__packed__)); -+ -+/* IOCTLs */ -+#define SDTX_IOCTL_EVENTS_ENABLE _IO(0xa5, 0x21) -+#define SDTX_IOCTL_EVENTS_DISABLE _IO(0xa5, 0x22) -+ -+#define SDTX_IOCTL_LATCH_LOCK _IO(0xa5, 0x23) -+#define SDTX_IOCTL_LATCH_UNLOCK _IO(0xa5, 0x24) -+ -+#define SDTX_IOCTL_LATCH_REQUEST _IO(0xa5, 0x25) -+#define SDTX_IOCTL_LATCH_CONFIRM _IO(0xa5, 0x26) -+#define SDTX_IOCTL_LATCH_HEARTBEAT _IO(0xa5, 0x27) -+#define SDTX_IOCTL_LATCH_CANCEL _IO(0xa5, 0x28) -+ -+#define SDTX_IOCTL_GET_BASE_INFO _IOR(0xa5, 0x29, struct sdtx_base_info) -+#define SDTX_IOCTL_GET_DEVICE_MODE _IOR(0xa5, 0x2a, __u16) -+#define SDTX_IOCTL_GET_LATCH_STATUS _IOR(0xa5, 0x2b, __u16) -+ -+#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H */ --- -2.31.1 - -From 3fe5d502923d75fbc2ec028e0c4d75b3e9edcbea Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 9 Feb 2021 02:50:11 +0100 -Subject: [PATCH] platform/surface: dtx: Add support for native SSAM devices - -Add support for native SSAM devices to the DTX driver. This allows -support for the Surface Book 3, on which the DTX device is not present -in ACPI. - -Patchset: surface-sam ---- - drivers/platform/surface/Kconfig | 4 ++ - drivers/platform/surface/surface_dtx.c | 90 +++++++++++++++++++++++++- - 2 files changed, 93 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 745f9d2eb6a7..dea313989b4c 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -127,6 +127,10 @@ config SURFACE_DTX - behavior of this process, which includes the option to abort it in - case the base is still in use or speed it up in case it is not. - -+ Note that this module can be built without support for the Surface -+ Aggregator Bus (i.e. CONFIG_SURFACE_AGGREGATOR_BUS=n). In that case, -+ some devices, specifically the Surface Book 3, will not be supported. -+ - config SURFACE_GPE - tristate "Surface GPE/Lid Support Driver" - depends on DMI -diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c -index a95adc1094aa..4bb5d286bf95 100644 ---- a/drivers/platform/surface/surface_dtx.c -+++ b/drivers/platform/surface/surface_dtx.c -@@ -27,6 +27,7 @@ - #include - - #include -+#include - #include - - -@@ -1194,7 +1195,94 @@ static struct platform_driver surface_dtx_platform_driver = { - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - }, - }; --module_platform_driver(surface_dtx_platform_driver); -+ -+ -+/* -- SSAM device driver. --------------------------------------------------- */ -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+ -+static int surface_dtx_ssam_probe(struct ssam_device *sdev) -+{ -+ struct sdtx_device *ddev; -+ -+ ddev = sdtx_device_create(&sdev->dev, sdev->ctrl); -+ if (IS_ERR(ddev)) -+ return PTR_ERR(ddev); -+ -+ ssam_device_set_drvdata(sdev, ddev); -+ return 0; -+} -+ -+static void surface_dtx_ssam_remove(struct ssam_device *sdev) -+{ -+ sdtx_device_destroy(ssam_device_get_drvdata(sdev)); -+} -+ -+static const struct ssam_device_id surface_dtx_ssam_match[] = { -+ { SSAM_SDEV(BAS, 0x01, 0x00, 0x00) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match); -+ -+static struct ssam_device_driver surface_dtx_ssam_driver = { -+ .probe = surface_dtx_ssam_probe, -+ .remove = surface_dtx_ssam_remove, -+ .match_table = surface_dtx_ssam_match, -+ .driver = { -+ .name = "surface_dtx", -+ .pm = &surface_dtx_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+static int ssam_dtx_driver_register(void) -+{ -+ return ssam_device_driver_register(&surface_dtx_ssam_driver); -+} -+ -+static void ssam_dtx_driver_unregister(void) -+{ -+ ssam_device_driver_unregister(&surface_dtx_ssam_driver); -+} -+ -+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+static int ssam_dtx_driver_register(void) -+{ -+ return 0; -+} -+ -+static void ssam_dtx_driver_unregister(void) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+ -+/* -- Module setup. --------------------------------------------------------- */ -+ -+static int __init surface_dtx_init(void) -+{ -+ int status; -+ -+ status = ssam_dtx_driver_register(); -+ if (status) -+ return status; -+ -+ status = platform_driver_register(&surface_dtx_platform_driver); -+ if (status) -+ ssam_dtx_driver_unregister(); -+ -+ return status; -+} -+module_init(surface_dtx_init); -+ -+static void __exit surface_dtx_exit(void) -+{ -+ platform_driver_unregister(&surface_dtx_platform_driver); -+ ssam_dtx_driver_unregister(); -+} -+module_exit(surface_dtx_exit); - - MODULE_AUTHOR("Maximilian Luz "); - MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module"); --- -2.31.1 - -From 847255eb9f166fe368464aac96fbd3b4f7b4ec11 Mon Sep 17 00:00:00 2001 -From: kernel test robot -Date: Wed, 7 Apr 2021 01:51:07 +0200 -Subject: [PATCH] platform/surface: fix semicolon.cocci warnings - -drivers/platform/surface/surface_dtx.c:651:2-3: Unneeded semicolon - - Remove unneeded semicolon. - -Generated by: scripts/coccinelle/misc/semicolon.cocci - -Fixes: 1d609992832e ("platform/surface: Add DTX driver") -CC: Maximilian Luz -Reported-by: kernel test robot -Signed-off-by: kernel test robot -Reviewed-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210319051919.GA39801@ae4f36e4f012 -Signed-off-by: Hans de Goede -Upstream: e4899ff6a9120ca5dfa82035d51d4d118260be6e -Patchset: surface-sam ---- - drivers/platform/surface/surface_dtx.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c -index 4bb5d286bf95..2591b875b016 100644 ---- a/drivers/platform/surface/surface_dtx.c -+++ b/drivers/platform/surface/surface_dtx.c -@@ -649,7 +649,7 @@ static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event - - default: - return 0; -- }; -+ } - - if (in->length != len) { - dev_err(ddev->dev, --- -2.31.1 - -From 72f889dc911452c6845d304fa95bf4e5e7f5faa3 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 9 Feb 2021 02:55:31 +0100 -Subject: [PATCH] docs: driver-api: Add Surface DTX driver documentation - -Add documentation for the user-space interface of the Surface DTX -(detachment system) driver, used on Microsoft Surface Book series -devices. - -Patchset: surface-sam ---- - .../surface_aggregator/clients/dtx.rst | 718 ++++++++++++++++++ - .../surface_aggregator/clients/index.rst | 1 + - MAINTAINERS | 1 + - 3 files changed, 720 insertions(+) - create mode 100644 Documentation/driver-api/surface_aggregator/clients/dtx.rst - -diff --git a/Documentation/driver-api/surface_aggregator/clients/dtx.rst b/Documentation/driver-api/surface_aggregator/clients/dtx.rst -new file mode 100644 -index 000000000000..e7e7c20007f0 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/clients/dtx.rst -@@ -0,0 +1,718 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |__u16| replace:: :c:type:`__u16 <__u16>` -+.. |sdtx_event| replace:: :c:type:`struct sdtx_event ` -+.. |sdtx_event_code| replace:: :c:type:`enum sdtx_event_code ` -+.. |sdtx_base_info| replace:: :c:type:`struct sdtx_base_info ` -+.. |sdtx_device_mode| replace:: :c:type:`struct sdtx_device_mode ` -+ -+====================================================== -+User-Space DTX (Clipboard Detachment System) Interface -+====================================================== -+ -+The ``surface_dtx`` driver is responsible for proper clipboard detachment -+and re-attachment handling. To this end, it provides the ``/dev/surface/dtx`` -+device file, through which it can interface with a user-space daemon. This -+daemon is then ultimately responsible for determining and taking necessary -+actions, such as unmounting devices attached to the base, -+unloading/reloading the graphics-driver, user-notifications, etc. -+ -+There are two basic communication principles used in this driver: Commands -+(in other parts of the documentation also referred to as requests) and -+events. Commands are sent to the EC and may have a different implications in -+different contexts. Events are sent by the EC upon some internal state -+change. Commands are always driver-initiated, whereas events are always -+initiated by the EC. -+ -+.. contents:: -+ -+Nomenclature -+============ -+ -+* **Clipboard:** -+ The detachable upper part of the Surface Book, housing the screen and CPU. -+ -+* **Base:** -+ The lower part of the Surface Book from which the clipboard can be -+ detached, optionally (model dependent) housing the discrete GPU (dGPU). -+ -+* **Latch:** -+ The mechanism keeping the clipboard attached to the base in normal -+ operation and allowing it to be detached when requested. -+ -+* **Silently ignored commands:** -+ The command is accepted by the EC as a valid command and acknowledged -+ (following the standard communication protocol), but the EC does not act -+ upon it, i.e. ignores it.e upper part of the -+ -+ -+Detachment Process -+================== -+ -+Warning: This part of the documentation is based on reverse engineering and -+testing and thus may contain errors or be incomplete. -+ -+Latch States -+------------ -+ -+The latch mechanism has two major states: *open* and *closed*. In the -+*closed* state (default), the clipboard is secured to the base, whereas in -+the *open* state, the clipboard can be removed by a user. -+ -+The latch can additionally be locked and, correspondingly, unlocked, which -+can influence the detachment procedure. Specifically, this locking mechanism -+is intended to prevent the dGPU, positioned in the base of the device, from -+being hot-unplugged while in use. More details can be found in the -+documentation for the detachment procedure below. By default, the latch is -+unlocked. -+ -+Detachment Procedure -+-------------------- -+ -+Note that the detachment process is governed fully by the EC. The -+``surface_dtx`` driver only relays events from the EC to user-space and -+commands from user-space to the EC, i.e. it does not influence this process. -+ -+The detachment process is started with the user pressing the *detach* button -+on the base of the device or executing the ``SDTX_IOCTL_LATCH_REQUEST`` IOCTL. -+Following that: -+ -+1. The EC turns on the indicator led on the detach-button, sends a -+ *detach-request* event (``SDTX_EVENT_REQUEST``), and awaits further -+ instructions/commands. In case the latch is unlocked, the led will flash -+ green. If the latch has been locked, the led will be solid red -+ -+2. The event is, via the ``surface_dtx`` driver, relayed to user-space, where -+ an appropriate user-space daemon can handle it and send instructions back -+ to the EC via IOCTLs provided by this driver. -+ -+3. The EC waits for instructions from user-space and acts according to them. -+ If the EC does not receive any instructions in a given period, it will -+ time out and continue as follows: -+ -+ - If the latch is unlocked, the EC will open the latch and the clipboard -+ can be detached from the base. This is the exact behavior as without -+ this driver or any user-space daemon. See the ``SDTX_IOCTL_LATCH_CONFIRM`` -+ description below for more details on the follow-up behavior of the EC. -+ -+ - If the latch is locked, the EC will *not* open the latch, meaning the -+ clipboard cannot be detached from the base. Furthermore, the EC sends -+ an cancel event (``SDTX_EVENT_CANCEL``) detailing this with the cancel -+ reason ``SDTX_DETACH_TIMEDOUT`` (see :ref:`events` for details). -+ -+Valid responses by a user-space daemon to a detachment request event are: -+ -+- Execute ``SDTX_IOCTL_LATCH_REQUEST``. This will immediately abort the -+ detachment process. Furthermore, the EC will send a detach-request event, -+ similar to the user pressing the detach-button to cancel said process (see -+ below). -+ -+- Execute ``SDTX_IOCTL_LATCH_CONFIRM``. This will cause the EC to open the -+ latch, after which the user can separate clipboard and base. -+ -+ As this changes the latch state, a *latch-status* event -+ (``SDTX_EVENT_LATCH_STATUS``) will be sent once the latch has been opened -+ successfully. If the EC fails to open the latch, e.g. due to hardware -+ error or low battery, a latch-cancel event (``SDTX_EVENT_CANCEL``) will be -+ sent with the cancel reason indicating the specific failure. -+ -+ If the latch is currently locked, the latch will automatically be -+ unlocked before it is opened. -+ -+- Execute ``SDTX_IOCTL_LATCH_HEARTBEAT``. This will reset the internal timeout. -+ No other actions will be performed, i.e. the detachment process will neither -+ be completed nor canceled, and the EC will still be waiting for further -+ responses. -+ -+- Execute ``SDTX_IOCTL_LATCH_CANCEL``. This will abort the detachment process, -+ similar to ``SDTX_IOCTL_LATCH_REQUEST``, described above, or the button -+ press, described below. A *generic request* event (``SDTX_EVENT_REQUEST``) -+ is send in response to this. In contrast to those, however, this command -+ does not trigger a new detachment process if none is currently in -+ progress. -+ -+- Do nothing. The detachment process eventually times out as described in -+ point 3. -+ -+See :ref:`ioctls` for more details on these responses. -+ -+It is important to note that, if the user presses the detach button at any -+point when a detachment operation is in progress (i.e. after the EC has sent -+the initial *detach-request* event (``SDTX_EVENT_REQUEST``) and before it -+received the corresponding response concluding the process), the detachment -+process is canceled on the EC-level and an identical event is being sent. -+Thus a *detach-request* event, by itself, does not signal the start of the -+detachment process. -+ -+The detachment process may further be canceled by the EC due to hardware -+failures or a low clipboard battery. This is done via a cancel event -+(``SDTX_EVENT_CANCEL``) with the corresponding cancel reason. -+ -+ -+User-Space Interface Documentation -+================================== -+ -+Error Codes and Status Values -+----------------------------- -+ -+Error and status codes are divided into different categories, which can be -+used to determine if the status code is an error, and, if it is, the -+severity and type of that error. The current categories are: -+ -+.. flat-table:: Overview of Status/Error Categories. -+ :widths: 2 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Value -+ - Short Description -+ -+ * - ``STATUS`` -+ - ``0x0000`` -+ - Non-error status codes. -+ -+ * - ``RUNTIME_ERROR`` -+ - ``0x1000`` -+ - Non-critical runtime errors. -+ -+ * - ``HARDWARE_ERROR`` -+ - ``0x2000`` -+ - Critical hardware failures. -+ -+ * - ``UNKNOWN`` -+ - ``0xF000`` -+ - Unknown error codes. -+ -+Other categories are reserved for future use. The ``SDTX_CATEGORY()`` macro -+can be used to determine the category of any status value. The -+``SDTX_SUCCESS()`` macro can be used to check if the status value is a -+success value (``SDTX_CATEGORY_STATUS``) or if it indicates a failure. -+ -+Unknown status or error codes sent by the EC are assigned to the ``UNKNOWN`` -+category by the driver and may be implemented via their own code in the -+future. -+ -+Currently used error codes are: -+ -+.. flat-table:: Overview of Error Codes. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Category -+ - Value -+ - Short Description -+ -+ * - ``SDTX_DETACH_NOT_FEASIBLE`` -+ - ``RUNTIME`` -+ - ``0x1001`` -+ - Detachment not feasible due to low clipboard battery. -+ -+ * - ``SDTX_DETACH_TIMEDOUT`` -+ - ``RUNTIME`` -+ - ``0x1002`` -+ - Detachment process timed out while the latch was locked. -+ -+ * - ``SDTX_ERR_FAILED_TO_OPEN`` -+ - ``HARDWARE`` -+ - ``0x2001`` -+ - Failed to open latch. -+ -+ * - ``SDTX_ERR_FAILED_TO_REMAIN_OPEN`` -+ - ``HARDWARE`` -+ - ``0x2002`` -+ - Failed to keep latch open. -+ -+ * - ``SDTX_ERR_FAILED_TO_CLOSE`` -+ - ``HARDWARE`` -+ - ``0x2003`` -+ - Failed to close latch. -+ -+Other error codes are reserved for future use. Non-error status codes may -+overlap and are generally only unique within their use-case: -+ -+.. flat-table:: Latch Status Codes. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Category -+ - Value -+ - Short Description -+ -+ * - ``SDTX_LATCH_CLOSED`` -+ - ``STATUS`` -+ - ``0x0000`` -+ - Latch is closed/has been closed. -+ -+ * - ``SDTX_LATCH_OPENED`` -+ - ``STATUS`` -+ - ``0x0001`` -+ - Latch is open/has been opened. -+ -+.. flat-table:: Base State Codes. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Category -+ - Value -+ - Short Description -+ -+ * - ``SDTX_BASE_DETACHED`` -+ - ``STATUS`` -+ - ``0x0000`` -+ - Base has been detached/is not present. -+ -+ * - ``SDTX_BASE_ATTACHED`` -+ - ``STATUS`` -+ - ``0x0001`` -+ - Base has been attached/is present. -+ -+Again, other codes are reserved for future use. -+ -+.. _events: -+ -+Events -+------ -+ -+Events can be received by reading from the device file. They are disabled by -+default and have to be enabled by executing ``SDTX_IOCTL_EVENTS_ENABLE`` -+first. All events follow the layout prescribed by |sdtx_event|. Specific -+event types can be identified by their event code, described in -+|sdtx_event_code|. Note that other event codes are reserved for future use, -+thus an event parser must be able to handle any unknown/unsupported event -+types gracefully, by relying on the payload length given in the event header. -+ -+Currently provided event types are: -+ -+.. flat-table:: Overview of DTX events. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Code -+ - Payload -+ - Short Description -+ -+ * - ``SDTX_EVENT_REQUEST`` -+ - ``1`` -+ - ``0`` bytes -+ - Detachment process initiated/aborted. -+ -+ * - ``SDTX_EVENT_CANCEL`` -+ - ``2`` -+ - ``2`` bytes -+ - EC canceled detachment process. -+ -+ * - ``SDTX_EVENT_BASE_CONNECTION`` -+ - ``3`` -+ - ``4`` bytes -+ - Base connection state changed. -+ -+ * - ``SDTX_EVENT_LATCH_STATUS`` -+ - ``4`` -+ - ``2`` bytes -+ - Latch status changed. -+ -+ * - ``SDTX_EVENT_DEVICE_MODE`` -+ - ``5`` -+ - ``2`` bytes -+ - Device mode changed. -+ -+Individual events in more detail: -+ -+``SDTX_EVENT_REQUEST`` -+^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when a detachment process is started or, if in progress, aborted by the -+user, either via a detach button press or a detach request -+(``SDTX_IOCTL_LATCH_REQUEST``) being sent from user-space. -+ -+Does not have any payload. -+ -+``SDTX_EVENT_CANCEL`` -+^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when a detachment process is canceled by the EC due to unfulfilled -+preconditions (e.g. clipboard battery too low to detach) or hardware -+failure. The reason for cancellation is given in the event payload detailed -+below and can be one of -+ -+* ``SDTX_DETACH_TIMEDOUT``: Detachment timed out while the latch was locked. -+ The latch has neither been opened nor unlocked. -+ -+* ``SDTX_DETACH_NOT_FEASIBLE``: Detachment not feasible due to low clipboard -+ battery. -+ -+* ``SDTX_ERR_FAILED_TO_OPEN``: Could not open the latch (hardware failure). -+ -+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``: Could not keep the latch open (hardware -+ failure). -+ -+* ``SDTX_ERR_FAILED_TO_CLOSE``: Could not close the latch (hardware failure). -+ -+Other error codes in this context are reserved for future use. -+ -+These codes can be classified via the ``SDTX_CATEGORY()`` macro to discern -+between critical hardware errors (``SDTX_CATEGORY_HARDWARE_ERROR``) or -+runtime errors (``SDTX_CATEGORY_RUNTIME_ERROR``), the latter of which may -+happen during normal operation if certain preconditions for detachment are -+not given. -+ -+.. flat-table:: Detachment Cancel Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``reason`` -+ - |__u16| -+ - Reason for cancellation. -+ -+``SDTX_EVENT_BASE_CONNECTION`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when the base connection state has changed, i.e. when the base has been -+attached, detached, or detachment has become infeasible due to low clipboard -+battery. The new state and, if a base is connected, ID of the base is -+provided as payload of type |sdtx_base_info| with its layout presented -+below: -+ -+.. flat-table:: Base-Connection-Change Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``state`` -+ - |__u16| -+ - Base connection state. -+ -+ * - ``base_id`` -+ - |__u16| -+ - Type of base connected (zero if none). -+ -+Possible values for ``state`` are: -+ -+* ``SDTX_BASE_DETACHED``, -+* ``SDTX_BASE_ATTACHED``, and -+* ``SDTX_DETACH_NOT_FEASIBLE``. -+ -+Other values are reserved for future use. -+ -+``SDTX_EVENT_LATCH_STATUS`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when the latch status has changed, i.e. when the latch has been opened, -+closed, or an error occurred. The current status is provided as payload: -+ -+.. flat-table:: Latch-Status-Change Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``status`` -+ - |__u16| -+ - Latch status. -+ -+Possible values for ``status`` are: -+ -+* ``SDTX_LATCH_CLOSED``, -+* ``SDTX_LATCH_OPENED``, -+* ``SDTX_ERR_FAILED_TO_OPEN``, -+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and -+* ``SDTX_ERR_FAILED_TO_CLOSE``. -+ -+Other values are reserved for future use. -+ -+``SDTX_EVENT_DEVICE_MODE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when the device mode has changed. The new device mode is provided as -+payload: -+ -+.. flat-table:: Device-Mode-Change Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``mode`` -+ - |__u16| -+ - Device operation mode. -+ -+Possible values for ``mode`` are: -+ -+* ``SDTX_DEVICE_MODE_TABLET``, -+* ``SDTX_DEVICE_MODE_LAPTOP``, and -+* ``SDTX_DEVICE_MODE_STUDIO``. -+ -+Other values are reserved for future use. -+ -+.. _ioctls: -+ -+IOCTLs -+------ -+ -+The following IOCTLs are provided: -+ -+.. flat-table:: Overview of DTX IOCTLs -+ :widths: 1 1 1 1 4 -+ :header-rows: 1 -+ -+ * - Type -+ - Number -+ - Direction -+ - Name -+ - Description -+ -+ * - ``0xA5`` -+ - ``0x21`` -+ - ``-`` -+ - ``EVENTS_ENABLE`` -+ - Enable events for the current file descriptor. -+ -+ * - ``0xA5`` -+ - ``0x22`` -+ - ``-`` -+ - ``EVENTS_DISABLE`` -+ - Disable events for the current file descriptor. -+ -+ * - ``0xA5`` -+ - ``0x23`` -+ - ``-`` -+ - ``LATCH_LOCK`` -+ - Lock the latch. -+ -+ * - ``0xA5`` -+ - ``0x24`` -+ - ``-`` -+ - ``LATCH_UNLOCK`` -+ - Unlock the latch. -+ -+ * - ``0xA5`` -+ - ``0x25`` -+ - ``-`` -+ - ``LATCH_REQUEST`` -+ - Request clipboard detachment. -+ -+ * - ``0xA5`` -+ - ``0x26`` -+ - ``-`` -+ - ``LATCH_CONFIRM`` -+ - Confirm clipboard detachment request. -+ -+ * - ``0xA5`` -+ - ``0x27`` -+ - ``-`` -+ - ``LATCH_HEARTBEAT`` -+ - Send heartbeat signal to EC. -+ -+ * - ``0xA5`` -+ - ``0x28`` -+ - ``-`` -+ - ``LATCH_CANCEL`` -+ - Cancel detachment process. -+ -+ * - ``0xA5`` -+ - ``0x29`` -+ - ``R`` -+ - ``GET_BASE_INFO`` -+ - Get current base/connection information. -+ -+ * - ``0xA5`` -+ - ``0x2A`` -+ - ``R`` -+ - ``GET_DEVICE_MODE`` -+ - Get current device operation mode. -+ -+ * - ``0xA5`` -+ - ``0x2B`` -+ - ``R`` -+ - ``GET_LATCH_STATUS`` -+ - Get current device latch status. -+ -+``SDTX_IOCTL_EVENTS_ENABLE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x22)``. -+ -+Enable events for the current file descriptor. Events can be obtained by -+reading from the device, if enabled. Events are disabled by default. -+ -+``SDTX_IOCTL_EVENTS_DISABLE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x22)``. -+ -+Disable events for the current file descriptor. Events can be obtained by -+reading from the device, if enabled. Events are disabled by default. -+ -+``SDTX_IOCTL_LATCH_LOCK`` -+^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x23)``. -+ -+Locks the latch, causing the detachment procedure to abort without opening -+the latch on timeout. The latch is unlocked by default. This command will be -+silently ignored if the latch is already locked. -+ -+``SDTX_IOCTL_LATCH_UNLOCK`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x24)``. -+ -+Unlocks the latch, causing the detachment procedure to open the latch on -+timeout. The latch is unlocked by default. This command will not open the -+latch when sent during an ongoing detachment process. It will be silently -+ignored if the latch is already unlocked. -+ -+``SDTX_IOCTL_LATCH_REQUEST`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x25)``. -+ -+Generic latch request. Behavior depends on the context: If no -+detachment-process is active, detachment is requested. Otherwise the -+currently active detachment-process will be aborted. -+ -+If a detachment process is canceled by this operation, a generic detachment -+request event (``SDTX_EVENT_REQUEST``) will be sent. -+ -+This essentially behaves the same as a detachment button press. -+ -+``SDTX_IOCTL_LATCH_CONFIRM`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x26)``. -+ -+Acknowledges and confirms a latch request. If sent during an ongoing -+detachment process, this command causes the latch to be opened immediately. -+The latch will also be opened if it has been locked. In this case, the latch -+lock is reset to the unlocked state. -+ -+This command will be silently ignored if there is currently no detachment -+procedure in progress. -+ -+``SDTX_IOCTL_LATCH_HEARTBEAT`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x27)``. -+ -+Sends a heartbeat, essentially resetting the detachment timeout. This -+command can be used to keep the detachment process alive while work required -+for the detachment to succeed is still in progress. -+ -+This command will be silently ignored if there is currently no detachment -+procedure in progress. -+ -+``SDTX_IOCTL_LATCH_CANCEL`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x28)``. -+ -+Cancels detachment in progress (if any). If a detachment process is canceled -+by this operation, a generic detachment request event -+(``SDTX_EVENT_REQUEST``) will be sent. -+ -+This command will be silently ignored if there is currently no detachment -+procedure in progress. -+ -+``SDTX_IOCTL_GET_BASE_INFO`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IOR(0xA5, 0x29, struct sdtx_base_info)``. -+ -+Get the current base connection state (i.e. attached/detached) and the type -+of the base connected to the clipboard. This is command essentially provides -+a way to query the information provided by the base connection change event -+(``SDTX_EVENT_BASE_CONNECTION``). -+ -+Possible values for ``struct sdtx_base_info.state`` are: -+ -+* ``SDTX_BASE_DETACHED``, -+* ``SDTX_BASE_ATTACHED``, and -+* ``SDTX_DETACH_NOT_FEASIBLE``. -+ -+Other values are reserved for future use. -+ -+``SDTX_IOCTL_GET_DEVICE_MODE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IOR(0xA5, 0x2A, __u16)``. -+ -+Returns the device operation mode, indicating if and how the base is -+attached to the clipboard. This is command essentially provides a way to -+query the information provided by the device mode change event -+(``SDTX_EVENT_DEVICE_MODE``). -+ -+Returned values are: -+ -+* ``SDTX_DEVICE_MODE_LAPTOP`` -+* ``SDTX_DEVICE_MODE_TABLET`` -+* ``SDTX_DEVICE_MODE_STUDIO`` -+ -+See |sdtx_device_mode| for details. Other values are reserved for future -+use. -+ -+ -+``SDTX_IOCTL_GET_LATCH_STATUS`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IOR(0xA5, 0x2B, __u16)``. -+ -+Get the current latch status or (presumably) the last error encountered when -+trying to open/close the latch. This is command essentially provides a way -+to query the information provided by the latch status change event -+(``SDTX_EVENT_LATCH_STATUS``). -+ -+Returned values are: -+ -+* ``SDTX_LATCH_CLOSED``, -+* ``SDTX_LATCH_OPENED``, -+* ``SDTX_ERR_FAILED_TO_OPEN``, -+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and -+* ``SDTX_ERR_FAILED_TO_CLOSE``. -+ -+Other values are reserved for future use. -+ -+A Note on Base IDs -+------------------ -+ -+Base types/IDs provided via ``SDTX_EVENT_BASE_CONNECTION`` or -+``SDTX_IOCTL_GET_BASE_INFO`` are directly forwarded from the EC in the lower -+byte of the combined |__u16| value, with the driver storing the EC type from -+which this ID comes in the high byte (without this, base IDs over different -+types of ECs may be overlapping). -+ -+The ``SDTX_DEVICE_TYPE()`` macro can be used to determine the EC device -+type. This can be one of -+ -+* ``SDTX_DEVICE_TYPE_HID``, for Surface Aggregator Module over HID, and -+ -+* ``SDTX_DEVICE_TYPE_SSH``, for Surface Aggregator Module over Surface Serial -+ Hub. -+ -+Note that currently only the ``SSH`` type EC is supported, however ``HID`` -+type is reserved for future use. -+ -+Structures and Enums -+-------------------- -+ -+.. kernel-doc:: include/uapi/linux/surface_aggregator/dtx.h -+ -+API Users -+========= -+ -+A user-space daemon utilizing this API can be found at -+https://github.com/linux-surface/surface-dtx-daemon. -diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst -index 3ccabce23271..98ea9946b8a2 100644 ---- a/Documentation/driver-api/surface_aggregator/clients/index.rst -+++ b/Documentation/driver-api/surface_aggregator/clients/index.rst -@@ -11,6 +11,7 @@ This is the documentation for client drivers themselves. Refer to - :maxdepth: 1 - - cdev -+ dtx - san - - .. only:: subproject and html -diff --git a/MAINTAINERS b/MAINTAINERS -index 1a60e353df38..a6a4f5afdfa8 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11789,6 +11789,7 @@ MICROSOFT SURFACE DTX DRIVER - M: Maximilian Luz - L: platform-driver-x86@vger.kernel.org - S: Maintained -+F: Documentation/driver-api/surface_aggregator/clients/dtx.rst - F: drivers/platform/surface/surface_dtx.c - F: include/uapi/linux/surface_aggregator/dtx.h - --- -2.31.1 - -From 32140e7528370b2154fc068c0cba03eafe0c6646 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 11 Feb 2021 20:08:50 +0100 -Subject: [PATCH] HID: Add support for Surface Aggregator Module HID transport - -Add a HID transport driver to support integrated HID devices on newer -Microsoft Surface models (specifically 7th-generation, i.e. Surface -Laptop 3, Surface Book 3, and later). - -On those models, the internal keyboard and touchpad (as well as some -other HID devices with currently unknown function) are connected via the -generic HID subsystem (TC=0x15) of the Surface System Aggregator Module -(SSAM). This subsystem provides a generic HID transport layer, support -for which is implemented by this driver. - -Patchset: surface-sam ---- - MAINTAINERS | 7 + - drivers/hid/Kconfig | 2 + - drivers/hid/Makefile | 2 + - drivers/hid/surface-hid/Kconfig | 28 +++ - drivers/hid/surface-hid/Makefile | 6 + - drivers/hid/surface-hid/surface_hid.c | 256 +++++++++++++++++++ - drivers/hid/surface-hid/surface_hid_core.c | 272 +++++++++++++++++++++ - drivers/hid/surface-hid/surface_hid_core.h | 77 ++++++ - 8 files changed, 650 insertions(+) - create mode 100644 drivers/hid/surface-hid/Kconfig - create mode 100644 drivers/hid/surface-hid/Makefile - create mode 100644 drivers/hid/surface-hid/surface_hid.c - create mode 100644 drivers/hid/surface-hid/surface_hid_core.c - create mode 100644 drivers/hid/surface-hid/surface_hid_core.h - -diff --git a/MAINTAINERS b/MAINTAINERS -index a6a4f5afdfa8..2eac975aee50 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11808,6 +11808,13 @@ S: Maintained - T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git - F: drivers/platform/surface/ - -+MICROSOFT SURFACE HID TRANSPORT DRIVER -+M: Maximilian Luz -+L: linux-input@vger.kernel.org -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/hid/surface-hid/ -+ - MICROSOFT SURFACE PRO 3 BUTTON DRIVER - M: Chen Yu - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig -index 09fa75a2b289..e82474897919 100644 ---- a/drivers/hid/Kconfig -+++ b/drivers/hid/Kconfig -@@ -1187,4 +1187,6 @@ source "drivers/hid/intel-ish-hid/Kconfig" - - source "drivers/hid/amd-sfh-hid/Kconfig" - -+source "drivers/hid/surface-hid/Kconfig" -+ - endmenu -diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile -index 014d21fe7dac..98d6b9f666ec 100644 ---- a/drivers/hid/Makefile -+++ b/drivers/hid/Makefile -@@ -144,3 +144,5 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ - obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ - - obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ -+ -+obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ -diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig -new file mode 100644 -index 000000000000..642c7f0e64fe ---- /dev/null -+++ b/drivers/hid/surface-hid/Kconfig -@@ -0,0 +1,28 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+menu "Surface System Aggregator Module HID support" -+ depends on SURFACE_AGGREGATOR -+ depends on INPUT -+ -+config SURFACE_HID -+ tristate "HID transport driver for Surface System Aggregator Module" -+ depends on SURFACE_AGGREGATOR_REGISTRY -+ select SURFACE_HID_CORE -+ help -+ Driver to support integrated HID devices on newer Microsoft Surface -+ models. -+ -+ This driver provides support for the HID transport protocol provided -+ by the Surface Aggregator Module (i.e. the embedded controller) on -+ 7th-generation Microsoft Surface devices, i.e. Surface Book 3 and -+ Surface Laptop 3. On those models, it is mainly used to connect the -+ integrated touchpad and keyboard. -+ -+ Say M or Y here, if you want support for integrated HID devices, i.e. -+ integrated touchpad and keyboard, on 7th generation Microsoft Surface -+ models. -+ -+endmenu -+ -+config SURFACE_HID_CORE -+ tristate -+ select HID -diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile -new file mode 100644 -index 000000000000..62fc04632d3d ---- /dev/null -+++ b/drivers/hid/surface-hid/Makefile -@@ -0,0 +1,6 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+# -+# Makefile - Surface System Aggregator Module (SSAM) HID transport driver. -+# -+obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o -+obj-$(CONFIG_SURFACE_HID) += surface_hid.o -diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c -new file mode 100644 -index 000000000000..e4477c328536 ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_hid.c -@@ -0,0 +1,256 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) HID transport driver for the -+ * generic HID interface (HID/TC=0x15 subsystem). Provides support for -+ * integrated HID devices on Surface Laptop 3, Book 3, and later. -+ * -+ * Copyright (C) 2019-2021 Blaž Hrastnik , -+ * Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "surface_hid_core.h" -+ -+ -+/* -- SAM interface. -------------------------------------------------------- */ -+ -+struct surface_hid_buffer_slice { -+ __u8 entry; -+ __le32 offset; -+ __le32 length; -+ __u8 end; -+ __u8 data[]; -+} __packed; -+ -+static_assert(sizeof(struct surface_hid_buffer_slice) == 10); -+ -+enum surface_hid_cid { -+ SURFACE_HID_CID_OUTPUT_REPORT = 0x01, -+ SURFACE_HID_CID_GET_FEATURE_REPORT = 0x02, -+ SURFACE_HID_CID_SET_FEATURE_REPORT = 0x03, -+ SURFACE_HID_CID_GET_DESCRIPTOR = 0x04, -+}; -+ -+static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len) -+{ -+ u8 buffer[sizeof(struct surface_hid_buffer_slice) + 0x76]; -+ struct surface_hid_buffer_slice *slice; -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ u32 buffer_len, offset, length; -+ int status; -+ -+ /* -+ * Note: The 0x76 above has been chosen because that's what's used by -+ * the Windows driver. Together with the header, this leads to a 128 -+ * byte payload in total. -+ */ -+ -+ buffer_len = ARRAY_SIZE(buffer) - sizeof(struct surface_hid_buffer_slice); -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_HID_CID_GET_DESCRIPTOR; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(struct surface_hid_buffer_slice); -+ rqst.payload = buffer; -+ -+ rsp.capacity = ARRAY_SIZE(buffer); -+ rsp.pointer = buffer; -+ -+ slice = (struct surface_hid_buffer_slice *)buffer; -+ slice->entry = entry; -+ slice->end = 0; -+ -+ offset = 0; -+ length = buffer_len; -+ -+ while (!slice->end && offset < len) { -+ put_unaligned_le32(offset, &slice->offset); -+ put_unaligned_le32(length, &slice->length); -+ -+ rsp.length = 0; -+ -+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, -+ sizeof(*slice)); -+ if (status) -+ return status; -+ -+ offset = get_unaligned_le32(&slice->offset); -+ length = get_unaligned_le32(&slice->length); -+ -+ /* Don't mess stuff up in case we receive garbage. */ -+ if (length > buffer_len || offset > len) -+ return -EPROTO; -+ -+ if (offset + length > len) -+ length = len - offset; -+ -+ memcpy(buf + offset, &slice->data[0], length); -+ -+ offset += length; -+ length = buffer_len; -+ } -+ -+ if (offset != len) { -+ dev_err(shid->dev, "unexpected descriptor length: got %u, expected %zu\n", -+ offset, len); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id, bool feature, -+ u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ u8 cid; -+ -+ if (feature) -+ cid = SURFACE_HID_CID_SET_FEATURE_REPORT; -+ else -+ cid = SURFACE_HID_CID_OUTPUT_REPORT; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.instance_id = shid->uid.instance; -+ rqst.command_id = cid; -+ rqst.flags = 0; -+ rqst.length = len; -+ rqst.payload = buf; -+ -+ buf[0] = rprt_id; -+ -+ return ssam_retry(ssam_request_sync, shid->ctrl, &rqst, NULL); -+} -+ -+static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.instance_id = shid->uid.instance; -+ rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT; -+ rqst.flags = 0; -+ rqst.length = sizeof(rprt_id); -+ rqst.payload = &rprt_id; -+ -+ rsp.capacity = len; -+ rsp.length = 0; -+ rsp.pointer = buf; -+ -+ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id)); -+} -+ -+static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif); -+ int status; -+ -+ if (event->command_id != 0x00) -+ return 0; -+ -+ status = hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], -+ event->length, 0); -+ -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+} -+ -+ -+/* -- Transport driver. ----------------------------------------------------- */ -+ -+static int shid_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_set_raw_report(shid, rprt_id, false, buf, len); -+ return status >= 0 ? len : status; -+} -+ -+static int shid_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_get_raw_report(shid, rprt_id, buf, len); -+ return status >= 0 ? len : status; -+} -+ -+static int shid_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_set_raw_report(shid, rprt_id, true, buf, len); -+ return status >= 0 ? len : status; -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int surface_hid_probe(struct ssam_device *sdev) -+{ -+ struct surface_hid_device *shid; -+ -+ shid = devm_kzalloc(&sdev->dev, sizeof(*shid), GFP_KERNEL); -+ if (!shid) -+ return -ENOMEM; -+ -+ shid->dev = &sdev->dev; -+ shid->ctrl = sdev->ctrl; -+ shid->uid = sdev->uid; -+ -+ shid->notif.base.priority = 1; -+ shid->notif.base.fn = ssam_hid_event_fn; -+ shid->notif.event.reg = SSAM_EVENT_REGISTRY_REG; -+ shid->notif.event.id.target_category = sdev->uid.category; -+ shid->notif.event.id.instance = sdev->uid.instance; -+ shid->notif.event.mask = SSAM_EVENT_MASK_STRICT; -+ shid->notif.event.flags = 0; -+ -+ shid->ops.get_descriptor = ssam_hid_get_descriptor; -+ shid->ops.output_report = shid_output_report; -+ shid->ops.get_feature_report = shid_get_feature_report; -+ shid->ops.set_feature_report = shid_set_feature_report; -+ -+ ssam_device_set_drvdata(sdev, shid); -+ return surface_hid_device_add(shid); -+} -+ -+static void surface_hid_remove(struct ssam_device *sdev) -+{ -+ surface_hid_device_destroy(ssam_device_get_drvdata(sdev)); -+} -+ -+static const struct ssam_device_id surface_hid_match[] = { -+ { SSAM_SDEV(HID, 0x02, SSAM_ANY_IID, 0x00) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_hid_match); -+ -+static struct ssam_device_driver surface_hid_driver = { -+ .probe = surface_hid_probe, -+ .remove = surface_hid_remove, -+ .match_table = surface_hid_match, -+ .driver = { -+ .name = "surface_hid", -+ .pm = &surface_hid_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_hid_driver); -+ -+MODULE_AUTHOR("Blaž Hrastnik "); -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("HID transport driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c -new file mode 100644 -index 000000000000..7b27ec392232 ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_hid_core.c -@@ -0,0 +1,272 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Common/core components for the Surface System Aggregator Module (SSAM) HID -+ * transport driver. Provides support for integrated HID devices on Microsoft -+ * Surface models. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "surface_hid_core.h" -+ -+ -+/* -- Device descriptor access. --------------------------------------------- */ -+ -+static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) -+{ -+ int status; -+ -+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, -+ (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); -+ if (status) -+ return status; -+ -+ if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) { -+ dev_err(shid->dev, "unexpected HID descriptor length: got %u, expected %zu\n", -+ shid->hid_desc.desc_len, sizeof(shid->hid_desc)); -+ return -EPROTO; -+ } -+ -+ if (shid->hid_desc.desc_type != HID_DT_HID) { -+ dev_err(shid->dev, "unexpected HID descriptor type: got %#04x, expected %#04x\n", -+ shid->hid_desc.desc_type, HID_DT_HID); -+ return -EPROTO; -+ } -+ -+ if (shid->hid_desc.num_descriptors != 1) { -+ dev_err(shid->dev, "unexpected number of descriptors: got %u, expected 1\n", -+ shid->hid_desc.num_descriptors); -+ return -EPROTO; -+ } -+ -+ if (shid->hid_desc.report_desc_type != HID_DT_REPORT) { -+ dev_err(shid->dev, "unexpected report descriptor type: got %#04x, expected %#04x\n", -+ shid->hid_desc.report_desc_type, HID_DT_REPORT); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static int surface_hid_load_device_attributes(struct surface_hid_device *shid) -+{ -+ int status; -+ -+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, -+ (u8 *)&shid->attrs, sizeof(shid->attrs)); -+ if (status) -+ return status; -+ -+ if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) { -+ dev_err(shid->dev, "unexpected attribute length: got %u, expected %zu\n", -+ get_unaligned_le32(&shid->attrs.length), sizeof(shid->attrs)); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+ -+/* -- Transport driver (common). -------------------------------------------- */ -+ -+static int surface_hid_start(struct hid_device *hid) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ -+ return ssam_notifier_register(shid->ctrl, &shid->notif); -+} -+ -+static void surface_hid_stop(struct hid_device *hid) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ -+ /* Note: This call will log errors for us, so ignore them here. */ -+ ssam_notifier_unregister(shid->ctrl, &shid->notif); -+} -+ -+static int surface_hid_open(struct hid_device *hid) -+{ -+ return 0; -+} -+ -+static void surface_hid_close(struct hid_device *hid) -+{ -+} -+ -+static int surface_hid_parse(struct hid_device *hid) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len); -+ u8 *buf; -+ int status; -+ -+ buf = kzalloc(len, GFP_KERNEL); -+ if (!buf) -+ return -ENOMEM; -+ -+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len); -+ if (!status) -+ status = hid_parse_report(hid, buf, len); -+ -+ kfree(buf); -+ return status; -+} -+ -+static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportnum, u8 *buf, -+ size_t len, unsigned char rtype, int reqtype) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ -+ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) -+ return shid->ops.output_report(shid, reportnum, buf, len); -+ -+ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) -+ return shid->ops.get_feature_report(shid, reportnum, buf, len); -+ -+ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) -+ return shid->ops.set_feature_report(shid, reportnum, buf, len); -+ -+ return -EIO; -+} -+ -+static struct hid_ll_driver surface_hid_ll_driver = { -+ .start = surface_hid_start, -+ .stop = surface_hid_stop, -+ .open = surface_hid_open, -+ .close = surface_hid_close, -+ .parse = surface_hid_parse, -+ .raw_request = surface_hid_raw_request, -+}; -+ -+ -+/* -- Common device setup. -------------------------------------------------- */ -+ -+int surface_hid_device_add(struct surface_hid_device *shid) -+{ -+ int status; -+ -+ status = surface_hid_load_hid_descriptor(shid); -+ if (status) -+ return status; -+ -+ status = surface_hid_load_device_attributes(shid); -+ if (status) -+ return status; -+ -+ shid->hid = hid_allocate_device(); -+ if (IS_ERR(shid->hid)) -+ return PTR_ERR(shid->hid); -+ -+ shid->hid->dev.parent = shid->dev; -+ shid->hid->bus = BUS_HOST; -+ shid->hid->vendor = cpu_to_le16(shid->attrs.vendor); -+ shid->hid->product = cpu_to_le16(shid->attrs.product); -+ shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version); -+ shid->hid->country = shid->hid_desc.country_code; -+ -+ snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X", -+ shid->hid->vendor, shid->hid->product); -+ -+ strscpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys)); -+ -+ shid->hid->driver_data = shid; -+ shid->hid->ll_driver = &surface_hid_ll_driver; -+ -+ status = hid_add_device(shid->hid); -+ if (status) -+ hid_destroy_device(shid->hid); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(surface_hid_device_add); -+ -+void surface_hid_device_destroy(struct surface_hid_device *shid) -+{ -+ hid_destroy_device(shid->hid); -+} -+EXPORT_SYMBOL_GPL(surface_hid_device_destroy); -+ -+ -+/* -- PM ops. --------------------------------------------------------------- */ -+ -+#ifdef CONFIG_PM_SLEEP -+ -+static int surface_hid_suspend(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_SUSPEND); -+ -+ return 0; -+} -+ -+static int surface_hid_resume(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->resume) -+ return d->hid->driver->resume(d->hid); -+ -+ return 0; -+} -+ -+static int surface_hid_freeze(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_FREEZE); -+ -+ return 0; -+} -+ -+static int surface_hid_poweroff(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE); -+ -+ return 0; -+} -+ -+static int surface_hid_restore(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->reset_resume) -+ return d->hid->driver->reset_resume(d->hid); -+ -+ return 0; -+} -+ -+const struct dev_pm_ops surface_hid_pm_ops = { -+ .freeze = surface_hid_freeze, -+ .thaw = surface_hid_resume, -+ .suspend = surface_hid_suspend, -+ .resume = surface_hid_resume, -+ .poweroff = surface_hid_poweroff, -+ .restore = surface_hid_restore, -+}; -+EXPORT_SYMBOL_GPL(surface_hid_pm_ops); -+ -+#else /* CONFIG_PM_SLEEP */ -+ -+const struct dev_pm_ops surface_hid_pm_ops = { }; -+EXPORT_SYMBOL_GPL(surface_hid_pm_ops); -+ -+#endif /* CONFIG_PM_SLEEP */ -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/hid/surface-hid/surface_hid_core.h b/drivers/hid/surface-hid/surface_hid_core.h -new file mode 100644 -index 000000000000..4b1a7b57e035 ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_hid_core.h -@@ -0,0 +1,77 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Common/core components for the Surface System Aggregator Module (SSAM) HID -+ * transport driver. Provides support for integrated HID devices on Microsoft -+ * Surface models. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef SURFACE_HID_CORE_H -+#define SURFACE_HID_CORE_H -+ -+#include -+#include -+#include -+ -+#include -+#include -+ -+enum surface_hid_descriptor_entry { -+ SURFACE_HID_DESC_HID = 0, -+ SURFACE_HID_DESC_REPORT = 1, -+ SURFACE_HID_DESC_ATTRS = 2, -+}; -+ -+struct surface_hid_descriptor { -+ __u8 desc_len; /* = 9 */ -+ __u8 desc_type; /* = HID_DT_HID */ -+ __le16 hid_version; -+ __u8 country_code; -+ __u8 num_descriptors; /* = 1 */ -+ -+ __u8 report_desc_type; /* = HID_DT_REPORT */ -+ __le16 report_desc_len; -+} __packed; -+ -+static_assert(sizeof(struct surface_hid_descriptor) == 9); -+ -+struct surface_hid_attributes { -+ __le32 length; -+ __le16 vendor; -+ __le16 product; -+ __le16 version; -+ __u8 _unknown[22]; -+} __packed; -+ -+static_assert(sizeof(struct surface_hid_attributes) == 32); -+ -+struct surface_hid_device; -+ -+struct surface_hid_device_ops { -+ int (*get_descriptor)(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len); -+ int (*output_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); -+ int (*get_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); -+ int (*set_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); -+}; -+ -+struct surface_hid_device { -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ struct ssam_device_uid uid; -+ -+ struct surface_hid_descriptor hid_desc; -+ struct surface_hid_attributes attrs; -+ -+ struct ssam_event_notifier notif; -+ struct hid_device *hid; -+ -+ struct surface_hid_device_ops ops; -+}; -+ -+int surface_hid_device_add(struct surface_hid_device *shid); -+void surface_hid_device_destroy(struct surface_hid_device *shid); -+ -+extern const struct dev_pm_ops surface_hid_pm_ops; -+ -+#endif /* SURFACE_HID_CORE_H */ --- -2.31.1 - -From 72d132979aab5c24e6e10820f9f3aabb6df55a84 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 11 Feb 2021 20:10:17 +0100 -Subject: [PATCH] HID: surface-hid: Add support for legacy keyboard interface - -Add support for the legacy keyboard (KBD/TC=0x08) HID transport layer of -the Surface System Aggregator Module (SSAM) to the Surface HID driver. -On Surface Laptops 1 and 2, this interface is used to connect the -integrated keyboard. - -Note that this subsystem interface essentially provides a limited HID -transport layer. In contras to the generic HID interface (TC=0x15) used -on newer Surface models, this interface only allows (as far as we know) -for a single device to be connected and is otherwise severely limited in -terms of support for feature- and output-reports. Specifically, only -caps-lock-LED output-reports and a single read-only feature-report are -supported. - -Patchset: surface-sam ---- - drivers/hid/surface-hid/Kconfig | 14 ++ - drivers/hid/surface-hid/Makefile | 1 + - drivers/hid/surface-hid/surface_hid.c | 7 +- - drivers/hid/surface-hid/surface_kbd.c | 300 ++++++++++++++++++++++++++ - 4 files changed, 317 insertions(+), 5 deletions(-) - create mode 100644 drivers/hid/surface-hid/surface_kbd.c - -diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig -index 642c7f0e64fe..7ce9b5d641eb 100644 ---- a/drivers/hid/surface-hid/Kconfig -+++ b/drivers/hid/surface-hid/Kconfig -@@ -21,6 +21,20 @@ config SURFACE_HID - integrated touchpad and keyboard, on 7th generation Microsoft Surface - models. - -+config SURFACE_KBD -+ tristate "HID keyboard transport driver for Surface System Aggregator Module" -+ select SURFACE_HID_CORE -+ help -+ Driver to support HID keyboards on Surface Laptop 1 and 2 devices. -+ -+ This driver provides support for the HID transport protocol provided -+ by the Surface Aggregator Module (i.e. the embedded controller) on -+ Microsoft Surface Laptops 1 and 2. It is used to connect the -+ integrated keyboard on those devices. -+ -+ Say M or Y here, if you want support for the integrated keyboard on -+ Microsoft Surface Laptops 1 and 2. -+ - endmenu - - config SURFACE_HID_CORE -diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile -index 62fc04632d3d..4ae11cf09b25 100644 ---- a/drivers/hid/surface-hid/Makefile -+++ b/drivers/hid/surface-hid/Makefile -@@ -4,3 +4,4 @@ - # - obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o - obj-$(CONFIG_SURFACE_HID) += surface_hid.o -+obj-$(CONFIG_SURFACE_KBD) += surface_kbd.o -diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c -index e4477c328536..3477b31611ae 100644 ---- a/drivers/hid/surface-hid/surface_hid.c -+++ b/drivers/hid/surface-hid/surface_hid.c -@@ -157,15 +157,12 @@ static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, - static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event) - { - struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif); -- int status; - - if (event->command_id != 0x00) - return 0; - -- status = hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], -- event->length, 0); -- -- return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+ hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0); -+ return SSAM_NOTIF_HANDLED; - } - - -diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c -new file mode 100644 -index 000000000000..0635341bc517 ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_kbd.c -@@ -0,0 +1,300 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) HID transport driver for the legacy -+ * keyboard interface (KBD/TC=0x08 subsystem). Provides support for the -+ * integrated HID keyboard on Surface Laptops 1 and 2. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "surface_hid_core.h" -+ -+ -+/* -- SAM interface (KBD). -------------------------------------------------- */ -+ -+#define KBD_FEATURE_REPORT_SIZE 7 /* 6 + report ID */ -+ -+enum surface_kbd_cid { -+ SURFACE_KBD_CID_GET_DESCRIPTOR = 0x00, -+ SURFACE_KBD_CID_SET_CAPSLOCK_LED = 0x01, -+ SURFACE_KBD_CID_EVT_INPUT_GENERIC = 0x03, -+ SURFACE_KBD_CID_EVT_INPUT_HOTKEYS = 0x04, -+ SURFACE_KBD_CID_GET_FEATURE_REPORT = 0x0b, -+}; -+ -+static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ int status; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(entry); -+ rqst.payload = &entry; -+ -+ rsp.capacity = len; -+ rsp.length = 0; -+ rsp.pointer = buf; -+ -+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry)); -+ if (status) -+ return status; -+ -+ if (rsp.length != len) { -+ dev_err(shid->dev, "invalid descriptor length: got %zu, expected, %zu\n", -+ rsp.length, len); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value) -+{ -+ struct ssam_request rqst; -+ u8 value_u8 = value; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = 0; -+ rqst.length = sizeof(value_u8); -+ rqst.payload = &value_u8; -+ -+ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8)); -+} -+ -+static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ u8 payload = 0; -+ int status; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(payload); -+ rqst.payload = &payload; -+ -+ rsp.capacity = len; -+ rsp.length = 0; -+ rsp.pointer = buf; -+ -+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload)); -+ if (status) -+ return status; -+ -+ if (rsp.length != len) { -+ dev_err(shid->dev, "invalid feature report length: got %zu, expected, %zu\n", -+ rsp.length, len); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static bool ssam_kbd_is_input_event(const struct ssam_event *event) -+{ -+ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC) -+ return true; -+ -+ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS) -+ return true; -+ -+ return false; -+} -+ -+static u32 ssam_kbd_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif); -+ -+ /* -+ * Check against device UID manually, as registry and device target -+ * category doesn't line up. -+ */ -+ -+ if (shid->uid.category != event->target_category) -+ return 0; -+ -+ if (shid->uid.target != event->target_id) -+ return 0; -+ -+ if (shid->uid.instance != event->instance_id) -+ return 0; -+ -+ if (!ssam_kbd_is_input_event(event)) -+ return 0; -+ -+ hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+ -+/* -- Transport driver (KBD). ----------------------------------------------- */ -+ -+static int skbd_get_caps_led_value(struct hid_device *hid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ struct hid_field *field; -+ unsigned int offset, size; -+ int i; -+ -+ /* Get LED field. */ -+ field = hidinput_get_led_field(hid); -+ if (!field) -+ return -ENOENT; -+ -+ /* Check if we got the correct report. */ -+ if (len != hid_report_len(field->report)) -+ return -ENOENT; -+ -+ if (rprt_id != field->report->id) -+ return -ENOENT; -+ -+ /* Get caps lock LED index. */ -+ for (i = 0; i < field->report_count; i++) -+ if ((field->usage[i].hid & 0xffff) == 0x02) -+ break; -+ -+ if (i == field->report_count) -+ return -ENOENT; -+ -+ /* Extract value. */ -+ size = field->report_size; -+ offset = field->report_offset + i * size; -+ return !!hid_field_extract(hid, buf + 1, size, offset); -+} -+ -+static int skbd_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int caps_led; -+ int status; -+ -+ caps_led = skbd_get_caps_led_value(shid->hid, rprt_id, buf, len); -+ if (caps_led < 0) -+ return -EIO; /* Only caps LED output reports are supported. */ -+ -+ status = ssam_kbd_set_caps_led(shid, caps_led); -+ if (status < 0) -+ return status; -+ -+ return len; -+} -+ -+static int skbd_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ u8 report[KBD_FEATURE_REPORT_SIZE]; -+ int status; -+ -+ /* -+ * The keyboard only has a single hard-coded read-only feature report -+ * of size KBD_FEATURE_REPORT_SIZE. Try to load it and compare its -+ * report ID against the requested one. -+ */ -+ -+ if (len < ARRAY_SIZE(report)) -+ return -ENOSPC; -+ -+ status = ssam_kbd_get_feature_report(shid, report, ARRAY_SIZE(report)); -+ if (status < 0) -+ return status; -+ -+ if (rprt_id != report[0]) -+ return -ENOENT; -+ -+ memcpy(buf, report, ARRAY_SIZE(report)); -+ return len; -+} -+ -+static int skbd_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ /* Not supported. See skbd_get_feature_report() for details. */ -+ return -EIO; -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int surface_kbd_probe(struct platform_device *pdev) -+{ -+ struct ssam_controller *ctrl; -+ struct surface_hid_device *shid; -+ -+ /* Add device link to EC. */ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL); -+ if (!shid) -+ return -ENOMEM; -+ -+ shid->dev = &pdev->dev; -+ shid->ctrl = ctrl; -+ -+ shid->uid.domain = SSAM_DOMAIN_SERIALHUB; -+ shid->uid.category = SSAM_SSH_TC_KBD; -+ shid->uid.target = 2; -+ shid->uid.instance = 0; -+ shid->uid.function = 0; -+ -+ shid->notif.base.priority = 1; -+ shid->notif.base.fn = ssam_kbd_event_fn; -+ shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ shid->notif.event.id.target_category = shid->uid.category; -+ shid->notif.event.id.instance = shid->uid.instance; -+ shid->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ shid->notif.event.flags = 0; -+ -+ shid->ops.get_descriptor = ssam_kbd_get_descriptor; -+ shid->ops.output_report = skbd_output_report; -+ shid->ops.get_feature_report = skbd_get_feature_report; -+ shid->ops.set_feature_report = skbd_set_feature_report; -+ -+ platform_set_drvdata(pdev, shid); -+ return surface_hid_device_add(shid); -+} -+ -+static int surface_kbd_remove(struct platform_device *pdev) -+{ -+ surface_hid_device_destroy(platform_get_drvdata(pdev)); -+ return 0; -+} -+ -+static const struct acpi_device_id surface_kbd_match[] = { -+ { "MSHW0096" }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_kbd_match); -+ -+static struct platform_driver surface_kbd_driver = { -+ .probe = surface_kbd_probe, -+ .remove = surface_kbd_remove, -+ .driver = { -+ .name = "surface_keyboard", -+ .acpi_match_table = surface_kbd_match, -+ .pm = &surface_hid_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_kbd_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); --- -2.31.1 - -From e5088f3fdddb98aeb0a392b6faffaaf8194ab3bd Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 23 Apr 2021 00:51:22 +0200 -Subject: [PATCH] HID: surface-hid: Fix integer endian conversion - -We want to convert from 16 bit (unsigned) little endian values contained -in a packed struct to CPU native endian values here, not the other way -around. So replace cpu_to_le16() with get_unaligned_le16(), using the -latter instead of le16_to_cpu() to acknowledge that we are reading from -a packed struct. - -Reported-by: kernel test robot -Fixes: b05ff1002a5c ("HID: Add support for Surface Aggregator Module HID transport") -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/hid/surface-hid/surface_hid_core.c | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c -index 7b27ec392232..5571e74abe91 100644 ---- a/drivers/hid/surface-hid/surface_hid_core.c -+++ b/drivers/hid/surface-hid/surface_hid_core.c -@@ -168,9 +168,9 @@ int surface_hid_device_add(struct surface_hid_device *shid) - - shid->hid->dev.parent = shid->dev; - shid->hid->bus = BUS_HOST; -- shid->hid->vendor = cpu_to_le16(shid->attrs.vendor); -- shid->hid->product = cpu_to_le16(shid->attrs.product); -- shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version); -+ shid->hid->vendor = get_unaligned_le16(&shid->attrs.vendor); -+ shid->hid->product = get_unaligned_le16(&shid->attrs.product); -+ shid->hid->version = get_unaligned_le16(&shid->hid_desc.hid_version); - shid->hid->country = shid->hid_desc.country_code; - - snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X", --- -2.31.1 - -From 788ae75abd99fcc4454291e93fbdadf8ef992991 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 12 Feb 2021 21:06:12 +0100 -Subject: [PATCH] power: supply: Add battery driver for Surface Aggregator - Module - -On newer Microsoft Surface models (specifically 7th-generation, i.e. -Surface Pro 7, Surface Book 3, Surface Laptop 3, and Surface Laptop Go), -battery and AC status/information is no longer handled via standard ACPI -devices, but instead directly via the Surface System Aggregator Module -(SSAM), i.e. the embedded controller on those devices. - -While on previous generation models, battery status is also handled via -SSAM, an ACPI shim was present to translate the standard ACPI battery -interface to SSAM requests. The SSAM interface itself, which is modeled -closely after the ACPI interface, has not changed. - -This commit introduces a new SSAM client device driver to support -battery status/information via the aforementioned interface on said -Surface models. It is in parts based on the standard ACPI battery -driver. - -Patchset: surface-sam ---- - MAINTAINERS | 7 + - drivers/power/supply/Kconfig | 16 + - drivers/power/supply/Makefile | 1 + - drivers/power/supply/surface_battery.c | 865 +++++++++++++++++++++++++ - 4 files changed, 889 insertions(+) - create mode 100644 drivers/power/supply/surface_battery.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 2eac975aee50..54a4769114e9 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11785,6 +11785,13 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch] - F: include/linux/cciss*.h - F: include/uapi/linux/cciss*.h - -+MICROSOFT SURFACE BATTERY AND AC DRIVERS -+M: Maximilian Luz -+L: linux-pm@vger.kernel.org -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/power/supply/surface_battery.c -+ - MICROSOFT SURFACE DTX DRIVER - M: Maximilian Luz - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig -index 1699b9269a78..b635331daba4 100644 ---- a/drivers/power/supply/Kconfig -+++ b/drivers/power/supply/Kconfig -@@ -775,4 +775,20 @@ config RN5T618_POWER - This driver can also be built as a module. If so, the module will be - called rn5t618_power. - -+config BATTERY_SURFACE -+ tristate "Battery driver for 7th-generation Microsoft Surface devices" -+ depends on SURFACE_AGGREGATOR_REGISTRY -+ help -+ Driver for battery devices connected via/managed by the Surface System -+ Aggregator Module (SSAM). -+ -+ This driver provides battery-information and -status support for -+ Surface devices where said data is not exposed via the standard ACPI -+ devices. On those models (7th-generation), battery-information is -+ instead handled directly via SSAM client devices and this driver. -+ -+ Say M or Y here to include battery status support for 7th-generation -+ Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, -+ Surface Book 3, and Surface Laptop Go. -+ - endif # POWER_SUPPLY -diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile -index dd4b86318cd9..cddc18994119 100644 ---- a/drivers/power/supply/Makefile -+++ b/drivers/power/supply/Makefile -@@ -98,3 +98,4 @@ obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o - obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o - obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o - obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o -+obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -new file mode 100644 -index 000000000000..1f114f3f3f59 ---- /dev/null -+++ b/drivers/power/supply/surface_battery.c -@@ -0,0 +1,865 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Battery driver for 7th-generation Microsoft Surface devices via Surface -+ * System Aggregator Module (SSAM). -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- SAM interface. -------------------------------------------------------- */ -+ -+enum sam_event_cid_bat { -+ SAM_EVENT_CID_BAT_BIX = 0x15, -+ SAM_EVENT_CID_BAT_BST = 0x16, -+ SAM_EVENT_CID_BAT_ADP = 0x17, -+ SAM_EVENT_CID_BAT_PROT = 0x18, -+ SAM_EVENT_CID_BAT_DPTF = 0x53, -+}; -+ -+enum sam_battery_sta { -+ SAM_BATTERY_STA_OK = 0x0f, -+ SAM_BATTERY_STA_PRESENT = 0x10, -+}; -+ -+enum sam_battery_state { -+ SAM_BATTERY_STATE_DISCHARGING = BIT(0), -+ SAM_BATTERY_STATE_CHARGING = BIT(1), -+ SAM_BATTERY_STATE_CRITICAL = BIT(2), -+}; -+ -+enum sam_battery_power_unit { -+ SAM_BATTERY_POWER_UNIT_mW = 0, -+ SAM_BATTERY_POWER_UNIT_mA = 1, -+}; -+ -+/* Equivalent to data returned in ACPI _BIX method, revision 0. */ -+struct spwr_bix { -+ u8 revision; -+ __le32 power_unit; -+ __le32 design_cap; -+ __le32 last_full_charge_cap; -+ __le32 technology; -+ __le32 design_voltage; -+ __le32 design_cap_warn; -+ __le32 design_cap_low; -+ __le32 cycle_count; -+ __le32 measurement_accuracy; -+ __le32 max_sampling_time; -+ __le32 min_sampling_time; -+ __le32 max_avg_interval; -+ __le32 min_avg_interval; -+ __le32 bat_cap_granularity_1; -+ __le32 bat_cap_granularity_2; -+ __u8 model[21]; -+ __u8 serial[11]; -+ __u8 type[5]; -+ __u8 oem_info[21]; -+} __packed; -+ -+static_assert(sizeof(struct spwr_bix) == 119); -+ -+/* Equivalent to data returned in ACPI _BST method. */ -+struct spwr_bst { -+ __le32 state; -+ __le32 present_rate; -+ __le32 remaining_cap; -+ __le32 present_voltage; -+} __packed; -+ -+static_assert(sizeof(struct spwr_bst) == 16); -+ -+#define SPWR_BIX_REVISION 0 -+#define SPWR_BATTERY_VALUE_UNKNOWN 0xffffffff -+ -+/* Get battery status (_STA) */ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x01, -+}); -+ -+/* Get battery static information (_BIX). */ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bix, struct spwr_bix, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x02, -+}); -+ -+/* Get battery dynamic information (_BST). */ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bst, struct spwr_bst, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x03, -+}); -+ -+/* Set battery trip point (_BTP). */ -+static SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x04, -+}); -+ -+ -+/* -- Device structures. ---------------------------------------------------- */ -+ -+struct spwr_psy_properties { -+ const char *name; -+ struct ssam_event_registry registry; -+}; -+ -+struct spwr_battery_device { -+ struct ssam_device *sdev; -+ -+ char name[32]; -+ struct power_supply *psy; -+ struct power_supply_desc psy_desc; -+ -+ struct delayed_work update_work; -+ -+ struct ssam_event_notifier notif; -+ -+ struct mutex lock; /* Guards access to state data below. */ -+ unsigned long timestamp; -+ -+ __le32 sta; -+ struct spwr_bix bix; -+ struct spwr_bst bst; -+ u32 alarm; -+}; -+ -+ -+/* -- Module parameters. ---------------------------------------------------- */ -+ -+static unsigned int cache_time = 1000; -+module_param(cache_time, uint, 0644); -+MODULE_PARM_DESC(cache_time, "battery state caching time in milliseconds [default: 1000]"); -+ -+ -+/* -- State management. ----------------------------------------------------- */ -+ -+/* -+ * Delay for battery update quirk. See spwr_external_power_changed() below -+ * for more details. -+ */ -+#define SPWR_AC_BAT_UPDATE_DELAY msecs_to_jiffies(5000) -+ -+static bool spwr_battery_present(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT; -+} -+ -+static int spwr_battery_load_sta(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ return ssam_retry(ssam_bat_get_sta, bat->sdev, &bat->sta); -+} -+ -+static int spwr_battery_load_bix(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (!spwr_battery_present(bat)) -+ return 0; -+ -+ status = ssam_retry(ssam_bat_get_bix, bat->sdev, &bat->bix); -+ -+ /* Enforce NULL terminated strings in case anything goes wrong... */ -+ bat->bix.model[ARRAY_SIZE(bat->bix.model) - 1] = 0; -+ bat->bix.serial[ARRAY_SIZE(bat->bix.serial) - 1] = 0; -+ bat->bix.type[ARRAY_SIZE(bat->bix.type) - 1] = 0; -+ bat->bix.oem_info[ARRAY_SIZE(bat->bix.oem_info) - 1] = 0; -+ -+ return status; -+} -+ -+static int spwr_battery_load_bst(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ if (!spwr_battery_present(bat)) -+ return 0; -+ -+ return ssam_retry(ssam_bat_get_bst, bat->sdev, &bat->bst); -+} -+ -+static int spwr_battery_set_alarm_unlocked(struct spwr_battery_device *bat, u32 value) -+{ -+ __le32 value_le = cpu_to_le32(value); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ bat->alarm = value; -+ return ssam_retry(ssam_bat_set_btp, bat->sdev, &value_le); -+} -+ -+static int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, bool cached) -+{ -+ unsigned long cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time); -+ int status; -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (cached && bat->timestamp && time_is_after_jiffies(cache_deadline)) -+ return 0; -+ -+ status = spwr_battery_load_sta(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bst(bat); -+ if (status) -+ return status; -+ -+ bat->timestamp = jiffies; -+ return 0; -+} -+ -+static int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached) -+{ -+ int status; -+ -+ mutex_lock(&bat->lock); -+ status = spwr_battery_update_bst_unlocked(bat, cached); -+ mutex_unlock(&bat->lock); -+ -+ return status; -+} -+ -+static int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ lockdep_assert_held(&bat->lock); -+ -+ status = spwr_battery_load_sta(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bix(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bst(bat); -+ if (status) -+ return status; -+ -+ if (bat->bix.revision != SPWR_BIX_REVISION) -+ dev_warn(&bat->sdev->dev, "unsupported battery revision: %u\n", bat->bix.revision); -+ -+ bat->timestamp = jiffies; -+ return 0; -+} -+ -+static u32 sprw_battery_get_full_cap_safe(struct spwr_battery_device *bat) -+{ -+ u32 full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN) -+ full_cap = get_unaligned_le32(&bat->bix.design_cap); -+ -+ return full_cap; -+} -+ -+static bool spwr_battery_is_full(struct spwr_battery_device *bat) -+{ -+ u32 state = get_unaligned_le32(&bat->bst.state); -+ u32 full_cap = sprw_battery_get_full_cap_safe(bat); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ return full_cap != SPWR_BATTERY_VALUE_UNKNOWN && full_cap != 0 && -+ remaining_cap != SPWR_BATTERY_VALUE_UNKNOWN && -+ remaining_cap >= full_cap && -+ state == 0; -+} -+ -+static int spwr_battery_recheck_full(struct spwr_battery_device *bat) -+{ -+ bool present; -+ u32 unit; -+ int status; -+ -+ mutex_lock(&bat->lock); -+ unit = get_unaligned_le32(&bat->bix.power_unit); -+ present = spwr_battery_present(bat); -+ -+ status = spwr_battery_update_bix_unlocked(bat); -+ if (status) -+ goto out; -+ -+ /* If battery has been attached, (re-)initialize alarm. */ -+ if (!present && spwr_battery_present(bat)) { -+ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); -+ -+ status = spwr_battery_set_alarm_unlocked(bat, cap_warn); -+ if (status) -+ goto out; -+ } -+ -+ /* -+ * Warn if the unit has changed. This is something we genuinely don't -+ * expect to happen, so make this a big warning. If it does, we'll -+ * need to add support for it. -+ */ -+ WARN_ON(unit != get_unaligned_le32(&bat->bix.power_unit)); -+ -+out: -+ mutex_unlock(&bat->lock); -+ -+ if (!status) -+ power_supply_changed(bat->psy); -+ -+ return status; -+} -+ -+static int spwr_battery_recheck_status(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ status = spwr_battery_update_bst(bat, false); -+ if (!status) -+ power_supply_changed(bat->psy); -+ -+ return status; -+} -+ -+static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif); -+ int status; -+ -+ dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", -+ event->command_id, event->instance_id, event->target_id); -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_BAT_BIX: -+ status = spwr_battery_recheck_full(bat); -+ break; -+ -+ case SAM_EVENT_CID_BAT_BST: -+ status = spwr_battery_recheck_status(bat); -+ break; -+ -+ case SAM_EVENT_CID_BAT_PROT: -+ /* -+ * TODO: Implement support for battery protection status change -+ * event. -+ */ -+ status = 0; -+ break; -+ -+ case SAM_EVENT_CID_BAT_DPTF: -+ /* -+ * TODO: Implement support for DPTF event. -+ */ -+ status = 0; -+ break; -+ -+ default: -+ return 0; -+ } -+ -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+} -+ -+static void spwr_battery_update_bst_workfn(struct work_struct *work) -+{ -+ struct delayed_work *dwork = to_delayed_work(work); -+ struct spwr_battery_device *bat; -+ int status; -+ -+ bat = container_of(dwork, struct spwr_battery_device, update_work); -+ -+ status = spwr_battery_update_bst(bat, false); -+ if (status) { -+ dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", status); -+ return; -+ } -+ -+ power_supply_changed(bat->psy); -+} -+ -+static void spwr_external_power_changed(struct power_supply *psy) -+{ -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ -+ /* -+ * Handle battery update quirk: When the battery is fully charged (or -+ * charged up to the limit imposed by the UEFI battery limit) and the -+ * adapter is plugged in or removed, the EC does not send a separate -+ * event for the state (charging/discharging) change. Furthermore it -+ * may take some time until the state is updated on the battery. -+ * Schedule an update to solve this. -+ */ -+ -+ schedule_delayed_work(&bat->update_work, SPWR_AC_BAT_UPDATE_DELAY); -+} -+ -+ -+/* -- Properties. ----------------------------------------------------------- */ -+ -+static const enum power_supply_property spwr_battery_props_chg[] = { -+ POWER_SUPPLY_PROP_STATUS, -+ POWER_SUPPLY_PROP_PRESENT, -+ POWER_SUPPLY_PROP_TECHNOLOGY, -+ POWER_SUPPLY_PROP_CYCLE_COUNT, -+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, -+ POWER_SUPPLY_PROP_VOLTAGE_NOW, -+ POWER_SUPPLY_PROP_CURRENT_NOW, -+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, -+ POWER_SUPPLY_PROP_CHARGE_FULL, -+ POWER_SUPPLY_PROP_CHARGE_NOW, -+ POWER_SUPPLY_PROP_CAPACITY, -+ POWER_SUPPLY_PROP_CAPACITY_LEVEL, -+ POWER_SUPPLY_PROP_MODEL_NAME, -+ POWER_SUPPLY_PROP_MANUFACTURER, -+ POWER_SUPPLY_PROP_SERIAL_NUMBER, -+}; -+ -+static const enum power_supply_property spwr_battery_props_eng[] = { -+ POWER_SUPPLY_PROP_STATUS, -+ POWER_SUPPLY_PROP_PRESENT, -+ POWER_SUPPLY_PROP_TECHNOLOGY, -+ POWER_SUPPLY_PROP_CYCLE_COUNT, -+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, -+ POWER_SUPPLY_PROP_VOLTAGE_NOW, -+ POWER_SUPPLY_PROP_POWER_NOW, -+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, -+ POWER_SUPPLY_PROP_ENERGY_FULL, -+ POWER_SUPPLY_PROP_ENERGY_NOW, -+ POWER_SUPPLY_PROP_CAPACITY, -+ POWER_SUPPLY_PROP_CAPACITY_LEVEL, -+ POWER_SUPPLY_PROP_MODEL_NAME, -+ POWER_SUPPLY_PROP_MANUFACTURER, -+ POWER_SUPPLY_PROP_SERIAL_NUMBER, -+}; -+ -+static int spwr_battery_prop_status(struct spwr_battery_device *bat) -+{ -+ u32 state = get_unaligned_le32(&bat->bst.state); -+ u32 present_rate = get_unaligned_le32(&bat->bst.present_rate); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (state & SAM_BATTERY_STATE_DISCHARGING) -+ return POWER_SUPPLY_STATUS_DISCHARGING; -+ -+ if (state & SAM_BATTERY_STATE_CHARGING) -+ return POWER_SUPPLY_STATUS_CHARGING; -+ -+ if (spwr_battery_is_full(bat)) -+ return POWER_SUPPLY_STATUS_FULL; -+ -+ if (present_rate == 0) -+ return POWER_SUPPLY_STATUS_NOT_CHARGING; -+ -+ return POWER_SUPPLY_STATUS_UNKNOWN; -+} -+ -+static int spwr_battery_prop_technology(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ if (!strcasecmp("NiCd", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_NiCd; -+ -+ if (!strcasecmp("NiMH", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_NiMH; -+ -+ if (!strcasecmp("LION", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_LION; -+ -+ if (!strncasecmp("LI-ION", bat->bix.type, 6)) -+ return POWER_SUPPLY_TECHNOLOGY_LION; -+ -+ if (!strcasecmp("LiP", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_LIPO; -+ -+ return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; -+} -+ -+static int spwr_battery_prop_capacity(struct spwr_battery_device *bat) -+{ -+ u32 full_cap = sprw_battery_get_full_cap_safe(bat); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN) -+ return -ENODATA; -+ -+ if (remaining_cap == SPWR_BATTERY_VALUE_UNKNOWN) -+ return -ENODATA; -+ -+ return remaining_cap * 100 / full_cap; -+} -+ -+static int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat) -+{ -+ u32 state = get_unaligned_le32(&bat->bst.state); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (state & SAM_BATTERY_STATE_CRITICAL) -+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; -+ -+ if (spwr_battery_is_full(bat)) -+ return POWER_SUPPLY_CAPACITY_LEVEL_FULL; -+ -+ if (remaining_cap <= bat->alarm) -+ return POWER_SUPPLY_CAPACITY_LEVEL_LOW; -+ -+ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; -+} -+ -+static int spwr_battery_get_property(struct power_supply *psy, enum power_supply_property psp, -+ union power_supply_propval *val) -+{ -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ u32 value; -+ int status; -+ -+ mutex_lock(&bat->lock); -+ -+ status = spwr_battery_update_bst_unlocked(bat, true); -+ if (status) -+ goto out; -+ -+ /* Abort if battery is not present. */ -+ if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) { -+ status = -ENODEV; -+ goto out; -+ } -+ -+ switch (psp) { -+ case POWER_SUPPLY_PROP_STATUS: -+ val->intval = spwr_battery_prop_status(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_PRESENT: -+ val->intval = spwr_battery_present(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_TECHNOLOGY: -+ val->intval = spwr_battery_prop_technology(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_CYCLE_COUNT: -+ value = get_unaligned_le32(&bat->bix.cycle_count); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: -+ value = get_unaligned_le32(&bat->bix.design_voltage); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_VOLTAGE_NOW: -+ value = get_unaligned_le32(&bat->bst.present_voltage); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CURRENT_NOW: -+ case POWER_SUPPLY_PROP_POWER_NOW: -+ value = get_unaligned_le32(&bat->bst.present_rate); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: -+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: -+ value = get_unaligned_le32(&bat->bix.design_cap); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_FULL: -+ case POWER_SUPPLY_PROP_ENERGY_FULL: -+ value = get_unaligned_le32(&bat->bix.last_full_charge_cap); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_NOW: -+ case POWER_SUPPLY_PROP_ENERGY_NOW: -+ value = get_unaligned_le32(&bat->bst.remaining_cap); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CAPACITY: -+ val->intval = spwr_battery_prop_capacity(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL: -+ val->intval = spwr_battery_prop_capacity_level(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_MODEL_NAME: -+ val->strval = bat->bix.model; -+ break; -+ -+ case POWER_SUPPLY_PROP_MANUFACTURER: -+ val->strval = bat->bix.oem_info; -+ break; -+ -+ case POWER_SUPPLY_PROP_SERIAL_NUMBER: -+ val->strval = bat->bix.serial; -+ break; -+ -+ default: -+ status = -EINVAL; -+ break; -+ } -+ -+out: -+ mutex_unlock(&bat->lock); -+ return status; -+} -+ -+ -+/* -- Alarm attribute. ------------------------------------------------------ */ -+ -+static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct power_supply *psy = dev_get_drvdata(dev); -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ int status; -+ -+ mutex_lock(&bat->lock); -+ status = sysfs_emit(buf, "%d\n", bat->alarm * 1000); -+ mutex_unlock(&bat->lock); -+ -+ return status; -+} -+ -+static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, const char *buf, -+ size_t count) -+{ -+ struct power_supply *psy = dev_get_drvdata(dev); -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ unsigned long value; -+ int status; -+ -+ status = kstrtoul(buf, 0, &value); -+ if (status) -+ return status; -+ -+ mutex_lock(&bat->lock); -+ -+ if (!spwr_battery_present(bat)) { -+ mutex_unlock(&bat->lock); -+ return -ENODEV; -+ } -+ -+ status = spwr_battery_set_alarm_unlocked(bat, value / 1000); -+ if (status) { -+ mutex_unlock(&bat->lock); -+ return status; -+ } -+ -+ mutex_unlock(&bat->lock); -+ return count; -+} -+ -+DEVICE_ATTR_RW(alarm); -+ -+static struct attribute *spwr_battery_attrs[] = { -+ &dev_attr_alarm.attr, -+ NULL, -+}; -+ATTRIBUTE_GROUPS(spwr_battery); -+ -+ -+/* -- Device setup. --------------------------------------------------------- */ -+ -+static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_device *sdev, -+ struct ssam_event_registry registry, const char *name) -+{ -+ mutex_init(&bat->lock); -+ strncpy(bat->name, name, ARRAY_SIZE(bat->name) - 1); -+ -+ bat->sdev = sdev; -+ -+ bat->notif.base.priority = 1; -+ bat->notif.base.fn = spwr_notify_bat; -+ bat->notif.event.reg = registry; -+ bat->notif.event.id.target_category = sdev->uid.category; -+ bat->notif.event.id.instance = 0; -+ bat->notif.event.mask = SSAM_EVENT_MASK_STRICT; -+ bat->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ bat->psy_desc.name = bat->name; -+ bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; -+ bat->psy_desc.get_property = spwr_battery_get_property; -+ -+ INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn); -+} -+ -+static int spwr_battery_register(struct spwr_battery_device *bat) -+{ -+ struct power_supply_config psy_cfg = {}; -+ __le32 sta; -+ int status; -+ -+ /* Make sure the device is there and functioning properly. */ -+ status = ssam_retry(ssam_bat_get_sta, bat->sdev, &sta); -+ if (status) -+ return status; -+ -+ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) -+ return -ENODEV; -+ -+ /* Satisfy lockdep although we are in an exclusive context here. */ -+ mutex_lock(&bat->lock); -+ -+ status = spwr_battery_update_bix_unlocked(bat); -+ if (status) { -+ mutex_unlock(&bat->lock); -+ return status; -+ } -+ -+ if (spwr_battery_present(bat)) { -+ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); -+ -+ status = spwr_battery_set_alarm_unlocked(bat, cap_warn); -+ if (status) { -+ mutex_unlock(&bat->lock); -+ return status; -+ } -+ } -+ -+ mutex_unlock(&bat->lock); -+ -+ bat->psy_desc.external_power_changed = spwr_external_power_changed; -+ -+ switch (get_unaligned_le32(&bat->bix.power_unit)) { -+ case SAM_BATTERY_POWER_UNIT_mW: -+ bat->psy_desc.properties = spwr_battery_props_eng; -+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng); -+ break; -+ -+ case SAM_BATTERY_POWER_UNIT_mA: -+ bat->psy_desc.properties = spwr_battery_props_chg; -+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg); -+ break; -+ -+ default: -+ dev_err(&bat->sdev->dev, "unsupported battery power unit: %u\n", -+ get_unaligned_le32(&bat->bix.power_unit)); -+ return -EINVAL; -+ } -+ -+ psy_cfg.drv_data = bat; -+ psy_cfg.attr_grp = spwr_battery_groups; -+ -+ bat->psy = devm_power_supply_register(&bat->sdev->dev, &bat->psy_desc, &psy_cfg); -+ if (IS_ERR(bat->psy)) -+ return PTR_ERR(bat->psy); -+ -+ return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int __maybe_unused surface_battery_resume(struct device *dev) -+{ -+ return spwr_battery_recheck_full(dev_get_drvdata(dev)); -+} -+SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume); -+ -+static int surface_battery_probe(struct ssam_device *sdev) -+{ -+ const struct spwr_psy_properties *p; -+ struct spwr_battery_device *bat; -+ -+ p = ssam_device_get_match_data(sdev); -+ if (!p) -+ return -ENODEV; -+ -+ bat = devm_kzalloc(&sdev->dev, sizeof(*bat), GFP_KERNEL); -+ if (!bat) -+ return -ENOMEM; -+ -+ spwr_battery_init(bat, sdev, p->registry, p->name); -+ ssam_device_set_drvdata(sdev, bat); -+ -+ return spwr_battery_register(bat); -+} -+ -+static void surface_battery_remove(struct ssam_device *sdev) -+{ -+ struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); -+ -+ ssam_notifier_unregister(sdev->ctrl, &bat->notif); -+ cancel_delayed_work_sync(&bat->update_work); -+} -+ -+static const struct spwr_psy_properties spwr_psy_props_bat1 = { -+ .name = "BAT1", -+ .registry = SSAM_EVENT_REGISTRY_SAM, -+}; -+ -+static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { -+ .name = "BAT2", -+ .registry = SSAM_EVENT_REGISTRY_KIP, -+}; -+ -+static const struct ssam_device_id surface_battery_match[] = { -+ { SSAM_SDEV(BAT, 0x01, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, -+ { SSAM_SDEV(BAT, 0x02, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_battery_match); -+ -+static struct ssam_device_driver surface_battery_driver = { -+ .probe = surface_battery_probe, -+ .remove = surface_battery_remove, -+ .match_table = surface_battery_match, -+ .driver = { -+ .name = "surface_battery", -+ .pm = &surface_battery_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_battery_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Battery driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); --- -2.31.1 - -From d85a4c61565e30b7b13d233da0f5fec6a0751493 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 12 Feb 2021 21:07:17 +0100 -Subject: [PATCH] power: supply: Add AC driver for Surface Aggregator Module - -On newer Microsoft Surface models (specifically 7th-generation, i.e. -Surface Pro 7, Surface Book 3, Surface Laptop 3, and Surface Laptop Go), -battery and AC status/information is no longer handled via standard ACPI -devices, but instead directly via the Surface System Aggregator Module -(SSAM), i.e. the embedded controller on those devices. - -While on previous generation models, AC status is also handled via SSAM, -an ACPI shim was present to translate the standard ACPI AC interface to -SSAM requests. The SSAM interface itself, which is modeled closely after -the ACPI interface, has not changed. - -This commit introduces a new SSAM client device driver to support AC -status/information via the aforementioned interface on said Surface -models. - -Patchset: surface-sam ---- - MAINTAINERS | 1 + - drivers/power/supply/Kconfig | 16 ++ - drivers/power/supply/Makefile | 1 + - drivers/power/supply/surface_charger.c | 282 +++++++++++++++++++++++++ - 4 files changed, 300 insertions(+) - create mode 100644 drivers/power/supply/surface_charger.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 54a4769114e9..3d57740df499 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11791,6 +11791,7 @@ L: linux-pm@vger.kernel.org - L: platform-driver-x86@vger.kernel.org - S: Maintained - F: drivers/power/supply/surface_battery.c -+F: drivers/power/supply/surface_charger.c - - MICROSOFT SURFACE DTX DRIVER - M: Maximilian Luz -diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig -index b635331daba4..d4d756d95778 100644 ---- a/drivers/power/supply/Kconfig -+++ b/drivers/power/supply/Kconfig -@@ -791,4 +791,20 @@ config BATTERY_SURFACE - Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, - Surface Book 3, and Surface Laptop Go. - -+config CHARGER_SURFACE -+ tristate "AC driver for 7th-generation Microsoft Surface devices" -+ depends on SURFACE_AGGREGATOR_REGISTRY -+ help -+ Driver for AC devices connected via/managed by the Surface System -+ Aggregator Module (SSAM). -+ -+ This driver provides AC-information and -status support for Surface -+ devices where said data is not exposed via the standard ACPI devices. -+ On those models (7th-generation), AC-information is instead handled -+ directly via a SSAM client device and this driver. -+ -+ Say M or Y here to include AC status support for 7th-generation -+ Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, -+ Surface Book 3, and Surface Laptop Go. -+ - endif # POWER_SUPPLY -diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile -index cddc18994119..9fdd34956153 100644 ---- a/drivers/power/supply/Makefile -+++ b/drivers/power/supply/Makefile -@@ -99,3 +99,4 @@ obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o - obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o - obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o - obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o -+obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -new file mode 100644 -index 000000000000..e89ffab86e9c ---- /dev/null -+++ b/drivers/power/supply/surface_charger.c -@@ -0,0 +1,282 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * AC driver for 7th-generation Microsoft Surface devices via Surface System -+ * Aggregator Module (SSAM). -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- SAM interface. -------------------------------------------------------- */ -+ -+enum sam_event_cid_bat { -+ SAM_EVENT_CID_BAT_ADP = 0x17, -+}; -+ -+enum sam_battery_sta { -+ SAM_BATTERY_STA_OK = 0x0f, -+ SAM_BATTERY_STA_PRESENT = 0x10, -+}; -+ -+/* Get battery status (_STA). */ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x01, -+}); -+ -+/* Get platform power source for battery (_PSR / DPTF PSRC). */ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x0d, -+}); -+ -+ -+/* -- Device structures. ---------------------------------------------------- */ -+ -+struct spwr_psy_properties { -+ const char *name; -+ struct ssam_event_registry registry; -+}; -+ -+struct spwr_ac_device { -+ struct ssam_device *sdev; -+ -+ char name[32]; -+ struct power_supply *psy; -+ struct power_supply_desc psy_desc; -+ -+ struct ssam_event_notifier notif; -+ -+ struct mutex lock; /* Guards access to state below. */ -+ -+ __le32 state; -+}; -+ -+ -+/* -- State management. ----------------------------------------------------- */ -+ -+static int spwr_ac_update_unlocked(struct spwr_ac_device *ac) -+{ -+ u32 old = ac->state; -+ int status; -+ -+ lockdep_assert_held(&ac->lock); -+ -+ status = ssam_retry(ssam_bat_get_psrc, ac->sdev, &ac->state); -+ if (status < 0) -+ return status; -+ -+ return old != ac->state; -+} -+ -+static int spwr_ac_update(struct spwr_ac_device *ac) -+{ -+ int status; -+ -+ mutex_lock(&ac->lock); -+ status = spwr_ac_update_unlocked(ac); -+ mutex_unlock(&ac->lock); -+ -+ return status; -+} -+ -+static int spwr_ac_recheck(struct spwr_ac_device *ac) -+{ -+ int status; -+ -+ status = spwr_ac_update(ac); -+ if (status > 0) -+ power_supply_changed(ac->psy); -+ -+ return status >= 0 ? 0 : status; -+} -+ -+static u32 spwr_notify_ac(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct spwr_ac_device *ac; -+ int status; -+ -+ ac = container_of(nf, struct spwr_ac_device, notif); -+ -+ dev_dbg(&ac->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", -+ event->command_id, event->instance_id, event->target_id); -+ -+ /* -+ * Allow events of all targets/instances here. Global adapter status -+ * seems to be handled via target=1 and instance=1, but events are -+ * reported on all targets/instances in use. -+ * -+ * While it should be enough to just listen on 1/1, listen everywhere to -+ * make sure we don't miss anything. -+ */ -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_BAT_ADP: -+ status = spwr_ac_recheck(ac); -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+ -+ default: -+ return 0; -+ } -+} -+ -+ -+/* -- Properties. ----------------------------------------------------------- */ -+ -+static const enum power_supply_property spwr_ac_props[] = { -+ POWER_SUPPLY_PROP_ONLINE, -+}; -+ -+static int spwr_ac_get_property(struct power_supply *psy, enum power_supply_property psp, -+ union power_supply_propval *val) -+{ -+ struct spwr_ac_device *ac = power_supply_get_drvdata(psy); -+ int status; -+ -+ mutex_lock(&ac->lock); -+ -+ status = spwr_ac_update_unlocked(ac); -+ if (status) -+ goto out; -+ -+ switch (psp) { -+ case POWER_SUPPLY_PROP_ONLINE: -+ val->intval = !!le32_to_cpu(ac->state); -+ break; -+ -+ default: -+ status = -EINVAL; -+ goto out; -+ } -+ -+out: -+ mutex_unlock(&ac->lock); -+ return status; -+} -+ -+ -+/* -- Device setup. --------------------------------------------------------- */ -+ -+static char *battery_supplied_to[] = { -+ "BAT1", -+ "BAT2", -+}; -+ -+static void spwr_ac_init(struct spwr_ac_device *ac, struct ssam_device *sdev, -+ struct ssam_event_registry registry, const char *name) -+{ -+ mutex_init(&ac->lock); -+ strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1); -+ -+ ac->sdev = sdev; -+ -+ ac->notif.base.priority = 1; -+ ac->notif.base.fn = spwr_notify_ac; -+ ac->notif.event.reg = registry; -+ ac->notif.event.id.target_category = sdev->uid.category; -+ ac->notif.event.id.instance = 0; -+ ac->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ ac->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ ac->psy_desc.name = ac->name; -+ ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; -+ ac->psy_desc.properties = spwr_ac_props; -+ ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props); -+ ac->psy_desc.get_property = spwr_ac_get_property; -+} -+ -+static int spwr_ac_register(struct spwr_ac_device *ac) -+{ -+ struct power_supply_config psy_cfg = {}; -+ __le32 sta; -+ int status; -+ -+ /* Make sure the device is there and functioning properly. */ -+ status = ssam_retry(ssam_bat_get_sta, ac->sdev, &sta); -+ if (status) -+ return status; -+ -+ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) -+ return -ENODEV; -+ -+ psy_cfg.drv_data = ac; -+ psy_cfg.supplied_to = battery_supplied_to; -+ psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); -+ -+ ac->psy = devm_power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg); -+ if (IS_ERR(ac->psy)) -+ return PTR_ERR(ac->psy); -+ -+ return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int __maybe_unused surface_ac_resume(struct device *dev) -+{ -+ return spwr_ac_recheck(dev_get_drvdata(dev)); -+} -+SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); -+ -+static int surface_ac_probe(struct ssam_device *sdev) -+{ -+ const struct spwr_psy_properties *p; -+ struct spwr_ac_device *ac; -+ -+ p = ssam_device_get_match_data(sdev); -+ if (!p) -+ return -ENODEV; -+ -+ ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL); -+ if (!ac) -+ return -ENOMEM; -+ -+ spwr_ac_init(ac, sdev, p->registry, p->name); -+ ssam_device_set_drvdata(sdev, ac); -+ -+ return spwr_ac_register(ac); -+} -+ -+static void surface_ac_remove(struct ssam_device *sdev) -+{ -+ struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); -+ -+ ssam_notifier_unregister(sdev->ctrl, &ac->notif); -+} -+ -+static const struct spwr_psy_properties spwr_psy_props_adp1 = { -+ .name = "ADP1", -+ .registry = SSAM_EVENT_REGISTRY_SAM, -+}; -+ -+static const struct ssam_device_id surface_ac_match[] = { -+ { SSAM_SDEV(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_ac_match); -+ -+static struct ssam_device_driver surface_ac_driver = { -+ .probe = surface_ac_probe, -+ .remove = surface_ac_remove, -+ .match_table = surface_ac_match, -+ .driver = { -+ .name = "surface_ac", -+ .pm = &surface_ac_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_ac_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("AC driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); --- -2.31.1 - -From 71d96bd3e0abe9ec6edc6dc2eb15750bedf14c53 Mon Sep 17 00:00:00 2001 -From: Qiheng Lin -Date: Sat, 10 Apr 2021 12:12:46 +0800 -Subject: [PATCH] power: supply: surface-battery: Make some symbols static - -The sparse tool complains as follows: - -drivers/power/supply/surface_battery.c:700:1: warning: - symbol 'dev_attr_alarm' was not declared. Should it be static? -drivers/power/supply/surface_battery.c:805:1: warning: - symbol 'surface_battery_pm_ops' was not declared. Should it be static? - -This symbol is not used outside of surface_battery.c, so this -commit marks it static. - -Reported-by: Hulk Robot -Signed-off-by: Qiheng Lin -Acked-by: Maximilian Luz -Signed-off-by: Sebastian Reichel -Patchset: surface-sam ---- - drivers/power/supply/surface_battery.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -index 1f114f3f3f59..41342f4534f6 100644 ---- a/drivers/power/supply/surface_battery.c -+++ b/drivers/power/supply/surface_battery.c -@@ -697,7 +697,7 @@ static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, co - return count; - } - --DEVICE_ATTR_RW(alarm); -+static DEVICE_ATTR_RW(alarm); - - static struct attribute *spwr_battery_attrs[] = { - &dev_attr_alarm.attr, -@@ -802,7 +802,7 @@ static int __maybe_unused surface_battery_resume(struct device *dev) - { - return spwr_battery_recheck_full(dev_get_drvdata(dev)); - } --SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume); -+static SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume); - - static int surface_battery_probe(struct ssam_device *sdev) - { --- -2.31.1 - -From bcc95ef1e29eb1395f64ba2aa8c108d569f681a2 Mon Sep 17 00:00:00 2001 -From: Qiheng Lin -Date: Sat, 10 Apr 2021 12:12:49 +0800 -Subject: [PATCH] power: supply: surface-charger: Make symbol - 'surface_ac_pm_ops' static - -The sparse tool complains as follows: - -drivers/power/supply/surface_charger.c:229:1: warning: - symbol 'surface_ac_pm_ops' was not declared. Should it be static? - -This symbol is not used outside of surface_charger.c, so this -commit marks it static. - -Reported-by: Hulk Robot -Signed-off-by: Qiheng Lin -Acked-by: Maximilian Luz -Signed-off-by: Sebastian Reichel -Patchset: surface-sam ---- - drivers/power/supply/surface_charger.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -index e89ffab86e9c..367f63116d35 100644 ---- a/drivers/power/supply/surface_charger.c -+++ b/drivers/power/supply/surface_charger.c -@@ -226,7 +226,7 @@ static int __maybe_unused surface_ac_resume(struct device *dev) - { - return spwr_ac_recheck(dev_get_drvdata(dev)); - } --SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); -+static SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); - - static int surface_ac_probe(struct ssam_device *sdev) - { --- -2.31.1 - -From 0db9ae71709092e1281ba961ed8533abb47aef4a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 4 May 2021 20:00:46 +0200 -Subject: [PATCH] power: supply: surface_battery: Fix battery event handling - -The battery subsystem of the Surface Aggregator Module EC requires us to -register the battery notifier with instance ID 0. However, battery -events are actually sent with the instance ID corresponding to the -device, which is nonzero. Thus, the strict-matching approach doesn't -work here and will discard events that the driver is expected to handle. - -To fix this we have to fall back on notifier matching by target-category -only and have to manually check the instance ID in the notifier -callback. - -Fixes: 167f77f7d0b3 ("power: supply: Add battery driver for Surface Aggregator Module") -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/power/supply/surface_battery.c | 14 ++++++++++++-- - 1 file changed, 12 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -index 41342f4534f6..3844146b06b8 100644 ---- a/drivers/power/supply/surface_battery.c -+++ b/drivers/power/supply/surface_battery.c -@@ -345,6 +345,16 @@ static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_eve - struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif); - int status; - -+ /* -+ * We cannot use strict matching when registering the notifier as the -+ * EC expects us to register it against instance ID 0. Strict matching -+ * would thus drop events, as those may have non-zero instance IDs in -+ * this subsystem. So we need to check the instance ID of the event -+ * here manually. -+ */ -+ if (event->instance_id != bat->sdev->uid.instance) -+ return 0; -+ - dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", - event->command_id, event->instance_id, event->target_id); - -@@ -720,8 +730,8 @@ static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_devic - bat->notif.base.fn = spwr_notify_bat; - bat->notif.event.reg = registry; - bat->notif.event.id.target_category = sdev->uid.category; -- bat->notif.event.id.instance = 0; -- bat->notif.event.mask = SSAM_EVENT_MASK_STRICT; -+ bat->notif.event.id.instance = 0; /* need to register with instance 0 */ -+ bat->notif.event.mask = SSAM_EVENT_MASK_TARGET; - bat->notif.event.flags = SSAM_EVENT_SEQUENCED; - - bat->psy_desc.name = bat->name; --- -2.31.1 - -From 91ad06698342f3f927fccc406a0c0773f78648a5 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 11 May 2021 11:24:21 +0200 -Subject: [PATCH] power: supply: surface-charger: Fix type of integer variable - -The ac->state field is __le32, not u32. So change the variable we're -temporarily storing it in to __le32 as well. - -Reported-by: kernel test robot -Fixes: e61ffb344591 ("power: supply: Add AC driver for Surface Aggregator Module") -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/power/supply/surface_charger.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -index 367f63116d35..d2e1e7215e7b 100644 ---- a/drivers/power/supply/surface_charger.c -+++ b/drivers/power/supply/surface_charger.c -@@ -66,7 +66,7 @@ struct spwr_ac_device { - - static int spwr_ac_update_unlocked(struct spwr_ac_device *ac) - { -- u32 old = ac->state; -+ __le32 old = ac->state; - int status; - - lockdep_assert_held(&ac->lock); --- -2.31.1 - -From acad9b2913ef523cca0b59548ea4220286f9e18a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 13 Feb 2021 19:58:50 +0100 -Subject: [PATCH] platform/surface: Add performance mode driver - -Add driver to support performance mode on Surface devices with Surface -Aggregator Module. - -Intended to be replaced by a platform profile driver in 5.12. - -Patchset: surface-sam ---- - drivers/platform/surface/Kconfig | 17 +++ - drivers/platform/surface/Makefile | 1 + - drivers/platform/surface/surface_perfmode.c | 122 ++++++++++++++++++++ - 3 files changed, 140 insertions(+) - create mode 100644 drivers/platform/surface/surface_perfmode.c - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index dea313989b4c..3ceeb316d56e 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -140,6 +140,23 @@ config SURFACE_GPE - accordingly. It is required on those devices to allow wake-ups from - suspend by opening the lid. - -+config SURFACE_PERFMODE -+ tristate "Surface Performance-Mode Driver" -+ depends on SURFACE_AGGREGATOR_BUS -+ depends on SYSFS -+ help -+ Driver for the performance-/cooling-mode interface of Microsoft -+ Surface devices. -+ -+ Microsoft Surface devices using the Surface System Aggregator Module -+ (SSAM) can be switched between different performance modes. This, -+ depending on the device, can influence their cooling behavior and may -+ influence power limits, allowing users to choose between performance -+ and higher power-draw, or lower power-draw and more silent operation. -+ -+ This driver provides a user-space interface (via sysfs) for -+ controlling said mode via the corresponding client device. -+ - config SURFACE_PRO3_BUTTON - tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet" - depends on INPUT -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 19b661e274c3..31098983decc 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -14,4 +14,5 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o -+obj-$(CONFIG_SURFACE_PERFMODE) += surface_perfmode.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o -diff --git a/drivers/platform/surface/surface_perfmode.c b/drivers/platform/surface/surface_perfmode.c -new file mode 100644 -index 000000000000..3b92a43f8606 ---- /dev/null -+++ b/drivers/platform/surface/surface_perfmode.c -@@ -0,0 +1,122 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface performance-mode driver. -+ * -+ * Provides a user-space interface for the performance mode control provided -+ * by the Surface System Aggregator Module (SSAM), influencing cooling -+ * behavior of the device and potentially managing power limits. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+enum sam_perf_mode { -+ SAM_PERF_MODE_NORMAL = 1, -+ SAM_PERF_MODE_BATTERY = 2, -+ SAM_PERF_MODE_PERF1 = 3, -+ SAM_PERF_MODE_PERF2 = 4, -+ -+ __SAM_PERF_MODE__MIN = 1, -+ __SAM_PERF_MODE__MAX = 4, -+}; -+ -+struct ssam_perf_info { -+ __le32 mode; -+ __le16 unknown1; -+ __le16 unknown2; -+} __packed; -+ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_tmp_perf_mode_get, struct ssam_perf_info, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .command_id = 0x02, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_perf_mode_set, __le32, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .command_id = 0x03, -+}); -+ -+static int ssam_tmp_perf_mode_set(struct ssam_device *sdev, u32 mode) -+{ -+ __le32 mode_le = cpu_to_le32(mode); -+ -+ if (mode < __SAM_PERF_MODE__MIN || mode > __SAM_PERF_MODE__MAX) -+ return -EINVAL; -+ -+ return ssam_retry(__ssam_tmp_perf_mode_set, sdev, &mode_le); -+} -+ -+static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, -+ char *data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ struct ssam_perf_info info; -+ int status; -+ -+ status = ssam_retry(ssam_tmp_perf_mode_get, sdev, &info); -+ if (status) { -+ dev_err(dev, "failed to get current performance mode: %d\n", -+ status); -+ return -EIO; -+ } -+ -+ return sprintf(data, "%d\n", le32_to_cpu(info.mode)); -+} -+ -+static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr, -+ const char *data, size_t count) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ int perf_mode; -+ int status; -+ -+ status = kstrtoint(data, 0, &perf_mode); -+ if (status < 0) -+ return status; -+ -+ status = ssam_tmp_perf_mode_set(sdev, perf_mode); -+ if (status < 0) -+ return status; -+ -+ return count; -+} -+ -+static const DEVICE_ATTR_RW(perf_mode); -+ -+static int surface_sam_sid_perfmode_probe(struct ssam_device *sdev) -+{ -+ return sysfs_create_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); -+} -+ -+static void surface_sam_sid_perfmode_remove(struct ssam_device *sdev) -+{ -+ sysfs_remove_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); -+} -+ -+static const struct ssam_device_id ssam_perfmode_match[] = { -+ { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, ssam_perfmode_match); -+ -+static struct ssam_device_driver surface_sam_sid_perfmode = { -+ .probe = surface_sam_sid_perfmode_probe, -+ .remove = surface_sam_sid_perfmode_remove, -+ .match_table = ssam_perfmode_match, -+ .driver = { -+ .name = "surface_performance_mode", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_sam_sid_perfmode); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Performance mode interface for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); --- -2.31.1 - -From 7e0d3e45893cd314dbf4a476b1e1ce62da2e3528 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 4 Mar 2021 20:05:24 +0100 -Subject: [PATCH] platform/surface: aggregator: Make SSAM_DEFINE_SYNC_REQUEST_x - define static functions - -The SSAM_DEFINE_SYNC_REQUEST_x() macros are intended to reduce -boiler-plate code for SSAM request definitions by defining a wrapper -function for the specified request. The client device variants of those -macros, i.e. SSAM_DEFINE_SYNC_REQUEST_CL_x() in particular rely on the -multi-device (MD) variants, e.g.: - - #define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \ - SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \ - int name(struct ssam_device *sdev, rtype *ret) \ - { \ - return __raw_##name(sdev->ctrl, sdev->uid.target, \ - sdev->uid.instance, ret); \ - } - -This now creates the problem that it is not possible to declare the -generated functions static via - - static SSAM_DEFINE_SYNC_REQUEST_CL_R(...) - -as this will only apply to the function defined by the multi-device -macro, i.e. SSAM_DEFINE_SYNC_REQUEST_MD_R(). Thus compiling with -`-Wmissing-prototypes' rightfully complains that there is a 'static' -keyword missing. - -To solve this, make all SSAM_DEFINE_SYNC_REQUEST_x() macros define -static functions. Non-client-device macros are also changed for -consistency. In general, we expect those functions to be only used -locally in the respective drivers for the corresponding interfaces, so -having to define a wrapper function to be able to export this should be -the odd case out. - -Reported-by: kernel test robot -Fixes: b78b4982d763 ("platform/surface: Add platform profile driver") -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210304190524.1172197-1-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../driver-api/surface_aggregator/client.rst | 4 +- - .../platform/surface/aggregator/controller.c | 10 +-- - .../surface/surface_aggregator_registry.c | 2 +- - drivers/platform/surface/surface_dtx.c | 18 ++--- - drivers/platform/surface/surface_perfmode.c | 4 +- - drivers/power/supply/surface_battery.c | 8 +- - drivers/power/supply/surface_charger.c | 4 +- - include/linux/surface_aggregator/controller.h | 74 +++++++++---------- - include/linux/surface_aggregator/device.h | 31 ++++---- - 9 files changed, 78 insertions(+), 77 deletions(-) - -diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst -index 26d13085a117..e519d374c378 100644 ---- a/Documentation/driver-api/surface_aggregator/client.rst -+++ b/Documentation/driver-api/surface_aggregator/client.rst -@@ -248,7 +248,7 @@ This example defines a function - - .. code-block:: c - -- int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg); -+ static int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg); - - executing the specified request, with the controller passed in when calling - said function. In this example, the argument is provided via the ``arg`` -@@ -296,7 +296,7 @@ This invocation of the macro defines a function - - .. code-block:: c - -- int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret); -+ static int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret); - - executing the specified request, using the device IDs and controller given - in the client device. The full list of such macros for client devices is: -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index 5bcb59ed579d..aa6f37b4f46e 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -1750,35 +1750,35 @@ EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer); - - /* -- Internal SAM requests. ------------------------------------------------ */ - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { - .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, - .command_id = 0x13, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { - .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, - .command_id = 0x15, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { - .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, - .command_id = 0x16, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { - .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, - .command_id = 0x33, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { - .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, - .command_id = 0x34, -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index e618bdd5cf30..4428c4330229 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -286,7 +286,7 @@ struct ssam_base_hub { - struct ssam_event_notifier notif; - }; - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, - .command_id = 0x0d, -diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c -index 2591b875b016..1fedacf74050 100644 ---- a/drivers/platform/surface/surface_dtx.c -+++ b/drivers/platform/surface/surface_dtx.c -@@ -69,63 +69,63 @@ struct ssam_bas_base_info { - - static_assert(sizeof(struct ssam_bas_base_info) == 2); - --static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, { -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, - .command_id = 0x06, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, { -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, - .command_id = 0x07, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, { -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, - .command_id = 0x08, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, { -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, - .command_id = 0x09, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, { -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, - .command_id = 0x0a, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, { -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, - .command_id = 0x0b, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, - .command_id = 0x0c, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, - .command_id = 0x0d, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, - .command_id = 0x11, -diff --git a/drivers/platform/surface/surface_perfmode.c b/drivers/platform/surface/surface_perfmode.c -index 3b92a43f8606..a9114e001d0d 100644 ---- a/drivers/platform/surface/surface_perfmode.c -+++ b/drivers/platform/surface/surface_perfmode.c -@@ -33,12 +33,12 @@ struct ssam_perf_info { - __le16 unknown2; - } __packed; - --static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_tmp_perf_mode_get, struct ssam_perf_info, { -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_tmp_perf_mode_get, struct ssam_perf_info, { - .target_category = SSAM_SSH_TC_TMP, - .command_id = 0x02, - }); - --static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_perf_mode_set, __le32, { -+SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_perf_mode_set, __le32, { - .target_category = SSAM_SSH_TC_TMP, - .command_id = 0x03, - }); -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -index 3844146b06b8..5ec2e6bb2465 100644 ---- a/drivers/power/supply/surface_battery.c -+++ b/drivers/power/supply/surface_battery.c -@@ -85,25 +85,25 @@ static_assert(sizeof(struct spwr_bst) == 16); - #define SPWR_BATTERY_VALUE_UNKNOWN 0xffffffff - - /* Get battery status (_STA) */ --static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { - .target_category = SSAM_SSH_TC_BAT, - .command_id = 0x01, - }); - - /* Get battery static information (_BIX). */ --static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bix, struct spwr_bix, { -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bix, struct spwr_bix, { - .target_category = SSAM_SSH_TC_BAT, - .command_id = 0x02, - }); - - /* Get battery dynamic information (_BST). */ --static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bst, struct spwr_bst, { -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bst, struct spwr_bst, { - .target_category = SSAM_SSH_TC_BAT, - .command_id = 0x03, - }); - - /* Set battery trip point (_BTP). */ --static SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, { -+SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, { - .target_category = SSAM_SSH_TC_BAT, - .command_id = 0x04, - }); -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -index d2e1e7215e7b..a060c36c7766 100644 ---- a/drivers/power/supply/surface_charger.c -+++ b/drivers/power/supply/surface_charger.c -@@ -28,13 +28,13 @@ enum sam_battery_sta { - }; - - /* Get battery status (_STA). */ --static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { - .target_category = SSAM_SSH_TC_BAT, - .command_id = 0x01, - }); - - /* Get platform power source for battery (_PSR / DPTF PSRC). */ --static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, { -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, { - .target_category = SSAM_SSH_TC_BAT, - .command_id = 0x0d, - }); -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index f4b1ba887384..0806796eabcb 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -344,16 +344,16 @@ struct ssam_request_spec_md { - * request has been fully completed. The required transport buffer will be - * allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_controller -- * *ctrl)``, returning the status of the request, which is zero on success and -- * negative on failure. The ``ctrl`` parameter is the controller via which the -- * request is being sent. -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl)``, returning the status of the request, which is -+ * zero on success and negative on failure. The ``ctrl`` parameter is the -+ * controller via which the request is being sent. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_N(name, spec...) \ -- int name(struct ssam_controller *ctrl) \ -+ static int name(struct ssam_controller *ctrl) \ - { \ - struct ssam_request_spec s = (struct ssam_request_spec)spec; \ - struct ssam_request rqst; \ -@@ -383,17 +383,17 @@ struct ssam_request_spec_md { - * returning once the request has been fully completed. The required transport - * buffer will be allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_controller -- * *ctrl, const atype *arg)``, returning the status of the request, which is -- * zero on success and negative on failure. The ``ctrl`` parameter is the -- * controller via which the request is sent. The request argument is specified -- * via the ``arg`` pointer. -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, const atype *arg)``, returning the status of the -+ * request, which is zero on success and negative on failure. The ``ctrl`` -+ * parameter is the controller via which the request is sent. The request -+ * argument is specified via the ``arg`` pointer. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_W(name, atype, spec...) \ -- int name(struct ssam_controller *ctrl, const atype *arg) \ -+ static int name(struct ssam_controller *ctrl, const atype *arg) \ - { \ - struct ssam_request_spec s = (struct ssam_request_spec)spec; \ - struct ssam_request rqst; \ -@@ -424,17 +424,17 @@ struct ssam_request_spec_md { - * request itself, returning once the request has been fully completed. The - * required transport buffer will be allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_controller -- * *ctrl, rtype *ret)``, returning the status of the request, which is zero on -- * success and negative on failure. The ``ctrl`` parameter is the controller -- * via which the request is sent. The request's return value is written to the -- * memory pointed to by the ``ret`` parameter. -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, rtype *ret)``, returning the status of the request, -+ * which is zero on success and negative on failure. The ``ctrl`` parameter is -+ * the controller via which the request is sent. The request's return value is -+ * written to the memory pointed to by the ``ret`` parameter. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_R(name, rtype, spec...) \ -- int name(struct ssam_controller *ctrl, rtype *ret) \ -+ static int name(struct ssam_controller *ctrl, rtype *ret) \ - { \ - struct ssam_request_spec s = (struct ssam_request_spec)spec; \ - struct ssam_request rqst; \ -@@ -483,17 +483,17 @@ struct ssam_request_spec_md { - * returning once the request has been fully completed. The required transport - * buffer will be allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_controller -- * *ctrl, u8 tid, u8 iid)``, returning the status of the request, which is -- * zero on success and negative on failure. The ``ctrl`` parameter is the -- * controller via which the request is sent, ``tid`` the target ID for the -- * request, and ``iid`` the instance ID. -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, u8 tid, u8 iid)``, returning the status of the -+ * request, which is zero on success and negative on failure. The ``ctrl`` -+ * parameter is the controller via which the request is sent, ``tid`` the -+ * target ID for the request, and ``iid`` the instance ID. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_MD_N(name, spec...) \ -- int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \ -+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \ - { \ - struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ - struct ssam_request rqst; \ -@@ -524,18 +524,18 @@ struct ssam_request_spec_md { - * the request itself, returning once the request has been fully completed. - * The required transport buffer will be allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_controller -- * *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the status of the -- * request, which is zero on success and negative on failure. The ``ctrl`` -- * parameter is the controller via which the request is sent, ``tid`` the -- * target ID for the request, and ``iid`` the instance ID. The request argument -- * is specified via the ``arg`` pointer. -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the -+ * status of the request, which is zero on success and negative on failure. -+ * The ``ctrl`` parameter is the controller via which the request is sent, -+ * ``tid`` the target ID for the request, and ``iid`` the instance ID. The -+ * request argument is specified via the ``arg`` pointer. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_MD_W(name, atype, spec...) \ -- int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)\ -+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg) \ - { \ - struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ - struct ssam_request rqst; \ -@@ -567,18 +567,18 @@ struct ssam_request_spec_md { - * execution of the request itself, returning once the request has been fully - * completed. The required transport buffer will be allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_controller -- * *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status of the request, -- * which is zero on success and negative on failure. The ``ctrl`` parameter is -- * the controller via which the request is sent, ``tid`` the target ID for the -- * request, and ``iid`` the instance ID. The request's return value is written -- * to the memory pointed to by the ``ret`` parameter. -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status -+ * of the request, which is zero on success and negative on failure. The -+ * ``ctrl`` parameter is the controller via which the request is sent, ``tid`` -+ * the target ID for the request, and ``iid`` the instance ID. The request's -+ * return value is written to the memory pointed to by the ``ret`` parameter. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_MD_R(name, rtype, spec...) \ -- int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \ -+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \ - { \ - struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ - struct ssam_request rqst; \ -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 02f3e06c0a60..4441ad667c3f 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -336,17 +336,18 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); - * request has been fully completed. The required transport buffer will be - * allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_device *sdev)``, -- * returning the status of the request, which is zero on success and negative -- * on failure. The ``sdev`` parameter specifies both the target device of the -- * request and by association the controller via which the request is sent. -+ * The generated function is defined as ``static int name(struct ssam_device -+ * *sdev)``, returning the status of the request, which is zero on success and -+ * negative on failure. The ``sdev`` parameter specifies both the target -+ * device of the request and by association the controller via which the -+ * request is sent. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_CL_N(name, spec...) \ - SSAM_DEFINE_SYNC_REQUEST_MD_N(__raw_##name, spec) \ -- int name(struct ssam_device *sdev) \ -+ static int name(struct ssam_device *sdev) \ - { \ - return __raw_##name(sdev->ctrl, sdev->uid.target, \ - sdev->uid.instance); \ -@@ -368,19 +369,19 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); - * itself, returning once the request has been fully completed. The required - * transport buffer will be allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_device *sdev, -- * const atype *arg)``, returning the status of the request, which is zero on -- * success and negative on failure. The ``sdev`` parameter specifies both the -- * target device of the request and by association the controller via which -- * the request is sent. The request's argument is specified via the ``arg`` -- * pointer. -+ * The generated function is defined as ``static int name(struct ssam_device -+ * *sdev, const atype *arg)``, returning the status of the request, which is -+ * zero on success and negative on failure. The ``sdev`` parameter specifies -+ * both the target device of the request and by association the controller via -+ * which the request is sent. The request's argument is specified via the -+ * ``arg`` pointer. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_CL_W(name, atype, spec...) \ - SSAM_DEFINE_SYNC_REQUEST_MD_W(__raw_##name, atype, spec) \ -- int name(struct ssam_device *sdev, const atype *arg) \ -+ static int name(struct ssam_device *sdev, const atype *arg) \ - { \ - return __raw_##name(sdev->ctrl, sdev->uid.target, \ - sdev->uid.instance, arg); \ -@@ -402,8 +403,8 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); - * itself, returning once the request has been fully completed. The required - * transport buffer will be allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_device *sdev, -- * rtype *ret)``, returning the status of the request, which is zero on -+ * The generated function is defined as ``static int name(struct ssam_device -+ * *sdev, rtype *ret)``, returning the status of the request, which is zero on - * success and negative on failure. The ``sdev`` parameter specifies both the - * target device of the request and by association the controller via which - * the request is sent. The request's return value is written to the memory -@@ -414,7 +415,7 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); - */ - #define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \ - SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \ -- int name(struct ssam_device *sdev, rtype *ret) \ -+ static int name(struct ssam_device *sdev, rtype *ret) \ - { \ - return __raw_##name(sdev->ctrl, sdev->uid.target, \ - sdev->uid.instance, ret); \ --- -2.31.1 - -From f4ab3e524ede5f191dd0cc9f375b5b54d2e021a1 Mon Sep 17 00:00:00 2001 -From: Dan Carpenter -Date: Tue, 20 Apr 2021 11:44:02 +0300 -Subject: [PATCH] platform/surface: aggregator: fix a bit test - -The "funcs" variable is a u64. If "func" is more than 31 then the -BIT() shift will wrap instead of testing the high bits. - -Fixes: c167b9c7e3d6 ("platform/surface: Add Surface Aggregator subsystem") -Reported-by: kernel test robot -Signed-off-by: Dan Carpenter -Reviewed-by: Maximilian Luz -Link: https://lore.kernel.org/r/YH6UUhJhGk3mk13b@mwanda -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/controller.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index aa6f37b4f46e..88ec47cae5bf 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -1040,7 +1040,7 @@ static int ssam_dsm_load_u32(acpi_handle handle, u64 funcs, u64 func, u32 *ret) - union acpi_object *obj; - u64 val; - -- if (!(funcs & BIT(func))) -+ if (!(funcs & BIT_ULL(func))) - return 0; /* Not supported, leave *ret at its default value */ - - obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, --- -2.31.1 - -From 8374040b7fb4bf763811b311e7cb79a474d57b64 Mon Sep 17 00:00:00 2001 -From: Arnd Bergmann -Date: Fri, 14 May 2021 22:04:36 +0200 -Subject: [PATCH] platform/surface: aggregator: avoid clang - -Wconstant-conversion warning - -Clang complains about the assignment of SSAM_ANY_IID to -ssam_device_uid->instance: - -drivers/platform/surface/surface_aggregator_registry.c:478:25: error: implicit conversion from 'int' to '__u8' (aka 'unsigned char') changes value from 65535 to 255 [-Werror,-Wconstant-conversion] - { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) }, - ~ ^~~~~~~~~~~~ -include/linux/surface_aggregator/device.h:71:23: note: expanded from macro 'SSAM_ANY_IID' - #define SSAM_ANY_IID 0xffff - ^~~~~~ -include/linux/surface_aggregator/device.h:126:63: note: expanded from macro 'SSAM_VDEV' - SSAM_DEVICE(SSAM_DOMAIN_VIRTUAL, SSAM_VIRTUAL_TC_##cat, tid, iid, fun) - ^~~ -include/linux/surface_aggregator/device.h:102:41: note: expanded from macro 'SSAM_DEVICE' - .instance = ((iid) != SSAM_ANY_IID) ? (iid) : 0, \ - ^~~ - -The assignment doesn't actually happen, but clang checks the type limits -before checking whether this assignment is reached. Replace the ?: -operator with a __builtin_choose_expr() invocation that avoids the -warning for the untaken part. - -Fixes: eb0e90a82098 ("platform/surface: aggregator: Add dedicated bus and device type") -Cc: platform-driver-x86@vger.kernel.org -Signed-off-by: Arnd Bergmann -Reviewed-by: Nathan Chancellor -Reviewed-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210514200453.1542978-1-arnd@kernel.org -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - include/linux/surface_aggregator/device.h | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 4441ad667c3f..6ff9c58b3e17 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -98,9 +98,9 @@ struct ssam_device_uid { - | (((fun) != SSAM_ANY_FUN) ? SSAM_MATCH_FUNCTION : 0), \ - .domain = d, \ - .category = cat, \ -- .target = ((tid) != SSAM_ANY_TID) ? (tid) : 0, \ -- .instance = ((iid) != SSAM_ANY_IID) ? (iid) : 0, \ -- .function = ((fun) != SSAM_ANY_FUN) ? (fun) : 0 \ -+ .target = __builtin_choose_expr((tid) != SSAM_ANY_TID, (tid), 0), \ -+ .instance = __builtin_choose_expr((iid) != SSAM_ANY_IID, (iid), 0), \ -+ .function = __builtin_choose_expr((fun) != SSAM_ANY_FUN, (fun), 0) - - /** - * SSAM_VDEV() - Initialize a &struct ssam_device_id as virtual device with --- -2.31.1 - -From 0b97c75b52a67b4a1bd6164e74a8b5415446300e Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 13 May 2021 15:44:37 +0200 -Subject: [PATCH] platform/surface: dtx: Fix poll function - -The poll function should not return -ERESTARTSYS. - -Furthermore, locking in this function is completely unnecessary. The -ddev->lock protects access to the main device and controller (ddev->dev -and ddev->ctrl), ensuring that both are and remain valid while being -accessed by clients. Both are, however, never accessed in the poll -function. The shutdown test (via atomic bit flags) be safely done -without locking, so drop locking here entirely. - -Reported-by: kernel test robot -Fixes: 1d609992832e ("platform/surface: Add DTX driver) -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210513134437.2431022-1-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_dtx.c | 8 +------- - 1 file changed, 1 insertion(+), 7 deletions(-) - -diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c -index 1fedacf74050..f486fabf9319 100644 ---- a/drivers/platform/surface/surface_dtx.c -+++ b/drivers/platform/surface/surface_dtx.c -@@ -527,20 +527,14 @@ static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt - struct sdtx_client *client = file->private_data; - __poll_t events = 0; - -- if (down_read_killable(&client->ddev->lock)) -- return -ERESTARTSYS; -- -- if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) { -- up_read(&client->ddev->lock); -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) - return EPOLLHUP | EPOLLERR; -- } - - poll_wait(file, &client->ddev->waitq, pt); - - if (!kfifo_is_empty(&client->buffer)) - events |= EPOLLIN | EPOLLRDNORM; - -- up_read(&client->ddev->lock); - return events; - } - --- -2.31.1 - -From f183c082a394c8c5687f3eb72d5917cb9a9ce5cf Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 5 May 2021 18:17:04 +0200 -Subject: [PATCH] serial: 8250_dw: Add device HID for new AMD UART controller - -Add device HID AMDI0022 to the AMD UART controller driver match table -and create a platform device for it. This controller can be found on -Microsoft Surface Laptop 4 devices and seems similar enough that we can -just copy the existing AMDI0020 entries. - -Cc: # 5.10+ -Tested-by: Sachi King -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/acpi/acpi_apd.c | 1 + - drivers/tty/serial/8250/8250_dw.c | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/drivers/acpi/acpi_apd.c b/drivers/acpi/acpi_apd.c -index 39359ce0eb2c..645e82a66bb0 100644 ---- a/drivers/acpi/acpi_apd.c -+++ b/drivers/acpi/acpi_apd.c -@@ -226,6 +226,7 @@ static const struct acpi_device_id acpi_apd_device_ids[] = { - { "AMDI0010", APD_ADDR(wt_i2c_desc) }, - { "AMD0020", APD_ADDR(cz_uart_desc) }, - { "AMDI0020", APD_ADDR(cz_uart_desc) }, -+ { "AMDI0022", APD_ADDR(cz_uart_desc) }, - { "AMD0030", }, - { "AMD0040", APD_ADDR(fch_misc_desc)}, - { "HYGO0010", APD_ADDR(wt_i2c_desc) }, -diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c -index 9e204f9b799a..a3a0154da567 100644 ---- a/drivers/tty/serial/8250/8250_dw.c -+++ b/drivers/tty/serial/8250/8250_dw.c -@@ -714,6 +714,7 @@ static const struct acpi_device_id dw8250_acpi_match[] = { - { "APMC0D08", 0}, - { "AMD0020", 0 }, - { "AMDI0020", 0 }, -+ { "AMDI0022", 0 }, - { "BRCM2032", 0 }, - { "HISI0031", 0 }, - { }, --- -2.31.1 - -From c612c3e4d2068da47b2fdbb1495f4856bf2b1233 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 5 May 2021 18:22:04 +0200 -Subject: [PATCH] pinctrl/amd: Add device HID for new AMD GPIO controller - -Add device HID AMDI0031 to the AMD GPIO controller driver match table. -This controller can be found on Microsoft Surface Laptop 4 devices and -seems similar enough that we can just copy the existing AMDI0030 entry. - -Cc: # 5.10+ -Tested-by: Sachi King -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/pinctrl/pinctrl-amd.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/drivers/pinctrl/pinctrl-amd.c b/drivers/pinctrl/pinctrl-amd.c -index 2d4acf21117c..c5950a3b4e4c 100644 ---- a/drivers/pinctrl/pinctrl-amd.c -+++ b/drivers/pinctrl/pinctrl-amd.c -@@ -991,6 +991,7 @@ static int amd_gpio_remove(struct platform_device *pdev) - static const struct acpi_device_id amd_gpio_acpi_match[] = { - { "AMD0030", 0 }, - { "AMDI0030", 0}, -+ { "AMDI0031", 0}, - { }, - }; - MODULE_DEVICE_TABLE(acpi, amd_gpio_acpi_match); --- -2.31.1 - -From 4b9b94ecf40ac57a487fdd4ec58e3ca76b5fd65d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 10 May 2021 23:33:17 +0200 -Subject: [PATCH] x86/i8259: Work around buggy legacy PIC - -The legacy PIC on the AMD variant of the Microsoft Surface Laptop 4 has -some problems on boot. For some reason it consistently does not respond -on the first try, requiring a couple more tries before it finally -responds. - -This currently leads to the PIC not being properly recognized, which -prevents interrupt handling down the line. Ultimately, this also leads -to the pinctrl-amd driver failing to probe due to platform_get_irq() -returning -EINVAL for its base IRQ. That, in turn, means that several -interrupts are not available and device drivers relying on those will -defer probing indefinitely, as querying those interrupts returns --EPROBE_DEFER. - -Add a quirk table and a retry-loop to work around that. - -Also switch to pr_info() due to complaints by checkpatch and add a -pr_fmt() definition for completeness. - -Cc: # 5.10+ -Co-developed-by: Sachi King -Signed-off-by: Sachi King -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - arch/x86/kernel/i8259.c | 51 +++++++++++++++++++++++++++++++++++++---- - 1 file changed, 46 insertions(+), 5 deletions(-) - -diff --git a/arch/x86/kernel/i8259.c b/arch/x86/kernel/i8259.c -index 282b4ee1339f..0da757c6b292 100644 ---- a/arch/x86/kernel/i8259.c -+++ b/arch/x86/kernel/i8259.c -@@ -1,4 +1,7 @@ - // SPDX-License-Identifier: GPL-2.0 -+ -+#define pr_fmt(fmt) "i8259: " fmt -+ - #include - #include - #include -@@ -16,6 +19,7 @@ - #include - #include - #include -+#include - - #include - #include -@@ -298,11 +302,39 @@ static void unmask_8259A(void) - raw_spin_unlock_irqrestore(&i8259A_lock, flags); - } - -+/* -+ * DMI table to identify devices with quirky probe behavior. See comment in -+ * probe_8259A() for more details. -+ */ -+static const struct dmi_system_id retry_probe_quirk_table[] = { -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") -+ }, -+ }, -+ {} -+}; -+ - static int probe_8259A(void) - { - unsigned long flags; - unsigned char probe_val = ~(1 << PIC_CASCADE_IR); - unsigned char new_val; -+ unsigned int i, imax = 1; -+ -+ /* -+ * Some systems have a legacy PIC that doesn't immediately respond -+ * after boot. We know it's there, we know it should respond and is -+ * required for proper interrupt handling later on, so let's try a -+ * couple of times. -+ */ -+ if (dmi_check_system(retry_probe_quirk_table)) { -+ pr_warn("system with broken legacy PIC detected, re-trying multiple times if necessary\n"); -+ imax = 10; -+ } -+ - /* - * Check to see if we have a PIC. - * Mask all except the cascade and read -@@ -312,15 +344,24 @@ static int probe_8259A(void) - */ - raw_spin_lock_irqsave(&i8259A_lock, flags); - -- outb(0xff, PIC_SLAVE_IMR); /* mask all of 8259A-2 */ -- outb(probe_val, PIC_MASTER_IMR); -- new_val = inb(PIC_MASTER_IMR); -- if (new_val != probe_val) { -- printk(KERN_INFO "Using NULL legacy PIC\n"); -+ for (i = 0; i < imax; i++) { -+ outb(0xff, PIC_SLAVE_IMR); /* mask all of 8259A-2 */ -+ outb(probe_val, PIC_MASTER_IMR); -+ new_val = inb(PIC_MASTER_IMR); -+ if (new_val == probe_val) -+ break; -+ } -+ -+ if (i == imax) { -+ pr_info("using NULL legacy PIC\n"); - legacy_pic = &null_legacy_pic; - } - - raw_spin_unlock_irqrestore(&i8259A_lock, flags); -+ -+ if (imax > 1 && i < imax) -+ pr_info("got legacy PIC after %d tries\n", i + 1); -+ - return nr_legacy_irqs(); - } - --- -2.31.1 - diff --git a/patches/5.11/0006-surface-hotplug.patch b/patches/5.11/0006-surface-hotplug.patch deleted file mode 100644 index fa527b454..000000000 --- a/patches/5.11/0006-surface-hotplug.patch +++ /dev/null @@ -1,386 +0,0 @@ -From cd52292158d7d66780db63a93606d7ba6a84e96d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 5 Feb 2021 02:26:57 +0100 -Subject: [PATCH] platform/surface: Add Surface Hot-Plug driver - -Some Surface Book 2 and 3 models have a discrete GPU (dGPU) that is -hot-pluggable. On those devices, the dGPU is contained in the base, -which can be separated from the tablet part (containing CPU and -touchscreen) while the device is running. - -It (in general) is presented as/behaves like a standard PCIe hot-plug -capable device, however, this device can also be put into D3cold. In -D3cold, the device itself is turned off and can thus not submit any -standard PCIe hot-plug events. To properly detect hot-(un)plugging while -the dGPU is in D3cold, out-of-band signaling is required. Without this, -the device state will only get updated during the next bus-check, eg. -via a manually issued lspci call. - -This commit adds a driver to handle out-of-band PCIe hot-(un)plug events -on Microsoft Surface devices. On those devices, said events can be -detected via GPIO interrupts, which are then forwarded to the -corresponding ACPI DSM calls by this driver. The DSM then takes care of -issuing the appropriate bus-/device-check, causing the PCI core to -properly pick up the device change. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210205012657.1951753-1-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-hotplug ---- - MAINTAINERS | 6 + - drivers/platform/surface/Kconfig | 19 ++ - drivers/platform/surface/Makefile | 1 + - drivers/platform/surface/surface_hotplug.c | 282 +++++++++++++++++++++ - 4 files changed, 308 insertions(+) - create mode 100644 drivers/platform/surface/surface_hotplug.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 3d57740df499..d1b36e222cd1 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11823,6 +11823,12 @@ L: platform-driver-x86@vger.kernel.org - S: Maintained - F: drivers/hid/surface-hid/ - -+MICROSOFT SURFACE HOT-PLUG DRIVER -+M: Maximilian Luz -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/platform/surface/surface_hotplug.c -+ - MICROSOFT SURFACE PRO 3 BUTTON DRIVER - M: Chen Yu - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 3ceeb316d56e..2784a480f310 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -140,6 +140,25 @@ config SURFACE_GPE - accordingly. It is required on those devices to allow wake-ups from - suspend by opening the lid. - -+config SURFACE_HOTPLUG -+ tristate "Surface Hot-Plug Driver" -+ depends on GPIOLIB -+ help -+ Driver for out-of-band hot-plug event signaling on Microsoft Surface -+ devices with hot-pluggable PCIe cards. -+ -+ This driver is used on Surface Book (2 and 3) devices with a -+ hot-pluggable discrete GPU (dGPU). When not in use, the dGPU on those -+ devices can enter D3cold, which prevents in-band (standard) PCIe -+ hot-plug signaling. Thus, without this driver, detaching the base -+ containing the dGPU will not correctly update the state of the -+ corresponding PCIe device if it is in D3cold. This driver adds support -+ for out-of-band hot-plug notifications, ensuring that the device state -+ is properly updated even when the device in question is in D3cold. -+ -+ Select M or Y here, if you want to (fully) support hot-plugging of -+ dGPU devices on the Surface Book 2 and/or 3 during D3cold. -+ - config SURFACE_PERFMODE - tristate "Surface Performance-Mode Driver" - depends on SURFACE_AGGREGATOR_BUS -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 31098983decc..a103fdb3ef1a 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -14,5 +14,6 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o -+obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o - obj-$(CONFIG_SURFACE_PERFMODE) += surface_perfmode.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o -diff --git a/drivers/platform/surface/surface_hotplug.c b/drivers/platform/surface/surface_hotplug.c -new file mode 100644 -index 000000000000..cfcc15cfbacb ---- /dev/null -+++ b/drivers/platform/surface/surface_hotplug.c -@@ -0,0 +1,282 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface Book (2 and later) hot-plug driver. -+ * -+ * Surface Book devices (can) have a hot-pluggable discrete GPU (dGPU). This -+ * driver is responsible for out-of-band hot-plug event signaling on these -+ * devices. It is specifically required when the hot-plug device is in D3cold -+ * and can thus not generate PCIe hot-plug events itself. -+ * -+ * Event signaling is handled via ACPI, which will generate the appropriate -+ * device-check notifications to be picked up by the PCIe hot-plug driver. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+static const struct acpi_gpio_params shps_base_presence_int = { 0, 0, false }; -+static const struct acpi_gpio_params shps_base_presence = { 1, 0, false }; -+static const struct acpi_gpio_params shps_device_power_int = { 2, 0, false }; -+static const struct acpi_gpio_params shps_device_power = { 3, 0, false }; -+static const struct acpi_gpio_params shps_device_presence_int = { 4, 0, false }; -+static const struct acpi_gpio_params shps_device_presence = { 5, 0, false }; -+ -+static const struct acpi_gpio_mapping shps_acpi_gpios[] = { -+ { "base_presence-int-gpio", &shps_base_presence_int, 1 }, -+ { "base_presence-gpio", &shps_base_presence, 1 }, -+ { "device_power-int-gpio", &shps_device_power_int, 1 }, -+ { "device_power-gpio", &shps_device_power, 1 }, -+ { "device_presence-int-gpio", &shps_device_presence_int, 1 }, -+ { "device_presence-gpio", &shps_device_presence, 1 }, -+ { }, -+}; -+ -+/* 5515a847-ed55-4b27-8352-cd320e10360a */ -+static const guid_t shps_dsm_guid = -+ GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, 0x32, 0x0e, 0x10, 0x36, 0x0a); -+ -+#define SHPS_DSM_REVISION 1 -+ -+enum shps_dsm_fn { -+ SHPS_DSM_FN_PCI_NUM_ENTRIES = 0x01, -+ SHPS_DSM_FN_PCI_GET_ENTRIES = 0x02, -+ SHPS_DSM_FN_IRQ_BASE_PRESENCE = 0x03, -+ SHPS_DSM_FN_IRQ_DEVICE_POWER = 0x04, -+ SHPS_DSM_FN_IRQ_DEVICE_PRESENCE = 0x05, -+}; -+ -+enum shps_irq_type { -+ /* NOTE: Must be in order of enum shps_dsm_fn above. */ -+ SHPS_IRQ_TYPE_BASE_PRESENCE = 0, -+ SHPS_IRQ_TYPE_DEVICE_POWER = 1, -+ SHPS_IRQ_TYPE_DEVICE_PRESENCE = 2, -+ SHPS_NUM_IRQS, -+}; -+ -+static const char *const shps_gpio_names[] = { -+ [SHPS_IRQ_TYPE_BASE_PRESENCE] = "base_presence", -+ [SHPS_IRQ_TYPE_DEVICE_POWER] = "device_power", -+ [SHPS_IRQ_TYPE_DEVICE_PRESENCE] = "device_presence", -+}; -+ -+struct shps_device { -+ struct mutex lock[SHPS_NUM_IRQS]; /* Protects update in shps_dsm_notify_irq() */ -+ struct gpio_desc *gpio[SHPS_NUM_IRQS]; -+ unsigned int irq[SHPS_NUM_IRQS]; -+}; -+ -+#define SHPS_IRQ_NOT_PRESENT ((unsigned int)-1) -+ -+static enum shps_dsm_fn shps_dsm_fn_for_irq(enum shps_irq_type type) -+{ -+ return SHPS_DSM_FN_IRQ_BASE_PRESENCE + type; -+} -+ -+static void shps_dsm_notify_irq(struct platform_device *pdev, enum shps_irq_type type) -+{ -+ struct shps_device *sdev = platform_get_drvdata(pdev); -+ acpi_handle handle = ACPI_HANDLE(&pdev->dev); -+ union acpi_object *result; -+ union acpi_object param; -+ int value; -+ -+ mutex_lock(&sdev->lock[type]); -+ -+ value = gpiod_get_value_cansleep(sdev->gpio[type]); -+ if (value < 0) { -+ mutex_unlock(&sdev->lock[type]); -+ dev_err(&pdev->dev, "failed to get gpio: %d (irq=%d)\n", type, value); -+ return; -+ } -+ -+ dev_dbg(&pdev->dev, "IRQ notification via DSM (irq=%d, value=%d)\n", type, value); -+ -+ param.type = ACPI_TYPE_INTEGER; -+ param.integer.value = value; -+ -+ result = acpi_evaluate_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, -+ shps_dsm_fn_for_irq(type), ¶m); -+ -+ if (!result) { -+ dev_err(&pdev->dev, "IRQ notification via DSM failed (irq=%d, gpio=%d)\n", -+ type, value); -+ -+ } else if (result->type != ACPI_TYPE_BUFFER) { -+ dev_err(&pdev->dev, -+ "IRQ notification via DSM failed: unexpected result type (irq=%d, gpio=%d)\n", -+ type, value); -+ -+ } else if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) { -+ dev_err(&pdev->dev, -+ "IRQ notification via DSM failed: unexpected result value (irq=%d, gpio=%d)\n", -+ type, value); -+ } -+ -+ mutex_unlock(&sdev->lock[type]); -+ -+ if (result) -+ ACPI_FREE(result); -+} -+ -+static irqreturn_t shps_handle_irq(int irq, void *data) -+{ -+ struct platform_device *pdev = data; -+ struct shps_device *sdev = platform_get_drvdata(pdev); -+ int type; -+ -+ /* Figure out which IRQ we're handling. */ -+ for (type = 0; type < SHPS_NUM_IRQS; type++) -+ if (irq == sdev->irq[type]) -+ break; -+ -+ /* We should have found our interrupt, if not: this is a bug. */ -+ if (WARN(type >= SHPS_NUM_IRQS, "invalid IRQ number: %d\n", irq)) -+ return IRQ_HANDLED; -+ -+ /* Forward interrupt to ACPI via DSM. */ -+ shps_dsm_notify_irq(pdev, type); -+ return IRQ_HANDLED; -+} -+ -+static int shps_setup_irq(struct platform_device *pdev, enum shps_irq_type type) -+{ -+ unsigned long flags = IRQF_ONESHOT | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING; -+ struct shps_device *sdev = platform_get_drvdata(pdev); -+ struct gpio_desc *gpiod; -+ acpi_handle handle = ACPI_HANDLE(&pdev->dev); -+ const char *irq_name; -+ const int dsm = shps_dsm_fn_for_irq(type); -+ int status, irq; -+ -+ /* -+ * Only set up interrupts that we actually need: The Surface Book 3 -+ * does not have a DSM for base presence, so don't set up an interrupt -+ * for that. -+ */ -+ if (!acpi_check_dsm(handle, &shps_dsm_guid, SHPS_DSM_REVISION, BIT(dsm))) { -+ dev_dbg(&pdev->dev, "IRQ notification via DSM not present (irq=%d)\n", type); -+ return 0; -+ } -+ -+ gpiod = devm_gpiod_get(&pdev->dev, shps_gpio_names[type], GPIOD_ASIS); -+ if (IS_ERR(gpiod)) -+ return PTR_ERR(gpiod); -+ -+ irq = gpiod_to_irq(gpiod); -+ if (irq < 0) -+ return irq; -+ -+ irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "shps-irq-%d", type); -+ if (!irq_name) -+ return -ENOMEM; -+ -+ status = devm_request_threaded_irq(&pdev->dev, irq, NULL, shps_handle_irq, -+ flags, irq_name, pdev); -+ if (status) -+ return status; -+ -+ dev_dbg(&pdev->dev, "set up irq %d as type %d\n", irq, type); -+ -+ sdev->gpio[type] = gpiod; -+ sdev->irq[type] = irq; -+ -+ return 0; -+} -+ -+static int surface_hotplug_remove(struct platform_device *pdev) -+{ -+ struct shps_device *sdev = platform_get_drvdata(pdev); -+ int i; -+ -+ /* Ensure that IRQs have been fully handled and won't trigger any more. */ -+ for (i = 0; i < SHPS_NUM_IRQS; i++) { -+ if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT) -+ disable_irq(sdev->irq[i]); -+ -+ mutex_destroy(&sdev->lock[i]); -+ } -+ -+ return 0; -+} -+ -+static int surface_hotplug_probe(struct platform_device *pdev) -+{ -+ struct shps_device *sdev; -+ int status, i; -+ -+ /* -+ * The MSHW0153 device is also present on the Surface Laptop 3, -+ * however that doesn't have a hot-pluggable PCIe device. It also -+ * doesn't have any GPIO interrupts/pins under the MSHW0153, so filter -+ * it out here. -+ */ -+ if (gpiod_count(&pdev->dev, NULL) < 0) -+ return -ENODEV; -+ -+ status = devm_acpi_dev_add_driver_gpios(&pdev->dev, shps_acpi_gpios); -+ if (status) -+ return status; -+ -+ sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL); -+ if (!sdev) -+ return -ENOMEM; -+ -+ platform_set_drvdata(pdev, sdev); -+ -+ /* -+ * Initialize IRQs so that we can safely call surface_hotplug_remove() -+ * on errors. -+ */ -+ for (i = 0; i < SHPS_NUM_IRQS; i++) -+ sdev->irq[i] = SHPS_IRQ_NOT_PRESENT; -+ -+ /* Set up IRQs. */ -+ for (i = 0; i < SHPS_NUM_IRQS; i++) { -+ mutex_init(&sdev->lock[i]); -+ -+ status = shps_setup_irq(pdev, i); -+ if (status) { -+ dev_err(&pdev->dev, "failed to set up IRQ %d: %d\n", i, status); -+ goto err; -+ } -+ } -+ -+ /* Ensure everything is up-to-date. */ -+ for (i = 0; i < SHPS_NUM_IRQS; i++) -+ if (sdev->irq[i] != SHPS_IRQ_NOT_PRESENT) -+ shps_dsm_notify_irq(pdev, i); -+ -+ return 0; -+ -+err: -+ surface_hotplug_remove(pdev); -+ return status; -+} -+ -+static const struct acpi_device_id surface_hotplug_acpi_match[] = { -+ { "MSHW0153", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_hotplug_acpi_match); -+ -+static struct platform_driver surface_hotplug_driver = { -+ .probe = surface_hotplug_probe, -+ .remove = surface_hotplug_remove, -+ .driver = { -+ .name = "surface_hotplug", -+ .acpi_match_table = surface_hotplug_acpi_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_hotplug_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Hot-Plug Signaling Driver for Surface Book Devices"); -+MODULE_LICENSE("GPL"); --- -2.31.1 - diff --git a/patches/5.11/0007-surface-typecover.patch b/patches/5.11/0007-surface-typecover.patch deleted file mode 100644 index 902b7c81d..000000000 --- a/patches/5.11/0007-surface-typecover.patch +++ /dev/null @@ -1,233 +0,0 @@ -From ebb46023cfc40d73efe0fdf42f2e072728c9de6c Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 5 Nov 2020 13:09:45 +0100 -Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when - suspending - -The Type Cover for Microsoft Surface devices supports a special usb -control request to disable or enable the built-in keyboard backlight. -On Windows, this request happens when putting the device into suspend or -resuming it, without it the backlight of the Type Cover will remain -enabled for some time even though the computer is suspended, which looks -weird to the user. - -So add support for this special usb control request to hid-multitouch, -which is the driver that's handling the Type Cover. - -The reason we have to use a pm_notifier for this instead of the usual -suspend/resume methods is that those won't get called in case the usb -device is already autosuspended. - -Also, if the device is autosuspended, we have to briefly autoresume it -in order to send the request. Doing that should be fine, the usb-core -driver does something similar during suspend inside choose_wakeup(). - -To make sure we don't send that request to every device but only to -devices which support it, add a new quirk -MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk -is only enabled for the usb id of the Surface Pro 2017 Type Cover, which -is where I confirmed that it's working. - -Patchset: surface-typecover ---- - drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- - 1 file changed, 98 insertions(+), 2 deletions(-) - -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index 8429ebe7097e..44d48e8bbe1a 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -34,7 +34,10 @@ - #include - #include - #include -+#include - #include -+#include -+#include - #include - #include - #include -@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); - MODULE_LICENSE("GPL"); - - #include "hid-ids.h" -+#include "usbhid/usbhid.h" - - /* quirks to control the device */ - #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) -@@ -70,12 +74,15 @@ MODULE_LICENSE("GPL"); - #define MT_QUIRK_WIN8_PTP_BUTTONS BIT(18) - #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) - #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) -+#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(21) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 - - #define MT_BUTTONTYPE_CLICKPAD 0 - -+#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+ - enum latency_mode { - HID_LATENCY_NORMAL = 0, - HID_LATENCY_HIGH = 1, -@@ -167,6 +174,8 @@ struct mt_device { - - struct list_head applications; - struct list_head reports; -+ -+ struct notifier_block pm_notifier; - }; - - static void mt_post_parse_default_settings(struct mt_device *td, -@@ -208,6 +217,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); - #define MT_CLS_GOOGLE 0x0111 - #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 - #define MT_CLS_SMART_TECH 0x0113 -+#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 - - #define MT_DEFAULT_MAXCONTACT 10 - #define MT_MAX_MAXCONTACT 250 -@@ -367,6 +377,16 @@ static const struct mt_class mt_classes[] = { - MT_QUIRK_CONTACT_CNT_ACCURATE | - MT_QUIRK_SEPARATE_APP_REPORT, - }, -+ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_ALWAYS_VALID | -+ MT_QUIRK_IGNORE_DUPLICATES | -+ MT_QUIRK_HOVERING | -+ MT_QUIRK_CONTACT_CNT_ACCURATE | -+ MT_QUIRK_STICKY_FINGERS | -+ MT_QUIRK_WIN8_PTP_BUTTONS, -+ .export_all_inputs = true -+ }, - { } - }; - -@@ -1674,6 +1694,69 @@ static void mt_expired_timeout(struct timer_list *t) - clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - -+static void get_type_cover_backlight_field(struct hid_device *hdev, -+ struct hid_field **field) -+{ -+ struct hid_report_enum *rep_enum; -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid -+ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { -+ *field = cur_field; -+ return; -+ } -+ } -+ } -+ } -+} -+ -+static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) -+{ -+ struct usb_device *udev = hid_to_usb_dev(hdev); -+ struct hid_field *field = NULL; -+ -+ /* Wake up the device in case it's already suspended */ -+ pm_runtime_get_sync(&udev->dev); -+ -+ get_type_cover_backlight_field(hdev, &field); -+ if (!field) { -+ hid_err(hdev, "couldn't find backlight field\n"); -+ goto out; -+ } -+ -+ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; -+ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); -+ -+out: -+ pm_runtime_put_sync(&udev->dev); -+} -+ -+static int mt_pm_notifier(struct notifier_block *notifier, -+ unsigned long pm_event, -+ void *unused) -+{ -+ struct mt_device *td = -+ container_of(notifier, struct mt_device, pm_notifier); -+ struct hid_device *hdev = td->hdev; -+ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { -+ if (pm_event == PM_SUSPEND_PREPARE) -+ update_keyboard_backlight(hdev, 0); -+ else if (pm_event == PM_POST_SUSPEND) -+ update_keyboard_backlight(hdev, 1); -+ } -+ -+ return NOTIFY_DONE; -+} -+ - static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - { - int ret, i; -@@ -1697,6 +1780,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; - hid_set_drvdata(hdev, td); - -+ td->pm_notifier.notifier_call = mt_pm_notifier; -+ register_pm_notifier(&td->pm_notifier); -+ - INIT_LIST_HEAD(&td->applications); - INIT_LIST_HEAD(&td->reports); - -@@ -1726,15 +1812,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - timer_setup(&td->release_timer, mt_expired_timeout, 0); - - ret = hid_parse(hdev); -- if (ret != 0) -+ if (ret != 0) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) - mt_fix_const_fields(hdev, HID_DG_CONTACTID); - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); -- if (ret) -+ if (ret) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); - if (ret) -@@ -1770,6 +1860,7 @@ static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); - -+ unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); - - sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); -@@ -2121,6 +2212,11 @@ static const struct hid_device_id mt_devices[] = { - MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, - USB_DEVICE_ID_XIROKU_CSR2) }, - -+ /* Microsoft Surface type cover */ -+ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, -+ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, -+ - /* Google MT devices */ - { .driver_data = MT_CLS_GOOGLE, - HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, --- -2.31.1 - diff --git a/patches/5.11/0008-surface-sensors.patch b/patches/5.11/0008-surface-sensors.patch deleted file mode 100644 index cc26718e2..000000000 --- a/patches/5.11/0008-surface-sensors.patch +++ /dev/null @@ -1,53 +0,0 @@ -From e6521f48502703660d850535e61356da31f4d058 Mon Sep 17 00:00:00 2001 -From: Max Leiter -Date: Sat, 19 Dec 2020 17:50:55 -0800 -Subject: [PATCH] iio:light:apds9960 add detection for MSHW0184 ACPI device in - apds9960 driver - -The device is used in the Microsoft Surface Book 3 and Surface Pro 7 - -Signed-off-by: Max Leiter -Reviewed-by: Matt Ranostay -Link: https://lore.kernel.org/r/20201220015057.107246-1-maxwell.leiter@gmail.com -Signed-off-by: Jonathan Cameron -Patchset: surface-sensors ---- - drivers/iio/light/apds9960.c | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/drivers/iio/light/apds9960.c b/drivers/iio/light/apds9960.c -index 547e7f9d6920..df0647856e5d 100644 ---- a/drivers/iio/light/apds9960.c -+++ b/drivers/iio/light/apds9960.c -@@ -8,6 +8,7 @@ - * TODO: gesture + proximity calib offsets - */ - -+#include - #include - #include - #include -@@ -1113,6 +1114,12 @@ static const struct i2c_device_id apds9960_id[] = { - }; - MODULE_DEVICE_TABLE(i2c, apds9960_id); - -+static const struct acpi_device_id apds9960_acpi_match[] = { -+ { "MSHW0184" }, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, apds9960_acpi_match); -+ - static const struct of_device_id apds9960_of_match[] = { - { .compatible = "avago,apds9960" }, - { } -@@ -1124,6 +1131,7 @@ static struct i2c_driver apds9960_driver = { - .name = APDS9960_DRV_NAME, - .of_match_table = apds9960_of_match, - .pm = &apds9960_pm_ops, -+ .acpi_match_table = apds9960_acpi_match, - }, - .probe = apds9960_probe, - .remove = apds9960_remove, --- -2.31.1 - diff --git a/patches/5.11/0009-cameras.patch b/patches/5.11/0009-cameras.patch deleted file mode 100644 index 86b13355f..000000000 --- a/patches/5.11/0009-cameras.patch +++ /dev/null @@ -1,11127 +0,0 @@ -From 0f79e1d515c2d93ba8d1dac9d2e4dcc88995ffd7 Mon Sep 17 00:00:00 2001 -From: Andy Shevchenko -Date: Wed, 30 Dec 2020 22:44:05 +0200 -Subject: [PATCH] media: ipu3-cio2: Add headers that ipu3-cio2.h is direct user - of - -Add headers that ipu3-cio2.h is direct user of. - -Signed-off-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Reviewed-by: Daniel Scally -Tested-by: Daniel Scally -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2.h | 18 ++++++++++++++++++ - 1 file changed, 18 insertions(+) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.h b/drivers/media/pci/intel/ipu3/ipu3-cio2.h -index ccf0b85ae36f..62187ab5ae43 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2.h -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.h -@@ -4,8 +4,26 @@ - #ifndef __IPU3_CIO2_H - #define __IPU3_CIO2_H - -+#include -+#include -+#include -+#include - #include - -+#include -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+struct cio2_fbpt_entry; /* defined here, after the first usage */ -+struct pci_dev; -+ - #define CIO2_NAME "ipu3-cio2" - #define CIO2_DEVICE_NAME "Intel IPU3 CIO2" - #define CIO2_ENTITY_NAME "ipu3-csi2" --- -2.31.1 - -From a341370af7deeb47fee9ece241790c14a7e36682 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 24 Oct 2020 22:42:28 +0100 -Subject: [PATCH] device property: Return true in fwnode_device_is_available - for NULL ops - -Some types of fwnode_handle do not implement the device_is_available() -check, such as those created by software_nodes. There isn't really a -meaningful way to check for the availability of a device that doesn't -actually exist, so if the check isn't implemented just assume that the -"device" is present. - -Suggested-by: Laurent Pinchart -Reviewed-by: Laurent Pinchart -Reviewed-by: Andy Shevchenko -Acked-by: Sakari Ailus -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/base/property.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/base/property.c b/drivers/base/property.c -index 35b95c6ac0c6..0bf5260f14c6 100644 ---- a/drivers/base/property.c -+++ b/drivers/base/property.c -@@ -837,9 +837,15 @@ EXPORT_SYMBOL_GPL(fwnode_handle_put); - /** - * fwnode_device_is_available - check if a device is available for use - * @fwnode: Pointer to the fwnode of the device. -+ * -+ * For fwnode node types that don't implement the .device_is_available() -+ * operation, this function returns true. - */ - bool fwnode_device_is_available(const struct fwnode_handle *fwnode) - { -+ if (!fwnode_has_op(fwnode, device_is_available)) -+ return true; -+ - return fwnode_call_bool_op(fwnode, device_is_available); - } - EXPORT_SYMBOL_GPL(fwnode_device_is_available); --- -2.31.1 - -From 2295235fb4009d54dae3c2b5d5bed2f071cc9adc Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 21 Nov 2020 22:06:38 +0000 -Subject: [PATCH] device property: Call fwnode_graph_get_endpoint_by_id() for - fwnode->secondary - -This function is used to find fwnode endpoints against a device. In -some instances those endpoints are software nodes which are children of -fwnode->secondary. Add support to fwnode_graph_get_endpoint_by_id() to -find those endpoints by recursively calling itself passing the ptr to -fwnode->secondary in the event no endpoint is found for the primary. - -Reviewed-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Acked-by: Sakari Ailus -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/base/property.c | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/drivers/base/property.c b/drivers/base/property.c -index 0bf5260f14c6..1421e9548857 100644 ---- a/drivers/base/property.c -+++ b/drivers/base/property.c -@@ -1215,7 +1215,14 @@ fwnode_graph_get_endpoint_by_id(const struct fwnode_handle *fwnode, - best_ep_id = fwnode_ep.id; - } - -- return best_ep; -+ if (best_ep) -+ return best_ep; -+ -+ if (fwnode && !IS_ERR_OR_NULL(fwnode->secondary)) -+ return fwnode_graph_get_endpoint_by_id(fwnode->secondary, port, -+ endpoint, flags); -+ -+ return NULL; - } - EXPORT_SYMBOL_GPL(fwnode_graph_get_endpoint_by_id); - --- -2.31.1 - -From a3fd73a31c9fd72320ea18e18bb844f3caae5ff6 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 25 Oct 2020 22:49:08 +0000 -Subject: [PATCH] software_node: Enforce parent before child ordering of nodes - arrays - -Registering software_nodes with the .parent member set to point to a -currently unregistered software_node has the potential for problems, -so enforce parent -> child ordering in arrays passed in to -software_node_register_nodes(). - -Software nodes that are children of another software node should be -unregistered before their parent. To allow easy unregistering of an array -of software_nodes ordered parent to child, reverse the order in which -software_node_unregister_nodes() unregisters software_nodes. - -Suggested-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/base/swnode.c | 42 ++++++++++++++++++++++++++++++------------ - 1 file changed, 30 insertions(+), 12 deletions(-) - -diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c -index fbfb01ff1856..edfdd67daccd 100644 ---- a/drivers/base/swnode.c -+++ b/drivers/base/swnode.c -@@ -692,7 +692,11 @@ swnode_register(const struct software_node *node, struct swnode *parent, - * software_node_register_nodes - Register an array of software nodes - * @nodes: Zero terminated array of software nodes to be registered - * -- * Register multiple software nodes at once. -+ * Register multiple software nodes at once. If any node in the array -+ * has its .parent pointer set (which can only be to another software_node), -+ * then its parent **must** have been registered before it is; either outside -+ * of this function or by ordering the array such that parent comes before -+ * child. - */ - int software_node_register_nodes(const struct software_node *nodes) - { -@@ -700,14 +704,23 @@ int software_node_register_nodes(const struct software_node *nodes) - int i; - - for (i = 0; nodes[i].name; i++) { -- ret = software_node_register(&nodes[i]); -- if (ret) { -- software_node_unregister_nodes(nodes); -- return ret; -+ const struct software_node *parent = nodes[i].parent; -+ -+ if (parent && !software_node_to_swnode(parent)) { -+ ret = -EINVAL; -+ goto err_unregister_nodes; - } -+ -+ ret = software_node_register(&nodes[i]); -+ if (ret) -+ goto err_unregister_nodes; - } - - return 0; -+ -+err_unregister_nodes: -+ software_node_unregister_nodes(nodes); -+ return ret; - } - EXPORT_SYMBOL_GPL(software_node_register_nodes); - -@@ -715,18 +728,23 @@ EXPORT_SYMBOL_GPL(software_node_register_nodes); - * software_node_unregister_nodes - Unregister an array of software nodes - * @nodes: Zero terminated array of software nodes to be unregistered - * -- * Unregister multiple software nodes at once. -+ * Unregister multiple software nodes at once. If parent pointers are set up -+ * in any of the software nodes then the array **must** be ordered such that -+ * parents come before their children. - * -- * NOTE: Be careful using this call if the nodes had parent pointers set up in -- * them before registering. If so, it is wiser to remove the nodes -- * individually, in the correct order (child before parent) instead of relying -- * on the sequential order of the list of nodes in the array. -+ * NOTE: If you are uncertain whether the array is ordered such that -+ * parents will be unregistered before their children, it is wiser to -+ * remove the nodes individually, in the correct order (child before -+ * parent). - */ - void software_node_unregister_nodes(const struct software_node *nodes) - { -- int i; -+ unsigned int i = 0; -+ -+ while (nodes[i].name) -+ i++; - -- for (i = 0; nodes[i].name; i++) -+ while (i--) - software_node_unregister(&nodes[i]); - } - EXPORT_SYMBOL_GPL(software_node_unregister_nodes); --- -2.31.1 - -From 6702197b187628686cef125435d5090a8363b20f Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 21 Oct 2020 22:25:03 +0100 -Subject: [PATCH] software_node: unregister software_nodes in reverse order - -To maintain consistency with software_node_unregister_nodes(), reverse -the order in which the software_node_unregister_node_group() function -unregisters nodes. - -Reported-by: kernel test robot -Reported-by: Dan Carpenter -Reviewed-by: Laurent Pinchart -Reviewed-by: Sakari Ailus -Suggested-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/base/swnode.c | 15 +++++++++++---- - 1 file changed, 11 insertions(+), 4 deletions(-) - -diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c -index edfdd67daccd..b22290106284 100644 ---- a/drivers/base/swnode.c -+++ b/drivers/base/swnode.c -@@ -779,16 +779,23 @@ EXPORT_SYMBOL_GPL(software_node_register_node_group); - * software_node_unregister_node_group - Unregister a group of software nodes - * @node_group: NULL terminated array of software node pointers to be unregistered - * -- * Unregister multiple software nodes at once. -+ * Unregister multiple software nodes at once. The array will be unwound in -+ * reverse order (i.e. last entry first) and thus if any members of the array are -+ * children of another member then the children must appear later in the list such -+ * that they are unregistered first. - */ --void software_node_unregister_node_group(const struct software_node **node_group) -+void software_node_unregister_node_group( -+ const struct software_node **node_group) - { -- unsigned int i; -+ unsigned int i = 0; - - if (!node_group) - return; - -- for (i = 0; node_group[i]; i++) -+ while (node_group[i]) -+ i++; -+ -+ while (i--) - software_node_unregister(node_group[i]); - } - EXPORT_SYMBOL_GPL(software_node_unregister_node_group); --- -2.31.1 - -From 396ec69ecdab90bc93aa31c24dc5b6766ae68d62 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 22 Dec 2020 13:09:05 +0000 -Subject: [PATCH] device property: Define format macros for ports and endpoints - -OF, ACPI and software_nodes all implement graphs including nodes for ports -and endpoints. These are all intended to be named with a common schema, -as "port@n" and "endpoint@n" where n is an unsigned int representing the -index of the node. To ensure commonality across the subsystems, provide a -set of macros to define the format. - -Suggested-by: Andy Shevchenko -Reviewed-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - include/linux/fwnode.h | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/include/linux/fwnode.h b/include/linux/fwnode.h -index fde4ad97564c..77414e431e89 100644 ---- a/include/linux/fwnode.h -+++ b/include/linux/fwnode.h -@@ -50,6 +50,13 @@ struct fwnode_endpoint { - const struct fwnode_handle *local_fwnode; - }; - -+/* -+ * ports and endpoints defined as software_nodes should all follow a common -+ * naming scheme; use these macros to ensure commonality. -+ */ -+#define SWNODE_GRAPH_PORT_NAME_FMT "port@%u" -+#define SWNODE_GRAPH_ENDPOINT_NAME_FMT "endpoint@%u" -+ - #define NR_FWNODE_REFERENCE_ARGS 8 - - /** --- -2.31.1 - -From 7884439a1c8a217ae5cb246f2d4577ae43351ca2 Mon Sep 17 00:00:00 2001 -From: Heikki Krogerus -Date: Tue, 15 Sep 2020 15:47:46 +0100 -Subject: [PATCH] software_node: Add support for fwnode_graph*() family of - functions - -This implements the remaining .graph_*() callbacks in the fwnode -operations structure for the software nodes. That makes the -fwnode_graph_*() functions available in the drivers also when software -nodes are used. - -The implementation tries to mimic the "OF graph" as much as possible, but -there is no support for the "reg" device property. The ports will need to -have the index in their name which starts with "port@" (for example -"port@0", "port@1", ...) and endpoints will use the index of the software -node that is given to them during creation. The port nodes can also be -grouped under a specially named "ports" subnode, just like in DT, if -necessary. - -The remote-endpoints are reference properties under the endpoint nodes -that are named "remote-endpoint". - -Reviewed-by: Laurent Pinchart -Reviewed-by: Andy Shevchenko -Signed-off-by: Heikki Krogerus -Co-developed-by: Daniel Scally -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/base/swnode.c | 115 +++++++++++++++++++++++++++++++++++++++++- - 1 file changed, 114 insertions(+), 1 deletion(-) - -diff --git a/drivers/base/swnode.c b/drivers/base/swnode.c -index b22290106284..0e90bbf6e08c 100644 ---- a/drivers/base/swnode.c -+++ b/drivers/base/swnode.c -@@ -540,6 +540,115 @@ software_node_get_reference_args(const struct fwnode_handle *fwnode, - return 0; - } - -+static struct fwnode_handle * -+swnode_graph_find_next_port(const struct fwnode_handle *parent, -+ struct fwnode_handle *port) -+{ -+ struct fwnode_handle *old = port; -+ -+ while ((port = software_node_get_next_child(parent, old))) { -+ /* -+ * fwnode ports have naming style "port@", so we search for any -+ * children that follow that convention. -+ */ -+ if (!strncmp(to_swnode(port)->node->name, "port@", -+ strlen("port@"))) -+ return port; -+ old = port; -+ } -+ -+ return NULL; -+} -+ -+static struct fwnode_handle * -+software_node_graph_get_next_endpoint(const struct fwnode_handle *fwnode, -+ struct fwnode_handle *endpoint) -+{ -+ struct swnode *swnode = to_swnode(fwnode); -+ struct fwnode_handle *parent; -+ struct fwnode_handle *port; -+ -+ if (!swnode) -+ return NULL; -+ -+ if (endpoint) { -+ port = software_node_get_parent(endpoint); -+ parent = software_node_get_parent(port); -+ } else { -+ parent = software_node_get_named_child_node(fwnode, "ports"); -+ if (!parent) -+ parent = software_node_get(&swnode->fwnode); -+ -+ port = swnode_graph_find_next_port(parent, NULL); -+ } -+ -+ for (; port; port = swnode_graph_find_next_port(parent, port)) { -+ endpoint = software_node_get_next_child(port, endpoint); -+ if (endpoint) { -+ fwnode_handle_put(port); -+ break; -+ } -+ } -+ -+ fwnode_handle_put(parent); -+ -+ return endpoint; -+} -+ -+static struct fwnode_handle * -+software_node_graph_get_remote_endpoint(const struct fwnode_handle *fwnode) -+{ -+ struct swnode *swnode = to_swnode(fwnode); -+ const struct software_node_ref_args *ref; -+ const struct property_entry *prop; -+ -+ if (!swnode) -+ return NULL; -+ -+ prop = property_entry_get(swnode->node->properties, "remote-endpoint"); -+ if (!prop || prop->type != DEV_PROP_REF || prop->is_inline) -+ return NULL; -+ -+ ref = prop->pointer; -+ -+ return software_node_get(software_node_fwnode(ref[0].node)); -+} -+ -+static struct fwnode_handle * -+software_node_graph_get_port_parent(struct fwnode_handle *fwnode) -+{ -+ struct swnode *swnode = to_swnode(fwnode); -+ -+ swnode = swnode->parent; -+ if (swnode && !strcmp(swnode->node->name, "ports")) -+ swnode = swnode->parent; -+ -+ return swnode ? software_node_get(&swnode->fwnode) : NULL; -+} -+ -+static int -+software_node_graph_parse_endpoint(const struct fwnode_handle *fwnode, -+ struct fwnode_endpoint *endpoint) -+{ -+ struct swnode *swnode = to_swnode(fwnode); -+ const char *parent_name = swnode->parent->node->name; -+ int ret; -+ -+ if (strlen("port@") >= strlen(parent_name) || -+ strncmp(parent_name, "port@", strlen("port@"))) -+ return -EINVAL; -+ -+ /* Ports have naming style "port@n", we need to select the n */ -+ ret = kstrtou32(parent_name + strlen("port@"), 10, &endpoint->port); -+ if (ret) -+ return ret; -+ -+ endpoint->id = swnode->id; -+ endpoint->local_fwnode = fwnode; -+ -+ return 0; -+} -+ - static const struct fwnode_operations software_node_ops = { - .get = software_node_get, - .put = software_node_put, -@@ -551,7 +660,11 @@ static const struct fwnode_operations software_node_ops = { - .get_parent = software_node_get_parent, - .get_next_child_node = software_node_get_next_child, - .get_named_child_node = software_node_get_named_child_node, -- .get_reference_args = software_node_get_reference_args -+ .get_reference_args = software_node_get_reference_args, -+ .graph_get_next_endpoint = software_node_graph_get_next_endpoint, -+ .graph_get_remote_endpoint = software_node_graph_get_remote_endpoint, -+ .graph_get_port_parent = software_node_graph_get_port_parent, -+ .graph_parse_endpoint = software_node_graph_parse_endpoint, - }; - - /* -------------------------------------------------------------------------- */ --- -2.31.1 - -From 52ca4770dc6d01fb95727ea42acd2f86ed33eabf Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Oct 2020 23:07:22 +0100 -Subject: [PATCH] lib/test_printf.c: Use helper function to unwind array of - software_nodes - -Use the software_node_unregister_nodes() helper function to unwind this -array in a cleaner way. - -Acked-by: Petr Mladek -Reviewed-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Reviewed-by: Sergey Senozhatsky -Suggested-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - lib/test_printf.c | 4 +--- - 1 file changed, 1 insertion(+), 3 deletions(-) - -diff --git a/lib/test_printf.c b/lib/test_printf.c -index 7ac87f18a10f..7d60f24240a4 100644 ---- a/lib/test_printf.c -+++ b/lib/test_printf.c -@@ -644,9 +644,7 @@ static void __init fwnode_pointer(void) - test(second_name, "%pfwP", software_node_fwnode(&softnodes[1])); - test(third_name, "%pfwP", software_node_fwnode(&softnodes[2])); - -- software_node_unregister(&softnodes[2]); -- software_node_unregister(&softnodes[1]); -- software_node_unregister(&softnodes[0]); -+ software_node_unregister_nodes(softnodes); - } - - static void __init --- -2.31.1 - -From 035dc1125532aa59d97d2a96642f62da2f7eb80b Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Oct 2020 23:11:36 +0100 -Subject: [PATCH] ipu3-cio2: Add T: entry to MAINTAINERS - -Development for the ipu3-cio2 driver is taking place in media_tree, but -there's no T: entry in MAINTAINERS to denote that - rectify that oversight - -Reviewed-by: Laurent Pinchart -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/MAINTAINERS b/MAINTAINERS -index d1b36e222cd1..c6c13433ecf6 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -9009,6 +9009,7 @@ M: Bingbu Cao - R: Tianshu Qiu - L: linux-media@vger.kernel.org - S: Maintained -+T: git git://linuxtv.org/media_tree.git - F: Documentation/userspace-api/media/v4l/pixfmt-srggb10-ipu3.rst - F: drivers/media/pci/intel/ipu3/ - --- -2.31.1 - -From bde2d98ef70af9d8d47fe654af8f3d671678c0ad Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Oct 2020 22:47:21 +0100 -Subject: [PATCH] ipu3-cio2: Rename ipu3-cio2.c - -ipu3-cio2 driver needs extending with multiple files; rename the main -source file and specify the renamed file in Makefile to accommodate that. - -Suggested-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/Makefile | 2 ++ - drivers/media/pci/intel/ipu3/{ipu3-cio2.c => ipu3-cio2-main.c} | 0 - 2 files changed, 2 insertions(+) - rename drivers/media/pci/intel/ipu3/{ipu3-cio2.c => ipu3-cio2-main.c} (100%) - -diff --git a/drivers/media/pci/intel/ipu3/Makefile b/drivers/media/pci/intel/ipu3/Makefile -index 98ddd5beafe0..429d516452e4 100644 ---- a/drivers/media/pci/intel/ipu3/Makefile -+++ b/drivers/media/pci/intel/ipu3/Makefile -@@ -1,2 +1,4 @@ - # SPDX-License-Identifier: GPL-2.0-only - obj-$(CONFIG_VIDEO_IPU3_CIO2) += ipu3-cio2.o -+ -+ipu3-cio2-y += ipu3-cio2-main.o -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -similarity index 100% -rename from drivers/media/pci/intel/ipu3/ipu3-cio2.c -rename to drivers/media/pci/intel/ipu3/ipu3-cio2-main.c --- -2.31.1 - -From 0f89c00a20fec334b3e016d4f4ac960a967ba9ca Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 21 Oct 2020 21:53:05 +0100 -Subject: [PATCH] media: v4l2-core: v4l2-async: Check sd->fwnode->secondary in - match_fwnode() - -Where the fwnode graph is comprised of software_nodes, these will be -assigned as the secondary to dev->fwnode. Check the v4l2_subdev's fwnode -for a secondary and attempt to match against it during match_fwnode() to -accommodate that possibility. - -Reviewed-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/v4l2-core/v4l2-async.c | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c -index e3ab003a6c85..9dd896d085ec 100644 ---- a/drivers/media/v4l2-core/v4l2-async.c -+++ b/drivers/media/v4l2-core/v4l2-async.c -@@ -87,6 +87,14 @@ static bool match_fwnode(struct v4l2_async_notifier *notifier, - if (sd->fwnode == asd->match.fwnode) - return true; - -+ /* -+ * Check the same situation for any possible secondary assigned to the -+ * subdev's fwnode -+ */ -+ if (!IS_ERR_OR_NULL(sd->fwnode->secondary) && -+ sd->fwnode->secondary == asd->match.fwnode) -+ return true; -+ - /* - * Otherwise, check if the sd fwnode and the asd fwnode refer to an - * endpoint or a device. If they're of the same type, there's no match. --- -2.31.1 - -From e3d0e2a69393cb9ba5e98a1dad1c71078ea3f669 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 15 Nov 2020 08:15:34 +0000 -Subject: [PATCH] ACPI / bus: Add acpi_dev_get_next_match_dev() and helper - macro - -To ensure we handle situations in which multiple sensors of the same -model (and therefore _HID) are present in a system, we need to be able -to iterate over devices matching a known _HID but unknown _UID and _HRV - - add acpi_dev_get_next_match_dev() to accommodate that possibility and -change acpi_dev_get_first_match_dev() to simply call the new function -with a NULL starting point. Add an iterator macro for convenience. - -Reviewed-by: Andy Shevchenko -Reviewed-by: Sakari Ailus -Suggested-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/acpi/utils.c | 30 ++++++++++++++++++++++++++---- - include/acpi/acpi_bus.h | 7 +++++++ - 2 files changed, 33 insertions(+), 4 deletions(-) - -diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c -index d5411a166685..ddca1550cce6 100644 ---- a/drivers/acpi/utils.c -+++ b/drivers/acpi/utils.c -@@ -843,12 +843,13 @@ bool acpi_dev_present(const char *hid, const char *uid, s64 hrv) - EXPORT_SYMBOL(acpi_dev_present); - - /** -- * acpi_dev_get_first_match_dev - Return the first match of ACPI device -+ * acpi_dev_get_next_match_dev - Return the next match of ACPI device -+ * @adev: Pointer to the previous acpi_device matching this @hid, @uid and @hrv - * @hid: Hardware ID of the device. - * @uid: Unique ID of the device, pass NULL to not check _UID - * @hrv: Hardware Revision of the device, pass -1 to not check _HRV - * -- * Return the first match of ACPI device if a matching device was present -+ * Return the next match of ACPI device if another matching device was present - * at the moment of invocation, or NULL otherwise. - * - * The caller is responsible to call put_device() on the returned device. -@@ -856,8 +857,9 @@ EXPORT_SYMBOL(acpi_dev_present); - * See additional information in acpi_dev_present() as well. - */ - struct acpi_device * --acpi_dev_get_first_match_dev(const char *hid, const char *uid, s64 hrv) -+acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv) - { -+ struct device *start = adev ? &adev->dev : NULL; - struct acpi_dev_match_info match = {}; - struct device *dev; - -@@ -865,9 +867,29 @@ acpi_dev_get_first_match_dev(const char *hid, const char *uid, s64 hrv) - match.uid = uid; - match.hrv = hrv; - -- dev = bus_find_device(&acpi_bus_type, NULL, &match, acpi_dev_match_cb); -+ dev = bus_find_device(&acpi_bus_type, start, &match, acpi_dev_match_cb); - return dev ? to_acpi_device(dev) : NULL; - } -+EXPORT_SYMBOL(acpi_dev_get_next_match_dev); -+ -+/** -+ * acpi_dev_get_first_match_dev - Return the first match of ACPI device -+ * @hid: Hardware ID of the device. -+ * @uid: Unique ID of the device, pass NULL to not check _UID -+ * @hrv: Hardware Revision of the device, pass -1 to not check _HRV -+ * -+ * Return the first match of ACPI device if a matching device was present -+ * at the moment of invocation, or NULL otherwise. -+ * -+ * The caller is responsible to call put_device() on the returned device. -+ * -+ * See additional information in acpi_dev_present() as well. -+ */ -+struct acpi_device * -+acpi_dev_get_first_match_dev(const char *hid, const char *uid, s64 hrv) -+{ -+ return acpi_dev_get_next_match_dev(NULL, hid, uid, hrv); -+} - EXPORT_SYMBOL(acpi_dev_get_first_match_dev); - - /* -diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h -index 37dac195adbb..f28b097c658f 100644 ---- a/include/acpi/acpi_bus.h -+++ b/include/acpi/acpi_bus.h -@@ -684,9 +684,16 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) - - bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); - -+struct acpi_device * -+acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); - struct acpi_device * - acpi_dev_get_first_match_dev(const char *hid, const char *uid, s64 hrv); - -+#define for_each_acpi_dev_match(adev, hid, uid, hrv) \ -+ for (adev = acpi_dev_get_first_match_dev(hid, uid, hrv); \ -+ adev; \ -+ adev = acpi_dev_get_next_match_dev(adev, hid, uid, hrv)) -+ - static inline void acpi_dev_put(struct acpi_device *adev) - { - put_device(&adev->dev); --- -2.31.1 - -From 821d8607d78a340e6e196d354a62f3793b02a8d9 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 19 Dec 2020 23:55:04 +0000 -Subject: [PATCH] media: v4l2-fwnode: Include v4l2_fwnode_bus_type - -V4L2 fwnode bus types are enumerated in v4l2-fwnode.c, meaning they aren't -available to the rest of the kernel. Move the enum to the corresponding -header so that I can use the label to refer to those values. - -Suggested-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/v4l2-core/v4l2-fwnode.c | 11 ----------- - include/media/v4l2-fwnode.h | 22 ++++++++++++++++++++++ - 2 files changed, 22 insertions(+), 11 deletions(-) - -diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c -index 5353e37eb950..c1c2b3060532 100644 ---- a/drivers/media/v4l2-core/v4l2-fwnode.c -+++ b/drivers/media/v4l2-core/v4l2-fwnode.c -@@ -28,17 +28,6 @@ - #include - #include - --enum v4l2_fwnode_bus_type { -- V4L2_FWNODE_BUS_TYPE_GUESS = 0, -- V4L2_FWNODE_BUS_TYPE_CSI2_CPHY, -- V4L2_FWNODE_BUS_TYPE_CSI1, -- V4L2_FWNODE_BUS_TYPE_CCP2, -- V4L2_FWNODE_BUS_TYPE_CSI2_DPHY, -- V4L2_FWNODE_BUS_TYPE_PARALLEL, -- V4L2_FWNODE_BUS_TYPE_BT656, -- NR_OF_V4L2_FWNODE_BUS_TYPE, --}; -- - static const struct v4l2_fwnode_bus_conv { - enum v4l2_fwnode_bus_type fwnode_bus_type; - enum v4l2_mbus_type mbus_type; -diff --git a/include/media/v4l2-fwnode.h b/include/media/v4l2-fwnode.h -index 4365430eea6f..77fd6a3ec308 100644 ---- a/include/media/v4l2-fwnode.h -+++ b/include/media/v4l2-fwnode.h -@@ -213,6 +213,28 @@ struct v4l2_fwnode_connector { - } connector; - }; - -+/** -+ * enum v4l2_fwnode_bus_type - Video bus types defined by firmware properties -+ * @V4L2_FWNODE_BUS_TYPE_GUESS: Default value if no bus-type fwnode property -+ * @V4L2_FWNODE_BUS_TYPE_CSI2_CPHY: MIPI CSI-2 bus, C-PHY physical layer -+ * @V4L2_FWNODE_BUS_TYPE_CSI1: MIPI CSI-1 bus -+ * @V4L2_FWNODE_BUS_TYPE_CCP2: SMIA Compact Camera Port 2 bus -+ * @V4L2_FWNODE_BUS_TYPE_CSI2_DPHY: MIPI CSI-2 bus, D-PHY physical layer -+ * @V4L2_FWNODE_BUS_TYPE_PARALLEL: Camera Parallel Interface bus -+ * @V4L2_FWNODE_BUS_TYPE_BT656: BT.656 video format bus-type -+ * @NR_OF_V4L2_FWNODE_BUS_TYPE: Number of bus-types -+ */ -+enum v4l2_fwnode_bus_type { -+ V4L2_FWNODE_BUS_TYPE_GUESS = 0, -+ V4L2_FWNODE_BUS_TYPE_CSI2_CPHY, -+ V4L2_FWNODE_BUS_TYPE_CSI1, -+ V4L2_FWNODE_BUS_TYPE_CCP2, -+ V4L2_FWNODE_BUS_TYPE_CSI2_DPHY, -+ V4L2_FWNODE_BUS_TYPE_PARALLEL, -+ V4L2_FWNODE_BUS_TYPE_BT656, -+ NR_OF_V4L2_FWNODE_BUS_TYPE -+}; -+ - /** - * v4l2_fwnode_endpoint_parse() - parse all fwnode node properties - * @fwnode: pointer to the endpoint's fwnode handle --- -2.31.1 - -From 30287cf2c0dbf32bb95cb8ef77b0e11b11a98e79 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 21 Oct 2020 21:53:44 +0100 -Subject: [PATCH] ipu3-cio2: Add cio2-bridge to ipu3-cio2 driver - -Currently on platforms designed for Windows, connections between CIO2 and -sensors are not properly defined in DSDT. This patch extends the ipu3-cio2 -driver to compensate by building software_node connections, parsing the -connection properties from the sensor's SSDB buffer. - -Suggested-by: Jordan Hand -Reviewed-by: Laurent Pinchart -Reviewed-by: Andy Shevchenko -Reviewed-by: Kieran Bingham -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 1 + - drivers/media/pci/intel/ipu3/Kconfig | 18 + - drivers/media/pci/intel/ipu3/Makefile | 1 + - drivers/media/pci/intel/ipu3/cio2-bridge.c | 311 ++++++++++++++++++ - drivers/media/pci/intel/ipu3/cio2-bridge.h | 125 +++++++ - drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 34 ++ - drivers/media/pci/intel/ipu3/ipu3-cio2.h | 6 + - 7 files changed, 496 insertions(+) - create mode 100644 drivers/media/pci/intel/ipu3/cio2-bridge.c - create mode 100644 drivers/media/pci/intel/ipu3/cio2-bridge.h - -diff --git a/MAINTAINERS b/MAINTAINERS -index c6c13433ecf6..1bade5b42a40 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -9006,6 +9006,7 @@ INTEL IPU3 CSI-2 CIO2 DRIVER - M: Yong Zhi - M: Sakari Ailus - M: Bingbu Cao -+M: Dan Scally - R: Tianshu Qiu - L: linux-media@vger.kernel.org - S: Maintained -diff --git a/drivers/media/pci/intel/ipu3/Kconfig b/drivers/media/pci/intel/ipu3/Kconfig -index 7a805201034b..24f4e79fe0cb 100644 ---- a/drivers/media/pci/intel/ipu3/Kconfig -+++ b/drivers/media/pci/intel/ipu3/Kconfig -@@ -17,3 +17,21 @@ config VIDEO_IPU3_CIO2 - Say Y or M here if you have a Skylake/Kaby Lake SoC with MIPI CSI-2 - connected camera. - The module will be called ipu3-cio2. -+ -+config CIO2_BRIDGE -+ bool "IPU3 CIO2 Sensors Bridge" -+ depends on VIDEO_IPU3_CIO2 -+ help -+ This extension provides an API for the ipu3-cio2 driver to create -+ connections to cameras that are hidden in the SSDB buffer in ACPI. -+ It can be used to enable support for cameras in detachable / hybrid -+ devices that ship with Windows. -+ -+ Say Y here if your device is a detachable / hybrid laptop that comes -+ with Windows installed by the OEM, for example: -+ -+ - Microsoft Surface models (except Surface Pro 3) -+ - The Lenovo Miix line (for example the 510, 520, 710 and 720) -+ - Dell 7285 -+ -+ If in doubt, say N here. -diff --git a/drivers/media/pci/intel/ipu3/Makefile b/drivers/media/pci/intel/ipu3/Makefile -index 429d516452e4..933777e6ea8a 100644 ---- a/drivers/media/pci/intel/ipu3/Makefile -+++ b/drivers/media/pci/intel/ipu3/Makefile -@@ -2,3 +2,4 @@ - obj-$(CONFIG_VIDEO_IPU3_CIO2) += ipu3-cio2.o - - ipu3-cio2-y += ipu3-cio2-main.o -+ipu3-cio2-$(CONFIG_CIO2_BRIDGE) += cio2-bridge.o -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -new file mode 100644 -index 000000000000..143f3c0f445e ---- /dev/null -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -0,0 +1,311 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "cio2-bridge.h" -+ -+/* -+ * Extend this array with ACPI Hardware IDs of devices known to be working -+ * plus the number of link-frequencies expected by their drivers, along with -+ * the frequency values in hertz. This is somewhat opportunistic way of adding -+ * support for this for now in the hopes of a better source for the information -+ * (possibly some encoded value in the SSDB buffer that we're unaware of) -+ * becoming apparent in the future. -+ * -+ * Do not add an entry for a sensor that is not actually supported. -+ */ -+static const struct cio2_sensor_config cio2_supported_sensors[] = { -+ /* Omnivision OV5693 */ -+ CIO2_SENSOR_CONFIG("INT33BE", 0), -+ /* Omnivision OV2680 */ -+ CIO2_SENSOR_CONFIG("OVTI2680", 0), -+}; -+ -+static const struct cio2_property_names prop_names = { -+ .clock_frequency = "clock-frequency", -+ .rotation = "rotation", -+ .bus_type = "bus-type", -+ .data_lanes = "data-lanes", -+ .remote_endpoint = "remote-endpoint", -+ .link_frequencies = "link-frequencies", -+}; -+ -+static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, -+ void *data, u32 size) -+{ -+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; -+ union acpi_object *obj; -+ acpi_status status; -+ int ret = 0; -+ -+ status = acpi_evaluate_object(adev->handle, id, NULL, &buffer); -+ if (ACPI_FAILURE(status)) -+ return -ENODEV; -+ -+ obj = buffer.pointer; -+ if (!obj) { -+ dev_err(&adev->dev, "Couldn't locate ACPI buffer\n"); -+ return -ENODEV; -+ } -+ -+ if (obj->type != ACPI_TYPE_BUFFER) { -+ dev_err(&adev->dev, "Not an ACPI buffer\n"); -+ ret = -ENODEV; -+ goto out_free_buff; -+ } -+ -+ if (obj->buffer.length > size) { -+ dev_err(&adev->dev, "Given buffer is too small\n"); -+ ret = -EINVAL; -+ goto out_free_buff; -+ } -+ -+ memcpy(data, obj->buffer.pointer, obj->buffer.length); -+ -+out_free_buff: -+ kfree(buffer.pointer); -+ return ret; -+} -+ -+static void cio2_bridge_create_fwnode_properties( -+ struct cio2_sensor *sensor, -+ struct cio2_bridge *bridge, -+ const struct cio2_sensor_config *cfg) -+{ -+ sensor->prop_names = prop_names; -+ -+ sensor->local_ref[0].node = &sensor->swnodes[SWNODE_CIO2_ENDPOINT]; -+ sensor->remote_ref[0].node = &sensor->swnodes[SWNODE_SENSOR_ENDPOINT]; -+ -+ sensor->dev_properties[0] = PROPERTY_ENTRY_U32( -+ sensor->prop_names.clock_frequency, -+ sensor->ssdb.mclkspeed); -+ sensor->dev_properties[1] = PROPERTY_ENTRY_U8( -+ sensor->prop_names.rotation, -+ sensor->ssdb.degree); -+ -+ sensor->ep_properties[0] = PROPERTY_ENTRY_U32( -+ sensor->prop_names.bus_type, -+ V4L2_FWNODE_BUS_TYPE_CSI2_DPHY); -+ sensor->ep_properties[1] = PROPERTY_ENTRY_U32_ARRAY_LEN( -+ sensor->prop_names.data_lanes, -+ bridge->data_lanes, -+ sensor->ssdb.lanes); -+ sensor->ep_properties[2] = PROPERTY_ENTRY_REF_ARRAY( -+ sensor->prop_names.remote_endpoint, -+ sensor->local_ref); -+ -+ if (cfg->nr_link_freqs > 0) -+ sensor->ep_properties[3] = PROPERTY_ENTRY_U64_ARRAY_LEN( -+ sensor->prop_names.link_frequencies, -+ cfg->link_freqs, -+ cfg->nr_link_freqs); -+ -+ sensor->cio2_properties[0] = PROPERTY_ENTRY_U32_ARRAY_LEN( -+ sensor->prop_names.data_lanes, -+ bridge->data_lanes, -+ sensor->ssdb.lanes); -+ sensor->cio2_properties[1] = PROPERTY_ENTRY_REF_ARRAY( -+ sensor->prop_names.remote_endpoint, -+ sensor->remote_ref); -+} -+ -+static void cio2_bridge_init_swnode_names(struct cio2_sensor *sensor) -+{ -+ snprintf(sensor->node_names.remote_port, -+ sizeof(sensor->node_names.remote_port), -+ SWNODE_GRAPH_PORT_NAME_FMT, sensor->ssdb.link); -+ snprintf(sensor->node_names.port, -+ sizeof(sensor->node_names.port), -+ SWNODE_GRAPH_PORT_NAME_FMT, 0); /* Always port 0 */ -+ snprintf(sensor->node_names.endpoint, -+ sizeof(sensor->node_names.endpoint), -+ SWNODE_GRAPH_ENDPOINT_NAME_FMT, 0); /* And endpoint 0 */ -+} -+ -+static void cio2_bridge_create_connection_swnodes(struct cio2_bridge *bridge, -+ struct cio2_sensor *sensor) -+{ -+ struct software_node *nodes = sensor->swnodes; -+ -+ cio2_bridge_init_swnode_names(sensor); -+ -+ nodes[SWNODE_SENSOR_HID] = NODE_SENSOR(sensor->name, -+ sensor->dev_properties); -+ nodes[SWNODE_SENSOR_PORT] = NODE_PORT(sensor->node_names.port, -+ &nodes[SWNODE_SENSOR_HID]); -+ nodes[SWNODE_SENSOR_ENDPOINT] = NODE_ENDPOINT( -+ sensor->node_names.endpoint, -+ &nodes[SWNODE_SENSOR_PORT], -+ sensor->ep_properties); -+ nodes[SWNODE_CIO2_PORT] = NODE_PORT(sensor->node_names.remote_port, -+ &bridge->cio2_hid_node); -+ nodes[SWNODE_CIO2_ENDPOINT] = NODE_ENDPOINT( -+ sensor->node_names.endpoint, -+ &nodes[SWNODE_CIO2_PORT], -+ sensor->cio2_properties); -+} -+ -+static void cio2_bridge_unregister_sensors(struct cio2_bridge *bridge) -+{ -+ struct cio2_sensor *sensor; -+ unsigned int i; -+ -+ for (i = 0; i < bridge->n_sensors; i++) { -+ sensor = &bridge->sensors[i]; -+ software_node_unregister_nodes(sensor->swnodes); -+ acpi_dev_put(sensor->adev); -+ } -+} -+ -+static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, -+ struct cio2_bridge *bridge, -+ struct pci_dev *cio2) -+{ -+ struct fwnode_handle *fwnode; -+ struct cio2_sensor *sensor; -+ struct acpi_device *adev; -+ int ret; -+ -+ for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { -+ if (!adev->status.enabled) -+ continue; -+ -+ if (bridge->n_sensors >= CIO2_NUM_PORTS) { -+ dev_err(&cio2->dev, "Exceeded available CIO2 ports\n"); -+ cio2_bridge_unregister_sensors(bridge); -+ ret = -EINVAL; -+ goto err_out; -+ } -+ -+ sensor = &bridge->sensors[bridge->n_sensors]; -+ sensor->adev = adev; -+ strscpy(sensor->name, cfg->hid, sizeof(sensor->name)); -+ -+ ret = cio2_bridge_read_acpi_buffer(adev, "SSDB", -+ &sensor->ssdb, -+ sizeof(sensor->ssdb)); -+ if (ret) -+ goto err_put_adev; -+ -+ if (sensor->ssdb.lanes > CIO2_MAX_LANES) { -+ dev_err(&adev->dev, -+ "Number of lanes in SSDB is invalid\n"); -+ ret = -EINVAL; -+ goto err_put_adev; -+ } -+ -+ cio2_bridge_create_fwnode_properties(sensor, bridge, cfg); -+ cio2_bridge_create_connection_swnodes(bridge, sensor); -+ -+ ret = software_node_register_nodes(sensor->swnodes); -+ if (ret) -+ goto err_put_adev; -+ -+ fwnode = software_node_fwnode(&sensor->swnodes[SWNODE_SENSOR_HID]); -+ if (!fwnode) { -+ ret = -ENODEV; -+ goto err_free_swnodes; -+ } -+ -+ adev->fwnode.secondary = fwnode; -+ -+ dev_info(&cio2->dev, "Found supported sensor %s\n", -+ acpi_dev_name(adev)); -+ -+ bridge->n_sensors++; -+ } -+ -+ return 0; -+ -+err_free_swnodes: -+ software_node_unregister_nodes(sensor->swnodes); -+err_put_adev: -+ acpi_dev_put(sensor->adev); -+err_out: -+ return ret; -+} -+ -+static int cio2_bridge_connect_sensors(struct cio2_bridge *bridge, -+ struct pci_dev *cio2) -+{ -+ unsigned int i; -+ int ret; -+ -+ for (i = 0; i < ARRAY_SIZE(cio2_supported_sensors); i++) { -+ const struct cio2_sensor_config *cfg = &cio2_supported_sensors[i]; -+ -+ ret = cio2_bridge_connect_sensor(cfg, bridge, cio2); -+ if (ret) -+ goto err_unregister_sensors; -+ } -+ -+ return 0; -+ -+err_unregister_sensors: -+ cio2_bridge_unregister_sensors(bridge); -+ return ret; -+} -+ -+int cio2_bridge_init(struct pci_dev *cio2) -+{ -+ struct device *dev = &cio2->dev; -+ struct fwnode_handle *fwnode; -+ struct cio2_bridge *bridge; -+ unsigned int i; -+ int ret; -+ -+ bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); -+ if (!bridge) -+ return -ENOMEM; -+ -+ strscpy(bridge->cio2_node_name, CIO2_HID, sizeof(bridge->cio2_node_name)); -+ bridge->cio2_hid_node.name = bridge->cio2_node_name; -+ -+ ret = software_node_register(&bridge->cio2_hid_node); -+ if (ret < 0) { -+ dev_err(dev, "Failed to register the CIO2 HID node\n"); -+ goto err_free_bridge; -+ } -+ -+ /* -+ * Map the lane arrangement, which is fixed for the IPU3 (meaning we -+ * only need one, rather than one per sensor). We include it as a -+ * member of the struct cio2_bridge rather than a global variable so -+ * that it survives if the module is unloaded along with the rest of -+ * the struct. -+ */ -+ for (i = 0; i < CIO2_MAX_LANES; i++) -+ bridge->data_lanes[i] = i + 1; -+ -+ ret = cio2_bridge_connect_sensors(bridge, cio2); -+ if (ret || bridge->n_sensors == 0) -+ goto err_unregister_cio2; -+ -+ dev_info(dev, "Connected %d cameras\n", bridge->n_sensors); -+ -+ fwnode = software_node_fwnode(&bridge->cio2_hid_node); -+ if (!fwnode) { -+ dev_err(dev, "Error getting fwnode from cio2 software_node\n"); -+ ret = -ENODEV; -+ goto err_unregister_sensors; -+ } -+ -+ set_secondary_fwnode(dev, fwnode); -+ -+ return 0; -+ -+err_unregister_sensors: -+ cio2_bridge_unregister_sensors(bridge); -+err_unregister_cio2: -+ software_node_unregister(&bridge->cio2_hid_node); -+err_free_bridge: -+ kfree(bridge); -+ -+ return ret; -+} -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -new file mode 100644 -index 000000000000..dd0ffcafa489 ---- /dev/null -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -0,0 +1,125 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* Author: Dan Scally */ -+#ifndef __CIO2_BRIDGE_H -+#define __CIO2_BRIDGE_H -+ -+#include -+#include -+ -+#include "ipu3-cio2.h" -+ -+#define CIO2_HID "INT343E" -+#define CIO2_MAX_LANES 4 -+#define MAX_NUM_LINK_FREQS 3 -+ -+#define CIO2_SENSOR_CONFIG(_HID, _NR, ...) \ -+ (const struct cio2_sensor_config) { \ -+ .hid = _HID, \ -+ .nr_link_freqs = _NR, \ -+ .link_freqs = { __VA_ARGS__ } \ -+ } -+ -+#define NODE_SENSOR(_HID, _PROPS) \ -+ (const struct software_node) { \ -+ .name = _HID, \ -+ .properties = _PROPS, \ -+ } -+ -+#define NODE_PORT(_PORT, _SENSOR_NODE) \ -+ (const struct software_node) { \ -+ .name = _PORT, \ -+ .parent = _SENSOR_NODE, \ -+ } -+ -+#define NODE_ENDPOINT(_EP, _PORT, _PROPS) \ -+ (const struct software_node) { \ -+ .name = _EP, \ -+ .parent = _PORT, \ -+ .properties = _PROPS, \ -+ } -+ -+enum cio2_sensor_swnodes { -+ SWNODE_SENSOR_HID, -+ SWNODE_SENSOR_PORT, -+ SWNODE_SENSOR_ENDPOINT, -+ SWNODE_CIO2_PORT, -+ SWNODE_CIO2_ENDPOINT, -+ SWNODE_COUNT -+}; -+ -+/* Data representation as it is in ACPI SSDB buffer */ -+struct cio2_sensor_ssdb { -+ u8 version; -+ u8 sku; -+ u8 guid_csi2[16]; -+ u8 devfunction; -+ u8 bus; -+ u32 dphylinkenfuses; -+ u32 clockdiv; -+ u8 link; -+ u8 lanes; -+ u32 csiparams[10]; -+ u32 maxlanespeed; -+ u8 sensorcalibfileidx; -+ u8 sensorcalibfileidxInMBZ[3]; -+ u8 romtype; -+ u8 vcmtype; -+ u8 platforminfo; -+ u8 platformsubinfo; -+ u8 flash; -+ u8 privacyled; -+ u8 degree; -+ u8 mipilinkdefined; -+ u32 mclkspeed; -+ u8 controllogicid; -+ u8 reserved1[3]; -+ u8 mclkport; -+ u8 reserved2[13]; -+} __packed; -+ -+struct cio2_property_names { -+ char clock_frequency[16]; -+ char rotation[9]; -+ char bus_type[9]; -+ char data_lanes[11]; -+ char remote_endpoint[16]; -+ char link_frequencies[17]; -+}; -+ -+struct cio2_node_names { -+ char port[7]; -+ char endpoint[11]; -+ char remote_port[7]; -+}; -+ -+struct cio2_sensor_config { -+ const char *hid; -+ const u8 nr_link_freqs; -+ const u64 link_freqs[MAX_NUM_LINK_FREQS]; -+}; -+ -+struct cio2_sensor { -+ char name[ACPI_ID_LEN]; -+ struct acpi_device *adev; -+ -+ struct software_node swnodes[6]; -+ struct cio2_node_names node_names; -+ -+ struct cio2_sensor_ssdb ssdb; -+ struct cio2_property_names prop_names; -+ struct property_entry ep_properties[5]; -+ struct property_entry dev_properties[3]; -+ struct property_entry cio2_properties[3]; -+ struct software_node_ref_args local_ref[1]; -+ struct software_node_ref_args remote_ref[1]; -+}; -+ -+struct cio2_bridge { -+ char cio2_node_name[ACPI_ID_LEN]; -+ struct software_node cio2_hid_node; -+ u32 data_lanes[4]; -+ unsigned int n_sensors; -+ struct cio2_sensor sensors[CIO2_NUM_PORTS]; -+}; -+ -+#endif -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -index 325c1483f42b..5e0a449fe2bc 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -@@ -1702,11 +1702,28 @@ static void cio2_queues_exit(struct cio2_device *cio2) - cio2_queue_exit(cio2, &cio2->queue[i]); - } - -+static int cio2_check_fwnode_graph(struct fwnode_handle *fwnode) -+{ -+ struct fwnode_handle *endpoint; -+ -+ if (IS_ERR_OR_NULL(fwnode)) -+ return -EINVAL; -+ -+ endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); -+ if (endpoint) { -+ fwnode_handle_put(endpoint); -+ return 0; -+ } -+ -+ return cio2_check_fwnode_graph(fwnode->secondary); -+} -+ - /**************** PCI interface ****************/ - - static int cio2_pci_probe(struct pci_dev *pci_dev, - const struct pci_device_id *id) - { -+ struct fwnode_handle *fwnode = dev_fwnode(&pci_dev->dev); - struct cio2_device *cio2; - int r; - -@@ -1715,6 +1732,23 @@ static int cio2_pci_probe(struct pci_dev *pci_dev, - return -ENOMEM; - cio2->pci_dev = pci_dev; - -+ /* -+ * On some platforms no connections to sensors are defined in firmware, -+ * if the device has no endpoints then we can try to build those as -+ * software_nodes parsed from SSDB. -+ */ -+ r = cio2_check_fwnode_graph(fwnode); -+ if (r) { -+ if (fwnode && !IS_ERR_OR_NULL(fwnode->secondary)) { -+ dev_err(&pci_dev->dev, "fwnode graph has no endpoints connected\n"); -+ return -EINVAL; -+ } -+ -+ r = cio2_bridge_init(pci_dev); -+ if (r) -+ return r; -+ } -+ - r = pcim_enable_device(pci_dev); - if (r) { - dev_err(&pci_dev->dev, "failed to enable device (%d)\n", r); -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.h b/drivers/media/pci/intel/ipu3/ipu3-cio2.h -index 62187ab5ae43..dc3e343a37fb 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2.h -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.h -@@ -455,4 +455,10 @@ static inline struct cio2_queue *vb2q_to_cio2_queue(struct vb2_queue *vq) - return container_of(vq, struct cio2_queue, vbq); - } - -+#if IS_ENABLED(CONFIG_CIO2_BRIDGE) -+int cio2_bridge_init(struct pci_dev *cio2); -+#else -+int cio2_bridge_init(struct pci_dev *cio2) { return 0; } -+#endif -+ - #endif --- -2.31.1 - -From 8be47a0765f714b6ae57f03ff7398a3c55ab1a0b Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 2 Dec 2020 12:38:10 +0000 -Subject: [PATCH] acpi: utils: move acpi_lpss_dep() to utils - -I need to be able to identify devices which declare themselves to be -dependent on other devices through _DEP; add this function to utils.c -and export it to the rest of the ACPI layer. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/acpi/acpi_lpss.c | 24 ------------------------ - drivers/acpi/internal.h | 1 + - drivers/acpi/utils.c | 24 ++++++++++++++++++++++++ - 3 files changed, 25 insertions(+), 24 deletions(-) - -diff --git a/drivers/acpi/acpi_lpss.c b/drivers/acpi/acpi_lpss.c -index be73974ce449..70c7d9a3f715 100644 ---- a/drivers/acpi/acpi_lpss.c -+++ b/drivers/acpi/acpi_lpss.c -@@ -543,30 +543,6 @@ static struct device *acpi_lpss_find_device(const char *hid, const char *uid) - return bus_find_device(&pci_bus_type, NULL, &data, match_hid_uid); - } - --static bool acpi_lpss_dep(struct acpi_device *adev, acpi_handle handle) --{ -- struct acpi_handle_list dep_devices; -- acpi_status status; -- int i; -- -- if (!acpi_has_method(adev->handle, "_DEP")) -- return false; -- -- status = acpi_evaluate_reference(adev->handle, "_DEP", NULL, -- &dep_devices); -- if (ACPI_FAILURE(status)) { -- dev_dbg(&adev->dev, "Failed to evaluate _DEP.\n"); -- return false; -- } -- -- for (i = 0; i < dep_devices.count; i++) { -- if (dep_devices.handles[i] == handle) -- return true; -- } -- -- return false; --} -- - static void acpi_lpss_link_consumer(struct device *dev1, - const struct lpss_device_links *link) - { -diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h -index cb8f70842249..a7051a944c26 100644 ---- a/drivers/acpi/internal.h -+++ b/drivers/acpi/internal.h -@@ -81,6 +81,7 @@ static inline void acpi_lpss_init(void) {} - #endif - - void acpi_apd_init(void); -+bool acpi_lpss_dep(struct acpi_device *adev, acpi_handle handle); - - acpi_status acpi_hotplug_schedule(struct acpi_device *adev, u32 src); - bool acpi_queue_hotplug_work(struct work_struct *work); -diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c -index ddca1550cce6..78b38775f18b 100644 ---- a/drivers/acpi/utils.c -+++ b/drivers/acpi/utils.c -@@ -807,6 +807,30 @@ static int acpi_dev_match_cb(struct device *dev, const void *data) - return hrv == match->hrv; - } - -+bool acpi_lpss_dep(struct acpi_device *adev, acpi_handle handle) -+{ -+ struct acpi_handle_list dep_devices; -+ acpi_status status; -+ int i; -+ -+ if (!acpi_has_method(adev->handle, "_DEP")) -+ return false; -+ -+ status = acpi_evaluate_reference(adev->handle, "_DEP", NULL, -+ &dep_devices); -+ if (ACPI_FAILURE(status)) { -+ dev_dbg(&adev->dev, "Failed to evaluate _DEP.\n"); -+ return false; -+ } -+ -+ for (i = 0; i < dep_devices.count; i++) { -+ if (dep_devices.handles[i] == handle) -+ return true; -+ } -+ -+ return false; -+} -+ - /** - * acpi_dev_present - Detect that a given ACPI device is present - * @hid: Hardware ID of the device. --- -2.31.1 - -From f3ba823775b88ccc7f65e17f768fbf47c04743ba Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 26 Nov 2020 21:12:41 +0000 -Subject: [PATCH] acpi: utils: Add function to fetch dependent acpi_devices - -In some ACPI tables we encounter, devices use the _DEP method to assert -a dependence on other ACPI devices as opposed to the OpRegions that the -specification intends. We need to be able to find those devices "from" -the dependee, so add a function to parse all ACPI Devices and check if -the include the handle of the dependee device in their _DEP buffer. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/acpi/utils.c | 34 ++++++++++++++++++++++++++++++++++ - include/acpi/acpi_bus.h | 2 ++ - 2 files changed, 36 insertions(+) - -diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c -index 78b38775f18b..ec6a2406a886 100644 ---- a/drivers/acpi/utils.c -+++ b/drivers/acpi/utils.c -@@ -831,6 +831,18 @@ bool acpi_lpss_dep(struct acpi_device *adev, acpi_handle handle) - return false; - } - -+static int acpi_dev_match_by_dep(struct device *dev, const void *data) -+{ -+ struct acpi_device *adev = to_acpi_device(dev); -+ const struct acpi_device *dependee = data; -+ acpi_handle handle = dependee->handle; -+ -+ if (acpi_lpss_dep(adev, handle)) -+ return 1; -+ -+ return 0; -+} -+ - /** - * acpi_dev_present - Detect that a given ACPI device is present - * @hid: Hardware ID of the device. -@@ -866,6 +878,28 @@ bool acpi_dev_present(const char *hid, const char *uid, s64 hrv) - } - EXPORT_SYMBOL(acpi_dev_present); - -+/** -+ * acpi_dev_get_next_dep_dev - Return next ACPI device dependent on input dev -+ * @adev: Pointer to the dependee device -+ * @prev: Pointer to the previous dependent device (or NULL for first match) -+ * -+ * Return the next ACPI device which declares itself dependent on @adev in -+ * the _DEP buffer. -+ * -+ * The caller is responsible to call put_device() on the returned device. -+ */ -+struct acpi_device *acpi_dev_get_next_dep_dev(struct acpi_device *adev, -+ struct acpi_device *prev) -+{ -+ struct device *start = prev ? &prev->dev : NULL; -+ struct device *dev; -+ -+ dev = bus_find_device(&acpi_bus_type, start, adev, acpi_dev_match_by_dep); -+ -+ return dev ? to_acpi_device(dev) : NULL; -+} -+EXPORT_SYMBOL(acpi_dev_get_next_dep_dev); -+ - /** - * acpi_dev_get_next_match_dev - Return the next match of ACPI device - * @adev: Pointer to the previous acpi_device matching this @hid, @uid and @hrv -diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h -index f28b097c658f..9bec3373f850 100644 ---- a/include/acpi/acpi_bus.h -+++ b/include/acpi/acpi_bus.h -@@ -684,6 +684,8 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) - - bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); - -+struct acpi_device * -+acpi_dev_get_next_dep_dev(struct acpi_device *adev, struct acpi_device *prev); - struct acpi_device * - acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); - struct acpi_device * --- -2.31.1 - -From 9868b1f445f3016247e6995caa27347bebb5b0cf Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 16 Nov 2020 21:38:49 +0000 -Subject: [PATCH] i2c: i2c-core-base: Use format macro in i2c_dev_set_name() - -Some places in the kernel allow users to map resources to a device -using device name (for example, gpiod_lookup_table). Currently -this involves waiting for the i2c_client to have been registered so we -can use dev_name(&client->dev). We want to add a function to allow users -to refer to an i2c device by name before it has been instantiated, so -create a macro for the format that's accessible outside the i2c layer -and use it in i2c_dev_set_name() - -Suggested-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/i2c/i2c-core-base.c | 4 ++-- - include/linux/i2c.h | 7 +++++++ - 2 files changed, 9 insertions(+), 2 deletions(-) - -diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c -index f21362355973..e2cf16f27d65 100644 ---- a/drivers/i2c/i2c-core-base.c -+++ b/drivers/i2c/i2c-core-base.c -@@ -812,12 +812,12 @@ static void i2c_dev_set_name(struct i2c_adapter *adap, - struct acpi_device *adev = ACPI_COMPANION(&client->dev); - - if (info && info->dev_name) { -- dev_set_name(&client->dev, "i2c-%s", info->dev_name); -+ dev_set_name(&client->dev, I2C_DEV_NAME_FORMAT, info->dev_name); - return; - } - - if (adev) { -- dev_set_name(&client->dev, "i2c-%s", acpi_dev_name(adev)); -+ dev_set_name(&client->dev, I2C_DEV_NAME_FORMAT, acpi_dev_name(adev)); - return; - } - -diff --git a/include/linux/i2c.h b/include/linux/i2c.h -index a670ae129f4b..b18172f240af 100644 ---- a/include/linux/i2c.h -+++ b/include/linux/i2c.h -@@ -39,6 +39,9 @@ enum i2c_slave_event; - typedef int (*i2c_slave_cb_t)(struct i2c_client *client, - enum i2c_slave_event event, u8 *val); - -+/* I2C Device Name Format - to maintain consistency outside the i2c layer */ -+#define I2C_DEV_NAME_FORMAT "i2c-%s" -+ - /* I2C Frequency Modes */ - #define I2C_MAX_STANDARD_MODE_FREQ 100000 - #define I2C_MAX_FAST_MODE_FREQ 400000 -@@ -1013,6 +1016,10 @@ static inline struct i2c_client *i2c_acpi_new_device(struct device *dev, - { - return ERR_PTR(-ENODEV); - } -+static inline char *i2c_acpi_dev_name(struct acpi_device *adev) -+{ -+ return NULL; -+} - static inline struct i2c_adapter *i2c_acpi_find_adapter_by_handle(acpi_handle handle) - { - return NULL; --- -2.31.1 - -From 84a1cde3c9578bf6f4ced9bea6b3ff344ef363fc Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 2 Dec 2020 16:41:42 +0000 -Subject: [PATCH] i2c: i2c-core-acpi: Add i2c_acpi_dev_name() - -We want to refer to an i2c device by name before it has been -created by the kernel; add a function that constructs the name -from the acpi device instead. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/i2c/i2c-core-acpi.c | 16 ++++++++++++++++ - include/linux/i2c.h | 1 + - 2 files changed, 17 insertions(+) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index aed579942436..89751415b69b 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -497,6 +497,22 @@ struct i2c_client *i2c_acpi_new_device(struct device *dev, int index, - } - EXPORT_SYMBOL_GPL(i2c_acpi_new_device); - -+/** -+ * i2c_acpi_dev_name - Construct i2c device name for devs sourced from ACPI -+ * @adev: ACPI device to construct the name for -+ * -+ * Constructs the name of an i2c device matching the format used by -+ * i2c_dev_set_name() to allow users to refer to an i2c device by name even -+ * before they have been instantiated. -+ * -+ * The caller is responsible for freeing the returned pointer. -+ */ -+char *i2c_acpi_dev_name(struct acpi_device *adev) -+{ -+ return kasprintf(GFP_KERNEL, I2C_DEV_NAME_FORMAT, acpi_dev_name(adev)); -+} -+EXPORT_SYMBOL_GPL(i2c_acpi_dev_name); -+ - #ifdef CONFIG_ACPI_I2C_OPREGION - static int acpi_gsb_i2c_read_bytes(struct i2c_client *client, - u8 cmd, u8 *data, u8 data_len) -diff --git a/include/linux/i2c.h b/include/linux/i2c.h -index b18172f240af..269a2009080c 100644 ---- a/include/linux/i2c.h -+++ b/include/linux/i2c.h -@@ -1000,6 +1000,7 @@ bool i2c_acpi_get_i2c_resource(struct acpi_resource *ares, - u32 i2c_acpi_find_bus_speed(struct device *dev); - struct i2c_client *i2c_acpi_new_device(struct device *dev, int index, - struct i2c_board_info *info); -+char *i2c_acpi_dev_name(struct acpi_device *adev); - struct i2c_adapter *i2c_acpi_find_adapter_by_handle(acpi_handle handle); - #else - static inline bool i2c_acpi_get_i2c_resource(struct acpi_resource *ares, --- -2.31.1 - -From 88904c22d4cad03a741d4769d2d6a1241cea562d Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 16 Nov 2020 00:16:56 +0000 -Subject: [PATCH] gpio: gpiolib-acpi: Export acpi_get_gpiod() - -I need to be able to translate GPIO resources in an acpi_device's _CRS -into gpio_descs. Those are represented in _CRS as a pathname to a GPIO -device plus the pin's index number: this function is perfect for that -purpose. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/gpio/gpiolib-acpi.c | 3 ++- - include/linux/acpi.h | 5 +++++ - 2 files changed, 7 insertions(+), 1 deletion(-) - -diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c -index 1aacd2a5a1fd..94a3d3d05560 100644 ---- a/drivers/gpio/gpiolib-acpi.c -+++ b/drivers/gpio/gpiolib-acpi.c -@@ -111,7 +111,7 @@ static int acpi_gpiochip_find(struct gpio_chip *gc, void *data) - * controller does not have GPIO chip registered at the moment. This is to - * support probe deferral. - */ --static struct gpio_desc *acpi_get_gpiod(char *path, int pin) -+struct gpio_desc *acpi_get_gpiod(char *path, int pin) - { - struct gpio_chip *chip; - acpi_handle handle; -@@ -127,6 +127,7 @@ static struct gpio_desc *acpi_get_gpiod(char *path, int pin) - - return gpiochip_get_desc(chip, pin); - } -+EXPORT_SYMBOL_GPL(acpi_get_gpiod); - - static irqreturn_t acpi_gpio_irq_handler(int irq, void *data) - { -diff --git a/include/linux/acpi.h b/include/linux/acpi.h -index 2f7508c3c2d6..b01109930678 100644 ---- a/include/linux/acpi.h -+++ b/include/linux/acpi.h -@@ -1080,6 +1080,7 @@ void __acpi_handle_debug(struct _ddebug *descriptor, acpi_handle handle, const c - bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, - struct acpi_resource_gpio **agpio); - int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, const char *name, int index); -+struct gpio_desc *acpi_get_gpiod(char *path, int pin); - #else - static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, - struct acpi_resource_gpio **agpio) -@@ -1091,6 +1092,10 @@ static inline int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, - { - return -ENXIO; - } -+struct gpio_desc *acpi_get_gpiod(char *path, int pin) -+{ -+ return NULL; -+} - #endif - - static inline int acpi_dev_gpio_irq_get(struct acpi_device *adev, int index) --- -2.31.1 - -From cc8092eab5c7dd0ae7ec411ccdd3778e83f749e9 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 12 Dec 2020 23:56:59 +0000 -Subject: [PATCH] mfd: Remove tps68470 MFD driver - -This driver only covered one scenario in which ACPI devices with _HID -INT3472 are found, and its functionality has been taken over by the -intel-skl-int3472 module, so remove it. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/acpi/pmic/Kconfig | 1 - - drivers/gpio/Kconfig | 1 - - drivers/mfd/Kconfig | 18 -------- - drivers/mfd/Makefile | 1 - - drivers/mfd/tps68470.c | 97 --------------------------------------- - 5 files changed, 118 deletions(-) - delete mode 100644 drivers/mfd/tps68470.c - -diff --git a/drivers/acpi/pmic/Kconfig b/drivers/acpi/pmic/Kconfig -index 56bbcb2ce61b..e27d8ef3a32c 100644 ---- a/drivers/acpi/pmic/Kconfig -+++ b/drivers/acpi/pmic/Kconfig -@@ -52,7 +52,6 @@ endif # PMIC_OPREGION - - config TPS68470_PMIC_OPREGION - bool "ACPI operation region support for TPS68470 PMIC" -- depends on MFD_TPS68470 - help - This config adds ACPI operation region support for TI TPS68470 PMIC. - TPS68470 device is an advanced power management unit that powers -diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig -index fa225175e68d..27b43d9c5da6 100644 ---- a/drivers/gpio/Kconfig -+++ b/drivers/gpio/Kconfig -@@ -1347,7 +1347,6 @@ config GPIO_TPS65912 - - config GPIO_TPS68470 - bool "TPS68470 GPIO" -- depends on MFD_TPS68470 - help - Select this option to enable GPIO driver for the TPS68470 - chip family. -diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig -index bdfce7b15621..9a1f648efde0 100644 ---- a/drivers/mfd/Kconfig -+++ b/drivers/mfd/Kconfig -@@ -1520,24 +1520,6 @@ config MFD_TPS65217 - This driver can also be built as a module. If so, the module - will be called tps65217. - --config MFD_TPS68470 -- bool "TI TPS68470 Power Management / LED chips" -- depends on ACPI && PCI && I2C=y -- depends on I2C_DESIGNWARE_PLATFORM=y -- select MFD_CORE -- select REGMAP_I2C -- help -- If you say yes here you get support for the TPS68470 series of -- Power Management / LED chips. -- -- These include voltage regulators, LEDs and other features -- that are often used in portable devices. -- -- This option is a bool as it provides an ACPI operation -- region, which must be available before any of the devices -- using this are probed. This option also configures the -- designware-i2c driver to be built-in, for the same reason. -- - config MFD_TI_LP873X - tristate "TI LP873X Power Management IC" - depends on I2C -diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile -index 14fdb188af02..5994e812f479 100644 ---- a/drivers/mfd/Makefile -+++ b/drivers/mfd/Makefile -@@ -105,7 +105,6 @@ obj-$(CONFIG_MFD_TPS65910) += tps65910.o - obj-$(CONFIG_MFD_TPS65912) += tps65912-core.o - obj-$(CONFIG_MFD_TPS65912_I2C) += tps65912-i2c.o - obj-$(CONFIG_MFD_TPS65912_SPI) += tps65912-spi.o --obj-$(CONFIG_MFD_TPS68470) += tps68470.o - obj-$(CONFIG_MFD_TPS80031) += tps80031.o - obj-$(CONFIG_MENELAUS) += menelaus.o - -diff --git a/drivers/mfd/tps68470.c b/drivers/mfd/tps68470.c -deleted file mode 100644 -index 4a4df4ffd18c..000000000000 ---- a/drivers/mfd/tps68470.c -+++ /dev/null -@@ -1,97 +0,0 @@ --// SPDX-License-Identifier: GPL-2.0 --/* -- * TPS68470 chip Parent driver -- * -- * Copyright (C) 2017 Intel Corporation -- * -- * Authors: -- * Rajmohan Mani -- * Tianshu Qiu -- * Jian Xu Zheng -- * Yuning Pu -- */ -- --#include --#include --#include --#include --#include --#include --#include -- --static const struct mfd_cell tps68470s[] = { -- { .name = "tps68470-gpio" }, -- { .name = "tps68470_pmic_opregion" }, --}; -- --static const struct regmap_config tps68470_regmap_config = { -- .reg_bits = 8, -- .val_bits = 8, -- .max_register = TPS68470_REG_MAX, --}; -- --static int tps68470_chip_init(struct device *dev, struct regmap *regmap) --{ -- unsigned int version; -- int ret; -- -- /* Force software reset */ -- ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); -- if (ret) -- return ret; -- -- ret = regmap_read(regmap, TPS68470_REG_REVID, &version); -- if (ret) { -- dev_err(dev, "Failed to read revision register: %d\n", ret); -- return ret; -- } -- -- dev_info(dev, "TPS68470 REVID: 0x%x\n", version); -- -- return 0; --} -- --static int tps68470_probe(struct i2c_client *client) --{ -- struct device *dev = &client->dev; -- struct regmap *regmap; -- int ret; -- -- regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); -- if (IS_ERR(regmap)) { -- dev_err(dev, "devm_regmap_init_i2c Error %ld\n", -- PTR_ERR(regmap)); -- return PTR_ERR(regmap); -- } -- -- i2c_set_clientdata(client, regmap); -- -- ret = tps68470_chip_init(dev, regmap); -- if (ret < 0) { -- dev_err(dev, "TPS68470 Init Error %d\n", ret); -- return ret; -- } -- -- ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, tps68470s, -- ARRAY_SIZE(tps68470s), NULL, 0, NULL); -- if (ret < 0) { -- dev_err(dev, "devm_mfd_add_devices failed: %d\n", ret); -- return ret; -- } -- -- return 0; --} -- --static const struct acpi_device_id tps68470_acpi_ids[] = { -- {"INT3472"}, -- {}, --}; -- --static struct i2c_driver tps68470_driver = { -- .driver = { -- .name = "tps68470", -- .acpi_match_table = tps68470_acpi_ids, -- }, -- .probe_new = tps68470_probe, --}; --builtin_i2c_driver(tps68470_driver); --- -2.31.1 - -From 1349b00341f9a4196d975e465046e80dc42566c4 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 15 Jan 2021 12:37:31 +0000 -Subject: [PATCH] platform: x86: Add intel_skl_int3472 driver - -ACPI devices with _HID INT3472 are currently matched to the tps68470 -driver, however this does not cover all situations in which that _HID -occurs. We've encountered three possibilities: - -1. On Chrome OS devices, an ACPI device with _HID INT3472 (representing -a physical tps68470 device) that requires a GPIO and OpRegion driver -2. On devices designed for Windows, an ACPI device with _HID INT3472 -(again representing a physical tps68470 device) which requires GPIO, -Clock and Regulator drivers. -3. On other devices designed for Windows, an ACPI device with _HID -INT3472 which does NOT represent a physical tps68470, and is instead -used as a dummy device to group some system GPIO lines which are meant -to be consumed by the sensor that is dependent on this entry. - -This commit adds a new module, registering a platform driver to deal -with the 3rd scenario plus an i2c-driver to deal with #1 and #2, by -querying the CLDB buffer found against INT3472 entries to determine -which is most appropriate. - -Suggested-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 5 + - drivers/platform/x86/Kconfig | 25 + - drivers/platform/x86/Makefile | 5 + - .../platform/x86/intel_skl_int3472_common.c | 100 ++++ - .../platform/x86/intel_skl_int3472_common.h | 99 ++++ - .../platform/x86/intel_skl_int3472_discrete.c | 489 ++++++++++++++++++ - .../platform/x86/intel_skl_int3472_tps68470.c | 145 ++++++ - 7 files changed, 868 insertions(+) - create mode 100644 drivers/platform/x86/intel_skl_int3472_common.c - create mode 100644 drivers/platform/x86/intel_skl_int3472_common.h - create mode 100644 drivers/platform/x86/intel_skl_int3472_discrete.c - create mode 100644 drivers/platform/x86/intel_skl_int3472_tps68470.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 1bade5b42a40..2aa943def82b 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -9139,6 +9139,11 @@ S: Maintained - F: arch/x86/include/asm/intel_scu_ipc.h - F: drivers/platform/x86/intel_scu_* - -+INTEL SKL INT3472 ACPI DEVICE DRIVER -+M: Daniel Scally -+S: Maintained -+F: drivers/platform/x86/intel_skl_int3472_* -+ - INTEL SPEED SELECT TECHNOLOGY - M: Srinivas Pandruvada - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index ac4125ec0660..ca95ec1cbc4e 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -844,6 +844,31 @@ config INTEL_CHT_INT33FE - device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m - for Type-C device. - -+config INTEL_SKL_INT3472 -+ tristate "Intel SkyLake ACPI INT3472 Driver" -+ depends on X86 && ACPI -+ select REGMAP_I2C -+ help -+ This driver adds support for the INT3472 ACPI devices found on some -+ Intel SkyLake devices. -+ -+ There are 3 kinds of INT3472 ACPI device possible; two for devices -+ designed for Windows (either with or without a physical tps68470 -+ PMIC) and one designed for Chrome OS. This driver handles all three -+ situations by discovering information it needs to discern them at -+ runtime. -+ -+ If your device was designed for Chrome OS, this driver will provide -+ an ACPI operation region, which must be available before any of the -+ devices using this are probed. For this reason, you should select Y -+ if your device was designed for ChromeOS. This option also configures -+ the designware-i2c driver to be built-in, for the same reason. -+ -+ Say Y or M here if you have a SkyLake device designed for use -+ with Windows or ChromeOS. Say N here if you are not sure. -+ -+ The module will be named "intel-skl-int3472" -+ - config INTEL_HID_EVENT - tristate "INTEL HID Event" - depends on ACPI -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 581475f59819..3cefe67761af 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -86,6 +86,11 @@ obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o - obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o - obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o - obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o -+obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o -+intel_skl_int3472-objs := intel_skl_int3472_common.o \ -+ intel_skl_int3472_discrete.o \ -+ intel_skl_int3472_tps68470.o -+ - obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o - - # MSI -diff --git a/drivers/platform/x86/intel_skl_int3472_common.c b/drivers/platform/x86/intel_skl_int3472_common.c -new file mode 100644 -index 000000000000..08cb9d3c06aa ---- /dev/null -+++ b/drivers/platform/x86/intel_skl_int3472_common.c -@@ -0,0 +1,100 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+ -+#include "intel_skl_int3472_common.h" -+ -+int skl_int3472_get_cldb_buffer(struct acpi_device *adev, -+ struct int3472_cldb *cldb) -+{ -+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; -+ acpi_handle handle = adev->handle; -+ union acpi_object *obj; -+ acpi_status status; -+ int ret = 0; -+ -+ status = acpi_evaluate_object(handle, "CLDB", NULL, &buffer); -+ if (ACPI_FAILURE(status)) -+ return -ENODEV; -+ -+ obj = buffer.pointer; -+ if (!obj) { -+ dev_err(&adev->dev, "ACPI device has no CLDB object\n"); -+ return -ENODEV; -+ } -+ -+ if (obj->type != ACPI_TYPE_BUFFER) { -+ dev_err(&adev->dev, "CLDB object is not an ACPI buffer\n"); -+ ret = -EINVAL; -+ goto out_free_buff; -+ } -+ -+ if (obj->buffer.length > sizeof(*cldb)) { -+ dev_err(&adev->dev, "The CLDB buffer is too large\n"); -+ ret = -EINVAL; -+ goto out_free_buff; -+ } -+ -+ memcpy(cldb, obj->buffer.pointer, obj->buffer.length); -+ -+out_free_buff: -+ kfree(buffer.pointer); -+ return ret; -+} -+ -+static const struct acpi_device_id int3472_device_id[] = { -+ { "INT3472", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, int3472_device_id); -+ -+static struct platform_driver int3472_discrete = { -+ .driver = { -+ .name = "int3472-discrete", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe = skl_int3472_discrete_probe, -+ .remove = skl_int3472_discrete_remove, -+}; -+ -+static struct i2c_driver int3472_tps68470 = { -+ .driver = { -+ .name = "int3472-tps68470", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe_new = skl_int3472_tps68470_probe, -+}; -+ -+static int skl_int3472_init(void) -+{ -+ int ret = 0; -+ -+ ret = platform_driver_register(&int3472_discrete); -+ if (ret) -+ return ret; -+ -+ ret = i2c_register_driver(THIS_MODULE, &int3472_tps68470); -+ if (ret) -+ goto err_unregister_plat_drv; -+ -+ return 0; -+ -+err_unregister_plat_drv: -+ platform_driver_unregister(&int3472_discrete); -+ return ret; -+} -+module_init(skl_int3472_init); -+ -+static void skl_int3472_exit(void) -+{ -+ platform_driver_unregister(&int3472_discrete); -+ i2c_del_driver(&int3472_tps68470); -+} -+module_exit(skl_int3472_exit); -+ -+MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver"); -+MODULE_AUTHOR("Daniel Scally "); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/platform/x86/intel_skl_int3472_common.h b/drivers/platform/x86/intel_skl_int3472_common.h -new file mode 100644 -index 000000000000..4ac6bb2b223f ---- /dev/null -+++ b/drivers/platform/x86/intel_skl_int3472_common.h -@@ -0,0 +1,99 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* Author: Dan Scally */ -+#include -+#include -+#include -+#include -+#include -+ -+/* PMIC GPIO Types */ -+#define INT3472_GPIO_TYPE_RESET 0x00 -+#define INT3472_GPIO_TYPE_POWERDOWN 0x01 -+#define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c -+#define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b -+#define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d -+#define INT3472_PDEV_MAX_NAME_LEN 23 -+#define INT3472_MAX_SENSOR_GPIOS 3 -+#define GPIO_REGULATOR_NAME_LENGTH 27 -+#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9 -+ -+#define INT3472_REGULATOR(_NAME, _SUPPLY, _OPS) \ -+ (const struct regulator_desc) { \ -+ .name = _NAME, \ -+ .supply_name = _SUPPLY, \ -+ .id = 0, \ -+ .type = REGULATOR_VOLTAGE, \ -+ .ops = _OPS, \ -+ .owner = THIS_MODULE, \ -+ } -+ -+#define INT3472_GPIO_FUNCTION_REMAP(_PIN, _FUNCTION) \ -+ (const struct int3472_gpio_function_remap) { \ -+ .documented = _PIN, \ -+ .actual = _FUNCTION \ -+ } -+ -+#define to_int3472_clk(hw) \ -+ container_of(hw, struct int3472_gpio_clock, clk_hw) -+ -+struct int3472_cldb { -+ u8 version; -+ /* -+ * control logic type -+ * 0: UNKNOWN -+ * 1: DISCRETE(CRD-D) -+ * 2: PMIC TPS68470 -+ * 3: PMIC uP6641 -+ */ -+ u8 control_logic_type; -+ u8 control_logic_id; -+ u8 sensor_card_sku; -+ u8 reserved[28]; -+}; -+ -+struct int3472_gpio_regulator { -+ char regulator_name[GPIO_REGULATOR_NAME_LENGTH]; -+ char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH]; -+ struct gpio_desc *gpio; -+ struct regulator_dev *rdev; -+ struct regulator_desc rdesc; -+}; -+ -+struct int3472_gpio_clock { -+ struct clk *clk; -+ struct clk_hw clk_hw; -+ struct gpio_desc *gpio; -+}; -+ -+struct int3472_device { -+ struct acpi_device *adev; -+ struct platform_device *pdev; -+ struct acpi_device *sensor; -+ char *sensor_name; -+ -+ unsigned int n_gpios; /* how many GPIOs have we seen */ -+ -+ struct int3472_gpio_regulator regulator; -+ struct int3472_gpio_clock clock; -+ -+ unsigned int n_sensor_gpios; /* how many have we mapped to sensor */ -+ bool gpios_mapped; -+ struct gpiod_lookup_table gpios; -+}; -+ -+struct int3472_gpio_function_remap { -+ char *documented; -+ char *actual; -+}; -+ -+struct int3472_sensor_config { -+ char *sensor_module_name; -+ struct regulator_consumer_supply supply_map; -+ const struct int3472_gpio_function_remap *function_maps; -+}; -+ -+int skl_int3472_discrete_probe(struct platform_device *pdev); -+int skl_int3472_discrete_remove(struct platform_device *pdev); -+int skl_int3472_tps68470_probe(struct i2c_client *client); -+int skl_int3472_get_cldb_buffer(struct acpi_device *adev, -+ struct int3472_cldb *cldb); -diff --git a/drivers/platform/x86/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel_skl_int3472_discrete.c -new file mode 100644 -index 000000000000..ea7e57f3e3f0 ---- /dev/null -+++ b/drivers/platform/x86/intel_skl_int3472_discrete.c -@@ -0,0 +1,489 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "intel_skl_int3472_common.h" -+ -+/* 79234640-9e10-4fea-a5c1b5aa8b19756f */ -+static const guid_t int3472_gpio_guid = -+ GUID_INIT(0x79234640, 0x9e10, 0x4fea, -+ 0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f); -+ -+/* 822ace8f-2814-4174-a56b5f029fe079ee */ -+static const guid_t cio2_sensor_module_guid = -+ GUID_INIT(0x822ace8f, 0x2814, 0x4174, -+ 0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee); -+ -+/* -+ * Here follows platform specific mapping information that we can pass to -+ * the functions mapping resources to the sensors. Where the sensors have -+ * a power enable pin defined in DSDT we need to provide a supply name so -+ * the sensor drivers can find the regulator. Optionally, we can provide a -+ * NULL terminated array of function name mappings to deal with any platform -+ * specific deviations from the documented behaviour of GPIOs. -+ * -+ * Map a GPIO function name to NULL to prevent the driver from mapping that -+ * GPIO at all. -+ */ -+ -+static const struct int3472_gpio_function_remap ov2680_gpio_function_remaps[] = { -+ INT3472_GPIO_FUNCTION_REMAP("reset", NULL), -+ INT3472_GPIO_FUNCTION_REMAP("powerdown", "reset"), -+ { } -+}; -+ -+static struct int3472_sensor_config int3472_sensor_configs[] = { -+ /* Lenovo Miix 510-12ISK - OV2680, Front */ -+ { "GNDF140809R", { 0 }, ov2680_gpio_function_remaps}, -+ /* Lenovo Miix 510-12ISK - OV5648, Rear */ -+ { "GEFF150023R", REGULATOR_SUPPLY("avdd", "i2c-OVTI5648:00"), NULL}, -+ /* Surface Go 1&2 - OV5693, Front */ -+ { "YHCU", REGULATOR_SUPPLY("avdd", "i2c-INT33BE:00"), NULL}, -+}; -+ -+/* -+ * The regulators have to have .ops to be valid, but the only ops we actually -+ * support are .enable and .disable which are handled via .ena_gpiod. Pass an -+ * empty struct to clear the check without lying about capabilities. -+ */ -+static const struct regulator_ops int3472_gpio_regulator_ops = { 0 }; -+ -+static int skl_int3472_clk_enable(struct clk_hw *hw) -+{ -+ struct int3472_gpio_clock *clk = to_int3472_clk(hw); -+ -+ gpiod_set_value(clk->gpio, 1); -+ -+ return 0; -+} -+ -+static void skl_int3472_clk_disable(struct clk_hw *hw) -+{ -+ struct int3472_gpio_clock *clk = to_int3472_clk(hw); -+ -+ gpiod_set_value(clk->gpio, 0); -+} -+ -+static int skl_int3472_clk_prepare(struct clk_hw *hw) -+{ -+ /* -+ * We're just turning a GPIO on to enable, so nothing to do here, but -+ * we want to provide the op so prepare_enable() works. -+ */ -+ return 0; -+} -+ -+static void skl_int3472_clk_unprepare(struct clk_hw *hw) -+{ -+ /* Likewise, nothing to do here... */ -+} -+ -+static const struct clk_ops skl_int3472_clock_ops = { -+ .prepare = skl_int3472_clk_prepare, -+ .unprepare = skl_int3472_clk_unprepare, -+ .enable = skl_int3472_clk_enable, -+ .disable = skl_int3472_clk_disable, -+}; -+ -+static struct int3472_sensor_config * -+int3472_get_sensor_module_config(struct int3472_device *int3472) -+{ -+ unsigned int i = ARRAY_SIZE(int3472_sensor_configs); -+ struct int3472_sensor_config *ret; -+ union acpi_object *obj; -+ -+ obj = acpi_evaluate_dsm_typed(int3472->sensor->handle, -+ &cio2_sensor_module_guid, 0x00, -+ 0x01, NULL, ACPI_TYPE_STRING); -+ -+ if (!obj) { -+ dev_err(&int3472->pdev->dev, -+ "Failed to get sensor module string from _DSM\n"); -+ return ERR_PTR(-ENODEV); -+ } -+ -+ if (obj->string.type != ACPI_TYPE_STRING) { -+ dev_err(&int3472->pdev->dev, -+ "Sensor _DSM returned a non-string value\n"); -+ ret = ERR_PTR(-EINVAL); -+ goto out_free_obj; -+ } -+ -+ ret = ERR_PTR(-ENODEV); -+ while (i--) { -+ if (!strcmp(int3472_sensor_configs[i].sensor_module_name, -+ obj->string.pointer)) { -+ ret = &int3472_sensor_configs[i]; -+ goto out_free_obj; -+ } -+ } -+ -+out_free_obj: -+ ACPI_FREE(obj); -+ return ret; -+} -+ -+static int int3472_map_gpio_to_sensor(struct int3472_device *int3472, -+ struct acpi_resource *ares, -+ char *func, u32 polarity) -+{ -+ char *path = ares->data.gpio.resource_source.string_ptr; -+ struct int3472_sensor_config *sensor_config; -+ struct gpiod_lookup table_entry; -+ struct acpi_device *adev; -+ acpi_handle handle; -+ acpi_status status; -+ int ret; -+ -+ sensor_config = int3472_get_sensor_module_config(int3472); -+ if (!IS_ERR(sensor_config) && sensor_config->function_maps) { -+ unsigned int i = 0; -+ -+ while (sensor_config->function_maps[i].documented) { -+ if (!strcmp(func, sensor_config->function_maps[i].documented)) { -+ func = sensor_config->function_maps[i].actual; -+ -+ break; -+ } -+ -+ i++; -+ } -+ } -+ -+ if (!func) -+ return 0; -+ -+ if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { -+ dev_warn(&int3472->pdev->dev, "Too many GPIOs mapped\n"); -+ return -EINVAL; -+ } -+ -+ status = acpi_get_handle(NULL, path, &handle); -+ if (ACPI_FAILURE(status)) -+ return -EINVAL; -+ -+ ret = acpi_bus_get_device(handle, &adev); -+ if (ret) -+ return -ENODEV; -+ -+ table_entry = (struct gpiod_lookup)GPIO_LOOKUP_IDX(acpi_dev_name(adev), -+ ares->data.gpio.pin_table[0], -+ func, 0, polarity); -+ -+ memcpy(&int3472->gpios.table[int3472->n_sensor_gpios], &table_entry, -+ sizeof(table_entry)); -+ -+ int3472->n_sensor_gpios++; -+ -+ return 0; -+} -+ -+static int int3472_register_clock(struct int3472_device *int3472, -+ struct acpi_resource *ares) -+{ -+ char *path = ares->data.gpio.resource_source.string_ptr; -+ struct clk_init_data init = { }; -+ int ret = 0; -+ -+ init.name = kasprintf(GFP_KERNEL, "%s-clk", acpi_dev_name(int3472->adev)); -+ init.ops = &skl_int3472_clock_ops; -+ -+ int3472->clock.gpio = acpi_get_gpiod(path, ares->data.gpio.pin_table[0]); -+ if (IS_ERR(int3472->clock.gpio)) { -+ ret = PTR_ERR(int3472->clock.gpio); -+ goto out_free_init_name; -+ } -+ -+ int3472->clock.clk_hw.init = &init; -+ int3472->clock.clk = clk_register(&int3472->adev->dev, -+ &int3472->clock.clk_hw); -+ if (IS_ERR(int3472->clock.clk)) { -+ ret = PTR_ERR(int3472->clock.clk); -+ goto err_put_gpio; -+ } -+ -+ ret = clk_register_clkdev(int3472->clock.clk, "xvclk", int3472->sensor_name); -+ if (ret) -+ goto err_unregister_clk; -+ -+ goto out_free_init_name; -+ -+err_unregister_clk: -+ clk_unregister(int3472->clock.clk); -+err_put_gpio: -+ gpiod_put(int3472->clock.gpio); -+out_free_init_name: -+ kfree(init.name); -+ -+ return ret; -+} -+ -+static int int3472_register_regulator(struct int3472_device *int3472, -+ struct acpi_resource *ares) -+{ -+ char *path = ares->data.gpio.resource_source.string_ptr; -+ struct int3472_sensor_config *sensor_config; -+ struct regulator_init_data init_data = { }; -+ struct int3472_gpio_regulator *regulator; -+ struct regulator_config cfg = { }; -+ int ret; -+ -+ sensor_config = int3472_get_sensor_module_config(int3472); -+ if (IS_ERR_OR_NULL(sensor_config)) { -+ dev_err(&int3472->pdev->dev, "No sensor module config\n"); -+ return PTR_ERR(sensor_config); -+ } -+ -+ if (!sensor_config->supply_map.supply) { -+ dev_err(&int3472->pdev->dev, "No supply name defined\n"); -+ return -ENODEV; -+ } -+ -+ init_data.supply_regulator = NULL; -+ init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; -+ init_data.num_consumer_supplies = 1; -+ init_data.consumer_supplies = &sensor_config->supply_map; -+ -+ snprintf(int3472->regulator.regulator_name, GPIO_REGULATOR_NAME_LENGTH, -+ "int3472-discrete-regulator"); -+ snprintf(int3472->regulator.supply_name, GPIO_REGULATOR_SUPPLY_NAME_LENGTH, -+ "supply-0"); -+ -+ int3472->regulator.rdesc = INT3472_REGULATOR(int3472->regulator.regulator_name, -+ int3472->regulator.supply_name, -+ &int3472_gpio_regulator_ops); -+ -+ int3472->regulator.gpio = acpi_get_gpiod(path, ares->data.gpio.pin_table[0]); -+ if (IS_ERR(int3472->regulator.gpio)) { -+ ret = PTR_ERR(int3472->regulator.gpio); -+ goto err_free_regulator; -+ } -+ -+ cfg.dev = &int3472->adev->dev; -+ cfg.init_data = &init_data; -+ cfg.ena_gpiod = int3472->regulator.gpio; -+ -+ int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc, &cfg); -+ if (IS_ERR(int3472->regulator.rdev)) { -+ ret = PTR_ERR(int3472->regulator.rdev); -+ goto err_free_gpio; -+ } -+ -+ return 0; -+ -+err_free_gpio: -+ gpiod_put(regulator->gpio); -+err_free_regulator: -+ kfree(regulator); -+ -+ return ret; -+} -+ -+/** -+ * int3472_handle_gpio_resources: maps PMIC resources to consuming sensor -+ * @ares: A pointer to a &struct acpi_resource -+ * @data: A pointer to a &struct int3472_device -+ * -+ * This function handles GPIO resources that are against an INT3472 -+ * ACPI device, by checking the value of the corresponding _DSM entry. -+ * This will return a 32bit int, where the lowest byte represents the -+ * function of the GPIO pin: -+ * -+ * 0x00 Reset -+ * 0x01 Power down -+ * 0x0b Power enable -+ * 0x0c Clock enable -+ * 0x0d Privacy LED -+ * -+ * There are some known platform specific quirks where that does not quite -+ * hold up; for example where a pin with type 0x01 (Power down) is mapped to -+ * a sensor pin that performs a reset function. These will be handled by the -+ * mapping sub-functions. -+ * -+ * GPIOs will either be mapped directly to the sensor device or else used -+ * to create clocks and regulators via the usual frameworks. -+ * -+ * Return: -+ * * 0 - When all resources found are handled properly. -+ * * -EINVAL - If the resource is not a GPIO IO resource -+ * * -ENODEV - If the resource has no corresponding _DSM entry -+ * * -Other - Errors propagated from one of the sub-functions. -+ */ -+static int int3472_handle_gpio_resources(struct acpi_resource *ares, -+ void *data) -+{ -+ struct int3472_device *int3472 = data; -+ union acpi_object *obj; -+ int ret = 0; -+ -+ if (ares->type != ACPI_RESOURCE_TYPE_GPIO || -+ ares->data.gpio.connection_type != ACPI_RESOURCE_GPIO_TYPE_IO) -+ return EINVAL; /* Deliberately positive so parsing continues */ -+ -+ /* -+ * n_gpios + 2 because the index of this _DSM function is 1-based and -+ * the first function is just a count. -+ */ -+ obj = acpi_evaluate_dsm_typed(int3472->adev->handle, -+ &int3472_gpio_guid, 0x00, -+ int3472->n_gpios + 2, -+ NULL, ACPI_TYPE_INTEGER); -+ -+ if (!obj) { -+ dev_warn(&int3472->pdev->dev, -+ "No _DSM entry for this GPIO pin\n"); -+ return ENODEV; -+ } -+ -+ switch (obj->integer.value & 0xff) { -+ case INT3472_GPIO_TYPE_RESET: -+ ret = int3472_map_gpio_to_sensor(int3472, ares, "reset", -+ GPIO_ACTIVE_LOW); -+ if (ret) -+ dev_err(&int3472->pdev->dev, -+ "Failed to map reset pin to sensor\n"); -+ -+ break; -+ case INT3472_GPIO_TYPE_POWERDOWN: -+ ret = int3472_map_gpio_to_sensor(int3472, ares, "powerdown", -+ GPIO_ACTIVE_LOW); -+ if (ret) -+ dev_err(&int3472->pdev->dev, -+ "Failed to map powerdown pin to sensor\n"); -+ -+ break; -+ case INT3472_GPIO_TYPE_CLK_ENABLE: -+ ret = int3472_register_clock(int3472, ares); -+ if (ret) -+ dev_err(&int3472->pdev->dev, -+ "Failed to map clock to sensor\n"); -+ -+ break; -+ case INT3472_GPIO_TYPE_POWER_ENABLE: -+ ret = int3472_register_regulator(int3472, ares); -+ if (ret) { -+ dev_err(&int3472->pdev->dev, -+ "Failed to map regulator to sensor\n"); -+ } -+ -+ break; -+ case INT3472_GPIO_TYPE_PRIVACY_LED: -+ ret = int3472_map_gpio_to_sensor(int3472, ares, "indicator-led", -+ GPIO_ACTIVE_HIGH); -+ if (ret) -+ dev_err(&int3472->pdev->dev, -+ "Failed to map indicator led to sensor\n"); -+ -+ break; -+ default: -+ dev_warn(&int3472->pdev->dev, -+ "GPIO type 0x%llx unknown; the sensor may not work\n", -+ (obj->integer.value & 0xff)); -+ ret = EINVAL; -+ } -+ -+ int3472->n_gpios++; -+ ACPI_FREE(obj); -+ -+ return ret; -+} -+ -+static int int3472_parse_crs(struct int3472_device *int3472) -+{ -+ struct list_head resource_list; -+ int ret = 0; -+ -+ INIT_LIST_HEAD(&resource_list); -+ -+ ret = acpi_dev_get_resources(int3472->adev, &resource_list, -+ int3472_handle_gpio_resources, int3472); -+ -+ if (!ret) { -+ gpiod_add_lookup_table(&int3472->gpios); -+ int3472->gpios_mapped = true; -+ } -+ -+ acpi_dev_free_resource_list(&resource_list); -+ -+ return ret; -+} -+ -+int skl_int3472_discrete_probe(struct platform_device *pdev) -+{ -+ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); -+ struct int3472_device *int3472; -+ struct int3472_cldb cldb; -+ int ret = 0; -+ -+ ret = skl_int3472_get_cldb_buffer(adev, &cldb); -+ if (ret || cldb.control_logic_type != 1) -+ return -EINVAL; -+ -+ int3472 = kzalloc(sizeof(*int3472) + -+ ((INT3472_MAX_SENSOR_GPIOS + 1) * sizeof(struct gpiod_lookup)), -+ GFP_KERNEL); -+ if (!int3472) -+ return -ENOMEM; -+ -+ int3472->adev = adev; -+ int3472->pdev = pdev; -+ platform_set_drvdata(pdev, int3472); -+ -+ int3472->sensor = acpi_dev_get_next_dep_dev(adev, NULL); -+ if (!int3472->sensor) { -+ dev_err(&pdev->dev, -+ "This INT3472 entry seems to have no dependents.\n"); -+ ret = -ENODEV; -+ goto err_free_int3472; -+ } -+ int3472->sensor_name = i2c_acpi_dev_name(int3472->sensor); -+ int3472->gpios.dev_id = int3472->sensor_name; -+ -+ ret = int3472_parse_crs(int3472); -+ if (ret) { -+ skl_int3472_discrete_remove(pdev); -+ goto err_return_ret; -+ } -+ -+ return 0; -+ -+err_free_int3472: -+ kfree(int3472); -+err_return_ret: -+ return ret; -+} -+ -+int skl_int3472_discrete_remove(struct platform_device *pdev) -+{ -+ struct int3472_device *int3472; -+ -+ int3472 = platform_get_drvdata(pdev); -+ -+ if (int3472->gpios_mapped) -+ gpiod_remove_lookup_table(&int3472->gpios); -+ -+ if (!IS_ERR_OR_NULL(int3472->regulator.rdev)) { -+ gpiod_put(int3472->regulator.gpio); -+ regulator_unregister(int3472->regulator.rdev); -+ } -+ -+ if (!IS_ERR_OR_NULL(int3472->clock.clk)) { -+ gpiod_put(int3472->clock.gpio); -+ clk_unregister(int3472->clock.clk); -+ } -+ -+ acpi_dev_put(int3472->sensor); -+ -+ kfree(int3472->sensor_name); -+ kfree(int3472); -+ -+ return 0; -+} -diff --git a/drivers/platform/x86/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel_skl_int3472_tps68470.c -new file mode 100644 -index 000000000000..3fe27ec0caff ---- /dev/null -+++ b/drivers/platform/x86/intel_skl_int3472_tps68470.c -@@ -0,0 +1,145 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+#include -+ -+#include "intel_skl_int3472_common.h" -+ -+static const struct regmap_config tps68470_regmap_config = { -+ .reg_bits = 8, -+ .val_bits = 8, -+ .max_register = TPS68470_REG_MAX, -+}; -+ -+static int tps68470_chip_init(struct device *dev, struct regmap *regmap) -+{ -+ unsigned int version; -+ int ret; -+ -+ /* Force software reset */ -+ ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); -+ if (ret) -+ return ret; -+ -+ ret = regmap_read(regmap, TPS68470_REG_REVID, &version); -+ if (ret) { -+ dev_err(dev, "Failed to read revision register: %d\n", ret); -+ return ret; -+ } -+ -+ dev_info(dev, "TPS68470 REVID: 0x%x\n", version); -+ -+ return 0; -+} -+ -+static struct platform_device * -+skl_int3472_register_pdev(const char *name, struct device *parent) -+{ -+ struct platform_device *pdev; -+ int ret; -+ -+ pdev = platform_device_alloc(name, PLATFORM_DEVID_NONE); -+ if (IS_ERR_OR_NULL(pdev)) -+ return ERR_PTR(-ENOMEM); -+ -+ pdev->dev.parent = parent; -+ pdev->driver_override = kstrndup(pdev->name, INT3472_PDEV_MAX_NAME_LEN, -+ GFP_KERNEL); -+ -+ ret = platform_device_add(pdev); -+ if (ret) { -+ platform_device_put(pdev); -+ return ERR_PTR(ret); -+ } -+ -+ return pdev; -+} -+ -+int skl_int3472_tps68470_probe(struct i2c_client *client) -+{ -+ struct acpi_device *adev = ACPI_COMPANION(&client->dev); -+ struct platform_device *regulator_dev; -+ struct platform_device *opregion_dev; -+ struct platform_device *gpio_dev; -+ struct int3472_cldb cldb = { 0 }; -+ struct platform_device *clk_dev; -+ bool cldb_present = true; -+ struct regmap *regmap; -+ int ret = 0; -+ -+ regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); -+ if (IS_ERR(regmap)) { -+ dev_err(&client->dev, "devm_regmap_init_i2c Error %ld\n", -+ PTR_ERR(regmap)); -+ return PTR_ERR(regmap); -+ } -+ -+ i2c_set_clientdata(client, regmap); -+ -+ ret = tps68470_chip_init(&client->dev, regmap); -+ if (ret < 0) { -+ dev_err(&client->dev, "TPS68470 Init Error %d\n", ret); -+ return ret; -+ } -+ -+ /* -+ * Check CLDB buffer against the PMIC's adev. If present, then we check -+ * the value of control_logic_type field and follow one of the following -+ * scenarios: -+ * -+ * 1. No CLDB - likely ACPI tables designed for ChromeOS. We create -+ * platform devices for the GPIOs and OpRegion drivers. -+ * -+ * 2. CLDB, with control_logic_type = 2 - probably ACPI tables made -+ * for Windows 2-in-1 platforms. Register pdevs for GPIO, Clock and -+ * Regulator drivers to bind to. -+ * -+ * 3. Any other value in control_logic_type, we should never have -+ * gotten to this point; crash and burn. -+ */ -+ ret = skl_int3472_get_cldb_buffer(adev, &cldb); -+ if (!ret && cldb.control_logic_type != 2) -+ return -EINVAL; -+ -+ if (ret) -+ cldb_present = false; -+ -+ gpio_dev = skl_int3472_register_pdev("tps68470-gpio", &client->dev); -+ if (IS_ERR(gpio_dev)) -+ return PTR_ERR(gpio_dev); -+ -+ if (cldb_present) { -+ clk_dev = skl_int3472_register_pdev("tps68470-clk", -+ &client->dev); -+ if (IS_ERR(clk_dev)) { -+ ret = PTR_ERR(clk_dev); -+ goto err_free_gpio; -+ } -+ -+ regulator_dev = skl_int3472_register_pdev("tps68470-regulator", -+ &client->dev); -+ if (IS_ERR(regulator_dev)) { -+ ret = PTR_ERR(regulator_dev); -+ goto err_free_clk; -+ } -+ } else { -+ opregion_dev = skl_int3472_register_pdev("tps68470_pmic_opregion", -+ &client->dev); -+ if (IS_ERR(opregion_dev)) { -+ ret = PTR_ERR(opregion_dev); -+ goto err_free_gpio; -+ } -+ } -+ -+ return 0; -+ -+err_free_clk: -+ platform_device_put(clk_dev); -+err_free_gpio: -+ platform_device_put(gpio_dev); -+ -+ return ret; -+} --- -2.31.1 - -From 79abed3ac8ba43d256cdcc026ac5614563affeb2 Mon Sep 17 00:00:00 2001 -From: Jake Day -Date: Fri, 25 Sep 2020 10:24:53 -0400 -Subject: [PATCH] media: i2c: Add support for the OV5693 image sensor - -The OV5693 is a 5 Mpx CMOS image sensor, connected via MIPI CSI-2 -in a one or two lane configuration. - -Signed-off-by: Jean-Michel Hautbois -Patchset: cameras ---- - drivers/media/i2c/Kconfig | 11 + - drivers/media/i2c/Makefile | 1 + - drivers/media/i2c/ad5823.h | 63 ++ - drivers/media/i2c/ov5693.c | 1788 ++++++++++++++++++++++++++++++++++++ - drivers/media/i2c/ov5693.h | 1430 ++++++++++++++++++++++++++++ - 5 files changed, 3293 insertions(+) - create mode 100644 drivers/media/i2c/ad5823.h - create mode 100644 drivers/media/i2c/ov5693.c - create mode 100644 drivers/media/i2c/ov5693.h - -diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig -index 6eed3209ee2d..fc96e3d4764b 100644 ---- a/drivers/media/i2c/Kconfig -+++ b/drivers/media/i2c/Kconfig -@@ -972,6 +972,17 @@ config VIDEO_OV5675 - To compile this driver as a module, choose M here: the - module will be called ov5675. - -+config VIDEO_OV5693 -+ tristate "OmniVision OV5693 sensor support" -+ depends on I2C && VIDEO_V4L2 -+ select V4L2_FWNODE -+ help -+ This is a Video4Linux2 sensor driver for the OmniVision -+ OV5693 camera. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ov5693. -+ - config VIDEO_OV5695 - tristate "OmniVision OV5695 sensor support" - depends on I2C && VIDEO_V4L2 -diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile -index a3149dce21bb..cac649668a4e 100644 ---- a/drivers/media/i2c/Makefile -+++ b/drivers/media/i2c/Makefile -@@ -74,6 +74,7 @@ obj-$(CONFIG_VIDEO_OV5645) += ov5645.o - obj-$(CONFIG_VIDEO_OV5647) += ov5647.o - obj-$(CONFIG_VIDEO_OV5670) += ov5670.o - obj-$(CONFIG_VIDEO_OV5675) += ov5675.o -+obj-$(CONFIG_VIDEO_OV5693) += ov5693.o - obj-$(CONFIG_VIDEO_OV5695) += ov5695.o - obj-$(CONFIG_VIDEO_OV6650) += ov6650.o - obj-$(CONFIG_VIDEO_OV7251) += ov7251.o -diff --git a/drivers/media/i2c/ad5823.h b/drivers/media/i2c/ad5823.h -new file mode 100644 -index 000000000000..f1362cd69f6e ---- /dev/null -+++ b/drivers/media/i2c/ad5823.h -@@ -0,0 +1,63 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* -+ * Support for AD5823 VCM. -+ * -+ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. -+ * -+ * 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 __AD5823_H__ -+#define __AD5823_H__ -+ -+#include -+ -+#define AD5823_VCM_ADDR 0x0c -+ -+#define AD5823_REG_RESET 0x01 -+#define AD5823_REG_MODE 0x02 -+#define AD5823_REG_VCM_MOVE_TIME 0x03 -+#define AD5823_REG_VCM_CODE_MSB 0x04 -+#define AD5823_REG_VCM_CODE_LSB 0x05 -+#define AD5823_REG_VCM_THRESHOLD_MSB 0x06 -+#define AD5823_REG_VCM_THRESHOLD_LSB 0x07 -+ -+#define AD5823_REG_LENGTH 0x1 -+ -+#define AD5823_RING_CTRL_ENABLE 0x04 -+#define AD5823_RING_CTRL_DISABLE 0x00 -+ -+#define AD5823_RESONANCE_PERIOD 100000 -+#define AD5823_RESONANCE_COEF 512 -+#define AD5823_HIGH_FREQ_RANGE 0x80 -+ -+#define VCM_CODE_MSB_MASK 0xfc -+#define AD5823_INIT_FOCUS_POS 350 -+ -+enum ad5823_tok_type { -+ AD5823_8BIT = 0x1, -+ AD5823_16BIT = 0x2, -+}; -+ -+enum ad5823_vcm_mode { -+ AD5823_ARC_RES0 = 0x0, /* Actuator response control RES1 */ -+ AD5823_ARC_RES1 = 0x1, /* Actuator response control RES0.5 */ -+ AD5823_ARC_RES2 = 0x2, /* Actuator response control RES2 */ -+ AD5823_ESRC = 0x3, /* Enhanced slew rate control */ -+ AD5823_DIRECT = 0x4, /* Direct control */ -+}; -+ -+#define AD5823_INVALID_CONFIG 0xffffffff -+#define AD5823_MAX_FOCUS_POS 1023 -+#define DELAY_PER_STEP_NS 1000000 -+#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) -+#endif -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -new file mode 100644 -index 000000000000..32485e4ed42b ---- /dev/null -+++ b/drivers/media/i2c/ov5693.c -@@ -0,0 +1,1788 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Support for OmniVision OV5693 1080p HD camera sensor. -+ * -+ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. -+ * -+ * This program is free software; you can redistribute it and/or -+ * modify it under the terms of the GNU General Public License version -+ * 2 as published by the Free Software Foundation. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "ov5693.h" -+#include "ad5823.h" -+ -+#define __cci_delay(t) \ -+ do { \ -+ if ((t) < 10) { \ -+ usleep_range((t) * 1000, ((t) + 1) * 1000); \ -+ } else { \ -+ msleep((t)); \ -+ } \ -+ } while (0) -+ -+/* Value 30ms reached through experimentation on byt ecs. -+ * The DS specifies a much lower value but when using a smaller value -+ * the I2C bus sometimes locks up permanently when starting the camera. -+ * This issue could not be reproduced on cht, so we can reduce the -+ * delay value to a lower value when insmod. -+ */ -+static uint up_delay = 30; -+module_param(up_delay, uint, 0644); -+MODULE_PARM_DESC(up_delay, -+ "Delay prior to the first CCI transaction for ov5693"); -+ -+ -+/* Exposure/gain */ -+ -+#define OV5693_EXPOSURE_CTRL_HH_REG 0x3500 -+#define OV5693_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(18, 16)) >> 16) -+#define OV5693_EXPOSURE_CTRL_H_REG 0x3501 -+#define OV5693_EXPOSURE_CTRL_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_EXPOSURE_CTRL_L_REG 0x3502 -+#define OV5693_EXPOSURE_CTRL_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_EXPOSURE_GAIN_MANUAL_REG 0x3509 -+ -+#define OV5693_GAIN_CTRL_H_REG 0x3504 -+#define OV5693_GAIN_CTRL_H(v) (((v) & GENMASK(9, 8)) >> 8) -+#define OV5693_GAIN_CTRL_L_REG 0x3505 -+#define OV5693_GAIN_CTRL_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_FORMAT1_REG 0x3820 -+#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -+#define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) -+#define OV5693_FORMAT2_REG 0x3821 -+#define OV5693_FORMAT2_HSYNC_EN BIT(6) -+#define OV5693_FORMAT2_FST_VBIN_EN BIT(5) -+#define OV5693_FORMAT2_FST_HBIN_EN BIT(4) -+#define OV5693_FORMAT2_ISP_HORZ_VAR2_EN BIT(3) -+#define OV5693_FORMAT2_FLIP_HORZ_ISP_EN BIT(2) -+#define OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN BIT(1) -+#define OV5693_FORMAT2_SYNC_HBIN_EN BIT(0) -+ -+/* ISP */ -+ -+#define OV5693_ISP_CTRL0_REG 0x5000 -+#define OV5693_ISP_CTRL0_LENC_EN BIT(7) -+#define OV5693_ISP_CTRL0_WHITE_BALANCE_EN BIT(4) -+#define OV5693_ISP_CTRL0_DPC_BLACK_EN BIT(2) -+#define OV5693_ISP_CTRL0_DPC_WHITE_EN BIT(1) -+#define OV5693_ISP_CTRL1_REG 0x5001 -+#define OV5693_ISP_CTRL1_BLC_EN BIT(0) -+ -+/* native and active pixel array size. */ -+#define OV5693_NATIVE_WIDTH 2688U -+#define OV5693_NATIVE_HEIGHT 1984U -+#define OV5693_PIXEL_ARRAY_LEFT 48U -+#define OV5693_PIXEL_ARRAY_TOP 20U -+#define OV5693_PIXEL_ARRAY_WIDTH 2592U -+#define OV5693_PIXEL_ARRAY_HEIGHT 1944U -+ -+#define OV5693_PPL_DEFAULT 2800 -+ -+static int vcm_ad_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) -+{ -+ int err; -+ struct i2c_msg msg; -+ u8 buf[2]; -+ -+ buf[0] = reg; -+ buf[1] = val; -+ -+ msg.addr = VCM_ADDR; -+ msg.flags = 0; -+ msg.len = 2; -+ msg.buf = &buf[0]; -+ -+ err = i2c_transfer(client->adapter, &msg, 1); -+ if (err != 1) { -+ dev_err(&client->dev, "%s: vcm i2c fail, err code = %d\n", -+ __func__, err); -+ return -EIO; -+ } -+ return 0; -+} -+ -+static int ad5823_i2c_write(struct i2c_client *client, u8 reg, u8 val) -+{ -+ struct i2c_msg msg; -+ u8 buf[2]; -+ -+ buf[0] = reg; -+ buf[1] = val; -+ msg.addr = AD5823_VCM_ADDR; -+ msg.flags = 0; -+ msg.len = 0x02; -+ msg.buf = &buf[0]; -+ -+ if (i2c_transfer(client->adapter, &msg, 1) != 1) -+ return -EIO; -+ return 0; -+} -+ -+static int ad5823_i2c_read(struct i2c_client *client, u8 reg, u8 *val) -+{ -+ struct i2c_msg msg[2]; -+ u8 buf[2]; -+ -+ buf[0] = reg; -+ buf[1] = 0; -+ -+ msg[0].addr = AD5823_VCM_ADDR; -+ msg[0].flags = 0; -+ msg[0].len = 0x01; -+ msg[0].buf = &buf[0]; -+ -+ msg[1].addr = 0x0c; -+ msg[1].flags = I2C_M_RD; -+ msg[1].len = 0x01; -+ msg[1].buf = &buf[1]; -+ *val = 0; -+ if (i2c_transfer(client->adapter, msg, 2) != 2) -+ return -EIO; -+ *val = buf[1]; -+ return 0; -+} -+ -+static const u32 ov5693_embedded_effective_size = 28; -+ -+/* i2c read/write stuff */ -+static int ov5693_read_reg(struct i2c_client *client, -+ u16 data_length, u16 reg, u16 *val) -+{ -+ int err; -+ struct i2c_msg msg[2]; -+ unsigned char data[6]; -+ -+ if (!client->adapter) { -+ dev_err(&client->dev, "%s error, no client->adapter\n", -+ __func__); -+ return -ENODEV; -+ } -+ -+ if (data_length != OV5693_8BIT && data_length != OV5693_16BIT -+ && data_length != OV5693_32BIT) { -+ dev_err(&client->dev, "%s error, invalid data length\n", -+ __func__); -+ return -EINVAL; -+ } -+ -+ memset(msg, 0, sizeof(msg)); -+ -+ msg[0].addr = client->addr; -+ msg[0].flags = 0; -+ msg[0].len = I2C_MSG_LENGTH; -+ msg[0].buf = data; -+ -+ /* high byte goes out first */ -+ data[0] = (u8)(reg >> 8); -+ data[1] = (u8)(reg & 0xff); -+ -+ msg[1].addr = client->addr; -+ msg[1].len = data_length; -+ msg[1].flags = I2C_M_RD; -+ msg[1].buf = data; -+ -+ err = i2c_transfer(client->adapter, msg, 2); -+ if (err != 2) { -+ if (err >= 0) -+ err = -EIO; -+ dev_err(&client->dev, -+ "read from offset 0x%x error %d", reg, err); -+ return err; -+ } -+ -+ *val = 0; -+ /* high byte comes first */ -+ if (data_length == OV5693_8BIT) -+ *val = (u8)data[0]; -+ else if (data_length == OV5693_16BIT) -+ *val = be16_to_cpu(*(__be16 *)&data[0]); -+ else -+ *val = be32_to_cpu(*(__be32 *)&data[0]); -+ -+ return 0; -+} -+ -+static int ov5693_i2c_write(struct i2c_client *client, u16 len, u8 *data) -+{ -+ struct i2c_msg msg; -+ const int num_msg = 1; -+ int ret; -+ -+ msg.addr = client->addr; -+ msg.flags = 0; -+ msg.len = len; -+ msg.buf = data; -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ return ret == num_msg ? 0 : -EIO; -+} -+ -+static int vcm_dw_i2c_write(struct i2c_client *client, u16 data) -+{ -+ struct i2c_msg msg; -+ const int num_msg = 1; -+ int ret; -+ __be16 val; -+ -+ val = cpu_to_be16(data); -+ msg.addr = VCM_ADDR; -+ msg.flags = 0; -+ msg.len = OV5693_16BIT; -+ msg.buf = (void *)&val; -+ -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ return ret == num_msg ? 0 : -EIO; -+} -+ -+/* -+ * Theory: per datasheet, the two VCMs both allow for a 2-byte read. -+ * The DW9714 doesn't actually specify what this does (it has a -+ * two-byte write-only protocol, but specifies the read sequence as -+ * legal), but it returns the same data (zeroes) always, after an -+ * undocumented initial NAK. The AD5823 has a one-byte address -+ * register to which all writes go, and subsequent reads will cycle -+ * through the 8 bytes of registers. Notably, the default values (the -+ * device is always power-cycled affirmatively, so we can rely on -+ * these) in AD5823 are not pairwise repetitions of the same 16 bit -+ * word. So all we have to do is sequentially read two bytes at a -+ * time and see if we detect a difference in any of the first four -+ * pairs. -+ */ -+static int vcm_detect(struct i2c_client *client) -+{ -+ int i, ret; -+ struct i2c_msg msg; -+ u16 data0 = 0, data; -+ -+ for (i = 0; i < 4; i++) { -+ msg.addr = VCM_ADDR; -+ msg.flags = I2C_M_RD; -+ msg.len = sizeof(data); -+ msg.buf = (u8 *)&data; -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ /* -+ * DW9714 always fails the first read and returns -+ * zeroes for subsequent ones -+ */ -+ if (i == 0 && ret == -EREMOTEIO) { -+ data0 = 0; -+ continue; -+ } -+ -+ if (i == 0) -+ data0 = data; -+ -+ if (data != data0) -+ return VCM_AD5823; -+ } -+ return ret == 1 ? VCM_DW9714 : ret; -+} -+ -+static int ov5693_write_reg(struct i2c_client *client, u16 data_length, -+ u16 reg, u16 val) -+{ -+ int ret; -+ unsigned char data[4] = {0}; -+ __be16 *wreg = (void *)data; -+ const u16 len = data_length + sizeof(u16); /* 16-bit address + data */ -+ -+ if (data_length != OV5693_8BIT && data_length != OV5693_16BIT) { -+ dev_err(&client->dev, -+ "%s error, invalid data_length\n", __func__); -+ return -EINVAL; -+ } -+ -+ /* high byte goes out first */ -+ *wreg = cpu_to_be16(reg); -+ -+ if (data_length == OV5693_8BIT) { -+ data[2] = (u8)(val); -+ } else { -+ /* OV5693_16BIT */ -+ __be16 *wdata = (void *)&data[2]; -+ -+ *wdata = cpu_to_be16(val); -+ } -+ -+ ret = ov5693_i2c_write(client, len, data); -+ if (ret) -+ dev_err(&client->dev, -+ "write error: wrote 0x%x to offset 0x%x error %d", -+ val, reg, ret); -+ -+ return ret; -+} -+ -+/* -+ * ov5693_write_reg_array - Initializes a list of OV5693 registers -+ * @client: i2c driver client structure -+ * @reglist: list of registers to be written -+ * -+ * This function initializes a list of registers. When consecutive addresses -+ * are found in a row on the list, this function creates a buffer and sends -+ * consecutive data in a single i2c_transfer(). -+ * -+ * __ov5693_flush_reg_array, __ov5693_buf_reg_array() and -+ * __ov5693_write_reg_is_consecutive() are internal functions to -+ * ov5693_write_reg_array_fast() and should be not used anywhere else. -+ * -+ */ -+ -+static int __ov5693_flush_reg_array(struct i2c_client *client, -+ struct ov5693_write_ctrl *ctrl) -+{ -+ u16 size; -+ __be16 *reg = (void *)&ctrl->buffer.addr; -+ -+ if (ctrl->index == 0) -+ return 0; -+ -+ size = sizeof(u16) + ctrl->index; /* 16-bit address + data */ -+ -+ *reg = cpu_to_be16(ctrl->buffer.addr); -+ ctrl->index = 0; -+ -+ return ov5693_i2c_write(client, size, (u8 *)reg); -+} -+ -+static int __ov5693_buf_reg_array(struct i2c_client *client, -+ struct ov5693_write_ctrl *ctrl, -+ const struct ov5693_reg *next) -+{ -+ int size; -+ __be16 *data16; -+ -+ switch (next->type) { -+ case OV5693_8BIT: -+ size = 1; -+ ctrl->buffer.data[ctrl->index] = (u8)next->val; -+ break; -+ case OV5693_16BIT: -+ size = 2; -+ -+ data16 = (void *)&ctrl->buffer.data[ctrl->index]; -+ *data16 = cpu_to_be16((u16)next->val); -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ /* When first item is added, we need to store its starting address */ -+ if (ctrl->index == 0) -+ ctrl->buffer.addr = next->reg; -+ -+ ctrl->index += size; -+ -+ /* -+ * Buffer cannot guarantee free space for u32? Better flush it to avoid -+ * possible lack of memory for next item. -+ */ -+ if (ctrl->index + sizeof(u16) >= OV5693_MAX_WRITE_BUF_SIZE) -+ return __ov5693_flush_reg_array(client, ctrl); -+ -+ return 0; -+} -+ -+static int __ov5693_write_reg_is_consecutive(struct i2c_client *client, -+ struct ov5693_write_ctrl *ctrl, -+ const struct ov5693_reg *next) -+{ -+ if (ctrl->index == 0) -+ return 1; -+ -+ return ctrl->buffer.addr + ctrl->index == next->reg; -+} -+ -+static int ov5693_write_reg_array(struct i2c_client *client, -+ const struct ov5693_reg *reglist) -+{ -+ const struct ov5693_reg *next = reglist; -+ struct ov5693_write_ctrl ctrl; -+ int err; -+ -+ ctrl.index = 0; -+ for (; next->type != OV5693_TOK_TERM; next++) { -+ switch (next->type & OV5693_TOK_MASK) { -+ case OV5693_TOK_DELAY: -+ err = __ov5693_flush_reg_array(client, &ctrl); -+ if (err) -+ return err; -+ msleep(next->val); -+ break; -+ default: -+ /* -+ * If next address is not consecutive, data needs to be -+ * flushed before proceed. -+ */ -+ if (!__ov5693_write_reg_is_consecutive(client, &ctrl, -+ next)) { -+ err = __ov5693_flush_reg_array(client, &ctrl); -+ if (err) -+ return err; -+ } -+ err = __ov5693_buf_reg_array(client, &ctrl, next); -+ if (err) { -+ dev_err(&client->dev, -+ "%s: write error, aborted\n", -+ __func__); -+ return err; -+ } -+ break; -+ } -+ } -+ -+ return __ov5693_flush_reg_array(client, &ctrl); -+} -+ -+static int ov5693_read_otp_reg_array(struct i2c_client *client, u16 size, -+ u16 addr, u8 *buf) -+{ -+ u16 index; -+ int ret; -+ u16 *pVal = NULL; -+ -+ for (index = 0; index <= size; index++) { -+ pVal = (u16 *)(buf + index); -+ ret = -+ ov5693_read_reg(client, OV5693_8BIT, addr + index, -+ pVal); -+ if (ret) -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static int __ov5693_otp_read(struct v4l2_subdev *sd, u8 *buf) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ int ret; -+ int i; -+ u8 *b = buf; -+ -+ dev->otp_size = 0; -+ for (i = 1; i < OV5693_OTP_BANK_MAX; i++) { -+ /*set bank NO and OTP read mode. */ -+ ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_OTP_BANK_REG, -+ (i | 0xc0)); //[7:6] 2'b11 [5:0] bank no -+ if (ret) { -+ dev_err(&client->dev, "failed to prepare OTP page\n"); -+ return ret; -+ } -+ //dev_dbg(&client->dev, "write 0x%x->0x%x\n",OV5693_OTP_BANK_REG,(i|0xc0)); -+ -+ /*enable read */ -+ ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_OTP_READ_REG, -+ OV5693_OTP_MODE_READ); // enable :1 -+ if (ret) { -+ dev_err(&client->dev, -+ "failed to set OTP reading mode page"); -+ return ret; -+ } -+ //dev_dbg(&client->dev, "write 0x%x->0x%x\n", -+ // OV5693_OTP_READ_REG,OV5693_OTP_MODE_READ); -+ -+ /* Reading the OTP data array */ -+ ret = ov5693_read_otp_reg_array(client, OV5693_OTP_BANK_SIZE, -+ OV5693_OTP_START_ADDR, -+ b); -+ if (ret) { -+ dev_err(&client->dev, "failed to read OTP data\n"); -+ return ret; -+ } -+ -+ //dev_dbg(&client->dev, -+ // "BANK[%2d] %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", -+ // i, *b, *(b+1), *(b+2), *(b+3), *(b+4), *(b+5), *(b+6), *(b+7), -+ // *(b+8), *(b+9), *(b+10), *(b+11), *(b+12), *(b+13), *(b+14), *(b+15)); -+ -+ //Intel OTP map, try to read 320byts first. -+ if (i == 21) { -+ if ((*b) == 0) { -+ dev->otp_size = 320; -+ break; -+ } -+ /* (*b) != 0 */ -+ b = buf; -+ continue; -+ } else if (i == -+ 24) { //if the first 320bytes data doesn't not exist, try to read the next 32bytes data. -+ if ((*b) == 0) { -+ dev->otp_size = 32; -+ break; -+ } -+ /* (*b) != 0 */ -+ b = buf; -+ continue; -+ } else if (i == -+ 27) { //if the prvious 32bytes data doesn't exist, try to read the next 32bytes data again. -+ if ((*b) == 0) { -+ dev->otp_size = 32; -+ break; -+ } -+ /* (*b) != 0 */ -+ dev->otp_size = 0; // no OTP data. -+ break; -+ } -+ -+ b = b + OV5693_OTP_BANK_SIZE; -+ } -+ return 0; -+} -+ -+/* -+ * Read otp data and store it into a kmalloced buffer. -+ * The caller must kfree the buffer when no more needed. -+ * @size: set to the size of the returned otp data. -+ */ -+static void *ov5693_otp_read(struct v4l2_subdev *sd) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ u8 *buf; -+ int ret; -+ -+ buf = devm_kzalloc(&client->dev, (OV5693_OTP_DATA_SIZE + 16), GFP_KERNEL); -+ if (!buf) -+ return ERR_PTR(-ENOMEM); -+ -+ //otp valid after mipi on and sw stream on -+ ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_FRAME_OFF_NUM, 0x00); -+ -+ ret = ov5693_write_reg(client, OV5693_8BIT, -+ OV5693_SW_STREAM, OV5693_START_STREAMING); -+ -+ ret = __ov5693_otp_read(sd, buf); -+ -+ //mipi off and sw stream off after otp read -+ ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_FRAME_OFF_NUM, 0x0f); -+ -+ ret = ov5693_write_reg(client, OV5693_8BIT, -+ OV5693_SW_STREAM, OV5693_STOP_STREAMING); -+ -+ /* Driver has failed to find valid data */ -+ if (ret) { -+ dev_err(&client->dev, "sensor found no valid OTP data\n"); -+ return ERR_PTR(ret); -+ } -+ -+ return buf; -+} -+ -+static int ov5693_update_bits(struct ov5693_device *sensor, u16 address, -+ u16 mask, u16 bits) -+{ -+ u16 value = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, address, &value); -+ if (ret) -+ return ret; -+ -+ value &= ~mask; -+ value |= bits; -+ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, address, value); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+/* Flip */ -+ -+static int ov5693_flip_vert_configure(struct ov5693_device *sensor, bool enable) -+{ -+ u8 bits = OV5693_FORMAT1_FLIP_VERT_ISP_EN | -+ OV5693_FORMAT1_FLIP_VERT_SENSOR_EN; -+ int ret; -+ -+ ret = ov5693_update_bits(sensor, OV5693_FORMAT1_REG, bits, -+ enable ? bits : 0); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int ov5693_flip_horz_configure(struct ov5693_device *sensor, bool enable) -+{ -+ u8 bits = OV5693_FORMAT2_FLIP_HORZ_ISP_EN | -+ OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN; -+ int ret; -+ -+ ret = ov5693_update_bits(sensor, OV5693_FORMAT2_REG, bits, -+ enable ? bits : 0); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+/* -+ * This returns the exposure time being used. This should only be used -+ * for filling in EXIF data, not for actual image processing. -+ */ -+static int ov5693_q_exposure(struct v4l2_subdev *sd, s32 *value) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ u16 reg_v, reg_v2; -+ int ret; -+ -+ /* get exposure */ -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_EXPOSURE_L, -+ ®_v); -+ if (ret) -+ goto err; -+ -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_EXPOSURE_M, -+ ®_v2); -+ if (ret) -+ goto err; -+ -+ reg_v += reg_v2 << 8; -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_EXPOSURE_H, -+ ®_v2); -+ if (ret) -+ goto err; -+ -+ *value = reg_v + (((u32)reg_v2 << 16)); -+err: -+ return ret; -+} -+ -+static int ad5823_t_focus_vcm(struct v4l2_subdev *sd, u16 val) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret = -EINVAL; -+ u8 vcm_code; -+ -+ ret = ad5823_i2c_read(client, AD5823_REG_VCM_CODE_MSB, &vcm_code); -+ if (ret) -+ return ret; -+ -+ /* set reg VCM_CODE_MSB Bit[1:0] */ -+ vcm_code = (vcm_code & VCM_CODE_MSB_MASK) | -+ ((val >> 8) & ~VCM_CODE_MSB_MASK); -+ ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_MSB, vcm_code); -+ if (ret) -+ return ret; -+ -+ /* set reg VCM_CODE_LSB Bit[7:0] */ -+ ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_LSB, (val & 0xff)); -+ if (ret) -+ return ret; -+ -+ /* set required vcm move time */ -+ vcm_code = AD5823_RESONANCE_PERIOD / AD5823_RESONANCE_COEF -+ - AD5823_HIGH_FREQ_RANGE; -+ ret = ad5823_i2c_write(client, AD5823_REG_VCM_MOVE_TIME, vcm_code); -+ -+ return ret; -+} -+ -+static int ad5823_t_focus_abs(struct v4l2_subdev *sd, s32 value) -+{ -+ value = min(value, AD5823_MAX_FOCUS_POS); -+ return ad5823_t_focus_vcm(sd, value); -+} -+ -+static int ov5693_t_focus_abs(struct v4l2_subdev *sd, s32 value) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret = 0; -+ -+ dev_dbg(&client->dev, "%s: FOCUS_POS: 0x%x\n", __func__, value); -+ value = clamp(value, 0, OV5693_VCM_MAX_FOCUS_POS); -+ if (dev->vcm == VCM_DW9714) { -+ if (dev->vcm_update) { -+ ret = vcm_dw_i2c_write(client, VCM_PROTECTION_OFF); -+ if (ret) -+ return ret; -+ ret = vcm_dw_i2c_write(client, DIRECT_VCM); -+ if (ret) -+ return ret; -+ ret = vcm_dw_i2c_write(client, VCM_PROTECTION_ON); -+ if (ret) -+ return ret; -+ dev->vcm_update = false; -+ } -+ ret = vcm_dw_i2c_write(client, -+ vcm_val(value, VCM_DEFAULT_S)); -+ } else if (dev->vcm == VCM_AD5823) { -+ ad5823_t_focus_abs(sd, value); -+ } -+ if (ret == 0) { -+ dev->number_of_steps = value - dev->focus; -+ dev->focus = value; -+ dev->timestamp_t_focus_abs = ktime_get(); -+ } else -+ dev_err(&client->dev, -+ "%s: i2c failed. ret %d\n", __func__, ret); -+ -+ return ret; -+} -+ -+static int ov5693_t_focus_rel(struct v4l2_subdev *sd, s32 value) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ -+ return ov5693_t_focus_abs(sd, dev->focus + value); -+} -+ -+#define DELAY_PER_STEP_NS 1000000 -+#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) -+ -+/* Exposure */ -+ -+static int ov5693_get_exposure(struct ov5693_device *sensor) -+{ -+ u16 reg_v, reg_v2; -+ int ret = 0; -+ -+ /* get exposure */ -+ ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_EXPOSURE_L, -+ ®_v); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_EXPOSURE_M, -+ ®_v2); -+ if (ret) -+ return ret; -+ -+ reg_v += reg_v2 << 8; -+ ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_EXPOSURE_H, -+ ®_v2); -+ if (ret) -+ return ret; -+ -+ printk("exposure set to: %u\n", reg_v + (((u32)reg_v2 << 16))); -+ return ret; -+} -+ -+static int ov5693_exposure_configure(struct ov5693_device *sensor, u32 exposure) -+{ -+ int ret; -+ -+ ov5693_get_exposure(sensor); -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_EXPOSURE_CTRL_HH_REG, OV5693_EXPOSURE_CTRL_HH(exposure)); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_EXPOSURE_CTRL_H_REG, OV5693_EXPOSURE_CTRL_H(exposure)); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_EXPOSURE_CTRL_L_REG, OV5693_EXPOSURE_CTRL_L(exposure)); -+ if (ret) -+ return ret; -+ ov5693_get_exposure(sensor); -+ -+ return 0; -+} -+ -+/* Gain */ -+ -+static int ov5693_get_gain(struct ov5693_device *sensor, u32 *gain) -+{ -+ u16 gain_l, gain_h; -+ int ret = 0; -+ -+ ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_GAIN_CTRL_L_REG, -+ &gain_l); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_GAIN_CTRL_H_REG, -+ &gain_h); -+ if (ret) -+ return ret; -+ -+ *gain = (u32)(((gain_h >> 8) & 0x03) | -+ (gain_l & 0xff)); -+ -+ return ret; -+} -+static int ov5693_gain_configure(struct ov5693_device *sensor, u32 gain) -+{ -+ int ret; -+ -+ /* A 1.0 gain is 0x400 */ -+ gain = (gain * 1024)/1000; -+ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_16BIT, -+ OV5693_MWB_RED_GAIN_H, gain); -+ if (ret) { -+ dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ __func__, OV5693_MWB_RED_GAIN_H); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_16BIT, -+ OV5693_MWB_GREEN_GAIN_H, gain); -+ if (ret) { -+ dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ __func__, OV5693_MWB_RED_GAIN_H); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_16BIT, -+ OV5693_MWB_BLUE_GAIN_H, gain); -+ if (ret) { -+ dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ __func__, OV5693_MWB_RED_GAIN_H); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static int ov5693_analog_gain_configure(struct ov5693_device *sensor, u32 gain) -+{ -+ int ret; -+ -+ /* Analog gain */ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_AGC_L, gain & 0xff); -+ if (ret) { -+ dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ __func__, OV5693_AGC_L); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_AGC_H, (gain >> 8) & 0xff); -+ if (ret) { -+ dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ __func__, OV5693_AGC_H); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *dev = -+ container_of(ctrl->handler, struct ov5693_device, ctrl_handler); -+ struct i2c_client *client = v4l2_get_subdevdata(&dev->sd); -+ int ret = 0; -+ -+ switch (ctrl->id) { -+ case V4L2_CID_FOCUS_ABSOLUTE: -+ dev_dbg(&client->dev, "%s: CID_FOCUS_ABSOLUTE:%d.\n", -+ __func__, ctrl->val); -+ ret = ov5693_t_focus_abs(&dev->sd, ctrl->val); -+ break; -+ case V4L2_CID_FOCUS_RELATIVE: -+ dev_dbg(&client->dev, "%s: CID_FOCUS_RELATIVE:%d.\n", -+ __func__, ctrl->val); -+ ret = ov5693_t_focus_rel(&dev->sd, ctrl->val); -+ break; -+ case V4L2_CID_EXPOSURE: -+ dev_dbg(&client->dev, "%s: CID_EXPOSURE:%d.\n", -+ __func__, ctrl->val); -+ ret = ov5693_exposure_configure(dev, ctrl->val); -+ if (ret) -+ return ret; -+ break; -+ case V4L2_CID_ANALOGUE_GAIN: -+ dev_dbg(&client->dev, "%s: CID_ANALOGUE_GAIN:%d.\n", -+ __func__, ctrl->val); -+ ret = ov5693_analog_gain_configure(dev, ctrl->val); -+ if (ret) -+ return ret; -+ break; -+ case V4L2_CID_DIGITAL_GAIN: -+ dev_dbg(&client->dev, "%s: CID_DIGITAL_GAIN:%d.\n", -+ __func__, ctrl->val); -+ ret = ov5693_gain_configure(dev, ctrl->val); -+ if (ret) -+ return ret; -+ break; -+ case V4L2_CID_HFLIP: -+ return ov5693_flip_horz_configure(dev, !!ctrl->val); -+ case V4L2_CID_VFLIP: -+ return ov5693_flip_vert_configure(dev, !!ctrl->val); -+ default: -+ ret = -EINVAL; -+ } -+ return ret; -+} -+ -+static int ov5693_g_volatile_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *dev = -+ container_of(ctrl->handler, struct ov5693_device, ctrl_handler); -+ int ret = 0; -+ -+ switch (ctrl->id) { -+ case V4L2_CID_EXPOSURE_ABSOLUTE: -+ ret = ov5693_q_exposure(&dev->sd, &ctrl->val); -+ break; -+ case V4L2_CID_AUTOGAIN: -+ ret = ov5693_get_gain(dev, &ctrl->val); -+ break; -+ case V4L2_CID_FOCUS_ABSOLUTE: -+ /* NOTE: there was atomisp-specific function ov5693_q_focus_abs() */ -+ break; -+ default: -+ ret = -EINVAL; -+ } -+ -+ return ret; -+} -+ -+static const struct v4l2_ctrl_ops ov5693_ctrl_ops = { -+ .s_ctrl = ov5693_s_ctrl, -+ .g_volatile_ctrl = ov5693_g_volatile_ctrl -+}; -+ -+static const struct v4l2_ctrl_config ov5693_controls[] = { -+ { -+ .ops = &ov5693_ctrl_ops, -+ .id = V4L2_CID_FOCUS_ABSOLUTE, -+ .type = V4L2_CTRL_TYPE_INTEGER, -+ .name = "focus move absolute", -+ .min = 0, -+ .max = OV5693_VCM_MAX_FOCUS_POS, -+ .step = 1, -+ .def = 0, -+ .flags = 0, -+ }, -+ { -+ .ops = &ov5693_ctrl_ops, -+ .id = V4L2_CID_FOCUS_RELATIVE, -+ .type = V4L2_CTRL_TYPE_INTEGER, -+ .name = "focus move relative", -+ .min = OV5693_VCM_MAX_FOCUS_NEG, -+ .max = OV5693_VCM_MAX_FOCUS_POS, -+ .step = 1, -+ .def = 0, -+ .flags = 0, -+ }, -+}; -+ -+static int ov5693_isp_configure(struct ov5693_device *sensor) -+{ -+ int ret; -+ -+ /* Enable lens correction. */ -+ ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ OV5693_ISP_CTRL0_REG, 0x86); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int ov5693_init(struct v4l2_subdev *sd) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret; -+ -+ if (!dev->has_vcm) -+ return 0; -+ -+ dev_info(&client->dev, "%s\n", __func__); -+ mutex_lock(&dev->input_lock); -+ dev->vcm_update = false; -+ -+ if (dev->vcm == VCM_AD5823) { -+ ret = vcm_ad_i2c_wr8(client, 0x01, 0x01); /* vcm init test */ -+ if (ret) -+ dev_err(&client->dev, -+ "vcm reset failed\n"); -+ /*change the mode*/ -+ ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_MSB, -+ AD5823_RING_CTRL_ENABLE); -+ if (ret) -+ dev_err(&client->dev, -+ "vcm enable ringing failed\n"); -+ ret = ad5823_i2c_write(client, AD5823_REG_MODE, -+ AD5823_ARC_RES1); -+ if (ret) -+ dev_err(&client->dev, -+ "vcm change mode failed\n"); -+ } -+ -+ /*change initial focus value for ad5823*/ -+ if (dev->vcm == VCM_AD5823) { -+ dev->focus = AD5823_INIT_FOCUS_POS; -+ ov5693_t_focus_abs(sd, AD5823_INIT_FOCUS_POS); -+ } else { -+ dev->focus = 0; -+ ov5693_t_focus_abs(sd, 0); -+ } -+ -+ ov5693_isp_configure(dev); -+ mutex_unlock(&dev->input_lock); -+ -+ return 0; -+} -+ -+static int __power_up(struct v4l2_subdev *sd) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ struct ov5693_device *sensor = to_ov5693_sensor(sd); -+ int ret; -+ -+ ret = clk_prepare_enable(sensor->clk); -+ if (ret) { -+ dev_err(&client->dev, "Error enabling clock\n"); -+ return -EINVAL; -+ } -+ -+ if (sensor->indicator_led) -+ gpiod_set_value_cansleep(sensor->indicator_led, 1); -+ -+ ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, -+ sensor->supplies); -+ if (ret) -+ goto fail_power; -+ -+ __cci_delay(up_delay); -+ -+ return 0; -+ -+fail_power: -+ if (sensor->indicator_led) -+ gpiod_set_value_cansleep(sensor->indicator_led, 0); -+ dev_err(&client->dev, "sensor power-up failed\n"); -+ -+ return ret; -+} -+ -+static int power_down(struct v4l2_subdev *sd) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ -+ dev->focus = OV5693_INVALID_CONFIG; -+ -+ clk_disable_unprepare(dev->clk); -+ -+ if (dev->indicator_led) -+ gpiod_set_value_cansleep(dev->indicator_led, 0); -+ return regulator_bulk_disable(OV5693_NUM_SUPPLIES, dev->supplies); -+} -+ -+static int power_up(struct v4l2_subdev *sd) -+{ -+ static const int retry_count = 4; -+ int i, ret; -+ -+ for (i = 0; i < retry_count; i++) { -+ ret = __power_up(sd); -+ if (!ret) -+ return 0; -+ -+ power_down(sd); -+ } -+ return ret; -+} -+ -+static int ov5693_s_power(struct v4l2_subdev *sd, int on) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret; -+ -+ dev_info(&client->dev, "%s: on %d\n", __func__, on); -+ -+ if (on == 0) -+ return power_down(sd); -+ -+ /* on == 1 */ -+ ret = power_up(sd); -+ if (!ret) { -+ ret = ov5693_init(sd); -+ /* restore settings */ -+ ov5693_res = ov5693_res_video; -+ N_RES = N_RES_VIDEO; -+ } -+ -+ return ret; -+} -+ -+/* -+ * distance - calculate the distance -+ * @res: resolution -+ * @w: width -+ * @h: height -+ * -+ * Get the gap between res_w/res_h and w/h. -+ * distance = (res_w/res_h - w/h) / (w/h) * 8192 -+ * res->width/height smaller than w/h wouldn't be considered. -+ * The gap of ratio larger than 1/8 wouldn't be considered. -+ * Returns the value of gap or -1 if fail. -+ */ -+#define LARGEST_ALLOWED_RATIO_MISMATCH 1024 -+static int distance(struct ov5693_resolution *res, u32 w, u32 h) -+{ -+ int ratio; -+ int distance; -+ -+ if (w == 0 || h == 0 || -+ res->width < w || res->height < h) -+ return -1; -+ -+ ratio = res->width << 13; -+ ratio /= w; -+ ratio *= h; -+ ratio /= res->height; -+ -+ distance = abs(ratio - 8192); -+ -+ if (distance > LARGEST_ALLOWED_RATIO_MISMATCH) -+ return -1; -+ -+ return distance; -+} -+ -+/* Return the nearest higher resolution index -+ * Firstly try to find the approximate aspect ratio resolution -+ * If we find multiple same AR resolutions, choose the -+ * minimal size. -+ */ -+static int nearest_resolution_index(int w, int h) -+{ -+ int i; -+ int idx = -1; -+ int dist; -+ int min_dist = INT_MAX; -+ int min_res_w = INT_MAX; -+ struct ov5693_resolution *tmp_res = NULL; -+ -+ for (i = 0; i < N_RES; i++) { -+ tmp_res = &ov5693_res[i]; -+ dist = distance(tmp_res, w, h); -+ if (dist == -1) -+ continue; -+ if (dist < min_dist) { -+ min_dist = dist; -+ idx = i; -+ min_res_w = ov5693_res[i].width; -+ continue; -+ } -+ if (dist == min_dist && ov5693_res[i].width < min_res_w) -+ idx = i; -+ } -+ -+ return idx; -+} -+ -+static int get_resolution_index(int w, int h) -+{ -+ int i; -+ -+ for (i = 0; i < N_RES; i++) { -+ if (w != ov5693_res[i].width) -+ continue; -+ if (h != ov5693_res[i].height) -+ continue; -+ -+ return i; -+ } -+ -+ return -1; -+} -+ -+/* TODO: remove it. */ -+static int startup(struct v4l2_subdev *sd) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret = 0; -+ -+ ret = ov5693_write_reg(client, OV5693_8BIT, -+ OV5693_SW_RESET, 0x01); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 reset err.\n"); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg_array(client, ov5693_global_setting); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 write register err.\n"); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg_array(client, ov5693_res[dev->fmt_idx].regs); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 write register err.\n"); -+ return ret; -+ } -+ -+ return ret; -+} -+ -+static int ov5693_set_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_format *format) -+{ -+ struct v4l2_mbus_framefmt *fmt = &format->format; -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret = 0; -+ int idx; -+ int cnt; -+ -+ if (format->pad) -+ return -EINVAL; -+ if (!fmt) -+ return -EINVAL; -+ -+ mutex_lock(&dev->input_lock); -+ idx = nearest_resolution_index(fmt->width, fmt->height); -+ if (idx == -1) { -+ /* return the largest resolution */ -+ fmt->width = ov5693_res[N_RES - 1].width; -+ fmt->height = ov5693_res[N_RES - 1].height; -+ } else { -+ fmt->width = ov5693_res[idx].width; -+ fmt->height = ov5693_res[idx].height; -+ } -+ -+ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) { -+ cfg->try_fmt = *fmt; -+ ret = 0; -+ goto mutex_unlock; -+ } -+ -+ dev->fmt_idx = get_resolution_index(fmt->width, fmt->height); -+ if (dev->fmt_idx == -1) { -+ dev_err(&client->dev, "get resolution fail\n"); -+ ret = -EINVAL; -+ goto mutex_unlock; -+ } -+ -+ for (cnt = 0; cnt < OV5693_POWER_UP_RETRY_NUM; cnt++) { -+ ret = power_up(sd); -+ if (ret) { -+ dev_err(&client->dev, "power up failed\n"); -+ continue; -+ } -+ -+ mutex_unlock(&dev->input_lock); -+ ov5693_init(sd); -+ mutex_lock(&dev->input_lock); -+ ret = startup(sd); -+ if (ret) -+ dev_err(&client->dev, " startup() FAILED!\n"); -+ else -+ break; -+ } -+ if (cnt == OV5693_POWER_UP_RETRY_NUM) { -+ dev_err(&client->dev, "power up failed, gave up\n"); -+ goto mutex_unlock; -+ } -+ -+ -+ -+ /* -+ * After sensor settings are set to HW, sometimes stream is started. -+ * This would cause ISP timeout because ISP is not ready to receive -+ * data yet. So add stop streaming here. -+ */ -+ ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_SW_STREAM, -+ OV5693_STOP_STREAMING); -+ if (ret) -+ dev_warn(&client->dev, "ov5693 stream off err\n"); -+ -+mutex_unlock: -+ mutex_unlock(&dev->input_lock); -+ return ret; -+} -+ -+static const struct v4l2_rect * -+__ov5693_get_pad_crop(struct ov5693_device *dev, struct v4l2_subdev_pad_config *cfg, -+ unsigned int pad, enum v4l2_subdev_format_whence which) -+{ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ return v4l2_subdev_get_try_crop(&dev->sd, cfg, pad); -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ return &dev->mode->crop; -+ } -+ -+ return NULL; -+} -+static int ov5693_get_selection(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_selection *sel) -+{ -+ switch (sel->target) { -+ case V4L2_SEL_TGT_CROP: { -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ -+ mutex_lock(&dev->input_lock); -+ sel->r = *__ov5693_get_pad_crop(dev, cfg, sel->pad, -+ sel->which); -+ mutex_unlock(&dev->input_lock); -+ -+ return 0; -+ } -+ -+ case V4L2_SEL_TGT_NATIVE_SIZE: -+ sel->r.top = 0; -+ sel->r.left = 0; -+ sel->r.width = OV5693_NATIVE_WIDTH; -+ sel->r.height = OV5693_NATIVE_HEIGHT; -+ -+ return 0; -+ -+ case V4L2_SEL_TGT_CROP_DEFAULT: -+ sel->r.top = OV5693_PIXEL_ARRAY_TOP; -+ sel->r.left = OV5693_PIXEL_ARRAY_LEFT; -+ sel->r.width = OV5693_PIXEL_ARRAY_WIDTH; -+ sel->r.height = OV5693_PIXEL_ARRAY_HEIGHT; -+ -+ return 0; -+ } -+ -+ return -EINVAL; -+} -+ -+static int ov5693_get_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_format *format) -+{ -+ struct v4l2_mbus_framefmt *fmt = &format->format; -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ -+ if (format->pad) -+ return -EINVAL; -+ -+ if (!fmt) -+ return -EINVAL; -+ -+ fmt->width = ov5693_res[dev->fmt_idx].width; -+ fmt->height = ov5693_res[dev->fmt_idx].height; -+ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ -+ return 0; -+} -+ -+static int ov5693_detect(struct i2c_client *client) -+{ -+ struct i2c_adapter *adapter = client->adapter; -+ u16 high, low; -+ int ret; -+ u16 id; -+ u8 revision; -+ -+ if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) -+ return -ENODEV; -+ -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_SC_CMMN_CHIP_ID_H, &high); -+ if (ret) { -+ dev_err(&client->dev, "sensor_id_high = 0x%x\n", high); -+ return -ENODEV; -+ } -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_SC_CMMN_CHIP_ID_L, &low); -+ id = ((((u16)high) << 8) | (u16)low); -+ -+ if (id != OV5693_ID) { -+ dev_err(&client->dev, "sensor ID error 0x%x\n", id); -+ return -ENODEV; -+ } -+ -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_SC_CMMN_SUB_ID, &high); -+ revision = (u8)high & 0x0f; -+ -+ dev_info(&client->dev, "sensor_revision = 0x%x\n", revision); -+ dev_info(&client->dev, "sensor_address = 0x%02x\n", client->addr); -+ dev_info(&client->dev, "detect ov5693 success\n"); -+ return 0; -+} -+ -+static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret; -+ -+ mutex_lock(&dev->input_lock); -+ -+ /* power_on() here before streaming for regular PCs. */ -+ if (enable) { -+ ret = power_up(sd); -+ if (ret) { -+ dev_err(&client->dev, "sensor power-up error\n"); -+ goto out; -+ } -+ } -+ -+ ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_SW_STREAM, -+ enable ? OV5693_START_STREAMING : -+ OV5693_STOP_STREAMING); -+ -+ /* power_off() here after streaming for regular PCs. */ -+ if (!enable) -+ power_down(sd); -+ -+out: -+ mutex_unlock(&dev->input_lock); -+ -+ return ret; -+} -+ -+static int ov5693_s_config(struct v4l2_subdev *sd, int irq) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret = 0; -+ -+ mutex_lock(&dev->input_lock); -+ ret = power_up(sd); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 power-up err.\n"); -+ goto fail_power_on; -+ } -+ -+ if (!dev->vcm) -+ dev->vcm = vcm_detect(client); -+ -+ /* config & detect sensor */ -+ ret = ov5693_detect(client); -+ if (ret) { -+ dev_err(&client->dev, "ov5693_detect err s_config.\n"); -+ goto fail_power_on; -+ } -+ -+ dev->otp_data = ov5693_otp_read(sd); -+ -+ /* turn off sensor, after probed */ -+ ret = power_down(sd); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 power-off err.\n"); -+ goto fail_power_on; -+ } -+ mutex_unlock(&dev->input_lock); -+ -+ return ret; -+ -+fail_power_on: -+ power_down(sd); -+ dev_err(&client->dev, "sensor power-gating failed\n"); -+ mutex_unlock(&dev->input_lock); -+ return ret; -+} -+ -+static int ov5693_g_frame_interval(struct v4l2_subdev *sd, -+ struct v4l2_subdev_frame_interval *interval) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ -+ interval->interval.numerator = 1; -+ interval->interval.denominator = ov5693_res[dev->fmt_idx].fps; -+ -+ return 0; -+} -+ -+static int ov5693_enum_mbus_code(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_mbus_code_enum *code) -+{ -+ if (code->index >= MAX_FMTS) -+ return -EINVAL; -+ -+ code->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ return 0; -+} -+ -+static int ov5693_enum_frame_size(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_frame_size_enum *fse) -+{ -+ int index = fse->index; -+ -+ if (index >= N_RES) -+ return -EINVAL; -+ -+ fse->min_width = ov5693_res[index].width; -+ fse->min_height = ov5693_res[index].height; -+ fse->max_width = ov5693_res[index].width; -+ fse->max_height = ov5693_res[index].height; -+ -+ return 0; -+} -+ -+static const struct v4l2_subdev_video_ops ov5693_video_ops = { -+ .s_stream = ov5693_s_stream, -+ .g_frame_interval = ov5693_g_frame_interval, -+}; -+ -+static const struct v4l2_subdev_core_ops ov5693_core_ops = { -+ .s_power = ov5693_s_power, -+}; -+ -+static const struct v4l2_subdev_pad_ops ov5693_pad_ops = { -+ .enum_mbus_code = ov5693_enum_mbus_code, -+ .enum_frame_size = ov5693_enum_frame_size, -+ .get_fmt = ov5693_get_fmt, -+ .set_fmt = ov5693_set_fmt, -+ .get_selection = ov5693_get_selection, -+}; -+ -+static const struct v4l2_subdev_ops ov5693_ops = { -+ .core = &ov5693_core_ops, -+ .video = &ov5693_video_ops, -+ .pad = &ov5693_pad_ops, -+}; -+ -+static int ov5693_remove(struct i2c_client *client) -+{ -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ unsigned int i = OV5693_NUM_SUPPLIES; -+ -+ dev_info(&client->dev, "%s...\n", __func__); -+ -+ gpiod_put(ov5693->reset); -+ gpiod_put(ov5693->indicator_led); -+ while (i--) -+ regulator_put(ov5693->supplies[i].consumer); -+ -+ v4l2_async_unregister_subdev(sd); -+ -+ media_entity_cleanup(&ov5693->sd.entity); -+ v4l2_ctrl_handler_free(&ov5693->ctrl_handler); -+ kfree(ov5693); -+ -+ return 0; -+} -+ -+static int ov5693_init_controls(struct ov5693_device *ov5693) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(&ov5693->sd); -+ const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; -+ struct v4l2_ctrl *ctrl; -+ unsigned int i; -+ int ret; -+ int hblank; -+ -+ ret = v4l2_ctrl_handler_init(&ov5693->ctrl_handler, -+ ARRAY_SIZE(ov5693_controls)); -+ if (ret) { -+ ov5693_remove(client); -+ return ret; -+ } -+ -+ for (i = 0; i < ARRAY_SIZE(ov5693_controls); i++) -+ v4l2_ctrl_new_custom(&ov5693->ctrl_handler, -+ &ov5693_controls[i], -+ NULL); -+ -+ /* link freq */ -+ ctrl = v4l2_ctrl_new_int_menu(&ov5693->ctrl_handler, NULL, -+ V4L2_CID_LINK_FREQ, -+ 0, 0, link_freq_menu_items); -+ if (ctrl) -+ ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ -+ /* pixel rate */ -+ v4l2_ctrl_new_std(&ov5693->ctrl_handler, NULL, V4L2_CID_PIXEL_RATE, -+ 0, OV5693_PIXEL_RATE, 1, OV5693_PIXEL_RATE); -+ -+ if (ov5693->ctrl_handler.error) { -+ ov5693_remove(client); -+ return ov5693->ctrl_handler.error; -+ } -+ -+ /* Exposure */ -+ -+ v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_EXPOSURE, 16, 1048575, 16, -+ 512); -+ -+ /* Gain */ -+ -+ v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_ANALOGUE_GAIN, 1, 1023, 1, 128); -+ v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_DIGITAL_GAIN, 1, 3999, 1, 1000); -+ -+ /* Flip */ -+ -+ v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_HFLIP, 0, 1, 1, 0); -+ v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_VFLIP, 0, 1, 1, 0); -+ -+ hblank = OV5693_PPL_DEFAULT - ov5693->mode->width; -+ ov5693->hblank = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -+ V4L2_CID_HBLANK, hblank, hblank, -+ 1, hblank); -+ if (ov5693->hblank) -+ ov5693->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ -+ /* Use same lock for controls as for everything else. */ -+ ov5693->ctrl_handler.lock = &ov5693->input_lock; -+ ov5693->sd.ctrl_handler = &ov5693->ctrl_handler; -+ -+ return 0; -+} -+ -+static int ov5693_configure_gpios(struct ov5693_device *ov5693) -+{ -+ ov5693->reset = gpiod_get_index(&ov5693->i2c_client->dev, "reset", 0, -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->reset)) { -+ dev_err(&ov5693->i2c_client->dev, "Couldn't find reset GPIO\n"); -+ return -EINVAL; -+ } -+ -+ ov5693->indicator_led = gpiod_get_index_optional(&ov5693->i2c_client->dev, "indicator-led", 0, -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->indicator_led)) { -+ dev_err(&ov5693->i2c_client->dev, "Couldn't find indicator-led GPIO\n"); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int ov5693_get_regulators(struct ov5693_device *ov5693) -+{ -+ unsigned int i; -+ -+ for (i = 0; i < OV5693_NUM_SUPPLIES; i++) -+ ov5693->supplies[i].supply = ov5693_supply_names[i]; -+ -+ return regulator_bulk_get(&ov5693->i2c_client->dev, -+ OV5693_NUM_SUPPLIES, -+ ov5693->supplies); -+} -+ -+static int ov5693_probe(struct i2c_client *client) -+{ -+ struct ov5693_device *ov5693; -+ int ret = 0; -+ -+ dev_info(&client->dev, "%s() called", __func__); -+ -+ ov5693 = kzalloc(sizeof(*ov5693), GFP_KERNEL); -+ if (!ov5693) -+ return -ENOMEM; -+ -+ ov5693->i2c_client = client; -+ -+ /* check if VCM device exists */ -+ /* TODO: read from SSDB */ -+ ov5693->has_vcm = false; -+ -+ mutex_init(&ov5693->input_lock); -+ -+ v4l2_i2c_subdev_init(&ov5693->sd, client, &ov5693_ops); -+ -+ ov5693->clk = devm_clk_get(&client->dev, "xvclk"); -+ if (IS_ERR(ov5693->clk)) { -+ dev_err(&client->dev, "Error getting clock\n"); -+ return -EINVAL; -+ } -+ -+ ret = ov5693_configure_gpios(ov5693); -+ if (ret) -+ goto out_free; -+ -+ ret = ov5693_get_regulators(ov5693); -+ if (ret) -+ goto out_put_reset; -+ -+ ret = ov5693_s_config(&ov5693->sd, client->irq); -+ if (ret) -+ goto out_put_reset; -+ -+ ov5693->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; -+ ov5693->pad.flags = MEDIA_PAD_FL_SOURCE; -+ ov5693->format.code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ ov5693->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; -+ ov5693->mode = &ov5693_res_video[N_RES_VIDEO-1]; -+ -+ ret = ov5693_init_controls(ov5693); -+ if (ret) -+ ov5693_remove(client); -+ -+ ret = media_entity_pads_init(&ov5693->sd.entity, 1, &ov5693->pad); -+ if (ret) -+ ov5693_remove(client); -+ -+ ret = v4l2_async_register_subdev_sensor_common(&ov5693->sd); -+ if (ret) { -+ dev_err(&client->dev, "failed to register V4L2 subdev: %d", ret); -+ goto media_entity_cleanup; -+ } -+ -+ return ret; -+ -+media_entity_cleanup: -+ media_entity_cleanup(&ov5693->sd.entity); -+out_put_reset: -+ gpiod_put(ov5693->reset); -+out_free: -+ v4l2_device_unregister_subdev(&ov5693->sd); -+ kfree(ov5693); -+ return ret; -+} -+ -+static const struct acpi_device_id ov5693_acpi_match[] = { -+ {"INT33BE"}, -+ {}, -+}; -+MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); -+ -+static struct i2c_driver ov5693_driver = { -+ .driver = { -+ .name = "ov5693", -+ .acpi_match_table = ov5693_acpi_match, -+ }, -+ .probe_new = ov5693_probe, -+ .remove = ov5693_remove, -+}; -+module_i2c_driver(ov5693_driver); -+ -+MODULE_DESCRIPTION("A low-level driver for OmniVision 5693 sensors"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -new file mode 100644 -index 000000000000..9a508e1f3624 ---- /dev/null -+++ b/drivers/media/i2c/ov5693.h -@@ -0,0 +1,1430 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* -+ * Support for OmniVision OV5693 5M camera sensor. -+ * -+ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. -+ * -+ * 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 __OV5693_H__ -+#define __OV5693_H__ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define OV5693_HID "INT33BE" -+ -+/* -+ * FIXME: non-preview resolutions are currently broken -+ */ -+#define ENABLE_NON_PREVIEW 1 -+ -+#define OV5693_POWER_UP_RETRY_NUM 5 -+ -+/* Defines for register writes and register array processing */ -+#define I2C_MSG_LENGTH 0x2 -+#define I2C_RETRY_COUNT 5 -+ -+#define OV5693_FOCAL_LENGTH_NUM 334 /*3.34mm*/ -+#define OV5693_FOCAL_LENGTH_DEM 100 -+#define OV5693_F_NUMBER_DEFAULT_NUM 24 -+#define OV5693_F_NUMBER_DEM 10 -+ -+#define MAX_FMTS 1 -+ -+/* sensor_mode_data read_mode adaptation */ -+#define OV5693_READ_MODE_BINNING_ON 0x0400 -+#define OV5693_READ_MODE_BINNING_OFF 0x00 -+#define OV5693_INTEGRATION_TIME_MARGIN 8 -+ -+#define OV5693_MAX_EXPOSURE_VALUE 0xFFF1 -+#define OV5693_MAX_GAIN_VALUE 0xFF -+ -+/* -+ * focal length bits definition: -+ * bits 31-16: numerator, bits 15-0: denominator -+ */ -+#define OV5693_FOCAL_LENGTH_DEFAULT 0x1B70064 -+ -+/* -+ * current f-number bits definition: -+ * bits 31-16: numerator, bits 15-0: denominator -+ */ -+#define OV5693_F_NUMBER_DEFAULT 0x18000a -+ -+/* -+ * f-number range bits definition: -+ * bits 31-24: max f-number numerator -+ * bits 23-16: max f-number denominator -+ * bits 15-8: min f-number numerator -+ * bits 7-0: min f-number denominator -+ */ -+#define OV5693_F_NUMBER_RANGE 0x180a180a -+#define OV5693_ID 0x5690 -+ -+#define OV5693_FINE_INTG_TIME_MIN 0 -+#define OV5693_FINE_INTG_TIME_MAX_MARGIN 0 -+#define OV5693_COARSE_INTG_TIME_MIN 1 -+#define OV5693_COARSE_INTG_TIME_MAX_MARGIN 6 -+ -+#define OV5693_BIN_FACTOR_MAX 4 -+/* -+ * OV5693 System control registers -+ */ -+#define OV5693_SW_SLEEP 0x0100 -+#define OV5693_SW_RESET 0x0103 -+#define OV5693_SW_STREAM 0x0100 -+ -+#define OV5693_SC_CMMN_CHIP_ID_H 0x300A -+#define OV5693_SC_CMMN_CHIP_ID_L 0x300B -+#define OV5693_SC_CMMN_SCCB_ID 0x300C -+#define OV5693_SC_CMMN_SUB_ID 0x302A /* process, version*/ -+/*Bit[7:4] Group control, Bit[3:0] Group ID*/ -+#define OV5693_GROUP_ACCESS 0x3208 -+/* -+*Bit[3:0] Bit[19:16] of exposure, -+*remaining 16 bits lies in Reg0x3501&Reg0x3502 -+*/ -+#define OV5693_EXPOSURE_H 0x3500 -+#define OV5693_EXPOSURE_M 0x3501 -+#define OV5693_EXPOSURE_L 0x3502 -+/*Bit[1:0] means Bit[9:8] of gain*/ -+#define OV5693_AGC_H 0x350A -+#define OV5693_AGC_L 0x350B /*Bit[7:0] of gain*/ -+ -+#define OV5693_HORIZONTAL_START_H 0x3800 /*Bit[11:8]*/ -+#define OV5693_HORIZONTAL_START_L 0x3801 /*Bit[7:0]*/ -+#define OV5693_VERTICAL_START_H 0x3802 /*Bit[11:8]*/ -+#define OV5693_VERTICAL_START_L 0x3803 /*Bit[7:0]*/ -+#define OV5693_HORIZONTAL_END_H 0x3804 /*Bit[11:8]*/ -+#define OV5693_HORIZONTAL_END_L 0x3805 /*Bit[7:0]*/ -+#define OV5693_VERTICAL_END_H 0x3806 /*Bit[11:8]*/ -+#define OV5693_VERTICAL_END_L 0x3807 /*Bit[7:0]*/ -+#define OV5693_HORIZONTAL_OUTPUT_SIZE_H 0x3808 /*Bit[3:0]*/ -+#define OV5693_HORIZONTAL_OUTPUT_SIZE_L 0x3809 /*Bit[7:0]*/ -+#define OV5693_VERTICAL_OUTPUT_SIZE_H 0x380a /*Bit[3:0]*/ -+#define OV5693_VERTICAL_OUTPUT_SIZE_L 0x380b /*Bit[7:0]*/ -+/*High 8-bit, and low 8-bit HTS address is 0x380d*/ -+#define OV5693_TIMING_HTS_H 0x380C -+/*High 8-bit, and low 8-bit HTS address is 0x380d*/ -+#define OV5693_TIMING_HTS_L 0x380D -+/*High 8-bit, and low 8-bit HTS address is 0x380f*/ -+#define OV5693_TIMING_VTS_H 0x380e -+/*High 8-bit, and low 8-bit HTS address is 0x380f*/ -+#define OV5693_TIMING_VTS_L 0x380f -+ -+#define OV5693_MWB_RED_GAIN_H 0x3400 -+#define OV5693_MWB_GREEN_GAIN_H 0x3402 -+#define OV5693_MWB_BLUE_GAIN_H 0x3404 -+#define OV5693_MWB_GAIN_MAX 0x0fff -+ -+#define OV5693_START_STREAMING 0x01 -+#define OV5693_STOP_STREAMING 0x00 -+ -+#define VCM_ADDR 0x0c -+#define VCM_CODE_MSB 0x04 -+ -+#define OV5693_INVALID_CONFIG 0xffffffff -+ -+#define OV5693_VCM_SLEW_STEP 0x30F0 -+#define OV5693_VCM_SLEW_STEP_MAX 0x7 -+#define OV5693_VCM_SLEW_STEP_MASK 0x7 -+#define OV5693_VCM_CODE 0x30F2 -+#define OV5693_VCM_SLEW_TIME 0x30F4 -+#define OV5693_VCM_SLEW_TIME_MAX 0xffff -+#define OV5693_VCM_ENABLE 0x8000 -+ -+#define OV5693_VCM_MAX_FOCUS_NEG -1023 -+#define OV5693_VCM_MAX_FOCUS_POS 1023 -+ -+#define DLC_ENABLE 1 -+#define DLC_DISABLE 0 -+#define VCM_PROTECTION_OFF 0xeca3 -+#define VCM_PROTECTION_ON 0xdc51 -+#define VCM_DEFAULT_S 0x0 -+#define vcm_step_s(a) (u8)(a & 0xf) -+#define vcm_step_mclk(a) (u8)((a >> 4) & 0x3) -+#define vcm_dlc_mclk(dlc, mclk) (u16)((dlc << 3) | mclk | 0xa104) -+#define vcm_tsrc(tsrc) (u16)(tsrc << 3 | 0xf200) -+#define vcm_val(data, s) (u16)(data << 4 | s) -+#define DIRECT_VCM vcm_dlc_mclk(0, 0) -+ -+/* Defines for OTP Data Registers */ -+#define OV5693_FRAME_OFF_NUM 0x4202 -+#define OV5693_OTP_BYTE_MAX 32 //change to 32 as needed by otpdata -+#define OV5693_OTP_SHORT_MAX 16 -+#define OV5693_OTP_START_ADDR 0x3D00 -+#define OV5693_OTP_END_ADDR 0x3D0F -+#define OV5693_OTP_DATA_SIZE 320 -+#define OV5693_OTP_PROGRAM_REG 0x3D80 -+#define OV5693_OTP_READ_REG 0x3D81 // 1:Enable 0:disable -+#define OV5693_OTP_BANK_REG 0x3D84 //otp bank and mode -+#define OV5693_OTP_READY_REG_DONE 1 -+#define OV5693_OTP_BANK_MAX 28 -+#define OV5693_OTP_BANK_SIZE 16 //16 bytes per bank -+#define OV5693_OTP_READ_ONETIME 16 -+#define OV5693_OTP_MODE_READ 1 -+ -+/* link freq and pixel rate required for IPU3 */ -+#define OV5693_LINK_FREQ_640MHZ 640000000 -+/* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample -+ * To avoid integer overflow, dividing by bits_per_sample first. -+ */ -+#define OV5693_PIXEL_RATE (OV5693_LINK_FREQ_640MHZ / 10) * 2 * 2 -+static const s64 link_freq_menu_items[] = { -+ OV5693_LINK_FREQ_640MHZ -+}; -+ -+#define OV5693_NUM_SUPPLIES 2 -+static const char * const ov5693_supply_names[] = { -+ "avdd", -+ "dovdd", -+}; -+ -+struct regval_list { -+ u16 reg_num; -+ u8 value; -+}; -+ -+struct ov5693_resolution { -+ u8 *desc; -+ const struct ov5693_reg *regs; -+ int res; -+ int width; -+ int height; -+ int fps; -+ int pix_clk_freq; -+ u16 pixels_per_line; -+ u16 lines_per_frame; -+ u8 bin_factor_x; -+ u8 bin_factor_y; -+ u8 bin_mode; -+ bool used; -+ -+ /* Analog crop rectangle. */ -+ struct v4l2_rect crop; -+}; -+ -+struct ov5693_format { -+ u8 *desc; -+ u32 pixelformat; -+ struct ov5693_reg *regs; -+}; -+ -+enum vcm_type { -+ VCM_UNKNOWN, -+ VCM_AD5823, -+ VCM_DW9714, -+}; -+ -+/* -+ * ov5693 device structure. -+ */ -+struct ov5693_device { -+ struct i2c_client *i2c_client; -+ struct v4l2_subdev sd; -+ struct media_pad pad; -+ struct v4l2_mbus_framefmt format; -+ struct mutex input_lock; -+ struct v4l2_ctrl_handler ctrl_handler; -+ -+ struct gpio_desc *reset; -+ struct gpio_desc *indicator_led; -+ struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; -+ struct clk *clk; -+ -+ /* Current mode */ -+ const struct ov5693_resolution *mode; -+ -+ struct camera_sensor_platform_data *platform_data; -+ ktime_t timestamp_t_focus_abs; -+ int vt_pix_clk_freq_mhz; -+ int fmt_idx; -+ int run_mode; -+ int otp_size; -+ u8 *otp_data; -+ u32 focus; -+ s16 number_of_steps; -+ u8 res; -+ u8 type; -+ bool vcm_update; -+ enum vcm_type vcm; -+ -+ bool has_vcm; -+ -+ struct v4l2_ctrl *hblank; -+}; -+ -+enum ov5693_tok_type { -+ OV5693_8BIT = 0x0001, -+ OV5693_16BIT = 0x0002, -+ OV5693_32BIT = 0x0004, -+ OV5693_TOK_TERM = 0xf000, /* terminating token for reg list */ -+ OV5693_TOK_DELAY = 0xfe00, /* delay token for reg list */ -+ OV5693_TOK_MASK = 0xfff0 -+}; -+ -+/** -+ * struct ov5693_reg - MI sensor register format -+ * @type: type of the register -+ * @reg: 16-bit offset to register -+ * @val: 8/16/32-bit register value -+ * -+ * Define a structure for sensor register initialization values -+ */ -+struct ov5693_reg { -+ enum ov5693_tok_type type; -+ u16 reg; -+ u32 val; /* @set value for read/mod/write, @mask */ -+}; -+ -+#define to_ov5693_sensor(x) container_of(x, struct ov5693_device, sd) -+ -+#define OV5693_MAX_WRITE_BUF_SIZE 30 -+ -+struct ov5693_write_buffer { -+ u16 addr; -+ u8 data[OV5693_MAX_WRITE_BUF_SIZE]; -+}; -+ -+struct ov5693_write_ctrl { -+ int index; -+ struct ov5693_write_buffer buffer; -+}; -+ -+static struct ov5693_reg const ov5693_global_setting[] = { -+ {OV5693_8BIT, 0x0103, 0x01}, -+ {OV5693_8BIT, 0x3001, 0x0a}, -+ {OV5693_8BIT, 0x3002, 0x80}, -+ {OV5693_8BIT, 0x3006, 0x00}, -+ {OV5693_8BIT, 0x3011, 0x21}, -+ {OV5693_8BIT, 0x3012, 0x09}, -+ {OV5693_8BIT, 0x3013, 0x10}, -+ {OV5693_8BIT, 0x3014, 0x00}, -+ {OV5693_8BIT, 0x3015, 0x08}, -+ {OV5693_8BIT, 0x3016, 0xf0}, -+ {OV5693_8BIT, 0x3017, 0xf0}, -+ {OV5693_8BIT, 0x3018, 0xf0}, -+ {OV5693_8BIT, 0x301b, 0xb4}, -+ {OV5693_8BIT, 0x301d, 0x02}, -+ {OV5693_8BIT, 0x3021, 0x00}, -+ {OV5693_8BIT, 0x3022, 0x01}, -+ {OV5693_8BIT, 0x3028, 0x44}, -+ {OV5693_8BIT, 0x3098, 0x02}, -+ {OV5693_8BIT, 0x3099, 0x19}, -+ {OV5693_8BIT, 0x309a, 0x02}, -+ {OV5693_8BIT, 0x309b, 0x01}, -+ {OV5693_8BIT, 0x309c, 0x00}, -+ {OV5693_8BIT, 0x30a0, 0xd2}, -+ {OV5693_8BIT, 0x30a2, 0x01}, -+ {OV5693_8BIT, 0x30b2, 0x00}, -+ {OV5693_8BIT, 0x30b3, 0x7d}, -+ {OV5693_8BIT, 0x30b4, 0x03}, -+ {OV5693_8BIT, 0x30b5, 0x04}, -+ {OV5693_8BIT, 0x30b6, 0x01}, -+ {OV5693_8BIT, 0x3104, 0x21}, -+ {OV5693_8BIT, 0x3106, 0x00}, -+ {OV5693_8BIT, 0x3400, 0x04}, -+ {OV5693_8BIT, 0x3401, 0x00}, -+ {OV5693_8BIT, 0x3402, 0x04}, -+ {OV5693_8BIT, 0x3403, 0x00}, -+ {OV5693_8BIT, 0x3404, 0x04}, -+ {OV5693_8BIT, 0x3405, 0x00}, -+ {OV5693_8BIT, 0x3406, 0x01}, -+ {OV5693_8BIT, 0x3500, 0x00}, -+ {OV5693_8BIT, 0x3503, 0x07}, -+ {OV5693_8BIT, 0x3504, 0x00}, -+ {OV5693_8BIT, 0x3505, 0x00}, -+ {OV5693_8BIT, 0x3506, 0x00}, -+ {OV5693_8BIT, 0x3507, 0x02}, -+ {OV5693_8BIT, 0x3508, 0x00}, -+ {OV5693_8BIT, 0x3509, 0x10}, -+ {OV5693_8BIT, 0x350a, 0x00}, -+ {OV5693_8BIT, 0x350b, 0x40}, -+ {OV5693_8BIT, 0x3601, 0x0a}, -+ {OV5693_8BIT, 0x3602, 0x38}, -+ {OV5693_8BIT, 0x3612, 0x80}, -+ {OV5693_8BIT, 0x3620, 0x54}, -+ {OV5693_8BIT, 0x3621, 0xc7}, -+ {OV5693_8BIT, 0x3622, 0x0f}, -+ {OV5693_8BIT, 0x3625, 0x10}, -+ {OV5693_8BIT, 0x3630, 0x55}, -+ {OV5693_8BIT, 0x3631, 0xf4}, -+ {OV5693_8BIT, 0x3632, 0x00}, -+ {OV5693_8BIT, 0x3633, 0x34}, -+ {OV5693_8BIT, 0x3634, 0x02}, -+ {OV5693_8BIT, 0x364d, 0x0d}, -+ {OV5693_8BIT, 0x364f, 0xdd}, -+ {OV5693_8BIT, 0x3660, 0x04}, -+ {OV5693_8BIT, 0x3662, 0x10}, -+ {OV5693_8BIT, 0x3663, 0xf1}, -+ {OV5693_8BIT, 0x3665, 0x00}, -+ {OV5693_8BIT, 0x3666, 0x20}, -+ {OV5693_8BIT, 0x3667, 0x00}, -+ {OV5693_8BIT, 0x366a, 0x80}, -+ {OV5693_8BIT, 0x3680, 0xe0}, -+ {OV5693_8BIT, 0x3681, 0x00}, -+ {OV5693_8BIT, 0x3700, 0x42}, -+ {OV5693_8BIT, 0x3701, 0x14}, -+ {OV5693_8BIT, 0x3702, 0xa0}, -+ {OV5693_8BIT, 0x3703, 0xd8}, -+ {OV5693_8BIT, 0x3704, 0x78}, -+ {OV5693_8BIT, 0x3705, 0x02}, -+ {OV5693_8BIT, 0x370a, 0x00}, -+ {OV5693_8BIT, 0x370b, 0x20}, -+ {OV5693_8BIT, 0x370c, 0x0c}, -+ {OV5693_8BIT, 0x370d, 0x11}, -+ {OV5693_8BIT, 0x370e, 0x00}, -+ {OV5693_8BIT, 0x370f, 0x40}, -+ {OV5693_8BIT, 0x3710, 0x00}, -+ {OV5693_8BIT, 0x371a, 0x1c}, -+ {OV5693_8BIT, 0x371b, 0x05}, -+ {OV5693_8BIT, 0x371c, 0x01}, -+ {OV5693_8BIT, 0x371e, 0xa1}, -+ {OV5693_8BIT, 0x371f, 0x0c}, -+ {OV5693_8BIT, 0x3721, 0x00}, -+ {OV5693_8BIT, 0x3724, 0x10}, -+ {OV5693_8BIT, 0x3726, 0x00}, -+ {OV5693_8BIT, 0x372a, 0x01}, -+ {OV5693_8BIT, 0x3730, 0x10}, -+ {OV5693_8BIT, 0x3738, 0x22}, -+ {OV5693_8BIT, 0x3739, 0xe5}, -+ {OV5693_8BIT, 0x373a, 0x50}, -+ {OV5693_8BIT, 0x373b, 0x02}, -+ {OV5693_8BIT, 0x373c, 0x41}, -+ {OV5693_8BIT, 0x373f, 0x02}, -+ {OV5693_8BIT, 0x3740, 0x42}, -+ {OV5693_8BIT, 0x3741, 0x02}, -+ {OV5693_8BIT, 0x3742, 0x18}, -+ {OV5693_8BIT, 0x3743, 0x01}, -+ {OV5693_8BIT, 0x3744, 0x02}, -+ {OV5693_8BIT, 0x3747, 0x10}, -+ {OV5693_8BIT, 0x374c, 0x04}, -+ {OV5693_8BIT, 0x3751, 0xf0}, -+ {OV5693_8BIT, 0x3752, 0x00}, -+ {OV5693_8BIT, 0x3753, 0x00}, -+ {OV5693_8BIT, 0x3754, 0xc0}, -+ {OV5693_8BIT, 0x3755, 0x00}, -+ {OV5693_8BIT, 0x3756, 0x1a}, -+ {OV5693_8BIT, 0x3758, 0x00}, -+ {OV5693_8BIT, 0x3759, 0x0f}, -+ {OV5693_8BIT, 0x376b, 0x44}, -+ {OV5693_8BIT, 0x375c, 0x04}, -+ {OV5693_8BIT, 0x3774, 0x10}, -+ {OV5693_8BIT, 0x3776, 0x00}, -+ {OV5693_8BIT, 0x377f, 0x08}, -+ {OV5693_8BIT, 0x3780, 0x22}, -+ {OV5693_8BIT, 0x3781, 0x0c}, -+ {OV5693_8BIT, 0x3784, 0x2c}, -+ {OV5693_8BIT, 0x3785, 0x1e}, -+ {OV5693_8BIT, 0x378f, 0xf5}, -+ {OV5693_8BIT, 0x3791, 0xb0}, -+ {OV5693_8BIT, 0x3795, 0x00}, -+ {OV5693_8BIT, 0x3796, 0x64}, -+ {OV5693_8BIT, 0x3797, 0x11}, -+ {OV5693_8BIT, 0x3798, 0x30}, -+ {OV5693_8BIT, 0x3799, 0x41}, -+ {OV5693_8BIT, 0x379a, 0x07}, -+ {OV5693_8BIT, 0x379b, 0xb0}, -+ {OV5693_8BIT, 0x379c, 0x0c}, -+ {OV5693_8BIT, 0x37c5, 0x00}, -+ {OV5693_8BIT, 0x37c6, 0x00}, -+ {OV5693_8BIT, 0x37c7, 0x00}, -+ {OV5693_8BIT, 0x37c9, 0x00}, -+ {OV5693_8BIT, 0x37ca, 0x00}, -+ {OV5693_8BIT, 0x37cb, 0x00}, -+ {OV5693_8BIT, 0x37de, 0x00}, -+ {OV5693_8BIT, 0x37df, 0x00}, -+ {OV5693_8BIT, 0x3800, 0x00}, -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, -+ {OV5693_8BIT, 0x3804, 0x0a}, -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3810, 0x00}, -+ {OV5693_8BIT, 0x3812, 0x00}, -+ {OV5693_8BIT, 0x3823, 0x00}, -+ {OV5693_8BIT, 0x3824, 0x00}, -+ {OV5693_8BIT, 0x3825, 0x00}, -+ {OV5693_8BIT, 0x3826, 0x00}, -+ {OV5693_8BIT, 0x3827, 0x00}, -+ {OV5693_8BIT, 0x382a, 0x04}, -+ {OV5693_8BIT, 0x3a04, 0x06}, -+ {OV5693_8BIT, 0x3a05, 0x14}, -+ {OV5693_8BIT, 0x3a06, 0x00}, -+ {OV5693_8BIT, 0x3a07, 0xfe}, -+ {OV5693_8BIT, 0x3b00, 0x00}, -+ {OV5693_8BIT, 0x3b02, 0x00}, -+ {OV5693_8BIT, 0x3b03, 0x00}, -+ {OV5693_8BIT, 0x3b04, 0x00}, -+ {OV5693_8BIT, 0x3b05, 0x00}, -+ {OV5693_8BIT, 0x3e07, 0x20}, -+ {OV5693_8BIT, 0x4000, 0x08}, -+ {OV5693_8BIT, 0x4001, 0x04}, -+ {OV5693_8BIT, 0x4002, 0x45}, -+ {OV5693_8BIT, 0x4004, 0x08}, -+ {OV5693_8BIT, 0x4005, 0x18}, -+ {OV5693_8BIT, 0x4006, 0x20}, -+ {OV5693_8BIT, 0x4008, 0x24}, -+ {OV5693_8BIT, 0x4009, 0x10}, -+ {OV5693_8BIT, 0x400c, 0x00}, -+ {OV5693_8BIT, 0x400d, 0x00}, -+ {OV5693_8BIT, 0x4058, 0x00}, -+ {OV5693_8BIT, 0x404e, 0x37}, -+ {OV5693_8BIT, 0x404f, 0x8f}, -+ {OV5693_8BIT, 0x4058, 0x00}, -+ {OV5693_8BIT, 0x4101, 0xb2}, -+ {OV5693_8BIT, 0x4303, 0x00}, -+ {OV5693_8BIT, 0x4304, 0x08}, -+ {OV5693_8BIT, 0x4307, 0x31}, -+ {OV5693_8BIT, 0x4311, 0x04}, -+ {OV5693_8BIT, 0x4315, 0x01}, -+ {OV5693_8BIT, 0x4511, 0x05}, -+ {OV5693_8BIT, 0x4512, 0x01}, -+ {OV5693_8BIT, 0x4806, 0x00}, -+ {OV5693_8BIT, 0x4816, 0x52}, -+ {OV5693_8BIT, 0x481f, 0x30}, -+ {OV5693_8BIT, 0x4826, 0x2c}, -+ {OV5693_8BIT, 0x4831, 0x64}, -+ {OV5693_8BIT, 0x4d00, 0x04}, -+ {OV5693_8BIT, 0x4d01, 0x71}, -+ {OV5693_8BIT, 0x4d02, 0xfd}, -+ {OV5693_8BIT, 0x4d03, 0xf5}, -+ {OV5693_8BIT, 0x4d04, 0x0c}, -+ {OV5693_8BIT, 0x4d05, 0xcc}, -+ {OV5693_8BIT, 0x4837, 0x0a}, -+ {OV5693_8BIT, 0x5000, 0x06}, -+ {OV5693_8BIT, 0x5001, 0x01}, -+ {OV5693_8BIT, 0x5003, 0x20}, -+ {OV5693_8BIT, 0x5046, 0x0a}, -+ {OV5693_8BIT, 0x5013, 0x00}, -+ {OV5693_8BIT, 0x5046, 0x0a}, -+ {OV5693_8BIT, 0x5780, 0x1c}, -+ {OV5693_8BIT, 0x5786, 0x20}, -+ {OV5693_8BIT, 0x5787, 0x10}, -+ {OV5693_8BIT, 0x5788, 0x18}, -+ {OV5693_8BIT, 0x578a, 0x04}, -+ {OV5693_8BIT, 0x578b, 0x02}, -+ {OV5693_8BIT, 0x578c, 0x02}, -+ {OV5693_8BIT, 0x578e, 0x06}, -+ {OV5693_8BIT, 0x578f, 0x02}, -+ {OV5693_8BIT, 0x5790, 0x02}, -+ {OV5693_8BIT, 0x5791, 0xff}, -+ {OV5693_8BIT, 0x5842, 0x01}, -+ {OV5693_8BIT, 0x5843, 0x2b}, -+ {OV5693_8BIT, 0x5844, 0x01}, -+ {OV5693_8BIT, 0x5845, 0x92}, -+ {OV5693_8BIT, 0x5846, 0x01}, -+ {OV5693_8BIT, 0x5847, 0x8f}, -+ {OV5693_8BIT, 0x5848, 0x01}, -+ {OV5693_8BIT, 0x5849, 0x0c}, -+ {OV5693_8BIT, 0x5e00, 0x00}, -+ {OV5693_8BIT, 0x5e10, 0x0c}, -+ {OV5693_8BIT, 0x0100, 0x00}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+#if ENABLE_NON_PREVIEW -+/* -+ * 654x496 30fps 17ms VBlanking 2lane 10Bit (Scaling) -+ */ -+static struct ov5693_reg const ov5693_654x496[] = { -+ {OV5693_8BIT, 0x3501, 0x3d}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe6}, -+ {OV5693_8BIT, 0x3709, 0xc7}, -+ {OV5693_8BIT, 0x3803, 0x00}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xa3}, -+ {OV5693_8BIT, 0x3808, 0x02}, -+ {OV5693_8BIT, 0x3809, 0x90}, -+ {OV5693_8BIT, 0x380a, 0x01}, -+ {OV5693_8BIT, 0x380b, 0xf0}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x08}, -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x04}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+ * 1296x976 30fps 17ms VBlanking 2lane 10Bit (Scaling) -+*DS from 2592x1952 -+*/ -+static struct ov5693_reg const ov5693_1296x976[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ -+ {OV5693_8BIT, 0x3800, 0x00}, -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, -+ {OV5693_8BIT, 0x3803, 0x00}, -+ -+ {OV5693_8BIT, 0x3804, 0x0a}, -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xA3}, -+ -+ {OV5693_8BIT, 0x3808, 0x05}, -+ {OV5693_8BIT, 0x3809, 0x10}, -+ {OV5693_8BIT, 0x380a, 0x03}, -+ {OV5693_8BIT, 0x380b, 0xD0}, -+ -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ -+ {OV5693_8BIT, 0x3810, 0x00}, -+ {OV5693_8BIT, 0x3811, 0x10}, -+ {OV5693_8BIT, 0x3812, 0x00}, -+ {OV5693_8BIT, 0x3813, 0x02}, -+ -+ {OV5693_8BIT, 0x3814, 0x11}, /*X subsample control*/ -+ {OV5693_8BIT, 0x3815, 0x11}, /*Y subsample control*/ -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+ -+}; -+ -+/* -+ * 336x256 30fps 17ms VBlanking 2lane 10Bit (Scaling) -+ DS from 2564x1956 -+ */ -+static struct ov5693_reg const ov5693_336x256[] = { -+ {OV5693_8BIT, 0x3501, 0x3d}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe6}, -+ {OV5693_8BIT, 0x3709, 0xc7}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xa3}, -+ {OV5693_8BIT, 0x3808, 0x01}, -+ {OV5693_8BIT, 0x3809, 0x50}, -+ {OV5693_8BIT, 0x380a, 0x01}, -+ {OV5693_8BIT, 0x380b, 0x00}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x1E}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x04}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+ * 336x256 30fps 17ms VBlanking 2lane 10Bit (Scaling) -+ DS from 2368x1956 -+ */ -+static struct ov5693_reg const ov5693_368x304[] = { -+ {OV5693_8BIT, 0x3501, 0x3d}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe6}, -+ {OV5693_8BIT, 0x3709, 0xc7}, -+ {OV5693_8BIT, 0x3808, 0x01}, -+ {OV5693_8BIT, 0x3809, 0x70}, -+ {OV5693_8BIT, 0x380a, 0x01}, -+ {OV5693_8BIT, 0x380b, 0x30}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x80}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x04}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+ * ov5693_192x160 30fps 17ms VBlanking 2lane 10Bit (Scaling) -+ DS from 2460x1956 -+ */ -+static struct ov5693_reg const ov5693_192x160[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x80}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3804, 0x0a}, -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xA3}, -+ {OV5693_8BIT, 0x3808, 0x00}, -+ {OV5693_8BIT, 0x3809, 0xC0}, -+ {OV5693_8BIT, 0x380a, 0x00}, -+ {OV5693_8BIT, 0x380b, 0xA0}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x40}, -+ {OV5693_8BIT, 0x3813, 0x00}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x04}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+static struct ov5693_reg const ov5693_736x496[] = { -+ {OV5693_8BIT, 0x3501, 0x3d}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe6}, -+ {OV5693_8BIT, 0x3709, 0xc7}, -+ {OV5693_8BIT, 0x3803, 0x68}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0x3b}, -+ {OV5693_8BIT, 0x3808, 0x02}, -+ {OV5693_8BIT, 0x3809, 0xe0}, -+ {OV5693_8BIT, 0x380a, 0x01}, -+ {OV5693_8BIT, 0x380b, 0xf0}, -+ {OV5693_8BIT, 0x380c, 0x0a}, /*hts*/ -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, /*vts*/ -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x08}, -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x04}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+#endif -+ -+/* -+static struct ov5693_reg const ov5693_736x496[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe6}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3803, 0x00}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xa3}, -+ {OV5693_8BIT, 0x3808, 0x02}, -+ {OV5693_8BIT, 0x3809, 0xe0}, -+ {OV5693_8BIT, 0x380a, 0x01}, -+ {OV5693_8BIT, 0x380b, 0xf0}, -+ {OV5693_8BIT, 0x380c, 0x0d}, -+ {OV5693_8BIT, 0x380d, 0xb0}, -+ {OV5693_8BIT, 0x380e, 0x05}, -+ {OV5693_8BIT, 0x380f, 0xf2}, -+ {OV5693_8BIT, 0x3811, 0x08}, -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x01}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+*/ -+/* -+ * 976x556 30fps 8.8ms VBlanking 2lane 10Bit (Scaling) -+ */ -+#if ENABLE_NON_PREVIEW -+static struct ov5693_reg const ov5693_976x556[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3803, 0xf0}, -+ {OV5693_8BIT, 0x3806, 0x06}, -+ {OV5693_8BIT, 0x3807, 0xa7}, -+ {OV5693_8BIT, 0x3808, 0x03}, -+ {OV5693_8BIT, 0x3809, 0xd0}, -+ {OV5693_8BIT, 0x380a, 0x02}, -+ {OV5693_8BIT, 0x380b, 0x2C}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x10}, -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/*DS from 2624x1492*/ -+static struct ov5693_reg const ov5693_1296x736[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ -+ {OV5693_8BIT, 0x3800, 0x00}, -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, -+ {OV5693_8BIT, 0x3803, 0x00}, -+ -+ {OV5693_8BIT, 0x3804, 0x0a}, -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xA3}, -+ -+ {OV5693_8BIT, 0x3808, 0x05}, -+ {OV5693_8BIT, 0x3809, 0x10}, -+ {OV5693_8BIT, 0x380a, 0x02}, -+ {OV5693_8BIT, 0x380b, 0xe0}, -+ -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ -+ {OV5693_8BIT, 0x3813, 0xE8}, -+ -+ {OV5693_8BIT, 0x3814, 0x11}, /*X subsample control*/ -+ {OV5693_8BIT, 0x3815, 0x11}, /*Y subsample control*/ -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+static struct ov5693_reg const ov5693_1636p_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3803, 0xf0}, -+ {OV5693_8BIT, 0x3806, 0x06}, -+ {OV5693_8BIT, 0x3807, 0xa7}, -+ {OV5693_8BIT, 0x3808, 0x06}, -+ {OV5693_8BIT, 0x3809, 0x64}, -+ {OV5693_8BIT, 0x380a, 0x04}, -+ {OV5693_8BIT, 0x380b, 0x48}, -+ {OV5693_8BIT, 0x380c, 0x0a}, /*hts*/ -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, /*vts*/ -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x02}, -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+#endif -+ -+static struct ov5693_reg const ov5693_1616x1216_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x80}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3800, 0x00}, /*{3800,3801} Array X start*/ -+ {OV5693_8BIT, 0x3801, 0x08}, /* 04 //{3800,3801} Array X start*/ -+ {OV5693_8BIT, 0x3802, 0x00}, /*{3802,3803} Array Y start*/ -+ {OV5693_8BIT, 0x3803, 0x04}, /* 00 //{3802,3803} Array Y start*/ -+ {OV5693_8BIT, 0x3804, 0x0a}, /*{3804,3805} Array X end*/ -+ {OV5693_8BIT, 0x3805, 0x37}, /* 3b //{3804,3805} Array X end*/ -+ {OV5693_8BIT, 0x3806, 0x07}, /*{3806,3807} Array Y end*/ -+ {OV5693_8BIT, 0x3807, 0x9f}, /* a3 //{3806,3807} Array Y end*/ -+ {OV5693_8BIT, 0x3808, 0x06}, /*{3808,3809} Final output H size*/ -+ {OV5693_8BIT, 0x3809, 0x50}, /*{3808,3809} Final output H size*/ -+ {OV5693_8BIT, 0x380a, 0x04}, /*{380a,380b} Final output V size*/ -+ {OV5693_8BIT, 0x380b, 0xc0}, /*{380a,380b} Final output V size*/ -+ {OV5693_8BIT, 0x380c, 0x0a}, /*{380c,380d} HTS*/ -+ {OV5693_8BIT, 0x380d, 0x80}, /*{380c,380d} HTS*/ -+ {OV5693_8BIT, 0x380e, 0x07}, /*{380e,380f} VTS*/ -+ {OV5693_8BIT, 0x380f, 0xc0}, /* bc //{380e,380f} VTS*/ -+ {OV5693_8BIT, 0x3810, 0x00}, /*{3810,3811} windowing X offset*/ -+ {OV5693_8BIT, 0x3811, 0x10}, /*{3810,3811} windowing X offset*/ -+ {OV5693_8BIT, 0x3812, 0x00}, /*{3812,3813} windowing Y offset*/ -+ {OV5693_8BIT, 0x3813, 0x06}, /*{3812,3813} windowing Y offset*/ -+ {OV5693_8BIT, 0x3814, 0x11}, /*X subsample control*/ -+ {OV5693_8BIT, 0x3815, 0x11}, /*Y subsample control*/ -+ {OV5693_8BIT, 0x3820, 0x00}, /*FLIP/Binnning control*/ -+ {OV5693_8BIT, 0x3821, 0x1e}, /*MIRROR control*/ -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x5041, 0x84}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+ * 1940x1096 30fps 8.8ms VBlanking 2lane 10bit (Scaling) -+ */ -+#if ENABLE_NON_PREVIEW -+static struct ov5693_reg const ov5693_1940x1096[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3803, 0xf0}, -+ {OV5693_8BIT, 0x3806, 0x06}, -+ {OV5693_8BIT, 0x3807, 0xa7}, -+ {OV5693_8BIT, 0x3808, 0x07}, -+ {OV5693_8BIT, 0x3809, 0x94}, -+ {OV5693_8BIT, 0x380a, 0x04}, -+ {OV5693_8BIT, 0x380b, 0x48}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x02}, -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+static struct ov5693_reg const ov5693_2592x1456_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3800, 0x00}, -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, -+ {OV5693_8BIT, 0x3803, 0xf0}, -+ {OV5693_8BIT, 0x3804, 0x0a}, -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3806, 0x06}, -+ {OV5693_8BIT, 0x3807, 0xa4}, -+ {OV5693_8BIT, 0x3808, 0x0a}, -+ {OV5693_8BIT, 0x3809, 0x20}, -+ {OV5693_8BIT, 0x380a, 0x05}, -+ {OV5693_8BIT, 0x380b, 0xb0}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x10}, -+ {OV5693_8BIT, 0x3813, 0x00}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+#endif -+ -+static struct ov5693_reg const ov5693_2576x1456_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3800, 0x00}, -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, -+ {OV5693_8BIT, 0x3803, 0xf0}, -+ {OV5693_8BIT, 0x3804, 0x0a}, -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3806, 0x06}, -+ {OV5693_8BIT, 0x3807, 0xa4}, -+ {OV5693_8BIT, 0x3808, 0x0a}, -+ {OV5693_8BIT, 0x3809, 0x10}, -+ {OV5693_8BIT, 0x380a, 0x05}, -+ {OV5693_8BIT, 0x380b, 0xb0}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x18}, -+ {OV5693_8BIT, 0x3813, 0x00}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+ * 2592x1944 30fps 0.6ms VBlanking 2lane 10Bit -+ */ -+#if ENABLE_NON_PREVIEW -+static struct ov5693_reg const ov5693_2592x1944_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3803, 0x00}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xa3}, -+ {OV5693_8BIT, 0x3808, 0x0a}, -+ {OV5693_8BIT, 0x3809, 0x20}, -+ {OV5693_8BIT, 0x380a, 0x07}, -+ {OV5693_8BIT, 0x380b, 0x98}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x10}, -+ {OV5693_8BIT, 0x3813, 0x00}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+#endif -+ -+/* -+ * 11:9 Full FOV Output, expected FOV Res: 2346x1920 -+ * ISP Effect Res: 1408x1152 -+ * Sensor out: 1424x1168, DS From: 2380x1952 -+ * -+ * WA: Left Offset: 8, Hor scal: 64 -+ */ -+#if ENABLE_NON_PREVIEW -+static struct ov5693_reg const ov5693_1424x1168_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x3b}, /* long exposure[15:8] */ -+ {OV5693_8BIT, 0x3502, 0x80}, /* long exposure[7:0] */ -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3800, 0x00}, /* TIMING_X_ADDR_START */ -+ {OV5693_8BIT, 0x3801, 0x50}, /* 80 */ -+ {OV5693_8BIT, 0x3802, 0x00}, /* TIMING_Y_ADDR_START */ -+ {OV5693_8BIT, 0x3803, 0x02}, /* 2 */ -+ {OV5693_8BIT, 0x3804, 0x09}, /* TIMING_X_ADDR_END */ -+ {OV5693_8BIT, 0x3805, 0xdd}, /* 2525 */ -+ {OV5693_8BIT, 0x3806, 0x07}, /* TIMING_Y_ADDR_END */ -+ {OV5693_8BIT, 0x3807, 0xa1}, /* 1953 */ -+ {OV5693_8BIT, 0x3808, 0x05}, /* TIMING_X_OUTPUT_SIZE */ -+ {OV5693_8BIT, 0x3809, 0x90}, /* 1424 */ -+ {OV5693_8BIT, 0x380a, 0x04}, /* TIMING_Y_OUTPUT_SIZE */ -+ {OV5693_8BIT, 0x380b, 0x90}, /* 1168 */ -+ {OV5693_8BIT, 0x380c, 0x0a}, /* TIMING_HTS */ -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, /* TIMING_VTS */ -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3810, 0x00}, /* TIMING_ISP_X_WIN */ -+ {OV5693_8BIT, 0x3811, 0x02}, /* 2 */ -+ {OV5693_8BIT, 0x3812, 0x00}, /* TIMING_ISP_Y_WIN */ -+ {OV5693_8BIT, 0x3813, 0x00}, /* 0 */ -+ {OV5693_8BIT, 0x3814, 0x11}, /* TIME_X_INC */ -+ {OV5693_8BIT, 0x3815, 0x11}, /* TIME_Y_INC */ -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+#endif -+ -+/* -+ * 3:2 Full FOV Output, expected FOV Res: 2560x1706 -+ * ISP Effect Res: 720x480 -+ * Sensor out: 736x496, DS From 2616x1764 -+ */ -+static struct ov5693_reg const ov5693_736x496_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x3b}, /* long exposure[15:8] */ -+ {OV5693_8BIT, 0x3502, 0x80}, /* long exposure[7:0] */ -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3800, 0x00}, /* TIMING_X_ADDR_START */ -+ {OV5693_8BIT, 0x3801, 0x02}, /* 2 */ -+ {OV5693_8BIT, 0x3802, 0x00}, /* TIMING_Y_ADDR_START */ -+ {OV5693_8BIT, 0x3803, 0x62}, /* 98 */ -+ {OV5693_8BIT, 0x3804, 0x0a}, /* TIMING_X_ADDR_END */ -+ {OV5693_8BIT, 0x3805, 0x3b}, /* 2619 */ -+ {OV5693_8BIT, 0x3806, 0x07}, /* TIMING_Y_ADDR_END */ -+ {OV5693_8BIT, 0x3807, 0x43}, /* 1859 */ -+ {OV5693_8BIT, 0x3808, 0x02}, /* TIMING_X_OUTPUT_SIZE */ -+ {OV5693_8BIT, 0x3809, 0xe0}, /* 736 */ -+ {OV5693_8BIT, 0x380a, 0x01}, /* TIMING_Y_OUTPUT_SIZE */ -+ {OV5693_8BIT, 0x380b, 0xf0}, /* 496 */ -+ {OV5693_8BIT, 0x380c, 0x0a}, /* TIMING_HTS */ -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, /* TIMING_VTS */ -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3810, 0x00}, /* TIMING_ISP_X_WIN */ -+ {OV5693_8BIT, 0x3811, 0x02}, /* 2 */ -+ {OV5693_8BIT, 0x3812, 0x00}, /* TIMING_ISP_Y_WIN */ -+ {OV5693_8BIT, 0x3813, 0x00}, /* 0 */ -+ {OV5693_8BIT, 0x3814, 0x11}, /* TIME_X_INC */ -+ {OV5693_8BIT, 0x3815, 0x11}, /* TIME_Y_INC */ -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+static struct ov5693_reg const ov5693_2576x1936_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3803, 0x00}, -+ {OV5693_8BIT, 0x3806, 0x07}, -+ {OV5693_8BIT, 0x3807, 0xa3}, -+ {OV5693_8BIT, 0x3808, 0x0a}, -+ {OV5693_8BIT, 0x3809, 0x10}, -+ {OV5693_8BIT, 0x380a, 0x07}, -+ {OV5693_8BIT, 0x380b, 0x90}, -+ {OV5693_8BIT, 0x380c, 0x0a}, -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3811, 0x18}, -+ {OV5693_8BIT, 0x3813, 0x00}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_8BIT, 0x0100, 0x01}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+static struct ov5693_resolution ov5693_res_preview[] = { -+ { -+ .desc = "ov5693_736x496_30fps", -+ .width = 736, -+ .height = 496, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_736x496_30fps, -+ }, -+ { -+ .desc = "ov5693_1616x1216_30fps", -+ .width = 1616, -+ .height = 1216, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_1616x1216_30fps, -+ }, -+ { -+ .desc = "ov5693_5M_30fps", -+ .width = 2576, -+ .height = 1456, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_2576x1456_30fps, -+ }, -+ { -+ .desc = "ov5693_5M_30fps", -+ .width = 2576, -+ .height = 1936, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_2576x1936_30fps, -+ }, -+}; -+ -+#define N_RES_PREVIEW (ARRAY_SIZE(ov5693_res_preview)) -+ -+/* -+ * Disable non-preview configurations until the configuration selection is -+ * improved. -+ */ -+#if ENABLE_NON_PREVIEW -+struct ov5693_resolution ov5693_res_still[] = { -+ { -+ .desc = "ov5693_736x496_30fps", -+ .width = 736, -+ .height = 496, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_736x496_30fps, -+ }, -+ { -+ .desc = "ov5693_1424x1168_30fps", -+ .width = 1424, -+ .height = 1168, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_1424x1168_30fps, -+ }, -+ { -+ .desc = "ov5693_1616x1216_30fps", -+ .width = 1616, -+ .height = 1216, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_1616x1216_30fps, -+ }, -+ { -+ .desc = "ov5693_5M_30fps", -+ .width = 2592, -+ .height = 1456, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_2592x1456_30fps, -+ }, -+ { -+ .desc = "ov5693_5M_30fps", -+ .width = 2592, -+ .height = 1944, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_2592x1944_30fps, -+ }, -+}; -+ -+#define N_RES_STILL (ARRAY_SIZE(ov5693_res_still)) -+ -+struct ov5693_resolution ov5693_res_video[] = { -+ { -+ .desc = "ov5693_736x496_30fps", -+ .width = 736, -+ .height = 496, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 1, -+ .regs = ov5693_736x496, -+ }, -+ { -+ .desc = "ov5693_336x256_30fps", -+ .width = 336, -+ .height = 256, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 1, -+ .regs = ov5693_336x256, -+ }, -+ { -+ .desc = "ov5693_368x304_30fps", -+ .width = 368, -+ .height = 304, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 1, -+ .regs = ov5693_368x304, -+ }, -+ { -+ .desc = "ov5693_192x160_30fps", -+ .width = 192, -+ .height = 160, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 1, -+ .regs = ov5693_192x160, -+ }, -+ { -+ .desc = "ov5693_1296x736_30fps", -+ .width = 1296, -+ .height = 736, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 0, -+ .regs = ov5693_1296x736, -+ }, -+ { -+ .desc = "ov5693_1296x976_30fps", -+ .width = 1296, -+ .height = 976, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 0, -+ .regs = ov5693_1296x976, -+ }, -+ { -+ .desc = "ov5693_1636P_30fps", -+ .width = 1636, -+ .height = 1096, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_1636p_30fps, -+ }, -+ { -+ .desc = "ov5693_1080P_30fps", -+ .width = 1940, -+ .height = 1096, -+ .fps = 30, -+ .pix_clk_freq = 160, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_1940x1096, -+ }, -+ { -+ .desc = "ov5693_5M_30fps", -+ .width = 2592, -+ .height = 1456, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_2592x1456_30fps, -+ }, -+ { -+ .desc = "ov5693_5M_30fps", -+ .width = 2592, -+ .height = 1944, -+ .pix_clk_freq = 160, -+ .fps = 30, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 1, -+ .bin_factor_y = 1, -+ .bin_mode = 0, -+ .regs = ov5693_2592x1944_30fps, -+ .crop = { -+ .left = 0, -+ .top = 0, -+ .width = 2592, -+ .height = 1944 -+ }, -+ }, -+}; -+ -+#define N_RES_VIDEO (ARRAY_SIZE(ov5693_res_video)) -+#endif -+ -+static struct ov5693_resolution *ov5693_res = ov5693_res_video; -+static unsigned long N_RES = N_RES_VIDEO; -+#endif --- -2.31.1 - -From 488d44e707524867a901a4f8c9ba85b0c21f23af Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 17 Jan 2021 19:08:18 +0000 -Subject: [PATCH] media: i2c: Add reset pin toggling to ov5693 - -The ov5693 has an xshutdown pin which can be present and, if so, needs -toggling as part of power on sequence. - -Add calls to handle the reset GPIO - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 32485e4ed42b..f9ced52ad37a 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1085,6 +1085,8 @@ static int __power_up(struct v4l2_subdev *sd) - if (ret) - goto fail_power; - -+ gpiod_set_value_cansleep(sensor->reset, 0); -+ - __cci_delay(up_delay); - - return 0; -@@ -1103,6 +1105,8 @@ static int power_down(struct v4l2_subdev *sd) - - dev->focus = OV5693_INVALID_CONFIG; - -+ gpiod_set_value_cansleep(sensor->reset, 1); -+ - clk_disable_unprepare(dev->clk); - - if (dev->indicator_led) --- -2.31.1 - -From dc022a7cb6da36033151cabbd014930083b8ab99 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 17 Jan 2021 21:39:15 +0000 -Subject: [PATCH] media: i2c: Fix misnamed variable in power_down() for ov5693 - -Fix the misnamed variable in gpiod_set_value_cansleep(). - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index f9ced52ad37a..9fd44a3d1d85 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1105,7 +1105,7 @@ static int power_down(struct v4l2_subdev *sd) - - dev->focus = OV5693_INVALID_CONFIG; - -- gpiod_set_value_cansleep(sensor->reset, 1); -+ gpiod_set_value_cansleep(dev->reset, 1); - - clk_disable_unprepare(dev->clk); - --- -2.31.1 - -From bfe339c0170e1ef63fd69a4651e8ead0fac22f74 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Fri, 22 Jan 2021 20:58:13 +0100 -Subject: [PATCH] cio2-bridge: Parse sensor orientation and rotation -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -The sensor orientation is read from the _PLC ACPI buffer and converted -to a v4l2 format. - -See https://uefi.org/sites/default/files/resources/ACPI_6_3_final_Jan30.pdf -page 351 for a definition of the Panel property. - -The sensor rotation is read from the SSDB ACPI buffer and converted into -degrees. - -Signed-off-by: Fabian Wüthrich -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 45 ++++++++++++++++++++-- - drivers/media/pci/intel/ipu3/cio2-bridge.h | 3 ++ - 2 files changed, 44 insertions(+), 4 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index 143f3c0f445e..806d4e5fc177 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -29,6 +29,7 @@ static const struct cio2_sensor_config cio2_supported_sensors[] = { - static const struct cio2_property_names prop_names = { - .clock_frequency = "clock-frequency", - .rotation = "rotation", -+ .orientation = "orientation", - .bus_type = "bus-type", - .data_lanes = "data-lanes", - .remote_endpoint = "remote-endpoint", -@@ -72,11 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, - return ret; - } - -+static u32 cio2_bridge_parse_rotation(u8 rotation) -+{ -+ if (rotation == 1) -+ return 180; -+ return 0; -+} -+ -+static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) -+{ -+ switch (panel) { -+ case 4: -+ return V4L2_FWNODE_ORIENTATION_FRONT; -+ case 5: -+ return V4L2_FWNODE_ORIENTATION_BACK; -+ default: -+ return V4L2_FWNODE_ORIENTATION_EXTERNAL; -+ } -+} -+ - static void cio2_bridge_create_fwnode_properties( - struct cio2_sensor *sensor, - struct cio2_bridge *bridge, - const struct cio2_sensor_config *cfg) - { -+ u32 rotation; -+ enum v4l2_fwnode_orientation orientation; -+ -+ rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); -+ orientation = cio2_bridge_parse_orientation(sensor->pld->panel); -+ - sensor->prop_names = prop_names; - - sensor->local_ref[0].node = &sensor->swnodes[SWNODE_CIO2_ENDPOINT]; -@@ -85,9 +111,12 @@ static void cio2_bridge_create_fwnode_properties( - sensor->dev_properties[0] = PROPERTY_ENTRY_U32( - sensor->prop_names.clock_frequency, - sensor->ssdb.mclkspeed); -- sensor->dev_properties[1] = PROPERTY_ENTRY_U8( -+ sensor->dev_properties[1] = PROPERTY_ENTRY_U32( - sensor->prop_names.rotation, -- sensor->ssdb.degree); -+ rotation); -+ sensor->dev_properties[2] = PROPERTY_ENTRY_U32( -+ sensor->prop_names.orientation, -+ orientation); - - sensor->ep_properties[0] = PROPERTY_ENTRY_U32( - sensor->prop_names.bus_type, -@@ -159,6 +188,7 @@ static void cio2_bridge_unregister_sensors(struct cio2_bridge *bridge) - for (i = 0; i < bridge->n_sensors; i++) { - sensor = &bridge->sensors[i]; - software_node_unregister_nodes(sensor->swnodes); -+ ACPI_FREE(sensor->pld); - acpi_dev_put(sensor->adev); - } - } -@@ -170,6 +200,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - struct fwnode_handle *fwnode; - struct cio2_sensor *sensor; - struct acpi_device *adev; -+ acpi_status status; - int ret; - - for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { -@@ -193,11 +224,15 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - if (ret) - goto err_put_adev; - -+ status = acpi_get_physical_device_location(adev->handle, &sensor->pld); -+ if (ACPI_FAILURE(status)) -+ goto err_put_adev; -+ - if (sensor->ssdb.lanes > CIO2_MAX_LANES) { - dev_err(&adev->dev, - "Number of lanes in SSDB is invalid\n"); - ret = -EINVAL; -- goto err_put_adev; -+ goto err_free_pld; - } - - cio2_bridge_create_fwnode_properties(sensor, bridge, cfg); -@@ -205,7 +240,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - - ret = software_node_register_nodes(sensor->swnodes); - if (ret) -- goto err_put_adev; -+ goto err_free_pld; - - fwnode = software_node_fwnode(&sensor->swnodes[SWNODE_SENSOR_HID]); - if (!fwnode) { -@@ -225,6 +260,8 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - - err_free_swnodes: - software_node_unregister_nodes(sensor->swnodes); -+err_free_pld: -+ ACPI_FREE(sensor->pld); - err_put_adev: - acpi_dev_put(sensor->adev); - err_out: -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index dd0ffcafa489..924d99d20328 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -80,6 +80,7 @@ struct cio2_sensor_ssdb { - struct cio2_property_names { - char clock_frequency[16]; - char rotation[9]; -+ char orientation[12]; - char bus_type[9]; - char data_lanes[11]; - char remote_endpoint[16]; -@@ -106,6 +107,8 @@ struct cio2_sensor { - struct cio2_node_names node_names; - - struct cio2_sensor_ssdb ssdb; -+ struct acpi_pld_info *pld; -+ - struct cio2_property_names prop_names; - struct property_entry ep_properties[5]; - struct property_entry dev_properties[3]; --- -2.31.1 - -From f7b131d6c1f8813f327ca9eeea2639641d238ba7 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Fri, 22 Jan 2021 21:23:47 +0100 -Subject: [PATCH] ov5693: Add orientation and rotation controls -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - - Parse orientation and rotation from fwnodes and initialize the - respective controls. - -Signed-off-by: Fabian Wüthrich -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 11 +++++++++++ - 1 file changed, 11 insertions(+) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 9fd44a3d1d85..1a85800df7ed 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -31,6 +31,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -1608,6 +1609,7 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - { - struct i2c_client *client = v4l2_get_subdevdata(&ov5693->sd); - const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; -+ struct v4l2_fwnode_device_properties props; - struct v4l2_ctrl *ctrl; - unsigned int i; - int ret; -@@ -1663,6 +1665,15 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - if (ov5693->hblank) - ov5693->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; - -+ /* set properties from fwnode (e.g. rotation, orientation) */ -+ ret = v4l2_fwnode_device_parse(&client->dev, &props); -+ if (ret) -+ return ret; -+ -+ ret = v4l2_ctrl_new_fwnode_properties(&ov5693->ctrl_handler, ops, &props); -+ if (ret) -+ return ret; -+ - /* Use same lock for controls as for everything else. */ - ov5693->ctrl_handler.lock = &ov5693->input_lock; - ov5693->sd.ctrl_handler = &ov5693->ctrl_handler; --- -2.31.1 - -From 1e9b82baddeff537b88a9a3d607dea6cd98da5c8 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 23 Jan 2021 00:28:32 +0000 -Subject: [PATCH] platform: x86: Stylistic updates for intel-skl-int3472 - -This commit makes a bunch of stylistic updates, minor changes and other -stuff that's part of the improvements pass I'm doing to the code after -taking into account feedback from the list. - -It also alters the ACPI buffer fetching code to be more generalisable so -I can re-use it to fetch the clock frequency. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - .../platform/x86/intel_skl_int3472_common.c | 37 ++++--- - .../platform/x86/intel_skl_int3472_common.h | 7 +- - .../platform/x86/intel_skl_int3472_discrete.c | 101 +++++++++--------- - .../platform/x86/intel_skl_int3472_tps68470.c | 16 +-- - 4 files changed, 89 insertions(+), 72 deletions(-) - -diff --git a/drivers/platform/x86/intel_skl_int3472_common.c b/drivers/platform/x86/intel_skl_int3472_common.c -index 08cb9d3c06aa..549d211979e1 100644 ---- a/drivers/platform/x86/intel_skl_int3472_common.c -+++ b/drivers/platform/x86/intel_skl_int3472_common.c -@@ -7,41 +7,52 @@ - - #include "intel_skl_int3472_common.h" - --int skl_int3472_get_cldb_buffer(struct acpi_device *adev, -- struct int3472_cldb *cldb) -+union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, -+ char *id) - { - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - acpi_handle handle = adev->handle; - union acpi_object *obj; - acpi_status status; -- int ret = 0; - -- status = acpi_evaluate_object(handle, "CLDB", NULL, &buffer); -+ status = acpi_evaluate_object(handle, id, NULL, &buffer); - if (ACPI_FAILURE(status)) -- return -ENODEV; -+ return ERR_PTR(-ENODEV); - - obj = buffer.pointer; - if (!obj) { -- dev_err(&adev->dev, "ACPI device has no CLDB object\n"); -- return -ENODEV; -+ dev_err(&adev->dev, "ACPI device has no %s object\n", id); -+ return ERR_PTR(-ENODEV); - } - - if (obj->type != ACPI_TYPE_BUFFER) { -- dev_err(&adev->dev, "CLDB object is not an ACPI buffer\n"); -- ret = -EINVAL; -- goto out_free_buff; -+ dev_err(&adev->dev, "%s object is not an ACPI buffer\n", id); -+ kfree(obj); -+ return ERR_PTR(-EINVAL); - } - -+ return obj; -+} -+ -+int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) -+{ -+ union acpi_object *obj; -+ int ret = 0; -+ -+ obj = skl_int3472_get_acpi_buffer(adev, "CLDB"); -+ if (IS_ERR(obj)) -+ return PTR_ERR(obj); -+ - if (obj->buffer.length > sizeof(*cldb)) { - dev_err(&adev->dev, "The CLDB buffer is too large\n"); - ret = -EINVAL; -- goto out_free_buff; -+ goto out_free_obj; - } - - memcpy(cldb, obj->buffer.pointer, obj->buffer.length); - --out_free_buff: -- kfree(buffer.pointer); -+out_free_obj: -+ kfree(obj); - return ret; - } - -diff --git a/drivers/platform/x86/intel_skl_int3472_common.h b/drivers/platform/x86/intel_skl_int3472_common.h -index 4ac6bb2b223f..e1083bb67dc6 100644 ---- a/drivers/platform/x86/intel_skl_int3472_common.h -+++ b/drivers/platform/x86/intel_skl_int3472_common.h -@@ -29,7 +29,7 @@ - - #define INT3472_GPIO_FUNCTION_REMAP(_PIN, _FUNCTION) \ - (const struct int3472_gpio_function_remap) { \ -- .documented = _PIN, \ -+ .documented = _PIN, \ - .actual = _FUNCTION \ - } - -@@ -95,5 +95,6 @@ struct int3472_sensor_config { - int skl_int3472_discrete_probe(struct platform_device *pdev); - int skl_int3472_discrete_remove(struct platform_device *pdev); - int skl_int3472_tps68470_probe(struct i2c_client *client); --int skl_int3472_get_cldb_buffer(struct acpi_device *adev, -- struct int3472_cldb *cldb); -+union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, -+ char *id); -+int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); -diff --git a/drivers/platform/x86/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel_skl_int3472_discrete.c -index ea7e57f3e3f0..42ae8396eb64 100644 ---- a/drivers/platform/x86/intel_skl_int3472_discrete.c -+++ b/drivers/platform/x86/intel_skl_int3472_discrete.c -@@ -12,12 +12,12 @@ - - #include "intel_skl_int3472_common.h" - --/* 79234640-9e10-4fea-a5c1b5aa8b19756f */ -+/* 79234640-9e10-4fea-a5c1-b5aa8b19756f */ - static const guid_t int3472_gpio_guid = - GUID_INIT(0x79234640, 0x9e10, 0x4fea, - 0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f); - --/* 822ace8f-2814-4174-a56b5f029fe079ee */ -+/* 822ace8f-2814-4174-a56b-5f029fe079ee */ - static const guid_t cio2_sensor_module_guid = - GUID_INIT(0x822ace8f, 0x2814, 0x4174, - 0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee); -@@ -94,7 +94,7 @@ static const struct clk_ops skl_int3472_clock_ops = { - }; - - static struct int3472_sensor_config * --int3472_get_sensor_module_config(struct int3472_device *int3472) -+skl_int3472_get_sensor_module_config(struct int3472_device *int3472) - { - unsigned int i = ARRAY_SIZE(int3472_sensor_configs); - struct int3472_sensor_config *ret; -@@ -131,9 +131,9 @@ int3472_get_sensor_module_config(struct int3472_device *int3472) - return ret; - } - --static int int3472_map_gpio_to_sensor(struct int3472_device *int3472, -- struct acpi_resource *ares, -- char *func, u32 polarity) -+static int skl_int3472_map_gpio_to_sensor(struct int3472_device *int3472, -+ struct acpi_resource *ares, -+ char *func, u32 polarity) - { - char *path = ares->data.gpio.resource_source.string_ptr; - struct int3472_sensor_config *sensor_config; -@@ -143,7 +143,7 @@ static int int3472_map_gpio_to_sensor(struct int3472_device *int3472, - acpi_status status; - int ret; - -- sensor_config = int3472_get_sensor_module_config(int3472); -+ sensor_config = skl_int3472_get_sensor_module_config(int3472); - if (!IS_ERR(sensor_config) && sensor_config->function_maps) { - unsigned int i = 0; - -@@ -186,17 +186,19 @@ static int int3472_map_gpio_to_sensor(struct int3472_device *int3472, - return 0; - } - --static int int3472_register_clock(struct int3472_device *int3472, -- struct acpi_resource *ares) -+static int skl_int3472_register_clock(struct int3472_device *int3472, -+ struct acpi_resource *ares) - { - char *path = ares->data.gpio.resource_source.string_ptr; -- struct clk_init_data init = { }; -+ struct clk_init_data init = { 0 }; - int ret = 0; - -- init.name = kasprintf(GFP_KERNEL, "%s-clk", acpi_dev_name(int3472->adev)); -+ init.name = kasprintf(GFP_KERNEL, "%s-clk", -+ acpi_dev_name(int3472->adev)); - init.ops = &skl_int3472_clock_ops; - -- int3472->clock.gpio = acpi_get_gpiod(path, ares->data.gpio.pin_table[0]); -+ int3472->clock.gpio = acpi_get_gpiod(path, -+ ares->data.gpio.pin_table[0]); - if (IS_ERR(int3472->clock.gpio)) { - ret = PTR_ERR(int3472->clock.gpio); - goto out_free_init_name; -@@ -226,17 +228,16 @@ static int int3472_register_clock(struct int3472_device *int3472, - return ret; - } - --static int int3472_register_regulator(struct int3472_device *int3472, -- struct acpi_resource *ares) -+static int skl_int3472_register_regulator(struct int3472_device *int3472, -+ struct acpi_resource *ares) - { - char *path = ares->data.gpio.resource_source.string_ptr; - struct int3472_sensor_config *sensor_config; - struct regulator_init_data init_data = { }; -- struct int3472_gpio_regulator *regulator; - struct regulator_config cfg = { }; - int ret; - -- sensor_config = int3472_get_sensor_module_config(int3472); -+ sensor_config = skl_int3472_get_sensor_module_config(int3472); - if (IS_ERR_OR_NULL(sensor_config)) { - dev_err(&int3472->pdev->dev, "No sensor module config\n"); - return PTR_ERR(sensor_config); -@@ -252,26 +253,29 @@ static int int3472_register_regulator(struct int3472_device *int3472, - init_data.num_consumer_supplies = 1; - init_data.consumer_supplies = &sensor_config->supply_map; - -- snprintf(int3472->regulator.regulator_name, GPIO_REGULATOR_NAME_LENGTH, -- "int3472-discrete-regulator"); -- snprintf(int3472->regulator.supply_name, GPIO_REGULATOR_SUPPLY_NAME_LENGTH, -- "supply-0"); -+ snprintf(int3472->regulator.regulator_name, -+ GPIO_REGULATOR_NAME_LENGTH, "int3472-discrete-regulator"); -+ snprintf(int3472->regulator.supply_name, -+ GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0"); - -- int3472->regulator.rdesc = INT3472_REGULATOR(int3472->regulator.regulator_name, -- int3472->regulator.supply_name, -- &int3472_gpio_regulator_ops); -+ int3472->regulator.rdesc = INT3472_REGULATOR( -+ int3472->regulator.regulator_name, -+ int3472->regulator.supply_name, -+ &int3472_gpio_regulator_ops); - -- int3472->regulator.gpio = acpi_get_gpiod(path, ares->data.gpio.pin_table[0]); -+ int3472->regulator.gpio = acpi_get_gpiod(path, -+ ares->data.gpio.pin_table[0]); - if (IS_ERR(int3472->regulator.gpio)) { -- ret = PTR_ERR(int3472->regulator.gpio); -- goto err_free_regulator; -+ dev_err(&int3472->pdev->dev, "Failed to get GPIO line\n"); -+ return PTR_ERR(int3472->regulator.gpio); - } - - cfg.dev = &int3472->adev->dev; - cfg.init_data = &init_data; - cfg.ena_gpiod = int3472->regulator.gpio; - -- int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc, &cfg); -+ int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc, -+ &cfg); - if (IS_ERR(int3472->regulator.rdev)) { - ret = PTR_ERR(int3472->regulator.rdev); - goto err_free_gpio; -@@ -280,15 +284,13 @@ static int int3472_register_regulator(struct int3472_device *int3472, - return 0; - - err_free_gpio: -- gpiod_put(regulator->gpio); --err_free_regulator: -- kfree(regulator); -+ gpiod_put(int3472->regulator.gpio); - - return ret; - } - - /** -- * int3472_handle_gpio_resources: maps PMIC resources to consuming sensor -+ * skl_int3472_handle_gpio_resources: maps PMIC resources to consuming sensor - * @ares: A pointer to a &struct acpi_resource - * @data: A pointer to a &struct int3472_device - * -@@ -305,8 +307,9 @@ static int int3472_register_regulator(struct int3472_device *int3472, - * - * There are some known platform specific quirks where that does not quite - * hold up; for example where a pin with type 0x01 (Power down) is mapped to -- * a sensor pin that performs a reset function. These will be handled by the -- * mapping sub-functions. -+ * a sensor pin that performs a reset function or entries in _CRS and _DSM that -+ * do not actually correspond to a physical connection. These will be handled by -+ * the mapping sub-functions. - * - * GPIOs will either be mapped directly to the sensor device or else used - * to create clocks and regulators via the usual frameworks. -@@ -317,8 +320,8 @@ static int int3472_register_regulator(struct int3472_device *int3472, - * * -ENODEV - If the resource has no corresponding _DSM entry - * * -Other - Errors propagated from one of the sub-functions. - */ --static int int3472_handle_gpio_resources(struct acpi_resource *ares, -- void *data) -+static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, -+ void *data) - { - struct int3472_device *int3472 = data; - union acpi_object *obj; -@@ -345,30 +348,30 @@ static int int3472_handle_gpio_resources(struct acpi_resource *ares, - - switch (obj->integer.value & 0xff) { - case INT3472_GPIO_TYPE_RESET: -- ret = int3472_map_gpio_to_sensor(int3472, ares, "reset", -- GPIO_ACTIVE_LOW); -+ ret = skl_int3472_map_gpio_to_sensor(int3472, ares, "reset", -+ GPIO_ACTIVE_LOW); - if (ret) - dev_err(&int3472->pdev->dev, - "Failed to map reset pin to sensor\n"); - - break; - case INT3472_GPIO_TYPE_POWERDOWN: -- ret = int3472_map_gpio_to_sensor(int3472, ares, "powerdown", -- GPIO_ACTIVE_LOW); -+ ret = skl_int3472_map_gpio_to_sensor(int3472, ares, "powerdown", -+ GPIO_ACTIVE_LOW); - if (ret) - dev_err(&int3472->pdev->dev, - "Failed to map powerdown pin to sensor\n"); - - break; - case INT3472_GPIO_TYPE_CLK_ENABLE: -- ret = int3472_register_clock(int3472, ares); -+ ret = skl_int3472_register_clock(int3472, ares); - if (ret) - dev_err(&int3472->pdev->dev, - "Failed to map clock to sensor\n"); - - break; - case INT3472_GPIO_TYPE_POWER_ENABLE: -- ret = int3472_register_regulator(int3472, ares); -+ ret = skl_int3472_register_regulator(int3472, ares); - if (ret) { - dev_err(&int3472->pdev->dev, - "Failed to map regulator to sensor\n"); -@@ -376,8 +379,9 @@ static int int3472_handle_gpio_resources(struct acpi_resource *ares, - - break; - case INT3472_GPIO_TYPE_PRIVACY_LED: -- ret = int3472_map_gpio_to_sensor(int3472, ares, "indicator-led", -- GPIO_ACTIVE_HIGH); -+ ret = skl_int3472_map_gpio_to_sensor(int3472, ares, -+ "indicator-led", -+ GPIO_ACTIVE_HIGH); - if (ret) - dev_err(&int3472->pdev->dev, - "Failed to map indicator led to sensor\n"); -@@ -396,7 +400,7 @@ static int int3472_handle_gpio_resources(struct acpi_resource *ares, - return ret; - } - --static int int3472_parse_crs(struct int3472_device *int3472) -+static int skl_int3472_parse_crs(struct int3472_device *int3472) - { - struct list_head resource_list; - int ret = 0; -@@ -404,7 +408,8 @@ static int int3472_parse_crs(struct int3472_device *int3472) - INIT_LIST_HEAD(&resource_list); - - ret = acpi_dev_get_resources(int3472->adev, &resource_list, -- int3472_handle_gpio_resources, int3472); -+ skl_int3472_handle_gpio_resources, -+ int3472); - - if (!ret) { - gpiod_add_lookup_table(&int3472->gpios); -@@ -423,7 +428,7 @@ int skl_int3472_discrete_probe(struct platform_device *pdev) - struct int3472_cldb cldb; - int ret = 0; - -- ret = skl_int3472_get_cldb_buffer(adev, &cldb); -+ ret = skl_int3472_fill_cldb(adev, &cldb); - if (ret || cldb.control_logic_type != 1) - return -EINVAL; - -@@ -444,10 +449,10 @@ int skl_int3472_discrete_probe(struct platform_device *pdev) - ret = -ENODEV; - goto err_free_int3472; - } -- int3472->sensor_name = i2c_acpi_dev_name(int3472->sensor); -+ int3472->sensor_name = kasprintf(GFP_KERNEL, I2C_DEV_NAME_FORMAT, acpi_dev_name(int3472->sensor)); - int3472->gpios.dev_id = int3472->sensor_name; - -- ret = int3472_parse_crs(int3472); -+ ret = skl_int3472_parse_crs(int3472); - if (ret) { - skl_int3472_discrete_remove(pdev); - goto err_return_ret; -diff --git a/drivers/platform/x86/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel_skl_int3472_tps68470.c -index 3fe27ec0caff..40629291b339 100644 ---- a/drivers/platform/x86/intel_skl_int3472_tps68470.c -+++ b/drivers/platform/x86/intel_skl_int3472_tps68470.c -@@ -87,20 +87,20 @@ int skl_int3472_tps68470_probe(struct i2c_client *client) - - /* - * Check CLDB buffer against the PMIC's adev. If present, then we check -- * the value of control_logic_type field and follow one of the following -- * scenarios: -+ * the value of control_logic_type field and follow one of the -+ * following scenarios: - * -- * 1. No CLDB - likely ACPI tables designed for ChromeOS. We create -- * platform devices for the GPIOs and OpRegion drivers. -+ * 1. No CLDB - likely ACPI tables designed for ChromeOS. We -+ * create platform devices for the GPIOs and OpRegion drivers. - * -- * 2. CLDB, with control_logic_type = 2 - probably ACPI tables made -- * for Windows 2-in-1 platforms. Register pdevs for GPIO, Clock and -- * Regulator drivers to bind to. -+ * 2. CLDB, with control_logic_type = 2 - probably ACPI tables -+ * made for Windows 2-in-1 platforms. Register pdevs for GPIO, -+ * Clock and Regulator drivers to bind to. - * - * 3. Any other value in control_logic_type, we should never have - * gotten to this point; crash and burn. - */ -- ret = skl_int3472_get_cldb_buffer(adev, &cldb); -+ ret = skl_int3472_fill_cldb(adev, &cldb); - if (!ret && cldb.control_logic_type != 2) - return -EINVAL; - --- -2.31.1 - -From 3b0e86111cd5d4f0478c8d7aa45d43821c00c3b3 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 23 Jan 2021 00:30:15 +0000 -Subject: [PATCH] platform: x86: Add recalc_rate opp to int3472-discrete clock - -This commit adds the recalc_rate opp to the clock registered by -int3472-discrete so that sensor drivers calling clk_get_rate() will get a -valid value returned. - -The value is simply read from the sensor's SSDB buffer, and so we pass -CLK_GET_RATE_NOCACHE - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - .../platform/x86/intel_skl_int3472_common.h | 6 +++ - .../platform/x86/intel_skl_int3472_discrete.c | 37 ++++++++++++++++++- - 2 files changed, 41 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/x86/intel_skl_int3472_common.h b/drivers/platform/x86/intel_skl_int3472_common.h -index e1083bb67dc6..860c849b7769 100644 ---- a/drivers/platform/x86/intel_skl_int3472_common.h -+++ b/drivers/platform/x86/intel_skl_int3472_common.h -@@ -17,6 +17,8 @@ - #define GPIO_REGULATOR_NAME_LENGTH 27 - #define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9 - -+#define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86 -+ - #define INT3472_REGULATOR(_NAME, _SUPPLY, _OPS) \ - (const struct regulator_desc) { \ - .name = _NAME, \ -@@ -36,6 +38,9 @@ - #define to_int3472_clk(hw) \ - container_of(hw, struct int3472_gpio_clock, clk_hw) - -+#define to_int3472_device(clk) \ -+ container_of(clk, struct int3472_device, clock) -+ - struct int3472_cldb { - u8 version; - /* -@@ -62,6 +67,7 @@ struct int3472_gpio_regulator { - struct int3472_gpio_clock { - struct clk *clk; - struct clk_hw clk_hw; -+ struct clk_lookup *cl; - struct gpio_desc *gpio; - }; - -diff --git a/drivers/platform/x86/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel_skl_int3472_discrete.c -index 42ae8396eb64..98eb1ec3399e 100644 ---- a/drivers/platform/x86/intel_skl_int3472_discrete.c -+++ b/drivers/platform/x86/intel_skl_int3472_discrete.c -@@ -86,11 +86,41 @@ static void skl_int3472_clk_unprepare(struct clk_hw *hw) - /* Likewise, nothing to do here... */ - } - -+static unsigned int skl_int3472_get_clk_frequency(struct int3472_device *int3472) -+{ -+ union acpi_object *obj; -+ unsigned int ret = 0; -+ -+ obj = skl_int3472_get_acpi_buffer(int3472->sensor, "SSDB"); -+ if (IS_ERR(obj)) -+ goto out_free_buff; /* report rate as 0 on error */ -+ -+ if (obj->buffer.length < CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET + sizeof(u32)) { -+ dev_err(&int3472->pdev->dev, "The buffer is too small\n"); -+ goto out_free_buff; -+ } -+ -+ ret = *(u32*)(obj->buffer.pointer + CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET); -+ -+out_free_buff: -+ kfree(obj); -+ return ret; -+} -+ -+static unsigned long skl_int3472_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) -+{ -+ struct int3472_gpio_clock *clk = to_int3472_clk(hw); -+ struct int3472_device *int3472 = to_int3472_device(clk); -+ -+ return skl_int3472_get_clk_frequency(int3472); -+} -+ - static const struct clk_ops skl_int3472_clock_ops = { - .prepare = skl_int3472_clk_prepare, - .unprepare = skl_int3472_clk_unprepare, - .enable = skl_int3472_clk_enable, - .disable = skl_int3472_clk_disable, -+ .recalc_rate = skl_int3472_clk_recalc_rate, - }; - - static struct int3472_sensor_config * -@@ -196,6 +226,7 @@ static int skl_int3472_register_clock(struct int3472_device *int3472, - init.name = kasprintf(GFP_KERNEL, "%s-clk", - acpi_dev_name(int3472->adev)); - init.ops = &skl_int3472_clock_ops; -+ init.flags |= CLK_GET_RATE_NOCACHE; - - int3472->clock.gpio = acpi_get_gpiod(path, - ares->data.gpio.pin_table[0]); -@@ -212,8 +243,9 @@ static int skl_int3472_register_clock(struct int3472_device *int3472, - goto err_put_gpio; - } - -- ret = clk_register_clkdev(int3472->clock.clk, "xvclk", int3472->sensor_name); -- if (ret) -+ int3472->clock.cl = clkdev_create(int3472->clock.clk, "xvclk", -+ int3472->sensor_name); -+ if (IS_ERR_OR_NULL(int3472->clock.cl)) - goto err_unregister_clk; - - goto out_free_init_name; -@@ -483,6 +515,7 @@ int skl_int3472_discrete_remove(struct platform_device *pdev) - if (!IS_ERR_OR_NULL(int3472->clock.clk)) { - gpiod_put(int3472->clock.gpio); - clk_unregister(int3472->clock.clk); -+ clkdev_drop(int3472->clock.cl); - } - - acpi_dev_put(int3472->sensor); --- -2.31.1 - -From b61bdcf5ebe19d2d8781074fdb3d3f2a7c247109 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Sun, 24 Jan 2021 11:07:42 +0100 -Subject: [PATCH] cio2-bridge: Use macros and add warnings -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Use macros for the _PLD panel as defined in the ACPI spec 6.3 and emit -a warning if we see an unknown value. - -Signed-off-by: Fabian Wüthrich -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 33 ++++++++++++++++------ - drivers/media/pci/intel/ipu3/cio2-bridge.h | 13 +++++++++ - 2 files changed, 37 insertions(+), 9 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index 806d4e5fc177..3c373ad1c0b0 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -73,21 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, - return ret; - } - --static u32 cio2_bridge_parse_rotation(u8 rotation) -+static u32 cio2_bridge_parse_rotation(struct cio2_sensor *sensor) - { -- if (rotation == 1) -+ switch (sensor->ssdb.degree) { -+ case CIO2_SENSOR_ROTATION_NORMAL: -+ return 0; -+ case CIO2_SENSOR_ROTATION_INVERTED: - return 180; -- return 0; -+ default: -+ dev_warn(&sensor->adev->dev, -+ "Unknown rotation %d. Assume 0 degree rotation\n", -+ sensor->ssdb.degree); -+ return 0; -+ } - } - --static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) -+static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(struct cio2_sensor *sensor) - { -- switch (panel) { -- case 4: -+ switch (sensor->pld->panel) { -+ case CIO2_PLD_PANEL_FRONT: - return V4L2_FWNODE_ORIENTATION_FRONT; -- case 5: -+ case CIO2_PLD_PANEL_BACK: - return V4L2_FWNODE_ORIENTATION_BACK; -+ case CIO2_PLD_PANEL_TOP: -+ case CIO2_PLD_PANEL_LEFT: -+ case CIO2_PLD_PANEL_RIGHT: -+ case CIO2_PLD_PANEL_UNKNOWN: -+ return V4L2_FWNODE_ORIENTATION_EXTERNAL; - default: -+ dev_warn(&sensor->adev->dev, "Unknown _PLD panel value %d\n", -+ sensor->pld->panel); - return V4L2_FWNODE_ORIENTATION_EXTERNAL; - } - } -@@ -100,8 +115,8 @@ static void cio2_bridge_create_fwnode_properties( - u32 rotation; - enum v4l2_fwnode_orientation orientation; - -- rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); -- orientation = cio2_bridge_parse_orientation(sensor->pld->panel); -+ rotation = cio2_bridge_parse_rotation(sensor); -+ orientation = cio2_bridge_parse_orientation(sensor); - - sensor->prop_names = prop_names; - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index 924d99d20328..e1e388cc9f45 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -12,6 +12,19 @@ - #define CIO2_MAX_LANES 4 - #define MAX_NUM_LINK_FREQS 3 - -+/* Values are estimated guesses as we don't have a spec */ -+#define CIO2_SENSOR_ROTATION_NORMAL 0 -+#define CIO2_SENSOR_ROTATION_INVERTED 1 -+ -+/* Panel position defined in _PLD section of ACPI Specification 6.3 */ -+#define CIO2_PLD_PANEL_TOP 0 -+#define CIO2_PLD_PANEL_BOTTOM 1 -+#define CIO2_PLD_PANEL_LEFT 2 -+#define CIO2_PLD_PANEL_RIGHT 3 -+#define CIO2_PLD_PANEL_FRONT 4 -+#define CIO2_PLD_PANEL_BACK 5 -+#define CIO2_PLD_PANEL_UNKNOWN 6 -+ - #define CIO2_SENSOR_CONFIG(_HID, _NR, ...) \ - (const struct cio2_sensor_config) { \ - .hid = _HID, \ --- -2.31.1 - -From 8355535afa261a7599c589440f71d2b06b77b168 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 8 Feb 2021 21:44:38 +0000 -Subject: [PATCH] media: i2c: Tidy up ov5693_init_controls() - -The ov5693 driver initialises a bunch of v4l2 controls and throws away -the pointers. This seems weird, let's not do that. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 46 ++++++++++++++++++++++---------------- - drivers/media/i2c/ov5693.h | 12 +++++++++- - 2 files changed, 38 insertions(+), 20 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 1a85800df7ed..a9747ab783d7 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1610,7 +1610,6 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - struct i2c_client *client = v4l2_get_subdevdata(&ov5693->sd); - const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; - struct v4l2_fwnode_device_properties props; -- struct v4l2_ctrl *ctrl; - unsigned int i; - int ret; - int hblank; -@@ -1628,15 +1627,17 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - NULL); - - /* link freq */ -- ctrl = v4l2_ctrl_new_int_menu(&ov5693->ctrl_handler, NULL, -- V4L2_CID_LINK_FREQ, -- 0, 0, link_freq_menu_items); -- if (ctrl) -- ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ ov5693->ctrls.link_freq = v4l2_ctrl_new_int_menu(&ov5693->ctrl_handler, -+ NULL, V4L2_CID_LINK_FREQ, -+ 0, 0, link_freq_menu_items); -+ if (ov5693->ctrls.link_freq) -+ ov5693->ctrls.link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; - - /* pixel rate */ -- v4l2_ctrl_new_std(&ov5693->ctrl_handler, NULL, V4L2_CID_PIXEL_RATE, -- 0, OV5693_PIXEL_RATE, 1, OV5693_PIXEL_RATE); -+ ov5693->ctrls.pixel_rate = v4l2_ctrl_new_std(&ov5693->ctrl_handler, NULL, -+ V4L2_CID_PIXEL_RATE, 0, -+ OV5693_PIXEL_RATE, 1, -+ OV5693_PIXEL_RATE); - - if (ov5693->ctrl_handler.error) { - ov5693_remove(client); -@@ -1645,25 +1646,32 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - - /* Exposure */ - -- v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_EXPOSURE, 16, 1048575, 16, -- 512); -+ ov5693->ctrls.exposure = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -+ V4L2_CID_EXPOSURE, 16, -+ 1048575, 16, 512); - - /* Gain */ - -- v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_ANALOGUE_GAIN, 1, 1023, 1, 128); -- v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_DIGITAL_GAIN, 1, 3999, 1, 1000); -+ ov5693->ctrls.analogue_gain = v4l2_ctrl_new_std(&ov5693->ctrl_handler, -+ ops, V4L2_CID_ANALOGUE_GAIN, -+ 1, 1023, 1, 128); -+ ov5693->ctrls.digital_gain = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -+ V4L2_CID_DIGITAL_GAIN, 1, -+ 3999, 1, 1000); - - /* Flip */ - -- v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_HFLIP, 0, 1, 1, 0); -- v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, V4L2_CID_VFLIP, 0, 1, 1, 0); -+ ov5693->ctrls.hflip = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -+ V4L2_CID_HFLIP, 0, 1, 1, 0); -+ ov5693->ctrls.vflip = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -+ V4L2_CID_VFLIP, 0, 1, 1, 0); - - hblank = OV5693_PPL_DEFAULT - ov5693->mode->width; -- ov5693->hblank = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -- V4L2_CID_HBLANK, hblank, hblank, -- 1, hblank); -- if (ov5693->hblank) -- ov5693->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ ov5693->ctrls.hblank = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -+ V4L2_CID_HBLANK, hblank, hblank, -+ 1, hblank); -+ if (ov5693->ctrls.hblank) -+ ov5693->ctrls.hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; - - /* set properties from fwnode (e.g. rotation, orientation) */ - ret = v4l2_fwnode_device_parse(&client->dev, &props); -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 9a508e1f3624..26819cf3f4d2 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -270,7 +270,17 @@ struct ov5693_device { - - bool has_vcm; - -- struct v4l2_ctrl *hblank; -+ struct ov5693_v4l2_ctrls { -+ struct v4l2_ctrl *link_freq; -+ struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *exposure; -+ struct v4l2_ctrl *analogue_gain; -+ struct v4l2_ctrl *digital_gain; -+ struct v4l2_ctrl *hflip; -+ struct v4l2_ctrl *vflip; -+ struct v4l2_ctrl *hblank; -+ } ctrls; -+ - }; - - enum ov5693_tok_type { --- -2.31.1 - -From 5810385a943520d0c350e453a34fbe1f06eeadfc Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 8 Feb 2021 21:46:49 +0000 -Subject: [PATCH] media: i2c: Remove OV5693_PPL_DEFAULT - -No need for this macro, the PPL setting is against the mode structs. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 4 +--- - 1 file changed, 1 insertion(+), 3 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index a9747ab783d7..7fb368eec327 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -105,8 +105,6 @@ MODULE_PARM_DESC(up_delay, - #define OV5693_PIXEL_ARRAY_WIDTH 2592U - #define OV5693_PIXEL_ARRAY_HEIGHT 1944U - --#define OV5693_PPL_DEFAULT 2800 -- - static int vcm_ad_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) - { - int err; -@@ -1666,7 +1664,7 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - ov5693->ctrls.vflip = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, - V4L2_CID_VFLIP, 0, 1, 1, 0); - -- hblank = OV5693_PPL_DEFAULT - ov5693->mode->width; -+ hblank = ov5693->mode->pixels_per_line - ov5693->mode->width; - ov5693->ctrls.hblank = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, - V4L2_CID_HBLANK, hblank, hblank, - 1, hblank); --- -2.31.1 - -From 17cb27cf93f98b262a0e0efd25f91d35c49be2f7 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 8 Feb 2021 22:53:02 +0000 -Subject: [PATCH] media: i2c: Add vblank control to ov5693 driver - -The latest libcamera requires a V4L2_CID_VBLANK control in each sensor -driver; add a skeleton one to the ov5693 to fulfill the requirement. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 12 ++++++++++++ - drivers/media/i2c/ov5693.h | 3 +++ - 2 files changed, 15 insertions(+) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 7fb368eec327..1950d7ac2d54 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -946,6 +946,10 @@ static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - return ov5693_flip_horz_configure(dev, !!ctrl->val); - case V4L2_CID_VFLIP: - return ov5693_flip_vert_configure(dev, !!ctrl->val); -+ case V4L2_CID_VBLANK: -+ ret = ov5693_write_reg(client, OV5693_16BIT, OV5693_TIMING_VTS_H, -+ dev->mode->height + ctrl->val); -+ break; - default: - ret = -EINVAL; - } -@@ -1611,6 +1615,7 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - unsigned int i; - int ret; - int hblank; -+ int vblank_max, vblank_min, vblank_def; - - ret = v4l2_ctrl_handler_init(&ov5693->ctrl_handler, - ARRAY_SIZE(ov5693_controls)); -@@ -1671,6 +1676,13 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - if (ov5693->ctrls.hblank) - ov5693->ctrls.hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; - -+ vblank_max = OV5693_TIMING_MAX_VTS - ov5693->mode->height; -+ vblank_def = ov5693->mode->lines_per_frame - ov5693->mode->height; -+ vblank_min = ov5693->mode->lines_per_frame - ov5693->mode->height; -+ ov5693->ctrls.vblank = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -+ V4L2_CID_VBLANK, vblank_min, -+ vblank_max, 1, vblank_def); -+ - /* set properties from fwnode (e.g. rotation, orientation) */ - ret = v4l2_fwnode_device_parse(&client->dev, &props); - if (ret) -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 26819cf3f4d2..9d7eed97963b 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -131,6 +131,8 @@ - /*High 8-bit, and low 8-bit HTS address is 0x380f*/ - #define OV5693_TIMING_VTS_L 0x380f - -+#define OV5693_TIMING_MAX_VTS 0xffff -+ - #define OV5693_MWB_RED_GAIN_H 0x3400 - #define OV5693_MWB_GREEN_GAIN_H 0x3402 - #define OV5693_MWB_BLUE_GAIN_H 0x3404 -@@ -279,6 +281,7 @@ struct ov5693_device { - struct v4l2_ctrl *hflip; - struct v4l2_ctrl *vflip; - struct v4l2_ctrl *hblank; -+ struct v4l2_ctrl *vblank; - } ctrls; - - }; --- -2.31.1 - -From 8f399550aace680ac9e8391ce1438565f4ee3e2d Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 10 Feb 2021 00:36:32 +0000 -Subject: [PATCH] media: i2c: update exposure control for ov5693 - -The exposure control for ov5693 currently is in units of 1/16th of a line, -but I think the framework expects it in units of lines. Set the control to -work in lines and simply apply the multiplication when configuring the chip -registers instead. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 23 ++++++++++++++++++++--- - 1 file changed, 20 insertions(+), 3 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 1950d7ac2d54..cea767230aa9 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -801,6 +801,12 @@ static int ov5693_exposure_configure(struct ov5693_device *sensor, u32 exposure) - { - int ret; - -+ /* -+ * The control for exposure seems to be in units of lines, but the chip -+ * datasheet specifies exposure is in units of 1/16th of a line. -+ */ -+ exposure = exposure * 16; -+ - ov5693_get_exposure(sensor); - ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, - OV5693_EXPOSURE_CTRL_HH_REG, OV5693_EXPOSURE_CTRL_HH(exposure)); -@@ -910,6 +916,16 @@ static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - struct i2c_client *client = v4l2_get_subdevdata(&dev->sd); - int ret = 0; - -+ /* If VBLANK is altered we need to update exposure to compensate */ -+ if (ctrl->id == V4L2_CID_VBLANK) { -+ int exposure_max; -+ exposure_max = dev->mode->lines_per_frame - 8; -+ __v4l2_ctrl_modify_range(dev->ctrls.exposure, dev->ctrls.exposure->minimum, -+ exposure_max, dev->ctrls.exposure->step, -+ dev->ctrls.exposure->val < exposure_max ? -+ dev->ctrls.exposure->val : exposure_max); -+ } -+ - switch (ctrl->id) { - case V4L2_CID_FOCUS_ABSOLUTE: - dev_dbg(&client->dev, "%s: CID_FOCUS_ABSOLUTE:%d.\n", -@@ -1616,6 +1632,7 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - int ret; - int hblank; - int vblank_max, vblank_min, vblank_def; -+ int exposure_max; - - ret = v4l2_ctrl_handler_init(&ov5693->ctrl_handler, - ARRAY_SIZE(ov5693_controls)); -@@ -1648,10 +1665,10 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - } - - /* Exposure */ -- -+ exposure_max = ov5693->mode->lines_per_frame - 8; - ov5693->ctrls.exposure = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, -- V4L2_CID_EXPOSURE, 16, -- 1048575, 16, 512); -+ V4L2_CID_EXPOSURE, 1, -+ exposure_max, 1, 123); - - /* Gain */ - --- -2.31.1 - -From 82c812e90f36e647a1b6edc1f89202be0e8c1603 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 10 Feb 2021 00:39:42 +0000 -Subject: [PATCH] media: i2c: Fix incorrect bit-setting - -The bitmask macros to set the exposure for the ov5693 are not quite right. -Update them so that they're setting the correct bits in the registers. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index cea767230aa9..f681dbfcec56 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -63,11 +63,11 @@ MODULE_PARM_DESC(up_delay, - /* Exposure/gain */ - - #define OV5693_EXPOSURE_CTRL_HH_REG 0x3500 --#define OV5693_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(18, 16)) >> 16) -+#define OV5693_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(14, 12)) >> 12) - #define OV5693_EXPOSURE_CTRL_H_REG 0x3501 --#define OV5693_EXPOSURE_CTRL_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_EXPOSURE_CTRL_H(v) (((v) & GENMASK(11, 4)) >> 4) - #define OV5693_EXPOSURE_CTRL_L_REG 0x3502 --#define OV5693_EXPOSURE_CTRL_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_EXPOSURE_CTRL_L(v) (((v) & GENMASK(3, 0)) << 4) - #define OV5693_EXPOSURE_GAIN_MANUAL_REG 0x3509 - - #define OV5693_GAIN_CTRL_H_REG 0x3504 --- -2.31.1 - -From c25f345598a0e098cf40bbfb225f8980ed46d3a5 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 10 Feb 2021 16:25:48 +0000 -Subject: [PATCH] media: i2c: Don't set stream on during mode config - -Currently the register lists for the ov5693 include setting stream on. -That register shouldn't be set until the control is called, so remove -this setting from all of the modes. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.h | 16 ---------------- - 1 file changed, 16 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 9d7eed97963b..965208078c2b 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -581,7 +581,6 @@ static struct ov5693_reg const ov5693_654x496[] = { - {OV5693_8BIT, 0x3820, 0x04}, - {OV5693_8BIT, 0x3821, 0x1f}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -626,7 +625,6 @@ static struct ov5693_reg const ov5693_1296x976[] = { - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x00}, - {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - - }; -@@ -656,7 +654,6 @@ static struct ov5693_reg const ov5693_336x256[] = { - {OV5693_8BIT, 0x3820, 0x04}, - {OV5693_8BIT, 0x3821, 0x1f}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -683,7 +680,6 @@ static struct ov5693_reg const ov5693_368x304[] = { - {OV5693_8BIT, 0x3820, 0x04}, - {OV5693_8BIT, 0x3821, 0x1f}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -715,7 +711,6 @@ static struct ov5693_reg const ov5693_192x160[] = { - {OV5693_8BIT, 0x3820, 0x04}, - {OV5693_8BIT, 0x3821, 0x1f}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -742,7 +737,6 @@ static struct ov5693_reg const ov5693_736x496[] = { - {OV5693_8BIT, 0x3820, 0x04}, - {OV5693_8BIT, 0x3821, 0x1f}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - #endif -@@ -771,7 +765,6 @@ static struct ov5693_reg const ov5693_736x496[] = { - {OV5693_8BIT, 0x3820, 0x01}, - {OV5693_8BIT, 0x3821, 0x1f}, - {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - */ -@@ -802,7 +795,6 @@ static struct ov5693_reg const ov5693_976x556[] = { - {OV5693_8BIT, 0x3820, 0x00}, - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -841,7 +833,6 @@ static struct ov5693_reg const ov5693_1296x736[] = { - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x00}, - {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -868,7 +859,6 @@ static struct ov5693_reg const ov5693_1636p_30fps[] = { - {OV5693_8BIT, 0x3820, 0x00}, - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - #endif -@@ -904,7 +894,6 @@ static struct ov5693_reg const ov5693_1616x1216_30fps[] = { - {OV5693_8BIT, 0x3821, 0x1e}, /*MIRROR control*/ - {OV5693_8BIT, 0x5002, 0x00}, - {OV5693_8BIT, 0x5041, 0x84}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -935,7 +924,6 @@ static struct ov5693_reg const ov5693_1940x1096[] = { - {OV5693_8BIT, 0x3820, 0x00}, - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -1029,7 +1017,6 @@ static struct ov5693_reg const ov5693_2592x1944_30fps[] = { - {OV5693_8BIT, 0x3820, 0x00}, - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - #endif -@@ -1073,7 +1060,6 @@ static struct ov5693_reg const ov5693_1424x1168_30fps[] = { - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x00}, - {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - #endif -@@ -1114,7 +1100,6 @@ static struct ov5693_reg const ov5693_736x496_30fps[] = { - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x00}, - {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - -@@ -1141,7 +1126,6 @@ static struct ov5693_reg const ov5693_2576x1936_30fps[] = { - {OV5693_8BIT, 0x3820, 0x00}, - {OV5693_8BIT, 0x3821, 0x1e}, - {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_8BIT, 0x0100, 0x01}, - {OV5693_TOK_TERM, 0, 0} - }; - --- -2.31.1 - -From a260d023487fd06f331faab704bce1dafc4c2799 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 10 Feb 2021 16:35:24 +0000 -Subject: [PATCH] media: i2c: Update gain control for ov5693 - -The gain control of the ov5693 driver is setting the wrong bits and -defining an invalid maximum value; change (and use) the bitshifting -macros and update the control's ranges. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 18 +++++++++++------- - 1 file changed, 11 insertions(+), 7 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index f681dbfcec56..51eb3b05d121 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -71,9 +71,9 @@ MODULE_PARM_DESC(up_delay, - #define OV5693_EXPOSURE_GAIN_MANUAL_REG 0x3509 - - #define OV5693_GAIN_CTRL_H_REG 0x3504 --#define OV5693_GAIN_CTRL_H(v) (((v) & GENMASK(9, 8)) >> 8) -+#define OV5693_GAIN_CTRL_H(v) ((v >> 4) & GENMASK(2, 0)) - #define OV5693_GAIN_CTRL_L_REG 0x3505 --#define OV5693_GAIN_CTRL_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_GAIN_CTRL_L(v) ((v << 4) & GENMASK(7, 4)) - - #define OV5693_FORMAT1_REG 0x3820 - #define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -@@ -889,9 +889,13 @@ static int ov5693_analog_gain_configure(struct ov5693_device *sensor, u32 gain) - { - int ret; - -- /* Analog gain */ -+ /* -+ * As with exposure, the lowest 4 bits are fractional bits. Setting -+ * those is not supported, so we have a tiny bit of bit shifting to -+ * do. -+ */ - ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -- OV5693_AGC_L, gain & 0xff); -+ OV5693_AGC_L, OV5693_GAIN_CTRL_L(gain)); - if (ret) { - dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", - __func__, OV5693_AGC_L); -@@ -899,7 +903,7 @@ static int ov5693_analog_gain_configure(struct ov5693_device *sensor, u32 gain) - } - - ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -- OV5693_AGC_H, (gain >> 8) & 0xff); -+ OV5693_AGC_H, OV5693_GAIN_CTRL_H(gain)); - if (ret) { - dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", - __func__, OV5693_AGC_H); -@@ -1674,10 +1678,10 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - - ov5693->ctrls.analogue_gain = v4l2_ctrl_new_std(&ov5693->ctrl_handler, - ops, V4L2_CID_ANALOGUE_GAIN, -- 1, 1023, 1, 128); -+ 1, 127, 1, 8); - ov5693->ctrls.digital_gain = v4l2_ctrl_new_std(&ov5693->ctrl_handler, ops, - V4L2_CID_DIGITAL_GAIN, 1, -- 3999, 1, 1000); -+ 4095, 1, 1024); - - /* Flip */ - --- -2.31.1 - -From 517edadd896692e5e5ed67947753c6fec5fe2a57 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 10 Feb 2021 23:44:39 +0000 -Subject: [PATCH] media: i2c: Fixup gain read - -This function reads the bits from the gain registers poorly. Update -it to do that properly (although, it probably just needs to be deleted) - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 17 +++++++++++------ - 1 file changed, 11 insertions(+), 6 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 51eb3b05d121..952558c4f33b 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -770,30 +770,35 @@ static int ov5693_t_focus_rel(struct v4l2_subdev *sd, s32 value) - - static int ov5693_get_exposure(struct ov5693_device *sensor) - { -- u16 reg_v, reg_v2; -+ u32 exposure = 0; -+ u16 tmp; - int ret = 0; - - /* get exposure */ - ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, - OV5693_EXPOSURE_L, -- ®_v); -+ &tmp); - if (ret) - return ret; - -+ exposure |= ((tmp >> 4) & 0b1111); -+ - ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, - OV5693_EXPOSURE_M, -- ®_v2); -+ &tmp); - if (ret) - return ret; - -- reg_v += reg_v2 << 8; -+ exposure |= (tmp << 4); - ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, - OV5693_EXPOSURE_H, -- ®_v2); -+ &tmp); - if (ret) - return ret; - -- printk("exposure set to: %u\n", reg_v + (((u32)reg_v2 << 16))); -+ exposure |= (tmp << 12); -+ -+ printk("exposure set to: %u\n", exposure); - return ret; - } - --- -2.31.1 - -From 2caa720896232eb723bfa24b8c1b76e8b78a3789 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 11 Feb 2021 00:40:10 +0000 -Subject: [PATCH] media: i2c: Update controls on stream - -Currently the ov5693 driver throws away control setting by simply loading -each mode's default registers. Instead, re-set the user defined controls -during stream with __v4l2_ctrl_handler_setup() - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 952558c4f33b..dd31083eeb7b 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1492,6 +1492,12 @@ static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) - } - } - -+ ret = __v4l2_ctrl_handler_setup(&dev->ctrl_handler); -+ if (ret) { -+ power_down(sd); -+ return ret; -+ } -+ - ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_SW_STREAM, - enable ? OV5693_START_STREAMING : - OV5693_STOP_STREAMING); --- -2.31.1 - -From 650d1eef03996a358d72746472ee5f44a7a15383 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 11 Feb 2021 23:29:15 +0000 -Subject: [PATCH] media: i2c: Correct link frequency value - -The link frequency is given by vts * hts * fps * bits / lanes / 2. In the -case of the ov5693 driver that works out to 400MHz, not 640Mhz. Correct -the macro. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.h | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 965208078c2b..7f1d31a82d3d 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -186,13 +186,13 @@ - #define OV5693_OTP_MODE_READ 1 - - /* link freq and pixel rate required for IPU3 */ --#define OV5693_LINK_FREQ_640MHZ 640000000 -+#define OV5693_LINK_FREQ_400MHZ 400000000 - /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample - * To avoid integer overflow, dividing by bits_per_sample first. - */ --#define OV5693_PIXEL_RATE (OV5693_LINK_FREQ_640MHZ / 10) * 2 * 2 -+#define OV5693_PIXEL_RATE (OV5693_LINK_FREQ_400MHZ / 10) * 2 * 2 - static const s64 link_freq_menu_items[] = { -- OV5693_LINK_FREQ_640MHZ -+ OV5693_LINK_FREQ_400MHZ - }; - - #define OV5693_NUM_SUPPLIES 2 --- -2.31.1 - -From 19ed6b37c6757afd6aebe17b3949563ce6f1c674 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 25 Jan 2021 23:12:09 +0000 -Subject: [PATCH] media: i2c: Cleanup ov5693 driver - -This commit performs some cleanup to the ov5693 driver: - -1. Superfluous words in variable names dropped; "i2c_client" becomes - "client", "input_lock" becomes "lock" -2. ov5693_configure_gpios() is does error handling properly, and uses - gpiod_get_optional() -3. The name of the struct ov5693_device variable in each functions, which - previously was a mix of dev, sensor or ov5693, is standardised to the - latter. -4. The list of headers is alphabetised (and probably also needs trimming) - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 309 +++++++++++++++++++------------------ - drivers/media/i2c/ov5693.h | 5 +- - 2 files changed, 165 insertions(+), 149 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index dd31083eeb7b..0643390c872a 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -16,25 +16,25 @@ - * - */ - -+#include - #include --#include --#include --#include --#include --#include -+#include -+#include - #include -+#include - #include -+#include -+#include - #include --#include --#include --#include --#include -+#include - #include -+#include -+#include -+#include -+#include -+#include - #include - #include --#include --#include --#include - - #include "ov5693.h" - #include "ad5823.h" -@@ -485,12 +485,12 @@ static int ov5693_read_otp_reg_array(struct i2c_client *client, u16 size, - static int __ov5693_otp_read(struct v4l2_subdev *sd, u8 *buf) - { - struct i2c_client *client = v4l2_get_subdevdata(sd); -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - int ret; - int i; - u8 *b = buf; - -- dev->otp_size = 0; -+ ov5693->otp_size = 0; - for (i = 1; i < OV5693_OTP_BANK_MAX; i++) { - /*set bank NO and OTP read mode. */ - ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_OTP_BANK_REG, -@@ -529,7 +529,7 @@ static int __ov5693_otp_read(struct v4l2_subdev *sd, u8 *buf) - //Intel OTP map, try to read 320byts first. - if (i == 21) { - if ((*b) == 0) { -- dev->otp_size = 320; -+ ov5693->otp_size = 320; - break; - } - /* (*b) != 0 */ -@@ -538,7 +538,7 @@ static int __ov5693_otp_read(struct v4l2_subdev *sd, u8 *buf) - } else if (i == - 24) { //if the first 320bytes data doesn't not exist, try to read the next 32bytes data. - if ((*b) == 0) { -- dev->otp_size = 32; -+ ov5693->otp_size = 32; - break; - } - /* (*b) != 0 */ -@@ -547,11 +547,11 @@ static int __ov5693_otp_read(struct v4l2_subdev *sd, u8 *buf) - } else if (i == - 27) { //if the prvious 32bytes data doesn't exist, try to read the next 32bytes data again. - if ((*b) == 0) { -- dev->otp_size = 32; -+ ov5693->otp_size = 32; - break; - } - /* (*b) != 0 */ -- dev->otp_size = 0; // no OTP data. -+ ov5693->otp_size = 0; // no OTP data. - break; - } - -@@ -598,20 +598,20 @@ static void *ov5693_otp_read(struct v4l2_subdev *sd) - return buf; - } - --static int ov5693_update_bits(struct ov5693_device *sensor, u16 address, -+static int ov5693_update_bits(struct ov5693_device *ov5693, u16 address, - u16 mask, u16 bits) - { - u16 value = 0; - int ret; - -- ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, address, &value); -+ ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, address, &value); - if (ret) - return ret; - - value &= ~mask; - value |= bits; - -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, address, value); -+ ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, address, value); - if (ret) - return ret; - -@@ -620,13 +620,13 @@ static int ov5693_update_bits(struct ov5693_device *sensor, u16 address, - - /* Flip */ - --static int ov5693_flip_vert_configure(struct ov5693_device *sensor, bool enable) -+static int ov5693_flip_vert_configure(struct ov5693_device *ov5693, bool enable) - { - u8 bits = OV5693_FORMAT1_FLIP_VERT_ISP_EN | - OV5693_FORMAT1_FLIP_VERT_SENSOR_EN; - int ret; - -- ret = ov5693_update_bits(sensor, OV5693_FORMAT1_REG, bits, -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, bits, - enable ? bits : 0); - if (ret) - return ret; -@@ -634,13 +634,13 @@ static int ov5693_flip_vert_configure(struct ov5693_device *sensor, bool enable) - return 0; - } - --static int ov5693_flip_horz_configure(struct ov5693_device *sensor, bool enable) -+static int ov5693_flip_horz_configure(struct ov5693_device *ov5693, bool enable) - { - u8 bits = OV5693_FORMAT2_FLIP_HORZ_ISP_EN | - OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN; - int ret; - -- ret = ov5693_update_bits(sensor, OV5693_FORMAT2_REG, bits, -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, bits, - enable ? bits : 0); - if (ret) - return ret; -@@ -721,14 +721,14 @@ static int ad5823_t_focus_abs(struct v4l2_subdev *sd, s32 value) - - static int ov5693_t_focus_abs(struct v4l2_subdev *sd, s32 value) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret = 0; - - dev_dbg(&client->dev, "%s: FOCUS_POS: 0x%x\n", __func__, value); - value = clamp(value, 0, OV5693_VCM_MAX_FOCUS_POS); -- if (dev->vcm == VCM_DW9714) { -- if (dev->vcm_update) { -+ if (ov5693->vcm == VCM_DW9714) { -+ if (ov5693->vcm_update) { - ret = vcm_dw_i2c_write(client, VCM_PROTECTION_OFF); - if (ret) - return ret; -@@ -738,17 +738,17 @@ static int ov5693_t_focus_abs(struct v4l2_subdev *sd, s32 value) - ret = vcm_dw_i2c_write(client, VCM_PROTECTION_ON); - if (ret) - return ret; -- dev->vcm_update = false; -+ ov5693->vcm_update = false; - } - ret = vcm_dw_i2c_write(client, - vcm_val(value, VCM_DEFAULT_S)); -- } else if (dev->vcm == VCM_AD5823) { -+ } else if (ov5693->vcm == VCM_AD5823) { - ad5823_t_focus_abs(sd, value); - } - if (ret == 0) { -- dev->number_of_steps = value - dev->focus; -- dev->focus = value; -- dev->timestamp_t_focus_abs = ktime_get(); -+ ov5693->number_of_steps = value - ov5693->focus; -+ ov5693->focus = value; -+ ov5693->timestamp_t_focus_abs = ktime_get(); - } else - dev_err(&client->dev, - "%s: i2c failed. ret %d\n", __func__, ret); -@@ -758,9 +758,9 @@ static int ov5693_t_focus_abs(struct v4l2_subdev *sd, s32 value) - - static int ov5693_t_focus_rel(struct v4l2_subdev *sd, s32 value) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - -- return ov5693_t_focus_abs(sd, dev->focus + value); -+ return ov5693_t_focus_abs(sd, ov5693->focus + value); - } - - #define DELAY_PER_STEP_NS 1000000 -@@ -768,14 +768,14 @@ static int ov5693_t_focus_rel(struct v4l2_subdev *sd, s32 value) - - /* Exposure */ - --static int ov5693_get_exposure(struct ov5693_device *sensor) -+static int ov5693_get_exposure(struct ov5693_device *ov5693) - { - u32 exposure = 0; - u16 tmp; - int ret = 0; - - /* get exposure */ -- ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, - OV5693_EXPOSURE_L, - &tmp); - if (ret) -@@ -783,14 +783,14 @@ static int ov5693_get_exposure(struct ov5693_device *sensor) - - exposure |= ((tmp >> 4) & 0b1111); - -- ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, - OV5693_EXPOSURE_M, - &tmp); - if (ret) - return ret; - - exposure |= (tmp << 4); -- ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, - OV5693_EXPOSURE_H, - &tmp); - if (ret) -@@ -802,7 +802,7 @@ static int ov5693_get_exposure(struct ov5693_device *sensor) - return ret; - } - --static int ov5693_exposure_configure(struct ov5693_device *sensor, u32 exposure) -+static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) - { - int ret; - -@@ -812,40 +812,40 @@ static int ov5693_exposure_configure(struct ov5693_device *sensor, u32 exposure) - */ - exposure = exposure * 16; - -- ov5693_get_exposure(sensor); -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ ov5693_get_exposure(ov5693); -+ ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, - OV5693_EXPOSURE_CTRL_HH_REG, OV5693_EXPOSURE_CTRL_HH(exposure)); - if (ret) - return ret; - -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, - OV5693_EXPOSURE_CTRL_H_REG, OV5693_EXPOSURE_CTRL_H(exposure)); - if (ret) - return ret; - -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, - OV5693_EXPOSURE_CTRL_L_REG, OV5693_EXPOSURE_CTRL_L(exposure)); - if (ret) - return ret; -- ov5693_get_exposure(sensor); -+ ov5693_get_exposure(ov5693); - - return 0; - } - - /* Gain */ - --static int ov5693_get_gain(struct ov5693_device *sensor, u32 *gain) -+static int ov5693_get_gain(struct ov5693_device *ov5693, u32 *gain) - { - u16 gain_l, gain_h; - int ret = 0; - -- ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, - OV5693_GAIN_CTRL_L_REG, - &gain_l); - if (ret) - return ret; - -- ret = ov5693_read_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, - OV5693_GAIN_CTRL_H_REG, - &gain_h); - if (ret) -@@ -856,33 +856,33 @@ static int ov5693_get_gain(struct ov5693_device *sensor, u32 *gain) - - return ret; - } --static int ov5693_gain_configure(struct ov5693_device *sensor, u32 gain) -+static int ov5693_gain_configure(struct ov5693_device *ov5693, u32 gain) - { - int ret; - - /* A 1.0 gain is 0x400 */ - gain = (gain * 1024)/1000; - -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_16BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_16BIT, - OV5693_MWB_RED_GAIN_H, gain); - if (ret) { -- dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ dev_err(&ov5693->client->dev, "%s: write %x error, aborted\n", - __func__, OV5693_MWB_RED_GAIN_H); - return ret; - } - -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_16BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_16BIT, - OV5693_MWB_GREEN_GAIN_H, gain); - if (ret) { -- dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ dev_err(&ov5693->client->dev, "%s: write %x error, aborted\n", - __func__, OV5693_MWB_RED_GAIN_H); - return ret; - } - -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_16BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_16BIT, - OV5693_MWB_BLUE_GAIN_H, gain); - if (ret) { -- dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ dev_err(&ov5693->client->dev, "%s: write %x error, aborted\n", - __func__, OV5693_MWB_RED_GAIN_H); - return ret; - } -@@ -890,7 +890,7 @@ static int ov5693_gain_configure(struct ov5693_device *sensor, u32 gain) - return 0; - } - --static int ov5693_analog_gain_configure(struct ov5693_device *sensor, u32 gain) -+static int ov5693_analog_gain_configure(struct ov5693_device *ov5693, u32 gain) - { - int ret; - -@@ -899,18 +899,18 @@ static int ov5693_analog_gain_configure(struct ov5693_device *sensor, u32 gain) - * those is not supported, so we have a tiny bit of bit shifting to - * do. - */ -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, - OV5693_AGC_L, OV5693_GAIN_CTRL_L(gain)); - if (ret) { -- dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ dev_err(&ov5693->client->dev, "%s: write %x error, aborted\n", - __func__, OV5693_AGC_L); - return ret; - } - -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, - OV5693_AGC_H, OV5693_GAIN_CTRL_H(gain)); - if (ret) { -- dev_err(&sensor->i2c_client->dev, "%s: write %x error, aborted\n", -+ dev_err(&ov5693->client->dev, "%s: write %x error, aborted\n", - __func__, OV5693_AGC_H); - return ret; - } -@@ -920,60 +920,60 @@ static int ov5693_analog_gain_configure(struct ov5693_device *sensor, u32 gain) - - static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - { -- struct ov5693_device *dev = -+ struct ov5693_device *ov5693 = - container_of(ctrl->handler, struct ov5693_device, ctrl_handler); -- struct i2c_client *client = v4l2_get_subdevdata(&dev->sd); -+ struct i2c_client *client = v4l2_get_subdevdata(&ov5693->sd); - int ret = 0; - - /* If VBLANK is altered we need to update exposure to compensate */ - if (ctrl->id == V4L2_CID_VBLANK) { - int exposure_max; -- exposure_max = dev->mode->lines_per_frame - 8; -- __v4l2_ctrl_modify_range(dev->ctrls.exposure, dev->ctrls.exposure->minimum, -- exposure_max, dev->ctrls.exposure->step, -- dev->ctrls.exposure->val < exposure_max ? -- dev->ctrls.exposure->val : exposure_max); -+ exposure_max = ov5693->mode->lines_per_frame - 8; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, ov5693->ctrls.exposure->minimum, -+ exposure_max, ov5693->ctrls.exposure->step, -+ ov5693->ctrls.exposure->val < exposure_max ? -+ ov5693->ctrls.exposure->val : exposure_max); - } - - switch (ctrl->id) { - case V4L2_CID_FOCUS_ABSOLUTE: - dev_dbg(&client->dev, "%s: CID_FOCUS_ABSOLUTE:%d.\n", - __func__, ctrl->val); -- ret = ov5693_t_focus_abs(&dev->sd, ctrl->val); -+ ret = ov5693_t_focus_abs(&ov5693->sd, ctrl->val); - break; - case V4L2_CID_FOCUS_RELATIVE: - dev_dbg(&client->dev, "%s: CID_FOCUS_RELATIVE:%d.\n", - __func__, ctrl->val); -- ret = ov5693_t_focus_rel(&dev->sd, ctrl->val); -+ ret = ov5693_t_focus_rel(&ov5693->sd, ctrl->val); - break; - case V4L2_CID_EXPOSURE: - dev_dbg(&client->dev, "%s: CID_EXPOSURE:%d.\n", - __func__, ctrl->val); -- ret = ov5693_exposure_configure(dev, ctrl->val); -+ ret = ov5693_exposure_configure(ov5693, ctrl->val); - if (ret) - return ret; - break; - case V4L2_CID_ANALOGUE_GAIN: - dev_dbg(&client->dev, "%s: CID_ANALOGUE_GAIN:%d.\n", - __func__, ctrl->val); -- ret = ov5693_analog_gain_configure(dev, ctrl->val); -+ ret = ov5693_analog_gain_configure(ov5693, ctrl->val); - if (ret) - return ret; - break; - case V4L2_CID_DIGITAL_GAIN: - dev_dbg(&client->dev, "%s: CID_DIGITAL_GAIN:%d.\n", - __func__, ctrl->val); -- ret = ov5693_gain_configure(dev, ctrl->val); -+ ret = ov5693_gain_configure(ov5693, ctrl->val); - if (ret) - return ret; - break; - case V4L2_CID_HFLIP: -- return ov5693_flip_horz_configure(dev, !!ctrl->val); -+ return ov5693_flip_horz_configure(ov5693, !!ctrl->val); - case V4L2_CID_VFLIP: -- return ov5693_flip_vert_configure(dev, !!ctrl->val); -+ return ov5693_flip_vert_configure(ov5693, !!ctrl->val); - case V4L2_CID_VBLANK: - ret = ov5693_write_reg(client, OV5693_16BIT, OV5693_TIMING_VTS_H, -- dev->mode->height + ctrl->val); -+ ov5693->mode->height + ctrl->val); - break; - default: - ret = -EINVAL; -@@ -983,16 +983,16 @@ static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - - static int ov5693_g_volatile_ctrl(struct v4l2_ctrl *ctrl) - { -- struct ov5693_device *dev = -+ struct ov5693_device *ov5693 = - container_of(ctrl->handler, struct ov5693_device, ctrl_handler); - int ret = 0; - - switch (ctrl->id) { - case V4L2_CID_EXPOSURE_ABSOLUTE: -- ret = ov5693_q_exposure(&dev->sd, &ctrl->val); -+ ret = ov5693_q_exposure(&ov5693->sd, &ctrl->val); - break; - case V4L2_CID_AUTOGAIN: -- ret = ov5693_get_gain(dev, &ctrl->val); -+ ret = ov5693_get_gain(ov5693, &ctrl->val); - break; - case V4L2_CID_FOCUS_ABSOLUTE: - /* NOTE: there was atomisp-specific function ov5693_q_focus_abs() */ -@@ -1034,12 +1034,12 @@ static const struct v4l2_ctrl_config ov5693_controls[] = { - }, - }; - --static int ov5693_isp_configure(struct ov5693_device *sensor) -+static int ov5693_isp_configure(struct ov5693_device *ov5693) - { - int ret; - - /* Enable lens correction. */ -- ret = ov5693_write_reg(sensor->i2c_client, OV5693_8BIT, -+ ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, - OV5693_ISP_CTRL0_REG, 0x86); - if (ret) - return ret; -@@ -1049,18 +1049,18 @@ static int ov5693_isp_configure(struct ov5693_device *sensor) - - static int ov5693_init(struct v4l2_subdev *sd) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret; - -- if (!dev->has_vcm) -+ if (!ov5693->has_vcm) - return 0; - - dev_info(&client->dev, "%s\n", __func__); -- mutex_lock(&dev->input_lock); -- dev->vcm_update = false; -+ mutex_lock(&ov5693->lock); -+ ov5693->vcm_update = false; - -- if (dev->vcm == VCM_AD5823) { -+ if (ov5693->vcm == VCM_AD5823) { - ret = vcm_ad_i2c_wr8(client, 0x01, 0x01); /* vcm init test */ - if (ret) - dev_err(&client->dev, -@@ -1079,16 +1079,16 @@ static int ov5693_init(struct v4l2_subdev *sd) - } - - /*change initial focus value for ad5823*/ -- if (dev->vcm == VCM_AD5823) { -- dev->focus = AD5823_INIT_FOCUS_POS; -+ if (ov5693->vcm == VCM_AD5823) { -+ ov5693->focus = AD5823_INIT_FOCUS_POS; - ov5693_t_focus_abs(sd, AD5823_INIT_FOCUS_POS); - } else { -- dev->focus = 0; -+ ov5693->focus = 0; - ov5693_t_focus_abs(sd, 0); - } - -- ov5693_isp_configure(dev); -- mutex_unlock(&dev->input_lock); -+ ov5693_isp_configure(ov5693); -+ mutex_unlock(&ov5693->lock); - - return 0; - } -@@ -1096,32 +1096,32 @@ static int ov5693_init(struct v4l2_subdev *sd) - static int __power_up(struct v4l2_subdev *sd) - { - struct i2c_client *client = v4l2_get_subdevdata(sd); -- struct ov5693_device *sensor = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - int ret; - -- ret = clk_prepare_enable(sensor->clk); -+ ret = clk_prepare_enable(ov5693->clk); - if (ret) { - dev_err(&client->dev, "Error enabling clock\n"); - return -EINVAL; - } - -- if (sensor->indicator_led) -- gpiod_set_value_cansleep(sensor->indicator_led, 1); -+ if (ov5693->indicator_led) -+ gpiod_set_value_cansleep(ov5693->indicator_led, 1); - - ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, -- sensor->supplies); -+ ov5693->supplies); - if (ret) - goto fail_power; - -- gpiod_set_value_cansleep(sensor->reset, 0); -+ gpiod_set_value_cansleep(ov5693->reset, 0); - - __cci_delay(up_delay); - - return 0; - - fail_power: -- if (sensor->indicator_led) -- gpiod_set_value_cansleep(sensor->indicator_led, 0); -+ if (ov5693->indicator_led) -+ gpiod_set_value_cansleep(ov5693->indicator_led, 0); - dev_err(&client->dev, "sensor power-up failed\n"); - - return ret; -@@ -1129,17 +1129,17 @@ static int __power_up(struct v4l2_subdev *sd) - - static int power_down(struct v4l2_subdev *sd) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - -- dev->focus = OV5693_INVALID_CONFIG; -+ ov5693->focus = OV5693_INVALID_CONFIG; - -- gpiod_set_value_cansleep(dev->reset, 1); -+ gpiod_set_value_cansleep(ov5693->reset, 1); - -- clk_disable_unprepare(dev->clk); -+ clk_disable_unprepare(ov5693->clk); - -- if (dev->indicator_led) -- gpiod_set_value_cansleep(dev->indicator_led, 0); -- return regulator_bulk_disable(OV5693_NUM_SUPPLIES, dev->supplies); -+ if (ov5693->indicator_led) -+ gpiod_set_value_cansleep(ov5693->indicator_led, 0); -+ return regulator_bulk_disable(OV5693_NUM_SUPPLIES, ov5693->supplies); - } - - static int power_up(struct v4l2_subdev *sd) -@@ -1265,7 +1265,7 @@ static int get_resolution_index(int w, int h) - /* TODO: remove it. */ - static int startup(struct v4l2_subdev *sd) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret = 0; - -@@ -1282,7 +1282,7 @@ static int startup(struct v4l2_subdev *sd) - return ret; - } - -- ret = ov5693_write_reg_array(client, ov5693_res[dev->fmt_idx].regs); -+ ret = ov5693_write_reg_array(client, ov5693_res[ov5693->fmt_idx].regs); - if (ret) { - dev_err(&client->dev, "ov5693 write register err.\n"); - return ret; -@@ -1296,7 +1296,7 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_format *format) - { - struct v4l2_mbus_framefmt *fmt = &format->format; -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret = 0; - int idx; -@@ -1307,7 +1307,7 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - if (!fmt) - return -EINVAL; - -- mutex_lock(&dev->input_lock); -+ mutex_lock(&ov5693->lock); - idx = nearest_resolution_index(fmt->width, fmt->height); - if (idx == -1) { - /* return the largest resolution */ -@@ -1325,8 +1325,8 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - goto mutex_unlock; - } - -- dev->fmt_idx = get_resolution_index(fmt->width, fmt->height); -- if (dev->fmt_idx == -1) { -+ ov5693->fmt_idx = get_resolution_index(fmt->width, fmt->height); -+ if (ov5693->fmt_idx == -1) { - dev_err(&client->dev, "get resolution fail\n"); - ret = -EINVAL; - goto mutex_unlock; -@@ -1339,9 +1339,9 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - continue; - } - -- mutex_unlock(&dev->input_lock); -+ mutex_unlock(&ov5693->lock); - ov5693_init(sd); -- mutex_lock(&dev->input_lock); -+ mutex_lock(&ov5693->lock); - ret = startup(sd); - if (ret) - dev_err(&client->dev, " startup() FAILED!\n"); -@@ -1353,8 +1353,6 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - goto mutex_unlock; - } - -- -- - /* - * After sensor settings are set to HW, sometimes stream is started. - * This would cause ISP timeout because ISP is not ready to receive -@@ -1366,19 +1364,19 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - dev_warn(&client->dev, "ov5693 stream off err\n"); - - mutex_unlock: -- mutex_unlock(&dev->input_lock); -+ mutex_unlock(&ov5693->lock); - return ret; - } - - static const struct v4l2_rect * --__ov5693_get_pad_crop(struct ov5693_device *dev, struct v4l2_subdev_pad_config *cfg, -+__ov5693_get_pad_crop(struct ov5693_device *ov5693, struct v4l2_subdev_pad_config *cfg, - unsigned int pad, enum v4l2_subdev_format_whence which) - { - switch (which) { - case V4L2_SUBDEV_FORMAT_TRY: -- return v4l2_subdev_get_try_crop(&dev->sd, cfg, pad); -+ return v4l2_subdev_get_try_crop(&ov5693->sd, cfg, pad); - case V4L2_SUBDEV_FORMAT_ACTIVE: -- return &dev->mode->crop; -+ return &ov5693->mode->crop; - } - - return NULL; -@@ -1389,12 +1387,12 @@ static int ov5693_get_selection(struct v4l2_subdev *sd, - { - switch (sel->target) { - case V4L2_SEL_TGT_CROP: { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - -- mutex_lock(&dev->input_lock); -- sel->r = *__ov5693_get_pad_crop(dev, cfg, sel->pad, -+ mutex_lock(&ov5693->lock); -+ sel->r = *__ov5693_get_pad_crop(ov5693, cfg, sel->pad, - sel->which); -- mutex_unlock(&dev->input_lock); -+ mutex_unlock(&ov5693->lock); - - return 0; - } -@@ -1424,7 +1422,7 @@ static int ov5693_get_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_format *format) - { - struct v4l2_mbus_framefmt *fmt = &format->format; -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - - if (format->pad) - return -EINVAL; -@@ -1432,8 +1430,8 @@ static int ov5693_get_fmt(struct v4l2_subdev *sd, - if (!fmt) - return -EINVAL; - -- fmt->width = ov5693_res[dev->fmt_idx].width; -- fmt->height = ov5693_res[dev->fmt_idx].height; -+ fmt->width = ov5693_res[ov5693->fmt_idx].width; -+ fmt->height = ov5693_res[ov5693->fmt_idx].height; - fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; - - return 0; -@@ -1481,7 +1479,7 @@ static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret; - -- mutex_lock(&dev->input_lock); -+ mutex_lock(&dev->lock); - - /* power_on() here before streaming for regular PCs. */ - if (enable) { -@@ -1507,26 +1505,26 @@ static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) - power_down(sd); - - out: -- mutex_unlock(&dev->input_lock); -+ mutex_unlock(&dev->lock); - - return ret; - } - - static int ov5693_s_config(struct v4l2_subdev *sd, int irq) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret = 0; - -- mutex_lock(&dev->input_lock); -+ mutex_lock(&ov5693->lock); - ret = power_up(sd); - if (ret) { - dev_err(&client->dev, "ov5693 power-up err.\n"); - goto fail_power_on; - } - -- if (!dev->vcm) -- dev->vcm = vcm_detect(client); -+ if (!ov5693->vcm) -+ ov5693->vcm = vcm_detect(client); - - /* config & detect sensor */ - ret = ov5693_detect(client); -@@ -1535,7 +1533,7 @@ static int ov5693_s_config(struct v4l2_subdev *sd, int irq) - goto fail_power_on; - } - -- dev->otp_data = ov5693_otp_read(sd); -+ ov5693->otp_data = ov5693_otp_read(sd); - - /* turn off sensor, after probed */ - ret = power_down(sd); -@@ -1543,24 +1541,24 @@ static int ov5693_s_config(struct v4l2_subdev *sd, int irq) - dev_err(&client->dev, "ov5693 power-off err.\n"); - goto fail_power_on; - } -- mutex_unlock(&dev->input_lock); -+ mutex_unlock(&ov5693->lock); - - return ret; - - fail_power_on: - power_down(sd); - dev_err(&client->dev, "sensor power-gating failed\n"); -- mutex_unlock(&dev->input_lock); -+ mutex_unlock(&ov5693->lock); - return ret; - } - - static int ov5693_g_frame_interval(struct v4l2_subdev *sd, - struct v4l2_subdev_frame_interval *interval) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - - interval->interval.numerator = 1; -- interval->interval.denominator = ov5693_res[dev->fmt_idx].fps; -+ interval->interval.denominator = ov5693_res[ov5693->fmt_idx].fps; - - return 0; - } -@@ -1725,7 +1723,7 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - return ret; - - /* Use same lock for controls as for everything else. */ -- ov5693->ctrl_handler.lock = &ov5693->input_lock; -+ ov5693->ctrl_handler.lock = &ov5693->lock; - ov5693->sd.ctrl_handler = &ov5693->ctrl_handler; - - return 0; -@@ -1733,21 +1731,38 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - - static int ov5693_configure_gpios(struct ov5693_device *ov5693) - { -- ov5693->reset = gpiod_get_index(&ov5693->i2c_client->dev, "reset", 0, -+ int ret; -+ -+ ov5693->reset = gpiod_get_optional(&ov5693->client->dev, "reset", - GPIOD_OUT_HIGH); - if (IS_ERR(ov5693->reset)) { -- dev_err(&ov5693->i2c_client->dev, "Couldn't find reset GPIO\n"); -- return -EINVAL; -+ dev_err(&ov5693->client->dev, "Couldn't find reset GPIO\n"); -+ return PTR_ERR(ov5693->reset); -+ } -+ -+ ov5693->powerdown = gpiod_get_optional(&ov5693->client->dev, "powerdown", -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->powerdown)) { -+ dev_err(&ov5693->client->dev, "Couldn't find powerdown GPIO\n"); -+ ret = PTR_ERR(ov5693->powerdown); -+ goto err_put_reset; - } - -- ov5693->indicator_led = gpiod_get_index_optional(&ov5693->i2c_client->dev, "indicator-led", 0, -+ ov5693->indicator_led = gpiod_get_optional(&ov5693->client->dev, "indicator-led", - GPIOD_OUT_HIGH); - if (IS_ERR(ov5693->indicator_led)) { -- dev_err(&ov5693->i2c_client->dev, "Couldn't find indicator-led GPIO\n"); -- return -EINVAL; -+ dev_err(&ov5693->client->dev, "Couldn't find indicator-led GPIO\n"); -+ ret = PTR_ERR(ov5693->indicator_led); -+ goto err_put_powerdown; - } - - return 0; -+err_put_reset: -+ gpiod_put(ov5693->reset); -+err_put_powerdown: -+ gpiod_put(ov5693->powerdown); -+ -+ return ret; - } - - static int ov5693_get_regulators(struct ov5693_device *ov5693) -@@ -1757,7 +1772,7 @@ static int ov5693_get_regulators(struct ov5693_device *ov5693) - for (i = 0; i < OV5693_NUM_SUPPLIES; i++) - ov5693->supplies[i].supply = ov5693_supply_names[i]; - -- return regulator_bulk_get(&ov5693->i2c_client->dev, -+ return regulator_bulk_get(&ov5693->client->dev, - OV5693_NUM_SUPPLIES, - ov5693->supplies); - } -@@ -1773,13 +1788,13 @@ static int ov5693_probe(struct i2c_client *client) - if (!ov5693) - return -ENOMEM; - -- ov5693->i2c_client = client; -+ ov5693->client = client; - - /* check if VCM device exists */ - /* TODO: read from SSDB */ - ov5693->has_vcm = false; - -- mutex_init(&ov5693->input_lock); -+ mutex_init(&ov5693->lock); - - v4l2_i2c_subdev_init(&ov5693->sd, client, &ov5693_ops); - -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 7f1d31a82d3d..70ccb3aae4c7 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -241,14 +241,15 @@ enum vcm_type { - * ov5693 device structure. - */ - struct ov5693_device { -- struct i2c_client *i2c_client; -+ struct i2c_client *client; - struct v4l2_subdev sd; - struct media_pad pad; - struct v4l2_mbus_framefmt format; -- struct mutex input_lock; -+ struct mutex lock; - struct v4l2_ctrl_handler ctrl_handler; - - struct gpio_desc *reset; -+ struct gpio_desc *powerdown; - struct gpio_desc *indicator_led; - struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; - struct clk *clk; --- -2.31.1 - -From 94455706c3081720558c506713f197bfcedf564c Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 28 Jan 2021 12:04:38 +0000 -Subject: [PATCH] media: i2c: Add pm_runtime support to ov5693 driver - -The ov5693 driver currently uses hacky and horrible power up/down methods -called directly in s_stream. Add pm_runtime support and use that in -s_stream instead. Replace all other uses of the power+up/down() calls with -the single ov5693_sensor_stream() for now. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 183 +++++++++++++++++++++++++++++-------- - drivers/media/i2c/ov5693.h | 1 + - 2 files changed, 146 insertions(+), 38 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 0643390c872a..f2eaa5f71a31 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -29,6 +29,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -935,6 +936,10 @@ static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - ov5693->ctrls.exposure->val : exposure_max); - } - -+ /* Only apply changes to the controls if the device is powered up */ -+ if (!pm_runtime_get_if_in_use(&ov5693->client->dev)) -+ return 0; -+ - switch (ctrl->id) { - case V4L2_CID_FOCUS_ABSOLUTE: - dev_dbg(&client->dev, "%s: CID_FOCUS_ABSOLUTE:%d.\n", -@@ -950,27 +955,23 @@ static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - dev_dbg(&client->dev, "%s: CID_EXPOSURE:%d.\n", - __func__, ctrl->val); - ret = ov5693_exposure_configure(ov5693, ctrl->val); -- if (ret) -- return ret; - break; - case V4L2_CID_ANALOGUE_GAIN: - dev_dbg(&client->dev, "%s: CID_ANALOGUE_GAIN:%d.\n", - __func__, ctrl->val); - ret = ov5693_analog_gain_configure(ov5693, ctrl->val); -- if (ret) -- return ret; - break; - case V4L2_CID_DIGITAL_GAIN: - dev_dbg(&client->dev, "%s: CID_DIGITAL_GAIN:%d.\n", - __func__, ctrl->val); - ret = ov5693_gain_configure(ov5693, ctrl->val); -- if (ret) -- return ret; - break; - case V4L2_CID_HFLIP: -- return ov5693_flip_horz_configure(ov5693, !!ctrl->val); -+ ret = ov5693_flip_horz_configure(ov5693, !!ctrl->val); -+ break; - case V4L2_CID_VFLIP: -- return ov5693_flip_vert_configure(ov5693, !!ctrl->val); -+ ret = ov5693_flip_vert_configure(ov5693, !!ctrl->val); -+ break; - case V4L2_CID_VBLANK: - ret = ov5693_write_reg(client, OV5693_16BIT, OV5693_TIMING_VTS_H, - ov5693->mode->height + ctrl->val); -@@ -978,6 +979,9 @@ static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - default: - ret = -EINVAL; - } -+ -+ pm_runtime_put(&ov5693->client->dev); -+ - return ret; - } - -@@ -1093,6 +1097,106 @@ static int ov5693_init(struct v4l2_subdev *sd) - return 0; - } - -+static int ov5693_sw_standby(struct ov5693_device *ov5693, bool standby) -+{ -+ return ov5693_write_reg(ov5693->client, OV5693_8BIT, OV5693_SW_STREAM, -+ standby ? OV5693_STOP_STREAMING : OV5693_START_STREAMING); -+} -+ -+static void ov5693_sensor_powerdown(struct ov5693_device *ov5693) -+{ -+ gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); -+ -+ regulator_bulk_disable(OV5693_NUM_SUPPLIES, ov5693->supplies); -+ -+ clk_disable_unprepare(ov5693->clk); -+ gpiod_set_value_cansleep(ov5693->indicator_led, 0); -+} -+ -+ -+static int ov5693_sensor_powerup(struct ov5693_device *ov5693) -+{ -+ int ret = 0; -+ -+ gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); -+ -+ ret = clk_prepare_enable(ov5693->clk); -+ if (ret) { -+ dev_err(&ov5693->client->dev, "Failed to enable clk\n"); -+ goto fail_power; -+ } -+ -+ ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, ov5693->supplies); -+ if (ret) { -+ dev_err(&ov5693->client->dev, "Failed to enable regulators\n"); -+ goto fail_power; -+ } -+ -+ gpiod_set_value_cansleep(ov5693->reset, 0); -+ gpiod_set_value_cansleep(ov5693->powerdown, 0); -+ gpiod_set_value_cansleep(ov5693->indicator_led, 1); -+ -+ usleep_range(20000, 25000); -+ -+ return 0; -+ -+fail_power: -+ ov5693_sensor_powerdown(ov5693); -+ return ret; -+} -+ -+static int __maybe_unused ov5693_sensor_suspend(struct device *dev) -+{ -+ struct i2c_client *client = i2c_verify_client(dev); -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ int ret; -+ -+ mutex_lock(&ov5693->lock); -+ -+ if (ov5693->streaming) { -+ ret = ov5693_sw_standby(ov5693, true); -+ if (ret) -+ goto out_unlock; -+ } -+ -+ ov5693_sensor_powerdown(ov5693); -+ -+out_unlock: -+ mutex_unlock(&ov5693->lock); -+ return ret; -+} -+ -+static int __maybe_unused ov5693_sensor_resume(struct device *dev) -+{ -+ struct i2c_client *client = i2c_verify_client(dev); -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ int ret; -+ -+ mutex_lock(&ov5693->lock); -+ -+ ret = ov5693_sensor_powerup(ov5693); -+ if (ret) -+ goto out_unlock; -+ -+ if (ov5693->streaming) { -+ ret = ov5693_sw_standby(ov5693, false); -+ if (ret) -+ goto err_power; -+ } -+ -+ goto out_unlock; -+ -+err_power: -+ ov5693_sensor_powerdown(ov5693); -+out_unlock: -+ mutex_unlock(&ov5693->lock); -+ return ret; -+} -+ - static int __power_up(struct v4l2_subdev *sd) - { - struct i2c_client *client = v4l2_get_subdevdata(sd); -@@ -1134,6 +1238,7 @@ static int power_down(struct v4l2_subdev *sd) - ov5693->focus = OV5693_INVALID_CONFIG; - - gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); - - clk_disable_unprepare(ov5693->clk); - -@@ -1333,7 +1438,7 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - } - - for (cnt = 0; cnt < OV5693_POWER_UP_RETRY_NUM; cnt++) { -- ret = power_up(sd); -+ ret = ov5693_sensor_powerup(ov5693); - if (ret) { - dev_err(&client->dev, "power up failed\n"); - continue; -@@ -1475,38 +1580,34 @@ static int ov5693_detect(struct i2c_client *client) - - static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) - { -- struct ov5693_device *dev = to_ov5693_sensor(sd); -- struct i2c_client *client = v4l2_get_subdevdata(sd); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - int ret; - -- mutex_lock(&dev->lock); -- -- /* power_on() here before streaming for regular PCs. */ - if (enable) { -- ret = power_up(sd); -- if (ret) { -- dev_err(&client->dev, "sensor power-up error\n"); -- goto out; -- } -+ ret = pm_runtime_get_sync(&ov5693->client->dev); -+ if (ret < 0) -+ goto err_power_down; - } - -- ret = __v4l2_ctrl_handler_setup(&dev->ctrl_handler); -- if (ret) { -- power_down(sd); -- return ret; -- } -+ ret = __v4l2_ctrl_handler_setup(&ov5693->ctrl_handler); -+ if (ret) -+ goto err_power_down; - -- ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_SW_STREAM, -- enable ? OV5693_START_STREAMING : -- OV5693_STOP_STREAMING); -+ mutex_lock(&ov5693->lock); -+ ret = ov5693_sw_standby(ov5693, !enable); -+ mutex_unlock(&ov5693->lock); -+ -+ if (ret) -+ goto err_power_down; -+ ov5693->streaming = !!enable; - - /* power_off() here after streaming for regular PCs. */ - if (!enable) -- power_down(sd); -- --out: -- mutex_unlock(&dev->lock); -+ pm_runtime_put(&ov5693->client->dev); - -+ return 0; -+err_power_down: -+ pm_runtime_put_noidle(&ov5693->client->dev); - return ret; - } - -@@ -1517,7 +1618,7 @@ static int ov5693_s_config(struct v4l2_subdev *sd, int irq) - int ret = 0; - - mutex_lock(&ov5693->lock); -- ret = power_up(sd); -+ ret = ov5693_sensor_powerup(ov5693); - if (ret) { - dev_err(&client->dev, "ov5693 power-up err.\n"); - goto fail_power_on; -@@ -1536,17 +1637,14 @@ static int ov5693_s_config(struct v4l2_subdev *sd, int irq) - ov5693->otp_data = ov5693_otp_read(sd); - - /* turn off sensor, after probed */ -- ret = power_down(sd); -- if (ret) { -- dev_err(&client->dev, "ov5693 power-off err.\n"); -- goto fail_power_on; -- } -+ ov5693_sensor_powerdown(ov5693); -+ - mutex_unlock(&ov5693->lock); - - return ret; - - fail_power_on: -- power_down(sd); -+ ov5693_sensor_powerdown(ov5693); - dev_err(&client->dev, "sensor power-gating failed\n"); - mutex_unlock(&ov5693->lock); - return ret; -@@ -1830,6 +1928,9 @@ static int ov5693_probe(struct i2c_client *client) - if (ret) - ov5693_remove(client); - -+ pm_runtime_enable(&client->dev); -+ pm_runtime_set_suspended(&client->dev); -+ - ret = v4l2_async_register_subdev_sensor_common(&ov5693->sd); - if (ret) { - dev_err(&client->dev, "failed to register V4L2 subdev: %d", ret); -@@ -1839,6 +1940,7 @@ static int ov5693_probe(struct i2c_client *client) - return ret; - - media_entity_cleanup: -+ pm_runtime_disable(&client->dev); - media_entity_cleanup(&ov5693->sd.entity); - out_put_reset: - gpiod_put(ov5693->reset); -@@ -1848,6 +1950,10 @@ static int ov5693_probe(struct i2c_client *client) - return ret; - } - -+static const struct dev_pm_ops ov5693_pm_ops = { -+ SET_RUNTIME_PM_OPS(ov5693_sensor_suspend, ov5693_sensor_resume, NULL) -+}; -+ - static const struct acpi_device_id ov5693_acpi_match[] = { - {"INT33BE"}, - {}, -@@ -1858,6 +1964,7 @@ static struct i2c_driver ov5693_driver = { - .driver = { - .name = "ov5693", - .acpi_match_table = ov5693_acpi_match, -+ .pm = &ov5693_pm_ops, - }, - .probe_new = ov5693_probe, - .remove = ov5693_remove, -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 70ccb3aae4c7..b78d3b474a43 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -256,6 +256,7 @@ struct ov5693_device { - - /* Current mode */ - const struct ov5693_resolution *mode; -+ bool streaming; - - struct camera_sensor_platform_data *platform_data; - ktime_t timestamp_t_focus_abs; --- -2.31.1 - -From 1051ec31e21f7f0750fee0462b11e449d940a2ec Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 28 Jan 2021 12:07:36 +0000 -Subject: [PATCH] media: i2c: Remove old power methods from ov5693 - -Now that we have replaced the power_up/down() methods with a unified -function and pm_runtime support, we can remove these old methods from the -driver entirely along with some macros and a header. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 114 ------------------------------------- - 1 file changed, 114 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index f2eaa5f71a31..ce26ce86fbd5 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -27,7 +27,6 @@ - #include - #include - #include --#include - #include - #include - #include -@@ -40,27 +39,6 @@ - #include "ov5693.h" - #include "ad5823.h" - --#define __cci_delay(t) \ -- do { \ -- if ((t) < 10) { \ -- usleep_range((t) * 1000, ((t) + 1) * 1000); \ -- } else { \ -- msleep((t)); \ -- } \ -- } while (0) -- --/* Value 30ms reached through experimentation on byt ecs. -- * The DS specifies a much lower value but when using a smaller value -- * the I2C bus sometimes locks up permanently when starting the camera. -- * This issue could not be reproduced on cht, so we can reduce the -- * delay value to a lower value when insmod. -- */ --static uint up_delay = 30; --module_param(up_delay, uint, 0644); --MODULE_PARM_DESC(up_delay, -- "Delay prior to the first CCI transaction for ov5693"); -- -- - /* Exposure/gain */ - - #define OV5693_EXPOSURE_CTRL_HH_REG 0x3500 -@@ -1197,93 +1175,6 @@ static int __maybe_unused ov5693_sensor_resume(struct device *dev) - return ret; - } - --static int __power_up(struct v4l2_subdev *sd) --{ -- struct i2c_client *client = v4l2_get_subdevdata(sd); -- struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- int ret; -- -- ret = clk_prepare_enable(ov5693->clk); -- if (ret) { -- dev_err(&client->dev, "Error enabling clock\n"); -- return -EINVAL; -- } -- -- if (ov5693->indicator_led) -- gpiod_set_value_cansleep(ov5693->indicator_led, 1); -- -- ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, -- ov5693->supplies); -- if (ret) -- goto fail_power; -- -- gpiod_set_value_cansleep(ov5693->reset, 0); -- -- __cci_delay(up_delay); -- -- return 0; -- --fail_power: -- if (ov5693->indicator_led) -- gpiod_set_value_cansleep(ov5693->indicator_led, 0); -- dev_err(&client->dev, "sensor power-up failed\n"); -- -- return ret; --} -- --static int power_down(struct v4l2_subdev *sd) --{ -- struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- -- ov5693->focus = OV5693_INVALID_CONFIG; -- -- gpiod_set_value_cansleep(ov5693->reset, 1); -- gpiod_set_value_cansleep(ov5693->powerdown, 1); -- -- clk_disable_unprepare(ov5693->clk); -- -- if (ov5693->indicator_led) -- gpiod_set_value_cansleep(ov5693->indicator_led, 0); -- return regulator_bulk_disable(OV5693_NUM_SUPPLIES, ov5693->supplies); --} -- --static int power_up(struct v4l2_subdev *sd) --{ -- static const int retry_count = 4; -- int i, ret; -- -- for (i = 0; i < retry_count; i++) { -- ret = __power_up(sd); -- if (!ret) -- return 0; -- -- power_down(sd); -- } -- return ret; --} -- --static int ov5693_s_power(struct v4l2_subdev *sd, int on) --{ -- struct i2c_client *client = v4l2_get_subdevdata(sd); -- int ret; -- -- dev_info(&client->dev, "%s: on %d\n", __func__, on); -- -- if (on == 0) -- return power_down(sd); -- -- /* on == 1 */ -- ret = power_up(sd); -- if (!ret) { -- ret = ov5693_init(sd); -- /* restore settings */ -- ov5693_res = ov5693_res_video; -- N_RES = N_RES_VIDEO; -- } -- -- return ret; --} -- - /* - * distance - calculate the distance - * @res: resolution -@@ -1694,10 +1585,6 @@ static const struct v4l2_subdev_video_ops ov5693_video_ops = { - .g_frame_interval = ov5693_g_frame_interval, - }; - --static const struct v4l2_subdev_core_ops ov5693_core_ops = { -- .s_power = ov5693_s_power, --}; -- - static const struct v4l2_subdev_pad_ops ov5693_pad_ops = { - .enum_mbus_code = ov5693_enum_mbus_code, - .enum_frame_size = ov5693_enum_frame_size, -@@ -1707,7 +1594,6 @@ static const struct v4l2_subdev_pad_ops ov5693_pad_ops = { - }; - - static const struct v4l2_subdev_ops ov5693_ops = { -- .core = &ov5693_core_ops, - .video = &ov5693_video_ops, - .pad = &ov5693_pad_ops, - }; --- -2.31.1 - -From 51ed4c0166e8a5775506db1f50420516e31f7e0f Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 28 Jan 2021 12:14:00 +0000 -Subject: [PATCH] media: i2c: Trim unused headers from ov5693 - -The ov5693 driver includes a ton of unecessary headers, -trim the list down. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 8 -------- - 1 file changed, 8 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index ce26ce86fbd5..b3b391a49fdb 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -20,19 +20,11 @@ - #include - #include - #include --#include - #include --#include --#include --#include --#include - #include --#include - #include - #include - #include --#include --#include - #include - #include - --- -2.31.1 - -From 271f6c470f8922faf28575b68345a99edb430799 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 13 Feb 2021 21:39:35 +0000 -Subject: [PATCH] media: i2c: Remove VCM stuff - -This all needs binning, since we have no idea if it's right. It needs to -be moved to a driver for the VCM device I guess. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 325 +------------------------------------ - 1 file changed, 1 insertion(+), 324 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index b3b391a49fdb..2c82b6578de9 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -76,72 +76,6 @@ - #define OV5693_PIXEL_ARRAY_WIDTH 2592U - #define OV5693_PIXEL_ARRAY_HEIGHT 1944U - --static int vcm_ad_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) --{ -- int err; -- struct i2c_msg msg; -- u8 buf[2]; -- -- buf[0] = reg; -- buf[1] = val; -- -- msg.addr = VCM_ADDR; -- msg.flags = 0; -- msg.len = 2; -- msg.buf = &buf[0]; -- -- err = i2c_transfer(client->adapter, &msg, 1); -- if (err != 1) { -- dev_err(&client->dev, "%s: vcm i2c fail, err code = %d\n", -- __func__, err); -- return -EIO; -- } -- return 0; --} -- --static int ad5823_i2c_write(struct i2c_client *client, u8 reg, u8 val) --{ -- struct i2c_msg msg; -- u8 buf[2]; -- -- buf[0] = reg; -- buf[1] = val; -- msg.addr = AD5823_VCM_ADDR; -- msg.flags = 0; -- msg.len = 0x02; -- msg.buf = &buf[0]; -- -- if (i2c_transfer(client->adapter, &msg, 1) != 1) -- return -EIO; -- return 0; --} -- --static int ad5823_i2c_read(struct i2c_client *client, u8 reg, u8 *val) --{ -- struct i2c_msg msg[2]; -- u8 buf[2]; -- -- buf[0] = reg; -- buf[1] = 0; -- -- msg[0].addr = AD5823_VCM_ADDR; -- msg[0].flags = 0; -- msg[0].len = 0x01; -- msg[0].buf = &buf[0]; -- -- msg[1].addr = 0x0c; -- msg[1].flags = I2C_M_RD; -- msg[1].len = 0x01; -- msg[1].buf = &buf[1]; -- *val = 0; -- if (i2c_transfer(client->adapter, msg, 2) != 2) -- return -EIO; -- *val = buf[1]; -- return 0; --} -- --static const u32 ov5693_embedded_effective_size = 28; -- - /* i2c read/write stuff */ - static int ov5693_read_reg(struct i2c_client *client, - u16 data_length, u16 reg, u16 *val) -@@ -215,69 +149,6 @@ static int ov5693_i2c_write(struct i2c_client *client, u16 len, u8 *data) - return ret == num_msg ? 0 : -EIO; - } - --static int vcm_dw_i2c_write(struct i2c_client *client, u16 data) --{ -- struct i2c_msg msg; -- const int num_msg = 1; -- int ret; -- __be16 val; -- -- val = cpu_to_be16(data); -- msg.addr = VCM_ADDR; -- msg.flags = 0; -- msg.len = OV5693_16BIT; -- msg.buf = (void *)&val; -- -- ret = i2c_transfer(client->adapter, &msg, 1); -- -- return ret == num_msg ? 0 : -EIO; --} -- --/* -- * Theory: per datasheet, the two VCMs both allow for a 2-byte read. -- * The DW9714 doesn't actually specify what this does (it has a -- * two-byte write-only protocol, but specifies the read sequence as -- * legal), but it returns the same data (zeroes) always, after an -- * undocumented initial NAK. The AD5823 has a one-byte address -- * register to which all writes go, and subsequent reads will cycle -- * through the 8 bytes of registers. Notably, the default values (the -- * device is always power-cycled affirmatively, so we can rely on -- * these) in AD5823 are not pairwise repetitions of the same 16 bit -- * word. So all we have to do is sequentially read two bytes at a -- * time and see if we detect a difference in any of the first four -- * pairs. -- */ --static int vcm_detect(struct i2c_client *client) --{ -- int i, ret; -- struct i2c_msg msg; -- u16 data0 = 0, data; -- -- for (i = 0; i < 4; i++) { -- msg.addr = VCM_ADDR; -- msg.flags = I2C_M_RD; -- msg.len = sizeof(data); -- msg.buf = (u8 *)&data; -- ret = i2c_transfer(client->adapter, &msg, 1); -- -- /* -- * DW9714 always fails the first read and returns -- * zeroes for subsequent ones -- */ -- if (i == 0 && ret == -EREMOTEIO) { -- data0 = 0; -- continue; -- } -- -- if (i == 0) -- data0 = data; -- -- if (data != data0) -- return VCM_AD5823; -- } -- return ret == 1 ? VCM_DW9714 : ret; --} -- - static int ov5693_write_reg(struct i2c_client *client, u16 data_length, - u16 reg, u16 val) - { -@@ -654,89 +525,6 @@ static int ov5693_q_exposure(struct v4l2_subdev *sd, s32 *value) - return ret; - } - --static int ad5823_t_focus_vcm(struct v4l2_subdev *sd, u16 val) --{ -- struct i2c_client *client = v4l2_get_subdevdata(sd); -- int ret = -EINVAL; -- u8 vcm_code; -- -- ret = ad5823_i2c_read(client, AD5823_REG_VCM_CODE_MSB, &vcm_code); -- if (ret) -- return ret; -- -- /* set reg VCM_CODE_MSB Bit[1:0] */ -- vcm_code = (vcm_code & VCM_CODE_MSB_MASK) | -- ((val >> 8) & ~VCM_CODE_MSB_MASK); -- ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_MSB, vcm_code); -- if (ret) -- return ret; -- -- /* set reg VCM_CODE_LSB Bit[7:0] */ -- ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_LSB, (val & 0xff)); -- if (ret) -- return ret; -- -- /* set required vcm move time */ -- vcm_code = AD5823_RESONANCE_PERIOD / AD5823_RESONANCE_COEF -- - AD5823_HIGH_FREQ_RANGE; -- ret = ad5823_i2c_write(client, AD5823_REG_VCM_MOVE_TIME, vcm_code); -- -- return ret; --} -- --static int ad5823_t_focus_abs(struct v4l2_subdev *sd, s32 value) --{ -- value = min(value, AD5823_MAX_FOCUS_POS); -- return ad5823_t_focus_vcm(sd, value); --} -- --static int ov5693_t_focus_abs(struct v4l2_subdev *sd, s32 value) --{ -- struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- struct i2c_client *client = v4l2_get_subdevdata(sd); -- int ret = 0; -- -- dev_dbg(&client->dev, "%s: FOCUS_POS: 0x%x\n", __func__, value); -- value = clamp(value, 0, OV5693_VCM_MAX_FOCUS_POS); -- if (ov5693->vcm == VCM_DW9714) { -- if (ov5693->vcm_update) { -- ret = vcm_dw_i2c_write(client, VCM_PROTECTION_OFF); -- if (ret) -- return ret; -- ret = vcm_dw_i2c_write(client, DIRECT_VCM); -- if (ret) -- return ret; -- ret = vcm_dw_i2c_write(client, VCM_PROTECTION_ON); -- if (ret) -- return ret; -- ov5693->vcm_update = false; -- } -- ret = vcm_dw_i2c_write(client, -- vcm_val(value, VCM_DEFAULT_S)); -- } else if (ov5693->vcm == VCM_AD5823) { -- ad5823_t_focus_abs(sd, value); -- } -- if (ret == 0) { -- ov5693->number_of_steps = value - ov5693->focus; -- ov5693->focus = value; -- ov5693->timestamp_t_focus_abs = ktime_get(); -- } else -- dev_err(&client->dev, -- "%s: i2c failed. ret %d\n", __func__, ret); -- -- return ret; --} -- --static int ov5693_t_focus_rel(struct v4l2_subdev *sd, s32 value) --{ -- struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- -- return ov5693_t_focus_abs(sd, ov5693->focus + value); --} -- --#define DELAY_PER_STEP_NS 1000000 --#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) -- - /* Exposure */ - - static int ov5693_get_exposure(struct ov5693_device *ov5693) -@@ -911,16 +699,6 @@ static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) - return 0; - - switch (ctrl->id) { -- case V4L2_CID_FOCUS_ABSOLUTE: -- dev_dbg(&client->dev, "%s: CID_FOCUS_ABSOLUTE:%d.\n", -- __func__, ctrl->val); -- ret = ov5693_t_focus_abs(&ov5693->sd, ctrl->val); -- break; -- case V4L2_CID_FOCUS_RELATIVE: -- dev_dbg(&client->dev, "%s: CID_FOCUS_RELATIVE:%d.\n", -- __func__, ctrl->val); -- ret = ov5693_t_focus_rel(&ov5693->sd, ctrl->val); -- break; - case V4L2_CID_EXPOSURE: - dev_dbg(&client->dev, "%s: CID_EXPOSURE:%d.\n", - __func__, ctrl->val); -@@ -983,90 +761,6 @@ static const struct v4l2_ctrl_ops ov5693_ctrl_ops = { - .g_volatile_ctrl = ov5693_g_volatile_ctrl - }; - --static const struct v4l2_ctrl_config ov5693_controls[] = { -- { -- .ops = &ov5693_ctrl_ops, -- .id = V4L2_CID_FOCUS_ABSOLUTE, -- .type = V4L2_CTRL_TYPE_INTEGER, -- .name = "focus move absolute", -- .min = 0, -- .max = OV5693_VCM_MAX_FOCUS_POS, -- .step = 1, -- .def = 0, -- .flags = 0, -- }, -- { -- .ops = &ov5693_ctrl_ops, -- .id = V4L2_CID_FOCUS_RELATIVE, -- .type = V4L2_CTRL_TYPE_INTEGER, -- .name = "focus move relative", -- .min = OV5693_VCM_MAX_FOCUS_NEG, -- .max = OV5693_VCM_MAX_FOCUS_POS, -- .step = 1, -- .def = 0, -- .flags = 0, -- }, --}; -- --static int ov5693_isp_configure(struct ov5693_device *ov5693) --{ -- int ret; -- -- /* Enable lens correction. */ -- ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, -- OV5693_ISP_CTRL0_REG, 0x86); -- if (ret) -- return ret; -- -- return 0; --} -- --static int ov5693_init(struct v4l2_subdev *sd) --{ -- struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- struct i2c_client *client = v4l2_get_subdevdata(sd); -- int ret; -- -- if (!ov5693->has_vcm) -- return 0; -- -- dev_info(&client->dev, "%s\n", __func__); -- mutex_lock(&ov5693->lock); -- ov5693->vcm_update = false; -- -- if (ov5693->vcm == VCM_AD5823) { -- ret = vcm_ad_i2c_wr8(client, 0x01, 0x01); /* vcm init test */ -- if (ret) -- dev_err(&client->dev, -- "vcm reset failed\n"); -- /*change the mode*/ -- ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_MSB, -- AD5823_RING_CTRL_ENABLE); -- if (ret) -- dev_err(&client->dev, -- "vcm enable ringing failed\n"); -- ret = ad5823_i2c_write(client, AD5823_REG_MODE, -- AD5823_ARC_RES1); -- if (ret) -- dev_err(&client->dev, -- "vcm change mode failed\n"); -- } -- -- /*change initial focus value for ad5823*/ -- if (ov5693->vcm == VCM_AD5823) { -- ov5693->focus = AD5823_INIT_FOCUS_POS; -- ov5693_t_focus_abs(sd, AD5823_INIT_FOCUS_POS); -- } else { -- ov5693->focus = 0; -- ov5693_t_focus_abs(sd, 0); -- } -- -- ov5693_isp_configure(ov5693); -- mutex_unlock(&ov5693->lock); -- -- return 0; --} -- - static int ov5693_sw_standby(struct ov5693_device *ov5693, bool standby) - { - return ov5693_write_reg(ov5693->client, OV5693_8BIT, OV5693_SW_STREAM, -@@ -1327,9 +1021,6 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - continue; - } - -- mutex_unlock(&ov5693->lock); -- ov5693_init(sd); -- mutex_lock(&ov5693->lock); - ret = startup(sd); - if (ret) - dev_err(&client->dev, " startup() FAILED!\n"); -@@ -1507,9 +1198,6 @@ static int ov5693_s_config(struct v4l2_subdev *sd, int irq) - goto fail_power_on; - } - -- if (!ov5693->vcm) -- ov5693->vcm = vcm_detect(client); -- - /* config & detect sensor */ - ret = ov5693_detect(client); - if (ret) { -@@ -1617,24 +1305,17 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - struct i2c_client *client = v4l2_get_subdevdata(&ov5693->sd); - const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; - struct v4l2_fwnode_device_properties props; -- unsigned int i; - int ret; - int hblank; - int vblank_max, vblank_min, vblank_def; - int exposure_max; - -- ret = v4l2_ctrl_handler_init(&ov5693->ctrl_handler, -- ARRAY_SIZE(ov5693_controls)); -+ ret = v4l2_ctrl_handler_init(&ov5693->ctrl_handler, 8); - if (ret) { - ov5693_remove(client); - return ret; - } - -- for (i = 0; i < ARRAY_SIZE(ov5693_controls); i++) -- v4l2_ctrl_new_custom(&ov5693->ctrl_handler, -- &ov5693_controls[i], -- NULL); -- - /* link freq */ - ov5693->ctrls.link_freq = v4l2_ctrl_new_int_menu(&ov5693->ctrl_handler, - NULL, V4L2_CID_LINK_FREQ, -@@ -1766,10 +1447,6 @@ static int ov5693_probe(struct i2c_client *client) - - ov5693->client = client; - -- /* check if VCM device exists */ -- /* TODO: read from SSDB */ -- ov5693->has_vcm = false; -- - mutex_init(&ov5693->lock); - - v4l2_i2c_subdev_init(&ov5693->sd, client, &ov5693_ops); --- -2.31.1 - -From 5208a3e8ef37658d2ddcfd687742eb2591a19530 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 13 Feb 2021 22:16:08 +0000 -Subject: [PATCH] media: i2c: Tidy up ov5693 sensor init - -The initialisation of a mode when the sensor is activated is a bit messy, -so lets tidy that up a bit to bring it in line with other drivers. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 100 ++++++++++++++++--------------------- - 1 file changed, 42 insertions(+), 58 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 2c82b6578de9..313bc9177328 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -767,6 +767,42 @@ static int ov5693_sw_standby(struct ov5693_device *ov5693, bool standby) - standby ? OV5693_STOP_STREAMING : OV5693_START_STREAMING); - } - -+static int ov5693_sw_reset(struct ov5693_device *ov5693) -+{ -+ return ov5693_write_reg(ov5693->client, OV5693_8BIT, OV5693_SW_RESET, -+ 0x01); -+} -+ -+static int ov5693_sensor_init(struct ov5693_device *ov5693) -+{ -+ struct i2c_client *client = ov5693->client; -+ int ret = 0; -+ -+ ret = ov5693_sw_reset(ov5693); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 reset err.\n"); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg_array(client, ov5693_global_setting); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 write register err.\n"); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg_array(client, ov5693_res[ov5693->fmt_idx].regs); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 write register err.\n"); -+ return ret; -+ } -+ -+ ret = ov5693_sw_standby(ov5693, true); -+ if (ret) -+ dev_err(&client->dev, "ov5693 stream off error\n"); -+ -+ return ret; -+} -+ - static void ov5693_sensor_powerdown(struct ov5693_device *ov5693) - { - gpiod_set_value_cansleep(ov5693->reset, 1); -@@ -846,6 +882,12 @@ static int __maybe_unused ov5693_sensor_resume(struct device *dev) - if (ret) - goto out_unlock; - -+ ret = ov5693_sensor_init(ov5693); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 sensor init failure\n"); -+ goto err_power; -+ } -+ - if (ov5693->streaming) { - ret = ov5693_sw_standby(ov5693, false); - if (ret) -@@ -944,35 +986,6 @@ static int get_resolution_index(int w, int h) - return -1; - } - --/* TODO: remove it. */ --static int startup(struct v4l2_subdev *sd) --{ -- struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- struct i2c_client *client = v4l2_get_subdevdata(sd); -- int ret = 0; -- -- ret = ov5693_write_reg(client, OV5693_8BIT, -- OV5693_SW_RESET, 0x01); -- if (ret) { -- dev_err(&client->dev, "ov5693 reset err.\n"); -- return ret; -- } -- -- ret = ov5693_write_reg_array(client, ov5693_global_setting); -- if (ret) { -- dev_err(&client->dev, "ov5693 write register err.\n"); -- return ret; -- } -- -- ret = ov5693_write_reg_array(client, ov5693_res[ov5693->fmt_idx].regs); -- if (ret) { -- dev_err(&client->dev, "ov5693 write register err.\n"); -- return ret; -- } -- -- return ret; --} -- - static int ov5693_set_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *format) -@@ -982,7 +995,6 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - struct i2c_client *client = v4l2_get_subdevdata(sd); - int ret = 0; - int idx; -- int cnt; - - if (format->pad) - return -EINVAL; -@@ -1014,34 +1026,6 @@ static int ov5693_set_fmt(struct v4l2_subdev *sd, - goto mutex_unlock; - } - -- for (cnt = 0; cnt < OV5693_POWER_UP_RETRY_NUM; cnt++) { -- ret = ov5693_sensor_powerup(ov5693); -- if (ret) { -- dev_err(&client->dev, "power up failed\n"); -- continue; -- } -- -- ret = startup(sd); -- if (ret) -- dev_err(&client->dev, " startup() FAILED!\n"); -- else -- break; -- } -- if (cnt == OV5693_POWER_UP_RETRY_NUM) { -- dev_err(&client->dev, "power up failed, gave up\n"); -- goto mutex_unlock; -- } -- -- /* -- * After sensor settings are set to HW, sometimes stream is started. -- * This would cause ISP timeout because ISP is not ready to receive -- * data yet. So add stop streaming here. -- */ -- ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_SW_STREAM, -- OV5693_STOP_STREAMING); -- if (ret) -- dev_warn(&client->dev, "ov5693 stream off err\n"); -- - mutex_unlock: - mutex_unlock(&ov5693->lock); - return ret; --- -2.31.1 - -From 7e5080f1e226fb30cf78237a77c3d715d4eef081 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 12 Feb 2021 16:14:04 +0000 -Subject: [PATCH] media: i2c: cleanup macros in ov5693.h - -Lots of orphaned or duplicated macros in this header file. Clean -those up a bit so it's less ugly. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.h | 89 +------------------------------------- - 1 file changed, 2 insertions(+), 87 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index b78d3b474a43..6502777eb5f3 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -37,68 +37,23 @@ - */ - #define ENABLE_NON_PREVIEW 1 - --#define OV5693_POWER_UP_RETRY_NUM 5 -- - /* Defines for register writes and register array processing */ --#define I2C_MSG_LENGTH 0x2 --#define I2C_RETRY_COUNT 5 -- --#define OV5693_FOCAL_LENGTH_NUM 334 /*3.34mm*/ --#define OV5693_FOCAL_LENGTH_DEM 100 --#define OV5693_F_NUMBER_DEFAULT_NUM 24 --#define OV5693_F_NUMBER_DEM 10 -+#define I2C_MSG_LENGTH 0x2 - - #define MAX_FMTS 1 - --/* sensor_mode_data read_mode adaptation */ --#define OV5693_READ_MODE_BINNING_ON 0x0400 --#define OV5693_READ_MODE_BINNING_OFF 0x00 --#define OV5693_INTEGRATION_TIME_MARGIN 8 -- --#define OV5693_MAX_EXPOSURE_VALUE 0xFFF1 --#define OV5693_MAX_GAIN_VALUE 0xFF -- --/* -- * focal length bits definition: -- * bits 31-16: numerator, bits 15-0: denominator -- */ --#define OV5693_FOCAL_LENGTH_DEFAULT 0x1B70064 -- --/* -- * current f-number bits definition: -- * bits 31-16: numerator, bits 15-0: denominator -- */ --#define OV5693_F_NUMBER_DEFAULT 0x18000a -- --/* -- * f-number range bits definition: -- * bits 31-24: max f-number numerator -- * bits 23-16: max f-number denominator -- * bits 15-8: min f-number numerator -- * bits 7-0: min f-number denominator -- */ --#define OV5693_F_NUMBER_RANGE 0x180a180a - #define OV5693_ID 0x5690 - --#define OV5693_FINE_INTG_TIME_MIN 0 --#define OV5693_FINE_INTG_TIME_MAX_MARGIN 0 --#define OV5693_COARSE_INTG_TIME_MIN 1 --#define OV5693_COARSE_INTG_TIME_MAX_MARGIN 6 -- --#define OV5693_BIN_FACTOR_MAX 4 - /* - * OV5693 System control registers - */ --#define OV5693_SW_SLEEP 0x0100 - #define OV5693_SW_RESET 0x0103 - #define OV5693_SW_STREAM 0x0100 - - #define OV5693_SC_CMMN_CHIP_ID_H 0x300A - #define OV5693_SC_CMMN_CHIP_ID_L 0x300B --#define OV5693_SC_CMMN_SCCB_ID 0x300C - #define OV5693_SC_CMMN_SUB_ID 0x302A /* process, version*/ --/*Bit[7:4] Group control, Bit[3:0] Group ID*/ --#define OV5693_GROUP_ACCESS 0x3208 -+ - /* - *Bit[3:0] Bit[19:16] of exposure, - *remaining 16 bits lies in Reg0x3501&Reg0x3502 -@@ -110,18 +65,6 @@ - #define OV5693_AGC_H 0x350A - #define OV5693_AGC_L 0x350B /*Bit[7:0] of gain*/ - --#define OV5693_HORIZONTAL_START_H 0x3800 /*Bit[11:8]*/ --#define OV5693_HORIZONTAL_START_L 0x3801 /*Bit[7:0]*/ --#define OV5693_VERTICAL_START_H 0x3802 /*Bit[11:8]*/ --#define OV5693_VERTICAL_START_L 0x3803 /*Bit[7:0]*/ --#define OV5693_HORIZONTAL_END_H 0x3804 /*Bit[11:8]*/ --#define OV5693_HORIZONTAL_END_L 0x3805 /*Bit[7:0]*/ --#define OV5693_VERTICAL_END_H 0x3806 /*Bit[11:8]*/ --#define OV5693_VERTICAL_END_L 0x3807 /*Bit[7:0]*/ --#define OV5693_HORIZONTAL_OUTPUT_SIZE_H 0x3808 /*Bit[3:0]*/ --#define OV5693_HORIZONTAL_OUTPUT_SIZE_L 0x3809 /*Bit[7:0]*/ --#define OV5693_VERTICAL_OUTPUT_SIZE_H 0x380a /*Bit[3:0]*/ --#define OV5693_VERTICAL_OUTPUT_SIZE_L 0x380b /*Bit[7:0]*/ - /*High 8-bit, and low 8-bit HTS address is 0x380d*/ - #define OV5693_TIMING_HTS_H 0x380C - /*High 8-bit, and low 8-bit HTS address is 0x380d*/ -@@ -141,34 +84,6 @@ - #define OV5693_START_STREAMING 0x01 - #define OV5693_STOP_STREAMING 0x00 - --#define VCM_ADDR 0x0c --#define VCM_CODE_MSB 0x04 -- --#define OV5693_INVALID_CONFIG 0xffffffff -- --#define OV5693_VCM_SLEW_STEP 0x30F0 --#define OV5693_VCM_SLEW_STEP_MAX 0x7 --#define OV5693_VCM_SLEW_STEP_MASK 0x7 --#define OV5693_VCM_CODE 0x30F2 --#define OV5693_VCM_SLEW_TIME 0x30F4 --#define OV5693_VCM_SLEW_TIME_MAX 0xffff --#define OV5693_VCM_ENABLE 0x8000 -- --#define OV5693_VCM_MAX_FOCUS_NEG -1023 --#define OV5693_VCM_MAX_FOCUS_POS 1023 -- --#define DLC_ENABLE 1 --#define DLC_DISABLE 0 --#define VCM_PROTECTION_OFF 0xeca3 --#define VCM_PROTECTION_ON 0xdc51 --#define VCM_DEFAULT_S 0x0 --#define vcm_step_s(a) (u8)(a & 0xf) --#define vcm_step_mclk(a) (u8)((a >> 4) & 0x3) --#define vcm_dlc_mclk(dlc, mclk) (u16)((dlc << 3) | mclk | 0xa104) --#define vcm_tsrc(tsrc) (u16)(tsrc << 3 | 0xf200) --#define vcm_val(data, s) (u16)(data << 4 | s) --#define DIRECT_VCM vcm_dlc_mclk(0, 0) -- - /* Defines for OTP Data Registers */ - #define OV5693_FRAME_OFF_NUM 0x4202 - #define OV5693_OTP_BYTE_MAX 32 //change to 32 as needed by otpdata --- -2.31.1 - -From 2e668cb83a45e959ca777e71f18b1f7eb3bdf804 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 12 Feb 2021 16:19:09 +0000 -Subject: [PATCH] media: i2c: use devm_kzalloc() to initialise ov5693 - -There's a memory leak in probe because we're not using devres; swtich -so that we are. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 313bc9177328..d092ed698eb3 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1425,7 +1425,7 @@ static int ov5693_probe(struct i2c_client *client) - - dev_info(&client->dev, "%s() called", __func__); - -- ov5693 = kzalloc(sizeof(*ov5693), GFP_KERNEL); -+ ov5693 = devm_kzalloc(&client->dev, sizeof(*ov5693), GFP_KERNEL); - if (!ov5693) - return -ENOMEM; - --- -2.31.1 - -From 629fb46807053b881ff5edaf08b97106c07a38ad Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 12 Feb 2021 16:26:21 +0000 -Subject: [PATCH] media: i2c: Check for supported clk rate in probe - -The ov5693 driver is configured to support a 19.2MHz external clock only. -Check that we do indeed have that value and if not, exit with -EINVAL. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 8 ++++++++ - drivers/media/i2c/ov5693.h | 2 ++ - 2 files changed, 10 insertions(+) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index d092ed698eb3..8082d37841da 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1421,6 +1421,7 @@ static int ov5693_get_regulators(struct ov5693_device *ov5693) - static int ov5693_probe(struct i2c_client *client) - { - struct ov5693_device *ov5693; -+ u32 clk_rate; - int ret = 0; - - dev_info(&client->dev, "%s() called", __func__); -@@ -1441,6 +1442,13 @@ static int ov5693_probe(struct i2c_client *client) - return -EINVAL; - } - -+ clk_rate = clk_get_rate(ov5693->clk); -+ if (clk_rate != OV5693_XVCLK_FREQ) { -+ dev_err(&client->dev, "Unsupported clk freq %u, expected %u\n", -+ clk_rate, OV5693_XVCLK_FREQ); -+ return -EINVAL; -+ } -+ - ret = ov5693_configure_gpios(ov5693); - if (ret) - goto out_free; -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 6502777eb5f3..0dfbbe9a0ff2 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -100,6 +100,8 @@ - #define OV5693_OTP_READ_ONETIME 16 - #define OV5693_OTP_MODE_READ 1 - -+#define OV5693_XVCLK_FREQ 19200000 -+ - /* link freq and pixel rate required for IPU3 */ - #define OV5693_LINK_FREQ_400MHZ 400000000 - /* pixel_rate = link_freq * 2 * nr_of_lanes / bits_per_sample --- -2.31.1 - -From e2e5c6fed06e85872f36cd284023b5b3b26a6b04 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 13 Feb 2021 23:17:50 +0000 -Subject: [PATCH] media: i2c: Use devres to fetch gpios - -Use devres; it'll simplify error handling through this function -and probe. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 22 +++++----------------- - 1 file changed, 5 insertions(+), 17 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 8082d37841da..c580159079d2 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1270,8 +1270,6 @@ static int ov5693_remove(struct i2c_client *client) - - dev_info(&client->dev, "%s...\n", __func__); - -- gpiod_put(ov5693->reset); -- gpiod_put(ov5693->indicator_led); - while (i--) - regulator_put(ov5693->supplies[i].consumer); - -@@ -1372,38 +1370,28 @@ static int ov5693_init_controls(struct ov5693_device *ov5693) - - static int ov5693_configure_gpios(struct ov5693_device *ov5693) - { -- int ret; -- -- ov5693->reset = gpiod_get_optional(&ov5693->client->dev, "reset", -+ ov5693->reset = devm_gpiod_get_optional(&ov5693->client->dev, "reset", - GPIOD_OUT_HIGH); - if (IS_ERR(ov5693->reset)) { - dev_err(&ov5693->client->dev, "Couldn't find reset GPIO\n"); - return PTR_ERR(ov5693->reset); - } - -- ov5693->powerdown = gpiod_get_optional(&ov5693->client->dev, "powerdown", -+ ov5693->powerdown = devm_gpiod_get_optional(&ov5693->client->dev, "powerdown", - GPIOD_OUT_HIGH); - if (IS_ERR(ov5693->powerdown)) { - dev_err(&ov5693->client->dev, "Couldn't find powerdown GPIO\n"); -- ret = PTR_ERR(ov5693->powerdown); -- goto err_put_reset; -+ return PTR_ERR(ov5693->powerdown); - } - -- ov5693->indicator_led = gpiod_get_optional(&ov5693->client->dev, "indicator-led", -+ ov5693->indicator_led = devm_gpiod_get_optional(&ov5693->client->dev, "indicator-led", - GPIOD_OUT_HIGH); - if (IS_ERR(ov5693->indicator_led)) { - dev_err(&ov5693->client->dev, "Couldn't find indicator-led GPIO\n"); -- ret = PTR_ERR(ov5693->indicator_led); -- goto err_put_powerdown; -+ return PTR_ERR(ov5693->indicator_led); - } - - return 0; --err_put_reset: -- gpiod_put(ov5693->reset); --err_put_powerdown: -- gpiod_put(ov5693->powerdown); -- -- return ret; - } - - static int ov5693_get_regulators(struct ov5693_device *ov5693) --- -2.31.1 - -From 8d6f23cc4022885c37727c32ab37879f915cfac9 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 13 Feb 2021 23:20:47 +0000 -Subject: [PATCH] media: i2c: Use devres to fetch regulators - -As before, use devres to simplify error handling and driver removal - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 6 +----- - 1 file changed, 1 insertion(+), 5 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index c580159079d2..9f61b470f8ba 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -1266,13 +1266,9 @@ static int ov5693_remove(struct i2c_client *client) - { - struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- unsigned int i = OV5693_NUM_SUPPLIES; - - dev_info(&client->dev, "%s...\n", __func__); - -- while (i--) -- regulator_put(ov5693->supplies[i].consumer); -- - v4l2_async_unregister_subdev(sd); - - media_entity_cleanup(&ov5693->sd.entity); -@@ -1401,7 +1397,7 @@ static int ov5693_get_regulators(struct ov5693_device *ov5693) - for (i = 0; i < OV5693_NUM_SUPPLIES; i++) - ov5693->supplies[i].supply = ov5693_supply_names[i]; - -- return regulator_bulk_get(&ov5693->client->dev, -+ return devm_regulator_bulk_get(&ov5693->client->dev, - OV5693_NUM_SUPPLIES, - ov5693->supplies); - } --- -2.31.1 - -From eed01e22edce2bb5c1a299cb01501ca62fc9dfb9 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 14 Feb 2021 12:39:14 +0000 -Subject: [PATCH] media: i2c: remove debug print - -The exposure configure function has a debug print. It's working fine, -so bin it. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 37 ------------------------------------- - 1 file changed, 37 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 9f61b470f8ba..622a7ddf4063 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -526,41 +526,6 @@ static int ov5693_q_exposure(struct v4l2_subdev *sd, s32 *value) - } - - /* Exposure */ -- --static int ov5693_get_exposure(struct ov5693_device *ov5693) --{ -- u32 exposure = 0; -- u16 tmp; -- int ret = 0; -- -- /* get exposure */ -- ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, -- OV5693_EXPOSURE_L, -- &tmp); -- if (ret) -- return ret; -- -- exposure |= ((tmp >> 4) & 0b1111); -- -- ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, -- OV5693_EXPOSURE_M, -- &tmp); -- if (ret) -- return ret; -- -- exposure |= (tmp << 4); -- ret = ov5693_read_reg(ov5693->client, OV5693_8BIT, -- OV5693_EXPOSURE_H, -- &tmp); -- if (ret) -- return ret; -- -- exposure |= (tmp << 12); -- -- printk("exposure set to: %u\n", exposure); -- return ret; --} -- - static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) - { - int ret; -@@ -571,7 +536,6 @@ static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) - */ - exposure = exposure * 16; - -- ov5693_get_exposure(ov5693); - ret = ov5693_write_reg(ov5693->client, OV5693_8BIT, - OV5693_EXPOSURE_CTRL_HH_REG, OV5693_EXPOSURE_CTRL_HH(exposure)); - if (ret) -@@ -586,7 +550,6 @@ static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) - OV5693_EXPOSURE_CTRL_L_REG, OV5693_EXPOSURE_CTRL_L(exposure)); - if (ret) - return ret; -- ov5693_get_exposure(ov5693); - - return 0; - } --- -2.31.1 - -From d6345bec14d3bdd085e99d8275b33287347bc2b6 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 14 Feb 2021 14:32:50 +0000 -Subject: [PATCH] media: i2c: Remove unused resolutions from ov5693 - -The list of resolutions in here is really unmaintanably long. For now just -bin all of the ones that are not part of ov5693_res_video, which is the -only array of resolutions in use anyway. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.h | 357 +------------------------------------ - 1 file changed, 1 insertion(+), 356 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 0dfbbe9a0ff2..29e6735112da 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -474,34 +474,7 @@ static struct ov5693_reg const ov5693_global_setting[] = { - }; - - #if ENABLE_NON_PREVIEW --/* -- * 654x496 30fps 17ms VBlanking 2lane 10Bit (Scaling) -- */ --static struct ov5693_reg const ov5693_654x496[] = { -- {OV5693_8BIT, 0x3501, 0x3d}, -- {OV5693_8BIT, 0x3502, 0x00}, -- {OV5693_8BIT, 0x3708, 0xe6}, -- {OV5693_8BIT, 0x3709, 0xc7}, -- {OV5693_8BIT, 0x3803, 0x00}, -- {OV5693_8BIT, 0x3806, 0x07}, -- {OV5693_8BIT, 0x3807, 0xa3}, -- {OV5693_8BIT, 0x3808, 0x02}, -- {OV5693_8BIT, 0x3809, 0x90}, -- {OV5693_8BIT, 0x380a, 0x01}, -- {OV5693_8BIT, 0x380b, 0xf0}, -- {OV5693_8BIT, 0x380c, 0x0a}, -- {OV5693_8BIT, 0x380d, 0x80}, -- {OV5693_8BIT, 0x380e, 0x07}, -- {OV5693_8BIT, 0x380f, 0xc0}, -- {OV5693_8BIT, 0x3811, 0x08}, -- {OV5693_8BIT, 0x3813, 0x02}, -- {OV5693_8BIT, 0x3814, 0x31}, -- {OV5693_8BIT, 0x3815, 0x31}, -- {OV5693_8BIT, 0x3820, 0x04}, -- {OV5693_8BIT, 0x3821, 0x1f}, -- {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_TOK_TERM, 0, 0} --}; -+ - - /* - * 1296x976 30fps 17ms VBlanking 2lane 10Bit (Scaling) -@@ -660,62 +633,10 @@ static struct ov5693_reg const ov5693_736x496[] = { - }; - #endif - --/* --static struct ov5693_reg const ov5693_736x496[] = { -- {OV5693_8BIT, 0x3501, 0x7b}, -- {OV5693_8BIT, 0x3502, 0x00}, -- {OV5693_8BIT, 0x3708, 0xe6}, -- {OV5693_8BIT, 0x3709, 0xc3}, -- {OV5693_8BIT, 0x3803, 0x00}, -- {OV5693_8BIT, 0x3806, 0x07}, -- {OV5693_8BIT, 0x3807, 0xa3}, -- {OV5693_8BIT, 0x3808, 0x02}, -- {OV5693_8BIT, 0x3809, 0xe0}, -- {OV5693_8BIT, 0x380a, 0x01}, -- {OV5693_8BIT, 0x380b, 0xf0}, -- {OV5693_8BIT, 0x380c, 0x0d}, -- {OV5693_8BIT, 0x380d, 0xb0}, -- {OV5693_8BIT, 0x380e, 0x05}, -- {OV5693_8BIT, 0x380f, 0xf2}, -- {OV5693_8BIT, 0x3811, 0x08}, -- {OV5693_8BIT, 0x3813, 0x02}, -- {OV5693_8BIT, 0x3814, 0x31}, -- {OV5693_8BIT, 0x3815, 0x31}, -- {OV5693_8BIT, 0x3820, 0x01}, -- {OV5693_8BIT, 0x3821, 0x1f}, -- {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_TOK_TERM, 0, 0} --}; --*/ - /* - * 976x556 30fps 8.8ms VBlanking 2lane 10Bit (Scaling) - */ - #if ENABLE_NON_PREVIEW --static struct ov5693_reg const ov5693_976x556[] = { -- {OV5693_8BIT, 0x3501, 0x7b}, -- {OV5693_8BIT, 0x3502, 0x00}, -- {OV5693_8BIT, 0x3708, 0xe2}, -- {OV5693_8BIT, 0x3709, 0xc3}, -- {OV5693_8BIT, 0x3803, 0xf0}, -- {OV5693_8BIT, 0x3806, 0x06}, -- {OV5693_8BIT, 0x3807, 0xa7}, -- {OV5693_8BIT, 0x3808, 0x03}, -- {OV5693_8BIT, 0x3809, 0xd0}, -- {OV5693_8BIT, 0x380a, 0x02}, -- {OV5693_8BIT, 0x380b, 0x2C}, -- {OV5693_8BIT, 0x380c, 0x0a}, -- {OV5693_8BIT, 0x380d, 0x80}, -- {OV5693_8BIT, 0x380e, 0x07}, -- {OV5693_8BIT, 0x380f, 0xc0}, -- {OV5693_8BIT, 0x3811, 0x10}, -- {OV5693_8BIT, 0x3813, 0x02}, -- {OV5693_8BIT, 0x3814, 0x11}, -- {OV5693_8BIT, 0x3815, 0x11}, -- {OV5693_8BIT, 0x3820, 0x00}, -- {OV5693_8BIT, 0x3821, 0x1e}, -- {OV5693_8BIT, 0x5002, 0x80}, -- {OV5693_TOK_TERM, 0, 0} --}; - - /*DS from 2624x1492*/ - static struct ov5693_reg const ov5693_1296x736[] = { -@@ -782,40 +703,6 @@ static struct ov5693_reg const ov5693_1636p_30fps[] = { - }; - #endif - --static struct ov5693_reg const ov5693_1616x1216_30fps[] = { -- {OV5693_8BIT, 0x3501, 0x7b}, -- {OV5693_8BIT, 0x3502, 0x80}, -- {OV5693_8BIT, 0x3708, 0xe2}, -- {OV5693_8BIT, 0x3709, 0xc3}, -- {OV5693_8BIT, 0x3800, 0x00}, /*{3800,3801} Array X start*/ -- {OV5693_8BIT, 0x3801, 0x08}, /* 04 //{3800,3801} Array X start*/ -- {OV5693_8BIT, 0x3802, 0x00}, /*{3802,3803} Array Y start*/ -- {OV5693_8BIT, 0x3803, 0x04}, /* 00 //{3802,3803} Array Y start*/ -- {OV5693_8BIT, 0x3804, 0x0a}, /*{3804,3805} Array X end*/ -- {OV5693_8BIT, 0x3805, 0x37}, /* 3b //{3804,3805} Array X end*/ -- {OV5693_8BIT, 0x3806, 0x07}, /*{3806,3807} Array Y end*/ -- {OV5693_8BIT, 0x3807, 0x9f}, /* a3 //{3806,3807} Array Y end*/ -- {OV5693_8BIT, 0x3808, 0x06}, /*{3808,3809} Final output H size*/ -- {OV5693_8BIT, 0x3809, 0x50}, /*{3808,3809} Final output H size*/ -- {OV5693_8BIT, 0x380a, 0x04}, /*{380a,380b} Final output V size*/ -- {OV5693_8BIT, 0x380b, 0xc0}, /*{380a,380b} Final output V size*/ -- {OV5693_8BIT, 0x380c, 0x0a}, /*{380c,380d} HTS*/ -- {OV5693_8BIT, 0x380d, 0x80}, /*{380c,380d} HTS*/ -- {OV5693_8BIT, 0x380e, 0x07}, /*{380e,380f} VTS*/ -- {OV5693_8BIT, 0x380f, 0xc0}, /* bc //{380e,380f} VTS*/ -- {OV5693_8BIT, 0x3810, 0x00}, /*{3810,3811} windowing X offset*/ -- {OV5693_8BIT, 0x3811, 0x10}, /*{3810,3811} windowing X offset*/ -- {OV5693_8BIT, 0x3812, 0x00}, /*{3812,3813} windowing Y offset*/ -- {OV5693_8BIT, 0x3813, 0x06}, /*{3812,3813} windowing Y offset*/ -- {OV5693_8BIT, 0x3814, 0x11}, /*X subsample control*/ -- {OV5693_8BIT, 0x3815, 0x11}, /*Y subsample control*/ -- {OV5693_8BIT, 0x3820, 0x00}, /*FLIP/Binnning control*/ -- {OV5693_8BIT, 0x3821, 0x1e}, /*MIRROR control*/ -- {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_8BIT, 0x5041, 0x84}, -- {OV5693_TOK_TERM, 0, 0} --}; -- - /* - * 1940x1096 30fps 8.8ms VBlanking 2lane 10bit (Scaling) - */ -@@ -878,37 +765,6 @@ static struct ov5693_reg const ov5693_2592x1456_30fps[] = { - }; - #endif - --static struct ov5693_reg const ov5693_2576x1456_30fps[] = { -- {OV5693_8BIT, 0x3501, 0x7b}, -- {OV5693_8BIT, 0x3502, 0x00}, -- {OV5693_8BIT, 0x3708, 0xe2}, -- {OV5693_8BIT, 0x3709, 0xc3}, -- {OV5693_8BIT, 0x3800, 0x00}, -- {OV5693_8BIT, 0x3801, 0x00}, -- {OV5693_8BIT, 0x3802, 0x00}, -- {OV5693_8BIT, 0x3803, 0xf0}, -- {OV5693_8BIT, 0x3804, 0x0a}, -- {OV5693_8BIT, 0x3805, 0x3f}, -- {OV5693_8BIT, 0x3806, 0x06}, -- {OV5693_8BIT, 0x3807, 0xa4}, -- {OV5693_8BIT, 0x3808, 0x0a}, -- {OV5693_8BIT, 0x3809, 0x10}, -- {OV5693_8BIT, 0x380a, 0x05}, -- {OV5693_8BIT, 0x380b, 0xb0}, -- {OV5693_8BIT, 0x380c, 0x0a}, -- {OV5693_8BIT, 0x380d, 0x80}, -- {OV5693_8BIT, 0x380e, 0x07}, -- {OV5693_8BIT, 0x380f, 0xc0}, -- {OV5693_8BIT, 0x3811, 0x18}, -- {OV5693_8BIT, 0x3813, 0x00}, -- {OV5693_8BIT, 0x3814, 0x11}, -- {OV5693_8BIT, 0x3815, 0x11}, -- {OV5693_8BIT, 0x3820, 0x00}, -- {OV5693_8BIT, 0x3821, 0x1e}, -- {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_TOK_TERM, 0, 0} --}; -- - /* - * 2592x1944 30fps 0.6ms VBlanking 2lane 10Bit - */ -@@ -940,49 +796,6 @@ static struct ov5693_reg const ov5693_2592x1944_30fps[] = { - }; - #endif - --/* -- * 11:9 Full FOV Output, expected FOV Res: 2346x1920 -- * ISP Effect Res: 1408x1152 -- * Sensor out: 1424x1168, DS From: 2380x1952 -- * -- * WA: Left Offset: 8, Hor scal: 64 -- */ --#if ENABLE_NON_PREVIEW --static struct ov5693_reg const ov5693_1424x1168_30fps[] = { -- {OV5693_8BIT, 0x3501, 0x3b}, /* long exposure[15:8] */ -- {OV5693_8BIT, 0x3502, 0x80}, /* long exposure[7:0] */ -- {OV5693_8BIT, 0x3708, 0xe2}, -- {OV5693_8BIT, 0x3709, 0xc3}, -- {OV5693_8BIT, 0x3800, 0x00}, /* TIMING_X_ADDR_START */ -- {OV5693_8BIT, 0x3801, 0x50}, /* 80 */ -- {OV5693_8BIT, 0x3802, 0x00}, /* TIMING_Y_ADDR_START */ -- {OV5693_8BIT, 0x3803, 0x02}, /* 2 */ -- {OV5693_8BIT, 0x3804, 0x09}, /* TIMING_X_ADDR_END */ -- {OV5693_8BIT, 0x3805, 0xdd}, /* 2525 */ -- {OV5693_8BIT, 0x3806, 0x07}, /* TIMING_Y_ADDR_END */ -- {OV5693_8BIT, 0x3807, 0xa1}, /* 1953 */ -- {OV5693_8BIT, 0x3808, 0x05}, /* TIMING_X_OUTPUT_SIZE */ -- {OV5693_8BIT, 0x3809, 0x90}, /* 1424 */ -- {OV5693_8BIT, 0x380a, 0x04}, /* TIMING_Y_OUTPUT_SIZE */ -- {OV5693_8BIT, 0x380b, 0x90}, /* 1168 */ -- {OV5693_8BIT, 0x380c, 0x0a}, /* TIMING_HTS */ -- {OV5693_8BIT, 0x380d, 0x80}, -- {OV5693_8BIT, 0x380e, 0x07}, /* TIMING_VTS */ -- {OV5693_8BIT, 0x380f, 0xc0}, -- {OV5693_8BIT, 0x3810, 0x00}, /* TIMING_ISP_X_WIN */ -- {OV5693_8BIT, 0x3811, 0x02}, /* 2 */ -- {OV5693_8BIT, 0x3812, 0x00}, /* TIMING_ISP_Y_WIN */ -- {OV5693_8BIT, 0x3813, 0x00}, /* 0 */ -- {OV5693_8BIT, 0x3814, 0x11}, /* TIME_X_INC */ -- {OV5693_8BIT, 0x3815, 0x11}, /* TIME_Y_INC */ -- {OV5693_8BIT, 0x3820, 0x00}, -- {OV5693_8BIT, 0x3821, 0x1e}, -- {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_8BIT, 0x5041, 0x84}, /* scale is auto enabled */ -- {OV5693_TOK_TERM, 0, 0} --}; --#endif -- - /* - * 3:2 Full FOV Output, expected FOV Res: 2560x1706 - * ISP Effect Res: 720x480 -@@ -1022,173 +835,6 @@ static struct ov5693_reg const ov5693_736x496_30fps[] = { - {OV5693_TOK_TERM, 0, 0} - }; - --static struct ov5693_reg const ov5693_2576x1936_30fps[] = { -- {OV5693_8BIT, 0x3501, 0x7b}, -- {OV5693_8BIT, 0x3502, 0x00}, -- {OV5693_8BIT, 0x3708, 0xe2}, -- {OV5693_8BIT, 0x3709, 0xc3}, -- {OV5693_8BIT, 0x3803, 0x00}, -- {OV5693_8BIT, 0x3806, 0x07}, -- {OV5693_8BIT, 0x3807, 0xa3}, -- {OV5693_8BIT, 0x3808, 0x0a}, -- {OV5693_8BIT, 0x3809, 0x10}, -- {OV5693_8BIT, 0x380a, 0x07}, -- {OV5693_8BIT, 0x380b, 0x90}, -- {OV5693_8BIT, 0x380c, 0x0a}, -- {OV5693_8BIT, 0x380d, 0x80}, -- {OV5693_8BIT, 0x380e, 0x07}, -- {OV5693_8BIT, 0x380f, 0xc0}, -- {OV5693_8BIT, 0x3811, 0x18}, -- {OV5693_8BIT, 0x3813, 0x00}, -- {OV5693_8BIT, 0x3814, 0x11}, -- {OV5693_8BIT, 0x3815, 0x11}, -- {OV5693_8BIT, 0x3820, 0x00}, -- {OV5693_8BIT, 0x3821, 0x1e}, -- {OV5693_8BIT, 0x5002, 0x00}, -- {OV5693_TOK_TERM, 0, 0} --}; -- --static struct ov5693_resolution ov5693_res_preview[] = { -- { -- .desc = "ov5693_736x496_30fps", -- .width = 736, -- .height = 496, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_736x496_30fps, -- }, -- { -- .desc = "ov5693_1616x1216_30fps", -- .width = 1616, -- .height = 1216, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_1616x1216_30fps, -- }, -- { -- .desc = "ov5693_5M_30fps", -- .width = 2576, -- .height = 1456, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_2576x1456_30fps, -- }, -- { -- .desc = "ov5693_5M_30fps", -- .width = 2576, -- .height = 1936, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_2576x1936_30fps, -- }, --}; -- --#define N_RES_PREVIEW (ARRAY_SIZE(ov5693_res_preview)) -- --/* -- * Disable non-preview configurations until the configuration selection is -- * improved. -- */ --#if ENABLE_NON_PREVIEW --struct ov5693_resolution ov5693_res_still[] = { -- { -- .desc = "ov5693_736x496_30fps", -- .width = 736, -- .height = 496, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_736x496_30fps, -- }, -- { -- .desc = "ov5693_1424x1168_30fps", -- .width = 1424, -- .height = 1168, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_1424x1168_30fps, -- }, -- { -- .desc = "ov5693_1616x1216_30fps", -- .width = 1616, -- .height = 1216, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_1616x1216_30fps, -- }, -- { -- .desc = "ov5693_5M_30fps", -- .width = 2592, -- .height = 1456, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_2592x1456_30fps, -- }, -- { -- .desc = "ov5693_5M_30fps", -- .width = 2592, -- .height = 1944, -- .pix_clk_freq = 160, -- .fps = 30, -- .used = 0, -- .pixels_per_line = 2688, -- .lines_per_frame = 1984, -- .bin_factor_x = 1, -- .bin_factor_y = 1, -- .bin_mode = 0, -- .regs = ov5693_2592x1944_30fps, -- }, --}; -- --#define N_RES_STILL (ARRAY_SIZE(ov5693_res_still)) -- - struct ov5693_resolution ov5693_res_video[] = { - { - .desc = "ov5693_736x496_30fps", -@@ -1343,4 +989,3 @@ struct ov5693_resolution ov5693_res_video[] = { - - static struct ov5693_resolution *ov5693_res = ov5693_res_video; - static unsigned long N_RES = N_RES_VIDEO; --#endif --- -2.31.1 - -From 5fd88253510a38efbc8d800a857fe93fbc15f94e Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 14 Feb 2021 14:45:58 +0000 -Subject: [PATCH] media: i2c: update set_fmt() for ov5693 - -The set_fmt() function is a bit messy still, using home grown solutions to -find the closest supported resolution instead of the v4l2 helpers. It also -fails to update control ranges to account for the new mode (though this is -moot currently as they're all the same, but the probably shouldn't be). - -Fix it up a little. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 148 ++++++++++--------------------------- - drivers/media/i2c/ov5693.h | 5 +- - 2 files changed, 40 insertions(+), 113 deletions(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 622a7ddf4063..09c84006d5c9 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -753,7 +753,7 @@ static int ov5693_sensor_init(struct ov5693_device *ov5693) - return ret; - } - -- ret = ov5693_write_reg_array(client, ov5693_res[ov5693->fmt_idx].regs); -+ ret = ov5693_write_reg_array(client, ov5693->mode->regs); - if (ret) { - dev_err(&client->dev, "ov5693 write register err.\n"); - return ret; -@@ -866,128 +866,56 @@ static int __maybe_unused ov5693_sensor_resume(struct device *dev) - return ret; - } - --/* -- * distance - calculate the distance -- * @res: resolution -- * @w: width -- * @h: height -- * -- * Get the gap between res_w/res_h and w/h. -- * distance = (res_w/res_h - w/h) / (w/h) * 8192 -- * res->width/height smaller than w/h wouldn't be considered. -- * The gap of ratio larger than 1/8 wouldn't be considered. -- * Returns the value of gap or -1 if fail. -- */ --#define LARGEST_ALLOWED_RATIO_MISMATCH 1024 --static int distance(struct ov5693_resolution *res, u32 w, u32 h) --{ -- int ratio; -- int distance; -- -- if (w == 0 || h == 0 || -- res->width < w || res->height < h) -- return -1; -- -- ratio = res->width << 13; -- ratio /= w; -- ratio *= h; -- ratio /= res->height; -- -- distance = abs(ratio - 8192); -- -- if (distance > LARGEST_ALLOWED_RATIO_MISMATCH) -- return -1; -- -- return distance; --} -- --/* Return the nearest higher resolution index -- * Firstly try to find the approximate aspect ratio resolution -- * If we find multiple same AR resolutions, choose the -- * minimal size. -- */ --static int nearest_resolution_index(int w, int h) --{ -- int i; -- int idx = -1; -- int dist; -- int min_dist = INT_MAX; -- int min_res_w = INT_MAX; -- struct ov5693_resolution *tmp_res = NULL; -- -- for (i = 0; i < N_RES; i++) { -- tmp_res = &ov5693_res[i]; -- dist = distance(tmp_res, w, h); -- if (dist == -1) -- continue; -- if (dist < min_dist) { -- min_dist = dist; -- idx = i; -- min_res_w = ov5693_res[i].width; -- continue; -- } -- if (dist == min_dist && ov5693_res[i].width < min_res_w) -- idx = i; -- } -- -- return idx; --} -- --static int get_resolution_index(int w, int h) --{ -- int i; -- -- for (i = 0; i < N_RES; i++) { -- if (w != ov5693_res[i].width) -- continue; -- if (h != ov5693_res[i].height) -- continue; -- -- return i; -- } -- -- return -1; --} -- - static int ov5693_set_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *format) - { -- struct v4l2_mbus_framefmt *fmt = &format->format; - struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -- struct i2c_client *client = v4l2_get_subdevdata(sd); -+ const struct ov5693_resolution *mode; -+ int exposure_max; - int ret = 0; -- int idx; -+ int hblank; - - if (format->pad) - return -EINVAL; -- if (!fmt) -- return -EINVAL; - - mutex_lock(&ov5693->lock); -- idx = nearest_resolution_index(fmt->width, fmt->height); -- if (idx == -1) { -- /* return the largest resolution */ -- fmt->width = ov5693_res[N_RES - 1].width; -- fmt->height = ov5693_res[N_RES - 1].height; -- } else { -- fmt->width = ov5693_res[idx].width; -- fmt->height = ov5693_res[idx].height; -- } - -- fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ mode = v4l2_find_nearest_size(ov5693_res_video, ARRAY_SIZE(ov5693_res_video), -+ width, height, format->format.width, -+ format->format.height); -+ -+ if (!mode) -+ return -EINVAL; -+ -+ format->format.width = mode->width; -+ format->format.height = mode->height; -+ format->format.code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ - if (format->which == V4L2_SUBDEV_FORMAT_TRY) { -- cfg->try_fmt = *fmt; -- ret = 0; -+ *v4l2_subdev_get_try_format(sd, cfg, format->pad) = format->format; - goto mutex_unlock; - } - -- ov5693->fmt_idx = get_resolution_index(fmt->width, fmt->height); -- if (ov5693->fmt_idx == -1) { -- dev_err(&client->dev, "get resolution fail\n"); -- ret = -EINVAL; -- goto mutex_unlock; -- } -+ ov5693->mode = mode; -+ -+ /* Update limits and set FPS to default */ -+ __v4l2_ctrl_modify_range(ov5693->ctrls.vblank, -+ mode->lines_per_frame - mode->height, -+ OV5693_TIMING_MAX_VTS - mode->height, -+ 1, mode->lines_per_frame - mode->height); -+ __v4l2_ctrl_s_ctrl(ov5693->ctrls.vblank, -+ mode->lines_per_frame - mode->height); -+ -+ hblank = mode->pixels_per_line - mode->width; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.hblank, hblank, hblank, 1, hblank); -+ -+ exposure_max = mode->lines_per_frame - 8; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, -+ ov5693->ctrls.exposure->minimum, exposure_max, -+ ov5693->ctrls.exposure->step, -+ ov5693->ctrls.exposure->val < exposure_max ? -+ ov5693->ctrls.exposure->val : exposure_max); - - mutex_unlock: - mutex_unlock(&ov5693->lock); -@@ -1056,8 +984,8 @@ static int ov5693_get_fmt(struct v4l2_subdev *sd, - if (!fmt) - return -EINVAL; - -- fmt->width = ov5693_res[ov5693->fmt_idx].width; -- fmt->height = ov5693_res[ov5693->fmt_idx].height; -+ fmt->width = ov5693->mode->width; -+ fmt->height = ov5693->mode->height; - fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; - - return 0; -@@ -1174,7 +1102,7 @@ static int ov5693_g_frame_interval(struct v4l2_subdev *sd, - struct ov5693_device *ov5693 = to_ov5693_sensor(sd); - - interval->interval.numerator = 1; -- interval->interval.denominator = ov5693_res[ov5693->fmt_idx].fps; -+ interval->interval.denominator = ov5693->mode->fps; - - return 0; - } -diff --git a/drivers/media/i2c/ov5693.h b/drivers/media/i2c/ov5693.h -index 29e6735112da..0377853f8b2b 100644 ---- a/drivers/media/i2c/ov5693.h -+++ b/drivers/media/i2c/ov5693.h -@@ -127,8 +127,8 @@ struct ov5693_resolution { - u8 *desc; - const struct ov5693_reg *regs; - int res; -- int width; -- int height; -+ u32 width; -+ u32 height; - int fps; - int pix_clk_freq; - u16 pixels_per_line; -@@ -178,7 +178,6 @@ struct ov5693_device { - struct camera_sensor_platform_data *platform_data; - ktime_t timestamp_t_focus_abs; - int vt_pix_clk_freq_mhz; -- int fmt_idx; - int run_mode; - int otp_size; - u8 *otp_data; --- -2.31.1 - diff --git a/patches/5.11/0010-ath10k-firmware-override.patch b/patches/5.11/0010-ath10k-firmware-override.patch deleted file mode 100644 index 56873a85a..000000000 --- a/patches/5.11/0010-ath10k-firmware-override.patch +++ /dev/null @@ -1,121 +0,0 @@ -From e798d41f1c6b8ac7aa1ac1d522ba766b4e05d5b7 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 27 Feb 2021 00:45:52 +0100 -Subject: [PATCH] ath10k: Add module parameters to override board files - -Some Surface devices, specifically the Surface Go and AMD version of the -Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better -with a different board file, as it seems that the firmeware included -upstream is buggy. - -As it is generally not a good idea to randomly overwrite files, let -alone doing so via packages, we add module parameters to override those -file names in the driver. This allows us to package/deploy the override -via a modprobe.d config. - -Signed-off-by: Maximilian Luz -Patchset: ath10k-firmware-override ---- - drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++ - 1 file changed, 58 insertions(+) - -diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c -index a419ec7130f9..b2c21bf5995e 100644 ---- a/drivers/net/wireless/ath/ath10k/core.c -+++ b/drivers/net/wireless/ath/ath10k/core.c -@@ -35,6 +35,9 @@ static bool skip_otp; - static bool rawmode; - static bool fw_diag_log; - -+static char *override_board = ""; -+static char *override_board2 = ""; -+ - unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | - BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); - -@@ -47,6 +50,9 @@ module_param(rawmode, bool, 0644); - module_param(fw_diag_log, bool, 0644); - module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); - -+module_param(override_board, charp, 0644); -+module_param(override_board2, charp, 0644); -+ - MODULE_PARM_DESC(debug_mask, "Debugging mask"); - MODULE_PARM_DESC(uart_print, "Uart target debugging"); - MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); -@@ -55,6 +61,9 @@ MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); - MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); - MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); - -+MODULE_PARM_DESC(override_board, "Override for board.bin file"); -+MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); -+ - static const struct ath10k_hw_params ath10k_hw_params_list[] = { - { - .id = QCA988X_HW_2_0_VERSION, -@@ -810,6 +819,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) - return 0; - } - -+static const char *ath10k_override_board_fw_file(struct ath10k *ar, -+ const char *file) -+{ -+ if (strcmp(file, "board.bin") == 0) { -+ if (strcmp(override_board, "") == 0) -+ return file; -+ -+ if (strcmp(override_board, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", -+ override_board); -+ -+ return override_board; -+ } -+ -+ if (strcmp(file, "board-2.bin") == 0) { -+ if (strcmp(override_board2, "") == 0) -+ return file; -+ -+ if (strcmp(override_board2, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", -+ override_board2); -+ -+ return override_board2; -+ } -+ -+ return file; -+} -+ - static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - const char *dir, - const char *file) -@@ -824,6 +869,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - if (dir == NULL) - dir = "."; - -+ /* HACK: Override board.bin and board-2.bin files if specified. -+ * -+ * Some Surface devices perform better with a different board -+ * configuration. To this end, one would need to replace the board.bin -+ * file with the modified config and remove the board-2.bin file. -+ * Unfortunately, that's not a solution that we can easily package. So -+ * we add module options to perform these overrides here. -+ */ -+ -+ file = ath10k_override_board_fw_file(ar, file); -+ if (!file) -+ return ERR_PTR(-ENOENT); -+ - snprintf(filename, sizeof(filename), "%s/%s", dir, file); - ret = firmware_request_nowarn(&fw, filename, ar->dev); - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", --- -2.31.1 - diff --git a/patches/5.12/0001-surface3-oemb.patch b/patches/5.12/0001-surface3-oemb.patch deleted file mode 100644 index ef7a85f47..000000000 --- a/patches/5.12/0001-surface3-oemb.patch +++ /dev/null @@ -1,101 +0,0 @@ -From 6e0788e583a88850a4694aa5fd034e1271ee13d6 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 18 Oct 2020 16:42:44 +0900 -Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI - table - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI data into dmi_system_id tables used -for quirks so that each driver can enable quirks even on the affected -systems. - -On affected systems, DMI data will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: surface3-oemb ---- - drivers/platform/surface/surface3-wmi.c | 7 +++++++ - sound/soc/codecs/rt5645.c | 9 +++++++++ - sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ - 3 files changed, 24 insertions(+) - -diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c -index fcd1d4fb94d5..ee26a5998b07 100644 ---- a/drivers/platform/surface/surface3-wmi.c -+++ b/drivers/platform/surface/surface3-wmi.c -@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - #endif - { } - }; -diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c -index 63a7e052eaa0..9806fd800020 100644 ---- a/sound/soc/codecs/rt5645.c -+++ b/sound/soc/codecs/rt5645.c -@@ -3694,6 +3694,15 @@ static const struct dmi_system_id dmi_platform_data[] = { - }, - .driver_data = (void *)&intel_braswell_platform_data, - }, -+ { -+ .ident = "Microsoft Surface 3", -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)&intel_braswell_platform_data, -+ }, - { - /* - * Match for the GPDwin which unfortunately uses somewhat -diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -index 227424236fd5..1013a57be89a 100644 ---- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c -+++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .callback = cht_surface_quirk_cb, -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - { } - }; - --- -2.32.0 - diff --git a/patches/5.12/0002-mwifiex.patch b/patches/5.12/0002-mwifiex.patch deleted file mode 100644 index aa4098f35..000000000 --- a/patches/5.12/0002-mwifiex.patch +++ /dev/null @@ -1,2622 +0,0 @@ -From b8016a9915956ec090571969cdbeacbd1d358a25 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Mon, 28 Sep 2020 17:46:49 +0900 -Subject: [PATCH] mwifiex: pcie: add DMI-based quirk impl for Surface devices - -This commit adds quirk implementation based on DMI matching with DMI -table for Surface devices. - -This implementation can be used for quirks later. - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/Makefile | 1 + - drivers/net/wireless/marvell/mwifiex/pcie.c | 4 + - drivers/net/wireless/marvell/mwifiex/pcie.h | 1 + - .../wireless/marvell/mwifiex/pcie_quirks.c | 114 ++++++++++++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.h | 11 ++ - 5 files changed, 131 insertions(+) - create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.c - create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.h - -diff --git a/drivers/net/wireless/marvell/mwifiex/Makefile b/drivers/net/wireless/marvell/mwifiex/Makefile -index 162d557b78af..2bd00f40958e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/Makefile -+++ b/drivers/net/wireless/marvell/mwifiex/Makefile -@@ -49,6 +49,7 @@ mwifiex_sdio-y += sdio.o - obj-$(CONFIG_MWIFIEX_SDIO) += mwifiex_sdio.o - - mwifiex_pcie-y += pcie.o -+mwifiex_pcie-y += pcie_quirks.o - obj-$(CONFIG_MWIFIEX_PCIE) += mwifiex_pcie.o - - mwifiex_usb-y += usb.o -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 94228b316df1..02fdce926de5 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -27,6 +27,7 @@ - #include "wmm.h" - #include "11n.h" - #include "pcie.h" -+#include "pcie_quirks.h" - - #define PCIE_VERSION "1.0" - #define DRV_NAME "Marvell mwifiex PCIe" -@@ -410,6 +411,9 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return ret; - } - -+ /* check quirks */ -+ mwifiex_initialize_quirks(card); -+ - if (mwifiex_add_card(card, &card->fw_done, &pcie_ops, - MWIFIEX_PCIE, &pdev->dev)) { - pr_err("%s failed\n", __func__); -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.h b/drivers/net/wireless/marvell/mwifiex/pcie.h -index 5ed613d65709..981e330c77d7 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.h -@@ -244,6 +244,7 @@ struct pcie_service_card { - unsigned long work_flags; - - bool pci_reset_ongoing; -+ unsigned long quirks; - }; - - static inline int -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -new file mode 100644 -index 000000000000..929aee2b0a60 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -0,0 +1,114 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * File for PCIe quirks. -+ */ -+ -+/* The low-level PCI operations will be performed in this file. Therefore, -+ * let's use dev_*() instead of mwifiex_dbg() here to avoid troubles (e.g. -+ * to avoid using mwifiex_adapter struct before init or wifi is powered -+ * down, or causes NULL ptr deref). -+ */ -+ -+#include -+ -+#include "pcie_quirks.h" -+ -+/* quirk table based on DMI matching */ -+static const struct dmi_system_id mwifiex_quirk_table[] = { -+ { -+ .ident = "Surface Pro 4", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 5", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 5 (LTE)", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 6", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Book 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Book 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Laptop 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Laptop 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 3"), -+ }, -+ .driver_data = 0, -+ }, -+ {} -+}; -+ -+void mwifiex_initialize_quirks(struct pcie_service_card *card) -+{ -+ struct pci_dev *pdev = card->dev; -+ const struct dmi_system_id *dmi_id; -+ -+ dmi_id = dmi_first_match(mwifiex_quirk_table); -+ if (dmi_id) -+ card->quirks = (uintptr_t)dmi_id->driver_data; -+ -+ if (!card->quirks) -+ dev_info(&pdev->dev, "no quirks enabled\n"); -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -new file mode 100644 -index 000000000000..5326ae7e5671 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -0,0 +1,11 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* -+ * Header file for PCIe quirks. -+ */ -+ -+#include "pcie.h" -+ -+/* quirks */ -+// quirk flags can be added here -+ -+void mwifiex_initialize_quirks(struct pcie_service_card *card); --- -2.32.0 - -From 27e9906691507ac94c7e3270ee589fa8f15d8388 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:25:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_d3cold quirk for Surface gen4+ - devices - -To reset mwifiex on Surface gen4+ (Pro 4 or later gen) devices, it -seems that putting the wifi device into D3cold is required according -to errata.inf file on Windows installation (Windows/INF/errata.inf). - -This patch adds a function that performs power-cycle (put into D3cold -then D0) and call the function at the end of reset_prepare(). - -Note: Need to also reset the parent device (bridge) of wifi on SB1; -it might be because the bridge of wifi always reports it's in D3hot. -When I tried to reset only the wifi device (not touching parent), it gave -the following error and the reset failed: - - acpi device:4b: Cannot transition to power state D0 for parent in D3hot - mwifiex_pcie 0000:03:00.0: can't change power state from D3cold to D0 (config space inaccessible) - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 ++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 73 +++++++++++++++++-- - .../wireless/marvell/mwifiex/pcie_quirks.h | 3 +- - 3 files changed, 74 insertions(+), 9 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 02fdce926de5..d9acfea395ad 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -528,6 +528,13 @@ static void mwifiex_pcie_reset_prepare(struct pci_dev *pdev) - mwifiex_shutdown_sw(adapter); - clear_bit(MWIFIEX_IFACE_WORK_DEVICE_DUMP, &card->work_flags); - clear_bit(MWIFIEX_IFACE_WORK_CARD_RESET, &card->work_flags); -+ -+ /* For Surface gen4+ devices, we need to put wifi into D3cold right -+ * before performing FLR -+ */ -+ if (card->quirks & QUIRK_FW_RST_D3COLD) -+ mwifiex_pcie_reset_d3cold_quirk(pdev); -+ - mwifiex_dbg(adapter, INFO, "%s, successful\n", __func__); - - card->pci_reset_ongoing = true; -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 929aee2b0a60..edc739c542fe 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -21,7 +21,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 5", -@@ -30,7 +30,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -39,7 +39,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 6", -@@ -47,7 +47,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Book 1", -@@ -55,7 +55,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Book 2", -@@ -63,7 +63,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Laptop 1", -@@ -71,7 +71,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Laptop 2", -@@ -79,7 +79,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface 3", -@@ -111,4 +111,61 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - - if (!card->quirks) - dev_info(&pdev->dev, "no quirks enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_D3COLD) -+ dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+} -+ -+static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -+{ -+ dev_info(&pdev->dev, "putting into D3cold...\n"); -+ -+ pci_save_state(pdev); -+ if (pci_is_enabled(pdev)) -+ pci_disable_device(pdev); -+ pci_set_power_state(pdev, PCI_D3cold); -+} -+ -+static int mwifiex_pcie_set_power_d0(struct pci_dev *pdev) -+{ -+ int ret; -+ -+ dev_info(&pdev->dev, "putting into D0...\n"); -+ -+ pci_set_power_state(pdev, PCI_D0); -+ ret = pci_enable_device(pdev); -+ if (ret) { -+ dev_err(&pdev->dev, "pci_enable_device failed\n"); -+ return ret; -+ } -+ pci_restore_state(pdev); -+ -+ return 0; -+} -+ -+int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) -+{ -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); -+ int ret; -+ -+ /* Power-cycle (put into D3cold then D0) */ -+ dev_info(&pdev->dev, "Using reset_d3cold quirk to perform FW reset\n"); -+ -+ /* We need to perform power-cycle also for bridge of wifi because -+ * on some devices (e.g. Surface Book 1), the OS for some reasons -+ * can't know the real power state of the bridge. -+ * When tried to power-cycle only wifi, the reset failed with the -+ * following dmesg log: -+ * "Cannot transition to power state D0 for parent in D3hot". -+ */ -+ mwifiex_pcie_set_power_d3cold(pdev); -+ mwifiex_pcie_set_power_d3cold(parent_pdev); -+ -+ ret = mwifiex_pcie_set_power_d0(parent_pdev); -+ if (ret) -+ return ret; -+ ret = mwifiex_pcie_set_power_d0(pdev); -+ if (ret) -+ return ret; -+ -+ return 0; - } -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 5326ae7e5671..8b9dcb5070d8 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -6,6 +6,7 @@ - #include "pcie.h" - - /* quirks */ --// quirk flags can be added here -+#define QUIRK_FW_RST_D3COLD BIT(0) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); -+int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.32.0 - -From 9eca2718cca0e5c6a2a4f08970609b807f828f5a Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:32:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_wsid quirk for Surface 3 - -This commit adds reset_wsid quirk and uses this quirk for Surface 3 on -card reset. - -To reset mwifiex on Surface 3, it seems that calling the _DSM method -exists in \_SB.WSID [1] device is required. - -On Surface 3, calling the _DSM method removes/re-probes the card by -itself. So, need to place the reset function before performing FLR and -skip performing any other reset-related works. - -Note that Surface Pro 3 also has the WSID device [2], but it seems to need -more work. This commit only supports Surface 3 yet. - -[1] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_3/dsdt.dsl#L11947-L12011 -[2] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_pro_3/dsdt.dsl#L12164-L12216 - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 10 +++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 77 ++++++++++++++++++- - .../wireless/marvell/mwifiex/pcie_quirks.h | 5 ++ - 3 files changed, 91 insertions(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index d9acfea395ad..6e049236ae1a 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -2969,6 +2969,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - -+ /* On Surface 3, reset_wsid method removes then re-probes card by -+ * itself. So, need to place it here and skip performing any other -+ * reset-related works. -+ */ -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) { -+ mwifiex_pcie_reset_wsid_quirk(card->dev); -+ /* skip performing any other reset-related works */ -+ return; -+ } -+ - /* We can't afford to wait here; remove() might be waiting on us. If we - * can't grab the device lock, maybe we'll get another chance later. - */ -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index edc739c542fe..f0a6fa0a7ae5 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -9,10 +9,21 @@ - * down, or causes NULL ptr deref). - */ - -+#include - #include - - #include "pcie_quirks.h" - -+/* For reset_wsid quirk */ -+#define ACPI_WSID_PATH "\\_SB.WSID" -+#define WSID_REV 0x0 -+#define WSID_FUNC_WIFI_PWR_OFF 0x1 -+#define WSID_FUNC_WIFI_PWR_ON 0x2 -+/* WSID _DSM UUID: "534ea3bf-fcc2-4e7a-908f-a13978f0c7ef" */ -+static const guid_t wsid_dsm_guid = -+ GUID_INIT(0x534ea3bf, 0xfcc2, 0x4e7a, -+ 0x90, 0x8f, 0xa1, 0x39, 0x78, 0xf0, 0xc7, 0xef); -+ - /* quirk table based on DMI matching */ - static const struct dmi_system_id mwifiex_quirk_table[] = { - { -@@ -87,7 +98,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, - { - .ident = "Surface Pro 3", -@@ -113,6 +124,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - dev_info(&pdev->dev, "no quirks enabled\n"); - if (card->quirks & QUIRK_FW_RST_D3COLD) - dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) -+ dev_info(&pdev->dev, -+ "quirk reset_wsid for Surface 3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -@@ -169,3 +183,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) - - return 0; - } -+ -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev) -+{ -+ acpi_handle handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n"); -+ -+ status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&pdev->dev, "No ACPI handle for path %s\n", -+ ACPI_WSID_PATH); -+ return -ENODEV; -+ } -+ -+ if (!acpi_has_method(handle, "_DSM")) { -+ dev_err(&pdev->dev, "_DSM method not found\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power off func\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power on func\n"); -+ return -ENODEV; -+ } -+ -+ /* card will be removed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi off...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi off\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ /* card will be re-probed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi on...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi on\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ return 0; -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 8b9dcb5070d8..3ef7440418e3 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -7,6 +7,11 @@ - - /* quirks */ - #define QUIRK_FW_RST_D3COLD BIT(0) -+/* Surface 3 and Surface Pro 3 have the same _DSM method but need to -+ * be handled differently. Currently, only S3 is supported. -+ */ -+#define QUIRK_FW_RST_WSID_S3 BIT(1) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); --- -2.32.0 - -From 1fb5bff63aa8953bd58ff6aed00f799fc46ad890 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Wed, 30 Sep 2020 18:08:24 +0900 -Subject: [PATCH] mwifiex: pcie: (OEMB) add quirk for Surface 3 with broken DMI - table - -(made referring to http://git.osdn.net/view?p=android-x86/kernel.git;a=commitdiff;h=18e2e857c57633b25b3b4120f212224a108cd883) - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI info for the affected Surface 3. - -On affected systems, DMI info will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie_quirks.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index f0a6fa0a7ae5..34dcd84f02a6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -100,6 +100,15 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - { - .ident = "Surface Pro 3", - .matches = { --- -2.32.0 - -From 0dd136f878c33a58a19464196d51c6175a7d1093 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 4 Oct 2020 00:11:49 +0900 -Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ - -Currently, mwifiex fw will crash after suspend on recent kernel series. -On Windows, it seems that the root port of wifi will never enter D3 state -(stay on D0 state). And on Linux, disabling the D3 state for the -bridge fixes fw crashing after suspend. - -This commit disables the D3 state of root port on driver initialization -and fixes fw crashing after suspend. - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 27 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 6e049236ae1a..d027c875d7a0 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -379,6 +379,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) - { - struct pcie_service_card *card; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - int ret; - - pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", -@@ -420,6 +421,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return -1; - } - -+ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing -+ * after suspend -+ */ -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ parent_pdev->bridge_d3 = false; -+ - return 0; - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 34dcd84f02a6..a2aeb2af907e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -32,7 +32,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5", -@@ -41,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -50,7 +52,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 6", -@@ -58,7 +61,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 1", -@@ -66,7 +70,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 2", -@@ -74,7 +79,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 1", -@@ -82,7 +88,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 2", -@@ -90,7 +97,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface 3", -@@ -136,6 +144,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_FW_RST_WSID_S3) - dev_info(&pdev->dev, - "quirk reset_wsid for Surface 3 enabled\n"); -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ dev_info(&pdev->dev, -+ "quirk no_brigde_d3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 3ef7440418e3..a95ebac06e13 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -11,6 +11,7 @@ - * be handled differently. Currently, only S3 is supported. - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) -+#define QUIRK_NO_BRIDGE_D3 BIT(2) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.32.0 - -From c3cdd9ca1e89e1a44a44db58e3e6266cdf8c0fe6 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 3 Nov 2020 13:28:04 +0100 -Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface - devices - -The most recent firmware of the 88W8897 card reports a hardcoded LTR -value to the system during initialization, probably as an (unsuccessful) -attempt of the developers to fix firmware crashes. This LTR value -prevents most of the Microsoft Surface devices from entering deep -powersaving states (either platform C-State 10 or S0ix state), because -the exit latency of that state would be higher than what the card can -tolerate. - -Turns out the card works just the same (including the firmware crashes) -no matter if that hardcoded LTR value is reported or not, so it's kind -of useless and only prevents us from saving power. - -To get rid of those hardcoded LTR reports, it's possible to reset the -PCI bridge device after initializing the cards firmware. I'm not exactly -sure why that works, maybe the power management subsystem of the PCH -resets its stored LTR values when doing a function level reset of the -bridge device. Doing the reset once after starting the wifi firmware -works very well, probably because the firmware only reports that LTR -value a single time during firmware startup. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 31 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index d027c875d7a0..8a99e243aff2 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -1757,9 +1757,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) - static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; -+ struct pci_dev *pdev = card->dev; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; - int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; - -+ /* Trigger a function level reset of the PCI bridge device, this makes -+ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value -+ * that prevents the system from entering package C10 and S0ix powersaving -+ * states. -+ * We need to do it here because it must happen after firmware -+ * initialization and this function is called after that is done. -+ */ -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ pci_reset_function(parent_pdev); -+ - /* Write the RX ring read pointer in to reg->rx_rdptr */ - if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | - tx_wrap)) { -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index a2aeb2af907e..6885575826a6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -33,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5", -@@ -43,7 +44,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -53,7 +55,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 6", -@@ -62,7 +65,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 1", -@@ -71,7 +75,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 2", -@@ -80,7 +85,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 1", -@@ -89,7 +95,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 2", -@@ -98,7 +105,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface 3", -@@ -147,6 +155,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_NO_BRIDGE_D3) - dev_info(&pdev->dev, - "quirk no_brigde_d3 enabled\n"); -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index a95ebac06e13..4ec2ae72f632 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -12,6 +12,7 @@ - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) - #define QUIRK_NO_BRIDGE_D3 BIT(2) -+#define QUIRK_DO_FLR_ON_BRIDGE BIT(3) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.32.0 - -From 530dc69e71307e3054ae8f0095b55a1ae31bbf2d Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 25 Mar 2021 11:33:02 +0100 -Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell - 88W8897 - -The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) -is used in a lot of Microsoft Surface devices, and all those devices -suffer from very low 2.4GHz wifi connection speeds while bluetooth is -enabled. The reason for that is that the default passive scanning -interval for Bluetooth Low Energy devices is quite high in Linux -(interval of 60 msec and scan window of 30 msec, see hci_core.c), and -the Marvell chip is known for its bad bt+wifi coexisting performance. - -So decrease that passive scan interval and make the scan window shorter -on this particular device to allow for spending more time transmitting -wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and -the new scan window is 6.25 msec (0xa * 0,625 msec). - -This change has a very large impact on the 2.4GHz wifi speeds and gets -it up to performance comparable with the Windows driver, which seems to -apply a similar quirk. - -The interval and window length were tested and found to work very well -with a lot of Bluetooth Low Energy devices, including the Surface Pen, a -Bluetooth Speaker and two modern Bluetooth headphones. All devices were -discovered immediately after turning them on. Even lower values were -also tested, but they introduced longer delays until devices get -discovered. - -Patchset: mwifiex ---- - drivers/bluetooth/btusb.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index ddc7b86725cd..22df5fe95678 100644 ---- a/drivers/bluetooth/btusb.c -+++ b/drivers/bluetooth/btusb.c -@@ -61,6 +61,7 @@ static struct usb_driver btusb_driver; - #define BTUSB_VALID_LE_STATES 0x800000 - #define BTUSB_QCA_WCN6855 0x1000000 - #define BTUSB_INTEL_NEWGEN 0x2000000 -+#define BTUSB_LOWER_LESCAN_INTERVAL BIT(26) - - static const struct usb_device_id btusb_table[] = { - /* Generic Bluetooth USB device */ -@@ -357,6 +358,7 @@ static const struct usb_device_id blacklist_table[] = { - { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, -+ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, - - /* Intel Bluetooth devices */ - { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_NEW | -@@ -4717,6 +4719,19 @@ static int btusb_probe(struct usb_interface *intf, - if (id->driver_info & BTUSB_MARVELL) - hdev->set_bdaddr = btusb_set_bdaddr_marvell; - -+ /* The Marvell 88W8897 combined wifi and bluetooth card is known for -+ * very bad bt+wifi coexisting performance. -+ * -+ * Decrease the passive BT Low Energy scan interval a bit -+ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter -+ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly -+ * higher wifi throughput while passively scanning for BT LE devices. -+ */ -+ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { -+ hdev->le_scan_interval = 0x0190; -+ hdev->le_scan_window = 0x000a; -+ } -+ - if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && - (id->driver_info & BTUSB_MEDIATEK)) { - hdev->setup = btusb_mtk_setup; --- -2.32.0 - -From ac2fb3eb8df4577d8fd3aa10576b705960ef1d53 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 12:31:26 +0100 -Subject: [PATCH] mwifiex: Small cleanup for handling virtual interface type - changes - -Handle the obvious invalid virtual interface type changes with a general -check instead of looking at the individual change. - -For type changes from P2P_CLIENT to P2P_GO and the other way round, this -changes the behavior slightly: We now still do nothing, but return --EOPNOTSUPP instead of 0. Now that behavior was incorrect before and -still is, because type changes between these two types are actually -possible and supported, which we'll fix in a following commit. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 39 +++++++------------ - 1 file changed, 14 insertions(+), 25 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index a2ed268ce0da..789de1b0c5b1 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1141,6 +1141,20 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return -EBUSY; - } - -+ if (type == NL80211_IFTYPE_UNSPECIFIED) { -+ mwifiex_dbg(priv->adapter, INFO, -+ "%s: no new type specified, keeping old type %d\n", -+ dev->name, curr_iftype); -+ return 0; -+ } -+ -+ if (curr_iftype == type) { -+ mwifiex_dbg(priv->adapter, INFO, -+ "%s: interface already is of type %d\n", -+ dev->name, curr_iftype); -+ return 0; -+ } -+ - switch (curr_iftype) { - case NL80211_IFTYPE_ADHOC: - switch (type) { -@@ -1160,12 +1174,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as IBSS\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_ADHOC: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1191,12 +1199,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as STA\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_STATION: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1214,12 +1216,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_P2P_GO: - return mwifiex_change_vif_to_p2p(dev, curr_iftype, - type, params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as AP\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_AP: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1254,13 +1250,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return -EFAULT; - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as P2P\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", --- -2.32.0 - -From a4b2dae28e3cfbfc2202716cf72ba2c25ed1a598 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 10 Nov 2020 12:49:56 +0100 -Subject: [PATCH] mwifiex: Use non-posted PCI register writes - -On the 88W8897 card it's very important the TX ring write pointer is -updated correctly to its new value before setting the TX ready -interrupt, otherwise the firmware appears to crash (probably because -it's trying to DMA-read from the wrong place). - -Since PCI uses "posted writes" when writing to a register, it's not -guaranteed that a write will happen immediately. That means the pointer -might be outdated when setting the TX ready interrupt, leading to -firmware crashes especially when ASPM L1 and L1 substates are enabled -(because of the higher link latency, the write will probably take -longer). - -So fix those firmware crashes by always forcing non-posted writes. We do -that by simply reading back the register after writing it, just as a lot -of other drivers do. - -There are two reproducers that are fixed with this patch: - -1) During rx/tx traffic and with ASPM L1 substates enabled (the enabled -substates are platform dependent), the firmware crashes and eventually a -command timeout appears in the logs. That crash is fixed by using a -non-posted write in mwifiex_pcie_send_data(). - -2) When sending lots of commands to the card, waking it up from sleep in -very quick intervals, the firmware eventually crashes. That crash -appears to be fixed by some other non-posted write included here. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 8a99e243aff2..84b1d30e07e4 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -237,6 +237,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) - - iowrite32(data, card->pci_mmap1 + reg); - -+ /* Do a read-back, which makes the write non-posted, ensuring the -+ * completion before returning. -+ * The firmware of the 88W8897 card is buggy and this avoids crashes. -+ */ -+ ioread32(card->pci_mmap1 + reg); -+ - return 0; - } - --- -2.32.0 - -From 1320ab07fb559742a97992f137c1ec015b3e788f Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 12:44:39 +0100 -Subject: [PATCH] mwifiex: Use function to check whether interface type change - is allowed - -Instead of bailing out in the function which is supposed to do the type -change, detect invalid changes beforehand using a generic function and -return an error if the change is not allowed. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 139 ++++++++++++------ - 1 file changed, 92 insertions(+), 47 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 789de1b0c5b1..13698818e58a 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -939,6 +939,76 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, - return 0; - } - -+static bool -+is_vif_type_change_allowed(struct mwifiex_adapter *adapter, -+ enum nl80211_iftype old_iftype, -+ enum nl80211_iftype new_iftype) -+{ -+ switch (old_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_STATION: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_AP: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return adapter->curr_iface_comb.sta_intf != -+ adapter->iface_limit.sta_intf; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ default: -+ break; -+ } -+ -+ return false; -+} -+ - static int - mwifiex_change_vif_to_p2p(struct net_device *dev, - enum nl80211_iftype curr_iftype, -@@ -955,13 +1025,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - - adapter = priv->adapter; - -- if (adapter->curr_iface_comb.p2p_intf == -- adapter->iface_limit.p2p_intf) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple P2P ifaces\n"); -- return -1; -- } -- - mwifiex_dbg(adapter, INFO, - "%s: changing role to p2p\n", dev->name); - -@@ -1027,15 +1090,6 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - - adapter = priv->adapter; - -- if ((curr_iftype != NL80211_IFTYPE_P2P_CLIENT && -- curr_iftype != NL80211_IFTYPE_P2P_GO) && -- (adapter->curr_iface_comb.sta_intf == -- adapter->iface_limit.sta_intf)) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple station/adhoc ifaces\n"); -- return -1; -- } -- - if (type == NL80211_IFTYPE_STATION) - mwifiex_dbg(adapter, INFO, - "%s: changing role to station\n", dev->name); -@@ -1086,13 +1140,6 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - - adapter = priv->adapter; - -- if (adapter->curr_iface_comb.uap_intf == -- adapter->iface_limit.uap_intf) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple AP ifaces\n"); -- return -1; -- } -- - mwifiex_dbg(adapter, INFO, - "%s: changing role to AP\n", dev->name); - -@@ -1155,6 +1202,13 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return 0; - } - -+ if (!is_vif_type_change_allowed(priv->adapter, curr_iftype, type)) { -+ mwifiex_dbg(priv->adapter, ERROR, -+ "%s: change from type %d to %d is not allowed\n", -+ dev->name, curr_iftype, type); -+ return -EOPNOTSUPP; -+ } -+ - switch (curr_iftype) { - case NL80211_IFTYPE_ADHOC: - switch (type) { -@@ -1175,12 +1229,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_STATION: - switch (type) { - case NL80211_IFTYPE_ADHOC: -@@ -1200,12 +1251,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_AP: - switch (type) { - case NL80211_IFTYPE_ADHOC: -@@ -1217,12 +1265,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_p2p(dev, curr_iftype, - type, params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_P2P_CLIENT: - case NL80211_IFTYPE_P2P_GO: - switch (type) { -@@ -1251,21 +1296,21 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: unknown iftype: %d\n", -- dev->name, dev->ieee80211_ptr->iftype); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } - - - return 0; -+ -+errnotsupp: -+ mwifiex_dbg(priv->adapter, ERROR, -+ "unsupported interface type transition: %d to %d\n", -+ curr_iftype, type); -+ return -EOPNOTSUPP; - } - - static void --- -2.32.0 - -From 17711ec7598f042c61810b73edf54efa34712650 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 13:33:04 +0100 -Subject: [PATCH] mwifiex: Run SET_BSS_MODE when changing from P2P to STATION - vif-type - -We currently handle changing from the P2P to the STATION virtual -interface type slightly different than changing from P2P to ADHOC: When -changing to STATION, we don't send the SET_BSS_MODE command. We do send -that command on all other type-changes though, and it probably makes -sense to send the command since after all we just changed our BSS_MODE. -Looking at prior changes to this part of the code, it seems that this is -simply a leftover from old refactorings. - -Since sending the SET_BSS_MODE command is the only difference between -mwifiex_change_vif_to_sta_adhoc() and the current code, we can now use -mwifiex_change_vif_to_sta_adhoc() for both switching to ADHOC and -STATION interface type. - -This does not fix any particular bug and just "looked right", so there's -a small chance it might be a regression. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 22 ++++--------------- - 1 file changed, 4 insertions(+), 18 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 13698818e58a..f5b9f1d26114 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1270,29 +1270,15 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - - case NL80211_IFTYPE_P2P_CLIENT: - case NL80211_IFTYPE_P2P_GO: -+ if (mwifiex_cfg80211_deinit_p2p(priv)) -+ return -EFAULT; -+ - switch (type) { -- case NL80211_IFTYPE_STATION: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; -- priv->adapter->curr_iface_comb.p2p_intf--; -- priv->adapter->curr_iface_comb.sta_intf++; -- dev->ieee80211_ptr->iftype = type; -- if (mwifiex_deinit_priv_params(priv)) -- return -1; -- if (mwifiex_init_new_priv_params(priv, dev, type)) -- return -1; -- if (mwifiex_sta_init_cmd(priv, false, false)) -- return -1; -- break; - case NL80211_IFTYPE_ADHOC: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; -+ case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); -- break; - case NL80211_IFTYPE_AP: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: --- -2.32.0 - -From 6fe37bfe033b5c6d2ba8a5a5ff3d8e08bae26c40 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 14:42:54 +0100 -Subject: [PATCH] mwifiex: Use helper function for counting interface types - -Use a small helper function to increment and decrement the counter of -the interface types we currently manage. This makes the code that -actually changes and sets up the interface type a bit less messy and -also helps avoiding mistakes in case someone increments/decrements a -counter wrongly. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 110 ++++++------------ - 1 file changed, 35 insertions(+), 75 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index f5b9f1d26114..44cff715bf29 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1009,6 +1009,32 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, - return false; - } - -+static void -+update_vif_type_counter(struct mwifiex_adapter *adapter, -+ enum nl80211_iftype iftype, -+ int change) -+{ -+ switch (iftype) { -+ case NL80211_IFTYPE_UNSPECIFIED: -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ adapter->curr_iface_comb.sta_intf += change; -+ break; -+ case NL80211_IFTYPE_AP: -+ adapter->curr_iface_comb.uap_intf += change; -+ break; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ adapter->curr_iface_comb.p2p_intf += change; -+ break; -+ default: -+ mwifiex_dbg(adapter, ERROR, -+ "%s: Unsupported iftype passed: %d\n", -+ __func__, iftype); -+ break; -+ } -+} -+ - static int - mwifiex_change_vif_to_p2p(struct net_device *dev, - enum nl80211_iftype curr_iftype, -@@ -1056,19 +1082,8 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.p2p_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; - - return 0; -@@ -1107,20 +1122,10 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.sta_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; -+ - return 0; - } - -@@ -1153,20 +1158,8 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.uap_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; - return 0; - } -@@ -3131,23 +3124,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, - mwifiex_dev_debugfs_init(priv); - #endif - -- switch (type) { -- case NL80211_IFTYPE_UNSPECIFIED: -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf++; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf++; -- break; -- case NL80211_IFTYPE_P2P_CLIENT: -- adapter->curr_iface_comb.p2p_intf++; -- break; -- default: -- /* This should be dead code; checked above */ -- mwifiex_dbg(adapter, ERROR, "type not supported\n"); -- return ERR_PTR(-EINVAL); -- } -+ update_vif_type_counter(adapter, type, +1); - - return &priv->wdev; - -@@ -3213,24 +3190,7 @@ int mwifiex_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) - /* Clear the priv in adapter */ - priv->netdev = NULL; - -- switch (priv->bss_mode) { -- case NL80211_IFTYPE_UNSPECIFIED: -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- default: -- mwifiex_dbg(adapter, ERROR, -- "del_virtual_intf: type not supported\n"); -- break; -- } -+ update_vif_type_counter(adapter, priv->bss_mode, -1); - - priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; - --- -2.32.0 - -From 3e4fc97460389e821651285b7b0aaee700558177 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:56:58 +0100 -Subject: [PATCH] mwifiex: Update virtual interface counters right after - setting bss_type - -In mwifiex_init_new_priv_params() we update our private driver state to -reflect the currently selected virtual interface type. Most notably we -set the bss_mode to the mode we're going to put the firmware in. - -Now after we updated the driver state we actually start talking to the -firmware and instruct it to set up the new mode. Those commands can and -will sometimes fail, in which case we return with an error from -mwifiex_change_vif_to_*. We currently update our virtual interface type -counters after this return, which means the code is never reached when a -firmware error happens and we never update the counters. Since we have -updated our bss_mode earlier though, the counters now no longer reflect -the actual state of the driver. - -This will break things on the next virtual interface change, because the -virtual interface type we're switching away from didn't get its counter -incremented, and we end up decrementing a 0-counter. - -To fix this, simply update the virtual interface type counters right -after updating our driver structures, so that they are always in sync. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 25 +++++++++++-------- - 1 file changed, 14 insertions(+), 11 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 44cff715bf29..e637129a411f 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1059,6 +1059,10 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; - -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - switch (type) { - case NL80211_IFTYPE_P2P_CLIENT: - if (mwifiex_cfg80211_init_p2p_client(priv)) -@@ -1082,10 +1086,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; -- - return 0; - } - -@@ -1116,16 +1116,17 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - return -1; - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; -+ -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, - HostCmd_ACT_GEN_SET, 0, NULL, true)) - return -1; - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; -- - return 0; - } - -@@ -1152,15 +1153,17 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - return -1; - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; -+ -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, - HostCmd_ACT_GEN_SET, 0, NULL, true)) - return -1; - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; - return 0; - } - /* --- -2.32.0 - -From 1c9ba019f06fdc28cfbd6e2abdbe9d39b448d2c7 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 13:42:40 +0100 -Subject: [PATCH] mwifiex: Allow switching interface type from P2P_CLIENT to - P2P_GO - -It's possible to change virtual interface type between P2P_CLIENT and -P2P_GO, the card supports that just fine, and it happens for example -when using miracast with the miraclecast software. - -So allow type changes between P2P_CLIENT and P2P_GO and simply call into -mwifiex_change_vif_to_p2p(), which handles this just fine. We have to -call mwifiex_cfg80211_deinit_p2p() before though to make sure the old -p2p mode is properly uninitialized. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 36 +++++++++++++++++++ - 1 file changed, 36 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index e637129a411f..395573db6405 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -990,11 +990,26 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, - } - - case NL80211_IFTYPE_P2P_CLIENT: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_P2P_GO: -+ return true; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ - case NL80211_IFTYPE_P2P_GO: - switch (new_iftype) { - case NL80211_IFTYPE_ADHOC: - case NL80211_IFTYPE_STATION: - return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ return true; - case NL80211_IFTYPE_AP: - return adapter->curr_iface_comb.uap_intf != - adapter->iface_limit.uap_intf; -@@ -1265,6 +1280,24 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - } - - case NL80211_IFTYPE_P2P_CLIENT: -+ if (mwifiex_cfg80211_deinit_p2p(priv)) -+ return -EFAULT; -+ -+ switch (type) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, -+ type, params); -+ case NL80211_IFTYPE_P2P_GO: -+ return mwifiex_change_vif_to_p2p(dev, curr_iftype, -+ type, params); -+ case NL80211_IFTYPE_AP: -+ return mwifiex_change_vif_to_ap(dev, curr_iftype, type, -+ params); -+ default: -+ goto errnotsupp; -+ } -+ - case NL80211_IFTYPE_P2P_GO: - if (mwifiex_cfg80211_deinit_p2p(priv)) - return -EFAULT; -@@ -1274,6 +1307,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); -+ case NL80211_IFTYPE_P2P_CLIENT: -+ return mwifiex_change_vif_to_p2p(dev, curr_iftype, -+ type, params); - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); --- -2.32.0 - -From 7ab426927ac757af531ab6ee7e3ab472f21e30d5 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:31:08 +0100 -Subject: [PATCH] mwifiex: Handle interface type changes from AP to STATION - -Looks like this case was simply overseen, so handle it, too. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 395573db6405..90a757aa0b25 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1268,6 +1268,7 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - switch (type) { - case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); - break; --- -2.32.0 - -From b76dff0198def0fe82a951eb972287722b6b88c1 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:32:16 +0100 -Subject: [PATCH] mwifiex: Properly initialize private structure on interface - type changes - -When creating a new virtual interface in mwifiex_add_virtual_intf(), we -update our internal driver states like bss_type, bss_priority, bss_role -and bss_mode to reflect the mode the firmware will be set to. - -When switching virtual interface mode using -mwifiex_init_new_priv_params() though, we currently only update bss_mode -and bss_role. In order for the interface mode switch to actually work, -we also need to update bss_type to its proper value, so do that. - -This fixes a crash of the firmware (because the driver tries to execute -commands that are invalid in AP mode) when switching from station mode -to AP mode. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 +++++++--- - 1 file changed, 7 insertions(+), 3 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 90a757aa0b25..0c01d8f9048e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -908,16 +908,20 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, - switch (type) { - case NL80211_IFTYPE_STATION: - case NL80211_IFTYPE_ADHOC: -- priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_type = MWIFIEX_BSS_TYPE_STA; - break; - case NL80211_IFTYPE_P2P_CLIENT: -- priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - break; - case NL80211_IFTYPE_P2P_GO: -- priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - break; - case NL80211_IFTYPE_AP: - priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_type = MWIFIEX_BSS_TYPE_UAP; - break; - default: - mwifiex_dbg(adapter, ERROR, --- -2.32.0 - -From ec78570ecadff12e4ea7263633332bcd02cad945 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sat, 27 Mar 2021 12:19:14 +0100 -Subject: [PATCH] mwifiex: Fix copy-paste mistake when creating virtual - interface - -The BSS priority here for a new P2P_CLIENT device was accidentally set -to an enum that's certainly not meant for this. Since -MWIFIEX_BSS_ROLE_STA is 0 anyway, we can just set the bss_priority to 0 -instead here. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 0c01d8f9048e..8c472b2d982a 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -3057,7 +3057,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, - priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - - priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II; -- priv->bss_priority = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_priority = 0; - priv->bss_role = MWIFIEX_BSS_ROLE_STA; - priv->bss_started = 0; - --- -2.32.0 - -From fbe72d5c20e945b463c4e861479b11c7c9b801a9 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sun, 28 Mar 2021 21:10:06 +0200 -Subject: [PATCH] mwifiex: Try waking the firmware until we get an interrupt - -It seems that the firmware of the 88W8897 card sometimes ignores or -misses when we try to wake it up by reading the firmware status -register. This leads to the firmware wakeup timeout expiring and the -driver resetting the card because we assume the firmware has hung up or -crashed (unfortunately that's not unlikely with this card). - -Turns out that most of the time the firmware actually didn't hang up, -but simply "missed" our wakeup request and doesn't send us an AWAKE -event. - -Trying again to read the firmware status register after a short timeout -usually makes the firmware wake we up as expected, so add a small retry -loop to mwifiex_pm_wakeup_card() that looks at the interrupt status to -check whether the card woke up. - -The number of tries and timeout lengths for this were determined -experimentally: The firmware usually takes about 500 us to wake up -after we attempt to read the status register. In some cases where the -firmware is very busy (for example while doing a bluetooth scan) it -might even miss our requests for multiple milliseconds, which is why -after 15 tries the waiting time gets increased to 10 ms. The maximum -number of tries it took to wake the firmware when testing this was -around 20, so a maximum number of 50 tries should give us plenty of -safety margin. - -A good reproducer for this issue is letting the firmware sleep and wake -up in very short intervals, for example by pinging an device on the -network every 0.1 seconds. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 29 ++++++++++++++++----- - 1 file changed, 23 insertions(+), 6 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 84b1d30e07e4..88d30ec6d57d 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -665,6 +665,7 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; -+ int n_tries = 0; - - mwifiex_dbg(adapter, EVENT, - "event: Wakeup device...\n"); -@@ -672,12 +673,28 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) - if (reg->sleep_cookie) - mwifiex_pcie_dev_wakeup_delay(adapter); - -- /* Accessing fw_status register will wakeup device */ -- if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) { -- mwifiex_dbg(adapter, ERROR, -- "Writing fw_status register failed\n"); -- return -1; -- } -+ /* Access the fw_status register to wake up the device. -+ * Since the 88W8897 firmware sometimes appears to ignore or miss -+ * that wakeup request, we continue trying until we receive an -+ * interrupt from the card. -+ */ -+ do { -+ if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) { -+ mwifiex_dbg(adapter, ERROR, -+ "Writing fw_status register failed\n"); -+ return -1; -+ } -+ -+ n_tries++; -+ -+ if (n_tries <= 15) -+ usleep_range(400, 700); -+ else -+ msleep(10); -+ } while (n_tries <= 50 && READ_ONCE(adapter->int_status) == 0); -+ -+ mwifiex_dbg(adapter, EVENT, -+ "event: Tried %d times until firmware woke up\n", n_tries); - - if (reg->sleep_cookie) { - mwifiex_pcie_dev_wakeup_delay(adapter); --- -2.32.0 - -From ccf5b0f76e3ba3b74e3a433b89e0858d6900546b Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:30:28 +0200 -Subject: [PATCH] mwifiex: Deactive host sleep using HSCFG after it was - activated manually - -When powersaving (so either wifi powersaving or deep sleep, depending on -which state the firmware is in) is disabled, the way the firmware goes -into host sleep is different: Usually the firmware implicitely enters -host sleep on the next SLEEP event we get when we configured host sleep -via HSCFG before. When powersaving is disabled though, there are no -SLEEP events, the way we enter host sleep in that case is different: The -firmware will send us a HS_ACT_REQ event and after that we "manually" -make the firmware enter host sleep by sending it another HSCFG command -with the action HS_ACTIVATE. - -Now waking up from host sleep appears to be different depending on -whether powersaving is enabled again: When powersaving is enabled, the -firmware implicitely leaves host sleep as soon as it wakes up and sends -us an AWAKE event. When powersaving is disabled though, it apparently -doesn't implicitely leave host sleep, but instead we need to send it a -HSCFG command with the HS_CONFIGURE action and the HS_CFG_CANCEL -condition. We didn't do that so far, which is why waking up from host -sleep was broken when powersaving is disabled. - -So add some additional state to mwifiex_adapter where we keep track of -whether host sleep was activated manually via HS_ACTIVATE, and if that -was the case, deactivate it manually again via HS_CFG_CANCEL. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cmdevt.c | 21 +++++++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.c | 18 ++++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmd.c | 4 ++++ - 4 files changed, 44 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cmdevt.c b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -index 3a11342a6bde..5487df8f994d 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cmdevt.c -+++ b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -@@ -608,6 +608,11 @@ int mwifiex_send_cmd(struct mwifiex_private *priv, u16 cmd_no, - return -1; - } - -+ if (priv->adapter->hs_activated_manually && -+ cmd_no != HostCmd_CMD_802_11_HS_CFG_ENH) { -+ mwifiex_cancel_hs(priv, MWIFIEX_ASYNC_CMD); -+ priv->adapter->hs_activated_manually = false; -+ } - - /* Get a new command node */ - cmd_node = mwifiex_get_cmd_node(adapter); -@@ -714,6 +719,15 @@ mwifiex_insert_cmd_to_pending_q(struct mwifiex_adapter *adapter, - } - } - -+ /* Same with exit host sleep cmd, luckily that can't happen at the same time as EXIT_PS */ -+ if (command == HostCmd_CMD_802_11_HS_CFG_ENH) { -+ struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = -+ &host_cmd->params.opt_hs_cfg; -+ -+ if (le16_to_cpu(hs_cfg->action) == HS_ACTIVATE) -+ add_tail = false; -+ } -+ - spin_lock_bh(&adapter->cmd_pending_q_lock); - if (add_tail) - list_add_tail(&cmd_node->list, &adapter->cmd_pending_q); -@@ -1216,6 +1230,13 @@ mwifiex_process_hs_config(struct mwifiex_adapter *adapter) - __func__); - - adapter->if_ops.wakeup(adapter); -+ -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv (adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - adapter->hs_activated = false; - clear_bit(MWIFIEX_IS_HS_CONFIGURED, &adapter->work_flags); - clear_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags); -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index 529dfd8b7ae8..56e4385927b4 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -401,6 +401,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !adapter->scan_processing) && - !adapter->data_sent && - !skb_queue_empty(&adapter->tx_data_q)) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_process_tx_queue(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -@@ -418,6 +424,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !mwifiex_bypass_txlist_empty(adapter) && - !mwifiex_is_tdls_chan_switching - (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_process_bypass_tx(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -@@ -434,6 +446,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !adapter->data_sent && !mwifiex_wmm_lists_empty(adapter) && - !mwifiex_is_tdls_chan_switching - (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_wmm_process_tx(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 5923c5c14c8d..90012cbcfd15 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -986,6 +986,7 @@ struct mwifiex_adapter { - struct timer_list wakeup_timer; - struct mwifiex_hs_config_param hs_cfg; - u8 hs_activated; -+ u8 hs_activated_manually; - u16 hs_activate_wait_q_woken; - wait_queue_head_t hs_activate_wait_q; - u8 event_body[MAX_EVENT_SIZE]; -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -index d3a968ef21ef..76db9a7b8199 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -@@ -396,6 +396,10 @@ mwifiex_cmd_802_11_hs_cfg(struct mwifiex_private *priv, - if (hs_activate) { - hs_cfg->action = cpu_to_le16(HS_ACTIVATE); - hs_cfg->params.hs_activate.resp_ctrl = cpu_to_le16(RESP_NEEDED); -+ -+ adapter->hs_activated_manually = true; -+ mwifiex_dbg(priv->adapter, CMD, -+ "cmd: Activating host sleep manually\n"); - } else { - hs_cfg->action = cpu_to_le16(HS_CONFIGURE); - hs_cfg->params.hs_config.conditions = hscfg_param->conditions; --- -2.32.0 - -From de9c0ec4c8963ea15d62d80b9372a575a27371a7 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:23:05 +0200 -Subject: [PATCH] mwifiex: Add quirk to disable deep sleep with certain - hardware revision - -The 88W8897 pcie card with the hardware revision 20 apparently has a -hardware issue where the card wakes up from deep sleep randomly and very -often, somewhat depending on the card activity, maybe the hardware has a -floating wakeup pin or something. - -Those continuous wakeups prevent the card from entering host sleep when -the computer suspends. And because the host won't answer to events from -the card anymore while it's suspended, the firmwares internal -powersaving state machine seems to get confused and the card can't sleep -anymore at all after that. - -Since we can't work around that hardware bug in the firmware, let's -get the hardware revision string from the firmware and match it with -known bad revisions. Then disable auto deep sleep for those revisions, -which makes sure we no longer get those spurious wakeups. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/main.c | 14 ++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmdresp.c | 16 ++++++++++++++++ - 3 files changed, 31 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index 56e4385927b4..a6be59ac1e27 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -226,6 +226,19 @@ static int mwifiex_process_rx(struct mwifiex_adapter *adapter) - return 0; - } - -+static void maybe_quirk_fw_disable_ds(struct mwifiex_adapter *adapter) -+{ -+ struct mwifiex_private *priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA); -+ struct mwifiex_ver_ext ver_ext; -+ -+ set_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &adapter->work_flags); -+ -+ memset(&ver_ext, 0, sizeof(ver_ext)); -+ ver_ext.version_str_sel = 1; -+ mwifiex_send_cmd(priv, HostCmd_CMD_VERSION_EXT, -+ HostCmd_ACT_GEN_GET, 0, &ver_ext, false); -+} -+ - /* - * The main process. - * -@@ -356,6 +369,7 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - if (adapter->hw_status == MWIFIEX_HW_STATUS_INIT_DONE) { - adapter->hw_status = MWIFIEX_HW_STATUS_READY; - mwifiex_init_fw_complete(adapter); -+ maybe_quirk_fw_disable_ds(adapter); - } - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 90012cbcfd15..1e829d84b1f6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -524,6 +524,7 @@ enum mwifiex_adapter_work_flags { - MWIFIEX_IS_SUSPENDED, - MWIFIEX_IS_HS_CONFIGURED, - MWIFIEX_IS_HS_ENABLING, -+ MWIFIEX_IS_REQUESTING_FW_VEREXT, - }; - - struct mwifiex_band_config { -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -index 6b5d35d9e69f..8e49ebca1847 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -@@ -708,6 +708,22 @@ static int mwifiex_ret_ver_ext(struct mwifiex_private *priv, - { - struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext; - -+ if (test_and_clear_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &priv->adapter->work_flags)) { -+ if (strncmp(ver_ext->version_str, "ChipRev:20, BB:9b(10.00), RF:40(21)", 128) == 0) { -+ struct mwifiex_ds_auto_ds auto_ds = { -+ .auto_ds = DEEP_SLEEP_OFF, -+ }; -+ -+ mwifiex_dbg(priv->adapter, MSG, -+ "Bad HW revision detected, disabling deep sleep\n"); -+ -+ mwifiex_send_cmd(priv, HostCmd_CMD_802_11_PS_MODE_ENH, -+ DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, false); -+ } -+ -+ return 0; -+ } -+ - if (version_ext) { - version_ext->version_str_sel = ver_ext->version_str_sel; - memcpy(version_ext->version_str, ver_ext->version_str, --- -2.32.0 - -From adb758f70e0efad5c491ddf119799d2b95c5bfe1 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 15:17:07 +0100 -Subject: [PATCH] mwifiex: Don't log error on suspend if wake-on-wlan is - disabled - -It's not an error if someone chooses to put their computer to sleep, not -wanting it to wake up because the person next door has just discovered -what a magic packet is. So change the loglevel of this annoying message -from ERROR to INFO. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 8c472b2d982a..153025d1b2fa 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -3497,7 +3497,7 @@ static int mwifiex_cfg80211_suspend(struct wiphy *wiphy, - } - - if (!wowlan) { -- mwifiex_dbg(adapter, ERROR, -+ mwifiex_dbg(adapter, INFO, - "None of the WOWLAN triggers enabled\n"); - ret = 0; - goto done; --- -2.32.0 - -From 4fca5d7bef47e2c6253f4cd2ba7e29ba2f88d7c9 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sun, 28 Mar 2021 21:42:54 +0200 -Subject: [PATCH] mwifiex: Log an error on command failure during key-material - upload - -Sometimes the KEY_MATERIAL command can fail with the 88W8897 firmware -(when this happens exactly seems pretty random). This appears to prevent -the access point from starting, so it seems like a good idea to log an -error in that case. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 ++++++++-- - 1 file changed, 8 insertions(+), 2 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 153025d1b2fa..ef6ce3f63aec 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -519,8 +519,14 @@ mwifiex_cfg80211_set_default_mgmt_key(struct wiphy *wiphy, - encrypt_key.is_igtk_def_key = true; - eth_broadcast_addr(encrypt_key.mac_addr); - -- return mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, -- HostCmd_ACT_GEN_SET, true, &encrypt_key, true); -+ if (mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, -+ HostCmd_ACT_GEN_SET, true, &encrypt_key, true)) { -+ mwifiex_dbg(priv->adapter, ERROR, -+ "Sending KEY_MATERIAL command failed\n"); -+ return -1; -+ } -+ -+ return 0; - } - - /* --- -2.32.0 - -From 24a146510541489f80575e51778a4316fe47558a Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:44:03 +0200 -Subject: [PATCH] mwifiex: Fix an incorrect comment - -We're sending DELBA requests here, not ADDBA requests. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/11n.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c -index 6696bce56178..b0695432b26a 100644 ---- a/drivers/net/wireless/marvell/mwifiex/11n.c -+++ b/drivers/net/wireless/marvell/mwifiex/11n.c -@@ -125,7 +125,7 @@ int mwifiex_ret_11n_delba(struct mwifiex_private *priv, - tx_ba_tbl->ra); - } else { /* - * In case of failure, recreate the deleted stream in case -- * we initiated the ADDBA -+ * we initiated the DELBA - */ - if (!INITIATOR_BIT(del_ba_param_set)) - return 0; --- -2.32.0 - -From 05abf46ed7f5799dcb15bfe5f10fb7898fdba162 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:45:59 +0200 -Subject: [PATCH] mwifiex: Send DELBA requests according to spec - -We're currently failing to set the initiator bit for DELBA requests: -While we set the bit on our del_ba_param_set bitmask, we forget to -actually copy that bitmask over to the command struct, which means we -never actually set the initiator bit. - -Fix that and copy the bitmask over to the host_cmd_ds_11n_delba command -struct. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/11n.c | 5 +++-- - 1 file changed, 3 insertions(+), 2 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c -index b0695432b26a..9ff2058bcd7e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/11n.c -+++ b/drivers/net/wireless/marvell/mwifiex/11n.c -@@ -657,14 +657,15 @@ int mwifiex_send_delba(struct mwifiex_private *priv, int tid, u8 *peer_mac, - uint16_t del_ba_param_set; - - memset(&delba, 0, sizeof(delba)); -- delba.del_ba_param_set = cpu_to_le16(tid << DELBA_TID_POS); - -- del_ba_param_set = le16_to_cpu(delba.del_ba_param_set); -+ del_ba_param_set = tid << DELBA_TID_POS; -+ - if (initiator) - del_ba_param_set |= IEEE80211_DELBA_PARAM_INITIATOR_MASK; - else - del_ba_param_set &= ~IEEE80211_DELBA_PARAM_INITIATOR_MASK; - -+ delba.del_ba_param_set = cpu_to_le16(del_ba_param_set); - memcpy(&delba.peer_mac_addr, peer_mac, ETH_ALEN); - - /* We don't wait for the response of this command */ --- -2.32.0 - -From f812b5aafb09406323c76a965255ff3aec2321a8 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:57:41 +0200 -Subject: [PATCH] mwifiex: Ignore BTCOEX events from the firmware - -The firmware of the pcie 88W8897 chip sends those events very -unreliably, which means we sometimes end up actually capping the window -size while bluetooth is disabled, artifically limiting wifi speeds even -though it's not needed. - -Since we can't fix the firmware, let's just ignore those events, it -seems that the Windows driver also doesn't change the rx/tx block ack -buffer sizes when bluetooth gets enabled or disabled, so this is -consistent with the Windows driver. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/sta_event.c | 4 +--- - 1 file changed, 1 insertion(+), 3 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_event.c b/drivers/net/wireless/marvell/mwifiex/sta_event.c -index 68c63268e2e6..933111a3511c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_event.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_event.c -@@ -1057,9 +1057,7 @@ int mwifiex_process_sta_event(struct mwifiex_private *priv) - adapter->event_skb); - break; - case EVENT_BT_COEX_WLAN_PARA_CHANGE: -- dev_dbg(adapter->dev, "EVENT: BT coex wlan param update\n"); -- mwifiex_bt_coex_wlan_param_update_event(priv, -- adapter->event_skb); -+ dev_dbg(adapter->dev, "EVENT: ignoring BT coex wlan param update\n"); - break; - case EVENT_RXBA_SYNC: - dev_dbg(adapter->dev, "EVENT: RXBA_SYNC\n"); --- -2.32.0 - -From 00da7f84e795a4a5f57f07a45b6c491323e38da7 Mon Sep 17 00:00:00 2001 -From: Brian Norris -Date: Fri, 14 May 2021 19:42:27 -0700 -Subject: [PATCH] mwifiex: bring down link before deleting interface - -We can deadlock when rmmod'ing the driver or going through firmware -reset, because the cfg80211_unregister_wdev() has to bring down the link -for us, ... which then grab the same wiphy lock. - -nl80211_del_interface() already handles a very similar case, with a nice -description: - - /* - * We hold RTNL, so this is safe, without RTNL opencount cannot - * reach 0, and thus the rdev cannot be deleted. - * - * We need to do it for the dev_close(), since that will call - * the netdev notifiers, and we need to acquire the mutex there - * but don't know if we get there from here or from some other - * place (e.g. "ip link set ... down"). - */ - mutex_unlock(&rdev->wiphy.mtx); -... - -Do similarly for mwifiex teardown, by ensuring we bring the link down -first. - -Sample deadlock trace: - -[ 247.103516] INFO: task rmmod:2119 blocked for more than 123 seconds. -[ 247.110630] Not tainted 5.12.4 #5 -[ 247.115796] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. -[ 247.124557] task:rmmod state:D stack: 0 pid: 2119 ppid: 2114 flags:0x00400208 -[ 247.133905] Call trace: -[ 247.136644] __switch_to+0x130/0x170 -[ 247.140643] __schedule+0x714/0xa0c -[ 247.144548] schedule_preempt_disabled+0x88/0xf4 -[ 247.149714] __mutex_lock_common+0x43c/0x750 -[ 247.154496] mutex_lock_nested+0x5c/0x68 -[ 247.158884] cfg80211_netdev_notifier_call+0x280/0x4e0 [cfg80211] -[ 247.165769] raw_notifier_call_chain+0x4c/0x78 -[ 247.170742] call_netdevice_notifiers_info+0x68/0xa4 -[ 247.176305] __dev_close_many+0x7c/0x138 -[ 247.180693] dev_close_many+0x7c/0x10c -[ 247.184893] unregister_netdevice_many+0xfc/0x654 -[ 247.190158] unregister_netdevice_queue+0xb4/0xe0 -[ 247.195424] _cfg80211_unregister_wdev+0xa4/0x204 [cfg80211] -[ 247.201816] cfg80211_unregister_wdev+0x20/0x2c [cfg80211] -[ 247.208016] mwifiex_del_virtual_intf+0xc8/0x188 [mwifiex] -[ 247.214174] mwifiex_uninit_sw+0x158/0x1b0 [mwifiex] -[ 247.219747] mwifiex_remove_card+0x38/0xa0 [mwifiex] -[ 247.225316] mwifiex_pcie_remove+0xd0/0xe0 [mwifiex_pcie] -[ 247.231451] pci_device_remove+0x50/0xe0 -[ 247.235849] device_release_driver_internal+0x110/0x1b0 -[ 247.241701] driver_detach+0x5c/0x9c -[ 247.245704] bus_remove_driver+0x84/0xb8 -[ 247.250095] driver_unregister+0x3c/0x60 -[ 247.254486] pci_unregister_driver+0x2c/0x90 -[ 247.259267] cleanup_module+0x18/0xcdc [mwifiex_pcie] - -Fixes: a05829a7222e ("cfg80211: avoid holding the RTNL when calling the driver") -Cc: stable@vger.kernel.org -Link: https://lore.kernel.org/linux-wireless/98392296-40ee-6300-369c-32e16cff3725@gmail.com/ -Link: https://lore.kernel.org/linux-wireless/ab4d00ce52f32bd8e45ad0448a44737e@bewaar.me/ -Reported-by: Maximilian Luz -Reported-by: dave@bewaar.me -Cc: Johannes Berg -Signed-off-by: Brian Norris -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/main.c | 13 ++++++++++--- - 1 file changed, 10 insertions(+), 3 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index a6be59ac1e27..be40813ffa5c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -1477,11 +1477,18 @@ static void mwifiex_uninit_sw(struct mwifiex_adapter *adapter) - if (!priv) - continue; - rtnl_lock(); -- wiphy_lock(adapter->wiphy); - if (priv->netdev && -- priv->wdev.iftype != NL80211_IFTYPE_UNSPECIFIED) -+ priv->wdev.iftype != NL80211_IFTYPE_UNSPECIFIED) { -+ /* -+ * Close the netdev now, because if we do it later, the -+ * netdev notifiers will need to acquire the wiphy lock -+ * again --> deadlock. -+ */ -+ dev_close(priv->wdev.netdev); -+ wiphy_lock(adapter->wiphy); - mwifiex_del_virtual_intf(adapter->wiphy, &priv->wdev); -- wiphy_unlock(adapter->wiphy); -+ wiphy_unlock(adapter->wiphy); -+ } - rtnl_unlock(); - } - --- -2.32.0 - diff --git a/patches/5.12/0003-ath10k.patch b/patches/5.12/0003-ath10k.patch deleted file mode 100644 index 913524a5d..000000000 --- a/patches/5.12/0003-ath10k.patch +++ /dev/null @@ -1,121 +0,0 @@ -From aac49442ea6beab80251e92f1debcf760d2f34e0 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 27 Feb 2021 00:45:52 +0100 -Subject: [PATCH] ath10k: Add module parameters to override board files - -Some Surface devices, specifically the Surface Go and AMD version of the -Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better -with a different board file, as it seems that the firmeware included -upstream is buggy. - -As it is generally not a good idea to randomly overwrite files, let -alone doing so via packages, we add module parameters to override those -file names in the driver. This allows us to package/deploy the override -via a modprobe.d config. - -Signed-off-by: Maximilian Luz -Patchset: ath10k ---- - drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++ - 1 file changed, 58 insertions(+) - -diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c -index 2f9be182fbfb..84ae17af3f98 100644 ---- a/drivers/net/wireless/ath/ath10k/core.c -+++ b/drivers/net/wireless/ath/ath10k/core.c -@@ -35,6 +35,9 @@ static bool skip_otp; - static bool rawmode; - static bool fw_diag_log; - -+static char *override_board = ""; -+static char *override_board2 = ""; -+ - unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | - BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); - -@@ -47,6 +50,9 @@ module_param(rawmode, bool, 0644); - module_param(fw_diag_log, bool, 0644); - module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); - -+module_param(override_board, charp, 0644); -+module_param(override_board2, charp, 0644); -+ - MODULE_PARM_DESC(debug_mask, "Debugging mask"); - MODULE_PARM_DESC(uart_print, "Uart target debugging"); - MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); -@@ -55,6 +61,9 @@ MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); - MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); - MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); - -+MODULE_PARM_DESC(override_board, "Override for board.bin file"); -+MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); -+ - static const struct ath10k_hw_params ath10k_hw_params_list[] = { - { - .id = QCA988X_HW_2_0_VERSION, -@@ -826,6 +835,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) - return 0; - } - -+static const char *ath10k_override_board_fw_file(struct ath10k *ar, -+ const char *file) -+{ -+ if (strcmp(file, "board.bin") == 0) { -+ if (strcmp(override_board, "") == 0) -+ return file; -+ -+ if (strcmp(override_board, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", -+ override_board); -+ -+ return override_board; -+ } -+ -+ if (strcmp(file, "board-2.bin") == 0) { -+ if (strcmp(override_board2, "") == 0) -+ return file; -+ -+ if (strcmp(override_board2, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", -+ override_board2); -+ -+ return override_board2; -+ } -+ -+ return file; -+} -+ - static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - const char *dir, - const char *file) -@@ -840,6 +885,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - if (dir == NULL) - dir = "."; - -+ /* HACK: Override board.bin and board-2.bin files if specified. -+ * -+ * Some Surface devices perform better with a different board -+ * configuration. To this end, one would need to replace the board.bin -+ * file with the modified config and remove the board-2.bin file. -+ * Unfortunately, that's not a solution that we can easily package. So -+ * we add module options to perform these overrides here. -+ */ -+ -+ file = ath10k_override_board_fw_file(ar, file); -+ if (!file) -+ return ERR_PTR(-ENOENT); -+ - snprintf(filename, sizeof(filename), "%s/%s", dir, file); - ret = firmware_request_nowarn(&fw, filename, ar->dev); - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", --- -2.32.0 - diff --git a/patches/5.12/0004-ipts.patch b/patches/5.12/0004-ipts.patch deleted file mode 100644 index afde2185a..000000000 --- a/patches/5.12/0004-ipts.patch +++ /dev/null @@ -1,1503 +0,0 @@ -From 81047d4ca369df66b2fcf5bdc2901b42a7f2aedc Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 30 Jul 2020 13:21:53 +0200 -Subject: [PATCH] misc: mei: Add missing IPTS device IDs - -Patchset: ipts ---- - drivers/misc/mei/hw-me-regs.h | 1 + - drivers/misc/mei/pci-me.c | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h -index cb34925e10f1..2b3f8073a3ec 100644 ---- a/drivers/misc/mei/hw-me-regs.h -+++ b/drivers/misc/mei/hw-me-regs.h -@@ -92,6 +92,7 @@ - #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ - - #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ -+#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ - - #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ - -diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c -index c3393b383e59..0098f98426c1 100644 ---- a/drivers/misc/mei/pci-me.c -+++ b/drivers/misc/mei/pci-me.c -@@ -96,6 +96,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { - {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, -+ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_H, MEI_ME_PCH15_SPS_CFG)}, --- -2.32.0 - -From ec54d9b5d0a9406fb4066409117e3fe86f70ab77 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 6 Aug 2020 11:20:41 +0200 -Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus - -Based on linux-surface/intel-precise-touch@3f362c - -Signed-off-by: Dorian Stoll -Patchset: ipts ---- - drivers/misc/Kconfig | 1 + - drivers/misc/Makefile | 1 + - drivers/misc/ipts/Kconfig | 17 ++ - drivers/misc/ipts/Makefile | 12 ++ - drivers/misc/ipts/context.h | 47 +++++ - drivers/misc/ipts/control.c | 113 +++++++++++ - drivers/misc/ipts/control.h | 24 +++ - drivers/misc/ipts/mei.c | 125 ++++++++++++ - drivers/misc/ipts/protocol.h | 347 ++++++++++++++++++++++++++++++++++ - drivers/misc/ipts/receiver.c | 224 ++++++++++++++++++++++ - drivers/misc/ipts/receiver.h | 16 ++ - drivers/misc/ipts/resources.c | 128 +++++++++++++ - drivers/misc/ipts/resources.h | 17 ++ - drivers/misc/ipts/uapi.c | 208 ++++++++++++++++++++ - drivers/misc/ipts/uapi.h | 47 +++++ - 15 files changed, 1327 insertions(+) - create mode 100644 drivers/misc/ipts/Kconfig - create mode 100644 drivers/misc/ipts/Makefile - create mode 100644 drivers/misc/ipts/context.h - create mode 100644 drivers/misc/ipts/control.c - create mode 100644 drivers/misc/ipts/control.h - create mode 100644 drivers/misc/ipts/mei.c - create mode 100644 drivers/misc/ipts/protocol.h - create mode 100644 drivers/misc/ipts/receiver.c - create mode 100644 drivers/misc/ipts/receiver.h - create mode 100644 drivers/misc/ipts/resources.c - create mode 100644 drivers/misc/ipts/resources.h - create mode 100644 drivers/misc/ipts/uapi.c - create mode 100644 drivers/misc/ipts/uapi.h - -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index f532c59bb59b..cc973f55f5ca 100644 ---- a/drivers/misc/Kconfig -+++ b/drivers/misc/Kconfig -@@ -461,4 +461,5 @@ source "drivers/misc/bcm-vk/Kconfig" - source "drivers/misc/cardreader/Kconfig" - source "drivers/misc/habanalabs/Kconfig" - source "drivers/misc/uacce/Kconfig" -+source "drivers/misc/ipts/Kconfig" - endmenu -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index 99b6f15a3c70..9470a93d7fa4 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -56,3 +56,4 @@ obj-$(CONFIG_HABANA_AI) += habanalabs/ - obj-$(CONFIG_UACCE) += uacce/ - obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o - obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o -+obj-$(CONFIG_MISC_IPTS) += ipts/ -diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig -new file mode 100644 -index 000000000000..83e2a930c396 ---- /dev/null -+++ b/drivers/misc/ipts/Kconfig -@@ -0,0 +1,17 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+ -+config MISC_IPTS -+ tristate "Intel Precise Touch & Stylus" -+ depends on INTEL_MEI -+ help -+ Say Y here if your system has a touchscreen using Intels -+ Precise Touch & Stylus (IPTS) technology. -+ -+ If unsure say N. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ipts. -+ -+ Building this driver alone will not give you a working touchscreen. -+ It only exposed a userspace API that can be used by a daemon to -+ receive and process data from the touchscreen hardware. -diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile -new file mode 100644 -index 000000000000..8f58b9adbc94 ---- /dev/null -+++ b/drivers/misc/ipts/Makefile -@@ -0,0 +1,12 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+# -+# Makefile for the IPTS touchscreen driver -+# -+ -+obj-$(CONFIG_MISC_IPTS) += ipts.o -+ipts-objs := control.o -+ipts-objs += mei.o -+ipts-objs += receiver.o -+ipts-objs += resources.o -+ipts-objs += uapi.o -+ -diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h -new file mode 100644 -index 000000000000..f4b06a2d3f72 ---- /dev/null -+++ b/drivers/misc/ipts/context.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTEXT_H_ -+#define _IPTS_CONTEXT_H_ -+ -+#include -+#include -+#include -+#include -+ -+#include "protocol.h" -+ -+enum ipts_host_status { -+ IPTS_HOST_STATUS_STARTING, -+ IPTS_HOST_STATUS_STARTED, -+ IPTS_HOST_STATUS_STOPPING, -+ IPTS_HOST_STATUS_STOPPED, -+}; -+ -+struct ipts_buffer_info { -+ u8 *address; -+ dma_addr_t dma_address; -+}; -+ -+struct ipts_context { -+ struct mei_cl_device *cldev; -+ struct device *dev; -+ -+ bool restart; -+ enum ipts_host_status status; -+ struct ipts_get_device_info_rsp device_info; -+ -+ struct ipts_buffer_info data[IPTS_BUFFERS]; -+ struct ipts_buffer_info doorbell; -+ -+ struct ipts_buffer_info feedback[IPTS_BUFFERS]; -+ struct ipts_buffer_info workqueue; -+ struct ipts_buffer_info host2me; -+}; -+ -+#endif /* _IPTS_CONTEXT_H_ */ -diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c -new file mode 100644 -index 000000000000..a1d1f97a13d7 ---- /dev/null -+++ b/drivers/misc/ipts/control.c -@@ -0,0 +1,113 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+#include "protocol.h" -+#include "resources.h" -+#include "uapi.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload, -+ size_t size) -+{ -+ int ret; -+ struct ipts_command cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_command)); -+ cmd.code = code; -+ -+ if (payload && size > 0) -+ memcpy(&cmd.payload, payload, size); -+ -+ ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size); -+ if (ret >= 0) -+ return 0; -+ -+ /* -+ * During shutdown the device might get pulled away from below our feet. -+ * Dont log an error in this case, because it will confuse people. -+ */ -+ if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING) -+ dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret); -+ -+ return ret; -+} -+ -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) -+{ -+ struct ipts_feedback_cmd cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); -+ cmd.buffer = buffer; -+ -+ return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, -+ sizeof(struct ipts_feedback_cmd)); -+} -+ -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value) -+{ -+ struct ipts_feedback_buffer *feedback; -+ -+ memset(ipts->host2me.address, 0, ipts->device_info.feedback_size); -+ feedback = (struct ipts_feedback_buffer *)ipts->host2me.address; -+ -+ feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; -+ feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; -+ feedback->buffer = IPTS_HOST2ME_BUFFER; -+ feedback->size = 2; -+ feedback->payload[0] = report; -+ feedback->payload[1] = value; -+ -+ return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER); -+} -+ -+int ipts_control_start(struct ipts_context *ipts) -+{ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Starting IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STARTING; -+ ipts->restart = false; -+ -+ ipts_uapi_link(ipts); -+ return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); -+} -+ -+int ipts_control_stop(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPING) -+ return -EBUSY; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Stopping IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STOPPING; -+ -+ ipts_uapi_unlink(); -+ ipts_resources_free(ipts); -+ -+ ret = ipts_control_send_feedback(ipts, 0); -+ if (ret == -ENODEV) -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ return ret; -+} -+ -+int ipts_control_restart(struct ipts_context *ipts) -+{ -+ if (ipts->restart) -+ return -EBUSY; -+ -+ ipts->restart = true; -+ return ipts_control_stop(ipts); -+} -diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h -new file mode 100644 -index 000000000000..2c44e9e0e99f ---- /dev/null -+++ b/drivers/misc/ipts/control.h -@@ -0,0 +1,24 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTROL_H_ -+#define _IPTS_CONTROL_H_ -+ -+#include -+ -+#include "context.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, -+ size_t size); -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value); -+int ipts_control_start(struct ipts_context *ipts); -+int ipts_control_restart(struct ipts_context *ipts); -+int ipts_control_stop(struct ipts_context *ipts); -+ -+#endif /* _IPTS_CONTROL_H_ */ -diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c -new file mode 100644 -index 000000000000..59ecf13e00d2 ---- /dev/null -+++ b/drivers/misc/ipts/mei.c -@@ -0,0 +1,125 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "receiver.h" -+#include "uapi.h" -+ -+static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev) -+{ -+ int ret; -+ -+ ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)); -+ if (!ret) -+ return 0; -+ -+ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); -+} -+ -+static int ipts_mei_probe(struct mei_cl_device *cldev, -+ const struct mei_cl_device_id *id) -+{ -+ int ret; -+ struct ipts_context *ipts; -+ -+ if (ipts_mei_set_dma_mask(cldev)) { -+ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n"); -+ return -EFAULT; -+ } -+ -+ ret = mei_cldev_enable(cldev); -+ if (ret) { -+ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); -+ return ret; -+ } -+ -+ ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); -+ if (!ipts) { -+ mei_cldev_disable(cldev); -+ return -ENOMEM; -+ } -+ -+ ipts->cldev = cldev; -+ ipts->dev = &cldev->dev; -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ mei_cldev_set_drvdata(cldev, ipts); -+ mei_cldev_register_rx_cb(cldev, ipts_receiver_callback); -+ -+ return ipts_control_start(ipts); -+} -+ -+static void ipts_mei_remove(struct mei_cl_device *cldev) -+{ -+ int i; -+ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); -+ -+ ipts_control_stop(ipts); -+ -+ for (i = 0; i < 20; i++) { -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ break; -+ -+ msleep(25); -+ } -+ -+ mei_cldev_disable(cldev); -+ kfree(ipts); -+} -+ -+static struct mei_cl_device_id ipts_mei_device_id_table[] = { -+ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, -+ {}, -+}; -+MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); -+ -+static struct mei_cl_driver ipts_mei_driver = { -+ .id_table = ipts_mei_device_id_table, -+ .name = "ipts", -+ .probe = ipts_mei_probe, -+ .remove = ipts_mei_remove, -+}; -+ -+static int __init ipts_mei_init(void) -+{ -+ int ret; -+ -+ ret = ipts_uapi_init(); -+ if (ret) -+ return ret; -+ -+ ret = mei_cldev_driver_register(&ipts_mei_driver); -+ if (ret) { -+ ipts_uapi_free(); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static void __exit ipts_mei_exit(void) -+{ -+ mei_cldev_driver_unregister(&ipts_mei_driver); -+ ipts_uapi_free(); -+} -+ -+MODULE_DESCRIPTION("IPTS touchscreen driver"); -+MODULE_AUTHOR("Dorian Stoll "); -+MODULE_LICENSE("GPL"); -+ -+module_init(ipts_mei_init); -+module_exit(ipts_mei_exit); -diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h -new file mode 100644 -index 000000000000..c3458904a94d ---- /dev/null -+++ b/drivers/misc/ipts/protocol.h -@@ -0,0 +1,347 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_PROTOCOL_H_ -+#define _IPTS_PROTOCOL_H_ -+ -+#include -+ -+/* -+ * The MEI client ID for IPTS functionality. -+ */ -+#define IPTS_MEI_UUID \ -+ UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, \ -+ 0x02, 0xae, 0x04) -+ -+/* -+ * Queries the device for vendor specific information. -+ * -+ * The command must not contain any payload. -+ * The response will contain struct ipts_get_device_info_rsp as payload. -+ */ -+#define IPTS_CMD_GET_DEVICE_INFO 0x00000001 -+#define IPTS_RSP_GET_DEVICE_INFO 0x80000001 -+ -+/* -+ * Sets the mode that IPTS will operate in. -+ * -+ * The command must contain struct ipts_set_mode_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MODE 0x00000002 -+#define IPTS_RSP_SET_MODE 0x80000002 -+ -+/* -+ * Configures the memory buffers that the ME will use -+ * for passing data to the host. -+ * -+ * The command must contain struct ipts_set_mem_window_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MEM_WINDOW 0x00000003 -+#define IPTS_RSP_SET_MEM_WINDOW 0x80000003 -+ -+/* -+ * Signals that the host is ready to receive data to the ME. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_READY_FOR_DATA 0x00000005 -+#define IPTS_RSP_READY_FOR_DATA 0x80000005 -+ -+/* -+ * Signals that a buffer can be refilled to the ME. -+ * -+ * The command must contain struct ipts_feedback_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_FEEDBACK 0x00000006 -+#define IPTS_RSP_FEEDBACK 0x80000006 -+ -+/* -+ * Resets the data flow from the ME to the hosts and -+ * clears the buffers that were set with SET_MEM_WINDOW. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007 -+#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007 -+ -+/* -+ * Instructs the ME to reset the touch sensor. -+ * -+ * The command must contain struct ipts_reset_sensor_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_RESET_SENSOR 0x0000000B -+#define IPTS_RSP_RESET_SENSOR 0x8000000B -+ -+/** -+ * enum ipts_status - Possible status codes returned by IPTS commands. -+ * @IPTS_STATUS_SUCCESS: Operation completed successfully. -+ * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. -+ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. -+ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. -+ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. -+ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. -+ * The host must wait for a response before sending another -+ * command of the same type. -+ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it -+ * has not been initialized yet, or the system is improperly -+ * configured. -+ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. -+ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. -+ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. -+ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. -+ * The host can ignore this error and attempt to continue. -+ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. -+ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. -+ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. -+ * @IPTS_STATUS_TIMEOUT: The operation timed out. -+ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. -+ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. -+ * Further progress is not possible. -+ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. -+ * The host can attempt to continue. -+ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. -+ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. -+ */ -+enum ipts_status { -+ IPTS_STATUS_SUCCESS = 0, -+ IPTS_STATUS_INVALID_PARAMS = 1, -+ IPTS_STATUS_ACCESS_DENIED = 2, -+ IPTS_STATUS_CMD_SIZE_ERROR = 3, -+ IPTS_STATUS_NOT_READY = 4, -+ IPTS_STATUS_REQUEST_OUTSTANDING = 5, -+ IPTS_STATUS_NO_SENSOR_FOUND = 6, -+ IPTS_STATUS_OUT_OF_MEMORY = 7, -+ IPTS_STATUS_INTERNAL_ERROR = 8, -+ IPTS_STATUS_SENSOR_DISABLED = 9, -+ IPTS_STATUS_COMPAT_CHECK_FAIL = 10, -+ IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, -+ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, -+ IPTS_STATUS_RESET_FAILED = 13, -+ IPTS_STATUS_TIMEOUT = 14, -+ IPTS_STATUS_TEST_MODE_FAIL = 15, -+ IPTS_STATUS_SENSOR_FAIL_FATAL = 16, -+ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, -+ IPTS_STATUS_INVALID_DEVICE_CAPS = 18, -+ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, -+}; -+ -+/* -+ * The amount of buffers that is used for IPTS -+ */ -+#define IPTS_BUFFERS 16 -+ -+/* -+ * The special buffer ID that is used for direct host2me feedback. -+ */ -+#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS -+ -+/** -+ * enum ipts_mode - Operation mode for IPTS hardware -+ * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. -+ * The data is received as a HID report with ID 64. -+ * @IPTS_MODE_MULTITOUCH: The "proper" operation mode for IPTS. It will return -+ * stylus data as well as capacitive heatmap touch data. -+ * This data needs to be processed in userspace. -+ */ -+enum ipts_mode { -+ IPTS_MODE_SINGLETOUCH = 0, -+ IPTS_MODE_MULTITOUCH = 1, -+}; -+ -+/** -+ * struct ipts_set_mode_cmd - Payload for the SET_MODE command. -+ * @mode: The mode that IPTS should operate in. -+ */ -+struct ipts_set_mode_cmd { -+ enum ipts_mode mode; -+ u8 reserved[12]; -+} __packed; -+ -+#define IPTS_WORKQUEUE_SIZE 8192 -+#define IPTS_WORKQUEUE_ITEM_SIZE 16 -+ -+/** -+ * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command. -+ * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses. -+ * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses. -+ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. -+ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. -+ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. -+ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. -+ * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses. -+ * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses. -+ * @host2me_addr_lower: Lower 32 bits of the host2me buffer address. -+ * @host2me_addr_upper: Upper 32 bits of the host2me buffer address. -+ * @workqueue_item_size: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) -+ * @workqueue_size: Magic value. (IPTS_WORKQUEUE_SIZE) -+ * -+ * The data buffers are buffers that get filled with touch data by the ME. -+ * The doorbell buffer is a u32 that gets incremented by the ME once a data -+ * buffer has been filled with new data. -+ * -+ * The other buffers are required for using GuC submission with binary -+ * firmware. Since support for GuC submission has been dropped from i915, -+ * they are not used anymore, but they need to be allocated and passed, -+ * otherwise the hardware will refuse to start. -+ */ -+struct ipts_set_mem_window_cmd { -+ u32 data_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 data_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 workqueue_addr_lower; -+ u32 workqueue_addr_upper; -+ u32 doorbell_addr_lower; -+ u32 doorbell_addr_upper; -+ u32 feedback_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 feedback_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 host2me_addr_lower; -+ u32 host2me_addr_upper; -+ u32 host2me_size; -+ u8 reserved1; -+ u8 workqueue_item_size; -+ u16 workqueue_size; -+ u8 reserved[32]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_cmd - Payload for the FEEDBACK command. -+ * @buffer: The buffer that the ME should refill. -+ */ -+struct ipts_feedback_cmd { -+ u32 buffer; -+ u8 reserved[12]; -+} __packed; -+ -+/** -+ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. -+ */ -+enum ipts_feedback_cmd_type { -+ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, -+ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, -+ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, -+}; -+ -+/** -+ * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains. -+ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. -+ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. -+ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. -+ */ -+enum ipts_feedback_data_type { -+ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, -+ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, -+ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, -+ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, -+ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, -+}; -+ -+/** -+ * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer. -+ * @cmd_type: A command that should be executed on the sensor. -+ * @size: The size of the payload to be written. -+ * @buffer: The ID of the buffer that contains this feedback data. -+ * @protocol: The protocol version of the EDS. -+ * @data_type: The type of payload that the buffer contains. -+ * @spi_offset: The offset at which to write the payload data. -+ * @payload: Payload for the feedback command, or 0 if no payload is sent. -+ */ -+struct ipts_feedback_buffer { -+ enum ipts_feedback_cmd_type cmd_type; -+ u32 size; -+ u32 buffer; -+ u32 protocol; -+ enum ipts_feedback_data_type data_type; -+ u32 spi_offset; -+ u8 reserved[40]; -+ u8 payload[]; -+} __packed; -+ -+/** -+ * enum ipts_reset_type - Possible ways of resetting the touch sensor -+ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. -+ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. -+ */ -+enum ipts_reset_type { -+ IPTS_RESET_TYPE_HARD = 0, -+ IPTS_RESET_TYPE_SOFT = 1, -+}; -+ -+/** -+ * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. -+ * @type: What type of reset should be performed. -+ */ -+struct ipts_reset_sensor_cmd { -+ enum ipts_reset_type type; -+ u8 reserved[4]; -+} __packed; -+ -+/** -+ * struct ipts_command - A message sent from the host to the ME. -+ * @code: The message code describing the command. (see IPTS_CMD_*) -+ * @payload: Payload for the command, or 0 if no payload is required. -+ */ -+struct ipts_command { -+ u32 code; -+ u8 payload[320]; -+} __packed; -+ -+/** -+ * struct ipts_device_info - Payload for the GET_DEVICE_INFO response. -+ * @vendor_id: Vendor ID of the touch sensor. -+ * @device_id: Device ID of the touch sensor. -+ * @hw_rev: Hardware revision of the touch sensor. -+ * @fw_rev: Firmware revision of the touch sensor. -+ * @data_size: Required size of one data buffer. -+ * @feedback_size: Required size of one feedback buffer. -+ * @mode: Current operation mode of IPTS. -+ * @max_contacts: The amount of concurrent touches supported by the sensor. -+ */ -+struct ipts_get_device_info_rsp { -+ u16 vendor_id; -+ u16 device_id; -+ u32 hw_rev; -+ u32 fw_rev; -+ u32 data_size; -+ u32 feedback_size; -+ enum ipts_mode mode; -+ u8 max_contacts; -+ u8 reserved[19]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_rsp - Payload for the FEEDBACK response. -+ * @buffer: The buffer that has received feedback. -+ */ -+struct ipts_feedback_rsp { -+ u32 buffer; -+} __packed; -+ -+/** -+ * struct ipts_response - A message sent from the ME to the host. -+ * @code: The message code describing the response. (see IPTS_RSP_*) -+ * @status: The status code returned by the command. -+ * @payload: Payload returned by the command. -+ */ -+struct ipts_response { -+ u32 code; -+ enum ipts_status status; -+ u8 payload[80]; -+} __packed; -+ -+#endif /* _IPTS_PROTOCOL_H_ */ -diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c -new file mode 100644 -index 000000000000..23dca13c2139 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.c -@@ -0,0 +1,224 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "resources.h" -+ -+/* -+ * Temporary parameter to guard gen7 multitouch mode. -+ * Remove once gen7 has stable iptsd support. -+ */ -+static bool gen7mt; -+module_param(gen7mt, bool, 0644); -+ -+static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_set_mode_cmd cmd; -+ -+ memcpy(&ipts->device_info, rsp->payload, -+ sizeof(struct ipts_get_device_info_rsp)); -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd)); -+ cmd.mode = IPTS_MODE_MULTITOUCH; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd, -+ sizeof(struct ipts_set_mode_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mode(struct ipts_context *ipts) -+{ -+ int i, ret; -+ struct ipts_set_mem_window_cmd cmd; -+ -+ ret = ipts_resources_alloc(ipts); -+ if (ret) { -+ dev_err(ipts->dev, "Failed to allocate resources\n"); -+ return ret; -+ } -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ cmd.data_buffer_addr_lower[i] = -+ lower_32_bits(ipts->data[i].dma_address); -+ -+ cmd.data_buffer_addr_upper[i] = -+ upper_32_bits(ipts->data[i].dma_address); -+ -+ cmd.feedback_buffer_addr_lower[i] = -+ lower_32_bits(ipts->feedback[i].dma_address); -+ -+ cmd.feedback_buffer_addr_upper[i] = -+ upper_32_bits(ipts->feedback[i].dma_address); -+ } -+ -+ cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); -+ cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); -+ -+ cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); -+ cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); -+ -+ cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); -+ cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); -+ -+ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; -+ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, -+ sizeof(struct ipts_set_mem_window_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ dev_info(ipts->dev, "Device %04hX:%04hX ready\n", -+ ipts->device_info.vendor_id, ipts->device_info.device_id); -+ ipts->status = IPTS_HOST_STATUS_STARTED; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); -+ if (ret) -+ return ret; -+ -+ if (!gen7mt) -+ return 0; -+ -+ return ipts_control_set_feature(ipts, 0x5, 0x1); -+} -+ -+static int ipts_receiver_handle_feedback(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_feedback_rsp feedback; -+ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPING) -+ return 0; -+ -+ memcpy(&feedback, rsp->payload, sizeof(feedback)); -+ -+ if (feedback.buffer < IPTS_BUFFERS - 1) -+ return ipts_control_send_feedback(ipts, feedback.buffer + 1); -+ -+ return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); -+} -+ -+static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) -+{ -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ if (ipts->restart) -+ return ipts_control_start(ipts); -+ -+ return 0; -+} -+ -+static bool ipts_receiver_sensor_was_reset(u32 status) -+{ -+ return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || -+ status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; -+} -+ -+static bool ipts_receiver_handle_error(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ bool error; -+ -+ switch (rsp->status) { -+ case IPTS_STATUS_SUCCESS: -+ case IPTS_STATUS_COMPAT_CHECK_FAIL: -+ error = false; -+ break; -+ case IPTS_STATUS_INVALID_PARAMS: -+ error = rsp->code != IPTS_RSP_FEEDBACK; -+ break; -+ case IPTS_STATUS_SENSOR_DISABLED: -+ error = ipts->status != IPTS_HOST_STATUS_STOPPING; -+ break; -+ default: -+ error = true; -+ break; -+ } -+ -+ if (!error) -+ return false; -+ -+ dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code, -+ rsp->status); -+ -+ if (ipts_receiver_sensor_was_reset(rsp->status)) { -+ dev_err(ipts->dev, "Sensor was reset\n"); -+ -+ if (ipts_control_restart(ipts)) -+ dev_err(ipts->dev, "Failed to restart IPTS\n"); -+ } -+ -+ return true; -+} -+ -+static void ipts_receiver_handle_response(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ int ret; -+ -+ if (ipts_receiver_handle_error(ipts, rsp)) -+ return; -+ -+ switch (rsp->code) { -+ case IPTS_RSP_GET_DEVICE_INFO: -+ ret = ipts_receiver_handle_get_device_info(ipts, rsp); -+ break; -+ case IPTS_RSP_SET_MODE: -+ ret = ipts_receiver_handle_set_mode(ipts); -+ break; -+ case IPTS_RSP_SET_MEM_WINDOW: -+ ret = ipts_receiver_handle_set_mem_window(ipts); -+ break; -+ case IPTS_RSP_FEEDBACK: -+ ret = ipts_receiver_handle_feedback(ipts, rsp); -+ break; -+ case IPTS_RSP_CLEAR_MEM_WINDOW: -+ ret = ipts_receiver_handle_clear_mem_window(ipts); -+ break; -+ default: -+ ret = 0; -+ break; -+ } -+ -+ if (!ret) -+ return; -+ -+ dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n", -+ rsp->code, ret); -+ -+ if (ipts_control_stop(ipts)) -+ dev_err(ipts->dev, "Failed to stop IPTS\n"); -+} -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev) -+{ -+ int ret; -+ struct ipts_response rsp; -+ struct ipts_context *ipts; -+ -+ ipts = mei_cldev_get_drvdata(cldev); -+ -+ ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response)); -+ if (ret <= 0) { -+ dev_err(ipts->dev, "Error while reading response: %d\n", ret); -+ return; -+ } -+ -+ ipts_receiver_handle_response(ipts, &rsp); -+} -diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h -new file mode 100644 -index 000000000000..7f075afa7ef8 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.h -@@ -0,0 +1,16 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RECEIVER_H_ -+#define _IPTS_RECEIVER_H_ -+ -+#include -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev); -+ -+#endif /* _IPTS_RECEIVER_H_ */ -diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c -new file mode 100644 -index 000000000000..8e3a2409e438 ---- /dev/null -+++ b/drivers/misc/ipts/resources.c -@@ -0,0 +1,128 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+ -+void ipts_resources_free(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, data_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ if (ipts->doorbell.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->doorbell.address, -+ ipts->doorbell.dma_address); -+ -+ ipts->doorbell.address = NULL; -+ ipts->doorbell.dma_address = 0; -+ } -+ -+ if (ipts->workqueue.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->workqueue.address, -+ ipts->workqueue.dma_address); -+ -+ ipts->workqueue.address = NULL; -+ ipts->workqueue.dma_address = 0; -+ } -+ -+ if (ipts->host2me.address) { -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ ipts->host2me.address, -+ ipts->host2me.dma_address); -+ -+ ipts->host2me.address = NULL; -+ ipts->host2me.dma_address = 0; -+ } -+} -+ -+int ipts_resources_alloc(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, data_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ ipts->doorbell.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->doorbell.dma_address, GFP_KERNEL); -+ -+ if (!ipts->doorbell.address) -+ goto release_resources; -+ -+ ipts->workqueue.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->workqueue.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ ipts->host2me.address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &ipts->host2me.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ return 0; -+ -+release_resources: -+ -+ ipts_resources_free(ipts); -+ return -ENOMEM; -+} -diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h -new file mode 100644 -index 000000000000..fdac0eee9156 ---- /dev/null -+++ b/drivers/misc/ipts/resources.h -@@ -0,0 +1,17 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RESOURCES_H_ -+#define _IPTS_RESOURCES_H_ -+ -+#include "context.h" -+ -+int ipts_resources_alloc(struct ipts_context *ipts); -+void ipts_resources_free(struct ipts_context *ipts); -+ -+#endif /* _IPTS_RESOURCES_H_ */ -diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c -new file mode 100644 -index 000000000000..598f0710ad64 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.c -@@ -0,0 +1,208 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "uapi.h" -+ -+struct ipts_uapi uapi; -+ -+static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count, -+ loff_t *offset) -+{ -+ int buffer; -+ int maxbytes; -+ struct ipts_context *ipts = uapi.ipts; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ maxbytes = ipts->device_info.data_size - *offset; -+ if (maxbytes <= 0 || count > maxbytes) -+ return -EINVAL; -+ -+ if (copy_to_user(buf, ipts->data[buffer].address + *offset, count)) -+ return -EFAULT; -+ -+ return count; -+} -+ -+static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ u8 ready = 0; -+ -+ if (ipts) -+ ready = ipts->status == IPTS_HOST_STATUS_STARTED; -+ -+ if (copy_to_user(buffer, &ready, sizeof(u8))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ struct ipts_device_info info; -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ info.vendor = ipts->device_info.vendor_id; -+ info.product = ipts->device_info.device_id; -+ info.version = ipts->device_info.fw_rev; -+ info.buffer_size = ipts->device_info.data_size; -+ info.max_contacts = ipts->device_info.max_contacts; -+ -+ if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts, -+ struct file *file) -+{ -+ int ret; -+ u32 buffer; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ ret = ipts_control_send_feedback(ipts, buffer); -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) -+{ -+ int ret; -+ struct ipts_reset_sensor_cmd cmd; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); -+ cmd.type = IPTS_RESET_TYPE_SOFT; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, -+ sizeof(struct ipts_reset_sensor_cmd)); -+ -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) -+{ -+ struct ipts_context *ipts = uapi.ipts; -+ -+ switch (cmd) { -+ case IPTS_IOCTL_GET_DEVICE_READY: -+ return ipts_uapi_ioctl_get_device_ready(ipts, arg); -+ case IPTS_IOCTL_GET_DEVICE_INFO: -+ return ipts_uapi_ioctl_get_device_info(ipts, arg); -+ case IPTS_IOCTL_GET_DOORBELL: -+ return ipts_uapi_ioctl_get_doorbell(ipts, arg); -+ case IPTS_IOCTL_SEND_FEEDBACK: -+ return ipts_uapi_ioctl_send_feedback(ipts, file); -+ case IPTS_IOCTL_SEND_RESET: -+ return ipts_uapi_ioctl_send_reset(ipts); -+ default: -+ return -ENOTTY; -+ } -+} -+ -+static const struct file_operations ipts_uapi_fops = { -+ .owner = THIS_MODULE, -+ .read = ipts_uapi_read, -+ .unlocked_ioctl = ipts_uapi_ioctl, -+#ifdef CONFIG_COMPAT -+ .compat_ioctl = ipts_uapi_ioctl, -+#endif -+}; -+ -+void ipts_uapi_link(struct ipts_context *ipts) -+{ -+ uapi.ipts = ipts; -+} -+ -+void ipts_uapi_unlink(void) -+{ -+ uapi.ipts = NULL; -+} -+ -+int ipts_uapi_init(void) -+{ -+ int i, major; -+ -+ alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts"); -+ uapi.class = class_create(THIS_MODULE, "ipts"); -+ -+ major = MAJOR(uapi.dev); -+ -+ cdev_init(&uapi.cdev, &ipts_uapi_fops); -+ uapi.cdev.owner = THIS_MODULE; -+ cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ device_create(uapi.class, NULL, MKDEV(major, i), NULL, -+ "ipts/%d", i); -+ } -+ -+ return 0; -+} -+ -+void ipts_uapi_free(void) -+{ -+ int i; -+ int major; -+ -+ major = MAJOR(uapi.dev); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) -+ device_destroy(uapi.class, MKDEV(major, i)); -+ -+ cdev_del(&uapi.cdev); -+ -+ unregister_chrdev_region(MKDEV(major, 0), MINORMASK); -+ class_destroy(uapi.class); -+} -diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h -new file mode 100644 -index 000000000000..53fb86a88f97 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_UAPI_H_ -+#define _IPTS_UAPI_H_ -+ -+#include -+ -+#include "context.h" -+ -+struct ipts_uapi { -+ dev_t dev; -+ struct class *class; -+ struct cdev cdev; -+ -+ struct ipts_context *ipts; -+}; -+ -+struct ipts_device_info { -+ __u16 vendor; -+ __u16 product; -+ __u32 version; -+ __u32 buffer_size; -+ __u8 max_contacts; -+ -+ /* For future expansion */ -+ __u8 reserved[19]; -+}; -+ -+#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8) -+#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info) -+#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) -+#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04) -+#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) -+ -+void ipts_uapi_link(struct ipts_context *ipts); -+void ipts_uapi_unlink(void); -+ -+int ipts_uapi_init(void); -+void ipts_uapi_free(void); -+ -+#endif /* _IPTS_UAPI_H_ */ --- -2.32.0 - diff --git a/patches/5.12/0005-surface-sam-over-hid.patch b/patches/5.12/0005-surface-sam-over-hid.patch deleted file mode 100644 index 19b6b9bb3..000000000 --- a/patches/5.12/0005-surface-sam-over-hid.patch +++ /dev/null @@ -1,335 +0,0 @@ -From 2bf5d7b498510c16129bc2ee593cad09cef7d54d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jul 2020 17:19:53 +0200 -Subject: [PATCH] i2c: acpi: Implement RawBytes read access - -Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C -device via a generic serial bus operation region and RawBytes read -access. On the Surface Book 1, this access is required to turn on (and -off) the discrete GPU. - -Multiple things are to note here: - -a) The RawBytes access is device/driver dependent. The ACPI - specification states: - - > Raw accesses assume that the writer has knowledge of the bus that - > the access is made over and the device that is being accessed. The - > protocol may only ensure that the buffer is transmitted to the - > appropriate driver, but the driver must be able to interpret the - > buffer to communicate to a register. - - Thus this implementation may likely not work on other devices - accessing I2C via the RawBytes accessor type. - -b) The MSHW0030 I2C device is an HID-over-I2C device which seems to - serve multiple functions: - - 1. It is the main access point for the legacy-type Surface Aggregator - Module (also referred to as SAM-over-HID, as opposed to the newer - SAM-over-SSH/UART). It has currently not been determined on how - support for the legacy SAM should be implemented. Likely via a - custom HID driver. - - 2. It seems to serve as the HID device for the Integrated Sensor Hub. - This might complicate matters with regards to implementing a - SAM-over-HID driver required by legacy SAM. - -In light of this, the simplest approach has been chosen for now. -However, it may make more sense regarding breakage and compatibility to -either provide functionality for replacing or enhancing the default -operation region handler via some additional API functions, or even to -completely blacklist MSHW0030 from the I2C core and provide a custom -driver for it. - -Replacing/enhancing the default operation region handler would, however, -either require some sort of secondary driver and access point for it, -from which the new API functions would be called and the new handler -(part) would be installed, or hard-coding them via some sort of -quirk-like interface into the I2C core. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam-over-hid ---- - drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ - 1 file changed, 35 insertions(+) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index 8ceaa88dd78f..deceed0d76c6 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -570,6 +570,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, - return (ret == 1) ? 0 : -EIO; - } - -+static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, -+ u8 *data, u8 data_len) -+{ -+ struct i2c_msg msgs[1]; -+ int ret = AE_OK; -+ -+ msgs[0].addr = client->addr; -+ msgs[0].flags = client->flags; -+ msgs[0].len = data_len + 1; -+ msgs[0].buf = data; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ -+ if (ret < 0) { -+ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); -+ return ret; -+ } -+ -+ /* 1 transfer must have completed successfully */ -+ return (ret == 1) ? 0 : -EIO; -+} -+ - static acpi_status - i2c_acpi_space_handler(u32 function, acpi_physical_address command, - u32 bits, u64 *value64, -@@ -671,6 +693,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, - } - break; - -+ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: -+ if (action == ACPI_READ) { -+ dev_warn(&adapter->dev, -+ "protocol 0x%02x not supported for client 0x%02x\n", -+ accessor_type, client->addr); -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } else { -+ status = acpi_gsb_i2c_write_raw_bytes(client, -+ gsb->data, info->access_length); -+ } -+ break; -+ - default: - dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", - accessor_type, client->addr); --- -2.32.0 - -From 600c757e1f753768656742ccc1ef6fe6cf5ee689 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 13 Feb 2021 16:41:18 +0100 -Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch - -Add driver exposing the discrete GPU power-switch of the Microsoft -Surface Book 1 to user-space. - -On the Surface Book 1, the dGPU power is controlled via the Surface -System Aggregator Module (SAM). The specific SAM-over-HID command for -this is exposed via ACPI. This module provides a simple driver exposing -the ACPI call via a sysfs parameter to user-space, so that users can -easily power-on/-off the dGPU. - -Patchset: surface-sam-over-hid ---- - drivers/platform/surface/Kconfig | 7 + - drivers/platform/surface/Makefile | 1 + - .../surface/surfacebook1_dgpu_switch.c | 162 ++++++++++++++++++ - 3 files changed, 170 insertions(+) - create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 0847b2dc97bf..fd45940ab6ce 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -77,6 +77,13 @@ config SURFACE_AGGREGATOR_CDEV - The provided interface is intended for debugging and development only, - and should not be used otherwise. - -+config SURFACE_BOOK1_DGPU_SWITCH -+ tristate "Surface Book 1 dGPU Switch Driver" -+ depends on SYSFS -+ help -+ This driver provides a sysfs switch to set the power-state of the -+ discrete GPU found on the Microsoft Surface Book 1. -+ - config SURFACE_GPE - tristate "Surface GPE/Lid Support Driver" - depends on DMI -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 990424c5f0c9..6b69175598ab 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -10,6 +10,7 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o -+obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o -diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c -new file mode 100644 -index 000000000000..8b816ed8f35c ---- /dev/null -+++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c -@@ -0,0 +1,162 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+ -+#include -+#include -+#include -+#include -+ -+ -+#ifdef pr_fmt -+#undef pr_fmt -+#endif -+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ -+ -+ -+static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, -+ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); -+ -+#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" -+#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" -+#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" -+ -+ -+static int sb1_dgpu_sw_dsmcall(void) -+{ -+ union acpi_object *ret; -+ acpi_handle handle; -+ acpi_status status; -+ -+ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); -+ if (status) -+ return -EINVAL; -+ -+ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); -+ if (!ret) -+ return -EINVAL; -+ -+ ACPI_FREE(ret); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgon(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGON: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-on dGPU via HGON\n"); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgof(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGOF: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-off dGPU via HGOF\n"); -+ return 0; -+} -+ -+ -+static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ int status, value; -+ -+ status = kstrtoint(buf, 0, &value); -+ if (status < 0) -+ return status; -+ -+ if (value != 1) -+ return -EINVAL; -+ -+ status = sb1_dgpu_sw_dsmcall(); -+ -+ return status < 0 ? status : len; -+} -+ -+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ bool power; -+ int status; -+ -+ status = kstrtobool(buf, &power); -+ if (status < 0) -+ return status; -+ -+ if (power) -+ status = sb1_dgpu_sw_hgon(); -+ else -+ status = sb1_dgpu_sw_hgof(); -+ -+ return status < 0 ? status : len; -+} -+ -+static DEVICE_ATTR_WO(dgpu_dsmcall); -+static DEVICE_ATTR_WO(dgpu_power); -+ -+static struct attribute *sb1_dgpu_sw_attrs[] = { -+ &dev_attr_dgpu_dsmcall.attr, -+ &dev_attr_dgpu_power.attr, -+ NULL, -+}; -+ -+static const struct attribute_group sb1_dgpu_sw_attr_group = { -+ .attrs = sb1_dgpu_sw_attrs, -+}; -+ -+ -+static int sb1_dgpu_sw_probe(struct platform_device *pdev) -+{ -+ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+} -+ -+static int sb1_dgpu_sw_remove(struct platform_device *pdev) -+{ -+ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+ return 0; -+} -+ -+/* -+ * The dGPU power seems to be actually handled by MSHW0040. However, that is -+ * also the power-/volume-button device with a mainline driver. So let's use -+ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. -+ */ -+static const struct acpi_device_id sb1_dgpu_sw_match[] = { -+ { "MSHW0041", }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); -+ -+static struct platform_driver sb1_dgpu_sw = { -+ .probe = sb1_dgpu_sw_probe, -+ .remove = sb1_dgpu_sw_remove, -+ .driver = { -+ .name = "surfacebook1_dgpu_switch", -+ .acpi_match_table = sb1_dgpu_sw_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(sb1_dgpu_sw); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); -+MODULE_LICENSE("GPL"); --- -2.32.0 - diff --git a/patches/5.12/0006-surface-sam.patch b/patches/5.12/0006-surface-sam.patch deleted file mode 100644 index efb27dcb3..000000000 --- a/patches/5.12/0006-surface-sam.patch +++ /dev/null @@ -1,10239 +0,0 @@ -From e4addc12f76b5625d0a2c48459709b9be269f5df Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 12 Feb 2021 12:54:34 +0100 -Subject: [PATCH] platform/surface: Set up Surface Aggregator device registry - -The Surface System Aggregator Module (SSAM) subsystem provides various -functionalities, which are separated by spreading them across multiple -devices and corresponding drivers. Parts of that functionality / some of -those devices, however, can (as far as we currently know) not be -auto-detected by conventional means. While older (specifically 5th- and -6th-)generation models do advertise most of their functionality via -standard platform devices in ACPI, newer generations do not. - -As we are currently also not aware of any feasible way to query said -functionalities dynamically, this poses a problem. There is, however, a -device in ACPI that seems to be used by Windows for identifying -different Surface models: The Windows Surface Integration Device (WSID). -This device seems to have a HID corresponding to the overall set of -functionalities SSAM provides for the associated model. - -This commit introduces a registry providing non-detectable device -information via software nodes. In addition, a SSAM platform hub driver -is introduced, which takes care of creating and managing the SSAM -devices specified in this registry. This approach allows for a -hierarchical setup akin to ACPI and is easily extendable, e.g. via -firmware node properties. - -Note that this commit only provides the basis for the platform hub and -registry, and does not add any content to it. The registry will be -expanded in subsequent commits. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210212115439.1525216-2-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - MAINTAINERS | 1 + - drivers/platform/surface/Kconfig | 27 ++ - drivers/platform/surface/Makefile | 1 + - .../surface/surface_aggregator_registry.c | 284 ++++++++++++++++++ - 4 files changed, 313 insertions(+) - create mode 100644 drivers/platform/surface/surface_aggregator_registry.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 9450e052f1b1..f6c524630575 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11904,6 +11904,7 @@ F: Documentation/driver-api/surface_aggregator/ - F: drivers/platform/surface/aggregator/ - F: drivers/platform/surface/surface_acpi_notify.c - F: drivers/platform/surface/surface_aggregator_cdev.c -+F: drivers/platform/surface/surface_aggregator_registry.c - F: include/linux/surface_acpi_notify.h - F: include/linux/surface_aggregator/ - F: include/uapi/linux/surface_aggregator/ -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index fd45940ab6ce..c51c55204b5f 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -77,6 +77,33 @@ config SURFACE_AGGREGATOR_CDEV - The provided interface is intended for debugging and development only, - and should not be used otherwise. - -+config SURFACE_AGGREGATOR_REGISTRY -+ tristate "Surface System Aggregator Module Device Registry" -+ depends on SURFACE_AGGREGATOR -+ depends on SURFACE_AGGREGATOR_BUS -+ help -+ Device-registry and device-hubs for Surface System Aggregator Module -+ (SSAM) devices. -+ -+ Provides a module and driver which act as a device-registry for SSAM -+ client devices that cannot be detected automatically, e.g. via ACPI. -+ Such devices are instead provided via this registry and attached via -+ device hubs, also provided in this module. -+ -+ Devices provided via this registry are: -+ - Platform profile (performance-/cooling-mode) device (5th- and later -+ generations). -+ - Battery/AC devices (7th-generation). -+ - HID input devices (7th-generation). -+ -+ Select M (recommended) or Y here if you want support for the above -+ mentioned devices on the corresponding Surface models. Without this -+ module, the respective devices will not be instantiated and thus any -+ functionality provided by them will be missing, even when drivers for -+ these devices are present. In other words, this module only provides -+ the respective client devices. Drivers for these devices still need to -+ be selected via the other options. -+ - config SURFACE_BOOK1_DGPU_SWITCH - tristate "Surface Book 1 dGPU Switch Driver" - depends on SYSFS -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 6b69175598ab..ed12676f06e6 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -10,6 +10,7 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o -+obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -new file mode 100644 -index 000000000000..a051d941ad96 ---- /dev/null -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -0,0 +1,284 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) client device registry. -+ * -+ * Registry for non-platform/non-ACPI SSAM client devices, i.e. devices that -+ * cannot be auto-detected. Provides device-hubs and performs instantiation -+ * for these devices. -+ * -+ * Copyright (C) 2020-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+ -+/* -- Device registry. ------------------------------------------------------ */ -+ -+/* -+ * SSAM device names follow the SSAM module alias, meaning they are prefixed -+ * with 'ssam:', followed by domain, category, target ID, instance ID, and -+ * function, each encoded as two-digit hexadecimal, separated by ':'. In other -+ * words, it follows the scheme -+ * -+ * ssam:dd:cc:tt:ii:ff -+ * -+ * Where, 'dd', 'cc', 'tt', 'ii', and 'ff' are the two-digit hexadecimal -+ * values mentioned above, respectively. -+ */ -+ -+/* Root node. */ -+static const struct software_node ssam_node_root = { -+ .name = "ssam_platform_hub", -+}; -+ -+/* Devices for Surface Book 2. */ -+static const struct software_node *ssam_node_group_sb2[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Book 3. */ -+static const struct software_node *ssam_node_group_sb3[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Laptop 1. */ -+static const struct software_node *ssam_node_group_sl1[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Laptop 2. */ -+static const struct software_node *ssam_node_group_sl2[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Laptop 3. */ -+static const struct software_node *ssam_node_group_sl3[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Laptop Go. */ -+static const struct software_node *ssam_node_group_slg1[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Pro 5. */ -+static const struct software_node *ssam_node_group_sp5[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Pro 6. */ -+static const struct software_node *ssam_node_group_sp6[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+/* Devices for Surface Pro 7. */ -+static const struct software_node *ssam_node_group_sp7[] = { -+ &ssam_node_root, -+ NULL, -+}; -+ -+ -+/* -- Device registry helper functions. ------------------------------------- */ -+ -+static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) -+{ -+ u8 d, tc, tid, iid, fn; -+ int n; -+ -+ n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); -+ if (n != 5) -+ return -EINVAL; -+ -+ uid->domain = d; -+ uid->category = tc; -+ uid->target = tid; -+ uid->instance = iid; -+ uid->function = fn; -+ -+ return 0; -+} -+ -+static int ssam_hub_remove_devices_fn(struct device *dev, void *data) -+{ -+ if (!is_ssam_device(dev)) -+ return 0; -+ -+ ssam_device_remove(to_ssam_device(dev)); -+ return 0; -+} -+ -+static void ssam_hub_remove_devices(struct device *parent) -+{ -+ device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn); -+} -+ -+static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ struct ssam_device_uid uid; -+ struct ssam_device *sdev; -+ int status; -+ -+ status = ssam_uid_from_string(fwnode_get_name(node), &uid); -+ if (status) -+ return status; -+ -+ sdev = ssam_device_alloc(ctrl, uid); -+ if (!sdev) -+ return -ENOMEM; -+ -+ sdev->dev.parent = parent; -+ sdev->dev.fwnode = node; -+ -+ status = ssam_device_add(sdev); -+ if (status) -+ ssam_device_put(sdev); -+ -+ return status; -+} -+ -+static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ struct fwnode_handle *child; -+ int status; -+ -+ fwnode_for_each_child_node(node, child) { -+ /* -+ * Try to add the device specified in the firmware node. If -+ * this fails with -EINVAL, the node does not specify any SSAM -+ * device, so ignore it and continue with the next one. -+ */ -+ -+ status = ssam_hub_add_device(parent, ctrl, child); -+ if (status && status != -EINVAL) -+ goto err; -+ } -+ -+ return 0; -+err: -+ ssam_hub_remove_devices(parent); -+ return status; -+} -+ -+ -+/* -- SSAM platform/meta-hub driver. ---------------------------------------- */ -+ -+static const struct acpi_device_id ssam_platform_hub_match[] = { -+ /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ -+ { "MSHW0081", (unsigned long)ssam_node_group_sp5 }, -+ -+ /* Surface Pro 6 (OMBR >= 0x10) */ -+ { "MSHW0111", (unsigned long)ssam_node_group_sp6 }, -+ -+ /* Surface Pro 7 */ -+ { "MSHW0116", (unsigned long)ssam_node_group_sp7 }, -+ -+ /* Surface Book 2 */ -+ { "MSHW0107", (unsigned long)ssam_node_group_sb2 }, -+ -+ /* Surface Book 3 */ -+ { "MSHW0117", (unsigned long)ssam_node_group_sb3 }, -+ -+ /* Surface Laptop 1 */ -+ { "MSHW0086", (unsigned long)ssam_node_group_sl1 }, -+ -+ /* Surface Laptop 2 */ -+ { "MSHW0112", (unsigned long)ssam_node_group_sl2 }, -+ -+ /* Surface Laptop 3 (13", Intel) */ -+ { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, -+ -+ /* Surface Laptop 3 (15", AMD) */ -+ { "MSHW0110", (unsigned long)ssam_node_group_sl3 }, -+ -+ /* Surface Laptop Go 1 */ -+ { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, -+ -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match); -+ -+static int ssam_platform_hub_probe(struct platform_device *pdev) -+{ -+ const struct software_node **nodes; -+ struct ssam_controller *ctrl; -+ struct fwnode_handle *root; -+ int status; -+ -+ nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev); -+ if (!nodes) -+ return -ENODEV; -+ -+ /* -+ * As we're adding the SSAM client devices as children under this device -+ * and not the SSAM controller, we need to add a device link to the -+ * controller to ensure that we remove all of our devices before the -+ * controller is removed. This also guarantees proper ordering for -+ * suspend/resume of the devices on this hub. -+ */ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ status = software_node_register_node_group(nodes); -+ if (status) -+ return status; -+ -+ root = software_node_fwnode(&ssam_node_root); -+ if (!root) { -+ software_node_unregister_node_group(nodes); -+ return -ENOENT; -+ } -+ -+ set_secondary_fwnode(&pdev->dev, root); -+ -+ status = ssam_hub_add_devices(&pdev->dev, ctrl, root); -+ if (status) { -+ set_secondary_fwnode(&pdev->dev, NULL); -+ software_node_unregister_node_group(nodes); -+ } -+ -+ platform_set_drvdata(pdev, nodes); -+ return status; -+} -+ -+static int ssam_platform_hub_remove(struct platform_device *pdev) -+{ -+ const struct software_node **nodes = platform_get_drvdata(pdev); -+ -+ ssam_hub_remove_devices(&pdev->dev); -+ set_secondary_fwnode(&pdev->dev, NULL); -+ software_node_unregister_node_group(nodes); -+ return 0; -+} -+ -+static struct platform_driver ssam_platform_hub_driver = { -+ .probe = ssam_platform_hub_probe, -+ .remove = ssam_platform_hub_remove, -+ .driver = { -+ .name = "surface_aggregator_platform_hub", -+ .acpi_match_table = ssam_platform_hub_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(ssam_platform_hub_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); --- -2.32.0 - -From c70ad8f475c65c0000e4d08acdc1861ff557fe01 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 12 Feb 2021 12:54:35 +0100 -Subject: [PATCH] platform/surface: aggregator_registry: Add base device hub - -The Surface Book 3 has a detachable base part. While the top part -(so-called clipboard) contains the CPU, touchscreen, and primary -battery, the base contains, among other things, a keyboard, touchpad, -and secondary battery. - -Those devices do not react well to being accessed when the base part is -detached and should thus be removed and added in sync with the base. To -facilitate this, we introduce a virtual base device hub, which -automatically removes or adds the devices registered under it. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210212115439.1525216-3-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 261 +++++++++++++++++- - 1 file changed, 260 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index a051d941ad96..6c23d75a044c 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -11,9 +11,12 @@ - - #include - #include -+#include - #include -+#include - #include - #include -+#include - - #include - #include -@@ -38,6 +41,12 @@ static const struct software_node ssam_node_root = { - .name = "ssam_platform_hub", - }; - -+/* Base device hub (devices attached to Surface Book 3 base). */ -+static const struct software_node ssam_node_hub_base = { -+ .name = "ssam:00:00:02:00:00", -+ .parent = &ssam_node_root, -+}; -+ - /* Devices for Surface Book 2. */ - static const struct software_node *ssam_node_group_sb2[] = { - &ssam_node_root, -@@ -47,6 +56,7 @@ static const struct software_node *ssam_node_group_sb2[] = { - /* Devices for Surface Book 3. */ - static const struct software_node *ssam_node_group_sb3[] = { - &ssam_node_root, -+ &ssam_node_hub_base, - NULL, - }; - -@@ -177,6 +187,230 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c - } - - -+/* -- SSAM base-hub driver. ------------------------------------------------- */ -+ -+enum ssam_base_hub_state { -+ SSAM_BASE_HUB_UNINITIALIZED, -+ SSAM_BASE_HUB_CONNECTED, -+ SSAM_BASE_HUB_DISCONNECTED, -+}; -+ -+struct ssam_base_hub { -+ struct ssam_device *sdev; -+ -+ struct mutex lock; /* Guards state update checks and transitions. */ -+ enum ssam_base_hub_state state; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0d, -+ .instance_id = 0x00, -+}); -+ -+#define SSAM_BAS_OPMODE_TABLET 0x00 -+#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c -+ -+static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state) -+{ -+ u8 opmode; -+ int status; -+ -+ status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); -+ return status; -+ } -+ -+ if (opmode != SSAM_BAS_OPMODE_TABLET) -+ *state = SSAM_BASE_HUB_CONNECTED; -+ else -+ *state = SSAM_BASE_HUB_DISCONNECTED; -+ -+ return 0; -+} -+ -+static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr, -+ char *buf) -+{ -+ struct ssam_base_hub *hub = dev_get_drvdata(dev); -+ bool connected; -+ -+ mutex_lock(&hub->lock); -+ connected = hub->state == SSAM_BASE_HUB_CONNECTED; -+ mutex_unlock(&hub->lock); -+ -+ return sysfs_emit(buf, "%d\n", connected); -+} -+ -+static struct device_attribute ssam_base_hub_attr_state = -+ __ATTR(state, 0444, ssam_base_hub_state_show, NULL); -+ -+static struct attribute *ssam_base_hub_attrs[] = { -+ &ssam_base_hub_attr_state.attr, -+ NULL, -+}; -+ -+const struct attribute_group ssam_base_hub_group = { -+ .attrs = ssam_base_hub_attrs, -+}; -+ -+static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new) -+{ -+ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -+ int status = 0; -+ -+ lockdep_assert_held(&hub->lock); -+ -+ if (hub->state == new) -+ return 0; -+ hub->state = new; -+ -+ if (hub->state == SSAM_BASE_HUB_CONNECTED) -+ status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); -+ else -+ ssam_hub_remove_devices(&hub->sdev->dev); -+ -+ if (status) -+ dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); -+ -+ return status; -+} -+ -+static int ssam_base_hub_update(struct ssam_base_hub *hub) -+{ -+ enum ssam_base_hub_state state; -+ int status; -+ -+ mutex_lock(&hub->lock); -+ -+ status = ssam_base_hub_query_state(hub, &state); -+ if (!status) -+ status = __ssam_base_hub_update(hub, state); -+ -+ mutex_unlock(&hub->lock); -+ return status; -+} -+ -+static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_base_hub *hub; -+ struct ssam_device *sdev; -+ enum ssam_base_hub_state new; -+ -+ hub = container_of(nf, struct ssam_base_hub, notif); -+ sdev = hub->sdev; -+ -+ if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) -+ return 0; -+ -+ if (event->length < 1) { -+ dev_err(&sdev->dev, "unexpected payload size: %u\n", -+ event->length); -+ return 0; -+ } -+ -+ if (event->data[0]) -+ new = SSAM_BASE_HUB_CONNECTED; -+ else -+ new = SSAM_BASE_HUB_DISCONNECTED; -+ -+ mutex_lock(&hub->lock); -+ __ssam_base_hub_update(hub, new); -+ mutex_unlock(&hub->lock); -+ -+ /* -+ * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -+ * consumed by the detachment system driver. We're just a (more or less) -+ * silent observer. -+ */ -+ return 0; -+} -+ -+static int __maybe_unused ssam_base_hub_resume(struct device *dev) -+{ -+ return ssam_base_hub_update(dev_get_drvdata(dev)); -+} -+static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); -+ -+static int ssam_base_hub_probe(struct ssam_device *sdev) -+{ -+ struct ssam_base_hub *hub; -+ int status; -+ -+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -+ if (!hub) -+ return -ENOMEM; -+ -+ mutex_init(&hub->lock); -+ -+ hub->sdev = sdev; -+ hub->state = SSAM_BASE_HUB_UNINITIALIZED; -+ -+ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -+ hub->notif.base.fn = ssam_base_hub_notif; -+ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ hub->notif.event.id.target_category = SSAM_SSH_TC_BAS, -+ hub->notif.event.id.instance = 0, -+ hub->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ ssam_device_set_drvdata(sdev, hub); -+ -+ status = ssam_notifier_register(sdev->ctrl, &hub->notif); -+ if (status) -+ goto err_register; -+ -+ status = ssam_base_hub_update(hub); -+ if (status) -+ goto err_update; -+ -+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); -+ if (status) -+ goto err_update; -+ -+ return 0; -+ -+err_update: -+ ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_hub_remove_devices(&sdev->dev); -+err_register: -+ mutex_destroy(&hub->lock); -+ return status; -+} -+ -+static void ssam_base_hub_remove(struct ssam_device *sdev) -+{ -+ struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); -+ -+ sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); -+ -+ ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_hub_remove_devices(&sdev->dev); -+ -+ mutex_destroy(&hub->lock); -+} -+ -+static const struct ssam_device_id ssam_base_hub_match[] = { -+ { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) }, -+ { }, -+}; -+ -+static struct ssam_device_driver ssam_base_hub_driver = { -+ .probe = ssam_base_hub_probe, -+ .remove = ssam_base_hub_remove, -+ .match_table = ssam_base_hub_match, -+ .driver = { -+ .name = "surface_aggregator_base_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_base_hub_pm_ops, -+ }, -+}; -+ -+ - /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ - - static const struct acpi_device_id ssam_platform_hub_match[] = { -@@ -277,7 +511,32 @@ static struct platform_driver ssam_platform_hub_driver = { - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - }, - }; --module_platform_driver(ssam_platform_hub_driver); -+ -+ -+/* -- Module initialization. ------------------------------------------------ */ -+ -+static int __init ssam_device_hub_init(void) -+{ -+ int status; -+ -+ status = platform_driver_register(&ssam_platform_hub_driver); -+ if (status) -+ return status; -+ -+ status = ssam_device_driver_register(&ssam_base_hub_driver); -+ if (status) -+ platform_driver_unregister(&ssam_platform_hub_driver); -+ -+ return status; -+} -+module_init(ssam_device_hub_init); -+ -+static void __exit ssam_device_hub_exit(void) -+{ -+ ssam_device_driver_unregister(&ssam_base_hub_driver); -+ platform_driver_unregister(&ssam_platform_hub_driver); -+} -+module_exit(ssam_device_hub_exit); - - MODULE_AUTHOR("Maximilian Luz "); - MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); --- -2.32.0 - -From 9652c52f812afe1f5a75508d4fe077cf73d71e3f Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 12 Feb 2021 12:54:36 +0100 -Subject: [PATCH] platform/surface: aggregator_registry: Add battery subsystem - devices - -Add battery subsystem (TC=0x02) devices (battery and AC) to the SSAM -device registry. These devices need to be registered for 7th-generation -Surface models. On 5th- and 6th-generation models, these devices are -handled via the standard ACPI battery/AC interface, which in turn -accesses the same SSAM interface via the Surface ACPI Notify (SAN) -driver. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210212115439.1525216-4-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 27 +++++++++++++++++++ - 1 file changed, 27 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 6c23d75a044c..cde279692842 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -47,6 +47,24 @@ static const struct software_node ssam_node_hub_base = { - .parent = &ssam_node_root, - }; - -+/* AC adapter. */ -+static const struct software_node ssam_node_bat_ac = { -+ .name = "ssam:01:02:01:01:01", -+ .parent = &ssam_node_root, -+}; -+ -+/* Primary battery. */ -+static const struct software_node ssam_node_bat_main = { -+ .name = "ssam:01:02:01:01:00", -+ .parent = &ssam_node_root, -+}; -+ -+/* Secondary battery (Surface Book 3). */ -+static const struct software_node ssam_node_bat_sb3base = { -+ .name = "ssam:01:02:02:01:00", -+ .parent = &ssam_node_hub_base, -+}; -+ - /* Devices for Surface Book 2. */ - static const struct software_node *ssam_node_group_sb2[] = { - &ssam_node_root, -@@ -57,6 +75,9 @@ static const struct software_node *ssam_node_group_sb2[] = { - static const struct software_node *ssam_node_group_sb3[] = { - &ssam_node_root, - &ssam_node_hub_base, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, -+ &ssam_node_bat_sb3base, - NULL, - }; - -@@ -75,12 +96,16 @@ static const struct software_node *ssam_node_group_sl2[] = { - /* Devices for Surface Laptop 3. */ - static const struct software_node *ssam_node_group_sl3[] = { - &ssam_node_root, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, - NULL, - }; - - /* Devices for Surface Laptop Go. */ - static const struct software_node *ssam_node_group_slg1[] = { - &ssam_node_root, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, - NULL, - }; - -@@ -99,6 +124,8 @@ static const struct software_node *ssam_node_group_sp6[] = { - /* Devices for Surface Pro 7. */ - static const struct software_node *ssam_node_group_sp7[] = { - &ssam_node_root, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, - NULL, - }; - --- -2.32.0 - -From 4d0bc54f28f710ec656a063bf3c5265a57d3b13d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 12 Feb 2021 12:54:37 +0100 -Subject: [PATCH] platform/surface: aggregator_registry: Add platform profile - device - -Add the SSAM platform profile device to the SSAM device registry. This -device is accessible under the thermal subsystem (TC=0x03) and needs to -be registered for all Surface models. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210212115439.1525216-5-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index cde279692842..33904613dd4b 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -65,9 +65,16 @@ static const struct software_node ssam_node_bat_sb3base = { - .parent = &ssam_node_hub_base, - }; - -+/* Platform profile / performance-mode device. */ -+static const struct software_node ssam_node_tmp_pprof = { -+ .name = "ssam:01:03:01:00:01", -+ .parent = &ssam_node_root, -+}; -+ - /* Devices for Surface Book 2. */ - static const struct software_node *ssam_node_group_sb2[] = { - &ssam_node_root, -+ &ssam_node_tmp_pprof, - NULL, - }; - -@@ -78,18 +85,21 @@ static const struct software_node *ssam_node_group_sb3[] = { - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_bat_sb3base, -+ &ssam_node_tmp_pprof, - NULL, - }; - - /* Devices for Surface Laptop 1. */ - static const struct software_node *ssam_node_group_sl1[] = { - &ssam_node_root, -+ &ssam_node_tmp_pprof, - NULL, - }; - - /* Devices for Surface Laptop 2. */ - static const struct software_node *ssam_node_group_sl2[] = { - &ssam_node_root, -+ &ssam_node_tmp_pprof, - NULL, - }; - -@@ -98,6 +108,7 @@ static const struct software_node *ssam_node_group_sl3[] = { - &ssam_node_root, - &ssam_node_bat_ac, - &ssam_node_bat_main, -+ &ssam_node_tmp_pprof, - NULL, - }; - -@@ -106,18 +117,21 @@ static const struct software_node *ssam_node_group_slg1[] = { - &ssam_node_root, - &ssam_node_bat_ac, - &ssam_node_bat_main, -+ &ssam_node_tmp_pprof, - NULL, - }; - - /* Devices for Surface Pro 5. */ - static const struct software_node *ssam_node_group_sp5[] = { - &ssam_node_root, -+ &ssam_node_tmp_pprof, - NULL, - }; - - /* Devices for Surface Pro 6. */ - static const struct software_node *ssam_node_group_sp6[] = { - &ssam_node_root, -+ &ssam_node_tmp_pprof, - NULL, - }; - -@@ -126,6 +140,7 @@ static const struct software_node *ssam_node_group_sp7[] = { - &ssam_node_root, - &ssam_node_bat_ac, - &ssam_node_bat_main, -+ &ssam_node_tmp_pprof, - NULL, - }; - --- -2.32.0 - -From d7dedaa2b75bb813a9fdd78d7a08827f67f0ea31 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 12 Feb 2021 12:54:38 +0100 -Subject: [PATCH] platform/surface: aggregator_registry: Add DTX device - -Add the detachment system (DTX) SSAM device for the Surface Book 3. This -device is accessible under the base (TC=0x11) subsystem. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210212115439.1525216-6-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 33904613dd4b..dc044d06828b 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -71,6 +71,12 @@ static const struct software_node ssam_node_tmp_pprof = { - .parent = &ssam_node_root, - }; - -+/* DTX / detachment-system device (Surface Book 3). */ -+static const struct software_node ssam_node_bas_dtx = { -+ .name = "ssam:01:11:01:00:00", -+ .parent = &ssam_node_root, -+}; -+ - /* Devices for Surface Book 2. */ - static const struct software_node *ssam_node_group_sb2[] = { - &ssam_node_root, -@@ -86,6 +92,7 @@ static const struct software_node *ssam_node_group_sb3[] = { - &ssam_node_bat_main, - &ssam_node_bat_sb3base, - &ssam_node_tmp_pprof, -+ &ssam_node_bas_dtx, - NULL, - }; - --- -2.32.0 - -From 48e36688edec92d06ede33ddb78c708adbe4179f Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 12 Feb 2021 12:54:39 +0100 -Subject: [PATCH] platform/surface: aggregator_registry: Add HID subsystem - devices - -Add HID subsystem (TC=0x15) devices. These devices need to be registered -for 7th-generation Surface models. On previous generations, these -devices are either provided as platform devices via ACPI (Surface Laptop -1 and 2) or implemented as standard USB device. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210212115439.1525216-7-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 49 +++++++++++++++++++ - 1 file changed, 49 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index dc044d06828b..caee90d135c5 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -77,6 +77,48 @@ static const struct software_node ssam_node_bas_dtx = { - .parent = &ssam_node_root, - }; - -+/* HID keyboard. */ -+static const struct software_node ssam_node_hid_main_keyboard = { -+ .name = "ssam:01:15:02:01:00", -+ .parent = &ssam_node_root, -+}; -+ -+/* HID touchpad. */ -+static const struct software_node ssam_node_hid_main_touchpad = { -+ .name = "ssam:01:15:02:03:00", -+ .parent = &ssam_node_root, -+}; -+ -+/* HID device instance 5 (unknown HID device). */ -+static const struct software_node ssam_node_hid_main_iid5 = { -+ .name = "ssam:01:15:02:05:00", -+ .parent = &ssam_node_root, -+}; -+ -+/* HID keyboard (base hub). */ -+static const struct software_node ssam_node_hid_base_keyboard = { -+ .name = "ssam:01:15:02:01:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+/* HID touchpad (base hub). */ -+static const struct software_node ssam_node_hid_base_touchpad = { -+ .name = "ssam:01:15:02:03:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+/* HID device instance 5 (unknown HID device, base hub). */ -+static const struct software_node ssam_node_hid_base_iid5 = { -+ .name = "ssam:01:15:02:05:00", -+ .parent = &ssam_node_hub_base, -+}; -+ -+/* HID device instance 6 (unknown HID device, base hub). */ -+static const struct software_node ssam_node_hid_base_iid6 = { -+ .name = "ssam:01:15:02:06:00", -+ .parent = &ssam_node_hub_base, -+}; -+ - /* Devices for Surface Book 2. */ - static const struct software_node *ssam_node_group_sb2[] = { - &ssam_node_root, -@@ -93,6 +135,10 @@ static const struct software_node *ssam_node_group_sb3[] = { - &ssam_node_bat_sb3base, - &ssam_node_tmp_pprof, - &ssam_node_bas_dtx, -+ &ssam_node_hid_base_keyboard, -+ &ssam_node_hid_base_touchpad, -+ &ssam_node_hid_base_iid5, -+ &ssam_node_hid_base_iid6, - NULL, - }; - -@@ -116,6 +162,9 @@ static const struct software_node *ssam_node_group_sl3[] = { - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -+ &ssam_node_hid_main_keyboard, -+ &ssam_node_hid_main_touchpad, -+ &ssam_node_hid_main_iid5, - NULL, - }; - --- -2.32.0 - -From 7178cfe4327a0e70115450be0fe768f5023fe1c3 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 11 Feb 2021 21:17:03 +0100 -Subject: [PATCH] platform/surface: Add platform profile driver - -Add a driver to provide platform profile support on 5th- and later -generation Microsoft Surface devices with a Surface System Aggregator -Module. On those devices, the platform profile can be used to influence -cooling behavior and power consumption. - -For example, the default 'quiet' profile limits fan noise and in turn -sacrifices performance of the discrete GPU found on Surface Books. Its -full performance can only be unlocked on the 'performance' profile. - -Signed-off-by: Maximilian Luz -Reviewed-by: Hans de Goede -Link: https://lore.kernel.org/r/20210211201703.658240-5-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - MAINTAINERS | 6 + - drivers/platform/surface/Kconfig | 22 ++ - drivers/platform/surface/Makefile | 1 + - .../surface/surface_platform_profile.c | 190 ++++++++++++++++++ - 4 files changed, 219 insertions(+) - create mode 100644 drivers/platform/surface/surface_platform_profile.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index f6c524630575..fce5cdcefc0b 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11889,6 +11889,12 @@ L: platform-driver-x86@vger.kernel.org - S: Maintained - F: drivers/platform/surface/surface_hotplug.c - -+MICROSOFT SURFACE PLATFORM PROFILE DRIVER -+M: Maximilian Luz -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/platform/surface/surface_platform_profile.c -+ - MICROSOFT SURFACE PRO 3 BUTTON DRIVER - M: Chen Yu - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index c51c55204b5f..6fb304da845f 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -139,6 +139,28 @@ config SURFACE_HOTPLUG - Select M or Y here, if you want to (fully) support hot-plugging of - dGPU devices on the Surface Book 2 and/or 3 during D3cold. - -+config SURFACE_PLATFORM_PROFILE -+ tristate "Surface Platform Profile Driver" -+ depends on SURFACE_AGGREGATOR_REGISTRY -+ select ACPI_PLATFORM_PROFILE -+ help -+ Provides support for the ACPI platform profile on 5th- and later -+ generation Microsoft Surface devices. -+ -+ More specifically, this driver provides ACPI platform profile support -+ on Microsoft Surface devices with a Surface System Aggregator Module -+ (SSAM) connected via the Surface Serial Hub (SSH / SAM-over-SSH). In -+ other words, this driver provides platform profile support on the -+ Surface Pro 5, Surface Book 2, Surface Laptop, Surface Laptop Go and -+ later. On those devices, the platform profile can significantly -+ influence cooling behavior, e.g. setting it to 'quiet' (default) or -+ 'low-power' can significantly limit performance of the discrete GPU on -+ Surface Books, while in turn leading to lower power consumption and/or -+ less fan noise. -+ -+ Select M or Y here, if you want to include ACPI platform profile -+ support on the above mentioned devices. -+ - config SURFACE_PRO3_BUTTON - tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet" - depends on INPUT -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index ed12676f06e6..f7187bae1729 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -14,4 +14,5 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -+obj-$(CONFIG_SURFACE_PLATFORM_PROFILE) += surface_platform_profile.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o -diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c -new file mode 100644 -index 000000000000..0081b01a5b0f ---- /dev/null -+++ b/drivers/platform/surface/surface_platform_profile.c -@@ -0,0 +1,190 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface Platform Profile / Performance Mode driver for Surface System -+ * Aggregator Module (thermal subsystem). -+ * -+ * Copyright (C) 2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+enum ssam_tmp_profile { -+ SSAM_TMP_PROFILE_NORMAL = 1, -+ SSAM_TMP_PROFILE_BATTERY_SAVER = 2, -+ SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3, -+ SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4, -+}; -+ -+struct ssam_tmp_profile_info { -+ __le32 profile; -+ __le16 unknown1; -+ __le16 unknown2; -+} __packed; -+ -+struct ssam_tmp_profile_device { -+ struct ssam_device *sdev; -+ struct platform_profile_handler handler; -+}; -+ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .command_id = 0x02, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .command_id = 0x03, -+}); -+ -+static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p) -+{ -+ struct ssam_tmp_profile_info info; -+ int status; -+ -+ status = ssam_retry(__ssam_tmp_profile_get, sdev, &info); -+ if (status < 0) -+ return status; -+ -+ *p = le32_to_cpu(info.profile); -+ return 0; -+} -+ -+static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p) -+{ -+ __le32 profile_le = cpu_to_le32(p); -+ -+ return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le); -+} -+ -+static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p) -+{ -+ switch (p) { -+ case SSAM_TMP_PROFILE_NORMAL: -+ return PLATFORM_PROFILE_BALANCED; -+ -+ case SSAM_TMP_PROFILE_BATTERY_SAVER: -+ return PLATFORM_PROFILE_LOW_POWER; -+ -+ case SSAM_TMP_PROFILE_BETTER_PERFORMANCE: -+ return PLATFORM_PROFILE_BALANCED_PERFORMANCE; -+ -+ case SSAM_TMP_PROFILE_BEST_PERFORMANCE: -+ return PLATFORM_PROFILE_PERFORMANCE; -+ -+ default: -+ dev_err(&sdev->dev, "invalid performance profile: %d", p); -+ return -EINVAL; -+ } -+} -+ -+static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p) -+{ -+ switch (p) { -+ case PLATFORM_PROFILE_LOW_POWER: -+ return SSAM_TMP_PROFILE_BATTERY_SAVER; -+ -+ case PLATFORM_PROFILE_BALANCED: -+ return SSAM_TMP_PROFILE_NORMAL; -+ -+ case PLATFORM_PROFILE_BALANCED_PERFORMANCE: -+ return SSAM_TMP_PROFILE_BETTER_PERFORMANCE; -+ -+ case PLATFORM_PROFILE_PERFORMANCE: -+ return SSAM_TMP_PROFILE_BEST_PERFORMANCE; -+ -+ default: -+ /* This should have already been caught by platform_profile_store(). */ -+ WARN(true, "unsupported platform profile"); -+ return -EOPNOTSUPP; -+ } -+} -+ -+static int ssam_platform_profile_get(struct platform_profile_handler *pprof, -+ enum platform_profile_option *profile) -+{ -+ struct ssam_tmp_profile_device *tpd; -+ enum ssam_tmp_profile tp; -+ int status; -+ -+ tpd = container_of(pprof, struct ssam_tmp_profile_device, handler); -+ -+ status = ssam_tmp_profile_get(tpd->sdev, &tp); -+ if (status) -+ return status; -+ -+ status = convert_ssam_to_profile(tpd->sdev, tp); -+ if (status < 0) -+ return status; -+ -+ *profile = status; -+ return 0; -+} -+ -+static int ssam_platform_profile_set(struct platform_profile_handler *pprof, -+ enum platform_profile_option profile) -+{ -+ struct ssam_tmp_profile_device *tpd; -+ int tp; -+ -+ tpd = container_of(pprof, struct ssam_tmp_profile_device, handler); -+ -+ tp = convert_profile_to_ssam(tpd->sdev, profile); -+ if (tp < 0) -+ return tp; -+ -+ return ssam_tmp_profile_set(tpd->sdev, tp); -+} -+ -+static int surface_platform_profile_probe(struct ssam_device *sdev) -+{ -+ struct ssam_tmp_profile_device *tpd; -+ -+ tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL); -+ if (!tpd) -+ return -ENOMEM; -+ -+ tpd->sdev = sdev; -+ -+ tpd->handler.profile_get = ssam_platform_profile_get; -+ tpd->handler.profile_set = ssam_platform_profile_set; -+ -+ set_bit(PLATFORM_PROFILE_LOW_POWER, tpd->handler.choices); -+ set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices); -+ set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices); -+ set_bit(PLATFORM_PROFILE_PERFORMANCE, tpd->handler.choices); -+ -+ platform_profile_register(&tpd->handler); -+ return 0; -+} -+ -+static void surface_platform_profile_remove(struct ssam_device *sdev) -+{ -+ platform_profile_remove(); -+} -+ -+static const struct ssam_device_id ssam_platform_profile_match[] = { -+ { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match); -+ -+static struct ssam_device_driver surface_platform_profile = { -+ .probe = surface_platform_profile_probe, -+ .remove = surface_platform_profile_remove, -+ .match_table = ssam_platform_profile_match, -+ .driver = { -+ .name = "surface_platform_profile", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_platform_profile); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); --- -2.32.0 - -From 9b2de618ae2ae75163dd8ae3a77704a58aaad345 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 4 Mar 2021 20:05:24 +0100 -Subject: [PATCH] platform/surface: aggregator: Make SSAM_DEFINE_SYNC_REQUEST_x - define static functions - -The SSAM_DEFINE_SYNC_REQUEST_x() macros are intended to reduce -boiler-plate code for SSAM request definitions by defining a wrapper -function for the specified request. The client device variants of those -macros, i.e. SSAM_DEFINE_SYNC_REQUEST_CL_x() in particular rely on the -multi-device (MD) variants, e.g.: - - #define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \ - SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \ - int name(struct ssam_device *sdev, rtype *ret) \ - { \ - return __raw_##name(sdev->ctrl, sdev->uid.target, \ - sdev->uid.instance, ret); \ - } - -This now creates the problem that it is not possible to declare the -generated functions static via - - static SSAM_DEFINE_SYNC_REQUEST_CL_R(...) - -as this will only apply to the function defined by the multi-device -macro, i.e. SSAM_DEFINE_SYNC_REQUEST_MD_R(). Thus compiling with -`-Wmissing-prototypes' rightfully complains that there is a 'static' -keyword missing. - -To solve this, make all SSAM_DEFINE_SYNC_REQUEST_x() macros define -static functions. Non-client-device macros are also changed for -consistency. In general, we expect those functions to be only used -locally in the respective drivers for the corresponding interfaces, so -having to define a wrapper function to be able to export this should be -the odd case out. - -Reported-by: kernel test robot -Fixes: b78b4982d763 ("platform/surface: Add platform profile driver") -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210304190524.1172197-1-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../driver-api/surface_aggregator/client.rst | 4 +- - .../platform/surface/aggregator/controller.c | 10 +-- - .../surface/surface_aggregator_registry.c | 2 +- - .../surface/surface_platform_profile.c | 4 +- - include/linux/surface_aggregator/controller.h | 74 +++++++++---------- - include/linux/surface_aggregator/device.h | 31 ++++---- - 6 files changed, 63 insertions(+), 62 deletions(-) - -diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst -index 26d13085a117..e519d374c378 100644 ---- a/Documentation/driver-api/surface_aggregator/client.rst -+++ b/Documentation/driver-api/surface_aggregator/client.rst -@@ -248,7 +248,7 @@ This example defines a function - - .. code-block:: c - -- int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg); -+ static int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg); - - executing the specified request, with the controller passed in when calling - said function. In this example, the argument is provided via the ``arg`` -@@ -296,7 +296,7 @@ This invocation of the macro defines a function - - .. code-block:: c - -- int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret); -+ static int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret); - - executing the specified request, using the device IDs and controller given - in the client device. The full list of such macros for client devices is: -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index d68d51fb24ff..d006a36b2924 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -1750,35 +1750,35 @@ EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer); - - /* -- Internal SAM requests. ------------------------------------------------ */ - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, { - .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, - .command_id = 0x13, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, { - .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, - .command_id = 0x15, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, { - .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, - .command_id = 0x16, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, { - .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, - .command_id = 0x33, - .instance_id = 0x00, - }); - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, { - .target_category = SSAM_SSH_TC_SAM, - .target_id = 0x01, - .command_id = 0x34, -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index caee90d135c5..cdb4a95af3e8 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -302,7 +302,7 @@ struct ssam_base_hub { - struct ssam_event_notifier notif; - }; - --static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, - .command_id = 0x0d, -diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c -index 0081b01a5b0f..6373d3b5eb7f 100644 ---- a/drivers/platform/surface/surface_platform_profile.c -+++ b/drivers/platform/surface/surface_platform_profile.c -@@ -32,12 +32,12 @@ struct ssam_tmp_profile_device { - struct platform_profile_handler handler; - }; - --static SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, { -+SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, { - .target_category = SSAM_SSH_TC_TMP, - .command_id = 0x02, - }); - --static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, { -+SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, { - .target_category = SSAM_SSH_TC_TMP, - .command_id = 0x03, - }); -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index f4b1ba887384..0806796eabcb 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -344,16 +344,16 @@ struct ssam_request_spec_md { - * request has been fully completed. The required transport buffer will be - * allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_controller -- * *ctrl)``, returning the status of the request, which is zero on success and -- * negative on failure. The ``ctrl`` parameter is the controller via which the -- * request is being sent. -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl)``, returning the status of the request, which is -+ * zero on success and negative on failure. The ``ctrl`` parameter is the -+ * controller via which the request is being sent. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_N(name, spec...) \ -- int name(struct ssam_controller *ctrl) \ -+ static int name(struct ssam_controller *ctrl) \ - { \ - struct ssam_request_spec s = (struct ssam_request_spec)spec; \ - struct ssam_request rqst; \ -@@ -383,17 +383,17 @@ struct ssam_request_spec_md { - * returning once the request has been fully completed. The required transport - * buffer will be allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_controller -- * *ctrl, const atype *arg)``, returning the status of the request, which is -- * zero on success and negative on failure. The ``ctrl`` parameter is the -- * controller via which the request is sent. The request argument is specified -- * via the ``arg`` pointer. -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, const atype *arg)``, returning the status of the -+ * request, which is zero on success and negative on failure. The ``ctrl`` -+ * parameter is the controller via which the request is sent. The request -+ * argument is specified via the ``arg`` pointer. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_W(name, atype, spec...) \ -- int name(struct ssam_controller *ctrl, const atype *arg) \ -+ static int name(struct ssam_controller *ctrl, const atype *arg) \ - { \ - struct ssam_request_spec s = (struct ssam_request_spec)spec; \ - struct ssam_request rqst; \ -@@ -424,17 +424,17 @@ struct ssam_request_spec_md { - * request itself, returning once the request has been fully completed. The - * required transport buffer will be allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_controller -- * *ctrl, rtype *ret)``, returning the status of the request, which is zero on -- * success and negative on failure. The ``ctrl`` parameter is the controller -- * via which the request is sent. The request's return value is written to the -- * memory pointed to by the ``ret`` parameter. -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, rtype *ret)``, returning the status of the request, -+ * which is zero on success and negative on failure. The ``ctrl`` parameter is -+ * the controller via which the request is sent. The request's return value is -+ * written to the memory pointed to by the ``ret`` parameter. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_R(name, rtype, spec...) \ -- int name(struct ssam_controller *ctrl, rtype *ret) \ -+ static int name(struct ssam_controller *ctrl, rtype *ret) \ - { \ - struct ssam_request_spec s = (struct ssam_request_spec)spec; \ - struct ssam_request rqst; \ -@@ -483,17 +483,17 @@ struct ssam_request_spec_md { - * returning once the request has been fully completed. The required transport - * buffer will be allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_controller -- * *ctrl, u8 tid, u8 iid)``, returning the status of the request, which is -- * zero on success and negative on failure. The ``ctrl`` parameter is the -- * controller via which the request is sent, ``tid`` the target ID for the -- * request, and ``iid`` the instance ID. -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, u8 tid, u8 iid)``, returning the status of the -+ * request, which is zero on success and negative on failure. The ``ctrl`` -+ * parameter is the controller via which the request is sent, ``tid`` the -+ * target ID for the request, and ``iid`` the instance ID. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_MD_N(name, spec...) \ -- int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \ -+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \ - { \ - struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ - struct ssam_request rqst; \ -@@ -524,18 +524,18 @@ struct ssam_request_spec_md { - * the request itself, returning once the request has been fully completed. - * The required transport buffer will be allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_controller -- * *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the status of the -- * request, which is zero on success and negative on failure. The ``ctrl`` -- * parameter is the controller via which the request is sent, ``tid`` the -- * target ID for the request, and ``iid`` the instance ID. The request argument -- * is specified via the ``arg`` pointer. -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the -+ * status of the request, which is zero on success and negative on failure. -+ * The ``ctrl`` parameter is the controller via which the request is sent, -+ * ``tid`` the target ID for the request, and ``iid`` the instance ID. The -+ * request argument is specified via the ``arg`` pointer. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_MD_W(name, atype, spec...) \ -- int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)\ -+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg) \ - { \ - struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ - struct ssam_request rqst; \ -@@ -567,18 +567,18 @@ struct ssam_request_spec_md { - * execution of the request itself, returning once the request has been fully - * completed. The required transport buffer will be allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_controller -- * *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status of the request, -- * which is zero on success and negative on failure. The ``ctrl`` parameter is -- * the controller via which the request is sent, ``tid`` the target ID for the -- * request, and ``iid`` the instance ID. The request's return value is written -- * to the memory pointed to by the ``ret`` parameter. -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status -+ * of the request, which is zero on success and negative on failure. The -+ * ``ctrl`` parameter is the controller via which the request is sent, ``tid`` -+ * the target ID for the request, and ``iid`` the instance ID. The request's -+ * return value is written to the memory pointed to by the ``ret`` parameter. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_MD_R(name, rtype, spec...) \ -- int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \ -+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \ - { \ - struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ - struct ssam_request rqst; \ -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 02f3e06c0a60..4441ad667c3f 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -336,17 +336,18 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); - * request has been fully completed. The required transport buffer will be - * allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_device *sdev)``, -- * returning the status of the request, which is zero on success and negative -- * on failure. The ``sdev`` parameter specifies both the target device of the -- * request and by association the controller via which the request is sent. -+ * The generated function is defined as ``static int name(struct ssam_device -+ * *sdev)``, returning the status of the request, which is zero on success and -+ * negative on failure. The ``sdev`` parameter specifies both the target -+ * device of the request and by association the controller via which the -+ * request is sent. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_CL_N(name, spec...) \ - SSAM_DEFINE_SYNC_REQUEST_MD_N(__raw_##name, spec) \ -- int name(struct ssam_device *sdev) \ -+ static int name(struct ssam_device *sdev) \ - { \ - return __raw_##name(sdev->ctrl, sdev->uid.target, \ - sdev->uid.instance); \ -@@ -368,19 +369,19 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); - * itself, returning once the request has been fully completed. The required - * transport buffer will be allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_device *sdev, -- * const atype *arg)``, returning the status of the request, which is zero on -- * success and negative on failure. The ``sdev`` parameter specifies both the -- * target device of the request and by association the controller via which -- * the request is sent. The request's argument is specified via the ``arg`` -- * pointer. -+ * The generated function is defined as ``static int name(struct ssam_device -+ * *sdev, const atype *arg)``, returning the status of the request, which is -+ * zero on success and negative on failure. The ``sdev`` parameter specifies -+ * both the target device of the request and by association the controller via -+ * which the request is sent. The request's argument is specified via the -+ * ``arg`` pointer. - * - * Refer to ssam_request_sync_onstack() for more details on the behavior of - * the generated function. - */ - #define SSAM_DEFINE_SYNC_REQUEST_CL_W(name, atype, spec...) \ - SSAM_DEFINE_SYNC_REQUEST_MD_W(__raw_##name, atype, spec) \ -- int name(struct ssam_device *sdev, const atype *arg) \ -+ static int name(struct ssam_device *sdev, const atype *arg) \ - { \ - return __raw_##name(sdev->ctrl, sdev->uid.target, \ - sdev->uid.instance, arg); \ -@@ -402,8 +403,8 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); - * itself, returning once the request has been fully completed. The required - * transport buffer will be allocated on the stack. - * -- * The generated function is defined as ``int name(struct ssam_device *sdev, -- * rtype *ret)``, returning the status of the request, which is zero on -+ * The generated function is defined as ``static int name(struct ssam_device -+ * *sdev, rtype *ret)``, returning the status of the request, which is zero on - * success and negative on failure. The ``sdev`` parameter specifies both the - * target device of the request and by association the controller via which - * the request is sent. The request's return value is written to the memory -@@ -414,7 +415,7 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); - */ - #define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \ - SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \ -- int name(struct ssam_device *sdev, rtype *ret) \ -+ static int name(struct ssam_device *sdev, rtype *ret) \ - { \ - return __raw_##name(sdev->ctrl, sdev->uid.target, \ - sdev->uid.instance, ret); \ --- -2.32.0 - -From 19c9c3b67fc1ea05aea51df4959ea59056dc6e02 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 8 Mar 2021 19:48:17 +0100 -Subject: [PATCH] platform/surface: Add DTX driver - -The Microsoft Surface Book series devices consist of a so-called -clipboard part (containing the CPU, touchscreen, and primary battery) -and a base part (containing keyboard, secondary battery, and optional -discrete GPU). These parts can be separated, i.e. the clipboard can be -detached and used as tablet. - -This detachment process is initiated by pressing a button. On the -Surface Book 2 and 3 (targeted with this commit), the Surface Aggregator -Module (i.e. the embedded controller on those devices) attempts to send -a notification to any listening client driver and waits for further -instructions (i.e. whether the detachment process should continue or be -aborted). If it does not receive a response in a certain time-frame, the -detachment process (by default) continues and the clipboard can be -physically separated. In other words, (by default and) without a driver, -the detachment process takes about 10 seconds to complete. - -This commit introduces a driver for this detachment system (called DTX). -This driver allows a user-space daemon to control and influence the -detachment behavior. Specifically, it forwards any detachment requests -to user-space, allows user-space to make such requests itself, and -allows handling of those requests. Requests can be handled by either -aborting, continuing/allowing, or delaying (i.e. resetting the timeout -via a heartbeat commend). The user-space API is implemented via the -/dev/surface/dtx miscdevice. - -In addition, user-space can change the default behavior on timeout from -allowing detachment to disallowing it, which is useful if the (optional) -discrete GPU is in use. - -Furthermore, this driver allows user-space to receive notifications -about the state of the base, specifically when it is physically removed -(as opposed to detachment requested), in what manner it is connected -(i.e. in reverse-/tent-/studio- or laptop-mode), and what type of base -is connected. Based on this information, the driver also provides a -simple tablet-mode switch (aliasing all modes without keyboard access, -i.e. tablet-mode and studio-mode to its reported tablet-mode). - -An implementation of such a user-space daemon, allowing configuration of -detachment behavior via scripts (e.g. safely unmounting USB devices -connected to the base before continuing) can be found at [1]. - -[1]: https://github.com/linux-surface/surface-dtx-daemon - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210308184819.437438-2-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../userspace-api/ioctl/ioctl-number.rst | 2 + - MAINTAINERS | 7 + - drivers/platform/surface/Kconfig | 16 + - drivers/platform/surface/Makefile | 1 + - drivers/platform/surface/surface_dtx.c | 1201 +++++++++++++++++ - include/uapi/linux/surface_aggregator/dtx.h | 146 ++ - 6 files changed, 1373 insertions(+) - create mode 100644 drivers/platform/surface/surface_dtx.c - create mode 100644 include/uapi/linux/surface_aggregator/dtx.h - -diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst -index 599bd4493944..1c28b8ef6677 100644 ---- a/Documentation/userspace-api/ioctl/ioctl-number.rst -+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst -@@ -327,6 +327,8 @@ Code Seq# Include File Comments - 0xA4 00-1F uapi/asm/sgx.h - 0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator - -+0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver -+ - 0xAA 00-3F linux/uapi/linux/userfaultfd.h - 0xAB 00-1F linux/nbd.h - 0xAC 00-1F linux/raw.h -diff --git a/MAINTAINERS b/MAINTAINERS -index fce5cdcefc0b..3917e7363520 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11868,6 +11868,13 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch] - F: include/linux/cciss*.h - F: include/uapi/linux/cciss*.h - -+MICROSOFT SURFACE DTX DRIVER -+M: Maximilian Luz -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/platform/surface/surface_dtx.c -+F: include/uapi/linux/surface_aggregator/dtx.h -+ - MICROSOFT SURFACE GPE LID SUPPORT DRIVER - M: Maximilian Luz - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 6fb304da845f..41d67bb250fd 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -111,6 +111,22 @@ config SURFACE_BOOK1_DGPU_SWITCH - This driver provides a sysfs switch to set the power-state of the - discrete GPU found on the Microsoft Surface Book 1. - -+config SURFACE_DTX -+ tristate "Surface DTX (Detachment System) Driver" -+ depends on SURFACE_AGGREGATOR -+ depends on INPUT -+ help -+ Driver for the Surface Book clipboard detachment system (DTX). -+ -+ On the Surface Book series devices, the display part containing the -+ CPU (called the clipboard) can be detached from the base (containing a -+ battery, the keyboard, and, optionally, a discrete GPU) by (if -+ necessary) unlocking and opening the latch connecting both parts. -+ -+ This driver provides a user-space interface that can influence the -+ behavior of this process, which includes the option to abort it in -+ case the base is still in use or speed it up in case it is not. -+ - config SURFACE_GPE - tristate "Surface GPE/Lid Support Driver" - depends on DMI -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index f7187bae1729..0cc63440328d 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o -+obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o - obj-$(CONFIG_SURFACE_PLATFORM_PROFILE) += surface_platform_profile.o -diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c -new file mode 100644 -index 000000000000..1301fab0ea14 ---- /dev/null -+++ b/drivers/platform/surface/surface_dtx.c -@@ -0,0 +1,1201 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface Book (gen. 2 and later) detachment system (DTX) driver. -+ * -+ * Provides a user-space interface to properly handle clipboard/tablet -+ * (containing screen and processor) detachment from the base of the device -+ * (containing the keyboard and optionally a discrete GPU). Allows to -+ * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in -+ * use), or request detachment via user-space. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+ -+/* -- SSAM interface. ------------------------------------------------------- */ -+ -+enum sam_event_cid_bas { -+ SAM_EVENT_CID_DTX_CONNECTION = 0x0c, -+ SAM_EVENT_CID_DTX_REQUEST = 0x0e, -+ SAM_EVENT_CID_DTX_CANCEL = 0x0f, -+ SAM_EVENT_CID_DTX_LATCH_STATUS = 0x11, -+}; -+ -+enum ssam_bas_base_state { -+ SSAM_BAS_BASE_STATE_DETACH_SUCCESS = 0x00, -+ SSAM_BAS_BASE_STATE_ATTACHED = 0x01, -+ SSAM_BAS_BASE_STATE_NOT_FEASIBLE = 0x02, -+}; -+ -+enum ssam_bas_latch_status { -+ SSAM_BAS_LATCH_STATUS_CLOSED = 0x00, -+ SSAM_BAS_LATCH_STATUS_OPENED = 0x01, -+ SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN = 0x02, -+ SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN = 0x03, -+ SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE = 0x04, -+}; -+ -+enum ssam_bas_cancel_reason { -+ SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE = 0x00, /* Low battery. */ -+ SSAM_BAS_CANCEL_REASON_TIMEOUT = 0x02, -+ SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN = 0x03, -+ SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN = 0x04, -+ SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE = 0x05, -+}; -+ -+struct ssam_bas_base_info { -+ u8 state; -+ u8 base_id; -+} __packed; -+ -+static_assert(sizeof(struct ssam_bas_base_info) == 2); -+ -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x06, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x07, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x08, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x09, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0a, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0b, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0c, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0d, -+ .instance_id = 0x00, -+}); -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x11, -+ .instance_id = 0x00, -+}); -+ -+ -+/* -- Main structures. ------------------------------------------------------ */ -+ -+enum sdtx_device_state { -+ SDTX_DEVICE_SHUTDOWN_BIT = BIT(0), -+ SDTX_DEVICE_DIRTY_BASE_BIT = BIT(1), -+ SDTX_DEVICE_DIRTY_MODE_BIT = BIT(2), -+ SDTX_DEVICE_DIRTY_LATCH_BIT = BIT(3), -+}; -+ -+struct sdtx_device { -+ struct kref kref; -+ struct rw_semaphore lock; /* Guards device and controller reference. */ -+ -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ unsigned long flags; -+ -+ struct miscdevice mdev; -+ wait_queue_head_t waitq; -+ struct mutex write_lock; /* Guards order of events/notifications. */ -+ struct rw_semaphore client_lock; /* Guards client list. */ -+ struct list_head client_list; -+ -+ struct delayed_work state_work; -+ struct { -+ struct ssam_bas_base_info base; -+ u8 device_mode; -+ u8 latch_status; -+ } state; -+ -+ struct delayed_work mode_work; -+ struct input_dev *mode_switch; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+enum sdtx_client_state { -+ SDTX_CLIENT_EVENTS_ENABLED_BIT = BIT(0), -+}; -+ -+struct sdtx_client { -+ struct sdtx_device *ddev; -+ struct list_head node; -+ unsigned long flags; -+ -+ struct fasync_struct *fasync; -+ -+ struct mutex read_lock; /* Guards FIFO buffer read access. */ -+ DECLARE_KFIFO(buffer, u8, 512); -+}; -+ -+static void __sdtx_device_release(struct kref *kref) -+{ -+ struct sdtx_device *ddev = container_of(kref, struct sdtx_device, kref); -+ -+ mutex_destroy(&ddev->write_lock); -+ kfree(ddev); -+} -+ -+static struct sdtx_device *sdtx_device_get(struct sdtx_device *ddev) -+{ -+ if (ddev) -+ kref_get(&ddev->kref); -+ -+ return ddev; -+} -+ -+static void sdtx_device_put(struct sdtx_device *ddev) -+{ -+ if (ddev) -+ kref_put(&ddev->kref, __sdtx_device_release); -+} -+ -+ -+/* -- Firmware value translations. ------------------------------------------ */ -+ -+static u16 sdtx_translate_base_state(struct sdtx_device *ddev, u8 state) -+{ -+ switch (state) { -+ case SSAM_BAS_BASE_STATE_ATTACHED: -+ return SDTX_BASE_ATTACHED; -+ -+ case SSAM_BAS_BASE_STATE_DETACH_SUCCESS: -+ return SDTX_BASE_DETACHED; -+ -+ case SSAM_BAS_BASE_STATE_NOT_FEASIBLE: -+ return SDTX_DETACH_NOT_FEASIBLE; -+ -+ default: -+ dev_err(ddev->dev, "unknown base state: %#04x\n", state); -+ return SDTX_UNKNOWN(state); -+ } -+} -+ -+static u16 sdtx_translate_latch_status(struct sdtx_device *ddev, u8 status) -+{ -+ switch (status) { -+ case SSAM_BAS_LATCH_STATUS_CLOSED: -+ return SDTX_LATCH_CLOSED; -+ -+ case SSAM_BAS_LATCH_STATUS_OPENED: -+ return SDTX_LATCH_OPENED; -+ -+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN: -+ return SDTX_ERR_FAILED_TO_OPEN; -+ -+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN: -+ return SDTX_ERR_FAILED_TO_REMAIN_OPEN; -+ -+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE: -+ return SDTX_ERR_FAILED_TO_CLOSE; -+ -+ default: -+ dev_err(ddev->dev, "unknown latch status: %#04x\n", status); -+ return SDTX_UNKNOWN(status); -+ } -+} -+ -+static u16 sdtx_translate_cancel_reason(struct sdtx_device *ddev, u8 reason) -+{ -+ switch (reason) { -+ case SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE: -+ return SDTX_DETACH_NOT_FEASIBLE; -+ -+ case SSAM_BAS_CANCEL_REASON_TIMEOUT: -+ return SDTX_DETACH_TIMEDOUT; -+ -+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN: -+ return SDTX_ERR_FAILED_TO_OPEN; -+ -+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN: -+ return SDTX_ERR_FAILED_TO_REMAIN_OPEN; -+ -+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE: -+ return SDTX_ERR_FAILED_TO_CLOSE; -+ -+ default: -+ dev_err(ddev->dev, "unknown cancel reason: %#04x\n", reason); -+ return SDTX_UNKNOWN(reason); -+ } -+} -+ -+ -+/* -- IOCTLs. --------------------------------------------------------------- */ -+ -+static int sdtx_ioctl_get_base_info(struct sdtx_device *ddev, -+ struct sdtx_base_info __user *buf) -+{ -+ struct ssam_bas_base_info raw; -+ struct sdtx_base_info info; -+ int status; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &raw); -+ if (status < 0) -+ return status; -+ -+ info.state = sdtx_translate_base_state(ddev, raw.state); -+ info.base_id = SDTX_BASE_TYPE_SSH(raw.base_id); -+ -+ if (copy_to_user(buf, &info, sizeof(info))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static int sdtx_ioctl_get_device_mode(struct sdtx_device *ddev, u16 __user *buf) -+{ -+ u8 mode; -+ int status; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); -+ if (status < 0) -+ return status; -+ -+ return put_user(mode, buf); -+} -+ -+static int sdtx_ioctl_get_latch_status(struct sdtx_device *ddev, u16 __user *buf) -+{ -+ u8 latch; -+ int status; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch); -+ if (status < 0) -+ return status; -+ -+ return put_user(sdtx_translate_latch_status(ddev, latch), buf); -+} -+ -+static long __surface_dtx_ioctl(struct sdtx_client *client, unsigned int cmd, unsigned long arg) -+{ -+ struct sdtx_device *ddev = client->ddev; -+ -+ lockdep_assert_held_read(&ddev->lock); -+ -+ switch (cmd) { -+ case SDTX_IOCTL_EVENTS_ENABLE: -+ set_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags); -+ return 0; -+ -+ case SDTX_IOCTL_EVENTS_DISABLE: -+ clear_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags); -+ return 0; -+ -+ case SDTX_IOCTL_LATCH_LOCK: -+ return ssam_retry(ssam_bas_latch_lock, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_UNLOCK: -+ return ssam_retry(ssam_bas_latch_unlock, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_REQUEST: -+ return ssam_retry(ssam_bas_latch_request, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_CONFIRM: -+ return ssam_retry(ssam_bas_latch_confirm, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_HEARTBEAT: -+ return ssam_retry(ssam_bas_latch_heartbeat, ddev->ctrl); -+ -+ case SDTX_IOCTL_LATCH_CANCEL: -+ return ssam_retry(ssam_bas_latch_cancel, ddev->ctrl); -+ -+ case SDTX_IOCTL_GET_BASE_INFO: -+ return sdtx_ioctl_get_base_info(ddev, (struct sdtx_base_info __user *)arg); -+ -+ case SDTX_IOCTL_GET_DEVICE_MODE: -+ return sdtx_ioctl_get_device_mode(ddev, (u16 __user *)arg); -+ -+ case SDTX_IOCTL_GET_LATCH_STATUS: -+ return sdtx_ioctl_get_latch_status(ddev, (u16 __user *)arg); -+ -+ default: -+ return -EINVAL; -+ } -+} -+ -+static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -+{ -+ struct sdtx_client *client = file->private_data; -+ long status; -+ -+ if (down_read_killable(&client->ddev->lock)) -+ return -ERESTARTSYS; -+ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) { -+ up_read(&client->ddev->lock); -+ return -ENODEV; -+ } -+ -+ status = __surface_dtx_ioctl(client, cmd, arg); -+ -+ up_read(&client->ddev->lock); -+ return status; -+} -+ -+ -+/* -- File operations. ------------------------------------------------------ */ -+ -+static int surface_dtx_open(struct inode *inode, struct file *file) -+{ -+ struct sdtx_device *ddev = container_of(file->private_data, struct sdtx_device, mdev); -+ struct sdtx_client *client; -+ -+ /* Initialize client. */ -+ client = kzalloc(sizeof(*client), GFP_KERNEL); -+ if (!client) -+ return -ENOMEM; -+ -+ client->ddev = sdtx_device_get(ddev); -+ -+ INIT_LIST_HEAD(&client->node); -+ -+ mutex_init(&client->read_lock); -+ INIT_KFIFO(client->buffer); -+ -+ file->private_data = client; -+ -+ /* Attach client. */ -+ down_write(&ddev->client_lock); -+ -+ /* -+ * Do not add a new client if the device has been shut down. Note that -+ * it's enough to hold the client_lock here as, during shutdown, we -+ * only acquire that lock and remove clients after marking the device -+ * as shut down. -+ */ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { -+ up_write(&ddev->client_lock); -+ sdtx_device_put(client->ddev); -+ kfree(client); -+ return -ENODEV; -+ } -+ -+ list_add_tail(&client->node, &ddev->client_list); -+ up_write(&ddev->client_lock); -+ -+ stream_open(inode, file); -+ return 0; -+} -+ -+static int surface_dtx_release(struct inode *inode, struct file *file) -+{ -+ struct sdtx_client *client = file->private_data; -+ -+ /* Detach client. */ -+ down_write(&client->ddev->client_lock); -+ list_del(&client->node); -+ up_write(&client->ddev->client_lock); -+ -+ /* Free client. */ -+ sdtx_device_put(client->ddev); -+ mutex_destroy(&client->read_lock); -+ kfree(client); -+ -+ return 0; -+} -+ -+static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs) -+{ -+ struct sdtx_client *client = file->private_data; -+ struct sdtx_device *ddev = client->ddev; -+ unsigned int copied; -+ int status = 0; -+ -+ if (down_read_killable(&ddev->lock)) -+ return -ERESTARTSYS; -+ -+ /* Make sure we're not shut down. */ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { -+ up_read(&ddev->lock); -+ return -ENODEV; -+ } -+ -+ do { -+ /* Check availability, wait if necessary. */ -+ if (kfifo_is_empty(&client->buffer)) { -+ up_read(&ddev->lock); -+ -+ if (file->f_flags & O_NONBLOCK) -+ return -EAGAIN; -+ -+ status = wait_event_interruptible(ddev->waitq, -+ !kfifo_is_empty(&client->buffer) || -+ test_bit(SDTX_DEVICE_SHUTDOWN_BIT, -+ &ddev->flags)); -+ if (status < 0) -+ return status; -+ -+ if (down_read_killable(&client->ddev->lock)) -+ return -ERESTARTSYS; -+ -+ /* Need to check that we're not shut down again. */ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { -+ up_read(&ddev->lock); -+ return -ENODEV; -+ } -+ } -+ -+ /* Try to read from FIFO. */ -+ if (mutex_lock_interruptible(&client->read_lock)) { -+ up_read(&ddev->lock); -+ return -ERESTARTSYS; -+ } -+ -+ status = kfifo_to_user(&client->buffer, buf, count, &copied); -+ mutex_unlock(&client->read_lock); -+ -+ if (status < 0) { -+ up_read(&ddev->lock); -+ return status; -+ } -+ -+ /* We might not have gotten anything, check this here. */ -+ if (copied == 0 && (file->f_flags & O_NONBLOCK)) { -+ up_read(&ddev->lock); -+ return -EAGAIN; -+ } -+ } while (copied == 0); -+ -+ up_read(&ddev->lock); -+ return copied; -+} -+ -+static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt) -+{ -+ struct sdtx_client *client = file->private_data; -+ __poll_t events = 0; -+ -+ if (down_read_killable(&client->ddev->lock)) -+ return -ERESTARTSYS; -+ -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) { -+ up_read(&client->ddev->lock); -+ return EPOLLHUP | EPOLLERR; -+ } -+ -+ poll_wait(file, &client->ddev->waitq, pt); -+ -+ if (!kfifo_is_empty(&client->buffer)) -+ events |= EPOLLIN | EPOLLRDNORM; -+ -+ up_read(&client->ddev->lock); -+ return events; -+} -+ -+static int surface_dtx_fasync(int fd, struct file *file, int on) -+{ -+ struct sdtx_client *client = file->private_data; -+ -+ return fasync_helper(fd, file, on, &client->fasync); -+} -+ -+static const struct file_operations surface_dtx_fops = { -+ .owner = THIS_MODULE, -+ .open = surface_dtx_open, -+ .release = surface_dtx_release, -+ .read = surface_dtx_read, -+ .poll = surface_dtx_poll, -+ .fasync = surface_dtx_fasync, -+ .unlocked_ioctl = surface_dtx_ioctl, -+ .compat_ioctl = surface_dtx_ioctl, -+ .llseek = no_llseek, -+}; -+ -+ -+/* -- Event handling/forwarding. -------------------------------------------- */ -+ -+/* -+ * The device operation mode is not immediately updated on the EC when the -+ * base has been connected, i.e. querying the device mode inside the -+ * connection event callback yields an outdated value. Thus, we can only -+ * determine the new tablet-mode switch and device mode values after some -+ * time. -+ * -+ * These delays have been chosen by experimenting. We first delay on connect -+ * events, then check and validate the device mode against the base state and -+ * if invalid delay again by the "recheck" delay. -+ */ -+#define SDTX_DEVICE_MODE_DELAY_CONNECT msecs_to_jiffies(100) -+#define SDTX_DEVICE_MODE_DELAY_RECHECK msecs_to_jiffies(100) -+ -+struct sdtx_status_event { -+ struct sdtx_event e; -+ __u16 v; -+} __packed; -+ -+struct sdtx_base_info_event { -+ struct sdtx_event e; -+ struct sdtx_base_info v; -+} __packed; -+ -+union sdtx_generic_event { -+ struct sdtx_event common; -+ struct sdtx_status_event status; -+ struct sdtx_base_info_event base; -+}; -+ -+static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay); -+ -+/* Must be executed with ddev->write_lock held. */ -+static void sdtx_push_event(struct sdtx_device *ddev, struct sdtx_event *evt) -+{ -+ const size_t len = sizeof(struct sdtx_event) + evt->length; -+ struct sdtx_client *client; -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ down_read(&ddev->client_lock); -+ list_for_each_entry(client, &ddev->client_list, node) { -+ if (!test_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags)) -+ continue; -+ -+ if (likely(kfifo_avail(&client->buffer) >= len)) -+ kfifo_in(&client->buffer, (const u8 *)evt, len); -+ else -+ dev_warn(ddev->dev, "event buffer overrun\n"); -+ -+ kill_fasync(&client->fasync, SIGIO, POLL_IN); -+ } -+ up_read(&ddev->client_lock); -+ -+ wake_up_interruptible(&ddev->waitq); -+} -+ -+static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in) -+{ -+ struct sdtx_device *ddev = container_of(nf, struct sdtx_device, notif); -+ union sdtx_generic_event event; -+ size_t len; -+ -+ /* Validate event payload length. */ -+ switch (in->command_id) { -+ case SAM_EVENT_CID_DTX_CONNECTION: -+ len = 2 * sizeof(u8); -+ break; -+ -+ case SAM_EVENT_CID_DTX_REQUEST: -+ len = 0; -+ break; -+ -+ case SAM_EVENT_CID_DTX_CANCEL: -+ len = sizeof(u8); -+ break; -+ -+ case SAM_EVENT_CID_DTX_LATCH_STATUS: -+ len = sizeof(u8); -+ break; -+ -+ default: -+ return 0; -+ }; -+ -+ if (in->length != len) { -+ dev_err(ddev->dev, -+ "unexpected payload size for event %#04x: got %u, expected %zu\n", -+ in->command_id, in->length, len); -+ return 0; -+ } -+ -+ mutex_lock(&ddev->write_lock); -+ -+ /* Translate event. */ -+ switch (in->command_id) { -+ case SAM_EVENT_CID_DTX_CONNECTION: -+ clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags); -+ -+ /* If state has not changed: do not send new event. */ -+ if (ddev->state.base.state == in->data[0] && -+ ddev->state.base.base_id == in->data[1]) -+ goto out; -+ -+ ddev->state.base.state = in->data[0]; -+ ddev->state.base.base_id = in->data[1]; -+ -+ event.base.e.length = sizeof(struct sdtx_base_info); -+ event.base.e.code = SDTX_EVENT_BASE_CONNECTION; -+ event.base.v.state = sdtx_translate_base_state(ddev, in->data[0]); -+ event.base.v.base_id = SDTX_BASE_TYPE_SSH(in->data[1]); -+ break; -+ -+ case SAM_EVENT_CID_DTX_REQUEST: -+ event.common.code = SDTX_EVENT_REQUEST; -+ event.common.length = 0; -+ break; -+ -+ case SAM_EVENT_CID_DTX_CANCEL: -+ event.status.e.length = sizeof(u16); -+ event.status.e.code = SDTX_EVENT_CANCEL; -+ event.status.v = sdtx_translate_cancel_reason(ddev, in->data[0]); -+ break; -+ -+ case SAM_EVENT_CID_DTX_LATCH_STATUS: -+ clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags); -+ -+ /* If state has not changed: do not send new event. */ -+ if (ddev->state.latch_status == in->data[0]) -+ goto out; -+ -+ ddev->state.latch_status = in->data[0]; -+ -+ event.status.e.length = sizeof(u16); -+ event.status.e.code = SDTX_EVENT_LATCH_STATUS; -+ event.status.v = sdtx_translate_latch_status(ddev, in->data[0]); -+ break; -+ } -+ -+ sdtx_push_event(ddev, &event.common); -+ -+ /* Update device mode on base connection change. */ -+ if (in->command_id == SAM_EVENT_CID_DTX_CONNECTION) { -+ unsigned long delay; -+ -+ delay = in->data[0] ? SDTX_DEVICE_MODE_DELAY_CONNECT : 0; -+ sdtx_update_device_mode(ddev, delay); -+ } -+ -+out: -+ mutex_unlock(&ddev->write_lock); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+ -+/* -- State update functions. ----------------------------------------------- */ -+ -+static bool sdtx_device_mode_invalid(u8 mode, u8 base_state) -+{ -+ return ((base_state == SSAM_BAS_BASE_STATE_ATTACHED) && -+ (mode == SDTX_DEVICE_MODE_TABLET)) || -+ ((base_state == SSAM_BAS_BASE_STATE_DETACH_SUCCESS) && -+ (mode != SDTX_DEVICE_MODE_TABLET)); -+} -+ -+static void sdtx_device_mode_workfn(struct work_struct *work) -+{ -+ struct sdtx_device *ddev = container_of(work, struct sdtx_device, mode_work.work); -+ struct sdtx_status_event event; -+ struct ssam_bas_base_info base; -+ int status, tablet; -+ u8 mode; -+ -+ /* Get operation mode. */ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); -+ if (status) { -+ dev_err(ddev->dev, "failed to get device mode: %d\n", status); -+ return; -+ } -+ -+ /* Get base info. */ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base); -+ if (status) { -+ dev_err(ddev->dev, "failed to get base info: %d\n", status); -+ return; -+ } -+ -+ /* -+ * In some cases (specifically when attaching the base), the device -+ * mode isn't updated right away. Thus we check if the device mode -+ * makes sense for the given base state and try again later if it -+ * doesn't. -+ */ -+ if (sdtx_device_mode_invalid(mode, base.state)) { -+ dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); -+ sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); -+ return; -+ } -+ -+ mutex_lock(&ddev->write_lock); -+ clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags); -+ -+ /* Avoid sending duplicate device-mode events. */ -+ if (ddev->state.device_mode == mode) { -+ mutex_unlock(&ddev->write_lock); -+ return; -+ } -+ -+ ddev->state.device_mode = mode; -+ -+ event.e.length = sizeof(u16); -+ event.e.code = SDTX_EVENT_DEVICE_MODE; -+ event.v = mode; -+ -+ sdtx_push_event(ddev, &event.e); -+ -+ /* Send SW_TABLET_MODE event. */ -+ tablet = mode != SDTX_DEVICE_MODE_LAPTOP; -+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); -+ input_sync(ddev->mode_switch); -+ -+ mutex_unlock(&ddev->write_lock); -+} -+ -+static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay) -+{ -+ schedule_delayed_work(&ddev->mode_work, delay); -+} -+ -+/* Must be executed with ddev->write_lock held. */ -+static void __sdtx_device_state_update_base(struct sdtx_device *ddev, -+ struct ssam_bas_base_info info) -+{ -+ struct sdtx_base_info_event event; -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ /* Prevent duplicate events. */ -+ if (ddev->state.base.state == info.state && -+ ddev->state.base.base_id == info.base_id) -+ return; -+ -+ ddev->state.base = info; -+ -+ event.e.length = sizeof(struct sdtx_base_info); -+ event.e.code = SDTX_EVENT_BASE_CONNECTION; -+ event.v.state = sdtx_translate_base_state(ddev, info.state); -+ event.v.base_id = SDTX_BASE_TYPE_SSH(info.base_id); -+ -+ sdtx_push_event(ddev, &event.e); -+} -+ -+/* Must be executed with ddev->write_lock held. */ -+static void __sdtx_device_state_update_mode(struct sdtx_device *ddev, u8 mode) -+{ -+ struct sdtx_status_event event; -+ int tablet; -+ -+ /* -+ * Note: This function must be called after updating the base state -+ * via __sdtx_device_state_update_base(), as we rely on the updated -+ * base state value in the validity check below. -+ */ -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ if (sdtx_device_mode_invalid(mode, ddev->state.base.state)) { -+ dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); -+ sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); -+ return; -+ } -+ -+ /* Prevent duplicate events. */ -+ if (ddev->state.device_mode == mode) -+ return; -+ -+ ddev->state.device_mode = mode; -+ -+ /* Send event. */ -+ event.e.length = sizeof(u16); -+ event.e.code = SDTX_EVENT_DEVICE_MODE; -+ event.v = mode; -+ -+ sdtx_push_event(ddev, &event.e); -+ -+ /* Send SW_TABLET_MODE event. */ -+ tablet = mode != SDTX_DEVICE_MODE_LAPTOP; -+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); -+ input_sync(ddev->mode_switch); -+} -+ -+/* Must be executed with ddev->write_lock held. */ -+static void __sdtx_device_state_update_latch(struct sdtx_device *ddev, u8 status) -+{ -+ struct sdtx_status_event event; -+ -+ lockdep_assert_held(&ddev->write_lock); -+ -+ /* Prevent duplicate events. */ -+ if (ddev->state.latch_status == status) -+ return; -+ -+ ddev->state.latch_status = status; -+ -+ event.e.length = sizeof(struct sdtx_base_info); -+ event.e.code = SDTX_EVENT_BASE_CONNECTION; -+ event.v = sdtx_translate_latch_status(ddev, status); -+ -+ sdtx_push_event(ddev, &event.e); -+} -+ -+static void sdtx_device_state_workfn(struct work_struct *work) -+{ -+ struct sdtx_device *ddev = container_of(work, struct sdtx_device, state_work.work); -+ struct ssam_bas_base_info base; -+ u8 mode, latch; -+ int status; -+ -+ /* Mark everything as dirty. */ -+ set_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags); -+ set_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags); -+ set_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags); -+ -+ /* -+ * Ensure that the state gets marked as dirty before continuing to -+ * query it. Necessary to ensure that clear_bit() calls in -+ * sdtx_notifier() and sdtx_device_mode_workfn() actually clear these -+ * bits if an event is received while updating the state here. -+ */ -+ smp_mb__after_atomic(); -+ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base); -+ if (status) { -+ dev_err(ddev->dev, "failed to get base state: %d\n", status); -+ return; -+ } -+ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode); -+ if (status) { -+ dev_err(ddev->dev, "failed to get device mode: %d\n", status); -+ return; -+ } -+ -+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch); -+ if (status) { -+ dev_err(ddev->dev, "failed to get latch status: %d\n", status); -+ return; -+ } -+ -+ mutex_lock(&ddev->write_lock); -+ -+ /* -+ * If the respective dirty-bit has been cleared, an event has been -+ * received, updating this state. The queried state may thus be out of -+ * date. At this point, we can safely assume that the state provided -+ * by the event is either up to date, or we're about to receive -+ * another event updating it. -+ */ -+ -+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags)) -+ __sdtx_device_state_update_base(ddev, base); -+ -+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags)) -+ __sdtx_device_state_update_mode(ddev, mode); -+ -+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags)) -+ __sdtx_device_state_update_latch(ddev, latch); -+ -+ mutex_unlock(&ddev->write_lock); -+} -+ -+static void sdtx_update_device_state(struct sdtx_device *ddev, unsigned long delay) -+{ -+ schedule_delayed_work(&ddev->state_work, delay); -+} -+ -+ -+/* -- Common device initialization. ----------------------------------------- */ -+ -+static int sdtx_device_init(struct sdtx_device *ddev, struct device *dev, -+ struct ssam_controller *ctrl) -+{ -+ int status, tablet_mode; -+ -+ /* Basic initialization. */ -+ kref_init(&ddev->kref); -+ init_rwsem(&ddev->lock); -+ ddev->dev = dev; -+ ddev->ctrl = ctrl; -+ -+ ddev->mdev.minor = MISC_DYNAMIC_MINOR; -+ ddev->mdev.name = "surface_dtx"; -+ ddev->mdev.nodename = "surface/dtx"; -+ ddev->mdev.fops = &surface_dtx_fops; -+ -+ ddev->notif.base.priority = 1; -+ ddev->notif.base.fn = sdtx_notifier; -+ ddev->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ ddev->notif.event.id.target_category = SSAM_SSH_TC_BAS; -+ ddev->notif.event.id.instance = 0; -+ ddev->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ ddev->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ init_waitqueue_head(&ddev->waitq); -+ mutex_init(&ddev->write_lock); -+ init_rwsem(&ddev->client_lock); -+ INIT_LIST_HEAD(&ddev->client_list); -+ -+ INIT_DELAYED_WORK(&ddev->mode_work, sdtx_device_mode_workfn); -+ INIT_DELAYED_WORK(&ddev->state_work, sdtx_device_state_workfn); -+ -+ /* -+ * Get current device state. We want to guarantee that events are only -+ * sent when state actually changes. Thus we cannot use special -+ * "uninitialized" values, as that would cause problems when manually -+ * querying the state in surface_dtx_pm_complete(). I.e. we would not -+ * be able to detect state changes there if no change event has been -+ * received between driver initialization and first device suspension. -+ * -+ * Note that we also need to do this before registering the event -+ * notifier, as that may access the state values. -+ */ -+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &ddev->state.base); -+ if (status) -+ return status; -+ -+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &ddev->state.device_mode); -+ if (status) -+ return status; -+ -+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &ddev->state.latch_status); -+ if (status) -+ return status; -+ -+ /* Set up tablet mode switch. */ -+ ddev->mode_switch = input_allocate_device(); -+ if (!ddev->mode_switch) -+ return -ENOMEM; -+ -+ ddev->mode_switch->name = "Microsoft Surface DTX Device Mode Switch"; -+ ddev->mode_switch->phys = "ssam/01:11:01:00:00/input0"; -+ ddev->mode_switch->id.bustype = BUS_HOST; -+ ddev->mode_switch->dev.parent = ddev->dev; -+ -+ tablet_mode = (ddev->state.device_mode != SDTX_DEVICE_MODE_LAPTOP); -+ input_set_capability(ddev->mode_switch, EV_SW, SW_TABLET_MODE); -+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet_mode); -+ -+ status = input_register_device(ddev->mode_switch); -+ if (status) { -+ input_free_device(ddev->mode_switch); -+ return status; -+ } -+ -+ /* Set up event notifier. */ -+ status = ssam_notifier_register(ddev->ctrl, &ddev->notif); -+ if (status) -+ goto err_notif; -+ -+ /* Register miscdevice. */ -+ status = misc_register(&ddev->mdev); -+ if (status) -+ goto err_mdev; -+ -+ /* -+ * Update device state in case it has changed between getting the -+ * initial mode and registering the event notifier. -+ */ -+ sdtx_update_device_state(ddev, 0); -+ return 0; -+ -+err_notif: -+ ssam_notifier_unregister(ddev->ctrl, &ddev->notif); -+ cancel_delayed_work_sync(&ddev->mode_work); -+err_mdev: -+ input_unregister_device(ddev->mode_switch); -+ return status; -+} -+ -+static struct sdtx_device *sdtx_device_create(struct device *dev, struct ssam_controller *ctrl) -+{ -+ struct sdtx_device *ddev; -+ int status; -+ -+ ddev = kzalloc(sizeof(*ddev), GFP_KERNEL); -+ if (!ddev) -+ return ERR_PTR(-ENOMEM); -+ -+ status = sdtx_device_init(ddev, dev, ctrl); -+ if (status) { -+ sdtx_device_put(ddev); -+ return ERR_PTR(status); -+ } -+ -+ return ddev; -+} -+ -+static void sdtx_device_destroy(struct sdtx_device *ddev) -+{ -+ struct sdtx_client *client; -+ -+ /* -+ * Mark device as shut-down. Prevent new clients from being added and -+ * new operations from being executed. -+ */ -+ set_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags); -+ -+ /* Disable notifiers, prevent new events from arriving. */ -+ ssam_notifier_unregister(ddev->ctrl, &ddev->notif); -+ -+ /* Stop mode_work, prevent access to mode_switch. */ -+ cancel_delayed_work_sync(&ddev->mode_work); -+ -+ /* Stop state_work. */ -+ cancel_delayed_work_sync(&ddev->state_work); -+ -+ /* With mode_work canceled, we can unregister the mode_switch. */ -+ input_unregister_device(ddev->mode_switch); -+ -+ /* Wake up async clients. */ -+ down_write(&ddev->client_lock); -+ list_for_each_entry(client, &ddev->client_list, node) { -+ kill_fasync(&client->fasync, SIGIO, POLL_HUP); -+ } -+ up_write(&ddev->client_lock); -+ -+ /* Wake up blocking clients. */ -+ wake_up_interruptible(&ddev->waitq); -+ -+ /* -+ * Wait for clients to finish their current operation. After this, the -+ * controller and device references are guaranteed to be no longer in -+ * use. -+ */ -+ down_write(&ddev->lock); -+ ddev->dev = NULL; -+ ddev->ctrl = NULL; -+ up_write(&ddev->lock); -+ -+ /* Finally remove the misc-device. */ -+ misc_deregister(&ddev->mdev); -+ -+ /* -+ * We're now guaranteed that sdtx_device_open() won't be called any -+ * more, so we can now drop out reference. -+ */ -+ sdtx_device_put(ddev); -+} -+ -+ -+/* -- PM ops. --------------------------------------------------------------- */ -+ -+#ifdef CONFIG_PM_SLEEP -+ -+static void surface_dtx_pm_complete(struct device *dev) -+{ -+ struct sdtx_device *ddev = dev_get_drvdata(dev); -+ -+ /* -+ * Normally, the EC will store events while suspended (i.e. in -+ * display-off state) and release them when resumed (i.e. transitioned -+ * to display-on state). During hibernation, however, the EC will be -+ * shut down and does not store events. Furthermore, events might be -+ * dropped during prolonged suspension (it is currently unknown how -+ * big this event buffer is and how it behaves on overruns). -+ * -+ * To prevent any problems, we update the device state here. We do -+ * this delayed to ensure that any events sent by the EC directly -+ * after resuming will be handled first. The delay below has been -+ * chosen (experimentally), so that there should be ample time for -+ * these events to be handled, before we check and, if necessary, -+ * update the state. -+ */ -+ sdtx_update_device_state(ddev, msecs_to_jiffies(1000)); -+} -+ -+static const struct dev_pm_ops surface_dtx_pm_ops = { -+ .complete = surface_dtx_pm_complete, -+}; -+ -+#else /* CONFIG_PM_SLEEP */ -+ -+static const struct dev_pm_ops surface_dtx_pm_ops = {}; -+ -+#endif /* CONFIG_PM_SLEEP */ -+ -+ -+/* -- Platform driver. ------------------------------------------------------ */ -+ -+static int surface_dtx_platform_probe(struct platform_device *pdev) -+{ -+ struct ssam_controller *ctrl; -+ struct sdtx_device *ddev; -+ -+ /* Link to EC. */ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ ddev = sdtx_device_create(&pdev->dev, ctrl); -+ if (IS_ERR(ddev)) -+ return PTR_ERR(ddev); -+ -+ platform_set_drvdata(pdev, ddev); -+ return 0; -+} -+ -+static int surface_dtx_platform_remove(struct platform_device *pdev) -+{ -+ sdtx_device_destroy(platform_get_drvdata(pdev)); -+ return 0; -+} -+ -+static const struct acpi_device_id surface_dtx_acpi_match[] = { -+ { "MSHW0133", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_dtx_acpi_match); -+ -+static struct platform_driver surface_dtx_platform_driver = { -+ .probe = surface_dtx_platform_probe, -+ .remove = surface_dtx_platform_remove, -+ .driver = { -+ .name = "surface_dtx_pltf", -+ .acpi_match_table = surface_dtx_acpi_match, -+ .pm = &surface_dtx_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_dtx_platform_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/include/uapi/linux/surface_aggregator/dtx.h b/include/uapi/linux/surface_aggregator/dtx.h -new file mode 100644 -index 000000000000..0833aab0d819 ---- /dev/null -+++ b/include/uapi/linux/surface_aggregator/dtx.h -@@ -0,0 +1,146 @@ -+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ -+/* -+ * Surface DTX (clipboard detachment system driver) user-space interface. -+ * -+ * Definitions, structs, and IOCTLs for the /dev/surface/dtx misc device. This -+ * device allows user-space to control the clipboard detachment process on -+ * Surface Book series devices. -+ * -+ * Copyright (C) 2020-2021 Maximilian Luz -+ */ -+ -+#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H -+#define _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H -+ -+#include -+#include -+ -+/* Status/error categories */ -+#define SDTX_CATEGORY_STATUS 0x0000 -+#define SDTX_CATEGORY_RUNTIME_ERROR 0x1000 -+#define SDTX_CATEGORY_HARDWARE_ERROR 0x2000 -+#define SDTX_CATEGORY_UNKNOWN 0xf000 -+ -+#define SDTX_CATEGORY_MASK 0xf000 -+#define SDTX_CATEGORY(value) ((value) & SDTX_CATEGORY_MASK) -+ -+#define SDTX_STATUS(code) ((code) | SDTX_CATEGORY_STATUS) -+#define SDTX_ERR_RT(code) ((code) | SDTX_CATEGORY_RUNTIME_ERROR) -+#define SDTX_ERR_HW(code) ((code) | SDTX_CATEGORY_HARDWARE_ERROR) -+#define SDTX_UNKNOWN(code) ((code) | SDTX_CATEGORY_UNKNOWN) -+ -+#define SDTX_SUCCESS(value) (SDTX_CATEGORY(value) == SDTX_CATEGORY_STATUS) -+ -+/* Latch status values */ -+#define SDTX_LATCH_CLOSED SDTX_STATUS(0x00) -+#define SDTX_LATCH_OPENED SDTX_STATUS(0x01) -+ -+/* Base state values */ -+#define SDTX_BASE_DETACHED SDTX_STATUS(0x00) -+#define SDTX_BASE_ATTACHED SDTX_STATUS(0x01) -+ -+/* Runtime errors (non-critical) */ -+#define SDTX_DETACH_NOT_FEASIBLE SDTX_ERR_RT(0x01) -+#define SDTX_DETACH_TIMEDOUT SDTX_ERR_RT(0x02) -+ -+/* Hardware errors (critical) */ -+#define SDTX_ERR_FAILED_TO_OPEN SDTX_ERR_HW(0x01) -+#define SDTX_ERR_FAILED_TO_REMAIN_OPEN SDTX_ERR_HW(0x02) -+#define SDTX_ERR_FAILED_TO_CLOSE SDTX_ERR_HW(0x03) -+ -+/* Base types */ -+#define SDTX_DEVICE_TYPE_HID 0x0100 -+#define SDTX_DEVICE_TYPE_SSH 0x0200 -+ -+#define SDTX_DEVICE_TYPE_MASK 0x0f00 -+#define SDTX_DEVICE_TYPE(value) ((value) & SDTX_DEVICE_TYPE_MASK) -+ -+#define SDTX_BASE_TYPE_HID(id) ((id) | SDTX_DEVICE_TYPE_HID) -+#define SDTX_BASE_TYPE_SSH(id) ((id) | SDTX_DEVICE_TYPE_SSH) -+ -+/** -+ * enum sdtx_device_mode - Mode describing how (and if) the clipboard is -+ * attached to the base of the device. -+ * @SDTX_DEVICE_MODE_TABLET: The clipboard is detached from the base and the -+ * device operates as tablet. -+ * @SDTX_DEVICE_MODE_LAPTOP: The clipboard is attached normally to the base -+ * and the device operates as laptop. -+ * @SDTX_DEVICE_MODE_STUDIO: The clipboard is attached to the base in reverse. -+ * The device operates as tablet with keyboard and -+ * touchpad deactivated, however, the base battery -+ * and, if present in the specific device model, dGPU -+ * are available to the system. -+ */ -+enum sdtx_device_mode { -+ SDTX_DEVICE_MODE_TABLET = 0x00, -+ SDTX_DEVICE_MODE_LAPTOP = 0x01, -+ SDTX_DEVICE_MODE_STUDIO = 0x02, -+}; -+ -+/** -+ * struct sdtx_event - Event provided by reading from the DTX device file. -+ * @length: Length of the event payload, in bytes. -+ * @code: Event code, detailing what type of event this is. -+ * @data: Payload of the event, containing @length bytes. -+ * -+ * See &enum sdtx_event_code for currently valid event codes. -+ */ -+struct sdtx_event { -+ __u16 length; -+ __u16 code; -+ __u8 data[]; -+} __attribute__((__packed__)); -+ -+/** -+ * enum sdtx_event_code - Code describing the type of an event. -+ * @SDTX_EVENT_REQUEST: Detachment request event type. -+ * @SDTX_EVENT_CANCEL: Cancel detachment process event type. -+ * @SDTX_EVENT_BASE_CONNECTION: Base/clipboard connection change event type. -+ * @SDTX_EVENT_LATCH_STATUS: Latch status change event type. -+ * @SDTX_EVENT_DEVICE_MODE: Device mode change event type. -+ * -+ * Used in &struct sdtx_event to describe the type of the event. Further event -+ * codes are reserved for future use. Any event parser should be able to -+ * gracefully handle unknown events, i.e. by simply skipping them. -+ * -+ * Consult the DTX user-space interface documentation for details regarding -+ * the individual event types. -+ */ -+enum sdtx_event_code { -+ SDTX_EVENT_REQUEST = 1, -+ SDTX_EVENT_CANCEL = 2, -+ SDTX_EVENT_BASE_CONNECTION = 3, -+ SDTX_EVENT_LATCH_STATUS = 4, -+ SDTX_EVENT_DEVICE_MODE = 5, -+}; -+ -+/** -+ * struct sdtx_base_info - Describes if and what type of base is connected. -+ * @state: The state of the connection. Valid values are %SDTX_BASE_DETACHED, -+ * %SDTX_BASE_ATTACHED, and %SDTX_DETACH_NOT_FEASIBLE (in case a base -+ * is attached but low clipboard battery prevents detachment). Other -+ * values are currently reserved. -+ * @base_id: The type of base connected. Zero if no base is connected. -+ */ -+struct sdtx_base_info { -+ __u16 state; -+ __u16 base_id; -+} __attribute__((__packed__)); -+ -+/* IOCTLs */ -+#define SDTX_IOCTL_EVENTS_ENABLE _IO(0xa5, 0x21) -+#define SDTX_IOCTL_EVENTS_DISABLE _IO(0xa5, 0x22) -+ -+#define SDTX_IOCTL_LATCH_LOCK _IO(0xa5, 0x23) -+#define SDTX_IOCTL_LATCH_UNLOCK _IO(0xa5, 0x24) -+ -+#define SDTX_IOCTL_LATCH_REQUEST _IO(0xa5, 0x25) -+#define SDTX_IOCTL_LATCH_CONFIRM _IO(0xa5, 0x26) -+#define SDTX_IOCTL_LATCH_HEARTBEAT _IO(0xa5, 0x27) -+#define SDTX_IOCTL_LATCH_CANCEL _IO(0xa5, 0x28) -+ -+#define SDTX_IOCTL_GET_BASE_INFO _IOR(0xa5, 0x29, struct sdtx_base_info) -+#define SDTX_IOCTL_GET_DEVICE_MODE _IOR(0xa5, 0x2a, __u16) -+#define SDTX_IOCTL_GET_LATCH_STATUS _IOR(0xa5, 0x2b, __u16) -+ -+#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H */ --- -2.32.0 - -From 710cbcbc3064eb0815f9f82a67762e9438fc6f94 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 8 Mar 2021 19:48:18 +0100 -Subject: [PATCH] platform/surface: dtx: Add support for native SSAM devices - -Add support for native SSAM devices to the DTX driver. This allows -support for the Surface Book 3, on which the DTX device is not present -in ACPI. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210308184819.437438-3-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/Kconfig | 4 ++ - drivers/platform/surface/surface_dtx.c | 90 +++++++++++++++++++++++++- - 2 files changed, 93 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 41d67bb250fd..53beaedefdd1 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -127,6 +127,10 @@ config SURFACE_DTX - behavior of this process, which includes the option to abort it in - case the base is still in use or speed it up in case it is not. - -+ Note that this module can be built without support for the Surface -+ Aggregator Bus (i.e. CONFIG_SURFACE_AGGREGATOR_BUS=n). In that case, -+ some devices, specifically the Surface Book 3, will not be supported. -+ - config SURFACE_GPE - tristate "Surface GPE/Lid Support Driver" - depends on DMI -diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c -index 1301fab0ea14..85451eb94d98 100644 ---- a/drivers/platform/surface/surface_dtx.c -+++ b/drivers/platform/surface/surface_dtx.c -@@ -27,6 +27,7 @@ - #include - - #include -+#include - #include - - -@@ -1194,7 +1195,94 @@ static struct platform_driver surface_dtx_platform_driver = { - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - }, - }; --module_platform_driver(surface_dtx_platform_driver); -+ -+ -+/* -- SSAM device driver. --------------------------------------------------- */ -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+ -+static int surface_dtx_ssam_probe(struct ssam_device *sdev) -+{ -+ struct sdtx_device *ddev; -+ -+ ddev = sdtx_device_create(&sdev->dev, sdev->ctrl); -+ if (IS_ERR(ddev)) -+ return PTR_ERR(ddev); -+ -+ ssam_device_set_drvdata(sdev, ddev); -+ return 0; -+} -+ -+static void surface_dtx_ssam_remove(struct ssam_device *sdev) -+{ -+ sdtx_device_destroy(ssam_device_get_drvdata(sdev)); -+} -+ -+static const struct ssam_device_id surface_dtx_ssam_match[] = { -+ { SSAM_SDEV(BAS, 0x01, 0x00, 0x00) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match); -+ -+static struct ssam_device_driver surface_dtx_ssam_driver = { -+ .probe = surface_dtx_ssam_probe, -+ .remove = surface_dtx_ssam_remove, -+ .match_table = surface_dtx_ssam_match, -+ .driver = { -+ .name = "surface_dtx", -+ .pm = &surface_dtx_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+static int ssam_dtx_driver_register(void) -+{ -+ return ssam_device_driver_register(&surface_dtx_ssam_driver); -+} -+ -+static void ssam_dtx_driver_unregister(void) -+{ -+ ssam_device_driver_unregister(&surface_dtx_ssam_driver); -+} -+ -+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+static int ssam_dtx_driver_register(void) -+{ -+ return 0; -+} -+ -+static void ssam_dtx_driver_unregister(void) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+ -+/* -- Module setup. --------------------------------------------------------- */ -+ -+static int __init surface_dtx_init(void) -+{ -+ int status; -+ -+ status = ssam_dtx_driver_register(); -+ if (status) -+ return status; -+ -+ status = platform_driver_register(&surface_dtx_platform_driver); -+ if (status) -+ ssam_dtx_driver_unregister(); -+ -+ return status; -+} -+module_init(surface_dtx_init); -+ -+static void __exit surface_dtx_exit(void) -+{ -+ platform_driver_unregister(&surface_dtx_platform_driver); -+ ssam_dtx_driver_unregister(); -+} -+module_exit(surface_dtx_exit); - - MODULE_AUTHOR("Maximilian Luz "); - MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module"); --- -2.32.0 - -From 6c4d22698f9e940ae13d6f58d46f28f2ae90fde9 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 8 Mar 2021 19:48:19 +0100 -Subject: [PATCH] docs: driver-api: Add Surface DTX driver documentation - -Add documentation for the user-space interface of the Surface DTX -(detachment system) driver, used on Microsoft Surface Book series -devices. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210308184819.437438-4-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface_aggregator/clients/dtx.rst | 718 ++++++++++++++++++ - .../surface_aggregator/clients/index.rst | 1 + - MAINTAINERS | 1 + - 3 files changed, 720 insertions(+) - create mode 100644 Documentation/driver-api/surface_aggregator/clients/dtx.rst - -diff --git a/Documentation/driver-api/surface_aggregator/clients/dtx.rst b/Documentation/driver-api/surface_aggregator/clients/dtx.rst -new file mode 100644 -index 000000000000..e7e7c20007f0 ---- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/clients/dtx.rst -@@ -0,0 +1,718 @@ -+.. SPDX-License-Identifier: GPL-2.0+ -+ -+.. |__u16| replace:: :c:type:`__u16 <__u16>` -+.. |sdtx_event| replace:: :c:type:`struct sdtx_event ` -+.. |sdtx_event_code| replace:: :c:type:`enum sdtx_event_code ` -+.. |sdtx_base_info| replace:: :c:type:`struct sdtx_base_info ` -+.. |sdtx_device_mode| replace:: :c:type:`struct sdtx_device_mode ` -+ -+====================================================== -+User-Space DTX (Clipboard Detachment System) Interface -+====================================================== -+ -+The ``surface_dtx`` driver is responsible for proper clipboard detachment -+and re-attachment handling. To this end, it provides the ``/dev/surface/dtx`` -+device file, through which it can interface with a user-space daemon. This -+daemon is then ultimately responsible for determining and taking necessary -+actions, such as unmounting devices attached to the base, -+unloading/reloading the graphics-driver, user-notifications, etc. -+ -+There are two basic communication principles used in this driver: Commands -+(in other parts of the documentation also referred to as requests) and -+events. Commands are sent to the EC and may have a different implications in -+different contexts. Events are sent by the EC upon some internal state -+change. Commands are always driver-initiated, whereas events are always -+initiated by the EC. -+ -+.. contents:: -+ -+Nomenclature -+============ -+ -+* **Clipboard:** -+ The detachable upper part of the Surface Book, housing the screen and CPU. -+ -+* **Base:** -+ The lower part of the Surface Book from which the clipboard can be -+ detached, optionally (model dependent) housing the discrete GPU (dGPU). -+ -+* **Latch:** -+ The mechanism keeping the clipboard attached to the base in normal -+ operation and allowing it to be detached when requested. -+ -+* **Silently ignored commands:** -+ The command is accepted by the EC as a valid command and acknowledged -+ (following the standard communication protocol), but the EC does not act -+ upon it, i.e. ignores it.e upper part of the -+ -+ -+Detachment Process -+================== -+ -+Warning: This part of the documentation is based on reverse engineering and -+testing and thus may contain errors or be incomplete. -+ -+Latch States -+------------ -+ -+The latch mechanism has two major states: *open* and *closed*. In the -+*closed* state (default), the clipboard is secured to the base, whereas in -+the *open* state, the clipboard can be removed by a user. -+ -+The latch can additionally be locked and, correspondingly, unlocked, which -+can influence the detachment procedure. Specifically, this locking mechanism -+is intended to prevent the dGPU, positioned in the base of the device, from -+being hot-unplugged while in use. More details can be found in the -+documentation for the detachment procedure below. By default, the latch is -+unlocked. -+ -+Detachment Procedure -+-------------------- -+ -+Note that the detachment process is governed fully by the EC. The -+``surface_dtx`` driver only relays events from the EC to user-space and -+commands from user-space to the EC, i.e. it does not influence this process. -+ -+The detachment process is started with the user pressing the *detach* button -+on the base of the device or executing the ``SDTX_IOCTL_LATCH_REQUEST`` IOCTL. -+Following that: -+ -+1. The EC turns on the indicator led on the detach-button, sends a -+ *detach-request* event (``SDTX_EVENT_REQUEST``), and awaits further -+ instructions/commands. In case the latch is unlocked, the led will flash -+ green. If the latch has been locked, the led will be solid red -+ -+2. The event is, via the ``surface_dtx`` driver, relayed to user-space, where -+ an appropriate user-space daemon can handle it and send instructions back -+ to the EC via IOCTLs provided by this driver. -+ -+3. The EC waits for instructions from user-space and acts according to them. -+ If the EC does not receive any instructions in a given period, it will -+ time out and continue as follows: -+ -+ - If the latch is unlocked, the EC will open the latch and the clipboard -+ can be detached from the base. This is the exact behavior as without -+ this driver or any user-space daemon. See the ``SDTX_IOCTL_LATCH_CONFIRM`` -+ description below for more details on the follow-up behavior of the EC. -+ -+ - If the latch is locked, the EC will *not* open the latch, meaning the -+ clipboard cannot be detached from the base. Furthermore, the EC sends -+ an cancel event (``SDTX_EVENT_CANCEL``) detailing this with the cancel -+ reason ``SDTX_DETACH_TIMEDOUT`` (see :ref:`events` for details). -+ -+Valid responses by a user-space daemon to a detachment request event are: -+ -+- Execute ``SDTX_IOCTL_LATCH_REQUEST``. This will immediately abort the -+ detachment process. Furthermore, the EC will send a detach-request event, -+ similar to the user pressing the detach-button to cancel said process (see -+ below). -+ -+- Execute ``SDTX_IOCTL_LATCH_CONFIRM``. This will cause the EC to open the -+ latch, after which the user can separate clipboard and base. -+ -+ As this changes the latch state, a *latch-status* event -+ (``SDTX_EVENT_LATCH_STATUS``) will be sent once the latch has been opened -+ successfully. If the EC fails to open the latch, e.g. due to hardware -+ error or low battery, a latch-cancel event (``SDTX_EVENT_CANCEL``) will be -+ sent with the cancel reason indicating the specific failure. -+ -+ If the latch is currently locked, the latch will automatically be -+ unlocked before it is opened. -+ -+- Execute ``SDTX_IOCTL_LATCH_HEARTBEAT``. This will reset the internal timeout. -+ No other actions will be performed, i.e. the detachment process will neither -+ be completed nor canceled, and the EC will still be waiting for further -+ responses. -+ -+- Execute ``SDTX_IOCTL_LATCH_CANCEL``. This will abort the detachment process, -+ similar to ``SDTX_IOCTL_LATCH_REQUEST``, described above, or the button -+ press, described below. A *generic request* event (``SDTX_EVENT_REQUEST``) -+ is send in response to this. In contrast to those, however, this command -+ does not trigger a new detachment process if none is currently in -+ progress. -+ -+- Do nothing. The detachment process eventually times out as described in -+ point 3. -+ -+See :ref:`ioctls` for more details on these responses. -+ -+It is important to note that, if the user presses the detach button at any -+point when a detachment operation is in progress (i.e. after the EC has sent -+the initial *detach-request* event (``SDTX_EVENT_REQUEST``) and before it -+received the corresponding response concluding the process), the detachment -+process is canceled on the EC-level and an identical event is being sent. -+Thus a *detach-request* event, by itself, does not signal the start of the -+detachment process. -+ -+The detachment process may further be canceled by the EC due to hardware -+failures or a low clipboard battery. This is done via a cancel event -+(``SDTX_EVENT_CANCEL``) with the corresponding cancel reason. -+ -+ -+User-Space Interface Documentation -+================================== -+ -+Error Codes and Status Values -+----------------------------- -+ -+Error and status codes are divided into different categories, which can be -+used to determine if the status code is an error, and, if it is, the -+severity and type of that error. The current categories are: -+ -+.. flat-table:: Overview of Status/Error Categories. -+ :widths: 2 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Value -+ - Short Description -+ -+ * - ``STATUS`` -+ - ``0x0000`` -+ - Non-error status codes. -+ -+ * - ``RUNTIME_ERROR`` -+ - ``0x1000`` -+ - Non-critical runtime errors. -+ -+ * - ``HARDWARE_ERROR`` -+ - ``0x2000`` -+ - Critical hardware failures. -+ -+ * - ``UNKNOWN`` -+ - ``0xF000`` -+ - Unknown error codes. -+ -+Other categories are reserved for future use. The ``SDTX_CATEGORY()`` macro -+can be used to determine the category of any status value. The -+``SDTX_SUCCESS()`` macro can be used to check if the status value is a -+success value (``SDTX_CATEGORY_STATUS``) or if it indicates a failure. -+ -+Unknown status or error codes sent by the EC are assigned to the ``UNKNOWN`` -+category by the driver and may be implemented via their own code in the -+future. -+ -+Currently used error codes are: -+ -+.. flat-table:: Overview of Error Codes. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Category -+ - Value -+ - Short Description -+ -+ * - ``SDTX_DETACH_NOT_FEASIBLE`` -+ - ``RUNTIME`` -+ - ``0x1001`` -+ - Detachment not feasible due to low clipboard battery. -+ -+ * - ``SDTX_DETACH_TIMEDOUT`` -+ - ``RUNTIME`` -+ - ``0x1002`` -+ - Detachment process timed out while the latch was locked. -+ -+ * - ``SDTX_ERR_FAILED_TO_OPEN`` -+ - ``HARDWARE`` -+ - ``0x2001`` -+ - Failed to open latch. -+ -+ * - ``SDTX_ERR_FAILED_TO_REMAIN_OPEN`` -+ - ``HARDWARE`` -+ - ``0x2002`` -+ - Failed to keep latch open. -+ -+ * - ``SDTX_ERR_FAILED_TO_CLOSE`` -+ - ``HARDWARE`` -+ - ``0x2003`` -+ - Failed to close latch. -+ -+Other error codes are reserved for future use. Non-error status codes may -+overlap and are generally only unique within their use-case: -+ -+.. flat-table:: Latch Status Codes. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Category -+ - Value -+ - Short Description -+ -+ * - ``SDTX_LATCH_CLOSED`` -+ - ``STATUS`` -+ - ``0x0000`` -+ - Latch is closed/has been closed. -+ -+ * - ``SDTX_LATCH_OPENED`` -+ - ``STATUS`` -+ - ``0x0001`` -+ - Latch is open/has been opened. -+ -+.. flat-table:: Base State Codes. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Category -+ - Value -+ - Short Description -+ -+ * - ``SDTX_BASE_DETACHED`` -+ - ``STATUS`` -+ - ``0x0000`` -+ - Base has been detached/is not present. -+ -+ * - ``SDTX_BASE_ATTACHED`` -+ - ``STATUS`` -+ - ``0x0001`` -+ - Base has been attached/is present. -+ -+Again, other codes are reserved for future use. -+ -+.. _events: -+ -+Events -+------ -+ -+Events can be received by reading from the device file. They are disabled by -+default and have to be enabled by executing ``SDTX_IOCTL_EVENTS_ENABLE`` -+first. All events follow the layout prescribed by |sdtx_event|. Specific -+event types can be identified by their event code, described in -+|sdtx_event_code|. Note that other event codes are reserved for future use, -+thus an event parser must be able to handle any unknown/unsupported event -+types gracefully, by relying on the payload length given in the event header. -+ -+Currently provided event types are: -+ -+.. flat-table:: Overview of DTX events. -+ :widths: 2 1 1 3 -+ :header-rows: 1 -+ -+ * - Name -+ - Code -+ - Payload -+ - Short Description -+ -+ * - ``SDTX_EVENT_REQUEST`` -+ - ``1`` -+ - ``0`` bytes -+ - Detachment process initiated/aborted. -+ -+ * - ``SDTX_EVENT_CANCEL`` -+ - ``2`` -+ - ``2`` bytes -+ - EC canceled detachment process. -+ -+ * - ``SDTX_EVENT_BASE_CONNECTION`` -+ - ``3`` -+ - ``4`` bytes -+ - Base connection state changed. -+ -+ * - ``SDTX_EVENT_LATCH_STATUS`` -+ - ``4`` -+ - ``2`` bytes -+ - Latch status changed. -+ -+ * - ``SDTX_EVENT_DEVICE_MODE`` -+ - ``5`` -+ - ``2`` bytes -+ - Device mode changed. -+ -+Individual events in more detail: -+ -+``SDTX_EVENT_REQUEST`` -+^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when a detachment process is started or, if in progress, aborted by the -+user, either via a detach button press or a detach request -+(``SDTX_IOCTL_LATCH_REQUEST``) being sent from user-space. -+ -+Does not have any payload. -+ -+``SDTX_EVENT_CANCEL`` -+^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when a detachment process is canceled by the EC due to unfulfilled -+preconditions (e.g. clipboard battery too low to detach) or hardware -+failure. The reason for cancellation is given in the event payload detailed -+below and can be one of -+ -+* ``SDTX_DETACH_TIMEDOUT``: Detachment timed out while the latch was locked. -+ The latch has neither been opened nor unlocked. -+ -+* ``SDTX_DETACH_NOT_FEASIBLE``: Detachment not feasible due to low clipboard -+ battery. -+ -+* ``SDTX_ERR_FAILED_TO_OPEN``: Could not open the latch (hardware failure). -+ -+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``: Could not keep the latch open (hardware -+ failure). -+ -+* ``SDTX_ERR_FAILED_TO_CLOSE``: Could not close the latch (hardware failure). -+ -+Other error codes in this context are reserved for future use. -+ -+These codes can be classified via the ``SDTX_CATEGORY()`` macro to discern -+between critical hardware errors (``SDTX_CATEGORY_HARDWARE_ERROR``) or -+runtime errors (``SDTX_CATEGORY_RUNTIME_ERROR``), the latter of which may -+happen during normal operation if certain preconditions for detachment are -+not given. -+ -+.. flat-table:: Detachment Cancel Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``reason`` -+ - |__u16| -+ - Reason for cancellation. -+ -+``SDTX_EVENT_BASE_CONNECTION`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when the base connection state has changed, i.e. when the base has been -+attached, detached, or detachment has become infeasible due to low clipboard -+battery. The new state and, if a base is connected, ID of the base is -+provided as payload of type |sdtx_base_info| with its layout presented -+below: -+ -+.. flat-table:: Base-Connection-Change Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``state`` -+ - |__u16| -+ - Base connection state. -+ -+ * - ``base_id`` -+ - |__u16| -+ - Type of base connected (zero if none). -+ -+Possible values for ``state`` are: -+ -+* ``SDTX_BASE_DETACHED``, -+* ``SDTX_BASE_ATTACHED``, and -+* ``SDTX_DETACH_NOT_FEASIBLE``. -+ -+Other values are reserved for future use. -+ -+``SDTX_EVENT_LATCH_STATUS`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when the latch status has changed, i.e. when the latch has been opened, -+closed, or an error occurred. The current status is provided as payload: -+ -+.. flat-table:: Latch-Status-Change Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``status`` -+ - |__u16| -+ - Latch status. -+ -+Possible values for ``status`` are: -+ -+* ``SDTX_LATCH_CLOSED``, -+* ``SDTX_LATCH_OPENED``, -+* ``SDTX_ERR_FAILED_TO_OPEN``, -+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and -+* ``SDTX_ERR_FAILED_TO_CLOSE``. -+ -+Other values are reserved for future use. -+ -+``SDTX_EVENT_DEVICE_MODE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Sent when the device mode has changed. The new device mode is provided as -+payload: -+ -+.. flat-table:: Device-Mode-Change Event Payload -+ :widths: 1 1 4 -+ :header-rows: 1 -+ -+ * - Field -+ - Type -+ - Description -+ -+ * - ``mode`` -+ - |__u16| -+ - Device operation mode. -+ -+Possible values for ``mode`` are: -+ -+* ``SDTX_DEVICE_MODE_TABLET``, -+* ``SDTX_DEVICE_MODE_LAPTOP``, and -+* ``SDTX_DEVICE_MODE_STUDIO``. -+ -+Other values are reserved for future use. -+ -+.. _ioctls: -+ -+IOCTLs -+------ -+ -+The following IOCTLs are provided: -+ -+.. flat-table:: Overview of DTX IOCTLs -+ :widths: 1 1 1 1 4 -+ :header-rows: 1 -+ -+ * - Type -+ - Number -+ - Direction -+ - Name -+ - Description -+ -+ * - ``0xA5`` -+ - ``0x21`` -+ - ``-`` -+ - ``EVENTS_ENABLE`` -+ - Enable events for the current file descriptor. -+ -+ * - ``0xA5`` -+ - ``0x22`` -+ - ``-`` -+ - ``EVENTS_DISABLE`` -+ - Disable events for the current file descriptor. -+ -+ * - ``0xA5`` -+ - ``0x23`` -+ - ``-`` -+ - ``LATCH_LOCK`` -+ - Lock the latch. -+ -+ * - ``0xA5`` -+ - ``0x24`` -+ - ``-`` -+ - ``LATCH_UNLOCK`` -+ - Unlock the latch. -+ -+ * - ``0xA5`` -+ - ``0x25`` -+ - ``-`` -+ - ``LATCH_REQUEST`` -+ - Request clipboard detachment. -+ -+ * - ``0xA5`` -+ - ``0x26`` -+ - ``-`` -+ - ``LATCH_CONFIRM`` -+ - Confirm clipboard detachment request. -+ -+ * - ``0xA5`` -+ - ``0x27`` -+ - ``-`` -+ - ``LATCH_HEARTBEAT`` -+ - Send heartbeat signal to EC. -+ -+ * - ``0xA5`` -+ - ``0x28`` -+ - ``-`` -+ - ``LATCH_CANCEL`` -+ - Cancel detachment process. -+ -+ * - ``0xA5`` -+ - ``0x29`` -+ - ``R`` -+ - ``GET_BASE_INFO`` -+ - Get current base/connection information. -+ -+ * - ``0xA5`` -+ - ``0x2A`` -+ - ``R`` -+ - ``GET_DEVICE_MODE`` -+ - Get current device operation mode. -+ -+ * - ``0xA5`` -+ - ``0x2B`` -+ - ``R`` -+ - ``GET_LATCH_STATUS`` -+ - Get current device latch status. -+ -+``SDTX_IOCTL_EVENTS_ENABLE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x22)``. -+ -+Enable events for the current file descriptor. Events can be obtained by -+reading from the device, if enabled. Events are disabled by default. -+ -+``SDTX_IOCTL_EVENTS_DISABLE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x22)``. -+ -+Disable events for the current file descriptor. Events can be obtained by -+reading from the device, if enabled. Events are disabled by default. -+ -+``SDTX_IOCTL_LATCH_LOCK`` -+^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x23)``. -+ -+Locks the latch, causing the detachment procedure to abort without opening -+the latch on timeout. The latch is unlocked by default. This command will be -+silently ignored if the latch is already locked. -+ -+``SDTX_IOCTL_LATCH_UNLOCK`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x24)``. -+ -+Unlocks the latch, causing the detachment procedure to open the latch on -+timeout. The latch is unlocked by default. This command will not open the -+latch when sent during an ongoing detachment process. It will be silently -+ignored if the latch is already unlocked. -+ -+``SDTX_IOCTL_LATCH_REQUEST`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x25)``. -+ -+Generic latch request. Behavior depends on the context: If no -+detachment-process is active, detachment is requested. Otherwise the -+currently active detachment-process will be aborted. -+ -+If a detachment process is canceled by this operation, a generic detachment -+request event (``SDTX_EVENT_REQUEST``) will be sent. -+ -+This essentially behaves the same as a detachment button press. -+ -+``SDTX_IOCTL_LATCH_CONFIRM`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x26)``. -+ -+Acknowledges and confirms a latch request. If sent during an ongoing -+detachment process, this command causes the latch to be opened immediately. -+The latch will also be opened if it has been locked. In this case, the latch -+lock is reset to the unlocked state. -+ -+This command will be silently ignored if there is currently no detachment -+procedure in progress. -+ -+``SDTX_IOCTL_LATCH_HEARTBEAT`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x27)``. -+ -+Sends a heartbeat, essentially resetting the detachment timeout. This -+command can be used to keep the detachment process alive while work required -+for the detachment to succeed is still in progress. -+ -+This command will be silently ignored if there is currently no detachment -+procedure in progress. -+ -+``SDTX_IOCTL_LATCH_CANCEL`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IO(0xA5, 0x28)``. -+ -+Cancels detachment in progress (if any). If a detachment process is canceled -+by this operation, a generic detachment request event -+(``SDTX_EVENT_REQUEST``) will be sent. -+ -+This command will be silently ignored if there is currently no detachment -+procedure in progress. -+ -+``SDTX_IOCTL_GET_BASE_INFO`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IOR(0xA5, 0x29, struct sdtx_base_info)``. -+ -+Get the current base connection state (i.e. attached/detached) and the type -+of the base connected to the clipboard. This is command essentially provides -+a way to query the information provided by the base connection change event -+(``SDTX_EVENT_BASE_CONNECTION``). -+ -+Possible values for ``struct sdtx_base_info.state`` are: -+ -+* ``SDTX_BASE_DETACHED``, -+* ``SDTX_BASE_ATTACHED``, and -+* ``SDTX_DETACH_NOT_FEASIBLE``. -+ -+Other values are reserved for future use. -+ -+``SDTX_IOCTL_GET_DEVICE_MODE`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IOR(0xA5, 0x2A, __u16)``. -+ -+Returns the device operation mode, indicating if and how the base is -+attached to the clipboard. This is command essentially provides a way to -+query the information provided by the device mode change event -+(``SDTX_EVENT_DEVICE_MODE``). -+ -+Returned values are: -+ -+* ``SDTX_DEVICE_MODE_LAPTOP`` -+* ``SDTX_DEVICE_MODE_TABLET`` -+* ``SDTX_DEVICE_MODE_STUDIO`` -+ -+See |sdtx_device_mode| for details. Other values are reserved for future -+use. -+ -+ -+``SDTX_IOCTL_GET_LATCH_STATUS`` -+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -+ -+Defined as ``_IOR(0xA5, 0x2B, __u16)``. -+ -+Get the current latch status or (presumably) the last error encountered when -+trying to open/close the latch. This is command essentially provides a way -+to query the information provided by the latch status change event -+(``SDTX_EVENT_LATCH_STATUS``). -+ -+Returned values are: -+ -+* ``SDTX_LATCH_CLOSED``, -+* ``SDTX_LATCH_OPENED``, -+* ``SDTX_ERR_FAILED_TO_OPEN``, -+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and -+* ``SDTX_ERR_FAILED_TO_CLOSE``. -+ -+Other values are reserved for future use. -+ -+A Note on Base IDs -+------------------ -+ -+Base types/IDs provided via ``SDTX_EVENT_BASE_CONNECTION`` or -+``SDTX_IOCTL_GET_BASE_INFO`` are directly forwarded from the EC in the lower -+byte of the combined |__u16| value, with the driver storing the EC type from -+which this ID comes in the high byte (without this, base IDs over different -+types of ECs may be overlapping). -+ -+The ``SDTX_DEVICE_TYPE()`` macro can be used to determine the EC device -+type. This can be one of -+ -+* ``SDTX_DEVICE_TYPE_HID``, for Surface Aggregator Module over HID, and -+ -+* ``SDTX_DEVICE_TYPE_SSH``, for Surface Aggregator Module over Surface Serial -+ Hub. -+ -+Note that currently only the ``SSH`` type EC is supported, however ``HID`` -+type is reserved for future use. -+ -+Structures and Enums -+-------------------- -+ -+.. kernel-doc:: include/uapi/linux/surface_aggregator/dtx.h -+ -+API Users -+========= -+ -+A user-space daemon utilizing this API can be found at -+https://github.com/linux-surface/surface-dtx-daemon. -diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst -index 3ccabce23271..98ea9946b8a2 100644 ---- a/Documentation/driver-api/surface_aggregator/clients/index.rst -+++ b/Documentation/driver-api/surface_aggregator/clients/index.rst -@@ -11,6 +11,7 @@ This is the documentation for client drivers themselves. Refer to - :maxdepth: 1 - - cdev -+ dtx - san - - .. only:: subproject and html -diff --git a/MAINTAINERS b/MAINTAINERS -index 3917e7363520..da1487d672a8 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11872,6 +11872,7 @@ MICROSOFT SURFACE DTX DRIVER - M: Maximilian Luz - L: platform-driver-x86@vger.kernel.org - S: Maintained -+F: Documentation/driver-api/surface_aggregator/clients/dtx.rst - F: drivers/platform/surface/surface_dtx.c - F: include/uapi/linux/surface_aggregator/dtx.h - --- -2.32.0 - -From 2ba03413577d1c0491ae621c72e9d11898fe1cb0 Mon Sep 17 00:00:00 2001 -From: Wei Yongjun -Date: Tue, 9 Mar 2021 13:15:00 +0000 -Subject: [PATCH] platform/surface: aggregator_registry: Make symbol - 'ssam_base_hub_group' static - -The sparse tool complains as follows: - -drivers/platform/surface/surface_aggregator_registry.c:355:30: warning: - symbol 'ssam_base_hub_group' was not declared. Should it be static? - -This symbol is not used outside of surface_aggregator_registry.c, so this -commit marks it static. - -Fixes: 797e78564634 ("platform/surface: aggregator_registry: Add base device hub") -Reported-by: Hulk Robot -Signed-off-by: Wei Yongjun -Reviewed-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210309131500.1885772-1-weiyongjun1@huawei.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index cdb4a95af3e8..86cff5fce3cd 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -352,7 +352,7 @@ static struct attribute *ssam_base_hub_attrs[] = { - NULL, - }; - --const struct attribute_group ssam_base_hub_group = { -+static const struct attribute_group ssam_base_hub_group = { - .attrs = ssam_base_hub_attrs, - }; - --- -2.32.0 - -From 7751eb2a68de5841822b775f1fb8dd49163feb24 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 9 Mar 2021 17:25:50 +0100 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for - Surface Pro 7+ - -The Surface Pro 7+ is essentially a refresh of the Surface Pro 7 with -updated hardware and a new WSID identifier. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210309162550.302161-1-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 86cff5fce3cd..eccb9d1007cd 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -191,7 +191,7 @@ static const struct software_node *ssam_node_group_sp6[] = { - NULL, - }; - --/* Devices for Surface Pro 7. */ -+/* Devices for Surface Pro 7 and Surface Pro 7+. */ - static const struct software_node *ssam_node_group_sp7[] = { - &ssam_node_root, - &ssam_node_bat_ac, -@@ -521,6 +521,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { - /* Surface Pro 7 */ - { "MSHW0116", (unsigned long)ssam_node_group_sp7 }, - -+ /* Surface Pro 7+ */ -+ { "MSHW0119", (unsigned long)ssam_node_group_sp7 }, -+ - /* Surface Book 2 */ - { "MSHW0107", (unsigned long)ssam_node_group_sb2 }, - --- -2.32.0 - -From 31da7032ce116c1d3678049476fb2a6bd0570d26 Mon Sep 17 00:00:00 2001 -From: kernel test robot -Date: Fri, 19 Mar 2021 13:19:19 +0800 -Subject: [PATCH] platform/surface: fix semicolon.cocci warnings - -drivers/platform/surface/surface_dtx.c:651:2-3: Unneeded semicolon - - Remove unneeded semicolon. - -Generated by: scripts/coccinelle/misc/semicolon.cocci - -Fixes: 1d609992832e ("platform/surface: Add DTX driver") -CC: Maximilian Luz -Reported-by: kernel test robot -Signed-off-by: kernel test robot -Reviewed-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210319051919.GA39801@ae4f36e4f012 -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_dtx.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c -index 85451eb94d98..1fedacf74050 100644 ---- a/drivers/platform/surface/surface_dtx.c -+++ b/drivers/platform/surface/surface_dtx.c -@@ -649,7 +649,7 @@ static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event - - default: - return 0; -- }; -+ } - - if (in->length != len) { - dev_err(ddev->dev, --- -2.32.0 - -From 2f1c22a481481c8623e98d8cc8b78d287755f3e3 Mon Sep 17 00:00:00 2001 -From: Dan Carpenter -Date: Fri, 26 Mar 2021 15:28:48 +0300 -Subject: [PATCH] platform/surface: clean up a variable in surface_dtx_read() - -The "&client->ddev->lock" and "&ddev->lock" are the same thing. Let's -use "&ddev->lock" consistently. - -Signed-off-by: Dan Carpenter -Reviewed-by: Maximilian Luz -Link: https://lore.kernel.org/r/YF3TgCcpcCYl3a//@mwanda -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_dtx.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c -index 1fedacf74050..63ce587e79e3 100644 ---- a/drivers/platform/surface/surface_dtx.c -+++ b/drivers/platform/surface/surface_dtx.c -@@ -487,7 +487,7 @@ static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t coun - if (status < 0) - return status; - -- if (down_read_killable(&client->ddev->lock)) -+ if (down_read_killable(&ddev->lock)) - return -ERESTARTSYS; - - /* Need to check that we're not shut down again. */ --- -2.32.0 - -From c0a095e902338a17e4b090ac78837aedd32c5abf Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 6 Apr 2021 01:12:22 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Give devices time to - set up when connecting - -Sometimes, the "base connected" event that we rely on to (re-)attach the -device connected to the base is sent a bit too early. When this happens, -some devices may not be completely ready yet. - -Specifically, the battery has been observed to report zero-values for -things like full charge capacity, which, however, is only loaded once -when the driver for that device probes. This can thus result in battery -readings being unavailable. - -As we cannot easily and reliably discern between devices that are not -ready yet and devices that are not connected (i.e. will never be ready), -delay adding these devices. This should give them enough time to set up. - -The delay is set to 2.5 seconds, which should give us a good safety -margin based on testing and still be fairly responsive for users. - -To achieve that delay switch to updating via a delayed work struct, -which means that we can also get rid of some locking. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210405231222.358113-1-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 98 ++++++++----------- - 1 file changed, 40 insertions(+), 58 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index eccb9d1007cd..685d37a7add1 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -13,10 +13,10 @@ - #include - #include - #include --#include - #include - #include - #include -+#include - - #include - #include -@@ -287,6 +287,13 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c - - /* -- SSAM base-hub driver. ------------------------------------------------- */ - -+/* -+ * Some devices (especially battery) may need a bit of time to be fully usable -+ * after being (re-)connected. This delay has been determined via -+ * experimentation. -+ */ -+#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) -+ - enum ssam_base_hub_state { - SSAM_BASE_HUB_UNINITIALIZED, - SSAM_BASE_HUB_CONNECTED, -@@ -296,8 +303,8 @@ enum ssam_base_hub_state { - struct ssam_base_hub { - struct ssam_device *sdev; - -- struct mutex lock; /* Guards state update checks and transitions. */ - enum ssam_base_hub_state state; -+ struct delayed_work update_work; - - struct ssam_event_notifier notif; - }; -@@ -335,11 +342,7 @@ static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attrib - char *buf) - { - struct ssam_base_hub *hub = dev_get_drvdata(dev); -- bool connected; -- -- mutex_lock(&hub->lock); -- connected = hub->state == SSAM_BASE_HUB_CONNECTED; -- mutex_unlock(&hub->lock); -+ bool connected = hub->state == SSAM_BASE_HUB_CONNECTED; - - return sysfs_emit(buf, "%d\n", connected); - } -@@ -356,16 +359,20 @@ static const struct attribute_group ssam_base_hub_group = { - .attrs = ssam_base_hub_attrs, - }; - --static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new) -+static void ssam_base_hub_update_workfn(struct work_struct *work) - { -+ struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work); - struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -+ enum ssam_base_hub_state state; - int status = 0; - -- lockdep_assert_held(&hub->lock); -+ status = ssam_base_hub_query_state(hub, &state); -+ if (status) -+ return; - -- if (hub->state == new) -- return 0; -- hub->state = new; -+ if (hub->state == state) -+ return; -+ hub->state = state; - - if (hub->state == SSAM_BASE_HUB_CONNECTED) - status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); -@@ -374,51 +381,28 @@ static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_ - - if (status) - dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); -- -- return status; --} -- --static int ssam_base_hub_update(struct ssam_base_hub *hub) --{ -- enum ssam_base_hub_state state; -- int status; -- -- mutex_lock(&hub->lock); -- -- status = ssam_base_hub_query_state(hub, &state); -- if (!status) -- status = __ssam_base_hub_update(hub, state); -- -- mutex_unlock(&hub->lock); -- return status; - } - - static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) - { -- struct ssam_base_hub *hub; -- struct ssam_device *sdev; -- enum ssam_base_hub_state new; -- -- hub = container_of(nf, struct ssam_base_hub, notif); -- sdev = hub->sdev; -+ struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif); -+ unsigned long delay; - - if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) - return 0; - - if (event->length < 1) { -- dev_err(&sdev->dev, "unexpected payload size: %u\n", -- event->length); -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); - return 0; - } - -- if (event->data[0]) -- new = SSAM_BASE_HUB_CONNECTED; -- else -- new = SSAM_BASE_HUB_DISCONNECTED; -+ /* -+ * Delay update when the base is being connected to give devices/EC -+ * some time to set up. -+ */ -+ delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0; - -- mutex_lock(&hub->lock); -- __ssam_base_hub_update(hub, new); -- mutex_unlock(&hub->lock); -+ schedule_delayed_work(&hub->update_work, delay); - - /* - * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -@@ -430,7 +414,10 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam - - static int __maybe_unused ssam_base_hub_resume(struct device *dev) - { -- return ssam_base_hub_update(dev_get_drvdata(dev)); -+ struct ssam_base_hub *hub = dev_get_drvdata(dev); -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; - } - static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); - -@@ -443,8 +430,6 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - if (!hub) - return -ENOMEM; - -- mutex_init(&hub->lock); -- - hub->sdev = sdev; - hub->state = SSAM_BASE_HUB_UNINITIALIZED; - -@@ -456,27 +441,25 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - hub->notif.event.mask = SSAM_EVENT_MASK_NONE; - hub->notif.event.flags = SSAM_EVENT_SEQUENCED; - -+ INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn); -+ - ssam_device_set_drvdata(sdev, hub); - - status = ssam_notifier_register(sdev->ctrl, &hub->notif); - if (status) -- goto err_register; -- -- status = ssam_base_hub_update(hub); -- if (status) -- goto err_update; -+ return status; - - status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); - if (status) -- goto err_update; -+ goto err; - -+ schedule_delayed_work(&hub->update_work, 0); - return 0; - --err_update: -+err: - ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); - ssam_hub_remove_devices(&sdev->dev); --err_register: -- mutex_destroy(&hub->lock); - return status; - } - -@@ -487,9 +470,8 @@ static void ssam_base_hub_remove(struct ssam_device *sdev) - sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); - - ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); - ssam_hub_remove_devices(&sdev->dev); -- -- mutex_destroy(&hub->lock); - } - - static const struct ssam_device_id ssam_base_hub_match[] = { --- -2.32.0 - -From 09e3d34a400dfbcf06674de5bfe68e16bb9e53d4 Mon Sep 17 00:00:00 2001 -From: Barry Song -Date: Wed, 3 Mar 2021 11:49:15 +1300 -Subject: [PATCH] genirq: Add IRQF_NO_AUTOEN for request_irq/nmi() - -Many drivers don't want interrupts enabled automatically via request_irq(). -So they are handling this issue by either way of the below two: - -(1) - irq_set_status_flags(irq, IRQ_NOAUTOEN); - request_irq(dev, irq...); - -(2) - request_irq(dev, irq...); - disable_irq(irq); - -The code in the second way is silly and unsafe. In the small time gap -between request_irq() and disable_irq(), interrupts can still come. - -The code in the first way is safe though it's subobtimal. - -Add a new IRQF_NO_AUTOEN flag which can be handed in by drivers to -request_irq() and request_nmi(). It prevents the automatic enabling of the -requested interrupt/nmi in the same safe way as #1 above. With that the -various usage sites of #1 and #2 above can be simplified and corrected. - -Signed-off-by: Barry Song -Signed-off-by: Thomas Gleixner -Signed-off-by: Ingo Molnar -Cc: dmitry.torokhov@gmail.com -Link: https://lore.kernel.org/r/20210302224916.13980-2-song.bao.hua@hisilicon.com -Patchset: surface-sam ---- - include/linux/interrupt.h | 4 ++++ - kernel/irq/manage.c | 11 +++++++++-- - 2 files changed, 13 insertions(+), 2 deletions(-) - -diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h -index 967e25767153..76f1161a441a 100644 ---- a/include/linux/interrupt.h -+++ b/include/linux/interrupt.h -@@ -61,6 +61,9 @@ - * interrupt handler after suspending interrupts. For system - * wakeup devices users need to implement wakeup detection in - * their interrupt handlers. -+ * IRQF_NO_AUTOEN - Don't enable IRQ or NMI automatically when users request it. -+ * Users will enable it explicitly by enable_irq() or enable_nmi() -+ * later. - */ - #define IRQF_SHARED 0x00000080 - #define IRQF_PROBE_SHARED 0x00000100 -@@ -74,6 +77,7 @@ - #define IRQF_NO_THREAD 0x00010000 - #define IRQF_EARLY_RESUME 0x00020000 - #define IRQF_COND_SUSPEND 0x00040000 -+#define IRQF_NO_AUTOEN 0x00080000 - - #define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD) - -diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c -index 21ea370fccda..49288e941365 100644 ---- a/kernel/irq/manage.c -+++ b/kernel/irq/manage.c -@@ -1697,7 +1697,8 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new) - irqd_set(&desc->irq_data, IRQD_NO_BALANCING); - } - -- if (irq_settings_can_autoenable(desc)) { -+ if (!(new->flags & IRQF_NO_AUTOEN) && -+ irq_settings_can_autoenable(desc)) { - irq_startup(desc, IRQ_RESEND, IRQ_START_COND); - } else { - /* -@@ -2090,10 +2091,15 @@ int request_threaded_irq(unsigned int irq, irq_handler_t handler, - * which interrupt is which (messes up the interrupt freeing - * logic etc). - * -+ * Also shared interrupts do not go well with disabling auto enable. -+ * The sharing interrupt might request it while it's still disabled -+ * and then wait for interrupts forever. -+ * - * Also IRQF_COND_SUSPEND only makes sense for shared interrupts and - * it cannot be set along with IRQF_NO_SUSPEND. - */ - if (((irqflags & IRQF_SHARED) && !dev_id) || -+ ((irqflags & IRQF_SHARED) && (irqflags & IRQF_NO_AUTOEN)) || - (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) || - ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND))) - return -EINVAL; -@@ -2249,7 +2255,8 @@ int request_nmi(unsigned int irq, irq_handler_t handler, - - desc = irq_to_desc(irq); - -- if (!desc || irq_settings_can_autoenable(desc) || -+ if (!desc || (irq_settings_can_autoenable(desc) && -+ !(irqflags & IRQF_NO_AUTOEN)) || - !irq_settings_can_request(desc) || - WARN_ON(irq_settings_is_per_cpu_devid(desc)) || - !irq_supports_nmi(desc)) --- -2.32.0 - -From 71e1f5ac8a599f20e9389bdcc05251d1e14a2b9a Mon Sep 17 00:00:00 2001 -From: Tian Tao -Date: Wed, 7 Apr 2021 15:00:52 +0800 -Subject: [PATCH] platform/surface: aggregator: move to use request_irq by - IRQF_NO_AUTOEN flag - -disable_irq() after request_irq() still has a time gap in which -interrupts can come. request_irq() with IRQF_NO_AUTOEN flag will -disable IRQ auto-enable because of requesting. - -this patch is made base on "add IRQF_NO_AUTOEN for request_irq" which -is being merged: https://lore.kernel.org/patchwork/patch/1388765/ - -Signed-off-by: Tian Tao -Reviewed-by: Maximilian Luz -Link: https://lore.kernel.org/r/1617778852-26492-1-git-send-email-tiantao6@hisilicon.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/controller.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index d006a36b2924..eace5e9374fe 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2483,7 +2483,8 @@ int ssam_irq_setup(struct ssam_controller *ctrl) - * interrupt, and let the SAM resume callback during the controller - * resume process clear it. - */ -- const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING; -+ const int irqf = IRQF_SHARED | IRQF_ONESHOT | -+ IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN; - - gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS); - if (IS_ERR(gpiod)) -@@ -2501,7 +2502,6 @@ int ssam_irq_setup(struct ssam_controller *ctrl) - return status; - - ctrl->irq.num = irq; -- disable_irq(ctrl->irq.num); - return 0; - } - --- -2.32.0 - -From d9deaec079e9f7b1e732ef6edec03989d99a2691 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 5 May 2021 14:53:45 +0200 -Subject: [PATCH] platform/surface: aggregator: Do not mark interrupt as shared - -Having both IRQF_NO_AUTOEN and IRQF_SHARED set causes -request_threaded_irq() to return with -EINVAL (see comment in flag -validation in that function). As the interrupt is currently not shared -between multiple devices, drop the IRQF_SHARED flag. - -Fixes: 507cf5a2f1e2 ("platform/surface: aggregator: move to use request_irq by IRQF_NO_AUTOEN flag") -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/controller.c | 3 +-- - 1 file changed, 1 insertion(+), 2 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index eace5e9374fe..a06964aa96e7 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2483,8 +2483,7 @@ int ssam_irq_setup(struct ssam_controller *ctrl) - * interrupt, and let the SAM resume callback during the controller - * resume process clear it. - */ -- const int irqf = IRQF_SHARED | IRQF_ONESHOT | -- IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN; -+ const int irqf = IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN; - - gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS); - if (IS_ERR(gpiod)) --- -2.32.0 - -From 6083e10f0f23437f53a013052f42293f333d0018 Mon Sep 17 00:00:00 2001 -From: Arnd Bergmann -Date: Fri, 14 May 2021 22:04:36 +0200 -Subject: [PATCH] platform/surface: aggregator: avoid clang - -Wconstant-conversion warning - -Clang complains about the assignment of SSAM_ANY_IID to -ssam_device_uid->instance: - -drivers/platform/surface/surface_aggregator_registry.c:478:25: error: implicit conversion from 'int' to '__u8' (aka 'unsigned char') changes value from 65535 to 255 [-Werror,-Wconstant-conversion] - { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) }, - ~ ^~~~~~~~~~~~ -include/linux/surface_aggregator/device.h:71:23: note: expanded from macro 'SSAM_ANY_IID' - #define SSAM_ANY_IID 0xffff - ^~~~~~ -include/linux/surface_aggregator/device.h:126:63: note: expanded from macro 'SSAM_VDEV' - SSAM_DEVICE(SSAM_DOMAIN_VIRTUAL, SSAM_VIRTUAL_TC_##cat, tid, iid, fun) - ^~~ -include/linux/surface_aggregator/device.h:102:41: note: expanded from macro 'SSAM_DEVICE' - .instance = ((iid) != SSAM_ANY_IID) ? (iid) : 0, \ - ^~~ - -The assignment doesn't actually happen, but clang checks the type limits -before checking whether this assignment is reached. Replace the ?: -operator with a __builtin_choose_expr() invocation that avoids the -warning for the untaken part. - -Fixes: eb0e90a82098 ("platform/surface: aggregator: Add dedicated bus and device type") -Cc: platform-driver-x86@vger.kernel.org -Signed-off-by: Arnd Bergmann -Reviewed-by: Nathan Chancellor -Reviewed-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210514200453.1542978-1-arnd@kernel.org -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - include/linux/surface_aggregator/device.h | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 4441ad667c3f..6ff9c58b3e17 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -98,9 +98,9 @@ struct ssam_device_uid { - | (((fun) != SSAM_ANY_FUN) ? SSAM_MATCH_FUNCTION : 0), \ - .domain = d, \ - .category = cat, \ -- .target = ((tid) != SSAM_ANY_TID) ? (tid) : 0, \ -- .instance = ((iid) != SSAM_ANY_IID) ? (iid) : 0, \ -- .function = ((fun) != SSAM_ANY_FUN) ? (fun) : 0 \ -+ .target = __builtin_choose_expr((tid) != SSAM_ANY_TID, (tid), 0), \ -+ .instance = __builtin_choose_expr((iid) != SSAM_ANY_IID, (iid), 0), \ -+ .function = __builtin_choose_expr((fun) != SSAM_ANY_FUN, (fun), 0) - - /** - * SSAM_VDEV() - Initialize a &struct ssam_device_id as virtual device with --- -2.32.0 - -From 9c33d72e05577956908445f2093720ab105b781d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 13 May 2021 15:44:37 +0200 -Subject: [PATCH] platform/surface: dtx: Fix poll function - -The poll function should not return -ERESTARTSYS. - -Furthermore, locking in this function is completely unnecessary. The -ddev->lock protects access to the main device and controller (ddev->dev -and ddev->ctrl), ensuring that both are and remain valid while being -accessed by clients. Both are, however, never accessed in the poll -function. The shutdown test (via atomic bit flags) be safely done -without locking, so drop locking here entirely. - -Reported-by: kernel test robot -Fixes: 1d609992832e ("platform/surface: Add DTX driver) -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210513134437.2431022-1-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_dtx.c | 8 +------- - 1 file changed, 1 insertion(+), 7 deletions(-) - -diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c -index 63ce587e79e3..5d9b758a99bb 100644 ---- a/drivers/platform/surface/surface_dtx.c -+++ b/drivers/platform/surface/surface_dtx.c -@@ -527,20 +527,14 @@ static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt - struct sdtx_client *client = file->private_data; - __poll_t events = 0; - -- if (down_read_killable(&client->ddev->lock)) -- return -ERESTARTSYS; -- -- if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) { -- up_read(&client->ddev->lock); -+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) - return EPOLLHUP | EPOLLERR; -- } - - poll_wait(file, &client->ddev->waitq, pt); - - if (!kfifo_is_empty(&client->buffer)) - events |= EPOLLIN | EPOLLRDNORM; - -- up_read(&client->ddev->lock); - return events; - } - --- -2.32.0 - -From 4311553db3d140a6a78e82bdc6514c731203d17e Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 23 May 2021 14:35:37 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Update comments for - 15" AMD Surface Laptop 4 - -The 15" AMD version of the Surface Laptop 4 shares its WSID HID with the -15" AMD version of the Surface Laptop 3. Update the comments -accordingly. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 685d37a7add1..bdc09305aab7 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -156,7 +156,7 @@ static const struct software_node *ssam_node_group_sl2[] = { - NULL, - }; - --/* Devices for Surface Laptop 3. */ -+/* Devices for Surface Laptop 3 and 4. */ - static const struct software_node *ssam_node_group_sl3[] = { - &ssam_node_root, - &ssam_node_bat_ac, -@@ -521,7 +521,7 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { - /* Surface Laptop 3 (13", Intel) */ - { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, - -- /* Surface Laptop 3 (15", AMD) */ -+ /* Surface Laptop 3 (15", AMD) and 4 (15", AMD) */ - { "MSHW0110", (unsigned long)ssam_node_group_sl3 }, - - /* Surface Laptop Go 1 */ --- -2.32.0 - -From a46d9237d3bf025f577319510993da8f72244c33 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 23 May 2021 14:36:36 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for 13" - Intel Surface Laptop 4 - -Add support for the 13" Intel version of the Surface Laptop 4. - -Use the existing node group for the Surface Laptop 3 since the 15" AMD -version already shares its WSID HID with its predecessor and there don't -seem to be any significant differences with regards to SAM. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index bdc09305aab7..ef83461fa536 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -524,6 +524,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { - /* Surface Laptop 3 (15", AMD) and 4 (15", AMD) */ - { "MSHW0110", (unsigned long)ssam_node_group_sl3 }, - -+ /* Surface Laptop 4 (13", Intel) */ -+ { "MSHW0250", (unsigned long)ssam_node_group_sl3 }, -+ - /* Surface Laptop Go 1 */ - { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, - --- -2.32.0 - -From 45ebc764a23cc78d163036f28e6a59a4150b8971 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 23 May 2021 14:09:42 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Consolidate node - groups for 5th- and 6th-gen devices - -5th- and 6th-generation Surface devices have all SAM clients defined in -ACPI, except for the platform profile/performance mode which his handled -via the WSID (Windows Surface Integration Device). Thus, the node groups -for those devices are the same and we can just use a single one instead -of re-defining the same one over and over again. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 47 +++++-------------- - 1 file changed, 12 insertions(+), 35 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index ef83461fa536..4428c4330229 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -119,8 +119,13 @@ static const struct software_node ssam_node_hid_base_iid6 = { - .parent = &ssam_node_hub_base, - }; - --/* Devices for Surface Book 2. */ --static const struct software_node *ssam_node_group_sb2[] = { -+/* -+ * Devices for 5th- and 6th-generations models: -+ * - Surface Book 2, -+ * - Surface Laptop 1 and 2, -+ * - Surface Pro 5 and 6. -+ */ -+static const struct software_node *ssam_node_group_gen5[] = { - &ssam_node_root, - &ssam_node_tmp_pprof, - NULL, -@@ -142,20 +147,6 @@ static const struct software_node *ssam_node_group_sb3[] = { - NULL, - }; - --/* Devices for Surface Laptop 1. */ --static const struct software_node *ssam_node_group_sl1[] = { -- &ssam_node_root, -- &ssam_node_tmp_pprof, -- NULL, --}; -- --/* Devices for Surface Laptop 2. */ --static const struct software_node *ssam_node_group_sl2[] = { -- &ssam_node_root, -- &ssam_node_tmp_pprof, -- NULL, --}; -- - /* Devices for Surface Laptop 3 and 4. */ - static const struct software_node *ssam_node_group_sl3[] = { - &ssam_node_root, -@@ -177,20 +168,6 @@ static const struct software_node *ssam_node_group_slg1[] = { - NULL, - }; - --/* Devices for Surface Pro 5. */ --static const struct software_node *ssam_node_group_sp5[] = { -- &ssam_node_root, -- &ssam_node_tmp_pprof, -- NULL, --}; -- --/* Devices for Surface Pro 6. */ --static const struct software_node *ssam_node_group_sp6[] = { -- &ssam_node_root, -- &ssam_node_tmp_pprof, -- NULL, --}; -- - /* Devices for Surface Pro 7 and Surface Pro 7+. */ - static const struct software_node *ssam_node_group_sp7[] = { - &ssam_node_root, -@@ -495,10 +472,10 @@ static struct ssam_device_driver ssam_base_hub_driver = { - - static const struct acpi_device_id ssam_platform_hub_match[] = { - /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ -- { "MSHW0081", (unsigned long)ssam_node_group_sp5 }, -+ { "MSHW0081", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Pro 6 (OMBR >= 0x10) */ -- { "MSHW0111", (unsigned long)ssam_node_group_sp6 }, -+ { "MSHW0111", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Pro 7 */ - { "MSHW0116", (unsigned long)ssam_node_group_sp7 }, -@@ -507,16 +484,16 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { - { "MSHW0119", (unsigned long)ssam_node_group_sp7 }, - - /* Surface Book 2 */ -- { "MSHW0107", (unsigned long)ssam_node_group_sb2 }, -+ { "MSHW0107", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Book 3 */ - { "MSHW0117", (unsigned long)ssam_node_group_sb3 }, - - /* Surface Laptop 1 */ -- { "MSHW0086", (unsigned long)ssam_node_group_sl1 }, -+ { "MSHW0086", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Laptop 2 */ -- { "MSHW0112", (unsigned long)ssam_node_group_sl2 }, -+ { "MSHW0112", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Laptop 3 (13", Intel) */ - { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, --- -2.32.0 - -From 2b18fb4c666e9c129abe78fc276c417e5e90c744 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 10 Mar 2021 23:53:28 +0100 -Subject: [PATCH] HID: Add support for Surface Aggregator Module HID transport -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Add a HID transport driver to support integrated HID devices on newer -Microsoft Surface models (specifically 7th-generation, i.e. Surface -Laptop 3, Surface Book 3, and later). - -On those models, the internal keyboard and touchpad (as well as some -other HID devices with currently unknown function) are connected via the -generic HID subsystem (TC=0x15) of the Surface System Aggregator Module -(SSAM). This subsystem provides a generic HID transport layer, support -for which is implemented by this driver. - -Co-developed-by: Blaž Hrastnik -Signed-off-by: Blaž Hrastnik -Signed-off-by: Maximilian Luz -Signed-off-by: Jiri Kosina -Patchset: surface-sam ---- - MAINTAINERS | 7 + - drivers/hid/Kconfig | 2 + - drivers/hid/Makefile | 2 + - drivers/hid/surface-hid/Kconfig | 28 +++ - drivers/hid/surface-hid/Makefile | 6 + - drivers/hid/surface-hid/surface_hid.c | 253 +++++++++++++++++++ - drivers/hid/surface-hid/surface_hid_core.c | 272 +++++++++++++++++++++ - drivers/hid/surface-hid/surface_hid_core.h | 77 ++++++ - 8 files changed, 647 insertions(+) - create mode 100644 drivers/hid/surface-hid/Kconfig - create mode 100644 drivers/hid/surface-hid/Makefile - create mode 100644 drivers/hid/surface-hid/surface_hid.c - create mode 100644 drivers/hid/surface-hid/surface_hid_core.c - create mode 100644 drivers/hid/surface-hid/surface_hid_core.h - -diff --git a/MAINTAINERS b/MAINTAINERS -index da1487d672a8..f54b22333ec6 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11891,6 +11891,13 @@ S: Maintained - T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git - F: drivers/platform/surface/ - -+MICROSOFT SURFACE HID TRANSPORT DRIVER -+M: Maximilian Luz -+L: linux-input@vger.kernel.org -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/hid/surface-hid/ -+ - MICROSOFT SURFACE HOT-PLUG DRIVER - M: Maximilian Luz - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig -index c6a643f4fc5f..a0913a67f614 100644 ---- a/drivers/hid/Kconfig -+++ b/drivers/hid/Kconfig -@@ -1206,4 +1206,6 @@ source "drivers/hid/intel-ish-hid/Kconfig" - - source "drivers/hid/amd-sfh-hid/Kconfig" - -+source "drivers/hid/surface-hid/Kconfig" -+ - endmenu -diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile -index c4f6d5c613dc..1044ed238856 100644 ---- a/drivers/hid/Makefile -+++ b/drivers/hid/Makefile -@@ -145,3 +145,5 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/ - obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ - - obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ -+ -+obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ -diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig -new file mode 100644 -index 000000000000..642c7f0e64fe ---- /dev/null -+++ b/drivers/hid/surface-hid/Kconfig -@@ -0,0 +1,28 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+menu "Surface System Aggregator Module HID support" -+ depends on SURFACE_AGGREGATOR -+ depends on INPUT -+ -+config SURFACE_HID -+ tristate "HID transport driver for Surface System Aggregator Module" -+ depends on SURFACE_AGGREGATOR_REGISTRY -+ select SURFACE_HID_CORE -+ help -+ Driver to support integrated HID devices on newer Microsoft Surface -+ models. -+ -+ This driver provides support for the HID transport protocol provided -+ by the Surface Aggregator Module (i.e. the embedded controller) on -+ 7th-generation Microsoft Surface devices, i.e. Surface Book 3 and -+ Surface Laptop 3. On those models, it is mainly used to connect the -+ integrated touchpad and keyboard. -+ -+ Say M or Y here, if you want support for integrated HID devices, i.e. -+ integrated touchpad and keyboard, on 7th generation Microsoft Surface -+ models. -+ -+endmenu -+ -+config SURFACE_HID_CORE -+ tristate -+ select HID -diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile -new file mode 100644 -index 000000000000..62fc04632d3d ---- /dev/null -+++ b/drivers/hid/surface-hid/Makefile -@@ -0,0 +1,6 @@ -+# SPDX-License-Identifier: GPL-2.0+ -+# -+# Makefile - Surface System Aggregator Module (SSAM) HID transport driver. -+# -+obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o -+obj-$(CONFIG_SURFACE_HID) += surface_hid.o -diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c -new file mode 100644 -index 000000000000..3477b31611ae ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_hid.c -@@ -0,0 +1,253 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) HID transport driver for the -+ * generic HID interface (HID/TC=0x15 subsystem). Provides support for -+ * integrated HID devices on Surface Laptop 3, Book 3, and later. -+ * -+ * Copyright (C) 2019-2021 Blaž Hrastnik , -+ * Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "surface_hid_core.h" -+ -+ -+/* -- SAM interface. -------------------------------------------------------- */ -+ -+struct surface_hid_buffer_slice { -+ __u8 entry; -+ __le32 offset; -+ __le32 length; -+ __u8 end; -+ __u8 data[]; -+} __packed; -+ -+static_assert(sizeof(struct surface_hid_buffer_slice) == 10); -+ -+enum surface_hid_cid { -+ SURFACE_HID_CID_OUTPUT_REPORT = 0x01, -+ SURFACE_HID_CID_GET_FEATURE_REPORT = 0x02, -+ SURFACE_HID_CID_SET_FEATURE_REPORT = 0x03, -+ SURFACE_HID_CID_GET_DESCRIPTOR = 0x04, -+}; -+ -+static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len) -+{ -+ u8 buffer[sizeof(struct surface_hid_buffer_slice) + 0x76]; -+ struct surface_hid_buffer_slice *slice; -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ u32 buffer_len, offset, length; -+ int status; -+ -+ /* -+ * Note: The 0x76 above has been chosen because that's what's used by -+ * the Windows driver. Together with the header, this leads to a 128 -+ * byte payload in total. -+ */ -+ -+ buffer_len = ARRAY_SIZE(buffer) - sizeof(struct surface_hid_buffer_slice); -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_HID_CID_GET_DESCRIPTOR; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(struct surface_hid_buffer_slice); -+ rqst.payload = buffer; -+ -+ rsp.capacity = ARRAY_SIZE(buffer); -+ rsp.pointer = buffer; -+ -+ slice = (struct surface_hid_buffer_slice *)buffer; -+ slice->entry = entry; -+ slice->end = 0; -+ -+ offset = 0; -+ length = buffer_len; -+ -+ while (!slice->end && offset < len) { -+ put_unaligned_le32(offset, &slice->offset); -+ put_unaligned_le32(length, &slice->length); -+ -+ rsp.length = 0; -+ -+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, -+ sizeof(*slice)); -+ if (status) -+ return status; -+ -+ offset = get_unaligned_le32(&slice->offset); -+ length = get_unaligned_le32(&slice->length); -+ -+ /* Don't mess stuff up in case we receive garbage. */ -+ if (length > buffer_len || offset > len) -+ return -EPROTO; -+ -+ if (offset + length > len) -+ length = len - offset; -+ -+ memcpy(buf + offset, &slice->data[0], length); -+ -+ offset += length; -+ length = buffer_len; -+ } -+ -+ if (offset != len) { -+ dev_err(shid->dev, "unexpected descriptor length: got %u, expected %zu\n", -+ offset, len); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id, bool feature, -+ u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ u8 cid; -+ -+ if (feature) -+ cid = SURFACE_HID_CID_SET_FEATURE_REPORT; -+ else -+ cid = SURFACE_HID_CID_OUTPUT_REPORT; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.instance_id = shid->uid.instance; -+ rqst.command_id = cid; -+ rqst.flags = 0; -+ rqst.length = len; -+ rqst.payload = buf; -+ -+ buf[0] = rprt_id; -+ -+ return ssam_retry(ssam_request_sync, shid->ctrl, &rqst, NULL); -+} -+ -+static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.instance_id = shid->uid.instance; -+ rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT; -+ rqst.flags = 0; -+ rqst.length = sizeof(rprt_id); -+ rqst.payload = &rprt_id; -+ -+ rsp.capacity = len; -+ rsp.length = 0; -+ rsp.pointer = buf; -+ -+ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id)); -+} -+ -+static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif); -+ -+ if (event->command_id != 0x00) -+ return 0; -+ -+ hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+ -+/* -- Transport driver. ----------------------------------------------------- */ -+ -+static int shid_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_set_raw_report(shid, rprt_id, false, buf, len); -+ return status >= 0 ? len : status; -+} -+ -+static int shid_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_get_raw_report(shid, rprt_id, buf, len); -+ return status >= 0 ? len : status; -+} -+ -+static int shid_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int status; -+ -+ status = ssam_hid_set_raw_report(shid, rprt_id, true, buf, len); -+ return status >= 0 ? len : status; -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int surface_hid_probe(struct ssam_device *sdev) -+{ -+ struct surface_hid_device *shid; -+ -+ shid = devm_kzalloc(&sdev->dev, sizeof(*shid), GFP_KERNEL); -+ if (!shid) -+ return -ENOMEM; -+ -+ shid->dev = &sdev->dev; -+ shid->ctrl = sdev->ctrl; -+ shid->uid = sdev->uid; -+ -+ shid->notif.base.priority = 1; -+ shid->notif.base.fn = ssam_hid_event_fn; -+ shid->notif.event.reg = SSAM_EVENT_REGISTRY_REG; -+ shid->notif.event.id.target_category = sdev->uid.category; -+ shid->notif.event.id.instance = sdev->uid.instance; -+ shid->notif.event.mask = SSAM_EVENT_MASK_STRICT; -+ shid->notif.event.flags = 0; -+ -+ shid->ops.get_descriptor = ssam_hid_get_descriptor; -+ shid->ops.output_report = shid_output_report; -+ shid->ops.get_feature_report = shid_get_feature_report; -+ shid->ops.set_feature_report = shid_set_feature_report; -+ -+ ssam_device_set_drvdata(sdev, shid); -+ return surface_hid_device_add(shid); -+} -+ -+static void surface_hid_remove(struct ssam_device *sdev) -+{ -+ surface_hid_device_destroy(ssam_device_get_drvdata(sdev)); -+} -+ -+static const struct ssam_device_id surface_hid_match[] = { -+ { SSAM_SDEV(HID, 0x02, SSAM_ANY_IID, 0x00) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_hid_match); -+ -+static struct ssam_device_driver surface_hid_driver = { -+ .probe = surface_hid_probe, -+ .remove = surface_hid_remove, -+ .match_table = surface_hid_match, -+ .driver = { -+ .name = "surface_hid", -+ .pm = &surface_hid_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_hid_driver); -+ -+MODULE_AUTHOR("Blaž Hrastnik "); -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("HID transport driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c -new file mode 100644 -index 000000000000..7b27ec392232 ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_hid_core.c -@@ -0,0 +1,272 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Common/core components for the Surface System Aggregator Module (SSAM) HID -+ * transport driver. Provides support for integrated HID devices on Microsoft -+ * Surface models. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "surface_hid_core.h" -+ -+ -+/* -- Device descriptor access. --------------------------------------------- */ -+ -+static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) -+{ -+ int status; -+ -+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, -+ (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); -+ if (status) -+ return status; -+ -+ if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) { -+ dev_err(shid->dev, "unexpected HID descriptor length: got %u, expected %zu\n", -+ shid->hid_desc.desc_len, sizeof(shid->hid_desc)); -+ return -EPROTO; -+ } -+ -+ if (shid->hid_desc.desc_type != HID_DT_HID) { -+ dev_err(shid->dev, "unexpected HID descriptor type: got %#04x, expected %#04x\n", -+ shid->hid_desc.desc_type, HID_DT_HID); -+ return -EPROTO; -+ } -+ -+ if (shid->hid_desc.num_descriptors != 1) { -+ dev_err(shid->dev, "unexpected number of descriptors: got %u, expected 1\n", -+ shid->hid_desc.num_descriptors); -+ return -EPROTO; -+ } -+ -+ if (shid->hid_desc.report_desc_type != HID_DT_REPORT) { -+ dev_err(shid->dev, "unexpected report descriptor type: got %#04x, expected %#04x\n", -+ shid->hid_desc.report_desc_type, HID_DT_REPORT); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static int surface_hid_load_device_attributes(struct surface_hid_device *shid) -+{ -+ int status; -+ -+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, -+ (u8 *)&shid->attrs, sizeof(shid->attrs)); -+ if (status) -+ return status; -+ -+ if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) { -+ dev_err(shid->dev, "unexpected attribute length: got %u, expected %zu\n", -+ get_unaligned_le32(&shid->attrs.length), sizeof(shid->attrs)); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+ -+/* -- Transport driver (common). -------------------------------------------- */ -+ -+static int surface_hid_start(struct hid_device *hid) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ -+ return ssam_notifier_register(shid->ctrl, &shid->notif); -+} -+ -+static void surface_hid_stop(struct hid_device *hid) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ -+ /* Note: This call will log errors for us, so ignore them here. */ -+ ssam_notifier_unregister(shid->ctrl, &shid->notif); -+} -+ -+static int surface_hid_open(struct hid_device *hid) -+{ -+ return 0; -+} -+ -+static void surface_hid_close(struct hid_device *hid) -+{ -+} -+ -+static int surface_hid_parse(struct hid_device *hid) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len); -+ u8 *buf; -+ int status; -+ -+ buf = kzalloc(len, GFP_KERNEL); -+ if (!buf) -+ return -ENOMEM; -+ -+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len); -+ if (!status) -+ status = hid_parse_report(hid, buf, len); -+ -+ kfree(buf); -+ return status; -+} -+ -+static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportnum, u8 *buf, -+ size_t len, unsigned char rtype, int reqtype) -+{ -+ struct surface_hid_device *shid = hid->driver_data; -+ -+ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) -+ return shid->ops.output_report(shid, reportnum, buf, len); -+ -+ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) -+ return shid->ops.get_feature_report(shid, reportnum, buf, len); -+ -+ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) -+ return shid->ops.set_feature_report(shid, reportnum, buf, len); -+ -+ return -EIO; -+} -+ -+static struct hid_ll_driver surface_hid_ll_driver = { -+ .start = surface_hid_start, -+ .stop = surface_hid_stop, -+ .open = surface_hid_open, -+ .close = surface_hid_close, -+ .parse = surface_hid_parse, -+ .raw_request = surface_hid_raw_request, -+}; -+ -+ -+/* -- Common device setup. -------------------------------------------------- */ -+ -+int surface_hid_device_add(struct surface_hid_device *shid) -+{ -+ int status; -+ -+ status = surface_hid_load_hid_descriptor(shid); -+ if (status) -+ return status; -+ -+ status = surface_hid_load_device_attributes(shid); -+ if (status) -+ return status; -+ -+ shid->hid = hid_allocate_device(); -+ if (IS_ERR(shid->hid)) -+ return PTR_ERR(shid->hid); -+ -+ shid->hid->dev.parent = shid->dev; -+ shid->hid->bus = BUS_HOST; -+ shid->hid->vendor = cpu_to_le16(shid->attrs.vendor); -+ shid->hid->product = cpu_to_le16(shid->attrs.product); -+ shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version); -+ shid->hid->country = shid->hid_desc.country_code; -+ -+ snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X", -+ shid->hid->vendor, shid->hid->product); -+ -+ strscpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys)); -+ -+ shid->hid->driver_data = shid; -+ shid->hid->ll_driver = &surface_hid_ll_driver; -+ -+ status = hid_add_device(shid->hid); -+ if (status) -+ hid_destroy_device(shid->hid); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(surface_hid_device_add); -+ -+void surface_hid_device_destroy(struct surface_hid_device *shid) -+{ -+ hid_destroy_device(shid->hid); -+} -+EXPORT_SYMBOL_GPL(surface_hid_device_destroy); -+ -+ -+/* -- PM ops. --------------------------------------------------------------- */ -+ -+#ifdef CONFIG_PM_SLEEP -+ -+static int surface_hid_suspend(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_SUSPEND); -+ -+ return 0; -+} -+ -+static int surface_hid_resume(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->resume) -+ return d->hid->driver->resume(d->hid); -+ -+ return 0; -+} -+ -+static int surface_hid_freeze(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_FREEZE); -+ -+ return 0; -+} -+ -+static int surface_hid_poweroff(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE); -+ -+ return 0; -+} -+ -+static int surface_hid_restore(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->reset_resume) -+ return d->hid->driver->reset_resume(d->hid); -+ -+ return 0; -+} -+ -+const struct dev_pm_ops surface_hid_pm_ops = { -+ .freeze = surface_hid_freeze, -+ .thaw = surface_hid_resume, -+ .suspend = surface_hid_suspend, -+ .resume = surface_hid_resume, -+ .poweroff = surface_hid_poweroff, -+ .restore = surface_hid_restore, -+}; -+EXPORT_SYMBOL_GPL(surface_hid_pm_ops); -+ -+#else /* CONFIG_PM_SLEEP */ -+ -+const struct dev_pm_ops surface_hid_pm_ops = { }; -+EXPORT_SYMBOL_GPL(surface_hid_pm_ops); -+ -+#endif /* CONFIG_PM_SLEEP */ -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/hid/surface-hid/surface_hid_core.h b/drivers/hid/surface-hid/surface_hid_core.h -new file mode 100644 -index 000000000000..4b1a7b57e035 ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_hid_core.h -@@ -0,0 +1,77 @@ -+/* SPDX-License-Identifier: GPL-2.0+ */ -+/* -+ * Common/core components for the Surface System Aggregator Module (SSAM) HID -+ * transport driver. Provides support for integrated HID devices on Microsoft -+ * Surface models. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#ifndef SURFACE_HID_CORE_H -+#define SURFACE_HID_CORE_H -+ -+#include -+#include -+#include -+ -+#include -+#include -+ -+enum surface_hid_descriptor_entry { -+ SURFACE_HID_DESC_HID = 0, -+ SURFACE_HID_DESC_REPORT = 1, -+ SURFACE_HID_DESC_ATTRS = 2, -+}; -+ -+struct surface_hid_descriptor { -+ __u8 desc_len; /* = 9 */ -+ __u8 desc_type; /* = HID_DT_HID */ -+ __le16 hid_version; -+ __u8 country_code; -+ __u8 num_descriptors; /* = 1 */ -+ -+ __u8 report_desc_type; /* = HID_DT_REPORT */ -+ __le16 report_desc_len; -+} __packed; -+ -+static_assert(sizeof(struct surface_hid_descriptor) == 9); -+ -+struct surface_hid_attributes { -+ __le32 length; -+ __le16 vendor; -+ __le16 product; -+ __le16 version; -+ __u8 _unknown[22]; -+} __packed; -+ -+static_assert(sizeof(struct surface_hid_attributes) == 32); -+ -+struct surface_hid_device; -+ -+struct surface_hid_device_ops { -+ int (*get_descriptor)(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len); -+ int (*output_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); -+ int (*get_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); -+ int (*set_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len); -+}; -+ -+struct surface_hid_device { -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ struct ssam_device_uid uid; -+ -+ struct surface_hid_descriptor hid_desc; -+ struct surface_hid_attributes attrs; -+ -+ struct ssam_event_notifier notif; -+ struct hid_device *hid; -+ -+ struct surface_hid_device_ops ops; -+}; -+ -+int surface_hid_device_add(struct surface_hid_device *shid); -+void surface_hid_device_destroy(struct surface_hid_device *shid); -+ -+extern const struct dev_pm_ops surface_hid_pm_ops; -+ -+#endif /* SURFACE_HID_CORE_H */ --- -2.32.0 - -From ca407872def8e83d86ddaa834c2ec02fe725a1b2 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 10 Mar 2021 23:53:29 +0100 -Subject: [PATCH] HID: surface-hid: Add support for legacy keyboard interface - -Add support for the legacy keyboard (KBD/TC=0x08) HID transport layer of -the Surface System Aggregator Module (SSAM) to the Surface HID driver. -On Surface Laptops 1 and 2, this interface is used to connect the -integrated keyboard. - -Note that this subsystem interface essentially provides a limited HID -transport layer. In contrast to the generic HID interface (TC=0x15) used -on newer Surface models, this interface only allows (as far as we know) -for a single device to be connected and is otherwise severely limited in -terms of support for feature- and output-reports. Specifically, only -caps-lock-LED output-reports and a single read-only feature-report are -supported. - -Signed-off-by: Maximilian Luz -Signed-off-by: Jiri Kosina -Patchset: surface-sam ---- - drivers/hid/surface-hid/Kconfig | 14 ++ - drivers/hid/surface-hid/Makefile | 1 + - drivers/hid/surface-hid/surface_kbd.c | 300 ++++++++++++++++++++++++++ - 3 files changed, 315 insertions(+) - create mode 100644 drivers/hid/surface-hid/surface_kbd.c - -diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig -index 642c7f0e64fe..7ce9b5d641eb 100644 ---- a/drivers/hid/surface-hid/Kconfig -+++ b/drivers/hid/surface-hid/Kconfig -@@ -21,6 +21,20 @@ config SURFACE_HID - integrated touchpad and keyboard, on 7th generation Microsoft Surface - models. - -+config SURFACE_KBD -+ tristate "HID keyboard transport driver for Surface System Aggregator Module" -+ select SURFACE_HID_CORE -+ help -+ Driver to support HID keyboards on Surface Laptop 1 and 2 devices. -+ -+ This driver provides support for the HID transport protocol provided -+ by the Surface Aggregator Module (i.e. the embedded controller) on -+ Microsoft Surface Laptops 1 and 2. It is used to connect the -+ integrated keyboard on those devices. -+ -+ Say M or Y here, if you want support for the integrated keyboard on -+ Microsoft Surface Laptops 1 and 2. -+ - endmenu - - config SURFACE_HID_CORE -diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile -index 62fc04632d3d..4ae11cf09b25 100644 ---- a/drivers/hid/surface-hid/Makefile -+++ b/drivers/hid/surface-hid/Makefile -@@ -4,3 +4,4 @@ - # - obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o - obj-$(CONFIG_SURFACE_HID) += surface_hid.o -+obj-$(CONFIG_SURFACE_KBD) += surface_kbd.o -diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c -new file mode 100644 -index 000000000000..0635341bc517 ---- /dev/null -+++ b/drivers/hid/surface-hid/surface_kbd.c -@@ -0,0 +1,300 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) HID transport driver for the legacy -+ * keyboard interface (KBD/TC=0x08 subsystem). Provides support for the -+ * integrated HID keyboard on Surface Laptops 1 and 2. -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "surface_hid_core.h" -+ -+ -+/* -- SAM interface (KBD). -------------------------------------------------- */ -+ -+#define KBD_FEATURE_REPORT_SIZE 7 /* 6 + report ID */ -+ -+enum surface_kbd_cid { -+ SURFACE_KBD_CID_GET_DESCRIPTOR = 0x00, -+ SURFACE_KBD_CID_SET_CAPSLOCK_LED = 0x01, -+ SURFACE_KBD_CID_EVT_INPUT_GENERIC = 0x03, -+ SURFACE_KBD_CID_EVT_INPUT_HOTKEYS = 0x04, -+ SURFACE_KBD_CID_GET_FEATURE_REPORT = 0x0b, -+}; -+ -+static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ int status; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(entry); -+ rqst.payload = &entry; -+ -+ rsp.capacity = len; -+ rsp.length = 0; -+ rsp.pointer = buf; -+ -+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry)); -+ if (status) -+ return status; -+ -+ if (rsp.length != len) { -+ dev_err(shid->dev, "invalid descriptor length: got %zu, expected, %zu\n", -+ rsp.length, len); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value) -+{ -+ struct ssam_request rqst; -+ u8 value_u8 = value; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = 0; -+ rqst.length = sizeof(value_u8); -+ rqst.payload = &value_u8; -+ -+ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8)); -+} -+ -+static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ u8 payload = 0; -+ int status; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(payload); -+ rqst.payload = &payload; -+ -+ rsp.capacity = len; -+ rsp.length = 0; -+ rsp.pointer = buf; -+ -+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload)); -+ if (status) -+ return status; -+ -+ if (rsp.length != len) { -+ dev_err(shid->dev, "invalid feature report length: got %zu, expected, %zu\n", -+ rsp.length, len); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static bool ssam_kbd_is_input_event(const struct ssam_event *event) -+{ -+ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC) -+ return true; -+ -+ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS) -+ return true; -+ -+ return false; -+} -+ -+static u32 ssam_kbd_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif); -+ -+ /* -+ * Check against device UID manually, as registry and device target -+ * category doesn't line up. -+ */ -+ -+ if (shid->uid.category != event->target_category) -+ return 0; -+ -+ if (shid->uid.target != event->target_id) -+ return 0; -+ -+ if (shid->uid.instance != event->instance_id) -+ return 0; -+ -+ if (!ssam_kbd_is_input_event(event)) -+ return 0; -+ -+ hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+ -+/* -- Transport driver (KBD). ----------------------------------------------- */ -+ -+static int skbd_get_caps_led_value(struct hid_device *hid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ struct hid_field *field; -+ unsigned int offset, size; -+ int i; -+ -+ /* Get LED field. */ -+ field = hidinput_get_led_field(hid); -+ if (!field) -+ return -ENOENT; -+ -+ /* Check if we got the correct report. */ -+ if (len != hid_report_len(field->report)) -+ return -ENOENT; -+ -+ if (rprt_id != field->report->id) -+ return -ENOENT; -+ -+ /* Get caps lock LED index. */ -+ for (i = 0; i < field->report_count; i++) -+ if ((field->usage[i].hid & 0xffff) == 0x02) -+ break; -+ -+ if (i == field->report_count) -+ return -ENOENT; -+ -+ /* Extract value. */ -+ size = field->report_size; -+ offset = field->report_offset + i * size; -+ return !!hid_field_extract(hid, buf + 1, size, offset); -+} -+ -+static int skbd_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ int caps_led; -+ int status; -+ -+ caps_led = skbd_get_caps_led_value(shid->hid, rprt_id, buf, len); -+ if (caps_led < 0) -+ return -EIO; /* Only caps LED output reports are supported. */ -+ -+ status = ssam_kbd_set_caps_led(shid, caps_led); -+ if (status < 0) -+ return status; -+ -+ return len; -+} -+ -+static int skbd_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ u8 report[KBD_FEATURE_REPORT_SIZE]; -+ int status; -+ -+ /* -+ * The keyboard only has a single hard-coded read-only feature report -+ * of size KBD_FEATURE_REPORT_SIZE. Try to load it and compare its -+ * report ID against the requested one. -+ */ -+ -+ if (len < ARRAY_SIZE(report)) -+ return -ENOSPC; -+ -+ status = ssam_kbd_get_feature_report(shid, report, ARRAY_SIZE(report)); -+ if (status < 0) -+ return status; -+ -+ if (rprt_id != report[0]) -+ return -ENOENT; -+ -+ memcpy(buf, report, ARRAY_SIZE(report)); -+ return len; -+} -+ -+static int skbd_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len) -+{ -+ /* Not supported. See skbd_get_feature_report() for details. */ -+ return -EIO; -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int surface_kbd_probe(struct platform_device *pdev) -+{ -+ struct ssam_controller *ctrl; -+ struct surface_hid_device *shid; -+ -+ /* Add device link to EC. */ -+ ctrl = ssam_client_bind(&pdev->dev); -+ if (IS_ERR(ctrl)) -+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl); -+ -+ shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL); -+ if (!shid) -+ return -ENOMEM; -+ -+ shid->dev = &pdev->dev; -+ shid->ctrl = ctrl; -+ -+ shid->uid.domain = SSAM_DOMAIN_SERIALHUB; -+ shid->uid.category = SSAM_SSH_TC_KBD; -+ shid->uid.target = 2; -+ shid->uid.instance = 0; -+ shid->uid.function = 0; -+ -+ shid->notif.base.priority = 1; -+ shid->notif.base.fn = ssam_kbd_event_fn; -+ shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ shid->notif.event.id.target_category = shid->uid.category; -+ shid->notif.event.id.instance = shid->uid.instance; -+ shid->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ shid->notif.event.flags = 0; -+ -+ shid->ops.get_descriptor = ssam_kbd_get_descriptor; -+ shid->ops.output_report = skbd_output_report; -+ shid->ops.get_feature_report = skbd_get_feature_report; -+ shid->ops.set_feature_report = skbd_set_feature_report; -+ -+ platform_set_drvdata(pdev, shid); -+ return surface_hid_device_add(shid); -+} -+ -+static int surface_kbd_remove(struct platform_device *pdev) -+{ -+ surface_hid_device_destroy(platform_get_drvdata(pdev)); -+ return 0; -+} -+ -+static const struct acpi_device_id surface_kbd_match[] = { -+ { "MSHW0096" }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_kbd_match); -+ -+static struct platform_driver surface_kbd_driver = { -+ .probe = surface_kbd_probe, -+ .remove = surface_kbd_remove, -+ .driver = { -+ .name = "surface_keyboard", -+ .acpi_match_table = surface_kbd_match, -+ .pm = &surface_hid_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_kbd_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); --- -2.32.0 - -From bf74b38c3b0fca5cf1255a61760acab783272854 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 23 Apr 2021 00:51:22 +0200 -Subject: [PATCH] HID: surface-hid: Fix integer endian conversion - -We want to convert from 16 bit (unsigned) little endian values contained -in a packed struct to CPU native endian values here, not the other way -around. So replace cpu_to_le16() with get_unaligned_le16(), using the -latter instead of le16_to_cpu() to acknowledge that we are reading from -a packed struct. - -Reported-by: kernel test robot -Fixes: b05ff1002a5c ("HID: Add support for Surface Aggregator Module HID transport") -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/hid/surface-hid/surface_hid_core.c | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c -index 7b27ec392232..5571e74abe91 100644 ---- a/drivers/hid/surface-hid/surface_hid_core.c -+++ b/drivers/hid/surface-hid/surface_hid_core.c -@@ -168,9 +168,9 @@ int surface_hid_device_add(struct surface_hid_device *shid) - - shid->hid->dev.parent = shid->dev; - shid->hid->bus = BUS_HOST; -- shid->hid->vendor = cpu_to_le16(shid->attrs.vendor); -- shid->hid->product = cpu_to_le16(shid->attrs.product); -- shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version); -+ shid->hid->vendor = get_unaligned_le16(&shid->attrs.vendor); -+ shid->hid->product = get_unaligned_le16(&shid->attrs.product); -+ shid->hid->version = get_unaligned_le16(&shid->hid_desc.hid_version); - shid->hid->country = shid->hid_desc.country_code; - - snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X", --- -2.32.0 - -From df72fbd50fbc91af28598541702ef8f829b709c6 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Mon, 7 Jun 2021 21:56:22 +0200 -Subject: [PATCH] HID: surface-hid: Fix get-report request - -Getting a report (e.g. feature report) from a device requires us to send -a request indicating which report we want to retreive and then waiting -for the corresponding response containing that report. We already -provide the response structure to the request call, but the request -isn't marked as a request that expects a response. Thus the request -returns before we receive the response and the response buffer indicates -a zero length response due to that. - -This essentially means that the get-report calls are broken and will -always indicate that a report of length zero has been read. - -Fix this by appropriately marking the request. - -Fixes: b05ff1002a5c ("HID: Add support for Surface Aggregator Module HID transport") -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/hid/surface-hid/surface_hid.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c -index 3477b31611ae..a3a70e4f3f6c 100644 ---- a/drivers/hid/surface-hid/surface_hid.c -+++ b/drivers/hid/surface-hid/surface_hid.c -@@ -143,7 +143,7 @@ static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, - rqst.target_id = shid->uid.target; - rqst.instance_id = shid->uid.instance; - rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT; -- rqst.flags = 0; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; - rqst.length = sizeof(rprt_id); - rqst.payload = &rprt_id; - --- -2.32.0 - -From 4a2ca25f24557a8c8f8eb65d6a7cb49f62b2fb23 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 6 Apr 2021 01:41:25 +0200 -Subject: [PATCH] power: supply: Add battery driver for Surface Aggregator - Module - -On newer Microsoft Surface models (specifically 7th-generation, i.e. -Surface Pro 7, Surface Book 3, Surface Laptop 3, and Surface Laptop Go), -battery and AC status/information is no longer handled via standard ACPI -devices, but instead directly via the Surface System Aggregator Module -(SSAM), i.e. the embedded controller on those devices. - -While on previous generation models, battery status is also handled via -SSAM, an ACPI shim was present to translate the standard ACPI battery -interface to SSAM requests. The SSAM interface itself, which is modeled -closely after the ACPI interface, has not changed. - -This commit introduces a new SSAM client device driver to support -battery status/information via the aforementioned interface on said -Surface models. It is in parts based on the standard ACPI battery -driver. - -Signed-off-by: Maximilian Luz -Signed-off-by: Sebastian Reichel -Patchset: surface-sam ---- - .../ABI/testing/sysfs-class-power-surface | 15 + - MAINTAINERS | 7 + - drivers/power/supply/Kconfig | 16 + - drivers/power/supply/Makefile | 1 + - drivers/power/supply/surface_battery.c | 865 ++++++++++++++++++ - 5 files changed, 904 insertions(+) - create mode 100644 Documentation/ABI/testing/sysfs-class-power-surface - create mode 100644 drivers/power/supply/surface_battery.c - -diff --git a/Documentation/ABI/testing/sysfs-class-power-surface b/Documentation/ABI/testing/sysfs-class-power-surface -new file mode 100644 -index 000000000000..79cde4dcf2f5 ---- /dev/null -+++ b/Documentation/ABI/testing/sysfs-class-power-surface -@@ -0,0 +1,15 @@ -+What: /sys/class/power_supply//alarm -+Date: April 2021 -+KernelVersion: 5.13 -+Contact: Maximilian Luz -+Description: -+ Battery trip point. When the remaining battery capacity crosses this -+ value in either direction, the system will be notified and if -+ necessary woken. -+ -+ Set to zero to clear/disable. -+ -+ Access: Read, Write -+ -+ Valid values: In micro-Wh or micro-Ah, depending on the power unit -+ of the battery -diff --git a/MAINTAINERS b/MAINTAINERS -index f54b22333ec6..7ee93b732270 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11868,6 +11868,13 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch] - F: include/linux/cciss*.h - F: include/uapi/linux/cciss*.h - -+MICROSOFT SURFACE BATTERY AND AC DRIVERS -+M: Maximilian Luz -+L: linux-pm@vger.kernel.org -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/power/supply/surface_battery.c -+ - MICROSOFT SURFACE DTX DRIVER - M: Maximilian Luz - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig -index 006b95eca673..cebeff10d543 100644 ---- a/drivers/power/supply/Kconfig -+++ b/drivers/power/supply/Kconfig -@@ -801,4 +801,20 @@ config BATTERY_ACER_A500 - help - Say Y to include support for Acer Iconia Tab A500 battery fuel gauge. - -+config BATTERY_SURFACE -+ tristate "Battery driver for 7th-generation Microsoft Surface devices" -+ depends on SURFACE_AGGREGATOR_REGISTRY -+ help -+ Driver for battery devices connected via/managed by the Surface System -+ Aggregator Module (SSAM). -+ -+ This driver provides battery-information and -status support for -+ Surface devices where said data is not exposed via the standard ACPI -+ devices. On those models (7th-generation), battery-information is -+ instead handled directly via SSAM client devices and this driver. -+ -+ Say M or Y here to include battery status support for 7th-generation -+ Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, -+ Surface Book 3, and Surface Laptop Go. -+ - endif # POWER_SUPPLY -diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile -index 5e5fdbbef531..134041538d2c 100644 ---- a/drivers/power/supply/Makefile -+++ b/drivers/power/supply/Makefile -@@ -101,3 +101,4 @@ obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o - obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o - obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o - obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o -+obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -new file mode 100644 -index 000000000000..4116dd839ecd ---- /dev/null -+++ b/drivers/power/supply/surface_battery.c -@@ -0,0 +1,865 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Battery driver for 7th-generation Microsoft Surface devices via Surface -+ * System Aggregator Module (SSAM). -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- SAM interface. -------------------------------------------------------- */ -+ -+enum sam_event_cid_bat { -+ SAM_EVENT_CID_BAT_BIX = 0x15, -+ SAM_EVENT_CID_BAT_BST = 0x16, -+ SAM_EVENT_CID_BAT_ADP = 0x17, -+ SAM_EVENT_CID_BAT_PROT = 0x18, -+ SAM_EVENT_CID_BAT_DPTF = 0x53, -+}; -+ -+enum sam_battery_sta { -+ SAM_BATTERY_STA_OK = 0x0f, -+ SAM_BATTERY_STA_PRESENT = 0x10, -+}; -+ -+enum sam_battery_state { -+ SAM_BATTERY_STATE_DISCHARGING = BIT(0), -+ SAM_BATTERY_STATE_CHARGING = BIT(1), -+ SAM_BATTERY_STATE_CRITICAL = BIT(2), -+}; -+ -+enum sam_battery_power_unit { -+ SAM_BATTERY_POWER_UNIT_mW = 0, -+ SAM_BATTERY_POWER_UNIT_mA = 1, -+}; -+ -+/* Equivalent to data returned in ACPI _BIX method, revision 0. */ -+struct spwr_bix { -+ u8 revision; -+ __le32 power_unit; -+ __le32 design_cap; -+ __le32 last_full_charge_cap; -+ __le32 technology; -+ __le32 design_voltage; -+ __le32 design_cap_warn; -+ __le32 design_cap_low; -+ __le32 cycle_count; -+ __le32 measurement_accuracy; -+ __le32 max_sampling_time; -+ __le32 min_sampling_time; -+ __le32 max_avg_interval; -+ __le32 min_avg_interval; -+ __le32 bat_cap_granularity_1; -+ __le32 bat_cap_granularity_2; -+ __u8 model[21]; -+ __u8 serial[11]; -+ __u8 type[5]; -+ __u8 oem_info[21]; -+} __packed; -+ -+static_assert(sizeof(struct spwr_bix) == 119); -+ -+/* Equivalent to data returned in ACPI _BST method. */ -+struct spwr_bst { -+ __le32 state; -+ __le32 present_rate; -+ __le32 remaining_cap; -+ __le32 present_voltage; -+} __packed; -+ -+static_assert(sizeof(struct spwr_bst) == 16); -+ -+#define SPWR_BIX_REVISION 0 -+#define SPWR_BATTERY_VALUE_UNKNOWN 0xffffffff -+ -+/* Get battery status (_STA) */ -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x01, -+}); -+ -+/* Get battery static information (_BIX). */ -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bix, struct spwr_bix, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x02, -+}); -+ -+/* Get battery dynamic information (_BST). */ -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bst, struct spwr_bst, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x03, -+}); -+ -+/* Set battery trip point (_BTP). */ -+SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x04, -+}); -+ -+ -+/* -- Device structures. ---------------------------------------------------- */ -+ -+struct spwr_psy_properties { -+ const char *name; -+ struct ssam_event_registry registry; -+}; -+ -+struct spwr_battery_device { -+ struct ssam_device *sdev; -+ -+ char name[32]; -+ struct power_supply *psy; -+ struct power_supply_desc psy_desc; -+ -+ struct delayed_work update_work; -+ -+ struct ssam_event_notifier notif; -+ -+ struct mutex lock; /* Guards access to state data below. */ -+ unsigned long timestamp; -+ -+ __le32 sta; -+ struct spwr_bix bix; -+ struct spwr_bst bst; -+ u32 alarm; -+}; -+ -+ -+/* -- Module parameters. ---------------------------------------------------- */ -+ -+static unsigned int cache_time = 1000; -+module_param(cache_time, uint, 0644); -+MODULE_PARM_DESC(cache_time, "battery state caching time in milliseconds [default: 1000]"); -+ -+ -+/* -- State management. ----------------------------------------------------- */ -+ -+/* -+ * Delay for battery update quirk. See spwr_external_power_changed() below -+ * for more details. -+ */ -+#define SPWR_AC_BAT_UPDATE_DELAY msecs_to_jiffies(5000) -+ -+static bool spwr_battery_present(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT; -+} -+ -+static int spwr_battery_load_sta(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ return ssam_retry(ssam_bat_get_sta, bat->sdev, &bat->sta); -+} -+ -+static int spwr_battery_load_bix(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (!spwr_battery_present(bat)) -+ return 0; -+ -+ status = ssam_retry(ssam_bat_get_bix, bat->sdev, &bat->bix); -+ -+ /* Enforce NULL terminated strings in case anything goes wrong... */ -+ bat->bix.model[ARRAY_SIZE(bat->bix.model) - 1] = 0; -+ bat->bix.serial[ARRAY_SIZE(bat->bix.serial) - 1] = 0; -+ bat->bix.type[ARRAY_SIZE(bat->bix.type) - 1] = 0; -+ bat->bix.oem_info[ARRAY_SIZE(bat->bix.oem_info) - 1] = 0; -+ -+ return status; -+} -+ -+static int spwr_battery_load_bst(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ if (!spwr_battery_present(bat)) -+ return 0; -+ -+ return ssam_retry(ssam_bat_get_bst, bat->sdev, &bat->bst); -+} -+ -+static int spwr_battery_set_alarm_unlocked(struct spwr_battery_device *bat, u32 value) -+{ -+ __le32 value_le = cpu_to_le32(value); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ bat->alarm = value; -+ return ssam_retry(ssam_bat_set_btp, bat->sdev, &value_le); -+} -+ -+static int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, bool cached) -+{ -+ unsigned long cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time); -+ int status; -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (cached && bat->timestamp && time_is_after_jiffies(cache_deadline)) -+ return 0; -+ -+ status = spwr_battery_load_sta(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bst(bat); -+ if (status) -+ return status; -+ -+ bat->timestamp = jiffies; -+ return 0; -+} -+ -+static int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached) -+{ -+ int status; -+ -+ mutex_lock(&bat->lock); -+ status = spwr_battery_update_bst_unlocked(bat, cached); -+ mutex_unlock(&bat->lock); -+ -+ return status; -+} -+ -+static int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ lockdep_assert_held(&bat->lock); -+ -+ status = spwr_battery_load_sta(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bix(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bst(bat); -+ if (status) -+ return status; -+ -+ if (bat->bix.revision != SPWR_BIX_REVISION) -+ dev_warn(&bat->sdev->dev, "unsupported battery revision: %u\n", bat->bix.revision); -+ -+ bat->timestamp = jiffies; -+ return 0; -+} -+ -+static u32 sprw_battery_get_full_cap_safe(struct spwr_battery_device *bat) -+{ -+ u32 full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN) -+ full_cap = get_unaligned_le32(&bat->bix.design_cap); -+ -+ return full_cap; -+} -+ -+static bool spwr_battery_is_full(struct spwr_battery_device *bat) -+{ -+ u32 state = get_unaligned_le32(&bat->bst.state); -+ u32 full_cap = sprw_battery_get_full_cap_safe(bat); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ return full_cap != SPWR_BATTERY_VALUE_UNKNOWN && full_cap != 0 && -+ remaining_cap != SPWR_BATTERY_VALUE_UNKNOWN && -+ remaining_cap >= full_cap && -+ state == 0; -+} -+ -+static int spwr_battery_recheck_full(struct spwr_battery_device *bat) -+{ -+ bool present; -+ u32 unit; -+ int status; -+ -+ mutex_lock(&bat->lock); -+ unit = get_unaligned_le32(&bat->bix.power_unit); -+ present = spwr_battery_present(bat); -+ -+ status = spwr_battery_update_bix_unlocked(bat); -+ if (status) -+ goto out; -+ -+ /* If battery has been attached, (re-)initialize alarm. */ -+ if (!present && spwr_battery_present(bat)) { -+ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); -+ -+ status = spwr_battery_set_alarm_unlocked(bat, cap_warn); -+ if (status) -+ goto out; -+ } -+ -+ /* -+ * Warn if the unit has changed. This is something we genuinely don't -+ * expect to happen, so make this a big warning. If it does, we'll -+ * need to add support for it. -+ */ -+ WARN_ON(unit != get_unaligned_le32(&bat->bix.power_unit)); -+ -+out: -+ mutex_unlock(&bat->lock); -+ -+ if (!status) -+ power_supply_changed(bat->psy); -+ -+ return status; -+} -+ -+static int spwr_battery_recheck_status(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ status = spwr_battery_update_bst(bat, false); -+ if (!status) -+ power_supply_changed(bat->psy); -+ -+ return status; -+} -+ -+static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif); -+ int status; -+ -+ dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", -+ event->command_id, event->instance_id, event->target_id); -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_BAT_BIX: -+ status = spwr_battery_recheck_full(bat); -+ break; -+ -+ case SAM_EVENT_CID_BAT_BST: -+ status = spwr_battery_recheck_status(bat); -+ break; -+ -+ case SAM_EVENT_CID_BAT_PROT: -+ /* -+ * TODO: Implement support for battery protection status change -+ * event. -+ */ -+ status = 0; -+ break; -+ -+ case SAM_EVENT_CID_BAT_DPTF: -+ /* -+ * TODO: Implement support for DPTF event. -+ */ -+ status = 0; -+ break; -+ -+ default: -+ return 0; -+ } -+ -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+} -+ -+static void spwr_battery_update_bst_workfn(struct work_struct *work) -+{ -+ struct delayed_work *dwork = to_delayed_work(work); -+ struct spwr_battery_device *bat; -+ int status; -+ -+ bat = container_of(dwork, struct spwr_battery_device, update_work); -+ -+ status = spwr_battery_update_bst(bat, false); -+ if (status) { -+ dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", status); -+ return; -+ } -+ -+ power_supply_changed(bat->psy); -+} -+ -+static void spwr_external_power_changed(struct power_supply *psy) -+{ -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ -+ /* -+ * Handle battery update quirk: When the battery is fully charged (or -+ * charged up to the limit imposed by the UEFI battery limit) and the -+ * adapter is plugged in or removed, the EC does not send a separate -+ * event for the state (charging/discharging) change. Furthermore it -+ * may take some time until the state is updated on the battery. -+ * Schedule an update to solve this. -+ */ -+ -+ schedule_delayed_work(&bat->update_work, SPWR_AC_BAT_UPDATE_DELAY); -+} -+ -+ -+/* -- Properties. ----------------------------------------------------------- */ -+ -+static const enum power_supply_property spwr_battery_props_chg[] = { -+ POWER_SUPPLY_PROP_STATUS, -+ POWER_SUPPLY_PROP_PRESENT, -+ POWER_SUPPLY_PROP_TECHNOLOGY, -+ POWER_SUPPLY_PROP_CYCLE_COUNT, -+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, -+ POWER_SUPPLY_PROP_VOLTAGE_NOW, -+ POWER_SUPPLY_PROP_CURRENT_NOW, -+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, -+ POWER_SUPPLY_PROP_CHARGE_FULL, -+ POWER_SUPPLY_PROP_CHARGE_NOW, -+ POWER_SUPPLY_PROP_CAPACITY, -+ POWER_SUPPLY_PROP_CAPACITY_LEVEL, -+ POWER_SUPPLY_PROP_MODEL_NAME, -+ POWER_SUPPLY_PROP_MANUFACTURER, -+ POWER_SUPPLY_PROP_SERIAL_NUMBER, -+}; -+ -+static const enum power_supply_property spwr_battery_props_eng[] = { -+ POWER_SUPPLY_PROP_STATUS, -+ POWER_SUPPLY_PROP_PRESENT, -+ POWER_SUPPLY_PROP_TECHNOLOGY, -+ POWER_SUPPLY_PROP_CYCLE_COUNT, -+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, -+ POWER_SUPPLY_PROP_VOLTAGE_NOW, -+ POWER_SUPPLY_PROP_POWER_NOW, -+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, -+ POWER_SUPPLY_PROP_ENERGY_FULL, -+ POWER_SUPPLY_PROP_ENERGY_NOW, -+ POWER_SUPPLY_PROP_CAPACITY, -+ POWER_SUPPLY_PROP_CAPACITY_LEVEL, -+ POWER_SUPPLY_PROP_MODEL_NAME, -+ POWER_SUPPLY_PROP_MANUFACTURER, -+ POWER_SUPPLY_PROP_SERIAL_NUMBER, -+}; -+ -+static int spwr_battery_prop_status(struct spwr_battery_device *bat) -+{ -+ u32 state = get_unaligned_le32(&bat->bst.state); -+ u32 present_rate = get_unaligned_le32(&bat->bst.present_rate); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (state & SAM_BATTERY_STATE_DISCHARGING) -+ return POWER_SUPPLY_STATUS_DISCHARGING; -+ -+ if (state & SAM_BATTERY_STATE_CHARGING) -+ return POWER_SUPPLY_STATUS_CHARGING; -+ -+ if (spwr_battery_is_full(bat)) -+ return POWER_SUPPLY_STATUS_FULL; -+ -+ if (present_rate == 0) -+ return POWER_SUPPLY_STATUS_NOT_CHARGING; -+ -+ return POWER_SUPPLY_STATUS_UNKNOWN; -+} -+ -+static int spwr_battery_prop_technology(struct spwr_battery_device *bat) -+{ -+ lockdep_assert_held(&bat->lock); -+ -+ if (!strcasecmp("NiCd", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_NiCd; -+ -+ if (!strcasecmp("NiMH", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_NiMH; -+ -+ if (!strcasecmp("LION", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_LION; -+ -+ if (!strncasecmp("LI-ION", bat->bix.type, 6)) -+ return POWER_SUPPLY_TECHNOLOGY_LION; -+ -+ if (!strcasecmp("LiP", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_LIPO; -+ -+ return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; -+} -+ -+static int spwr_battery_prop_capacity(struct spwr_battery_device *bat) -+{ -+ u32 full_cap = sprw_battery_get_full_cap_safe(bat); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN) -+ return -ENODATA; -+ -+ if (remaining_cap == SPWR_BATTERY_VALUE_UNKNOWN) -+ return -ENODATA; -+ -+ return remaining_cap * 100 / full_cap; -+} -+ -+static int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat) -+{ -+ u32 state = get_unaligned_le32(&bat->bst.state); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ lockdep_assert_held(&bat->lock); -+ -+ if (state & SAM_BATTERY_STATE_CRITICAL) -+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; -+ -+ if (spwr_battery_is_full(bat)) -+ return POWER_SUPPLY_CAPACITY_LEVEL_FULL; -+ -+ if (remaining_cap <= bat->alarm) -+ return POWER_SUPPLY_CAPACITY_LEVEL_LOW; -+ -+ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; -+} -+ -+static int spwr_battery_get_property(struct power_supply *psy, enum power_supply_property psp, -+ union power_supply_propval *val) -+{ -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ u32 value; -+ int status; -+ -+ mutex_lock(&bat->lock); -+ -+ status = spwr_battery_update_bst_unlocked(bat, true); -+ if (status) -+ goto out; -+ -+ /* Abort if battery is not present. */ -+ if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) { -+ status = -ENODEV; -+ goto out; -+ } -+ -+ switch (psp) { -+ case POWER_SUPPLY_PROP_STATUS: -+ val->intval = spwr_battery_prop_status(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_PRESENT: -+ val->intval = spwr_battery_present(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_TECHNOLOGY: -+ val->intval = spwr_battery_prop_technology(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_CYCLE_COUNT: -+ value = get_unaligned_le32(&bat->bix.cycle_count); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: -+ value = get_unaligned_le32(&bat->bix.design_voltage); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_VOLTAGE_NOW: -+ value = get_unaligned_le32(&bat->bst.present_voltage); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CURRENT_NOW: -+ case POWER_SUPPLY_PROP_POWER_NOW: -+ value = get_unaligned_le32(&bat->bst.present_rate); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: -+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: -+ value = get_unaligned_le32(&bat->bix.design_cap); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_FULL: -+ case POWER_SUPPLY_PROP_ENERGY_FULL: -+ value = get_unaligned_le32(&bat->bix.last_full_charge_cap); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_NOW: -+ case POWER_SUPPLY_PROP_ENERGY_NOW: -+ value = get_unaligned_le32(&bat->bst.remaining_cap); -+ if (value != SPWR_BATTERY_VALUE_UNKNOWN) -+ val->intval = value * 1000; -+ else -+ status = -ENODATA; -+ break; -+ -+ case POWER_SUPPLY_PROP_CAPACITY: -+ val->intval = spwr_battery_prop_capacity(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL: -+ val->intval = spwr_battery_prop_capacity_level(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_MODEL_NAME: -+ val->strval = bat->bix.model; -+ break; -+ -+ case POWER_SUPPLY_PROP_MANUFACTURER: -+ val->strval = bat->bix.oem_info; -+ break; -+ -+ case POWER_SUPPLY_PROP_SERIAL_NUMBER: -+ val->strval = bat->bix.serial; -+ break; -+ -+ default: -+ status = -EINVAL; -+ break; -+ } -+ -+out: -+ mutex_unlock(&bat->lock); -+ return status; -+} -+ -+ -+/* -- Alarm attribute. ------------------------------------------------------ */ -+ -+static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct power_supply *psy = dev_get_drvdata(dev); -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ int status; -+ -+ mutex_lock(&bat->lock); -+ status = sysfs_emit(buf, "%d\n", bat->alarm * 1000); -+ mutex_unlock(&bat->lock); -+ -+ return status; -+} -+ -+static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, const char *buf, -+ size_t count) -+{ -+ struct power_supply *psy = dev_get_drvdata(dev); -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ unsigned long value; -+ int status; -+ -+ status = kstrtoul(buf, 0, &value); -+ if (status) -+ return status; -+ -+ mutex_lock(&bat->lock); -+ -+ if (!spwr_battery_present(bat)) { -+ mutex_unlock(&bat->lock); -+ return -ENODEV; -+ } -+ -+ status = spwr_battery_set_alarm_unlocked(bat, value / 1000); -+ if (status) { -+ mutex_unlock(&bat->lock); -+ return status; -+ } -+ -+ mutex_unlock(&bat->lock); -+ return count; -+} -+ -+DEVICE_ATTR_RW(alarm); -+ -+static struct attribute *spwr_battery_attrs[] = { -+ &dev_attr_alarm.attr, -+ NULL, -+}; -+ATTRIBUTE_GROUPS(spwr_battery); -+ -+ -+/* -- Device setup. --------------------------------------------------------- */ -+ -+static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_device *sdev, -+ struct ssam_event_registry registry, const char *name) -+{ -+ mutex_init(&bat->lock); -+ strncpy(bat->name, name, ARRAY_SIZE(bat->name) - 1); -+ -+ bat->sdev = sdev; -+ -+ bat->notif.base.priority = 1; -+ bat->notif.base.fn = spwr_notify_bat; -+ bat->notif.event.reg = registry; -+ bat->notif.event.id.target_category = sdev->uid.category; -+ bat->notif.event.id.instance = 0; -+ bat->notif.event.mask = SSAM_EVENT_MASK_STRICT; -+ bat->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ bat->psy_desc.name = bat->name; -+ bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; -+ bat->psy_desc.get_property = spwr_battery_get_property; -+ -+ INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn); -+} -+ -+static int spwr_battery_register(struct spwr_battery_device *bat) -+{ -+ struct power_supply_config psy_cfg = {}; -+ __le32 sta; -+ int status; -+ -+ /* Make sure the device is there and functioning properly. */ -+ status = ssam_retry(ssam_bat_get_sta, bat->sdev, &sta); -+ if (status) -+ return status; -+ -+ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) -+ return -ENODEV; -+ -+ /* Satisfy lockdep although we are in an exclusive context here. */ -+ mutex_lock(&bat->lock); -+ -+ status = spwr_battery_update_bix_unlocked(bat); -+ if (status) { -+ mutex_unlock(&bat->lock); -+ return status; -+ } -+ -+ if (spwr_battery_present(bat)) { -+ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); -+ -+ status = spwr_battery_set_alarm_unlocked(bat, cap_warn); -+ if (status) { -+ mutex_unlock(&bat->lock); -+ return status; -+ } -+ } -+ -+ mutex_unlock(&bat->lock); -+ -+ bat->psy_desc.external_power_changed = spwr_external_power_changed; -+ -+ switch (get_unaligned_le32(&bat->bix.power_unit)) { -+ case SAM_BATTERY_POWER_UNIT_mW: -+ bat->psy_desc.properties = spwr_battery_props_eng; -+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng); -+ break; -+ -+ case SAM_BATTERY_POWER_UNIT_mA: -+ bat->psy_desc.properties = spwr_battery_props_chg; -+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg); -+ break; -+ -+ default: -+ dev_err(&bat->sdev->dev, "unsupported battery power unit: %u\n", -+ get_unaligned_le32(&bat->bix.power_unit)); -+ return -EINVAL; -+ } -+ -+ psy_cfg.drv_data = bat; -+ psy_cfg.attr_grp = spwr_battery_groups; -+ -+ bat->psy = devm_power_supply_register(&bat->sdev->dev, &bat->psy_desc, &psy_cfg); -+ if (IS_ERR(bat->psy)) -+ return PTR_ERR(bat->psy); -+ -+ return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int __maybe_unused surface_battery_resume(struct device *dev) -+{ -+ return spwr_battery_recheck_full(dev_get_drvdata(dev)); -+} -+SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume); -+ -+static int surface_battery_probe(struct ssam_device *sdev) -+{ -+ const struct spwr_psy_properties *p; -+ struct spwr_battery_device *bat; -+ -+ p = ssam_device_get_match_data(sdev); -+ if (!p) -+ return -ENODEV; -+ -+ bat = devm_kzalloc(&sdev->dev, sizeof(*bat), GFP_KERNEL); -+ if (!bat) -+ return -ENOMEM; -+ -+ spwr_battery_init(bat, sdev, p->registry, p->name); -+ ssam_device_set_drvdata(sdev, bat); -+ -+ return spwr_battery_register(bat); -+} -+ -+static void surface_battery_remove(struct ssam_device *sdev) -+{ -+ struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); -+ -+ ssam_notifier_unregister(sdev->ctrl, &bat->notif); -+ cancel_delayed_work_sync(&bat->update_work); -+} -+ -+static const struct spwr_psy_properties spwr_psy_props_bat1 = { -+ .name = "BAT1", -+ .registry = SSAM_EVENT_REGISTRY_SAM, -+}; -+ -+static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { -+ .name = "BAT2", -+ .registry = SSAM_EVENT_REGISTRY_KIP, -+}; -+ -+static const struct ssam_device_id surface_battery_match[] = { -+ { SSAM_SDEV(BAT, 0x01, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, -+ { SSAM_SDEV(BAT, 0x02, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_battery_match); -+ -+static struct ssam_device_driver surface_battery_driver = { -+ .probe = surface_battery_probe, -+ .remove = surface_battery_remove, -+ .match_table = surface_battery_match, -+ .driver = { -+ .name = "surface_battery", -+ .pm = &surface_battery_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_battery_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Battery driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); --- -2.32.0 - -From b1345bcac21c00c2e679203a6ffcefc03d066fc9 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 6 Apr 2021 01:41:26 +0200 -Subject: [PATCH] power: supply: Add AC driver for Surface Aggregator Module - -On newer Microsoft Surface models (specifically 7th-generation, i.e. -Surface Pro 7, Surface Book 3, Surface Laptop 3, and Surface Laptop Go), -battery and AC status/information is no longer handled via standard ACPI -devices, but instead directly via the Surface System Aggregator Module -(SSAM), i.e. the embedded controller on those devices. - -While on previous generation models, AC status is also handled via SSAM, -an ACPI shim was present to translate the standard ACPI AC interface to -SSAM requests. The SSAM interface itself, which is modeled closely after -the ACPI interface, has not changed. - -This commit introduces a new SSAM client device driver to support AC -status/information via the aforementioned interface on said Surface -models. - -Signed-off-by: Maximilian Luz -Signed-off-by: Sebastian Reichel -Patchset: surface-sam ---- - MAINTAINERS | 1 + - drivers/power/supply/Kconfig | 16 ++ - drivers/power/supply/Makefile | 1 + - drivers/power/supply/surface_charger.c | 282 +++++++++++++++++++++++++ - 4 files changed, 300 insertions(+) - create mode 100644 drivers/power/supply/surface_charger.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 7ee93b732270..710617e26f3e 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -11874,6 +11874,7 @@ L: linux-pm@vger.kernel.org - L: platform-driver-x86@vger.kernel.org - S: Maintained - F: drivers/power/supply/surface_battery.c -+F: drivers/power/supply/surface_charger.c - - MICROSOFT SURFACE DTX DRIVER - M: Maximilian Luz -diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig -index cebeff10d543..91f7cf425ac9 100644 ---- a/drivers/power/supply/Kconfig -+++ b/drivers/power/supply/Kconfig -@@ -817,4 +817,20 @@ config BATTERY_SURFACE - Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, - Surface Book 3, and Surface Laptop Go. - -+config CHARGER_SURFACE -+ tristate "AC driver for 7th-generation Microsoft Surface devices" -+ depends on SURFACE_AGGREGATOR_REGISTRY -+ help -+ Driver for AC devices connected via/managed by the Surface System -+ Aggregator Module (SSAM). -+ -+ This driver provides AC-information and -status support for Surface -+ devices where said data is not exposed via the standard ACPI devices. -+ On those models (7th-generation), AC-information is instead handled -+ directly via a SSAM client device and this driver. -+ -+ Say M or Y here to include AC status support for 7th-generation -+ Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, -+ Surface Book 3, and Surface Laptop Go. -+ - endif # POWER_SUPPLY -diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile -index 134041538d2c..a7309a3d1a47 100644 ---- a/drivers/power/supply/Makefile -+++ b/drivers/power/supply/Makefile -@@ -102,3 +102,4 @@ obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o - obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o - obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o - obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o -+obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -new file mode 100644 -index 000000000000..c2dd7e604d14 ---- /dev/null -+++ b/drivers/power/supply/surface_charger.c -@@ -0,0 +1,282 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * AC driver for 7th-generation Microsoft Surface devices via Surface System -+ * Aggregator Module (SSAM). -+ * -+ * Copyright (C) 2019-2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- SAM interface. -------------------------------------------------------- */ -+ -+enum sam_event_cid_bat { -+ SAM_EVENT_CID_BAT_ADP = 0x17, -+}; -+ -+enum sam_battery_sta { -+ SAM_BATTERY_STA_OK = 0x0f, -+ SAM_BATTERY_STA_PRESENT = 0x10, -+}; -+ -+/* Get battery status (_STA). */ -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x01, -+}); -+ -+/* Get platform power source for battery (_PSR / DPTF PSRC). */ -+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x0d, -+}); -+ -+ -+/* -- Device structures. ---------------------------------------------------- */ -+ -+struct spwr_psy_properties { -+ const char *name; -+ struct ssam_event_registry registry; -+}; -+ -+struct spwr_ac_device { -+ struct ssam_device *sdev; -+ -+ char name[32]; -+ struct power_supply *psy; -+ struct power_supply_desc psy_desc; -+ -+ struct ssam_event_notifier notif; -+ -+ struct mutex lock; /* Guards access to state below. */ -+ -+ __le32 state; -+}; -+ -+ -+/* -- State management. ----------------------------------------------------- */ -+ -+static int spwr_ac_update_unlocked(struct spwr_ac_device *ac) -+{ -+ u32 old = ac->state; -+ int status; -+ -+ lockdep_assert_held(&ac->lock); -+ -+ status = ssam_retry(ssam_bat_get_psrc, ac->sdev, &ac->state); -+ if (status < 0) -+ return status; -+ -+ return old != ac->state; -+} -+ -+static int spwr_ac_update(struct spwr_ac_device *ac) -+{ -+ int status; -+ -+ mutex_lock(&ac->lock); -+ status = spwr_ac_update_unlocked(ac); -+ mutex_unlock(&ac->lock); -+ -+ return status; -+} -+ -+static int spwr_ac_recheck(struct spwr_ac_device *ac) -+{ -+ int status; -+ -+ status = spwr_ac_update(ac); -+ if (status > 0) -+ power_supply_changed(ac->psy); -+ -+ return status >= 0 ? 0 : status; -+} -+ -+static u32 spwr_notify_ac(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct spwr_ac_device *ac; -+ int status; -+ -+ ac = container_of(nf, struct spwr_ac_device, notif); -+ -+ dev_dbg(&ac->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", -+ event->command_id, event->instance_id, event->target_id); -+ -+ /* -+ * Allow events of all targets/instances here. Global adapter status -+ * seems to be handled via target=1 and instance=1, but events are -+ * reported on all targets/instances in use. -+ * -+ * While it should be enough to just listen on 1/1, listen everywhere to -+ * make sure we don't miss anything. -+ */ -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_BAT_ADP: -+ status = spwr_ac_recheck(ac); -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+ -+ default: -+ return 0; -+ } -+} -+ -+ -+/* -- Properties. ----------------------------------------------------------- */ -+ -+static const enum power_supply_property spwr_ac_props[] = { -+ POWER_SUPPLY_PROP_ONLINE, -+}; -+ -+static int spwr_ac_get_property(struct power_supply *psy, enum power_supply_property psp, -+ union power_supply_propval *val) -+{ -+ struct spwr_ac_device *ac = power_supply_get_drvdata(psy); -+ int status; -+ -+ mutex_lock(&ac->lock); -+ -+ status = spwr_ac_update_unlocked(ac); -+ if (status) -+ goto out; -+ -+ switch (psp) { -+ case POWER_SUPPLY_PROP_ONLINE: -+ val->intval = !!le32_to_cpu(ac->state); -+ break; -+ -+ default: -+ status = -EINVAL; -+ goto out; -+ } -+ -+out: -+ mutex_unlock(&ac->lock); -+ return status; -+} -+ -+ -+/* -- Device setup. --------------------------------------------------------- */ -+ -+static char *battery_supplied_to[] = { -+ "BAT1", -+ "BAT2", -+}; -+ -+static void spwr_ac_init(struct spwr_ac_device *ac, struct ssam_device *sdev, -+ struct ssam_event_registry registry, const char *name) -+{ -+ mutex_init(&ac->lock); -+ strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1); -+ -+ ac->sdev = sdev; -+ -+ ac->notif.base.priority = 1; -+ ac->notif.base.fn = spwr_notify_ac; -+ ac->notif.event.reg = registry; -+ ac->notif.event.id.target_category = sdev->uid.category; -+ ac->notif.event.id.instance = 0; -+ ac->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ ac->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ ac->psy_desc.name = ac->name; -+ ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; -+ ac->psy_desc.properties = spwr_ac_props; -+ ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props); -+ ac->psy_desc.get_property = spwr_ac_get_property; -+} -+ -+static int spwr_ac_register(struct spwr_ac_device *ac) -+{ -+ struct power_supply_config psy_cfg = {}; -+ __le32 sta; -+ int status; -+ -+ /* Make sure the device is there and functioning properly. */ -+ status = ssam_retry(ssam_bat_get_sta, ac->sdev, &sta); -+ if (status) -+ return status; -+ -+ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) -+ return -ENODEV; -+ -+ psy_cfg.drv_data = ac; -+ psy_cfg.supplied_to = battery_supplied_to; -+ psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to); -+ -+ ac->psy = devm_power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg); -+ if (IS_ERR(ac->psy)) -+ return PTR_ERR(ac->psy); -+ -+ return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int __maybe_unused surface_ac_resume(struct device *dev) -+{ -+ return spwr_ac_recheck(dev_get_drvdata(dev)); -+} -+SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); -+ -+static int surface_ac_probe(struct ssam_device *sdev) -+{ -+ const struct spwr_psy_properties *p; -+ struct spwr_ac_device *ac; -+ -+ p = ssam_device_get_match_data(sdev); -+ if (!p) -+ return -ENODEV; -+ -+ ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL); -+ if (!ac) -+ return -ENOMEM; -+ -+ spwr_ac_init(ac, sdev, p->registry, p->name); -+ ssam_device_set_drvdata(sdev, ac); -+ -+ return spwr_ac_register(ac); -+} -+ -+static void surface_ac_remove(struct ssam_device *sdev) -+{ -+ struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); -+ -+ ssam_notifier_unregister(sdev->ctrl, &ac->notif); -+} -+ -+static const struct spwr_psy_properties spwr_psy_props_adp1 = { -+ .name = "ADP1", -+ .registry = SSAM_EVENT_REGISTRY_SAM, -+}; -+ -+static const struct ssam_device_id surface_ac_match[] = { -+ { SSAM_SDEV(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, surface_ac_match); -+ -+static struct ssam_device_driver surface_ac_driver = { -+ .probe = surface_ac_probe, -+ .remove = surface_ac_remove, -+ .match_table = surface_ac_match, -+ .driver = { -+ .name = "surface_ac", -+ .pm = &surface_ac_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_ssam_device_driver(surface_ac_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("AC driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); --- -2.32.0 - -From e32231d4ac303912945147b70d733d21ea1b1b18 Mon Sep 17 00:00:00 2001 -From: Qiheng Lin -Date: Sat, 10 Apr 2021 12:12:46 +0800 -Subject: [PATCH] power: supply: surface-battery: Make some symbols static - -The sparse tool complains as follows: - -drivers/power/supply/surface_battery.c:700:1: warning: - symbol 'dev_attr_alarm' was not declared. Should it be static? -drivers/power/supply/surface_battery.c:805:1: warning: - symbol 'surface_battery_pm_ops' was not declared. Should it be static? - -This symbol is not used outside of surface_battery.c, so this -commit marks it static. - -Reported-by: Hulk Robot -Signed-off-by: Qiheng Lin -Acked-by: Maximilian Luz -Signed-off-by: Sebastian Reichel -Patchset: surface-sam ---- - drivers/power/supply/surface_battery.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -index 4116dd839ecd..7efa431a62b2 100644 ---- a/drivers/power/supply/surface_battery.c -+++ b/drivers/power/supply/surface_battery.c -@@ -697,7 +697,7 @@ static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, co - return count; - } - --DEVICE_ATTR_RW(alarm); -+static DEVICE_ATTR_RW(alarm); - - static struct attribute *spwr_battery_attrs[] = { - &dev_attr_alarm.attr, -@@ -802,7 +802,7 @@ static int __maybe_unused surface_battery_resume(struct device *dev) - { - return spwr_battery_recheck_full(dev_get_drvdata(dev)); - } --SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume); -+static SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume); - - static int surface_battery_probe(struct ssam_device *sdev) - { --- -2.32.0 - -From ae4baf9def6a94953b826b6cacb00b4ff72457d4 Mon Sep 17 00:00:00 2001 -From: Qiheng Lin -Date: Sat, 10 Apr 2021 12:12:49 +0800 -Subject: [PATCH] power: supply: surface-charger: Make symbol - 'surface_ac_pm_ops' static - -The sparse tool complains as follows: - -drivers/power/supply/surface_charger.c:229:1: warning: - symbol 'surface_ac_pm_ops' was not declared. Should it be static? - -This symbol is not used outside of surface_charger.c, so this -commit marks it static. - -Reported-by: Hulk Robot -Signed-off-by: Qiheng Lin -Acked-by: Maximilian Luz -Signed-off-by: Sebastian Reichel -Patchset: surface-sam ---- - drivers/power/supply/surface_charger.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -index c2dd7e604d14..81a5b79822c9 100644 ---- a/drivers/power/supply/surface_charger.c -+++ b/drivers/power/supply/surface_charger.c -@@ -226,7 +226,7 @@ static int __maybe_unused surface_ac_resume(struct device *dev) - { - return spwr_ac_recheck(dev_get_drvdata(dev)); - } --SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); -+static SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); - - static int surface_ac_probe(struct ssam_device *sdev) - { --- -2.32.0 - -From cf2597e96e97d4eb9dfb19ec4e6e3fe8c4226102 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 4 May 2021 20:00:46 +0200 -Subject: [PATCH] power: supply: surface_battery: Fix battery event handling - -The battery subsystem of the Surface Aggregator Module EC requires us to -register the battery notifier with instance ID 0. However, battery -events are actually sent with the instance ID corresponding to the -device, which is nonzero. Thus, the strict-matching approach doesn't -work here and will discard events that the driver is expected to handle. - -To fix this we have to fall back on notifier matching by target-category -only and have to manually check the instance ID in the notifier -callback. - -Fixes: 167f77f7d0b3 ("power: supply: Add battery driver for Surface Aggregator Module") -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/power/supply/surface_battery.c | 14 ++++++++++++-- - 1 file changed, 12 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -index 7efa431a62b2..5ec2e6bb2465 100644 ---- a/drivers/power/supply/surface_battery.c -+++ b/drivers/power/supply/surface_battery.c -@@ -345,6 +345,16 @@ static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_eve - struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif); - int status; - -+ /* -+ * We cannot use strict matching when registering the notifier as the -+ * EC expects us to register it against instance ID 0. Strict matching -+ * would thus drop events, as those may have non-zero instance IDs in -+ * this subsystem. So we need to check the instance ID of the event -+ * here manually. -+ */ -+ if (event->instance_id != bat->sdev->uid.instance) -+ return 0; -+ - dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n", - event->command_id, event->instance_id, event->target_id); - -@@ -720,8 +730,8 @@ static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_devic - bat->notif.base.fn = spwr_notify_bat; - bat->notif.event.reg = registry; - bat->notif.event.id.target_category = sdev->uid.category; -- bat->notif.event.id.instance = 0; -- bat->notif.event.mask = SSAM_EVENT_MASK_STRICT; -+ bat->notif.event.id.instance = 0; /* need to register with instance 0 */ -+ bat->notif.event.mask = SSAM_EVENT_MASK_TARGET; - bat->notif.event.flags = SSAM_EVENT_SEQUENCED; - - bat->psy_desc.name = bat->name; --- -2.32.0 - -From 12c6919dc79bbd4d6782dd089460276b0072e4ba Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 11 May 2021 11:24:21 +0200 -Subject: [PATCH] power: supply: surface-charger: Fix type of integer variable - -The ac->state field is __le32, not u32. So change the variable we're -temporarily storing it in to __le32 as well. - -Reported-by: kernel test robot -Fixes: e61ffb344591 ("power: supply: Add AC driver for Surface Aggregator Module") -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/power/supply/surface_charger.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -index 81a5b79822c9..a060c36c7766 100644 ---- a/drivers/power/supply/surface_charger.c -+++ b/drivers/power/supply/surface_charger.c -@@ -66,7 +66,7 @@ struct spwr_ac_device { - - static int spwr_ac_update_unlocked(struct spwr_ac_device *ac) - { -- u32 old = ac->state; -+ __le32 old = ac->state; - int status; - - lockdep_assert_held(&ac->lock); --- -2.32.0 - -From fcab68d61af80d7a72ac2c920968f13d6ea9af16 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 2 Jun 2021 18:27:21 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow registering notifiers - without enabling events - -Currently, each SSAM event notifier is directly tied to one group of -events. This makes sense as registering a notifier will automatically -take care of enabling the corresponding event group and normally drivers -only need notifications for a very limited number of events, associated -with different callbacks for each group. - -However, there are rare cases, especially for debugging, when we want to -get notifications for a whole event target category instead of just a -single group of events in that category. Registering multiple notifiers, -i.e. one per group, may be infeasible due to two issues: a) we might not -know every event enable/disable specification as some events are -auto-enabled by the EC and b) forwarding this to the same callback will -lead to duplicate events as we might not know the full event -specification to perform the appropriate filtering. - -This commit introduces observer-notifiers, which are notifiers that are -not tied to a specific event group and do not attempt to manage any -events. In other words, they can be registered without enabling any -event group or incrementing the corresponding reference count and just -act as silent observers, listening to all currently/previously enabled -events based on their match-specification. - -Essentially, this allows us to register one single notifier for a full -event target category, meaning that we can process all events of that -target category in a single callback without duplication. Specifically, -this will be used in the cdev debug interface to forward events to -user-space via a device file from which the events can be read. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../platform/surface/aggregator/controller.c | 69 +++++++++++-------- - include/linux/surface_aggregator/controller.h | 17 +++++ - 2 files changed, 58 insertions(+), 28 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index a06964aa96e7..cd3a6b77f48d 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2127,9 +2127,15 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) - * @ctrl: The controller to register the notifier on. - * @n: The event notifier to register. - * -- * Register an event notifier and increment the usage counter of the -- * associated SAM event. If the event was previously not enabled, it will be -- * enabled during this call. -+ * Register an event notifier. Increment the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the event is not -+ * marked as an observer and is currently not enabled, it will be enabled -+ * during this call. If the notifier is marked as an observer, no attempt will -+ * be made at enabling any event and no reference count will be modified. -+ * -+ * Notifiers marked as observers do not need to be associated with one specific -+ * event, i.e. as long as no event matching is performed, only the event target -+ * category needs to be set. - * - * Return: Returns zero on success, %-ENOSPC if there have already been - * %INT_MAX notifiers for the event ID/type associated with the notifier block -@@ -2138,11 +2144,10 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) - * for the specific associated event, returns the status of the event-enable - * EC-command. - */ --int ssam_notifier_register(struct ssam_controller *ctrl, -- struct ssam_event_notifier *n) -+int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n) - { - u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); -- struct ssam_nf_refcount_entry *entry; -+ struct ssam_nf_refcount_entry *entry = NULL; - struct ssam_nf_head *nf_head; - struct ssam_nf *nf; - int status; -@@ -2155,29 +2160,32 @@ int ssam_notifier_register(struct ssam_controller *ctrl, - - mutex_lock(&nf->lock); - -- entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); -- if (IS_ERR(entry)) { -- mutex_unlock(&nf->lock); -- return PTR_ERR(entry); -- } -+ if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { -+ entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); -+ if (IS_ERR(entry)) { -+ mutex_unlock(&nf->lock); -+ return PTR_ERR(entry); -+ } - -- ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -- n->event.reg.target_category, n->event.id.target_category, -- n->event.id.instance, entry->refcount); -+ ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ n->event.reg.target_category, n->event.id.target_category, -+ n->event.id.instance, entry->refcount); -+ } - - status = ssam_nfblk_insert(nf_head, &n->base); - if (status) { -- entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); -- if (entry->refcount == 0) -- kfree(entry); -+ if (entry) { -+ entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); -+ if (entry->refcount == 0) -+ kfree(entry); -+ } - - mutex_unlock(&nf->lock); - return status; - } - -- if (entry->refcount == 1) { -- status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, -- n->event.flags); -+ if (entry && entry->refcount == 1) { -+ status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, n->event.flags); - if (status) { - ssam_nfblk_remove(&n->base); - kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id)); -@@ -2188,7 +2196,7 @@ int ssam_notifier_register(struct ssam_controller *ctrl, - - entry->flags = n->event.flags; - -- } else if (entry->flags != n->event.flags) { -+ } else if (entry && entry->flags != n->event.flags) { - ssam_warn(ctrl, - "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", - n->event.flags, entry->flags, n->event.reg.target_category, -@@ -2205,17 +2213,16 @@ EXPORT_SYMBOL_GPL(ssam_notifier_register); - * @ctrl: The controller the notifier has been registered on. - * @n: The event notifier to unregister. - * -- * Unregister an event notifier and decrement the usage counter of the -- * associated SAM event. If the usage counter reaches zero, the event will be -- * disabled. -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. - * - * Return: Returns zero on success, %-ENOENT if the given notifier block has - * not been registered on the controller. If the given notifier block was the - * last one associated with its specific event, returns the status of the - * event-disable EC-command. - */ --int ssam_notifier_unregister(struct ssam_controller *ctrl, -- struct ssam_event_notifier *n) -+int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) - { - u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); - struct ssam_nf_refcount_entry *entry; -@@ -2236,6 +2243,13 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, - return -ENOENT; - } - -+ /* -+ * If this is an observer notifier, do not attempt to disable the -+ * event, just remove it. -+ */ -+ if (n->flags & SSAM_EVENT_NOTIFIER_OBSERVER) -+ goto remove; -+ - entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); - if (WARN_ON(!entry)) { - /* -@@ -2260,8 +2274,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, - } - - if (entry->refcount == 0) { -- status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, -- n->event.flags); -+ status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, n->event.flags); - kfree(entry); - } - -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index 0806796eabcb..cf4bb48a850e 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -795,6 +795,20 @@ enum ssam_event_mask { - #define SSAM_EVENT_REGISTRY_REG \ - SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02) - -+/** -+ * enum ssam_event_notifier_flags - Flags for event notifiers. -+ * @SSAM_EVENT_NOTIFIER_OBSERVER: -+ * The corresponding notifier acts as observer. Registering a notifier -+ * with this flag set will not attempt to enable any event. Equally, -+ * unregistering will not attempt to disable any event. Note that a -+ * notifier with this flag may not even correspond to a certain event at -+ * all, only to a specific event target category. Event matching will not -+ * be influenced by this flag. -+ */ -+enum ssam_event_notifier_flags { -+ SSAM_EVENT_NOTIFIER_OBSERVER = BIT(0), -+}; -+ - /** - * struct ssam_event_notifier - Notifier block for SSAM events. - * @base: The base notifier block with callback function and priority. -@@ -803,6 +817,7 @@ enum ssam_event_mask { - * @event.id: ID specifying the event. - * @event.mask: Flags determining how events are matched to the notifier. - * @event.flags: Flags used for enabling the event. -+ * @flags: Notifier flags (see &enum ssam_event_notifier_flags). - */ - struct ssam_event_notifier { - struct ssam_notifier_block base; -@@ -813,6 +828,8 @@ struct ssam_event_notifier { - enum ssam_event_mask mask; - u8 flags; - } event; -+ -+ unsigned long flags; - }; - - int ssam_notifier_register(struct ssam_controller *ctrl, --- -2.32.0 - -From 674b886f8e962823be0e849b33ca9f842319a429 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 2 Jun 2021 18:34:48 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow enabling of events - without notifiers - -We can already enable and disable SAM events via one of two ways: either -via a (non-observer) notifier tied to a specific event group, or a -generic event enable/disable request. In some instances, however, -neither method may be desirable. - -The first method will tie the event enable request to a specific -notifier, however, when we want to receive notifications for multiple -event groups of the same target category and forward this to the same -notifier callback, we may receive duplicate events, i.e. one event per -registered notifier. The second method will bypass the internal -reference counting mechanism, meaning that a disable request will -disable the event regardless of any other client driver using it, which -may break the functionality of that driver. - -To address this problem, add new functions that allow enabling and -disabling of events via the event reference counting mechanism built -into the controller, without needing to register a notifier. - -This can then be used in combination with observer notifiers to process -multiple events of the same target category without duplication in the -same callback function. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../platform/surface/aggregator/controller.c | 135 ++++++++++++++++++ - include/linux/surface_aggregator/controller.h | 8 ++ - 2 files changed, 143 insertions(+) - -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index cd3a6b77f48d..49edddea417e 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2287,6 +2287,141 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - } - EXPORT_SYMBOL_GPL(ssam_notifier_unregister); - -+/** -+ * ssam_controller_event_enable() - Enable the specified event. -+ * @ctrl: The controller to enable the event for. -+ * @reg: The event registry to use for enabling the event. -+ * @id: The event ID specifying the event to be enabled. -+ * @flags: The SAM event flags used for enabling the event. -+ * -+ * Increment the event reference count of the specified event. If the event has -+ * not been enabled previously, it will be enabled by this call. -+ * -+ * Note: In general, ssam_notifier_register() with a non-observer notifier -+ * should be preferred for enabling/disabling events, as this will guarantee -+ * proper ordering and event forwarding in case of errors during event -+ * enabling/disabling. -+ * -+ * Return: Returns zero on success, %-ENOSPC if the reference count for the -+ * specified event has reached its maximum, %-ENOMEM if the corresponding event -+ * entry could not be allocated. If this is the first time that this event has -+ * been enabled (i.e. the reference count was incremented from zero to one by -+ * this call), returns the status of the event-enable EC-command. -+ */ -+int ssam_controller_event_enable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags) -+{ -+ u16 rqid = ssh_tc_to_rqid(id.target_category); -+ struct ssam_nf_refcount_entry *entry; -+ struct ssam_nf_head *nf_head; -+ struct ssam_nf *nf; -+ int status; -+ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ nf = &ctrl->cplt.event.notif; -+ nf_head = &nf->head[ssh_rqid_to_event(rqid)]; -+ -+ mutex_lock(&nf->lock); -+ -+ entry = ssam_nf_refcount_inc(nf, reg, id); -+ if (IS_ERR(entry)) { -+ mutex_unlock(&nf->lock); -+ return PTR_ERR(entry); -+ } -+ -+ ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ reg.target_category, id.target_category, id.instance, -+ entry->refcount); -+ -+ if (entry->refcount == 1) { -+ status = ssam_ssh_event_enable(ctrl, reg, id, flags); -+ if (status) { -+ kfree(ssam_nf_refcount_dec(nf, reg, id)); -+ mutex_unlock(&nf->lock); -+ return status; -+ } -+ -+ entry->flags = flags; -+ -+ } else if (entry->flags != flags) { -+ ssam_warn(ctrl, -+ "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -+ flags, entry->flags, reg.target_category, -+ id.target_category, id.instance); -+ } -+ -+ mutex_unlock(&nf->lock); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_controller_event_enable); -+ -+/** -+ * ssam_controller_event_disable() - Disable the specified event. -+ * @ctrl: The controller to disable the event for. -+ * @reg: The event registry to use for disabling the event. -+ * @id: The event ID specifying the event to be disabled. -+ * @flags: The flags used when enabling the event. -+ * -+ * Decrement the reference count of the specified event. If the reference count -+ * reaches zero, the event will be disabled. -+ * -+ * Note: In general, ssam_notifier_register()/ssam_notifier_unregister() with a -+ * non-observer notifier should be preferred for enabling/disabling events, as -+ * this will guarantee proper ordering and event forwarding in case of errors -+ * during event enabling/disabling. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given event has not been -+ * enabled on the controller. If the reference count of the event reaches zero -+ * during this call, returns the status of the event-disable EC-command. -+ */ -+int ssam_controller_event_disable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags) -+{ -+ u16 rqid = ssh_tc_to_rqid(id.target_category); -+ struct ssam_nf_refcount_entry *entry; -+ struct ssam_nf_head *nf_head; -+ struct ssam_nf *nf; -+ int status = 0; -+ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ nf = &ctrl->cplt.event.notif; -+ nf_head = &nf->head[ssh_rqid_to_event(rqid)]; -+ -+ mutex_lock(&nf->lock); -+ -+ entry = ssam_nf_refcount_dec(nf, reg, id); -+ if (WARN_ON(!entry)) { -+ mutex_unlock(&nf->lock); -+ return -ENOENT; -+ } -+ -+ ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ reg.target_category, id.target_category, id.instance, -+ entry->refcount); -+ -+ if (entry->flags != flags) { -+ ssam_warn(ctrl, -+ "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -+ flags, entry->flags, reg.target_category, -+ id.target_category, id.instance); -+ } -+ -+ if (entry->refcount == 0) { -+ status = ssam_ssh_event_disable(ctrl, reg, id, flags); -+ kfree(entry); -+ } -+ -+ mutex_unlock(&nf->lock); -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_controller_event_disable); -+ - /** - * ssam_notifier_disable_registered() - Disable events for all registered - * notifiers. -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index cf4bb48a850e..7965bdc669c5 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -838,4 +838,12 @@ int ssam_notifier_register(struct ssam_controller *ctrl, - int ssam_notifier_unregister(struct ssam_controller *ctrl, - struct ssam_event_notifier *n); - -+int ssam_controller_event_enable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags); -+ -+int ssam_controller_event_disable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags); -+ - #endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */ --- -2.32.0 - -From 4f63f260aaa1b2f61d407fd32e9ecc1b108e5472 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 3 Jun 2021 00:10:38 +0200 -Subject: [PATCH] platform/surface: aggregator: Update copyright - -It's 2021, update the copyright accordingly. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/Kconfig | 2 +- - drivers/platform/surface/aggregator/Makefile | 2 +- - drivers/platform/surface/aggregator/bus.c | 2 +- - drivers/platform/surface/aggregator/bus.h | 2 +- - drivers/platform/surface/aggregator/controller.c | 2 +- - drivers/platform/surface/aggregator/controller.h | 2 +- - drivers/platform/surface/aggregator/core.c | 2 +- - drivers/platform/surface/aggregator/ssh_msgb.h | 2 +- - drivers/platform/surface/aggregator/ssh_packet_layer.c | 2 +- - drivers/platform/surface/aggregator/ssh_packet_layer.h | 2 +- - drivers/platform/surface/aggregator/ssh_parser.c | 2 +- - drivers/platform/surface/aggregator/ssh_parser.h | 2 +- - drivers/platform/surface/aggregator/ssh_request_layer.c | 2 +- - drivers/platform/surface/aggregator/ssh_request_layer.h | 2 +- - drivers/platform/surface/aggregator/trace.h | 2 +- - include/linux/surface_aggregator/controller.h | 2 +- - include/linux/surface_aggregator/device.h | 2 +- - include/linux/surface_aggregator/serial_hub.h | 2 +- - 18 files changed, 18 insertions(+), 18 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig -index 3aaeea9f0433..fd6dc452f3e8 100644 ---- a/drivers/platform/surface/aggregator/Kconfig -+++ b/drivers/platform/surface/aggregator/Kconfig -@@ -1,5 +1,5 @@ - # SPDX-License-Identifier: GPL-2.0+ --# Copyright (C) 2019-2020 Maximilian Luz -+# Copyright (C) 2019-2021 Maximilian Luz - - menuconfig SURFACE_AGGREGATOR - tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" -diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile -index c112e2c7112b..c8498c41e758 100644 ---- a/drivers/platform/surface/aggregator/Makefile -+++ b/drivers/platform/surface/aggregator/Makefile -@@ -1,5 +1,5 @@ - # SPDX-License-Identifier: GPL-2.0+ --# Copyright (C) 2019-2020 Maximilian Luz -+# Copyright (C) 2019-2021 Maximilian Luz - - # For include/trace/define_trace.h to include trace.h - CFLAGS_core.o = -I$(src) -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -index a9b660af0917..0169677c243e 100644 ---- a/drivers/platform/surface/aggregator/bus.c -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -2,7 +2,7 @@ - /* - * Surface System Aggregator Module bus and device integration. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h -index 7712baaed6a5..ed032c2cbdb2 100644 ---- a/drivers/platform/surface/aggregator/bus.h -+++ b/drivers/platform/surface/aggregator/bus.h -@@ -2,7 +2,7 @@ - /* - * Surface System Aggregator Module bus and device integration. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_BUS_H -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index 49edddea417e..e91ee7e72c14 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2,7 +2,7 @@ - /* - * Main SSAM/SSH controller structure and functionality. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h -index 8297d34e7489..a0963c3562ff 100644 ---- a/drivers/platform/surface/aggregator/controller.h -+++ b/drivers/platform/surface/aggregator/controller.h -@@ -2,7 +2,7 @@ - /* - * Main SSAM/SSH controller structure and functionality. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_CONTROLLER_H -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -index 8dc2c267bcd6..5d780e55f4a1 100644 ---- a/drivers/platform/surface/aggregator/core.c -+++ b/drivers/platform/surface/aggregator/core.c -@@ -7,7 +7,7 @@ - * Handles communication via requests as well as enabling, disabling, and - * relaying of events. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h -index 1221f642dda1..e562958ffdf0 100644 ---- a/drivers/platform/surface/aggregator/ssh_msgb.h -+++ b/drivers/platform/surface/aggregator/ssh_msgb.h -@@ -2,7 +2,7 @@ - /* - * SSH message builder functions. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c -index 15d96eac6811..5e08049fc3ac 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c -@@ -2,7 +2,7 @@ - /* - * SSH packet transport layer. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h -index e8757d03f279..2eb329f0b91a 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.h -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h -@@ -2,7 +2,7 @@ - /* - * SSH packet transport layer. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H -diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c -index e2dead8de94a..b77912f8f13b 100644 ---- a/drivers/platform/surface/aggregator/ssh_parser.c -+++ b/drivers/platform/surface/aggregator/ssh_parser.c -@@ -2,7 +2,7 @@ - /* - * SSH message parser. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h -index 63c38d350988..3bd6e180fd16 100644 ---- a/drivers/platform/surface/aggregator/ssh_parser.h -+++ b/drivers/platform/surface/aggregator/ssh_parser.h -@@ -2,7 +2,7 @@ - /* - * SSH message parser. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c -index 52a83a8fcf82..bfe1aaf38065 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.c -@@ -2,7 +2,7 @@ - /* - * SSH request transport layer. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h -index cb35815858d1..9c3cbae2d4bd 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.h -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.h -@@ -2,7 +2,7 @@ - /* - * SSH request transport layer. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H -diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h -index eb332bb53ae4..de64cf169060 100644 ---- a/drivers/platform/surface/aggregator/trace.h -+++ b/drivers/platform/surface/aggregator/trace.h -@@ -2,7 +2,7 @@ - /* - * Trace points for SSAM/SSH. - * -- * Copyright (C) 2020 Maximilian Luz -+ * Copyright (C) 2020-2021 Maximilian Luz - */ - - #undef TRACE_SYSTEM -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index 7965bdc669c5..068e1982ad37 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -6,7 +6,7 @@ - * managing access and communication to and from the SSAM EC, as well as main - * communication structures and definitions. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 6ff9c58b3e17..f636c5310321 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -7,7 +7,7 @@ - * Provides support for non-platform/non-ACPI SSAM clients via dedicated - * subsystem. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H -diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h -index 64276fbfa1d5..c3de43edcffa 100644 ---- a/include/linux/surface_aggregator/serial_hub.h -+++ b/include/linux/surface_aggregator/serial_hub.h -@@ -6,7 +6,7 @@ - * Surface System Aggregator Module (SSAM). Provides the interface for basic - * packet- and request-based communication with the SSAM EC via SSH. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H --- -2.32.0 - -From e8076a85fafbd1ede0edea60727eda1f2b83ad40 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 2 Jun 2021 19:29:16 +0200 -Subject: [PATCH] platform/surface: aggregator_cdev: Add support for forwarding - events to user-space - -Currently, debugging unknown events requires writing a custom driver. -This is somewhat difficult, slow to adapt, and not entirely -user-friendly for quickly trying to figure out things on devices of some -third-party user. We can do better. We already have a user-space -interface intended for debugging SAM EC requests, so let's add support -for receiving events to that. - -This commit provides support for receiving events by reading from the -controller file. It additionally introduces two new IOCTLs to control -which event categories will be forwarded. Specifically, a user-space -client can specify which target categories it wants to receive events -from by registering the corresponding notifier(s) via the IOCTLs and -after that, read the received events by reading from the controller -device. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../userspace-api/ioctl/ioctl-number.rst | 2 +- - .../surface/surface_aggregator_cdev.c | 454 +++++++++++++++++- - include/uapi/linux/surface_aggregator/cdev.h | 41 +- - 3 files changed, 471 insertions(+), 26 deletions(-) - -diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst -index 1c28b8ef6677..cfd1610e5e95 100644 ---- a/Documentation/userspace-api/ioctl/ioctl-number.rst -+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst -@@ -325,7 +325,7 @@ Code Seq# Include File Comments - 0xA3 90-9F linux/dtlk.h - 0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem - 0xA4 00-1F uapi/asm/sgx.h --0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator -+0xA5 01-05 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator - - 0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver - -diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c -index 79e28fab7e40..7b27c8ca38a5 100644 ---- a/drivers/platform/surface/surface_aggregator_cdev.c -+++ b/drivers/platform/surface/surface_aggregator_cdev.c -@@ -3,29 +3,68 @@ - * Provides user-space access to the SSAM EC via the /dev/surface/aggregator - * misc device. Intended for debugging and development. - * -- * Copyright (C) 2020 Maximilian Luz -+ * Copyright (C) 2020-2021 Maximilian Luz - */ - - #include -+#include - #include -+#include - #include - #include - #include - #include -+#include - #include - #include - #include -+#include - - #include - #include - - #define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" - -+ -+/* -- Main structures. ------------------------------------------------------ */ -+ -+enum ssam_cdev_device_state { -+ SSAM_CDEV_DEVICE_SHUTDOWN_BIT = BIT(0), -+}; -+ - struct ssam_cdev { - struct kref kref; - struct rw_semaphore lock; -+ -+ struct device *dev; - struct ssam_controller *ctrl; - struct miscdevice mdev; -+ unsigned long flags; -+ -+ struct rw_semaphore client_lock; /* Guards client list. */ -+ struct list_head client_list; -+}; -+ -+struct ssam_cdev_client; -+ -+struct ssam_cdev_notifier { -+ struct ssam_cdev_client *client; -+ struct ssam_event_notifier nf; -+}; -+ -+struct ssam_cdev_client { -+ struct ssam_cdev *cdev; -+ struct list_head node; -+ -+ struct mutex notifier_lock; /* Guards notifier access for registration */ -+ struct ssam_cdev_notifier *notifier[SSH_NUM_EVENTS]; -+ -+ struct mutex read_lock; /* Guards FIFO buffer read access */ -+ struct mutex write_lock; /* Guards FIFO buffer write access */ -+ DECLARE_KFIFO(buffer, u8, 4096); -+ -+ wait_queue_head_t waitq; -+ struct fasync_struct *fasync; - }; - - static void __ssam_cdev_release(struct kref *kref) -@@ -47,24 +86,167 @@ static void ssam_cdev_put(struct ssam_cdev *cdev) - kref_put(&cdev->kref, __ssam_cdev_release); - } - --static int ssam_cdev_device_open(struct inode *inode, struct file *filp) -+ -+/* -- Notifier handling. ---------------------------------------------------- */ -+ -+static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in) - { -- struct miscdevice *mdev = filp->private_data; -- struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); -+ struct ssam_cdev_notifier *cdev_nf = container_of(nf, struct ssam_cdev_notifier, nf); -+ struct ssam_cdev_client *client = cdev_nf->client; -+ struct ssam_cdev_event event; -+ size_t n = struct_size(&event, data, in->length); -+ -+ /* Translate event. */ -+ event.target_category = in->target_category; -+ event.target_id = in->target_id; -+ event.command_id = in->command_id; -+ event.instance_id = in->instance_id; -+ event.length = in->length; -+ -+ mutex_lock(&client->write_lock); -+ -+ /* Make sure we have enough space. */ -+ if (kfifo_avail(&client->buffer) < n) { -+ dev_warn(client->cdev->dev, -+ "buffer full, dropping event (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", -+ in->target_category, in->target_id, in->command_id, in->instance_id); -+ mutex_unlock(&client->write_lock); -+ return 0; -+ } - -- filp->private_data = ssam_cdev_get(cdev); -- return stream_open(inode, filp); -+ /* Copy event header and payload. */ -+ kfifo_in(&client->buffer, (const u8 *)&event, struct_size(&event, data, 0)); -+ kfifo_in(&client->buffer, &in->data[0], in->length); -+ -+ mutex_unlock(&client->write_lock); -+ -+ /* Notify waiting readers. */ -+ kill_fasync(&client->fasync, SIGIO, POLL_IN); -+ wake_up_interruptible(&client->waitq); -+ -+ /* -+ * Don't mark events as handled, this is the job of a proper driver and -+ * not the debugging interface. -+ */ -+ return 0; - } - --static int ssam_cdev_device_release(struct inode *inode, struct file *filp) -+static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 category, int priority) - { -- ssam_cdev_put(filp->private_data); -- return 0; -+ struct ssam_cdev_notifier *nf; -+ int index = ((int)category) - 1; -+ int status; -+ -+ /* Validate notifier target category. */ -+ if (index < 0 || index >= SSH_NUM_EVENTS) -+ return -EINVAL; -+ -+ mutex_lock(&client->notifier_lock); -+ -+ /* Check if the notifier has already been registered. */ -+ if (client->notifier[index]) { -+ mutex_unlock(&client->notifier_lock); -+ return -EEXIST; -+ } -+ -+ /* Allocate new notifier. */ -+ nf = kzalloc(sizeof(*nf), GFP_KERNEL); -+ if (!nf) { -+ mutex_unlock(&client->notifier_lock); -+ return -ENOMEM; -+ } -+ -+ /* -+ * Create a dummy notifier with the minimal required fields for -+ * observer registration. Note that we can skip fully specifying event -+ * and registry here as we do not need any matching and use silent -+ * registration, which does not enable the corresponding event. -+ */ -+ nf->client = client; -+ nf->nf.base.fn = ssam_cdev_notifier; -+ nf->nf.base.priority = priority; -+ nf->nf.event.id.target_category = category; -+ nf->nf.event.mask = 0; /* Do not do any matching. */ -+ nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER; -+ -+ /* Register notifier. */ -+ status = ssam_notifier_register(client->cdev->ctrl, &nf->nf); -+ if (status) -+ kfree(nf); -+ else -+ client->notifier[index] = nf; -+ -+ mutex_unlock(&client->notifier_lock); -+ return status; - } - --static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) -+static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 category) -+{ -+ int index = ((int)category) - 1; -+ int status; -+ -+ /* Validate notifier target category. */ -+ if (index < 0 || index >= SSH_NUM_EVENTS) -+ return -EINVAL; -+ -+ mutex_lock(&client->notifier_lock); -+ -+ /* Check if the notifier is currently registered. */ -+ if (!client->notifier[index]) { -+ mutex_unlock(&client->notifier_lock); -+ return -ENOENT; -+ } -+ -+ /* Unregister and free notifier. */ -+ status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[index]->nf); -+ kfree(client->notifier[index]); -+ client->notifier[index] = NULL; -+ -+ mutex_unlock(&client->notifier_lock); -+ return status; -+} -+ -+static void ssam_cdev_notifier_unregister_all(struct ssam_cdev_client *client) -+{ -+ int i; -+ -+ down_read(&client->cdev->lock); -+ -+ /* -+ * This function may be used during shutdown, thus we need to test for -+ * cdev->ctrl instead of the SSAM_CDEV_DEVICE_SHUTDOWN_BIT bit. -+ */ -+ if (client->cdev->ctrl) { -+ for (i = 0; i < SSH_NUM_EVENTS; i++) -+ ssam_cdev_notifier_unregister(client, i + 1); -+ -+ } else { -+ int count = 0; -+ -+ /* -+ * Device has been shut down. Any notifier remaining is a bug, -+ * so warn about that as this would otherwise hardly be -+ * noticeable. Nevertheless, free them as well. -+ */ -+ mutex_lock(&client->notifier_lock); -+ for (i = 0; i < SSH_NUM_EVENTS; i++) { -+ count += !!(client->notifier[i]); -+ kfree(client->notifier[i]); -+ client->notifier[i] = NULL; -+ } -+ mutex_unlock(&client->notifier_lock); -+ -+ WARN_ON(count > 0); -+ } -+ -+ up_read(&client->cdev->lock); -+} -+ -+ -+/* -- IOCTL functions. ------------------------------------------------------ */ -+ -+static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_request __user *r) - { -- struct ssam_cdev_request __user *r; - struct ssam_cdev_request rqst; - struct ssam_request spec = {}; - struct ssam_response rsp = {}; -@@ -72,7 +254,6 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) - void __user *rspdata; - int status = 0, ret = 0, tmp; - -- r = (struct ssam_cdev_request __user *)arg; - ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); - if (ret) - goto out; -@@ -152,7 +333,7 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) - } - - /* Perform request. */ -- status = ssam_request_sync(cdev->ctrl, &spec, &rsp); -+ status = ssam_request_sync(client->cdev->ctrl, &spec, &rsp); - if (status) - goto out; - -@@ -177,48 +358,244 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) - return ret; - } - --static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd, -+static long ssam_cdev_notif_register(struct ssam_cdev_client *client, -+ const struct ssam_cdev_notifier_desc __user *d) -+{ -+ struct ssam_cdev_notifier_desc desc; -+ long ret; -+ -+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ return ssam_cdev_notifier_register(client, desc.target_category, desc.priority); -+} -+ -+static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, -+ const struct ssam_cdev_notifier_desc __user *d) -+{ -+ struct ssam_cdev_notifier_desc desc; -+ long ret; -+ -+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ return ssam_cdev_notifier_unregister(client, desc.target_category); -+} -+ -+ -+/* -- File operations. ------------------------------------------------------ */ -+ -+static int ssam_cdev_device_open(struct inode *inode, struct file *filp) -+{ -+ struct miscdevice *mdev = filp->private_data; -+ struct ssam_cdev_client *client; -+ struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); -+ -+ /* Initialize client */ -+ client = vzalloc(sizeof(*client)); -+ if (!client) -+ return -ENOMEM; -+ -+ client->cdev = ssam_cdev_get(cdev); -+ -+ INIT_LIST_HEAD(&client->node); -+ -+ mutex_init(&client->notifier_lock); -+ -+ mutex_init(&client->read_lock); -+ mutex_init(&client->write_lock); -+ INIT_KFIFO(client->buffer); -+ init_waitqueue_head(&client->waitq); -+ -+ filp->private_data = client; -+ -+ /* Attach client. */ -+ down_write(&cdev->client_lock); -+ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { -+ up_write(&cdev->client_lock); -+ ssam_cdev_put(client->cdev); -+ vfree(client); -+ return -ENODEV; -+ } -+ list_add_tail(&client->node, &cdev->client_list); -+ -+ up_write(&cdev->client_lock); -+ -+ stream_open(inode, filp); -+ return 0; -+} -+ -+static int ssam_cdev_device_release(struct inode *inode, struct file *filp) -+{ -+ struct ssam_cdev_client *client = filp->private_data; -+ -+ /* Force-unregister all remaining notifiers of this client. */ -+ ssam_cdev_notifier_unregister_all(client); -+ -+ /* Detach client. */ -+ down_write(&client->cdev->client_lock); -+ list_del(&client->node); -+ up_write(&client->cdev->client_lock); -+ -+ /* Free client. */ -+ mutex_destroy(&client->write_lock); -+ mutex_destroy(&client->read_lock); -+ -+ mutex_destroy(&client->notifier_lock); -+ -+ ssam_cdev_put(client->cdev); -+ vfree(client); -+ -+ return 0; -+} -+ -+static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd, - unsigned long arg) - { - switch (cmd) { - case SSAM_CDEV_REQUEST: -- return ssam_cdev_request(cdev, arg); -+ return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg); -+ -+ case SSAM_CDEV_NOTIF_REGISTER: -+ return ssam_cdev_notif_register(client, -+ (struct ssam_cdev_notifier_desc __user *)arg); -+ -+ case SSAM_CDEV_NOTIF_UNREGISTER: -+ return ssam_cdev_notif_unregister(client, -+ (struct ssam_cdev_notifier_desc __user *)arg); - - default: - return -ENOTTY; - } - } - --static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, -- unsigned long arg) -+static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) - { -- struct ssam_cdev *cdev = file->private_data; -+ struct ssam_cdev_client *client = file->private_data; - long status; - - /* Ensure that controller is valid for as long as we need it. */ -+ if (down_read_killable(&client->cdev->lock)) -+ return -ERESTARTSYS; -+ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) { -+ up_read(&client->cdev->lock); -+ return -ENODEV; -+ } -+ -+ status = __ssam_cdev_device_ioctl(client, cmd, arg); -+ -+ up_read(&client->cdev->lock); -+ return status; -+} -+ -+static ssize_t ssam_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offs) -+{ -+ struct ssam_cdev_client *client = file->private_data; -+ struct ssam_cdev *cdev = client->cdev; -+ unsigned int copied; -+ int status = 0; -+ - if (down_read_killable(&cdev->lock)) - return -ERESTARTSYS; - -- if (!cdev->ctrl) { -+ /* Make sure we're not shut down. */ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { - up_read(&cdev->lock); - return -ENODEV; - } - -- status = __ssam_cdev_device_ioctl(cdev, cmd, arg); -+ do { -+ /* Check availability, wait if necessary. */ -+ if (kfifo_is_empty(&client->buffer)) { -+ up_read(&cdev->lock); -+ -+ if (file->f_flags & O_NONBLOCK) -+ return -EAGAIN; -+ -+ status = wait_event_interruptible(client->waitq, -+ !kfifo_is_empty(&client->buffer) || -+ test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, -+ &cdev->flags)); -+ if (status < 0) -+ return status; -+ -+ if (down_read_killable(&cdev->lock)) -+ return -ERESTARTSYS; -+ -+ /* Need to check that we're not shut down again. */ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { -+ up_read(&cdev->lock); -+ return -ENODEV; -+ } -+ } -+ -+ /* Try to read from FIFO. */ -+ if (mutex_lock_interruptible(&client->read_lock)) { -+ up_read(&cdev->lock); -+ return -ERESTARTSYS; -+ } -+ -+ status = kfifo_to_user(&client->buffer, buf, count, &copied); -+ mutex_unlock(&client->read_lock); -+ -+ if (status < 0) { -+ up_read(&cdev->lock); -+ return status; -+ } -+ -+ /* We might not have gotten anything, check this here. */ -+ if (copied == 0 && (file->f_flags & O_NONBLOCK)) { -+ up_read(&cdev->lock); -+ return -EAGAIN; -+ } -+ } while (copied == 0); - - up_read(&cdev->lock); -- return status; -+ return copied; -+} -+ -+static __poll_t ssam_cdev_poll(struct file *file, struct poll_table_struct *pt) -+{ -+ struct ssam_cdev_client *client = file->private_data; -+ __poll_t events = 0; -+ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) -+ return EPOLLHUP | EPOLLERR; -+ -+ poll_wait(file, &client->waitq, pt); -+ -+ if (!kfifo_is_empty(&client->buffer)) -+ events |= EPOLLIN | EPOLLRDNORM; -+ -+ return events; -+} -+ -+static int ssam_cdev_fasync(int fd, struct file *file, int on) -+{ -+ struct ssam_cdev_client *client = file->private_data; -+ -+ return fasync_helper(fd, file, on, &client->fasync); - } - - static const struct file_operations ssam_controller_fops = { - .owner = THIS_MODULE, - .open = ssam_cdev_device_open, - .release = ssam_cdev_device_release, -+ .read = ssam_cdev_read, -+ .poll = ssam_cdev_poll, -+ .fasync = ssam_cdev_fasync, - .unlocked_ioctl = ssam_cdev_device_ioctl, - .compat_ioctl = ssam_cdev_device_ioctl, -- .llseek = noop_llseek, -+ .llseek = no_llseek, - }; - -+ -+/* -- Device and driver setup ----------------------------------------------- */ -+ - static int ssam_dbg_device_probe(struct platform_device *pdev) - { - struct ssam_controller *ctrl; -@@ -236,6 +613,7 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) - kref_init(&cdev->kref); - init_rwsem(&cdev->lock); - cdev->ctrl = ctrl; -+ cdev->dev = &pdev->dev; - - cdev->mdev.parent = &pdev->dev; - cdev->mdev.minor = MISC_DYNAMIC_MINOR; -@@ -243,6 +621,9 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) - cdev->mdev.nodename = "surface/aggregator"; - cdev->mdev.fops = &ssam_controller_fops; - -+ init_rwsem(&cdev->client_lock); -+ INIT_LIST_HEAD(&cdev->client_list); -+ - status = misc_register(&cdev->mdev); - if (status) { - kfree(cdev); -@@ -256,8 +637,32 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) - static int ssam_dbg_device_remove(struct platform_device *pdev) - { - struct ssam_cdev *cdev = platform_get_drvdata(pdev); -+ struct ssam_cdev_client *client; - -- misc_deregister(&cdev->mdev); -+ /* -+ * Mark device as shut-down. Prevent new clients from being added and -+ * new operations from being executed. -+ */ -+ set_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags); -+ -+ down_write(&cdev->client_lock); -+ -+ /* Remove all notifiers registered by us. */ -+ list_for_each_entry(client, &cdev->client_list, node) { -+ ssam_cdev_notifier_unregister_all(client); -+ } -+ -+ /* Wake up async clients. */ -+ list_for_each_entry(client, &cdev->client_list, node) { -+ kill_fasync(&client->fasync, SIGIO, POLL_HUP); -+ } -+ -+ /* Wake up blocking clients. */ -+ list_for_each_entry(client, &cdev->client_list, node) { -+ wake_up_interruptible(&client->waitq); -+ } -+ -+ up_write(&cdev->client_lock); - - /* - * The controller is only guaranteed to be valid for as long as the -@@ -266,8 +671,11 @@ static int ssam_dbg_device_remove(struct platform_device *pdev) - */ - down_write(&cdev->lock); - cdev->ctrl = NULL; -+ cdev->dev = NULL; - up_write(&cdev->lock); - -+ misc_deregister(&cdev->mdev); -+ - ssam_cdev_put(cdev); - return 0; - } -diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h -index fbcce04abfe9..4f393fafc235 100644 ---- a/include/uapi/linux/surface_aggregator/cdev.h -+++ b/include/uapi/linux/surface_aggregator/cdev.h -@@ -6,7 +6,7 @@ - * device. This device provides direct user-space access to the SSAM EC. - * Intended for debugging and development. - * -- * Copyright (C) 2020 Maximilian Luz -+ * Copyright (C) 2020-2021 Maximilian Luz - */ - - #ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H -@@ -73,6 +73,43 @@ struct ssam_cdev_request { - } response; - } __attribute__((__packed__)); - --#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) -+/** -+ * struct ssam_cdev_notifier_desc - Notifier descriptor. -+ * @priority: Priority value determining the order in which notifier -+ * callbacks will be called. A higher value means higher -+ * priority, i.e. the associated callback will be executed -+ * earlier than other (lower priority) callbacks. -+ * @target_category: The event target category for which this notifier should -+ * receive events. -+ * -+ * Specifies the notifier that should be registered or unregistered, -+ * specifically with which priority and for which target category of events. -+ */ -+struct ssam_cdev_notifier_desc { -+ __s32 priority; -+ __u8 target_category; -+} __attribute__((__packed__)); -+ -+/** -+ * struct ssam_cdev_event - SSAM event sent by the EC. -+ * @target_category: Target category of the event source. See &enum ssam_ssh_tc. -+ * @target_id: Target ID of the event source. -+ * @command_id: Command ID of the event. -+ * @instance_id: Instance ID of the event source. -+ * @length: Length of the event payload in bytes. -+ * @data: Event payload data. -+ */ -+struct ssam_cdev_event { -+ __u8 target_category; -+ __u8 target_id; -+ __u8 command_id; -+ __u8 instance_id; -+ __u16 length; -+ __u8 data[]; -+} __attribute__((__packed__)); -+ -+#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) -+#define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc) -+#define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc) - - #endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ --- -2.32.0 - -From 6c1af5eaf46d1462ebf82ec40fee70cdc5d37405 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 2 Jun 2021 19:30:42 +0200 -Subject: [PATCH] platform/surface: aggregator_cdev: Allow enabling of events - from user-space - -While events can already be enabled and disabled via the generic request -IOCTL, this bypasses the internal reference counting mechanism of the -controller. Due to that, disabling an event will turn it off regardless -of any other client having requested said event, which may break -functionality of that client. - -To solve this, add IOCTLs wrapping the ssam_controller_event_enable() -and ssam_controller_event_disable() functions, which have been -previously introduced for this specific purpose. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_cdev.c | 58 +++++++++++++++++++ - include/uapi/linux/surface_aggregator/cdev.h | 32 ++++++++++ - 2 files changed, 90 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c -index 7b27c8ca38a5..55bf55c93624 100644 ---- a/drivers/platform/surface/surface_aggregator_cdev.c -+++ b/drivers/platform/surface/surface_aggregator_cdev.c -@@ -384,6 +384,58 @@ static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, - return ssam_cdev_notifier_unregister(client, desc.target_category); - } - -+static long ssam_cdev_event_enable(struct ssam_cdev_client *client, -+ const struct ssam_cdev_event_desc __user *d) -+{ -+ struct ssam_cdev_event_desc desc; -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ long ret; -+ -+ /* Read descriptor from user-space. */ -+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ /* Translate descriptor. */ -+ reg.target_category = desc.reg.target_category; -+ reg.target_id = desc.reg.target_id; -+ reg.cid_enable = desc.reg.cid_enable; -+ reg.cid_disable = desc.reg.cid_disable; -+ -+ id.target_category = desc.id.target_category; -+ id.instance = desc.id.instance; -+ -+ /* Disable event. */ -+ return ssam_controller_event_enable(client->cdev->ctrl, reg, id, desc.flags); -+} -+ -+static long ssam_cdev_event_disable(struct ssam_cdev_client *client, -+ const struct ssam_cdev_event_desc __user *d) -+{ -+ struct ssam_cdev_event_desc desc; -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ long ret; -+ -+ /* Read descriptor from user-space. */ -+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ /* Translate descriptor. */ -+ reg.target_category = desc.reg.target_category; -+ reg.target_id = desc.reg.target_id; -+ reg.cid_enable = desc.reg.cid_enable; -+ reg.cid_disable = desc.reg.cid_disable; -+ -+ id.target_category = desc.id.target_category; -+ id.instance = desc.id.instance; -+ -+ /* Disable event. */ -+ return ssam_controller_event_disable(client->cdev->ctrl, reg, id, desc.flags); -+} -+ - - /* -- File operations. ------------------------------------------------------ */ - -@@ -467,6 +519,12 @@ static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned i - return ssam_cdev_notif_unregister(client, - (struct ssam_cdev_notifier_desc __user *)arg); - -+ case SSAM_CDEV_EVENT_ENABLE: -+ return ssam_cdev_event_enable(client, (struct ssam_cdev_event_desc __user *)arg); -+ -+ case SSAM_CDEV_EVENT_DISABLE: -+ return ssam_cdev_event_disable(client, (struct ssam_cdev_event_desc __user *)arg); -+ - default: - return -ENOTTY; - } -diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h -index 4f393fafc235..08f46b60b151 100644 ---- a/include/uapi/linux/surface_aggregator/cdev.h -+++ b/include/uapi/linux/surface_aggregator/cdev.h -@@ -90,6 +90,36 @@ struct ssam_cdev_notifier_desc { - __u8 target_category; - } __attribute__((__packed__)); - -+/** -+ * struct ssam_cdev_event_desc - Event descriptor. -+ * @reg: Registry via which the event will be enabled/disabled. -+ * @reg.target_category: Target category for the event registry requests. -+ * @reg.target_id: Target ID for the event registry requests. -+ * @reg.cid_enable: Command ID for the event-enable request. -+ * @reg.cid_disable: Command ID for the event-disable request. -+ * @id: ID specifying the event. -+ * @id.target_category: Target category of the event source. -+ * @id.instance: Instance ID of the event source. -+ * @flags: Flags used for enabling the event. -+ * -+ * Specifies which event should be enabled/disabled and how to do that. -+ */ -+struct ssam_cdev_event_desc { -+ struct { -+ __u8 target_category; -+ __u8 target_id; -+ __u8 cid_enable; -+ __u8 cid_disable; -+ } reg; -+ -+ struct { -+ __u8 target_category; -+ __u8 instance; -+ } id; -+ -+ __u8 flags; -+} __attribute__((__packed__)); -+ - /** - * struct ssam_cdev_event - SSAM event sent by the EC. - * @target_category: Target category of the event source. See &enum ssam_ssh_tc. -@@ -111,5 +141,7 @@ struct ssam_cdev_event { - #define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) - #define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc) - #define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc) -+#define SSAM_CDEV_EVENT_ENABLE _IOW(0xA5, 4, struct ssam_cdev_event_desc) -+#define SSAM_CDEV_EVENT_DISABLE _IOW(0xA5, 5, struct ssam_cdev_event_desc) - - #endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ --- -2.32.0 - -From 38fd84ffd2930a1800f8bbb308536096394b31dc Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 2 Jun 2021 19:38:07 +0200 -Subject: [PATCH] platform/surface: aggregator_cdev: Add lockdep support - -Mark functions with locking requirements via the corresponding lockdep -calls for debugging and documentary purposes. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../platform/surface/surface_aggregator_cdev.c | 16 ++++++++++++++++ - 1 file changed, 16 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c -index 55bf55c93624..2cad4147645c 100644 ---- a/drivers/platform/surface/surface_aggregator_cdev.c -+++ b/drivers/platform/surface/surface_aggregator_cdev.c -@@ -137,6 +137,8 @@ static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 categ - int index = ((int)category) - 1; - int status; - -+ lockdep_assert_held_read(&client->cdev->lock); -+ - /* Validate notifier target category. */ - if (index < 0 || index >= SSH_NUM_EVENTS) - return -EINVAL; -@@ -185,6 +187,8 @@ static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 cat - int index = ((int)category) - 1; - int status; - -+ lockdep_assert_held_read(&client->cdev->lock); -+ - /* Validate notifier target category. */ - if (index < 0 || index >= SSH_NUM_EVENTS) - return -EINVAL; -@@ -254,6 +258,8 @@ static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_ - void __user *rspdata; - int status = 0, ret = 0, tmp; - -+ lockdep_assert_held_read(&client->cdev->lock); -+ - ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); - if (ret) - goto out; -@@ -364,6 +370,8 @@ static long ssam_cdev_notif_register(struct ssam_cdev_client *client, - struct ssam_cdev_notifier_desc desc; - long ret; - -+ lockdep_assert_held_read(&client->cdev->lock); -+ - ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); - if (ret) - return ret; -@@ -377,6 +385,8 @@ static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, - struct ssam_cdev_notifier_desc desc; - long ret; - -+ lockdep_assert_held_read(&client->cdev->lock); -+ - ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); - if (ret) - return ret; -@@ -392,6 +402,8 @@ static long ssam_cdev_event_enable(struct ssam_cdev_client *client, - struct ssam_event_id id; - long ret; - -+ lockdep_assert_held_read(&client->cdev->lock); -+ - /* Read descriptor from user-space. */ - ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); - if (ret) -@@ -418,6 +430,8 @@ static long ssam_cdev_event_disable(struct ssam_cdev_client *client, - struct ssam_event_id id; - long ret; - -+ lockdep_assert_held_read(&client->cdev->lock); -+ - /* Read descriptor from user-space. */ - ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); - if (ret) -@@ -507,6 +521,8 @@ static int ssam_cdev_device_release(struct inode *inode, struct file *filp) - static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd, - unsigned long arg) - { -+ lockdep_assert_held_read(&client->cdev->lock); -+ - switch (cmd) { - case SSAM_CDEV_REQUEST: - return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg); --- -2.32.0 - -From f8a8ca2b134d58d8f253acb92e83ae0d20cbedca Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 4 Jun 2021 22:28:41 +0200 -Subject: [PATCH] platform/surface: aggregator: Fixups for user-space event - forwarding series - -Patchset: surface-sam ---- - .../platform/surface/aggregator/controller.c | 248 +++++++++++------- - .../surface/surface_aggregator_cdev.c | 32 ++- - 2 files changed, 174 insertions(+), 106 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index e91ee7e72c14..6646f4d6e10d 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -407,6 +407,31 @@ ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg, - return NULL; - } - -+/** -+ * ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the -+ * given event and free its entry if the reference count reaches zero. -+ * @nf: The notifier system reference. -+ * @reg: The registry used to enable/disable the event. -+ * @id: The event ID. -+ * -+ * Decrements the reference-/activation-count of the specified event, freeing -+ * its entry if it reaches zero. -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ */ -+static void ssam_nf_refcount_dec_free(struct ssam_nf *nf, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id) -+{ -+ struct ssam_nf_refcount_entry *entry; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ entry = ssam_nf_refcount_dec(nf, reg, id); -+ if (entry && entry->refcount == 0) -+ kfree(entry); -+} -+ - /** - * ssam_nf_refcount_empty() - Test if the notification system has any - * enabled/active events. -@@ -2122,6 +2147,109 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) - - /* -- Top-level event registry interface. ----------------------------------- */ - -+/** -+ * ssam_nf_refcount_enable() - Enable event for reference count entry if it has -+ * not already been enabled. -+ * @ctrl: The controller to enable the event on. -+ * @entry: The reference count entry for the event to be enabled. -+ * @flags: The flags used for enabling the event on the EC. -+ * -+ * Enable the event associated with the given reference count entry if the -+ * reference count equals one, i.e. the event has not previously been enabled. -+ * If the event has already been enabled (i.e. reference count not equal to -+ * one), check that the flags used for enabling match and warn about this if -+ * they do not. -+ * -+ * This does not modify the reference count itself, which is done with -+ * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ * -+ * Return: Returns zero on success. If the event is enabled by this call, -+ * returns the status of the event-enable EC command. -+ */ -+static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, -+ struct ssam_nf_refcount_entry *entry, u8 flags) -+{ -+ const struct ssam_event_registry reg = entry->key.reg; -+ const struct ssam_event_id id = entry->key.id; -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ int status; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ reg.target_category, id.target_category, id.instance, entry->refcount); -+ -+ if (entry->refcount == 1) { -+ status = ssam_ssh_event_enable(ctrl, reg, id, flags); -+ if (status) -+ return status; -+ -+ entry->flags = flags; -+ -+ } else if (entry->flags != flags) { -+ ssam_warn(ctrl, -+ "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -+ flags, entry->flags, reg.target_category, id.target_category, -+ id.instance); -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is -+ * no longer in use and free the corresponding entry. -+ * @ctrl: The controller to disable the event on. -+ * @entry: The reference count entry for the event to be disabled. -+ * @flags: The flags used for enabling the event on the EC. -+ * -+ * If the reference count equals zero, i.e. the event is no longer requested by -+ * any client, the event will be disabled and the corresponding reference count -+ * entry freed. The reference count entry must not be used any more after a -+ * call to this function. -+ * -+ * Also checks if the flags used for disabling the event match the flags used -+ * for enabling the event and warns if they do not (regardless of reference -+ * count). -+ * -+ * This does not modify the reference count itself, which is done with -+ * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ * -+ * Return: Returns zero on success. If the event is disabled by this call, -+ * returns the status of the event-enable EC command. -+ */ -+static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, -+ struct ssam_nf_refcount_entry *entry, u8 flags) -+{ -+ const struct ssam_event_registry reg = entry->key.reg; -+ const struct ssam_event_id id = entry->key.id; -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ int status; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ reg.target_category, id.target_category, id.instance, entry->refcount); -+ -+ if (entry->flags != flags) { -+ ssam_warn(ctrl, -+ "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -+ flags, entry->flags, reg.target_category, id.target_category, -+ id.instance); -+ } -+ -+ if (entry->refcount == 0) { -+ status = ssam_ssh_event_disable(ctrl, reg, id, flags); -+ kfree(entry); -+ } -+ -+ return status; -+} -+ - /** - * ssam_notifier_register() - Register an event notifier. - * @ctrl: The controller to register the notifier on. -@@ -2166,41 +2294,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif - mutex_unlock(&nf->lock); - return PTR_ERR(entry); - } -- -- ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -- n->event.reg.target_category, n->event.id.target_category, -- n->event.id.instance, entry->refcount); - } - - status = ssam_nfblk_insert(nf_head, &n->base); - if (status) { -- if (entry) { -- entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); -- if (entry->refcount == 0) -- kfree(entry); -- } -+ if (entry) -+ ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); - - mutex_unlock(&nf->lock); - return status; - } - -- if (entry && entry->refcount == 1) { -- status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, n->event.flags); -+ if (entry) { -+ status = ssam_nf_refcount_enable(ctrl, entry, n->event.flags); - if (status) { - ssam_nfblk_remove(&n->base); -- kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id)); -+ ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); - mutex_unlock(&nf->lock); - synchronize_srcu(&nf_head->srcu); - return status; - } -- -- entry->flags = n->event.flags; -- -- } else if (entry && entry->flags != n->event.flags) { -- ssam_warn(ctrl, -- "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -- n->event.flags, entry->flags, n->event.reg.target_category, -- n->event.id.target_category, n->event.id.instance); - } - - mutex_unlock(&nf->lock); -@@ -2247,35 +2360,20 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - * If this is an observer notifier, do not attempt to disable the - * event, just remove it. - */ -- if (n->flags & SSAM_EVENT_NOTIFIER_OBSERVER) -- goto remove; -- -- entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); -- if (WARN_ON(!entry)) { -- /* -- * If this does not return an entry, there's a logic error -- * somewhere: The notifier block is registered, but the event -- * refcount entry is not there. Remove the notifier block -- * anyways. -- */ -- status = -ENOENT; -- goto remove; -- } -- -- ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -- n->event.reg.target_category, n->event.id.target_category, -- n->event.id.instance, entry->refcount); -+ if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { -+ entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); -+ if (WARN_ON(!entry)) { -+ /* -+ * If this does not return an entry, there's a logic -+ * error somewhere: The notifier block is registered, -+ * but the event refcount entry is not there. Remove -+ * the notifier block anyways. -+ */ -+ status = -ENOENT; -+ goto remove; -+ } - -- if (entry->flags != n->event.flags) { -- ssam_warn(ctrl, -- "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -- n->event.flags, entry->flags, n->event.reg.target_category, -- n->event.id.target_category, n->event.id.instance); -- } -- -- if (entry->refcount == 0) { -- status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, n->event.flags); -- kfree(entry); -+ status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); - } - - remove: -@@ -2313,17 +2411,13 @@ int ssam_controller_event_enable(struct ssam_controller *ctrl, - struct ssam_event_id id, u8 flags) - { - u16 rqid = ssh_tc_to_rqid(id.target_category); -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; - struct ssam_nf_refcount_entry *entry; -- struct ssam_nf_head *nf_head; -- struct ssam_nf *nf; - int status; - - if (!ssh_rqid_is_event(rqid)) - return -EINVAL; - -- nf = &ctrl->cplt.event.notif; -- nf_head = &nf->head[ssh_rqid_to_event(rqid)]; -- - mutex_lock(&nf->lock); - - entry = ssam_nf_refcount_inc(nf, reg, id); -@@ -2332,25 +2426,11 @@ int ssam_controller_event_enable(struct ssam_controller *ctrl, - return PTR_ERR(entry); - } - -- ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -- reg.target_category, id.target_category, id.instance, -- entry->refcount); -- -- if (entry->refcount == 1) { -- status = ssam_ssh_event_enable(ctrl, reg, id, flags); -- if (status) { -- kfree(ssam_nf_refcount_dec(nf, reg, id)); -- mutex_unlock(&nf->lock); -- return status; -- } -- -- entry->flags = flags; -- -- } else if (entry->flags != flags) { -- ssam_warn(ctrl, -- "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -- flags, entry->flags, reg.target_category, -- id.target_category, id.instance); -+ status = ssam_nf_refcount_enable(ctrl, entry, flags); -+ if (status) { -+ ssam_nf_refcount_dec_free(nf, reg, id); -+ mutex_unlock(&nf->lock); -+ return status; - } - - mutex_unlock(&nf->lock); -@@ -2382,40 +2462,22 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, - struct ssam_event_id id, u8 flags) - { - u16 rqid = ssh_tc_to_rqid(id.target_category); -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; - struct ssam_nf_refcount_entry *entry; -- struct ssam_nf_head *nf_head; -- struct ssam_nf *nf; - int status = 0; - - if (!ssh_rqid_is_event(rqid)) - return -EINVAL; - -- nf = &ctrl->cplt.event.notif; -- nf_head = &nf->head[ssh_rqid_to_event(rqid)]; -- - mutex_lock(&nf->lock); - - entry = ssam_nf_refcount_dec(nf, reg, id); -- if (WARN_ON(!entry)) { -+ if (!entry) { - mutex_unlock(&nf->lock); - return -ENOENT; - } - -- ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -- reg.target_category, id.target_category, id.instance, -- entry->refcount); -- -- if (entry->flags != flags) { -- ssam_warn(ctrl, -- "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -- flags, entry->flags, reg.target_category, -- id.target_category, id.instance); -- } -- -- if (entry->refcount == 0) { -- status = ssam_ssh_event_disable(ctrl, reg, id, flags); -- kfree(entry); -- } -+ status = ssam_nf_refcount_disable_free(ctrl, entry, flags); - - mutex_unlock(&nf->lock); - return status; -diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c -index 2cad4147645c..30fb50fde450 100644 ---- a/drivers/platform/surface/surface_aggregator_cdev.c -+++ b/drivers/platform/surface/surface_aggregator_cdev.c -@@ -22,6 +22,7 @@ - - #include - #include -+#include - - #define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" - -@@ -131,22 +132,23 @@ static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_ - return 0; - } - --static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 category, int priority) -+static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, int priority) - { -+ const u16 rqid = ssh_tc_to_rqid(tc); -+ const u16 event = ssh_rqid_to_event(rqid); - struct ssam_cdev_notifier *nf; -- int index = ((int)category) - 1; - int status; - - lockdep_assert_held_read(&client->cdev->lock); - - /* Validate notifier target category. */ -- if (index < 0 || index >= SSH_NUM_EVENTS) -+ if (!ssh_rqid_is_event(rqid)) - return -EINVAL; - - mutex_lock(&client->notifier_lock); - - /* Check if the notifier has already been registered. */ -- if (client->notifier[index]) { -+ if (client->notifier[event]) { - mutex_unlock(&client->notifier_lock); - return -EEXIST; - } -@@ -167,7 +169,7 @@ static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 categ - nf->client = client; - nf->nf.base.fn = ssam_cdev_notifier; - nf->nf.base.priority = priority; -- nf->nf.event.id.target_category = category; -+ nf->nf.event.id.target_category = tc; - nf->nf.event.mask = 0; /* Do not do any matching. */ - nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER; - -@@ -176,35 +178,36 @@ static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 categ - if (status) - kfree(nf); - else -- client->notifier[index] = nf; -+ client->notifier[event] = nf; - - mutex_unlock(&client->notifier_lock); - return status; - } - --static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 category) -+static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc) - { -- int index = ((int)category) - 1; -+ const u16 rqid = ssh_tc_to_rqid(tc); -+ const u16 event = ssh_rqid_to_event(rqid); - int status; - - lockdep_assert_held_read(&client->cdev->lock); - - /* Validate notifier target category. */ -- if (index < 0 || index >= SSH_NUM_EVENTS) -+ if (!ssh_rqid_is_event(rqid)) - return -EINVAL; - - mutex_lock(&client->notifier_lock); - - /* Check if the notifier is currently registered. */ -- if (!client->notifier[index]) { -+ if (!client->notifier[event]) { - mutex_unlock(&client->notifier_lock); - return -ENOENT; - } - - /* Unregister and free notifier. */ -- status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[index]->nf); -- kfree(client->notifier[index]); -- client->notifier[index] = NULL; -+ status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[event]->nf); -+ kfree(client->notifier[event]); -+ client->notifier[event] = NULL; - - mutex_unlock(&client->notifier_lock); - return status; -@@ -482,6 +485,9 @@ static int ssam_cdev_device_open(struct inode *inode, struct file *filp) - - if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { - up_write(&cdev->client_lock); -+ mutex_destroy(&client->write_lock); -+ mutex_destroy(&client->read_lock); -+ mutex_destroy(&client->notifier_lock); - ssam_cdev_put(client->cdev); - vfree(client); - return -ENODEV; --- -2.32.0 - -From fe4a216fc5f8e021db1d78df71c4f99ad5110bfd Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 4 Jun 2021 22:56:38 +0200 -Subject: [PATCH] platform/surface: aggregator: Do not return uninitialized - value - -The status variable in ssam_nf_refcount_disable_free() is only set when -the reference count equals zero. Otherwise, it is returned -uninitialized. Fix this by always initializing status to zero. - -Reported-by: kernel test robot -Fixes: 640ee17199e4 ("platform/surface: aggregator: Allow enabling of events without notifiers") -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/controller.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index 6646f4d6e10d..634399387d76 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2228,7 +2228,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - const struct ssam_event_registry reg = entry->key.reg; - const struct ssam_event_id id = entry->key.id; - struct ssam_nf *nf = &ctrl->cplt.event.notif; -- int status; -+ int status = 0; - - lockdep_assert_held(&nf->lock); - --- -2.32.0 - -From 79c87e7add447e1021595ff6a56c34ded0734926 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 4 Jun 2021 23:00:47 +0200 -Subject: [PATCH] platform/surface: aggregator: Drop unnecessary variable - initialization - -The status variable in ssam_controller_event_disable() is always set, no -need to initialize it. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/controller.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index 634399387d76..b8c377b3f932 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2464,7 +2464,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, - u16 rqid = ssh_tc_to_rqid(id.target_category); - struct ssam_nf *nf = &ctrl->cplt.event.notif; - struct ssam_nf_refcount_entry *entry; -- int status = 0; -+ int status; - - if (!ssh_rqid_is_event(rqid)) - return -EINVAL; --- -2.32.0 - -From 220729198b2a72ada278f36112f6c064d82c7a0e Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 2 Jun 2021 20:07:47 +0200 -Subject: [PATCH] docs: driver-api: Update Surface Aggregator user-space - interface documentation - -Update the controller-device user-space interface (cdev) documentation -for the newly introduced IOCTLs and event interface. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface_aggregator/clients/cdev.rst | 127 +++++++++++++++++- - 1 file changed, 122 insertions(+), 5 deletions(-) - -diff --git a/Documentation/driver-api/surface_aggregator/clients/cdev.rst b/Documentation/driver-api/surface_aggregator/clients/cdev.rst -index 248c1372d879..0134a841a079 100644 ---- a/Documentation/driver-api/surface_aggregator/clients/cdev.rst -+++ b/Documentation/driver-api/surface_aggregator/clients/cdev.rst -@@ -1,9 +1,8 @@ - .. SPDX-License-Identifier: GPL-2.0+ - --.. |u8| replace:: :c:type:`u8 ` --.. |u16| replace:: :c:type:`u16 ` - .. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request ` - .. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags ` -+.. |ssam_cdev_event| replace:: :c:type:`struct ssam_cdev_event ` - - ============================== - User-Space EC Interface (cdev) -@@ -23,6 +22,40 @@ These IOCTLs and their respective input/output parameter structs are defined in - A small python library and scripts for accessing this interface can be found - at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam. - -+.. contents:: -+ -+ -+Receiving Events -+================ -+ -+Events can be received by reading from the device-file. The are represented by -+the |ssam_cdev_event| datatype. -+ -+Before events are available to be read, however, the desired notifiers must be -+registered via the ``SSAM_CDEV_NOTIF_REGISTER`` IOCTL. Notifiers are, in -+essence, callbacks, called when the EC sends an event. They are, in this -+interface, associated with a specific target category and device-file-instance. -+They forward any event of this category to the buffer of the corresponding -+instance, from which it can then be read. -+ -+Notifiers themselves do not enable events on the EC. Thus, it may additionally -+be necessary to enable events via the ``SSAM_CDEV_EVENT_ENABLE`` IOCTL. While -+notifiers work per-client (i.e. per-device-file-instance), events are enabled -+globally, for the EC and all of its clients (regardless of userspace or -+non-userspace). The ``SSAM_CDEV_EVENT_ENABLE`` and ``SSAM_CDEV_EVENT_DISABLE`` -+IOCTLs take care of reference counting the events, such that an event is -+enabled as long as there is a client that has requested it. -+ -+Note that enabled events are not automatically disabled once the client -+instance is closed. Therefore any client process (or group of processes) should -+balance their event enable calls with the corresponding event disable calls. It -+is, however, perfectly valid to enable and disable events on different client -+instances. For example, it is valid to set up notifiers and read events on -+client instance ``A``, enable those events on instance ``B`` (note that these -+will also be received by A since events are enabled/disabled globally), and -+after no more events are desired, disable the previously enabled events via -+instance ``C``. -+ - - Controller IOCTLs - ================= -@@ -45,9 +78,33 @@ The following IOCTLs are provided: - - ``REQUEST`` - - Perform synchronous SAM request. - -+ * - ``0xA5`` -+ - ``2`` -+ - ``W`` -+ - ``NOTIF_REGISTER`` -+ - Register event notifier. - --``REQUEST`` ------------- -+ * - ``0xA5`` -+ - ``3`` -+ - ``W`` -+ - ``NOTIF_UNREGISTER`` -+ - Unregister event notifier. -+ -+ * - ``0xA5`` -+ - ``4`` -+ - ``W`` -+ - ``EVENT_ENABLE`` -+ - Enable event source. -+ -+ * - ``0xA5`` -+ - ``5`` -+ - ``W`` -+ - ``EVENT_DISABLE`` -+ - Disable event source. -+ -+ -+``SSAM_CDEV_REQUEST`` -+--------------------- - - Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``. - -@@ -82,6 +139,66 @@ submitted, and completed (i.e. handed back to user-space) successfully from - inside the IOCTL, but the request ``status`` member may still be negative in - case the actual execution of the request failed after it has been submitted. - --A full definition of the argument struct is provided below: -+A full definition of the argument struct is provided below. -+ -+``SSAM_CDEV_NOTIF_REGISTER`` -+---------------------------- -+ -+Defined as ``_IOW(0xA5, 2, struct ssam_cdev_notifier_desc)``. -+ -+Register a notifier for the event target category specified in the given -+notifier description with the specified priority. Notifiers registration is -+required to receive events, but does not enable events themselves. After a -+notifier for a specific target category has been registered, all events of that -+category will be forwarded to the userspace client and can then be read from -+the device file instance. Note that events may have to be enabled, e.g. via the -+``SSAM_CDEV_EVENT_ENABLE`` IOCTL, before the EC will send them. -+ -+Only one notifier can be registered per target category and client instance. If -+a notifier has already been registered, this IOCTL will fail with ``-EEXIST``. -+ -+Notifiers will automatically be removed when the device file instance is -+closed. -+ -+``SSAM_CDEV_NOTIF_UNREGISTER`` -+------------------------------ -+ -+Defined as ``_IOW(0xA5, 3, struct ssam_cdev_notifier_desc)``. -+ -+Unregisters the notifier associated with the specified target category. The -+priority field will be ignored by this IOCTL. If no notifier has been -+registered for this client instance and the given category, this IOCTL will -+fail with ``-ENOENT``. -+ -+``SSAM_CDEV_EVENT_ENABLE`` -+-------------------------- -+ -+Defined as ``_IOW(0xA5, 4, struct ssam_cdev_event_desc)``. -+ -+Enable the event associated with the given event descriptor. -+ -+Note that this call will not register a notifier itself, it will only enable -+events on the controller. If you want to receive events by reading from the -+device file, you will need to register the corresponding notifier(s) on that -+instance. -+ -+Events are not automatically disabled when the device file is closed. This must -+be done manually, via a call to the ``SSAM_CDEV_EVENT_DISABLE`` IOCTL. -+ -+``SSAM_CDEV_EVENT_DISABLE`` -+--------------------------- -+ -+Defined as ``_IOW(0xA5, 5, struct ssam_cdev_event_desc)``. -+ -+Disable the event associated with the given event descriptor. -+ -+Note that this will not unregister any notifiers. Events may still be received -+and forwarded to user-space after this call. The only safe way of stopping -+events from being received is unregistering all previously registered -+notifiers. -+ -+ -+Structures and Enums -+==================== - - .. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h --- -2.32.0 - -From a12ef238262023ecb08d5fd7ac5b589c5cb41524 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 5 May 2021 18:22:04 +0200 -Subject: [PATCH] pinctrl/amd: Add device HID for new AMD GPIO controller - -Add device HID AMDI0031 to the AMD GPIO controller driver match table. -This controller can be found on Microsoft Surface Laptop 4 devices and -seems similar enough that we can just copy the existing AMDI0030 entry. - -Cc: # 5.10+ -Tested-by: Sachi King -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/pinctrl/pinctrl-amd.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/drivers/pinctrl/pinctrl-amd.c b/drivers/pinctrl/pinctrl-amd.c -index 2d4acf21117c..c5950a3b4e4c 100644 ---- a/drivers/pinctrl/pinctrl-amd.c -+++ b/drivers/pinctrl/pinctrl-amd.c -@@ -991,6 +991,7 @@ static int amd_gpio_remove(struct platform_device *pdev) - static const struct acpi_device_id amd_gpio_acpi_match[] = { - { "AMD0030", 0 }, - { "AMDI0030", 0}, -+ { "AMDI0031", 0}, - { }, - }; - MODULE_DEVICE_TABLE(acpi, amd_gpio_acpi_match); --- -2.32.0 - -From 5500228a79c6136a40035f557bd62160b2c7b4eb Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Sat, 29 May 2021 17:47:38 +1000 -Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 - override - -This patch is the work of Thomas Gleixner and is -copied from: -https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ - -This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin -setup that is missing in the laptops ACPI table. - -This patch was used for validation of the issue, and is not a proper -fix, but is probably a better temporary hack than continuing to probe -the Legacy PIC and run with the PIC in an unknown state. - -Patchset: surface-sam ---- - arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ - 1 file changed, 17 insertions(+) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 14cd3186dc77..ab3ba60cb6da 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -21,6 +21,7 @@ - #include - #include - #include -+#include - - #include - #include -@@ -1155,6 +1156,17 @@ static void __init mp_config_acpi_legacy_irqs(void) - } - } - -+static const struct dmi_system_id surface_quirk[] __initconst = { -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") -+ }, -+ }, -+ {} -+}; -+ - /* - * Parse IOAPIC related entries in MADT - * returns 0 on success, < 0 on error -@@ -1212,6 +1224,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) - acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, - acpi_gbl_FADT.sci_interrupt); - -+ if (dmi_check_system(surface_quirk)) { -+ pr_warn("Surface hack: Override irq 7\n"); -+ mp_override_legacy_irq(7, 3, 3, 7); -+ } -+ - /* Fill in identity legacy mappings where no override */ - mp_config_acpi_legacy_irqs(); - --- -2.32.0 - -From 14310a79f4542660ae09e7ee337547e1aa7a37bf Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 3 Jun 2021 14:04:26 +0200 -Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override - quirk - -The 13" version of the Surface Laptop 4 has the same problem as the 15" -version, but uses a different SKU. Add that SKU to the quirk as well. - -Patchset: surface-sam ---- - arch/x86/kernel/acpi/boot.c | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index ab3ba60cb6da..fa1dcdd119e5 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -1158,12 +1158,19 @@ static void __init mp_config_acpi_legacy_irqs(void) - - static const struct dmi_system_id surface_quirk[] __initconst = { - { -- .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") - }, - }, -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") -+ }, -+ }, - {} - }; - --- -2.32.0 - -From 418f3b0405c0134e8ce5fb53d857435b4865fa2e Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Sat, 29 May 2021 22:27:25 +1000 -Subject: [PATCH] platform/x86: amd-pmc: Add device HID for AMD PMC - -The Surface Laptop 4 appears to have used AMD0005 for the PMC instead of -the AMDI0005 which would match the ACPI ID Registry. - -AMD appears to have previously used "AMD" in a number of IDs in the past, -and AMD is not allocated to any other entity as an ID, so adding this ID -should not cause any harm. - -Signed-off-by: Sachi King -Patchset: surface-sam ---- - drivers/platform/x86/amd-pmc.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c -index b9da58ee9b1e..0b5578a8a449 100644 ---- a/drivers/platform/x86/amd-pmc.c -+++ b/drivers/platform/x86/amd-pmc.c -@@ -275,6 +275,7 @@ static int amd_pmc_remove(struct platform_device *pdev) - static const struct acpi_device_id amd_pmc_acpi_ids[] = { - {"AMDI0005", 0}, - {"AMD0004", 0}, -+ {"AMD0005", 0}, - { } - }; - MODULE_DEVICE_TABLE(acpi, amd_pmc_acpi_ids); --- -2.32.0 - diff --git a/patches/5.12/0007-surface-hotplug.patch b/patches/5.12/0007-surface-hotplug.patch deleted file mode 100644 index fcb5a5a1e..000000000 --- a/patches/5.12/0007-surface-hotplug.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 0e803e7c36a7486f1b9ebf3dee79bda3f38b6086 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 2 Jul 2021 14:27:14 +0200 -Subject: [PATCH] Revert "Revert "PCI: PM: Do not read power state in - pci_enable_device_flags()"" - -This reverts commit 133ad06e0419eea137cce7b0c453b9c9622de161. - -Patchset: surface-hotplug ---- - drivers/pci/pci.c | 16 +++------------- - 1 file changed, 3 insertions(+), 13 deletions(-) - -diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c -index 16a17215f633..e4d4e399004b 100644 ---- a/drivers/pci/pci.c -+++ b/drivers/pci/pci.c -@@ -1870,20 +1870,10 @@ static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags) - int err; - int i, bars = 0; - -- /* -- * Power state could be unknown at this point, either due to a fresh -- * boot or a device removal call. So get the current power state -- * so that things like MSI message writing will behave as expected -- * (e.g. if the device really is in D0 at enable time). -- */ -- if (dev->pm_cap) { -- u16 pmcsr; -- pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); -- dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK); -- } -- -- if (atomic_inc_return(&dev->enable_cnt) > 1) -+ if (atomic_inc_return(&dev->enable_cnt) > 1) { -+ pci_update_current_state(dev, dev->current_state); - return 0; /* already enabled */ -+ } - - bridge = pci_upstream_bridge(dev); - if (bridge) --- -2.32.0 - diff --git a/patches/5.12/0008-surface-typecover.patch b/patches/5.12/0008-surface-typecover.patch deleted file mode 100644 index 889e2bd01..000000000 --- a/patches/5.12/0008-surface-typecover.patch +++ /dev/null @@ -1,233 +0,0 @@ -From e0891ad134ca2f90f2e47e070ce83992932f410a Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 5 Nov 2020 13:09:45 +0100 -Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when - suspending - -The Type Cover for Microsoft Surface devices supports a special usb -control request to disable or enable the built-in keyboard backlight. -On Windows, this request happens when putting the device into suspend or -resuming it, without it the backlight of the Type Cover will remain -enabled for some time even though the computer is suspended, which looks -weird to the user. - -So add support for this special usb control request to hid-multitouch, -which is the driver that's handling the Type Cover. - -The reason we have to use a pm_notifier for this instead of the usual -suspend/resume methods is that those won't get called in case the usb -device is already autosuspended. - -Also, if the device is autosuspended, we have to briefly autoresume it -in order to send the request. Doing that should be fine, the usb-core -driver does something similar during suspend inside choose_wakeup(). - -To make sure we don't send that request to every device but only to -devices which support it, add a new quirk -MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk -is only enabled for the usb id of the Surface Pro 2017 Type Cover, which -is where I confirmed that it's working. - -Patchset: surface-typecover ---- - drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- - 1 file changed, 98 insertions(+), 2 deletions(-) - -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index 2e4fb76c45f3..d7a27d891fba 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -34,7 +34,10 @@ - #include - #include - #include -+#include - #include -+#include -+#include - #include - #include - #include -@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); - MODULE_LICENSE("GPL"); - - #include "hid-ids.h" -+#include "usbhid/usbhid.h" - - /* quirks to control the device */ - #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) -@@ -71,12 +75,15 @@ MODULE_LICENSE("GPL"); - #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) - #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) - #define MT_QUIRK_DISABLE_WAKEUP BIT(21) -+#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 - - #define MT_BUTTONTYPE_CLICKPAD 0 - -+#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+ - enum latency_mode { - HID_LATENCY_NORMAL = 0, - HID_LATENCY_HIGH = 1, -@@ -168,6 +175,8 @@ struct mt_device { - - struct list_head applications; - struct list_head reports; -+ -+ struct notifier_block pm_notifier; - }; - - static void mt_post_parse_default_settings(struct mt_device *td, -@@ -210,6 +219,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); - #define MT_CLS_GOOGLE 0x0111 - #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 - #define MT_CLS_SMART_TECH 0x0113 -+#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 - - #define MT_DEFAULT_MAXCONTACT 10 - #define MT_MAX_MAXCONTACT 250 -@@ -378,6 +388,16 @@ static const struct mt_class mt_classes[] = { - MT_QUIRK_CONTACT_CNT_ACCURATE | - MT_QUIRK_SEPARATE_APP_REPORT, - }, -+ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_ALWAYS_VALID | -+ MT_QUIRK_IGNORE_DUPLICATES | -+ MT_QUIRK_HOVERING | -+ MT_QUIRK_CONTACT_CNT_ACCURATE | -+ MT_QUIRK_STICKY_FINGERS | -+ MT_QUIRK_WIN8_PTP_BUTTONS, -+ .export_all_inputs = true -+ }, - { } - }; - -@@ -1690,6 +1710,69 @@ static void mt_expired_timeout(struct timer_list *t) - clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - -+static void get_type_cover_backlight_field(struct hid_device *hdev, -+ struct hid_field **field) -+{ -+ struct hid_report_enum *rep_enum; -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid -+ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { -+ *field = cur_field; -+ return; -+ } -+ } -+ } -+ } -+} -+ -+static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) -+{ -+ struct usb_device *udev = hid_to_usb_dev(hdev); -+ struct hid_field *field = NULL; -+ -+ /* Wake up the device in case it's already suspended */ -+ pm_runtime_get_sync(&udev->dev); -+ -+ get_type_cover_backlight_field(hdev, &field); -+ if (!field) { -+ hid_err(hdev, "couldn't find backlight field\n"); -+ goto out; -+ } -+ -+ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; -+ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); -+ -+out: -+ pm_runtime_put_sync(&udev->dev); -+} -+ -+static int mt_pm_notifier(struct notifier_block *notifier, -+ unsigned long pm_event, -+ void *unused) -+{ -+ struct mt_device *td = -+ container_of(notifier, struct mt_device, pm_notifier); -+ struct hid_device *hdev = td->hdev; -+ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { -+ if (pm_event == PM_SUSPEND_PREPARE) -+ update_keyboard_backlight(hdev, 0); -+ else if (pm_event == PM_POST_SUSPEND) -+ update_keyboard_backlight(hdev, 1); -+ } -+ -+ return NOTIFY_DONE; -+} -+ - static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - { - int ret, i; -@@ -1713,6 +1796,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; - hid_set_drvdata(hdev, td); - -+ td->pm_notifier.notifier_call = mt_pm_notifier; -+ register_pm_notifier(&td->pm_notifier); -+ - INIT_LIST_HEAD(&td->applications); - INIT_LIST_HEAD(&td->reports); - -@@ -1742,15 +1828,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - timer_setup(&td->release_timer, mt_expired_timeout, 0); - - ret = hid_parse(hdev); -- if (ret != 0) -+ if (ret != 0) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) - mt_fix_const_fields(hdev, HID_DG_CONTACTID); - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); -- if (ret) -+ if (ret) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); - if (ret) -@@ -1801,6 +1891,7 @@ static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); - -+ unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); - - sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); -@@ -2158,6 +2249,11 @@ static const struct hid_device_id mt_devices[] = { - MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, - USB_DEVICE_ID_XIROKU_CSR2) }, - -+ /* Microsoft Surface type cover */ -+ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, -+ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, -+ - /* Google MT devices */ - { .driver_data = MT_CLS_GOOGLE, - HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, --- -2.32.0 - diff --git a/patches/5.12/0009-surface-go-touchscreen.patch b/patches/5.12/0009-surface-go-touchscreen.patch deleted file mode 100644 index 3ddbb3184..000000000 --- a/patches/5.12/0009-surface-go-touchscreen.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 9d6d54b56cca35c8c1b8c8671397f711e4751870 Mon Sep 17 00:00:00 2001 -From: Zoltan Tamas Vajda -Date: Thu, 3 Jun 2021 10:50:55 +0200 -Subject: [PATCH] Added quirk for Surface Go touchscreen - -Patchset: surface-go-touchscreen ---- - drivers/hid/hid-ids.h | 1 + - drivers/hid/hid-input.c | 2 ++ - 2 files changed, 3 insertions(+) - -diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h -index 03978111d944..06168f485722 100644 ---- a/drivers/hid/hid-ids.h -+++ b/drivers/hid/hid-ids.h -@@ -397,6 +397,7 @@ - #define USB_DEVICE_ID_HP_X2_10_COVER 0x0755 - #define I2C_DEVICE_ID_HP_SPECTRE_X360_15 0x2817 - #define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN 0x2706 -+#define I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN 0x261A - - #define USB_VENDOR_ID_ELECOM 0x056e - #define USB_DEVICE_ID_ELECOM_BM084 0x0061 -diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c -index e982d8173c9c..f7d482619dfd 100644 ---- a/drivers/hid/hid-input.c -+++ b/drivers/hid/hid-input.c -@@ -326,6 +326,8 @@ static const struct hid_device_id hid_battery_quirks[] = { - HID_BATTERY_QUIRK_IGNORE }, - { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_SPECTRE_X360_15), - HID_BATTERY_QUIRK_IGNORE }, -+ { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN), -+ HID_BATTERY_QUIRK_IGNORE }, - {} - }; - --- -2.32.0 - diff --git a/patches/5.12/0010-cameras.patch b/patches/5.12/0010-cameras.patch deleted file mode 100644 index 6dad75369..000000000 --- a/patches/5.12/0010-cameras.patch +++ /dev/null @@ -1,4047 +0,0 @@ -From d240380936c76e34eadcdba7ee87c91791287129 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 20 May 2021 15:09:21 +0100 -Subject: [PATCH] ACPI: scan: Extend acpi_walk_dep_device_list() - -The acpi_walk_dep_device_list() is not as generalisable as its name -implies, serving only to decrement the dependency count for each -dependent device of the input. Extend the function to instead accept -a callback which can be applied to all the dependencies in acpi_dep_list. -Replace all existing calls to the function with calls to a wrapper, passing -a callback that applies the same dependency reduction. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/acpi/ec.c | 2 +- - drivers/acpi/pmic/intel_pmic_chtdc_ti.c | 2 +- - drivers/acpi/scan.c | 69 ++++++++++++++----- - drivers/gpio/gpiolib-acpi.c | 10 +-- - drivers/i2c/i2c-core-acpi.c | 8 +-- - drivers/platform/surface/aggregator/core.c | 6 +- - drivers/platform/surface/surface3_power.c | 22 +++--- - .../platform/surface/surface_acpi_notify.c | 7 +- - include/acpi/acpi_bus.h | 7 ++ - include/linux/acpi.h | 4 +- - 10 files changed, 90 insertions(+), 47 deletions(-) - -diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c -index 13565629ce0a..3f7680a007a3 100644 ---- a/drivers/acpi/ec.c -+++ b/drivers/acpi/ec.c -@@ -1627,7 +1627,7 @@ static int acpi_ec_add(struct acpi_device *device) - WARN(!ret, "Could not request EC cmd io port 0x%lx", ec->command_addr); - - /* Reprobe devices depending on the EC */ -- acpi_walk_dep_device_list(ec->handle); -+ acpi_dev_clear_dependencies(device); - - acpi_handle_debug(ec->handle, "enumerated.\n"); - return 0; -diff --git a/drivers/acpi/pmic/intel_pmic_chtdc_ti.c b/drivers/acpi/pmic/intel_pmic_chtdc_ti.c -index a5101b07611a..fef7831d0d63 100644 ---- a/drivers/acpi/pmic/intel_pmic_chtdc_ti.c -+++ b/drivers/acpi/pmic/intel_pmic_chtdc_ti.c -@@ -117,7 +117,7 @@ static int chtdc_ti_pmic_opregion_probe(struct platform_device *pdev) - return err; - - /* Re-enumerate devices depending on PMIC */ -- acpi_walk_dep_device_list(ACPI_HANDLE(pdev->dev.parent)); -+ acpi_dev_clear_dependencies(ACPI_COMPANION(pdev->dev.parent)); - return 0; - } - -diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c -index 345777bf7af9..f5440729ffe9 100644 ---- a/drivers/acpi/scan.c -+++ b/drivers/acpi/scan.c -@@ -47,12 +47,6 @@ static DEFINE_MUTEX(acpi_hp_context_lock); - */ - static u64 spcr_uart_addr; - --struct acpi_dep_data { -- struct list_head node; -- acpi_handle supplier; -- acpi_handle consumer; --}; -- - void acpi_scan_lock_acquire(void) - { - mutex_lock(&acpi_scan_lock); -@@ -2142,30 +2136,69 @@ static void acpi_bus_attach(struct acpi_device *device, bool first_pass) - device->handler->hotplug.notify_online(device); - } - --void acpi_walk_dep_device_list(acpi_handle handle) -+static int acpi_scan_clear_dep(struct acpi_dep_data *dep, void *data) - { -- struct acpi_dep_data *dep, *tmp; - struct acpi_device *adev; - -+ acpi_bus_get_device(dep->consumer, &adev); -+ -+ if (adev) { -+ adev->dep_unmet--; -+ if (!adev->dep_unmet) -+ acpi_bus_attach(adev, true); -+ } -+ -+ list_del(&dep->node); -+ kfree(dep); -+ -+ return 0; -+} -+ -+/** -+ * acpi_walk_dep_device_list - Apply a callback to every entry in acpi_dep_list -+ * @handle: The ACPI handle of the supplier device -+ * @callback: Pointer to the callback function to apply -+ * @data: Pointer to some data to pass to the callback -+ * -+ * The return value of the callback determines this function's behaviour. If 0 -+ * is returned we continue to iterate over acpi_dep_list. If a positive value -+ * is returned then the loop is broken but this function returns 0. If a -+ * negative value is returned by the callback then the loop is broken and that -+ * value is returned as the final error. -+ */ -+int acpi_walk_dep_device_list(acpi_handle handle, -+ int (*callback)(struct acpi_dep_data *, void *), -+ void *data) -+{ -+ struct acpi_dep_data *dep, *tmp; -+ int ret; -+ - mutex_lock(&acpi_dep_list_lock); - list_for_each_entry_safe(dep, tmp, &acpi_dep_list, node) { - if (dep->supplier == handle) { -- acpi_bus_get_device(dep->consumer, &adev); -- -- if (adev) { -- adev->dep_unmet--; -- if (!adev->dep_unmet) -- acpi_bus_attach(adev, true); -- } -- -- list_del(&dep->node); -- kfree(dep); -+ ret = callback(dep, data); -+ if (ret) -+ break; - } - } - mutex_unlock(&acpi_dep_list_lock); -+ -+ return ret > 0 ? 0 : ret; - } - EXPORT_SYMBOL_GPL(acpi_walk_dep_device_list); - -+/** -+ * acpi_dev_clear_dependencies - Inform consumers that the device is now active -+ * @supplier: Pointer to the supplier &struct acpi_device -+ * -+ * Clear dependencies on the given device. -+ */ -+void acpi_dev_clear_dependencies(struct acpi_device *supplier) -+{ -+ acpi_walk_dep_device_list(supplier->handle, acpi_scan_clear_dep, NULL); -+} -+EXPORT_SYMBOL_GPL(acpi_dev_clear_dependencies); -+ - /** - * acpi_bus_scan - Add ACPI device node objects in a given namespace scope. - * @handle: Root of the namespace scope to scan. -diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c -index 174839f3772f..51a2f568e371 100644 ---- a/drivers/gpio/gpiolib-acpi.c -+++ b/drivers/gpio/gpiolib-acpi.c -@@ -1233,14 +1233,14 @@ static void acpi_gpiochip_scan_gpios(struct acpi_gpio_chip *achip) - void acpi_gpiochip_add(struct gpio_chip *chip) - { - struct acpi_gpio_chip *acpi_gpio; -- acpi_handle handle; -+ struct acpi_device *adev; - acpi_status status; - - if (!chip || !chip->parent) - return; - -- handle = ACPI_HANDLE(chip->parent); -- if (!handle) -+ adev = ACPI_COMPANION(chip->parent); -+ if (!adev) - return; - - acpi_gpio = kzalloc(sizeof(*acpi_gpio), GFP_KERNEL); -@@ -1254,7 +1254,7 @@ void acpi_gpiochip_add(struct gpio_chip *chip) - INIT_LIST_HEAD(&acpi_gpio->events); - INIT_LIST_HEAD(&acpi_gpio->deferred_req_irqs_list_entry); - -- status = acpi_attach_data(handle, acpi_gpio_chip_dh, acpi_gpio); -+ status = acpi_attach_data(adev->handle, acpi_gpio_chip_dh, acpi_gpio); - if (ACPI_FAILURE(status)) { - dev_err(chip->parent, "Failed to attach ACPI GPIO chip\n"); - kfree(acpi_gpio); -@@ -1263,7 +1263,7 @@ void acpi_gpiochip_add(struct gpio_chip *chip) - - acpi_gpiochip_request_regions(acpi_gpio); - acpi_gpiochip_scan_gpios(acpi_gpio); -- acpi_walk_dep_device_list(handle); -+ acpi_dev_clear_dependencies(adev); - } - - void acpi_gpiochip_remove(struct gpio_chip *chip) -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index deceed0d76c6..13eb5ac82729 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -259,8 +259,8 @@ static acpi_status i2c_acpi_add_device(acpi_handle handle, u32 level, - */ - void i2c_acpi_register_devices(struct i2c_adapter *adap) - { -+ struct acpi_device *adev; - acpi_status status; -- acpi_handle handle; - - if (!has_acpi_companion(&adap->dev)) - return; -@@ -275,11 +275,11 @@ void i2c_acpi_register_devices(struct i2c_adapter *adap) - if (!adap->dev.parent) - return; - -- handle = ACPI_HANDLE(adap->dev.parent); -- if (!handle) -+ adev = ACPI_COMPANION(adap->dev.parent); -+ if (!adev) - return; - -- acpi_walk_dep_device_list(handle); -+ acpi_dev_clear_dependencies(adev); - } - - static const struct acpi_device_id i2c_acpi_force_400khz_device_ids[] = { -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -index 5d780e55f4a1..279d9df19c01 100644 ---- a/drivers/platform/surface/aggregator/core.c -+++ b/drivers/platform/surface/aggregator/core.c -@@ -621,8 +621,8 @@ static const struct acpi_gpio_mapping ssam_acpi_gpios[] = { - - static int ssam_serial_hub_probe(struct serdev_device *serdev) - { -+ struct acpi_device *ssh = ACPI_COMPANION(&serdev->dev); - struct ssam_controller *ctrl; -- acpi_handle *ssh = ACPI_HANDLE(&serdev->dev); - acpi_status astatus; - int status; - -@@ -652,7 +652,7 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) - if (status) - goto err_devopen; - -- astatus = ssam_serdev_setup_via_acpi(ssh, serdev); -+ astatus = ssam_serdev_setup_via_acpi(ssh->handle, serdev); - if (ACPI_FAILURE(astatus)) { - status = -ENXIO; - goto err_devinit; -@@ -706,7 +706,7 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) - * For now let's thus default power/wakeup to false. - */ - device_set_wakeup_capable(&serdev->dev, true); -- acpi_walk_dep_device_list(ssh); -+ acpi_dev_clear_dependencies(ssh); - - return 0; - -diff --git a/drivers/platform/surface/surface3_power.c b/drivers/platform/surface/surface3_power.c -index cc4f9cba6856..dea82aa1abd4 100644 ---- a/drivers/platform/surface/surface3_power.c -+++ b/drivers/platform/surface/surface3_power.c -@@ -446,12 +446,12 @@ mshw0011_space_handler(u32 function, acpi_physical_address command, - - static int mshw0011_install_space_handler(struct i2c_client *client) - { -- acpi_handle handle; -+ struct acpi_device *adev; - struct mshw0011_handler_data *data; - acpi_status status; - -- handle = ACPI_HANDLE(&client->dev); -- if (!handle) -+ adev = ACPI_COMPANION(&client->dev); -+ if (!adev) - return -ENODEV; - - data = kzalloc(sizeof(struct mshw0011_handler_data), -@@ -460,25 +460,25 @@ static int mshw0011_install_space_handler(struct i2c_client *client) - return -ENOMEM; - - data->client = client; -- status = acpi_bus_attach_private_data(handle, (void *)data); -+ status = acpi_bus_attach_private_data(adev->handle, (void *)data); - if (ACPI_FAILURE(status)) { - kfree(data); - return -ENOMEM; - } - -- status = acpi_install_address_space_handler(handle, -- ACPI_ADR_SPACE_GSBUS, -- &mshw0011_space_handler, -- NULL, -- data); -+ status = acpi_install_address_space_handler(adev->handle, -+ ACPI_ADR_SPACE_GSBUS, -+ &mshw0011_space_handler, -+ NULL, -+ data); - if (ACPI_FAILURE(status)) { - dev_err(&client->dev, "Error installing i2c space handler\n"); -- acpi_bus_detach_private_data(handle); -+ acpi_bus_detach_private_data(adev->handle); - kfree(data); - return -ENOMEM; - } - -- acpi_walk_dep_device_list(handle); -+ acpi_dev_clear_dependencies(adev); - return 0; - } - -diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c -index ef9c1f8e8336..8339988d95c1 100644 ---- a/drivers/platform/surface/surface_acpi_notify.c -+++ b/drivers/platform/surface/surface_acpi_notify.c -@@ -798,7 +798,7 @@ static int san_consumer_links_setup(struct platform_device *pdev) - - static int san_probe(struct platform_device *pdev) - { -- acpi_handle san = ACPI_HANDLE(&pdev->dev); -+ struct acpi_device *san = ACPI_COMPANION(&pdev->dev); - struct ssam_controller *ctrl; - struct san_data *data; - acpi_status astatus; -@@ -821,7 +821,8 @@ static int san_probe(struct platform_device *pdev) - - platform_set_drvdata(pdev, data); - -- astatus = acpi_install_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, -+ astatus = acpi_install_address_space_handler(san->handle, -+ ACPI_ADR_SPACE_GSBUS, - &san_opreg_handler, NULL, - &data->info); - if (ACPI_FAILURE(astatus)) -@@ -835,7 +836,7 @@ static int san_probe(struct platform_device *pdev) - if (status) - goto err_install_dev; - -- acpi_walk_dep_device_list(san); -+ acpi_dev_clear_dependencies(san); - return 0; - - err_install_dev: -diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h -index f28b097c658f..849f3540ed53 100644 ---- a/include/acpi/acpi_bus.h -+++ b/include/acpi/acpi_bus.h -@@ -279,6 +279,12 @@ struct acpi_device_power { - struct acpi_device_power_state states[ACPI_D_STATE_COUNT]; /* Power states (D0-D3Cold) */ - }; - -+struct acpi_dep_data { -+ struct list_head node; -+ acpi_handle supplier; -+ acpi_handle consumer; -+}; -+ - /* Performance Management */ - - struct acpi_device_perf_flags { -@@ -684,6 +690,7 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) - - bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); - -+void acpi_dev_clear_dependencies(struct acpi_device *supplier); - struct acpi_device * - acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); - struct acpi_device * -diff --git a/include/linux/acpi.h b/include/linux/acpi.h -index 3bdcfc4401b7..c2da6b8939c0 100644 ---- a/include/linux/acpi.h -+++ b/include/linux/acpi.h -@@ -666,7 +666,9 @@ extern bool acpi_driver_match_device(struct device *dev, - const struct device_driver *drv); - int acpi_device_uevent_modalias(struct device *, struct kobj_uevent_env *); - int acpi_device_modalias(struct device *, char *, int); --void acpi_walk_dep_device_list(acpi_handle handle); -+int acpi_walk_dep_device_list(acpi_handle handle, -+ int (*callback)(struct acpi_dep_data *, void *), -+ void *data); - - struct platform_device *acpi_create_platform_device(struct acpi_device *, - struct property_entry *); --- -2.32.0 - -From 6d7e1348614f069b0ab99b75968c47c5492d039a Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 20 May 2021 15:09:22 +0100 -Subject: [PATCH] ACPI: scan: Add function to fetch dependent of acpi device - -In some ACPI tables we encounter, devices use the _DEP method to assert -a dependence on other ACPI devices as opposed to the OpRegions that the -specification intends. We need to be able to find those devices "from" -the dependee, so add a callback and a wrapper to walk over the -acpi_dep_list and return the dependent ACPI device. - -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/acpi/scan.c | 38 ++++++++++++++++++++++++++++++++++++++ - include/acpi/acpi_bus.h | 1 + - 2 files changed, 39 insertions(+) - -diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c -index f5440729ffe9..4fcc7c95af78 100644 ---- a/drivers/acpi/scan.c -+++ b/drivers/acpi/scan.c -@@ -2136,6 +2136,21 @@ static void acpi_bus_attach(struct acpi_device *device, bool first_pass) - device->handler->hotplug.notify_online(device); - } - -+static int acpi_return_dep_dev(struct acpi_dep_data *dep, void *data) -+{ -+ struct acpi_device *adev; -+ int ret; -+ -+ ret = acpi_bus_get_device(dep->consumer, &adev); -+ if (ret) -+ /* If we don't find an adev then we want to continue parsing */ -+ return 0; -+ -+ *(struct acpi_device **)data = adev; -+ -+ return 1; -+} -+ - static int acpi_scan_clear_dep(struct acpi_dep_data *dep, void *data) - { - struct acpi_device *adev; -@@ -2199,6 +2214,29 @@ void acpi_dev_clear_dependencies(struct acpi_device *supplier) - } - EXPORT_SYMBOL_GPL(acpi_dev_clear_dependencies); - -+/** -+ * acpi_dev_get_dependent_dev - Return ACPI device dependent on @supplier -+ * @supplier: Pointer to the dependee device -+ * -+ * Returns the first &struct acpi_device which declares itself dependent on -+ * @supplier via the _DEP buffer, parsed from the acpi_dep_list. -+ * -+ * The caller is responsible for putting the reference to adev when it is no -+ * longer needed. -+ */ -+struct acpi_device *acpi_dev_get_dependent_dev(struct acpi_device *supplier) -+{ -+ struct acpi_device *adev = NULL; -+ -+ acpi_walk_dep_device_list(supplier->handle, acpi_return_dep_dev, &adev); -+ -+ if (adev) -+ get_device(&adev->dev); -+ -+ return adev; -+} -+EXPORT_SYMBOL_GPL(acpi_dev_get_dependent_dev); -+ - /** - * acpi_bus_scan - Add ACPI device node objects in a given namespace scope. - * @handle: Root of the namespace scope to scan. -diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h -index 849f3540ed53..b531750eb422 100644 ---- a/include/acpi/acpi_bus.h -+++ b/include/acpi/acpi_bus.h -@@ -691,6 +691,7 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) - bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); - - void acpi_dev_clear_dependencies(struct acpi_device *supplier); -+struct acpi_device *acpi_dev_get_dependent_dev(struct acpi_device *supplier); - struct acpi_device * - acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); - struct acpi_device * --- -2.32.0 - -From 43b6ae71e7425322c1fdf35bc1a126e045264581 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 20 May 2021 15:09:23 +0100 -Subject: [PATCH] i2c: core: Add a format macro for I2C device names - -Some places in the kernel allow users to map resources to a device -using device name (for example, in the struct gpiod_lookup_table). -Currently this involves waiting for the I2C client to have been registered -so we can use dev_name(&client->dev). We want to add a function to allow -users to refer to an I2C device by name before it has been instantiated, -so create a macro for the format that's accessible outside the I2C layer -and use it in i2c_dev_set_name(). - -Acked-by: Wolfram Sang -Suggested-by: Andy Shevchenko -Reviewed-by: Laurent Pinchart -Reviewed-by: Sakari Ailus -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/i2c/i2c-core-base.c | 4 ++-- - include/linux/i2c.h | 3 +++ - 2 files changed, 5 insertions(+), 2 deletions(-) - -diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c -index f21362355973..e2cf16f27d65 100644 ---- a/drivers/i2c/i2c-core-base.c -+++ b/drivers/i2c/i2c-core-base.c -@@ -812,12 +812,12 @@ static void i2c_dev_set_name(struct i2c_adapter *adap, - struct acpi_device *adev = ACPI_COMPANION(&client->dev); - - if (info && info->dev_name) { -- dev_set_name(&client->dev, "i2c-%s", info->dev_name); -+ dev_set_name(&client->dev, I2C_DEV_NAME_FORMAT, info->dev_name); - return; - } - - if (adev) { -- dev_set_name(&client->dev, "i2c-%s", acpi_dev_name(adev)); -+ dev_set_name(&client->dev, I2C_DEV_NAME_FORMAT, acpi_dev_name(adev)); - return; - } - -diff --git a/include/linux/i2c.h b/include/linux/i2c.h -index a670ae129f4b..a2f6ee71b5be 100644 ---- a/include/linux/i2c.h -+++ b/include/linux/i2c.h -@@ -39,6 +39,9 @@ enum i2c_slave_event; - typedef int (*i2c_slave_cb_t)(struct i2c_client *client, - enum i2c_slave_event event, u8 *val); - -+/* I2C Device Name Format - to maintain consistency outside the i2c layer */ -+#define I2C_DEV_NAME_FORMAT "i2c-%s" -+ - /* I2C Frequency Modes */ - #define I2C_MAX_STANDARD_MODE_FREQ 100000 - #define I2C_MAX_FAST_MODE_FREQ 400000 --- -2.32.0 - -From 194dd795351f3cd31c9a1518214ac61d26c6bc16 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 20 May 2021 15:09:24 +0100 -Subject: [PATCH] gpiolib: acpi: Export acpi_get_gpiod() - -We need to be able to translate GPIO resources in an ACPI device's _CRS -into GPIO descriptor array. Those are represented in _CRS as a pathname -to a GPIO device plus the pin's index number: the acpi_get_gpiod() -function is perfect for that purpose. - -As it's currently only used internally within the GPIO layer, provide and -export a wrapper function that additionally holds a reference to the GPIO -device. - -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/gpio/gpiolib-acpi.c | 28 ++++++++++++++++++++++++++++ - include/linux/gpio/consumer.h | 2 ++ - 2 files changed, 30 insertions(+) - -diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c -index 51a2f568e371..24432a1e8437 100644 ---- a/drivers/gpio/gpiolib-acpi.c -+++ b/drivers/gpio/gpiolib-acpi.c -@@ -128,6 +128,34 @@ static struct gpio_desc *acpi_get_gpiod(char *path, int pin) - return gpiochip_get_desc(chip, pin); - } - -+/** -+ * acpi_get_and_request_gpiod() - Translate ACPI GPIO pin to GPIO descriptor -+ * and hold a refcount to the GPIO device. -+ * @path: ACPI GPIO controller full path name, (e.g. "\\_SB.GPO1") -+ * @pin: ACPI GPIO pin number (0-based, controller-relative) -+ * @label: Label to pass to gpiod_request() -+ * -+ * This function is a simple pass-through to acpi_get_gpiod(), except that -+ * as it is intended for use outside of the GPIO layer (in a similar fashion to -+ * gpiod_get_index() for example) it also holds a reference to the GPIO device. -+ */ -+struct gpio_desc *acpi_get_and_request_gpiod(char *path, int pin, char *label) -+{ -+ struct gpio_desc *gpio; -+ int ret; -+ -+ gpio = acpi_get_gpiod(path, pin); -+ if (IS_ERR(gpio)) -+ return gpio; -+ -+ ret = gpiod_request(gpio, label); -+ if (ret) -+ return ERR_PTR(ret); -+ -+ return gpio; -+} -+EXPORT_SYMBOL_GPL(acpi_get_and_request_gpiod); -+ - static irqreturn_t acpi_gpio_irq_handler(int irq, void *data) - { - struct acpi_gpio_event *event = data; -diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h -index c73b25bc9213..566feb56601f 100644 ---- a/include/linux/gpio/consumer.h -+++ b/include/linux/gpio/consumer.h -@@ -692,6 +692,8 @@ int devm_acpi_dev_add_driver_gpios(struct device *dev, - const struct acpi_gpio_mapping *gpios); - void devm_acpi_dev_remove_driver_gpios(struct device *dev); - -+struct gpio_desc *acpi_get_and_request_gpiod(char *path, int pin, char *label); -+ - #else /* CONFIG_GPIOLIB && CONFIG_ACPI */ - - struct acpi_device; --- -2.32.0 - -From d221d01156abe159f00c2756327859e4b091f125 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 20 May 2021 15:09:25 +0100 -Subject: [PATCH] clkdev: Make clkdev_drop() null aware - -To simplify error handling paths, many functions are no-ops when passed -NULL pointers, for example gpiod_remove_lookup_table(). Mirror that -behaviour for clkdev_drop(). - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/clk/clkdev.c | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/drivers/clk/clkdev.c b/drivers/clk/clkdev.c -index 0f2e3fcf0f19..c082720f8ade 100644 ---- a/drivers/clk/clkdev.c -+++ b/drivers/clk/clkdev.c -@@ -286,6 +286,9 @@ EXPORT_SYMBOL(clk_add_alias); - */ - void clkdev_drop(struct clk_lookup *cl) - { -+ if (!cl) -+ return; -+ - mutex_lock(&clocks_mutex); - list_del(&cl->node); - mutex_unlock(&clocks_mutex); --- -2.32.0 - -From 1292bbbf83ab916bee76c5ce12bb6745a39dd9fe Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 20 May 2021 15:09:26 +0100 -Subject: [PATCH] gpiolib: acpi: Add acpi_gpio_get_io_resource() - -Add a function to verify that a given acpi_resource represents an IO -type GPIO resource, and return it if so. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/gpio/gpiolib-acpi.c | 23 +++++++++++++++++++++++ - include/linux/acpi.h | 7 +++++++ - 2 files changed, 30 insertions(+) - -diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c -index 24432a1e8437..f17bd347bd05 100644 ---- a/drivers/gpio/gpiolib-acpi.c -+++ b/drivers/gpio/gpiolib-acpi.c -@@ -196,6 +196,29 @@ bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, - } - EXPORT_SYMBOL_GPL(acpi_gpio_get_irq_resource); - -+/** -+ * acpi_gpio_get_io_resource - Fetch details of an ACPI resource if it is a GPIO -+ * I/O resource or return False if not. -+ * @ares: Pointer to the ACPI resource to fetch -+ * @agpio: Pointer to a &struct acpi_resource_gpio to store the output pointer -+ */ -+bool acpi_gpio_get_io_resource(struct acpi_resource *ares, -+ struct acpi_resource_gpio **agpio) -+{ -+ struct acpi_resource_gpio *gpio; -+ -+ if (ares->type != ACPI_RESOURCE_TYPE_GPIO) -+ return false; -+ -+ gpio = &ares->data.gpio; -+ if (gpio->connection_type != ACPI_RESOURCE_GPIO_TYPE_IO) -+ return false; -+ -+ *agpio = gpio; -+ return true; -+} -+EXPORT_SYMBOL_GPL(acpi_gpio_get_io_resource); -+ - static void acpi_gpiochip_request_irq(struct acpi_gpio_chip *acpi_gpio, - struct acpi_gpio_event *event) - { -diff --git a/include/linux/acpi.h b/include/linux/acpi.h -index c2da6b8939c0..07a0044397e1 100644 ---- a/include/linux/acpi.h -+++ b/include/linux/acpi.h -@@ -1088,6 +1088,8 @@ void __acpi_handle_debug(struct _ddebug *descriptor, acpi_handle handle, const c - #if defined(CONFIG_ACPI) && defined(CONFIG_GPIOLIB) - bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, - struct acpi_resource_gpio **agpio); -+bool acpi_gpio_get_io_resource(struct acpi_resource *ares, -+ struct acpi_resource_gpio **agpio); - int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, const char *name, int index); - #else - static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, -@@ -1095,6 +1097,11 @@ static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, - { - return false; - } -+static inline bool acpi_gpio_get_io_resource(struct acpi_resource *ares, -+ struct acpi_resource_gpio **agpio) -+{ -+ return false; -+} - static inline int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, - const char *name, int index) - { --- -2.32.0 - -From 910cf531bc23323813cba36f9aaaf5f894ab640c Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 20 May 2021 15:09:27 +0100 -Subject: [PATCH] platform/x86: Add intel_skl_int3472 driver - -ACPI devices with _HID INT3472 are currently matched to the tps68470 -driver, however this does not cover all situations in which that _HID -occurs. We've encountered three possibilities: - -1. On Chrome OS devices, an ACPI device with _HID INT3472 (representing -a physical TPS68470 device) that requires a GPIO and OpRegion driver -2. On devices designed for Windows, an ACPI device with _HID INT3472 -(again representing a physical TPS68470 device) which requires GPIO, -Clock and Regulator drivers. -3. On other devices designed for Windows, an ACPI device with _HID -INT3472 which does **not** represent a physical TPS68470, and is instead -used as a dummy device to group some system GPIO lines which are meant -to be consumed by the sensor that is dependent on this entry. - -This commit adds a new module, registering a platform driver to deal -with the 3rd scenario plus an i2c driver to deal with #1 and #2, by -querying the CLDB buffer found against INT3472 entries to determine -which is most appropriate. - -Suggested-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 5 + - drivers/platform/x86/Kconfig | 2 + - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/intel-int3472/Kconfig | 31 ++ - drivers/platform/x86/intel-int3472/Makefile | 5 + - .../intel_skl_int3472_clk_and_regulator.c | 195 +++++++++ - .../intel-int3472/intel_skl_int3472_common.c | 106 +++++ - .../intel-int3472/intel_skl_int3472_common.h | 113 +++++ - .../intel_skl_int3472_discrete.c | 409 ++++++++++++++++++ - .../intel_skl_int3472_tps68470.c | 109 +++++ - 10 files changed, 976 insertions(+) - create mode 100644 drivers/platform/x86/intel-int3472/Kconfig - create mode 100644 drivers/platform/x86/intel-int3472/Makefile - create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c - create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c - create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h - create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c - create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 710617e26f3e..2a421d2e4b07 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -9191,6 +9191,11 @@ S: Maintained - F: arch/x86/include/asm/intel_scu_ipc.h - F: drivers/platform/x86/intel_scu_* - -+INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER -+M: Daniel Scally -+S: Maintained -+F: drivers/platform/x86/intel-int3472/intel_skl_int3472_* -+ - INTEL SPEED SELECT TECHNOLOGY - M: Srinivas Pandruvada - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 205a096e9cee..254056a353b2 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -674,6 +674,8 @@ config INTEL_CHT_INT33FE - device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m - for Type-C device. - -+source "drivers/platform/x86/intel-int3472/Kconfig" -+ - config INTEL_HID_EVENT - tristate "INTEL HID Event" - depends on ACPI -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 60d554073749..4cbcff47a571 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -72,6 +72,7 @@ obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o - obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o - obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o - obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o -+obj-$(CONFIG_INTEL_SKL_INT3472) += intel-int3472/ - obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o - - # MSI -diff --git a/drivers/platform/x86/intel-int3472/Kconfig b/drivers/platform/x86/intel-int3472/Kconfig -new file mode 100644 -index 000000000000..f0fd0df65e4d ---- /dev/null -+++ b/drivers/platform/x86/intel-int3472/Kconfig -@@ -0,0 +1,31 @@ -+config INTEL_SKL_INT3472 -+ tristate "Intel SkyLake ACPI INT3472 Driver" -+ depends on ACPI -+ depends on COMMON_CLK && CLKDEV_LOOKUP -+ depends on I2C -+ depends on GPIOLIB -+ depends on REGULATOR -+ select MFD_CORE -+ select REGMAP_I2C -+ help -+ This driver adds support for the INT3472 ACPI devices found on some -+ Intel SkyLake devices. -+ -+ The INT3472 is an Intel camera power controller, a logical device -+ found on some Skylake-based systems that can map to different -+ hardware devices depending on the platform. On machines -+ designed for Chrome OS, it maps to a TPS68470 camera PMIC. On -+ machines designed for Windows, it maps to either a TP68470 -+ camera PMIC, a uP6641Q sensor PMIC, or a set of discrete GPIOs -+ and power gates. -+ -+ If your device was designed for Chrome OS, this driver will provide -+ an ACPI OpRegion, which must be available before any of the devices -+ using it are probed. For this reason, you should select Y if your -+ device was designed for ChromeOS. For the same reason the -+ I2C_DESIGNWARE_PLATFORM option must be set to Y too. -+ -+ Say Y or M here if you have a SkyLake device designed for use -+ with Windows or ChromeOS. Say N here if you are not sure. -+ -+ The module will be named "intel-skl-int3472" -diff --git a/drivers/platform/x86/intel-int3472/Makefile b/drivers/platform/x86/intel-int3472/Makefile -new file mode 100644 -index 000000000000..48bd97f0a04e ---- /dev/null -+++ b/drivers/platform/x86/intel-int3472/Makefile -@@ -0,0 +1,5 @@ -+obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o -+intel_skl_int3472-objs := intel_skl_int3472_common.o \ -+ intel_skl_int3472_discrete.o \ -+ intel_skl_int3472_tps68470.o \ -+ intel_skl_int3472_clk_and_regulator.o -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c -new file mode 100644 -index 000000000000..82b95f753549 ---- /dev/null -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c -@@ -0,0 +1,195 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "intel_skl_int3472_common.h" -+ -+/* -+ * The regulators have to have .ops to be valid, but the only ops we actually -+ * support are .enable and .disable which are handled via .ena_gpiod. Pass an -+ * empty struct to clear the check without lying about capabilities. -+ */ -+static const struct regulator_ops int3472_gpio_regulator_ops; -+ -+static int skl_int3472_clk_prepare(struct clk_hw *hw) -+{ -+ struct int3472_gpio_clock *clk = to_int3472_clk(hw); -+ -+ gpiod_set_value(clk->ena_gpio, 1); -+ gpiod_set_value(clk->led_gpio, 1); -+ -+ return 0; -+} -+ -+static void skl_int3472_clk_unprepare(struct clk_hw *hw) -+{ -+ struct int3472_gpio_clock *clk = to_int3472_clk(hw); -+ -+ gpiod_set_value(clk->ena_gpio, 0); -+ gpiod_set_value(clk->led_gpio, 0); -+} -+ -+static int skl_int3472_clk_enable(struct clk_hw *hw) -+{ -+ /* -+ * We're just turning a GPIO on to enable the clock, which operation -+ * has the potential to sleep. Given .enable() cannot sleep, but -+ * .prepare() can, we toggle the GPIO in .prepare() instead. Thus, -+ * nothing to do here. -+ */ -+ return 0; -+} -+ -+static void skl_int3472_clk_disable(struct clk_hw *hw) -+{ -+ /* Likewise, nothing to do here... */ -+} -+ -+static unsigned int skl_int3472_get_clk_frequency(struct int3472_discrete_device *int3472) -+{ -+ union acpi_object *obj; -+ unsigned int freq; -+ -+ obj = skl_int3472_get_acpi_buffer(int3472->sensor, "SSDB"); -+ if (IS_ERR(obj)) -+ return 0; /* report rate as 0 on error */ -+ -+ if (obj->buffer.length < CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET + sizeof(u32)) { -+ dev_err(int3472->dev, "The buffer is too small\n"); -+ goto out_free_buff; -+ } -+ -+ freq = *(u32 *)(obj->buffer.pointer + CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET); -+ -+out_free_buff: -+ kfree(obj); -+ return freq; -+} -+ -+static unsigned long skl_int3472_clk_recalc_rate(struct clk_hw *hw, -+ unsigned long parent_rate) -+{ -+ struct int3472_gpio_clock *clk = to_int3472_clk(hw); -+ -+ return clk->frequency; -+} -+ -+static const struct clk_ops skl_int3472_clock_ops = { -+ .prepare = skl_int3472_clk_prepare, -+ .unprepare = skl_int3472_clk_unprepare, -+ .enable = skl_int3472_clk_enable, -+ .disable = skl_int3472_clk_disable, -+ .recalc_rate = skl_int3472_clk_recalc_rate, -+}; -+ -+int skl_int3472_register_clock(struct int3472_discrete_device *int3472) -+{ -+ struct clk_init_data init = { -+ .ops = &skl_int3472_clock_ops, -+ .flags = CLK_GET_RATE_NOCACHE, -+ }; -+ int ret = 0; -+ -+ init.name = kasprintf(GFP_KERNEL, "%s-clk", -+ acpi_dev_name(int3472->adev)); -+ if (!init.name) -+ return -ENOMEM; -+ -+ int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472); -+ -+ int3472->clock.clk_hw.init = &init; -+ int3472->clock.clk = clk_register(&int3472->adev->dev, -+ &int3472->clock.clk_hw); -+ if (IS_ERR(int3472->clock.clk)) { -+ ret = PTR_ERR(int3472->clock.clk); -+ goto out_free_init_name; -+ } -+ -+ int3472->clock.cl = clkdev_create(int3472->clock.clk, NULL, -+ int3472->sensor_name); -+ if (!int3472->clock.cl) { -+ ret = -ENOMEM; -+ goto err_unregister_clk; -+ } -+ -+ goto out_free_init_name; -+ -+err_unregister_clk: -+ clk_unregister(int3472->clock.clk); -+out_free_init_name: -+ kfree(init.name); -+ -+ return ret; -+} -+ -+int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, -+ struct acpi_resource *ares) -+{ -+ char *path = ares->data.gpio.resource_source.string_ptr; -+ const struct int3472_sensor_config *sensor_config; -+ struct regulator_consumer_supply supply_map; -+ struct regulator_init_data init_data = { }; -+ struct regulator_config cfg = { }; -+ int ret; -+ -+ sensor_config = int3472->sensor_config; -+ if (IS_ERR(sensor_config)) { -+ dev_err(int3472->dev, "No sensor module config\n"); -+ return PTR_ERR(sensor_config); -+ } -+ -+ if (!sensor_config->supply_map.supply) { -+ dev_err(int3472->dev, "No supply name defined\n"); -+ return -ENODEV; -+ } -+ -+ init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; -+ init_data.num_consumer_supplies = 1; -+ supply_map = sensor_config->supply_map; -+ supply_map.dev_name = int3472->sensor_name; -+ init_data.consumer_supplies = &supply_map; -+ -+ snprintf(int3472->regulator.regulator_name, -+ sizeof(int3472->regulator.regulator_name), "%s-regulator", -+ acpi_dev_name(int3472->adev)); -+ snprintf(int3472->regulator.supply_name, -+ GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0"); -+ -+ int3472->regulator.rdesc = INT3472_REGULATOR( -+ int3472->regulator.regulator_name, -+ int3472->regulator.supply_name, -+ &int3472_gpio_regulator_ops); -+ -+ int3472->regulator.gpio = acpi_get_and_request_gpiod(path, -+ ares->data.gpio.pin_table[0], -+ "int3472,regulator"); -+ if (IS_ERR(int3472->regulator.gpio)) { -+ dev_err(int3472->dev, "Failed to get regulator GPIO line\n"); -+ return PTR_ERR(int3472->regulator.gpio); -+ } -+ -+ cfg.dev = &int3472->adev->dev; -+ cfg.init_data = &init_data; -+ cfg.ena_gpiod = int3472->regulator.gpio; -+ -+ int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc, -+ &cfg); -+ if (IS_ERR(int3472->regulator.rdev)) { -+ ret = PTR_ERR(int3472->regulator.rdev); -+ goto err_free_gpio; -+ } -+ -+ return 0; -+ -+err_free_gpio: -+ gpiod_put(int3472->regulator.gpio); -+ -+ return ret; -+} -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c -new file mode 100644 -index 000000000000..8712ef9d8f59 ---- /dev/null -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c -@@ -0,0 +1,106 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+#include -+ -+#include "intel_skl_int3472_common.h" -+ -+union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, -+ char *id) -+{ -+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; -+ acpi_handle handle = adev->handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(handle, id, NULL, &buffer); -+ if (ACPI_FAILURE(status)) -+ return ERR_PTR(-ENODEV); -+ -+ obj = buffer.pointer; -+ if (!obj) -+ return ERR_PTR(-ENODEV); -+ -+ if (obj->type != ACPI_TYPE_BUFFER) { -+ acpi_handle_err(handle, "%s object is not an ACPI buffer\n", id); -+ kfree(obj); -+ return ERR_PTR(-EINVAL); -+ } -+ -+ return obj; -+} -+ -+int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) -+{ -+ union acpi_object *obj; -+ int ret = 0; -+ -+ obj = skl_int3472_get_acpi_buffer(adev, "CLDB"); -+ if (IS_ERR(obj)) -+ return PTR_ERR(obj); -+ -+ if (obj->buffer.length > sizeof(*cldb)) { -+ acpi_handle_err(adev->handle, "The CLDB buffer is too large\n"); -+ ret = -EINVAL; -+ goto out_free_obj; -+ } -+ -+ memcpy(cldb, obj->buffer.pointer, obj->buffer.length); -+ -+out_free_obj: -+ kfree(obj); -+ return ret; -+} -+ -+static const struct acpi_device_id int3472_device_id[] = { -+ { "INT3472", 0 }, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, int3472_device_id); -+ -+static struct platform_driver int3472_discrete = { -+ .driver = { -+ .name = "int3472-discrete", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe = skl_int3472_discrete_probe, -+ .remove = skl_int3472_discrete_remove, -+}; -+ -+static struct i2c_driver int3472_tps68470 = { -+ .driver = { -+ .name = "int3472-tps68470", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe_new = skl_int3472_tps68470_probe, -+}; -+ -+static int skl_int3472_init(void) -+{ -+ int ret; -+ -+ ret = platform_driver_register(&int3472_discrete); -+ if (ret) -+ return ret; -+ -+ ret = i2c_register_driver(THIS_MODULE, &int3472_tps68470); -+ if (ret) -+ platform_driver_unregister(&int3472_discrete); -+ -+ return ret; -+} -+module_init(skl_int3472_init); -+ -+static void skl_int3472_exit(void) -+{ -+ platform_driver_unregister(&int3472_discrete); -+ i2c_del_driver(&int3472_tps68470); -+} -+module_exit(skl_int3472_exit); -+ -+MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver"); -+MODULE_AUTHOR("Daniel Scally "); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h -new file mode 100644 -index 000000000000..4c923d15e3b1 ---- /dev/null -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h -@@ -0,0 +1,113 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* Author: Dan Scally */ -+ -+#ifndef _INTEL_SKL_INT3472_H -+#define _INTEL_SKL_INT3472_H -+ -+#include -+#include -+#include -+#include -+#include -+ -+/* PMIC GPIO Types */ -+#define INT3472_GPIO_TYPE_RESET 0x00 -+#define INT3472_GPIO_TYPE_POWERDOWN 0x01 -+#define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b -+#define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c -+#define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d -+ -+#define INT3472_PDEV_MAX_NAME_LEN 23 -+#define INT3472_MAX_SENSOR_GPIOS 3 -+ -+#define GPIO_REGULATOR_NAME_LENGTH 21 -+#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9 -+ -+#define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86 -+ -+#define INT3472_REGULATOR(_name, _supply, _ops) \ -+ (const struct regulator_desc) { \ -+ .name = _name, \ -+ .supply_name = _supply, \ -+ .type = REGULATOR_VOLTAGE, \ -+ .ops = _ops, \ -+ .owner = THIS_MODULE, \ -+ } -+ -+#define to_int3472_clk(hw) \ -+ container_of(hw, struct int3472_gpio_clock, clk_hw) -+ -+#define to_int3472_device(clk) \ -+ container_of(clk, struct int3472_discrete_device, clock) -+ -+struct acpi_device; -+struct i2c_client; -+struct platform_device; -+ -+struct int3472_cldb { -+ u8 version; -+ /* -+ * control logic type -+ * 0: UNKNOWN -+ * 1: DISCRETE(CRD-D) -+ * 2: PMIC TPS68470 -+ * 3: PMIC uP6641 -+ */ -+ u8 control_logic_type; -+ u8 control_logic_id; -+ u8 sensor_card_sku; -+ u8 reserved[28]; -+}; -+ -+struct int3472_gpio_function_remap { -+ const char *documented; -+ const char *actual; -+}; -+ -+struct int3472_sensor_config { -+ const char *sensor_module_name; -+ struct regulator_consumer_supply supply_map; -+ const struct int3472_gpio_function_remap *function_maps; -+}; -+ -+struct int3472_discrete_device { -+ struct acpi_device *adev; -+ struct device *dev; -+ struct acpi_device *sensor; -+ const char *sensor_name; -+ -+ const struct int3472_sensor_config *sensor_config; -+ -+ struct int3472_gpio_regulator { -+ char regulator_name[GPIO_REGULATOR_NAME_LENGTH]; -+ char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH]; -+ struct gpio_desc *gpio; -+ struct regulator_dev *rdev; -+ struct regulator_desc rdesc; -+ } regulator; -+ -+ struct int3472_gpio_clock { -+ struct clk *clk; -+ struct clk_hw clk_hw; -+ struct clk_lookup *cl; -+ struct gpio_desc *ena_gpio; -+ struct gpio_desc *led_gpio; -+ u32 frequency; -+ } clock; -+ -+ unsigned int n_gpios; /* how many GPIOs have we seen */ -+ unsigned int n_sensor_gpios; /* how many have we mapped to sensor */ -+ struct gpiod_lookup_table gpios; -+}; -+ -+int skl_int3472_discrete_probe(struct platform_device *pdev); -+int skl_int3472_discrete_remove(struct platform_device *pdev); -+int skl_int3472_tps68470_probe(struct i2c_client *client); -+union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, -+ char *id); -+int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); -+int skl_int3472_register_clock(struct int3472_discrete_device *int3472); -+int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, -+ struct acpi_resource *ares); -+ -+#endif -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -new file mode 100644 -index 000000000000..cb91ab9e65cc ---- /dev/null -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -@@ -0,0 +1,409 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "intel_skl_int3472_common.h" -+ -+/* -+ * 79234640-9e10-4fea-a5c1-b5aa8b19756f -+ * This _DSM GUID returns information about the GPIO lines mapped to a -+ * discrete INT3472 device. Function number 1 returns a count of the GPIO -+ * lines that are mapped. Subsequent functions return 32 bit ints encoding -+ * information about the GPIO line, including its purpose. -+ */ -+static const guid_t int3472_gpio_guid = -+ GUID_INIT(0x79234640, 0x9e10, 0x4fea, -+ 0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f); -+ -+/* -+ * 822ace8f-2814-4174-a56b-5f029fe079ee -+ * This _DSM GUID returns a string from the sensor device, which acts as a -+ * module identifier. -+ */ -+static const guid_t cio2_sensor_module_guid = -+ GUID_INIT(0x822ace8f, 0x2814, 0x4174, -+ 0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee); -+ -+/* -+ * Here follows platform specific mapping information that we can pass to -+ * the functions mapping resources to the sensors. Where the sensors have -+ * a power enable pin defined in DSDT we need to provide a supply name so -+ * the sensor drivers can find the regulator. The device name will be derived -+ * from the sensor's ACPI device within the code. Optionally, we can provide a -+ * NULL terminated array of function name mappings to deal with any platform -+ * specific deviations from the documented behaviour of GPIOs. -+ * -+ * Map a GPIO function name to NULL to prevent the driver from mapping that -+ * GPIO at all. -+ */ -+ -+static const struct int3472_gpio_function_remap ov2680_gpio_function_remaps[] = { -+ { "reset", NULL }, -+ { "powerdown", "reset" }, -+ { } -+}; -+ -+static const struct int3472_sensor_config int3472_sensor_configs[] = { -+ /* Lenovo Miix 510-12ISK - OV2680, Front */ -+ { "GNDF140809R", { 0 }, ov2680_gpio_function_remaps }, -+ /* Lenovo Miix 510-12ISK - OV5648, Rear */ -+ { "GEFF150023R", REGULATOR_SUPPLY("avdd", NULL), NULL }, -+ /* Surface Go 1&2 - OV5693, Front */ -+ { "YHCU", REGULATOR_SUPPLY("avdd", NULL), NULL }, -+}; -+ -+static const struct int3472_sensor_config * -+skl_int3472_get_sensor_module_config(struct int3472_discrete_device *int3472) -+{ -+ const struct int3472_sensor_config *ret; -+ union acpi_object *obj; -+ unsigned int i; -+ -+ obj = acpi_evaluate_dsm_typed(int3472->sensor->handle, -+ &cio2_sensor_module_guid, 0x00, -+ 0x01, NULL, ACPI_TYPE_STRING); -+ -+ if (!obj) { -+ dev_err(int3472->dev, -+ "Failed to get sensor module string from _DSM\n"); -+ return ERR_PTR(-ENODEV); -+ } -+ -+ if (obj->string.type != ACPI_TYPE_STRING) { -+ dev_err(int3472->dev, -+ "Sensor _DSM returned a non-string value\n"); -+ ret = ERR_PTR(-EINVAL); -+ goto out_free_obj; -+ } -+ -+ ret = ERR_PTR(-EINVAL); -+ for (i = 0; i < ARRAY_SIZE(int3472_sensor_configs); i++) { -+ if (!strcmp(int3472_sensor_configs[i].sensor_module_name, -+ obj->string.pointer)) { -+ ret = &int3472_sensor_configs[i]; -+ break; -+ } -+ } -+ -+out_free_obj: -+ ACPI_FREE(obj); -+ return ret; -+} -+ -+static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int3472, -+ struct acpi_resource *ares, -+ const char *func, u32 polarity) -+{ -+ char *path = ares->data.gpio.resource_source.string_ptr; -+ const struct int3472_sensor_config *sensor_config; -+ struct gpiod_lookup *table_entry; -+ struct acpi_device *adev; -+ acpi_handle handle; -+ acpi_status status; -+ int ret; -+ -+ if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { -+ dev_warn(int3472->dev, "Too many GPIOs mapped\n"); -+ return -EINVAL; -+ } -+ -+ sensor_config = int3472->sensor_config; -+ if (!IS_ERR(sensor_config) && sensor_config->function_maps) { -+ const struct int3472_gpio_function_remap *remap; -+ -+ for (remap = sensor_config->function_maps; remap->documented; remap++) { -+ if (!strcmp(func, remap->documented)) { -+ func = remap->actual; -+ break; -+ } -+ } -+ } -+ -+ /* Functions mapped to NULL should not be mapped to the sensor */ -+ if (!func) -+ return 0; -+ -+ status = acpi_get_handle(NULL, path, &handle); -+ if (ACPI_FAILURE(status)) -+ return -EINVAL; -+ -+ ret = acpi_bus_get_device(handle, &adev); -+ if (ret) -+ return -ENODEV; -+ -+ table_entry = &int3472->gpios.table[int3472->n_sensor_gpios]; -+ table_entry->key = acpi_dev_name(adev); -+ table_entry->chip_hwnum = ares->data.gpio.pin_table[0]; -+ table_entry->con_id = func; -+ table_entry->idx = 0; -+ table_entry->flags = polarity; -+ -+ int3472->n_sensor_gpios++; -+ -+ return 0; -+} -+ -+static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472, -+ struct acpi_resource *ares, u8 type) -+{ -+ char *path = ares->data.gpio.resource_source.string_ptr; -+ struct gpio_desc *gpio; -+ -+ switch (type) { -+ case INT3472_GPIO_TYPE_CLK_ENABLE: -+ gpio = acpi_get_and_request_gpiod(path, ares->data.gpio.pin_table[0], -+ "int3472,clk-enable"); -+ if (IS_ERR(gpio)) -+ return (PTR_ERR(gpio)); -+ -+ int3472->clock.ena_gpio = gpio; -+ break; -+ case INT3472_GPIO_TYPE_PRIVACY_LED: -+ gpio = acpi_get_and_request_gpiod(path, ares->data.gpio.pin_table[0], -+ "int3472,privacy-led"); -+ if (IS_ERR(gpio)) -+ return (PTR_ERR(gpio)); -+ -+ int3472->clock.led_gpio = gpio; -+ break; -+ default: -+ dev_err(int3472->dev, "Invalid GPIO type 0x%02x for clock\n", -+ type); -+ break; -+ } -+ -+ return 0; -+} -+ -+/** -+ * skl_int3472_handle_gpio_resources: Map PMIC resources to consuming sensor -+ * @ares: A pointer to a &struct acpi_resource -+ * @data: A pointer to a &struct int3472_discrete_device -+ * -+ * This function handles GPIO resources that are against an INT3472 -+ * ACPI device, by checking the value of the corresponding _DSM entry. -+ * This will return a 32bit int, where the lowest byte represents the -+ * function of the GPIO pin: -+ * -+ * 0x00 Reset -+ * 0x01 Power down -+ * 0x0b Power enable -+ * 0x0c Clock enable -+ * 0x0d Privacy LED -+ * -+ * There are some known platform specific quirks where that does not quite -+ * hold up; for example where a pin with type 0x01 (Power down) is mapped to -+ * a sensor pin that performs a reset function or entries in _CRS and _DSM that -+ * do not actually correspond to a physical connection. These will be handled -+ * by the mapping sub-functions. -+ * -+ * GPIOs will either be mapped directly to the sensor device or else used -+ * to create clocks and regulators via the usual frameworks. -+ * -+ * Return: -+ * * 0 - When all resources found are handled properly. -+ * * -EINVAL - If the resource is not a GPIO IO resource -+ * * -ENODEV - If the resource has no corresponding _DSM entry -+ * * -Other - Errors propagated from one of the sub-functions. -+ */ -+static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, -+ void *data) -+{ -+ struct int3472_discrete_device *int3472 = data; -+ struct acpi_resource_gpio *agpio; -+ union acpi_object *obj; -+ const char *err_msg; -+ int ret; -+ u8 type; -+ -+ if (!acpi_gpio_get_io_resource(ares, &agpio)) -+ return 1; /* Deliberately positive so parsing continues */ -+ -+ /* -+ * n_gpios + 2 because the index of this _DSM function is 1-based and -+ * the first function is just a count. -+ */ -+ obj = acpi_evaluate_dsm_typed(int3472->adev->handle, -+ &int3472_gpio_guid, 0x00, -+ int3472->n_gpios + 2, -+ NULL, ACPI_TYPE_INTEGER); -+ -+ if (!obj) { -+ dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n", -+ ares->data.gpio.pin_table[0]); -+ return 1; -+ } -+ -+ type = obj->integer.value & 0xff; -+ -+ switch (type) { -+ case INT3472_GPIO_TYPE_RESET: -+ ret = skl_int3472_map_gpio_to_sensor(int3472, ares, "reset", -+ GPIO_ACTIVE_LOW); -+ if (ret) -+ err_msg = "Failed to map reset pin to sensor\n"; -+ -+ break; -+ case INT3472_GPIO_TYPE_POWERDOWN: -+ ret = skl_int3472_map_gpio_to_sensor(int3472, ares, -+ "powerdown", -+ GPIO_ACTIVE_LOW); -+ if (ret) -+ err_msg = "Failed to map powerdown pin to sensor\n"; -+ -+ break; -+ case INT3472_GPIO_TYPE_CLK_ENABLE: -+ case INT3472_GPIO_TYPE_PRIVACY_LED: -+ ret = skl_int3472_map_gpio_to_clk(int3472, ares, type); -+ if (ret) -+ err_msg = "Failed to map GPIO to clock\n"; -+ -+ break; -+ case INT3472_GPIO_TYPE_POWER_ENABLE: -+ ret = skl_int3472_register_regulator(int3472, ares); -+ if (ret) -+ err_msg = "Failed to map regulator to sensor\n"; -+ -+ break; -+ default: -+ dev_warn(int3472->dev, -+ "GPIO type 0x%02x unknown; the sensor may not work\n", -+ type); -+ ret = 1; -+ break; -+ } -+ -+ int3472->n_gpios++; -+ ACPI_FREE(obj); -+ -+ if (ret) -+ return dev_err_probe(int3472->dev, ret, err_msg); -+ -+ return 0; -+} -+ -+static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) -+{ -+ LIST_HEAD(resource_list); -+ int ret; -+ -+ /* -+ * No error check, because not having a sensor config is not necessarily -+ * a failure mode. -+ */ -+ int3472->sensor_config = skl_int3472_get_sensor_module_config(int3472); -+ -+ ret = acpi_dev_get_resources(int3472->adev, &resource_list, -+ skl_int3472_handle_gpio_resources, -+ int3472); -+ if (ret) -+ goto out_free_res_list; -+ -+ if (int3472->clock.ena_gpio) { -+ ret = skl_int3472_register_clock(int3472); -+ if (ret) -+ goto out_free_res_list; -+ } else { -+ if (int3472->clock.led_gpio) -+ dev_warn(int3472->dev, -+ "No clk GPIO. The privacy LED won't work\n"); -+ } -+ -+ int3472->gpios.dev_id = int3472->sensor_name; -+ gpiod_add_lookup_table(&int3472->gpios); -+ -+out_free_res_list: -+ acpi_dev_free_resource_list(&resource_list); -+ -+ return ret; -+} -+ -+int skl_int3472_discrete_probe(struct platform_device *pdev) -+{ -+ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); -+ struct int3472_discrete_device *int3472; -+ struct int3472_cldb cldb; -+ int ret; -+ -+ ret = skl_int3472_fill_cldb(adev, &cldb); -+ if (ret) { -+ dev_err(&pdev->dev, "Couldn't fill CLDB structure\n"); -+ return ret; -+ } -+ -+ if (cldb.control_logic_type != 1) { -+ dev_err(&pdev->dev, "Unsupported control logic type %u\n", -+ cldb.control_logic_type); -+ return -EINVAL; -+ } -+ -+ /* Max num GPIOs we've seen plus a terminator */ -+ int3472 = devm_kzalloc(&pdev->dev, struct_size(int3472, gpios.table, -+ INT3472_MAX_SENSOR_GPIOS + 1), GFP_KERNEL); -+ if (!int3472) -+ return -ENOMEM; -+ -+ int3472->adev = adev; -+ int3472->dev = &pdev->dev; -+ platform_set_drvdata(pdev, int3472); -+ -+ int3472->sensor = acpi_dev_get_dependent_dev(adev); -+ if (!int3472->sensor) { -+ dev_err(&pdev->dev, "INT3472 seems to have no dependents.\n"); -+ return -ENODEV; -+ } -+ -+ int3472->sensor_name = devm_kasprintf(int3472->dev, GFP_KERNEL, -+ I2C_DEV_NAME_FORMAT, -+ acpi_dev_name(int3472->sensor)); -+ if (!int3472->sensor_name) { -+ ret = -ENOMEM; -+ goto err_put_sensor; -+ } -+ -+ /* -+ * Initialising this list means we can call gpiod_remove_lookup_table() -+ * in failure paths without issue. -+ */ -+ INIT_LIST_HEAD(&int3472->gpios.list); -+ -+ ret = skl_int3472_parse_crs(int3472); -+ if (ret) { -+ skl_int3472_discrete_remove(pdev); -+ return ret; -+ } -+ -+ return 0; -+ -+err_put_sensor: -+ acpi_dev_put(int3472->sensor); -+ -+ return ret; -+} -+ -+int skl_int3472_discrete_remove(struct platform_device *pdev) -+{ -+ struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); -+ -+ gpiod_remove_lookup_table(&int3472->gpios); -+ regulator_unregister(int3472->regulator.rdev); -+ clk_unregister(int3472->clock.clk); -+ clkdev_drop(int3472->clock.cl); -+ gpiod_put(int3472->regulator.gpio); -+ gpiod_put(int3472->clock.ena_gpio); -+ gpiod_put(int3472->clock.led_gpio); -+ -+ return 0; -+} -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c -new file mode 100644 -index 000000000000..843eaa27e9da ---- /dev/null -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c -@@ -0,0 +1,109 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "intel_skl_int3472_common.h" -+ -+static const struct mfd_cell tps68470_cros[] = { -+ { .name = "tps68470-gpio" }, -+ { .name = "tps68470_pmic_opregion" }, -+}; -+ -+static const struct mfd_cell tps68470_win[] = { -+ { .name = "tps68470-gpio" }, -+ { .name = "tps68470-clk" }, -+ { .name = "tps68470-regulator" }, -+}; -+ -+static const struct regmap_config tps68470_regmap_config = { -+ .reg_bits = 8, -+ .val_bits = 8, -+ .max_register = TPS68470_REG_MAX, -+}; -+ -+static int tps68470_chip_init(struct device *dev, struct regmap *regmap) -+{ -+ unsigned int version; -+ int ret; -+ -+ /* Force software reset */ -+ ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); -+ if (ret) -+ return ret; -+ -+ ret = regmap_read(regmap, TPS68470_REG_REVID, &version); -+ if (ret) { -+ dev_err(dev, "Failed to read revision register: %d\n", ret); -+ return ret; -+ } -+ -+ dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); -+ -+ return 0; -+} -+ -+int skl_int3472_tps68470_probe(struct i2c_client *client) -+{ -+ struct acpi_device *adev = ACPI_COMPANION(&client->dev); -+ struct int3472_cldb cldb = { 0 }; -+ struct regmap *regmap; -+ int ret; -+ -+ regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); -+ if (IS_ERR(regmap)) { -+ dev_err(&client->dev, "Failed to create regmap: %ld\n", -+ PTR_ERR(regmap)); -+ return PTR_ERR(regmap); -+ } -+ -+ i2c_set_clientdata(client, regmap); -+ -+ ret = tps68470_chip_init(&client->dev, regmap); -+ if (ret < 0) { -+ dev_err(&client->dev, "TPS68470 init error %d\n", ret); -+ return ret; -+ } -+ -+ /* -+ * Check CLDB buffer against the PMIC's adev. If present, then we check -+ * the value of control_logic_type field and follow one of the -+ * following scenarios: -+ * -+ * 1. No CLDB - likely ACPI tables designed for ChromeOS. We -+ * create platform devices for the GPIOs and OpRegion drivers. -+ * -+ * 2. CLDB, with control_logic_type = 2 - probably ACPI tables -+ * made for Windows 2-in-1 platforms. Register pdevs for GPIO, -+ * Clock and Regulator drivers to bind to. -+ * -+ * 3. Any other value in control_logic_type, we should never have -+ * gotten to this point; fail probe and return. -+ */ -+ ret = skl_int3472_fill_cldb(adev, &cldb); -+ if (!ret && cldb.control_logic_type != 2) { -+ dev_err(&client->dev, "Unsupported control logic type %u\n", -+ cldb.control_logic_type); -+ return -EINVAL; -+ } -+ -+ if (ret) -+ ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, -+ tps68470_cros, ARRAY_SIZE(tps68470_cros), -+ NULL, 0, NULL); -+ else -+ ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, -+ tps68470_win, ARRAY_SIZE(tps68470_win), -+ NULL, 0, NULL); -+ -+ if (ret) { -+ dev_err(&client->dev, "Failed to add MFD devices\n"); -+ return ret; -+ } -+ -+ return 0; -+} --- -2.32.0 - -From 6d2bc22506f8dde18ab75b64ad67d716da0db1af Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 20 May 2021 15:09:28 +0100 -Subject: [PATCH] mfd: tps68470: Remove tps68470 MFD driver - -This driver only covered one scenario in which ACPI devices with _HID -INT3472 are found, and its functionality has been taken over by the -intel-skl-int3472 module, so remove it. - -Acked-by: Andy Shevchenko -Acked-by: Lee Jones -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/acpi/pmic/Kconfig | 2 +- - drivers/gpio/Kconfig | 2 +- - drivers/mfd/Kconfig | 18 -------- - drivers/mfd/Makefile | 1 - - drivers/mfd/tps68470.c | 97 --------------------------------------- - 5 files changed, 2 insertions(+), 118 deletions(-) - delete mode 100644 drivers/mfd/tps68470.c - -diff --git a/drivers/acpi/pmic/Kconfig b/drivers/acpi/pmic/Kconfig -index 56bbcb2ce61b..f84b8f6038dc 100644 ---- a/drivers/acpi/pmic/Kconfig -+++ b/drivers/acpi/pmic/Kconfig -@@ -52,7 +52,7 @@ endif # PMIC_OPREGION - - config TPS68470_PMIC_OPREGION - bool "ACPI operation region support for TPS68470 PMIC" -- depends on MFD_TPS68470 -+ depends on INTEL_SKL_INT3472 - help - This config adds ACPI operation region support for TI TPS68470 PMIC. - TPS68470 device is an advanced power management unit that powers -diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig -index e3607ec4c2e8..269d88c7d5a5 100644 ---- a/drivers/gpio/Kconfig -+++ b/drivers/gpio/Kconfig -@@ -1345,7 +1345,7 @@ config GPIO_TPS65912 - - config GPIO_TPS68470 - bool "TPS68470 GPIO" -- depends on MFD_TPS68470 -+ depends on INTEL_SKL_INT3472 - help - Select this option to enable GPIO driver for the TPS68470 - chip family. -diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig -index b74efa469e90..8a112a194c3b 100644 ---- a/drivers/mfd/Kconfig -+++ b/drivers/mfd/Kconfig -@@ -1511,24 +1511,6 @@ config MFD_TPS65217 - This driver can also be built as a module. If so, the module - will be called tps65217. - --config MFD_TPS68470 -- bool "TI TPS68470 Power Management / LED chips" -- depends on ACPI && PCI && I2C=y -- depends on I2C_DESIGNWARE_PLATFORM=y -- select MFD_CORE -- select REGMAP_I2C -- help -- If you say yes here you get support for the TPS68470 series of -- Power Management / LED chips. -- -- These include voltage regulators, LEDs and other features -- that are often used in portable devices. -- -- This option is a bool as it provides an ACPI operation -- region, which must be available before any of the devices -- using this are probed. This option also configures the -- designware-i2c driver to be built-in, for the same reason. -- - config MFD_TI_LP873X - tristate "TI LP873X Power Management IC" - depends on I2C -diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile -index 834f5463af28..daccc3475de5 100644 ---- a/drivers/mfd/Makefile -+++ b/drivers/mfd/Makefile -@@ -105,7 +105,6 @@ obj-$(CONFIG_MFD_TPS65910) += tps65910.o - obj-$(CONFIG_MFD_TPS65912) += tps65912-core.o - obj-$(CONFIG_MFD_TPS65912_I2C) += tps65912-i2c.o - obj-$(CONFIG_MFD_TPS65912_SPI) += tps65912-spi.o --obj-$(CONFIG_MFD_TPS68470) += tps68470.o - obj-$(CONFIG_MFD_TPS80031) += tps80031.o - obj-$(CONFIG_MENELAUS) += menelaus.o - -diff --git a/drivers/mfd/tps68470.c b/drivers/mfd/tps68470.c -deleted file mode 100644 -index 4a4df4ffd18c..000000000000 ---- a/drivers/mfd/tps68470.c -+++ /dev/null -@@ -1,97 +0,0 @@ --// SPDX-License-Identifier: GPL-2.0 --/* -- * TPS68470 chip Parent driver -- * -- * Copyright (C) 2017 Intel Corporation -- * -- * Authors: -- * Rajmohan Mani -- * Tianshu Qiu -- * Jian Xu Zheng -- * Yuning Pu -- */ -- --#include --#include --#include --#include --#include --#include --#include -- --static const struct mfd_cell tps68470s[] = { -- { .name = "tps68470-gpio" }, -- { .name = "tps68470_pmic_opregion" }, --}; -- --static const struct regmap_config tps68470_regmap_config = { -- .reg_bits = 8, -- .val_bits = 8, -- .max_register = TPS68470_REG_MAX, --}; -- --static int tps68470_chip_init(struct device *dev, struct regmap *regmap) --{ -- unsigned int version; -- int ret; -- -- /* Force software reset */ -- ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); -- if (ret) -- return ret; -- -- ret = regmap_read(regmap, TPS68470_REG_REVID, &version); -- if (ret) { -- dev_err(dev, "Failed to read revision register: %d\n", ret); -- return ret; -- } -- -- dev_info(dev, "TPS68470 REVID: 0x%x\n", version); -- -- return 0; --} -- --static int tps68470_probe(struct i2c_client *client) --{ -- struct device *dev = &client->dev; -- struct regmap *regmap; -- int ret; -- -- regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); -- if (IS_ERR(regmap)) { -- dev_err(dev, "devm_regmap_init_i2c Error %ld\n", -- PTR_ERR(regmap)); -- return PTR_ERR(regmap); -- } -- -- i2c_set_clientdata(client, regmap); -- -- ret = tps68470_chip_init(dev, regmap); -- if (ret < 0) { -- dev_err(dev, "TPS68470 Init Error %d\n", ret); -- return ret; -- } -- -- ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, tps68470s, -- ARRAY_SIZE(tps68470s), NULL, 0, NULL); -- if (ret < 0) { -- dev_err(dev, "devm_mfd_add_devices failed: %d\n", ret); -- return ret; -- } -- -- return 0; --} -- --static const struct acpi_device_id tps68470_acpi_ids[] = { -- {"INT3472"}, -- {}, --}; -- --static struct i2c_driver tps68470_driver = { -- .driver = { -- .name = "tps68470", -- .acpi_match_table = tps68470_acpi_ids, -- }, -- .probe_new = tps68470_probe, --}; --builtin_i2c_driver(tps68470_driver); --- -2.32.0 - -From b29ed9bc07d7f8de7ba84f6f7626902abb88fe09 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 5 Apr 2021 23:56:53 +0100 -Subject: [PATCH] media: ipu3-cio2: Toggle sensor streaming in pm runtime ops - -The .suspend() and .resume() runtime_pm operations for the ipu3-cio2 -driver currently do not handle the sensor's stream. Setting .s_stream() on -or off for the sensor subdev means that sensors will pause and resume the -stream at the appropriate time even if their drivers don't implement those -operations. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 15 ++++++++++++++- - 1 file changed, 14 insertions(+), 1 deletion(-) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -index fecef85bd62e..9dafb9470708 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -@@ -1973,12 +1973,19 @@ static int __maybe_unused cio2_suspend(struct device *dev) - struct pci_dev *pci_dev = to_pci_dev(dev); - struct cio2_device *cio2 = pci_get_drvdata(pci_dev); - struct cio2_queue *q = cio2->cur_queue; -+ int r; - - dev_dbg(dev, "cio2 suspend\n"); - if (!cio2->streaming) - return 0; - - /* Stop stream */ -+ r = v4l2_subdev_call(q->sensor, video, s_stream, 0); -+ if (r) { -+ dev_err(dev, "failed to stop sensor streaming\n"); -+ return r; -+ } -+ - cio2_hw_exit(cio2, q); - synchronize_irq(pci_dev->irq); - -@@ -2013,8 +2020,14 @@ static int __maybe_unused cio2_resume(struct device *dev) - } - - r = cio2_hw_init(cio2, q); -- if (r) -+ if (r) { - dev_err(dev, "fail to init cio2 hw\n"); -+ return r; -+ } -+ -+ r = v4l2_subdev_call(q->sensor, video, s_stream, 1); -+ if (r) -+ dev_err(dev, "fail to start sensor streaming\n"); - - return r; - } --- -2.32.0 - -From 455b9f9e61b3b1f57ad6b8042cd2cb89601cbb45 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 5 Apr 2021 23:56:54 +0100 -Subject: [PATCH] media: i2c: Add support for ov5693 sensor - -The OV5693 is a 5 Mpx CMOS image sensor, connected via MIPI CSI-2. The -chip is capable of a single lane configuration, but currently only two -lanes are supported. - -Most of the sensor's features are supported, with the main exception -being the lens correction algorithm. - -The driver provides all mandatory, optional and recommended V4L2 controls -for maximum compatibility with libcamera. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 7 + - drivers/media/i2c/Kconfig | 11 + - drivers/media/i2c/Makefile | 1 + - drivers/media/i2c/ov5693.c | 1557 ++++++++++++++++++++++++++++++++++++ - 4 files changed, 1576 insertions(+) - create mode 100644 drivers/media/i2c/ov5693.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 2a421d2e4b07..b270caa6f766 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -13276,6 +13276,13 @@ S: Maintained - T: git git://linuxtv.org/media_tree.git - F: drivers/media/i2c/ov5675.c - -+OMNIVISION OV5693 SENSOR DRIVER -+M: Daniel Scally -+L: linux-media@vger.kernel.org -+S: Maintained -+T: git git://linuxtv.org/media_tree.git -+F: drivers/media/i2c/ov5693.c -+ - OMNIVISION OV5695 SENSOR DRIVER - M: Shunqian Zheng - L: linux-media@vger.kernel.org -diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig -index 462c0e059754..2893e74af99a 100644 ---- a/drivers/media/i2c/Kconfig -+++ b/drivers/media/i2c/Kconfig -@@ -999,6 +999,17 @@ config VIDEO_OV5675 - To compile this driver as a module, choose M here: the - module will be called ov5675. - -+config VIDEO_OV5693 -+ tristate "OmniVision OV5693 sensor support" -+ depends on I2C && VIDEO_V4L2 -+ select V4L2_FWNODE -+ help -+ This is a Video4Linux2 sensor driver for the OmniVision -+ OV5693 camera. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ov5693. -+ - config VIDEO_OV5695 - tristate "OmniVision OV5695 sensor support" - depends on I2C && VIDEO_V4L2 -diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile -index 0c067beca066..7d0884bc89f1 100644 ---- a/drivers/media/i2c/Makefile -+++ b/drivers/media/i2c/Makefile -@@ -75,6 +75,7 @@ obj-$(CONFIG_VIDEO_OV5647) += ov5647.o - obj-$(CONFIG_VIDEO_OV5648) += ov5648.o - obj-$(CONFIG_VIDEO_OV5670) += ov5670.o - obj-$(CONFIG_VIDEO_OV5675) += ov5675.o -+obj-$(CONFIG_VIDEO_OV5693) += ov5693.o - obj-$(CONFIG_VIDEO_OV5695) += ov5695.o - obj-$(CONFIG_VIDEO_OV6650) += ov6650.o - obj-$(CONFIG_VIDEO_OV7251) += ov7251.o -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -new file mode 100644 -index 000000000000..da2ca99a7ad3 ---- /dev/null -+++ b/drivers/media/i2c/ov5693.c -@@ -0,0 +1,1557 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. -+ * -+ * Adapted from the atomisp-ov5693 driver, with contributions from: -+ * -+ * Daniel Scally -+ * Jean-Michel Hautbois -+ * Fabian Wuthrich -+ * Tsuchiya Yuto -+ * Jordan Hand -+ * Jake Day -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+/* System Control */ -+#define OV5693_SW_RESET_REG 0x0103 -+#define OV5693_SW_STREAM_REG 0x0100 -+#define OV5693_START_STREAMING 0x01 -+#define OV5693_STOP_STREAMING 0x00 -+#define OV5693_SW_RESET 0x01 -+ -+#define OV5693_REG_CHIP_ID_H 0x300a -+#define OV5693_REG_CHIP_ID_L 0x300b -+/* Yes, this is right. The datasheet for the OV5693 gives its ID as 0x5690 */ -+#define OV5693_CHIP_ID 0x5690 -+ -+/* Exposure */ -+#define OV5693_EXPOSURE_L_CTRL_HH_REG 0x3500 -+#define OV5693_EXPOSURE_L_CTRL_H_REG 0x3501 -+#define OV5693_EXPOSURE_L_CTRL_L_REG 0x3502 -+#define OV5693_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(14, 12)) >> 12) -+#define OV5693_EXPOSURE_CTRL_H(v) (((v) & GENMASK(11, 4)) >> 4) -+#define OV5693_EXPOSURE_CTRL_L(v) (((v) & GENMASK(3, 0)) << 4) -+#define OV5693_INTEGRATION_TIME_MARGIN 8 -+#define OV5693_EXPOSURE_MIN 1 -+#define OV5693_EXPOSURE_STEP 1 -+ -+/* Analogue Gain */ -+#define OV5693_GAIN_CTRL_H_REG 0x350a -+#define OV5693_GAIN_CTRL_H(v) (((v) >> 4) & GENMASK(2, 0)) -+#define OV5693_GAIN_CTRL_L_REG 0x350b -+#define OV5693_GAIN_CTRL_L(v) (((v) << 4) & GENMASK(7, 4)) -+#define OV5693_GAIN_MIN 1 -+#define OV5693_GAIN_MAX 127 -+#define OV5693_GAIN_DEF 8 -+#define OV5693_GAIN_STEP 1 -+ -+/* Digital Gain */ -+#define OV5693_MWB_RED_GAIN_H_REG 0x3400 -+#define OV5693_MWB_RED_GAIN_L_REG 0x3401 -+#define OV5693_MWB_GREEN_GAIN_H_REG 0x3402 -+#define OV5693_MWB_GREEN_GAIN_L_REG 0x3403 -+#define OV5693_MWB_BLUE_GAIN_H_REG 0x3404 -+#define OV5693_MWB_BLUE_GAIN_L_REG 0x3405 -+#define OV5693_MWB_GAIN_H_CTRL(v) (((v) >> 8) & GENMASK(3, 0)) -+#define OV5693_MWB_GAIN_L_CTRL(v) ((v) & GENMASK(7, 0)) -+#define OV5693_MWB_GAIN_MAX 0x0fff -+#define OV5693_DIGITAL_GAIN_MIN 1 -+#define OV5693_DIGITAL_GAIN_MAX 4095 -+#define OV5693_DIGITAL_GAIN_DEF 1024 -+#define OV5693_DIGITAL_GAIN_STEP 1 -+ -+/* Timing and Format */ -+#define OV5693_CROP_START_X_H_REG 0x3800 -+#define OV5693_CROP_START_X_H(v) (((v) & GENMASK(12, 8)) >> 8) -+#define OV5693_CROP_START_X_L_REG 0x3801 -+#define OV5693_CROP_START_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_START_Y_H_REG 0x3802 -+#define OV5693_CROP_START_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) -+#define OV5693_CROP_START_Y_L_REG 0x3803 -+#define OV5693_CROP_START_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_END_X_H_REG 0x3804 -+#define OV5693_CROP_END_X_H(v) (((v) & GENMASK(12, 8)) >> 8) -+#define OV5693_CROP_END_X_L_REG 0x3805 -+#define OV5693_CROP_END_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_END_Y_H_REG 0x3806 -+#define OV5693_CROP_END_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) -+#define OV5693_CROP_END_Y_L_REG 0x3807 -+#define OV5693_CROP_END_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OUTPUT_SIZE_X_H_REG 0x3808 -+#define OV5693_OUTPUT_SIZE_X_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OUTPUT_SIZE_X_L_REG 0x3809 -+#define OV5693_OUTPUT_SIZE_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OUTPUT_SIZE_Y_H_REG 0x380a -+#define OV5693_OUTPUT_SIZE_Y_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OUTPUT_SIZE_Y_L_REG 0x380b -+#define OV5693_OUTPUT_SIZE_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_TIMING_HTS_H_REG 0x380c -+#define OV5693_TIMING_HTS_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_TIMING_HTS_L_REG 0x380d -+#define OV5693_TIMING_HTS_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_FIXED_PPL 2688U -+ -+#define OV5693_TIMING_VTS_H_REG 0x380e -+#define OV5693_TIMING_VTS_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_TIMING_VTS_L_REG 0x380f -+#define OV5693_TIMING_VTS_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_TIMING_MAX_VTS 0xffff -+#define OV5693_TIMING_MIN_VTS 0x04 -+ -+#define OV5693_OFFSET_START_X_H_REG 0x3810 -+#define OV5693_OFFSET_START_X_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OFFSET_START_X_L_REG 0x3811 -+#define OV5693_OFFSET_START_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OFFSET_START_Y_H_REG 0x3812 -+#define OV5693_OFFSET_START_Y_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OFFSET_START_Y_L_REG 0x3813 -+#define OV5693_OFFSET_START_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_SUB_INC_X_REG 0x3814 -+#define OV5693_SUB_INC_Y_REG 0x3815 -+ -+#define OV5693_FORMAT1_REG 0x3820 -+#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -+#define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) -+#define OV5693_FORMAT1_VBIN_EN BIT(0) -+#define OV5693_FORMAT2_REG 0x3821 -+#define OV5693_FORMAT2_HDR_EN BIT(7) -+#define OV5693_FORMAT2_FLIP_HORZ_ISP_EN BIT(2) -+#define OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN BIT(1) -+#define OV5693_FORMAT2_HBIN_EN BIT(0) -+ -+#define OV5693_ISP_CTRL2_REG 0x5002 -+#define OV5693_ISP_SCALE_ENABLE BIT(7) -+ -+/* Pixel Array */ -+#define OV5693_NATIVE_WIDTH 2624 -+#define OV5693_NATIVE_HEIGHT 1956 -+#define OV5693_NATIVE_START_LEFT 0 -+#define OV5693_NATIVE_START_TOP 0 -+#define OV5693_ACTIVE_WIDTH 2592 -+#define OV5693_ACTIVE_HEIGHT 1944 -+#define OV5693_ACTIVE_START_LEFT 16 -+#define OV5693_ACTIVE_START_TOP 6 -+#define OV5693_MIN_CROP_WIDTH 2 -+#define OV5693_MIN_CROP_HEIGHT 2 -+ -+/* Test Pattern */ -+#define OV5693_TEST_PATTERN_REG 0x5e00 -+#define OV5693_TEST_PATTERN_ENABLE BIT(7) -+#define OV5693_TEST_PATTERN_ROLLING BIT(6) -+#define OV5693_TEST_PATTERN_RANDOM 0x01 -+#define OV5693_TEST_PATTERN_BARS 0x00 -+ -+/* System Frequencies */ -+#define OV5693_XVCLK_FREQ 19200000 -+#define OV5693_LINK_FREQ_400MHZ 400000000 -+#define OV5693_PIXEL_RATE 160000000 -+ -+/* Miscellaneous */ -+#define OV5693_NUM_SUPPLIES 2 -+ -+#define to_ov5693_sensor(x) container_of(x, struct ov5693_device, sd) -+ -+struct ov5693_reg { -+ u16 reg; -+ u8 val; -+}; -+ -+struct ov5693_reg_list { -+ u32 num_regs; -+ const struct ov5693_reg *regs; -+}; -+ -+struct ov5693_device { -+ struct i2c_client *client; -+ struct device *dev; -+ -+ /* Protect against concurrent changes to controls */ -+ struct mutex lock; -+ -+ struct gpio_desc *reset; -+ struct gpio_desc *powerdown; -+ struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; -+ struct clk *clk; -+ -+ struct ov5693_mode { -+ struct v4l2_rect crop; -+ struct v4l2_mbus_framefmt format; -+ bool binning_x; -+ bool binning_y; -+ unsigned int inc_x_odd; -+ unsigned int inc_y_odd; -+ unsigned int vts; -+ } mode; -+ bool streaming; -+ -+ struct v4l2_subdev sd; -+ struct media_pad pad; -+ -+ struct ov5693_v4l2_ctrls { -+ struct v4l2_ctrl_handler handler; -+ struct v4l2_ctrl *link_freq; -+ struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *exposure; -+ struct v4l2_ctrl *analogue_gain; -+ struct v4l2_ctrl *digital_gain; -+ struct v4l2_ctrl *hflip; -+ struct v4l2_ctrl *vflip; -+ struct v4l2_ctrl *hblank; -+ struct v4l2_ctrl *vblank; -+ struct v4l2_ctrl *test_pattern; -+ } ctrls; -+}; -+ -+static const struct ov5693_reg ov5693_global_regs[] = { -+ {0x3016, 0xf0}, -+ {0x3017, 0xf0}, -+ {0x3018, 0xf0}, -+ {0x3022, 0x01}, -+ {0x3028, 0x44}, -+ {0x3098, 0x02}, -+ {0x3099, 0x19}, -+ {0x309a, 0x02}, -+ {0x309b, 0x01}, -+ {0x309c, 0x00}, -+ {0x30a0, 0xd2}, -+ {0x30a2, 0x01}, -+ {0x30b2, 0x00}, -+ {0x30b3, 0x7d}, -+ {0x30b4, 0x03}, -+ {0x30b5, 0x04}, -+ {0x30b6, 0x01}, -+ {0x3104, 0x21}, -+ {0x3106, 0x00}, -+ {0x3406, 0x01}, -+ {0x3503, 0x07}, -+ {0x350b, 0x40}, -+ {0x3601, 0x0a}, -+ {0x3602, 0x38}, -+ {0x3612, 0x80}, -+ {0x3620, 0x54}, -+ {0x3621, 0xc7}, -+ {0x3622, 0x0f}, -+ {0x3625, 0x10}, -+ {0x3630, 0x55}, -+ {0x3631, 0xf4}, -+ {0x3632, 0x00}, -+ {0x3633, 0x34}, -+ {0x3634, 0x02}, -+ {0x364d, 0x0d}, -+ {0x364f, 0xdd}, -+ {0x3660, 0x04}, -+ {0x3662, 0x10}, -+ {0x3663, 0xf1}, -+ {0x3665, 0x00}, -+ {0x3666, 0x20}, -+ {0x3667, 0x00}, -+ {0x366a, 0x80}, -+ {0x3680, 0xe0}, -+ {0x3681, 0x00}, -+ {0x3700, 0x42}, -+ {0x3701, 0x14}, -+ {0x3702, 0xa0}, -+ {0x3703, 0xd8}, -+ {0x3704, 0x78}, -+ {0x3705, 0x02}, -+ {0x370a, 0x00}, -+ {0x370b, 0x20}, -+ {0x370c, 0x0c}, -+ {0x370d, 0x11}, -+ {0x370e, 0x00}, -+ {0x370f, 0x40}, -+ {0x3710, 0x00}, -+ {0x371a, 0x1c}, -+ {0x371b, 0x05}, -+ {0x371c, 0x01}, -+ {0x371e, 0xa1}, -+ {0x371f, 0x0c}, -+ {0x3721, 0x00}, -+ {0x3724, 0x10}, -+ {0x3726, 0x00}, -+ {0x372a, 0x01}, -+ {0x3730, 0x10}, -+ {0x3738, 0x22}, -+ {0x3739, 0xe5}, -+ {0x373a, 0x50}, -+ {0x373b, 0x02}, -+ {0x373c, 0x41}, -+ {0x373f, 0x02}, -+ {0x3740, 0x42}, -+ {0x3741, 0x02}, -+ {0x3742, 0x18}, -+ {0x3743, 0x01}, -+ {0x3744, 0x02}, -+ {0x3747, 0x10}, -+ {0x374c, 0x04}, -+ {0x3751, 0xf0}, -+ {0x3752, 0x00}, -+ {0x3753, 0x00}, -+ {0x3754, 0xc0}, -+ {0x3755, 0x00}, -+ {0x3756, 0x1a}, -+ {0x3758, 0x00}, -+ {0x3759, 0x0f}, -+ {0x376b, 0x44}, -+ {0x375c, 0x04}, -+ {0x3774, 0x10}, -+ {0x3776, 0x00}, -+ {0x377f, 0x08}, -+ {0x3780, 0x22}, -+ {0x3781, 0x0c}, -+ {0x3784, 0x2c}, -+ {0x3785, 0x1e}, -+ {0x378f, 0xf5}, -+ {0x3791, 0xb0}, -+ {0x3795, 0x00}, -+ {0x3796, 0x64}, -+ {0x3797, 0x11}, -+ {0x3798, 0x30}, -+ {0x3799, 0x41}, -+ {0x379a, 0x07}, -+ {0x379b, 0xb0}, -+ {0x379c, 0x0c}, -+ {0x3a04, 0x06}, -+ {0x3a05, 0x14}, -+ {0x3e07, 0x20}, -+ {0x4000, 0x08}, -+ {0x4001, 0x04}, -+ {0x4004, 0x08}, -+ {0x4006, 0x20}, -+ {0x4008, 0x24}, -+ {0x4009, 0x10}, -+ {0x4058, 0x00}, -+ {0x4101, 0xb2}, -+ {0x4307, 0x31}, -+ {0x4511, 0x05}, -+ {0x4512, 0x01}, -+ {0x481f, 0x30}, -+ {0x4826, 0x2c}, -+ {0x4d02, 0xfd}, -+ {0x4d03, 0xf5}, -+ {0x4d04, 0x0c}, -+ {0x4d05, 0xcc}, -+ {0x4837, 0x0a}, -+ {0x5003, 0x20}, -+ {0x5013, 0x00}, -+ {0x5842, 0x01}, -+ {0x5843, 0x2b}, -+ {0x5844, 0x01}, -+ {0x5845, 0x92}, -+ {0x5846, 0x01}, -+ {0x5847, 0x8f}, -+ {0x5848, 0x01}, -+ {0x5849, 0x0c}, -+ {0x5e10, 0x0c}, -+ {0x3820, 0x00}, -+ {0x3821, 0x1e}, -+ {0x5041, 0x14} -+}; -+ -+static const struct ov5693_reg_list ov5693_global_setting = { -+ .num_regs = ARRAY_SIZE(ov5693_global_regs), -+ .regs = ov5693_global_regs, -+}; -+ -+static const struct v4l2_rect ov5693_default_crop = { -+ .left = OV5693_ACTIVE_START_LEFT, -+ .top = OV5693_ACTIVE_START_TOP, -+ .width = OV5693_ACTIVE_WIDTH, -+ .height = OV5693_ACTIVE_HEIGHT, -+}; -+ -+static const struct v4l2_mbus_framefmt ov5693_default_fmt = { -+ .width = OV5693_ACTIVE_WIDTH, -+ .height = OV5693_ACTIVE_HEIGHT, -+ .code = MEDIA_BUS_FMT_SBGGR10_1X10, -+}; -+ -+static const s64 link_freq_menu_items[] = { -+ OV5693_LINK_FREQ_400MHZ -+}; -+ -+static const char * const ov5693_supply_names[] = { -+ "avdd", -+ "dovdd", -+}; -+ -+static const char * const ov5693_test_pattern_menu[] = { -+ "Disabled", -+ "Random Data", -+ "Colour Bars", -+ "Colour Bars with Rolling Bar" -+}; -+ -+static const u8 ov5693_test_pattern_bits[] = { -+ 0, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_RANDOM, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS | -+ OV5693_TEST_PATTERN_ROLLING, -+}; -+ -+/* I2C I/O Operations */ -+ -+static int ov5693_read_reg(struct ov5693_device *ov5693, u16 addr, u8 *value) -+{ -+ struct i2c_client *client = ov5693->client; -+ struct i2c_msg msgs[2]; -+ u8 addr_buf[2]; -+ u8 data_buf; -+ int ret; -+ -+ put_unaligned_be16(addr, addr_buf); -+ -+ /* 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 register value */ -+ msgs[1].addr = client->addr; -+ msgs[1].flags = I2C_M_RD; -+ msgs[1].len = 1; -+ msgs[1].buf = &data_buf; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ if (ret != ARRAY_SIZE(msgs)) -+ return -EIO; -+ -+ *value = data_buf; -+ -+ return 0; -+} -+ -+static void ov5693_write_reg(struct ov5693_device *ov5693, u16 addr, u8 value, -+ int *error) -+{ -+ unsigned char data[3] = { addr >> 8, addr & 0xff, value }; -+ int ret; -+ -+ if (*error < 0) -+ return; -+ -+ ret = i2c_master_send(ov5693->client, data, sizeof(data)); -+ if (ret < 0) { -+ dev_dbg(ov5693->dev, "i2c send error at address 0x%04x: %d\n", -+ addr, ret); -+ *error = ret; -+ } -+} -+ -+static int ov5693_write_reg_array(struct ov5693_device *ov5693, -+ const struct ov5693_reg_list *reglist) -+{ -+ unsigned int i; -+ int ret = 0; -+ -+ for (i = 0; i < reglist->num_regs; i++) -+ ov5693_write_reg(ov5693, reglist->regs[i].reg, -+ reglist->regs[i].val, &ret); -+ -+ return ret; -+} -+ -+static int ov5693_update_bits(struct ov5693_device *ov5693, u16 address, -+ u16 mask, u16 bits) -+{ -+ u8 value = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, address, &value); -+ if (ret) -+ return ret; -+ -+ value &= ~mask; -+ value |= bits; -+ -+ ov5693_write_reg(ov5693, address, value, &ret); -+ -+ return ret; -+} -+ -+/* V4L2 Controls Functions */ -+ -+static int ov5693_flip_vert_configure(struct ov5693_device *ov5693, bool enable) -+{ -+ u8 bits = OV5693_FORMAT1_FLIP_VERT_ISP_EN | -+ OV5693_FORMAT1_FLIP_VERT_SENSOR_EN; -+ int ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, bits, -+ enable ? bits : 0); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int ov5693_flip_horz_configure(struct ov5693_device *ov5693, bool enable) -+{ -+ u8 bits = OV5693_FORMAT2_FLIP_HORZ_ISP_EN | -+ OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN; -+ int ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, bits, -+ enable ? bits : 0); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int ov5693_get_exposure(struct ov5693_device *ov5693, s32 *value) -+{ -+ u8 exposure_hh = 0, exposure_h = 0, exposure_l = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, &exposure_hh); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, &exposure_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, &exposure_l); -+ if (ret) -+ return ret; -+ -+ /* The lowest 4 bits are unsupported fractional bits */ -+ *value = ((exposure_hh << 16) | (exposure_h << 8) | exposure_l) >> 4; -+ -+ return 0; -+} -+ -+static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, -+ OV5693_EXPOSURE_CTRL_HH(exposure), &ret); -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, -+ OV5693_EXPOSURE_CTRL_H(exposure), &ret); -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, -+ OV5693_EXPOSURE_CTRL_L(exposure), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_get_gain(struct ov5693_device *ov5693, u32 *gain) -+{ -+ u8 gain_l = 0, gain_h = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_H_REG, &gain_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_L_REG, &gain_l); -+ if (ret) -+ return ret; -+ -+ /* As with exposure, the lowest 4 bits are fractional bits. */ -+ *gain = ((gain_h << 8) | gain_l) >> 4; -+ -+ return ret; -+} -+ -+static int ov5693_digital_gain_configure(struct ov5693_device *ov5693, u32 gain) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_analog_gain_configure(struct ov5693_device *ov5693, u32 gain) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_L_REG, -+ OV5693_GAIN_CTRL_L(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_H_REG, -+ OV5693_GAIN_CTRL_H(gain), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_vts_configure(struct ov5693_device *ov5693, u32 vblank) -+{ -+ u16 vts = ov5693->mode.format.height + vblank; -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG, -+ OV5693_TIMING_VTS_H(vts), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG, -+ OV5693_TIMING_VTS_L(vts), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_test_pattern_configure(struct ov5693_device *ov5693, u32 idx) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_TEST_PATTERN_REG, -+ ov5693_test_pattern_bits[idx], &ret); -+ -+ return ret; -+} -+ -+static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *ov5693 = -+ container_of(ctrl->handler, struct ov5693_device, ctrls.handler); -+ int ret = 0; -+ -+ /* If VBLANK is altered we need to update exposure to compensate */ -+ if (ctrl->id == V4L2_CID_VBLANK) { -+ int exposure_max; -+ -+ exposure_max = ov5693->mode.format.height + ctrl->val - -+ OV5693_INTEGRATION_TIME_MARGIN; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, -+ ov5693->ctrls.exposure->minimum, -+ exposure_max, -+ ov5693->ctrls.exposure->step, -+ min(ov5693->ctrls.exposure->val, exposure_max)); -+ } -+ -+ /* Only apply changes to the controls if the device is powered up */ -+ if (!pm_runtime_get_if_in_use(ov5693->dev)) -+ return 0; -+ -+ switch (ctrl->id) { -+ case V4L2_CID_EXPOSURE: -+ ret = ov5693_exposure_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_ANALOGUE_GAIN: -+ ret = ov5693_analog_gain_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_DIGITAL_GAIN: -+ ret = ov5693_digital_gain_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_HFLIP: -+ ret = ov5693_flip_horz_configure(ov5693, !!ctrl->val); -+ break; -+ case V4L2_CID_VFLIP: -+ ret = ov5693_flip_vert_configure(ov5693, !!ctrl->val); -+ break; -+ case V4L2_CID_VBLANK: -+ ret = ov5693_vts_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_TEST_PATTERN: -+ ret = ov5693_test_pattern_configure(ov5693, ctrl->val); -+ break; -+ default: -+ ret = -EINVAL; -+ } -+ -+ pm_runtime_put(ov5693->dev); -+ -+ return ret; -+} -+ -+static int ov5693_g_volatile_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *ov5693 = -+ container_of(ctrl->handler, struct ov5693_device, ctrls.handler); -+ -+ switch (ctrl->id) { -+ case V4L2_CID_EXPOSURE_ABSOLUTE: -+ return ov5693_get_exposure(ov5693, &ctrl->val); -+ case V4L2_CID_AUTOGAIN: -+ return ov5693_get_gain(ov5693, &ctrl->val); -+ default: -+ return -EINVAL; -+ } -+} -+ -+static const struct v4l2_ctrl_ops ov5693_ctrl_ops = { -+ .s_ctrl = ov5693_s_ctrl, -+ .g_volatile_ctrl = ov5693_g_volatile_ctrl -+}; -+ -+/* System Control Functions */ -+ -+static int ov5693_mode_configure(struct ov5693_device *ov5693) -+{ -+ const struct ov5693_mode *mode = &ov5693->mode; -+ int ret = 0; -+ -+ /* Crop Start X */ -+ ov5693_write_reg(ov5693, OV5693_CROP_START_X_H_REG, -+ OV5693_CROP_START_X_H(mode->crop.left), &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_START_X_L_REG, -+ OV5693_CROP_START_X_L(mode->crop.left), &ret); -+ -+ /* Offset X */ -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_H_REG, -+ OV5693_OFFSET_START_X_H(0), &ret); -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_L_REG, -+ OV5693_OFFSET_START_X_L(0), &ret); -+ -+ /* Output Size X */ -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_H_REG, -+ OV5693_OUTPUT_SIZE_X_H(mode->format.width), &ret); -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_L_REG, -+ OV5693_OUTPUT_SIZE_X_L(mode->format.width), &ret); -+ -+ /* Crop End X */ -+ ov5693_write_reg(ov5693, OV5693_CROP_END_X_H_REG, -+ OV5693_CROP_END_X_H(mode->crop.left + mode->crop.width), -+ &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_END_X_L_REG, -+ OV5693_CROP_END_X_L(mode->crop.left + mode->crop.width), -+ &ret); -+ -+ /* Horizontal Total Size */ -+ ov5693_write_reg(ov5693, OV5693_TIMING_HTS_H_REG, -+ OV5693_TIMING_HTS_H(OV5693_FIXED_PPL), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_HTS_L_REG, -+ OV5693_TIMING_HTS_L(OV5693_FIXED_PPL), &ret); -+ -+ /* Crop Start Y */ -+ ov5693_write_reg(ov5693, OV5693_CROP_START_Y_H_REG, -+ OV5693_CROP_START_Y_H(mode->crop.top), &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_START_Y_L_REG, -+ OV5693_CROP_START_Y_L(mode->crop.top), &ret); -+ -+ /* Offset Y */ -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_H_REG, -+ OV5693_OFFSET_START_Y_H(0), &ret); -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_L_REG, -+ OV5693_OFFSET_START_Y_L(0), &ret); -+ -+ /* Output Size Y */ -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_H_REG, -+ OV5693_OUTPUT_SIZE_Y_H(mode->format.height), &ret); -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_L_REG, -+ OV5693_OUTPUT_SIZE_Y_L(mode->format.height), &ret); -+ -+ /* Crop End Y */ -+ ov5693_write_reg(ov5693, OV5693_CROP_END_Y_H_REG, -+ OV5693_CROP_END_Y_H(mode->crop.top + mode->crop.height), -+ &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_END_Y_L_REG, -+ OV5693_CROP_END_Y_L(mode->crop.top + mode->crop.height), -+ &ret); -+ -+ /* Vertical Total Size */ -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG, -+ OV5693_TIMING_VTS_H(mode->vts), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG, -+ OV5693_TIMING_VTS_L(mode->vts), &ret); -+ -+ /* Subsample X increase */ -+ ov5693_write_reg(ov5693, OV5693_SUB_INC_X_REG, -+ ((mode->inc_x_odd << 4) & 0xf0) | 0x01, &ret); -+ /* Subsample Y increase */ -+ ov5693_write_reg(ov5693, OV5693_SUB_INC_Y_REG, -+ ((mode->inc_y_odd << 4) & 0xf0) | 0x01, &ret); -+ -+ /* Binning */ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, -+ OV5693_FORMAT1_VBIN_EN, -+ mode->binning_y ? OV5693_FORMAT1_VBIN_EN : 0); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, -+ OV5693_FORMAT2_HBIN_EN, -+ mode->binning_x ? OV5693_FORMAT2_HBIN_EN : 0); -+ -+ return ret; -+} -+ -+static int ov5693_sw_standby(struct ov5693_device *ov5693, bool standby) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_SW_STREAM_REG, -+ standby ? OV5693_STOP_STREAMING : OV5693_START_STREAMING, -+ &ret); -+ -+ return ret; -+} -+ -+static int ov5693_sw_reset(struct ov5693_device *ov5693) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_SW_RESET_REG, OV5693_SW_RESET, &ret); -+ -+ return ret; -+} -+ -+static int ov5693_sensor_init(struct ov5693_device *ov5693) -+{ -+ int ret = 0; -+ -+ ret = ov5693_sw_reset(ov5693); -+ if (ret) { -+ dev_err(ov5693->dev, "%s software reset error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg_array(ov5693, &ov5693_global_setting); -+ if (ret) { -+ dev_err(ov5693->dev, "%s global settings error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_mode_configure(ov5693); -+ if (ret) { -+ dev_err(ov5693->dev, "%s mode configure error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_sw_standby(ov5693, true); -+ if (ret) -+ dev_err(ov5693->dev, "%s software standby error\n", __func__); -+ -+ return ret; -+} -+ -+static void ov5693_sensor_powerdown(struct ov5693_device *ov5693) -+{ -+ gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); -+ -+ regulator_bulk_disable(OV5693_NUM_SUPPLIES, ov5693->supplies); -+ -+ clk_disable_unprepare(ov5693->clk); -+} -+ -+static int ov5693_sensor_powerup(struct ov5693_device *ov5693) -+{ -+ int ret; -+ -+ gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); -+ -+ ret = clk_prepare_enable(ov5693->clk); -+ if (ret) { -+ dev_err(ov5693->dev, "Failed to enable clk\n"); -+ goto fail_power; -+ } -+ -+ ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, ov5693->supplies); -+ if (ret) { -+ dev_err(ov5693->dev, "Failed to enable regulators\n"); -+ goto fail_power; -+ } -+ -+ gpiod_set_value_cansleep(ov5693->powerdown, 0); -+ gpiod_set_value_cansleep(ov5693->reset, 0); -+ -+ usleep_range(5000, 7500); -+ -+ return 0; -+ -+fail_power: -+ ov5693_sensor_powerdown(ov5693); -+ return ret; -+} -+ -+static int __maybe_unused ov5693_sensor_suspend(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ ov5693_sensor_powerdown(ov5693); -+ -+ return 0; -+} -+ -+static int __maybe_unused ov5693_sensor_resume(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ int ret; -+ -+ mutex_lock(&ov5693->lock); -+ -+ ret = ov5693_sensor_powerup(ov5693); -+ if (ret) -+ goto out_unlock; -+ -+ ret = ov5693_sensor_init(ov5693); -+ if (ret) { -+ dev_err(dev, "ov5693 sensor init failure\n"); -+ goto err_power; -+ } -+ -+ goto out_unlock; -+ -+err_power: -+ ov5693_sensor_powerdown(ov5693); -+out_unlock: -+ mutex_unlock(&ov5693->lock); -+ return ret; -+} -+ -+static int ov5693_detect(struct ov5693_device *ov5693) -+{ -+ u8 id_l = 0, id_h = 0; -+ u16 id = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_H, &id_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_L, &id_l); -+ if (ret) -+ return ret; -+ -+ id = (id_h << 8) | id_l; -+ -+ if (id != OV5693_CHIP_ID) { -+ dev_err(ov5693->dev, "sensor ID mismatch. Found 0x%04x\n", id); -+ return -ENODEV; -+ } -+ -+ return 0; -+} -+ -+/* V4L2 Framework callbacks */ -+ -+static unsigned int __ov5693_calc_vts(u32 height) -+{ -+ /* -+ * We need to set a sensible default VTS for whatever format height we -+ * happen to be given from set_fmt(). This function just targets -+ * an even multiple of 30fps. -+ */ -+ -+ unsigned int tgt_fps; -+ -+ tgt_fps = rounddown(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / height, 30); -+ -+ return ALIGN_DOWN(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / tgt_fps, 2); -+} -+ -+static struct v4l2_mbus_framefmt * -+__ov5693_get_pad_format(struct ov5693_device *ov5693, -+ struct v4l2_subdev_pad_config *cfg, -+ unsigned int pad, enum v4l2_subdev_format_whence which) -+{ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ return v4l2_subdev_get_try_format(&ov5693->sd, cfg, pad); -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ return &ov5693->mode.format; -+ default: -+ return NULL; -+ } -+} -+ -+static struct v4l2_rect * -+__ov5693_get_pad_crop(struct ov5693_device *ov5693, -+ struct v4l2_subdev_pad_config *cfg, -+ unsigned int pad, enum v4l2_subdev_format_whence which) -+{ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ return v4l2_subdev_get_try_crop(&ov5693->sd, cfg, pad); -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ return &ov5693->mode.crop; -+ } -+ -+ return NULL; -+} -+ -+static int ov5693_get_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_format *format) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ format->format = ov5693->mode.format; -+ -+ return 0; -+} -+ -+static int ov5693_set_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_format *format) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ const struct v4l2_rect *crop; -+ struct v4l2_mbus_framefmt *fmt; -+ unsigned int hratio, vratio; -+ unsigned int width, height; -+ unsigned int hblank; -+ int exposure_max; -+ int ret = 0; -+ -+ crop = __ov5693_get_pad_crop(ov5693, cfg, format->pad, format->which); -+ -+ /* -+ * Align to two to simplify the binning calculations below, and clamp -+ * the requested format at the crop rectangle -+ */ -+ width = clamp_t(unsigned int, ALIGN(format->format.width, 2), -+ OV5693_MIN_CROP_WIDTH, crop->width); -+ height = clamp_t(unsigned int, ALIGN(format->format.height, 2), -+ OV5693_MIN_CROP_HEIGHT, crop->height); -+ -+ /* -+ * We can only support setting either the dimensions of the crop rect -+ * or those dimensions binned (separately) by a factor of two. -+ */ -+ hratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->width, width), 1, 2); -+ vratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->height, height), 1, 2); -+ -+ fmt = __ov5693_get_pad_format(ov5693, cfg, format->pad, format->which); -+ -+ fmt->width = crop->width / hratio; -+ fmt->height = crop->height / vratio; -+ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ -+ format->format = *fmt; -+ -+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) -+ return ret; -+ -+ mutex_lock(&ov5693->lock); -+ -+ ov5693->mode.binning_x = hratio > 1 ? true : false; -+ ov5693->mode.inc_x_odd = hratio > 1 ? 3 : 1; -+ ov5693->mode.binning_y = vratio > 1 ? true : false; -+ ov5693->mode.inc_y_odd = vratio > 1 ? 3 : 1; -+ -+ ov5693->mode.vts = __ov5693_calc_vts(fmt->height); -+ -+ __v4l2_ctrl_modify_range(ov5693->ctrls.vblank, -+ OV5693_TIMING_MIN_VTS, -+ OV5693_TIMING_MAX_VTS - fmt->height, -+ 1, ov5693->mode.vts - fmt->height); -+ __v4l2_ctrl_s_ctrl(ov5693->ctrls.vblank, -+ ov5693->mode.vts - fmt->height); -+ -+ hblank = OV5693_FIXED_PPL - fmt->width; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.hblank, hblank, hblank, 1, -+ hblank); -+ -+ exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, -+ ov5693->ctrls.exposure->minimum, exposure_max, -+ ov5693->ctrls.exposure->step, -+ min(ov5693->ctrls.exposure->val, exposure_max)); -+ -+ mutex_unlock(&ov5693->lock); -+ return ret; -+} -+ -+static int ov5693_get_selection(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ switch (sel->target) { -+ case V4L2_SEL_TGT_CROP: -+ mutex_lock(&ov5693->lock); -+ sel->r = *__ov5693_get_pad_crop(ov5693, cfg, sel->pad, -+ sel->which); -+ mutex_unlock(&ov5693->lock); -+ break; -+ case V4L2_SEL_TGT_NATIVE_SIZE: -+ sel->r.top = 0; -+ sel->r.left = 0; -+ sel->r.width = OV5693_NATIVE_WIDTH; -+ sel->r.height = OV5693_NATIVE_HEIGHT; -+ break; -+ case V4L2_SEL_TGT_CROP_BOUNDS: -+ case V4L2_SEL_TGT_CROP_DEFAULT: -+ sel->r.top = OV5693_ACTIVE_START_TOP; -+ sel->r.left = OV5693_ACTIVE_START_LEFT; -+ sel->r.width = OV5693_ACTIVE_WIDTH; -+ sel->r.height = OV5693_ACTIVE_HEIGHT; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int ov5693_set_selection(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(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 boundaries of the crop rectangle to the size of the sensor -+ * pixel array. Align to multiples of 2 to ensure Bayer pattern isn't -+ * disrupted. -+ */ -+ rect.left = clamp(ALIGN(sel->r.left, 2), OV5693_NATIVE_START_LEFT, -+ OV5693_NATIVE_WIDTH); -+ rect.top = clamp(ALIGN(sel->r.top, 2), OV5693_NATIVE_START_TOP, -+ OV5693_NATIVE_HEIGHT); -+ rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), -+ OV5693_MIN_CROP_WIDTH, OV5693_NATIVE_WIDTH); -+ rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), -+ OV5693_MIN_CROP_HEIGHT, OV5693_NATIVE_HEIGHT); -+ -+ /* Make sure the crop rectangle isn't outside the bounds of the array */ -+ rect.width = min_t(unsigned int, rect.width, -+ OV5693_NATIVE_WIDTH - rect.left); -+ rect.height = min_t(unsigned int, rect.height, -+ OV5693_NATIVE_HEIGHT - rect.top); -+ -+ __crop = __ov5693_get_pad_crop(ov5693, cfg, sel->pad, sel->which); -+ -+ if (rect.width != __crop->width || rect.height != __crop->height) { -+ /* -+ * Reset the output image size if the crop rectangle size has -+ * been modified. -+ */ -+ format = __ov5693_get_pad_format(ov5693, cfg, sel->pad, sel->which); -+ format->width = rect.width; -+ format->height = rect.height; -+ } -+ -+ *__crop = rect; -+ sel->r = rect; -+ -+ return 0; -+} -+ -+static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ int ret; -+ -+ if (enable) { -+ ret = pm_runtime_get_sync(ov5693->dev); -+ if (ret < 0) -+ goto err_power_down; -+ -+ ret = __v4l2_ctrl_handler_setup(&ov5693->ctrls.handler); -+ if (ret) -+ goto err_power_down; -+ } -+ -+ mutex_lock(&ov5693->lock); -+ ret = ov5693_sw_standby(ov5693, !enable); -+ mutex_unlock(&ov5693->lock); -+ -+ if (ret) -+ goto err_power_down; -+ ov5693->streaming = !!enable; -+ -+ if (!enable) -+ pm_runtime_put(ov5693->dev); -+ -+ return 0; -+err_power_down: -+ pm_runtime_put_noidle(ov5693->dev); -+ return ret; -+} -+ -+static int ov5693_g_frame_interval(struct v4l2_subdev *sd, -+ struct v4l2_subdev_frame_interval *interval) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ unsigned int framesize = OV5693_FIXED_PPL * (ov5693->mode.format.height + -+ ov5693->ctrls.vblank->val); -+ unsigned int fps = DIV_ROUND_CLOSEST(OV5693_PIXEL_RATE, framesize); -+ -+ interval->interval.numerator = 1; -+ interval->interval.denominator = fps; -+ -+ return 0; -+} -+ -+static int ov5693_enum_mbus_code(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_mbus_code_enum *code) -+{ -+ /* Only a single mbus format is supported */ -+ if (code->index > 0) -+ return -EINVAL; -+ -+ code->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ return 0; -+} -+ -+static int ov5693_enum_frame_size(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_frame_size_enum *fse) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ struct v4l2_rect *__crop; -+ -+ if (fse->index > 1 || fse->code != MEDIA_BUS_FMT_SBGGR10_1X10) -+ return -EINVAL; -+ -+ __crop = __ov5693_get_pad_crop(ov5693, cfg, fse->pad, fse->which); -+ if (!__crop) -+ return -EINVAL; -+ -+ fse->min_width = __crop->width / (fse->index + 1); -+ fse->min_height = __crop->height / (fse->index + 1); -+ fse->max_width = fse->min_width; -+ fse->max_height = fse->min_height; -+ -+ return 0; -+} -+ -+static const struct v4l2_subdev_video_ops ov5693_video_ops = { -+ .s_stream = ov5693_s_stream, -+ .g_frame_interval = ov5693_g_frame_interval, -+}; -+ -+static const struct v4l2_subdev_pad_ops ov5693_pad_ops = { -+ .enum_mbus_code = ov5693_enum_mbus_code, -+ .enum_frame_size = ov5693_enum_frame_size, -+ .get_fmt = ov5693_get_fmt, -+ .set_fmt = ov5693_set_fmt, -+ .get_selection = ov5693_get_selection, -+ .set_selection = ov5693_set_selection, -+}; -+ -+static const struct v4l2_subdev_ops ov5693_ops = { -+ .video = &ov5693_video_ops, -+ .pad = &ov5693_pad_ops, -+}; -+ -+/* Sensor and Driver Configuration Functions */ -+ -+static int ov5693_init_controls(struct ov5693_device *ov5693) -+{ -+ const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; -+ struct v4l2_fwnode_device_properties props; -+ int vblank_max, vblank_def; -+ int exposure_max; -+ int hblank; -+ int ret; -+ -+ ret = v4l2_ctrl_handler_init(&ov5693->ctrls.handler, 12); -+ if (ret) -+ return ret; -+ -+ /* link freq */ -+ ov5693->ctrls.link_freq = v4l2_ctrl_new_int_menu(&ov5693->ctrls.handler, -+ NULL, V4L2_CID_LINK_FREQ, -+ 0, 0, link_freq_menu_items); -+ if (ov5693->ctrls.link_freq) -+ ov5693->ctrls.link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ -+ /* pixel rate */ -+ ov5693->ctrls.pixel_rate = v4l2_ctrl_new_std(&ov5693->ctrls.handler, NULL, -+ V4L2_CID_PIXEL_RATE, 0, -+ OV5693_PIXEL_RATE, 1, -+ OV5693_PIXEL_RATE); -+ -+ /* Exposure */ -+ exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN; -+ ov5693->ctrls.exposure = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_EXPOSURE, -+ OV5693_EXPOSURE_MIN, -+ exposure_max, -+ OV5693_EXPOSURE_STEP, -+ exposure_max); -+ -+ /* Gain */ -+ ov5693->ctrls.analogue_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, -+ ops, V4L2_CID_ANALOGUE_GAIN, -+ OV5693_GAIN_MIN, -+ OV5693_GAIN_MAX, -+ OV5693_GAIN_STEP, -+ OV5693_GAIN_DEF); -+ -+ ov5693->ctrls.digital_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_DIGITAL_GAIN, -+ OV5693_DIGITAL_GAIN_MIN, -+ OV5693_DIGITAL_GAIN_MAX, -+ OV5693_DIGITAL_GAIN_STEP, -+ OV5693_DIGITAL_GAIN_DEF); -+ -+ /* Flip */ -+ ov5693->ctrls.hflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_HFLIP, 0, 1, 1, 0); -+ -+ ov5693->ctrls.vflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_VFLIP, 0, 1, 1, 0); -+ -+ hblank = OV5693_FIXED_PPL - ov5693->mode.format.width; -+ ov5693->ctrls.hblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_HBLANK, hblank, -+ hblank, 1, hblank); -+ -+ if (ov5693->ctrls.hblank) -+ ov5693->ctrls.hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ -+ vblank_max = OV5693_TIMING_MAX_VTS - ov5693->mode.format.height; -+ vblank_def = ov5693->mode.vts - ov5693->mode.format.height; -+ ov5693->ctrls.vblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_VBLANK, -+ OV5693_TIMING_MIN_VTS, -+ vblank_max, 1, vblank_def); -+ -+ ov5693->ctrls.test_pattern = v4l2_ctrl_new_std_menu_items( -+ &ov5693->ctrls.handler, ops, -+ V4L2_CID_TEST_PATTERN, -+ ARRAY_SIZE(ov5693_test_pattern_menu) - 1, -+ 0, 0, ov5693_test_pattern_menu); -+ -+ if (ov5693->ctrls.handler.error) { -+ dev_err(ov5693->dev, "Error initialising v4l2 ctrls\n"); -+ ret = ov5693->ctrls.handler.error; -+ goto err_free_handler; -+ } -+ -+ /* set properties from fwnode (e.g. rotation, orientation) */ -+ ret = v4l2_fwnode_device_parse(ov5693->dev, &props); -+ if (ret) -+ goto err_free_handler; -+ -+ ret = v4l2_ctrl_new_fwnode_properties(&ov5693->ctrls.handler, ops, -+ &props); -+ if (ret) -+ goto err_free_handler; -+ -+ /* Use same lock for controls as for everything else. */ -+ ov5693->ctrls.handler.lock = &ov5693->lock; -+ ov5693->sd.ctrl_handler = &ov5693->ctrls.handler; -+ -+ return 0; -+ -+err_free_handler: -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ return ret; -+} -+ -+static int ov5693_configure_gpios(struct ov5693_device *ov5693) -+{ -+ ov5693->reset = devm_gpiod_get_optional(ov5693->dev, "reset", -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->reset)) { -+ dev_err(ov5693->dev, "Error fetching reset GPIO\n"); -+ return PTR_ERR(ov5693->reset); -+ } -+ -+ ov5693->powerdown = devm_gpiod_get_optional(ov5693->dev, "powerdown", -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->powerdown)) { -+ dev_err(ov5693->dev, "Error fetching powerdown GPIO\n"); -+ return PTR_ERR(ov5693->powerdown); -+ } -+ -+ return 0; -+} -+ -+static int ov5693_get_regulators(struct ov5693_device *ov5693) -+{ -+ unsigned int i; -+ -+ for (i = 0; i < OV5693_NUM_SUPPLIES; i++) -+ ov5693->supplies[i].supply = ov5693_supply_names[i]; -+ -+ return devm_regulator_bulk_get(ov5693->dev, OV5693_NUM_SUPPLIES, -+ ov5693->supplies); -+} -+ -+static int ov5693_probe(struct i2c_client *client) -+{ -+ struct fwnode_handle *fwnode = dev_fwnode(&client->dev); -+ struct fwnode_handle *endpoint; -+ struct ov5693_device *ov5693; -+ u32 clk_rate; -+ int ret = 0; -+ -+ endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); -+ if (!endpoint && !IS_ERR_OR_NULL(fwnode->secondary)) -+ endpoint = fwnode_graph_get_next_endpoint(fwnode->secondary, NULL); -+ if (!endpoint) -+ return -EPROBE_DEFER; -+ -+ ov5693 = devm_kzalloc(&client->dev, sizeof(*ov5693), GFP_KERNEL); -+ if (!ov5693) -+ return -ENOMEM; -+ -+ ov5693->client = client; -+ ov5693->dev = &client->dev; -+ -+ mutex_init(&ov5693->lock); -+ -+ v4l2_i2c_subdev_init(&ov5693->sd, client, &ov5693_ops); -+ -+ ov5693->clk = devm_clk_get(&client->dev, "xvclk"); -+ if (IS_ERR(ov5693->clk)) { -+ dev_err(&client->dev, "Error getting clock\n"); -+ return PTR_ERR(ov5693->clk); -+ } -+ -+ clk_rate = clk_get_rate(ov5693->clk); -+ if (clk_rate != OV5693_XVCLK_FREQ) { -+ dev_err(&client->dev, "Unsupported clk freq %u, expected %u\n", -+ clk_rate, OV5693_XVCLK_FREQ); -+ return -EINVAL; -+ } -+ -+ ret = ov5693_configure_gpios(ov5693); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_get_regulators(ov5693); -+ if (ret) { -+ dev_err(&client->dev, "Error fetching regulators\n"); -+ return ret; -+ } -+ -+ ov5693->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; -+ ov5693->pad.flags = MEDIA_PAD_FL_SOURCE; -+ ov5693->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; -+ -+ ov5693->mode.crop = ov5693_default_crop; -+ ov5693->mode.format = ov5693_default_fmt; -+ ov5693->mode.vts = __ov5693_calc_vts(ov5693->mode.format.height); -+ -+ ret = ov5693_init_controls(ov5693); -+ if (ret) -+ return ret; -+ -+ ret = media_entity_pads_init(&ov5693->sd.entity, 1, &ov5693->pad); -+ if (ret) -+ goto err_ctrl_handler_free; -+ -+ /* -+ * We need the driver to work in the event that pm runtime is disable in -+ * the kernel, so power up and verify the chip now. In the event that -+ * runtime pm is disabled this will leave the chip on, so that streaming -+ * will work. -+ */ -+ -+ ret = ov5693_sensor_powerup(ov5693); -+ if (ret) -+ goto err_media_entity_cleanup; -+ -+ ret = ov5693_detect(ov5693); -+ if (ret) -+ goto err_powerdown; -+ -+ pm_runtime_set_active(&client->dev); -+ pm_runtime_get_noresume(&client->dev); -+ pm_runtime_enable(&client->dev); -+ -+ ret = v4l2_async_register_subdev_sensor_common(&ov5693->sd); -+ if (ret) { -+ dev_err(&client->dev, "failed to register V4L2 subdev: %d", -+ ret); -+ goto err_pm_runtime; -+ } -+ -+ pm_runtime_set_autosuspend_delay(&client->dev, 1000); -+ pm_runtime_use_autosuspend(&client->dev); -+ pm_runtime_put_autosuspend(&client->dev); -+ -+ return ret; -+ -+err_pm_runtime: -+ pm_runtime_disable(&client->dev); -+ pm_runtime_put_noidle(&client->dev); -+err_powerdown: -+ ov5693_sensor_powerdown(ov5693); -+err_media_entity_cleanup: -+ media_entity_cleanup(&ov5693->sd.entity); -+err_ctrl_handler_free: -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ -+ return ret; -+} -+ -+static int ov5693_remove(struct i2c_client *client) -+{ -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ v4l2_async_unregister_subdev(sd); -+ media_entity_cleanup(&ov5693->sd.entity); -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ mutex_destroy(&ov5693->lock); -+ -+ /* -+ * Disable runtime PM. In case runtime PM is disabled in the kernel, -+ * make sure to turn power off manually. -+ */ -+ pm_runtime_disable(&client->dev); -+ if (!pm_runtime_status_suspended(&client->dev)) -+ ov5693_sensor_powerdown(ov5693); -+ pm_runtime_set_suspended(&client->dev); -+ -+ return 0; -+} -+ -+static const struct dev_pm_ops ov5693_pm_ops = { -+ SET_RUNTIME_PM_OPS(ov5693_sensor_suspend, ov5693_sensor_resume, NULL) -+}; -+ -+static const struct acpi_device_id ov5693_acpi_match[] = { -+ {"INT33BE"}, -+ {}, -+}; -+MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); -+ -+static struct i2c_driver ov5693_driver = { -+ .driver = { -+ .name = "ov5693", -+ .acpi_match_table = ov5693_acpi_match, -+ .pm = &ov5693_pm_ops, -+ }, -+ .probe_new = ov5693_probe, -+ .remove = ov5693_remove, -+}; -+module_i2c_driver(ov5693_driver); -+ -+MODULE_DESCRIPTION("A low-level driver for OmniVision 5693 sensors"); -+MODULE_LICENSE("GPL"); --- -2.32.0 - -From a3f2f62b38c961c6440adc29c88533ec2a9bb108 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Fri, 22 Jan 2021 20:58:13 +0100 -Subject: [PATCH] cio2-bridge: Parse sensor orientation and rotation -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -The sensor orientation is read from the _PLC ACPI buffer and converted -to a v4l2 format. - -See https://uefi.org/sites/default/files/resources/ACPI_6_3_final_Jan30.pdf -page 351 for a definition of the Panel property. - -The sensor rotation is read from the SSDB ACPI buffer and converted into -degrees. - -Signed-off-by: Fabian Wüthrich -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 45 ++++++++++++++++++++-- - drivers/media/pci/intel/ipu3/cio2-bridge.h | 3 ++ - 2 files changed, 44 insertions(+), 4 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index c2199042d3db..9a8a4a55d6a7 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -29,6 +29,7 @@ static const struct cio2_sensor_config cio2_supported_sensors[] = { - static const struct cio2_property_names prop_names = { - .clock_frequency = "clock-frequency", - .rotation = "rotation", -+ .orientation = "orientation", - .bus_type = "bus-type", - .data_lanes = "data-lanes", - .remote_endpoint = "remote-endpoint", -@@ -72,11 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, - return ret; - } - -+static u32 cio2_bridge_parse_rotation(u8 rotation) -+{ -+ if (rotation == 1) -+ return 180; -+ return 0; -+} -+ -+static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) -+{ -+ switch (panel) { -+ case 4: -+ return V4L2_FWNODE_ORIENTATION_FRONT; -+ case 5: -+ return V4L2_FWNODE_ORIENTATION_BACK; -+ default: -+ return V4L2_FWNODE_ORIENTATION_EXTERNAL; -+ } -+} -+ - static void cio2_bridge_create_fwnode_properties( - struct cio2_sensor *sensor, - struct cio2_bridge *bridge, - const struct cio2_sensor_config *cfg) - { -+ u32 rotation; -+ enum v4l2_fwnode_orientation orientation; -+ -+ rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); -+ orientation = cio2_bridge_parse_orientation(sensor->pld->panel); -+ - sensor->prop_names = prop_names; - - sensor->local_ref[0].node = &sensor->swnodes[SWNODE_CIO2_ENDPOINT]; -@@ -85,9 +111,12 @@ static void cio2_bridge_create_fwnode_properties( - sensor->dev_properties[0] = PROPERTY_ENTRY_U32( - sensor->prop_names.clock_frequency, - sensor->ssdb.mclkspeed); -- sensor->dev_properties[1] = PROPERTY_ENTRY_U8( -+ sensor->dev_properties[1] = PROPERTY_ENTRY_U32( - sensor->prop_names.rotation, -- sensor->ssdb.degree); -+ rotation); -+ sensor->dev_properties[2] = PROPERTY_ENTRY_U32( -+ sensor->prop_names.orientation, -+ orientation); - - sensor->ep_properties[0] = PROPERTY_ENTRY_U32( - sensor->prop_names.bus_type, -@@ -159,6 +188,7 @@ static void cio2_bridge_unregister_sensors(struct cio2_bridge *bridge) - for (i = 0; i < bridge->n_sensors; i++) { - sensor = &bridge->sensors[i]; - software_node_unregister_nodes(sensor->swnodes); -+ ACPI_FREE(sensor->pld); - acpi_dev_put(sensor->adev); - } - } -@@ -170,6 +200,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - struct fwnode_handle *fwnode; - struct cio2_sensor *sensor; - struct acpi_device *adev; -+ acpi_status status; - int ret; - - for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { -@@ -193,11 +224,15 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - if (ret) - goto err_put_adev; - -+ status = acpi_get_physical_device_location(adev->handle, &sensor->pld); -+ if (ACPI_FAILURE(status)) -+ goto err_put_adev; -+ - if (sensor->ssdb.lanes > CIO2_MAX_LANES) { - dev_err(&adev->dev, - "Number of lanes in SSDB is invalid\n"); - ret = -EINVAL; -- goto err_put_adev; -+ goto err_free_pld; - } - - cio2_bridge_create_fwnode_properties(sensor, bridge, cfg); -@@ -205,7 +240,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - - ret = software_node_register_nodes(sensor->swnodes); - if (ret) -- goto err_put_adev; -+ goto err_free_pld; - - fwnode = software_node_fwnode(&sensor->swnodes[ - SWNODE_SENSOR_HID]); -@@ -226,6 +261,8 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - - err_free_swnodes: - software_node_unregister_nodes(sensor->swnodes); -+err_free_pld: -+ ACPI_FREE(sensor->pld); - err_put_adev: - acpi_dev_put(sensor->adev); - err_out: -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index dd0ffcafa489..924d99d20328 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -80,6 +80,7 @@ struct cio2_sensor_ssdb { - struct cio2_property_names { - char clock_frequency[16]; - char rotation[9]; -+ char orientation[12]; - char bus_type[9]; - char data_lanes[11]; - char remote_endpoint[16]; -@@ -106,6 +107,8 @@ struct cio2_sensor { - struct cio2_node_names node_names; - - struct cio2_sensor_ssdb ssdb; -+ struct acpi_pld_info *pld; -+ - struct cio2_property_names prop_names; - struct property_entry ep_properties[5]; - struct property_entry dev_properties[3]; --- -2.32.0 - -From 997ee8fc601da7743d0c9abe4d0ac1dddec0327e Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Sun, 24 Jan 2021 11:07:42 +0100 -Subject: [PATCH] cio2-bridge: Use macros and add warnings -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Use macros for the _PLD panel as defined in the ACPI spec 6.3 and emit -a warning if we see an unknown value. - -Signed-off-by: Fabian Wüthrich -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 33 ++++++++++++++++------ - drivers/media/pci/intel/ipu3/cio2-bridge.h | 13 +++++++++ - 2 files changed, 37 insertions(+), 9 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index 9a8a4a55d6a7..503809907b92 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -73,21 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, - return ret; - } - --static u32 cio2_bridge_parse_rotation(u8 rotation) -+static u32 cio2_bridge_parse_rotation(struct cio2_sensor *sensor) - { -- if (rotation == 1) -+ switch (sensor->ssdb.degree) { -+ case CIO2_SENSOR_ROTATION_NORMAL: -+ return 0; -+ case CIO2_SENSOR_ROTATION_INVERTED: - return 180; -- return 0; -+ default: -+ dev_warn(&sensor->adev->dev, -+ "Unknown rotation %d. Assume 0 degree rotation\n", -+ sensor->ssdb.degree); -+ return 0; -+ } - } - --static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) -+static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(struct cio2_sensor *sensor) - { -- switch (panel) { -- case 4: -+ switch (sensor->pld->panel) { -+ case CIO2_PLD_PANEL_FRONT: - return V4L2_FWNODE_ORIENTATION_FRONT; -- case 5: -+ case CIO2_PLD_PANEL_BACK: - return V4L2_FWNODE_ORIENTATION_BACK; -+ case CIO2_PLD_PANEL_TOP: -+ case CIO2_PLD_PANEL_LEFT: -+ case CIO2_PLD_PANEL_RIGHT: -+ case CIO2_PLD_PANEL_UNKNOWN: -+ return V4L2_FWNODE_ORIENTATION_EXTERNAL; - default: -+ dev_warn(&sensor->adev->dev, "Unknown _PLD panel value %d\n", -+ sensor->pld->panel); - return V4L2_FWNODE_ORIENTATION_EXTERNAL; - } - } -@@ -100,8 +115,8 @@ static void cio2_bridge_create_fwnode_properties( - u32 rotation; - enum v4l2_fwnode_orientation orientation; - -- rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); -- orientation = cio2_bridge_parse_orientation(sensor->pld->panel); -+ rotation = cio2_bridge_parse_rotation(sensor); -+ orientation = cio2_bridge_parse_orientation(sensor); - - sensor->prop_names = prop_names; - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index 924d99d20328..e1e388cc9f45 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -12,6 +12,19 @@ - #define CIO2_MAX_LANES 4 - #define MAX_NUM_LINK_FREQS 3 - -+/* Values are estimated guesses as we don't have a spec */ -+#define CIO2_SENSOR_ROTATION_NORMAL 0 -+#define CIO2_SENSOR_ROTATION_INVERTED 1 -+ -+/* Panel position defined in _PLD section of ACPI Specification 6.3 */ -+#define CIO2_PLD_PANEL_TOP 0 -+#define CIO2_PLD_PANEL_BOTTOM 1 -+#define CIO2_PLD_PANEL_LEFT 2 -+#define CIO2_PLD_PANEL_RIGHT 3 -+#define CIO2_PLD_PANEL_FRONT 4 -+#define CIO2_PLD_PANEL_BACK 5 -+#define CIO2_PLD_PANEL_UNKNOWN 6 -+ - #define CIO2_SENSOR_CONFIG(_HID, _NR, ...) \ - (const struct cio2_sensor_config) { \ - .hid = _HID, \ --- -2.32.0 - -From a0254719dee6757d140de3a409e80e5dd0a516d7 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Thu, 6 May 2021 07:52:44 +0200 -Subject: [PATCH] cio2-bridge: Use correct dev_properties size - -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index e1e388cc9f45..deaf5804f70d 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -124,7 +124,7 @@ struct cio2_sensor { - - struct cio2_property_names prop_names; - struct property_entry ep_properties[5]; -- struct property_entry dev_properties[3]; -+ struct property_entry dev_properties[4]; - struct property_entry cio2_properties[3]; - struct software_node_ref_args local_ref[1]; - struct software_node_ref_args remote_ref[1]; --- -2.32.0 - -From 7196d9fff983f3b33d361e58cf5aef01e235808a Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 20 May 2021 23:31:04 +0100 -Subject: [PATCH] media: i2c: Fix vertical flip in ov5693 - -The pinkness experienced by users with rotated sensors in their laptops -was due to an incorrect setting for the vertical flip function; the -datasheet for the sensor gives the settings as bits 1&2 in one place and -bits 1&6 in another. - -Switch to flipping bit 6 instead of bit 2 for 0x3820 in the vertical -flip function to fix the pink hue. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index da2ca99a7ad3..52279eeffc54 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -133,7 +133,7 @@ - #define OV5693_SUB_INC_Y_REG 0x3815 - - #define OV5693_FORMAT1_REG 0x3820 --#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -+#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(6) - #define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) - #define OV5693_FORMAT1_VBIN_EN BIT(0) - #define OV5693_FORMAT2_REG 0x3821 --- -2.32.0 - diff --git a/patches/5.12/0011-s0ix-amd.patch b/patches/5.12/0011-s0ix-amd.patch deleted file mode 100644 index fd11247ea..000000000 --- a/patches/5.12/0011-s0ix-amd.patch +++ /dev/null @@ -1,1228 +0,0 @@ -From 3a7422993f0471af14c1a92493659fe2196e1619 Mon Sep 17 00:00:00 2001 -From: Mario Limonciello -Date: Wed, 12 May 2021 17:15:14 -0500 -Subject: [PATCH] ACPI: processor idle: Fix up C-state latency if not ordered - -Generally, the C-state latency is provided by the _CST method or -FADT, but some OEM platforms using AMD Picasso, Renoir, Van Gogh, -and Cezanne set the C2 latency greater than C3's which causes the -C2 state to be skipped. - -That will block the core entering PC6, which prevents S0ix working -properly on Linux systems. - -In other operating systems, the latency values are not validated and -this does not cause problems by skipping states. - -To avoid this issue on Linux, detect when latencies are not an -arithmetic progression and sort them. - -Link: https://gitlab.freedesktop.org/agd5f/linux/-/commit/026d186e4592c1ee9c1cb44295912d0294508725 -Link: https://gitlab.freedesktop.org/drm/amd/-/issues/1230#note_712174 -Suggested-by: Prike Liang -Suggested-by: Alex Deucher -Signed-off-by: Mario Limonciello -[ rjw: Subject and changelog edits ] -Signed-off-by: Rafael J. Wysocki -Patchset: s0ix-amd ---- - drivers/acpi/processor_idle.c | 40 +++++++++++++++++++++++++++++++++++ - 1 file changed, 40 insertions(+) - -diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c -index 4e2d76b8b697..6790df5a2462 100644 ---- a/drivers/acpi/processor_idle.c -+++ b/drivers/acpi/processor_idle.c -@@ -16,6 +16,7 @@ - #include - #include - #include /* need_resched() */ -+#include - #include - #include - #include -@@ -388,10 +389,37 @@ static void acpi_processor_power_verify_c3(struct acpi_processor *pr, - return; - } - -+static int acpi_cst_latency_cmp(const void *a, const void *b) -+{ -+ const struct acpi_processor_cx *x = a, *y = b; -+ -+ if (!(x->valid && y->valid)) -+ return 0; -+ if (x->latency > y->latency) -+ return 1; -+ if (x->latency < y->latency) -+ return -1; -+ return 0; -+} -+static void acpi_cst_latency_swap(void *a, void *b, int n) -+{ -+ struct acpi_processor_cx *x = a, *y = b; -+ u32 tmp; -+ -+ if (!(x->valid && y->valid)) -+ return; -+ tmp = x->latency; -+ x->latency = y->latency; -+ y->latency = tmp; -+} -+ - static int acpi_processor_power_verify(struct acpi_processor *pr) - { - unsigned int i; - unsigned int working = 0; -+ unsigned int last_latency = 0; -+ unsigned int last_type = 0; -+ bool buggy_latency = false; - - pr->power.timer_broadcast_on_state = INT_MAX; - -@@ -415,12 +443,24 @@ static int acpi_processor_power_verify(struct acpi_processor *pr) - } - if (!cx->valid) - continue; -+ if (cx->type >= last_type && cx->latency < last_latency) -+ buggy_latency = true; -+ last_latency = cx->latency; -+ last_type = cx->type; - - lapic_timer_check_state(i, pr, cx); - tsc_check_state(cx->type); - working++; - } - -+ if (buggy_latency) { -+ pr_notice("FW issue: working around C-state latencies out of order\n"); -+ sort(&pr->power.states[1], max_cstate, -+ sizeof(struct acpi_processor_cx), -+ acpi_cst_latency_cmp, -+ acpi_cst_latency_swap); -+ } -+ - lapic_timer_propagate_broadcast(pr); - - return (working); --- -2.32.0 - -From 07ee2c4f1c48d74d7971dd017df43edd95582898 Mon Sep 17 00:00:00 2001 -From: Marcin Bachry -Date: Tue, 16 Mar 2021 15:28:51 -0400 -Subject: [PATCH] PCI: quirks: Quirk PCI d3hot delay for AMD xhci - -Renoir needs a similar delay. - -Signed-off-by: Marcin Bachry -Signed-off-by: Alex Deucher -Patchset: s0ix-amd ---- - drivers/pci/quirks.c | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c -index 7bf76bca888d..8e70605666d4 100644 ---- a/drivers/pci/quirks.c -+++ b/drivers/pci/quirks.c -@@ -1904,6 +1904,9 @@ static void quirk_ryzen_xhci_d3hot(struct pci_dev *dev) - } - DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x15e0, quirk_ryzen_xhci_d3hot); - DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x15e1, quirk_ryzen_xhci_d3hot); -+/* Renoir XHCI requires longer delay when transitioning from D0 to -+ * D3hot */ -+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1639, quirk_ryzen_xhci_d3hot); - - #ifdef CONFIG_X86_IO_APIC - static int dmi_disable_ioapicreroute(const struct dmi_system_id *d) --- -2.32.0 - -From 5dd9cee2331182d21222220425e1f4d354f4342f Mon Sep 17 00:00:00 2001 -From: Mario Limonciello -Date: Fri, 28 May 2021 11:02:34 -0500 -Subject: [PATCH] nvme-pci: look for StorageD3Enable on companion ACPI device - instead - -The documentation around the StorageD3Enable property hints that it -should be made on the PCI device. This is where newer AMD systems set -the property and it's required for S0i3 support. - -So rather than look for nodes of the root port only present on Intel -systems, switch to the companion ACPI device for all systems. -David Box from Intel indicated this should work on Intel as well. - -Link: https://lore.kernel.org/linux-nvme/YK6gmAWqaRmvpJXb@google.com/T/#m900552229fa455867ee29c33b854845fce80ba70 -Link: https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/power-management-for-storage-hardware-devices-intro -Fixes: df4f9bc4fb9c ("nvme-pci: add support for ACPI StorageD3Enable property") -Suggested-by: Liang Prike -Acked-by: Raul E Rangel -Signed-off-by: Mario Limonciello -Reviewed-by: David E. Box -Signed-off-by: Christoph Hellwig -Patchset: s0ix-amd ---- - drivers/nvme/host/pci.c | 24 +----------------------- - 1 file changed, 1 insertion(+), 23 deletions(-) - -diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c -index c92a15c3fbc5..60c1c83e03fa 100644 ---- a/drivers/nvme/host/pci.c -+++ b/drivers/nvme/host/pci.c -@@ -2834,10 +2834,7 @@ static unsigned long check_vendor_combination_bug(struct pci_dev *pdev) - #ifdef CONFIG_ACPI - static bool nvme_acpi_storage_d3(struct pci_dev *dev) - { -- struct acpi_device *adev; -- struct pci_dev *root; -- acpi_handle handle; -- acpi_status status; -+ struct acpi_device *adev = ACPI_COMPANION(&dev->dev); - u8 val; - - /* -@@ -2845,28 +2842,9 @@ static bool nvme_acpi_storage_d3(struct pci_dev *dev) - * must use D3 to support deep platform power savings during - * suspend-to-idle. - */ -- root = pcie_find_root_port(dev); -- if (!root) -- return false; - -- adev = ACPI_COMPANION(&root->dev); - if (!adev) - return false; -- -- /* -- * The property is defined in the PXSX device for South complex ports -- * and in the PEGP device for North complex ports. -- */ -- status = acpi_get_handle(adev->handle, "PXSX", &handle); -- if (ACPI_FAILURE(status)) { -- status = acpi_get_handle(adev->handle, "PEGP", &handle); -- if (ACPI_FAILURE(status)) -- return false; -- } -- -- if (acpi_bus_get_device(handle, &adev)) -- return false; -- - if (fwnode_property_read_u8(acpi_fwnode_handle(adev), "StorageD3Enable", - &val)) - return false; --- -2.32.0 - -From c5039db994a0f6de250b3039bd94d2e610f66ab7 Mon Sep 17 00:00:00 2001 -From: Mario Limonciello -Date: Wed, 9 Jun 2021 13:40:17 -0500 -Subject: [PATCH] ACPI: Check StorageD3Enable _DSD property in ACPI code - -Although first implemented for NVME, this check may be usable by -other drivers as well. Microsoft's specification explicitly mentions -that is may be usable by SATA and AHCI devices. Google also indicates -that they have used this with SDHCI in a downstream kernel tree that -a user can plug a storage device into. - -Link: https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/power-management-for-storage-hardware-devices-intro -Suggested-by: Keith Busch -CC: Shyam-sundar S-k -CC: Alexander Deucher -CC: Rafael J. Wysocki -CC: Prike Liang -Signed-off-by: Mario Limonciello -Reviewed-by: Rafael J. Wysocki -Signed-off-by: Christoph Hellwig -Patchset: s0ix-amd ---- - drivers/acpi/device_pm.c | 29 +++++++++++++++++++++++++++++ - drivers/nvme/host/pci.c | 28 +--------------------------- - include/linux/acpi.h | 5 +++++ - 3 files changed, 35 insertions(+), 27 deletions(-) - -diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c -index 58876248b192..1e278785c7db 100644 ---- a/drivers/acpi/device_pm.c -+++ b/drivers/acpi/device_pm.c -@@ -1337,4 +1337,33 @@ int acpi_dev_pm_attach(struct device *dev, bool power_on) - return 1; - } - EXPORT_SYMBOL_GPL(acpi_dev_pm_attach); -+ -+/** -+ * acpi_storage_d3 - Check if D3 should be used in the suspend path -+ * @dev: Device to check -+ * -+ * Return %true if the platform firmware wants @dev to be programmed -+ * into D3hot or D3cold (if supported) in the suspend path, or %false -+ * when there is no specific preference. On some platforms, if this -+ * hint is ignored, @dev may remain unresponsive after suspending the -+ * platform as a whole. -+ * -+ * Although the property has storage in the name it actually is -+ * applied to the PCIe slot and plugging in a non-storage device the -+ * same platform restrictions will likely apply. -+ */ -+bool acpi_storage_d3(struct device *dev) -+{ -+ struct acpi_device *adev = ACPI_COMPANION(dev); -+ u8 val; -+ -+ if (!adev) -+ return false; -+ if (fwnode_property_read_u8(acpi_fwnode_handle(adev), "StorageD3Enable", -+ &val)) -+ return false; -+ return val == 1; -+} -+EXPORT_SYMBOL_GPL(acpi_storage_d3); -+ - #endif /* CONFIG_PM */ -diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c -index 60c1c83e03fa..8593161d4da0 100644 ---- a/drivers/nvme/host/pci.c -+++ b/drivers/nvme/host/pci.c -@@ -2831,32 +2831,6 @@ static unsigned long check_vendor_combination_bug(struct pci_dev *pdev) - return 0; - } - --#ifdef CONFIG_ACPI --static bool nvme_acpi_storage_d3(struct pci_dev *dev) --{ -- struct acpi_device *adev = ACPI_COMPANION(&dev->dev); -- u8 val; -- -- /* -- * Look for _DSD property specifying that the storage device on the port -- * must use D3 to support deep platform power savings during -- * suspend-to-idle. -- */ -- -- if (!adev) -- return false; -- if (fwnode_property_read_u8(acpi_fwnode_handle(adev), "StorageD3Enable", -- &val)) -- return false; -- return val == 1; --} --#else --static inline bool nvme_acpi_storage_d3(struct pci_dev *dev) --{ -- return false; --} --#endif /* CONFIG_ACPI */ -- - static void nvme_async_probe(void *data, async_cookie_t cookie) - { - struct nvme_dev *dev = data; -@@ -2906,7 +2880,7 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) - - quirks |= check_vendor_combination_bug(pdev); - -- if (!noacpi && nvme_acpi_storage_d3(pdev)) { -+ if (!noacpi && acpi_storage_d3(&pdev->dev)) { - /* - * Some systems use a bios work around to ask for D3 on - * platforms that support kernel managed suspend. -diff --git a/include/linux/acpi.h b/include/linux/acpi.h -index 07a0044397e1..1997fc6589b9 100644 ---- a/include/linux/acpi.h -+++ b/include/linux/acpi.h -@@ -1001,6 +1001,7 @@ int acpi_dev_resume(struct device *dev); - int acpi_subsys_runtime_suspend(struct device *dev); - int acpi_subsys_runtime_resume(struct device *dev); - int acpi_dev_pm_attach(struct device *dev, bool power_on); -+bool acpi_storage_d3(struct device *dev); - #else - static inline int acpi_subsys_runtime_suspend(struct device *dev) { return 0; } - static inline int acpi_subsys_runtime_resume(struct device *dev) { return 0; } -@@ -1008,6 +1009,10 @@ static inline int acpi_dev_pm_attach(struct device *dev, bool power_on) - { - return 0; - } -+static inline bool acpi_storage_d3(struct device *dev) -+{ -+ return false; -+} - #endif - - #if defined(CONFIG_ACPI) && defined(CONFIG_PM_SLEEP) --- -2.32.0 - -From f130be1f855c90db03b7926a87a3f53ebcfb9e81 Mon Sep 17 00:00:00 2001 -From: Mario Limonciello -Date: Wed, 9 Jun 2021 13:40:18 -0500 -Subject: [PATCH] ACPI: Add quirks for AMD Renoir/Lucienne CPUs to force the D3 - hint - -AMD systems from Renoir and Lucienne require that the NVME controller -is put into D3 over a Modern Standby / suspend-to-idle -cycle. This is "typically" accomplished using the `StorageD3Enable` -property in the _DSD, but this property was introduced after many -of these systems launched and most OEM systems don't have it in -their BIOS. - -On AMD Renoir without these drives going into D3 over suspend-to-idle -the resume will fail with the NVME controller being reset and a trace -like this in the kernel logs: -``` -[ 83.556118] nvme nvme0: I/O 161 QID 2 timeout, aborting -[ 83.556178] nvme nvme0: I/O 162 QID 2 timeout, aborting -[ 83.556187] nvme nvme0: I/O 163 QID 2 timeout, aborting -[ 83.556196] nvme nvme0: I/O 164 QID 2 timeout, aborting -[ 95.332114] nvme nvme0: I/O 25 QID 0 timeout, reset controller -[ 95.332843] nvme nvme0: Abort status: 0x371 -[ 95.332852] nvme nvme0: Abort status: 0x371 -[ 95.332856] nvme nvme0: Abort status: 0x371 -[ 95.332859] nvme nvme0: Abort status: 0x371 -[ 95.332909] PM: dpm_run_callback(): pci_pm_resume+0x0/0xe0 returns -16 -[ 95.332936] nvme 0000:03:00.0: PM: failed to resume async: error -16 -``` - -The Microsoft documentation for StorageD3Enable mentioned that Windows has -a hardcoded allowlist for D3 support, which was used for these platforms. -Introduce quirks to hardcode them for Linux as well. - -As this property is now "standardized", OEM systems using AMD Cezanne and -newer APU's have adopted this property, and quirks like this should not be -necessary. - -CC: Shyam-sundar S-k -CC: Alexander Deucher -CC: Prike Liang -Link: https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/power-management-for-storage-hardware-devices-intro -Signed-off-by: Mario Limonciello -Acked-by: Rafael J. Wysocki -Tested-by: Julian Sikorski -Signed-off-by: Christoph Hellwig -Patchset: s0ix-amd ---- - drivers/acpi/device_pm.c | 3 +++ - drivers/acpi/internal.h | 9 +++++++++ - drivers/acpi/x86/utils.c | 25 +++++++++++++++++++++++++ - 3 files changed, 37 insertions(+) - -diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c -index 1e278785c7db..28f629a3d95c 100644 ---- a/drivers/acpi/device_pm.c -+++ b/drivers/acpi/device_pm.c -@@ -1357,6 +1357,9 @@ bool acpi_storage_d3(struct device *dev) - struct acpi_device *adev = ACPI_COMPANION(dev); - u8 val; - -+ if (force_storage_d3()) -+ return true; -+ - if (!adev) - return false; - if (fwnode_property_read_u8(acpi_fwnode_handle(adev), "StorageD3Enable", -diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h -index cb8f70842249..96471be3f0c8 100644 ---- a/drivers/acpi/internal.h -+++ b/drivers/acpi/internal.h -@@ -236,6 +236,15 @@ static inline int suspend_nvs_save(void) { return 0; } - static inline void suspend_nvs_restore(void) {} - #endif - -+#ifdef CONFIG_X86 -+bool force_storage_d3(void); -+#else -+static inline bool force_storage_d3(void) -+{ -+ return false; -+} -+#endif -+ - /*-------------------------------------------------------------------------- - Device properties - -------------------------------------------------------------------------- */ -diff --git a/drivers/acpi/x86/utils.c b/drivers/acpi/x86/utils.c -index bdc1ba00aee9..5298bb4d81fe 100644 ---- a/drivers/acpi/x86/utils.c -+++ b/drivers/acpi/x86/utils.c -@@ -135,3 +135,28 @@ bool acpi_device_always_present(struct acpi_device *adev) - - return ret; - } -+ -+/* -+ * AMD systems from Renoir and Lucienne *require* that the NVME controller -+ * is put into D3 over a Modern Standby / suspend-to-idle cycle. -+ * -+ * This is "typically" accomplished using the `StorageD3Enable` -+ * property in the _DSD that is checked via the `acpi_storage_d3` function -+ * but this property was introduced after many of these systems launched -+ * and most OEM systems don't have it in their BIOS. -+ * -+ * The Microsoft documentation for StorageD3Enable mentioned that Windows has -+ * a hardcoded allowlist for D3 support, which was used for these platforms. -+ * -+ * This allows quirking on Linux in a similar fashion. -+ */ -+const struct x86_cpu_id storage_d3_cpu_ids[] = { -+ X86_MATCH_VENDOR_FAM_MODEL(AMD, 23, 96, NULL), /* Renoir */ -+ X86_MATCH_VENDOR_FAM_MODEL(AMD, 23, 104, NULL), /* Lucienne */ -+ {} -+}; -+ -+bool force_storage_d3(void) -+{ -+ return x86_match_cpu(storage_d3_cpu_ids); -+} --- -2.32.0 - -From 82b63c1e1ee410922d54592397aadc2ba9738915 Mon Sep 17 00:00:00 2001 -From: Alex Deucher -Date: Wed, 5 May 2021 09:20:32 -0400 -Subject: [PATCH] ACPI: PM: s2idle: Add missing LPS0 functions for AMD - -These are supposedly not required for AMD platforms, -but at least some HP laptops seem to require it to -properly turn off the keyboard backlight. - -Based on a patch from Marcin Bachry . - -Bug: https://gitlab.freedesktop.org/drm/amd/-/issues/1230 -Reviewed-by: Hans de Goede -Signed-off-by: Alex Deucher -Signed-off-by: Rafael J. Wysocki -Patchset: s0ix-amd ---- - drivers/acpi/x86/s2idle.c | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c -index 2b69536cdccb..2d7ddb8a8cb6 100644 ---- a/drivers/acpi/x86/s2idle.c -+++ b/drivers/acpi/x86/s2idle.c -@@ -42,6 +42,8 @@ static const struct acpi_device_id lps0_device_ids[] = { - - /* AMD */ - #define ACPI_LPS0_DSM_UUID_AMD "e3f32452-febc-43ce-9039-932122d37721" -+#define ACPI_LPS0_ENTRY_AMD 2 -+#define ACPI_LPS0_EXIT_AMD 3 - #define ACPI_LPS0_SCREEN_OFF_AMD 4 - #define ACPI_LPS0_SCREEN_ON_AMD 5 - -@@ -408,6 +410,7 @@ int acpi_s2idle_prepare_late(void) - - if (acpi_s2idle_vendor_amd()) { - acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF_AMD); -+ acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY_AMD); - } else { - acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF); - acpi_sleep_run_lps0_dsm(ACPI_LPS0_ENTRY); -@@ -422,6 +425,7 @@ void acpi_s2idle_restore_early(void) - return; - - if (acpi_s2idle_vendor_amd()) { -+ acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT_AMD); - acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON_AMD); - } else { - acpi_sleep_run_lps0_dsm(ACPI_LPS0_EXIT); --- -2.32.0 - -From 7d78ab3e4a44be78c5ef67a1b49632084527656f Mon Sep 17 00:00:00 2001 -From: Alex Deucher -Date: Wed, 17 Mar 2021 10:38:42 -0400 -Subject: [PATCH] platform/x86: force LPS0 functions for AMD - -ACPI_LPS0_ENTRY_AMD/ACPI_LPS0_EXIT_AMD are supposedly not -required for AMD platforms, and on some platforms they are -not even listed in the function mask but at least some HP -laptops seem to require it to properly support s0ix. - -Based on a patch from Marcin Bachry . - -Bug: https://gitlab.freedesktop.org/drm/amd/-/issues/1230 -Signed-off-by: Alex Deucher -Cc: Marcin Bachry -Reviewed-by: Hans de Goede -Patchset: s0ix-amd ---- - drivers/acpi/x86/s2idle.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c -index 2d7ddb8a8cb6..482e6b23b21a 100644 ---- a/drivers/acpi/x86/s2idle.c -+++ b/drivers/acpi/x86/s2idle.c -@@ -368,6 +368,13 @@ static int lps0_device_attach(struct acpi_device *adev, - - ACPI_FREE(out_obj); - -+ /* -+ * Some HP laptops require ACPI_LPS0_ENTRY_AMD/ACPI_LPS0_EXIT_AMD for proper -+ * S0ix, but don't set the function mask correctly. Fix that up here. -+ */ -+ if (acpi_s2idle_vendor_amd()) -+ lps0_dsm_func_mask |= (1 << ACPI_LPS0_ENTRY_AMD) | (1 << ACPI_LPS0_EXIT_AMD); -+ - acpi_handle_debug(adev->handle, "_DSM function mask: 0x%x\n", - lps0_dsm_func_mask); - --- -2.32.0 - -From 47c6565338ddb199d82a5407f04986eb20e4a28a Mon Sep 17 00:00:00 2001 -From: Shyam Sundar S K -Date: Thu, 17 Jun 2021 17:00:35 +0530 -Subject: [PATCH] platform/x86: amd-pmc: Fix command completion code - -The protocol to submit a job request to SMU is to wait for -AMD_PMC_REGISTER_RESPONSE to return 1,meaning SMU is ready to take -requests. PMC driver has to make sure that the response code is always -AMD_PMC_RESULT_OK before making any command submissions. - -Also, when we submit a message to SMU, we have to wait until it processes -the request. Adding a read_poll_timeout() check as this was missing in -the existing code. - -Fixes: 156ec4731cb2 ("platform/x86: amd-pmc: Add AMD platform support for S2Idle") -Signed-off-by: Shyam Sundar S K -Reviewed-by: Hans de Goede -Patchset: s0ix-amd ---- - drivers/platform/x86/amd-pmc.c | 10 +++++++++- - 1 file changed, 9 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c -index 0b5578a8a449..535e431f98a8 100644 ---- a/drivers/platform/x86/amd-pmc.c -+++ b/drivers/platform/x86/amd-pmc.c -@@ -140,7 +140,7 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) - - /* Wait until we get a valid response */ - rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, -- val, val > 0, PMC_MSG_DELAY_MIN_US, -+ val, val == AMD_PMC_RESULT_OK, PMC_MSG_DELAY_MIN_US, - PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); - if (rc) { - dev_err(dev->dev, "failed to talk to SMU\n"); -@@ -156,6 +156,14 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) - /* Write message ID to message ID register */ - msg = (dev->cpu_id == AMD_CPU_ID_RN) ? MSG_OS_HINT_RN : MSG_OS_HINT_PCO; - amd_pmc_reg_write(dev, AMD_PMC_REGISTER_MESSAGE, msg); -+ /* Wait until we get a valid response */ -+ rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, -+ val, val == AMD_PMC_RESULT_OK, PMC_MSG_DELAY_MIN_US, -+ PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); -+ if (rc) { -+ dev_err(dev->dev, "SMU response timed out\n"); -+ return rc; -+ } - return 0; - } - --- -2.32.0 - -From 95350e7ba7dc7866ede307c58f45296d3d8df5c1 Mon Sep 17 00:00:00 2001 -From: Shyam Sundar S K -Date: Thu, 17 Jun 2021 17:00:36 +0530 -Subject: [PATCH] platform/x86: amd-pmc: Fix SMU firmware reporting mechanism - -It was lately understood that the current mechanism available in the -driver to get SMU firmware info works only on internal SMU builds and -there is a separate way to get all the SMU logging counters (addressed -in the next patch). Hence remove all the smu info shown via debugfs as it -is no more useful. - -Also, use dump registers routine only at one place i.e. after the command -submission to SMU is done. - -Fixes: 156ec4731cb2 ("platform/x86: amd-pmc: Add AMD platform support for S2Idle") -Signed-off-by: Shyam Sundar S K -Patchset: s0ix-amd ---- - drivers/platform/x86/amd-pmc.c | 15 +-------------- - 1 file changed, 1 insertion(+), 14 deletions(-) - -diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c -index 535e431f98a8..d32f0a0eeb9f 100644 ---- a/drivers/platform/x86/amd-pmc.c -+++ b/drivers/platform/x86/amd-pmc.c -@@ -52,7 +52,6 @@ - #define AMD_CPU_ID_PCO AMD_CPU_ID_RV - #define AMD_CPU_ID_CZN AMD_CPU_ID_RN - --#define AMD_SMU_FW_VERSION 0x0 - #define PMC_MSG_DELAY_MIN_US 100 - #define RESPONSE_REGISTER_LOOP_MAX 200 - -@@ -88,11 +87,6 @@ static inline void amd_pmc_reg_write(struct amd_pmc_dev *dev, int reg_offset, u3 - #ifdef CONFIG_DEBUG_FS - static int smu_fw_info_show(struct seq_file *s, void *unused) - { -- struct amd_pmc_dev *dev = s->private; -- u32 value; -- -- value = ioread32(dev->smu_base + AMD_SMU_FW_VERSION); -- seq_printf(s, "SMU FW Info: %x\n", value); - return 0; - } - DEFINE_SHOW_ATTRIBUTE(smu_fw_info); -@@ -164,6 +158,7 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) - dev_err(dev->dev, "SMU response timed out\n"); - return rc; - } -+ amd_pmc_dump_registers(dev); - return 0; - } - -@@ -176,7 +171,6 @@ static int __maybe_unused amd_pmc_suspend(struct device *dev) - if (rc) - dev_err(pdev->dev, "suspend failed\n"); - -- amd_pmc_dump_registers(pdev); - return 0; - } - -@@ -189,7 +183,6 @@ static int __maybe_unused amd_pmc_resume(struct device *dev) - if (rc) - dev_err(pdev->dev, "resume failed\n"); - -- amd_pmc_dump_registers(pdev); - return 0; - } - -@@ -256,17 +249,11 @@ static int amd_pmc_probe(struct platform_device *pdev) - pci_dev_put(rdev); - base_addr = ((u64)base_addr_hi << 32 | base_addr_lo); - -- dev->smu_base = devm_ioremap(dev->dev, base_addr, AMD_PMC_MAPPING_SIZE); -- if (!dev->smu_base) -- return -ENOMEM; -- - dev->regbase = devm_ioremap(dev->dev, base_addr + AMD_PMC_BASE_ADDR_OFFSET, - AMD_PMC_MAPPING_SIZE); - if (!dev->regbase) - return -ENOMEM; - -- amd_pmc_dump_registers(dev); -- - platform_set_drvdata(pdev, dev); - amd_pmc_dbgfs_register(dev); - return 0; --- -2.32.0 - -From 3c26a54d62e99ed2ce8a87f556d82db14722e80c Mon Sep 17 00:00:00 2001 -From: Shyam Sundar S K -Date: Thu, 17 Jun 2021 17:00:37 +0530 -Subject: [PATCH] platform/x86: amd-pmc: Add support for logging SMU metrics - -SMU provides a way to dump the s0ix debug statistics in the form of a -metrics table via a of set special mailbox commands. - -Add support to the driver which can send these commands to SMU and expose -the information received via debugfs. The information contains the s0ix -entry/exit, active time of each IP block etc. - -As a side note, SMU subsystem logging is not supported on Picasso based -SoC's. - -Signed-off-by: Shyam Sundar S K -Patchset: s0ix-amd ---- - drivers/platform/x86/amd-pmc.c | 148 +++++++++++++++++++++++++++++++-- - 1 file changed, 140 insertions(+), 8 deletions(-) - -diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c -index d32f0a0eeb9f..b5249fdeb95f 100644 ---- a/drivers/platform/x86/amd-pmc.c -+++ b/drivers/platform/x86/amd-pmc.c -@@ -46,6 +46,14 @@ - #define AMD_PMC_RESULT_CMD_UNKNOWN 0xFE - #define AMD_PMC_RESULT_FAILED 0xFF - -+/* SMU Message Definations */ -+#define SMU_MSG_GETSMUVERSION 0x02 -+#define SMU_MSG_LOG_GETDRAM_ADDR_HI 0x04 -+#define SMU_MSG_LOG_GETDRAM_ADDR_LO 0x05 -+#define SMU_MSG_LOG_START 0x06 -+#define SMU_MSG_LOG_RESET 0x07 -+#define SMU_MSG_LOG_DUMP_DATA 0x08 -+#define SMU_MSG_GET_SUP_CONSTRAINTS 0x09 - /* List of supported CPU ids */ - #define AMD_CPU_ID_RV 0x15D0 - #define AMD_CPU_ID_RN 0x1630 -@@ -55,17 +63,42 @@ - #define PMC_MSG_DELAY_MIN_US 100 - #define RESPONSE_REGISTER_LOOP_MAX 200 - -+#define SOC_SUBSYSTEM_IP_MAX 12 -+#define DELAY_MIN_US 2000 -+#define DELAY_MAX_US 3000 - enum amd_pmc_def { - MSG_TEST = 0x01, - MSG_OS_HINT_PCO, - MSG_OS_HINT_RN, - }; - -+struct amd_pmc_bit_map { -+ const char *name; -+ u32 bit_mask; -+}; -+ -+static const struct amd_pmc_bit_map soc15_ip_blk[] = { -+ {"DISPLAY", BIT(0)}, -+ {"CPU", BIT(1)}, -+ {"GFX", BIT(2)}, -+ {"VDD", BIT(3)}, -+ {"ACP", BIT(4)}, -+ {"VCN", BIT(5)}, -+ {"ISP", BIT(6)}, -+ {"NBIO", BIT(7)}, -+ {"DF", BIT(8)}, -+ {"USB0", BIT(9)}, -+ {"USB1", BIT(10)}, -+ {"LAPIC", BIT(11)}, -+ {} -+}; -+ - struct amd_pmc_dev { - void __iomem *regbase; -- void __iomem *smu_base; -+ void __iomem *smu_virt_addr; - u32 base_addr; - u32 cpu_id; -+ u32 active_ips; - struct device *dev; - #if IS_ENABLED(CONFIG_DEBUG_FS) - struct dentry *dbgfs_dir; -@@ -73,6 +106,7 @@ struct amd_pmc_dev { - }; - - static struct amd_pmc_dev pmc; -+static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set, u32 *data, u8 msg, bool ret); - - static inline u32 amd_pmc_reg_read(struct amd_pmc_dev *dev, int reg_offset) - { -@@ -84,9 +118,50 @@ static inline void amd_pmc_reg_write(struct amd_pmc_dev *dev, int reg_offset, u3 - iowrite32(val, dev->regbase + reg_offset); - } - -+struct smu_metrics { -+ u32 table_version; -+ u32 hint_count; -+ u32 s0i3_cyclecount; -+ u32 timein_s0i2; -+ u64 timeentering_s0i3_lastcapture; -+ u64 timeentering_s0i3_totaltime; -+ u64 timeto_resume_to_os_lastcapture; -+ u64 timeto_resume_to_os_totaltime; -+ u64 timein_s0i3_lastcapture; -+ u64 timein_s0i3_totaltime; -+ u64 timein_swdrips_lastcapture; -+ u64 timein_swdrips_totaltime; -+ u64 timecondition_notmet_lastcapture[SOC_SUBSYSTEM_IP_MAX]; -+ u64 timecondition_notmet_totaltime[SOC_SUBSYSTEM_IP_MAX]; -+} __packed; -+ - #ifdef CONFIG_DEBUG_FS - static int smu_fw_info_show(struct seq_file *s, void *unused) - { -+ struct amd_pmc_dev *dev = s->private; -+ struct smu_metrics table; -+ u32 value; -+ int idx; -+ -+ if (dev->cpu_id == AMD_CPU_ID_PCO) -+ return -EINVAL; -+ -+ memcpy_fromio(&table, dev->smu_virt_addr, sizeof(struct smu_metrics)); -+ -+ seq_puts(s, "\n=== SMU Statistics ===\n"); -+ seq_printf(s, "Table Version: %d\n", table.table_version); -+ seq_printf(s, "Hint Count: %d\n", table.hint_count); -+ seq_printf(s, "S0i3 Cycle Count: %d\n", table.s0i3_cyclecount); -+ seq_printf(s, "Time (in us) to S0i3: %lld\n", table.timeentering_s0i3_lastcapture); -+ seq_printf(s, "Time (in us) in S0i3: %lld\n", table.timein_s0i3_lastcapture); -+ -+ seq_puts(s, "\n=== Active time (in us) ===\n"); -+ for (idx = 0 ; idx < SOC_SUBSYSTEM_IP_MAX ; idx++) { -+ if (soc15_ip_blk[idx].bit_mask & dev->active_ips) -+ seq_printf(s, "%-8s : %lld\n", soc15_ip_blk[idx].name, -+ table.timecondition_notmet_lastcapture[idx]); -+ } -+ - return 0; - } - DEFINE_SHOW_ATTRIBUTE(smu_fw_info); -@@ -112,6 +187,32 @@ static inline void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev) - } - #endif /* CONFIG_DEBUG_FS */ - -+static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev) -+{ -+ u32 phys_addr_low, phys_addr_hi; -+ u64 smu_phys_addr; -+ -+ if (dev->cpu_id == AMD_CPU_ID_PCO) -+ return -EINVAL; -+ -+ /* Get Active devices list from SMU */ -+ amd_pmc_send_cmd(dev, 0, &dev->active_ips, SMU_MSG_GET_SUP_CONSTRAINTS, 1); -+ -+ /* Get dram address */ -+ amd_pmc_send_cmd(dev, 0, &phys_addr_low, SMU_MSG_LOG_GETDRAM_ADDR_LO, 1); -+ amd_pmc_send_cmd(dev, 0, &phys_addr_hi, SMU_MSG_LOG_GETDRAM_ADDR_HI, 1); -+ smu_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low); -+ -+ dev->smu_virt_addr = devm_ioremap(dev->dev, smu_phys_addr, sizeof(struct smu_metrics)); -+ if (!dev->smu_virt_addr) -+ return -ENOMEM; -+ -+ /* Start the logging */ -+ amd_pmc_send_cmd(dev, 0, NULL, SMU_MSG_LOG_START, 0); -+ -+ return 0; -+} -+ - static void amd_pmc_dump_registers(struct amd_pmc_dev *dev) - { - u32 value; -@@ -126,10 +227,9 @@ static void amd_pmc_dump_registers(struct amd_pmc_dev *dev) - dev_dbg(dev->dev, "AMD_PMC_REGISTER_MESSAGE:%x\n", value); - } - --static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) -+static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set, u32 *data, u8 msg, bool ret) - { - int rc; -- u8 msg; - u32 val; - - /* Wait until we get a valid response */ -@@ -148,8 +248,8 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) - amd_pmc_reg_write(dev, AMD_PMC_REGISTER_ARGUMENT, set); - - /* Write message ID to message ID register */ -- msg = (dev->cpu_id == AMD_CPU_ID_RN) ? MSG_OS_HINT_RN : MSG_OS_HINT_PCO; - amd_pmc_reg_write(dev, AMD_PMC_REGISTER_MESSAGE, msg); -+ - /* Wait until we get a valid response */ - rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, - val, val == AMD_PMC_RESULT_OK, PMC_MSG_DELAY_MIN_US, -@@ -158,16 +258,40 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) - dev_err(dev->dev, "SMU response timed out\n"); - return rc; - } -+ -+ if (ret) { -+ /* PMFW may take longer time to return back the data */ -+ usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); -+ *data = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT); -+ } -+ - amd_pmc_dump_registers(dev); - return 0; - } - -+static int amd_pmc_get_os_hint(struct amd_pmc_dev *dev) -+{ -+ switch (dev->cpu_id) { -+ case AMD_CPU_ID_PCO: -+ return MSG_OS_HINT_PCO; -+ case AMD_CPU_ID_RN: -+ return MSG_OS_HINT_RN; -+ } -+ return -EINVAL; -+} -+ - static int __maybe_unused amd_pmc_suspend(struct device *dev) - { - struct amd_pmc_dev *pdev = dev_get_drvdata(dev); - int rc; -+ u8 msg; -+ -+ /* Reset and Start SMU logging - to monitor the s0i3 stats */ -+ amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_RESET, 0); -+ amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_START, 0); - -- rc = amd_pmc_send_cmd(pdev, 1); -+ msg = amd_pmc_get_os_hint(pdev); -+ rc = amd_pmc_send_cmd(pdev, 1, NULL, msg, 0); - if (rc) - dev_err(pdev->dev, "suspend failed\n"); - -@@ -178,8 +302,13 @@ static int __maybe_unused amd_pmc_resume(struct device *dev) - { - struct amd_pmc_dev *pdev = dev_get_drvdata(dev); - int rc; -+ u8 msg; -+ -+ /* Let SMU know that we are looking for stats */ -+ amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_DUMP_DATA, 0); - -- rc = amd_pmc_send_cmd(pdev, 0); -+ msg = amd_pmc_get_os_hint(pdev); -+ rc = amd_pmc_send_cmd(pdev, 0, NULL, msg, 0); - if (rc) - dev_err(pdev->dev, "resume failed\n"); - -@@ -202,8 +331,7 @@ static int amd_pmc_probe(struct platform_device *pdev) - { - struct amd_pmc_dev *dev = &pmc; - struct pci_dev *rdev; -- u32 base_addr_lo; -- u32 base_addr_hi; -+ u32 base_addr_lo, base_addr_hi; - u64 base_addr; - int err; - u32 val; -@@ -254,6 +382,10 @@ static int amd_pmc_probe(struct platform_device *pdev) - if (!dev->regbase) - return -ENOMEM; - -+ /* Use SMU to get the s0i3 debug stats */ -+ err = amd_pmc_setup_smu_logging(dev); -+ if (err) -+ dev_err(dev->dev, "SMU debugging info not supported on this platform\n"); - platform_set_drvdata(pdev, dev); - amd_pmc_dbgfs_register(dev); - return 0; --- -2.32.0 - -From c0b1001ec46fd2912b7d2882f22973bfe309520f Mon Sep 17 00:00:00 2001 -From: Shyam Sundar S K -Date: Thu, 17 Jun 2021 17:00:38 +0530 -Subject: [PATCH] platform/x86: amd-pmc: Add support for logging s0ix counters - -Even the FCH SSC registers provides certain level of information -about the s0ix entry and exit times which comes handy when the SMU -fails to report the statistics via the mailbox communication. - -This information is captured via a new debugfs file "s0ix_stats". -A non-zero entry in this counters would mean that the system entered -the s0ix state. - -If s0ix entry time and exit time don't change during suspend to idle, -the silicon has not entered the deepest state. - -Signed-off-by: Shyam Sundar S K -Patchset: s0ix-amd ---- - drivers/platform/x86/amd-pmc.c | 46 ++++++++++++++++++++++++++++++++-- - 1 file changed, 44 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c -index b5249fdeb95f..b6ad290c9a86 100644 ---- a/drivers/platform/x86/amd-pmc.c -+++ b/drivers/platform/x86/amd-pmc.c -@@ -46,6 +46,15 @@ - #define AMD_PMC_RESULT_CMD_UNKNOWN 0xFE - #define AMD_PMC_RESULT_FAILED 0xFF - -+/* FCH SSC Registers */ -+#define FCH_S0I3_ENTRY_TIME_L_OFFSET 0x30 -+#define FCH_S0I3_ENTRY_TIME_H_OFFSET 0x34 -+#define FCH_S0I3_EXIT_TIME_L_OFFSET 0x38 -+#define FCH_S0I3_EXIT_TIME_H_OFFSET 0x3C -+#define FCH_SSC_MAPPING_SIZE 0x800 -+#define FCH_BASE_PHY_ADDR_LOW 0xFED81100 -+#define FCH_BASE_PHY_ADDR_HIGH 0x00000000 -+ - /* SMU Message Definations */ - #define SMU_MSG_GETSMUVERSION 0x02 - #define SMU_MSG_LOG_GETDRAM_ADDR_HI 0x04 -@@ -96,6 +105,7 @@ static const struct amd_pmc_bit_map soc15_ip_blk[] = { - struct amd_pmc_dev { - void __iomem *regbase; - void __iomem *smu_virt_addr; -+ void __iomem *fch_virt_addr; - u32 base_addr; - u32 cpu_id; - u32 active_ips; -@@ -140,7 +150,6 @@ static int smu_fw_info_show(struct seq_file *s, void *unused) - { - struct amd_pmc_dev *dev = s->private; - struct smu_metrics table; -- u32 value; - int idx; - - if (dev->cpu_id == AMD_CPU_ID_PCO) -@@ -166,6 +175,29 @@ static int smu_fw_info_show(struct seq_file *s, void *unused) - } - DEFINE_SHOW_ATTRIBUTE(smu_fw_info); - -+static int s0ix_stats_show(struct seq_file *s, void *unused) -+{ -+ struct amd_pmc_dev *dev = s->private; -+ u64 entry_time, exit_time, residency; -+ -+ entry_time = ioread32(dev->fch_virt_addr + FCH_S0I3_ENTRY_TIME_H_OFFSET); -+ entry_time = entry_time << 32 | ioread32(dev->fch_virt_addr + FCH_S0I3_ENTRY_TIME_L_OFFSET); -+ -+ exit_time = ioread32(dev->fch_virt_addr + FCH_S0I3_EXIT_TIME_H_OFFSET); -+ exit_time = exit_time << 32 | ioread32(dev->fch_virt_addr + FCH_S0I3_EXIT_TIME_L_OFFSET); -+ -+ /* It's in 48MHz. We need to convert it to unit of 100ns */ -+ residency = (exit_time - entry_time) * 10 / 48; -+ -+ seq_puts(s, "=== S0ix statistics ===\n"); -+ seq_printf(s, "S0ix Entry Time: %lld\n", entry_time); -+ seq_printf(s, "S0ix Exit Time: %lld\n", exit_time); -+ seq_printf(s, "Residency Time: %lld\n", residency); -+ -+ return 0; -+} -+DEFINE_SHOW_ATTRIBUTE(s0ix_stats); -+ - static void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev) - { - debugfs_remove_recursive(dev->dbgfs_dir); -@@ -176,6 +208,8 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) - dev->dbgfs_dir = debugfs_create_dir("amd_pmc", NULL); - debugfs_create_file("smu_fw_info", 0644, dev->dbgfs_dir, dev, - &smu_fw_info_fops); -+ debugfs_create_file("s0ix_stats", 0644, dev->dbgfs_dir, dev, -+ &s0ix_stats_fops); - } - #else - static inline void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) -@@ -332,7 +366,7 @@ static int amd_pmc_probe(struct platform_device *pdev) - struct amd_pmc_dev *dev = &pmc; - struct pci_dev *rdev; - u32 base_addr_lo, base_addr_hi; -- u64 base_addr; -+ u64 base_addr, fch_phys_addr; - int err; - u32 val; - -@@ -382,6 +416,14 @@ static int amd_pmc_probe(struct platform_device *pdev) - if (!dev->regbase) - return -ENOMEM; - -+ /* Use FCH registers to get the S0ix stats */ -+ base_addr_lo = FCH_BASE_PHY_ADDR_LOW; -+ base_addr_hi = FCH_BASE_PHY_ADDR_HIGH; -+ fch_phys_addr = ((u64)base_addr_hi << 32 | base_addr_lo); -+ dev->fch_virt_addr = devm_ioremap(dev->dev, fch_phys_addr, FCH_SSC_MAPPING_SIZE); -+ if (!dev->fch_virt_addr) -+ return -ENOMEM; -+ - /* Use SMU to get the s0i3 debug stats */ - err = amd_pmc_setup_smu_logging(dev); - if (err) --- -2.32.0 - -From c1e5e94f1bf15f1b70ae991bcabab92d9e9150a5 Mon Sep 17 00:00:00 2001 -From: Shyam Sundar S K -Date: Thu, 17 Jun 2021 17:00:39 +0530 -Subject: [PATCH] platform/x86: amd-pmc: Add support for ACPI ID AMDI0006 - -Some newer BIOSes have added another ACPI ID for the uPEP device. -SMU statistics behave identically on this device. - -Signed-off-by: Shyam Sundar S K -Patchset: s0ix-amd ---- - drivers/platform/x86/amd-pmc.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c -index b6ad290c9a86..2a73fe0deaf3 100644 ---- a/drivers/platform/x86/amd-pmc.c -+++ b/drivers/platform/x86/amd-pmc.c -@@ -443,6 +443,7 @@ static int amd_pmc_remove(struct platform_device *pdev) - - static const struct acpi_device_id amd_pmc_acpi_ids[] = { - {"AMDI0005", 0}, -+ {"AMDI0006", 0}, - {"AMD0004", 0}, - {"AMD0005", 0}, - { } --- -2.32.0 - -From 31b773122734f73887807b6f8b1d7e2229b5af8b Mon Sep 17 00:00:00 2001 -From: Shyam Sundar S K -Date: Thu, 17 Jun 2021 17:00:40 +0530 -Subject: [PATCH] platform/x86: amd-pmc: Add new acpi id for future PMC - controllers - -The upcoming PMC controller would have a newer acpi id, add that to -the supported acpid device list. - -Signed-off-by: Shyam Sundar S K -Patchset: s0ix-amd ---- - drivers/platform/x86/amd-pmc.c | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c -index 2a73fe0deaf3..5a2be598fc2e 100644 ---- a/drivers/platform/x86/amd-pmc.c -+++ b/drivers/platform/x86/amd-pmc.c -@@ -68,6 +68,7 @@ - #define AMD_CPU_ID_RN 0x1630 - #define AMD_CPU_ID_PCO AMD_CPU_ID_RV - #define AMD_CPU_ID_CZN AMD_CPU_ID_RN -+#define AMD_CPU_ID_YC 0x14B5 - - #define PMC_MSG_DELAY_MIN_US 100 - #define RESPONSE_REGISTER_LOOP_MAX 200 -@@ -309,6 +310,7 @@ static int amd_pmc_get_os_hint(struct amd_pmc_dev *dev) - case AMD_CPU_ID_PCO: - return MSG_OS_HINT_PCO; - case AMD_CPU_ID_RN: -+ case AMD_CPU_ID_YC: - return MSG_OS_HINT_RN; - } - return -EINVAL; -@@ -354,6 +356,7 @@ static const struct dev_pm_ops amd_pmc_pm_ops = { - }; - - static const struct pci_device_id pmc_pci_ids[] = { -+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_YC) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_CZN) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RN) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PCO) }, -@@ -444,6 +447,7 @@ static int amd_pmc_remove(struct platform_device *pdev) - static const struct acpi_device_id amd_pmc_acpi_ids[] = { - {"AMDI0005", 0}, - {"AMDI0006", 0}, -+ {"AMDI0007", 0}, - {"AMD0004", 0}, - {"AMD0005", 0}, - { } --- -2.32.0 - -From 7001c6c1a202585885a14575aa8f731f60714c5c Mon Sep 17 00:00:00 2001 -From: Pratik Vishwakarma -Date: Thu, 17 Jun 2021 11:42:08 -0500 -Subject: [PATCH] ACPI: PM: s2idle: Use correct revision id - -AMD spec mentions only revision 0. With this change, -device constraint list is populated properly. - -Signed-off-by: Pratik Vishwakarma -Patchset: s0ix-amd ---- - drivers/acpi/x86/s2idle.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c -index 482e6b23b21a..4339e6da0dd6 100644 ---- a/drivers/acpi/x86/s2idle.c -+++ b/drivers/acpi/x86/s2idle.c -@@ -96,7 +96,7 @@ static void lpi_device_get_constraints_amd(void) - int i, j, k; - - out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid, -- 1, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, -+ rev_id, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, - NULL, ACPI_TYPE_PACKAGE); - - if (!out_obj) --- -2.32.0 - diff --git a/patches/5.13/0001-surface3-oemb.patch b/patches/5.13/0001-surface3-oemb.patch deleted file mode 100644 index 0879133a2..000000000 --- a/patches/5.13/0001-surface3-oemb.patch +++ /dev/null @@ -1,101 +0,0 @@ -From 9e9d5df989333ccfa23e32fee0bc15fb3dfbad85 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 18 Oct 2020 16:42:44 +0900 -Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI - table - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI data into dmi_system_id tables used -for quirks so that each driver can enable quirks even on the affected -systems. - -On affected systems, DMI data will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: surface3-oemb ---- - drivers/platform/surface/surface3-wmi.c | 7 +++++++ - sound/soc/codecs/rt5645.c | 9 +++++++++ - sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ - 3 files changed, 24 insertions(+) - -diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c -index fcd1d4fb94d5..ee26a5998b07 100644 ---- a/drivers/platform/surface/surface3-wmi.c -+++ b/drivers/platform/surface/surface3-wmi.c -@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - #endif - { } - }; -diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c -index 9408ee63cb26..5cac83953901 100644 ---- a/sound/soc/codecs/rt5645.c -+++ b/sound/soc/codecs/rt5645.c -@@ -3718,6 +3718,15 @@ static const struct dmi_system_id dmi_platform_data[] = { - }, - .driver_data = (void *)&intel_braswell_platform_data, - }, -+ { -+ .ident = "Microsoft Surface 3", -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)&intel_braswell_platform_data, -+ }, - { - /* - * Match for the GPDwin which unfortunately uses somewhat -diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -index 227424236fd5..1013a57be89a 100644 ---- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c -+++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .callback = cht_surface_quirk_cb, -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - { } - }; - --- -2.33.0 - diff --git a/patches/5.13/0002-mwifiex.patch b/patches/5.13/0002-mwifiex.patch deleted file mode 100644 index c1f9f1faf..000000000 --- a/patches/5.13/0002-mwifiex.patch +++ /dev/null @@ -1,2520 +0,0 @@ -From 64ecb0e73f1d88211c2452321b73f0a4a22f3641 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Mon, 28 Sep 2020 17:46:49 +0900 -Subject: [PATCH] mwifiex: pcie: add DMI-based quirk impl for Surface devices - -This commit adds quirk implementation based on DMI matching with DMI -table for Surface devices. - -This implementation can be used for quirks later. - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/Makefile | 1 + - drivers/net/wireless/marvell/mwifiex/pcie.c | 4 + - drivers/net/wireless/marvell/mwifiex/pcie.h | 1 + - .../wireless/marvell/mwifiex/pcie_quirks.c | 114 ++++++++++++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.h | 11 ++ - 5 files changed, 131 insertions(+) - create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.c - create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.h - -diff --git a/drivers/net/wireless/marvell/mwifiex/Makefile b/drivers/net/wireless/marvell/mwifiex/Makefile -index 162d557b78af..2bd00f40958e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/Makefile -+++ b/drivers/net/wireless/marvell/mwifiex/Makefile -@@ -49,6 +49,7 @@ mwifiex_sdio-y += sdio.o - obj-$(CONFIG_MWIFIEX_SDIO) += mwifiex_sdio.o - - mwifiex_pcie-y += pcie.o -+mwifiex_pcie-y += pcie_quirks.o - obj-$(CONFIG_MWIFIEX_PCIE) += mwifiex_pcie.o - - mwifiex_usb-y += usb.o -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 46517515ba72..a530832c9421 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -27,6 +27,7 @@ - #include "wmm.h" - #include "11n.h" - #include "pcie.h" -+#include "pcie_quirks.h" - - #define PCIE_VERSION "1.0" - #define DRV_NAME "Marvell mwifiex PCIe" -@@ -410,6 +411,9 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return ret; - } - -+ /* check quirks */ -+ mwifiex_initialize_quirks(card); -+ - if (mwifiex_add_card(card, &card->fw_done, &pcie_ops, - MWIFIEX_PCIE, &pdev->dev)) { - pr_err("%s failed\n", __func__); -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.h b/drivers/net/wireless/marvell/mwifiex/pcie.h -index 5ed613d65709..981e330c77d7 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.h -@@ -244,6 +244,7 @@ struct pcie_service_card { - unsigned long work_flags; - - bool pci_reset_ongoing; -+ unsigned long quirks; - }; - - static inline int -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -new file mode 100644 -index 000000000000..929aee2b0a60 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -0,0 +1,114 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * File for PCIe quirks. -+ */ -+ -+/* The low-level PCI operations will be performed in this file. Therefore, -+ * let's use dev_*() instead of mwifiex_dbg() here to avoid troubles (e.g. -+ * to avoid using mwifiex_adapter struct before init or wifi is powered -+ * down, or causes NULL ptr deref). -+ */ -+ -+#include -+ -+#include "pcie_quirks.h" -+ -+/* quirk table based on DMI matching */ -+static const struct dmi_system_id mwifiex_quirk_table[] = { -+ { -+ .ident = "Surface Pro 4", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 5", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 5 (LTE)", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 6", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Book 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Book 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Laptop 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Laptop 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), -+ }, -+ .driver_data = 0, -+ }, -+ { -+ .ident = "Surface Pro 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 3"), -+ }, -+ .driver_data = 0, -+ }, -+ {} -+}; -+ -+void mwifiex_initialize_quirks(struct pcie_service_card *card) -+{ -+ struct pci_dev *pdev = card->dev; -+ const struct dmi_system_id *dmi_id; -+ -+ dmi_id = dmi_first_match(mwifiex_quirk_table); -+ if (dmi_id) -+ card->quirks = (uintptr_t)dmi_id->driver_data; -+ -+ if (!card->quirks) -+ dev_info(&pdev->dev, "no quirks enabled\n"); -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -new file mode 100644 -index 000000000000..5326ae7e5671 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -0,0 +1,11 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* -+ * Header file for PCIe quirks. -+ */ -+ -+#include "pcie.h" -+ -+/* quirks */ -+// quirk flags can be added here -+ -+void mwifiex_initialize_quirks(struct pcie_service_card *card); --- -2.33.0 - -From f45477440861fdcdddd5ae7f414220beac238f43 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:25:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_d3cold quirk for Surface gen4+ - devices - -To reset mwifiex on Surface gen4+ (Pro 4 or later gen) devices, it -seems that putting the wifi device into D3cold is required according -to errata.inf file on Windows installation (Windows/INF/errata.inf). - -This patch adds a function that performs power-cycle (put into D3cold -then D0) and call the function at the end of reset_prepare(). - -Note: Need to also reset the parent device (bridge) of wifi on SB1; -it might be because the bridge of wifi always reports it's in D3hot. -When I tried to reset only the wifi device (not touching parent), it gave -the following error and the reset failed: - - acpi device:4b: Cannot transition to power state D0 for parent in D3hot - mwifiex_pcie 0000:03:00.0: can't change power state from D3cold to D0 (config space inaccessible) - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 ++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 73 +++++++++++++++++-- - .../wireless/marvell/mwifiex/pcie_quirks.h | 3 +- - 3 files changed, 74 insertions(+), 9 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index a530832c9421..fbf2b9d30656 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -528,6 +528,13 @@ static void mwifiex_pcie_reset_prepare(struct pci_dev *pdev) - mwifiex_shutdown_sw(adapter); - clear_bit(MWIFIEX_IFACE_WORK_DEVICE_DUMP, &card->work_flags); - clear_bit(MWIFIEX_IFACE_WORK_CARD_RESET, &card->work_flags); -+ -+ /* For Surface gen4+ devices, we need to put wifi into D3cold right -+ * before performing FLR -+ */ -+ if (card->quirks & QUIRK_FW_RST_D3COLD) -+ mwifiex_pcie_reset_d3cold_quirk(pdev); -+ - mwifiex_dbg(adapter, INFO, "%s, successful\n", __func__); - - card->pci_reset_ongoing = true; -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 929aee2b0a60..edc739c542fe 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -21,7 +21,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 5", -@@ -30,7 +30,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -39,7 +39,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Pro 6", -@@ -47,7 +47,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Book 1", -@@ -55,7 +55,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Book 2", -@@ -63,7 +63,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Laptop 1", -@@ -71,7 +71,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface Laptop 2", -@@ -79,7 +79,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, - { - .ident = "Surface 3", -@@ -111,4 +111,61 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - - if (!card->quirks) - dev_info(&pdev->dev, "no quirks enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_D3COLD) -+ dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+} -+ -+static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -+{ -+ dev_info(&pdev->dev, "putting into D3cold...\n"); -+ -+ pci_save_state(pdev); -+ if (pci_is_enabled(pdev)) -+ pci_disable_device(pdev); -+ pci_set_power_state(pdev, PCI_D3cold); -+} -+ -+static int mwifiex_pcie_set_power_d0(struct pci_dev *pdev) -+{ -+ int ret; -+ -+ dev_info(&pdev->dev, "putting into D0...\n"); -+ -+ pci_set_power_state(pdev, PCI_D0); -+ ret = pci_enable_device(pdev); -+ if (ret) { -+ dev_err(&pdev->dev, "pci_enable_device failed\n"); -+ return ret; -+ } -+ pci_restore_state(pdev); -+ -+ return 0; -+} -+ -+int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) -+{ -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); -+ int ret; -+ -+ /* Power-cycle (put into D3cold then D0) */ -+ dev_info(&pdev->dev, "Using reset_d3cold quirk to perform FW reset\n"); -+ -+ /* We need to perform power-cycle also for bridge of wifi because -+ * on some devices (e.g. Surface Book 1), the OS for some reasons -+ * can't know the real power state of the bridge. -+ * When tried to power-cycle only wifi, the reset failed with the -+ * following dmesg log: -+ * "Cannot transition to power state D0 for parent in D3hot". -+ */ -+ mwifiex_pcie_set_power_d3cold(pdev); -+ mwifiex_pcie_set_power_d3cold(parent_pdev); -+ -+ ret = mwifiex_pcie_set_power_d0(parent_pdev); -+ if (ret) -+ return ret; -+ ret = mwifiex_pcie_set_power_d0(pdev); -+ if (ret) -+ return ret; -+ -+ return 0; - } -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 5326ae7e5671..8b9dcb5070d8 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -6,6 +6,7 @@ - #include "pcie.h" - - /* quirks */ --// quirk flags can be added here -+#define QUIRK_FW_RST_D3COLD BIT(0) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); -+int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.33.0 - -From 56828c0e10435c42edd09cca6ab3e53a1639eebe Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:32:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_wsid quirk for Surface 3 - -This commit adds reset_wsid quirk and uses this quirk for Surface 3 on -card reset. - -To reset mwifiex on Surface 3, it seems that calling the _DSM method -exists in \_SB.WSID [1] device is required. - -On Surface 3, calling the _DSM method removes/re-probes the card by -itself. So, need to place the reset function before performing FLR and -skip performing any other reset-related works. - -Note that Surface Pro 3 also has the WSID device [2], but it seems to need -more work. This commit only supports Surface 3 yet. - -[1] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_3/dsdt.dsl#L11947-L12011 -[2] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_pro_3/dsdt.dsl#L12164-L12216 - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 10 +++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 77 ++++++++++++++++++- - .../wireless/marvell/mwifiex/pcie_quirks.h | 5 ++ - 3 files changed, 91 insertions(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index fbf2b9d30656..10e66d866a0f 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -2967,6 +2967,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - -+ /* On Surface 3, reset_wsid method removes then re-probes card by -+ * itself. So, need to place it here and skip performing any other -+ * reset-related works. -+ */ -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) { -+ mwifiex_pcie_reset_wsid_quirk(card->dev); -+ /* skip performing any other reset-related works */ -+ return; -+ } -+ - /* We can't afford to wait here; remove() might be waiting on us. If we - * can't grab the device lock, maybe we'll get another chance later. - */ -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index edc739c542fe..f0a6fa0a7ae5 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -9,10 +9,21 @@ - * down, or causes NULL ptr deref). - */ - -+#include - #include - - #include "pcie_quirks.h" - -+/* For reset_wsid quirk */ -+#define ACPI_WSID_PATH "\\_SB.WSID" -+#define WSID_REV 0x0 -+#define WSID_FUNC_WIFI_PWR_OFF 0x1 -+#define WSID_FUNC_WIFI_PWR_ON 0x2 -+/* WSID _DSM UUID: "534ea3bf-fcc2-4e7a-908f-a13978f0c7ef" */ -+static const guid_t wsid_dsm_guid = -+ GUID_INIT(0x534ea3bf, 0xfcc2, 0x4e7a, -+ 0x90, 0x8f, 0xa1, 0x39, 0x78, 0xf0, 0xc7, 0xef); -+ - /* quirk table based on DMI matching */ - static const struct dmi_system_id mwifiex_quirk_table[] = { - { -@@ -87,7 +98,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, -- .driver_data = 0, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, - { - .ident = "Surface Pro 3", -@@ -113,6 +124,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - dev_info(&pdev->dev, "no quirks enabled\n"); - if (card->quirks & QUIRK_FW_RST_D3COLD) - dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) -+ dev_info(&pdev->dev, -+ "quirk reset_wsid for Surface 3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -@@ -169,3 +183,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) - - return 0; - } -+ -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev) -+{ -+ acpi_handle handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n"); -+ -+ status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&pdev->dev, "No ACPI handle for path %s\n", -+ ACPI_WSID_PATH); -+ return -ENODEV; -+ } -+ -+ if (!acpi_has_method(handle, "_DSM")) { -+ dev_err(&pdev->dev, "_DSM method not found\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power off func\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power on func\n"); -+ return -ENODEV; -+ } -+ -+ /* card will be removed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi off...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi off\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ /* card will be re-probed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi on...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi on\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ return 0; -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 8b9dcb5070d8..3ef7440418e3 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -7,6 +7,11 @@ - - /* quirks */ - #define QUIRK_FW_RST_D3COLD BIT(0) -+/* Surface 3 and Surface Pro 3 have the same _DSM method but need to -+ * be handled differently. Currently, only S3 is supported. -+ */ -+#define QUIRK_FW_RST_WSID_S3 BIT(1) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); --- -2.33.0 - -From b4dcc04655839772147cb85c669a5d43b4750177 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Wed, 30 Sep 2020 18:08:24 +0900 -Subject: [PATCH] mwifiex: pcie: (OEMB) add quirk for Surface 3 with broken DMI - table - -(made referring to http://git.osdn.net/view?p=android-x86/kernel.git;a=commitdiff;h=18e2e857c57633b25b3b4120f212224a108cd883) - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI info for the affected Surface 3. - -On affected systems, DMI info will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie_quirks.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index f0a6fa0a7ae5..34dcd84f02a6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -100,6 +100,15 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - { - .ident = "Surface Pro 3", - .matches = { --- -2.33.0 - -From 1f6fe80e4fb0b63b37bb9273455c4846b0a1b0a5 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 4 Oct 2020 00:11:49 +0900 -Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ - -Currently, mwifiex fw will crash after suspend on recent kernel series. -On Windows, it seems that the root port of wifi will never enter D3 state -(stay on D0 state). And on Linux, disabling the D3 state for the -bridge fixes fw crashing after suspend. - -This commit disables the D3 state of root port on driver initialization -and fixes fw crashing after suspend. - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 27 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 10e66d866a0f..767032cd696b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -379,6 +379,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) - { - struct pcie_service_card *card; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - int ret; - - pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", -@@ -420,6 +421,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return -1; - } - -+ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing -+ * after suspend -+ */ -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ parent_pdev->bridge_d3 = false; -+ - return 0; - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 34dcd84f02a6..a2aeb2af907e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -32,7 +32,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5", -@@ -41,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -50,7 +52,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 6", -@@ -58,7 +61,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 1", -@@ -66,7 +70,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 2", -@@ -74,7 +79,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 1", -@@ -82,7 +88,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 2", -@@ -90,7 +97,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface 3", -@@ -136,6 +144,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_FW_RST_WSID_S3) - dev_info(&pdev->dev, - "quirk reset_wsid for Surface 3 enabled\n"); -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ dev_info(&pdev->dev, -+ "quirk no_brigde_d3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 3ef7440418e3..a95ebac06e13 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -11,6 +11,7 @@ - * be handled differently. Currently, only S3 is supported. - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) -+#define QUIRK_NO_BRIDGE_D3 BIT(2) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.33.0 - -From f2ecf364e1e6564442e4cc96b671598041efc1bc Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 3 Nov 2020 13:28:04 +0100 -Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface - devices - -The most recent firmware of the 88W8897 card reports a hardcoded LTR -value to the system during initialization, probably as an (unsuccessful) -attempt of the developers to fix firmware crashes. This LTR value -prevents most of the Microsoft Surface devices from entering deep -powersaving states (either platform C-State 10 or S0ix state), because -the exit latency of that state would be higher than what the card can -tolerate. - -Turns out the card works just the same (including the firmware crashes) -no matter if that hardcoded LTR value is reported or not, so it's kind -of useless and only prevents us from saving power. - -To get rid of those hardcoded LTR reports, it's possible to reset the -PCI bridge device after initializing the cards firmware. I'm not exactly -sure why that works, maybe the power management subsystem of the PCH -resets its stored LTR values when doing a function level reset of the -bridge device. Doing the reset once after starting the wifi firmware -works very well, probably because the firmware only reports that LTR -value a single time during firmware startup. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 31 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 767032cd696b..745946f5a519 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -1755,9 +1755,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) - static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; -+ struct pci_dev *pdev = card->dev; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; - int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; - -+ /* Trigger a function level reset of the PCI bridge device, this makes -+ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value -+ * that prevents the system from entering package C10 and S0ix powersaving -+ * states. -+ * We need to do it here because it must happen after firmware -+ * initialization and this function is called after that is done. -+ */ -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ pci_reset_function(parent_pdev); -+ - /* Write the RX ring read pointer in to reg->rx_rdptr */ - if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | - tx_wrap)) { -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index a2aeb2af907e..6885575826a6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -33,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5", -@@ -43,7 +44,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -53,7 +55,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 6", -@@ -62,7 +65,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 1", -@@ -71,7 +75,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 2", -@@ -80,7 +85,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 1", -@@ -89,7 +95,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 2", -@@ -98,7 +105,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface 3", -@@ -147,6 +155,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_NO_BRIDGE_D3) - dev_info(&pdev->dev, - "quirk no_brigde_d3 enabled\n"); -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index a95ebac06e13..4ec2ae72f632 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -12,6 +12,7 @@ - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) - #define QUIRK_NO_BRIDGE_D3 BIT(2) -+#define QUIRK_DO_FLR_ON_BRIDGE BIT(3) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.33.0 - -From 177ba1313b80323738279b054ef82b8134245ca7 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 25 Mar 2021 11:33:02 +0100 -Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell - 88W8897 - -The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) -is used in a lot of Microsoft Surface devices, and all those devices -suffer from very low 2.4GHz wifi connection speeds while bluetooth is -enabled. The reason for that is that the default passive scanning -interval for Bluetooth Low Energy devices is quite high in Linux -(interval of 60 msec and scan window of 30 msec, see hci_core.c), and -the Marvell chip is known for its bad bt+wifi coexisting performance. - -So decrease that passive scan interval and make the scan window shorter -on this particular device to allow for spending more time transmitting -wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and -the new scan window is 6.25 msec (0xa * 0,625 msec). - -This change has a very large impact on the 2.4GHz wifi speeds and gets -it up to performance comparable with the Windows driver, which seems to -apply a similar quirk. - -The interval and window length were tested and found to work very well -with a lot of Bluetooth Low Energy devices, including the Surface Pen, a -Bluetooth Speaker and two modern Bluetooth headphones. All devices were -discovered immediately after turning them on. Even lower values were -also tested, but they introduced longer delays until devices get -discovered. - -Patchset: mwifiex ---- - drivers/bluetooth/btusb.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index 6d23308119d1..31f255428b4b 100644 ---- a/drivers/bluetooth/btusb.c -+++ b/drivers/bluetooth/btusb.c -@@ -61,6 +61,7 @@ static struct usb_driver btusb_driver; - #define BTUSB_VALID_LE_STATES 0x800000 - #define BTUSB_QCA_WCN6855 0x1000000 - #define BTUSB_INTEL_NEWGEN 0x2000000 -+#define BTUSB_LOWER_LESCAN_INTERVAL BIT(26) - - static const struct usb_device_id btusb_table[] = { - /* Generic Bluetooth USB device */ -@@ -359,6 +360,7 @@ static const struct usb_device_id blacklist_table[] = { - { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, -+ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, - - /* Intel Bluetooth devices */ - { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_NEW | -@@ -4672,6 +4674,19 @@ static int btusb_probe(struct usb_interface *intf, - if (id->driver_info & BTUSB_MARVELL) - hdev->set_bdaddr = btusb_set_bdaddr_marvell; - -+ /* The Marvell 88W8897 combined wifi and bluetooth card is known for -+ * very bad bt+wifi coexisting performance. -+ * -+ * Decrease the passive BT Low Energy scan interval a bit -+ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter -+ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly -+ * higher wifi throughput while passively scanning for BT LE devices. -+ */ -+ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { -+ hdev->le_scan_interval = 0x0190; -+ hdev->le_scan_window = 0x000a; -+ } -+ - if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && - (id->driver_info & BTUSB_MEDIATEK)) { - hdev->setup = btusb_mtk_setup; --- -2.33.0 - -From e445be04f103870d5c32202ec25823f94df46b81 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 12:31:26 +0100 -Subject: [PATCH] mwifiex: Small cleanup for handling virtual interface type - changes - -Handle the obvious invalid virtual interface type changes with a general -check instead of looking at the individual change. - -For type changes from P2P_CLIENT to P2P_GO and the other way round, this -changes the behavior slightly: We now still do nothing, but return --EOPNOTSUPP instead of 0. Now that behavior was incorrect before and -still is, because type changes between these two types are actually -possible and supported, which we'll fix in a following commit. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 39 +++++++------------ - 1 file changed, 14 insertions(+), 25 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 0961f4a5e415..e8deba119ff1 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1141,6 +1141,20 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return -EBUSY; - } - -+ if (type == NL80211_IFTYPE_UNSPECIFIED) { -+ mwifiex_dbg(priv->adapter, INFO, -+ "%s: no new type specified, keeping old type %d\n", -+ dev->name, curr_iftype); -+ return 0; -+ } -+ -+ if (curr_iftype == type) { -+ mwifiex_dbg(priv->adapter, INFO, -+ "%s: interface already is of type %d\n", -+ dev->name, curr_iftype); -+ return 0; -+ } -+ - switch (curr_iftype) { - case NL80211_IFTYPE_ADHOC: - switch (type) { -@@ -1160,12 +1174,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as IBSS\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_ADHOC: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1191,12 +1199,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as STA\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_STATION: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1214,12 +1216,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_P2P_GO: - return mwifiex_change_vif_to_p2p(dev, curr_iftype, - type, params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as AP\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_AP: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1254,13 +1250,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return -EFAULT; - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as P2P\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", --- -2.33.0 - -From 425437fc5c21adcf57d94661d40ccfef976b381a Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 10 Nov 2020 12:49:56 +0100 -Subject: [PATCH] mwifiex: Use non-posted PCI register writes - -On the 88W8897 card it's very important the TX ring write pointer is -updated correctly to its new value before setting the TX ready -interrupt, otherwise the firmware appears to crash (probably because -it's trying to DMA-read from the wrong place). - -Since PCI uses "posted writes" when writing to a register, it's not -guaranteed that a write will happen immediately. That means the pointer -might be outdated when setting the TX ready interrupt, leading to -firmware crashes especially when ASPM L1 and L1 substates are enabled -(because of the higher link latency, the write will probably take -longer). - -So fix those firmware crashes by always forcing non-posted writes. We do -that by simply reading back the register after writing it, just as a lot -of other drivers do. - -There are two reproducers that are fixed with this patch: - -1) During rx/tx traffic and with ASPM L1 substates enabled (the enabled -substates are platform dependent), the firmware crashes and eventually a -command timeout appears in the logs. That crash is fixed by using a -non-posted write in mwifiex_pcie_send_data(). - -2) When sending lots of commands to the card, waking it up from sleep in -very quick intervals, the firmware eventually crashes. That crash -appears to be fixed by some other non-posted write included here. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 745946f5a519..9b62937111a1 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -237,6 +237,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) - - iowrite32(data, card->pci_mmap1 + reg); - -+ /* Do a read-back, which makes the write non-posted, ensuring the -+ * completion before returning. -+ * The firmware of the 88W8897 card is buggy and this avoids crashes. -+ */ -+ ioread32(card->pci_mmap1 + reg); -+ - return 0; - } - --- -2.33.0 - -From 9ff39eead06a323e763d60caea73101790bc9a98 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 12:44:39 +0100 -Subject: [PATCH] mwifiex: Use function to check whether interface type change - is allowed - -Instead of bailing out in the function which is supposed to do the type -change, detect invalid changes beforehand using a generic function and -return an error if the change is not allowed. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 139 ++++++++++++------ - 1 file changed, 92 insertions(+), 47 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index e8deba119ff1..dabc59c47de3 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -939,6 +939,76 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, - return 0; - } - -+static bool -+is_vif_type_change_allowed(struct mwifiex_adapter *adapter, -+ enum nl80211_iftype old_iftype, -+ enum nl80211_iftype new_iftype) -+{ -+ switch (old_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_STATION: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_AP: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return adapter->curr_iface_comb.sta_intf != -+ adapter->iface_limit.sta_intf; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ default: -+ break; -+ } -+ -+ return false; -+} -+ - static int - mwifiex_change_vif_to_p2p(struct net_device *dev, - enum nl80211_iftype curr_iftype, -@@ -955,13 +1025,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - - adapter = priv->adapter; - -- if (adapter->curr_iface_comb.p2p_intf == -- adapter->iface_limit.p2p_intf) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple P2P ifaces\n"); -- return -1; -- } -- - mwifiex_dbg(adapter, INFO, - "%s: changing role to p2p\n", dev->name); - -@@ -1027,15 +1090,6 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - - adapter = priv->adapter; - -- if ((curr_iftype != NL80211_IFTYPE_P2P_CLIENT && -- curr_iftype != NL80211_IFTYPE_P2P_GO) && -- (adapter->curr_iface_comb.sta_intf == -- adapter->iface_limit.sta_intf)) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple station/adhoc ifaces\n"); -- return -1; -- } -- - if (type == NL80211_IFTYPE_STATION) - mwifiex_dbg(adapter, INFO, - "%s: changing role to station\n", dev->name); -@@ -1086,13 +1140,6 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - - adapter = priv->adapter; - -- if (adapter->curr_iface_comb.uap_intf == -- adapter->iface_limit.uap_intf) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple AP ifaces\n"); -- return -1; -- } -- - mwifiex_dbg(adapter, INFO, - "%s: changing role to AP\n", dev->name); - -@@ -1155,6 +1202,13 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return 0; - } - -+ if (!is_vif_type_change_allowed(priv->adapter, curr_iftype, type)) { -+ mwifiex_dbg(priv->adapter, ERROR, -+ "%s: change from type %d to %d is not allowed\n", -+ dev->name, curr_iftype, type); -+ return -EOPNOTSUPP; -+ } -+ - switch (curr_iftype) { - case NL80211_IFTYPE_ADHOC: - switch (type) { -@@ -1175,12 +1229,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_STATION: - switch (type) { - case NL80211_IFTYPE_ADHOC: -@@ -1200,12 +1251,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_AP: - switch (type) { - case NL80211_IFTYPE_ADHOC: -@@ -1217,12 +1265,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_p2p(dev, curr_iftype, - type, params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_P2P_CLIENT: - case NL80211_IFTYPE_P2P_GO: - switch (type) { -@@ -1251,21 +1296,21 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: unknown iftype: %d\n", -- dev->name, dev->ieee80211_ptr->iftype); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } - - - return 0; -+ -+errnotsupp: -+ mwifiex_dbg(priv->adapter, ERROR, -+ "unsupported interface type transition: %d to %d\n", -+ curr_iftype, type); -+ return -EOPNOTSUPP; - } - - static void --- -2.33.0 - -From 0b0b7f7c01f2ba2e12a9c0ca24ee947bccd095c8 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 13:33:04 +0100 -Subject: [PATCH] mwifiex: Run SET_BSS_MODE when changing from P2P to STATION - vif-type - -We currently handle changing from the P2P to the STATION virtual -interface type slightly different than changing from P2P to ADHOC: When -changing to STATION, we don't send the SET_BSS_MODE command. We do send -that command on all other type-changes though, and it probably makes -sense to send the command since after all we just changed our BSS_MODE. -Looking at prior changes to this part of the code, it seems that this is -simply a leftover from old refactorings. - -Since sending the SET_BSS_MODE command is the only difference between -mwifiex_change_vif_to_sta_adhoc() and the current code, we can now use -mwifiex_change_vif_to_sta_adhoc() for both switching to ADHOC and -STATION interface type. - -This does not fix any particular bug and just "looked right", so there's -a small chance it might be a regression. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 22 ++++--------------- - 1 file changed, 4 insertions(+), 18 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index dabc59c47de3..146aabe14753 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1270,29 +1270,15 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - - case NL80211_IFTYPE_P2P_CLIENT: - case NL80211_IFTYPE_P2P_GO: -+ if (mwifiex_cfg80211_deinit_p2p(priv)) -+ return -EFAULT; -+ - switch (type) { -- case NL80211_IFTYPE_STATION: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; -- priv->adapter->curr_iface_comb.p2p_intf--; -- priv->adapter->curr_iface_comb.sta_intf++; -- dev->ieee80211_ptr->iftype = type; -- if (mwifiex_deinit_priv_params(priv)) -- return -1; -- if (mwifiex_init_new_priv_params(priv, dev, type)) -- return -1; -- if (mwifiex_sta_init_cmd(priv, false, false)) -- return -1; -- break; - case NL80211_IFTYPE_ADHOC: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; -+ case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); -- break; - case NL80211_IFTYPE_AP: -- if (mwifiex_cfg80211_deinit_p2p(priv)) -- return -EFAULT; - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: --- -2.33.0 - -From d26659f19dd73e3fc27d61f7c0cd62cbb58972e6 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 14:42:54 +0100 -Subject: [PATCH] mwifiex: Use helper function for counting interface types - -Use a small helper function to increment and decrement the counter of -the interface types we currently manage. This makes the code that -actually changes and sets up the interface type a bit less messy and -also helps avoiding mistakes in case someone increments/decrements a -counter wrongly. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 110 ++++++------------ - 1 file changed, 35 insertions(+), 75 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 146aabe14753..8b9517c243c8 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1009,6 +1009,32 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, - return false; - } - -+static void -+update_vif_type_counter(struct mwifiex_adapter *adapter, -+ enum nl80211_iftype iftype, -+ int change) -+{ -+ switch (iftype) { -+ case NL80211_IFTYPE_UNSPECIFIED: -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ adapter->curr_iface_comb.sta_intf += change; -+ break; -+ case NL80211_IFTYPE_AP: -+ adapter->curr_iface_comb.uap_intf += change; -+ break; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ adapter->curr_iface_comb.p2p_intf += change; -+ break; -+ default: -+ mwifiex_dbg(adapter, ERROR, -+ "%s: Unsupported iftype passed: %d\n", -+ __func__, iftype); -+ break; -+ } -+} -+ - static int - mwifiex_change_vif_to_p2p(struct net_device *dev, - enum nl80211_iftype curr_iftype, -@@ -1056,19 +1082,8 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.p2p_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; - - return 0; -@@ -1107,20 +1122,10 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.sta_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; -+ - return 0; - } - -@@ -1153,20 +1158,8 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.uap_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; - return 0; - } -@@ -3128,23 +3121,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, - mwifiex_dev_debugfs_init(priv); - #endif - -- switch (type) { -- case NL80211_IFTYPE_UNSPECIFIED: -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf++; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf++; -- break; -- case NL80211_IFTYPE_P2P_CLIENT: -- adapter->curr_iface_comb.p2p_intf++; -- break; -- default: -- /* This should be dead code; checked above */ -- mwifiex_dbg(adapter, ERROR, "type not supported\n"); -- return ERR_PTR(-EINVAL); -- } -+ update_vif_type_counter(adapter, type, +1); - - return &priv->wdev; - -@@ -3210,24 +3187,7 @@ int mwifiex_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) - /* Clear the priv in adapter */ - priv->netdev = NULL; - -- switch (priv->bss_mode) { -- case NL80211_IFTYPE_UNSPECIFIED: -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- default: -- mwifiex_dbg(adapter, ERROR, -- "del_virtual_intf: type not supported\n"); -- break; -- } -+ update_vif_type_counter(adapter, priv->bss_mode, -1); - - priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; - --- -2.33.0 - -From da5517dd6d7f482fe4a0328ac007fdacc040a316 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:56:58 +0100 -Subject: [PATCH] mwifiex: Update virtual interface counters right after - setting bss_type - -In mwifiex_init_new_priv_params() we update our private driver state to -reflect the currently selected virtual interface type. Most notably we -set the bss_mode to the mode we're going to put the firmware in. - -Now after we updated the driver state we actually start talking to the -firmware and instruct it to set up the new mode. Those commands can and -will sometimes fail, in which case we return with an error from -mwifiex_change_vif_to_*. We currently update our virtual interface type -counters after this return, which means the code is never reached when a -firmware error happens and we never update the counters. Since we have -updated our bss_mode earlier though, the counters now no longer reflect -the actual state of the driver. - -This will break things on the next virtual interface change, because the -virtual interface type we're switching away from didn't get its counter -incremented, and we end up decrementing a 0-counter. - -To fix this, simply update the virtual interface type counters right -after updating our driver structures, so that they are always in sync. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 25 +++++++++++-------- - 1 file changed, 14 insertions(+), 11 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 8b9517c243c8..f2797102c5a2 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1059,6 +1059,10 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; - -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - switch (type) { - case NL80211_IFTYPE_P2P_CLIENT: - if (mwifiex_cfg80211_init_p2p_client(priv)) -@@ -1082,10 +1086,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; -- - return 0; - } - -@@ -1116,16 +1116,17 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - return -1; - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; -+ -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, - HostCmd_ACT_GEN_SET, 0, NULL, true)) - return -1; - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; -- - return 0; - } - -@@ -1152,15 +1153,17 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - return -1; - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; -+ -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, - HostCmd_ACT_GEN_SET, 0, NULL, true)) - return -1; - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; - return 0; - } - /* --- -2.33.0 - -From 1764c27f1e031e51c6ca03937aaf93897bdedff5 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 13:42:40 +0100 -Subject: [PATCH] mwifiex: Allow switching interface type from P2P_CLIENT to - P2P_GO - -It's possible to change virtual interface type between P2P_CLIENT and -P2P_GO, the card supports that just fine, and it happens for example -when using miracast with the miraclecast software. - -So allow type changes between P2P_CLIENT and P2P_GO and simply call into -mwifiex_change_vif_to_p2p(), which handles this just fine. We have to -call mwifiex_cfg80211_deinit_p2p() before though to make sure the old -p2p mode is properly uninitialized. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 36 +++++++++++++++++++ - 1 file changed, 36 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index f2797102c5a2..ed4041ff9c89 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -990,11 +990,26 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, - } - - case NL80211_IFTYPE_P2P_CLIENT: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_P2P_GO: -+ return true; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ - case NL80211_IFTYPE_P2P_GO: - switch (new_iftype) { - case NL80211_IFTYPE_ADHOC: - case NL80211_IFTYPE_STATION: - return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ return true; - case NL80211_IFTYPE_AP: - return adapter->curr_iface_comb.uap_intf != - adapter->iface_limit.uap_intf; -@@ -1265,6 +1280,24 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - } - - case NL80211_IFTYPE_P2P_CLIENT: -+ if (mwifiex_cfg80211_deinit_p2p(priv)) -+ return -EFAULT; -+ -+ switch (type) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, -+ type, params); -+ case NL80211_IFTYPE_P2P_GO: -+ return mwifiex_change_vif_to_p2p(dev, curr_iftype, -+ type, params); -+ case NL80211_IFTYPE_AP: -+ return mwifiex_change_vif_to_ap(dev, curr_iftype, type, -+ params); -+ default: -+ goto errnotsupp; -+ } -+ - case NL80211_IFTYPE_P2P_GO: - if (mwifiex_cfg80211_deinit_p2p(priv)) - return -EFAULT; -@@ -1274,6 +1307,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); -+ case NL80211_IFTYPE_P2P_CLIENT: -+ return mwifiex_change_vif_to_p2p(dev, curr_iftype, -+ type, params); - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); --- -2.33.0 - -From 7ebd8e0e7f643e5d90e7e21dbff7f59c71010d40 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:31:08 +0100 -Subject: [PATCH] mwifiex: Handle interface type changes from AP to STATION - -Looks like this case was simply overseen, so handle it, too. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index ed4041ff9c89..64caa5c4350d 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1268,6 +1268,7 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - switch (type) { - case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); - break; --- -2.33.0 - -From acd033e87fc5e3aa282e616ff787d2696600881e Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:32:16 +0100 -Subject: [PATCH] mwifiex: Properly initialize private structure on interface - type changes - -When creating a new virtual interface in mwifiex_add_virtual_intf(), we -update our internal driver states like bss_type, bss_priority, bss_role -and bss_mode to reflect the mode the firmware will be set to. - -When switching virtual interface mode using -mwifiex_init_new_priv_params() though, we currently only update bss_mode -and bss_role. In order for the interface mode switch to actually work, -we also need to update bss_type to its proper value, so do that. - -This fixes a crash of the firmware (because the driver tries to execute -commands that are invalid in AP mode) when switching from station mode -to AP mode. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 +++++++--- - 1 file changed, 7 insertions(+), 3 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 64caa5c4350d..0eb31201a82b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -908,16 +908,20 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, - switch (type) { - case NL80211_IFTYPE_STATION: - case NL80211_IFTYPE_ADHOC: -- priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_type = MWIFIEX_BSS_TYPE_STA; - break; - case NL80211_IFTYPE_P2P_CLIENT: -- priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_role = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - break; - case NL80211_IFTYPE_P2P_GO: -- priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - break; - case NL80211_IFTYPE_AP: - priv->bss_role = MWIFIEX_BSS_ROLE_UAP; -+ priv->bss_type = MWIFIEX_BSS_TYPE_UAP; - break; - default: - mwifiex_dbg(adapter, ERROR, --- -2.33.0 - -From 4b991744de0e6625b83ea90456ce0071dfb73b84 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sat, 27 Mar 2021 12:19:14 +0100 -Subject: [PATCH] mwifiex: Fix copy-paste mistake when creating virtual - interface - -The BSS priority here for a new P2P_CLIENT device was accidentally set -to an enum that's certainly not meant for this. Since -MWIFIEX_BSS_ROLE_STA is 0 anyway, we can just set the bss_priority to 0 -instead here. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 0eb31201a82b..d62a20de3ada 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -3054,7 +3054,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, - priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - - priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II; -- priv->bss_priority = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_priority = 0; - priv->bss_role = MWIFIEX_BSS_ROLE_STA; - priv->bss_started = 0; - --- -2.33.0 - -From 60fa4ef19a271e44475cd19e16316de749b450c8 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sun, 28 Mar 2021 21:10:06 +0200 -Subject: [PATCH] mwifiex: Try waking the firmware until we get an interrupt - -It seems that the firmware of the 88W8897 card sometimes ignores or -misses when we try to wake it up by reading the firmware status -register. This leads to the firmware wakeup timeout expiring and the -driver resetting the card because we assume the firmware has hung up or -crashed (unfortunately that's not unlikely with this card). - -Turns out that most of the time the firmware actually didn't hang up, -but simply "missed" our wakeup request and doesn't send us an AWAKE -event. - -Trying again to read the firmware status register after a short timeout -usually makes the firmware wake we up as expected, so add a small retry -loop to mwifiex_pm_wakeup_card() that looks at the interrupt status to -check whether the card woke up. - -The number of tries and timeout lengths for this were determined -experimentally: The firmware usually takes about 500 us to wake up -after we attempt to read the status register. In some cases where the -firmware is very busy (for example while doing a bluetooth scan) it -might even miss our requests for multiple milliseconds, which is why -after 15 tries the waiting time gets increased to 10 ms. The maximum -number of tries it took to wake the firmware when testing this was -around 20, so a maximum number of 50 tries should give us plenty of -safety margin. - -A good reproducer for this issue is letting the firmware sleep and wake -up in very short intervals, for example by pinging an device on the -network every 0.1 seconds. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 29 ++++++++++++++++----- - 1 file changed, 23 insertions(+), 6 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 9b62937111a1..0a52549a1703 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -665,6 +665,7 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; -+ int n_tries = 0; - - mwifiex_dbg(adapter, EVENT, - "event: Wakeup device...\n"); -@@ -672,12 +673,28 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) - if (reg->sleep_cookie) - mwifiex_pcie_dev_wakeup_delay(adapter); - -- /* Accessing fw_status register will wakeup device */ -- if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) { -- mwifiex_dbg(adapter, ERROR, -- "Writing fw_status register failed\n"); -- return -1; -- } -+ /* Access the fw_status register to wake up the device. -+ * Since the 88W8897 firmware sometimes appears to ignore or miss -+ * that wakeup request, we continue trying until we receive an -+ * interrupt from the card. -+ */ -+ do { -+ if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) { -+ mwifiex_dbg(adapter, ERROR, -+ "Writing fw_status register failed\n"); -+ return -1; -+ } -+ -+ n_tries++; -+ -+ if (n_tries <= 15) -+ usleep_range(400, 700); -+ else -+ msleep(10); -+ } while (n_tries <= 50 && READ_ONCE(adapter->int_status) == 0); -+ -+ mwifiex_dbg(adapter, EVENT, -+ "event: Tried %d times until firmware woke up\n", n_tries); - - if (reg->sleep_cookie) { - mwifiex_pcie_dev_wakeup_delay(adapter); --- -2.33.0 - -From fc9f5cb203dcc5aeb81e9bb9d9c183f45b6b8b87 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:30:28 +0200 -Subject: [PATCH] mwifiex: Deactive host sleep using HSCFG after it was - activated manually - -When powersaving (so either wifi powersaving or deep sleep, depending on -which state the firmware is in) is disabled, the way the firmware goes -into host sleep is different: Usually the firmware implicitely enters -host sleep on the next SLEEP event we get when we configured host sleep -via HSCFG before. When powersaving is disabled though, there are no -SLEEP events, the way we enter host sleep in that case is different: The -firmware will send us a HS_ACT_REQ event and after that we "manually" -make the firmware enter host sleep by sending it another HSCFG command -with the action HS_ACTIVATE. - -Now waking up from host sleep appears to be different depending on -whether powersaving is enabled again: When powersaving is enabled, the -firmware implicitely leaves host sleep as soon as it wakes up and sends -us an AWAKE event. When powersaving is disabled though, it apparently -doesn't implicitely leave host sleep, but instead we need to send it a -HSCFG command with the HS_CONFIGURE action and the HS_CFG_CANCEL -condition. We didn't do that so far, which is why waking up from host -sleep was broken when powersaving is disabled. - -So add some additional state to mwifiex_adapter where we keep track of -whether host sleep was activated manually via HS_ACTIVATE, and if that -was the case, deactivate it manually again via HS_CFG_CANCEL. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cmdevt.c | 21 +++++++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.c | 18 ++++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmd.c | 4 ++++ - 4 files changed, 44 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cmdevt.c b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -index 3a11342a6bde..5487df8f994d 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cmdevt.c -+++ b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -@@ -608,6 +608,11 @@ int mwifiex_send_cmd(struct mwifiex_private *priv, u16 cmd_no, - return -1; - } - -+ if (priv->adapter->hs_activated_manually && -+ cmd_no != HostCmd_CMD_802_11_HS_CFG_ENH) { -+ mwifiex_cancel_hs(priv, MWIFIEX_ASYNC_CMD); -+ priv->adapter->hs_activated_manually = false; -+ } - - /* Get a new command node */ - cmd_node = mwifiex_get_cmd_node(adapter); -@@ -714,6 +719,15 @@ mwifiex_insert_cmd_to_pending_q(struct mwifiex_adapter *adapter, - } - } - -+ /* Same with exit host sleep cmd, luckily that can't happen at the same time as EXIT_PS */ -+ if (command == HostCmd_CMD_802_11_HS_CFG_ENH) { -+ struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = -+ &host_cmd->params.opt_hs_cfg; -+ -+ if (le16_to_cpu(hs_cfg->action) == HS_ACTIVATE) -+ add_tail = false; -+ } -+ - spin_lock_bh(&adapter->cmd_pending_q_lock); - if (add_tail) - list_add_tail(&cmd_node->list, &adapter->cmd_pending_q); -@@ -1216,6 +1230,13 @@ mwifiex_process_hs_config(struct mwifiex_adapter *adapter) - __func__); - - adapter->if_ops.wakeup(adapter); -+ -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv (adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - adapter->hs_activated = false; - clear_bit(MWIFIEX_IS_HS_CONFIGURED, &adapter->work_flags); - clear_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags); -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index 17399d4aa129..1fbf5ba1042b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -401,6 +401,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !adapter->scan_processing) && - !adapter->data_sent && - !skb_queue_empty(&adapter->tx_data_q)) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_process_tx_queue(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -@@ -418,6 +424,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !mwifiex_bypass_txlist_empty(adapter) && - !mwifiex_is_tdls_chan_switching - (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_process_bypass_tx(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -@@ -434,6 +446,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !adapter->data_sent && !mwifiex_wmm_lists_empty(adapter) && - !mwifiex_is_tdls_chan_switching - (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_wmm_process_tx(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 5923c5c14c8d..90012cbcfd15 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -986,6 +986,7 @@ struct mwifiex_adapter { - struct timer_list wakeup_timer; - struct mwifiex_hs_config_param hs_cfg; - u8 hs_activated; -+ u8 hs_activated_manually; - u16 hs_activate_wait_q_woken; - wait_queue_head_t hs_activate_wait_q; - u8 event_body[MAX_EVENT_SIZE]; -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -index d3a968ef21ef..76db9a7b8199 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -@@ -396,6 +396,10 @@ mwifiex_cmd_802_11_hs_cfg(struct mwifiex_private *priv, - if (hs_activate) { - hs_cfg->action = cpu_to_le16(HS_ACTIVATE); - hs_cfg->params.hs_activate.resp_ctrl = cpu_to_le16(RESP_NEEDED); -+ -+ adapter->hs_activated_manually = true; -+ mwifiex_dbg(priv->adapter, CMD, -+ "cmd: Activating host sleep manually\n"); - } else { - hs_cfg->action = cpu_to_le16(HS_CONFIGURE); - hs_cfg->params.hs_config.conditions = hscfg_param->conditions; --- -2.33.0 - -From 4256d842194dbff0f43d0440228b7ad88f5e2512 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:23:05 +0200 -Subject: [PATCH] mwifiex: Add quirk to disable deep sleep with certain - hardware revision - -The 88W8897 pcie card with the hardware revision 20 apparently has a -hardware issue where the card wakes up from deep sleep randomly and very -often, somewhat depending on the card activity, maybe the hardware has a -floating wakeup pin or something. - -Those continuous wakeups prevent the card from entering host sleep when -the computer suspends. And because the host won't answer to events from -the card anymore while it's suspended, the firmwares internal -powersaving state machine seems to get confused and the card can't sleep -anymore at all after that. - -Since we can't work around that hardware bug in the firmware, let's -get the hardware revision string from the firmware and match it with -known bad revisions. Then disable auto deep sleep for those revisions, -which makes sure we no longer get those spurious wakeups. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/main.c | 14 ++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmdresp.c | 16 ++++++++++++++++ - 3 files changed, 31 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index 1fbf5ba1042b..be40813ffa5c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -226,6 +226,19 @@ static int mwifiex_process_rx(struct mwifiex_adapter *adapter) - return 0; - } - -+static void maybe_quirk_fw_disable_ds(struct mwifiex_adapter *adapter) -+{ -+ struct mwifiex_private *priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA); -+ struct mwifiex_ver_ext ver_ext; -+ -+ set_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &adapter->work_flags); -+ -+ memset(&ver_ext, 0, sizeof(ver_ext)); -+ ver_ext.version_str_sel = 1; -+ mwifiex_send_cmd(priv, HostCmd_CMD_VERSION_EXT, -+ HostCmd_ACT_GEN_GET, 0, &ver_ext, false); -+} -+ - /* - * The main process. - * -@@ -356,6 +369,7 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - if (adapter->hw_status == MWIFIEX_HW_STATUS_INIT_DONE) { - adapter->hw_status = MWIFIEX_HW_STATUS_READY; - mwifiex_init_fw_complete(adapter); -+ maybe_quirk_fw_disable_ds(adapter); - } - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 90012cbcfd15..1e829d84b1f6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -524,6 +524,7 @@ enum mwifiex_adapter_work_flags { - MWIFIEX_IS_SUSPENDED, - MWIFIEX_IS_HS_CONFIGURED, - MWIFIEX_IS_HS_ENABLING, -+ MWIFIEX_IS_REQUESTING_FW_VEREXT, - }; - - struct mwifiex_band_config { -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -index 6b5d35d9e69f..8e49ebca1847 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -@@ -708,6 +708,22 @@ static int mwifiex_ret_ver_ext(struct mwifiex_private *priv, - { - struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext; - -+ if (test_and_clear_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &priv->adapter->work_flags)) { -+ if (strncmp(ver_ext->version_str, "ChipRev:20, BB:9b(10.00), RF:40(21)", 128) == 0) { -+ struct mwifiex_ds_auto_ds auto_ds = { -+ .auto_ds = DEEP_SLEEP_OFF, -+ }; -+ -+ mwifiex_dbg(priv->adapter, MSG, -+ "Bad HW revision detected, disabling deep sleep\n"); -+ -+ mwifiex_send_cmd(priv, HostCmd_CMD_802_11_PS_MODE_ENH, -+ DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, false); -+ } -+ -+ return 0; -+ } -+ - if (version_ext) { - version_ext->version_str_sel = ver_ext->version_str_sel; - memcpy(version_ext->version_str, ver_ext->version_str, --- -2.33.0 - -From 3aa90b14c3d2e24c96903e861f80e3dfcab97d84 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 15:17:07 +0100 -Subject: [PATCH] mwifiex: Don't log error on suspend if wake-on-wlan is - disabled - -It's not an error if someone chooses to put their computer to sleep, not -wanting it to wake up because the person next door has just discovered -what a magic packet is. So change the loglevel of this annoying message -from ERROR to INFO. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index d62a20de3ada..18b1a6d54bc8 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -3494,7 +3494,7 @@ static int mwifiex_cfg80211_suspend(struct wiphy *wiphy, - } - - if (!wowlan) { -- mwifiex_dbg(adapter, ERROR, -+ mwifiex_dbg(adapter, INFO, - "None of the WOWLAN triggers enabled\n"); - ret = 0; - goto done; --- -2.33.0 - -From 923de1ab544014df5112898208ce6d226910368b Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sun, 28 Mar 2021 21:42:54 +0200 -Subject: [PATCH] mwifiex: Log an error on command failure during key-material - upload - -Sometimes the KEY_MATERIAL command can fail with the 88W8897 firmware -(when this happens exactly seems pretty random). This appears to prevent -the access point from starting, so it seems like a good idea to log an -error in that case. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 ++++++++-- - 1 file changed, 8 insertions(+), 2 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 18b1a6d54bc8..c00791701d78 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -519,8 +519,14 @@ mwifiex_cfg80211_set_default_mgmt_key(struct wiphy *wiphy, - encrypt_key.is_igtk_def_key = true; - eth_broadcast_addr(encrypt_key.mac_addr); - -- return mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, -- HostCmd_ACT_GEN_SET, true, &encrypt_key, true); -+ if (mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, -+ HostCmd_ACT_GEN_SET, true, &encrypt_key, true)) { -+ mwifiex_dbg(priv->adapter, ERROR, -+ "Sending KEY_MATERIAL command failed\n"); -+ return -1; -+ } -+ -+ return 0; - } - - /* --- -2.33.0 - -From 67f7959c5bc15d63e3c359bf09c4583ff76d4b40 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:44:03 +0200 -Subject: [PATCH] mwifiex: Fix an incorrect comment - -We're sending DELBA requests here, not ADDBA requests. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/11n.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c -index 6696bce56178..b0695432b26a 100644 ---- a/drivers/net/wireless/marvell/mwifiex/11n.c -+++ b/drivers/net/wireless/marvell/mwifiex/11n.c -@@ -125,7 +125,7 @@ int mwifiex_ret_11n_delba(struct mwifiex_private *priv, - tx_ba_tbl->ra); - } else { /* - * In case of failure, recreate the deleted stream in case -- * we initiated the ADDBA -+ * we initiated the DELBA - */ - if (!INITIATOR_BIT(del_ba_param_set)) - return 0; --- -2.33.0 - -From ff99f8862a8087a38d48328be5802561f9e6e1e6 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:45:59 +0200 -Subject: [PATCH] mwifiex: Send DELBA requests according to spec - -We're currently failing to set the initiator bit for DELBA requests: -While we set the bit on our del_ba_param_set bitmask, we forget to -actually copy that bitmask over to the command struct, which means we -never actually set the initiator bit. - -Fix that and copy the bitmask over to the host_cmd_ds_11n_delba command -struct. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/11n.c | 5 +++-- - 1 file changed, 3 insertions(+), 2 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c -index b0695432b26a..9ff2058bcd7e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/11n.c -+++ b/drivers/net/wireless/marvell/mwifiex/11n.c -@@ -657,14 +657,15 @@ int mwifiex_send_delba(struct mwifiex_private *priv, int tid, u8 *peer_mac, - uint16_t del_ba_param_set; - - memset(&delba, 0, sizeof(delba)); -- delba.del_ba_param_set = cpu_to_le16(tid << DELBA_TID_POS); - -- del_ba_param_set = le16_to_cpu(delba.del_ba_param_set); -+ del_ba_param_set = tid << DELBA_TID_POS; -+ - if (initiator) - del_ba_param_set |= IEEE80211_DELBA_PARAM_INITIATOR_MASK; - else - del_ba_param_set &= ~IEEE80211_DELBA_PARAM_INITIATOR_MASK; - -+ delba.del_ba_param_set = cpu_to_le16(del_ba_param_set); - memcpy(&delba.peer_mac_addr, peer_mac, ETH_ALEN); - - /* We don't wait for the response of this command */ --- -2.33.0 - -From 1adb5735763ba2d52458392bb23ff78aade00086 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:57:41 +0200 -Subject: [PATCH] mwifiex: Ignore BTCOEX events from the firmware - -The firmware of the pcie 88W8897 chip sends those events very -unreliably, which means we sometimes end up actually capping the window -size while bluetooth is disabled, artifically limiting wifi speeds even -though it's not needed. - -Since we can't fix the firmware, let's just ignore those events, it -seems that the Windows driver also doesn't change the rx/tx block ack -buffer sizes when bluetooth gets enabled or disabled, so this is -consistent with the Windows driver. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/sta_event.c | 4 +--- - 1 file changed, 1 insertion(+), 3 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_event.c b/drivers/net/wireless/marvell/mwifiex/sta_event.c -index 68c63268e2e6..933111a3511c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_event.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_event.c -@@ -1057,9 +1057,7 @@ int mwifiex_process_sta_event(struct mwifiex_private *priv) - adapter->event_skb); - break; - case EVENT_BT_COEX_WLAN_PARA_CHANGE: -- dev_dbg(adapter->dev, "EVENT: BT coex wlan param update\n"); -- mwifiex_bt_coex_wlan_param_update_event(priv, -- adapter->event_skb); -+ dev_dbg(adapter->dev, "EVENT: ignoring BT coex wlan param update\n"); - break; - case EVENT_RXBA_SYNC: - dev_dbg(adapter->dev, "EVENT: RXBA_SYNC\n"); --- -2.33.0 - diff --git a/patches/5.13/0003-ath10k.patch b/patches/5.13/0003-ath10k.patch deleted file mode 100644 index f96898ea3..000000000 --- a/patches/5.13/0003-ath10k.patch +++ /dev/null @@ -1,121 +0,0 @@ -From 90951c54a948f49883041a01d7bb9d7f2df45b2d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 27 Feb 2021 00:45:52 +0100 -Subject: [PATCH] ath10k: Add module parameters to override board files - -Some Surface devices, specifically the Surface Go and AMD version of the -Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better -with a different board file, as it seems that the firmeware included -upstream is buggy. - -As it is generally not a good idea to randomly overwrite files, let -alone doing so via packages, we add module parameters to override those -file names in the driver. This allows us to package/deploy the override -via a modprobe.d config. - -Signed-off-by: Maximilian Luz -Patchset: ath10k ---- - drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++ - 1 file changed, 58 insertions(+) - -diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c -index 2f9be182fbfb..84ae17af3f98 100644 ---- a/drivers/net/wireless/ath/ath10k/core.c -+++ b/drivers/net/wireless/ath/ath10k/core.c -@@ -35,6 +35,9 @@ static bool skip_otp; - static bool rawmode; - static bool fw_diag_log; - -+static char *override_board = ""; -+static char *override_board2 = ""; -+ - unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | - BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); - -@@ -47,6 +50,9 @@ module_param(rawmode, bool, 0644); - module_param(fw_diag_log, bool, 0644); - module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); - -+module_param(override_board, charp, 0644); -+module_param(override_board2, charp, 0644); -+ - MODULE_PARM_DESC(debug_mask, "Debugging mask"); - MODULE_PARM_DESC(uart_print, "Uart target debugging"); - MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); -@@ -55,6 +61,9 @@ MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); - MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); - MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); - -+MODULE_PARM_DESC(override_board, "Override for board.bin file"); -+MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); -+ - static const struct ath10k_hw_params ath10k_hw_params_list[] = { - { - .id = QCA988X_HW_2_0_VERSION, -@@ -826,6 +835,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) - return 0; - } - -+static const char *ath10k_override_board_fw_file(struct ath10k *ar, -+ const char *file) -+{ -+ if (strcmp(file, "board.bin") == 0) { -+ if (strcmp(override_board, "") == 0) -+ return file; -+ -+ if (strcmp(override_board, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", -+ override_board); -+ -+ return override_board; -+ } -+ -+ if (strcmp(file, "board-2.bin") == 0) { -+ if (strcmp(override_board2, "") == 0) -+ return file; -+ -+ if (strcmp(override_board2, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", -+ override_board2); -+ -+ return override_board2; -+ } -+ -+ return file; -+} -+ - static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - const char *dir, - const char *file) -@@ -840,6 +885,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - if (dir == NULL) - dir = "."; - -+ /* HACK: Override board.bin and board-2.bin files if specified. -+ * -+ * Some Surface devices perform better with a different board -+ * configuration. To this end, one would need to replace the board.bin -+ * file with the modified config and remove the board-2.bin file. -+ * Unfortunately, that's not a solution that we can easily package. So -+ * we add module options to perform these overrides here. -+ */ -+ -+ file = ath10k_override_board_fw_file(ar, file); -+ if (!file) -+ return ERR_PTR(-ENOENT); -+ - snprintf(filename, sizeof(filename), "%s/%s", dir, file); - ret = firmware_request_nowarn(&fw, filename, ar->dev); - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", --- -2.33.0 - diff --git a/patches/5.13/0004-ipts.patch b/patches/5.13/0004-ipts.patch deleted file mode 100644 index 51821e2f4..000000000 --- a/patches/5.13/0004-ipts.patch +++ /dev/null @@ -1,1503 +0,0 @@ -From 804550e95b9d5f3e36dd58b4b89c22b1372a63d3 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 30 Jul 2020 13:21:53 +0200 -Subject: [PATCH] misc: mei: Add missing IPTS device IDs - -Patchset: ipts ---- - drivers/misc/mei/hw-me-regs.h | 1 + - drivers/misc/mei/pci-me.c | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h -index cb34925e10f1..2b3f8073a3ec 100644 ---- a/drivers/misc/mei/hw-me-regs.h -+++ b/drivers/misc/mei/hw-me-regs.h -@@ -92,6 +92,7 @@ - #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ - - #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ -+#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ - - #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ - -diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c -index c3393b383e59..0098f98426c1 100644 ---- a/drivers/misc/mei/pci-me.c -+++ b/drivers/misc/mei/pci-me.c -@@ -96,6 +96,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { - {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, -+ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_H, MEI_ME_PCH15_SPS_CFG)}, --- -2.33.0 - -From 5cfa0081c6f400ee1afd373480ac7b0f8b0550bb Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 6 Aug 2020 11:20:41 +0200 -Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus - -Based on linux-surface/intel-precise-touch@3f362c - -Signed-off-by: Dorian Stoll -Patchset: ipts ---- - drivers/misc/Kconfig | 1 + - drivers/misc/Makefile | 1 + - drivers/misc/ipts/Kconfig | 17 ++ - drivers/misc/ipts/Makefile | 12 ++ - drivers/misc/ipts/context.h | 47 +++++ - drivers/misc/ipts/control.c | 113 +++++++++++ - drivers/misc/ipts/control.h | 24 +++ - drivers/misc/ipts/mei.c | 125 ++++++++++++ - drivers/misc/ipts/protocol.h | 347 ++++++++++++++++++++++++++++++++++ - drivers/misc/ipts/receiver.c | 224 ++++++++++++++++++++++ - drivers/misc/ipts/receiver.h | 16 ++ - drivers/misc/ipts/resources.c | 128 +++++++++++++ - drivers/misc/ipts/resources.h | 17 ++ - drivers/misc/ipts/uapi.c | 208 ++++++++++++++++++++ - drivers/misc/ipts/uapi.h | 47 +++++ - 15 files changed, 1327 insertions(+) - create mode 100644 drivers/misc/ipts/Kconfig - create mode 100644 drivers/misc/ipts/Makefile - create mode 100644 drivers/misc/ipts/context.h - create mode 100644 drivers/misc/ipts/control.c - create mode 100644 drivers/misc/ipts/control.h - create mode 100644 drivers/misc/ipts/mei.c - create mode 100644 drivers/misc/ipts/protocol.h - create mode 100644 drivers/misc/ipts/receiver.c - create mode 100644 drivers/misc/ipts/receiver.h - create mode 100644 drivers/misc/ipts/resources.c - create mode 100644 drivers/misc/ipts/resources.h - create mode 100644 drivers/misc/ipts/uapi.c - create mode 100644 drivers/misc/ipts/uapi.h - -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index f4fb5c52b863..0e5a2fe6bffe 100644 ---- a/drivers/misc/Kconfig -+++ b/drivers/misc/Kconfig -@@ -464,4 +464,5 @@ source "drivers/misc/cardreader/Kconfig" - source "drivers/misc/habanalabs/Kconfig" - source "drivers/misc/uacce/Kconfig" - source "drivers/misc/pvpanic/Kconfig" -+source "drivers/misc/ipts/Kconfig" - endmenu -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index e92a56d4442f..7adce5540183 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -57,3 +57,4 @@ obj-$(CONFIG_HABANA_AI) += habanalabs/ - obj-$(CONFIG_UACCE) += uacce/ - obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o - obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o -+obj-$(CONFIG_MISC_IPTS) += ipts/ -diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig -new file mode 100644 -index 000000000000..83e2a930c396 ---- /dev/null -+++ b/drivers/misc/ipts/Kconfig -@@ -0,0 +1,17 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+ -+config MISC_IPTS -+ tristate "Intel Precise Touch & Stylus" -+ depends on INTEL_MEI -+ help -+ Say Y here if your system has a touchscreen using Intels -+ Precise Touch & Stylus (IPTS) technology. -+ -+ If unsure say N. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ipts. -+ -+ Building this driver alone will not give you a working touchscreen. -+ It only exposed a userspace API that can be used by a daemon to -+ receive and process data from the touchscreen hardware. -diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile -new file mode 100644 -index 000000000000..8f58b9adbc94 ---- /dev/null -+++ b/drivers/misc/ipts/Makefile -@@ -0,0 +1,12 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+# -+# Makefile for the IPTS touchscreen driver -+# -+ -+obj-$(CONFIG_MISC_IPTS) += ipts.o -+ipts-objs := control.o -+ipts-objs += mei.o -+ipts-objs += receiver.o -+ipts-objs += resources.o -+ipts-objs += uapi.o -+ -diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h -new file mode 100644 -index 000000000000..f4b06a2d3f72 ---- /dev/null -+++ b/drivers/misc/ipts/context.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTEXT_H_ -+#define _IPTS_CONTEXT_H_ -+ -+#include -+#include -+#include -+#include -+ -+#include "protocol.h" -+ -+enum ipts_host_status { -+ IPTS_HOST_STATUS_STARTING, -+ IPTS_HOST_STATUS_STARTED, -+ IPTS_HOST_STATUS_STOPPING, -+ IPTS_HOST_STATUS_STOPPED, -+}; -+ -+struct ipts_buffer_info { -+ u8 *address; -+ dma_addr_t dma_address; -+}; -+ -+struct ipts_context { -+ struct mei_cl_device *cldev; -+ struct device *dev; -+ -+ bool restart; -+ enum ipts_host_status status; -+ struct ipts_get_device_info_rsp device_info; -+ -+ struct ipts_buffer_info data[IPTS_BUFFERS]; -+ struct ipts_buffer_info doorbell; -+ -+ struct ipts_buffer_info feedback[IPTS_BUFFERS]; -+ struct ipts_buffer_info workqueue; -+ struct ipts_buffer_info host2me; -+}; -+ -+#endif /* _IPTS_CONTEXT_H_ */ -diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c -new file mode 100644 -index 000000000000..a1d1f97a13d7 ---- /dev/null -+++ b/drivers/misc/ipts/control.c -@@ -0,0 +1,113 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+#include "protocol.h" -+#include "resources.h" -+#include "uapi.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload, -+ size_t size) -+{ -+ int ret; -+ struct ipts_command cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_command)); -+ cmd.code = code; -+ -+ if (payload && size > 0) -+ memcpy(&cmd.payload, payload, size); -+ -+ ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size); -+ if (ret >= 0) -+ return 0; -+ -+ /* -+ * During shutdown the device might get pulled away from below our feet. -+ * Dont log an error in this case, because it will confuse people. -+ */ -+ if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING) -+ dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret); -+ -+ return ret; -+} -+ -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) -+{ -+ struct ipts_feedback_cmd cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); -+ cmd.buffer = buffer; -+ -+ return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, -+ sizeof(struct ipts_feedback_cmd)); -+} -+ -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value) -+{ -+ struct ipts_feedback_buffer *feedback; -+ -+ memset(ipts->host2me.address, 0, ipts->device_info.feedback_size); -+ feedback = (struct ipts_feedback_buffer *)ipts->host2me.address; -+ -+ feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; -+ feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; -+ feedback->buffer = IPTS_HOST2ME_BUFFER; -+ feedback->size = 2; -+ feedback->payload[0] = report; -+ feedback->payload[1] = value; -+ -+ return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER); -+} -+ -+int ipts_control_start(struct ipts_context *ipts) -+{ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Starting IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STARTING; -+ ipts->restart = false; -+ -+ ipts_uapi_link(ipts); -+ return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); -+} -+ -+int ipts_control_stop(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPING) -+ return -EBUSY; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Stopping IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STOPPING; -+ -+ ipts_uapi_unlink(); -+ ipts_resources_free(ipts); -+ -+ ret = ipts_control_send_feedback(ipts, 0); -+ if (ret == -ENODEV) -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ return ret; -+} -+ -+int ipts_control_restart(struct ipts_context *ipts) -+{ -+ if (ipts->restart) -+ return -EBUSY; -+ -+ ipts->restart = true; -+ return ipts_control_stop(ipts); -+} -diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h -new file mode 100644 -index 000000000000..2c44e9e0e99f ---- /dev/null -+++ b/drivers/misc/ipts/control.h -@@ -0,0 +1,24 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTROL_H_ -+#define _IPTS_CONTROL_H_ -+ -+#include -+ -+#include "context.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, -+ size_t size); -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value); -+int ipts_control_start(struct ipts_context *ipts); -+int ipts_control_restart(struct ipts_context *ipts); -+int ipts_control_stop(struct ipts_context *ipts); -+ -+#endif /* _IPTS_CONTROL_H_ */ -diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c -new file mode 100644 -index 000000000000..59ecf13e00d2 ---- /dev/null -+++ b/drivers/misc/ipts/mei.c -@@ -0,0 +1,125 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "receiver.h" -+#include "uapi.h" -+ -+static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev) -+{ -+ int ret; -+ -+ ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)); -+ if (!ret) -+ return 0; -+ -+ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); -+} -+ -+static int ipts_mei_probe(struct mei_cl_device *cldev, -+ const struct mei_cl_device_id *id) -+{ -+ int ret; -+ struct ipts_context *ipts; -+ -+ if (ipts_mei_set_dma_mask(cldev)) { -+ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n"); -+ return -EFAULT; -+ } -+ -+ ret = mei_cldev_enable(cldev); -+ if (ret) { -+ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); -+ return ret; -+ } -+ -+ ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); -+ if (!ipts) { -+ mei_cldev_disable(cldev); -+ return -ENOMEM; -+ } -+ -+ ipts->cldev = cldev; -+ ipts->dev = &cldev->dev; -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ mei_cldev_set_drvdata(cldev, ipts); -+ mei_cldev_register_rx_cb(cldev, ipts_receiver_callback); -+ -+ return ipts_control_start(ipts); -+} -+ -+static void ipts_mei_remove(struct mei_cl_device *cldev) -+{ -+ int i; -+ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); -+ -+ ipts_control_stop(ipts); -+ -+ for (i = 0; i < 20; i++) { -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ break; -+ -+ msleep(25); -+ } -+ -+ mei_cldev_disable(cldev); -+ kfree(ipts); -+} -+ -+static struct mei_cl_device_id ipts_mei_device_id_table[] = { -+ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, -+ {}, -+}; -+MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); -+ -+static struct mei_cl_driver ipts_mei_driver = { -+ .id_table = ipts_mei_device_id_table, -+ .name = "ipts", -+ .probe = ipts_mei_probe, -+ .remove = ipts_mei_remove, -+}; -+ -+static int __init ipts_mei_init(void) -+{ -+ int ret; -+ -+ ret = ipts_uapi_init(); -+ if (ret) -+ return ret; -+ -+ ret = mei_cldev_driver_register(&ipts_mei_driver); -+ if (ret) { -+ ipts_uapi_free(); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static void __exit ipts_mei_exit(void) -+{ -+ mei_cldev_driver_unregister(&ipts_mei_driver); -+ ipts_uapi_free(); -+} -+ -+MODULE_DESCRIPTION("IPTS touchscreen driver"); -+MODULE_AUTHOR("Dorian Stoll "); -+MODULE_LICENSE("GPL"); -+ -+module_init(ipts_mei_init); -+module_exit(ipts_mei_exit); -diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h -new file mode 100644 -index 000000000000..c3458904a94d ---- /dev/null -+++ b/drivers/misc/ipts/protocol.h -@@ -0,0 +1,347 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_PROTOCOL_H_ -+#define _IPTS_PROTOCOL_H_ -+ -+#include -+ -+/* -+ * The MEI client ID for IPTS functionality. -+ */ -+#define IPTS_MEI_UUID \ -+ UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, \ -+ 0x02, 0xae, 0x04) -+ -+/* -+ * Queries the device for vendor specific information. -+ * -+ * The command must not contain any payload. -+ * The response will contain struct ipts_get_device_info_rsp as payload. -+ */ -+#define IPTS_CMD_GET_DEVICE_INFO 0x00000001 -+#define IPTS_RSP_GET_DEVICE_INFO 0x80000001 -+ -+/* -+ * Sets the mode that IPTS will operate in. -+ * -+ * The command must contain struct ipts_set_mode_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MODE 0x00000002 -+#define IPTS_RSP_SET_MODE 0x80000002 -+ -+/* -+ * Configures the memory buffers that the ME will use -+ * for passing data to the host. -+ * -+ * The command must contain struct ipts_set_mem_window_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MEM_WINDOW 0x00000003 -+#define IPTS_RSP_SET_MEM_WINDOW 0x80000003 -+ -+/* -+ * Signals that the host is ready to receive data to the ME. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_READY_FOR_DATA 0x00000005 -+#define IPTS_RSP_READY_FOR_DATA 0x80000005 -+ -+/* -+ * Signals that a buffer can be refilled to the ME. -+ * -+ * The command must contain struct ipts_feedback_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_FEEDBACK 0x00000006 -+#define IPTS_RSP_FEEDBACK 0x80000006 -+ -+/* -+ * Resets the data flow from the ME to the hosts and -+ * clears the buffers that were set with SET_MEM_WINDOW. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007 -+#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007 -+ -+/* -+ * Instructs the ME to reset the touch sensor. -+ * -+ * The command must contain struct ipts_reset_sensor_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_RESET_SENSOR 0x0000000B -+#define IPTS_RSP_RESET_SENSOR 0x8000000B -+ -+/** -+ * enum ipts_status - Possible status codes returned by IPTS commands. -+ * @IPTS_STATUS_SUCCESS: Operation completed successfully. -+ * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. -+ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. -+ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. -+ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. -+ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. -+ * The host must wait for a response before sending another -+ * command of the same type. -+ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it -+ * has not been initialized yet, or the system is improperly -+ * configured. -+ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. -+ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. -+ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. -+ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. -+ * The host can ignore this error and attempt to continue. -+ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. -+ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. -+ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. -+ * @IPTS_STATUS_TIMEOUT: The operation timed out. -+ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. -+ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. -+ * Further progress is not possible. -+ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. -+ * The host can attempt to continue. -+ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. -+ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. -+ */ -+enum ipts_status { -+ IPTS_STATUS_SUCCESS = 0, -+ IPTS_STATUS_INVALID_PARAMS = 1, -+ IPTS_STATUS_ACCESS_DENIED = 2, -+ IPTS_STATUS_CMD_SIZE_ERROR = 3, -+ IPTS_STATUS_NOT_READY = 4, -+ IPTS_STATUS_REQUEST_OUTSTANDING = 5, -+ IPTS_STATUS_NO_SENSOR_FOUND = 6, -+ IPTS_STATUS_OUT_OF_MEMORY = 7, -+ IPTS_STATUS_INTERNAL_ERROR = 8, -+ IPTS_STATUS_SENSOR_DISABLED = 9, -+ IPTS_STATUS_COMPAT_CHECK_FAIL = 10, -+ IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, -+ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, -+ IPTS_STATUS_RESET_FAILED = 13, -+ IPTS_STATUS_TIMEOUT = 14, -+ IPTS_STATUS_TEST_MODE_FAIL = 15, -+ IPTS_STATUS_SENSOR_FAIL_FATAL = 16, -+ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, -+ IPTS_STATUS_INVALID_DEVICE_CAPS = 18, -+ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, -+}; -+ -+/* -+ * The amount of buffers that is used for IPTS -+ */ -+#define IPTS_BUFFERS 16 -+ -+/* -+ * The special buffer ID that is used for direct host2me feedback. -+ */ -+#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS -+ -+/** -+ * enum ipts_mode - Operation mode for IPTS hardware -+ * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. -+ * The data is received as a HID report with ID 64. -+ * @IPTS_MODE_MULTITOUCH: The "proper" operation mode for IPTS. It will return -+ * stylus data as well as capacitive heatmap touch data. -+ * This data needs to be processed in userspace. -+ */ -+enum ipts_mode { -+ IPTS_MODE_SINGLETOUCH = 0, -+ IPTS_MODE_MULTITOUCH = 1, -+}; -+ -+/** -+ * struct ipts_set_mode_cmd - Payload for the SET_MODE command. -+ * @mode: The mode that IPTS should operate in. -+ */ -+struct ipts_set_mode_cmd { -+ enum ipts_mode mode; -+ u8 reserved[12]; -+} __packed; -+ -+#define IPTS_WORKQUEUE_SIZE 8192 -+#define IPTS_WORKQUEUE_ITEM_SIZE 16 -+ -+/** -+ * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command. -+ * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses. -+ * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses. -+ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. -+ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. -+ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. -+ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. -+ * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses. -+ * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses. -+ * @host2me_addr_lower: Lower 32 bits of the host2me buffer address. -+ * @host2me_addr_upper: Upper 32 bits of the host2me buffer address. -+ * @workqueue_item_size: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) -+ * @workqueue_size: Magic value. (IPTS_WORKQUEUE_SIZE) -+ * -+ * The data buffers are buffers that get filled with touch data by the ME. -+ * The doorbell buffer is a u32 that gets incremented by the ME once a data -+ * buffer has been filled with new data. -+ * -+ * The other buffers are required for using GuC submission with binary -+ * firmware. Since support for GuC submission has been dropped from i915, -+ * they are not used anymore, but they need to be allocated and passed, -+ * otherwise the hardware will refuse to start. -+ */ -+struct ipts_set_mem_window_cmd { -+ u32 data_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 data_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 workqueue_addr_lower; -+ u32 workqueue_addr_upper; -+ u32 doorbell_addr_lower; -+ u32 doorbell_addr_upper; -+ u32 feedback_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 feedback_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 host2me_addr_lower; -+ u32 host2me_addr_upper; -+ u32 host2me_size; -+ u8 reserved1; -+ u8 workqueue_item_size; -+ u16 workqueue_size; -+ u8 reserved[32]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_cmd - Payload for the FEEDBACK command. -+ * @buffer: The buffer that the ME should refill. -+ */ -+struct ipts_feedback_cmd { -+ u32 buffer; -+ u8 reserved[12]; -+} __packed; -+ -+/** -+ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. -+ */ -+enum ipts_feedback_cmd_type { -+ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, -+ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, -+ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, -+}; -+ -+/** -+ * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains. -+ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. -+ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. -+ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. -+ */ -+enum ipts_feedback_data_type { -+ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, -+ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, -+ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, -+ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, -+ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, -+}; -+ -+/** -+ * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer. -+ * @cmd_type: A command that should be executed on the sensor. -+ * @size: The size of the payload to be written. -+ * @buffer: The ID of the buffer that contains this feedback data. -+ * @protocol: The protocol version of the EDS. -+ * @data_type: The type of payload that the buffer contains. -+ * @spi_offset: The offset at which to write the payload data. -+ * @payload: Payload for the feedback command, or 0 if no payload is sent. -+ */ -+struct ipts_feedback_buffer { -+ enum ipts_feedback_cmd_type cmd_type; -+ u32 size; -+ u32 buffer; -+ u32 protocol; -+ enum ipts_feedback_data_type data_type; -+ u32 spi_offset; -+ u8 reserved[40]; -+ u8 payload[]; -+} __packed; -+ -+/** -+ * enum ipts_reset_type - Possible ways of resetting the touch sensor -+ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. -+ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. -+ */ -+enum ipts_reset_type { -+ IPTS_RESET_TYPE_HARD = 0, -+ IPTS_RESET_TYPE_SOFT = 1, -+}; -+ -+/** -+ * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. -+ * @type: What type of reset should be performed. -+ */ -+struct ipts_reset_sensor_cmd { -+ enum ipts_reset_type type; -+ u8 reserved[4]; -+} __packed; -+ -+/** -+ * struct ipts_command - A message sent from the host to the ME. -+ * @code: The message code describing the command. (see IPTS_CMD_*) -+ * @payload: Payload for the command, or 0 if no payload is required. -+ */ -+struct ipts_command { -+ u32 code; -+ u8 payload[320]; -+} __packed; -+ -+/** -+ * struct ipts_device_info - Payload for the GET_DEVICE_INFO response. -+ * @vendor_id: Vendor ID of the touch sensor. -+ * @device_id: Device ID of the touch sensor. -+ * @hw_rev: Hardware revision of the touch sensor. -+ * @fw_rev: Firmware revision of the touch sensor. -+ * @data_size: Required size of one data buffer. -+ * @feedback_size: Required size of one feedback buffer. -+ * @mode: Current operation mode of IPTS. -+ * @max_contacts: The amount of concurrent touches supported by the sensor. -+ */ -+struct ipts_get_device_info_rsp { -+ u16 vendor_id; -+ u16 device_id; -+ u32 hw_rev; -+ u32 fw_rev; -+ u32 data_size; -+ u32 feedback_size; -+ enum ipts_mode mode; -+ u8 max_contacts; -+ u8 reserved[19]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_rsp - Payload for the FEEDBACK response. -+ * @buffer: The buffer that has received feedback. -+ */ -+struct ipts_feedback_rsp { -+ u32 buffer; -+} __packed; -+ -+/** -+ * struct ipts_response - A message sent from the ME to the host. -+ * @code: The message code describing the response. (see IPTS_RSP_*) -+ * @status: The status code returned by the command. -+ * @payload: Payload returned by the command. -+ */ -+struct ipts_response { -+ u32 code; -+ enum ipts_status status; -+ u8 payload[80]; -+} __packed; -+ -+#endif /* _IPTS_PROTOCOL_H_ */ -diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c -new file mode 100644 -index 000000000000..23dca13c2139 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.c -@@ -0,0 +1,224 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "resources.h" -+ -+/* -+ * Temporary parameter to guard gen7 multitouch mode. -+ * Remove once gen7 has stable iptsd support. -+ */ -+static bool gen7mt; -+module_param(gen7mt, bool, 0644); -+ -+static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_set_mode_cmd cmd; -+ -+ memcpy(&ipts->device_info, rsp->payload, -+ sizeof(struct ipts_get_device_info_rsp)); -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd)); -+ cmd.mode = IPTS_MODE_MULTITOUCH; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd, -+ sizeof(struct ipts_set_mode_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mode(struct ipts_context *ipts) -+{ -+ int i, ret; -+ struct ipts_set_mem_window_cmd cmd; -+ -+ ret = ipts_resources_alloc(ipts); -+ if (ret) { -+ dev_err(ipts->dev, "Failed to allocate resources\n"); -+ return ret; -+ } -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ cmd.data_buffer_addr_lower[i] = -+ lower_32_bits(ipts->data[i].dma_address); -+ -+ cmd.data_buffer_addr_upper[i] = -+ upper_32_bits(ipts->data[i].dma_address); -+ -+ cmd.feedback_buffer_addr_lower[i] = -+ lower_32_bits(ipts->feedback[i].dma_address); -+ -+ cmd.feedback_buffer_addr_upper[i] = -+ upper_32_bits(ipts->feedback[i].dma_address); -+ } -+ -+ cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); -+ cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); -+ -+ cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); -+ cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); -+ -+ cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); -+ cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); -+ -+ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; -+ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, -+ sizeof(struct ipts_set_mem_window_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ dev_info(ipts->dev, "Device %04hX:%04hX ready\n", -+ ipts->device_info.vendor_id, ipts->device_info.device_id); -+ ipts->status = IPTS_HOST_STATUS_STARTED; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); -+ if (ret) -+ return ret; -+ -+ if (!gen7mt) -+ return 0; -+ -+ return ipts_control_set_feature(ipts, 0x5, 0x1); -+} -+ -+static int ipts_receiver_handle_feedback(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_feedback_rsp feedback; -+ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPING) -+ return 0; -+ -+ memcpy(&feedback, rsp->payload, sizeof(feedback)); -+ -+ if (feedback.buffer < IPTS_BUFFERS - 1) -+ return ipts_control_send_feedback(ipts, feedback.buffer + 1); -+ -+ return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); -+} -+ -+static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) -+{ -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ if (ipts->restart) -+ return ipts_control_start(ipts); -+ -+ return 0; -+} -+ -+static bool ipts_receiver_sensor_was_reset(u32 status) -+{ -+ return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || -+ status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; -+} -+ -+static bool ipts_receiver_handle_error(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ bool error; -+ -+ switch (rsp->status) { -+ case IPTS_STATUS_SUCCESS: -+ case IPTS_STATUS_COMPAT_CHECK_FAIL: -+ error = false; -+ break; -+ case IPTS_STATUS_INVALID_PARAMS: -+ error = rsp->code != IPTS_RSP_FEEDBACK; -+ break; -+ case IPTS_STATUS_SENSOR_DISABLED: -+ error = ipts->status != IPTS_HOST_STATUS_STOPPING; -+ break; -+ default: -+ error = true; -+ break; -+ } -+ -+ if (!error) -+ return false; -+ -+ dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code, -+ rsp->status); -+ -+ if (ipts_receiver_sensor_was_reset(rsp->status)) { -+ dev_err(ipts->dev, "Sensor was reset\n"); -+ -+ if (ipts_control_restart(ipts)) -+ dev_err(ipts->dev, "Failed to restart IPTS\n"); -+ } -+ -+ return true; -+} -+ -+static void ipts_receiver_handle_response(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ int ret; -+ -+ if (ipts_receiver_handle_error(ipts, rsp)) -+ return; -+ -+ switch (rsp->code) { -+ case IPTS_RSP_GET_DEVICE_INFO: -+ ret = ipts_receiver_handle_get_device_info(ipts, rsp); -+ break; -+ case IPTS_RSP_SET_MODE: -+ ret = ipts_receiver_handle_set_mode(ipts); -+ break; -+ case IPTS_RSP_SET_MEM_WINDOW: -+ ret = ipts_receiver_handle_set_mem_window(ipts); -+ break; -+ case IPTS_RSP_FEEDBACK: -+ ret = ipts_receiver_handle_feedback(ipts, rsp); -+ break; -+ case IPTS_RSP_CLEAR_MEM_WINDOW: -+ ret = ipts_receiver_handle_clear_mem_window(ipts); -+ break; -+ default: -+ ret = 0; -+ break; -+ } -+ -+ if (!ret) -+ return; -+ -+ dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n", -+ rsp->code, ret); -+ -+ if (ipts_control_stop(ipts)) -+ dev_err(ipts->dev, "Failed to stop IPTS\n"); -+} -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev) -+{ -+ int ret; -+ struct ipts_response rsp; -+ struct ipts_context *ipts; -+ -+ ipts = mei_cldev_get_drvdata(cldev); -+ -+ ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response)); -+ if (ret <= 0) { -+ dev_err(ipts->dev, "Error while reading response: %d\n", ret); -+ return; -+ } -+ -+ ipts_receiver_handle_response(ipts, &rsp); -+} -diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h -new file mode 100644 -index 000000000000..7f075afa7ef8 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.h -@@ -0,0 +1,16 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RECEIVER_H_ -+#define _IPTS_RECEIVER_H_ -+ -+#include -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev); -+ -+#endif /* _IPTS_RECEIVER_H_ */ -diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c -new file mode 100644 -index 000000000000..8e3a2409e438 ---- /dev/null -+++ b/drivers/misc/ipts/resources.c -@@ -0,0 +1,128 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+ -+void ipts_resources_free(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, data_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ if (ipts->doorbell.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->doorbell.address, -+ ipts->doorbell.dma_address); -+ -+ ipts->doorbell.address = NULL; -+ ipts->doorbell.dma_address = 0; -+ } -+ -+ if (ipts->workqueue.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->workqueue.address, -+ ipts->workqueue.dma_address); -+ -+ ipts->workqueue.address = NULL; -+ ipts->workqueue.dma_address = 0; -+ } -+ -+ if (ipts->host2me.address) { -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ ipts->host2me.address, -+ ipts->host2me.dma_address); -+ -+ ipts->host2me.address = NULL; -+ ipts->host2me.dma_address = 0; -+ } -+} -+ -+int ipts_resources_alloc(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, data_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ ipts->doorbell.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->doorbell.dma_address, GFP_KERNEL); -+ -+ if (!ipts->doorbell.address) -+ goto release_resources; -+ -+ ipts->workqueue.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->workqueue.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ ipts->host2me.address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &ipts->host2me.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ return 0; -+ -+release_resources: -+ -+ ipts_resources_free(ipts); -+ return -ENOMEM; -+} -diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h -new file mode 100644 -index 000000000000..fdac0eee9156 ---- /dev/null -+++ b/drivers/misc/ipts/resources.h -@@ -0,0 +1,17 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RESOURCES_H_ -+#define _IPTS_RESOURCES_H_ -+ -+#include "context.h" -+ -+int ipts_resources_alloc(struct ipts_context *ipts); -+void ipts_resources_free(struct ipts_context *ipts); -+ -+#endif /* _IPTS_RESOURCES_H_ */ -diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c -new file mode 100644 -index 000000000000..598f0710ad64 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.c -@@ -0,0 +1,208 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "uapi.h" -+ -+struct ipts_uapi uapi; -+ -+static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count, -+ loff_t *offset) -+{ -+ int buffer; -+ int maxbytes; -+ struct ipts_context *ipts = uapi.ipts; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ maxbytes = ipts->device_info.data_size - *offset; -+ if (maxbytes <= 0 || count > maxbytes) -+ return -EINVAL; -+ -+ if (copy_to_user(buf, ipts->data[buffer].address + *offset, count)) -+ return -EFAULT; -+ -+ return count; -+} -+ -+static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ u8 ready = 0; -+ -+ if (ipts) -+ ready = ipts->status == IPTS_HOST_STATUS_STARTED; -+ -+ if (copy_to_user(buffer, &ready, sizeof(u8))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ struct ipts_device_info info; -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ info.vendor = ipts->device_info.vendor_id; -+ info.product = ipts->device_info.device_id; -+ info.version = ipts->device_info.fw_rev; -+ info.buffer_size = ipts->device_info.data_size; -+ info.max_contacts = ipts->device_info.max_contacts; -+ -+ if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts, -+ struct file *file) -+{ -+ int ret; -+ u32 buffer; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ ret = ipts_control_send_feedback(ipts, buffer); -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) -+{ -+ int ret; -+ struct ipts_reset_sensor_cmd cmd; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); -+ cmd.type = IPTS_RESET_TYPE_SOFT; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, -+ sizeof(struct ipts_reset_sensor_cmd)); -+ -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) -+{ -+ struct ipts_context *ipts = uapi.ipts; -+ -+ switch (cmd) { -+ case IPTS_IOCTL_GET_DEVICE_READY: -+ return ipts_uapi_ioctl_get_device_ready(ipts, arg); -+ case IPTS_IOCTL_GET_DEVICE_INFO: -+ return ipts_uapi_ioctl_get_device_info(ipts, arg); -+ case IPTS_IOCTL_GET_DOORBELL: -+ return ipts_uapi_ioctl_get_doorbell(ipts, arg); -+ case IPTS_IOCTL_SEND_FEEDBACK: -+ return ipts_uapi_ioctl_send_feedback(ipts, file); -+ case IPTS_IOCTL_SEND_RESET: -+ return ipts_uapi_ioctl_send_reset(ipts); -+ default: -+ return -ENOTTY; -+ } -+} -+ -+static const struct file_operations ipts_uapi_fops = { -+ .owner = THIS_MODULE, -+ .read = ipts_uapi_read, -+ .unlocked_ioctl = ipts_uapi_ioctl, -+#ifdef CONFIG_COMPAT -+ .compat_ioctl = ipts_uapi_ioctl, -+#endif -+}; -+ -+void ipts_uapi_link(struct ipts_context *ipts) -+{ -+ uapi.ipts = ipts; -+} -+ -+void ipts_uapi_unlink(void) -+{ -+ uapi.ipts = NULL; -+} -+ -+int ipts_uapi_init(void) -+{ -+ int i, major; -+ -+ alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts"); -+ uapi.class = class_create(THIS_MODULE, "ipts"); -+ -+ major = MAJOR(uapi.dev); -+ -+ cdev_init(&uapi.cdev, &ipts_uapi_fops); -+ uapi.cdev.owner = THIS_MODULE; -+ cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ device_create(uapi.class, NULL, MKDEV(major, i), NULL, -+ "ipts/%d", i); -+ } -+ -+ return 0; -+} -+ -+void ipts_uapi_free(void) -+{ -+ int i; -+ int major; -+ -+ major = MAJOR(uapi.dev); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) -+ device_destroy(uapi.class, MKDEV(major, i)); -+ -+ cdev_del(&uapi.cdev); -+ -+ unregister_chrdev_region(MKDEV(major, 0), MINORMASK); -+ class_destroy(uapi.class); -+} -diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h -new file mode 100644 -index 000000000000..53fb86a88f97 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_UAPI_H_ -+#define _IPTS_UAPI_H_ -+ -+#include -+ -+#include "context.h" -+ -+struct ipts_uapi { -+ dev_t dev; -+ struct class *class; -+ struct cdev cdev; -+ -+ struct ipts_context *ipts; -+}; -+ -+struct ipts_device_info { -+ __u16 vendor; -+ __u16 product; -+ __u32 version; -+ __u32 buffer_size; -+ __u8 max_contacts; -+ -+ /* For future expansion */ -+ __u8 reserved[19]; -+}; -+ -+#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8) -+#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info) -+#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) -+#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04) -+#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) -+ -+void ipts_uapi_link(struct ipts_context *ipts); -+void ipts_uapi_unlink(void); -+ -+int ipts_uapi_init(void); -+void ipts_uapi_free(void); -+ -+#endif /* _IPTS_UAPI_H_ */ --- -2.33.0 - diff --git a/patches/5.13/0005-surface-sam-over-hid.patch b/patches/5.13/0005-surface-sam-over-hid.patch deleted file mode 100644 index 9264ba47c..000000000 --- a/patches/5.13/0005-surface-sam-over-hid.patch +++ /dev/null @@ -1,335 +0,0 @@ -From 8b986b04304762d5bec137018886cfe6eb43b29a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jul 2020 17:19:53 +0200 -Subject: [PATCH] i2c: acpi: Implement RawBytes read access - -Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C -device via a generic serial bus operation region and RawBytes read -access. On the Surface Book 1, this access is required to turn on (and -off) the discrete GPU. - -Multiple things are to note here: - -a) The RawBytes access is device/driver dependent. The ACPI - specification states: - - > Raw accesses assume that the writer has knowledge of the bus that - > the access is made over and the device that is being accessed. The - > protocol may only ensure that the buffer is transmitted to the - > appropriate driver, but the driver must be able to interpret the - > buffer to communicate to a register. - - Thus this implementation may likely not work on other devices - accessing I2C via the RawBytes accessor type. - -b) The MSHW0030 I2C device is an HID-over-I2C device which seems to - serve multiple functions: - - 1. It is the main access point for the legacy-type Surface Aggregator - Module (also referred to as SAM-over-HID, as opposed to the newer - SAM-over-SSH/UART). It has currently not been determined on how - support for the legacy SAM should be implemented. Likely via a - custom HID driver. - - 2. It seems to serve as the HID device for the Integrated Sensor Hub. - This might complicate matters with regards to implementing a - SAM-over-HID driver required by legacy SAM. - -In light of this, the simplest approach has been chosen for now. -However, it may make more sense regarding breakage and compatibility to -either provide functionality for replacing or enhancing the default -operation region handler via some additional API functions, or even to -completely blacklist MSHW0030 from the I2C core and provide a custom -driver for it. - -Replacing/enhancing the default operation region handler would, however, -either require some sort of secondary driver and access point for it, -from which the new API functions would be called and the new handler -(part) would be installed, or hard-coding them via some sort of -quirk-like interface into the I2C core. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam-over-hid ---- - drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ - 1 file changed, 35 insertions(+) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index 8ceaa88dd78f..deceed0d76c6 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -570,6 +570,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, - return (ret == 1) ? 0 : -EIO; - } - -+static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, -+ u8 *data, u8 data_len) -+{ -+ struct i2c_msg msgs[1]; -+ int ret = AE_OK; -+ -+ msgs[0].addr = client->addr; -+ msgs[0].flags = client->flags; -+ msgs[0].len = data_len + 1; -+ msgs[0].buf = data; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ -+ if (ret < 0) { -+ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); -+ return ret; -+ } -+ -+ /* 1 transfer must have completed successfully */ -+ return (ret == 1) ? 0 : -EIO; -+} -+ - static acpi_status - i2c_acpi_space_handler(u32 function, acpi_physical_address command, - u32 bits, u64 *value64, -@@ -671,6 +693,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, - } - break; - -+ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: -+ if (action == ACPI_READ) { -+ dev_warn(&adapter->dev, -+ "protocol 0x%02x not supported for client 0x%02x\n", -+ accessor_type, client->addr); -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } else { -+ status = acpi_gsb_i2c_write_raw_bytes(client, -+ gsb->data, info->access_length); -+ } -+ break; -+ - default: - dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", - accessor_type, client->addr); --- -2.33.0 - -From 1de17517b1103d792ee6169813ec30380fcdceda Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 13 Feb 2021 16:41:18 +0100 -Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch - -Add driver exposing the discrete GPU power-switch of the Microsoft -Surface Book 1 to user-space. - -On the Surface Book 1, the dGPU power is controlled via the Surface -System Aggregator Module (SAM). The specific SAM-over-HID command for -this is exposed via ACPI. This module provides a simple driver exposing -the ACPI call via a sysfs parameter to user-space, so that users can -easily power-on/-off the dGPU. - -Patchset: surface-sam-over-hid ---- - drivers/platform/surface/Kconfig | 7 + - drivers/platform/surface/Makefile | 1 + - .../surface/surfacebook1_dgpu_switch.c | 162 ++++++++++++++++++ - 3 files changed, 170 insertions(+) - create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 3105f651614f..53beaedefdd1 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -104,6 +104,13 @@ config SURFACE_AGGREGATOR_REGISTRY - the respective client devices. Drivers for these devices still need to - be selected via the other options. - -+config SURFACE_BOOK1_DGPU_SWITCH -+ tristate "Surface Book 1 dGPU Switch Driver" -+ depends on SYSFS -+ help -+ This driver provides a sysfs switch to set the power-state of the -+ discrete GPU found on the Microsoft Surface Book 1. -+ - config SURFACE_DTX - tristate "Surface DTX (Detachment System) Driver" - depends on SURFACE_AGGREGATOR -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 32889482de55..0cc63440328d 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -11,6 +11,7 @@ obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o -+obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c -new file mode 100644 -index 000000000000..8b816ed8f35c ---- /dev/null -+++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c -@@ -0,0 +1,162 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+ -+#include -+#include -+#include -+#include -+ -+ -+#ifdef pr_fmt -+#undef pr_fmt -+#endif -+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ -+ -+ -+static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, -+ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); -+ -+#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" -+#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" -+#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" -+ -+ -+static int sb1_dgpu_sw_dsmcall(void) -+{ -+ union acpi_object *ret; -+ acpi_handle handle; -+ acpi_status status; -+ -+ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); -+ if (status) -+ return -EINVAL; -+ -+ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); -+ if (!ret) -+ return -EINVAL; -+ -+ ACPI_FREE(ret); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgon(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGON: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-on dGPU via HGON\n"); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgof(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGOF: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-off dGPU via HGOF\n"); -+ return 0; -+} -+ -+ -+static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ int status, value; -+ -+ status = kstrtoint(buf, 0, &value); -+ if (status < 0) -+ return status; -+ -+ if (value != 1) -+ return -EINVAL; -+ -+ status = sb1_dgpu_sw_dsmcall(); -+ -+ return status < 0 ? status : len; -+} -+ -+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ bool power; -+ int status; -+ -+ status = kstrtobool(buf, &power); -+ if (status < 0) -+ return status; -+ -+ if (power) -+ status = sb1_dgpu_sw_hgon(); -+ else -+ status = sb1_dgpu_sw_hgof(); -+ -+ return status < 0 ? status : len; -+} -+ -+static DEVICE_ATTR_WO(dgpu_dsmcall); -+static DEVICE_ATTR_WO(dgpu_power); -+ -+static struct attribute *sb1_dgpu_sw_attrs[] = { -+ &dev_attr_dgpu_dsmcall.attr, -+ &dev_attr_dgpu_power.attr, -+ NULL, -+}; -+ -+static const struct attribute_group sb1_dgpu_sw_attr_group = { -+ .attrs = sb1_dgpu_sw_attrs, -+}; -+ -+ -+static int sb1_dgpu_sw_probe(struct platform_device *pdev) -+{ -+ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+} -+ -+static int sb1_dgpu_sw_remove(struct platform_device *pdev) -+{ -+ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+ return 0; -+} -+ -+/* -+ * The dGPU power seems to be actually handled by MSHW0040. However, that is -+ * also the power-/volume-button device with a mainline driver. So let's use -+ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. -+ */ -+static const struct acpi_device_id sb1_dgpu_sw_match[] = { -+ { "MSHW0041", }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); -+ -+static struct platform_driver sb1_dgpu_sw = { -+ .probe = sb1_dgpu_sw_probe, -+ .remove = sb1_dgpu_sw_remove, -+ .driver = { -+ .name = "surfacebook1_dgpu_switch", -+ .acpi_match_table = sb1_dgpu_sw_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(sb1_dgpu_sw); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); -+MODULE_LICENSE("GPL"); --- -2.33.0 - diff --git a/patches/5.13/0006-surface-sam.patch b/patches/5.13/0006-surface-sam.patch deleted file mode 100644 index aa02c841d..000000000 --- a/patches/5.13/0006-surface-sam.patch +++ /dev/null @@ -1,2324 +0,0 @@ -From e86e95ef7660bc45a6dce58d56d1024158649ab9 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 23 May 2021 14:09:42 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Consolidate node - groups for 5th- and 6th-gen devices - -5th- and 6th-generation Surface devices have all SAM clients defined in -ACPI, except for the platform profile/performance mode which his handled -via the WSID (Windows Surface Integration Device). Thus, the node groups -for those devices are the same and we can just use a single one instead -of re-defining the same one over and over again. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 47 +++++-------------- - 1 file changed, 12 insertions(+), 35 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index ef83461fa536..4428c4330229 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -119,8 +119,13 @@ static const struct software_node ssam_node_hid_base_iid6 = { - .parent = &ssam_node_hub_base, - }; - --/* Devices for Surface Book 2. */ --static const struct software_node *ssam_node_group_sb2[] = { -+/* -+ * Devices for 5th- and 6th-generations models: -+ * - Surface Book 2, -+ * - Surface Laptop 1 and 2, -+ * - Surface Pro 5 and 6. -+ */ -+static const struct software_node *ssam_node_group_gen5[] = { - &ssam_node_root, - &ssam_node_tmp_pprof, - NULL, -@@ -142,20 +147,6 @@ static const struct software_node *ssam_node_group_sb3[] = { - NULL, - }; - --/* Devices for Surface Laptop 1. */ --static const struct software_node *ssam_node_group_sl1[] = { -- &ssam_node_root, -- &ssam_node_tmp_pprof, -- NULL, --}; -- --/* Devices for Surface Laptop 2. */ --static const struct software_node *ssam_node_group_sl2[] = { -- &ssam_node_root, -- &ssam_node_tmp_pprof, -- NULL, --}; -- - /* Devices for Surface Laptop 3 and 4. */ - static const struct software_node *ssam_node_group_sl3[] = { - &ssam_node_root, -@@ -177,20 +168,6 @@ static const struct software_node *ssam_node_group_slg1[] = { - NULL, - }; - --/* Devices for Surface Pro 5. */ --static const struct software_node *ssam_node_group_sp5[] = { -- &ssam_node_root, -- &ssam_node_tmp_pprof, -- NULL, --}; -- --/* Devices for Surface Pro 6. */ --static const struct software_node *ssam_node_group_sp6[] = { -- &ssam_node_root, -- &ssam_node_tmp_pprof, -- NULL, --}; -- - /* Devices for Surface Pro 7 and Surface Pro 7+. */ - static const struct software_node *ssam_node_group_sp7[] = { - &ssam_node_root, -@@ -495,10 +472,10 @@ static struct ssam_device_driver ssam_base_hub_driver = { - - static const struct acpi_device_id ssam_platform_hub_match[] = { - /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ -- { "MSHW0081", (unsigned long)ssam_node_group_sp5 }, -+ { "MSHW0081", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Pro 6 (OMBR >= 0x10) */ -- { "MSHW0111", (unsigned long)ssam_node_group_sp6 }, -+ { "MSHW0111", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Pro 7 */ - { "MSHW0116", (unsigned long)ssam_node_group_sp7 }, -@@ -507,16 +484,16 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { - { "MSHW0119", (unsigned long)ssam_node_group_sp7 }, - - /* Surface Book 2 */ -- { "MSHW0107", (unsigned long)ssam_node_group_sb2 }, -+ { "MSHW0107", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Book 3 */ - { "MSHW0117", (unsigned long)ssam_node_group_sb3 }, - - /* Surface Laptop 1 */ -- { "MSHW0086", (unsigned long)ssam_node_group_sl1 }, -+ { "MSHW0086", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Laptop 2 */ -- { "MSHW0112", (unsigned long)ssam_node_group_sl2 }, -+ { "MSHW0112", (unsigned long)ssam_node_group_gen5 }, - - /* Surface Laptop 3 (13", Intel) */ - { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, --- -2.33.0 - -From 88dc6f1ea08c6175a1679d1f739c2bca5d639e0b Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 4 Jun 2021 15:47:49 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow registering notifiers - without enabling events - -Currently, each SSAM event notifier is directly tied to one group of -events. This makes sense as registering a notifier will automatically -take care of enabling the corresponding event group and normally drivers -only need notifications for a very limited number of events, associated -with different callbacks for each group. - -However, there are rare cases, especially for debugging, when we want to -get notifications for a whole event target category instead of just a -single group of events in that category. Registering multiple notifiers, -i.e. one per group, may be infeasible due to two issues: a) we might not -know every event enable/disable specification as some events are -auto-enabled by the EC and b) forwarding this to the same callback will -lead to duplicate events as we might not know the full event -specification to perform the appropriate filtering. - -This commit introduces observer-notifiers, which are notifiers that are -not tied to a specific event group and do not attempt to manage any -events. In other words, they can be registered without enabling any -event group or incrementing the corresponding reference count and just -act as silent observers, listening to all currently/previously enabled -events based on their match-specification. - -Essentially, this allows us to register one single notifier for a full -event target category, meaning that we can process all events of that -target category in a single callback without duplication. Specifically, -this will be used in the cdev debug interface to forward events to -user-space via a device file from which the events can be read. - -Signed-off-by: Maximilian Luz -Reviewed-by: Hans de Goede -Link: https://lore.kernel.org/r/20210604134755.535590-2-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../platform/surface/aggregator/controller.c | 69 +++++++++++-------- - include/linux/surface_aggregator/controller.h | 17 +++++ - 2 files changed, 58 insertions(+), 28 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index a06964aa96e7..cd3a6b77f48d 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2127,9 +2127,15 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) - * @ctrl: The controller to register the notifier on. - * @n: The event notifier to register. - * -- * Register an event notifier and increment the usage counter of the -- * associated SAM event. If the event was previously not enabled, it will be -- * enabled during this call. -+ * Register an event notifier. Increment the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the event is not -+ * marked as an observer and is currently not enabled, it will be enabled -+ * during this call. If the notifier is marked as an observer, no attempt will -+ * be made at enabling any event and no reference count will be modified. -+ * -+ * Notifiers marked as observers do not need to be associated with one specific -+ * event, i.e. as long as no event matching is performed, only the event target -+ * category needs to be set. - * - * Return: Returns zero on success, %-ENOSPC if there have already been - * %INT_MAX notifiers for the event ID/type associated with the notifier block -@@ -2138,11 +2144,10 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) - * for the specific associated event, returns the status of the event-enable - * EC-command. - */ --int ssam_notifier_register(struct ssam_controller *ctrl, -- struct ssam_event_notifier *n) -+int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n) - { - u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); -- struct ssam_nf_refcount_entry *entry; -+ struct ssam_nf_refcount_entry *entry = NULL; - struct ssam_nf_head *nf_head; - struct ssam_nf *nf; - int status; -@@ -2155,29 +2160,32 @@ int ssam_notifier_register(struct ssam_controller *ctrl, - - mutex_lock(&nf->lock); - -- entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); -- if (IS_ERR(entry)) { -- mutex_unlock(&nf->lock); -- return PTR_ERR(entry); -- } -+ if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { -+ entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); -+ if (IS_ERR(entry)) { -+ mutex_unlock(&nf->lock); -+ return PTR_ERR(entry); -+ } - -- ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -- n->event.reg.target_category, n->event.id.target_category, -- n->event.id.instance, entry->refcount); -+ ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ n->event.reg.target_category, n->event.id.target_category, -+ n->event.id.instance, entry->refcount); -+ } - - status = ssam_nfblk_insert(nf_head, &n->base); - if (status) { -- entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); -- if (entry->refcount == 0) -- kfree(entry); -+ if (entry) { -+ entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); -+ if (entry->refcount == 0) -+ kfree(entry); -+ } - - mutex_unlock(&nf->lock); - return status; - } - -- if (entry->refcount == 1) { -- status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, -- n->event.flags); -+ if (entry && entry->refcount == 1) { -+ status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, n->event.flags); - if (status) { - ssam_nfblk_remove(&n->base); - kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id)); -@@ -2188,7 +2196,7 @@ int ssam_notifier_register(struct ssam_controller *ctrl, - - entry->flags = n->event.flags; - -- } else if (entry->flags != n->event.flags) { -+ } else if (entry && entry->flags != n->event.flags) { - ssam_warn(ctrl, - "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", - n->event.flags, entry->flags, n->event.reg.target_category, -@@ -2205,17 +2213,16 @@ EXPORT_SYMBOL_GPL(ssam_notifier_register); - * @ctrl: The controller the notifier has been registered on. - * @n: The event notifier to unregister. - * -- * Unregister an event notifier and decrement the usage counter of the -- * associated SAM event. If the usage counter reaches zero, the event will be -- * disabled. -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. - * - * Return: Returns zero on success, %-ENOENT if the given notifier block has - * not been registered on the controller. If the given notifier block was the - * last one associated with its specific event, returns the status of the - * event-disable EC-command. - */ --int ssam_notifier_unregister(struct ssam_controller *ctrl, -- struct ssam_event_notifier *n) -+int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) - { - u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); - struct ssam_nf_refcount_entry *entry; -@@ -2236,6 +2243,13 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, - return -ENOENT; - } - -+ /* -+ * If this is an observer notifier, do not attempt to disable the -+ * event, just remove it. -+ */ -+ if (n->flags & SSAM_EVENT_NOTIFIER_OBSERVER) -+ goto remove; -+ - entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); - if (WARN_ON(!entry)) { - /* -@@ -2260,8 +2274,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, - } - - if (entry->refcount == 0) { -- status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, -- n->event.flags); -+ status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, n->event.flags); - kfree(entry); - } - -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index 0806796eabcb..cf4bb48a850e 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -795,6 +795,20 @@ enum ssam_event_mask { - #define SSAM_EVENT_REGISTRY_REG \ - SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02) - -+/** -+ * enum ssam_event_notifier_flags - Flags for event notifiers. -+ * @SSAM_EVENT_NOTIFIER_OBSERVER: -+ * The corresponding notifier acts as observer. Registering a notifier -+ * with this flag set will not attempt to enable any event. Equally, -+ * unregistering will not attempt to disable any event. Note that a -+ * notifier with this flag may not even correspond to a certain event at -+ * all, only to a specific event target category. Event matching will not -+ * be influenced by this flag. -+ */ -+enum ssam_event_notifier_flags { -+ SSAM_EVENT_NOTIFIER_OBSERVER = BIT(0), -+}; -+ - /** - * struct ssam_event_notifier - Notifier block for SSAM events. - * @base: The base notifier block with callback function and priority. -@@ -803,6 +817,7 @@ enum ssam_event_mask { - * @event.id: ID specifying the event. - * @event.mask: Flags determining how events are matched to the notifier. - * @event.flags: Flags used for enabling the event. -+ * @flags: Notifier flags (see &enum ssam_event_notifier_flags). - */ - struct ssam_event_notifier { - struct ssam_notifier_block base; -@@ -813,6 +828,8 @@ struct ssam_event_notifier { - enum ssam_event_mask mask; - u8 flags; - } event; -+ -+ unsigned long flags; - }; - - int ssam_notifier_register(struct ssam_controller *ctrl, --- -2.33.0 - -From 74f47ca38affed9e9d399dec770ddefd550e043b Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 4 Jun 2021 15:47:50 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow enabling of events - without notifiers - -We can already enable and disable SAM events via one of two ways: either -via a (non-observer) notifier tied to a specific event group, or a -generic event enable/disable request. In some instances, however, -neither method may be desirable. - -The first method will tie the event enable request to a specific -notifier, however, when we want to receive notifications for multiple -event groups of the same target category and forward this to the same -notifier callback, we may receive duplicate events, i.e. one event per -registered notifier. The second method will bypass the internal -reference counting mechanism, meaning that a disable request will -disable the event regardless of any other client driver using it, which -may break the functionality of that driver. - -To address this problem, add new functions that allow enabling and -disabling of events via the event reference counting mechanism built -into the controller, without needing to register a notifier. - -This can then be used in combination with observer notifiers to process -multiple events of the same target category without duplication in the -same callback function. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210604134755.535590-3-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../platform/surface/aggregator/controller.c | 293 +++++++++++++++--- - include/linux/surface_aggregator/controller.h | 8 + - 2 files changed, 253 insertions(+), 48 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index cd3a6b77f48d..cedd0f779f7a 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -407,6 +407,31 @@ ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg, - return NULL; - } - -+/** -+ * ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the -+ * given event and free its entry if the reference count reaches zero. -+ * @nf: The notifier system reference. -+ * @reg: The registry used to enable/disable the event. -+ * @id: The event ID. -+ * -+ * Decrements the reference-/activation-count of the specified event, freeing -+ * its entry if it reaches zero. -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ */ -+static void ssam_nf_refcount_dec_free(struct ssam_nf *nf, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id) -+{ -+ struct ssam_nf_refcount_entry *entry; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ entry = ssam_nf_refcount_dec(nf, reg, id); -+ if (entry && entry->refcount == 0) -+ kfree(entry); -+} -+ - /** - * ssam_nf_refcount_empty() - Test if the notification system has any - * enabled/active events. -@@ -2122,6 +2147,109 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) - - /* -- Top-level event registry interface. ----------------------------------- */ - -+/** -+ * ssam_nf_refcount_enable() - Enable event for reference count entry if it has -+ * not already been enabled. -+ * @ctrl: The controller to enable the event on. -+ * @entry: The reference count entry for the event to be enabled. -+ * @flags: The flags used for enabling the event on the EC. -+ * -+ * Enable the event associated with the given reference count entry if the -+ * reference count equals one, i.e. the event has not previously been enabled. -+ * If the event has already been enabled (i.e. reference count not equal to -+ * one), check that the flags used for enabling match and warn about this if -+ * they do not. -+ * -+ * This does not modify the reference count itself, which is done with -+ * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ * -+ * Return: Returns zero on success. If the event is enabled by this call, -+ * returns the status of the event-enable EC command. -+ */ -+static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, -+ struct ssam_nf_refcount_entry *entry, u8 flags) -+{ -+ const struct ssam_event_registry reg = entry->key.reg; -+ const struct ssam_event_id id = entry->key.id; -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ int status; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ reg.target_category, id.target_category, id.instance, entry->refcount); -+ -+ if (entry->refcount == 1) { -+ status = ssam_ssh_event_enable(ctrl, reg, id, flags); -+ if (status) -+ return status; -+ -+ entry->flags = flags; -+ -+ } else if (entry->flags != flags) { -+ ssam_warn(ctrl, -+ "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -+ flags, entry->flags, reg.target_category, id.target_category, -+ id.instance); -+ } -+ -+ return 0; -+} -+ -+/** -+ * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is -+ * no longer in use and free the corresponding entry. -+ * @ctrl: The controller to disable the event on. -+ * @entry: The reference count entry for the event to be disabled. -+ * @flags: The flags used for enabling the event on the EC. -+ * -+ * If the reference count equals zero, i.e. the event is no longer requested by -+ * any client, the event will be disabled and the corresponding reference count -+ * entry freed. The reference count entry must not be used any more after a -+ * call to this function. -+ * -+ * Also checks if the flags used for disabling the event match the flags used -+ * for enabling the event and warns if they do not (regardless of reference -+ * count). -+ * -+ * This does not modify the reference count itself, which is done with -+ * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). -+ * -+ * Note: ``nf->lock`` must be held when calling this function. -+ * -+ * Return: Returns zero on success. If the event is disabled by this call, -+ * returns the status of the event-enable EC command. -+ */ -+static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, -+ struct ssam_nf_refcount_entry *entry, u8 flags) -+{ -+ const struct ssam_event_registry reg = entry->key.reg; -+ const struct ssam_event_id id = entry->key.id; -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ int status; -+ -+ lockdep_assert_held(&nf->lock); -+ -+ ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ reg.target_category, id.target_category, id.instance, entry->refcount); -+ -+ if (entry->flags != flags) { -+ ssam_warn(ctrl, -+ "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -+ flags, entry->flags, reg.target_category, id.target_category, -+ id.instance); -+ } -+ -+ if (entry->refcount == 0) { -+ status = ssam_ssh_event_disable(ctrl, reg, id, flags); -+ kfree(entry); -+ } -+ -+ return status; -+} -+ - /** - * ssam_notifier_register() - Register an event notifier. - * @ctrl: The controller to register the notifier on. -@@ -2166,41 +2294,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif - mutex_unlock(&nf->lock); - return PTR_ERR(entry); - } -- -- ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -- n->event.reg.target_category, n->event.id.target_category, -- n->event.id.instance, entry->refcount); - } - - status = ssam_nfblk_insert(nf_head, &n->base); - if (status) { -- if (entry) { -- entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); -- if (entry->refcount == 0) -- kfree(entry); -- } -+ if (entry) -+ ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); - - mutex_unlock(&nf->lock); - return status; - } - -- if (entry && entry->refcount == 1) { -- status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, n->event.flags); -+ if (entry) { -+ status = ssam_nf_refcount_enable(ctrl, entry, n->event.flags); - if (status) { - ssam_nfblk_remove(&n->base); -- kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id)); -+ ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); - mutex_unlock(&nf->lock); - synchronize_srcu(&nf_head->srcu); - return status; - } -- -- entry->flags = n->event.flags; -- -- } else if (entry && entry->flags != n->event.flags) { -- ssam_warn(ctrl, -- "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -- n->event.flags, entry->flags, n->event.reg.target_category, -- n->event.id.target_category, n->event.id.instance); - } - - mutex_unlock(&nf->lock); -@@ -2247,35 +2360,20 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - * If this is an observer notifier, do not attempt to disable the - * event, just remove it. - */ -- if (n->flags & SSAM_EVENT_NOTIFIER_OBSERVER) -- goto remove; -- -- entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); -- if (WARN_ON(!entry)) { -- /* -- * If this does not return an entry, there's a logic error -- * somewhere: The notifier block is registered, but the event -- * refcount entry is not there. Remove the notifier block -- * anyways. -- */ -- status = -ENOENT; -- goto remove; -- } -- -- ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -- n->event.reg.target_category, n->event.id.target_category, -- n->event.id.instance, entry->refcount); -- -- if (entry->flags != n->event.flags) { -- ssam_warn(ctrl, -- "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", -- n->event.flags, entry->flags, n->event.reg.target_category, -- n->event.id.target_category, n->event.id.instance); -- } -+ if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { -+ entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); -+ if (WARN_ON(!entry)) { -+ /* -+ * If this does not return an entry, there's a logic -+ * error somewhere: The notifier block is registered, -+ * but the event refcount entry is not there. Remove -+ * the notifier block anyways. -+ */ -+ status = -ENOENT; -+ goto remove; -+ } - -- if (entry->refcount == 0) { -- status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, n->event.flags); -- kfree(entry); -+ status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); - } - - remove: -@@ -2287,6 +2385,105 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - } - EXPORT_SYMBOL_GPL(ssam_notifier_unregister); - -+/** -+ * ssam_controller_event_enable() - Enable the specified event. -+ * @ctrl: The controller to enable the event for. -+ * @reg: The event registry to use for enabling the event. -+ * @id: The event ID specifying the event to be enabled. -+ * @flags: The SAM event flags used for enabling the event. -+ * -+ * Increment the event reference count of the specified event. If the event has -+ * not been enabled previously, it will be enabled by this call. -+ * -+ * Note: In general, ssam_notifier_register() with a non-observer notifier -+ * should be preferred for enabling/disabling events, as this will guarantee -+ * proper ordering and event forwarding in case of errors during event -+ * enabling/disabling. -+ * -+ * Return: Returns zero on success, %-ENOSPC if the reference count for the -+ * specified event has reached its maximum, %-ENOMEM if the corresponding event -+ * entry could not be allocated. If this is the first time that this event has -+ * been enabled (i.e. the reference count was incremented from zero to one by -+ * this call), returns the status of the event-enable EC-command. -+ */ -+int ssam_controller_event_enable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags) -+{ -+ u16 rqid = ssh_tc_to_rqid(id.target_category); -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct ssam_nf_refcount_entry *entry; -+ int status; -+ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ mutex_lock(&nf->lock); -+ -+ entry = ssam_nf_refcount_inc(nf, reg, id); -+ if (IS_ERR(entry)) { -+ mutex_unlock(&nf->lock); -+ return PTR_ERR(entry); -+ } -+ -+ status = ssam_nf_refcount_enable(ctrl, entry, flags); -+ if (status) { -+ ssam_nf_refcount_dec_free(nf, reg, id); -+ mutex_unlock(&nf->lock); -+ return status; -+ } -+ -+ mutex_unlock(&nf->lock); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(ssam_controller_event_enable); -+ -+/** -+ * ssam_controller_event_disable() - Disable the specified event. -+ * @ctrl: The controller to disable the event for. -+ * @reg: The event registry to use for disabling the event. -+ * @id: The event ID specifying the event to be disabled. -+ * @flags: The flags used when enabling the event. -+ * -+ * Decrement the reference count of the specified event. If the reference count -+ * reaches zero, the event will be disabled. -+ * -+ * Note: In general, ssam_notifier_register()/ssam_notifier_unregister() with a -+ * non-observer notifier should be preferred for enabling/disabling events, as -+ * this will guarantee proper ordering and event forwarding in case of errors -+ * during event enabling/disabling. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given event has not been -+ * enabled on the controller. If the reference count of the event reaches zero -+ * during this call, returns the status of the event-disable EC-command. -+ */ -+int ssam_controller_event_disable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags) -+{ -+ u16 rqid = ssh_tc_to_rqid(id.target_category); -+ struct ssam_nf *nf = &ctrl->cplt.event.notif; -+ struct ssam_nf_refcount_entry *entry; -+ int status = 0; -+ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ mutex_lock(&nf->lock); -+ -+ entry = ssam_nf_refcount_dec(nf, reg, id); -+ if (!entry) { -+ mutex_unlock(&nf->lock); -+ return -ENOENT; -+ } -+ -+ status = ssam_nf_refcount_disable_free(ctrl, entry, flags); -+ -+ mutex_unlock(&nf->lock); -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_controller_event_disable); -+ - /** - * ssam_notifier_disable_registered() - Disable events for all registered - * notifiers. -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index cf4bb48a850e..7965bdc669c5 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -838,4 +838,12 @@ int ssam_notifier_register(struct ssam_controller *ctrl, - int ssam_notifier_unregister(struct ssam_controller *ctrl, - struct ssam_event_notifier *n); - -+int ssam_controller_event_enable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags); -+ -+int ssam_controller_event_disable(struct ssam_controller *ctrl, -+ struct ssam_event_registry reg, -+ struct ssam_event_id id, u8 flags); -+ - #endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */ --- -2.33.0 - -From 9526a89c84b92359d384c45ac58acd2c14cbb5bc Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 4 Jun 2021 15:47:51 +0200 -Subject: [PATCH] platform/surface: aggregator: Update copyright - -It's 2021, update the copyright accordingly. - -Signed-off-by: Maximilian Luz -Reviewed-by: Hans de Goede -Link: https://lore.kernel.org/r/20210604134755.535590-4-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/Kconfig | 2 +- - drivers/platform/surface/aggregator/Makefile | 2 +- - drivers/platform/surface/aggregator/bus.c | 2 +- - drivers/platform/surface/aggregator/bus.h | 2 +- - drivers/platform/surface/aggregator/controller.c | 2 +- - drivers/platform/surface/aggregator/controller.h | 2 +- - drivers/platform/surface/aggregator/core.c | 2 +- - drivers/platform/surface/aggregator/ssh_msgb.h | 2 +- - drivers/platform/surface/aggregator/ssh_packet_layer.c | 2 +- - drivers/platform/surface/aggregator/ssh_packet_layer.h | 2 +- - drivers/platform/surface/aggregator/ssh_parser.c | 2 +- - drivers/platform/surface/aggregator/ssh_parser.h | 2 +- - drivers/platform/surface/aggregator/ssh_request_layer.c | 2 +- - drivers/platform/surface/aggregator/ssh_request_layer.h | 2 +- - drivers/platform/surface/aggregator/trace.h | 2 +- - include/linux/surface_aggregator/controller.h | 2 +- - include/linux/surface_aggregator/device.h | 2 +- - include/linux/surface_aggregator/serial_hub.h | 2 +- - 18 files changed, 18 insertions(+), 18 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig -index 3aaeea9f0433..fd6dc452f3e8 100644 ---- a/drivers/platform/surface/aggregator/Kconfig -+++ b/drivers/platform/surface/aggregator/Kconfig -@@ -1,5 +1,5 @@ - # SPDX-License-Identifier: GPL-2.0+ --# Copyright (C) 2019-2020 Maximilian Luz -+# Copyright (C) 2019-2021 Maximilian Luz - - menuconfig SURFACE_AGGREGATOR - tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" -diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile -index c112e2c7112b..c8498c41e758 100644 ---- a/drivers/platform/surface/aggregator/Makefile -+++ b/drivers/platform/surface/aggregator/Makefile -@@ -1,5 +1,5 @@ - # SPDX-License-Identifier: GPL-2.0+ --# Copyright (C) 2019-2020 Maximilian Luz -+# Copyright (C) 2019-2021 Maximilian Luz - - # For include/trace/define_trace.h to include trace.h - CFLAGS_core.o = -I$(src) -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -index a9b660af0917..0169677c243e 100644 ---- a/drivers/platform/surface/aggregator/bus.c -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -2,7 +2,7 @@ - /* - * Surface System Aggregator Module bus and device integration. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h -index 7712baaed6a5..ed032c2cbdb2 100644 ---- a/drivers/platform/surface/aggregator/bus.h -+++ b/drivers/platform/surface/aggregator/bus.h -@@ -2,7 +2,7 @@ - /* - * Surface System Aggregator Module bus and device integration. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_BUS_H -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index cedd0f779f7a..6646f4d6e10d 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2,7 +2,7 @@ - /* - * Main SSAM/SSH controller structure and functionality. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h -index 8297d34e7489..a0963c3562ff 100644 ---- a/drivers/platform/surface/aggregator/controller.h -+++ b/drivers/platform/surface/aggregator/controller.h -@@ -2,7 +2,7 @@ - /* - * Main SSAM/SSH controller structure and functionality. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_CONTROLLER_H -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -index 8dc2c267bcd6..5d780e55f4a1 100644 ---- a/drivers/platform/surface/aggregator/core.c -+++ b/drivers/platform/surface/aggregator/core.c -@@ -7,7 +7,7 @@ - * Handles communication via requests as well as enabling, disabling, and - * relaying of events. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h -index 1221f642dda1..e562958ffdf0 100644 ---- a/drivers/platform/surface/aggregator/ssh_msgb.h -+++ b/drivers/platform/surface/aggregator/ssh_msgb.h -@@ -2,7 +2,7 @@ - /* - * SSH message builder functions. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c -index 15d96eac6811..5e08049fc3ac 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c -@@ -2,7 +2,7 @@ - /* - * SSH packet transport layer. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h -index e8757d03f279..2eb329f0b91a 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.h -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h -@@ -2,7 +2,7 @@ - /* - * SSH packet transport layer. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H -diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c -index e2dead8de94a..b77912f8f13b 100644 ---- a/drivers/platform/surface/aggregator/ssh_parser.c -+++ b/drivers/platform/surface/aggregator/ssh_parser.c -@@ -2,7 +2,7 @@ - /* - * SSH message parser. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h -index 63c38d350988..3bd6e180fd16 100644 ---- a/drivers/platform/surface/aggregator/ssh_parser.h -+++ b/drivers/platform/surface/aggregator/ssh_parser.h -@@ -2,7 +2,7 @@ - /* - * SSH message parser. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c -index 52a83a8fcf82..bfe1aaf38065 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.c -@@ -2,7 +2,7 @@ - /* - * SSH request transport layer. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h -index cb35815858d1..9c3cbae2d4bd 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.h -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.h -@@ -2,7 +2,7 @@ - /* - * SSH request transport layer. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H -diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h -index eb332bb53ae4..de64cf169060 100644 ---- a/drivers/platform/surface/aggregator/trace.h -+++ b/drivers/platform/surface/aggregator/trace.h -@@ -2,7 +2,7 @@ - /* - * Trace points for SSAM/SSH. - * -- * Copyright (C) 2020 Maximilian Luz -+ * Copyright (C) 2020-2021 Maximilian Luz - */ - - #undef TRACE_SYSTEM -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index 7965bdc669c5..068e1982ad37 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -6,7 +6,7 @@ - * managing access and communication to and from the SSAM EC, as well as main - * communication structures and definitions. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 6ff9c58b3e17..f636c5310321 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -7,7 +7,7 @@ - * Provides support for non-platform/non-ACPI SSAM clients via dedicated - * subsystem. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H -diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h -index 64276fbfa1d5..c3de43edcffa 100644 ---- a/include/linux/surface_aggregator/serial_hub.h -+++ b/include/linux/surface_aggregator/serial_hub.h -@@ -6,7 +6,7 @@ - * Surface System Aggregator Module (SSAM). Provides the interface for basic - * packet- and request-based communication with the SSAM EC via SSH. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2021 Maximilian Luz - */ - - #ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H --- -2.33.0 - -From ab4937068c246b90061d16fd7fa15bf8b28ee3fe Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 4 Jun 2021 15:47:52 +0200 -Subject: [PATCH] platform/surface: aggregator_cdev: Add support for forwarding - events to user-space - -Currently, debugging unknown events requires writing a custom driver. -This is somewhat difficult, slow to adapt, and not entirely -user-friendly for quickly trying to figure out things on devices of some -third-party user. We can do better. We already have a user-space -interface intended for debugging SAM EC requests, so let's add support -for receiving events to that. - -This commit provides support for receiving events by reading from the -controller file. It additionally introduces two new IOCTLs to control -which event categories will be forwarded. Specifically, a user-space -client can specify which target categories it wants to receive events -from by registering the corresponding notifier(s) via the IOCTLs and -after that, read the received events by reading from the controller -device. - -Signed-off-by: Maximilian Luz -Reviewed-by: Hans de Goede -Link: https://lore.kernel.org/r/20210604134755.535590-5-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../userspace-api/ioctl/ioctl-number.rst | 2 +- - .../surface/surface_aggregator_cdev.c | 460 +++++++++++++++++- - include/uapi/linux/surface_aggregator/cdev.h | 41 +- - 3 files changed, 477 insertions(+), 26 deletions(-) - -diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst -index 9bfc2b510c64..1409e40e6345 100644 ---- a/Documentation/userspace-api/ioctl/ioctl-number.rst -+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst -@@ -325,7 +325,7 @@ Code Seq# Include File Comments - 0xA3 90-9F linux/dtlk.h - 0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem - 0xA4 00-1F uapi/asm/sgx.h --0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator -+0xA5 01-05 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator - - 0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver - -diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c -index 79e28fab7e40..dcda377896b7 100644 ---- a/drivers/platform/surface/surface_aggregator_cdev.c -+++ b/drivers/platform/surface/surface_aggregator_cdev.c -@@ -3,29 +3,69 @@ - * Provides user-space access to the SSAM EC via the /dev/surface/aggregator - * misc device. Intended for debugging and development. - * -- * Copyright (C) 2020 Maximilian Luz -+ * Copyright (C) 2020-2021 Maximilian Luz - */ - - #include -+#include - #include -+#include - #include - #include - #include - #include -+#include - #include - #include - #include -+#include - - #include - #include -+#include - - #define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" - -+ -+/* -- Main structures. ------------------------------------------------------ */ -+ -+enum ssam_cdev_device_state { -+ SSAM_CDEV_DEVICE_SHUTDOWN_BIT = BIT(0), -+}; -+ - struct ssam_cdev { - struct kref kref; - struct rw_semaphore lock; -+ -+ struct device *dev; - struct ssam_controller *ctrl; - struct miscdevice mdev; -+ unsigned long flags; -+ -+ struct rw_semaphore client_lock; /* Guards client list. */ -+ struct list_head client_list; -+}; -+ -+struct ssam_cdev_client; -+ -+struct ssam_cdev_notifier { -+ struct ssam_cdev_client *client; -+ struct ssam_event_notifier nf; -+}; -+ -+struct ssam_cdev_client { -+ struct ssam_cdev *cdev; -+ struct list_head node; -+ -+ struct mutex notifier_lock; /* Guards notifier access for registration */ -+ struct ssam_cdev_notifier *notifier[SSH_NUM_EVENTS]; -+ -+ struct mutex read_lock; /* Guards FIFO buffer read access */ -+ struct mutex write_lock; /* Guards FIFO buffer write access */ -+ DECLARE_KFIFO(buffer, u8, 4096); -+ -+ wait_queue_head_t waitq; -+ struct fasync_struct *fasync; - }; - - static void __ssam_cdev_release(struct kref *kref) -@@ -47,24 +87,169 @@ static void ssam_cdev_put(struct ssam_cdev *cdev) - kref_put(&cdev->kref, __ssam_cdev_release); - } - --static int ssam_cdev_device_open(struct inode *inode, struct file *filp) -+ -+/* -- Notifier handling. ---------------------------------------------------- */ -+ -+static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in) - { -- struct miscdevice *mdev = filp->private_data; -- struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); -+ struct ssam_cdev_notifier *cdev_nf = container_of(nf, struct ssam_cdev_notifier, nf); -+ struct ssam_cdev_client *client = cdev_nf->client; -+ struct ssam_cdev_event event; -+ size_t n = struct_size(&event, data, in->length); -+ -+ /* Translate event. */ -+ event.target_category = in->target_category; -+ event.target_id = in->target_id; -+ event.command_id = in->command_id; -+ event.instance_id = in->instance_id; -+ event.length = in->length; -+ -+ mutex_lock(&client->write_lock); -+ -+ /* Make sure we have enough space. */ -+ if (kfifo_avail(&client->buffer) < n) { -+ dev_warn(client->cdev->dev, -+ "buffer full, dropping event (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", -+ in->target_category, in->target_id, in->command_id, in->instance_id); -+ mutex_unlock(&client->write_lock); -+ return 0; -+ } - -- filp->private_data = ssam_cdev_get(cdev); -- return stream_open(inode, filp); -+ /* Copy event header and payload. */ -+ kfifo_in(&client->buffer, (const u8 *)&event, struct_size(&event, data, 0)); -+ kfifo_in(&client->buffer, &in->data[0], in->length); -+ -+ mutex_unlock(&client->write_lock); -+ -+ /* Notify waiting readers. */ -+ kill_fasync(&client->fasync, SIGIO, POLL_IN); -+ wake_up_interruptible(&client->waitq); -+ -+ /* -+ * Don't mark events as handled, this is the job of a proper driver and -+ * not the debugging interface. -+ */ -+ return 0; - } - --static int ssam_cdev_device_release(struct inode *inode, struct file *filp) -+static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, int priority) - { -- ssam_cdev_put(filp->private_data); -- return 0; -+ const u16 rqid = ssh_tc_to_rqid(tc); -+ const u16 event = ssh_rqid_to_event(rqid); -+ struct ssam_cdev_notifier *nf; -+ int status; -+ -+ /* Validate notifier target category. */ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ mutex_lock(&client->notifier_lock); -+ -+ /* Check if the notifier has already been registered. */ -+ if (client->notifier[event]) { -+ mutex_unlock(&client->notifier_lock); -+ return -EEXIST; -+ } -+ -+ /* Allocate new notifier. */ -+ nf = kzalloc(sizeof(*nf), GFP_KERNEL); -+ if (!nf) { -+ mutex_unlock(&client->notifier_lock); -+ return -ENOMEM; -+ } -+ -+ /* -+ * Create a dummy notifier with the minimal required fields for -+ * observer registration. Note that we can skip fully specifying event -+ * and registry here as we do not need any matching and use silent -+ * registration, which does not enable the corresponding event. -+ */ -+ nf->client = client; -+ nf->nf.base.fn = ssam_cdev_notifier; -+ nf->nf.base.priority = priority; -+ nf->nf.event.id.target_category = tc; -+ nf->nf.event.mask = 0; /* Do not do any matching. */ -+ nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER; -+ -+ /* Register notifier. */ -+ status = ssam_notifier_register(client->cdev->ctrl, &nf->nf); -+ if (status) -+ kfree(nf); -+ else -+ client->notifier[event] = nf; -+ -+ mutex_unlock(&client->notifier_lock); -+ return status; - } - --static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) -+static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc) -+{ -+ const u16 rqid = ssh_tc_to_rqid(tc); -+ const u16 event = ssh_rqid_to_event(rqid); -+ int status; -+ -+ /* Validate notifier target category. */ -+ if (!ssh_rqid_is_event(rqid)) -+ return -EINVAL; -+ -+ mutex_lock(&client->notifier_lock); -+ -+ /* Check if the notifier is currently registered. */ -+ if (!client->notifier[event]) { -+ mutex_unlock(&client->notifier_lock); -+ return -ENOENT; -+ } -+ -+ /* Unregister and free notifier. */ -+ status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[event]->nf); -+ kfree(client->notifier[event]); -+ client->notifier[event] = NULL; -+ -+ mutex_unlock(&client->notifier_lock); -+ return status; -+} -+ -+static void ssam_cdev_notifier_unregister_all(struct ssam_cdev_client *client) -+{ -+ int i; -+ -+ down_read(&client->cdev->lock); -+ -+ /* -+ * This function may be used during shutdown, thus we need to test for -+ * cdev->ctrl instead of the SSAM_CDEV_DEVICE_SHUTDOWN_BIT bit. -+ */ -+ if (client->cdev->ctrl) { -+ for (i = 0; i < SSH_NUM_EVENTS; i++) -+ ssam_cdev_notifier_unregister(client, i + 1); -+ -+ } else { -+ int count = 0; -+ -+ /* -+ * Device has been shut down. Any notifier remaining is a bug, -+ * so warn about that as this would otherwise hardly be -+ * noticeable. Nevertheless, free them as well. -+ */ -+ mutex_lock(&client->notifier_lock); -+ for (i = 0; i < SSH_NUM_EVENTS; i++) { -+ count += !!(client->notifier[i]); -+ kfree(client->notifier[i]); -+ client->notifier[i] = NULL; -+ } -+ mutex_unlock(&client->notifier_lock); -+ -+ WARN_ON(count > 0); -+ } -+ -+ up_read(&client->cdev->lock); -+} -+ -+ -+/* -- IOCTL functions. ------------------------------------------------------ */ -+ -+static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_request __user *r) - { -- struct ssam_cdev_request __user *r; - struct ssam_cdev_request rqst; - struct ssam_request spec = {}; - struct ssam_response rsp = {}; -@@ -72,7 +257,6 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) - void __user *rspdata; - int status = 0, ret = 0, tmp; - -- r = (struct ssam_cdev_request __user *)arg; - ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); - if (ret) - goto out; -@@ -152,7 +336,7 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) - } - - /* Perform request. */ -- status = ssam_request_sync(cdev->ctrl, &spec, &rsp); -+ status = ssam_request_sync(client->cdev->ctrl, &spec, &rsp); - if (status) - goto out; - -@@ -177,48 +361,247 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) - return ret; - } - --static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd, -+static long ssam_cdev_notif_register(struct ssam_cdev_client *client, -+ const struct ssam_cdev_notifier_desc __user *d) -+{ -+ struct ssam_cdev_notifier_desc desc; -+ long ret; -+ -+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ return ssam_cdev_notifier_register(client, desc.target_category, desc.priority); -+} -+ -+static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, -+ const struct ssam_cdev_notifier_desc __user *d) -+{ -+ struct ssam_cdev_notifier_desc desc; -+ long ret; -+ -+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ return ssam_cdev_notifier_unregister(client, desc.target_category); -+} -+ -+ -+/* -- File operations. ------------------------------------------------------ */ -+ -+static int ssam_cdev_device_open(struct inode *inode, struct file *filp) -+{ -+ struct miscdevice *mdev = filp->private_data; -+ struct ssam_cdev_client *client; -+ struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); -+ -+ /* Initialize client */ -+ client = vzalloc(sizeof(*client)); -+ if (!client) -+ return -ENOMEM; -+ -+ client->cdev = ssam_cdev_get(cdev); -+ -+ INIT_LIST_HEAD(&client->node); -+ -+ mutex_init(&client->notifier_lock); -+ -+ mutex_init(&client->read_lock); -+ mutex_init(&client->write_lock); -+ INIT_KFIFO(client->buffer); -+ init_waitqueue_head(&client->waitq); -+ -+ filp->private_data = client; -+ -+ /* Attach client. */ -+ down_write(&cdev->client_lock); -+ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { -+ up_write(&cdev->client_lock); -+ mutex_destroy(&client->write_lock); -+ mutex_destroy(&client->read_lock); -+ mutex_destroy(&client->notifier_lock); -+ ssam_cdev_put(client->cdev); -+ vfree(client); -+ return -ENODEV; -+ } -+ list_add_tail(&client->node, &cdev->client_list); -+ -+ up_write(&cdev->client_lock); -+ -+ stream_open(inode, filp); -+ return 0; -+} -+ -+static int ssam_cdev_device_release(struct inode *inode, struct file *filp) -+{ -+ struct ssam_cdev_client *client = filp->private_data; -+ -+ /* Force-unregister all remaining notifiers of this client. */ -+ ssam_cdev_notifier_unregister_all(client); -+ -+ /* Detach client. */ -+ down_write(&client->cdev->client_lock); -+ list_del(&client->node); -+ up_write(&client->cdev->client_lock); -+ -+ /* Free client. */ -+ mutex_destroy(&client->write_lock); -+ mutex_destroy(&client->read_lock); -+ -+ mutex_destroy(&client->notifier_lock); -+ -+ ssam_cdev_put(client->cdev); -+ vfree(client); -+ -+ return 0; -+} -+ -+static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd, - unsigned long arg) - { - switch (cmd) { - case SSAM_CDEV_REQUEST: -- return ssam_cdev_request(cdev, arg); -+ return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg); -+ -+ case SSAM_CDEV_NOTIF_REGISTER: -+ return ssam_cdev_notif_register(client, -+ (struct ssam_cdev_notifier_desc __user *)arg); -+ -+ case SSAM_CDEV_NOTIF_UNREGISTER: -+ return ssam_cdev_notif_unregister(client, -+ (struct ssam_cdev_notifier_desc __user *)arg); - - default: - return -ENOTTY; - } - } - --static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, -- unsigned long arg) -+static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) - { -- struct ssam_cdev *cdev = file->private_data; -+ struct ssam_cdev_client *client = file->private_data; - long status; - - /* Ensure that controller is valid for as long as we need it. */ -+ if (down_read_killable(&client->cdev->lock)) -+ return -ERESTARTSYS; -+ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) { -+ up_read(&client->cdev->lock); -+ return -ENODEV; -+ } -+ -+ status = __ssam_cdev_device_ioctl(client, cmd, arg); -+ -+ up_read(&client->cdev->lock); -+ return status; -+} -+ -+static ssize_t ssam_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offs) -+{ -+ struct ssam_cdev_client *client = file->private_data; -+ struct ssam_cdev *cdev = client->cdev; -+ unsigned int copied; -+ int status = 0; -+ - if (down_read_killable(&cdev->lock)) - return -ERESTARTSYS; - -- if (!cdev->ctrl) { -+ /* Make sure we're not shut down. */ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { - up_read(&cdev->lock); - return -ENODEV; - } - -- status = __ssam_cdev_device_ioctl(cdev, cmd, arg); -+ do { -+ /* Check availability, wait if necessary. */ -+ if (kfifo_is_empty(&client->buffer)) { -+ up_read(&cdev->lock); -+ -+ if (file->f_flags & O_NONBLOCK) -+ return -EAGAIN; -+ -+ status = wait_event_interruptible(client->waitq, -+ !kfifo_is_empty(&client->buffer) || -+ test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, -+ &cdev->flags)); -+ if (status < 0) -+ return status; -+ -+ if (down_read_killable(&cdev->lock)) -+ return -ERESTARTSYS; -+ -+ /* Need to check that we're not shut down again. */ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { -+ up_read(&cdev->lock); -+ return -ENODEV; -+ } -+ } -+ -+ /* Try to read from FIFO. */ -+ if (mutex_lock_interruptible(&client->read_lock)) { -+ up_read(&cdev->lock); -+ return -ERESTARTSYS; -+ } -+ -+ status = kfifo_to_user(&client->buffer, buf, count, &copied); -+ mutex_unlock(&client->read_lock); -+ -+ if (status < 0) { -+ up_read(&cdev->lock); -+ return status; -+ } -+ -+ /* We might not have gotten anything, check this here. */ -+ if (copied == 0 && (file->f_flags & O_NONBLOCK)) { -+ up_read(&cdev->lock); -+ return -EAGAIN; -+ } -+ } while (copied == 0); - - up_read(&cdev->lock); -- return status; -+ return copied; -+} -+ -+static __poll_t ssam_cdev_poll(struct file *file, struct poll_table_struct *pt) -+{ -+ struct ssam_cdev_client *client = file->private_data; -+ __poll_t events = 0; -+ -+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) -+ return EPOLLHUP | EPOLLERR; -+ -+ poll_wait(file, &client->waitq, pt); -+ -+ if (!kfifo_is_empty(&client->buffer)) -+ events |= EPOLLIN | EPOLLRDNORM; -+ -+ return events; -+} -+ -+static int ssam_cdev_fasync(int fd, struct file *file, int on) -+{ -+ struct ssam_cdev_client *client = file->private_data; -+ -+ return fasync_helper(fd, file, on, &client->fasync); - } - - static const struct file_operations ssam_controller_fops = { - .owner = THIS_MODULE, - .open = ssam_cdev_device_open, - .release = ssam_cdev_device_release, -+ .read = ssam_cdev_read, -+ .poll = ssam_cdev_poll, -+ .fasync = ssam_cdev_fasync, - .unlocked_ioctl = ssam_cdev_device_ioctl, - .compat_ioctl = ssam_cdev_device_ioctl, -- .llseek = noop_llseek, -+ .llseek = no_llseek, - }; - -+ -+/* -- Device and driver setup ----------------------------------------------- */ -+ - static int ssam_dbg_device_probe(struct platform_device *pdev) - { - struct ssam_controller *ctrl; -@@ -236,6 +619,7 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) - kref_init(&cdev->kref); - init_rwsem(&cdev->lock); - cdev->ctrl = ctrl; -+ cdev->dev = &pdev->dev; - - cdev->mdev.parent = &pdev->dev; - cdev->mdev.minor = MISC_DYNAMIC_MINOR; -@@ -243,6 +627,9 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) - cdev->mdev.nodename = "surface/aggregator"; - cdev->mdev.fops = &ssam_controller_fops; - -+ init_rwsem(&cdev->client_lock); -+ INIT_LIST_HEAD(&cdev->client_list); -+ - status = misc_register(&cdev->mdev); - if (status) { - kfree(cdev); -@@ -256,8 +643,32 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) - static int ssam_dbg_device_remove(struct platform_device *pdev) - { - struct ssam_cdev *cdev = platform_get_drvdata(pdev); -+ struct ssam_cdev_client *client; - -- misc_deregister(&cdev->mdev); -+ /* -+ * Mark device as shut-down. Prevent new clients from being added and -+ * new operations from being executed. -+ */ -+ set_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags); -+ -+ down_write(&cdev->client_lock); -+ -+ /* Remove all notifiers registered by us. */ -+ list_for_each_entry(client, &cdev->client_list, node) { -+ ssam_cdev_notifier_unregister_all(client); -+ } -+ -+ /* Wake up async clients. */ -+ list_for_each_entry(client, &cdev->client_list, node) { -+ kill_fasync(&client->fasync, SIGIO, POLL_HUP); -+ } -+ -+ /* Wake up blocking clients. */ -+ list_for_each_entry(client, &cdev->client_list, node) { -+ wake_up_interruptible(&client->waitq); -+ } -+ -+ up_write(&cdev->client_lock); - - /* - * The controller is only guaranteed to be valid for as long as the -@@ -266,8 +677,11 @@ static int ssam_dbg_device_remove(struct platform_device *pdev) - */ - down_write(&cdev->lock); - cdev->ctrl = NULL; -+ cdev->dev = NULL; - up_write(&cdev->lock); - -+ misc_deregister(&cdev->mdev); -+ - ssam_cdev_put(cdev); - return 0; - } -diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h -index fbcce04abfe9..4f393fafc235 100644 ---- a/include/uapi/linux/surface_aggregator/cdev.h -+++ b/include/uapi/linux/surface_aggregator/cdev.h -@@ -6,7 +6,7 @@ - * device. This device provides direct user-space access to the SSAM EC. - * Intended for debugging and development. - * -- * Copyright (C) 2020 Maximilian Luz -+ * Copyright (C) 2020-2021 Maximilian Luz - */ - - #ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H -@@ -73,6 +73,43 @@ struct ssam_cdev_request { - } response; - } __attribute__((__packed__)); - --#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) -+/** -+ * struct ssam_cdev_notifier_desc - Notifier descriptor. -+ * @priority: Priority value determining the order in which notifier -+ * callbacks will be called. A higher value means higher -+ * priority, i.e. the associated callback will be executed -+ * earlier than other (lower priority) callbacks. -+ * @target_category: The event target category for which this notifier should -+ * receive events. -+ * -+ * Specifies the notifier that should be registered or unregistered, -+ * specifically with which priority and for which target category of events. -+ */ -+struct ssam_cdev_notifier_desc { -+ __s32 priority; -+ __u8 target_category; -+} __attribute__((__packed__)); -+ -+/** -+ * struct ssam_cdev_event - SSAM event sent by the EC. -+ * @target_category: Target category of the event source. See &enum ssam_ssh_tc. -+ * @target_id: Target ID of the event source. -+ * @command_id: Command ID of the event. -+ * @instance_id: Instance ID of the event source. -+ * @length: Length of the event payload in bytes. -+ * @data: Event payload data. -+ */ -+struct ssam_cdev_event { -+ __u8 target_category; -+ __u8 target_id; -+ __u8 command_id; -+ __u8 instance_id; -+ __u16 length; -+ __u8 data[]; -+} __attribute__((__packed__)); -+ -+#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) -+#define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc) -+#define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc) - - #endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ --- -2.33.0 - -From bba096d6352db3e754471a8b6c5d329bf442df9d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 4 Jun 2021 15:47:53 +0200 -Subject: [PATCH] platform/surface: aggregator_cdev: Allow enabling of events - from user-space - -While events can already be enabled and disabled via the generic request -IOCTL, this bypasses the internal reference counting mechanism of the -controller. Due to that, disabling an event will turn it off regardless -of any other client having requested said event, which may break -functionality of that client. - -To solve this, add IOCTLs wrapping the ssam_controller_event_enable() -and ssam_controller_event_disable() functions, which have been -previously introduced for this specific purpose. - -Signed-off-by: Maximilian Luz -Reviewed-by: Hans de Goede -Link: https://lore.kernel.org/r/20210604134755.535590-6-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface/surface_aggregator_cdev.c | 58 +++++++++++++++++++ - include/uapi/linux/surface_aggregator/cdev.h | 32 ++++++++++ - 2 files changed, 90 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c -index dcda377896b7..7b86b36eaaa0 100644 ---- a/drivers/platform/surface/surface_aggregator_cdev.c -+++ b/drivers/platform/surface/surface_aggregator_cdev.c -@@ -387,6 +387,58 @@ static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, - return ssam_cdev_notifier_unregister(client, desc.target_category); - } - -+static long ssam_cdev_event_enable(struct ssam_cdev_client *client, -+ const struct ssam_cdev_event_desc __user *d) -+{ -+ struct ssam_cdev_event_desc desc; -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ long ret; -+ -+ /* Read descriptor from user-space. */ -+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ /* Translate descriptor. */ -+ reg.target_category = desc.reg.target_category; -+ reg.target_id = desc.reg.target_id; -+ reg.cid_enable = desc.reg.cid_enable; -+ reg.cid_disable = desc.reg.cid_disable; -+ -+ id.target_category = desc.id.target_category; -+ id.instance = desc.id.instance; -+ -+ /* Disable event. */ -+ return ssam_controller_event_enable(client->cdev->ctrl, reg, id, desc.flags); -+} -+ -+static long ssam_cdev_event_disable(struct ssam_cdev_client *client, -+ const struct ssam_cdev_event_desc __user *d) -+{ -+ struct ssam_cdev_event_desc desc; -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ long ret; -+ -+ /* Read descriptor from user-space. */ -+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); -+ if (ret) -+ return ret; -+ -+ /* Translate descriptor. */ -+ reg.target_category = desc.reg.target_category; -+ reg.target_id = desc.reg.target_id; -+ reg.cid_enable = desc.reg.cid_enable; -+ reg.cid_disable = desc.reg.cid_disable; -+ -+ id.target_category = desc.id.target_category; -+ id.instance = desc.id.instance; -+ -+ /* Disable event. */ -+ return ssam_controller_event_disable(client->cdev->ctrl, reg, id, desc.flags); -+} -+ - - /* -- File operations. ------------------------------------------------------ */ - -@@ -473,6 +525,12 @@ static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned i - return ssam_cdev_notif_unregister(client, - (struct ssam_cdev_notifier_desc __user *)arg); - -+ case SSAM_CDEV_EVENT_ENABLE: -+ return ssam_cdev_event_enable(client, (struct ssam_cdev_event_desc __user *)arg); -+ -+ case SSAM_CDEV_EVENT_DISABLE: -+ return ssam_cdev_event_disable(client, (struct ssam_cdev_event_desc __user *)arg); -+ - default: - return -ENOTTY; - } -diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h -index 4f393fafc235..08f46b60b151 100644 ---- a/include/uapi/linux/surface_aggregator/cdev.h -+++ b/include/uapi/linux/surface_aggregator/cdev.h -@@ -90,6 +90,36 @@ struct ssam_cdev_notifier_desc { - __u8 target_category; - } __attribute__((__packed__)); - -+/** -+ * struct ssam_cdev_event_desc - Event descriptor. -+ * @reg: Registry via which the event will be enabled/disabled. -+ * @reg.target_category: Target category for the event registry requests. -+ * @reg.target_id: Target ID for the event registry requests. -+ * @reg.cid_enable: Command ID for the event-enable request. -+ * @reg.cid_disable: Command ID for the event-disable request. -+ * @id: ID specifying the event. -+ * @id.target_category: Target category of the event source. -+ * @id.instance: Instance ID of the event source. -+ * @flags: Flags used for enabling the event. -+ * -+ * Specifies which event should be enabled/disabled and how to do that. -+ */ -+struct ssam_cdev_event_desc { -+ struct { -+ __u8 target_category; -+ __u8 target_id; -+ __u8 cid_enable; -+ __u8 cid_disable; -+ } reg; -+ -+ struct { -+ __u8 target_category; -+ __u8 instance; -+ } id; -+ -+ __u8 flags; -+} __attribute__((__packed__)); -+ - /** - * struct ssam_cdev_event - SSAM event sent by the EC. - * @target_category: Target category of the event source. See &enum ssam_ssh_tc. -@@ -111,5 +141,7 @@ struct ssam_cdev_event { - #define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) - #define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc) - #define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc) -+#define SSAM_CDEV_EVENT_ENABLE _IOW(0xA5, 4, struct ssam_cdev_event_desc) -+#define SSAM_CDEV_EVENT_DISABLE _IOW(0xA5, 5, struct ssam_cdev_event_desc) - - #endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ --- -2.33.0 - -From a96012260a61b2a179e75cf113317370d5f5d7e8 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 4 Jun 2021 15:47:54 +0200 -Subject: [PATCH] platform/surface: aggregator_cdev: Add lockdep support - -Mark functions with locking requirements via the corresponding lockdep -calls for debugging and documentary purposes. - -Signed-off-by: Maximilian Luz -Reviewed-by: Hans de Goede -Link: https://lore.kernel.org/r/20210604134755.535590-7-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../platform/surface/surface_aggregator_cdev.c | 16 ++++++++++++++++ - 1 file changed, 16 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c -index 7b86b36eaaa0..30fb50fde450 100644 ---- a/drivers/platform/surface/surface_aggregator_cdev.c -+++ b/drivers/platform/surface/surface_aggregator_cdev.c -@@ -139,6 +139,8 @@ static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, i - struct ssam_cdev_notifier *nf; - int status; - -+ lockdep_assert_held_read(&client->cdev->lock); -+ - /* Validate notifier target category. */ - if (!ssh_rqid_is_event(rqid)) - return -EINVAL; -@@ -188,6 +190,8 @@ static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc) - const u16 event = ssh_rqid_to_event(rqid); - int status; - -+ lockdep_assert_held_read(&client->cdev->lock); -+ - /* Validate notifier target category. */ - if (!ssh_rqid_is_event(rqid)) - return -EINVAL; -@@ -257,6 +261,8 @@ static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_ - void __user *rspdata; - int status = 0, ret = 0, tmp; - -+ lockdep_assert_held_read(&client->cdev->lock); -+ - ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); - if (ret) - goto out; -@@ -367,6 +373,8 @@ static long ssam_cdev_notif_register(struct ssam_cdev_client *client, - struct ssam_cdev_notifier_desc desc; - long ret; - -+ lockdep_assert_held_read(&client->cdev->lock); -+ - ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); - if (ret) - return ret; -@@ -380,6 +388,8 @@ static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, - struct ssam_cdev_notifier_desc desc; - long ret; - -+ lockdep_assert_held_read(&client->cdev->lock); -+ - ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); - if (ret) - return ret; -@@ -395,6 +405,8 @@ static long ssam_cdev_event_enable(struct ssam_cdev_client *client, - struct ssam_event_id id; - long ret; - -+ lockdep_assert_held_read(&client->cdev->lock); -+ - /* Read descriptor from user-space. */ - ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); - if (ret) -@@ -421,6 +433,8 @@ static long ssam_cdev_event_disable(struct ssam_cdev_client *client, - struct ssam_event_id id; - long ret; - -+ lockdep_assert_held_read(&client->cdev->lock); -+ - /* Read descriptor from user-space. */ - ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); - if (ret) -@@ -513,6 +527,8 @@ static int ssam_cdev_device_release(struct inode *inode, struct file *filp) - static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd, - unsigned long arg) - { -+ lockdep_assert_held_read(&client->cdev->lock); -+ - switch (cmd) { - case SSAM_CDEV_REQUEST: - return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg); --- -2.33.0 - -From ce84847fc64f4e994c60ca0ce06f8d214b58fc18 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 2 Jun 2021 20:07:47 +0200 -Subject: [PATCH] docs: driver-api: Update Surface Aggregator user-space - interface documentation - -Update the controller-device user-space interface (cdev) documentation -for the newly introduced IOCTLs and event interface. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface_aggregator/clients/cdev.rst | 127 +++++++++++++++++- - 1 file changed, 122 insertions(+), 5 deletions(-) - -diff --git a/Documentation/driver-api/surface_aggregator/clients/cdev.rst b/Documentation/driver-api/surface_aggregator/clients/cdev.rst -index 248c1372d879..0134a841a079 100644 ---- a/Documentation/driver-api/surface_aggregator/clients/cdev.rst -+++ b/Documentation/driver-api/surface_aggregator/clients/cdev.rst -@@ -1,9 +1,8 @@ - .. SPDX-License-Identifier: GPL-2.0+ - --.. |u8| replace:: :c:type:`u8 ` --.. |u16| replace:: :c:type:`u16 ` - .. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request ` - .. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags ` -+.. |ssam_cdev_event| replace:: :c:type:`struct ssam_cdev_event ` - - ============================== - User-Space EC Interface (cdev) -@@ -23,6 +22,40 @@ These IOCTLs and their respective input/output parameter structs are defined in - A small python library and scripts for accessing this interface can be found - at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam. - -+.. contents:: -+ -+ -+Receiving Events -+================ -+ -+Events can be received by reading from the device-file. The are represented by -+the |ssam_cdev_event| datatype. -+ -+Before events are available to be read, however, the desired notifiers must be -+registered via the ``SSAM_CDEV_NOTIF_REGISTER`` IOCTL. Notifiers are, in -+essence, callbacks, called when the EC sends an event. They are, in this -+interface, associated with a specific target category and device-file-instance. -+They forward any event of this category to the buffer of the corresponding -+instance, from which it can then be read. -+ -+Notifiers themselves do not enable events on the EC. Thus, it may additionally -+be necessary to enable events via the ``SSAM_CDEV_EVENT_ENABLE`` IOCTL. While -+notifiers work per-client (i.e. per-device-file-instance), events are enabled -+globally, for the EC and all of its clients (regardless of userspace or -+non-userspace). The ``SSAM_CDEV_EVENT_ENABLE`` and ``SSAM_CDEV_EVENT_DISABLE`` -+IOCTLs take care of reference counting the events, such that an event is -+enabled as long as there is a client that has requested it. -+ -+Note that enabled events are not automatically disabled once the client -+instance is closed. Therefore any client process (or group of processes) should -+balance their event enable calls with the corresponding event disable calls. It -+is, however, perfectly valid to enable and disable events on different client -+instances. For example, it is valid to set up notifiers and read events on -+client instance ``A``, enable those events on instance ``B`` (note that these -+will also be received by A since events are enabled/disabled globally), and -+after no more events are desired, disable the previously enabled events via -+instance ``C``. -+ - - Controller IOCTLs - ================= -@@ -45,9 +78,33 @@ The following IOCTLs are provided: - - ``REQUEST`` - - Perform synchronous SAM request. - -+ * - ``0xA5`` -+ - ``2`` -+ - ``W`` -+ - ``NOTIF_REGISTER`` -+ - Register event notifier. - --``REQUEST`` ------------- -+ * - ``0xA5`` -+ - ``3`` -+ - ``W`` -+ - ``NOTIF_UNREGISTER`` -+ - Unregister event notifier. -+ -+ * - ``0xA5`` -+ - ``4`` -+ - ``W`` -+ - ``EVENT_ENABLE`` -+ - Enable event source. -+ -+ * - ``0xA5`` -+ - ``5`` -+ - ``W`` -+ - ``EVENT_DISABLE`` -+ - Disable event source. -+ -+ -+``SSAM_CDEV_REQUEST`` -+--------------------- - - Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``. - -@@ -82,6 +139,66 @@ submitted, and completed (i.e. handed back to user-space) successfully from - inside the IOCTL, but the request ``status`` member may still be negative in - case the actual execution of the request failed after it has been submitted. - --A full definition of the argument struct is provided below: -+A full definition of the argument struct is provided below. -+ -+``SSAM_CDEV_NOTIF_REGISTER`` -+---------------------------- -+ -+Defined as ``_IOW(0xA5, 2, struct ssam_cdev_notifier_desc)``. -+ -+Register a notifier for the event target category specified in the given -+notifier description with the specified priority. Notifiers registration is -+required to receive events, but does not enable events themselves. After a -+notifier for a specific target category has been registered, all events of that -+category will be forwarded to the userspace client and can then be read from -+the device file instance. Note that events may have to be enabled, e.g. via the -+``SSAM_CDEV_EVENT_ENABLE`` IOCTL, before the EC will send them. -+ -+Only one notifier can be registered per target category and client instance. If -+a notifier has already been registered, this IOCTL will fail with ``-EEXIST``. -+ -+Notifiers will automatically be removed when the device file instance is -+closed. -+ -+``SSAM_CDEV_NOTIF_UNREGISTER`` -+------------------------------ -+ -+Defined as ``_IOW(0xA5, 3, struct ssam_cdev_notifier_desc)``. -+ -+Unregisters the notifier associated with the specified target category. The -+priority field will be ignored by this IOCTL. If no notifier has been -+registered for this client instance and the given category, this IOCTL will -+fail with ``-ENOENT``. -+ -+``SSAM_CDEV_EVENT_ENABLE`` -+-------------------------- -+ -+Defined as ``_IOW(0xA5, 4, struct ssam_cdev_event_desc)``. -+ -+Enable the event associated with the given event descriptor. -+ -+Note that this call will not register a notifier itself, it will only enable -+events on the controller. If you want to receive events by reading from the -+device file, you will need to register the corresponding notifier(s) on that -+instance. -+ -+Events are not automatically disabled when the device file is closed. This must -+be done manually, via a call to the ``SSAM_CDEV_EVENT_DISABLE`` IOCTL. -+ -+``SSAM_CDEV_EVENT_DISABLE`` -+--------------------------- -+ -+Defined as ``_IOW(0xA5, 5, struct ssam_cdev_event_desc)``. -+ -+Disable the event associated with the given event descriptor. -+ -+Note that this will not unregister any notifiers. Events may still be received -+and forwarded to user-space after this call. The only safe way of stopping -+events from being received is unregistering all previously registered -+notifiers. -+ -+ -+Structures and Enums -+==================== - - .. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h --- -2.33.0 - -From 51b3bcb37da96838183cc99e0773808abfa06cd5 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 4 Jun 2021 23:09:06 +0200 -Subject: [PATCH] platform/surface: aggregator: Do not return uninitialized - value - -The status variable in ssam_nf_refcount_disable_free() is only set when -the reference count equals zero. Otherwise, it is returned -uninitialized. Fix this by always initializing status to zero. - -Reported-by: kernel test robot -Fixes: 640ee17199e4 ("platform/surface: aggregator: Allow enabling of events without notifiers") -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210604210907.25738-2-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/controller.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index 6646f4d6e10d..634399387d76 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2228,7 +2228,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - const struct ssam_event_registry reg = entry->key.reg; - const struct ssam_event_id id = entry->key.id; - struct ssam_nf *nf = &ctrl->cplt.event.notif; -- int status; -+ int status = 0; - - lockdep_assert_held(&nf->lock); - --- -2.33.0 - -From 3fef58d7f20633549584c2c28459f292a88a6f5a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 4 Jun 2021 23:09:07 +0200 -Subject: [PATCH] platform/surface: aggregator: Drop unnecessary variable - initialization - -The status variable in ssam_controller_event_disable() is always set, no -need to initialize it. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210604210907.25738-3-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/controller.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index 634399387d76..b8c377b3f932 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2464,7 +2464,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, - u16 rqid = ssh_tc_to_rqid(id.target_category); - struct ssam_nf *nf = &ctrl->cplt.event.notif; - struct ssam_nf_refcount_entry *entry; -- int status = 0; -+ int status; - - if (!ssh_rqid_is_event(rqid)) - return -EINVAL; --- -2.33.0 - -From c55a8b13e2165c6568cee66415ae49e98ff36deb Mon Sep 17 00:00:00 2001 -From: Baokun Li -Date: Wed, 9 Jun 2021 15:26:38 +0800 -Subject: [PATCH] platform/surface: aggregator: Use list_move_tail instead of - list_del/list_add_tail in ssh_request_layer.c - -Using list_move_tail() instead of list_del() + list_add_tail() in ssh_request_layer.c. - -Reported-by: Hulk Robot -Signed-off-by: Baokun Li -Reviewed-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210609072638.1358174-1-libaokun1@huawei.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../platform/surface/aggregator/ssh_request_layer.c | 10 +++------- - 1 file changed, 3 insertions(+), 7 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c -index bfe1aaf38065..790f7f0eee98 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.c -@@ -863,9 +863,7 @@ static void ssh_rtl_timeout_reap(struct work_struct *work) - clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); - - atomic_dec(&rtl->pending.count); -- list_del(&r->node); -- -- list_add_tail(&r->node, &claimed); -+ list_move_tail(&r->node, &claimed); - } - spin_unlock(&rtl->pending.lock); - -@@ -1204,8 +1202,7 @@ void ssh_rtl_shutdown(struct ssh_rtl *rtl) - smp_mb__before_atomic(); - clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); - -- list_del(&r->node); -- list_add_tail(&r->node, &claimed); -+ list_move_tail(&r->node, &claimed); - } - spin_unlock(&rtl->queue.lock); - -@@ -1238,8 +1235,7 @@ void ssh_rtl_shutdown(struct ssh_rtl *rtl) - smp_mb__before_atomic(); - clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); - -- list_del(&r->node); -- list_add_tail(&r->node, &claimed); -+ list_move_tail(&r->node, &claimed); - } - spin_unlock(&rtl->pending.lock); - } --- -2.33.0 - -From 89cedb89f0155bc4c83b4cd7adf17252106ed3ab Mon Sep 17 00:00:00 2001 -From: Baokun Li -Date: Wed, 9 Jun 2021 15:24:48 +0800 -Subject: [PATCH] platform/surface: aggregator: Use list_move_tail instead of - list_del/list_add_tail in ssh_packet_layer.c - -Using list_move_tail() instead of list_del() + list_add_tail() in ssh_packet_layer.c. - -Reported-by: Hulk Robot -Signed-off-by: Baokun Li -Reviewed-by: Maximilian Luz -Link: https://lore.kernel.org/r/20210609072448.1357524-1-libaokun1@huawei.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/ssh_packet_layer.c | 10 +++------- - 1 file changed, 3 insertions(+), 7 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c -index 5e08049fc3ac..8a4451c1ffe5 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c -@@ -1567,9 +1567,7 @@ static void ssh_ptl_timeout_reap(struct work_struct *work) - clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); - - atomic_dec(&ptl->pending.count); -- list_del(&p->pending_node); -- -- list_add_tail(&p->pending_node, &claimed); -+ list_move_tail(&p->pending_node, &claimed); - } - - spin_unlock(&ptl->pending.lock); -@@ -1957,8 +1955,7 @@ void ssh_ptl_shutdown(struct ssh_ptl *ptl) - smp_mb__before_atomic(); - clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); - -- list_del(&p->queue_node); -- list_add_tail(&p->queue_node, &complete_q); -+ list_move_tail(&p->queue_node, &complete_q); - } - spin_unlock(&ptl->queue.lock); - -@@ -1970,8 +1967,7 @@ void ssh_ptl_shutdown(struct ssh_ptl *ptl) - smp_mb__before_atomic(); - clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); - -- list_del(&p->pending_node); -- list_add_tail(&p->pending_node, &complete_q); -+ list_move_tail(&p->pending_node, &complete_q); - } - atomic_set(&ptl->pending.count, 0); - spin_unlock(&ptl->pending.lock); --- -2.33.0 - diff --git a/patches/5.13/0007-surface-hotplug.patch b/patches/5.13/0007-surface-hotplug.patch deleted file mode 100644 index 90cee3271..000000000 --- a/patches/5.13/0007-surface-hotplug.patch +++ /dev/null @@ -1,44 +0,0 @@ -From 5ff5180cf53f8b2218cabfb209006eb48a3e6a75 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 2 Jul 2021 15:51:07 +0200 -Subject: [PATCH] Revert "Revert "PCI: PM: Do not read power state in - pci_enable_device_flags()"" - -This reverts commit 4d6035f9bf4ea12776322746a216e856dfe46698. - -Patchset: surface-hotplug ---- - drivers/pci/pci.c | 16 +++------------- - 1 file changed, 3 insertions(+), 13 deletions(-) - -diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c -index 8d4ebe095d0c..b717680377a9 100644 ---- a/drivers/pci/pci.c -+++ b/drivers/pci/pci.c -@@ -1900,20 +1900,10 @@ static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags) - int err; - int i, bars = 0; - -- /* -- * Power state could be unknown at this point, either due to a fresh -- * boot or a device removal call. So get the current power state -- * so that things like MSI message writing will behave as expected -- * (e.g. if the device really is in D0 at enable time). -- */ -- if (dev->pm_cap) { -- u16 pmcsr; -- pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); -- dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK); -- } -- -- if (atomic_inc_return(&dev->enable_cnt) > 1) -+ if (atomic_inc_return(&dev->enable_cnt) > 1) { -+ pci_update_current_state(dev, dev->current_state); - return 0; /* already enabled */ -+ } - - bridge = pci_upstream_bridge(dev); - if (bridge) --- -2.33.0 - diff --git a/patches/5.13/0008-surface-typecover.patch b/patches/5.13/0008-surface-typecover.patch deleted file mode 100644 index 251719372..000000000 --- a/patches/5.13/0008-surface-typecover.patch +++ /dev/null @@ -1,233 +0,0 @@ -From 58bfc8f855dba9bcf3864e85430002bf744f76e6 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 5 Nov 2020 13:09:45 +0100 -Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when - suspending - -The Type Cover for Microsoft Surface devices supports a special usb -control request to disable or enable the built-in keyboard backlight. -On Windows, this request happens when putting the device into suspend or -resuming it, without it the backlight of the Type Cover will remain -enabled for some time even though the computer is suspended, which looks -weird to the user. - -So add support for this special usb control request to hid-multitouch, -which is the driver that's handling the Type Cover. - -The reason we have to use a pm_notifier for this instead of the usual -suspend/resume methods is that those won't get called in case the usb -device is already autosuspended. - -Also, if the device is autosuspended, we have to briefly autoresume it -in order to send the request. Doing that should be fine, the usb-core -driver does something similar during suspend inside choose_wakeup(). - -To make sure we don't send that request to every device but only to -devices which support it, add a new quirk -MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk -is only enabled for the usb id of the Surface Pro 2017 Type Cover, which -is where I confirmed that it's working. - -Patchset: surface-typecover ---- - drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- - 1 file changed, 98 insertions(+), 2 deletions(-) - -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index 2e4fb76c45f3..d7a27d891fba 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -34,7 +34,10 @@ - #include - #include - #include -+#include - #include -+#include -+#include - #include - #include - #include -@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); - MODULE_LICENSE("GPL"); - - #include "hid-ids.h" -+#include "usbhid/usbhid.h" - - /* quirks to control the device */ - #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) -@@ -71,12 +75,15 @@ MODULE_LICENSE("GPL"); - #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) - #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) - #define MT_QUIRK_DISABLE_WAKEUP BIT(21) -+#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 - - #define MT_BUTTONTYPE_CLICKPAD 0 - -+#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+ - enum latency_mode { - HID_LATENCY_NORMAL = 0, - HID_LATENCY_HIGH = 1, -@@ -168,6 +175,8 @@ struct mt_device { - - struct list_head applications; - struct list_head reports; -+ -+ struct notifier_block pm_notifier; - }; - - static void mt_post_parse_default_settings(struct mt_device *td, -@@ -210,6 +219,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); - #define MT_CLS_GOOGLE 0x0111 - #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 - #define MT_CLS_SMART_TECH 0x0113 -+#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 - - #define MT_DEFAULT_MAXCONTACT 10 - #define MT_MAX_MAXCONTACT 250 -@@ -378,6 +388,16 @@ static const struct mt_class mt_classes[] = { - MT_QUIRK_CONTACT_CNT_ACCURATE | - MT_QUIRK_SEPARATE_APP_REPORT, - }, -+ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_ALWAYS_VALID | -+ MT_QUIRK_IGNORE_DUPLICATES | -+ MT_QUIRK_HOVERING | -+ MT_QUIRK_CONTACT_CNT_ACCURATE | -+ MT_QUIRK_STICKY_FINGERS | -+ MT_QUIRK_WIN8_PTP_BUTTONS, -+ .export_all_inputs = true -+ }, - { } - }; - -@@ -1690,6 +1710,69 @@ static void mt_expired_timeout(struct timer_list *t) - clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - -+static void get_type_cover_backlight_field(struct hid_device *hdev, -+ struct hid_field **field) -+{ -+ struct hid_report_enum *rep_enum; -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid -+ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { -+ *field = cur_field; -+ return; -+ } -+ } -+ } -+ } -+} -+ -+static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) -+{ -+ struct usb_device *udev = hid_to_usb_dev(hdev); -+ struct hid_field *field = NULL; -+ -+ /* Wake up the device in case it's already suspended */ -+ pm_runtime_get_sync(&udev->dev); -+ -+ get_type_cover_backlight_field(hdev, &field); -+ if (!field) { -+ hid_err(hdev, "couldn't find backlight field\n"); -+ goto out; -+ } -+ -+ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; -+ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); -+ -+out: -+ pm_runtime_put_sync(&udev->dev); -+} -+ -+static int mt_pm_notifier(struct notifier_block *notifier, -+ unsigned long pm_event, -+ void *unused) -+{ -+ struct mt_device *td = -+ container_of(notifier, struct mt_device, pm_notifier); -+ struct hid_device *hdev = td->hdev; -+ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { -+ if (pm_event == PM_SUSPEND_PREPARE) -+ update_keyboard_backlight(hdev, 0); -+ else if (pm_event == PM_POST_SUSPEND) -+ update_keyboard_backlight(hdev, 1); -+ } -+ -+ return NOTIFY_DONE; -+} -+ - static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - { - int ret, i; -@@ -1713,6 +1796,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; - hid_set_drvdata(hdev, td); - -+ td->pm_notifier.notifier_call = mt_pm_notifier; -+ register_pm_notifier(&td->pm_notifier); -+ - INIT_LIST_HEAD(&td->applications); - INIT_LIST_HEAD(&td->reports); - -@@ -1742,15 +1828,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - timer_setup(&td->release_timer, mt_expired_timeout, 0); - - ret = hid_parse(hdev); -- if (ret != 0) -+ if (ret != 0) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) - mt_fix_const_fields(hdev, HID_DG_CONTACTID); - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); -- if (ret) -+ if (ret) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); - if (ret) -@@ -1801,6 +1891,7 @@ static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); - -+ unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); - - sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); -@@ -2158,6 +2249,11 @@ static const struct hid_device_id mt_devices[] = { - MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, - USB_DEVICE_ID_XIROKU_CSR2) }, - -+ /* Microsoft Surface type cover */ -+ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, -+ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, -+ - /* Google MT devices */ - { .driver_data = MT_CLS_GOOGLE, - HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, --- -2.33.0 - diff --git a/patches/5.13/0009-cameras.patch b/patches/5.13/0009-cameras.patch deleted file mode 100644 index fb9eab5fb..000000000 --- a/patches/5.13/0009-cameras.patch +++ /dev/null @@ -1,5713 +0,0 @@ -From 2a96c94a25ca756a6056d6c89e492cc2b671bbd4 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 3 Jun 2021 23:40:02 +0100 -Subject: [PATCH] ACPI: scan: Extend acpi_walk_dep_device_list() - -The acpi_walk_dep_device_list() function is not as generic as its -name implies, serving only to decrement the dependency count for each -dependent device of the input. - -Extend it to accept a callback which can be applied to all the -dependencies in acpi_dep_list. - -Replace all existing calls to the function with calls to a wrapper, -passing a callback that applies the same dependency reduction. - -Reviewed-by: Andy Shevchenko -Acked-by: Maximilian Luz # for platform/surface parts -Signed-off-by: Daniel Scally -[ rjw: Changelog edits ] -Signed-off-by: Rafael J. Wysocki -Patchset: cameras ---- - drivers/acpi/ec.c | 2 +- - drivers/acpi/pmic/intel_pmic_chtdc_ti.c | 2 +- - drivers/acpi/scan.c | 69 ++++++++++++++----- - drivers/gpio/gpiolib-acpi.c | 10 +-- - drivers/i2c/i2c-core-acpi.c | 8 +-- - drivers/platform/surface/aggregator/core.c | 6 +- - drivers/platform/surface/surface3_power.c | 22 +++--- - .../platform/surface/surface_acpi_notify.c | 7 +- - include/acpi/acpi_bus.h | 7 ++ - include/linux/acpi.h | 4 +- - 10 files changed, 90 insertions(+), 47 deletions(-) - -diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c -index 87c3b4a099b9..e629e891d1bb 100644 ---- a/drivers/acpi/ec.c -+++ b/drivers/acpi/ec.c -@@ -1629,7 +1629,7 @@ static int acpi_ec_add(struct acpi_device *device) - WARN(!ret, "Could not request EC cmd io port 0x%lx", ec->command_addr); - - /* Reprobe devices depending on the EC */ -- acpi_walk_dep_device_list(ec->handle); -+ acpi_dev_clear_dependencies(device); - - acpi_handle_debug(ec->handle, "enumerated.\n"); - return 0; -diff --git a/drivers/acpi/pmic/intel_pmic_chtdc_ti.c b/drivers/acpi/pmic/intel_pmic_chtdc_ti.c -index a5101b07611a..fef7831d0d63 100644 ---- a/drivers/acpi/pmic/intel_pmic_chtdc_ti.c -+++ b/drivers/acpi/pmic/intel_pmic_chtdc_ti.c -@@ -117,7 +117,7 @@ static int chtdc_ti_pmic_opregion_probe(struct platform_device *pdev) - return err; - - /* Re-enumerate devices depending on PMIC */ -- acpi_walk_dep_device_list(ACPI_HANDLE(pdev->dev.parent)); -+ acpi_dev_clear_dependencies(ACPI_COMPANION(pdev->dev.parent)); - return 0; - } - -diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c -index 438df8da6d12..607aeea9a210 100644 ---- a/drivers/acpi/scan.c -+++ b/drivers/acpi/scan.c -@@ -47,12 +47,6 @@ static DEFINE_MUTEX(acpi_hp_context_lock); - */ - static u64 spcr_uart_addr; - --struct acpi_dep_data { -- struct list_head node; -- acpi_handle supplier; -- acpi_handle consumer; --}; -- - void acpi_scan_lock_acquire(void) - { - mutex_lock(&acpi_scan_lock); -@@ -2107,30 +2101,69 @@ static void acpi_bus_attach(struct acpi_device *device, bool first_pass) - device->handler->hotplug.notify_online(device); - } - --void acpi_walk_dep_device_list(acpi_handle handle) -+static int acpi_scan_clear_dep(struct acpi_dep_data *dep, void *data) - { -- struct acpi_dep_data *dep, *tmp; - struct acpi_device *adev; - -+ acpi_bus_get_device(dep->consumer, &adev); -+ -+ if (adev) { -+ adev->dep_unmet--; -+ if (!adev->dep_unmet) -+ acpi_bus_attach(adev, true); -+ } -+ -+ list_del(&dep->node); -+ kfree(dep); -+ -+ return 0; -+} -+ -+/** -+ * acpi_walk_dep_device_list - Apply a callback to every entry in acpi_dep_list -+ * @handle: The ACPI handle of the supplier device -+ * @callback: Pointer to the callback function to apply -+ * @data: Pointer to some data to pass to the callback -+ * -+ * The return value of the callback determines this function's behaviour. If 0 -+ * is returned we continue to iterate over acpi_dep_list. If a positive value -+ * is returned then the loop is broken but this function returns 0. If a -+ * negative value is returned by the callback then the loop is broken and that -+ * value is returned as the final error. -+ */ -+int acpi_walk_dep_device_list(acpi_handle handle, -+ int (*callback)(struct acpi_dep_data *, void *), -+ void *data) -+{ -+ struct acpi_dep_data *dep, *tmp; -+ int ret; -+ - mutex_lock(&acpi_dep_list_lock); - list_for_each_entry_safe(dep, tmp, &acpi_dep_list, node) { - if (dep->supplier == handle) { -- acpi_bus_get_device(dep->consumer, &adev); -- -- if (adev) { -- adev->dep_unmet--; -- if (!adev->dep_unmet) -- acpi_bus_attach(adev, true); -- } -- -- list_del(&dep->node); -- kfree(dep); -+ ret = callback(dep, data); -+ if (ret) -+ break; - } - } - mutex_unlock(&acpi_dep_list_lock); -+ -+ return ret > 0 ? 0 : ret; - } - EXPORT_SYMBOL_GPL(acpi_walk_dep_device_list); - -+/** -+ * acpi_dev_clear_dependencies - Inform consumers that the device is now active -+ * @supplier: Pointer to the supplier &struct acpi_device -+ * -+ * Clear dependencies on the given device. -+ */ -+void acpi_dev_clear_dependencies(struct acpi_device *supplier) -+{ -+ acpi_walk_dep_device_list(supplier->handle, acpi_scan_clear_dep, NULL); -+} -+EXPORT_SYMBOL_GPL(acpi_dev_clear_dependencies); -+ - /** - * acpi_bus_scan - Add ACPI device node objects in a given namespace scope. - * @handle: Root of the namespace scope to scan. -diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c -index 3ef22a3c104d..5b4111e4be3f 100644 ---- a/drivers/gpio/gpiolib-acpi.c -+++ b/drivers/gpio/gpiolib-acpi.c -@@ -1233,14 +1233,14 @@ static void acpi_gpiochip_scan_gpios(struct acpi_gpio_chip *achip) - void acpi_gpiochip_add(struct gpio_chip *chip) - { - struct acpi_gpio_chip *acpi_gpio; -- acpi_handle handle; -+ struct acpi_device *adev; - acpi_status status; - - if (!chip || !chip->parent) - return; - -- handle = ACPI_HANDLE(chip->parent); -- if (!handle) -+ adev = ACPI_COMPANION(chip->parent); -+ if (!adev) - return; - - acpi_gpio = kzalloc(sizeof(*acpi_gpio), GFP_KERNEL); -@@ -1254,7 +1254,7 @@ void acpi_gpiochip_add(struct gpio_chip *chip) - INIT_LIST_HEAD(&acpi_gpio->events); - INIT_LIST_HEAD(&acpi_gpio->deferred_req_irqs_list_entry); - -- status = acpi_attach_data(handle, acpi_gpio_chip_dh, acpi_gpio); -+ status = acpi_attach_data(adev->handle, acpi_gpio_chip_dh, acpi_gpio); - if (ACPI_FAILURE(status)) { - dev_err(chip->parent, "Failed to attach ACPI GPIO chip\n"); - kfree(acpi_gpio); -@@ -1263,7 +1263,7 @@ void acpi_gpiochip_add(struct gpio_chip *chip) - - acpi_gpiochip_request_regions(acpi_gpio); - acpi_gpiochip_scan_gpios(acpi_gpio); -- acpi_walk_dep_device_list(handle); -+ acpi_dev_clear_dependencies(adev); - } - - void acpi_gpiochip_remove(struct gpio_chip *chip) -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index deceed0d76c6..13eb5ac82729 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -259,8 +259,8 @@ static acpi_status i2c_acpi_add_device(acpi_handle handle, u32 level, - */ - void i2c_acpi_register_devices(struct i2c_adapter *adap) - { -+ struct acpi_device *adev; - acpi_status status; -- acpi_handle handle; - - if (!has_acpi_companion(&adap->dev)) - return; -@@ -275,11 +275,11 @@ void i2c_acpi_register_devices(struct i2c_adapter *adap) - if (!adap->dev.parent) - return; - -- handle = ACPI_HANDLE(adap->dev.parent); -- if (!handle) -+ adev = ACPI_COMPANION(adap->dev.parent); -+ if (!adev) - return; - -- acpi_walk_dep_device_list(handle); -+ acpi_dev_clear_dependencies(adev); - } - - static const struct acpi_device_id i2c_acpi_force_400khz_device_ids[] = { -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -index 5d780e55f4a1..279d9df19c01 100644 ---- a/drivers/platform/surface/aggregator/core.c -+++ b/drivers/platform/surface/aggregator/core.c -@@ -621,8 +621,8 @@ static const struct acpi_gpio_mapping ssam_acpi_gpios[] = { - - static int ssam_serial_hub_probe(struct serdev_device *serdev) - { -+ struct acpi_device *ssh = ACPI_COMPANION(&serdev->dev); - struct ssam_controller *ctrl; -- acpi_handle *ssh = ACPI_HANDLE(&serdev->dev); - acpi_status astatus; - int status; - -@@ -652,7 +652,7 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) - if (status) - goto err_devopen; - -- astatus = ssam_serdev_setup_via_acpi(ssh, serdev); -+ astatus = ssam_serdev_setup_via_acpi(ssh->handle, serdev); - if (ACPI_FAILURE(astatus)) { - status = -ENXIO; - goto err_devinit; -@@ -706,7 +706,7 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) - * For now let's thus default power/wakeup to false. - */ - device_set_wakeup_capable(&serdev->dev, true); -- acpi_walk_dep_device_list(ssh); -+ acpi_dev_clear_dependencies(ssh); - - return 0; - -diff --git a/drivers/platform/surface/surface3_power.c b/drivers/platform/surface/surface3_power.c -index cc4f9cba6856..dea82aa1abd4 100644 ---- a/drivers/platform/surface/surface3_power.c -+++ b/drivers/platform/surface/surface3_power.c -@@ -446,12 +446,12 @@ mshw0011_space_handler(u32 function, acpi_physical_address command, - - static int mshw0011_install_space_handler(struct i2c_client *client) - { -- acpi_handle handle; -+ struct acpi_device *adev; - struct mshw0011_handler_data *data; - acpi_status status; - -- handle = ACPI_HANDLE(&client->dev); -- if (!handle) -+ adev = ACPI_COMPANION(&client->dev); -+ if (!adev) - return -ENODEV; - - data = kzalloc(sizeof(struct mshw0011_handler_data), -@@ -460,25 +460,25 @@ static int mshw0011_install_space_handler(struct i2c_client *client) - return -ENOMEM; - - data->client = client; -- status = acpi_bus_attach_private_data(handle, (void *)data); -+ status = acpi_bus_attach_private_data(adev->handle, (void *)data); - if (ACPI_FAILURE(status)) { - kfree(data); - return -ENOMEM; - } - -- status = acpi_install_address_space_handler(handle, -- ACPI_ADR_SPACE_GSBUS, -- &mshw0011_space_handler, -- NULL, -- data); -+ status = acpi_install_address_space_handler(adev->handle, -+ ACPI_ADR_SPACE_GSBUS, -+ &mshw0011_space_handler, -+ NULL, -+ data); - if (ACPI_FAILURE(status)) { - dev_err(&client->dev, "Error installing i2c space handler\n"); -- acpi_bus_detach_private_data(handle); -+ acpi_bus_detach_private_data(adev->handle); - kfree(data); - return -ENOMEM; - } - -- acpi_walk_dep_device_list(handle); -+ acpi_dev_clear_dependencies(adev); - return 0; - } - -diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c -index ef9c1f8e8336..8339988d95c1 100644 ---- a/drivers/platform/surface/surface_acpi_notify.c -+++ b/drivers/platform/surface/surface_acpi_notify.c -@@ -798,7 +798,7 @@ static int san_consumer_links_setup(struct platform_device *pdev) - - static int san_probe(struct platform_device *pdev) - { -- acpi_handle san = ACPI_HANDLE(&pdev->dev); -+ struct acpi_device *san = ACPI_COMPANION(&pdev->dev); - struct ssam_controller *ctrl; - struct san_data *data; - acpi_status astatus; -@@ -821,7 +821,8 @@ static int san_probe(struct platform_device *pdev) - - platform_set_drvdata(pdev, data); - -- astatus = acpi_install_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, -+ astatus = acpi_install_address_space_handler(san->handle, -+ ACPI_ADR_SPACE_GSBUS, - &san_opreg_handler, NULL, - &data->info); - if (ACPI_FAILURE(astatus)) -@@ -835,7 +836,7 @@ static int san_probe(struct platform_device *pdev) - if (status) - goto err_install_dev; - -- acpi_walk_dep_device_list(san); -+ acpi_dev_clear_dependencies(san); - return 0; - - err_install_dev: -diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h -index 394eb42d0ec1..b934c77b735f 100644 ---- a/include/acpi/acpi_bus.h -+++ b/include/acpi/acpi_bus.h -@@ -280,6 +280,12 @@ struct acpi_device_power { - struct acpi_device_power_state states[ACPI_D_STATE_COUNT]; /* Power states (D0-D3Cold) */ - }; - -+struct acpi_dep_data { -+ struct list_head node; -+ acpi_handle supplier; -+ acpi_handle consumer; -+}; -+ - /* Performance Management */ - - struct acpi_device_perf_flags { -@@ -685,6 +691,7 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) - - bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); - -+void acpi_dev_clear_dependencies(struct acpi_device *supplier); - struct acpi_device * - acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); - struct acpi_device * -diff --git a/include/linux/acpi.h b/include/linux/acpi.h -index c60745f657e9..170b9bebdb2b 100644 ---- a/include/linux/acpi.h -+++ b/include/linux/acpi.h -@@ -666,7 +666,9 @@ extern bool acpi_driver_match_device(struct device *dev, - const struct device_driver *drv); - int acpi_device_uevent_modalias(struct device *, struct kobj_uevent_env *); - int acpi_device_modalias(struct device *, char *, int); --void acpi_walk_dep_device_list(acpi_handle handle); -+int acpi_walk_dep_device_list(acpi_handle handle, -+ int (*callback)(struct acpi_dep_data *, void *), -+ void *data); - - struct platform_device *acpi_create_platform_device(struct acpi_device *, - struct property_entry *); --- -2.33.0 - -From 49022760499bae8d2971805281842cf844271b05 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 3 Jun 2021 23:40:03 +0100 -Subject: [PATCH] ACPI: scan: Add function to fetch dependent of ACPI device - -In some ACPI tables we encounter, devices use the _DEP method to assert -a dependence on other ACPI devices as opposed to the OpRegions that the -specification intends. - -We need to be able to find those devices "from" the dependee, so add -a callback and a wrapper to walk over the acpi_dep_list and return -the dependent ACPI device. - -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Signed-off-by: Rafael J. Wysocki -Patchset: cameras ---- - drivers/acpi/scan.c | 35 +++++++++++++++++++++++++++++++++++ - include/acpi/acpi_bus.h | 1 + - 2 files changed, 36 insertions(+) - -diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c -index 607aeea9a210..7f36295579ce 100644 ---- a/drivers/acpi/scan.c -+++ b/drivers/acpi/scan.c -@@ -2101,6 +2101,20 @@ static void acpi_bus_attach(struct acpi_device *device, bool first_pass) - device->handler->hotplug.notify_online(device); - } - -+static int acpi_dev_get_first_consumer_dev_cb(struct acpi_dep_data *dep, void *data) -+{ -+ struct acpi_device *adev; -+ -+ adev = acpi_bus_get_acpi_device(dep->consumer); -+ if (!adev) -+ /* If we don't find an adev then we want to continue parsing */ -+ return 0; -+ -+ *(struct acpi_device **)data = adev; -+ -+ return 1; -+} -+ - static int acpi_scan_clear_dep(struct acpi_dep_data *dep, void *data) - { - struct acpi_device *adev; -@@ -2164,6 +2178,27 @@ void acpi_dev_clear_dependencies(struct acpi_device *supplier) - } - EXPORT_SYMBOL_GPL(acpi_dev_clear_dependencies); - -+/** -+ * acpi_dev_get_first_consumer_dev - Return ACPI device dependent on @supplier -+ * @supplier: Pointer to the dependee device -+ * -+ * Returns the first &struct acpi_device which declares itself dependent on -+ * @supplier via the _DEP buffer, parsed from the acpi_dep_list. -+ * -+ * The caller is responsible for putting the reference to adev when it is no -+ * longer needed. -+ */ -+struct acpi_device *acpi_dev_get_first_consumer_dev(struct acpi_device *supplier) -+{ -+ struct acpi_device *adev = NULL; -+ -+ acpi_walk_dep_device_list(supplier->handle, -+ acpi_dev_get_first_consumer_dev_cb, &adev); -+ -+ return adev; -+} -+EXPORT_SYMBOL_GPL(acpi_dev_get_first_consumer_dev); -+ - /** - * acpi_bus_scan - Add ACPI device node objects in a given namespace scope. - * @handle: Root of the namespace scope to scan. -diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h -index b934c77b735f..db20595b24d1 100644 ---- a/include/acpi/acpi_bus.h -+++ b/include/acpi/acpi_bus.h -@@ -692,6 +692,7 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) - bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); - - void acpi_dev_clear_dependencies(struct acpi_device *supplier); -+struct acpi_device *acpi_dev_get_first_consumer_dev(struct acpi_device *supplier); - struct acpi_device * - acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); - struct acpi_device * --- -2.33.0 - -From b59579c09cf15ff8c78c50d3f4a457bb198d7906 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 3 Jun 2021 23:40:04 +0100 -Subject: [PATCH] gpiolib: acpi: Introduce acpi_get_and_request_gpiod() helper - -We need to be able to translate GPIO resources in an ACPI device's _CRS -into GPIO descriptor array. Those are represented in _CRS as a pathname -to a GPIO device plus the pin's index number: the acpi_get_gpiod() -function is perfect for that purpose. - -As it's currently only used internally within the GPIO layer, provide and -export a wrapper function that additionally holds a reference to the GPIO -device. - -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Signed-off-by: Andy Shevchenko -Patchset: cameras ---- - drivers/gpio/gpiolib-acpi.c | 28 ++++++++++++++++++++++++++++ - include/linux/gpio/consumer.h | 2 ++ - 2 files changed, 30 insertions(+) - -diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c -index 5b4111e4be3f..b5acb2c50836 100644 ---- a/drivers/gpio/gpiolib-acpi.c -+++ b/drivers/gpio/gpiolib-acpi.c -@@ -128,6 +128,34 @@ static struct gpio_desc *acpi_get_gpiod(char *path, int pin) - return gpiochip_get_desc(chip, pin); - } - -+/** -+ * acpi_get_and_request_gpiod - Translate ACPI GPIO pin to GPIO descriptor and -+ * hold a refcount to the GPIO device. -+ * @path: ACPI GPIO controller full path name, (e.g. "\\_SB.GPO1") -+ * @pin: ACPI GPIO pin number (0-based, controller-relative) -+ * @label: Label to pass to gpiod_request() -+ * -+ * This function is a simple pass-through to acpi_get_gpiod(), except that -+ * as it is intended for use outside of the GPIO layer (in a similar fashion to -+ * gpiod_get_index() for example) it also holds a reference to the GPIO device. -+ */ -+struct gpio_desc *acpi_get_and_request_gpiod(char *path, int pin, char *label) -+{ -+ struct gpio_desc *gpio; -+ int ret; -+ -+ gpio = acpi_get_gpiod(path, pin); -+ if (IS_ERR(gpio)) -+ return gpio; -+ -+ ret = gpiod_request(gpio, label); -+ if (ret) -+ return ERR_PTR(ret); -+ -+ return gpio; -+} -+EXPORT_SYMBOL_GPL(acpi_get_and_request_gpiod); -+ - static irqreturn_t acpi_gpio_irq_handler(int irq, void *data) - { - struct acpi_gpio_event *event = data; -diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h -index c73b25bc9213..566feb56601f 100644 ---- a/include/linux/gpio/consumer.h -+++ b/include/linux/gpio/consumer.h -@@ -692,6 +692,8 @@ int devm_acpi_dev_add_driver_gpios(struct device *dev, - const struct acpi_gpio_mapping *gpios); - void devm_acpi_dev_remove_driver_gpios(struct device *dev); - -+struct gpio_desc *acpi_get_and_request_gpiod(char *path, int pin, char *label); -+ - #else /* CONFIG_GPIOLIB && CONFIG_ACPI */ - - struct acpi_device; --- -2.33.0 - -From 6e23f9976c758315c57f0b8a52d002507d087d88 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 3 Jun 2021 23:40:05 +0100 -Subject: [PATCH] gpiolib: acpi: Add acpi_gpio_get_io_resource() - -Add a function to verify that a given ACPI resource represents a GpioIo() -type of resource, and return it if so. - -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Signed-off-by: Andy Shevchenko -Patchset: cameras ---- - drivers/gpio/gpiolib-acpi.c | 23 +++++++++++++++++++++++ - include/linux/acpi.h | 7 +++++++ - 2 files changed, 30 insertions(+) - -diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c -index b5acb2c50836..411525ac4cc4 100644 ---- a/drivers/gpio/gpiolib-acpi.c -+++ b/drivers/gpio/gpiolib-acpi.c -@@ -196,6 +196,29 @@ bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, - } - EXPORT_SYMBOL_GPL(acpi_gpio_get_irq_resource); - -+/** -+ * acpi_gpio_get_io_resource - Fetch details of an ACPI resource if it is a GPIO -+ * I/O resource or return False if not. -+ * @ares: Pointer to the ACPI resource to fetch -+ * @agpio: Pointer to a &struct acpi_resource_gpio to store the output pointer -+ */ -+bool acpi_gpio_get_io_resource(struct acpi_resource *ares, -+ struct acpi_resource_gpio **agpio) -+{ -+ struct acpi_resource_gpio *gpio; -+ -+ if (ares->type != ACPI_RESOURCE_TYPE_GPIO) -+ return false; -+ -+ gpio = &ares->data.gpio; -+ if (gpio->connection_type != ACPI_RESOURCE_GPIO_TYPE_IO) -+ return false; -+ -+ *agpio = gpio; -+ return true; -+} -+EXPORT_SYMBOL_GPL(acpi_gpio_get_io_resource); -+ - static void acpi_gpiochip_request_irq(struct acpi_gpio_chip *acpi_gpio, - struct acpi_gpio_event *event) - { -diff --git a/include/linux/acpi.h b/include/linux/acpi.h -index 170b9bebdb2b..e8ba7063c000 100644 ---- a/include/linux/acpi.h -+++ b/include/linux/acpi.h -@@ -1098,6 +1098,8 @@ void __acpi_handle_debug(struct _ddebug *descriptor, acpi_handle handle, const c - #if defined(CONFIG_ACPI) && defined(CONFIG_GPIOLIB) - bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, - struct acpi_resource_gpio **agpio); -+bool acpi_gpio_get_io_resource(struct acpi_resource *ares, -+ struct acpi_resource_gpio **agpio); - int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, const char *name, int index); - #else - static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, -@@ -1105,6 +1107,11 @@ static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, - { - return false; - } -+static inline bool acpi_gpio_get_io_resource(struct acpi_resource *ares, -+ struct acpi_resource_gpio **agpio) -+{ -+ return false; -+} - static inline int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, - const char *name, int index) - { --- -2.33.0 - -From e5553a8ae0cb56f7a447e369eced872c6cb9e4d2 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 3 Jun 2021 23:40:06 +0100 -Subject: [PATCH] platform/x86: Add intel_skl_int3472 driver - -ACPI devices with _HID INT3472 are currently matched to the tps68470 -driver, however this does not cover all situations in which that _HID -occurs. We've encountered three possibilities: - -1. On Chrome OS devices, an ACPI device with _HID INT3472 (representing -a physical TPS68470 device) that requires a GPIO and OpRegion driver -2. On devices designed for Windows, an ACPI device with _HID INT3472 -(again representing a physical TPS68470 device) which requires GPIO, -Clock and Regulator drivers. -3. On other devices designed for Windows, an ACPI device with _HID -INT3472 which does **not** represent a physical TPS68470, and is instead -used as a dummy device to group some system GPIO lines which are meant -to be consumed by the sensor that is dependent on this entry. - -This commit adds a new module, registering a platform driver to deal -with the 3rd scenario plus an i2c driver to deal with #1 and #2, by -querying the CLDB buffer found against INT3472 entries to determine -which is most appropriate. - -Suggested-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Link: https://lore.kernel.org/r/20210603224007.120560-6-djrscally@gmail.com -[hdegoede@redhat.com Make skl_int3472_tps68470_calc_type() static] -Signed-off-by: Hans de Goede -Patchset: cameras ---- - MAINTAINERS | 5 + - drivers/platform/x86/Kconfig | 2 + - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/intel-int3472/Kconfig | 30 ++ - drivers/platform/x86/intel-int3472/Makefile | 5 + - .../intel_skl_int3472_clk_and_regulator.c | 196 ++++++++ - .../intel-int3472/intel_skl_int3472_common.c | 106 +++++ - .../intel-int3472/intel_skl_int3472_common.h | 118 +++++ - .../intel_skl_int3472_discrete.c | 417 ++++++++++++++++++ - .../intel_skl_int3472_tps68470.c | 137 ++++++ - 10 files changed, 1017 insertions(+) - create mode 100644 drivers/platform/x86/intel-int3472/Kconfig - create mode 100644 drivers/platform/x86/intel-int3472/Makefile - create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c - create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c - create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h - create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c - create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 0cce91cd5624..1db7311d78a6 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -9386,6 +9386,11 @@ S: Maintained - F: arch/x86/include/asm/intel_scu_ipc.h - F: drivers/platform/x86/intel_scu_* - -+INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER -+M: Daniel Scally -+S: Maintained -+F: drivers/platform/x86/intel-int3472/ -+ - INTEL SPEED SELECT TECHNOLOGY - M: Srinivas Pandruvada - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 60592fb88e7a..88134aaacefc 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -697,6 +697,8 @@ config INTEL_CHT_INT33FE - device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m - for Type-C device. - -+source "drivers/platform/x86/intel-int3472/Kconfig" -+ - config INTEL_HID_EVENT - tristate "INTEL HID Event" - depends on ACPI -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index dcc8cdb95b4d..c0612c02d037 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -76,6 +76,7 @@ obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o - obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o - obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o - obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o -+obj-$(CONFIG_INTEL_SKL_INT3472) += intel-int3472/ - obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o - - # MSI -diff --git a/drivers/platform/x86/intel-int3472/Kconfig b/drivers/platform/x86/intel-int3472/Kconfig -new file mode 100644 -index 000000000000..c112878e833b ---- /dev/null -+++ b/drivers/platform/x86/intel-int3472/Kconfig -@@ -0,0 +1,30 @@ -+config INTEL_SKL_INT3472 -+ tristate "Intel SkyLake ACPI INT3472 Driver" -+ depends on ACPI -+ depends on COMMON_CLK && CLKDEV_LOOKUP -+ depends on I2C -+ depends on GPIOLIB -+ depends on REGULATOR -+ select MFD_CORE -+ select REGMAP_I2C -+ help -+ This driver adds power controller support for the Intel SkyCam -+ devices found on the Intel SkyLake platforms. -+ -+ The INT3472 is a camera power controller, a logical device found on -+ Intel Skylake-based systems that can map to different hardware -+ devices depending on the platform. On machines designed for Chrome OS -+ it maps to a TPS68470 camera PMIC. On machines designed for Windows, -+ it maps to either a TP68470 camera PMIC, a uP6641Q sensor PMIC, or a -+ set of discrete GPIOs and power gates. -+ -+ If your device was designed for Chrome OS, this driver will provide -+ an ACPI OpRegion, which must be available before any of the devices -+ using it are probed. For this reason, you should select Y if your -+ device was designed for ChromeOS. For the same reason the -+ I2C_DESIGNWARE_PLATFORM option must be set to Y too. -+ -+ Say Y or M here if you have a SkyLake device designed for use -+ with Windows or ChromeOS. Say N here if you are not sure. -+ -+ The module will be named "intel-skl-int3472". -diff --git a/drivers/platform/x86/intel-int3472/Makefile b/drivers/platform/x86/intel-int3472/Makefile -new file mode 100644 -index 000000000000..48bd97f0a04e ---- /dev/null -+++ b/drivers/platform/x86/intel-int3472/Makefile -@@ -0,0 +1,5 @@ -+obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o -+intel_skl_int3472-objs := intel_skl_int3472_common.o \ -+ intel_skl_int3472_discrete.o \ -+ intel_skl_int3472_tps68470.o \ -+ intel_skl_int3472_clk_and_regulator.o -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c -new file mode 100644 -index 000000000000..ceee860e2c07 ---- /dev/null -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c -@@ -0,0 +1,196 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "intel_skl_int3472_common.h" -+ -+/* -+ * The regulators have to have .ops to be valid, but the only ops we actually -+ * support are .enable and .disable which are handled via .ena_gpiod. Pass an -+ * empty struct to clear the check without lying about capabilities. -+ */ -+static const struct regulator_ops int3472_gpio_regulator_ops; -+ -+static int skl_int3472_clk_prepare(struct clk_hw *hw) -+{ -+ struct int3472_gpio_clock *clk = to_int3472_clk(hw); -+ -+ gpiod_set_value_cansleep(clk->ena_gpio, 1); -+ gpiod_set_value_cansleep(clk->led_gpio, 1); -+ -+ return 0; -+} -+ -+static void skl_int3472_clk_unprepare(struct clk_hw *hw) -+{ -+ struct int3472_gpio_clock *clk = to_int3472_clk(hw); -+ -+ gpiod_set_value_cansleep(clk->ena_gpio, 0); -+ gpiod_set_value_cansleep(clk->led_gpio, 0); -+} -+ -+static int skl_int3472_clk_enable(struct clk_hw *hw) -+{ -+ /* -+ * We're just turning a GPIO on to enable the clock, which operation -+ * has the potential to sleep. Given .enable() cannot sleep, but -+ * .prepare() can, we toggle the GPIO in .prepare() instead. Thus, -+ * nothing to do here. -+ */ -+ return 0; -+} -+ -+static void skl_int3472_clk_disable(struct clk_hw *hw) -+{ -+ /* Likewise, nothing to do here... */ -+} -+ -+static unsigned int skl_int3472_get_clk_frequency(struct int3472_discrete_device *int3472) -+{ -+ union acpi_object *obj; -+ unsigned int freq; -+ -+ obj = skl_int3472_get_acpi_buffer(int3472->sensor, "SSDB"); -+ if (IS_ERR(obj)) -+ return 0; /* report rate as 0 on error */ -+ -+ if (obj->buffer.length < CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET + sizeof(u32)) { -+ dev_err(int3472->dev, "The buffer is too small\n"); -+ kfree(obj); -+ return 0; -+ } -+ -+ freq = *(u32 *)(obj->buffer.pointer + CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET); -+ -+ kfree(obj); -+ return freq; -+} -+ -+static unsigned long skl_int3472_clk_recalc_rate(struct clk_hw *hw, -+ unsigned long parent_rate) -+{ -+ struct int3472_gpio_clock *clk = to_int3472_clk(hw); -+ -+ return clk->frequency; -+} -+ -+static const struct clk_ops skl_int3472_clock_ops = { -+ .prepare = skl_int3472_clk_prepare, -+ .unprepare = skl_int3472_clk_unprepare, -+ .enable = skl_int3472_clk_enable, -+ .disable = skl_int3472_clk_disable, -+ .recalc_rate = skl_int3472_clk_recalc_rate, -+}; -+ -+int skl_int3472_register_clock(struct int3472_discrete_device *int3472) -+{ -+ struct clk_init_data init = { -+ .ops = &skl_int3472_clock_ops, -+ .flags = CLK_GET_RATE_NOCACHE, -+ }; -+ int ret; -+ -+ init.name = kasprintf(GFP_KERNEL, "%s-clk", -+ acpi_dev_name(int3472->adev)); -+ if (!init.name) -+ return -ENOMEM; -+ -+ int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472); -+ -+ int3472->clock.clk_hw.init = &init; -+ int3472->clock.clk = clk_register(&int3472->adev->dev, -+ &int3472->clock.clk_hw); -+ if (IS_ERR(int3472->clock.clk)) { -+ ret = PTR_ERR(int3472->clock.clk); -+ goto out_free_init_name; -+ } -+ -+ int3472->clock.cl = clkdev_create(int3472->clock.clk, NULL, -+ int3472->sensor_name); -+ if (!int3472->clock.cl) { -+ ret = -ENOMEM; -+ goto err_unregister_clk; -+ } -+ -+ kfree(init.name); -+ return 0; -+ -+err_unregister_clk: -+ clk_unregister(int3472->clock.clk); -+out_free_init_name: -+ kfree(init.name); -+ -+ return ret; -+} -+ -+int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, -+ struct acpi_resource *ares) -+{ -+ char *path = ares->data.gpio.resource_source.string_ptr; -+ const struct int3472_sensor_config *sensor_config; -+ struct regulator_consumer_supply supply_map; -+ struct regulator_init_data init_data = { }; -+ struct regulator_config cfg = { }; -+ int ret; -+ -+ sensor_config = int3472->sensor_config; -+ if (IS_ERR(sensor_config)) { -+ dev_err(int3472->dev, "No sensor module config\n"); -+ return PTR_ERR(sensor_config); -+ } -+ -+ if (!sensor_config->supply_map.supply) { -+ dev_err(int3472->dev, "No supply name defined\n"); -+ return -ENODEV; -+ } -+ -+ init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; -+ init_data.num_consumer_supplies = 1; -+ supply_map = sensor_config->supply_map; -+ supply_map.dev_name = int3472->sensor_name; -+ init_data.consumer_supplies = &supply_map; -+ -+ snprintf(int3472->regulator.regulator_name, -+ sizeof(int3472->regulator.regulator_name), "%s-regulator", -+ acpi_dev_name(int3472->adev)); -+ snprintf(int3472->regulator.supply_name, -+ GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0"); -+ -+ int3472->regulator.rdesc = INT3472_REGULATOR( -+ int3472->regulator.regulator_name, -+ int3472->regulator.supply_name, -+ &int3472_gpio_regulator_ops); -+ -+ int3472->regulator.gpio = acpi_get_and_request_gpiod(path, -+ ares->data.gpio.pin_table[0], -+ "int3472,regulator"); -+ if (IS_ERR(int3472->regulator.gpio)) { -+ dev_err(int3472->dev, "Failed to get regulator GPIO line\n"); -+ return PTR_ERR(int3472->regulator.gpio); -+ } -+ -+ cfg.dev = &int3472->adev->dev; -+ cfg.init_data = &init_data; -+ cfg.ena_gpiod = int3472->regulator.gpio; -+ -+ int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc, -+ &cfg); -+ if (IS_ERR(int3472->regulator.rdev)) { -+ ret = PTR_ERR(int3472->regulator.rdev); -+ goto err_free_gpio; -+ } -+ -+ return 0; -+ -+err_free_gpio: -+ gpiod_put(int3472->regulator.gpio); -+ -+ return ret; -+} -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c -new file mode 100644 -index 000000000000..497e74fba75f ---- /dev/null -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c -@@ -0,0 +1,106 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+#include -+ -+#include "intel_skl_int3472_common.h" -+ -+union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id) -+{ -+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; -+ acpi_handle handle = adev->handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(handle, id, NULL, &buffer); -+ if (ACPI_FAILURE(status)) -+ return ERR_PTR(-ENODEV); -+ -+ obj = buffer.pointer; -+ if (!obj) -+ return ERR_PTR(-ENODEV); -+ -+ if (obj->type != ACPI_TYPE_BUFFER) { -+ acpi_handle_err(handle, "%s object is not an ACPI buffer\n", id); -+ kfree(obj); -+ return ERR_PTR(-EINVAL); -+ } -+ -+ return obj; -+} -+ -+int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) -+{ -+ union acpi_object *obj; -+ int ret; -+ -+ obj = skl_int3472_get_acpi_buffer(adev, "CLDB"); -+ if (IS_ERR(obj)) -+ return PTR_ERR(obj); -+ -+ if (obj->buffer.length > sizeof(*cldb)) { -+ acpi_handle_err(adev->handle, "The CLDB buffer is too large\n"); -+ ret = -EINVAL; -+ goto out_free_obj; -+ } -+ -+ memcpy(cldb, obj->buffer.pointer, obj->buffer.length); -+ ret = 0; -+ -+out_free_obj: -+ kfree(obj); -+ return ret; -+} -+ -+static const struct acpi_device_id int3472_device_id[] = { -+ { "INT3472", 0 }, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, int3472_device_id); -+ -+static struct platform_driver int3472_discrete = { -+ .driver = { -+ .name = "int3472-discrete", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe = skl_int3472_discrete_probe, -+ .remove = skl_int3472_discrete_remove, -+}; -+ -+static struct i2c_driver int3472_tps68470 = { -+ .driver = { -+ .name = "int3472-tps68470", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe_new = skl_int3472_tps68470_probe, -+}; -+ -+static int skl_int3472_init(void) -+{ -+ int ret; -+ -+ ret = platform_driver_register(&int3472_discrete); -+ if (ret) -+ return ret; -+ -+ ret = i2c_register_driver(THIS_MODULE, &int3472_tps68470); -+ if (ret) -+ platform_driver_unregister(&int3472_discrete); -+ -+ return ret; -+} -+module_init(skl_int3472_init); -+ -+static void skl_int3472_exit(void) -+{ -+ platform_driver_unregister(&int3472_discrete); -+ i2c_del_driver(&int3472_tps68470); -+} -+module_exit(skl_int3472_exit); -+ -+MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver"); -+MODULE_AUTHOR("Daniel Scally "); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h -new file mode 100644 -index 000000000000..6fdf78584219 ---- /dev/null -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h -@@ -0,0 +1,118 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* Author: Dan Scally */ -+ -+#ifndef _INTEL_SKL_INT3472_H -+#define _INTEL_SKL_INT3472_H -+ -+#include -+#include -+#include -+#include -+#include -+ -+/* FIXME drop this once the I2C_DEV_NAME_FORMAT macro has been added to include/linux/i2c.h */ -+#ifndef I2C_DEV_NAME_FORMAT -+#define I2C_DEV_NAME_FORMAT "i2c-%s" -+#endif -+ -+/* PMIC GPIO Types */ -+#define INT3472_GPIO_TYPE_RESET 0x00 -+#define INT3472_GPIO_TYPE_POWERDOWN 0x01 -+#define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b -+#define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c -+#define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d -+ -+#define INT3472_PDEV_MAX_NAME_LEN 23 -+#define INT3472_MAX_SENSOR_GPIOS 3 -+ -+#define GPIO_REGULATOR_NAME_LENGTH 21 -+#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9 -+ -+#define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86 -+ -+#define INT3472_REGULATOR(_name, _supply, _ops) \ -+ (const struct regulator_desc) { \ -+ .name = _name, \ -+ .supply_name = _supply, \ -+ .type = REGULATOR_VOLTAGE, \ -+ .ops = _ops, \ -+ .owner = THIS_MODULE, \ -+ } -+ -+#define to_int3472_clk(hw) \ -+ container_of(hw, struct int3472_gpio_clock, clk_hw) -+ -+#define to_int3472_device(clk) \ -+ container_of(clk, struct int3472_discrete_device, clock) -+ -+struct acpi_device; -+struct i2c_client; -+struct platform_device; -+ -+struct int3472_cldb { -+ u8 version; -+ /* -+ * control logic type -+ * 0: UNKNOWN -+ * 1: DISCRETE(CRD-D) -+ * 2: PMIC TPS68470 -+ * 3: PMIC uP6641 -+ */ -+ u8 control_logic_type; -+ u8 control_logic_id; -+ u8 sensor_card_sku; -+ u8 reserved[28]; -+}; -+ -+struct int3472_gpio_function_remap { -+ const char *documented; -+ const char *actual; -+}; -+ -+struct int3472_sensor_config { -+ const char *sensor_module_name; -+ struct regulator_consumer_supply supply_map; -+ const struct int3472_gpio_function_remap *function_maps; -+}; -+ -+struct int3472_discrete_device { -+ struct acpi_device *adev; -+ struct device *dev; -+ struct acpi_device *sensor; -+ const char *sensor_name; -+ -+ const struct int3472_sensor_config *sensor_config; -+ -+ struct int3472_gpio_regulator { -+ char regulator_name[GPIO_REGULATOR_NAME_LENGTH]; -+ char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH]; -+ struct gpio_desc *gpio; -+ struct regulator_dev *rdev; -+ struct regulator_desc rdesc; -+ } regulator; -+ -+ struct int3472_gpio_clock { -+ struct clk *clk; -+ struct clk_hw clk_hw; -+ struct clk_lookup *cl; -+ struct gpio_desc *ena_gpio; -+ struct gpio_desc *led_gpio; -+ u32 frequency; -+ } clock; -+ -+ unsigned int ngpios; /* how many GPIOs have we seen */ -+ unsigned int n_sensor_gpios; /* how many have we mapped to sensor */ -+ struct gpiod_lookup_table gpios; -+}; -+ -+int skl_int3472_discrete_probe(struct platform_device *pdev); -+int skl_int3472_discrete_remove(struct platform_device *pdev); -+int skl_int3472_tps68470_probe(struct i2c_client *client); -+union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, -+ char *id); -+int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); -+int skl_int3472_register_clock(struct int3472_discrete_device *int3472); -+int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, -+ struct acpi_resource *ares); -+ -+#endif -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -new file mode 100644 -index 000000000000..8c18dbff1c43 ---- /dev/null -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -@@ -0,0 +1,417 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "intel_skl_int3472_common.h" -+ -+/* -+ * 79234640-9e10-4fea-a5c1-b5aa8b19756f -+ * This _DSM GUID returns information about the GPIO lines mapped to a -+ * discrete INT3472 device. Function number 1 returns a count of the GPIO -+ * lines that are mapped. Subsequent functions return 32 bit ints encoding -+ * information about the GPIO line, including its purpose. -+ */ -+static const guid_t int3472_gpio_guid = -+ GUID_INIT(0x79234640, 0x9e10, 0x4fea, -+ 0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f); -+ -+/* -+ * 822ace8f-2814-4174-a56b-5f029fe079ee -+ * This _DSM GUID returns a string from the sensor device, which acts as a -+ * module identifier. -+ */ -+static const guid_t cio2_sensor_module_guid = -+ GUID_INIT(0x822ace8f, 0x2814, 0x4174, -+ 0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee); -+ -+/* -+ * Here follows platform specific mapping information that we can pass to -+ * the functions mapping resources to the sensors. Where the sensors have -+ * a power enable pin defined in DSDT we need to provide a supply name so -+ * the sensor drivers can find the regulator. The device name will be derived -+ * from the sensor's ACPI device within the code. Optionally, we can provide a -+ * NULL terminated array of function name mappings to deal with any platform -+ * specific deviations from the documented behaviour of GPIOs. -+ * -+ * Map a GPIO function name to NULL to prevent the driver from mapping that -+ * GPIO at all. -+ */ -+ -+static const struct int3472_gpio_function_remap ov2680_gpio_function_remaps[] = { -+ { "reset", NULL }, -+ { "powerdown", "reset" }, -+ { } -+}; -+ -+static const struct int3472_sensor_config int3472_sensor_configs[] = { -+ /* Lenovo Miix 510-12ISK - OV2680, Front */ -+ { "GNDF140809R", { 0 }, ov2680_gpio_function_remaps }, -+ /* Lenovo Miix 510-12ISK - OV5648, Rear */ -+ { "GEFF150023R", REGULATOR_SUPPLY("avdd", NULL), NULL }, -+ /* Surface Go 1&2 - OV5693, Front */ -+ { "YHCU", REGULATOR_SUPPLY("avdd", NULL), NULL }, -+}; -+ -+static const struct int3472_sensor_config * -+skl_int3472_get_sensor_module_config(struct int3472_discrete_device *int3472) -+{ -+ union acpi_object *obj; -+ unsigned int i; -+ -+ obj = acpi_evaluate_dsm_typed(int3472->sensor->handle, -+ &cio2_sensor_module_guid, 0x00, -+ 0x01, NULL, ACPI_TYPE_STRING); -+ -+ if (!obj) { -+ dev_err(int3472->dev, -+ "Failed to get sensor module string from _DSM\n"); -+ return ERR_PTR(-ENODEV); -+ } -+ -+ if (obj->string.type != ACPI_TYPE_STRING) { -+ dev_err(int3472->dev, -+ "Sensor _DSM returned a non-string value\n"); -+ -+ ACPI_FREE(obj); -+ return ERR_PTR(-EINVAL); -+ } -+ -+ for (i = 0; i < ARRAY_SIZE(int3472_sensor_configs); i++) { -+ if (!strcmp(int3472_sensor_configs[i].sensor_module_name, -+ obj->string.pointer)) -+ break; -+ } -+ -+ ACPI_FREE(obj); -+ -+ if (i >= ARRAY_SIZE(int3472_sensor_configs)) -+ return ERR_PTR(-EINVAL); -+ -+ return &int3472_sensor_configs[i]; -+} -+ -+static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int3472, -+ struct acpi_resource *ares, -+ const char *func, u32 polarity) -+{ -+ char *path = ares->data.gpio.resource_source.string_ptr; -+ const struct int3472_sensor_config *sensor_config; -+ struct gpiod_lookup *table_entry; -+ struct acpi_device *adev; -+ acpi_handle handle; -+ acpi_status status; -+ int ret; -+ -+ if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { -+ dev_warn(int3472->dev, "Too many GPIOs mapped\n"); -+ return -EINVAL; -+ } -+ -+ sensor_config = int3472->sensor_config; -+ if (!IS_ERR(sensor_config) && sensor_config->function_maps) { -+ const struct int3472_gpio_function_remap *remap; -+ -+ for (remap = sensor_config->function_maps; remap->documented; remap++) { -+ if (!strcmp(func, remap->documented)) { -+ func = remap->actual; -+ break; -+ } -+ } -+ } -+ -+ /* Functions mapped to NULL should not be mapped to the sensor */ -+ if (!func) -+ return 0; -+ -+ status = acpi_get_handle(NULL, path, &handle); -+ if (ACPI_FAILURE(status)) -+ return -EINVAL; -+ -+ ret = acpi_bus_get_device(handle, &adev); -+ if (ret) -+ return -ENODEV; -+ -+ table_entry = &int3472->gpios.table[int3472->n_sensor_gpios]; -+ table_entry->key = acpi_dev_name(adev); -+ table_entry->chip_hwnum = ares->data.gpio.pin_table[0]; -+ table_entry->con_id = func; -+ table_entry->idx = 0; -+ table_entry->flags = polarity; -+ -+ int3472->n_sensor_gpios++; -+ -+ return 0; -+} -+ -+static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472, -+ struct acpi_resource *ares, u8 type) -+{ -+ char *path = ares->data.gpio.resource_source.string_ptr; -+ struct gpio_desc *gpio; -+ -+ switch (type) { -+ case INT3472_GPIO_TYPE_CLK_ENABLE: -+ gpio = acpi_get_and_request_gpiod(path, ares->data.gpio.pin_table[0], -+ "int3472,clk-enable"); -+ if (IS_ERR(gpio)) -+ return (PTR_ERR(gpio)); -+ -+ int3472->clock.ena_gpio = gpio; -+ break; -+ case INT3472_GPIO_TYPE_PRIVACY_LED: -+ gpio = acpi_get_and_request_gpiod(path, ares->data.gpio.pin_table[0], -+ "int3472,privacy-led"); -+ if (IS_ERR(gpio)) -+ return (PTR_ERR(gpio)); -+ -+ int3472->clock.led_gpio = gpio; -+ break; -+ default: -+ dev_err(int3472->dev, "Invalid GPIO type 0x%02x for clock\n", type); -+ break; -+ } -+ -+ return 0; -+} -+ -+/** -+ * skl_int3472_handle_gpio_resources: Map PMIC resources to consuming sensor -+ * @ares: A pointer to a &struct acpi_resource -+ * @data: A pointer to a &struct int3472_discrete_device -+ * -+ * This function handles GPIO resources that are against an INT3472 -+ * ACPI device, by checking the value of the corresponding _DSM entry. -+ * This will return a 32bit int, where the lowest byte represents the -+ * function of the GPIO pin: -+ * -+ * 0x00 Reset -+ * 0x01 Power down -+ * 0x0b Power enable -+ * 0x0c Clock enable -+ * 0x0d Privacy LED -+ * -+ * There are some known platform specific quirks where that does not quite -+ * hold up; for example where a pin with type 0x01 (Power down) is mapped to -+ * a sensor pin that performs a reset function or entries in _CRS and _DSM that -+ * do not actually correspond to a physical connection. These will be handled -+ * by the mapping sub-functions. -+ * -+ * GPIOs will either be mapped directly to the sensor device or else used -+ * to create clocks and regulators via the usual frameworks. -+ * -+ * Return: -+ * * 1 - To continue the loop -+ * * 0 - When all resources found are handled properly. -+ * * -EINVAL - If the resource is not a GPIO IO resource -+ * * -ENODEV - If the resource has no corresponding _DSM entry -+ * * -Other - Errors propagated from one of the sub-functions. -+ */ -+static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, -+ void *data) -+{ -+ struct int3472_discrete_device *int3472 = data; -+ struct acpi_resource_gpio *agpio; -+ union acpi_object *obj; -+ const char *err_msg; -+ int ret; -+ u8 type; -+ -+ if (!acpi_gpio_get_io_resource(ares, &agpio)) -+ return 1; -+ -+ /* -+ * ngpios + 2 because the index of this _DSM function is 1-based and -+ * the first function is just a count. -+ */ -+ obj = acpi_evaluate_dsm_typed(int3472->adev->handle, -+ &int3472_gpio_guid, 0x00, -+ int3472->ngpios + 2, -+ NULL, ACPI_TYPE_INTEGER); -+ -+ if (!obj) { -+ dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n", -+ ares->data.gpio.pin_table[0]); -+ return 1; -+ } -+ -+ type = obj->integer.value & 0xff; -+ -+ switch (type) { -+ case INT3472_GPIO_TYPE_RESET: -+ ret = skl_int3472_map_gpio_to_sensor(int3472, ares, "reset", -+ GPIO_ACTIVE_LOW); -+ if (ret) -+ err_msg = "Failed to map reset pin to sensor\n"; -+ -+ break; -+ case INT3472_GPIO_TYPE_POWERDOWN: -+ ret = skl_int3472_map_gpio_to_sensor(int3472, ares, -+ "powerdown", -+ GPIO_ACTIVE_LOW); -+ if (ret) -+ err_msg = "Failed to map powerdown pin to sensor\n"; -+ -+ break; -+ case INT3472_GPIO_TYPE_CLK_ENABLE: -+ case INT3472_GPIO_TYPE_PRIVACY_LED: -+ ret = skl_int3472_map_gpio_to_clk(int3472, ares, type); -+ if (ret) -+ err_msg = "Failed to map GPIO to clock\n"; -+ -+ break; -+ case INT3472_GPIO_TYPE_POWER_ENABLE: -+ ret = skl_int3472_register_regulator(int3472, ares); -+ if (ret) -+ err_msg = "Failed to map regulator to sensor\n"; -+ -+ break; -+ default: -+ dev_warn(int3472->dev, -+ "GPIO type 0x%02x unknown; the sensor may not work\n", -+ type); -+ ret = 1; -+ break; -+ } -+ -+ int3472->ngpios++; -+ ACPI_FREE(obj); -+ -+ if (ret) -+ return dev_err_probe(int3472->dev, ret, err_msg); -+ -+ return 0; -+} -+ -+static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) -+{ -+ LIST_HEAD(resource_list); -+ int ret; -+ -+ /* -+ * No error check, because not having a sensor config is not necessarily -+ * a failure mode. -+ */ -+ int3472->sensor_config = skl_int3472_get_sensor_module_config(int3472); -+ -+ ret = acpi_dev_get_resources(int3472->adev, &resource_list, -+ skl_int3472_handle_gpio_resources, -+ int3472); -+ if (ret) -+ goto out_free_res_list; -+ -+ /* -+ * If we find no clock enable GPIO pin then the privacy LED won't work. -+ * We've never seen that situation, but it's possible. Warn the user so -+ * it's clear what's happened. -+ */ -+ if (int3472->clock.ena_gpio) { -+ ret = skl_int3472_register_clock(int3472); -+ if (ret) -+ goto out_free_res_list; -+ } else { -+ if (int3472->clock.led_gpio) -+ dev_warn(int3472->dev, -+ "No clk GPIO. The privacy LED won't work\n"); -+ } -+ -+ int3472->gpios.dev_id = int3472->sensor_name; -+ gpiod_add_lookup_table(&int3472->gpios); -+ -+out_free_res_list: -+ acpi_dev_free_resource_list(&resource_list); -+ -+ return ret; -+} -+ -+int skl_int3472_discrete_probe(struct platform_device *pdev) -+{ -+ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); -+ struct int3472_discrete_device *int3472; -+ struct int3472_cldb cldb; -+ int ret; -+ -+ ret = skl_int3472_fill_cldb(adev, &cldb); -+ if (ret) { -+ dev_err(&pdev->dev, "Couldn't fill CLDB structure\n"); -+ return ret; -+ } -+ -+ if (cldb.control_logic_type != 1) { -+ dev_err(&pdev->dev, "Unsupported control logic type %u\n", -+ cldb.control_logic_type); -+ return -EINVAL; -+ } -+ -+ /* Max num GPIOs we've seen plus a terminator */ -+ int3472 = devm_kzalloc(&pdev->dev, struct_size(int3472, gpios.table, -+ INT3472_MAX_SENSOR_GPIOS + 1), GFP_KERNEL); -+ if (!int3472) -+ return -ENOMEM; -+ -+ int3472->adev = adev; -+ int3472->dev = &pdev->dev; -+ platform_set_drvdata(pdev, int3472); -+ -+ int3472->sensor = acpi_dev_get_first_consumer_dev(adev); -+ if (!int3472->sensor) { -+ dev_err(&pdev->dev, "INT3472 seems to have no dependents.\n"); -+ return -ENODEV; -+ } -+ -+ int3472->sensor_name = devm_kasprintf(int3472->dev, GFP_KERNEL, -+ I2C_DEV_NAME_FORMAT, -+ acpi_dev_name(int3472->sensor)); -+ if (!int3472->sensor_name) { -+ ret = -ENOMEM; -+ goto err_put_sensor; -+ } -+ -+ /* -+ * Initialising this list means we can call gpiod_remove_lookup_table() -+ * in failure paths without issue. -+ */ -+ INIT_LIST_HEAD(&int3472->gpios.list); -+ -+ ret = skl_int3472_parse_crs(int3472); -+ if (ret) { -+ skl_int3472_discrete_remove(pdev); -+ return ret; -+ } -+ -+ return 0; -+ -+err_put_sensor: -+ acpi_dev_put(int3472->sensor); -+ -+ return ret; -+} -+ -+int skl_int3472_discrete_remove(struct platform_device *pdev) -+{ -+ struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); -+ -+ gpiod_remove_lookup_table(&int3472->gpios); -+ regulator_unregister(int3472->regulator.rdev); -+ clk_unregister(int3472->clock.clk); -+ -+ if (int3472->clock.cl) -+ clkdev_drop(int3472->clock.cl); -+ -+ gpiod_put(int3472->regulator.gpio); -+ gpiod_put(int3472->clock.ena_gpio); -+ gpiod_put(int3472->clock.led_gpio); -+ -+ return 0; -+} -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c -new file mode 100644 -index 000000000000..c05b4cf502fe ---- /dev/null -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c -@@ -0,0 +1,137 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "intel_skl_int3472_common.h" -+ -+#define DESIGNED_FOR_CHROMEOS 1 -+#define DESIGNED_FOR_WINDOWS 2 -+ -+static const struct mfd_cell tps68470_cros[] = { -+ { .name = "tps68470-gpio" }, -+ { .name = "tps68470_pmic_opregion" }, -+}; -+ -+static const struct mfd_cell tps68470_win[] = { -+ { .name = "tps68470-gpio" }, -+ { .name = "tps68470-clk" }, -+ { .name = "tps68470-regulator" }, -+}; -+ -+static const struct regmap_config tps68470_regmap_config = { -+ .reg_bits = 8, -+ .val_bits = 8, -+ .max_register = TPS68470_REG_MAX, -+}; -+ -+static int tps68470_chip_init(struct device *dev, struct regmap *regmap) -+{ -+ unsigned int version; -+ int ret; -+ -+ /* Force software reset */ -+ ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); -+ if (ret) -+ return ret; -+ -+ ret = regmap_read(regmap, TPS68470_REG_REVID, &version); -+ if (ret) { -+ dev_err(dev, "Failed to read revision register: %d\n", ret); -+ return ret; -+ } -+ -+ dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); -+ -+ return 0; -+} -+ -+/** skl_int3472_tps68470_calc_type: Check what platform a device is designed for -+ * @adev: A pointer to a &struct acpi_device -+ * -+ * Check CLDB buffer against the PMIC's adev. If present, then we check -+ * the value of control_logic_type field and follow one of the -+ * following scenarios: -+ * -+ * 1. No CLDB - likely ACPI tables designed for ChromeOS. We -+ * create platform devices for the GPIOs and OpRegion drivers. -+ * -+ * 2. CLDB, with control_logic_type = 2 - probably ACPI tables -+ * made for Windows 2-in-1 platforms. Register pdevs for GPIO, -+ * Clock and Regulator drivers to bind to. -+ * -+ * 3. Any other value in control_logic_type, we should never have -+ * gotten to this point; fail probe and return. -+ * -+ * Return: -+ * * 1 Device intended for ChromeOS -+ * * 2 Device intended for Windows -+ * * -EINVAL Where @adev has an object named CLDB but it does not conform to -+ * our expectations -+ */ -+static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) -+{ -+ struct int3472_cldb cldb = { 0 }; -+ int ret; -+ -+ /* -+ * A CLDB buffer that exists, but which does not match our expectations -+ * should trigger an error so we don't blindly continue. -+ */ -+ ret = skl_int3472_fill_cldb(adev, &cldb); -+ if (ret && ret != -ENODEV) -+ return ret; -+ -+ if (ret) -+ return DESIGNED_FOR_CHROMEOS; -+ -+ if (cldb.control_logic_type != 2) -+ return -EINVAL; -+ -+ return DESIGNED_FOR_WINDOWS; -+} -+ -+int skl_int3472_tps68470_probe(struct i2c_client *client) -+{ -+ struct acpi_device *adev = ACPI_COMPANION(&client->dev); -+ struct regmap *regmap; -+ int device_type; -+ int ret; -+ -+ regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); -+ if (IS_ERR(regmap)) { -+ dev_err(&client->dev, "Failed to create regmap: %ld\n", PTR_ERR(regmap)); -+ return PTR_ERR(regmap); -+ } -+ -+ i2c_set_clientdata(client, regmap); -+ -+ ret = tps68470_chip_init(&client->dev, regmap); -+ if (ret < 0) { -+ dev_err(&client->dev, "TPS68470 init error %d\n", ret); -+ return ret; -+ } -+ -+ device_type = skl_int3472_tps68470_calc_type(adev); -+ switch (device_type) { -+ case DESIGNED_FOR_WINDOWS: -+ ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, -+ tps68470_win, ARRAY_SIZE(tps68470_win), -+ NULL, 0, NULL); -+ break; -+ case DESIGNED_FOR_CHROMEOS: -+ ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, -+ tps68470_cros, ARRAY_SIZE(tps68470_cros), -+ NULL, 0, NULL); -+ break; -+ default: -+ dev_err(&client->dev, "Failed to add MFD devices\n"); -+ return device_type; -+ } -+ -+ return ret; -+} --- -2.33.0 - -From 06899d0ea5e8fa1e6d3ab17c3b59e9d9bdea4980 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 3 Jun 2021 23:40:07 +0100 -Subject: [PATCH] mfd: tps68470: Remove tps68470 MFD driver - -This driver only covered one scenario in which ACPI devices with _HID -INT3472 are found, and its functionality has been taken over by the -intel-skl-int3472 module, so remove it. - -Acked-by: Andy Shevchenko -Acked-by: Lee Jones -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Link: https://lore.kernel.org/r/20210603224007.120560-7-djrscally@gmail.com -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/acpi/pmic/Kconfig | 2 +- - drivers/gpio/Kconfig | 2 +- - drivers/mfd/Kconfig | 18 -------- - drivers/mfd/Makefile | 1 - - drivers/mfd/tps68470.c | 97 --------------------------------------- - 5 files changed, 2 insertions(+), 118 deletions(-) - delete mode 100644 drivers/mfd/tps68470.c - -diff --git a/drivers/acpi/pmic/Kconfig b/drivers/acpi/pmic/Kconfig -index 56bbcb2ce61b..f84b8f6038dc 100644 ---- a/drivers/acpi/pmic/Kconfig -+++ b/drivers/acpi/pmic/Kconfig -@@ -52,7 +52,7 @@ endif # PMIC_OPREGION - - config TPS68470_PMIC_OPREGION - bool "ACPI operation region support for TPS68470 PMIC" -- depends on MFD_TPS68470 -+ depends on INTEL_SKL_INT3472 - help - This config adds ACPI operation region support for TI TPS68470 PMIC. - TPS68470 device is an advanced power management unit that powers -diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig -index 3c69b785cb79..63b84ba161dc 100644 ---- a/drivers/gpio/Kconfig -+++ b/drivers/gpio/Kconfig -@@ -1367,7 +1367,7 @@ config GPIO_TPS65912 - - config GPIO_TPS68470 - bool "TPS68470 GPIO" -- depends on MFD_TPS68470 -+ depends on INTEL_SKL_INT3472 - help - Select this option to enable GPIO driver for the TPS68470 - chip family. -diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig -index 5c408c1dc58c..ace0d1b8f55c 100644 ---- a/drivers/mfd/Kconfig -+++ b/drivers/mfd/Kconfig -@@ -1500,24 +1500,6 @@ config MFD_TPS65217 - This driver can also be built as a module. If so, the module - will be called tps65217. - --config MFD_TPS68470 -- bool "TI TPS68470 Power Management / LED chips" -- depends on ACPI && PCI && I2C=y -- depends on I2C_DESIGNWARE_PLATFORM=y -- select MFD_CORE -- select REGMAP_I2C -- help -- If you say yes here you get support for the TPS68470 series of -- Power Management / LED chips. -- -- These include voltage regulators, LEDs and other features -- that are often used in portable devices. -- -- This option is a bool as it provides an ACPI operation -- region, which must be available before any of the devices -- using this are probed. This option also configures the -- designware-i2c driver to be built-in, for the same reason. -- - config MFD_TI_LP873X - tristate "TI LP873X Power Management IC" - depends on I2C -diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile -index 4f6d2b8a5f76..8b322d89a0c5 100644 ---- a/drivers/mfd/Makefile -+++ b/drivers/mfd/Makefile -@@ -105,7 +105,6 @@ obj-$(CONFIG_MFD_TPS65910) += tps65910.o - obj-$(CONFIG_MFD_TPS65912) += tps65912-core.o - obj-$(CONFIG_MFD_TPS65912_I2C) += tps65912-i2c.o - obj-$(CONFIG_MFD_TPS65912_SPI) += tps65912-spi.o --obj-$(CONFIG_MFD_TPS68470) += tps68470.o - obj-$(CONFIG_MFD_TPS80031) += tps80031.o - obj-$(CONFIG_MENELAUS) += menelaus.o - -diff --git a/drivers/mfd/tps68470.c b/drivers/mfd/tps68470.c -deleted file mode 100644 -index 4a4df4ffd18c..000000000000 ---- a/drivers/mfd/tps68470.c -+++ /dev/null -@@ -1,97 +0,0 @@ --// SPDX-License-Identifier: GPL-2.0 --/* -- * TPS68470 chip Parent driver -- * -- * Copyright (C) 2017 Intel Corporation -- * -- * Authors: -- * Rajmohan Mani -- * Tianshu Qiu -- * Jian Xu Zheng -- * Yuning Pu -- */ -- --#include --#include --#include --#include --#include --#include --#include -- --static const struct mfd_cell tps68470s[] = { -- { .name = "tps68470-gpio" }, -- { .name = "tps68470_pmic_opregion" }, --}; -- --static const struct regmap_config tps68470_regmap_config = { -- .reg_bits = 8, -- .val_bits = 8, -- .max_register = TPS68470_REG_MAX, --}; -- --static int tps68470_chip_init(struct device *dev, struct regmap *regmap) --{ -- unsigned int version; -- int ret; -- -- /* Force software reset */ -- ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); -- if (ret) -- return ret; -- -- ret = regmap_read(regmap, TPS68470_REG_REVID, &version); -- if (ret) { -- dev_err(dev, "Failed to read revision register: %d\n", ret); -- return ret; -- } -- -- dev_info(dev, "TPS68470 REVID: 0x%x\n", version); -- -- return 0; --} -- --static int tps68470_probe(struct i2c_client *client) --{ -- struct device *dev = &client->dev; -- struct regmap *regmap; -- int ret; -- -- regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); -- if (IS_ERR(regmap)) { -- dev_err(dev, "devm_regmap_init_i2c Error %ld\n", -- PTR_ERR(regmap)); -- return PTR_ERR(regmap); -- } -- -- i2c_set_clientdata(client, regmap); -- -- ret = tps68470_chip_init(dev, regmap); -- if (ret < 0) { -- dev_err(dev, "TPS68470 Init Error %d\n", ret); -- return ret; -- } -- -- ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, tps68470s, -- ARRAY_SIZE(tps68470s), NULL, 0, NULL); -- if (ret < 0) { -- dev_err(dev, "devm_mfd_add_devices failed: %d\n", ret); -- return ret; -- } -- -- return 0; --} -- --static const struct acpi_device_id tps68470_acpi_ids[] = { -- {"INT3472"}, -- {}, --}; -- --static struct i2c_driver tps68470_driver = { -- .driver = { -- .name = "tps68470", -- .acpi_match_table = tps68470_acpi_ids, -- }, -- .probe_new = tps68470_probe, --}; --builtin_i2c_driver(tps68470_driver); --- -2.33.0 - -From 375118c70ce1ecc24b7729f0f48c487fa24418aa Mon Sep 17 00:00:00 2001 -From: Andy Shevchenko -Date: Fri, 18 Jun 2021 15:55:10 +0300 -Subject: [PATCH] platform/x86: intel_skl_int3472: Free ACPI device resources - after use - -We may free ACPI device resources immediately after use. -Refactor skl_int3472_parse_crs() accordingly. - -Signed-off-by: Andy Shevchenko -Reviewed-by: Daniel Scally -Tested-by: Daniel Scally -Link: https://lore.kernel.org/r/20210618125516.53510-2-andriy.shevchenko@linux.intel.com -Signed-off-by: Hans de Goede -Patchset: cameras ---- - .../x86/intel-int3472/intel_skl_int3472_discrete.c | 13 ++++++------- - 1 file changed, 6 insertions(+), 7 deletions(-) - -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -index 8c18dbff1c43..48a00a1f4fb6 100644 ---- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -@@ -308,8 +308,10 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) - ret = acpi_dev_get_resources(int3472->adev, &resource_list, - skl_int3472_handle_gpio_resources, - int3472); -- if (ret) -- goto out_free_res_list; -+ if (ret < 0) -+ return ret; -+ -+ acpi_dev_free_resource_list(&resource_list); - - /* - * If we find no clock enable GPIO pin then the privacy LED won't work. -@@ -319,7 +321,7 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) - if (int3472->clock.ena_gpio) { - ret = skl_int3472_register_clock(int3472); - if (ret) -- goto out_free_res_list; -+ return ret; - } else { - if (int3472->clock.led_gpio) - dev_warn(int3472->dev, -@@ -329,10 +331,7 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) - int3472->gpios.dev_id = int3472->sensor_name; - gpiod_add_lookup_table(&int3472->gpios); - --out_free_res_list: -- acpi_dev_free_resource_list(&resource_list); -- -- return ret; -+ return 0; - } - - int skl_int3472_discrete_probe(struct platform_device *pdev) --- -2.33.0 - -From a15fa9ed6d6509ad6e11721f34bfb79f8e292128 Mon Sep 17 00:00:00 2001 -From: Andy Shevchenko -Date: Fri, 18 Jun 2021 15:55:11 +0300 -Subject: [PATCH] platform/x86: intel_skl_int3472: Fix dependencies (drop - CLKDEV_LOOKUP) - -Besides the fact that COMMON_CLK selects CLKDEV_LOOKUP, the latter -is going to be removed from clock framework. - -Reviewed-by: Daniel Scally -Signed-off-by: Andy Shevchenko -Link: https://lore.kernel.org/r/20210618125516.53510-3-andriy.shevchenko@linux.intel.com -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel-int3472/Kconfig | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/platform/x86/intel-int3472/Kconfig b/drivers/platform/x86/intel-int3472/Kconfig -index c112878e833b..62e5d4cf9ee5 100644 ---- a/drivers/platform/x86/intel-int3472/Kconfig -+++ b/drivers/platform/x86/intel-int3472/Kconfig -@@ -1,7 +1,7 @@ - config INTEL_SKL_INT3472 - tristate "Intel SkyLake ACPI INT3472 Driver" - depends on ACPI -- depends on COMMON_CLK && CLKDEV_LOOKUP -+ depends on COMMON_CLK - depends on I2C - depends on GPIOLIB - depends on REGULATOR --- -2.33.0 - -From 687cd00f086741323a81dfb1290a3723b9485ce2 Mon Sep 17 00:00:00 2001 -From: Andy Shevchenko -Date: Fri, 18 Jun 2021 15:55:12 +0300 -Subject: [PATCH] platform/x86: intel_skl_int3472: Use ACPI GPIO resource - directly - -When we call acpi_gpio_get_io_resource(), the output will be -the pointer to the ACPI GPIO resource. Use it directly instead of -dereferencing the generic resource. - -Signed-off-by: Andy Shevchenko -Reviewed-by: Daniel Scally -Tested-by: Daniel Scally -Link: https://lore.kernel.org/r/20210618125516.53510-4-andriy.shevchenko@linux.intel.com -Signed-off-by: Hans de Goede -Patchset: cameras ---- - .../intel_skl_int3472_clk_and_regulator.c | 7 ++--- - .../intel-int3472/intel_skl_int3472_common.h | 2 +- - .../intel_skl_int3472_discrete.c | 28 +++++++++---------- - 3 files changed, 17 insertions(+), 20 deletions(-) - -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c -index ceee860e2c07..49ea1e86c193 100644 ---- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c -@@ -131,10 +131,10 @@ int skl_int3472_register_clock(struct int3472_discrete_device *int3472) - } - - int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, -- struct acpi_resource *ares) -+ struct acpi_resource_gpio *agpio) - { -- char *path = ares->data.gpio.resource_source.string_ptr; - const struct int3472_sensor_config *sensor_config; -+ char *path = agpio->resource_source.string_ptr; - struct regulator_consumer_supply supply_map; - struct regulator_init_data init_data = { }; - struct regulator_config cfg = { }; -@@ -168,8 +168,7 @@ int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, - int3472->regulator.supply_name, - &int3472_gpio_regulator_ops); - -- int3472->regulator.gpio = acpi_get_and_request_gpiod(path, -- ares->data.gpio.pin_table[0], -+ int3472->regulator.gpio = acpi_get_and_request_gpiod(path, agpio->pin_table[0], - "int3472,regulator"); - if (IS_ERR(int3472->regulator.gpio)) { - dev_err(int3472->dev, "Failed to get regulator GPIO line\n"); -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h -index 6fdf78584219..765e01ec1604 100644 ---- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h -@@ -113,6 +113,6 @@ union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, - int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); - int skl_int3472_register_clock(struct int3472_discrete_device *int3472); - int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, -- struct acpi_resource *ares); -+ struct acpi_resource_gpio *agpio); - - #endif -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -index 48a00a1f4fb6..fd681d2a73fe 100644 ---- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -@@ -103,11 +103,11 @@ skl_int3472_get_sensor_module_config(struct int3472_discrete_device *int3472) - } - - static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int3472, -- struct acpi_resource *ares, -+ struct acpi_resource_gpio *agpio, - const char *func, u32 polarity) - { -- char *path = ares->data.gpio.resource_source.string_ptr; - const struct int3472_sensor_config *sensor_config; -+ char *path = agpio->resource_source.string_ptr; - struct gpiod_lookup *table_entry; - struct acpi_device *adev; - acpi_handle handle; -@@ -145,7 +145,7 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 - - table_entry = &int3472->gpios.table[int3472->n_sensor_gpios]; - table_entry->key = acpi_dev_name(adev); -- table_entry->chip_hwnum = ares->data.gpio.pin_table[0]; -+ table_entry->chip_hwnum = agpio->pin_table[0]; - table_entry->con_id = func; - table_entry->idx = 0; - table_entry->flags = polarity; -@@ -156,23 +156,22 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 - } - - static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472, -- struct acpi_resource *ares, u8 type) -+ struct acpi_resource_gpio *agpio, u8 type) - { -- char *path = ares->data.gpio.resource_source.string_ptr; -+ char *path = agpio->resource_source.string_ptr; -+ u16 pin = agpio->pin_table[0]; - struct gpio_desc *gpio; - - switch (type) { - case INT3472_GPIO_TYPE_CLK_ENABLE: -- gpio = acpi_get_and_request_gpiod(path, ares->data.gpio.pin_table[0], -- "int3472,clk-enable"); -+ gpio = acpi_get_and_request_gpiod(path, pin, "int3472,clk-enable"); - if (IS_ERR(gpio)) - return (PTR_ERR(gpio)); - - int3472->clock.ena_gpio = gpio; - break; - case INT3472_GPIO_TYPE_PRIVACY_LED: -- gpio = acpi_get_and_request_gpiod(path, ares->data.gpio.pin_table[0], -- "int3472,privacy-led"); -+ gpio = acpi_get_and_request_gpiod(path, pin, "int3472,privacy-led"); - if (IS_ERR(gpio)) - return (PTR_ERR(gpio)); - -@@ -242,7 +241,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, - - if (!obj) { - dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n", -- ares->data.gpio.pin_table[0]); -+ agpio->pin_table[0]); - return 1; - } - -@@ -250,15 +249,14 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, - - switch (type) { - case INT3472_GPIO_TYPE_RESET: -- ret = skl_int3472_map_gpio_to_sensor(int3472, ares, "reset", -+ ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "reset", - GPIO_ACTIVE_LOW); - if (ret) - err_msg = "Failed to map reset pin to sensor\n"; - - break; - case INT3472_GPIO_TYPE_POWERDOWN: -- ret = skl_int3472_map_gpio_to_sensor(int3472, ares, -- "powerdown", -+ ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "powerdown", - GPIO_ACTIVE_LOW); - if (ret) - err_msg = "Failed to map powerdown pin to sensor\n"; -@@ -266,13 +264,13 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, - break; - case INT3472_GPIO_TYPE_CLK_ENABLE: - case INT3472_GPIO_TYPE_PRIVACY_LED: -- ret = skl_int3472_map_gpio_to_clk(int3472, ares, type); -+ ret = skl_int3472_map_gpio_to_clk(int3472, agpio, type); - if (ret) - err_msg = "Failed to map GPIO to clock\n"; - - break; - case INT3472_GPIO_TYPE_POWER_ENABLE: -- ret = skl_int3472_register_regulator(int3472, ares); -+ ret = skl_int3472_register_regulator(int3472, agpio); - if (ret) - err_msg = "Failed to map regulator to sensor\n"; - --- -2.33.0 - -From 110d7885979dad5b0d6beb81369d93c3cc38d0d8 Mon Sep 17 00:00:00 2001 -From: Andy Shevchenko -Date: Fri, 18 Jun 2021 15:55:13 +0300 -Subject: [PATCH] platform/x86: intel_skl_int3472: Provide - skl_int3472_unregister_regulator() - -For the sake of APIs to be properly layered provide -skl_int3472_unregister_regulator(). - -Signed-off-by: Andy Shevchenko -Reviewed-by: Daniel Scally -Tested-by: Daniel Scally -Link: https://lore.kernel.org/r/20210618125516.53510-5-andriy.shevchenko@linux.intel.com -Signed-off-by: Hans de Goede -Patchset: cameras ---- - .../x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c | 6 ++++++ - .../platform/x86/intel-int3472/intel_skl_int3472_common.h | 2 ++ - .../platform/x86/intel-int3472/intel_skl_int3472_discrete.c | 4 ++-- - 3 files changed, 10 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c -index 49ea1e86c193..60c7128f44ee 100644 ---- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c -@@ -193,3 +193,9 @@ int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, - - return ret; - } -+ -+void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472) -+{ -+ regulator_unregister(int3472->regulator.rdev); -+ gpiod_put(int3472->regulator.gpio); -+} -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h -index 765e01ec1604..50f73c6eab44 100644 ---- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h -@@ -112,7 +112,9 @@ union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, - char *id); - int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); - int skl_int3472_register_clock(struct int3472_discrete_device *int3472); -+ - int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, - struct acpi_resource_gpio *agpio); -+void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472); - - #endif -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -index fd681d2a73fe..2638d375e226 100644 ---- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -@@ -400,15 +400,15 @@ int skl_int3472_discrete_remove(struct platform_device *pdev) - struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); - - gpiod_remove_lookup_table(&int3472->gpios); -- regulator_unregister(int3472->regulator.rdev); - clk_unregister(int3472->clock.clk); - - if (int3472->clock.cl) - clkdev_drop(int3472->clock.cl); - -- gpiod_put(int3472->regulator.gpio); - gpiod_put(int3472->clock.ena_gpio); - gpiod_put(int3472->clock.led_gpio); - -+ skl_int3472_unregister_regulator(int3472); -+ - return 0; - } --- -2.33.0 - -From 41625860cfb0264e6b3f575bd3c7330ce3df59e0 Mon Sep 17 00:00:00 2001 -From: Andy Shevchenko -Date: Fri, 18 Jun 2021 15:55:14 +0300 -Subject: [PATCH] platform/x86: intel_skl_int3472: Provide - skl_int3472_unregister_clock() - -For the sake of APIs to be properly layered provide -skl_int3472_unregister_clock(). - -Signed-off-by: Andy Shevchenko -Reviewed-by: Daniel Scally -Tested-by: Daniel Scally -Link: https://lore.kernel.org/r/20210618125516.53510-6-andriy.shevchenko@linux.intel.com -Signed-off-by: Hans de Goede -Patchset: cameras ---- - .../x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c | 6 ++++++ - .../platform/x86/intel-int3472/intel_skl_int3472_common.h | 2 ++ - .../platform/x86/intel-int3472/intel_skl_int3472_discrete.c | 5 ++--- - 3 files changed, 10 insertions(+), 3 deletions(-) - -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c -index 60c7128f44ee..1700e7557a82 100644 ---- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c -@@ -130,6 +130,12 @@ int skl_int3472_register_clock(struct int3472_discrete_device *int3472) - return ret; - } - -+void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472) -+{ -+ clkdev_drop(int3472->clock.cl); -+ clk_unregister(int3472->clock.clk); -+} -+ - int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, - struct acpi_resource_gpio *agpio) - { -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h -index 50f73c6eab44..714fde73b524 100644 ---- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h -@@ -111,7 +111,9 @@ int skl_int3472_tps68470_probe(struct i2c_client *client); - union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, - char *id); - int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); -+ - int skl_int3472_register_clock(struct int3472_discrete_device *int3472); -+void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472); - - int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, - struct acpi_resource_gpio *agpio); -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -index 2638d375e226..17c6fe830765 100644 ---- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -@@ -400,10 +400,9 @@ int skl_int3472_discrete_remove(struct platform_device *pdev) - struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); - - gpiod_remove_lookup_table(&int3472->gpios); -- clk_unregister(int3472->clock.clk); - -- if (int3472->clock.cl) -- clkdev_drop(int3472->clock.cl); -+ if (int3472->clock.ena_gpio) -+ skl_int3472_unregister_clock(int3472); - - gpiod_put(int3472->clock.ena_gpio); - gpiod_put(int3472->clock.led_gpio); --- -2.33.0 - -From 7be2b7369e4bb716b797c5532f8381d73fd941e0 Mon Sep 17 00:00:00 2001 -From: Dan Carpenter -Date: Fri, 25 Jun 2021 16:01:04 +0300 -Subject: [PATCH] platform/x86: intel_skl_int3472: Uninitialized variable in - skl_int3472_handle_gpio_resources() - -This function returns negative error codes, zero (to indicate that -everything has been completed successfully) and one (to indicate that -more resources need to be handled still). - -This code prints an uninitialized error message when the function -returns one which potentially leads to an Oops. - -Fixes: 5de691bffe57 ("platform/x86: Add intel_skl_int3472 driver") -Signed-off-by: Dan Carpenter -Reviewed-by: Daniel Scally -Link: https://lore.kernel.org/r/YNXTkLNtiTDlFlZa@mwanda -Signed-off-by: Hans de Goede -Patchset: cameras ---- - .../platform/x86/intel-int3472/intel_skl_int3472_discrete.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -index 17c6fe830765..9fe0a2527e1c 100644 ---- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -+++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c -@@ -286,10 +286,10 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, - int3472->ngpios++; - ACPI_FREE(obj); - -- if (ret) -+ if (ret < 0) - return dev_err_probe(int3472->dev, ret, err_msg); - -- return 0; -+ return ret; - } - - static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) --- -2.33.0 - -From 0487dd4fbc6e26a0e03cae276049d094cfa6c448 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 5 Apr 2021 23:56:53 +0100 -Subject: [PATCH] media: ipu3-cio2: Toggle sensor streaming in pm runtime ops - -The .suspend() and .resume() runtime_pm operations for the ipu3-cio2 -driver currently do not handle the sensor's stream. Setting .s_stream() on -or off for the sensor subdev means that sensors will pause and resume the -stream at the appropriate time even if their drivers don't implement those -operations. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 15 ++++++++++++++- - 1 file changed, 14 insertions(+), 1 deletion(-) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -index fecef85bd62e..9dafb9470708 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -@@ -1973,12 +1973,19 @@ static int __maybe_unused cio2_suspend(struct device *dev) - struct pci_dev *pci_dev = to_pci_dev(dev); - struct cio2_device *cio2 = pci_get_drvdata(pci_dev); - struct cio2_queue *q = cio2->cur_queue; -+ int r; - - dev_dbg(dev, "cio2 suspend\n"); - if (!cio2->streaming) - return 0; - - /* Stop stream */ -+ r = v4l2_subdev_call(q->sensor, video, s_stream, 0); -+ if (r) { -+ dev_err(dev, "failed to stop sensor streaming\n"); -+ return r; -+ } -+ - cio2_hw_exit(cio2, q); - synchronize_irq(pci_dev->irq); - -@@ -2013,8 +2020,14 @@ static int __maybe_unused cio2_resume(struct device *dev) - } - - r = cio2_hw_init(cio2, q); -- if (r) -+ if (r) { - dev_err(dev, "fail to init cio2 hw\n"); -+ return r; -+ } -+ -+ r = v4l2_subdev_call(q->sensor, video, s_stream, 1); -+ if (r) -+ dev_err(dev, "fail to start sensor streaming\n"); - - return r; - } --- -2.33.0 - -From d4d0ceeedc1d0f728e8eca398e88bfcb848cb99d Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 5 Apr 2021 23:56:54 +0100 -Subject: [PATCH] media: i2c: Add support for ov5693 sensor - -The OV5693 is a 5 Mpx CMOS image sensor, connected via MIPI CSI-2. The -chip is capable of a single lane configuration, but currently only two -lanes are supported. - -Most of the sensor's features are supported, with the main exception -being the lens correction algorithm. - -The driver provides all mandatory, optional and recommended V4L2 controls -for maximum compatibility with libcamera. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 7 + - drivers/media/i2c/Kconfig | 11 + - drivers/media/i2c/Makefile | 1 + - drivers/media/i2c/ov5693.c | 1557 ++++++++++++++++++++++++++++++++++++ - 4 files changed, 1576 insertions(+) - create mode 100644 drivers/media/i2c/ov5693.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 1db7311d78a6..567a5b64d297 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -13563,6 +13563,13 @@ S: Maintained - T: git git://linuxtv.org/media_tree.git - F: drivers/media/i2c/ov5675.c - -+OMNIVISION OV5693 SENSOR DRIVER -+M: Daniel Scally -+L: linux-media@vger.kernel.org -+S: Maintained -+T: git git://linuxtv.org/media_tree.git -+F: drivers/media/i2c/ov5693.c -+ - OMNIVISION OV5695 SENSOR DRIVER - M: Shunqian Zheng - L: linux-media@vger.kernel.org -diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig -index 462c0e059754..2893e74af99a 100644 ---- a/drivers/media/i2c/Kconfig -+++ b/drivers/media/i2c/Kconfig -@@ -999,6 +999,17 @@ config VIDEO_OV5675 - To compile this driver as a module, choose M here: the - module will be called ov5675. - -+config VIDEO_OV5693 -+ tristate "OmniVision OV5693 sensor support" -+ depends on I2C && VIDEO_V4L2 -+ select V4L2_FWNODE -+ help -+ This is a Video4Linux2 sensor driver for the OmniVision -+ OV5693 camera. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ov5693. -+ - config VIDEO_OV5695 - tristate "OmniVision OV5695 sensor support" - depends on I2C && VIDEO_V4L2 -diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile -index 0c067beca066..7d0884bc89f1 100644 ---- a/drivers/media/i2c/Makefile -+++ b/drivers/media/i2c/Makefile -@@ -75,6 +75,7 @@ obj-$(CONFIG_VIDEO_OV5647) += ov5647.o - obj-$(CONFIG_VIDEO_OV5648) += ov5648.o - obj-$(CONFIG_VIDEO_OV5670) += ov5670.o - obj-$(CONFIG_VIDEO_OV5675) += ov5675.o -+obj-$(CONFIG_VIDEO_OV5693) += ov5693.o - obj-$(CONFIG_VIDEO_OV5695) += ov5695.o - obj-$(CONFIG_VIDEO_OV6650) += ov6650.o - obj-$(CONFIG_VIDEO_OV7251) += ov7251.o -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -new file mode 100644 -index 000000000000..276f625d4d23 ---- /dev/null -+++ b/drivers/media/i2c/ov5693.c -@@ -0,0 +1,1557 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. -+ * -+ * Adapted from the atomisp-ov5693 driver, with contributions from: -+ * -+ * Daniel Scally -+ * Jean-Michel Hautbois -+ * Fabian Wuthrich -+ * Tsuchiya Yuto -+ * Jordan Hand -+ * Jake Day -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+/* System Control */ -+#define OV5693_SW_RESET_REG 0x0103 -+#define OV5693_SW_STREAM_REG 0x0100 -+#define OV5693_START_STREAMING 0x01 -+#define OV5693_STOP_STREAMING 0x00 -+#define OV5693_SW_RESET 0x01 -+ -+#define OV5693_REG_CHIP_ID_H 0x300a -+#define OV5693_REG_CHIP_ID_L 0x300b -+/* Yes, this is right. The datasheet for the OV5693 gives its ID as 0x5690 */ -+#define OV5693_CHIP_ID 0x5690 -+ -+/* Exposure */ -+#define OV5693_EXPOSURE_L_CTRL_HH_REG 0x3500 -+#define OV5693_EXPOSURE_L_CTRL_H_REG 0x3501 -+#define OV5693_EXPOSURE_L_CTRL_L_REG 0x3502 -+#define OV5693_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(14, 12)) >> 12) -+#define OV5693_EXPOSURE_CTRL_H(v) (((v) & GENMASK(11, 4)) >> 4) -+#define OV5693_EXPOSURE_CTRL_L(v) (((v) & GENMASK(3, 0)) << 4) -+#define OV5693_INTEGRATION_TIME_MARGIN 8 -+#define OV5693_EXPOSURE_MIN 1 -+#define OV5693_EXPOSURE_STEP 1 -+ -+/* Analogue Gain */ -+#define OV5693_GAIN_CTRL_H_REG 0x350a -+#define OV5693_GAIN_CTRL_H(v) (((v) >> 4) & GENMASK(2, 0)) -+#define OV5693_GAIN_CTRL_L_REG 0x350b -+#define OV5693_GAIN_CTRL_L(v) (((v) << 4) & GENMASK(7, 4)) -+#define OV5693_GAIN_MIN 1 -+#define OV5693_GAIN_MAX 127 -+#define OV5693_GAIN_DEF 8 -+#define OV5693_GAIN_STEP 1 -+ -+/* Digital Gain */ -+#define OV5693_MWB_RED_GAIN_H_REG 0x3400 -+#define OV5693_MWB_RED_GAIN_L_REG 0x3401 -+#define OV5693_MWB_GREEN_GAIN_H_REG 0x3402 -+#define OV5693_MWB_GREEN_GAIN_L_REG 0x3403 -+#define OV5693_MWB_BLUE_GAIN_H_REG 0x3404 -+#define OV5693_MWB_BLUE_GAIN_L_REG 0x3405 -+#define OV5693_MWB_GAIN_H_CTRL(v) (((v) >> 8) & GENMASK(3, 0)) -+#define OV5693_MWB_GAIN_L_CTRL(v) ((v) & GENMASK(7, 0)) -+#define OV5693_MWB_GAIN_MAX 0x0fff -+#define OV5693_DIGITAL_GAIN_MIN 1 -+#define OV5693_DIGITAL_GAIN_MAX 4095 -+#define OV5693_DIGITAL_GAIN_DEF 1024 -+#define OV5693_DIGITAL_GAIN_STEP 1 -+ -+/* Timing and Format */ -+#define OV5693_CROP_START_X_H_REG 0x3800 -+#define OV5693_CROP_START_X_H(v) (((v) & GENMASK(12, 8)) >> 8) -+#define OV5693_CROP_START_X_L_REG 0x3801 -+#define OV5693_CROP_START_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_START_Y_H_REG 0x3802 -+#define OV5693_CROP_START_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) -+#define OV5693_CROP_START_Y_L_REG 0x3803 -+#define OV5693_CROP_START_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_END_X_H_REG 0x3804 -+#define OV5693_CROP_END_X_H(v) (((v) & GENMASK(12, 8)) >> 8) -+#define OV5693_CROP_END_X_L_REG 0x3805 -+#define OV5693_CROP_END_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_END_Y_H_REG 0x3806 -+#define OV5693_CROP_END_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) -+#define OV5693_CROP_END_Y_L_REG 0x3807 -+#define OV5693_CROP_END_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OUTPUT_SIZE_X_H_REG 0x3808 -+#define OV5693_OUTPUT_SIZE_X_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OUTPUT_SIZE_X_L_REG 0x3809 -+#define OV5693_OUTPUT_SIZE_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OUTPUT_SIZE_Y_H_REG 0x380a -+#define OV5693_OUTPUT_SIZE_Y_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OUTPUT_SIZE_Y_L_REG 0x380b -+#define OV5693_OUTPUT_SIZE_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_TIMING_HTS_H_REG 0x380c -+#define OV5693_TIMING_HTS_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_TIMING_HTS_L_REG 0x380d -+#define OV5693_TIMING_HTS_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_FIXED_PPL 2688U -+ -+#define OV5693_TIMING_VTS_H_REG 0x380e -+#define OV5693_TIMING_VTS_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_TIMING_VTS_L_REG 0x380f -+#define OV5693_TIMING_VTS_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_TIMING_MAX_VTS 0xffff -+#define OV5693_TIMING_MIN_VTS 0x04 -+ -+#define OV5693_OFFSET_START_X_H_REG 0x3810 -+#define OV5693_OFFSET_START_X_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OFFSET_START_X_L_REG 0x3811 -+#define OV5693_OFFSET_START_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OFFSET_START_Y_H_REG 0x3812 -+#define OV5693_OFFSET_START_Y_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OFFSET_START_Y_L_REG 0x3813 -+#define OV5693_OFFSET_START_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_SUB_INC_X_REG 0x3814 -+#define OV5693_SUB_INC_Y_REG 0x3815 -+ -+#define OV5693_FORMAT1_REG 0x3820 -+#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -+#define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) -+#define OV5693_FORMAT1_VBIN_EN BIT(0) -+#define OV5693_FORMAT2_REG 0x3821 -+#define OV5693_FORMAT2_HDR_EN BIT(7) -+#define OV5693_FORMAT2_FLIP_HORZ_ISP_EN BIT(2) -+#define OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN BIT(1) -+#define OV5693_FORMAT2_HBIN_EN BIT(0) -+ -+#define OV5693_ISP_CTRL2_REG 0x5002 -+#define OV5693_ISP_SCALE_ENABLE BIT(7) -+ -+/* Pixel Array */ -+#define OV5693_NATIVE_WIDTH 2624 -+#define OV5693_NATIVE_HEIGHT 1956 -+#define OV5693_NATIVE_START_LEFT 0 -+#define OV5693_NATIVE_START_TOP 0 -+#define OV5693_ACTIVE_WIDTH 2592 -+#define OV5693_ACTIVE_HEIGHT 1944 -+#define OV5693_ACTIVE_START_LEFT 16 -+#define OV5693_ACTIVE_START_TOP 6 -+#define OV5693_MIN_CROP_WIDTH 2 -+#define OV5693_MIN_CROP_HEIGHT 2 -+ -+/* Test Pattern */ -+#define OV5693_TEST_PATTERN_REG 0x5e00 -+#define OV5693_TEST_PATTERN_ENABLE BIT(7) -+#define OV5693_TEST_PATTERN_ROLLING BIT(6) -+#define OV5693_TEST_PATTERN_RANDOM 0x01 -+#define OV5693_TEST_PATTERN_BARS 0x00 -+ -+/* System Frequencies */ -+#define OV5693_XVCLK_FREQ 19200000 -+#define OV5693_LINK_FREQ_400MHZ 400000000 -+#define OV5693_PIXEL_RATE 160000000 -+ -+/* Miscellaneous */ -+#define OV5693_NUM_SUPPLIES 2 -+ -+#define to_ov5693_sensor(x) container_of(x, struct ov5693_device, sd) -+ -+struct ov5693_reg { -+ u16 reg; -+ u8 val; -+}; -+ -+struct ov5693_reg_list { -+ u32 num_regs; -+ const struct ov5693_reg *regs; -+}; -+ -+struct ov5693_device { -+ struct i2c_client *client; -+ struct device *dev; -+ -+ /* Protect against concurrent changes to controls */ -+ struct mutex lock; -+ -+ struct gpio_desc *reset; -+ struct gpio_desc *powerdown; -+ struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; -+ struct clk *clk; -+ -+ struct ov5693_mode { -+ struct v4l2_rect crop; -+ struct v4l2_mbus_framefmt format; -+ bool binning_x; -+ bool binning_y; -+ unsigned int inc_x_odd; -+ unsigned int inc_y_odd; -+ unsigned int vts; -+ } mode; -+ bool streaming; -+ -+ struct v4l2_subdev sd; -+ struct media_pad pad; -+ -+ struct ov5693_v4l2_ctrls { -+ struct v4l2_ctrl_handler handler; -+ struct v4l2_ctrl *link_freq; -+ struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *exposure; -+ struct v4l2_ctrl *analogue_gain; -+ struct v4l2_ctrl *digital_gain; -+ struct v4l2_ctrl *hflip; -+ struct v4l2_ctrl *vflip; -+ struct v4l2_ctrl *hblank; -+ struct v4l2_ctrl *vblank; -+ struct v4l2_ctrl *test_pattern; -+ } ctrls; -+}; -+ -+static const struct ov5693_reg ov5693_global_regs[] = { -+ {0x3016, 0xf0}, -+ {0x3017, 0xf0}, -+ {0x3018, 0xf0}, -+ {0x3022, 0x01}, -+ {0x3028, 0x44}, -+ {0x3098, 0x02}, -+ {0x3099, 0x19}, -+ {0x309a, 0x02}, -+ {0x309b, 0x01}, -+ {0x309c, 0x00}, -+ {0x30a0, 0xd2}, -+ {0x30a2, 0x01}, -+ {0x30b2, 0x00}, -+ {0x30b3, 0x7d}, -+ {0x30b4, 0x03}, -+ {0x30b5, 0x04}, -+ {0x30b6, 0x01}, -+ {0x3104, 0x21}, -+ {0x3106, 0x00}, -+ {0x3406, 0x01}, -+ {0x3503, 0x07}, -+ {0x350b, 0x40}, -+ {0x3601, 0x0a}, -+ {0x3602, 0x38}, -+ {0x3612, 0x80}, -+ {0x3620, 0x54}, -+ {0x3621, 0xc7}, -+ {0x3622, 0x0f}, -+ {0x3625, 0x10}, -+ {0x3630, 0x55}, -+ {0x3631, 0xf4}, -+ {0x3632, 0x00}, -+ {0x3633, 0x34}, -+ {0x3634, 0x02}, -+ {0x364d, 0x0d}, -+ {0x364f, 0xdd}, -+ {0x3660, 0x04}, -+ {0x3662, 0x10}, -+ {0x3663, 0xf1}, -+ {0x3665, 0x00}, -+ {0x3666, 0x20}, -+ {0x3667, 0x00}, -+ {0x366a, 0x80}, -+ {0x3680, 0xe0}, -+ {0x3681, 0x00}, -+ {0x3700, 0x42}, -+ {0x3701, 0x14}, -+ {0x3702, 0xa0}, -+ {0x3703, 0xd8}, -+ {0x3704, 0x78}, -+ {0x3705, 0x02}, -+ {0x370a, 0x00}, -+ {0x370b, 0x20}, -+ {0x370c, 0x0c}, -+ {0x370d, 0x11}, -+ {0x370e, 0x00}, -+ {0x370f, 0x40}, -+ {0x3710, 0x00}, -+ {0x371a, 0x1c}, -+ {0x371b, 0x05}, -+ {0x371c, 0x01}, -+ {0x371e, 0xa1}, -+ {0x371f, 0x0c}, -+ {0x3721, 0x00}, -+ {0x3724, 0x10}, -+ {0x3726, 0x00}, -+ {0x372a, 0x01}, -+ {0x3730, 0x10}, -+ {0x3738, 0x22}, -+ {0x3739, 0xe5}, -+ {0x373a, 0x50}, -+ {0x373b, 0x02}, -+ {0x373c, 0x41}, -+ {0x373f, 0x02}, -+ {0x3740, 0x42}, -+ {0x3741, 0x02}, -+ {0x3742, 0x18}, -+ {0x3743, 0x01}, -+ {0x3744, 0x02}, -+ {0x3747, 0x10}, -+ {0x374c, 0x04}, -+ {0x3751, 0xf0}, -+ {0x3752, 0x00}, -+ {0x3753, 0x00}, -+ {0x3754, 0xc0}, -+ {0x3755, 0x00}, -+ {0x3756, 0x1a}, -+ {0x3758, 0x00}, -+ {0x3759, 0x0f}, -+ {0x376b, 0x44}, -+ {0x375c, 0x04}, -+ {0x3774, 0x10}, -+ {0x3776, 0x00}, -+ {0x377f, 0x08}, -+ {0x3780, 0x22}, -+ {0x3781, 0x0c}, -+ {0x3784, 0x2c}, -+ {0x3785, 0x1e}, -+ {0x378f, 0xf5}, -+ {0x3791, 0xb0}, -+ {0x3795, 0x00}, -+ {0x3796, 0x64}, -+ {0x3797, 0x11}, -+ {0x3798, 0x30}, -+ {0x3799, 0x41}, -+ {0x379a, 0x07}, -+ {0x379b, 0xb0}, -+ {0x379c, 0x0c}, -+ {0x3a04, 0x06}, -+ {0x3a05, 0x14}, -+ {0x3e07, 0x20}, -+ {0x4000, 0x08}, -+ {0x4001, 0x04}, -+ {0x4004, 0x08}, -+ {0x4006, 0x20}, -+ {0x4008, 0x24}, -+ {0x4009, 0x10}, -+ {0x4058, 0x00}, -+ {0x4101, 0xb2}, -+ {0x4307, 0x31}, -+ {0x4511, 0x05}, -+ {0x4512, 0x01}, -+ {0x481f, 0x30}, -+ {0x4826, 0x2c}, -+ {0x4d02, 0xfd}, -+ {0x4d03, 0xf5}, -+ {0x4d04, 0x0c}, -+ {0x4d05, 0xcc}, -+ {0x4837, 0x0a}, -+ {0x5003, 0x20}, -+ {0x5013, 0x00}, -+ {0x5842, 0x01}, -+ {0x5843, 0x2b}, -+ {0x5844, 0x01}, -+ {0x5845, 0x92}, -+ {0x5846, 0x01}, -+ {0x5847, 0x8f}, -+ {0x5848, 0x01}, -+ {0x5849, 0x0c}, -+ {0x5e10, 0x0c}, -+ {0x3820, 0x00}, -+ {0x3821, 0x1e}, -+ {0x5041, 0x14} -+}; -+ -+static const struct ov5693_reg_list ov5693_global_setting = { -+ .num_regs = ARRAY_SIZE(ov5693_global_regs), -+ .regs = ov5693_global_regs, -+}; -+ -+static const struct v4l2_rect ov5693_default_crop = { -+ .left = OV5693_ACTIVE_START_LEFT, -+ .top = OV5693_ACTIVE_START_TOP, -+ .width = OV5693_ACTIVE_WIDTH, -+ .height = OV5693_ACTIVE_HEIGHT, -+}; -+ -+static const struct v4l2_mbus_framefmt ov5693_default_fmt = { -+ .width = OV5693_ACTIVE_WIDTH, -+ .height = OV5693_ACTIVE_HEIGHT, -+ .code = MEDIA_BUS_FMT_SBGGR10_1X10, -+}; -+ -+static const s64 link_freq_menu_items[] = { -+ OV5693_LINK_FREQ_400MHZ -+}; -+ -+static const char * const ov5693_supply_names[] = { -+ "avdd", -+ "dovdd", -+}; -+ -+static const char * const ov5693_test_pattern_menu[] = { -+ "Disabled", -+ "Random Data", -+ "Colour Bars", -+ "Colour Bars with Rolling Bar" -+}; -+ -+static const u8 ov5693_test_pattern_bits[] = { -+ 0, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_RANDOM, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS | -+ OV5693_TEST_PATTERN_ROLLING, -+}; -+ -+/* I2C I/O Operations */ -+ -+static int ov5693_read_reg(struct ov5693_device *ov5693, u16 addr, u8 *value) -+{ -+ struct i2c_client *client = ov5693->client; -+ struct i2c_msg msgs[2]; -+ u8 addr_buf[2]; -+ u8 data_buf; -+ int ret; -+ -+ put_unaligned_be16(addr, addr_buf); -+ -+ /* 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 register value */ -+ msgs[1].addr = client->addr; -+ msgs[1].flags = I2C_M_RD; -+ msgs[1].len = 1; -+ msgs[1].buf = &data_buf; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ if (ret != ARRAY_SIZE(msgs)) -+ return -EIO; -+ -+ *value = data_buf; -+ -+ return 0; -+} -+ -+static void ov5693_write_reg(struct ov5693_device *ov5693, u16 addr, u8 value, -+ int *error) -+{ -+ unsigned char data[3] = { addr >> 8, addr & 0xff, value }; -+ int ret; -+ -+ if (*error < 0) -+ return; -+ -+ ret = i2c_master_send(ov5693->client, data, sizeof(data)); -+ if (ret < 0) { -+ dev_dbg(ov5693->dev, "i2c send error at address 0x%04x: %d\n", -+ addr, ret); -+ *error = ret; -+ } -+} -+ -+static int ov5693_write_reg_array(struct ov5693_device *ov5693, -+ const struct ov5693_reg_list *reglist) -+{ -+ unsigned int i; -+ int ret = 0; -+ -+ for (i = 0; i < reglist->num_regs; i++) -+ ov5693_write_reg(ov5693, reglist->regs[i].reg, -+ reglist->regs[i].val, &ret); -+ -+ return ret; -+} -+ -+static int ov5693_update_bits(struct ov5693_device *ov5693, u16 address, -+ u16 mask, u16 bits) -+{ -+ u8 value = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, address, &value); -+ if (ret) -+ return ret; -+ -+ value &= ~mask; -+ value |= bits; -+ -+ ov5693_write_reg(ov5693, address, value, &ret); -+ -+ return ret; -+} -+ -+/* V4L2 Controls Functions */ -+ -+static int ov5693_flip_vert_configure(struct ov5693_device *ov5693, bool enable) -+{ -+ u8 bits = OV5693_FORMAT1_FLIP_VERT_ISP_EN | -+ OV5693_FORMAT1_FLIP_VERT_SENSOR_EN; -+ int ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, bits, -+ enable ? bits : 0); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int ov5693_flip_horz_configure(struct ov5693_device *ov5693, bool enable) -+{ -+ u8 bits = OV5693_FORMAT2_FLIP_HORZ_ISP_EN | -+ OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN; -+ int ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, bits, -+ enable ? bits : 0); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int ov5693_get_exposure(struct ov5693_device *ov5693, s32 *value) -+{ -+ u8 exposure_hh = 0, exposure_h = 0, exposure_l = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, &exposure_hh); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, &exposure_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, &exposure_l); -+ if (ret) -+ return ret; -+ -+ /* The lowest 4 bits are unsupported fractional bits */ -+ *value = ((exposure_hh << 16) | (exposure_h << 8) | exposure_l) >> 4; -+ -+ return 0; -+} -+ -+static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, -+ OV5693_EXPOSURE_CTRL_HH(exposure), &ret); -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, -+ OV5693_EXPOSURE_CTRL_H(exposure), &ret); -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, -+ OV5693_EXPOSURE_CTRL_L(exposure), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_get_gain(struct ov5693_device *ov5693, u32 *gain) -+{ -+ u8 gain_l = 0, gain_h = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_H_REG, &gain_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_L_REG, &gain_l); -+ if (ret) -+ return ret; -+ -+ /* As with exposure, the lowest 4 bits are fractional bits. */ -+ *gain = ((gain_h << 8) | gain_l) >> 4; -+ -+ return ret; -+} -+ -+static int ov5693_digital_gain_configure(struct ov5693_device *ov5693, u32 gain) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_analog_gain_configure(struct ov5693_device *ov5693, u32 gain) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_L_REG, -+ OV5693_GAIN_CTRL_L(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_H_REG, -+ OV5693_GAIN_CTRL_H(gain), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_vts_configure(struct ov5693_device *ov5693, u32 vblank) -+{ -+ u16 vts = ov5693->mode.format.height + vblank; -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG, -+ OV5693_TIMING_VTS_H(vts), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG, -+ OV5693_TIMING_VTS_L(vts), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_test_pattern_configure(struct ov5693_device *ov5693, u32 idx) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_TEST_PATTERN_REG, -+ ov5693_test_pattern_bits[idx], &ret); -+ -+ return ret; -+} -+ -+static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *ov5693 = -+ container_of(ctrl->handler, struct ov5693_device, ctrls.handler); -+ int ret = 0; -+ -+ /* If VBLANK is altered we need to update exposure to compensate */ -+ if (ctrl->id == V4L2_CID_VBLANK) { -+ int exposure_max; -+ -+ exposure_max = ov5693->mode.format.height + ctrl->val - -+ OV5693_INTEGRATION_TIME_MARGIN; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, -+ ov5693->ctrls.exposure->minimum, -+ exposure_max, -+ ov5693->ctrls.exposure->step, -+ min(ov5693->ctrls.exposure->val, exposure_max)); -+ } -+ -+ /* Only apply changes to the controls if the device is powered up */ -+ if (!pm_runtime_get_if_in_use(ov5693->dev)) -+ return 0; -+ -+ switch (ctrl->id) { -+ case V4L2_CID_EXPOSURE: -+ ret = ov5693_exposure_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_ANALOGUE_GAIN: -+ ret = ov5693_analog_gain_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_DIGITAL_GAIN: -+ ret = ov5693_digital_gain_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_HFLIP: -+ ret = ov5693_flip_horz_configure(ov5693, !!ctrl->val); -+ break; -+ case V4L2_CID_VFLIP: -+ ret = ov5693_flip_vert_configure(ov5693, !!ctrl->val); -+ break; -+ case V4L2_CID_VBLANK: -+ ret = ov5693_vts_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_TEST_PATTERN: -+ ret = ov5693_test_pattern_configure(ov5693, ctrl->val); -+ break; -+ default: -+ ret = -EINVAL; -+ } -+ -+ pm_runtime_put(ov5693->dev); -+ -+ return ret; -+} -+ -+static int ov5693_g_volatile_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *ov5693 = -+ container_of(ctrl->handler, struct ov5693_device, ctrls.handler); -+ -+ switch (ctrl->id) { -+ case V4L2_CID_EXPOSURE_ABSOLUTE: -+ return ov5693_get_exposure(ov5693, &ctrl->val); -+ case V4L2_CID_AUTOGAIN: -+ return ov5693_get_gain(ov5693, &ctrl->val); -+ default: -+ return -EINVAL; -+ } -+} -+ -+static const struct v4l2_ctrl_ops ov5693_ctrl_ops = { -+ .s_ctrl = ov5693_s_ctrl, -+ .g_volatile_ctrl = ov5693_g_volatile_ctrl -+}; -+ -+/* System Control Functions */ -+ -+static int ov5693_mode_configure(struct ov5693_device *ov5693) -+{ -+ const struct ov5693_mode *mode = &ov5693->mode; -+ int ret = 0; -+ -+ /* Crop Start X */ -+ ov5693_write_reg(ov5693, OV5693_CROP_START_X_H_REG, -+ OV5693_CROP_START_X_H(mode->crop.left), &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_START_X_L_REG, -+ OV5693_CROP_START_X_L(mode->crop.left), &ret); -+ -+ /* Offset X */ -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_H_REG, -+ OV5693_OFFSET_START_X_H(0), &ret); -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_L_REG, -+ OV5693_OFFSET_START_X_L(0), &ret); -+ -+ /* Output Size X */ -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_H_REG, -+ OV5693_OUTPUT_SIZE_X_H(mode->format.width), &ret); -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_L_REG, -+ OV5693_OUTPUT_SIZE_X_L(mode->format.width), &ret); -+ -+ /* Crop End X */ -+ ov5693_write_reg(ov5693, OV5693_CROP_END_X_H_REG, -+ OV5693_CROP_END_X_H(mode->crop.left + mode->crop.width), -+ &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_END_X_L_REG, -+ OV5693_CROP_END_X_L(mode->crop.left + mode->crop.width), -+ &ret); -+ -+ /* Horizontal Total Size */ -+ ov5693_write_reg(ov5693, OV5693_TIMING_HTS_H_REG, -+ OV5693_TIMING_HTS_H(OV5693_FIXED_PPL), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_HTS_L_REG, -+ OV5693_TIMING_HTS_L(OV5693_FIXED_PPL), &ret); -+ -+ /* Crop Start Y */ -+ ov5693_write_reg(ov5693, OV5693_CROP_START_Y_H_REG, -+ OV5693_CROP_START_Y_H(mode->crop.top), &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_START_Y_L_REG, -+ OV5693_CROP_START_Y_L(mode->crop.top), &ret); -+ -+ /* Offset Y */ -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_H_REG, -+ OV5693_OFFSET_START_Y_H(0), &ret); -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_L_REG, -+ OV5693_OFFSET_START_Y_L(0), &ret); -+ -+ /* Output Size Y */ -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_H_REG, -+ OV5693_OUTPUT_SIZE_Y_H(mode->format.height), &ret); -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_L_REG, -+ OV5693_OUTPUT_SIZE_Y_L(mode->format.height), &ret); -+ -+ /* Crop End Y */ -+ ov5693_write_reg(ov5693, OV5693_CROP_END_Y_H_REG, -+ OV5693_CROP_END_Y_H(mode->crop.top + mode->crop.height), -+ &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_END_Y_L_REG, -+ OV5693_CROP_END_Y_L(mode->crop.top + mode->crop.height), -+ &ret); -+ -+ /* Vertical Total Size */ -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG, -+ OV5693_TIMING_VTS_H(mode->vts), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG, -+ OV5693_TIMING_VTS_L(mode->vts), &ret); -+ -+ /* Subsample X increase */ -+ ov5693_write_reg(ov5693, OV5693_SUB_INC_X_REG, -+ ((mode->inc_x_odd << 4) & 0xf0) | 0x01, &ret); -+ /* Subsample Y increase */ -+ ov5693_write_reg(ov5693, OV5693_SUB_INC_Y_REG, -+ ((mode->inc_y_odd << 4) & 0xf0) | 0x01, &ret); -+ -+ /* Binning */ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, -+ OV5693_FORMAT1_VBIN_EN, -+ mode->binning_y ? OV5693_FORMAT1_VBIN_EN : 0); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, -+ OV5693_FORMAT2_HBIN_EN, -+ mode->binning_x ? OV5693_FORMAT2_HBIN_EN : 0); -+ -+ return ret; -+} -+ -+static int ov5693_sw_standby(struct ov5693_device *ov5693, bool standby) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_SW_STREAM_REG, -+ standby ? OV5693_STOP_STREAMING : OV5693_START_STREAMING, -+ &ret); -+ -+ return ret; -+} -+ -+static int ov5693_sw_reset(struct ov5693_device *ov5693) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_SW_RESET_REG, OV5693_SW_RESET, &ret); -+ -+ return ret; -+} -+ -+static int ov5693_sensor_init(struct ov5693_device *ov5693) -+{ -+ int ret = 0; -+ -+ ret = ov5693_sw_reset(ov5693); -+ if (ret) { -+ dev_err(ov5693->dev, "%s software reset error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg_array(ov5693, &ov5693_global_setting); -+ if (ret) { -+ dev_err(ov5693->dev, "%s global settings error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_mode_configure(ov5693); -+ if (ret) { -+ dev_err(ov5693->dev, "%s mode configure error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_sw_standby(ov5693, true); -+ if (ret) -+ dev_err(ov5693->dev, "%s software standby error\n", __func__); -+ -+ return ret; -+} -+ -+static void ov5693_sensor_powerdown(struct ov5693_device *ov5693) -+{ -+ gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); -+ -+ regulator_bulk_disable(OV5693_NUM_SUPPLIES, ov5693->supplies); -+ -+ clk_disable_unprepare(ov5693->clk); -+} -+ -+static int ov5693_sensor_powerup(struct ov5693_device *ov5693) -+{ -+ int ret; -+ -+ gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); -+ -+ ret = clk_prepare_enable(ov5693->clk); -+ if (ret) { -+ dev_err(ov5693->dev, "Failed to enable clk\n"); -+ goto fail_power; -+ } -+ -+ ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, ov5693->supplies); -+ if (ret) { -+ dev_err(ov5693->dev, "Failed to enable regulators\n"); -+ goto fail_power; -+ } -+ -+ gpiod_set_value_cansleep(ov5693->powerdown, 0); -+ gpiod_set_value_cansleep(ov5693->reset, 0); -+ -+ usleep_range(5000, 7500); -+ -+ return 0; -+ -+fail_power: -+ ov5693_sensor_powerdown(ov5693); -+ return ret; -+} -+ -+static int __maybe_unused ov5693_sensor_suspend(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ ov5693_sensor_powerdown(ov5693); -+ -+ return 0; -+} -+ -+static int __maybe_unused ov5693_sensor_resume(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ int ret; -+ -+ mutex_lock(&ov5693->lock); -+ -+ ret = ov5693_sensor_powerup(ov5693); -+ if (ret) -+ goto out_unlock; -+ -+ ret = ov5693_sensor_init(ov5693); -+ if (ret) { -+ dev_err(dev, "ov5693 sensor init failure\n"); -+ goto err_power; -+ } -+ -+ goto out_unlock; -+ -+err_power: -+ ov5693_sensor_powerdown(ov5693); -+out_unlock: -+ mutex_unlock(&ov5693->lock); -+ return ret; -+} -+ -+static int ov5693_detect(struct ov5693_device *ov5693) -+{ -+ u8 id_l = 0, id_h = 0; -+ u16 id = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_H, &id_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_L, &id_l); -+ if (ret) -+ return ret; -+ -+ id = (id_h << 8) | id_l; -+ -+ if (id != OV5693_CHIP_ID) { -+ dev_err(ov5693->dev, "sensor ID mismatch. Found 0x%04x\n", id); -+ return -ENODEV; -+ } -+ -+ return 0; -+} -+ -+/* V4L2 Framework callbacks */ -+ -+static unsigned int __ov5693_calc_vts(u32 height) -+{ -+ /* -+ * We need to set a sensible default VTS for whatever format height we -+ * happen to be given from set_fmt(). This function just targets -+ * an even multiple of 30fps. -+ */ -+ -+ unsigned int tgt_fps; -+ -+ tgt_fps = rounddown(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / height, 30); -+ -+ return ALIGN_DOWN(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / tgt_fps, 2); -+} -+ -+static struct v4l2_mbus_framefmt * -+__ov5693_get_pad_format(struct ov5693_device *ov5693, -+ struct v4l2_subdev_pad_config *cfg, -+ unsigned int pad, enum v4l2_subdev_format_whence which) -+{ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ return v4l2_subdev_get_try_format(&ov5693->sd, cfg, pad); -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ return &ov5693->mode.format; -+ default: -+ return NULL; -+ } -+} -+ -+static struct v4l2_rect * -+__ov5693_get_pad_crop(struct ov5693_device *ov5693, -+ struct v4l2_subdev_pad_config *cfg, -+ unsigned int pad, enum v4l2_subdev_format_whence which) -+{ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ return v4l2_subdev_get_try_crop(&ov5693->sd, cfg, pad); -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ return &ov5693->mode.crop; -+ } -+ -+ return NULL; -+} -+ -+static int ov5693_get_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_format *format) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ format->format = ov5693->mode.format; -+ -+ return 0; -+} -+ -+static int ov5693_set_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_format *format) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ const struct v4l2_rect *crop; -+ struct v4l2_mbus_framefmt *fmt; -+ unsigned int hratio, vratio; -+ unsigned int width, height; -+ unsigned int hblank; -+ int exposure_max; -+ int ret = 0; -+ -+ crop = __ov5693_get_pad_crop(ov5693, cfg, format->pad, format->which); -+ -+ /* -+ * Align to two to simplify the binning calculations below, and clamp -+ * the requested format at the crop rectangle -+ */ -+ width = clamp_t(unsigned int, ALIGN(format->format.width, 2), -+ OV5693_MIN_CROP_WIDTH, crop->width); -+ height = clamp_t(unsigned int, ALIGN(format->format.height, 2), -+ OV5693_MIN_CROP_HEIGHT, crop->height); -+ -+ /* -+ * We can only support setting either the dimensions of the crop rect -+ * or those dimensions binned (separately) by a factor of two. -+ */ -+ hratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->width, width), 1, 2); -+ vratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->height, height), 1, 2); -+ -+ fmt = __ov5693_get_pad_format(ov5693, cfg, format->pad, format->which); -+ -+ fmt->width = crop->width / hratio; -+ fmt->height = crop->height / vratio; -+ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ -+ format->format = *fmt; -+ -+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) -+ return ret; -+ -+ mutex_lock(&ov5693->lock); -+ -+ ov5693->mode.binning_x = hratio > 1 ? true : false; -+ ov5693->mode.inc_x_odd = hratio > 1 ? 3 : 1; -+ ov5693->mode.binning_y = vratio > 1 ? true : false; -+ ov5693->mode.inc_y_odd = vratio > 1 ? 3 : 1; -+ -+ ov5693->mode.vts = __ov5693_calc_vts(fmt->height); -+ -+ __v4l2_ctrl_modify_range(ov5693->ctrls.vblank, -+ OV5693_TIMING_MIN_VTS, -+ OV5693_TIMING_MAX_VTS - fmt->height, -+ 1, ov5693->mode.vts - fmt->height); -+ __v4l2_ctrl_s_ctrl(ov5693->ctrls.vblank, -+ ov5693->mode.vts - fmt->height); -+ -+ hblank = OV5693_FIXED_PPL - fmt->width; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.hblank, hblank, hblank, 1, -+ hblank); -+ -+ exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, -+ ov5693->ctrls.exposure->minimum, exposure_max, -+ ov5693->ctrls.exposure->step, -+ min(ov5693->ctrls.exposure->val, exposure_max)); -+ -+ mutex_unlock(&ov5693->lock); -+ return ret; -+} -+ -+static int ov5693_get_selection(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ switch (sel->target) { -+ case V4L2_SEL_TGT_CROP: -+ mutex_lock(&ov5693->lock); -+ sel->r = *__ov5693_get_pad_crop(ov5693, cfg, sel->pad, -+ sel->which); -+ mutex_unlock(&ov5693->lock); -+ break; -+ case V4L2_SEL_TGT_NATIVE_SIZE: -+ sel->r.top = 0; -+ sel->r.left = 0; -+ sel->r.width = OV5693_NATIVE_WIDTH; -+ sel->r.height = OV5693_NATIVE_HEIGHT; -+ break; -+ case V4L2_SEL_TGT_CROP_BOUNDS: -+ case V4L2_SEL_TGT_CROP_DEFAULT: -+ sel->r.top = OV5693_ACTIVE_START_TOP; -+ sel->r.left = OV5693_ACTIVE_START_LEFT; -+ sel->r.width = OV5693_ACTIVE_WIDTH; -+ sel->r.height = OV5693_ACTIVE_HEIGHT; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int ov5693_set_selection(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(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 boundaries of the crop rectangle to the size of the sensor -+ * pixel array. Align to multiples of 2 to ensure Bayer pattern isn't -+ * disrupted. -+ */ -+ rect.left = clamp(ALIGN(sel->r.left, 2), OV5693_NATIVE_START_LEFT, -+ OV5693_NATIVE_WIDTH); -+ rect.top = clamp(ALIGN(sel->r.top, 2), OV5693_NATIVE_START_TOP, -+ OV5693_NATIVE_HEIGHT); -+ rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), -+ OV5693_MIN_CROP_WIDTH, OV5693_NATIVE_WIDTH); -+ rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), -+ OV5693_MIN_CROP_HEIGHT, OV5693_NATIVE_HEIGHT); -+ -+ /* Make sure the crop rectangle isn't outside the bounds of the array */ -+ rect.width = min_t(unsigned int, rect.width, -+ OV5693_NATIVE_WIDTH - rect.left); -+ rect.height = min_t(unsigned int, rect.height, -+ OV5693_NATIVE_HEIGHT - rect.top); -+ -+ __crop = __ov5693_get_pad_crop(ov5693, cfg, sel->pad, sel->which); -+ -+ if (rect.width != __crop->width || rect.height != __crop->height) { -+ /* -+ * Reset the output image size if the crop rectangle size has -+ * been modified. -+ */ -+ format = __ov5693_get_pad_format(ov5693, cfg, sel->pad, sel->which); -+ format->width = rect.width; -+ format->height = rect.height; -+ } -+ -+ *__crop = rect; -+ sel->r = rect; -+ -+ return 0; -+} -+ -+static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ int ret; -+ -+ if (enable) { -+ ret = pm_runtime_get_sync(ov5693->dev); -+ if (ret < 0) -+ goto err_power_down; -+ -+ ret = __v4l2_ctrl_handler_setup(&ov5693->ctrls.handler); -+ if (ret) -+ goto err_power_down; -+ } -+ -+ mutex_lock(&ov5693->lock); -+ ret = ov5693_sw_standby(ov5693, !enable); -+ mutex_unlock(&ov5693->lock); -+ -+ if (ret) -+ goto err_power_down; -+ ov5693->streaming = !!enable; -+ -+ if (!enable) -+ pm_runtime_put(ov5693->dev); -+ -+ return 0; -+err_power_down: -+ pm_runtime_put_noidle(ov5693->dev); -+ return ret; -+} -+ -+static int ov5693_g_frame_interval(struct v4l2_subdev *sd, -+ struct v4l2_subdev_frame_interval *interval) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ unsigned int framesize = OV5693_FIXED_PPL * (ov5693->mode.format.height + -+ ov5693->ctrls.vblank->val); -+ unsigned int fps = DIV_ROUND_CLOSEST(OV5693_PIXEL_RATE, framesize); -+ -+ interval->interval.numerator = 1; -+ interval->interval.denominator = fps; -+ -+ return 0; -+} -+ -+static int ov5693_enum_mbus_code(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_mbus_code_enum *code) -+{ -+ /* Only a single mbus format is supported */ -+ if (code->index > 0) -+ return -EINVAL; -+ -+ code->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ return 0; -+} -+ -+static int ov5693_enum_frame_size(struct v4l2_subdev *sd, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_frame_size_enum *fse) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ struct v4l2_rect *__crop; -+ -+ if (fse->index > 1 || fse->code != MEDIA_BUS_FMT_SBGGR10_1X10) -+ return -EINVAL; -+ -+ __crop = __ov5693_get_pad_crop(ov5693, cfg, fse->pad, fse->which); -+ if (!__crop) -+ return -EINVAL; -+ -+ fse->min_width = __crop->width / (fse->index + 1); -+ fse->min_height = __crop->height / (fse->index + 1); -+ fse->max_width = fse->min_width; -+ fse->max_height = fse->min_height; -+ -+ return 0; -+} -+ -+static const struct v4l2_subdev_video_ops ov5693_video_ops = { -+ .s_stream = ov5693_s_stream, -+ .g_frame_interval = ov5693_g_frame_interval, -+}; -+ -+static const struct v4l2_subdev_pad_ops ov5693_pad_ops = { -+ .enum_mbus_code = ov5693_enum_mbus_code, -+ .enum_frame_size = ov5693_enum_frame_size, -+ .get_fmt = ov5693_get_fmt, -+ .set_fmt = ov5693_set_fmt, -+ .get_selection = ov5693_get_selection, -+ .set_selection = ov5693_set_selection, -+}; -+ -+static const struct v4l2_subdev_ops ov5693_ops = { -+ .video = &ov5693_video_ops, -+ .pad = &ov5693_pad_ops, -+}; -+ -+/* Sensor and Driver Configuration Functions */ -+ -+static int ov5693_init_controls(struct ov5693_device *ov5693) -+{ -+ const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; -+ struct v4l2_fwnode_device_properties props; -+ int vblank_max, vblank_def; -+ int exposure_max; -+ int hblank; -+ int ret; -+ -+ ret = v4l2_ctrl_handler_init(&ov5693->ctrls.handler, 12); -+ if (ret) -+ return ret; -+ -+ /* link freq */ -+ ov5693->ctrls.link_freq = v4l2_ctrl_new_int_menu(&ov5693->ctrls.handler, -+ NULL, V4L2_CID_LINK_FREQ, -+ 0, 0, link_freq_menu_items); -+ if (ov5693->ctrls.link_freq) -+ ov5693->ctrls.link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ -+ /* pixel rate */ -+ ov5693->ctrls.pixel_rate = v4l2_ctrl_new_std(&ov5693->ctrls.handler, NULL, -+ V4L2_CID_PIXEL_RATE, 0, -+ OV5693_PIXEL_RATE, 1, -+ OV5693_PIXEL_RATE); -+ -+ /* Exposure */ -+ exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN; -+ ov5693->ctrls.exposure = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_EXPOSURE, -+ OV5693_EXPOSURE_MIN, -+ exposure_max, -+ OV5693_EXPOSURE_STEP, -+ exposure_max); -+ -+ /* Gain */ -+ ov5693->ctrls.analogue_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, -+ ops, V4L2_CID_ANALOGUE_GAIN, -+ OV5693_GAIN_MIN, -+ OV5693_GAIN_MAX, -+ OV5693_GAIN_STEP, -+ OV5693_GAIN_DEF); -+ -+ ov5693->ctrls.digital_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_DIGITAL_GAIN, -+ OV5693_DIGITAL_GAIN_MIN, -+ OV5693_DIGITAL_GAIN_MAX, -+ OV5693_DIGITAL_GAIN_STEP, -+ OV5693_DIGITAL_GAIN_DEF); -+ -+ /* Flip */ -+ ov5693->ctrls.hflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_HFLIP, 0, 1, 1, 0); -+ -+ ov5693->ctrls.vflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_VFLIP, 0, 1, 1, 0); -+ -+ hblank = OV5693_FIXED_PPL - ov5693->mode.format.width; -+ ov5693->ctrls.hblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_HBLANK, hblank, -+ hblank, 1, hblank); -+ -+ if (ov5693->ctrls.hblank) -+ ov5693->ctrls.hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ -+ vblank_max = OV5693_TIMING_MAX_VTS - ov5693->mode.format.height; -+ vblank_def = ov5693->mode.vts - ov5693->mode.format.height; -+ ov5693->ctrls.vblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_VBLANK, -+ OV5693_TIMING_MIN_VTS, -+ vblank_max, 1, vblank_def); -+ -+ ov5693->ctrls.test_pattern = v4l2_ctrl_new_std_menu_items( -+ &ov5693->ctrls.handler, ops, -+ V4L2_CID_TEST_PATTERN, -+ ARRAY_SIZE(ov5693_test_pattern_menu) - 1, -+ 0, 0, ov5693_test_pattern_menu); -+ -+ if (ov5693->ctrls.handler.error) { -+ dev_err(ov5693->dev, "Error initialising v4l2 ctrls\n"); -+ ret = ov5693->ctrls.handler.error; -+ goto err_free_handler; -+ } -+ -+ /* set properties from fwnode (e.g. rotation, orientation) */ -+ ret = v4l2_fwnode_device_parse(ov5693->dev, &props); -+ if (ret) -+ goto err_free_handler; -+ -+ ret = v4l2_ctrl_new_fwnode_properties(&ov5693->ctrls.handler, ops, -+ &props); -+ if (ret) -+ goto err_free_handler; -+ -+ /* Use same lock for controls as for everything else. */ -+ ov5693->ctrls.handler.lock = &ov5693->lock; -+ ov5693->sd.ctrl_handler = &ov5693->ctrls.handler; -+ -+ return 0; -+ -+err_free_handler: -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ return ret; -+} -+ -+static int ov5693_configure_gpios(struct ov5693_device *ov5693) -+{ -+ ov5693->reset = devm_gpiod_get_optional(ov5693->dev, "reset", -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->reset)) { -+ dev_err(ov5693->dev, "Error fetching reset GPIO\n"); -+ return PTR_ERR(ov5693->reset); -+ } -+ -+ ov5693->powerdown = devm_gpiod_get_optional(ov5693->dev, "powerdown", -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->powerdown)) { -+ dev_err(ov5693->dev, "Error fetching powerdown GPIO\n"); -+ return PTR_ERR(ov5693->powerdown); -+ } -+ -+ return 0; -+} -+ -+static int ov5693_get_regulators(struct ov5693_device *ov5693) -+{ -+ unsigned int i; -+ -+ for (i = 0; i < OV5693_NUM_SUPPLIES; i++) -+ ov5693->supplies[i].supply = ov5693_supply_names[i]; -+ -+ return devm_regulator_bulk_get(ov5693->dev, OV5693_NUM_SUPPLIES, -+ ov5693->supplies); -+} -+ -+static int ov5693_probe(struct i2c_client *client) -+{ -+ struct fwnode_handle *fwnode = dev_fwnode(&client->dev); -+ struct fwnode_handle *endpoint; -+ struct ov5693_device *ov5693; -+ u32 clk_rate; -+ int ret = 0; -+ -+ endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); -+ if (!endpoint && !IS_ERR_OR_NULL(fwnode->secondary)) -+ endpoint = fwnode_graph_get_next_endpoint(fwnode->secondary, NULL); -+ if (!endpoint) -+ return -EPROBE_DEFER; -+ -+ ov5693 = devm_kzalloc(&client->dev, sizeof(*ov5693), GFP_KERNEL); -+ if (!ov5693) -+ return -ENOMEM; -+ -+ ov5693->client = client; -+ ov5693->dev = &client->dev; -+ -+ mutex_init(&ov5693->lock); -+ -+ v4l2_i2c_subdev_init(&ov5693->sd, client, &ov5693_ops); -+ -+ ov5693->clk = devm_clk_get(&client->dev, "xvclk"); -+ if (IS_ERR(ov5693->clk)) { -+ dev_err(&client->dev, "Error getting clock\n"); -+ return PTR_ERR(ov5693->clk); -+ } -+ -+ clk_rate = clk_get_rate(ov5693->clk); -+ if (clk_rate != OV5693_XVCLK_FREQ) { -+ dev_err(&client->dev, "Unsupported clk freq %u, expected %u\n", -+ clk_rate, OV5693_XVCLK_FREQ); -+ return -EINVAL; -+ } -+ -+ ret = ov5693_configure_gpios(ov5693); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_get_regulators(ov5693); -+ if (ret) { -+ dev_err(&client->dev, "Error fetching regulators\n"); -+ return ret; -+ } -+ -+ ov5693->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; -+ ov5693->pad.flags = MEDIA_PAD_FL_SOURCE; -+ ov5693->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; -+ -+ ov5693->mode.crop = ov5693_default_crop; -+ ov5693->mode.format = ov5693_default_fmt; -+ ov5693->mode.vts = __ov5693_calc_vts(ov5693->mode.format.height); -+ -+ ret = ov5693_init_controls(ov5693); -+ if (ret) -+ return ret; -+ -+ ret = media_entity_pads_init(&ov5693->sd.entity, 1, &ov5693->pad); -+ if (ret) -+ goto err_ctrl_handler_free; -+ -+ /* -+ * We need the driver to work in the event that pm runtime is disable in -+ * the kernel, so power up and verify the chip now. In the event that -+ * runtime pm is disabled this will leave the chip on, so that streaming -+ * will work. -+ */ -+ -+ ret = ov5693_sensor_powerup(ov5693); -+ if (ret) -+ goto err_media_entity_cleanup; -+ -+ ret = ov5693_detect(ov5693); -+ if (ret) -+ goto err_powerdown; -+ -+ pm_runtime_set_active(&client->dev); -+ pm_runtime_get_noresume(&client->dev); -+ pm_runtime_enable(&client->dev); -+ -+ ret = v4l2_async_register_subdev_sensor(&ov5693->sd); -+ if (ret) { -+ dev_err(&client->dev, "failed to register V4L2 subdev: %d", -+ ret); -+ goto err_pm_runtime; -+ } -+ -+ pm_runtime_set_autosuspend_delay(&client->dev, 1000); -+ pm_runtime_use_autosuspend(&client->dev); -+ pm_runtime_put_autosuspend(&client->dev); -+ -+ return ret; -+ -+err_pm_runtime: -+ pm_runtime_disable(&client->dev); -+ pm_runtime_put_noidle(&client->dev); -+err_powerdown: -+ ov5693_sensor_powerdown(ov5693); -+err_media_entity_cleanup: -+ media_entity_cleanup(&ov5693->sd.entity); -+err_ctrl_handler_free: -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ -+ return ret; -+} -+ -+static int ov5693_remove(struct i2c_client *client) -+{ -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ v4l2_async_unregister_subdev(sd); -+ media_entity_cleanup(&ov5693->sd.entity); -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ mutex_destroy(&ov5693->lock); -+ -+ /* -+ * Disable runtime PM. In case runtime PM is disabled in the kernel, -+ * make sure to turn power off manually. -+ */ -+ pm_runtime_disable(&client->dev); -+ if (!pm_runtime_status_suspended(&client->dev)) -+ ov5693_sensor_powerdown(ov5693); -+ pm_runtime_set_suspended(&client->dev); -+ -+ return 0; -+} -+ -+static const struct dev_pm_ops ov5693_pm_ops = { -+ SET_RUNTIME_PM_OPS(ov5693_sensor_suspend, ov5693_sensor_resume, NULL) -+}; -+ -+static const struct acpi_device_id ov5693_acpi_match[] = { -+ {"INT33BE"}, -+ {}, -+}; -+MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); -+ -+static struct i2c_driver ov5693_driver = { -+ .driver = { -+ .name = "ov5693", -+ .acpi_match_table = ov5693_acpi_match, -+ .pm = &ov5693_pm_ops, -+ }, -+ .probe_new = ov5693_probe, -+ .remove = ov5693_remove, -+}; -+module_i2c_driver(ov5693_driver); -+ -+MODULE_DESCRIPTION("A low-level driver for OmniVision 5693 sensors"); -+MODULE_LICENSE("GPL"); --- -2.33.0 - -From bbaa83538fbcfaf3d26a03019a2fad4ffe6243d8 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Fri, 22 Jan 2021 20:58:13 +0100 -Subject: [PATCH] cio2-bridge: Parse sensor orientation and rotation -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -The sensor orientation is read from the _PLC ACPI buffer and converted -to a v4l2 format. - -See https://uefi.org/sites/default/files/resources/ACPI_6_3_final_Jan30.pdf -page 351 for a definition of the Panel property. - -The sensor rotation is read from the SSDB ACPI buffer and converted into -degrees. - -Signed-off-by: Fabian Wüthrich -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 45 ++++++++++++++++++++-- - drivers/media/pci/intel/ipu3/cio2-bridge.h | 3 ++ - 2 files changed, 44 insertions(+), 4 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index 59a36f922675..d4a48cec2644 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -29,6 +29,7 @@ static const struct cio2_sensor_config cio2_supported_sensors[] = { - static const struct cio2_property_names prop_names = { - .clock_frequency = "clock-frequency", - .rotation = "rotation", -+ .orientation = "orientation", - .bus_type = "bus-type", - .data_lanes = "data-lanes", - .remote_endpoint = "remote-endpoint", -@@ -72,11 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, - return ret; - } - -+static u32 cio2_bridge_parse_rotation(u8 rotation) -+{ -+ if (rotation == 1) -+ return 180; -+ return 0; -+} -+ -+static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) -+{ -+ switch (panel) { -+ case 4: -+ return V4L2_FWNODE_ORIENTATION_FRONT; -+ case 5: -+ return V4L2_FWNODE_ORIENTATION_BACK; -+ default: -+ return V4L2_FWNODE_ORIENTATION_EXTERNAL; -+ } -+} -+ - static void cio2_bridge_create_fwnode_properties( - struct cio2_sensor *sensor, - struct cio2_bridge *bridge, - const struct cio2_sensor_config *cfg) - { -+ u32 rotation; -+ enum v4l2_fwnode_orientation orientation; -+ -+ rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); -+ orientation = cio2_bridge_parse_orientation(sensor->pld->panel); -+ - sensor->prop_names = prop_names; - - sensor->local_ref[0] = SOFTWARE_NODE_REFERENCE(&sensor->swnodes[SWNODE_CIO2_ENDPOINT]); -@@ -85,9 +111,12 @@ static void cio2_bridge_create_fwnode_properties( - sensor->dev_properties[0] = PROPERTY_ENTRY_U32( - sensor->prop_names.clock_frequency, - sensor->ssdb.mclkspeed); -- sensor->dev_properties[1] = PROPERTY_ENTRY_U8( -+ sensor->dev_properties[1] = PROPERTY_ENTRY_U32( - sensor->prop_names.rotation, -- sensor->ssdb.degree); -+ rotation); -+ sensor->dev_properties[2] = PROPERTY_ENTRY_U32( -+ sensor->prop_names.orientation, -+ orientation); - - sensor->ep_properties[0] = PROPERTY_ENTRY_U32( - sensor->prop_names.bus_type, -@@ -159,6 +188,7 @@ static void cio2_bridge_unregister_sensors(struct cio2_bridge *bridge) - for (i = 0; i < bridge->n_sensors; i++) { - sensor = &bridge->sensors[i]; - software_node_unregister_nodes(sensor->swnodes); -+ ACPI_FREE(sensor->pld); - acpi_dev_put(sensor->adev); - } - } -@@ -170,6 +200,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - struct fwnode_handle *fwnode; - struct cio2_sensor *sensor; - struct acpi_device *adev; -+ acpi_status status; - int ret; - - for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { -@@ -191,11 +222,15 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - if (ret) - goto err_put_adev; - -+ status = acpi_get_physical_device_location(adev->handle, &sensor->pld); -+ if (ACPI_FAILURE(status)) -+ goto err_put_adev; -+ - if (sensor->ssdb.lanes > CIO2_MAX_LANES) { - dev_err(&adev->dev, - "Number of lanes in SSDB is invalid\n"); - ret = -EINVAL; -- goto err_put_adev; -+ goto err_free_pld; - } - - cio2_bridge_create_fwnode_properties(sensor, bridge, cfg); -@@ -203,7 +238,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - - ret = software_node_register_nodes(sensor->swnodes); - if (ret) -- goto err_put_adev; -+ goto err_free_pld; - - fwnode = software_node_fwnode(&sensor->swnodes[ - SWNODE_SENSOR_HID]); -@@ -225,6 +260,8 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - - err_free_swnodes: - software_node_unregister_nodes(sensor->swnodes); -+err_free_pld: -+ ACPI_FREE(sensor->pld); - err_put_adev: - acpi_dev_put(sensor->adev); - return ret; -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index dd0ffcafa489..924d99d20328 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -80,6 +80,7 @@ struct cio2_sensor_ssdb { - struct cio2_property_names { - char clock_frequency[16]; - char rotation[9]; -+ char orientation[12]; - char bus_type[9]; - char data_lanes[11]; - char remote_endpoint[16]; -@@ -106,6 +107,8 @@ struct cio2_sensor { - struct cio2_node_names node_names; - - struct cio2_sensor_ssdb ssdb; -+ struct acpi_pld_info *pld; -+ - struct cio2_property_names prop_names; - struct property_entry ep_properties[5]; - struct property_entry dev_properties[3]; --- -2.33.0 - -From 2daaab808ea446cbfee2022b1b94ac20cbeaf466 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Sun, 24 Jan 2021 11:07:42 +0100 -Subject: [PATCH] cio2-bridge: Use macros and add warnings -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Use macros for the _PLD panel as defined in the ACPI spec 6.3 and emit -a warning if we see an unknown value. - -Signed-off-by: Fabian Wüthrich -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 33 ++++++++++++++++------ - drivers/media/pci/intel/ipu3/cio2-bridge.h | 13 +++++++++ - 2 files changed, 37 insertions(+), 9 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index d4a48cec2644..1052885caa25 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -73,21 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, - return ret; - } - --static u32 cio2_bridge_parse_rotation(u8 rotation) -+static u32 cio2_bridge_parse_rotation(struct cio2_sensor *sensor) - { -- if (rotation == 1) -+ switch (sensor->ssdb.degree) { -+ case CIO2_SENSOR_ROTATION_NORMAL: -+ return 0; -+ case CIO2_SENSOR_ROTATION_INVERTED: - return 180; -- return 0; -+ default: -+ dev_warn(&sensor->adev->dev, -+ "Unknown rotation %d. Assume 0 degree rotation\n", -+ sensor->ssdb.degree); -+ return 0; -+ } - } - --static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) -+static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(struct cio2_sensor *sensor) - { -- switch (panel) { -- case 4: -+ switch (sensor->pld->panel) { -+ case CIO2_PLD_PANEL_FRONT: - return V4L2_FWNODE_ORIENTATION_FRONT; -- case 5: -+ case CIO2_PLD_PANEL_BACK: - return V4L2_FWNODE_ORIENTATION_BACK; -+ case CIO2_PLD_PANEL_TOP: -+ case CIO2_PLD_PANEL_LEFT: -+ case CIO2_PLD_PANEL_RIGHT: -+ case CIO2_PLD_PANEL_UNKNOWN: -+ return V4L2_FWNODE_ORIENTATION_EXTERNAL; - default: -+ dev_warn(&sensor->adev->dev, "Unknown _PLD panel value %d\n", -+ sensor->pld->panel); - return V4L2_FWNODE_ORIENTATION_EXTERNAL; - } - } -@@ -100,8 +115,8 @@ static void cio2_bridge_create_fwnode_properties( - u32 rotation; - enum v4l2_fwnode_orientation orientation; - -- rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); -- orientation = cio2_bridge_parse_orientation(sensor->pld->panel); -+ rotation = cio2_bridge_parse_rotation(sensor); -+ orientation = cio2_bridge_parse_orientation(sensor); - - sensor->prop_names = prop_names; - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index 924d99d20328..e1e388cc9f45 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -12,6 +12,19 @@ - #define CIO2_MAX_LANES 4 - #define MAX_NUM_LINK_FREQS 3 - -+/* Values are estimated guesses as we don't have a spec */ -+#define CIO2_SENSOR_ROTATION_NORMAL 0 -+#define CIO2_SENSOR_ROTATION_INVERTED 1 -+ -+/* Panel position defined in _PLD section of ACPI Specification 6.3 */ -+#define CIO2_PLD_PANEL_TOP 0 -+#define CIO2_PLD_PANEL_BOTTOM 1 -+#define CIO2_PLD_PANEL_LEFT 2 -+#define CIO2_PLD_PANEL_RIGHT 3 -+#define CIO2_PLD_PANEL_FRONT 4 -+#define CIO2_PLD_PANEL_BACK 5 -+#define CIO2_PLD_PANEL_UNKNOWN 6 -+ - #define CIO2_SENSOR_CONFIG(_HID, _NR, ...) \ - (const struct cio2_sensor_config) { \ - .hid = _HID, \ --- -2.33.0 - -From c5d21ba34347b8960c0450e92622177869a6ae37 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Thu, 6 May 2021 07:52:44 +0200 -Subject: [PATCH] cio2-bridge: Use correct dev_properties size - -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index e1e388cc9f45..deaf5804f70d 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -124,7 +124,7 @@ struct cio2_sensor { - - struct cio2_property_names prop_names; - struct property_entry ep_properties[5]; -- struct property_entry dev_properties[3]; -+ struct property_entry dev_properties[4]; - struct property_entry cio2_properties[3]; - struct software_node_ref_args local_ref[1]; - struct software_node_ref_args remote_ref[1]; --- -2.33.0 - -From b8b2e8e1b91d360c294c0b9260b249eb90be898e Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 20 May 2021 23:31:04 +0100 -Subject: [PATCH] media: i2c: Fix vertical flip in ov5693 - -The pinkness experienced by users with rotated sensors in their laptops -was due to an incorrect setting for the vertical flip function; the -datasheet for the sensor gives the settings as bits 1&2 in one place and -bits 1&6 in another. - -Switch to flipping bit 6 instead of bit 2 for 0x3820 in the vertical -flip function to fix the pink hue. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 276f625d4d23..1653fb49f6e0 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -133,7 +133,7 @@ - #define OV5693_SUB_INC_Y_REG 0x3815 - - #define OV5693_FORMAT1_REG 0x3820 --#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -+#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(6) - #define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) - #define OV5693_FORMAT1_VBIN_EN BIT(0) - #define OV5693_FORMAT2_REG 0x3821 --- -2.33.0 - -From 8898454c0454f212a0aecef0b880cf51c5b0c9b1 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 9 Jul 2021 16:39:18 +0100 -Subject: [PATCH] media: i2c: Add ACPI support to ov8865 - -The ov8865 sensor is sometimes found on x86 platforms enumerated via ACPI. -Add an ACPI match table to the driver so that it's probed on those -platforms. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 9ecf180635ee..a28adf45b1b1 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -5,6 +5,7 @@ - * Author: Paul Kocialkowski - */ - -+#include - #include - #include - #include -@@ -2948,6 +2949,12 @@ static const struct dev_pm_ops ov8865_pm_ops = { - SET_RUNTIME_PM_OPS(ov8865_suspend, ov8865_resume, NULL) - }; - -+static const struct acpi_device_id ov8865_acpi_match[] = { -+ {"INT347A"}, -+ {}, -+}; -+MODULE_DEVICE_TABLE(acpi, ov8865_acpi_match); -+ - static const struct of_device_id ov8865_of_match[] = { - { .compatible = "ovti,ov8865" }, - { } -@@ -2958,6 +2965,7 @@ static struct i2c_driver ov8865_driver = { - .driver = { - .name = "ov8865", - .of_match_table = ov8865_of_match, -+ .acpi_match_table = ov8865_acpi_match, - .pm = &ov8865_pm_ops, - }, - .probe_new = ov8865_probe, --- -2.33.0 - -From 9ca51e2ceb46d583c14f3aa3f048e2affe6bbcf7 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 21:20:17 +0100 -Subject: [PATCH] media: i2c: Fix incorrect value in comment - -The PLL configuration defined here sets 72MHz (which is correct), not -80MHz. Correct the comment. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index a28adf45b1b1..7d716b0d47c1 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -713,7 +713,7 @@ static const struct ov8865_pll2_config ov8865_pll2_config_native = { - /* - * EXTCLK = 24 MHz - * DAC_CLK = 360 MHz -- * SCLK = 80 MHz -+ * SCLK = 72 MHz - */ - - static const struct ov8865_pll2_config ov8865_pll2_config_binning = { --- -2.33.0 - -From 3b21a856d783be36d9f2ed2759e51c44163cae18 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:21:52 +0100 -Subject: [PATCH] media: i2c: Check fwnode->secondary for endpoint - -The ov8865 driver is one of those that can be connected to a CIO2 -device by the cio2-bridge code. This means that the absence of an -endpoint for this device is not necessarily fatal, as one might be -built by the cio2-bridge when it probes. Check fwnode->secondary for -an endpoint, and defer probing if one isn't found rather than fail. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 11 ++++++----- - 1 file changed, 6 insertions(+), 5 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 7d716b0d47c1..5fb290a6fc6a 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2781,6 +2781,7 @@ static int ov8865_resume(struct device *dev) - static int ov8865_probe(struct i2c_client *client) - { - struct device *dev = &client->dev; -+ struct fwnode_handle *fwnode = dev_fwnode(dev); - struct fwnode_handle *handle; - struct ov8865_sensor *sensor; - struct v4l2_subdev *subdev; -@@ -2797,11 +2798,11 @@ static int ov8865_probe(struct i2c_client *client) - - /* Graph Endpoint */ - -- handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); -- if (!handle) { -- dev_err(dev, "unable to find endpoint node\n"); -- return -EINVAL; -- } -+ handle = fwnode_graph_get_next_endpoint(fwnode, NULL); -+ if (!handle && !IS_ERR_OR_NULL(fwnode->secondary)) -+ handle = fwnode_graph_get_next_endpoint(fwnode->secondary, NULL); -+ if (!handle) -+ return -EPROBE_DEFER; - - sensor->endpoint.bus_type = V4L2_MBUS_CSI2_DPHY; - --- -2.33.0 - -From e43b667cb36e343789bf55168b4c6622642356df Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:00:25 +0100 -Subject: [PATCH] media: i2c: Support 19.2MHz input clock in ov8865 - -The ov8865 driver as written expects a 24MHz input clock, but the sensor -is sometimes found on x86 platforms with a 19.2MHz input clock supplied. -Add a set of PLL configurations to the driver to support that rate too. -As ACPI doesn't auto-configure the clock rate, check for a clock-frequency -during probe and set that rate if one is found. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 157 +++++++++++++++++++++++++++---------- - 1 file changed, 114 insertions(+), 43 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 5fb290a6fc6a..cae7dc9da49d 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -21,10 +21,6 @@ - #include - #include - --/* Clock rate */ -- --#define OV8865_EXTCLK_RATE 24000000 -- - /* Register definitions */ - - /* System */ -@@ -665,6 +661,9 @@ struct ov8865_sensor { - struct regulator *avdd; - struct regulator *dvdd; - struct regulator *dovdd; -+ -+ unsigned long extclk_rate; -+ unsigned int extclk_rate_idx; - struct clk *extclk; - - struct v4l2_fwnode_endpoint endpoint; -@@ -680,49 +679,83 @@ struct ov8865_sensor { - /* Static definitions */ - - /* -- * EXTCLK = 24 MHz - * PHY_SCLK = 720 MHz - * MIPI_PCLK = 90 MHz - */ --static const struct ov8865_pll1_config ov8865_pll1_config_native = { -- .pll_pre_div_half = 1, -- .pll_pre_div = 0, -- .pll_mul = 30, -- .m_div = 1, -- .mipi_div = 3, -- .pclk_div = 1, -- .sys_pre_div = 1, -- .sys_div = 2, -+ -+static const struct ov8865_pll1_config ov8865_pll1_configs_native[] = { -+ { /* 19.2 MHz input clock */ -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 2, -+ .pll_mul = 75, -+ .m_div = 1, -+ .mipi_div = 3, -+ .pclk_div = 1, -+ .sys_pre_div = 1, -+ .sys_div = 2, -+ }, -+ { /* 24MHz input clock */ -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 0, -+ .pll_mul = 30, -+ .m_div = 1, -+ .mipi_div = 3, -+ .pclk_div = 1, -+ .sys_pre_div = 1, -+ .sys_div = 2, -+ }, - }; - - /* -- * EXTCLK = 24 MHz - * DAC_CLK = 360 MHz - * SCLK = 144 MHz - */ - --static const struct ov8865_pll2_config ov8865_pll2_config_native = { -- .pll_pre_div_half = 1, -- .pll_pre_div = 0, -- .pll_mul = 30, -- .dac_div = 2, -- .sys_pre_div = 5, -- .sys_div = 0, -+static const struct ov8865_pll2_config ov8865_pll2_configs_native[] = { -+ /* 19.2MHz input clock */ -+ { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 5, -+ .pll_mul = 75, -+ .dac_div = 1, -+ .sys_pre_div = 1, -+ .sys_div = 3, -+ }, -+ /* 24MHz input clock */ -+ { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 0, -+ .pll_mul = 30, -+ .dac_div = 2, -+ .sys_pre_div = 5, -+ .sys_div = 0, -+ } - }; - - /* -- * EXTCLK = 24 MHz - * DAC_CLK = 360 MHz - * SCLK = 72 MHz - */ - --static const struct ov8865_pll2_config ov8865_pll2_config_binning = { -+static const struct ov8865_pll2_config ov8865_pll2_configs_binning[] = { -+ /* 19.2MHz input clock */ -+ { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 2, -+ .pll_mul = 75, -+ .dac_div = 2, -+ .sys_pre_div = 10, -+ .sys_div = 0, -+ }, -+ /* 24MHz input clock */ -+ { - .pll_pre_div_half = 1, - .pll_pre_div = 0, - .pll_mul = 30, - .dac_div = 2, - .sys_pre_div = 10, - .sys_div = 0, -+ } - }; - - static const struct ov8865_sclk_config ov8865_sclk_config_native = { -@@ -934,8 +967,8 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 30 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_native, -+ .pll1_config = ov8865_pll1_configs_native, -+ .pll2_config = ov8865_pll2_configs_native, - .sclk_config = &ov8865_sclk_config_native, - - /* Registers */ -@@ -990,8 +1023,8 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 30 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_native, -+ .pll1_config = ov8865_pll1_configs_native, -+ .pll2_config = ov8865_pll2_configs_native, - .sclk_config = &ov8865_sclk_config_native, - - /* Registers */ -@@ -1050,8 +1083,8 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 30 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_binning, -+ .pll1_config = ov8865_pll1_configs_native, -+ .pll2_config = ov8865_pll2_configs_binning, - .sclk_config = &ov8865_sclk_config_native, - - /* Registers */ -@@ -1116,8 +1149,8 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 90 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_binning, -+ .pll1_config = ov8865_pll1_configs_native, -+ .pll2_config = ov8865_pll2_configs_binning, - .sclk_config = &ov8865_sclk_config_native, - - /* Registers */ -@@ -1266,6 +1299,13 @@ static const struct ov8865_register_value ov8865_init_sequence[] = { - { 0x4503, 0x10 }, - }; - -+/* Clock rate */ -+ -+static const unsigned long supported_extclk_rates[] = { -+ 19200000, -+ 24000000, -+}; -+ - static const s64 ov8865_link_freq_menu[] = { - 360000000, - }; -@@ -1513,12 +1553,11 @@ static int ov8865_isp_configure(struct ov8865_sensor *sensor) - static unsigned long ov8865_mode_pll1_rate(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_pll1_config *config = mode->pll1_config; -- unsigned long extclk_rate; -+ const struct ov8865_pll1_config *config; - unsigned long pll1_rate; - -- extclk_rate = clk_get_rate(sensor->extclk); -- pll1_rate = extclk_rate * config->pll_mul / config->pll_pre_div_half; -+ config = &mode->pll1_config[sensor->extclk_rate_idx]; -+ pll1_rate = sensor->extclk_rate * config->pll_mul / config->pll_pre_div_half; - - switch (config->pll_pre_div) { - case 0: -@@ -1552,10 +1591,12 @@ static int ov8865_mode_pll1_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode, - u32 mbus_code) - { -- const struct ov8865_pll1_config *config = mode->pll1_config; -+ const struct ov8865_pll1_config *config; - u8 value; - int ret; - -+ config = &mode->pll1_config[sensor->extclk_rate_idx]; -+ - switch (mbus_code) { - case MEDIA_BUS_FMT_SBGGR10_1X10: - value = OV8865_MIPI_BIT_SEL(10); -@@ -1622,9 +1663,11 @@ static int ov8865_mode_pll1_configure(struct ov8865_sensor *sensor, - static int ov8865_mode_pll2_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_pll2_config *config = mode->pll2_config; -+ const struct ov8865_pll2_config *config; - int ret; - -+ config = &mode->pll2_config[sensor->extclk_rate_idx]; -+ - ret = ov8865_write(sensor, OV8865_PLL_CTRL12_REG, - OV8865_PLL_CTRL12_PRE_DIV_HALF(config->pll_pre_div_half) | - OV8865_PLL_CTRL12_DAC_DIV(config->dac_div)); -@@ -2053,9 +2096,11 @@ static int ov8865_mode_configure(struct ov8865_sensor *sensor, - static unsigned long ov8865_mode_mipi_clk_rate(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_pll1_config *config = mode->pll1_config; -+ const struct ov8865_pll1_config *config; - unsigned long pll1_rate; - -+ config = &mode->pll1_config[sensor->extclk_rate_idx]; -+ - pll1_rate = ov8865_mode_pll1_rate(sensor, mode); - - return pll1_rate / config->m_div / 2; -@@ -2786,7 +2831,8 @@ static int ov8865_probe(struct i2c_client *client) - struct ov8865_sensor *sensor; - struct v4l2_subdev *subdev; - struct media_pad *pad; -- unsigned long rate; -+ unsigned int rate; -+ unsigned int i; - int ret; - - sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); -@@ -2863,13 +2909,38 @@ static int ov8865_probe(struct i2c_client *client) - goto error_endpoint; - } - -- rate = clk_get_rate(sensor->extclk); -- if (rate != OV8865_EXTCLK_RATE) { -- dev_err(dev, "clock rate %lu Hz is unsupported\n", rate); -+ /* -+ * We could have either a 24MHz or 19.2MHz clock rate. Check for a -+ * clock-frequency property and if found, set that rate. This should -+ * cover ACPI case. If the system uses devicetree then the configured -+ * rate should already be set, so we'll have to check it. -+ */ -+ -+ ret = fwnode_property_read_u32(fwnode, "clock-frequency", &rate); -+ if (!ret) { -+ ret = clk_set_rate(sensor->extclk, rate); -+ if (ret) { -+ dev_err(dev, "failed to set clock rate\n"); -+ return ret; -+ } -+ } -+ -+ sensor->extclk_rate = clk_get_rate(sensor->extclk); -+ -+ for (i = 0; i < ARRAY_SIZE(supported_extclk_rates); i++) { -+ if (sensor->extclk_rate == supported_extclk_rates[i]) -+ break; -+ } -+ -+ if (i == ARRAY_SIZE(supported_extclk_rates)) { -+ dev_err(dev, "clock rate %lu Hz is unsupported\n", -+ sensor->extclk_rate); - ret = -EINVAL; - goto error_endpoint; - } - -+ sensor->extclk_rate_idx = i; -+ - /* Subdev, entity and pad */ - - subdev = &sensor->subdev; --- -2.33.0 - -From 13f5cd8218ed183accc1ea89780b42355ef1b522 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:19:10 +0100 -Subject: [PATCH] media: i2c: Add .get_selection() support to ov8865 - -The ov8865 drivers media pad ops currently does not include -.get_selection() - add support for that callback. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 61 ++++++++++++++++++++++++++++++++++++++ - 1 file changed, 61 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index cae7dc9da49d..3ce0af7e0054 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -450,6 +450,15 @@ - #define OV8865_PRE_CTRL0_PATTERN_COLOR_SQUARES 2 - #define OV8865_PRE_CTRL0_PATTERN_BLACK 3 - -+/* Pixel Array */ -+ -+#define OV8865_NATIVE_WIDTH 3296 -+#define OV8865_NATIVE_HEIGHT 2528 -+#define OV8865_ACTIVE_START_TOP 32 -+#define OV8865_ACTIVE_START_LEFT 80 -+#define OV8865_ACTIVE_WIDTH 3264 -+#define OV8865_ACTIVE_HEIGHT 2448 -+ - /* Macros */ - - #define ov8865_subdev_sensor(s) \ -@@ -2745,12 +2754,64 @@ static int ov8865_enum_frame_interval(struct v4l2_subdev *subdev, - return 0; - } - -+static void -+__ov8865_get_pad_crop(struct ov8865_sensor *sensor, -+ struct v4l2_subdev_pad_config *cfg, unsigned int pad, -+ enum v4l2_subdev_format_whence which, struct v4l2_rect *r) -+{ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ *r = *v4l2_subdev_get_try_crop(&sensor->subdev, cfg, pad); -+ break; -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ r->height = sensor->state.mode->output_size_y; -+ r->width = sensor->state.mode->output_size_x; -+ r->top = (OV8865_NATIVE_HEIGHT - sensor->state.mode->output_size_y) / 2; -+ r->left = (OV8865_NATIVE_WIDTH - sensor->state.mode->output_size_x) / 2; -+ break; -+ } -+} -+ -+static int ov8865_get_selection(struct v4l2_subdev *subdev, -+ struct v4l2_subdev_pad_config *cfg, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); -+ -+ switch (sel->target) { -+ case V4L2_SEL_TGT_CROP: -+ mutex_lock(&sensor->mutex); -+ __ov8865_get_pad_crop(sensor, cfg, sel->pad, -+ sel->which, &sel->r); -+ mutex_unlock(&sensor->mutex); -+ break; -+ case V4L2_SEL_TGT_NATIVE_SIZE: -+ sel->r.top = 0; -+ sel->r.left = 0; -+ sel->r.width = OV8865_NATIVE_WIDTH; -+ sel->r.height = OV8865_NATIVE_HEIGHT; -+ break; -+ case V4L2_SEL_TGT_CROP_BOUNDS: -+ case V4L2_SEL_TGT_CROP_DEFAULT: -+ sel->r.top = OV8865_ACTIVE_START_TOP; -+ sel->r.left = OV8865_ACTIVE_START_LEFT; -+ sel->r.width = OV8865_ACTIVE_WIDTH; -+ sel->r.height = OV8865_ACTIVE_HEIGHT; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ - static const struct v4l2_subdev_pad_ops ov8865_subdev_pad_ops = { - .enum_mbus_code = ov8865_enum_mbus_code, - .get_fmt = ov8865_get_fmt, - .set_fmt = ov8865_set_fmt, - .enum_frame_size = ov8865_enum_frame_size, - .enum_frame_interval = ov8865_enum_frame_interval, -+ .get_selection = ov8865_get_selection, - }; - - static const struct v4l2_subdev_ops ov8865_subdev_ops = { --- -2.33.0 - -From b08e46416ae81314e89b7b3e74c0c69de1f41f4e Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:34:43 +0100 -Subject: [PATCH] media: i2c: Switch control to V4L2_CID_ANALOGUE_GAIN - -The V4L2_CID_GAIN control for this driver configures registers that -the datasheet specifies as analogue gain. Switch the control's ID -to V4L2_CID_ANALOGUE_GAIN. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 3ce0af7e0054..c0c6b1d7e1ed 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2137,7 +2137,7 @@ static int ov8865_exposure_configure(struct ov8865_sensor *sensor, u32 exposure) - - /* Gain */ - --static int ov8865_gain_configure(struct ov8865_sensor *sensor, u32 gain) -+static int ov8865_analog_gain_configure(struct ov8865_sensor *sensor, u32 gain) - { - int ret; - -@@ -2447,8 +2447,8 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - if (ret) - return ret; - break; -- case V4L2_CID_GAIN: -- ret = ov8865_gain_configure(sensor, ctrl->val); -+ case V4L2_CID_ANALOGUE_GAIN: -+ ret = ov8865_analog_gain_configure(sensor, ctrl->val); - if (ret) - return ret; - break; -@@ -2493,7 +2493,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Gain */ - -- v4l2_ctrl_new_std(handler, ops, V4L2_CID_GAIN, 128, 8191, 128, 128); -+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_ANALOGUE_GAIN, 128, 8191, 128, 128); - - /* White Balance */ - --- -2.33.0 - -From aa022a89b38f9807db2f243e21b2c4b33a0f51cc Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 12 Jul 2021 22:54:56 +0100 -Subject: [PATCH] media: i2c: Add vblank control to ov8865 - -Add a V4L2_CID_VBLANK control to the ov8865 driver. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 34 ++++++++++++++++++++++++++++++++++ - 1 file changed, 34 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index c0c6b1d7e1ed..5f67d85e33bc 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -183,6 +183,8 @@ - #define OV8865_VTS_H(v) (((v) & GENMASK(11, 8)) >> 8) - #define OV8865_VTS_L_REG 0x380f - #define OV8865_VTS_L(v) ((v) & GENMASK(7, 0)) -+#define OV8865_TIMING_MAX_VTS 0xffff -+#define OV8865_TIMING_MIN_VTS 0x04 - #define OV8865_OFFSET_X_H_REG 0x3810 - #define OV8865_OFFSET_X_H(v) (((v) & GENMASK(15, 8)) >> 8) - #define OV8865_OFFSET_X_L_REG 0x3811 -@@ -658,6 +660,7 @@ struct ov8865_state { - struct ov8865_ctrls { - struct v4l2_ctrl *link_freq; - struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *vblank; - - struct v4l2_ctrl_handler handler; - }; -@@ -2212,6 +2215,20 @@ static int ov8865_test_pattern_configure(struct ov8865_sensor *sensor, - ov8865_test_pattern_bits[index]); - } - -+/* Blanking */ -+ -+static int ov8865_vts_configure(struct ov8865_sensor *sensor, u32 vblank) -+{ -+ u16 vts = sensor->state.mode->output_size_y + vblank; -+ int ret; -+ -+ ret = ov8865_write(sensor, OV8865_VTS_H_REG, OV8865_VTS_H(vts)); -+ if (ret) -+ return ret; -+ -+ return ov8865_write(sensor, OV8865_VTS_L_REG, OV8865_VTS_L(vts)); -+} -+ - /* State */ - - static int ov8865_state_mipi_configure(struct ov8865_sensor *sensor, -@@ -2463,6 +2480,8 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - case V4L2_CID_TEST_PATTERN: - index = (unsigned int)ctrl->val; - return ov8865_test_pattern_configure(sensor, index); -+ case V4L2_CID_VBLANK: -+ return ov8865_vts_configure(sensor, ctrl->val); - default: - return -EINVAL; - } -@@ -2479,6 +2498,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - struct ov8865_ctrls *ctrls = &sensor->ctrls; - struct v4l2_ctrl_handler *handler = &ctrls->handler; - const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; -+ const struct ov8865_mode *mode = sensor->state.mode; -+ unsigned int vblank_max, vblank_def; - int ret; - - v4l2_ctrl_handler_init(handler, 32); -@@ -2514,6 +2535,13 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - ARRAY_SIZE(ov8865_test_pattern_menu) - 1, - 0, 0, ov8865_test_pattern_menu); - -+ /* Blanking */ -+ vblank_max = OV8865_TIMING_MAX_VTS - mode->output_size_y; -+ vblank_def = mode->vts - mode->output_size_y; -+ ctrls->vblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_VBLANK, -+ OV8865_TIMING_MIN_VTS, vblank_max, 1, -+ vblank_def); -+ - /* MIPI CSI-2 */ - - ctrls->link_freq = -@@ -2696,6 +2724,10 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - sensor->state.mbus_code != mbus_code) - ret = ov8865_state_configure(sensor, mode, mbus_code); - -+ __v4l2_ctrl_modify_range(sensor->ctrls.vblank, OV8865_TIMING_MIN_VTS, -+ OV8865_TIMING_MAX_VTS - mode->output_size_y, -+ 1, mode->vts - mode->output_size_y); -+ - complete: - mutex_unlock(&sensor->mutex); - -@@ -3023,6 +3055,8 @@ static int ov8865_probe(struct i2c_client *client) - - /* Sensor */ - -+ sensor->state.mode = &ov8865_modes[0]; -+ - ret = ov8865_ctrls_init(sensor); - if (ret) - goto error_mutex; --- -2.33.0 - -From 11edb4401ec485ac51a517be5f1e3c1b11957c71 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 13 Jul 2021 23:40:33 +0100 -Subject: [PATCH] media: i2c: Add hblank control to ov8865 - -Add a V4L2_CID_HBLANK control to the ov8865 driver. This is read only -with timing control intended to be done via vblanking alone. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 14 ++++++++++++++ - 1 file changed, 14 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 5f67d85e33bc..66754ff62a22 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -660,6 +660,7 @@ struct ov8865_state { - struct ov8865_ctrls { - struct v4l2_ctrl *link_freq; - struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *hblank; - struct v4l2_ctrl *vblank; - - struct v4l2_ctrl_handler handler; -@@ -2500,6 +2501,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; - const struct ov8865_mode *mode = sensor->state.mode; - unsigned int vblank_max, vblank_def; -+ unsigned int hblank; - int ret; - - v4l2_ctrl_handler_init(handler, 32); -@@ -2536,6 +2538,13 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - 0, 0, ov8865_test_pattern_menu); - - /* Blanking */ -+ hblank = mode->hts < mode->output_size_x ? 0 : mode->hts - mode->output_size_x; -+ ctrls->hblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_HBLANK, hblank, -+ hblank, 1, hblank); -+ -+ if (ctrls->hblank) -+ ctrls->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ - vblank_max = OV8865_TIMING_MAX_VTS - mode->output_size_y; - vblank_def = mode->vts - mode->output_size_y; - ctrls->vblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_VBLANK, -@@ -2684,6 +2693,7 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - struct v4l2_mbus_framefmt *mbus_format = &format->format; - const struct ov8865_mode *mode; - u32 mbus_code = 0; -+ unsigned int hblank; - unsigned int index; - int ret = 0; - -@@ -2728,6 +2738,10 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - OV8865_TIMING_MAX_VTS - mode->output_size_y, - 1, mode->vts - mode->output_size_y); - -+ hblank = mode->hts < mode->output_size_x ? 0 : mode->hts - mode->output_size_x; -+ __v4l2_ctrl_modify_range(sensor->ctrls.hblank, hblank, hblank, 1, -+ hblank); -+ - complete: - mutex_unlock(&sensor->mutex); - --- -2.33.0 - -From d27a9793580dcf69c63178e50c1935cd2636ec82 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 13 Jul 2021 23:43:17 +0100 -Subject: [PATCH] media: i2c: cap exposure at height + vblank in ov8865 - -Exposure limits depend on the total height; when vblank is altered (and -thus the total height is altered), change the exposure limits to reflect -the new cap. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 24 ++++++++++++++++++++++-- - 1 file changed, 22 insertions(+), 2 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 66754ff62a22..93e741952050 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -662,6 +662,7 @@ struct ov8865_ctrls { - struct v4l2_ctrl *pixel_rate; - struct v4l2_ctrl *hblank; - struct v4l2_ctrl *vblank; -+ struct v4l2_ctrl *exposure; - - struct v4l2_ctrl_handler handler; - }; -@@ -2455,6 +2456,18 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - unsigned int index; - int ret; - -+ /* If VBLANK is altered we need to update exposure to compensate */ -+ if (ctrl->id == V4L2_CID_VBLANK) { -+ int exposure_max; -+ -+ exposure_max = sensor->state.mode->output_size_y + ctrl->val; -+ __v4l2_ctrl_modify_range(sensor->ctrls.exposure, -+ sensor->ctrls.exposure->minimum, -+ exposure_max, -+ sensor->ctrls.exposure->step, -+ min(sensor->ctrls.exposure->val, exposure_max)); -+ } -+ - /* Wait for the sensor to be on before setting controls. */ - if (pm_runtime_suspended(sensor->dev)) - return 0; -@@ -2511,8 +2524,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Exposure */ - -- v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, 1048575, 16, -- 512); -+ ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, -+ 1048575, 16, 512); - - /* Gain */ - -@@ -2695,6 +2708,7 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - u32 mbus_code = 0; - unsigned int hblank; - unsigned int index; -+ int exposure_max; - int ret = 0; - - mutex_lock(&sensor->mutex); -@@ -2742,6 +2756,12 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - __v4l2_ctrl_modify_range(sensor->ctrls.hblank, hblank, hblank, 1, - hblank); - -+ exposure_max = mode->vts; -+ __v4l2_ctrl_modify_range(sensor->ctrls.exposure, -+ sensor->ctrls.exposure->minimum, exposure_max, -+ sensor->ctrls.exposure->step, -+ min(sensor->ctrls.exposure->val, exposure_max)); -+ - complete: - mutex_unlock(&sensor->mutex); - --- -2.33.0 - -From 15e734a203c861eac5794ccd6cfd1ccc31e4d9b9 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 14 Jul 2021 18:05:44 +0100 -Subject: [PATCH] media: i2c: Remove unused macros from ov8865 - -There are a number of macros defined in this driver that aren't actually -used within it. There's a lot of macros defined in total, so removing the -unused ones helps make it a bit less busy. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 137 +------------------------------------ - 1 file changed, 1 insertion(+), 136 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 93e741952050..fd4de2ea1fa9 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -46,8 +46,6 @@ - #define OV8865_PLL_CTRL6_REG 0x306 - #define OV8865_PLL_CTRL6_SYS_DIV(v) (((v) - 1) & BIT(0)) - --#define OV8865_PLL_CTRL8_REG 0x308 --#define OV8865_PLL_CTRL9_REG 0x309 - #define OV8865_PLL_CTRLA_REG 0x30a - #define OV8865_PLL_CTRLA_PRE_DIV_HALF(v) (((v) - 1) & BIT(0)) - #define OV8865_PLL_CTRLB_REG 0x30b -@@ -60,41 +58,21 @@ - #define OV8865_PLL_CTRLE_SYS_DIV(v) ((v) & GENMASK(2, 0)) - #define OV8865_PLL_CTRLF_REG 0x30f - #define OV8865_PLL_CTRLF_SYS_PRE_DIV(v) (((v) - 1) & GENMASK(3, 0)) --#define OV8865_PLL_CTRL10_REG 0x310 --#define OV8865_PLL_CTRL11_REG 0x311 - #define OV8865_PLL_CTRL12_REG 0x312 - #define OV8865_PLL_CTRL12_PRE_DIV_HALF(v) ((((v) - 1) << 4) & BIT(4)) - #define OV8865_PLL_CTRL12_DAC_DIV(v) (((v) - 1) & GENMASK(3, 0)) - --#define OV8865_PLL_CTRL1B_REG 0x31b --#define OV8865_PLL_CTRL1C_REG 0x31c -- - #define OV8865_PLL_CTRL1E_REG 0x31e - #define OV8865_PLL_CTRL1E_PLL1_NO_LAT BIT(3) - --#define OV8865_PAD_OEN0_REG 0x3000 -- --#define OV8865_PAD_OEN2_REG 0x3002 -- --#define OV8865_CLK_RST5_REG 0x3005 -- - #define OV8865_CHIP_ID_HH_REG 0x300a - #define OV8865_CHIP_ID_HH_VALUE 0x00 - #define OV8865_CHIP_ID_H_REG 0x300b - #define OV8865_CHIP_ID_H_VALUE 0x88 - #define OV8865_CHIP_ID_L_REG 0x300c - #define OV8865_CHIP_ID_L_VALUE 0x65 --#define OV8865_PAD_OUT2_REG 0x300d -- --#define OV8865_PAD_SEL2_REG 0x3010 --#define OV8865_PAD_PK_REG 0x3011 --#define OV8865_PAD_PK_DRIVE_STRENGTH_1X (0 << 5) --#define OV8865_PAD_PK_DRIVE_STRENGTH_2X (1 << 5) --#define OV8865_PAD_PK_DRIVE_STRENGTH_3X (2 << 5) --#define OV8865_PAD_PK_DRIVE_STRENGTH_4X (3 << 5) - - #define OV8865_PUMP_CLK_DIV_REG 0x3015 --#define OV8865_PUMP_CLK_DIV_PUMP_N(v) (((v) << 4) & GENMASK(6, 4)) - #define OV8865_PUMP_CLK_DIV_PUMP_P(v) ((v) & GENMASK(2, 0)) - - #define OV8865_MIPI_SC_CTRL0_REG 0x3018 -@@ -102,21 +80,12 @@ - GENMASK(7, 5)) - #define OV8865_MIPI_SC_CTRL0_MIPI_EN BIT(4) - #define OV8865_MIPI_SC_CTRL0_UNKNOWN BIT(1) --#define OV8865_MIPI_SC_CTRL0_LANES_PD_MIPI BIT(0) --#define OV8865_MIPI_SC_CTRL1_REG 0x3019 --#define OV8865_CLK_RST0_REG 0x301a --#define OV8865_CLK_RST1_REG 0x301b --#define OV8865_CLK_RST2_REG 0x301c --#define OV8865_CLK_RST3_REG 0x301d --#define OV8865_CLK_RST4_REG 0x301e - - #define OV8865_PCLK_SEL_REG 0x3020 - #define OV8865_PCLK_SEL_PCLK_DIV_MASK BIT(3) - #define OV8865_PCLK_SEL_PCLK_DIV(v) ((((v) - 1) << 3) & BIT(3)) - --#define OV8865_MISC_CTRL_REG 0x3021 - #define OV8865_MIPI_SC_CTRL2_REG 0x3022 --#define OV8865_MIPI_SC_CTRL2_CLK_LANES_PD_MIPI BIT(1) - #define OV8865_MIPI_SC_CTRL2_PD_MIPI_RST_SYNC BIT(0) - - #define OV8865_MIPI_BIT_SEL_REG 0x3031 -@@ -125,7 +94,6 @@ - #define OV8865_CLK_SEL0_PLL1_SYS_SEL(v) (((v) << 7) & BIT(7)) - #define OV8865_CLK_SEL1_REG 0x3033 - #define OV8865_CLK_SEL1_MIPI_EOF BIT(5) --#define OV8865_CLK_SEL1_UNKNOWN BIT(2) - #define OV8865_CLK_SEL1_PLL_SCLK_SEL_MASK BIT(1) - #define OV8865_CLK_SEL1_PLL_SCLK_SEL(v) (((v) << 1) & BIT(1)) - -@@ -142,7 +110,6 @@ - #define OV8865_EXPOSURE_CTRL_H(v) (((v) & GENMASK(15, 8)) >> 8) - #define OV8865_EXPOSURE_CTRL_L_REG 0x3502 - #define OV8865_EXPOSURE_CTRL_L(v) ((v) & GENMASK(7, 0)) --#define OV8865_EXPOSURE_GAIN_MANUAL_REG 0x3503 - - #define OV8865_GAIN_CTRL_H_REG 0x3508 - #define OV8865_GAIN_CTRL_H(v) (((v) & GENMASK(12, 8)) >> 8) -@@ -197,18 +164,6 @@ - #define OV8865_INC_X_ODD(v) ((v) & GENMASK(4, 0)) - #define OV8865_INC_X_EVEN_REG 0x3815 - #define OV8865_INC_X_EVEN(v) ((v) & GENMASK(4, 0)) --#define OV8865_VSYNC_START_H_REG 0x3816 --#define OV8865_VSYNC_START_H(v) (((v) & GENMASK(15, 8)) >> 8) --#define OV8865_VSYNC_START_L_REG 0x3817 --#define OV8865_VSYNC_START_L(v) ((v) & GENMASK(7, 0)) --#define OV8865_VSYNC_END_H_REG 0x3818 --#define OV8865_VSYNC_END_H(v) (((v) & GENMASK(15, 8)) >> 8) --#define OV8865_VSYNC_END_L_REG 0x3819 --#define OV8865_VSYNC_END_L(v) ((v) & GENMASK(7, 0)) --#define OV8865_HSYNC_FIRST_H_REG 0x381a --#define OV8865_HSYNC_FIRST_H(v) (((v) & GENMASK(15, 8)) >> 8) --#define OV8865_HSYNC_FIRST_L_REG 0x381b --#define OV8865_HSYNC_FIRST_L(v) ((v) & GENMASK(7, 0)) - - #define OV8865_FORMAT1_REG 0x3820 - #define OV8865_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -@@ -240,10 +195,6 @@ - #define OV8865_AUTO_SIZE_CTRL_CROP_END_X_REG BIT(2) - #define OV8865_AUTO_SIZE_CTRL_CROP_START_Y_REG BIT(1) - #define OV8865_AUTO_SIZE_CTRL_CROP_START_X_REG BIT(0) --#define OV8865_AUTO_SIZE_X_OFFSET_H_REG 0x3842 --#define OV8865_AUTO_SIZE_X_OFFSET_L_REG 0x3843 --#define OV8865_AUTO_SIZE_Y_OFFSET_H_REG 0x3844 --#define OV8865_AUTO_SIZE_Y_OFFSET_L_REG 0x3845 - #define OV8865_AUTO_SIZE_BOUNDARIES_REG 0x3846 - #define OV8865_AUTO_SIZE_BOUNDARIES_Y(v) (((v) << 4) & GENMASK(7, 4)) - #define OV8865_AUTO_SIZE_BOUNDARIES_X(v) ((v) & GENMASK(3, 0)) -@@ -259,30 +210,10 @@ - #define OV8865_BLC_CTRL0_TRIG_FORMAT_EN BIT(6) - #define OV8865_BLC_CTRL0_TRIG_GAIN_EN BIT(5) - #define OV8865_BLC_CTRL0_TRIG_EXPOSURE_EN BIT(4) --#define OV8865_BLC_CTRL0_TRIG_MANUAL_EN BIT(3) --#define OV8865_BLC_CTRL0_FREEZE_EN BIT(2) --#define OV8865_BLC_CTRL0_ALWAYS_EN BIT(1) - #define OV8865_BLC_CTRL0_FILTER_EN BIT(0) - #define OV8865_BLC_CTRL1_REG 0x4001 --#define OV8865_BLC_CTRL1_DITHER_EN BIT(7) --#define OV8865_BLC_CTRL1_ZERO_LINE_DIFF_EN BIT(6) --#define OV8865_BLC_CTRL1_COL_SHIFT_256 (0 << 4) - #define OV8865_BLC_CTRL1_COL_SHIFT_128 (1 << 4) --#define OV8865_BLC_CTRL1_COL_SHIFT_64 (2 << 4) --#define OV8865_BLC_CTRL1_COL_SHIFT_32 (3 << 4) - #define OV8865_BLC_CTRL1_OFFSET_LIMIT_EN BIT(2) --#define OV8865_BLC_CTRL1_COLUMN_CANCEL_EN BIT(1) --#define OV8865_BLC_CTRL2_REG 0x4002 --#define OV8865_BLC_CTRL3_REG 0x4003 --#define OV8865_BLC_CTRL4_REG 0x4004 --#define OV8865_BLC_CTRL5_REG 0x4005 --#define OV8865_BLC_CTRL6_REG 0x4006 --#define OV8865_BLC_CTRL7_REG 0x4007 --#define OV8865_BLC_CTRL8_REG 0x4008 --#define OV8865_BLC_CTRL9_REG 0x4009 --#define OV8865_BLC_CTRLA_REG 0x400a --#define OV8865_BLC_CTRLB_REG 0x400b --#define OV8865_BLC_CTRLC_REG 0x400c - #define OV8865_BLC_CTRLD_REG 0x400d - #define OV8865_BLC_CTRLD_OFFSET_TRIGGER(v) ((v) & GENMASK(7, 0)) - -@@ -337,66 +268,8 @@ - - /* MIPI */ - --#define OV8865_MIPI_CTRL0_REG 0x4800 --#define OV8865_MIPI_CTRL1_REG 0x4801 --#define OV8865_MIPI_CTRL2_REG 0x4802 --#define OV8865_MIPI_CTRL3_REG 0x4803 --#define OV8865_MIPI_CTRL4_REG 0x4804 --#define OV8865_MIPI_CTRL5_REG 0x4805 --#define OV8865_MIPI_CTRL6_REG 0x4806 --#define OV8865_MIPI_CTRL7_REG 0x4807 --#define OV8865_MIPI_CTRL8_REG 0x4808 -- --#define OV8865_MIPI_FCNT_MAX_H_REG 0x4810 --#define OV8865_MIPI_FCNT_MAX_L_REG 0x4811 -- --#define OV8865_MIPI_CTRL13_REG 0x4813 --#define OV8865_MIPI_CTRL14_REG 0x4814 --#define OV8865_MIPI_CTRL15_REG 0x4815 --#define OV8865_MIPI_EMBEDDED_DT_REG 0x4816 -- --#define OV8865_MIPI_HS_ZERO_MIN_H_REG 0x4818 --#define OV8865_MIPI_HS_ZERO_MIN_L_REG 0x4819 --#define OV8865_MIPI_HS_TRAIL_MIN_H_REG 0x481a --#define OV8865_MIPI_HS_TRAIL_MIN_L_REG 0x481b --#define OV8865_MIPI_CLK_ZERO_MIN_H_REG 0x481c --#define OV8865_MIPI_CLK_ZERO_MIN_L_REG 0x481d --#define OV8865_MIPI_CLK_PREPARE_MAX_REG 0x481e --#define OV8865_MIPI_CLK_PREPARE_MIN_REG 0x481f --#define OV8865_MIPI_CLK_POST_MIN_H_REG 0x4820 --#define OV8865_MIPI_CLK_POST_MIN_L_REG 0x4821 --#define OV8865_MIPI_CLK_TRAIL_MIN_H_REG 0x4822 --#define OV8865_MIPI_CLK_TRAIL_MIN_L_REG 0x4823 --#define OV8865_MIPI_LPX_P_MIN_H_REG 0x4824 --#define OV8865_MIPI_LPX_P_MIN_L_REG 0x4825 --#define OV8865_MIPI_HS_PREPARE_MIN_REG 0x4826 --#define OV8865_MIPI_HS_PREPARE_MAX_REG 0x4827 --#define OV8865_MIPI_HS_EXIT_MIN_H_REG 0x4828 --#define OV8865_MIPI_HS_EXIT_MIN_L_REG 0x4829 --#define OV8865_MIPI_UI_HS_ZERO_MIN_REG 0x482a --#define OV8865_MIPI_UI_HS_TRAIL_MIN_REG 0x482b --#define OV8865_MIPI_UI_CLK_ZERO_MIN_REG 0x482c --#define OV8865_MIPI_UI_CLK_PREPARE_REG 0x482d --#define OV8865_MIPI_UI_CLK_POST_MIN_REG 0x482e --#define OV8865_MIPI_UI_CLK_TRAIL_MIN_REG 0x482f --#define OV8865_MIPI_UI_LPX_P_MIN_REG 0x4830 --#define OV8865_MIPI_UI_HS_PREPARE_REG 0x4831 --#define OV8865_MIPI_UI_HS_EXIT_MIN_REG 0x4832 --#define OV8865_MIPI_PKT_START_SIZE_REG 0x4833 -- - #define OV8865_MIPI_PCLK_PERIOD_REG 0x4837 --#define OV8865_MIPI_LP_GPIO0_REG 0x4838 --#define OV8865_MIPI_LP_GPIO1_REG 0x4839 -- --#define OV8865_MIPI_CTRL3C_REG 0x483c --#define OV8865_MIPI_LP_GPIO4_REG 0x483d -- --#define OV8865_MIPI_CTRL4A_REG 0x484a --#define OV8865_MIPI_CTRL4B_REG 0x484b --#define OV8865_MIPI_CTRL4C_REG 0x484c --#define OV8865_MIPI_LANE_TEST_PATTERN_REG 0x484d --#define OV8865_MIPI_FRAME_END_DELAY_REG 0x484e --#define OV8865_MIPI_CLOCK_TEST_PATTERN_REG 0x484f -+ - #define OV8865_MIPI_LANE_SEL01_REG 0x4850 - #define OV8865_MIPI_LANE_SEL01_LANE0(v) (((v) << 0) & GENMASK(2, 0)) - #define OV8865_MIPI_LANE_SEL01_LANE1(v) (((v) << 4) & GENMASK(6, 4)) -@@ -407,7 +280,6 @@ - /* ISP */ - - #define OV8865_ISP_CTRL0_REG 0x5000 --#define OV8865_ISP_CTRL0_LENC_EN BIT(7) - #define OV8865_ISP_CTRL0_WHITE_BALANCE_EN BIT(4) - #define OV8865_ISP_CTRL0_DPC_BLACK_EN BIT(2) - #define OV8865_ISP_CTRL0_DPC_WHITE_EN BIT(1) -@@ -416,17 +288,11 @@ - #define OV8865_ISP_CTRL2_REG 0x5002 - #define OV8865_ISP_CTRL2_DEBUG BIT(3) - #define OV8865_ISP_CTRL2_VARIOPIXEL_EN BIT(2) --#define OV8865_ISP_CTRL2_VSYNC_LATCH_EN BIT(0) --#define OV8865_ISP_CTRL3_REG 0x5003 - - #define OV8865_ISP_GAIN_RED_H_REG 0x5018 - #define OV8865_ISP_GAIN_RED_H(v) (((v) & GENMASK(13, 6)) >> 6) - #define OV8865_ISP_GAIN_RED_L_REG 0x5019 - #define OV8865_ISP_GAIN_RED_L(v) ((v) & GENMASK(5, 0)) --#define OV8865_ISP_GAIN_GREEN_H_REG 0x501a --#define OV8865_ISP_GAIN_GREEN_H(v) (((v) & GENMASK(13, 6)) >> 6) --#define OV8865_ISP_GAIN_GREEN_L_REG 0x501b --#define OV8865_ISP_GAIN_GREEN_L(v) ((v) & GENMASK(5, 0)) - #define OV8865_ISP_GAIN_BLUE_H_REG 0x501c - #define OV8865_ISP_GAIN_BLUE_H(v) (((v) & GENMASK(13, 6)) >> 6) - #define OV8865_ISP_GAIN_BLUE_L_REG 0x501d -@@ -434,7 +300,6 @@ - - /* VarioPixel */ - --#define OV8865_VAP_CTRL0_REG 0x5900 - #define OV8865_VAP_CTRL1_REG 0x5901 - #define OV8865_VAP_CTRL1_HSUB_COEF(v) ((((v) - 1) << 2) & \ - GENMASK(3, 2)) --- -2.33.0 - -From 0c71885c10af33ed271073d670d03c3ea41fbb65 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 16 Jul 2021 00:00:54 +0100 -Subject: [PATCH] media: i2c: Switch exposure control unit to lines - -The ov8865 driver currently has the unit of the V4L2_CID_EXPOSURE control -as 1/16th of a line. This is what the sensor expects, but isn't very -intuitive. Switch the control to be in units of a line and simply do the -16x multiplication before passing the value to the sensor. - -The datasheet for this sensor gives minimum exposure as 2 lines, so take -the opportunity to correct the lower bounds of the control. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 7 +++++-- - 1 file changed, 5 insertions(+), 2 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index fd4de2ea1fa9..1905028742d5 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -1991,6 +1991,9 @@ static int ov8865_exposure_configure(struct ov8865_sensor *sensor, u32 exposure) - { - int ret; - -+ /* The sensor stores exposure in units of 1/16th of a line */ -+ exposure *= 16; -+ - ret = ov8865_write(sensor, OV8865_EXPOSURE_CTRL_HH_REG, - OV8865_EXPOSURE_CTRL_HH(exposure)); - if (ret) -@@ -2389,8 +2392,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Exposure */ - -- ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, -- 1048575, 16, 512); -+ ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 2, -+ 65535, 1, 32); - - /* Gain */ - --- -2.33.0 - -From 14f39d347de9bfd1ff52dfef55ef10a364938a96 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 16 Jul 2021 22:56:15 +0100 -Subject: [PATCH] media: i2c: Add controls from fwnode to ov8865 - -Add V4L2_CID_ORIENTATION and V4L2_CID_ROTATION controls to the ov8865 -driver by attempting to parse them from firmware. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 10 ++++++++++ - 1 file changed, 10 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 1905028742d5..e88825ea76aa 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2381,6 +2381,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - struct v4l2_ctrl_handler *handler = &ctrls->handler; - const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; - const struct ov8865_mode *mode = sensor->state.mode; -+ struct v4l2_fwnode_device_properties props; - unsigned int vblank_max, vblank_def; - unsigned int hblank; - int ret; -@@ -2443,6 +2444,15 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 1, - INT_MAX, 1, 1); - -+ /* set properties from fwnode (e.g. rotation, orientation) */ -+ ret = v4l2_fwnode_device_parse(sensor->dev, &props); -+ if (ret) -+ goto error_ctrls; -+ -+ ret = v4l2_ctrl_new_fwnode_properties(handler, ops, &props); -+ if (ret) -+ goto error_ctrls; -+ - if (handler->error) { - ret = handler->error; - goto error_ctrls; --- -2.33.0 - -From 3bd30feb956376b0ffff78f2e7b2b7e9bd945608 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 14 Jul 2021 00:05:04 +0100 -Subject: [PATCH] media: ipu3-cio2: Add INT347A to cio2-bridge - -ACPI _HID INT347A represents the OV8865 sensor, the driver for which can -support the platforms that the cio2-bridge serves. Add it to the array -of supported sensors so the bridge will connect the sensor to the CIO2 -device. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index 1052885caa25..856dc7439a59 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -24,6 +24,8 @@ static const struct cio2_sensor_config cio2_supported_sensors[] = { - CIO2_SENSOR_CONFIG("INT33BE", 0), - /* Omnivision OV2680 */ - CIO2_SENSOR_CONFIG("OVTI2680", 0), -+ /* Omnivision OV8865 */ -+ CIO2_SENSOR_CONFIG("INT347A", 1, 360000000), - }; - - static const struct cio2_property_names prop_names = { --- -2.33.0 - diff --git a/patches/5.13/0010-amd-gpio.patch b/patches/5.13/0010-amd-gpio.patch deleted file mode 100644 index 41a78882d..000000000 --- a/patches/5.13/0010-amd-gpio.patch +++ /dev/null @@ -1,109 +0,0 @@ -From 56d3c1a06f0647c53df57bf5105571ed20559ce0 Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Sat, 29 May 2021 17:47:38 +1000 -Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 - override - -This patch is the work of Thomas Gleixner and is -copied from: -https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ - -This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin -setup that is missing in the laptops ACPI table. - -This patch was used for validation of the issue, and is not a proper -fix, but is probably a better temporary hack than continuing to probe -the Legacy PIC and run with the PIC in an unknown state. - -Patchset: amd-gpio ---- - arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ - 1 file changed, 17 insertions(+) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index e90310cbe73a..d89aebf69ce1 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -21,6 +21,7 @@ - #include - #include - #include -+#include - - #include - #include -@@ -1155,6 +1156,17 @@ static void __init mp_config_acpi_legacy_irqs(void) - } - } - -+static const struct dmi_system_id surface_quirk[] __initconst = { -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") -+ }, -+ }, -+ {} -+}; -+ - /* - * Parse IOAPIC related entries in MADT - * returns 0 on success, < 0 on error -@@ -1212,6 +1224,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) - acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, - acpi_gbl_FADT.sci_interrupt); - -+ if (dmi_check_system(surface_quirk)) { -+ pr_warn("Surface hack: Override irq 7\n"); -+ mp_override_legacy_irq(7, 3, 3, 7); -+ } -+ - /* Fill in identity legacy mappings where no override */ - mp_config_acpi_legacy_irqs(); - --- -2.33.0 - -From 75fe508808f39af24f60f2d0899e771270fe016a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 3 Jun 2021 14:04:26 +0200 -Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override - quirk - -The 13" version of the Surface Laptop 4 has the same problem as the 15" -version, but uses a different SKU. Add that SKU to the quirk as well. - -Patchset: amd-gpio ---- - arch/x86/kernel/acpi/boot.c | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index d89aebf69ce1..b18a1dc69b9b 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -1158,12 +1158,19 @@ static void __init mp_config_acpi_legacy_irqs(void) - - static const struct dmi_system_id surface_quirk[] __initconst = { - { -- .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") - }, - }, -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") -+ }, -+ }, - {} - }; - --- -2.33.0 - diff --git a/patches/5.13/0011-amd-s0ix.patch b/patches/5.13/0011-amd-s0ix.patch deleted file mode 100644 index 80e2b7ed4..000000000 --- a/patches/5.13/0011-amd-s0ix.patch +++ /dev/null @@ -1,905 +0,0 @@ -From 79d369222dfb0dd242bdc7e1f3d5d8d39f1b58df Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Sat, 29 May 2021 22:27:25 +1000 -Subject: [PATCH] platform/x86: amd-pmc: Add device HID for AMD PMC - -The Surface Laptop 4 appears to have used AMD0005 for the PMC instead of -the AMDI0005 which would match the ACPI ID Registry. - -AMD appears to have previously used "AMD" in a number of IDs in the past, -and AMD is not allocated to any other entity as an ID, so adding this ID -should not cause any harm. - -Signed-off-by: Sachi King -Patchset: amd-s0ix ---- - drivers/platform/x86/amd-pmc.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c -index ca95c2a52e26..65a81d295beb 100644 ---- a/drivers/platform/x86/amd-pmc.c -+++ b/drivers/platform/x86/amd-pmc.c -@@ -299,6 +299,7 @@ static int amd_pmc_remove(struct platform_device *pdev) - static const struct acpi_device_id amd_pmc_acpi_ids[] = { - {"AMDI0005", 0}, - {"AMD0004", 0}, -+ {"AMD0005", 0}, - { } - }; - MODULE_DEVICE_TABLE(acpi, amd_pmc_acpi_ids); --- -2.33.0 - -From 0c8847a9028c612cf05bbe0e49e8d24b57e48dac Mon Sep 17 00:00:00 2001 -From: Mario Limonciello -Date: Wed, 9 Jun 2021 13:40:17 -0500 -Subject: [PATCH] ACPI: Check StorageD3Enable _DSD property in ACPI code - -Although first implemented for NVME, this check may be usable by -other drivers as well. Microsoft's specification explicitly mentions -that is may be usable by SATA and AHCI devices. Google also indicates -that they have used this with SDHCI in a downstream kernel tree that -a user can plug a storage device into. - -Link: https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/power-management-for-storage-hardware-devices-intro -Suggested-by: Keith Busch -CC: Shyam-sundar S-k -CC: Alexander Deucher -CC: Rafael J. Wysocki -CC: Prike Liang -Signed-off-by: Mario Limonciello -Reviewed-by: Rafael J. Wysocki -Signed-off-by: Christoph Hellwig -Patchset: amd-s0ix ---- - drivers/acpi/device_pm.c | 29 +++++++++++++++++++++++++++++ - drivers/nvme/host/pci.c | 28 +--------------------------- - include/linux/acpi.h | 5 +++++ - 3 files changed, 35 insertions(+), 27 deletions(-) - -diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c -index 9d2d3b9bb8b5..b20ae93415f0 100644 ---- a/drivers/acpi/device_pm.c -+++ b/drivers/acpi/device_pm.c -@@ -1338,4 +1338,33 @@ int acpi_dev_pm_attach(struct device *dev, bool power_on) - return 1; - } - EXPORT_SYMBOL_GPL(acpi_dev_pm_attach); -+ -+/** -+ * acpi_storage_d3 - Check if D3 should be used in the suspend path -+ * @dev: Device to check -+ * -+ * Return %true if the platform firmware wants @dev to be programmed -+ * into D3hot or D3cold (if supported) in the suspend path, or %false -+ * when there is no specific preference. On some platforms, if this -+ * hint is ignored, @dev may remain unresponsive after suspending the -+ * platform as a whole. -+ * -+ * Although the property has storage in the name it actually is -+ * applied to the PCIe slot and plugging in a non-storage device the -+ * same platform restrictions will likely apply. -+ */ -+bool acpi_storage_d3(struct device *dev) -+{ -+ struct acpi_device *adev = ACPI_COMPANION(dev); -+ u8 val; -+ -+ if (!adev) -+ return false; -+ if (fwnode_property_read_u8(acpi_fwnode_handle(adev), "StorageD3Enable", -+ &val)) -+ return false; -+ return val == 1; -+} -+EXPORT_SYMBOL_GPL(acpi_storage_d3); -+ - #endif /* CONFIG_PM */ -diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c -index d963f25fc7ae..66455e2261d0 100644 ---- a/drivers/nvme/host/pci.c -+++ b/drivers/nvme/host/pci.c -@@ -2880,32 +2880,6 @@ static unsigned long check_vendor_combination_bug(struct pci_dev *pdev) - return 0; - } - --#ifdef CONFIG_ACPI --static bool nvme_acpi_storage_d3(struct pci_dev *dev) --{ -- struct acpi_device *adev = ACPI_COMPANION(&dev->dev); -- u8 val; -- -- /* -- * Look for _DSD property specifying that the storage device on the port -- * must use D3 to support deep platform power savings during -- * suspend-to-idle. -- */ -- -- if (!adev) -- return false; -- if (fwnode_property_read_u8(acpi_fwnode_handle(adev), "StorageD3Enable", -- &val)) -- return false; -- return val == 1; --} --#else --static inline bool nvme_acpi_storage_d3(struct pci_dev *dev) --{ -- return false; --} --#endif /* CONFIG_ACPI */ -- - static void nvme_async_probe(void *data, async_cookie_t cookie) - { - struct nvme_dev *dev = data; -@@ -2955,7 +2929,7 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) - - quirks |= check_vendor_combination_bug(pdev); - -- if (!noacpi && nvme_acpi_storage_d3(pdev)) { -+ if (!noacpi && acpi_storage_d3(&pdev->dev)) { - /* - * Some systems use a bios work around to ask for D3 on - * platforms that support kernel managed suspend. -diff --git a/include/linux/acpi.h b/include/linux/acpi.h -index e8ba7063c000..66c43abef4a4 100644 ---- a/include/linux/acpi.h -+++ b/include/linux/acpi.h -@@ -1006,6 +1006,7 @@ int acpi_dev_resume(struct device *dev); - int acpi_subsys_runtime_suspend(struct device *dev); - int acpi_subsys_runtime_resume(struct device *dev); - int acpi_dev_pm_attach(struct device *dev, bool power_on); -+bool acpi_storage_d3(struct device *dev); - #else - static inline int acpi_subsys_runtime_suspend(struct device *dev) { return 0; } - static inline int acpi_subsys_runtime_resume(struct device *dev) { return 0; } -@@ -1013,6 +1014,10 @@ static inline int acpi_dev_pm_attach(struct device *dev, bool power_on) - { - return 0; - } -+static inline bool acpi_storage_d3(struct device *dev) -+{ -+ return false; -+} - #endif - - #if defined(CONFIG_ACPI) && defined(CONFIG_PM_SLEEP) --- -2.33.0 - -From 50a89b7d7b3e72b75858a55d396ecafea907d098 Mon Sep 17 00:00:00 2001 -From: Mario Limonciello -Date: Wed, 9 Jun 2021 13:40:18 -0500 -Subject: [PATCH] ACPI: Add quirks for AMD Renoir/Lucienne CPUs to force the D3 - hint - -AMD systems from Renoir and Lucienne require that the NVME controller -is put into D3 over a Modern Standby / suspend-to-idle -cycle. This is "typically" accomplished using the `StorageD3Enable` -property in the _DSD, but this property was introduced after many -of these systems launched and most OEM systems don't have it in -their BIOS. - -On AMD Renoir without these drives going into D3 over suspend-to-idle -the resume will fail with the NVME controller being reset and a trace -like this in the kernel logs: -``` -[ 83.556118] nvme nvme0: I/O 161 QID 2 timeout, aborting -[ 83.556178] nvme nvme0: I/O 162 QID 2 timeout, aborting -[ 83.556187] nvme nvme0: I/O 163 QID 2 timeout, aborting -[ 83.556196] nvme nvme0: I/O 164 QID 2 timeout, aborting -[ 95.332114] nvme nvme0: I/O 25 QID 0 timeout, reset controller -[ 95.332843] nvme nvme0: Abort status: 0x371 -[ 95.332852] nvme nvme0: Abort status: 0x371 -[ 95.332856] nvme nvme0: Abort status: 0x371 -[ 95.332859] nvme nvme0: Abort status: 0x371 -[ 95.332909] PM: dpm_run_callback(): pci_pm_resume+0x0/0xe0 returns -16 -[ 95.332936] nvme 0000:03:00.0: PM: failed to resume async: error -16 -``` - -The Microsoft documentation for StorageD3Enable mentioned that Windows has -a hardcoded allowlist for D3 support, which was used for these platforms. -Introduce quirks to hardcode them for Linux as well. - -As this property is now "standardized", OEM systems using AMD Cezanne and -newer APU's have adopted this property, and quirks like this should not be -necessary. - -CC: Shyam-sundar S-k -CC: Alexander Deucher -CC: Prike Liang -Link: https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/power-management-for-storage-hardware-devices-intro -Signed-off-by: Mario Limonciello -Acked-by: Rafael J. Wysocki -Tested-by: Julian Sikorski -Signed-off-by: Christoph Hellwig -Patchset: amd-s0ix ---- - drivers/acpi/device_pm.c | 3 +++ - drivers/acpi/internal.h | 9 +++++++++ - drivers/acpi/x86/utils.c | 25 +++++++++++++++++++++++++ - 3 files changed, 37 insertions(+) - -diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c -index b20ae93415f0..0cfdef2fc3ad 100644 ---- a/drivers/acpi/device_pm.c -+++ b/drivers/acpi/device_pm.c -@@ -1358,6 +1358,9 @@ bool acpi_storage_d3(struct device *dev) - struct acpi_device *adev = ACPI_COMPANION(dev); - u8 val; - -+ if (force_storage_d3()) -+ return true; -+ - if (!adev) - return false; - if (fwnode_property_read_u8(acpi_fwnode_handle(adev), "StorageD3Enable", -diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h -index e21611c9a170..7ac01b03ba67 100644 ---- a/drivers/acpi/internal.h -+++ b/drivers/acpi/internal.h -@@ -236,6 +236,15 @@ static inline int suspend_nvs_save(void) { return 0; } - static inline void suspend_nvs_restore(void) {} - #endif - -+#ifdef CONFIG_X86 -+bool force_storage_d3(void); -+#else -+static inline bool force_storage_d3(void) -+{ -+ return false; -+} -+#endif -+ - /*-------------------------------------------------------------------------- - Device properties - -------------------------------------------------------------------------- */ -diff --git a/drivers/acpi/x86/utils.c b/drivers/acpi/x86/utils.c -index bdc1ba00aee9..5298bb4d81fe 100644 ---- a/drivers/acpi/x86/utils.c -+++ b/drivers/acpi/x86/utils.c -@@ -135,3 +135,28 @@ bool acpi_device_always_present(struct acpi_device *adev) - - return ret; - } -+ -+/* -+ * AMD systems from Renoir and Lucienne *require* that the NVME controller -+ * is put into D3 over a Modern Standby / suspend-to-idle cycle. -+ * -+ * This is "typically" accomplished using the `StorageD3Enable` -+ * property in the _DSD that is checked via the `acpi_storage_d3` function -+ * but this property was introduced after many of these systems launched -+ * and most OEM systems don't have it in their BIOS. -+ * -+ * The Microsoft documentation for StorageD3Enable mentioned that Windows has -+ * a hardcoded allowlist for D3 support, which was used for these platforms. -+ * -+ * This allows quirking on Linux in a similar fashion. -+ */ -+const struct x86_cpu_id storage_d3_cpu_ids[] = { -+ X86_MATCH_VENDOR_FAM_MODEL(AMD, 23, 96, NULL), /* Renoir */ -+ X86_MATCH_VENDOR_FAM_MODEL(AMD, 23, 104, NULL), /* Lucienne */ -+ {} -+}; -+ -+bool force_storage_d3(void) -+{ -+ return x86_match_cpu(storage_d3_cpu_ids); -+} --- -2.33.0 - -From 7ca60620bd771b2fb7a05b3418bf758fa77d21b5 Mon Sep 17 00:00:00 2001 -From: Alex Deucher -Date: Wed, 17 Mar 2021 10:38:42 -0400 -Subject: [PATCH] platform/x86: force LPS0 functions for AMD - -ACPI_LPS0_ENTRY_AMD/ACPI_LPS0_EXIT_AMD are supposedly not -required for AMD platforms, and on some platforms they are -not even listed in the function mask but at least some HP -laptops seem to require it to properly support s0ix. - -Based on a patch from Marcin Bachry . - -Bug: https://gitlab.freedesktop.org/drm/amd/-/issues/1230 -Signed-off-by: Alex Deucher -Cc: Marcin Bachry -Reviewed-by: Hans de Goede -Patchset: amd-s0ix ---- - drivers/acpi/x86/s2idle.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c -index 2d7ddb8a8cb6..482e6b23b21a 100644 ---- a/drivers/acpi/x86/s2idle.c -+++ b/drivers/acpi/x86/s2idle.c -@@ -368,6 +368,13 @@ static int lps0_device_attach(struct acpi_device *adev, - - ACPI_FREE(out_obj); - -+ /* -+ * Some HP laptops require ACPI_LPS0_ENTRY_AMD/ACPI_LPS0_EXIT_AMD for proper -+ * S0ix, but don't set the function mask correctly. Fix that up here. -+ */ -+ if (acpi_s2idle_vendor_amd()) -+ lps0_dsm_func_mask |= (1 << ACPI_LPS0_ENTRY_AMD) | (1 << ACPI_LPS0_EXIT_AMD); -+ - acpi_handle_debug(adev->handle, "_DSM function mask: 0x%x\n", - lps0_dsm_func_mask); - --- -2.33.0 - -From 87679fb3dc27b52a355e38fcc93be82fa9c8b032 Mon Sep 17 00:00:00 2001 -From: Pratik Vishwakarma -Date: Thu, 17 Jun 2021 11:42:08 -0500 -Subject: [PATCH] ACPI: PM: s2idle: Use correct revision id - -AMD spec mentions only revision 0. With this change, -device constraint list is populated properly. - -Signed-off-by: Pratik Vishwakarma -Patchset: amd-s0ix ---- - drivers/acpi/x86/s2idle.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c -index 482e6b23b21a..4339e6da0dd6 100644 ---- a/drivers/acpi/x86/s2idle.c -+++ b/drivers/acpi/x86/s2idle.c -@@ -96,7 +96,7 @@ static void lpi_device_get_constraints_amd(void) - int i, j, k; - - out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid, -- 1, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, -+ rev_id, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, - NULL, ACPI_TYPE_PACKAGE); - - if (!out_obj) --- -2.33.0 - -From 009c715526355925f0eef04947a4002da05f6741 Mon Sep 17 00:00:00 2001 -From: Shyam Sundar S K -Date: Tue, 29 Jun 2021 14:17:59 +0530 -Subject: [PATCH] platform/x86: amd-pmc: call dump registers only once - -Currently amd_pmc_dump_registers() routine is being called at -multiple places. The best to call it is after command submission -to SMU. - -Signed-off-by: Shyam Sundar S K -Reviewed-by: Hans de Goede -Patchset: amd-s0ix ---- - drivers/platform/x86/amd-pmc.c | 5 +---- - 1 file changed, 1 insertion(+), 4 deletions(-) - -diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c -index 65a81d295beb..d08ff5907e4c 100644 ---- a/drivers/platform/x86/amd-pmc.c -+++ b/drivers/platform/x86/amd-pmc.c -@@ -182,6 +182,7 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) - - out_unlock: - mutex_unlock(&dev->lock); -+ amd_pmc_dump_registers(dev); - return rc; - } - -@@ -194,7 +195,6 @@ static int __maybe_unused amd_pmc_suspend(struct device *dev) - if (rc) - dev_err(pdev->dev, "suspend failed\n"); - -- amd_pmc_dump_registers(pdev); - return 0; - } - -@@ -207,7 +207,6 @@ static int __maybe_unused amd_pmc_resume(struct device *dev) - if (rc) - dev_err(pdev->dev, "resume failed\n"); - -- amd_pmc_dump_registers(pdev); - return 0; - } - -@@ -279,8 +278,6 @@ static int amd_pmc_probe(struct platform_device *pdev) - if (!dev->regbase) - return -ENOMEM; - -- amd_pmc_dump_registers(dev); -- - mutex_init(&dev->lock); - platform_set_drvdata(pdev, dev); - amd_pmc_dbgfs_register(dev); --- -2.33.0 - -From 2f215688e66e9621ea6fbef1eed55edc9d7833cf Mon Sep 17 00:00:00 2001 -From: Shyam Sundar S K -Date: Tue, 29 Jun 2021 14:18:00 +0530 -Subject: [PATCH] platform/x86: amd-pmc: Add support for logging SMU metrics - -SMU provides a way to dump the s0ix debug statistics in the form of a -metrics table via a of set special mailbox commands. - -Add support to the driver which can send these commands to SMU and expose -the information received via debugfs. The information contains the s0ix -entry/exit, active time of each IP block etc. - -As a side note, SMU subsystem logging is not supported on Picasso based -SoC's. - -Signed-off-by: Shyam Sundar S K -Reviewed-by: Hans de Goede -Patchset: amd-s0ix ---- - drivers/platform/x86/amd-pmc.c | 147 +++++++++++++++++++++++++++++++-- - 1 file changed, 139 insertions(+), 8 deletions(-) - -diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c -index d08ff5907e4c..cfa5c44bb170 100644 ---- a/drivers/platform/x86/amd-pmc.c -+++ b/drivers/platform/x86/amd-pmc.c -@@ -46,6 +46,14 @@ - #define AMD_PMC_RESULT_CMD_UNKNOWN 0xFE - #define AMD_PMC_RESULT_FAILED 0xFF - -+/* SMU Message Definations */ -+#define SMU_MSG_GETSMUVERSION 0x02 -+#define SMU_MSG_LOG_GETDRAM_ADDR_HI 0x04 -+#define SMU_MSG_LOG_GETDRAM_ADDR_LO 0x05 -+#define SMU_MSG_LOG_START 0x06 -+#define SMU_MSG_LOG_RESET 0x07 -+#define SMU_MSG_LOG_DUMP_DATA 0x08 -+#define SMU_MSG_GET_SUP_CONSTRAINTS 0x09 - /* List of supported CPU ids */ - #define AMD_CPU_ID_RV 0x15D0 - #define AMD_CPU_ID_RN 0x1630 -@@ -55,17 +63,42 @@ - #define PMC_MSG_DELAY_MIN_US 100 - #define RESPONSE_REGISTER_LOOP_MAX 200 - -+#define SOC_SUBSYSTEM_IP_MAX 12 -+#define DELAY_MIN_US 2000 -+#define DELAY_MAX_US 3000 - enum amd_pmc_def { - MSG_TEST = 0x01, - MSG_OS_HINT_PCO, - MSG_OS_HINT_RN, - }; - -+struct amd_pmc_bit_map { -+ const char *name; -+ u32 bit_mask; -+}; -+ -+static const struct amd_pmc_bit_map soc15_ip_blk[] = { -+ {"DISPLAY", BIT(0)}, -+ {"CPU", BIT(1)}, -+ {"GFX", BIT(2)}, -+ {"VDD", BIT(3)}, -+ {"ACP", BIT(4)}, -+ {"VCN", BIT(5)}, -+ {"ISP", BIT(6)}, -+ {"NBIO", BIT(7)}, -+ {"DF", BIT(8)}, -+ {"USB0", BIT(9)}, -+ {"USB1", BIT(10)}, -+ {"LAPIC", BIT(11)}, -+ {} -+}; -+ - struct amd_pmc_dev { - void __iomem *regbase; -- void __iomem *smu_base; -+ void __iomem *smu_virt_addr; - u32 base_addr; - u32 cpu_id; -+ u32 active_ips; - struct device *dev; - struct mutex lock; /* generic mutex lock */ - #if IS_ENABLED(CONFIG_DEBUG_FS) -@@ -74,6 +107,7 @@ struct amd_pmc_dev { - }; - - static struct amd_pmc_dev pmc; -+static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set, u32 *data, u8 msg, bool ret); - - static inline u32 amd_pmc_reg_read(struct amd_pmc_dev *dev, int reg_offset) - { -@@ -85,9 +119,49 @@ static inline void amd_pmc_reg_write(struct amd_pmc_dev *dev, int reg_offset, u3 - iowrite32(val, dev->regbase + reg_offset); - } - -+struct smu_metrics { -+ u32 table_version; -+ u32 hint_count; -+ u32 s0i3_cyclecount; -+ u32 timein_s0i2; -+ u64 timeentering_s0i3_lastcapture; -+ u64 timeentering_s0i3_totaltime; -+ u64 timeto_resume_to_os_lastcapture; -+ u64 timeto_resume_to_os_totaltime; -+ u64 timein_s0i3_lastcapture; -+ u64 timein_s0i3_totaltime; -+ u64 timein_swdrips_lastcapture; -+ u64 timein_swdrips_totaltime; -+ u64 timecondition_notmet_lastcapture[SOC_SUBSYSTEM_IP_MAX]; -+ u64 timecondition_notmet_totaltime[SOC_SUBSYSTEM_IP_MAX]; -+} __packed; -+ - #ifdef CONFIG_DEBUG_FS - static int smu_fw_info_show(struct seq_file *s, void *unused) - { -+ struct amd_pmc_dev *dev = s->private; -+ struct smu_metrics table; -+ int idx; -+ -+ if (dev->cpu_id == AMD_CPU_ID_PCO) -+ return -EINVAL; -+ -+ memcpy_fromio(&table, dev->smu_virt_addr, sizeof(struct smu_metrics)); -+ -+ seq_puts(s, "\n=== SMU Statistics ===\n"); -+ seq_printf(s, "Table Version: %d\n", table.table_version); -+ seq_printf(s, "Hint Count: %d\n", table.hint_count); -+ seq_printf(s, "S0i3 Cycle Count: %d\n", table.s0i3_cyclecount); -+ seq_printf(s, "Time (in us) to S0i3: %lld\n", table.timeentering_s0i3_lastcapture); -+ seq_printf(s, "Time (in us) in S0i3: %lld\n", table.timein_s0i3_lastcapture); -+ -+ seq_puts(s, "\n=== Active time (in us) ===\n"); -+ for (idx = 0 ; idx < SOC_SUBSYSTEM_IP_MAX ; idx++) { -+ if (soc15_ip_blk[idx].bit_mask & dev->active_ips) -+ seq_printf(s, "%-8s : %lld\n", soc15_ip_blk[idx].name, -+ table.timecondition_notmet_lastcapture[idx]); -+ } -+ - return 0; - } - DEFINE_SHOW_ATTRIBUTE(smu_fw_info); -@@ -113,6 +187,32 @@ static inline void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev) - } - #endif /* CONFIG_DEBUG_FS */ - -+static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev) -+{ -+ u32 phys_addr_low, phys_addr_hi; -+ u64 smu_phys_addr; -+ -+ if (dev->cpu_id == AMD_CPU_ID_PCO) -+ return -EINVAL; -+ -+ /* Get Active devices list from SMU */ -+ amd_pmc_send_cmd(dev, 0, &dev->active_ips, SMU_MSG_GET_SUP_CONSTRAINTS, 1); -+ -+ /* Get dram address */ -+ amd_pmc_send_cmd(dev, 0, &phys_addr_low, SMU_MSG_LOG_GETDRAM_ADDR_LO, 1); -+ amd_pmc_send_cmd(dev, 0, &phys_addr_hi, SMU_MSG_LOG_GETDRAM_ADDR_HI, 1); -+ smu_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low); -+ -+ dev->smu_virt_addr = devm_ioremap(dev->dev, smu_phys_addr, sizeof(struct smu_metrics)); -+ if (!dev->smu_virt_addr) -+ return -ENOMEM; -+ -+ /* Start the logging */ -+ amd_pmc_send_cmd(dev, 0, NULL, SMU_MSG_LOG_START, 0); -+ -+ return 0; -+} -+ - static void amd_pmc_dump_registers(struct amd_pmc_dev *dev) - { - u32 value; -@@ -127,10 +227,9 @@ static void amd_pmc_dump_registers(struct amd_pmc_dev *dev) - dev_dbg(dev->dev, "AMD_PMC_REGISTER_MESSAGE:%x\n", value); - } - --static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) -+static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set, u32 *data, u8 msg, bool ret) - { - int rc; -- u8 msg; - u32 val; - - mutex_lock(&dev->lock); -@@ -150,8 +249,8 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) - amd_pmc_reg_write(dev, AMD_PMC_REGISTER_ARGUMENT, set); - - /* Write message ID to message ID register */ -- msg = (dev->cpu_id == AMD_CPU_ID_RN) ? MSG_OS_HINT_RN : MSG_OS_HINT_PCO; - amd_pmc_reg_write(dev, AMD_PMC_REGISTER_MESSAGE, msg); -+ - /* Wait until we get a valid response */ - rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, - val, val != 0, PMC_MSG_DELAY_MIN_US, -@@ -163,6 +262,11 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) - - switch (val) { - case AMD_PMC_RESULT_OK: -+ if (ret) { -+ /* PMFW may take longer time to return back the data */ -+ usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); -+ *data = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT); -+ } - break; - case AMD_PMC_RESULT_CMD_REJECT_BUSY: - dev_err(dev->dev, "SMU not ready. err: 0x%x\n", val); -@@ -186,12 +290,29 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) - return rc; - } - -+static int amd_pmc_get_os_hint(struct amd_pmc_dev *dev) -+{ -+ switch (dev->cpu_id) { -+ case AMD_CPU_ID_PCO: -+ return MSG_OS_HINT_PCO; -+ case AMD_CPU_ID_RN: -+ return MSG_OS_HINT_RN; -+ } -+ return -EINVAL; -+} -+ - static int __maybe_unused amd_pmc_suspend(struct device *dev) - { - struct amd_pmc_dev *pdev = dev_get_drvdata(dev); - int rc; -+ u8 msg; - -- rc = amd_pmc_send_cmd(pdev, 1); -+ /* Reset and Start SMU logging - to monitor the s0i3 stats */ -+ amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_RESET, 0); -+ amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_START, 0); -+ -+ msg = amd_pmc_get_os_hint(pdev); -+ rc = amd_pmc_send_cmd(pdev, 1, NULL, msg, 0); - if (rc) - dev_err(pdev->dev, "suspend failed\n"); - -@@ -202,8 +323,13 @@ static int __maybe_unused amd_pmc_resume(struct device *dev) - { - struct amd_pmc_dev *pdev = dev_get_drvdata(dev); - int rc; -+ u8 msg; - -- rc = amd_pmc_send_cmd(pdev, 0); -+ /* Let SMU know that we are looking for stats */ -+ amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_DUMP_DATA, 0); -+ -+ msg = amd_pmc_get_os_hint(pdev); -+ rc = amd_pmc_send_cmd(pdev, 0, NULL, msg, 0); - if (rc) - dev_err(pdev->dev, "resume failed\n"); - -@@ -226,8 +352,7 @@ static int amd_pmc_probe(struct platform_device *pdev) - { - struct amd_pmc_dev *dev = &pmc; - struct pci_dev *rdev; -- u32 base_addr_lo; -- u32 base_addr_hi; -+ u32 base_addr_lo, base_addr_hi; - u64 base_addr; - int err; - u32 val; -@@ -279,6 +404,12 @@ static int amd_pmc_probe(struct platform_device *pdev) - return -ENOMEM; - - mutex_init(&dev->lock); -+ -+ /* Use SMU to get the s0i3 debug stats */ -+ err = amd_pmc_setup_smu_logging(dev); -+ if (err) -+ dev_err(dev->dev, "SMU debugging info not supported on this platform\n"); -+ - platform_set_drvdata(pdev, dev); - amd_pmc_dbgfs_register(dev); - return 0; --- -2.33.0 - -From 1e7d8f464c1fed48df76d81f811cfa26d5aef0c6 Mon Sep 17 00:00:00 2001 -From: Shyam Sundar S K -Date: Tue, 29 Jun 2021 14:18:01 +0530 -Subject: [PATCH] amd-pmc: Add support for logging s0ix counters - -Even the FCH SSC registers provides certain level of information -about the s0ix entry and exit times which comes handy when the SMU -fails to report the statistics via the mailbox communication. - -This information is captured via a new debugfs file "s0ix_stats". -A non-zero entry in this counters would mean that the system entered -the s0ix state. - -If s0ix entry time and exit time don't change during suspend to idle, -the silicon has not entered the deepest state. - -Signed-off-by: Shyam Sundar S K -Reviewed-by: Hans de Goede -Patchset: amd-s0ix ---- - drivers/platform/x86/amd-pmc.c | 45 +++++++++++++++++++++++++++++++++- - 1 file changed, 44 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c -index cfa5c44bb170..d1db12e28b32 100644 ---- a/drivers/platform/x86/amd-pmc.c -+++ b/drivers/platform/x86/amd-pmc.c -@@ -46,6 +46,15 @@ - #define AMD_PMC_RESULT_CMD_UNKNOWN 0xFE - #define AMD_PMC_RESULT_FAILED 0xFF - -+/* FCH SSC Registers */ -+#define FCH_S0I3_ENTRY_TIME_L_OFFSET 0x30 -+#define FCH_S0I3_ENTRY_TIME_H_OFFSET 0x34 -+#define FCH_S0I3_EXIT_TIME_L_OFFSET 0x38 -+#define FCH_S0I3_EXIT_TIME_H_OFFSET 0x3C -+#define FCH_SSC_MAPPING_SIZE 0x800 -+#define FCH_BASE_PHY_ADDR_LOW 0xFED81100 -+#define FCH_BASE_PHY_ADDR_HIGH 0x00000000 -+ - /* SMU Message Definations */ - #define SMU_MSG_GETSMUVERSION 0x02 - #define SMU_MSG_LOG_GETDRAM_ADDR_HI 0x04 -@@ -96,6 +105,7 @@ static const struct amd_pmc_bit_map soc15_ip_blk[] = { - struct amd_pmc_dev { - void __iomem *regbase; - void __iomem *smu_virt_addr; -+ void __iomem *fch_virt_addr; - u32 base_addr; - u32 cpu_id; - u32 active_ips; -@@ -166,6 +176,29 @@ static int smu_fw_info_show(struct seq_file *s, void *unused) - } - DEFINE_SHOW_ATTRIBUTE(smu_fw_info); - -+static int s0ix_stats_show(struct seq_file *s, void *unused) -+{ -+ struct amd_pmc_dev *dev = s->private; -+ u64 entry_time, exit_time, residency; -+ -+ entry_time = ioread32(dev->fch_virt_addr + FCH_S0I3_ENTRY_TIME_H_OFFSET); -+ entry_time = entry_time << 32 | ioread32(dev->fch_virt_addr + FCH_S0I3_ENTRY_TIME_L_OFFSET); -+ -+ exit_time = ioread32(dev->fch_virt_addr + FCH_S0I3_EXIT_TIME_H_OFFSET); -+ exit_time = exit_time << 32 | ioread32(dev->fch_virt_addr + FCH_S0I3_EXIT_TIME_L_OFFSET); -+ -+ /* It's in 48MHz. We need to convert it */ -+ residency = (exit_time - entry_time) / 48; -+ -+ seq_puts(s, "=== S0ix statistics ===\n"); -+ seq_printf(s, "S0ix Entry Time: %lld\n", entry_time); -+ seq_printf(s, "S0ix Exit Time: %lld\n", exit_time); -+ seq_printf(s, "Residency Time: %lld\n", residency); -+ -+ return 0; -+} -+DEFINE_SHOW_ATTRIBUTE(s0ix_stats); -+ - static void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev) - { - debugfs_remove_recursive(dev->dbgfs_dir); -@@ -176,6 +209,8 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) - dev->dbgfs_dir = debugfs_create_dir("amd_pmc", NULL); - debugfs_create_file("smu_fw_info", 0644, dev->dbgfs_dir, dev, - &smu_fw_info_fops); -+ debugfs_create_file("s0ix_stats", 0644, dev->dbgfs_dir, dev, -+ &s0ix_stats_fops); - } - #else - static inline void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) -@@ -353,7 +388,7 @@ static int amd_pmc_probe(struct platform_device *pdev) - struct amd_pmc_dev *dev = &pmc; - struct pci_dev *rdev; - u32 base_addr_lo, base_addr_hi; -- u64 base_addr; -+ u64 base_addr, fch_phys_addr; - int err; - u32 val; - -@@ -405,6 +440,14 @@ static int amd_pmc_probe(struct platform_device *pdev) - - mutex_init(&dev->lock); - -+ /* Use FCH registers to get the S0ix stats */ -+ base_addr_lo = FCH_BASE_PHY_ADDR_LOW; -+ base_addr_hi = FCH_BASE_PHY_ADDR_HIGH; -+ fch_phys_addr = ((u64)base_addr_hi << 32 | base_addr_lo); -+ dev->fch_virt_addr = devm_ioremap(dev->dev, fch_phys_addr, FCH_SSC_MAPPING_SIZE); -+ if (!dev->fch_virt_addr) -+ return -ENOMEM; -+ - /* Use SMU to get the s0i3 debug stats */ - err = amd_pmc_setup_smu_logging(dev); - if (err) --- -2.33.0 - -From 7fb74e20ab2dca5d8f03c77c6d9e3bbdb7e9db81 Mon Sep 17 00:00:00 2001 -From: Shyam Sundar S K -Date: Tue, 29 Jun 2021 14:18:02 +0530 -Subject: [PATCH] platform/x86: amd-pmc: Add support for ACPI ID AMDI0006 - -Some newer BIOSes have added another ACPI ID for the uPEP device. -SMU statistics behave identically on this device. - -Signed-off-by: Shyam Sundar S K -Reviewed-by: Hans de Goede -Patchset: amd-s0ix ---- - drivers/platform/x86/amd-pmc.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c -index d1db12e28b32..b8740daecd7b 100644 ---- a/drivers/platform/x86/amd-pmc.c -+++ b/drivers/platform/x86/amd-pmc.c -@@ -469,6 +469,7 @@ static int amd_pmc_remove(struct platform_device *pdev) - - static const struct acpi_device_id amd_pmc_acpi_ids[] = { - {"AMDI0005", 0}, -+ {"AMDI0006", 0}, - {"AMD0004", 0}, - {"AMD0005", 0}, - { } --- -2.33.0 - -From acfa4cdf28630a705fa2df3ad9c6ea0fa7e03352 Mon Sep 17 00:00:00 2001 -From: Shyam Sundar S K -Date: Tue, 29 Jun 2021 14:18:03 +0530 -Subject: [PATCH] platform/x86: amd-pmc: Add new acpi id for future PMC - controllers - -The upcoming PMC controller would have a newer acpi id, add that to -the supported acpid device list. - -Signed-off-by: Shyam Sundar S K -Reviewed-by: Hans de Goede -Patchset: amd-s0ix ---- - drivers/platform/x86/amd-pmc.c | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c -index b8740daecd7b..267173b142c1 100644 ---- a/drivers/platform/x86/amd-pmc.c -+++ b/drivers/platform/x86/amd-pmc.c -@@ -68,6 +68,7 @@ - #define AMD_CPU_ID_RN 0x1630 - #define AMD_CPU_ID_PCO AMD_CPU_ID_RV - #define AMD_CPU_ID_CZN AMD_CPU_ID_RN -+#define AMD_CPU_ID_YC 0x14B5 - - #define PMC_MSG_DELAY_MIN_US 100 - #define RESPONSE_REGISTER_LOOP_MAX 200 -@@ -331,6 +332,7 @@ static int amd_pmc_get_os_hint(struct amd_pmc_dev *dev) - case AMD_CPU_ID_PCO: - return MSG_OS_HINT_PCO; - case AMD_CPU_ID_RN: -+ case AMD_CPU_ID_YC: - return MSG_OS_HINT_RN; - } - return -EINVAL; -@@ -376,6 +378,7 @@ static const struct dev_pm_ops amd_pmc_pm_ops = { - }; - - static const struct pci_device_id pmc_pci_ids[] = { -+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_YC) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_CZN) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RN) }, - { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PCO) }, -@@ -470,6 +473,7 @@ static int amd_pmc_remove(struct platform_device *pdev) - static const struct acpi_device_id amd_pmc_acpi_ids[] = { - {"AMDI0005", 0}, - {"AMDI0006", 0}, -+ {"AMDI0007", 0}, - {"AMD0004", 0}, - {"AMD0005", 0}, - { } --- -2.33.0 - diff --git a/patches/5.14/0001-surface3-oemb.patch b/patches/5.14/0001-surface3-oemb.patch deleted file mode 100644 index 95bbbf0b7..000000000 --- a/patches/5.14/0001-surface3-oemb.patch +++ /dev/null @@ -1,101 +0,0 @@ -From 0af29705fc39c579ece3a4985b27db35ae260487 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 18 Oct 2020 16:42:44 +0900 -Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI - table - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI data into dmi_system_id tables used -for quirks so that each driver can enable quirks even on the affected -systems. - -On affected systems, DMI data will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: surface3-oemb ---- - drivers/platform/surface/surface3-wmi.c | 7 +++++++ - sound/soc/codecs/rt5645.c | 9 +++++++++ - sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ - 3 files changed, 24 insertions(+) - -diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c -index fcd1d4fb94d5..ee26a5998b07 100644 ---- a/drivers/platform/surface/surface3-wmi.c -+++ b/drivers/platform/surface/surface3-wmi.c -@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - #endif - { } - }; -diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c -index 9408ee63cb26..5cac83953901 100644 ---- a/sound/soc/codecs/rt5645.c -+++ b/sound/soc/codecs/rt5645.c -@@ -3718,6 +3718,15 @@ static const struct dmi_system_id dmi_platform_data[] = { - }, - .driver_data = (void *)&intel_braswell_platform_data, - }, -+ { -+ .ident = "Microsoft Surface 3", -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)&intel_braswell_platform_data, -+ }, - { - /* - * Match for the GPDwin which unfortunately uses somewhat -diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -index 227424236fd5..1013a57be89a 100644 ---- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c -+++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .callback = cht_surface_quirk_cb, -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - { } - }; - --- -2.34.0 - diff --git a/patches/5.14/0002-mwifiex.patch b/patches/5.14/0002-mwifiex.patch deleted file mode 100644 index 9b7670116..000000000 --- a/patches/5.14/0002-mwifiex.patch +++ /dev/null @@ -1,2217 +0,0 @@ -From a940aae58d05eac46f61c0723861148ac326cd64 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 20 Aug 2021 16:20:49 +0200 -Subject: [PATCH] mwifiex: pcie: add DMI-based quirk implementation for Surface - devices -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -This commit adds the ability to apply device-specific quirks to the -mwifiex driver. It uses DMI matching similar to the quirks brcmfmac uses -with dmi.c. We'll add identifiers to match various MS Surface devices, -which this is primarily meant for, later. - -This commit is a slightly modified version of a previous patch sent in -by Tsuchiya Yuto. - -Co-developed-by: Tsuchiya Yuto -Signed-off-by: Tsuchiya Yuto -Signed-off-by: Jonas Dreßler -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/Makefile | 1 + - drivers/net/wireless/marvell/mwifiex/pcie.c | 4 ++ - drivers/net/wireless/marvell/mwifiex/pcie.h | 1 + - .../wireless/marvell/mwifiex/pcie_quirks.c | 38 +++++++++++++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.h | 20 ++++++++++ - 5 files changed, 64 insertions(+) - create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.c - create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.h - -diff --git a/drivers/net/wireless/marvell/mwifiex/Makefile b/drivers/net/wireless/marvell/mwifiex/Makefile -index 162d557b78af..2bd00f40958e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/Makefile -+++ b/drivers/net/wireless/marvell/mwifiex/Makefile -@@ -49,6 +49,7 @@ mwifiex_sdio-y += sdio.o - obj-$(CONFIG_MWIFIEX_SDIO) += mwifiex_sdio.o - - mwifiex_pcie-y += pcie.o -+mwifiex_pcie-y += pcie_quirks.o - obj-$(CONFIG_MWIFIEX_PCIE) += mwifiex_pcie.o - - mwifiex_usb-y += usb.o -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 777c0bab65d5..9e5abd80c78e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -28,6 +28,7 @@ - #include "wmm.h" - #include "11n.h" - #include "pcie.h" -+#include "pcie_quirks.h" - - #define PCIE_VERSION "1.0" - #define DRV_NAME "Marvell mwifiex PCIe" -@@ -411,6 +412,9 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return ret; - } - -+ /* check quirks */ -+ mwifiex_initialize_quirks(card); -+ - if (mwifiex_add_card(card, &card->fw_done, &pcie_ops, - MWIFIEX_PCIE, &pdev->dev)) { - pr_err("%s failed\n", __func__); -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.h b/drivers/net/wireless/marvell/mwifiex/pcie.h -index 5ed613d65709..981e330c77d7 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.h -@@ -244,6 +244,7 @@ struct pcie_service_card { - unsigned long work_flags; - - bool pci_reset_ongoing; -+ unsigned long quirks; - }; - - static inline int -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -new file mode 100644 -index 000000000000..c1665ac5c5d9 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -0,0 +1,38 @@ -+/* -+ * NXP Wireless LAN device driver: PCIE and platform specific quirks -+ * -+ * This software file (the "File") is distributed by NXP -+ * under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc., -+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the -+ * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+#include -+ -+#include "pcie_quirks.h" -+ -+/* quirk table based on DMI matching */ -+static const struct dmi_system_id mwifiex_quirk_table[] = { -+ {} -+}; -+ -+void mwifiex_initialize_quirks(struct pcie_service_card *card) -+{ -+ struct pci_dev *pdev = card->dev; -+ const struct dmi_system_id *dmi_id; -+ -+ dmi_id = dmi_first_match(mwifiex_quirk_table); -+ if (dmi_id) -+ card->quirks = (uintptr_t)dmi_id->driver_data; -+ -+ if (!card->quirks) -+ dev_info(&pdev->dev, "no quirks enabled\n"); -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -new file mode 100644 -index 000000000000..18eacc8c2d3a ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -0,0 +1,20 @@ -+/* -+ * NXP Wireless LAN device driver: PCIE and platform specific quirks -+ * -+ * This software file (the "File") is distributed by NXP -+ * under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc., -+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the -+ * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+#include "pcie.h" -+ -+void mwifiex_initialize_quirks(struct pcie_service_card *card); --- -2.34.0 - -From 18e0425d01bc1f46acd12b24153797ebfab820a8 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Fri, 20 Aug 2021 16:20:50 +0200 -Subject: [PATCH] mwifiex: pcie: add reset_d3cold quirk for Surface gen4+ - devices -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -To reset mwifiex on Surface gen4+ (Pro 4 or later gen) devices, it -seems that putting the wifi device into D3cold is required according -to errata.inf file on Windows installation (Windows/INF/errata.inf). - -This patch adds a function that performs power-cycle (put into D3cold -then D0) and call the function at the end of reset_prepare(). - -Note: Need to also reset the parent device (bridge) of wifi on SB1; -it might be because the bridge of wifi always reports it's in D3hot. -When I tried to reset only the wifi device (not touching parent), it gave -the following error and the reset failed: - - acpi device:4b: Cannot transition to power state D0 for parent in D3hot - mwifiex_pcie 0000:03:00.0: can't change power state from D3cold to D0 (config space inaccessible) - -Signed-off-by: Tsuchiya Yuto -Signed-off-by: Jonas Dreßler -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 + - .../wireless/marvell/mwifiex/pcie_quirks.c | 123 ++++++++++++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.h | 3 + - 3 files changed, 133 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 9e5abd80c78e..c3f5583ea70d 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -529,6 +529,13 @@ static void mwifiex_pcie_reset_prepare(struct pci_dev *pdev) - mwifiex_shutdown_sw(adapter); - clear_bit(MWIFIEX_IFACE_WORK_DEVICE_DUMP, &card->work_flags); - clear_bit(MWIFIEX_IFACE_WORK_CARD_RESET, &card->work_flags); -+ -+ /* On MS Surface gen4+ devices FLR isn't effective to recover from -+ * hangups, so we power-cycle the card instead. -+ */ -+ if (card->quirks & QUIRK_FW_RST_D3COLD) -+ mwifiex_pcie_reset_d3cold_quirk(pdev); -+ - mwifiex_dbg(adapter, INFO, "%s, successful\n", __func__); - - card->pci_reset_ongoing = true; -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index c1665ac5c5d9..0234cf3c2974 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -21,6 +21,72 @@ - - /* quirk table based on DMI matching */ - static const struct dmi_system_id mwifiex_quirk_table[] = { -+ { -+ .ident = "Surface Pro 4", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ }, -+ { -+ .ident = "Surface Pro 5", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ }, -+ { -+ .ident = "Surface Pro 5 (LTE)", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ }, -+ { -+ .ident = "Surface Pro 6", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ }, -+ { -+ .ident = "Surface Book 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ }, -+ { -+ .ident = "Surface Book 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ }, -+ { -+ .ident = "Surface Laptop 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ }, -+ { -+ .ident = "Surface Laptop 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ }, - {} - }; - -@@ -35,4 +101,61 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - - if (!card->quirks) - dev_info(&pdev->dev, "no quirks enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_D3COLD) -+ dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+} -+ -+static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -+{ -+ dev_info(&pdev->dev, "putting into D3cold...\n"); -+ -+ pci_save_state(pdev); -+ if (pci_is_enabled(pdev)) -+ pci_disable_device(pdev); -+ pci_set_power_state(pdev, PCI_D3cold); -+} -+ -+static int mwifiex_pcie_set_power_d0(struct pci_dev *pdev) -+{ -+ int ret; -+ -+ dev_info(&pdev->dev, "putting into D0...\n"); -+ -+ pci_set_power_state(pdev, PCI_D0); -+ ret = pci_enable_device(pdev); -+ if (ret) { -+ dev_err(&pdev->dev, "pci_enable_device failed\n"); -+ return ret; -+ } -+ pci_restore_state(pdev); -+ -+ return 0; -+} -+ -+int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) -+{ -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); -+ int ret; -+ -+ /* Power-cycle (put into D3cold then D0) */ -+ dev_info(&pdev->dev, "Using reset_d3cold quirk to perform FW reset\n"); -+ -+ /* We need to perform power-cycle also for bridge of wifi because -+ * on some devices (e.g. Surface Book 1), the OS for some reasons -+ * can't know the real power state of the bridge. -+ * When tried to power-cycle only wifi, the reset failed with the -+ * following dmesg log: -+ * "Cannot transition to power state D0 for parent in D3hot". -+ */ -+ mwifiex_pcie_set_power_d3cold(pdev); -+ mwifiex_pcie_set_power_d3cold(parent_pdev); -+ -+ ret = mwifiex_pcie_set_power_d0(parent_pdev); -+ if (ret) -+ return ret; -+ ret = mwifiex_pcie_set_power_d0(pdev); -+ if (ret) -+ return ret; -+ -+ return 0; - } -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 18eacc8c2d3a..8ec4176d698f 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -17,4 +17,7 @@ - - #include "pcie.h" - -+#define QUIRK_FW_RST_D3COLD BIT(0) -+ - void mwifiex_initialize_quirks(struct pcie_service_card *card); -+int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.34.0 - -From 300fd6232caf7ca7bc5158d1744550432127d0ea Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:32:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_wsid quirk for Surface 3 - -This commit adds reset_wsid quirk and uses this quirk for Surface 3 on -card reset. - -To reset mwifiex on Surface 3, it seems that calling the _DSM method -exists in \_SB.WSID [1] device is required. - -On Surface 3, calling the _DSM method removes/re-probes the card by -itself. So, need to place the reset function before performing FLR and -skip performing any other reset-related works. - -Note that Surface Pro 3 also has the WSID device [2], but it seems to need -more work. This commit only supports Surface 3 yet. - -[1] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_3/dsdt.dsl#L11947-L12011 -[2] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_pro_3/dsdt.dsl#L12164-L12216 - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 10 +++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 83 +++++++++++++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.h | 6 ++ - 3 files changed, 99 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index c3f5583ea70d..3f5138008594 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -2993,6 +2993,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - -+ /* On Surface 3, reset_wsid method removes then re-probes card by -+ * itself. So, need to place it here and skip performing any other -+ * reset-related works. -+ */ -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) { -+ mwifiex_pcie_reset_wsid_quirk(card->dev); -+ /* skip performing any other reset-related works */ -+ return; -+ } -+ - /* We can't afford to wait here; remove() might be waiting on us. If we - * can't grab the device lock, maybe we'll get another chance later. - */ -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 0234cf3c2974..563dd0d5ac79 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -15,10 +15,21 @@ - * this warranty disclaimer. - */ - -+#include - #include - - #include "pcie_quirks.h" - -+/* For reset_wsid quirk */ -+#define ACPI_WSID_PATH "\\_SB.WSID" -+#define WSID_REV 0x0 -+#define WSID_FUNC_WIFI_PWR_OFF 0x1 -+#define WSID_FUNC_WIFI_PWR_ON 0x2 -+/* WSID _DSM UUID: "534ea3bf-fcc2-4e7a-908f-a13978f0c7ef" */ -+static const guid_t wsid_dsm_guid = -+ GUID_INIT(0x534ea3bf, 0xfcc2, 0x4e7a, -+ 0x90, 0x8f, 0xa1, 0x39, 0x78, 0xf0, 0xc7, 0xef); -+ - /* quirk table based on DMI matching */ - static const struct dmi_system_id mwifiex_quirk_table[] = { - { -@@ -87,6 +98,14 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - {} - }; - -@@ -103,6 +122,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - dev_info(&pdev->dev, "no quirks enabled\n"); - if (card->quirks & QUIRK_FW_RST_D3COLD) - dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) -+ dev_info(&pdev->dev, -+ "quirk reset_wsid for Surface 3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -@@ -159,3 +181,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) - - return 0; - } -+ -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev) -+{ -+ acpi_handle handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n"); -+ -+ status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&pdev->dev, "No ACPI handle for path %s\n", -+ ACPI_WSID_PATH); -+ return -ENODEV; -+ } -+ -+ if (!acpi_has_method(handle, "_DSM")) { -+ dev_err(&pdev->dev, "_DSM method not found\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power off func\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power on func\n"); -+ return -ENODEV; -+ } -+ -+ /* card will be removed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi off...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi off\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ /* card will be re-probed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi on...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi on\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ return 0; -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 8ec4176d698f..25370c5a4f59 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -19,5 +19,11 @@ - - #define QUIRK_FW_RST_D3COLD BIT(0) - -+/* Surface 3 and Surface Pro 3 have the same _DSM method but need to -+ * be handled differently. Currently, only S3 is supported. -+ */ -+#define QUIRK_FW_RST_WSID_S3 BIT(1) -+ - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); --- -2.34.0 - -From c053005c26429a00fb5ada483d198870cfc1866e Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Wed, 30 Sep 2020 18:08:24 +0900 -Subject: [PATCH] mwifiex: pcie: (OEMB) add quirk for Surface 3 with broken DMI - table - -(made referring to http://git.osdn.net/view?p=android-x86/kernel.git;a=commitdiff;h=18e2e857c57633b25b3b4120f212224a108cd883) - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI info for the affected Surface 3. - -On affected systems, DMI info will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie_quirks.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 563dd0d5ac79..32e2f000e57b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -106,6 +106,15 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - {} - }; - --- -2.34.0 - -From 2e3db01cb53b96250df039fdca6e43c0d91957a2 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 4 Oct 2020 00:11:49 +0900 -Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ - -Currently, mwifiex fw will crash after suspend on recent kernel series. -On Windows, it seems that the root port of wifi will never enter D3 state -(stay on D0 state). And on Linux, disabling the D3 state for the -bridge fixes fw crashing after suspend. - -This commit disables the D3 state of root port on driver initialization -and fixes fw crashing after suspend. - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 27 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 3f5138008594..372dde99725c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -380,6 +380,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) - { - struct pcie_service_card *card; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - int ret; - - pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", -@@ -421,6 +422,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return -1; - } - -+ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing -+ * after suspend -+ */ -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ parent_pdev->bridge_d3 = false; -+ - return 0; - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 32e2f000e57b..356401bab59c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -38,7 +38,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5", -@@ -47,7 +48,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -56,7 +58,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 6", -@@ -64,7 +67,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 1", -@@ -72,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 2", -@@ -80,7 +85,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 1", -@@ -88,7 +94,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 2", -@@ -96,7 +103,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface 3", -@@ -134,6 +142,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_FW_RST_WSID_S3) - dev_info(&pdev->dev, - "quirk reset_wsid for Surface 3 enabled\n"); -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ dev_info(&pdev->dev, -+ "quirk no_brigde_d3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 25370c5a4f59..a1de111ad1db 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -23,6 +23,7 @@ - * be handled differently. Currently, only S3 is supported. - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) -+#define QUIRK_NO_BRIDGE_D3 BIT(2) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.34.0 - -From a424a4dbba9af7cbb8c16f2dc3b1b1710779ed37 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 3 Nov 2020 13:28:04 +0100 -Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface - devices - -The most recent firmware of the 88W8897 card reports a hardcoded LTR -value to the system during initialization, probably as an (unsuccessful) -attempt of the developers to fix firmware crashes. This LTR value -prevents most of the Microsoft Surface devices from entering deep -powersaving states (either platform C-State 10 or S0ix state), because -the exit latency of that state would be higher than what the card can -tolerate. - -Turns out the card works just the same (including the firmware crashes) -no matter if that hardcoded LTR value is reported or not, so it's kind -of useless and only prevents us from saving power. - -To get rid of those hardcoded LTR reports, it's possible to reset the -PCI bridge device after initializing the cards firmware. I'm not exactly -sure why that works, maybe the power management subsystem of the PCH -resets its stored LTR values when doing a function level reset of the -bridge device. Doing the reset once after starting the wifi firmware -works very well, probably because the firmware only reports that LTR -value a single time during firmware startup. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 31 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 372dde99725c..586c79dc0a98 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -1781,9 +1781,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) - static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; -+ struct pci_dev *pdev = card->dev; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; - int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; - -+ /* Trigger a function level reset of the PCI bridge device, this makes -+ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value -+ * that prevents the system from entering package C10 and S0ix powersaving -+ * states. -+ * We need to do it here because it must happen after firmware -+ * initialization and this function is called after that is done. -+ */ -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ pci_reset_function(parent_pdev); -+ - /* Write the RX ring read pointer in to reg->rx_rdptr */ - if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | - tx_wrap)) { -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 356401bab59c..6437f067d07a 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -39,7 +39,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5", -@@ -49,7 +50,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -59,7 +61,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 6", -@@ -68,7 +71,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 1", -@@ -77,7 +81,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 2", -@@ -86,7 +91,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 1", -@@ -95,7 +101,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 2", -@@ -104,7 +111,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface 3", -@@ -145,6 +153,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_NO_BRIDGE_D3) - dev_info(&pdev->dev, - "quirk no_brigde_d3 enabled\n"); -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index a1de111ad1db..0e429779bb04 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -24,6 +24,7 @@ - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) - #define QUIRK_NO_BRIDGE_D3 BIT(2) -+#define QUIRK_DO_FLR_ON_BRIDGE BIT(3) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.34.0 - -From 6dba993c14b79425f20362983b91afc002e043a3 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 25 Mar 2021 11:33:02 +0100 -Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell - 88W8897 - -The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) -is used in a lot of Microsoft Surface devices, and all those devices -suffer from very low 2.4GHz wifi connection speeds while bluetooth is -enabled. The reason for that is that the default passive scanning -interval for Bluetooth Low Energy devices is quite high in Linux -(interval of 60 msec and scan window of 30 msec, see hci_core.c), and -the Marvell chip is known for its bad bt+wifi coexisting performance. - -So decrease that passive scan interval and make the scan window shorter -on this particular device to allow for spending more time transmitting -wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and -the new scan window is 6.25 msec (0xa * 0,625 msec). - -This change has a very large impact on the 2.4GHz wifi speeds and gets -it up to performance comparable with the Windows driver, which seems to -apply a similar quirk. - -The interval and window length were tested and found to work very well -with a lot of Bluetooth Low Energy devices, including the Surface Pen, a -Bluetooth Speaker and two modern Bluetooth headphones. All devices were -discovered immediately after turning them on. Even lower values were -also tested, but they introduced longer delays until devices get -discovered. - -Patchset: mwifiex ---- - drivers/bluetooth/btusb.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index bd37d6fb88c2..d12fb2034d46 100644 ---- a/drivers/bluetooth/btusb.c -+++ b/drivers/bluetooth/btusb.c -@@ -61,6 +61,7 @@ static struct usb_driver btusb_driver; - #define BTUSB_VALID_LE_STATES 0x800000 - #define BTUSB_QCA_WCN6855 0x1000000 - #define BTUSB_INTEL_NEWGEN 0x2000000 -+#define BTUSB_LOWER_LESCAN_INTERVAL BIT(26) - - static const struct usb_device_id btusb_table[] = { - /* Generic Bluetooth USB device */ -@@ -359,6 +360,7 @@ static const struct usb_device_id blacklist_table[] = { - { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, -+ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, - - /* Intel Bluetooth devices */ - { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_NEW | -@@ -4697,6 +4699,19 @@ static int btusb_probe(struct usb_interface *intf, - if (id->driver_info & BTUSB_MARVELL) - hdev->set_bdaddr = btusb_set_bdaddr_marvell; - -+ /* The Marvell 88W8897 combined wifi and bluetooth card is known for -+ * very bad bt+wifi coexisting performance. -+ * -+ * Decrease the passive BT Low Energy scan interval a bit -+ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter -+ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly -+ * higher wifi throughput while passively scanning for BT LE devices. -+ */ -+ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { -+ hdev->le_scan_interval = 0x0190; -+ hdev->le_scan_window = 0x000a; -+ } -+ - if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && - (id->driver_info & BTUSB_MEDIATEK)) { - hdev->setup = btusb_mtk_setup; --- -2.34.0 - -From 1655948416d489cb030d188741b9e146160edefa Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 12:31:26 +0100 -Subject: [PATCH] mwifiex: Small cleanup for handling virtual interface type - changes - -Handle the obvious invalid virtual interface type changes with a general -check instead of looking at the individual change. - -For type changes from P2P_CLIENT to P2P_GO and the other way round, this -changes the behavior slightly: We now still do nothing, but return --EOPNOTSUPP instead of 0. Now that behavior was incorrect before and -still is, because type changes between these two types are actually -possible and supported, which we'll fix in a following commit. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 39 +++++++------------ - 1 file changed, 14 insertions(+), 25 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 97f0f39364d6..dd30d21edc01 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1145,6 +1145,20 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return -EBUSY; - } - -+ if (type == NL80211_IFTYPE_UNSPECIFIED) { -+ mwifiex_dbg(priv->adapter, INFO, -+ "%s: no new type specified, keeping old type %d\n", -+ dev->name, curr_iftype); -+ return 0; -+ } -+ -+ if (curr_iftype == type) { -+ mwifiex_dbg(priv->adapter, INFO, -+ "%s: interface already is of type %d\n", -+ dev->name, curr_iftype); -+ return 0; -+ } -+ - switch (curr_iftype) { - case NL80211_IFTYPE_ADHOC: - switch (type) { -@@ -1164,12 +1178,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as IBSS\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_ADHOC: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1195,12 +1203,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as STA\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_STATION: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1218,12 +1220,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_P2P_GO: - return mwifiex_change_vif_to_p2p(dev, curr_iftype, - type, params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as AP\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_AP: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1244,13 +1240,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as P2P\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", --- -2.34.0 - -From cbc06e933a2721afa63d5f405a89554a8a15d7a4 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 10 Nov 2020 12:49:56 +0100 -Subject: [PATCH] mwifiex: Use non-posted PCI register writes - -On the 88W8897 card it's very important the TX ring write pointer is -updated correctly to its new value before setting the TX ready -interrupt, otherwise the firmware appears to crash (probably because -it's trying to DMA-read from the wrong place). - -Since PCI uses "posted writes" when writing to a register, it's not -guaranteed that a write will happen immediately. That means the pointer -might be outdated when setting the TX ready interrupt, leading to -firmware crashes especially when ASPM L1 and L1 substates are enabled -(because of the higher link latency, the write will probably take -longer). - -So fix those firmware crashes by always forcing non-posted writes. We do -that by simply reading back the register after writing it, just as a lot -of other drivers do. - -There are two reproducers that are fixed with this patch: - -1) During rx/tx traffic and with ASPM L1 substates enabled (the enabled -substates are platform dependent), the firmware crashes and eventually a -command timeout appears in the logs. That crash is fixed by using a -non-posted write in mwifiex_pcie_send_data(). - -2) When sending lots of commands to the card, waking it up from sleep in -very quick intervals, the firmware eventually crashes. That crash -appears to be fixed by some other non-posted write included here. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 586c79dc0a98..f87bc9bdfba7 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -238,6 +238,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) - - iowrite32(data, card->pci_mmap1 + reg); - -+ /* Do a read-back, which makes the write non-posted, ensuring the -+ * completion before returning. -+ * The firmware of the 88W8897 card is buggy and this avoids crashes. -+ */ -+ ioread32(card->pci_mmap1 + reg); -+ - return 0; - } - --- -2.34.0 - -From b7b240b7281c94e45c950a93277aa3cb3e70e474 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 12:44:39 +0100 -Subject: [PATCH] mwifiex: Use function to check whether interface type change - is allowed - -Instead of bailing out in the function which is supposed to do the type -change, detect invalid changes beforehand using a generic function and -return an error if the change is not allowed. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 139 ++++++++++++------ - 1 file changed, 92 insertions(+), 47 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index dd30d21edc01..e4d44705c827 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -943,6 +943,76 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, - return 0; - } - -+static bool -+is_vif_type_change_allowed(struct mwifiex_adapter *adapter, -+ enum nl80211_iftype old_iftype, -+ enum nl80211_iftype new_iftype) -+{ -+ switch (old_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_STATION: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_AP: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return adapter->curr_iface_comb.sta_intf != -+ adapter->iface_limit.sta_intf; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ default: -+ break; -+ } -+ -+ return false; -+} -+ - static int - mwifiex_change_vif_to_p2p(struct net_device *dev, - enum nl80211_iftype curr_iftype, -@@ -959,13 +1029,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - - adapter = priv->adapter; - -- if (adapter->curr_iface_comb.p2p_intf == -- adapter->iface_limit.p2p_intf) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple P2P ifaces\n"); -- return -1; -- } -- - mwifiex_dbg(adapter, INFO, - "%s: changing role to p2p\n", dev->name); - -@@ -1031,15 +1094,6 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - - adapter = priv->adapter; - -- if ((curr_iftype != NL80211_IFTYPE_P2P_CLIENT && -- curr_iftype != NL80211_IFTYPE_P2P_GO) && -- (adapter->curr_iface_comb.sta_intf == -- adapter->iface_limit.sta_intf)) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple station/adhoc ifaces\n"); -- return -1; -- } -- - if (type == NL80211_IFTYPE_STATION) - mwifiex_dbg(adapter, INFO, - "%s: changing role to station\n", dev->name); -@@ -1090,13 +1144,6 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - - adapter = priv->adapter; - -- if (adapter->curr_iface_comb.uap_intf == -- adapter->iface_limit.uap_intf) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple AP ifaces\n"); -- return -1; -- } -- - mwifiex_dbg(adapter, INFO, - "%s: changing role to AP\n", dev->name); - -@@ -1159,6 +1206,13 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return 0; - } - -+ if (!is_vif_type_change_allowed(priv->adapter, curr_iftype, type)) { -+ mwifiex_dbg(priv->adapter, ERROR, -+ "%s: change from type %d to %d is not allowed\n", -+ dev->name, curr_iftype, type); -+ return -EOPNOTSUPP; -+ } -+ - switch (curr_iftype) { - case NL80211_IFTYPE_ADHOC: - switch (type) { -@@ -1179,12 +1233,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_STATION: - switch (type) { - case NL80211_IFTYPE_ADHOC: -@@ -1204,12 +1255,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_AP: - switch (type) { - case NL80211_IFTYPE_ADHOC: -@@ -1221,12 +1269,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_p2p(dev, curr_iftype, - type, params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_P2P_CLIENT: - case NL80211_IFTYPE_P2P_GO: - if (mwifiex_cfg80211_deinit_p2p(priv)) -@@ -1241,21 +1286,21 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: unknown iftype: %d\n", -- dev->name, dev->ieee80211_ptr->iftype); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } - - - return 0; -+ -+errnotsupp: -+ mwifiex_dbg(priv->adapter, ERROR, -+ "unsupported interface type transition: %d to %d\n", -+ curr_iftype, type); -+ return -EOPNOTSUPP; - } - - static void --- -2.34.0 - -From 7c13816b20777d631732398d48695f97cd62ee5e Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 14:42:54 +0100 -Subject: [PATCH] mwifiex: Use helper function for counting interface types - -Use a small helper function to increment and decrement the counter of -the interface types we currently manage. This makes the code that -actually changes and sets up the interface type a bit less messy and -also helps avoiding mistakes in case someone increments/decrements a -counter wrongly. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 110 ++++++------------ - 1 file changed, 35 insertions(+), 75 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index e4d44705c827..a688fd898564 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1013,6 +1013,32 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, - return false; - } - -+static void -+update_vif_type_counter(struct mwifiex_adapter *adapter, -+ enum nl80211_iftype iftype, -+ int change) -+{ -+ switch (iftype) { -+ case NL80211_IFTYPE_UNSPECIFIED: -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ adapter->curr_iface_comb.sta_intf += change; -+ break; -+ case NL80211_IFTYPE_AP: -+ adapter->curr_iface_comb.uap_intf += change; -+ break; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ adapter->curr_iface_comb.p2p_intf += change; -+ break; -+ default: -+ mwifiex_dbg(adapter, ERROR, -+ "%s: Unsupported iftype passed: %d\n", -+ __func__, iftype); -+ break; -+ } -+} -+ - static int - mwifiex_change_vif_to_p2p(struct net_device *dev, - enum nl80211_iftype curr_iftype, -@@ -1060,19 +1086,8 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.p2p_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; - - return 0; -@@ -1111,20 +1126,10 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.sta_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; -+ - return 0; - } - -@@ -1157,20 +1162,8 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.uap_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; - return 0; - } -@@ -3132,23 +3125,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, - mwifiex_dev_debugfs_init(priv); - #endif - -- switch (type) { -- case NL80211_IFTYPE_UNSPECIFIED: -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf++; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf++; -- break; -- case NL80211_IFTYPE_P2P_CLIENT: -- adapter->curr_iface_comb.p2p_intf++; -- break; -- default: -- /* This should be dead code; checked above */ -- mwifiex_dbg(adapter, ERROR, "type not supported\n"); -- return ERR_PTR(-EINVAL); -- } -+ update_vif_type_counter(adapter, type, +1); - - return &priv->wdev; - -@@ -3214,24 +3191,7 @@ int mwifiex_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) - /* Clear the priv in adapter */ - priv->netdev = NULL; - -- switch (priv->bss_mode) { -- case NL80211_IFTYPE_UNSPECIFIED: -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- default: -- mwifiex_dbg(adapter, ERROR, -- "del_virtual_intf: type not supported\n"); -- break; -- } -+ update_vif_type_counter(adapter, priv->bss_mode, -1); - - priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; - --- -2.34.0 - -From 76cc02b935eccda3e2574033f7896235e96d89bb Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:56:58 +0100 -Subject: [PATCH] mwifiex: Update virtual interface counters right after - setting bss_type - -In mwifiex_init_new_priv_params() we update our private driver state to -reflect the currently selected virtual interface type. Most notably we -set the bss_mode to the mode we're going to put the firmware in. - -Now after we updated the driver state we actually start talking to the -firmware and instruct it to set up the new mode. Those commands can and -will sometimes fail, in which case we return with an error from -mwifiex_change_vif_to_*. We currently update our virtual interface type -counters after this return, which means the code is never reached when a -firmware error happens and we never update the counters. Since we have -updated our bss_mode earlier though, the counters now no longer reflect -the actual state of the driver. - -This will break things on the next virtual interface change, because the -virtual interface type we're switching away from didn't get its counter -incremented, and we end up decrementing a 0-counter. - -To fix this, simply update the virtual interface type counters right -after updating our driver structures, so that they are always in sync. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 25 +++++++++++-------- - 1 file changed, 14 insertions(+), 11 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index a688fd898564..2a938e8e0bb1 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1063,6 +1063,10 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; - -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - switch (type) { - case NL80211_IFTYPE_P2P_CLIENT: - if (mwifiex_cfg80211_init_p2p_client(priv)) -@@ -1086,10 +1090,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; -- - return 0; - } - -@@ -1120,16 +1120,17 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - return -1; - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; -+ -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, - HostCmd_ACT_GEN_SET, 0, NULL, true)) - return -1; - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; -- - return 0; - } - -@@ -1156,15 +1157,17 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - return -1; - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; -+ -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, - HostCmd_ACT_GEN_SET, 0, NULL, true)) - return -1; - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; - return 0; - } - /* --- -2.34.0 - -From d6abb383fe29b5f5b3096d85db224b68e719a75c Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 13:42:40 +0100 -Subject: [PATCH] mwifiex: Allow switching interface type from P2P_CLIENT to - P2P_GO - -It's possible to change virtual interface type between P2P_CLIENT and -P2P_GO, the card supports that just fine, and it happens for example -when using miracast with the miraclecast software. - -So allow type changes between P2P_CLIENT and P2P_GO and simply call into -mwifiex_change_vif_to_p2p(), which handles this just fine. We have to -call mwifiex_cfg80211_deinit_p2p() before though to make sure the old -p2p mode is properly uninitialized. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 36 +++++++++++++++++++ - 1 file changed, 36 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 2a938e8e0bb1..2a3f9ebb3182 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -994,11 +994,26 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, - } - - case NL80211_IFTYPE_P2P_CLIENT: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_P2P_GO: -+ return true; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ - case NL80211_IFTYPE_P2P_GO: - switch (new_iftype) { - case NL80211_IFTYPE_ADHOC: - case NL80211_IFTYPE_STATION: - return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ return true; - case NL80211_IFTYPE_AP: - return adapter->curr_iface_comb.uap_intf != - adapter->iface_limit.uap_intf; -@@ -1269,6 +1284,24 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - } - - case NL80211_IFTYPE_P2P_CLIENT: -+ if (mwifiex_cfg80211_deinit_p2p(priv)) -+ return -EFAULT; -+ -+ switch (type) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, -+ type, params); -+ case NL80211_IFTYPE_P2P_GO: -+ return mwifiex_change_vif_to_p2p(dev, curr_iftype, -+ type, params); -+ case NL80211_IFTYPE_AP: -+ return mwifiex_change_vif_to_ap(dev, curr_iftype, type, -+ params); -+ default: -+ goto errnotsupp; -+ } -+ - case NL80211_IFTYPE_P2P_GO: - if (mwifiex_cfg80211_deinit_p2p(priv)) - return -EFAULT; -@@ -1278,6 +1311,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); -+ case NL80211_IFTYPE_P2P_CLIENT: -+ return mwifiex_change_vif_to_p2p(dev, curr_iftype, -+ type, params); - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); --- -2.34.0 - -From b33a4b386ac29c11fa2d54b66794d270a82782a3 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:31:08 +0100 -Subject: [PATCH] mwifiex: Handle interface type changes from AP to STATION - -Looks like this case was simply overseen, so handle it, too. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 2a3f9ebb3182..0eb31201a82b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1272,6 +1272,7 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - switch (type) { - case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); - break; --- -2.34.0 - -From 994e91f48bc64f4c95f3da25b51a71bfa60baefb Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sat, 27 Mar 2021 12:19:14 +0100 -Subject: [PATCH] mwifiex: Fix copy-paste mistake when creating virtual - interface - -The BSS priority here for a new P2P_CLIENT device was accidentally set -to an enum that's certainly not meant for this. Since -MWIFIEX_BSS_ROLE_STA is 0 anyway, we can just set the bss_priority to 0 -instead here. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 0eb31201a82b..d62a20de3ada 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -3054,7 +3054,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, - priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - - priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II; -- priv->bss_priority = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_priority = 0; - priv->bss_role = MWIFIEX_BSS_ROLE_STA; - priv->bss_started = 0; - --- -2.34.0 - -From a790bedaa4f0b9e35e77a939564a645c2c110ad0 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:30:28 +0200 -Subject: [PATCH] mwifiex: Deactive host sleep using HSCFG after it was - activated manually - -When powersaving (so either wifi powersaving or deep sleep, depending on -which state the firmware is in) is disabled, the way the firmware goes -into host sleep is different: Usually the firmware implicitely enters -host sleep on the next SLEEP event we get when we configured host sleep -via HSCFG before. When powersaving is disabled though, there are no -SLEEP events, the way we enter host sleep in that case is different: The -firmware will send us a HS_ACT_REQ event and after that we "manually" -make the firmware enter host sleep by sending it another HSCFG command -with the action HS_ACTIVATE. - -Now waking up from host sleep appears to be different depending on -whether powersaving is enabled again: When powersaving is enabled, the -firmware implicitely leaves host sleep as soon as it wakes up and sends -us an AWAKE event. When powersaving is disabled though, it apparently -doesn't implicitely leave host sleep, but instead we need to send it a -HSCFG command with the HS_CONFIGURE action and the HS_CFG_CANCEL -condition. We didn't do that so far, which is why waking up from host -sleep was broken when powersaving is disabled. - -So add some additional state to mwifiex_adapter where we keep track of -whether host sleep was activated manually via HS_ACTIVATE, and if that -was the case, deactivate it manually again via HS_CFG_CANCEL. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cmdevt.c | 21 +++++++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.c | 18 ++++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmd.c | 4 ++++ - 4 files changed, 44 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cmdevt.c b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -index 3a11342a6bde..5487df8f994d 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cmdevt.c -+++ b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -@@ -608,6 +608,11 @@ int mwifiex_send_cmd(struct mwifiex_private *priv, u16 cmd_no, - return -1; - } - -+ if (priv->adapter->hs_activated_manually && -+ cmd_no != HostCmd_CMD_802_11_HS_CFG_ENH) { -+ mwifiex_cancel_hs(priv, MWIFIEX_ASYNC_CMD); -+ priv->adapter->hs_activated_manually = false; -+ } - - /* Get a new command node */ - cmd_node = mwifiex_get_cmd_node(adapter); -@@ -714,6 +719,15 @@ mwifiex_insert_cmd_to_pending_q(struct mwifiex_adapter *adapter, - } - } - -+ /* Same with exit host sleep cmd, luckily that can't happen at the same time as EXIT_PS */ -+ if (command == HostCmd_CMD_802_11_HS_CFG_ENH) { -+ struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = -+ &host_cmd->params.opt_hs_cfg; -+ -+ if (le16_to_cpu(hs_cfg->action) == HS_ACTIVATE) -+ add_tail = false; -+ } -+ - spin_lock_bh(&adapter->cmd_pending_q_lock); - if (add_tail) - list_add_tail(&cmd_node->list, &adapter->cmd_pending_q); -@@ -1216,6 +1230,13 @@ mwifiex_process_hs_config(struct mwifiex_adapter *adapter) - __func__); - - adapter->if_ops.wakeup(adapter); -+ -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv (adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - adapter->hs_activated = false; - clear_bit(MWIFIEX_IS_HS_CONFIGURED, &adapter->work_flags); - clear_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags); -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index 17399d4aa129..1fbf5ba1042b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -401,6 +401,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !adapter->scan_processing) && - !adapter->data_sent && - !skb_queue_empty(&adapter->tx_data_q)) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_process_tx_queue(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -@@ -418,6 +424,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !mwifiex_bypass_txlist_empty(adapter) && - !mwifiex_is_tdls_chan_switching - (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_process_bypass_tx(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -@@ -434,6 +446,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !adapter->data_sent && !mwifiex_wmm_lists_empty(adapter) && - !mwifiex_is_tdls_chan_switching - (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_wmm_process_tx(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 5923c5c14c8d..90012cbcfd15 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -986,6 +986,7 @@ struct mwifiex_adapter { - struct timer_list wakeup_timer; - struct mwifiex_hs_config_param hs_cfg; - u8 hs_activated; -+ u8 hs_activated_manually; - u16 hs_activate_wait_q_woken; - wait_queue_head_t hs_activate_wait_q; - u8 event_body[MAX_EVENT_SIZE]; -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -index 48ea00da1fc9..1e2798dce18f 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -@@ -396,6 +396,10 @@ mwifiex_cmd_802_11_hs_cfg(struct mwifiex_private *priv, - if (hs_activate) { - hs_cfg->action = cpu_to_le16(HS_ACTIVATE); - hs_cfg->params.hs_activate.resp_ctrl = cpu_to_le16(RESP_NEEDED); -+ -+ adapter->hs_activated_manually = true; -+ mwifiex_dbg(priv->adapter, CMD, -+ "cmd: Activating host sleep manually\n"); - } else { - hs_cfg->action = cpu_to_le16(HS_CONFIGURE); - hs_cfg->params.hs_config.conditions = hscfg_param->conditions; --- -2.34.0 - -From c64bf36ce3c3735979d3ab8acec70ebaa29b784f Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:23:05 +0200 -Subject: [PATCH] mwifiex: Add quirk to disable deep sleep with certain - hardware revision - -The 88W8897 pcie card with the hardware revision 20 apparently has a -hardware issue where the card wakes up from deep sleep randomly and very -often, somewhat depending on the card activity, maybe the hardware has a -floating wakeup pin or something. - -Those continuous wakeups prevent the card from entering host sleep when -the computer suspends. And because the host won't answer to events from -the card anymore while it's suspended, the firmwares internal -powersaving state machine seems to get confused and the card can't sleep -anymore at all after that. - -Since we can't work around that hardware bug in the firmware, let's -get the hardware revision string from the firmware and match it with -known bad revisions. Then disable auto deep sleep for those revisions, -which makes sure we no longer get those spurious wakeups. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/main.c | 14 ++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmdresp.c | 16 ++++++++++++++++ - 3 files changed, 31 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index 1fbf5ba1042b..be40813ffa5c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -226,6 +226,19 @@ static int mwifiex_process_rx(struct mwifiex_adapter *adapter) - return 0; - } - -+static void maybe_quirk_fw_disable_ds(struct mwifiex_adapter *adapter) -+{ -+ struct mwifiex_private *priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA); -+ struct mwifiex_ver_ext ver_ext; -+ -+ set_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &adapter->work_flags); -+ -+ memset(&ver_ext, 0, sizeof(ver_ext)); -+ ver_ext.version_str_sel = 1; -+ mwifiex_send_cmd(priv, HostCmd_CMD_VERSION_EXT, -+ HostCmd_ACT_GEN_GET, 0, &ver_ext, false); -+} -+ - /* - * The main process. - * -@@ -356,6 +369,7 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - if (adapter->hw_status == MWIFIEX_HW_STATUS_INIT_DONE) { - adapter->hw_status = MWIFIEX_HW_STATUS_READY; - mwifiex_init_fw_complete(adapter); -+ maybe_quirk_fw_disable_ds(adapter); - } - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 90012cbcfd15..1e829d84b1f6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -524,6 +524,7 @@ enum mwifiex_adapter_work_flags { - MWIFIEX_IS_SUSPENDED, - MWIFIEX_IS_HS_CONFIGURED, - MWIFIEX_IS_HS_ENABLING, -+ MWIFIEX_IS_REQUESTING_FW_VEREXT, - }; - - struct mwifiex_band_config { -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -index 6b5d35d9e69f..8e49ebca1847 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -@@ -708,6 +708,22 @@ static int mwifiex_ret_ver_ext(struct mwifiex_private *priv, - { - struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext; - -+ if (test_and_clear_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &priv->adapter->work_flags)) { -+ if (strncmp(ver_ext->version_str, "ChipRev:20, BB:9b(10.00), RF:40(21)", 128) == 0) { -+ struct mwifiex_ds_auto_ds auto_ds = { -+ .auto_ds = DEEP_SLEEP_OFF, -+ }; -+ -+ mwifiex_dbg(priv->adapter, MSG, -+ "Bad HW revision detected, disabling deep sleep\n"); -+ -+ mwifiex_send_cmd(priv, HostCmd_CMD_802_11_PS_MODE_ENH, -+ DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, false); -+ } -+ -+ return 0; -+ } -+ - if (version_ext) { - version_ext->version_str_sel = ver_ext->version_str_sel; - memcpy(version_ext->version_str, ver_ext->version_str, --- -2.34.0 - -From 9fbde98f4d7ef7391f1cf7b2a0267921ac4a1b5f Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 15:17:07 +0100 -Subject: [PATCH] mwifiex: Don't log error on suspend if wake-on-wlan is - disabled - -It's not an error if someone chooses to put their computer to sleep, not -wanting it to wake up because the person next door has just discovered -what a magic packet is. So change the loglevel of this annoying message -from ERROR to INFO. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index d62a20de3ada..18b1a6d54bc8 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -3494,7 +3494,7 @@ static int mwifiex_cfg80211_suspend(struct wiphy *wiphy, - } - - if (!wowlan) { -- mwifiex_dbg(adapter, ERROR, -+ mwifiex_dbg(adapter, INFO, - "None of the WOWLAN triggers enabled\n"); - ret = 0; - goto done; --- -2.34.0 - -From deb1509c987c2a79b420572a75e38c278ed2726b Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sun, 28 Mar 2021 21:42:54 +0200 -Subject: [PATCH] mwifiex: Log an error on command failure during key-material - upload - -Sometimes the KEY_MATERIAL command can fail with the 88W8897 firmware -(when this happens exactly seems pretty random). This appears to prevent -the access point from starting, so it seems like a good idea to log an -error in that case. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 ++++++++-- - 1 file changed, 8 insertions(+), 2 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 18b1a6d54bc8..c00791701d78 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -519,8 +519,14 @@ mwifiex_cfg80211_set_default_mgmt_key(struct wiphy *wiphy, - encrypt_key.is_igtk_def_key = true; - eth_broadcast_addr(encrypt_key.mac_addr); - -- return mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, -- HostCmd_ACT_GEN_SET, true, &encrypt_key, true); -+ if (mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, -+ HostCmd_ACT_GEN_SET, true, &encrypt_key, true)) { -+ mwifiex_dbg(priv->adapter, ERROR, -+ "Sending KEY_MATERIAL command failed\n"); -+ return -1; -+ } -+ -+ return 0; - } - - /* --- -2.34.0 - -From 86cacfe17db469a101e6ab87c5e97f6056fa7f18 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:44:03 +0200 -Subject: [PATCH] mwifiex: Fix an incorrect comment - -We're sending DELBA requests here, not ADDBA requests. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/11n.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c -index cf08a4af84d6..9ff2058bcd7e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/11n.c -+++ b/drivers/net/wireless/marvell/mwifiex/11n.c -@@ -125,7 +125,7 @@ int mwifiex_ret_11n_delba(struct mwifiex_private *priv, - tx_ba_tbl->ra); - } else { /* - * In case of failure, recreate the deleted stream in case -- * we initiated the ADDBA -+ * we initiated the DELBA - */ - if (!INITIATOR_BIT(del_ba_param_set)) - return 0; --- -2.34.0 - -From 2b49f347f1a72707d8acb038fceaf65869a1cb02 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:57:41 +0200 -Subject: [PATCH] mwifiex: Ignore BTCOEX events from the firmware - -The firmware of the pcie 88W8897 chip sends those events very -unreliably, which means we sometimes end up actually capping the window -size while bluetooth is disabled, artifically limiting wifi speeds even -though it's not needed. - -Since we can't fix the firmware, let's just ignore those events, it -seems that the Windows driver also doesn't change the rx/tx block ack -buffer sizes when bluetooth gets enabled or disabled, so this is -consistent with the Windows driver. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/sta_event.c | 4 +--- - 1 file changed, 1 insertion(+), 3 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_event.c b/drivers/net/wireless/marvell/mwifiex/sta_event.c -index 68c63268e2e6..933111a3511c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_event.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_event.c -@@ -1057,9 +1057,7 @@ int mwifiex_process_sta_event(struct mwifiex_private *priv) - adapter->event_skb); - break; - case EVENT_BT_COEX_WLAN_PARA_CHANGE: -- dev_dbg(adapter->dev, "EVENT: BT coex wlan param update\n"); -- mwifiex_bt_coex_wlan_param_update_event(priv, -- adapter->event_skb); -+ dev_dbg(adapter->dev, "EVENT: ignoring BT coex wlan param update\n"); - break; - case EVENT_RXBA_SYNC: - dev_dbg(adapter->dev, "EVENT: RXBA_SYNC\n"); --- -2.34.0 - diff --git a/patches/5.14/0003-ath10k.patch b/patches/5.14/0003-ath10k.patch deleted file mode 100644 index d4835a5f7..000000000 --- a/patches/5.14/0003-ath10k.patch +++ /dev/null @@ -1,121 +0,0 @@ -From 261da92e38fd3bd003c1e724edcc9322cc9b194f Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 27 Feb 2021 00:45:52 +0100 -Subject: [PATCH] ath10k: Add module parameters to override board files - -Some Surface devices, specifically the Surface Go and AMD version of the -Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better -with a different board file, as it seems that the firmeware included -upstream is buggy. - -As it is generally not a good idea to randomly overwrite files, let -alone doing so via packages, we add module parameters to override those -file names in the driver. This allows us to package/deploy the override -via a modprobe.d config. - -Signed-off-by: Maximilian Luz -Patchset: ath10k ---- - drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++ - 1 file changed, 58 insertions(+) - -diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c -index 64c7145b51a2..1e71a60cfb11 100644 ---- a/drivers/net/wireless/ath/ath10k/core.c -+++ b/drivers/net/wireless/ath/ath10k/core.c -@@ -35,6 +35,9 @@ static bool skip_otp; - static bool rawmode; - static bool fw_diag_log; - -+static char *override_board = ""; -+static char *override_board2 = ""; -+ - unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | - BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); - -@@ -47,6 +50,9 @@ module_param(rawmode, bool, 0644); - module_param(fw_diag_log, bool, 0644); - module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); - -+module_param(override_board, charp, 0644); -+module_param(override_board2, charp, 0644); -+ - MODULE_PARM_DESC(debug_mask, "Debugging mask"); - MODULE_PARM_DESC(uart_print, "Uart target debugging"); - MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); -@@ -55,6 +61,9 @@ MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); - MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); - MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); - -+MODULE_PARM_DESC(override_board, "Override for board.bin file"); -+MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); -+ - static const struct ath10k_hw_params ath10k_hw_params_list[] = { - { - .id = QCA988X_HW_2_0_VERSION, -@@ -826,6 +835,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) - return 0; - } - -+static const char *ath10k_override_board_fw_file(struct ath10k *ar, -+ const char *file) -+{ -+ if (strcmp(file, "board.bin") == 0) { -+ if (strcmp(override_board, "") == 0) -+ return file; -+ -+ if (strcmp(override_board, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", -+ override_board); -+ -+ return override_board; -+ } -+ -+ if (strcmp(file, "board-2.bin") == 0) { -+ if (strcmp(override_board2, "") == 0) -+ return file; -+ -+ if (strcmp(override_board2, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", -+ override_board2); -+ -+ return override_board2; -+ } -+ -+ return file; -+} -+ - static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - const char *dir, - const char *file) -@@ -840,6 +885,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - if (dir == NULL) - dir = "."; - -+ /* HACK: Override board.bin and board-2.bin files if specified. -+ * -+ * Some Surface devices perform better with a different board -+ * configuration. To this end, one would need to replace the board.bin -+ * file with the modified config and remove the board-2.bin file. -+ * Unfortunately, that's not a solution that we can easily package. So -+ * we add module options to perform these overrides here. -+ */ -+ -+ file = ath10k_override_board_fw_file(ar, file); -+ if (!file) -+ return ERR_PTR(-ENOENT); -+ - snprintf(filename, sizeof(filename), "%s/%s", dir, file); - ret = firmware_request_nowarn(&fw, filename, ar->dev); - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", --- -2.34.0 - diff --git a/patches/5.14/0004-ipts.patch b/patches/5.14/0004-ipts.patch deleted file mode 100644 index 01446905f..000000000 --- a/patches/5.14/0004-ipts.patch +++ /dev/null @@ -1,1503 +0,0 @@ -From c0c0233e107398277401acbdc68263b75e4ec764 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 30 Jul 2020 13:21:53 +0200 -Subject: [PATCH] misc: mei: Add missing IPTS device IDs - -Patchset: ipts ---- - drivers/misc/mei/hw-me-regs.h | 1 + - drivers/misc/mei/pci-me.c | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h -index 67bb6a25fd0a..d1cb94d3452e 100644 ---- a/drivers/misc/mei/hw-me-regs.h -+++ b/drivers/misc/mei/hw-me-regs.h -@@ -92,6 +92,7 @@ - #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ - - #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ -+#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ - #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ - - #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ -diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c -index 3a45aaf002ac..55b8ee30a03c 100644 ---- a/drivers/misc/mei/pci-me.c -+++ b/drivers/misc/mei/pci-me.c -@@ -96,6 +96,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { - {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, -+ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, --- -2.34.0 - -From d8fe8086fc5c53e0b4d24dbbcc1bb926482a9a78 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 6 Aug 2020 11:20:41 +0200 -Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus - -Based on linux-surface/intel-precise-touch@3f362c - -Signed-off-by: Dorian Stoll -Patchset: ipts ---- - drivers/misc/Kconfig | 1 + - drivers/misc/Makefile | 1 + - drivers/misc/ipts/Kconfig | 17 ++ - drivers/misc/ipts/Makefile | 12 ++ - drivers/misc/ipts/context.h | 47 +++++ - drivers/misc/ipts/control.c | 113 +++++++++++ - drivers/misc/ipts/control.h | 24 +++ - drivers/misc/ipts/mei.c | 125 ++++++++++++ - drivers/misc/ipts/protocol.h | 347 ++++++++++++++++++++++++++++++++++ - drivers/misc/ipts/receiver.c | 224 ++++++++++++++++++++++ - drivers/misc/ipts/receiver.h | 16 ++ - drivers/misc/ipts/resources.c | 128 +++++++++++++ - drivers/misc/ipts/resources.h | 17 ++ - drivers/misc/ipts/uapi.c | 208 ++++++++++++++++++++ - drivers/misc/ipts/uapi.h | 47 +++++ - 15 files changed, 1327 insertions(+) - create mode 100644 drivers/misc/ipts/Kconfig - create mode 100644 drivers/misc/ipts/Makefile - create mode 100644 drivers/misc/ipts/context.h - create mode 100644 drivers/misc/ipts/control.c - create mode 100644 drivers/misc/ipts/control.h - create mode 100644 drivers/misc/ipts/mei.c - create mode 100644 drivers/misc/ipts/protocol.h - create mode 100644 drivers/misc/ipts/receiver.c - create mode 100644 drivers/misc/ipts/receiver.h - create mode 100644 drivers/misc/ipts/resources.c - create mode 100644 drivers/misc/ipts/resources.h - create mode 100644 drivers/misc/ipts/uapi.c - create mode 100644 drivers/misc/ipts/uapi.h - -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index f4fb5c52b863..0e5a2fe6bffe 100644 ---- a/drivers/misc/Kconfig -+++ b/drivers/misc/Kconfig -@@ -464,4 +464,5 @@ source "drivers/misc/cardreader/Kconfig" - source "drivers/misc/habanalabs/Kconfig" - source "drivers/misc/uacce/Kconfig" - source "drivers/misc/pvpanic/Kconfig" -+source "drivers/misc/ipts/Kconfig" - endmenu -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index e92a56d4442f..7adce5540183 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -57,3 +57,4 @@ obj-$(CONFIG_HABANA_AI) += habanalabs/ - obj-$(CONFIG_UACCE) += uacce/ - obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o - obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o -+obj-$(CONFIG_MISC_IPTS) += ipts/ -diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig -new file mode 100644 -index 000000000000..83e2a930c396 ---- /dev/null -+++ b/drivers/misc/ipts/Kconfig -@@ -0,0 +1,17 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+ -+config MISC_IPTS -+ tristate "Intel Precise Touch & Stylus" -+ depends on INTEL_MEI -+ help -+ Say Y here if your system has a touchscreen using Intels -+ Precise Touch & Stylus (IPTS) technology. -+ -+ If unsure say N. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ipts. -+ -+ Building this driver alone will not give you a working touchscreen. -+ It only exposed a userspace API that can be used by a daemon to -+ receive and process data from the touchscreen hardware. -diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile -new file mode 100644 -index 000000000000..8f58b9adbc94 ---- /dev/null -+++ b/drivers/misc/ipts/Makefile -@@ -0,0 +1,12 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+# -+# Makefile for the IPTS touchscreen driver -+# -+ -+obj-$(CONFIG_MISC_IPTS) += ipts.o -+ipts-objs := control.o -+ipts-objs += mei.o -+ipts-objs += receiver.o -+ipts-objs += resources.o -+ipts-objs += uapi.o -+ -diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h -new file mode 100644 -index 000000000000..f4b06a2d3f72 ---- /dev/null -+++ b/drivers/misc/ipts/context.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTEXT_H_ -+#define _IPTS_CONTEXT_H_ -+ -+#include -+#include -+#include -+#include -+ -+#include "protocol.h" -+ -+enum ipts_host_status { -+ IPTS_HOST_STATUS_STARTING, -+ IPTS_HOST_STATUS_STARTED, -+ IPTS_HOST_STATUS_STOPPING, -+ IPTS_HOST_STATUS_STOPPED, -+}; -+ -+struct ipts_buffer_info { -+ u8 *address; -+ dma_addr_t dma_address; -+}; -+ -+struct ipts_context { -+ struct mei_cl_device *cldev; -+ struct device *dev; -+ -+ bool restart; -+ enum ipts_host_status status; -+ struct ipts_get_device_info_rsp device_info; -+ -+ struct ipts_buffer_info data[IPTS_BUFFERS]; -+ struct ipts_buffer_info doorbell; -+ -+ struct ipts_buffer_info feedback[IPTS_BUFFERS]; -+ struct ipts_buffer_info workqueue; -+ struct ipts_buffer_info host2me; -+}; -+ -+#endif /* _IPTS_CONTEXT_H_ */ -diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c -new file mode 100644 -index 000000000000..a1d1f97a13d7 ---- /dev/null -+++ b/drivers/misc/ipts/control.c -@@ -0,0 +1,113 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+#include "protocol.h" -+#include "resources.h" -+#include "uapi.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload, -+ size_t size) -+{ -+ int ret; -+ struct ipts_command cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_command)); -+ cmd.code = code; -+ -+ if (payload && size > 0) -+ memcpy(&cmd.payload, payload, size); -+ -+ ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size); -+ if (ret >= 0) -+ return 0; -+ -+ /* -+ * During shutdown the device might get pulled away from below our feet. -+ * Dont log an error in this case, because it will confuse people. -+ */ -+ if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING) -+ dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret); -+ -+ return ret; -+} -+ -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) -+{ -+ struct ipts_feedback_cmd cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); -+ cmd.buffer = buffer; -+ -+ return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, -+ sizeof(struct ipts_feedback_cmd)); -+} -+ -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value) -+{ -+ struct ipts_feedback_buffer *feedback; -+ -+ memset(ipts->host2me.address, 0, ipts->device_info.feedback_size); -+ feedback = (struct ipts_feedback_buffer *)ipts->host2me.address; -+ -+ feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; -+ feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; -+ feedback->buffer = IPTS_HOST2ME_BUFFER; -+ feedback->size = 2; -+ feedback->payload[0] = report; -+ feedback->payload[1] = value; -+ -+ return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER); -+} -+ -+int ipts_control_start(struct ipts_context *ipts) -+{ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Starting IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STARTING; -+ ipts->restart = false; -+ -+ ipts_uapi_link(ipts); -+ return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); -+} -+ -+int ipts_control_stop(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPING) -+ return -EBUSY; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Stopping IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STOPPING; -+ -+ ipts_uapi_unlink(); -+ ipts_resources_free(ipts); -+ -+ ret = ipts_control_send_feedback(ipts, 0); -+ if (ret == -ENODEV) -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ return ret; -+} -+ -+int ipts_control_restart(struct ipts_context *ipts) -+{ -+ if (ipts->restart) -+ return -EBUSY; -+ -+ ipts->restart = true; -+ return ipts_control_stop(ipts); -+} -diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h -new file mode 100644 -index 000000000000..2c44e9e0e99f ---- /dev/null -+++ b/drivers/misc/ipts/control.h -@@ -0,0 +1,24 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTROL_H_ -+#define _IPTS_CONTROL_H_ -+ -+#include -+ -+#include "context.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, -+ size_t size); -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value); -+int ipts_control_start(struct ipts_context *ipts); -+int ipts_control_restart(struct ipts_context *ipts); -+int ipts_control_stop(struct ipts_context *ipts); -+ -+#endif /* _IPTS_CONTROL_H_ */ -diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c -new file mode 100644 -index 000000000000..59ecf13e00d2 ---- /dev/null -+++ b/drivers/misc/ipts/mei.c -@@ -0,0 +1,125 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "receiver.h" -+#include "uapi.h" -+ -+static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev) -+{ -+ int ret; -+ -+ ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)); -+ if (!ret) -+ return 0; -+ -+ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); -+} -+ -+static int ipts_mei_probe(struct mei_cl_device *cldev, -+ const struct mei_cl_device_id *id) -+{ -+ int ret; -+ struct ipts_context *ipts; -+ -+ if (ipts_mei_set_dma_mask(cldev)) { -+ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n"); -+ return -EFAULT; -+ } -+ -+ ret = mei_cldev_enable(cldev); -+ if (ret) { -+ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); -+ return ret; -+ } -+ -+ ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); -+ if (!ipts) { -+ mei_cldev_disable(cldev); -+ return -ENOMEM; -+ } -+ -+ ipts->cldev = cldev; -+ ipts->dev = &cldev->dev; -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ mei_cldev_set_drvdata(cldev, ipts); -+ mei_cldev_register_rx_cb(cldev, ipts_receiver_callback); -+ -+ return ipts_control_start(ipts); -+} -+ -+static void ipts_mei_remove(struct mei_cl_device *cldev) -+{ -+ int i; -+ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); -+ -+ ipts_control_stop(ipts); -+ -+ for (i = 0; i < 20; i++) { -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ break; -+ -+ msleep(25); -+ } -+ -+ mei_cldev_disable(cldev); -+ kfree(ipts); -+} -+ -+static struct mei_cl_device_id ipts_mei_device_id_table[] = { -+ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, -+ {}, -+}; -+MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); -+ -+static struct mei_cl_driver ipts_mei_driver = { -+ .id_table = ipts_mei_device_id_table, -+ .name = "ipts", -+ .probe = ipts_mei_probe, -+ .remove = ipts_mei_remove, -+}; -+ -+static int __init ipts_mei_init(void) -+{ -+ int ret; -+ -+ ret = ipts_uapi_init(); -+ if (ret) -+ return ret; -+ -+ ret = mei_cldev_driver_register(&ipts_mei_driver); -+ if (ret) { -+ ipts_uapi_free(); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static void __exit ipts_mei_exit(void) -+{ -+ mei_cldev_driver_unregister(&ipts_mei_driver); -+ ipts_uapi_free(); -+} -+ -+MODULE_DESCRIPTION("IPTS touchscreen driver"); -+MODULE_AUTHOR("Dorian Stoll "); -+MODULE_LICENSE("GPL"); -+ -+module_init(ipts_mei_init); -+module_exit(ipts_mei_exit); -diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h -new file mode 100644 -index 000000000000..c3458904a94d ---- /dev/null -+++ b/drivers/misc/ipts/protocol.h -@@ -0,0 +1,347 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_PROTOCOL_H_ -+#define _IPTS_PROTOCOL_H_ -+ -+#include -+ -+/* -+ * The MEI client ID for IPTS functionality. -+ */ -+#define IPTS_MEI_UUID \ -+ UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, \ -+ 0x02, 0xae, 0x04) -+ -+/* -+ * Queries the device for vendor specific information. -+ * -+ * The command must not contain any payload. -+ * The response will contain struct ipts_get_device_info_rsp as payload. -+ */ -+#define IPTS_CMD_GET_DEVICE_INFO 0x00000001 -+#define IPTS_RSP_GET_DEVICE_INFO 0x80000001 -+ -+/* -+ * Sets the mode that IPTS will operate in. -+ * -+ * The command must contain struct ipts_set_mode_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MODE 0x00000002 -+#define IPTS_RSP_SET_MODE 0x80000002 -+ -+/* -+ * Configures the memory buffers that the ME will use -+ * for passing data to the host. -+ * -+ * The command must contain struct ipts_set_mem_window_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MEM_WINDOW 0x00000003 -+#define IPTS_RSP_SET_MEM_WINDOW 0x80000003 -+ -+/* -+ * Signals that the host is ready to receive data to the ME. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_READY_FOR_DATA 0x00000005 -+#define IPTS_RSP_READY_FOR_DATA 0x80000005 -+ -+/* -+ * Signals that a buffer can be refilled to the ME. -+ * -+ * The command must contain struct ipts_feedback_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_FEEDBACK 0x00000006 -+#define IPTS_RSP_FEEDBACK 0x80000006 -+ -+/* -+ * Resets the data flow from the ME to the hosts and -+ * clears the buffers that were set with SET_MEM_WINDOW. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007 -+#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007 -+ -+/* -+ * Instructs the ME to reset the touch sensor. -+ * -+ * The command must contain struct ipts_reset_sensor_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_RESET_SENSOR 0x0000000B -+#define IPTS_RSP_RESET_SENSOR 0x8000000B -+ -+/** -+ * enum ipts_status - Possible status codes returned by IPTS commands. -+ * @IPTS_STATUS_SUCCESS: Operation completed successfully. -+ * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. -+ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. -+ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. -+ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. -+ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. -+ * The host must wait for a response before sending another -+ * command of the same type. -+ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it -+ * has not been initialized yet, or the system is improperly -+ * configured. -+ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. -+ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. -+ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. -+ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. -+ * The host can ignore this error and attempt to continue. -+ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. -+ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. -+ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. -+ * @IPTS_STATUS_TIMEOUT: The operation timed out. -+ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. -+ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. -+ * Further progress is not possible. -+ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. -+ * The host can attempt to continue. -+ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. -+ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. -+ */ -+enum ipts_status { -+ IPTS_STATUS_SUCCESS = 0, -+ IPTS_STATUS_INVALID_PARAMS = 1, -+ IPTS_STATUS_ACCESS_DENIED = 2, -+ IPTS_STATUS_CMD_SIZE_ERROR = 3, -+ IPTS_STATUS_NOT_READY = 4, -+ IPTS_STATUS_REQUEST_OUTSTANDING = 5, -+ IPTS_STATUS_NO_SENSOR_FOUND = 6, -+ IPTS_STATUS_OUT_OF_MEMORY = 7, -+ IPTS_STATUS_INTERNAL_ERROR = 8, -+ IPTS_STATUS_SENSOR_DISABLED = 9, -+ IPTS_STATUS_COMPAT_CHECK_FAIL = 10, -+ IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, -+ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, -+ IPTS_STATUS_RESET_FAILED = 13, -+ IPTS_STATUS_TIMEOUT = 14, -+ IPTS_STATUS_TEST_MODE_FAIL = 15, -+ IPTS_STATUS_SENSOR_FAIL_FATAL = 16, -+ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, -+ IPTS_STATUS_INVALID_DEVICE_CAPS = 18, -+ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, -+}; -+ -+/* -+ * The amount of buffers that is used for IPTS -+ */ -+#define IPTS_BUFFERS 16 -+ -+/* -+ * The special buffer ID that is used for direct host2me feedback. -+ */ -+#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS -+ -+/** -+ * enum ipts_mode - Operation mode for IPTS hardware -+ * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. -+ * The data is received as a HID report with ID 64. -+ * @IPTS_MODE_MULTITOUCH: The "proper" operation mode for IPTS. It will return -+ * stylus data as well as capacitive heatmap touch data. -+ * This data needs to be processed in userspace. -+ */ -+enum ipts_mode { -+ IPTS_MODE_SINGLETOUCH = 0, -+ IPTS_MODE_MULTITOUCH = 1, -+}; -+ -+/** -+ * struct ipts_set_mode_cmd - Payload for the SET_MODE command. -+ * @mode: The mode that IPTS should operate in. -+ */ -+struct ipts_set_mode_cmd { -+ enum ipts_mode mode; -+ u8 reserved[12]; -+} __packed; -+ -+#define IPTS_WORKQUEUE_SIZE 8192 -+#define IPTS_WORKQUEUE_ITEM_SIZE 16 -+ -+/** -+ * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command. -+ * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses. -+ * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses. -+ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. -+ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. -+ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. -+ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. -+ * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses. -+ * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses. -+ * @host2me_addr_lower: Lower 32 bits of the host2me buffer address. -+ * @host2me_addr_upper: Upper 32 bits of the host2me buffer address. -+ * @workqueue_item_size: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) -+ * @workqueue_size: Magic value. (IPTS_WORKQUEUE_SIZE) -+ * -+ * The data buffers are buffers that get filled with touch data by the ME. -+ * The doorbell buffer is a u32 that gets incremented by the ME once a data -+ * buffer has been filled with new data. -+ * -+ * The other buffers are required for using GuC submission with binary -+ * firmware. Since support for GuC submission has been dropped from i915, -+ * they are not used anymore, but they need to be allocated and passed, -+ * otherwise the hardware will refuse to start. -+ */ -+struct ipts_set_mem_window_cmd { -+ u32 data_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 data_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 workqueue_addr_lower; -+ u32 workqueue_addr_upper; -+ u32 doorbell_addr_lower; -+ u32 doorbell_addr_upper; -+ u32 feedback_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 feedback_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 host2me_addr_lower; -+ u32 host2me_addr_upper; -+ u32 host2me_size; -+ u8 reserved1; -+ u8 workqueue_item_size; -+ u16 workqueue_size; -+ u8 reserved[32]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_cmd - Payload for the FEEDBACK command. -+ * @buffer: The buffer that the ME should refill. -+ */ -+struct ipts_feedback_cmd { -+ u32 buffer; -+ u8 reserved[12]; -+} __packed; -+ -+/** -+ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. -+ */ -+enum ipts_feedback_cmd_type { -+ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, -+ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, -+ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, -+}; -+ -+/** -+ * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains. -+ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. -+ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. -+ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. -+ */ -+enum ipts_feedback_data_type { -+ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, -+ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, -+ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, -+ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, -+ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, -+}; -+ -+/** -+ * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer. -+ * @cmd_type: A command that should be executed on the sensor. -+ * @size: The size of the payload to be written. -+ * @buffer: The ID of the buffer that contains this feedback data. -+ * @protocol: The protocol version of the EDS. -+ * @data_type: The type of payload that the buffer contains. -+ * @spi_offset: The offset at which to write the payload data. -+ * @payload: Payload for the feedback command, or 0 if no payload is sent. -+ */ -+struct ipts_feedback_buffer { -+ enum ipts_feedback_cmd_type cmd_type; -+ u32 size; -+ u32 buffer; -+ u32 protocol; -+ enum ipts_feedback_data_type data_type; -+ u32 spi_offset; -+ u8 reserved[40]; -+ u8 payload[]; -+} __packed; -+ -+/** -+ * enum ipts_reset_type - Possible ways of resetting the touch sensor -+ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. -+ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. -+ */ -+enum ipts_reset_type { -+ IPTS_RESET_TYPE_HARD = 0, -+ IPTS_RESET_TYPE_SOFT = 1, -+}; -+ -+/** -+ * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. -+ * @type: What type of reset should be performed. -+ */ -+struct ipts_reset_sensor_cmd { -+ enum ipts_reset_type type; -+ u8 reserved[4]; -+} __packed; -+ -+/** -+ * struct ipts_command - A message sent from the host to the ME. -+ * @code: The message code describing the command. (see IPTS_CMD_*) -+ * @payload: Payload for the command, or 0 if no payload is required. -+ */ -+struct ipts_command { -+ u32 code; -+ u8 payload[320]; -+} __packed; -+ -+/** -+ * struct ipts_device_info - Payload for the GET_DEVICE_INFO response. -+ * @vendor_id: Vendor ID of the touch sensor. -+ * @device_id: Device ID of the touch sensor. -+ * @hw_rev: Hardware revision of the touch sensor. -+ * @fw_rev: Firmware revision of the touch sensor. -+ * @data_size: Required size of one data buffer. -+ * @feedback_size: Required size of one feedback buffer. -+ * @mode: Current operation mode of IPTS. -+ * @max_contacts: The amount of concurrent touches supported by the sensor. -+ */ -+struct ipts_get_device_info_rsp { -+ u16 vendor_id; -+ u16 device_id; -+ u32 hw_rev; -+ u32 fw_rev; -+ u32 data_size; -+ u32 feedback_size; -+ enum ipts_mode mode; -+ u8 max_contacts; -+ u8 reserved[19]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_rsp - Payload for the FEEDBACK response. -+ * @buffer: The buffer that has received feedback. -+ */ -+struct ipts_feedback_rsp { -+ u32 buffer; -+} __packed; -+ -+/** -+ * struct ipts_response - A message sent from the ME to the host. -+ * @code: The message code describing the response. (see IPTS_RSP_*) -+ * @status: The status code returned by the command. -+ * @payload: Payload returned by the command. -+ */ -+struct ipts_response { -+ u32 code; -+ enum ipts_status status; -+ u8 payload[80]; -+} __packed; -+ -+#endif /* _IPTS_PROTOCOL_H_ */ -diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c -new file mode 100644 -index 000000000000..23dca13c2139 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.c -@@ -0,0 +1,224 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "resources.h" -+ -+/* -+ * Temporary parameter to guard gen7 multitouch mode. -+ * Remove once gen7 has stable iptsd support. -+ */ -+static bool gen7mt; -+module_param(gen7mt, bool, 0644); -+ -+static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_set_mode_cmd cmd; -+ -+ memcpy(&ipts->device_info, rsp->payload, -+ sizeof(struct ipts_get_device_info_rsp)); -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd)); -+ cmd.mode = IPTS_MODE_MULTITOUCH; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd, -+ sizeof(struct ipts_set_mode_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mode(struct ipts_context *ipts) -+{ -+ int i, ret; -+ struct ipts_set_mem_window_cmd cmd; -+ -+ ret = ipts_resources_alloc(ipts); -+ if (ret) { -+ dev_err(ipts->dev, "Failed to allocate resources\n"); -+ return ret; -+ } -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ cmd.data_buffer_addr_lower[i] = -+ lower_32_bits(ipts->data[i].dma_address); -+ -+ cmd.data_buffer_addr_upper[i] = -+ upper_32_bits(ipts->data[i].dma_address); -+ -+ cmd.feedback_buffer_addr_lower[i] = -+ lower_32_bits(ipts->feedback[i].dma_address); -+ -+ cmd.feedback_buffer_addr_upper[i] = -+ upper_32_bits(ipts->feedback[i].dma_address); -+ } -+ -+ cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); -+ cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); -+ -+ cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); -+ cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); -+ -+ cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); -+ cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); -+ -+ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; -+ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, -+ sizeof(struct ipts_set_mem_window_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ dev_info(ipts->dev, "Device %04hX:%04hX ready\n", -+ ipts->device_info.vendor_id, ipts->device_info.device_id); -+ ipts->status = IPTS_HOST_STATUS_STARTED; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); -+ if (ret) -+ return ret; -+ -+ if (!gen7mt) -+ return 0; -+ -+ return ipts_control_set_feature(ipts, 0x5, 0x1); -+} -+ -+static int ipts_receiver_handle_feedback(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_feedback_rsp feedback; -+ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPING) -+ return 0; -+ -+ memcpy(&feedback, rsp->payload, sizeof(feedback)); -+ -+ if (feedback.buffer < IPTS_BUFFERS - 1) -+ return ipts_control_send_feedback(ipts, feedback.buffer + 1); -+ -+ return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); -+} -+ -+static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) -+{ -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ if (ipts->restart) -+ return ipts_control_start(ipts); -+ -+ return 0; -+} -+ -+static bool ipts_receiver_sensor_was_reset(u32 status) -+{ -+ return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || -+ status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; -+} -+ -+static bool ipts_receiver_handle_error(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ bool error; -+ -+ switch (rsp->status) { -+ case IPTS_STATUS_SUCCESS: -+ case IPTS_STATUS_COMPAT_CHECK_FAIL: -+ error = false; -+ break; -+ case IPTS_STATUS_INVALID_PARAMS: -+ error = rsp->code != IPTS_RSP_FEEDBACK; -+ break; -+ case IPTS_STATUS_SENSOR_DISABLED: -+ error = ipts->status != IPTS_HOST_STATUS_STOPPING; -+ break; -+ default: -+ error = true; -+ break; -+ } -+ -+ if (!error) -+ return false; -+ -+ dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code, -+ rsp->status); -+ -+ if (ipts_receiver_sensor_was_reset(rsp->status)) { -+ dev_err(ipts->dev, "Sensor was reset\n"); -+ -+ if (ipts_control_restart(ipts)) -+ dev_err(ipts->dev, "Failed to restart IPTS\n"); -+ } -+ -+ return true; -+} -+ -+static void ipts_receiver_handle_response(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ int ret; -+ -+ if (ipts_receiver_handle_error(ipts, rsp)) -+ return; -+ -+ switch (rsp->code) { -+ case IPTS_RSP_GET_DEVICE_INFO: -+ ret = ipts_receiver_handle_get_device_info(ipts, rsp); -+ break; -+ case IPTS_RSP_SET_MODE: -+ ret = ipts_receiver_handle_set_mode(ipts); -+ break; -+ case IPTS_RSP_SET_MEM_WINDOW: -+ ret = ipts_receiver_handle_set_mem_window(ipts); -+ break; -+ case IPTS_RSP_FEEDBACK: -+ ret = ipts_receiver_handle_feedback(ipts, rsp); -+ break; -+ case IPTS_RSP_CLEAR_MEM_WINDOW: -+ ret = ipts_receiver_handle_clear_mem_window(ipts); -+ break; -+ default: -+ ret = 0; -+ break; -+ } -+ -+ if (!ret) -+ return; -+ -+ dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n", -+ rsp->code, ret); -+ -+ if (ipts_control_stop(ipts)) -+ dev_err(ipts->dev, "Failed to stop IPTS\n"); -+} -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev) -+{ -+ int ret; -+ struct ipts_response rsp; -+ struct ipts_context *ipts; -+ -+ ipts = mei_cldev_get_drvdata(cldev); -+ -+ ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response)); -+ if (ret <= 0) { -+ dev_err(ipts->dev, "Error while reading response: %d\n", ret); -+ return; -+ } -+ -+ ipts_receiver_handle_response(ipts, &rsp); -+} -diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h -new file mode 100644 -index 000000000000..7f075afa7ef8 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.h -@@ -0,0 +1,16 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RECEIVER_H_ -+#define _IPTS_RECEIVER_H_ -+ -+#include -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev); -+ -+#endif /* _IPTS_RECEIVER_H_ */ -diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c -new file mode 100644 -index 000000000000..8e3a2409e438 ---- /dev/null -+++ b/drivers/misc/ipts/resources.c -@@ -0,0 +1,128 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+ -+void ipts_resources_free(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, data_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ if (ipts->doorbell.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->doorbell.address, -+ ipts->doorbell.dma_address); -+ -+ ipts->doorbell.address = NULL; -+ ipts->doorbell.dma_address = 0; -+ } -+ -+ if (ipts->workqueue.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->workqueue.address, -+ ipts->workqueue.dma_address); -+ -+ ipts->workqueue.address = NULL; -+ ipts->workqueue.dma_address = 0; -+ } -+ -+ if (ipts->host2me.address) { -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ ipts->host2me.address, -+ ipts->host2me.dma_address); -+ -+ ipts->host2me.address = NULL; -+ ipts->host2me.dma_address = 0; -+ } -+} -+ -+int ipts_resources_alloc(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, data_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ ipts->doorbell.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->doorbell.dma_address, GFP_KERNEL); -+ -+ if (!ipts->doorbell.address) -+ goto release_resources; -+ -+ ipts->workqueue.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->workqueue.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ ipts->host2me.address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &ipts->host2me.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ return 0; -+ -+release_resources: -+ -+ ipts_resources_free(ipts); -+ return -ENOMEM; -+} -diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h -new file mode 100644 -index 000000000000..fdac0eee9156 ---- /dev/null -+++ b/drivers/misc/ipts/resources.h -@@ -0,0 +1,17 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RESOURCES_H_ -+#define _IPTS_RESOURCES_H_ -+ -+#include "context.h" -+ -+int ipts_resources_alloc(struct ipts_context *ipts); -+void ipts_resources_free(struct ipts_context *ipts); -+ -+#endif /* _IPTS_RESOURCES_H_ */ -diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c -new file mode 100644 -index 000000000000..598f0710ad64 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.c -@@ -0,0 +1,208 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "uapi.h" -+ -+struct ipts_uapi uapi; -+ -+static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count, -+ loff_t *offset) -+{ -+ int buffer; -+ int maxbytes; -+ struct ipts_context *ipts = uapi.ipts; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ maxbytes = ipts->device_info.data_size - *offset; -+ if (maxbytes <= 0 || count > maxbytes) -+ return -EINVAL; -+ -+ if (copy_to_user(buf, ipts->data[buffer].address + *offset, count)) -+ return -EFAULT; -+ -+ return count; -+} -+ -+static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ u8 ready = 0; -+ -+ if (ipts) -+ ready = ipts->status == IPTS_HOST_STATUS_STARTED; -+ -+ if (copy_to_user(buffer, &ready, sizeof(u8))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ struct ipts_device_info info; -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ info.vendor = ipts->device_info.vendor_id; -+ info.product = ipts->device_info.device_id; -+ info.version = ipts->device_info.fw_rev; -+ info.buffer_size = ipts->device_info.data_size; -+ info.max_contacts = ipts->device_info.max_contacts; -+ -+ if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts, -+ struct file *file) -+{ -+ int ret; -+ u32 buffer; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ ret = ipts_control_send_feedback(ipts, buffer); -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) -+{ -+ int ret; -+ struct ipts_reset_sensor_cmd cmd; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); -+ cmd.type = IPTS_RESET_TYPE_SOFT; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, -+ sizeof(struct ipts_reset_sensor_cmd)); -+ -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) -+{ -+ struct ipts_context *ipts = uapi.ipts; -+ -+ switch (cmd) { -+ case IPTS_IOCTL_GET_DEVICE_READY: -+ return ipts_uapi_ioctl_get_device_ready(ipts, arg); -+ case IPTS_IOCTL_GET_DEVICE_INFO: -+ return ipts_uapi_ioctl_get_device_info(ipts, arg); -+ case IPTS_IOCTL_GET_DOORBELL: -+ return ipts_uapi_ioctl_get_doorbell(ipts, arg); -+ case IPTS_IOCTL_SEND_FEEDBACK: -+ return ipts_uapi_ioctl_send_feedback(ipts, file); -+ case IPTS_IOCTL_SEND_RESET: -+ return ipts_uapi_ioctl_send_reset(ipts); -+ default: -+ return -ENOTTY; -+ } -+} -+ -+static const struct file_operations ipts_uapi_fops = { -+ .owner = THIS_MODULE, -+ .read = ipts_uapi_read, -+ .unlocked_ioctl = ipts_uapi_ioctl, -+#ifdef CONFIG_COMPAT -+ .compat_ioctl = ipts_uapi_ioctl, -+#endif -+}; -+ -+void ipts_uapi_link(struct ipts_context *ipts) -+{ -+ uapi.ipts = ipts; -+} -+ -+void ipts_uapi_unlink(void) -+{ -+ uapi.ipts = NULL; -+} -+ -+int ipts_uapi_init(void) -+{ -+ int i, major; -+ -+ alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts"); -+ uapi.class = class_create(THIS_MODULE, "ipts"); -+ -+ major = MAJOR(uapi.dev); -+ -+ cdev_init(&uapi.cdev, &ipts_uapi_fops); -+ uapi.cdev.owner = THIS_MODULE; -+ cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ device_create(uapi.class, NULL, MKDEV(major, i), NULL, -+ "ipts/%d", i); -+ } -+ -+ return 0; -+} -+ -+void ipts_uapi_free(void) -+{ -+ int i; -+ int major; -+ -+ major = MAJOR(uapi.dev); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) -+ device_destroy(uapi.class, MKDEV(major, i)); -+ -+ cdev_del(&uapi.cdev); -+ -+ unregister_chrdev_region(MKDEV(major, 0), MINORMASK); -+ class_destroy(uapi.class); -+} -diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h -new file mode 100644 -index 000000000000..53fb86a88f97 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_UAPI_H_ -+#define _IPTS_UAPI_H_ -+ -+#include -+ -+#include "context.h" -+ -+struct ipts_uapi { -+ dev_t dev; -+ struct class *class; -+ struct cdev cdev; -+ -+ struct ipts_context *ipts; -+}; -+ -+struct ipts_device_info { -+ __u16 vendor; -+ __u16 product; -+ __u32 version; -+ __u32 buffer_size; -+ __u8 max_contacts; -+ -+ /* For future expansion */ -+ __u8 reserved[19]; -+}; -+ -+#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8) -+#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info) -+#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) -+#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04) -+#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) -+ -+void ipts_uapi_link(struct ipts_context *ipts); -+void ipts_uapi_unlink(void); -+ -+int ipts_uapi_init(void); -+void ipts_uapi_free(void); -+ -+#endif /* _IPTS_UAPI_H_ */ --- -2.34.0 - diff --git a/patches/5.14/0005-surface-sam.patch b/patches/5.14/0005-surface-sam.patch deleted file mode 100644 index 4d614524e..000000000 --- a/patches/5.14/0005-surface-sam.patch +++ /dev/null @@ -1,2265 +0,0 @@ -From f6611d8d35d47d43b7aae454a36d69232c430958 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 28 Oct 2021 03:28:45 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add initial support - for Surface Pro 8 - -Add preliminary support for the Surface Pro 8 to the Surface Aggregator -registry. This includes battery/charger status and platform profile -support. - -In contrast to earlier Surface Pro generations, the keyboard cover is -now also connected via the Surface Aggregator Module (whereas it was -previously connected via USB or HID-over-I2C). To properly support the -HID devices of that cover, however, more changes regarding hot-removal -of Surface Aggregator client devices as well as a new device hub driver -are required. We will address those things in a follow-up series, so do -not add any HID device IDs just yet. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20211028012845.1887219-1-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../platform/surface/surface_aggregator_registry.c | 12 ++++++++++++ - 1 file changed, 12 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 1679811eff50..e70f4c63554e 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -228,6 +228,15 @@ static const struct software_node *ssam_node_group_sp7[] = { - NULL, - }; - -+static const struct software_node *ssam_node_group_sp8[] = { -+ &ssam_node_root, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, -+ &ssam_node_tmp_pprof, -+ /* TODO: Add support for keyboard cover. */ -+ NULL, -+}; -+ - - /* -- Device registry helper functions. ------------------------------------- */ - -@@ -534,6 +543,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { - /* Surface Pro 7+ */ - { "MSHW0119", (unsigned long)ssam_node_group_sp7 }, - -+ /* Surface Pro 8 */ -+ { "MSHW0263", (unsigned long)ssam_node_group_sp8 }, -+ - /* Surface Book 2 */ - { "MSHW0107", (unsigned long)ssam_node_group_gen5 }, - --- -2.34.0 - -From 11a1289287c02cfec4efbdd63f85dea6d68284f2 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 2 Jun 2021 03:34:06 +0200 -Subject: [PATCH] platform/surface: aggregator: Make client device removal more - generic - -Currently, there are similar functions defined in the Aggregator -Registry and the controller core. - -Make client device removal more generic and export it. We can then use -this function later on to remove client devices from device hubs as well -as the controller and avoid re-defining similar things. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/bus.c | 24 ++++++++-------------- - drivers/platform/surface/aggregator/bus.h | 3 --- - drivers/platform/surface/aggregator/core.c | 3 ++- - include/linux/surface_aggregator/device.h | 9 ++++++++ - 4 files changed, 19 insertions(+), 20 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -index 0169677c243e..eaa8626baead 100644 ---- a/drivers/platform/surface/aggregator/bus.c -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -376,27 +376,19 @@ static int ssam_remove_device(struct device *dev, void *_data) - } - - /** -- * ssam_controller_remove_clients() - Remove SSAM client devices registered as -- * direct children under the given controller. -- * @ctrl: The controller to remove all direct clients for. -+ * ssam_remove_clients() - Remove SSAM client devices registered as direct -+ * children under the given parent device. -+ * @dev: The (parent) device to remove all direct clients for. - * -- * Remove all SSAM client devices registered as direct children under the -- * given controller. Note that this only accounts for direct children of the -- * controller device. This does not take care of any client devices where the -- * parent device has been manually set before calling ssam_device_add. Refer -- * to ssam_device_add()/ssam_device_remove() for more details on those cases. -- * -- * To avoid new devices being added in parallel to this call, the main -- * controller lock (not statelock) must be held during this (and if -- * necessary, any subsequent deinitialization) call. -+ * Remove all SSAM client devices registered as direct children under the given -+ * device. Note that this only accounts for direct children of the device. -+ * Refer to ssam_device_add()/ssam_device_remove() for more details. - */ --void ssam_controller_remove_clients(struct ssam_controller *ctrl) -+void ssam_remove_clients(struct device *dev) - { -- struct device *dev; -- -- dev = ssam_controller_device(ctrl); - device_for_each_child_reverse(dev, NULL, ssam_remove_device); - } -+EXPORT_SYMBOL_GPL(ssam_remove_clients); - - /** - * ssam_bus_register() - Register and set-up the SSAM client device bus. -diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h -index ed032c2cbdb2..6964ee84e79c 100644 ---- a/drivers/platform/surface/aggregator/bus.h -+++ b/drivers/platform/surface/aggregator/bus.h -@@ -12,14 +12,11 @@ - - #ifdef CONFIG_SURFACE_AGGREGATOR_BUS - --void ssam_controller_remove_clients(struct ssam_controller *ctrl); -- - int ssam_bus_register(void); - void ssam_bus_unregister(void); - - #else /* CONFIG_SURFACE_AGGREGATOR_BUS */ - --static inline void ssam_controller_remove_clients(struct ssam_controller *ctrl) {} - static inline int ssam_bus_register(void) { return 0; } - static inline void ssam_bus_unregister(void) {} - -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -index 279d9df19c01..124df0100a9f 100644 ---- a/drivers/platform/surface/aggregator/core.c -+++ b/drivers/platform/surface/aggregator/core.c -@@ -22,6 +22,7 @@ - #include - - #include -+#include - - #include "bus.h" - #include "controller.h" -@@ -742,7 +743,7 @@ static void ssam_serial_hub_remove(struct serdev_device *serdev) - ssam_controller_lock(ctrl); - - /* Remove all client devices. */ -- ssam_controller_remove_clients(ctrl); -+ ssam_remove_clients(&serdev->dev); - - /* Act as if suspending to silence events. */ - status = ssam_ctrl_notif_display_off(ctrl); -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index f636c5310321..cc257097eb05 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -319,6 +319,15 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); - ssam_device_driver_unregister) - - -+/* -- Helpers for controller and hub devices. ------------------------------- */ -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+void ssam_remove_clients(struct device *dev); -+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+static inline void ssam_remove_clients(struct device *dev) {} -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+ - /* -- Helpers for client-device requests. ----------------------------------- */ - - /** --- -2.34.0 - -From 4a84f3d00f6fa73d2d423f737b31e574b5a5f30a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 02:06:38 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Use generic client - removal function - -Use generic client removal function introduced in the previous commit -instead of defining our own one. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 24 ++++--------------- - 1 file changed, 5 insertions(+), 19 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index e70f4c63554e..f6c639342b9d 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -258,20 +258,6 @@ static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) - return 0; - } - --static int ssam_hub_remove_devices_fn(struct device *dev, void *data) --{ -- if (!is_ssam_device(dev)) -- return 0; -- -- ssam_device_remove(to_ssam_device(dev)); -- return 0; --} -- --static void ssam_hub_remove_devices(struct device *parent) --{ -- device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn); --} -- - static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, - struct fwnode_handle *node) - { -@@ -317,7 +303,7 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c - - return 0; - err: -- ssam_hub_remove_devices(parent); -+ ssam_remove_clients(parent); - return status; - } - -@@ -414,7 +400,7 @@ static void ssam_base_hub_update_workfn(struct work_struct *work) - if (hub->state == SSAM_BASE_HUB_CONNECTED) - status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); - else -- ssam_hub_remove_devices(&hub->sdev->dev); -+ ssam_remove_clients(&hub->sdev->dev); - - if (status) - dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); -@@ -496,7 +482,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - err: - ssam_notifier_unregister(sdev->ctrl, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); -- ssam_hub_remove_devices(&sdev->dev); -+ ssam_remove_clients(&sdev->dev); - return status; - } - -@@ -508,7 +494,7 @@ static void ssam_base_hub_remove(struct ssam_device *sdev) - - ssam_notifier_unregister(sdev->ctrl, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); -- ssam_hub_remove_devices(&sdev->dev); -+ ssam_remove_clients(&sdev->dev); - } - - static const struct ssam_device_id ssam_base_hub_match[] = { -@@ -625,7 +611,7 @@ static int ssam_platform_hub_remove(struct platform_device *pdev) - { - const struct software_node **nodes = platform_get_drvdata(pdev); - -- ssam_hub_remove_devices(&pdev->dev); -+ ssam_remove_clients(&pdev->dev); - set_secondary_fwnode(&pdev->dev, NULL); - software_node_unregister_node_group(nodes); - return 0; --- -2.34.0 - -From a801735c964f387be196a2d63d22b030667d592e Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 02:07:33 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Rename device - registration function - -Rename the device registration function to better align names with the -newly introduced device removal function. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index f6c639342b9d..ce2bd88feeaa 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -283,8 +283,8 @@ static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ct - return status; - } - --static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl, -- struct fwnode_handle *node) -+static int ssam_hub_register_clients(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) - { - struct fwnode_handle *child; - int status; -@@ -398,7 +398,7 @@ static void ssam_base_hub_update_workfn(struct work_struct *work) - hub->state = state; - - if (hub->state == SSAM_BASE_HUB_CONNECTED) -- status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); -+ status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); - else - ssam_remove_clients(&hub->sdev->dev); - -@@ -597,7 +597,7 @@ static int ssam_platform_hub_probe(struct platform_device *pdev) - - set_secondary_fwnode(&pdev->dev, root); - -- status = ssam_hub_add_devices(&pdev->dev, ctrl, root); -+ status = ssam_hub_register_clients(&pdev->dev, ctrl, root); - if (status) { - set_secondary_fwnode(&pdev->dev, NULL); - software_node_unregister_node_group(nodes); --- -2.34.0 - -From b18657a4239bad3b786edb87311b130403a5a1fd Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 00:24:47 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow devices to be marked as - hot-removed - -Some SSAM devices, notably the keyboard cover (keyboard and touchpad) on -the Surface Pro 8, can be hot-removed. When this occurs, communication -with the device may fail and time out. This timeout can unnecessarily -block and slow down device removal and even cause issues when the -devices are detached and re-attached quickly. Thus, communication should -generally be avoided once hot-removal is detected. - -While we already remove a device as soon as we detect its (hot-)removal, -the corresponding device driver may still attempt to communicate with -the device during teardown. This is especially critical as communication -failure may also extend to disabling of events, which is typically done -at that stage. - -Add a flag to allow marking devices as hot-removed. This can then be -used during client driver teardown to check if any communication -attempts should be avoided. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/bus.c | 3 ++ - include/linux/surface_aggregator/device.h | 48 +++++++++++++++++++++-- - 2 files changed, 48 insertions(+), 3 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -index eaa8626baead..736cc43c9104 100644 ---- a/drivers/platform/surface/aggregator/bus.c -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -390,6 +390,9 @@ void ssam_remove_clients(struct device *dev) - } - EXPORT_SYMBOL_GPL(ssam_remove_clients); - -+ -+/* -- Bus registration. ----------------------------------------------------- */ -+ - /** - * ssam_bus_register() - Register and set-up the SSAM client device bus. - */ -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index cc257097eb05..491aa7e9f4bc 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -148,17 +148,30 @@ struct ssam_device_uid { - #define SSAM_SDEV(cat, tid, iid, fun) \ - SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) - -+/* -+ * enum ssam_device_flags - Flags for SSAM client devices. -+ * @SSAM_DEVICE_HOT_REMOVED_BIT: -+ * The device has been hot-removed. Further communication with it may time -+ * out and should be avoided. -+ */ -+enum ssam_device_flags { -+ SSAM_DEVICE_HOT_REMOVED_BIT = 0, -+}; -+ - /** - * struct ssam_device - SSAM client device. -- * @dev: Driver model representation of the device. -- * @ctrl: SSAM controller managing this device. -- * @uid: UID identifying the device. -+ * @dev: Driver model representation of the device. -+ * @ctrl: SSAM controller managing this device. -+ * @uid: UID identifying the device. -+ * @flags: Device state flags, see &enum ssam_device_flags. - */ - struct ssam_device { - struct device dev; - struct ssam_controller *ctrl; - - struct ssam_device_uid uid; -+ -+ unsigned long flags; - }; - - /** -@@ -240,6 +253,35 @@ struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, - int ssam_device_add(struct ssam_device *sdev); - void ssam_device_remove(struct ssam_device *sdev); - -+/** -+ * ssam_device_mark_hot_removed() - Mark the given device as hot-removed. -+ * @sdev: The device to mark as hot-removed. -+ * -+ * Mark the device as having been hot-removed. This signals drivers using the -+ * device that communication with the device should be avoided and may lead to -+ * timeouts. -+ */ -+static inline void ssam_device_mark_hot_removed(struct ssam_device *sdev) -+{ -+ dev_dbg(&sdev->dev, "marking device as hot-removed\n"); -+ set_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); -+} -+ -+/** -+ * ssam_device_is_hot_removed() - Check if the given device has been -+ * hot-removed. -+ * @sdev: The device to check. -+ * -+ * Checks if the given device has been marked as hot-removed. See -+ * ssam_device_mark_hot_removed() for more details. -+ * -+ * Return: Returns ``true`` if the device has been marked as hot-removed. -+ */ -+static inline bool ssam_device_is_hot_removed(struct ssam_device *sdev) -+{ -+ return test_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); -+} -+ - /** - * ssam_device_get() - Increment reference count of SSAM client device. - * @sdev: The device to increment the reference count of. --- -2.34.0 - -From f95d6ac5874db1bb881ffa482abc35f94bb13f5e Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 00:48:22 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow notifiers to avoid - communication on unregistering - -When SSAM client devices have been (physically) hot-removed, -communication attempts with those devices may fail and time out. This -can even extend to event notifiers, due to which timeouts may occur -during device removal, slowing down that process. - -Add a flag to the notifier unregister function that allows skipping -communication with the EC to prevent this. Furthermore, add wrappers for -registering and unregistering notifiers belonging to SSAM client devices -that automatically check if the device has been marked as hot-removed -and communication should be avoided. - -Note that non-SSAM client devices can generally not be hot-removed, so -also add a convenience wrapper for those, defaulting to allow -communication. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../driver-api/surface_aggregator/client.rst | 6 +- - .../platform/surface/aggregator/controller.c | 53 ++++++++++------ - include/linux/surface_aggregator/controller.h | 24 +++++++- - include/linux/surface_aggregator/device.h | 60 +++++++++++++++++++ - 4 files changed, 122 insertions(+), 21 deletions(-) - -diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst -index e519d374c378..27f95abdbe99 100644 ---- a/Documentation/driver-api/surface_aggregator/client.rst -+++ b/Documentation/driver-api/surface_aggregator/client.rst -@@ -17,6 +17,8 @@ - .. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` - .. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` - .. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` -+.. |ssam_device_notifier_register| replace:: :c:func:`ssam_device_notifier_register` -+.. |ssam_device_notifier_unregister| replace:: :c:func:`ssam_device_notifier_unregister` - .. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` - .. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` - -@@ -312,7 +314,9 @@ Handling Events - To receive events from the SAM EC, an event notifier must be registered for - the desired event via |ssam_notifier_register|. The notifier must be - unregistered via |ssam_notifier_unregister| once it is not required any --more. -+more. For |ssam_device| type clients, the |ssam_device_notifier_register| and -+|ssam_device_notifier_unregister| wrappers should be preferred as they properly -+handle hot-removal of client devices. - - Event notifiers are registered by providing (at minimum) a callback to call - in case an event has been received, the registry specifying how the event -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index b8c377b3f932..6de834b52b63 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2199,16 +2199,26 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, - } - - /** -- * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is -- * no longer in use and free the corresponding entry. -+ * ssam_nf_refcount_disable_free() - Disable event for reference count entry if -+ * it is no longer in use and free the corresponding entry. - * @ctrl: The controller to disable the event on. - * @entry: The reference count entry for the event to be disabled. - * @flags: The flags used for enabling the event on the EC. -+ * @ec: Flag specifying if the event should actually be disabled on the EC. - * -- * If the reference count equals zero, i.e. the event is no longer requested by -- * any client, the event will be disabled and the corresponding reference count -- * entry freed. The reference count entry must not be used any more after a -- * call to this function. -+ * If ``ec`` equals ``true`` and the reference count equals zero (i.e. the -+ * event is no longer requested by any client), the specified event will be -+ * disabled on the EC via the corresponding request. -+ * -+ * If ``ec`` equals ``false``, no request will be sent to the EC and the event -+ * can be considered in a detached state (i.e. no longer used but still -+ * enabled). Disabling an event via this method may be required for -+ * hot-removable devices, where event disable requests may time out after the -+ * device has been physically removed. -+ * -+ * In both cases, if the reference count equals zero, the corresponding -+ * reference count entry will be freed. The reference count entry must not be -+ * used any more after a call to this function. - * - * Also checks if the flags used for disabling the event match the flags used - * for enabling the event and warns if they do not (regardless of reference -@@ -2223,7 +2233,7 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, - * returns the status of the event-enable EC command. - */ - static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, -- struct ssam_nf_refcount_entry *entry, u8 flags) -+ struct ssam_nf_refcount_entry *entry, u8 flags, bool ec) - { - const struct ssam_event_registry reg = entry->key.reg; - const struct ssam_event_id id = entry->key.id; -@@ -2232,8 +2242,9 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - - lockdep_assert_held(&nf->lock); - -- ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -- reg.target_category, id.target_category, id.instance, entry->refcount); -+ ssam_dbg(ctrl, "%s event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ ec ? "disabling" : "detaching", reg.target_category, id.target_category, -+ id.instance, entry->refcount); - - if (entry->flags != flags) { - ssam_warn(ctrl, -@@ -2242,7 +2253,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - id.instance); - } - -- if (entry->refcount == 0) { -+ if (ec && entry->refcount == 0) { - status = ssam_ssh_event_disable(ctrl, reg, id, flags); - kfree(entry); - } -@@ -2322,20 +2333,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif - EXPORT_SYMBOL_GPL(ssam_notifier_register); - - /** -- * ssam_notifier_unregister() - Unregister an event notifier. -- * @ctrl: The controller the notifier has been registered on. -- * @n: The event notifier to unregister. -+ * __ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * @disable: Whether to disable the corresponding event on the EC. - * - * Unregister an event notifier. Decrement the usage counter of the associated - * SAM event if the notifier is not marked as an observer. If the usage counter -- * reaches zero, the event will be disabled. -+ * reaches zero and ``disable`` equals ``true``, the event will be disabled. -+ * -+ * Useful for hot-removable devices, where communication may fail once the -+ * device has been physically removed. In that case, specifying ``disable`` as -+ * ``false`` avoids communication with the EC. - * - * Return: Returns zero on success, %-ENOENT if the given notifier block has - * not been registered on the controller. If the given notifier block was the - * last one associated with its specific event, returns the status of the - * event-disable EC-command. - */ --int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) -+int __ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n, -+ bool disable) - { - u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); - struct ssam_nf_refcount_entry *entry; -@@ -2373,7 +2390,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - goto remove; - } - -- status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); -+ status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags, disable); - } - - remove: -@@ -2383,7 +2400,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - - return status; - } --EXPORT_SYMBOL_GPL(ssam_notifier_unregister); -+EXPORT_SYMBOL_GPL(__ssam_notifier_unregister); - - /** - * ssam_controller_event_enable() - Enable the specified event. -@@ -2477,7 +2494,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, - return -ENOENT; - } - -- status = ssam_nf_refcount_disable_free(ctrl, entry, flags); -+ status = ssam_nf_refcount_disable_free(ctrl, entry, flags, true); - - mutex_unlock(&nf->lock); - return status; -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index 74bfdffaf7b0..50a2b4926c06 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -835,8 +835,28 @@ struct ssam_event_notifier { - int ssam_notifier_register(struct ssam_controller *ctrl, - struct ssam_event_notifier *n); - --int ssam_notifier_unregister(struct ssam_controller *ctrl, -- struct ssam_event_notifier *n); -+int __ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n, bool disable); -+ -+/** -+ * ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+static inline int ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n) -+{ -+ return __ssam_notifier_unregister(ctrl, n, true); -+} - - int ssam_controller_event_enable(struct ssam_controller *ctrl, - struct ssam_event_registry reg, -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 491aa7e9f4bc..16816c34da3e 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -472,4 +472,64 @@ static inline void ssam_remove_clients(struct device *dev) {} - sdev->uid.instance, ret); \ - } - -+ -+/* -- Helpers for client-device notifiers. ---------------------------------- */ -+ -+/** -+ * ssam_device_notifier_register() - Register an event notifier for the -+ * specified client device. -+ * @sdev: The device the notifier should be registered on. -+ * @n: The event notifier to register. -+ * -+ * Register an event notifier. Increment the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the event is not -+ * marked as an observer and is currently not enabled, it will be enabled -+ * during this call. If the notifier is marked as an observer, no attempt will -+ * be made at enabling any event and no reference count will be modified. -+ * -+ * Notifiers marked as observers do not need to be associated with one specific -+ * event, i.e. as long as no event matching is performed, only the event target -+ * category needs to be set. -+ * -+ * Return: Returns zero on success, %-ENOSPC if there have already been -+ * %INT_MAX notifiers for the event ID/type associated with the notifier block -+ * registered, %-ENOMEM if the corresponding event entry could not be -+ * allocated, %-ENODEV if the device is marked as hot-removed. If this is the -+ * first time that a notifier block is registered for the specific associated -+ * event, returns the status of the event-enable EC-command. -+ */ -+static inline int ssam_device_notifier_register(struct ssam_device *sdev, -+ struct ssam_event_notifier *n) -+{ -+ if (ssam_device_is_hot_removed(sdev)) -+ return -ENODEV; -+ -+ return ssam_notifier_register(sdev->ctrl, n); -+} -+ -+/** -+ * ssam_device_notifier_unregister() - Unregister an event notifier for the -+ * specified client device. -+ * @sdev: The device the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. -+ * -+ * In case the device has been marked as hot-removed, the event will not be -+ * disabled on the EC, as in those cases any attempt at doing so may time out. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+static inline int ssam_device_notifier_unregister(struct ssam_device *sdev, -+ struct ssam_event_notifier *n) -+{ -+ return __ssam_notifier_unregister(sdev->ctrl, n, -+ !ssam_device_is_hot_removed(sdev)); -+} -+ - #endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ --- -2.34.0 - -From fe2e44cf86dee61dc84b56f76d432e8b87c2710a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 01:20:49 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Use client device - wrappers for notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index ce2bd88feeaa..9f630e890ff7 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -468,7 +468,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - - ssam_device_set_drvdata(sdev, hub); - -- status = ssam_notifier_register(sdev->ctrl, &hub->notif); -+ status = ssam_device_notifier_register(sdev, &hub->notif); - if (status) - return status; - -@@ -480,7 +480,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - return 0; - - err: -- ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_device_notifier_unregister(sdev, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); - return status; -@@ -492,7 +492,7 @@ static void ssam_base_hub_remove(struct ssam_device *sdev) - - sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); - -- ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_device_notifier_unregister(sdev, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); - } --- -2.34.0 - -From 05ded3f8005f37297e5c47821d43c24dc243250c Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 28 Oct 2021 03:37:06 +0200 -Subject: [PATCH] power/supply: surface_charger: Use client device wrappers for - notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/power/supply/surface_charger.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -index a060c36c7766..59182d55742d 100644 ---- a/drivers/power/supply/surface_charger.c -+++ b/drivers/power/supply/surface_charger.c -@@ -216,7 +216,7 @@ static int spwr_ac_register(struct spwr_ac_device *ac) - if (IS_ERR(ac->psy)) - return PTR_ERR(ac->psy); - -- return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); -+ return ssam_device_notifier_register(ac->sdev, &ac->notif); - } - - -@@ -251,7 +251,7 @@ static void surface_ac_remove(struct ssam_device *sdev) - { - struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); - -- ssam_notifier_unregister(sdev->ctrl, &ac->notif); -+ ssam_device_notifier_unregister(sdev, &ac->notif); - } - - static const struct spwr_psy_properties spwr_psy_props_adp1 = { --- -2.34.0 - -From 18b5e32f9cf968307c604677143eea7dec7a8533 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 28 Oct 2021 03:38:09 +0200 -Subject: [PATCH] power/supply: surface_battery: Use client device wrappers for - notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/power/supply/surface_battery.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -index 5ec2e6bb2465..540707882bb0 100644 ---- a/drivers/power/supply/surface_battery.c -+++ b/drivers/power/supply/surface_battery.c -@@ -802,7 +802,7 @@ static int spwr_battery_register(struct spwr_battery_device *bat) - if (IS_ERR(bat->psy)) - return PTR_ERR(bat->psy); - -- return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); -+ return ssam_device_notifier_register(bat->sdev, &bat->notif); - } - - -@@ -837,7 +837,7 @@ static void surface_battery_remove(struct ssam_device *sdev) - { - struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); - -- ssam_notifier_unregister(sdev->ctrl, &bat->notif); -+ ssam_device_notifier_unregister(sdev, &bat->notif); - cancel_delayed_work_sync(&bat->update_work); - } - --- -2.34.0 - -From 43c9f03affd1b3e0ecf04a370efe3ffa463a26c9 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 01:33:02 +0200 -Subject: [PATCH] HID: surface-hid: Add support for hot-removal - -Add support for hot-removal of SSAM HID client devices. - -Once a device has been hot-removed, further communication with it should -be avoided as it may fail and time out. While the device will be removed -as soon as we detect hot-removal, communication may still occur during -teardown, especially when unregistering notifiers. - -While hot-removal is a surprise event that can happen any time, try to -avoid communication as much as possible once it has been detected to -prevent timeouts that can slow down device removal and cause issues, -e.g. when quickly re-attaching the device. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/hid/surface-hid/surface_hid_core.c | 38 +++++++++++++++++++++- - 1 file changed, 37 insertions(+), 1 deletion(-) - -diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c -index 5571e74abe91..d2e695e942b6 100644 ---- a/drivers/hid/surface-hid/surface_hid_core.c -+++ b/drivers/hid/surface-hid/surface_hid_core.c -@@ -19,12 +19,30 @@ - #include "surface_hid_core.h" - - -+/* -- Utility functions. ---------------------------------------------------- */ -+ -+static bool surface_hid_is_hot_removed(struct surface_hid_device *shid) -+{ -+ /* -+ * Non-ssam client devices, i.e. platform client devices, cannot be -+ * hot-removed. -+ */ -+ if (!is_ssam_device(shid->dev)) -+ return false; -+ -+ return ssam_device_is_hot_removed(to_ssam_device(shid->dev)); -+} -+ -+ - /* -- Device descriptor access. --------------------------------------------- */ - - static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) - { - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, - (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); - if (status) -@@ -61,6 +79,9 @@ static int surface_hid_load_device_attributes(struct surface_hid_device *shid) - { - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, - (u8 *)&shid->attrs, sizeof(shid->attrs)); - if (status) -@@ -88,9 +109,18 @@ static int surface_hid_start(struct hid_device *hid) - static void surface_hid_stop(struct hid_device *hid) - { - struct surface_hid_device *shid = hid->driver_data; -+ bool hot_removed; -+ -+ /* -+ * Communication may fail for devices that have been hot-removed. This -+ * also includes unregistration of HID events, so we need to check this -+ * here. Only if the device has not been marked as hot-removed, we can -+ * safely disable events. -+ */ -+ hot_removed = surface_hid_is_hot_removed(shid); - - /* Note: This call will log errors for us, so ignore them here. */ -- ssam_notifier_unregister(shid->ctrl, &shid->notif); -+ __ssam_notifier_unregister(shid->ctrl, &shid->notif, !hot_removed); - } - - static int surface_hid_open(struct hid_device *hid) -@@ -109,6 +139,9 @@ static int surface_hid_parse(struct hid_device *hid) - u8 *buf; - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - buf = kzalloc(len, GFP_KERNEL); - if (!buf) - return -ENOMEM; -@@ -126,6 +159,9 @@ static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportn - { - struct surface_hid_device *shid = hid->driver_data; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) - return shid->ops.output_report(shid, reportnum, buf, len); - --- -2.34.0 - -From 4b49b3f485689ddf1e9fbb4280c7ab4a8eb405ff Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 31 Oct 2021 12:34:08 +0100 -Subject: [PATCH] platform/surface: aggregator: Add comment for KIP subsystem - category - -The KIP subsystem (full name unknown, abbreviation has been obtained -through reverse engineering) handles detachable peripherals such as the -keyboard cover on the Surface Pro X and Surface Pro 8. - -It is currently not entirely clear what this subsystem entails, but at -the very least it provides event notifications for when the keyboard -cover on the Surface Pro X and Surface Pro 8 have been detached or -re-attached, as well as the state that the keyboard cover is currently -in (e.g. folded-back, folded laptop-like, closed, etc.). - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - include/linux/surface_aggregator/serial_hub.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h -index c3de43edcffa..d1efac85caf1 100644 ---- a/include/linux/surface_aggregator/serial_hub.h -+++ b/include/linux/surface_aggregator/serial_hub.h -@@ -306,7 +306,7 @@ enum ssam_ssh_tc { - SSAM_SSH_TC_LPC = 0x0b, - SSAM_SSH_TC_TCL = 0x0c, - SSAM_SSH_TC_SFL = 0x0d, -- SSAM_SSH_TC_KIP = 0x0e, -+ SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ - SSAM_SSH_TC_EXT = 0x0f, - SSAM_SSH_TC_BLD = 0x10, - SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ --- -2.34.0 - -From e0933caec88e7140b057a8ce85a21b9797fd567c Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 10 Oct 2021 23:56:23 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add KIP device hub - -Add a Surface System Aggregator Module (SSAM) client device hub for -hot-removable devices managed via the KIP subsystem. - -The KIP subsystem (full name unknown, abbreviation has been obtained -through reverse engineering) is a subsystem that manages hot-removable -SSAM client devices. Specifically, it manages HID input devices -contained in the detachable keyboard cover of the Surface Pro 8 and -Surface Pro X. - -To properly handle detachable devices, we need to remove their kernel -representation when the physical device has been detached and (re-)add -and (re-)initialize said representation when the physical device has -been (re-)attached. Note that we need to hot-remove those devices, as -communication (especially during event notifier unregistration) may time -out when the physical device is no longer present, which would lead to -an unnecessary delay. This delay might become problematic when devices -are detached and re-attached quickly. - -The KIP subsystem handles a single group of devices (e.g. all devices -contained in the keyboard cover) and cannot handle devices individually. -Thus we model it as a client device hub, which removes all devices -contained under it once removal of the hub (e.g. keyboard cover) has -been detected and (re-)adds all devices once the physical hub device has -been (re-)attached. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 247 +++++++++++++++++- - 1 file changed, 245 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 9f630e890ff7..4838ce6519a6 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -514,6 +514,237 @@ static struct ssam_device_driver ssam_base_hub_driver = { - }; - - -+/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ -+ -+/* -+ * Some devices may need a bit of time to be fully usable after being -+ * (re-)connected. This delay has been determined via experimentation. -+ */ -+#define SSAM_KIP_UPDATE_CONNECT_DELAY msecs_to_jiffies(250) -+ -+#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c -+ -+enum ssam_kip_hub_state { -+ SSAM_KIP_HUB_UNINITIALIZED, -+ SSAM_KIP_HUB_CONNECTED, -+ SSAM_KIP_HUB_DISCONNECTED, -+}; -+ -+struct ssam_kip_hub { -+ struct ssam_device *sdev; -+ -+ enum ssam_kip_hub_state state; -+ struct delayed_work update_work; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_connection_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x2c, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_get_connection_state(struct ssam_kip_hub *hub, enum ssam_kip_hub_state *state) -+{ -+ int status; -+ u8 connected; -+ -+ status = ssam_retry(__ssam_kip_get_connection_state, hub->sdev->ctrl, &connected); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); -+ return status; -+ } -+ -+ *state = connected ? SSAM_KIP_HUB_CONNECTED : SSAM_KIP_HUB_DISCONNECTED; -+ return 0; -+} -+ -+static ssize_t ssam_kip_hub_state_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct ssam_kip_hub *hub = dev_get_drvdata(dev); -+ const char *state; -+ -+ switch (hub->state) { -+ case SSAM_KIP_HUB_UNINITIALIZED: -+ state = "uninitialized"; -+ break; -+ -+ case SSAM_KIP_HUB_CONNECTED: -+ state = "connected"; -+ break; -+ -+ case SSAM_KIP_HUB_DISCONNECTED: -+ state = "disconnected"; -+ break; -+ -+ default: -+ /* -+ * Any value not handled in the above cases is invalid and -+ * should never have been set. Thus this case should be -+ * impossible to reach. -+ */ -+ WARN(1, "invalid KIP hub state: %d\n", hub->state); -+ state = ""; -+ break; -+ } -+ -+ return sysfs_emit(buf, "%s\n", state); -+} -+ -+static struct device_attribute ssam_kip_hub_attr_state = -+ __ATTR(state, 0444, ssam_kip_hub_state_show, NULL); -+ -+static struct attribute *ssam_kip_hub_attrs[] = { -+ &ssam_kip_hub_attr_state.attr, -+ NULL, -+}; -+ -+static const struct attribute_group ssam_kip_hub_group = { -+ .attrs = ssam_kip_hub_attrs, -+}; -+ -+static int ssam_kip_hub_mark_hot_removed(struct device *dev, void *_data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (is_ssam_device(dev)) -+ ssam_device_mark_hot_removed(sdev); -+ -+ return 0; -+} -+ -+static void ssam_kip_hub_update_workfn(struct work_struct *work) -+{ -+ struct ssam_kip_hub *hub = container_of(work, struct ssam_kip_hub, update_work.work); -+ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -+ enum ssam_kip_hub_state state; -+ int status = 0; -+ -+ status = ssam_kip_get_connection_state(hub, &state); -+ if (status) -+ return; -+ -+ if (hub->state == state) -+ return; -+ hub->state = state; -+ -+ if (hub->state == SSAM_KIP_HUB_CONNECTED) -+ status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); -+ else -+ ssam_remove_clients(&hub->sdev->dev); -+ -+ if (status) -+ dev_err(&hub->sdev->dev, "failed to update KIP-hub devices: %d\n", status); -+} -+ -+static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_kip_hub *hub = container_of(nf, struct ssam_kip_hub, notif); -+ unsigned long delay; -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) { -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ /* Mark devices as hot-removed before we remove any */ -+ if (!event->data[0]) -+ device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_kip_hub_mark_hot_removed); -+ -+ /* -+ * Delay update when KIP devices are being connected to give devices/EC -+ * some time to set up. -+ */ -+ delay = event->data[0] ? SSAM_KIP_UPDATE_CONNECT_DELAY : 0; -+ -+ schedule_delayed_work(&hub->update_work, delay); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static int __maybe_unused ssam_kip_hub_resume(struct device *dev) -+{ -+ struct ssam_kip_hub *hub = dev_get_drvdata(dev); -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_kip_hub_pm_ops, NULL, ssam_kip_hub_resume); -+ -+static int ssam_kip_hub_probe(struct ssam_device *sdev) -+{ -+ struct ssam_kip_hub *hub; -+ int status; -+ -+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -+ if (!hub) -+ return -ENOMEM; -+ -+ hub->sdev = sdev; -+ hub->state = SSAM_KIP_HUB_UNINITIALIZED; -+ -+ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -+ hub->notif.base.fn = ssam_kip_hub_notif; -+ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ hub->notif.event.id.target_category = SSAM_SSH_TC_KIP, -+ hub->notif.event.id.instance = 0, -+ hub->notif.event.mask = SSAM_EVENT_MASK_TARGET; -+ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ INIT_DELAYED_WORK(&hub->update_work, ssam_kip_hub_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, hub); -+ -+ status = ssam_device_notifier_register(sdev, &hub->notif); -+ if (status) -+ return status; -+ -+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_kip_hub_group); -+ if (status) -+ goto err; -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+ -+err: -+ ssam_device_notifier_unregister(sdev, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); -+ ssam_remove_clients(&sdev->dev); -+ return status; -+} -+ -+static void ssam_kip_hub_remove(struct ssam_device *sdev) -+{ -+ struct ssam_kip_hub *hub = ssam_device_get_drvdata(sdev); -+ -+ sysfs_remove_group(&sdev->dev.kobj, &ssam_kip_hub_group); -+ -+ ssam_device_notifier_unregister(sdev, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); -+ ssam_remove_clients(&sdev->dev); -+} -+ -+static const struct ssam_device_id ssam_kip_hub_match[] = { -+ { SSAM_SDEV(KIP, 0x01, 0x00, 0x00) }, -+ { }, -+}; -+ -+static struct ssam_device_driver ssam_kip_hub_driver = { -+ .probe = ssam_kip_hub_probe, -+ .remove = ssam_kip_hub_remove, -+ .match_table = ssam_kip_hub_match, -+ .driver = { -+ .name = "surface_kip_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_kip_hub_pm_ops, -+ }, -+}; -+ -+ - /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ - - static const struct acpi_device_id ssam_platform_hub_match[] = { -@@ -636,18 +867,30 @@ static int __init ssam_device_hub_init(void) - - status = platform_driver_register(&ssam_platform_hub_driver); - if (status) -- return status; -+ goto err_platform; - - status = ssam_device_driver_register(&ssam_base_hub_driver); - if (status) -- platform_driver_unregister(&ssam_platform_hub_driver); -+ goto err_base; -+ -+ status = ssam_device_driver_register(&ssam_kip_hub_driver); -+ if (status) -+ goto err_kip; - -+ return 0; -+ -+err_kip: -+ ssam_device_driver_unregister(&ssam_base_hub_driver); -+err_base: -+ platform_driver_unregister(&ssam_platform_hub_driver); -+err_platform: - return status; - } - module_init(ssam_device_hub_init); - - static void __exit ssam_device_hub_exit(void) - { -+ ssam_device_driver_unregister(&ssam_kip_hub_driver); - ssam_device_driver_unregister(&ssam_base_hub_driver); - platform_driver_unregister(&ssam_platform_hub_driver); - } --- -2.34.0 - -From 0fc4fd278cac0d5f3274656187d706f75ef48008 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 22:33:03 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for - keyboard cover on Surface Pro 8 - -Add support for the detachable keyboard cover on the Surface Pro 8. - -The keyboard cover on the Surface Pro 8 is, unlike the keyboard covers -of earlier Surface Pro generations, handled via the Surface System -Aggregator Module (SSAM). The keyboard and touchpad (as well as other -HID input devices) of this cover are standard SSAM HID client devices -(just like keyboard and touchpad on e.g. the Surface Laptop 3 and 4), -however, some care needs to be taken as they can be physically detached -(similarly to the Surface Book 3). Specifically, the respective SSAM -client devices need to be removed when the keyboard cover has been -detached and (re-)initialized when the keyboard cover has been -(re-)attached. - -On the Surface Pro 8, detachment of the keyboard cover (and by extension -its devices) is managed via the KIP subsystem. Therefore, said devices -need to be registered under the KIP device hub, which in turn will -remove and re-create/re-initialize those devices as needed. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 37 ++++++++++++++++++- - 1 file changed, 36 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 4838ce6519a6..c0e29c0514df 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -47,6 +47,12 @@ static const struct software_node ssam_node_hub_base = { - .parent = &ssam_node_root, - }; - -+/* KIP device hub (connects keyboard cover devices on Surface Pro 8). */ -+static const struct software_node ssam_node_hub_kip = { -+ .name = "ssam:01:0e:01:00:00", -+ .parent = &ssam_node_root, -+}; -+ - /* AC adapter. */ - static const struct software_node ssam_node_bat_ac = { - .name = "ssam:01:02:01:01:01", -@@ -155,6 +161,30 @@ static const struct software_node ssam_node_hid_base_iid6 = { - .parent = &ssam_node_hub_base, - }; - -+/* HID keyboard (KIP hub). */ -+static const struct software_node ssam_node_hid_kip_keyboard = { -+ .name = "ssam:01:15:02:01:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID pen stash (KIP hub; pen taken / stashed away evens). */ -+static const struct software_node ssam_node_hid_kip_penstash = { -+ .name = "ssam:01:15:02:02:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID touchpad (KIP hub). */ -+static const struct software_node ssam_node_hid_kip_touchpad = { -+ .name = "ssam:01:15:02:03:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID device instance 5 (KIP hub, unknown HID device). */ -+static const struct software_node ssam_node_hid_kip_iid5 = { -+ .name = "ssam:01:15:02:05:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ - /* - * Devices for 5th- and 6th-generations models: - * - Surface Book 2, -@@ -230,10 +260,15 @@ static const struct software_node *ssam_node_group_sp7[] = { - - static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_root, -+ &ssam_node_hub_kip, - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -- /* TODO: Add support for keyboard cover. */ -+ &ssam_node_hid_kip_keyboard, -+ &ssam_node_hid_kip_penstash, -+ &ssam_node_hid_kip_touchpad, -+ &ssam_node_hid_kip_iid5, -+ /* TODO: Add support for tablet mode switch. */ - NULL, - }; - --- -2.34.0 - -From 50b107f14ef9e33f8510d726630138f69f2cf480 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 03:19:20 +0200 -Subject: [PATCH] platform/surface: Add KIP tablet-mode switch - -Add a driver providing a tablet-mode switch input device for Surface -models using the KIP subsystem to manage detachable peripherals. - -The Surface Pro 8 has a detachable keyboard cover. Unlike the keyboard -covers of previous generation Surface Pro models, this cover is fully -handled by the Surface System Aggregator Module (SSAM). The SSAM KIP -subsystem (full name unknown, abbreviation found through reverse -engineering) provides notifications for mode changes of the cover. -Specifically, it allows us to know when the cover has been folded back, -detached, or whether it is in laptop mode. - -The driver introduced with this change captures these events and -translates them to standard SW_TABLET_MODE input events. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - MAINTAINERS | 6 + - drivers/platform/surface/Kconfig | 22 ++ - drivers/platform/surface/Makefile | 1 + - .../surface/surface_kip_tablet_switch.c | 245 ++++++++++++++++++ - 4 files changed, 274 insertions(+) - create mode 100644 drivers/platform/surface/surface_kip_tablet_switch.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index d7b4f32875a9..5713585d060d 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -12357,6 +12357,12 @@ L: platform-driver-x86@vger.kernel.org - S: Maintained - F: drivers/platform/surface/surface_hotplug.c - -+MICROSOFT SURFACE KIP TABLET-MODE SWITCH -+M: Maximilian Luz -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/platform/surface/surface_kip_tablet_switch.c -+ - MICROSOFT SURFACE PLATFORM PROFILE DRIVER - M: Maximilian Luz - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 3105f651614f..3c0ee0cdaef5 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -152,6 +152,28 @@ config SURFACE_HOTPLUG - Select M or Y here, if you want to (fully) support hot-plugging of - dGPU devices on the Surface Book 2 and/or 3 during D3cold. - -+config SURFACE_KIP_TABLET_SWITCH -+ tristate "Surface KIP Tablet-Mode Switch Driver" -+ depends on SURFACE_AGGREGATOR -+ depends on SURFACE_AGGREGATOR_BUS -+ depends on INPUT -+ help -+ Provides a tablet-mode switch input device on Microsoft Surface models -+ using the KIP subsystem for detachable keyboards (e.g. keyboard -+ covers). -+ -+ The KIP subsystem is used on newer Surface generations to handle -+ detachable input peripherals, specifically the keyboard cover -+ (containing keyboard and touchpad) on the Surface Pro 8. This module -+ provides a driver to let user-space know when the device should be -+ considered in tablet-mode due to the keyboard cover being detached or -+ folded back (essentially signaling when the keyboard is not available -+ for input). It does so by creating a tablet-mode switch input device, -+ sending the standard SW_TABLET_MODE event on mode change. -+ -+ Select M or Y here, if you want to provide tablet-mode switch input -+ events on the Surface Pro 8. -+ - config SURFACE_PLATFORM_PROFILE - tristate "Surface Platform Profile Driver" - depends on SURFACE_AGGREGATOR_REGISTRY -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 32889482de55..6d9291c993c4 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -14,5 +14,6 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -+obj-$(CONFIG_SURFACE_KIP_TABLET_SWITCH) += surface_kip_tablet_switch.o - obj-$(CONFIG_SURFACE_PLATFORM_PROFILE) += surface_platform_profile.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o -diff --git a/drivers/platform/surface/surface_kip_tablet_switch.c b/drivers/platform/surface/surface_kip_tablet_switch.c -new file mode 100644 -index 000000000000..458470067579 ---- /dev/null -+++ b/drivers/platform/surface/surface_kip_tablet_switch.c -@@ -0,0 +1,245 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) tablet mode switch via KIP -+ * subsystem. -+ * -+ * Copyright (C) 2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#define SSAM_EVENT_KIP_CID_LID_STATE 0x1d -+ -+enum ssam_kip_lid_state { -+ SSAM_KIP_LID_STATE_DISCONNECTED = 0x01, -+ SSAM_KIP_LID_STATE_CLOSED = 0x02, -+ SSAM_KIP_LID_STATE_LAPTOP = 0x03, -+ SSAM_KIP_LID_STATE_FOLDED_CANVAS = 0x04, -+ SSAM_KIP_LID_STATE_FOLDED_BACK = 0x05, -+}; -+ -+struct ssam_kip_sw { -+ struct ssam_device *sdev; -+ -+ enum ssam_kip_lid_state state; -+ struct work_struct update_work; -+ struct input_dev *mode_switch; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_lid_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x1d, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_get_lid_state(struct ssam_kip_sw *sw, enum ssam_kip_lid_state *state) -+{ -+ int status; -+ u8 raw; -+ -+ status = ssam_retry(__ssam_kip_get_lid_state, sw->sdev->ctrl, &raw); -+ if (status < 0) { -+ dev_err(&sw->sdev->dev, "failed to query KIP lid state: %d\n", status); -+ return status; -+ } -+ -+ *state = raw; -+ return 0; -+} -+ -+static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct ssam_kip_sw *sw = dev_get_drvdata(dev); -+ const char *state; -+ -+ switch (sw->state) { -+ case SSAM_KIP_LID_STATE_DISCONNECTED: -+ state = "disconnected"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_CLOSED: -+ state = "closed"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_LAPTOP: -+ state = "laptop"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_FOLDED_CANVAS: -+ state = "folded-canvas"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_FOLDED_BACK: -+ state = "folded-back"; -+ break; -+ -+ default: -+ state = ""; -+ dev_warn(dev, "unknown KIP lid state: %d\n", sw->state); -+ break; -+ } -+ -+ return sysfs_emit(buf, "%s\n", state); -+} -+static DEVICE_ATTR_RO(state); -+ -+static struct attribute *ssam_kip_sw_attrs[] = { -+ &dev_attr_state.attr, -+ NULL, -+}; -+ -+static const struct attribute_group ssam_kip_sw_group = { -+ .attrs = ssam_kip_sw_attrs, -+}; -+ -+static void ssam_kip_sw_update_workfn(struct work_struct *work) -+{ -+ struct ssam_kip_sw *sw = container_of(work, struct ssam_kip_sw, update_work); -+ enum ssam_kip_lid_state state; -+ int tablet, status; -+ -+ status = ssam_kip_get_lid_state(sw, &state); -+ if (status) -+ return; -+ -+ if (sw->state == state) -+ return; -+ sw->state = state; -+ -+ /* Send SW_TABLET_MODE event. */ -+ tablet = state != SSAM_KIP_LID_STATE_LAPTOP; -+ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); -+ input_sync(sw->mode_switch); -+} -+ -+static u32 ssam_kip_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_kip_sw *sw = container_of(nf, struct ssam_kip_sw, notif); -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_LID_STATE) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) { -+ dev_err(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ schedule_work(&sw->update_work); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static int __maybe_unused ssam_kip_sw_resume(struct device *dev) -+{ -+ struct ssam_kip_sw *sw = dev_get_drvdata(dev); -+ -+ schedule_work(&sw->update_work); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_kip_sw_pm_ops, NULL, ssam_kip_sw_resume); -+ -+static int ssam_kip_sw_probe(struct ssam_device *sdev) -+{ -+ struct ssam_kip_sw *sw; -+ int tablet, status; -+ -+ sw = devm_kzalloc(&sdev->dev, sizeof(*sw), GFP_KERNEL); -+ if (!sw) -+ return -ENOMEM; -+ -+ sw->sdev = sdev; -+ INIT_WORK(&sw->update_work, ssam_kip_sw_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, sw); -+ -+ /* Get initial state. */ -+ status = ssam_kip_get_lid_state(sw, &sw->state); -+ if (status) -+ return status; -+ -+ /* Set up tablet mode switch. */ -+ sw->mode_switch = devm_input_allocate_device(&sdev->dev); -+ if (!sw->mode_switch) -+ return -ENOMEM; -+ -+ sw->mode_switch->name = "Microsoft Surface KIP Tablet Mode Switch"; -+ sw->mode_switch->phys = "ssam/01:0e:01:00:01/input0"; -+ sw->mode_switch->id.bustype = BUS_HOST; -+ sw->mode_switch->dev.parent = &sdev->dev; -+ -+ tablet = sw->state != SSAM_KIP_LID_STATE_LAPTOP; -+ input_set_capability(sw->mode_switch, EV_SW, SW_TABLET_MODE); -+ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); -+ -+ status = input_register_device(sw->mode_switch); -+ if (status) -+ return status; -+ -+ /* Set up notifier. */ -+ sw->notif.base.priority = 0; -+ sw->notif.base.fn = ssam_kip_sw_notif; -+ sw->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ sw->notif.event.id.target_category = SSAM_SSH_TC_KIP, -+ sw->notif.event.id.instance = 0, -+ sw->notif.event.mask = SSAM_EVENT_MASK_TARGET; -+ sw->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ status = ssam_device_notifier_register(sdev, &sw->notif); -+ if (status) -+ return status; -+ -+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_kip_sw_group); -+ if (status) -+ goto err; -+ -+ /* We might have missed events during setup, so check again. */ -+ schedule_work(&sw->update_work); -+ return 0; -+ -+err: -+ ssam_device_notifier_unregister(sdev, &sw->notif); -+ cancel_work_sync(&sw->update_work); -+ return status; -+} -+ -+static void ssam_kip_sw_remove(struct ssam_device *sdev) -+{ -+ struct ssam_kip_sw *sw = ssam_device_get_drvdata(sdev); -+ -+ sysfs_remove_group(&sdev->dev.kobj, &ssam_kip_sw_group); -+ -+ ssam_device_notifier_unregister(sdev, &sw->notif); -+ cancel_work_sync(&sw->update_work); -+} -+ -+static const struct ssam_device_id ssam_kip_sw_match[] = { -+ { SSAM_SDEV(KIP, 0x01, 0x00, 0x01) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, ssam_kip_sw_match); -+ -+static struct ssam_device_driver ssam_kip_sw_driver = { -+ .probe = ssam_kip_sw_probe, -+ .remove = ssam_kip_sw_remove, -+ .match_table = ssam_kip_sw_match, -+ .driver = { -+ .name = "surface_kip_tablet_mode_switch", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_kip_sw_pm_ops, -+ }, -+}; -+module_ssam_device_driver(ssam_kip_sw_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Tablet mode switch driver for Surface devices using KIP subsystem"); -+MODULE_LICENSE("GPL"); --- -2.34.0 - -From 2b1685c0a906769031590c7bfc0793bcc6fb9e98 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 22:33:03 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for tablet - mode switch on Surface Pro 8 - -Add a KIP subsystem tablet-mode switch device for the Surface Pro 8. -The respective driver for this device provides SW_TABLET_MODE input -events for user-space based on the state of the keyboard cover (e.g. -detached, folded-back, normal/laptop mode). - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 8 +++++++- - 1 file changed, 7 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index c0e29c0514df..eaf0054627a5 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -77,6 +77,12 @@ static const struct software_node ssam_node_tmp_pprof = { - .parent = &ssam_node_root, - }; - -+/* Tablet-mode switch via KIP subsystem. */ -+static const struct software_node ssam_node_kip_tablet_switch = { -+ .name = "ssam:01:0e:01:00:01", -+ .parent = &ssam_node_root, -+}; -+ - /* DTX / detachment-system device (Surface Book 3). */ - static const struct software_node ssam_node_bas_dtx = { - .name = "ssam:01:11:01:00:00", -@@ -264,11 +270,11 @@ static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -+ &ssam_node_kip_tablet_switch, - &ssam_node_hid_kip_keyboard, - &ssam_node_hid_kip_penstash, - &ssam_node_hid_kip_touchpad, - &ssam_node_hid_kip_iid5, -- /* TODO: Add support for tablet mode switch. */ - NULL, - }; - --- -2.34.0 - -From fea397d2b114521a7e4cbc13998e451ebd9796ad Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 28 Oct 2021 03:40:22 +0200 -Subject: [PATCH] power/supply: surface_battery: Add support for hot-removal - -In cases of hot-removal, further communication with the device should be -avoided whenever possible, as it may fail and time out. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/power/supply/surface_battery.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -index 540707882bb0..ebf1f96e9a89 100644 ---- a/drivers/power/supply/surface_battery.c -+++ b/drivers/power/supply/surface_battery.c -@@ -156,6 +156,9 @@ static bool spwr_battery_present(struct spwr_battery_device *bat) - { - lockdep_assert_held(&bat->lock); - -+ if (ssam_device_is_hot_removed(bat->sdev)) -+ return false; -+ - return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT; - } - -@@ -245,6 +248,9 @@ static int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat) - - lockdep_assert_held(&bat->lock); - -+ if (ssam_device_is_hot_removed(bat->sdev)) -+ return 0; -+ - status = spwr_battery_load_sta(bat); - if (status) - return status; --- -2.34.0 - -From 1610b9bfd7295dfdde50ffc777a7d88c6a7e9531 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 31 Oct 2021 18:07:39 +0100 -Subject: [PATCH] platform/surface: aggregator_registry: Use KIP hub for - Surface Book 3 base devices - -It turns out that the Surface Book 3 manages the devices contained in -its detachable base via the KIP hub as well, similarly to the Surface -Pro 8 and Surface Pro X. So move them over to the KIP hub. - -Right now, we (mis-)use the detachment subsystem (DTX), which is -designed for handling detachment requests and physical locking of the -base, to properly remove and re-attach Surface System Aggregator Module -(SSAM) client devices contained in the base. This system does not seem -to be intended for managing the (sub-)devices contained in the base, -which may need some time to be set up properly. - -The KIP subsystem seems to be the intended subsystem for managing those -devices, thus let's use that one instead. - -Note that this also changes the way in which devices on the Surface Book -3 are removed when they have been detached, specifically from normal -removal to hot-removal (avoiding further communication with the embedded -controller). It seems that the "communication timeout after device -removal" issue does also occur on the Surface Book 3, but has so far -been missed as it does not happen reliably every time. Switching to -hot-removal fixes this issue and should not have any noticable -drawbacks. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 56 ++++++------------- - 1 file changed, 16 insertions(+), 40 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index eaf0054627a5..d17f656b2dad 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -41,13 +41,7 @@ static const struct software_node ssam_node_root = { - .name = "ssam_platform_hub", - }; - --/* Base device hub (devices attached to Surface Book 3 base). */ --static const struct software_node ssam_node_hub_base = { -- .name = "ssam:00:00:02:00:00", -- .parent = &ssam_node_root, --}; -- --/* KIP device hub (connects keyboard cover devices on Surface Pro 8). */ -+/* KIP device hub (connects detachable keyboard/touchpad on Surface Pro 8 and Book 3). */ - static const struct software_node ssam_node_hub_kip = { - .name = "ssam:01:0e:01:00:00", - .parent = &ssam_node_root, -@@ -65,10 +59,10 @@ static const struct software_node ssam_node_bat_main = { - .parent = &ssam_node_root, - }; - --/* Secondary battery (Surface Book 3). */ --static const struct software_node ssam_node_bat_sb3base = { -+/* Secondary battery (Surface Book 3, managed via KIP hub). */ -+static const struct software_node ssam_node_bat_kip = { - .name = "ssam:01:02:02:01:00", -- .parent = &ssam_node_hub_base, -+ .parent = &ssam_node_hub_kip, - }; - - /* Platform profile / performance-mode device. */ -@@ -143,30 +137,6 @@ static const struct software_node ssam_node_hid_main_iid5 = { - .parent = &ssam_node_root, - }; - --/* HID keyboard (base hub). */ --static const struct software_node ssam_node_hid_base_keyboard = { -- .name = "ssam:01:15:02:01:00", -- .parent = &ssam_node_hub_base, --}; -- --/* HID touchpad (base hub). */ --static const struct software_node ssam_node_hid_base_touchpad = { -- .name = "ssam:01:15:02:03:00", -- .parent = &ssam_node_hub_base, --}; -- --/* HID device instance 5 (unknown HID device, base hub). */ --static const struct software_node ssam_node_hid_base_iid5 = { -- .name = "ssam:01:15:02:05:00", -- .parent = &ssam_node_hub_base, --}; -- --/* HID device instance 6 (unknown HID device, base hub). */ --static const struct software_node ssam_node_hid_base_iid6 = { -- .name = "ssam:01:15:02:06:00", -- .parent = &ssam_node_hub_base, --}; -- - /* HID keyboard (KIP hub). */ - static const struct software_node ssam_node_hid_kip_keyboard = { - .name = "ssam:01:15:02:01:00", -@@ -191,6 +161,12 @@ static const struct software_node ssam_node_hid_kip_iid5 = { - .parent = &ssam_node_hub_kip, - }; - -+/* HID device instance 6 (KIP hub, unknown HID device). */ -+static const struct software_node ssam_node_hid_kip_iid6 = { -+ .name = "ssam:01:15:02:06:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ - /* - * Devices for 5th- and 6th-generations models: - * - Surface Book 2, -@@ -206,16 +182,16 @@ static const struct software_node *ssam_node_group_gen5[] = { - /* Devices for Surface Book 3. */ - static const struct software_node *ssam_node_group_sb3[] = { - &ssam_node_root, -- &ssam_node_hub_base, -+ &ssam_node_hub_kip, - &ssam_node_bat_ac, - &ssam_node_bat_main, -- &ssam_node_bat_sb3base, -+ &ssam_node_bat_kip, - &ssam_node_tmp_pprof, - &ssam_node_bas_dtx, -- &ssam_node_hid_base_keyboard, -- &ssam_node_hid_base_touchpad, -- &ssam_node_hid_base_iid5, -- &ssam_node_hid_base_iid6, -+ &ssam_node_hid_kip_keyboard, -+ &ssam_node_hid_kip_touchpad, -+ &ssam_node_hid_kip_iid5, -+ &ssam_node_hid_kip_iid6, - NULL, - }; - --- -2.34.0 - -From 236abbbef8f8a528621e839383e357f50591cb7c Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 31 Oct 2021 18:09:53 +0100 -Subject: [PATCH] platform/surface: aggregator_registry: Remove base hub driver - -The base hub was a virtual device hub for Surface System Aggregator -Module (SSAM) client devices contained in the detachable Surface Book 3 -base. Remove it as it is no longer needed. - -In the previous change, we have moved all devices from the base hub to -the KIP hub. That change has also removed the only base-hub-device that -ever existed, as it was essentially replaced by the KIP-hub-device and -thus was no longer needed. This means that there is no remaining -hub-device against which the base hub driver can load, i.e. it is now -essentially dead code. So remove the driver as well. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 213 ------------------ - 1 file changed, 213 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index d17f656b2dad..590473220e9d 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -325,212 +325,6 @@ static int ssam_hub_register_clients(struct device *parent, struct ssam_controll - } - - --/* -- SSAM base-hub driver. ------------------------------------------------- */ -- --/* -- * Some devices (especially battery) may need a bit of time to be fully usable -- * after being (re-)connected. This delay has been determined via -- * experimentation. -- */ --#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) -- --enum ssam_base_hub_state { -- SSAM_BASE_HUB_UNINITIALIZED, -- SSAM_BASE_HUB_CONNECTED, -- SSAM_BASE_HUB_DISCONNECTED, --}; -- --struct ssam_base_hub { -- struct ssam_device *sdev; -- -- enum ssam_base_hub_state state; -- struct delayed_work update_work; -- -- struct ssam_event_notifier notif; --}; -- --SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { -- .target_category = SSAM_SSH_TC_BAS, -- .target_id = 0x01, -- .command_id = 0x0d, -- .instance_id = 0x00, --}); -- --#define SSAM_BAS_OPMODE_TABLET 0x00 --#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c -- --static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state) --{ -- u8 opmode; -- int status; -- -- status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); -- if (status < 0) { -- dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); -- return status; -- } -- -- if (opmode != SSAM_BAS_OPMODE_TABLET) -- *state = SSAM_BASE_HUB_CONNECTED; -- else -- *state = SSAM_BASE_HUB_DISCONNECTED; -- -- return 0; --} -- --static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr, -- char *buf) --{ -- struct ssam_base_hub *hub = dev_get_drvdata(dev); -- bool connected = hub->state == SSAM_BASE_HUB_CONNECTED; -- -- return sysfs_emit(buf, "%d\n", connected); --} -- --static struct device_attribute ssam_base_hub_attr_state = -- __ATTR(state, 0444, ssam_base_hub_state_show, NULL); -- --static struct attribute *ssam_base_hub_attrs[] = { -- &ssam_base_hub_attr_state.attr, -- NULL, --}; -- --static const struct attribute_group ssam_base_hub_group = { -- .attrs = ssam_base_hub_attrs, --}; -- --static void ssam_base_hub_update_workfn(struct work_struct *work) --{ -- struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work); -- struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -- enum ssam_base_hub_state state; -- int status = 0; -- -- status = ssam_base_hub_query_state(hub, &state); -- if (status) -- return; -- -- if (hub->state == state) -- return; -- hub->state = state; -- -- if (hub->state == SSAM_BASE_HUB_CONNECTED) -- status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); -- else -- ssam_remove_clients(&hub->sdev->dev); -- -- if (status) -- dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); --} -- --static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) --{ -- struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif); -- unsigned long delay; -- -- if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) -- return 0; -- -- if (event->length < 1) { -- dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -- return 0; -- } -- -- /* -- * Delay update when the base is being connected to give devices/EC -- * some time to set up. -- */ -- delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0; -- -- schedule_delayed_work(&hub->update_work, delay); -- -- /* -- * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -- * consumed by the detachment system driver. We're just a (more or less) -- * silent observer. -- */ -- return 0; --} -- --static int __maybe_unused ssam_base_hub_resume(struct device *dev) --{ -- struct ssam_base_hub *hub = dev_get_drvdata(dev); -- -- schedule_delayed_work(&hub->update_work, 0); -- return 0; --} --static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); -- --static int ssam_base_hub_probe(struct ssam_device *sdev) --{ -- struct ssam_base_hub *hub; -- int status; -- -- hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -- if (!hub) -- return -ENOMEM; -- -- hub->sdev = sdev; -- hub->state = SSAM_BASE_HUB_UNINITIALIZED; -- -- hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -- hub->notif.base.fn = ssam_base_hub_notif; -- hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -- hub->notif.event.id.target_category = SSAM_SSH_TC_BAS, -- hub->notif.event.id.instance = 0, -- hub->notif.event.mask = SSAM_EVENT_MASK_NONE; -- hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -- -- INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn); -- -- ssam_device_set_drvdata(sdev, hub); -- -- status = ssam_device_notifier_register(sdev, &hub->notif); -- if (status) -- return status; -- -- status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); -- if (status) -- goto err; -- -- schedule_delayed_work(&hub->update_work, 0); -- return 0; -- --err: -- ssam_device_notifier_unregister(sdev, &hub->notif); -- cancel_delayed_work_sync(&hub->update_work); -- ssam_remove_clients(&sdev->dev); -- return status; --} -- --static void ssam_base_hub_remove(struct ssam_device *sdev) --{ -- struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); -- -- sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); -- -- ssam_device_notifier_unregister(sdev, &hub->notif); -- cancel_delayed_work_sync(&hub->update_work); -- ssam_remove_clients(&sdev->dev); --} -- --static const struct ssam_device_id ssam_base_hub_match[] = { -- { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) }, -- { }, --}; -- --static struct ssam_device_driver ssam_base_hub_driver = { -- .probe = ssam_base_hub_probe, -- .remove = ssam_base_hub_remove, -- .match_table = ssam_base_hub_match, -- .driver = { -- .name = "surface_aggregator_base_hub", -- .probe_type = PROBE_PREFER_ASYNCHRONOUS, -- .pm = &ssam_base_hub_pm_ops, -- }, --}; -- -- - /* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ - - /* -@@ -886,10 +680,6 @@ static int __init ssam_device_hub_init(void) - if (status) - goto err_platform; - -- status = ssam_device_driver_register(&ssam_base_hub_driver); -- if (status) -- goto err_base; -- - status = ssam_device_driver_register(&ssam_kip_hub_driver); - if (status) - goto err_kip; -@@ -897,8 +687,6 @@ static int __init ssam_device_hub_init(void) - return 0; - - err_kip: -- ssam_device_driver_unregister(&ssam_base_hub_driver); --err_base: - platform_driver_unregister(&ssam_platform_hub_driver); - err_platform: - return status; -@@ -908,7 +696,6 @@ module_init(ssam_device_hub_init); - static void __exit ssam_device_hub_exit(void) - { - ssam_device_driver_unregister(&ssam_kip_hub_driver); -- ssam_device_driver_unregister(&ssam_base_hub_driver); - platform_driver_unregister(&ssam_platform_hub_driver); - } - module_exit(ssam_device_hub_exit); --- -2.34.0 - diff --git a/patches/5.14/0006-surface-sam-over-hid.patch b/patches/5.14/0006-surface-sam-over-hid.patch deleted file mode 100644 index d464ed94a..000000000 --- a/patches/5.14/0006-surface-sam-over-hid.patch +++ /dev/null @@ -1,335 +0,0 @@ -From 93a1213b90b28361a0df7a96b087aa0ab2ff105f Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jul 2020 17:19:53 +0200 -Subject: [PATCH] i2c: acpi: Implement RawBytes read access - -Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C -device via a generic serial bus operation region and RawBytes read -access. On the Surface Book 1, this access is required to turn on (and -off) the discrete GPU. - -Multiple things are to note here: - -a) The RawBytes access is device/driver dependent. The ACPI - specification states: - - > Raw accesses assume that the writer has knowledge of the bus that - > the access is made over and the device that is being accessed. The - > protocol may only ensure that the buffer is transmitted to the - > appropriate driver, but the driver must be able to interpret the - > buffer to communicate to a register. - - Thus this implementation may likely not work on other devices - accessing I2C via the RawBytes accessor type. - -b) The MSHW0030 I2C device is an HID-over-I2C device which seems to - serve multiple functions: - - 1. It is the main access point for the legacy-type Surface Aggregator - Module (also referred to as SAM-over-HID, as opposed to the newer - SAM-over-SSH/UART). It has currently not been determined on how - support for the legacy SAM should be implemented. Likely via a - custom HID driver. - - 2. It seems to serve as the HID device for the Integrated Sensor Hub. - This might complicate matters with regards to implementing a - SAM-over-HID driver required by legacy SAM. - -In light of this, the simplest approach has been chosen for now. -However, it may make more sense regarding breakage and compatibility to -either provide functionality for replacing or enhancing the default -operation region handler via some additional API functions, or even to -completely blacklist MSHW0030 from the I2C core and provide a custom -driver for it. - -Replacing/enhancing the default operation region handler would, however, -either require some sort of secondary driver and access point for it, -from which the new API functions would be called and the new handler -(part) would be installed, or hard-coding them via some sort of -quirk-like interface into the I2C core. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam-over-hid ---- - drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ - 1 file changed, 35 insertions(+) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index 74925621f239..169713964358 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -571,6 +571,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, - return (ret == 1) ? 0 : -EIO; - } - -+static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, -+ u8 *data, u8 data_len) -+{ -+ struct i2c_msg msgs[1]; -+ int ret = AE_OK; -+ -+ msgs[0].addr = client->addr; -+ msgs[0].flags = client->flags; -+ msgs[0].len = data_len + 1; -+ msgs[0].buf = data; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ -+ if (ret < 0) { -+ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); -+ return ret; -+ } -+ -+ /* 1 transfer must have completed successfully */ -+ return (ret == 1) ? 0 : -EIO; -+} -+ - static acpi_status - i2c_acpi_space_handler(u32 function, acpi_physical_address command, - u32 bits, u64 *value64, -@@ -672,6 +694,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, - } - break; - -+ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: -+ if (action == ACPI_READ) { -+ dev_warn(&adapter->dev, -+ "protocol 0x%02x not supported for client 0x%02x\n", -+ accessor_type, client->addr); -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } else { -+ status = acpi_gsb_i2c_write_raw_bytes(client, -+ gsb->data, info->access_length); -+ } -+ break; -+ - default: - dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", - accessor_type, client->addr); --- -2.34.0 - -From 6cbc6203a8929aacd63257100c9b7a2160adf7fd Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 13 Feb 2021 16:41:18 +0100 -Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch - -Add driver exposing the discrete GPU power-switch of the Microsoft -Surface Book 1 to user-space. - -On the Surface Book 1, the dGPU power is controlled via the Surface -System Aggregator Module (SAM). The specific SAM-over-HID command for -this is exposed via ACPI. This module provides a simple driver exposing -the ACPI call via a sysfs parameter to user-space, so that users can -easily power-on/-off the dGPU. - -Patchset: surface-sam-over-hid ---- - drivers/platform/surface/Kconfig | 7 + - drivers/platform/surface/Makefile | 1 + - .../surface/surfacebook1_dgpu_switch.c | 162 ++++++++++++++++++ - 3 files changed, 170 insertions(+) - create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 3c0ee0cdaef5..e5eedb85d471 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -104,6 +104,13 @@ config SURFACE_AGGREGATOR_REGISTRY - the respective client devices. Drivers for these devices still need to - be selected via the other options. - -+config SURFACE_BOOK1_DGPU_SWITCH -+ tristate "Surface Book 1 dGPU Switch Driver" -+ depends on SYSFS -+ help -+ This driver provides a sysfs switch to set the power-state of the -+ discrete GPU found on the Microsoft Surface Book 1. -+ - config SURFACE_DTX - tristate "Surface DTX (Detachment System) Driver" - depends on SURFACE_AGGREGATOR -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 6d9291c993c4..9eb3a7e6382c 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -11,6 +11,7 @@ obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o -+obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c -new file mode 100644 -index 000000000000..8b816ed8f35c ---- /dev/null -+++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c -@@ -0,0 +1,162 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+ -+#include -+#include -+#include -+#include -+ -+ -+#ifdef pr_fmt -+#undef pr_fmt -+#endif -+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ -+ -+ -+static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, -+ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); -+ -+#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" -+#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" -+#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" -+ -+ -+static int sb1_dgpu_sw_dsmcall(void) -+{ -+ union acpi_object *ret; -+ acpi_handle handle; -+ acpi_status status; -+ -+ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); -+ if (status) -+ return -EINVAL; -+ -+ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); -+ if (!ret) -+ return -EINVAL; -+ -+ ACPI_FREE(ret); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgon(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGON: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-on dGPU via HGON\n"); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgof(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGOF: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-off dGPU via HGOF\n"); -+ return 0; -+} -+ -+ -+static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ int status, value; -+ -+ status = kstrtoint(buf, 0, &value); -+ if (status < 0) -+ return status; -+ -+ if (value != 1) -+ return -EINVAL; -+ -+ status = sb1_dgpu_sw_dsmcall(); -+ -+ return status < 0 ? status : len; -+} -+ -+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ bool power; -+ int status; -+ -+ status = kstrtobool(buf, &power); -+ if (status < 0) -+ return status; -+ -+ if (power) -+ status = sb1_dgpu_sw_hgon(); -+ else -+ status = sb1_dgpu_sw_hgof(); -+ -+ return status < 0 ? status : len; -+} -+ -+static DEVICE_ATTR_WO(dgpu_dsmcall); -+static DEVICE_ATTR_WO(dgpu_power); -+ -+static struct attribute *sb1_dgpu_sw_attrs[] = { -+ &dev_attr_dgpu_dsmcall.attr, -+ &dev_attr_dgpu_power.attr, -+ NULL, -+}; -+ -+static const struct attribute_group sb1_dgpu_sw_attr_group = { -+ .attrs = sb1_dgpu_sw_attrs, -+}; -+ -+ -+static int sb1_dgpu_sw_probe(struct platform_device *pdev) -+{ -+ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+} -+ -+static int sb1_dgpu_sw_remove(struct platform_device *pdev) -+{ -+ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+ return 0; -+} -+ -+/* -+ * The dGPU power seems to be actually handled by MSHW0040. However, that is -+ * also the power-/volume-button device with a mainline driver. So let's use -+ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. -+ */ -+static const struct acpi_device_id sb1_dgpu_sw_match[] = { -+ { "MSHW0041", }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); -+ -+static struct platform_driver sb1_dgpu_sw = { -+ .probe = sb1_dgpu_sw_probe, -+ .remove = sb1_dgpu_sw_remove, -+ .driver = { -+ .name = "surfacebook1_dgpu_switch", -+ .acpi_match_table = sb1_dgpu_sw_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(sb1_dgpu_sw); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); -+MODULE_LICENSE("GPL"); --- -2.34.0 - diff --git a/patches/5.14/0007-surface-gpe.patch b/patches/5.14/0007-surface-gpe.patch deleted file mode 100644 index afa856f29..000000000 --- a/patches/5.14/0007-surface-gpe.patch +++ /dev/null @@ -1,85 +0,0 @@ -From 7e5f221e11e90bb48db72531ac144d7bdfc9e9a1 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 10 Oct 2021 00:02:44 +0200 -Subject: [PATCH] platform/surface: gpe: Add support for Surface Laptop Studio - -The new Surface Laptop Studio uses GPEs for lid events as well. Add an -entry for that so that the lid can be used to wake the device. - -Signed-off-by: Maximilian Luz -Patchset: surface-gpe ---- - drivers/platform/surface/surface_gpe.c | 13 +++++++++++++ - 1 file changed, 13 insertions(+) - -diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c -index 86f6991b1215..c1775db29efb 100644 ---- a/drivers/platform/surface/surface_gpe.c -+++ b/drivers/platform/surface/surface_gpe.c -@@ -26,6 +26,11 @@ static const struct property_entry lid_device_props_l17[] = { - {}, - }; - -+static const struct property_entry lid_device_props_l4B[] = { -+ PROPERTY_ENTRY_U32("gpe", 0x4B), -+ {}, -+}; -+ - static const struct property_entry lid_device_props_l4D[] = { - PROPERTY_ENTRY_U32("gpe", 0x4D), - {}, -@@ -158,6 +163,14 @@ static const struct dmi_system_id dmi_lid_device_table[] = { - }, - .driver_data = (void *)lid_device_props_l4D, - }, -+ { -+ .ident = "Surface Laptop Studio", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop Studio"), -+ }, -+ .driver_data = (void *)lid_device_props_l4B, -+ }, - { } - }; - --- -2.34.0 - -From c91d2440570c52e33c4a5c518452413d25e2cb21 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 00:56:11 +0200 -Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 8 - -The new Surface Pro 8 uses GPEs for lid events as well. Add an entry for -that so that the lid can be used to wake the device. Note that this is a -device with a keyboard type cover, where this acts as the "lid". - -Signed-off-by: Maximilian Luz -Patchset: surface-gpe ---- - drivers/platform/surface/surface_gpe.c | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c -index c1775db29efb..ec66fde28e75 100644 ---- a/drivers/platform/surface/surface_gpe.c -+++ b/drivers/platform/surface/surface_gpe.c -@@ -99,6 +99,14 @@ static const struct dmi_system_id dmi_lid_device_table[] = { - }, - .driver_data = (void *)lid_device_props_l4D, - }, -+ { -+ .ident = "Surface Pro 8", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 8"), -+ }, -+ .driver_data = (void *)lid_device_props_l4B, -+ }, - { - .ident = "Surface Book 1", - .matches = { --- -2.34.0 - diff --git a/patches/5.14/0008-surface-button.patch b/patches/5.14/0008-surface-button.patch deleted file mode 100644 index d90fa6798..000000000 --- a/patches/5.14/0008-surface-button.patch +++ /dev/null @@ -1,149 +0,0 @@ -From 49d6cd6417adf1df985e7b90c5e48a743162333f Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Tue, 5 Oct 2021 00:05:09 +1100 -Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices - -The power button on the AMD variant of the Surface Laptop uses the -same MSHW0040 device ID as the 5th and later generation of Surface -devices, however they report 0 for their OEM platform revision. As the -_DSM does not exist on the devices requiring special casing, check for -the existance of the _DSM to determine if soc_button_array should be -loaded. - -Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") -Co-developed-by: Maximilian Luz - -Signed-off-by: Sachi King -Patchset: surface-button ---- - drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- - 1 file changed, 8 insertions(+), 25 deletions(-) - -diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c -index cb6ec59a045d..4e8944f59def 100644 ---- a/drivers/input/misc/soc_button_array.c -+++ b/drivers/input/misc/soc_button_array.c -@@ -474,8 +474,8 @@ static const struct soc_device_data soc_device_INT33D3 = { - * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned - * devices use MSHW0040 for power and volume buttons, however the way they - * have to be addressed differs. Make sure that we only load this drivers -- * for the correct devices by checking the OEM Platform Revision provided by -- * the _DSM method. -+ * for the correct devices by checking if the OEM Platform Revision DSM call -+ * exists. - */ - #define MSHW0040_DSM_REVISION 0x01 - #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision -@@ -486,31 +486,14 @@ static const guid_t MSHW0040_DSM_UUID = - static int soc_device_check_MSHW0040(struct device *dev) - { - acpi_handle handle = ACPI_HANDLE(dev); -- union acpi_object *result; -- u64 oem_platform_rev = 0; // valid revisions are nonzero -- -- // get OEM platform revision -- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -- MSHW0040_DSM_REVISION, -- MSHW0040_DSM_GET_OMPR, NULL, -- ACPI_TYPE_INTEGER); -- -- if (result) { -- oem_platform_rev = result->integer.value; -- ACPI_FREE(result); -- } -- -- /* -- * If the revision is zero here, the _DSM evaluation has failed. This -- * indicates that we have a Pro 4 or Book 1 and this driver should not -- * be used. -- */ -- if (oem_platform_rev == 0) -- return -ENODEV; -+ bool exists; - -- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); -+ // check if OEM platform revision DSM call exists -+ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ BIT(MSHW0040_DSM_GET_OMPR)); - -- return 0; -+ return exists ? 0 : -ENODEV; - } - - /* --- -2.34.0 - -From c97966112f5fe77962a750b70c7899dcd535cc01 Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Tue, 5 Oct 2021 00:22:57 +1100 -Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd - variant - -The AMD variant of the Surface Laptop report 0 for their OEM platform -revision. The Surface devices that require the surfacepro3_button -driver do not have the _DSM that gets the OEM platform revision. If the -method does not exist, load surfacepro3_button. - -Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") -Co-developed-by: Maximilian Luz - -Signed-off-by: Sachi King -Patchset: surface-button ---- - drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- - 1 file changed, 6 insertions(+), 24 deletions(-) - -diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c -index 242fb690dcaf..30eea54dbb47 100644 ---- a/drivers/platform/surface/surfacepro3_button.c -+++ b/drivers/platform/surface/surfacepro3_button.c -@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) - /* - * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device - * ID (MSHW0040) for the power/volume buttons. Make sure this is the right -- * device by checking for the _DSM method and OEM Platform Revision. -+ * device by checking for the _DSM method and OEM Platform Revision DSM -+ * function. - * - * Returns true if the driver should bind to this device, i.e. the device is - * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. -@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) - static bool surface_button_check_MSHW0040(struct acpi_device *dev) - { - acpi_handle handle = dev->handle; -- union acpi_object *result; -- u64 oem_platform_rev = 0; // valid revisions are nonzero -- -- // get OEM platform revision -- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -- MSHW0040_DSM_REVISION, -- MSHW0040_DSM_GET_OMPR, -- NULL, ACPI_TYPE_INTEGER); -- -- /* -- * If evaluating the _DSM fails, the method is not present. This means -- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we -- * should use this driver. We use revision 0 indicating it is -- * unavailable. -- */ -- -- if (result) { -- oem_platform_rev = result->integer.value; -- ACPI_FREE(result); -- } -- -- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); - -- return oem_platform_rev == 0; -+ // make sure that OEM platform revision DSM call does not exist -+ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ BIT(MSHW0040_DSM_GET_OMPR)); - } - - --- -2.34.0 - diff --git a/patches/5.14/0009-surface-typecover.patch b/patches/5.14/0009-surface-typecover.patch deleted file mode 100644 index 02254015a..000000000 --- a/patches/5.14/0009-surface-typecover.patch +++ /dev/null @@ -1,233 +0,0 @@ -From da5a9a75b947342ff8695abb61ecc90d4f71f03b Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 5 Nov 2020 13:09:45 +0100 -Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when - suspending - -The Type Cover for Microsoft Surface devices supports a special usb -control request to disable or enable the built-in keyboard backlight. -On Windows, this request happens when putting the device into suspend or -resuming it, without it the backlight of the Type Cover will remain -enabled for some time even though the computer is suspended, which looks -weird to the user. - -So add support for this special usb control request to hid-multitouch, -which is the driver that's handling the Type Cover. - -The reason we have to use a pm_notifier for this instead of the usual -suspend/resume methods is that those won't get called in case the usb -device is already autosuspended. - -Also, if the device is autosuspended, we have to briefly autoresume it -in order to send the request. Doing that should be fine, the usb-core -driver does something similar during suspend inside choose_wakeup(). - -To make sure we don't send that request to every device but only to -devices which support it, add a new quirk -MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk -is only enabled for the usb id of the Surface Pro 2017 Type Cover, which -is where I confirmed that it's working. - -Patchset: surface-typecover ---- - drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- - 1 file changed, 98 insertions(+), 2 deletions(-) - -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index 3ea7cb1cda84..92fb053e0dd2 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -34,7 +34,10 @@ - #include - #include - #include -+#include - #include -+#include -+#include - #include - #include - #include -@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); - MODULE_LICENSE("GPL"); - - #include "hid-ids.h" -+#include "usbhid/usbhid.h" - - /* quirks to control the device */ - #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) -@@ -71,12 +75,15 @@ MODULE_LICENSE("GPL"); - #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) - #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) - #define MT_QUIRK_DISABLE_WAKEUP BIT(21) -+#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 - - #define MT_BUTTONTYPE_CLICKPAD 0 - -+#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+ - enum latency_mode { - HID_LATENCY_NORMAL = 0, - HID_LATENCY_HIGH = 1, -@@ -168,6 +175,8 @@ struct mt_device { - - struct list_head applications; - struct list_head reports; -+ -+ struct notifier_block pm_notifier; - }; - - static void mt_post_parse_default_settings(struct mt_device *td, -@@ -210,6 +219,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); - #define MT_CLS_GOOGLE 0x0111 - #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 - #define MT_CLS_SMART_TECH 0x0113 -+#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 - - #define MT_DEFAULT_MAXCONTACT 10 - #define MT_MAX_MAXCONTACT 250 -@@ -378,6 +388,16 @@ static const struct mt_class mt_classes[] = { - MT_QUIRK_CONTACT_CNT_ACCURATE | - MT_QUIRK_SEPARATE_APP_REPORT, - }, -+ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_ALWAYS_VALID | -+ MT_QUIRK_IGNORE_DUPLICATES | -+ MT_QUIRK_HOVERING | -+ MT_QUIRK_CONTACT_CNT_ACCURATE | -+ MT_QUIRK_STICKY_FINGERS | -+ MT_QUIRK_WIN8_PTP_BUTTONS, -+ .export_all_inputs = true -+ }, - { } - }; - -@@ -1690,6 +1710,69 @@ static void mt_expired_timeout(struct timer_list *t) - clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - -+static void get_type_cover_backlight_field(struct hid_device *hdev, -+ struct hid_field **field) -+{ -+ struct hid_report_enum *rep_enum; -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid -+ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { -+ *field = cur_field; -+ return; -+ } -+ } -+ } -+ } -+} -+ -+static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) -+{ -+ struct usb_device *udev = hid_to_usb_dev(hdev); -+ struct hid_field *field = NULL; -+ -+ /* Wake up the device in case it's already suspended */ -+ pm_runtime_get_sync(&udev->dev); -+ -+ get_type_cover_backlight_field(hdev, &field); -+ if (!field) { -+ hid_err(hdev, "couldn't find backlight field\n"); -+ goto out; -+ } -+ -+ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; -+ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); -+ -+out: -+ pm_runtime_put_sync(&udev->dev); -+} -+ -+static int mt_pm_notifier(struct notifier_block *notifier, -+ unsigned long pm_event, -+ void *unused) -+{ -+ struct mt_device *td = -+ container_of(notifier, struct mt_device, pm_notifier); -+ struct hid_device *hdev = td->hdev; -+ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { -+ if (pm_event == PM_SUSPEND_PREPARE) -+ update_keyboard_backlight(hdev, 0); -+ else if (pm_event == PM_POST_SUSPEND) -+ update_keyboard_backlight(hdev, 1); -+ } -+ -+ return NOTIFY_DONE; -+} -+ - static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - { - int ret, i; -@@ -1713,6 +1796,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; - hid_set_drvdata(hdev, td); - -+ td->pm_notifier.notifier_call = mt_pm_notifier; -+ register_pm_notifier(&td->pm_notifier); -+ - INIT_LIST_HEAD(&td->applications); - INIT_LIST_HEAD(&td->reports); - -@@ -1742,15 +1828,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - timer_setup(&td->release_timer, mt_expired_timeout, 0); - - ret = hid_parse(hdev); -- if (ret != 0) -+ if (ret != 0) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) - mt_fix_const_fields(hdev, HID_DG_CONTACTID); - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); -- if (ret) -+ if (ret) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); - if (ret) -@@ -1802,6 +1892,7 @@ static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); - -+ unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); - - sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); -@@ -2159,6 +2250,11 @@ static const struct hid_device_id mt_devices[] = { - MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, - USB_DEVICE_ID_XIROKU_CSR2) }, - -+ /* Microsoft Surface type cover */ -+ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, -+ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, -+ - /* Google MT devices */ - { .driver_data = MT_CLS_GOOGLE, - HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, --- -2.34.0 - diff --git a/patches/5.14/0010-cameras.patch b/patches/5.14/0010-cameras.patch deleted file mode 100644 index d3f7c16e4..000000000 --- a/patches/5.14/0010-cameras.patch +++ /dev/null @@ -1,5357 +0,0 @@ -From 92a75d7afacee622b3f426bc75c7b59aeb36d304 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 5 Apr 2021 23:56:53 +0100 -Subject: [PATCH] media: ipu3-cio2: Toggle sensor streaming in pm runtime ops - -The .suspend() and .resume() runtime_pm operations for the ipu3-cio2 -driver currently do not handle the sensor's stream. Setting .s_stream() on -or off for the sensor subdev means that sensors will pause and resume the -stream at the appropriate time even if their drivers don't implement those -operations. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 15 ++++++++++++++- - 1 file changed, 14 insertions(+), 1 deletion(-) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -index 47db0ee0fcbf..7bb86e246ebe 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -@@ -1973,12 +1973,19 @@ static int __maybe_unused cio2_suspend(struct device *dev) - struct pci_dev *pci_dev = to_pci_dev(dev); - struct cio2_device *cio2 = pci_get_drvdata(pci_dev); - struct cio2_queue *q = cio2->cur_queue; -+ int r; - - dev_dbg(dev, "cio2 suspend\n"); - if (!cio2->streaming) - return 0; - - /* Stop stream */ -+ r = v4l2_subdev_call(q->sensor, video, s_stream, 0); -+ if (r) { -+ dev_err(dev, "failed to stop sensor streaming\n"); -+ return r; -+ } -+ - cio2_hw_exit(cio2, q); - synchronize_irq(pci_dev->irq); - -@@ -2013,8 +2020,14 @@ static int __maybe_unused cio2_resume(struct device *dev) - } - - r = cio2_hw_init(cio2, q); -- if (r) -+ if (r) { - dev_err(dev, "fail to init cio2 hw\n"); -+ return r; -+ } -+ -+ r = v4l2_subdev_call(q->sensor, video, s_stream, 1); -+ if (r) -+ dev_err(dev, "fail to start sensor streaming\n"); - - return r; - } --- -2.34.0 - -From aef7dcd154115d18e90e2bc4b48891730ecff98b Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 5 Apr 2021 23:56:54 +0100 -Subject: [PATCH] media: i2c: Add support for ov5693 sensor - -The OV5693 is a 5 Mpx CMOS image sensor, connected via MIPI CSI-2. The -chip is capable of a single lane configuration, but currently only two -lanes are supported. - -Most of the sensor's features are supported, with the main exception -being the lens correction algorithm. - -The driver provides all mandatory, optional and recommended V4L2 controls -for maximum compatibility with libcamera. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 7 + - drivers/media/i2c/Kconfig | 11 + - drivers/media/i2c/Makefile | 1 + - drivers/media/i2c/ov5693.c | 1557 ++++++++++++++++++++++++++++++++++++ - 4 files changed, 1576 insertions(+) - create mode 100644 drivers/media/i2c/ov5693.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 5713585d060d..33fdad7a7ddf 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -13757,6 +13757,13 @@ S: Maintained - T: git git://linuxtv.org/media_tree.git - F: drivers/media/i2c/ov5675.c - -+OMNIVISION OV5693 SENSOR DRIVER -+M: Daniel Scally -+L: linux-media@vger.kernel.org -+S: Maintained -+T: git git://linuxtv.org/media_tree.git -+F: drivers/media/i2c/ov5693.c -+ - OMNIVISION OV5695 SENSOR DRIVER - M: Shunqian Zheng - L: linux-media@vger.kernel.org -diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig -index bde7fb021564..b2ac84d78b91 100644 ---- a/drivers/media/i2c/Kconfig -+++ b/drivers/media/i2c/Kconfig -@@ -1015,6 +1015,17 @@ config VIDEO_OV5675 - To compile this driver as a module, choose M here: the - module will be called ov5675. - -+config VIDEO_OV5693 -+ tristate "OmniVision OV5693 sensor support" -+ depends on I2C && VIDEO_V4L2 -+ select V4L2_FWNODE -+ help -+ This is a Video4Linux2 sensor driver for the OmniVision -+ OV5693 camera. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ov5693. -+ - config VIDEO_OV5695 - tristate "OmniVision OV5695 sensor support" - depends on I2C && VIDEO_V4L2 -diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile -index 1168fa6b84ed..011e90c1a288 100644 ---- a/drivers/media/i2c/Makefile -+++ b/drivers/media/i2c/Makefile -@@ -75,6 +75,7 @@ obj-$(CONFIG_VIDEO_OV5647) += ov5647.o - obj-$(CONFIG_VIDEO_OV5648) += ov5648.o - obj-$(CONFIG_VIDEO_OV5670) += ov5670.o - obj-$(CONFIG_VIDEO_OV5675) += ov5675.o -+obj-$(CONFIG_VIDEO_OV5693) += ov5693.o - obj-$(CONFIG_VIDEO_OV5695) += ov5695.o - obj-$(CONFIG_VIDEO_OV6650) += ov6650.o - obj-$(CONFIG_VIDEO_OV7251) += ov7251.o -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -new file mode 100644 -index 000000000000..9499ee10f56c ---- /dev/null -+++ b/drivers/media/i2c/ov5693.c -@@ -0,0 +1,1557 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. -+ * -+ * Adapted from the atomisp-ov5693 driver, with contributions from: -+ * -+ * Daniel Scally -+ * Jean-Michel Hautbois -+ * Fabian Wuthrich -+ * Tsuchiya Yuto -+ * Jordan Hand -+ * Jake Day -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+/* System Control */ -+#define OV5693_SW_RESET_REG 0x0103 -+#define OV5693_SW_STREAM_REG 0x0100 -+#define OV5693_START_STREAMING 0x01 -+#define OV5693_STOP_STREAMING 0x00 -+#define OV5693_SW_RESET 0x01 -+ -+#define OV5693_REG_CHIP_ID_H 0x300a -+#define OV5693_REG_CHIP_ID_L 0x300b -+/* Yes, this is right. The datasheet for the OV5693 gives its ID as 0x5690 */ -+#define OV5693_CHIP_ID 0x5690 -+ -+/* Exposure */ -+#define OV5693_EXPOSURE_L_CTRL_HH_REG 0x3500 -+#define OV5693_EXPOSURE_L_CTRL_H_REG 0x3501 -+#define OV5693_EXPOSURE_L_CTRL_L_REG 0x3502 -+#define OV5693_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(14, 12)) >> 12) -+#define OV5693_EXPOSURE_CTRL_H(v) (((v) & GENMASK(11, 4)) >> 4) -+#define OV5693_EXPOSURE_CTRL_L(v) (((v) & GENMASK(3, 0)) << 4) -+#define OV5693_INTEGRATION_TIME_MARGIN 8 -+#define OV5693_EXPOSURE_MIN 1 -+#define OV5693_EXPOSURE_STEP 1 -+ -+/* Analogue Gain */ -+#define OV5693_GAIN_CTRL_H_REG 0x350a -+#define OV5693_GAIN_CTRL_H(v) (((v) >> 4) & GENMASK(2, 0)) -+#define OV5693_GAIN_CTRL_L_REG 0x350b -+#define OV5693_GAIN_CTRL_L(v) (((v) << 4) & GENMASK(7, 4)) -+#define OV5693_GAIN_MIN 1 -+#define OV5693_GAIN_MAX 127 -+#define OV5693_GAIN_DEF 8 -+#define OV5693_GAIN_STEP 1 -+ -+/* Digital Gain */ -+#define OV5693_MWB_RED_GAIN_H_REG 0x3400 -+#define OV5693_MWB_RED_GAIN_L_REG 0x3401 -+#define OV5693_MWB_GREEN_GAIN_H_REG 0x3402 -+#define OV5693_MWB_GREEN_GAIN_L_REG 0x3403 -+#define OV5693_MWB_BLUE_GAIN_H_REG 0x3404 -+#define OV5693_MWB_BLUE_GAIN_L_REG 0x3405 -+#define OV5693_MWB_GAIN_H_CTRL(v) (((v) >> 8) & GENMASK(3, 0)) -+#define OV5693_MWB_GAIN_L_CTRL(v) ((v) & GENMASK(7, 0)) -+#define OV5693_MWB_GAIN_MAX 0x0fff -+#define OV5693_DIGITAL_GAIN_MIN 1 -+#define OV5693_DIGITAL_GAIN_MAX 4095 -+#define OV5693_DIGITAL_GAIN_DEF 1024 -+#define OV5693_DIGITAL_GAIN_STEP 1 -+ -+/* Timing and Format */ -+#define OV5693_CROP_START_X_H_REG 0x3800 -+#define OV5693_CROP_START_X_H(v) (((v) & GENMASK(12, 8)) >> 8) -+#define OV5693_CROP_START_X_L_REG 0x3801 -+#define OV5693_CROP_START_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_START_Y_H_REG 0x3802 -+#define OV5693_CROP_START_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) -+#define OV5693_CROP_START_Y_L_REG 0x3803 -+#define OV5693_CROP_START_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_END_X_H_REG 0x3804 -+#define OV5693_CROP_END_X_H(v) (((v) & GENMASK(12, 8)) >> 8) -+#define OV5693_CROP_END_X_L_REG 0x3805 -+#define OV5693_CROP_END_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_END_Y_H_REG 0x3806 -+#define OV5693_CROP_END_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) -+#define OV5693_CROP_END_Y_L_REG 0x3807 -+#define OV5693_CROP_END_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OUTPUT_SIZE_X_H_REG 0x3808 -+#define OV5693_OUTPUT_SIZE_X_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OUTPUT_SIZE_X_L_REG 0x3809 -+#define OV5693_OUTPUT_SIZE_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OUTPUT_SIZE_Y_H_REG 0x380a -+#define OV5693_OUTPUT_SIZE_Y_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OUTPUT_SIZE_Y_L_REG 0x380b -+#define OV5693_OUTPUT_SIZE_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_TIMING_HTS_H_REG 0x380c -+#define OV5693_TIMING_HTS_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_TIMING_HTS_L_REG 0x380d -+#define OV5693_TIMING_HTS_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_FIXED_PPL 2688U -+ -+#define OV5693_TIMING_VTS_H_REG 0x380e -+#define OV5693_TIMING_VTS_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_TIMING_VTS_L_REG 0x380f -+#define OV5693_TIMING_VTS_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_TIMING_MAX_VTS 0xffff -+#define OV5693_TIMING_MIN_VTS 0x04 -+ -+#define OV5693_OFFSET_START_X_H_REG 0x3810 -+#define OV5693_OFFSET_START_X_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OFFSET_START_X_L_REG 0x3811 -+#define OV5693_OFFSET_START_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OFFSET_START_Y_H_REG 0x3812 -+#define OV5693_OFFSET_START_Y_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OFFSET_START_Y_L_REG 0x3813 -+#define OV5693_OFFSET_START_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_SUB_INC_X_REG 0x3814 -+#define OV5693_SUB_INC_Y_REG 0x3815 -+ -+#define OV5693_FORMAT1_REG 0x3820 -+#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -+#define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) -+#define OV5693_FORMAT1_VBIN_EN BIT(0) -+#define OV5693_FORMAT2_REG 0x3821 -+#define OV5693_FORMAT2_HDR_EN BIT(7) -+#define OV5693_FORMAT2_FLIP_HORZ_ISP_EN BIT(2) -+#define OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN BIT(1) -+#define OV5693_FORMAT2_HBIN_EN BIT(0) -+ -+#define OV5693_ISP_CTRL2_REG 0x5002 -+#define OV5693_ISP_SCALE_ENABLE BIT(7) -+ -+/* Pixel Array */ -+#define OV5693_NATIVE_WIDTH 2624 -+#define OV5693_NATIVE_HEIGHT 1956 -+#define OV5693_NATIVE_START_LEFT 0 -+#define OV5693_NATIVE_START_TOP 0 -+#define OV5693_ACTIVE_WIDTH 2592 -+#define OV5693_ACTIVE_HEIGHT 1944 -+#define OV5693_ACTIVE_START_LEFT 16 -+#define OV5693_ACTIVE_START_TOP 6 -+#define OV5693_MIN_CROP_WIDTH 2 -+#define OV5693_MIN_CROP_HEIGHT 2 -+ -+/* Test Pattern */ -+#define OV5693_TEST_PATTERN_REG 0x5e00 -+#define OV5693_TEST_PATTERN_ENABLE BIT(7) -+#define OV5693_TEST_PATTERN_ROLLING BIT(6) -+#define OV5693_TEST_PATTERN_RANDOM 0x01 -+#define OV5693_TEST_PATTERN_BARS 0x00 -+ -+/* System Frequencies */ -+#define OV5693_XVCLK_FREQ 19200000 -+#define OV5693_LINK_FREQ_400MHZ 400000000 -+#define OV5693_PIXEL_RATE 160000000 -+ -+/* Miscellaneous */ -+#define OV5693_NUM_SUPPLIES 2 -+ -+#define to_ov5693_sensor(x) container_of(x, struct ov5693_device, sd) -+ -+struct ov5693_reg { -+ u16 reg; -+ u8 val; -+}; -+ -+struct ov5693_reg_list { -+ u32 num_regs; -+ const struct ov5693_reg *regs; -+}; -+ -+struct ov5693_device { -+ struct i2c_client *client; -+ struct device *dev; -+ -+ /* Protect against concurrent changes to controls */ -+ struct mutex lock; -+ -+ struct gpio_desc *reset; -+ struct gpio_desc *powerdown; -+ struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; -+ struct clk *clk; -+ -+ struct ov5693_mode { -+ struct v4l2_rect crop; -+ struct v4l2_mbus_framefmt format; -+ bool binning_x; -+ bool binning_y; -+ unsigned int inc_x_odd; -+ unsigned int inc_y_odd; -+ unsigned int vts; -+ } mode; -+ bool streaming; -+ -+ struct v4l2_subdev sd; -+ struct media_pad pad; -+ -+ struct ov5693_v4l2_ctrls { -+ struct v4l2_ctrl_handler handler; -+ struct v4l2_ctrl *link_freq; -+ struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *exposure; -+ struct v4l2_ctrl *analogue_gain; -+ struct v4l2_ctrl *digital_gain; -+ struct v4l2_ctrl *hflip; -+ struct v4l2_ctrl *vflip; -+ struct v4l2_ctrl *hblank; -+ struct v4l2_ctrl *vblank; -+ struct v4l2_ctrl *test_pattern; -+ } ctrls; -+}; -+ -+static const struct ov5693_reg ov5693_global_regs[] = { -+ {0x3016, 0xf0}, -+ {0x3017, 0xf0}, -+ {0x3018, 0xf0}, -+ {0x3022, 0x01}, -+ {0x3028, 0x44}, -+ {0x3098, 0x02}, -+ {0x3099, 0x19}, -+ {0x309a, 0x02}, -+ {0x309b, 0x01}, -+ {0x309c, 0x00}, -+ {0x30a0, 0xd2}, -+ {0x30a2, 0x01}, -+ {0x30b2, 0x00}, -+ {0x30b3, 0x7d}, -+ {0x30b4, 0x03}, -+ {0x30b5, 0x04}, -+ {0x30b6, 0x01}, -+ {0x3104, 0x21}, -+ {0x3106, 0x00}, -+ {0x3406, 0x01}, -+ {0x3503, 0x07}, -+ {0x350b, 0x40}, -+ {0x3601, 0x0a}, -+ {0x3602, 0x38}, -+ {0x3612, 0x80}, -+ {0x3620, 0x54}, -+ {0x3621, 0xc7}, -+ {0x3622, 0x0f}, -+ {0x3625, 0x10}, -+ {0x3630, 0x55}, -+ {0x3631, 0xf4}, -+ {0x3632, 0x00}, -+ {0x3633, 0x34}, -+ {0x3634, 0x02}, -+ {0x364d, 0x0d}, -+ {0x364f, 0xdd}, -+ {0x3660, 0x04}, -+ {0x3662, 0x10}, -+ {0x3663, 0xf1}, -+ {0x3665, 0x00}, -+ {0x3666, 0x20}, -+ {0x3667, 0x00}, -+ {0x366a, 0x80}, -+ {0x3680, 0xe0}, -+ {0x3681, 0x00}, -+ {0x3700, 0x42}, -+ {0x3701, 0x14}, -+ {0x3702, 0xa0}, -+ {0x3703, 0xd8}, -+ {0x3704, 0x78}, -+ {0x3705, 0x02}, -+ {0x370a, 0x00}, -+ {0x370b, 0x20}, -+ {0x370c, 0x0c}, -+ {0x370d, 0x11}, -+ {0x370e, 0x00}, -+ {0x370f, 0x40}, -+ {0x3710, 0x00}, -+ {0x371a, 0x1c}, -+ {0x371b, 0x05}, -+ {0x371c, 0x01}, -+ {0x371e, 0xa1}, -+ {0x371f, 0x0c}, -+ {0x3721, 0x00}, -+ {0x3724, 0x10}, -+ {0x3726, 0x00}, -+ {0x372a, 0x01}, -+ {0x3730, 0x10}, -+ {0x3738, 0x22}, -+ {0x3739, 0xe5}, -+ {0x373a, 0x50}, -+ {0x373b, 0x02}, -+ {0x373c, 0x41}, -+ {0x373f, 0x02}, -+ {0x3740, 0x42}, -+ {0x3741, 0x02}, -+ {0x3742, 0x18}, -+ {0x3743, 0x01}, -+ {0x3744, 0x02}, -+ {0x3747, 0x10}, -+ {0x374c, 0x04}, -+ {0x3751, 0xf0}, -+ {0x3752, 0x00}, -+ {0x3753, 0x00}, -+ {0x3754, 0xc0}, -+ {0x3755, 0x00}, -+ {0x3756, 0x1a}, -+ {0x3758, 0x00}, -+ {0x3759, 0x0f}, -+ {0x376b, 0x44}, -+ {0x375c, 0x04}, -+ {0x3774, 0x10}, -+ {0x3776, 0x00}, -+ {0x377f, 0x08}, -+ {0x3780, 0x22}, -+ {0x3781, 0x0c}, -+ {0x3784, 0x2c}, -+ {0x3785, 0x1e}, -+ {0x378f, 0xf5}, -+ {0x3791, 0xb0}, -+ {0x3795, 0x00}, -+ {0x3796, 0x64}, -+ {0x3797, 0x11}, -+ {0x3798, 0x30}, -+ {0x3799, 0x41}, -+ {0x379a, 0x07}, -+ {0x379b, 0xb0}, -+ {0x379c, 0x0c}, -+ {0x3a04, 0x06}, -+ {0x3a05, 0x14}, -+ {0x3e07, 0x20}, -+ {0x4000, 0x08}, -+ {0x4001, 0x04}, -+ {0x4004, 0x08}, -+ {0x4006, 0x20}, -+ {0x4008, 0x24}, -+ {0x4009, 0x10}, -+ {0x4058, 0x00}, -+ {0x4101, 0xb2}, -+ {0x4307, 0x31}, -+ {0x4511, 0x05}, -+ {0x4512, 0x01}, -+ {0x481f, 0x30}, -+ {0x4826, 0x2c}, -+ {0x4d02, 0xfd}, -+ {0x4d03, 0xf5}, -+ {0x4d04, 0x0c}, -+ {0x4d05, 0xcc}, -+ {0x4837, 0x0a}, -+ {0x5003, 0x20}, -+ {0x5013, 0x00}, -+ {0x5842, 0x01}, -+ {0x5843, 0x2b}, -+ {0x5844, 0x01}, -+ {0x5845, 0x92}, -+ {0x5846, 0x01}, -+ {0x5847, 0x8f}, -+ {0x5848, 0x01}, -+ {0x5849, 0x0c}, -+ {0x5e10, 0x0c}, -+ {0x3820, 0x00}, -+ {0x3821, 0x1e}, -+ {0x5041, 0x14} -+}; -+ -+static const struct ov5693_reg_list ov5693_global_setting = { -+ .num_regs = ARRAY_SIZE(ov5693_global_regs), -+ .regs = ov5693_global_regs, -+}; -+ -+static const struct v4l2_rect ov5693_default_crop = { -+ .left = OV5693_ACTIVE_START_LEFT, -+ .top = OV5693_ACTIVE_START_TOP, -+ .width = OV5693_ACTIVE_WIDTH, -+ .height = OV5693_ACTIVE_HEIGHT, -+}; -+ -+static const struct v4l2_mbus_framefmt ov5693_default_fmt = { -+ .width = OV5693_ACTIVE_WIDTH, -+ .height = OV5693_ACTIVE_HEIGHT, -+ .code = MEDIA_BUS_FMT_SBGGR10_1X10, -+}; -+ -+static const s64 link_freq_menu_items[] = { -+ OV5693_LINK_FREQ_400MHZ -+}; -+ -+static const char * const ov5693_supply_names[] = { -+ "avdd", -+ "dovdd", -+}; -+ -+static const char * const ov5693_test_pattern_menu[] = { -+ "Disabled", -+ "Random Data", -+ "Colour Bars", -+ "Colour Bars with Rolling Bar" -+}; -+ -+static const u8 ov5693_test_pattern_bits[] = { -+ 0, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_RANDOM, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS | -+ OV5693_TEST_PATTERN_ROLLING, -+}; -+ -+/* I2C I/O Operations */ -+ -+static int ov5693_read_reg(struct ov5693_device *ov5693, u16 addr, u8 *value) -+{ -+ struct i2c_client *client = ov5693->client; -+ struct i2c_msg msgs[2]; -+ u8 addr_buf[2]; -+ u8 data_buf; -+ int ret; -+ -+ put_unaligned_be16(addr, addr_buf); -+ -+ /* 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 register value */ -+ msgs[1].addr = client->addr; -+ msgs[1].flags = I2C_M_RD; -+ msgs[1].len = 1; -+ msgs[1].buf = &data_buf; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ if (ret != ARRAY_SIZE(msgs)) -+ return -EIO; -+ -+ *value = data_buf; -+ -+ return 0; -+} -+ -+static void ov5693_write_reg(struct ov5693_device *ov5693, u16 addr, u8 value, -+ int *error) -+{ -+ unsigned char data[3] = { addr >> 8, addr & 0xff, value }; -+ int ret; -+ -+ if (*error < 0) -+ return; -+ -+ ret = i2c_master_send(ov5693->client, data, sizeof(data)); -+ if (ret < 0) { -+ dev_dbg(ov5693->dev, "i2c send error at address 0x%04x: %d\n", -+ addr, ret); -+ *error = ret; -+ } -+} -+ -+static int ov5693_write_reg_array(struct ov5693_device *ov5693, -+ const struct ov5693_reg_list *reglist) -+{ -+ unsigned int i; -+ int ret = 0; -+ -+ for (i = 0; i < reglist->num_regs; i++) -+ ov5693_write_reg(ov5693, reglist->regs[i].reg, -+ reglist->regs[i].val, &ret); -+ -+ return ret; -+} -+ -+static int ov5693_update_bits(struct ov5693_device *ov5693, u16 address, -+ u16 mask, u16 bits) -+{ -+ u8 value = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, address, &value); -+ if (ret) -+ return ret; -+ -+ value &= ~mask; -+ value |= bits; -+ -+ ov5693_write_reg(ov5693, address, value, &ret); -+ -+ return ret; -+} -+ -+/* V4L2 Controls Functions */ -+ -+static int ov5693_flip_vert_configure(struct ov5693_device *ov5693, bool enable) -+{ -+ u8 bits = OV5693_FORMAT1_FLIP_VERT_ISP_EN | -+ OV5693_FORMAT1_FLIP_VERT_SENSOR_EN; -+ int ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, bits, -+ enable ? bits : 0); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int ov5693_flip_horz_configure(struct ov5693_device *ov5693, bool enable) -+{ -+ u8 bits = OV5693_FORMAT2_FLIP_HORZ_ISP_EN | -+ OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN; -+ int ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, bits, -+ enable ? bits : 0); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int ov5693_get_exposure(struct ov5693_device *ov5693, s32 *value) -+{ -+ u8 exposure_hh = 0, exposure_h = 0, exposure_l = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, &exposure_hh); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, &exposure_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, &exposure_l); -+ if (ret) -+ return ret; -+ -+ /* The lowest 4 bits are unsupported fractional bits */ -+ *value = ((exposure_hh << 16) | (exposure_h << 8) | exposure_l) >> 4; -+ -+ return 0; -+} -+ -+static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, -+ OV5693_EXPOSURE_CTRL_HH(exposure), &ret); -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, -+ OV5693_EXPOSURE_CTRL_H(exposure), &ret); -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, -+ OV5693_EXPOSURE_CTRL_L(exposure), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_get_gain(struct ov5693_device *ov5693, u32 *gain) -+{ -+ u8 gain_l = 0, gain_h = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_H_REG, &gain_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_L_REG, &gain_l); -+ if (ret) -+ return ret; -+ -+ /* As with exposure, the lowest 4 bits are fractional bits. */ -+ *gain = ((gain_h << 8) | gain_l) >> 4; -+ -+ return ret; -+} -+ -+static int ov5693_digital_gain_configure(struct ov5693_device *ov5693, u32 gain) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_analog_gain_configure(struct ov5693_device *ov5693, u32 gain) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_L_REG, -+ OV5693_GAIN_CTRL_L(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_H_REG, -+ OV5693_GAIN_CTRL_H(gain), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_vts_configure(struct ov5693_device *ov5693, u32 vblank) -+{ -+ u16 vts = ov5693->mode.format.height + vblank; -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG, -+ OV5693_TIMING_VTS_H(vts), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG, -+ OV5693_TIMING_VTS_L(vts), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_test_pattern_configure(struct ov5693_device *ov5693, u32 idx) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_TEST_PATTERN_REG, -+ ov5693_test_pattern_bits[idx], &ret); -+ -+ return ret; -+} -+ -+static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *ov5693 = -+ container_of(ctrl->handler, struct ov5693_device, ctrls.handler); -+ int ret = 0; -+ -+ /* If VBLANK is altered we need to update exposure to compensate */ -+ if (ctrl->id == V4L2_CID_VBLANK) { -+ int exposure_max; -+ -+ exposure_max = ov5693->mode.format.height + ctrl->val - -+ OV5693_INTEGRATION_TIME_MARGIN; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, -+ ov5693->ctrls.exposure->minimum, -+ exposure_max, -+ ov5693->ctrls.exposure->step, -+ min(ov5693->ctrls.exposure->val, exposure_max)); -+ } -+ -+ /* Only apply changes to the controls if the device is powered up */ -+ if (!pm_runtime_get_if_in_use(ov5693->dev)) -+ return 0; -+ -+ switch (ctrl->id) { -+ case V4L2_CID_EXPOSURE: -+ ret = ov5693_exposure_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_ANALOGUE_GAIN: -+ ret = ov5693_analog_gain_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_DIGITAL_GAIN: -+ ret = ov5693_digital_gain_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_HFLIP: -+ ret = ov5693_flip_horz_configure(ov5693, !!ctrl->val); -+ break; -+ case V4L2_CID_VFLIP: -+ ret = ov5693_flip_vert_configure(ov5693, !!ctrl->val); -+ break; -+ case V4L2_CID_VBLANK: -+ ret = ov5693_vts_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_TEST_PATTERN: -+ ret = ov5693_test_pattern_configure(ov5693, ctrl->val); -+ break; -+ default: -+ ret = -EINVAL; -+ } -+ -+ pm_runtime_put(ov5693->dev); -+ -+ return ret; -+} -+ -+static int ov5693_g_volatile_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *ov5693 = -+ container_of(ctrl->handler, struct ov5693_device, ctrls.handler); -+ -+ switch (ctrl->id) { -+ case V4L2_CID_EXPOSURE_ABSOLUTE: -+ return ov5693_get_exposure(ov5693, &ctrl->val); -+ case V4L2_CID_AUTOGAIN: -+ return ov5693_get_gain(ov5693, &ctrl->val); -+ default: -+ return -EINVAL; -+ } -+} -+ -+static const struct v4l2_ctrl_ops ov5693_ctrl_ops = { -+ .s_ctrl = ov5693_s_ctrl, -+ .g_volatile_ctrl = ov5693_g_volatile_ctrl -+}; -+ -+/* System Control Functions */ -+ -+static int ov5693_mode_configure(struct ov5693_device *ov5693) -+{ -+ const struct ov5693_mode *mode = &ov5693->mode; -+ int ret = 0; -+ -+ /* Crop Start X */ -+ ov5693_write_reg(ov5693, OV5693_CROP_START_X_H_REG, -+ OV5693_CROP_START_X_H(mode->crop.left), &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_START_X_L_REG, -+ OV5693_CROP_START_X_L(mode->crop.left), &ret); -+ -+ /* Offset X */ -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_H_REG, -+ OV5693_OFFSET_START_X_H(0), &ret); -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_L_REG, -+ OV5693_OFFSET_START_X_L(0), &ret); -+ -+ /* Output Size X */ -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_H_REG, -+ OV5693_OUTPUT_SIZE_X_H(mode->format.width), &ret); -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_L_REG, -+ OV5693_OUTPUT_SIZE_X_L(mode->format.width), &ret); -+ -+ /* Crop End X */ -+ ov5693_write_reg(ov5693, OV5693_CROP_END_X_H_REG, -+ OV5693_CROP_END_X_H(mode->crop.left + mode->crop.width), -+ &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_END_X_L_REG, -+ OV5693_CROP_END_X_L(mode->crop.left + mode->crop.width), -+ &ret); -+ -+ /* Horizontal Total Size */ -+ ov5693_write_reg(ov5693, OV5693_TIMING_HTS_H_REG, -+ OV5693_TIMING_HTS_H(OV5693_FIXED_PPL), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_HTS_L_REG, -+ OV5693_TIMING_HTS_L(OV5693_FIXED_PPL), &ret); -+ -+ /* Crop Start Y */ -+ ov5693_write_reg(ov5693, OV5693_CROP_START_Y_H_REG, -+ OV5693_CROP_START_Y_H(mode->crop.top), &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_START_Y_L_REG, -+ OV5693_CROP_START_Y_L(mode->crop.top), &ret); -+ -+ /* Offset Y */ -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_H_REG, -+ OV5693_OFFSET_START_Y_H(0), &ret); -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_L_REG, -+ OV5693_OFFSET_START_Y_L(0), &ret); -+ -+ /* Output Size Y */ -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_H_REG, -+ OV5693_OUTPUT_SIZE_Y_H(mode->format.height), &ret); -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_L_REG, -+ OV5693_OUTPUT_SIZE_Y_L(mode->format.height), &ret); -+ -+ /* Crop End Y */ -+ ov5693_write_reg(ov5693, OV5693_CROP_END_Y_H_REG, -+ OV5693_CROP_END_Y_H(mode->crop.top + mode->crop.height), -+ &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_END_Y_L_REG, -+ OV5693_CROP_END_Y_L(mode->crop.top + mode->crop.height), -+ &ret); -+ -+ /* Vertical Total Size */ -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG, -+ OV5693_TIMING_VTS_H(mode->vts), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG, -+ OV5693_TIMING_VTS_L(mode->vts), &ret); -+ -+ /* Subsample X increase */ -+ ov5693_write_reg(ov5693, OV5693_SUB_INC_X_REG, -+ ((mode->inc_x_odd << 4) & 0xf0) | 0x01, &ret); -+ /* Subsample Y increase */ -+ ov5693_write_reg(ov5693, OV5693_SUB_INC_Y_REG, -+ ((mode->inc_y_odd << 4) & 0xf0) | 0x01, &ret); -+ -+ /* Binning */ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, -+ OV5693_FORMAT1_VBIN_EN, -+ mode->binning_y ? OV5693_FORMAT1_VBIN_EN : 0); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, -+ OV5693_FORMAT2_HBIN_EN, -+ mode->binning_x ? OV5693_FORMAT2_HBIN_EN : 0); -+ -+ return ret; -+} -+ -+static int ov5693_sw_standby(struct ov5693_device *ov5693, bool standby) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_SW_STREAM_REG, -+ standby ? OV5693_STOP_STREAMING : OV5693_START_STREAMING, -+ &ret); -+ -+ return ret; -+} -+ -+static int ov5693_sw_reset(struct ov5693_device *ov5693) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_SW_RESET_REG, OV5693_SW_RESET, &ret); -+ -+ return ret; -+} -+ -+static int ov5693_sensor_init(struct ov5693_device *ov5693) -+{ -+ int ret = 0; -+ -+ ret = ov5693_sw_reset(ov5693); -+ if (ret) { -+ dev_err(ov5693->dev, "%s software reset error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg_array(ov5693, &ov5693_global_setting); -+ if (ret) { -+ dev_err(ov5693->dev, "%s global settings error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_mode_configure(ov5693); -+ if (ret) { -+ dev_err(ov5693->dev, "%s mode configure error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_sw_standby(ov5693, true); -+ if (ret) -+ dev_err(ov5693->dev, "%s software standby error\n", __func__); -+ -+ return ret; -+} -+ -+static void ov5693_sensor_powerdown(struct ov5693_device *ov5693) -+{ -+ gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); -+ -+ regulator_bulk_disable(OV5693_NUM_SUPPLIES, ov5693->supplies); -+ -+ clk_disable_unprepare(ov5693->clk); -+} -+ -+static int ov5693_sensor_powerup(struct ov5693_device *ov5693) -+{ -+ int ret; -+ -+ gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); -+ -+ ret = clk_prepare_enable(ov5693->clk); -+ if (ret) { -+ dev_err(ov5693->dev, "Failed to enable clk\n"); -+ goto fail_power; -+ } -+ -+ ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, ov5693->supplies); -+ if (ret) { -+ dev_err(ov5693->dev, "Failed to enable regulators\n"); -+ goto fail_power; -+ } -+ -+ gpiod_set_value_cansleep(ov5693->powerdown, 0); -+ gpiod_set_value_cansleep(ov5693->reset, 0); -+ -+ usleep_range(5000, 7500); -+ -+ return 0; -+ -+fail_power: -+ ov5693_sensor_powerdown(ov5693); -+ return ret; -+} -+ -+static int __maybe_unused ov5693_sensor_suspend(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ ov5693_sensor_powerdown(ov5693); -+ -+ return 0; -+} -+ -+static int __maybe_unused ov5693_sensor_resume(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ int ret; -+ -+ mutex_lock(&ov5693->lock); -+ -+ ret = ov5693_sensor_powerup(ov5693); -+ if (ret) -+ goto out_unlock; -+ -+ ret = ov5693_sensor_init(ov5693); -+ if (ret) { -+ dev_err(dev, "ov5693 sensor init failure\n"); -+ goto err_power; -+ } -+ -+ goto out_unlock; -+ -+err_power: -+ ov5693_sensor_powerdown(ov5693); -+out_unlock: -+ mutex_unlock(&ov5693->lock); -+ return ret; -+} -+ -+static int ov5693_detect(struct ov5693_device *ov5693) -+{ -+ u8 id_l = 0, id_h = 0; -+ u16 id = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_H, &id_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_L, &id_l); -+ if (ret) -+ return ret; -+ -+ id = (id_h << 8) | id_l; -+ -+ if (id != OV5693_CHIP_ID) { -+ dev_err(ov5693->dev, "sensor ID mismatch. Found 0x%04x\n", id); -+ return -ENODEV; -+ } -+ -+ return 0; -+} -+ -+/* V4L2 Framework callbacks */ -+ -+static unsigned int __ov5693_calc_vts(u32 height) -+{ -+ /* -+ * We need to set a sensible default VTS for whatever format height we -+ * happen to be given from set_fmt(). This function just targets -+ * an even multiple of 30fps. -+ */ -+ -+ unsigned int tgt_fps; -+ -+ tgt_fps = rounddown(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / height, 30); -+ -+ return ALIGN_DOWN(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / tgt_fps, 2); -+} -+ -+static struct v4l2_mbus_framefmt * -+__ov5693_get_pad_format(struct ov5693_device *ov5693, -+ struct v4l2_subdev_state *state, -+ unsigned int pad, enum v4l2_subdev_format_whence which) -+{ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ return v4l2_subdev_get_try_format(&ov5693->sd, state, pad); -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ return &ov5693->mode.format; -+ default: -+ return NULL; -+ } -+} -+ -+static struct v4l2_rect * -+__ov5693_get_pad_crop(struct ov5693_device *ov5693, -+ struct v4l2_subdev_state *state, -+ unsigned int pad, enum v4l2_subdev_format_whence which) -+{ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ return v4l2_subdev_get_try_crop(&ov5693->sd, state, pad); -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ return &ov5693->mode.crop; -+ } -+ -+ return NULL; -+} -+ -+static int ov5693_get_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_format *format) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ format->format = ov5693->mode.format; -+ -+ return 0; -+} -+ -+static int ov5693_set_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_format *format) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ const struct v4l2_rect *crop; -+ struct v4l2_mbus_framefmt *fmt; -+ unsigned int hratio, vratio; -+ unsigned int width, height; -+ unsigned int hblank; -+ int exposure_max; -+ int ret = 0; -+ -+ crop = __ov5693_get_pad_crop(ov5693, state, format->pad, format->which); -+ -+ /* -+ * Align to two to simplify the binning calculations below, and clamp -+ * the requested format at the crop rectangle -+ */ -+ width = clamp_t(unsigned int, ALIGN(format->format.width, 2), -+ OV5693_MIN_CROP_WIDTH, crop->width); -+ height = clamp_t(unsigned int, ALIGN(format->format.height, 2), -+ OV5693_MIN_CROP_HEIGHT, crop->height); -+ -+ /* -+ * We can only support setting either the dimensions of the crop rect -+ * or those dimensions binned (separately) by a factor of two. -+ */ -+ hratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->width, width), 1, 2); -+ vratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->height, height), 1, 2); -+ -+ fmt = __ov5693_get_pad_format(ov5693, state, format->pad, format->which); -+ -+ fmt->width = crop->width / hratio; -+ fmt->height = crop->height / vratio; -+ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ -+ format->format = *fmt; -+ -+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) -+ return ret; -+ -+ mutex_lock(&ov5693->lock); -+ -+ ov5693->mode.binning_x = hratio > 1 ? true : false; -+ ov5693->mode.inc_x_odd = hratio > 1 ? 3 : 1; -+ ov5693->mode.binning_y = vratio > 1 ? true : false; -+ ov5693->mode.inc_y_odd = vratio > 1 ? 3 : 1; -+ -+ ov5693->mode.vts = __ov5693_calc_vts(fmt->height); -+ -+ __v4l2_ctrl_modify_range(ov5693->ctrls.vblank, -+ OV5693_TIMING_MIN_VTS, -+ OV5693_TIMING_MAX_VTS - fmt->height, -+ 1, ov5693->mode.vts - fmt->height); -+ __v4l2_ctrl_s_ctrl(ov5693->ctrls.vblank, -+ ov5693->mode.vts - fmt->height); -+ -+ hblank = OV5693_FIXED_PPL - fmt->width; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.hblank, hblank, hblank, 1, -+ hblank); -+ -+ exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, -+ ov5693->ctrls.exposure->minimum, exposure_max, -+ ov5693->ctrls.exposure->step, -+ min(ov5693->ctrls.exposure->val, exposure_max)); -+ -+ mutex_unlock(&ov5693->lock); -+ return ret; -+} -+ -+static int ov5693_get_selection(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ switch (sel->target) { -+ case V4L2_SEL_TGT_CROP: -+ mutex_lock(&ov5693->lock); -+ sel->r = *__ov5693_get_pad_crop(ov5693, state, sel->pad, -+ sel->which); -+ mutex_unlock(&ov5693->lock); -+ break; -+ case V4L2_SEL_TGT_NATIVE_SIZE: -+ sel->r.top = 0; -+ sel->r.left = 0; -+ sel->r.width = OV5693_NATIVE_WIDTH; -+ sel->r.height = OV5693_NATIVE_HEIGHT; -+ break; -+ case V4L2_SEL_TGT_CROP_BOUNDS: -+ case V4L2_SEL_TGT_CROP_DEFAULT: -+ sel->r.top = OV5693_ACTIVE_START_TOP; -+ sel->r.left = OV5693_ACTIVE_START_LEFT; -+ sel->r.width = OV5693_ACTIVE_WIDTH; -+ sel->r.height = OV5693_ACTIVE_HEIGHT; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int ov5693_set_selection(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(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 boundaries of the crop rectangle to the size of the sensor -+ * pixel array. Align to multiples of 2 to ensure Bayer pattern isn't -+ * disrupted. -+ */ -+ rect.left = clamp(ALIGN(sel->r.left, 2), OV5693_NATIVE_START_LEFT, -+ OV5693_NATIVE_WIDTH); -+ rect.top = clamp(ALIGN(sel->r.top, 2), OV5693_NATIVE_START_TOP, -+ OV5693_NATIVE_HEIGHT); -+ rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), -+ OV5693_MIN_CROP_WIDTH, OV5693_NATIVE_WIDTH); -+ rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), -+ OV5693_MIN_CROP_HEIGHT, OV5693_NATIVE_HEIGHT); -+ -+ /* Make sure the crop rectangle isn't outside the bounds of the array */ -+ rect.width = min_t(unsigned int, rect.width, -+ OV5693_NATIVE_WIDTH - rect.left); -+ rect.height = min_t(unsigned int, rect.height, -+ OV5693_NATIVE_HEIGHT - rect.top); -+ -+ __crop = __ov5693_get_pad_crop(ov5693, state, sel->pad, sel->which); -+ -+ if (rect.width != __crop->width || rect.height != __crop->height) { -+ /* -+ * Reset the output image size if the crop rectangle size has -+ * been modified. -+ */ -+ format = __ov5693_get_pad_format(ov5693, state, sel->pad, sel->which); -+ format->width = rect.width; -+ format->height = rect.height; -+ } -+ -+ *__crop = rect; -+ sel->r = rect; -+ -+ return 0; -+} -+ -+static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ int ret; -+ -+ if (enable) { -+ ret = pm_runtime_get_sync(ov5693->dev); -+ if (ret < 0) -+ goto err_power_down; -+ -+ ret = __v4l2_ctrl_handler_setup(&ov5693->ctrls.handler); -+ if (ret) -+ goto err_power_down; -+ } -+ -+ mutex_lock(&ov5693->lock); -+ ret = ov5693_sw_standby(ov5693, !enable); -+ mutex_unlock(&ov5693->lock); -+ -+ if (ret) -+ goto err_power_down; -+ ov5693->streaming = !!enable; -+ -+ if (!enable) -+ pm_runtime_put(ov5693->dev); -+ -+ return 0; -+err_power_down: -+ pm_runtime_put_noidle(ov5693->dev); -+ return ret; -+} -+ -+static int ov5693_g_frame_interval(struct v4l2_subdev *sd, -+ struct v4l2_subdev_frame_interval *interval) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ unsigned int framesize = OV5693_FIXED_PPL * (ov5693->mode.format.height + -+ ov5693->ctrls.vblank->val); -+ unsigned int fps = DIV_ROUND_CLOSEST(OV5693_PIXEL_RATE, framesize); -+ -+ interval->interval.numerator = 1; -+ interval->interval.denominator = fps; -+ -+ return 0; -+} -+ -+static int ov5693_enum_mbus_code(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_mbus_code_enum *code) -+{ -+ /* Only a single mbus format is supported */ -+ if (code->index > 0) -+ return -EINVAL; -+ -+ code->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ return 0; -+} -+ -+static int ov5693_enum_frame_size(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_frame_size_enum *fse) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ struct v4l2_rect *__crop; -+ -+ if (fse->index > 1 || fse->code != MEDIA_BUS_FMT_SBGGR10_1X10) -+ return -EINVAL; -+ -+ __crop = __ov5693_get_pad_crop(ov5693, state, fse->pad, fse->which); -+ if (!__crop) -+ return -EINVAL; -+ -+ fse->min_width = __crop->width / (fse->index + 1); -+ fse->min_height = __crop->height / (fse->index + 1); -+ fse->max_width = fse->min_width; -+ fse->max_height = fse->min_height; -+ -+ return 0; -+} -+ -+static const struct v4l2_subdev_video_ops ov5693_video_ops = { -+ .s_stream = ov5693_s_stream, -+ .g_frame_interval = ov5693_g_frame_interval, -+}; -+ -+static const struct v4l2_subdev_pad_ops ov5693_pad_ops = { -+ .enum_mbus_code = ov5693_enum_mbus_code, -+ .enum_frame_size = ov5693_enum_frame_size, -+ .get_fmt = ov5693_get_fmt, -+ .set_fmt = ov5693_set_fmt, -+ .get_selection = ov5693_get_selection, -+ .set_selection = ov5693_set_selection, -+}; -+ -+static const struct v4l2_subdev_ops ov5693_ops = { -+ .video = &ov5693_video_ops, -+ .pad = &ov5693_pad_ops, -+}; -+ -+/* Sensor and Driver Configuration Functions */ -+ -+static int ov5693_init_controls(struct ov5693_device *ov5693) -+{ -+ const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; -+ struct v4l2_fwnode_device_properties props; -+ int vblank_max, vblank_def; -+ int exposure_max; -+ int hblank; -+ int ret; -+ -+ ret = v4l2_ctrl_handler_init(&ov5693->ctrls.handler, 12); -+ if (ret) -+ return ret; -+ -+ /* link freq */ -+ ov5693->ctrls.link_freq = v4l2_ctrl_new_int_menu(&ov5693->ctrls.handler, -+ NULL, V4L2_CID_LINK_FREQ, -+ 0, 0, link_freq_menu_items); -+ if (ov5693->ctrls.link_freq) -+ ov5693->ctrls.link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ -+ /* pixel rate */ -+ ov5693->ctrls.pixel_rate = v4l2_ctrl_new_std(&ov5693->ctrls.handler, NULL, -+ V4L2_CID_PIXEL_RATE, 0, -+ OV5693_PIXEL_RATE, 1, -+ OV5693_PIXEL_RATE); -+ -+ /* Exposure */ -+ exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN; -+ ov5693->ctrls.exposure = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_EXPOSURE, -+ OV5693_EXPOSURE_MIN, -+ exposure_max, -+ OV5693_EXPOSURE_STEP, -+ exposure_max); -+ -+ /* Gain */ -+ ov5693->ctrls.analogue_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, -+ ops, V4L2_CID_ANALOGUE_GAIN, -+ OV5693_GAIN_MIN, -+ OV5693_GAIN_MAX, -+ OV5693_GAIN_STEP, -+ OV5693_GAIN_DEF); -+ -+ ov5693->ctrls.digital_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_DIGITAL_GAIN, -+ OV5693_DIGITAL_GAIN_MIN, -+ OV5693_DIGITAL_GAIN_MAX, -+ OV5693_DIGITAL_GAIN_STEP, -+ OV5693_DIGITAL_GAIN_DEF); -+ -+ /* Flip */ -+ ov5693->ctrls.hflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_HFLIP, 0, 1, 1, 0); -+ -+ ov5693->ctrls.vflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_VFLIP, 0, 1, 1, 0); -+ -+ hblank = OV5693_FIXED_PPL - ov5693->mode.format.width; -+ ov5693->ctrls.hblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_HBLANK, hblank, -+ hblank, 1, hblank); -+ -+ if (ov5693->ctrls.hblank) -+ ov5693->ctrls.hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ -+ vblank_max = OV5693_TIMING_MAX_VTS - ov5693->mode.format.height; -+ vblank_def = ov5693->mode.vts - ov5693->mode.format.height; -+ ov5693->ctrls.vblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_VBLANK, -+ OV5693_TIMING_MIN_VTS, -+ vblank_max, 1, vblank_def); -+ -+ ov5693->ctrls.test_pattern = v4l2_ctrl_new_std_menu_items( -+ &ov5693->ctrls.handler, ops, -+ V4L2_CID_TEST_PATTERN, -+ ARRAY_SIZE(ov5693_test_pattern_menu) - 1, -+ 0, 0, ov5693_test_pattern_menu); -+ -+ if (ov5693->ctrls.handler.error) { -+ dev_err(ov5693->dev, "Error initialising v4l2 ctrls\n"); -+ ret = ov5693->ctrls.handler.error; -+ goto err_free_handler; -+ } -+ -+ /* set properties from fwnode (e.g. rotation, orientation) */ -+ ret = v4l2_fwnode_device_parse(ov5693->dev, &props); -+ if (ret) -+ goto err_free_handler; -+ -+ ret = v4l2_ctrl_new_fwnode_properties(&ov5693->ctrls.handler, ops, -+ &props); -+ if (ret) -+ goto err_free_handler; -+ -+ /* Use same lock for controls as for everything else. */ -+ ov5693->ctrls.handler.lock = &ov5693->lock; -+ ov5693->sd.ctrl_handler = &ov5693->ctrls.handler; -+ -+ return 0; -+ -+err_free_handler: -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ return ret; -+} -+ -+static int ov5693_configure_gpios(struct ov5693_device *ov5693) -+{ -+ ov5693->reset = devm_gpiod_get_optional(ov5693->dev, "reset", -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->reset)) { -+ dev_err(ov5693->dev, "Error fetching reset GPIO\n"); -+ return PTR_ERR(ov5693->reset); -+ } -+ -+ ov5693->powerdown = devm_gpiod_get_optional(ov5693->dev, "powerdown", -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->powerdown)) { -+ dev_err(ov5693->dev, "Error fetching powerdown GPIO\n"); -+ return PTR_ERR(ov5693->powerdown); -+ } -+ -+ return 0; -+} -+ -+static int ov5693_get_regulators(struct ov5693_device *ov5693) -+{ -+ unsigned int i; -+ -+ for (i = 0; i < OV5693_NUM_SUPPLIES; i++) -+ ov5693->supplies[i].supply = ov5693_supply_names[i]; -+ -+ return devm_regulator_bulk_get(ov5693->dev, OV5693_NUM_SUPPLIES, -+ ov5693->supplies); -+} -+ -+static int ov5693_probe(struct i2c_client *client) -+{ -+ struct fwnode_handle *fwnode = dev_fwnode(&client->dev); -+ struct fwnode_handle *endpoint; -+ struct ov5693_device *ov5693; -+ u32 clk_rate; -+ int ret = 0; -+ -+ endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); -+ if (!endpoint && !IS_ERR_OR_NULL(fwnode->secondary)) -+ endpoint = fwnode_graph_get_next_endpoint(fwnode->secondary, NULL); -+ if (!endpoint) -+ return -EPROBE_DEFER; -+ -+ ov5693 = devm_kzalloc(&client->dev, sizeof(*ov5693), GFP_KERNEL); -+ if (!ov5693) -+ return -ENOMEM; -+ -+ ov5693->client = client; -+ ov5693->dev = &client->dev; -+ -+ mutex_init(&ov5693->lock); -+ -+ v4l2_i2c_subdev_init(&ov5693->sd, client, &ov5693_ops); -+ -+ ov5693->clk = devm_clk_get(&client->dev, "xvclk"); -+ if (IS_ERR(ov5693->clk)) { -+ dev_err(&client->dev, "Error getting clock\n"); -+ return PTR_ERR(ov5693->clk); -+ } -+ -+ clk_rate = clk_get_rate(ov5693->clk); -+ if (clk_rate != OV5693_XVCLK_FREQ) { -+ dev_err(&client->dev, "Unsupported clk freq %u, expected %u\n", -+ clk_rate, OV5693_XVCLK_FREQ); -+ return -EINVAL; -+ } -+ -+ ret = ov5693_configure_gpios(ov5693); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_get_regulators(ov5693); -+ if (ret) { -+ dev_err(&client->dev, "Error fetching regulators\n"); -+ return ret; -+ } -+ -+ ov5693->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; -+ ov5693->pad.flags = MEDIA_PAD_FL_SOURCE; -+ ov5693->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; -+ -+ ov5693->mode.crop = ov5693_default_crop; -+ ov5693->mode.format = ov5693_default_fmt; -+ ov5693->mode.vts = __ov5693_calc_vts(ov5693->mode.format.height); -+ -+ ret = ov5693_init_controls(ov5693); -+ if (ret) -+ return ret; -+ -+ ret = media_entity_pads_init(&ov5693->sd.entity, 1, &ov5693->pad); -+ if (ret) -+ goto err_ctrl_handler_free; -+ -+ /* -+ * We need the driver to work in the event that pm runtime is disable in -+ * the kernel, so power up and verify the chip now. In the event that -+ * runtime pm is disabled this will leave the chip on, so that streaming -+ * will work. -+ */ -+ -+ ret = ov5693_sensor_powerup(ov5693); -+ if (ret) -+ goto err_media_entity_cleanup; -+ -+ ret = ov5693_detect(ov5693); -+ if (ret) -+ goto err_powerdown; -+ -+ pm_runtime_set_active(&client->dev); -+ pm_runtime_get_noresume(&client->dev); -+ pm_runtime_enable(&client->dev); -+ -+ ret = v4l2_async_register_subdev_sensor(&ov5693->sd); -+ if (ret) { -+ dev_err(&client->dev, "failed to register V4L2 subdev: %d", -+ ret); -+ goto err_pm_runtime; -+ } -+ -+ pm_runtime_set_autosuspend_delay(&client->dev, 1000); -+ pm_runtime_use_autosuspend(&client->dev); -+ pm_runtime_put_autosuspend(&client->dev); -+ -+ return ret; -+ -+err_pm_runtime: -+ pm_runtime_disable(&client->dev); -+ pm_runtime_put_noidle(&client->dev); -+err_powerdown: -+ ov5693_sensor_powerdown(ov5693); -+err_media_entity_cleanup: -+ media_entity_cleanup(&ov5693->sd.entity); -+err_ctrl_handler_free: -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ -+ return ret; -+} -+ -+static int ov5693_remove(struct i2c_client *client) -+{ -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ v4l2_async_unregister_subdev(sd); -+ media_entity_cleanup(&ov5693->sd.entity); -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ mutex_destroy(&ov5693->lock); -+ -+ /* -+ * Disable runtime PM. In case runtime PM is disabled in the kernel, -+ * make sure to turn power off manually. -+ */ -+ pm_runtime_disable(&client->dev); -+ if (!pm_runtime_status_suspended(&client->dev)) -+ ov5693_sensor_powerdown(ov5693); -+ pm_runtime_set_suspended(&client->dev); -+ -+ return 0; -+} -+ -+static const struct dev_pm_ops ov5693_pm_ops = { -+ SET_RUNTIME_PM_OPS(ov5693_sensor_suspend, ov5693_sensor_resume, NULL) -+}; -+ -+static const struct acpi_device_id ov5693_acpi_match[] = { -+ {"INT33BE"}, -+ {}, -+}; -+MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); -+ -+static struct i2c_driver ov5693_driver = { -+ .driver = { -+ .name = "ov5693", -+ .acpi_match_table = ov5693_acpi_match, -+ .pm = &ov5693_pm_ops, -+ }, -+ .probe_new = ov5693_probe, -+ .remove = ov5693_remove, -+}; -+module_i2c_driver(ov5693_driver); -+ -+MODULE_DESCRIPTION("A low-level driver for OmniVision 5693 sensors"); -+MODULE_LICENSE("GPL"); --- -2.34.0 - -From 7e9c49131077a564296f86e3d3e1813448b0fdae Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Fri, 22 Jan 2021 20:58:13 +0100 -Subject: [PATCH] cio2-bridge: Parse sensor orientation and rotation -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -The sensor orientation is read from the _PLC ACPI buffer and converted -to a v4l2 format. - -See https://uefi.org/sites/default/files/resources/ACPI_6_3_final_Jan30.pdf -page 351 for a definition of the Panel property. - -The sensor rotation is read from the SSDB ACPI buffer and converted into -degrees. - -Signed-off-by: Fabian Wüthrich -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 45 ++++++++++++++++++++-- - drivers/media/pci/intel/ipu3/cio2-bridge.h | 3 ++ - 2 files changed, 44 insertions(+), 4 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index 30d29b96a339..77c97bf6521e 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -29,6 +29,7 @@ static const struct cio2_sensor_config cio2_supported_sensors[] = { - static const struct cio2_property_names prop_names = { - .clock_frequency = "clock-frequency", - .rotation = "rotation", -+ .orientation = "orientation", - .bus_type = "bus-type", - .data_lanes = "data-lanes", - .remote_endpoint = "remote-endpoint", -@@ -72,11 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, - return ret; - } - -+static u32 cio2_bridge_parse_rotation(u8 rotation) -+{ -+ if (rotation == 1) -+ return 180; -+ return 0; -+} -+ -+static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) -+{ -+ switch (panel) { -+ case 4: -+ return V4L2_FWNODE_ORIENTATION_FRONT; -+ case 5: -+ return V4L2_FWNODE_ORIENTATION_BACK; -+ default: -+ return V4L2_FWNODE_ORIENTATION_EXTERNAL; -+ } -+} -+ - static void cio2_bridge_create_fwnode_properties( - struct cio2_sensor *sensor, - struct cio2_bridge *bridge, - const struct cio2_sensor_config *cfg) - { -+ u32 rotation; -+ enum v4l2_fwnode_orientation orientation; -+ -+ rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); -+ orientation = cio2_bridge_parse_orientation(sensor->pld->panel); -+ - sensor->prop_names = prop_names; - - sensor->local_ref[0] = SOFTWARE_NODE_REFERENCE(&sensor->swnodes[SWNODE_CIO2_ENDPOINT]); -@@ -85,9 +111,12 @@ static void cio2_bridge_create_fwnode_properties( - sensor->dev_properties[0] = PROPERTY_ENTRY_U32( - sensor->prop_names.clock_frequency, - sensor->ssdb.mclkspeed); -- sensor->dev_properties[1] = PROPERTY_ENTRY_U8( -+ sensor->dev_properties[1] = PROPERTY_ENTRY_U32( - sensor->prop_names.rotation, -- sensor->ssdb.degree); -+ rotation); -+ sensor->dev_properties[2] = PROPERTY_ENTRY_U32( -+ sensor->prop_names.orientation, -+ orientation); - - sensor->ep_properties[0] = PROPERTY_ENTRY_U32( - sensor->prop_names.bus_type, -@@ -159,6 +188,7 @@ static void cio2_bridge_unregister_sensors(struct cio2_bridge *bridge) - for (i = 0; i < bridge->n_sensors; i++) { - sensor = &bridge->sensors[i]; - software_node_unregister_nodes(sensor->swnodes); -+ ACPI_FREE(sensor->pld); - acpi_dev_put(sensor->adev); - } - } -@@ -170,6 +200,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - struct fwnode_handle *fwnode; - struct cio2_sensor *sensor; - struct acpi_device *adev; -+ acpi_status status; - int ret; - - for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { -@@ -191,11 +222,15 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - if (ret) - goto err_put_adev; - -+ status = acpi_get_physical_device_location(adev->handle, &sensor->pld); -+ if (ACPI_FAILURE(status)) -+ goto err_put_adev; -+ - if (sensor->ssdb.lanes > CIO2_MAX_LANES) { - dev_err(&adev->dev, - "Number of lanes in SSDB is invalid\n"); - ret = -EINVAL; -- goto err_put_adev; -+ goto err_free_pld; - } - - cio2_bridge_create_fwnode_properties(sensor, bridge, cfg); -@@ -203,7 +238,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - - ret = software_node_register_nodes(sensor->swnodes); - if (ret) -- goto err_put_adev; -+ goto err_free_pld; - - fwnode = software_node_fwnode(&sensor->swnodes[ - SWNODE_SENSOR_HID]); -@@ -225,6 +260,8 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - - err_free_swnodes: - software_node_unregister_nodes(sensor->swnodes); -+err_free_pld: -+ ACPI_FREE(sensor->pld); - err_put_adev: - acpi_dev_put(adev); - return ret; -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index dd0ffcafa489..924d99d20328 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -80,6 +80,7 @@ struct cio2_sensor_ssdb { - struct cio2_property_names { - char clock_frequency[16]; - char rotation[9]; -+ char orientation[12]; - char bus_type[9]; - char data_lanes[11]; - char remote_endpoint[16]; -@@ -106,6 +107,8 @@ struct cio2_sensor { - struct cio2_node_names node_names; - - struct cio2_sensor_ssdb ssdb; -+ struct acpi_pld_info *pld; -+ - struct cio2_property_names prop_names; - struct property_entry ep_properties[5]; - struct property_entry dev_properties[3]; --- -2.34.0 - -From e163137f9b923398a4d31e3a21fe78486974d0d9 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Sun, 24 Jan 2021 11:07:42 +0100 -Subject: [PATCH] cio2-bridge: Use macros and add warnings -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Use macros for the _PLD panel as defined in the ACPI spec 6.3 and emit -a warning if we see an unknown value. - -Signed-off-by: Fabian Wüthrich -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 33 ++++++++++++++++------ - drivers/media/pci/intel/ipu3/cio2-bridge.h | 13 +++++++++ - 2 files changed, 37 insertions(+), 9 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index 77c97bf6521e..7e582135dfb8 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -73,21 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, - return ret; - } - --static u32 cio2_bridge_parse_rotation(u8 rotation) -+static u32 cio2_bridge_parse_rotation(struct cio2_sensor *sensor) - { -- if (rotation == 1) -+ switch (sensor->ssdb.degree) { -+ case CIO2_SENSOR_ROTATION_NORMAL: -+ return 0; -+ case CIO2_SENSOR_ROTATION_INVERTED: - return 180; -- return 0; -+ default: -+ dev_warn(&sensor->adev->dev, -+ "Unknown rotation %d. Assume 0 degree rotation\n", -+ sensor->ssdb.degree); -+ return 0; -+ } - } - --static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) -+static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(struct cio2_sensor *sensor) - { -- switch (panel) { -- case 4: -+ switch (sensor->pld->panel) { -+ case CIO2_PLD_PANEL_FRONT: - return V4L2_FWNODE_ORIENTATION_FRONT; -- case 5: -+ case CIO2_PLD_PANEL_BACK: - return V4L2_FWNODE_ORIENTATION_BACK; -+ case CIO2_PLD_PANEL_TOP: -+ case CIO2_PLD_PANEL_LEFT: -+ case CIO2_PLD_PANEL_RIGHT: -+ case CIO2_PLD_PANEL_UNKNOWN: -+ return V4L2_FWNODE_ORIENTATION_EXTERNAL; - default: -+ dev_warn(&sensor->adev->dev, "Unknown _PLD panel value %d\n", -+ sensor->pld->panel); - return V4L2_FWNODE_ORIENTATION_EXTERNAL; - } - } -@@ -100,8 +115,8 @@ static void cio2_bridge_create_fwnode_properties( - u32 rotation; - enum v4l2_fwnode_orientation orientation; - -- rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); -- orientation = cio2_bridge_parse_orientation(sensor->pld->panel); -+ rotation = cio2_bridge_parse_rotation(sensor); -+ orientation = cio2_bridge_parse_orientation(sensor); - - sensor->prop_names = prop_names; - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index 924d99d20328..e1e388cc9f45 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -12,6 +12,19 @@ - #define CIO2_MAX_LANES 4 - #define MAX_NUM_LINK_FREQS 3 - -+/* Values are estimated guesses as we don't have a spec */ -+#define CIO2_SENSOR_ROTATION_NORMAL 0 -+#define CIO2_SENSOR_ROTATION_INVERTED 1 -+ -+/* Panel position defined in _PLD section of ACPI Specification 6.3 */ -+#define CIO2_PLD_PANEL_TOP 0 -+#define CIO2_PLD_PANEL_BOTTOM 1 -+#define CIO2_PLD_PANEL_LEFT 2 -+#define CIO2_PLD_PANEL_RIGHT 3 -+#define CIO2_PLD_PANEL_FRONT 4 -+#define CIO2_PLD_PANEL_BACK 5 -+#define CIO2_PLD_PANEL_UNKNOWN 6 -+ - #define CIO2_SENSOR_CONFIG(_HID, _NR, ...) \ - (const struct cio2_sensor_config) { \ - .hid = _HID, \ --- -2.34.0 - -From d6e2797aba5ac844c067d177000d8a7966481adc Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Thu, 6 May 2021 07:52:44 +0200 -Subject: [PATCH] cio2-bridge: Use correct dev_properties size - -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index e1e388cc9f45..deaf5804f70d 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -124,7 +124,7 @@ struct cio2_sensor { - - struct cio2_property_names prop_names; - struct property_entry ep_properties[5]; -- struct property_entry dev_properties[3]; -+ struct property_entry dev_properties[4]; - struct property_entry cio2_properties[3]; - struct software_node_ref_args local_ref[1]; - struct software_node_ref_args remote_ref[1]; --- -2.34.0 - -From d12db7dfc3e8a96d138c575811131acd50f227da Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 20 May 2021 23:31:04 +0100 -Subject: [PATCH] media: i2c: Fix vertical flip in ov5693 - -The pinkness experienced by users with rotated sensors in their laptops -was due to an incorrect setting for the vertical flip function; the -datasheet for the sensor gives the settings as bits 1&2 in one place and -bits 1&6 in another. - -Switch to flipping bit 6 instead of bit 2 for 0x3820 in the vertical -flip function to fix the pink hue. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 9499ee10f56c..c558f9b48c83 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -133,7 +133,7 @@ - #define OV5693_SUB_INC_Y_REG 0x3815 - - #define OV5693_FORMAT1_REG 0x3820 --#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -+#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(6) - #define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) - #define OV5693_FORMAT1_VBIN_EN BIT(0) - #define OV5693_FORMAT2_REG 0x3821 --- -2.34.0 - -From daec57877a80e0d8f6657619fffada365a5b6371 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 9 Jul 2021 16:39:18 +0100 -Subject: [PATCH] media: i2c: Add ACPI support to ov8865 - -The ov8865 sensor is sometimes found on x86 platforms enumerated via ACPI. -Add an ACPI match table to the driver so that it's probed on those -platforms. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index ce50f3ea87b8..7626c8608f8f 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -9,6 +9,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -2946,6 +2947,12 @@ static const struct dev_pm_ops ov8865_pm_ops = { - SET_RUNTIME_PM_OPS(ov8865_suspend, ov8865_resume, NULL) - }; - -+static const struct acpi_device_id ov8865_acpi_match[] = { -+ {"INT347A"}, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, ov8865_acpi_match); -+ - static const struct of_device_id ov8865_of_match[] = { - { .compatible = "ovti,ov8865" }, - { } -@@ -2956,6 +2963,7 @@ static struct i2c_driver ov8865_driver = { - .driver = { - .name = "ov8865", - .of_match_table = ov8865_of_match, -+ .acpi_match_table = ov8865_acpi_match, - .pm = &ov8865_pm_ops, - }, - .probe_new = ov8865_probe, --- -2.34.0 - -From 9213ddc74a32af22cb5de8af6e0a249fa9ed8351 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 21:20:17 +0100 -Subject: [PATCH] media: i2c: Fix incorrect value in comment - -The PLL configuration defined here sets 72MHz (which is correct), not -80MHz. Correct the comment. - -Reviewed-by: Paul Kocialkowski -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 7626c8608f8f..8e3f8a554452 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -713,7 +713,7 @@ static const struct ov8865_pll2_config ov8865_pll2_config_native = { - /* - * EXTCLK = 24 MHz - * DAC_CLK = 360 MHz -- * SCLK = 80 MHz -+ * SCLK = 72 MHz - */ - - static const struct ov8865_pll2_config ov8865_pll2_config_binning = { --- -2.34.0 - -From 5c3e54c1c791a2b7781bf37572d8e8c2f602dffa Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:21:52 +0100 -Subject: [PATCH] media: i2c: Defer probe if not endpoint found - -The ov8865 driver is one of those that can be connected to a CIO2 -device by the cio2-bridge code. This means that the absence of an -endpoint for this device is not necessarily fatal, as one might be -built by the cio2-bridge when it probes. Return -EPROBE_DEFER if no -endpoint is found rather than a fatal error. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 6 ++---- - 1 file changed, 2 insertions(+), 4 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 8e3f8a554452..9bc8d5d8199b 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2796,10 +2796,8 @@ static int ov8865_probe(struct i2c_client *client) - /* Graph Endpoint */ - - handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); -- if (!handle) { -- dev_err(dev, "unable to find endpoint node\n"); -- return -EINVAL; -- } -+ if (!handle) -+ return -EPROBE_DEFER; - - sensor->endpoint.bus_type = V4L2_MBUS_CSI2_DPHY; - --- -2.34.0 - -From d7e4c156c95f8a52d1290b245cc9b7144c2e750d Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:00:25 +0100 -Subject: [PATCH] media: i2c: Support 19.2MHz input clock in ov8865 - -The ov8865 driver as written expects a 24MHz input clock, but the sensor -is sometimes found on x86 platforms with a 19.2MHz input clock supplied. -Add a set of PLL configurations to the driver to support that rate too. -As ACPI doesn't auto-configure the clock rate, check for a clock-frequency -during probe and set that rate if one is found. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 186 +++++++++++++++++++++++++++---------- - 1 file changed, 135 insertions(+), 51 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 9bc8d5d8199b..4ddc1b277cc0 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -21,10 +21,6 @@ - #include - #include - --/* Clock rate */ -- --#define OV8865_EXTCLK_RATE 24000000 -- - /* Register definitions */ - - /* System */ -@@ -567,6 +563,25 @@ struct ov8865_sclk_config { - unsigned int sclk_div; - }; - -+struct ov8865_pll_configs { -+ const struct ov8865_pll1_config *pll1_config; -+ const struct ov8865_pll2_config *pll2_config_native; -+ const struct ov8865_pll2_config *pll2_config_binning; -+}; -+ -+/* Clock rate */ -+ -+enum extclk_rate { -+ OV8865_19_2_MHZ, -+ OV8865_24_MHZ, -+ OV8865_NUM_SUPPORTED_RATES -+}; -+ -+static const unsigned long supported_extclk_rates[] = { -+ [OV8865_19_2_MHZ] = 19200000, -+ [OV8865_24_MHZ] = 24000000, -+}; -+ - /* - * General formulas for (array-centered) mode calculation: - * - photo_array_width = 3296 -@@ -635,9 +650,7 @@ struct ov8865_mode { - - struct v4l2_fract frame_interval; - -- const struct ov8865_pll1_config *pll1_config; -- const struct ov8865_pll2_config *pll2_config; -- const struct ov8865_sclk_config *sclk_config; -+ bool pll2_binning; - - const struct ov8865_register_value *register_values; - unsigned int register_values_count; -@@ -665,6 +678,9 @@ struct ov8865_sensor { - struct regulator *avdd; - struct regulator *dvdd; - struct regulator *dovdd; -+ -+ unsigned long extclk_rate; -+ const struct ov8865_pll_configs *pll_configs; - struct clk *extclk; - - struct v4l2_fwnode_endpoint endpoint; -@@ -680,43 +696,70 @@ struct ov8865_sensor { - /* Static definitions */ - - /* -- * EXTCLK = 24 MHz - * PHY_SCLK = 720 MHz - * MIPI_PCLK = 90 MHz - */ --static const struct ov8865_pll1_config ov8865_pll1_config_native = { -- .pll_pre_div_half = 1, -- .pll_pre_div = 0, -- .pll_mul = 30, -- .m_div = 1, -- .mipi_div = 3, -- .pclk_div = 1, -- .sys_pre_div = 1, -- .sys_div = 2, -+ -+static const struct ov8865_pll1_config ov8865_pll1_config_native_19_2mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 2, -+ .pll_mul = 75, -+ .m_div = 1, -+ .mipi_div = 3, -+ .pclk_div = 1, -+ .sys_pre_div = 1, -+ .sys_div = 2, -+}; -+ -+static const struct ov8865_pll1_config ov8865_pll1_config_native_24mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 0, -+ .pll_mul = 30, -+ .m_div = 1, -+ .mipi_div = 3, -+ .pclk_div = 1, -+ .sys_pre_div = 1, -+ .sys_div = 2, - }; - - /* -- * EXTCLK = 24 MHz - * DAC_CLK = 360 MHz - * SCLK = 144 MHz - */ - --static const struct ov8865_pll2_config ov8865_pll2_config_native = { -- .pll_pre_div_half = 1, -- .pll_pre_div = 0, -- .pll_mul = 30, -- .dac_div = 2, -- .sys_pre_div = 5, -- .sys_div = 0, -+static const struct ov8865_pll2_config ov8865_pll2_config_native_19_2mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 5, -+ .pll_mul = 75, -+ .dac_div = 1, -+ .sys_pre_div = 1, -+ .sys_div = 3, -+}; -+ -+static const struct ov8865_pll2_config ov8865_pll2_config_native_24mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 0, -+ .pll_mul = 30, -+ .dac_div = 2, -+ .sys_pre_div = 5, -+ .sys_div = 0, - }; - - /* -- * EXTCLK = 24 MHz - * DAC_CLK = 360 MHz - * SCLK = 72 MHz - */ - --static const struct ov8865_pll2_config ov8865_pll2_config_binning = { -+static const struct ov8865_pll2_config ov8865_pll2_config_binning_19_2mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 2, -+ .pll_mul = 75, -+ .dac_div = 2, -+ .sys_pre_div = 10, -+ .sys_div = 0, -+}; -+ -+static const struct ov8865_pll2_config ov8865_pll2_config_binning_24mhz = { - .pll_pre_div_half = 1, - .pll_pre_div = 0, - .pll_mul = 30, -@@ -725,6 +768,23 @@ static const struct ov8865_pll2_config ov8865_pll2_config_binning = { - .sys_div = 0, - }; - -+static struct ov8865_pll_configs ov8865_pll_configs_19_2mhz = { -+ .pll1_config = &ov8865_pll1_config_native_19_2mhz, -+ .pll2_config_native = &ov8865_pll2_config_native_19_2mhz, -+ .pll2_config_binning = &ov8865_pll2_config_binning_19_2mhz, -+}; -+ -+static struct ov8865_pll_configs ov8865_pll_configs_24mhz = { -+ .pll1_config = &ov8865_pll1_config_native_24mhz, -+ .pll2_config_native = &ov8865_pll2_config_native_24mhz, -+ .pll2_config_binning = &ov8865_pll2_config_binning_24mhz, -+}; -+ -+static const struct ov8865_pll_configs *ov8865_pll_configs[] = { -+ &ov8865_pll_configs_19_2mhz, -+ &ov8865_pll_configs_24mhz, -+}; -+ - static const struct ov8865_sclk_config ov8865_sclk_config_native = { - .sys_sel = 1, - .sclk_sel = 0, -@@ -934,9 +994,7 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 30 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_native, -- .sclk_config = &ov8865_sclk_config_native, -+ .pll2_binning = false, - - /* Registers */ - .register_values = ov8865_register_values_native, -@@ -990,9 +1048,7 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 30 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_native, -- .sclk_config = &ov8865_sclk_config_native, -+ .pll2_binning = false, - - /* Registers */ - .register_values = ov8865_register_values_native, -@@ -1050,9 +1106,7 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 30 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_binning, -- .sclk_config = &ov8865_sclk_config_native, -+ .pll2_binning = true, - - /* Registers */ - .register_values = ov8865_register_values_binning, -@@ -1116,9 +1170,7 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 90 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_binning, -- .sclk_config = &ov8865_sclk_config_native, -+ .pll2_binning = true, - - /* Registers */ - .register_values = ov8865_register_values_binning, -@@ -1513,12 +1565,11 @@ static int ov8865_isp_configure(struct ov8865_sensor *sensor) - static unsigned long ov8865_mode_pll1_rate(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_pll1_config *config = mode->pll1_config; -- unsigned long extclk_rate; -+ const struct ov8865_pll1_config *config; - unsigned long pll1_rate; - -- extclk_rate = clk_get_rate(sensor->extclk); -- pll1_rate = extclk_rate * config->pll_mul / config->pll_pre_div_half; -+ config = sensor->pll_configs->pll1_config; -+ pll1_rate = sensor->extclk_rate * config->pll_mul / config->pll_pre_div_half; - - switch (config->pll_pre_div) { - case 0: -@@ -1552,10 +1603,12 @@ static int ov8865_mode_pll1_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode, - u32 mbus_code) - { -- const struct ov8865_pll1_config *config = mode->pll1_config; -+ const struct ov8865_pll1_config *config; - u8 value; - int ret; - -+ config = sensor->pll_configs->pll1_config; -+ - switch (mbus_code) { - case MEDIA_BUS_FMT_SBGGR10_1X10: - value = OV8865_MIPI_BIT_SEL(10); -@@ -1622,9 +1675,12 @@ static int ov8865_mode_pll1_configure(struct ov8865_sensor *sensor, - static int ov8865_mode_pll2_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_pll2_config *config = mode->pll2_config; -+ const struct ov8865_pll2_config *config; - int ret; - -+ config = mode->pll2_binning ? sensor->pll_configs->pll2_config_binning : -+ sensor->pll_configs->pll2_config_native; -+ - ret = ov8865_write(sensor, OV8865_PLL_CTRL12_REG, - OV8865_PLL_CTRL12_PRE_DIV_HALF(config->pll_pre_div_half) | - OV8865_PLL_CTRL12_DAC_DIV(config->dac_div)); -@@ -1658,7 +1714,7 @@ static int ov8865_mode_pll2_configure(struct ov8865_sensor *sensor, - static int ov8865_mode_sclk_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_sclk_config *config = mode->sclk_config; -+ const struct ov8865_sclk_config *config = &ov8865_sclk_config_native; - int ret; - - ret = ov8865_write(sensor, OV8865_CLK_SEL0_REG, -@@ -2053,9 +2109,11 @@ static int ov8865_mode_configure(struct ov8865_sensor *sensor, - static unsigned long ov8865_mode_mipi_clk_rate(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_pll1_config *config = mode->pll1_config; -+ const struct ov8865_pll1_config *config; - unsigned long pll1_rate; - -+ config = sensor->pll_configs->pll1_config; -+ - pll1_rate = ov8865_mode_pll1_rate(sensor, mode); - - return pll1_rate / config->m_div / 2; -@@ -2783,7 +2841,8 @@ static int ov8865_probe(struct i2c_client *client) - struct ov8865_sensor *sensor; - struct v4l2_subdev *subdev; - struct media_pad *pad; -- unsigned long rate; -+ unsigned int rate; -+ unsigned int i; - int ret; - - sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); -@@ -2858,13 +2917,38 @@ static int ov8865_probe(struct i2c_client *client) - goto error_endpoint; - } - -- rate = clk_get_rate(sensor->extclk); -- if (rate != OV8865_EXTCLK_RATE) { -- dev_err(dev, "clock rate %lu Hz is unsupported\n", rate); -+ /* -+ * We could have either a 24MHz or 19.2MHz clock rate. Check for a -+ * clock-frequency property and if found, set that rate. This should -+ * cover the ACPI case. If the system uses devicetree then the -+ * configured rate should already be set, so we'll have to check it. -+ */ -+ ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", -+ &rate); -+ if (!ret) { -+ ret = clk_set_rate(sensor->extclk, rate); -+ if (ret) { -+ dev_err(dev, "failed to set clock rate\n"); -+ return ret; -+ } -+ } -+ -+ sensor->extclk_rate = clk_get_rate(sensor->extclk); -+ -+ for (i = 0; i < ARRAY_SIZE(supported_extclk_rates); i++) { -+ if (sensor->extclk_rate == supported_extclk_rates[i]) -+ break; -+ } -+ -+ if (i == ARRAY_SIZE(supported_extclk_rates)) { -+ dev_err(dev, "clock rate %lu Hz is unsupported\n", -+ sensor->extclk_rate); - ret = -EINVAL; - goto error_endpoint; - } - -+ sensor->pll_configs = ov8865_pll_configs[i]; -+ - /* Subdev, entity and pad */ - - subdev = &sensor->subdev; --- -2.34.0 - -From 82e605cd4e01ec30e300e0c03a5c805ce311ef86 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:19:10 +0100 -Subject: [PATCH] media: i2c: Add .get_selection() support to ov8865 - -The ov8865 driver's v4l2_subdev_pad_ops currently does not include -.get_selection() - add support for that callback. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 64 ++++++++++++++++++++++++++++++++++++++ - 1 file changed, 64 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 4ddc1b277cc0..0f2776390a8e 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -450,6 +450,15 @@ - #define OV8865_PRE_CTRL0_PATTERN_COLOR_SQUARES 2 - #define OV8865_PRE_CTRL0_PATTERN_BLACK 3 - -+/* Pixel Array */ -+ -+#define OV8865_NATIVE_WIDTH 3296 -+#define OV8865_NATIVE_HEIGHT 2528 -+#define OV8865_ACTIVE_START_TOP 32 -+#define OV8865_ACTIVE_START_LEFT 80 -+#define OV8865_ACTIVE_WIDTH 3264 -+#define OV8865_ACTIVE_HEIGHT 2448 -+ - /* Macros */ - - #define ov8865_subdev_sensor(s) \ -@@ -2756,12 +2765,67 @@ static int ov8865_enum_frame_interval(struct v4l2_subdev *subdev, - return 0; - } - -+static void -+__ov8865_get_pad_crop(struct ov8865_sensor *sensor, -+ struct v4l2_subdev_state *state, unsigned int pad, -+ enum v4l2_subdev_format_whence which, struct v4l2_rect *r) -+{ -+ const struct ov8865_mode *mode = sensor->state.mode; -+ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ *r = *v4l2_subdev_get_try_crop(&sensor->subdev, state, pad); -+ break; -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ r->height = mode->output_size_y; -+ r->width = mode->output_size_x; -+ r->top = (OV8865_NATIVE_HEIGHT - mode->output_size_y) / 2; -+ r->left = (OV8865_NATIVE_WIDTH - mode->output_size_x) / 2; -+ break; -+ } -+} -+ -+static int ov8865_get_selection(struct v4l2_subdev *subdev, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); -+ -+ switch (sel->target) { -+ case V4L2_SEL_TGT_CROP: -+ mutex_lock(&sensor->mutex); -+ __ov8865_get_pad_crop(sensor, state, sel->pad, -+ sel->which, &sel->r); -+ mutex_unlock(&sensor->mutex); -+ break; -+ case V4L2_SEL_TGT_NATIVE_SIZE: -+ sel->r.top = 0; -+ sel->r.left = 0; -+ sel->r.width = OV8865_NATIVE_WIDTH; -+ sel->r.height = OV8865_NATIVE_HEIGHT; -+ break; -+ case V4L2_SEL_TGT_CROP_BOUNDS: -+ case V4L2_SEL_TGT_CROP_DEFAULT: -+ sel->r.top = OV8865_ACTIVE_START_TOP; -+ sel->r.left = OV8865_ACTIVE_START_LEFT; -+ sel->r.width = OV8865_ACTIVE_WIDTH; -+ sel->r.height = OV8865_ACTIVE_HEIGHT; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ - static const struct v4l2_subdev_pad_ops ov8865_subdev_pad_ops = { - .enum_mbus_code = ov8865_enum_mbus_code, - .get_fmt = ov8865_get_fmt, - .set_fmt = ov8865_set_fmt, - .enum_frame_size = ov8865_enum_frame_size, - .enum_frame_interval = ov8865_enum_frame_interval, -+ .get_selection = ov8865_get_selection, -+ .set_selection = ov8865_get_selection, - }; - - static const struct v4l2_subdev_ops ov8865_subdev_ops = { --- -2.34.0 - -From 66365119072809fd0a13addb7149ec8637b564a1 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:34:43 +0100 -Subject: [PATCH] media: i2c: Switch control to V4L2_CID_ANALOGUE_GAIN - -The V4L2_CID_GAIN control for this driver configures registers that -the datasheet specifies as analogue gain. Switch the control's ID -to V4L2_CID_ANALOGUE_GAIN. - -Reviewed-by: Paul Kocialkowski -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 9 +++++---- - 1 file changed, 5 insertions(+), 4 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 0f2776390a8e..a832938c33b6 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2150,7 +2150,7 @@ static int ov8865_exposure_configure(struct ov8865_sensor *sensor, u32 exposure) - - /* Gain */ - --static int ov8865_gain_configure(struct ov8865_sensor *sensor, u32 gain) -+static int ov8865_analog_gain_configure(struct ov8865_sensor *sensor, u32 gain) - { - int ret; - -@@ -2460,8 +2460,8 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - if (ret) - return ret; - break; -- case V4L2_CID_GAIN: -- ret = ov8865_gain_configure(sensor, ctrl->val); -+ case V4L2_CID_ANALOGUE_GAIN: -+ ret = ov8865_analog_gain_configure(sensor, ctrl->val); - if (ret) - return ret; - break; -@@ -2506,7 +2506,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Gain */ - -- v4l2_ctrl_new_std(handler, ops, V4L2_CID_GAIN, 128, 8191, 128, 128); -+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_ANALOGUE_GAIN, 128, 8191, 128, -+ 128); - - /* White Balance */ - --- -2.34.0 - -From c49266bfb501eeb85eba0420be36e42ffcbd7063 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 12 Jul 2021 22:54:56 +0100 -Subject: [PATCH] media: i2c: Add vblank control to ov8865 - -Add a V4L2_CID_VBLANK control to the ov8865 driver. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 34 ++++++++++++++++++++++++++++++++++ - 1 file changed, 34 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index a832938c33b6..f741c0713ca4 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -183,6 +183,8 @@ - #define OV8865_VTS_H(v) (((v) & GENMASK(11, 8)) >> 8) - #define OV8865_VTS_L_REG 0x380f - #define OV8865_VTS_L(v) ((v) & GENMASK(7, 0)) -+#define OV8865_TIMING_MAX_VTS 0xffff -+#define OV8865_TIMING_MIN_VTS 0x04 - #define OV8865_OFFSET_X_H_REG 0x3810 - #define OV8865_OFFSET_X_H(v) (((v) & GENMASK(15, 8)) >> 8) - #define OV8865_OFFSET_X_L_REG 0x3811 -@@ -675,6 +677,7 @@ struct ov8865_state { - struct ov8865_ctrls { - struct v4l2_ctrl *link_freq; - struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *vblank; - - struct v4l2_ctrl_handler handler; - }; -@@ -2225,6 +2228,20 @@ static int ov8865_test_pattern_configure(struct ov8865_sensor *sensor, - ov8865_test_pattern_bits[index]); - } - -+/* Blanking */ -+ -+static int ov8865_vts_configure(struct ov8865_sensor *sensor, u32 vblank) -+{ -+ u16 vts = sensor->state.mode->output_size_y + vblank; -+ int ret; -+ -+ ret = ov8865_write(sensor, OV8865_VTS_H_REG, OV8865_VTS_H(vts)); -+ if (ret) -+ return ret; -+ -+ return ov8865_write(sensor, OV8865_VTS_L_REG, OV8865_VTS_L(vts)); -+} -+ - /* State */ - - static int ov8865_state_mipi_configure(struct ov8865_sensor *sensor, -@@ -2476,6 +2493,8 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - case V4L2_CID_TEST_PATTERN: - index = (unsigned int)ctrl->val; - return ov8865_test_pattern_configure(sensor, index); -+ case V4L2_CID_VBLANK: -+ return ov8865_vts_configure(sensor, ctrl->val); - default: - return -EINVAL; - } -@@ -2492,6 +2511,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - struct ov8865_ctrls *ctrls = &sensor->ctrls; - struct v4l2_ctrl_handler *handler = &ctrls->handler; - const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; -+ const struct ov8865_mode *mode = sensor->state.mode; -+ unsigned int vblank_max, vblank_def; - int ret; - - v4l2_ctrl_handler_init(handler, 32); -@@ -2528,6 +2549,13 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - ARRAY_SIZE(ov8865_test_pattern_menu) - 1, - 0, 0, ov8865_test_pattern_menu); - -+ /* Blanking */ -+ vblank_max = OV8865_TIMING_MAX_VTS - mode->output_size_y; -+ vblank_def = mode->vts - mode->output_size_y; -+ ctrls->vblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_VBLANK, -+ OV8865_TIMING_MIN_VTS, vblank_max, 1, -+ vblank_def); -+ - /* MIPI CSI-2 */ - - ctrls->link_freq = -@@ -2708,6 +2736,10 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - sensor->state.mbus_code != mbus_code) - ret = ov8865_state_configure(sensor, mode, mbus_code); - -+ __v4l2_ctrl_modify_range(sensor->ctrls.vblank, OV8865_TIMING_MIN_VTS, -+ OV8865_TIMING_MAX_VTS - mode->output_size_y, -+ 1, mode->vts - mode->output_size_y); -+ - complete: - mutex_unlock(&sensor->mutex); - -@@ -3035,6 +3067,8 @@ static int ov8865_probe(struct i2c_client *client) - - /* Sensor */ - -+ sensor->state.mode = &ov8865_modes[0]; -+ - ret = ov8865_ctrls_init(sensor); - if (ret) - goto error_mutex; --- -2.34.0 - -From 808fd37ee10d2cca8e039c3ae88cb8a9191b3eed Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 13 Jul 2021 23:40:33 +0100 -Subject: [PATCH] media: i2c: Add hblank control to ov8865 - -Add a V4L2_CID_HBLANK control to the ov8865 driver. This is read only -with timing control intended to be done via vblanking alone. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 14 ++++++++++++++ - 1 file changed, 14 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index f741c0713ca4..4b18cc80f985 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -677,6 +677,7 @@ struct ov8865_state { - struct ov8865_ctrls { - struct v4l2_ctrl *link_freq; - struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *hblank; - struct v4l2_ctrl *vblank; - - struct v4l2_ctrl_handler handler; -@@ -2513,6 +2514,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; - const struct ov8865_mode *mode = sensor->state.mode; - unsigned int vblank_max, vblank_def; -+ unsigned int hblank; - int ret; - - v4l2_ctrl_handler_init(handler, 32); -@@ -2550,6 +2552,13 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - 0, 0, ov8865_test_pattern_menu); - - /* Blanking */ -+ hblank = mode->hts - mode->output_size_x; -+ ctrls->hblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_HBLANK, hblank, -+ hblank, 1, hblank); -+ -+ if (ctrls->hblank) -+ ctrls->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ - vblank_max = OV8865_TIMING_MAX_VTS - mode->output_size_y; - vblank_def = mode->vts - mode->output_size_y; - ctrls->vblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_VBLANK, -@@ -2696,6 +2705,7 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - struct v4l2_mbus_framefmt *mbus_format = &format->format; - const struct ov8865_mode *mode; - u32 mbus_code = 0; -+ unsigned int hblank; - unsigned int index; - int ret = 0; - -@@ -2740,6 +2750,10 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - OV8865_TIMING_MAX_VTS - mode->output_size_y, - 1, mode->vts - mode->output_size_y); - -+ hblank = mode->hts - mode->output_size_x; -+ __v4l2_ctrl_modify_range(sensor->ctrls.hblank, hblank, hblank, 1, -+ hblank); -+ - complete: - mutex_unlock(&sensor->mutex); - --- -2.34.0 - -From 94ef87e674e1e6a3585b34aebf00c612f189c7ec Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 20 Oct 2021 22:43:54 +0100 -Subject: [PATCH] media: i2c: Update HTS values in ov8865 - -The HTS values for some of the modes in the ov8865 driver are a bit -unusual, coming in lower than the output_size_x is set to. It seems -like they might be calculated to fit the desired framerate into a -configuration with just two data lanes. To bring this more in line -with expected behaviour, raise the HTS values above the output_size_x. - -The corollary of that change is that the hardcoded frame intervals -against the modes no longer make sense, so remove those entirely. -Update the .g/s_frame_interval() callbacks to calculate the frame -interval based on the current mode and the vblank and hblank settings -plus the number of data lanes detected from firmware. - -The implementation of the .enum_frame_interval() callback is no longer -suitable since the possible frame rate is now a continuous range depending -on the vblank control setting, so remove that callback entirely. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 65 +++++++------------------------------- - 1 file changed, 11 insertions(+), 54 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 4b18cc80f985..1b8674152750 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -659,8 +659,6 @@ struct ov8865_mode { - unsigned int blc_anchor_right_start; - unsigned int blc_anchor_right_end; - -- struct v4l2_fract frame_interval; -- - bool pll2_binning; - - const struct ov8865_register_value *register_values; -@@ -964,7 +962,7 @@ static const struct ov8865_mode ov8865_modes[] = { - { - /* Horizontal */ - .output_size_x = 3264, -- .hts = 1944, -+ .hts = 3888, - - /* Vertical */ - .output_size_y = 2448, -@@ -1003,9 +1001,6 @@ static const struct ov8865_mode ov8865_modes[] = { - .blc_anchor_right_start = 1984, - .blc_anchor_right_end = 2239, - -- /* Frame Interval */ -- .frame_interval = { 1, 30 }, -- - /* PLL */ - .pll2_binning = false, - -@@ -1018,11 +1013,11 @@ static const struct ov8865_mode ov8865_modes[] = { - { - /* Horizontal */ - .output_size_x = 3264, -- .hts = 2582, -+ .hts = 3888, - - /* Vertical */ - .output_size_y = 1836, -- .vts = 2002, -+ .vts = 2470, - - .size_auto = true, - .size_auto_boundary_x = 8, -@@ -1057,9 +1052,6 @@ static const struct ov8865_mode ov8865_modes[] = { - .blc_anchor_right_start = 1984, - .blc_anchor_right_end = 2239, - -- /* Frame Interval */ -- .frame_interval = { 1, 30 }, -- - /* PLL */ - .pll2_binning = false, - -@@ -1115,9 +1107,6 @@ static const struct ov8865_mode ov8865_modes[] = { - .blc_anchor_right_start = 992, - .blc_anchor_right_end = 1119, - -- /* Frame Interval */ -- .frame_interval = { 1, 30 }, -- - /* PLL */ - .pll2_binning = true, - -@@ -1179,9 +1168,6 @@ static const struct ov8865_mode ov8865_modes[] = { - .blc_anchor_right_start = 992, - .blc_anchor_right_end = 1119, - -- /* Frame Interval */ -- .frame_interval = { 1, 90 }, -- - /* PLL */ - .pll2_binning = true, - -@@ -2628,11 +2614,18 @@ static int ov8865_g_frame_interval(struct v4l2_subdev *subdev, - { - struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); - const struct ov8865_mode *mode; -+ unsigned int framesize; -+ unsigned int fps; - - mutex_lock(&sensor->mutex); - - mode = sensor->state.mode; -- interval->interval = mode->frame_interval; -+ framesize = mode->hts * (mode->output_size_y + -+ sensor->ctrls.vblank->val); -+ fps = DIV_ROUND_CLOSEST(sensor->ctrls.pixel_rate->val, framesize); -+ -+ interval->interval.numerator = 1; -+ interval->interval.denominator = fps; - - mutex_unlock(&sensor->mutex); - -@@ -2777,41 +2770,6 @@ static int ov8865_enum_frame_size(struct v4l2_subdev *subdev, - return 0; - } - --static int ov8865_enum_frame_interval(struct v4l2_subdev *subdev, -- struct v4l2_subdev_state *sd_state, -- struct v4l2_subdev_frame_interval_enum *interval_enum) --{ -- const struct ov8865_mode *mode = NULL; -- unsigned int mode_index; -- unsigned int interval_index; -- -- if (interval_enum->index > 0) -- return -EINVAL; -- /* -- * Multiple modes with the same dimensions may have different frame -- * intervals, so look up each relevant mode. -- */ -- for (mode_index = 0, interval_index = 0; -- mode_index < ARRAY_SIZE(ov8865_modes); mode_index++) { -- mode = &ov8865_modes[mode_index]; -- -- if (mode->output_size_x == interval_enum->width && -- mode->output_size_y == interval_enum->height) { -- if (interval_index == interval_enum->index) -- break; -- -- interval_index++; -- } -- } -- -- if (mode_index == ARRAY_SIZE(ov8865_modes)) -- return -EINVAL; -- -- interval_enum->interval = mode->frame_interval; -- -- return 0; --} -- - static void - __ov8865_get_pad_crop(struct ov8865_sensor *sensor, - struct v4l2_subdev_state *state, unsigned int pad, -@@ -2870,7 +2828,6 @@ static const struct v4l2_subdev_pad_ops ov8865_subdev_pad_ops = { - .get_fmt = ov8865_get_fmt, - .set_fmt = ov8865_set_fmt, - .enum_frame_size = ov8865_enum_frame_size, -- .enum_frame_interval = ov8865_enum_frame_interval, - .get_selection = ov8865_get_selection, - .set_selection = ov8865_get_selection, - }; --- -2.34.0 - -From 34188431bc1debbcba03078f71629f03a044343a Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 13 Jul 2021 23:43:17 +0100 -Subject: [PATCH] media: i2c: cap exposure at height + vblank in ov8865 - -Exposure limits depend on the total height; when vblank is altered (and -thus the total height is altered), change the exposure limits to reflect -the new cap. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 26 ++++++++++++++++++++++++-- - 1 file changed, 24 insertions(+), 2 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 1b8674152750..99548ad15dcd 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -677,6 +677,7 @@ struct ov8865_ctrls { - struct v4l2_ctrl *pixel_rate; - struct v4l2_ctrl *hblank; - struct v4l2_ctrl *vblank; -+ struct v4l2_ctrl *exposure; - - struct v4l2_ctrl_handler handler; - }; -@@ -2454,6 +2455,19 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - unsigned int index; - int ret; - -+ /* If VBLANK is altered we need to update exposure to compensate */ -+ if (ctrl->id == V4L2_CID_VBLANK) { -+ int exposure_max; -+ -+ exposure_max = sensor->state.mode->output_size_y + ctrl->val; -+ __v4l2_ctrl_modify_range(sensor->ctrls.exposure, -+ sensor->ctrls.exposure->minimum, -+ exposure_max, -+ sensor->ctrls.exposure->step, -+ min(sensor->ctrls.exposure->val, -+ exposure_max)); -+ } -+ - /* Wait for the sensor to be on before setting controls. */ - if (pm_runtime_suspended(sensor->dev)) - return 0; -@@ -2510,8 +2524,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Exposure */ - -- v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, 1048575, 16, -- 512); -+ ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, -+ 1048575, 16, 512); - - /* Gain */ - -@@ -2700,6 +2714,7 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - u32 mbus_code = 0; - unsigned int hblank; - unsigned int index; -+ int exposure_max; - int ret = 0; - - mutex_lock(&sensor->mutex); -@@ -2747,6 +2762,13 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - __v4l2_ctrl_modify_range(sensor->ctrls.hblank, hblank, hblank, 1, - hblank); - -+ exposure_max = mode->vts; -+ __v4l2_ctrl_modify_range(sensor->ctrls.exposure, -+ sensor->ctrls.exposure->minimum, exposure_max, -+ sensor->ctrls.exposure->step, -+ min(sensor->ctrls.exposure->val, -+ exposure_max)); -+ - complete: - mutex_unlock(&sensor->mutex); - --- -2.34.0 - -From 50e23908b0e79c2f2b936b136b6050fae46fed5c Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 16 Jul 2021 22:56:15 +0100 -Subject: [PATCH] media: i2c: Add controls from fwnode to ov8865 - -Add V4L2_CID_CAMERA_ORIENTATION and V4L2_CID_CAMERA_SENSOR_ROTATION -controls to the ov8865 driver by attempting to parse them from firmware. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 10 ++++++++++ - 1 file changed, 10 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 99548ad15dcd..dfb5095ef16b 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2513,6 +2513,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - struct v4l2_ctrl_handler *handler = &ctrls->handler; - const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; - const struct ov8865_mode *mode = sensor->state.mode; -+ struct v4l2_fwnode_device_properties props; - unsigned int vblank_max, vblank_def; - unsigned int hblank; - int ret; -@@ -2576,6 +2577,15 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 1, - INT_MAX, 1, 1); - -+ /* set properties from fwnode (e.g. rotation, orientation) */ -+ ret = v4l2_fwnode_device_parse(sensor->dev, &props); -+ if (ret) -+ goto error_ctrls; -+ -+ ret = v4l2_ctrl_new_fwnode_properties(handler, ops, &props); -+ if (ret) -+ goto error_ctrls; -+ - if (handler->error) { - ret = handler->error; - goto error_ctrls; --- -2.34.0 - -From 6464bee63f9a1de50640765b8942e142d8e73d3c Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 16 Jul 2021 00:00:54 +0100 -Subject: [PATCH] media: i2c: Switch exposure control unit to lines - -The ov8865 driver currently has the unit of the V4L2_CID_EXPOSURE control -as 1/16th of a line. This is what the sensor expects, but isn't very -intuitive. Switch the control to be in units of a line and simply do the -16x multiplication before passing the value to the sensor. - -The datasheet for this sensor gives minimum exposure as 2 lines, so take -the opportunity to correct the lower bounds of the control. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 7 +++++-- - 1 file changed, 5 insertions(+), 2 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index dfb5095ef16b..5f19d82554df 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2125,6 +2125,9 @@ static int ov8865_exposure_configure(struct ov8865_sensor *sensor, u32 exposure) - { - int ret; - -+ /* The sensor stores exposure in units of 1/16th of a line */ -+ exposure *= 16; -+ - ret = ov8865_write(sensor, OV8865_EXPOSURE_CTRL_HH_REG, - OV8865_EXPOSURE_CTRL_HH(exposure)); - if (ret) -@@ -2525,8 +2528,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Exposure */ - -- ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, -- 1048575, 16, 512); -+ ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 2, -+ 65535, 1, 32); - - /* Gain */ - --- -2.34.0 - -From db9b9acd48871e17f9b2ebb11c77a3512496e154 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 24 Aug 2021 22:39:02 +0100 -Subject: [PATCH] media: i2c: Re-order runtime pm initialisation - -The kerneldoc for pm_runtime_set_suspended() says: - -"It is not valid to call this function for devices with runtime PM -enabled" - -To satisfy that requirement, re-order the calls so that -pm_runtime_enable() is the last one. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 5f19d82554df..18b5f1e8e9a7 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -3085,8 +3085,8 @@ static int ov8865_probe(struct i2c_client *client) - - /* Runtime PM */ - -- pm_runtime_enable(sensor->dev); - pm_runtime_set_suspended(sensor->dev); -+ pm_runtime_enable(sensor->dev); - - /* V4L2 subdev register */ - --- -2.34.0 - -From 4be77309a0952399f9ff05ce08129ddc35936c77 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 24 Aug 2021 23:17:39 +0100 -Subject: [PATCH] media: i2c: Use dev_err_probe() in ov8865 - -There is a chance that regulator_get() returns -EPROBE_DEFER, in which -case printing an error message is undesirable. To avoid spurious messages -in dmesg in the event that -EPROBE_DEFER is returned, use dev_err_probe() -on error paths for regulator_get(). - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 46 +++++++++++++++++--------------------- - 1 file changed, 20 insertions(+), 26 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 18b5f1e8e9a7..19e6bebf340d 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2955,6 +2955,26 @@ static int ov8865_probe(struct i2c_client *client) - sensor->dev = dev; - sensor->i2c_client = client; - -+ /* Regulators */ -+ -+ /* DVDD: digital core */ -+ sensor->dvdd = devm_regulator_get(dev, "dvdd"); -+ if (IS_ERR(sensor->dvdd)) -+ return dev_err_probe(dev, PTR_ERR(sensor->dvdd), -+ "cannot get DVDD regulator\n"); -+ -+ /* DOVDD: digital I/O */ -+ sensor->dovdd = devm_regulator_get(dev, "dovdd"); -+ if (IS_ERR(sensor->dovdd)) -+ return dev_err_probe(dev, PTR_ERR(sensor->dovdd), -+ "cannot get DOVDD regulator\n"); -+ -+ /* AVDD: analog */ -+ sensor->avdd = devm_regulator_get(dev, "avdd"); -+ if (IS_ERR(sensor->avdd)) -+ return dev_err_probe(dev, PTR_ERR(sensor->avdd), -+ "cannot get AVDD regulator\n"); -+ - /* Graph Endpoint */ - - handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); -@@ -2985,32 +3005,6 @@ static int ov8865_probe(struct i2c_client *client) - goto error_endpoint; - } - -- /* Regulators */ -- -- /* DVDD: digital core */ -- sensor->dvdd = devm_regulator_get(dev, "dvdd"); -- if (IS_ERR(sensor->dvdd)) { -- dev_err(dev, "cannot get DVDD (digital core) regulator\n"); -- ret = PTR_ERR(sensor->dvdd); -- goto error_endpoint; -- } -- -- /* DOVDD: digital I/O */ -- sensor->dovdd = devm_regulator_get(dev, "dovdd"); -- if (IS_ERR(sensor->dovdd)) { -- dev_err(dev, "cannot get DOVDD (digital I/O) regulator\n"); -- ret = PTR_ERR(sensor->dovdd); -- goto error_endpoint; -- } -- -- /* AVDD: analog */ -- sensor->avdd = devm_regulator_get(dev, "avdd"); -- if (IS_ERR(sensor->avdd)) { -- dev_err(dev, "cannot get AVDD (analog) regulator\n"); -- ret = PTR_ERR(sensor->avdd); -- goto error_endpoint; -- } -- - /* External Clock */ - - sensor->extclk = devm_clk_get(dev, NULL); --- -2.34.0 - -From 8058a58388e75455a71addd0f9c2bc8fba1ab0ad Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 14 Jul 2021 00:05:04 +0100 -Subject: [PATCH] media: ipu3-cio2: Add INT347A to cio2-bridge - -ACPI _HID INT347A represents the OV8865 sensor, the driver for which can -support the platforms that the cio2-bridge serves. Add it to the array -of supported sensors so the bridge will connect the sensor to the CIO2 -device. - -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index 7e582135dfb8..0132f0bd9b41 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -22,6 +22,8 @@ - static const struct cio2_sensor_config cio2_supported_sensors[] = { - /* Omnivision OV5693 */ - CIO2_SENSOR_CONFIG("INT33BE", 0), -+ /* Omnivision OV8865 */ -+ CIO2_SENSOR_CONFIG("INT347A", 1, 360000000), - /* Omnivision OV2680 */ - CIO2_SENSOR_CONFIG("OVTI2680", 0), - }; --- -2.34.0 - -From 20864ac9a8f63bfb299e092dba9fe8534aa46a10 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Thu, 7 Oct 2021 15:34:52 +0200 -Subject: [PATCH] media: i2c: ov8865: Fix lockdep error - -ov8865_state_init() calls ov8865_state_mipi_configure() which uses -__v4l2_ctrl_s_ctrl[_int64](). This means that sensor->mutex (which -is also sensor->ctrls.handler.lock) must be locked before calling -ov8865_state_init(). - -Note ov8865_state_mipi_configure() is also used in other places where -the lock is already held so it cannot be changed itself. - -This fixes the following lockdep kernel WARN: - -[ 13.233413] ------------[ cut here ]------------ -[ 13.233421] WARNING: CPU: 0 PID: 8 at drivers/media/v4l2-core/v4l2-ctrls-api.c:833 __v4l2_ctrl_s_ctrl+0x4d/0x60 [videodev] -... -[ 13.234063] Call Trace: -[ 13.234074] ov8865_state_configure+0x98b/0xc00 [ov8865] -[ 13.234095] ov8865_probe+0x4b1/0x54c [ov8865] -[ 13.234117] i2c_device_probe+0x13c/0x2d0 - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 19e6bebf340d..d5af8aedf5e8 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -3073,7 +3073,9 @@ static int ov8865_probe(struct i2c_client *client) - if (ret) - goto error_mutex; - -+ mutex_lock(&sensor->mutex); - ret = ov8865_state_init(sensor); -+ mutex_unlock(&sensor->mutex); - if (ret) - goto error_ctrls; - --- -2.34.0 - -From 719656084e09243f5ad1a6f795d5f74dd72f4867 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:56:57 +0200 -Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an - INT3472 device - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around cases where this info is not present in the firmware tables, -which is often the case on x86/ACPI devices, both frameworks allow the -provider-driver to attach info about consumers to the clks/regulators -when registering these. - -This causes problems with the probe ordering wrt drivers for consumers -of these clks/regulators. Since the lookups are only registered when the -provider-driver binds, trying to get these clks/regulators before then -results in a -ENOENT error for clks and a dummy regulator for regulators. - -One case where we hit this issue is camera sensors such as e.g. the OV8865 -sensor found on the Microsoft Surface Go. The sensor uses clks, regulators -and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 -ACPI device. There is special platform code handling this and setting -platform_data with the necessary consumer info on the MFD cells -instantiated for the PMIC under: drivers/platform/x86/intel/int3472. - -For this to work properly the ov8865 driver must not bind to the I2C-client -for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and -clk MFD cells have all been fully setup. - -The OV8865 on the Microsoft Surface Go is just one example, all X86 -devices using the Intel IPU3 camera block found on recent Intel SoCs -have similar issues where there is an INT3472 HID ACPI-device, which -describes the clks and regulators, and the driver for this INT3472 device -must be fully initialized before the sensor driver (any sensor driver) -binds for things to work properly. - -On these devices the ACPI nodes describing the sensors all have a _DEP -dependency on the matching INT3472 ACPI device (there is one per sensor). - -This allows solving the probe-ordering problem by delaying the enumeration -(instantiation of the I2C-client in the ov8865 example) of ACPI-devices -which have a _DEP dependency on an INT3472 device. - -The new acpi_dev_ready_for_enumeration() helper used for this is also -exported because for devices, which have the enumeration_by_parent flag -set, the parent-driver will do its own scan of child ACPI devices and -it will try to enumerate those during its probe(). Code doing this such -as e.g. the i2c-core-acpi.c code must call this new helper to ensure -that it too delays the enumeration until all the _DEP dependencies are -met on devices which have the new honor_deps flag set. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/acpi/scan.c | 36 ++++++++++++++++++++++++++++++++++-- - include/acpi/acpi_bus.h | 5 ++++- - 2 files changed, 38 insertions(+), 3 deletions(-) - -diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c -index ae9464091f1b..4a5cc1d695b4 100644 ---- a/drivers/acpi/scan.c -+++ b/drivers/acpi/scan.c -@@ -797,6 +797,12 @@ static const char * const acpi_ignore_dep_ids[] = { - NULL - }; - -+/* List of HIDs for which we honor deps of matching ACPI devs, when checking _DEP lists. */ -+static const char * const acpi_honor_dep_ids[] = { -+ "INT3472", /* Camera sensor PMIC / clk and regulator info */ -+ NULL -+}; -+ - static struct acpi_device *acpi_bus_get_parent(acpi_handle handle) - { - struct acpi_device *device = NULL; -@@ -1758,8 +1764,12 @@ static void acpi_scan_dep_init(struct acpi_device *adev) - struct acpi_dep_data *dep; - - list_for_each_entry(dep, &acpi_dep_list, node) { -- if (dep->consumer == adev->handle) -+ if (dep->consumer == adev->handle) { -+ if (dep->honor_dep) -+ adev->flags.honor_deps = 1; -+ - adev->dep_unmet++; -+ } - } - } - -@@ -1963,7 +1973,7 @@ static u32 acpi_scan_check_dep(acpi_handle handle, bool check_dep) - for (count = 0, i = 0; i < dep_devices.count; i++) { - struct acpi_device_info *info; - struct acpi_dep_data *dep; -- bool skip; -+ bool skip, honor_dep; - - status = acpi_get_object_info(dep_devices.handles[i], &info); - if (ACPI_FAILURE(status)) { -@@ -1972,6 +1982,7 @@ static u32 acpi_scan_check_dep(acpi_handle handle, bool check_dep) - } - - skip = acpi_info_matches_ids(info, acpi_ignore_dep_ids); -+ honor_dep = acpi_info_matches_ids(info, acpi_honor_dep_ids); - kfree(info); - - if (skip) -@@ -1985,6 +1996,7 @@ static u32 acpi_scan_check_dep(acpi_handle handle, bool check_dep) - - dep->supplier = dep_devices.handles[i]; - dep->consumer = handle; -+ dep->honor_dep = honor_dep; - - mutex_lock(&acpi_dep_list_lock); - list_add_tail(&dep->node , &acpi_dep_list); -@@ -2072,6 +2084,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, - - static void acpi_default_enumeration(struct acpi_device *device) - { -+ if (!acpi_dev_ready_for_enumeration(device)) -+ return; -+ - /* - * Do not enumerate devices with enumeration_by_parent flag set as - * they will be enumerated by their respective parents. -@@ -2314,6 +2329,23 @@ void acpi_dev_clear_dependencies(struct acpi_device *supplier) - } - EXPORT_SYMBOL_GPL(acpi_dev_clear_dependencies); - -+/** -+ * acpi_dev_ready_for_enumeration - Check if the ACPI device is ready for enumeration -+ * @device: Pointer to the &struct acpi_device to check -+ * -+ * Check if the device is present and has no unmet dependencies. -+ * -+ * Return true if the device is ready for enumeratino. Otherwise, return false. -+ */ -+bool acpi_dev_ready_for_enumeration(const struct acpi_device *device) -+{ -+ if (device->flags.honor_deps && device->dep_unmet) -+ return false; -+ -+ return acpi_device_is_present(device); -+} -+EXPORT_SYMBOL_GPL(acpi_dev_ready_for_enumeration); -+ - /** - * acpi_dev_get_first_consumer_dev - Return ACPI device dependent on @supplier - * @supplier: Pointer to the dependee device -diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h -index 13d93371790e..2da53b7b4965 100644 ---- a/include/acpi/acpi_bus.h -+++ b/include/acpi/acpi_bus.h -@@ -202,7 +202,8 @@ struct acpi_device_flags { - u32 coherent_dma:1; - u32 cca_seen:1; - u32 enumeration_by_parent:1; -- u32 reserved:19; -+ u32 honor_deps:1; -+ u32 reserved:18; - }; - - /* File System */ -@@ -284,6 +285,7 @@ struct acpi_dep_data { - struct list_head node; - acpi_handle supplier; - acpi_handle consumer; -+ bool honor_dep; - }; - - /* Performance Management */ -@@ -693,6 +695,7 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) - bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); - - void acpi_dev_clear_dependencies(struct acpi_device *supplier); -+bool acpi_dev_ready_for_enumeration(const struct acpi_device *device); - struct acpi_device *acpi_dev_get_first_consumer_dev(struct acpi_device *supplier); - struct acpi_device * - acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); --- -2.34.0 - -From 7d90f6ec28235414b69af24ff2cab4d172c8ad1a Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:56:58 +0200 -Subject: [PATCH] i2c: acpi: Use acpi_dev_ready_for_enumeration() helper - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around cases where this info is not present in the firmware tables, -which is often the case on x86/ACPI devices, both frameworks allow the -provider-driver to attach info about consumers to the clks/regulators -when registering these. - -This causes problems with the probe ordering wrt drivers for consumers -of these clks/regulators. Since the lookups are only registered when the -provider-driver binds, trying to get these clks/regulators before then -results in a -ENOENT error for clks and a dummy regulator for regulators. - -To ensure the correct probe-ordering the ACPI core has code to defer the -enumeration of consumers affected by this until the providers are ready. - -Call the new acpi_dev_ready_for_enumeration() helper to avoid -enumerating / instantiating i2c-clients too early. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/i2c/i2c-core-acpi.c | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index 169713964358..9f0e719cd2c8 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -112,9 +112,12 @@ static int i2c_acpi_do_lookup(struct acpi_device *adev, - struct list_head resource_list; - int ret; - -- if (acpi_bus_get_status(adev) || !adev->status.present) -+ if (acpi_bus_get_status(adev)) - return -EINVAL; - -+ if (!acpi_dev_ready_for_enumeration(adev)) -+ return -ENODEV; -+ - if (acpi_match_device_ids(adev, i2c_acpi_ignored_device_ids) == 0) - return -ENODEV; - --- -2.34.0 - -From 2eacb6feb10dd81f94140bb16dbef6c335d20b9b Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:56:59 +0200 -Subject: [PATCH] platform_data: Add linux/platform_data/tps68470.h file - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around cases where this info is not present in the firmware tables, -which is often the case on x86/ACPI devices, both frameworks allow the -provider-driver to attach info about consumers to the provider-device -during probe/registration of the provider device. - -The TI TPS68470 PMIC is used x86/ACPI devices with the consumer-info -missing from the ACPI tables. Thus the tps68470-clk and tps68470-regulator -drivers must provide the consumer-info at probe time. - -Define tps68470_clk_platform_data and tps68470_regulator_platform_data -structs to allow the x86 platform code to pass the necessary consumer info -to these drivers. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - include/linux/platform_data/tps68470.h | 35 ++++++++++++++++++++++++++ - 1 file changed, 35 insertions(+) - create mode 100644 include/linux/platform_data/tps68470.h - -diff --git a/include/linux/platform_data/tps68470.h b/include/linux/platform_data/tps68470.h -new file mode 100644 -index 000000000000..126d082c3f2e ---- /dev/null -+++ b/include/linux/platform_data/tps68470.h -@@ -0,0 +1,35 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * TI TPS68470 PMIC platform data definition. -+ * -+ * Copyright (c) 2021 Red Hat Inc. -+ * -+ * Red Hat authors: -+ * Hans de Goede -+ */ -+#ifndef __PDATA_TPS68470_H -+#define __PDATA_TPS68470_H -+ -+enum tps68470_regulators { -+ TPS68470_CORE, -+ TPS68470_ANA, -+ TPS68470_VCM, -+ TPS68470_VIO, -+ TPS68470_VSIO, -+ TPS68470_AUX1, -+ TPS68470_AUX2, -+ TPS68470_NUM_REGULATORS -+}; -+ -+struct regulator_init_data; -+ -+struct tps68470_regulator_platform_data { -+ const struct regulator_init_data *reg_init_data[TPS68470_NUM_REGULATORS]; -+}; -+ -+struct tps68470_clk_platform_data { -+ const char *consumer_dev_name; -+ const char *consumer_con_id; -+}; -+ -+#endif --- -2.34.0 - -From 2f1936ac79975f2a90f821bac31cf20c76a8bbce Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:00 +0200 -Subject: [PATCH] regulator: Introduce tps68470-regulator driver - -The TPS68470 PMIC provides Clocks, GPIOs and Regulators. At present in -the kernel the Regulators and Clocks are controlled by an OpRegion -driver designed to work with power control methods defined in ACPI, but -some platforms lack those methods, meaning drivers need to be able to -consume the resources of these chips through the usual frameworks. - -This commit adds a driver for the regulators provided by the tps68470, -and is designed to bind to the platform_device registered by the -intel_skl_int3472 module. - -This is based on this out of tree driver written by Intel: -https://github.com/intel/linux-intel-lts/blob/4.14/base/drivers/regulator/tps68470-regulator.c -with various cleanups added. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/regulator/Kconfig | 9 ++ - drivers/regulator/Makefile | 1 + - drivers/regulator/tps68470-regulator.c | 193 +++++++++++++++++++++++++ - 3 files changed, 203 insertions(+) - create mode 100644 drivers/regulator/tps68470-regulator.c - -diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig -index 24ce9a17ab4f..97caab5f7f9f 100644 ---- a/drivers/regulator/Kconfig -+++ b/drivers/regulator/Kconfig -@@ -1319,6 +1319,15 @@ config REGULATOR_TPS65912 - help - This driver supports TPS65912 voltage regulator chip. - -+config REGULATOR_TPS68470 -+ tristate "TI TPS68370 PMIC Regulators Driver" -+ depends on INTEL_SKL_INT3472 -+ help -+ This driver adds support for the TPS68470 PMIC to register -+ regulators against the usual framework. -+ -+ The module will be called "tps68470-regulator" -+ - config REGULATOR_TPS80031 - tristate "TI TPS80031/TPS80032 power regulator driver" - depends on MFD_TPS80031 -diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile -index 8c2f82206b94..8dcc4506ecf8 100644 ---- a/drivers/regulator/Makefile -+++ b/drivers/regulator/Makefile -@@ -156,6 +156,7 @@ obj-$(CONFIG_REGULATOR_TPS6524X) += tps6524x-regulator.o - obj-$(CONFIG_REGULATOR_TPS6586X) += tps6586x-regulator.o - obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o - obj-$(CONFIG_REGULATOR_TPS65912) += tps65912-regulator.o -+obj-$(CONFIG_REGULATOR_TPS68470) += tps68470-regulator.o - obj-$(CONFIG_REGULATOR_TPS80031) += tps80031-regulator.o - obj-$(CONFIG_REGULATOR_TPS65132) += tps65132-regulator.o - obj-$(CONFIG_REGULATOR_TWL4030) += twl-regulator.o twl6030-regulator.o -diff --git a/drivers/regulator/tps68470-regulator.c b/drivers/regulator/tps68470-regulator.c -new file mode 100644 -index 000000000000..3129fa13a122 ---- /dev/null -+++ b/drivers/regulator/tps68470-regulator.c -@@ -0,0 +1,193 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Regulator driver for TPS68470 PMIC -+ * -+ * Copyright (C) 2018 Intel Corporation -+ * -+ * Authors: -+ * Zaikuo Wang -+ * Tianshu Qiu -+ * Jian Xu Zheng -+ * Yuning Pu -+ * Rajmohan Mani -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define TPS68470_REGULATOR(_name, _id, _ops, _n, _vr, \ -+ _vm, _er, _em, _t, _lr, _nlr) \ -+ [TPS68470_ ## _name] = { \ -+ .name = # _name, \ -+ .id = _id, \ -+ .ops = &_ops, \ -+ .n_voltages = _n, \ -+ .type = REGULATOR_VOLTAGE, \ -+ .owner = THIS_MODULE, \ -+ .vsel_reg = _vr, \ -+ .vsel_mask = _vm, \ -+ .enable_reg = _er, \ -+ .enable_mask = _em, \ -+ .volt_table = _t, \ -+ .linear_ranges = _lr, \ -+ .n_linear_ranges = _nlr, \ -+ } -+ -+static const struct linear_range tps68470_ldo_ranges[] = { -+ REGULATOR_LINEAR_RANGE(875000, 0, 125, 17800), -+}; -+ -+static const struct linear_range tps68470_core_ranges[] = { -+ REGULATOR_LINEAR_RANGE(900000, 0, 42, 25000), -+}; -+ -+/* Operations permitted on DCDCx, LDO2, LDO3 and LDO4 */ -+static const struct regulator_ops tps68470_regulator_ops = { -+ .is_enabled = regulator_is_enabled_regmap, -+ .enable = regulator_enable_regmap, -+ .disable = regulator_disable_regmap, -+ .get_voltage_sel = regulator_get_voltage_sel_regmap, -+ .set_voltage_sel = regulator_set_voltage_sel_regmap, -+ .list_voltage = regulator_list_voltage_linear_range, -+ .map_voltage = regulator_map_voltage_linear_range, -+}; -+ -+static const struct regulator_desc regulators[] = { -+ TPS68470_REGULATOR(CORE, TPS68470_CORE, -+ tps68470_regulator_ops, 43, TPS68470_REG_VDVAL, -+ TPS68470_VDVAL_DVOLT_MASK, TPS68470_REG_VDCTL, -+ TPS68470_VDCTL_EN_MASK, -+ NULL, tps68470_core_ranges, -+ ARRAY_SIZE(tps68470_core_ranges)), -+ TPS68470_REGULATOR(ANA, TPS68470_ANA, -+ tps68470_regulator_ops, 126, TPS68470_REG_VAVAL, -+ TPS68470_VAVAL_AVOLT_MASK, TPS68470_REG_VACTL, -+ TPS68470_VACTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ TPS68470_REGULATOR(VCM, TPS68470_VCM, -+ tps68470_regulator_ops, 126, TPS68470_REG_VCMVAL, -+ TPS68470_VCMVAL_VCVOLT_MASK, TPS68470_REG_VCMCTL, -+ TPS68470_VCMCTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ TPS68470_REGULATOR(VIO, TPS68470_VIO, -+ tps68470_regulator_ops, 126, TPS68470_REG_VIOVAL, -+ TPS68470_VIOVAL_IOVOLT_MASK, TPS68470_REG_S_I2C_CTL, -+ TPS68470_S_I2C_CTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ -+/* -+ * (1) This register must have same setting as VIOVAL if S_IO LDO is used to -+ * power daisy chained IOs in the receive side. -+ * (2) If there is no I2C daisy chain it can be set freely. -+ * -+ */ -+ TPS68470_REGULATOR(VSIO, TPS68470_VSIO, -+ tps68470_regulator_ops, 126, TPS68470_REG_VSIOVAL, -+ TPS68470_VSIOVAL_IOVOLT_MASK, TPS68470_REG_S_I2C_CTL, -+ TPS68470_S_I2C_CTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ TPS68470_REGULATOR(AUX1, TPS68470_AUX1, -+ tps68470_regulator_ops, 126, TPS68470_REG_VAUX1VAL, -+ TPS68470_VAUX1VAL_AUX1VOLT_MASK, -+ TPS68470_REG_VAUX1CTL, -+ TPS68470_VAUX1CTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ TPS68470_REGULATOR(AUX2, TPS68470_AUX2, -+ tps68470_regulator_ops, 126, TPS68470_REG_VAUX2VAL, -+ TPS68470_VAUX2VAL_AUX2VOLT_MASK, -+ TPS68470_REG_VAUX2CTL, -+ TPS68470_VAUX2CTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+}; -+ -+#define TPS68470_REG_INIT_DATA(_name, _min_uV, _max_uV) \ -+ [TPS68470_ ## _name] = { \ -+ .constraints = { \ -+ .name = # _name, \ -+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | \ -+ REGULATOR_CHANGE_STATUS, \ -+ .min_uV = _min_uV, \ -+ .max_uV = _max_uV, \ -+ }, \ -+ } -+ -+struct regulator_init_data tps68470_init[] = { -+ TPS68470_REG_INIT_DATA(CORE, 900000, 1950000), -+ TPS68470_REG_INIT_DATA(ANA, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(VCM, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(VIO, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(VSIO, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(AUX1, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(AUX2, 875000, 3100000), -+}; -+ -+static int tps68470_regulator_probe(struct platform_device *pdev) -+{ -+ struct tps68470_regulator_platform_data *pdata = pdev->dev.platform_data; -+ struct regulator_config config = { }; -+ struct regmap *tps68470_regmap; -+ struct regulator_dev *rdev; -+ int i; -+ -+ tps68470_regmap = dev_get_drvdata(pdev->dev.parent); -+ -+ for (i = 0; i < TPS68470_NUM_REGULATORS; i++) { -+ config.dev = pdev->dev.parent; -+ config.regmap = tps68470_regmap; -+ if (pdata && pdata->reg_init_data[i]) -+ config.init_data = pdata->reg_init_data[i]; -+ else -+ config.init_data = &tps68470_init[i]; -+ -+ rdev = devm_regulator_register(&pdev->dev, ®ulators[i], &config); -+ if (IS_ERR(rdev)) { -+ dev_err(&pdev->dev, "failed to register %s regulator\n", -+ regulators[i].name); -+ return PTR_ERR(rdev); -+ } -+ } -+ -+ return 0; -+} -+ -+static struct platform_driver tps68470_regulator_driver = { -+ .driver = { -+ .name = "tps68470-regulator", -+ }, -+ .probe = tps68470_regulator_probe, -+}; -+ -+/* -+ * The ACPI tps68470 probe-ordering depends on the clk/gpio/regulator drivers -+ * registering before the drivers for the camera-sensors which use them bind. -+ * subsys_initcall() ensures this when the drivers are builtin. -+ */ -+static int __init tps68470_regulator_init(void) -+{ -+ return platform_driver_register(&tps68470_regulator_driver); -+} -+subsys_initcall(tps68470_regulator_init); -+ -+static void __exit tps68470_regulator_exit(void) -+{ -+ platform_driver_unregister(&tps68470_regulator_driver); -+} -+module_exit(tps68470_regulator_exit); -+ -+MODULE_ALIAS("platform:tps68470-regulator"); -+MODULE_DESCRIPTION("TPS68470 voltage regulator driver"); -+MODULE_LICENSE("GPL v2"); --- -2.34.0 - -From d114fbffa257b641e5f21a3d65878ec0ff6a012f Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:01 +0200 -Subject: [PATCH] clk: Introduce clk-tps68470 driver - -The TPS68470 PMIC provides Clocks, GPIOs and Regulators. At present in -the kernel the Regulators and Clocks are controlled by an OpRegion -driver designed to work with power control methods defined in ACPI, but -some platforms lack those methods, meaning drivers need to be able to -consume the resources of these chips through the usual frameworks. - -This commit adds a driver for the clocks provided by the tps68470, -and is designed to bind to the platform_device registered by the -intel_skl_int3472 module. - -This is based on this out of tree driver written by Intel: -https://github.com/intel/linux-intel-lts/blob/4.14/base/drivers/clk/clk-tps68470.c -with various cleanups added. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/clk/Kconfig | 6 + - drivers/clk/Makefile | 1 + - drivers/clk/clk-tps68470.c | 256 +++++++++++++++++++++++++++++++++++ - include/linux/mfd/tps68470.h | 11 ++ - 4 files changed, 274 insertions(+) - create mode 100644 drivers/clk/clk-tps68470.c - -diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig -index e873f9ea2e65..f5255b085fe2 100644 ---- a/drivers/clk/Kconfig -+++ b/drivers/clk/Kconfig -@@ -169,6 +169,12 @@ config COMMON_CLK_CDCE706 - help - This driver supports TI CDCE706 programmable 3-PLL clock synthesizer. - -+config COMMON_CLK_TPS68470 -+ tristate "Clock Driver for TI TPS68470 PMIC" -+ depends on I2C && REGMAP_I2C && INTEL_SKL_INT3472 -+ help -+ This driver supports the clocks provided by TPS68470 -+ - config COMMON_CLK_CDCE925 - tristate "Clock driver for TI CDCE913/925/937/949 devices" - depends on I2C -diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile -index 2b91d34c582b..1f7dad3461f2 100644 ---- a/drivers/clk/Makefile -+++ b/drivers/clk/Makefile -@@ -63,6 +63,7 @@ obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o - obj-$(CONFIG_COMMON_CLK_STM32F) += clk-stm32f4.o - obj-$(CONFIG_COMMON_CLK_STM32H7) += clk-stm32h7.o - obj-$(CONFIG_COMMON_CLK_STM32MP157) += clk-stm32mp1.o -+obj-$(CONFIG_COMMON_CLK_TPS68470) += clk-tps68470.o - obj-$(CONFIG_CLK_TWL6040) += clk-twl6040.o - obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o - obj-$(CONFIG_COMMON_CLK_VC5) += clk-versaclock5.o -diff --git a/drivers/clk/clk-tps68470.c b/drivers/clk/clk-tps68470.c -new file mode 100644 -index 000000000000..27e8cbd0f60e ---- /dev/null -+++ b/drivers/clk/clk-tps68470.c -@@ -0,0 +1,256 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Clock driver for TPS68470 PMIC -+ * -+ * Copyright (C) 2018 Intel Corporation -+ * -+ * Authors: -+ * Zaikuo Wang -+ * Tianshu Qiu -+ * Jian Xu Zheng -+ * Yuning Pu -+ * Antti Laakso -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define TPS68470_CLK_NAME "tps68470-clk" -+ -+#define to_tps68470_clkdata(clkd) \ -+ container_of(clkd, struct tps68470_clkdata, clkout_hw) -+ -+struct tps68470_clkout_freqs { -+ unsigned long freq; -+ unsigned int xtaldiv; -+ unsigned int plldiv; -+ unsigned int postdiv; -+ unsigned int buckdiv; -+ unsigned int boostdiv; -+} clk_freqs[] = { -+/* -+ * The PLL is used to multiply the crystal oscillator -+ * frequency range of 3 MHz to 27 MHz by a programmable -+ * factor of F = (M/N)*(1/P) such that the output -+ * available at the HCLK_A or HCLK_B pins are in the range -+ * of 4 MHz to 64 MHz in increments of 0.1 MHz -+ * -+ * hclk_# = osc_in * (((plldiv*2)+320) / (xtaldiv+30)) * (1 / 2^postdiv) -+ * -+ * PLL_REF_CLK should be as close as possible to 100kHz -+ * PLL_REF_CLK = input clk / XTALDIV[7:0] + 30) -+ * -+ * PLL_VCO_CLK = (PLL_REF_CLK * (plldiv*2 + 320)) -+ * -+ * BOOST should be as close as possible to 2Mhz -+ * BOOST = PLL_VCO_CLK / (BOOSTDIV[4:0] + 16) * -+ * -+ * BUCK should be as close as possible to 5.2Mhz -+ * BUCK = PLL_VCO_CLK / (BUCKDIV[3:0] + 5) -+ * -+ * osc_in xtaldiv plldiv postdiv hclk_# -+ * 20Mhz 170 32 1 19.2Mhz -+ * 20Mhz 170 40 1 20Mhz -+ * 20Mhz 170 80 1 24Mhz -+ * -+ */ -+ { 19200000, 170, 32, 1, 2, 3 }, -+ { 20000000, 170, 40, 1, 3, 4 }, -+ { 24000000, 170, 80, 1, 4, 8 }, -+}; -+ -+struct tps68470_clkdata { -+ struct clk_hw clkout_hw; -+ struct regmap *regmap; -+ struct clk *clk; -+ int clk_cfg_idx; -+}; -+ -+static int tps68470_clk_is_prepared(struct clk_hw *hw) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ int val; -+ -+ if (regmap_read(clkdata->regmap, TPS68470_REG_PLLCTL, &val)) -+ return 0; -+ -+ return val & TPS68470_PLL_EN_MASK; -+} -+ -+static int tps68470_clk_prepare(struct clk_hw *hw) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ int idx = clkdata->clk_cfg_idx; -+ -+ regmap_write(clkdata->regmap, TPS68470_REG_BOOSTDIV, clk_freqs[idx].boostdiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_BUCKDIV, clk_freqs[idx].buckdiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLSWR, TPS68470_PLLSWR_DEFAULT); -+ regmap_write(clkdata->regmap, TPS68470_REG_XTALDIV, clk_freqs[idx].xtaldiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLDIV, clk_freqs[idx].plldiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_POSTDIV, clk_freqs[idx].postdiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_POSTDIV2, clk_freqs[idx].postdiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG2, TPS68470_CLKCFG2_DRV_STR_2MA); -+ -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLCTL, -+ TPS68470_OSC_EXT_CAP_DEFAULT << TPS68470_OSC_EXT_CAP_SHIFT | -+ TPS68470_CLK_SRC_XTAL << TPS68470_CLK_SRC_SHIFT); -+ -+ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG1, -+ (TPS68470_PLL_OUTPUT_ENABLE << -+ TPS68470_OUTPUT_A_SHIFT) | -+ (TPS68470_PLL_OUTPUT_ENABLE << -+ TPS68470_OUTPUT_B_SHIFT)); -+ -+ regmap_update_bits(clkdata->regmap, TPS68470_REG_PLLCTL, -+ TPS68470_PLL_EN_MASK, TPS68470_PLL_EN_MASK); -+ -+ return 0; -+} -+ -+static void tps68470_clk_unprepare(struct clk_hw *hw) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ -+ /* disable clock first*/ -+ regmap_update_bits(clkdata->regmap, TPS68470_REG_PLLCTL, TPS68470_PLL_EN_MASK, 0); -+ -+ /* write hw defaults */ -+ regmap_write(clkdata->regmap, TPS68470_REG_BOOSTDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_BUCKDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLSWR, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_XTALDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_POSTDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG2, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG1, 0); -+} -+ -+static unsigned long tps68470_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ -+ return clk_freqs[clkdata->clk_cfg_idx].freq; -+} -+ -+static int tps68470_clk_cfg_lookup(unsigned long rate) -+{ -+ long diff, best_diff = LONG_MAX; -+ int i, best_idx = 0; -+ -+ for (i = 0; i < ARRAY_SIZE(clk_freqs); i++) { -+ diff = clk_freqs[i].freq - rate; -+ if (diff == 0) -+ return i; -+ -+ diff = abs(diff); -+ if (diff < best_diff) { -+ best_diff = diff; -+ best_idx = i; -+ } -+ } -+ -+ return best_idx; -+} -+ -+static long tps68470_clk_round_rate(struct clk_hw *hw, unsigned long rate, -+ unsigned long *parent_rate) -+{ -+ int idx = tps68470_clk_cfg_lookup(rate); -+ -+ return clk_freqs[idx].freq; -+} -+ -+static int tps68470_clk_set_rate(struct clk_hw *hw, unsigned long rate, -+ unsigned long parent_rate) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ int idx = tps68470_clk_cfg_lookup(rate); -+ -+ if (rate != clk_freqs[idx].freq) -+ return -EINVAL; -+ -+ clkdata->clk_cfg_idx = idx; -+ return 0; -+} -+ -+static const struct clk_ops tps68470_clk_ops = { -+ .is_prepared = tps68470_clk_is_prepared, -+ .prepare = tps68470_clk_prepare, -+ .unprepare = tps68470_clk_unprepare, -+ .recalc_rate = tps68470_clk_recalc_rate, -+ .round_rate = tps68470_clk_round_rate, -+ .set_rate = tps68470_clk_set_rate, -+}; -+ -+static struct clk_init_data tps68470_clk_initdata = { -+ .name = TPS68470_CLK_NAME, -+ .ops = &tps68470_clk_ops, -+}; -+ -+static int tps68470_clk_probe(struct platform_device *pdev) -+{ -+ struct tps68470_clk_platform_data *pdata = pdev->dev.platform_data; -+ struct tps68470_clkdata *tps68470_clkdata; -+ int ret; -+ -+ tps68470_clkdata = devm_kzalloc(&pdev->dev, sizeof(*tps68470_clkdata), -+ GFP_KERNEL); -+ if (!tps68470_clkdata) -+ return -ENOMEM; -+ -+ tps68470_clkdata->regmap = dev_get_drvdata(pdev->dev.parent); -+ tps68470_clkdata->clkout_hw.init = &tps68470_clk_initdata; -+ tps68470_clkdata->clk = devm_clk_register(&pdev->dev, &tps68470_clkdata->clkout_hw); -+ if (IS_ERR(tps68470_clkdata->clk)) -+ return PTR_ERR(tps68470_clkdata->clk); -+ -+ ret = devm_clk_hw_register_clkdev(&pdev->dev, &tps68470_clkdata->clkout_hw, -+ TPS68470_CLK_NAME, NULL); -+ if (ret) -+ return ret; -+ -+ if (pdata) { -+ ret = devm_clk_hw_register_clkdev(&pdev->dev, -+ &tps68470_clkdata->clkout_hw, -+ pdata->consumer_con_id, -+ pdata->consumer_dev_name); -+ if (ret) -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static struct platform_driver tps68470_clk_driver = { -+ .driver = { -+ .name = TPS68470_CLK_NAME, -+ }, -+ .probe = tps68470_clk_probe, -+}; -+ -+/* -+ * The ACPI tps68470 probe-ordering depends on the clk/gpio/regulator drivers -+ * registering before the drivers for the camera-sensors which use them bind. -+ * subsys_initcall() ensures this when the drivers are builtin. -+ */ -+static int __init tps68470_clk_init(void) -+{ -+ return platform_driver_register(&tps68470_clk_driver); -+} -+subsys_initcall(tps68470_clk_init); -+ -+static void __exit tps68470_clk_exit(void) -+{ -+ platform_driver_unregister(&tps68470_clk_driver); -+} -+module_exit(tps68470_clk_exit); -+ -+MODULE_ALIAS("platform:tps68470-clk"); -+MODULE_DESCRIPTION("clock driver for TPS68470 pmic"); -+MODULE_LICENSE("GPL"); -diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h -index ffe81127d91c..7807fa329db0 100644 ---- a/include/linux/mfd/tps68470.h -+++ b/include/linux/mfd/tps68470.h -@@ -75,6 +75,17 @@ - #define TPS68470_CLKCFG1_MODE_A_MASK GENMASK(1, 0) - #define TPS68470_CLKCFG1_MODE_B_MASK GENMASK(3, 2) - -+#define TPS68470_CLKCFG2_DRV_STR_2MA 0x05 -+#define TPS68470_PLL_OUTPUT_ENABLE 0x02 -+#define TPS68470_CLK_SRC_XTAL BIT(0) -+#define TPS68470_PLLSWR_DEFAULT GENMASK(1, 0) -+#define TPS68470_OSC_EXT_CAP_DEFAULT 0x05 -+ -+#define TPS68470_OUTPUT_A_SHIFT 0x00 -+#define TPS68470_OUTPUT_B_SHIFT 0x02 -+#define TPS68470_CLK_SRC_SHIFT GENMASK(2, 0) -+#define TPS68470_OSC_EXT_CAP_SHIFT BIT(2) -+ - #define TPS68470_GPIO_CTL_REG_A(x) (TPS68470_REG_GPCTL0A + (x) * 2) - #define TPS68470_GPIO_CTL_REG_B(x) (TPS68470_REG_GPCTL0B + (x) * 2) - #define TPS68470_GPIO_MODE_MASK GENMASK(1, 0) --- -2.34.0 - -From 235191b72abd1f1b529b5917c3f6fe60415ae4f5 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 10 Oct 2021 20:57:02 +0200 -Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain - -The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic -can be forwarded to a device connected to the PMIC as though it were -connected directly to the system bus. Enable this mode when the chip -is initialised. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - .../x86/intel/int3472/intel_skl_int3472_tps68470.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -index c05b4cf502fe..42e688f4cad4 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -+++ b/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -@@ -45,6 +45,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) - return ret; - } - -+ /* Enable I2C daisy chain */ -+ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); -+ if (ret) { -+ dev_err(dev, "Failed to enable i2c daisy chain\n"); -+ return ret; -+ } -+ - dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); - - return 0; --- -2.34.0 - -From eef319f98c64c8e155be21f592cbb16ef30da91e Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:03 +0200 -Subject: [PATCH] platform/x86: int3472: Split into 2 drivers - -The intel_skl_int3472.ko module contains 2 separate drivers, -the int3472_discrete platform driver and the int3472_tps68470 -I2C-driver. - -These 2 drivers contain very little shared code, only -skl_int3472_get_acpi_buffer() and skl_int3472_fill_cldb() are -shared. - -Split the module into 2 drivers, linking the little shared code -directly into both. - -This will allow us to add soft-module dependencies for the -tps68470 clk, gpio and regulator drivers to the new -intel_skl_int3472_tps68470.ko to help with probe ordering issues -without causing these modules to get loaded on boards which only -use the int3472_discrete platform driver. - -While at it also rename the .c and .h files to remove the -cumbersome intel_skl_int3472_ prefix. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/Makefile | 9 ++-- - ...lk_and_regulator.c => clk_and_regulator.c} | 2 +- - drivers/platform/x86/intel/int3472/common.c | 54 +++++++++++++++++++ - .../{intel_skl_int3472_common.h => common.h} | 3 -- - ...ntel_skl_int3472_discrete.c => discrete.c} | 28 ++++++++-- - ...ntel_skl_int3472_tps68470.c => tps68470.c} | 23 +++++++- - 6 files changed, 105 insertions(+), 14 deletions(-) - rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_clk_and_regulator.c => clk_and_regulator.c} (99%) - create mode 100644 drivers/platform/x86/intel/int3472/common.c - rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_common.h => common.h} (94%) - rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_discrete.c => discrete.c} (93%) - rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_tps68470.c => tps68470.c} (85%) - -diff --git a/drivers/platform/x86/intel/int3472/Makefile b/drivers/platform/x86/intel/int3472/Makefile -index 48bd97f0a04e..4a4b2518ea16 100644 ---- a/drivers/platform/x86/intel/int3472/Makefile -+++ b/drivers/platform/x86/intel/int3472/Makefile -@@ -1,5 +1,4 @@ --obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o --intel_skl_int3472-objs := intel_skl_int3472_common.o \ -- intel_skl_int3472_discrete.o \ -- intel_skl_int3472_tps68470.o \ -- intel_skl_int3472_clk_and_regulator.o -+obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472_discrete.o \ -+ intel_skl_int3472_tps68470.o -+intel_skl_int3472_discrete-y := discrete.o clk_and_regulator.o common.o -+intel_skl_int3472_tps68470-y := tps68470.o common.o -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel/int3472/clk_and_regulator.c -similarity index 99% -rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c -rename to drivers/platform/x86/intel/int3472/clk_and_regulator.c -index 1700e7557a82..1cf958983e86 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c -+++ b/drivers/platform/x86/intel/int3472/clk_and_regulator.c -@@ -9,7 +9,7 @@ - #include - #include - --#include "intel_skl_int3472_common.h" -+#include "common.h" - - /* - * The regulators have to have .ops to be valid, but the only ops we actually -diff --git a/drivers/platform/x86/intel/int3472/common.c b/drivers/platform/x86/intel/int3472/common.c -new file mode 100644 -index 000000000000..350655a9515b ---- /dev/null -+++ b/drivers/platform/x86/intel/int3472/common.c -@@ -0,0 +1,54 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+ -+#include "common.h" -+ -+union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id) -+{ -+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; -+ acpi_handle handle = adev->handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(handle, id, NULL, &buffer); -+ if (ACPI_FAILURE(status)) -+ return ERR_PTR(-ENODEV); -+ -+ obj = buffer.pointer; -+ if (!obj) -+ return ERR_PTR(-ENODEV); -+ -+ if (obj->type != ACPI_TYPE_BUFFER) { -+ acpi_handle_err(handle, "%s object is not an ACPI buffer\n", id); -+ kfree(obj); -+ return ERR_PTR(-EINVAL); -+ } -+ -+ return obj; -+} -+ -+int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) -+{ -+ union acpi_object *obj; -+ int ret; -+ -+ obj = skl_int3472_get_acpi_buffer(adev, "CLDB"); -+ if (IS_ERR(obj)) -+ return PTR_ERR(obj); -+ -+ if (obj->buffer.length > sizeof(*cldb)) { -+ acpi_handle_err(adev->handle, "The CLDB buffer is too large\n"); -+ ret = -EINVAL; -+ goto out_free_obj; -+ } -+ -+ memcpy(cldb, obj->buffer.pointer, obj->buffer.length); -+ ret = 0; -+ -+out_free_obj: -+ kfree(obj); -+ return ret; -+} -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel/int3472/common.h -similarity index 94% -rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h -rename to drivers/platform/x86/intel/int3472/common.h -index 714fde73b524..d14944ee8586 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h -+++ b/drivers/platform/x86/intel/int3472/common.h -@@ -105,9 +105,6 @@ struct int3472_discrete_device { - struct gpiod_lookup_table gpios; - }; - --int skl_int3472_discrete_probe(struct platform_device *pdev); --int skl_int3472_discrete_remove(struct platform_device *pdev); --int skl_int3472_tps68470_probe(struct i2c_client *client); - union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, - char *id); - int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel/int3472/discrete.c -similarity index 93% -rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c -rename to drivers/platform/x86/intel/int3472/discrete.c -index 9fe0a2527e1c..856602a2f6bb 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c -+++ b/drivers/platform/x86/intel/int3472/discrete.c -@@ -14,7 +14,7 @@ - #include - #include - --#include "intel_skl_int3472_common.h" -+#include "common.h" - - /* - * 79234640-9e10-4fea-a5c1-b5aa8b19756f -@@ -332,7 +332,9 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) - return 0; - } - --int skl_int3472_discrete_probe(struct platform_device *pdev) -+static int skl_int3472_discrete_remove(struct platform_device *pdev); -+ -+static int skl_int3472_discrete_probe(struct platform_device *pdev) - { - struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); - struct int3472_discrete_device *int3472; -@@ -395,7 +397,7 @@ int skl_int3472_discrete_probe(struct platform_device *pdev) - return ret; - } - --int skl_int3472_discrete_remove(struct platform_device *pdev) -+static int skl_int3472_discrete_remove(struct platform_device *pdev) - { - struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); - -@@ -411,3 +413,23 @@ int skl_int3472_discrete_remove(struct platform_device *pdev) - - return 0; - } -+ -+static const struct acpi_device_id int3472_device_id[] = { -+ { "INT3472", 0 }, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, int3472_device_id); -+ -+static struct platform_driver int3472_discrete = { -+ .driver = { -+ .name = "int3472-discrete", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe = skl_int3472_discrete_probe, -+ .remove = skl_int3472_discrete_remove, -+}; -+module_platform_driver(int3472_discrete); -+ -+MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Discrete Device Driver"); -+MODULE_AUTHOR("Daniel Scally "); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -similarity index 85% -rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -rename to drivers/platform/x86/intel/int3472/tps68470.c -index 42e688f4cad4..b94cf66ab61f 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -7,7 +7,7 @@ - #include - #include - --#include "intel_skl_int3472_common.h" -+#include "common.h" - - #define DESIGNED_FOR_CHROMEOS 1 - #define DESIGNED_FOR_WINDOWS 2 -@@ -102,7 +102,7 @@ static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) - return DESIGNED_FOR_WINDOWS; - } - --int skl_int3472_tps68470_probe(struct i2c_client *client) -+static int skl_int3472_tps68470_probe(struct i2c_client *client) - { - struct acpi_device *adev = ACPI_COMPANION(&client->dev); - struct regmap *regmap; -@@ -142,3 +142,22 @@ int skl_int3472_tps68470_probe(struct i2c_client *client) - - return ret; - } -+ -+static const struct acpi_device_id int3472_device_id[] = { -+ { "INT3472", 0 }, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, int3472_device_id); -+ -+static struct i2c_driver int3472_tps68470 = { -+ .driver = { -+ .name = "int3472-tps68470", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe_new = skl_int3472_tps68470_probe, -+}; -+module_i2c_driver(int3472_tps68470); -+ -+MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI TPS68470 Device Driver"); -+MODULE_AUTHOR("Daniel Scally "); -+MODULE_LICENSE("GPL v2"); --- -2.34.0 - -From 25895ad4036113835c557ee75f9f9c524d56adb7 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:04 +0200 -Subject: [PATCH] platform/x86: int3472: Add get_sensor_adev_and_name() helper - -The discrete.c code is not the only code which needs to lookup the -acpi_device and device-name for the sensor for which the INT3472 -ACPI-device is a GPIO/clk/regulator provider. - -The tps68470.c code also needs this functionality, so factor this -out into a new get_sensor_adev_and_name() helper. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/common.c | 28 +++++++++++++++++++ - drivers/platform/x86/intel/int3472/common.h | 3 ++ - drivers/platform/x86/intel/int3472/discrete.c | 22 +++------------ - 3 files changed, 35 insertions(+), 18 deletions(-) - -diff --git a/drivers/platform/x86/intel/int3472/common.c b/drivers/platform/x86/intel/int3472/common.c -index 350655a9515b..77cf058e4168 100644 ---- a/drivers/platform/x86/intel/int3472/common.c -+++ b/drivers/platform/x86/intel/int3472/common.c -@@ -52,3 +52,31 @@ int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) - kfree(obj); - return ret; - } -+ -+/* sensor_adev_ret may be NULL, name_ret must not be NULL */ -+int skl_int3472_get_sensor_adev_and_name(struct device *dev, -+ struct acpi_device **sensor_adev_ret, -+ const char **name_ret) -+{ -+ struct acpi_device *adev = ACPI_COMPANION(dev); -+ struct acpi_device *sensor; -+ int ret = 0; -+ -+ sensor = acpi_dev_get_first_consumer_dev(adev); -+ if (!sensor) { -+ dev_err(dev, "INT3472 seems to have no dependents.\n"); -+ return -ENODEV; -+ } -+ -+ *name_ret = devm_kasprintf(dev, GFP_KERNEL, I2C_DEV_NAME_FORMAT, -+ acpi_dev_name(sensor)); -+ if (!*name_ret) -+ ret = -ENOMEM; -+ -+ if (ret == 0 && sensor_adev_ret) -+ *sensor_adev_ret = sensor; -+ else -+ acpi_dev_put(sensor); -+ -+ return ret; -+} -diff --git a/drivers/platform/x86/intel/int3472/common.h b/drivers/platform/x86/intel/int3472/common.h -index d14944ee8586..53270d19c73a 100644 ---- a/drivers/platform/x86/intel/int3472/common.h -+++ b/drivers/platform/x86/intel/int3472/common.h -@@ -108,6 +108,9 @@ struct int3472_discrete_device { - union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, - char *id); - int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); -+int skl_int3472_get_sensor_adev_and_name(struct device *dev, -+ struct acpi_device **sensor_adev_ret, -+ const char **name_ret); - - int skl_int3472_register_clock(struct int3472_discrete_device *int3472); - void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472); -diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c -index 856602a2f6bb..22a4894f1cc7 100644 ---- a/drivers/platform/x86/intel/int3472/discrete.c -+++ b/drivers/platform/x86/intel/int3472/discrete.c -@@ -363,19 +363,10 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) - int3472->dev = &pdev->dev; - platform_set_drvdata(pdev, int3472); - -- int3472->sensor = acpi_dev_get_first_consumer_dev(adev); -- if (!int3472->sensor) { -- dev_err(&pdev->dev, "INT3472 seems to have no dependents.\n"); -- return -ENODEV; -- } -- -- int3472->sensor_name = devm_kasprintf(int3472->dev, GFP_KERNEL, -- I2C_DEV_NAME_FORMAT, -- acpi_dev_name(int3472->sensor)); -- if (!int3472->sensor_name) { -- ret = -ENOMEM; -- goto err_put_sensor; -- } -+ ret = skl_int3472_get_sensor_adev_and_name(&pdev->dev, &int3472->sensor, -+ &int3472->sensor_name); -+ if (ret) -+ return ret; - - /* - * Initialising this list means we can call gpiod_remove_lookup_table() -@@ -390,11 +381,6 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) - } - - return 0; -- --err_put_sensor: -- acpi_dev_put(int3472->sensor); -- -- return ret; - } - - static int skl_int3472_discrete_remove(struct platform_device *pdev) --- -2.34.0 - -From 3ed4113df3ad5142e726a54ae3c6592733ee8367 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:05 +0200 -Subject: [PATCH] platform/x86: int3472: Pass tps68470_clk_platform_data to the - tps68470-regulator MFD-cell - -Pass tps68470_clk_platform_data to the tps68470-clk MFD-cell, -so that sensors which use the TPS68470 can find their clock. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/tps68470.c | 33 ++++++++++++++----- - 1 file changed, 25 insertions(+), 8 deletions(-) - -diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -index b94cf66ab61f..78e34e7b6969 100644 ---- a/drivers/platform/x86/intel/int3472/tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -5,6 +5,7 @@ - #include - #include - #include -+#include - #include - - #include "common.h" -@@ -17,12 +18,6 @@ static const struct mfd_cell tps68470_cros[] = { - { .name = "tps68470_pmic_opregion" }, - }; - --static const struct mfd_cell tps68470_win[] = { -- { .name = "tps68470-gpio" }, -- { .name = "tps68470-clk" }, -- { .name = "tps68470-regulator" }, --}; -- - static const struct regmap_config tps68470_regmap_config = { - .reg_bits = 8, - .val_bits = 8, -@@ -105,10 +100,17 @@ static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) - static int skl_int3472_tps68470_probe(struct i2c_client *client) - { - struct acpi_device *adev = ACPI_COMPANION(&client->dev); -+ struct tps68470_clk_platform_data clk_pdata = {}; -+ struct mfd_cell *cells; - struct regmap *regmap; - int device_type; - int ret; - -+ ret = skl_int3472_get_sensor_adev_and_name(&client->dev, NULL, -+ &clk_pdata.consumer_dev_name); -+ if (ret) -+ return ret; -+ - regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); - if (IS_ERR(regmap)) { - dev_err(&client->dev, "Failed to create regmap: %ld\n", PTR_ERR(regmap)); -@@ -126,9 +128,24 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - device_type = skl_int3472_tps68470_calc_type(adev); - switch (device_type) { - case DESIGNED_FOR_WINDOWS: -- ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, -- tps68470_win, ARRAY_SIZE(tps68470_win), -+ cells = kcalloc(3, sizeof(*cells), GFP_KERNEL); -+ if (!cells) -+ return -ENOMEM; -+ -+ cells[0].name = "tps68470-clk"; -+ cells[0].platform_data = &clk_pdata; -+ cells[0].pdata_size = sizeof(clk_pdata); -+ cells[1].name = "tps68470-regulator"; -+ /* -+ * The GPIO cell must be last because acpi_gpiochip_add() calls -+ * acpi_dev_clear_dependencies() and the clk + regulators must -+ * be ready when this happens. -+ */ -+ cells[2].name = "tps68470-gpio"; -+ -+ ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, cells, 3, - NULL, 0, NULL); -+ kfree(cells); - break; - case DESIGNED_FOR_CHROMEOS: - ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, --- -2.34.0 - -From 63ac25978683d2ab14df13c54f566c1e49558d77 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:06 +0200 -Subject: [PATCH] platform/x86: int3472: Pass tps68470_regulator_platform_data - to the tps68470-regulator MFD-cell - -Pass tps68470_regulator_platform_data to the tps68470-regulator -MFD-cell, specifying the voltages of the various regulators and -tying the regulators to the sensor supplies so that sensors which use -the TPS68470 can find their regulators. - -Since the voltages and supply connections are board-specific, this -introduces a DMI matches int3472_tps68470_board_data struct which -contains the necessary per-board info. - -This per-board info also includes GPIO lookup information for the -sensor GPIOs which may be connected to the tps68470 gpios. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/Makefile | 2 +- - drivers/platform/x86/intel/int3472/tps68470.c | 43 +++++-- - drivers/platform/x86/intel/int3472/tps68470.h | 25 ++++ - .../x86/intel/int3472/tps68470_board_data.c | 118 ++++++++++++++++++ - 4 files changed, 180 insertions(+), 8 deletions(-) - create mode 100644 drivers/platform/x86/intel/int3472/tps68470.h - create mode 100644 drivers/platform/x86/intel/int3472/tps68470_board_data.c - -diff --git a/drivers/platform/x86/intel/int3472/Makefile b/drivers/platform/x86/intel/int3472/Makefile -index 4a4b2518ea16..ca56e7eea781 100644 ---- a/drivers/platform/x86/intel/int3472/Makefile -+++ b/drivers/platform/x86/intel/int3472/Makefile -@@ -1,4 +1,4 @@ - obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472_discrete.o \ - intel_skl_int3472_tps68470.o - intel_skl_int3472_discrete-y := discrete.o clk_and_regulator.o common.o --intel_skl_int3472_tps68470-y := tps68470.o common.o -+intel_skl_int3472_tps68470-y := tps68470.o tps68470_board_data.o common.o -diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -index 78e34e7b6969..aae24d228770 100644 ---- a/drivers/platform/x86/intel/int3472/tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -9,6 +9,7 @@ - #include - - #include "common.h" -+#include "tps68470.h" - - #define DESIGNED_FOR_CHROMEOS 1 - #define DESIGNED_FOR_WINDOWS 2 -@@ -100,6 +101,7 @@ static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) - static int skl_int3472_tps68470_probe(struct i2c_client *client) - { - struct acpi_device *adev = ACPI_COMPANION(&client->dev); -+ const struct int3472_tps68470_board_data *board_data; - struct tps68470_clk_platform_data clk_pdata = {}; - struct mfd_cell *cells; - struct regmap *regmap; -@@ -128,6 +130,12 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - device_type = skl_int3472_tps68470_calc_type(adev); - switch (device_type) { - case DESIGNED_FOR_WINDOWS: -+ board_data = int3472_tps68470_get_board_data(dev_name(&client->dev)); -+ if (!board_data) { -+ dev_err(&client->dev, "No board-data found for this laptop/tablet model\n"); -+ return -ENODEV; -+ } -+ - cells = kcalloc(3, sizeof(*cells), GFP_KERNEL); - if (!cells) - return -ENOMEM; -@@ -136,6 +144,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - cells[0].platform_data = &clk_pdata; - cells[0].pdata_size = sizeof(clk_pdata); - cells[1].name = "tps68470-regulator"; -+ cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; -+ cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); - /* - * The GPIO cell must be last because acpi_gpiochip_add() calls - * acpi_dev_clear_dependencies() and the clk + regulators must -@@ -143,9 +153,15 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - */ - cells[2].name = "tps68470-gpio"; - -+ gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_table); -+ - ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, cells, 3, - NULL, 0, NULL); - kfree(cells); -+ -+ if (ret) -+ gpiod_remove_lookup_table(board_data->tps68470_gpio_lookup_table); -+ - break; - case DESIGNED_FOR_CHROMEOS: - ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, -@@ -160,18 +176,31 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - return ret; - } - -+static int skl_int3472_tps68470_remove(struct i2c_client *client) -+{ -+ const struct int3472_tps68470_board_data *board_data; -+ -+ board_data = int3472_tps68470_get_board_data(dev_name(&client->dev)); -+ if (board_data) -+ gpiod_remove_lookup_table(board_data->tps68470_gpio_lookup_table); -+ -+ return 0; -+} -+ -+ - static const struct acpi_device_id int3472_device_id[] = { -- { "INT3472", 0 }, -- { } -+ { "INT3472", 0 }, -+ { } - }; - MODULE_DEVICE_TABLE(acpi, int3472_device_id); - - static struct i2c_driver int3472_tps68470 = { -- .driver = { -- .name = "int3472-tps68470", -- .acpi_match_table = int3472_device_id, -- }, -- .probe_new = skl_int3472_tps68470_probe, -+ .driver = { -+ .name = "int3472-tps68470", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe_new = skl_int3472_tps68470_probe, -+ .remove = skl_int3472_tps68470_remove, - }; - module_i2c_driver(int3472_tps68470); - -diff --git a/drivers/platform/x86/intel/int3472/tps68470.h b/drivers/platform/x86/intel/int3472/tps68470.h -new file mode 100644 -index 000000000000..cfd33eb62740 ---- /dev/null -+++ b/drivers/platform/x86/intel/int3472/tps68470.h -@@ -0,0 +1,25 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* -+ * TI TPS68470 PMIC platform data definition. -+ * -+ * Copyright (c) 2021 Red Hat Inc. -+ * -+ * Red Hat authors: -+ * Hans de Goede -+ */ -+ -+#ifndef _INTEL_SKL_INT3472_TPS68470_H -+#define _INTEL_SKL_INT3472_TPS68470_H -+ -+struct gpiod_lookup_table; -+struct tps68470_regulator_platform_data; -+ -+struct int3472_tps68470_board_data { -+ const char *dev_name; -+ struct gpiod_lookup_table *tps68470_gpio_lookup_table; -+ const struct tps68470_regulator_platform_data *tps68470_regulator_pdata; -+}; -+ -+const struct int3472_tps68470_board_data *int3472_tps68470_get_board_data(const char *dev_name); -+ -+#endif -diff --git a/drivers/platform/x86/intel/int3472/tps68470_board_data.c b/drivers/platform/x86/intel/int3472/tps68470_board_data.c -new file mode 100644 -index 000000000000..96954a789bb8 ---- /dev/null -+++ b/drivers/platform/x86/intel/int3472/tps68470_board_data.c -@@ -0,0 +1,118 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * TI TPS68470 PMIC platform data definition. -+ * -+ * Copyright (c) 2021 Dan Scally -+ * Copyright (c) 2021 Red Hat Inc. -+ * -+ * Red Hat authors: -+ * Hans de Goede -+ */ -+ -+#include -+#include -+#include -+#include -+#include "tps68470.h" -+ -+static struct regulator_consumer_supply int347a_core_consumer_supplies[] = { -+ REGULATOR_SUPPLY("dvdd", "i2c-INT347A:00"), -+}; -+ -+static struct regulator_consumer_supply int347a_ana_consumer_supplies[] = { -+ REGULATOR_SUPPLY("avdd", "i2c-INT347A:00"), -+}; -+ -+static struct regulator_consumer_supply int347a_vsio_consumer_supplies[] = { -+ REGULATOR_SUPPLY("dovdd", "i2c-INT347A:00"), -+}; -+ -+static const struct regulator_init_data surface_go_tps68470_core_reg_init_data = { -+ .constraints = { -+ .min_uV = 1200000, -+ .max_uV = 1200000, -+ .apply_uV = 1, -+ .valid_ops_mask = REGULATOR_CHANGE_STATUS, -+ }, -+ .num_consumer_supplies = ARRAY_SIZE(int347a_core_consumer_supplies), -+ .consumer_supplies = int347a_core_consumer_supplies, -+}; -+ -+static const struct regulator_init_data surface_go_tps68470_ana_reg_init_data = { -+ .constraints = { -+ .min_uV = 2815200, -+ .max_uV = 2815200, -+ .apply_uV = 1, -+ .valid_ops_mask = REGULATOR_CHANGE_STATUS, -+ }, -+ .num_consumer_supplies = ARRAY_SIZE(int347a_ana_consumer_supplies), -+ .consumer_supplies = int347a_ana_consumer_supplies, -+}; -+ -+static const struct regulator_init_data surface_go_tps68470_vsio_reg_init_data = { -+ .constraints = { -+ .min_uV = 1800600, -+ .max_uV = 1800600, -+ .apply_uV = 1, -+ .valid_ops_mask = REGULATOR_CHANGE_STATUS, -+ }, -+ .num_consumer_supplies = ARRAY_SIZE(int347a_vsio_consumer_supplies), -+ .consumer_supplies = int347a_vsio_consumer_supplies, -+}; -+ -+static const struct tps68470_regulator_platform_data surface_go_tps68470_pdata = { -+ .reg_init_data = { -+ [TPS68470_CORE] = &surface_go_tps68470_core_reg_init_data, -+ [TPS68470_ANA] = &surface_go_tps68470_ana_reg_init_data, -+ [TPS68470_VSIO] = &surface_go_tps68470_vsio_reg_init_data, -+ }, -+}; -+ -+static struct gpiod_lookup_table surface_go_tps68470_gpios = { -+ .dev_id = "i2c-INT347A:00", -+ .table = { -+ GPIO_LOOKUP("tps68470-gpio", 9, "reset", GPIO_ACTIVE_LOW), -+ GPIO_LOOKUP("tps68470-gpio", 7, "powerdown", GPIO_ACTIVE_LOW) -+ } -+}; -+ -+static const struct int3472_tps68470_board_data surface_go_tps68470_board_data = { -+ .dev_name = "i2c-INT3472:05", -+ .tps68470_gpio_lookup_table = &surface_go_tps68470_gpios, -+ .tps68470_regulator_pdata = &surface_go_tps68470_pdata, -+}; -+ -+static const struct dmi_system_id int3472_tps68470_board_data_table[] = { -+ { -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Go"), -+ }, -+ .driver_data = (void *)&surface_go_tps68470_board_data, -+ }, -+ { -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Go 2"), -+ }, -+ .driver_data = (void *)&surface_go_tps68470_board_data, -+ }, -+ { } -+}; -+ -+const struct int3472_tps68470_board_data *int3472_tps68470_get_board_data(const char *dev_name) -+{ -+ const struct int3472_tps68470_board_data *board_data; -+ const struct dmi_system_id *match; -+ -+ match = dmi_first_match(int3472_tps68470_board_data_table); -+ while (match) { -+ board_data = match->driver_data; -+ if (strcmp(board_data->dev_name, dev_name) == 0) -+ return board_data; -+ -+ dmi_first_match(++match); -+ } -+ -+ return NULL; -+} --- -2.34.0 - -From d6ccffda8953c4027933bd1d9a8e8d08794d737b Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:07 +0200 -Subject: [PATCH] platform/x86: int3472: Deal with probe ordering issues - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around this info missing from the ACPI tables on devices where -the int3472 driver is used, the int3472 MFD-cell drivers attach info about -consumers to the clks/regulators when registering these. - -This causes problems with the probe ordering wrt drivers for consumers -of these clks/regulators. Since the lookups are only registered when the -provider-driver binds, trying to get these clks/regulators before then -results in a -ENOENT error for clks and a dummy regulator for regulators. - -All the sensor ACPI fw-nodes have a _DEP dependency on the INT3472 ACPI -fw-node, so to work around these probe ordering issues the ACPI core / -i2c-code does not instantiate the I2C-clients for any ACPI devices -which have a _DEP dependency on an INT3472 ACPI device until all -_DEP-s are met. - -This relies on acpi_dev_clear_dependencies() getting called by the driver -for the _DEP-s when they are ready, add a acpi_dev_clear_dependencies() -call to the discrete.c probe code. - -In the tps68470 case calling acpi_dev_clear_dependencies() is already done -by the acpi_gpiochip_add() call done by the driver for the GPIO MFD cell -(The GPIO cell is deliberately the last cell created to make sure the -clk + regulator cells are already instantiated when this happens). - -However for proper probe ordering, the clk/regulator cells must not just -be instantiated the must be fully ready (the clks + regulators must be -registered with their subsystems). - -Add MODULE_SOFTDEP dependencies for the clk and regulator drivers for -the instantiated MFD-cells so that these are loaded before us and so -that they bind immediately when the platform-devs are instantiated. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/discrete.c | 1 + - drivers/platform/x86/intel/int3472/tps68470.c | 6 ++++++ - 2 files changed, 7 insertions(+) - -diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c -index 22a4894f1cc7..3e1d755b3f83 100644 ---- a/drivers/platform/x86/intel/int3472/discrete.c -+++ b/drivers/platform/x86/intel/int3472/discrete.c -@@ -380,6 +380,7 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) - return ret; - } - -+ acpi_dev_clear_dependencies(adev); - return 0; - } - -diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -index aae24d228770..21c6c1a6edfc 100644 ---- a/drivers/platform/x86/intel/int3472/tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -173,6 +173,11 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - return device_type; - } - -+ /* -+ * No acpi_dev_clear_dependencies() here, since the acpi_gpiochip_add() -+ * for the GPIO cell already does this. -+ */ -+ - return ret; - } - -@@ -207,3 +212,4 @@ module_i2c_driver(int3472_tps68470); - MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI TPS68470 Device Driver"); - MODULE_AUTHOR("Daniel Scally "); - MODULE_LICENSE("GPL v2"); -+MODULE_SOFTDEP("pre: clk-tps68470 tps68470-regulator"); --- -2.34.0 - -From 64c49983fc05d4f61403f89c1640710efcaf6373 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 22 Jul 2021 00:20:46 +0100 -Subject: [PATCH] Revert "media: device property: Call - fwnode_graph_get_endpoint_by_id() for fwnode->secondary" - -This reverts commit acd418bfcfc415cf5e6414b6d1c6acfec850f290. Checking for -endpoints against fwnode->secondary in fwnode_graph_get_next_endpoint() is -a better way to do this since that function is also used in a bunch of -other places, for instance sensor drivers checking that they do have an -endpoint connected during probe. - -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/base/property.c | 9 +-------- - 1 file changed, 1 insertion(+), 8 deletions(-) - -diff --git a/drivers/base/property.c b/drivers/base/property.c -index d0874f6c29bb..4d7ff55df95d 100644 ---- a/drivers/base/property.c -+++ b/drivers/base/property.c -@@ -1212,14 +1212,7 @@ fwnode_graph_get_endpoint_by_id(const struct fwnode_handle *fwnode, - best_ep_id = fwnode_ep.id; - } - -- if (best_ep) -- return best_ep; -- -- if (fwnode && !IS_ERR_OR_NULL(fwnode->secondary)) -- return fwnode_graph_get_endpoint_by_id(fwnode->secondary, port, -- endpoint, flags); -- -- return NULL; -+ return best_ep; - } - EXPORT_SYMBOL_GPL(fwnode_graph_get_endpoint_by_id); - --- -2.34.0 - -From 4a3ae1cf1b9f54a434dfc7bfbf9cb9a28a311133 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 18 Jul 2021 23:52:42 +0100 -Subject: [PATCH] device property: Check fwnode->secondary in - fwnode_graph_get_next_endpoint() - -Sensor drivers often check for an endpoint to make sure that they're -connected to a consuming device like a CIO2 during .probe(). Some of -those endpoints might be in the form of software_nodes assigned as -a secondary to the device's fwnode_handle. Account for this possibility -in fwnode_graph_get_next_endpoint() to avoid having to do it in the -sensor drivers themselves. - -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/base/property.c | 21 ++++++++++++++++++++- - 1 file changed, 20 insertions(+), 1 deletion(-) - -diff --git a/drivers/base/property.c b/drivers/base/property.c -index 4d7ff55df95d..453918eb7390 100644 ---- a/drivers/base/property.c -+++ b/drivers/base/property.c -@@ -1033,7 +1033,26 @@ struct fwnode_handle * - fwnode_graph_get_next_endpoint(const struct fwnode_handle *fwnode, - struct fwnode_handle *prev) - { -- return fwnode_call_ptr_op(fwnode, graph_get_next_endpoint, prev); -+ const struct fwnode_handle *parent; -+ struct fwnode_handle *ep; -+ -+ /* -+ * If this function is in a loop and the previous iteration returned -+ * an endpoint from fwnode->secondary, then we need to use the secondary -+ * as parent rather than @fwnode. -+ */ -+ if (prev) -+ parent = fwnode_graph_get_port_parent(prev); -+ else -+ parent = fwnode; -+ -+ ep = fwnode_call_ptr_op(parent, graph_get_next_endpoint, prev); -+ -+ if (IS_ERR_OR_NULL(ep) && -+ !IS_ERR_OR_NULL(parent) && !IS_ERR_OR_NULL(parent->secondary)) -+ ep = fwnode_graph_get_next_endpoint(parent->secondary, NULL); -+ -+ return ep; - } - EXPORT_SYMBOL_GPL(fwnode_graph_get_next_endpoint); - --- -2.34.0 - -From cdfc01465ee94ccc1a18afca99de9e63cc9dae34 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 4 Nov 2021 21:46:27 +0000 -Subject: [PATCH] media: i2c: Add integration time margin to ov8865 - -Without this integration margin to reduce the max exposure, it seems -that we trip over a limit that results in the image being entirely -black when max exposure is set. Add the margin to prevent this issue. - -With thanks to jhautbois for spotting and reporting. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 4 +++- - 1 file changed, 3 insertions(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index d5af8aedf5e8..966487e32bfe 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -143,6 +143,7 @@ - #define OV8865_EXPOSURE_CTRL_L_REG 0x3502 - #define OV8865_EXPOSURE_CTRL_L(v) ((v) & GENMASK(7, 0)) - #define OV8865_EXPOSURE_GAIN_MANUAL_REG 0x3503 -+#define OV8865_INTEGRATION_TIME_MARGIN 8 - - #define OV8865_GAIN_CTRL_H_REG 0x3508 - #define OV8865_GAIN_CTRL_H(v) (((v) & GENMASK(12, 8)) >> 8) -@@ -2462,7 +2463,8 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - if (ctrl->id == V4L2_CID_VBLANK) { - int exposure_max; - -- exposure_max = sensor->state.mode->output_size_y + ctrl->val; -+ exposure_max = sensor->state.mode->output_size_y + ctrl->val - -+ OV8865_INTEGRATION_TIME_MARGIN; - __v4l2_ctrl_modify_range(sensor->ctrls.exposure, - sensor->ctrls.exposure->minimum, - exposure_max, --- -2.34.0 - -From 65117e827b931d5b5b62bc8428ee3543efbb422c Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 4 Nov 2021 21:48:38 +0000 -Subject: [PATCH] media: i2c: Fix max gain in ov8865 - -The maximum gain figure in the v4l2 ctrl is wrong. The field is 12 bits -wide, which is where the 8191 figure comes from, but the datasheet is -specific that maximum gain is 16x (the low seven bits are fractional, so -16x gain is 2048) - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 966487e32bfe..6c78edb65d1e 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2535,7 +2535,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Gain */ - -- v4l2_ctrl_new_std(handler, ops, V4L2_CID_ANALOGUE_GAIN, 128, 8191, 128, -+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_ANALOGUE_GAIN, 128, 2048, 128, - 128); - - /* White Balance */ --- -2.34.0 - diff --git a/patches/5.14/0011-amd-gpio.patch b/patches/5.14/0011-amd-gpio.patch deleted file mode 100644 index be3ddd47d..000000000 --- a/patches/5.14/0011-amd-gpio.patch +++ /dev/null @@ -1,109 +0,0 @@ -From 67dc820f8128554bf33bf737be1b28a9fb7178a3 Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Sat, 29 May 2021 17:47:38 +1000 -Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 - override - -This patch is the work of Thomas Gleixner and is -copied from: -https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ - -This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin -setup that is missing in the laptops ACPI table. - -This patch was used for validation of the issue, and is not a proper -fix, but is probably a better temporary hack than continuing to probe -the Legacy PIC and run with the PIC in an unknown state. - -Patchset: amd-gpio ---- - arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ - 1 file changed, 17 insertions(+) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index e55e0c1fad8c..46dfad41b401 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -22,6 +22,7 @@ - #include - #include - #include -+#include - - #include - #include -@@ -1143,6 +1144,17 @@ static void __init mp_config_acpi_legacy_irqs(void) - } - } - -+static const struct dmi_system_id surface_quirk[] __initconst = { -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") -+ }, -+ }, -+ {} -+}; -+ - /* - * Parse IOAPIC related entries in MADT - * returns 0 on success, < 0 on error -@@ -1198,6 +1210,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) - acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, - acpi_gbl_FADT.sci_interrupt); - -+ if (dmi_check_system(surface_quirk)) { -+ pr_warn("Surface hack: Override irq 7\n"); -+ mp_override_legacy_irq(7, 3, 3, 7); -+ } -+ - /* Fill in identity legacy mappings where no override */ - mp_config_acpi_legacy_irqs(); - --- -2.34.0 - -From 203d2a88203f6e8648ec869f9a017dc21c17cab8 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 3 Jun 2021 14:04:26 +0200 -Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override - quirk - -The 13" version of the Surface Laptop 4 has the same problem as the 15" -version, but uses a different SKU. Add that SKU to the quirk as well. - -Patchset: amd-gpio ---- - arch/x86/kernel/acpi/boot.c | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 46dfad41b401..78bf6a097dc5 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -1146,12 +1146,19 @@ static void __init mp_config_acpi_legacy_irqs(void) - - static const struct dmi_system_id surface_quirk[] __initconst = { - { -- .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") - }, - }, -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") -+ }, -+ }, - {} - }; - --- -2.34.0 - diff --git a/patches/5.14/0012-misc-fixes.patch b/patches/5.14/0012-misc-fixes.patch deleted file mode 100644 index 1458ed288..000000000 --- a/patches/5.14/0012-misc-fixes.patch +++ /dev/null @@ -1,70 +0,0 @@ -From 417141a5157f7982f13fdd717ef4e1e7ebec6dbf Mon Sep 17 00:00:00 2001 -From: Mathias Nyman -Date: Fri, 29 Oct 2021 15:51:54 +0300 -Subject: [PATCH] xhci: Fix commad ring abort, write all 64 bits to CRCR - register. - -Turns out some xHC controllers require all 64 bits in the CRCR register -to be written to execute a command abort. - -The lower 32 bits containing the command abort bit is written first. -In case the command ring stops before we write the upper 32 bits then -hardware may use these upper bits to set the commnd ring dequeue pointer. - -Solve this by making sure the upper 32 bits contain a valid command -ring dequeue pointer. - -The original patch that only wrote the first 32 to stop the ring went -to stable, so this fix should go there as well. - -Fixes: ff0e50d3564f ("xhci: Fix command ring pointer corruption while aborting a command") -Cc: stable@vger.kernel.org -Signed-off-by: Mathias Nyman -Patchset: misc-fixes ---- - drivers/usb/host/xhci-ring.c | 21 ++++++++++++++------- - 1 file changed, 14 insertions(+), 7 deletions(-) - -diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c -index 8fedf1bf292b..3dd5929b67f0 100644 ---- a/drivers/usb/host/xhci-ring.c -+++ b/drivers/usb/host/xhci-ring.c -@@ -366,7 +366,9 @@ static void xhci_handle_stopped_cmd_ring(struct xhci_hcd *xhci, - /* Must be called with xhci->lock held, releases and aquires lock back */ - static int xhci_abort_cmd_ring(struct xhci_hcd *xhci, unsigned long flags) - { -- u32 temp_32; -+ struct xhci_segment *new_seg = xhci->cmd_ring->deq_seg; -+ union xhci_trb *new_deq = xhci->cmd_ring->dequeue; -+ u64 crcr; - int ret; - - xhci_dbg(xhci, "Abort command ring\n"); -@@ -375,13 +377,18 @@ static int xhci_abort_cmd_ring(struct xhci_hcd *xhci, unsigned long flags) - - /* - * The control bits like command stop, abort are located in lower -- * dword of the command ring control register. Limit the write -- * to the lower dword to avoid corrupting the command ring pointer -- * in case if the command ring is stopped by the time upper dword -- * is written. -+ * dword of the command ring control register. -+ * Some controllers require all 64 bits to be written to abort the ring. -+ * Make sure the upper dword is valid, pointing to the next command, -+ * avoiding corrupting the command ring pointer in case the command ring -+ * is stopped by the time the upper dword is written. - */ -- temp_32 = readl(&xhci->op_regs->cmd_ring); -- writel(temp_32 | CMD_RING_ABORT, &xhci->op_regs->cmd_ring); -+ next_trb(xhci, NULL, &new_seg, &new_deq); -+ if (trb_is_link(new_deq)) -+ next_trb(xhci, NULL, &new_seg, &new_deq); -+ -+ crcr = xhci_trb_virt_to_dma(new_seg, new_deq); -+ xhci_write_64(xhci, crcr | CMD_RING_ABORT, &xhci->op_regs->cmd_ring); - - /* Section 4.6.1.2 of xHCI 1.0 spec says software should also time the - * completion of the Command Abort operation. If CRR is not negated in 5 --- -2.34.0 - diff --git a/patches/5.15/0001-surface3-oemb.patch b/patches/5.15/0001-surface3-oemb.patch deleted file mode 100644 index a1e01af09..000000000 --- a/patches/5.15/0001-surface3-oemb.patch +++ /dev/null @@ -1,101 +0,0 @@ -From 221e1d5986c085f98f4487d48bd222d5c050b568 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 18 Oct 2020 16:42:44 +0900 -Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI - table - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI data into dmi_system_id tables used -for quirks so that each driver can enable quirks even on the affected -systems. - -On affected systems, DMI data will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: surface3-oemb ---- - drivers/platform/surface/surface3-wmi.c | 7 +++++++ - sound/soc/codecs/rt5645.c | 9 +++++++++ - sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ - 3 files changed, 24 insertions(+) - -diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c -index fcd1d4fb94d5..ee26a5998b07 100644 ---- a/drivers/platform/surface/surface3-wmi.c -+++ b/drivers/platform/surface/surface3-wmi.c -@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - #endif - { } - }; -diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c -index 9408ee63cb26..5cac83953901 100644 ---- a/sound/soc/codecs/rt5645.c -+++ b/sound/soc/codecs/rt5645.c -@@ -3718,6 +3718,15 @@ static const struct dmi_system_id dmi_platform_data[] = { - }, - .driver_data = (void *)&intel_braswell_platform_data, - }, -+ { -+ .ident = "Microsoft Surface 3", -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)&intel_braswell_platform_data, -+ }, - { - /* - * Match for the GPDwin which unfortunately uses somewhat -diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -index 227424236fd5..1013a57be89a 100644 ---- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c -+++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .callback = cht_surface_quirk_cb, -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - { } - }; - --- -2.34.1 - diff --git a/patches/5.15/0002-mwifiex.patch b/patches/5.15/0002-mwifiex.patch deleted file mode 100644 index d3a80ee00..000000000 --- a/patches/5.15/0002-mwifiex.patch +++ /dev/null @@ -1,1862 +0,0 @@ -From 1025349f89c7c29571b114f93a0f59d9a1ec74de Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:32:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_wsid quirk for Surface 3 - -This commit adds reset_wsid quirk and uses this quirk for Surface 3 on -card reset. - -To reset mwifiex on Surface 3, it seems that calling the _DSM method -exists in \_SB.WSID [1] device is required. - -On Surface 3, calling the _DSM method removes/re-probes the card by -itself. So, need to place the reset function before performing FLR and -skip performing any other reset-related works. - -Note that Surface Pro 3 also has the WSID device [2], but it seems to need -more work. This commit only supports Surface 3 yet. - -[1] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_3/dsdt.dsl#L11947-L12011 -[2] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_pro_3/dsdt.dsl#L12164-L12216 - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 10 +++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 83 +++++++++++++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.h | 6 ++ - 3 files changed, 99 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index c3f5583ea70d..3f5138008594 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -2993,6 +2993,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - -+ /* On Surface 3, reset_wsid method removes then re-probes card by -+ * itself. So, need to place it here and skip performing any other -+ * reset-related works. -+ */ -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) { -+ mwifiex_pcie_reset_wsid_quirk(card->dev); -+ /* skip performing any other reset-related works */ -+ return; -+ } -+ - /* We can't afford to wait here; remove() might be waiting on us. If we - * can't grab the device lock, maybe we'll get another chance later. - */ -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 0234cf3c2974..563dd0d5ac79 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -15,10 +15,21 @@ - * this warranty disclaimer. - */ - -+#include - #include - - #include "pcie_quirks.h" - -+/* For reset_wsid quirk */ -+#define ACPI_WSID_PATH "\\_SB.WSID" -+#define WSID_REV 0x0 -+#define WSID_FUNC_WIFI_PWR_OFF 0x1 -+#define WSID_FUNC_WIFI_PWR_ON 0x2 -+/* WSID _DSM UUID: "534ea3bf-fcc2-4e7a-908f-a13978f0c7ef" */ -+static const guid_t wsid_dsm_guid = -+ GUID_INIT(0x534ea3bf, 0xfcc2, 0x4e7a, -+ 0x90, 0x8f, 0xa1, 0x39, 0x78, 0xf0, 0xc7, 0xef); -+ - /* quirk table based on DMI matching */ - static const struct dmi_system_id mwifiex_quirk_table[] = { - { -@@ -87,6 +98,14 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - {} - }; - -@@ -103,6 +122,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - dev_info(&pdev->dev, "no quirks enabled\n"); - if (card->quirks & QUIRK_FW_RST_D3COLD) - dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) -+ dev_info(&pdev->dev, -+ "quirk reset_wsid for Surface 3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -@@ -159,3 +181,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) - - return 0; - } -+ -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev) -+{ -+ acpi_handle handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n"); -+ -+ status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&pdev->dev, "No ACPI handle for path %s\n", -+ ACPI_WSID_PATH); -+ return -ENODEV; -+ } -+ -+ if (!acpi_has_method(handle, "_DSM")) { -+ dev_err(&pdev->dev, "_DSM method not found\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power off func\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power on func\n"); -+ return -ENODEV; -+ } -+ -+ /* card will be removed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi off...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi off\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ /* card will be re-probed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi on...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi on\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ return 0; -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 8ec4176d698f..25370c5a4f59 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -19,5 +19,11 @@ - - #define QUIRK_FW_RST_D3COLD BIT(0) - -+/* Surface 3 and Surface Pro 3 have the same _DSM method but need to -+ * be handled differently. Currently, only S3 is supported. -+ */ -+#define QUIRK_FW_RST_WSID_S3 BIT(1) -+ - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); --- -2.34.1 - -From 40e111c42888b16e5a22bfd939de86ce584a37b3 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Wed, 30 Sep 2020 18:08:24 +0900 -Subject: [PATCH] mwifiex: pcie: (OEMB) add quirk for Surface 3 with broken DMI - table - -(made referring to http://git.osdn.net/view?p=android-x86/kernel.git;a=commitdiff;h=18e2e857c57633b25b3b4120f212224a108cd883) - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI info for the affected Surface 3. - -On affected systems, DMI info will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie_quirks.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 563dd0d5ac79..32e2f000e57b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -106,6 +106,15 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - {} - }; - --- -2.34.1 - -From f1230ee0fe882b8b4a966daf0b53edf3fd2ccd08 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 4 Oct 2020 00:11:49 +0900 -Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ - -Currently, mwifiex fw will crash after suspend on recent kernel series. -On Windows, it seems that the root port of wifi will never enter D3 state -(stay on D0 state). And on Linux, disabling the D3 state for the -bridge fixes fw crashing after suspend. - -This commit disables the D3 state of root port on driver initialization -and fixes fw crashing after suspend. - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 27 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 3f5138008594..372dde99725c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -380,6 +380,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) - { - struct pcie_service_card *card; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - int ret; - - pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", -@@ -421,6 +422,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return -1; - } - -+ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing -+ * after suspend -+ */ -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ parent_pdev->bridge_d3 = false; -+ - return 0; - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 32e2f000e57b..356401bab59c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -38,7 +38,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5", -@@ -47,7 +48,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -56,7 +58,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 6", -@@ -64,7 +67,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 1", -@@ -72,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 2", -@@ -80,7 +85,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 1", -@@ -88,7 +94,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 2", -@@ -96,7 +103,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface 3", -@@ -134,6 +142,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_FW_RST_WSID_S3) - dev_info(&pdev->dev, - "quirk reset_wsid for Surface 3 enabled\n"); -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ dev_info(&pdev->dev, -+ "quirk no_brigde_d3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 25370c5a4f59..a1de111ad1db 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -23,6 +23,7 @@ - * be handled differently. Currently, only S3 is supported. - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) -+#define QUIRK_NO_BRIDGE_D3 BIT(2) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.34.1 - -From 72627e5f3f10fb8c7e639360c19ff54968e1a99b Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 3 Nov 2020 13:28:04 +0100 -Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface - devices - -The most recent firmware of the 88W8897 card reports a hardcoded LTR -value to the system during initialization, probably as an (unsuccessful) -attempt of the developers to fix firmware crashes. This LTR value -prevents most of the Microsoft Surface devices from entering deep -powersaving states (either platform C-State 10 or S0ix state), because -the exit latency of that state would be higher than what the card can -tolerate. - -Turns out the card works just the same (including the firmware crashes) -no matter if that hardcoded LTR value is reported or not, so it's kind -of useless and only prevents us from saving power. - -To get rid of those hardcoded LTR reports, it's possible to reset the -PCI bridge device after initializing the cards firmware. I'm not exactly -sure why that works, maybe the power management subsystem of the PCH -resets its stored LTR values when doing a function level reset of the -bridge device. Doing the reset once after starting the wifi firmware -works very well, probably because the firmware only reports that LTR -value a single time during firmware startup. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 31 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 372dde99725c..586c79dc0a98 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -1781,9 +1781,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) - static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; -+ struct pci_dev *pdev = card->dev; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; - int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; - -+ /* Trigger a function level reset of the PCI bridge device, this makes -+ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value -+ * that prevents the system from entering package C10 and S0ix powersaving -+ * states. -+ * We need to do it here because it must happen after firmware -+ * initialization and this function is called after that is done. -+ */ -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ pci_reset_function(parent_pdev); -+ - /* Write the RX ring read pointer in to reg->rx_rdptr */ - if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | - tx_wrap)) { -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 356401bab59c..6437f067d07a 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -39,7 +39,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5", -@@ -49,7 +50,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -59,7 +61,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 6", -@@ -68,7 +71,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 1", -@@ -77,7 +81,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 2", -@@ -86,7 +91,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 1", -@@ -95,7 +101,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 2", -@@ -104,7 +111,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface 3", -@@ -145,6 +153,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_NO_BRIDGE_D3) - dev_info(&pdev->dev, - "quirk no_brigde_d3 enabled\n"); -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index a1de111ad1db..0e429779bb04 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -24,6 +24,7 @@ - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) - #define QUIRK_NO_BRIDGE_D3 BIT(2) -+#define QUIRK_DO_FLR_ON_BRIDGE BIT(3) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.34.1 - -From a2d358f0d1db22a04e34ee5386f082896d27cca8 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 25 Mar 2021 11:33:02 +0100 -Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell - 88W8897 - -The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) -is used in a lot of Microsoft Surface devices, and all those devices -suffer from very low 2.4GHz wifi connection speeds while bluetooth is -enabled. The reason for that is that the default passive scanning -interval for Bluetooth Low Energy devices is quite high in Linux -(interval of 60 msec and scan window of 30 msec, see hci_core.c), and -the Marvell chip is known for its bad bt+wifi coexisting performance. - -So decrease that passive scan interval and make the scan window shorter -on this particular device to allow for spending more time transmitting -wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and -the new scan window is 6.25 msec (0xa * 0,625 msec). - -This change has a very large impact on the 2.4GHz wifi speeds and gets -it up to performance comparable with the Windows driver, which seems to -apply a similar quirk. - -The interval and window length were tested and found to work very well -with a lot of Bluetooth Low Energy devices, including the Surface Pen, a -Bluetooth Speaker and two modern Bluetooth headphones. All devices were -discovered immediately after turning them on. Even lower values were -also tested, but they introduced longer delays until devices get -discovered. - -Patchset: mwifiex ---- - drivers/bluetooth/btusb.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index 79d0db542da3..ec96f3e7ad33 100644 ---- a/drivers/bluetooth/btusb.c -+++ b/drivers/bluetooth/btusb.c -@@ -60,6 +60,7 @@ static struct usb_driver btusb_driver; - #define BTUSB_VALID_LE_STATES 0x800000 - #define BTUSB_QCA_WCN6855 0x1000000 - #define BTUSB_INTEL_BROKEN_INITIAL_NCMD 0x4000000 -+#define BTUSB_LOWER_LESCAN_INTERVAL 0x8000000 - - static const struct usb_device_id btusb_table[] = { - /* Generic Bluetooth USB device */ -@@ -356,6 +357,7 @@ static const struct usb_device_id blacklist_table[] = { - { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, -+ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, - - /* Intel Bluetooth devices */ - { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, -@@ -3817,6 +3819,19 @@ static int btusb_probe(struct usb_interface *intf, - if (id->driver_info & BTUSB_MARVELL) - hdev->set_bdaddr = btusb_set_bdaddr_marvell; - -+ /* The Marvell 88W8897 combined wifi and bluetooth card is known for -+ * very bad bt+wifi coexisting performance. -+ * -+ * Decrease the passive BT Low Energy scan interval a bit -+ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter -+ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly -+ * higher wifi throughput while passively scanning for BT LE devices. -+ */ -+ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { -+ hdev->le_scan_interval = 0x0190; -+ hdev->le_scan_window = 0x000a; -+ } -+ - if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && - (id->driver_info & BTUSB_MEDIATEK)) { - hdev->setup = btusb_mtk_setup; --- -2.34.1 - -From 7e91f5ee8de1a56baea1d1abe116aa6ef824e68c Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 12:31:26 +0100 -Subject: [PATCH] mwifiex: Small cleanup for handling virtual interface type - changes - -Handle the obvious invalid virtual interface type changes with a general -check instead of looking at the individual change. - -For type changes from P2P_CLIENT to P2P_GO and the other way round, this -changes the behavior slightly: We now still do nothing, but return --EOPNOTSUPP instead of 0. Now that behavior was incorrect before and -still is, because type changes between these two types are actually -possible and supported, which we'll fix in a following commit. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 39 +++++++------------ - 1 file changed, 14 insertions(+), 25 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 97f0f39364d6..dd30d21edc01 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1145,6 +1145,20 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return -EBUSY; - } - -+ if (type == NL80211_IFTYPE_UNSPECIFIED) { -+ mwifiex_dbg(priv->adapter, INFO, -+ "%s: no new type specified, keeping old type %d\n", -+ dev->name, curr_iftype); -+ return 0; -+ } -+ -+ if (curr_iftype == type) { -+ mwifiex_dbg(priv->adapter, INFO, -+ "%s: interface already is of type %d\n", -+ dev->name, curr_iftype); -+ return 0; -+ } -+ - switch (curr_iftype) { - case NL80211_IFTYPE_ADHOC: - switch (type) { -@@ -1164,12 +1178,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as IBSS\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_ADHOC: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1195,12 +1203,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as STA\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_STATION: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1218,12 +1220,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_P2P_GO: - return mwifiex_change_vif_to_p2p(dev, curr_iftype, - type, params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as AP\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_AP: /* This shouldn't happen */ -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", -@@ -1244,13 +1240,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); -- case NL80211_IFTYPE_UNSPECIFIED: -- mwifiex_dbg(priv->adapter, INFO, -- "%s: kept type as P2P\n", dev->name); -- fallthrough; -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- return 0; - default: - mwifiex_dbg(priv->adapter, ERROR, - "%s: changing to %d not supported\n", --- -2.34.1 - -From bd2cb93459c0740d29706da1a524051675a28fa6 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 10 Nov 2020 12:49:56 +0100 -Subject: [PATCH] mwifiex: Use non-posted PCI register writes - -On the 88W8897 card it's very important the TX ring write pointer is -updated correctly to its new value before setting the TX ready -interrupt, otherwise the firmware appears to crash (probably because -it's trying to DMA-read from the wrong place). - -Since PCI uses "posted writes" when writing to a register, it's not -guaranteed that a write will happen immediately. That means the pointer -might be outdated when setting the TX ready interrupt, leading to -firmware crashes especially when ASPM L1 and L1 substates are enabled -(because of the higher link latency, the write will probably take -longer). - -So fix those firmware crashes by always forcing non-posted writes. We do -that by simply reading back the register after writing it, just as a lot -of other drivers do. - -There are two reproducers that are fixed with this patch: - -1) During rx/tx traffic and with ASPM L1 substates enabled (the enabled -substates are platform dependent), the firmware crashes and eventually a -command timeout appears in the logs. That crash is fixed by using a -non-posted write in mwifiex_pcie_send_data(). - -2) When sending lots of commands to the card, waking it up from sleep in -very quick intervals, the firmware eventually crashes. That crash -appears to be fixed by some other non-posted write included here. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 586c79dc0a98..f87bc9bdfba7 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -238,6 +238,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) - - iowrite32(data, card->pci_mmap1 + reg); - -+ /* Do a read-back, which makes the write non-posted, ensuring the -+ * completion before returning. -+ * The firmware of the 88W8897 card is buggy and this avoids crashes. -+ */ -+ ioread32(card->pci_mmap1 + reg); -+ - return 0; - } - --- -2.34.1 - -From 49998f5502996379463c06e4887e3ca39a45bdde Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 12:44:39 +0100 -Subject: [PATCH] mwifiex: Use function to check whether interface type change - is allowed - -Instead of bailing out in the function which is supposed to do the type -change, detect invalid changes beforehand using a generic function and -return an error if the change is not allowed. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 139 ++++++++++++------ - 1 file changed, 92 insertions(+), 47 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index dd30d21edc01..e4d44705c827 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -943,6 +943,76 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, - return 0; - } - -+static bool -+is_vif_type_change_allowed(struct mwifiex_adapter *adapter, -+ enum nl80211_iftype old_iftype, -+ enum nl80211_iftype new_iftype) -+{ -+ switch (old_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_STATION: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_AP: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return adapter->curr_iface_comb.sta_intf != -+ adapter->iface_limit.sta_intf; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ return adapter->curr_iface_comb.p2p_intf != -+ adapter->iface_limit.p2p_intf; -+ default: -+ return false; -+ } -+ -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ -+ default: -+ break; -+ } -+ -+ return false; -+} -+ - static int - mwifiex_change_vif_to_p2p(struct net_device *dev, - enum nl80211_iftype curr_iftype, -@@ -959,13 +1029,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - - adapter = priv->adapter; - -- if (adapter->curr_iface_comb.p2p_intf == -- adapter->iface_limit.p2p_intf) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple P2P ifaces\n"); -- return -1; -- } -- - mwifiex_dbg(adapter, INFO, - "%s: changing role to p2p\n", dev->name); - -@@ -1031,15 +1094,6 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - - adapter = priv->adapter; - -- if ((curr_iftype != NL80211_IFTYPE_P2P_CLIENT && -- curr_iftype != NL80211_IFTYPE_P2P_GO) && -- (adapter->curr_iface_comb.sta_intf == -- adapter->iface_limit.sta_intf)) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple station/adhoc ifaces\n"); -- return -1; -- } -- - if (type == NL80211_IFTYPE_STATION) - mwifiex_dbg(adapter, INFO, - "%s: changing role to station\n", dev->name); -@@ -1090,13 +1144,6 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - - adapter = priv->adapter; - -- if (adapter->curr_iface_comb.uap_intf == -- adapter->iface_limit.uap_intf) { -- mwifiex_dbg(adapter, ERROR, -- "cannot create multiple AP ifaces\n"); -- return -1; -- } -- - mwifiex_dbg(adapter, INFO, - "%s: changing role to AP\n", dev->name); - -@@ -1159,6 +1206,13 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return 0; - } - -+ if (!is_vif_type_change_allowed(priv->adapter, curr_iftype, type)) { -+ mwifiex_dbg(priv->adapter, ERROR, -+ "%s: change from type %d to %d is not allowed\n", -+ dev->name, curr_iftype, type); -+ return -EOPNOTSUPP; -+ } -+ - switch (curr_iftype) { - case NL80211_IFTYPE_ADHOC: - switch (type) { -@@ -1179,12 +1233,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_STATION: - switch (type) { - case NL80211_IFTYPE_ADHOC: -@@ -1204,12 +1255,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_AP: - switch (type) { - case NL80211_IFTYPE_ADHOC: -@@ -1221,12 +1269,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_p2p(dev, curr_iftype, - type, params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - case NL80211_IFTYPE_P2P_CLIENT: - case NL80211_IFTYPE_P2P_GO: - if (mwifiex_cfg80211_deinit_p2p(priv)) -@@ -1241,21 +1286,21 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: changing to %d not supported\n", -- dev->name, type); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } -- break; -+ - default: -- mwifiex_dbg(priv->adapter, ERROR, -- "%s: unknown iftype: %d\n", -- dev->name, dev->ieee80211_ptr->iftype); -- return -EOPNOTSUPP; -+ goto errnotsupp; - } - - - return 0; -+ -+errnotsupp: -+ mwifiex_dbg(priv->adapter, ERROR, -+ "unsupported interface type transition: %d to %d\n", -+ curr_iftype, type); -+ return -EOPNOTSUPP; - } - - static void --- -2.34.1 - -From 6784f13828e3746c7fee273ad42467e3166c47f4 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 14:42:54 +0100 -Subject: [PATCH] mwifiex: Use helper function for counting interface types - -Use a small helper function to increment and decrement the counter of -the interface types we currently manage. This makes the code that -actually changes and sets up the interface type a bit less messy and -also helps avoiding mistakes in case someone increments/decrements a -counter wrongly. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 110 ++++++------------ - 1 file changed, 35 insertions(+), 75 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index e4d44705c827..a688fd898564 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1013,6 +1013,32 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, - return false; - } - -+static void -+update_vif_type_counter(struct mwifiex_adapter *adapter, -+ enum nl80211_iftype iftype, -+ int change) -+{ -+ switch (iftype) { -+ case NL80211_IFTYPE_UNSPECIFIED: -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ adapter->curr_iface_comb.sta_intf += change; -+ break; -+ case NL80211_IFTYPE_AP: -+ adapter->curr_iface_comb.uap_intf += change; -+ break; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ case NL80211_IFTYPE_P2P_GO: -+ adapter->curr_iface_comb.p2p_intf += change; -+ break; -+ default: -+ mwifiex_dbg(adapter, ERROR, -+ "%s: Unsupported iftype passed: %d\n", -+ __func__, iftype); -+ break; -+ } -+} -+ - static int - mwifiex_change_vif_to_p2p(struct net_device *dev, - enum nl80211_iftype curr_iftype, -@@ -1060,19 +1086,8 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.p2p_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; - - return 0; -@@ -1111,20 +1126,10 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.sta_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; -+ - return 0; - } - -@@ -1157,20 +1162,8 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- switch (curr_iftype) { -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- default: -- break; -- } -- -- adapter->curr_iface_comb.uap_intf++; -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); - dev->ieee80211_ptr->iftype = type; - return 0; - } -@@ -3132,23 +3125,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, - mwifiex_dev_debugfs_init(priv); - #endif - -- switch (type) { -- case NL80211_IFTYPE_UNSPECIFIED: -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf++; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf++; -- break; -- case NL80211_IFTYPE_P2P_CLIENT: -- adapter->curr_iface_comb.p2p_intf++; -- break; -- default: -- /* This should be dead code; checked above */ -- mwifiex_dbg(adapter, ERROR, "type not supported\n"); -- return ERR_PTR(-EINVAL); -- } -+ update_vif_type_counter(adapter, type, +1); - - return &priv->wdev; - -@@ -3214,24 +3191,7 @@ int mwifiex_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) - /* Clear the priv in adapter */ - priv->netdev = NULL; - -- switch (priv->bss_mode) { -- case NL80211_IFTYPE_UNSPECIFIED: -- case NL80211_IFTYPE_STATION: -- case NL80211_IFTYPE_ADHOC: -- adapter->curr_iface_comb.sta_intf--; -- break; -- case NL80211_IFTYPE_AP: -- adapter->curr_iface_comb.uap_intf--; -- break; -- case NL80211_IFTYPE_P2P_CLIENT: -- case NL80211_IFTYPE_P2P_GO: -- adapter->curr_iface_comb.p2p_intf--; -- break; -- default: -- mwifiex_dbg(adapter, ERROR, -- "del_virtual_intf: type not supported\n"); -- break; -- } -+ update_vif_type_counter(adapter, priv->bss_mode, -1); - - priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; - --- -2.34.1 - -From cf24b8feb32c748f5e6942c779a8dfc3047ba9cb Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:56:58 +0100 -Subject: [PATCH] mwifiex: Update virtual interface counters right after - setting bss_type - -In mwifiex_init_new_priv_params() we update our private driver state to -reflect the currently selected virtual interface type. Most notably we -set the bss_mode to the mode we're going to put the firmware in. - -Now after we updated the driver state we actually start talking to the -firmware and instruct it to set up the new mode. Those commands can and -will sometimes fail, in which case we return with an error from -mwifiex_change_vif_to_*. We currently update our virtual interface type -counters after this return, which means the code is never reached when a -firmware error happens and we never update the counters. Since we have -updated our bss_mode earlier though, the counters now no longer reflect -the actual state of the driver. - -This will break things on the next virtual interface change, because the -virtual interface type we're switching away from didn't get its counter -incremented, and we end up decrementing a 0-counter. - -To fix this, simply update the virtual interface type counters right -after updating our driver structures, so that they are always in sync. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 25 +++++++++++-------- - 1 file changed, 14 insertions(+), 11 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index a688fd898564..2a938e8e0bb1 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1063,6 +1063,10 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; - -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - switch (type) { - case NL80211_IFTYPE_P2P_CLIENT: - if (mwifiex_cfg80211_init_p2p_client(priv)) -@@ -1086,10 +1090,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; -- - return 0; - } - -@@ -1120,16 +1120,17 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, - return -1; - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; -+ -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, - HostCmd_ACT_GEN_SET, 0, NULL, true)) - return -1; - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; -- - return 0; - } - -@@ -1156,15 +1157,17 @@ mwifiex_change_vif_to_ap(struct net_device *dev, - return -1; - if (mwifiex_init_new_priv_params(priv, dev, type)) - return -1; -+ -+ update_vif_type_counter(adapter, curr_iftype, -1); -+ update_vif_type_counter(adapter, type, +1); -+ dev->ieee80211_ptr->iftype = type; -+ - if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, - HostCmd_ACT_GEN_SET, 0, NULL, true)) - return -1; - if (mwifiex_sta_init_cmd(priv, false, false)) - return -1; - -- update_vif_type_counter(adapter, curr_iftype, -1); -- update_vif_type_counter(adapter, type, +1); -- dev->ieee80211_ptr->iftype = type; - return 0; - } - /* --- -2.34.1 - -From c10841e4255e5bfc6f175bd8d1fe12450eef1966 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 13:42:40 +0100 -Subject: [PATCH] mwifiex: Allow switching interface type from P2P_CLIENT to - P2P_GO - -It's possible to change virtual interface type between P2P_CLIENT and -P2P_GO, the card supports that just fine, and it happens for example -when using miracast with the miraclecast software. - -So allow type changes between P2P_CLIENT and P2P_GO and simply call into -mwifiex_change_vif_to_p2p(), which handles this just fine. We have to -call mwifiex_cfg80211_deinit_p2p() before though to make sure the old -p2p mode is properly uninitialized. - -Patchset: mwifiex ---- - .../net/wireless/marvell/mwifiex/cfg80211.c | 36 +++++++++++++++++++ - 1 file changed, 36 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 2a938e8e0bb1..2a3f9ebb3182 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -994,11 +994,26 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, - } - - case NL80211_IFTYPE_P2P_CLIENT: -+ switch (new_iftype) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return true; -+ case NL80211_IFTYPE_P2P_GO: -+ return true; -+ case NL80211_IFTYPE_AP: -+ return adapter->curr_iface_comb.uap_intf != -+ adapter->iface_limit.uap_intf; -+ default: -+ return false; -+ } -+ - case NL80211_IFTYPE_P2P_GO: - switch (new_iftype) { - case NL80211_IFTYPE_ADHOC: - case NL80211_IFTYPE_STATION: - return true; -+ case NL80211_IFTYPE_P2P_CLIENT: -+ return true; - case NL80211_IFTYPE_AP: - return adapter->curr_iface_comb.uap_intf != - adapter->iface_limit.uap_intf; -@@ -1269,6 +1284,24 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - } - - case NL80211_IFTYPE_P2P_CLIENT: -+ if (mwifiex_cfg80211_deinit_p2p(priv)) -+ return -EFAULT; -+ -+ switch (type) { -+ case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: -+ return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, -+ type, params); -+ case NL80211_IFTYPE_P2P_GO: -+ return mwifiex_change_vif_to_p2p(dev, curr_iftype, -+ type, params); -+ case NL80211_IFTYPE_AP: -+ return mwifiex_change_vif_to_ap(dev, curr_iftype, type, -+ params); -+ default: -+ goto errnotsupp; -+ } -+ - case NL80211_IFTYPE_P2P_GO: - if (mwifiex_cfg80211_deinit_p2p(priv)) - return -EFAULT; -@@ -1278,6 +1311,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); -+ case NL80211_IFTYPE_P2P_CLIENT: -+ return mwifiex_change_vif_to_p2p(dev, curr_iftype, -+ type, params); - case NL80211_IFTYPE_AP: - return mwifiex_change_vif_to_ap(dev, curr_iftype, type, - params); --- -2.34.1 - -From 56786eb390b49262fdab94e37ac8a91a036c6192 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Fri, 26 Mar 2021 15:31:08 +0100 -Subject: [PATCH] mwifiex: Handle interface type changes from AP to STATION - -Looks like this case was simply overseen, so handle it, too. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 2a3f9ebb3182..0eb31201a82b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -1272,6 +1272,7 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, - case NL80211_IFTYPE_AP: - switch (type) { - case NL80211_IFTYPE_ADHOC: -+ case NL80211_IFTYPE_STATION: - return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, - type, params); - break; --- -2.34.1 - -From 23d99b295859674f29ee130fe3c6cafb49c400c9 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sat, 27 Mar 2021 12:19:14 +0100 -Subject: [PATCH] mwifiex: Fix copy-paste mistake when creating virtual - interface - -The BSS priority here for a new P2P_CLIENT device was accidentally set -to an enum that's certainly not meant for this. Since -MWIFIEX_BSS_ROLE_STA is 0 anyway, we can just set the bss_priority to 0 -instead here. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 0eb31201a82b..d62a20de3ada 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -3054,7 +3054,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, - priv->bss_type = MWIFIEX_BSS_TYPE_P2P; - - priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II; -- priv->bss_priority = MWIFIEX_BSS_ROLE_STA; -+ priv->bss_priority = 0; - priv->bss_role = MWIFIEX_BSS_ROLE_STA; - priv->bss_started = 0; - --- -2.34.1 - -From ac0a5dcb8aa036a19953ca76e5156b7c375344e4 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:30:28 +0200 -Subject: [PATCH] mwifiex: Deactive host sleep using HSCFG after it was - activated manually - -When powersaving (so either wifi powersaving or deep sleep, depending on -which state the firmware is in) is disabled, the way the firmware goes -into host sleep is different: Usually the firmware implicitely enters -host sleep on the next SLEEP event we get when we configured host sleep -via HSCFG before. When powersaving is disabled though, there are no -SLEEP events, the way we enter host sleep in that case is different: The -firmware will send us a HS_ACT_REQ event and after that we "manually" -make the firmware enter host sleep by sending it another HSCFG command -with the action HS_ACTIVATE. - -Now waking up from host sleep appears to be different depending on -whether powersaving is enabled again: When powersaving is enabled, the -firmware implicitely leaves host sleep as soon as it wakes up and sends -us an AWAKE event. When powersaving is disabled though, it apparently -doesn't implicitely leave host sleep, but instead we need to send it a -HSCFG command with the HS_CONFIGURE action and the HS_CFG_CANCEL -condition. We didn't do that so far, which is why waking up from host -sleep was broken when powersaving is disabled. - -So add some additional state to mwifiex_adapter where we keep track of -whether host sleep was activated manually via HS_ACTIVATE, and if that -was the case, deactivate it manually again via HS_CFG_CANCEL. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cmdevt.c | 21 +++++++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.c | 18 ++++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmd.c | 4 ++++ - 4 files changed, 44 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cmdevt.c b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -index 171a25742600..d6a61f850c6f 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cmdevt.c -+++ b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -@@ -608,6 +608,11 @@ int mwifiex_send_cmd(struct mwifiex_private *priv, u16 cmd_no, - return -1; - } - -+ if (priv->adapter->hs_activated_manually && -+ cmd_no != HostCmd_CMD_802_11_HS_CFG_ENH) { -+ mwifiex_cancel_hs(priv, MWIFIEX_ASYNC_CMD); -+ priv->adapter->hs_activated_manually = false; -+ } - - /* Get a new command node */ - cmd_node = mwifiex_get_cmd_node(adapter); -@@ -714,6 +719,15 @@ mwifiex_insert_cmd_to_pending_q(struct mwifiex_adapter *adapter, - } - } - -+ /* Same with exit host sleep cmd, luckily that can't happen at the same time as EXIT_PS */ -+ if (command == HostCmd_CMD_802_11_HS_CFG_ENH) { -+ struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = -+ &host_cmd->params.opt_hs_cfg; -+ -+ if (le16_to_cpu(hs_cfg->action) == HS_ACTIVATE) -+ add_tail = false; -+ } -+ - spin_lock_bh(&adapter->cmd_pending_q_lock); - if (add_tail) - list_add_tail(&cmd_node->list, &adapter->cmd_pending_q); -@@ -1216,6 +1230,13 @@ mwifiex_process_hs_config(struct mwifiex_adapter *adapter) - __func__); - - adapter->if_ops.wakeup(adapter); -+ -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv (adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - adapter->hs_activated = false; - clear_bit(MWIFIEX_IS_HS_CONFIGURED, &adapter->work_flags); - clear_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags); -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index 17399d4aa129..1fbf5ba1042b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -401,6 +401,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !adapter->scan_processing) && - !adapter->data_sent && - !skb_queue_empty(&adapter->tx_data_q)) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_process_tx_queue(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -@@ -418,6 +424,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !mwifiex_bypass_txlist_empty(adapter) && - !mwifiex_is_tdls_chan_switching - (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_process_bypass_tx(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -@@ -434,6 +446,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - !adapter->data_sent && !mwifiex_wmm_lists_empty(adapter) && - !mwifiex_is_tdls_chan_switching - (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { -+ if (adapter->hs_activated_manually) { -+ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), -+ MWIFIEX_ASYNC_CMD); -+ adapter->hs_activated_manually = false; -+ } -+ - mwifiex_wmm_process_tx(adapter); - if (adapter->hs_activated) { - clear_bit(MWIFIEX_IS_HS_CONFIGURED, -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 5923c5c14c8d..90012cbcfd15 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -986,6 +986,7 @@ struct mwifiex_adapter { - struct timer_list wakeup_timer; - struct mwifiex_hs_config_param hs_cfg; - u8 hs_activated; -+ u8 hs_activated_manually; - u16 hs_activate_wait_q_woken; - wait_queue_head_t hs_activate_wait_q; - u8 event_body[MAX_EVENT_SIZE]; -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -index 48ea00da1fc9..1e2798dce18f 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -@@ -396,6 +396,10 @@ mwifiex_cmd_802_11_hs_cfg(struct mwifiex_private *priv, - if (hs_activate) { - hs_cfg->action = cpu_to_le16(HS_ACTIVATE); - hs_cfg->params.hs_activate.resp_ctrl = cpu_to_le16(RESP_NEEDED); -+ -+ adapter->hs_activated_manually = true; -+ mwifiex_dbg(priv->adapter, CMD, -+ "cmd: Activating host sleep manually\n"); - } else { - hs_cfg->action = cpu_to_le16(HS_CONFIGURE); - hs_cfg->params.hs_config.conditions = hscfg_param->conditions; --- -2.34.1 - -From 0a7fabd93bd6915a918cc9468145df42596636e4 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:23:05 +0200 -Subject: [PATCH] mwifiex: Add quirk to disable deep sleep with certain - hardware revision - -The 88W8897 pcie card with the hardware revision 20 apparently has a -hardware issue where the card wakes up from deep sleep randomly and very -often, somewhat depending on the card activity, maybe the hardware has a -floating wakeup pin or something. - -Those continuous wakeups prevent the card from entering host sleep when -the computer suspends. And because the host won't answer to events from -the card anymore while it's suspended, the firmwares internal -powersaving state machine seems to get confused and the card can't sleep -anymore at all after that. - -Since we can't work around that hardware bug in the firmware, let's -get the hardware revision string from the firmware and match it with -known bad revisions. Then disable auto deep sleep for those revisions, -which makes sure we no longer get those spurious wakeups. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/main.c | 14 ++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmdresp.c | 16 ++++++++++++++++ - 3 files changed, 31 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index 1fbf5ba1042b..be40813ffa5c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -226,6 +226,19 @@ static int mwifiex_process_rx(struct mwifiex_adapter *adapter) - return 0; - } - -+static void maybe_quirk_fw_disable_ds(struct mwifiex_adapter *adapter) -+{ -+ struct mwifiex_private *priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA); -+ struct mwifiex_ver_ext ver_ext; -+ -+ set_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &adapter->work_flags); -+ -+ memset(&ver_ext, 0, sizeof(ver_ext)); -+ ver_ext.version_str_sel = 1; -+ mwifiex_send_cmd(priv, HostCmd_CMD_VERSION_EXT, -+ HostCmd_ACT_GEN_GET, 0, &ver_ext, false); -+} -+ - /* - * The main process. - * -@@ -356,6 +369,7 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - if (adapter->hw_status == MWIFIEX_HW_STATUS_INIT_DONE) { - adapter->hw_status = MWIFIEX_HW_STATUS_READY; - mwifiex_init_fw_complete(adapter); -+ maybe_quirk_fw_disable_ds(adapter); - } - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 90012cbcfd15..1e829d84b1f6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -524,6 +524,7 @@ enum mwifiex_adapter_work_flags { - MWIFIEX_IS_SUSPENDED, - MWIFIEX_IS_HS_CONFIGURED, - MWIFIEX_IS_HS_ENABLING, -+ MWIFIEX_IS_REQUESTING_FW_VEREXT, - }; - - struct mwifiex_band_config { -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -index 6b5d35d9e69f..8e49ebca1847 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -@@ -708,6 +708,22 @@ static int mwifiex_ret_ver_ext(struct mwifiex_private *priv, - { - struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext; - -+ if (test_and_clear_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &priv->adapter->work_flags)) { -+ if (strncmp(ver_ext->version_str, "ChipRev:20, BB:9b(10.00), RF:40(21)", 128) == 0) { -+ struct mwifiex_ds_auto_ds auto_ds = { -+ .auto_ds = DEEP_SLEEP_OFF, -+ }; -+ -+ mwifiex_dbg(priv->adapter, MSG, -+ "Bad HW revision detected, disabling deep sleep\n"); -+ -+ mwifiex_send_cmd(priv, HostCmd_CMD_802_11_PS_MODE_ENH, -+ DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, false); -+ } -+ -+ return 0; -+ } -+ - if (version_ext) { - version_ext->version_str_sel = ver_ext->version_str_sel; - memcpy(version_ext->version_str, ver_ext->version_str, --- -2.34.1 - -From d142a666b72efa6ed94c6b30f64c795ab540110d Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Wed, 11 Nov 2020 15:17:07 +0100 -Subject: [PATCH] mwifiex: Don't log error on suspend if wake-on-wlan is - disabled - -It's not an error if someone chooses to put their computer to sleep, not -wanting it to wake up because the person next door has just discovered -what a magic packet is. So change the loglevel of this annoying message -from ERROR to INFO. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index d62a20de3ada..18b1a6d54bc8 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -3494,7 +3494,7 @@ static int mwifiex_cfg80211_suspend(struct wiphy *wiphy, - } - - if (!wowlan) { -- mwifiex_dbg(adapter, ERROR, -+ mwifiex_dbg(adapter, INFO, - "None of the WOWLAN triggers enabled\n"); - ret = 0; - goto done; --- -2.34.1 - -From df28f0d72425a9f77c8e49e9dc842e836307d5b8 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Sun, 28 Mar 2021 21:42:54 +0200 -Subject: [PATCH] mwifiex: Log an error on command failure during key-material - upload - -Sometimes the KEY_MATERIAL command can fail with the 88W8897 firmware -(when this happens exactly seems pretty random). This appears to prevent -the access point from starting, so it seems like a good idea to log an -error in that case. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 ++++++++-- - 1 file changed, 8 insertions(+), 2 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index 18b1a6d54bc8..c00791701d78 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -519,8 +519,14 @@ mwifiex_cfg80211_set_default_mgmt_key(struct wiphy *wiphy, - encrypt_key.is_igtk_def_key = true; - eth_broadcast_addr(encrypt_key.mac_addr); - -- return mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, -- HostCmd_ACT_GEN_SET, true, &encrypt_key, true); -+ if (mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, -+ HostCmd_ACT_GEN_SET, true, &encrypt_key, true)) { -+ mwifiex_dbg(priv->adapter, ERROR, -+ "Sending KEY_MATERIAL command failed\n"); -+ return -1; -+ } -+ -+ return 0; - } - - /* --- -2.34.1 - -From e7744556cad01e89209113ad2867f54afd4658a3 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:44:03 +0200 -Subject: [PATCH] mwifiex: Fix an incorrect comment - -We're sending DELBA requests here, not ADDBA requests. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/11n.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c -index cf08a4af84d6..9ff2058bcd7e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/11n.c -+++ b/drivers/net/wireless/marvell/mwifiex/11n.c -@@ -125,7 +125,7 @@ int mwifiex_ret_11n_delba(struct mwifiex_private *priv, - tx_ba_tbl->ra); - } else { /* - * In case of failure, recreate the deleted stream in case -- * we initiated the ADDBA -+ * we initiated the DELBA - */ - if (!INITIATOR_BIT(del_ba_param_set)) - return 0; --- -2.34.1 - -From 5b44a520ee92af6d8127c5a0f16fe9c85b8ced76 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:57:41 +0200 -Subject: [PATCH] mwifiex: Ignore BTCOEX events from the firmware - -The firmware of the pcie 88W8897 chip sends those events very -unreliably, which means we sometimes end up actually capping the window -size while bluetooth is disabled, artifically limiting wifi speeds even -though it's not needed. - -Since we can't fix the firmware, let's just ignore those events, it -seems that the Windows driver also doesn't change the rx/tx block ack -buffer sizes when bluetooth gets enabled or disabled, so this is -consistent with the Windows driver. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/sta_event.c | 4 +--- - 1 file changed, 1 insertion(+), 3 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_event.c b/drivers/net/wireless/marvell/mwifiex/sta_event.c -index 68c63268e2e6..933111a3511c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_event.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_event.c -@@ -1057,9 +1057,7 @@ int mwifiex_process_sta_event(struct mwifiex_private *priv) - adapter->event_skb); - break; - case EVENT_BT_COEX_WLAN_PARA_CHANGE: -- dev_dbg(adapter->dev, "EVENT: BT coex wlan param update\n"); -- mwifiex_bt_coex_wlan_param_update_event(priv, -- adapter->event_skb); -+ dev_dbg(adapter->dev, "EVENT: ignoring BT coex wlan param update\n"); - break; - case EVENT_RXBA_SYNC: - dev_dbg(adapter->dev, "EVENT: RXBA_SYNC\n"); --- -2.34.1 - diff --git a/patches/5.15/0003-ath10k.patch b/patches/5.15/0003-ath10k.patch deleted file mode 100644 index 89501e2f7..000000000 --- a/patches/5.15/0003-ath10k.patch +++ /dev/null @@ -1,121 +0,0 @@ -From 884f7715d516d67b71fdbb7f7a6849473ae30faf Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 27 Feb 2021 00:45:52 +0100 -Subject: [PATCH] ath10k: Add module parameters to override board files - -Some Surface devices, specifically the Surface Go and AMD version of the -Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better -with a different board file, as it seems that the firmeware included -upstream is buggy. - -As it is generally not a good idea to randomly overwrite files, let -alone doing so via packages, we add module parameters to override those -file names in the driver. This allows us to package/deploy the override -via a modprobe.d config. - -Signed-off-by: Maximilian Luz -Patchset: ath10k ---- - drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++ - 1 file changed, 58 insertions(+) - -diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c -index 64c7145b51a2..1e71a60cfb11 100644 ---- a/drivers/net/wireless/ath/ath10k/core.c -+++ b/drivers/net/wireless/ath/ath10k/core.c -@@ -35,6 +35,9 @@ static bool skip_otp; - static bool rawmode; - static bool fw_diag_log; - -+static char *override_board = ""; -+static char *override_board2 = ""; -+ - unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | - BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); - -@@ -47,6 +50,9 @@ module_param(rawmode, bool, 0644); - module_param(fw_diag_log, bool, 0644); - module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); - -+module_param(override_board, charp, 0644); -+module_param(override_board2, charp, 0644); -+ - MODULE_PARM_DESC(debug_mask, "Debugging mask"); - MODULE_PARM_DESC(uart_print, "Uart target debugging"); - MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); -@@ -55,6 +61,9 @@ MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); - MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); - MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); - -+MODULE_PARM_DESC(override_board, "Override for board.bin file"); -+MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); -+ - static const struct ath10k_hw_params ath10k_hw_params_list[] = { - { - .id = QCA988X_HW_2_0_VERSION, -@@ -826,6 +835,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) - return 0; - } - -+static const char *ath10k_override_board_fw_file(struct ath10k *ar, -+ const char *file) -+{ -+ if (strcmp(file, "board.bin") == 0) { -+ if (strcmp(override_board, "") == 0) -+ return file; -+ -+ if (strcmp(override_board, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", -+ override_board); -+ -+ return override_board; -+ } -+ -+ if (strcmp(file, "board-2.bin") == 0) { -+ if (strcmp(override_board2, "") == 0) -+ return file; -+ -+ if (strcmp(override_board2, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", -+ override_board2); -+ -+ return override_board2; -+ } -+ -+ return file; -+} -+ - static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - const char *dir, - const char *file) -@@ -840,6 +885,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - if (dir == NULL) - dir = "."; - -+ /* HACK: Override board.bin and board-2.bin files if specified. -+ * -+ * Some Surface devices perform better with a different board -+ * configuration. To this end, one would need to replace the board.bin -+ * file with the modified config and remove the board-2.bin file. -+ * Unfortunately, that's not a solution that we can easily package. So -+ * we add module options to perform these overrides here. -+ */ -+ -+ file = ath10k_override_board_fw_file(ar, file); -+ if (!file) -+ return ERR_PTR(-ENOENT); -+ - snprintf(filename, sizeof(filename), "%s/%s", dir, file); - ret = firmware_request_nowarn(&fw, filename, ar->dev); - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", --- -2.34.1 - diff --git a/patches/5.15/0004-ipts.patch b/patches/5.15/0004-ipts.patch deleted file mode 100644 index fe66f511f..000000000 --- a/patches/5.15/0004-ipts.patch +++ /dev/null @@ -1,1503 +0,0 @@ -From 6494ee60869fd7709226f1f31bbd930ca19d9072 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 30 Jul 2020 13:21:53 +0200 -Subject: [PATCH] misc: mei: Add missing IPTS device IDs - -Patchset: ipts ---- - drivers/misc/mei/hw-me-regs.h | 1 + - drivers/misc/mei/pci-me.c | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h -index 67bb6a25fd0a..d1cb94d3452e 100644 ---- a/drivers/misc/mei/hw-me-regs.h -+++ b/drivers/misc/mei/hw-me-regs.h -@@ -92,6 +92,7 @@ - #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ - - #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ -+#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ - #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ - - #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ -diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c -index 3a45aaf002ac..55b8ee30a03c 100644 ---- a/drivers/misc/mei/pci-me.c -+++ b/drivers/misc/mei/pci-me.c -@@ -96,6 +96,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { - {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, -+ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, --- -2.34.1 - -From ca4dd939db867a78340198a8c20532d5c7ab82f7 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 6 Aug 2020 11:20:41 +0200 -Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus - -Based on linux-surface/intel-precise-touch@3f362c - -Signed-off-by: Dorian Stoll -Patchset: ipts ---- - drivers/misc/Kconfig | 1 + - drivers/misc/Makefile | 1 + - drivers/misc/ipts/Kconfig | 17 ++ - drivers/misc/ipts/Makefile | 12 ++ - drivers/misc/ipts/context.h | 47 +++++ - drivers/misc/ipts/control.c | 113 +++++++++++ - drivers/misc/ipts/control.h | 24 +++ - drivers/misc/ipts/mei.c | 125 ++++++++++++ - drivers/misc/ipts/protocol.h | 347 ++++++++++++++++++++++++++++++++++ - drivers/misc/ipts/receiver.c | 224 ++++++++++++++++++++++ - drivers/misc/ipts/receiver.h | 16 ++ - drivers/misc/ipts/resources.c | 128 +++++++++++++ - drivers/misc/ipts/resources.h | 17 ++ - drivers/misc/ipts/uapi.c | 208 ++++++++++++++++++++ - drivers/misc/ipts/uapi.h | 47 +++++ - 15 files changed, 1327 insertions(+) - create mode 100644 drivers/misc/ipts/Kconfig - create mode 100644 drivers/misc/ipts/Makefile - create mode 100644 drivers/misc/ipts/context.h - create mode 100644 drivers/misc/ipts/control.c - create mode 100644 drivers/misc/ipts/control.h - create mode 100644 drivers/misc/ipts/mei.c - create mode 100644 drivers/misc/ipts/protocol.h - create mode 100644 drivers/misc/ipts/receiver.c - create mode 100644 drivers/misc/ipts/receiver.h - create mode 100644 drivers/misc/ipts/resources.c - create mode 100644 drivers/misc/ipts/resources.h - create mode 100644 drivers/misc/ipts/uapi.c - create mode 100644 drivers/misc/ipts/uapi.h - -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index 0f5a49fc7c9e..12b081bc875a 100644 ---- a/drivers/misc/Kconfig -+++ b/drivers/misc/Kconfig -@@ -487,4 +487,5 @@ source "drivers/misc/cardreader/Kconfig" - source "drivers/misc/habanalabs/Kconfig" - source "drivers/misc/uacce/Kconfig" - source "drivers/misc/pvpanic/Kconfig" -+source "drivers/misc/ipts/Kconfig" - endmenu -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index a086197af544..972cae33ba36 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -59,3 +59,4 @@ obj-$(CONFIG_UACCE) += uacce/ - obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o - obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o - obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o -+obj-$(CONFIG_MISC_IPTS) += ipts/ -diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig -new file mode 100644 -index 000000000000..83e2a930c396 ---- /dev/null -+++ b/drivers/misc/ipts/Kconfig -@@ -0,0 +1,17 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+ -+config MISC_IPTS -+ tristate "Intel Precise Touch & Stylus" -+ depends on INTEL_MEI -+ help -+ Say Y here if your system has a touchscreen using Intels -+ Precise Touch & Stylus (IPTS) technology. -+ -+ If unsure say N. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ipts. -+ -+ Building this driver alone will not give you a working touchscreen. -+ It only exposed a userspace API that can be used by a daemon to -+ receive and process data from the touchscreen hardware. -diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile -new file mode 100644 -index 000000000000..8f58b9adbc94 ---- /dev/null -+++ b/drivers/misc/ipts/Makefile -@@ -0,0 +1,12 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+# -+# Makefile for the IPTS touchscreen driver -+# -+ -+obj-$(CONFIG_MISC_IPTS) += ipts.o -+ipts-objs := control.o -+ipts-objs += mei.o -+ipts-objs += receiver.o -+ipts-objs += resources.o -+ipts-objs += uapi.o -+ -diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h -new file mode 100644 -index 000000000000..f4b06a2d3f72 ---- /dev/null -+++ b/drivers/misc/ipts/context.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTEXT_H_ -+#define _IPTS_CONTEXT_H_ -+ -+#include -+#include -+#include -+#include -+ -+#include "protocol.h" -+ -+enum ipts_host_status { -+ IPTS_HOST_STATUS_STARTING, -+ IPTS_HOST_STATUS_STARTED, -+ IPTS_HOST_STATUS_STOPPING, -+ IPTS_HOST_STATUS_STOPPED, -+}; -+ -+struct ipts_buffer_info { -+ u8 *address; -+ dma_addr_t dma_address; -+}; -+ -+struct ipts_context { -+ struct mei_cl_device *cldev; -+ struct device *dev; -+ -+ bool restart; -+ enum ipts_host_status status; -+ struct ipts_get_device_info_rsp device_info; -+ -+ struct ipts_buffer_info data[IPTS_BUFFERS]; -+ struct ipts_buffer_info doorbell; -+ -+ struct ipts_buffer_info feedback[IPTS_BUFFERS]; -+ struct ipts_buffer_info workqueue; -+ struct ipts_buffer_info host2me; -+}; -+ -+#endif /* _IPTS_CONTEXT_H_ */ -diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c -new file mode 100644 -index 000000000000..a1d1f97a13d7 ---- /dev/null -+++ b/drivers/misc/ipts/control.c -@@ -0,0 +1,113 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+#include "protocol.h" -+#include "resources.h" -+#include "uapi.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload, -+ size_t size) -+{ -+ int ret; -+ struct ipts_command cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_command)); -+ cmd.code = code; -+ -+ if (payload && size > 0) -+ memcpy(&cmd.payload, payload, size); -+ -+ ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size); -+ if (ret >= 0) -+ return 0; -+ -+ /* -+ * During shutdown the device might get pulled away from below our feet. -+ * Dont log an error in this case, because it will confuse people. -+ */ -+ if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING) -+ dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret); -+ -+ return ret; -+} -+ -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) -+{ -+ struct ipts_feedback_cmd cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); -+ cmd.buffer = buffer; -+ -+ return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, -+ sizeof(struct ipts_feedback_cmd)); -+} -+ -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value) -+{ -+ struct ipts_feedback_buffer *feedback; -+ -+ memset(ipts->host2me.address, 0, ipts->device_info.feedback_size); -+ feedback = (struct ipts_feedback_buffer *)ipts->host2me.address; -+ -+ feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; -+ feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; -+ feedback->buffer = IPTS_HOST2ME_BUFFER; -+ feedback->size = 2; -+ feedback->payload[0] = report; -+ feedback->payload[1] = value; -+ -+ return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER); -+} -+ -+int ipts_control_start(struct ipts_context *ipts) -+{ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Starting IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STARTING; -+ ipts->restart = false; -+ -+ ipts_uapi_link(ipts); -+ return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); -+} -+ -+int ipts_control_stop(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPING) -+ return -EBUSY; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Stopping IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STOPPING; -+ -+ ipts_uapi_unlink(); -+ ipts_resources_free(ipts); -+ -+ ret = ipts_control_send_feedback(ipts, 0); -+ if (ret == -ENODEV) -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ return ret; -+} -+ -+int ipts_control_restart(struct ipts_context *ipts) -+{ -+ if (ipts->restart) -+ return -EBUSY; -+ -+ ipts->restart = true; -+ return ipts_control_stop(ipts); -+} -diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h -new file mode 100644 -index 000000000000..2c44e9e0e99f ---- /dev/null -+++ b/drivers/misc/ipts/control.h -@@ -0,0 +1,24 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTROL_H_ -+#define _IPTS_CONTROL_H_ -+ -+#include -+ -+#include "context.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, -+ size_t size); -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value); -+int ipts_control_start(struct ipts_context *ipts); -+int ipts_control_restart(struct ipts_context *ipts); -+int ipts_control_stop(struct ipts_context *ipts); -+ -+#endif /* _IPTS_CONTROL_H_ */ -diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c -new file mode 100644 -index 000000000000..59ecf13e00d2 ---- /dev/null -+++ b/drivers/misc/ipts/mei.c -@@ -0,0 +1,125 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "receiver.h" -+#include "uapi.h" -+ -+static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev) -+{ -+ int ret; -+ -+ ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)); -+ if (!ret) -+ return 0; -+ -+ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); -+} -+ -+static int ipts_mei_probe(struct mei_cl_device *cldev, -+ const struct mei_cl_device_id *id) -+{ -+ int ret; -+ struct ipts_context *ipts; -+ -+ if (ipts_mei_set_dma_mask(cldev)) { -+ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n"); -+ return -EFAULT; -+ } -+ -+ ret = mei_cldev_enable(cldev); -+ if (ret) { -+ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); -+ return ret; -+ } -+ -+ ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); -+ if (!ipts) { -+ mei_cldev_disable(cldev); -+ return -ENOMEM; -+ } -+ -+ ipts->cldev = cldev; -+ ipts->dev = &cldev->dev; -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ mei_cldev_set_drvdata(cldev, ipts); -+ mei_cldev_register_rx_cb(cldev, ipts_receiver_callback); -+ -+ return ipts_control_start(ipts); -+} -+ -+static void ipts_mei_remove(struct mei_cl_device *cldev) -+{ -+ int i; -+ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); -+ -+ ipts_control_stop(ipts); -+ -+ for (i = 0; i < 20; i++) { -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ break; -+ -+ msleep(25); -+ } -+ -+ mei_cldev_disable(cldev); -+ kfree(ipts); -+} -+ -+static struct mei_cl_device_id ipts_mei_device_id_table[] = { -+ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, -+ {}, -+}; -+MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); -+ -+static struct mei_cl_driver ipts_mei_driver = { -+ .id_table = ipts_mei_device_id_table, -+ .name = "ipts", -+ .probe = ipts_mei_probe, -+ .remove = ipts_mei_remove, -+}; -+ -+static int __init ipts_mei_init(void) -+{ -+ int ret; -+ -+ ret = ipts_uapi_init(); -+ if (ret) -+ return ret; -+ -+ ret = mei_cldev_driver_register(&ipts_mei_driver); -+ if (ret) { -+ ipts_uapi_free(); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static void __exit ipts_mei_exit(void) -+{ -+ mei_cldev_driver_unregister(&ipts_mei_driver); -+ ipts_uapi_free(); -+} -+ -+MODULE_DESCRIPTION("IPTS touchscreen driver"); -+MODULE_AUTHOR("Dorian Stoll "); -+MODULE_LICENSE("GPL"); -+ -+module_init(ipts_mei_init); -+module_exit(ipts_mei_exit); -diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h -new file mode 100644 -index 000000000000..c3458904a94d ---- /dev/null -+++ b/drivers/misc/ipts/protocol.h -@@ -0,0 +1,347 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_PROTOCOL_H_ -+#define _IPTS_PROTOCOL_H_ -+ -+#include -+ -+/* -+ * The MEI client ID for IPTS functionality. -+ */ -+#define IPTS_MEI_UUID \ -+ UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, \ -+ 0x02, 0xae, 0x04) -+ -+/* -+ * Queries the device for vendor specific information. -+ * -+ * The command must not contain any payload. -+ * The response will contain struct ipts_get_device_info_rsp as payload. -+ */ -+#define IPTS_CMD_GET_DEVICE_INFO 0x00000001 -+#define IPTS_RSP_GET_DEVICE_INFO 0x80000001 -+ -+/* -+ * Sets the mode that IPTS will operate in. -+ * -+ * The command must contain struct ipts_set_mode_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MODE 0x00000002 -+#define IPTS_RSP_SET_MODE 0x80000002 -+ -+/* -+ * Configures the memory buffers that the ME will use -+ * for passing data to the host. -+ * -+ * The command must contain struct ipts_set_mem_window_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MEM_WINDOW 0x00000003 -+#define IPTS_RSP_SET_MEM_WINDOW 0x80000003 -+ -+/* -+ * Signals that the host is ready to receive data to the ME. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_READY_FOR_DATA 0x00000005 -+#define IPTS_RSP_READY_FOR_DATA 0x80000005 -+ -+/* -+ * Signals that a buffer can be refilled to the ME. -+ * -+ * The command must contain struct ipts_feedback_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_FEEDBACK 0x00000006 -+#define IPTS_RSP_FEEDBACK 0x80000006 -+ -+/* -+ * Resets the data flow from the ME to the hosts and -+ * clears the buffers that were set with SET_MEM_WINDOW. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007 -+#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007 -+ -+/* -+ * Instructs the ME to reset the touch sensor. -+ * -+ * The command must contain struct ipts_reset_sensor_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_RESET_SENSOR 0x0000000B -+#define IPTS_RSP_RESET_SENSOR 0x8000000B -+ -+/** -+ * enum ipts_status - Possible status codes returned by IPTS commands. -+ * @IPTS_STATUS_SUCCESS: Operation completed successfully. -+ * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. -+ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. -+ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. -+ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. -+ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. -+ * The host must wait for a response before sending another -+ * command of the same type. -+ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it -+ * has not been initialized yet, or the system is improperly -+ * configured. -+ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. -+ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. -+ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. -+ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. -+ * The host can ignore this error and attempt to continue. -+ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. -+ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. -+ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. -+ * @IPTS_STATUS_TIMEOUT: The operation timed out. -+ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. -+ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. -+ * Further progress is not possible. -+ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. -+ * The host can attempt to continue. -+ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. -+ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. -+ */ -+enum ipts_status { -+ IPTS_STATUS_SUCCESS = 0, -+ IPTS_STATUS_INVALID_PARAMS = 1, -+ IPTS_STATUS_ACCESS_DENIED = 2, -+ IPTS_STATUS_CMD_SIZE_ERROR = 3, -+ IPTS_STATUS_NOT_READY = 4, -+ IPTS_STATUS_REQUEST_OUTSTANDING = 5, -+ IPTS_STATUS_NO_SENSOR_FOUND = 6, -+ IPTS_STATUS_OUT_OF_MEMORY = 7, -+ IPTS_STATUS_INTERNAL_ERROR = 8, -+ IPTS_STATUS_SENSOR_DISABLED = 9, -+ IPTS_STATUS_COMPAT_CHECK_FAIL = 10, -+ IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, -+ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, -+ IPTS_STATUS_RESET_FAILED = 13, -+ IPTS_STATUS_TIMEOUT = 14, -+ IPTS_STATUS_TEST_MODE_FAIL = 15, -+ IPTS_STATUS_SENSOR_FAIL_FATAL = 16, -+ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, -+ IPTS_STATUS_INVALID_DEVICE_CAPS = 18, -+ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, -+}; -+ -+/* -+ * The amount of buffers that is used for IPTS -+ */ -+#define IPTS_BUFFERS 16 -+ -+/* -+ * The special buffer ID that is used for direct host2me feedback. -+ */ -+#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS -+ -+/** -+ * enum ipts_mode - Operation mode for IPTS hardware -+ * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. -+ * The data is received as a HID report with ID 64. -+ * @IPTS_MODE_MULTITOUCH: The "proper" operation mode for IPTS. It will return -+ * stylus data as well as capacitive heatmap touch data. -+ * This data needs to be processed in userspace. -+ */ -+enum ipts_mode { -+ IPTS_MODE_SINGLETOUCH = 0, -+ IPTS_MODE_MULTITOUCH = 1, -+}; -+ -+/** -+ * struct ipts_set_mode_cmd - Payload for the SET_MODE command. -+ * @mode: The mode that IPTS should operate in. -+ */ -+struct ipts_set_mode_cmd { -+ enum ipts_mode mode; -+ u8 reserved[12]; -+} __packed; -+ -+#define IPTS_WORKQUEUE_SIZE 8192 -+#define IPTS_WORKQUEUE_ITEM_SIZE 16 -+ -+/** -+ * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command. -+ * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses. -+ * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses. -+ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. -+ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. -+ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. -+ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. -+ * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses. -+ * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses. -+ * @host2me_addr_lower: Lower 32 bits of the host2me buffer address. -+ * @host2me_addr_upper: Upper 32 bits of the host2me buffer address. -+ * @workqueue_item_size: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) -+ * @workqueue_size: Magic value. (IPTS_WORKQUEUE_SIZE) -+ * -+ * The data buffers are buffers that get filled with touch data by the ME. -+ * The doorbell buffer is a u32 that gets incremented by the ME once a data -+ * buffer has been filled with new data. -+ * -+ * The other buffers are required for using GuC submission with binary -+ * firmware. Since support for GuC submission has been dropped from i915, -+ * they are not used anymore, but they need to be allocated and passed, -+ * otherwise the hardware will refuse to start. -+ */ -+struct ipts_set_mem_window_cmd { -+ u32 data_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 data_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 workqueue_addr_lower; -+ u32 workqueue_addr_upper; -+ u32 doorbell_addr_lower; -+ u32 doorbell_addr_upper; -+ u32 feedback_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 feedback_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 host2me_addr_lower; -+ u32 host2me_addr_upper; -+ u32 host2me_size; -+ u8 reserved1; -+ u8 workqueue_item_size; -+ u16 workqueue_size; -+ u8 reserved[32]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_cmd - Payload for the FEEDBACK command. -+ * @buffer: The buffer that the ME should refill. -+ */ -+struct ipts_feedback_cmd { -+ u32 buffer; -+ u8 reserved[12]; -+} __packed; -+ -+/** -+ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. -+ */ -+enum ipts_feedback_cmd_type { -+ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, -+ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, -+ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, -+}; -+ -+/** -+ * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains. -+ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. -+ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. -+ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. -+ */ -+enum ipts_feedback_data_type { -+ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, -+ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, -+ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, -+ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, -+ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, -+}; -+ -+/** -+ * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer. -+ * @cmd_type: A command that should be executed on the sensor. -+ * @size: The size of the payload to be written. -+ * @buffer: The ID of the buffer that contains this feedback data. -+ * @protocol: The protocol version of the EDS. -+ * @data_type: The type of payload that the buffer contains. -+ * @spi_offset: The offset at which to write the payload data. -+ * @payload: Payload for the feedback command, or 0 if no payload is sent. -+ */ -+struct ipts_feedback_buffer { -+ enum ipts_feedback_cmd_type cmd_type; -+ u32 size; -+ u32 buffer; -+ u32 protocol; -+ enum ipts_feedback_data_type data_type; -+ u32 spi_offset; -+ u8 reserved[40]; -+ u8 payload[]; -+} __packed; -+ -+/** -+ * enum ipts_reset_type - Possible ways of resetting the touch sensor -+ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. -+ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. -+ */ -+enum ipts_reset_type { -+ IPTS_RESET_TYPE_HARD = 0, -+ IPTS_RESET_TYPE_SOFT = 1, -+}; -+ -+/** -+ * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. -+ * @type: What type of reset should be performed. -+ */ -+struct ipts_reset_sensor_cmd { -+ enum ipts_reset_type type; -+ u8 reserved[4]; -+} __packed; -+ -+/** -+ * struct ipts_command - A message sent from the host to the ME. -+ * @code: The message code describing the command. (see IPTS_CMD_*) -+ * @payload: Payload for the command, or 0 if no payload is required. -+ */ -+struct ipts_command { -+ u32 code; -+ u8 payload[320]; -+} __packed; -+ -+/** -+ * struct ipts_device_info - Payload for the GET_DEVICE_INFO response. -+ * @vendor_id: Vendor ID of the touch sensor. -+ * @device_id: Device ID of the touch sensor. -+ * @hw_rev: Hardware revision of the touch sensor. -+ * @fw_rev: Firmware revision of the touch sensor. -+ * @data_size: Required size of one data buffer. -+ * @feedback_size: Required size of one feedback buffer. -+ * @mode: Current operation mode of IPTS. -+ * @max_contacts: The amount of concurrent touches supported by the sensor. -+ */ -+struct ipts_get_device_info_rsp { -+ u16 vendor_id; -+ u16 device_id; -+ u32 hw_rev; -+ u32 fw_rev; -+ u32 data_size; -+ u32 feedback_size; -+ enum ipts_mode mode; -+ u8 max_contacts; -+ u8 reserved[19]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_rsp - Payload for the FEEDBACK response. -+ * @buffer: The buffer that has received feedback. -+ */ -+struct ipts_feedback_rsp { -+ u32 buffer; -+} __packed; -+ -+/** -+ * struct ipts_response - A message sent from the ME to the host. -+ * @code: The message code describing the response. (see IPTS_RSP_*) -+ * @status: The status code returned by the command. -+ * @payload: Payload returned by the command. -+ */ -+struct ipts_response { -+ u32 code; -+ enum ipts_status status; -+ u8 payload[80]; -+} __packed; -+ -+#endif /* _IPTS_PROTOCOL_H_ */ -diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c -new file mode 100644 -index 000000000000..23dca13c2139 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.c -@@ -0,0 +1,224 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "resources.h" -+ -+/* -+ * Temporary parameter to guard gen7 multitouch mode. -+ * Remove once gen7 has stable iptsd support. -+ */ -+static bool gen7mt; -+module_param(gen7mt, bool, 0644); -+ -+static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_set_mode_cmd cmd; -+ -+ memcpy(&ipts->device_info, rsp->payload, -+ sizeof(struct ipts_get_device_info_rsp)); -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd)); -+ cmd.mode = IPTS_MODE_MULTITOUCH; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd, -+ sizeof(struct ipts_set_mode_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mode(struct ipts_context *ipts) -+{ -+ int i, ret; -+ struct ipts_set_mem_window_cmd cmd; -+ -+ ret = ipts_resources_alloc(ipts); -+ if (ret) { -+ dev_err(ipts->dev, "Failed to allocate resources\n"); -+ return ret; -+ } -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ cmd.data_buffer_addr_lower[i] = -+ lower_32_bits(ipts->data[i].dma_address); -+ -+ cmd.data_buffer_addr_upper[i] = -+ upper_32_bits(ipts->data[i].dma_address); -+ -+ cmd.feedback_buffer_addr_lower[i] = -+ lower_32_bits(ipts->feedback[i].dma_address); -+ -+ cmd.feedback_buffer_addr_upper[i] = -+ upper_32_bits(ipts->feedback[i].dma_address); -+ } -+ -+ cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); -+ cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); -+ -+ cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); -+ cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); -+ -+ cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); -+ cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); -+ -+ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; -+ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, -+ sizeof(struct ipts_set_mem_window_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ dev_info(ipts->dev, "Device %04hX:%04hX ready\n", -+ ipts->device_info.vendor_id, ipts->device_info.device_id); -+ ipts->status = IPTS_HOST_STATUS_STARTED; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); -+ if (ret) -+ return ret; -+ -+ if (!gen7mt) -+ return 0; -+ -+ return ipts_control_set_feature(ipts, 0x5, 0x1); -+} -+ -+static int ipts_receiver_handle_feedback(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_feedback_rsp feedback; -+ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPING) -+ return 0; -+ -+ memcpy(&feedback, rsp->payload, sizeof(feedback)); -+ -+ if (feedback.buffer < IPTS_BUFFERS - 1) -+ return ipts_control_send_feedback(ipts, feedback.buffer + 1); -+ -+ return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); -+} -+ -+static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) -+{ -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ if (ipts->restart) -+ return ipts_control_start(ipts); -+ -+ return 0; -+} -+ -+static bool ipts_receiver_sensor_was_reset(u32 status) -+{ -+ return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || -+ status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; -+} -+ -+static bool ipts_receiver_handle_error(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ bool error; -+ -+ switch (rsp->status) { -+ case IPTS_STATUS_SUCCESS: -+ case IPTS_STATUS_COMPAT_CHECK_FAIL: -+ error = false; -+ break; -+ case IPTS_STATUS_INVALID_PARAMS: -+ error = rsp->code != IPTS_RSP_FEEDBACK; -+ break; -+ case IPTS_STATUS_SENSOR_DISABLED: -+ error = ipts->status != IPTS_HOST_STATUS_STOPPING; -+ break; -+ default: -+ error = true; -+ break; -+ } -+ -+ if (!error) -+ return false; -+ -+ dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code, -+ rsp->status); -+ -+ if (ipts_receiver_sensor_was_reset(rsp->status)) { -+ dev_err(ipts->dev, "Sensor was reset\n"); -+ -+ if (ipts_control_restart(ipts)) -+ dev_err(ipts->dev, "Failed to restart IPTS\n"); -+ } -+ -+ return true; -+} -+ -+static void ipts_receiver_handle_response(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ int ret; -+ -+ if (ipts_receiver_handle_error(ipts, rsp)) -+ return; -+ -+ switch (rsp->code) { -+ case IPTS_RSP_GET_DEVICE_INFO: -+ ret = ipts_receiver_handle_get_device_info(ipts, rsp); -+ break; -+ case IPTS_RSP_SET_MODE: -+ ret = ipts_receiver_handle_set_mode(ipts); -+ break; -+ case IPTS_RSP_SET_MEM_WINDOW: -+ ret = ipts_receiver_handle_set_mem_window(ipts); -+ break; -+ case IPTS_RSP_FEEDBACK: -+ ret = ipts_receiver_handle_feedback(ipts, rsp); -+ break; -+ case IPTS_RSP_CLEAR_MEM_WINDOW: -+ ret = ipts_receiver_handle_clear_mem_window(ipts); -+ break; -+ default: -+ ret = 0; -+ break; -+ } -+ -+ if (!ret) -+ return; -+ -+ dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n", -+ rsp->code, ret); -+ -+ if (ipts_control_stop(ipts)) -+ dev_err(ipts->dev, "Failed to stop IPTS\n"); -+} -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev) -+{ -+ int ret; -+ struct ipts_response rsp; -+ struct ipts_context *ipts; -+ -+ ipts = mei_cldev_get_drvdata(cldev); -+ -+ ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response)); -+ if (ret <= 0) { -+ dev_err(ipts->dev, "Error while reading response: %d\n", ret); -+ return; -+ } -+ -+ ipts_receiver_handle_response(ipts, &rsp); -+} -diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h -new file mode 100644 -index 000000000000..7f075afa7ef8 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.h -@@ -0,0 +1,16 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RECEIVER_H_ -+#define _IPTS_RECEIVER_H_ -+ -+#include -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev); -+ -+#endif /* _IPTS_RECEIVER_H_ */ -diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c -new file mode 100644 -index 000000000000..8e3a2409e438 ---- /dev/null -+++ b/drivers/misc/ipts/resources.c -@@ -0,0 +1,128 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+ -+void ipts_resources_free(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, data_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ if (ipts->doorbell.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->doorbell.address, -+ ipts->doorbell.dma_address); -+ -+ ipts->doorbell.address = NULL; -+ ipts->doorbell.dma_address = 0; -+ } -+ -+ if (ipts->workqueue.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->workqueue.address, -+ ipts->workqueue.dma_address); -+ -+ ipts->workqueue.address = NULL; -+ ipts->workqueue.dma_address = 0; -+ } -+ -+ if (ipts->host2me.address) { -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ ipts->host2me.address, -+ ipts->host2me.dma_address); -+ -+ ipts->host2me.address = NULL; -+ ipts->host2me.dma_address = 0; -+ } -+} -+ -+int ipts_resources_alloc(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, data_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ ipts->doorbell.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->doorbell.dma_address, GFP_KERNEL); -+ -+ if (!ipts->doorbell.address) -+ goto release_resources; -+ -+ ipts->workqueue.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->workqueue.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ ipts->host2me.address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &ipts->host2me.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ return 0; -+ -+release_resources: -+ -+ ipts_resources_free(ipts); -+ return -ENOMEM; -+} -diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h -new file mode 100644 -index 000000000000..fdac0eee9156 ---- /dev/null -+++ b/drivers/misc/ipts/resources.h -@@ -0,0 +1,17 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RESOURCES_H_ -+#define _IPTS_RESOURCES_H_ -+ -+#include "context.h" -+ -+int ipts_resources_alloc(struct ipts_context *ipts); -+void ipts_resources_free(struct ipts_context *ipts); -+ -+#endif /* _IPTS_RESOURCES_H_ */ -diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c -new file mode 100644 -index 000000000000..598f0710ad64 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.c -@@ -0,0 +1,208 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "uapi.h" -+ -+struct ipts_uapi uapi; -+ -+static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count, -+ loff_t *offset) -+{ -+ int buffer; -+ int maxbytes; -+ struct ipts_context *ipts = uapi.ipts; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ maxbytes = ipts->device_info.data_size - *offset; -+ if (maxbytes <= 0 || count > maxbytes) -+ return -EINVAL; -+ -+ if (copy_to_user(buf, ipts->data[buffer].address + *offset, count)) -+ return -EFAULT; -+ -+ return count; -+} -+ -+static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ u8 ready = 0; -+ -+ if (ipts) -+ ready = ipts->status == IPTS_HOST_STATUS_STARTED; -+ -+ if (copy_to_user(buffer, &ready, sizeof(u8))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ struct ipts_device_info info; -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ info.vendor = ipts->device_info.vendor_id; -+ info.product = ipts->device_info.device_id; -+ info.version = ipts->device_info.fw_rev; -+ info.buffer_size = ipts->device_info.data_size; -+ info.max_contacts = ipts->device_info.max_contacts; -+ -+ if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts, -+ struct file *file) -+{ -+ int ret; -+ u32 buffer; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ ret = ipts_control_send_feedback(ipts, buffer); -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) -+{ -+ int ret; -+ struct ipts_reset_sensor_cmd cmd; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); -+ cmd.type = IPTS_RESET_TYPE_SOFT; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, -+ sizeof(struct ipts_reset_sensor_cmd)); -+ -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) -+{ -+ struct ipts_context *ipts = uapi.ipts; -+ -+ switch (cmd) { -+ case IPTS_IOCTL_GET_DEVICE_READY: -+ return ipts_uapi_ioctl_get_device_ready(ipts, arg); -+ case IPTS_IOCTL_GET_DEVICE_INFO: -+ return ipts_uapi_ioctl_get_device_info(ipts, arg); -+ case IPTS_IOCTL_GET_DOORBELL: -+ return ipts_uapi_ioctl_get_doorbell(ipts, arg); -+ case IPTS_IOCTL_SEND_FEEDBACK: -+ return ipts_uapi_ioctl_send_feedback(ipts, file); -+ case IPTS_IOCTL_SEND_RESET: -+ return ipts_uapi_ioctl_send_reset(ipts); -+ default: -+ return -ENOTTY; -+ } -+} -+ -+static const struct file_operations ipts_uapi_fops = { -+ .owner = THIS_MODULE, -+ .read = ipts_uapi_read, -+ .unlocked_ioctl = ipts_uapi_ioctl, -+#ifdef CONFIG_COMPAT -+ .compat_ioctl = ipts_uapi_ioctl, -+#endif -+}; -+ -+void ipts_uapi_link(struct ipts_context *ipts) -+{ -+ uapi.ipts = ipts; -+} -+ -+void ipts_uapi_unlink(void) -+{ -+ uapi.ipts = NULL; -+} -+ -+int ipts_uapi_init(void) -+{ -+ int i, major; -+ -+ alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts"); -+ uapi.class = class_create(THIS_MODULE, "ipts"); -+ -+ major = MAJOR(uapi.dev); -+ -+ cdev_init(&uapi.cdev, &ipts_uapi_fops); -+ uapi.cdev.owner = THIS_MODULE; -+ cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ device_create(uapi.class, NULL, MKDEV(major, i), NULL, -+ "ipts/%d", i); -+ } -+ -+ return 0; -+} -+ -+void ipts_uapi_free(void) -+{ -+ int i; -+ int major; -+ -+ major = MAJOR(uapi.dev); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) -+ device_destroy(uapi.class, MKDEV(major, i)); -+ -+ cdev_del(&uapi.cdev); -+ -+ unregister_chrdev_region(MKDEV(major, 0), MINORMASK); -+ class_destroy(uapi.class); -+} -diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h -new file mode 100644 -index 000000000000..53fb86a88f97 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_UAPI_H_ -+#define _IPTS_UAPI_H_ -+ -+#include -+ -+#include "context.h" -+ -+struct ipts_uapi { -+ dev_t dev; -+ struct class *class; -+ struct cdev cdev; -+ -+ struct ipts_context *ipts; -+}; -+ -+struct ipts_device_info { -+ __u16 vendor; -+ __u16 product; -+ __u32 version; -+ __u32 buffer_size; -+ __u8 max_contacts; -+ -+ /* For future expansion */ -+ __u8 reserved[19]; -+}; -+ -+#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8) -+#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info) -+#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) -+#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04) -+#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) -+ -+void ipts_uapi_link(struct ipts_context *ipts); -+void ipts_uapi_unlink(void); -+ -+int ipts_uapi_init(void); -+void ipts_uapi_free(void); -+ -+#endif /* _IPTS_UAPI_H_ */ --- -2.34.1 - diff --git a/patches/5.15/0005-surface-sam.patch b/patches/5.15/0005-surface-sam.patch deleted file mode 100644 index ad069898c..000000000 --- a/patches/5.15/0005-surface-sam.patch +++ /dev/null @@ -1,1816 +0,0 @@ -From c2f7d2e8baaac8f7be586a5b849acce4b22145d2 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 28 Oct 2021 03:28:45 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add initial support - for Surface Pro 8 - -Add preliminary support for the Surface Pro 8 to the Surface Aggregator -registry. This includes battery/charger status and platform profile -support. - -In contrast to earlier Surface Pro generations, the keyboard cover is -now also connected via the Surface Aggregator Module (whereas it was -previously connected via USB or HID-over-I2C). To properly support the -HID devices of that cover, however, more changes regarding hot-removal -of Surface Aggregator client devices as well as a new device hub driver -are required. We will address those things in a follow-up series, so do -not add any HID device IDs just yet. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20211028012845.1887219-1-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../platform/surface/surface_aggregator_registry.c | 12 ++++++++++++ - 1 file changed, 12 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 1679811eff50..e70f4c63554e 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -228,6 +228,15 @@ static const struct software_node *ssam_node_group_sp7[] = { - NULL, - }; - -+static const struct software_node *ssam_node_group_sp8[] = { -+ &ssam_node_root, -+ &ssam_node_bat_ac, -+ &ssam_node_bat_main, -+ &ssam_node_tmp_pprof, -+ /* TODO: Add support for keyboard cover. */ -+ NULL, -+}; -+ - - /* -- Device registry helper functions. ------------------------------------- */ - -@@ -534,6 +543,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { - /* Surface Pro 7+ */ - { "MSHW0119", (unsigned long)ssam_node_group_sp7 }, - -+ /* Surface Pro 8 */ -+ { "MSHW0263", (unsigned long)ssam_node_group_sp8 }, -+ - /* Surface Book 2 */ - { "MSHW0107", (unsigned long)ssam_node_group_gen5 }, - --- -2.34.1 - -From 758aee088b5a4eb00df31bac196212ee7f0eb95b Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 2 Jun 2021 03:34:06 +0200 -Subject: [PATCH] platform/surface: aggregator: Make client device removal more - generic - -Currently, there are similar functions defined in the Aggregator -Registry and the controller core. - -Make client device removal more generic and export it. We can then use -this function later on to remove client devices from device hubs as well -as the controller and avoid re-defining similar things. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/bus.c | 24 ++++++++-------------- - drivers/platform/surface/aggregator/bus.h | 3 --- - drivers/platform/surface/aggregator/core.c | 3 ++- - include/linux/surface_aggregator/device.h | 9 ++++++++ - 4 files changed, 19 insertions(+), 20 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -index 0a40dd9c94ed..abbbb5b08b07 100644 ---- a/drivers/platform/surface/aggregator/bus.c -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -374,27 +374,19 @@ static int ssam_remove_device(struct device *dev, void *_data) - } - - /** -- * ssam_controller_remove_clients() - Remove SSAM client devices registered as -- * direct children under the given controller. -- * @ctrl: The controller to remove all direct clients for. -+ * ssam_remove_clients() - Remove SSAM client devices registered as direct -+ * children under the given parent device. -+ * @dev: The (parent) device to remove all direct clients for. - * -- * Remove all SSAM client devices registered as direct children under the -- * given controller. Note that this only accounts for direct children of the -- * controller device. This does not take care of any client devices where the -- * parent device has been manually set before calling ssam_device_add. Refer -- * to ssam_device_add()/ssam_device_remove() for more details on those cases. -- * -- * To avoid new devices being added in parallel to this call, the main -- * controller lock (not statelock) must be held during this (and if -- * necessary, any subsequent deinitialization) call. -+ * Remove all SSAM client devices registered as direct children under the given -+ * device. Note that this only accounts for direct children of the device. -+ * Refer to ssam_device_add()/ssam_device_remove() for more details. - */ --void ssam_controller_remove_clients(struct ssam_controller *ctrl) -+void ssam_remove_clients(struct device *dev) - { -- struct device *dev; -- -- dev = ssam_controller_device(ctrl); - device_for_each_child_reverse(dev, NULL, ssam_remove_device); - } -+EXPORT_SYMBOL_GPL(ssam_remove_clients); - - /** - * ssam_bus_register() - Register and set-up the SSAM client device bus. -diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h -index ed032c2cbdb2..6964ee84e79c 100644 ---- a/drivers/platform/surface/aggregator/bus.h -+++ b/drivers/platform/surface/aggregator/bus.h -@@ -12,14 +12,11 @@ - - #ifdef CONFIG_SURFACE_AGGREGATOR_BUS - --void ssam_controller_remove_clients(struct ssam_controller *ctrl); -- - int ssam_bus_register(void); - void ssam_bus_unregister(void); - - #else /* CONFIG_SURFACE_AGGREGATOR_BUS */ - --static inline void ssam_controller_remove_clients(struct ssam_controller *ctrl) {} - static inline int ssam_bus_register(void) { return 0; } - static inline void ssam_bus_unregister(void) {} - -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -index c61bbeeec2df..d384d36098c2 100644 ---- a/drivers/platform/surface/aggregator/core.c -+++ b/drivers/platform/surface/aggregator/core.c -@@ -22,6 +22,7 @@ - #include - - #include -+#include - - #include "bus.h" - #include "controller.h" -@@ -735,7 +736,7 @@ static void ssam_serial_hub_remove(struct serdev_device *serdev) - ssam_controller_lock(ctrl); - - /* Remove all client devices. */ -- ssam_controller_remove_clients(ctrl); -+ ssam_remove_clients(&serdev->dev); - - /* Act as if suspending to silence events. */ - status = ssam_ctrl_notif_display_off(ctrl); -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index f636c5310321..cc257097eb05 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -319,6 +319,15 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); - ssam_device_driver_unregister) - - -+/* -- Helpers for controller and hub devices. ------------------------------- */ -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+void ssam_remove_clients(struct device *dev); -+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+static inline void ssam_remove_clients(struct device *dev) {} -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+ - /* -- Helpers for client-device requests. ----------------------------------- */ - - /** --- -2.34.1 - -From 45882009a18267754ea40e3eb044f61f84ea6d49 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 02:06:38 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Use generic client - removal function - -Use generic client removal function introduced in the previous commit -instead of defining our own one. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 24 ++++--------------- - 1 file changed, 5 insertions(+), 19 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index e70f4c63554e..f6c639342b9d 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -258,20 +258,6 @@ static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) - return 0; - } - --static int ssam_hub_remove_devices_fn(struct device *dev, void *data) --{ -- if (!is_ssam_device(dev)) -- return 0; -- -- ssam_device_remove(to_ssam_device(dev)); -- return 0; --} -- --static void ssam_hub_remove_devices(struct device *parent) --{ -- device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn); --} -- - static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, - struct fwnode_handle *node) - { -@@ -317,7 +303,7 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c - - return 0; - err: -- ssam_hub_remove_devices(parent); -+ ssam_remove_clients(parent); - return status; - } - -@@ -414,7 +400,7 @@ static void ssam_base_hub_update_workfn(struct work_struct *work) - if (hub->state == SSAM_BASE_HUB_CONNECTED) - status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); - else -- ssam_hub_remove_devices(&hub->sdev->dev); -+ ssam_remove_clients(&hub->sdev->dev); - - if (status) - dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); -@@ -496,7 +482,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - err: - ssam_notifier_unregister(sdev->ctrl, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); -- ssam_hub_remove_devices(&sdev->dev); -+ ssam_remove_clients(&sdev->dev); - return status; - } - -@@ -508,7 +494,7 @@ static void ssam_base_hub_remove(struct ssam_device *sdev) - - ssam_notifier_unregister(sdev->ctrl, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); -- ssam_hub_remove_devices(&sdev->dev); -+ ssam_remove_clients(&sdev->dev); - } - - static const struct ssam_device_id ssam_base_hub_match[] = { -@@ -625,7 +611,7 @@ static int ssam_platform_hub_remove(struct platform_device *pdev) - { - const struct software_node **nodes = platform_get_drvdata(pdev); - -- ssam_hub_remove_devices(&pdev->dev); -+ ssam_remove_clients(&pdev->dev); - set_secondary_fwnode(&pdev->dev, NULL); - software_node_unregister_node_group(nodes); - return 0; --- -2.34.1 - -From 06fbe764d97fb5ffb46a910efe4a8bcd3f2d2b69 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 02:07:33 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Rename device - registration function - -Rename the device registration function to better align names with the -newly introduced device removal function. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index f6c639342b9d..ce2bd88feeaa 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -283,8 +283,8 @@ static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ct - return status; - } - --static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl, -- struct fwnode_handle *node) -+static int ssam_hub_register_clients(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) - { - struct fwnode_handle *child; - int status; -@@ -398,7 +398,7 @@ static void ssam_base_hub_update_workfn(struct work_struct *work) - hub->state = state; - - if (hub->state == SSAM_BASE_HUB_CONNECTED) -- status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); -+ status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); - else - ssam_remove_clients(&hub->sdev->dev); - -@@ -597,7 +597,7 @@ static int ssam_platform_hub_probe(struct platform_device *pdev) - - set_secondary_fwnode(&pdev->dev, root); - -- status = ssam_hub_add_devices(&pdev->dev, ctrl, root); -+ status = ssam_hub_register_clients(&pdev->dev, ctrl, root); - if (status) { - set_secondary_fwnode(&pdev->dev, NULL); - software_node_unregister_node_group(nodes); --- -2.34.1 - -From 7c45966cfe0b5187690039080bf0d456bcd05e90 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 00:24:47 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow devices to be marked as - hot-removed - -Some SSAM devices, notably the keyboard cover (keyboard and touchpad) on -the Surface Pro 8, can be hot-removed. When this occurs, communication -with the device may fail and time out. This timeout can unnecessarily -block and slow down device removal and even cause issues when the -devices are detached and re-attached quickly. Thus, communication should -generally be avoided once hot-removal is detected. - -While we already remove a device as soon as we detect its (hot-)removal, -the corresponding device driver may still attempt to communicate with -the device during teardown. This is especially critical as communication -failure may also extend to disabling of events, which is typically done -at that stage. - -Add a flag to allow marking devices as hot-removed. This can then be -used during client driver teardown to check if any communication -attempts should be avoided. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/bus.c | 3 ++ - include/linux/surface_aggregator/device.h | 48 +++++++++++++++++++++-- - 2 files changed, 48 insertions(+), 3 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -index abbbb5b08b07..2b003dcbfc4b 100644 ---- a/drivers/platform/surface/aggregator/bus.c -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -388,6 +388,9 @@ void ssam_remove_clients(struct device *dev) - } - EXPORT_SYMBOL_GPL(ssam_remove_clients); - -+ -+/* -- Bus registration. ----------------------------------------------------- */ -+ - /** - * ssam_bus_register() - Register and set-up the SSAM client device bus. - */ -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index cc257097eb05..491aa7e9f4bc 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -148,17 +148,30 @@ struct ssam_device_uid { - #define SSAM_SDEV(cat, tid, iid, fun) \ - SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) - -+/* -+ * enum ssam_device_flags - Flags for SSAM client devices. -+ * @SSAM_DEVICE_HOT_REMOVED_BIT: -+ * The device has been hot-removed. Further communication with it may time -+ * out and should be avoided. -+ */ -+enum ssam_device_flags { -+ SSAM_DEVICE_HOT_REMOVED_BIT = 0, -+}; -+ - /** - * struct ssam_device - SSAM client device. -- * @dev: Driver model representation of the device. -- * @ctrl: SSAM controller managing this device. -- * @uid: UID identifying the device. -+ * @dev: Driver model representation of the device. -+ * @ctrl: SSAM controller managing this device. -+ * @uid: UID identifying the device. -+ * @flags: Device state flags, see &enum ssam_device_flags. - */ - struct ssam_device { - struct device dev; - struct ssam_controller *ctrl; - - struct ssam_device_uid uid; -+ -+ unsigned long flags; - }; - - /** -@@ -240,6 +253,35 @@ struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, - int ssam_device_add(struct ssam_device *sdev); - void ssam_device_remove(struct ssam_device *sdev); - -+/** -+ * ssam_device_mark_hot_removed() - Mark the given device as hot-removed. -+ * @sdev: The device to mark as hot-removed. -+ * -+ * Mark the device as having been hot-removed. This signals drivers using the -+ * device that communication with the device should be avoided and may lead to -+ * timeouts. -+ */ -+static inline void ssam_device_mark_hot_removed(struct ssam_device *sdev) -+{ -+ dev_dbg(&sdev->dev, "marking device as hot-removed\n"); -+ set_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); -+} -+ -+/** -+ * ssam_device_is_hot_removed() - Check if the given device has been -+ * hot-removed. -+ * @sdev: The device to check. -+ * -+ * Checks if the given device has been marked as hot-removed. See -+ * ssam_device_mark_hot_removed() for more details. -+ * -+ * Return: Returns ``true`` if the device has been marked as hot-removed. -+ */ -+static inline bool ssam_device_is_hot_removed(struct ssam_device *sdev) -+{ -+ return test_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); -+} -+ - /** - * ssam_device_get() - Increment reference count of SSAM client device. - * @sdev: The device to increment the reference count of. --- -2.34.1 - -From 44770b304ec78acf99c7cb3afb053f82346e54f0 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 00:48:22 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow notifiers to avoid - communication on unregistering - -When SSAM client devices have been (physically) hot-removed, -communication attempts with those devices may fail and time out. This -can even extend to event notifiers, due to which timeouts may occur -during device removal, slowing down that process. - -Add a flag to the notifier unregister function that allows skipping -communication with the EC to prevent this. Furthermore, add wrappers for -registering and unregistering notifiers belonging to SSAM client devices -that automatically check if the device has been marked as hot-removed -and communication should be avoided. - -Note that non-SSAM client devices can generally not be hot-removed, so -also add a convenience wrapper for those, defaulting to allow -communication. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../driver-api/surface_aggregator/client.rst | 6 +- - .../platform/surface/aggregator/controller.c | 53 ++++++++++------ - include/linux/surface_aggregator/controller.h | 24 +++++++- - include/linux/surface_aggregator/device.h | 60 +++++++++++++++++++ - 4 files changed, 122 insertions(+), 21 deletions(-) - -diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst -index e519d374c378..27f95abdbe99 100644 ---- a/Documentation/driver-api/surface_aggregator/client.rst -+++ b/Documentation/driver-api/surface_aggregator/client.rst -@@ -17,6 +17,8 @@ - .. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` - .. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` - .. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` -+.. |ssam_device_notifier_register| replace:: :c:func:`ssam_device_notifier_register` -+.. |ssam_device_notifier_unregister| replace:: :c:func:`ssam_device_notifier_unregister` - .. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` - .. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` - -@@ -312,7 +314,9 @@ Handling Events - To receive events from the SAM EC, an event notifier must be registered for - the desired event via |ssam_notifier_register|. The notifier must be - unregistered via |ssam_notifier_unregister| once it is not required any --more. -+more. For |ssam_device| type clients, the |ssam_device_notifier_register| and -+|ssam_device_notifier_unregister| wrappers should be preferred as they properly -+handle hot-removal of client devices. - - Event notifiers are registered by providing (at minimum) a callback to call - in case an event has been received, the registry specifying how the event -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index b8c377b3f932..6de834b52b63 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2199,16 +2199,26 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, - } - - /** -- * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is -- * no longer in use and free the corresponding entry. -+ * ssam_nf_refcount_disable_free() - Disable event for reference count entry if -+ * it is no longer in use and free the corresponding entry. - * @ctrl: The controller to disable the event on. - * @entry: The reference count entry for the event to be disabled. - * @flags: The flags used for enabling the event on the EC. -+ * @ec: Flag specifying if the event should actually be disabled on the EC. - * -- * If the reference count equals zero, i.e. the event is no longer requested by -- * any client, the event will be disabled and the corresponding reference count -- * entry freed. The reference count entry must not be used any more after a -- * call to this function. -+ * If ``ec`` equals ``true`` and the reference count equals zero (i.e. the -+ * event is no longer requested by any client), the specified event will be -+ * disabled on the EC via the corresponding request. -+ * -+ * If ``ec`` equals ``false``, no request will be sent to the EC and the event -+ * can be considered in a detached state (i.e. no longer used but still -+ * enabled). Disabling an event via this method may be required for -+ * hot-removable devices, where event disable requests may time out after the -+ * device has been physically removed. -+ * -+ * In both cases, if the reference count equals zero, the corresponding -+ * reference count entry will be freed. The reference count entry must not be -+ * used any more after a call to this function. - * - * Also checks if the flags used for disabling the event match the flags used - * for enabling the event and warns if they do not (regardless of reference -@@ -2223,7 +2233,7 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, - * returns the status of the event-enable EC command. - */ - static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, -- struct ssam_nf_refcount_entry *entry, u8 flags) -+ struct ssam_nf_refcount_entry *entry, u8 flags, bool ec) - { - const struct ssam_event_registry reg = entry->key.reg; - const struct ssam_event_id id = entry->key.id; -@@ -2232,8 +2242,9 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - - lockdep_assert_held(&nf->lock); - -- ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -- reg.target_category, id.target_category, id.instance, entry->refcount); -+ ssam_dbg(ctrl, "%s event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ ec ? "disabling" : "detaching", reg.target_category, id.target_category, -+ id.instance, entry->refcount); - - if (entry->flags != flags) { - ssam_warn(ctrl, -@@ -2242,7 +2253,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - id.instance); - } - -- if (entry->refcount == 0) { -+ if (ec && entry->refcount == 0) { - status = ssam_ssh_event_disable(ctrl, reg, id, flags); - kfree(entry); - } -@@ -2322,20 +2333,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif - EXPORT_SYMBOL_GPL(ssam_notifier_register); - - /** -- * ssam_notifier_unregister() - Unregister an event notifier. -- * @ctrl: The controller the notifier has been registered on. -- * @n: The event notifier to unregister. -+ * __ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * @disable: Whether to disable the corresponding event on the EC. - * - * Unregister an event notifier. Decrement the usage counter of the associated - * SAM event if the notifier is not marked as an observer. If the usage counter -- * reaches zero, the event will be disabled. -+ * reaches zero and ``disable`` equals ``true``, the event will be disabled. -+ * -+ * Useful for hot-removable devices, where communication may fail once the -+ * device has been physically removed. In that case, specifying ``disable`` as -+ * ``false`` avoids communication with the EC. - * - * Return: Returns zero on success, %-ENOENT if the given notifier block has - * not been registered on the controller. If the given notifier block was the - * last one associated with its specific event, returns the status of the - * event-disable EC-command. - */ --int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) -+int __ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n, -+ bool disable) - { - u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); - struct ssam_nf_refcount_entry *entry; -@@ -2373,7 +2390,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - goto remove; - } - -- status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); -+ status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags, disable); - } - - remove: -@@ -2383,7 +2400,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - - return status; - } --EXPORT_SYMBOL_GPL(ssam_notifier_unregister); -+EXPORT_SYMBOL_GPL(__ssam_notifier_unregister); - - /** - * ssam_controller_event_enable() - Enable the specified event. -@@ -2477,7 +2494,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, - return -ENOENT; - } - -- status = ssam_nf_refcount_disable_free(ctrl, entry, flags); -+ status = ssam_nf_refcount_disable_free(ctrl, entry, flags, true); - - mutex_unlock(&nf->lock); - return status; -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index 74bfdffaf7b0..50a2b4926c06 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -835,8 +835,28 @@ struct ssam_event_notifier { - int ssam_notifier_register(struct ssam_controller *ctrl, - struct ssam_event_notifier *n); - --int ssam_notifier_unregister(struct ssam_controller *ctrl, -- struct ssam_event_notifier *n); -+int __ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n, bool disable); -+ -+/** -+ * ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+static inline int ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n) -+{ -+ return __ssam_notifier_unregister(ctrl, n, true); -+} - - int ssam_controller_event_enable(struct ssam_controller *ctrl, - struct ssam_event_registry reg, -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 491aa7e9f4bc..16816c34da3e 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -472,4 +472,64 @@ static inline void ssam_remove_clients(struct device *dev) {} - sdev->uid.instance, ret); \ - } - -+ -+/* -- Helpers for client-device notifiers. ---------------------------------- */ -+ -+/** -+ * ssam_device_notifier_register() - Register an event notifier for the -+ * specified client device. -+ * @sdev: The device the notifier should be registered on. -+ * @n: The event notifier to register. -+ * -+ * Register an event notifier. Increment the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the event is not -+ * marked as an observer and is currently not enabled, it will be enabled -+ * during this call. If the notifier is marked as an observer, no attempt will -+ * be made at enabling any event and no reference count will be modified. -+ * -+ * Notifiers marked as observers do not need to be associated with one specific -+ * event, i.e. as long as no event matching is performed, only the event target -+ * category needs to be set. -+ * -+ * Return: Returns zero on success, %-ENOSPC if there have already been -+ * %INT_MAX notifiers for the event ID/type associated with the notifier block -+ * registered, %-ENOMEM if the corresponding event entry could not be -+ * allocated, %-ENODEV if the device is marked as hot-removed. If this is the -+ * first time that a notifier block is registered for the specific associated -+ * event, returns the status of the event-enable EC-command. -+ */ -+static inline int ssam_device_notifier_register(struct ssam_device *sdev, -+ struct ssam_event_notifier *n) -+{ -+ if (ssam_device_is_hot_removed(sdev)) -+ return -ENODEV; -+ -+ return ssam_notifier_register(sdev->ctrl, n); -+} -+ -+/** -+ * ssam_device_notifier_unregister() - Unregister an event notifier for the -+ * specified client device. -+ * @sdev: The device the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. -+ * -+ * In case the device has been marked as hot-removed, the event will not be -+ * disabled on the EC, as in those cases any attempt at doing so may time out. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+static inline int ssam_device_notifier_unregister(struct ssam_device *sdev, -+ struct ssam_event_notifier *n) -+{ -+ return __ssam_notifier_unregister(sdev->ctrl, n, -+ !ssam_device_is_hot_removed(sdev)); -+} -+ - #endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ --- -2.34.1 - -From 7faa13d1310e9ab4412976b30faef30229f21a50 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 01:20:49 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Use client device - wrappers for notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index ce2bd88feeaa..9f630e890ff7 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -468,7 +468,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - - ssam_device_set_drvdata(sdev, hub); - -- status = ssam_notifier_register(sdev->ctrl, &hub->notif); -+ status = ssam_device_notifier_register(sdev, &hub->notif); - if (status) - return status; - -@@ -480,7 +480,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - return 0; - - err: -- ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_device_notifier_unregister(sdev, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); - return status; -@@ -492,7 +492,7 @@ static void ssam_base_hub_remove(struct ssam_device *sdev) - - sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); - -- ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_device_notifier_unregister(sdev, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); - } --- -2.34.1 - -From 11ed57aa167e7d6d08f21968da3fca1cfb9fc4e4 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 28 Oct 2021 03:37:06 +0200 -Subject: [PATCH] power/supply: surface_charger: Use client device wrappers for - notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/power/supply/surface_charger.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -index a060c36c7766..59182d55742d 100644 ---- a/drivers/power/supply/surface_charger.c -+++ b/drivers/power/supply/surface_charger.c -@@ -216,7 +216,7 @@ static int spwr_ac_register(struct spwr_ac_device *ac) - if (IS_ERR(ac->psy)) - return PTR_ERR(ac->psy); - -- return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); -+ return ssam_device_notifier_register(ac->sdev, &ac->notif); - } - - -@@ -251,7 +251,7 @@ static void surface_ac_remove(struct ssam_device *sdev) - { - struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); - -- ssam_notifier_unregister(sdev->ctrl, &ac->notif); -+ ssam_device_notifier_unregister(sdev, &ac->notif); - } - - static const struct spwr_psy_properties spwr_psy_props_adp1 = { --- -2.34.1 - -From b7351a57ad6c46030e3bb7526fa9db843052f8bc Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 28 Oct 2021 03:38:09 +0200 -Subject: [PATCH] power/supply: surface_battery: Use client device wrappers for - notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/power/supply/surface_battery.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -index 5ec2e6bb2465..540707882bb0 100644 ---- a/drivers/power/supply/surface_battery.c -+++ b/drivers/power/supply/surface_battery.c -@@ -802,7 +802,7 @@ static int spwr_battery_register(struct spwr_battery_device *bat) - if (IS_ERR(bat->psy)) - return PTR_ERR(bat->psy); - -- return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); -+ return ssam_device_notifier_register(bat->sdev, &bat->notif); - } - - -@@ -837,7 +837,7 @@ static void surface_battery_remove(struct ssam_device *sdev) - { - struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); - -- ssam_notifier_unregister(sdev->ctrl, &bat->notif); -+ ssam_device_notifier_unregister(sdev, &bat->notif); - cancel_delayed_work_sync(&bat->update_work); - } - --- -2.34.1 - -From 198eb4c63c1a62d0e97b5f445678d4bced8d5f02 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 01:33:02 +0200 -Subject: [PATCH] HID: surface-hid: Add support for hot-removal - -Add support for hot-removal of SSAM HID client devices. - -Once a device has been hot-removed, further communication with it should -be avoided as it may fail and time out. While the device will be removed -as soon as we detect hot-removal, communication may still occur during -teardown, especially when unregistering notifiers. - -While hot-removal is a surprise event that can happen any time, try to -avoid communication as much as possible once it has been detected to -prevent timeouts that can slow down device removal and cause issues, -e.g. when quickly re-attaching the device. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/hid/surface-hid/surface_hid_core.c | 38 +++++++++++++++++++++- - 1 file changed, 37 insertions(+), 1 deletion(-) - -diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c -index 5571e74abe91..d2e695e942b6 100644 ---- a/drivers/hid/surface-hid/surface_hid_core.c -+++ b/drivers/hid/surface-hid/surface_hid_core.c -@@ -19,12 +19,30 @@ - #include "surface_hid_core.h" - - -+/* -- Utility functions. ---------------------------------------------------- */ -+ -+static bool surface_hid_is_hot_removed(struct surface_hid_device *shid) -+{ -+ /* -+ * Non-ssam client devices, i.e. platform client devices, cannot be -+ * hot-removed. -+ */ -+ if (!is_ssam_device(shid->dev)) -+ return false; -+ -+ return ssam_device_is_hot_removed(to_ssam_device(shid->dev)); -+} -+ -+ - /* -- Device descriptor access. --------------------------------------------- */ - - static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) - { - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, - (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); - if (status) -@@ -61,6 +79,9 @@ static int surface_hid_load_device_attributes(struct surface_hid_device *shid) - { - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, - (u8 *)&shid->attrs, sizeof(shid->attrs)); - if (status) -@@ -88,9 +109,18 @@ static int surface_hid_start(struct hid_device *hid) - static void surface_hid_stop(struct hid_device *hid) - { - struct surface_hid_device *shid = hid->driver_data; -+ bool hot_removed; -+ -+ /* -+ * Communication may fail for devices that have been hot-removed. This -+ * also includes unregistration of HID events, so we need to check this -+ * here. Only if the device has not been marked as hot-removed, we can -+ * safely disable events. -+ */ -+ hot_removed = surface_hid_is_hot_removed(shid); - - /* Note: This call will log errors for us, so ignore them here. */ -- ssam_notifier_unregister(shid->ctrl, &shid->notif); -+ __ssam_notifier_unregister(shid->ctrl, &shid->notif, !hot_removed); - } - - static int surface_hid_open(struct hid_device *hid) -@@ -109,6 +139,9 @@ static int surface_hid_parse(struct hid_device *hid) - u8 *buf; - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - buf = kzalloc(len, GFP_KERNEL); - if (!buf) - return -ENOMEM; -@@ -126,6 +159,9 @@ static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportn - { - struct surface_hid_device *shid = hid->driver_data; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) - return shid->ops.output_report(shid, reportnum, buf, len); - --- -2.34.1 - -From 5bbe985afa59130ca2378b9a1ef7409886427c7c Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 31 Oct 2021 12:34:08 +0100 -Subject: [PATCH] platform/surface: aggregator: Add comment for KIP subsystem - category - -The KIP subsystem (full name unknown, abbreviation has been obtained -through reverse engineering) handles detachable peripherals such as the -keyboard cover on the Surface Pro X and Surface Pro 8. - -It is currently not entirely clear what this subsystem entails, but at -the very least it provides event notifications for when the keyboard -cover on the Surface Pro X and Surface Pro 8 have been detached or -re-attached, as well as the state that the keyboard cover is currently -in (e.g. folded-back, folded laptop-like, closed, etc.). - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - include/linux/surface_aggregator/serial_hub.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h -index c3de43edcffa..d1efac85caf1 100644 ---- a/include/linux/surface_aggregator/serial_hub.h -+++ b/include/linux/surface_aggregator/serial_hub.h -@@ -306,7 +306,7 @@ enum ssam_ssh_tc { - SSAM_SSH_TC_LPC = 0x0b, - SSAM_SSH_TC_TCL = 0x0c, - SSAM_SSH_TC_SFL = 0x0d, -- SSAM_SSH_TC_KIP = 0x0e, -+ SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ - SSAM_SSH_TC_EXT = 0x0f, - SSAM_SSH_TC_BLD = 0x10, - SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ --- -2.34.1 - -From aa859c6ce8852971e66865919ff98879a95ba6e9 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 10 Oct 2021 23:56:23 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add KIP device hub - -Add a Surface System Aggregator Module (SSAM) client device hub for -hot-removable devices managed via the KIP subsystem. - -The KIP subsystem (full name unknown, abbreviation has been obtained -through reverse engineering) is a subsystem that manages hot-removable -SSAM client devices. Specifically, it manages HID input devices -contained in the detachable keyboard cover of the Surface Pro 8 and -Surface Pro X. - -To properly handle detachable devices, we need to remove their kernel -representation when the physical device has been detached and (re-)add -and (re-)initialize said representation when the physical device has -been (re-)attached. Note that we need to hot-remove those devices, as -communication (especially during event notifier unregistration) may time -out when the physical device is no longer present, which would lead to -an unnecessary delay. This delay might become problematic when devices -are detached and re-attached quickly. - -The KIP subsystem handles a single group of devices (e.g. all devices -contained in the keyboard cover) and cannot handle devices individually. -Thus we model it as a client device hub, which removes all devices -contained under it once removal of the hub (e.g. keyboard cover) has -been detected and (re-)adds all devices once the physical hub device has -been (re-)attached. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 247 +++++++++++++++++- - 1 file changed, 245 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 9f630e890ff7..4838ce6519a6 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -514,6 +514,237 @@ static struct ssam_device_driver ssam_base_hub_driver = { - }; - - -+/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ -+ -+/* -+ * Some devices may need a bit of time to be fully usable after being -+ * (re-)connected. This delay has been determined via experimentation. -+ */ -+#define SSAM_KIP_UPDATE_CONNECT_DELAY msecs_to_jiffies(250) -+ -+#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c -+ -+enum ssam_kip_hub_state { -+ SSAM_KIP_HUB_UNINITIALIZED, -+ SSAM_KIP_HUB_CONNECTED, -+ SSAM_KIP_HUB_DISCONNECTED, -+}; -+ -+struct ssam_kip_hub { -+ struct ssam_device *sdev; -+ -+ enum ssam_kip_hub_state state; -+ struct delayed_work update_work; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_connection_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x2c, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_get_connection_state(struct ssam_kip_hub *hub, enum ssam_kip_hub_state *state) -+{ -+ int status; -+ u8 connected; -+ -+ status = ssam_retry(__ssam_kip_get_connection_state, hub->sdev->ctrl, &connected); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); -+ return status; -+ } -+ -+ *state = connected ? SSAM_KIP_HUB_CONNECTED : SSAM_KIP_HUB_DISCONNECTED; -+ return 0; -+} -+ -+static ssize_t ssam_kip_hub_state_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct ssam_kip_hub *hub = dev_get_drvdata(dev); -+ const char *state; -+ -+ switch (hub->state) { -+ case SSAM_KIP_HUB_UNINITIALIZED: -+ state = "uninitialized"; -+ break; -+ -+ case SSAM_KIP_HUB_CONNECTED: -+ state = "connected"; -+ break; -+ -+ case SSAM_KIP_HUB_DISCONNECTED: -+ state = "disconnected"; -+ break; -+ -+ default: -+ /* -+ * Any value not handled in the above cases is invalid and -+ * should never have been set. Thus this case should be -+ * impossible to reach. -+ */ -+ WARN(1, "invalid KIP hub state: %d\n", hub->state); -+ state = ""; -+ break; -+ } -+ -+ return sysfs_emit(buf, "%s\n", state); -+} -+ -+static struct device_attribute ssam_kip_hub_attr_state = -+ __ATTR(state, 0444, ssam_kip_hub_state_show, NULL); -+ -+static struct attribute *ssam_kip_hub_attrs[] = { -+ &ssam_kip_hub_attr_state.attr, -+ NULL, -+}; -+ -+static const struct attribute_group ssam_kip_hub_group = { -+ .attrs = ssam_kip_hub_attrs, -+}; -+ -+static int ssam_kip_hub_mark_hot_removed(struct device *dev, void *_data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (is_ssam_device(dev)) -+ ssam_device_mark_hot_removed(sdev); -+ -+ return 0; -+} -+ -+static void ssam_kip_hub_update_workfn(struct work_struct *work) -+{ -+ struct ssam_kip_hub *hub = container_of(work, struct ssam_kip_hub, update_work.work); -+ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -+ enum ssam_kip_hub_state state; -+ int status = 0; -+ -+ status = ssam_kip_get_connection_state(hub, &state); -+ if (status) -+ return; -+ -+ if (hub->state == state) -+ return; -+ hub->state = state; -+ -+ if (hub->state == SSAM_KIP_HUB_CONNECTED) -+ status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); -+ else -+ ssam_remove_clients(&hub->sdev->dev); -+ -+ if (status) -+ dev_err(&hub->sdev->dev, "failed to update KIP-hub devices: %d\n", status); -+} -+ -+static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_kip_hub *hub = container_of(nf, struct ssam_kip_hub, notif); -+ unsigned long delay; -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) { -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ /* Mark devices as hot-removed before we remove any */ -+ if (!event->data[0]) -+ device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_kip_hub_mark_hot_removed); -+ -+ /* -+ * Delay update when KIP devices are being connected to give devices/EC -+ * some time to set up. -+ */ -+ delay = event->data[0] ? SSAM_KIP_UPDATE_CONNECT_DELAY : 0; -+ -+ schedule_delayed_work(&hub->update_work, delay); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static int __maybe_unused ssam_kip_hub_resume(struct device *dev) -+{ -+ struct ssam_kip_hub *hub = dev_get_drvdata(dev); -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_kip_hub_pm_ops, NULL, ssam_kip_hub_resume); -+ -+static int ssam_kip_hub_probe(struct ssam_device *sdev) -+{ -+ struct ssam_kip_hub *hub; -+ int status; -+ -+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -+ if (!hub) -+ return -ENOMEM; -+ -+ hub->sdev = sdev; -+ hub->state = SSAM_KIP_HUB_UNINITIALIZED; -+ -+ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -+ hub->notif.base.fn = ssam_kip_hub_notif; -+ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ hub->notif.event.id.target_category = SSAM_SSH_TC_KIP, -+ hub->notif.event.id.instance = 0, -+ hub->notif.event.mask = SSAM_EVENT_MASK_TARGET; -+ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ INIT_DELAYED_WORK(&hub->update_work, ssam_kip_hub_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, hub); -+ -+ status = ssam_device_notifier_register(sdev, &hub->notif); -+ if (status) -+ return status; -+ -+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_kip_hub_group); -+ if (status) -+ goto err; -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+ -+err: -+ ssam_device_notifier_unregister(sdev, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); -+ ssam_remove_clients(&sdev->dev); -+ return status; -+} -+ -+static void ssam_kip_hub_remove(struct ssam_device *sdev) -+{ -+ struct ssam_kip_hub *hub = ssam_device_get_drvdata(sdev); -+ -+ sysfs_remove_group(&sdev->dev.kobj, &ssam_kip_hub_group); -+ -+ ssam_device_notifier_unregister(sdev, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); -+ ssam_remove_clients(&sdev->dev); -+} -+ -+static const struct ssam_device_id ssam_kip_hub_match[] = { -+ { SSAM_SDEV(KIP, 0x01, 0x00, 0x00) }, -+ { }, -+}; -+ -+static struct ssam_device_driver ssam_kip_hub_driver = { -+ .probe = ssam_kip_hub_probe, -+ .remove = ssam_kip_hub_remove, -+ .match_table = ssam_kip_hub_match, -+ .driver = { -+ .name = "surface_kip_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_kip_hub_pm_ops, -+ }, -+}; -+ -+ - /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ - - static const struct acpi_device_id ssam_platform_hub_match[] = { -@@ -636,18 +867,30 @@ static int __init ssam_device_hub_init(void) - - status = platform_driver_register(&ssam_platform_hub_driver); - if (status) -- return status; -+ goto err_platform; - - status = ssam_device_driver_register(&ssam_base_hub_driver); - if (status) -- platform_driver_unregister(&ssam_platform_hub_driver); -+ goto err_base; -+ -+ status = ssam_device_driver_register(&ssam_kip_hub_driver); -+ if (status) -+ goto err_kip; - -+ return 0; -+ -+err_kip: -+ ssam_device_driver_unregister(&ssam_base_hub_driver); -+err_base: -+ platform_driver_unregister(&ssam_platform_hub_driver); -+err_platform: - return status; - } - module_init(ssam_device_hub_init); - - static void __exit ssam_device_hub_exit(void) - { -+ ssam_device_driver_unregister(&ssam_kip_hub_driver); - ssam_device_driver_unregister(&ssam_base_hub_driver); - platform_driver_unregister(&ssam_platform_hub_driver); - } --- -2.34.1 - -From cd22fa32a60433a791c3be82fa41da4a1c8b4a94 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 22:33:03 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for - keyboard cover on Surface Pro 8 - -Add support for the detachable keyboard cover on the Surface Pro 8. - -The keyboard cover on the Surface Pro 8 is, unlike the keyboard covers -of earlier Surface Pro generations, handled via the Surface System -Aggregator Module (SSAM). The keyboard and touchpad (as well as other -HID input devices) of this cover are standard SSAM HID client devices -(just like keyboard and touchpad on e.g. the Surface Laptop 3 and 4), -however, some care needs to be taken as they can be physically detached -(similarly to the Surface Book 3). Specifically, the respective SSAM -client devices need to be removed when the keyboard cover has been -detached and (re-)initialized when the keyboard cover has been -(re-)attached. - -On the Surface Pro 8, detachment of the keyboard cover (and by extension -its devices) is managed via the KIP subsystem. Therefore, said devices -need to be registered under the KIP device hub, which in turn will -remove and re-create/re-initialize those devices as needed. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 37 ++++++++++++++++++- - 1 file changed, 36 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 4838ce6519a6..c0e29c0514df 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -47,6 +47,12 @@ static const struct software_node ssam_node_hub_base = { - .parent = &ssam_node_root, - }; - -+/* KIP device hub (connects keyboard cover devices on Surface Pro 8). */ -+static const struct software_node ssam_node_hub_kip = { -+ .name = "ssam:01:0e:01:00:00", -+ .parent = &ssam_node_root, -+}; -+ - /* AC adapter. */ - static const struct software_node ssam_node_bat_ac = { - .name = "ssam:01:02:01:01:01", -@@ -155,6 +161,30 @@ static const struct software_node ssam_node_hid_base_iid6 = { - .parent = &ssam_node_hub_base, - }; - -+/* HID keyboard (KIP hub). */ -+static const struct software_node ssam_node_hid_kip_keyboard = { -+ .name = "ssam:01:15:02:01:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID pen stash (KIP hub; pen taken / stashed away evens). */ -+static const struct software_node ssam_node_hid_kip_penstash = { -+ .name = "ssam:01:15:02:02:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID touchpad (KIP hub). */ -+static const struct software_node ssam_node_hid_kip_touchpad = { -+ .name = "ssam:01:15:02:03:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID device instance 5 (KIP hub, unknown HID device). */ -+static const struct software_node ssam_node_hid_kip_iid5 = { -+ .name = "ssam:01:15:02:05:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ - /* - * Devices for 5th- and 6th-generations models: - * - Surface Book 2, -@@ -230,10 +260,15 @@ static const struct software_node *ssam_node_group_sp7[] = { - - static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_root, -+ &ssam_node_hub_kip, - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -- /* TODO: Add support for keyboard cover. */ -+ &ssam_node_hid_kip_keyboard, -+ &ssam_node_hid_kip_penstash, -+ &ssam_node_hid_kip_touchpad, -+ &ssam_node_hid_kip_iid5, -+ /* TODO: Add support for tablet mode switch. */ - NULL, - }; - --- -2.34.1 - -From 27370f6b89684508be9ec4157851b24a20602302 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 03:19:20 +0200 -Subject: [PATCH] platform/surface: Add KIP tablet-mode switch - -Add a driver providing a tablet-mode switch input device for Surface -models using the KIP subsystem to manage detachable peripherals. - -The Surface Pro 8 has a detachable keyboard cover. Unlike the keyboard -covers of previous generation Surface Pro models, this cover is fully -handled by the Surface System Aggregator Module (SSAM). The SSAM KIP -subsystem (full name unknown, abbreviation found through reverse -engineering) provides notifications for mode changes of the cover. -Specifically, it allows us to know when the cover has been folded back, -detached, or whether it is in laptop mode. - -The driver introduced with this change captures these events and -translates them to standard SW_TABLET_MODE input events. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - MAINTAINERS | 6 + - drivers/platform/surface/Kconfig | 22 ++ - drivers/platform/surface/Makefile | 1 + - .../surface/surface_kip_tablet_switch.c | 245 ++++++++++++++++++ - 4 files changed, 274 insertions(+) - create mode 100644 drivers/platform/surface/surface_kip_tablet_switch.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 3b79fd441dde..84e43aae33c0 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -12468,6 +12468,12 @@ L: platform-driver-x86@vger.kernel.org - S: Maintained - F: drivers/platform/surface/surface_hotplug.c - -+MICROSOFT SURFACE KIP TABLET-MODE SWITCH -+M: Maximilian Luz -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/platform/surface/surface_kip_tablet_switch.c -+ - MICROSOFT SURFACE PLATFORM PROFILE DRIVER - M: Maximilian Luz - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 3105f651614f..3c0ee0cdaef5 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -152,6 +152,28 @@ config SURFACE_HOTPLUG - Select M or Y here, if you want to (fully) support hot-plugging of - dGPU devices on the Surface Book 2 and/or 3 during D3cold. - -+config SURFACE_KIP_TABLET_SWITCH -+ tristate "Surface KIP Tablet-Mode Switch Driver" -+ depends on SURFACE_AGGREGATOR -+ depends on SURFACE_AGGREGATOR_BUS -+ depends on INPUT -+ help -+ Provides a tablet-mode switch input device on Microsoft Surface models -+ using the KIP subsystem for detachable keyboards (e.g. keyboard -+ covers). -+ -+ The KIP subsystem is used on newer Surface generations to handle -+ detachable input peripherals, specifically the keyboard cover -+ (containing keyboard and touchpad) on the Surface Pro 8. This module -+ provides a driver to let user-space know when the device should be -+ considered in tablet-mode due to the keyboard cover being detached or -+ folded back (essentially signaling when the keyboard is not available -+ for input). It does so by creating a tablet-mode switch input device, -+ sending the standard SW_TABLET_MODE event on mode change. -+ -+ Select M or Y here, if you want to provide tablet-mode switch input -+ events on the Surface Pro 8. -+ - config SURFACE_PLATFORM_PROFILE - tristate "Surface Platform Profile Driver" - depends on SURFACE_AGGREGATOR_REGISTRY -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 32889482de55..6d9291c993c4 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -14,5 +14,6 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -+obj-$(CONFIG_SURFACE_KIP_TABLET_SWITCH) += surface_kip_tablet_switch.o - obj-$(CONFIG_SURFACE_PLATFORM_PROFILE) += surface_platform_profile.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o -diff --git a/drivers/platform/surface/surface_kip_tablet_switch.c b/drivers/platform/surface/surface_kip_tablet_switch.c -new file mode 100644 -index 000000000000..458470067579 ---- /dev/null -+++ b/drivers/platform/surface/surface_kip_tablet_switch.c -@@ -0,0 +1,245 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) tablet mode switch via KIP -+ * subsystem. -+ * -+ * Copyright (C) 2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#define SSAM_EVENT_KIP_CID_LID_STATE 0x1d -+ -+enum ssam_kip_lid_state { -+ SSAM_KIP_LID_STATE_DISCONNECTED = 0x01, -+ SSAM_KIP_LID_STATE_CLOSED = 0x02, -+ SSAM_KIP_LID_STATE_LAPTOP = 0x03, -+ SSAM_KIP_LID_STATE_FOLDED_CANVAS = 0x04, -+ SSAM_KIP_LID_STATE_FOLDED_BACK = 0x05, -+}; -+ -+struct ssam_kip_sw { -+ struct ssam_device *sdev; -+ -+ enum ssam_kip_lid_state state; -+ struct work_struct update_work; -+ struct input_dev *mode_switch; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_lid_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x1d, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_get_lid_state(struct ssam_kip_sw *sw, enum ssam_kip_lid_state *state) -+{ -+ int status; -+ u8 raw; -+ -+ status = ssam_retry(__ssam_kip_get_lid_state, sw->sdev->ctrl, &raw); -+ if (status < 0) { -+ dev_err(&sw->sdev->dev, "failed to query KIP lid state: %d\n", status); -+ return status; -+ } -+ -+ *state = raw; -+ return 0; -+} -+ -+static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct ssam_kip_sw *sw = dev_get_drvdata(dev); -+ const char *state; -+ -+ switch (sw->state) { -+ case SSAM_KIP_LID_STATE_DISCONNECTED: -+ state = "disconnected"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_CLOSED: -+ state = "closed"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_LAPTOP: -+ state = "laptop"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_FOLDED_CANVAS: -+ state = "folded-canvas"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_FOLDED_BACK: -+ state = "folded-back"; -+ break; -+ -+ default: -+ state = ""; -+ dev_warn(dev, "unknown KIP lid state: %d\n", sw->state); -+ break; -+ } -+ -+ return sysfs_emit(buf, "%s\n", state); -+} -+static DEVICE_ATTR_RO(state); -+ -+static struct attribute *ssam_kip_sw_attrs[] = { -+ &dev_attr_state.attr, -+ NULL, -+}; -+ -+static const struct attribute_group ssam_kip_sw_group = { -+ .attrs = ssam_kip_sw_attrs, -+}; -+ -+static void ssam_kip_sw_update_workfn(struct work_struct *work) -+{ -+ struct ssam_kip_sw *sw = container_of(work, struct ssam_kip_sw, update_work); -+ enum ssam_kip_lid_state state; -+ int tablet, status; -+ -+ status = ssam_kip_get_lid_state(sw, &state); -+ if (status) -+ return; -+ -+ if (sw->state == state) -+ return; -+ sw->state = state; -+ -+ /* Send SW_TABLET_MODE event. */ -+ tablet = state != SSAM_KIP_LID_STATE_LAPTOP; -+ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); -+ input_sync(sw->mode_switch); -+} -+ -+static u32 ssam_kip_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_kip_sw *sw = container_of(nf, struct ssam_kip_sw, notif); -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_LID_STATE) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) { -+ dev_err(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ schedule_work(&sw->update_work); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static int __maybe_unused ssam_kip_sw_resume(struct device *dev) -+{ -+ struct ssam_kip_sw *sw = dev_get_drvdata(dev); -+ -+ schedule_work(&sw->update_work); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_kip_sw_pm_ops, NULL, ssam_kip_sw_resume); -+ -+static int ssam_kip_sw_probe(struct ssam_device *sdev) -+{ -+ struct ssam_kip_sw *sw; -+ int tablet, status; -+ -+ sw = devm_kzalloc(&sdev->dev, sizeof(*sw), GFP_KERNEL); -+ if (!sw) -+ return -ENOMEM; -+ -+ sw->sdev = sdev; -+ INIT_WORK(&sw->update_work, ssam_kip_sw_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, sw); -+ -+ /* Get initial state. */ -+ status = ssam_kip_get_lid_state(sw, &sw->state); -+ if (status) -+ return status; -+ -+ /* Set up tablet mode switch. */ -+ sw->mode_switch = devm_input_allocate_device(&sdev->dev); -+ if (!sw->mode_switch) -+ return -ENOMEM; -+ -+ sw->mode_switch->name = "Microsoft Surface KIP Tablet Mode Switch"; -+ sw->mode_switch->phys = "ssam/01:0e:01:00:01/input0"; -+ sw->mode_switch->id.bustype = BUS_HOST; -+ sw->mode_switch->dev.parent = &sdev->dev; -+ -+ tablet = sw->state != SSAM_KIP_LID_STATE_LAPTOP; -+ input_set_capability(sw->mode_switch, EV_SW, SW_TABLET_MODE); -+ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); -+ -+ status = input_register_device(sw->mode_switch); -+ if (status) -+ return status; -+ -+ /* Set up notifier. */ -+ sw->notif.base.priority = 0; -+ sw->notif.base.fn = ssam_kip_sw_notif; -+ sw->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ sw->notif.event.id.target_category = SSAM_SSH_TC_KIP, -+ sw->notif.event.id.instance = 0, -+ sw->notif.event.mask = SSAM_EVENT_MASK_TARGET; -+ sw->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ status = ssam_device_notifier_register(sdev, &sw->notif); -+ if (status) -+ return status; -+ -+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_kip_sw_group); -+ if (status) -+ goto err; -+ -+ /* We might have missed events during setup, so check again. */ -+ schedule_work(&sw->update_work); -+ return 0; -+ -+err: -+ ssam_device_notifier_unregister(sdev, &sw->notif); -+ cancel_work_sync(&sw->update_work); -+ return status; -+} -+ -+static void ssam_kip_sw_remove(struct ssam_device *sdev) -+{ -+ struct ssam_kip_sw *sw = ssam_device_get_drvdata(sdev); -+ -+ sysfs_remove_group(&sdev->dev.kobj, &ssam_kip_sw_group); -+ -+ ssam_device_notifier_unregister(sdev, &sw->notif); -+ cancel_work_sync(&sw->update_work); -+} -+ -+static const struct ssam_device_id ssam_kip_sw_match[] = { -+ { SSAM_SDEV(KIP, 0x01, 0x00, 0x01) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, ssam_kip_sw_match); -+ -+static struct ssam_device_driver ssam_kip_sw_driver = { -+ .probe = ssam_kip_sw_probe, -+ .remove = ssam_kip_sw_remove, -+ .match_table = ssam_kip_sw_match, -+ .driver = { -+ .name = "surface_kip_tablet_mode_switch", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_kip_sw_pm_ops, -+ }, -+}; -+module_ssam_device_driver(ssam_kip_sw_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Tablet mode switch driver for Surface devices using KIP subsystem"); -+MODULE_LICENSE("GPL"); --- -2.34.1 - -From e613c88ebbe907fa87798d939f7e9ae18604b8c0 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 22:33:03 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for tablet - mode switch on Surface Pro 8 - -Add a KIP subsystem tablet-mode switch device for the Surface Pro 8. -The respective driver for this device provides SW_TABLET_MODE input -events for user-space based on the state of the keyboard cover (e.g. -detached, folded-back, normal/laptop mode). - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 8 +++++++- - 1 file changed, 7 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index c0e29c0514df..eaf0054627a5 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -77,6 +77,12 @@ static const struct software_node ssam_node_tmp_pprof = { - .parent = &ssam_node_root, - }; - -+/* Tablet-mode switch via KIP subsystem. */ -+static const struct software_node ssam_node_kip_tablet_switch = { -+ .name = "ssam:01:0e:01:00:01", -+ .parent = &ssam_node_root, -+}; -+ - /* DTX / detachment-system device (Surface Book 3). */ - static const struct software_node ssam_node_bas_dtx = { - .name = "ssam:01:11:01:00:00", -@@ -264,11 +270,11 @@ static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -+ &ssam_node_kip_tablet_switch, - &ssam_node_hid_kip_keyboard, - &ssam_node_hid_kip_penstash, - &ssam_node_hid_kip_touchpad, - &ssam_node_hid_kip_iid5, -- /* TODO: Add support for tablet mode switch. */ - NULL, - }; - --- -2.34.1 - diff --git a/patches/5.15/0006-surface-sam-over-hid.patch b/patches/5.15/0006-surface-sam-over-hid.patch deleted file mode 100644 index f03d02da3..000000000 --- a/patches/5.15/0006-surface-sam-over-hid.patch +++ /dev/null @@ -1,335 +0,0 @@ -From 281fae27184e6db9262441a46f1f4d29426c74e6 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jul 2020 17:19:53 +0200 -Subject: [PATCH] i2c: acpi: Implement RawBytes read access - -Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C -device via a generic serial bus operation region and RawBytes read -access. On the Surface Book 1, this access is required to turn on (and -off) the discrete GPU. - -Multiple things are to note here: - -a) The RawBytes access is device/driver dependent. The ACPI - specification states: - - > Raw accesses assume that the writer has knowledge of the bus that - > the access is made over and the device that is being accessed. The - > protocol may only ensure that the buffer is transmitted to the - > appropriate driver, but the driver must be able to interpret the - > buffer to communicate to a register. - - Thus this implementation may likely not work on other devices - accessing I2C via the RawBytes accessor type. - -b) The MSHW0030 I2C device is an HID-over-I2C device which seems to - serve multiple functions: - - 1. It is the main access point for the legacy-type Surface Aggregator - Module (also referred to as SAM-over-HID, as opposed to the newer - SAM-over-SSH/UART). It has currently not been determined on how - support for the legacy SAM should be implemented. Likely via a - custom HID driver. - - 2. It seems to serve as the HID device for the Integrated Sensor Hub. - This might complicate matters with regards to implementing a - SAM-over-HID driver required by legacy SAM. - -In light of this, the simplest approach has been chosen for now. -However, it may make more sense regarding breakage and compatibility to -either provide functionality for replacing or enhancing the default -operation region handler via some additional API functions, or even to -completely blacklist MSHW0030 from the I2C core and provide a custom -driver for it. - -Replacing/enhancing the default operation region handler would, however, -either require some sort of secondary driver and access point for it, -from which the new API functions would be called and the new handler -(part) would be installed, or hard-coding them via some sort of -quirk-like interface into the I2C core. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam-over-hid ---- - drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ - 1 file changed, 35 insertions(+) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index 546cc935e035..006e25a1b0d5 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -603,6 +603,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, - return (ret == 1) ? 0 : -EIO; - } - -+static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, -+ u8 *data, u8 data_len) -+{ -+ struct i2c_msg msgs[1]; -+ int ret = AE_OK; -+ -+ msgs[0].addr = client->addr; -+ msgs[0].flags = client->flags; -+ msgs[0].len = data_len + 1; -+ msgs[0].buf = data; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ -+ if (ret < 0) { -+ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); -+ return ret; -+ } -+ -+ /* 1 transfer must have completed successfully */ -+ return (ret == 1) ? 0 : -EIO; -+} -+ - static acpi_status - i2c_acpi_space_handler(u32 function, acpi_physical_address command, - u32 bits, u64 *value64, -@@ -704,6 +726,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, - } - break; - -+ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: -+ if (action == ACPI_READ) { -+ dev_warn(&adapter->dev, -+ "protocol 0x%02x not supported for client 0x%02x\n", -+ accessor_type, client->addr); -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } else { -+ status = acpi_gsb_i2c_write_raw_bytes(client, -+ gsb->data, info->access_length); -+ } -+ break; -+ - default: - dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", - accessor_type, client->addr); --- -2.34.1 - -From 2f900b5bbfbcca95220ec243ade1cf29727cca7e Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 13 Feb 2021 16:41:18 +0100 -Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch - -Add driver exposing the discrete GPU power-switch of the Microsoft -Surface Book 1 to user-space. - -On the Surface Book 1, the dGPU power is controlled via the Surface -System Aggregator Module (SAM). The specific SAM-over-HID command for -this is exposed via ACPI. This module provides a simple driver exposing -the ACPI call via a sysfs parameter to user-space, so that users can -easily power-on/-off the dGPU. - -Patchset: surface-sam-over-hid ---- - drivers/platform/surface/Kconfig | 7 + - drivers/platform/surface/Makefile | 1 + - .../surface/surfacebook1_dgpu_switch.c | 162 ++++++++++++++++++ - 3 files changed, 170 insertions(+) - create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 3c0ee0cdaef5..e5eedb85d471 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -104,6 +104,13 @@ config SURFACE_AGGREGATOR_REGISTRY - the respective client devices. Drivers for these devices still need to - be selected via the other options. - -+config SURFACE_BOOK1_DGPU_SWITCH -+ tristate "Surface Book 1 dGPU Switch Driver" -+ depends on SYSFS -+ help -+ This driver provides a sysfs switch to set the power-state of the -+ discrete GPU found on the Microsoft Surface Book 1. -+ - config SURFACE_DTX - tristate "Surface DTX (Detachment System) Driver" - depends on SURFACE_AGGREGATOR -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 6d9291c993c4..9eb3a7e6382c 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -11,6 +11,7 @@ obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o -+obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c -new file mode 100644 -index 000000000000..8b816ed8f35c ---- /dev/null -+++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c -@@ -0,0 +1,162 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+ -+#include -+#include -+#include -+#include -+ -+ -+#ifdef pr_fmt -+#undef pr_fmt -+#endif -+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ -+ -+ -+static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, -+ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); -+ -+#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" -+#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" -+#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" -+ -+ -+static int sb1_dgpu_sw_dsmcall(void) -+{ -+ union acpi_object *ret; -+ acpi_handle handle; -+ acpi_status status; -+ -+ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); -+ if (status) -+ return -EINVAL; -+ -+ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); -+ if (!ret) -+ return -EINVAL; -+ -+ ACPI_FREE(ret); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgon(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGON: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-on dGPU via HGON\n"); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgof(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGOF: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-off dGPU via HGOF\n"); -+ return 0; -+} -+ -+ -+static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ int status, value; -+ -+ status = kstrtoint(buf, 0, &value); -+ if (status < 0) -+ return status; -+ -+ if (value != 1) -+ return -EINVAL; -+ -+ status = sb1_dgpu_sw_dsmcall(); -+ -+ return status < 0 ? status : len; -+} -+ -+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ bool power; -+ int status; -+ -+ status = kstrtobool(buf, &power); -+ if (status < 0) -+ return status; -+ -+ if (power) -+ status = sb1_dgpu_sw_hgon(); -+ else -+ status = sb1_dgpu_sw_hgof(); -+ -+ return status < 0 ? status : len; -+} -+ -+static DEVICE_ATTR_WO(dgpu_dsmcall); -+static DEVICE_ATTR_WO(dgpu_power); -+ -+static struct attribute *sb1_dgpu_sw_attrs[] = { -+ &dev_attr_dgpu_dsmcall.attr, -+ &dev_attr_dgpu_power.attr, -+ NULL, -+}; -+ -+static const struct attribute_group sb1_dgpu_sw_attr_group = { -+ .attrs = sb1_dgpu_sw_attrs, -+}; -+ -+ -+static int sb1_dgpu_sw_probe(struct platform_device *pdev) -+{ -+ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+} -+ -+static int sb1_dgpu_sw_remove(struct platform_device *pdev) -+{ -+ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+ return 0; -+} -+ -+/* -+ * The dGPU power seems to be actually handled by MSHW0040. However, that is -+ * also the power-/volume-button device with a mainline driver. So let's use -+ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. -+ */ -+static const struct acpi_device_id sb1_dgpu_sw_match[] = { -+ { "MSHW0041", }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); -+ -+static struct platform_driver sb1_dgpu_sw = { -+ .probe = sb1_dgpu_sw_probe, -+ .remove = sb1_dgpu_sw_remove, -+ .driver = { -+ .name = "surfacebook1_dgpu_switch", -+ .acpi_match_table = sb1_dgpu_sw_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(sb1_dgpu_sw); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); -+MODULE_LICENSE("GPL"); --- -2.34.1 - diff --git a/patches/5.15/0007-surface-gpe.patch b/patches/5.15/0007-surface-gpe.patch deleted file mode 100644 index b489e216e..000000000 --- a/patches/5.15/0007-surface-gpe.patch +++ /dev/null @@ -1,85 +0,0 @@ -From 6d69e602cc56f2de7e9b3ef002bd1b6b42fcfa59 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 10 Oct 2021 00:02:44 +0200 -Subject: [PATCH] platform/surface: gpe: Add support for Surface Laptop Studio - -The new Surface Laptop Studio uses GPEs for lid events as well. Add an -entry for that so that the lid can be used to wake the device. - -Signed-off-by: Maximilian Luz -Patchset: surface-gpe ---- - drivers/platform/surface/surface_gpe.c | 13 +++++++++++++ - 1 file changed, 13 insertions(+) - -diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c -index 86f6991b1215..c1775db29efb 100644 ---- a/drivers/platform/surface/surface_gpe.c -+++ b/drivers/platform/surface/surface_gpe.c -@@ -26,6 +26,11 @@ static const struct property_entry lid_device_props_l17[] = { - {}, - }; - -+static const struct property_entry lid_device_props_l4B[] = { -+ PROPERTY_ENTRY_U32("gpe", 0x4B), -+ {}, -+}; -+ - static const struct property_entry lid_device_props_l4D[] = { - PROPERTY_ENTRY_U32("gpe", 0x4D), - {}, -@@ -158,6 +163,14 @@ static const struct dmi_system_id dmi_lid_device_table[] = { - }, - .driver_data = (void *)lid_device_props_l4D, - }, -+ { -+ .ident = "Surface Laptop Studio", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop Studio"), -+ }, -+ .driver_data = (void *)lid_device_props_l4B, -+ }, - { } - }; - --- -2.34.1 - -From b01cc41a1f5eda6e0964b6eca3b221c2aac6597f Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 00:56:11 +0200 -Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 8 - -The new Surface Pro 8 uses GPEs for lid events as well. Add an entry for -that so that the lid can be used to wake the device. Note that this is a -device with a keyboard type cover, where this acts as the "lid". - -Signed-off-by: Maximilian Luz -Patchset: surface-gpe ---- - drivers/platform/surface/surface_gpe.c | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c -index c1775db29efb..ec66fde28e75 100644 ---- a/drivers/platform/surface/surface_gpe.c -+++ b/drivers/platform/surface/surface_gpe.c -@@ -99,6 +99,14 @@ static const struct dmi_system_id dmi_lid_device_table[] = { - }, - .driver_data = (void *)lid_device_props_l4D, - }, -+ { -+ .ident = "Surface Pro 8", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 8"), -+ }, -+ .driver_data = (void *)lid_device_props_l4B, -+ }, - { - .ident = "Surface Book 1", - .matches = { --- -2.34.1 - diff --git a/patches/5.15/0008-surface-button.patch b/patches/5.15/0008-surface-button.patch deleted file mode 100644 index b0b1b48f3..000000000 --- a/patches/5.15/0008-surface-button.patch +++ /dev/null @@ -1,149 +0,0 @@ -From ab51b1549415b29b55a403adb603d8222574650d Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Tue, 5 Oct 2021 00:05:09 +1100 -Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices - -The power button on the AMD variant of the Surface Laptop uses the -same MSHW0040 device ID as the 5th and later generation of Surface -devices, however they report 0 for their OEM platform revision. As the -_DSM does not exist on the devices requiring special casing, check for -the existance of the _DSM to determine if soc_button_array should be -loaded. - -Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") -Co-developed-by: Maximilian Luz - -Signed-off-by: Sachi King -Patchset: surface-button ---- - drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- - 1 file changed, 8 insertions(+), 25 deletions(-) - -diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c -index cb6ec59a045d..4e8944f59def 100644 ---- a/drivers/input/misc/soc_button_array.c -+++ b/drivers/input/misc/soc_button_array.c -@@ -474,8 +474,8 @@ static const struct soc_device_data soc_device_INT33D3 = { - * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned - * devices use MSHW0040 for power and volume buttons, however the way they - * have to be addressed differs. Make sure that we only load this drivers -- * for the correct devices by checking the OEM Platform Revision provided by -- * the _DSM method. -+ * for the correct devices by checking if the OEM Platform Revision DSM call -+ * exists. - */ - #define MSHW0040_DSM_REVISION 0x01 - #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision -@@ -486,31 +486,14 @@ static const guid_t MSHW0040_DSM_UUID = - static int soc_device_check_MSHW0040(struct device *dev) - { - acpi_handle handle = ACPI_HANDLE(dev); -- union acpi_object *result; -- u64 oem_platform_rev = 0; // valid revisions are nonzero -- -- // get OEM platform revision -- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -- MSHW0040_DSM_REVISION, -- MSHW0040_DSM_GET_OMPR, NULL, -- ACPI_TYPE_INTEGER); -- -- if (result) { -- oem_platform_rev = result->integer.value; -- ACPI_FREE(result); -- } -- -- /* -- * If the revision is zero here, the _DSM evaluation has failed. This -- * indicates that we have a Pro 4 or Book 1 and this driver should not -- * be used. -- */ -- if (oem_platform_rev == 0) -- return -ENODEV; -+ bool exists; - -- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); -+ // check if OEM platform revision DSM call exists -+ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ BIT(MSHW0040_DSM_GET_OMPR)); - -- return 0; -+ return exists ? 0 : -ENODEV; - } - - /* --- -2.34.1 - -From f0ad3ea54d4d370a357f076ee9600809506a52ed Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Tue, 5 Oct 2021 00:22:57 +1100 -Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd - variant - -The AMD variant of the Surface Laptop report 0 for their OEM platform -revision. The Surface devices that require the surfacepro3_button -driver do not have the _DSM that gets the OEM platform revision. If the -method does not exist, load surfacepro3_button. - -Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") -Co-developed-by: Maximilian Luz - -Signed-off-by: Sachi King -Patchset: surface-button ---- - drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- - 1 file changed, 6 insertions(+), 24 deletions(-) - -diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c -index 242fb690dcaf..30eea54dbb47 100644 ---- a/drivers/platform/surface/surfacepro3_button.c -+++ b/drivers/platform/surface/surfacepro3_button.c -@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) - /* - * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device - * ID (MSHW0040) for the power/volume buttons. Make sure this is the right -- * device by checking for the _DSM method and OEM Platform Revision. -+ * device by checking for the _DSM method and OEM Platform Revision DSM -+ * function. - * - * Returns true if the driver should bind to this device, i.e. the device is - * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. -@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) - static bool surface_button_check_MSHW0040(struct acpi_device *dev) - { - acpi_handle handle = dev->handle; -- union acpi_object *result; -- u64 oem_platform_rev = 0; // valid revisions are nonzero -- -- // get OEM platform revision -- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -- MSHW0040_DSM_REVISION, -- MSHW0040_DSM_GET_OMPR, -- NULL, ACPI_TYPE_INTEGER); -- -- /* -- * If evaluating the _DSM fails, the method is not present. This means -- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we -- * should use this driver. We use revision 0 indicating it is -- * unavailable. -- */ -- -- if (result) { -- oem_platform_rev = result->integer.value; -- ACPI_FREE(result); -- } -- -- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); - -- return oem_platform_rev == 0; -+ // make sure that OEM platform revision DSM call does not exist -+ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ BIT(MSHW0040_DSM_GET_OMPR)); - } - - --- -2.34.1 - diff --git a/patches/5.15/0009-surface-typecover.patch b/patches/5.15/0009-surface-typecover.patch deleted file mode 100644 index 05dd4c239..000000000 --- a/patches/5.15/0009-surface-typecover.patch +++ /dev/null @@ -1,233 +0,0 @@ -From a4e37266855a261d3128a24f92ade3376016b38b Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 5 Nov 2020 13:09:45 +0100 -Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when - suspending - -The Type Cover for Microsoft Surface devices supports a special usb -control request to disable or enable the built-in keyboard backlight. -On Windows, this request happens when putting the device into suspend or -resuming it, without it the backlight of the Type Cover will remain -enabled for some time even though the computer is suspended, which looks -weird to the user. - -So add support for this special usb control request to hid-multitouch, -which is the driver that's handling the Type Cover. - -The reason we have to use a pm_notifier for this instead of the usual -suspend/resume methods is that those won't get called in case the usb -device is already autosuspended. - -Also, if the device is autosuspended, we have to briefly autoresume it -in order to send the request. Doing that should be fine, the usb-core -driver does something similar during suspend inside choose_wakeup(). - -To make sure we don't send that request to every device but only to -devices which support it, add a new quirk -MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk -is only enabled for the usb id of the Surface Pro 2017 Type Cover, which -is where I confirmed that it's working. - -Patchset: surface-typecover ---- - drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- - 1 file changed, 98 insertions(+), 2 deletions(-) - -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index e1afddb7b33d..c15600b04b28 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -34,7 +34,10 @@ - #include - #include - #include -+#include - #include -+#include -+#include - #include - #include - #include -@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); - MODULE_LICENSE("GPL"); - - #include "hid-ids.h" -+#include "usbhid/usbhid.h" - - /* quirks to control the device */ - #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) -@@ -71,12 +75,15 @@ MODULE_LICENSE("GPL"); - #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) - #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) - #define MT_QUIRK_DISABLE_WAKEUP BIT(21) -+#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 - - #define MT_BUTTONTYPE_CLICKPAD 0 - -+#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+ - enum latency_mode { - HID_LATENCY_NORMAL = 0, - HID_LATENCY_HIGH = 1, -@@ -168,6 +175,8 @@ struct mt_device { - - struct list_head applications; - struct list_head reports; -+ -+ struct notifier_block pm_notifier; - }; - - static void mt_post_parse_default_settings(struct mt_device *td, -@@ -211,6 +220,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); - #define MT_CLS_GOOGLE 0x0111 - #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 - #define MT_CLS_SMART_TECH 0x0113 -+#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 - - #define MT_DEFAULT_MAXCONTACT 10 - #define MT_MAX_MAXCONTACT 250 -@@ -386,6 +396,16 @@ static const struct mt_class mt_classes[] = { - MT_QUIRK_CONTACT_CNT_ACCURATE | - MT_QUIRK_SEPARATE_APP_REPORT, - }, -+ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_ALWAYS_VALID | -+ MT_QUIRK_IGNORE_DUPLICATES | -+ MT_QUIRK_HOVERING | -+ MT_QUIRK_CONTACT_CNT_ACCURATE | -+ MT_QUIRK_STICKY_FINGERS | -+ MT_QUIRK_WIN8_PTP_BUTTONS, -+ .export_all_inputs = true -+ }, - { } - }; - -@@ -1698,6 +1718,69 @@ static void mt_expired_timeout(struct timer_list *t) - clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - -+static void get_type_cover_backlight_field(struct hid_device *hdev, -+ struct hid_field **field) -+{ -+ struct hid_report_enum *rep_enum; -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid -+ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { -+ *field = cur_field; -+ return; -+ } -+ } -+ } -+ } -+} -+ -+static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) -+{ -+ struct usb_device *udev = hid_to_usb_dev(hdev); -+ struct hid_field *field = NULL; -+ -+ /* Wake up the device in case it's already suspended */ -+ pm_runtime_get_sync(&udev->dev); -+ -+ get_type_cover_backlight_field(hdev, &field); -+ if (!field) { -+ hid_err(hdev, "couldn't find backlight field\n"); -+ goto out; -+ } -+ -+ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; -+ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); -+ -+out: -+ pm_runtime_put_sync(&udev->dev); -+} -+ -+static int mt_pm_notifier(struct notifier_block *notifier, -+ unsigned long pm_event, -+ void *unused) -+{ -+ struct mt_device *td = -+ container_of(notifier, struct mt_device, pm_notifier); -+ struct hid_device *hdev = td->hdev; -+ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { -+ if (pm_event == PM_SUSPEND_PREPARE) -+ update_keyboard_backlight(hdev, 0); -+ else if (pm_event == PM_POST_SUSPEND) -+ update_keyboard_backlight(hdev, 1); -+ } -+ -+ return NOTIFY_DONE; -+} -+ - static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - { - int ret, i; -@@ -1721,6 +1804,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; - hid_set_drvdata(hdev, td); - -+ td->pm_notifier.notifier_call = mt_pm_notifier; -+ register_pm_notifier(&td->pm_notifier); -+ - INIT_LIST_HEAD(&td->applications); - INIT_LIST_HEAD(&td->reports); - -@@ -1750,15 +1836,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - timer_setup(&td->release_timer, mt_expired_timeout, 0); - - ret = hid_parse(hdev); -- if (ret != 0) -+ if (ret != 0) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) - mt_fix_const_fields(hdev, HID_DG_CONTACTID); - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); -- if (ret) -+ if (ret) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); - if (ret) -@@ -1810,6 +1900,7 @@ static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); - -+ unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); - - sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); -@@ -2172,6 +2263,11 @@ static const struct hid_device_id mt_devices[] = { - MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, - USB_DEVICE_ID_XIROKU_CSR2) }, - -+ /* Microsoft Surface type cover */ -+ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, -+ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, -+ - /* Google MT devices */ - { .driver_data = MT_CLS_GOOGLE, - HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, --- -2.34.1 - diff --git a/patches/5.15/0010-cameras.patch b/patches/5.15/0010-cameras.patch deleted file mode 100644 index ab4635186..000000000 --- a/patches/5.15/0010-cameras.patch +++ /dev/null @@ -1,5414 +0,0 @@ -From 00ecb5c60a5bb401198d11f568f5a6289a41f5f8 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 5 Apr 2021 23:56:53 +0100 -Subject: [PATCH] media: ipu3-cio2: Toggle sensor streaming in pm runtime ops - -The .suspend() and .resume() runtime_pm operations for the ipu3-cio2 -driver currently do not handle the sensor's stream. Setting .s_stream() on -or off for the sensor subdev means that sensors will pause and resume the -stream at the appropriate time even if their drivers don't implement those -operations. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 15 ++++++++++++++- - 1 file changed, 14 insertions(+), 1 deletion(-) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -index 47db0ee0fcbf..7bb86e246ebe 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -@@ -1973,12 +1973,19 @@ static int __maybe_unused cio2_suspend(struct device *dev) - struct pci_dev *pci_dev = to_pci_dev(dev); - struct cio2_device *cio2 = pci_get_drvdata(pci_dev); - struct cio2_queue *q = cio2->cur_queue; -+ int r; - - dev_dbg(dev, "cio2 suspend\n"); - if (!cio2->streaming) - return 0; - - /* Stop stream */ -+ r = v4l2_subdev_call(q->sensor, video, s_stream, 0); -+ if (r) { -+ dev_err(dev, "failed to stop sensor streaming\n"); -+ return r; -+ } -+ - cio2_hw_exit(cio2, q); - synchronize_irq(pci_dev->irq); - -@@ -2013,8 +2020,14 @@ static int __maybe_unused cio2_resume(struct device *dev) - } - - r = cio2_hw_init(cio2, q); -- if (r) -+ if (r) { - dev_err(dev, "fail to init cio2 hw\n"); -+ return r; -+ } -+ -+ r = v4l2_subdev_call(q->sensor, video, s_stream, 1); -+ if (r) -+ dev_err(dev, "fail to start sensor streaming\n"); - - return r; - } --- -2.34.1 - -From 162dd2fdcaa9936c7b0bc22e1cacb6fdad220889 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 5 Apr 2021 23:56:54 +0100 -Subject: [PATCH] media: i2c: Add support for ov5693 sensor - -The OV5693 is a 5 Mpx CMOS image sensor, connected via MIPI CSI-2. The -chip is capable of a single lane configuration, but currently only two -lanes are supported. - -Most of the sensor's features are supported, with the main exception -being the lens correction algorithm. - -The driver provides all mandatory, optional and recommended V4L2 controls -for maximum compatibility with libcamera. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 7 + - drivers/media/i2c/Kconfig | 11 + - drivers/media/i2c/Makefile | 1 + - drivers/media/i2c/ov5693.c | 1557 ++++++++++++++++++++++++++++++++++++ - 4 files changed, 1576 insertions(+) - create mode 100644 drivers/media/i2c/ov5693.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 84e43aae33c0..53fd78cf9fd0 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -13878,6 +13878,13 @@ S: Maintained - T: git git://linuxtv.org/media_tree.git - F: drivers/media/i2c/ov5675.c - -+OMNIVISION OV5693 SENSOR DRIVER -+M: Daniel Scally -+L: linux-media@vger.kernel.org -+S: Maintained -+T: git git://linuxtv.org/media_tree.git -+F: drivers/media/i2c/ov5693.c -+ - OMNIVISION OV5695 SENSOR DRIVER - M: Shunqian Zheng - L: linux-media@vger.kernel.org -diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig -index 6157e73eef24..bec9b7fd3b4a 100644 ---- a/drivers/media/i2c/Kconfig -+++ b/drivers/media/i2c/Kconfig -@@ -1043,6 +1043,17 @@ config VIDEO_OV5675 - To compile this driver as a module, choose M here: the - module will be called ov5675. - -+config VIDEO_OV5693 -+ tristate "OmniVision OV5693 sensor support" -+ depends on I2C && VIDEO_V4L2 -+ select V4L2_FWNODE -+ help -+ This is a Video4Linux2 sensor driver for the OmniVision -+ OV5693 camera. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ov5693. -+ - config VIDEO_OV5695 - tristate "OmniVision OV5695 sensor support" - depends on I2C && VIDEO_V4L2 -diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile -index 83268f20aa3a..6b910ba2dde2 100644 ---- a/drivers/media/i2c/Makefile -+++ b/drivers/media/i2c/Makefile -@@ -75,6 +75,7 @@ obj-$(CONFIG_VIDEO_OV5647) += ov5647.o - obj-$(CONFIG_VIDEO_OV5648) += ov5648.o - obj-$(CONFIG_VIDEO_OV5670) += ov5670.o - obj-$(CONFIG_VIDEO_OV5675) += ov5675.o -+obj-$(CONFIG_VIDEO_OV5693) += ov5693.o - obj-$(CONFIG_VIDEO_OV5695) += ov5695.o - obj-$(CONFIG_VIDEO_OV6650) += ov6650.o - obj-$(CONFIG_VIDEO_OV7251) += ov7251.o -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -new file mode 100644 -index 000000000000..9499ee10f56c ---- /dev/null -+++ b/drivers/media/i2c/ov5693.c -@@ -0,0 +1,1557 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. -+ * -+ * Adapted from the atomisp-ov5693 driver, with contributions from: -+ * -+ * Daniel Scally -+ * Jean-Michel Hautbois -+ * Fabian Wuthrich -+ * Tsuchiya Yuto -+ * Jordan Hand -+ * Jake Day -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+/* System Control */ -+#define OV5693_SW_RESET_REG 0x0103 -+#define OV5693_SW_STREAM_REG 0x0100 -+#define OV5693_START_STREAMING 0x01 -+#define OV5693_STOP_STREAMING 0x00 -+#define OV5693_SW_RESET 0x01 -+ -+#define OV5693_REG_CHIP_ID_H 0x300a -+#define OV5693_REG_CHIP_ID_L 0x300b -+/* Yes, this is right. The datasheet for the OV5693 gives its ID as 0x5690 */ -+#define OV5693_CHIP_ID 0x5690 -+ -+/* Exposure */ -+#define OV5693_EXPOSURE_L_CTRL_HH_REG 0x3500 -+#define OV5693_EXPOSURE_L_CTRL_H_REG 0x3501 -+#define OV5693_EXPOSURE_L_CTRL_L_REG 0x3502 -+#define OV5693_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(14, 12)) >> 12) -+#define OV5693_EXPOSURE_CTRL_H(v) (((v) & GENMASK(11, 4)) >> 4) -+#define OV5693_EXPOSURE_CTRL_L(v) (((v) & GENMASK(3, 0)) << 4) -+#define OV5693_INTEGRATION_TIME_MARGIN 8 -+#define OV5693_EXPOSURE_MIN 1 -+#define OV5693_EXPOSURE_STEP 1 -+ -+/* Analogue Gain */ -+#define OV5693_GAIN_CTRL_H_REG 0x350a -+#define OV5693_GAIN_CTRL_H(v) (((v) >> 4) & GENMASK(2, 0)) -+#define OV5693_GAIN_CTRL_L_REG 0x350b -+#define OV5693_GAIN_CTRL_L(v) (((v) << 4) & GENMASK(7, 4)) -+#define OV5693_GAIN_MIN 1 -+#define OV5693_GAIN_MAX 127 -+#define OV5693_GAIN_DEF 8 -+#define OV5693_GAIN_STEP 1 -+ -+/* Digital Gain */ -+#define OV5693_MWB_RED_GAIN_H_REG 0x3400 -+#define OV5693_MWB_RED_GAIN_L_REG 0x3401 -+#define OV5693_MWB_GREEN_GAIN_H_REG 0x3402 -+#define OV5693_MWB_GREEN_GAIN_L_REG 0x3403 -+#define OV5693_MWB_BLUE_GAIN_H_REG 0x3404 -+#define OV5693_MWB_BLUE_GAIN_L_REG 0x3405 -+#define OV5693_MWB_GAIN_H_CTRL(v) (((v) >> 8) & GENMASK(3, 0)) -+#define OV5693_MWB_GAIN_L_CTRL(v) ((v) & GENMASK(7, 0)) -+#define OV5693_MWB_GAIN_MAX 0x0fff -+#define OV5693_DIGITAL_GAIN_MIN 1 -+#define OV5693_DIGITAL_GAIN_MAX 4095 -+#define OV5693_DIGITAL_GAIN_DEF 1024 -+#define OV5693_DIGITAL_GAIN_STEP 1 -+ -+/* Timing and Format */ -+#define OV5693_CROP_START_X_H_REG 0x3800 -+#define OV5693_CROP_START_X_H(v) (((v) & GENMASK(12, 8)) >> 8) -+#define OV5693_CROP_START_X_L_REG 0x3801 -+#define OV5693_CROP_START_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_START_Y_H_REG 0x3802 -+#define OV5693_CROP_START_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) -+#define OV5693_CROP_START_Y_L_REG 0x3803 -+#define OV5693_CROP_START_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_END_X_H_REG 0x3804 -+#define OV5693_CROP_END_X_H(v) (((v) & GENMASK(12, 8)) >> 8) -+#define OV5693_CROP_END_X_L_REG 0x3805 -+#define OV5693_CROP_END_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_END_Y_H_REG 0x3806 -+#define OV5693_CROP_END_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) -+#define OV5693_CROP_END_Y_L_REG 0x3807 -+#define OV5693_CROP_END_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OUTPUT_SIZE_X_H_REG 0x3808 -+#define OV5693_OUTPUT_SIZE_X_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OUTPUT_SIZE_X_L_REG 0x3809 -+#define OV5693_OUTPUT_SIZE_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OUTPUT_SIZE_Y_H_REG 0x380a -+#define OV5693_OUTPUT_SIZE_Y_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OUTPUT_SIZE_Y_L_REG 0x380b -+#define OV5693_OUTPUT_SIZE_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_TIMING_HTS_H_REG 0x380c -+#define OV5693_TIMING_HTS_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_TIMING_HTS_L_REG 0x380d -+#define OV5693_TIMING_HTS_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_FIXED_PPL 2688U -+ -+#define OV5693_TIMING_VTS_H_REG 0x380e -+#define OV5693_TIMING_VTS_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_TIMING_VTS_L_REG 0x380f -+#define OV5693_TIMING_VTS_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_TIMING_MAX_VTS 0xffff -+#define OV5693_TIMING_MIN_VTS 0x04 -+ -+#define OV5693_OFFSET_START_X_H_REG 0x3810 -+#define OV5693_OFFSET_START_X_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OFFSET_START_X_L_REG 0x3811 -+#define OV5693_OFFSET_START_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OFFSET_START_Y_H_REG 0x3812 -+#define OV5693_OFFSET_START_Y_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OFFSET_START_Y_L_REG 0x3813 -+#define OV5693_OFFSET_START_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_SUB_INC_X_REG 0x3814 -+#define OV5693_SUB_INC_Y_REG 0x3815 -+ -+#define OV5693_FORMAT1_REG 0x3820 -+#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -+#define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) -+#define OV5693_FORMAT1_VBIN_EN BIT(0) -+#define OV5693_FORMAT2_REG 0x3821 -+#define OV5693_FORMAT2_HDR_EN BIT(7) -+#define OV5693_FORMAT2_FLIP_HORZ_ISP_EN BIT(2) -+#define OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN BIT(1) -+#define OV5693_FORMAT2_HBIN_EN BIT(0) -+ -+#define OV5693_ISP_CTRL2_REG 0x5002 -+#define OV5693_ISP_SCALE_ENABLE BIT(7) -+ -+/* Pixel Array */ -+#define OV5693_NATIVE_WIDTH 2624 -+#define OV5693_NATIVE_HEIGHT 1956 -+#define OV5693_NATIVE_START_LEFT 0 -+#define OV5693_NATIVE_START_TOP 0 -+#define OV5693_ACTIVE_WIDTH 2592 -+#define OV5693_ACTIVE_HEIGHT 1944 -+#define OV5693_ACTIVE_START_LEFT 16 -+#define OV5693_ACTIVE_START_TOP 6 -+#define OV5693_MIN_CROP_WIDTH 2 -+#define OV5693_MIN_CROP_HEIGHT 2 -+ -+/* Test Pattern */ -+#define OV5693_TEST_PATTERN_REG 0x5e00 -+#define OV5693_TEST_PATTERN_ENABLE BIT(7) -+#define OV5693_TEST_PATTERN_ROLLING BIT(6) -+#define OV5693_TEST_PATTERN_RANDOM 0x01 -+#define OV5693_TEST_PATTERN_BARS 0x00 -+ -+/* System Frequencies */ -+#define OV5693_XVCLK_FREQ 19200000 -+#define OV5693_LINK_FREQ_400MHZ 400000000 -+#define OV5693_PIXEL_RATE 160000000 -+ -+/* Miscellaneous */ -+#define OV5693_NUM_SUPPLIES 2 -+ -+#define to_ov5693_sensor(x) container_of(x, struct ov5693_device, sd) -+ -+struct ov5693_reg { -+ u16 reg; -+ u8 val; -+}; -+ -+struct ov5693_reg_list { -+ u32 num_regs; -+ const struct ov5693_reg *regs; -+}; -+ -+struct ov5693_device { -+ struct i2c_client *client; -+ struct device *dev; -+ -+ /* Protect against concurrent changes to controls */ -+ struct mutex lock; -+ -+ struct gpio_desc *reset; -+ struct gpio_desc *powerdown; -+ struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; -+ struct clk *clk; -+ -+ struct ov5693_mode { -+ struct v4l2_rect crop; -+ struct v4l2_mbus_framefmt format; -+ bool binning_x; -+ bool binning_y; -+ unsigned int inc_x_odd; -+ unsigned int inc_y_odd; -+ unsigned int vts; -+ } mode; -+ bool streaming; -+ -+ struct v4l2_subdev sd; -+ struct media_pad pad; -+ -+ struct ov5693_v4l2_ctrls { -+ struct v4l2_ctrl_handler handler; -+ struct v4l2_ctrl *link_freq; -+ struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *exposure; -+ struct v4l2_ctrl *analogue_gain; -+ struct v4l2_ctrl *digital_gain; -+ struct v4l2_ctrl *hflip; -+ struct v4l2_ctrl *vflip; -+ struct v4l2_ctrl *hblank; -+ struct v4l2_ctrl *vblank; -+ struct v4l2_ctrl *test_pattern; -+ } ctrls; -+}; -+ -+static const struct ov5693_reg ov5693_global_regs[] = { -+ {0x3016, 0xf0}, -+ {0x3017, 0xf0}, -+ {0x3018, 0xf0}, -+ {0x3022, 0x01}, -+ {0x3028, 0x44}, -+ {0x3098, 0x02}, -+ {0x3099, 0x19}, -+ {0x309a, 0x02}, -+ {0x309b, 0x01}, -+ {0x309c, 0x00}, -+ {0x30a0, 0xd2}, -+ {0x30a2, 0x01}, -+ {0x30b2, 0x00}, -+ {0x30b3, 0x7d}, -+ {0x30b4, 0x03}, -+ {0x30b5, 0x04}, -+ {0x30b6, 0x01}, -+ {0x3104, 0x21}, -+ {0x3106, 0x00}, -+ {0x3406, 0x01}, -+ {0x3503, 0x07}, -+ {0x350b, 0x40}, -+ {0x3601, 0x0a}, -+ {0x3602, 0x38}, -+ {0x3612, 0x80}, -+ {0x3620, 0x54}, -+ {0x3621, 0xc7}, -+ {0x3622, 0x0f}, -+ {0x3625, 0x10}, -+ {0x3630, 0x55}, -+ {0x3631, 0xf4}, -+ {0x3632, 0x00}, -+ {0x3633, 0x34}, -+ {0x3634, 0x02}, -+ {0x364d, 0x0d}, -+ {0x364f, 0xdd}, -+ {0x3660, 0x04}, -+ {0x3662, 0x10}, -+ {0x3663, 0xf1}, -+ {0x3665, 0x00}, -+ {0x3666, 0x20}, -+ {0x3667, 0x00}, -+ {0x366a, 0x80}, -+ {0x3680, 0xe0}, -+ {0x3681, 0x00}, -+ {0x3700, 0x42}, -+ {0x3701, 0x14}, -+ {0x3702, 0xa0}, -+ {0x3703, 0xd8}, -+ {0x3704, 0x78}, -+ {0x3705, 0x02}, -+ {0x370a, 0x00}, -+ {0x370b, 0x20}, -+ {0x370c, 0x0c}, -+ {0x370d, 0x11}, -+ {0x370e, 0x00}, -+ {0x370f, 0x40}, -+ {0x3710, 0x00}, -+ {0x371a, 0x1c}, -+ {0x371b, 0x05}, -+ {0x371c, 0x01}, -+ {0x371e, 0xa1}, -+ {0x371f, 0x0c}, -+ {0x3721, 0x00}, -+ {0x3724, 0x10}, -+ {0x3726, 0x00}, -+ {0x372a, 0x01}, -+ {0x3730, 0x10}, -+ {0x3738, 0x22}, -+ {0x3739, 0xe5}, -+ {0x373a, 0x50}, -+ {0x373b, 0x02}, -+ {0x373c, 0x41}, -+ {0x373f, 0x02}, -+ {0x3740, 0x42}, -+ {0x3741, 0x02}, -+ {0x3742, 0x18}, -+ {0x3743, 0x01}, -+ {0x3744, 0x02}, -+ {0x3747, 0x10}, -+ {0x374c, 0x04}, -+ {0x3751, 0xf0}, -+ {0x3752, 0x00}, -+ {0x3753, 0x00}, -+ {0x3754, 0xc0}, -+ {0x3755, 0x00}, -+ {0x3756, 0x1a}, -+ {0x3758, 0x00}, -+ {0x3759, 0x0f}, -+ {0x376b, 0x44}, -+ {0x375c, 0x04}, -+ {0x3774, 0x10}, -+ {0x3776, 0x00}, -+ {0x377f, 0x08}, -+ {0x3780, 0x22}, -+ {0x3781, 0x0c}, -+ {0x3784, 0x2c}, -+ {0x3785, 0x1e}, -+ {0x378f, 0xf5}, -+ {0x3791, 0xb0}, -+ {0x3795, 0x00}, -+ {0x3796, 0x64}, -+ {0x3797, 0x11}, -+ {0x3798, 0x30}, -+ {0x3799, 0x41}, -+ {0x379a, 0x07}, -+ {0x379b, 0xb0}, -+ {0x379c, 0x0c}, -+ {0x3a04, 0x06}, -+ {0x3a05, 0x14}, -+ {0x3e07, 0x20}, -+ {0x4000, 0x08}, -+ {0x4001, 0x04}, -+ {0x4004, 0x08}, -+ {0x4006, 0x20}, -+ {0x4008, 0x24}, -+ {0x4009, 0x10}, -+ {0x4058, 0x00}, -+ {0x4101, 0xb2}, -+ {0x4307, 0x31}, -+ {0x4511, 0x05}, -+ {0x4512, 0x01}, -+ {0x481f, 0x30}, -+ {0x4826, 0x2c}, -+ {0x4d02, 0xfd}, -+ {0x4d03, 0xf5}, -+ {0x4d04, 0x0c}, -+ {0x4d05, 0xcc}, -+ {0x4837, 0x0a}, -+ {0x5003, 0x20}, -+ {0x5013, 0x00}, -+ {0x5842, 0x01}, -+ {0x5843, 0x2b}, -+ {0x5844, 0x01}, -+ {0x5845, 0x92}, -+ {0x5846, 0x01}, -+ {0x5847, 0x8f}, -+ {0x5848, 0x01}, -+ {0x5849, 0x0c}, -+ {0x5e10, 0x0c}, -+ {0x3820, 0x00}, -+ {0x3821, 0x1e}, -+ {0x5041, 0x14} -+}; -+ -+static const struct ov5693_reg_list ov5693_global_setting = { -+ .num_regs = ARRAY_SIZE(ov5693_global_regs), -+ .regs = ov5693_global_regs, -+}; -+ -+static const struct v4l2_rect ov5693_default_crop = { -+ .left = OV5693_ACTIVE_START_LEFT, -+ .top = OV5693_ACTIVE_START_TOP, -+ .width = OV5693_ACTIVE_WIDTH, -+ .height = OV5693_ACTIVE_HEIGHT, -+}; -+ -+static const struct v4l2_mbus_framefmt ov5693_default_fmt = { -+ .width = OV5693_ACTIVE_WIDTH, -+ .height = OV5693_ACTIVE_HEIGHT, -+ .code = MEDIA_BUS_FMT_SBGGR10_1X10, -+}; -+ -+static const s64 link_freq_menu_items[] = { -+ OV5693_LINK_FREQ_400MHZ -+}; -+ -+static const char * const ov5693_supply_names[] = { -+ "avdd", -+ "dovdd", -+}; -+ -+static const char * const ov5693_test_pattern_menu[] = { -+ "Disabled", -+ "Random Data", -+ "Colour Bars", -+ "Colour Bars with Rolling Bar" -+}; -+ -+static const u8 ov5693_test_pattern_bits[] = { -+ 0, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_RANDOM, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS | -+ OV5693_TEST_PATTERN_ROLLING, -+}; -+ -+/* I2C I/O Operations */ -+ -+static int ov5693_read_reg(struct ov5693_device *ov5693, u16 addr, u8 *value) -+{ -+ struct i2c_client *client = ov5693->client; -+ struct i2c_msg msgs[2]; -+ u8 addr_buf[2]; -+ u8 data_buf; -+ int ret; -+ -+ put_unaligned_be16(addr, addr_buf); -+ -+ /* 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 register value */ -+ msgs[1].addr = client->addr; -+ msgs[1].flags = I2C_M_RD; -+ msgs[1].len = 1; -+ msgs[1].buf = &data_buf; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ if (ret != ARRAY_SIZE(msgs)) -+ return -EIO; -+ -+ *value = data_buf; -+ -+ return 0; -+} -+ -+static void ov5693_write_reg(struct ov5693_device *ov5693, u16 addr, u8 value, -+ int *error) -+{ -+ unsigned char data[3] = { addr >> 8, addr & 0xff, value }; -+ int ret; -+ -+ if (*error < 0) -+ return; -+ -+ ret = i2c_master_send(ov5693->client, data, sizeof(data)); -+ if (ret < 0) { -+ dev_dbg(ov5693->dev, "i2c send error at address 0x%04x: %d\n", -+ addr, ret); -+ *error = ret; -+ } -+} -+ -+static int ov5693_write_reg_array(struct ov5693_device *ov5693, -+ const struct ov5693_reg_list *reglist) -+{ -+ unsigned int i; -+ int ret = 0; -+ -+ for (i = 0; i < reglist->num_regs; i++) -+ ov5693_write_reg(ov5693, reglist->regs[i].reg, -+ reglist->regs[i].val, &ret); -+ -+ return ret; -+} -+ -+static int ov5693_update_bits(struct ov5693_device *ov5693, u16 address, -+ u16 mask, u16 bits) -+{ -+ u8 value = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, address, &value); -+ if (ret) -+ return ret; -+ -+ value &= ~mask; -+ value |= bits; -+ -+ ov5693_write_reg(ov5693, address, value, &ret); -+ -+ return ret; -+} -+ -+/* V4L2 Controls Functions */ -+ -+static int ov5693_flip_vert_configure(struct ov5693_device *ov5693, bool enable) -+{ -+ u8 bits = OV5693_FORMAT1_FLIP_VERT_ISP_EN | -+ OV5693_FORMAT1_FLIP_VERT_SENSOR_EN; -+ int ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, bits, -+ enable ? bits : 0); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int ov5693_flip_horz_configure(struct ov5693_device *ov5693, bool enable) -+{ -+ u8 bits = OV5693_FORMAT2_FLIP_HORZ_ISP_EN | -+ OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN; -+ int ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, bits, -+ enable ? bits : 0); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int ov5693_get_exposure(struct ov5693_device *ov5693, s32 *value) -+{ -+ u8 exposure_hh = 0, exposure_h = 0, exposure_l = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, &exposure_hh); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, &exposure_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, &exposure_l); -+ if (ret) -+ return ret; -+ -+ /* The lowest 4 bits are unsupported fractional bits */ -+ *value = ((exposure_hh << 16) | (exposure_h << 8) | exposure_l) >> 4; -+ -+ return 0; -+} -+ -+static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, -+ OV5693_EXPOSURE_CTRL_HH(exposure), &ret); -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, -+ OV5693_EXPOSURE_CTRL_H(exposure), &ret); -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, -+ OV5693_EXPOSURE_CTRL_L(exposure), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_get_gain(struct ov5693_device *ov5693, u32 *gain) -+{ -+ u8 gain_l = 0, gain_h = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_H_REG, &gain_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_L_REG, &gain_l); -+ if (ret) -+ return ret; -+ -+ /* As with exposure, the lowest 4 bits are fractional bits. */ -+ *gain = ((gain_h << 8) | gain_l) >> 4; -+ -+ return ret; -+} -+ -+static int ov5693_digital_gain_configure(struct ov5693_device *ov5693, u32 gain) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_analog_gain_configure(struct ov5693_device *ov5693, u32 gain) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_L_REG, -+ OV5693_GAIN_CTRL_L(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_H_REG, -+ OV5693_GAIN_CTRL_H(gain), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_vts_configure(struct ov5693_device *ov5693, u32 vblank) -+{ -+ u16 vts = ov5693->mode.format.height + vblank; -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG, -+ OV5693_TIMING_VTS_H(vts), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG, -+ OV5693_TIMING_VTS_L(vts), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_test_pattern_configure(struct ov5693_device *ov5693, u32 idx) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_TEST_PATTERN_REG, -+ ov5693_test_pattern_bits[idx], &ret); -+ -+ return ret; -+} -+ -+static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *ov5693 = -+ container_of(ctrl->handler, struct ov5693_device, ctrls.handler); -+ int ret = 0; -+ -+ /* If VBLANK is altered we need to update exposure to compensate */ -+ if (ctrl->id == V4L2_CID_VBLANK) { -+ int exposure_max; -+ -+ exposure_max = ov5693->mode.format.height + ctrl->val - -+ OV5693_INTEGRATION_TIME_MARGIN; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, -+ ov5693->ctrls.exposure->minimum, -+ exposure_max, -+ ov5693->ctrls.exposure->step, -+ min(ov5693->ctrls.exposure->val, exposure_max)); -+ } -+ -+ /* Only apply changes to the controls if the device is powered up */ -+ if (!pm_runtime_get_if_in_use(ov5693->dev)) -+ return 0; -+ -+ switch (ctrl->id) { -+ case V4L2_CID_EXPOSURE: -+ ret = ov5693_exposure_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_ANALOGUE_GAIN: -+ ret = ov5693_analog_gain_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_DIGITAL_GAIN: -+ ret = ov5693_digital_gain_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_HFLIP: -+ ret = ov5693_flip_horz_configure(ov5693, !!ctrl->val); -+ break; -+ case V4L2_CID_VFLIP: -+ ret = ov5693_flip_vert_configure(ov5693, !!ctrl->val); -+ break; -+ case V4L2_CID_VBLANK: -+ ret = ov5693_vts_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_TEST_PATTERN: -+ ret = ov5693_test_pattern_configure(ov5693, ctrl->val); -+ break; -+ default: -+ ret = -EINVAL; -+ } -+ -+ pm_runtime_put(ov5693->dev); -+ -+ return ret; -+} -+ -+static int ov5693_g_volatile_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *ov5693 = -+ container_of(ctrl->handler, struct ov5693_device, ctrls.handler); -+ -+ switch (ctrl->id) { -+ case V4L2_CID_EXPOSURE_ABSOLUTE: -+ return ov5693_get_exposure(ov5693, &ctrl->val); -+ case V4L2_CID_AUTOGAIN: -+ return ov5693_get_gain(ov5693, &ctrl->val); -+ default: -+ return -EINVAL; -+ } -+} -+ -+static const struct v4l2_ctrl_ops ov5693_ctrl_ops = { -+ .s_ctrl = ov5693_s_ctrl, -+ .g_volatile_ctrl = ov5693_g_volatile_ctrl -+}; -+ -+/* System Control Functions */ -+ -+static int ov5693_mode_configure(struct ov5693_device *ov5693) -+{ -+ const struct ov5693_mode *mode = &ov5693->mode; -+ int ret = 0; -+ -+ /* Crop Start X */ -+ ov5693_write_reg(ov5693, OV5693_CROP_START_X_H_REG, -+ OV5693_CROP_START_X_H(mode->crop.left), &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_START_X_L_REG, -+ OV5693_CROP_START_X_L(mode->crop.left), &ret); -+ -+ /* Offset X */ -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_H_REG, -+ OV5693_OFFSET_START_X_H(0), &ret); -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_L_REG, -+ OV5693_OFFSET_START_X_L(0), &ret); -+ -+ /* Output Size X */ -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_H_REG, -+ OV5693_OUTPUT_SIZE_X_H(mode->format.width), &ret); -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_L_REG, -+ OV5693_OUTPUT_SIZE_X_L(mode->format.width), &ret); -+ -+ /* Crop End X */ -+ ov5693_write_reg(ov5693, OV5693_CROP_END_X_H_REG, -+ OV5693_CROP_END_X_H(mode->crop.left + mode->crop.width), -+ &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_END_X_L_REG, -+ OV5693_CROP_END_X_L(mode->crop.left + mode->crop.width), -+ &ret); -+ -+ /* Horizontal Total Size */ -+ ov5693_write_reg(ov5693, OV5693_TIMING_HTS_H_REG, -+ OV5693_TIMING_HTS_H(OV5693_FIXED_PPL), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_HTS_L_REG, -+ OV5693_TIMING_HTS_L(OV5693_FIXED_PPL), &ret); -+ -+ /* Crop Start Y */ -+ ov5693_write_reg(ov5693, OV5693_CROP_START_Y_H_REG, -+ OV5693_CROP_START_Y_H(mode->crop.top), &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_START_Y_L_REG, -+ OV5693_CROP_START_Y_L(mode->crop.top), &ret); -+ -+ /* Offset Y */ -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_H_REG, -+ OV5693_OFFSET_START_Y_H(0), &ret); -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_L_REG, -+ OV5693_OFFSET_START_Y_L(0), &ret); -+ -+ /* Output Size Y */ -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_H_REG, -+ OV5693_OUTPUT_SIZE_Y_H(mode->format.height), &ret); -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_L_REG, -+ OV5693_OUTPUT_SIZE_Y_L(mode->format.height), &ret); -+ -+ /* Crop End Y */ -+ ov5693_write_reg(ov5693, OV5693_CROP_END_Y_H_REG, -+ OV5693_CROP_END_Y_H(mode->crop.top + mode->crop.height), -+ &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_END_Y_L_REG, -+ OV5693_CROP_END_Y_L(mode->crop.top + mode->crop.height), -+ &ret); -+ -+ /* Vertical Total Size */ -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG, -+ OV5693_TIMING_VTS_H(mode->vts), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG, -+ OV5693_TIMING_VTS_L(mode->vts), &ret); -+ -+ /* Subsample X increase */ -+ ov5693_write_reg(ov5693, OV5693_SUB_INC_X_REG, -+ ((mode->inc_x_odd << 4) & 0xf0) | 0x01, &ret); -+ /* Subsample Y increase */ -+ ov5693_write_reg(ov5693, OV5693_SUB_INC_Y_REG, -+ ((mode->inc_y_odd << 4) & 0xf0) | 0x01, &ret); -+ -+ /* Binning */ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, -+ OV5693_FORMAT1_VBIN_EN, -+ mode->binning_y ? OV5693_FORMAT1_VBIN_EN : 0); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, -+ OV5693_FORMAT2_HBIN_EN, -+ mode->binning_x ? OV5693_FORMAT2_HBIN_EN : 0); -+ -+ return ret; -+} -+ -+static int ov5693_sw_standby(struct ov5693_device *ov5693, bool standby) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_SW_STREAM_REG, -+ standby ? OV5693_STOP_STREAMING : OV5693_START_STREAMING, -+ &ret); -+ -+ return ret; -+} -+ -+static int ov5693_sw_reset(struct ov5693_device *ov5693) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_SW_RESET_REG, OV5693_SW_RESET, &ret); -+ -+ return ret; -+} -+ -+static int ov5693_sensor_init(struct ov5693_device *ov5693) -+{ -+ int ret = 0; -+ -+ ret = ov5693_sw_reset(ov5693); -+ if (ret) { -+ dev_err(ov5693->dev, "%s software reset error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg_array(ov5693, &ov5693_global_setting); -+ if (ret) { -+ dev_err(ov5693->dev, "%s global settings error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_mode_configure(ov5693); -+ if (ret) { -+ dev_err(ov5693->dev, "%s mode configure error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_sw_standby(ov5693, true); -+ if (ret) -+ dev_err(ov5693->dev, "%s software standby error\n", __func__); -+ -+ return ret; -+} -+ -+static void ov5693_sensor_powerdown(struct ov5693_device *ov5693) -+{ -+ gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); -+ -+ regulator_bulk_disable(OV5693_NUM_SUPPLIES, ov5693->supplies); -+ -+ clk_disable_unprepare(ov5693->clk); -+} -+ -+static int ov5693_sensor_powerup(struct ov5693_device *ov5693) -+{ -+ int ret; -+ -+ gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); -+ -+ ret = clk_prepare_enable(ov5693->clk); -+ if (ret) { -+ dev_err(ov5693->dev, "Failed to enable clk\n"); -+ goto fail_power; -+ } -+ -+ ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, ov5693->supplies); -+ if (ret) { -+ dev_err(ov5693->dev, "Failed to enable regulators\n"); -+ goto fail_power; -+ } -+ -+ gpiod_set_value_cansleep(ov5693->powerdown, 0); -+ gpiod_set_value_cansleep(ov5693->reset, 0); -+ -+ usleep_range(5000, 7500); -+ -+ return 0; -+ -+fail_power: -+ ov5693_sensor_powerdown(ov5693); -+ return ret; -+} -+ -+static int __maybe_unused ov5693_sensor_suspend(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ ov5693_sensor_powerdown(ov5693); -+ -+ return 0; -+} -+ -+static int __maybe_unused ov5693_sensor_resume(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ int ret; -+ -+ mutex_lock(&ov5693->lock); -+ -+ ret = ov5693_sensor_powerup(ov5693); -+ if (ret) -+ goto out_unlock; -+ -+ ret = ov5693_sensor_init(ov5693); -+ if (ret) { -+ dev_err(dev, "ov5693 sensor init failure\n"); -+ goto err_power; -+ } -+ -+ goto out_unlock; -+ -+err_power: -+ ov5693_sensor_powerdown(ov5693); -+out_unlock: -+ mutex_unlock(&ov5693->lock); -+ return ret; -+} -+ -+static int ov5693_detect(struct ov5693_device *ov5693) -+{ -+ u8 id_l = 0, id_h = 0; -+ u16 id = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_H, &id_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_L, &id_l); -+ if (ret) -+ return ret; -+ -+ id = (id_h << 8) | id_l; -+ -+ if (id != OV5693_CHIP_ID) { -+ dev_err(ov5693->dev, "sensor ID mismatch. Found 0x%04x\n", id); -+ return -ENODEV; -+ } -+ -+ return 0; -+} -+ -+/* V4L2 Framework callbacks */ -+ -+static unsigned int __ov5693_calc_vts(u32 height) -+{ -+ /* -+ * We need to set a sensible default VTS for whatever format height we -+ * happen to be given from set_fmt(). This function just targets -+ * an even multiple of 30fps. -+ */ -+ -+ unsigned int tgt_fps; -+ -+ tgt_fps = rounddown(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / height, 30); -+ -+ return ALIGN_DOWN(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / tgt_fps, 2); -+} -+ -+static struct v4l2_mbus_framefmt * -+__ov5693_get_pad_format(struct ov5693_device *ov5693, -+ struct v4l2_subdev_state *state, -+ unsigned int pad, enum v4l2_subdev_format_whence which) -+{ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ return v4l2_subdev_get_try_format(&ov5693->sd, state, pad); -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ return &ov5693->mode.format; -+ default: -+ return NULL; -+ } -+} -+ -+static struct v4l2_rect * -+__ov5693_get_pad_crop(struct ov5693_device *ov5693, -+ struct v4l2_subdev_state *state, -+ unsigned int pad, enum v4l2_subdev_format_whence which) -+{ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ return v4l2_subdev_get_try_crop(&ov5693->sd, state, pad); -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ return &ov5693->mode.crop; -+ } -+ -+ return NULL; -+} -+ -+static int ov5693_get_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_format *format) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ format->format = ov5693->mode.format; -+ -+ return 0; -+} -+ -+static int ov5693_set_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_format *format) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ const struct v4l2_rect *crop; -+ struct v4l2_mbus_framefmt *fmt; -+ unsigned int hratio, vratio; -+ unsigned int width, height; -+ unsigned int hblank; -+ int exposure_max; -+ int ret = 0; -+ -+ crop = __ov5693_get_pad_crop(ov5693, state, format->pad, format->which); -+ -+ /* -+ * Align to two to simplify the binning calculations below, and clamp -+ * the requested format at the crop rectangle -+ */ -+ width = clamp_t(unsigned int, ALIGN(format->format.width, 2), -+ OV5693_MIN_CROP_WIDTH, crop->width); -+ height = clamp_t(unsigned int, ALIGN(format->format.height, 2), -+ OV5693_MIN_CROP_HEIGHT, crop->height); -+ -+ /* -+ * We can only support setting either the dimensions of the crop rect -+ * or those dimensions binned (separately) by a factor of two. -+ */ -+ hratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->width, width), 1, 2); -+ vratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->height, height), 1, 2); -+ -+ fmt = __ov5693_get_pad_format(ov5693, state, format->pad, format->which); -+ -+ fmt->width = crop->width / hratio; -+ fmt->height = crop->height / vratio; -+ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ -+ format->format = *fmt; -+ -+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) -+ return ret; -+ -+ mutex_lock(&ov5693->lock); -+ -+ ov5693->mode.binning_x = hratio > 1 ? true : false; -+ ov5693->mode.inc_x_odd = hratio > 1 ? 3 : 1; -+ ov5693->mode.binning_y = vratio > 1 ? true : false; -+ ov5693->mode.inc_y_odd = vratio > 1 ? 3 : 1; -+ -+ ov5693->mode.vts = __ov5693_calc_vts(fmt->height); -+ -+ __v4l2_ctrl_modify_range(ov5693->ctrls.vblank, -+ OV5693_TIMING_MIN_VTS, -+ OV5693_TIMING_MAX_VTS - fmt->height, -+ 1, ov5693->mode.vts - fmt->height); -+ __v4l2_ctrl_s_ctrl(ov5693->ctrls.vblank, -+ ov5693->mode.vts - fmt->height); -+ -+ hblank = OV5693_FIXED_PPL - fmt->width; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.hblank, hblank, hblank, 1, -+ hblank); -+ -+ exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, -+ ov5693->ctrls.exposure->minimum, exposure_max, -+ ov5693->ctrls.exposure->step, -+ min(ov5693->ctrls.exposure->val, exposure_max)); -+ -+ mutex_unlock(&ov5693->lock); -+ return ret; -+} -+ -+static int ov5693_get_selection(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ switch (sel->target) { -+ case V4L2_SEL_TGT_CROP: -+ mutex_lock(&ov5693->lock); -+ sel->r = *__ov5693_get_pad_crop(ov5693, state, sel->pad, -+ sel->which); -+ mutex_unlock(&ov5693->lock); -+ break; -+ case V4L2_SEL_TGT_NATIVE_SIZE: -+ sel->r.top = 0; -+ sel->r.left = 0; -+ sel->r.width = OV5693_NATIVE_WIDTH; -+ sel->r.height = OV5693_NATIVE_HEIGHT; -+ break; -+ case V4L2_SEL_TGT_CROP_BOUNDS: -+ case V4L2_SEL_TGT_CROP_DEFAULT: -+ sel->r.top = OV5693_ACTIVE_START_TOP; -+ sel->r.left = OV5693_ACTIVE_START_LEFT; -+ sel->r.width = OV5693_ACTIVE_WIDTH; -+ sel->r.height = OV5693_ACTIVE_HEIGHT; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int ov5693_set_selection(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(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 boundaries of the crop rectangle to the size of the sensor -+ * pixel array. Align to multiples of 2 to ensure Bayer pattern isn't -+ * disrupted. -+ */ -+ rect.left = clamp(ALIGN(sel->r.left, 2), OV5693_NATIVE_START_LEFT, -+ OV5693_NATIVE_WIDTH); -+ rect.top = clamp(ALIGN(sel->r.top, 2), OV5693_NATIVE_START_TOP, -+ OV5693_NATIVE_HEIGHT); -+ rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), -+ OV5693_MIN_CROP_WIDTH, OV5693_NATIVE_WIDTH); -+ rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), -+ OV5693_MIN_CROP_HEIGHT, OV5693_NATIVE_HEIGHT); -+ -+ /* Make sure the crop rectangle isn't outside the bounds of the array */ -+ rect.width = min_t(unsigned int, rect.width, -+ OV5693_NATIVE_WIDTH - rect.left); -+ rect.height = min_t(unsigned int, rect.height, -+ OV5693_NATIVE_HEIGHT - rect.top); -+ -+ __crop = __ov5693_get_pad_crop(ov5693, state, sel->pad, sel->which); -+ -+ if (rect.width != __crop->width || rect.height != __crop->height) { -+ /* -+ * Reset the output image size if the crop rectangle size has -+ * been modified. -+ */ -+ format = __ov5693_get_pad_format(ov5693, state, sel->pad, sel->which); -+ format->width = rect.width; -+ format->height = rect.height; -+ } -+ -+ *__crop = rect; -+ sel->r = rect; -+ -+ return 0; -+} -+ -+static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ int ret; -+ -+ if (enable) { -+ ret = pm_runtime_get_sync(ov5693->dev); -+ if (ret < 0) -+ goto err_power_down; -+ -+ ret = __v4l2_ctrl_handler_setup(&ov5693->ctrls.handler); -+ if (ret) -+ goto err_power_down; -+ } -+ -+ mutex_lock(&ov5693->lock); -+ ret = ov5693_sw_standby(ov5693, !enable); -+ mutex_unlock(&ov5693->lock); -+ -+ if (ret) -+ goto err_power_down; -+ ov5693->streaming = !!enable; -+ -+ if (!enable) -+ pm_runtime_put(ov5693->dev); -+ -+ return 0; -+err_power_down: -+ pm_runtime_put_noidle(ov5693->dev); -+ return ret; -+} -+ -+static int ov5693_g_frame_interval(struct v4l2_subdev *sd, -+ struct v4l2_subdev_frame_interval *interval) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ unsigned int framesize = OV5693_FIXED_PPL * (ov5693->mode.format.height + -+ ov5693->ctrls.vblank->val); -+ unsigned int fps = DIV_ROUND_CLOSEST(OV5693_PIXEL_RATE, framesize); -+ -+ interval->interval.numerator = 1; -+ interval->interval.denominator = fps; -+ -+ return 0; -+} -+ -+static int ov5693_enum_mbus_code(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_mbus_code_enum *code) -+{ -+ /* Only a single mbus format is supported */ -+ if (code->index > 0) -+ return -EINVAL; -+ -+ code->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ return 0; -+} -+ -+static int ov5693_enum_frame_size(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_frame_size_enum *fse) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ struct v4l2_rect *__crop; -+ -+ if (fse->index > 1 || fse->code != MEDIA_BUS_FMT_SBGGR10_1X10) -+ return -EINVAL; -+ -+ __crop = __ov5693_get_pad_crop(ov5693, state, fse->pad, fse->which); -+ if (!__crop) -+ return -EINVAL; -+ -+ fse->min_width = __crop->width / (fse->index + 1); -+ fse->min_height = __crop->height / (fse->index + 1); -+ fse->max_width = fse->min_width; -+ fse->max_height = fse->min_height; -+ -+ return 0; -+} -+ -+static const struct v4l2_subdev_video_ops ov5693_video_ops = { -+ .s_stream = ov5693_s_stream, -+ .g_frame_interval = ov5693_g_frame_interval, -+}; -+ -+static const struct v4l2_subdev_pad_ops ov5693_pad_ops = { -+ .enum_mbus_code = ov5693_enum_mbus_code, -+ .enum_frame_size = ov5693_enum_frame_size, -+ .get_fmt = ov5693_get_fmt, -+ .set_fmt = ov5693_set_fmt, -+ .get_selection = ov5693_get_selection, -+ .set_selection = ov5693_set_selection, -+}; -+ -+static const struct v4l2_subdev_ops ov5693_ops = { -+ .video = &ov5693_video_ops, -+ .pad = &ov5693_pad_ops, -+}; -+ -+/* Sensor and Driver Configuration Functions */ -+ -+static int ov5693_init_controls(struct ov5693_device *ov5693) -+{ -+ const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; -+ struct v4l2_fwnode_device_properties props; -+ int vblank_max, vblank_def; -+ int exposure_max; -+ int hblank; -+ int ret; -+ -+ ret = v4l2_ctrl_handler_init(&ov5693->ctrls.handler, 12); -+ if (ret) -+ return ret; -+ -+ /* link freq */ -+ ov5693->ctrls.link_freq = v4l2_ctrl_new_int_menu(&ov5693->ctrls.handler, -+ NULL, V4L2_CID_LINK_FREQ, -+ 0, 0, link_freq_menu_items); -+ if (ov5693->ctrls.link_freq) -+ ov5693->ctrls.link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ -+ /* pixel rate */ -+ ov5693->ctrls.pixel_rate = v4l2_ctrl_new_std(&ov5693->ctrls.handler, NULL, -+ V4L2_CID_PIXEL_RATE, 0, -+ OV5693_PIXEL_RATE, 1, -+ OV5693_PIXEL_RATE); -+ -+ /* Exposure */ -+ exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN; -+ ov5693->ctrls.exposure = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_EXPOSURE, -+ OV5693_EXPOSURE_MIN, -+ exposure_max, -+ OV5693_EXPOSURE_STEP, -+ exposure_max); -+ -+ /* Gain */ -+ ov5693->ctrls.analogue_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, -+ ops, V4L2_CID_ANALOGUE_GAIN, -+ OV5693_GAIN_MIN, -+ OV5693_GAIN_MAX, -+ OV5693_GAIN_STEP, -+ OV5693_GAIN_DEF); -+ -+ ov5693->ctrls.digital_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_DIGITAL_GAIN, -+ OV5693_DIGITAL_GAIN_MIN, -+ OV5693_DIGITAL_GAIN_MAX, -+ OV5693_DIGITAL_GAIN_STEP, -+ OV5693_DIGITAL_GAIN_DEF); -+ -+ /* Flip */ -+ ov5693->ctrls.hflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_HFLIP, 0, 1, 1, 0); -+ -+ ov5693->ctrls.vflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_VFLIP, 0, 1, 1, 0); -+ -+ hblank = OV5693_FIXED_PPL - ov5693->mode.format.width; -+ ov5693->ctrls.hblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_HBLANK, hblank, -+ hblank, 1, hblank); -+ -+ if (ov5693->ctrls.hblank) -+ ov5693->ctrls.hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ -+ vblank_max = OV5693_TIMING_MAX_VTS - ov5693->mode.format.height; -+ vblank_def = ov5693->mode.vts - ov5693->mode.format.height; -+ ov5693->ctrls.vblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_VBLANK, -+ OV5693_TIMING_MIN_VTS, -+ vblank_max, 1, vblank_def); -+ -+ ov5693->ctrls.test_pattern = v4l2_ctrl_new_std_menu_items( -+ &ov5693->ctrls.handler, ops, -+ V4L2_CID_TEST_PATTERN, -+ ARRAY_SIZE(ov5693_test_pattern_menu) - 1, -+ 0, 0, ov5693_test_pattern_menu); -+ -+ if (ov5693->ctrls.handler.error) { -+ dev_err(ov5693->dev, "Error initialising v4l2 ctrls\n"); -+ ret = ov5693->ctrls.handler.error; -+ goto err_free_handler; -+ } -+ -+ /* set properties from fwnode (e.g. rotation, orientation) */ -+ ret = v4l2_fwnode_device_parse(ov5693->dev, &props); -+ if (ret) -+ goto err_free_handler; -+ -+ ret = v4l2_ctrl_new_fwnode_properties(&ov5693->ctrls.handler, ops, -+ &props); -+ if (ret) -+ goto err_free_handler; -+ -+ /* Use same lock for controls as for everything else. */ -+ ov5693->ctrls.handler.lock = &ov5693->lock; -+ ov5693->sd.ctrl_handler = &ov5693->ctrls.handler; -+ -+ return 0; -+ -+err_free_handler: -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ return ret; -+} -+ -+static int ov5693_configure_gpios(struct ov5693_device *ov5693) -+{ -+ ov5693->reset = devm_gpiod_get_optional(ov5693->dev, "reset", -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->reset)) { -+ dev_err(ov5693->dev, "Error fetching reset GPIO\n"); -+ return PTR_ERR(ov5693->reset); -+ } -+ -+ ov5693->powerdown = devm_gpiod_get_optional(ov5693->dev, "powerdown", -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->powerdown)) { -+ dev_err(ov5693->dev, "Error fetching powerdown GPIO\n"); -+ return PTR_ERR(ov5693->powerdown); -+ } -+ -+ return 0; -+} -+ -+static int ov5693_get_regulators(struct ov5693_device *ov5693) -+{ -+ unsigned int i; -+ -+ for (i = 0; i < OV5693_NUM_SUPPLIES; i++) -+ ov5693->supplies[i].supply = ov5693_supply_names[i]; -+ -+ return devm_regulator_bulk_get(ov5693->dev, OV5693_NUM_SUPPLIES, -+ ov5693->supplies); -+} -+ -+static int ov5693_probe(struct i2c_client *client) -+{ -+ struct fwnode_handle *fwnode = dev_fwnode(&client->dev); -+ struct fwnode_handle *endpoint; -+ struct ov5693_device *ov5693; -+ u32 clk_rate; -+ int ret = 0; -+ -+ endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); -+ if (!endpoint && !IS_ERR_OR_NULL(fwnode->secondary)) -+ endpoint = fwnode_graph_get_next_endpoint(fwnode->secondary, NULL); -+ if (!endpoint) -+ return -EPROBE_DEFER; -+ -+ ov5693 = devm_kzalloc(&client->dev, sizeof(*ov5693), GFP_KERNEL); -+ if (!ov5693) -+ return -ENOMEM; -+ -+ ov5693->client = client; -+ ov5693->dev = &client->dev; -+ -+ mutex_init(&ov5693->lock); -+ -+ v4l2_i2c_subdev_init(&ov5693->sd, client, &ov5693_ops); -+ -+ ov5693->clk = devm_clk_get(&client->dev, "xvclk"); -+ if (IS_ERR(ov5693->clk)) { -+ dev_err(&client->dev, "Error getting clock\n"); -+ return PTR_ERR(ov5693->clk); -+ } -+ -+ clk_rate = clk_get_rate(ov5693->clk); -+ if (clk_rate != OV5693_XVCLK_FREQ) { -+ dev_err(&client->dev, "Unsupported clk freq %u, expected %u\n", -+ clk_rate, OV5693_XVCLK_FREQ); -+ return -EINVAL; -+ } -+ -+ ret = ov5693_configure_gpios(ov5693); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_get_regulators(ov5693); -+ if (ret) { -+ dev_err(&client->dev, "Error fetching regulators\n"); -+ return ret; -+ } -+ -+ ov5693->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; -+ ov5693->pad.flags = MEDIA_PAD_FL_SOURCE; -+ ov5693->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; -+ -+ ov5693->mode.crop = ov5693_default_crop; -+ ov5693->mode.format = ov5693_default_fmt; -+ ov5693->mode.vts = __ov5693_calc_vts(ov5693->mode.format.height); -+ -+ ret = ov5693_init_controls(ov5693); -+ if (ret) -+ return ret; -+ -+ ret = media_entity_pads_init(&ov5693->sd.entity, 1, &ov5693->pad); -+ if (ret) -+ goto err_ctrl_handler_free; -+ -+ /* -+ * We need the driver to work in the event that pm runtime is disable in -+ * the kernel, so power up and verify the chip now. In the event that -+ * runtime pm is disabled this will leave the chip on, so that streaming -+ * will work. -+ */ -+ -+ ret = ov5693_sensor_powerup(ov5693); -+ if (ret) -+ goto err_media_entity_cleanup; -+ -+ ret = ov5693_detect(ov5693); -+ if (ret) -+ goto err_powerdown; -+ -+ pm_runtime_set_active(&client->dev); -+ pm_runtime_get_noresume(&client->dev); -+ pm_runtime_enable(&client->dev); -+ -+ ret = v4l2_async_register_subdev_sensor(&ov5693->sd); -+ if (ret) { -+ dev_err(&client->dev, "failed to register V4L2 subdev: %d", -+ ret); -+ goto err_pm_runtime; -+ } -+ -+ pm_runtime_set_autosuspend_delay(&client->dev, 1000); -+ pm_runtime_use_autosuspend(&client->dev); -+ pm_runtime_put_autosuspend(&client->dev); -+ -+ return ret; -+ -+err_pm_runtime: -+ pm_runtime_disable(&client->dev); -+ pm_runtime_put_noidle(&client->dev); -+err_powerdown: -+ ov5693_sensor_powerdown(ov5693); -+err_media_entity_cleanup: -+ media_entity_cleanup(&ov5693->sd.entity); -+err_ctrl_handler_free: -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ -+ return ret; -+} -+ -+static int ov5693_remove(struct i2c_client *client) -+{ -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ v4l2_async_unregister_subdev(sd); -+ media_entity_cleanup(&ov5693->sd.entity); -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ mutex_destroy(&ov5693->lock); -+ -+ /* -+ * Disable runtime PM. In case runtime PM is disabled in the kernel, -+ * make sure to turn power off manually. -+ */ -+ pm_runtime_disable(&client->dev); -+ if (!pm_runtime_status_suspended(&client->dev)) -+ ov5693_sensor_powerdown(ov5693); -+ pm_runtime_set_suspended(&client->dev); -+ -+ return 0; -+} -+ -+static const struct dev_pm_ops ov5693_pm_ops = { -+ SET_RUNTIME_PM_OPS(ov5693_sensor_suspend, ov5693_sensor_resume, NULL) -+}; -+ -+static const struct acpi_device_id ov5693_acpi_match[] = { -+ {"INT33BE"}, -+ {}, -+}; -+MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); -+ -+static struct i2c_driver ov5693_driver = { -+ .driver = { -+ .name = "ov5693", -+ .acpi_match_table = ov5693_acpi_match, -+ .pm = &ov5693_pm_ops, -+ }, -+ .probe_new = ov5693_probe, -+ .remove = ov5693_remove, -+}; -+module_i2c_driver(ov5693_driver); -+ -+MODULE_DESCRIPTION("A low-level driver for OmniVision 5693 sensors"); -+MODULE_LICENSE("GPL"); --- -2.34.1 - -From 136eeb8f124636d10ebe2af30a0420578744fd83 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Fri, 22 Jan 2021 20:58:13 +0100 -Subject: [PATCH] cio2-bridge: Parse sensor orientation and rotation -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -The sensor orientation is read from the _PLC ACPI buffer and converted -to a v4l2 format. - -See https://uefi.org/sites/default/files/resources/ACPI_6_3_final_Jan30.pdf -page 351 for a definition of the Panel property. - -The sensor rotation is read from the SSDB ACPI buffer and converted into -degrees. - -Signed-off-by: Fabian Wüthrich -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 45 ++++++++++++++++++++-- - drivers/media/pci/intel/ipu3/cio2-bridge.h | 3 ++ - 2 files changed, 44 insertions(+), 4 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index 30d29b96a339..77c97bf6521e 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -29,6 +29,7 @@ static const struct cio2_sensor_config cio2_supported_sensors[] = { - static const struct cio2_property_names prop_names = { - .clock_frequency = "clock-frequency", - .rotation = "rotation", -+ .orientation = "orientation", - .bus_type = "bus-type", - .data_lanes = "data-lanes", - .remote_endpoint = "remote-endpoint", -@@ -72,11 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, - return ret; - } - -+static u32 cio2_bridge_parse_rotation(u8 rotation) -+{ -+ if (rotation == 1) -+ return 180; -+ return 0; -+} -+ -+static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) -+{ -+ switch (panel) { -+ case 4: -+ return V4L2_FWNODE_ORIENTATION_FRONT; -+ case 5: -+ return V4L2_FWNODE_ORIENTATION_BACK; -+ default: -+ return V4L2_FWNODE_ORIENTATION_EXTERNAL; -+ } -+} -+ - static void cio2_bridge_create_fwnode_properties( - struct cio2_sensor *sensor, - struct cio2_bridge *bridge, - const struct cio2_sensor_config *cfg) - { -+ u32 rotation; -+ enum v4l2_fwnode_orientation orientation; -+ -+ rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); -+ orientation = cio2_bridge_parse_orientation(sensor->pld->panel); -+ - sensor->prop_names = prop_names; - - sensor->local_ref[0] = SOFTWARE_NODE_REFERENCE(&sensor->swnodes[SWNODE_CIO2_ENDPOINT]); -@@ -85,9 +111,12 @@ static void cio2_bridge_create_fwnode_properties( - sensor->dev_properties[0] = PROPERTY_ENTRY_U32( - sensor->prop_names.clock_frequency, - sensor->ssdb.mclkspeed); -- sensor->dev_properties[1] = PROPERTY_ENTRY_U8( -+ sensor->dev_properties[1] = PROPERTY_ENTRY_U32( - sensor->prop_names.rotation, -- sensor->ssdb.degree); -+ rotation); -+ sensor->dev_properties[2] = PROPERTY_ENTRY_U32( -+ sensor->prop_names.orientation, -+ orientation); - - sensor->ep_properties[0] = PROPERTY_ENTRY_U32( - sensor->prop_names.bus_type, -@@ -159,6 +188,7 @@ static void cio2_bridge_unregister_sensors(struct cio2_bridge *bridge) - for (i = 0; i < bridge->n_sensors; i++) { - sensor = &bridge->sensors[i]; - software_node_unregister_nodes(sensor->swnodes); -+ ACPI_FREE(sensor->pld); - acpi_dev_put(sensor->adev); - } - } -@@ -170,6 +200,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - struct fwnode_handle *fwnode; - struct cio2_sensor *sensor; - struct acpi_device *adev; -+ acpi_status status; - int ret; - - for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { -@@ -191,11 +222,15 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - if (ret) - goto err_put_adev; - -+ status = acpi_get_physical_device_location(adev->handle, &sensor->pld); -+ if (ACPI_FAILURE(status)) -+ goto err_put_adev; -+ - if (sensor->ssdb.lanes > CIO2_MAX_LANES) { - dev_err(&adev->dev, - "Number of lanes in SSDB is invalid\n"); - ret = -EINVAL; -- goto err_put_adev; -+ goto err_free_pld; - } - - cio2_bridge_create_fwnode_properties(sensor, bridge, cfg); -@@ -203,7 +238,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - - ret = software_node_register_nodes(sensor->swnodes); - if (ret) -- goto err_put_adev; -+ goto err_free_pld; - - fwnode = software_node_fwnode(&sensor->swnodes[ - SWNODE_SENSOR_HID]); -@@ -225,6 +260,8 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, - - err_free_swnodes: - software_node_unregister_nodes(sensor->swnodes); -+err_free_pld: -+ ACPI_FREE(sensor->pld); - err_put_adev: - acpi_dev_put(adev); - return ret; -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index dd0ffcafa489..924d99d20328 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -80,6 +80,7 @@ struct cio2_sensor_ssdb { - struct cio2_property_names { - char clock_frequency[16]; - char rotation[9]; -+ char orientation[12]; - char bus_type[9]; - char data_lanes[11]; - char remote_endpoint[16]; -@@ -106,6 +107,8 @@ struct cio2_sensor { - struct cio2_node_names node_names; - - struct cio2_sensor_ssdb ssdb; -+ struct acpi_pld_info *pld; -+ - struct cio2_property_names prop_names; - struct property_entry ep_properties[5]; - struct property_entry dev_properties[3]; --- -2.34.1 - -From 88a274a25883f64304c4d14fa4efbf5ec9e6fe36 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Sun, 24 Jan 2021 11:07:42 +0100 -Subject: [PATCH] cio2-bridge: Use macros and add warnings -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Use macros for the _PLD panel as defined in the ACPI spec 6.3 and emit -a warning if we see an unknown value. - -Signed-off-by: Fabian Wüthrich -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 33 ++++++++++++++++------ - drivers/media/pci/intel/ipu3/cio2-bridge.h | 13 +++++++++ - 2 files changed, 37 insertions(+), 9 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index 77c97bf6521e..7e582135dfb8 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -73,21 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, - return ret; - } - --static u32 cio2_bridge_parse_rotation(u8 rotation) -+static u32 cio2_bridge_parse_rotation(struct cio2_sensor *sensor) - { -- if (rotation == 1) -+ switch (sensor->ssdb.degree) { -+ case CIO2_SENSOR_ROTATION_NORMAL: -+ return 0; -+ case CIO2_SENSOR_ROTATION_INVERTED: - return 180; -- return 0; -+ default: -+ dev_warn(&sensor->adev->dev, -+ "Unknown rotation %d. Assume 0 degree rotation\n", -+ sensor->ssdb.degree); -+ return 0; -+ } - } - --static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) -+static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(struct cio2_sensor *sensor) - { -- switch (panel) { -- case 4: -+ switch (sensor->pld->panel) { -+ case CIO2_PLD_PANEL_FRONT: - return V4L2_FWNODE_ORIENTATION_FRONT; -- case 5: -+ case CIO2_PLD_PANEL_BACK: - return V4L2_FWNODE_ORIENTATION_BACK; -+ case CIO2_PLD_PANEL_TOP: -+ case CIO2_PLD_PANEL_LEFT: -+ case CIO2_PLD_PANEL_RIGHT: -+ case CIO2_PLD_PANEL_UNKNOWN: -+ return V4L2_FWNODE_ORIENTATION_EXTERNAL; - default: -+ dev_warn(&sensor->adev->dev, "Unknown _PLD panel value %d\n", -+ sensor->pld->panel); - return V4L2_FWNODE_ORIENTATION_EXTERNAL; - } - } -@@ -100,8 +115,8 @@ static void cio2_bridge_create_fwnode_properties( - u32 rotation; - enum v4l2_fwnode_orientation orientation; - -- rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); -- orientation = cio2_bridge_parse_orientation(sensor->pld->panel); -+ rotation = cio2_bridge_parse_rotation(sensor); -+ orientation = cio2_bridge_parse_orientation(sensor); - - sensor->prop_names = prop_names; - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index 924d99d20328..e1e388cc9f45 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -12,6 +12,19 @@ - #define CIO2_MAX_LANES 4 - #define MAX_NUM_LINK_FREQS 3 - -+/* Values are estimated guesses as we don't have a spec */ -+#define CIO2_SENSOR_ROTATION_NORMAL 0 -+#define CIO2_SENSOR_ROTATION_INVERTED 1 -+ -+/* Panel position defined in _PLD section of ACPI Specification 6.3 */ -+#define CIO2_PLD_PANEL_TOP 0 -+#define CIO2_PLD_PANEL_BOTTOM 1 -+#define CIO2_PLD_PANEL_LEFT 2 -+#define CIO2_PLD_PANEL_RIGHT 3 -+#define CIO2_PLD_PANEL_FRONT 4 -+#define CIO2_PLD_PANEL_BACK 5 -+#define CIO2_PLD_PANEL_UNKNOWN 6 -+ - #define CIO2_SENSOR_CONFIG(_HID, _NR, ...) \ - (const struct cio2_sensor_config) { \ - .hid = _HID, \ --- -2.34.1 - -From c08f359741a191fc8bd6f19409cddf73b0ab7711 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= -Date: Thu, 6 May 2021 07:52:44 +0200 -Subject: [PATCH] cio2-bridge: Use correct dev_properties size - -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h -index e1e388cc9f45..deaf5804f70d 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.h -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h -@@ -124,7 +124,7 @@ struct cio2_sensor { - - struct cio2_property_names prop_names; - struct property_entry ep_properties[5]; -- struct property_entry dev_properties[3]; -+ struct property_entry dev_properties[4]; - struct property_entry cio2_properties[3]; - struct software_node_ref_args local_ref[1]; - struct software_node_ref_args remote_ref[1]; --- -2.34.1 - -From 4c4be6ec52117d2e126257c793b04b31e1d74bfd Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 20 May 2021 23:31:04 +0100 -Subject: [PATCH] media: i2c: Fix vertical flip in ov5693 - -The pinkness experienced by users with rotated sensors in their laptops -was due to an incorrect setting for the vertical flip function; the -datasheet for the sensor gives the settings as bits 1&2 in one place and -bits 1&6 in another. - -Switch to flipping bit 6 instead of bit 2 for 0x3820 in the vertical -flip function to fix the pink hue. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 9499ee10f56c..c558f9b48c83 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -133,7 +133,7 @@ - #define OV5693_SUB_INC_Y_REG 0x3815 - - #define OV5693_FORMAT1_REG 0x3820 --#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -+#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(6) - #define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) - #define OV5693_FORMAT1_VBIN_EN BIT(0) - #define OV5693_FORMAT2_REG 0x3821 --- -2.34.1 - -From 26e05c5625d90882ccc212fc1f5845e3e61e205f Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 9 Jul 2021 16:39:18 +0100 -Subject: [PATCH] media: i2c: Add ACPI support to ov8865 - -The ov8865 sensor is sometimes found on x86 platforms enumerated via ACPI. -Add an ACPI match table to the driver so that it's probed on those -platforms. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index ce50f3ea87b8..7626c8608f8f 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -9,6 +9,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -2946,6 +2947,12 @@ static const struct dev_pm_ops ov8865_pm_ops = { - SET_RUNTIME_PM_OPS(ov8865_suspend, ov8865_resume, NULL) - }; - -+static const struct acpi_device_id ov8865_acpi_match[] = { -+ {"INT347A"}, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, ov8865_acpi_match); -+ - static const struct of_device_id ov8865_of_match[] = { - { .compatible = "ovti,ov8865" }, - { } -@@ -2956,6 +2963,7 @@ static struct i2c_driver ov8865_driver = { - .driver = { - .name = "ov8865", - .of_match_table = ov8865_of_match, -+ .acpi_match_table = ov8865_acpi_match, - .pm = &ov8865_pm_ops, - }, - .probe_new = ov8865_probe, --- -2.34.1 - -From 904e8118d8e679ab6183e43f0de4589d5cda277b Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 21:20:17 +0100 -Subject: [PATCH] media: i2c: Fix incorrect value in comment - -The PLL configuration defined here sets 72MHz (which is correct), not -80MHz. Correct the comment. - -Reviewed-by: Paul Kocialkowski -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 7626c8608f8f..8e3f8a554452 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -713,7 +713,7 @@ static const struct ov8865_pll2_config ov8865_pll2_config_native = { - /* - * EXTCLK = 24 MHz - * DAC_CLK = 360 MHz -- * SCLK = 80 MHz -+ * SCLK = 72 MHz - */ - - static const struct ov8865_pll2_config ov8865_pll2_config_binning = { --- -2.34.1 - -From 61a682fcbbc4c4506811c587c3e5a5eb78335e46 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:21:52 +0100 -Subject: [PATCH] media: i2c: Defer probe if not endpoint found - -The ov8865 driver is one of those that can be connected to a CIO2 -device by the cio2-bridge code. This means that the absence of an -endpoint for this device is not necessarily fatal, as one might be -built by the cio2-bridge when it probes. Return -EPROBE_DEFER if no -endpoint is found rather than a fatal error. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 6 ++---- - 1 file changed, 2 insertions(+), 4 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 8e3f8a554452..9bc8d5d8199b 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2796,10 +2796,8 @@ static int ov8865_probe(struct i2c_client *client) - /* Graph Endpoint */ - - handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); -- if (!handle) { -- dev_err(dev, "unable to find endpoint node\n"); -- return -EINVAL; -- } -+ if (!handle) -+ return -EPROBE_DEFER; - - sensor->endpoint.bus_type = V4L2_MBUS_CSI2_DPHY; - --- -2.34.1 - -From 81e52e8e870b2dd7622fd2e7626f0869ea4e2068 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:00:25 +0100 -Subject: [PATCH] media: i2c: Support 19.2MHz input clock in ov8865 - -The ov8865 driver as written expects a 24MHz input clock, but the sensor -is sometimes found on x86 platforms with a 19.2MHz input clock supplied. -Add a set of PLL configurations to the driver to support that rate too. -As ACPI doesn't auto-configure the clock rate, check for a clock-frequency -during probe and set that rate if one is found. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 186 +++++++++++++++++++++++++++---------- - 1 file changed, 135 insertions(+), 51 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 9bc8d5d8199b..4ddc1b277cc0 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -21,10 +21,6 @@ - #include - #include - --/* Clock rate */ -- --#define OV8865_EXTCLK_RATE 24000000 -- - /* Register definitions */ - - /* System */ -@@ -567,6 +563,25 @@ struct ov8865_sclk_config { - unsigned int sclk_div; - }; - -+struct ov8865_pll_configs { -+ const struct ov8865_pll1_config *pll1_config; -+ const struct ov8865_pll2_config *pll2_config_native; -+ const struct ov8865_pll2_config *pll2_config_binning; -+}; -+ -+/* Clock rate */ -+ -+enum extclk_rate { -+ OV8865_19_2_MHZ, -+ OV8865_24_MHZ, -+ OV8865_NUM_SUPPORTED_RATES -+}; -+ -+static const unsigned long supported_extclk_rates[] = { -+ [OV8865_19_2_MHZ] = 19200000, -+ [OV8865_24_MHZ] = 24000000, -+}; -+ - /* - * General formulas for (array-centered) mode calculation: - * - photo_array_width = 3296 -@@ -635,9 +650,7 @@ struct ov8865_mode { - - struct v4l2_fract frame_interval; - -- const struct ov8865_pll1_config *pll1_config; -- const struct ov8865_pll2_config *pll2_config; -- const struct ov8865_sclk_config *sclk_config; -+ bool pll2_binning; - - const struct ov8865_register_value *register_values; - unsigned int register_values_count; -@@ -665,6 +678,9 @@ struct ov8865_sensor { - struct regulator *avdd; - struct regulator *dvdd; - struct regulator *dovdd; -+ -+ unsigned long extclk_rate; -+ const struct ov8865_pll_configs *pll_configs; - struct clk *extclk; - - struct v4l2_fwnode_endpoint endpoint; -@@ -680,43 +696,70 @@ struct ov8865_sensor { - /* Static definitions */ - - /* -- * EXTCLK = 24 MHz - * PHY_SCLK = 720 MHz - * MIPI_PCLK = 90 MHz - */ --static const struct ov8865_pll1_config ov8865_pll1_config_native = { -- .pll_pre_div_half = 1, -- .pll_pre_div = 0, -- .pll_mul = 30, -- .m_div = 1, -- .mipi_div = 3, -- .pclk_div = 1, -- .sys_pre_div = 1, -- .sys_div = 2, -+ -+static const struct ov8865_pll1_config ov8865_pll1_config_native_19_2mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 2, -+ .pll_mul = 75, -+ .m_div = 1, -+ .mipi_div = 3, -+ .pclk_div = 1, -+ .sys_pre_div = 1, -+ .sys_div = 2, -+}; -+ -+static const struct ov8865_pll1_config ov8865_pll1_config_native_24mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 0, -+ .pll_mul = 30, -+ .m_div = 1, -+ .mipi_div = 3, -+ .pclk_div = 1, -+ .sys_pre_div = 1, -+ .sys_div = 2, - }; - - /* -- * EXTCLK = 24 MHz - * DAC_CLK = 360 MHz - * SCLK = 144 MHz - */ - --static const struct ov8865_pll2_config ov8865_pll2_config_native = { -- .pll_pre_div_half = 1, -- .pll_pre_div = 0, -- .pll_mul = 30, -- .dac_div = 2, -- .sys_pre_div = 5, -- .sys_div = 0, -+static const struct ov8865_pll2_config ov8865_pll2_config_native_19_2mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 5, -+ .pll_mul = 75, -+ .dac_div = 1, -+ .sys_pre_div = 1, -+ .sys_div = 3, -+}; -+ -+static const struct ov8865_pll2_config ov8865_pll2_config_native_24mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 0, -+ .pll_mul = 30, -+ .dac_div = 2, -+ .sys_pre_div = 5, -+ .sys_div = 0, - }; - - /* -- * EXTCLK = 24 MHz - * DAC_CLK = 360 MHz - * SCLK = 72 MHz - */ - --static const struct ov8865_pll2_config ov8865_pll2_config_binning = { -+static const struct ov8865_pll2_config ov8865_pll2_config_binning_19_2mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 2, -+ .pll_mul = 75, -+ .dac_div = 2, -+ .sys_pre_div = 10, -+ .sys_div = 0, -+}; -+ -+static const struct ov8865_pll2_config ov8865_pll2_config_binning_24mhz = { - .pll_pre_div_half = 1, - .pll_pre_div = 0, - .pll_mul = 30, -@@ -725,6 +768,23 @@ static const struct ov8865_pll2_config ov8865_pll2_config_binning = { - .sys_div = 0, - }; - -+static struct ov8865_pll_configs ov8865_pll_configs_19_2mhz = { -+ .pll1_config = &ov8865_pll1_config_native_19_2mhz, -+ .pll2_config_native = &ov8865_pll2_config_native_19_2mhz, -+ .pll2_config_binning = &ov8865_pll2_config_binning_19_2mhz, -+}; -+ -+static struct ov8865_pll_configs ov8865_pll_configs_24mhz = { -+ .pll1_config = &ov8865_pll1_config_native_24mhz, -+ .pll2_config_native = &ov8865_pll2_config_native_24mhz, -+ .pll2_config_binning = &ov8865_pll2_config_binning_24mhz, -+}; -+ -+static const struct ov8865_pll_configs *ov8865_pll_configs[] = { -+ &ov8865_pll_configs_19_2mhz, -+ &ov8865_pll_configs_24mhz, -+}; -+ - static const struct ov8865_sclk_config ov8865_sclk_config_native = { - .sys_sel = 1, - .sclk_sel = 0, -@@ -934,9 +994,7 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 30 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_native, -- .sclk_config = &ov8865_sclk_config_native, -+ .pll2_binning = false, - - /* Registers */ - .register_values = ov8865_register_values_native, -@@ -990,9 +1048,7 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 30 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_native, -- .sclk_config = &ov8865_sclk_config_native, -+ .pll2_binning = false, - - /* Registers */ - .register_values = ov8865_register_values_native, -@@ -1050,9 +1106,7 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 30 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_binning, -- .sclk_config = &ov8865_sclk_config_native, -+ .pll2_binning = true, - - /* Registers */ - .register_values = ov8865_register_values_binning, -@@ -1116,9 +1170,7 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 90 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_binning, -- .sclk_config = &ov8865_sclk_config_native, -+ .pll2_binning = true, - - /* Registers */ - .register_values = ov8865_register_values_binning, -@@ -1513,12 +1565,11 @@ static int ov8865_isp_configure(struct ov8865_sensor *sensor) - static unsigned long ov8865_mode_pll1_rate(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_pll1_config *config = mode->pll1_config; -- unsigned long extclk_rate; -+ const struct ov8865_pll1_config *config; - unsigned long pll1_rate; - -- extclk_rate = clk_get_rate(sensor->extclk); -- pll1_rate = extclk_rate * config->pll_mul / config->pll_pre_div_half; -+ config = sensor->pll_configs->pll1_config; -+ pll1_rate = sensor->extclk_rate * config->pll_mul / config->pll_pre_div_half; - - switch (config->pll_pre_div) { - case 0: -@@ -1552,10 +1603,12 @@ static int ov8865_mode_pll1_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode, - u32 mbus_code) - { -- const struct ov8865_pll1_config *config = mode->pll1_config; -+ const struct ov8865_pll1_config *config; - u8 value; - int ret; - -+ config = sensor->pll_configs->pll1_config; -+ - switch (mbus_code) { - case MEDIA_BUS_FMT_SBGGR10_1X10: - value = OV8865_MIPI_BIT_SEL(10); -@@ -1622,9 +1675,12 @@ static int ov8865_mode_pll1_configure(struct ov8865_sensor *sensor, - static int ov8865_mode_pll2_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_pll2_config *config = mode->pll2_config; -+ const struct ov8865_pll2_config *config; - int ret; - -+ config = mode->pll2_binning ? sensor->pll_configs->pll2_config_binning : -+ sensor->pll_configs->pll2_config_native; -+ - ret = ov8865_write(sensor, OV8865_PLL_CTRL12_REG, - OV8865_PLL_CTRL12_PRE_DIV_HALF(config->pll_pre_div_half) | - OV8865_PLL_CTRL12_DAC_DIV(config->dac_div)); -@@ -1658,7 +1714,7 @@ static int ov8865_mode_pll2_configure(struct ov8865_sensor *sensor, - static int ov8865_mode_sclk_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_sclk_config *config = mode->sclk_config; -+ const struct ov8865_sclk_config *config = &ov8865_sclk_config_native; - int ret; - - ret = ov8865_write(sensor, OV8865_CLK_SEL0_REG, -@@ -2053,9 +2109,11 @@ static int ov8865_mode_configure(struct ov8865_sensor *sensor, - static unsigned long ov8865_mode_mipi_clk_rate(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_pll1_config *config = mode->pll1_config; -+ const struct ov8865_pll1_config *config; - unsigned long pll1_rate; - -+ config = sensor->pll_configs->pll1_config; -+ - pll1_rate = ov8865_mode_pll1_rate(sensor, mode); - - return pll1_rate / config->m_div / 2; -@@ -2783,7 +2841,8 @@ static int ov8865_probe(struct i2c_client *client) - struct ov8865_sensor *sensor; - struct v4l2_subdev *subdev; - struct media_pad *pad; -- unsigned long rate; -+ unsigned int rate; -+ unsigned int i; - int ret; - - sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); -@@ -2858,13 +2917,38 @@ static int ov8865_probe(struct i2c_client *client) - goto error_endpoint; - } - -- rate = clk_get_rate(sensor->extclk); -- if (rate != OV8865_EXTCLK_RATE) { -- dev_err(dev, "clock rate %lu Hz is unsupported\n", rate); -+ /* -+ * We could have either a 24MHz or 19.2MHz clock rate. Check for a -+ * clock-frequency property and if found, set that rate. This should -+ * cover the ACPI case. If the system uses devicetree then the -+ * configured rate should already be set, so we'll have to check it. -+ */ -+ ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", -+ &rate); -+ if (!ret) { -+ ret = clk_set_rate(sensor->extclk, rate); -+ if (ret) { -+ dev_err(dev, "failed to set clock rate\n"); -+ return ret; -+ } -+ } -+ -+ sensor->extclk_rate = clk_get_rate(sensor->extclk); -+ -+ for (i = 0; i < ARRAY_SIZE(supported_extclk_rates); i++) { -+ if (sensor->extclk_rate == supported_extclk_rates[i]) -+ break; -+ } -+ -+ if (i == ARRAY_SIZE(supported_extclk_rates)) { -+ dev_err(dev, "clock rate %lu Hz is unsupported\n", -+ sensor->extclk_rate); - ret = -EINVAL; - goto error_endpoint; - } - -+ sensor->pll_configs = ov8865_pll_configs[i]; -+ - /* Subdev, entity and pad */ - - subdev = &sensor->subdev; --- -2.34.1 - -From 3a77e00a37ba4a6b0d29844f5f4073dca0093ce4 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:19:10 +0100 -Subject: [PATCH] media: i2c: Add .get_selection() support to ov8865 - -The ov8865 driver's v4l2_subdev_pad_ops currently does not include -.get_selection() - add support for that callback. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 64 ++++++++++++++++++++++++++++++++++++++ - 1 file changed, 64 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 4ddc1b277cc0..0f2776390a8e 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -450,6 +450,15 @@ - #define OV8865_PRE_CTRL0_PATTERN_COLOR_SQUARES 2 - #define OV8865_PRE_CTRL0_PATTERN_BLACK 3 - -+/* Pixel Array */ -+ -+#define OV8865_NATIVE_WIDTH 3296 -+#define OV8865_NATIVE_HEIGHT 2528 -+#define OV8865_ACTIVE_START_TOP 32 -+#define OV8865_ACTIVE_START_LEFT 80 -+#define OV8865_ACTIVE_WIDTH 3264 -+#define OV8865_ACTIVE_HEIGHT 2448 -+ - /* Macros */ - - #define ov8865_subdev_sensor(s) \ -@@ -2756,12 +2765,67 @@ static int ov8865_enum_frame_interval(struct v4l2_subdev *subdev, - return 0; - } - -+static void -+__ov8865_get_pad_crop(struct ov8865_sensor *sensor, -+ struct v4l2_subdev_state *state, unsigned int pad, -+ enum v4l2_subdev_format_whence which, struct v4l2_rect *r) -+{ -+ const struct ov8865_mode *mode = sensor->state.mode; -+ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ *r = *v4l2_subdev_get_try_crop(&sensor->subdev, state, pad); -+ break; -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ r->height = mode->output_size_y; -+ r->width = mode->output_size_x; -+ r->top = (OV8865_NATIVE_HEIGHT - mode->output_size_y) / 2; -+ r->left = (OV8865_NATIVE_WIDTH - mode->output_size_x) / 2; -+ break; -+ } -+} -+ -+static int ov8865_get_selection(struct v4l2_subdev *subdev, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); -+ -+ switch (sel->target) { -+ case V4L2_SEL_TGT_CROP: -+ mutex_lock(&sensor->mutex); -+ __ov8865_get_pad_crop(sensor, state, sel->pad, -+ sel->which, &sel->r); -+ mutex_unlock(&sensor->mutex); -+ break; -+ case V4L2_SEL_TGT_NATIVE_SIZE: -+ sel->r.top = 0; -+ sel->r.left = 0; -+ sel->r.width = OV8865_NATIVE_WIDTH; -+ sel->r.height = OV8865_NATIVE_HEIGHT; -+ break; -+ case V4L2_SEL_TGT_CROP_BOUNDS: -+ case V4L2_SEL_TGT_CROP_DEFAULT: -+ sel->r.top = OV8865_ACTIVE_START_TOP; -+ sel->r.left = OV8865_ACTIVE_START_LEFT; -+ sel->r.width = OV8865_ACTIVE_WIDTH; -+ sel->r.height = OV8865_ACTIVE_HEIGHT; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ - static const struct v4l2_subdev_pad_ops ov8865_subdev_pad_ops = { - .enum_mbus_code = ov8865_enum_mbus_code, - .get_fmt = ov8865_get_fmt, - .set_fmt = ov8865_set_fmt, - .enum_frame_size = ov8865_enum_frame_size, - .enum_frame_interval = ov8865_enum_frame_interval, -+ .get_selection = ov8865_get_selection, -+ .set_selection = ov8865_get_selection, - }; - - static const struct v4l2_subdev_ops ov8865_subdev_ops = { --- -2.34.1 - -From 36937f23f79dcef22edf507a068584db72157fa0 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:34:43 +0100 -Subject: [PATCH] media: i2c: Switch control to V4L2_CID_ANALOGUE_GAIN - -The V4L2_CID_GAIN control for this driver configures registers that -the datasheet specifies as analogue gain. Switch the control's ID -to V4L2_CID_ANALOGUE_GAIN. - -Reviewed-by: Paul Kocialkowski -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 9 +++++---- - 1 file changed, 5 insertions(+), 4 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 0f2776390a8e..a832938c33b6 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2150,7 +2150,7 @@ static int ov8865_exposure_configure(struct ov8865_sensor *sensor, u32 exposure) - - /* Gain */ - --static int ov8865_gain_configure(struct ov8865_sensor *sensor, u32 gain) -+static int ov8865_analog_gain_configure(struct ov8865_sensor *sensor, u32 gain) - { - int ret; - -@@ -2460,8 +2460,8 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - if (ret) - return ret; - break; -- case V4L2_CID_GAIN: -- ret = ov8865_gain_configure(sensor, ctrl->val); -+ case V4L2_CID_ANALOGUE_GAIN: -+ ret = ov8865_analog_gain_configure(sensor, ctrl->val); - if (ret) - return ret; - break; -@@ -2506,7 +2506,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Gain */ - -- v4l2_ctrl_new_std(handler, ops, V4L2_CID_GAIN, 128, 8191, 128, 128); -+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_ANALOGUE_GAIN, 128, 8191, 128, -+ 128); - - /* White Balance */ - --- -2.34.1 - -From eca95f4d00c5846723c63bb42330898de62b4db3 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 12 Jul 2021 22:54:56 +0100 -Subject: [PATCH] media: i2c: Add vblank control to ov8865 - -Add a V4L2_CID_VBLANK control to the ov8865 driver. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 34 ++++++++++++++++++++++++++++++++++ - 1 file changed, 34 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index a832938c33b6..f741c0713ca4 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -183,6 +183,8 @@ - #define OV8865_VTS_H(v) (((v) & GENMASK(11, 8)) >> 8) - #define OV8865_VTS_L_REG 0x380f - #define OV8865_VTS_L(v) ((v) & GENMASK(7, 0)) -+#define OV8865_TIMING_MAX_VTS 0xffff -+#define OV8865_TIMING_MIN_VTS 0x04 - #define OV8865_OFFSET_X_H_REG 0x3810 - #define OV8865_OFFSET_X_H(v) (((v) & GENMASK(15, 8)) >> 8) - #define OV8865_OFFSET_X_L_REG 0x3811 -@@ -675,6 +677,7 @@ struct ov8865_state { - struct ov8865_ctrls { - struct v4l2_ctrl *link_freq; - struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *vblank; - - struct v4l2_ctrl_handler handler; - }; -@@ -2225,6 +2228,20 @@ static int ov8865_test_pattern_configure(struct ov8865_sensor *sensor, - ov8865_test_pattern_bits[index]); - } - -+/* Blanking */ -+ -+static int ov8865_vts_configure(struct ov8865_sensor *sensor, u32 vblank) -+{ -+ u16 vts = sensor->state.mode->output_size_y + vblank; -+ int ret; -+ -+ ret = ov8865_write(sensor, OV8865_VTS_H_REG, OV8865_VTS_H(vts)); -+ if (ret) -+ return ret; -+ -+ return ov8865_write(sensor, OV8865_VTS_L_REG, OV8865_VTS_L(vts)); -+} -+ - /* State */ - - static int ov8865_state_mipi_configure(struct ov8865_sensor *sensor, -@@ -2476,6 +2493,8 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - case V4L2_CID_TEST_PATTERN: - index = (unsigned int)ctrl->val; - return ov8865_test_pattern_configure(sensor, index); -+ case V4L2_CID_VBLANK: -+ return ov8865_vts_configure(sensor, ctrl->val); - default: - return -EINVAL; - } -@@ -2492,6 +2511,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - struct ov8865_ctrls *ctrls = &sensor->ctrls; - struct v4l2_ctrl_handler *handler = &ctrls->handler; - const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; -+ const struct ov8865_mode *mode = sensor->state.mode; -+ unsigned int vblank_max, vblank_def; - int ret; - - v4l2_ctrl_handler_init(handler, 32); -@@ -2528,6 +2549,13 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - ARRAY_SIZE(ov8865_test_pattern_menu) - 1, - 0, 0, ov8865_test_pattern_menu); - -+ /* Blanking */ -+ vblank_max = OV8865_TIMING_MAX_VTS - mode->output_size_y; -+ vblank_def = mode->vts - mode->output_size_y; -+ ctrls->vblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_VBLANK, -+ OV8865_TIMING_MIN_VTS, vblank_max, 1, -+ vblank_def); -+ - /* MIPI CSI-2 */ - - ctrls->link_freq = -@@ -2708,6 +2736,10 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - sensor->state.mbus_code != mbus_code) - ret = ov8865_state_configure(sensor, mode, mbus_code); - -+ __v4l2_ctrl_modify_range(sensor->ctrls.vblank, OV8865_TIMING_MIN_VTS, -+ OV8865_TIMING_MAX_VTS - mode->output_size_y, -+ 1, mode->vts - mode->output_size_y); -+ - complete: - mutex_unlock(&sensor->mutex); - -@@ -3035,6 +3067,8 @@ static int ov8865_probe(struct i2c_client *client) - - /* Sensor */ - -+ sensor->state.mode = &ov8865_modes[0]; -+ - ret = ov8865_ctrls_init(sensor); - if (ret) - goto error_mutex; --- -2.34.1 - -From 2d12594800c2b827748872a0a8c03419b2330e54 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 13 Jul 2021 23:40:33 +0100 -Subject: [PATCH] media: i2c: Add hblank control to ov8865 - -Add a V4L2_CID_HBLANK control to the ov8865 driver. This is read only -with timing control intended to be done via vblanking alone. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 14 ++++++++++++++ - 1 file changed, 14 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index f741c0713ca4..4b18cc80f985 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -677,6 +677,7 @@ struct ov8865_state { - struct ov8865_ctrls { - struct v4l2_ctrl *link_freq; - struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *hblank; - struct v4l2_ctrl *vblank; - - struct v4l2_ctrl_handler handler; -@@ -2513,6 +2514,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; - const struct ov8865_mode *mode = sensor->state.mode; - unsigned int vblank_max, vblank_def; -+ unsigned int hblank; - int ret; - - v4l2_ctrl_handler_init(handler, 32); -@@ -2550,6 +2552,13 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - 0, 0, ov8865_test_pattern_menu); - - /* Blanking */ -+ hblank = mode->hts - mode->output_size_x; -+ ctrls->hblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_HBLANK, hblank, -+ hblank, 1, hblank); -+ -+ if (ctrls->hblank) -+ ctrls->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ - vblank_max = OV8865_TIMING_MAX_VTS - mode->output_size_y; - vblank_def = mode->vts - mode->output_size_y; - ctrls->vblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_VBLANK, -@@ -2696,6 +2705,7 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - struct v4l2_mbus_framefmt *mbus_format = &format->format; - const struct ov8865_mode *mode; - u32 mbus_code = 0; -+ unsigned int hblank; - unsigned int index; - int ret = 0; - -@@ -2740,6 +2750,10 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - OV8865_TIMING_MAX_VTS - mode->output_size_y, - 1, mode->vts - mode->output_size_y); - -+ hblank = mode->hts - mode->output_size_x; -+ __v4l2_ctrl_modify_range(sensor->ctrls.hblank, hblank, hblank, 1, -+ hblank); -+ - complete: - mutex_unlock(&sensor->mutex); - --- -2.34.1 - -From 3a39fd9c90eb75cb51f82d7c296824946b7e73a1 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 20 Oct 2021 22:43:54 +0100 -Subject: [PATCH] media: i2c: Update HTS values in ov8865 - -The HTS values for some of the modes in the ov8865 driver are a bit -unusual, coming in lower than the output_size_x is set to. It seems -like they might be calculated to fit the desired framerate into a -configuration with just two data lanes. To bring this more in line -with expected behaviour, raise the HTS values above the output_size_x. - -The corollary of that change is that the hardcoded frame intervals -against the modes no longer make sense, so remove those entirely. -Update the .g/s_frame_interval() callbacks to calculate the frame -interval based on the current mode and the vblank and hblank settings -plus the number of data lanes detected from firmware. - -The implementation of the .enum_frame_interval() callback is no longer -suitable since the possible frame rate is now a continuous range depending -on the vblank control setting, so remove that callback entirely. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 65 +++++++------------------------------- - 1 file changed, 11 insertions(+), 54 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 4b18cc80f985..1b8674152750 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -659,8 +659,6 @@ struct ov8865_mode { - unsigned int blc_anchor_right_start; - unsigned int blc_anchor_right_end; - -- struct v4l2_fract frame_interval; -- - bool pll2_binning; - - const struct ov8865_register_value *register_values; -@@ -964,7 +962,7 @@ static const struct ov8865_mode ov8865_modes[] = { - { - /* Horizontal */ - .output_size_x = 3264, -- .hts = 1944, -+ .hts = 3888, - - /* Vertical */ - .output_size_y = 2448, -@@ -1003,9 +1001,6 @@ static const struct ov8865_mode ov8865_modes[] = { - .blc_anchor_right_start = 1984, - .blc_anchor_right_end = 2239, - -- /* Frame Interval */ -- .frame_interval = { 1, 30 }, -- - /* PLL */ - .pll2_binning = false, - -@@ -1018,11 +1013,11 @@ static const struct ov8865_mode ov8865_modes[] = { - { - /* Horizontal */ - .output_size_x = 3264, -- .hts = 2582, -+ .hts = 3888, - - /* Vertical */ - .output_size_y = 1836, -- .vts = 2002, -+ .vts = 2470, - - .size_auto = true, - .size_auto_boundary_x = 8, -@@ -1057,9 +1052,6 @@ static const struct ov8865_mode ov8865_modes[] = { - .blc_anchor_right_start = 1984, - .blc_anchor_right_end = 2239, - -- /* Frame Interval */ -- .frame_interval = { 1, 30 }, -- - /* PLL */ - .pll2_binning = false, - -@@ -1115,9 +1107,6 @@ static const struct ov8865_mode ov8865_modes[] = { - .blc_anchor_right_start = 992, - .blc_anchor_right_end = 1119, - -- /* Frame Interval */ -- .frame_interval = { 1, 30 }, -- - /* PLL */ - .pll2_binning = true, - -@@ -1179,9 +1168,6 @@ static const struct ov8865_mode ov8865_modes[] = { - .blc_anchor_right_start = 992, - .blc_anchor_right_end = 1119, - -- /* Frame Interval */ -- .frame_interval = { 1, 90 }, -- - /* PLL */ - .pll2_binning = true, - -@@ -2628,11 +2614,18 @@ static int ov8865_g_frame_interval(struct v4l2_subdev *subdev, - { - struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); - const struct ov8865_mode *mode; -+ unsigned int framesize; -+ unsigned int fps; - - mutex_lock(&sensor->mutex); - - mode = sensor->state.mode; -- interval->interval = mode->frame_interval; -+ framesize = mode->hts * (mode->output_size_y + -+ sensor->ctrls.vblank->val); -+ fps = DIV_ROUND_CLOSEST(sensor->ctrls.pixel_rate->val, framesize); -+ -+ interval->interval.numerator = 1; -+ interval->interval.denominator = fps; - - mutex_unlock(&sensor->mutex); - -@@ -2777,41 +2770,6 @@ static int ov8865_enum_frame_size(struct v4l2_subdev *subdev, - return 0; - } - --static int ov8865_enum_frame_interval(struct v4l2_subdev *subdev, -- struct v4l2_subdev_state *sd_state, -- struct v4l2_subdev_frame_interval_enum *interval_enum) --{ -- const struct ov8865_mode *mode = NULL; -- unsigned int mode_index; -- unsigned int interval_index; -- -- if (interval_enum->index > 0) -- return -EINVAL; -- /* -- * Multiple modes with the same dimensions may have different frame -- * intervals, so look up each relevant mode. -- */ -- for (mode_index = 0, interval_index = 0; -- mode_index < ARRAY_SIZE(ov8865_modes); mode_index++) { -- mode = &ov8865_modes[mode_index]; -- -- if (mode->output_size_x == interval_enum->width && -- mode->output_size_y == interval_enum->height) { -- if (interval_index == interval_enum->index) -- break; -- -- interval_index++; -- } -- } -- -- if (mode_index == ARRAY_SIZE(ov8865_modes)) -- return -EINVAL; -- -- interval_enum->interval = mode->frame_interval; -- -- return 0; --} -- - static void - __ov8865_get_pad_crop(struct ov8865_sensor *sensor, - struct v4l2_subdev_state *state, unsigned int pad, -@@ -2870,7 +2828,6 @@ static const struct v4l2_subdev_pad_ops ov8865_subdev_pad_ops = { - .get_fmt = ov8865_get_fmt, - .set_fmt = ov8865_set_fmt, - .enum_frame_size = ov8865_enum_frame_size, -- .enum_frame_interval = ov8865_enum_frame_interval, - .get_selection = ov8865_get_selection, - .set_selection = ov8865_get_selection, - }; --- -2.34.1 - -From 85e12a7eb1520817e1604dcb37e096d341f4f8b7 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 13 Jul 2021 23:43:17 +0100 -Subject: [PATCH] media: i2c: cap exposure at height + vblank in ov8865 - -Exposure limits depend on the total height; when vblank is altered (and -thus the total height is altered), change the exposure limits to reflect -the new cap. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 26 ++++++++++++++++++++++++-- - 1 file changed, 24 insertions(+), 2 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 1b8674152750..99548ad15dcd 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -677,6 +677,7 @@ struct ov8865_ctrls { - struct v4l2_ctrl *pixel_rate; - struct v4l2_ctrl *hblank; - struct v4l2_ctrl *vblank; -+ struct v4l2_ctrl *exposure; - - struct v4l2_ctrl_handler handler; - }; -@@ -2454,6 +2455,19 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - unsigned int index; - int ret; - -+ /* If VBLANK is altered we need to update exposure to compensate */ -+ if (ctrl->id == V4L2_CID_VBLANK) { -+ int exposure_max; -+ -+ exposure_max = sensor->state.mode->output_size_y + ctrl->val; -+ __v4l2_ctrl_modify_range(sensor->ctrls.exposure, -+ sensor->ctrls.exposure->minimum, -+ exposure_max, -+ sensor->ctrls.exposure->step, -+ min(sensor->ctrls.exposure->val, -+ exposure_max)); -+ } -+ - /* Wait for the sensor to be on before setting controls. */ - if (pm_runtime_suspended(sensor->dev)) - return 0; -@@ -2510,8 +2524,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Exposure */ - -- v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, 1048575, 16, -- 512); -+ ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, -+ 1048575, 16, 512); - - /* Gain */ - -@@ -2700,6 +2714,7 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - u32 mbus_code = 0; - unsigned int hblank; - unsigned int index; -+ int exposure_max; - int ret = 0; - - mutex_lock(&sensor->mutex); -@@ -2747,6 +2762,13 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - __v4l2_ctrl_modify_range(sensor->ctrls.hblank, hblank, hblank, 1, - hblank); - -+ exposure_max = mode->vts; -+ __v4l2_ctrl_modify_range(sensor->ctrls.exposure, -+ sensor->ctrls.exposure->minimum, exposure_max, -+ sensor->ctrls.exposure->step, -+ min(sensor->ctrls.exposure->val, -+ exposure_max)); -+ - complete: - mutex_unlock(&sensor->mutex); - --- -2.34.1 - -From 65d323e63c37bcfb00474145cabda06c87c120e5 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 16 Jul 2021 22:56:15 +0100 -Subject: [PATCH] media: i2c: Add controls from fwnode to ov8865 - -Add V4L2_CID_CAMERA_ORIENTATION and V4L2_CID_CAMERA_SENSOR_ROTATION -controls to the ov8865 driver by attempting to parse them from firmware. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 10 ++++++++++ - 1 file changed, 10 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 99548ad15dcd..dfb5095ef16b 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2513,6 +2513,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - struct v4l2_ctrl_handler *handler = &ctrls->handler; - const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; - const struct ov8865_mode *mode = sensor->state.mode; -+ struct v4l2_fwnode_device_properties props; - unsigned int vblank_max, vblank_def; - unsigned int hblank; - int ret; -@@ -2576,6 +2577,15 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 1, - INT_MAX, 1, 1); - -+ /* set properties from fwnode (e.g. rotation, orientation) */ -+ ret = v4l2_fwnode_device_parse(sensor->dev, &props); -+ if (ret) -+ goto error_ctrls; -+ -+ ret = v4l2_ctrl_new_fwnode_properties(handler, ops, &props); -+ if (ret) -+ goto error_ctrls; -+ - if (handler->error) { - ret = handler->error; - goto error_ctrls; --- -2.34.1 - -From d27d69decc99364341abe181a1d8eb73a3634d44 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 16 Jul 2021 00:00:54 +0100 -Subject: [PATCH] media: i2c: Switch exposure control unit to lines - -The ov8865 driver currently has the unit of the V4L2_CID_EXPOSURE control -as 1/16th of a line. This is what the sensor expects, but isn't very -intuitive. Switch the control to be in units of a line and simply do the -16x multiplication before passing the value to the sensor. - -The datasheet for this sensor gives minimum exposure as 2 lines, so take -the opportunity to correct the lower bounds of the control. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 7 +++++-- - 1 file changed, 5 insertions(+), 2 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index dfb5095ef16b..5f19d82554df 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2125,6 +2125,9 @@ static int ov8865_exposure_configure(struct ov8865_sensor *sensor, u32 exposure) - { - int ret; - -+ /* The sensor stores exposure in units of 1/16th of a line */ -+ exposure *= 16; -+ - ret = ov8865_write(sensor, OV8865_EXPOSURE_CTRL_HH_REG, - OV8865_EXPOSURE_CTRL_HH(exposure)); - if (ret) -@@ -2525,8 +2528,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Exposure */ - -- ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, -- 1048575, 16, 512); -+ ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 2, -+ 65535, 1, 32); - - /* Gain */ - --- -2.34.1 - -From 17f766c846ddc7b94495123d4d00db1407b29047 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 24 Aug 2021 22:39:02 +0100 -Subject: [PATCH] media: i2c: Re-order runtime pm initialisation - -The kerneldoc for pm_runtime_set_suspended() says: - -"It is not valid to call this function for devices with runtime PM -enabled" - -To satisfy that requirement, re-order the calls so that -pm_runtime_enable() is the last one. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 5f19d82554df..18b5f1e8e9a7 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -3085,8 +3085,8 @@ static int ov8865_probe(struct i2c_client *client) - - /* Runtime PM */ - -- pm_runtime_enable(sensor->dev); - pm_runtime_set_suspended(sensor->dev); -+ pm_runtime_enable(sensor->dev); - - /* V4L2 subdev register */ - --- -2.34.1 - -From cc4fefcb7a85234a08c12581045aa305caa41d6f Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 24 Aug 2021 23:17:39 +0100 -Subject: [PATCH] media: i2c: Use dev_err_probe() in ov8865 - -There is a chance that regulator_get() returns -EPROBE_DEFER, in which -case printing an error message is undesirable. To avoid spurious messages -in dmesg in the event that -EPROBE_DEFER is returned, use dev_err_probe() -on error paths for regulator_get(). - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 46 +++++++++++++++++--------------------- - 1 file changed, 20 insertions(+), 26 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 18b5f1e8e9a7..19e6bebf340d 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2955,6 +2955,26 @@ static int ov8865_probe(struct i2c_client *client) - sensor->dev = dev; - sensor->i2c_client = client; - -+ /* Regulators */ -+ -+ /* DVDD: digital core */ -+ sensor->dvdd = devm_regulator_get(dev, "dvdd"); -+ if (IS_ERR(sensor->dvdd)) -+ return dev_err_probe(dev, PTR_ERR(sensor->dvdd), -+ "cannot get DVDD regulator\n"); -+ -+ /* DOVDD: digital I/O */ -+ sensor->dovdd = devm_regulator_get(dev, "dovdd"); -+ if (IS_ERR(sensor->dovdd)) -+ return dev_err_probe(dev, PTR_ERR(sensor->dovdd), -+ "cannot get DOVDD regulator\n"); -+ -+ /* AVDD: analog */ -+ sensor->avdd = devm_regulator_get(dev, "avdd"); -+ if (IS_ERR(sensor->avdd)) -+ return dev_err_probe(dev, PTR_ERR(sensor->avdd), -+ "cannot get AVDD regulator\n"); -+ - /* Graph Endpoint */ - - handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); -@@ -2985,32 +3005,6 @@ static int ov8865_probe(struct i2c_client *client) - goto error_endpoint; - } - -- /* Regulators */ -- -- /* DVDD: digital core */ -- sensor->dvdd = devm_regulator_get(dev, "dvdd"); -- if (IS_ERR(sensor->dvdd)) { -- dev_err(dev, "cannot get DVDD (digital core) regulator\n"); -- ret = PTR_ERR(sensor->dvdd); -- goto error_endpoint; -- } -- -- /* DOVDD: digital I/O */ -- sensor->dovdd = devm_regulator_get(dev, "dovdd"); -- if (IS_ERR(sensor->dovdd)) { -- dev_err(dev, "cannot get DOVDD (digital I/O) regulator\n"); -- ret = PTR_ERR(sensor->dovdd); -- goto error_endpoint; -- } -- -- /* AVDD: analog */ -- sensor->avdd = devm_regulator_get(dev, "avdd"); -- if (IS_ERR(sensor->avdd)) { -- dev_err(dev, "cannot get AVDD (analog) regulator\n"); -- ret = PTR_ERR(sensor->avdd); -- goto error_endpoint; -- } -- - /* External Clock */ - - sensor->extclk = devm_clk_get(dev, NULL); --- -2.34.1 - -From 0adc344253782bb322bfafe217954c24f8adbd52 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 14 Jul 2021 00:05:04 +0100 -Subject: [PATCH] media: ipu3-cio2: Add INT347A to cio2-bridge - -ACPI _HID INT347A represents the OV8865 sensor, the driver for which can -support the platforms that the cio2-bridge serves. Add it to the array -of supported sensors so the bridge will connect the sensor to the CIO2 -device. - -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index 7e582135dfb8..0132f0bd9b41 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -22,6 +22,8 @@ - static const struct cio2_sensor_config cio2_supported_sensors[] = { - /* Omnivision OV5693 */ - CIO2_SENSOR_CONFIG("INT33BE", 0), -+ /* Omnivision OV8865 */ -+ CIO2_SENSOR_CONFIG("INT347A", 1, 360000000), - /* Omnivision OV2680 */ - CIO2_SENSOR_CONFIG("OVTI2680", 0), - }; --- -2.34.1 - -From 9d435a1f47fb024ae4dd9f2161467105b65d40af Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Thu, 7 Oct 2021 15:34:52 +0200 -Subject: [PATCH] media: i2c: ov8865: Fix lockdep error - -ov8865_state_init() calls ov8865_state_mipi_configure() which uses -__v4l2_ctrl_s_ctrl[_int64](). This means that sensor->mutex (which -is also sensor->ctrls.handler.lock) must be locked before calling -ov8865_state_init(). - -Note ov8865_state_mipi_configure() is also used in other places where -the lock is already held so it cannot be changed itself. - -This fixes the following lockdep kernel WARN: - -[ 13.233413] ------------[ cut here ]------------ -[ 13.233421] WARNING: CPU: 0 PID: 8 at drivers/media/v4l2-core/v4l2-ctrls-api.c:833 __v4l2_ctrl_s_ctrl+0x4d/0x60 [videodev] -... -[ 13.234063] Call Trace: -[ 13.234074] ov8865_state_configure+0x98b/0xc00 [ov8865] -[ 13.234095] ov8865_probe+0x4b1/0x54c [ov8865] -[ 13.234117] i2c_device_probe+0x13c/0x2d0 - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 19e6bebf340d..d5af8aedf5e8 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -3073,7 +3073,9 @@ static int ov8865_probe(struct i2c_client *client) - if (ret) - goto error_mutex; - -+ mutex_lock(&sensor->mutex); - ret = ov8865_state_init(sensor); -+ mutex_unlock(&sensor->mutex); - if (ret) - goto error_ctrls; - --- -2.34.1 - -From 54785dd485c28fe1e8bae6b954f7c184324ede71 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:56:57 +0200 -Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an - INT3472 device - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around cases where this info is not present in the firmware tables, -which is often the case on x86/ACPI devices, both frameworks allow the -provider-driver to attach info about consumers to the clks/regulators -when registering these. - -This causes problems with the probe ordering wrt drivers for consumers -of these clks/regulators. Since the lookups are only registered when the -provider-driver binds, trying to get these clks/regulators before then -results in a -ENOENT error for clks and a dummy regulator for regulators. - -One case where we hit this issue is camera sensors such as e.g. the OV8865 -sensor found on the Microsoft Surface Go. The sensor uses clks, regulators -and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 -ACPI device. There is special platform code handling this and setting -platform_data with the necessary consumer info on the MFD cells -instantiated for the PMIC under: drivers/platform/x86/intel/int3472. - -For this to work properly the ov8865 driver must not bind to the I2C-client -for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and -clk MFD cells have all been fully setup. - -The OV8865 on the Microsoft Surface Go is just one example, all X86 -devices using the Intel IPU3 camera block found on recent Intel SoCs -have similar issues where there is an INT3472 HID ACPI-device, which -describes the clks and regulators, and the driver for this INT3472 device -must be fully initialized before the sensor driver (any sensor driver) -binds for things to work properly. - -On these devices the ACPI nodes describing the sensors all have a _DEP -dependency on the matching INT3472 ACPI device (there is one per sensor). - -This allows solving the probe-ordering problem by delaying the enumeration -(instantiation of the I2C-client in the ov8865 example) of ACPI-devices -which have a _DEP dependency on an INT3472 device. - -The new acpi_dev_ready_for_enumeration() helper used for this is also -exported because for devices, which have the enumeration_by_parent flag -set, the parent-driver will do its own scan of child ACPI devices and -it will try to enumerate those during its probe(). Code doing this such -as e.g. the i2c-core-acpi.c code must call this new helper to ensure -that it too delays the enumeration until all the _DEP dependencies are -met on devices which have the new honor_deps flag set. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/acpi/scan.c | 36 ++++++++++++++++++++++++++++++++++-- - include/acpi/acpi_bus.h | 5 ++++- - 2 files changed, 38 insertions(+), 3 deletions(-) - -diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c -index 5b54c80b9d32..efee6ee91c8f 100644 ---- a/drivers/acpi/scan.c -+++ b/drivers/acpi/scan.c -@@ -796,6 +796,12 @@ static const char * const acpi_ignore_dep_ids[] = { - NULL - }; - -+/* List of HIDs for which we honor deps of matching ACPI devs, when checking _DEP lists. */ -+static const char * const acpi_honor_dep_ids[] = { -+ "INT3472", /* Camera sensor PMIC / clk and regulator info */ -+ NULL -+}; -+ - static struct acpi_device *acpi_bus_get_parent(acpi_handle handle) - { - struct acpi_device *device = NULL; -@@ -1757,8 +1763,12 @@ static void acpi_scan_dep_init(struct acpi_device *adev) - struct acpi_dep_data *dep; - - list_for_each_entry(dep, &acpi_dep_list, node) { -- if (dep->consumer == adev->handle) -+ if (dep->consumer == adev->handle) { -+ if (dep->honor_dep) -+ adev->flags.honor_deps = 1; -+ - adev->dep_unmet++; -+ } - } - } - -@@ -1962,7 +1972,7 @@ static u32 acpi_scan_check_dep(acpi_handle handle, bool check_dep) - for (count = 0, i = 0; i < dep_devices.count; i++) { - struct acpi_device_info *info; - struct acpi_dep_data *dep; -- bool skip; -+ bool skip, honor_dep; - - status = acpi_get_object_info(dep_devices.handles[i], &info); - if (ACPI_FAILURE(status)) { -@@ -1971,6 +1981,7 @@ static u32 acpi_scan_check_dep(acpi_handle handle, bool check_dep) - } - - skip = acpi_info_matches_ids(info, acpi_ignore_dep_ids); -+ honor_dep = acpi_info_matches_ids(info, acpi_honor_dep_ids); - kfree(info); - - if (skip) -@@ -1984,6 +1995,7 @@ static u32 acpi_scan_check_dep(acpi_handle handle, bool check_dep) - - dep->supplier = dep_devices.handles[i]; - dep->consumer = handle; -+ dep->honor_dep = honor_dep; - - mutex_lock(&acpi_dep_list_lock); - list_add_tail(&dep->node , &acpi_dep_list); -@@ -2071,6 +2083,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, - - static void acpi_default_enumeration(struct acpi_device *device) - { -+ if (!acpi_dev_ready_for_enumeration(device)) -+ return; -+ - /* - * Do not enumerate devices with enumeration_by_parent flag set as - * they will be enumerated by their respective parents. -@@ -2313,6 +2328,23 @@ void acpi_dev_clear_dependencies(struct acpi_device *supplier) - } - EXPORT_SYMBOL_GPL(acpi_dev_clear_dependencies); - -+/** -+ * acpi_dev_ready_for_enumeration - Check if the ACPI device is ready for enumeration -+ * @device: Pointer to the &struct acpi_device to check -+ * -+ * Check if the device is present and has no unmet dependencies. -+ * -+ * Return true if the device is ready for enumeratino. Otherwise, return false. -+ */ -+bool acpi_dev_ready_for_enumeration(const struct acpi_device *device) -+{ -+ if (device->flags.honor_deps && device->dep_unmet) -+ return false; -+ -+ return acpi_device_is_present(device); -+} -+EXPORT_SYMBOL_GPL(acpi_dev_ready_for_enumeration); -+ - /** - * acpi_dev_get_first_consumer_dev - Return ACPI device dependent on @supplier - * @supplier: Pointer to the dependee device -diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h -index 13d93371790e..2da53b7b4965 100644 ---- a/include/acpi/acpi_bus.h -+++ b/include/acpi/acpi_bus.h -@@ -202,7 +202,8 @@ struct acpi_device_flags { - u32 coherent_dma:1; - u32 cca_seen:1; - u32 enumeration_by_parent:1; -- u32 reserved:19; -+ u32 honor_deps:1; -+ u32 reserved:18; - }; - - /* File System */ -@@ -284,6 +285,7 @@ struct acpi_dep_data { - struct list_head node; - acpi_handle supplier; - acpi_handle consumer; -+ bool honor_dep; - }; - - /* Performance Management */ -@@ -693,6 +695,7 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) - bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); - - void acpi_dev_clear_dependencies(struct acpi_device *supplier); -+bool acpi_dev_ready_for_enumeration(const struct acpi_device *device); - struct acpi_device *acpi_dev_get_first_consumer_dev(struct acpi_device *supplier); - struct acpi_device * - acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); --- -2.34.1 - -From 5a6287f4010feda3c17ae04fef416960206c7823 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:56:58 +0200 -Subject: [PATCH] i2c: acpi: Use acpi_dev_ready_for_enumeration() helper - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around cases where this info is not present in the firmware tables, -which is often the case on x86/ACPI devices, both frameworks allow the -provider-driver to attach info about consumers to the clks/regulators -when registering these. - -This causes problems with the probe ordering wrt drivers for consumers -of these clks/regulators. Since the lookups are only registered when the -provider-driver binds, trying to get these clks/regulators before then -results in a -ENOENT error for clks and a dummy regulator for regulators. - -To ensure the correct probe-ordering the ACPI core has code to defer the -enumeration of consumers affected by this until the providers are ready. - -Call the new acpi_dev_ready_for_enumeration() helper to avoid -enumerating / instantiating i2c-clients too early. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/i2c/i2c-core-acpi.c | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index 006e25a1b0d5..ff7f5cbb5c73 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -144,9 +144,12 @@ static int i2c_acpi_do_lookup(struct acpi_device *adev, - struct list_head resource_list; - int ret; - -- if (acpi_bus_get_status(adev) || !adev->status.present) -+ if (acpi_bus_get_status(adev)) - return -EINVAL; - -+ if (!acpi_dev_ready_for_enumeration(adev)) -+ return -ENODEV; -+ - if (acpi_match_device_ids(adev, i2c_acpi_ignored_device_ids) == 0) - return -ENODEV; - --- -2.34.1 - -From 6cfc74f67b797508c8275d04ee4e45691eb0bf32 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:56:59 +0200 -Subject: [PATCH] platform_data: Add linux/platform_data/tps68470.h file - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around cases where this info is not present in the firmware tables, -which is often the case on x86/ACPI devices, both frameworks allow the -provider-driver to attach info about consumers to the provider-device -during probe/registration of the provider device. - -The TI TPS68470 PMIC is used x86/ACPI devices with the consumer-info -missing from the ACPI tables. Thus the tps68470-clk and tps68470-regulator -drivers must provide the consumer-info at probe time. - -Define tps68470_clk_platform_data and tps68470_regulator_platform_data -structs to allow the x86 platform code to pass the necessary consumer info -to these drivers. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - include/linux/platform_data/tps68470.h | 35 ++++++++++++++++++++++++++ - 1 file changed, 35 insertions(+) - create mode 100644 include/linux/platform_data/tps68470.h - -diff --git a/include/linux/platform_data/tps68470.h b/include/linux/platform_data/tps68470.h -new file mode 100644 -index 000000000000..126d082c3f2e ---- /dev/null -+++ b/include/linux/platform_data/tps68470.h -@@ -0,0 +1,35 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * TI TPS68470 PMIC platform data definition. -+ * -+ * Copyright (c) 2021 Red Hat Inc. -+ * -+ * Red Hat authors: -+ * Hans de Goede -+ */ -+#ifndef __PDATA_TPS68470_H -+#define __PDATA_TPS68470_H -+ -+enum tps68470_regulators { -+ TPS68470_CORE, -+ TPS68470_ANA, -+ TPS68470_VCM, -+ TPS68470_VIO, -+ TPS68470_VSIO, -+ TPS68470_AUX1, -+ TPS68470_AUX2, -+ TPS68470_NUM_REGULATORS -+}; -+ -+struct regulator_init_data; -+ -+struct tps68470_regulator_platform_data { -+ const struct regulator_init_data *reg_init_data[TPS68470_NUM_REGULATORS]; -+}; -+ -+struct tps68470_clk_platform_data { -+ const char *consumer_dev_name; -+ const char *consumer_con_id; -+}; -+ -+#endif --- -2.34.1 - -From 904884587c5f88521227a0cf6aaee438e63f3d2e Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:00 +0200 -Subject: [PATCH] regulator: Introduce tps68470-regulator driver - -The TPS68470 PMIC provides Clocks, GPIOs and Regulators. At present in -the kernel the Regulators and Clocks are controlled by an OpRegion -driver designed to work with power control methods defined in ACPI, but -some platforms lack those methods, meaning drivers need to be able to -consume the resources of these chips through the usual frameworks. - -This commit adds a driver for the regulators provided by the tps68470, -and is designed to bind to the platform_device registered by the -intel_skl_int3472 module. - -This is based on this out of tree driver written by Intel: -https://github.com/intel/linux-intel-lts/blob/4.14/base/drivers/regulator/tps68470-regulator.c -with various cleanups added. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/regulator/Kconfig | 9 ++ - drivers/regulator/Makefile | 1 + - drivers/regulator/tps68470-regulator.c | 193 +++++++++++++++++++++++++ - 3 files changed, 203 insertions(+) - create mode 100644 drivers/regulator/tps68470-regulator.c - -diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig -index 4fd13b06231f..d107af5bff6c 100644 ---- a/drivers/regulator/Kconfig -+++ b/drivers/regulator/Kconfig -@@ -1339,6 +1339,15 @@ config REGULATOR_TPS65912 - help - This driver supports TPS65912 voltage regulator chip. - -+config REGULATOR_TPS68470 -+ tristate "TI TPS68370 PMIC Regulators Driver" -+ depends on INTEL_SKL_INT3472 -+ help -+ This driver adds support for the TPS68470 PMIC to register -+ regulators against the usual framework. -+ -+ The module will be called "tps68470-regulator" -+ - config REGULATOR_TPS80031 - tristate "TI TPS80031/TPS80032 power regulator driver" - depends on MFD_TPS80031 -diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile -index 9e382b50a5ef..03c318110986 100644 ---- a/drivers/regulator/Makefile -+++ b/drivers/regulator/Makefile -@@ -158,6 +158,7 @@ obj-$(CONFIG_REGULATOR_TPS6524X) += tps6524x-regulator.o - obj-$(CONFIG_REGULATOR_TPS6586X) += tps6586x-regulator.o - obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o - obj-$(CONFIG_REGULATOR_TPS65912) += tps65912-regulator.o -+obj-$(CONFIG_REGULATOR_TPS68470) += tps68470-regulator.o - obj-$(CONFIG_REGULATOR_TPS80031) += tps80031-regulator.o - obj-$(CONFIG_REGULATOR_TPS65132) += tps65132-regulator.o - obj-$(CONFIG_REGULATOR_TWL4030) += twl-regulator.o twl6030-regulator.o -diff --git a/drivers/regulator/tps68470-regulator.c b/drivers/regulator/tps68470-regulator.c -new file mode 100644 -index 000000000000..3129fa13a122 ---- /dev/null -+++ b/drivers/regulator/tps68470-regulator.c -@@ -0,0 +1,193 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Regulator driver for TPS68470 PMIC -+ * -+ * Copyright (C) 2018 Intel Corporation -+ * -+ * Authors: -+ * Zaikuo Wang -+ * Tianshu Qiu -+ * Jian Xu Zheng -+ * Yuning Pu -+ * Rajmohan Mani -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define TPS68470_REGULATOR(_name, _id, _ops, _n, _vr, \ -+ _vm, _er, _em, _t, _lr, _nlr) \ -+ [TPS68470_ ## _name] = { \ -+ .name = # _name, \ -+ .id = _id, \ -+ .ops = &_ops, \ -+ .n_voltages = _n, \ -+ .type = REGULATOR_VOLTAGE, \ -+ .owner = THIS_MODULE, \ -+ .vsel_reg = _vr, \ -+ .vsel_mask = _vm, \ -+ .enable_reg = _er, \ -+ .enable_mask = _em, \ -+ .volt_table = _t, \ -+ .linear_ranges = _lr, \ -+ .n_linear_ranges = _nlr, \ -+ } -+ -+static const struct linear_range tps68470_ldo_ranges[] = { -+ REGULATOR_LINEAR_RANGE(875000, 0, 125, 17800), -+}; -+ -+static const struct linear_range tps68470_core_ranges[] = { -+ REGULATOR_LINEAR_RANGE(900000, 0, 42, 25000), -+}; -+ -+/* Operations permitted on DCDCx, LDO2, LDO3 and LDO4 */ -+static const struct regulator_ops tps68470_regulator_ops = { -+ .is_enabled = regulator_is_enabled_regmap, -+ .enable = regulator_enable_regmap, -+ .disable = regulator_disable_regmap, -+ .get_voltage_sel = regulator_get_voltage_sel_regmap, -+ .set_voltage_sel = regulator_set_voltage_sel_regmap, -+ .list_voltage = regulator_list_voltage_linear_range, -+ .map_voltage = regulator_map_voltage_linear_range, -+}; -+ -+static const struct regulator_desc regulators[] = { -+ TPS68470_REGULATOR(CORE, TPS68470_CORE, -+ tps68470_regulator_ops, 43, TPS68470_REG_VDVAL, -+ TPS68470_VDVAL_DVOLT_MASK, TPS68470_REG_VDCTL, -+ TPS68470_VDCTL_EN_MASK, -+ NULL, tps68470_core_ranges, -+ ARRAY_SIZE(tps68470_core_ranges)), -+ TPS68470_REGULATOR(ANA, TPS68470_ANA, -+ tps68470_regulator_ops, 126, TPS68470_REG_VAVAL, -+ TPS68470_VAVAL_AVOLT_MASK, TPS68470_REG_VACTL, -+ TPS68470_VACTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ TPS68470_REGULATOR(VCM, TPS68470_VCM, -+ tps68470_regulator_ops, 126, TPS68470_REG_VCMVAL, -+ TPS68470_VCMVAL_VCVOLT_MASK, TPS68470_REG_VCMCTL, -+ TPS68470_VCMCTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ TPS68470_REGULATOR(VIO, TPS68470_VIO, -+ tps68470_regulator_ops, 126, TPS68470_REG_VIOVAL, -+ TPS68470_VIOVAL_IOVOLT_MASK, TPS68470_REG_S_I2C_CTL, -+ TPS68470_S_I2C_CTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ -+/* -+ * (1) This register must have same setting as VIOVAL if S_IO LDO is used to -+ * power daisy chained IOs in the receive side. -+ * (2) If there is no I2C daisy chain it can be set freely. -+ * -+ */ -+ TPS68470_REGULATOR(VSIO, TPS68470_VSIO, -+ tps68470_regulator_ops, 126, TPS68470_REG_VSIOVAL, -+ TPS68470_VSIOVAL_IOVOLT_MASK, TPS68470_REG_S_I2C_CTL, -+ TPS68470_S_I2C_CTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ TPS68470_REGULATOR(AUX1, TPS68470_AUX1, -+ tps68470_regulator_ops, 126, TPS68470_REG_VAUX1VAL, -+ TPS68470_VAUX1VAL_AUX1VOLT_MASK, -+ TPS68470_REG_VAUX1CTL, -+ TPS68470_VAUX1CTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ TPS68470_REGULATOR(AUX2, TPS68470_AUX2, -+ tps68470_regulator_ops, 126, TPS68470_REG_VAUX2VAL, -+ TPS68470_VAUX2VAL_AUX2VOLT_MASK, -+ TPS68470_REG_VAUX2CTL, -+ TPS68470_VAUX2CTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+}; -+ -+#define TPS68470_REG_INIT_DATA(_name, _min_uV, _max_uV) \ -+ [TPS68470_ ## _name] = { \ -+ .constraints = { \ -+ .name = # _name, \ -+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | \ -+ REGULATOR_CHANGE_STATUS, \ -+ .min_uV = _min_uV, \ -+ .max_uV = _max_uV, \ -+ }, \ -+ } -+ -+struct regulator_init_data tps68470_init[] = { -+ TPS68470_REG_INIT_DATA(CORE, 900000, 1950000), -+ TPS68470_REG_INIT_DATA(ANA, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(VCM, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(VIO, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(VSIO, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(AUX1, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(AUX2, 875000, 3100000), -+}; -+ -+static int tps68470_regulator_probe(struct platform_device *pdev) -+{ -+ struct tps68470_regulator_platform_data *pdata = pdev->dev.platform_data; -+ struct regulator_config config = { }; -+ struct regmap *tps68470_regmap; -+ struct regulator_dev *rdev; -+ int i; -+ -+ tps68470_regmap = dev_get_drvdata(pdev->dev.parent); -+ -+ for (i = 0; i < TPS68470_NUM_REGULATORS; i++) { -+ config.dev = pdev->dev.parent; -+ config.regmap = tps68470_regmap; -+ if (pdata && pdata->reg_init_data[i]) -+ config.init_data = pdata->reg_init_data[i]; -+ else -+ config.init_data = &tps68470_init[i]; -+ -+ rdev = devm_regulator_register(&pdev->dev, ®ulators[i], &config); -+ if (IS_ERR(rdev)) { -+ dev_err(&pdev->dev, "failed to register %s regulator\n", -+ regulators[i].name); -+ return PTR_ERR(rdev); -+ } -+ } -+ -+ return 0; -+} -+ -+static struct platform_driver tps68470_regulator_driver = { -+ .driver = { -+ .name = "tps68470-regulator", -+ }, -+ .probe = tps68470_regulator_probe, -+}; -+ -+/* -+ * The ACPI tps68470 probe-ordering depends on the clk/gpio/regulator drivers -+ * registering before the drivers for the camera-sensors which use them bind. -+ * subsys_initcall() ensures this when the drivers are builtin. -+ */ -+static int __init tps68470_regulator_init(void) -+{ -+ return platform_driver_register(&tps68470_regulator_driver); -+} -+subsys_initcall(tps68470_regulator_init); -+ -+static void __exit tps68470_regulator_exit(void) -+{ -+ platform_driver_unregister(&tps68470_regulator_driver); -+} -+module_exit(tps68470_regulator_exit); -+ -+MODULE_ALIAS("platform:tps68470-regulator"); -+MODULE_DESCRIPTION("TPS68470 voltage regulator driver"); -+MODULE_LICENSE("GPL v2"); --- -2.34.1 - -From 4728befba9b381eba694c8cd5fb301d4509aedbc Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:01 +0200 -Subject: [PATCH] clk: Introduce clk-tps68470 driver - -The TPS68470 PMIC provides Clocks, GPIOs and Regulators. At present in -the kernel the Regulators and Clocks are controlled by an OpRegion -driver designed to work with power control methods defined in ACPI, but -some platforms lack those methods, meaning drivers need to be able to -consume the resources of these chips through the usual frameworks. - -This commit adds a driver for the clocks provided by the tps68470, -and is designed to bind to the platform_device registered by the -intel_skl_int3472 module. - -This is based on this out of tree driver written by Intel: -https://github.com/intel/linux-intel-lts/blob/4.14/base/drivers/clk/clk-tps68470.c -with various cleanups added. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/clk/Kconfig | 6 + - drivers/clk/Makefile | 1 + - drivers/clk/clk-tps68470.c | 256 +++++++++++++++++++++++++++++++++++ - include/linux/mfd/tps68470.h | 11 ++ - 4 files changed, 274 insertions(+) - create mode 100644 drivers/clk/clk-tps68470.c - -diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig -index c5b3dc97396a..7dffecac83d1 100644 ---- a/drivers/clk/Kconfig -+++ b/drivers/clk/Kconfig -@@ -169,6 +169,12 @@ config COMMON_CLK_CDCE706 - help - This driver supports TI CDCE706 programmable 3-PLL clock synthesizer. - -+config COMMON_CLK_TPS68470 -+ tristate "Clock Driver for TI TPS68470 PMIC" -+ depends on I2C && REGMAP_I2C && INTEL_SKL_INT3472 -+ help -+ This driver supports the clocks provided by TPS68470 -+ - config COMMON_CLK_CDCE925 - tristate "Clock driver for TI CDCE913/925/937/949 devices" - depends on I2C -diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile -index e42312121e51..6b6a88ae1425 100644 ---- a/drivers/clk/Makefile -+++ b/drivers/clk/Makefile -@@ -63,6 +63,7 @@ obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o - obj-$(CONFIG_COMMON_CLK_STM32F) += clk-stm32f4.o - obj-$(CONFIG_COMMON_CLK_STM32H7) += clk-stm32h7.o - obj-$(CONFIG_COMMON_CLK_STM32MP157) += clk-stm32mp1.o -+obj-$(CONFIG_COMMON_CLK_TPS68470) += clk-tps68470.o - obj-$(CONFIG_CLK_TWL6040) += clk-twl6040.o - obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o - obj-$(CONFIG_COMMON_CLK_VC5) += clk-versaclock5.o -diff --git a/drivers/clk/clk-tps68470.c b/drivers/clk/clk-tps68470.c -new file mode 100644 -index 000000000000..27e8cbd0f60e ---- /dev/null -+++ b/drivers/clk/clk-tps68470.c -@@ -0,0 +1,256 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Clock driver for TPS68470 PMIC -+ * -+ * Copyright (C) 2018 Intel Corporation -+ * -+ * Authors: -+ * Zaikuo Wang -+ * Tianshu Qiu -+ * Jian Xu Zheng -+ * Yuning Pu -+ * Antti Laakso -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define TPS68470_CLK_NAME "tps68470-clk" -+ -+#define to_tps68470_clkdata(clkd) \ -+ container_of(clkd, struct tps68470_clkdata, clkout_hw) -+ -+struct tps68470_clkout_freqs { -+ unsigned long freq; -+ unsigned int xtaldiv; -+ unsigned int plldiv; -+ unsigned int postdiv; -+ unsigned int buckdiv; -+ unsigned int boostdiv; -+} clk_freqs[] = { -+/* -+ * The PLL is used to multiply the crystal oscillator -+ * frequency range of 3 MHz to 27 MHz by a programmable -+ * factor of F = (M/N)*(1/P) such that the output -+ * available at the HCLK_A or HCLK_B pins are in the range -+ * of 4 MHz to 64 MHz in increments of 0.1 MHz -+ * -+ * hclk_# = osc_in * (((plldiv*2)+320) / (xtaldiv+30)) * (1 / 2^postdiv) -+ * -+ * PLL_REF_CLK should be as close as possible to 100kHz -+ * PLL_REF_CLK = input clk / XTALDIV[7:0] + 30) -+ * -+ * PLL_VCO_CLK = (PLL_REF_CLK * (plldiv*2 + 320)) -+ * -+ * BOOST should be as close as possible to 2Mhz -+ * BOOST = PLL_VCO_CLK / (BOOSTDIV[4:0] + 16) * -+ * -+ * BUCK should be as close as possible to 5.2Mhz -+ * BUCK = PLL_VCO_CLK / (BUCKDIV[3:0] + 5) -+ * -+ * osc_in xtaldiv plldiv postdiv hclk_# -+ * 20Mhz 170 32 1 19.2Mhz -+ * 20Mhz 170 40 1 20Mhz -+ * 20Mhz 170 80 1 24Mhz -+ * -+ */ -+ { 19200000, 170, 32, 1, 2, 3 }, -+ { 20000000, 170, 40, 1, 3, 4 }, -+ { 24000000, 170, 80, 1, 4, 8 }, -+}; -+ -+struct tps68470_clkdata { -+ struct clk_hw clkout_hw; -+ struct regmap *regmap; -+ struct clk *clk; -+ int clk_cfg_idx; -+}; -+ -+static int tps68470_clk_is_prepared(struct clk_hw *hw) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ int val; -+ -+ if (regmap_read(clkdata->regmap, TPS68470_REG_PLLCTL, &val)) -+ return 0; -+ -+ return val & TPS68470_PLL_EN_MASK; -+} -+ -+static int tps68470_clk_prepare(struct clk_hw *hw) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ int idx = clkdata->clk_cfg_idx; -+ -+ regmap_write(clkdata->regmap, TPS68470_REG_BOOSTDIV, clk_freqs[idx].boostdiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_BUCKDIV, clk_freqs[idx].buckdiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLSWR, TPS68470_PLLSWR_DEFAULT); -+ regmap_write(clkdata->regmap, TPS68470_REG_XTALDIV, clk_freqs[idx].xtaldiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLDIV, clk_freqs[idx].plldiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_POSTDIV, clk_freqs[idx].postdiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_POSTDIV2, clk_freqs[idx].postdiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG2, TPS68470_CLKCFG2_DRV_STR_2MA); -+ -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLCTL, -+ TPS68470_OSC_EXT_CAP_DEFAULT << TPS68470_OSC_EXT_CAP_SHIFT | -+ TPS68470_CLK_SRC_XTAL << TPS68470_CLK_SRC_SHIFT); -+ -+ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG1, -+ (TPS68470_PLL_OUTPUT_ENABLE << -+ TPS68470_OUTPUT_A_SHIFT) | -+ (TPS68470_PLL_OUTPUT_ENABLE << -+ TPS68470_OUTPUT_B_SHIFT)); -+ -+ regmap_update_bits(clkdata->regmap, TPS68470_REG_PLLCTL, -+ TPS68470_PLL_EN_MASK, TPS68470_PLL_EN_MASK); -+ -+ return 0; -+} -+ -+static void tps68470_clk_unprepare(struct clk_hw *hw) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ -+ /* disable clock first*/ -+ regmap_update_bits(clkdata->regmap, TPS68470_REG_PLLCTL, TPS68470_PLL_EN_MASK, 0); -+ -+ /* write hw defaults */ -+ regmap_write(clkdata->regmap, TPS68470_REG_BOOSTDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_BUCKDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLSWR, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_XTALDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_POSTDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG2, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG1, 0); -+} -+ -+static unsigned long tps68470_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ -+ return clk_freqs[clkdata->clk_cfg_idx].freq; -+} -+ -+static int tps68470_clk_cfg_lookup(unsigned long rate) -+{ -+ long diff, best_diff = LONG_MAX; -+ int i, best_idx = 0; -+ -+ for (i = 0; i < ARRAY_SIZE(clk_freqs); i++) { -+ diff = clk_freqs[i].freq - rate; -+ if (diff == 0) -+ return i; -+ -+ diff = abs(diff); -+ if (diff < best_diff) { -+ best_diff = diff; -+ best_idx = i; -+ } -+ } -+ -+ return best_idx; -+} -+ -+static long tps68470_clk_round_rate(struct clk_hw *hw, unsigned long rate, -+ unsigned long *parent_rate) -+{ -+ int idx = tps68470_clk_cfg_lookup(rate); -+ -+ return clk_freqs[idx].freq; -+} -+ -+static int tps68470_clk_set_rate(struct clk_hw *hw, unsigned long rate, -+ unsigned long parent_rate) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ int idx = tps68470_clk_cfg_lookup(rate); -+ -+ if (rate != clk_freqs[idx].freq) -+ return -EINVAL; -+ -+ clkdata->clk_cfg_idx = idx; -+ return 0; -+} -+ -+static const struct clk_ops tps68470_clk_ops = { -+ .is_prepared = tps68470_clk_is_prepared, -+ .prepare = tps68470_clk_prepare, -+ .unprepare = tps68470_clk_unprepare, -+ .recalc_rate = tps68470_clk_recalc_rate, -+ .round_rate = tps68470_clk_round_rate, -+ .set_rate = tps68470_clk_set_rate, -+}; -+ -+static struct clk_init_data tps68470_clk_initdata = { -+ .name = TPS68470_CLK_NAME, -+ .ops = &tps68470_clk_ops, -+}; -+ -+static int tps68470_clk_probe(struct platform_device *pdev) -+{ -+ struct tps68470_clk_platform_data *pdata = pdev->dev.platform_data; -+ struct tps68470_clkdata *tps68470_clkdata; -+ int ret; -+ -+ tps68470_clkdata = devm_kzalloc(&pdev->dev, sizeof(*tps68470_clkdata), -+ GFP_KERNEL); -+ if (!tps68470_clkdata) -+ return -ENOMEM; -+ -+ tps68470_clkdata->regmap = dev_get_drvdata(pdev->dev.parent); -+ tps68470_clkdata->clkout_hw.init = &tps68470_clk_initdata; -+ tps68470_clkdata->clk = devm_clk_register(&pdev->dev, &tps68470_clkdata->clkout_hw); -+ if (IS_ERR(tps68470_clkdata->clk)) -+ return PTR_ERR(tps68470_clkdata->clk); -+ -+ ret = devm_clk_hw_register_clkdev(&pdev->dev, &tps68470_clkdata->clkout_hw, -+ TPS68470_CLK_NAME, NULL); -+ if (ret) -+ return ret; -+ -+ if (pdata) { -+ ret = devm_clk_hw_register_clkdev(&pdev->dev, -+ &tps68470_clkdata->clkout_hw, -+ pdata->consumer_con_id, -+ pdata->consumer_dev_name); -+ if (ret) -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static struct platform_driver tps68470_clk_driver = { -+ .driver = { -+ .name = TPS68470_CLK_NAME, -+ }, -+ .probe = tps68470_clk_probe, -+}; -+ -+/* -+ * The ACPI tps68470 probe-ordering depends on the clk/gpio/regulator drivers -+ * registering before the drivers for the camera-sensors which use them bind. -+ * subsys_initcall() ensures this when the drivers are builtin. -+ */ -+static int __init tps68470_clk_init(void) -+{ -+ return platform_driver_register(&tps68470_clk_driver); -+} -+subsys_initcall(tps68470_clk_init); -+ -+static void __exit tps68470_clk_exit(void) -+{ -+ platform_driver_unregister(&tps68470_clk_driver); -+} -+module_exit(tps68470_clk_exit); -+ -+MODULE_ALIAS("platform:tps68470-clk"); -+MODULE_DESCRIPTION("clock driver for TPS68470 pmic"); -+MODULE_LICENSE("GPL"); -diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h -index ffe81127d91c..7807fa329db0 100644 ---- a/include/linux/mfd/tps68470.h -+++ b/include/linux/mfd/tps68470.h -@@ -75,6 +75,17 @@ - #define TPS68470_CLKCFG1_MODE_A_MASK GENMASK(1, 0) - #define TPS68470_CLKCFG1_MODE_B_MASK GENMASK(3, 2) - -+#define TPS68470_CLKCFG2_DRV_STR_2MA 0x05 -+#define TPS68470_PLL_OUTPUT_ENABLE 0x02 -+#define TPS68470_CLK_SRC_XTAL BIT(0) -+#define TPS68470_PLLSWR_DEFAULT GENMASK(1, 0) -+#define TPS68470_OSC_EXT_CAP_DEFAULT 0x05 -+ -+#define TPS68470_OUTPUT_A_SHIFT 0x00 -+#define TPS68470_OUTPUT_B_SHIFT 0x02 -+#define TPS68470_CLK_SRC_SHIFT GENMASK(2, 0) -+#define TPS68470_OSC_EXT_CAP_SHIFT BIT(2) -+ - #define TPS68470_GPIO_CTL_REG_A(x) (TPS68470_REG_GPCTL0A + (x) * 2) - #define TPS68470_GPIO_CTL_REG_B(x) (TPS68470_REG_GPCTL0B + (x) * 2) - #define TPS68470_GPIO_MODE_MASK GENMASK(1, 0) --- -2.34.1 - -From a5c1472bd351c109e453fa8aeb634b8750339d68 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 10 Oct 2021 20:57:02 +0200 -Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain - -The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic -can be forwarded to a device connected to the PMIC as though it were -connected directly to the system bus. Enable this mode when the chip -is initialised. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - .../x86/intel/int3472/intel_skl_int3472_tps68470.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -index c05b4cf502fe..42e688f4cad4 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -+++ b/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -@@ -45,6 +45,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) - return ret; - } - -+ /* Enable I2C daisy chain */ -+ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); -+ if (ret) { -+ dev_err(dev, "Failed to enable i2c daisy chain\n"); -+ return ret; -+ } -+ - dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); - - return 0; --- -2.34.1 - -From e35314123d7b2f963fbfe376d0dc025ab1351215 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:03 +0200 -Subject: [PATCH] platform/x86: int3472: Split into 2 drivers - -The intel_skl_int3472.ko module contains 2 separate drivers, -the int3472_discrete platform driver and the int3472_tps68470 -I2C-driver. - -These 2 drivers contain very little shared code, only -skl_int3472_get_acpi_buffer() and skl_int3472_fill_cldb() are -shared. - -Split the module into 2 drivers, linking the little shared code -directly into both. - -This will allow us to add soft-module dependencies for the -tps68470 clk, gpio and regulator drivers to the new -intel_skl_int3472_tps68470.ko to help with probe ordering issues -without causing these modules to get loaded on boards which only -use the int3472_discrete platform driver. - -While at it also rename the .c and .h files to remove the -cumbersome intel_skl_int3472_ prefix. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/Makefile | 9 ++-- - ...lk_and_regulator.c => clk_and_regulator.c} | 2 +- - drivers/platform/x86/intel/int3472/common.c | 54 +++++++++++++++++++ - .../{intel_skl_int3472_common.h => common.h} | 3 -- - ...ntel_skl_int3472_discrete.c => discrete.c} | 28 ++++++++-- - ...ntel_skl_int3472_tps68470.c => tps68470.c} | 23 +++++++- - 6 files changed, 105 insertions(+), 14 deletions(-) - rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_clk_and_regulator.c => clk_and_regulator.c} (99%) - create mode 100644 drivers/platform/x86/intel/int3472/common.c - rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_common.h => common.h} (94%) - rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_discrete.c => discrete.c} (93%) - rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_tps68470.c => tps68470.c} (85%) - -diff --git a/drivers/platform/x86/intel/int3472/Makefile b/drivers/platform/x86/intel/int3472/Makefile -index 2362e04db18d..4a4b2518ea16 100644 ---- a/drivers/platform/x86/intel/int3472/Makefile -+++ b/drivers/platform/x86/intel/int3472/Makefile -@@ -1,5 +1,4 @@ --obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o --intel_skl_int3472-y := intel_skl_int3472_common.o \ -- intel_skl_int3472_discrete.o \ -- intel_skl_int3472_tps68470.o \ -- intel_skl_int3472_clk_and_regulator.o -+obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472_discrete.o \ -+ intel_skl_int3472_tps68470.o -+intel_skl_int3472_discrete-y := discrete.o clk_and_regulator.o common.o -+intel_skl_int3472_tps68470-y := tps68470.o common.o -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel/int3472/clk_and_regulator.c -similarity index 99% -rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c -rename to drivers/platform/x86/intel/int3472/clk_and_regulator.c -index 1700e7557a82..1cf958983e86 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c -+++ b/drivers/platform/x86/intel/int3472/clk_and_regulator.c -@@ -9,7 +9,7 @@ - #include - #include - --#include "intel_skl_int3472_common.h" -+#include "common.h" - - /* - * The regulators have to have .ops to be valid, but the only ops we actually -diff --git a/drivers/platform/x86/intel/int3472/common.c b/drivers/platform/x86/intel/int3472/common.c -new file mode 100644 -index 000000000000..350655a9515b ---- /dev/null -+++ b/drivers/platform/x86/intel/int3472/common.c -@@ -0,0 +1,54 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+ -+#include "common.h" -+ -+union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id) -+{ -+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; -+ acpi_handle handle = adev->handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(handle, id, NULL, &buffer); -+ if (ACPI_FAILURE(status)) -+ return ERR_PTR(-ENODEV); -+ -+ obj = buffer.pointer; -+ if (!obj) -+ return ERR_PTR(-ENODEV); -+ -+ if (obj->type != ACPI_TYPE_BUFFER) { -+ acpi_handle_err(handle, "%s object is not an ACPI buffer\n", id); -+ kfree(obj); -+ return ERR_PTR(-EINVAL); -+ } -+ -+ return obj; -+} -+ -+int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) -+{ -+ union acpi_object *obj; -+ int ret; -+ -+ obj = skl_int3472_get_acpi_buffer(adev, "CLDB"); -+ if (IS_ERR(obj)) -+ return PTR_ERR(obj); -+ -+ if (obj->buffer.length > sizeof(*cldb)) { -+ acpi_handle_err(adev->handle, "The CLDB buffer is too large\n"); -+ ret = -EINVAL; -+ goto out_free_obj; -+ } -+ -+ memcpy(cldb, obj->buffer.pointer, obj->buffer.length); -+ ret = 0; -+ -+out_free_obj: -+ kfree(obj); -+ return ret; -+} -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel/int3472/common.h -similarity index 94% -rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h -rename to drivers/platform/x86/intel/int3472/common.h -index 714fde73b524..d14944ee8586 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h -+++ b/drivers/platform/x86/intel/int3472/common.h -@@ -105,9 +105,6 @@ struct int3472_discrete_device { - struct gpiod_lookup_table gpios; - }; - --int skl_int3472_discrete_probe(struct platform_device *pdev); --int skl_int3472_discrete_remove(struct platform_device *pdev); --int skl_int3472_tps68470_probe(struct i2c_client *client); - union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, - char *id); - int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel/int3472/discrete.c -similarity index 93% -rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c -rename to drivers/platform/x86/intel/int3472/discrete.c -index e59d79c7e82f..a19a1f5dbdd7 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c -+++ b/drivers/platform/x86/intel/int3472/discrete.c -@@ -14,7 +14,7 @@ - #include - #include - --#include "intel_skl_int3472_common.h" -+#include "common.h" - - /* - * 79234640-9e10-4fea-a5c1-b5aa8b19756f -@@ -332,7 +332,9 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) - return 0; - } - --int skl_int3472_discrete_probe(struct platform_device *pdev) -+static int skl_int3472_discrete_remove(struct platform_device *pdev); -+ -+static int skl_int3472_discrete_probe(struct platform_device *pdev) - { - struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); - struct int3472_discrete_device *int3472; -@@ -395,7 +397,7 @@ int skl_int3472_discrete_probe(struct platform_device *pdev) - return ret; - } - --int skl_int3472_discrete_remove(struct platform_device *pdev) -+static int skl_int3472_discrete_remove(struct platform_device *pdev) - { - struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); - -@@ -411,3 +413,23 @@ int skl_int3472_discrete_remove(struct platform_device *pdev) - - return 0; - } -+ -+static const struct acpi_device_id int3472_device_id[] = { -+ { "INT3472", 0 }, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, int3472_device_id); -+ -+static struct platform_driver int3472_discrete = { -+ .driver = { -+ .name = "int3472-discrete", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe = skl_int3472_discrete_probe, -+ .remove = skl_int3472_discrete_remove, -+}; -+module_platform_driver(int3472_discrete); -+ -+MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Discrete Device Driver"); -+MODULE_AUTHOR("Daniel Scally "); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -similarity index 85% -rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -rename to drivers/platform/x86/intel/int3472/tps68470.c -index 42e688f4cad4..b94cf66ab61f 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -7,7 +7,7 @@ - #include - #include - --#include "intel_skl_int3472_common.h" -+#include "common.h" - - #define DESIGNED_FOR_CHROMEOS 1 - #define DESIGNED_FOR_WINDOWS 2 -@@ -102,7 +102,7 @@ static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) - return DESIGNED_FOR_WINDOWS; - } - --int skl_int3472_tps68470_probe(struct i2c_client *client) -+static int skl_int3472_tps68470_probe(struct i2c_client *client) - { - struct acpi_device *adev = ACPI_COMPANION(&client->dev); - struct regmap *regmap; -@@ -142,3 +142,22 @@ int skl_int3472_tps68470_probe(struct i2c_client *client) - - return ret; - } -+ -+static const struct acpi_device_id int3472_device_id[] = { -+ { "INT3472", 0 }, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, int3472_device_id); -+ -+static struct i2c_driver int3472_tps68470 = { -+ .driver = { -+ .name = "int3472-tps68470", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe_new = skl_int3472_tps68470_probe, -+}; -+module_i2c_driver(int3472_tps68470); -+ -+MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI TPS68470 Device Driver"); -+MODULE_AUTHOR("Daniel Scally "); -+MODULE_LICENSE("GPL v2"); --- -2.34.1 - -From b44d9c6c3f239d701373339adca648fe76bdebd5 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:04 +0200 -Subject: [PATCH] platform/x86: int3472: Add get_sensor_adev_and_name() helper - -The discrete.c code is not the only code which needs to lookup the -acpi_device and device-name for the sensor for which the INT3472 -ACPI-device is a GPIO/clk/regulator provider. - -The tps68470.c code also needs this functionality, so factor this -out into a new get_sensor_adev_and_name() helper. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/common.c | 28 +++++++++++++++++++ - drivers/platform/x86/intel/int3472/common.h | 3 ++ - drivers/platform/x86/intel/int3472/discrete.c | 22 +++------------ - 3 files changed, 35 insertions(+), 18 deletions(-) - -diff --git a/drivers/platform/x86/intel/int3472/common.c b/drivers/platform/x86/intel/int3472/common.c -index 350655a9515b..77cf058e4168 100644 ---- a/drivers/platform/x86/intel/int3472/common.c -+++ b/drivers/platform/x86/intel/int3472/common.c -@@ -52,3 +52,31 @@ int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) - kfree(obj); - return ret; - } -+ -+/* sensor_adev_ret may be NULL, name_ret must not be NULL */ -+int skl_int3472_get_sensor_adev_and_name(struct device *dev, -+ struct acpi_device **sensor_adev_ret, -+ const char **name_ret) -+{ -+ struct acpi_device *adev = ACPI_COMPANION(dev); -+ struct acpi_device *sensor; -+ int ret = 0; -+ -+ sensor = acpi_dev_get_first_consumer_dev(adev); -+ if (!sensor) { -+ dev_err(dev, "INT3472 seems to have no dependents.\n"); -+ return -ENODEV; -+ } -+ -+ *name_ret = devm_kasprintf(dev, GFP_KERNEL, I2C_DEV_NAME_FORMAT, -+ acpi_dev_name(sensor)); -+ if (!*name_ret) -+ ret = -ENOMEM; -+ -+ if (ret == 0 && sensor_adev_ret) -+ *sensor_adev_ret = sensor; -+ else -+ acpi_dev_put(sensor); -+ -+ return ret; -+} -diff --git a/drivers/platform/x86/intel/int3472/common.h b/drivers/platform/x86/intel/int3472/common.h -index d14944ee8586..53270d19c73a 100644 ---- a/drivers/platform/x86/intel/int3472/common.h -+++ b/drivers/platform/x86/intel/int3472/common.h -@@ -108,6 +108,9 @@ struct int3472_discrete_device { - union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, - char *id); - int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); -+int skl_int3472_get_sensor_adev_and_name(struct device *dev, -+ struct acpi_device **sensor_adev_ret, -+ const char **name_ret); - - int skl_int3472_register_clock(struct int3472_discrete_device *int3472); - void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472); -diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c -index a19a1f5dbdd7..efd31a0c7a88 100644 ---- a/drivers/platform/x86/intel/int3472/discrete.c -+++ b/drivers/platform/x86/intel/int3472/discrete.c -@@ -363,19 +363,10 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) - int3472->dev = &pdev->dev; - platform_set_drvdata(pdev, int3472); - -- int3472->sensor = acpi_dev_get_first_consumer_dev(adev); -- if (!int3472->sensor) { -- dev_err(&pdev->dev, "INT3472 seems to have no dependents.\n"); -- return -ENODEV; -- } -- -- int3472->sensor_name = devm_kasprintf(int3472->dev, GFP_KERNEL, -- I2C_DEV_NAME_FORMAT, -- acpi_dev_name(int3472->sensor)); -- if (!int3472->sensor_name) { -- ret = -ENOMEM; -- goto err_put_sensor; -- } -+ ret = skl_int3472_get_sensor_adev_and_name(&pdev->dev, &int3472->sensor, -+ &int3472->sensor_name); -+ if (ret) -+ return ret; - - /* - * Initialising this list means we can call gpiod_remove_lookup_table() -@@ -390,11 +381,6 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) - } - - return 0; -- --err_put_sensor: -- acpi_dev_put(int3472->sensor); -- -- return ret; - } - - static int skl_int3472_discrete_remove(struct platform_device *pdev) --- -2.34.1 - -From c2c331a5b417f82997b886919e334957b98ff44a Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:05 +0200 -Subject: [PATCH] platform/x86: int3472: Pass tps68470_clk_platform_data to the - tps68470-regulator MFD-cell - -Pass tps68470_clk_platform_data to the tps68470-clk MFD-cell, -so that sensors which use the TPS68470 can find their clock. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/tps68470.c | 33 ++++++++++++++----- - 1 file changed, 25 insertions(+), 8 deletions(-) - -diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -index b94cf66ab61f..78e34e7b6969 100644 ---- a/drivers/platform/x86/intel/int3472/tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -5,6 +5,7 @@ - #include - #include - #include -+#include - #include - - #include "common.h" -@@ -17,12 +18,6 @@ static const struct mfd_cell tps68470_cros[] = { - { .name = "tps68470_pmic_opregion" }, - }; - --static const struct mfd_cell tps68470_win[] = { -- { .name = "tps68470-gpio" }, -- { .name = "tps68470-clk" }, -- { .name = "tps68470-regulator" }, --}; -- - static const struct regmap_config tps68470_regmap_config = { - .reg_bits = 8, - .val_bits = 8, -@@ -105,10 +100,17 @@ static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) - static int skl_int3472_tps68470_probe(struct i2c_client *client) - { - struct acpi_device *adev = ACPI_COMPANION(&client->dev); -+ struct tps68470_clk_platform_data clk_pdata = {}; -+ struct mfd_cell *cells; - struct regmap *regmap; - int device_type; - int ret; - -+ ret = skl_int3472_get_sensor_adev_and_name(&client->dev, NULL, -+ &clk_pdata.consumer_dev_name); -+ if (ret) -+ return ret; -+ - regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); - if (IS_ERR(regmap)) { - dev_err(&client->dev, "Failed to create regmap: %ld\n", PTR_ERR(regmap)); -@@ -126,9 +128,24 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - device_type = skl_int3472_tps68470_calc_type(adev); - switch (device_type) { - case DESIGNED_FOR_WINDOWS: -- ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, -- tps68470_win, ARRAY_SIZE(tps68470_win), -+ cells = kcalloc(3, sizeof(*cells), GFP_KERNEL); -+ if (!cells) -+ return -ENOMEM; -+ -+ cells[0].name = "tps68470-clk"; -+ cells[0].platform_data = &clk_pdata; -+ cells[0].pdata_size = sizeof(clk_pdata); -+ cells[1].name = "tps68470-regulator"; -+ /* -+ * The GPIO cell must be last because acpi_gpiochip_add() calls -+ * acpi_dev_clear_dependencies() and the clk + regulators must -+ * be ready when this happens. -+ */ -+ cells[2].name = "tps68470-gpio"; -+ -+ ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, cells, 3, - NULL, 0, NULL); -+ kfree(cells); - break; - case DESIGNED_FOR_CHROMEOS: - ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, --- -2.34.1 - -From 0ed13262a631c62e5f9dcee55f9247cc963ec376 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:06 +0200 -Subject: [PATCH] platform/x86: int3472: Pass tps68470_regulator_platform_data - to the tps68470-regulator MFD-cell - -Pass tps68470_regulator_platform_data to the tps68470-regulator -MFD-cell, specifying the voltages of the various regulators and -tying the regulators to the sensor supplies so that sensors which use -the TPS68470 can find their regulators. - -Since the voltages and supply connections are board-specific, this -introduces a DMI matches int3472_tps68470_board_data struct which -contains the necessary per-board info. - -This per-board info also includes GPIO lookup information for the -sensor GPIOs which may be connected to the tps68470 gpios. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/Makefile | 2 +- - drivers/platform/x86/intel/int3472/tps68470.c | 43 +++++-- - drivers/platform/x86/intel/int3472/tps68470.h | 25 ++++ - .../x86/intel/int3472/tps68470_board_data.c | 118 ++++++++++++++++++ - 4 files changed, 180 insertions(+), 8 deletions(-) - create mode 100644 drivers/platform/x86/intel/int3472/tps68470.h - create mode 100644 drivers/platform/x86/intel/int3472/tps68470_board_data.c - -diff --git a/drivers/platform/x86/intel/int3472/Makefile b/drivers/platform/x86/intel/int3472/Makefile -index 4a4b2518ea16..ca56e7eea781 100644 ---- a/drivers/platform/x86/intel/int3472/Makefile -+++ b/drivers/platform/x86/intel/int3472/Makefile -@@ -1,4 +1,4 @@ - obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472_discrete.o \ - intel_skl_int3472_tps68470.o - intel_skl_int3472_discrete-y := discrete.o clk_and_regulator.o common.o --intel_skl_int3472_tps68470-y := tps68470.o common.o -+intel_skl_int3472_tps68470-y := tps68470.o tps68470_board_data.o common.o -diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -index 78e34e7b6969..aae24d228770 100644 ---- a/drivers/platform/x86/intel/int3472/tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -9,6 +9,7 @@ - #include - - #include "common.h" -+#include "tps68470.h" - - #define DESIGNED_FOR_CHROMEOS 1 - #define DESIGNED_FOR_WINDOWS 2 -@@ -100,6 +101,7 @@ static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) - static int skl_int3472_tps68470_probe(struct i2c_client *client) - { - struct acpi_device *adev = ACPI_COMPANION(&client->dev); -+ const struct int3472_tps68470_board_data *board_data; - struct tps68470_clk_platform_data clk_pdata = {}; - struct mfd_cell *cells; - struct regmap *regmap; -@@ -128,6 +130,12 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - device_type = skl_int3472_tps68470_calc_type(adev); - switch (device_type) { - case DESIGNED_FOR_WINDOWS: -+ board_data = int3472_tps68470_get_board_data(dev_name(&client->dev)); -+ if (!board_data) { -+ dev_err(&client->dev, "No board-data found for this laptop/tablet model\n"); -+ return -ENODEV; -+ } -+ - cells = kcalloc(3, sizeof(*cells), GFP_KERNEL); - if (!cells) - return -ENOMEM; -@@ -136,6 +144,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - cells[0].platform_data = &clk_pdata; - cells[0].pdata_size = sizeof(clk_pdata); - cells[1].name = "tps68470-regulator"; -+ cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; -+ cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); - /* - * The GPIO cell must be last because acpi_gpiochip_add() calls - * acpi_dev_clear_dependencies() and the clk + regulators must -@@ -143,9 +153,15 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - */ - cells[2].name = "tps68470-gpio"; - -+ gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_table); -+ - ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, cells, 3, - NULL, 0, NULL); - kfree(cells); -+ -+ if (ret) -+ gpiod_remove_lookup_table(board_data->tps68470_gpio_lookup_table); -+ - break; - case DESIGNED_FOR_CHROMEOS: - ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, -@@ -160,18 +176,31 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - return ret; - } - -+static int skl_int3472_tps68470_remove(struct i2c_client *client) -+{ -+ const struct int3472_tps68470_board_data *board_data; -+ -+ board_data = int3472_tps68470_get_board_data(dev_name(&client->dev)); -+ if (board_data) -+ gpiod_remove_lookup_table(board_data->tps68470_gpio_lookup_table); -+ -+ return 0; -+} -+ -+ - static const struct acpi_device_id int3472_device_id[] = { -- { "INT3472", 0 }, -- { } -+ { "INT3472", 0 }, -+ { } - }; - MODULE_DEVICE_TABLE(acpi, int3472_device_id); - - static struct i2c_driver int3472_tps68470 = { -- .driver = { -- .name = "int3472-tps68470", -- .acpi_match_table = int3472_device_id, -- }, -- .probe_new = skl_int3472_tps68470_probe, -+ .driver = { -+ .name = "int3472-tps68470", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe_new = skl_int3472_tps68470_probe, -+ .remove = skl_int3472_tps68470_remove, - }; - module_i2c_driver(int3472_tps68470); - -diff --git a/drivers/platform/x86/intel/int3472/tps68470.h b/drivers/platform/x86/intel/int3472/tps68470.h -new file mode 100644 -index 000000000000..cfd33eb62740 ---- /dev/null -+++ b/drivers/platform/x86/intel/int3472/tps68470.h -@@ -0,0 +1,25 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* -+ * TI TPS68470 PMIC platform data definition. -+ * -+ * Copyright (c) 2021 Red Hat Inc. -+ * -+ * Red Hat authors: -+ * Hans de Goede -+ */ -+ -+#ifndef _INTEL_SKL_INT3472_TPS68470_H -+#define _INTEL_SKL_INT3472_TPS68470_H -+ -+struct gpiod_lookup_table; -+struct tps68470_regulator_platform_data; -+ -+struct int3472_tps68470_board_data { -+ const char *dev_name; -+ struct gpiod_lookup_table *tps68470_gpio_lookup_table; -+ const struct tps68470_regulator_platform_data *tps68470_regulator_pdata; -+}; -+ -+const struct int3472_tps68470_board_data *int3472_tps68470_get_board_data(const char *dev_name); -+ -+#endif -diff --git a/drivers/platform/x86/intel/int3472/tps68470_board_data.c b/drivers/platform/x86/intel/int3472/tps68470_board_data.c -new file mode 100644 -index 000000000000..96954a789bb8 ---- /dev/null -+++ b/drivers/platform/x86/intel/int3472/tps68470_board_data.c -@@ -0,0 +1,118 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * TI TPS68470 PMIC platform data definition. -+ * -+ * Copyright (c) 2021 Dan Scally -+ * Copyright (c) 2021 Red Hat Inc. -+ * -+ * Red Hat authors: -+ * Hans de Goede -+ */ -+ -+#include -+#include -+#include -+#include -+#include "tps68470.h" -+ -+static struct regulator_consumer_supply int347a_core_consumer_supplies[] = { -+ REGULATOR_SUPPLY("dvdd", "i2c-INT347A:00"), -+}; -+ -+static struct regulator_consumer_supply int347a_ana_consumer_supplies[] = { -+ REGULATOR_SUPPLY("avdd", "i2c-INT347A:00"), -+}; -+ -+static struct regulator_consumer_supply int347a_vsio_consumer_supplies[] = { -+ REGULATOR_SUPPLY("dovdd", "i2c-INT347A:00"), -+}; -+ -+static const struct regulator_init_data surface_go_tps68470_core_reg_init_data = { -+ .constraints = { -+ .min_uV = 1200000, -+ .max_uV = 1200000, -+ .apply_uV = 1, -+ .valid_ops_mask = REGULATOR_CHANGE_STATUS, -+ }, -+ .num_consumer_supplies = ARRAY_SIZE(int347a_core_consumer_supplies), -+ .consumer_supplies = int347a_core_consumer_supplies, -+}; -+ -+static const struct regulator_init_data surface_go_tps68470_ana_reg_init_data = { -+ .constraints = { -+ .min_uV = 2815200, -+ .max_uV = 2815200, -+ .apply_uV = 1, -+ .valid_ops_mask = REGULATOR_CHANGE_STATUS, -+ }, -+ .num_consumer_supplies = ARRAY_SIZE(int347a_ana_consumer_supplies), -+ .consumer_supplies = int347a_ana_consumer_supplies, -+}; -+ -+static const struct regulator_init_data surface_go_tps68470_vsio_reg_init_data = { -+ .constraints = { -+ .min_uV = 1800600, -+ .max_uV = 1800600, -+ .apply_uV = 1, -+ .valid_ops_mask = REGULATOR_CHANGE_STATUS, -+ }, -+ .num_consumer_supplies = ARRAY_SIZE(int347a_vsio_consumer_supplies), -+ .consumer_supplies = int347a_vsio_consumer_supplies, -+}; -+ -+static const struct tps68470_regulator_platform_data surface_go_tps68470_pdata = { -+ .reg_init_data = { -+ [TPS68470_CORE] = &surface_go_tps68470_core_reg_init_data, -+ [TPS68470_ANA] = &surface_go_tps68470_ana_reg_init_data, -+ [TPS68470_VSIO] = &surface_go_tps68470_vsio_reg_init_data, -+ }, -+}; -+ -+static struct gpiod_lookup_table surface_go_tps68470_gpios = { -+ .dev_id = "i2c-INT347A:00", -+ .table = { -+ GPIO_LOOKUP("tps68470-gpio", 9, "reset", GPIO_ACTIVE_LOW), -+ GPIO_LOOKUP("tps68470-gpio", 7, "powerdown", GPIO_ACTIVE_LOW) -+ } -+}; -+ -+static const struct int3472_tps68470_board_data surface_go_tps68470_board_data = { -+ .dev_name = "i2c-INT3472:05", -+ .tps68470_gpio_lookup_table = &surface_go_tps68470_gpios, -+ .tps68470_regulator_pdata = &surface_go_tps68470_pdata, -+}; -+ -+static const struct dmi_system_id int3472_tps68470_board_data_table[] = { -+ { -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Go"), -+ }, -+ .driver_data = (void *)&surface_go_tps68470_board_data, -+ }, -+ { -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Go 2"), -+ }, -+ .driver_data = (void *)&surface_go_tps68470_board_data, -+ }, -+ { } -+}; -+ -+const struct int3472_tps68470_board_data *int3472_tps68470_get_board_data(const char *dev_name) -+{ -+ const struct int3472_tps68470_board_data *board_data; -+ const struct dmi_system_id *match; -+ -+ match = dmi_first_match(int3472_tps68470_board_data_table); -+ while (match) { -+ board_data = match->driver_data; -+ if (strcmp(board_data->dev_name, dev_name) == 0) -+ return board_data; -+ -+ dmi_first_match(++match); -+ } -+ -+ return NULL; -+} --- -2.34.1 - -From cd3df413475b164bf7145d2dd23a4715d1cf3475 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:07 +0200 -Subject: [PATCH] platform/x86: int3472: Deal with probe ordering issues - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around this info missing from the ACPI tables on devices where -the int3472 driver is used, the int3472 MFD-cell drivers attach info about -consumers to the clks/regulators when registering these. - -This causes problems with the probe ordering wrt drivers for consumers -of these clks/regulators. Since the lookups are only registered when the -provider-driver binds, trying to get these clks/regulators before then -results in a -ENOENT error for clks and a dummy regulator for regulators. - -All the sensor ACPI fw-nodes have a _DEP dependency on the INT3472 ACPI -fw-node, so to work around these probe ordering issues the ACPI core / -i2c-code does not instantiate the I2C-clients for any ACPI devices -which have a _DEP dependency on an INT3472 ACPI device until all -_DEP-s are met. - -This relies on acpi_dev_clear_dependencies() getting called by the driver -for the _DEP-s when they are ready, add a acpi_dev_clear_dependencies() -call to the discrete.c probe code. - -In the tps68470 case calling acpi_dev_clear_dependencies() is already done -by the acpi_gpiochip_add() call done by the driver for the GPIO MFD cell -(The GPIO cell is deliberately the last cell created to make sure the -clk + regulator cells are already instantiated when this happens). - -However for proper probe ordering, the clk/regulator cells must not just -be instantiated the must be fully ready (the clks + regulators must be -registered with their subsystems). - -Add MODULE_SOFTDEP dependencies for the clk and regulator drivers for -the instantiated MFD-cells so that these are loaded before us and so -that they bind immediately when the platform-devs are instantiated. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/discrete.c | 1 + - drivers/platform/x86/intel/int3472/tps68470.c | 6 ++++++ - 2 files changed, 7 insertions(+) - -diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c -index efd31a0c7a88..18e6d51acc96 100644 ---- a/drivers/platform/x86/intel/int3472/discrete.c -+++ b/drivers/platform/x86/intel/int3472/discrete.c -@@ -380,6 +380,7 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) - return ret; - } - -+ acpi_dev_clear_dependencies(adev); - return 0; - } - -diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -index aae24d228770..21c6c1a6edfc 100644 ---- a/drivers/platform/x86/intel/int3472/tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -173,6 +173,11 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - return device_type; - } - -+ /* -+ * No acpi_dev_clear_dependencies() here, since the acpi_gpiochip_add() -+ * for the GPIO cell already does this. -+ */ -+ - return ret; - } - -@@ -207,3 +212,4 @@ module_i2c_driver(int3472_tps68470); - MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI TPS68470 Device Driver"); - MODULE_AUTHOR("Daniel Scally "); - MODULE_LICENSE("GPL v2"); -+MODULE_SOFTDEP("pre: clk-tps68470 tps68470-regulator"); --- -2.34.1 - -From 8f8518750551d1ce2224cf1bb4c6e01de4457aad Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 4 Nov 2021 21:46:27 +0000 -Subject: [PATCH] media: i2c: Add integration time margin to ov8865 - -Without this integration margin to reduce the max exposure, it seems -that we trip over a limit that results in the image being entirely -black when max exposure is set. Add the margin to prevent this issue. - -With thanks to jhautbois for spotting and reporting. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 4 +++- - 1 file changed, 3 insertions(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index d5af8aedf5e8..966487e32bfe 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -143,6 +143,7 @@ - #define OV8865_EXPOSURE_CTRL_L_REG 0x3502 - #define OV8865_EXPOSURE_CTRL_L(v) ((v) & GENMASK(7, 0)) - #define OV8865_EXPOSURE_GAIN_MANUAL_REG 0x3503 -+#define OV8865_INTEGRATION_TIME_MARGIN 8 - - #define OV8865_GAIN_CTRL_H_REG 0x3508 - #define OV8865_GAIN_CTRL_H(v) (((v) & GENMASK(12, 8)) >> 8) -@@ -2462,7 +2463,8 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - if (ctrl->id == V4L2_CID_VBLANK) { - int exposure_max; - -- exposure_max = sensor->state.mode->output_size_y + ctrl->val; -+ exposure_max = sensor->state.mode->output_size_y + ctrl->val - -+ OV8865_INTEGRATION_TIME_MARGIN; - __v4l2_ctrl_modify_range(sensor->ctrls.exposure, - sensor->ctrls.exposure->minimum, - exposure_max, --- -2.34.1 - -From 65477c3b9dbb7a63efc1a9d482c56a0f1d2a6674 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 4 Nov 2021 21:48:38 +0000 -Subject: [PATCH] media: i2c: Fix max gain in ov8865 - -The maximum gain figure in the v4l2 ctrl is wrong. The field is 12 bits -wide, which is where the 8191 figure comes from, but the datasheet is -specific that maximum gain is 16x (the low seven bits are fractional, so -16x gain is 2048) - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 966487e32bfe..6c78edb65d1e 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2535,7 +2535,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Gain */ - -- v4l2_ctrl_new_std(handler, ops, V4L2_CID_ANALOGUE_GAIN, 128, 8191, 128, -+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_ANALOGUE_GAIN, 128, 2048, 128, - 128); - - /* White Balance */ --- -2.34.1 - -From 820ca5877c3fbb53d2204e2e39d0ed521f762f9c Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Fri, 3 Dec 2021 12:51:08 +0100 -Subject: [PATCH] mfd: intel-lpss: Fix I2C4 not being available on the - Microsoft Surface Go & Go 2 - -Many DSDTs for Kaby Lake and Kaby Lake Refresh models contain a -_SB.PCI0.GEXP ACPI Device node describing an I2C attached PCA953x -GPIO expander. - -This seems to be something which is copy and pasted from the DSDT -from some reference design since this ACPI Device is present even on -models where no such GPIO expander is used at all, such as on the -Microsoft Surface Go & Go 2. - -This ACPI Device is a problem because it contains a SystemMemory -OperationRegion which covers the MMIO for the I2C4 I2C controller. This -causes the MFD cell for the I2C4 controller to not be instantiated due -to a resource conflict, requiring the use of acpi_enforce_resources=lax -to work around this. - -I have done an extensive analysis of all the ACPI tables on the -Microsoft Surface Go and the _SB.PCI0.GEXP ACPI Device's methods are -not used by any code in the ACPI tables, neither are any of them -directly called by any Linux kernel code. This is unsurprising since -running i2cdetect on the I2C4 bus shows that there is no GPIO -expander chip present on these devices at all. - -This commit adds a PCI subsystem vendor:device table listing PCI devices -where it is known to be safe to ignore resource conflicts with ACPI -declared SystemMemory regions. - -This makes the I2C4 bus work out of the box on the Microsoft Surface -Go & Go 2, which is necessary for the cameras on these devices to work. - -Cc: Dan Scally -Cc: Kate Hsuan -Cc: Maximilian Luz -Reviewed-by: Laurent Pinchart -Reviewed-by: Andy Shevchenko -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/mfd/intel-lpss-pci.c | 12 ++++++++++++ - drivers/mfd/intel-lpss.c | 1 + - drivers/mfd/intel-lpss.h | 1 + - 3 files changed, 14 insertions(+) - -diff --git a/drivers/mfd/intel-lpss-pci.c b/drivers/mfd/intel-lpss-pci.c -index c54d19fb184c..4f3c041668a1 100644 ---- a/drivers/mfd/intel-lpss-pci.c -+++ b/drivers/mfd/intel-lpss-pci.c -@@ -17,6 +17,15 @@ - - #include "intel-lpss.h" - -+/* Some DSDTs have an unused GEXP ACPI device conflicting with I2C4 resources */ -+static const struct pci_device_id ignore_resource_conflicts_ids[] = { -+ /* Microsoft Surface Go (version 1) I2C4 */ -+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_INTEL, 0x9d64, 0x152d, 0x1182), }, -+ /* Microsoft Surface Go 2 I2C4 */ -+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_INTEL, 0x9d64, 0x152d, 0x1237), }, -+ { } -+}; -+ - static int intel_lpss_pci_probe(struct pci_dev *pdev, - const struct pci_device_id *id) - { -@@ -35,6 +44,9 @@ static int intel_lpss_pci_probe(struct pci_dev *pdev, - info->mem = &pdev->resource[0]; - info->irq = pdev->irq; - -+ if (pci_match_id(ignore_resource_conflicts_ids, pdev)) -+ info->ignore_resource_conflicts = true; -+ - pdev->d3cold_delay = 0; - - /* Probably it is enough to set this for iDMA capable devices only */ -diff --git a/drivers/mfd/intel-lpss.c b/drivers/mfd/intel-lpss.c -index 0e15afc39f54..cfbee2cfba6b 100644 ---- a/drivers/mfd/intel-lpss.c -+++ b/drivers/mfd/intel-lpss.c -@@ -401,6 +401,7 @@ int intel_lpss_probe(struct device *dev, - return ret; - - lpss->cell->swnode = info->swnode; -+ lpss->cell->ignore_resource_conflicts = info->ignore_resource_conflicts; - - intel_lpss_init_dev(lpss); - -diff --git a/drivers/mfd/intel-lpss.h b/drivers/mfd/intel-lpss.h -index 22dbc4aed793..062ce95b68b9 100644 ---- a/drivers/mfd/intel-lpss.h -+++ b/drivers/mfd/intel-lpss.h -@@ -19,6 +19,7 @@ struct software_node; - - struct intel_lpss_platform_info { - struct resource *mem; -+ bool ignore_resource_conflicts; - int irq; - unsigned long clk_rate; - const char *clk_con_id; --- -2.34.1 - -From bf41fc14463e04c814bd337547192264f5ed5f05 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 6 Jan 2022 22:12:38 +0000 -Subject: [PATCH] platform/x86: int3472: Add board data for Surface Go 3 - -The Surface Go 3 needs some board data in order to configure the -TPS68470 PMIC - add entries to the tables in tps68470_board_data.c -that define the configuration that's needed. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - .../x86/intel/int3472/tps68470_board_data.c | 13 +++++++++++++ - 1 file changed, 13 insertions(+) - -diff --git a/drivers/platform/x86/intel/int3472/tps68470_board_data.c b/drivers/platform/x86/intel/int3472/tps68470_board_data.c -index 96954a789bb8..2dcadfa62196 100644 ---- a/drivers/platform/x86/intel/int3472/tps68470_board_data.c -+++ b/drivers/platform/x86/intel/int3472/tps68470_board_data.c -@@ -82,6 +82,12 @@ static const struct int3472_tps68470_board_data surface_go_tps68470_board_data = - .tps68470_regulator_pdata = &surface_go_tps68470_pdata, - }; - -+static const struct int3472_tps68470_board_data surface_go3_tps68470_board_data = { -+ .dev_name = "i2c-INT3472:01", -+ .tps68470_gpio_lookup_table = &surface_go_tps68470_gpios, -+ .tps68470_regulator_pdata = &surface_go_tps68470_pdata, -+}; -+ - static const struct dmi_system_id int3472_tps68470_board_data_table[] = { - { - .matches = { -@@ -97,6 +103,13 @@ static const struct dmi_system_id int3472_tps68470_board_data_table[] = { - }, - .driver_data = (void *)&surface_go_tps68470_board_data, - }, -+ { -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Go 3"), -+ }, -+ .driver_data = (void *)&surface_go3_tps68470_board_data, -+ }, - { } - }; - --- -2.34.1 - diff --git a/patches/5.15/0011-amd-gpio.patch b/patches/5.15/0011-amd-gpio.patch deleted file mode 100644 index 108745eca..000000000 --- a/patches/5.15/0011-amd-gpio.patch +++ /dev/null @@ -1,109 +0,0 @@ -From 1fa47c006afddaf5d0feefd7aa753dadb180cb0b Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Sat, 29 May 2021 17:47:38 +1000 -Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 - override - -This patch is the work of Thomas Gleixner and is -copied from: -https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ - -This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin -setup that is missing in the laptops ACPI table. - -This patch was used for validation of the issue, and is not a proper -fix, but is probably a better temporary hack than continuing to probe -the Legacy PIC and run with the PIC in an unknown state. - -Patchset: amd-gpio ---- - arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ - 1 file changed, 17 insertions(+) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 14bcd59bcdee..159d26a664ca 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -22,6 +22,7 @@ - #include - #include - #include -+#include - - #include - #include -@@ -1143,6 +1144,17 @@ static void __init mp_config_acpi_legacy_irqs(void) - } - } - -+static const struct dmi_system_id surface_quirk[] __initconst = { -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") -+ }, -+ }, -+ {} -+}; -+ - /* - * Parse IOAPIC related entries in MADT - * returns 0 on success, < 0 on error -@@ -1198,6 +1210,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) - acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, - acpi_gbl_FADT.sci_interrupt); - -+ if (dmi_check_system(surface_quirk)) { -+ pr_warn("Surface hack: Override irq 7\n"); -+ mp_override_legacy_irq(7, 3, 3, 7); -+ } -+ - /* Fill in identity legacy mappings where no override */ - mp_config_acpi_legacy_irqs(); - --- -2.34.1 - -From 9894d200ff30a74831e7e0bab9528c7cabadadb0 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 3 Jun 2021 14:04:26 +0200 -Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override - quirk - -The 13" version of the Surface Laptop 4 has the same problem as the 15" -version, but uses a different SKU. Add that SKU to the quirk as well. - -Patchset: amd-gpio ---- - arch/x86/kernel/acpi/boot.c | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 159d26a664ca..77cd54849a8b 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -1146,12 +1146,19 @@ static void __init mp_config_acpi_legacy_irqs(void) - - static const struct dmi_system_id surface_quirk[] __initconst = { - { -- .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") - }, - }, -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") -+ }, -+ }, - {} - }; - --- -2.34.1 - diff --git a/patches/5.15/0012-misc-fixes.patch b/patches/5.15/0012-misc-fixes.patch deleted file mode 100644 index b34cfd90c..000000000 --- a/patches/5.15/0012-misc-fixes.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 7f6b73f659673d6bb91d148354c96d47a5b25af5 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 8 Dec 2021 16:22:50 +0100 -Subject: [PATCH] acpi/battery: Add device HID and quirk for Microsoft Surface - Go 3 - -For some reason, the Microsoft Surface Go 3 uses the standard ACPI -interface for battery information, but does not use the standard PNP0C0A -HID. Instead it uses MSHW0146 as identifier. Add that ID to the driver -as this seems to work well. - -Additionally, the power state is not updated immediately after the AC -has been (un-)plugged, so add the respective quirk for that. - -Signed-off-by: Maximilian Luz -Patchset: misc-fixes ---- - drivers/acpi/battery.c | 12 ++++++++++++ - 1 file changed, 12 insertions(+) - -diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c -index 8afa85d6eb6a..65882cb791a5 100644 ---- a/drivers/acpi/battery.c -+++ b/drivers/acpi/battery.c -@@ -59,6 +59,10 @@ MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); - - static const struct acpi_device_id battery_device_ids[] = { - {"PNP0C0A", 0}, -+ -+ /* Microsoft Surface Go 3 */ -+ {"MSHW0146", 0}, -+ - {"", 0}, - }; - -@@ -1155,6 +1159,14 @@ static const struct dmi_system_id bat_dmi_table[] __initconst = { - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo MIIX 320-10ICR"), - }, - }, -+ { -+ /* Microsoft Surface Go 3 */ -+ .callback = battery_notification_delay_quirk, -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Go 3"), -+ }, -+ }, - {}, - }; - --- -2.34.1 - diff --git a/patches/5.16/0001-surface3-oemb.patch b/patches/5.16/0001-surface3-oemb.patch deleted file mode 100644 index 35fc45728..000000000 --- a/patches/5.16/0001-surface3-oemb.patch +++ /dev/null @@ -1,101 +0,0 @@ -From b281caed8c64e4995e940b6392e343f9de430a78 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 18 Oct 2020 16:42:44 +0900 -Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI - table - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI data into dmi_system_id tables used -for quirks so that each driver can enable quirks even on the affected -systems. - -On affected systems, DMI data will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: surface3-oemb ---- - drivers/platform/surface/surface3-wmi.c | 7 +++++++ - sound/soc/codecs/rt5645.c | 9 +++++++++ - sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ - 3 files changed, 24 insertions(+) - -diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c -index 09ac9cfc40d8..c626109cf445 100644 ---- a/drivers/platform/surface/surface3-wmi.c -+++ b/drivers/platform/surface/surface3-wmi.c -@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - #endif - { } - }; -diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c -index 197c56047947..9893e9c3cdf7 100644 ---- a/sound/soc/codecs/rt5645.c -+++ b/sound/soc/codecs/rt5645.c -@@ -3718,6 +3718,15 @@ static const struct dmi_system_id dmi_platform_data[] = { - }, - .driver_data = (void *)&intel_braswell_platform_data, - }, -+ { -+ .ident = "Microsoft Surface 3", -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)&intel_braswell_platform_data, -+ }, - { - /* - * Match for the GPDwin which unfortunately uses somewhat -diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -index c60a5e8e7bc9..e947133a2c36 100644 ---- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c -+++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .callback = cht_surface_quirk_cb, -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - { } - }; - --- -2.35.1 - diff --git a/patches/5.16/0002-mwifiex.patch b/patches/5.16/0002-mwifiex.patch deleted file mode 100644 index 957f6d857..000000000 --- a/patches/5.16/0002-mwifiex.patch +++ /dev/null @@ -1,845 +0,0 @@ -From 6a027f5c058e37000185b232ebbf80e8d69d1140 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:32:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_wsid quirk for Surface 3 - -This commit adds reset_wsid quirk and uses this quirk for Surface 3 on -card reset. - -To reset mwifiex on Surface 3, it seems that calling the _DSM method -exists in \_SB.WSID [1] device is required. - -On Surface 3, calling the _DSM method removes/re-probes the card by -itself. So, need to place the reset function before performing FLR and -skip performing any other reset-related works. - -Note that Surface Pro 3 also has the WSID device [2], but it seems to need -more work. This commit only supports Surface 3 yet. - -[1] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_3/dsdt.dsl#L11947-L12011 -[2] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_pro_3/dsdt.dsl#L12164-L12216 - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 10 +++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 83 +++++++++++++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.h | 6 ++ - 3 files changed, 99 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index c3f5583ea70d..3f5138008594 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -2993,6 +2993,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - -+ /* On Surface 3, reset_wsid method removes then re-probes card by -+ * itself. So, need to place it here and skip performing any other -+ * reset-related works. -+ */ -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) { -+ mwifiex_pcie_reset_wsid_quirk(card->dev); -+ /* skip performing any other reset-related works */ -+ return; -+ } -+ - /* We can't afford to wait here; remove() might be waiting on us. If we - * can't grab the device lock, maybe we'll get another chance later. - */ -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 0234cf3c2974..563dd0d5ac79 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -15,10 +15,21 @@ - * this warranty disclaimer. - */ - -+#include - #include - - #include "pcie_quirks.h" - -+/* For reset_wsid quirk */ -+#define ACPI_WSID_PATH "\\_SB.WSID" -+#define WSID_REV 0x0 -+#define WSID_FUNC_WIFI_PWR_OFF 0x1 -+#define WSID_FUNC_WIFI_PWR_ON 0x2 -+/* WSID _DSM UUID: "534ea3bf-fcc2-4e7a-908f-a13978f0c7ef" */ -+static const guid_t wsid_dsm_guid = -+ GUID_INIT(0x534ea3bf, 0xfcc2, 0x4e7a, -+ 0x90, 0x8f, 0xa1, 0x39, 0x78, 0xf0, 0xc7, 0xef); -+ - /* quirk table based on DMI matching */ - static const struct dmi_system_id mwifiex_quirk_table[] = { - { -@@ -87,6 +98,14 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - {} - }; - -@@ -103,6 +122,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - dev_info(&pdev->dev, "no quirks enabled\n"); - if (card->quirks & QUIRK_FW_RST_D3COLD) - dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) -+ dev_info(&pdev->dev, -+ "quirk reset_wsid for Surface 3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -@@ -159,3 +181,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) - - return 0; - } -+ -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev) -+{ -+ acpi_handle handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n"); -+ -+ status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&pdev->dev, "No ACPI handle for path %s\n", -+ ACPI_WSID_PATH); -+ return -ENODEV; -+ } -+ -+ if (!acpi_has_method(handle, "_DSM")) { -+ dev_err(&pdev->dev, "_DSM method not found\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power off func\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power on func\n"); -+ return -ENODEV; -+ } -+ -+ /* card will be removed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi off...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi off\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ /* card will be re-probed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi on...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi on\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ return 0; -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 8ec4176d698f..25370c5a4f59 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -19,5 +19,11 @@ - - #define QUIRK_FW_RST_D3COLD BIT(0) - -+/* Surface 3 and Surface Pro 3 have the same _DSM method but need to -+ * be handled differently. Currently, only S3 is supported. -+ */ -+#define QUIRK_FW_RST_WSID_S3 BIT(1) -+ - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); --- -2.35.1 - -From 8f7a59af7ea9a70bc220b4456dad4697db24a8a4 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Wed, 30 Sep 2020 18:08:24 +0900 -Subject: [PATCH] mwifiex: pcie: (OEMB) add quirk for Surface 3 with broken DMI - table - -(made referring to http://git.osdn.net/view?p=android-x86/kernel.git;a=commitdiff;h=18e2e857c57633b25b3b4120f212224a108cd883) - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI info for the affected Surface 3. - -On affected systems, DMI info will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie_quirks.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 563dd0d5ac79..32e2f000e57b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -106,6 +106,15 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - {} - }; - --- -2.35.1 - -From 9d264868c3bf477498608c96af9d37b03d361e92 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 4 Oct 2020 00:11:49 +0900 -Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ - -Currently, mwifiex fw will crash after suspend on recent kernel series. -On Windows, it seems that the root port of wifi will never enter D3 state -(stay on D0 state). And on Linux, disabling the D3 state for the -bridge fixes fw crashing after suspend. - -This commit disables the D3 state of root port on driver initialization -and fixes fw crashing after suspend. - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 27 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 3f5138008594..372dde99725c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -380,6 +380,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) - { - struct pcie_service_card *card; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - int ret; - - pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", -@@ -421,6 +422,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return -1; - } - -+ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing -+ * after suspend -+ */ -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ parent_pdev->bridge_d3 = false; -+ - return 0; - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 32e2f000e57b..356401bab59c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -38,7 +38,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5", -@@ -47,7 +48,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -56,7 +58,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 6", -@@ -64,7 +67,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 1", -@@ -72,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 2", -@@ -80,7 +85,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 1", -@@ -88,7 +94,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 2", -@@ -96,7 +103,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface 3", -@@ -134,6 +142,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_FW_RST_WSID_S3) - dev_info(&pdev->dev, - "quirk reset_wsid for Surface 3 enabled\n"); -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ dev_info(&pdev->dev, -+ "quirk no_brigde_d3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 25370c5a4f59..a1de111ad1db 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -23,6 +23,7 @@ - * be handled differently. Currently, only S3 is supported. - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) -+#define QUIRK_NO_BRIDGE_D3 BIT(2) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.35.1 - -From c6c76184cea6e3dfd665006a46f6ebca65f094a5 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 3 Nov 2020 13:28:04 +0100 -Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface - devices - -The most recent firmware of the 88W8897 card reports a hardcoded LTR -value to the system during initialization, probably as an (unsuccessful) -attempt of the developers to fix firmware crashes. This LTR value -prevents most of the Microsoft Surface devices from entering deep -powersaving states (either platform C-State 10 or S0ix state), because -the exit latency of that state would be higher than what the card can -tolerate. - -Turns out the card works just the same (including the firmware crashes) -no matter if that hardcoded LTR value is reported or not, so it's kind -of useless and only prevents us from saving power. - -To get rid of those hardcoded LTR reports, it's possible to reset the -PCI bridge device after initializing the cards firmware. I'm not exactly -sure why that works, maybe the power management subsystem of the PCH -resets its stored LTR values when doing a function level reset of the -bridge device. Doing the reset once after starting the wifi firmware -works very well, probably because the firmware only reports that LTR -value a single time during firmware startup. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 31 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 372dde99725c..586c79dc0a98 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -1781,9 +1781,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) - static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; -+ struct pci_dev *pdev = card->dev; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; - int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; - -+ /* Trigger a function level reset of the PCI bridge device, this makes -+ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value -+ * that prevents the system from entering package C10 and S0ix powersaving -+ * states. -+ * We need to do it here because it must happen after firmware -+ * initialization and this function is called after that is done. -+ */ -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ pci_reset_function(parent_pdev); -+ - /* Write the RX ring read pointer in to reg->rx_rdptr */ - if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | - tx_wrap)) { -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 356401bab59c..6437f067d07a 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -39,7 +39,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5", -@@ -49,7 +50,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -59,7 +61,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 6", -@@ -68,7 +71,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 1", -@@ -77,7 +81,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 2", -@@ -86,7 +91,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 1", -@@ -95,7 +101,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 2", -@@ -104,7 +111,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface 3", -@@ -145,6 +153,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_NO_BRIDGE_D3) - dev_info(&pdev->dev, - "quirk no_brigde_d3 enabled\n"); -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index a1de111ad1db..0e429779bb04 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -24,6 +24,7 @@ - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) - #define QUIRK_NO_BRIDGE_D3 BIT(2) -+#define QUIRK_DO_FLR_ON_BRIDGE BIT(3) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.35.1 - -From 187231fc2e26b2321f904dd43f83fff761b37356 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 25 Mar 2021 11:33:02 +0100 -Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell - 88W8897 - -The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) -is used in a lot of Microsoft Surface devices, and all those devices -suffer from very low 2.4GHz wifi connection speeds while bluetooth is -enabled. The reason for that is that the default passive scanning -interval for Bluetooth Low Energy devices is quite high in Linux -(interval of 60 msec and scan window of 30 msec, see hci_core.c), and -the Marvell chip is known for its bad bt+wifi coexisting performance. - -So decrease that passive scan interval and make the scan window shorter -on this particular device to allow for spending more time transmitting -wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and -the new scan window is 6.25 msec (0xa * 0,625 msec). - -This change has a very large impact on the 2.4GHz wifi speeds and gets -it up to performance comparable with the Windows driver, which seems to -apply a similar quirk. - -The interval and window length were tested and found to work very well -with a lot of Bluetooth Low Energy devices, including the Surface Pen, a -Bluetooth Speaker and two modern Bluetooth headphones. All devices were -discovered immediately after turning them on. Even lower values were -also tested, but they introduced longer delays until devices get -discovered. - -Patchset: mwifiex ---- - drivers/bluetooth/btusb.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index ea72afb7abea..dc0d1d8e0daa 100644 ---- a/drivers/bluetooth/btusb.c -+++ b/drivers/bluetooth/btusb.c -@@ -61,6 +61,7 @@ static struct usb_driver btusb_driver; - #define BTUSB_QCA_WCN6855 0x1000000 - #define BTUSB_INTEL_BROKEN_SHUTDOWN_LED 0x2000000 - #define BTUSB_INTEL_BROKEN_INITIAL_NCMD 0x4000000 -+#define BTUSB_LOWER_LESCAN_INTERVAL 0x8000000 - - static const struct usb_device_id btusb_table[] = { - /* Generic Bluetooth USB device */ -@@ -375,6 +376,7 @@ static const struct usb_device_id blacklist_table[] = { - { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, -+ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, - - /* Intel Bluetooth devices */ - { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, -@@ -3908,6 +3910,19 @@ static int btusb_probe(struct usb_interface *intf, - if (id->driver_info & BTUSB_MARVELL) - hdev->set_bdaddr = btusb_set_bdaddr_marvell; - -+ /* The Marvell 88W8897 combined wifi and bluetooth card is known for -+ * very bad bt+wifi coexisting performance. -+ * -+ * Decrease the passive BT Low Energy scan interval a bit -+ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter -+ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly -+ * higher wifi throughput while passively scanning for BT LE devices. -+ */ -+ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { -+ hdev->le_scan_interval = 0x0190; -+ hdev->le_scan_window = 0x000a; -+ } -+ - if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && - (id->driver_info & BTUSB_MEDIATEK)) { - hdev->setup = btusb_mtk_setup; --- -2.35.1 - -From 65e0f3a3dfa02dffa631e5bdeed93e993aa6130b Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 10 Nov 2020 12:49:56 +0100 -Subject: [PATCH] mwifiex: Use non-posted PCI register writes - -On the 88W8897 card it's very important the TX ring write pointer is -updated correctly to its new value before setting the TX ready -interrupt, otherwise the firmware appears to crash (probably because -it's trying to DMA-read from the wrong place). - -Since PCI uses "posted writes" when writing to a register, it's not -guaranteed that a write will happen immediately. That means the pointer -might be outdated when setting the TX ready interrupt, leading to -firmware crashes especially when ASPM L1 and L1 substates are enabled -(because of the higher link latency, the write will probably take -longer). - -So fix those firmware crashes by always forcing non-posted writes. We do -that by simply reading back the register after writing it, just as a lot -of other drivers do. - -There are two reproducers that are fixed with this patch: - -1) During rx/tx traffic and with ASPM L1 substates enabled (the enabled -substates are platform dependent), the firmware crashes and eventually a -command timeout appears in the logs. That crash is fixed by using a -non-posted write in mwifiex_pcie_send_data(). - -2) When sending lots of commands to the card, waking it up from sleep in -very quick intervals, the firmware eventually crashes. That crash -appears to be fixed by some other non-posted write included here. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 586c79dc0a98..f87bc9bdfba7 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -238,6 +238,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) - - iowrite32(data, card->pci_mmap1 + reg); - -+ /* Do a read-back, which makes the write non-posted, ensuring the -+ * completion before returning. -+ * The firmware of the 88W8897 card is buggy and this avoids crashes. -+ */ -+ ioread32(card->pci_mmap1 + reg); -+ - return 0; - } - --- -2.35.1 - -From 64651de263b769378a255cdab1e18468c0b03ad0 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 14:23:05 +0200 -Subject: [PATCH] mwifiex: Add quirk to disable deep sleep with certain - hardware revision - -The 88W8897 pcie card with the hardware revision 20 apparently has a -hardware issue where the card wakes up from deep sleep randomly and very -often, somewhat depending on the card activity, maybe the hardware has a -floating wakeup pin or something. - -Those continuous wakeups prevent the card from entering host sleep when -the computer suspends. And because the host won't answer to events from -the card anymore while it's suspended, the firmwares internal -powersaving state machine seems to get confused and the card can't sleep -anymore at all after that. - -Since we can't work around that hardware bug in the firmware, let's -get the hardware revision string from the firmware and match it with -known bad revisions. Then disable auto deep sleep for those revisions, -which makes sure we no longer get those spurious wakeups. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/main.c | 14 ++++++++++++++ - drivers/net/wireless/marvell/mwifiex/main.h | 1 + - .../net/wireless/marvell/mwifiex/sta_cmdresp.c | 16 ++++++++++++++++ - 3 files changed, 31 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index 19b996c6a260..5ab2ad4c7006 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -226,6 +226,19 @@ static int mwifiex_process_rx(struct mwifiex_adapter *adapter) - return 0; - } - -+static void maybe_quirk_fw_disable_ds(struct mwifiex_adapter *adapter) -+{ -+ struct mwifiex_private *priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA); -+ struct mwifiex_ver_ext ver_ext; -+ -+ set_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &adapter->work_flags); -+ -+ memset(&ver_ext, 0, sizeof(ver_ext)); -+ ver_ext.version_str_sel = 1; -+ mwifiex_send_cmd(priv, HostCmd_CMD_VERSION_EXT, -+ HostCmd_ACT_GEN_GET, 0, &ver_ext, false); -+} -+ - /* - * The main process. - * -@@ -356,6 +369,7 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - if (adapter->hw_status == MWIFIEX_HW_STATUS_INIT_DONE) { - adapter->hw_status = MWIFIEX_HW_STATUS_READY; - mwifiex_init_fw_complete(adapter); -+ maybe_quirk_fw_disable_ds(adapter); - } - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index 90012cbcfd15..1e829d84b1f6 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -524,6 +524,7 @@ enum mwifiex_adapter_work_flags { - MWIFIEX_IS_SUSPENDED, - MWIFIEX_IS_HS_CONFIGURED, - MWIFIEX_IS_HS_ENABLING, -+ MWIFIEX_IS_REQUESTING_FW_VEREXT, - }; - - struct mwifiex_band_config { -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -index 6b5d35d9e69f..8e49ebca1847 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -@@ -708,6 +708,22 @@ static int mwifiex_ret_ver_ext(struct mwifiex_private *priv, - { - struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext; - -+ if (test_and_clear_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &priv->adapter->work_flags)) { -+ if (strncmp(ver_ext->version_str, "ChipRev:20, BB:9b(10.00), RF:40(21)", 128) == 0) { -+ struct mwifiex_ds_auto_ds auto_ds = { -+ .auto_ds = DEEP_SLEEP_OFF, -+ }; -+ -+ mwifiex_dbg(priv->adapter, MSG, -+ "Bad HW revision detected, disabling deep sleep\n"); -+ -+ mwifiex_send_cmd(priv, HostCmd_CMD_802_11_PS_MODE_ENH, -+ DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, false); -+ } -+ -+ return 0; -+ } -+ - if (version_ext) { - version_ext->version_str_sel = ver_ext->version_str_sel; - memcpy(version_ext->version_str, ver_ext->version_str, --- -2.35.1 - -From bea9eeec098b4cd62d2fc9ab6612d97fce3ace56 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 13 Apr 2021 12:57:41 +0200 -Subject: [PATCH] mwifiex: Ignore BTCOEX events from the firmware - -The firmware of the pcie 88W8897 chip sends those events very -unreliably, which means we sometimes end up actually capping the window -size while bluetooth is disabled, artifically limiting wifi speeds even -though it's not needed. - -Since we can't fix the firmware, let's just ignore those events, it -seems that the Windows driver also doesn't change the rx/tx block ack -buffer sizes when bluetooth gets enabled or disabled, so this is -consistent with the Windows driver. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/sta_event.c | 4 +--- - 1 file changed, 1 insertion(+), 3 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_event.c b/drivers/net/wireless/marvell/mwifiex/sta_event.c -index 2b2e6e0166e1..e9e2d4b86acb 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_event.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_event.c -@@ -1061,9 +1061,7 @@ int mwifiex_process_sta_event(struct mwifiex_private *priv) - adapter->event_skb); - break; - case EVENT_BT_COEX_WLAN_PARA_CHANGE: -- dev_dbg(adapter->dev, "EVENT: BT coex wlan param update\n"); -- mwifiex_bt_coex_wlan_param_update_event(priv, -- adapter->event_skb); -+ dev_dbg(adapter->dev, "EVENT: ignoring BT coex wlan param update\n"); - break; - case EVENT_RXBA_SYNC: - dev_dbg(adapter->dev, "EVENT: RXBA_SYNC\n"); --- -2.35.1 - diff --git a/patches/5.16/0003-ath10k.patch b/patches/5.16/0003-ath10k.patch deleted file mode 100644 index 5bcb625c7..000000000 --- a/patches/5.16/0003-ath10k.patch +++ /dev/null @@ -1,121 +0,0 @@ -From ddd1f30f0a41ae8229d875d6f3d1ef8fb8eb59f2 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 27 Feb 2021 00:45:52 +0100 -Subject: [PATCH] ath10k: Add module parameters to override board files - -Some Surface devices, specifically the Surface Go and AMD version of the -Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better -with a different board file, as it seems that the firmeware included -upstream is buggy. - -As it is generally not a good idea to randomly overwrite files, let -alone doing so via packages, we add module parameters to override those -file names in the driver. This allows us to package/deploy the override -via a modprobe.d config. - -Signed-off-by: Maximilian Luz -Patchset: ath10k ---- - drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++ - 1 file changed, 58 insertions(+) - -diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c -index 5e3b4d10c1a9..2475eeab9c98 100644 ---- a/drivers/net/wireless/ath/ath10k/core.c -+++ b/drivers/net/wireless/ath/ath10k/core.c -@@ -35,6 +35,9 @@ static bool skip_otp; - static bool rawmode; - static bool fw_diag_log; - -+static char *override_board = ""; -+static char *override_board2 = ""; -+ - unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | - BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); - -@@ -47,6 +50,9 @@ module_param(rawmode, bool, 0644); - module_param(fw_diag_log, bool, 0644); - module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); - -+module_param(override_board, charp, 0644); -+module_param(override_board2, charp, 0644); -+ - MODULE_PARM_DESC(debug_mask, "Debugging mask"); - MODULE_PARM_DESC(uart_print, "Uart target debugging"); - MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); -@@ -55,6 +61,9 @@ MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); - MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); - MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); - -+MODULE_PARM_DESC(override_board, "Override for board.bin file"); -+MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); -+ - static const struct ath10k_hw_params ath10k_hw_params_list[] = { - { - .id = QCA988X_HW_2_0_VERSION, -@@ -843,6 +852,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) - return 0; - } - -+static const char *ath10k_override_board_fw_file(struct ath10k *ar, -+ const char *file) -+{ -+ if (strcmp(file, "board.bin") == 0) { -+ if (strcmp(override_board, "") == 0) -+ return file; -+ -+ if (strcmp(override_board, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", -+ override_board); -+ -+ return override_board; -+ } -+ -+ if (strcmp(file, "board-2.bin") == 0) { -+ if (strcmp(override_board2, "") == 0) -+ return file; -+ -+ if (strcmp(override_board2, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", -+ override_board2); -+ -+ return override_board2; -+ } -+ -+ return file; -+} -+ - static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - const char *dir, - const char *file) -@@ -857,6 +902,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - if (dir == NULL) - dir = "."; - -+ /* HACK: Override board.bin and board-2.bin files if specified. -+ * -+ * Some Surface devices perform better with a different board -+ * configuration. To this end, one would need to replace the board.bin -+ * file with the modified config and remove the board-2.bin file. -+ * Unfortunately, that's not a solution that we can easily package. So -+ * we add module options to perform these overrides here. -+ */ -+ -+ file = ath10k_override_board_fw_file(ar, file); -+ if (!file) -+ return ERR_PTR(-ENOENT); -+ - snprintf(filename, sizeof(filename), "%s/%s", dir, file); - ret = firmware_request_nowarn(&fw, filename, ar->dev); - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", --- -2.35.1 - diff --git a/patches/5.16/0004-ipts.patch b/patches/5.16/0004-ipts.patch deleted file mode 100644 index 77339c5e5..000000000 --- a/patches/5.16/0004-ipts.patch +++ /dev/null @@ -1,1503 +0,0 @@ -From ac17ec1460ca95f496502ddd9acfeef9cee9928e Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 30 Jul 2020 13:21:53 +0200 -Subject: [PATCH] misc: mei: Add missing IPTS device IDs - -Patchset: ipts ---- - drivers/misc/mei/hw-me-regs.h | 1 + - drivers/misc/mei/pci-me.c | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h -index 67bb6a25fd0a..d1cb94d3452e 100644 ---- a/drivers/misc/mei/hw-me-regs.h -+++ b/drivers/misc/mei/hw-me-regs.h -@@ -92,6 +92,7 @@ - #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ - - #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ -+#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ - #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ - - #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ -diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c -index 3a45aaf002ac..55b8ee30a03c 100644 ---- a/drivers/misc/mei/pci-me.c -+++ b/drivers/misc/mei/pci-me.c -@@ -96,6 +96,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { - {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, -+ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, --- -2.35.1 - -From d4747f244ed7907f667efeb00f4f83d8ad9dc72e Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 6 Aug 2020 11:20:41 +0200 -Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus - -Based on linux-surface/intel-precise-touch@3f362c - -Signed-off-by: Dorian Stoll -Patchset: ipts ---- - drivers/misc/Kconfig | 1 + - drivers/misc/Makefile | 1 + - drivers/misc/ipts/Kconfig | 17 ++ - drivers/misc/ipts/Makefile | 12 ++ - drivers/misc/ipts/context.h | 47 +++++ - drivers/misc/ipts/control.c | 113 +++++++++++ - drivers/misc/ipts/control.h | 24 +++ - drivers/misc/ipts/mei.c | 125 ++++++++++++ - drivers/misc/ipts/protocol.h | 347 ++++++++++++++++++++++++++++++++++ - drivers/misc/ipts/receiver.c | 224 ++++++++++++++++++++++ - drivers/misc/ipts/receiver.h | 16 ++ - drivers/misc/ipts/resources.c | 128 +++++++++++++ - drivers/misc/ipts/resources.h | 17 ++ - drivers/misc/ipts/uapi.c | 208 ++++++++++++++++++++ - drivers/misc/ipts/uapi.h | 47 +++++ - 15 files changed, 1327 insertions(+) - create mode 100644 drivers/misc/ipts/Kconfig - create mode 100644 drivers/misc/ipts/Makefile - create mode 100644 drivers/misc/ipts/context.h - create mode 100644 drivers/misc/ipts/control.c - create mode 100644 drivers/misc/ipts/control.h - create mode 100644 drivers/misc/ipts/mei.c - create mode 100644 drivers/misc/ipts/protocol.h - create mode 100644 drivers/misc/ipts/receiver.c - create mode 100644 drivers/misc/ipts/receiver.h - create mode 100644 drivers/misc/ipts/resources.c - create mode 100644 drivers/misc/ipts/resources.h - create mode 100644 drivers/misc/ipts/uapi.c - create mode 100644 drivers/misc/ipts/uapi.h - -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index 0f5a49fc7c9e..12b081bc875a 100644 ---- a/drivers/misc/Kconfig -+++ b/drivers/misc/Kconfig -@@ -487,4 +487,5 @@ source "drivers/misc/cardreader/Kconfig" - source "drivers/misc/habanalabs/Kconfig" - source "drivers/misc/uacce/Kconfig" - source "drivers/misc/pvpanic/Kconfig" -+source "drivers/misc/ipts/Kconfig" - endmenu -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index a086197af544..972cae33ba36 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -59,3 +59,4 @@ obj-$(CONFIG_UACCE) += uacce/ - obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o - obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o - obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o -+obj-$(CONFIG_MISC_IPTS) += ipts/ -diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig -new file mode 100644 -index 000000000000..83e2a930c396 ---- /dev/null -+++ b/drivers/misc/ipts/Kconfig -@@ -0,0 +1,17 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+ -+config MISC_IPTS -+ tristate "Intel Precise Touch & Stylus" -+ depends on INTEL_MEI -+ help -+ Say Y here if your system has a touchscreen using Intels -+ Precise Touch & Stylus (IPTS) technology. -+ -+ If unsure say N. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ipts. -+ -+ Building this driver alone will not give you a working touchscreen. -+ It only exposed a userspace API that can be used by a daemon to -+ receive and process data from the touchscreen hardware. -diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile -new file mode 100644 -index 000000000000..8f58b9adbc94 ---- /dev/null -+++ b/drivers/misc/ipts/Makefile -@@ -0,0 +1,12 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+# -+# Makefile for the IPTS touchscreen driver -+# -+ -+obj-$(CONFIG_MISC_IPTS) += ipts.o -+ipts-objs := control.o -+ipts-objs += mei.o -+ipts-objs += receiver.o -+ipts-objs += resources.o -+ipts-objs += uapi.o -+ -diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h -new file mode 100644 -index 000000000000..f4b06a2d3f72 ---- /dev/null -+++ b/drivers/misc/ipts/context.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTEXT_H_ -+#define _IPTS_CONTEXT_H_ -+ -+#include -+#include -+#include -+#include -+ -+#include "protocol.h" -+ -+enum ipts_host_status { -+ IPTS_HOST_STATUS_STARTING, -+ IPTS_HOST_STATUS_STARTED, -+ IPTS_HOST_STATUS_STOPPING, -+ IPTS_HOST_STATUS_STOPPED, -+}; -+ -+struct ipts_buffer_info { -+ u8 *address; -+ dma_addr_t dma_address; -+}; -+ -+struct ipts_context { -+ struct mei_cl_device *cldev; -+ struct device *dev; -+ -+ bool restart; -+ enum ipts_host_status status; -+ struct ipts_get_device_info_rsp device_info; -+ -+ struct ipts_buffer_info data[IPTS_BUFFERS]; -+ struct ipts_buffer_info doorbell; -+ -+ struct ipts_buffer_info feedback[IPTS_BUFFERS]; -+ struct ipts_buffer_info workqueue; -+ struct ipts_buffer_info host2me; -+}; -+ -+#endif /* _IPTS_CONTEXT_H_ */ -diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c -new file mode 100644 -index 000000000000..a1d1f97a13d7 ---- /dev/null -+++ b/drivers/misc/ipts/control.c -@@ -0,0 +1,113 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+#include "protocol.h" -+#include "resources.h" -+#include "uapi.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload, -+ size_t size) -+{ -+ int ret; -+ struct ipts_command cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_command)); -+ cmd.code = code; -+ -+ if (payload && size > 0) -+ memcpy(&cmd.payload, payload, size); -+ -+ ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size); -+ if (ret >= 0) -+ return 0; -+ -+ /* -+ * During shutdown the device might get pulled away from below our feet. -+ * Dont log an error in this case, because it will confuse people. -+ */ -+ if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING) -+ dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret); -+ -+ return ret; -+} -+ -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) -+{ -+ struct ipts_feedback_cmd cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); -+ cmd.buffer = buffer; -+ -+ return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, -+ sizeof(struct ipts_feedback_cmd)); -+} -+ -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value) -+{ -+ struct ipts_feedback_buffer *feedback; -+ -+ memset(ipts->host2me.address, 0, ipts->device_info.feedback_size); -+ feedback = (struct ipts_feedback_buffer *)ipts->host2me.address; -+ -+ feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; -+ feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; -+ feedback->buffer = IPTS_HOST2ME_BUFFER; -+ feedback->size = 2; -+ feedback->payload[0] = report; -+ feedback->payload[1] = value; -+ -+ return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER); -+} -+ -+int ipts_control_start(struct ipts_context *ipts) -+{ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Starting IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STARTING; -+ ipts->restart = false; -+ -+ ipts_uapi_link(ipts); -+ return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); -+} -+ -+int ipts_control_stop(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPING) -+ return -EBUSY; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Stopping IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STOPPING; -+ -+ ipts_uapi_unlink(); -+ ipts_resources_free(ipts); -+ -+ ret = ipts_control_send_feedback(ipts, 0); -+ if (ret == -ENODEV) -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ return ret; -+} -+ -+int ipts_control_restart(struct ipts_context *ipts) -+{ -+ if (ipts->restart) -+ return -EBUSY; -+ -+ ipts->restart = true; -+ return ipts_control_stop(ipts); -+} -diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h -new file mode 100644 -index 000000000000..2c44e9e0e99f ---- /dev/null -+++ b/drivers/misc/ipts/control.h -@@ -0,0 +1,24 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTROL_H_ -+#define _IPTS_CONTROL_H_ -+ -+#include -+ -+#include "context.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, -+ size_t size); -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value); -+int ipts_control_start(struct ipts_context *ipts); -+int ipts_control_restart(struct ipts_context *ipts); -+int ipts_control_stop(struct ipts_context *ipts); -+ -+#endif /* _IPTS_CONTROL_H_ */ -diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c -new file mode 100644 -index 000000000000..59ecf13e00d2 ---- /dev/null -+++ b/drivers/misc/ipts/mei.c -@@ -0,0 +1,125 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "receiver.h" -+#include "uapi.h" -+ -+static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev) -+{ -+ int ret; -+ -+ ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)); -+ if (!ret) -+ return 0; -+ -+ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); -+} -+ -+static int ipts_mei_probe(struct mei_cl_device *cldev, -+ const struct mei_cl_device_id *id) -+{ -+ int ret; -+ struct ipts_context *ipts; -+ -+ if (ipts_mei_set_dma_mask(cldev)) { -+ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n"); -+ return -EFAULT; -+ } -+ -+ ret = mei_cldev_enable(cldev); -+ if (ret) { -+ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); -+ return ret; -+ } -+ -+ ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); -+ if (!ipts) { -+ mei_cldev_disable(cldev); -+ return -ENOMEM; -+ } -+ -+ ipts->cldev = cldev; -+ ipts->dev = &cldev->dev; -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ mei_cldev_set_drvdata(cldev, ipts); -+ mei_cldev_register_rx_cb(cldev, ipts_receiver_callback); -+ -+ return ipts_control_start(ipts); -+} -+ -+static void ipts_mei_remove(struct mei_cl_device *cldev) -+{ -+ int i; -+ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); -+ -+ ipts_control_stop(ipts); -+ -+ for (i = 0; i < 20; i++) { -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ break; -+ -+ msleep(25); -+ } -+ -+ mei_cldev_disable(cldev); -+ kfree(ipts); -+} -+ -+static struct mei_cl_device_id ipts_mei_device_id_table[] = { -+ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, -+ {}, -+}; -+MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); -+ -+static struct mei_cl_driver ipts_mei_driver = { -+ .id_table = ipts_mei_device_id_table, -+ .name = "ipts", -+ .probe = ipts_mei_probe, -+ .remove = ipts_mei_remove, -+}; -+ -+static int __init ipts_mei_init(void) -+{ -+ int ret; -+ -+ ret = ipts_uapi_init(); -+ if (ret) -+ return ret; -+ -+ ret = mei_cldev_driver_register(&ipts_mei_driver); -+ if (ret) { -+ ipts_uapi_free(); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static void __exit ipts_mei_exit(void) -+{ -+ mei_cldev_driver_unregister(&ipts_mei_driver); -+ ipts_uapi_free(); -+} -+ -+MODULE_DESCRIPTION("IPTS touchscreen driver"); -+MODULE_AUTHOR("Dorian Stoll "); -+MODULE_LICENSE("GPL"); -+ -+module_init(ipts_mei_init); -+module_exit(ipts_mei_exit); -diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h -new file mode 100644 -index 000000000000..c3458904a94d ---- /dev/null -+++ b/drivers/misc/ipts/protocol.h -@@ -0,0 +1,347 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_PROTOCOL_H_ -+#define _IPTS_PROTOCOL_H_ -+ -+#include -+ -+/* -+ * The MEI client ID for IPTS functionality. -+ */ -+#define IPTS_MEI_UUID \ -+ UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, \ -+ 0x02, 0xae, 0x04) -+ -+/* -+ * Queries the device for vendor specific information. -+ * -+ * The command must not contain any payload. -+ * The response will contain struct ipts_get_device_info_rsp as payload. -+ */ -+#define IPTS_CMD_GET_DEVICE_INFO 0x00000001 -+#define IPTS_RSP_GET_DEVICE_INFO 0x80000001 -+ -+/* -+ * Sets the mode that IPTS will operate in. -+ * -+ * The command must contain struct ipts_set_mode_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MODE 0x00000002 -+#define IPTS_RSP_SET_MODE 0x80000002 -+ -+/* -+ * Configures the memory buffers that the ME will use -+ * for passing data to the host. -+ * -+ * The command must contain struct ipts_set_mem_window_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MEM_WINDOW 0x00000003 -+#define IPTS_RSP_SET_MEM_WINDOW 0x80000003 -+ -+/* -+ * Signals that the host is ready to receive data to the ME. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_READY_FOR_DATA 0x00000005 -+#define IPTS_RSP_READY_FOR_DATA 0x80000005 -+ -+/* -+ * Signals that a buffer can be refilled to the ME. -+ * -+ * The command must contain struct ipts_feedback_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_FEEDBACK 0x00000006 -+#define IPTS_RSP_FEEDBACK 0x80000006 -+ -+/* -+ * Resets the data flow from the ME to the hosts and -+ * clears the buffers that were set with SET_MEM_WINDOW. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007 -+#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007 -+ -+/* -+ * Instructs the ME to reset the touch sensor. -+ * -+ * The command must contain struct ipts_reset_sensor_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_RESET_SENSOR 0x0000000B -+#define IPTS_RSP_RESET_SENSOR 0x8000000B -+ -+/** -+ * enum ipts_status - Possible status codes returned by IPTS commands. -+ * @IPTS_STATUS_SUCCESS: Operation completed successfully. -+ * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. -+ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. -+ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. -+ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. -+ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. -+ * The host must wait for a response before sending another -+ * command of the same type. -+ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it -+ * has not been initialized yet, or the system is improperly -+ * configured. -+ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. -+ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. -+ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. -+ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. -+ * The host can ignore this error and attempt to continue. -+ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. -+ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. -+ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. -+ * @IPTS_STATUS_TIMEOUT: The operation timed out. -+ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. -+ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. -+ * Further progress is not possible. -+ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. -+ * The host can attempt to continue. -+ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. -+ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. -+ */ -+enum ipts_status { -+ IPTS_STATUS_SUCCESS = 0, -+ IPTS_STATUS_INVALID_PARAMS = 1, -+ IPTS_STATUS_ACCESS_DENIED = 2, -+ IPTS_STATUS_CMD_SIZE_ERROR = 3, -+ IPTS_STATUS_NOT_READY = 4, -+ IPTS_STATUS_REQUEST_OUTSTANDING = 5, -+ IPTS_STATUS_NO_SENSOR_FOUND = 6, -+ IPTS_STATUS_OUT_OF_MEMORY = 7, -+ IPTS_STATUS_INTERNAL_ERROR = 8, -+ IPTS_STATUS_SENSOR_DISABLED = 9, -+ IPTS_STATUS_COMPAT_CHECK_FAIL = 10, -+ IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, -+ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, -+ IPTS_STATUS_RESET_FAILED = 13, -+ IPTS_STATUS_TIMEOUT = 14, -+ IPTS_STATUS_TEST_MODE_FAIL = 15, -+ IPTS_STATUS_SENSOR_FAIL_FATAL = 16, -+ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, -+ IPTS_STATUS_INVALID_DEVICE_CAPS = 18, -+ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, -+}; -+ -+/* -+ * The amount of buffers that is used for IPTS -+ */ -+#define IPTS_BUFFERS 16 -+ -+/* -+ * The special buffer ID that is used for direct host2me feedback. -+ */ -+#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS -+ -+/** -+ * enum ipts_mode - Operation mode for IPTS hardware -+ * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. -+ * The data is received as a HID report with ID 64. -+ * @IPTS_MODE_MULTITOUCH: The "proper" operation mode for IPTS. It will return -+ * stylus data as well as capacitive heatmap touch data. -+ * This data needs to be processed in userspace. -+ */ -+enum ipts_mode { -+ IPTS_MODE_SINGLETOUCH = 0, -+ IPTS_MODE_MULTITOUCH = 1, -+}; -+ -+/** -+ * struct ipts_set_mode_cmd - Payload for the SET_MODE command. -+ * @mode: The mode that IPTS should operate in. -+ */ -+struct ipts_set_mode_cmd { -+ enum ipts_mode mode; -+ u8 reserved[12]; -+} __packed; -+ -+#define IPTS_WORKQUEUE_SIZE 8192 -+#define IPTS_WORKQUEUE_ITEM_SIZE 16 -+ -+/** -+ * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command. -+ * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses. -+ * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses. -+ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. -+ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. -+ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. -+ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. -+ * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses. -+ * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses. -+ * @host2me_addr_lower: Lower 32 bits of the host2me buffer address. -+ * @host2me_addr_upper: Upper 32 bits of the host2me buffer address. -+ * @workqueue_item_size: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) -+ * @workqueue_size: Magic value. (IPTS_WORKQUEUE_SIZE) -+ * -+ * The data buffers are buffers that get filled with touch data by the ME. -+ * The doorbell buffer is a u32 that gets incremented by the ME once a data -+ * buffer has been filled with new data. -+ * -+ * The other buffers are required for using GuC submission with binary -+ * firmware. Since support for GuC submission has been dropped from i915, -+ * they are not used anymore, but they need to be allocated and passed, -+ * otherwise the hardware will refuse to start. -+ */ -+struct ipts_set_mem_window_cmd { -+ u32 data_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 data_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 workqueue_addr_lower; -+ u32 workqueue_addr_upper; -+ u32 doorbell_addr_lower; -+ u32 doorbell_addr_upper; -+ u32 feedback_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 feedback_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 host2me_addr_lower; -+ u32 host2me_addr_upper; -+ u32 host2me_size; -+ u8 reserved1; -+ u8 workqueue_item_size; -+ u16 workqueue_size; -+ u8 reserved[32]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_cmd - Payload for the FEEDBACK command. -+ * @buffer: The buffer that the ME should refill. -+ */ -+struct ipts_feedback_cmd { -+ u32 buffer; -+ u8 reserved[12]; -+} __packed; -+ -+/** -+ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. -+ */ -+enum ipts_feedback_cmd_type { -+ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, -+ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, -+ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, -+}; -+ -+/** -+ * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains. -+ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. -+ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. -+ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. -+ */ -+enum ipts_feedback_data_type { -+ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, -+ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, -+ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, -+ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, -+ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, -+}; -+ -+/** -+ * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer. -+ * @cmd_type: A command that should be executed on the sensor. -+ * @size: The size of the payload to be written. -+ * @buffer: The ID of the buffer that contains this feedback data. -+ * @protocol: The protocol version of the EDS. -+ * @data_type: The type of payload that the buffer contains. -+ * @spi_offset: The offset at which to write the payload data. -+ * @payload: Payload for the feedback command, or 0 if no payload is sent. -+ */ -+struct ipts_feedback_buffer { -+ enum ipts_feedback_cmd_type cmd_type; -+ u32 size; -+ u32 buffer; -+ u32 protocol; -+ enum ipts_feedback_data_type data_type; -+ u32 spi_offset; -+ u8 reserved[40]; -+ u8 payload[]; -+} __packed; -+ -+/** -+ * enum ipts_reset_type - Possible ways of resetting the touch sensor -+ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. -+ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. -+ */ -+enum ipts_reset_type { -+ IPTS_RESET_TYPE_HARD = 0, -+ IPTS_RESET_TYPE_SOFT = 1, -+}; -+ -+/** -+ * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. -+ * @type: What type of reset should be performed. -+ */ -+struct ipts_reset_sensor_cmd { -+ enum ipts_reset_type type; -+ u8 reserved[4]; -+} __packed; -+ -+/** -+ * struct ipts_command - A message sent from the host to the ME. -+ * @code: The message code describing the command. (see IPTS_CMD_*) -+ * @payload: Payload for the command, or 0 if no payload is required. -+ */ -+struct ipts_command { -+ u32 code; -+ u8 payload[320]; -+} __packed; -+ -+/** -+ * struct ipts_device_info - Payload for the GET_DEVICE_INFO response. -+ * @vendor_id: Vendor ID of the touch sensor. -+ * @device_id: Device ID of the touch sensor. -+ * @hw_rev: Hardware revision of the touch sensor. -+ * @fw_rev: Firmware revision of the touch sensor. -+ * @data_size: Required size of one data buffer. -+ * @feedback_size: Required size of one feedback buffer. -+ * @mode: Current operation mode of IPTS. -+ * @max_contacts: The amount of concurrent touches supported by the sensor. -+ */ -+struct ipts_get_device_info_rsp { -+ u16 vendor_id; -+ u16 device_id; -+ u32 hw_rev; -+ u32 fw_rev; -+ u32 data_size; -+ u32 feedback_size; -+ enum ipts_mode mode; -+ u8 max_contacts; -+ u8 reserved[19]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_rsp - Payload for the FEEDBACK response. -+ * @buffer: The buffer that has received feedback. -+ */ -+struct ipts_feedback_rsp { -+ u32 buffer; -+} __packed; -+ -+/** -+ * struct ipts_response - A message sent from the ME to the host. -+ * @code: The message code describing the response. (see IPTS_RSP_*) -+ * @status: The status code returned by the command. -+ * @payload: Payload returned by the command. -+ */ -+struct ipts_response { -+ u32 code; -+ enum ipts_status status; -+ u8 payload[80]; -+} __packed; -+ -+#endif /* _IPTS_PROTOCOL_H_ */ -diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c -new file mode 100644 -index 000000000000..23dca13c2139 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.c -@@ -0,0 +1,224 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "resources.h" -+ -+/* -+ * Temporary parameter to guard gen7 multitouch mode. -+ * Remove once gen7 has stable iptsd support. -+ */ -+static bool gen7mt; -+module_param(gen7mt, bool, 0644); -+ -+static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_set_mode_cmd cmd; -+ -+ memcpy(&ipts->device_info, rsp->payload, -+ sizeof(struct ipts_get_device_info_rsp)); -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd)); -+ cmd.mode = IPTS_MODE_MULTITOUCH; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd, -+ sizeof(struct ipts_set_mode_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mode(struct ipts_context *ipts) -+{ -+ int i, ret; -+ struct ipts_set_mem_window_cmd cmd; -+ -+ ret = ipts_resources_alloc(ipts); -+ if (ret) { -+ dev_err(ipts->dev, "Failed to allocate resources\n"); -+ return ret; -+ } -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ cmd.data_buffer_addr_lower[i] = -+ lower_32_bits(ipts->data[i].dma_address); -+ -+ cmd.data_buffer_addr_upper[i] = -+ upper_32_bits(ipts->data[i].dma_address); -+ -+ cmd.feedback_buffer_addr_lower[i] = -+ lower_32_bits(ipts->feedback[i].dma_address); -+ -+ cmd.feedback_buffer_addr_upper[i] = -+ upper_32_bits(ipts->feedback[i].dma_address); -+ } -+ -+ cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); -+ cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); -+ -+ cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); -+ cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); -+ -+ cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); -+ cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); -+ -+ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; -+ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, -+ sizeof(struct ipts_set_mem_window_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ dev_info(ipts->dev, "Device %04hX:%04hX ready\n", -+ ipts->device_info.vendor_id, ipts->device_info.device_id); -+ ipts->status = IPTS_HOST_STATUS_STARTED; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); -+ if (ret) -+ return ret; -+ -+ if (!gen7mt) -+ return 0; -+ -+ return ipts_control_set_feature(ipts, 0x5, 0x1); -+} -+ -+static int ipts_receiver_handle_feedback(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_feedback_rsp feedback; -+ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPING) -+ return 0; -+ -+ memcpy(&feedback, rsp->payload, sizeof(feedback)); -+ -+ if (feedback.buffer < IPTS_BUFFERS - 1) -+ return ipts_control_send_feedback(ipts, feedback.buffer + 1); -+ -+ return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); -+} -+ -+static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) -+{ -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ if (ipts->restart) -+ return ipts_control_start(ipts); -+ -+ return 0; -+} -+ -+static bool ipts_receiver_sensor_was_reset(u32 status) -+{ -+ return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || -+ status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; -+} -+ -+static bool ipts_receiver_handle_error(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ bool error; -+ -+ switch (rsp->status) { -+ case IPTS_STATUS_SUCCESS: -+ case IPTS_STATUS_COMPAT_CHECK_FAIL: -+ error = false; -+ break; -+ case IPTS_STATUS_INVALID_PARAMS: -+ error = rsp->code != IPTS_RSP_FEEDBACK; -+ break; -+ case IPTS_STATUS_SENSOR_DISABLED: -+ error = ipts->status != IPTS_HOST_STATUS_STOPPING; -+ break; -+ default: -+ error = true; -+ break; -+ } -+ -+ if (!error) -+ return false; -+ -+ dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code, -+ rsp->status); -+ -+ if (ipts_receiver_sensor_was_reset(rsp->status)) { -+ dev_err(ipts->dev, "Sensor was reset\n"); -+ -+ if (ipts_control_restart(ipts)) -+ dev_err(ipts->dev, "Failed to restart IPTS\n"); -+ } -+ -+ return true; -+} -+ -+static void ipts_receiver_handle_response(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ int ret; -+ -+ if (ipts_receiver_handle_error(ipts, rsp)) -+ return; -+ -+ switch (rsp->code) { -+ case IPTS_RSP_GET_DEVICE_INFO: -+ ret = ipts_receiver_handle_get_device_info(ipts, rsp); -+ break; -+ case IPTS_RSP_SET_MODE: -+ ret = ipts_receiver_handle_set_mode(ipts); -+ break; -+ case IPTS_RSP_SET_MEM_WINDOW: -+ ret = ipts_receiver_handle_set_mem_window(ipts); -+ break; -+ case IPTS_RSP_FEEDBACK: -+ ret = ipts_receiver_handle_feedback(ipts, rsp); -+ break; -+ case IPTS_RSP_CLEAR_MEM_WINDOW: -+ ret = ipts_receiver_handle_clear_mem_window(ipts); -+ break; -+ default: -+ ret = 0; -+ break; -+ } -+ -+ if (!ret) -+ return; -+ -+ dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n", -+ rsp->code, ret); -+ -+ if (ipts_control_stop(ipts)) -+ dev_err(ipts->dev, "Failed to stop IPTS\n"); -+} -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev) -+{ -+ int ret; -+ struct ipts_response rsp; -+ struct ipts_context *ipts; -+ -+ ipts = mei_cldev_get_drvdata(cldev); -+ -+ ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response)); -+ if (ret <= 0) { -+ dev_err(ipts->dev, "Error while reading response: %d\n", ret); -+ return; -+ } -+ -+ ipts_receiver_handle_response(ipts, &rsp); -+} -diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h -new file mode 100644 -index 000000000000..7f075afa7ef8 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.h -@@ -0,0 +1,16 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RECEIVER_H_ -+#define _IPTS_RECEIVER_H_ -+ -+#include -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev); -+ -+#endif /* _IPTS_RECEIVER_H_ */ -diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c -new file mode 100644 -index 000000000000..8e3a2409e438 ---- /dev/null -+++ b/drivers/misc/ipts/resources.c -@@ -0,0 +1,128 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+ -+void ipts_resources_free(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, data_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ if (ipts->doorbell.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->doorbell.address, -+ ipts->doorbell.dma_address); -+ -+ ipts->doorbell.address = NULL; -+ ipts->doorbell.dma_address = 0; -+ } -+ -+ if (ipts->workqueue.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->workqueue.address, -+ ipts->workqueue.dma_address); -+ -+ ipts->workqueue.address = NULL; -+ ipts->workqueue.dma_address = 0; -+ } -+ -+ if (ipts->host2me.address) { -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ ipts->host2me.address, -+ ipts->host2me.dma_address); -+ -+ ipts->host2me.address = NULL; -+ ipts->host2me.dma_address = 0; -+ } -+} -+ -+int ipts_resources_alloc(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, data_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ ipts->doorbell.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->doorbell.dma_address, GFP_KERNEL); -+ -+ if (!ipts->doorbell.address) -+ goto release_resources; -+ -+ ipts->workqueue.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->workqueue.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ ipts->host2me.address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &ipts->host2me.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ return 0; -+ -+release_resources: -+ -+ ipts_resources_free(ipts); -+ return -ENOMEM; -+} -diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h -new file mode 100644 -index 000000000000..fdac0eee9156 ---- /dev/null -+++ b/drivers/misc/ipts/resources.h -@@ -0,0 +1,17 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RESOURCES_H_ -+#define _IPTS_RESOURCES_H_ -+ -+#include "context.h" -+ -+int ipts_resources_alloc(struct ipts_context *ipts); -+void ipts_resources_free(struct ipts_context *ipts); -+ -+#endif /* _IPTS_RESOURCES_H_ */ -diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c -new file mode 100644 -index 000000000000..598f0710ad64 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.c -@@ -0,0 +1,208 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "uapi.h" -+ -+struct ipts_uapi uapi; -+ -+static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count, -+ loff_t *offset) -+{ -+ int buffer; -+ int maxbytes; -+ struct ipts_context *ipts = uapi.ipts; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ maxbytes = ipts->device_info.data_size - *offset; -+ if (maxbytes <= 0 || count > maxbytes) -+ return -EINVAL; -+ -+ if (copy_to_user(buf, ipts->data[buffer].address + *offset, count)) -+ return -EFAULT; -+ -+ return count; -+} -+ -+static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ u8 ready = 0; -+ -+ if (ipts) -+ ready = ipts->status == IPTS_HOST_STATUS_STARTED; -+ -+ if (copy_to_user(buffer, &ready, sizeof(u8))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ struct ipts_device_info info; -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ info.vendor = ipts->device_info.vendor_id; -+ info.product = ipts->device_info.device_id; -+ info.version = ipts->device_info.fw_rev; -+ info.buffer_size = ipts->device_info.data_size; -+ info.max_contacts = ipts->device_info.max_contacts; -+ -+ if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts, -+ struct file *file) -+{ -+ int ret; -+ u32 buffer; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ ret = ipts_control_send_feedback(ipts, buffer); -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) -+{ -+ int ret; -+ struct ipts_reset_sensor_cmd cmd; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); -+ cmd.type = IPTS_RESET_TYPE_SOFT; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, -+ sizeof(struct ipts_reset_sensor_cmd)); -+ -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) -+{ -+ struct ipts_context *ipts = uapi.ipts; -+ -+ switch (cmd) { -+ case IPTS_IOCTL_GET_DEVICE_READY: -+ return ipts_uapi_ioctl_get_device_ready(ipts, arg); -+ case IPTS_IOCTL_GET_DEVICE_INFO: -+ return ipts_uapi_ioctl_get_device_info(ipts, arg); -+ case IPTS_IOCTL_GET_DOORBELL: -+ return ipts_uapi_ioctl_get_doorbell(ipts, arg); -+ case IPTS_IOCTL_SEND_FEEDBACK: -+ return ipts_uapi_ioctl_send_feedback(ipts, file); -+ case IPTS_IOCTL_SEND_RESET: -+ return ipts_uapi_ioctl_send_reset(ipts); -+ default: -+ return -ENOTTY; -+ } -+} -+ -+static const struct file_operations ipts_uapi_fops = { -+ .owner = THIS_MODULE, -+ .read = ipts_uapi_read, -+ .unlocked_ioctl = ipts_uapi_ioctl, -+#ifdef CONFIG_COMPAT -+ .compat_ioctl = ipts_uapi_ioctl, -+#endif -+}; -+ -+void ipts_uapi_link(struct ipts_context *ipts) -+{ -+ uapi.ipts = ipts; -+} -+ -+void ipts_uapi_unlink(void) -+{ -+ uapi.ipts = NULL; -+} -+ -+int ipts_uapi_init(void) -+{ -+ int i, major; -+ -+ alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts"); -+ uapi.class = class_create(THIS_MODULE, "ipts"); -+ -+ major = MAJOR(uapi.dev); -+ -+ cdev_init(&uapi.cdev, &ipts_uapi_fops); -+ uapi.cdev.owner = THIS_MODULE; -+ cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ device_create(uapi.class, NULL, MKDEV(major, i), NULL, -+ "ipts/%d", i); -+ } -+ -+ return 0; -+} -+ -+void ipts_uapi_free(void) -+{ -+ int i; -+ int major; -+ -+ major = MAJOR(uapi.dev); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) -+ device_destroy(uapi.class, MKDEV(major, i)); -+ -+ cdev_del(&uapi.cdev); -+ -+ unregister_chrdev_region(MKDEV(major, 0), MINORMASK); -+ class_destroy(uapi.class); -+} -diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h -new file mode 100644 -index 000000000000..53fb86a88f97 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_UAPI_H_ -+#define _IPTS_UAPI_H_ -+ -+#include -+ -+#include "context.h" -+ -+struct ipts_uapi { -+ dev_t dev; -+ struct class *class; -+ struct cdev cdev; -+ -+ struct ipts_context *ipts; -+}; -+ -+struct ipts_device_info { -+ __u16 vendor; -+ __u16 product; -+ __u32 version; -+ __u32 buffer_size; -+ __u8 max_contacts; -+ -+ /* For future expansion */ -+ __u8 reserved[19]; -+}; -+ -+#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8) -+#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info) -+#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) -+#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04) -+#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) -+ -+void ipts_uapi_link(struct ipts_context *ipts); -+void ipts_uapi_unlink(void); -+ -+int ipts_uapi_init(void); -+void ipts_uapi_free(void); -+ -+#endif /* _IPTS_UAPI_H_ */ --- -2.35.1 - diff --git a/patches/5.16/0005-surface-sam.patch b/patches/5.16/0005-surface-sam.patch deleted file mode 100644 index e42b590d0..000000000 --- a/patches/5.16/0005-surface-sam.patch +++ /dev/null @@ -1,1756 +0,0 @@ -From 4944516f841b7ad107622276a66cdefcc018d74e Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 2 Jun 2021 03:34:06 +0200 -Subject: [PATCH] platform/surface: aggregator: Make client device removal more - generic - -Currently, there are similar functions defined in the Aggregator -Registry and the controller core. - -Make client device removal more generic and export it. We can then use -this function later on to remove client devices from device hubs as well -as the controller and avoid re-defining similar things. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/bus.c | 24 ++++++++-------------- - drivers/platform/surface/aggregator/bus.h | 3 --- - drivers/platform/surface/aggregator/core.c | 3 ++- - include/linux/surface_aggregator/device.h | 9 ++++++++ - 4 files changed, 19 insertions(+), 20 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -index 0a40dd9c94ed..abbbb5b08b07 100644 ---- a/drivers/platform/surface/aggregator/bus.c -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -374,27 +374,19 @@ static int ssam_remove_device(struct device *dev, void *_data) - } - - /** -- * ssam_controller_remove_clients() - Remove SSAM client devices registered as -- * direct children under the given controller. -- * @ctrl: The controller to remove all direct clients for. -+ * ssam_remove_clients() - Remove SSAM client devices registered as direct -+ * children under the given parent device. -+ * @dev: The (parent) device to remove all direct clients for. - * -- * Remove all SSAM client devices registered as direct children under the -- * given controller. Note that this only accounts for direct children of the -- * controller device. This does not take care of any client devices where the -- * parent device has been manually set before calling ssam_device_add. Refer -- * to ssam_device_add()/ssam_device_remove() for more details on those cases. -- * -- * To avoid new devices being added in parallel to this call, the main -- * controller lock (not statelock) must be held during this (and if -- * necessary, any subsequent deinitialization) call. -+ * Remove all SSAM client devices registered as direct children under the given -+ * device. Note that this only accounts for direct children of the device. -+ * Refer to ssam_device_add()/ssam_device_remove() for more details. - */ --void ssam_controller_remove_clients(struct ssam_controller *ctrl) -+void ssam_remove_clients(struct device *dev) - { -- struct device *dev; -- -- dev = ssam_controller_device(ctrl); - device_for_each_child_reverse(dev, NULL, ssam_remove_device); - } -+EXPORT_SYMBOL_GPL(ssam_remove_clients); - - /** - * ssam_bus_register() - Register and set-up the SSAM client device bus. -diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h -index ed032c2cbdb2..6964ee84e79c 100644 ---- a/drivers/platform/surface/aggregator/bus.h -+++ b/drivers/platform/surface/aggregator/bus.h -@@ -12,14 +12,11 @@ - - #ifdef CONFIG_SURFACE_AGGREGATOR_BUS - --void ssam_controller_remove_clients(struct ssam_controller *ctrl); -- - int ssam_bus_register(void); - void ssam_bus_unregister(void); - - #else /* CONFIG_SURFACE_AGGREGATOR_BUS */ - --static inline void ssam_controller_remove_clients(struct ssam_controller *ctrl) {} - static inline int ssam_bus_register(void) { return 0; } - static inline void ssam_bus_unregister(void) {} - -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -index c61bbeeec2df..d384d36098c2 100644 ---- a/drivers/platform/surface/aggregator/core.c -+++ b/drivers/platform/surface/aggregator/core.c -@@ -22,6 +22,7 @@ - #include - - #include -+#include - - #include "bus.h" - #include "controller.h" -@@ -735,7 +736,7 @@ static void ssam_serial_hub_remove(struct serdev_device *serdev) - ssam_controller_lock(ctrl); - - /* Remove all client devices. */ -- ssam_controller_remove_clients(ctrl); -+ ssam_remove_clients(&serdev->dev); - - /* Act as if suspending to silence events. */ - status = ssam_ctrl_notif_display_off(ctrl); -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index f636c5310321..cc257097eb05 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -319,6 +319,15 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); - ssam_device_driver_unregister) - - -+/* -- Helpers for controller and hub devices. ------------------------------- */ -+ -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+void ssam_remove_clients(struct device *dev); -+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+static inline void ssam_remove_clients(struct device *dev) {} -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+ - /* -- Helpers for client-device requests. ----------------------------------- */ - - /** --- -2.35.1 - -From 40bb25852c66032b2e4f150dbb7bad51138a98cb Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 02:06:38 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Use generic client - removal function - -Use generic client removal function introduced in the previous commit -instead of defining our own one. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 24 ++++--------------- - 1 file changed, 5 insertions(+), 19 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index e70f4c63554e..f6c639342b9d 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -258,20 +258,6 @@ static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) - return 0; - } - --static int ssam_hub_remove_devices_fn(struct device *dev, void *data) --{ -- if (!is_ssam_device(dev)) -- return 0; -- -- ssam_device_remove(to_ssam_device(dev)); -- return 0; --} -- --static void ssam_hub_remove_devices(struct device *parent) --{ -- device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn); --} -- - static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, - struct fwnode_handle *node) - { -@@ -317,7 +303,7 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c - - return 0; - err: -- ssam_hub_remove_devices(parent); -+ ssam_remove_clients(parent); - return status; - } - -@@ -414,7 +400,7 @@ static void ssam_base_hub_update_workfn(struct work_struct *work) - if (hub->state == SSAM_BASE_HUB_CONNECTED) - status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); - else -- ssam_hub_remove_devices(&hub->sdev->dev); -+ ssam_remove_clients(&hub->sdev->dev); - - if (status) - dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); -@@ -496,7 +482,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - err: - ssam_notifier_unregister(sdev->ctrl, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); -- ssam_hub_remove_devices(&sdev->dev); -+ ssam_remove_clients(&sdev->dev); - return status; - } - -@@ -508,7 +494,7 @@ static void ssam_base_hub_remove(struct ssam_device *sdev) - - ssam_notifier_unregister(sdev->ctrl, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); -- ssam_hub_remove_devices(&sdev->dev); -+ ssam_remove_clients(&sdev->dev); - } - - static const struct ssam_device_id ssam_base_hub_match[] = { -@@ -625,7 +611,7 @@ static int ssam_platform_hub_remove(struct platform_device *pdev) - { - const struct software_node **nodes = platform_get_drvdata(pdev); - -- ssam_hub_remove_devices(&pdev->dev); -+ ssam_remove_clients(&pdev->dev); - set_secondary_fwnode(&pdev->dev, NULL); - software_node_unregister_node_group(nodes); - return 0; --- -2.35.1 - -From 754d1c2cf93560e57abbd00bc178957732917e42 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 02:07:33 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Rename device - registration function - -Rename the device registration function to better align names with the -newly introduced device removal function. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index f6c639342b9d..ce2bd88feeaa 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -283,8 +283,8 @@ static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ct - return status; - } - --static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl, -- struct fwnode_handle *node) -+static int ssam_hub_register_clients(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) - { - struct fwnode_handle *child; - int status; -@@ -398,7 +398,7 @@ static void ssam_base_hub_update_workfn(struct work_struct *work) - hub->state = state; - - if (hub->state == SSAM_BASE_HUB_CONNECTED) -- status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); -+ status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); - else - ssam_remove_clients(&hub->sdev->dev); - -@@ -597,7 +597,7 @@ static int ssam_platform_hub_probe(struct platform_device *pdev) - - set_secondary_fwnode(&pdev->dev, root); - -- status = ssam_hub_add_devices(&pdev->dev, ctrl, root); -+ status = ssam_hub_register_clients(&pdev->dev, ctrl, root); - if (status) { - set_secondary_fwnode(&pdev->dev, NULL); - software_node_unregister_node_group(nodes); --- -2.35.1 - -From b9f37f2da29ac9ec35f95b4fe3dbab6f6fd0e67f Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 00:24:47 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow devices to be marked as - hot-removed - -Some SSAM devices, notably the keyboard cover (keyboard and touchpad) on -the Surface Pro 8, can be hot-removed. When this occurs, communication -with the device may fail and time out. This timeout can unnecessarily -block and slow down device removal and even cause issues when the -devices are detached and re-attached quickly. Thus, communication should -generally be avoided once hot-removal is detected. - -While we already remove a device as soon as we detect its (hot-)removal, -the corresponding device driver may still attempt to communicate with -the device during teardown. This is especially critical as communication -failure may also extend to disabling of events, which is typically done -at that stage. - -Add a flag to allow marking devices as hot-removed. This can then be -used during client driver teardown to check if any communication -attempts should be avoided. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/bus.c | 3 ++ - include/linux/surface_aggregator/device.h | 48 +++++++++++++++++++++-- - 2 files changed, 48 insertions(+), 3 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -index abbbb5b08b07..2b003dcbfc4b 100644 ---- a/drivers/platform/surface/aggregator/bus.c -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -388,6 +388,9 @@ void ssam_remove_clients(struct device *dev) - } - EXPORT_SYMBOL_GPL(ssam_remove_clients); - -+ -+/* -- Bus registration. ----------------------------------------------------- */ -+ - /** - * ssam_bus_register() - Register and set-up the SSAM client device bus. - */ -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index cc257097eb05..491aa7e9f4bc 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -148,17 +148,30 @@ struct ssam_device_uid { - #define SSAM_SDEV(cat, tid, iid, fun) \ - SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) - -+/* -+ * enum ssam_device_flags - Flags for SSAM client devices. -+ * @SSAM_DEVICE_HOT_REMOVED_BIT: -+ * The device has been hot-removed. Further communication with it may time -+ * out and should be avoided. -+ */ -+enum ssam_device_flags { -+ SSAM_DEVICE_HOT_REMOVED_BIT = 0, -+}; -+ - /** - * struct ssam_device - SSAM client device. -- * @dev: Driver model representation of the device. -- * @ctrl: SSAM controller managing this device. -- * @uid: UID identifying the device. -+ * @dev: Driver model representation of the device. -+ * @ctrl: SSAM controller managing this device. -+ * @uid: UID identifying the device. -+ * @flags: Device state flags, see &enum ssam_device_flags. - */ - struct ssam_device { - struct device dev; - struct ssam_controller *ctrl; - - struct ssam_device_uid uid; -+ -+ unsigned long flags; - }; - - /** -@@ -240,6 +253,35 @@ struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, - int ssam_device_add(struct ssam_device *sdev); - void ssam_device_remove(struct ssam_device *sdev); - -+/** -+ * ssam_device_mark_hot_removed() - Mark the given device as hot-removed. -+ * @sdev: The device to mark as hot-removed. -+ * -+ * Mark the device as having been hot-removed. This signals drivers using the -+ * device that communication with the device should be avoided and may lead to -+ * timeouts. -+ */ -+static inline void ssam_device_mark_hot_removed(struct ssam_device *sdev) -+{ -+ dev_dbg(&sdev->dev, "marking device as hot-removed\n"); -+ set_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); -+} -+ -+/** -+ * ssam_device_is_hot_removed() - Check if the given device has been -+ * hot-removed. -+ * @sdev: The device to check. -+ * -+ * Checks if the given device has been marked as hot-removed. See -+ * ssam_device_mark_hot_removed() for more details. -+ * -+ * Return: Returns ``true`` if the device has been marked as hot-removed. -+ */ -+static inline bool ssam_device_is_hot_removed(struct ssam_device *sdev) -+{ -+ return test_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); -+} -+ - /** - * ssam_device_get() - Increment reference count of SSAM client device. - * @sdev: The device to increment the reference count of. --- -2.35.1 - -From 2b35e342b2a48321e2ca7eb9976eefeb0ad01f91 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 00:48:22 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow notifiers to avoid - communication on unregistering - -When SSAM client devices have been (physically) hot-removed, -communication attempts with those devices may fail and time out. This -can even extend to event notifiers, due to which timeouts may occur -during device removal, slowing down that process. - -Add a flag to the notifier unregister function that allows skipping -communication with the EC to prevent this. Furthermore, add wrappers for -registering and unregistering notifiers belonging to SSAM client devices -that automatically check if the device has been marked as hot-removed -and communication should be avoided. - -Note that non-SSAM client devices can generally not be hot-removed, so -also add a convenience wrapper for those, defaulting to allow -communication. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../driver-api/surface_aggregator/client.rst | 6 +- - .../platform/surface/aggregator/controller.c | 53 ++++++++++------ - include/linux/surface_aggregator/controller.h | 24 +++++++- - include/linux/surface_aggregator/device.h | 60 +++++++++++++++++++ - 4 files changed, 122 insertions(+), 21 deletions(-) - -diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst -index e519d374c378..27f95abdbe99 100644 ---- a/Documentation/driver-api/surface_aggregator/client.rst -+++ b/Documentation/driver-api/surface_aggregator/client.rst -@@ -17,6 +17,8 @@ - .. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` - .. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` - .. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` -+.. |ssam_device_notifier_register| replace:: :c:func:`ssam_device_notifier_register` -+.. |ssam_device_notifier_unregister| replace:: :c:func:`ssam_device_notifier_unregister` - .. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` - .. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` - -@@ -312,7 +314,9 @@ Handling Events - To receive events from the SAM EC, an event notifier must be registered for - the desired event via |ssam_notifier_register|. The notifier must be - unregistered via |ssam_notifier_unregister| once it is not required any --more. -+more. For |ssam_device| type clients, the |ssam_device_notifier_register| and -+|ssam_device_notifier_unregister| wrappers should be preferred as they properly -+handle hot-removal of client devices. - - Event notifiers are registered by providing (at minimum) a callback to call - in case an event has been received, the registry specifying how the event -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index b8c377b3f932..6de834b52b63 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2199,16 +2199,26 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, - } - - /** -- * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is -- * no longer in use and free the corresponding entry. -+ * ssam_nf_refcount_disable_free() - Disable event for reference count entry if -+ * it is no longer in use and free the corresponding entry. - * @ctrl: The controller to disable the event on. - * @entry: The reference count entry for the event to be disabled. - * @flags: The flags used for enabling the event on the EC. -+ * @ec: Flag specifying if the event should actually be disabled on the EC. - * -- * If the reference count equals zero, i.e. the event is no longer requested by -- * any client, the event will be disabled and the corresponding reference count -- * entry freed. The reference count entry must not be used any more after a -- * call to this function. -+ * If ``ec`` equals ``true`` and the reference count equals zero (i.e. the -+ * event is no longer requested by any client), the specified event will be -+ * disabled on the EC via the corresponding request. -+ * -+ * If ``ec`` equals ``false``, no request will be sent to the EC and the event -+ * can be considered in a detached state (i.e. no longer used but still -+ * enabled). Disabling an event via this method may be required for -+ * hot-removable devices, where event disable requests may time out after the -+ * device has been physically removed. -+ * -+ * In both cases, if the reference count equals zero, the corresponding -+ * reference count entry will be freed. The reference count entry must not be -+ * used any more after a call to this function. - * - * Also checks if the flags used for disabling the event match the flags used - * for enabling the event and warns if they do not (regardless of reference -@@ -2223,7 +2233,7 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, - * returns the status of the event-enable EC command. - */ - static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, -- struct ssam_nf_refcount_entry *entry, u8 flags) -+ struct ssam_nf_refcount_entry *entry, u8 flags, bool ec) - { - const struct ssam_event_registry reg = entry->key.reg; - const struct ssam_event_id id = entry->key.id; -@@ -2232,8 +2242,9 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - - lockdep_assert_held(&nf->lock); - -- ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -- reg.target_category, id.target_category, id.instance, entry->refcount); -+ ssam_dbg(ctrl, "%s event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ ec ? "disabling" : "detaching", reg.target_category, id.target_category, -+ id.instance, entry->refcount); - - if (entry->flags != flags) { - ssam_warn(ctrl, -@@ -2242,7 +2253,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - id.instance); - } - -- if (entry->refcount == 0) { -+ if (ec && entry->refcount == 0) { - status = ssam_ssh_event_disable(ctrl, reg, id, flags); - kfree(entry); - } -@@ -2322,20 +2333,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif - EXPORT_SYMBOL_GPL(ssam_notifier_register); - - /** -- * ssam_notifier_unregister() - Unregister an event notifier. -- * @ctrl: The controller the notifier has been registered on. -- * @n: The event notifier to unregister. -+ * __ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * @disable: Whether to disable the corresponding event on the EC. - * - * Unregister an event notifier. Decrement the usage counter of the associated - * SAM event if the notifier is not marked as an observer. If the usage counter -- * reaches zero, the event will be disabled. -+ * reaches zero and ``disable`` equals ``true``, the event will be disabled. -+ * -+ * Useful for hot-removable devices, where communication may fail once the -+ * device has been physically removed. In that case, specifying ``disable`` as -+ * ``false`` avoids communication with the EC. - * - * Return: Returns zero on success, %-ENOENT if the given notifier block has - * not been registered on the controller. If the given notifier block was the - * last one associated with its specific event, returns the status of the - * event-disable EC-command. - */ --int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) -+int __ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n, -+ bool disable) - { - u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); - struct ssam_nf_refcount_entry *entry; -@@ -2373,7 +2390,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - goto remove; - } - -- status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); -+ status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags, disable); - } - - remove: -@@ -2383,7 +2400,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - - return status; - } --EXPORT_SYMBOL_GPL(ssam_notifier_unregister); -+EXPORT_SYMBOL_GPL(__ssam_notifier_unregister); - - /** - * ssam_controller_event_enable() - Enable the specified event. -@@ -2477,7 +2494,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, - return -ENOENT; - } - -- status = ssam_nf_refcount_disable_free(ctrl, entry, flags); -+ status = ssam_nf_refcount_disable_free(ctrl, entry, flags, true); - - mutex_unlock(&nf->lock); - return status; -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index 74bfdffaf7b0..50a2b4926c06 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -835,8 +835,28 @@ struct ssam_event_notifier { - int ssam_notifier_register(struct ssam_controller *ctrl, - struct ssam_event_notifier *n); - --int ssam_notifier_unregister(struct ssam_controller *ctrl, -- struct ssam_event_notifier *n); -+int __ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n, bool disable); -+ -+/** -+ * ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+static inline int ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n) -+{ -+ return __ssam_notifier_unregister(ctrl, n, true); -+} - - int ssam_controller_event_enable(struct ssam_controller *ctrl, - struct ssam_event_registry reg, -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 491aa7e9f4bc..16816c34da3e 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -472,4 +472,64 @@ static inline void ssam_remove_clients(struct device *dev) {} - sdev->uid.instance, ret); \ - } - -+ -+/* -- Helpers for client-device notifiers. ---------------------------------- */ -+ -+/** -+ * ssam_device_notifier_register() - Register an event notifier for the -+ * specified client device. -+ * @sdev: The device the notifier should be registered on. -+ * @n: The event notifier to register. -+ * -+ * Register an event notifier. Increment the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the event is not -+ * marked as an observer and is currently not enabled, it will be enabled -+ * during this call. If the notifier is marked as an observer, no attempt will -+ * be made at enabling any event and no reference count will be modified. -+ * -+ * Notifiers marked as observers do not need to be associated with one specific -+ * event, i.e. as long as no event matching is performed, only the event target -+ * category needs to be set. -+ * -+ * Return: Returns zero on success, %-ENOSPC if there have already been -+ * %INT_MAX notifiers for the event ID/type associated with the notifier block -+ * registered, %-ENOMEM if the corresponding event entry could not be -+ * allocated, %-ENODEV if the device is marked as hot-removed. If this is the -+ * first time that a notifier block is registered for the specific associated -+ * event, returns the status of the event-enable EC-command. -+ */ -+static inline int ssam_device_notifier_register(struct ssam_device *sdev, -+ struct ssam_event_notifier *n) -+{ -+ if (ssam_device_is_hot_removed(sdev)) -+ return -ENODEV; -+ -+ return ssam_notifier_register(sdev->ctrl, n); -+} -+ -+/** -+ * ssam_device_notifier_unregister() - Unregister an event notifier for the -+ * specified client device. -+ * @sdev: The device the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. -+ * -+ * In case the device has been marked as hot-removed, the event will not be -+ * disabled on the EC, as in those cases any attempt at doing so may time out. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+static inline int ssam_device_notifier_unregister(struct ssam_device *sdev, -+ struct ssam_event_notifier *n) -+{ -+ return __ssam_notifier_unregister(sdev->ctrl, n, -+ !ssam_device_is_hot_removed(sdev)); -+} -+ - #endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ --- -2.35.1 - -From 07940e6739b837bcedc00f070913cfd0e468a52b Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 01:20:49 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Use client device - wrappers for notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index ce2bd88feeaa..9f630e890ff7 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -468,7 +468,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - - ssam_device_set_drvdata(sdev, hub); - -- status = ssam_notifier_register(sdev->ctrl, &hub->notif); -+ status = ssam_device_notifier_register(sdev, &hub->notif); - if (status) - return status; - -@@ -480,7 +480,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - return 0; - - err: -- ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_device_notifier_unregister(sdev, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); - return status; -@@ -492,7 +492,7 @@ static void ssam_base_hub_remove(struct ssam_device *sdev) - - sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); - -- ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_device_notifier_unregister(sdev, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); - } --- -2.35.1 - -From 4a9a391f435e91808ef66ddd80ae93d1fc7c3faa Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 28 Oct 2021 03:37:06 +0200 -Subject: [PATCH] power/supply: surface_charger: Use client device wrappers for - notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/power/supply/surface_charger.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -index a060c36c7766..59182d55742d 100644 ---- a/drivers/power/supply/surface_charger.c -+++ b/drivers/power/supply/surface_charger.c -@@ -216,7 +216,7 @@ static int spwr_ac_register(struct spwr_ac_device *ac) - if (IS_ERR(ac->psy)) - return PTR_ERR(ac->psy); - -- return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); -+ return ssam_device_notifier_register(ac->sdev, &ac->notif); - } - - -@@ -251,7 +251,7 @@ static void surface_ac_remove(struct ssam_device *sdev) - { - struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); - -- ssam_notifier_unregister(sdev->ctrl, &ac->notif); -+ ssam_device_notifier_unregister(sdev, &ac->notif); - } - - static const struct spwr_psy_properties spwr_psy_props_adp1 = { --- -2.35.1 - -From c9711270b40bc6ca568a04ba9be49d1a213b8696 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 28 Oct 2021 03:38:09 +0200 -Subject: [PATCH] power/supply: surface_battery: Use client device wrappers for - notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/power/supply/surface_battery.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -index 5ec2e6bb2465..540707882bb0 100644 ---- a/drivers/power/supply/surface_battery.c -+++ b/drivers/power/supply/surface_battery.c -@@ -802,7 +802,7 @@ static int spwr_battery_register(struct spwr_battery_device *bat) - if (IS_ERR(bat->psy)) - return PTR_ERR(bat->psy); - -- return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); -+ return ssam_device_notifier_register(bat->sdev, &bat->notif); - } - - -@@ -837,7 +837,7 @@ static void surface_battery_remove(struct ssam_device *sdev) - { - struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); - -- ssam_notifier_unregister(sdev->ctrl, &bat->notif); -+ ssam_device_notifier_unregister(sdev, &bat->notif); - cancel_delayed_work_sync(&bat->update_work); - } - --- -2.35.1 - -From a90b4e28e22b94ff362ba5efbcfca41ea3f5b64a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 01:33:02 +0200 -Subject: [PATCH] HID: surface-hid: Add support for hot-removal - -Add support for hot-removal of SSAM HID client devices. - -Once a device has been hot-removed, further communication with it should -be avoided as it may fail and time out. While the device will be removed -as soon as we detect hot-removal, communication may still occur during -teardown, especially when unregistering notifiers. - -While hot-removal is a surprise event that can happen any time, try to -avoid communication as much as possible once it has been detected to -prevent timeouts that can slow down device removal and cause issues, -e.g. when quickly re-attaching the device. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/hid/surface-hid/surface_hid_core.c | 38 +++++++++++++++++++++- - 1 file changed, 37 insertions(+), 1 deletion(-) - -diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c -index 5571e74abe91..d2e695e942b6 100644 ---- a/drivers/hid/surface-hid/surface_hid_core.c -+++ b/drivers/hid/surface-hid/surface_hid_core.c -@@ -19,12 +19,30 @@ - #include "surface_hid_core.h" - - -+/* -- Utility functions. ---------------------------------------------------- */ -+ -+static bool surface_hid_is_hot_removed(struct surface_hid_device *shid) -+{ -+ /* -+ * Non-ssam client devices, i.e. platform client devices, cannot be -+ * hot-removed. -+ */ -+ if (!is_ssam_device(shid->dev)) -+ return false; -+ -+ return ssam_device_is_hot_removed(to_ssam_device(shid->dev)); -+} -+ -+ - /* -- Device descriptor access. --------------------------------------------- */ - - static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) - { - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, - (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); - if (status) -@@ -61,6 +79,9 @@ static int surface_hid_load_device_attributes(struct surface_hid_device *shid) - { - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, - (u8 *)&shid->attrs, sizeof(shid->attrs)); - if (status) -@@ -88,9 +109,18 @@ static int surface_hid_start(struct hid_device *hid) - static void surface_hid_stop(struct hid_device *hid) - { - struct surface_hid_device *shid = hid->driver_data; -+ bool hot_removed; -+ -+ /* -+ * Communication may fail for devices that have been hot-removed. This -+ * also includes unregistration of HID events, so we need to check this -+ * here. Only if the device has not been marked as hot-removed, we can -+ * safely disable events. -+ */ -+ hot_removed = surface_hid_is_hot_removed(shid); - - /* Note: This call will log errors for us, so ignore them here. */ -- ssam_notifier_unregister(shid->ctrl, &shid->notif); -+ __ssam_notifier_unregister(shid->ctrl, &shid->notif, !hot_removed); - } - - static int surface_hid_open(struct hid_device *hid) -@@ -109,6 +139,9 @@ static int surface_hid_parse(struct hid_device *hid) - u8 *buf; - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - buf = kzalloc(len, GFP_KERNEL); - if (!buf) - return -ENOMEM; -@@ -126,6 +159,9 @@ static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportn - { - struct surface_hid_device *shid = hid->driver_data; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) - return shid->ops.output_report(shid, reportnum, buf, len); - --- -2.35.1 - -From 11166e327e1806dad6f54583b78c9b24fed14bf8 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 31 Oct 2021 12:34:08 +0100 -Subject: [PATCH] platform/surface: aggregator: Add comment for KIP subsystem - category - -The KIP subsystem (full name unknown, abbreviation has been obtained -through reverse engineering) handles detachable peripherals such as the -keyboard cover on the Surface Pro X and Surface Pro 8. - -It is currently not entirely clear what this subsystem entails, but at -the very least it provides event notifications for when the keyboard -cover on the Surface Pro X and Surface Pro 8 have been detached or -re-attached, as well as the state that the keyboard cover is currently -in (e.g. folded-back, folded laptop-like, closed, etc.). - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - include/linux/surface_aggregator/serial_hub.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h -index c3de43edcffa..d1efac85caf1 100644 ---- a/include/linux/surface_aggregator/serial_hub.h -+++ b/include/linux/surface_aggregator/serial_hub.h -@@ -306,7 +306,7 @@ enum ssam_ssh_tc { - SSAM_SSH_TC_LPC = 0x0b, - SSAM_SSH_TC_TCL = 0x0c, - SSAM_SSH_TC_SFL = 0x0d, -- SSAM_SSH_TC_KIP = 0x0e, -+ SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ - SSAM_SSH_TC_EXT = 0x0f, - SSAM_SSH_TC_BLD = 0x10, - SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ --- -2.35.1 - -From 73c08c3066e6c6f4bfe08575e64f8ca5477de72d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 10 Oct 2021 23:56:23 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add KIP device hub - -Add a Surface System Aggregator Module (SSAM) client device hub for -hot-removable devices managed via the KIP subsystem. - -The KIP subsystem (full name unknown, abbreviation has been obtained -through reverse engineering) is a subsystem that manages hot-removable -SSAM client devices. Specifically, it manages HID input devices -contained in the detachable keyboard cover of the Surface Pro 8 and -Surface Pro X. - -To properly handle detachable devices, we need to remove their kernel -representation when the physical device has been detached and (re-)add -and (re-)initialize said representation when the physical device has -been (re-)attached. Note that we need to hot-remove those devices, as -communication (especially during event notifier unregistration) may time -out when the physical device is no longer present, which would lead to -an unnecessary delay. This delay might become problematic when devices -are detached and re-attached quickly. - -The KIP subsystem handles a single group of devices (e.g. all devices -contained in the keyboard cover) and cannot handle devices individually. -Thus we model it as a client device hub, which removes all devices -contained under it once removal of the hub (e.g. keyboard cover) has -been detected and (re-)adds all devices once the physical hub device has -been (re-)attached. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 247 +++++++++++++++++- - 1 file changed, 245 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 9f630e890ff7..4838ce6519a6 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -514,6 +514,237 @@ static struct ssam_device_driver ssam_base_hub_driver = { - }; - - -+/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ -+ -+/* -+ * Some devices may need a bit of time to be fully usable after being -+ * (re-)connected. This delay has been determined via experimentation. -+ */ -+#define SSAM_KIP_UPDATE_CONNECT_DELAY msecs_to_jiffies(250) -+ -+#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c -+ -+enum ssam_kip_hub_state { -+ SSAM_KIP_HUB_UNINITIALIZED, -+ SSAM_KIP_HUB_CONNECTED, -+ SSAM_KIP_HUB_DISCONNECTED, -+}; -+ -+struct ssam_kip_hub { -+ struct ssam_device *sdev; -+ -+ enum ssam_kip_hub_state state; -+ struct delayed_work update_work; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_connection_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x2c, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_get_connection_state(struct ssam_kip_hub *hub, enum ssam_kip_hub_state *state) -+{ -+ int status; -+ u8 connected; -+ -+ status = ssam_retry(__ssam_kip_get_connection_state, hub->sdev->ctrl, &connected); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); -+ return status; -+ } -+ -+ *state = connected ? SSAM_KIP_HUB_CONNECTED : SSAM_KIP_HUB_DISCONNECTED; -+ return 0; -+} -+ -+static ssize_t ssam_kip_hub_state_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct ssam_kip_hub *hub = dev_get_drvdata(dev); -+ const char *state; -+ -+ switch (hub->state) { -+ case SSAM_KIP_HUB_UNINITIALIZED: -+ state = "uninitialized"; -+ break; -+ -+ case SSAM_KIP_HUB_CONNECTED: -+ state = "connected"; -+ break; -+ -+ case SSAM_KIP_HUB_DISCONNECTED: -+ state = "disconnected"; -+ break; -+ -+ default: -+ /* -+ * Any value not handled in the above cases is invalid and -+ * should never have been set. Thus this case should be -+ * impossible to reach. -+ */ -+ WARN(1, "invalid KIP hub state: %d\n", hub->state); -+ state = ""; -+ break; -+ } -+ -+ return sysfs_emit(buf, "%s\n", state); -+} -+ -+static struct device_attribute ssam_kip_hub_attr_state = -+ __ATTR(state, 0444, ssam_kip_hub_state_show, NULL); -+ -+static struct attribute *ssam_kip_hub_attrs[] = { -+ &ssam_kip_hub_attr_state.attr, -+ NULL, -+}; -+ -+static const struct attribute_group ssam_kip_hub_group = { -+ .attrs = ssam_kip_hub_attrs, -+}; -+ -+static int ssam_kip_hub_mark_hot_removed(struct device *dev, void *_data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (is_ssam_device(dev)) -+ ssam_device_mark_hot_removed(sdev); -+ -+ return 0; -+} -+ -+static void ssam_kip_hub_update_workfn(struct work_struct *work) -+{ -+ struct ssam_kip_hub *hub = container_of(work, struct ssam_kip_hub, update_work.work); -+ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -+ enum ssam_kip_hub_state state; -+ int status = 0; -+ -+ status = ssam_kip_get_connection_state(hub, &state); -+ if (status) -+ return; -+ -+ if (hub->state == state) -+ return; -+ hub->state = state; -+ -+ if (hub->state == SSAM_KIP_HUB_CONNECTED) -+ status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); -+ else -+ ssam_remove_clients(&hub->sdev->dev); -+ -+ if (status) -+ dev_err(&hub->sdev->dev, "failed to update KIP-hub devices: %d\n", status); -+} -+ -+static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_kip_hub *hub = container_of(nf, struct ssam_kip_hub, notif); -+ unsigned long delay; -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) { -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ /* Mark devices as hot-removed before we remove any */ -+ if (!event->data[0]) -+ device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_kip_hub_mark_hot_removed); -+ -+ /* -+ * Delay update when KIP devices are being connected to give devices/EC -+ * some time to set up. -+ */ -+ delay = event->data[0] ? SSAM_KIP_UPDATE_CONNECT_DELAY : 0; -+ -+ schedule_delayed_work(&hub->update_work, delay); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static int __maybe_unused ssam_kip_hub_resume(struct device *dev) -+{ -+ struct ssam_kip_hub *hub = dev_get_drvdata(dev); -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_kip_hub_pm_ops, NULL, ssam_kip_hub_resume); -+ -+static int ssam_kip_hub_probe(struct ssam_device *sdev) -+{ -+ struct ssam_kip_hub *hub; -+ int status; -+ -+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -+ if (!hub) -+ return -ENOMEM; -+ -+ hub->sdev = sdev; -+ hub->state = SSAM_KIP_HUB_UNINITIALIZED; -+ -+ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -+ hub->notif.base.fn = ssam_kip_hub_notif; -+ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ hub->notif.event.id.target_category = SSAM_SSH_TC_KIP, -+ hub->notif.event.id.instance = 0, -+ hub->notif.event.mask = SSAM_EVENT_MASK_TARGET; -+ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ INIT_DELAYED_WORK(&hub->update_work, ssam_kip_hub_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, hub); -+ -+ status = ssam_device_notifier_register(sdev, &hub->notif); -+ if (status) -+ return status; -+ -+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_kip_hub_group); -+ if (status) -+ goto err; -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+ -+err: -+ ssam_device_notifier_unregister(sdev, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); -+ ssam_remove_clients(&sdev->dev); -+ return status; -+} -+ -+static void ssam_kip_hub_remove(struct ssam_device *sdev) -+{ -+ struct ssam_kip_hub *hub = ssam_device_get_drvdata(sdev); -+ -+ sysfs_remove_group(&sdev->dev.kobj, &ssam_kip_hub_group); -+ -+ ssam_device_notifier_unregister(sdev, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); -+ ssam_remove_clients(&sdev->dev); -+} -+ -+static const struct ssam_device_id ssam_kip_hub_match[] = { -+ { SSAM_SDEV(KIP, 0x01, 0x00, 0x00) }, -+ { }, -+}; -+ -+static struct ssam_device_driver ssam_kip_hub_driver = { -+ .probe = ssam_kip_hub_probe, -+ .remove = ssam_kip_hub_remove, -+ .match_table = ssam_kip_hub_match, -+ .driver = { -+ .name = "surface_kip_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_kip_hub_pm_ops, -+ }, -+}; -+ -+ - /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ - - static const struct acpi_device_id ssam_platform_hub_match[] = { -@@ -636,18 +867,30 @@ static int __init ssam_device_hub_init(void) - - status = platform_driver_register(&ssam_platform_hub_driver); - if (status) -- return status; -+ goto err_platform; - - status = ssam_device_driver_register(&ssam_base_hub_driver); - if (status) -- platform_driver_unregister(&ssam_platform_hub_driver); -+ goto err_base; -+ -+ status = ssam_device_driver_register(&ssam_kip_hub_driver); -+ if (status) -+ goto err_kip; - -+ return 0; -+ -+err_kip: -+ ssam_device_driver_unregister(&ssam_base_hub_driver); -+err_base: -+ platform_driver_unregister(&ssam_platform_hub_driver); -+err_platform: - return status; - } - module_init(ssam_device_hub_init); - - static void __exit ssam_device_hub_exit(void) - { -+ ssam_device_driver_unregister(&ssam_kip_hub_driver); - ssam_device_driver_unregister(&ssam_base_hub_driver); - platform_driver_unregister(&ssam_platform_hub_driver); - } --- -2.35.1 - -From 8cddf47c6b5f66bbe9fab52cdfc5d9ddaa922ef2 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 22:33:03 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for - keyboard cover on Surface Pro 8 - -Add support for the detachable keyboard cover on the Surface Pro 8. - -The keyboard cover on the Surface Pro 8 is, unlike the keyboard covers -of earlier Surface Pro generations, handled via the Surface System -Aggregator Module (SSAM). The keyboard and touchpad (as well as other -HID input devices) of this cover are standard SSAM HID client devices -(just like keyboard and touchpad on e.g. the Surface Laptop 3 and 4), -however, some care needs to be taken as they can be physically detached -(similarly to the Surface Book 3). Specifically, the respective SSAM -client devices need to be removed when the keyboard cover has been -detached and (re-)initialized when the keyboard cover has been -(re-)attached. - -On the Surface Pro 8, detachment of the keyboard cover (and by extension -its devices) is managed via the KIP subsystem. Therefore, said devices -need to be registered under the KIP device hub, which in turn will -remove and re-create/re-initialize those devices as needed. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 37 ++++++++++++++++++- - 1 file changed, 36 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 4838ce6519a6..c0e29c0514df 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -47,6 +47,12 @@ static const struct software_node ssam_node_hub_base = { - .parent = &ssam_node_root, - }; - -+/* KIP device hub (connects keyboard cover devices on Surface Pro 8). */ -+static const struct software_node ssam_node_hub_kip = { -+ .name = "ssam:01:0e:01:00:00", -+ .parent = &ssam_node_root, -+}; -+ - /* AC adapter. */ - static const struct software_node ssam_node_bat_ac = { - .name = "ssam:01:02:01:01:01", -@@ -155,6 +161,30 @@ static const struct software_node ssam_node_hid_base_iid6 = { - .parent = &ssam_node_hub_base, - }; - -+/* HID keyboard (KIP hub). */ -+static const struct software_node ssam_node_hid_kip_keyboard = { -+ .name = "ssam:01:15:02:01:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID pen stash (KIP hub; pen taken / stashed away evens). */ -+static const struct software_node ssam_node_hid_kip_penstash = { -+ .name = "ssam:01:15:02:02:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID touchpad (KIP hub). */ -+static const struct software_node ssam_node_hid_kip_touchpad = { -+ .name = "ssam:01:15:02:03:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID device instance 5 (KIP hub, unknown HID device). */ -+static const struct software_node ssam_node_hid_kip_iid5 = { -+ .name = "ssam:01:15:02:05:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ - /* - * Devices for 5th- and 6th-generations models: - * - Surface Book 2, -@@ -230,10 +260,15 @@ static const struct software_node *ssam_node_group_sp7[] = { - - static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_root, -+ &ssam_node_hub_kip, - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -- /* TODO: Add support for keyboard cover. */ -+ &ssam_node_hid_kip_keyboard, -+ &ssam_node_hid_kip_penstash, -+ &ssam_node_hid_kip_touchpad, -+ &ssam_node_hid_kip_iid5, -+ /* TODO: Add support for tablet mode switch. */ - NULL, - }; - --- -2.35.1 - -From 8b16468073e469878a5af177d47ea980239cf685 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 03:19:20 +0200 -Subject: [PATCH] platform/surface: Add KIP tablet-mode switch - -Add a driver providing a tablet-mode switch input device for Surface -models using the KIP subsystem to manage detachable peripherals. - -The Surface Pro 8 has a detachable keyboard cover. Unlike the keyboard -covers of previous generation Surface Pro models, this cover is fully -handled by the Surface System Aggregator Module (SSAM). The SSAM KIP -subsystem (full name unknown, abbreviation found through reverse -engineering) provides notifications for mode changes of the cover. -Specifically, it allows us to know when the cover has been folded back, -detached, or whether it is in laptop mode. - -The driver introduced with this change captures these events and -translates them to standard SW_TABLET_MODE input events. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - MAINTAINERS | 6 + - drivers/platform/surface/Kconfig | 22 ++ - drivers/platform/surface/Makefile | 1 + - .../surface/surface_kip_tablet_switch.c | 245 ++++++++++++++++++ - 4 files changed, 274 insertions(+) - create mode 100644 drivers/platform/surface/surface_kip_tablet_switch.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index dd36acc87ce6..d69fdf5aadec 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -12678,6 +12678,12 @@ L: platform-driver-x86@vger.kernel.org - S: Maintained - F: drivers/platform/surface/surface_hotplug.c - -+MICROSOFT SURFACE KIP TABLET-MODE SWITCH -+M: Maximilian Luz -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/platform/surface/surface_kip_tablet_switch.c -+ - MICROSOFT SURFACE PLATFORM PROFILE DRIVER - M: Maximilian Luz - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 3105f651614f..3c0ee0cdaef5 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -152,6 +152,28 @@ config SURFACE_HOTPLUG - Select M or Y here, if you want to (fully) support hot-plugging of - dGPU devices on the Surface Book 2 and/or 3 during D3cold. - -+config SURFACE_KIP_TABLET_SWITCH -+ tristate "Surface KIP Tablet-Mode Switch Driver" -+ depends on SURFACE_AGGREGATOR -+ depends on SURFACE_AGGREGATOR_BUS -+ depends on INPUT -+ help -+ Provides a tablet-mode switch input device on Microsoft Surface models -+ using the KIP subsystem for detachable keyboards (e.g. keyboard -+ covers). -+ -+ The KIP subsystem is used on newer Surface generations to handle -+ detachable input peripherals, specifically the keyboard cover -+ (containing keyboard and touchpad) on the Surface Pro 8. This module -+ provides a driver to let user-space know when the device should be -+ considered in tablet-mode due to the keyboard cover being detached or -+ folded back (essentially signaling when the keyboard is not available -+ for input). It does so by creating a tablet-mode switch input device, -+ sending the standard SW_TABLET_MODE event on mode change. -+ -+ Select M or Y here, if you want to provide tablet-mode switch input -+ events on the Surface Pro 8. -+ - config SURFACE_PLATFORM_PROFILE - tristate "Surface Platform Profile Driver" - depends on SURFACE_AGGREGATOR_REGISTRY -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 32889482de55..6d9291c993c4 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -14,5 +14,6 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -+obj-$(CONFIG_SURFACE_KIP_TABLET_SWITCH) += surface_kip_tablet_switch.o - obj-$(CONFIG_SURFACE_PLATFORM_PROFILE) += surface_platform_profile.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o -diff --git a/drivers/platform/surface/surface_kip_tablet_switch.c b/drivers/platform/surface/surface_kip_tablet_switch.c -new file mode 100644 -index 000000000000..458470067579 ---- /dev/null -+++ b/drivers/platform/surface/surface_kip_tablet_switch.c -@@ -0,0 +1,245 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) tablet mode switch via KIP -+ * subsystem. -+ * -+ * Copyright (C) 2021 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#define SSAM_EVENT_KIP_CID_LID_STATE 0x1d -+ -+enum ssam_kip_lid_state { -+ SSAM_KIP_LID_STATE_DISCONNECTED = 0x01, -+ SSAM_KIP_LID_STATE_CLOSED = 0x02, -+ SSAM_KIP_LID_STATE_LAPTOP = 0x03, -+ SSAM_KIP_LID_STATE_FOLDED_CANVAS = 0x04, -+ SSAM_KIP_LID_STATE_FOLDED_BACK = 0x05, -+}; -+ -+struct ssam_kip_sw { -+ struct ssam_device *sdev; -+ -+ enum ssam_kip_lid_state state; -+ struct work_struct update_work; -+ struct input_dev *mode_switch; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_lid_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x1d, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_get_lid_state(struct ssam_kip_sw *sw, enum ssam_kip_lid_state *state) -+{ -+ int status; -+ u8 raw; -+ -+ status = ssam_retry(__ssam_kip_get_lid_state, sw->sdev->ctrl, &raw); -+ if (status < 0) { -+ dev_err(&sw->sdev->dev, "failed to query KIP lid state: %d\n", status); -+ return status; -+ } -+ -+ *state = raw; -+ return 0; -+} -+ -+static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct ssam_kip_sw *sw = dev_get_drvdata(dev); -+ const char *state; -+ -+ switch (sw->state) { -+ case SSAM_KIP_LID_STATE_DISCONNECTED: -+ state = "disconnected"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_CLOSED: -+ state = "closed"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_LAPTOP: -+ state = "laptop"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_FOLDED_CANVAS: -+ state = "folded-canvas"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_FOLDED_BACK: -+ state = "folded-back"; -+ break; -+ -+ default: -+ state = ""; -+ dev_warn(dev, "unknown KIP lid state: %d\n", sw->state); -+ break; -+ } -+ -+ return sysfs_emit(buf, "%s\n", state); -+} -+static DEVICE_ATTR_RO(state); -+ -+static struct attribute *ssam_kip_sw_attrs[] = { -+ &dev_attr_state.attr, -+ NULL, -+}; -+ -+static const struct attribute_group ssam_kip_sw_group = { -+ .attrs = ssam_kip_sw_attrs, -+}; -+ -+static void ssam_kip_sw_update_workfn(struct work_struct *work) -+{ -+ struct ssam_kip_sw *sw = container_of(work, struct ssam_kip_sw, update_work); -+ enum ssam_kip_lid_state state; -+ int tablet, status; -+ -+ status = ssam_kip_get_lid_state(sw, &state); -+ if (status) -+ return; -+ -+ if (sw->state == state) -+ return; -+ sw->state = state; -+ -+ /* Send SW_TABLET_MODE event. */ -+ tablet = state != SSAM_KIP_LID_STATE_LAPTOP; -+ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); -+ input_sync(sw->mode_switch); -+} -+ -+static u32 ssam_kip_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_kip_sw *sw = container_of(nf, struct ssam_kip_sw, notif); -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_LID_STATE) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) { -+ dev_err(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ schedule_work(&sw->update_work); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static int __maybe_unused ssam_kip_sw_resume(struct device *dev) -+{ -+ struct ssam_kip_sw *sw = dev_get_drvdata(dev); -+ -+ schedule_work(&sw->update_work); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_kip_sw_pm_ops, NULL, ssam_kip_sw_resume); -+ -+static int ssam_kip_sw_probe(struct ssam_device *sdev) -+{ -+ struct ssam_kip_sw *sw; -+ int tablet, status; -+ -+ sw = devm_kzalloc(&sdev->dev, sizeof(*sw), GFP_KERNEL); -+ if (!sw) -+ return -ENOMEM; -+ -+ sw->sdev = sdev; -+ INIT_WORK(&sw->update_work, ssam_kip_sw_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, sw); -+ -+ /* Get initial state. */ -+ status = ssam_kip_get_lid_state(sw, &sw->state); -+ if (status) -+ return status; -+ -+ /* Set up tablet mode switch. */ -+ sw->mode_switch = devm_input_allocate_device(&sdev->dev); -+ if (!sw->mode_switch) -+ return -ENOMEM; -+ -+ sw->mode_switch->name = "Microsoft Surface KIP Tablet Mode Switch"; -+ sw->mode_switch->phys = "ssam/01:0e:01:00:01/input0"; -+ sw->mode_switch->id.bustype = BUS_HOST; -+ sw->mode_switch->dev.parent = &sdev->dev; -+ -+ tablet = sw->state != SSAM_KIP_LID_STATE_LAPTOP; -+ input_set_capability(sw->mode_switch, EV_SW, SW_TABLET_MODE); -+ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); -+ -+ status = input_register_device(sw->mode_switch); -+ if (status) -+ return status; -+ -+ /* Set up notifier. */ -+ sw->notif.base.priority = 0; -+ sw->notif.base.fn = ssam_kip_sw_notif; -+ sw->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ sw->notif.event.id.target_category = SSAM_SSH_TC_KIP, -+ sw->notif.event.id.instance = 0, -+ sw->notif.event.mask = SSAM_EVENT_MASK_TARGET; -+ sw->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ status = ssam_device_notifier_register(sdev, &sw->notif); -+ if (status) -+ return status; -+ -+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_kip_sw_group); -+ if (status) -+ goto err; -+ -+ /* We might have missed events during setup, so check again. */ -+ schedule_work(&sw->update_work); -+ return 0; -+ -+err: -+ ssam_device_notifier_unregister(sdev, &sw->notif); -+ cancel_work_sync(&sw->update_work); -+ return status; -+} -+ -+static void ssam_kip_sw_remove(struct ssam_device *sdev) -+{ -+ struct ssam_kip_sw *sw = ssam_device_get_drvdata(sdev); -+ -+ sysfs_remove_group(&sdev->dev.kobj, &ssam_kip_sw_group); -+ -+ ssam_device_notifier_unregister(sdev, &sw->notif); -+ cancel_work_sync(&sw->update_work); -+} -+ -+static const struct ssam_device_id ssam_kip_sw_match[] = { -+ { SSAM_SDEV(KIP, 0x01, 0x00, 0x01) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, ssam_kip_sw_match); -+ -+static struct ssam_device_driver ssam_kip_sw_driver = { -+ .probe = ssam_kip_sw_probe, -+ .remove = ssam_kip_sw_remove, -+ .match_table = ssam_kip_sw_match, -+ .driver = { -+ .name = "surface_kip_tablet_mode_switch", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_kip_sw_pm_ops, -+ }, -+}; -+module_ssam_device_driver(ssam_kip_sw_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Tablet mode switch driver for Surface devices using KIP subsystem"); -+MODULE_LICENSE("GPL"); --- -2.35.1 - -From a3a81804d853465aba844be04a4d755efe919a44 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 22:33:03 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for tablet - mode switch on Surface Pro 8 - -Add a KIP subsystem tablet-mode switch device for the Surface Pro 8. -The respective driver for this device provides SW_TABLET_MODE input -events for user-space based on the state of the keyboard cover (e.g. -detached, folded-back, normal/laptop mode). - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 8 +++++++- - 1 file changed, 7 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index c0e29c0514df..eaf0054627a5 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -77,6 +77,12 @@ static const struct software_node ssam_node_tmp_pprof = { - .parent = &ssam_node_root, - }; - -+/* Tablet-mode switch via KIP subsystem. */ -+static const struct software_node ssam_node_kip_tablet_switch = { -+ .name = "ssam:01:0e:01:00:01", -+ .parent = &ssam_node_root, -+}; -+ - /* DTX / detachment-system device (Surface Book 3). */ - static const struct software_node ssam_node_bas_dtx = { - .name = "ssam:01:11:01:00:00", -@@ -264,11 +270,11 @@ static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -+ &ssam_node_kip_tablet_switch, - &ssam_node_hid_kip_keyboard, - &ssam_node_hid_kip_penstash, - &ssam_node_hid_kip_touchpad, - &ssam_node_hid_kip_iid5, -- /* TODO: Add support for tablet mode switch. */ - NULL, - }; - --- -2.35.1 - diff --git a/patches/5.16/0006-surface-sam-over-hid.patch b/patches/5.16/0006-surface-sam-over-hid.patch deleted file mode 100644 index 8384deaa3..000000000 --- a/patches/5.16/0006-surface-sam-over-hid.patch +++ /dev/null @@ -1,335 +0,0 @@ -From a586f227220ffc968942b304242ffb85a4ad738a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jul 2020 17:19:53 +0200 -Subject: [PATCH] i2c: acpi: Implement RawBytes read access - -Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C -device via a generic serial bus operation region and RawBytes read -access. On the Surface Book 1, this access is required to turn on (and -off) the discrete GPU. - -Multiple things are to note here: - -a) The RawBytes access is device/driver dependent. The ACPI - specification states: - - > Raw accesses assume that the writer has knowledge of the bus that - > the access is made over and the device that is being accessed. The - > protocol may only ensure that the buffer is transmitted to the - > appropriate driver, but the driver must be able to interpret the - > buffer to communicate to a register. - - Thus this implementation may likely not work on other devices - accessing I2C via the RawBytes accessor type. - -b) The MSHW0030 I2C device is an HID-over-I2C device which seems to - serve multiple functions: - - 1. It is the main access point for the legacy-type Surface Aggregator - Module (also referred to as SAM-over-HID, as opposed to the newer - SAM-over-SSH/UART). It has currently not been determined on how - support for the legacy SAM should be implemented. Likely via a - custom HID driver. - - 2. It seems to serve as the HID device for the Integrated Sensor Hub. - This might complicate matters with regards to implementing a - SAM-over-HID driver required by legacy SAM. - -In light of this, the simplest approach has been chosen for now. -However, it may make more sense regarding breakage and compatibility to -either provide functionality for replacing or enhancing the default -operation region handler via some additional API functions, or even to -completely blacklist MSHW0030 from the I2C core and provide a custom -driver for it. - -Replacing/enhancing the default operation region handler would, however, -either require some sort of secondary driver and access point for it, -from which the new API functions would be called and the new handler -(part) would be installed, or hard-coding them via some sort of -quirk-like interface into the I2C core. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam-over-hid ---- - drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ - 1 file changed, 35 insertions(+) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index 92c1cc07ed46..3b688cea8e00 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -609,6 +609,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, - return (ret == 1) ? 0 : -EIO; - } - -+static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, -+ u8 *data, u8 data_len) -+{ -+ struct i2c_msg msgs[1]; -+ int ret = AE_OK; -+ -+ msgs[0].addr = client->addr; -+ msgs[0].flags = client->flags; -+ msgs[0].len = data_len + 1; -+ msgs[0].buf = data; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ -+ if (ret < 0) { -+ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); -+ return ret; -+ } -+ -+ /* 1 transfer must have completed successfully */ -+ return (ret == 1) ? 0 : -EIO; -+} -+ - static acpi_status - i2c_acpi_space_handler(u32 function, acpi_physical_address command, - u32 bits, u64 *value64, -@@ -710,6 +732,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, - } - break; - -+ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: -+ if (action == ACPI_READ) { -+ dev_warn(&adapter->dev, -+ "protocol 0x%02x not supported for client 0x%02x\n", -+ accessor_type, client->addr); -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } else { -+ status = acpi_gsb_i2c_write_raw_bytes(client, -+ gsb->data, info->access_length); -+ } -+ break; -+ - default: - dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", - accessor_type, client->addr); --- -2.35.1 - -From 21496a214799137c6188bf65c90869af0d90ca21 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 13 Feb 2021 16:41:18 +0100 -Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch - -Add driver exposing the discrete GPU power-switch of the Microsoft -Surface Book 1 to user-space. - -On the Surface Book 1, the dGPU power is controlled via the Surface -System Aggregator Module (SAM). The specific SAM-over-HID command for -this is exposed via ACPI. This module provides a simple driver exposing -the ACPI call via a sysfs parameter to user-space, so that users can -easily power-on/-off the dGPU. - -Patchset: surface-sam-over-hid ---- - drivers/platform/surface/Kconfig | 7 + - drivers/platform/surface/Makefile | 1 + - .../surface/surfacebook1_dgpu_switch.c | 162 ++++++++++++++++++ - 3 files changed, 170 insertions(+) - create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 3c0ee0cdaef5..e5eedb85d471 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -104,6 +104,13 @@ config SURFACE_AGGREGATOR_REGISTRY - the respective client devices. Drivers for these devices still need to - be selected via the other options. - -+config SURFACE_BOOK1_DGPU_SWITCH -+ tristate "Surface Book 1 dGPU Switch Driver" -+ depends on SYSFS -+ help -+ This driver provides a sysfs switch to set the power-state of the -+ discrete GPU found on the Microsoft Surface Book 1. -+ - config SURFACE_DTX - tristate "Surface DTX (Detachment System) Driver" - depends on SURFACE_AGGREGATOR -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 6d9291c993c4..9eb3a7e6382c 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -11,6 +11,7 @@ obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o -+obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c -new file mode 100644 -index 000000000000..8b816ed8f35c ---- /dev/null -+++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c -@@ -0,0 +1,162 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+ -+#include -+#include -+#include -+#include -+ -+ -+#ifdef pr_fmt -+#undef pr_fmt -+#endif -+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ -+ -+ -+static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, -+ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); -+ -+#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" -+#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" -+#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" -+ -+ -+static int sb1_dgpu_sw_dsmcall(void) -+{ -+ union acpi_object *ret; -+ acpi_handle handle; -+ acpi_status status; -+ -+ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); -+ if (status) -+ return -EINVAL; -+ -+ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); -+ if (!ret) -+ return -EINVAL; -+ -+ ACPI_FREE(ret); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgon(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGON: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-on dGPU via HGON\n"); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgof(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGOF: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-off dGPU via HGOF\n"); -+ return 0; -+} -+ -+ -+static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ int status, value; -+ -+ status = kstrtoint(buf, 0, &value); -+ if (status < 0) -+ return status; -+ -+ if (value != 1) -+ return -EINVAL; -+ -+ status = sb1_dgpu_sw_dsmcall(); -+ -+ return status < 0 ? status : len; -+} -+ -+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ bool power; -+ int status; -+ -+ status = kstrtobool(buf, &power); -+ if (status < 0) -+ return status; -+ -+ if (power) -+ status = sb1_dgpu_sw_hgon(); -+ else -+ status = sb1_dgpu_sw_hgof(); -+ -+ return status < 0 ? status : len; -+} -+ -+static DEVICE_ATTR_WO(dgpu_dsmcall); -+static DEVICE_ATTR_WO(dgpu_power); -+ -+static struct attribute *sb1_dgpu_sw_attrs[] = { -+ &dev_attr_dgpu_dsmcall.attr, -+ &dev_attr_dgpu_power.attr, -+ NULL, -+}; -+ -+static const struct attribute_group sb1_dgpu_sw_attr_group = { -+ .attrs = sb1_dgpu_sw_attrs, -+}; -+ -+ -+static int sb1_dgpu_sw_probe(struct platform_device *pdev) -+{ -+ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+} -+ -+static int sb1_dgpu_sw_remove(struct platform_device *pdev) -+{ -+ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+ return 0; -+} -+ -+/* -+ * The dGPU power seems to be actually handled by MSHW0040. However, that is -+ * also the power-/volume-button device with a mainline driver. So let's use -+ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. -+ */ -+static const struct acpi_device_id sb1_dgpu_sw_match[] = { -+ { "MSHW0041", }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); -+ -+static struct platform_driver sb1_dgpu_sw = { -+ .probe = sb1_dgpu_sw_probe, -+ .remove = sb1_dgpu_sw_remove, -+ .driver = { -+ .name = "surfacebook1_dgpu_switch", -+ .acpi_match_table = sb1_dgpu_sw_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(sb1_dgpu_sw); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); -+MODULE_LICENSE("GPL"); --- -2.35.1 - diff --git a/patches/5.16/0007-surface-gpe.patch b/patches/5.16/0007-surface-gpe.patch deleted file mode 100644 index 94c0310b1..000000000 --- a/patches/5.16/0007-surface-gpe.patch +++ /dev/null @@ -1,37 +0,0 @@ -From e7a76b51f9f78f4887e07ba09c949e50db7afdb6 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 00:56:11 +0200 -Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 8 - -The new Surface Pro 8 uses GPEs for lid events as well. Add an entry for -that so that the lid can be used to wake the device. Note that this is a -device with a keyboard type cover, where this acts as the "lid". - -Signed-off-by: Maximilian Luz -Patchset: surface-gpe ---- - drivers/platform/surface/surface_gpe.c | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c -index c1775db29efb..ec66fde28e75 100644 ---- a/drivers/platform/surface/surface_gpe.c -+++ b/drivers/platform/surface/surface_gpe.c -@@ -99,6 +99,14 @@ static const struct dmi_system_id dmi_lid_device_table[] = { - }, - .driver_data = (void *)lid_device_props_l4D, - }, -+ { -+ .ident = "Surface Pro 8", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 8"), -+ }, -+ .driver_data = (void *)lid_device_props_l4B, -+ }, - { - .ident = "Surface Book 1", - .matches = { --- -2.35.1 - diff --git a/patches/5.16/0008-surface-button.patch b/patches/5.16/0008-surface-button.patch deleted file mode 100644 index aea8b5086..000000000 --- a/patches/5.16/0008-surface-button.patch +++ /dev/null @@ -1,557 +0,0 @@ -From b66d19a4dcc7a4173c82640c9a1be2532c819ea0 Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Tue, 5 Oct 2021 00:05:09 +1100 -Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices - -The power button on the AMD variant of the Surface Laptop uses the -same MSHW0040 device ID as the 5th and later generation of Surface -devices, however they report 0 for their OEM platform revision. As the -_DSM does not exist on the devices requiring special casing, check for -the existance of the _DSM to determine if soc_button_array should be -loaded. - -Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") -Co-developed-by: Maximilian Luz - -Signed-off-by: Sachi King -Patchset: surface-button ---- - drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- - 1 file changed, 8 insertions(+), 25 deletions(-) - -diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c -index cb6ec59a045d..4e8944f59def 100644 ---- a/drivers/input/misc/soc_button_array.c -+++ b/drivers/input/misc/soc_button_array.c -@@ -474,8 +474,8 @@ static const struct soc_device_data soc_device_INT33D3 = { - * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned - * devices use MSHW0040 for power and volume buttons, however the way they - * have to be addressed differs. Make sure that we only load this drivers -- * for the correct devices by checking the OEM Platform Revision provided by -- * the _DSM method. -+ * for the correct devices by checking if the OEM Platform Revision DSM call -+ * exists. - */ - #define MSHW0040_DSM_REVISION 0x01 - #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision -@@ -486,31 +486,14 @@ static const guid_t MSHW0040_DSM_UUID = - static int soc_device_check_MSHW0040(struct device *dev) - { - acpi_handle handle = ACPI_HANDLE(dev); -- union acpi_object *result; -- u64 oem_platform_rev = 0; // valid revisions are nonzero -- -- // get OEM platform revision -- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -- MSHW0040_DSM_REVISION, -- MSHW0040_DSM_GET_OMPR, NULL, -- ACPI_TYPE_INTEGER); -- -- if (result) { -- oem_platform_rev = result->integer.value; -- ACPI_FREE(result); -- } -- -- /* -- * If the revision is zero here, the _DSM evaluation has failed. This -- * indicates that we have a Pro 4 or Book 1 and this driver should not -- * be used. -- */ -- if (oem_platform_rev == 0) -- return -ENODEV; -+ bool exists; - -- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); -+ // check if OEM platform revision DSM call exists -+ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ BIT(MSHW0040_DSM_GET_OMPR)); - -- return 0; -+ return exists ? 0 : -ENODEV; - } - - /* --- -2.35.1 - -From bc80832636dd4fb85653d81cc51bb836aacfabc4 Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Tue, 5 Oct 2021 00:22:57 +1100 -Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd - variant - -The AMD variant of the Surface Laptop report 0 for their OEM platform -revision. The Surface devices that require the surfacepro3_button -driver do not have the _DSM that gets the OEM platform revision. If the -method does not exist, load surfacepro3_button. - -Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") -Co-developed-by: Maximilian Luz - -Signed-off-by: Sachi King -Patchset: surface-button ---- - drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- - 1 file changed, 6 insertions(+), 24 deletions(-) - -diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c -index 242fb690dcaf..30eea54dbb47 100644 ---- a/drivers/platform/surface/surfacepro3_button.c -+++ b/drivers/platform/surface/surfacepro3_button.c -@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) - /* - * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device - * ID (MSHW0040) for the power/volume buttons. Make sure this is the right -- * device by checking for the _DSM method and OEM Platform Revision. -+ * device by checking for the _DSM method and OEM Platform Revision DSM -+ * function. - * - * Returns true if the driver should bind to this device, i.e. the device is - * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. -@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) - static bool surface_button_check_MSHW0040(struct acpi_device *dev) - { - acpi_handle handle = dev->handle; -- union acpi_object *result; -- u64 oem_platform_rev = 0; // valid revisions are nonzero -- -- // get OEM platform revision -- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -- MSHW0040_DSM_REVISION, -- MSHW0040_DSM_GET_OMPR, -- NULL, ACPI_TYPE_INTEGER); -- -- /* -- * If evaluating the _DSM fails, the method is not present. This means -- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we -- * should use this driver. We use revision 0 indicating it is -- * unavailable. -- */ -- -- if (result) { -- oem_platform_rev = result->integer.value; -- ACPI_FREE(result); -- } -- -- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); - -- return oem_platform_rev == 0; -+ // make sure that OEM platform revision DSM call does not exist -+ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ BIT(MSHW0040_DSM_GET_OMPR)); - } - - --- -2.35.1 - -From 19e6f67c7b640286a1a8d451c9234b41bc7d118c Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Thu, 24 Feb 2022 12:02:40 +0100 -Subject: [PATCH] Input: soc_button_array - add support for Microsoft Surface 3 - (MSHW0028) buttons - -The drivers/platform/surface/surface3_button.c code is alsmost a 1:1 copy -of the soc_button_array code. - -The only big difference is that it binds to an i2c_client rather then to -a platform_device. The cause of this is the ACPI resources for the MSHW0028 -device containing a bogus I2cSerialBusV2 resource which causes the kernel -to instantiate an i2c_client for it instead of a platform_device. - -Add "MSHW0028" to the ignore_serial_bus_ids[] list in drivers/apci/scan.c, -so that a platform_device will be instantiated and add support for -the MSHW0028 HID to soc_button_array. - -This fully replaces surface3_button, which will be removed in a separate -commit (since it binds to the now no longer created i2c_client it no -longer does anyyhing after this commit). - -Note the MSHW0028 id is used by Microsoft to describe the tablet buttons on -both the Surface 3 and the Surface 3 Pro and the actual API/implementation -for the Surface 3 Pro is quite different. The changes in this commit should -not impact the separate surfacepro3_button driver: - -1. Because of the bogus I2cSerialBusV2 resource problem that driver binds - to the acpi_device itself, so instantiating a platform_device instead of - an i2c_client does not matter. - -2. The soc_button_array driver will not bind to the MSHW0028 device on - the Surface 3 Pro, because it has no GPIO resources. - -Signed-off-by: Hans de Goede -Reviewed-by: Maximilian Luz -Patchset: surface-button ---- - drivers/acpi/scan.c | 5 +++++ - drivers/input/misc/soc_button_array.c | 24 +++++++++++++++++++++++- - 2 files changed, 28 insertions(+), 1 deletion(-) - -diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c -index 25d9f04f1995..608e83f07b48 100644 ---- a/drivers/acpi/scan.c -+++ b/drivers/acpi/scan.c -@@ -1708,6 +1708,11 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) - {"BSG2150", }, - {"INT33FE", }, - {"INT3515", }, -+ /* -+ * Some ACPI devs contain SerialBus resources even though they are not -+ * attached to a serial bus at all. -+ */ -+ {"MSHW0028", }, - /* - * HIDs of device with an UartSerialBusV2 resource for which userspace - * expects a regular tty cdev to be created (instead of the in kernel -diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c -index 4e8944f59def..f044c731c6a9 100644 ---- a/drivers/input/misc/soc_button_array.c -+++ b/drivers/input/misc/soc_button_array.c -@@ -469,6 +469,27 @@ static const struct soc_device_data soc_device_INT33D3 = { - .button_info = soc_button_INT33D3, - }; - -+/* -+ * Button info for Microsoft Surface 3 (non pro), this is indentical to -+ * the PNP0C40 info except that the home button is active-high. -+ * -+ * The Surface 3 Pro also has a MSHW0028 ACPI device, but that uses a custom -+ * version of the drivers/platform/x86/intel/hid.c 5 button array ACPI API -+ * instead. A check() callback is not necessary though as the Surface 3 Pro -+ * MSHW0028 ACPI device's resource table does not contain any GPIOs. -+ */ -+static const struct soc_button_info soc_button_MSHW0028[] = { -+ { "power", 0, EV_KEY, KEY_POWER, false, true, true }, -+ { "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false }, -+ { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true }, -+ { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true }, -+ { } -+}; -+ -+static const struct soc_device_data soc_device_MSHW0028 = { -+ .button_info = soc_button_MSHW0028, -+}; -+ - /* - * Special device check for Surface Book 2 and Surface Pro (2017). - * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned -@@ -518,7 +539,8 @@ static const struct acpi_device_id soc_button_acpi_match[] = { - { "ID9001", (unsigned long)&soc_device_INT33D3 }, - { "ACPI0011", 0 }, - -- /* Microsoft Surface Devices (5th and 6th generation) */ -+ /* Microsoft Surface Devices (3th, 5th and 6th generation) */ -+ { "MSHW0028", (unsigned long)&soc_device_MSHW0028 }, - { "MSHW0040", (unsigned long)&soc_device_MSHW0040 }, - - { } --- -2.35.1 - -From 0e894d02855d203d67b600a1c23eceee939f43f7 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Thu, 24 Feb 2022 12:02:41 +0100 -Subject: [PATCH] platform/surface: Remove Surface 3 Button driver - -The Surface 3 buttons are now handled by the generic soc_button_array -driver. As part of adding support to soc_button_array the ACPI code -now instantiates a platform_device rather then an i2c_client so there -no longer is an i2c_client for this driver to bind to. - -Signed-off-by: Hans de Goede -Reviewed-by: Maximilian Luz -Patchset: surface-button ---- - drivers/platform/surface/Kconfig | 6 - - drivers/platform/surface/Makefile | 1 - - drivers/platform/surface/surface3_button.c | 247 --------------------- - 3 files changed, 254 deletions(-) - delete mode 100644 drivers/platform/surface/surface3_button.c - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index e5eedb85d471..d13cf8d63cc3 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -28,12 +28,6 @@ config SURFACE3_WMI - To compile this driver as a module, choose M here: the module will - be called surface3-wmi. - --config SURFACE_3_BUTTON -- tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet" -- depends on KEYBOARD_GPIO && I2C -- help -- This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet. -- - config SURFACE_3_POWER_OPREGION - tristate "Surface 3 battery platform operation region support" - depends on I2C -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 9eb3a7e6382c..e4791b47f561 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -5,7 +5,6 @@ - # - - obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o --obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o - obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ -diff --git a/drivers/platform/surface/surface3_button.c b/drivers/platform/surface/surface3_button.c -deleted file mode 100644 -index 48d77e7aae76..000000000000 ---- a/drivers/platform/surface/surface3_button.c -+++ /dev/null -@@ -1,247 +0,0 @@ --// SPDX-License-Identifier: GPL-2.0-only --/* -- * Supports for the button array on the Surface tablets. -- * -- * (C) Copyright 2016 Red Hat, Inc -- * -- * Based on soc_button_array.c: -- * -- * {C} Copyright 2014 Intel Corporation -- */ -- --#include --#include --#include --#include --#include --#include --#include --#include --#include --#include --#include -- -- --#define SURFACE_BUTTON_OBJ_NAME "TEV2" --#define MAX_NBUTTONS 4 -- --/* -- * Some of the buttons like volume up/down are auto repeat, while others -- * are not. To support both, we register two platform devices, and put -- * buttons into them based on whether the key should be auto repeat. -- */ --#define BUTTON_TYPES 2 -- --/* -- * Power button, Home button, Volume buttons support is supposed to -- * be covered by drivers/input/misc/soc_button_array.c, which is implemented -- * according to "Windows ACPI Design Guide for SoC Platforms". -- * However surface 3 seems not to obey the specs, instead it uses -- * device TEV2(MSHW0028) for declaring the GPIOs. The gpios are also slightly -- * different in which the Home button is active high. -- * Compared to surfacepro3_button.c which also handles MSHW0028, the Surface 3 -- * is a reduce platform and thus uses GPIOs, not ACPI events. -- * We choose an I2C driver here because we need to access the resources -- * declared under the device node, while surfacepro3_button.c only needs -- * the ACPI companion node. -- */ --static const struct acpi_device_id surface3_acpi_match[] = { -- { "MSHW0028", 0 }, -- { } --}; --MODULE_DEVICE_TABLE(acpi, surface3_acpi_match); -- --struct surface3_button_info { -- const char *name; -- int acpi_index; -- unsigned int event_type; -- unsigned int event_code; -- bool autorepeat; -- bool wakeup; -- bool active_low; --}; -- --struct surface3_button_data { -- struct platform_device *children[BUTTON_TYPES]; --}; -- --/* -- * Get the Nth GPIO number from the ACPI object. -- */ --static int surface3_button_lookup_gpio(struct device *dev, int acpi_index) --{ -- struct gpio_desc *desc; -- int gpio; -- -- desc = gpiod_get_index(dev, NULL, acpi_index, GPIOD_ASIS); -- if (IS_ERR(desc)) -- return PTR_ERR(desc); -- -- gpio = desc_to_gpio(desc); -- -- gpiod_put(desc); -- -- return gpio; --} -- --static struct platform_device * --surface3_button_device_create(struct i2c_client *client, -- const struct surface3_button_info *button_info, -- bool autorepeat) --{ -- const struct surface3_button_info *info; -- struct platform_device *pd; -- struct gpio_keys_button *gpio_keys; -- struct gpio_keys_platform_data *gpio_keys_pdata; -- int n_buttons = 0; -- int gpio; -- int error; -- -- gpio_keys_pdata = devm_kzalloc(&client->dev, -- sizeof(*gpio_keys_pdata) + -- sizeof(*gpio_keys) * MAX_NBUTTONS, -- GFP_KERNEL); -- if (!gpio_keys_pdata) -- return ERR_PTR(-ENOMEM); -- -- gpio_keys = (void *)(gpio_keys_pdata + 1); -- -- for (info = button_info; info->name; info++) { -- if (info->autorepeat != autorepeat) -- continue; -- -- gpio = surface3_button_lookup_gpio(&client->dev, -- info->acpi_index); -- if (!gpio_is_valid(gpio)) -- continue; -- -- gpio_keys[n_buttons].type = info->event_type; -- gpio_keys[n_buttons].code = info->event_code; -- gpio_keys[n_buttons].gpio = gpio; -- gpio_keys[n_buttons].active_low = info->active_low; -- gpio_keys[n_buttons].desc = info->name; -- gpio_keys[n_buttons].wakeup = info->wakeup; -- n_buttons++; -- } -- -- if (n_buttons == 0) { -- error = -ENODEV; -- goto err_free_mem; -- } -- -- gpio_keys_pdata->buttons = gpio_keys; -- gpio_keys_pdata->nbuttons = n_buttons; -- gpio_keys_pdata->rep = autorepeat; -- -- pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO); -- if (!pd) { -- error = -ENOMEM; -- goto err_free_mem; -- } -- -- error = platform_device_add_data(pd, gpio_keys_pdata, -- sizeof(*gpio_keys_pdata)); -- if (error) -- goto err_free_pdev; -- -- error = platform_device_add(pd); -- if (error) -- goto err_free_pdev; -- -- return pd; -- --err_free_pdev: -- platform_device_put(pd); --err_free_mem: -- devm_kfree(&client->dev, gpio_keys_pdata); -- return ERR_PTR(error); --} -- --static int surface3_button_remove(struct i2c_client *client) --{ -- struct surface3_button_data *priv = i2c_get_clientdata(client); -- -- int i; -- -- for (i = 0; i < BUTTON_TYPES; i++) -- if (priv->children[i]) -- platform_device_unregister(priv->children[i]); -- -- return 0; --} -- --static struct surface3_button_info surface3_button_surface3[] = { -- { "power", 0, EV_KEY, KEY_POWER, false, true, true }, -- { "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false }, -- { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true }, -- { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true }, -- { } --}; -- --static int surface3_button_probe(struct i2c_client *client, -- const struct i2c_device_id *id) --{ -- struct device *dev = &client->dev; -- struct surface3_button_data *priv; -- struct platform_device *pd; -- int i; -- int error; -- -- if (strncmp(acpi_device_bid(ACPI_COMPANION(&client->dev)), -- SURFACE_BUTTON_OBJ_NAME, -- strlen(SURFACE_BUTTON_OBJ_NAME))) -- return -ENODEV; -- -- error = gpiod_count(dev, NULL); -- if (error < 0) { -- dev_dbg(dev, "no GPIO attached, ignoring...\n"); -- return error; -- } -- -- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); -- if (!priv) -- return -ENOMEM; -- -- i2c_set_clientdata(client, priv); -- -- for (i = 0; i < BUTTON_TYPES; i++) { -- pd = surface3_button_device_create(client, -- surface3_button_surface3, -- i == 0); -- if (IS_ERR(pd)) { -- error = PTR_ERR(pd); -- if (error != -ENODEV) { -- surface3_button_remove(client); -- return error; -- } -- continue; -- } -- -- priv->children[i] = pd; -- } -- -- if (!priv->children[0] && !priv->children[1]) -- return -ENODEV; -- -- return 0; --} -- --static const struct i2c_device_id surface3_id[] = { -- { } --}; --MODULE_DEVICE_TABLE(i2c, surface3_id); -- --static struct i2c_driver surface3_driver = { -- .probe = surface3_button_probe, -- .remove = surface3_button_remove, -- .id_table = surface3_id, -- .driver = { -- .name = "surface3", -- .acpi_match_table = ACPI_PTR(surface3_acpi_match), -- }, --}; --module_i2c_driver(surface3_driver); -- --MODULE_AUTHOR("Benjamin Tissoires "); --MODULE_DESCRIPTION("surface3 button array driver"); --MODULE_LICENSE("GPL v2"); --- -2.35.1 - diff --git a/patches/5.16/0009-surface-typecover.patch b/patches/5.16/0009-surface-typecover.patch deleted file mode 100644 index 8c6840114..000000000 --- a/patches/5.16/0009-surface-typecover.patch +++ /dev/null @@ -1,233 +0,0 @@ -From 6810c61094611fdb7fabfdc62ac51f207a923de9 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 5 Nov 2020 13:09:45 +0100 -Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when - suspending - -The Type Cover for Microsoft Surface devices supports a special usb -control request to disable or enable the built-in keyboard backlight. -On Windows, this request happens when putting the device into suspend or -resuming it, without it the backlight of the Type Cover will remain -enabled for some time even though the computer is suspended, which looks -weird to the user. - -So add support for this special usb control request to hid-multitouch, -which is the driver that's handling the Type Cover. - -The reason we have to use a pm_notifier for this instead of the usual -suspend/resume methods is that those won't get called in case the usb -device is already autosuspended. - -Also, if the device is autosuspended, we have to briefly autoresume it -in order to send the request. Doing that should be fine, the usb-core -driver does something similar during suspend inside choose_wakeup(). - -To make sure we don't send that request to every device but only to -devices which support it, add a new quirk -MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk -is only enabled for the usb id of the Surface Pro 2017 Type Cover, which -is where I confirmed that it's working. - -Patchset: surface-typecover ---- - drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- - 1 file changed, 98 insertions(+), 2 deletions(-) - -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index 082376a6cb3d..cfc2e684a22c 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -34,7 +34,10 @@ - #include - #include - #include -+#include - #include -+#include -+#include - #include - #include - #include -@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); - MODULE_LICENSE("GPL"); - - #include "hid-ids.h" -+#include "usbhid/usbhid.h" - - /* quirks to control the device */ - #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) -@@ -71,12 +75,15 @@ MODULE_LICENSE("GPL"); - #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) - #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) - #define MT_QUIRK_DISABLE_WAKEUP BIT(21) -+#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 - - #define MT_BUTTONTYPE_CLICKPAD 0 - -+#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+ - enum latency_mode { - HID_LATENCY_NORMAL = 0, - HID_LATENCY_HIGH = 1, -@@ -168,6 +175,8 @@ struct mt_device { - - struct list_head applications; - struct list_head reports; -+ -+ struct notifier_block pm_notifier; - }; - - static void mt_post_parse_default_settings(struct mt_device *td, -@@ -211,6 +220,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); - #define MT_CLS_GOOGLE 0x0111 - #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 - #define MT_CLS_SMART_TECH 0x0113 -+#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 - - #define MT_DEFAULT_MAXCONTACT 10 - #define MT_MAX_MAXCONTACT 250 -@@ -386,6 +396,16 @@ static const struct mt_class mt_classes[] = { - MT_QUIRK_CONTACT_CNT_ACCURATE | - MT_QUIRK_SEPARATE_APP_REPORT, - }, -+ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_ALWAYS_VALID | -+ MT_QUIRK_IGNORE_DUPLICATES | -+ MT_QUIRK_HOVERING | -+ MT_QUIRK_CONTACT_CNT_ACCURATE | -+ MT_QUIRK_STICKY_FINGERS | -+ MT_QUIRK_WIN8_PTP_BUTTONS, -+ .export_all_inputs = true -+ }, - { } - }; - -@@ -1698,6 +1718,69 @@ static void mt_expired_timeout(struct timer_list *t) - clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - -+static void get_type_cover_backlight_field(struct hid_device *hdev, -+ struct hid_field **field) -+{ -+ struct hid_report_enum *rep_enum; -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid -+ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { -+ *field = cur_field; -+ return; -+ } -+ } -+ } -+ } -+} -+ -+static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) -+{ -+ struct usb_device *udev = hid_to_usb_dev(hdev); -+ struct hid_field *field = NULL; -+ -+ /* Wake up the device in case it's already suspended */ -+ pm_runtime_get_sync(&udev->dev); -+ -+ get_type_cover_backlight_field(hdev, &field); -+ if (!field) { -+ hid_err(hdev, "couldn't find backlight field\n"); -+ goto out; -+ } -+ -+ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; -+ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); -+ -+out: -+ pm_runtime_put_sync(&udev->dev); -+} -+ -+static int mt_pm_notifier(struct notifier_block *notifier, -+ unsigned long pm_event, -+ void *unused) -+{ -+ struct mt_device *td = -+ container_of(notifier, struct mt_device, pm_notifier); -+ struct hid_device *hdev = td->hdev; -+ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { -+ if (pm_event == PM_SUSPEND_PREPARE) -+ update_keyboard_backlight(hdev, 0); -+ else if (pm_event == PM_POST_SUSPEND) -+ update_keyboard_backlight(hdev, 1); -+ } -+ -+ return NOTIFY_DONE; -+} -+ - static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - { - int ret, i; -@@ -1721,6 +1804,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; - hid_set_drvdata(hdev, td); - -+ td->pm_notifier.notifier_call = mt_pm_notifier; -+ register_pm_notifier(&td->pm_notifier); -+ - INIT_LIST_HEAD(&td->applications); - INIT_LIST_HEAD(&td->reports); - -@@ -1750,15 +1836,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - timer_setup(&td->release_timer, mt_expired_timeout, 0); - - ret = hid_parse(hdev); -- if (ret != 0) -+ if (ret != 0) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) - mt_fix_const_fields(hdev, HID_DG_CONTACTID); - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); -- if (ret) -+ if (ret) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); - if (ret) -@@ -1810,6 +1900,7 @@ static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); - -+ unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); - - sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); -@@ -2177,6 +2268,11 @@ static const struct hid_device_id mt_devices[] = { - MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, - USB_DEVICE_ID_XIROKU_CSR2) }, - -+ /* Microsoft Surface type cover */ -+ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, -+ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, -+ - /* Google MT devices */ - { .driver_data = MT_CLS_GOOGLE, - HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, --- -2.35.1 - diff --git a/patches/5.16/0010-cameras.patch b/patches/5.16/0010-cameras.patch deleted file mode 100644 index 98c39cb72..000000000 --- a/patches/5.16/0010-cameras.patch +++ /dev/null @@ -1,5039 +0,0 @@ -From 2d814309698aee8e7be3f75982e01c20f8e0910e Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 5 Apr 2021 23:56:53 +0100 -Subject: [PATCH] media: ipu3-cio2: Toggle sensor streaming in pm runtime ops - -The .suspend() and .resume() runtime_pm operations for the ipu3-cio2 -driver currently do not handle the sensor's stream. Setting .s_stream() on -or off for the sensor subdev means that sensors will pause and resume the -stream at the appropriate time even if their drivers don't implement those -operations. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 15 ++++++++++++++- - 1 file changed, 14 insertions(+), 1 deletion(-) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -index 356ea966cf8d..76fd4e6e8e46 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -@@ -1966,12 +1966,19 @@ static int __maybe_unused cio2_suspend(struct device *dev) - struct pci_dev *pci_dev = to_pci_dev(dev); - struct cio2_device *cio2 = pci_get_drvdata(pci_dev); - struct cio2_queue *q = cio2->cur_queue; -+ int r; - - dev_dbg(dev, "cio2 suspend\n"); - if (!cio2->streaming) - return 0; - - /* Stop stream */ -+ r = v4l2_subdev_call(q->sensor, video, s_stream, 0); -+ if (r) { -+ dev_err(dev, "failed to stop sensor streaming\n"); -+ return r; -+ } -+ - cio2_hw_exit(cio2, q); - synchronize_irq(pci_dev->irq); - -@@ -2005,8 +2012,14 @@ static int __maybe_unused cio2_resume(struct device *dev) - } - - r = cio2_hw_init(cio2, q); -- if (r) -+ if (r) { - dev_err(dev, "fail to init cio2 hw\n"); -+ return r; -+ } -+ -+ r = v4l2_subdev_call(q->sensor, video, s_stream, 1); -+ if (r) -+ dev_err(dev, "fail to start sensor streaming\n"); - - return r; - } --- -2.35.1 - -From fd895042d767f90f5eb5883f216cadb93c782771 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 5 Apr 2021 23:56:54 +0100 -Subject: [PATCH] media: i2c: Add support for ov5693 sensor - -The OV5693 is a 5 Mpx CMOS image sensor, connected via MIPI CSI-2. The -chip is capable of a single lane configuration, but currently only two -lanes are supported. - -Most of the sensor's features are supported, with the main exception -being the lens correction algorithm. - -The driver provides all mandatory, optional and recommended V4L2 controls -for maximum compatibility with libcamera. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 7 + - drivers/media/i2c/Kconfig | 11 + - drivers/media/i2c/Makefile | 1 + - drivers/media/i2c/ov5693.c | 1557 ++++++++++++++++++++++++++++++++++++ - 4 files changed, 1576 insertions(+) - create mode 100644 drivers/media/i2c/ov5693.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index d69fdf5aadec..2eeffdd634c2 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -14117,6 +14117,13 @@ S: Maintained - T: git git://linuxtv.org/media_tree.git - F: drivers/media/i2c/ov5675.c - -+OMNIVISION OV5693 SENSOR DRIVER -+M: Daniel Scally -+L: linux-media@vger.kernel.org -+S: Maintained -+T: git git://linuxtv.org/media_tree.git -+F: drivers/media/i2c/ov5693.c -+ - OMNIVISION OV5695 SENSOR DRIVER - M: Shunqian Zheng - L: linux-media@vger.kernel.org -diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig -index d6a5d4ca439a..8761a90a7a86 100644 ---- a/drivers/media/i2c/Kconfig -+++ b/drivers/media/i2c/Kconfig -@@ -1058,6 +1058,17 @@ config VIDEO_OV5675 - To compile this driver as a module, choose M here: the - module will be called ov5675. - -+config VIDEO_OV5693 -+ tristate "OmniVision OV5693 sensor support" -+ depends on I2C && VIDEO_V4L2 -+ select V4L2_FWNODE -+ help -+ This is a Video4Linux2 sensor driver for the OmniVision -+ OV5693 camera. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ov5693. -+ - config VIDEO_OV5695 - tristate "OmniVision OV5695 sensor support" - depends on I2C && VIDEO_V4L2 -diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile -index 4d4fe08d7a6a..b01f6cd05ee8 100644 ---- a/drivers/media/i2c/Makefile -+++ b/drivers/media/i2c/Makefile -@@ -75,6 +75,7 @@ obj-$(CONFIG_VIDEO_OV5647) += ov5647.o - obj-$(CONFIG_VIDEO_OV5648) += ov5648.o - obj-$(CONFIG_VIDEO_OV5670) += ov5670.o - obj-$(CONFIG_VIDEO_OV5675) += ov5675.o -+obj-$(CONFIG_VIDEO_OV5693) += ov5693.o - obj-$(CONFIG_VIDEO_OV5695) += ov5695.o - obj-$(CONFIG_VIDEO_OV6650) += ov6650.o - obj-$(CONFIG_VIDEO_OV7251) += ov7251.o -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -new file mode 100644 -index 000000000000..9499ee10f56c ---- /dev/null -+++ b/drivers/media/i2c/ov5693.c -@@ -0,0 +1,1557 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. -+ * -+ * Adapted from the atomisp-ov5693 driver, with contributions from: -+ * -+ * Daniel Scally -+ * Jean-Michel Hautbois -+ * Fabian Wuthrich -+ * Tsuchiya Yuto -+ * Jordan Hand -+ * Jake Day -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+/* System Control */ -+#define OV5693_SW_RESET_REG 0x0103 -+#define OV5693_SW_STREAM_REG 0x0100 -+#define OV5693_START_STREAMING 0x01 -+#define OV5693_STOP_STREAMING 0x00 -+#define OV5693_SW_RESET 0x01 -+ -+#define OV5693_REG_CHIP_ID_H 0x300a -+#define OV5693_REG_CHIP_ID_L 0x300b -+/* Yes, this is right. The datasheet for the OV5693 gives its ID as 0x5690 */ -+#define OV5693_CHIP_ID 0x5690 -+ -+/* Exposure */ -+#define OV5693_EXPOSURE_L_CTRL_HH_REG 0x3500 -+#define OV5693_EXPOSURE_L_CTRL_H_REG 0x3501 -+#define OV5693_EXPOSURE_L_CTRL_L_REG 0x3502 -+#define OV5693_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(14, 12)) >> 12) -+#define OV5693_EXPOSURE_CTRL_H(v) (((v) & GENMASK(11, 4)) >> 4) -+#define OV5693_EXPOSURE_CTRL_L(v) (((v) & GENMASK(3, 0)) << 4) -+#define OV5693_INTEGRATION_TIME_MARGIN 8 -+#define OV5693_EXPOSURE_MIN 1 -+#define OV5693_EXPOSURE_STEP 1 -+ -+/* Analogue Gain */ -+#define OV5693_GAIN_CTRL_H_REG 0x350a -+#define OV5693_GAIN_CTRL_H(v) (((v) >> 4) & GENMASK(2, 0)) -+#define OV5693_GAIN_CTRL_L_REG 0x350b -+#define OV5693_GAIN_CTRL_L(v) (((v) << 4) & GENMASK(7, 4)) -+#define OV5693_GAIN_MIN 1 -+#define OV5693_GAIN_MAX 127 -+#define OV5693_GAIN_DEF 8 -+#define OV5693_GAIN_STEP 1 -+ -+/* Digital Gain */ -+#define OV5693_MWB_RED_GAIN_H_REG 0x3400 -+#define OV5693_MWB_RED_GAIN_L_REG 0x3401 -+#define OV5693_MWB_GREEN_GAIN_H_REG 0x3402 -+#define OV5693_MWB_GREEN_GAIN_L_REG 0x3403 -+#define OV5693_MWB_BLUE_GAIN_H_REG 0x3404 -+#define OV5693_MWB_BLUE_GAIN_L_REG 0x3405 -+#define OV5693_MWB_GAIN_H_CTRL(v) (((v) >> 8) & GENMASK(3, 0)) -+#define OV5693_MWB_GAIN_L_CTRL(v) ((v) & GENMASK(7, 0)) -+#define OV5693_MWB_GAIN_MAX 0x0fff -+#define OV5693_DIGITAL_GAIN_MIN 1 -+#define OV5693_DIGITAL_GAIN_MAX 4095 -+#define OV5693_DIGITAL_GAIN_DEF 1024 -+#define OV5693_DIGITAL_GAIN_STEP 1 -+ -+/* Timing and Format */ -+#define OV5693_CROP_START_X_H_REG 0x3800 -+#define OV5693_CROP_START_X_H(v) (((v) & GENMASK(12, 8)) >> 8) -+#define OV5693_CROP_START_X_L_REG 0x3801 -+#define OV5693_CROP_START_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_START_Y_H_REG 0x3802 -+#define OV5693_CROP_START_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) -+#define OV5693_CROP_START_Y_L_REG 0x3803 -+#define OV5693_CROP_START_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_END_X_H_REG 0x3804 -+#define OV5693_CROP_END_X_H(v) (((v) & GENMASK(12, 8)) >> 8) -+#define OV5693_CROP_END_X_L_REG 0x3805 -+#define OV5693_CROP_END_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_CROP_END_Y_H_REG 0x3806 -+#define OV5693_CROP_END_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) -+#define OV5693_CROP_END_Y_L_REG 0x3807 -+#define OV5693_CROP_END_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OUTPUT_SIZE_X_H_REG 0x3808 -+#define OV5693_OUTPUT_SIZE_X_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OUTPUT_SIZE_X_L_REG 0x3809 -+#define OV5693_OUTPUT_SIZE_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OUTPUT_SIZE_Y_H_REG 0x380a -+#define OV5693_OUTPUT_SIZE_Y_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OUTPUT_SIZE_Y_L_REG 0x380b -+#define OV5693_OUTPUT_SIZE_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_TIMING_HTS_H_REG 0x380c -+#define OV5693_TIMING_HTS_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_TIMING_HTS_L_REG 0x380d -+#define OV5693_TIMING_HTS_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_FIXED_PPL 2688U -+ -+#define OV5693_TIMING_VTS_H_REG 0x380e -+#define OV5693_TIMING_VTS_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_TIMING_VTS_L_REG 0x380f -+#define OV5693_TIMING_VTS_L(v) ((v) & GENMASK(7, 0)) -+#define OV5693_TIMING_MAX_VTS 0xffff -+#define OV5693_TIMING_MIN_VTS 0x04 -+ -+#define OV5693_OFFSET_START_X_H_REG 0x3810 -+#define OV5693_OFFSET_START_X_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OFFSET_START_X_L_REG 0x3811 -+#define OV5693_OFFSET_START_X_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_OFFSET_START_Y_H_REG 0x3812 -+#define OV5693_OFFSET_START_Y_H(v) (((v) & GENMASK(15, 8)) >> 8) -+#define OV5693_OFFSET_START_Y_L_REG 0x3813 -+#define OV5693_OFFSET_START_Y_L(v) ((v) & GENMASK(7, 0)) -+ -+#define OV5693_SUB_INC_X_REG 0x3814 -+#define OV5693_SUB_INC_Y_REG 0x3815 -+ -+#define OV5693_FORMAT1_REG 0x3820 -+#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -+#define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) -+#define OV5693_FORMAT1_VBIN_EN BIT(0) -+#define OV5693_FORMAT2_REG 0x3821 -+#define OV5693_FORMAT2_HDR_EN BIT(7) -+#define OV5693_FORMAT2_FLIP_HORZ_ISP_EN BIT(2) -+#define OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN BIT(1) -+#define OV5693_FORMAT2_HBIN_EN BIT(0) -+ -+#define OV5693_ISP_CTRL2_REG 0x5002 -+#define OV5693_ISP_SCALE_ENABLE BIT(7) -+ -+/* Pixel Array */ -+#define OV5693_NATIVE_WIDTH 2624 -+#define OV5693_NATIVE_HEIGHT 1956 -+#define OV5693_NATIVE_START_LEFT 0 -+#define OV5693_NATIVE_START_TOP 0 -+#define OV5693_ACTIVE_WIDTH 2592 -+#define OV5693_ACTIVE_HEIGHT 1944 -+#define OV5693_ACTIVE_START_LEFT 16 -+#define OV5693_ACTIVE_START_TOP 6 -+#define OV5693_MIN_CROP_WIDTH 2 -+#define OV5693_MIN_CROP_HEIGHT 2 -+ -+/* Test Pattern */ -+#define OV5693_TEST_PATTERN_REG 0x5e00 -+#define OV5693_TEST_PATTERN_ENABLE BIT(7) -+#define OV5693_TEST_PATTERN_ROLLING BIT(6) -+#define OV5693_TEST_PATTERN_RANDOM 0x01 -+#define OV5693_TEST_PATTERN_BARS 0x00 -+ -+/* System Frequencies */ -+#define OV5693_XVCLK_FREQ 19200000 -+#define OV5693_LINK_FREQ_400MHZ 400000000 -+#define OV5693_PIXEL_RATE 160000000 -+ -+/* Miscellaneous */ -+#define OV5693_NUM_SUPPLIES 2 -+ -+#define to_ov5693_sensor(x) container_of(x, struct ov5693_device, sd) -+ -+struct ov5693_reg { -+ u16 reg; -+ u8 val; -+}; -+ -+struct ov5693_reg_list { -+ u32 num_regs; -+ const struct ov5693_reg *regs; -+}; -+ -+struct ov5693_device { -+ struct i2c_client *client; -+ struct device *dev; -+ -+ /* Protect against concurrent changes to controls */ -+ struct mutex lock; -+ -+ struct gpio_desc *reset; -+ struct gpio_desc *powerdown; -+ struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; -+ struct clk *clk; -+ -+ struct ov5693_mode { -+ struct v4l2_rect crop; -+ struct v4l2_mbus_framefmt format; -+ bool binning_x; -+ bool binning_y; -+ unsigned int inc_x_odd; -+ unsigned int inc_y_odd; -+ unsigned int vts; -+ } mode; -+ bool streaming; -+ -+ struct v4l2_subdev sd; -+ struct media_pad pad; -+ -+ struct ov5693_v4l2_ctrls { -+ struct v4l2_ctrl_handler handler; -+ struct v4l2_ctrl *link_freq; -+ struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *exposure; -+ struct v4l2_ctrl *analogue_gain; -+ struct v4l2_ctrl *digital_gain; -+ struct v4l2_ctrl *hflip; -+ struct v4l2_ctrl *vflip; -+ struct v4l2_ctrl *hblank; -+ struct v4l2_ctrl *vblank; -+ struct v4l2_ctrl *test_pattern; -+ } ctrls; -+}; -+ -+static const struct ov5693_reg ov5693_global_regs[] = { -+ {0x3016, 0xf0}, -+ {0x3017, 0xf0}, -+ {0x3018, 0xf0}, -+ {0x3022, 0x01}, -+ {0x3028, 0x44}, -+ {0x3098, 0x02}, -+ {0x3099, 0x19}, -+ {0x309a, 0x02}, -+ {0x309b, 0x01}, -+ {0x309c, 0x00}, -+ {0x30a0, 0xd2}, -+ {0x30a2, 0x01}, -+ {0x30b2, 0x00}, -+ {0x30b3, 0x7d}, -+ {0x30b4, 0x03}, -+ {0x30b5, 0x04}, -+ {0x30b6, 0x01}, -+ {0x3104, 0x21}, -+ {0x3106, 0x00}, -+ {0x3406, 0x01}, -+ {0x3503, 0x07}, -+ {0x350b, 0x40}, -+ {0x3601, 0x0a}, -+ {0x3602, 0x38}, -+ {0x3612, 0x80}, -+ {0x3620, 0x54}, -+ {0x3621, 0xc7}, -+ {0x3622, 0x0f}, -+ {0x3625, 0x10}, -+ {0x3630, 0x55}, -+ {0x3631, 0xf4}, -+ {0x3632, 0x00}, -+ {0x3633, 0x34}, -+ {0x3634, 0x02}, -+ {0x364d, 0x0d}, -+ {0x364f, 0xdd}, -+ {0x3660, 0x04}, -+ {0x3662, 0x10}, -+ {0x3663, 0xf1}, -+ {0x3665, 0x00}, -+ {0x3666, 0x20}, -+ {0x3667, 0x00}, -+ {0x366a, 0x80}, -+ {0x3680, 0xe0}, -+ {0x3681, 0x00}, -+ {0x3700, 0x42}, -+ {0x3701, 0x14}, -+ {0x3702, 0xa0}, -+ {0x3703, 0xd8}, -+ {0x3704, 0x78}, -+ {0x3705, 0x02}, -+ {0x370a, 0x00}, -+ {0x370b, 0x20}, -+ {0x370c, 0x0c}, -+ {0x370d, 0x11}, -+ {0x370e, 0x00}, -+ {0x370f, 0x40}, -+ {0x3710, 0x00}, -+ {0x371a, 0x1c}, -+ {0x371b, 0x05}, -+ {0x371c, 0x01}, -+ {0x371e, 0xa1}, -+ {0x371f, 0x0c}, -+ {0x3721, 0x00}, -+ {0x3724, 0x10}, -+ {0x3726, 0x00}, -+ {0x372a, 0x01}, -+ {0x3730, 0x10}, -+ {0x3738, 0x22}, -+ {0x3739, 0xe5}, -+ {0x373a, 0x50}, -+ {0x373b, 0x02}, -+ {0x373c, 0x41}, -+ {0x373f, 0x02}, -+ {0x3740, 0x42}, -+ {0x3741, 0x02}, -+ {0x3742, 0x18}, -+ {0x3743, 0x01}, -+ {0x3744, 0x02}, -+ {0x3747, 0x10}, -+ {0x374c, 0x04}, -+ {0x3751, 0xf0}, -+ {0x3752, 0x00}, -+ {0x3753, 0x00}, -+ {0x3754, 0xc0}, -+ {0x3755, 0x00}, -+ {0x3756, 0x1a}, -+ {0x3758, 0x00}, -+ {0x3759, 0x0f}, -+ {0x376b, 0x44}, -+ {0x375c, 0x04}, -+ {0x3774, 0x10}, -+ {0x3776, 0x00}, -+ {0x377f, 0x08}, -+ {0x3780, 0x22}, -+ {0x3781, 0x0c}, -+ {0x3784, 0x2c}, -+ {0x3785, 0x1e}, -+ {0x378f, 0xf5}, -+ {0x3791, 0xb0}, -+ {0x3795, 0x00}, -+ {0x3796, 0x64}, -+ {0x3797, 0x11}, -+ {0x3798, 0x30}, -+ {0x3799, 0x41}, -+ {0x379a, 0x07}, -+ {0x379b, 0xb0}, -+ {0x379c, 0x0c}, -+ {0x3a04, 0x06}, -+ {0x3a05, 0x14}, -+ {0x3e07, 0x20}, -+ {0x4000, 0x08}, -+ {0x4001, 0x04}, -+ {0x4004, 0x08}, -+ {0x4006, 0x20}, -+ {0x4008, 0x24}, -+ {0x4009, 0x10}, -+ {0x4058, 0x00}, -+ {0x4101, 0xb2}, -+ {0x4307, 0x31}, -+ {0x4511, 0x05}, -+ {0x4512, 0x01}, -+ {0x481f, 0x30}, -+ {0x4826, 0x2c}, -+ {0x4d02, 0xfd}, -+ {0x4d03, 0xf5}, -+ {0x4d04, 0x0c}, -+ {0x4d05, 0xcc}, -+ {0x4837, 0x0a}, -+ {0x5003, 0x20}, -+ {0x5013, 0x00}, -+ {0x5842, 0x01}, -+ {0x5843, 0x2b}, -+ {0x5844, 0x01}, -+ {0x5845, 0x92}, -+ {0x5846, 0x01}, -+ {0x5847, 0x8f}, -+ {0x5848, 0x01}, -+ {0x5849, 0x0c}, -+ {0x5e10, 0x0c}, -+ {0x3820, 0x00}, -+ {0x3821, 0x1e}, -+ {0x5041, 0x14} -+}; -+ -+static const struct ov5693_reg_list ov5693_global_setting = { -+ .num_regs = ARRAY_SIZE(ov5693_global_regs), -+ .regs = ov5693_global_regs, -+}; -+ -+static const struct v4l2_rect ov5693_default_crop = { -+ .left = OV5693_ACTIVE_START_LEFT, -+ .top = OV5693_ACTIVE_START_TOP, -+ .width = OV5693_ACTIVE_WIDTH, -+ .height = OV5693_ACTIVE_HEIGHT, -+}; -+ -+static const struct v4l2_mbus_framefmt ov5693_default_fmt = { -+ .width = OV5693_ACTIVE_WIDTH, -+ .height = OV5693_ACTIVE_HEIGHT, -+ .code = MEDIA_BUS_FMT_SBGGR10_1X10, -+}; -+ -+static const s64 link_freq_menu_items[] = { -+ OV5693_LINK_FREQ_400MHZ -+}; -+ -+static const char * const ov5693_supply_names[] = { -+ "avdd", -+ "dovdd", -+}; -+ -+static const char * const ov5693_test_pattern_menu[] = { -+ "Disabled", -+ "Random Data", -+ "Colour Bars", -+ "Colour Bars with Rolling Bar" -+}; -+ -+static const u8 ov5693_test_pattern_bits[] = { -+ 0, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_RANDOM, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS, -+ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS | -+ OV5693_TEST_PATTERN_ROLLING, -+}; -+ -+/* I2C I/O Operations */ -+ -+static int ov5693_read_reg(struct ov5693_device *ov5693, u16 addr, u8 *value) -+{ -+ struct i2c_client *client = ov5693->client; -+ struct i2c_msg msgs[2]; -+ u8 addr_buf[2]; -+ u8 data_buf; -+ int ret; -+ -+ put_unaligned_be16(addr, addr_buf); -+ -+ /* 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 register value */ -+ msgs[1].addr = client->addr; -+ msgs[1].flags = I2C_M_RD; -+ msgs[1].len = 1; -+ msgs[1].buf = &data_buf; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ if (ret != ARRAY_SIZE(msgs)) -+ return -EIO; -+ -+ *value = data_buf; -+ -+ return 0; -+} -+ -+static void ov5693_write_reg(struct ov5693_device *ov5693, u16 addr, u8 value, -+ int *error) -+{ -+ unsigned char data[3] = { addr >> 8, addr & 0xff, value }; -+ int ret; -+ -+ if (*error < 0) -+ return; -+ -+ ret = i2c_master_send(ov5693->client, data, sizeof(data)); -+ if (ret < 0) { -+ dev_dbg(ov5693->dev, "i2c send error at address 0x%04x: %d\n", -+ addr, ret); -+ *error = ret; -+ } -+} -+ -+static int ov5693_write_reg_array(struct ov5693_device *ov5693, -+ const struct ov5693_reg_list *reglist) -+{ -+ unsigned int i; -+ int ret = 0; -+ -+ for (i = 0; i < reglist->num_regs; i++) -+ ov5693_write_reg(ov5693, reglist->regs[i].reg, -+ reglist->regs[i].val, &ret); -+ -+ return ret; -+} -+ -+static int ov5693_update_bits(struct ov5693_device *ov5693, u16 address, -+ u16 mask, u16 bits) -+{ -+ u8 value = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, address, &value); -+ if (ret) -+ return ret; -+ -+ value &= ~mask; -+ value |= bits; -+ -+ ov5693_write_reg(ov5693, address, value, &ret); -+ -+ return ret; -+} -+ -+/* V4L2 Controls Functions */ -+ -+static int ov5693_flip_vert_configure(struct ov5693_device *ov5693, bool enable) -+{ -+ u8 bits = OV5693_FORMAT1_FLIP_VERT_ISP_EN | -+ OV5693_FORMAT1_FLIP_VERT_SENSOR_EN; -+ int ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, bits, -+ enable ? bits : 0); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int ov5693_flip_horz_configure(struct ov5693_device *ov5693, bool enable) -+{ -+ u8 bits = OV5693_FORMAT2_FLIP_HORZ_ISP_EN | -+ OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN; -+ int ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, bits, -+ enable ? bits : 0); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int ov5693_get_exposure(struct ov5693_device *ov5693, s32 *value) -+{ -+ u8 exposure_hh = 0, exposure_h = 0, exposure_l = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, &exposure_hh); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, &exposure_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, &exposure_l); -+ if (ret) -+ return ret; -+ -+ /* The lowest 4 bits are unsupported fractional bits */ -+ *value = ((exposure_hh << 16) | (exposure_h << 8) | exposure_l) >> 4; -+ -+ return 0; -+} -+ -+static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, -+ OV5693_EXPOSURE_CTRL_HH(exposure), &ret); -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, -+ OV5693_EXPOSURE_CTRL_H(exposure), &ret); -+ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, -+ OV5693_EXPOSURE_CTRL_L(exposure), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_get_gain(struct ov5693_device *ov5693, u32 *gain) -+{ -+ u8 gain_l = 0, gain_h = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_H_REG, &gain_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_L_REG, &gain_l); -+ if (ret) -+ return ret; -+ -+ /* As with exposure, the lowest 4 bits are fractional bits. */ -+ *gain = ((gain_h << 8) | gain_l) >> 4; -+ -+ return ret; -+} -+ -+static int ov5693_digital_gain_configure(struct ov5693_device *ov5693, u32 gain) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_H_REG, -+ OV5693_MWB_GAIN_H_CTRL(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_L_REG, -+ OV5693_MWB_GAIN_L_CTRL(gain), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_analog_gain_configure(struct ov5693_device *ov5693, u32 gain) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_L_REG, -+ OV5693_GAIN_CTRL_L(gain), &ret); -+ ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_H_REG, -+ OV5693_GAIN_CTRL_H(gain), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_vts_configure(struct ov5693_device *ov5693, u32 vblank) -+{ -+ u16 vts = ov5693->mode.format.height + vblank; -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG, -+ OV5693_TIMING_VTS_H(vts), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG, -+ OV5693_TIMING_VTS_L(vts), &ret); -+ -+ return ret; -+} -+ -+static int ov5693_test_pattern_configure(struct ov5693_device *ov5693, u32 idx) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_TEST_PATTERN_REG, -+ ov5693_test_pattern_bits[idx], &ret); -+ -+ return ret; -+} -+ -+static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *ov5693 = -+ container_of(ctrl->handler, struct ov5693_device, ctrls.handler); -+ int ret = 0; -+ -+ /* If VBLANK is altered we need to update exposure to compensate */ -+ if (ctrl->id == V4L2_CID_VBLANK) { -+ int exposure_max; -+ -+ exposure_max = ov5693->mode.format.height + ctrl->val - -+ OV5693_INTEGRATION_TIME_MARGIN; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, -+ ov5693->ctrls.exposure->minimum, -+ exposure_max, -+ ov5693->ctrls.exposure->step, -+ min(ov5693->ctrls.exposure->val, exposure_max)); -+ } -+ -+ /* Only apply changes to the controls if the device is powered up */ -+ if (!pm_runtime_get_if_in_use(ov5693->dev)) -+ return 0; -+ -+ switch (ctrl->id) { -+ case V4L2_CID_EXPOSURE: -+ ret = ov5693_exposure_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_ANALOGUE_GAIN: -+ ret = ov5693_analog_gain_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_DIGITAL_GAIN: -+ ret = ov5693_digital_gain_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_HFLIP: -+ ret = ov5693_flip_horz_configure(ov5693, !!ctrl->val); -+ break; -+ case V4L2_CID_VFLIP: -+ ret = ov5693_flip_vert_configure(ov5693, !!ctrl->val); -+ break; -+ case V4L2_CID_VBLANK: -+ ret = ov5693_vts_configure(ov5693, ctrl->val); -+ break; -+ case V4L2_CID_TEST_PATTERN: -+ ret = ov5693_test_pattern_configure(ov5693, ctrl->val); -+ break; -+ default: -+ ret = -EINVAL; -+ } -+ -+ pm_runtime_put(ov5693->dev); -+ -+ return ret; -+} -+ -+static int ov5693_g_volatile_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *ov5693 = -+ container_of(ctrl->handler, struct ov5693_device, ctrls.handler); -+ -+ switch (ctrl->id) { -+ case V4L2_CID_EXPOSURE_ABSOLUTE: -+ return ov5693_get_exposure(ov5693, &ctrl->val); -+ case V4L2_CID_AUTOGAIN: -+ return ov5693_get_gain(ov5693, &ctrl->val); -+ default: -+ return -EINVAL; -+ } -+} -+ -+static const struct v4l2_ctrl_ops ov5693_ctrl_ops = { -+ .s_ctrl = ov5693_s_ctrl, -+ .g_volatile_ctrl = ov5693_g_volatile_ctrl -+}; -+ -+/* System Control Functions */ -+ -+static int ov5693_mode_configure(struct ov5693_device *ov5693) -+{ -+ const struct ov5693_mode *mode = &ov5693->mode; -+ int ret = 0; -+ -+ /* Crop Start X */ -+ ov5693_write_reg(ov5693, OV5693_CROP_START_X_H_REG, -+ OV5693_CROP_START_X_H(mode->crop.left), &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_START_X_L_REG, -+ OV5693_CROP_START_X_L(mode->crop.left), &ret); -+ -+ /* Offset X */ -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_H_REG, -+ OV5693_OFFSET_START_X_H(0), &ret); -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_L_REG, -+ OV5693_OFFSET_START_X_L(0), &ret); -+ -+ /* Output Size X */ -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_H_REG, -+ OV5693_OUTPUT_SIZE_X_H(mode->format.width), &ret); -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_L_REG, -+ OV5693_OUTPUT_SIZE_X_L(mode->format.width), &ret); -+ -+ /* Crop End X */ -+ ov5693_write_reg(ov5693, OV5693_CROP_END_X_H_REG, -+ OV5693_CROP_END_X_H(mode->crop.left + mode->crop.width), -+ &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_END_X_L_REG, -+ OV5693_CROP_END_X_L(mode->crop.left + mode->crop.width), -+ &ret); -+ -+ /* Horizontal Total Size */ -+ ov5693_write_reg(ov5693, OV5693_TIMING_HTS_H_REG, -+ OV5693_TIMING_HTS_H(OV5693_FIXED_PPL), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_HTS_L_REG, -+ OV5693_TIMING_HTS_L(OV5693_FIXED_PPL), &ret); -+ -+ /* Crop Start Y */ -+ ov5693_write_reg(ov5693, OV5693_CROP_START_Y_H_REG, -+ OV5693_CROP_START_Y_H(mode->crop.top), &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_START_Y_L_REG, -+ OV5693_CROP_START_Y_L(mode->crop.top), &ret); -+ -+ /* Offset Y */ -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_H_REG, -+ OV5693_OFFSET_START_Y_H(0), &ret); -+ ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_L_REG, -+ OV5693_OFFSET_START_Y_L(0), &ret); -+ -+ /* Output Size Y */ -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_H_REG, -+ OV5693_OUTPUT_SIZE_Y_H(mode->format.height), &ret); -+ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_L_REG, -+ OV5693_OUTPUT_SIZE_Y_L(mode->format.height), &ret); -+ -+ /* Crop End Y */ -+ ov5693_write_reg(ov5693, OV5693_CROP_END_Y_H_REG, -+ OV5693_CROP_END_Y_H(mode->crop.top + mode->crop.height), -+ &ret); -+ ov5693_write_reg(ov5693, OV5693_CROP_END_Y_L_REG, -+ OV5693_CROP_END_Y_L(mode->crop.top + mode->crop.height), -+ &ret); -+ -+ /* Vertical Total Size */ -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG, -+ OV5693_TIMING_VTS_H(mode->vts), &ret); -+ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG, -+ OV5693_TIMING_VTS_L(mode->vts), &ret); -+ -+ /* Subsample X increase */ -+ ov5693_write_reg(ov5693, OV5693_SUB_INC_X_REG, -+ ((mode->inc_x_odd << 4) & 0xf0) | 0x01, &ret); -+ /* Subsample Y increase */ -+ ov5693_write_reg(ov5693, OV5693_SUB_INC_Y_REG, -+ ((mode->inc_y_odd << 4) & 0xf0) | 0x01, &ret); -+ -+ /* Binning */ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, -+ OV5693_FORMAT1_VBIN_EN, -+ mode->binning_y ? OV5693_FORMAT1_VBIN_EN : 0); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, -+ OV5693_FORMAT2_HBIN_EN, -+ mode->binning_x ? OV5693_FORMAT2_HBIN_EN : 0); -+ -+ return ret; -+} -+ -+static int ov5693_sw_standby(struct ov5693_device *ov5693, bool standby) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_SW_STREAM_REG, -+ standby ? OV5693_STOP_STREAMING : OV5693_START_STREAMING, -+ &ret); -+ -+ return ret; -+} -+ -+static int ov5693_sw_reset(struct ov5693_device *ov5693) -+{ -+ int ret = 0; -+ -+ ov5693_write_reg(ov5693, OV5693_SW_RESET_REG, OV5693_SW_RESET, &ret); -+ -+ return ret; -+} -+ -+static int ov5693_sensor_init(struct ov5693_device *ov5693) -+{ -+ int ret = 0; -+ -+ ret = ov5693_sw_reset(ov5693); -+ if (ret) { -+ dev_err(ov5693->dev, "%s software reset error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_write_reg_array(ov5693, &ov5693_global_setting); -+ if (ret) { -+ dev_err(ov5693->dev, "%s global settings error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_mode_configure(ov5693); -+ if (ret) { -+ dev_err(ov5693->dev, "%s mode configure error\n", __func__); -+ return ret; -+ } -+ -+ ret = ov5693_sw_standby(ov5693, true); -+ if (ret) -+ dev_err(ov5693->dev, "%s software standby error\n", __func__); -+ -+ return ret; -+} -+ -+static void ov5693_sensor_powerdown(struct ov5693_device *ov5693) -+{ -+ gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); -+ -+ regulator_bulk_disable(OV5693_NUM_SUPPLIES, ov5693->supplies); -+ -+ clk_disable_unprepare(ov5693->clk); -+} -+ -+static int ov5693_sensor_powerup(struct ov5693_device *ov5693) -+{ -+ int ret; -+ -+ gpiod_set_value_cansleep(ov5693->reset, 1); -+ gpiod_set_value_cansleep(ov5693->powerdown, 1); -+ -+ ret = clk_prepare_enable(ov5693->clk); -+ if (ret) { -+ dev_err(ov5693->dev, "Failed to enable clk\n"); -+ goto fail_power; -+ } -+ -+ ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, ov5693->supplies); -+ if (ret) { -+ dev_err(ov5693->dev, "Failed to enable regulators\n"); -+ goto fail_power; -+ } -+ -+ gpiod_set_value_cansleep(ov5693->powerdown, 0); -+ gpiod_set_value_cansleep(ov5693->reset, 0); -+ -+ usleep_range(5000, 7500); -+ -+ return 0; -+ -+fail_power: -+ ov5693_sensor_powerdown(ov5693); -+ return ret; -+} -+ -+static int __maybe_unused ov5693_sensor_suspend(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ ov5693_sensor_powerdown(ov5693); -+ -+ return 0; -+} -+ -+static int __maybe_unused ov5693_sensor_resume(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ int ret; -+ -+ mutex_lock(&ov5693->lock); -+ -+ ret = ov5693_sensor_powerup(ov5693); -+ if (ret) -+ goto out_unlock; -+ -+ ret = ov5693_sensor_init(ov5693); -+ if (ret) { -+ dev_err(dev, "ov5693 sensor init failure\n"); -+ goto err_power; -+ } -+ -+ goto out_unlock; -+ -+err_power: -+ ov5693_sensor_powerdown(ov5693); -+out_unlock: -+ mutex_unlock(&ov5693->lock); -+ return ret; -+} -+ -+static int ov5693_detect(struct ov5693_device *ov5693) -+{ -+ u8 id_l = 0, id_h = 0; -+ u16 id = 0; -+ int ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_H, &id_h); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_L, &id_l); -+ if (ret) -+ return ret; -+ -+ id = (id_h << 8) | id_l; -+ -+ if (id != OV5693_CHIP_ID) { -+ dev_err(ov5693->dev, "sensor ID mismatch. Found 0x%04x\n", id); -+ return -ENODEV; -+ } -+ -+ return 0; -+} -+ -+/* V4L2 Framework callbacks */ -+ -+static unsigned int __ov5693_calc_vts(u32 height) -+{ -+ /* -+ * We need to set a sensible default VTS for whatever format height we -+ * happen to be given from set_fmt(). This function just targets -+ * an even multiple of 30fps. -+ */ -+ -+ unsigned int tgt_fps; -+ -+ tgt_fps = rounddown(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / height, 30); -+ -+ return ALIGN_DOWN(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / tgt_fps, 2); -+} -+ -+static struct v4l2_mbus_framefmt * -+__ov5693_get_pad_format(struct ov5693_device *ov5693, -+ struct v4l2_subdev_state *state, -+ unsigned int pad, enum v4l2_subdev_format_whence which) -+{ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ return v4l2_subdev_get_try_format(&ov5693->sd, state, pad); -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ return &ov5693->mode.format; -+ default: -+ return NULL; -+ } -+} -+ -+static struct v4l2_rect * -+__ov5693_get_pad_crop(struct ov5693_device *ov5693, -+ struct v4l2_subdev_state *state, -+ unsigned int pad, enum v4l2_subdev_format_whence which) -+{ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ return v4l2_subdev_get_try_crop(&ov5693->sd, state, pad); -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ return &ov5693->mode.crop; -+ } -+ -+ return NULL; -+} -+ -+static int ov5693_get_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_format *format) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ format->format = ov5693->mode.format; -+ -+ return 0; -+} -+ -+static int ov5693_set_fmt(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_format *format) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ const struct v4l2_rect *crop; -+ struct v4l2_mbus_framefmt *fmt; -+ unsigned int hratio, vratio; -+ unsigned int width, height; -+ unsigned int hblank; -+ int exposure_max; -+ int ret = 0; -+ -+ crop = __ov5693_get_pad_crop(ov5693, state, format->pad, format->which); -+ -+ /* -+ * Align to two to simplify the binning calculations below, and clamp -+ * the requested format at the crop rectangle -+ */ -+ width = clamp_t(unsigned int, ALIGN(format->format.width, 2), -+ OV5693_MIN_CROP_WIDTH, crop->width); -+ height = clamp_t(unsigned int, ALIGN(format->format.height, 2), -+ OV5693_MIN_CROP_HEIGHT, crop->height); -+ -+ /* -+ * We can only support setting either the dimensions of the crop rect -+ * or those dimensions binned (separately) by a factor of two. -+ */ -+ hratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->width, width), 1, 2); -+ vratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->height, height), 1, 2); -+ -+ fmt = __ov5693_get_pad_format(ov5693, state, format->pad, format->which); -+ -+ fmt->width = crop->width / hratio; -+ fmt->height = crop->height / vratio; -+ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ -+ format->format = *fmt; -+ -+ if (format->which == V4L2_SUBDEV_FORMAT_TRY) -+ return ret; -+ -+ mutex_lock(&ov5693->lock); -+ -+ ov5693->mode.binning_x = hratio > 1 ? true : false; -+ ov5693->mode.inc_x_odd = hratio > 1 ? 3 : 1; -+ ov5693->mode.binning_y = vratio > 1 ? true : false; -+ ov5693->mode.inc_y_odd = vratio > 1 ? 3 : 1; -+ -+ ov5693->mode.vts = __ov5693_calc_vts(fmt->height); -+ -+ __v4l2_ctrl_modify_range(ov5693->ctrls.vblank, -+ OV5693_TIMING_MIN_VTS, -+ OV5693_TIMING_MAX_VTS - fmt->height, -+ 1, ov5693->mode.vts - fmt->height); -+ __v4l2_ctrl_s_ctrl(ov5693->ctrls.vblank, -+ ov5693->mode.vts - fmt->height); -+ -+ hblank = OV5693_FIXED_PPL - fmt->width; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.hblank, hblank, hblank, 1, -+ hblank); -+ -+ exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN; -+ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, -+ ov5693->ctrls.exposure->minimum, exposure_max, -+ ov5693->ctrls.exposure->step, -+ min(ov5693->ctrls.exposure->val, exposure_max)); -+ -+ mutex_unlock(&ov5693->lock); -+ return ret; -+} -+ -+static int ov5693_get_selection(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ switch (sel->target) { -+ case V4L2_SEL_TGT_CROP: -+ mutex_lock(&ov5693->lock); -+ sel->r = *__ov5693_get_pad_crop(ov5693, state, sel->pad, -+ sel->which); -+ mutex_unlock(&ov5693->lock); -+ break; -+ case V4L2_SEL_TGT_NATIVE_SIZE: -+ sel->r.top = 0; -+ sel->r.left = 0; -+ sel->r.width = OV5693_NATIVE_WIDTH; -+ sel->r.height = OV5693_NATIVE_HEIGHT; -+ break; -+ case V4L2_SEL_TGT_CROP_BOUNDS: -+ case V4L2_SEL_TGT_CROP_DEFAULT: -+ sel->r.top = OV5693_ACTIVE_START_TOP; -+ sel->r.left = OV5693_ACTIVE_START_LEFT; -+ sel->r.width = OV5693_ACTIVE_WIDTH; -+ sel->r.height = OV5693_ACTIVE_HEIGHT; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int ov5693_set_selection(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(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 boundaries of the crop rectangle to the size of the sensor -+ * pixel array. Align to multiples of 2 to ensure Bayer pattern isn't -+ * disrupted. -+ */ -+ rect.left = clamp(ALIGN(sel->r.left, 2), OV5693_NATIVE_START_LEFT, -+ OV5693_NATIVE_WIDTH); -+ rect.top = clamp(ALIGN(sel->r.top, 2), OV5693_NATIVE_START_TOP, -+ OV5693_NATIVE_HEIGHT); -+ rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), -+ OV5693_MIN_CROP_WIDTH, OV5693_NATIVE_WIDTH); -+ rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), -+ OV5693_MIN_CROP_HEIGHT, OV5693_NATIVE_HEIGHT); -+ -+ /* Make sure the crop rectangle isn't outside the bounds of the array */ -+ rect.width = min_t(unsigned int, rect.width, -+ OV5693_NATIVE_WIDTH - rect.left); -+ rect.height = min_t(unsigned int, rect.height, -+ OV5693_NATIVE_HEIGHT - rect.top); -+ -+ __crop = __ov5693_get_pad_crop(ov5693, state, sel->pad, sel->which); -+ -+ if (rect.width != __crop->width || rect.height != __crop->height) { -+ /* -+ * Reset the output image size if the crop rectangle size has -+ * been modified. -+ */ -+ format = __ov5693_get_pad_format(ov5693, state, sel->pad, sel->which); -+ format->width = rect.width; -+ format->height = rect.height; -+ } -+ -+ *__crop = rect; -+ sel->r = rect; -+ -+ return 0; -+} -+ -+static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ int ret; -+ -+ if (enable) { -+ ret = pm_runtime_get_sync(ov5693->dev); -+ if (ret < 0) -+ goto err_power_down; -+ -+ ret = __v4l2_ctrl_handler_setup(&ov5693->ctrls.handler); -+ if (ret) -+ goto err_power_down; -+ } -+ -+ mutex_lock(&ov5693->lock); -+ ret = ov5693_sw_standby(ov5693, !enable); -+ mutex_unlock(&ov5693->lock); -+ -+ if (ret) -+ goto err_power_down; -+ ov5693->streaming = !!enable; -+ -+ if (!enable) -+ pm_runtime_put(ov5693->dev); -+ -+ return 0; -+err_power_down: -+ pm_runtime_put_noidle(ov5693->dev); -+ return ret; -+} -+ -+static int ov5693_g_frame_interval(struct v4l2_subdev *sd, -+ struct v4l2_subdev_frame_interval *interval) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ unsigned int framesize = OV5693_FIXED_PPL * (ov5693->mode.format.height + -+ ov5693->ctrls.vblank->val); -+ unsigned int fps = DIV_ROUND_CLOSEST(OV5693_PIXEL_RATE, framesize); -+ -+ interval->interval.numerator = 1; -+ interval->interval.denominator = fps; -+ -+ return 0; -+} -+ -+static int ov5693_enum_mbus_code(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_mbus_code_enum *code) -+{ -+ /* Only a single mbus format is supported */ -+ if (code->index > 0) -+ return -EINVAL; -+ -+ code->code = MEDIA_BUS_FMT_SBGGR10_1X10; -+ return 0; -+} -+ -+static int ov5693_enum_frame_size(struct v4l2_subdev *sd, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_frame_size_enum *fse) -+{ -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ struct v4l2_rect *__crop; -+ -+ if (fse->index > 1 || fse->code != MEDIA_BUS_FMT_SBGGR10_1X10) -+ return -EINVAL; -+ -+ __crop = __ov5693_get_pad_crop(ov5693, state, fse->pad, fse->which); -+ if (!__crop) -+ return -EINVAL; -+ -+ fse->min_width = __crop->width / (fse->index + 1); -+ fse->min_height = __crop->height / (fse->index + 1); -+ fse->max_width = fse->min_width; -+ fse->max_height = fse->min_height; -+ -+ return 0; -+} -+ -+static const struct v4l2_subdev_video_ops ov5693_video_ops = { -+ .s_stream = ov5693_s_stream, -+ .g_frame_interval = ov5693_g_frame_interval, -+}; -+ -+static const struct v4l2_subdev_pad_ops ov5693_pad_ops = { -+ .enum_mbus_code = ov5693_enum_mbus_code, -+ .enum_frame_size = ov5693_enum_frame_size, -+ .get_fmt = ov5693_get_fmt, -+ .set_fmt = ov5693_set_fmt, -+ .get_selection = ov5693_get_selection, -+ .set_selection = ov5693_set_selection, -+}; -+ -+static const struct v4l2_subdev_ops ov5693_ops = { -+ .video = &ov5693_video_ops, -+ .pad = &ov5693_pad_ops, -+}; -+ -+/* Sensor and Driver Configuration Functions */ -+ -+static int ov5693_init_controls(struct ov5693_device *ov5693) -+{ -+ const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; -+ struct v4l2_fwnode_device_properties props; -+ int vblank_max, vblank_def; -+ int exposure_max; -+ int hblank; -+ int ret; -+ -+ ret = v4l2_ctrl_handler_init(&ov5693->ctrls.handler, 12); -+ if (ret) -+ return ret; -+ -+ /* link freq */ -+ ov5693->ctrls.link_freq = v4l2_ctrl_new_int_menu(&ov5693->ctrls.handler, -+ NULL, V4L2_CID_LINK_FREQ, -+ 0, 0, link_freq_menu_items); -+ if (ov5693->ctrls.link_freq) -+ ov5693->ctrls.link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ -+ /* pixel rate */ -+ ov5693->ctrls.pixel_rate = v4l2_ctrl_new_std(&ov5693->ctrls.handler, NULL, -+ V4L2_CID_PIXEL_RATE, 0, -+ OV5693_PIXEL_RATE, 1, -+ OV5693_PIXEL_RATE); -+ -+ /* Exposure */ -+ exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN; -+ ov5693->ctrls.exposure = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_EXPOSURE, -+ OV5693_EXPOSURE_MIN, -+ exposure_max, -+ OV5693_EXPOSURE_STEP, -+ exposure_max); -+ -+ /* Gain */ -+ ov5693->ctrls.analogue_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, -+ ops, V4L2_CID_ANALOGUE_GAIN, -+ OV5693_GAIN_MIN, -+ OV5693_GAIN_MAX, -+ OV5693_GAIN_STEP, -+ OV5693_GAIN_DEF); -+ -+ ov5693->ctrls.digital_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_DIGITAL_GAIN, -+ OV5693_DIGITAL_GAIN_MIN, -+ OV5693_DIGITAL_GAIN_MAX, -+ OV5693_DIGITAL_GAIN_STEP, -+ OV5693_DIGITAL_GAIN_DEF); -+ -+ /* Flip */ -+ ov5693->ctrls.hflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_HFLIP, 0, 1, 1, 0); -+ -+ ov5693->ctrls.vflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_VFLIP, 0, 1, 1, 0); -+ -+ hblank = OV5693_FIXED_PPL - ov5693->mode.format.width; -+ ov5693->ctrls.hblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_HBLANK, hblank, -+ hblank, 1, hblank); -+ -+ if (ov5693->ctrls.hblank) -+ ov5693->ctrls.hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ -+ vblank_max = OV5693_TIMING_MAX_VTS - ov5693->mode.format.height; -+ vblank_def = ov5693->mode.vts - ov5693->mode.format.height; -+ ov5693->ctrls.vblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, -+ V4L2_CID_VBLANK, -+ OV5693_TIMING_MIN_VTS, -+ vblank_max, 1, vblank_def); -+ -+ ov5693->ctrls.test_pattern = v4l2_ctrl_new_std_menu_items( -+ &ov5693->ctrls.handler, ops, -+ V4L2_CID_TEST_PATTERN, -+ ARRAY_SIZE(ov5693_test_pattern_menu) - 1, -+ 0, 0, ov5693_test_pattern_menu); -+ -+ if (ov5693->ctrls.handler.error) { -+ dev_err(ov5693->dev, "Error initialising v4l2 ctrls\n"); -+ ret = ov5693->ctrls.handler.error; -+ goto err_free_handler; -+ } -+ -+ /* set properties from fwnode (e.g. rotation, orientation) */ -+ ret = v4l2_fwnode_device_parse(ov5693->dev, &props); -+ if (ret) -+ goto err_free_handler; -+ -+ ret = v4l2_ctrl_new_fwnode_properties(&ov5693->ctrls.handler, ops, -+ &props); -+ if (ret) -+ goto err_free_handler; -+ -+ /* Use same lock for controls as for everything else. */ -+ ov5693->ctrls.handler.lock = &ov5693->lock; -+ ov5693->sd.ctrl_handler = &ov5693->ctrls.handler; -+ -+ return 0; -+ -+err_free_handler: -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ return ret; -+} -+ -+static int ov5693_configure_gpios(struct ov5693_device *ov5693) -+{ -+ ov5693->reset = devm_gpiod_get_optional(ov5693->dev, "reset", -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->reset)) { -+ dev_err(ov5693->dev, "Error fetching reset GPIO\n"); -+ return PTR_ERR(ov5693->reset); -+ } -+ -+ ov5693->powerdown = devm_gpiod_get_optional(ov5693->dev, "powerdown", -+ GPIOD_OUT_HIGH); -+ if (IS_ERR(ov5693->powerdown)) { -+ dev_err(ov5693->dev, "Error fetching powerdown GPIO\n"); -+ return PTR_ERR(ov5693->powerdown); -+ } -+ -+ return 0; -+} -+ -+static int ov5693_get_regulators(struct ov5693_device *ov5693) -+{ -+ unsigned int i; -+ -+ for (i = 0; i < OV5693_NUM_SUPPLIES; i++) -+ ov5693->supplies[i].supply = ov5693_supply_names[i]; -+ -+ return devm_regulator_bulk_get(ov5693->dev, OV5693_NUM_SUPPLIES, -+ ov5693->supplies); -+} -+ -+static int ov5693_probe(struct i2c_client *client) -+{ -+ struct fwnode_handle *fwnode = dev_fwnode(&client->dev); -+ struct fwnode_handle *endpoint; -+ struct ov5693_device *ov5693; -+ u32 clk_rate; -+ int ret = 0; -+ -+ endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); -+ if (!endpoint && !IS_ERR_OR_NULL(fwnode->secondary)) -+ endpoint = fwnode_graph_get_next_endpoint(fwnode->secondary, NULL); -+ if (!endpoint) -+ return -EPROBE_DEFER; -+ -+ ov5693 = devm_kzalloc(&client->dev, sizeof(*ov5693), GFP_KERNEL); -+ if (!ov5693) -+ return -ENOMEM; -+ -+ ov5693->client = client; -+ ov5693->dev = &client->dev; -+ -+ mutex_init(&ov5693->lock); -+ -+ v4l2_i2c_subdev_init(&ov5693->sd, client, &ov5693_ops); -+ -+ ov5693->clk = devm_clk_get(&client->dev, "xvclk"); -+ if (IS_ERR(ov5693->clk)) { -+ dev_err(&client->dev, "Error getting clock\n"); -+ return PTR_ERR(ov5693->clk); -+ } -+ -+ clk_rate = clk_get_rate(ov5693->clk); -+ if (clk_rate != OV5693_XVCLK_FREQ) { -+ dev_err(&client->dev, "Unsupported clk freq %u, expected %u\n", -+ clk_rate, OV5693_XVCLK_FREQ); -+ return -EINVAL; -+ } -+ -+ ret = ov5693_configure_gpios(ov5693); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_get_regulators(ov5693); -+ if (ret) { -+ dev_err(&client->dev, "Error fetching regulators\n"); -+ return ret; -+ } -+ -+ ov5693->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; -+ ov5693->pad.flags = MEDIA_PAD_FL_SOURCE; -+ ov5693->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; -+ -+ ov5693->mode.crop = ov5693_default_crop; -+ ov5693->mode.format = ov5693_default_fmt; -+ ov5693->mode.vts = __ov5693_calc_vts(ov5693->mode.format.height); -+ -+ ret = ov5693_init_controls(ov5693); -+ if (ret) -+ return ret; -+ -+ ret = media_entity_pads_init(&ov5693->sd.entity, 1, &ov5693->pad); -+ if (ret) -+ goto err_ctrl_handler_free; -+ -+ /* -+ * We need the driver to work in the event that pm runtime is disable in -+ * the kernel, so power up and verify the chip now. In the event that -+ * runtime pm is disabled this will leave the chip on, so that streaming -+ * will work. -+ */ -+ -+ ret = ov5693_sensor_powerup(ov5693); -+ if (ret) -+ goto err_media_entity_cleanup; -+ -+ ret = ov5693_detect(ov5693); -+ if (ret) -+ goto err_powerdown; -+ -+ pm_runtime_set_active(&client->dev); -+ pm_runtime_get_noresume(&client->dev); -+ pm_runtime_enable(&client->dev); -+ -+ ret = v4l2_async_register_subdev_sensor(&ov5693->sd); -+ if (ret) { -+ dev_err(&client->dev, "failed to register V4L2 subdev: %d", -+ ret); -+ goto err_pm_runtime; -+ } -+ -+ pm_runtime_set_autosuspend_delay(&client->dev, 1000); -+ pm_runtime_use_autosuspend(&client->dev); -+ pm_runtime_put_autosuspend(&client->dev); -+ -+ return ret; -+ -+err_pm_runtime: -+ pm_runtime_disable(&client->dev); -+ pm_runtime_put_noidle(&client->dev); -+err_powerdown: -+ ov5693_sensor_powerdown(ov5693); -+err_media_entity_cleanup: -+ media_entity_cleanup(&ov5693->sd.entity); -+err_ctrl_handler_free: -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ -+ return ret; -+} -+ -+static int ov5693_remove(struct i2c_client *client) -+{ -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); -+ -+ v4l2_async_unregister_subdev(sd); -+ media_entity_cleanup(&ov5693->sd.entity); -+ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); -+ mutex_destroy(&ov5693->lock); -+ -+ /* -+ * Disable runtime PM. In case runtime PM is disabled in the kernel, -+ * make sure to turn power off manually. -+ */ -+ pm_runtime_disable(&client->dev); -+ if (!pm_runtime_status_suspended(&client->dev)) -+ ov5693_sensor_powerdown(ov5693); -+ pm_runtime_set_suspended(&client->dev); -+ -+ return 0; -+} -+ -+static const struct dev_pm_ops ov5693_pm_ops = { -+ SET_RUNTIME_PM_OPS(ov5693_sensor_suspend, ov5693_sensor_resume, NULL) -+}; -+ -+static const struct acpi_device_id ov5693_acpi_match[] = { -+ {"INT33BE"}, -+ {}, -+}; -+MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); -+ -+static struct i2c_driver ov5693_driver = { -+ .driver = { -+ .name = "ov5693", -+ .acpi_match_table = ov5693_acpi_match, -+ .pm = &ov5693_pm_ops, -+ }, -+ .probe_new = ov5693_probe, -+ .remove = ov5693_remove, -+}; -+module_i2c_driver(ov5693_driver); -+ -+MODULE_DESCRIPTION("A low-level driver for OmniVision 5693 sensors"); -+MODULE_LICENSE("GPL"); --- -2.35.1 - -From 2dfb5edab4b1eb1266634e5c936fcbc45b82118a Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 20 May 2021 23:31:04 +0100 -Subject: [PATCH] media: i2c: Fix vertical flip in ov5693 - -The pinkness experienced by users with rotated sensors in their laptops -was due to an incorrect setting for the vertical flip function; the -datasheet for the sensor gives the settings as bits 1&2 in one place and -bits 1&6 in another. - -Switch to flipping bit 6 instead of bit 2 for 0x3820 in the vertical -flip function to fix the pink hue. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov5693.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c -index 9499ee10f56c..c558f9b48c83 100644 ---- a/drivers/media/i2c/ov5693.c -+++ b/drivers/media/i2c/ov5693.c -@@ -133,7 +133,7 @@ - #define OV5693_SUB_INC_Y_REG 0x3815 - - #define OV5693_FORMAT1_REG 0x3820 --#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) -+#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(6) - #define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) - #define OV5693_FORMAT1_VBIN_EN BIT(0) - #define OV5693_FORMAT2_REG 0x3821 --- -2.35.1 - -From bcd317183156badc42bdb64701c35217cde13a79 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 9 Jul 2021 16:39:18 +0100 -Subject: [PATCH] media: i2c: Add ACPI support to ov8865 - -The ov8865 sensor is sometimes found on x86 platforms enumerated via ACPI. -Add an ACPI match table to the driver so that it's probed on those -platforms. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 92f6c3a940cf..15325df45c2b 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -9,6 +9,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -2950,6 +2951,12 @@ static const struct dev_pm_ops ov8865_pm_ops = { - SET_RUNTIME_PM_OPS(ov8865_suspend, ov8865_resume, NULL) - }; - -+static const struct acpi_device_id ov8865_acpi_match[] = { -+ {"INT347A"}, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, ov8865_acpi_match); -+ - static const struct of_device_id ov8865_of_match[] = { - { .compatible = "ovti,ov8865" }, - { } -@@ -2960,6 +2967,7 @@ static struct i2c_driver ov8865_driver = { - .driver = { - .name = "ov8865", - .of_match_table = ov8865_of_match, -+ .acpi_match_table = ov8865_acpi_match, - .pm = &ov8865_pm_ops, - }, - .probe_new = ov8865_probe, --- -2.35.1 - -From 6e0c4749bc35cfe6519d2343c900ccccaea803dd Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 21:20:17 +0100 -Subject: [PATCH] media: i2c: Fix incorrect value in comment - -The PLL configuration defined here sets 72MHz (which is correct), not -80MHz. Correct the comment. - -Reviewed-by: Paul Kocialkowski -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 15325df45c2b..8dcdf29be25e 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -713,7 +713,7 @@ static const struct ov8865_pll2_config ov8865_pll2_config_native = { - /* - * EXTCLK = 24 MHz - * DAC_CLK = 360 MHz -- * SCLK = 80 MHz -+ * SCLK = 72 MHz - */ - - static const struct ov8865_pll2_config ov8865_pll2_config_binning = { --- -2.35.1 - -From 223d62642bfdc447a0551d3959c98912263d4dca Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:21:52 +0100 -Subject: [PATCH] media: i2c: Defer probe if not endpoint found - -The ov8865 driver is one of those that can be connected to a CIO2 -device by the cio2-bridge code. This means that the absence of an -endpoint for this device is not necessarily fatal, as one might be -built by the cio2-bridge when it probes. Return -EPROBE_DEFER if no -endpoint is found rather than a fatal error. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 6 ++---- - 1 file changed, 2 insertions(+), 4 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 8dcdf29be25e..ceb9a93b043a 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2798,10 +2798,8 @@ static int ov8865_probe(struct i2c_client *client) - /* Graph Endpoint */ - - handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); -- if (!handle) { -- dev_err(dev, "unable to find endpoint node\n"); -- return -EINVAL; -- } -+ if (!handle) -+ return -EPROBE_DEFER; - - sensor->endpoint.bus_type = V4L2_MBUS_CSI2_DPHY; - --- -2.35.1 - -From e4f13264f7e7c91ef0baf87d3be44a244858e7be Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:00:25 +0100 -Subject: [PATCH] media: i2c: Support 19.2MHz input clock in ov8865 - -The ov8865 driver as written expects a 24MHz input clock, but the sensor -is sometimes found on x86 platforms with a 19.2MHz input clock supplied. -Add a set of PLL configurations to the driver to support that rate too. -As ACPI doesn't auto-configure the clock rate, check for a clock-frequency -during probe and set that rate if one is found. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 186 +++++++++++++++++++++++++++---------- - 1 file changed, 135 insertions(+), 51 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index ceb9a93b043a..9bac32efa7fa 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -21,10 +21,6 @@ - #include - #include - --/* Clock rate */ -- --#define OV8865_EXTCLK_RATE 24000000 -- - /* Register definitions */ - - /* System */ -@@ -567,6 +563,25 @@ struct ov8865_sclk_config { - unsigned int sclk_div; - }; - -+struct ov8865_pll_configs { -+ const struct ov8865_pll1_config *pll1_config; -+ const struct ov8865_pll2_config *pll2_config_native; -+ const struct ov8865_pll2_config *pll2_config_binning; -+}; -+ -+/* Clock rate */ -+ -+enum extclk_rate { -+ OV8865_19_2_MHZ, -+ OV8865_24_MHZ, -+ OV8865_NUM_SUPPORTED_RATES -+}; -+ -+static const unsigned long supported_extclk_rates[] = { -+ [OV8865_19_2_MHZ] = 19200000, -+ [OV8865_24_MHZ] = 24000000, -+}; -+ - /* - * General formulas for (array-centered) mode calculation: - * - photo_array_width = 3296 -@@ -635,9 +650,7 @@ struct ov8865_mode { - - struct v4l2_fract frame_interval; - -- const struct ov8865_pll1_config *pll1_config; -- const struct ov8865_pll2_config *pll2_config; -- const struct ov8865_sclk_config *sclk_config; -+ bool pll2_binning; - - const struct ov8865_register_value *register_values; - unsigned int register_values_count; -@@ -665,6 +678,9 @@ struct ov8865_sensor { - struct regulator *avdd; - struct regulator *dvdd; - struct regulator *dovdd; -+ -+ unsigned long extclk_rate; -+ const struct ov8865_pll_configs *pll_configs; - struct clk *extclk; - - struct v4l2_fwnode_endpoint endpoint; -@@ -680,43 +696,70 @@ struct ov8865_sensor { - /* Static definitions */ - - /* -- * EXTCLK = 24 MHz - * PHY_SCLK = 720 MHz - * MIPI_PCLK = 90 MHz - */ --static const struct ov8865_pll1_config ov8865_pll1_config_native = { -- .pll_pre_div_half = 1, -- .pll_pre_div = 0, -- .pll_mul = 30, -- .m_div = 1, -- .mipi_div = 3, -- .pclk_div = 1, -- .sys_pre_div = 1, -- .sys_div = 2, -+ -+static const struct ov8865_pll1_config ov8865_pll1_config_native_19_2mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 2, -+ .pll_mul = 75, -+ .m_div = 1, -+ .mipi_div = 3, -+ .pclk_div = 1, -+ .sys_pre_div = 1, -+ .sys_div = 2, -+}; -+ -+static const struct ov8865_pll1_config ov8865_pll1_config_native_24mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 0, -+ .pll_mul = 30, -+ .m_div = 1, -+ .mipi_div = 3, -+ .pclk_div = 1, -+ .sys_pre_div = 1, -+ .sys_div = 2, - }; - - /* -- * EXTCLK = 24 MHz - * DAC_CLK = 360 MHz - * SCLK = 144 MHz - */ - --static const struct ov8865_pll2_config ov8865_pll2_config_native = { -- .pll_pre_div_half = 1, -- .pll_pre_div = 0, -- .pll_mul = 30, -- .dac_div = 2, -- .sys_pre_div = 5, -- .sys_div = 0, -+static const struct ov8865_pll2_config ov8865_pll2_config_native_19_2mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 5, -+ .pll_mul = 75, -+ .dac_div = 1, -+ .sys_pre_div = 1, -+ .sys_div = 3, -+}; -+ -+static const struct ov8865_pll2_config ov8865_pll2_config_native_24mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 0, -+ .pll_mul = 30, -+ .dac_div = 2, -+ .sys_pre_div = 5, -+ .sys_div = 0, - }; - - /* -- * EXTCLK = 24 MHz - * DAC_CLK = 360 MHz - * SCLK = 72 MHz - */ - --static const struct ov8865_pll2_config ov8865_pll2_config_binning = { -+static const struct ov8865_pll2_config ov8865_pll2_config_binning_19_2mhz = { -+ .pll_pre_div_half = 1, -+ .pll_pre_div = 2, -+ .pll_mul = 75, -+ .dac_div = 2, -+ .sys_pre_div = 10, -+ .sys_div = 0, -+}; -+ -+static const struct ov8865_pll2_config ov8865_pll2_config_binning_24mhz = { - .pll_pre_div_half = 1, - .pll_pre_div = 0, - .pll_mul = 30, -@@ -725,6 +768,23 @@ static const struct ov8865_pll2_config ov8865_pll2_config_binning = { - .sys_div = 0, - }; - -+static struct ov8865_pll_configs ov8865_pll_configs_19_2mhz = { -+ .pll1_config = &ov8865_pll1_config_native_19_2mhz, -+ .pll2_config_native = &ov8865_pll2_config_native_19_2mhz, -+ .pll2_config_binning = &ov8865_pll2_config_binning_19_2mhz, -+}; -+ -+static struct ov8865_pll_configs ov8865_pll_configs_24mhz = { -+ .pll1_config = &ov8865_pll1_config_native_24mhz, -+ .pll2_config_native = &ov8865_pll2_config_native_24mhz, -+ .pll2_config_binning = &ov8865_pll2_config_binning_24mhz, -+}; -+ -+static const struct ov8865_pll_configs *ov8865_pll_configs[] = { -+ &ov8865_pll_configs_19_2mhz, -+ &ov8865_pll_configs_24mhz, -+}; -+ - static const struct ov8865_sclk_config ov8865_sclk_config_native = { - .sys_sel = 1, - .sclk_sel = 0, -@@ -934,9 +994,7 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 30 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_native, -- .sclk_config = &ov8865_sclk_config_native, -+ .pll2_binning = false, - - /* Registers */ - .register_values = ov8865_register_values_native, -@@ -990,9 +1048,7 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 30 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_native, -- .sclk_config = &ov8865_sclk_config_native, -+ .pll2_binning = false, - - /* Registers */ - .register_values = ov8865_register_values_native, -@@ -1050,9 +1106,7 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 30 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_binning, -- .sclk_config = &ov8865_sclk_config_native, -+ .pll2_binning = true, - - /* Registers */ - .register_values = ov8865_register_values_binning, -@@ -1116,9 +1170,7 @@ static const struct ov8865_mode ov8865_modes[] = { - .frame_interval = { 1, 90 }, - - /* PLL */ -- .pll1_config = &ov8865_pll1_config_native, -- .pll2_config = &ov8865_pll2_config_binning, -- .sclk_config = &ov8865_sclk_config_native, -+ .pll2_binning = true, - - /* Registers */ - .register_values = ov8865_register_values_binning, -@@ -1513,12 +1565,11 @@ static int ov8865_isp_configure(struct ov8865_sensor *sensor) - static unsigned long ov8865_mode_pll1_rate(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_pll1_config *config = mode->pll1_config; -- unsigned long extclk_rate; -+ const struct ov8865_pll1_config *config; - unsigned long pll1_rate; - -- extclk_rate = clk_get_rate(sensor->extclk); -- pll1_rate = extclk_rate * config->pll_mul / config->pll_pre_div_half; -+ config = sensor->pll_configs->pll1_config; -+ pll1_rate = sensor->extclk_rate * config->pll_mul / config->pll_pre_div_half; - - switch (config->pll_pre_div) { - case 0: -@@ -1552,10 +1603,12 @@ static int ov8865_mode_pll1_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode, - u32 mbus_code) - { -- const struct ov8865_pll1_config *config = mode->pll1_config; -+ const struct ov8865_pll1_config *config; - u8 value; - int ret; - -+ config = sensor->pll_configs->pll1_config; -+ - switch (mbus_code) { - case MEDIA_BUS_FMT_SBGGR10_1X10: - value = OV8865_MIPI_BIT_SEL(10); -@@ -1622,9 +1675,12 @@ static int ov8865_mode_pll1_configure(struct ov8865_sensor *sensor, - static int ov8865_mode_pll2_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_pll2_config *config = mode->pll2_config; -+ const struct ov8865_pll2_config *config; - int ret; - -+ config = mode->pll2_binning ? sensor->pll_configs->pll2_config_binning : -+ sensor->pll_configs->pll2_config_native; -+ - ret = ov8865_write(sensor, OV8865_PLL_CTRL12_REG, - OV8865_PLL_CTRL12_PRE_DIV_HALF(config->pll_pre_div_half) | - OV8865_PLL_CTRL12_DAC_DIV(config->dac_div)); -@@ -1658,7 +1714,7 @@ static int ov8865_mode_pll2_configure(struct ov8865_sensor *sensor, - static int ov8865_mode_sclk_configure(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_sclk_config *config = mode->sclk_config; -+ const struct ov8865_sclk_config *config = &ov8865_sclk_config_native; - int ret; - - ret = ov8865_write(sensor, OV8865_CLK_SEL0_REG, -@@ -2053,9 +2109,11 @@ static int ov8865_mode_configure(struct ov8865_sensor *sensor, - static unsigned long ov8865_mode_mipi_clk_rate(struct ov8865_sensor *sensor, - const struct ov8865_mode *mode) - { -- const struct ov8865_pll1_config *config = mode->pll1_config; -+ const struct ov8865_pll1_config *config; - unsigned long pll1_rate; - -+ config = sensor->pll_configs->pll1_config; -+ - pll1_rate = ov8865_mode_pll1_rate(sensor, mode); - - return pll1_rate / config->m_div / 2; -@@ -2785,7 +2843,8 @@ static int ov8865_probe(struct i2c_client *client) - struct ov8865_sensor *sensor; - struct v4l2_subdev *subdev; - struct media_pad *pad; -- unsigned long rate; -+ unsigned int rate; -+ unsigned int i; - int ret; - - sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); -@@ -2860,13 +2919,38 @@ static int ov8865_probe(struct i2c_client *client) - goto error_endpoint; - } - -- rate = clk_get_rate(sensor->extclk); -- if (rate != OV8865_EXTCLK_RATE) { -- dev_err(dev, "clock rate %lu Hz is unsupported\n", rate); -+ /* -+ * We could have either a 24MHz or 19.2MHz clock rate. Check for a -+ * clock-frequency property and if found, set that rate. This should -+ * cover the ACPI case. If the system uses devicetree then the -+ * configured rate should already be set, so we'll have to check it. -+ */ -+ ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", -+ &rate); -+ if (!ret) { -+ ret = clk_set_rate(sensor->extclk, rate); -+ if (ret) { -+ dev_err(dev, "failed to set clock rate\n"); -+ return ret; -+ } -+ } -+ -+ sensor->extclk_rate = clk_get_rate(sensor->extclk); -+ -+ for (i = 0; i < ARRAY_SIZE(supported_extclk_rates); i++) { -+ if (sensor->extclk_rate == supported_extclk_rates[i]) -+ break; -+ } -+ -+ if (i == ARRAY_SIZE(supported_extclk_rates)) { -+ dev_err(dev, "clock rate %lu Hz is unsupported\n", -+ sensor->extclk_rate); - ret = -EINVAL; - goto error_endpoint; - } - -+ sensor->pll_configs = ov8865_pll_configs[i]; -+ - /* Subdev, entity and pad */ - - subdev = &sensor->subdev; --- -2.35.1 - -From ec4fb28d4b75ed9cde1a7a13671d807797ed3b29 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:19:10 +0100 -Subject: [PATCH] media: i2c: Add .get_selection() support to ov8865 - -The ov8865 driver's v4l2_subdev_pad_ops currently does not include -.get_selection() - add support for that callback. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 64 ++++++++++++++++++++++++++++++++++++++ - 1 file changed, 64 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 9bac32efa7fa..d41ce6b5af55 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -450,6 +450,15 @@ - #define OV8865_PRE_CTRL0_PATTERN_COLOR_SQUARES 2 - #define OV8865_PRE_CTRL0_PATTERN_BLACK 3 - -+/* Pixel Array */ -+ -+#define OV8865_NATIVE_WIDTH 3296 -+#define OV8865_NATIVE_HEIGHT 2528 -+#define OV8865_ACTIVE_START_TOP 32 -+#define OV8865_ACTIVE_START_LEFT 80 -+#define OV8865_ACTIVE_WIDTH 3264 -+#define OV8865_ACTIVE_HEIGHT 2448 -+ - /* Macros */ - - #define ov8865_subdev_sensor(s) \ -@@ -2758,12 +2767,67 @@ static int ov8865_enum_frame_interval(struct v4l2_subdev *subdev, - return 0; - } - -+static void -+__ov8865_get_pad_crop(struct ov8865_sensor *sensor, -+ struct v4l2_subdev_state *state, unsigned int pad, -+ enum v4l2_subdev_format_whence which, struct v4l2_rect *r) -+{ -+ const struct ov8865_mode *mode = sensor->state.mode; -+ -+ switch (which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ *r = *v4l2_subdev_get_try_crop(&sensor->subdev, state, pad); -+ break; -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ r->height = mode->output_size_y; -+ r->width = mode->output_size_x; -+ r->top = (OV8865_NATIVE_HEIGHT - mode->output_size_y) / 2; -+ r->left = (OV8865_NATIVE_WIDTH - mode->output_size_x) / 2; -+ break; -+ } -+} -+ -+static int ov8865_get_selection(struct v4l2_subdev *subdev, -+ struct v4l2_subdev_state *state, -+ struct v4l2_subdev_selection *sel) -+{ -+ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); -+ -+ switch (sel->target) { -+ case V4L2_SEL_TGT_CROP: -+ mutex_lock(&sensor->mutex); -+ __ov8865_get_pad_crop(sensor, state, sel->pad, -+ sel->which, &sel->r); -+ mutex_unlock(&sensor->mutex); -+ break; -+ case V4L2_SEL_TGT_NATIVE_SIZE: -+ sel->r.top = 0; -+ sel->r.left = 0; -+ sel->r.width = OV8865_NATIVE_WIDTH; -+ sel->r.height = OV8865_NATIVE_HEIGHT; -+ break; -+ case V4L2_SEL_TGT_CROP_BOUNDS: -+ case V4L2_SEL_TGT_CROP_DEFAULT: -+ sel->r.top = OV8865_ACTIVE_START_TOP; -+ sel->r.left = OV8865_ACTIVE_START_LEFT; -+ sel->r.width = OV8865_ACTIVE_WIDTH; -+ sel->r.height = OV8865_ACTIVE_HEIGHT; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ - static const struct v4l2_subdev_pad_ops ov8865_subdev_pad_ops = { - .enum_mbus_code = ov8865_enum_mbus_code, - .get_fmt = ov8865_get_fmt, - .set_fmt = ov8865_set_fmt, - .enum_frame_size = ov8865_enum_frame_size, - .enum_frame_interval = ov8865_enum_frame_interval, -+ .get_selection = ov8865_get_selection, -+ .set_selection = ov8865_get_selection, - }; - - static const struct v4l2_subdev_ops ov8865_subdev_ops = { --- -2.35.1 - -From 62c2d0b223798ec53f80a7311ffabaa3564491bf Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sat, 10 Jul 2021 22:34:43 +0100 -Subject: [PATCH] media: i2c: Switch control to V4L2_CID_ANALOGUE_GAIN - -The V4L2_CID_GAIN control for this driver configures registers that -the datasheet specifies as analogue gain. Switch the control's ID -to V4L2_CID_ANALOGUE_GAIN. - -Reviewed-by: Paul Kocialkowski -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 9 +++++---- - 1 file changed, 5 insertions(+), 4 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index d41ce6b5af55..07f34f3ae5ec 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2150,7 +2150,7 @@ static int ov8865_exposure_configure(struct ov8865_sensor *sensor, u32 exposure) - - /* Gain */ - --static int ov8865_gain_configure(struct ov8865_sensor *sensor, u32 gain) -+static int ov8865_analog_gain_configure(struct ov8865_sensor *sensor, u32 gain) - { - int ret; - -@@ -2462,8 +2462,8 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - if (ret) - return ret; - break; -- case V4L2_CID_GAIN: -- ret = ov8865_gain_configure(sensor, ctrl->val); -+ case V4L2_CID_ANALOGUE_GAIN: -+ ret = ov8865_analog_gain_configure(sensor, ctrl->val); - if (ret) - return ret; - break; -@@ -2508,7 +2508,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Gain */ - -- v4l2_ctrl_new_std(handler, ops, V4L2_CID_GAIN, 128, 8191, 128, 128); -+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_ANALOGUE_GAIN, 128, 8191, 128, -+ 128); - - /* White Balance */ - --- -2.35.1 - -From e677005e1d45907454dfdd12b2ec2fa8160f92ab Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 12 Jul 2021 22:54:56 +0100 -Subject: [PATCH] media: i2c: Add vblank control to ov8865 - -Add a V4L2_CID_VBLANK control to the ov8865 driver. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 34 ++++++++++++++++++++++++++++++++++ - 1 file changed, 34 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 07f34f3ae5ec..95c1b97eb89a 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -183,6 +183,8 @@ - #define OV8865_VTS_H(v) (((v) & GENMASK(11, 8)) >> 8) - #define OV8865_VTS_L_REG 0x380f - #define OV8865_VTS_L(v) ((v) & GENMASK(7, 0)) -+#define OV8865_TIMING_MAX_VTS 0xffff -+#define OV8865_TIMING_MIN_VTS 0x04 - #define OV8865_OFFSET_X_H_REG 0x3810 - #define OV8865_OFFSET_X_H(v) (((v) & GENMASK(15, 8)) >> 8) - #define OV8865_OFFSET_X_L_REG 0x3811 -@@ -675,6 +677,7 @@ struct ov8865_state { - struct ov8865_ctrls { - struct v4l2_ctrl *link_freq; - struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *vblank; - - struct v4l2_ctrl_handler handler; - }; -@@ -2225,6 +2228,20 @@ static int ov8865_test_pattern_configure(struct ov8865_sensor *sensor, - ov8865_test_pattern_bits[index]); - } - -+/* Blanking */ -+ -+static int ov8865_vts_configure(struct ov8865_sensor *sensor, u32 vblank) -+{ -+ u16 vts = sensor->state.mode->output_size_y + vblank; -+ int ret; -+ -+ ret = ov8865_write(sensor, OV8865_VTS_H_REG, OV8865_VTS_H(vts)); -+ if (ret) -+ return ret; -+ -+ return ov8865_write(sensor, OV8865_VTS_L_REG, OV8865_VTS_L(vts)); -+} -+ - /* State */ - - static int ov8865_state_mipi_configure(struct ov8865_sensor *sensor, -@@ -2478,6 +2495,8 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - case V4L2_CID_TEST_PATTERN: - index = (unsigned int)ctrl->val; - return ov8865_test_pattern_configure(sensor, index); -+ case V4L2_CID_VBLANK: -+ return ov8865_vts_configure(sensor, ctrl->val); - default: - return -EINVAL; - } -@@ -2494,6 +2513,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - struct ov8865_ctrls *ctrls = &sensor->ctrls; - struct v4l2_ctrl_handler *handler = &ctrls->handler; - const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; -+ const struct ov8865_mode *mode = sensor->state.mode; -+ unsigned int vblank_max, vblank_def; - int ret; - - v4l2_ctrl_handler_init(handler, 32); -@@ -2530,6 +2551,13 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - ARRAY_SIZE(ov8865_test_pattern_menu) - 1, - 0, 0, ov8865_test_pattern_menu); - -+ /* Blanking */ -+ vblank_max = OV8865_TIMING_MAX_VTS - mode->output_size_y; -+ vblank_def = mode->vts - mode->output_size_y; -+ ctrls->vblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_VBLANK, -+ OV8865_TIMING_MIN_VTS, vblank_max, 1, -+ vblank_def); -+ - /* MIPI CSI-2 */ - - ctrls->link_freq = -@@ -2710,6 +2738,10 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - sensor->state.mbus_code != mbus_code) - ret = ov8865_state_configure(sensor, mode, mbus_code); - -+ __v4l2_ctrl_modify_range(sensor->ctrls.vblank, OV8865_TIMING_MIN_VTS, -+ OV8865_TIMING_MAX_VTS - mode->output_size_y, -+ 1, mode->vts - mode->output_size_y); -+ - complete: - mutex_unlock(&sensor->mutex); - -@@ -3037,6 +3069,8 @@ static int ov8865_probe(struct i2c_client *client) - - /* Sensor */ - -+ sensor->state.mode = &ov8865_modes[0]; -+ - ret = ov8865_ctrls_init(sensor); - if (ret) - goto error_mutex; --- -2.35.1 - -From e03db43d2992c707235179319ba3adfe3da4441e Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 13 Jul 2021 23:40:33 +0100 -Subject: [PATCH] media: i2c: Add hblank control to ov8865 - -Add a V4L2_CID_HBLANK control to the ov8865 driver. This is read only -with timing control intended to be done via vblanking alone. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 14 ++++++++++++++ - 1 file changed, 14 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 95c1b97eb89a..85a76aea67a5 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -677,6 +677,7 @@ struct ov8865_state { - struct ov8865_ctrls { - struct v4l2_ctrl *link_freq; - struct v4l2_ctrl *pixel_rate; -+ struct v4l2_ctrl *hblank; - struct v4l2_ctrl *vblank; - - struct v4l2_ctrl_handler handler; -@@ -2515,6 +2516,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; - const struct ov8865_mode *mode = sensor->state.mode; - unsigned int vblank_max, vblank_def; -+ unsigned int hblank; - int ret; - - v4l2_ctrl_handler_init(handler, 32); -@@ -2552,6 +2554,13 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - 0, 0, ov8865_test_pattern_menu); - - /* Blanking */ -+ hblank = mode->hts - mode->output_size_x; -+ ctrls->hblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_HBLANK, hblank, -+ hblank, 1, hblank); -+ -+ if (ctrls->hblank) -+ ctrls->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; -+ - vblank_max = OV8865_TIMING_MAX_VTS - mode->output_size_y; - vblank_def = mode->vts - mode->output_size_y; - ctrls->vblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_VBLANK, -@@ -2698,6 +2707,7 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - struct v4l2_mbus_framefmt *mbus_format = &format->format; - const struct ov8865_mode *mode; - u32 mbus_code = 0; -+ unsigned int hblank; - unsigned int index; - int ret = 0; - -@@ -2742,6 +2752,10 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - OV8865_TIMING_MAX_VTS - mode->output_size_y, - 1, mode->vts - mode->output_size_y); - -+ hblank = mode->hts - mode->output_size_x; -+ __v4l2_ctrl_modify_range(sensor->ctrls.hblank, hblank, hblank, 1, -+ hblank); -+ - complete: - mutex_unlock(&sensor->mutex); - --- -2.35.1 - -From b103821c396fd02ddbec7f58ca6af55a65725883 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 20 Oct 2021 22:43:54 +0100 -Subject: [PATCH] media: i2c: Update HTS values in ov8865 - -The HTS values for some of the modes in the ov8865 driver are a bit -unusual, coming in lower than the output_size_x is set to. It seems -like they might be calculated to fit the desired framerate into a -configuration with just two data lanes. To bring this more in line -with expected behaviour, raise the HTS values above the output_size_x. - -The corollary of that change is that the hardcoded frame intervals -against the modes no longer make sense, so remove those entirely. -Update the .g/s_frame_interval() callbacks to calculate the frame -interval based on the current mode and the vblank and hblank settings -plus the number of data lanes detected from firmware. - -The implementation of the .enum_frame_interval() callback is no longer -suitable since the possible frame rate is now a continuous range depending -on the vblank control setting, so remove that callback entirely. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 65 +++++++------------------------------- - 1 file changed, 11 insertions(+), 54 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 85a76aea67a5..7f5b0c48eac4 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -659,8 +659,6 @@ struct ov8865_mode { - unsigned int blc_anchor_right_start; - unsigned int blc_anchor_right_end; - -- struct v4l2_fract frame_interval; -- - bool pll2_binning; - - const struct ov8865_register_value *register_values; -@@ -964,7 +962,7 @@ static const struct ov8865_mode ov8865_modes[] = { - { - /* Horizontal */ - .output_size_x = 3264, -- .hts = 1944, -+ .hts = 3888, - - /* Vertical */ - .output_size_y = 2448, -@@ -1003,9 +1001,6 @@ static const struct ov8865_mode ov8865_modes[] = { - .blc_anchor_right_start = 1984, - .blc_anchor_right_end = 2239, - -- /* Frame Interval */ -- .frame_interval = { 1, 30 }, -- - /* PLL */ - .pll2_binning = false, - -@@ -1018,11 +1013,11 @@ static const struct ov8865_mode ov8865_modes[] = { - { - /* Horizontal */ - .output_size_x = 3264, -- .hts = 2582, -+ .hts = 3888, - - /* Vertical */ - .output_size_y = 1836, -- .vts = 2002, -+ .vts = 2470, - - .size_auto = true, - .size_auto_boundary_x = 8, -@@ -1057,9 +1052,6 @@ static const struct ov8865_mode ov8865_modes[] = { - .blc_anchor_right_start = 1984, - .blc_anchor_right_end = 2239, - -- /* Frame Interval */ -- .frame_interval = { 1, 30 }, -- - /* PLL */ - .pll2_binning = false, - -@@ -1115,9 +1107,6 @@ static const struct ov8865_mode ov8865_modes[] = { - .blc_anchor_right_start = 992, - .blc_anchor_right_end = 1119, - -- /* Frame Interval */ -- .frame_interval = { 1, 30 }, -- - /* PLL */ - .pll2_binning = true, - -@@ -1179,9 +1168,6 @@ static const struct ov8865_mode ov8865_modes[] = { - .blc_anchor_right_start = 992, - .blc_anchor_right_end = 1119, - -- /* Frame Interval */ -- .frame_interval = { 1, 90 }, -- - /* PLL */ - .pll2_binning = true, - -@@ -2630,11 +2616,18 @@ static int ov8865_g_frame_interval(struct v4l2_subdev *subdev, - { - struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); - const struct ov8865_mode *mode; -+ unsigned int framesize; -+ unsigned int fps; - - mutex_lock(&sensor->mutex); - - mode = sensor->state.mode; -- interval->interval = mode->frame_interval; -+ framesize = mode->hts * (mode->output_size_y + -+ sensor->ctrls.vblank->val); -+ fps = DIV_ROUND_CLOSEST(sensor->ctrls.pixel_rate->val, framesize); -+ -+ interval->interval.numerator = 1; -+ interval->interval.denominator = fps; - - mutex_unlock(&sensor->mutex); - -@@ -2779,41 +2772,6 @@ static int ov8865_enum_frame_size(struct v4l2_subdev *subdev, - return 0; - } - --static int ov8865_enum_frame_interval(struct v4l2_subdev *subdev, -- struct v4l2_subdev_state *sd_state, -- struct v4l2_subdev_frame_interval_enum *interval_enum) --{ -- const struct ov8865_mode *mode = NULL; -- unsigned int mode_index; -- unsigned int interval_index; -- -- if (interval_enum->index > 0) -- return -EINVAL; -- /* -- * Multiple modes with the same dimensions may have different frame -- * intervals, so look up each relevant mode. -- */ -- for (mode_index = 0, interval_index = 0; -- mode_index < ARRAY_SIZE(ov8865_modes); mode_index++) { -- mode = &ov8865_modes[mode_index]; -- -- if (mode->output_size_x == interval_enum->width && -- mode->output_size_y == interval_enum->height) { -- if (interval_index == interval_enum->index) -- break; -- -- interval_index++; -- } -- } -- -- if (mode_index == ARRAY_SIZE(ov8865_modes)) -- return -EINVAL; -- -- interval_enum->interval = mode->frame_interval; -- -- return 0; --} -- - static void - __ov8865_get_pad_crop(struct ov8865_sensor *sensor, - struct v4l2_subdev_state *state, unsigned int pad, -@@ -2872,7 +2830,6 @@ static const struct v4l2_subdev_pad_ops ov8865_subdev_pad_ops = { - .get_fmt = ov8865_get_fmt, - .set_fmt = ov8865_set_fmt, - .enum_frame_size = ov8865_enum_frame_size, -- .enum_frame_interval = ov8865_enum_frame_interval, - .get_selection = ov8865_get_selection, - .set_selection = ov8865_get_selection, - }; --- -2.35.1 - -From cafb393c61f5ce95318c4c87e042f80b25f0d21d Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 13 Jul 2021 23:43:17 +0100 -Subject: [PATCH] media: i2c: cap exposure at height + vblank in ov8865 - -Exposure limits depend on the total height; when vblank is altered (and -thus the total height is altered), change the exposure limits to reflect -the new cap. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 26 ++++++++++++++++++++++++-- - 1 file changed, 24 insertions(+), 2 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 7f5b0c48eac4..d867676bf77e 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -677,6 +677,7 @@ struct ov8865_ctrls { - struct v4l2_ctrl *pixel_rate; - struct v4l2_ctrl *hblank; - struct v4l2_ctrl *vblank; -+ struct v4l2_ctrl *exposure; - - struct v4l2_ctrl_handler handler; - }; -@@ -2456,6 +2457,19 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - unsigned int index; - int ret; - -+ /* If VBLANK is altered we need to update exposure to compensate */ -+ if (ctrl->id == V4L2_CID_VBLANK) { -+ int exposure_max; -+ -+ exposure_max = sensor->state.mode->output_size_y + ctrl->val; -+ __v4l2_ctrl_modify_range(sensor->ctrls.exposure, -+ sensor->ctrls.exposure->minimum, -+ exposure_max, -+ sensor->ctrls.exposure->step, -+ min(sensor->ctrls.exposure->val, -+ exposure_max)); -+ } -+ - /* Wait for the sensor to be on before setting controls. */ - if (pm_runtime_suspended(sensor->dev)) - return 0; -@@ -2512,8 +2526,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Exposure */ - -- v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, 1048575, 16, -- 512); -+ ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, -+ 1048575, 16, 512); - - /* Gain */ - -@@ -2702,6 +2716,7 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - u32 mbus_code = 0; - unsigned int hblank; - unsigned int index; -+ int exposure_max; - int ret = 0; - - mutex_lock(&sensor->mutex); -@@ -2749,6 +2764,13 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, - __v4l2_ctrl_modify_range(sensor->ctrls.hblank, hblank, hblank, 1, - hblank); - -+ exposure_max = mode->vts; -+ __v4l2_ctrl_modify_range(sensor->ctrls.exposure, -+ sensor->ctrls.exposure->minimum, exposure_max, -+ sensor->ctrls.exposure->step, -+ min(sensor->ctrls.exposure->val, -+ exposure_max)); -+ - complete: - mutex_unlock(&sensor->mutex); - --- -2.35.1 - -From 5039c9f43f0c57244e178fe8ba81db898c7e3640 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 16 Jul 2021 22:56:15 +0100 -Subject: [PATCH] media: i2c: Add controls from fwnode to ov8865 - -Add V4L2_CID_CAMERA_ORIENTATION and V4L2_CID_CAMERA_SENSOR_ROTATION -controls to the ov8865 driver by attempting to parse them from firmware. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 10 ++++++++++ - 1 file changed, 10 insertions(+) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index d867676bf77e..d0303016e7b4 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2515,6 +2515,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - struct v4l2_ctrl_handler *handler = &ctrls->handler; - const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; - const struct ov8865_mode *mode = sensor->state.mode; -+ struct v4l2_fwnode_device_properties props; - unsigned int vblank_max, vblank_def; - unsigned int hblank; - int ret; -@@ -2578,6 +2579,15 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 1, - INT_MAX, 1, 1); - -+ /* set properties from fwnode (e.g. rotation, orientation) */ -+ ret = v4l2_fwnode_device_parse(sensor->dev, &props); -+ if (ret) -+ goto error_ctrls; -+ -+ ret = v4l2_ctrl_new_fwnode_properties(handler, ops, &props); -+ if (ret) -+ goto error_ctrls; -+ - if (handler->error) { - ret = handler->error; - goto error_ctrls; --- -2.35.1 - -From 1c96c757338cfa78250989826e32ba622aeb3848 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 16 Jul 2021 00:00:54 +0100 -Subject: [PATCH] media: i2c: Switch exposure control unit to lines - -The ov8865 driver currently has the unit of the V4L2_CID_EXPOSURE control -as 1/16th of a line. This is what the sensor expects, but isn't very -intuitive. Switch the control to be in units of a line and simply do the -16x multiplication before passing the value to the sensor. - -The datasheet for this sensor gives minimum exposure as 2 lines, so take -the opportunity to correct the lower bounds of the control. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 7 +++++-- - 1 file changed, 5 insertions(+), 2 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index d0303016e7b4..a638e53bb069 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2125,6 +2125,9 @@ static int ov8865_exposure_configure(struct ov8865_sensor *sensor, u32 exposure) - { - int ret; - -+ /* The sensor stores exposure in units of 1/16th of a line */ -+ exposure *= 16; -+ - ret = ov8865_write(sensor, OV8865_EXPOSURE_CTRL_HH_REG, - OV8865_EXPOSURE_CTRL_HH(exposure)); - if (ret) -@@ -2527,8 +2530,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Exposure */ - -- ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, -- 1048575, 16, 512); -+ ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 2, -+ 65535, 1, 32); - - /* Gain */ - --- -2.35.1 - -From c8f335a4e68e5d2197f3aee325fea582dc3d39b4 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Tue, 24 Aug 2021 23:17:39 +0100 -Subject: [PATCH] media: i2c: Use dev_err_probe() in ov8865 - -There is a chance that regulator_get() returns -EPROBE_DEFER, in which -case printing an error message is undesirable. To avoid spurious messages -in dmesg in the event that -EPROBE_DEFER is returned, use dev_err_probe() -on error paths for regulator_get(). - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 46 +++++++++++++++++--------------------- - 1 file changed, 20 insertions(+), 26 deletions(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index a638e53bb069..5ef9c407362a 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2957,6 +2957,26 @@ static int ov8865_probe(struct i2c_client *client) - sensor->dev = dev; - sensor->i2c_client = client; - -+ /* Regulators */ -+ -+ /* DVDD: digital core */ -+ sensor->dvdd = devm_regulator_get(dev, "dvdd"); -+ if (IS_ERR(sensor->dvdd)) -+ return dev_err_probe(dev, PTR_ERR(sensor->dvdd), -+ "cannot get DVDD regulator\n"); -+ -+ /* DOVDD: digital I/O */ -+ sensor->dovdd = devm_regulator_get(dev, "dovdd"); -+ if (IS_ERR(sensor->dovdd)) -+ return dev_err_probe(dev, PTR_ERR(sensor->dovdd), -+ "cannot get DOVDD regulator\n"); -+ -+ /* AVDD: analog */ -+ sensor->avdd = devm_regulator_get(dev, "avdd"); -+ if (IS_ERR(sensor->avdd)) -+ return dev_err_probe(dev, PTR_ERR(sensor->avdd), -+ "cannot get AVDD regulator\n"); -+ - /* Graph Endpoint */ - - handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); -@@ -2987,32 +3007,6 @@ static int ov8865_probe(struct i2c_client *client) - goto error_endpoint; - } - -- /* Regulators */ -- -- /* DVDD: digital core */ -- sensor->dvdd = devm_regulator_get(dev, "dvdd"); -- if (IS_ERR(sensor->dvdd)) { -- dev_err(dev, "cannot get DVDD (digital core) regulator\n"); -- ret = PTR_ERR(sensor->dvdd); -- goto error_endpoint; -- } -- -- /* DOVDD: digital I/O */ -- sensor->dovdd = devm_regulator_get(dev, "dovdd"); -- if (IS_ERR(sensor->dovdd)) { -- dev_err(dev, "cannot get DOVDD (digital I/O) regulator\n"); -- ret = PTR_ERR(sensor->dovdd); -- goto error_endpoint; -- } -- -- /* AVDD: analog */ -- sensor->avdd = devm_regulator_get(dev, "avdd"); -- if (IS_ERR(sensor->avdd)) { -- dev_err(dev, "cannot get AVDD (analog) regulator\n"); -- ret = PTR_ERR(sensor->avdd); -- goto error_endpoint; -- } -- - /* External Clock */ - - sensor->extclk = devm_clk_get(dev, NULL); --- -2.35.1 - -From d6ac836da9c5181634c9d7f317d199cd81ccc085 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 14 Jul 2021 00:05:04 +0100 -Subject: [PATCH] media: ipu3-cio2: Add INT347A to cio2-bridge - -ACPI _HID INT347A represents the OV8865 sensor, the driver for which can -support the platforms that the cio2-bridge serves. Add it to the array -of supported sensors so the bridge will connect the sensor to the CIO2 -device. - -Reviewed-by: Andy Shevchenko -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/cio2-bridge.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c -index 0b586b4e537e..4550be801311 100644 ---- a/drivers/media/pci/intel/ipu3/cio2-bridge.c -+++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c -@@ -22,6 +22,8 @@ - static const struct cio2_sensor_config cio2_supported_sensors[] = { - /* Omnivision OV5693 */ - CIO2_SENSOR_CONFIG("INT33BE", 0), -+ /* Omnivision OV8865 */ -+ CIO2_SENSOR_CONFIG("INT347A", 1, 360000000), - /* Omnivision OV2680 */ - CIO2_SENSOR_CONFIG("OVTI2680", 0), - }; --- -2.35.1 - -From dd52bc8c51c21d44afdd51239e6fba04bae259be Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:56:57 +0200 -Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an - INT3472 device - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around cases where this info is not present in the firmware tables, -which is often the case on x86/ACPI devices, both frameworks allow the -provider-driver to attach info about consumers to the clks/regulators -when registering these. - -This causes problems with the probe ordering wrt drivers for consumers -of these clks/regulators. Since the lookups are only registered when the -provider-driver binds, trying to get these clks/regulators before then -results in a -ENOENT error for clks and a dummy regulator for regulators. - -One case where we hit this issue is camera sensors such as e.g. the OV8865 -sensor found on the Microsoft Surface Go. The sensor uses clks, regulators -and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 -ACPI device. There is special platform code handling this and setting -platform_data with the necessary consumer info on the MFD cells -instantiated for the PMIC under: drivers/platform/x86/intel/int3472. - -For this to work properly the ov8865 driver must not bind to the I2C-client -for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and -clk MFD cells have all been fully setup. - -The OV8865 on the Microsoft Surface Go is just one example, all X86 -devices using the Intel IPU3 camera block found on recent Intel SoCs -have similar issues where there is an INT3472 HID ACPI-device, which -describes the clks and regulators, and the driver for this INT3472 device -must be fully initialized before the sensor driver (any sensor driver) -binds for things to work properly. - -On these devices the ACPI nodes describing the sensors all have a _DEP -dependency on the matching INT3472 ACPI device (there is one per sensor). - -This allows solving the probe-ordering problem by delaying the enumeration -(instantiation of the I2C-client in the ov8865 example) of ACPI-devices -which have a _DEP dependency on an INT3472 device. - -The new acpi_dev_ready_for_enumeration() helper used for this is also -exported because for devices, which have the enumeration_by_parent flag -set, the parent-driver will do its own scan of child ACPI devices and -it will try to enumerate those during its probe(). Code doing this such -as e.g. the i2c-core-acpi.c code must call this new helper to ensure -that it too delays the enumeration until all the _DEP dependencies are -met on devices which have the new honor_deps flag set. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/acpi/scan.c | 36 ++++++++++++++++++++++++++++++++++-- - include/acpi/acpi_bus.h | 5 ++++- - 2 files changed, 38 insertions(+), 3 deletions(-) - -diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c -index 608e83f07b48..ca33437def41 100644 ---- a/drivers/acpi/scan.c -+++ b/drivers/acpi/scan.c -@@ -797,6 +797,12 @@ static const char * const acpi_ignore_dep_ids[] = { - NULL - }; - -+/* List of HIDs for which we honor deps of matching ACPI devs, when checking _DEP lists. */ -+static const char * const acpi_honor_dep_ids[] = { -+ "INT3472", /* Camera sensor PMIC / clk and regulator info */ -+ NULL -+}; -+ - static struct acpi_device *acpi_bus_get_parent(acpi_handle handle) - { - struct acpi_device *device = NULL; -@@ -1774,8 +1780,12 @@ static void acpi_scan_dep_init(struct acpi_device *adev) - struct acpi_dep_data *dep; - - list_for_each_entry(dep, &acpi_dep_list, node) { -- if (dep->consumer == adev->handle) -+ if (dep->consumer == adev->handle) { -+ if (dep->honor_dep) -+ adev->flags.honor_deps = 1; -+ - adev->dep_unmet++; -+ } - } - } - -@@ -1979,7 +1989,7 @@ static u32 acpi_scan_check_dep(acpi_handle handle, bool check_dep) - for (count = 0, i = 0; i < dep_devices.count; i++) { - struct acpi_device_info *info; - struct acpi_dep_data *dep; -- bool skip; -+ bool skip, honor_dep; - - status = acpi_get_object_info(dep_devices.handles[i], &info); - if (ACPI_FAILURE(status)) { -@@ -1988,6 +1998,7 @@ static u32 acpi_scan_check_dep(acpi_handle handle, bool check_dep) - } - - skip = acpi_info_matches_ids(info, acpi_ignore_dep_ids); -+ honor_dep = acpi_info_matches_ids(info, acpi_honor_dep_ids); - kfree(info); - - if (skip) -@@ -2001,6 +2012,7 @@ static u32 acpi_scan_check_dep(acpi_handle handle, bool check_dep) - - dep->supplier = dep_devices.handles[i]; - dep->consumer = handle; -+ dep->honor_dep = honor_dep; - - mutex_lock(&acpi_dep_list_lock); - list_add_tail(&dep->node , &acpi_dep_list); -@@ -2088,6 +2100,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, - - static void acpi_default_enumeration(struct acpi_device *device) - { -+ if (!acpi_dev_ready_for_enumeration(device)) -+ return; -+ - /* - * Do not enumerate devices with enumeration_by_parent flag set as - * they will be enumerated by their respective parents. -@@ -2330,6 +2345,23 @@ void acpi_dev_clear_dependencies(struct acpi_device *supplier) - } - EXPORT_SYMBOL_GPL(acpi_dev_clear_dependencies); - -+/** -+ * acpi_dev_ready_for_enumeration - Check if the ACPI device is ready for enumeration -+ * @device: Pointer to the &struct acpi_device to check -+ * -+ * Check if the device is present and has no unmet dependencies. -+ * -+ * Return true if the device is ready for enumeratino. Otherwise, return false. -+ */ -+bool acpi_dev_ready_for_enumeration(const struct acpi_device *device) -+{ -+ if (device->flags.honor_deps && device->dep_unmet) -+ return false; -+ -+ return acpi_device_is_present(device); -+} -+EXPORT_SYMBOL_GPL(acpi_dev_ready_for_enumeration); -+ - /** - * acpi_dev_get_first_consumer_dev - Return ACPI device dependent on @supplier - * @supplier: Pointer to the dependee device -diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h -index d6fe27b695c3..5895f6c7f6db 100644 ---- a/include/acpi/acpi_bus.h -+++ b/include/acpi/acpi_bus.h -@@ -202,7 +202,8 @@ struct acpi_device_flags { - u32 coherent_dma:1; - u32 cca_seen:1; - u32 enumeration_by_parent:1; -- u32 reserved:19; -+ u32 honor_deps:1; -+ u32 reserved:18; - }; - - /* File System */ -@@ -285,6 +286,7 @@ struct acpi_dep_data { - struct list_head node; - acpi_handle supplier; - acpi_handle consumer; -+ bool honor_dep; - }; - - /* Performance Management */ -@@ -694,6 +696,7 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) - bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); - - void acpi_dev_clear_dependencies(struct acpi_device *supplier); -+bool acpi_dev_ready_for_enumeration(const struct acpi_device *device); - struct acpi_device *acpi_dev_get_first_consumer_dev(struct acpi_device *supplier); - struct acpi_device * - acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); --- -2.35.1 - -From 75b7492855a6bde63125f8d01804672d34e2257d Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:56:58 +0200 -Subject: [PATCH] i2c: acpi: Use acpi_dev_ready_for_enumeration() helper - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around cases where this info is not present in the firmware tables, -which is often the case on x86/ACPI devices, both frameworks allow the -provider-driver to attach info about consumers to the clks/regulators -when registering these. - -This causes problems with the probe ordering wrt drivers for consumers -of these clks/regulators. Since the lookups are only registered when the -provider-driver binds, trying to get these clks/regulators before then -results in a -ENOENT error for clks and a dummy regulator for regulators. - -To ensure the correct probe-ordering the ACPI core has code to defer the -enumeration of consumers affected by this until the providers are ready. - -Call the new acpi_dev_ready_for_enumeration() helper to avoid -enumerating / instantiating i2c-clients too early. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/i2c/i2c-core-acpi.c | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index 3b688cea8e00..0542d8aba902 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -144,9 +144,12 @@ static int i2c_acpi_do_lookup(struct acpi_device *adev, - struct list_head resource_list; - int ret; - -- if (acpi_bus_get_status(adev) || !adev->status.present) -+ if (acpi_bus_get_status(adev)) - return -EINVAL; - -+ if (!acpi_dev_ready_for_enumeration(adev)) -+ return -ENODEV; -+ - if (acpi_match_device_ids(adev, i2c_acpi_ignored_device_ids) == 0) - return -ENODEV; - --- -2.35.1 - -From 7337d1dae4ab8cf07f72960ab69f4cc0baa877f4 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:56:59 +0200 -Subject: [PATCH] platform_data: Add linux/platform_data/tps68470.h file - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around cases where this info is not present in the firmware tables, -which is often the case on x86/ACPI devices, both frameworks allow the -provider-driver to attach info about consumers to the provider-device -during probe/registration of the provider device. - -The TI TPS68470 PMIC is used x86/ACPI devices with the consumer-info -missing from the ACPI tables. Thus the tps68470-clk and tps68470-regulator -drivers must provide the consumer-info at probe time. - -Define tps68470_clk_platform_data and tps68470_regulator_platform_data -structs to allow the x86 platform code to pass the necessary consumer info -to these drivers. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - include/linux/platform_data/tps68470.h | 35 ++++++++++++++++++++++++++ - 1 file changed, 35 insertions(+) - create mode 100644 include/linux/platform_data/tps68470.h - -diff --git a/include/linux/platform_data/tps68470.h b/include/linux/platform_data/tps68470.h -new file mode 100644 -index 000000000000..126d082c3f2e ---- /dev/null -+++ b/include/linux/platform_data/tps68470.h -@@ -0,0 +1,35 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * TI TPS68470 PMIC platform data definition. -+ * -+ * Copyright (c) 2021 Red Hat Inc. -+ * -+ * Red Hat authors: -+ * Hans de Goede -+ */ -+#ifndef __PDATA_TPS68470_H -+#define __PDATA_TPS68470_H -+ -+enum tps68470_regulators { -+ TPS68470_CORE, -+ TPS68470_ANA, -+ TPS68470_VCM, -+ TPS68470_VIO, -+ TPS68470_VSIO, -+ TPS68470_AUX1, -+ TPS68470_AUX2, -+ TPS68470_NUM_REGULATORS -+}; -+ -+struct regulator_init_data; -+ -+struct tps68470_regulator_platform_data { -+ const struct regulator_init_data *reg_init_data[TPS68470_NUM_REGULATORS]; -+}; -+ -+struct tps68470_clk_platform_data { -+ const char *consumer_dev_name; -+ const char *consumer_con_id; -+}; -+ -+#endif --- -2.35.1 - -From afe75b849e2d0bdd23fc9b0cabd1ce0a46dc3d5e Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:00 +0200 -Subject: [PATCH] regulator: Introduce tps68470-regulator driver - -The TPS68470 PMIC provides Clocks, GPIOs and Regulators. At present in -the kernel the Regulators and Clocks are controlled by an OpRegion -driver designed to work with power control methods defined in ACPI, but -some platforms lack those methods, meaning drivers need to be able to -consume the resources of these chips through the usual frameworks. - -This commit adds a driver for the regulators provided by the tps68470, -and is designed to bind to the platform_device registered by the -intel_skl_int3472 module. - -This is based on this out of tree driver written by Intel: -https://github.com/intel/linux-intel-lts/blob/4.14/base/drivers/regulator/tps68470-regulator.c -with various cleanups added. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/regulator/Kconfig | 9 ++ - drivers/regulator/Makefile | 1 + - drivers/regulator/tps68470-regulator.c | 193 +++++++++++++++++++++++++ - 3 files changed, 203 insertions(+) - create mode 100644 drivers/regulator/tps68470-regulator.c - -diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig -index 6be9b1c8a615..25e3acb378e3 100644 ---- a/drivers/regulator/Kconfig -+++ b/drivers/regulator/Kconfig -@@ -1339,6 +1339,15 @@ config REGULATOR_TPS65912 - help - This driver supports TPS65912 voltage regulator chip. - -+config REGULATOR_TPS68470 -+ tristate "TI TPS68370 PMIC Regulators Driver" -+ depends on INTEL_SKL_INT3472 -+ help -+ This driver adds support for the TPS68470 PMIC to register -+ regulators against the usual framework. -+ -+ The module will be called "tps68470-regulator" -+ - config REGULATOR_TWL4030 - tristate "TI TWL4030/TWL5030/TWL6030/TPS659x0 PMIC" - depends on TWL4030_CORE -diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile -index b07d2a22df0b..0ea7e6adc267 100644 ---- a/drivers/regulator/Makefile -+++ b/drivers/regulator/Makefile -@@ -158,6 +158,7 @@ obj-$(CONFIG_REGULATOR_TPS6524X) += tps6524x-regulator.o - obj-$(CONFIG_REGULATOR_TPS6586X) += tps6586x-regulator.o - obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o - obj-$(CONFIG_REGULATOR_TPS65912) += tps65912-regulator.o -+obj-$(CONFIG_REGULATOR_TPS68470) += tps68470-regulator.o - obj-$(CONFIG_REGULATOR_TPS65132) += tps65132-regulator.o - obj-$(CONFIG_REGULATOR_TWL4030) += twl-regulator.o twl6030-regulator.o - obj-$(CONFIG_REGULATOR_UNIPHIER) += uniphier-regulator.o -diff --git a/drivers/regulator/tps68470-regulator.c b/drivers/regulator/tps68470-regulator.c -new file mode 100644 -index 000000000000..3129fa13a122 ---- /dev/null -+++ b/drivers/regulator/tps68470-regulator.c -@@ -0,0 +1,193 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Regulator driver for TPS68470 PMIC -+ * -+ * Copyright (C) 2018 Intel Corporation -+ * -+ * Authors: -+ * Zaikuo Wang -+ * Tianshu Qiu -+ * Jian Xu Zheng -+ * Yuning Pu -+ * Rajmohan Mani -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define TPS68470_REGULATOR(_name, _id, _ops, _n, _vr, \ -+ _vm, _er, _em, _t, _lr, _nlr) \ -+ [TPS68470_ ## _name] = { \ -+ .name = # _name, \ -+ .id = _id, \ -+ .ops = &_ops, \ -+ .n_voltages = _n, \ -+ .type = REGULATOR_VOLTAGE, \ -+ .owner = THIS_MODULE, \ -+ .vsel_reg = _vr, \ -+ .vsel_mask = _vm, \ -+ .enable_reg = _er, \ -+ .enable_mask = _em, \ -+ .volt_table = _t, \ -+ .linear_ranges = _lr, \ -+ .n_linear_ranges = _nlr, \ -+ } -+ -+static const struct linear_range tps68470_ldo_ranges[] = { -+ REGULATOR_LINEAR_RANGE(875000, 0, 125, 17800), -+}; -+ -+static const struct linear_range tps68470_core_ranges[] = { -+ REGULATOR_LINEAR_RANGE(900000, 0, 42, 25000), -+}; -+ -+/* Operations permitted on DCDCx, LDO2, LDO3 and LDO4 */ -+static const struct regulator_ops tps68470_regulator_ops = { -+ .is_enabled = regulator_is_enabled_regmap, -+ .enable = regulator_enable_regmap, -+ .disable = regulator_disable_regmap, -+ .get_voltage_sel = regulator_get_voltage_sel_regmap, -+ .set_voltage_sel = regulator_set_voltage_sel_regmap, -+ .list_voltage = regulator_list_voltage_linear_range, -+ .map_voltage = regulator_map_voltage_linear_range, -+}; -+ -+static const struct regulator_desc regulators[] = { -+ TPS68470_REGULATOR(CORE, TPS68470_CORE, -+ tps68470_regulator_ops, 43, TPS68470_REG_VDVAL, -+ TPS68470_VDVAL_DVOLT_MASK, TPS68470_REG_VDCTL, -+ TPS68470_VDCTL_EN_MASK, -+ NULL, tps68470_core_ranges, -+ ARRAY_SIZE(tps68470_core_ranges)), -+ TPS68470_REGULATOR(ANA, TPS68470_ANA, -+ tps68470_regulator_ops, 126, TPS68470_REG_VAVAL, -+ TPS68470_VAVAL_AVOLT_MASK, TPS68470_REG_VACTL, -+ TPS68470_VACTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ TPS68470_REGULATOR(VCM, TPS68470_VCM, -+ tps68470_regulator_ops, 126, TPS68470_REG_VCMVAL, -+ TPS68470_VCMVAL_VCVOLT_MASK, TPS68470_REG_VCMCTL, -+ TPS68470_VCMCTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ TPS68470_REGULATOR(VIO, TPS68470_VIO, -+ tps68470_regulator_ops, 126, TPS68470_REG_VIOVAL, -+ TPS68470_VIOVAL_IOVOLT_MASK, TPS68470_REG_S_I2C_CTL, -+ TPS68470_S_I2C_CTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ -+/* -+ * (1) This register must have same setting as VIOVAL if S_IO LDO is used to -+ * power daisy chained IOs in the receive side. -+ * (2) If there is no I2C daisy chain it can be set freely. -+ * -+ */ -+ TPS68470_REGULATOR(VSIO, TPS68470_VSIO, -+ tps68470_regulator_ops, 126, TPS68470_REG_VSIOVAL, -+ TPS68470_VSIOVAL_IOVOLT_MASK, TPS68470_REG_S_I2C_CTL, -+ TPS68470_S_I2C_CTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ TPS68470_REGULATOR(AUX1, TPS68470_AUX1, -+ tps68470_regulator_ops, 126, TPS68470_REG_VAUX1VAL, -+ TPS68470_VAUX1VAL_AUX1VOLT_MASK, -+ TPS68470_REG_VAUX1CTL, -+ TPS68470_VAUX1CTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+ TPS68470_REGULATOR(AUX2, TPS68470_AUX2, -+ tps68470_regulator_ops, 126, TPS68470_REG_VAUX2VAL, -+ TPS68470_VAUX2VAL_AUX2VOLT_MASK, -+ TPS68470_REG_VAUX2CTL, -+ TPS68470_VAUX2CTL_EN_MASK, -+ NULL, tps68470_ldo_ranges, -+ ARRAY_SIZE(tps68470_ldo_ranges)), -+}; -+ -+#define TPS68470_REG_INIT_DATA(_name, _min_uV, _max_uV) \ -+ [TPS68470_ ## _name] = { \ -+ .constraints = { \ -+ .name = # _name, \ -+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | \ -+ REGULATOR_CHANGE_STATUS, \ -+ .min_uV = _min_uV, \ -+ .max_uV = _max_uV, \ -+ }, \ -+ } -+ -+struct regulator_init_data tps68470_init[] = { -+ TPS68470_REG_INIT_DATA(CORE, 900000, 1950000), -+ TPS68470_REG_INIT_DATA(ANA, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(VCM, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(VIO, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(VSIO, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(AUX1, 875000, 3100000), -+ TPS68470_REG_INIT_DATA(AUX2, 875000, 3100000), -+}; -+ -+static int tps68470_regulator_probe(struct platform_device *pdev) -+{ -+ struct tps68470_regulator_platform_data *pdata = pdev->dev.platform_data; -+ struct regulator_config config = { }; -+ struct regmap *tps68470_regmap; -+ struct regulator_dev *rdev; -+ int i; -+ -+ tps68470_regmap = dev_get_drvdata(pdev->dev.parent); -+ -+ for (i = 0; i < TPS68470_NUM_REGULATORS; i++) { -+ config.dev = pdev->dev.parent; -+ config.regmap = tps68470_regmap; -+ if (pdata && pdata->reg_init_data[i]) -+ config.init_data = pdata->reg_init_data[i]; -+ else -+ config.init_data = &tps68470_init[i]; -+ -+ rdev = devm_regulator_register(&pdev->dev, ®ulators[i], &config); -+ if (IS_ERR(rdev)) { -+ dev_err(&pdev->dev, "failed to register %s regulator\n", -+ regulators[i].name); -+ return PTR_ERR(rdev); -+ } -+ } -+ -+ return 0; -+} -+ -+static struct platform_driver tps68470_regulator_driver = { -+ .driver = { -+ .name = "tps68470-regulator", -+ }, -+ .probe = tps68470_regulator_probe, -+}; -+ -+/* -+ * The ACPI tps68470 probe-ordering depends on the clk/gpio/regulator drivers -+ * registering before the drivers for the camera-sensors which use them bind. -+ * subsys_initcall() ensures this when the drivers are builtin. -+ */ -+static int __init tps68470_regulator_init(void) -+{ -+ return platform_driver_register(&tps68470_regulator_driver); -+} -+subsys_initcall(tps68470_regulator_init); -+ -+static void __exit tps68470_regulator_exit(void) -+{ -+ platform_driver_unregister(&tps68470_regulator_driver); -+} -+module_exit(tps68470_regulator_exit); -+ -+MODULE_ALIAS("platform:tps68470-regulator"); -+MODULE_DESCRIPTION("TPS68470 voltage regulator driver"); -+MODULE_LICENSE("GPL v2"); --- -2.35.1 - -From f2f07e994f692af710805ef8a53590b371288b8b Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:01 +0200 -Subject: [PATCH] clk: Introduce clk-tps68470 driver - -The TPS68470 PMIC provides Clocks, GPIOs and Regulators. At present in -the kernel the Regulators and Clocks are controlled by an OpRegion -driver designed to work with power control methods defined in ACPI, but -some platforms lack those methods, meaning drivers need to be able to -consume the resources of these chips through the usual frameworks. - -This commit adds a driver for the clocks provided by the tps68470, -and is designed to bind to the platform_device registered by the -intel_skl_int3472 module. - -This is based on this out of tree driver written by Intel: -https://github.com/intel/linux-intel-lts/blob/4.14/base/drivers/clk/clk-tps68470.c -with various cleanups added. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/clk/Kconfig | 6 + - drivers/clk/Makefile | 1 + - drivers/clk/clk-tps68470.c | 256 +++++++++++++++++++++++++++++++++++ - include/linux/mfd/tps68470.h | 11 ++ - 4 files changed, 274 insertions(+) - create mode 100644 drivers/clk/clk-tps68470.c - -diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig -index c5b3dc97396a..7dffecac83d1 100644 ---- a/drivers/clk/Kconfig -+++ b/drivers/clk/Kconfig -@@ -169,6 +169,12 @@ config COMMON_CLK_CDCE706 - help - This driver supports TI CDCE706 programmable 3-PLL clock synthesizer. - -+config COMMON_CLK_TPS68470 -+ tristate "Clock Driver for TI TPS68470 PMIC" -+ depends on I2C && REGMAP_I2C && INTEL_SKL_INT3472 -+ help -+ This driver supports the clocks provided by TPS68470 -+ - config COMMON_CLK_CDCE925 - tristate "Clock driver for TI CDCE913/925/937/949 devices" - depends on I2C -diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile -index e42312121e51..6b6a88ae1425 100644 ---- a/drivers/clk/Makefile -+++ b/drivers/clk/Makefile -@@ -63,6 +63,7 @@ obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o - obj-$(CONFIG_COMMON_CLK_STM32F) += clk-stm32f4.o - obj-$(CONFIG_COMMON_CLK_STM32H7) += clk-stm32h7.o - obj-$(CONFIG_COMMON_CLK_STM32MP157) += clk-stm32mp1.o -+obj-$(CONFIG_COMMON_CLK_TPS68470) += clk-tps68470.o - obj-$(CONFIG_CLK_TWL6040) += clk-twl6040.o - obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o - obj-$(CONFIG_COMMON_CLK_VC5) += clk-versaclock5.o -diff --git a/drivers/clk/clk-tps68470.c b/drivers/clk/clk-tps68470.c -new file mode 100644 -index 000000000000..27e8cbd0f60e ---- /dev/null -+++ b/drivers/clk/clk-tps68470.c -@@ -0,0 +1,256 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Clock driver for TPS68470 PMIC -+ * -+ * Copyright (C) 2018 Intel Corporation -+ * -+ * Authors: -+ * Zaikuo Wang -+ * Tianshu Qiu -+ * Jian Xu Zheng -+ * Yuning Pu -+ * Antti Laakso -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define TPS68470_CLK_NAME "tps68470-clk" -+ -+#define to_tps68470_clkdata(clkd) \ -+ container_of(clkd, struct tps68470_clkdata, clkout_hw) -+ -+struct tps68470_clkout_freqs { -+ unsigned long freq; -+ unsigned int xtaldiv; -+ unsigned int plldiv; -+ unsigned int postdiv; -+ unsigned int buckdiv; -+ unsigned int boostdiv; -+} clk_freqs[] = { -+/* -+ * The PLL is used to multiply the crystal oscillator -+ * frequency range of 3 MHz to 27 MHz by a programmable -+ * factor of F = (M/N)*(1/P) such that the output -+ * available at the HCLK_A or HCLK_B pins are in the range -+ * of 4 MHz to 64 MHz in increments of 0.1 MHz -+ * -+ * hclk_# = osc_in * (((plldiv*2)+320) / (xtaldiv+30)) * (1 / 2^postdiv) -+ * -+ * PLL_REF_CLK should be as close as possible to 100kHz -+ * PLL_REF_CLK = input clk / XTALDIV[7:0] + 30) -+ * -+ * PLL_VCO_CLK = (PLL_REF_CLK * (plldiv*2 + 320)) -+ * -+ * BOOST should be as close as possible to 2Mhz -+ * BOOST = PLL_VCO_CLK / (BOOSTDIV[4:0] + 16) * -+ * -+ * BUCK should be as close as possible to 5.2Mhz -+ * BUCK = PLL_VCO_CLK / (BUCKDIV[3:0] + 5) -+ * -+ * osc_in xtaldiv plldiv postdiv hclk_# -+ * 20Mhz 170 32 1 19.2Mhz -+ * 20Mhz 170 40 1 20Mhz -+ * 20Mhz 170 80 1 24Mhz -+ * -+ */ -+ { 19200000, 170, 32, 1, 2, 3 }, -+ { 20000000, 170, 40, 1, 3, 4 }, -+ { 24000000, 170, 80, 1, 4, 8 }, -+}; -+ -+struct tps68470_clkdata { -+ struct clk_hw clkout_hw; -+ struct regmap *regmap; -+ struct clk *clk; -+ int clk_cfg_idx; -+}; -+ -+static int tps68470_clk_is_prepared(struct clk_hw *hw) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ int val; -+ -+ if (regmap_read(clkdata->regmap, TPS68470_REG_PLLCTL, &val)) -+ return 0; -+ -+ return val & TPS68470_PLL_EN_MASK; -+} -+ -+static int tps68470_clk_prepare(struct clk_hw *hw) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ int idx = clkdata->clk_cfg_idx; -+ -+ regmap_write(clkdata->regmap, TPS68470_REG_BOOSTDIV, clk_freqs[idx].boostdiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_BUCKDIV, clk_freqs[idx].buckdiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLSWR, TPS68470_PLLSWR_DEFAULT); -+ regmap_write(clkdata->regmap, TPS68470_REG_XTALDIV, clk_freqs[idx].xtaldiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLDIV, clk_freqs[idx].plldiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_POSTDIV, clk_freqs[idx].postdiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_POSTDIV2, clk_freqs[idx].postdiv); -+ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG2, TPS68470_CLKCFG2_DRV_STR_2MA); -+ -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLCTL, -+ TPS68470_OSC_EXT_CAP_DEFAULT << TPS68470_OSC_EXT_CAP_SHIFT | -+ TPS68470_CLK_SRC_XTAL << TPS68470_CLK_SRC_SHIFT); -+ -+ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG1, -+ (TPS68470_PLL_OUTPUT_ENABLE << -+ TPS68470_OUTPUT_A_SHIFT) | -+ (TPS68470_PLL_OUTPUT_ENABLE << -+ TPS68470_OUTPUT_B_SHIFT)); -+ -+ regmap_update_bits(clkdata->regmap, TPS68470_REG_PLLCTL, -+ TPS68470_PLL_EN_MASK, TPS68470_PLL_EN_MASK); -+ -+ return 0; -+} -+ -+static void tps68470_clk_unprepare(struct clk_hw *hw) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ -+ /* disable clock first*/ -+ regmap_update_bits(clkdata->regmap, TPS68470_REG_PLLCTL, TPS68470_PLL_EN_MASK, 0); -+ -+ /* write hw defaults */ -+ regmap_write(clkdata->regmap, TPS68470_REG_BOOSTDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_BUCKDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLSWR, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_XTALDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_PLLDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_POSTDIV, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG2, 0); -+ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG1, 0); -+} -+ -+static unsigned long tps68470_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ -+ return clk_freqs[clkdata->clk_cfg_idx].freq; -+} -+ -+static int tps68470_clk_cfg_lookup(unsigned long rate) -+{ -+ long diff, best_diff = LONG_MAX; -+ int i, best_idx = 0; -+ -+ for (i = 0; i < ARRAY_SIZE(clk_freqs); i++) { -+ diff = clk_freqs[i].freq - rate; -+ if (diff == 0) -+ return i; -+ -+ diff = abs(diff); -+ if (diff < best_diff) { -+ best_diff = diff; -+ best_idx = i; -+ } -+ } -+ -+ return best_idx; -+} -+ -+static long tps68470_clk_round_rate(struct clk_hw *hw, unsigned long rate, -+ unsigned long *parent_rate) -+{ -+ int idx = tps68470_clk_cfg_lookup(rate); -+ -+ return clk_freqs[idx].freq; -+} -+ -+static int tps68470_clk_set_rate(struct clk_hw *hw, unsigned long rate, -+ unsigned long parent_rate) -+{ -+ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); -+ int idx = tps68470_clk_cfg_lookup(rate); -+ -+ if (rate != clk_freqs[idx].freq) -+ return -EINVAL; -+ -+ clkdata->clk_cfg_idx = idx; -+ return 0; -+} -+ -+static const struct clk_ops tps68470_clk_ops = { -+ .is_prepared = tps68470_clk_is_prepared, -+ .prepare = tps68470_clk_prepare, -+ .unprepare = tps68470_clk_unprepare, -+ .recalc_rate = tps68470_clk_recalc_rate, -+ .round_rate = tps68470_clk_round_rate, -+ .set_rate = tps68470_clk_set_rate, -+}; -+ -+static struct clk_init_data tps68470_clk_initdata = { -+ .name = TPS68470_CLK_NAME, -+ .ops = &tps68470_clk_ops, -+}; -+ -+static int tps68470_clk_probe(struct platform_device *pdev) -+{ -+ struct tps68470_clk_platform_data *pdata = pdev->dev.platform_data; -+ struct tps68470_clkdata *tps68470_clkdata; -+ int ret; -+ -+ tps68470_clkdata = devm_kzalloc(&pdev->dev, sizeof(*tps68470_clkdata), -+ GFP_KERNEL); -+ if (!tps68470_clkdata) -+ return -ENOMEM; -+ -+ tps68470_clkdata->regmap = dev_get_drvdata(pdev->dev.parent); -+ tps68470_clkdata->clkout_hw.init = &tps68470_clk_initdata; -+ tps68470_clkdata->clk = devm_clk_register(&pdev->dev, &tps68470_clkdata->clkout_hw); -+ if (IS_ERR(tps68470_clkdata->clk)) -+ return PTR_ERR(tps68470_clkdata->clk); -+ -+ ret = devm_clk_hw_register_clkdev(&pdev->dev, &tps68470_clkdata->clkout_hw, -+ TPS68470_CLK_NAME, NULL); -+ if (ret) -+ return ret; -+ -+ if (pdata) { -+ ret = devm_clk_hw_register_clkdev(&pdev->dev, -+ &tps68470_clkdata->clkout_hw, -+ pdata->consumer_con_id, -+ pdata->consumer_dev_name); -+ if (ret) -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static struct platform_driver tps68470_clk_driver = { -+ .driver = { -+ .name = TPS68470_CLK_NAME, -+ }, -+ .probe = tps68470_clk_probe, -+}; -+ -+/* -+ * The ACPI tps68470 probe-ordering depends on the clk/gpio/regulator drivers -+ * registering before the drivers for the camera-sensors which use them bind. -+ * subsys_initcall() ensures this when the drivers are builtin. -+ */ -+static int __init tps68470_clk_init(void) -+{ -+ return platform_driver_register(&tps68470_clk_driver); -+} -+subsys_initcall(tps68470_clk_init); -+ -+static void __exit tps68470_clk_exit(void) -+{ -+ platform_driver_unregister(&tps68470_clk_driver); -+} -+module_exit(tps68470_clk_exit); -+ -+MODULE_ALIAS("platform:tps68470-clk"); -+MODULE_DESCRIPTION("clock driver for TPS68470 pmic"); -+MODULE_LICENSE("GPL"); -diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h -index ffe81127d91c..7807fa329db0 100644 ---- a/include/linux/mfd/tps68470.h -+++ b/include/linux/mfd/tps68470.h -@@ -75,6 +75,17 @@ - #define TPS68470_CLKCFG1_MODE_A_MASK GENMASK(1, 0) - #define TPS68470_CLKCFG1_MODE_B_MASK GENMASK(3, 2) - -+#define TPS68470_CLKCFG2_DRV_STR_2MA 0x05 -+#define TPS68470_PLL_OUTPUT_ENABLE 0x02 -+#define TPS68470_CLK_SRC_XTAL BIT(0) -+#define TPS68470_PLLSWR_DEFAULT GENMASK(1, 0) -+#define TPS68470_OSC_EXT_CAP_DEFAULT 0x05 -+ -+#define TPS68470_OUTPUT_A_SHIFT 0x00 -+#define TPS68470_OUTPUT_B_SHIFT 0x02 -+#define TPS68470_CLK_SRC_SHIFT GENMASK(2, 0) -+#define TPS68470_OSC_EXT_CAP_SHIFT BIT(2) -+ - #define TPS68470_GPIO_CTL_REG_A(x) (TPS68470_REG_GPCTL0A + (x) * 2) - #define TPS68470_GPIO_CTL_REG_B(x) (TPS68470_REG_GPCTL0B + (x) * 2) - #define TPS68470_GPIO_MODE_MASK GENMASK(1, 0) --- -2.35.1 - -From bf27d7b319215a0e17269ccdc42464a2be1afd7a Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 10 Oct 2021 20:57:02 +0200 -Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain - -The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic -can be forwarded to a device connected to the PMIC as though it were -connected directly to the system bus. Enable this mode when the chip -is initialised. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - .../x86/intel/int3472/intel_skl_int3472_tps68470.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -index c05b4cf502fe..42e688f4cad4 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -+++ b/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -@@ -45,6 +45,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) - return ret; - } - -+ /* Enable I2C daisy chain */ -+ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); -+ if (ret) { -+ dev_err(dev, "Failed to enable i2c daisy chain\n"); -+ return ret; -+ } -+ - dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); - - return 0; --- -2.35.1 - -From 6ea9961efd3c29d366a7608f27c84bbd4b072a67 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:03 +0200 -Subject: [PATCH] platform/x86: int3472: Split into 2 drivers - -The intel_skl_int3472.ko module contains 2 separate drivers, -the int3472_discrete platform driver and the int3472_tps68470 -I2C-driver. - -These 2 drivers contain very little shared code, only -skl_int3472_get_acpi_buffer() and skl_int3472_fill_cldb() are -shared. - -Split the module into 2 drivers, linking the little shared code -directly into both. - -This will allow us to add soft-module dependencies for the -tps68470 clk, gpio and regulator drivers to the new -intel_skl_int3472_tps68470.ko to help with probe ordering issues -without causing these modules to get loaded on boards which only -use the int3472_discrete platform driver. - -While at it also rename the .c and .h files to remove the -cumbersome intel_skl_int3472_ prefix. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/Makefile | 9 ++-- - ...lk_and_regulator.c => clk_and_regulator.c} | 2 +- - drivers/platform/x86/intel/int3472/common.c | 54 +++++++++++++++++++ - .../{intel_skl_int3472_common.h => common.h} | 3 -- - ...ntel_skl_int3472_discrete.c => discrete.c} | 28 ++++++++-- - ...ntel_skl_int3472_tps68470.c => tps68470.c} | 23 +++++++- - 6 files changed, 105 insertions(+), 14 deletions(-) - rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_clk_and_regulator.c => clk_and_regulator.c} (99%) - create mode 100644 drivers/platform/x86/intel/int3472/common.c - rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_common.h => common.h} (94%) - rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_discrete.c => discrete.c} (93%) - rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_tps68470.c => tps68470.c} (85%) - -diff --git a/drivers/platform/x86/intel/int3472/Makefile b/drivers/platform/x86/intel/int3472/Makefile -index 2362e04db18d..4a4b2518ea16 100644 ---- a/drivers/platform/x86/intel/int3472/Makefile -+++ b/drivers/platform/x86/intel/int3472/Makefile -@@ -1,5 +1,4 @@ --obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o --intel_skl_int3472-y := intel_skl_int3472_common.o \ -- intel_skl_int3472_discrete.o \ -- intel_skl_int3472_tps68470.o \ -- intel_skl_int3472_clk_and_regulator.o -+obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472_discrete.o \ -+ intel_skl_int3472_tps68470.o -+intel_skl_int3472_discrete-y := discrete.o clk_and_regulator.o common.o -+intel_skl_int3472_tps68470-y := tps68470.o common.o -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel/int3472/clk_and_regulator.c -similarity index 99% -rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c -rename to drivers/platform/x86/intel/int3472/clk_and_regulator.c -index 1700e7557a82..1cf958983e86 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c -+++ b/drivers/platform/x86/intel/int3472/clk_and_regulator.c -@@ -9,7 +9,7 @@ - #include - #include - --#include "intel_skl_int3472_common.h" -+#include "common.h" - - /* - * The regulators have to have .ops to be valid, but the only ops we actually -diff --git a/drivers/platform/x86/intel/int3472/common.c b/drivers/platform/x86/intel/int3472/common.c -new file mode 100644 -index 000000000000..350655a9515b ---- /dev/null -+++ b/drivers/platform/x86/intel/int3472/common.c -@@ -0,0 +1,54 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* Author: Dan Scally */ -+ -+#include -+#include -+ -+#include "common.h" -+ -+union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id) -+{ -+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; -+ acpi_handle handle = adev->handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(handle, id, NULL, &buffer); -+ if (ACPI_FAILURE(status)) -+ return ERR_PTR(-ENODEV); -+ -+ obj = buffer.pointer; -+ if (!obj) -+ return ERR_PTR(-ENODEV); -+ -+ if (obj->type != ACPI_TYPE_BUFFER) { -+ acpi_handle_err(handle, "%s object is not an ACPI buffer\n", id); -+ kfree(obj); -+ return ERR_PTR(-EINVAL); -+ } -+ -+ return obj; -+} -+ -+int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) -+{ -+ union acpi_object *obj; -+ int ret; -+ -+ obj = skl_int3472_get_acpi_buffer(adev, "CLDB"); -+ if (IS_ERR(obj)) -+ return PTR_ERR(obj); -+ -+ if (obj->buffer.length > sizeof(*cldb)) { -+ acpi_handle_err(adev->handle, "The CLDB buffer is too large\n"); -+ ret = -EINVAL; -+ goto out_free_obj; -+ } -+ -+ memcpy(cldb, obj->buffer.pointer, obj->buffer.length); -+ ret = 0; -+ -+out_free_obj: -+ kfree(obj); -+ return ret; -+} -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel/int3472/common.h -similarity index 94% -rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h -rename to drivers/platform/x86/intel/int3472/common.h -index 714fde73b524..d14944ee8586 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h -+++ b/drivers/platform/x86/intel/int3472/common.h -@@ -105,9 +105,6 @@ struct int3472_discrete_device { - struct gpiod_lookup_table gpios; - }; - --int skl_int3472_discrete_probe(struct platform_device *pdev); --int skl_int3472_discrete_remove(struct platform_device *pdev); --int skl_int3472_tps68470_probe(struct i2c_client *client); - union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, - char *id); - int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel/int3472/discrete.c -similarity index 93% -rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c -rename to drivers/platform/x86/intel/int3472/discrete.c -index e59d79c7e82f..a19a1f5dbdd7 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c -+++ b/drivers/platform/x86/intel/int3472/discrete.c -@@ -14,7 +14,7 @@ - #include - #include - --#include "intel_skl_int3472_common.h" -+#include "common.h" - - /* - * 79234640-9e10-4fea-a5c1-b5aa8b19756f -@@ -332,7 +332,9 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) - return 0; - } - --int skl_int3472_discrete_probe(struct platform_device *pdev) -+static int skl_int3472_discrete_remove(struct platform_device *pdev); -+ -+static int skl_int3472_discrete_probe(struct platform_device *pdev) - { - struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); - struct int3472_discrete_device *int3472; -@@ -395,7 +397,7 @@ int skl_int3472_discrete_probe(struct platform_device *pdev) - return ret; - } - --int skl_int3472_discrete_remove(struct platform_device *pdev) -+static int skl_int3472_discrete_remove(struct platform_device *pdev) - { - struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); - -@@ -411,3 +413,23 @@ int skl_int3472_discrete_remove(struct platform_device *pdev) - - return 0; - } -+ -+static const struct acpi_device_id int3472_device_id[] = { -+ { "INT3472", 0 }, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, int3472_device_id); -+ -+static struct platform_driver int3472_discrete = { -+ .driver = { -+ .name = "int3472-discrete", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe = skl_int3472_discrete_probe, -+ .remove = skl_int3472_discrete_remove, -+}; -+module_platform_driver(int3472_discrete); -+ -+MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Discrete Device Driver"); -+MODULE_AUTHOR("Daniel Scally "); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -similarity index 85% -rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -rename to drivers/platform/x86/intel/int3472/tps68470.c -index 42e688f4cad4..b94cf66ab61f 100644 ---- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -7,7 +7,7 @@ - #include - #include - --#include "intel_skl_int3472_common.h" -+#include "common.h" - - #define DESIGNED_FOR_CHROMEOS 1 - #define DESIGNED_FOR_WINDOWS 2 -@@ -102,7 +102,7 @@ static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) - return DESIGNED_FOR_WINDOWS; - } - --int skl_int3472_tps68470_probe(struct i2c_client *client) -+static int skl_int3472_tps68470_probe(struct i2c_client *client) - { - struct acpi_device *adev = ACPI_COMPANION(&client->dev); - struct regmap *regmap; -@@ -142,3 +142,22 @@ int skl_int3472_tps68470_probe(struct i2c_client *client) - - return ret; - } -+ -+static const struct acpi_device_id int3472_device_id[] = { -+ { "INT3472", 0 }, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, int3472_device_id); -+ -+static struct i2c_driver int3472_tps68470 = { -+ .driver = { -+ .name = "int3472-tps68470", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe_new = skl_int3472_tps68470_probe, -+}; -+module_i2c_driver(int3472_tps68470); -+ -+MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI TPS68470 Device Driver"); -+MODULE_AUTHOR("Daniel Scally "); -+MODULE_LICENSE("GPL v2"); --- -2.35.1 - -From b242c3b30351ab16ff6bead5718c046fa4543d71 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:04 +0200 -Subject: [PATCH] platform/x86: int3472: Add get_sensor_adev_and_name() helper - -The discrete.c code is not the only code which needs to lookup the -acpi_device and device-name for the sensor for which the INT3472 -ACPI-device is a GPIO/clk/regulator provider. - -The tps68470.c code also needs this functionality, so factor this -out into a new get_sensor_adev_and_name() helper. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/common.c | 28 +++++++++++++++++++ - drivers/platform/x86/intel/int3472/common.h | 3 ++ - drivers/platform/x86/intel/int3472/discrete.c | 22 +++------------ - 3 files changed, 35 insertions(+), 18 deletions(-) - -diff --git a/drivers/platform/x86/intel/int3472/common.c b/drivers/platform/x86/intel/int3472/common.c -index 350655a9515b..77cf058e4168 100644 ---- a/drivers/platform/x86/intel/int3472/common.c -+++ b/drivers/platform/x86/intel/int3472/common.c -@@ -52,3 +52,31 @@ int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) - kfree(obj); - return ret; - } -+ -+/* sensor_adev_ret may be NULL, name_ret must not be NULL */ -+int skl_int3472_get_sensor_adev_and_name(struct device *dev, -+ struct acpi_device **sensor_adev_ret, -+ const char **name_ret) -+{ -+ struct acpi_device *adev = ACPI_COMPANION(dev); -+ struct acpi_device *sensor; -+ int ret = 0; -+ -+ sensor = acpi_dev_get_first_consumer_dev(adev); -+ if (!sensor) { -+ dev_err(dev, "INT3472 seems to have no dependents.\n"); -+ return -ENODEV; -+ } -+ -+ *name_ret = devm_kasprintf(dev, GFP_KERNEL, I2C_DEV_NAME_FORMAT, -+ acpi_dev_name(sensor)); -+ if (!*name_ret) -+ ret = -ENOMEM; -+ -+ if (ret == 0 && sensor_adev_ret) -+ *sensor_adev_ret = sensor; -+ else -+ acpi_dev_put(sensor); -+ -+ return ret; -+} -diff --git a/drivers/platform/x86/intel/int3472/common.h b/drivers/platform/x86/intel/int3472/common.h -index d14944ee8586..53270d19c73a 100644 ---- a/drivers/platform/x86/intel/int3472/common.h -+++ b/drivers/platform/x86/intel/int3472/common.h -@@ -108,6 +108,9 @@ struct int3472_discrete_device { - union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, - char *id); - int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); -+int skl_int3472_get_sensor_adev_and_name(struct device *dev, -+ struct acpi_device **sensor_adev_ret, -+ const char **name_ret); - - int skl_int3472_register_clock(struct int3472_discrete_device *int3472); - void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472); -diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c -index a19a1f5dbdd7..efd31a0c7a88 100644 ---- a/drivers/platform/x86/intel/int3472/discrete.c -+++ b/drivers/platform/x86/intel/int3472/discrete.c -@@ -363,19 +363,10 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) - int3472->dev = &pdev->dev; - platform_set_drvdata(pdev, int3472); - -- int3472->sensor = acpi_dev_get_first_consumer_dev(adev); -- if (!int3472->sensor) { -- dev_err(&pdev->dev, "INT3472 seems to have no dependents.\n"); -- return -ENODEV; -- } -- -- int3472->sensor_name = devm_kasprintf(int3472->dev, GFP_KERNEL, -- I2C_DEV_NAME_FORMAT, -- acpi_dev_name(int3472->sensor)); -- if (!int3472->sensor_name) { -- ret = -ENOMEM; -- goto err_put_sensor; -- } -+ ret = skl_int3472_get_sensor_adev_and_name(&pdev->dev, &int3472->sensor, -+ &int3472->sensor_name); -+ if (ret) -+ return ret; - - /* - * Initialising this list means we can call gpiod_remove_lookup_table() -@@ -390,11 +381,6 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) - } - - return 0; -- --err_put_sensor: -- acpi_dev_put(int3472->sensor); -- -- return ret; - } - - static int skl_int3472_discrete_remove(struct platform_device *pdev) --- -2.35.1 - -From ce6d00f1afc889c5cd0b93e8d7c4c26568ed5ade Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:05 +0200 -Subject: [PATCH] platform/x86: int3472: Pass tps68470_clk_platform_data to the - tps68470-regulator MFD-cell - -Pass tps68470_clk_platform_data to the tps68470-clk MFD-cell, -so that sensors which use the TPS68470 can find their clock. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/tps68470.c | 33 ++++++++++++++----- - 1 file changed, 25 insertions(+), 8 deletions(-) - -diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -index b94cf66ab61f..78e34e7b6969 100644 ---- a/drivers/platform/x86/intel/int3472/tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -5,6 +5,7 @@ - #include - #include - #include -+#include - #include - - #include "common.h" -@@ -17,12 +18,6 @@ static const struct mfd_cell tps68470_cros[] = { - { .name = "tps68470_pmic_opregion" }, - }; - --static const struct mfd_cell tps68470_win[] = { -- { .name = "tps68470-gpio" }, -- { .name = "tps68470-clk" }, -- { .name = "tps68470-regulator" }, --}; -- - static const struct regmap_config tps68470_regmap_config = { - .reg_bits = 8, - .val_bits = 8, -@@ -105,10 +100,17 @@ static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) - static int skl_int3472_tps68470_probe(struct i2c_client *client) - { - struct acpi_device *adev = ACPI_COMPANION(&client->dev); -+ struct tps68470_clk_platform_data clk_pdata = {}; -+ struct mfd_cell *cells; - struct regmap *regmap; - int device_type; - int ret; - -+ ret = skl_int3472_get_sensor_adev_and_name(&client->dev, NULL, -+ &clk_pdata.consumer_dev_name); -+ if (ret) -+ return ret; -+ - regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); - if (IS_ERR(regmap)) { - dev_err(&client->dev, "Failed to create regmap: %ld\n", PTR_ERR(regmap)); -@@ -126,9 +128,24 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - device_type = skl_int3472_tps68470_calc_type(adev); - switch (device_type) { - case DESIGNED_FOR_WINDOWS: -- ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, -- tps68470_win, ARRAY_SIZE(tps68470_win), -+ cells = kcalloc(3, sizeof(*cells), GFP_KERNEL); -+ if (!cells) -+ return -ENOMEM; -+ -+ cells[0].name = "tps68470-clk"; -+ cells[0].platform_data = &clk_pdata; -+ cells[0].pdata_size = sizeof(clk_pdata); -+ cells[1].name = "tps68470-regulator"; -+ /* -+ * The GPIO cell must be last because acpi_gpiochip_add() calls -+ * acpi_dev_clear_dependencies() and the clk + regulators must -+ * be ready when this happens. -+ */ -+ cells[2].name = "tps68470-gpio"; -+ -+ ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, cells, 3, - NULL, 0, NULL); -+ kfree(cells); - break; - case DESIGNED_FOR_CHROMEOS: - ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, --- -2.35.1 - -From 16cd889346121483b60ecde95f20bc2b1fe1eb19 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:06 +0200 -Subject: [PATCH] platform/x86: int3472: Pass tps68470_regulator_platform_data - to the tps68470-regulator MFD-cell - -Pass tps68470_regulator_platform_data to the tps68470-regulator -MFD-cell, specifying the voltages of the various regulators and -tying the regulators to the sensor supplies so that sensors which use -the TPS68470 can find their regulators. - -Since the voltages and supply connections are board-specific, this -introduces a DMI matches int3472_tps68470_board_data struct which -contains the necessary per-board info. - -This per-board info also includes GPIO lookup information for the -sensor GPIOs which may be connected to the tps68470 gpios. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/Makefile | 2 +- - drivers/platform/x86/intel/int3472/tps68470.c | 43 +++++-- - drivers/platform/x86/intel/int3472/tps68470.h | 25 ++++ - .../x86/intel/int3472/tps68470_board_data.c | 118 ++++++++++++++++++ - 4 files changed, 180 insertions(+), 8 deletions(-) - create mode 100644 drivers/platform/x86/intel/int3472/tps68470.h - create mode 100644 drivers/platform/x86/intel/int3472/tps68470_board_data.c - -diff --git a/drivers/platform/x86/intel/int3472/Makefile b/drivers/platform/x86/intel/int3472/Makefile -index 4a4b2518ea16..ca56e7eea781 100644 ---- a/drivers/platform/x86/intel/int3472/Makefile -+++ b/drivers/platform/x86/intel/int3472/Makefile -@@ -1,4 +1,4 @@ - obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472_discrete.o \ - intel_skl_int3472_tps68470.o - intel_skl_int3472_discrete-y := discrete.o clk_and_regulator.o common.o --intel_skl_int3472_tps68470-y := tps68470.o common.o -+intel_skl_int3472_tps68470-y := tps68470.o tps68470_board_data.o common.o -diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -index 78e34e7b6969..aae24d228770 100644 ---- a/drivers/platform/x86/intel/int3472/tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -9,6 +9,7 @@ - #include - - #include "common.h" -+#include "tps68470.h" - - #define DESIGNED_FOR_CHROMEOS 1 - #define DESIGNED_FOR_WINDOWS 2 -@@ -100,6 +101,7 @@ static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) - static int skl_int3472_tps68470_probe(struct i2c_client *client) - { - struct acpi_device *adev = ACPI_COMPANION(&client->dev); -+ const struct int3472_tps68470_board_data *board_data; - struct tps68470_clk_platform_data clk_pdata = {}; - struct mfd_cell *cells; - struct regmap *regmap; -@@ -128,6 +130,12 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - device_type = skl_int3472_tps68470_calc_type(adev); - switch (device_type) { - case DESIGNED_FOR_WINDOWS: -+ board_data = int3472_tps68470_get_board_data(dev_name(&client->dev)); -+ if (!board_data) { -+ dev_err(&client->dev, "No board-data found for this laptop/tablet model\n"); -+ return -ENODEV; -+ } -+ - cells = kcalloc(3, sizeof(*cells), GFP_KERNEL); - if (!cells) - return -ENOMEM; -@@ -136,6 +144,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - cells[0].platform_data = &clk_pdata; - cells[0].pdata_size = sizeof(clk_pdata); - cells[1].name = "tps68470-regulator"; -+ cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; -+ cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); - /* - * The GPIO cell must be last because acpi_gpiochip_add() calls - * acpi_dev_clear_dependencies() and the clk + regulators must -@@ -143,9 +153,15 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - */ - cells[2].name = "tps68470-gpio"; - -+ gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_table); -+ - ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, cells, 3, - NULL, 0, NULL); - kfree(cells); -+ -+ if (ret) -+ gpiod_remove_lookup_table(board_data->tps68470_gpio_lookup_table); -+ - break; - case DESIGNED_FOR_CHROMEOS: - ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, -@@ -160,18 +176,31 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - return ret; - } - -+static int skl_int3472_tps68470_remove(struct i2c_client *client) -+{ -+ const struct int3472_tps68470_board_data *board_data; -+ -+ board_data = int3472_tps68470_get_board_data(dev_name(&client->dev)); -+ if (board_data) -+ gpiod_remove_lookup_table(board_data->tps68470_gpio_lookup_table); -+ -+ return 0; -+} -+ -+ - static const struct acpi_device_id int3472_device_id[] = { -- { "INT3472", 0 }, -- { } -+ { "INT3472", 0 }, -+ { } - }; - MODULE_DEVICE_TABLE(acpi, int3472_device_id); - - static struct i2c_driver int3472_tps68470 = { -- .driver = { -- .name = "int3472-tps68470", -- .acpi_match_table = int3472_device_id, -- }, -- .probe_new = skl_int3472_tps68470_probe, -+ .driver = { -+ .name = "int3472-tps68470", -+ .acpi_match_table = int3472_device_id, -+ }, -+ .probe_new = skl_int3472_tps68470_probe, -+ .remove = skl_int3472_tps68470_remove, - }; - module_i2c_driver(int3472_tps68470); - -diff --git a/drivers/platform/x86/intel/int3472/tps68470.h b/drivers/platform/x86/intel/int3472/tps68470.h -new file mode 100644 -index 000000000000..cfd33eb62740 ---- /dev/null -+++ b/drivers/platform/x86/intel/int3472/tps68470.h -@@ -0,0 +1,25 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+/* -+ * TI TPS68470 PMIC platform data definition. -+ * -+ * Copyright (c) 2021 Red Hat Inc. -+ * -+ * Red Hat authors: -+ * Hans de Goede -+ */ -+ -+#ifndef _INTEL_SKL_INT3472_TPS68470_H -+#define _INTEL_SKL_INT3472_TPS68470_H -+ -+struct gpiod_lookup_table; -+struct tps68470_regulator_platform_data; -+ -+struct int3472_tps68470_board_data { -+ const char *dev_name; -+ struct gpiod_lookup_table *tps68470_gpio_lookup_table; -+ const struct tps68470_regulator_platform_data *tps68470_regulator_pdata; -+}; -+ -+const struct int3472_tps68470_board_data *int3472_tps68470_get_board_data(const char *dev_name); -+ -+#endif -diff --git a/drivers/platform/x86/intel/int3472/tps68470_board_data.c b/drivers/platform/x86/intel/int3472/tps68470_board_data.c -new file mode 100644 -index 000000000000..96954a789bb8 ---- /dev/null -+++ b/drivers/platform/x86/intel/int3472/tps68470_board_data.c -@@ -0,0 +1,118 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * TI TPS68470 PMIC platform data definition. -+ * -+ * Copyright (c) 2021 Dan Scally -+ * Copyright (c) 2021 Red Hat Inc. -+ * -+ * Red Hat authors: -+ * Hans de Goede -+ */ -+ -+#include -+#include -+#include -+#include -+#include "tps68470.h" -+ -+static struct regulator_consumer_supply int347a_core_consumer_supplies[] = { -+ REGULATOR_SUPPLY("dvdd", "i2c-INT347A:00"), -+}; -+ -+static struct regulator_consumer_supply int347a_ana_consumer_supplies[] = { -+ REGULATOR_SUPPLY("avdd", "i2c-INT347A:00"), -+}; -+ -+static struct regulator_consumer_supply int347a_vsio_consumer_supplies[] = { -+ REGULATOR_SUPPLY("dovdd", "i2c-INT347A:00"), -+}; -+ -+static const struct regulator_init_data surface_go_tps68470_core_reg_init_data = { -+ .constraints = { -+ .min_uV = 1200000, -+ .max_uV = 1200000, -+ .apply_uV = 1, -+ .valid_ops_mask = REGULATOR_CHANGE_STATUS, -+ }, -+ .num_consumer_supplies = ARRAY_SIZE(int347a_core_consumer_supplies), -+ .consumer_supplies = int347a_core_consumer_supplies, -+}; -+ -+static const struct regulator_init_data surface_go_tps68470_ana_reg_init_data = { -+ .constraints = { -+ .min_uV = 2815200, -+ .max_uV = 2815200, -+ .apply_uV = 1, -+ .valid_ops_mask = REGULATOR_CHANGE_STATUS, -+ }, -+ .num_consumer_supplies = ARRAY_SIZE(int347a_ana_consumer_supplies), -+ .consumer_supplies = int347a_ana_consumer_supplies, -+}; -+ -+static const struct regulator_init_data surface_go_tps68470_vsio_reg_init_data = { -+ .constraints = { -+ .min_uV = 1800600, -+ .max_uV = 1800600, -+ .apply_uV = 1, -+ .valid_ops_mask = REGULATOR_CHANGE_STATUS, -+ }, -+ .num_consumer_supplies = ARRAY_SIZE(int347a_vsio_consumer_supplies), -+ .consumer_supplies = int347a_vsio_consumer_supplies, -+}; -+ -+static const struct tps68470_regulator_platform_data surface_go_tps68470_pdata = { -+ .reg_init_data = { -+ [TPS68470_CORE] = &surface_go_tps68470_core_reg_init_data, -+ [TPS68470_ANA] = &surface_go_tps68470_ana_reg_init_data, -+ [TPS68470_VSIO] = &surface_go_tps68470_vsio_reg_init_data, -+ }, -+}; -+ -+static struct gpiod_lookup_table surface_go_tps68470_gpios = { -+ .dev_id = "i2c-INT347A:00", -+ .table = { -+ GPIO_LOOKUP("tps68470-gpio", 9, "reset", GPIO_ACTIVE_LOW), -+ GPIO_LOOKUP("tps68470-gpio", 7, "powerdown", GPIO_ACTIVE_LOW) -+ } -+}; -+ -+static const struct int3472_tps68470_board_data surface_go_tps68470_board_data = { -+ .dev_name = "i2c-INT3472:05", -+ .tps68470_gpio_lookup_table = &surface_go_tps68470_gpios, -+ .tps68470_regulator_pdata = &surface_go_tps68470_pdata, -+}; -+ -+static const struct dmi_system_id int3472_tps68470_board_data_table[] = { -+ { -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Go"), -+ }, -+ .driver_data = (void *)&surface_go_tps68470_board_data, -+ }, -+ { -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Go 2"), -+ }, -+ .driver_data = (void *)&surface_go_tps68470_board_data, -+ }, -+ { } -+}; -+ -+const struct int3472_tps68470_board_data *int3472_tps68470_get_board_data(const char *dev_name) -+{ -+ const struct int3472_tps68470_board_data *board_data; -+ const struct dmi_system_id *match; -+ -+ match = dmi_first_match(int3472_tps68470_board_data_table); -+ while (match) { -+ board_data = match->driver_data; -+ if (strcmp(board_data->dev_name, dev_name) == 0) -+ return board_data; -+ -+ dmi_first_match(++match); -+ } -+ -+ return NULL; -+} --- -2.35.1 - -From ebdde93fb0053e945e6fe053a14b150d299c2500 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:57:07 +0200 -Subject: [PATCH] platform/x86: int3472: Deal with probe ordering issues - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around this info missing from the ACPI tables on devices where -the int3472 driver is used, the int3472 MFD-cell drivers attach info about -consumers to the clks/regulators when registering these. - -This causes problems with the probe ordering wrt drivers for consumers -of these clks/regulators. Since the lookups are only registered when the -provider-driver binds, trying to get these clks/regulators before then -results in a -ENOENT error for clks and a dummy regulator for regulators. - -All the sensor ACPI fw-nodes have a _DEP dependency on the INT3472 ACPI -fw-node, so to work around these probe ordering issues the ACPI core / -i2c-code does not instantiate the I2C-clients for any ACPI devices -which have a _DEP dependency on an INT3472 ACPI device until all -_DEP-s are met. - -This relies on acpi_dev_clear_dependencies() getting called by the driver -for the _DEP-s when they are ready, add a acpi_dev_clear_dependencies() -call to the discrete.c probe code. - -In the tps68470 case calling acpi_dev_clear_dependencies() is already done -by the acpi_gpiochip_add() call done by the driver for the GPIO MFD cell -(The GPIO cell is deliberately the last cell created to make sure the -clk + regulator cells are already instantiated when this happens). - -However for proper probe ordering, the clk/regulator cells must not just -be instantiated the must be fully ready (the clks + regulators must be -registered with their subsystems). - -Add MODULE_SOFTDEP dependencies for the clk and regulator drivers for -the instantiated MFD-cells so that these are loaded before us and so -that they bind immediately when the platform-devs are instantiated. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/discrete.c | 1 + - drivers/platform/x86/intel/int3472/tps68470.c | 6 ++++++ - 2 files changed, 7 insertions(+) - -diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c -index efd31a0c7a88..18e6d51acc96 100644 ---- a/drivers/platform/x86/intel/int3472/discrete.c -+++ b/drivers/platform/x86/intel/int3472/discrete.c -@@ -380,6 +380,7 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) - return ret; - } - -+ acpi_dev_clear_dependencies(adev); - return 0; - } - -diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -index aae24d228770..21c6c1a6edfc 100644 ---- a/drivers/platform/x86/intel/int3472/tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -173,6 +173,11 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) - return device_type; - } - -+ /* -+ * No acpi_dev_clear_dependencies() here, since the acpi_gpiochip_add() -+ * for the GPIO cell already does this. -+ */ -+ - return ret; - } - -@@ -207,3 +212,4 @@ module_i2c_driver(int3472_tps68470); - MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI TPS68470 Device Driver"); - MODULE_AUTHOR("Daniel Scally "); - MODULE_LICENSE("GPL v2"); -+MODULE_SOFTDEP("pre: clk-tps68470 tps68470-regulator"); --- -2.35.1 - -From 12c17f7f09169af045cef3b15728c98403336c6c Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 4 Nov 2021 21:46:27 +0000 -Subject: [PATCH] media: i2c: Add integration time margin to ov8865 - -Without this integration margin to reduce the max exposure, it seems -that we trip over a limit that results in the image being entirely -black when max exposure is set. Add the margin to prevent this issue. - -With thanks to jhautbois for spotting and reporting. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 4 +++- - 1 file changed, 3 insertions(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index 5ef9c407362a..ed038efbc084 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -143,6 +143,7 @@ - #define OV8865_EXPOSURE_CTRL_L_REG 0x3502 - #define OV8865_EXPOSURE_CTRL_L(v) ((v) & GENMASK(7, 0)) - #define OV8865_EXPOSURE_GAIN_MANUAL_REG 0x3503 -+#define OV8865_INTEGRATION_TIME_MARGIN 8 - - #define OV8865_GAIN_CTRL_H_REG 0x3508 - #define OV8865_GAIN_CTRL_H(v) (((v) & GENMASK(12, 8)) >> 8) -@@ -2464,7 +2465,8 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) - if (ctrl->id == V4L2_CID_VBLANK) { - int exposure_max; - -- exposure_max = sensor->state.mode->output_size_y + ctrl->val; -+ exposure_max = sensor->state.mode->output_size_y + ctrl->val - -+ OV8865_INTEGRATION_TIME_MARGIN; - __v4l2_ctrl_modify_range(sensor->ctrls.exposure, - sensor->ctrls.exposure->minimum, - exposure_max, --- -2.35.1 - -From c7b51fa424ff2bf8398c412154017066e63fd909 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 4 Nov 2021 21:48:38 +0000 -Subject: [PATCH] media: i2c: Fix max gain in ov8865 - -The maximum gain figure in the v4l2 ctrl is wrong. The field is 12 bits -wide, which is where the 8191 figure comes from, but the datasheet is -specific that maximum gain is 16x (the low seven bits are fractional, so -16x gain is 2048) - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/i2c/ov8865.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c -index ed038efbc084..5bedcddafe36 100644 ---- a/drivers/media/i2c/ov8865.c -+++ b/drivers/media/i2c/ov8865.c -@@ -2537,7 +2537,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) - - /* Gain */ - -- v4l2_ctrl_new_std(handler, ops, V4L2_CID_ANALOGUE_GAIN, 128, 8191, 128, -+ v4l2_ctrl_new_std(handler, ops, V4L2_CID_ANALOGUE_GAIN, 128, 2048, 128, - 128); - - /* White Balance */ --- -2.35.1 - -From 6812a707a23ec35749f529e15eae3ff5a32bd0af Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Fri, 3 Dec 2021 12:51:08 +0100 -Subject: [PATCH] mfd: intel-lpss: Fix I2C4 not being available on the - Microsoft Surface Go & Go 2 - -Many DSDTs for Kaby Lake and Kaby Lake Refresh models contain a -_SB.PCI0.GEXP ACPI Device node describing an I2C attached PCA953x -GPIO expander. - -This seems to be something which is copy and pasted from the DSDT -from some reference design since this ACPI Device is present even on -models where no such GPIO expander is used at all, such as on the -Microsoft Surface Go & Go 2. - -This ACPI Device is a problem because it contains a SystemMemory -OperationRegion which covers the MMIO for the I2C4 I2C controller. This -causes the MFD cell for the I2C4 controller to not be instantiated due -to a resource conflict, requiring the use of acpi_enforce_resources=lax -to work around this. - -I have done an extensive analysis of all the ACPI tables on the -Microsoft Surface Go and the _SB.PCI0.GEXP ACPI Device's methods are -not used by any code in the ACPI tables, neither are any of them -directly called by any Linux kernel code. This is unsurprising since -running i2cdetect on the I2C4 bus shows that there is no GPIO -expander chip present on these devices at all. - -This commit adds a PCI subsystem vendor:device table listing PCI devices -where it is known to be safe to ignore resource conflicts with ACPI -declared SystemMemory regions. - -This makes the I2C4 bus work out of the box on the Microsoft Surface -Go & Go 2, which is necessary for the cameras on these devices to work. - -Cc: Dan Scally -Cc: Kate Hsuan -Cc: Maximilian Luz -Reviewed-by: Laurent Pinchart -Reviewed-by: Andy Shevchenko -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/mfd/intel-lpss-pci.c | 12 ++++++++++++ - drivers/mfd/intel-lpss.c | 1 + - drivers/mfd/intel-lpss.h | 1 + - 3 files changed, 14 insertions(+) - -diff --git a/drivers/mfd/intel-lpss-pci.c b/drivers/mfd/intel-lpss-pci.c -index f70464ce8e3d..22eefd71f8c4 100644 ---- a/drivers/mfd/intel-lpss-pci.c -+++ b/drivers/mfd/intel-lpss-pci.c -@@ -17,6 +17,15 @@ - - #include "intel-lpss.h" - -+/* Some DSDTs have an unused GEXP ACPI device conflicting with I2C4 resources */ -+static const struct pci_device_id ignore_resource_conflicts_ids[] = { -+ /* Microsoft Surface Go (version 1) I2C4 */ -+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_INTEL, 0x9d64, 0x152d, 0x1182), }, -+ /* Microsoft Surface Go 2 I2C4 */ -+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_INTEL, 0x9d64, 0x152d, 0x1237), }, -+ { } -+}; -+ - static int intel_lpss_pci_probe(struct pci_dev *pdev, - const struct pci_device_id *id) - { -@@ -35,6 +44,9 @@ static int intel_lpss_pci_probe(struct pci_dev *pdev, - info->mem = &pdev->resource[0]; - info->irq = pdev->irq; - -+ if (pci_match_id(ignore_resource_conflicts_ids, pdev)) -+ info->ignore_resource_conflicts = true; -+ - pdev->d3cold_delay = 0; - - /* Probably it is enough to set this for iDMA capable devices only */ -diff --git a/drivers/mfd/intel-lpss.c b/drivers/mfd/intel-lpss.c -index 0e15afc39f54..cfbee2cfba6b 100644 ---- a/drivers/mfd/intel-lpss.c -+++ b/drivers/mfd/intel-lpss.c -@@ -401,6 +401,7 @@ int intel_lpss_probe(struct device *dev, - return ret; - - lpss->cell->swnode = info->swnode; -+ lpss->cell->ignore_resource_conflicts = info->ignore_resource_conflicts; - - intel_lpss_init_dev(lpss); - -diff --git a/drivers/mfd/intel-lpss.h b/drivers/mfd/intel-lpss.h -index 22dbc4aed793..062ce95b68b9 100644 ---- a/drivers/mfd/intel-lpss.h -+++ b/drivers/mfd/intel-lpss.h -@@ -19,6 +19,7 @@ struct software_node; - - struct intel_lpss_platform_info { - struct resource *mem; -+ bool ignore_resource_conflicts; - int irq; - unsigned long clk_rate; - const char *clk_con_id; --- -2.35.1 - -From ffbd58376161c9353d29e8af0438114e37bd9e57 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 6 Jan 2022 22:12:38 +0000 -Subject: [PATCH] platform/x86: int3472: Add board data for Surface Go 3 - -The Surface Go 3 needs some board data in order to configure the -TPS68470 PMIC - add entries to the tables in tps68470_board_data.c -that define the configuration that's needed. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - .../x86/intel/int3472/tps68470_board_data.c | 13 +++++++++++++ - 1 file changed, 13 insertions(+) - -diff --git a/drivers/platform/x86/intel/int3472/tps68470_board_data.c b/drivers/platform/x86/intel/int3472/tps68470_board_data.c -index 96954a789bb8..2dcadfa62196 100644 ---- a/drivers/platform/x86/intel/int3472/tps68470_board_data.c -+++ b/drivers/platform/x86/intel/int3472/tps68470_board_data.c -@@ -82,6 +82,12 @@ static const struct int3472_tps68470_board_data surface_go_tps68470_board_data = - .tps68470_regulator_pdata = &surface_go_tps68470_pdata, - }; - -+static const struct int3472_tps68470_board_data surface_go3_tps68470_board_data = { -+ .dev_name = "i2c-INT3472:01", -+ .tps68470_gpio_lookup_table = &surface_go_tps68470_gpios, -+ .tps68470_regulator_pdata = &surface_go_tps68470_pdata, -+}; -+ - static const struct dmi_system_id int3472_tps68470_board_data_table[] = { - { - .matches = { -@@ -97,6 +103,13 @@ static const struct dmi_system_id int3472_tps68470_board_data_table[] = { - }, - .driver_data = (void *)&surface_go_tps68470_board_data, - }, -+ { -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Go 3"), -+ }, -+ .driver_data = (void *)&surface_go3_tps68470_board_data, -+ }, - { } - }; - --- -2.35.1 - diff --git a/patches/5.16/0011-amd-gpio.patch b/patches/5.16/0011-amd-gpio.patch deleted file mode 100644 index d731ae742..000000000 --- a/patches/5.16/0011-amd-gpio.patch +++ /dev/null @@ -1,109 +0,0 @@ -From 5f73c0ef09d059062b1596eb22b9f8ba6625f3c5 Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Sat, 29 May 2021 17:47:38 +1000 -Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 - override - -This patch is the work of Thomas Gleixner and is -copied from: -https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ - -This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin -setup that is missing in the laptops ACPI table. - -This patch was used for validation of the issue, and is not a proper -fix, but is probably a better temporary hack than continuing to probe -the Legacy PIC and run with the PIC in an unknown state. - -Patchset: amd-gpio ---- - arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ - 1 file changed, 17 insertions(+) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 5b6d1a95776f..0a05e196419a 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -22,6 +22,7 @@ - #include - #include - #include -+#include - - #include - #include -@@ -1152,6 +1153,17 @@ static void __init mp_config_acpi_legacy_irqs(void) - } - } - -+static const struct dmi_system_id surface_quirk[] __initconst = { -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") -+ }, -+ }, -+ {} -+}; -+ - /* - * Parse IOAPIC related entries in MADT - * returns 0 on success, < 0 on error -@@ -1207,6 +1219,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) - acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, - acpi_gbl_FADT.sci_interrupt); - -+ if (dmi_check_system(surface_quirk)) { -+ pr_warn("Surface hack: Override irq 7\n"); -+ mp_override_legacy_irq(7, 3, 3, 7); -+ } -+ - /* Fill in identity legacy mappings where no override */ - mp_config_acpi_legacy_irqs(); - --- -2.35.1 - -From 1d96852a8adf322ce139bbf7e850a8f076d88dd6 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 3 Jun 2021 14:04:26 +0200 -Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override - quirk - -The 13" version of the Surface Laptop 4 has the same problem as the 15" -version, but uses a different SKU. Add that SKU to the quirk as well. - -Patchset: amd-gpio ---- - arch/x86/kernel/acpi/boot.c | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 0a05e196419a..35de5613980a 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -1155,12 +1155,19 @@ static void __init mp_config_acpi_legacy_irqs(void) - - static const struct dmi_system_id surface_quirk[] __initconst = { - { -- .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") - }, - }, -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") -+ }, -+ }, - {} - }; - --- -2.35.1 - diff --git a/patches/5.16/0012-misc-fixes.patch b/patches/5.16/0012-misc-fixes.patch deleted file mode 100644 index 24376ba9d..000000000 --- a/patches/5.16/0012-misc-fixes.patch +++ /dev/null @@ -1,111 +0,0 @@ -From 0169488e6792b0939ec350c59b608e744faf3fc4 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 8 Dec 2021 16:22:50 +0100 -Subject: [PATCH] acpi/battery: Add device HID and quirk for Microsoft Surface - Go 3 - -For some reason, the Microsoft Surface Go 3 uses the standard ACPI -interface for battery information, but does not use the standard PNP0C0A -HID. Instead it uses MSHW0146 as identifier. Add that ID to the driver -as this seems to work well. - -Additionally, the power state is not updated immediately after the AC -has been (un-)plugged, so add the respective quirk for that. - -Signed-off-by: Maximilian Luz -Patchset: misc-fixes ---- - drivers/acpi/battery.c | 12 ++++++++++++ - 1 file changed, 12 insertions(+) - -diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c -index ead0114f27c9..56db7b4da514 100644 ---- a/drivers/acpi/battery.c -+++ b/drivers/acpi/battery.c -@@ -60,6 +60,10 @@ MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); - - static const struct acpi_device_id battery_device_ids[] = { - {"PNP0C0A", 0}, -+ -+ /* Microsoft Surface Go 3 */ -+ {"MSHW0146", 0}, -+ - {"", 0}, - }; - -@@ -1177,6 +1181,14 @@ static const struct dmi_system_id bat_dmi_table[] __initconst = { - DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad"), - }, - }, -+ { -+ /* Microsoft Surface Go 3 */ -+ .callback = battery_notification_delay_quirk, -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Go 3"), -+ }, -+ }, - {}, - }; - --- -2.35.1 - -From ed246dbe6d9a05f195f78fb1b325b088add2321a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Feb 2022 01:29:48 +0100 -Subject: [PATCH] ACPI: battery: Add "Not Charging" quirk for Microsoft Surface - devices - -Microsoft Surface devices have a limiter that sets a fixed maximum -charge capacity for the battery. When that maximum capacity has been -reached, charging stops. In that case, _BST returns a battery state -field with both "charging" and "discharging" bits cleared. The battery -driver, however, returns "unknown" as status. - -This seems to be the same behavior as observed on the ThinkPads, so -let's use the same quirk to handle that as well. - -Signed-off-by: Maximilian Luz -Patchset: misc-fixes - ---- -For what it's worth, I don't think the ACPI spec explicitly states that -any of the status bits need to be set, or that there are only the -"charging" and "discharging" states. As far as I can tell, ACPI only -states: - - Notice that the Charging bit and the Discharging bit are mutually - exclusive and must not both be set at the same time. Even in - critical state, hardware should report the corresponding - charging/discharging state. - -But that does not exclude the case that no bit is set. So, strictly -going by spec, I don't think it's necessary to put all of this behind a -quirk. ---- - drivers/acpi/battery.c | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c -index 56db7b4da514..8edaa3020af3 100644 ---- a/drivers/acpi/battery.c -+++ b/drivers/acpi/battery.c -@@ -1181,6 +1181,14 @@ static const struct dmi_system_id bat_dmi_table[] __initconst = { - DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad"), - }, - }, -+ { -+ .callback = battery_quirk_not_charging, -+ .ident = "Microsoft Surface", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "Surface"), -+ }, -+ }, - { - /* Microsoft Surface Go 3 */ - .callback = battery_notification_delay_quirk, --- -2.35.1 - diff --git a/patches/5.17/0001-surface3-oemb.patch b/patches/5.17/0001-surface3-oemb.patch deleted file mode 100644 index dae7c4407..000000000 --- a/patches/5.17/0001-surface3-oemb.patch +++ /dev/null @@ -1,101 +0,0 @@ -From d71e66bf8025abea7306b59ce5c27bc4384dc199 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 18 Oct 2020 16:42:44 +0900 -Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI - table - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI data into dmi_system_id tables used -for quirks so that each driver can enable quirks even on the affected -systems. - -On affected systems, DMI data will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: surface3-oemb ---- - drivers/platform/surface/surface3-wmi.c | 7 +++++++ - sound/soc/codecs/rt5645.c | 9 +++++++++ - sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ - 3 files changed, 24 insertions(+) - -diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c -index 09ac9cfc40d8..c626109cf445 100644 ---- a/drivers/platform/surface/surface3-wmi.c -+++ b/drivers/platform/surface/surface3-wmi.c -@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - #endif - { } - }; -diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c -index 4b2e027c1033..dc96ec7bcbd5 100644 ---- a/sound/soc/codecs/rt5645.c -+++ b/sound/soc/codecs/rt5645.c -@@ -3718,6 +3718,15 @@ static const struct dmi_system_id dmi_platform_data[] = { - }, - .driver_data = (void *)&intel_braswell_platform_data, - }, -+ { -+ .ident = "Microsoft Surface 3", -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)&intel_braswell_platform_data, -+ }, - { - /* - * Match for the GPDwin which unfortunately uses somewhat -diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -index c60a5e8e7bc9..e947133a2c36 100644 ---- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c -+++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .callback = cht_surface_quirk_cb, -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - { } - }; - --- -2.36.1 - diff --git a/patches/5.17/0002-mwifiex.patch b/patches/5.17/0002-mwifiex.patch deleted file mode 100644 index 13cd05b89..000000000 --- a/patches/5.17/0002-mwifiex.patch +++ /dev/null @@ -1,704 +0,0 @@ -From 3598ab0e8f35bdfac671a9a7193b77dce8b08abb Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:32:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_wsid quirk for Surface 3 - -This commit adds reset_wsid quirk and uses this quirk for Surface 3 on -card reset. - -To reset mwifiex on Surface 3, it seems that calling the _DSM method -exists in \_SB.WSID [1] device is required. - -On Surface 3, calling the _DSM method removes/re-probes the card by -itself. So, need to place the reset function before performing FLR and -skip performing any other reset-related works. - -Note that Surface Pro 3 also has the WSID device [2], but it seems to need -more work. This commit only supports Surface 3 yet. - -[1] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_3/dsdt.dsl#L11947-L12011 -[2] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_pro_3/dsdt.dsl#L12164-L12216 - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 10 +++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 83 +++++++++++++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.h | 6 ++ - 3 files changed, 99 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index d5fb29400bad..033648526f16 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -2993,6 +2993,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - -+ /* On Surface 3, reset_wsid method removes then re-probes card by -+ * itself. So, need to place it here and skip performing any other -+ * reset-related works. -+ */ -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) { -+ mwifiex_pcie_reset_wsid_quirk(card->dev); -+ /* skip performing any other reset-related works */ -+ return; -+ } -+ - /* We can't afford to wait here; remove() might be waiting on us. If we - * can't grab the device lock, maybe we'll get another chance later. - */ -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 0234cf3c2974..563dd0d5ac79 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -15,10 +15,21 @@ - * this warranty disclaimer. - */ - -+#include - #include - - #include "pcie_quirks.h" - -+/* For reset_wsid quirk */ -+#define ACPI_WSID_PATH "\\_SB.WSID" -+#define WSID_REV 0x0 -+#define WSID_FUNC_WIFI_PWR_OFF 0x1 -+#define WSID_FUNC_WIFI_PWR_ON 0x2 -+/* WSID _DSM UUID: "534ea3bf-fcc2-4e7a-908f-a13978f0c7ef" */ -+static const guid_t wsid_dsm_guid = -+ GUID_INIT(0x534ea3bf, 0xfcc2, 0x4e7a, -+ 0x90, 0x8f, 0xa1, 0x39, 0x78, 0xf0, 0xc7, 0xef); -+ - /* quirk table based on DMI matching */ - static const struct dmi_system_id mwifiex_quirk_table[] = { - { -@@ -87,6 +98,14 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - {} - }; - -@@ -103,6 +122,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - dev_info(&pdev->dev, "no quirks enabled\n"); - if (card->quirks & QUIRK_FW_RST_D3COLD) - dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) -+ dev_info(&pdev->dev, -+ "quirk reset_wsid for Surface 3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -@@ -159,3 +181,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) - - return 0; - } -+ -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev) -+{ -+ acpi_handle handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n"); -+ -+ status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&pdev->dev, "No ACPI handle for path %s\n", -+ ACPI_WSID_PATH); -+ return -ENODEV; -+ } -+ -+ if (!acpi_has_method(handle, "_DSM")) { -+ dev_err(&pdev->dev, "_DSM method not found\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power off func\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power on func\n"); -+ return -ENODEV; -+ } -+ -+ /* card will be removed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi off...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi off\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ /* card will be re-probed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi on...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi on\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ return 0; -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 8ec4176d698f..25370c5a4f59 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -19,5 +19,11 @@ - - #define QUIRK_FW_RST_D3COLD BIT(0) - -+/* Surface 3 and Surface Pro 3 have the same _DSM method but need to -+ * be handled differently. Currently, only S3 is supported. -+ */ -+#define QUIRK_FW_RST_WSID_S3 BIT(1) -+ - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); --- -2.36.1 - -From 8b9aac712c20f228f3e99c0583a31bcbbdec28a3 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Wed, 30 Sep 2020 18:08:24 +0900 -Subject: [PATCH] mwifiex: pcie: (OEMB) add quirk for Surface 3 with broken DMI - table - -(made referring to http://git.osdn.net/view?p=android-x86/kernel.git;a=commitdiff;h=18e2e857c57633b25b3b4120f212224a108cd883) - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI info for the affected Surface 3. - -On affected systems, DMI info will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie_quirks.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 563dd0d5ac79..32e2f000e57b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -106,6 +106,15 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - {} - }; - --- -2.36.1 - -From 8dbbbf65898f70d4f76e031f2f67b0093ad1998f Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 4 Oct 2020 00:11:49 +0900 -Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ - -Currently, mwifiex fw will crash after suspend on recent kernel series. -On Windows, it seems that the root port of wifi will never enter D3 state -(stay on D0 state). And on Linux, disabling the D3 state for the -bridge fixes fw crashing after suspend. - -This commit disables the D3 state of root port on driver initialization -and fixes fw crashing after suspend. - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 27 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 033648526f16..ca6bcbe4794c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -380,6 +380,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) - { - struct pcie_service_card *card; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - int ret; - - pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", -@@ -421,6 +422,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return -1; - } - -+ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing -+ * after suspend -+ */ -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ parent_pdev->bridge_d3 = false; -+ - return 0; - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 32e2f000e57b..356401bab59c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -38,7 +38,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5", -@@ -47,7 +48,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -56,7 +58,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 6", -@@ -64,7 +67,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 1", -@@ -72,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 2", -@@ -80,7 +85,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 1", -@@ -88,7 +94,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 2", -@@ -96,7 +103,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface 3", -@@ -134,6 +142,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_FW_RST_WSID_S3) - dev_info(&pdev->dev, - "quirk reset_wsid for Surface 3 enabled\n"); -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ dev_info(&pdev->dev, -+ "quirk no_brigde_d3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 25370c5a4f59..a1de111ad1db 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -23,6 +23,7 @@ - * be handled differently. Currently, only S3 is supported. - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) -+#define QUIRK_NO_BRIDGE_D3 BIT(2) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.36.1 - -From 2c4af6971faa65871724e80bdd774091a066baf0 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 3 Nov 2020 13:28:04 +0100 -Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface - devices - -The most recent firmware of the 88W8897 card reports a hardcoded LTR -value to the system during initialization, probably as an (unsuccessful) -attempt of the developers to fix firmware crashes. This LTR value -prevents most of the Microsoft Surface devices from entering deep -powersaving states (either platform C-State 10 or S0ix state), because -the exit latency of that state would be higher than what the card can -tolerate. - -Turns out the card works just the same (including the firmware crashes) -no matter if that hardcoded LTR value is reported or not, so it's kind -of useless and only prevents us from saving power. - -To get rid of those hardcoded LTR reports, it's possible to reset the -PCI bridge device after initializing the cards firmware. I'm not exactly -sure why that works, maybe the power management subsystem of the PCH -resets its stored LTR values when doing a function level reset of the -bridge device. Doing the reset once after starting the wifi firmware -works very well, probably because the firmware only reports that LTR -value a single time during firmware startup. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 31 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index ca6bcbe4794c..24bcd22a2618 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -1781,9 +1781,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) - static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; -+ struct pci_dev *pdev = card->dev; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; - int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; - -+ /* Trigger a function level reset of the PCI bridge device, this makes -+ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value -+ * that prevents the system from entering package C10 and S0ix powersaving -+ * states. -+ * We need to do it here because it must happen after firmware -+ * initialization and this function is called after that is done. -+ */ -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ pci_reset_function(parent_pdev); -+ - /* Write the RX ring read pointer in to reg->rx_rdptr */ - if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | - tx_wrap)) { -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 356401bab59c..6437f067d07a 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -39,7 +39,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5", -@@ -49,7 +50,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -59,7 +61,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 6", -@@ -68,7 +71,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 1", -@@ -77,7 +81,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 2", -@@ -86,7 +91,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 1", -@@ -95,7 +101,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 2", -@@ -104,7 +111,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface 3", -@@ -145,6 +153,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_NO_BRIDGE_D3) - dev_info(&pdev->dev, - "quirk no_brigde_d3 enabled\n"); -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index a1de111ad1db..0e429779bb04 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -24,6 +24,7 @@ - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) - #define QUIRK_NO_BRIDGE_D3 BIT(2) -+#define QUIRK_DO_FLR_ON_BRIDGE BIT(3) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.36.1 - -From 2124e045c2e4c17db97bb1cd8ac6d3aa085d6e52 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 25 Mar 2021 11:33:02 +0100 -Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell - 88W8897 - -The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) -is used in a lot of Microsoft Surface devices, and all those devices -suffer from very low 2.4GHz wifi connection speeds while bluetooth is -enabled. The reason for that is that the default passive scanning -interval for Bluetooth Low Energy devices is quite high in Linux -(interval of 60 msec and scan window of 30 msec, see hci_core.c), and -the Marvell chip is known for its bad bt+wifi coexisting performance. - -So decrease that passive scan interval and make the scan window shorter -on this particular device to allow for spending more time transmitting -wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and -the new scan window is 6.25 msec (0xa * 0,625 msec). - -This change has a very large impact on the 2.4GHz wifi speeds and gets -it up to performance comparable with the Windows driver, which seems to -apply a similar quirk. - -The interval and window length were tested and found to work very well -with a lot of Bluetooth Low Energy devices, including the Surface Pen, a -Bluetooth Speaker and two modern Bluetooth headphones. All devices were -discovered immediately after turning them on. Even lower values were -also tested, but they introduced longer delays until devices get -discovered. - -Patchset: mwifiex ---- - drivers/bluetooth/btusb.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index 304351d2cfdf..e5d9fcd03f98 100644 ---- a/drivers/bluetooth/btusb.c -+++ b/drivers/bluetooth/btusb.c -@@ -63,6 +63,7 @@ static struct usb_driver btusb_driver; - #define BTUSB_INTEL_BROKEN_SHUTDOWN_LED 0x2000000 - #define BTUSB_INTEL_BROKEN_INITIAL_NCMD 0x4000000 - #define BTUSB_INTEL_NO_WBS_SUPPORT 0x8000000 -+#define BTUSB_LOWER_LESCAN_INTERVAL 0x10000000 - - static const struct usb_device_id btusb_table[] = { - /* Generic Bluetooth USB device */ -@@ -377,6 +378,7 @@ static const struct usb_device_id blacklist_table[] = { - { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, -+ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, - - /* Intel Bluetooth devices */ - { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, -@@ -3757,6 +3759,19 @@ static int btusb_probe(struct usb_interface *intf, - if (id->driver_info & BTUSB_MARVELL) - hdev->set_bdaddr = btusb_set_bdaddr_marvell; - -+ /* The Marvell 88W8897 combined wifi and bluetooth card is known for -+ * very bad bt+wifi coexisting performance. -+ * -+ * Decrease the passive BT Low Energy scan interval a bit -+ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter -+ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly -+ * higher wifi throughput while passively scanning for BT LE devices. -+ */ -+ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { -+ hdev->le_scan_interval = 0x0190; -+ hdev->le_scan_window = 0x000a; -+ } -+ - if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && - (id->driver_info & BTUSB_MEDIATEK)) { - hdev->setup = btusb_mtk_setup; --- -2.36.1 - -From 58e2ebec3bd715b4a68be78a7d3dd4fb52b63d8b Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 10 Nov 2020 12:49:56 +0100 -Subject: [PATCH] mwifiex: Use non-posted PCI register writes - -On the 88W8897 card it's very important the TX ring write pointer is -updated correctly to its new value before setting the TX ready -interrupt, otherwise the firmware appears to crash (probably because -it's trying to DMA-read from the wrong place). - -Since PCI uses "posted writes" when writing to a register, it's not -guaranteed that a write will happen immediately. That means the pointer -might be outdated when setting the TX ready interrupt, leading to -firmware crashes especially when ASPM L1 and L1 substates are enabled -(because of the higher link latency, the write will probably take -longer). - -So fix those firmware crashes by always forcing non-posted writes. We do -that by simply reading back the register after writing it, just as a lot -of other drivers do. - -There are two reproducers that are fixed with this patch: - -1) During rx/tx traffic and with ASPM L1 substates enabled (the enabled -substates are platform dependent), the firmware crashes and eventually a -command timeout appears in the logs. That crash is fixed by using a -non-posted write in mwifiex_pcie_send_data(). - -2) When sending lots of commands to the card, waking it up from sleep in -very quick intervals, the firmware eventually crashes. That crash -appears to be fixed by some other non-posted write included here. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 24bcd22a2618..b4ad0113a035 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -238,6 +238,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) - - iowrite32(data, card->pci_mmap1 + reg); - -+ /* Do a read-back, which makes the write non-posted, ensuring the -+ * completion before returning. -+ * The firmware of the 88W8897 card is buggy and this avoids crashes. -+ */ -+ ioread32(card->pci_mmap1 + reg); -+ - return 0; - } - --- -2.36.1 - diff --git a/patches/5.17/0003-ath10k.patch b/patches/5.17/0003-ath10k.patch deleted file mode 100644 index edb53397b..000000000 --- a/patches/5.17/0003-ath10k.patch +++ /dev/null @@ -1,121 +0,0 @@ -From 123a2db32336c887b0c1354eccfa9f0cbbc09ce0 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 27 Feb 2021 00:45:52 +0100 -Subject: [PATCH] ath10k: Add module parameters to override board files - -Some Surface devices, specifically the Surface Go and AMD version of the -Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better -with a different board file, as it seems that the firmeware included -upstream is buggy. - -As it is generally not a good idea to randomly overwrite files, let -alone doing so via packages, we add module parameters to override those -file names in the driver. This allows us to package/deploy the override -via a modprobe.d config. - -Signed-off-by: Maximilian Luz -Patchset: ath10k ---- - drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++ - 1 file changed, 58 insertions(+) - -diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c -index 8f5b8eb368fa..80c1bac732bc 100644 ---- a/drivers/net/wireless/ath/ath10k/core.c -+++ b/drivers/net/wireless/ath/ath10k/core.c -@@ -36,6 +36,9 @@ static bool skip_otp; - static bool rawmode; - static bool fw_diag_log; - -+static char *override_board = ""; -+static char *override_board2 = ""; -+ - unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | - BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); - -@@ -48,6 +51,9 @@ module_param(rawmode, bool, 0644); - module_param(fw_diag_log, bool, 0644); - module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); - -+module_param(override_board, charp, 0644); -+module_param(override_board2, charp, 0644); -+ - MODULE_PARM_DESC(debug_mask, "Debugging mask"); - MODULE_PARM_DESC(uart_print, "Uart target debugging"); - MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); -@@ -56,6 +62,9 @@ MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); - MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); - MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); - -+MODULE_PARM_DESC(override_board, "Override for board.bin file"); -+MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); -+ - static const struct ath10k_hw_params ath10k_hw_params_list[] = { - { - .id = QCA988X_HW_2_0_VERSION, -@@ -844,6 +853,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) - return 0; - } - -+static const char *ath10k_override_board_fw_file(struct ath10k *ar, -+ const char *file) -+{ -+ if (strcmp(file, "board.bin") == 0) { -+ if (strcmp(override_board, "") == 0) -+ return file; -+ -+ if (strcmp(override_board, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", -+ override_board); -+ -+ return override_board; -+ } -+ -+ if (strcmp(file, "board-2.bin") == 0) { -+ if (strcmp(override_board2, "") == 0) -+ return file; -+ -+ if (strcmp(override_board2, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", -+ override_board2); -+ -+ return override_board2; -+ } -+ -+ return file; -+} -+ - static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - const char *dir, - const char *file) -@@ -858,6 +903,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - if (dir == NULL) - dir = "."; - -+ /* HACK: Override board.bin and board-2.bin files if specified. -+ * -+ * Some Surface devices perform better with a different board -+ * configuration. To this end, one would need to replace the board.bin -+ * file with the modified config and remove the board-2.bin file. -+ * Unfortunately, that's not a solution that we can easily package. So -+ * we add module options to perform these overrides here. -+ */ -+ -+ file = ath10k_override_board_fw_file(ar, file); -+ if (!file) -+ return ERR_PTR(-ENOENT); -+ - snprintf(filename, sizeof(filename), "%s/%s", dir, file); - ret = firmware_request_nowarn(&fw, filename, ar->dev); - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", --- -2.36.1 - diff --git a/patches/5.17/0004-ipts.patch b/patches/5.17/0004-ipts.patch deleted file mode 100644 index 622d4df43..000000000 --- a/patches/5.17/0004-ipts.patch +++ /dev/null @@ -1,1603 +0,0 @@ -From 24d1173dfe41535f144c6f07e2ecb861cdc3d549 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 30 Jul 2020 13:21:53 +0200 -Subject: [PATCH] misc: mei: Add missing IPTS device IDs - -Patchset: ipts ---- - drivers/misc/mei/hw-me-regs.h | 1 + - drivers/misc/mei/pci-me.c | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h -index 64ce3f830262..c208a1e3a7c1 100644 ---- a/drivers/misc/mei/hw-me-regs.h -+++ b/drivers/misc/mei/hw-me-regs.h -@@ -92,6 +92,7 @@ - #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ - - #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ -+#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ - #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ - - #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ -diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c -index a738253dbd05..4e1c3fe09e53 100644 ---- a/drivers/misc/mei/pci-me.c -+++ b/drivers/misc/mei/pci-me.c -@@ -96,6 +96,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { - {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, -+ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, --- -2.36.1 - -From 2ea30da760c58eb29dbbe23fbe0c309fe8386214 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 6 Aug 2020 11:20:41 +0200 -Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus - -Based on linux-surface/intel-precise-touch@3f362c - -Signed-off-by: Dorian Stoll -Patchset: ipts ---- - drivers/misc/Kconfig | 1 + - drivers/misc/Makefile | 1 + - drivers/misc/ipts/Kconfig | 17 ++ - drivers/misc/ipts/Makefile | 12 ++ - drivers/misc/ipts/context.h | 47 +++++ - drivers/misc/ipts/control.c | 113 +++++++++++ - drivers/misc/ipts/control.h | 24 +++ - drivers/misc/ipts/mei.c | 125 ++++++++++++ - drivers/misc/ipts/protocol.h | 347 ++++++++++++++++++++++++++++++++++ - drivers/misc/ipts/receiver.c | 224 ++++++++++++++++++++++ - drivers/misc/ipts/receiver.h | 16 ++ - drivers/misc/ipts/resources.c | 128 +++++++++++++ - drivers/misc/ipts/resources.h | 17 ++ - drivers/misc/ipts/uapi.c | 208 ++++++++++++++++++++ - drivers/misc/ipts/uapi.h | 47 +++++ - 15 files changed, 1327 insertions(+) - create mode 100644 drivers/misc/ipts/Kconfig - create mode 100644 drivers/misc/ipts/Makefile - create mode 100644 drivers/misc/ipts/context.h - create mode 100644 drivers/misc/ipts/control.c - create mode 100644 drivers/misc/ipts/control.h - create mode 100644 drivers/misc/ipts/mei.c - create mode 100644 drivers/misc/ipts/protocol.h - create mode 100644 drivers/misc/ipts/receiver.c - create mode 100644 drivers/misc/ipts/receiver.h - create mode 100644 drivers/misc/ipts/resources.c - create mode 100644 drivers/misc/ipts/resources.h - create mode 100644 drivers/misc/ipts/uapi.c - create mode 100644 drivers/misc/ipts/uapi.h - -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index 0f5a49fc7c9e..12b081bc875a 100644 ---- a/drivers/misc/Kconfig -+++ b/drivers/misc/Kconfig -@@ -487,4 +487,5 @@ source "drivers/misc/cardreader/Kconfig" - source "drivers/misc/habanalabs/Kconfig" - source "drivers/misc/uacce/Kconfig" - source "drivers/misc/pvpanic/Kconfig" -+source "drivers/misc/ipts/Kconfig" - endmenu -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index a086197af544..972cae33ba36 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -59,3 +59,4 @@ obj-$(CONFIG_UACCE) += uacce/ - obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o - obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o - obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o -+obj-$(CONFIG_MISC_IPTS) += ipts/ -diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig -new file mode 100644 -index 000000000000..83e2a930c396 ---- /dev/null -+++ b/drivers/misc/ipts/Kconfig -@@ -0,0 +1,17 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+ -+config MISC_IPTS -+ tristate "Intel Precise Touch & Stylus" -+ depends on INTEL_MEI -+ help -+ Say Y here if your system has a touchscreen using Intels -+ Precise Touch & Stylus (IPTS) technology. -+ -+ If unsure say N. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ipts. -+ -+ Building this driver alone will not give you a working touchscreen. -+ It only exposed a userspace API that can be used by a daemon to -+ receive and process data from the touchscreen hardware. -diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile -new file mode 100644 -index 000000000000..8f58b9adbc94 ---- /dev/null -+++ b/drivers/misc/ipts/Makefile -@@ -0,0 +1,12 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+# -+# Makefile for the IPTS touchscreen driver -+# -+ -+obj-$(CONFIG_MISC_IPTS) += ipts.o -+ipts-objs := control.o -+ipts-objs += mei.o -+ipts-objs += receiver.o -+ipts-objs += resources.o -+ipts-objs += uapi.o -+ -diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h -new file mode 100644 -index 000000000000..f4b06a2d3f72 ---- /dev/null -+++ b/drivers/misc/ipts/context.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTEXT_H_ -+#define _IPTS_CONTEXT_H_ -+ -+#include -+#include -+#include -+#include -+ -+#include "protocol.h" -+ -+enum ipts_host_status { -+ IPTS_HOST_STATUS_STARTING, -+ IPTS_HOST_STATUS_STARTED, -+ IPTS_HOST_STATUS_STOPPING, -+ IPTS_HOST_STATUS_STOPPED, -+}; -+ -+struct ipts_buffer_info { -+ u8 *address; -+ dma_addr_t dma_address; -+}; -+ -+struct ipts_context { -+ struct mei_cl_device *cldev; -+ struct device *dev; -+ -+ bool restart; -+ enum ipts_host_status status; -+ struct ipts_get_device_info_rsp device_info; -+ -+ struct ipts_buffer_info data[IPTS_BUFFERS]; -+ struct ipts_buffer_info doorbell; -+ -+ struct ipts_buffer_info feedback[IPTS_BUFFERS]; -+ struct ipts_buffer_info workqueue; -+ struct ipts_buffer_info host2me; -+}; -+ -+#endif /* _IPTS_CONTEXT_H_ */ -diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c -new file mode 100644 -index 000000000000..a1d1f97a13d7 ---- /dev/null -+++ b/drivers/misc/ipts/control.c -@@ -0,0 +1,113 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+#include "protocol.h" -+#include "resources.h" -+#include "uapi.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload, -+ size_t size) -+{ -+ int ret; -+ struct ipts_command cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_command)); -+ cmd.code = code; -+ -+ if (payload && size > 0) -+ memcpy(&cmd.payload, payload, size); -+ -+ ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size); -+ if (ret >= 0) -+ return 0; -+ -+ /* -+ * During shutdown the device might get pulled away from below our feet. -+ * Dont log an error in this case, because it will confuse people. -+ */ -+ if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING) -+ dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret); -+ -+ return ret; -+} -+ -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) -+{ -+ struct ipts_feedback_cmd cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); -+ cmd.buffer = buffer; -+ -+ return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, -+ sizeof(struct ipts_feedback_cmd)); -+} -+ -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value) -+{ -+ struct ipts_feedback_buffer *feedback; -+ -+ memset(ipts->host2me.address, 0, ipts->device_info.feedback_size); -+ feedback = (struct ipts_feedback_buffer *)ipts->host2me.address; -+ -+ feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; -+ feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; -+ feedback->buffer = IPTS_HOST2ME_BUFFER; -+ feedback->size = 2; -+ feedback->payload[0] = report; -+ feedback->payload[1] = value; -+ -+ return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER); -+} -+ -+int ipts_control_start(struct ipts_context *ipts) -+{ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Starting IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STARTING; -+ ipts->restart = false; -+ -+ ipts_uapi_link(ipts); -+ return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); -+} -+ -+int ipts_control_stop(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPING) -+ return -EBUSY; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Stopping IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STOPPING; -+ -+ ipts_uapi_unlink(); -+ ipts_resources_free(ipts); -+ -+ ret = ipts_control_send_feedback(ipts, 0); -+ if (ret == -ENODEV) -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ return ret; -+} -+ -+int ipts_control_restart(struct ipts_context *ipts) -+{ -+ if (ipts->restart) -+ return -EBUSY; -+ -+ ipts->restart = true; -+ return ipts_control_stop(ipts); -+} -diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h -new file mode 100644 -index 000000000000..2c44e9e0e99f ---- /dev/null -+++ b/drivers/misc/ipts/control.h -@@ -0,0 +1,24 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTROL_H_ -+#define _IPTS_CONTROL_H_ -+ -+#include -+ -+#include "context.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, -+ size_t size); -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value); -+int ipts_control_start(struct ipts_context *ipts); -+int ipts_control_restart(struct ipts_context *ipts); -+int ipts_control_stop(struct ipts_context *ipts); -+ -+#endif /* _IPTS_CONTROL_H_ */ -diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c -new file mode 100644 -index 000000000000..59ecf13e00d2 ---- /dev/null -+++ b/drivers/misc/ipts/mei.c -@@ -0,0 +1,125 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "receiver.h" -+#include "uapi.h" -+ -+static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev) -+{ -+ int ret; -+ -+ ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)); -+ if (!ret) -+ return 0; -+ -+ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); -+} -+ -+static int ipts_mei_probe(struct mei_cl_device *cldev, -+ const struct mei_cl_device_id *id) -+{ -+ int ret; -+ struct ipts_context *ipts; -+ -+ if (ipts_mei_set_dma_mask(cldev)) { -+ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n"); -+ return -EFAULT; -+ } -+ -+ ret = mei_cldev_enable(cldev); -+ if (ret) { -+ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); -+ return ret; -+ } -+ -+ ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); -+ if (!ipts) { -+ mei_cldev_disable(cldev); -+ return -ENOMEM; -+ } -+ -+ ipts->cldev = cldev; -+ ipts->dev = &cldev->dev; -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ mei_cldev_set_drvdata(cldev, ipts); -+ mei_cldev_register_rx_cb(cldev, ipts_receiver_callback); -+ -+ return ipts_control_start(ipts); -+} -+ -+static void ipts_mei_remove(struct mei_cl_device *cldev) -+{ -+ int i; -+ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); -+ -+ ipts_control_stop(ipts); -+ -+ for (i = 0; i < 20; i++) { -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ break; -+ -+ msleep(25); -+ } -+ -+ mei_cldev_disable(cldev); -+ kfree(ipts); -+} -+ -+static struct mei_cl_device_id ipts_mei_device_id_table[] = { -+ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, -+ {}, -+}; -+MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); -+ -+static struct mei_cl_driver ipts_mei_driver = { -+ .id_table = ipts_mei_device_id_table, -+ .name = "ipts", -+ .probe = ipts_mei_probe, -+ .remove = ipts_mei_remove, -+}; -+ -+static int __init ipts_mei_init(void) -+{ -+ int ret; -+ -+ ret = ipts_uapi_init(); -+ if (ret) -+ return ret; -+ -+ ret = mei_cldev_driver_register(&ipts_mei_driver); -+ if (ret) { -+ ipts_uapi_free(); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static void __exit ipts_mei_exit(void) -+{ -+ mei_cldev_driver_unregister(&ipts_mei_driver); -+ ipts_uapi_free(); -+} -+ -+MODULE_DESCRIPTION("IPTS touchscreen driver"); -+MODULE_AUTHOR("Dorian Stoll "); -+MODULE_LICENSE("GPL"); -+ -+module_init(ipts_mei_init); -+module_exit(ipts_mei_exit); -diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h -new file mode 100644 -index 000000000000..c3458904a94d ---- /dev/null -+++ b/drivers/misc/ipts/protocol.h -@@ -0,0 +1,347 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_PROTOCOL_H_ -+#define _IPTS_PROTOCOL_H_ -+ -+#include -+ -+/* -+ * The MEI client ID for IPTS functionality. -+ */ -+#define IPTS_MEI_UUID \ -+ UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, \ -+ 0x02, 0xae, 0x04) -+ -+/* -+ * Queries the device for vendor specific information. -+ * -+ * The command must not contain any payload. -+ * The response will contain struct ipts_get_device_info_rsp as payload. -+ */ -+#define IPTS_CMD_GET_DEVICE_INFO 0x00000001 -+#define IPTS_RSP_GET_DEVICE_INFO 0x80000001 -+ -+/* -+ * Sets the mode that IPTS will operate in. -+ * -+ * The command must contain struct ipts_set_mode_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MODE 0x00000002 -+#define IPTS_RSP_SET_MODE 0x80000002 -+ -+/* -+ * Configures the memory buffers that the ME will use -+ * for passing data to the host. -+ * -+ * The command must contain struct ipts_set_mem_window_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MEM_WINDOW 0x00000003 -+#define IPTS_RSP_SET_MEM_WINDOW 0x80000003 -+ -+/* -+ * Signals that the host is ready to receive data to the ME. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_READY_FOR_DATA 0x00000005 -+#define IPTS_RSP_READY_FOR_DATA 0x80000005 -+ -+/* -+ * Signals that a buffer can be refilled to the ME. -+ * -+ * The command must contain struct ipts_feedback_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_FEEDBACK 0x00000006 -+#define IPTS_RSP_FEEDBACK 0x80000006 -+ -+/* -+ * Resets the data flow from the ME to the hosts and -+ * clears the buffers that were set with SET_MEM_WINDOW. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007 -+#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007 -+ -+/* -+ * Instructs the ME to reset the touch sensor. -+ * -+ * The command must contain struct ipts_reset_sensor_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_RESET_SENSOR 0x0000000B -+#define IPTS_RSP_RESET_SENSOR 0x8000000B -+ -+/** -+ * enum ipts_status - Possible status codes returned by IPTS commands. -+ * @IPTS_STATUS_SUCCESS: Operation completed successfully. -+ * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. -+ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. -+ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. -+ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. -+ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. -+ * The host must wait for a response before sending another -+ * command of the same type. -+ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it -+ * has not been initialized yet, or the system is improperly -+ * configured. -+ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. -+ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. -+ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. -+ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. -+ * The host can ignore this error and attempt to continue. -+ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. -+ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. -+ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. -+ * @IPTS_STATUS_TIMEOUT: The operation timed out. -+ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. -+ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. -+ * Further progress is not possible. -+ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. -+ * The host can attempt to continue. -+ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. -+ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. -+ */ -+enum ipts_status { -+ IPTS_STATUS_SUCCESS = 0, -+ IPTS_STATUS_INVALID_PARAMS = 1, -+ IPTS_STATUS_ACCESS_DENIED = 2, -+ IPTS_STATUS_CMD_SIZE_ERROR = 3, -+ IPTS_STATUS_NOT_READY = 4, -+ IPTS_STATUS_REQUEST_OUTSTANDING = 5, -+ IPTS_STATUS_NO_SENSOR_FOUND = 6, -+ IPTS_STATUS_OUT_OF_MEMORY = 7, -+ IPTS_STATUS_INTERNAL_ERROR = 8, -+ IPTS_STATUS_SENSOR_DISABLED = 9, -+ IPTS_STATUS_COMPAT_CHECK_FAIL = 10, -+ IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, -+ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, -+ IPTS_STATUS_RESET_FAILED = 13, -+ IPTS_STATUS_TIMEOUT = 14, -+ IPTS_STATUS_TEST_MODE_FAIL = 15, -+ IPTS_STATUS_SENSOR_FAIL_FATAL = 16, -+ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, -+ IPTS_STATUS_INVALID_DEVICE_CAPS = 18, -+ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, -+}; -+ -+/* -+ * The amount of buffers that is used for IPTS -+ */ -+#define IPTS_BUFFERS 16 -+ -+/* -+ * The special buffer ID that is used for direct host2me feedback. -+ */ -+#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS -+ -+/** -+ * enum ipts_mode - Operation mode for IPTS hardware -+ * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. -+ * The data is received as a HID report with ID 64. -+ * @IPTS_MODE_MULTITOUCH: The "proper" operation mode for IPTS. It will return -+ * stylus data as well as capacitive heatmap touch data. -+ * This data needs to be processed in userspace. -+ */ -+enum ipts_mode { -+ IPTS_MODE_SINGLETOUCH = 0, -+ IPTS_MODE_MULTITOUCH = 1, -+}; -+ -+/** -+ * struct ipts_set_mode_cmd - Payload for the SET_MODE command. -+ * @mode: The mode that IPTS should operate in. -+ */ -+struct ipts_set_mode_cmd { -+ enum ipts_mode mode; -+ u8 reserved[12]; -+} __packed; -+ -+#define IPTS_WORKQUEUE_SIZE 8192 -+#define IPTS_WORKQUEUE_ITEM_SIZE 16 -+ -+/** -+ * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command. -+ * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses. -+ * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses. -+ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. -+ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. -+ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. -+ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. -+ * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses. -+ * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses. -+ * @host2me_addr_lower: Lower 32 bits of the host2me buffer address. -+ * @host2me_addr_upper: Upper 32 bits of the host2me buffer address. -+ * @workqueue_item_size: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) -+ * @workqueue_size: Magic value. (IPTS_WORKQUEUE_SIZE) -+ * -+ * The data buffers are buffers that get filled with touch data by the ME. -+ * The doorbell buffer is a u32 that gets incremented by the ME once a data -+ * buffer has been filled with new data. -+ * -+ * The other buffers are required for using GuC submission with binary -+ * firmware. Since support for GuC submission has been dropped from i915, -+ * they are not used anymore, but they need to be allocated and passed, -+ * otherwise the hardware will refuse to start. -+ */ -+struct ipts_set_mem_window_cmd { -+ u32 data_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 data_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 workqueue_addr_lower; -+ u32 workqueue_addr_upper; -+ u32 doorbell_addr_lower; -+ u32 doorbell_addr_upper; -+ u32 feedback_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 feedback_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 host2me_addr_lower; -+ u32 host2me_addr_upper; -+ u32 host2me_size; -+ u8 reserved1; -+ u8 workqueue_item_size; -+ u16 workqueue_size; -+ u8 reserved[32]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_cmd - Payload for the FEEDBACK command. -+ * @buffer: The buffer that the ME should refill. -+ */ -+struct ipts_feedback_cmd { -+ u32 buffer; -+ u8 reserved[12]; -+} __packed; -+ -+/** -+ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. -+ */ -+enum ipts_feedback_cmd_type { -+ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, -+ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, -+ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, -+}; -+ -+/** -+ * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains. -+ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. -+ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. -+ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. -+ */ -+enum ipts_feedback_data_type { -+ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, -+ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, -+ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, -+ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, -+ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, -+}; -+ -+/** -+ * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer. -+ * @cmd_type: A command that should be executed on the sensor. -+ * @size: The size of the payload to be written. -+ * @buffer: The ID of the buffer that contains this feedback data. -+ * @protocol: The protocol version of the EDS. -+ * @data_type: The type of payload that the buffer contains. -+ * @spi_offset: The offset at which to write the payload data. -+ * @payload: Payload for the feedback command, or 0 if no payload is sent. -+ */ -+struct ipts_feedback_buffer { -+ enum ipts_feedback_cmd_type cmd_type; -+ u32 size; -+ u32 buffer; -+ u32 protocol; -+ enum ipts_feedback_data_type data_type; -+ u32 spi_offset; -+ u8 reserved[40]; -+ u8 payload[]; -+} __packed; -+ -+/** -+ * enum ipts_reset_type - Possible ways of resetting the touch sensor -+ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. -+ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. -+ */ -+enum ipts_reset_type { -+ IPTS_RESET_TYPE_HARD = 0, -+ IPTS_RESET_TYPE_SOFT = 1, -+}; -+ -+/** -+ * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. -+ * @type: What type of reset should be performed. -+ */ -+struct ipts_reset_sensor_cmd { -+ enum ipts_reset_type type; -+ u8 reserved[4]; -+} __packed; -+ -+/** -+ * struct ipts_command - A message sent from the host to the ME. -+ * @code: The message code describing the command. (see IPTS_CMD_*) -+ * @payload: Payload for the command, or 0 if no payload is required. -+ */ -+struct ipts_command { -+ u32 code; -+ u8 payload[320]; -+} __packed; -+ -+/** -+ * struct ipts_device_info - Payload for the GET_DEVICE_INFO response. -+ * @vendor_id: Vendor ID of the touch sensor. -+ * @device_id: Device ID of the touch sensor. -+ * @hw_rev: Hardware revision of the touch sensor. -+ * @fw_rev: Firmware revision of the touch sensor. -+ * @data_size: Required size of one data buffer. -+ * @feedback_size: Required size of one feedback buffer. -+ * @mode: Current operation mode of IPTS. -+ * @max_contacts: The amount of concurrent touches supported by the sensor. -+ */ -+struct ipts_get_device_info_rsp { -+ u16 vendor_id; -+ u16 device_id; -+ u32 hw_rev; -+ u32 fw_rev; -+ u32 data_size; -+ u32 feedback_size; -+ enum ipts_mode mode; -+ u8 max_contacts; -+ u8 reserved[19]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_rsp - Payload for the FEEDBACK response. -+ * @buffer: The buffer that has received feedback. -+ */ -+struct ipts_feedback_rsp { -+ u32 buffer; -+} __packed; -+ -+/** -+ * struct ipts_response - A message sent from the ME to the host. -+ * @code: The message code describing the response. (see IPTS_RSP_*) -+ * @status: The status code returned by the command. -+ * @payload: Payload returned by the command. -+ */ -+struct ipts_response { -+ u32 code; -+ enum ipts_status status; -+ u8 payload[80]; -+} __packed; -+ -+#endif /* _IPTS_PROTOCOL_H_ */ -diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c -new file mode 100644 -index 000000000000..23dca13c2139 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.c -@@ -0,0 +1,224 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "resources.h" -+ -+/* -+ * Temporary parameter to guard gen7 multitouch mode. -+ * Remove once gen7 has stable iptsd support. -+ */ -+static bool gen7mt; -+module_param(gen7mt, bool, 0644); -+ -+static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_set_mode_cmd cmd; -+ -+ memcpy(&ipts->device_info, rsp->payload, -+ sizeof(struct ipts_get_device_info_rsp)); -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd)); -+ cmd.mode = IPTS_MODE_MULTITOUCH; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd, -+ sizeof(struct ipts_set_mode_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mode(struct ipts_context *ipts) -+{ -+ int i, ret; -+ struct ipts_set_mem_window_cmd cmd; -+ -+ ret = ipts_resources_alloc(ipts); -+ if (ret) { -+ dev_err(ipts->dev, "Failed to allocate resources\n"); -+ return ret; -+ } -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ cmd.data_buffer_addr_lower[i] = -+ lower_32_bits(ipts->data[i].dma_address); -+ -+ cmd.data_buffer_addr_upper[i] = -+ upper_32_bits(ipts->data[i].dma_address); -+ -+ cmd.feedback_buffer_addr_lower[i] = -+ lower_32_bits(ipts->feedback[i].dma_address); -+ -+ cmd.feedback_buffer_addr_upper[i] = -+ upper_32_bits(ipts->feedback[i].dma_address); -+ } -+ -+ cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); -+ cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); -+ -+ cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); -+ cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); -+ -+ cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); -+ cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); -+ -+ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; -+ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, -+ sizeof(struct ipts_set_mem_window_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ dev_info(ipts->dev, "Device %04hX:%04hX ready\n", -+ ipts->device_info.vendor_id, ipts->device_info.device_id); -+ ipts->status = IPTS_HOST_STATUS_STARTED; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); -+ if (ret) -+ return ret; -+ -+ if (!gen7mt) -+ return 0; -+ -+ return ipts_control_set_feature(ipts, 0x5, 0x1); -+} -+ -+static int ipts_receiver_handle_feedback(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_feedback_rsp feedback; -+ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPING) -+ return 0; -+ -+ memcpy(&feedback, rsp->payload, sizeof(feedback)); -+ -+ if (feedback.buffer < IPTS_BUFFERS - 1) -+ return ipts_control_send_feedback(ipts, feedback.buffer + 1); -+ -+ return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); -+} -+ -+static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) -+{ -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ if (ipts->restart) -+ return ipts_control_start(ipts); -+ -+ return 0; -+} -+ -+static bool ipts_receiver_sensor_was_reset(u32 status) -+{ -+ return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || -+ status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; -+} -+ -+static bool ipts_receiver_handle_error(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ bool error; -+ -+ switch (rsp->status) { -+ case IPTS_STATUS_SUCCESS: -+ case IPTS_STATUS_COMPAT_CHECK_FAIL: -+ error = false; -+ break; -+ case IPTS_STATUS_INVALID_PARAMS: -+ error = rsp->code != IPTS_RSP_FEEDBACK; -+ break; -+ case IPTS_STATUS_SENSOR_DISABLED: -+ error = ipts->status != IPTS_HOST_STATUS_STOPPING; -+ break; -+ default: -+ error = true; -+ break; -+ } -+ -+ if (!error) -+ return false; -+ -+ dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code, -+ rsp->status); -+ -+ if (ipts_receiver_sensor_was_reset(rsp->status)) { -+ dev_err(ipts->dev, "Sensor was reset\n"); -+ -+ if (ipts_control_restart(ipts)) -+ dev_err(ipts->dev, "Failed to restart IPTS\n"); -+ } -+ -+ return true; -+} -+ -+static void ipts_receiver_handle_response(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ int ret; -+ -+ if (ipts_receiver_handle_error(ipts, rsp)) -+ return; -+ -+ switch (rsp->code) { -+ case IPTS_RSP_GET_DEVICE_INFO: -+ ret = ipts_receiver_handle_get_device_info(ipts, rsp); -+ break; -+ case IPTS_RSP_SET_MODE: -+ ret = ipts_receiver_handle_set_mode(ipts); -+ break; -+ case IPTS_RSP_SET_MEM_WINDOW: -+ ret = ipts_receiver_handle_set_mem_window(ipts); -+ break; -+ case IPTS_RSP_FEEDBACK: -+ ret = ipts_receiver_handle_feedback(ipts, rsp); -+ break; -+ case IPTS_RSP_CLEAR_MEM_WINDOW: -+ ret = ipts_receiver_handle_clear_mem_window(ipts); -+ break; -+ default: -+ ret = 0; -+ break; -+ } -+ -+ if (!ret) -+ return; -+ -+ dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n", -+ rsp->code, ret); -+ -+ if (ipts_control_stop(ipts)) -+ dev_err(ipts->dev, "Failed to stop IPTS\n"); -+} -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev) -+{ -+ int ret; -+ struct ipts_response rsp; -+ struct ipts_context *ipts; -+ -+ ipts = mei_cldev_get_drvdata(cldev); -+ -+ ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response)); -+ if (ret <= 0) { -+ dev_err(ipts->dev, "Error while reading response: %d\n", ret); -+ return; -+ } -+ -+ ipts_receiver_handle_response(ipts, &rsp); -+} -diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h -new file mode 100644 -index 000000000000..7f075afa7ef8 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.h -@@ -0,0 +1,16 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RECEIVER_H_ -+#define _IPTS_RECEIVER_H_ -+ -+#include -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev); -+ -+#endif /* _IPTS_RECEIVER_H_ */ -diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c -new file mode 100644 -index 000000000000..8e3a2409e438 ---- /dev/null -+++ b/drivers/misc/ipts/resources.c -@@ -0,0 +1,128 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+ -+void ipts_resources_free(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, data_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ if (ipts->doorbell.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->doorbell.address, -+ ipts->doorbell.dma_address); -+ -+ ipts->doorbell.address = NULL; -+ ipts->doorbell.dma_address = 0; -+ } -+ -+ if (ipts->workqueue.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->workqueue.address, -+ ipts->workqueue.dma_address); -+ -+ ipts->workqueue.address = NULL; -+ ipts->workqueue.dma_address = 0; -+ } -+ -+ if (ipts->host2me.address) { -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ ipts->host2me.address, -+ ipts->host2me.dma_address); -+ -+ ipts->host2me.address = NULL; -+ ipts->host2me.dma_address = 0; -+ } -+} -+ -+int ipts_resources_alloc(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, data_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ ipts->doorbell.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->doorbell.dma_address, GFP_KERNEL); -+ -+ if (!ipts->doorbell.address) -+ goto release_resources; -+ -+ ipts->workqueue.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->workqueue.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ ipts->host2me.address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &ipts->host2me.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ return 0; -+ -+release_resources: -+ -+ ipts_resources_free(ipts); -+ return -ENOMEM; -+} -diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h -new file mode 100644 -index 000000000000..fdac0eee9156 ---- /dev/null -+++ b/drivers/misc/ipts/resources.h -@@ -0,0 +1,17 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RESOURCES_H_ -+#define _IPTS_RESOURCES_H_ -+ -+#include "context.h" -+ -+int ipts_resources_alloc(struct ipts_context *ipts); -+void ipts_resources_free(struct ipts_context *ipts); -+ -+#endif /* _IPTS_RESOURCES_H_ */ -diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c -new file mode 100644 -index 000000000000..598f0710ad64 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.c -@@ -0,0 +1,208 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "uapi.h" -+ -+struct ipts_uapi uapi; -+ -+static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count, -+ loff_t *offset) -+{ -+ int buffer; -+ int maxbytes; -+ struct ipts_context *ipts = uapi.ipts; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ maxbytes = ipts->device_info.data_size - *offset; -+ if (maxbytes <= 0 || count > maxbytes) -+ return -EINVAL; -+ -+ if (copy_to_user(buf, ipts->data[buffer].address + *offset, count)) -+ return -EFAULT; -+ -+ return count; -+} -+ -+static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ u8 ready = 0; -+ -+ if (ipts) -+ ready = ipts->status == IPTS_HOST_STATUS_STARTED; -+ -+ if (copy_to_user(buffer, &ready, sizeof(u8))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ struct ipts_device_info info; -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ info.vendor = ipts->device_info.vendor_id; -+ info.product = ipts->device_info.device_id; -+ info.version = ipts->device_info.fw_rev; -+ info.buffer_size = ipts->device_info.data_size; -+ info.max_contacts = ipts->device_info.max_contacts; -+ -+ if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts, -+ struct file *file) -+{ -+ int ret; -+ u32 buffer; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ ret = ipts_control_send_feedback(ipts, buffer); -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) -+{ -+ int ret; -+ struct ipts_reset_sensor_cmd cmd; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); -+ cmd.type = IPTS_RESET_TYPE_SOFT; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, -+ sizeof(struct ipts_reset_sensor_cmd)); -+ -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) -+{ -+ struct ipts_context *ipts = uapi.ipts; -+ -+ switch (cmd) { -+ case IPTS_IOCTL_GET_DEVICE_READY: -+ return ipts_uapi_ioctl_get_device_ready(ipts, arg); -+ case IPTS_IOCTL_GET_DEVICE_INFO: -+ return ipts_uapi_ioctl_get_device_info(ipts, arg); -+ case IPTS_IOCTL_GET_DOORBELL: -+ return ipts_uapi_ioctl_get_doorbell(ipts, arg); -+ case IPTS_IOCTL_SEND_FEEDBACK: -+ return ipts_uapi_ioctl_send_feedback(ipts, file); -+ case IPTS_IOCTL_SEND_RESET: -+ return ipts_uapi_ioctl_send_reset(ipts); -+ default: -+ return -ENOTTY; -+ } -+} -+ -+static const struct file_operations ipts_uapi_fops = { -+ .owner = THIS_MODULE, -+ .read = ipts_uapi_read, -+ .unlocked_ioctl = ipts_uapi_ioctl, -+#ifdef CONFIG_COMPAT -+ .compat_ioctl = ipts_uapi_ioctl, -+#endif -+}; -+ -+void ipts_uapi_link(struct ipts_context *ipts) -+{ -+ uapi.ipts = ipts; -+} -+ -+void ipts_uapi_unlink(void) -+{ -+ uapi.ipts = NULL; -+} -+ -+int ipts_uapi_init(void) -+{ -+ int i, major; -+ -+ alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts"); -+ uapi.class = class_create(THIS_MODULE, "ipts"); -+ -+ major = MAJOR(uapi.dev); -+ -+ cdev_init(&uapi.cdev, &ipts_uapi_fops); -+ uapi.cdev.owner = THIS_MODULE; -+ cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ device_create(uapi.class, NULL, MKDEV(major, i), NULL, -+ "ipts/%d", i); -+ } -+ -+ return 0; -+} -+ -+void ipts_uapi_free(void) -+{ -+ int i; -+ int major; -+ -+ major = MAJOR(uapi.dev); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) -+ device_destroy(uapi.class, MKDEV(major, i)); -+ -+ cdev_del(&uapi.cdev); -+ -+ unregister_chrdev_region(MKDEV(major, 0), MINORMASK); -+ class_destroy(uapi.class); -+} -diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h -new file mode 100644 -index 000000000000..53fb86a88f97 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_UAPI_H_ -+#define _IPTS_UAPI_H_ -+ -+#include -+ -+#include "context.h" -+ -+struct ipts_uapi { -+ dev_t dev; -+ struct class *class; -+ struct cdev cdev; -+ -+ struct ipts_context *ipts; -+}; -+ -+struct ipts_device_info { -+ __u16 vendor; -+ __u16 product; -+ __u32 version; -+ __u32 buffer_size; -+ __u8 max_contacts; -+ -+ /* For future expansion */ -+ __u8 reserved[19]; -+}; -+ -+#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8) -+#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info) -+#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) -+#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04) -+#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) -+ -+void ipts_uapi_link(struct ipts_context *ipts); -+void ipts_uapi_unlink(void); -+ -+int ipts_uapi_init(void); -+void ipts_uapi_free(void); -+ -+#endif /* _IPTS_UAPI_H_ */ --- -2.36.1 - -From c4decb8872f457ba68895d874b9aa8710a6bd333 Mon Sep 17 00:00:00 2001 -From: Liban Hannan -Date: Tue, 12 Apr 2022 23:31:12 +0100 -Subject: [PATCH] iommu: ipts: use IOMMU passthrough mode for IPTS - -Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. -Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: - -DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr -0x104ea3000 [fault reason 0x06] PTE Read access is not set - -This is very similar to the bug described at: -https://bugs.launchpad.net/bugs/1958004 - -Fixed with the following patch which this patch basically copies: -https://launchpadlibrarian.net/586396847/43255ca.diff -Patchset: ipts ---- - drivers/iommu/intel/iommu.c | 24 ++++++++++++++++++++++++ - 1 file changed, 24 insertions(+) - -diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c -index e3f15e0cae34..0bb308eaae4e 100644 ---- a/drivers/iommu/intel/iommu.c -+++ b/drivers/iommu/intel/iommu.c -@@ -57,6 +57,8 @@ - #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) - #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) - #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) -+#define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ -+ ((pdev)->device == 0x9d3e)) - #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) - - #define IOAPIC_RANGE_START (0xfee00000) -@@ -332,12 +334,14 @@ int intel_iommu_enabled = 0; - EXPORT_SYMBOL_GPL(intel_iommu_enabled); - - static int dmar_map_gfx = 1; -+static int dmar_map_ipts = 1; - static int intel_iommu_superpage = 1; - static int iommu_identity_mapping; - static int iommu_skip_te_disable; - - #define IDENTMAP_GFX 2 - #define IDENTMAP_AZALIA 4 -+#define IDENTMAP_IPTS 16 - - int intel_iommu_gfx_mapped; - EXPORT_SYMBOL_GPL(intel_iommu_gfx_mapped); -@@ -2987,6 +2991,9 @@ static int device_def_domain_type(struct device *dev) - - if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) - return IOMMU_DOMAIN_IDENTITY; -+ -+ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) -+ return IOMMU_DOMAIN_IDENTITY; - } - - return 0; -@@ -3423,6 +3430,9 @@ static int __init init_dmars(void) - if (!dmar_map_gfx) - iommu_identity_mapping |= IDENTMAP_GFX; - -+ if (!dmar_map_ipts) -+ iommu_identity_mapping |= IDENTMAP_IPTS; -+ - check_tylersburg_isoch(); - - ret = si_domain_init(hw_pass_through); -@@ -5664,6 +5674,17 @@ static void quirk_iommu_igfx(struct pci_dev *dev) - dmar_map_gfx = 0; - } - -+static void quirk_iommu_ipts(struct pci_dev *dev) -+{ -+ if (!IS_IPTS(dev)) -+ return; -+ -+ if (risky_device(dev)) -+ return; -+ -+ pci_info(dev, "Passthrough IOMMU for IPTS\n"); -+ dmar_map_ipts = 0; -+} - /* G4x/GM45 integrated gfx dmar support is totally busted. */ - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); -@@ -5699,6 +5720,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); - -+/* disable IPTS dmar support */ -+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); -+ - static void quirk_iommu_rwbf(struct pci_dev *dev) - { - if (risky_device(dev)) --- -2.36.1 - diff --git a/patches/5.17/0005-surface-sam.patch b/patches/5.17/0005-surface-sam.patch deleted file mode 100644 index 295a6bd1a..000000000 --- a/patches/5.17/0005-surface-sam.patch +++ /dev/null @@ -1,3567 +0,0 @@ -From 956e4d21cae3e7ad4f652a7b88dcfbaa431613ed Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 00:24:47 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow devices to be marked as - hot-removed - -Some SSAM devices, notably the keyboard cover (keyboard and touchpad) on -the Surface Pro 8, can be hot-removed. When this occurs, communication -with the device may fail and time out. This timeout can unnecessarily -block and slow down device removal and even cause issues when the -devices are detached and re-attached quickly. Thus, communication should -generally be avoided once hot-removal is detected. - -While we already remove a device as soon as we detect its (hot-)removal, -the corresponding device driver may still attempt to communicate with -the device during teardown. This is especially critical as communication -failure may also extend to disabling of events, which is typically done -at that stage. - -Add a flag to allow marking devices as hot-removed. This can then be -used during client driver teardown to check if any communication -attempts should be avoided. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - include/linux/surface_aggregator/device.h | 48 +++++++++++++++++++++-- - 1 file changed, 45 insertions(+), 3 deletions(-) - -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index cc257097eb05..491aa7e9f4bc 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -148,17 +148,30 @@ struct ssam_device_uid { - #define SSAM_SDEV(cat, tid, iid, fun) \ - SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) - -+/* -+ * enum ssam_device_flags - Flags for SSAM client devices. -+ * @SSAM_DEVICE_HOT_REMOVED_BIT: -+ * The device has been hot-removed. Further communication with it may time -+ * out and should be avoided. -+ */ -+enum ssam_device_flags { -+ SSAM_DEVICE_HOT_REMOVED_BIT = 0, -+}; -+ - /** - * struct ssam_device - SSAM client device. -- * @dev: Driver model representation of the device. -- * @ctrl: SSAM controller managing this device. -- * @uid: UID identifying the device. -+ * @dev: Driver model representation of the device. -+ * @ctrl: SSAM controller managing this device. -+ * @uid: UID identifying the device. -+ * @flags: Device state flags, see &enum ssam_device_flags. - */ - struct ssam_device { - struct device dev; - struct ssam_controller *ctrl; - - struct ssam_device_uid uid; -+ -+ unsigned long flags; - }; - - /** -@@ -240,6 +253,35 @@ struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, - int ssam_device_add(struct ssam_device *sdev); - void ssam_device_remove(struct ssam_device *sdev); - -+/** -+ * ssam_device_mark_hot_removed() - Mark the given device as hot-removed. -+ * @sdev: The device to mark as hot-removed. -+ * -+ * Mark the device as having been hot-removed. This signals drivers using the -+ * device that communication with the device should be avoided and may lead to -+ * timeouts. -+ */ -+static inline void ssam_device_mark_hot_removed(struct ssam_device *sdev) -+{ -+ dev_dbg(&sdev->dev, "marking device as hot-removed\n"); -+ set_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); -+} -+ -+/** -+ * ssam_device_is_hot_removed() - Check if the given device has been -+ * hot-removed. -+ * @sdev: The device to check. -+ * -+ * Checks if the given device has been marked as hot-removed. See -+ * ssam_device_mark_hot_removed() for more details. -+ * -+ * Return: Returns ``true`` if the device has been marked as hot-removed. -+ */ -+static inline bool ssam_device_is_hot_removed(struct ssam_device *sdev) -+{ -+ return test_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); -+} -+ - /** - * ssam_device_get() - Increment reference count of SSAM client device. - * @sdev: The device to increment the reference count of. --- -2.36.1 - -From fc6a2d704aa4fa7adad0cdb52418bad03fdd3239 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 00:48:22 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow notifiers to avoid - communication on unregistering - -When SSAM client devices have been (physically) hot-removed, -communication attempts with those devices may fail and time out. This -can even extend to event notifiers, due to which timeouts may occur -during device removal, slowing down that process. - -Add a parameter to the notifier unregister function that allows skipping -communication with the EC to prevent this. Furthermore, add wrappers for -registering and unregistering notifiers belonging to SSAM client devices -that automatically check if the device has been marked as hot-removed -and communication should be avoided. - -Note that non-SSAM client devices can generally not be hot-removed, so -also add a convenience wrapper for those, defaulting to allow -communication. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../driver-api/surface_aggregator/client.rst | 6 +- - .../platform/surface/aggregator/controller.c | 53 ++++++++++----- - include/linux/surface_aggregator/controller.h | 24 ++++++- - include/linux/surface_aggregator/device.h | 66 +++++++++++++++++++ - 4 files changed, 128 insertions(+), 21 deletions(-) - -diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst -index e519d374c378..27f95abdbe99 100644 ---- a/Documentation/driver-api/surface_aggregator/client.rst -+++ b/Documentation/driver-api/surface_aggregator/client.rst -@@ -17,6 +17,8 @@ - .. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` - .. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` - .. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` -+.. |ssam_device_notifier_register| replace:: :c:func:`ssam_device_notifier_register` -+.. |ssam_device_notifier_unregister| replace:: :c:func:`ssam_device_notifier_unregister` - .. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` - .. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` - -@@ -312,7 +314,9 @@ Handling Events - To receive events from the SAM EC, an event notifier must be registered for - the desired event via |ssam_notifier_register|. The notifier must be - unregistered via |ssam_notifier_unregister| once it is not required any --more. -+more. For |ssam_device| type clients, the |ssam_device_notifier_register| and -+|ssam_device_notifier_unregister| wrappers should be preferred as they properly -+handle hot-removal of client devices. - - Event notifiers are registered by providing (at minimum) a callback to call - in case an event has been received, the registry specifying how the event -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index b8c377b3f932..6de834b52b63 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2199,16 +2199,26 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, - } - - /** -- * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is -- * no longer in use and free the corresponding entry. -+ * ssam_nf_refcount_disable_free() - Disable event for reference count entry if -+ * it is no longer in use and free the corresponding entry. - * @ctrl: The controller to disable the event on. - * @entry: The reference count entry for the event to be disabled. - * @flags: The flags used for enabling the event on the EC. -+ * @ec: Flag specifying if the event should actually be disabled on the EC. - * -- * If the reference count equals zero, i.e. the event is no longer requested by -- * any client, the event will be disabled and the corresponding reference count -- * entry freed. The reference count entry must not be used any more after a -- * call to this function. -+ * If ``ec`` equals ``true`` and the reference count equals zero (i.e. the -+ * event is no longer requested by any client), the specified event will be -+ * disabled on the EC via the corresponding request. -+ * -+ * If ``ec`` equals ``false``, no request will be sent to the EC and the event -+ * can be considered in a detached state (i.e. no longer used but still -+ * enabled). Disabling an event via this method may be required for -+ * hot-removable devices, where event disable requests may time out after the -+ * device has been physically removed. -+ * -+ * In both cases, if the reference count equals zero, the corresponding -+ * reference count entry will be freed. The reference count entry must not be -+ * used any more after a call to this function. - * - * Also checks if the flags used for disabling the event match the flags used - * for enabling the event and warns if they do not (regardless of reference -@@ -2223,7 +2233,7 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, - * returns the status of the event-enable EC command. - */ - static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, -- struct ssam_nf_refcount_entry *entry, u8 flags) -+ struct ssam_nf_refcount_entry *entry, u8 flags, bool ec) - { - const struct ssam_event_registry reg = entry->key.reg; - const struct ssam_event_id id = entry->key.id; -@@ -2232,8 +2242,9 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - - lockdep_assert_held(&nf->lock); - -- ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -- reg.target_category, id.target_category, id.instance, entry->refcount); -+ ssam_dbg(ctrl, "%s event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ ec ? "disabling" : "detaching", reg.target_category, id.target_category, -+ id.instance, entry->refcount); - - if (entry->flags != flags) { - ssam_warn(ctrl, -@@ -2242,7 +2253,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - id.instance); - } - -- if (entry->refcount == 0) { -+ if (ec && entry->refcount == 0) { - status = ssam_ssh_event_disable(ctrl, reg, id, flags); - kfree(entry); - } -@@ -2322,20 +2333,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif - EXPORT_SYMBOL_GPL(ssam_notifier_register); - - /** -- * ssam_notifier_unregister() - Unregister an event notifier. -- * @ctrl: The controller the notifier has been registered on. -- * @n: The event notifier to unregister. -+ * __ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * @disable: Whether to disable the corresponding event on the EC. - * - * Unregister an event notifier. Decrement the usage counter of the associated - * SAM event if the notifier is not marked as an observer. If the usage counter -- * reaches zero, the event will be disabled. -+ * reaches zero and ``disable`` equals ``true``, the event will be disabled. -+ * -+ * Useful for hot-removable devices, where communication may fail once the -+ * device has been physically removed. In that case, specifying ``disable`` as -+ * ``false`` avoids communication with the EC. - * - * Return: Returns zero on success, %-ENOENT if the given notifier block has - * not been registered on the controller. If the given notifier block was the - * last one associated with its specific event, returns the status of the - * event-disable EC-command. - */ --int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) -+int __ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n, -+ bool disable) - { - u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); - struct ssam_nf_refcount_entry *entry; -@@ -2373,7 +2390,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - goto remove; - } - -- status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); -+ status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags, disable); - } - - remove: -@@ -2383,7 +2400,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - - return status; - } --EXPORT_SYMBOL_GPL(ssam_notifier_unregister); -+EXPORT_SYMBOL_GPL(__ssam_notifier_unregister); - - /** - * ssam_controller_event_enable() - Enable the specified event. -@@ -2477,7 +2494,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, - return -ENOENT; - } - -- status = ssam_nf_refcount_disable_free(ctrl, entry, flags); -+ status = ssam_nf_refcount_disable_free(ctrl, entry, flags, true); - - mutex_unlock(&nf->lock); - return status; -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index 74bfdffaf7b0..50a2b4926c06 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -835,8 +835,28 @@ struct ssam_event_notifier { - int ssam_notifier_register(struct ssam_controller *ctrl, - struct ssam_event_notifier *n); - --int ssam_notifier_unregister(struct ssam_controller *ctrl, -- struct ssam_event_notifier *n); -+int __ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n, bool disable); -+ -+/** -+ * ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+static inline int ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n) -+{ -+ return __ssam_notifier_unregister(ctrl, n, true); -+} - - int ssam_controller_event_enable(struct ssam_controller *ctrl, - struct ssam_event_registry reg, -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 491aa7e9f4bc..ad245c6b00d0 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -472,4 +472,70 @@ static inline void ssam_remove_clients(struct device *dev) {} - sdev->uid.instance, ret); \ - } - -+ -+/* -- Helpers for client-device notifiers. ---------------------------------- */ -+ -+/** -+ * ssam_device_notifier_register() - Register an event notifier for the -+ * specified client device. -+ * @sdev: The device the notifier should be registered on. -+ * @n: The event notifier to register. -+ * -+ * Register an event notifier. Increment the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the event is not -+ * marked as an observer and is currently not enabled, it will be enabled -+ * during this call. If the notifier is marked as an observer, no attempt will -+ * be made at enabling any event and no reference count will be modified. -+ * -+ * Notifiers marked as observers do not need to be associated with one specific -+ * event, i.e. as long as no event matching is performed, only the event target -+ * category needs to be set. -+ * -+ * Return: Returns zero on success, %-ENOSPC if there have already been -+ * %INT_MAX notifiers for the event ID/type associated with the notifier block -+ * registered, %-ENOMEM if the corresponding event entry could not be -+ * allocated, %-ENODEV if the device is marked as hot-removed. If this is the -+ * first time that a notifier block is registered for the specific associated -+ * event, returns the status of the event-enable EC-command. -+ */ -+static inline int ssam_device_notifier_register(struct ssam_device *sdev, -+ struct ssam_event_notifier *n) -+{ -+ /* -+ * Note that this check does not provide any guarantees whatsoever as -+ * hot-removal could happen at any point and we can't protect against -+ * it. Nevertheless, if we can detect hot-removal, bail early to avoid -+ * communication timeouts. -+ */ -+ if (ssam_device_is_hot_removed(sdev)) -+ return -ENODEV; -+ -+ return ssam_notifier_register(sdev->ctrl, n); -+} -+ -+/** -+ * ssam_device_notifier_unregister() - Unregister an event notifier for the -+ * specified client device. -+ * @sdev: The device the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. -+ * -+ * In case the device has been marked as hot-removed, the event will not be -+ * disabled on the EC, as in those cases any attempt at doing so may time out. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+static inline int ssam_device_notifier_unregister(struct ssam_device *sdev, -+ struct ssam_event_notifier *n) -+{ -+ return __ssam_notifier_unregister(sdev->ctrl, n, -+ !ssam_device_is_hot_removed(sdev)); -+} -+ - #endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ --- -2.36.1 - -From a20d4cf113fb4d38e431488007489384b2b2f807 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 01:20:49 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Use client device - wrappers for notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index ce2bd88feeaa..9f630e890ff7 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -468,7 +468,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - - ssam_device_set_drvdata(sdev, hub); - -- status = ssam_notifier_register(sdev->ctrl, &hub->notif); -+ status = ssam_device_notifier_register(sdev, &hub->notif); - if (status) - return status; - -@@ -480,7 +480,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - return 0; - - err: -- ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_device_notifier_unregister(sdev, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); - return status; -@@ -492,7 +492,7 @@ static void ssam_base_hub_remove(struct ssam_device *sdev) - - sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); - -- ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_device_notifier_unregister(sdev, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); - } --- -2.36.1 - -From 6863a5a91ec549ba5293c9b4e796869ebb22b34a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 28 Oct 2021 03:37:06 +0200 -Subject: [PATCH] power/supply: surface_charger: Use client device wrappers for - notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/power/supply/surface_charger.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -index a060c36c7766..59182d55742d 100644 ---- a/drivers/power/supply/surface_charger.c -+++ b/drivers/power/supply/surface_charger.c -@@ -216,7 +216,7 @@ static int spwr_ac_register(struct spwr_ac_device *ac) - if (IS_ERR(ac->psy)) - return PTR_ERR(ac->psy); - -- return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); -+ return ssam_device_notifier_register(ac->sdev, &ac->notif); - } - - -@@ -251,7 +251,7 @@ static void surface_ac_remove(struct ssam_device *sdev) - { - struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); - -- ssam_notifier_unregister(sdev->ctrl, &ac->notif); -+ ssam_device_notifier_unregister(sdev, &ac->notif); - } - - static const struct spwr_psy_properties spwr_psy_props_adp1 = { --- -2.36.1 - -From d0917349c7cd43f2067d048bbfd9cee5726e1883 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 28 Oct 2021 03:38:09 +0200 -Subject: [PATCH] power/supply: surface_battery: Use client device wrappers for - notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/power/supply/surface_battery.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -index 5ec2e6bb2465..540707882bb0 100644 ---- a/drivers/power/supply/surface_battery.c -+++ b/drivers/power/supply/surface_battery.c -@@ -802,7 +802,7 @@ static int spwr_battery_register(struct spwr_battery_device *bat) - if (IS_ERR(bat->psy)) - return PTR_ERR(bat->psy); - -- return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); -+ return ssam_device_notifier_register(bat->sdev, &bat->notif); - } - - -@@ -837,7 +837,7 @@ static void surface_battery_remove(struct ssam_device *sdev) - { - struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); - -- ssam_notifier_unregister(sdev->ctrl, &bat->notif); -+ ssam_device_notifier_unregister(sdev, &bat->notif); - cancel_delayed_work_sync(&bat->update_work); - } - --- -2.36.1 - -From 6730bf6e58208c3097a89e71c62b44f9f8b61408 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 01:33:02 +0200 -Subject: [PATCH] HID: surface-hid: Add support for hot-removal - -Add support for hot-removal of SSAM HID client devices. - -Once a device has been hot-removed, further communication with it should -be avoided as it may fail and time out. While the device will be removed -as soon as we detect hot-removal, communication may still occur during -teardown, especially when unregistering notifiers. - -While hot-removal is a surprise event that can happen at any time, try -to avoid communication as much as possible once it has been detected to -prevent timeouts that can slow down device removal and cause issues, -e.g. when quickly re-attaching the device. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/hid/surface-hid/surface_hid_core.c | 38 +++++++++++++++++++++- - 1 file changed, 37 insertions(+), 1 deletion(-) - -diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c -index e46330b2e561..87637f813de2 100644 ---- a/drivers/hid/surface-hid/surface_hid_core.c -+++ b/drivers/hid/surface-hid/surface_hid_core.c -@@ -19,12 +19,30 @@ - #include "surface_hid_core.h" - - -+/* -- Utility functions. ---------------------------------------------------- */ -+ -+static bool surface_hid_is_hot_removed(struct surface_hid_device *shid) -+{ -+ /* -+ * Non-ssam client devices, i.e. platform client devices, cannot be -+ * hot-removed. -+ */ -+ if (!is_ssam_device(shid->dev)) -+ return false; -+ -+ return ssam_device_is_hot_removed(to_ssam_device(shid->dev)); -+} -+ -+ - /* -- Device descriptor access. --------------------------------------------- */ - - static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) - { - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, - (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); - if (status) -@@ -61,6 +79,9 @@ static int surface_hid_load_device_attributes(struct surface_hid_device *shid) - { - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, - (u8 *)&shid->attrs, sizeof(shid->attrs)); - if (status) -@@ -88,9 +109,18 @@ static int surface_hid_start(struct hid_device *hid) - static void surface_hid_stop(struct hid_device *hid) - { - struct surface_hid_device *shid = hid->driver_data; -+ bool hot_removed; -+ -+ /* -+ * Communication may fail for devices that have been hot-removed. This -+ * also includes unregistration of HID events, so we need to check this -+ * here. Only if the device has not been marked as hot-removed, we can -+ * safely disable events. -+ */ -+ hot_removed = surface_hid_is_hot_removed(shid); - - /* Note: This call will log errors for us, so ignore them here. */ -- ssam_notifier_unregister(shid->ctrl, &shid->notif); -+ __ssam_notifier_unregister(shid->ctrl, &shid->notif, !hot_removed); - } - - static int surface_hid_open(struct hid_device *hid) -@@ -109,6 +139,9 @@ static int surface_hid_parse(struct hid_device *hid) - u8 *buf; - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - buf = kzalloc(len, GFP_KERNEL); - if (!buf) - return -ENOMEM; -@@ -126,6 +159,9 @@ static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportn - { - struct surface_hid_device *shid = hid->driver_data; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) - return shid->ops.output_report(shid, reportnum, buf, len); - --- -2.36.1 - -From 987fd2ce1f77db64bb6bfc4372f53569ff9279e6 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 31 Oct 2021 12:34:08 +0100 -Subject: [PATCH] platform/surface: aggregator: Add comment for KIP subsystem - category - -The KIP subsystem (full name unknown, abbreviation has been obtained -through reverse engineering) handles detachable peripherals such as the -keyboard cover on the Surface Pro X and Surface Pro 8. - -It is currently not entirely clear what this subsystem entails, but at -the very least it provides event notifications for when the keyboard -cover on the Surface Pro X and Surface Pro 8 have been detached or -re-attached, as well as the state that the keyboard cover is currently -in (e.g. folded-back, folded laptop-like, closed, etc.). - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - include/linux/surface_aggregator/serial_hub.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h -index c3de43edcffa..26b95ec12733 100644 ---- a/include/linux/surface_aggregator/serial_hub.h -+++ b/include/linux/surface_aggregator/serial_hub.h -@@ -306,7 +306,7 @@ enum ssam_ssh_tc { - SSAM_SSH_TC_LPC = 0x0b, - SSAM_SSH_TC_TCL = 0x0c, - SSAM_SSH_TC_SFL = 0x0d, -- SSAM_SSH_TC_KIP = 0x0e, -+ SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ - SSAM_SSH_TC_EXT = 0x0f, - SSAM_SSH_TC_BLD = 0x10, - SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ --- -2.36.1 - -From 638f1160c139d4b8847eff3e18db47c0b84f6b0d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 29 Apr 2022 22:42:32 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Generify subsystem hub - functionality - -The Surface System Aggregator Module (SSAM) has multiple subsystems that -can manage detachable devices. At the moment, we only support the "base" -(BAS/0x11) subsystem, which is used on the Surface Book 3 to manage -devices (including keyboard, touchpad, and secondary battery) connected -to the base of the device. - -The Surface Pro 8 has a new type-cover with keyboard and touchpad, which -is managed via the KIP/0x0e subsystem. The general procedure is the -same, but with slightly different events and setup. To make -implementation of the KIP hub easier and prevent duplication, generify -the parts of the base hub that we can use for the KIP hub (or any -potential future subsystem hubs). - -This also switches over to use the newly introduced "hot-remove" -functionality, which should prevent communication issues when devices -have been detached. - -Lastly, also drop the undocumented and unused sysfs "state" attribute of -the base hub. It has at best been useful for debugging. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 269 ++++++++++-------- - 1 file changed, 153 insertions(+), 116 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 9f630e890ff7..09cbeee2428b 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -308,30 +308,159 @@ static int ssam_hub_register_clients(struct device *parent, struct ssam_controll - } - - --/* -- SSAM base-hub driver. ------------------------------------------------- */ -+/* -- SSAM generic subsystem hub driver framework. -------------------------- */ - --/* -- * Some devices (especially battery) may need a bit of time to be fully usable -- * after being (re-)connected. This delay has been determined via -- * experimentation. -- */ --#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) -+enum ssam_hub_state { -+ SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ -+ SSAM_HUB_CONNECTED, -+ SSAM_HUB_DISCONNECTED, -+}; - --enum ssam_base_hub_state { -- SSAM_BASE_HUB_UNINITIALIZED, -- SSAM_BASE_HUB_CONNECTED, -- SSAM_BASE_HUB_DISCONNECTED, -+enum ssam_hub_flags { -+ SSAM_HUB_HOT_REMOVED, - }; - --struct ssam_base_hub { -+struct ssam_hub { - struct ssam_device *sdev; - -- enum ssam_base_hub_state state; -+ enum ssam_hub_state state; -+ unsigned long flags; -+ - struct delayed_work update_work; -+ unsigned long connect_delay; - - struct ssam_event_notifier notif; -+ -+ int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); - }; - -+static void ssam_hub_update_workfn(struct work_struct *work) -+{ -+ struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); -+ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -+ enum ssam_hub_state state; -+ int status = 0; -+ -+ status = hub->get_state(hub, &state); -+ if (status) -+ return; -+ -+ /* -+ * There is a small possibility that hub devices were hot-removed and -+ * re-added before we were able to remove them here. In that case, both -+ * the state returned by get_state() and the state of the hub will -+ * equal SSAM_HUB_CONNECTED and we would bail early below, which would -+ * leave child devices without proper (re-)initialization and the -+ * hot-remove flag set. -+ * -+ * Therefore, we check whether devices have been hot-removed via an -+ * additional flag on the hub and, in this case, override the returned -+ * hub state. In case of a missed disconnect (i.e. get_state returned -+ * "connected"), we further need to re-schedule this work (with the -+ * appropriate delay) as the actual connect work submission might have -+ * been merged with this one. -+ * -+ * This then leads to one of two cases: Either we submit an unnecessary -+ * work item (which will get ignored via either the queue or the state -+ * checks) or, in the unlikely case that the work is actually required, -+ * double the normal connect delay. -+ */ -+ if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { -+ if (state == SSAM_HUB_CONNECTED) -+ schedule_delayed_work(&hub->update_work, hub->connect_delay); -+ -+ state = SSAM_HUB_DISCONNECTED; -+ } -+ -+ if (hub->state == state) -+ return; -+ hub->state = state; -+ -+ if (hub->state == SSAM_HUB_CONNECTED) -+ status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); -+ else -+ ssam_remove_clients(&hub->sdev->dev); -+ -+ if (status) -+ dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); -+} -+ -+static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (is_ssam_device(dev)) -+ ssam_device_mark_hot_removed(sdev); -+ -+ return 0; -+} -+ -+static void ssam_hub_update(struct ssam_hub *hub, bool connected) -+{ -+ unsigned long delay; -+ -+ /* Mark devices as hot-removed before we remove any. */ -+ if (!connected) { -+ set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); -+ device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); -+ } -+ -+ /* -+ * Delay update when the base/keyboard cover is being connected to give -+ * devices/EC some time to set up. -+ */ -+ delay = connected ? hub->connect_delay : 0; -+ -+ schedule_delayed_work(&hub->update_work, delay); -+} -+ -+static int __maybe_unused ssam_hub_resume(struct device *dev) -+{ -+ struct ssam_hub *hub = dev_get_drvdata(dev); -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); -+ -+static int ssam_hub_setup(struct ssam_device *sdev, struct ssam_hub *hub) -+{ -+ int status; -+ -+ hub->sdev = sdev; -+ hub->state = SSAM_HUB_UNINITIALIZED; -+ -+ INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, hub); -+ -+ status = ssam_device_notifier_register(sdev, &hub->notif); -+ if (status) -+ return status; -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+ -+static void ssam_hub_remove(struct ssam_device *sdev) -+{ -+ struct ssam_hub *hub = ssam_device_get_drvdata(sdev); -+ -+ ssam_device_notifier_unregister(sdev, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); -+ ssam_remove_clients(&sdev->dev); -+} -+ -+ -+/* -- SSAM base-hub driver. ------------------------------------------------- */ -+ -+/* -+ * Some devices (especially battery) may need a bit of time to be fully usable -+ * after being (re-)connected. This delay has been determined via -+ * experimentation. -+ */ -+#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) -+ - SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, -@@ -342,7 +471,7 @@ SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { - #define SSAM_BAS_OPMODE_TABLET 0x00 - #define SSAM_EVENT_BAS_CID_CONNECTION 0x0c - --static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state) -+static int ssam_base_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) - { - u8 opmode; - int status; -@@ -354,62 +483,16 @@ static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_h - } - - if (opmode != SSAM_BAS_OPMODE_TABLET) -- *state = SSAM_BASE_HUB_CONNECTED; -+ *state = SSAM_HUB_CONNECTED; - else -- *state = SSAM_BASE_HUB_DISCONNECTED; -+ *state = SSAM_HUB_DISCONNECTED; - - return 0; - } - --static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr, -- char *buf) --{ -- struct ssam_base_hub *hub = dev_get_drvdata(dev); -- bool connected = hub->state == SSAM_BASE_HUB_CONNECTED; -- -- return sysfs_emit(buf, "%d\n", connected); --} -- --static struct device_attribute ssam_base_hub_attr_state = -- __ATTR(state, 0444, ssam_base_hub_state_show, NULL); -- --static struct attribute *ssam_base_hub_attrs[] = { -- &ssam_base_hub_attr_state.attr, -- NULL, --}; -- --static const struct attribute_group ssam_base_hub_group = { -- .attrs = ssam_base_hub_attrs, --}; -- --static void ssam_base_hub_update_workfn(struct work_struct *work) --{ -- struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work); -- struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -- enum ssam_base_hub_state state; -- int status = 0; -- -- status = ssam_base_hub_query_state(hub, &state); -- if (status) -- return; -- -- if (hub->state == state) -- return; -- hub->state = state; -- -- if (hub->state == SSAM_BASE_HUB_CONNECTED) -- status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); -- else -- ssam_remove_clients(&hub->sdev->dev); -- -- if (status) -- dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); --} -- - static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) - { -- struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif); -- unsigned long delay; -+ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); - - if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) - return 0; -@@ -419,13 +502,7 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam - return 0; - } - -- /* -- * Delay update when the base is being connected to give devices/EC -- * some time to set up. -- */ -- delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0; -- -- schedule_delayed_work(&hub->update_work, delay); -+ ssam_hub_update(hub, event->data[0]); - - /* - * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -@@ -435,27 +512,14 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam - return 0; - } - --static int __maybe_unused ssam_base_hub_resume(struct device *dev) --{ -- struct ssam_base_hub *hub = dev_get_drvdata(dev); -- -- schedule_delayed_work(&hub->update_work, 0); -- return 0; --} --static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); -- - static int ssam_base_hub_probe(struct ssam_device *sdev) - { -- struct ssam_base_hub *hub; -- int status; -+ struct ssam_hub *hub; - - hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); - if (!hub) - return -ENOMEM; - -- hub->sdev = sdev; -- hub->state = SSAM_BASE_HUB_UNINITIALIZED; -- - hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ - hub->notif.base.fn = ssam_base_hub_notif; - hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -@@ -464,37 +528,10 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - hub->notif.event.mask = SSAM_EVENT_MASK_NONE; - hub->notif.event.flags = SSAM_EVENT_SEQUENCED; - -- INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn); -- -- ssam_device_set_drvdata(sdev, hub); -- -- status = ssam_device_notifier_register(sdev, &hub->notif); -- if (status) -- return status; -- -- status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); -- if (status) -- goto err; -- -- schedule_delayed_work(&hub->update_work, 0); -- return 0; -+ hub->connect_delay = SSAM_BASE_UPDATE_CONNECT_DELAY; -+ hub->get_state = ssam_base_hub_query_state; - --err: -- ssam_device_notifier_unregister(sdev, &hub->notif); -- cancel_delayed_work_sync(&hub->update_work); -- ssam_remove_clients(&sdev->dev); -- return status; --} -- --static void ssam_base_hub_remove(struct ssam_device *sdev) --{ -- struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); -- -- sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); -- -- ssam_device_notifier_unregister(sdev, &hub->notif); -- cancel_delayed_work_sync(&hub->update_work); -- ssam_remove_clients(&sdev->dev); -+ return ssam_hub_setup(sdev, hub); - } - - static const struct ssam_device_id ssam_base_hub_match[] = { -@@ -504,12 +541,12 @@ static const struct ssam_device_id ssam_base_hub_match[] = { - - static struct ssam_device_driver ssam_base_hub_driver = { - .probe = ssam_base_hub_probe, -- .remove = ssam_base_hub_remove, -+ .remove = ssam_hub_remove, - .match_table = ssam_base_hub_match, - .driver = { - .name = "surface_aggregator_base_hub", - .probe_type = PROBE_PREFER_ASYNCHRONOUS, -- .pm = &ssam_base_hub_pm_ops, -+ .pm = &ssam_hub_pm_ops, - }, - }; - --- -2.36.1 - -From 1a1a35dd23f7723192cdd8a1847453990f81031b Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 29 Apr 2022 23:02:06 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add KIP device hub - -Add a Surface System Aggregator Module (SSAM) client device hub for -hot-removable devices managed via the KIP subsystem. - -The KIP subsystem (full name unknown, abbreviation has been obtained -through reverse engineering) is a subsystem that manages hot-removable -SSAM client devices. Specifically, it manages HID input devices -contained in the detachable keyboard cover of the Surface Pro 8 and -Surface Pro X. - -The KIP subsystem handles a single group of devices (e.g. all devices -contained in the keyboard cover) and cannot handle devices individually. -Thus we model it as a client device hub, which (hot-)removes all devices -contained under it once removal of the hub (e.g. keyboard cover) has -been detected and (re-)adds all devices once the physical hub device has -been (re-)attached. To do this, use the previously generified SSAM -subsystem hub framework. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 103 +++++++++++++++++- - 1 file changed, 101 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 09cbeee2428b..1e60435c7cce 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -551,6 +551,93 @@ static struct ssam_device_driver ssam_base_hub_driver = { - }; - - -+/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ -+ -+/* -+ * Some devices may need a bit of time to be fully usable after being -+ * (re-)connected. This delay has been determined via experimentation. -+ */ -+#define SSAM_KIP_UPDATE_CONNECT_DELAY msecs_to_jiffies(250) -+ -+#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_connection_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x2c, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_get_connection_state(struct ssam_hub *hub, enum ssam_hub_state *state) -+{ -+ int status; -+ u8 connected; -+ -+ status = ssam_retry(__ssam_kip_get_connection_state, hub->sdev->ctrl, &connected); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); -+ return status; -+ } -+ -+ *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; -+ return 0; -+} -+ -+static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) { -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ ssam_hub_update(hub, event->data[0]); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static int ssam_kip_hub_probe(struct ssam_device *sdev) -+{ -+ struct ssam_hub *hub; -+ -+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -+ if (!hub) -+ return -ENOMEM; -+ -+ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -+ hub->notif.base.fn = ssam_kip_hub_notif; -+ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ hub->notif.event.id.target_category = SSAM_SSH_TC_KIP, -+ hub->notif.event.id.instance = 0, -+ hub->notif.event.mask = SSAM_EVENT_MASK_TARGET; -+ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ hub->connect_delay = SSAM_KIP_UPDATE_CONNECT_DELAY; -+ hub->get_state = ssam_kip_get_connection_state; -+ -+ return ssam_hub_setup(sdev, hub); -+} -+ -+static const struct ssam_device_id ssam_kip_hub_match[] = { -+ { SSAM_SDEV(KIP, 0x01, 0x00, 0x00) }, -+ { }, -+}; -+ -+static struct ssam_device_driver ssam_kip_hub_driver = { -+ .probe = ssam_kip_hub_probe, -+ .remove = ssam_hub_remove, -+ .match_table = ssam_kip_hub_match, -+ .driver = { -+ .name = "surface_kip_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_hub_pm_ops, -+ }, -+}; -+ -+ - /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ - - static const struct acpi_device_id ssam_platform_hub_match[] = { -@@ -673,18 +760,30 @@ static int __init ssam_device_hub_init(void) - - status = platform_driver_register(&ssam_platform_hub_driver); - if (status) -- return status; -+ goto err_platform; - - status = ssam_device_driver_register(&ssam_base_hub_driver); - if (status) -- platform_driver_unregister(&ssam_platform_hub_driver); -+ goto err_base; -+ -+ status = ssam_device_driver_register(&ssam_kip_hub_driver); -+ if (status) -+ goto err_kip; - -+ return 0; -+ -+err_kip: -+ ssam_device_driver_unregister(&ssam_base_hub_driver); -+err_base: -+ platform_driver_unregister(&ssam_platform_hub_driver); -+err_platform: - return status; - } - module_init(ssam_device_hub_init); - - static void __exit ssam_device_hub_exit(void) - { -+ ssam_device_driver_unregister(&ssam_kip_hub_driver); - ssam_device_driver_unregister(&ssam_base_hub_driver); - platform_driver_unregister(&ssam_platform_hub_driver); - } --- -2.36.1 - -From 335ecc69e57311afd8329349024a0a2c9bc48a69 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 22:33:03 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for - keyboard cover on Surface Pro 8 - -Add support for the detachable keyboard cover on the Surface Pro 8. - -The keyboard cover on the Surface Pro 8 is, unlike the keyboard covers -of earlier Surface Pro generations, handled via the Surface System -Aggregator Module (SSAM). The keyboard and touchpad (as well as other -HID input devices) of this cover are standard SSAM HID client devices -(just like keyboard and touchpad on e.g. the Surface Laptop 3 and 4), -however, some care needs to be taken as they can be physically detached -(similarly to the Surface Book 3). Specifically, the respective SSAM -client devices need to be removed when the keyboard cover has been -detached and (re-)initialized when the keyboard cover has been -(re-)attached. - -On the Surface Pro 8, detachment of the keyboard cover (and by extension -its devices) is managed via the KIP subsystem. Therefore, said devices -need to be registered under the KIP device hub, which in turn will -remove and re-create/re-initialize those devices as needed. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 37 ++++++++++++++++++- - 1 file changed, 36 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 1e60435c7cce..ab69669316bd 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -47,6 +47,12 @@ static const struct software_node ssam_node_hub_base = { - .parent = &ssam_node_root, - }; - -+/* KIP device hub (connects keyboard cover devices on Surface Pro 8). */ -+static const struct software_node ssam_node_hub_kip = { -+ .name = "ssam:01:0e:01:00:00", -+ .parent = &ssam_node_root, -+}; -+ - /* AC adapter. */ - static const struct software_node ssam_node_bat_ac = { - .name = "ssam:01:02:01:01:01", -@@ -155,6 +161,30 @@ static const struct software_node ssam_node_hid_base_iid6 = { - .parent = &ssam_node_hub_base, - }; - -+/* HID keyboard (KIP hub). */ -+static const struct software_node ssam_node_hid_kip_keyboard = { -+ .name = "ssam:01:15:02:01:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID pen stash (KIP hub; pen taken / stashed away evens). */ -+static const struct software_node ssam_node_hid_kip_penstash = { -+ .name = "ssam:01:15:02:02:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID touchpad (KIP hub). */ -+static const struct software_node ssam_node_hid_kip_touchpad = { -+ .name = "ssam:01:15:02:03:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID device instance 5 (KIP hub, unknown HID device). */ -+static const struct software_node ssam_node_hid_kip_iid5 = { -+ .name = "ssam:01:15:02:05:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ - /* - * Devices for 5th- and 6th-generations models: - * - Surface Book 2, -@@ -230,10 +260,15 @@ static const struct software_node *ssam_node_group_sp7[] = { - - static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_root, -+ &ssam_node_hub_kip, - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -- /* TODO: Add support for keyboard cover. */ -+ &ssam_node_hid_kip_keyboard, -+ &ssam_node_hid_kip_penstash, -+ &ssam_node_hid_kip_touchpad, -+ &ssam_node_hid_kip_iid5, -+ /* TODO: Add support for tablet mode switch. */ - NULL, - }; - --- -2.36.1 - -From bbe7fdf41aa6efaa42b327eb9b399eb09532e593 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 03:19:20 +0200 -Subject: [PATCH] platform/surface: Add KIP tablet-mode switch - -Add a driver providing a tablet-mode switch input device for Surface -models using the KIP subsystem to manage detachable peripherals. - -The Surface Pro 8 has a detachable keyboard cover. Unlike the keyboard -covers of previous generation Surface Pro models, this cover is fully -handled by the Surface System Aggregator Module (SSAM). The SSAM KIP -subsystem (full name unknown, abbreviation found through reverse -engineering) provides notifications for mode changes of the cover. -Specifically, it allows us to know when the cover has been folded back, -detached, or whether it is in laptop mode. - -The driver introduced with this change captures these events and -translates them to standard SW_TABLET_MODE input events. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - MAINTAINERS | 6 + - drivers/platform/surface/Kconfig | 22 ++ - drivers/platform/surface/Makefile | 1 + - .../surface/surface_kip_tablet_switch.c | 245 ++++++++++++++++++ - 4 files changed, 274 insertions(+) - create mode 100644 drivers/platform/surface/surface_kip_tablet_switch.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index d9b2f1731ee0..4d83cd26e299 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -12823,6 +12823,12 @@ L: platform-driver-x86@vger.kernel.org - S: Maintained - F: drivers/platform/surface/surface_hotplug.c - -+MICROSOFT SURFACE KIP TABLET-MODE SWITCH -+M: Maximilian Luz -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/platform/surface/surface_kip_tablet_switch.c -+ - MICROSOFT SURFACE PLATFORM PROFILE DRIVER - M: Maximilian Luz - L: platform-driver-x86@vger.kernel.org -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 463f1ec5c14e..9c228090c35b 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -156,6 +156,28 @@ config SURFACE_HOTPLUG - Select M or Y here, if you want to (fully) support hot-plugging of - dGPU devices on the Surface Book 2 and/or 3 during D3cold. - -+config SURFACE_KIP_TABLET_SWITCH -+ tristate "Surface KIP Tablet-Mode Switch Driver" -+ depends on SURFACE_AGGREGATOR -+ depends on SURFACE_AGGREGATOR_BUS -+ depends on INPUT -+ help -+ Provides a tablet-mode switch input device on Microsoft Surface models -+ using the KIP subsystem for detachable keyboards (e.g. keyboard -+ covers). -+ -+ The KIP subsystem is used on newer Surface generations to handle -+ detachable input peripherals, specifically the keyboard cover -+ (containing keyboard and touchpad) on the Surface Pro 8. This module -+ provides a driver to let user-space know when the device should be -+ considered in tablet-mode due to the keyboard cover being detached or -+ folded back (essentially signaling when the keyboard is not available -+ for input). It does so by creating a tablet-mode switch input device, -+ sending the standard SW_TABLET_MODE event on mode change. -+ -+ Select M or Y here, if you want to provide tablet-mode switch input -+ events on the Surface Pro 8. -+ - config SURFACE_PLATFORM_PROFILE - tristate "Surface Platform Profile Driver" - depends on ACPI -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 32889482de55..6d9291c993c4 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -14,5 +14,6 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -+obj-$(CONFIG_SURFACE_KIP_TABLET_SWITCH) += surface_kip_tablet_switch.o - obj-$(CONFIG_SURFACE_PLATFORM_PROFILE) += surface_platform_profile.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o -diff --git a/drivers/platform/surface/surface_kip_tablet_switch.c b/drivers/platform/surface/surface_kip_tablet_switch.c -new file mode 100644 -index 000000000000..27371da71ef2 ---- /dev/null -+++ b/drivers/platform/surface/surface_kip_tablet_switch.c -@@ -0,0 +1,245 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) tablet mode switch via KIP -+ * subsystem. -+ * -+ * Copyright (C) 2022 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#define SSAM_EVENT_KIP_CID_LID_STATE 0x1d -+ -+enum ssam_kip_lid_state { -+ SSAM_KIP_LID_STATE_DISCONNECTED = 0x01, -+ SSAM_KIP_LID_STATE_CLOSED = 0x02, -+ SSAM_KIP_LID_STATE_LAPTOP = 0x03, -+ SSAM_KIP_LID_STATE_FOLDED_CANVAS = 0x04, -+ SSAM_KIP_LID_STATE_FOLDED_BACK = 0x05, -+}; -+ -+struct ssam_kip_sw { -+ struct ssam_device *sdev; -+ -+ enum ssam_kip_lid_state state; -+ struct work_struct update_work; -+ struct input_dev *mode_switch; -+ -+ struct ssam_event_notifier notif; -+}; -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_lid_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x1d, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_get_lid_state(struct ssam_kip_sw *sw, enum ssam_kip_lid_state *state) -+{ -+ int status; -+ u8 raw; -+ -+ status = ssam_retry(__ssam_kip_get_lid_state, sw->sdev->ctrl, &raw); -+ if (status < 0) { -+ dev_err(&sw->sdev->dev, "failed to query KIP lid state: %d\n", status); -+ return status; -+ } -+ -+ *state = raw; -+ return 0; -+} -+ -+static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct ssam_kip_sw *sw = dev_get_drvdata(dev); -+ const char *state; -+ -+ switch (sw->state) { -+ case SSAM_KIP_LID_STATE_DISCONNECTED: -+ state = "disconnected"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_CLOSED: -+ state = "closed"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_LAPTOP: -+ state = "laptop"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_FOLDED_CANVAS: -+ state = "folded-canvas"; -+ break; -+ -+ case SSAM_KIP_LID_STATE_FOLDED_BACK: -+ state = "folded-back"; -+ break; -+ -+ default: -+ state = ""; -+ dev_warn(dev, "unknown KIP lid state: %d\n", sw->state); -+ break; -+ } -+ -+ return sysfs_emit(buf, "%s\n", state); -+} -+static DEVICE_ATTR_RO(state); -+ -+static struct attribute *ssam_kip_sw_attrs[] = { -+ &dev_attr_state.attr, -+ NULL, -+}; -+ -+static const struct attribute_group ssam_kip_sw_group = { -+ .attrs = ssam_kip_sw_attrs, -+}; -+ -+static void ssam_kip_sw_update_workfn(struct work_struct *work) -+{ -+ struct ssam_kip_sw *sw = container_of(work, struct ssam_kip_sw, update_work); -+ enum ssam_kip_lid_state state; -+ int tablet, status; -+ -+ status = ssam_kip_get_lid_state(sw, &state); -+ if (status) -+ return; -+ -+ if (sw->state == state) -+ return; -+ sw->state = state; -+ -+ /* Send SW_TABLET_MODE event. */ -+ tablet = state != SSAM_KIP_LID_STATE_LAPTOP; -+ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); -+ input_sync(sw->mode_switch); -+} -+ -+static u32 ssam_kip_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_kip_sw *sw = container_of(nf, struct ssam_kip_sw, notif); -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_LID_STATE) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) { -+ dev_err(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ schedule_work(&sw->update_work); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static int __maybe_unused ssam_kip_sw_resume(struct device *dev) -+{ -+ struct ssam_kip_sw *sw = dev_get_drvdata(dev); -+ -+ schedule_work(&sw->update_work); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_kip_sw_pm_ops, NULL, ssam_kip_sw_resume); -+ -+static int ssam_kip_sw_probe(struct ssam_device *sdev) -+{ -+ struct ssam_kip_sw *sw; -+ int tablet, status; -+ -+ sw = devm_kzalloc(&sdev->dev, sizeof(*sw), GFP_KERNEL); -+ if (!sw) -+ return -ENOMEM; -+ -+ sw->sdev = sdev; -+ INIT_WORK(&sw->update_work, ssam_kip_sw_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, sw); -+ -+ /* Get initial state. */ -+ status = ssam_kip_get_lid_state(sw, &sw->state); -+ if (status) -+ return status; -+ -+ /* Set up tablet mode switch. */ -+ sw->mode_switch = devm_input_allocate_device(&sdev->dev); -+ if (!sw->mode_switch) -+ return -ENOMEM; -+ -+ sw->mode_switch->name = "Microsoft Surface KIP Tablet Mode Switch"; -+ sw->mode_switch->phys = "ssam/01:0e:01:00:01/input0"; -+ sw->mode_switch->id.bustype = BUS_HOST; -+ sw->mode_switch->dev.parent = &sdev->dev; -+ -+ tablet = sw->state != SSAM_KIP_LID_STATE_LAPTOP; -+ input_set_capability(sw->mode_switch, EV_SW, SW_TABLET_MODE); -+ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); -+ -+ status = input_register_device(sw->mode_switch); -+ if (status) -+ return status; -+ -+ /* Set up notifier. */ -+ sw->notif.base.priority = 0; -+ sw->notif.base.fn = ssam_kip_sw_notif; -+ sw->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ sw->notif.event.id.target_category = SSAM_SSH_TC_KIP, -+ sw->notif.event.id.instance = 0, -+ sw->notif.event.mask = SSAM_EVENT_MASK_TARGET; -+ sw->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ status = ssam_device_notifier_register(sdev, &sw->notif); -+ if (status) -+ return status; -+ -+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_kip_sw_group); -+ if (status) -+ goto err; -+ -+ /* We might have missed events during setup, so check again. */ -+ schedule_work(&sw->update_work); -+ return 0; -+ -+err: -+ ssam_device_notifier_unregister(sdev, &sw->notif); -+ cancel_work_sync(&sw->update_work); -+ return status; -+} -+ -+static void ssam_kip_sw_remove(struct ssam_device *sdev) -+{ -+ struct ssam_kip_sw *sw = ssam_device_get_drvdata(sdev); -+ -+ sysfs_remove_group(&sdev->dev.kobj, &ssam_kip_sw_group); -+ -+ ssam_device_notifier_unregister(sdev, &sw->notif); -+ cancel_work_sync(&sw->update_work); -+} -+ -+static const struct ssam_device_id ssam_kip_sw_match[] = { -+ { SSAM_SDEV(KIP, 0x01, 0x00, 0x01) }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, ssam_kip_sw_match); -+ -+static struct ssam_device_driver ssam_kip_sw_driver = { -+ .probe = ssam_kip_sw_probe, -+ .remove = ssam_kip_sw_remove, -+ .match_table = ssam_kip_sw_match, -+ .driver = { -+ .name = "surface_kip_tablet_mode_switch", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_kip_sw_pm_ops, -+ }, -+}; -+module_ssam_device_driver(ssam_kip_sw_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Tablet mode switch driver for Surface devices using KIP subsystem"); -+MODULE_LICENSE("GPL"); --- -2.36.1 - -From d60c4b561fd96cc78ae0a4513008a2c61cdfa7b9 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 22:33:03 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for tablet - mode switch on Surface Pro 8 - -Add a KIP subsystem tablet-mode switch device for the Surface Pro 8. -The respective driver for this device provides SW_TABLET_MODE input -events for user-space based on the state of the keyboard cover (e.g. -detached, folded-back, normal/laptop mode). - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 8 +++++++- - 1 file changed, 7 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index ab69669316bd..c666392d4a9a 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -77,6 +77,12 @@ static const struct software_node ssam_node_tmp_pprof = { - .parent = &ssam_node_root, - }; - -+/* Tablet-mode switch via KIP subsystem. */ -+static const struct software_node ssam_node_kip_tablet_switch = { -+ .name = "ssam:01:0e:01:00:01", -+ .parent = &ssam_node_root, -+}; -+ - /* DTX / detachment-system device (Surface Book 3). */ - static const struct software_node ssam_node_bas_dtx = { - .name = "ssam:01:11:01:00:00", -@@ -264,11 +270,11 @@ static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -+ &ssam_node_kip_tablet_switch, - &ssam_node_hid_kip_keyboard, - &ssam_node_hid_kip_penstash, - &ssam_node_hid_kip_touchpad, - &ssam_node_hid_kip_iid5, -- /* TODO: Add support for tablet mode switch. */ - NULL, - }; - --- -2.36.1 - -From 823f673da298f4a1b6b088c7a9d54b9c5faa180a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 21 May 2022 00:30:46 +0200 -Subject: [PATCH] platform/surface: aggregator: Move device registry helper - function to core module - -Move helper functions for client device registration to the core module. -This simplifies addition of future DT/OF support and also allows us to -split out the device hub drivers into their own module. - -At the same time, also improve device node validation a bit by not -silently skipping devices with invalid device UID specifiers. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/bus.c | 176 ++++++++++++++++-- - .../surface/surface_aggregator_registry.c | 75 +------- - include/linux/surface_aggregator/device.h | 37 ++++ - 3 files changed, 199 insertions(+), 89 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -index abbbb5b08b07..4bba60884bb5 100644 ---- a/drivers/platform/surface/aggregator/bus.c -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -6,6 +6,7 @@ - */ - - #include -+#include - #include - - #include -@@ -14,6 +15,9 @@ - #include "bus.h" - #include "controller.h" - -+ -+/* -- Device and bus functions. --------------------------------------------- */ -+ - static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, - char *buf) - { -@@ -363,6 +367,162 @@ void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) - } - EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); - -+ -+/* -- Bus registration. ----------------------------------------------------- */ -+ -+/** -+ * ssam_bus_register() - Register and set-up the SSAM client device bus. -+ */ -+int ssam_bus_register(void) -+{ -+ return bus_register(&ssam_bus_type); -+} -+ -+/** -+ * ssam_bus_unregister() - Unregister the SSAM client device bus. -+ */ -+void ssam_bus_unregister(void) -+{ -+ return bus_unregister(&ssam_bus_type); -+} -+ -+ -+/* -- Helpers for controller and hub devices. ------------------------------- */ -+ -+static int ssam_device_uid_from_string(const char *str, struct ssam_device_uid *uid) -+{ -+ u8 d, tc, tid, iid, fn; -+ int n; -+ -+ n = sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); -+ if (n != 5) -+ return -EINVAL; -+ -+ uid->domain = d; -+ uid->category = tc; -+ uid->target = tid; -+ uid->instance = iid; -+ uid->function = fn; -+ -+ return 0; -+} -+ -+static int ssam_get_uid_for_node(struct fwnode_handle *node, struct ssam_device_uid *uid) -+{ -+ const char* str = fwnode_get_name(node); -+ -+ /* -+ * To simplify definitions of firmware nodes, we set the device name -+ * based on the UID of the device, prefixed with "ssam:". -+ */ -+ if (strncmp(str, "ssam:", strlen("ssam:")) != 0) -+ return -ENODEV; -+ -+ str += strlen("ssam:"); -+ return ssam_device_uid_from_string(str, uid); -+} -+ -+static int ssam_add_client_device(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ struct ssam_device_uid uid; -+ struct ssam_device *sdev; -+ int status; -+ -+ status = ssam_get_uid_for_node(node, &uid); -+ if (status) -+ return status; -+ -+ sdev = ssam_device_alloc(ctrl, uid); -+ if (!sdev) -+ return -ENOMEM; -+ -+ sdev->dev.parent = parent; -+ sdev->dev.fwnode = node; -+ -+ status = ssam_device_add(sdev); -+ if (status) -+ ssam_device_put(sdev); -+ -+ return status; -+} -+ -+/** -+ * __ssam_register_clients() - Register client devices defined under the -+ * given firmware node as children of the given device. -+ * @parent: The parent device under which clients should be registered. -+ * @ctrl: The controller with which client should be registered. -+ * @node: The firmware node holding definitions of the devices to be added. -+ * -+ * Register all clients that have been defined as children of the given root -+ * firmware node as children of the given parent device. The respective child -+ * firmware nodes will be associated with the correspondingly created child -+ * devices. -+ * -+ * The given controller will be used to instantiate the new devices. See -+ * ssam_device_add() for details. -+ * -+ * Note that, generally, the use of either ssam_device_register_clients() or -+ * ssam_register_clients() should be preferred as they directly use the -+ * firmware node and/or controller associated with the given device. This -+ * function is only intended for use when different device specifications (e.g. -+ * ACPI and firmware nodes) need to be combined (as is done in the platform hub -+ * of the device registry). -+ * -+ * Return: Returns zero on success, nonzero on failure. -+ */ -+int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ struct fwnode_handle *child; -+ int status; -+ -+ fwnode_for_each_child_node(node, child) { -+ /* -+ * Try to add the device specified in the firmware node. If -+ * this fails with -ENODEV, the node does not specify any SSAM -+ * device, so ignore it and continue with the next one. -+ */ -+ status = ssam_add_client_device(parent, ctrl, child); -+ if (status && status != -ENODEV) -+ goto err; -+ } -+ -+ return 0; -+err: -+ ssam_remove_clients(parent); -+ return status; -+} -+EXPORT_SYMBOL_GPL(__ssam_register_clients); -+ -+/** -+ * ssam_register_clients() - Register all client devices defined under the -+ * given parent device. -+ * @dev: The parent device under which clients should be registered. -+ * @ctrl: The controller with which client should be registered. -+ * -+ * Register all clients that have via firmware nodes been defined as children -+ * of the given (parent) device. The respective child firmware nodes will be -+ * associated with the correspondingly created child devices. -+ * -+ * The given controller will be used to instantiate the new devices. See -+ * ssam_device_add() for details. -+ * -+ * Return: Returns zero on success, nonzero on failure. -+ */ -+int ssam_register_clients(struct device *dev, struct ssam_controller *ctrl) -+{ -+ struct fwnode_handle *node; -+ int status; -+ -+ node = fwnode_handle_get(dev_fwnode(dev)); -+ status = __ssam_register_clients(dev, ctrl, node); -+ fwnode_handle_put(node); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_register_clients); -+ - static int ssam_remove_device(struct device *dev, void *_data) - { - struct ssam_device *sdev = to_ssam_device(dev); -@@ -387,19 +547,3 @@ void ssam_remove_clients(struct device *dev) - device_for_each_child_reverse(dev, NULL, ssam_remove_device); - } - EXPORT_SYMBOL_GPL(ssam_remove_clients); -- --/** -- * ssam_bus_register() - Register and set-up the SSAM client device bus. -- */ --int ssam_bus_register(void) --{ -- return bus_register(&ssam_bus_type); --} -- --/** -- * ssam_bus_unregister() - Unregister the SSAM client device bus. -- */ --void ssam_bus_unregister(void) --{ -- return bus_unregister(&ssam_bus_type); --} -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index c666392d4a9a..3261c8141841 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -279,76 +279,6 @@ static const struct software_node *ssam_node_group_sp8[] = { - }; - - --/* -- Device registry helper functions. ------------------------------------- */ -- --static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) --{ -- u8 d, tc, tid, iid, fn; -- int n; -- -- n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); -- if (n != 5) -- return -EINVAL; -- -- uid->domain = d; -- uid->category = tc; -- uid->target = tid; -- uid->instance = iid; -- uid->function = fn; -- -- return 0; --} -- --static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, -- struct fwnode_handle *node) --{ -- struct ssam_device_uid uid; -- struct ssam_device *sdev; -- int status; -- -- status = ssam_uid_from_string(fwnode_get_name(node), &uid); -- if (status) -- return status; -- -- sdev = ssam_device_alloc(ctrl, uid); -- if (!sdev) -- return -ENOMEM; -- -- sdev->dev.parent = parent; -- sdev->dev.fwnode = node; -- -- status = ssam_device_add(sdev); -- if (status) -- ssam_device_put(sdev); -- -- return status; --} -- --static int ssam_hub_register_clients(struct device *parent, struct ssam_controller *ctrl, -- struct fwnode_handle *node) --{ -- struct fwnode_handle *child; -- int status; -- -- fwnode_for_each_child_node(node, child) { -- /* -- * Try to add the device specified in the firmware node. If -- * this fails with -EINVAL, the node does not specify any SSAM -- * device, so ignore it and continue with the next one. -- */ -- -- status = ssam_hub_add_device(parent, ctrl, child); -- if (status && status != -EINVAL) -- goto err; -- } -- -- return 0; --err: -- ssam_remove_clients(parent); -- return status; --} -- -- - /* -- SSAM generic subsystem hub driver framework. -------------------------- */ - - enum ssam_hub_state { -@@ -378,7 +308,6 @@ struct ssam_hub { - static void ssam_hub_update_workfn(struct work_struct *work) - { - struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); -- struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); - enum ssam_hub_state state; - int status = 0; - -@@ -418,7 +347,7 @@ static void ssam_hub_update_workfn(struct work_struct *work) - hub->state = state; - - if (hub->state == SSAM_HUB_CONNECTED) -- status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); -+ status = ssam_device_register_clients(hub->sdev); - else - ssam_remove_clients(&hub->sdev->dev); - -@@ -762,7 +691,7 @@ static int ssam_platform_hub_probe(struct platform_device *pdev) - - set_secondary_fwnode(&pdev->dev, root); - -- status = ssam_hub_register_clients(&pdev->dev, ctrl, root); -+ status = __ssam_register_clients(&pdev->dev, ctrl, root); - if (status) { - set_secondary_fwnode(&pdev->dev, NULL); - software_node_unregister_node_group(nodes); -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index ad245c6b00d0..6ae110e830b4 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -364,11 +364,48 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); - /* -- Helpers for controller and hub devices. ------------------------------- */ - - #ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+ -+int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node); -+int ssam_register_clients(struct device *dev, struct ssam_controller *ctrl); - void ssam_remove_clients(struct device *dev); -+ - #else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+static inline int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ return 0; -+} -+ -+static inline int ssam_register_clients(struct device *dev, struct ssam_controller *ctrl) -+{ -+ return 0; -+} -+ - static inline void ssam_remove_clients(struct device *dev) {} -+ - #endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ - -+/** -+ * ssam_device_register_clients() - Register all client devices defined under -+ * the given SSAM parent device. -+ * @sdev: The parent device under which clients should be registered. -+ * -+ * Register all clients that have via firmware nodes been defined as children -+ * of the given (parent) device. The respective child firmware nodes will be -+ * associated with the correspondingly created child devices. -+ * -+ * The controller used by the parent device will be used to instantiate the new -+ * devices. See ssam_device_add() for details. -+ * -+ * Return: Returns zero on success, nonzero on failure. -+ */ -+static inline int ssam_device_register_clients(struct ssam_device *sdev) -+{ -+ return ssam_register_clients(&sdev->dev, sdev->ctrl); -+} -+ - - /* -- Helpers for client-device requests. ----------------------------------- */ - --- -2.36.1 - -From a3075f9716bfa922bbbe9cba0eb31835a5b613ad Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 21 May 2022 00:39:56 +0200 -Subject: [PATCH] platform/surface: aggregator: Move subsystem hub drivers to - their own module - -Split out subsystem device hub drivers into their own module. This -allows us to load the hub drivers separately from the registry, which -will help future DT/OF support. - -While doing so, also remove a small bit of code duplication. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/Kconfig | 35 +- - drivers/platform/surface/Makefile | 1 + - .../platform/surface/surface_aggregator_hub.c | 363 +++++++++++++++++ - .../surface/surface_aggregator_registry.c | 371 +----------------- - 4 files changed, 396 insertions(+), 374 deletions(-) - create mode 100644 drivers/platform/surface/surface_aggregator_hub.c - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index 9c228090c35b..c685ec440535 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -79,18 +79,45 @@ config SURFACE_AGGREGATOR_CDEV - The provided interface is intended for debugging and development only, - and should not be used otherwise. - -+config SURFACE_AGGREGATOR_HUB -+ tristate "Surface System Aggregator Module Subsystem Device Hubs" -+ depends on SURFACE_AGGREGATOR -+ depends on SURFACE_AGGREGATOR_BUS -+ help -+ Device-hub drivers for Surface System Aggregator Module (SSAM) subsystem -+ devices. -+ -+ Provides subsystem hub drivers which manage client devices on various -+ SSAM subsystems. In some subsystems, notably the BAS subsystem managing -+ devices contained in the base of the Surface Book 3 and the KIP subsystem -+ managing type-cover devices in the Surface Pro 8 and Surface Pro X, -+ devices can be (hot-)removed. Hub devices and drivers are required to -+ manage these subdevices. -+ -+ Devices managed via these hubs are: -+ - Battery/AC devices (Surface Book 3). -+ - HID input devices (7th-generation and later models with detachable -+ input devices). -+ -+ Select M (recommended) or Y here if you want support for the above -+ mentioned devices on the corresponding Surface models. Without this -+ module, the respective devices mentioned above will not be instantiated -+ and thus any functionality provided by them will be missing, even when -+ drivers for these devices are present. This module only provides the -+ respective subsystem hubs. Both drivers and device specification (e.g. -+ via the Surface Aggregator Registry) for these devices still need to be -+ selected via other options. -+ - config SURFACE_AGGREGATOR_REGISTRY - tristate "Surface System Aggregator Module Device Registry" - depends on SURFACE_AGGREGATOR - depends on SURFACE_AGGREGATOR_BUS - help -- Device-registry and device-hubs for Surface System Aggregator Module -- (SSAM) devices. -+ Device-registry for Surface System Aggregator Module (SSAM) devices. - - Provides a module and driver which act as a device-registry for SSAM - client devices that cannot be detected automatically, e.g. via ACPI. -- Such devices are instead provided via this registry and attached via -- device hubs, also provided in this module. -+ Such devices are instead provided and managed via this registry. - - Devices provided via this registry are: - - Platform profile (performance-/cooling-mode) device (5th- and later -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 6d9291c993c4..fccd33e6780d 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -10,6 +10,7 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o -+obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o -diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c -new file mode 100644 -index 000000000000..20b1c38debfe ---- /dev/null -+++ b/drivers/platform/surface/surface_aggregator_hub.c -@@ -0,0 +1,363 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Driver for Surface System Aggregator Module (SSAM) subsystem device hubs. -+ * -+ * Provides a driver for SSAM subsystems device hubs. This driver performs -+ * instantiation of the devices managed by said hubs and takes care of -+ * (hot-)removal. -+ * -+ * Copyright (C) 2020-2022 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- SSAM generic subsystem hub driver framework. -------------------------- */ -+ -+enum ssam_hub_state { -+ SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ -+ SSAM_HUB_CONNECTED, -+ SSAM_HUB_DISCONNECTED, -+}; -+ -+enum ssam_hub_flags { -+ SSAM_HUB_HOT_REMOVED, -+}; -+ -+struct ssam_hub { -+ struct ssam_device *sdev; -+ -+ enum ssam_hub_state state; -+ unsigned long flags; -+ -+ struct delayed_work update_work; -+ unsigned long connect_delay; -+ -+ struct ssam_event_notifier notif; -+ -+ int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); -+}; -+ -+struct ssam_hub_info { -+ struct { -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ enum ssam_event_mask mask; -+ u8 flags; -+ } event; -+ -+ u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event); -+ int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); -+ -+ unsigned long connect_delay_ms; -+}; -+ -+static void ssam_hub_update_workfn(struct work_struct *work) -+{ -+ struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); -+ enum ssam_hub_state state; -+ int status = 0; -+ -+ status = hub->get_state(hub, &state); -+ if (status) -+ return; -+ -+ /* -+ * There is a small possibility that hub devices were hot-removed and -+ * re-added before we were able to remove them here. In that case, both -+ * the state returned by get_state() and the state of the hub will -+ * equal SSAM_HUB_CONNECTED and we would bail early below, which would -+ * leave child devices without proper (re-)initialization and the -+ * hot-remove flag set. -+ * -+ * Therefore, we check whether devices have been hot-removed via an -+ * additional flag on the hub and, in this case, override the returned -+ * hub state. In case of a missed disconnect (i.e. get_state returned -+ * "connected"), we further need to re-schedule this work (with the -+ * appropriate delay) as the actual connect work submission might have -+ * been merged with this one. -+ * -+ * This then leads to one of two cases: Either we submit an unnecessary -+ * work item (which will get ignored via either the queue or the state -+ * checks) or, in the unlikely case that the work is actually required, -+ * double the normal connect delay. -+ */ -+ if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { -+ if (state == SSAM_HUB_CONNECTED) -+ schedule_delayed_work(&hub->update_work, hub->connect_delay); -+ -+ state = SSAM_HUB_DISCONNECTED; -+ } -+ -+ if (hub->state == state) -+ return; -+ hub->state = state; -+ -+ if (hub->state == SSAM_HUB_CONNECTED) -+ status = ssam_device_register_clients(hub->sdev); -+ else -+ ssam_remove_clients(&hub->sdev->dev); -+ -+ if (status) -+ dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); -+} -+ -+static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (is_ssam_device(dev)) -+ ssam_device_mark_hot_removed(sdev); -+ -+ return 0; -+} -+ -+static void ssam_hub_update(struct ssam_hub *hub, bool connected) -+{ -+ unsigned long delay; -+ -+ /* Mark devices as hot-removed before we remove any. */ -+ if (!connected) { -+ set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); -+ device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); -+ } -+ -+ /* -+ * Delay update when the base/keyboard cover is being connected to give -+ * devices/EC some time to set up. -+ */ -+ delay = connected ? hub->connect_delay : 0; -+ -+ schedule_delayed_work(&hub->update_work, delay); -+} -+ -+static int __maybe_unused ssam_hub_resume(struct device *dev) -+{ -+ struct ssam_hub *hub = dev_get_drvdata(dev); -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); -+ -+static int ssam_hub_probe(struct ssam_device *sdev) -+{ -+ const struct ssam_hub_info *info; -+ struct ssam_hub *hub; -+ int status; -+ -+ info = ssam_device_get_match_data(sdev); -+ if (!info) { -+ WARN(1, "no driver match data specified"); -+ return -EINVAL; -+ } -+ -+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -+ if (!hub) -+ return -ENOMEM; -+ -+ hub->sdev = sdev; -+ hub->state = SSAM_HUB_UNINITIALIZED; -+ -+ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -+ hub->notif.base.fn = info->notify; -+ hub->notif.event.reg = info->event.reg; -+ hub->notif.event.id = info->event.id; -+ hub->notif.event.mask = info->event.mask; -+ hub->notif.event.flags = info->event.flags; -+ -+ hub->connect_delay = msecs_to_jiffies(info->connect_delay_ms); -+ hub->get_state = info->get_state; -+ -+ INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, hub); -+ -+ status = ssam_device_notifier_register(sdev, &hub->notif); -+ if (status) -+ return status; -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+ -+static void ssam_hub_remove(struct ssam_device *sdev) -+{ -+ struct ssam_hub *hub = ssam_device_get_drvdata(sdev); -+ -+ ssam_device_notifier_unregister(sdev, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); -+ ssam_remove_clients(&sdev->dev); -+} -+ -+ -+/* -- SSAM base-subsystem hub driver. --------------------------------------- */ -+ -+/* -+ * Some devices (especially battery) may need a bit of time to be fully usable -+ * after being (re-)connected. This delay has been determined via -+ * experimentation. -+ */ -+#define SSAM_BASE_UPDATE_CONNECT_DELAY 2500 -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0d, -+ .instance_id = 0x00, -+}); -+ -+#define SSAM_BAS_OPMODE_TABLET 0x00 -+#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c -+ -+static int ssam_base_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) -+{ -+ u8 opmode; -+ int status; -+ -+ status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); -+ return status; -+ } -+ -+ if (opmode != SSAM_BAS_OPMODE_TABLET) -+ *state = SSAM_HUB_CONNECTED; -+ else -+ *state = SSAM_HUB_DISCONNECTED; -+ -+ return 0; -+} -+ -+static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -+ -+ if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) -+ return 0; -+ -+ if (event->length < 1) { -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ ssam_hub_update(hub, event->data[0]); -+ -+ /* -+ * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -+ * consumed by the detachment system driver. We're just a (more or less) -+ * silent observer. -+ */ -+ return 0; -+} -+ -+static const struct ssam_hub_info base_hub = { -+ .event = { -+ .reg = SSAM_EVENT_REGISTRY_SAM, -+ .id = { -+ .target_category = SSAM_SSH_TC_BAS, -+ .instance = 0, -+ }, -+ .mask = SSAM_EVENT_MASK_NONE, -+ .flags = SSAM_EVENT_SEQUENCED, -+ }, -+ .notify = ssam_base_hub_notif, -+ .get_state = ssam_base_hub_query_state, -+ .connect_delay_ms = SSAM_BASE_UPDATE_CONNECT_DELAY, -+}; -+ -+ -+/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ -+ -+/* -+ * Some devices may need a bit of time to be fully usable after being -+ * (re-)connected. This delay has been determined via experimentation. -+ */ -+#define SSAM_KIP_UPDATE_CONNECT_DELAY 250 -+ -+#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_query_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x2c, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) -+{ -+ int status; -+ u8 connected; -+ -+ status = ssam_retry(__ssam_kip_query_state, hub->sdev->ctrl, &connected); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); -+ return status; -+ } -+ -+ *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; -+ return 0; -+} -+ -+static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) { -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ ssam_hub_update(hub, event->data[0]); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static const struct ssam_hub_info kip_hub = { -+ .event = { -+ .reg = SSAM_EVENT_REGISTRY_SAM, -+ .id = { -+ .target_category = SSAM_SSH_TC_KIP, -+ .instance = 0, -+ }, -+ .mask = SSAM_EVENT_MASK_TARGET, -+ .flags = SSAM_EVENT_SEQUENCED, -+ }, -+ .notify = ssam_kip_hub_notif, -+ .get_state = ssam_kip_hub_query_state, -+ .connect_delay_ms = SSAM_KIP_UPDATE_CONNECT_DELAY, -+}; -+ -+ -+/* -- Driver registration. -------------------------------------------------- */ -+ -+static const struct ssam_device_id ssam_hub_match[] = { -+ { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00), (unsigned long)&base_hub }, -+ { SSAM_SDEV(KIP, 0x01, 0x00, 0x00), (unsigned long)&kip_hub }, -+ { } -+}; -+MODULE_DEVICE_TABLE(ssam, ssam_hub_match); -+ -+static struct ssam_device_driver ssam_subsystem_hub_driver = { -+ .probe = ssam_hub_probe, -+ .remove = ssam_hub_remove, -+ .match_table = ssam_hub_match, -+ .driver = { -+ .name = "surface_aggregator_subsystem_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_hub_pm_ops, -+ }, -+}; -+module_ssam_device_driver(ssam_subsystem_hub_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Subsystem device hub driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 3261c8141841..11b51aa9ea73 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -11,14 +11,11 @@ - - #include - #include --#include - #include - #include - #include - #include --#include - --#include - #include - - -@@ -279,335 +276,6 @@ static const struct software_node *ssam_node_group_sp8[] = { - }; - - --/* -- SSAM generic subsystem hub driver framework. -------------------------- */ -- --enum ssam_hub_state { -- SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ -- SSAM_HUB_CONNECTED, -- SSAM_HUB_DISCONNECTED, --}; -- --enum ssam_hub_flags { -- SSAM_HUB_HOT_REMOVED, --}; -- --struct ssam_hub { -- struct ssam_device *sdev; -- -- enum ssam_hub_state state; -- unsigned long flags; -- -- struct delayed_work update_work; -- unsigned long connect_delay; -- -- struct ssam_event_notifier notif; -- -- int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); --}; -- --static void ssam_hub_update_workfn(struct work_struct *work) --{ -- struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); -- enum ssam_hub_state state; -- int status = 0; -- -- status = hub->get_state(hub, &state); -- if (status) -- return; -- -- /* -- * There is a small possibility that hub devices were hot-removed and -- * re-added before we were able to remove them here. In that case, both -- * the state returned by get_state() and the state of the hub will -- * equal SSAM_HUB_CONNECTED and we would bail early below, which would -- * leave child devices without proper (re-)initialization and the -- * hot-remove flag set. -- * -- * Therefore, we check whether devices have been hot-removed via an -- * additional flag on the hub and, in this case, override the returned -- * hub state. In case of a missed disconnect (i.e. get_state returned -- * "connected"), we further need to re-schedule this work (with the -- * appropriate delay) as the actual connect work submission might have -- * been merged with this one. -- * -- * This then leads to one of two cases: Either we submit an unnecessary -- * work item (which will get ignored via either the queue or the state -- * checks) or, in the unlikely case that the work is actually required, -- * double the normal connect delay. -- */ -- if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { -- if (state == SSAM_HUB_CONNECTED) -- schedule_delayed_work(&hub->update_work, hub->connect_delay); -- -- state = SSAM_HUB_DISCONNECTED; -- } -- -- if (hub->state == state) -- return; -- hub->state = state; -- -- if (hub->state == SSAM_HUB_CONNECTED) -- status = ssam_device_register_clients(hub->sdev); -- else -- ssam_remove_clients(&hub->sdev->dev); -- -- if (status) -- dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); --} -- --static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) --{ -- struct ssam_device *sdev = to_ssam_device(dev); -- -- if (is_ssam_device(dev)) -- ssam_device_mark_hot_removed(sdev); -- -- return 0; --} -- --static void ssam_hub_update(struct ssam_hub *hub, bool connected) --{ -- unsigned long delay; -- -- /* Mark devices as hot-removed before we remove any. */ -- if (!connected) { -- set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); -- device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); -- } -- -- /* -- * Delay update when the base/keyboard cover is being connected to give -- * devices/EC some time to set up. -- */ -- delay = connected ? hub->connect_delay : 0; -- -- schedule_delayed_work(&hub->update_work, delay); --} -- --static int __maybe_unused ssam_hub_resume(struct device *dev) --{ -- struct ssam_hub *hub = dev_get_drvdata(dev); -- -- schedule_delayed_work(&hub->update_work, 0); -- return 0; --} --static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); -- --static int ssam_hub_setup(struct ssam_device *sdev, struct ssam_hub *hub) --{ -- int status; -- -- hub->sdev = sdev; -- hub->state = SSAM_HUB_UNINITIALIZED; -- -- INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); -- -- ssam_device_set_drvdata(sdev, hub); -- -- status = ssam_device_notifier_register(sdev, &hub->notif); -- if (status) -- return status; -- -- schedule_delayed_work(&hub->update_work, 0); -- return 0; --} -- --static void ssam_hub_remove(struct ssam_device *sdev) --{ -- struct ssam_hub *hub = ssam_device_get_drvdata(sdev); -- -- ssam_device_notifier_unregister(sdev, &hub->notif); -- cancel_delayed_work_sync(&hub->update_work); -- ssam_remove_clients(&sdev->dev); --} -- -- --/* -- SSAM base-hub driver. ------------------------------------------------- */ -- --/* -- * Some devices (especially battery) may need a bit of time to be fully usable -- * after being (re-)connected. This delay has been determined via -- * experimentation. -- */ --#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) -- --SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { -- .target_category = SSAM_SSH_TC_BAS, -- .target_id = 0x01, -- .command_id = 0x0d, -- .instance_id = 0x00, --}); -- --#define SSAM_BAS_OPMODE_TABLET 0x00 --#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c -- --static int ssam_base_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) --{ -- u8 opmode; -- int status; -- -- status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); -- if (status < 0) { -- dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); -- return status; -- } -- -- if (opmode != SSAM_BAS_OPMODE_TABLET) -- *state = SSAM_HUB_CONNECTED; -- else -- *state = SSAM_HUB_DISCONNECTED; -- -- return 0; --} -- --static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) --{ -- struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -- -- if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) -- return 0; -- -- if (event->length < 1) { -- dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -- return 0; -- } -- -- ssam_hub_update(hub, event->data[0]); -- -- /* -- * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -- * consumed by the detachment system driver. We're just a (more or less) -- * silent observer. -- */ -- return 0; --} -- --static int ssam_base_hub_probe(struct ssam_device *sdev) --{ -- struct ssam_hub *hub; -- -- hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -- if (!hub) -- return -ENOMEM; -- -- hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -- hub->notif.base.fn = ssam_base_hub_notif; -- hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -- hub->notif.event.id.target_category = SSAM_SSH_TC_BAS, -- hub->notif.event.id.instance = 0, -- hub->notif.event.mask = SSAM_EVENT_MASK_NONE; -- hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -- -- hub->connect_delay = SSAM_BASE_UPDATE_CONNECT_DELAY; -- hub->get_state = ssam_base_hub_query_state; -- -- return ssam_hub_setup(sdev, hub); --} -- --static const struct ssam_device_id ssam_base_hub_match[] = { -- { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) }, -- { }, --}; -- --static struct ssam_device_driver ssam_base_hub_driver = { -- .probe = ssam_base_hub_probe, -- .remove = ssam_hub_remove, -- .match_table = ssam_base_hub_match, -- .driver = { -- .name = "surface_aggregator_base_hub", -- .probe_type = PROBE_PREFER_ASYNCHRONOUS, -- .pm = &ssam_hub_pm_ops, -- }, --}; -- -- --/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ -- --/* -- * Some devices may need a bit of time to be fully usable after being -- * (re-)connected. This delay has been determined via experimentation. -- */ --#define SSAM_KIP_UPDATE_CONNECT_DELAY msecs_to_jiffies(250) -- --#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c -- --SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_connection_state, u8, { -- .target_category = SSAM_SSH_TC_KIP, -- .target_id = 0x01, -- .command_id = 0x2c, -- .instance_id = 0x00, --}); -- --static int ssam_kip_get_connection_state(struct ssam_hub *hub, enum ssam_hub_state *state) --{ -- int status; -- u8 connected; -- -- status = ssam_retry(__ssam_kip_get_connection_state, hub->sdev->ctrl, &connected); -- if (status < 0) { -- dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); -- return status; -- } -- -- *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; -- return 0; --} -- --static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) --{ -- struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -- -- if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) -- return 0; /* Return "unhandled". */ -- -- if (event->length < 1) { -- dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -- return 0; -- } -- -- ssam_hub_update(hub, event->data[0]); -- return SSAM_NOTIF_HANDLED; --} -- --static int ssam_kip_hub_probe(struct ssam_device *sdev) --{ -- struct ssam_hub *hub; -- -- hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -- if (!hub) -- return -ENOMEM; -- -- hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -- hub->notif.base.fn = ssam_kip_hub_notif; -- hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -- hub->notif.event.id.target_category = SSAM_SSH_TC_KIP, -- hub->notif.event.id.instance = 0, -- hub->notif.event.mask = SSAM_EVENT_MASK_TARGET; -- hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -- -- hub->connect_delay = SSAM_KIP_UPDATE_CONNECT_DELAY; -- hub->get_state = ssam_kip_get_connection_state; -- -- return ssam_hub_setup(sdev, hub); --} -- --static const struct ssam_device_id ssam_kip_hub_match[] = { -- { SSAM_SDEV(KIP, 0x01, 0x00, 0x00) }, -- { }, --}; -- --static struct ssam_device_driver ssam_kip_hub_driver = { -- .probe = ssam_kip_hub_probe, -- .remove = ssam_hub_remove, -- .match_table = ssam_kip_hub_match, -- .driver = { -- .name = "surface_kip_hub", -- .probe_type = PROBE_PREFER_ASYNCHRONOUS, -- .pm = &ssam_hub_pm_ops, -- }, --}; -- -- - /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ - - static const struct acpi_device_id ssam_platform_hub_match[] = { -@@ -720,44 +388,7 @@ static struct platform_driver ssam_platform_hub_driver = { - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - }, - }; -- -- --/* -- Module initialization. ------------------------------------------------ */ -- --static int __init ssam_device_hub_init(void) --{ -- int status; -- -- status = platform_driver_register(&ssam_platform_hub_driver); -- if (status) -- goto err_platform; -- -- status = ssam_device_driver_register(&ssam_base_hub_driver); -- if (status) -- goto err_base; -- -- status = ssam_device_driver_register(&ssam_kip_hub_driver); -- if (status) -- goto err_kip; -- -- return 0; -- --err_kip: -- ssam_device_driver_unregister(&ssam_base_hub_driver); --err_base: -- platform_driver_unregister(&ssam_platform_hub_driver); --err_platform: -- return status; --} --module_init(ssam_device_hub_init); -- --static void __exit ssam_device_hub_exit(void) --{ -- ssam_device_driver_unregister(&ssam_kip_hub_driver); -- ssam_device_driver_unregister(&ssam_base_hub_driver); -- platform_driver_unregister(&ssam_platform_hub_driver); --} --module_exit(ssam_device_hub_exit); -+module_platform_driver(ssam_platform_hub_driver); - - MODULE_AUTHOR("Maximilian Luz "); - MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); --- -2.36.1 - -From 7b3b163901724689e4c52468232552f38d503523 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 21 May 2022 00:51:05 +0200 -Subject: [PATCH] platform/surface: aggregator: Be consistent with hub device - IDs - -Currently, we use a virtual device ID for the base (BAS) hub but an -actual device ID for the KIP hub. Let's be consistent about the naming -format and make all hubs virtual, with their instance ID reflecting the -subsystem. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_hub.c | 4 ++-- - .../platform/surface/surface_aggregator_registry.c | 12 ++++++------ - 2 files changed, 8 insertions(+), 8 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c -index 20b1c38debfe..c473bdebf90c 100644 ---- a/drivers/platform/surface/surface_aggregator_hub.c -+++ b/drivers/platform/surface/surface_aggregator_hub.c -@@ -340,8 +340,8 @@ static const struct ssam_hub_info kip_hub = { - /* -- Driver registration. -------------------------------------------------- */ - - static const struct ssam_device_id ssam_hub_match[] = { -- { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00), (unsigned long)&base_hub }, -- { SSAM_SDEV(KIP, 0x01, 0x00, 0x00), (unsigned long)&kip_hub }, -+ { SSAM_VDEV(HUB, 0x01, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, -+ { SSAM_VDEV(HUB, 0x02, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, - { } - }; - MODULE_DEVICE_TABLE(ssam, ssam_hub_match); -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 11b51aa9ea73..cee7121e7fa6 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -38,15 +38,15 @@ static const struct software_node ssam_node_root = { - .name = "ssam_platform_hub", - }; - --/* Base device hub (devices attached to Surface Book 3 base). */ --static const struct software_node ssam_node_hub_base = { -- .name = "ssam:00:00:02:00:00", -+/* KIP device hub (connects keyboard cover devices on Surface Pro 8). */ -+static const struct software_node ssam_node_hub_kip = { -+ .name = "ssam:00:00:01:0e:00", - .parent = &ssam_node_root, - }; - --/* KIP device hub (connects keyboard cover devices on Surface Pro 8). */ --static const struct software_node ssam_node_hub_kip = { -- .name = "ssam:01:0e:01:00:00", -+/* Base device hub (devices attached to Surface Book 3 base). */ -+static const struct software_node ssam_node_hub_base = { -+ .name = "ssam:00:00:02:11:00", - .parent = &ssam_node_root, - }; - --- -2.36.1 - -From da96ae977277e2c11ebbaf7755bc08b6e611bac3 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 21 May 2022 00:57:40 +0200 -Subject: [PATCH] platform/surface: Update copyright year of various drivers - -Update the copyright of various Surface drivers to the current year. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/Kconfig | 2 +- - drivers/platform/surface/aggregator/Makefile | 2 +- - drivers/platform/surface/aggregator/bus.c | 2 +- - drivers/platform/surface/aggregator/bus.h | 2 +- - drivers/platform/surface/aggregator/controller.c | 2 +- - drivers/platform/surface/aggregator/controller.h | 2 +- - drivers/platform/surface/aggregator/core.c | 2 +- - drivers/platform/surface/aggregator/ssh_msgb.h | 2 +- - drivers/platform/surface/aggregator/ssh_packet_layer.c | 2 +- - drivers/platform/surface/aggregator/ssh_packet_layer.h | 2 +- - drivers/platform/surface/aggregator/ssh_parser.c | 2 +- - drivers/platform/surface/aggregator/ssh_parser.h | 2 +- - drivers/platform/surface/aggregator/ssh_request_layer.c | 2 +- - drivers/platform/surface/aggregator/ssh_request_layer.h | 2 +- - drivers/platform/surface/aggregator/trace.h | 2 +- - drivers/platform/surface/surface_acpi_notify.c | 2 +- - drivers/platform/surface/surface_aggregator_cdev.c | 2 +- - drivers/platform/surface/surface_aggregator_registry.c | 2 +- - drivers/platform/surface/surface_dtx.c | 2 +- - drivers/platform/surface/surface_gpe.c | 2 +- - drivers/platform/surface/surface_hotplug.c | 2 +- - drivers/platform/surface/surface_platform_profile.c | 2 +- - 22 files changed, 22 insertions(+), 22 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig -index cab020324256..c114f9dd5fe1 100644 ---- a/drivers/platform/surface/aggregator/Kconfig -+++ b/drivers/platform/surface/aggregator/Kconfig -@@ -1,5 +1,5 @@ - # SPDX-License-Identifier: GPL-2.0+ --# Copyright (C) 2019-2021 Maximilian Luz -+# Copyright (C) 2019-2022 Maximilian Luz - - menuconfig SURFACE_AGGREGATOR - tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" -diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile -index c0d550eda5cd..fdf664a217f9 100644 ---- a/drivers/platform/surface/aggregator/Makefile -+++ b/drivers/platform/surface/aggregator/Makefile -@@ -1,5 +1,5 @@ - # SPDX-License-Identifier: GPL-2.0+ --# Copyright (C) 2019-2021 Maximilian Luz -+# Copyright (C) 2019-2022 Maximilian Luz - - # For include/trace/define_trace.h to include trace.h - CFLAGS_core.o = -I$(src) -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -index 4bba60884bb5..96986042a257 100644 ---- a/drivers/platform/surface/aggregator/bus.c -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -2,7 +2,7 @@ - /* - * Surface System Aggregator Module bus and device integration. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h -index 6964ee84e79c..5b4dbf21906c 100644 ---- a/drivers/platform/surface/aggregator/bus.h -+++ b/drivers/platform/surface/aggregator/bus.h -@@ -2,7 +2,7 @@ - /* - * Surface System Aggregator Module bus and device integration. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_BUS_H -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index 6de834b52b63..43e765199137 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2,7 +2,7 @@ - /* - * Main SSAM/SSH controller structure and functionality. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h -index a0963c3562ff..f0d987abc51e 100644 ---- a/drivers/platform/surface/aggregator/controller.h -+++ b/drivers/platform/surface/aggregator/controller.h -@@ -2,7 +2,7 @@ - /* - * Main SSAM/SSH controller structure and functionality. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_CONTROLLER_H -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -index a62c5dfe42d6..1a6373dea109 100644 ---- a/drivers/platform/surface/aggregator/core.c -+++ b/drivers/platform/surface/aggregator/core.c -@@ -7,7 +7,7 @@ - * Handles communication via requests as well as enabling, disabling, and - * relaying of events. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h -index e562958ffdf0..f3ecad92eefd 100644 ---- a/drivers/platform/surface/aggregator/ssh_msgb.h -+++ b/drivers/platform/surface/aggregator/ssh_msgb.h -@@ -2,7 +2,7 @@ - /* - * SSH message builder functions. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c -index 8a4451c1ffe5..6748fe4ac5d5 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c -@@ -2,7 +2,7 @@ - /* - * SSH packet transport layer. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h -index 2eb329f0b91a..64633522f971 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.h -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h -@@ -2,7 +2,7 @@ - /* - * SSH packet transport layer. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H -diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c -index b77912f8f13b..a6f668694365 100644 ---- a/drivers/platform/surface/aggregator/ssh_parser.c -+++ b/drivers/platform/surface/aggregator/ssh_parser.c -@@ -2,7 +2,7 @@ - /* - * SSH message parser. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h -index 3bd6e180fd16..801d8fa69fb5 100644 ---- a/drivers/platform/surface/aggregator/ssh_parser.h -+++ b/drivers/platform/surface/aggregator/ssh_parser.h -@@ -2,7 +2,7 @@ - /* - * SSH message parser. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c -index 790f7f0eee98..f5565570f16c 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.c -@@ -2,7 +2,7 @@ - /* - * SSH request transport layer. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h -index 9c3cbae2d4bd..4e387a031351 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.h -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.h -@@ -2,7 +2,7 @@ - /* - * SSH request transport layer. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H -diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h -index de64cf169060..7be0bb097dea 100644 ---- a/drivers/platform/surface/aggregator/trace.h -+++ b/drivers/platform/surface/aggregator/trace.h -@@ -2,7 +2,7 @@ - /* - * Trace points for SSAM/SSH. - * -- * Copyright (C) 2020-2021 Maximilian Luz -+ * Copyright (C) 2020-2022 Maximilian Luz - */ - - #undef TRACE_SYSTEM -diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c -index 8339988d95c1..acc958b43b57 100644 ---- a/drivers/platform/surface/surface_acpi_notify.c -+++ b/drivers/platform/surface/surface_acpi_notify.c -@@ -8,7 +8,7 @@ - * notifications sent from ACPI via the SAN interface by providing them to any - * registered external driver. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c -index 30fb50fde450..492c82e69182 100644 ---- a/drivers/platform/surface/surface_aggregator_cdev.c -+++ b/drivers/platform/surface/surface_aggregator_cdev.c -@@ -3,7 +3,7 @@ - * Provides user-space access to the SSAM EC via the /dev/surface/aggregator - * misc device. Intended for debugging and development. - * -- * Copyright (C) 2020-2021 Maximilian Luz -+ * Copyright (C) 2020-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index cee7121e7fa6..3f81db28a702 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -6,7 +6,7 @@ - * cannot be auto-detected. Provides device-hubs and performs instantiation - * for these devices. - * -- * Copyright (C) 2020-2021 Maximilian Luz -+ * Copyright (C) 2020-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c -index 1203b9a82993..ed36944467f9 100644 ---- a/drivers/platform/surface/surface_dtx.c -+++ b/drivers/platform/surface/surface_dtx.c -@@ -8,7 +8,7 @@ - * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in - * use), or request detachment via user-space. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c -index ec66fde28e75..27365cbe1ee9 100644 ---- a/drivers/platform/surface/surface_gpe.c -+++ b/drivers/platform/surface/surface_gpe.c -@@ -4,7 +4,7 @@ - * properly configuring the respective GPEs. Required for wakeup via lid on - * newer Intel-based Microsoft Surface devices. - * -- * Copyright (C) 2020 Maximilian Luz -+ * Copyright (C) 2020-2022 Maximilian Luz - */ - - #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -diff --git a/drivers/platform/surface/surface_hotplug.c b/drivers/platform/surface/surface_hotplug.c -index cfcc15cfbacb..f004a2495201 100644 ---- a/drivers/platform/surface/surface_hotplug.c -+++ b/drivers/platform/surface/surface_hotplug.c -@@ -10,7 +10,7 @@ - * Event signaling is handled via ACPI, which will generate the appropriate - * device-check notifications to be picked up by the PCIe hot-plug driver. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c -index 6373d3b5eb7f..fbf2e11fd6ce 100644 ---- a/drivers/platform/surface/surface_platform_profile.c -+++ b/drivers/platform/surface/surface_platform_profile.c -@@ -3,7 +3,7 @@ - * Surface Platform Profile / Performance Mode driver for Surface System - * Aggregator Module (thermal subsystem). - * -- * Copyright (C) 2021 Maximilian Luz -+ * Copyright (C) 2021-2022 Maximilian Luz - */ - - #include --- -2.36.1 - -From 9a243e27c34f1cb8a482253473c6170fee0bb27e Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 14 Jun 2022 13:17:49 +0200 -Subject: [PATCH] platform/surface: aggregator: Reserve more event- and - target-categories - -With the introduction of the Surface Laptop Studio, more event- and -target categories have been added. Therefore, increase the number of -reserved events and extend the enum of know target categories. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/trace.h | 80 +++++++++++-------- - include/linux/surface_aggregator/serial_hub.h | 75 +++++++++-------- - 2 files changed, 85 insertions(+), 70 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h -index 7be0bb097dea..2a2c17771d01 100644 ---- a/drivers/platform/surface/aggregator/trace.h -+++ b/drivers/platform/surface/aggregator/trace.h -@@ -76,7 +76,7 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM); --TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC0); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN); -@@ -85,6 +85,11 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SPT); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SYS); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC1); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SHB); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_POS); - - #define SSAM_PTR_UID_LEN 9 - #define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1) -@@ -229,40 +234,45 @@ static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) - - #define ssam_show_ssh_tc(rqid) \ - __print_symbolic(rqid, \ -- { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ -- { SSAM_SSH_TC_SAM, "SAM" }, \ -- { SSAM_SSH_TC_BAT, "BAT" }, \ -- { SSAM_SSH_TC_TMP, "TMP" }, \ -- { SSAM_SSH_TC_PMC, "PMC" }, \ -- { SSAM_SSH_TC_FAN, "FAN" }, \ -- { SSAM_SSH_TC_PoM, "PoM" }, \ -- { SSAM_SSH_TC_DBG, "DBG" }, \ -- { SSAM_SSH_TC_KBD, "KBD" }, \ -- { SSAM_SSH_TC_FWU, "FWU" }, \ -- { SSAM_SSH_TC_UNI, "UNI" }, \ -- { SSAM_SSH_TC_LPC, "LPC" }, \ -- { SSAM_SSH_TC_TCL, "TCL" }, \ -- { SSAM_SSH_TC_SFL, "SFL" }, \ -- { SSAM_SSH_TC_KIP, "KIP" }, \ -- { SSAM_SSH_TC_EXT, "EXT" }, \ -- { SSAM_SSH_TC_BLD, "BLD" }, \ -- { SSAM_SSH_TC_BAS, "BAS" }, \ -- { SSAM_SSH_TC_SEN, "SEN" }, \ -- { SSAM_SSH_TC_SRQ, "SRQ" }, \ -- { SSAM_SSH_TC_MCU, "MCU" }, \ -- { SSAM_SSH_TC_HID, "HID" }, \ -- { SSAM_SSH_TC_TCH, "TCH" }, \ -- { SSAM_SSH_TC_BKL, "BKL" }, \ -- { SSAM_SSH_TC_TAM, "TAM" }, \ -- { SSAM_SSH_TC_ACC, "ACC" }, \ -- { SSAM_SSH_TC_UFI, "UFI" }, \ -- { SSAM_SSH_TC_USC, "USC" }, \ -- { SSAM_SSH_TC_PEN, "PEN" }, \ -- { SSAM_SSH_TC_VID, "VID" }, \ -- { SSAM_SSH_TC_AUD, "AUD" }, \ -- { SSAM_SSH_TC_SMC, "SMC" }, \ -- { SSAM_SSH_TC_KPD, "KPD" }, \ -- { SSAM_SSH_TC_REG, "REG" } \ -+ { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ -+ { SSAM_SSH_TC_SAM, "SAM" }, \ -+ { SSAM_SSH_TC_BAT, "BAT" }, \ -+ { SSAM_SSH_TC_TMP, "TMP" }, \ -+ { SSAM_SSH_TC_PMC, "PMC" }, \ -+ { SSAM_SSH_TC_FAN, "FAN" }, \ -+ { SSAM_SSH_TC_PoM, "PoM" }, \ -+ { SSAM_SSH_TC_DBG, "DBG" }, \ -+ { SSAM_SSH_TC_KBD, "KBD" }, \ -+ { SSAM_SSH_TC_FWU, "FWU" }, \ -+ { SSAM_SSH_TC_UNI, "UNI" }, \ -+ { SSAM_SSH_TC_LPC, "LPC" }, \ -+ { SSAM_SSH_TC_TCL, "TCL" }, \ -+ { SSAM_SSH_TC_SFL, "SFL" }, \ -+ { SSAM_SSH_TC_KIP, "KIP" }, \ -+ { SSAM_SSH_TC_EXT, "EXT" }, \ -+ { SSAM_SSH_TC_BLD, "BLD" }, \ -+ { SSAM_SSH_TC_BAS, "BAS" }, \ -+ { SSAM_SSH_TC_SEN, "SEN" }, \ -+ { SSAM_SSH_TC_SRQ, "SRQ" }, \ -+ { SSAM_SSH_TC_MCU, "MCU" }, \ -+ { SSAM_SSH_TC_HID, "HID" }, \ -+ { SSAM_SSH_TC_TCH, "TCH" }, \ -+ { SSAM_SSH_TC_BKL, "BKL" }, \ -+ { SSAM_SSH_TC_TAM, "TAM" }, \ -+ { SSAM_SSH_TC_ACC0, "ACC0" }, \ -+ { SSAM_SSH_TC_UFI, "UFI" }, \ -+ { SSAM_SSH_TC_USC, "USC" }, \ -+ { SSAM_SSH_TC_PEN, "PEN" }, \ -+ { SSAM_SSH_TC_VID, "VID" }, \ -+ { SSAM_SSH_TC_AUD, "AUD" }, \ -+ { SSAM_SSH_TC_SMC, "SMC" }, \ -+ { SSAM_SSH_TC_KPD, "KPD" }, \ -+ { SSAM_SSH_TC_REG, "REG" }, \ -+ { SSAM_SSH_TC_SPT, "SPT" }, \ -+ { SSAM_SSH_TC_SYS, "SYS" }, \ -+ { SSAM_SSH_TC_ACC1, "ACC1" }, \ -+ { SSAM_SSH_TC_SHB, "SMB" }, \ -+ { SSAM_SSH_TC_POS, "POS" } \ - ) - - DECLARE_EVENT_CLASS(ssam_frame_class, -diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h -index 26b95ec12733..45501b6e54e8 100644 ---- a/include/linux/surface_aggregator/serial_hub.h -+++ b/include/linux/surface_aggregator/serial_hub.h -@@ -201,7 +201,7 @@ static inline u16 ssh_crc(const u8 *buf, size_t len) - * exception of zero, which is not an event ID. Thus, this is also the - * absolute maximum number of event handlers that can be registered. - */ --#define SSH_NUM_EVENTS 34 -+#define SSH_NUM_EVENTS 38 - - /* - * SSH_NUM_TARGETS - The number of communication targets used in the protocol. -@@ -292,40 +292,45 @@ struct ssam_span { - * Windows driver. - */ - enum ssam_ssh_tc { -- /* Category 0x00 is invalid for EC use. */ -- SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */ -- SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */ -- SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */ -- SSAM_SSH_TC_PMC = 0x04, -- SSAM_SSH_TC_FAN = 0x05, -- SSAM_SSH_TC_PoM = 0x06, -- SSAM_SSH_TC_DBG = 0x07, -- SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */ -- SSAM_SSH_TC_FWU = 0x09, -- SSAM_SSH_TC_UNI = 0x0a, -- SSAM_SSH_TC_LPC = 0x0b, -- SSAM_SSH_TC_TCL = 0x0c, -- SSAM_SSH_TC_SFL = 0x0d, -- SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ -- SSAM_SSH_TC_EXT = 0x0f, -- SSAM_SSH_TC_BLD = 0x10, -- SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ -- SSAM_SSH_TC_SEN = 0x12, -- SSAM_SSH_TC_SRQ = 0x13, -- SSAM_SSH_TC_MCU = 0x14, -- SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */ -- SSAM_SSH_TC_TCH = 0x16, -- SSAM_SSH_TC_BKL = 0x17, -- SSAM_SSH_TC_TAM = 0x18, -- SSAM_SSH_TC_ACC = 0x19, -- SSAM_SSH_TC_UFI = 0x1a, -- SSAM_SSH_TC_USC = 0x1b, -- SSAM_SSH_TC_PEN = 0x1c, -- SSAM_SSH_TC_VID = 0x1d, -- SSAM_SSH_TC_AUD = 0x1e, -- SSAM_SSH_TC_SMC = 0x1f, -- SSAM_SSH_TC_KPD = 0x20, -- SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */ -+ /* Category 0x00 is invalid for EC use. */ -+ SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */ -+ SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */ -+ SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */ -+ SSAM_SSH_TC_PMC = 0x04, -+ SSAM_SSH_TC_FAN = 0x05, -+ SSAM_SSH_TC_PoM = 0x06, -+ SSAM_SSH_TC_DBG = 0x07, -+ SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */ -+ SSAM_SSH_TC_FWU = 0x09, -+ SSAM_SSH_TC_UNI = 0x0a, -+ SSAM_SSH_TC_LPC = 0x0b, -+ SSAM_SSH_TC_TCL = 0x0c, -+ SSAM_SSH_TC_SFL = 0x0d, -+ SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ -+ SSAM_SSH_TC_EXT = 0x0f, -+ SSAM_SSH_TC_BLD = 0x10, -+ SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ -+ SSAM_SSH_TC_SEN = 0x12, -+ SSAM_SSH_TC_SRQ = 0x13, -+ SSAM_SSH_TC_MCU = 0x14, -+ SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */ -+ SSAM_SSH_TC_TCH = 0x16, -+ SSAM_SSH_TC_BKL = 0x17, -+ SSAM_SSH_TC_TAM = 0x18, -+ SSAM_SSH_TC_ACC0 = 0x19, -+ SSAM_SSH_TC_UFI = 0x1a, -+ SSAM_SSH_TC_USC = 0x1b, -+ SSAM_SSH_TC_PEN = 0x1c, -+ SSAM_SSH_TC_VID = 0x1d, -+ SSAM_SSH_TC_AUD = 0x1e, -+ SSAM_SSH_TC_SMC = 0x1f, -+ SSAM_SSH_TC_KPD = 0x20, -+ SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */ -+ SSAM_SSH_TC_SPT = 0x22, -+ SSAM_SSH_TC_SYS = 0x23, -+ SSAM_SSH_TC_ACC1 = 0x24, -+ SSAM_SSH_TC_SHB = 0x25, -+ SSAM_SSH_TC_POS = 0x26, /* For obtaining Laptop Studio screen position. */ - }; - - --- -2.36.1 - diff --git a/patches/5.17/0006-surface-sam-over-hid.patch b/patches/5.17/0006-surface-sam-over-hid.patch deleted file mode 100644 index 90c28701a..000000000 --- a/patches/5.17/0006-surface-sam-over-hid.patch +++ /dev/null @@ -1,335 +0,0 @@ -From ae12ad46087b4206ccc2869efefab2eb2a340230 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jul 2020 17:19:53 +0200 -Subject: [PATCH] i2c: acpi: Implement RawBytes read access - -Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C -device via a generic serial bus operation region and RawBytes read -access. On the Surface Book 1, this access is required to turn on (and -off) the discrete GPU. - -Multiple things are to note here: - -a) The RawBytes access is device/driver dependent. The ACPI - specification states: - - > Raw accesses assume that the writer has knowledge of the bus that - > the access is made over and the device that is being accessed. The - > protocol may only ensure that the buffer is transmitted to the - > appropriate driver, but the driver must be able to interpret the - > buffer to communicate to a register. - - Thus this implementation may likely not work on other devices - accessing I2C via the RawBytes accessor type. - -b) The MSHW0030 I2C device is an HID-over-I2C device which seems to - serve multiple functions: - - 1. It is the main access point for the legacy-type Surface Aggregator - Module (also referred to as SAM-over-HID, as opposed to the newer - SAM-over-SSH/UART). It has currently not been determined on how - support for the legacy SAM should be implemented. Likely via a - custom HID driver. - - 2. It seems to serve as the HID device for the Integrated Sensor Hub. - This might complicate matters with regards to implementing a - SAM-over-HID driver required by legacy SAM. - -In light of this, the simplest approach has been chosen for now. -However, it may make more sense regarding breakage and compatibility to -either provide functionality for replacing or enhancing the default -operation region handler via some additional API functions, or even to -completely blacklist MSHW0030 from the I2C core and provide a custom -driver for it. - -Replacing/enhancing the default operation region handler would, however, -either require some sort of secondary driver and access point for it, -from which the new API functions would be called and the new handler -(part) would be installed, or hard-coding them via some sort of -quirk-like interface into the I2C core. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam-over-hid ---- - drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ - 1 file changed, 35 insertions(+) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index 85ed4c1d4924..942c1c9a4ea5 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -624,6 +624,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, - return (ret == 1) ? 0 : -EIO; - } - -+static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, -+ u8 *data, u8 data_len) -+{ -+ struct i2c_msg msgs[1]; -+ int ret = AE_OK; -+ -+ msgs[0].addr = client->addr; -+ msgs[0].flags = client->flags; -+ msgs[0].len = data_len + 1; -+ msgs[0].buf = data; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ -+ if (ret < 0) { -+ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); -+ return ret; -+ } -+ -+ /* 1 transfer must have completed successfully */ -+ return (ret == 1) ? 0 : -EIO; -+} -+ - static acpi_status - i2c_acpi_space_handler(u32 function, acpi_physical_address command, - u32 bits, u64 *value64, -@@ -725,6 +747,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, - } - break; - -+ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: -+ if (action == ACPI_READ) { -+ dev_warn(&adapter->dev, -+ "protocol 0x%02x not supported for client 0x%02x\n", -+ accessor_type, client->addr); -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } else { -+ status = acpi_gsb_i2c_write_raw_bytes(client, -+ gsb->data, info->access_length); -+ } -+ break; -+ - default: - dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", - accessor_type, client->addr); --- -2.36.1 - -From d0ce33c5890a6301827426a0c8df1a882e22182a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 13 Feb 2021 16:41:18 +0100 -Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch - -Add driver exposing the discrete GPU power-switch of the Microsoft -Surface Book 1 to user-space. - -On the Surface Book 1, the dGPU power is controlled via the Surface -System Aggregator Module (SAM). The specific SAM-over-HID command for -this is exposed via ACPI. This module provides a simple driver exposing -the ACPI call via a sysfs parameter to user-space, so that users can -easily power-on/-off the dGPU. - -Patchset: surface-sam-over-hid ---- - drivers/platform/surface/Kconfig | 7 + - drivers/platform/surface/Makefile | 1 + - .../surface/surfacebook1_dgpu_switch.c | 162 ++++++++++++++++++ - 3 files changed, 170 insertions(+) - create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index c685ec440535..3d3659b87ba4 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -133,6 +133,13 @@ config SURFACE_AGGREGATOR_REGISTRY - the respective client devices. Drivers for these devices still need to - be selected via the other options. - -+config SURFACE_BOOK1_DGPU_SWITCH -+ tristate "Surface Book 1 dGPU Switch Driver" -+ depends on SYSFS -+ help -+ This driver provides a sysfs switch to set the power-state of the -+ discrete GPU found on the Microsoft Surface Book 1. -+ - config SURFACE_DTX - tristate "Surface DTX (Detachment System) Driver" - depends on SURFACE_AGGREGATOR -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index fccd33e6780d..20408730f425 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o - obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o -+obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c -new file mode 100644 -index 000000000000..8b816ed8f35c ---- /dev/null -+++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c -@@ -0,0 +1,162 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+ -+#include -+#include -+#include -+#include -+ -+ -+#ifdef pr_fmt -+#undef pr_fmt -+#endif -+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ -+ -+ -+static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, -+ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); -+ -+#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" -+#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" -+#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" -+ -+ -+static int sb1_dgpu_sw_dsmcall(void) -+{ -+ union acpi_object *ret; -+ acpi_handle handle; -+ acpi_status status; -+ -+ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); -+ if (status) -+ return -EINVAL; -+ -+ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); -+ if (!ret) -+ return -EINVAL; -+ -+ ACPI_FREE(ret); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgon(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGON: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-on dGPU via HGON\n"); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgof(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGOF: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-off dGPU via HGOF\n"); -+ return 0; -+} -+ -+ -+static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ int status, value; -+ -+ status = kstrtoint(buf, 0, &value); -+ if (status < 0) -+ return status; -+ -+ if (value != 1) -+ return -EINVAL; -+ -+ status = sb1_dgpu_sw_dsmcall(); -+ -+ return status < 0 ? status : len; -+} -+ -+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ bool power; -+ int status; -+ -+ status = kstrtobool(buf, &power); -+ if (status < 0) -+ return status; -+ -+ if (power) -+ status = sb1_dgpu_sw_hgon(); -+ else -+ status = sb1_dgpu_sw_hgof(); -+ -+ return status < 0 ? status : len; -+} -+ -+static DEVICE_ATTR_WO(dgpu_dsmcall); -+static DEVICE_ATTR_WO(dgpu_power); -+ -+static struct attribute *sb1_dgpu_sw_attrs[] = { -+ &dev_attr_dgpu_dsmcall.attr, -+ &dev_attr_dgpu_power.attr, -+ NULL, -+}; -+ -+static const struct attribute_group sb1_dgpu_sw_attr_group = { -+ .attrs = sb1_dgpu_sw_attrs, -+}; -+ -+ -+static int sb1_dgpu_sw_probe(struct platform_device *pdev) -+{ -+ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+} -+ -+static int sb1_dgpu_sw_remove(struct platform_device *pdev) -+{ -+ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+ return 0; -+} -+ -+/* -+ * The dGPU power seems to be actually handled by MSHW0040. However, that is -+ * also the power-/volume-button device with a mainline driver. So let's use -+ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. -+ */ -+static const struct acpi_device_id sb1_dgpu_sw_match[] = { -+ { "MSHW0041", }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); -+ -+static struct platform_driver sb1_dgpu_sw = { -+ .probe = sb1_dgpu_sw_probe, -+ .remove = sb1_dgpu_sw_remove, -+ .driver = { -+ .name = "surfacebook1_dgpu_switch", -+ .acpi_match_table = sb1_dgpu_sw_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(sb1_dgpu_sw); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); -+MODULE_LICENSE("GPL"); --- -2.36.1 - diff --git a/patches/5.17/0007-surface-button.patch b/patches/5.17/0007-surface-button.patch deleted file mode 100644 index ddd16a0ee..000000000 --- a/patches/5.17/0007-surface-button.patch +++ /dev/null @@ -1,596 +0,0 @@ -From 606fdddeaa0de1b8b76fddb9f6a6165978d3d688 Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Tue, 5 Oct 2021 00:05:09 +1100 -Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices - -The power button on the AMD variant of the Surface Laptop uses the -same MSHW0040 device ID as the 5th and later generation of Surface -devices, however they report 0 for their OEM platform revision. As the -_DSM does not exist on the devices requiring special casing, check for -the existance of the _DSM to determine if soc_button_array should be -loaded. - -Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") -Co-developed-by: Maximilian Luz - -Signed-off-by: Sachi King -Patchset: surface-button ---- - drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- - 1 file changed, 8 insertions(+), 25 deletions(-) - -diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c -index cb6ec59a045d..4e8944f59def 100644 ---- a/drivers/input/misc/soc_button_array.c -+++ b/drivers/input/misc/soc_button_array.c -@@ -474,8 +474,8 @@ static const struct soc_device_data soc_device_INT33D3 = { - * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned - * devices use MSHW0040 for power and volume buttons, however the way they - * have to be addressed differs. Make sure that we only load this drivers -- * for the correct devices by checking the OEM Platform Revision provided by -- * the _DSM method. -+ * for the correct devices by checking if the OEM Platform Revision DSM call -+ * exists. - */ - #define MSHW0040_DSM_REVISION 0x01 - #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision -@@ -486,31 +486,14 @@ static const guid_t MSHW0040_DSM_UUID = - static int soc_device_check_MSHW0040(struct device *dev) - { - acpi_handle handle = ACPI_HANDLE(dev); -- union acpi_object *result; -- u64 oem_platform_rev = 0; // valid revisions are nonzero -- -- // get OEM platform revision -- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -- MSHW0040_DSM_REVISION, -- MSHW0040_DSM_GET_OMPR, NULL, -- ACPI_TYPE_INTEGER); -- -- if (result) { -- oem_platform_rev = result->integer.value; -- ACPI_FREE(result); -- } -- -- /* -- * If the revision is zero here, the _DSM evaluation has failed. This -- * indicates that we have a Pro 4 or Book 1 and this driver should not -- * be used. -- */ -- if (oem_platform_rev == 0) -- return -ENODEV; -+ bool exists; - -- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); -+ // check if OEM platform revision DSM call exists -+ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ BIT(MSHW0040_DSM_GET_OMPR)); - -- return 0; -+ return exists ? 0 : -ENODEV; - } - - /* --- -2.36.1 - -From b2628b30b4d4b8b185276dfe96cddbe0f4c83ba7 Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Tue, 5 Oct 2021 00:22:57 +1100 -Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd - variant - -The AMD variant of the Surface Laptop report 0 for their OEM platform -revision. The Surface devices that require the surfacepro3_button -driver do not have the _DSM that gets the OEM platform revision. If the -method does not exist, load surfacepro3_button. - -Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") -Co-developed-by: Maximilian Luz - -Signed-off-by: Sachi King -Patchset: surface-button ---- - drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- - 1 file changed, 6 insertions(+), 24 deletions(-) - -diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c -index 242fb690dcaf..30eea54dbb47 100644 ---- a/drivers/platform/surface/surfacepro3_button.c -+++ b/drivers/platform/surface/surfacepro3_button.c -@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) - /* - * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device - * ID (MSHW0040) for the power/volume buttons. Make sure this is the right -- * device by checking for the _DSM method and OEM Platform Revision. -+ * device by checking for the _DSM method and OEM Platform Revision DSM -+ * function. - * - * Returns true if the driver should bind to this device, i.e. the device is - * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. -@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) - static bool surface_button_check_MSHW0040(struct acpi_device *dev) - { - acpi_handle handle = dev->handle; -- union acpi_object *result; -- u64 oem_platform_rev = 0; // valid revisions are nonzero -- -- // get OEM platform revision -- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -- MSHW0040_DSM_REVISION, -- MSHW0040_DSM_GET_OMPR, -- NULL, ACPI_TYPE_INTEGER); -- -- /* -- * If evaluating the _DSM fails, the method is not present. This means -- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we -- * should use this driver. We use revision 0 indicating it is -- * unavailable. -- */ -- -- if (result) { -- oem_platform_rev = result->integer.value; -- ACPI_FREE(result); -- } -- -- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); - -- return oem_platform_rev == 0; -+ // make sure that OEM platform revision DSM call does not exist -+ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ BIT(MSHW0040_DSM_GET_OMPR)); - } - - --- -2.36.1 - -From 63fdac97668fe9b579d3387c1b2047a36ec8c13e Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Thu, 24 Feb 2022 12:02:40 +0100 -Subject: [PATCH] Input: soc_button_array - add support for Microsoft Surface 3 - (MSHW0028) buttons - -The drivers/platform/surface/surface3_button.c code is alsmost a 1:1 copy -of the soc_button_array code. - -The only big difference is that it binds to an i2c_client rather then to -a platform_device. The cause of this is the ACPI resources for the MSHW0028 -device containing a bogus I2cSerialBusV2 resource which causes the kernel -to instantiate an i2c_client for it instead of a platform_device. - -Add "MSHW0028" to the ignore_serial_bus_ids[] list in drivers/apci/scan.c, -so that a platform_device will be instantiated and add support for -the MSHW0028 HID to soc_button_array. - -This fully replaces surface3_button, which will be removed in a separate -commit (since it binds to the now no longer created i2c_client it no -longer does anyyhing after this commit). - -Note the MSHW0028 id is used by Microsoft to describe the tablet buttons on -both the Surface 3 and the Surface 3 Pro and the actual API/implementation -for the Surface 3 Pro is quite different. The changes in this commit should -not impact the separate surfacepro3_button driver: - -1. Because of the bogus I2cSerialBusV2 resource problem that driver binds - to the acpi_device itself, so instantiating a platform_device instead of - an i2c_client does not matter. - -2. The soc_button_array driver will not bind to the MSHW0028 device on - the Surface 3 Pro, because it has no GPIO resources. - -Signed-off-by: Hans de Goede -Reviewed-by: Maximilian Luz -Date: Thu, 24 Feb 2022 12:02:41 +0100 -Subject: [PATCH] platform/surface: Remove Surface 3 Button driver - -The Surface 3 buttons are now handled by the generic soc_button_array -driver. As part of adding support to soc_button_array the ACPI code -now instantiates a platform_device rather then an i2c_client so there -no longer is an i2c_client for this driver to bind to. - -Signed-off-by: Hans de Goede -Reviewed-by: Maximilian Luz --#include --#include --#include --#include --#include --#include --#include --#include --#include --#include -- -- --#define SURFACE_BUTTON_OBJ_NAME "TEV2" --#define MAX_NBUTTONS 4 -- --/* -- * Some of the buttons like volume up/down are auto repeat, while others -- * are not. To support both, we register two platform devices, and put -- * buttons into them based on whether the key should be auto repeat. -- */ --#define BUTTON_TYPES 2 -- --/* -- * Power button, Home button, Volume buttons support is supposed to -- * be covered by drivers/input/misc/soc_button_array.c, which is implemented -- * according to "Windows ACPI Design Guide for SoC Platforms". -- * However surface 3 seems not to obey the specs, instead it uses -- * device TEV2(MSHW0028) for declaring the GPIOs. The gpios are also slightly -- * different in which the Home button is active high. -- * Compared to surfacepro3_button.c which also handles MSHW0028, the Surface 3 -- * is a reduce platform and thus uses GPIOs, not ACPI events. -- * We choose an I2C driver here because we need to access the resources -- * declared under the device node, while surfacepro3_button.c only needs -- * the ACPI companion node. -- */ --static const struct acpi_device_id surface3_acpi_match[] = { -- { "MSHW0028", 0 }, -- { } --}; --MODULE_DEVICE_TABLE(acpi, surface3_acpi_match); -- --struct surface3_button_info { -- const char *name; -- int acpi_index; -- unsigned int event_type; -- unsigned int event_code; -- bool autorepeat; -- bool wakeup; -- bool active_low; --}; -- --struct surface3_button_data { -- struct platform_device *children[BUTTON_TYPES]; --}; -- --/* -- * Get the Nth GPIO number from the ACPI object. -- */ --static int surface3_button_lookup_gpio(struct device *dev, int acpi_index) --{ -- struct gpio_desc *desc; -- int gpio; -- -- desc = gpiod_get_index(dev, NULL, acpi_index, GPIOD_ASIS); -- if (IS_ERR(desc)) -- return PTR_ERR(desc); -- -- gpio = desc_to_gpio(desc); -- -- gpiod_put(desc); -- -- return gpio; --} -- --static struct platform_device * --surface3_button_device_create(struct i2c_client *client, -- const struct surface3_button_info *button_info, -- bool autorepeat) --{ -- const struct surface3_button_info *info; -- struct platform_device *pd; -- struct gpio_keys_button *gpio_keys; -- struct gpio_keys_platform_data *gpio_keys_pdata; -- int n_buttons = 0; -- int gpio; -- int error; -- -- gpio_keys_pdata = devm_kzalloc(&client->dev, -- sizeof(*gpio_keys_pdata) + -- sizeof(*gpio_keys) * MAX_NBUTTONS, -- GFP_KERNEL); -- if (!gpio_keys_pdata) -- return ERR_PTR(-ENOMEM); -- -- gpio_keys = (void *)(gpio_keys_pdata + 1); -- -- for (info = button_info; info->name; info++) { -- if (info->autorepeat != autorepeat) -- continue; -- -- gpio = surface3_button_lookup_gpio(&client->dev, -- info->acpi_index); -- if (!gpio_is_valid(gpio)) -- continue; -- -- gpio_keys[n_buttons].type = info->event_type; -- gpio_keys[n_buttons].code = info->event_code; -- gpio_keys[n_buttons].gpio = gpio; -- gpio_keys[n_buttons].active_low = info->active_low; -- gpio_keys[n_buttons].desc = info->name; -- gpio_keys[n_buttons].wakeup = info->wakeup; -- n_buttons++; -- } -- -- if (n_buttons == 0) { -- error = -ENODEV; -- goto err_free_mem; -- } -- -- gpio_keys_pdata->buttons = gpio_keys; -- gpio_keys_pdata->nbuttons = n_buttons; -- gpio_keys_pdata->rep = autorepeat; -- -- pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO); -- if (!pd) { -- error = -ENOMEM; -- goto err_free_mem; -- } -- -- error = platform_device_add_data(pd, gpio_keys_pdata, -- sizeof(*gpio_keys_pdata)); -- if (error) -- goto err_free_pdev; -- -- error = platform_device_add(pd); -- if (error) -- goto err_free_pdev; -- -- return pd; -- --err_free_pdev: -- platform_device_put(pd); --err_free_mem: -- devm_kfree(&client->dev, gpio_keys_pdata); -- return ERR_PTR(error); --} -- --static int surface3_button_remove(struct i2c_client *client) --{ -- struct surface3_button_data *priv = i2c_get_clientdata(client); -- -- int i; -- -- for (i = 0; i < BUTTON_TYPES; i++) -- if (priv->children[i]) -- platform_device_unregister(priv->children[i]); -- -- return 0; --} -- --static struct surface3_button_info surface3_button_surface3[] = { -- { "power", 0, EV_KEY, KEY_POWER, false, true, true }, -- { "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false }, -- { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true }, -- { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true }, -- { } --}; -- --static int surface3_button_probe(struct i2c_client *client, -- const struct i2c_device_id *id) --{ -- struct device *dev = &client->dev; -- struct surface3_button_data *priv; -- struct platform_device *pd; -- int i; -- int error; -- -- if (strncmp(acpi_device_bid(ACPI_COMPANION(&client->dev)), -- SURFACE_BUTTON_OBJ_NAME, -- strlen(SURFACE_BUTTON_OBJ_NAME))) -- return -ENODEV; -- -- error = gpiod_count(dev, NULL); -- if (error < 0) { -- dev_dbg(dev, "no GPIO attached, ignoring...\n"); -- return error; -- } -- -- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); -- if (!priv) -- return -ENOMEM; -- -- i2c_set_clientdata(client, priv); -- -- for (i = 0; i < BUTTON_TYPES; i++) { -- pd = surface3_button_device_create(client, -- surface3_button_surface3, -- i == 0); -- if (IS_ERR(pd)) { -- error = PTR_ERR(pd); -- if (error != -ENODEV) { -- surface3_button_remove(client); -- return error; -- } -- continue; -- } -- -- priv->children[i] = pd; -- } -- -- if (!priv->children[0] && !priv->children[1]) -- return -ENODEV; -- -- return 0; --} -- --static const struct i2c_device_id surface3_id[] = { -- { } --}; --MODULE_DEVICE_TABLE(i2c, surface3_id); -- --static struct i2c_driver surface3_driver = { -- .probe = surface3_button_probe, -- .remove = surface3_button_remove, -- .id_table = surface3_id, -- .driver = { -- .name = "surface3", -- .acpi_match_table = ACPI_PTR(surface3_acpi_match), -- }, --}; --module_i2c_driver(surface3_driver); -- --MODULE_AUTHOR("Benjamin Tissoires "); --MODULE_DESCRIPTION("surface3 button array driver"); --MODULE_LICENSE("GPL v2"); --- -2.36.1 - -From 9f7ee621ddf2ac9983f0d47579b20f9b5bae9010 Mon Sep 17 00:00:00 2001 -From: Duke Lee -Date: Fri, 10 Jun 2022 13:49:48 -0700 -Subject: [PATCH] platform/x86/intel: hid: Add Surface Go to VGBS allow list - -The Surface Go reports Chassis Type 9 (Laptop,) so the device needs to be -added to dmi_vgbs_allow_list to enable tablet mode when an attached Type -Cover is folded back. - -Relevant bug report and discussion: -https://github.com/linux-surface/linux-surface/issues/837 - -Signed-off-by: Duke Lee -Patchset: surface-button ---- - drivers/platform/x86/intel/hid.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/platform/x86/intel/hid.c b/drivers/platform/x86/intel/hid.c -index 5c39d40a701b..75f01f3d109c 100644 ---- a/drivers/platform/x86/intel/hid.c -+++ b/drivers/platform/x86/intel/hid.c -@@ -122,6 +122,12 @@ static const struct dmi_system_id dmi_vgbs_allow_list[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x360 Convertible 15-df0xxx"), - }, - }, -+ { -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Go"), -+ }, -+ }, - { } - }; - --- -2.36.1 - diff --git a/patches/5.17/0008-surface-typecover.patch b/patches/5.17/0008-surface-typecover.patch deleted file mode 100644 index c4b9bf5c0..000000000 --- a/patches/5.17/0008-surface-typecover.patch +++ /dev/null @@ -1,533 +0,0 @@ -From c6577b38c93ea96d46cc43f4e0aa0859c7681d82 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 5 Nov 2020 13:09:45 +0100 -Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when - suspending - -The Type Cover for Microsoft Surface devices supports a special usb -control request to disable or enable the built-in keyboard backlight. -On Windows, this request happens when putting the device into suspend or -resuming it, without it the backlight of the Type Cover will remain -enabled for some time even though the computer is suspended, which looks -weird to the user. - -So add support for this special usb control request to hid-multitouch, -which is the driver that's handling the Type Cover. - -The reason we have to use a pm_notifier for this instead of the usual -suspend/resume methods is that those won't get called in case the usb -device is already autosuspended. - -Also, if the device is autosuspended, we have to briefly autoresume it -in order to send the request. Doing that should be fine, the usb-core -driver does something similar during suspend inside choose_wakeup(). - -To make sure we don't send that request to every device but only to -devices which support it, add a new quirk -MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk -is only enabled for the usb id of the Surface Pro 2017 Type Cover, which -is where I confirmed that it's working. - -Patchset: surface-typecover ---- - drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- - 1 file changed, 98 insertions(+), 2 deletions(-) - -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index 6bb3890b0f2c..c28349e90156 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -34,7 +34,10 @@ - #include - #include - #include -+#include - #include -+#include -+#include - #include - #include - #include -@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); - MODULE_LICENSE("GPL"); - - #include "hid-ids.h" -+#include "usbhid/usbhid.h" - - /* quirks to control the device */ - #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) -@@ -71,12 +75,15 @@ MODULE_LICENSE("GPL"); - #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) - #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) - #define MT_QUIRK_DISABLE_WAKEUP BIT(21) -+#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 - - #define MT_BUTTONTYPE_CLICKPAD 0 - -+#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+ - enum latency_mode { - HID_LATENCY_NORMAL = 0, - HID_LATENCY_HIGH = 1, -@@ -168,6 +175,8 @@ struct mt_device { - - struct list_head applications; - struct list_head reports; -+ -+ struct notifier_block pm_notifier; - }; - - static void mt_post_parse_default_settings(struct mt_device *td, -@@ -211,6 +220,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); - #define MT_CLS_GOOGLE 0x0111 - #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 - #define MT_CLS_SMART_TECH 0x0113 -+#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 - - #define MT_DEFAULT_MAXCONTACT 10 - #define MT_MAX_MAXCONTACT 250 -@@ -386,6 +396,16 @@ static const struct mt_class mt_classes[] = { - MT_QUIRK_CONTACT_CNT_ACCURATE | - MT_QUIRK_SEPARATE_APP_REPORT, - }, -+ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_ALWAYS_VALID | -+ MT_QUIRK_IGNORE_DUPLICATES | -+ MT_QUIRK_HOVERING | -+ MT_QUIRK_CONTACT_CNT_ACCURATE | -+ MT_QUIRK_STICKY_FINGERS | -+ MT_QUIRK_WIN8_PTP_BUTTONS, -+ .export_all_inputs = true -+ }, - { } - }; - -@@ -1695,6 +1715,69 @@ static void mt_expired_timeout(struct timer_list *t) - clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - -+static void get_type_cover_backlight_field(struct hid_device *hdev, -+ struct hid_field **field) -+{ -+ struct hid_report_enum *rep_enum; -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid -+ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { -+ *field = cur_field; -+ return; -+ } -+ } -+ } -+ } -+} -+ -+static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) -+{ -+ struct usb_device *udev = hid_to_usb_dev(hdev); -+ struct hid_field *field = NULL; -+ -+ /* Wake up the device in case it's already suspended */ -+ pm_runtime_get_sync(&udev->dev); -+ -+ get_type_cover_backlight_field(hdev, &field); -+ if (!field) { -+ hid_err(hdev, "couldn't find backlight field\n"); -+ goto out; -+ } -+ -+ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; -+ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); -+ -+out: -+ pm_runtime_put_sync(&udev->dev); -+} -+ -+static int mt_pm_notifier(struct notifier_block *notifier, -+ unsigned long pm_event, -+ void *unused) -+{ -+ struct mt_device *td = -+ container_of(notifier, struct mt_device, pm_notifier); -+ struct hid_device *hdev = td->hdev; -+ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { -+ if (pm_event == PM_SUSPEND_PREPARE) -+ update_keyboard_backlight(hdev, 0); -+ else if (pm_event == PM_POST_SUSPEND) -+ update_keyboard_backlight(hdev, 1); -+ } -+ -+ return NOTIFY_DONE; -+} -+ - static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - { - int ret, i; -@@ -1718,6 +1801,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; - hid_set_drvdata(hdev, td); - -+ td->pm_notifier.notifier_call = mt_pm_notifier; -+ register_pm_notifier(&td->pm_notifier); -+ - INIT_LIST_HEAD(&td->applications); - INIT_LIST_HEAD(&td->reports); - -@@ -1747,15 +1833,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - timer_setup(&td->release_timer, mt_expired_timeout, 0); - - ret = hid_parse(hdev); -- if (ret != 0) -+ if (ret != 0) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) - mt_fix_const_fields(hdev, HID_DG_CONTACTID); - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); -- if (ret) -+ if (ret) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); - if (ret) -@@ -1807,6 +1897,7 @@ static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); - -+ unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); - - sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); -@@ -2180,6 +2271,11 @@ static const struct hid_device_id mt_devices[] = { - MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, - USB_DEVICE_ID_XIROKU_CSR2) }, - -+ /* Microsoft Surface type cover */ -+ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, -+ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, -+ - /* Google MT devices */ - { .driver_data = MT_CLS_GOOGLE, - HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, --- -2.36.1 - -From 454f7c6a308d0c49c90bafa103ffb2b1e3c187b4 Mon Sep 17 00:00:00 2001 -From: PJungkamp -Date: Fri, 25 Feb 2022 12:04:25 +0100 -Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet - switch - -The Surface Pro Type Cover has several non standard HID usages in it's -hid report descriptor. -I noticed that, upon folding the typecover back, a vendor specific range -of 4 32 bit integer hid usages is transmitted. -Only the first byte of the message seems to convey reliable information -about the keyboard state. - -0x22 => Normal (keys enabled) -0x33 => Folded back (keys disabled) -0x53 => Rotated left/right side up (keys disabled) -0x13 => Cover closed (keys disabled) -0x43 => Folded back and Tablet upside down (keys disabled) -This list may not be exhaustive. - -The tablet mode switch will be disabled for a value of 0x22 and enabled -on any other value. - -Patchset: surface-typecover ---- - drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ - 1 file changed, 122 insertions(+), 26 deletions(-) - -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index c28349e90156..61142639be26 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -76,6 +76,7 @@ MODULE_LICENSE("GPL"); - #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) - #define MT_QUIRK_DISABLE_WAKEUP BIT(21) - #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) -+#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(23) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 -@@ -83,6 +84,8 @@ MODULE_LICENSE("GPL"); - #define MT_BUTTONTYPE_CLICKPAD 0 - - #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 -+#define MS_TYPE_COVER_APPLICATION 0xff050050 - - enum latency_mode { - HID_LATENCY_NORMAL = 0, -@@ -398,6 +401,7 @@ static const struct mt_class mt_classes[] = { - }, - { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, - .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | - MT_QUIRK_ALWAYS_VALID | - MT_QUIRK_IGNORE_DUPLICATES | - MT_QUIRK_HOVERING | -@@ -1357,6 +1361,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, - field->application != HID_CP_CONSUMER_CONTROL && - field->application != HID_GD_WIRELESS_RADIO_CTLS && - field->application != HID_GD_SYSTEM_MULTIAXIS && -+ !(field->application == MS_TYPE_COVER_APPLICATION && -+ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && - !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && - application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) - return -1; -@@ -1384,6 +1391,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, - return 1; - } - -+ /* -+ * The Microsoft Surface Pro Typecover has a non-standard HID -+ * tablet mode switch on a vendor specific usage page with vendor -+ * specific usage. -+ */ -+ if (field->application == MS_TYPE_COVER_APPLICATION && -+ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { -+ usage->type = EV_SW; -+ usage->code = SW_TABLET_MODE; -+ *max = SW_MAX; -+ *bit = hi->input->swbit; -+ return 1; -+ } -+ - if (rdata->is_mt_collection) - return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, - application); -@@ -1405,6 +1427,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, - { - struct mt_device *td = hid_get_drvdata(hdev); - struct mt_report_data *rdata; -+ struct input_dev *input; - - rdata = mt_find_report_data(td, field->report); - if (rdata && rdata->is_mt_collection) { -@@ -1412,6 +1435,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, - return -1; - } - -+ /* -+ * We own an input device which acts as a tablet mode switch for -+ * the Surface Pro Typecover. -+ */ -+ if (field->application == MS_TYPE_COVER_APPLICATION && -+ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { -+ input = hi->input; -+ input_set_capability(input, EV_SW, SW_TABLET_MODE); -+ input_report_switch(input, SW_TABLET_MODE, 0); -+ return -1; -+ } -+ - /* let hid-core decide for the others */ - return 0; - } -@@ -1421,11 +1457,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, - { - struct mt_device *td = hid_get_drvdata(hid); - struct mt_report_data *rdata; -+ struct input_dev *input; - - rdata = mt_find_report_data(td, field->report); - if (rdata && rdata->is_mt_collection) - return mt_touch_event(hid, field, usage, value); - -+ if (field->application == MS_TYPE_COVER_APPLICATION && -+ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { -+ input = field->hidinput->input; -+ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); -+ input_sync(input); -+ return 1; -+ } -+ - return 0; - } - -@@ -1578,6 +1624,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) - app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; - } - -+static int get_type_cover_field(struct hid_report_enum *rep_enum, -+ struct hid_field **field, int usage) -+{ -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ if (cur_field->application != MS_TYPE_COVER_APPLICATION) -+ continue; -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid == usage) { -+ *field = cur_field; -+ return true; -+ } -+ } -+ } -+ } -+ return false; -+} -+ -+static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) -+{ -+ struct hid_field *field; -+ -+ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], -+ &field, -+ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { -+ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); -+ } else { -+ hid_err(hdev, "couldn't find tablet mode field\n"); -+ } -+} -+ - static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) - { - struct mt_device *td = hid_get_drvdata(hdev); -@@ -1627,6 +1709,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) - /* force BTN_STYLUS to allow tablet matching in udev */ - __set_bit(BTN_STYLUS, hi->input->keybit); - break; -+ case MS_TYPE_COVER_APPLICATION: -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { -+ suffix = "Tablet Mode Switch"; -+ request_type_cover_tablet_mode_switch(hdev); -+ break; -+ } -+ fallthrough; - default: - suffix = "UNKNOWN"; - break; -@@ -1715,30 +1804,6 @@ static void mt_expired_timeout(struct timer_list *t) - clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - --static void get_type_cover_backlight_field(struct hid_device *hdev, -- struct hid_field **field) --{ -- struct hid_report_enum *rep_enum; -- struct hid_report *rep; -- struct hid_field *cur_field; -- int i, j; -- -- rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; -- list_for_each_entry(rep, &rep_enum->report_list, list) { -- for (i = 0; i < rep->maxfield; i++) { -- cur_field = rep->field[i]; -- -- for (j = 0; j < cur_field->maxusage; j++) { -- if (cur_field->usage[j].hid -- == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { -- *field = cur_field; -- return; -- } -- } -- } -- } --} -- - static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) - { - struct usb_device *udev = hid_to_usb_dev(hdev); -@@ -1747,8 +1812,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) - /* Wake up the device in case it's already suspended */ - pm_runtime_get_sync(&udev->dev); - -- get_type_cover_backlight_field(hdev, &field); -- if (!field) { -+ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], -+ &field, -+ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { - hid_err(hdev, "couldn't find backlight field\n"); - goto out; - } -@@ -1874,13 +1940,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) - - static int mt_reset_resume(struct hid_device *hdev) - { -+ struct mt_device *td = hid_get_drvdata(hdev); -+ - mt_release_contacts(hdev); - mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); -+ -+ /* Request an update on the typecover folding state on resume -+ * after reset. -+ */ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) -+ request_type_cover_tablet_mode_switch(hdev); -+ - return 0; - } - - static int mt_resume(struct hid_device *hdev) - { -+ struct mt_device *td = hid_get_drvdata(hdev); -+ - /* Some Elan legacy devices require SET_IDLE to be set on resume. - * It should be safe to send it to other devices too. - * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ -@@ -1889,6 +1966,10 @@ static int mt_resume(struct hid_device *hdev) - - mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); - -+ /* Request an update on the typecover folding state on resume. */ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) -+ request_type_cover_tablet_mode_switch(hdev); -+ - return 0; - } - #endif -@@ -1896,6 +1977,21 @@ static int mt_resume(struct hid_device *hdev) - static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); -+ struct hid_field *field; -+ struct input_dev *input; -+ -+ /* Reset tablet mode switch on disconnect. */ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { -+ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], -+ &field, -+ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { -+ input = field->hidinput->input; -+ input_report_switch(input, SW_TABLET_MODE, 0); -+ input_sync(input); -+ } else { -+ hid_err(hdev, "couldn't find tablet mode field\n"); -+ } -+ } - - unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); --- -2.36.1 - diff --git a/patches/5.17/0009-surface-battery.patch b/patches/5.17/0009-surface-battery.patch deleted file mode 100644 index 6924f9039..000000000 --- a/patches/5.17/0009-surface-battery.patch +++ /dev/null @@ -1,131 +0,0 @@ -From 0f0c7c99159906bcdfdee9d318a40eb639a3fc15 Mon Sep 17 00:00:00 2001 -From: Werner Sembach -Date: Wed, 27 Apr 2022 17:40:53 +0200 -Subject: [PATCH] ACPI: battery: Make "not-charging" the default on no charging - or full info - -When the battery is neither charging or discharging and is not full, -"not-charging" is a useful status description for the case in general. -Currently this state is set as "unknown" by default, expect when this is -explicitly replaced with "not-charging" on a per device or per vendor -basis. - -A lot of devices have this state without a BIOS specification available -explicitly describing it. e.g. some current Clevo barebones have a BIOS -setting to stop charging at a user defined battery level. - -Signed-off-by: Werner Sembach -Signed-off-by: Rafael J. Wysocki -Patchset: surface-battery ---- - drivers/acpi/battery.c | 24 +----------------------- - 1 file changed, 1 insertion(+), 23 deletions(-) - -diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c -index dc208f5f5a1f..306513fec1e1 100644 ---- a/drivers/acpi/battery.c -+++ b/drivers/acpi/battery.c -@@ -52,7 +52,6 @@ static bool battery_driver_registered; - static int battery_bix_broken_package; - static int battery_notification_delay_ms; - static int battery_ac_is_broken; --static int battery_quirk_notcharging; - static unsigned int cache_time = 1000; - module_param(cache_time, uint, 0644); - MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); -@@ -216,10 +215,8 @@ static int acpi_battery_get_property(struct power_supply *psy, - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (acpi_battery_is_charged(battery)) - val->intval = POWER_SUPPLY_STATUS_FULL; -- else if (battery_quirk_notcharging) -- val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - else -- val->intval = POWER_SUPPLY_STATUS_UNKNOWN; -+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = acpi_battery_present(battery); -@@ -1105,12 +1102,6 @@ battery_ac_is_broken_quirk(const struct dmi_system_id *d) - return 0; - } - --static int __init battery_quirk_not_charging(const struct dmi_system_id *d) --{ -- battery_quirk_notcharging = 1; -- return 0; --} -- - static const struct dmi_system_id bat_dmi_table[] __initconst = { - { - /* NEC LZ750/LS */ -@@ -1139,19 +1130,6 @@ static const struct dmi_system_id bat_dmi_table[] __initconst = { - DMI_MATCH(DMI_BIOS_DATE, "08/22/2014"), - }, - }, -- { -- /* -- * On Lenovo ThinkPads the BIOS specification defines -- * a state when the bits for charging and discharging -- * are both set to 0. That state is "Not Charging". -- */ -- .callback = battery_quirk_not_charging, -- .ident = "Lenovo ThinkPad", -- .matches = { -- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), -- DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad"), -- }, -- }, - { - /* Microsoft Surface Go 3 */ - .callback = battery_notification_delay_quirk, --- -2.36.1 - -From 8d958ad7373a56e188dea9645cb75c261c9ae42d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 25 May 2022 14:20:10 +0200 -Subject: [PATCH] HID: hid-input: add Surface Go battery quirk - -Similar to the Surface Go (1), the (Elantech) touchscreen/digitizer in -the Surface Go 2 mistakenly reports the battery of the stylus. Instead -of over the touchsreen device, battery information is provided via -bluetooth and the touchscreen device reports an empty battery. - -Apply the HID_BATTERY_QUIRK_IGNORE quirk to to ignore this battery and -prevent the erroneous low battery warnings. - -Signed-off-by: Maximilian Luz -Patchset: surface-battery ---- - drivers/hid/hid-ids.h | 1 + - drivers/hid/hid-input.c | 2 ++ - 2 files changed, 3 insertions(+) - -diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h -index aca7909c726d..741e38477a91 100644 ---- a/drivers/hid/hid-ids.h -+++ b/drivers/hid/hid-ids.h -@@ -405,6 +405,7 @@ - #define USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN 0x2544 - #define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN 0x2706 - #define I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN 0x261A -+#define I2C_DEVICE_ID_SURFACE_GO2_TOUCHSCREEN 0x2A1C - - #define USB_VENDOR_ID_ELECOM 0x056e - #define USB_DEVICE_ID_ELECOM_BM084 0x0061 -diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c -index 56ec27398a00..263a977801bf 100644 ---- a/drivers/hid/hid-input.c -+++ b/drivers/hid/hid-input.c -@@ -336,6 +336,8 @@ static const struct hid_device_id hid_battery_quirks[] = { - HID_BATTERY_QUIRK_IGNORE }, - { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN), - HID_BATTERY_QUIRK_IGNORE }, -+ { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_GO2_TOUCHSCREEN), -+ HID_BATTERY_QUIRK_IGNORE }, - {} - }; - --- -2.36.1 - diff --git a/patches/5.17/0010-cameras.patch b/patches/5.17/0010-cameras.patch deleted file mode 100644 index 7ff90bd22..000000000 --- a/patches/5.17/0010-cameras.patch +++ /dev/null @@ -1,1218 +0,0 @@ -From 6d050a12fd922f54962a9a049e2351630dcdaca1 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:56:57 +0200 -Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an - INT3472 device - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around cases where this info is not present in the firmware tables, -which is often the case on x86/ACPI devices, both frameworks allow the -provider-driver to attach info about consumers to the clks/regulators -when registering these. - -This causes problems with the probe ordering wrt drivers for consumers -of these clks/regulators. Since the lookups are only registered when the -provider-driver binds, trying to get these clks/regulators before then -results in a -ENOENT error for clks and a dummy regulator for regulators. - -One case where we hit this issue is camera sensors such as e.g. the OV8865 -sensor found on the Microsoft Surface Go. The sensor uses clks, regulators -and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 -ACPI device. There is special platform code handling this and setting -platform_data with the necessary consumer info on the MFD cells -instantiated for the PMIC under: drivers/platform/x86/intel/int3472. - -For this to work properly the ov8865 driver must not bind to the I2C-client -for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and -clk MFD cells have all been fully setup. - -The OV8865 on the Microsoft Surface Go is just one example, all X86 -devices using the Intel IPU3 camera block found on recent Intel SoCs -have similar issues where there is an INT3472 HID ACPI-device, which -describes the clks and regulators, and the driver for this INT3472 device -must be fully initialized before the sensor driver (any sensor driver) -binds for things to work properly. - -On these devices the ACPI nodes describing the sensors all have a _DEP -dependency on the matching INT3472 ACPI device (there is one per sensor). - -This allows solving the probe-ordering problem by delaying the enumeration -(instantiation of the I2C-client in the ov8865 example) of ACPI-devices -which have a _DEP dependency on an INT3472 device. - -The new acpi_dev_ready_for_enumeration() helper used for this is also -exported because for devices, which have the enumeration_by_parent flag -set, the parent-driver will do its own scan of child ACPI devices and -it will try to enumerate those during its probe(). Code doing this such -as e.g. the i2c-core-acpi.c code must call this new helper to ensure -that it too delays the enumeration until all the _DEP dependencies are -met on devices which have the new honor_deps flag set. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/acpi/scan.c | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c -index c82b1bfa1c3d..2227625202aa 100644 ---- a/drivers/acpi/scan.c -+++ b/drivers/acpi/scan.c -@@ -2130,6 +2130,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, - - static void acpi_default_enumeration(struct acpi_device *device) - { -+ if (!acpi_dev_ready_for_enumeration(device)) -+ return; -+ - /* - * Do not enumerate devices with enumeration_by_parent flag set as - * they will be enumerated by their respective parents. --- -2.36.1 - -From 9a7cd1c24bd5a09c61c4bc2ed790d4f0dc0932fa Mon Sep 17 00:00:00 2001 -From: zouxiaoh -Date: Fri, 25 Jun 2021 08:52:59 +0800 -Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs - -Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, -The IPU driver allocates its own page table that is not mapped -via the DMA, and thus the Intel IOMMU driver blocks access giving -this error: DMAR: DRHD: handling fault status reg 3 DMAR: -[DMA Read] Request device [00:05.0] PASID ffffffff -fault addr 76406000 [fault reason 06] PTE Read access is not set -As IPU is not an external facing device which is not risky, so use -IOMMU passthrough mode for Intel IPUs. - -Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b -Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 -Tracked-On: #JIITL8-411 -Signed-off-by: Bingbu Cao -Signed-off-by: zouxiaoh -Signed-off-by: Xu Chongyang -Patchset: cameras ---- - drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ - 1 file changed, 30 insertions(+) - -diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c -index 0bb308eaae4e..44411b057607 100644 ---- a/drivers/iommu/intel/iommu.c -+++ b/drivers/iommu/intel/iommu.c -@@ -57,6 +57,12 @@ - #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) - #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) - #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) -+#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ -+ ((pdev)->device == 0x9a19 || \ -+ (pdev)->device == 0x9a39 || \ -+ (pdev)->device == 0x4e19 || \ -+ (pdev)->device == 0x465d || \ -+ (pdev)->device == 0x1919)) - #define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ - ((pdev)->device == 0x9d3e)) - #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) -@@ -335,12 +341,14 @@ EXPORT_SYMBOL_GPL(intel_iommu_enabled); - - static int dmar_map_gfx = 1; - static int dmar_map_ipts = 1; -+static int dmar_map_ipu = 1; - static int intel_iommu_superpage = 1; - static int iommu_identity_mapping; - static int iommu_skip_te_disable; - - #define IDENTMAP_GFX 2 - #define IDENTMAP_AZALIA 4 -+#define IDENTMAP_IPU 8 - #define IDENTMAP_IPTS 16 - - int intel_iommu_gfx_mapped; -@@ -2992,6 +3000,9 @@ static int device_def_domain_type(struct device *dev) - if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) - return IOMMU_DOMAIN_IDENTITY; - -+ if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) -+ return IOMMU_DOMAIN_IDENTITY; -+ - if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) - return IOMMU_DOMAIN_IDENTITY; - } -@@ -3430,6 +3441,9 @@ static int __init init_dmars(void) - if (!dmar_map_gfx) - iommu_identity_mapping |= IDENTMAP_GFX; - -+ if (!dmar_map_ipu) -+ iommu_identity_mapping |= IDENTMAP_IPU; -+ - if (!dmar_map_ipts) - iommu_identity_mapping |= IDENTMAP_IPTS; - -@@ -5674,6 +5688,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) - dmar_map_gfx = 0; - } - -+static void quirk_iommu_ipu(struct pci_dev *dev) -+{ -+ if (!IS_INTEL_IPU(dev)) -+ return; -+ -+ if (risky_device(dev)) -+ return; -+ -+ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); -+ dmar_map_ipu = 0; -+} -+ - static void quirk_iommu_ipts(struct pci_dev *dev) - { - if (!IS_IPTS(dev)) -@@ -5685,6 +5711,7 @@ static void quirk_iommu_ipts(struct pci_dev *dev) - pci_info(dev, "Passthrough IOMMU for IPTS\n"); - dmar_map_ipts = 0; - } -+ - /* G4x/GM45 integrated gfx dmar support is totally busted. */ - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); -@@ -5720,6 +5747,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); - -+/* disable IPU dmar support */ -+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); -+ - /* disable IPTS dmar support */ - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); - --- -2.36.1 - -From 00b5fffd14cdb2e2174756cd63fb534c1649ca51 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 10 Oct 2021 20:57:02 +0200 -Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain - -The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic -can be forwarded to a device connected to the PMIC as though it were -connected directly to the system bus. Enable this mode when the chip -is initialised. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -index 22f61b47f9e5..e1de1ff40bba 100644 ---- a/drivers/platform/x86/intel/int3472/tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -45,6 +45,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) - return ret; - } - -+ /* Enable I2C daisy chain */ -+ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); -+ if (ret) { -+ dev_err(dev, "Failed to enable i2c daisy chain\n"); -+ return ret; -+ } -+ - dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); - - return 0; --- -2.36.1 - -From 14be9ae22b20fc3df91b8f70015c09908cf4f820 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 28 Oct 2021 21:55:16 +0100 -Subject: [PATCH] media: i2c: Add driver for DW9719 VCM - -Add a driver for the DW9719 VCM. The driver creates a v4l2 subdevice -and registers a control to set the desired focus. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 7 + - drivers/media/i2c/Kconfig | 11 + - drivers/media/i2c/Makefile | 1 + - drivers/media/i2c/dw9719.c | 427 +++++++++++++++++++++++++++++++++++++ - 4 files changed, 446 insertions(+) - create mode 100644 drivers/media/i2c/dw9719.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 4d83cd26e299..0e0e1e53e49c 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -5865,6 +5865,13 @@ T: git git://linuxtv.org/media_tree.git - F: Documentation/devicetree/bindings/media/i2c/dongwoon,dw9714.txt - F: drivers/media/i2c/dw9714.c - -+DONGWOON DW9719 LENS VOICE COIL DRIVER -+M: Daniel Scally -+L: linux-media@vger.kernel.org -+S: Maintained -+T: git git://linuxtv.org/media_tree.git -+F: drivers/media/i2c/dw9719.c -+ - DONGWOON DW9768 LENS VOICE COIL DRIVER - M: Dongchun Zhu - L: linux-media@vger.kernel.org -diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig -index 69c56e24a612..d9fac304f451 100644 ---- a/drivers/media/i2c/Kconfig -+++ b/drivers/media/i2c/Kconfig -@@ -1466,6 +1466,17 @@ config VIDEO_DW9714 - capability. This is designed for linear control of - voice coil motors, controlled via I2C serial interface. - -+config VIDEO_DW9719 -+ tristate "DW9719 lens voice coil support" -+ depends on I2C && VIDEO_V4L2 -+ select MEDIA_CONTROLLER -+ select VIDEO_V4L2_SUBDEV_API -+ select V4L2_ASYNC -+ help -+ This is a driver for the DW9719 camera lens voice coil. -+ This is designed for linear control of voice coil motors, -+ controlled via I2C serial interface. -+ - config VIDEO_DW9768 - tristate "DW9768 lens voice coil support" - depends on I2C && VIDEO_V4L2 -diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile -index b01f6cd05ee8..ba8b31d79222 100644 ---- a/drivers/media/i2c/Makefile -+++ b/drivers/media/i2c/Makefile -@@ -24,6 +24,7 @@ obj-$(CONFIG_VIDEO_SAA6752HS) += saa6752hs.o - obj-$(CONFIG_VIDEO_AD5820) += ad5820.o - obj-$(CONFIG_VIDEO_AK7375) += ak7375.o - obj-$(CONFIG_VIDEO_DW9714) += dw9714.o -+obj-$(CONFIG_VIDEO_DW9719) += dw9719.o - obj-$(CONFIG_VIDEO_DW9768) += dw9768.o - obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o - obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o -diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c -new file mode 100644 -index 000000000000..8451c75b696b ---- /dev/null -+++ b/drivers/media/i2c/dw9719.c -@@ -0,0 +1,427 @@ -+// SPDX-License-Identifier: GPL-2.0 -+// Copyright (c) 2012 Intel Corporation -+ -+/* -+ * Based on linux/modules/camera/drivers/media/i2c/imx/dw9719.c in this repo: -+ * https://github.com/ZenfoneArea/android_kernel_asus_zenfone5 -+ */ -+ -+#include -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+#define DW9719_MAX_FOCUS_POS 1023 -+#define DW9719_CTRL_STEPS 16 -+#define DW9719_CTRL_DELAY_US 1000 -+#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) -+ -+#define DW9719_INFO 0 -+#define DW9719_ID 0xF1 -+#define DW9719_CONTROL 2 -+#define DW9719_VCM_CURRENT 3 -+ -+#define DW9719_MODE 6 -+#define DW9719_VCM_FREQ 7 -+ -+#define DW9719_MODE_SAC3 0x40 -+#define DW9719_DEFAULT_VCM_FREQ 0x60 -+#define DW9719_ENABLE_RINGING 0x02 -+ -+#define NUM_REGULATORS 2 -+ -+#define to_dw9719_device(x) container_of(x, struct dw9719_device, sd) -+ -+struct dw9719_device { -+ struct device *dev; -+ struct i2c_client *client; -+ struct regulator_bulk_data regulators[NUM_REGULATORS]; -+ struct v4l2_subdev sd; -+ -+ struct dw9719_v4l2_ctrls { -+ struct v4l2_ctrl_handler handler; -+ struct v4l2_ctrl *focus; -+ } ctrls; -+}; -+ -+static int dw9719_i2c_rd8(struct i2c_client *client, u8 reg, u8 *val) -+{ -+ struct i2c_msg msg[2]; -+ u8 buf[2] = { reg }; -+ int ret; -+ -+ msg[0].addr = client->addr; -+ msg[0].flags = 0; -+ msg[0].len = 1; -+ msg[0].buf = buf; -+ -+ msg[1].addr = client->addr; -+ msg[1].flags = I2C_M_RD; -+ msg[1].len = 1; -+ msg[1].buf = &buf[1]; -+ *val = 0; -+ -+ ret = i2c_transfer(client->adapter, msg, 2); -+ if (ret < 0) -+ return ret; -+ -+ *val = buf[1]; -+ -+ return 0; -+} -+ -+static int dw9719_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) -+{ -+ struct i2c_msg msg; -+ int ret; -+ -+ u8 buf[2] = { reg, val }; -+ -+ msg.addr = client->addr; -+ msg.flags = 0; -+ msg.len = sizeof(buf); -+ msg.buf = buf; -+ -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ return ret < 0 ? ret : 0; -+} -+ -+static int dw9719_i2c_wr16(struct i2c_client *client, u8 reg, u16 val) -+{ -+ struct i2c_msg msg; -+ u8 buf[3] = { reg }; -+ int ret; -+ -+ put_unaligned_be16(val, buf + 1); -+ -+ msg.addr = client->addr; -+ msg.flags = 0; -+ msg.len = sizeof(buf); -+ msg.buf = buf; -+ -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ return ret < 0 ? ret : 0; -+} -+ -+static int dw9719_detect(struct dw9719_device *dw9719) -+{ -+ int ret; -+ u8 val; -+ -+ ret = dw9719_i2c_rd8(dw9719->client, DW9719_INFO, &val); -+ if (ret < 0) -+ return ret; -+ -+ if (val != DW9719_ID) { -+ dev_err(dw9719->dev, "Failed to detect correct id\n"); -+ ret = -ENXIO; -+ } -+ -+ return 0; -+} -+ -+static int dw9719_power_down(struct dw9719_device *dw9719) -+{ -+ return regulator_bulk_disable(NUM_REGULATORS, dw9719->regulators); -+} -+ -+static int dw9719_power_up(struct dw9719_device *dw9719) -+{ -+ int ret; -+ -+ ret = regulator_bulk_enable(NUM_REGULATORS, dw9719->regulators); -+ if (ret) -+ return ret; -+ -+ /* Jiggle SCL pin to wake up device */ -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, 1); -+ -+ /* Need 100us to transit from SHUTDOWN to STANDBY*/ -+ usleep_range(100, 1000); -+ -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, -+ DW9719_ENABLE_RINGING); -+ if (ret < 0) -+ goto fail_powerdown; -+ -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_MODE, DW9719_MODE_SAC3); -+ if (ret < 0) -+ goto fail_powerdown; -+ -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_VCM_FREQ, -+ DW9719_DEFAULT_VCM_FREQ); -+ if (ret < 0) -+ goto fail_powerdown; -+ -+ return 0; -+ -+fail_powerdown: -+ dw9719_power_down(dw9719); -+ return ret; -+} -+ -+static int dw9719_t_focus_abs(struct dw9719_device *dw9719, s32 value) -+{ -+ int ret; -+ -+ value = clamp(value, 0, DW9719_MAX_FOCUS_POS); -+ ret = dw9719_i2c_wr16(dw9719->client, DW9719_VCM_CURRENT, value); -+ if (ret < 0) -+ return ret; -+ -+ return 0; -+} -+ -+static int dw9719_set_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct dw9719_device *dw9719 = container_of(ctrl->handler, -+ struct dw9719_device, -+ ctrls.handler); -+ int ret; -+ -+ /* Only apply changes to the controls if the device is powered up */ -+ if (!pm_runtime_get_if_in_use(dw9719->dev)) -+ return 0; -+ -+ switch (ctrl->id) { -+ case V4L2_CID_FOCUS_ABSOLUTE: -+ ret = dw9719_t_focus_abs(dw9719, ctrl->val); -+ break; -+ default: -+ ret = -EINVAL; -+ } -+ -+ pm_runtime_put(dw9719->dev); -+ -+ return ret; -+} -+ -+static const struct v4l2_ctrl_ops dw9719_ctrl_ops = { -+ .s_ctrl = dw9719_set_ctrl, -+}; -+ -+static int __maybe_unused dw9719_suspend(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct dw9719_device *dw9719 = to_dw9719_device(sd); -+ int ret; -+ int val; -+ -+ for (val = dw9719->ctrls.focus->val; val >= 0; -+ val -= DW9719_CTRL_STEPS) { -+ ret = dw9719_t_focus_abs(dw9719, val); -+ if (ret) -+ return ret; -+ -+ usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); -+ } -+ -+ return dw9719_power_down(dw9719); -+} -+ -+static int __maybe_unused dw9719_resume(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct dw9719_device *dw9719 = to_dw9719_device(sd); -+ int current_focus = dw9719->ctrls.focus->val; -+ int ret; -+ int val; -+ -+ ret = dw9719_power_up(dw9719); -+ if (ret) -+ return ret; -+ -+ for (val = current_focus % DW9719_CTRL_STEPS; val < current_focus; -+ val += DW9719_CTRL_STEPS) { -+ ret = dw9719_t_focus_abs(dw9719, val); -+ if (ret) -+ goto err_power_down; -+ -+ usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); -+ } -+ -+ return 0; -+ -+err_power_down: -+ dw9719_power_down(dw9719); -+ return ret; -+} -+ -+static int dw9719_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -+{ -+ return pm_runtime_resume_and_get(sd->dev); -+} -+ -+static int dw9719_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -+{ -+ pm_runtime_put(sd->dev); -+ -+ return 0; -+} -+ -+static const struct v4l2_subdev_internal_ops dw9719_internal_ops = { -+ .open = dw9719_open, -+ .close = dw9719_close, -+}; -+ -+static int dw9719_init_controls(struct dw9719_device *dw9719) -+{ -+ const struct v4l2_ctrl_ops *ops = &dw9719_ctrl_ops; -+ int ret; -+ -+ ret = v4l2_ctrl_handler_init(&dw9719->ctrls.handler, 1); -+ if (ret) -+ return ret; -+ -+ dw9719->ctrls.focus = v4l2_ctrl_new_std(&dw9719->ctrls.handler, ops, -+ V4L2_CID_FOCUS_ABSOLUTE, 0, -+ DW9719_MAX_FOCUS_POS, 1, 0); -+ -+ if (dw9719->ctrls.handler.error) { -+ dev_err(dw9719->dev, "Error initialising v4l2 ctrls\n"); -+ ret = dw9719->ctrls.handler.error; -+ goto err_free_handler; -+ } -+ -+ dw9719->sd.ctrl_handler = &dw9719->ctrls.handler; -+ -+ return ret; -+ -+err_free_handler: -+ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); -+ return ret; -+} -+ -+static const struct v4l2_subdev_ops dw9719_ops = { }; -+ -+static int dw9719_probe(struct i2c_client *client) -+{ -+ struct dw9719_device *dw9719; -+ int ret; -+ -+ dw9719 = devm_kzalloc(&client->dev, sizeof(*dw9719), GFP_KERNEL); -+ if (!dw9719) -+ return -ENOMEM; -+ -+ dw9719->client = client; -+ dw9719->dev = &client->dev; -+ -+ dw9719->regulators[0].supply = "vdd"; -+ /* -+ * The DW9719 has only the 1 VDD voltage input, but some PMICs such as -+ * the TPS68470 PMIC have I2C passthrough capability, to disconnect the -+ * sensor's I2C pins from the I2C bus when the sensors VSIO (Sensor-IO) -+ * is off, because some sensors then short these pins to ground; -+ * and the DW9719 might sit behind this passthrough, this it needs to -+ * enable VSIO as that will also enable the I2C passthrough. -+ */ -+ dw9719->regulators[1].supply = "vsio"; -+ -+ ret = devm_regulator_bulk_get(&client->dev, NUM_REGULATORS, -+ dw9719->regulators); -+ if (ret) -+ return dev_err_probe(&client->dev, ret, "getting regulators\n"); -+ -+ v4l2_i2c_subdev_init(&dw9719->sd, client, &dw9719_ops); -+ dw9719->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; -+ dw9719->sd.internal_ops = &dw9719_internal_ops; -+ -+ ret = dw9719_init_controls(dw9719); -+ if (ret) -+ return ret; -+ -+ ret = media_entity_pads_init(&dw9719->sd.entity, 0, NULL); -+ if (ret < 0) -+ goto err_free_ctrl_handler; -+ -+ dw9719->sd.entity.function = MEDIA_ENT_F_LENS; -+ -+ /* -+ * We need the driver to work in the event that pm runtime is disable in -+ * the kernel, so power up and verify the chip now. In the event that -+ * runtime pm is disabled this will leave the chip on, so that the lens -+ * will work. -+ */ -+ -+ ret = dw9719_power_up(dw9719); -+ if (ret) -+ goto err_cleanup_media; -+ -+ ret = dw9719_detect(dw9719); -+ if (ret) -+ goto err_powerdown; -+ -+ pm_runtime_set_active(&client->dev); -+ pm_runtime_get_noresume(&client->dev); -+ pm_runtime_enable(&client->dev); -+ -+ ret = v4l2_async_register_subdev(&dw9719->sd); -+ if (ret < 0) -+ goto err_pm_runtime; -+ -+ pm_runtime_set_autosuspend_delay(&client->dev, 1000); -+ pm_runtime_use_autosuspend(&client->dev); -+ pm_runtime_put_autosuspend(&client->dev); -+ -+ return ret; -+ -+err_pm_runtime: -+ pm_runtime_disable(&client->dev); -+ pm_runtime_put_noidle(&client->dev); -+err_powerdown: -+ dw9719_power_down(dw9719); -+err_cleanup_media: -+ media_entity_cleanup(&dw9719->sd.entity); -+err_free_ctrl_handler: -+ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); -+ -+ return ret; -+} -+ -+static int dw9719_remove(struct i2c_client *client) -+{ -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct dw9719_device *dw9719 = container_of(sd, struct dw9719_device, -+ sd); -+ -+ pm_runtime_disable(&client->dev); -+ v4l2_async_unregister_subdev(sd); -+ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); -+ media_entity_cleanup(&dw9719->sd.entity); -+ -+ return 0; -+} -+ -+static const struct i2c_device_id dw9719_id_table[] = { -+ { "dw9719" }, -+ { } -+}; -+MODULE_DEVICE_TABLE(i2c, dw9719_id_table); -+ -+static const struct dev_pm_ops dw9719_pm_ops = { -+ SET_RUNTIME_PM_OPS(dw9719_suspend, dw9719_resume, NULL) -+}; -+ -+static struct i2c_driver dw9719_i2c_driver = { -+ .driver = { -+ .name = "dw9719", -+ .pm = &dw9719_pm_ops, -+ }, -+ .probe_new = dw9719_probe, -+ .remove = dw9719_remove, -+ .id_table = dw9719_id_table, -+}; -+module_i2c_driver(dw9719_i2c_driver); -+ -+MODULE_AUTHOR("Daniel Scally "); -+MODULE_DESCRIPTION("DW9719 VCM Driver"); -+MODULE_LICENSE("GPL"); --- -2.36.1 - -From 6b10e5301810c1c63ce84430d8ea9f0be7e1ad39 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 13 Dec 2021 22:38:17 +0000 -Subject: [PATCH] media: entity: Skip non-data links in graph iteration - -When iterating over the media graph, don't follow links that are not -data links. - -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/mc/mc-entity.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c -index b411f9796191..d0563ee4b28b 100644 ---- a/drivers/media/mc/mc-entity.c -+++ b/drivers/media/mc/mc-entity.c -@@ -295,6 +295,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; --- -2.36.1 - -From 6c7eadb12e4795830fae1b4d929adbe2aec7be9b Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 13 Dec 2021 22:53:09 +0000 -Subject: [PATCH] media: media.h: Add new media link type - -To describe in the kernel the connection between devices and their -supporting peripherals (for example, a camera sensor and the vcm -driving the focusing lens for it), add a new type of media link -to introduce the concept of these ancillary links. - -Add some elements to the uAPI documentation to explain the new link -type, their purpose and some aspects of their current implementation. - -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - .../media/mediactl/media-controller-model.rst | 6 ++++++ - .../media/mediactl/media-types.rst | 17 ++++++++++++----- - include/uapi/linux/media.h | 1 + - 3 files changed, 19 insertions(+), 5 deletions(-) - -diff --git a/Documentation/userspace-api/media/mediactl/media-controller-model.rst b/Documentation/userspace-api/media/mediactl/media-controller-model.rst -index 222cb99debb5..78bfdfb2a322 100644 ---- a/Documentation/userspace-api/media/mediactl/media-controller-model.rst -+++ b/Documentation/userspace-api/media/mediactl/media-controller-model.rst -@@ -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 -diff --git a/Documentation/userspace-api/media/mediactl/media-types.rst b/Documentation/userspace-api/media/mediactl/media-types.rst -index 0a26397bd01d..60747251d409 100644 ---- a/Documentation/userspace-api/media/mediactl/media-types.rst -+++ b/Documentation/userspace-api/media/mediactl/media-types.rst -@@ -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 -diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h -index 200fa8462b90..afbae7213d35 100644 ---- a/include/uapi/linux/media.h -+++ b/include/uapi/linux/media.h -@@ -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; --- -2.36.1 - -From 5aa28f39276b39d989ab6329eabd3388818b1951 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 13 Dec 2021 22:36:31 +0000 -Subject: [PATCH] media: entity: Add link_type_name() helper - -Now we have three types of media link, printing the right name during -debug output is slightly more complicated. Add a helper function to -make it easier. - -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/mc/mc-entity.c | 18 +++++++++++++++--- - 1 file changed, 15 insertions(+), 3 deletions(-) - -diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c -index d0563ee4b28b..1a7d0a4fb9e8 100644 ---- a/drivers/media/mc/mc-entity.c -+++ b/drivers/media/mc/mc-entity.c -@@ -44,6 +44,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) - { -@@ -89,9 +103,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; --- -2.36.1 - -From 08908b9fe6ed00d0cb224cf3b30d08c749e141e5 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 13 Dec 2021 22:54:10 +0000 -Subject: [PATCH] media: entity: Add support for ancillary links - -Add functions to create ancillary links, so that they don't need to -be manually created by users. - -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/mc/mc-entity.c | 22 ++++++++++++++++++++++ - include/media/media-entity.h | 19 +++++++++++++++++++ - 2 files changed, 41 insertions(+) - -diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c -index 1a7d0a4fb9e8..d7e2f78a83cc 100644 ---- a/drivers/media/mc/mc-entity.c -+++ b/drivers/media/mc/mc-entity.c -@@ -1032,3 +1032,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); -diff --git a/include/media/media-entity.h b/include/media/media-entity.h -index fea489f03d57..2a58defc4886 100644 ---- a/include/media/media-entity.h -+++ b/include/media/media-entity.h -@@ -1108,4 +1108,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 --- -2.36.1 - -From e840422acb56ca345058c0a8cbf87b33252135ec Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 26 Nov 2021 22:55:50 +0000 -Subject: [PATCH] media: v4l2-async: Create links during - v4l2_async_match_notify() - -Upon an async fwnode match, there's some typical behaviour that the -notifier and matching subdev will want to do. For example, a notifier -representing a sensor matching to an async subdev representing its -VCM will want to create an ancillary link to expose that relationship -to userspace. - -To avoid lots of code in individual drivers, try to build these links -within v4l2 core. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/v4l2-core/v4l2-async.c | 31 ++++++++++++++++++++++++++++ - 1 file changed, 31 insertions(+) - -diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c -index 0404267f1ae4..436bd6900fd8 100644 ---- a/drivers/media/v4l2-core/v4l2-async.c -+++ b/drivers/media/v4l2-core/v4l2-async.c -@@ -275,6 +275,24 @@ v4l2_async_nf_try_complete(struct v4l2_async_notifier *notifier) - static int - v4l2_async_nf_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_nf_call_unbind(notifier, sd, asd); -+ v4l2_device_unregister_subdev(sd); -+ return ret; -+ } -+ - /* Remove from the waiting list */ - list_del(&asd->list); - sd->asd = asd; --- -2.36.1 - -From 8dc582d9c02942a7aa2bedaeca6a6874980ae209 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 4 May 2022 23:21:45 +0100 -Subject: [PATCH] media: ipu3-cio2: Move functionality from .complete() to - .bound() - -Creating links and registering subdev nodes during the .complete() -callback has the unfortunate effect of preventing all cameras that -connect to a notifier from working if any one of their drivers fails -to probe. Moving the functionality from .complete() to .bound() allows -those camera sensor drivers that did probe correctly to work regardless. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 65 +++++++------------ - 1 file changed, 23 insertions(+), 42 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -index 0e9b0503b62a..50682a7b2a07 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -@@ -1382,7 +1382,10 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, - { - struct cio2_device *cio2 = to_cio2_device(notifier); - struct sensor_async_subdev *s_asd = to_sensor_asd(asd); -+ struct device *dev = &cio2->pci_dev->dev; - struct cio2_queue *q; -+ unsigned int pad; -+ int ret; - - if (cio2->queue[s_asd->csi2.port].sensor) - return -EBUSY; -@@ -1393,7 +1396,26 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, - q->sensor = sd; - q->csi_rx_base = cio2->base + CIO2_REG_PIPE_BASE(q->csi2.port); - -- return 0; -+ for (pad = 0; pad < q->sensor->entity.num_pads; pad++) -+ if (q->sensor->entity.pads[pad].flags & -+ MEDIA_PAD_FL_SOURCE) -+ break; -+ -+ if (pad == q->sensor->entity.num_pads) { -+ dev_err(dev, "failed to find src pad for %s\n", -+ q->sensor->name); -+ return -ENXIO; -+ } -+ -+ ret = media_create_pad_link(&q->sensor->entity, pad, &q->subdev.entity, -+ CIO2_PAD_SINK, 0); -+ if (ret) { -+ dev_err(dev, "failed to create link for %s\n", -+ q->sensor->name); -+ return ret; -+ } -+ -+ return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); - } - - /* The .unbind callback */ -@@ -1407,50 +1429,9 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, - cio2->queue[s_asd->csi2.port].sensor = NULL; - } - --/* .complete() is called after all subdevices have been located */ --static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) --{ -- struct cio2_device *cio2 = to_cio2_device(notifier); -- struct device *dev = &cio2->pci_dev->dev; -- struct sensor_async_subdev *s_asd; -- struct v4l2_async_subdev *asd; -- struct cio2_queue *q; -- unsigned int pad; -- int ret; -- -- list_for_each_entry(asd, &cio2->notifier.asd_list, asd_list) { -- s_asd = to_sensor_asd(asd); -- q = &cio2->queue[s_asd->csi2.port]; -- -- for (pad = 0; pad < q->sensor->entity.num_pads; pad++) -- if (q->sensor->entity.pads[pad].flags & -- MEDIA_PAD_FL_SOURCE) -- break; -- -- if (pad == q->sensor->entity.num_pads) { -- dev_err(dev, "failed to find src pad for %s\n", -- q->sensor->name); -- return -ENXIO; -- } -- -- ret = media_create_pad_link( -- &q->sensor->entity, pad, -- &q->subdev.entity, CIO2_PAD_SINK, -- 0); -- if (ret) { -- dev_err(dev, "failed to create link for %s\n", -- q->sensor->name); -- return ret; -- } -- } -- -- return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); --} -- - static const struct v4l2_async_notifier_operations cio2_async_ops = { - .bound = cio2_notifier_bound, - .unbind = cio2_notifier_unbind, -- .complete = cio2_notifier_complete, - }; - - static int cio2_parse_firmware(struct cio2_device *cio2) --- -2.36.1 - -From d6c1da8bbd5c3bf70b4d392f0cc4cd69d484b720 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 2 Jun 2022 22:15:56 +0100 -Subject: [PATCH] media: ipu3-cio2: Re-add .complete() to ipu3-cio2 - -Removing the .complete() callback had some unintended consequences. -Because the VCM driver is not directly linked to the ipu3-cio2 -driver .bound() never gets called for it, which means its devnode -is never created if it probes late. Because .complete() waits for -any sub-notifiers to also be complete it is captured in that call. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -index 50682a7b2a07..ff79582a583d 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -@@ -1429,9 +1429,18 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, - cio2->queue[s_asd->csi2.port].sensor = NULL; - } - -+/* .complete() is called after all subdevices have been located */ -+static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) -+{ -+ struct cio2_device *cio2 = to_cio2_device(notifier); -+ -+ return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); -+} -+ - static const struct v4l2_async_notifier_operations cio2_async_ops = { - .bound = cio2_notifier_bound, - .unbind = cio2_notifier_unbind, -+ .complete = cio2_notifier_complete, - }; - - static int cio2_parse_firmware(struct cio2_device *cio2) --- -2.36.1 - diff --git a/patches/5.17/0011-amd-gpio.patch b/patches/5.17/0011-amd-gpio.patch deleted file mode 100644 index 6e34d7c53..000000000 --- a/patches/5.17/0011-amd-gpio.patch +++ /dev/null @@ -1,109 +0,0 @@ -From fb0dae27e86c721f5db36d4e8837341bd47dcebb Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Sat, 29 May 2021 17:47:38 +1000 -Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 - override - -This patch is the work of Thomas Gleixner and is -copied from: -https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ - -This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin -setup that is missing in the laptops ACPI table. - -This patch was used for validation of the issue, and is not a proper -fix, but is probably a better temporary hack than continuing to probe -the Legacy PIC and run with the PIC in an unknown state. - -Patchset: amd-gpio ---- - arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ - 1 file changed, 17 insertions(+) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 0d01e7f5078c..2b06cf5f2b1f 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -22,6 +22,7 @@ - #include - #include - #include -+#include - - #include - #include -@@ -1152,6 +1153,17 @@ static void __init mp_config_acpi_legacy_irqs(void) - } - } - -+static const struct dmi_system_id surface_quirk[] __initconst = { -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") -+ }, -+ }, -+ {} -+}; -+ - /* - * Parse IOAPIC related entries in MADT - * returns 0 on success, < 0 on error -@@ -1207,6 +1219,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) - acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, - acpi_gbl_FADT.sci_interrupt); - -+ if (dmi_check_system(surface_quirk)) { -+ pr_warn("Surface hack: Override irq 7\n"); -+ mp_override_legacy_irq(7, 3, 3, 7); -+ } -+ - /* Fill in identity legacy mappings where no override */ - mp_config_acpi_legacy_irqs(); - --- -2.36.1 - -From aa300cc8764e36f4c4e40d9ac76ca0e1e963883b Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 3 Jun 2021 14:04:26 +0200 -Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override - quirk - -The 13" version of the Surface Laptop 4 has the same problem as the 15" -version, but uses a different SKU. Add that SKU to the quirk as well. - -Patchset: amd-gpio ---- - arch/x86/kernel/acpi/boot.c | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 2b06cf5f2b1f..caaec200bea2 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -1155,12 +1155,19 @@ static void __init mp_config_acpi_legacy_irqs(void) - - static const struct dmi_system_id surface_quirk[] __initconst = { - { -- .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") - }, - }, -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") -+ }, -+ }, - {} - }; - --- -2.36.1 - diff --git a/patches/5.18/0001-surface3-oemb.patch b/patches/5.18/0001-surface3-oemb.patch deleted file mode 100644 index 896946781..000000000 --- a/patches/5.18/0001-surface3-oemb.patch +++ /dev/null @@ -1,101 +0,0 @@ -From c6e3f1bc77076f24fd65fabf5daedc27d113c547 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 18 Oct 2020 16:42:44 +0900 -Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI - table - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI data into dmi_system_id tables used -for quirks so that each driver can enable quirks even on the affected -systems. - -On affected systems, DMI data will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: surface3-oemb ---- - drivers/platform/surface/surface3-wmi.c | 7 +++++++ - sound/soc/codecs/rt5645.c | 9 +++++++++ - sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ - 3 files changed, 24 insertions(+) - -diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c -index ca4602bcc7de..490b9731068a 100644 ---- a/drivers/platform/surface/surface3-wmi.c -+++ b/drivers/platform/surface/surface3-wmi.c -@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - #endif - { } - }; -diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c -index 4b2e027c1033..dc96ec7bcbd5 100644 ---- a/sound/soc/codecs/rt5645.c -+++ b/sound/soc/codecs/rt5645.c -@@ -3718,6 +3718,15 @@ static const struct dmi_system_id dmi_platform_data[] = { - }, - .driver_data = (void *)&intel_braswell_platform_data, - }, -+ { -+ .ident = "Microsoft Surface 3", -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)&intel_braswell_platform_data, -+ }, - { - /* - * Match for the GPDwin which unfortunately uses somewhat -diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -index 6beb00858c33..d82d77387a0a 100644 ---- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c -+++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .callback = cht_surface_quirk_cb, -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - { } - }; - --- -2.37.2 - diff --git a/patches/5.18/0002-mwifiex.patch b/patches/5.18/0002-mwifiex.patch deleted file mode 100644 index dc1d8c212..000000000 --- a/patches/5.18/0002-mwifiex.patch +++ /dev/null @@ -1,704 +0,0 @@ -From 5fd4fa4e0125e6047deecd25e5bcfb267e803af6 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:32:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_wsid quirk for Surface 3 - -This commit adds reset_wsid quirk and uses this quirk for Surface 3 on -card reset. - -To reset mwifiex on Surface 3, it seems that calling the _DSM method -exists in \_SB.WSID [1] device is required. - -On Surface 3, calling the _DSM method removes/re-probes the card by -itself. So, need to place the reset function before performing FLR and -skip performing any other reset-related works. - -Note that Surface Pro 3 also has the WSID device [2], but it seems to need -more work. This commit only supports Surface 3 yet. - -[1] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_3/dsdt.dsl#L11947-L12011 -[2] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_pro_3/dsdt.dsl#L12164-L12216 - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 10 +++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 83 +++++++++++++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.h | 6 ++ - 3 files changed, 99 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index d5fb29400bad..033648526f16 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -2993,6 +2993,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - -+ /* On Surface 3, reset_wsid method removes then re-probes card by -+ * itself. So, need to place it here and skip performing any other -+ * reset-related works. -+ */ -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) { -+ mwifiex_pcie_reset_wsid_quirk(card->dev); -+ /* skip performing any other reset-related works */ -+ return; -+ } -+ - /* We can't afford to wait here; remove() might be waiting on us. If we - * can't grab the device lock, maybe we'll get another chance later. - */ -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 0234cf3c2974..563dd0d5ac79 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -15,10 +15,21 @@ - * this warranty disclaimer. - */ - -+#include - #include - - #include "pcie_quirks.h" - -+/* For reset_wsid quirk */ -+#define ACPI_WSID_PATH "\\_SB.WSID" -+#define WSID_REV 0x0 -+#define WSID_FUNC_WIFI_PWR_OFF 0x1 -+#define WSID_FUNC_WIFI_PWR_ON 0x2 -+/* WSID _DSM UUID: "534ea3bf-fcc2-4e7a-908f-a13978f0c7ef" */ -+static const guid_t wsid_dsm_guid = -+ GUID_INIT(0x534ea3bf, 0xfcc2, 0x4e7a, -+ 0x90, 0x8f, 0xa1, 0x39, 0x78, 0xf0, 0xc7, 0xef); -+ - /* quirk table based on DMI matching */ - static const struct dmi_system_id mwifiex_quirk_table[] = { - { -@@ -87,6 +98,14 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - {} - }; - -@@ -103,6 +122,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - dev_info(&pdev->dev, "no quirks enabled\n"); - if (card->quirks & QUIRK_FW_RST_D3COLD) - dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) -+ dev_info(&pdev->dev, -+ "quirk reset_wsid for Surface 3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -@@ -159,3 +181,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) - - return 0; - } -+ -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev) -+{ -+ acpi_handle handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n"); -+ -+ status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&pdev->dev, "No ACPI handle for path %s\n", -+ ACPI_WSID_PATH); -+ return -ENODEV; -+ } -+ -+ if (!acpi_has_method(handle, "_DSM")) { -+ dev_err(&pdev->dev, "_DSM method not found\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power off func\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power on func\n"); -+ return -ENODEV; -+ } -+ -+ /* card will be removed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi off...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi off\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ /* card will be re-probed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi on...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi on\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ return 0; -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 8ec4176d698f..25370c5a4f59 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -19,5 +19,11 @@ - - #define QUIRK_FW_RST_D3COLD BIT(0) - -+/* Surface 3 and Surface Pro 3 have the same _DSM method but need to -+ * be handled differently. Currently, only S3 is supported. -+ */ -+#define QUIRK_FW_RST_WSID_S3 BIT(1) -+ - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); --- -2.37.2 - -From e8938ddf37c21a79376be890bc7898e6c39351e0 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Wed, 30 Sep 2020 18:08:24 +0900 -Subject: [PATCH] mwifiex: pcie: (OEMB) add quirk for Surface 3 with broken DMI - table - -(made referring to http://git.osdn.net/view?p=android-x86/kernel.git;a=commitdiff;h=18e2e857c57633b25b3b4120f212224a108cd883) - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI info for the affected Surface 3. - -On affected systems, DMI info will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie_quirks.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 563dd0d5ac79..32e2f000e57b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -106,6 +106,15 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - {} - }; - --- -2.37.2 - -From fa33af74c0ab3047b79a0c7f4c00bf9f277d29de Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 4 Oct 2020 00:11:49 +0900 -Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ - -Currently, mwifiex fw will crash after suspend on recent kernel series. -On Windows, it seems that the root port of wifi will never enter D3 state -(stay on D0 state). And on Linux, disabling the D3 state for the -bridge fixes fw crashing after suspend. - -This commit disables the D3 state of root port on driver initialization -and fixes fw crashing after suspend. - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 27 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 033648526f16..ca6bcbe4794c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -380,6 +380,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) - { - struct pcie_service_card *card; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - int ret; - - pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", -@@ -421,6 +422,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return -1; - } - -+ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing -+ * after suspend -+ */ -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ parent_pdev->bridge_d3 = false; -+ - return 0; - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 32e2f000e57b..356401bab59c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -38,7 +38,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5", -@@ -47,7 +48,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -56,7 +58,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 6", -@@ -64,7 +67,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 1", -@@ -72,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 2", -@@ -80,7 +85,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 1", -@@ -88,7 +94,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 2", -@@ -96,7 +103,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface 3", -@@ -134,6 +142,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_FW_RST_WSID_S3) - dev_info(&pdev->dev, - "quirk reset_wsid for Surface 3 enabled\n"); -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ dev_info(&pdev->dev, -+ "quirk no_brigde_d3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 25370c5a4f59..a1de111ad1db 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -23,6 +23,7 @@ - * be handled differently. Currently, only S3 is supported. - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) -+#define QUIRK_NO_BRIDGE_D3 BIT(2) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.37.2 - -From ada9adf937ff19d9eaaf43980f88715e8543480c Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 3 Nov 2020 13:28:04 +0100 -Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface - devices - -The most recent firmware of the 88W8897 card reports a hardcoded LTR -value to the system during initialization, probably as an (unsuccessful) -attempt of the developers to fix firmware crashes. This LTR value -prevents most of the Microsoft Surface devices from entering deep -powersaving states (either platform C-State 10 or S0ix state), because -the exit latency of that state would be higher than what the card can -tolerate. - -Turns out the card works just the same (including the firmware crashes) -no matter if that hardcoded LTR value is reported or not, so it's kind -of useless and only prevents us from saving power. - -To get rid of those hardcoded LTR reports, it's possible to reset the -PCI bridge device after initializing the cards firmware. I'm not exactly -sure why that works, maybe the power management subsystem of the PCH -resets its stored LTR values when doing a function level reset of the -bridge device. Doing the reset once after starting the wifi firmware -works very well, probably because the firmware only reports that LTR -value a single time during firmware startup. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 31 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index ca6bcbe4794c..24bcd22a2618 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -1781,9 +1781,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) - static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; -+ struct pci_dev *pdev = card->dev; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; - int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; - -+ /* Trigger a function level reset of the PCI bridge device, this makes -+ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value -+ * that prevents the system from entering package C10 and S0ix powersaving -+ * states. -+ * We need to do it here because it must happen after firmware -+ * initialization and this function is called after that is done. -+ */ -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ pci_reset_function(parent_pdev); -+ - /* Write the RX ring read pointer in to reg->rx_rdptr */ - if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | - tx_wrap)) { -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 356401bab59c..6437f067d07a 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -39,7 +39,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5", -@@ -49,7 +50,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -59,7 +61,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 6", -@@ -68,7 +71,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 1", -@@ -77,7 +81,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 2", -@@ -86,7 +91,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 1", -@@ -95,7 +101,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 2", -@@ -104,7 +111,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface 3", -@@ -145,6 +153,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_NO_BRIDGE_D3) - dev_info(&pdev->dev, - "quirk no_brigde_d3 enabled\n"); -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index a1de111ad1db..0e429779bb04 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -24,6 +24,7 @@ - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) - #define QUIRK_NO_BRIDGE_D3 BIT(2) -+#define QUIRK_DO_FLR_ON_BRIDGE BIT(3) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.37.2 - -From 18ba817428297818c766791748efb3c54b588476 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 25 Mar 2021 11:33:02 +0100 -Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell - 88W8897 - -The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) -is used in a lot of Microsoft Surface devices, and all those devices -suffer from very low 2.4GHz wifi connection speeds while bluetooth is -enabled. The reason for that is that the default passive scanning -interval for Bluetooth Low Energy devices is quite high in Linux -(interval of 60 msec and scan window of 30 msec, see hci_core.c), and -the Marvell chip is known for its bad bt+wifi coexisting performance. - -So decrease that passive scan interval and make the scan window shorter -on this particular device to allow for spending more time transmitting -wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and -the new scan window is 6.25 msec (0xa * 0,625 msec). - -This change has a very large impact on the 2.4GHz wifi speeds and gets -it up to performance comparable with the Windows driver, which seems to -apply a similar quirk. - -The interval and window length were tested and found to work very well -with a lot of Bluetooth Low Energy devices, including the Surface Pen, a -Bluetooth Speaker and two modern Bluetooth headphones. All devices were -discovered immediately after turning them on. Even lower values were -also tested, but they introduced longer delays until devices get -discovered. - -Patchset: mwifiex ---- - drivers/bluetooth/btusb.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index d789c077d95d..feda593a8a83 100644 ---- a/drivers/bluetooth/btusb.c -+++ b/drivers/bluetooth/btusb.c -@@ -63,6 +63,7 @@ static struct usb_driver btusb_driver; - #define BTUSB_INTEL_BROKEN_SHUTDOWN_LED BIT(24) - #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) - #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) -+#define BTUSB_LOWER_LESCAN_INTERVAL BIT(27) - - static const struct usb_device_id btusb_table[] = { - /* Generic Bluetooth USB device */ -@@ -377,6 +378,7 @@ static const struct usb_device_id blacklist_table[] = { - { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, -+ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, - - /* Intel Bluetooth devices */ - { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, -@@ -3790,6 +3792,19 @@ static int btusb_probe(struct usb_interface *intf, - if (id->driver_info & BTUSB_MARVELL) - hdev->set_bdaddr = btusb_set_bdaddr_marvell; - -+ /* The Marvell 88W8897 combined wifi and bluetooth card is known for -+ * very bad bt+wifi coexisting performance. -+ * -+ * Decrease the passive BT Low Energy scan interval a bit -+ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter -+ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly -+ * higher wifi throughput while passively scanning for BT LE devices. -+ */ -+ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { -+ hdev->le_scan_interval = 0x0190; -+ hdev->le_scan_window = 0x000a; -+ } -+ - if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && - (id->driver_info & BTUSB_MEDIATEK)) { - hdev->setup = btusb_mtk_setup; --- -2.37.2 - -From 002a63959850db74750e3f7323ca39fd83ea9436 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 10 Nov 2020 12:49:56 +0100 -Subject: [PATCH] mwifiex: Use non-posted PCI register writes - -On the 88W8897 card it's very important the TX ring write pointer is -updated correctly to its new value before setting the TX ready -interrupt, otherwise the firmware appears to crash (probably because -it's trying to DMA-read from the wrong place). - -Since PCI uses "posted writes" when writing to a register, it's not -guaranteed that a write will happen immediately. That means the pointer -might be outdated when setting the TX ready interrupt, leading to -firmware crashes especially when ASPM L1 and L1 substates are enabled -(because of the higher link latency, the write will probably take -longer). - -So fix those firmware crashes by always forcing non-posted writes. We do -that by simply reading back the register after writing it, just as a lot -of other drivers do. - -There are two reproducers that are fixed with this patch: - -1) During rx/tx traffic and with ASPM L1 substates enabled (the enabled -substates are platform dependent), the firmware crashes and eventually a -command timeout appears in the logs. That crash is fixed by using a -non-posted write in mwifiex_pcie_send_data(). - -2) When sending lots of commands to the card, waking it up from sleep in -very quick intervals, the firmware eventually crashes. That crash -appears to be fixed by some other non-posted write included here. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 24bcd22a2618..b4ad0113a035 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -238,6 +238,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) - - iowrite32(data, card->pci_mmap1 + reg); - -+ /* Do a read-back, which makes the write non-posted, ensuring the -+ * completion before returning. -+ * The firmware of the 88W8897 card is buggy and this avoids crashes. -+ */ -+ ioread32(card->pci_mmap1 + reg); -+ - return 0; - } - --- -2.37.2 - diff --git a/patches/5.18/0003-ath10k.patch b/patches/5.18/0003-ath10k.patch deleted file mode 100644 index 9255fe4c6..000000000 --- a/patches/5.18/0003-ath10k.patch +++ /dev/null @@ -1,121 +0,0 @@ -From 3934eee36afb9f84434891352cb2c8a2b124d9a1 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 27 Feb 2021 00:45:52 +0100 -Subject: [PATCH] ath10k: Add module parameters to override board files - -Some Surface devices, specifically the Surface Go and AMD version of the -Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better -with a different board file, as it seems that the firmeware included -upstream is buggy. - -As it is generally not a good idea to randomly overwrite files, let -alone doing so via packages, we add module parameters to override those -file names in the driver. This allows us to package/deploy the override -via a modprobe.d config. - -Signed-off-by: Maximilian Luz -Patchset: ath10k ---- - drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++ - 1 file changed, 58 insertions(+) - -diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c -index 9e1f483e1362..34cfce241e4a 100644 ---- a/drivers/net/wireless/ath/ath10k/core.c -+++ b/drivers/net/wireless/ath/ath10k/core.c -@@ -36,6 +36,9 @@ static bool skip_otp; - static bool rawmode; - static bool fw_diag_log; - -+static char *override_board = ""; -+static char *override_board2 = ""; -+ - unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | - BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); - -@@ -48,6 +51,9 @@ module_param(rawmode, bool, 0644); - module_param(fw_diag_log, bool, 0644); - module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); - -+module_param(override_board, charp, 0644); -+module_param(override_board2, charp, 0644); -+ - MODULE_PARM_DESC(debug_mask, "Debugging mask"); - MODULE_PARM_DESC(uart_print, "Uart target debugging"); - MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); -@@ -56,6 +62,9 @@ MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); - MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); - MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); - -+MODULE_PARM_DESC(override_board, "Override for board.bin file"); -+MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); -+ - static const struct ath10k_hw_params ath10k_hw_params_list[] = { - { - .id = QCA988X_HW_2_0_VERSION, -@@ -860,6 +869,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) - return 0; - } - -+static const char *ath10k_override_board_fw_file(struct ath10k *ar, -+ const char *file) -+{ -+ if (strcmp(file, "board.bin") == 0) { -+ if (strcmp(override_board, "") == 0) -+ return file; -+ -+ if (strcmp(override_board, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", -+ override_board); -+ -+ return override_board; -+ } -+ -+ if (strcmp(file, "board-2.bin") == 0) { -+ if (strcmp(override_board2, "") == 0) -+ return file; -+ -+ if (strcmp(override_board2, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", -+ override_board2); -+ -+ return override_board2; -+ } -+ -+ return file; -+} -+ - static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - const char *dir, - const char *file) -@@ -874,6 +919,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - if (dir == NULL) - dir = "."; - -+ /* HACK: Override board.bin and board-2.bin files if specified. -+ * -+ * Some Surface devices perform better with a different board -+ * configuration. To this end, one would need to replace the board.bin -+ * file with the modified config and remove the board-2.bin file. -+ * Unfortunately, that's not a solution that we can easily package. So -+ * we add module options to perform these overrides here. -+ */ -+ -+ file = ath10k_override_board_fw_file(ar, file); -+ if (!file) -+ return ERR_PTR(-ENOENT); -+ - snprintf(filename, sizeof(filename), "%s/%s", dir, file); - ret = firmware_request_nowarn(&fw, filename, ar->dev); - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", --- -2.37.2 - diff --git a/patches/5.18/0004-ipts.patch b/patches/5.18/0004-ipts.patch deleted file mode 100644 index ec6c38abc..000000000 --- a/patches/5.18/0004-ipts.patch +++ /dev/null @@ -1,1603 +0,0 @@ -From 343bbe8435d603e410c1f2d2756757d39ca20295 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 30 Jul 2020 13:21:53 +0200 -Subject: [PATCH] misc: mei: Add missing IPTS device IDs - -Patchset: ipts ---- - drivers/misc/mei/hw-me-regs.h | 1 + - drivers/misc/mei/pci-me.c | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h -index 15e8e2b322b1..91587b808323 100644 ---- a/drivers/misc/mei/hw-me-regs.h -+++ b/drivers/misc/mei/hw-me-regs.h -@@ -92,6 +92,7 @@ - #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ - - #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ -+#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ - #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ - - #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ -diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c -index 5435604327a7..1165ee4f5928 100644 ---- a/drivers/misc/mei/pci-me.c -+++ b/drivers/misc/mei/pci-me.c -@@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { - {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, -+ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, --- -2.37.2 - -From c53f690f596e9009c18e1677337983b21b15f83e Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 6 Aug 2020 11:20:41 +0200 -Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus - -Based on linux-surface/intel-precise-touch@3f362c - -Signed-off-by: Dorian Stoll -Patchset: ipts ---- - drivers/misc/Kconfig | 1 + - drivers/misc/Makefile | 1 + - drivers/misc/ipts/Kconfig | 17 ++ - drivers/misc/ipts/Makefile | 12 ++ - drivers/misc/ipts/context.h | 47 +++++ - drivers/misc/ipts/control.c | 113 +++++++++++ - drivers/misc/ipts/control.h | 24 +++ - drivers/misc/ipts/mei.c | 125 ++++++++++++ - drivers/misc/ipts/protocol.h | 347 ++++++++++++++++++++++++++++++++++ - drivers/misc/ipts/receiver.c | 224 ++++++++++++++++++++++ - drivers/misc/ipts/receiver.h | 16 ++ - drivers/misc/ipts/resources.c | 128 +++++++++++++ - drivers/misc/ipts/resources.h | 17 ++ - drivers/misc/ipts/uapi.c | 208 ++++++++++++++++++++ - drivers/misc/ipts/uapi.h | 47 +++++ - 15 files changed, 1327 insertions(+) - create mode 100644 drivers/misc/ipts/Kconfig - create mode 100644 drivers/misc/ipts/Makefile - create mode 100644 drivers/misc/ipts/context.h - create mode 100644 drivers/misc/ipts/control.c - create mode 100644 drivers/misc/ipts/control.h - create mode 100644 drivers/misc/ipts/mei.c - create mode 100644 drivers/misc/ipts/protocol.h - create mode 100644 drivers/misc/ipts/receiver.c - create mode 100644 drivers/misc/ipts/receiver.h - create mode 100644 drivers/misc/ipts/resources.c - create mode 100644 drivers/misc/ipts/resources.h - create mode 100644 drivers/misc/ipts/uapi.c - create mode 100644 drivers/misc/ipts/uapi.h - -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index 41d2bb0ae23a..effb258d4848 100644 ---- a/drivers/misc/Kconfig -+++ b/drivers/misc/Kconfig -@@ -500,4 +500,5 @@ source "drivers/misc/cardreader/Kconfig" - source "drivers/misc/habanalabs/Kconfig" - source "drivers/misc/uacce/Kconfig" - source "drivers/misc/pvpanic/Kconfig" -+source "drivers/misc/ipts/Kconfig" - endmenu -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index 70e800e9127f..a8d1e9447025 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -60,3 +60,4 @@ obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o - obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o - obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o - obj-$(CONFIG_OPEN_DICE) += open-dice.o -+obj-$(CONFIG_MISC_IPTS) += ipts/ -diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig -new file mode 100644 -index 000000000000..83e2a930c396 ---- /dev/null -+++ b/drivers/misc/ipts/Kconfig -@@ -0,0 +1,17 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+ -+config MISC_IPTS -+ tristate "Intel Precise Touch & Stylus" -+ depends on INTEL_MEI -+ help -+ Say Y here if your system has a touchscreen using Intels -+ Precise Touch & Stylus (IPTS) technology. -+ -+ If unsure say N. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ipts. -+ -+ Building this driver alone will not give you a working touchscreen. -+ It only exposed a userspace API that can be used by a daemon to -+ receive and process data from the touchscreen hardware. -diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile -new file mode 100644 -index 000000000000..8f58b9adbc94 ---- /dev/null -+++ b/drivers/misc/ipts/Makefile -@@ -0,0 +1,12 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+# -+# Makefile for the IPTS touchscreen driver -+# -+ -+obj-$(CONFIG_MISC_IPTS) += ipts.o -+ipts-objs := control.o -+ipts-objs += mei.o -+ipts-objs += receiver.o -+ipts-objs += resources.o -+ipts-objs += uapi.o -+ -diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h -new file mode 100644 -index 000000000000..f4b06a2d3f72 ---- /dev/null -+++ b/drivers/misc/ipts/context.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTEXT_H_ -+#define _IPTS_CONTEXT_H_ -+ -+#include -+#include -+#include -+#include -+ -+#include "protocol.h" -+ -+enum ipts_host_status { -+ IPTS_HOST_STATUS_STARTING, -+ IPTS_HOST_STATUS_STARTED, -+ IPTS_HOST_STATUS_STOPPING, -+ IPTS_HOST_STATUS_STOPPED, -+}; -+ -+struct ipts_buffer_info { -+ u8 *address; -+ dma_addr_t dma_address; -+}; -+ -+struct ipts_context { -+ struct mei_cl_device *cldev; -+ struct device *dev; -+ -+ bool restart; -+ enum ipts_host_status status; -+ struct ipts_get_device_info_rsp device_info; -+ -+ struct ipts_buffer_info data[IPTS_BUFFERS]; -+ struct ipts_buffer_info doorbell; -+ -+ struct ipts_buffer_info feedback[IPTS_BUFFERS]; -+ struct ipts_buffer_info workqueue; -+ struct ipts_buffer_info host2me; -+}; -+ -+#endif /* _IPTS_CONTEXT_H_ */ -diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c -new file mode 100644 -index 000000000000..a1d1f97a13d7 ---- /dev/null -+++ b/drivers/misc/ipts/control.c -@@ -0,0 +1,113 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+#include "protocol.h" -+#include "resources.h" -+#include "uapi.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload, -+ size_t size) -+{ -+ int ret; -+ struct ipts_command cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_command)); -+ cmd.code = code; -+ -+ if (payload && size > 0) -+ memcpy(&cmd.payload, payload, size); -+ -+ ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size); -+ if (ret >= 0) -+ return 0; -+ -+ /* -+ * During shutdown the device might get pulled away from below our feet. -+ * Dont log an error in this case, because it will confuse people. -+ */ -+ if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING) -+ dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret); -+ -+ return ret; -+} -+ -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) -+{ -+ struct ipts_feedback_cmd cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); -+ cmd.buffer = buffer; -+ -+ return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, -+ sizeof(struct ipts_feedback_cmd)); -+} -+ -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value) -+{ -+ struct ipts_feedback_buffer *feedback; -+ -+ memset(ipts->host2me.address, 0, ipts->device_info.feedback_size); -+ feedback = (struct ipts_feedback_buffer *)ipts->host2me.address; -+ -+ feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; -+ feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; -+ feedback->buffer = IPTS_HOST2ME_BUFFER; -+ feedback->size = 2; -+ feedback->payload[0] = report; -+ feedback->payload[1] = value; -+ -+ return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER); -+} -+ -+int ipts_control_start(struct ipts_context *ipts) -+{ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Starting IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STARTING; -+ ipts->restart = false; -+ -+ ipts_uapi_link(ipts); -+ return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); -+} -+ -+int ipts_control_stop(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPING) -+ return -EBUSY; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Stopping IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STOPPING; -+ -+ ipts_uapi_unlink(); -+ ipts_resources_free(ipts); -+ -+ ret = ipts_control_send_feedback(ipts, 0); -+ if (ret == -ENODEV) -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ return ret; -+} -+ -+int ipts_control_restart(struct ipts_context *ipts) -+{ -+ if (ipts->restart) -+ return -EBUSY; -+ -+ ipts->restart = true; -+ return ipts_control_stop(ipts); -+} -diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h -new file mode 100644 -index 000000000000..2c44e9e0e99f ---- /dev/null -+++ b/drivers/misc/ipts/control.h -@@ -0,0 +1,24 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTROL_H_ -+#define _IPTS_CONTROL_H_ -+ -+#include -+ -+#include "context.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, -+ size_t size); -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value); -+int ipts_control_start(struct ipts_context *ipts); -+int ipts_control_restart(struct ipts_context *ipts); -+int ipts_control_stop(struct ipts_context *ipts); -+ -+#endif /* _IPTS_CONTROL_H_ */ -diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c -new file mode 100644 -index 000000000000..59ecf13e00d2 ---- /dev/null -+++ b/drivers/misc/ipts/mei.c -@@ -0,0 +1,125 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "receiver.h" -+#include "uapi.h" -+ -+static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev) -+{ -+ int ret; -+ -+ ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)); -+ if (!ret) -+ return 0; -+ -+ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); -+} -+ -+static int ipts_mei_probe(struct mei_cl_device *cldev, -+ const struct mei_cl_device_id *id) -+{ -+ int ret; -+ struct ipts_context *ipts; -+ -+ if (ipts_mei_set_dma_mask(cldev)) { -+ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n"); -+ return -EFAULT; -+ } -+ -+ ret = mei_cldev_enable(cldev); -+ if (ret) { -+ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); -+ return ret; -+ } -+ -+ ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); -+ if (!ipts) { -+ mei_cldev_disable(cldev); -+ return -ENOMEM; -+ } -+ -+ ipts->cldev = cldev; -+ ipts->dev = &cldev->dev; -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ mei_cldev_set_drvdata(cldev, ipts); -+ mei_cldev_register_rx_cb(cldev, ipts_receiver_callback); -+ -+ return ipts_control_start(ipts); -+} -+ -+static void ipts_mei_remove(struct mei_cl_device *cldev) -+{ -+ int i; -+ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); -+ -+ ipts_control_stop(ipts); -+ -+ for (i = 0; i < 20; i++) { -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ break; -+ -+ msleep(25); -+ } -+ -+ mei_cldev_disable(cldev); -+ kfree(ipts); -+} -+ -+static struct mei_cl_device_id ipts_mei_device_id_table[] = { -+ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, -+ {}, -+}; -+MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); -+ -+static struct mei_cl_driver ipts_mei_driver = { -+ .id_table = ipts_mei_device_id_table, -+ .name = "ipts", -+ .probe = ipts_mei_probe, -+ .remove = ipts_mei_remove, -+}; -+ -+static int __init ipts_mei_init(void) -+{ -+ int ret; -+ -+ ret = ipts_uapi_init(); -+ if (ret) -+ return ret; -+ -+ ret = mei_cldev_driver_register(&ipts_mei_driver); -+ if (ret) { -+ ipts_uapi_free(); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static void __exit ipts_mei_exit(void) -+{ -+ mei_cldev_driver_unregister(&ipts_mei_driver); -+ ipts_uapi_free(); -+} -+ -+MODULE_DESCRIPTION("IPTS touchscreen driver"); -+MODULE_AUTHOR("Dorian Stoll "); -+MODULE_LICENSE("GPL"); -+ -+module_init(ipts_mei_init); -+module_exit(ipts_mei_exit); -diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h -new file mode 100644 -index 000000000000..c3458904a94d ---- /dev/null -+++ b/drivers/misc/ipts/protocol.h -@@ -0,0 +1,347 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_PROTOCOL_H_ -+#define _IPTS_PROTOCOL_H_ -+ -+#include -+ -+/* -+ * The MEI client ID for IPTS functionality. -+ */ -+#define IPTS_MEI_UUID \ -+ UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, \ -+ 0x02, 0xae, 0x04) -+ -+/* -+ * Queries the device for vendor specific information. -+ * -+ * The command must not contain any payload. -+ * The response will contain struct ipts_get_device_info_rsp as payload. -+ */ -+#define IPTS_CMD_GET_DEVICE_INFO 0x00000001 -+#define IPTS_RSP_GET_DEVICE_INFO 0x80000001 -+ -+/* -+ * Sets the mode that IPTS will operate in. -+ * -+ * The command must contain struct ipts_set_mode_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MODE 0x00000002 -+#define IPTS_RSP_SET_MODE 0x80000002 -+ -+/* -+ * Configures the memory buffers that the ME will use -+ * for passing data to the host. -+ * -+ * The command must contain struct ipts_set_mem_window_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MEM_WINDOW 0x00000003 -+#define IPTS_RSP_SET_MEM_WINDOW 0x80000003 -+ -+/* -+ * Signals that the host is ready to receive data to the ME. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_READY_FOR_DATA 0x00000005 -+#define IPTS_RSP_READY_FOR_DATA 0x80000005 -+ -+/* -+ * Signals that a buffer can be refilled to the ME. -+ * -+ * The command must contain struct ipts_feedback_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_FEEDBACK 0x00000006 -+#define IPTS_RSP_FEEDBACK 0x80000006 -+ -+/* -+ * Resets the data flow from the ME to the hosts and -+ * clears the buffers that were set with SET_MEM_WINDOW. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007 -+#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007 -+ -+/* -+ * Instructs the ME to reset the touch sensor. -+ * -+ * The command must contain struct ipts_reset_sensor_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_RESET_SENSOR 0x0000000B -+#define IPTS_RSP_RESET_SENSOR 0x8000000B -+ -+/** -+ * enum ipts_status - Possible status codes returned by IPTS commands. -+ * @IPTS_STATUS_SUCCESS: Operation completed successfully. -+ * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. -+ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. -+ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. -+ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. -+ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. -+ * The host must wait for a response before sending another -+ * command of the same type. -+ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it -+ * has not been initialized yet, or the system is improperly -+ * configured. -+ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. -+ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. -+ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. -+ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. -+ * The host can ignore this error and attempt to continue. -+ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. -+ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. -+ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. -+ * @IPTS_STATUS_TIMEOUT: The operation timed out. -+ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. -+ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. -+ * Further progress is not possible. -+ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. -+ * The host can attempt to continue. -+ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. -+ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. -+ */ -+enum ipts_status { -+ IPTS_STATUS_SUCCESS = 0, -+ IPTS_STATUS_INVALID_PARAMS = 1, -+ IPTS_STATUS_ACCESS_DENIED = 2, -+ IPTS_STATUS_CMD_SIZE_ERROR = 3, -+ IPTS_STATUS_NOT_READY = 4, -+ IPTS_STATUS_REQUEST_OUTSTANDING = 5, -+ IPTS_STATUS_NO_SENSOR_FOUND = 6, -+ IPTS_STATUS_OUT_OF_MEMORY = 7, -+ IPTS_STATUS_INTERNAL_ERROR = 8, -+ IPTS_STATUS_SENSOR_DISABLED = 9, -+ IPTS_STATUS_COMPAT_CHECK_FAIL = 10, -+ IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, -+ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, -+ IPTS_STATUS_RESET_FAILED = 13, -+ IPTS_STATUS_TIMEOUT = 14, -+ IPTS_STATUS_TEST_MODE_FAIL = 15, -+ IPTS_STATUS_SENSOR_FAIL_FATAL = 16, -+ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, -+ IPTS_STATUS_INVALID_DEVICE_CAPS = 18, -+ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, -+}; -+ -+/* -+ * The amount of buffers that is used for IPTS -+ */ -+#define IPTS_BUFFERS 16 -+ -+/* -+ * The special buffer ID that is used for direct host2me feedback. -+ */ -+#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS -+ -+/** -+ * enum ipts_mode - Operation mode for IPTS hardware -+ * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. -+ * The data is received as a HID report with ID 64. -+ * @IPTS_MODE_MULTITOUCH: The "proper" operation mode for IPTS. It will return -+ * stylus data as well as capacitive heatmap touch data. -+ * This data needs to be processed in userspace. -+ */ -+enum ipts_mode { -+ IPTS_MODE_SINGLETOUCH = 0, -+ IPTS_MODE_MULTITOUCH = 1, -+}; -+ -+/** -+ * struct ipts_set_mode_cmd - Payload for the SET_MODE command. -+ * @mode: The mode that IPTS should operate in. -+ */ -+struct ipts_set_mode_cmd { -+ enum ipts_mode mode; -+ u8 reserved[12]; -+} __packed; -+ -+#define IPTS_WORKQUEUE_SIZE 8192 -+#define IPTS_WORKQUEUE_ITEM_SIZE 16 -+ -+/** -+ * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command. -+ * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses. -+ * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses. -+ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. -+ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. -+ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. -+ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. -+ * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses. -+ * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses. -+ * @host2me_addr_lower: Lower 32 bits of the host2me buffer address. -+ * @host2me_addr_upper: Upper 32 bits of the host2me buffer address. -+ * @workqueue_item_size: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) -+ * @workqueue_size: Magic value. (IPTS_WORKQUEUE_SIZE) -+ * -+ * The data buffers are buffers that get filled with touch data by the ME. -+ * The doorbell buffer is a u32 that gets incremented by the ME once a data -+ * buffer has been filled with new data. -+ * -+ * The other buffers are required for using GuC submission with binary -+ * firmware. Since support for GuC submission has been dropped from i915, -+ * they are not used anymore, but they need to be allocated and passed, -+ * otherwise the hardware will refuse to start. -+ */ -+struct ipts_set_mem_window_cmd { -+ u32 data_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 data_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 workqueue_addr_lower; -+ u32 workqueue_addr_upper; -+ u32 doorbell_addr_lower; -+ u32 doorbell_addr_upper; -+ u32 feedback_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 feedback_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 host2me_addr_lower; -+ u32 host2me_addr_upper; -+ u32 host2me_size; -+ u8 reserved1; -+ u8 workqueue_item_size; -+ u16 workqueue_size; -+ u8 reserved[32]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_cmd - Payload for the FEEDBACK command. -+ * @buffer: The buffer that the ME should refill. -+ */ -+struct ipts_feedback_cmd { -+ u32 buffer; -+ u8 reserved[12]; -+} __packed; -+ -+/** -+ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. -+ */ -+enum ipts_feedback_cmd_type { -+ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, -+ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, -+ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, -+}; -+ -+/** -+ * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains. -+ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. -+ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. -+ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. -+ */ -+enum ipts_feedback_data_type { -+ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, -+ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, -+ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, -+ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, -+ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, -+}; -+ -+/** -+ * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer. -+ * @cmd_type: A command that should be executed on the sensor. -+ * @size: The size of the payload to be written. -+ * @buffer: The ID of the buffer that contains this feedback data. -+ * @protocol: The protocol version of the EDS. -+ * @data_type: The type of payload that the buffer contains. -+ * @spi_offset: The offset at which to write the payload data. -+ * @payload: Payload for the feedback command, or 0 if no payload is sent. -+ */ -+struct ipts_feedback_buffer { -+ enum ipts_feedback_cmd_type cmd_type; -+ u32 size; -+ u32 buffer; -+ u32 protocol; -+ enum ipts_feedback_data_type data_type; -+ u32 spi_offset; -+ u8 reserved[40]; -+ u8 payload[]; -+} __packed; -+ -+/** -+ * enum ipts_reset_type - Possible ways of resetting the touch sensor -+ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. -+ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. -+ */ -+enum ipts_reset_type { -+ IPTS_RESET_TYPE_HARD = 0, -+ IPTS_RESET_TYPE_SOFT = 1, -+}; -+ -+/** -+ * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. -+ * @type: What type of reset should be performed. -+ */ -+struct ipts_reset_sensor_cmd { -+ enum ipts_reset_type type; -+ u8 reserved[4]; -+} __packed; -+ -+/** -+ * struct ipts_command - A message sent from the host to the ME. -+ * @code: The message code describing the command. (see IPTS_CMD_*) -+ * @payload: Payload for the command, or 0 if no payload is required. -+ */ -+struct ipts_command { -+ u32 code; -+ u8 payload[320]; -+} __packed; -+ -+/** -+ * struct ipts_device_info - Payload for the GET_DEVICE_INFO response. -+ * @vendor_id: Vendor ID of the touch sensor. -+ * @device_id: Device ID of the touch sensor. -+ * @hw_rev: Hardware revision of the touch sensor. -+ * @fw_rev: Firmware revision of the touch sensor. -+ * @data_size: Required size of one data buffer. -+ * @feedback_size: Required size of one feedback buffer. -+ * @mode: Current operation mode of IPTS. -+ * @max_contacts: The amount of concurrent touches supported by the sensor. -+ */ -+struct ipts_get_device_info_rsp { -+ u16 vendor_id; -+ u16 device_id; -+ u32 hw_rev; -+ u32 fw_rev; -+ u32 data_size; -+ u32 feedback_size; -+ enum ipts_mode mode; -+ u8 max_contacts; -+ u8 reserved[19]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_rsp - Payload for the FEEDBACK response. -+ * @buffer: The buffer that has received feedback. -+ */ -+struct ipts_feedback_rsp { -+ u32 buffer; -+} __packed; -+ -+/** -+ * struct ipts_response - A message sent from the ME to the host. -+ * @code: The message code describing the response. (see IPTS_RSP_*) -+ * @status: The status code returned by the command. -+ * @payload: Payload returned by the command. -+ */ -+struct ipts_response { -+ u32 code; -+ enum ipts_status status; -+ u8 payload[80]; -+} __packed; -+ -+#endif /* _IPTS_PROTOCOL_H_ */ -diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c -new file mode 100644 -index 000000000000..23dca13c2139 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.c -@@ -0,0 +1,224 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "resources.h" -+ -+/* -+ * Temporary parameter to guard gen7 multitouch mode. -+ * Remove once gen7 has stable iptsd support. -+ */ -+static bool gen7mt; -+module_param(gen7mt, bool, 0644); -+ -+static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_set_mode_cmd cmd; -+ -+ memcpy(&ipts->device_info, rsp->payload, -+ sizeof(struct ipts_get_device_info_rsp)); -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd)); -+ cmd.mode = IPTS_MODE_MULTITOUCH; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd, -+ sizeof(struct ipts_set_mode_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mode(struct ipts_context *ipts) -+{ -+ int i, ret; -+ struct ipts_set_mem_window_cmd cmd; -+ -+ ret = ipts_resources_alloc(ipts); -+ if (ret) { -+ dev_err(ipts->dev, "Failed to allocate resources\n"); -+ return ret; -+ } -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ cmd.data_buffer_addr_lower[i] = -+ lower_32_bits(ipts->data[i].dma_address); -+ -+ cmd.data_buffer_addr_upper[i] = -+ upper_32_bits(ipts->data[i].dma_address); -+ -+ cmd.feedback_buffer_addr_lower[i] = -+ lower_32_bits(ipts->feedback[i].dma_address); -+ -+ cmd.feedback_buffer_addr_upper[i] = -+ upper_32_bits(ipts->feedback[i].dma_address); -+ } -+ -+ cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); -+ cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); -+ -+ cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); -+ cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); -+ -+ cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); -+ cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); -+ -+ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; -+ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, -+ sizeof(struct ipts_set_mem_window_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ dev_info(ipts->dev, "Device %04hX:%04hX ready\n", -+ ipts->device_info.vendor_id, ipts->device_info.device_id); -+ ipts->status = IPTS_HOST_STATUS_STARTED; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); -+ if (ret) -+ return ret; -+ -+ if (!gen7mt) -+ return 0; -+ -+ return ipts_control_set_feature(ipts, 0x5, 0x1); -+} -+ -+static int ipts_receiver_handle_feedback(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_feedback_rsp feedback; -+ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPING) -+ return 0; -+ -+ memcpy(&feedback, rsp->payload, sizeof(feedback)); -+ -+ if (feedback.buffer < IPTS_BUFFERS - 1) -+ return ipts_control_send_feedback(ipts, feedback.buffer + 1); -+ -+ return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); -+} -+ -+static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) -+{ -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ if (ipts->restart) -+ return ipts_control_start(ipts); -+ -+ return 0; -+} -+ -+static bool ipts_receiver_sensor_was_reset(u32 status) -+{ -+ return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || -+ status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; -+} -+ -+static bool ipts_receiver_handle_error(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ bool error; -+ -+ switch (rsp->status) { -+ case IPTS_STATUS_SUCCESS: -+ case IPTS_STATUS_COMPAT_CHECK_FAIL: -+ error = false; -+ break; -+ case IPTS_STATUS_INVALID_PARAMS: -+ error = rsp->code != IPTS_RSP_FEEDBACK; -+ break; -+ case IPTS_STATUS_SENSOR_DISABLED: -+ error = ipts->status != IPTS_HOST_STATUS_STOPPING; -+ break; -+ default: -+ error = true; -+ break; -+ } -+ -+ if (!error) -+ return false; -+ -+ dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code, -+ rsp->status); -+ -+ if (ipts_receiver_sensor_was_reset(rsp->status)) { -+ dev_err(ipts->dev, "Sensor was reset\n"); -+ -+ if (ipts_control_restart(ipts)) -+ dev_err(ipts->dev, "Failed to restart IPTS\n"); -+ } -+ -+ return true; -+} -+ -+static void ipts_receiver_handle_response(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ int ret; -+ -+ if (ipts_receiver_handle_error(ipts, rsp)) -+ return; -+ -+ switch (rsp->code) { -+ case IPTS_RSP_GET_DEVICE_INFO: -+ ret = ipts_receiver_handle_get_device_info(ipts, rsp); -+ break; -+ case IPTS_RSP_SET_MODE: -+ ret = ipts_receiver_handle_set_mode(ipts); -+ break; -+ case IPTS_RSP_SET_MEM_WINDOW: -+ ret = ipts_receiver_handle_set_mem_window(ipts); -+ break; -+ case IPTS_RSP_FEEDBACK: -+ ret = ipts_receiver_handle_feedback(ipts, rsp); -+ break; -+ case IPTS_RSP_CLEAR_MEM_WINDOW: -+ ret = ipts_receiver_handle_clear_mem_window(ipts); -+ break; -+ default: -+ ret = 0; -+ break; -+ } -+ -+ if (!ret) -+ return; -+ -+ dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n", -+ rsp->code, ret); -+ -+ if (ipts_control_stop(ipts)) -+ dev_err(ipts->dev, "Failed to stop IPTS\n"); -+} -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev) -+{ -+ int ret; -+ struct ipts_response rsp; -+ struct ipts_context *ipts; -+ -+ ipts = mei_cldev_get_drvdata(cldev); -+ -+ ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response)); -+ if (ret <= 0) { -+ dev_err(ipts->dev, "Error while reading response: %d\n", ret); -+ return; -+ } -+ -+ ipts_receiver_handle_response(ipts, &rsp); -+} -diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h -new file mode 100644 -index 000000000000..7f075afa7ef8 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.h -@@ -0,0 +1,16 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RECEIVER_H_ -+#define _IPTS_RECEIVER_H_ -+ -+#include -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev); -+ -+#endif /* _IPTS_RECEIVER_H_ */ -diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c -new file mode 100644 -index 000000000000..8e3a2409e438 ---- /dev/null -+++ b/drivers/misc/ipts/resources.c -@@ -0,0 +1,128 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+ -+void ipts_resources_free(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, data_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ if (ipts->doorbell.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->doorbell.address, -+ ipts->doorbell.dma_address); -+ -+ ipts->doorbell.address = NULL; -+ ipts->doorbell.dma_address = 0; -+ } -+ -+ if (ipts->workqueue.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->workqueue.address, -+ ipts->workqueue.dma_address); -+ -+ ipts->workqueue.address = NULL; -+ ipts->workqueue.dma_address = 0; -+ } -+ -+ if (ipts->host2me.address) { -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ ipts->host2me.address, -+ ipts->host2me.dma_address); -+ -+ ipts->host2me.address = NULL; -+ ipts->host2me.dma_address = 0; -+ } -+} -+ -+int ipts_resources_alloc(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, data_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ ipts->doorbell.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->doorbell.dma_address, GFP_KERNEL); -+ -+ if (!ipts->doorbell.address) -+ goto release_resources; -+ -+ ipts->workqueue.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->workqueue.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ ipts->host2me.address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &ipts->host2me.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ return 0; -+ -+release_resources: -+ -+ ipts_resources_free(ipts); -+ return -ENOMEM; -+} -diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h -new file mode 100644 -index 000000000000..fdac0eee9156 ---- /dev/null -+++ b/drivers/misc/ipts/resources.h -@@ -0,0 +1,17 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RESOURCES_H_ -+#define _IPTS_RESOURCES_H_ -+ -+#include "context.h" -+ -+int ipts_resources_alloc(struct ipts_context *ipts); -+void ipts_resources_free(struct ipts_context *ipts); -+ -+#endif /* _IPTS_RESOURCES_H_ */ -diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c -new file mode 100644 -index 000000000000..598f0710ad64 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.c -@@ -0,0 +1,208 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "uapi.h" -+ -+struct ipts_uapi uapi; -+ -+static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count, -+ loff_t *offset) -+{ -+ int buffer; -+ int maxbytes; -+ struct ipts_context *ipts = uapi.ipts; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ maxbytes = ipts->device_info.data_size - *offset; -+ if (maxbytes <= 0 || count > maxbytes) -+ return -EINVAL; -+ -+ if (copy_to_user(buf, ipts->data[buffer].address + *offset, count)) -+ return -EFAULT; -+ -+ return count; -+} -+ -+static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ u8 ready = 0; -+ -+ if (ipts) -+ ready = ipts->status == IPTS_HOST_STATUS_STARTED; -+ -+ if (copy_to_user(buffer, &ready, sizeof(u8))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ struct ipts_device_info info; -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ info.vendor = ipts->device_info.vendor_id; -+ info.product = ipts->device_info.device_id; -+ info.version = ipts->device_info.fw_rev; -+ info.buffer_size = ipts->device_info.data_size; -+ info.max_contacts = ipts->device_info.max_contacts; -+ -+ if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts, -+ struct file *file) -+{ -+ int ret; -+ u32 buffer; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ ret = ipts_control_send_feedback(ipts, buffer); -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) -+{ -+ int ret; -+ struct ipts_reset_sensor_cmd cmd; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); -+ cmd.type = IPTS_RESET_TYPE_SOFT; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, -+ sizeof(struct ipts_reset_sensor_cmd)); -+ -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) -+{ -+ struct ipts_context *ipts = uapi.ipts; -+ -+ switch (cmd) { -+ case IPTS_IOCTL_GET_DEVICE_READY: -+ return ipts_uapi_ioctl_get_device_ready(ipts, arg); -+ case IPTS_IOCTL_GET_DEVICE_INFO: -+ return ipts_uapi_ioctl_get_device_info(ipts, arg); -+ case IPTS_IOCTL_GET_DOORBELL: -+ return ipts_uapi_ioctl_get_doorbell(ipts, arg); -+ case IPTS_IOCTL_SEND_FEEDBACK: -+ return ipts_uapi_ioctl_send_feedback(ipts, file); -+ case IPTS_IOCTL_SEND_RESET: -+ return ipts_uapi_ioctl_send_reset(ipts); -+ default: -+ return -ENOTTY; -+ } -+} -+ -+static const struct file_operations ipts_uapi_fops = { -+ .owner = THIS_MODULE, -+ .read = ipts_uapi_read, -+ .unlocked_ioctl = ipts_uapi_ioctl, -+#ifdef CONFIG_COMPAT -+ .compat_ioctl = ipts_uapi_ioctl, -+#endif -+}; -+ -+void ipts_uapi_link(struct ipts_context *ipts) -+{ -+ uapi.ipts = ipts; -+} -+ -+void ipts_uapi_unlink(void) -+{ -+ uapi.ipts = NULL; -+} -+ -+int ipts_uapi_init(void) -+{ -+ int i, major; -+ -+ alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts"); -+ uapi.class = class_create(THIS_MODULE, "ipts"); -+ -+ major = MAJOR(uapi.dev); -+ -+ cdev_init(&uapi.cdev, &ipts_uapi_fops); -+ uapi.cdev.owner = THIS_MODULE; -+ cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ device_create(uapi.class, NULL, MKDEV(major, i), NULL, -+ "ipts/%d", i); -+ } -+ -+ return 0; -+} -+ -+void ipts_uapi_free(void) -+{ -+ int i; -+ int major; -+ -+ major = MAJOR(uapi.dev); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) -+ device_destroy(uapi.class, MKDEV(major, i)); -+ -+ cdev_del(&uapi.cdev); -+ -+ unregister_chrdev_region(MKDEV(major, 0), MINORMASK); -+ class_destroy(uapi.class); -+} -diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h -new file mode 100644 -index 000000000000..53fb86a88f97 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_UAPI_H_ -+#define _IPTS_UAPI_H_ -+ -+#include -+ -+#include "context.h" -+ -+struct ipts_uapi { -+ dev_t dev; -+ struct class *class; -+ struct cdev cdev; -+ -+ struct ipts_context *ipts; -+}; -+ -+struct ipts_device_info { -+ __u16 vendor; -+ __u16 product; -+ __u32 version; -+ __u32 buffer_size; -+ __u8 max_contacts; -+ -+ /* For future expansion */ -+ __u8 reserved[19]; -+}; -+ -+#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8) -+#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info) -+#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) -+#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04) -+#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) -+ -+void ipts_uapi_link(struct ipts_context *ipts); -+void ipts_uapi_unlink(void); -+ -+int ipts_uapi_init(void); -+void ipts_uapi_free(void); -+ -+#endif /* _IPTS_UAPI_H_ */ --- -2.37.2 - -From ba3d78eac41ed4d6be1de1f235e97bd932864162 Mon Sep 17 00:00:00 2001 -From: Liban Hannan -Date: Tue, 12 Apr 2022 23:31:12 +0100 -Subject: [PATCH] iommu: ipts: use IOMMU passthrough mode for IPTS - -Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. -Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: - -DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr -0x104ea3000 [fault reason 0x06] PTE Read access is not set - -This is very similar to the bug described at: -https://bugs.launchpad.net/bugs/1958004 - -Fixed with the following patch which this patch basically copies: -https://launchpadlibrarian.net/586396847/43255ca.diff -Patchset: ipts ---- - drivers/iommu/intel/iommu.c | 24 ++++++++++++++++++++++++ - 1 file changed, 24 insertions(+) - -diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c -index c7ec5177cf78..7f3699e19270 100644 ---- a/drivers/iommu/intel/iommu.c -+++ b/drivers/iommu/intel/iommu.c -@@ -37,6 +37,8 @@ - #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) - #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) - #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) -+#define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ -+ ((pdev)->device == 0x9d3e)) - #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) - - #define IOAPIC_RANGE_START (0xfee00000) -@@ -307,12 +309,14 @@ int intel_iommu_enabled = 0; - EXPORT_SYMBOL_GPL(intel_iommu_enabled); - - static int dmar_map_gfx = 1; -+static int dmar_map_ipts = 1; - static int intel_iommu_superpage = 1; - static int iommu_identity_mapping; - static int iommu_skip_te_disable; - - #define IDENTMAP_GFX 2 - #define IDENTMAP_AZALIA 4 -+#define IDENTMAP_IPTS 16 - - int intel_iommu_gfx_mapped; - EXPORT_SYMBOL_GPL(intel_iommu_gfx_mapped); -@@ -2700,6 +2704,9 @@ static int device_def_domain_type(struct device *dev) - - if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) - return IOMMU_DOMAIN_IDENTITY; -+ -+ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) -+ return IOMMU_DOMAIN_IDENTITY; - } - - return 0; -@@ -3136,6 +3143,9 @@ static int __init init_dmars(void) - if (!dmar_map_gfx) - iommu_identity_mapping |= IDENTMAP_GFX; - -+ if (!dmar_map_ipts) -+ iommu_identity_mapping |= IDENTMAP_IPTS; -+ - check_tylersburg_isoch(); - - ret = si_domain_init(hw_pass_through); -@@ -4907,6 +4917,17 @@ static void quirk_iommu_igfx(struct pci_dev *dev) - dmar_map_gfx = 0; - } - -+static void quirk_iommu_ipts(struct pci_dev *dev) -+{ -+ if (!IS_IPTS(dev)) -+ return; -+ -+ if (risky_device(dev)) -+ return; -+ -+ pci_info(dev, "Passthrough IOMMU for IPTS\n"); -+ dmar_map_ipts = 0; -+} - /* G4x/GM45 integrated gfx dmar support is totally busted. */ - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); -@@ -4942,6 +4963,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); - -+/* disable IPTS dmar support */ -+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); -+ - static void quirk_iommu_rwbf(struct pci_dev *dev) - { - if (risky_device(dev)) --- -2.37.2 - diff --git a/patches/5.18/0005-surface-sam.patch b/patches/5.18/0005-surface-sam.patch deleted file mode 100644 index 20ba5a42a..000000000 --- a/patches/5.18/0005-surface-sam.patch +++ /dev/null @@ -1,4445 +0,0 @@ -From 17f1e21d9b782aa7d25a446b3663b72c055fd7d7 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:36 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow is_ssam_device() to be - used when CONFIG_SURFACE_AGGREGATOR_BUS is disabled - -In SSAM subsystem drivers that handle both ACPI and SSAM-native client -devices, we may want to check whether we have a SSAM (native) client -device. Further, we may want to do this even when instantiation thereof -cannot happen due to CONFIG_SURFACE_AGGREGATOR_BUS=n. Currently, doing -so causes an error due to an undefined reference error due to -ssam_device_type being placed in the bus source unit. - -Therefore, if CONFIG_SURFACE_AGGREGATOR_BUS is not defined, simply let -is_ssam_device() return false to prevent this error. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-2-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - include/linux/surface_aggregator/device.h | 11 +++++++++++ - 1 file changed, 11 insertions(+) - -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index cc257097eb05..62b38b4487eb 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -177,6 +177,8 @@ struct ssam_device_driver { - void (*remove)(struct ssam_device *sdev); - }; - -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+ - extern struct bus_type ssam_bus_type; - extern const struct device_type ssam_device_type; - -@@ -193,6 +195,15 @@ static inline bool is_ssam_device(struct device *d) - return d->type == &ssam_device_type; - } - -+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+static inline bool is_ssam_device(struct device *d) -+{ -+ return false; -+} -+ -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ - /** - * to_ssam_device() - Casts the given device to a SSAM client device. - * @d: The device to cast. --- -2.37.2 - -From 64ea57d5ed105ab1a046ed0ca5f0ad3f29470101 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:37 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow devices to be marked as - hot-removed - -Some SSAM devices, notably the keyboard cover (keyboard and touchpad) on -the Surface Pro 8, can be hot-removed. When this occurs, communication -with the device may fail and time out. This timeout can unnecessarily -block and slow down device removal and even cause issues when the -devices are detached and re-attached quickly. Thus, communication should -generally be avoided once hot-removal is detected. - -While we already remove a device as soon as we detect its (hot-)removal, -the corresponding device driver may still attempt to communicate with -the device during teardown. This is especially critical as communication -failure may also extend to disabling of events, which is typically done -at that stage. - -Add a flag to allow marking devices as hot-removed. This can then be -used during client driver teardown to check if any communication -attempts should be avoided. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-3-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - include/linux/surface_aggregator/device.h | 48 +++++++++++++++++++++-- - 1 file changed, 45 insertions(+), 3 deletions(-) - -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 62b38b4487eb..6df7c8d4e50e 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -148,17 +148,30 @@ struct ssam_device_uid { - #define SSAM_SDEV(cat, tid, iid, fun) \ - SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) - -+/* -+ * enum ssam_device_flags - Flags for SSAM client devices. -+ * @SSAM_DEVICE_HOT_REMOVED_BIT: -+ * The device has been hot-removed. Further communication with it may time -+ * out and should be avoided. -+ */ -+enum ssam_device_flags { -+ SSAM_DEVICE_HOT_REMOVED_BIT = 0, -+}; -+ - /** - * struct ssam_device - SSAM client device. -- * @dev: Driver model representation of the device. -- * @ctrl: SSAM controller managing this device. -- * @uid: UID identifying the device. -+ * @dev: Driver model representation of the device. -+ * @ctrl: SSAM controller managing this device. -+ * @uid: UID identifying the device. -+ * @flags: Device state flags, see &enum ssam_device_flags. - */ - struct ssam_device { - struct device dev; - struct ssam_controller *ctrl; - - struct ssam_device_uid uid; -+ -+ unsigned long flags; - }; - - /** -@@ -251,6 +264,35 @@ struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, - int ssam_device_add(struct ssam_device *sdev); - void ssam_device_remove(struct ssam_device *sdev); - -+/** -+ * ssam_device_mark_hot_removed() - Mark the given device as hot-removed. -+ * @sdev: The device to mark as hot-removed. -+ * -+ * Mark the device as having been hot-removed. This signals drivers using the -+ * device that communication with the device should be avoided and may lead to -+ * timeouts. -+ */ -+static inline void ssam_device_mark_hot_removed(struct ssam_device *sdev) -+{ -+ dev_dbg(&sdev->dev, "marking device as hot-removed\n"); -+ set_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); -+} -+ -+/** -+ * ssam_device_is_hot_removed() - Check if the given device has been -+ * hot-removed. -+ * @sdev: The device to check. -+ * -+ * Checks if the given device has been marked as hot-removed. See -+ * ssam_device_mark_hot_removed() for more details. -+ * -+ * Return: Returns ``true`` if the device has been marked as hot-removed. -+ */ -+static inline bool ssam_device_is_hot_removed(struct ssam_device *sdev) -+{ -+ return test_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); -+} -+ - /** - * ssam_device_get() - Increment reference count of SSAM client device. - * @sdev: The device to increment the reference count of. --- -2.37.2 - -From 66750927a15616256b0e57fad5952d857ceea0cc Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:38 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow notifiers to avoid - communication on unregistering - -When SSAM client devices have been (physically) hot-removed, -communication attempts with those devices may fail and time out. This -can even extend to event notifiers, due to which timeouts may occur -during device removal, slowing down that process. - -Add a parameter to the notifier unregister function that allows skipping -communication with the EC to prevent this. Furthermore, add wrappers for -registering and unregistering notifiers belonging to SSAM client devices -that automatically check if the device has been marked as hot-removed -and communication should be avoided. - -Note that non-SSAM client devices can generally not be hot-removed, so -also add a convenience wrapper for those, defaulting to allow -communication. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-4-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../driver-api/surface_aggregator/client.rst | 6 +- - .../platform/surface/aggregator/controller.c | 53 ++++++++++----- - include/linux/surface_aggregator/controller.h | 24 ++++++- - include/linux/surface_aggregator/device.h | 66 +++++++++++++++++++ - 4 files changed, 128 insertions(+), 21 deletions(-) - -diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst -index e519d374c378..27f95abdbe99 100644 ---- a/Documentation/driver-api/surface_aggregator/client.rst -+++ b/Documentation/driver-api/surface_aggregator/client.rst -@@ -17,6 +17,8 @@ - .. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` - .. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` - .. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` -+.. |ssam_device_notifier_register| replace:: :c:func:`ssam_device_notifier_register` -+.. |ssam_device_notifier_unregister| replace:: :c:func:`ssam_device_notifier_unregister` - .. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` - .. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` - -@@ -312,7 +314,9 @@ Handling Events - To receive events from the SAM EC, an event notifier must be registered for - the desired event via |ssam_notifier_register|. The notifier must be - unregistered via |ssam_notifier_unregister| once it is not required any --more. -+more. For |ssam_device| type clients, the |ssam_device_notifier_register| and -+|ssam_device_notifier_unregister| wrappers should be preferred as they properly -+handle hot-removal of client devices. - - Event notifiers are registered by providing (at minimum) a callback to call - in case an event has been received, the registry specifying how the event -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index b8c377b3f932..6de834b52b63 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2199,16 +2199,26 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, - } - - /** -- * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is -- * no longer in use and free the corresponding entry. -+ * ssam_nf_refcount_disable_free() - Disable event for reference count entry if -+ * it is no longer in use and free the corresponding entry. - * @ctrl: The controller to disable the event on. - * @entry: The reference count entry for the event to be disabled. - * @flags: The flags used for enabling the event on the EC. -+ * @ec: Flag specifying if the event should actually be disabled on the EC. - * -- * If the reference count equals zero, i.e. the event is no longer requested by -- * any client, the event will be disabled and the corresponding reference count -- * entry freed. The reference count entry must not be used any more after a -- * call to this function. -+ * If ``ec`` equals ``true`` and the reference count equals zero (i.e. the -+ * event is no longer requested by any client), the specified event will be -+ * disabled on the EC via the corresponding request. -+ * -+ * If ``ec`` equals ``false``, no request will be sent to the EC and the event -+ * can be considered in a detached state (i.e. no longer used but still -+ * enabled). Disabling an event via this method may be required for -+ * hot-removable devices, where event disable requests may time out after the -+ * device has been physically removed. -+ * -+ * In both cases, if the reference count equals zero, the corresponding -+ * reference count entry will be freed. The reference count entry must not be -+ * used any more after a call to this function. - * - * Also checks if the flags used for disabling the event match the flags used - * for enabling the event and warns if they do not (regardless of reference -@@ -2223,7 +2233,7 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, - * returns the status of the event-enable EC command. - */ - static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, -- struct ssam_nf_refcount_entry *entry, u8 flags) -+ struct ssam_nf_refcount_entry *entry, u8 flags, bool ec) - { - const struct ssam_event_registry reg = entry->key.reg; - const struct ssam_event_id id = entry->key.id; -@@ -2232,8 +2242,9 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - - lockdep_assert_held(&nf->lock); - -- ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -- reg.target_category, id.target_category, id.instance, entry->refcount); -+ ssam_dbg(ctrl, "%s event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ ec ? "disabling" : "detaching", reg.target_category, id.target_category, -+ id.instance, entry->refcount); - - if (entry->flags != flags) { - ssam_warn(ctrl, -@@ -2242,7 +2253,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - id.instance); - } - -- if (entry->refcount == 0) { -+ if (ec && entry->refcount == 0) { - status = ssam_ssh_event_disable(ctrl, reg, id, flags); - kfree(entry); - } -@@ -2322,20 +2333,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif - EXPORT_SYMBOL_GPL(ssam_notifier_register); - - /** -- * ssam_notifier_unregister() - Unregister an event notifier. -- * @ctrl: The controller the notifier has been registered on. -- * @n: The event notifier to unregister. -+ * __ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * @disable: Whether to disable the corresponding event on the EC. - * - * Unregister an event notifier. Decrement the usage counter of the associated - * SAM event if the notifier is not marked as an observer. If the usage counter -- * reaches zero, the event will be disabled. -+ * reaches zero and ``disable`` equals ``true``, the event will be disabled. -+ * -+ * Useful for hot-removable devices, where communication may fail once the -+ * device has been physically removed. In that case, specifying ``disable`` as -+ * ``false`` avoids communication with the EC. - * - * Return: Returns zero on success, %-ENOENT if the given notifier block has - * not been registered on the controller. If the given notifier block was the - * last one associated with its specific event, returns the status of the - * event-disable EC-command. - */ --int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) -+int __ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n, -+ bool disable) - { - u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); - struct ssam_nf_refcount_entry *entry; -@@ -2373,7 +2390,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - goto remove; - } - -- status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); -+ status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags, disable); - } - - remove: -@@ -2383,7 +2400,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - - return status; - } --EXPORT_SYMBOL_GPL(ssam_notifier_unregister); -+EXPORT_SYMBOL_GPL(__ssam_notifier_unregister); - - /** - * ssam_controller_event_enable() - Enable the specified event. -@@ -2477,7 +2494,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, - return -ENOENT; - } - -- status = ssam_nf_refcount_disable_free(ctrl, entry, flags); -+ status = ssam_nf_refcount_disable_free(ctrl, entry, flags, true); - - mutex_unlock(&nf->lock); - return status; -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index 74bfdffaf7b0..50a2b4926c06 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -835,8 +835,28 @@ struct ssam_event_notifier { - int ssam_notifier_register(struct ssam_controller *ctrl, - struct ssam_event_notifier *n); - --int ssam_notifier_unregister(struct ssam_controller *ctrl, -- struct ssam_event_notifier *n); -+int __ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n, bool disable); -+ -+/** -+ * ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+static inline int ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n) -+{ -+ return __ssam_notifier_unregister(ctrl, n, true); -+} - - int ssam_controller_event_enable(struct ssam_controller *ctrl, - struct ssam_event_registry reg, -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 6df7c8d4e50e..c418f7f2732d 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -483,4 +483,70 @@ static inline void ssam_remove_clients(struct device *dev) {} - sdev->uid.instance, ret); \ - } - -+ -+/* -- Helpers for client-device notifiers. ---------------------------------- */ -+ -+/** -+ * ssam_device_notifier_register() - Register an event notifier for the -+ * specified client device. -+ * @sdev: The device the notifier should be registered on. -+ * @n: The event notifier to register. -+ * -+ * Register an event notifier. Increment the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the event is not -+ * marked as an observer and is currently not enabled, it will be enabled -+ * during this call. If the notifier is marked as an observer, no attempt will -+ * be made at enabling any event and no reference count will be modified. -+ * -+ * Notifiers marked as observers do not need to be associated with one specific -+ * event, i.e. as long as no event matching is performed, only the event target -+ * category needs to be set. -+ * -+ * Return: Returns zero on success, %-ENOSPC if there have already been -+ * %INT_MAX notifiers for the event ID/type associated with the notifier block -+ * registered, %-ENOMEM if the corresponding event entry could not be -+ * allocated, %-ENODEV if the device is marked as hot-removed. If this is the -+ * first time that a notifier block is registered for the specific associated -+ * event, returns the status of the event-enable EC-command. -+ */ -+static inline int ssam_device_notifier_register(struct ssam_device *sdev, -+ struct ssam_event_notifier *n) -+{ -+ /* -+ * Note that this check does not provide any guarantees whatsoever as -+ * hot-removal could happen at any point and we can't protect against -+ * it. Nevertheless, if we can detect hot-removal, bail early to avoid -+ * communication timeouts. -+ */ -+ if (ssam_device_is_hot_removed(sdev)) -+ return -ENODEV; -+ -+ return ssam_notifier_register(sdev->ctrl, n); -+} -+ -+/** -+ * ssam_device_notifier_unregister() - Unregister an event notifier for the -+ * specified client device. -+ * @sdev: The device the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. -+ * -+ * In case the device has been marked as hot-removed, the event will not be -+ * disabled on the EC, as in those cases any attempt at doing so may time out. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+static inline int ssam_device_notifier_unregister(struct ssam_device *sdev, -+ struct ssam_event_notifier *n) -+{ -+ return __ssam_notifier_unregister(sdev->ctrl, n, -+ !ssam_device_is_hot_removed(sdev)); -+} -+ - #endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ --- -2.37.2 - -From 6418fed20ac4e1540a8747b3799f48b69ec86c8c Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:39 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Use client device - wrappers for notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-5-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index ce2bd88feeaa..9f630e890ff7 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -468,7 +468,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - - ssam_device_set_drvdata(sdev, hub); - -- status = ssam_notifier_register(sdev->ctrl, &hub->notif); -+ status = ssam_device_notifier_register(sdev, &hub->notif); - if (status) - return status; - -@@ -480,7 +480,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - return 0; - - err: -- ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_device_notifier_unregister(sdev, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); - return status; -@@ -492,7 +492,7 @@ static void ssam_base_hub_remove(struct ssam_device *sdev) - - sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); - -- ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_device_notifier_unregister(sdev, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); - } --- -2.37.2 - -From 7c680b78726f4dcfb1d2714d55d68fc6ea4ac0b8 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:40 +0200 -Subject: [PATCH] power/supply: surface_charger: Use client device wrappers for - notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Acked-by: Sebastian Reichel -Link: https://lore.kernel.org/r/20220527023447.2460025-6-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/power/supply/surface_charger.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -index a060c36c7766..59182d55742d 100644 ---- a/drivers/power/supply/surface_charger.c -+++ b/drivers/power/supply/surface_charger.c -@@ -216,7 +216,7 @@ static int spwr_ac_register(struct spwr_ac_device *ac) - if (IS_ERR(ac->psy)) - return PTR_ERR(ac->psy); - -- return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); -+ return ssam_device_notifier_register(ac->sdev, &ac->notif); - } - - -@@ -251,7 +251,7 @@ static void surface_ac_remove(struct ssam_device *sdev) - { - struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); - -- ssam_notifier_unregister(sdev->ctrl, &ac->notif); -+ ssam_device_notifier_unregister(sdev, &ac->notif); - } - - static const struct spwr_psy_properties spwr_psy_props_adp1 = { --- -2.37.2 - -From c4a286e4b15803af7d9b7e893ba3ce86d785ad86 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:41 +0200 -Subject: [PATCH] power/supply: surface_battery: Use client device wrappers for - notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Acked-by: Sebastian Reichel -Link: https://lore.kernel.org/r/20220527023447.2460025-7-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/power/supply/surface_battery.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -index 5ec2e6bb2465..540707882bb0 100644 ---- a/drivers/power/supply/surface_battery.c -+++ b/drivers/power/supply/surface_battery.c -@@ -802,7 +802,7 @@ static int spwr_battery_register(struct spwr_battery_device *bat) - if (IS_ERR(bat->psy)) - return PTR_ERR(bat->psy); - -- return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); -+ return ssam_device_notifier_register(bat->sdev, &bat->notif); - } - - -@@ -837,7 +837,7 @@ static void surface_battery_remove(struct ssam_device *sdev) - { - struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); - -- ssam_notifier_unregister(sdev->ctrl, &bat->notif); -+ ssam_device_notifier_unregister(sdev, &bat->notif); - cancel_delayed_work_sync(&bat->update_work); - } - --- -2.37.2 - -From a76ab65a0845e951c6614ff9ab78ced2488e49b1 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:42 +0200 -Subject: [PATCH] HID: surface-hid: Add support for hot-removal - -Add support for hot-removal of SSAM HID client devices. - -Once a device has been hot-removed, further communication with it should -be avoided as it may fail and time out. While the device will be removed -as soon as we detect hot-removal, communication may still occur during -teardown, especially when unregistering notifiers. - -While hot-removal is a surprise event that can happen at any time, try -to avoid communication as much as possible once it has been detected to -prevent timeouts that can slow down device removal and cause issues, -e.g. when quickly re-attaching the device. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-8-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/hid/surface-hid/surface_hid_core.c | 38 +++++++++++++++++++++- - 1 file changed, 37 insertions(+), 1 deletion(-) - -diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c -index e46330b2e561..87637f813de2 100644 ---- a/drivers/hid/surface-hid/surface_hid_core.c -+++ b/drivers/hid/surface-hid/surface_hid_core.c -@@ -19,12 +19,30 @@ - #include "surface_hid_core.h" - - -+/* -- Utility functions. ---------------------------------------------------- */ -+ -+static bool surface_hid_is_hot_removed(struct surface_hid_device *shid) -+{ -+ /* -+ * Non-ssam client devices, i.e. platform client devices, cannot be -+ * hot-removed. -+ */ -+ if (!is_ssam_device(shid->dev)) -+ return false; -+ -+ return ssam_device_is_hot_removed(to_ssam_device(shid->dev)); -+} -+ -+ - /* -- Device descriptor access. --------------------------------------------- */ - - static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) - { - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, - (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); - if (status) -@@ -61,6 +79,9 @@ static int surface_hid_load_device_attributes(struct surface_hid_device *shid) - { - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, - (u8 *)&shid->attrs, sizeof(shid->attrs)); - if (status) -@@ -88,9 +109,18 @@ static int surface_hid_start(struct hid_device *hid) - static void surface_hid_stop(struct hid_device *hid) - { - struct surface_hid_device *shid = hid->driver_data; -+ bool hot_removed; -+ -+ /* -+ * Communication may fail for devices that have been hot-removed. This -+ * also includes unregistration of HID events, so we need to check this -+ * here. Only if the device has not been marked as hot-removed, we can -+ * safely disable events. -+ */ -+ hot_removed = surface_hid_is_hot_removed(shid); - - /* Note: This call will log errors for us, so ignore them here. */ -- ssam_notifier_unregister(shid->ctrl, &shid->notif); -+ __ssam_notifier_unregister(shid->ctrl, &shid->notif, !hot_removed); - } - - static int surface_hid_open(struct hid_device *hid) -@@ -109,6 +139,9 @@ static int surface_hid_parse(struct hid_device *hid) - u8 *buf; - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - buf = kzalloc(len, GFP_KERNEL); - if (!buf) - return -ENOMEM; -@@ -126,6 +159,9 @@ static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportn - { - struct surface_hid_device *shid = hid->driver_data; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) - return shid->ops.output_report(shid, reportnum, buf, len); - --- -2.37.2 - -From 65dd92ca9f5b7871e5c49040c2155d2e341e1039 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:43 +0200 -Subject: [PATCH] platform/surface: aggregator: Add comment for KIP subsystem - category - -The KIP subsystem (full name unknown, abbreviation has been obtained -through reverse engineering) handles detachable peripherals such as the -keyboard cover on the Surface Pro X and Surface Pro 8. - -It is currently not entirely clear what this subsystem entails, but at -the very least it provides event notifications for when the keyboard -cover on the Surface Pro X and Surface Pro 8 have been detached or -re-attached, as well as the state that the keyboard cover is currently -in (e.g. folded-back, folded laptop-like, closed, etc.). - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-9-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - include/linux/surface_aggregator/serial_hub.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h -index c3de43edcffa..26b95ec12733 100644 ---- a/include/linux/surface_aggregator/serial_hub.h -+++ b/include/linux/surface_aggregator/serial_hub.h -@@ -306,7 +306,7 @@ enum ssam_ssh_tc { - SSAM_SSH_TC_LPC = 0x0b, - SSAM_SSH_TC_TCL = 0x0c, - SSAM_SSH_TC_SFL = 0x0d, -- SSAM_SSH_TC_KIP = 0x0e, -+ SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ - SSAM_SSH_TC_EXT = 0x0f, - SSAM_SSH_TC_BLD = 0x10, - SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ --- -2.37.2 - -From c46d674b2876bb2ad81c649ac4d6c2fe182cc262 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:44 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Generify subsystem hub - functionality - -The Surface System Aggregator Module (SSAM) has multiple subsystems that -can manage detachable devices. At the moment, we only support the "base" -(BAS/0x11) subsystem, which is used on the Surface Book 3 to manage -devices (including keyboard, touchpad, and secondary battery) connected -to the base of the device. - -The Surface Pro 8 has a new type-cover with keyboard and touchpad, which -is managed via the KIP/0x0e subsystem. The general procedure is the -same, but with slightly different events and setup. To make -implementation of the KIP hub easier and prevent duplication, generify -the parts of the base hub that we can use for the KIP hub (or any -potential future subsystem hubs). - -This also switches over to use the newly introduced "hot-remove" -functionality, which should prevent communication issues when devices -have been detached. - -Lastly, also drop the undocumented and unused sysfs "state" attribute of -the base hub. It has at best been useful for debugging. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-10-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 269 ++++++++++-------- - 1 file changed, 153 insertions(+), 116 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 9f630e890ff7..09cbeee2428b 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -308,30 +308,159 @@ static int ssam_hub_register_clients(struct device *parent, struct ssam_controll - } - - --/* -- SSAM base-hub driver. ------------------------------------------------- */ -+/* -- SSAM generic subsystem hub driver framework. -------------------------- */ - --/* -- * Some devices (especially battery) may need a bit of time to be fully usable -- * after being (re-)connected. This delay has been determined via -- * experimentation. -- */ --#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) -+enum ssam_hub_state { -+ SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ -+ SSAM_HUB_CONNECTED, -+ SSAM_HUB_DISCONNECTED, -+}; - --enum ssam_base_hub_state { -- SSAM_BASE_HUB_UNINITIALIZED, -- SSAM_BASE_HUB_CONNECTED, -- SSAM_BASE_HUB_DISCONNECTED, -+enum ssam_hub_flags { -+ SSAM_HUB_HOT_REMOVED, - }; - --struct ssam_base_hub { -+struct ssam_hub { - struct ssam_device *sdev; - -- enum ssam_base_hub_state state; -+ enum ssam_hub_state state; -+ unsigned long flags; -+ - struct delayed_work update_work; -+ unsigned long connect_delay; - - struct ssam_event_notifier notif; -+ -+ int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); - }; - -+static void ssam_hub_update_workfn(struct work_struct *work) -+{ -+ struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); -+ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -+ enum ssam_hub_state state; -+ int status = 0; -+ -+ status = hub->get_state(hub, &state); -+ if (status) -+ return; -+ -+ /* -+ * There is a small possibility that hub devices were hot-removed and -+ * re-added before we were able to remove them here. In that case, both -+ * the state returned by get_state() and the state of the hub will -+ * equal SSAM_HUB_CONNECTED and we would bail early below, which would -+ * leave child devices without proper (re-)initialization and the -+ * hot-remove flag set. -+ * -+ * Therefore, we check whether devices have been hot-removed via an -+ * additional flag on the hub and, in this case, override the returned -+ * hub state. In case of a missed disconnect (i.e. get_state returned -+ * "connected"), we further need to re-schedule this work (with the -+ * appropriate delay) as the actual connect work submission might have -+ * been merged with this one. -+ * -+ * This then leads to one of two cases: Either we submit an unnecessary -+ * work item (which will get ignored via either the queue or the state -+ * checks) or, in the unlikely case that the work is actually required, -+ * double the normal connect delay. -+ */ -+ if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { -+ if (state == SSAM_HUB_CONNECTED) -+ schedule_delayed_work(&hub->update_work, hub->connect_delay); -+ -+ state = SSAM_HUB_DISCONNECTED; -+ } -+ -+ if (hub->state == state) -+ return; -+ hub->state = state; -+ -+ if (hub->state == SSAM_HUB_CONNECTED) -+ status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); -+ else -+ ssam_remove_clients(&hub->sdev->dev); -+ -+ if (status) -+ dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); -+} -+ -+static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (is_ssam_device(dev)) -+ ssam_device_mark_hot_removed(sdev); -+ -+ return 0; -+} -+ -+static void ssam_hub_update(struct ssam_hub *hub, bool connected) -+{ -+ unsigned long delay; -+ -+ /* Mark devices as hot-removed before we remove any. */ -+ if (!connected) { -+ set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); -+ device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); -+ } -+ -+ /* -+ * Delay update when the base/keyboard cover is being connected to give -+ * devices/EC some time to set up. -+ */ -+ delay = connected ? hub->connect_delay : 0; -+ -+ schedule_delayed_work(&hub->update_work, delay); -+} -+ -+static int __maybe_unused ssam_hub_resume(struct device *dev) -+{ -+ struct ssam_hub *hub = dev_get_drvdata(dev); -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); -+ -+static int ssam_hub_setup(struct ssam_device *sdev, struct ssam_hub *hub) -+{ -+ int status; -+ -+ hub->sdev = sdev; -+ hub->state = SSAM_HUB_UNINITIALIZED; -+ -+ INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, hub); -+ -+ status = ssam_device_notifier_register(sdev, &hub->notif); -+ if (status) -+ return status; -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+ -+static void ssam_hub_remove(struct ssam_device *sdev) -+{ -+ struct ssam_hub *hub = ssam_device_get_drvdata(sdev); -+ -+ ssam_device_notifier_unregister(sdev, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); -+ ssam_remove_clients(&sdev->dev); -+} -+ -+ -+/* -- SSAM base-hub driver. ------------------------------------------------- */ -+ -+/* -+ * Some devices (especially battery) may need a bit of time to be fully usable -+ * after being (re-)connected. This delay has been determined via -+ * experimentation. -+ */ -+#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) -+ - SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, -@@ -342,7 +471,7 @@ SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { - #define SSAM_BAS_OPMODE_TABLET 0x00 - #define SSAM_EVENT_BAS_CID_CONNECTION 0x0c - --static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state) -+static int ssam_base_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) - { - u8 opmode; - int status; -@@ -354,62 +483,16 @@ static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_h - } - - if (opmode != SSAM_BAS_OPMODE_TABLET) -- *state = SSAM_BASE_HUB_CONNECTED; -+ *state = SSAM_HUB_CONNECTED; - else -- *state = SSAM_BASE_HUB_DISCONNECTED; -+ *state = SSAM_HUB_DISCONNECTED; - - return 0; - } - --static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr, -- char *buf) --{ -- struct ssam_base_hub *hub = dev_get_drvdata(dev); -- bool connected = hub->state == SSAM_BASE_HUB_CONNECTED; -- -- return sysfs_emit(buf, "%d\n", connected); --} -- --static struct device_attribute ssam_base_hub_attr_state = -- __ATTR(state, 0444, ssam_base_hub_state_show, NULL); -- --static struct attribute *ssam_base_hub_attrs[] = { -- &ssam_base_hub_attr_state.attr, -- NULL, --}; -- --static const struct attribute_group ssam_base_hub_group = { -- .attrs = ssam_base_hub_attrs, --}; -- --static void ssam_base_hub_update_workfn(struct work_struct *work) --{ -- struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work); -- struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -- enum ssam_base_hub_state state; -- int status = 0; -- -- status = ssam_base_hub_query_state(hub, &state); -- if (status) -- return; -- -- if (hub->state == state) -- return; -- hub->state = state; -- -- if (hub->state == SSAM_BASE_HUB_CONNECTED) -- status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); -- else -- ssam_remove_clients(&hub->sdev->dev); -- -- if (status) -- dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); --} -- - static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) - { -- struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif); -- unsigned long delay; -+ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); - - if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) - return 0; -@@ -419,13 +502,7 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam - return 0; - } - -- /* -- * Delay update when the base is being connected to give devices/EC -- * some time to set up. -- */ -- delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0; -- -- schedule_delayed_work(&hub->update_work, delay); -+ ssam_hub_update(hub, event->data[0]); - - /* - * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -@@ -435,27 +512,14 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam - return 0; - } - --static int __maybe_unused ssam_base_hub_resume(struct device *dev) --{ -- struct ssam_base_hub *hub = dev_get_drvdata(dev); -- -- schedule_delayed_work(&hub->update_work, 0); -- return 0; --} --static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); -- - static int ssam_base_hub_probe(struct ssam_device *sdev) - { -- struct ssam_base_hub *hub; -- int status; -+ struct ssam_hub *hub; - - hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); - if (!hub) - return -ENOMEM; - -- hub->sdev = sdev; -- hub->state = SSAM_BASE_HUB_UNINITIALIZED; -- - hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ - hub->notif.base.fn = ssam_base_hub_notif; - hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -@@ -464,37 +528,10 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - hub->notif.event.mask = SSAM_EVENT_MASK_NONE; - hub->notif.event.flags = SSAM_EVENT_SEQUENCED; - -- INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn); -- -- ssam_device_set_drvdata(sdev, hub); -- -- status = ssam_device_notifier_register(sdev, &hub->notif); -- if (status) -- return status; -- -- status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); -- if (status) -- goto err; -- -- schedule_delayed_work(&hub->update_work, 0); -- return 0; -+ hub->connect_delay = SSAM_BASE_UPDATE_CONNECT_DELAY; -+ hub->get_state = ssam_base_hub_query_state; - --err: -- ssam_device_notifier_unregister(sdev, &hub->notif); -- cancel_delayed_work_sync(&hub->update_work); -- ssam_remove_clients(&sdev->dev); -- return status; --} -- --static void ssam_base_hub_remove(struct ssam_device *sdev) --{ -- struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); -- -- sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); -- -- ssam_device_notifier_unregister(sdev, &hub->notif); -- cancel_delayed_work_sync(&hub->update_work); -- ssam_remove_clients(&sdev->dev); -+ return ssam_hub_setup(sdev, hub); - } - - static const struct ssam_device_id ssam_base_hub_match[] = { -@@ -504,12 +541,12 @@ static const struct ssam_device_id ssam_base_hub_match[] = { - - static struct ssam_device_driver ssam_base_hub_driver = { - .probe = ssam_base_hub_probe, -- .remove = ssam_base_hub_remove, -+ .remove = ssam_hub_remove, - .match_table = ssam_base_hub_match, - .driver = { - .name = "surface_aggregator_base_hub", - .probe_type = PROBE_PREFER_ASYNCHRONOUS, -- .pm = &ssam_base_hub_pm_ops, -+ .pm = &ssam_hub_pm_ops, - }, - }; - --- -2.37.2 - -From d293378f454cb71b55886aeec00c58088e529ca7 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:45 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Change device ID for - base hub - -Use the target category of the (base) hub as instance id in the -(virtual) hub device UID. This makes association of the hub with the -respective subsystem easier. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-11-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 09cbeee2428b..b11ce87c7184 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -43,7 +43,7 @@ static const struct software_node ssam_node_root = { - - /* Base device hub (devices attached to Surface Book 3 base). */ - static const struct software_node ssam_node_hub_base = { -- .name = "ssam:00:00:02:00:00", -+ .name = "ssam:00:00:02:11:00", - .parent = &ssam_node_root, - }; - -@@ -535,7 +535,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - } - - static const struct ssam_device_id ssam_base_hub_match[] = { -- { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) }, -+ { SSAM_VDEV(HUB, 0x02, SSAM_SSH_TC_BAS, 0x00) }, - { }, - }; - --- -2.37.2 - -From c4bdb8393081ba9121aadc7d6643a42613fbcbd0 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:46 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add KIP device hub - -Add a Surface System Aggregator Module (SSAM) client device hub for -hot-removable devices managed via the KIP subsystem. - -The KIP subsystem (full name unknown, abbreviation has been obtained -through reverse engineering) is a subsystem that manages hot-removable -SSAM client devices. Specifically, it manages HID input devices -contained in the detachable keyboard cover of the Surface Pro 8 and -Surface Pro X. - -The KIP subsystem handles a single group of devices (e.g. all devices -contained in the keyboard cover) and cannot handle devices individually. -Thus we model it as a client device hub, which (hot-)removes all devices -contained under it once removal of the hub (e.g. keyboard cover) has -been detected and (re-)adds all devices once the physical hub device has -been (re-)attached. To do this, use the previously generified SSAM -subsystem hub framework. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-12-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 103 +++++++++++++++++- - 1 file changed, 101 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index b11ce87c7184..f15cef60630f 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -551,6 +551,93 @@ static struct ssam_device_driver ssam_base_hub_driver = { - }; - - -+/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ -+ -+/* -+ * Some devices may need a bit of time to be fully usable after being -+ * (re-)connected. This delay has been determined via experimentation. -+ */ -+#define SSAM_KIP_UPDATE_CONNECT_DELAY msecs_to_jiffies(250) -+ -+#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_connection_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x2c, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_get_connection_state(struct ssam_hub *hub, enum ssam_hub_state *state) -+{ -+ int status; -+ u8 connected; -+ -+ status = ssam_retry(__ssam_kip_get_connection_state, hub->sdev->ctrl, &connected); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); -+ return status; -+ } -+ -+ *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; -+ return 0; -+} -+ -+static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) { -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ ssam_hub_update(hub, event->data[0]); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static int ssam_kip_hub_probe(struct ssam_device *sdev) -+{ -+ struct ssam_hub *hub; -+ -+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -+ if (!hub) -+ return -ENOMEM; -+ -+ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -+ hub->notif.base.fn = ssam_kip_hub_notif; -+ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ hub->notif.event.id.target_category = SSAM_SSH_TC_KIP, -+ hub->notif.event.id.instance = 0, -+ hub->notif.event.mask = SSAM_EVENT_MASK_TARGET; -+ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ hub->connect_delay = SSAM_KIP_UPDATE_CONNECT_DELAY; -+ hub->get_state = ssam_kip_get_connection_state; -+ -+ return ssam_hub_setup(sdev, hub); -+} -+ -+static const struct ssam_device_id ssam_kip_hub_match[] = { -+ { SSAM_VDEV(HUB, 0x01, SSAM_SSH_TC_KIP, 0x00) }, -+ { }, -+}; -+ -+static struct ssam_device_driver ssam_kip_hub_driver = { -+ .probe = ssam_kip_hub_probe, -+ .remove = ssam_hub_remove, -+ .match_table = ssam_kip_hub_match, -+ .driver = { -+ .name = "surface_kip_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_hub_pm_ops, -+ }, -+}; -+ -+ - /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ - - static const struct acpi_device_id ssam_platform_hub_match[] = { -@@ -673,18 +760,30 @@ static int __init ssam_device_hub_init(void) - - status = platform_driver_register(&ssam_platform_hub_driver); - if (status) -- return status; -+ goto err_platform; - - status = ssam_device_driver_register(&ssam_base_hub_driver); - if (status) -- platform_driver_unregister(&ssam_platform_hub_driver); -+ goto err_base; -+ -+ status = ssam_device_driver_register(&ssam_kip_hub_driver); -+ if (status) -+ goto err_kip; - -+ return 0; -+ -+err_kip: -+ ssam_device_driver_unregister(&ssam_base_hub_driver); -+err_base: -+ platform_driver_unregister(&ssam_platform_hub_driver); -+err_platform: - return status; - } - module_init(ssam_device_hub_init); - - static void __exit ssam_device_hub_exit(void) - { -+ ssam_device_driver_unregister(&ssam_kip_hub_driver); - ssam_device_driver_unregister(&ssam_base_hub_driver); - platform_driver_unregister(&ssam_platform_hub_driver); - } --- -2.37.2 - -From f4c22cd50fb6eba94141a7823be1d4d4db06eb92 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:47 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for - keyboard cover on Surface Pro 8 - -Add support for the detachable keyboard cover on the Surface Pro 8. - -The keyboard cover on the Surface Pro 8 is, unlike the keyboard covers -of earlier Surface Pro generations, handled via the Surface System -Aggregator Module (SSAM). The keyboard and touchpad (as well as other -HID input devices) of this cover are standard SSAM HID client devices -(just like keyboard and touchpad on e.g. the Surface Laptop 3 and 4), -however, some care needs to be taken as they can be physically detached -(similarly to the Surface Book 3). Specifically, the respective SSAM -client devices need to be removed when the keyboard cover has been -detached and (re-)initialized when the keyboard cover has been -(re-)attached. - -On the Surface Pro 8, detachment of the keyboard cover (and by extension -its devices) is managed via the KIP subsystem. Therefore, said devices -need to be registered under the KIP device hub, which in turn will -remove and re-create/re-initialize those devices as needed. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-13-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 37 ++++++++++++++++++- - 1 file changed, 36 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index f15cef60630f..bf3303f1aa71 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -41,6 +41,12 @@ static const struct software_node ssam_node_root = { - .name = "ssam_platform_hub", - }; - -+/* KIP device hub (connects keyboard cover devices on Surface Pro 8). */ -+static const struct software_node ssam_node_hub_kip = { -+ .name = "ssam:00:00:01:0e:00", -+ .parent = &ssam_node_root, -+}; -+ - /* Base device hub (devices attached to Surface Book 3 base). */ - static const struct software_node ssam_node_hub_base = { - .name = "ssam:00:00:02:11:00", -@@ -155,6 +161,30 @@ static const struct software_node ssam_node_hid_base_iid6 = { - .parent = &ssam_node_hub_base, - }; - -+/* HID keyboard (KIP hub). */ -+static const struct software_node ssam_node_hid_kip_keyboard = { -+ .name = "ssam:01:15:02:01:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID pen stash (KIP hub; pen taken / stashed away evens). */ -+static const struct software_node ssam_node_hid_kip_penstash = { -+ .name = "ssam:01:15:02:02:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID touchpad (KIP hub). */ -+static const struct software_node ssam_node_hid_kip_touchpad = { -+ .name = "ssam:01:15:02:03:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID device instance 5 (KIP hub, unknown HID device). */ -+static const struct software_node ssam_node_hid_kip_iid5 = { -+ .name = "ssam:01:15:02:05:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ - /* - * Devices for 5th- and 6th-generations models: - * - Surface Book 2, -@@ -230,10 +260,15 @@ static const struct software_node *ssam_node_group_sp7[] = { - - static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_root, -+ &ssam_node_hub_kip, - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -- /* TODO: Add support for keyboard cover. */ -+ &ssam_node_hid_kip_keyboard, -+ &ssam_node_hid_kip_penstash, -+ &ssam_node_hid_kip_touchpad, -+ &ssam_node_hid_kip_iid5, -+ /* TODO: Add support for tablet mode switch. */ - NULL, - }; - --- -2.37.2 - -From 9fbb688836c42161b6389da82442a12ff2346e44 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 14 Jun 2022 13:17:49 +0200 -Subject: [PATCH] platform/surface: aggregator: Reserve more event- and - target-categories - -With the introduction of the Surface Laptop Studio, more event- and -target categories have been added. Therefore, increase the number of -reserved events and extend the enum of know target categories to -accommodate this. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/trace.h | 80 +++++++++++-------- - include/linux/surface_aggregator/serial_hub.h | 75 +++++++++-------- - 2 files changed, 85 insertions(+), 70 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h -index de64cf169060..cc9e73fbc18e 100644 ---- a/drivers/platform/surface/aggregator/trace.h -+++ b/drivers/platform/surface/aggregator/trace.h -@@ -76,7 +76,7 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM); --TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC0); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN); -@@ -85,6 +85,11 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SPT); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SYS); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC1); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SHB); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_POS); - - #define SSAM_PTR_UID_LEN 9 - #define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1) -@@ -229,40 +234,45 @@ static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) - - #define ssam_show_ssh_tc(rqid) \ - __print_symbolic(rqid, \ -- { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ -- { SSAM_SSH_TC_SAM, "SAM" }, \ -- { SSAM_SSH_TC_BAT, "BAT" }, \ -- { SSAM_SSH_TC_TMP, "TMP" }, \ -- { SSAM_SSH_TC_PMC, "PMC" }, \ -- { SSAM_SSH_TC_FAN, "FAN" }, \ -- { SSAM_SSH_TC_PoM, "PoM" }, \ -- { SSAM_SSH_TC_DBG, "DBG" }, \ -- { SSAM_SSH_TC_KBD, "KBD" }, \ -- { SSAM_SSH_TC_FWU, "FWU" }, \ -- { SSAM_SSH_TC_UNI, "UNI" }, \ -- { SSAM_SSH_TC_LPC, "LPC" }, \ -- { SSAM_SSH_TC_TCL, "TCL" }, \ -- { SSAM_SSH_TC_SFL, "SFL" }, \ -- { SSAM_SSH_TC_KIP, "KIP" }, \ -- { SSAM_SSH_TC_EXT, "EXT" }, \ -- { SSAM_SSH_TC_BLD, "BLD" }, \ -- { SSAM_SSH_TC_BAS, "BAS" }, \ -- { SSAM_SSH_TC_SEN, "SEN" }, \ -- { SSAM_SSH_TC_SRQ, "SRQ" }, \ -- { SSAM_SSH_TC_MCU, "MCU" }, \ -- { SSAM_SSH_TC_HID, "HID" }, \ -- { SSAM_SSH_TC_TCH, "TCH" }, \ -- { SSAM_SSH_TC_BKL, "BKL" }, \ -- { SSAM_SSH_TC_TAM, "TAM" }, \ -- { SSAM_SSH_TC_ACC, "ACC" }, \ -- { SSAM_SSH_TC_UFI, "UFI" }, \ -- { SSAM_SSH_TC_USC, "USC" }, \ -- { SSAM_SSH_TC_PEN, "PEN" }, \ -- { SSAM_SSH_TC_VID, "VID" }, \ -- { SSAM_SSH_TC_AUD, "AUD" }, \ -- { SSAM_SSH_TC_SMC, "SMC" }, \ -- { SSAM_SSH_TC_KPD, "KPD" }, \ -- { SSAM_SSH_TC_REG, "REG" } \ -+ { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ -+ { SSAM_SSH_TC_SAM, "SAM" }, \ -+ { SSAM_SSH_TC_BAT, "BAT" }, \ -+ { SSAM_SSH_TC_TMP, "TMP" }, \ -+ { SSAM_SSH_TC_PMC, "PMC" }, \ -+ { SSAM_SSH_TC_FAN, "FAN" }, \ -+ { SSAM_SSH_TC_PoM, "PoM" }, \ -+ { SSAM_SSH_TC_DBG, "DBG" }, \ -+ { SSAM_SSH_TC_KBD, "KBD" }, \ -+ { SSAM_SSH_TC_FWU, "FWU" }, \ -+ { SSAM_SSH_TC_UNI, "UNI" }, \ -+ { SSAM_SSH_TC_LPC, "LPC" }, \ -+ { SSAM_SSH_TC_TCL, "TCL" }, \ -+ { SSAM_SSH_TC_SFL, "SFL" }, \ -+ { SSAM_SSH_TC_KIP, "KIP" }, \ -+ { SSAM_SSH_TC_EXT, "EXT" }, \ -+ { SSAM_SSH_TC_BLD, "BLD" }, \ -+ { SSAM_SSH_TC_BAS, "BAS" }, \ -+ { SSAM_SSH_TC_SEN, "SEN" }, \ -+ { SSAM_SSH_TC_SRQ, "SRQ" }, \ -+ { SSAM_SSH_TC_MCU, "MCU" }, \ -+ { SSAM_SSH_TC_HID, "HID" }, \ -+ { SSAM_SSH_TC_TCH, "TCH" }, \ -+ { SSAM_SSH_TC_BKL, "BKL" }, \ -+ { SSAM_SSH_TC_TAM, "TAM" }, \ -+ { SSAM_SSH_TC_ACC0, "ACC0" }, \ -+ { SSAM_SSH_TC_UFI, "UFI" }, \ -+ { SSAM_SSH_TC_USC, "USC" }, \ -+ { SSAM_SSH_TC_PEN, "PEN" }, \ -+ { SSAM_SSH_TC_VID, "VID" }, \ -+ { SSAM_SSH_TC_AUD, "AUD" }, \ -+ { SSAM_SSH_TC_SMC, "SMC" }, \ -+ { SSAM_SSH_TC_KPD, "KPD" }, \ -+ { SSAM_SSH_TC_REG, "REG" }, \ -+ { SSAM_SSH_TC_SPT, "SPT" }, \ -+ { SSAM_SSH_TC_SYS, "SYS" }, \ -+ { SSAM_SSH_TC_ACC1, "ACC1" }, \ -+ { SSAM_SSH_TC_SHB, "SMB" }, \ -+ { SSAM_SSH_TC_POS, "POS" } \ - ) - - DECLARE_EVENT_CLASS(ssam_frame_class, -diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h -index 26b95ec12733..45501b6e54e8 100644 ---- a/include/linux/surface_aggregator/serial_hub.h -+++ b/include/linux/surface_aggregator/serial_hub.h -@@ -201,7 +201,7 @@ static inline u16 ssh_crc(const u8 *buf, size_t len) - * exception of zero, which is not an event ID. Thus, this is also the - * absolute maximum number of event handlers that can be registered. - */ --#define SSH_NUM_EVENTS 34 -+#define SSH_NUM_EVENTS 38 - - /* - * SSH_NUM_TARGETS - The number of communication targets used in the protocol. -@@ -292,40 +292,45 @@ struct ssam_span { - * Windows driver. - */ - enum ssam_ssh_tc { -- /* Category 0x00 is invalid for EC use. */ -- SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */ -- SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */ -- SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */ -- SSAM_SSH_TC_PMC = 0x04, -- SSAM_SSH_TC_FAN = 0x05, -- SSAM_SSH_TC_PoM = 0x06, -- SSAM_SSH_TC_DBG = 0x07, -- SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */ -- SSAM_SSH_TC_FWU = 0x09, -- SSAM_SSH_TC_UNI = 0x0a, -- SSAM_SSH_TC_LPC = 0x0b, -- SSAM_SSH_TC_TCL = 0x0c, -- SSAM_SSH_TC_SFL = 0x0d, -- SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ -- SSAM_SSH_TC_EXT = 0x0f, -- SSAM_SSH_TC_BLD = 0x10, -- SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ -- SSAM_SSH_TC_SEN = 0x12, -- SSAM_SSH_TC_SRQ = 0x13, -- SSAM_SSH_TC_MCU = 0x14, -- SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */ -- SSAM_SSH_TC_TCH = 0x16, -- SSAM_SSH_TC_BKL = 0x17, -- SSAM_SSH_TC_TAM = 0x18, -- SSAM_SSH_TC_ACC = 0x19, -- SSAM_SSH_TC_UFI = 0x1a, -- SSAM_SSH_TC_USC = 0x1b, -- SSAM_SSH_TC_PEN = 0x1c, -- SSAM_SSH_TC_VID = 0x1d, -- SSAM_SSH_TC_AUD = 0x1e, -- SSAM_SSH_TC_SMC = 0x1f, -- SSAM_SSH_TC_KPD = 0x20, -- SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */ -+ /* Category 0x00 is invalid for EC use. */ -+ SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */ -+ SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */ -+ SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */ -+ SSAM_SSH_TC_PMC = 0x04, -+ SSAM_SSH_TC_FAN = 0x05, -+ SSAM_SSH_TC_PoM = 0x06, -+ SSAM_SSH_TC_DBG = 0x07, -+ SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */ -+ SSAM_SSH_TC_FWU = 0x09, -+ SSAM_SSH_TC_UNI = 0x0a, -+ SSAM_SSH_TC_LPC = 0x0b, -+ SSAM_SSH_TC_TCL = 0x0c, -+ SSAM_SSH_TC_SFL = 0x0d, -+ SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ -+ SSAM_SSH_TC_EXT = 0x0f, -+ SSAM_SSH_TC_BLD = 0x10, -+ SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ -+ SSAM_SSH_TC_SEN = 0x12, -+ SSAM_SSH_TC_SRQ = 0x13, -+ SSAM_SSH_TC_MCU = 0x14, -+ SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */ -+ SSAM_SSH_TC_TCH = 0x16, -+ SSAM_SSH_TC_BKL = 0x17, -+ SSAM_SSH_TC_TAM = 0x18, -+ SSAM_SSH_TC_ACC0 = 0x19, -+ SSAM_SSH_TC_UFI = 0x1a, -+ SSAM_SSH_TC_USC = 0x1b, -+ SSAM_SSH_TC_PEN = 0x1c, -+ SSAM_SSH_TC_VID = 0x1d, -+ SSAM_SSH_TC_AUD = 0x1e, -+ SSAM_SSH_TC_SMC = 0x1f, -+ SSAM_SSH_TC_KPD = 0x20, -+ SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */ -+ SSAM_SSH_TC_SPT = 0x22, -+ SSAM_SSH_TC_SYS = 0x23, -+ SSAM_SSH_TC_ACC1 = 0x24, -+ SSAM_SSH_TC_SHB = 0x25, -+ SSAM_SSH_TC_POS = 0x26, /* For obtaining Laptop Studio screen position. */ - }; - - --- -2.37.2 - -From f26c891671fde07b2b198bc69d36b2c372a0ac55 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 16 Jun 2022 01:50:12 +0200 -Subject: [PATCH] platform/surface: aggregator: Add helper macros for requests - with argument and return value - -Add helper macros for synchronous stack-allocated Surface Aggregator -request with both argument and return value, similar to the current -argument-only and return-value-only ones. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - include/linux/surface_aggregator/controller.h | 125 ++++++++++++++++++ - include/linux/surface_aggregator/device.h | 36 +++++ - 2 files changed, 161 insertions(+) - -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index 50a2b4926c06..d11a1c6e3186 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -469,6 +469,67 @@ struct ssam_request_spec_md { - return 0; \ - } - -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_WR() - Define synchronous SAM request function with -+ * both argument and return value. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by @spec, -+ * with the request taking an argument of type @atype and having a return value -+ * of type @rtype. The generated function takes care of setting up the request -+ * and response structs, buffer allocation, as well as execution of the request -+ * itself, returning once the request has been fully completed. The required -+ * transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, const atype *arg, rtype *ret)``, returning the status -+ * of the request, which is zero on success and negative on failure. The -+ * ``ctrl`` parameter is the controller via which the request is sent. The -+ * request argument is specified via the ``arg`` pointer. The request's return -+ * value is written to the memory pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_WR(name, atype, rtype, spec...) \ -+ static int name(struct ssam_controller *ctrl, const atype *arg, rtype *ret) \ -+ { \ -+ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ -+ struct ssam_request rqst; \ -+ struct ssam_response rsp; \ -+ int status; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = s.target_id; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = s.instance_id; \ -+ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ -+ rqst.length = sizeof(atype); \ -+ rqst.payload = (u8 *)arg; \ -+ \ -+ rsp.capacity = sizeof(rtype); \ -+ rsp.length = 0; \ -+ rsp.pointer = (u8 *)ret; \ -+ \ -+ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, sizeof(atype)); \ -+ if (status) \ -+ return status; \ -+ \ -+ if (rsp.length != sizeof(rtype)) { \ -+ struct device *dev = ssam_controller_device(ctrl); \ -+ dev_err(dev, \ -+ "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ -+ sizeof(rtype), rsp.length, rqst.target_category,\ -+ rqst.command_id); \ -+ return -EIO; \ -+ } \ -+ \ -+ return 0; \ -+ } -+ - /** - * SSAM_DEFINE_SYNC_REQUEST_MD_N() - Define synchronous multi-device SAM - * request function with neither argument nor return value. -@@ -613,6 +674,70 @@ struct ssam_request_spec_md { - return 0; \ - } - -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_MD_WR() - Define synchronous multi-device SAM -+ * request function with both argument and return value. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by @spec, -+ * with the request taking an argument of type @atype and having a return value -+ * of type @rtype. Device specifying parameters are not hard-coded, but instead -+ * must be provided to the function. The generated function takes care of -+ * setting up the request and response structs, buffer allocation, as well as -+ * execution of the request itself, returning once the request has been fully -+ * completed. The required transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg, rtype *ret)``, -+ * returning the status of the request, which is zero on success and negative -+ * on failure. The ``ctrl`` parameter is the controller via which the request -+ * is sent, ``tid`` the target ID for the request, and ``iid`` the instance ID. -+ * The request argument is specified via the ``arg`` pointer. The request's -+ * return value is written to the memory pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_MD_WR(name, atype, rtype, spec...) \ -+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, \ -+ const atype *arg, rtype *ret) \ -+ { \ -+ struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ -+ struct ssam_request rqst; \ -+ struct ssam_response rsp; \ -+ int status; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = tid; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = iid; \ -+ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ -+ rqst.length = sizeof(atype); \ -+ rqst.payload = (u8 *)arg; \ -+ \ -+ rsp.capacity = sizeof(rtype); \ -+ rsp.length = 0; \ -+ rsp.pointer = (u8 *)ret; \ -+ \ -+ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, sizeof(atype)); \ -+ if (status) \ -+ return status; \ -+ \ -+ if (rsp.length != sizeof(rtype)) { \ -+ struct device *dev = ssam_controller_device(ctrl); \ -+ dev_err(dev, \ -+ "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ -+ sizeof(rtype), rsp.length, rqst.target_category,\ -+ rqst.command_id); \ -+ return -EIO; \ -+ } \ -+ \ -+ return 0; \ -+ } -+ - - /* -- Event notifier/callbacks. --------------------------------------------- */ - -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index c418f7f2732d..6cf7e80312d5 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -483,6 +483,42 @@ static inline void ssam_remove_clients(struct device *dev) {} - sdev->uid.instance, ret); \ - } - -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_CL_WR() - Define synchronous client-device SAM -+ * request function with argument and return value. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by @spec, -+ * with the request taking an argument of type @atype and having a return value -+ * of type @rtype. Device specifying parameters are not hard-coded, but instead -+ * are provided via the client device, specifically its UID, supplied when -+ * calling this function. The generated function takes care of setting up the -+ * request struct, buffer allocation, as well as execution of the request -+ * itself, returning once the request has been fully completed. The required -+ * transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct ssam_device -+ * *sdev, const atype *arg, rtype *ret)``, returning the status of the request, -+ * which is zero on success and negative on failure. The ``sdev`` parameter -+ * specifies both the target device of the request and by association the -+ * controller via which the request is sent. The request's argument is -+ * specified via the ``arg`` pointer. The request's return value is written to -+ * the memory pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_CL_WR(name, atype, rtype, spec...) \ -+ SSAM_DEFINE_SYNC_REQUEST_MD_WR(__raw_##name, atype, rtype, spec) \ -+ static int name(struct ssam_device *sdev, const atype *arg, rtype *ret) \ -+ { \ -+ return __raw_##name(sdev->ctrl, sdev->uid.target, \ -+ sdev->uid.instance, arg, ret); \ -+ } -+ - - /* -- Helpers for client-device notifiers. ---------------------------------- */ - --- -2.37.2 - -From b89f2ac538abd4dd449bd9bd84d86e93b5567b38 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 8 Jun 2021 03:19:20 +0200 -Subject: [PATCH] platform/surface: Add KIP tablet-mode switch - -Add a driver providing a tablet-mode switch input device for Surface -models using the KIP subsystem to manage detachable peripherals. - -The Surface Pro 8 has a detachable keyboard cover. Unlike the keyboard -covers of previous generation Surface Pro models, this cover is fully -handled by the Surface System Aggregator Module (SSAM). The SSAM KIP -subsystem (full name unknown, abbreviation found through reverse -engineering) provides notifications for mode changes of the cover. -Specifically, it allows us to know when the cover has been folded back, -detached, or whether it is in laptop mode. - -The driver introduced with this change captures these events and -translates them to standard SW_TABLET_MODE input events. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - MAINTAINERS | 6 + - drivers/platform/surface/Kconfig | 23 + - drivers/platform/surface/Makefile | 1 + - .../surface/surface_aggregator_tabletsw.c | 535 ++++++++++++++++++ - 4 files changed, 565 insertions(+) - create mode 100644 drivers/platform/surface/surface_aggregator_tabletsw.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index c7c7a96b62a8..852231f4e469 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -13030,6 +13030,12 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch] - F: include/linux/cciss*.h - F: include/uapi/linux/cciss*.h - -+MICROSOFT SURFACE AGGREGATOR TABLET-MODE SWITCH -+M: Maximilian Luz -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/platform/surface/surface_aggregator_tablet_switch.c -+ - MICROSOFT SURFACE BATTERY AND AC DRIVERS - M: Maximilian Luz - L: linux-pm@vger.kernel.org -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index eb79fbed8059..b152e930cc84 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -99,6 +99,29 @@ config SURFACE_AGGREGATOR_REGISTRY - the respective client devices. Drivers for these devices still need to - be selected via the other options. - -+config SURFACE_AGGREGATOR_TABLET_SWITCH -+ tristate "Surface Aggregator Generic Tablet-Mode Switch Driver" -+ depends on SURFACE_AGGREGATOR -+ depends on SURFACE_AGGREGATOR_BUS -+ depends on INPUT -+ help -+ Provides a tablet-mode switch input device on Microsoft Surface models -+ using the KIP subsystem for detachable keyboards (e.g. keyboard covers) -+ or the POS subsystem for device/screen posture changes. -+ -+ The KIP subsystem is used on newer Surface generations to handle -+ detachable input peripherals, specifically the keyboard cover (containing -+ keyboard and touchpad) on the Surface Pro 8 and Surface Pro X. The POS -+ subsystem is used for device posture change notifications on the Surface -+ Laptop Studio. This module provides a driver to let user-space know when -+ the device should be considered in tablet-mode due to the keyboard cover -+ being detached or folded back (essentially signaling when the keyboard is -+ not available for input). It does so by creating a tablet-mode switch -+ input device, sending the standard SW_TABLET_MODE event on mode change. -+ -+ Select M or Y here, if you want to provide tablet-mode switch input -+ events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. -+ - config SURFACE_DTX - tristate "Surface DTX (Detachment System) Driver" - depends on SURFACE_AGGREGATOR -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 0fc9cd3e4dd9..18b27898543e 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -10,6 +10,7 @@ obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o -+obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c -new file mode 100644 -index 000000000000..6f402d2ca894 ---- /dev/null -+++ b/drivers/platform/surface/surface_aggregator_tabletsw.c -@@ -0,0 +1,535 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) tablet mode switch driver. -+ * -+ * Copyright (C) 2022 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+ -+/* -- SSAM generic tablet switch driver framework. -------------------------- */ -+ -+struct ssam_tablet_sw; -+ -+struct ssam_tablet_sw_ops { -+ int (*get_state)(struct ssam_tablet_sw *sw, u32 *state); -+ const char *(*state_name)(struct ssam_tablet_sw *sw, u32 state); -+ bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw, u32 state); -+}; -+ -+struct ssam_tablet_sw { -+ struct ssam_device *sdev; -+ -+ u32 state; -+ struct work_struct update_work; -+ struct input_dev *mode_switch; -+ -+ struct ssam_tablet_sw_ops ops; -+ struct ssam_event_notifier notif; -+}; -+ -+struct ssam_tablet_sw_desc { -+ struct { -+ const char *name; -+ const char *phys; -+ } dev; -+ -+ struct { -+ u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event); -+ int (*get_state)(struct ssam_tablet_sw *sw, u32 *state); -+ const char *(*state_name)(struct ssam_tablet_sw *sw, u32 state); -+ bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw, u32 state); -+ } ops; -+ -+ struct { -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ enum ssam_event_mask mask; -+ u8 flags; -+ } event; -+}; -+ -+static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct ssam_tablet_sw *sw = dev_get_drvdata(dev); -+ const char *state = sw->ops.state_name(sw, sw->state); -+ -+ return sysfs_emit(buf, "%s\n", state); -+} -+static DEVICE_ATTR_RO(state); -+ -+static struct attribute *ssam_tablet_sw_attrs[] = { -+ &dev_attr_state.attr, -+ NULL, -+}; -+ -+static const struct attribute_group ssam_tablet_sw_group = { -+ .attrs = ssam_tablet_sw_attrs, -+}; -+ -+static void ssam_tablet_sw_update_workfn(struct work_struct *work) -+{ -+ struct ssam_tablet_sw *sw = container_of(work, struct ssam_tablet_sw, update_work); -+ int tablet, status; -+ u32 state; -+ -+ status = sw->ops.get_state(sw, &state); -+ if (status) -+ return; -+ -+ if (sw->state == state) -+ return; -+ sw->state = state; -+ -+ /* Send SW_TABLET_MODE event. */ -+ tablet = sw->ops.state_is_tablet_mode(sw, state); -+ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); -+ input_sync(sw->mode_switch); -+} -+ -+static int __maybe_unused ssam_tablet_sw_resume(struct device *dev) -+{ -+ struct ssam_tablet_sw *sw = dev_get_drvdata(dev); -+ -+ schedule_work(&sw->update_work); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_tablet_sw_pm_ops, NULL, ssam_tablet_sw_resume); -+ -+static int ssam_tablet_sw_probe(struct ssam_device *sdev) -+{ -+ const struct ssam_tablet_sw_desc *desc; -+ struct ssam_tablet_sw *sw; -+ int tablet, status; -+ -+ desc = ssam_device_get_match_data(sdev); -+ if (!desc) { -+ WARN(1, "no driver match data specified"); -+ return -EINVAL; -+ } -+ -+ sw = devm_kzalloc(&sdev->dev, sizeof(*sw), GFP_KERNEL); -+ if (!sw) -+ return -ENOMEM; -+ -+ sw->sdev = sdev; -+ -+ sw->ops.get_state = desc->ops.get_state; -+ sw->ops.state_name = desc->ops.state_name; -+ sw->ops.state_is_tablet_mode = desc->ops.state_is_tablet_mode; -+ -+ INIT_WORK(&sw->update_work, ssam_tablet_sw_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, sw); -+ -+ /* Get initial state. */ -+ status = sw->ops.get_state(sw, &sw->state); -+ if (status) -+ return status; -+ -+ /* Set up tablet mode switch. */ -+ sw->mode_switch = devm_input_allocate_device(&sdev->dev); -+ if (!sw->mode_switch) -+ return -ENOMEM; -+ -+ sw->mode_switch->name = desc->dev.name; -+ sw->mode_switch->phys = desc->dev.phys; -+ sw->mode_switch->id.bustype = BUS_HOST; -+ sw->mode_switch->dev.parent = &sdev->dev; -+ -+ tablet = sw->ops.state_is_tablet_mode(sw, sw->state); -+ input_set_capability(sw->mode_switch, EV_SW, SW_TABLET_MODE); -+ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); -+ -+ status = input_register_device(sw->mode_switch); -+ if (status) -+ return status; -+ -+ /* Set up notifier. */ -+ sw->notif.base.priority = 0; -+ sw->notif.base.fn = desc->ops.notify; -+ sw->notif.event.reg = desc->event.reg; -+ sw->notif.event.id = desc->event.id; -+ sw->notif.event.mask = desc->event.mask; -+ sw->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ status = ssam_device_notifier_register(sdev, &sw->notif); -+ if (status) -+ return status; -+ -+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_tablet_sw_group); -+ if (status) -+ goto err; -+ -+ /* We might have missed events during setup, so check again. */ -+ schedule_work(&sw->update_work); -+ return 0; -+ -+err: -+ ssam_device_notifier_unregister(sdev, &sw->notif); -+ cancel_work_sync(&sw->update_work); -+ return status; -+} -+ -+static void ssam_tablet_sw_remove(struct ssam_device *sdev) -+{ -+ struct ssam_tablet_sw *sw = ssam_device_get_drvdata(sdev); -+ -+ sysfs_remove_group(&sdev->dev.kobj, &ssam_tablet_sw_group); -+ -+ ssam_device_notifier_unregister(sdev, &sw->notif); -+ cancel_work_sync(&sw->update_work); -+} -+ -+ -+/* -- SSAM KIP tablet switch implementation. -------------------------------- */ -+ -+#define SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED 0x1d -+ -+enum ssam_kip_cover_state { -+ SSAM_KIP_COVER_STATE_DISCONNECTED = 0x01, -+ SSAM_KIP_COVER_STATE_CLOSED = 0x02, -+ SSAM_KIP_COVER_STATE_LAPTOP = 0x03, -+ SSAM_KIP_COVER_STATE_FOLDED_CANVAS = 0x04, -+ SSAM_KIP_COVER_STATE_FOLDED_BACK = 0x05, -+}; -+ -+static const char* ssam_kip_cover_state_name(struct ssam_tablet_sw *sw, u32 state) -+{ -+ switch (state) { -+ case SSAM_KIP_COVER_STATE_DISCONNECTED: -+ return "disconnected"; -+ -+ case SSAM_KIP_COVER_STATE_CLOSED: -+ return "closed"; -+ -+ case SSAM_KIP_COVER_STATE_LAPTOP: -+ return "laptop"; -+ -+ case SSAM_KIP_COVER_STATE_FOLDED_CANVAS: -+ return "folded-canvas"; -+ -+ case SSAM_KIP_COVER_STATE_FOLDED_BACK: -+ return "folded-back"; -+ -+ default: -+ dev_warn(&sw->sdev->dev, "unknown KIP cover state: %u\n", state); -+ return ""; -+ } -+} -+ -+static bool ssam_kip_cover_state_is_tablet_mode(struct ssam_tablet_sw *sw, u32 state) -+{ -+ switch (state) { -+ case SSAM_KIP_COVER_STATE_DISCONNECTED: -+ case SSAM_KIP_COVER_STATE_FOLDED_CANVAS: -+ case SSAM_KIP_COVER_STATE_FOLDED_BACK: -+ return true; -+ -+ case SSAM_KIP_COVER_STATE_CLOSED: -+ case SSAM_KIP_COVER_STATE_LAPTOP: -+ return false; -+ -+ default: -+ dev_warn(&sw->sdev->dev, "unknown KIP cover state: %d\n", sw->state); -+ return true; -+ } -+} -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_cover_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x1d, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_get_cover_state(struct ssam_tablet_sw *sw, u32 *state) -+{ -+ int status; -+ u8 raw; -+ -+ status = ssam_retry(__ssam_kip_get_cover_state, sw->sdev->ctrl, &raw); -+ if (status < 0) { -+ dev_err(&sw->sdev->dev, "failed to query KIP lid state: %d\n", status); -+ return status; -+ } -+ -+ *state = raw; -+ return 0; -+} -+ -+static u32 ssam_kip_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_tablet_sw *sw = container_of(nf, struct ssam_tablet_sw, notif); -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) { -+ dev_warn(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); -+ } -+ -+ schedule_work(&sw->update_work); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static const struct ssam_tablet_sw_desc ssam_kip_sw_desc = { -+ .dev = { -+ .name = "Microsoft Surface KIP Tablet Mode Switch", -+ .phys = "ssam/01:0e:01:00:01/input0", -+ }, -+ .ops = { -+ .notify = ssam_kip_sw_notif, -+ .get_state = ssam_kip_get_cover_state, -+ .state_name = ssam_kip_cover_state_name, -+ .state_is_tablet_mode = ssam_kip_cover_state_is_tablet_mode, -+ }, -+ .event = { -+ .reg = SSAM_EVENT_REGISTRY_SAM, -+ .id = { -+ .target_category = SSAM_SSH_TC_KIP, -+ .instance = 0, -+ }, -+ .mask = SSAM_EVENT_MASK_TARGET, -+ }, -+}; -+ -+ -+/* -- SSAM POS tablet switch implementation. -------------------------------- */ -+ -+static bool tablet_mode_in_slate_state = true; -+module_param(tablet_mode_in_slate_state, bool, S_IRUGO); -+MODULE_PARM_DESC(tablet_mode_in_slate_state, "Enable tablet mode in slate device posture, default is 'true'"); -+ -+#define SSAM_EVENT_POS_CID_POSTURE_CHANGED 0x03 -+#define SSAM_POS_MAX_SOURCES 4 -+ -+enum ssam_pos_state { -+ SSAM_POS_POSTURE_LID_CLOSED = 0x00, -+ SSAM_POS_POSTURE_LAPTOP = 0x01, -+ SSAM_POS_POSTURE_SLATE = 0x02, -+ SSAM_POS_POSTURE_TABLET = 0x03, -+}; -+ -+struct ssam_sources_list { -+ __le32 count; -+ __le32 id[SSAM_POS_MAX_SOURCES]; -+} __packed; -+ -+static const char* ssam_pos_state_name(struct ssam_tablet_sw *sw, u32 state) -+{ -+ switch (state) { -+ case SSAM_POS_POSTURE_LID_CLOSED: -+ return "closed"; -+ -+ case SSAM_POS_POSTURE_LAPTOP: -+ return "laptop"; -+ -+ case SSAM_POS_POSTURE_SLATE: -+ return "slate"; -+ -+ case SSAM_POS_POSTURE_TABLET: -+ return "tablet"; -+ -+ default: -+ dev_warn(&sw->sdev->dev, "unknown device posture: %u\n", state); -+ return ""; -+ } -+} -+ -+static bool ssam_pos_state_is_tablet_mode(struct ssam_tablet_sw *sw, u32 state) -+{ -+ switch (state) { -+ case SSAM_POS_POSTURE_LAPTOP: -+ case SSAM_POS_POSTURE_LID_CLOSED: -+ return false; -+ -+ case SSAM_POS_POSTURE_SLATE: -+ return tablet_mode_in_slate_state; -+ -+ case SSAM_POS_POSTURE_TABLET: -+ return true; -+ -+ default: -+ dev_warn(&sw->sdev->dev, "unknown device posture: %u\n", state); -+ return true; -+ } -+} -+ -+static int ssam_pos_get_sources_list(struct ssam_tablet_sw *sw, struct ssam_sources_list *sources) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ int status; -+ -+ rqst.target_category = SSAM_SSH_TC_POS; -+ rqst.target_id = 0x01; -+ rqst.command_id = 0x01; -+ rqst.instance_id = 0x00; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = 0; -+ rqst.payload = NULL; -+ -+ rsp.capacity = sizeof(*sources); -+ rsp.length = 0; -+ rsp.pointer = (u8 *)sources; -+ -+ status = ssam_retry(ssam_request_sync_onstack, sw->sdev->ctrl, &rqst, &rsp, 0); -+ if (status) -+ return status; -+ -+ /* We need at least the 'sources->count' field. */ -+ if (rsp.length < sizeof(__le32)) { -+ dev_err(&sw->sdev->dev, "received source list response is too small\n"); -+ return -EPROTO; -+ } -+ -+ /* Make sure 'sources->count' matches with the response length. */ -+ if (get_unaligned_le32(&sources->count) * sizeof(__le32) + sizeof(__le32) != rsp.length) { -+ dev_err(&sw->sdev->dev, "mismatch between number of sources and response size\n"); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static int ssam_pos_get_source(struct ssam_tablet_sw *sw, u32 *source_id) -+{ -+ struct ssam_sources_list sources = {}; -+ int status; -+ -+ status = ssam_pos_get_sources_list(sw, &sources); -+ if (status) -+ return status; -+ -+ if (sources.count == 0) { -+ dev_err(&sw->sdev->dev, "no posture sources found\n"); -+ return -ENODEV; -+ } -+ -+ /* -+ * We currently don't know what to do with more than one posture souce. -+ * At the moment, only one source seems to be used/provided. The -+ * WARN_ON() here should hopefully let us know quickly once there is a -+ * device that provides multiple sources, at which point we can then -+ * try to figure out how to handle them. -+ */ -+ WARN_ON(sources.count > 1); -+ -+ *source_id = get_unaligned_le32(&sources.id[0]); -+ return 0; -+} -+ -+SSAM_DEFINE_SYNC_REQUEST_WR(__ssam_pos_get_posture_for_source, __le32, __le32, { -+ .target_category = SSAM_SSH_TC_POS, -+ .target_id = 0x01, -+ .command_id = 0x02, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_pos_get_posture_for_source(struct ssam_tablet_sw *sw, u32 source_id, u32 *posture) -+{ -+ __le32 source_le = cpu_to_le32(source_id); -+ __le32 rspval_le = 0; -+ int status; -+ -+ status = ssam_retry(__ssam_pos_get_posture_for_source, sw->sdev->ctrl, -+ &source_le, &rspval_le); -+ if (status) -+ return status; -+ -+ *posture = le32_to_cpu(rspval_le); -+ return 0; -+} -+ -+static int ssam_pos_get_posture(struct ssam_tablet_sw *sw, u32 *state) -+{ -+ u32 source_id; -+ int status; -+ -+ status = ssam_pos_get_source(sw, &source_id); -+ if (status) { -+ dev_err(&sw->sdev->dev, "failed to get posture source ID: %d\n", status); -+ return status; -+ } -+ -+ status = ssam_pos_get_posture_for_source(sw, source_id, state); -+ if (status) { -+ dev_err(&sw->sdev->dev, "failed to get posture value for source %u: %d\n", -+ source_id, status); -+ return status; -+ } -+ -+ return 0; -+} -+ -+static u32 ssam_pos_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_tablet_sw *sw = container_of(nf, struct ssam_tablet_sw, notif); -+ -+ if (event->command_id != SSAM_EVENT_POS_CID_POSTURE_CHANGED) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length != sizeof(__le32) * 3) { -+ dev_warn(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); -+ } -+ -+ schedule_work(&sw->update_work); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { -+ .dev = { -+ .name = "Microsoft Surface POS Tablet Mode Switch", -+ .phys = "ssam/01:26:01:00:01/input0", -+ }, -+ .ops = { -+ .notify = ssam_pos_sw_notif, -+ .get_state = ssam_pos_get_posture, -+ .state_name = ssam_pos_state_name, -+ .state_is_tablet_mode = ssam_pos_state_is_tablet_mode, -+ }, -+ .event = { -+ .reg = SSAM_EVENT_REGISTRY_SAM, -+ .id = { -+ .target_category = SSAM_SSH_TC_POS, -+ .instance = 0, -+ }, -+ .mask = SSAM_EVENT_MASK_TARGET, -+ }, -+}; -+ -+ -+/* -- Driver registration. -------------------------------------------------- */ -+ -+static const struct ssam_device_id ssam_tablet_sw_match[] = { -+ { SSAM_SDEV(KIP, 0x01, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, -+ { SSAM_SDEV(POS, 0x01, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); -+ -+static struct ssam_device_driver ssam_tablet_sw_driver = { -+ .probe = ssam_tablet_sw_probe, -+ .remove = ssam_tablet_sw_remove, -+ .match_table = ssam_tablet_sw_match, -+ .driver = { -+ .name = "surface_aggregator_tablet_mode_switch", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_tablet_sw_pm_ops, -+ }, -+}; -+module_ssam_device_driver(ssam_tablet_sw_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Tablet mode switch driver for Surface devices using the Surface Aggregator Module"); -+MODULE_LICENSE("GPL"); --- -2.37.2 - -From 14f40e0eba6884c5eb7e71d0852463db08c9d3b1 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 27 Oct 2021 22:33:03 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for tablet - mode switch on Surface Pro 8 - -Add a KIP subsystem tablet-mode switch device for the Surface Pro 8. -The respective driver for this device provides SW_TABLET_MODE input -events for user-space based on the state of the keyboard cover (e.g. -detached, folded-back, normal/laptop mode). - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 8 +++++++- - 1 file changed, 7 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index bf3303f1aa71..8f249df673a4 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -77,6 +77,12 @@ static const struct software_node ssam_node_tmp_pprof = { - .parent = &ssam_node_root, - }; - -+/* Tablet-mode switch via KIP subsystem. */ -+static const struct software_node ssam_node_kip_tablet_switch = { -+ .name = "ssam:01:0e:01:00:01", -+ .parent = &ssam_node_root, -+}; -+ - /* DTX / detachment-system device (Surface Book 3). */ - static const struct software_node ssam_node_bas_dtx = { - .name = "ssam:01:11:01:00:00", -@@ -264,11 +270,11 @@ static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -+ &ssam_node_kip_tablet_switch, - &ssam_node_hid_kip_keyboard, - &ssam_node_hid_kip_penstash, - &ssam_node_hid_kip_touchpad, - &ssam_node_hid_kip_iid5, -- /* TODO: Add support for tablet mode switch. */ - NULL, - }; - --- -2.37.2 - -From 41c17d879bc5f5d1700673e7cbb8f1bd4862cafc Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 16 Jun 2022 02:30:16 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for tablet - mode switch on Surface Laptop Studio - -Add a POS subsystem tablet-mode switch device for the Surface Laptop -Studio. The respective driver for this device provides SW_TABLET_MODE -input events for user-space based on the posture of the screen. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 8f249df673a4..f1c5905f1c16 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -191,6 +191,12 @@ static const struct software_node ssam_node_hid_kip_iid5 = { - .parent = &ssam_node_hub_kip, - }; - -+/* Tablet-mode switch via POS subsystem. */ -+static const struct software_node ssam_node_pos_tablet_switch = { -+ .name = "ssam:01:26:01:00:01", -+ .parent = &ssam_node_root, -+}; -+ - /* - * Devices for 5th- and 6th-generations models: - * - Surface Book 2, -@@ -237,6 +243,7 @@ static const struct software_node *ssam_node_group_sls[] = { - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -+ &ssam_node_pos_tablet_switch, - &ssam_node_hid_tid1_keyboard, - &ssam_node_hid_tid1_penstash, - &ssam_node_hid_tid1_touchpad, --- -2.37.2 - -From 7814ea13e040abe5f2e273865e28f6bd6bc51f17 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 21 May 2022 00:30:46 +0200 -Subject: [PATCH] platform/surface: aggregator: Move device registry helper - functions to core module - -Move helper functions for client device registration to the core module. -This simplifies addition of future DT/OF support and also allows us to -split out the device hub drivers into their own module. - -At the same time, also improve device node validation a bit by not -silently skipping devices with invalid device UID specifiers. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/bus.c | 176 ++++++++++++++++-- - .../surface/surface_aggregator_registry.c | 75 +------- - include/linux/surface_aggregator/device.h | 37 ++++ - 3 files changed, 199 insertions(+), 89 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -index abbbb5b08b07..4bba60884bb5 100644 ---- a/drivers/platform/surface/aggregator/bus.c -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -6,6 +6,7 @@ - */ - - #include -+#include - #include - - #include -@@ -14,6 +15,9 @@ - #include "bus.h" - #include "controller.h" - -+ -+/* -- Device and bus functions. --------------------------------------------- */ -+ - static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, - char *buf) - { -@@ -363,6 +367,162 @@ void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) - } - EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); - -+ -+/* -- Bus registration. ----------------------------------------------------- */ -+ -+/** -+ * ssam_bus_register() - Register and set-up the SSAM client device bus. -+ */ -+int ssam_bus_register(void) -+{ -+ return bus_register(&ssam_bus_type); -+} -+ -+/** -+ * ssam_bus_unregister() - Unregister the SSAM client device bus. -+ */ -+void ssam_bus_unregister(void) -+{ -+ return bus_unregister(&ssam_bus_type); -+} -+ -+ -+/* -- Helpers for controller and hub devices. ------------------------------- */ -+ -+static int ssam_device_uid_from_string(const char *str, struct ssam_device_uid *uid) -+{ -+ u8 d, tc, tid, iid, fn; -+ int n; -+ -+ n = sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); -+ if (n != 5) -+ return -EINVAL; -+ -+ uid->domain = d; -+ uid->category = tc; -+ uid->target = tid; -+ uid->instance = iid; -+ uid->function = fn; -+ -+ return 0; -+} -+ -+static int ssam_get_uid_for_node(struct fwnode_handle *node, struct ssam_device_uid *uid) -+{ -+ const char* str = fwnode_get_name(node); -+ -+ /* -+ * To simplify definitions of firmware nodes, we set the device name -+ * based on the UID of the device, prefixed with "ssam:". -+ */ -+ if (strncmp(str, "ssam:", strlen("ssam:")) != 0) -+ return -ENODEV; -+ -+ str += strlen("ssam:"); -+ return ssam_device_uid_from_string(str, uid); -+} -+ -+static int ssam_add_client_device(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ struct ssam_device_uid uid; -+ struct ssam_device *sdev; -+ int status; -+ -+ status = ssam_get_uid_for_node(node, &uid); -+ if (status) -+ return status; -+ -+ sdev = ssam_device_alloc(ctrl, uid); -+ if (!sdev) -+ return -ENOMEM; -+ -+ sdev->dev.parent = parent; -+ sdev->dev.fwnode = node; -+ -+ status = ssam_device_add(sdev); -+ if (status) -+ ssam_device_put(sdev); -+ -+ return status; -+} -+ -+/** -+ * __ssam_register_clients() - Register client devices defined under the -+ * given firmware node as children of the given device. -+ * @parent: The parent device under which clients should be registered. -+ * @ctrl: The controller with which client should be registered. -+ * @node: The firmware node holding definitions of the devices to be added. -+ * -+ * Register all clients that have been defined as children of the given root -+ * firmware node as children of the given parent device. The respective child -+ * firmware nodes will be associated with the correspondingly created child -+ * devices. -+ * -+ * The given controller will be used to instantiate the new devices. See -+ * ssam_device_add() for details. -+ * -+ * Note that, generally, the use of either ssam_device_register_clients() or -+ * ssam_register_clients() should be preferred as they directly use the -+ * firmware node and/or controller associated with the given device. This -+ * function is only intended for use when different device specifications (e.g. -+ * ACPI and firmware nodes) need to be combined (as is done in the platform hub -+ * of the device registry). -+ * -+ * Return: Returns zero on success, nonzero on failure. -+ */ -+int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ struct fwnode_handle *child; -+ int status; -+ -+ fwnode_for_each_child_node(node, child) { -+ /* -+ * Try to add the device specified in the firmware node. If -+ * this fails with -ENODEV, the node does not specify any SSAM -+ * device, so ignore it and continue with the next one. -+ */ -+ status = ssam_add_client_device(parent, ctrl, child); -+ if (status && status != -ENODEV) -+ goto err; -+ } -+ -+ return 0; -+err: -+ ssam_remove_clients(parent); -+ return status; -+} -+EXPORT_SYMBOL_GPL(__ssam_register_clients); -+ -+/** -+ * ssam_register_clients() - Register all client devices defined under the -+ * given parent device. -+ * @dev: The parent device under which clients should be registered. -+ * @ctrl: The controller with which client should be registered. -+ * -+ * Register all clients that have via firmware nodes been defined as children -+ * of the given (parent) device. The respective child firmware nodes will be -+ * associated with the correspondingly created child devices. -+ * -+ * The given controller will be used to instantiate the new devices. See -+ * ssam_device_add() for details. -+ * -+ * Return: Returns zero on success, nonzero on failure. -+ */ -+int ssam_register_clients(struct device *dev, struct ssam_controller *ctrl) -+{ -+ struct fwnode_handle *node; -+ int status; -+ -+ node = fwnode_handle_get(dev_fwnode(dev)); -+ status = __ssam_register_clients(dev, ctrl, node); -+ fwnode_handle_put(node); -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(ssam_register_clients); -+ - static int ssam_remove_device(struct device *dev, void *_data) - { - struct ssam_device *sdev = to_ssam_device(dev); -@@ -387,19 +547,3 @@ void ssam_remove_clients(struct device *dev) - device_for_each_child_reverse(dev, NULL, ssam_remove_device); - } - EXPORT_SYMBOL_GPL(ssam_remove_clients); -- --/** -- * ssam_bus_register() - Register and set-up the SSAM client device bus. -- */ --int ssam_bus_register(void) --{ -- return bus_register(&ssam_bus_type); --} -- --/** -- * ssam_bus_unregister() - Unregister the SSAM client device bus. -- */ --void ssam_bus_unregister(void) --{ -- return bus_unregister(&ssam_bus_type); --} -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index f1c5905f1c16..c680792a037e 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -286,76 +286,6 @@ static const struct software_node *ssam_node_group_sp8[] = { - }; - - --/* -- Device registry helper functions. ------------------------------------- */ -- --static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) --{ -- u8 d, tc, tid, iid, fn; -- int n; -- -- n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); -- if (n != 5) -- return -EINVAL; -- -- uid->domain = d; -- uid->category = tc; -- uid->target = tid; -- uid->instance = iid; -- uid->function = fn; -- -- return 0; --} -- --static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, -- struct fwnode_handle *node) --{ -- struct ssam_device_uid uid; -- struct ssam_device *sdev; -- int status; -- -- status = ssam_uid_from_string(fwnode_get_name(node), &uid); -- if (status) -- return status; -- -- sdev = ssam_device_alloc(ctrl, uid); -- if (!sdev) -- return -ENOMEM; -- -- sdev->dev.parent = parent; -- sdev->dev.fwnode = node; -- -- status = ssam_device_add(sdev); -- if (status) -- ssam_device_put(sdev); -- -- return status; --} -- --static int ssam_hub_register_clients(struct device *parent, struct ssam_controller *ctrl, -- struct fwnode_handle *node) --{ -- struct fwnode_handle *child; -- int status; -- -- fwnode_for_each_child_node(node, child) { -- /* -- * Try to add the device specified in the firmware node. If -- * this fails with -EINVAL, the node does not specify any SSAM -- * device, so ignore it and continue with the next one. -- */ -- -- status = ssam_hub_add_device(parent, ctrl, child); -- if (status && status != -EINVAL) -- goto err; -- } -- -- return 0; --err: -- ssam_remove_clients(parent); -- return status; --} -- -- - /* -- SSAM generic subsystem hub driver framework. -------------------------- */ - - enum ssam_hub_state { -@@ -385,7 +315,6 @@ struct ssam_hub { - static void ssam_hub_update_workfn(struct work_struct *work) - { - struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); -- struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); - enum ssam_hub_state state; - int status = 0; - -@@ -425,7 +354,7 @@ static void ssam_hub_update_workfn(struct work_struct *work) - hub->state = state; - - if (hub->state == SSAM_HUB_CONNECTED) -- status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); -+ status = ssam_device_register_clients(hub->sdev); - else - ssam_remove_clients(&hub->sdev->dev); - -@@ -769,7 +698,7 @@ static int ssam_platform_hub_probe(struct platform_device *pdev) - - set_secondary_fwnode(&pdev->dev, root); - -- status = ssam_hub_register_clients(&pdev->dev, ctrl, root); -+ status = __ssam_register_clients(&pdev->dev, ctrl, root); - if (status) { - set_secondary_fwnode(&pdev->dev, NULL); - software_node_unregister_node_group(nodes); -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 6cf7e80312d5..6e75fb605479 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -375,11 +375,48 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); - /* -- Helpers for controller and hub devices. ------------------------------- */ - - #ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+ -+int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node); -+int ssam_register_clients(struct device *dev, struct ssam_controller *ctrl); - void ssam_remove_clients(struct device *dev); -+ - #else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+static inline int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ return 0; -+} -+ -+static inline int ssam_register_clients(struct device *dev, struct ssam_controller *ctrl) -+{ -+ return 0; -+} -+ - static inline void ssam_remove_clients(struct device *dev) {} -+ - #endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ - -+/** -+ * ssam_device_register_clients() - Register all client devices defined under -+ * the given SSAM parent device. -+ * @sdev: The parent device under which clients should be registered. -+ * -+ * Register all clients that have via firmware nodes been defined as children -+ * of the given (parent) device. The respective child firmware nodes will be -+ * associated with the correspondingly created child devices. -+ * -+ * The controller used by the parent device will be used to instantiate the new -+ * devices. See ssam_device_add() for details. -+ * -+ * Return: Returns zero on success, nonzero on failure. -+ */ -+static inline int ssam_device_register_clients(struct ssam_device *sdev) -+{ -+ return ssam_register_clients(&sdev->dev, sdev->ctrl); -+} -+ - - /* -- Helpers for client-device requests. ----------------------------------- */ - --- -2.37.2 - -From 446587a52772adcbdf620ec48d04414f062af173 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 21 May 2022 00:39:56 +0200 -Subject: [PATCH] platform/surface: aggregator: Move subsystem hub drivers to - their own module - -Split out subsystem device hub drivers into their own module. This -allows us to load the hub drivers separately from the registry, which -will help future DT/OF support. - -While doing so, also remove a small bit of code duplication. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - MAINTAINERS | 6 + - drivers/platform/surface/Kconfig | 35 +- - drivers/platform/surface/Makefile | 1 + - .../platform/surface/surface_aggregator_hub.c | 371 ++++++++++++++++++ - .../surface/surface_aggregator_registry.c | 371 +----------------- - 5 files changed, 410 insertions(+), 374 deletions(-) - create mode 100644 drivers/platform/surface/surface_aggregator_hub.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 852231f4e469..f96b3dba903a 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -13107,6 +13107,12 @@ F: include/linux/surface_acpi_notify.h - F: include/linux/surface_aggregator/ - F: include/uapi/linux/surface_aggregator/ - -+MICROSOFT SURFACE SYSTEM AGGREGATOR HUB DRIVER -+M: Maximilian Luz -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/platform/surface/surface_aggregator_hub.c -+ - MICROTEK X6 SCANNER - M: Oliver Neukum - S: Maintained -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index b152e930cc84..b629e82af97c 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -72,18 +72,45 @@ config SURFACE_AGGREGATOR_CDEV - The provided interface is intended for debugging and development only, - and should not be used otherwise. - -+config SURFACE_AGGREGATOR_HUB -+ tristate "Surface System Aggregator Module Subsystem Device Hubs" -+ depends on SURFACE_AGGREGATOR -+ depends on SURFACE_AGGREGATOR_BUS -+ help -+ Device-hub drivers for Surface System Aggregator Module (SSAM) subsystem -+ devices. -+ -+ Provides subsystem hub drivers which manage client devices on various -+ SSAM subsystems. In some subsystems, notably the BAS subsystem managing -+ devices contained in the base of the Surface Book 3 and the KIP subsystem -+ managing type-cover devices in the Surface Pro 8 and Surface Pro X, -+ devices can be (hot-)removed. Hub devices and drivers are required to -+ manage these subdevices. -+ -+ Devices managed via these hubs are: -+ - Battery/AC devices (Surface Book 3). -+ - HID input devices (7th-generation and later models with detachable -+ input devices). -+ -+ Select M (recommended) or Y here if you want support for the above -+ mentioned devices on the corresponding Surface models. Without this -+ module, the respective devices mentioned above will not be instantiated -+ and thus any functionality provided by them will be missing, even when -+ drivers for these devices are present. This module only provides the -+ respective subsystem hubs. Both drivers and device specification (e.g. -+ via the Surface Aggregator Registry) for these devices still need to be -+ selected via other options. -+ - config SURFACE_AGGREGATOR_REGISTRY - tristate "Surface System Aggregator Module Device Registry" - depends on SURFACE_AGGREGATOR - depends on SURFACE_AGGREGATOR_BUS - help -- Device-registry and device-hubs for Surface System Aggregator Module -- (SSAM) devices. -+ Device-registry for Surface System Aggregator Module (SSAM) devices. - - Provides a module and driver which act as a device-registry for SSAM - client devices that cannot be detected automatically, e.g. via ACPI. -- Such devices are instead provided via this registry and attached via -- device hubs, also provided in this module. -+ Such devices are instead provided and managed via this registry. - - Devices provided via this registry are: - - Platform profile (performance-/cooling-mode) device (5th- and later -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 18b27898543e..53344330939b 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -9,6 +9,7 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o -+obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o -diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c -new file mode 100644 -index 000000000000..43061514be38 ---- /dev/null -+++ b/drivers/platform/surface/surface_aggregator_hub.c -@@ -0,0 +1,371 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Driver for Surface System Aggregator Module (SSAM) subsystem device hubs. -+ * -+ * Provides a driver for SSAM subsystems device hubs. This driver performs -+ * instantiation of the devices managed by said hubs and takes care of -+ * (hot-)removal. -+ * -+ * Copyright (C) 2020-2022 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- SSAM generic subsystem hub driver framework. -------------------------- */ -+ -+enum ssam_hub_state { -+ SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ -+ SSAM_HUB_CONNECTED, -+ SSAM_HUB_DISCONNECTED, -+}; -+ -+enum ssam_hub_flags { -+ SSAM_HUB_HOT_REMOVED, -+}; -+ -+struct ssam_hub; -+ -+struct ssam_hub_ops { -+ int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); -+}; -+ -+struct ssam_hub { -+ struct ssam_device *sdev; -+ -+ enum ssam_hub_state state; -+ unsigned long flags; -+ -+ struct delayed_work update_work; -+ unsigned long connect_delay; -+ -+ struct ssam_event_notifier notif; -+ struct ssam_hub_ops ops; -+}; -+ -+struct ssam_hub_desc { -+ struct { -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ enum ssam_event_mask mask; -+ } event; -+ -+ struct { -+ u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event); -+ int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); -+ } ops; -+ -+ unsigned long connect_delay_ms; -+}; -+ -+static void ssam_hub_update_workfn(struct work_struct *work) -+{ -+ struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); -+ enum ssam_hub_state state; -+ int status = 0; -+ -+ status = hub->ops.get_state(hub, &state); -+ if (status) -+ return; -+ -+ /* -+ * There is a small possibility that hub devices were hot-removed and -+ * re-added before we were able to remove them here. In that case, both -+ * the state returned by get_state() and the state of the hub will -+ * equal SSAM_HUB_CONNECTED and we would bail early below, which would -+ * leave child devices without proper (re-)initialization and the -+ * hot-remove flag set. -+ * -+ * Therefore, we check whether devices have been hot-removed via an -+ * additional flag on the hub and, in this case, override the returned -+ * hub state. In case of a missed disconnect (i.e. get_state returned -+ * "connected"), we further need to re-schedule this work (with the -+ * appropriate delay) as the actual connect work submission might have -+ * been merged with this one. -+ * -+ * This then leads to one of two cases: Either we submit an unnecessary -+ * work item (which will get ignored via either the queue or the state -+ * checks) or, in the unlikely case that the work is actually required, -+ * double the normal connect delay. -+ */ -+ if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { -+ if (state == SSAM_HUB_CONNECTED) -+ schedule_delayed_work(&hub->update_work, hub->connect_delay); -+ -+ state = SSAM_HUB_DISCONNECTED; -+ } -+ -+ if (hub->state == state) -+ return; -+ hub->state = state; -+ -+ if (hub->state == SSAM_HUB_CONNECTED) -+ status = ssam_device_register_clients(hub->sdev); -+ else -+ ssam_remove_clients(&hub->sdev->dev); -+ -+ if (status) -+ dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); -+} -+ -+static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (is_ssam_device(dev)) -+ ssam_device_mark_hot_removed(sdev); -+ -+ return 0; -+} -+ -+static void ssam_hub_update(struct ssam_hub *hub, bool connected) -+{ -+ unsigned long delay; -+ -+ /* Mark devices as hot-removed before we remove any. */ -+ if (!connected) { -+ set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); -+ device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); -+ } -+ -+ /* -+ * Delay update when the base/keyboard cover is being connected to give -+ * devices/EC some time to set up. -+ */ -+ delay = connected ? hub->connect_delay : 0; -+ -+ schedule_delayed_work(&hub->update_work, delay); -+} -+ -+static int __maybe_unused ssam_hub_resume(struct device *dev) -+{ -+ struct ssam_hub *hub = dev_get_drvdata(dev); -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); -+ -+static int ssam_hub_probe(struct ssam_device *sdev) -+{ -+ const struct ssam_hub_desc *desc; -+ struct ssam_hub *hub; -+ int status; -+ -+ desc = ssam_device_get_match_data(sdev); -+ if (!desc) { -+ WARN(1, "no driver match data specified"); -+ return -EINVAL; -+ } -+ -+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -+ if (!hub) -+ return -ENOMEM; -+ -+ hub->sdev = sdev; -+ hub->state = SSAM_HUB_UNINITIALIZED; -+ -+ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -+ hub->notif.base.fn = desc->ops.notify; -+ hub->notif.event.reg = desc->event.reg; -+ hub->notif.event.id = desc->event.id; -+ hub->notif.event.mask = desc->event.mask; -+ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ hub->connect_delay = msecs_to_jiffies(desc->connect_delay_ms); -+ hub->ops.get_state = desc->ops.get_state; -+ -+ INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, hub); -+ -+ status = ssam_device_notifier_register(sdev, &hub->notif); -+ if (status) -+ return status; -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+ -+static void ssam_hub_remove(struct ssam_device *sdev) -+{ -+ struct ssam_hub *hub = ssam_device_get_drvdata(sdev); -+ -+ ssam_device_notifier_unregister(sdev, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); -+ ssam_remove_clients(&sdev->dev); -+} -+ -+ -+/* -- SSAM base-subsystem hub driver. --------------------------------------- */ -+ -+/* -+ * Some devices (especially battery) may need a bit of time to be fully usable -+ * after being (re-)connected. This delay has been determined via -+ * experimentation. -+ */ -+#define SSAM_BASE_UPDATE_CONNECT_DELAY 2500 -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0d, -+ .instance_id = 0x00, -+}); -+ -+#define SSAM_BAS_OPMODE_TABLET 0x00 -+#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c -+ -+static int ssam_base_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) -+{ -+ u8 opmode; -+ int status; -+ -+ status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); -+ return status; -+ } -+ -+ if (opmode != SSAM_BAS_OPMODE_TABLET) -+ *state = SSAM_HUB_CONNECTED; -+ else -+ *state = SSAM_HUB_DISCONNECTED; -+ -+ return 0; -+} -+ -+static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -+ -+ if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) -+ return 0; -+ -+ if (event->length < 1) { -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ ssam_hub_update(hub, event->data[0]); -+ -+ /* -+ * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -+ * consumed by the detachment system driver. We're just a (more or less) -+ * silent observer. -+ */ -+ return 0; -+} -+ -+static const struct ssam_hub_desc base_hub = { -+ .event = { -+ .reg = SSAM_EVENT_REGISTRY_SAM, -+ .id = { -+ .target_category = SSAM_SSH_TC_BAS, -+ .instance = 0, -+ }, -+ .mask = SSAM_EVENT_MASK_NONE, -+ }, -+ .ops = { -+ .notify = ssam_base_hub_notif, -+ .get_state = ssam_base_hub_query_state, -+ }, -+ .connect_delay_ms = SSAM_BASE_UPDATE_CONNECT_DELAY, -+}; -+ -+ -+/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ -+ -+/* -+ * Some devices may need a bit of time to be fully usable after being -+ * (re-)connected. This delay has been determined via experimentation. -+ */ -+#define SSAM_KIP_UPDATE_CONNECT_DELAY 250 -+ -+#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_query_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x2c, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) -+{ -+ int status; -+ u8 connected; -+ -+ status = ssam_retry(__ssam_kip_query_state, hub->sdev->ctrl, &connected); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); -+ return status; -+ } -+ -+ *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; -+ return 0; -+} -+ -+static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) { -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ ssam_hub_update(hub, event->data[0]); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static const struct ssam_hub_desc kip_hub = { -+ .event = { -+ .reg = SSAM_EVENT_REGISTRY_SAM, -+ .id = { -+ .target_category = SSAM_SSH_TC_KIP, -+ .instance = 0, -+ }, -+ .mask = SSAM_EVENT_MASK_TARGET, -+ }, -+ .ops = { -+ .notify = ssam_kip_hub_notif, -+ .get_state = ssam_kip_hub_query_state, -+ }, -+ .connect_delay_ms = SSAM_KIP_UPDATE_CONNECT_DELAY, -+}; -+ -+ -+/* -- Driver registration. -------------------------------------------------- */ -+ -+static const struct ssam_device_id ssam_hub_match[] = { -+ { SSAM_VDEV(HUB, 0x01, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, -+ { SSAM_VDEV(HUB, 0x02, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, -+ { } -+}; -+MODULE_DEVICE_TABLE(ssam, ssam_hub_match); -+ -+static struct ssam_device_driver ssam_subsystem_hub_driver = { -+ .probe = ssam_hub_probe, -+ .remove = ssam_hub_remove, -+ .match_table = ssam_hub_match, -+ .driver = { -+ .name = "surface_aggregator_subsystem_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_hub_pm_ops, -+ }, -+}; -+module_ssam_device_driver(ssam_subsystem_hub_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Subsystem device hub driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index c680792a037e..0cbb7f3a6b2d 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -11,14 +11,11 @@ - - #include - #include --#include - #include - #include - #include - #include --#include - --#include - #include - - -@@ -286,335 +283,6 @@ static const struct software_node *ssam_node_group_sp8[] = { - }; - - --/* -- SSAM generic subsystem hub driver framework. -------------------------- */ -- --enum ssam_hub_state { -- SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ -- SSAM_HUB_CONNECTED, -- SSAM_HUB_DISCONNECTED, --}; -- --enum ssam_hub_flags { -- SSAM_HUB_HOT_REMOVED, --}; -- --struct ssam_hub { -- struct ssam_device *sdev; -- -- enum ssam_hub_state state; -- unsigned long flags; -- -- struct delayed_work update_work; -- unsigned long connect_delay; -- -- struct ssam_event_notifier notif; -- -- int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); --}; -- --static void ssam_hub_update_workfn(struct work_struct *work) --{ -- struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); -- enum ssam_hub_state state; -- int status = 0; -- -- status = hub->get_state(hub, &state); -- if (status) -- return; -- -- /* -- * There is a small possibility that hub devices were hot-removed and -- * re-added before we were able to remove them here. In that case, both -- * the state returned by get_state() and the state of the hub will -- * equal SSAM_HUB_CONNECTED and we would bail early below, which would -- * leave child devices without proper (re-)initialization and the -- * hot-remove flag set. -- * -- * Therefore, we check whether devices have been hot-removed via an -- * additional flag on the hub and, in this case, override the returned -- * hub state. In case of a missed disconnect (i.e. get_state returned -- * "connected"), we further need to re-schedule this work (with the -- * appropriate delay) as the actual connect work submission might have -- * been merged with this one. -- * -- * This then leads to one of two cases: Either we submit an unnecessary -- * work item (which will get ignored via either the queue or the state -- * checks) or, in the unlikely case that the work is actually required, -- * double the normal connect delay. -- */ -- if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { -- if (state == SSAM_HUB_CONNECTED) -- schedule_delayed_work(&hub->update_work, hub->connect_delay); -- -- state = SSAM_HUB_DISCONNECTED; -- } -- -- if (hub->state == state) -- return; -- hub->state = state; -- -- if (hub->state == SSAM_HUB_CONNECTED) -- status = ssam_device_register_clients(hub->sdev); -- else -- ssam_remove_clients(&hub->sdev->dev); -- -- if (status) -- dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); --} -- --static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) --{ -- struct ssam_device *sdev = to_ssam_device(dev); -- -- if (is_ssam_device(dev)) -- ssam_device_mark_hot_removed(sdev); -- -- return 0; --} -- --static void ssam_hub_update(struct ssam_hub *hub, bool connected) --{ -- unsigned long delay; -- -- /* Mark devices as hot-removed before we remove any. */ -- if (!connected) { -- set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); -- device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); -- } -- -- /* -- * Delay update when the base/keyboard cover is being connected to give -- * devices/EC some time to set up. -- */ -- delay = connected ? hub->connect_delay : 0; -- -- schedule_delayed_work(&hub->update_work, delay); --} -- --static int __maybe_unused ssam_hub_resume(struct device *dev) --{ -- struct ssam_hub *hub = dev_get_drvdata(dev); -- -- schedule_delayed_work(&hub->update_work, 0); -- return 0; --} --static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); -- --static int ssam_hub_setup(struct ssam_device *sdev, struct ssam_hub *hub) --{ -- int status; -- -- hub->sdev = sdev; -- hub->state = SSAM_HUB_UNINITIALIZED; -- -- INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); -- -- ssam_device_set_drvdata(sdev, hub); -- -- status = ssam_device_notifier_register(sdev, &hub->notif); -- if (status) -- return status; -- -- schedule_delayed_work(&hub->update_work, 0); -- return 0; --} -- --static void ssam_hub_remove(struct ssam_device *sdev) --{ -- struct ssam_hub *hub = ssam_device_get_drvdata(sdev); -- -- ssam_device_notifier_unregister(sdev, &hub->notif); -- cancel_delayed_work_sync(&hub->update_work); -- ssam_remove_clients(&sdev->dev); --} -- -- --/* -- SSAM base-hub driver. ------------------------------------------------- */ -- --/* -- * Some devices (especially battery) may need a bit of time to be fully usable -- * after being (re-)connected. This delay has been determined via -- * experimentation. -- */ --#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) -- --SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { -- .target_category = SSAM_SSH_TC_BAS, -- .target_id = 0x01, -- .command_id = 0x0d, -- .instance_id = 0x00, --}); -- --#define SSAM_BAS_OPMODE_TABLET 0x00 --#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c -- --static int ssam_base_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) --{ -- u8 opmode; -- int status; -- -- status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); -- if (status < 0) { -- dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); -- return status; -- } -- -- if (opmode != SSAM_BAS_OPMODE_TABLET) -- *state = SSAM_HUB_CONNECTED; -- else -- *state = SSAM_HUB_DISCONNECTED; -- -- return 0; --} -- --static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) --{ -- struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -- -- if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) -- return 0; -- -- if (event->length < 1) { -- dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -- return 0; -- } -- -- ssam_hub_update(hub, event->data[0]); -- -- /* -- * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -- * consumed by the detachment system driver. We're just a (more or less) -- * silent observer. -- */ -- return 0; --} -- --static int ssam_base_hub_probe(struct ssam_device *sdev) --{ -- struct ssam_hub *hub; -- -- hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -- if (!hub) -- return -ENOMEM; -- -- hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -- hub->notif.base.fn = ssam_base_hub_notif; -- hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -- hub->notif.event.id.target_category = SSAM_SSH_TC_BAS, -- hub->notif.event.id.instance = 0, -- hub->notif.event.mask = SSAM_EVENT_MASK_NONE; -- hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -- -- hub->connect_delay = SSAM_BASE_UPDATE_CONNECT_DELAY; -- hub->get_state = ssam_base_hub_query_state; -- -- return ssam_hub_setup(sdev, hub); --} -- --static const struct ssam_device_id ssam_base_hub_match[] = { -- { SSAM_VDEV(HUB, 0x02, SSAM_SSH_TC_BAS, 0x00) }, -- { }, --}; -- --static struct ssam_device_driver ssam_base_hub_driver = { -- .probe = ssam_base_hub_probe, -- .remove = ssam_hub_remove, -- .match_table = ssam_base_hub_match, -- .driver = { -- .name = "surface_aggregator_base_hub", -- .probe_type = PROBE_PREFER_ASYNCHRONOUS, -- .pm = &ssam_hub_pm_ops, -- }, --}; -- -- --/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ -- --/* -- * Some devices may need a bit of time to be fully usable after being -- * (re-)connected. This delay has been determined via experimentation. -- */ --#define SSAM_KIP_UPDATE_CONNECT_DELAY msecs_to_jiffies(250) -- --#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c -- --SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_connection_state, u8, { -- .target_category = SSAM_SSH_TC_KIP, -- .target_id = 0x01, -- .command_id = 0x2c, -- .instance_id = 0x00, --}); -- --static int ssam_kip_get_connection_state(struct ssam_hub *hub, enum ssam_hub_state *state) --{ -- int status; -- u8 connected; -- -- status = ssam_retry(__ssam_kip_get_connection_state, hub->sdev->ctrl, &connected); -- if (status < 0) { -- dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); -- return status; -- } -- -- *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; -- return 0; --} -- --static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) --{ -- struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -- -- if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) -- return 0; /* Return "unhandled". */ -- -- if (event->length < 1) { -- dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -- return 0; -- } -- -- ssam_hub_update(hub, event->data[0]); -- return SSAM_NOTIF_HANDLED; --} -- --static int ssam_kip_hub_probe(struct ssam_device *sdev) --{ -- struct ssam_hub *hub; -- -- hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -- if (!hub) -- return -ENOMEM; -- -- hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -- hub->notif.base.fn = ssam_kip_hub_notif; -- hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -- hub->notif.event.id.target_category = SSAM_SSH_TC_KIP, -- hub->notif.event.id.instance = 0, -- hub->notif.event.mask = SSAM_EVENT_MASK_TARGET; -- hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -- -- hub->connect_delay = SSAM_KIP_UPDATE_CONNECT_DELAY; -- hub->get_state = ssam_kip_get_connection_state; -- -- return ssam_hub_setup(sdev, hub); --} -- --static const struct ssam_device_id ssam_kip_hub_match[] = { -- { SSAM_VDEV(HUB, 0x01, SSAM_SSH_TC_KIP, 0x00) }, -- { }, --}; -- --static struct ssam_device_driver ssam_kip_hub_driver = { -- .probe = ssam_kip_hub_probe, -- .remove = ssam_hub_remove, -- .match_table = ssam_kip_hub_match, -- .driver = { -- .name = "surface_kip_hub", -- .probe_type = PROBE_PREFER_ASYNCHRONOUS, -- .pm = &ssam_hub_pm_ops, -- }, --}; -- -- - /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ - - static const struct acpi_device_id ssam_platform_hub_match[] = { -@@ -727,44 +395,7 @@ static struct platform_driver ssam_platform_hub_driver = { - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - }, - }; -- -- --/* -- Module initialization. ------------------------------------------------ */ -- --static int __init ssam_device_hub_init(void) --{ -- int status; -- -- status = platform_driver_register(&ssam_platform_hub_driver); -- if (status) -- goto err_platform; -- -- status = ssam_device_driver_register(&ssam_base_hub_driver); -- if (status) -- goto err_base; -- -- status = ssam_device_driver_register(&ssam_kip_hub_driver); -- if (status) -- goto err_kip; -- -- return 0; -- --err_kip: -- ssam_device_driver_unregister(&ssam_base_hub_driver); --err_base: -- platform_driver_unregister(&ssam_platform_hub_driver); --err_platform: -- return status; --} --module_init(ssam_device_hub_init); -- --static void __exit ssam_device_hub_exit(void) --{ -- ssam_device_driver_unregister(&ssam_kip_hub_driver); -- ssam_device_driver_unregister(&ssam_base_hub_driver); -- platform_driver_unregister(&ssam_platform_hub_driver); --} --module_exit(ssam_device_hub_exit); -+module_platform_driver(ssam_platform_hub_driver); - - MODULE_AUTHOR("Maximilian Luz "); - MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); --- -2.37.2 - -From 19b914283b8e6567b0deb085053a8a781c1ad0fa Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 21 May 2022 00:57:40 +0200 -Subject: [PATCH] platform/surface: Update copyright year of various drivers - -Update the copyright of various Surface drivers to the current year. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/Kconfig | 2 +- - drivers/platform/surface/aggregator/Makefile | 2 +- - drivers/platform/surface/aggregator/bus.c | 2 +- - drivers/platform/surface/aggregator/bus.h | 2 +- - drivers/platform/surface/aggregator/controller.c | 2 +- - drivers/platform/surface/aggregator/controller.h | 2 +- - drivers/platform/surface/aggregator/core.c | 2 +- - drivers/platform/surface/aggregator/ssh_msgb.h | 2 +- - drivers/platform/surface/aggregator/ssh_packet_layer.c | 2 +- - drivers/platform/surface/aggregator/ssh_packet_layer.h | 2 +- - drivers/platform/surface/aggregator/ssh_parser.c | 2 +- - drivers/platform/surface/aggregator/ssh_parser.h | 2 +- - drivers/platform/surface/aggregator/ssh_request_layer.c | 2 +- - drivers/platform/surface/aggregator/ssh_request_layer.h | 2 +- - drivers/platform/surface/aggregator/trace.h | 2 +- - drivers/platform/surface/surface_acpi_notify.c | 2 +- - drivers/platform/surface/surface_aggregator_cdev.c | 2 +- - drivers/platform/surface/surface_aggregator_registry.c | 2 +- - drivers/platform/surface/surface_dtx.c | 2 +- - drivers/platform/surface/surface_gpe.c | 2 +- - drivers/platform/surface/surface_hotplug.c | 2 +- - drivers/platform/surface/surface_platform_profile.c | 2 +- - 22 files changed, 22 insertions(+), 22 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig -index cab020324256..c114f9dd5fe1 100644 ---- a/drivers/platform/surface/aggregator/Kconfig -+++ b/drivers/platform/surface/aggregator/Kconfig -@@ -1,5 +1,5 @@ - # SPDX-License-Identifier: GPL-2.0+ --# Copyright (C) 2019-2021 Maximilian Luz -+# Copyright (C) 2019-2022 Maximilian Luz - - menuconfig SURFACE_AGGREGATOR - tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" -diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile -index c0d550eda5cd..fdf664a217f9 100644 ---- a/drivers/platform/surface/aggregator/Makefile -+++ b/drivers/platform/surface/aggregator/Makefile -@@ -1,5 +1,5 @@ - # SPDX-License-Identifier: GPL-2.0+ --# Copyright (C) 2019-2021 Maximilian Luz -+# Copyright (C) 2019-2022 Maximilian Luz - - # For include/trace/define_trace.h to include trace.h - CFLAGS_core.o = -I$(src) -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -index 4bba60884bb5..96986042a257 100644 ---- a/drivers/platform/surface/aggregator/bus.c -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -2,7 +2,7 @@ - /* - * Surface System Aggregator Module bus and device integration. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h -index 6964ee84e79c..5b4dbf21906c 100644 ---- a/drivers/platform/surface/aggregator/bus.h -+++ b/drivers/platform/surface/aggregator/bus.h -@@ -2,7 +2,7 @@ - /* - * Surface System Aggregator Module bus and device integration. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_BUS_H -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index 6de834b52b63..43e765199137 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2,7 +2,7 @@ - /* - * Main SSAM/SSH controller structure and functionality. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h -index a0963c3562ff..f0d987abc51e 100644 ---- a/drivers/platform/surface/aggregator/controller.h -+++ b/drivers/platform/surface/aggregator/controller.h -@@ -2,7 +2,7 @@ - /* - * Main SSAM/SSH controller structure and functionality. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_CONTROLLER_H -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -index a62c5dfe42d6..1a6373dea109 100644 ---- a/drivers/platform/surface/aggregator/core.c -+++ b/drivers/platform/surface/aggregator/core.c -@@ -7,7 +7,7 @@ - * Handles communication via requests as well as enabling, disabling, and - * relaying of events. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h -index e562958ffdf0..f3ecad92eefd 100644 ---- a/drivers/platform/surface/aggregator/ssh_msgb.h -+++ b/drivers/platform/surface/aggregator/ssh_msgb.h -@@ -2,7 +2,7 @@ - /* - * SSH message builder functions. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c -index 8a4451c1ffe5..6748fe4ac5d5 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c -@@ -2,7 +2,7 @@ - /* - * SSH packet transport layer. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h -index 2eb329f0b91a..64633522f971 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.h -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h -@@ -2,7 +2,7 @@ - /* - * SSH packet transport layer. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H -diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c -index b77912f8f13b..a6f668694365 100644 ---- a/drivers/platform/surface/aggregator/ssh_parser.c -+++ b/drivers/platform/surface/aggregator/ssh_parser.c -@@ -2,7 +2,7 @@ - /* - * SSH message parser. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h -index 3bd6e180fd16..801d8fa69fb5 100644 ---- a/drivers/platform/surface/aggregator/ssh_parser.h -+++ b/drivers/platform/surface/aggregator/ssh_parser.h -@@ -2,7 +2,7 @@ - /* - * SSH message parser. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c -index 790f7f0eee98..f5565570f16c 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.c -@@ -2,7 +2,7 @@ - /* - * SSH request transport layer. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h -index 9c3cbae2d4bd..4e387a031351 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.h -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.h -@@ -2,7 +2,7 @@ - /* - * SSH request transport layer. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H -diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h -index cc9e73fbc18e..2a2c17771d01 100644 ---- a/drivers/platform/surface/aggregator/trace.h -+++ b/drivers/platform/surface/aggregator/trace.h -@@ -2,7 +2,7 @@ - /* - * Trace points for SSAM/SSH. - * -- * Copyright (C) 2020-2021 Maximilian Luz -+ * Copyright (C) 2020-2022 Maximilian Luz - */ - - #undef TRACE_SYSTEM -diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c -index 7b758f8cc137..b0a83255d060 100644 ---- a/drivers/platform/surface/surface_acpi_notify.c -+++ b/drivers/platform/surface/surface_acpi_notify.c -@@ -8,7 +8,7 @@ - * notifications sent from ACPI via the SAN interface by providing them to any - * registered external driver. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c -index 30fb50fde450..492c82e69182 100644 ---- a/drivers/platform/surface/surface_aggregator_cdev.c -+++ b/drivers/platform/surface/surface_aggregator_cdev.c -@@ -3,7 +3,7 @@ - * Provides user-space access to the SSAM EC via the /dev/surface/aggregator - * misc device. Intended for debugging and development. - * -- * Copyright (C) 2020-2021 Maximilian Luz -+ * Copyright (C) 2020-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 0cbb7f3a6b2d..d5655f6a4a41 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -6,7 +6,7 @@ - * cannot be auto-detected. Provides device-hubs and performs instantiation - * for these devices. - * -- * Copyright (C) 2020-2021 Maximilian Luz -+ * Copyright (C) 2020-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c -index 1203b9a82993..ed36944467f9 100644 ---- a/drivers/platform/surface/surface_dtx.c -+++ b/drivers/platform/surface/surface_dtx.c -@@ -8,7 +8,7 @@ - * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in - * use), or request detachment via user-space. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c -index ec66fde28e75..27365cbe1ee9 100644 ---- a/drivers/platform/surface/surface_gpe.c -+++ b/drivers/platform/surface/surface_gpe.c -@@ -4,7 +4,7 @@ - * properly configuring the respective GPEs. Required for wakeup via lid on - * newer Intel-based Microsoft Surface devices. - * -- * Copyright (C) 2020 Maximilian Luz -+ * Copyright (C) 2020-2022 Maximilian Luz - */ - - #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -diff --git a/drivers/platform/surface/surface_hotplug.c b/drivers/platform/surface/surface_hotplug.c -index cfcc15cfbacb..f004a2495201 100644 ---- a/drivers/platform/surface/surface_hotplug.c -+++ b/drivers/platform/surface/surface_hotplug.c -@@ -10,7 +10,7 @@ - * Event signaling is handled via ACPI, which will generate the appropriate - * device-check notifications to be picked up by the PCIe hot-plug driver. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c -index 6373d3b5eb7f..fbf2e11fd6ce 100644 ---- a/drivers/platform/surface/surface_platform_profile.c -+++ b/drivers/platform/surface/surface_platform_profile.c -@@ -3,7 +3,7 @@ - * Surface Platform Profile / Performance Mode driver for Surface System - * Aggregator Module (thermal subsystem). - * -- * Copyright (C) 2021 Maximilian Luz -+ * Copyright (C) 2021-2022 Maximilian Luz - */ - - #include --- -2.37.2 - -From 19a402021f13a0b2382271189e1dc017205ada1f Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jun 2022 20:42:00 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Rename HID device - nodes based on their function - -Rename HID device nodes based on their function. In particular, these -are nodes for firmware updates via the CFU mechanism (component firmware -update), HID based sensors, and a USB-C USCI client. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 18 +++++++++--------- - 1 file changed, 9 insertions(+), 9 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index d5655f6a4a41..b325fa0c5ee0 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -104,14 +104,14 @@ static const struct software_node ssam_node_hid_tid1_touchpad = { - .parent = &ssam_node_root, - }; - --/* HID device instance 6 (TID1, unknown HID device). */ --static const struct software_node ssam_node_hid_tid1_iid6 = { -+/* HID device instance 6 (TID1, HID sensor collection). */ -+static const struct software_node ssam_node_hid_tid1_sensors = { - .name = "ssam:01:15:01:06:00", - .parent = &ssam_node_root, - }; - --/* HID device instance 7 (TID1, unknown HID device). */ --static const struct software_node ssam_node_hid_tid1_iid7 = { -+/* HID device instance 7 (TID1, UCM USCI HID client). */ -+static const struct software_node ssam_node_hid_tid1_ucm_usci = { - .name = "ssam:01:15:01:07:00", - .parent = &ssam_node_root, - }; -@@ -182,8 +182,8 @@ static const struct software_node ssam_node_hid_kip_touchpad = { - .parent = &ssam_node_hub_kip, - }; - --/* HID device instance 5 (KIP hub, unknown HID device). */ --static const struct software_node ssam_node_hid_kip_iid5 = { -+/* HID device instance 5 (KIP hub, type-cover firmware update). */ -+static const struct software_node ssam_node_hid_kip_fwupd = { - .name = "ssam:01:15:02:05:00", - .parent = &ssam_node_hub_kip, - }; -@@ -244,8 +244,8 @@ static const struct software_node *ssam_node_group_sls[] = { - &ssam_node_hid_tid1_keyboard, - &ssam_node_hid_tid1_penstash, - &ssam_node_hid_tid1_touchpad, -- &ssam_node_hid_tid1_iid6, -- &ssam_node_hid_tid1_iid7, -+ &ssam_node_hid_tid1_sensors, -+ &ssam_node_hid_tid1_ucm_usci, - &ssam_node_hid_tid1_sysctrl, - NULL, - }; -@@ -278,7 +278,7 @@ static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_hid_kip_keyboard, - &ssam_node_hid_kip_penstash, - &ssam_node_hid_kip_touchpad, -- &ssam_node_hid_kip_iid5, -+ &ssam_node_hid_kip_fwupd, - NULL, - }; - --- -2.37.2 - -From 20db0502d55279d6b4cca93ebd7ff1b2920f320c Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jun 2022 20:52:47 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Rename HID device - nodes based on new findings - -On Windows, the HID devices with target ID 1 are grouped as "Surface Hot -Plug - SAM". Rename their device nodes in the registry to reflect that -and update the comments accordingly. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 36 +++++++++---------- - 1 file changed, 18 insertions(+), 18 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index b325fa0c5ee0..3aa825b5aa26 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -86,38 +86,38 @@ static const struct software_node ssam_node_bas_dtx = { - .parent = &ssam_node_root, - }; - --/* HID keyboard (TID1). */ --static const struct software_node ssam_node_hid_tid1_keyboard = { -+/* HID keyboard (SAM, TID=1). */ -+static const struct software_node ssam_node_hid_sam_keyboard = { - .name = "ssam:01:15:01:01:00", - .parent = &ssam_node_root, - }; - --/* HID pen stash (TID1; pen taken / stashed away evens). */ --static const struct software_node ssam_node_hid_tid1_penstash = { -+/* HID pen stash (SAM, TID=1; pen taken / stashed away evens). */ -+static const struct software_node ssam_node_hid_sam_penstash = { - .name = "ssam:01:15:01:02:00", - .parent = &ssam_node_root, - }; - --/* HID touchpad (TID1). */ --static const struct software_node ssam_node_hid_tid1_touchpad = { -+/* HID touchpad (SAM, TID=1). */ -+static const struct software_node ssam_node_hid_sam_touchpad = { - .name = "ssam:01:15:01:03:00", - .parent = &ssam_node_root, - }; - --/* HID device instance 6 (TID1, HID sensor collection). */ --static const struct software_node ssam_node_hid_tid1_sensors = { -+/* HID device instance 6 (SAM, TID=1, HID sensor collection). */ -+static const struct software_node ssam_node_hid_sam_sensors = { - .name = "ssam:01:15:01:06:00", - .parent = &ssam_node_root, - }; - --/* HID device instance 7 (TID1, UCM USCI HID client). */ --static const struct software_node ssam_node_hid_tid1_ucm_usci = { -+/* HID device instance 7 (SAM, TID=1, UCM USCI HID client). */ -+static const struct software_node ssam_node_hid_sam_ucm_usci = { - .name = "ssam:01:15:01:07:00", - .parent = &ssam_node_root, - }; - --/* HID system controls (TID1). */ --static const struct software_node ssam_node_hid_tid1_sysctrl = { -+/* HID system controls (SAM, TID=1). */ -+static const struct software_node ssam_node_hid_sam_sysctrl = { - .name = "ssam:01:15:01:08:00", - .parent = &ssam_node_root, - }; -@@ -241,12 +241,12 @@ static const struct software_node *ssam_node_group_sls[] = { - &ssam_node_bat_main, - &ssam_node_tmp_pprof, - &ssam_node_pos_tablet_switch, -- &ssam_node_hid_tid1_keyboard, -- &ssam_node_hid_tid1_penstash, -- &ssam_node_hid_tid1_touchpad, -- &ssam_node_hid_tid1_sensors, -- &ssam_node_hid_tid1_ucm_usci, -- &ssam_node_hid_tid1_sysctrl, -+ &ssam_node_hid_sam_keyboard, -+ &ssam_node_hid_sam_penstash, -+ &ssam_node_hid_sam_touchpad, -+ &ssam_node_hid_sam_sensors, -+ &ssam_node_hid_sam_ucm_usci, -+ &ssam_node_hid_sam_sysctrl, - NULL, - }; - --- -2.37.2 - -From 4680063aaad14e3083227e1e66d16f2c8c46da33 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jun 2022 20:54:59 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add HID devices for - sensors and USCI client to SP8 - -Add software nodes for the HID sensor collection and the UCM USCI HID -client to the Surface Pro 8. In contrast to the type-cover devices, -these devices are directly attached to the SAM controller, without any -hub. - -This enables support for HID-based sensors, including the ones used for -automatic screen rotation, on the Surface Pro 8. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 3aa825b5aa26..4c2f9f789354 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -279,6 +279,8 @@ static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_hid_kip_penstash, - &ssam_node_hid_kip_touchpad, - &ssam_node_hid_kip_fwupd, -+ &ssam_node_hid_sam_sensors, -+ &ssam_node_hid_sam_ucm_usci, - NULL, - }; - --- -2.37.2 - -From 72f14fa3dd5b2a5dd209a696ee3fb7562720a5d0 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 8 Jul 2022 03:34:44 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for - Surface Laptop Go 2 - -The Surface Laptop Go 2 seems to have the same SAM client devices as the -Surface Laptop Go 1, so re-use its node group. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 4c2f9f789354..49426b6e6b19 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -327,6 +327,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { - /* Surface Laptop Go 1 */ - { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, - -+ /* Surface Laptop Go 2 */ -+ { "MSHW0290", (unsigned long)ssam_node_group_slg1 }, -+ - /* Surface Laptop Studio */ - { "MSHW0123", (unsigned long)ssam_node_group_sls }, - --- -2.37.2 - diff --git a/patches/5.18/0006-surface-sam-over-hid.patch b/patches/5.18/0006-surface-sam-over-hid.patch deleted file mode 100644 index d0c12f6e7..000000000 --- a/patches/5.18/0006-surface-sam-over-hid.patch +++ /dev/null @@ -1,335 +0,0 @@ -From b64a10b0bee51c89d31ce74c19332da29a649593 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jul 2020 17:19:53 +0200 -Subject: [PATCH] i2c: acpi: Implement RawBytes read access - -Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C -device via a generic serial bus operation region and RawBytes read -access. On the Surface Book 1, this access is required to turn on (and -off) the discrete GPU. - -Multiple things are to note here: - -a) The RawBytes access is device/driver dependent. The ACPI - specification states: - - > Raw accesses assume that the writer has knowledge of the bus that - > the access is made over and the device that is being accessed. The - > protocol may only ensure that the buffer is transmitted to the - > appropriate driver, but the driver must be able to interpret the - > buffer to communicate to a register. - - Thus this implementation may likely not work on other devices - accessing I2C via the RawBytes accessor type. - -b) The MSHW0030 I2C device is an HID-over-I2C device which seems to - serve multiple functions: - - 1. It is the main access point for the legacy-type Surface Aggregator - Module (also referred to as SAM-over-HID, as opposed to the newer - SAM-over-SSH/UART). It has currently not been determined on how - support for the legacy SAM should be implemented. Likely via a - custom HID driver. - - 2. It seems to serve as the HID device for the Integrated Sensor Hub. - This might complicate matters with regards to implementing a - SAM-over-HID driver required by legacy SAM. - -In light of this, the simplest approach has been chosen for now. -However, it may make more sense regarding breakage and compatibility to -either provide functionality for replacing or enhancing the default -operation region handler via some additional API functions, or even to -completely blacklist MSHW0030 from the I2C core and provide a custom -driver for it. - -Replacing/enhancing the default operation region handler would, however, -either require some sort of secondary driver and access point for it, -from which the new API functions would be called and the new handler -(part) would be installed, or hard-coding them via some sort of -quirk-like interface into the I2C core. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam-over-hid ---- - drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ - 1 file changed, 35 insertions(+) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index 08b561f0709d..d7c397bce0f0 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -619,6 +619,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, - return (ret == 1) ? 0 : -EIO; - } - -+static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, -+ u8 *data, u8 data_len) -+{ -+ struct i2c_msg msgs[1]; -+ int ret = AE_OK; -+ -+ msgs[0].addr = client->addr; -+ msgs[0].flags = client->flags; -+ msgs[0].len = data_len + 1; -+ msgs[0].buf = data; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ -+ if (ret < 0) { -+ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); -+ return ret; -+ } -+ -+ /* 1 transfer must have completed successfully */ -+ return (ret == 1) ? 0 : -EIO; -+} -+ - static acpi_status - i2c_acpi_space_handler(u32 function, acpi_physical_address command, - u32 bits, u64 *value64, -@@ -720,6 +742,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, - } - break; - -+ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: -+ if (action == ACPI_READ) { -+ dev_warn(&adapter->dev, -+ "protocol 0x%02x not supported for client 0x%02x\n", -+ accessor_type, client->addr); -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } else { -+ status = acpi_gsb_i2c_write_raw_bytes(client, -+ gsb->data, info->access_length); -+ } -+ break; -+ - default: - dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", - accessor_type, client->addr); --- -2.37.2 - -From 3abe84a0aeaf48ba7989773d7174bf700b7079ac Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 13 Feb 2021 16:41:18 +0100 -Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch - -Add driver exposing the discrete GPU power-switch of the Microsoft -Surface Book 1 to user-space. - -On the Surface Book 1, the dGPU power is controlled via the Surface -System Aggregator Module (SAM). The specific SAM-over-HID command for -this is exposed via ACPI. This module provides a simple driver exposing -the ACPI call via a sysfs parameter to user-space, so that users can -easily power-on/-off the dGPU. - -Patchset: surface-sam-over-hid ---- - drivers/platform/surface/Kconfig | 7 + - drivers/platform/surface/Makefile | 1 + - .../surface/surfacebook1_dgpu_switch.c | 162 ++++++++++++++++++ - 3 files changed, 170 insertions(+) - create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index b629e82af97c..68656e8f309e 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH - Select M or Y here, if you want to provide tablet-mode switch input - events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. - -+config SURFACE_BOOK1_DGPU_SWITCH -+ tristate "Surface Book 1 dGPU Switch Driver" -+ depends on SYSFS -+ help -+ This driver provides a sysfs switch to set the power-state of the -+ discrete GPU found on the Microsoft Surface Book 1. -+ - config SURFACE_DTX - tristate "Surface DTX (Detachment System) Driver" - depends on SURFACE_AGGREGATOR -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 53344330939b..7efcd0cdb532 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o - obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o -+obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c -new file mode 100644 -index 000000000000..8b816ed8f35c ---- /dev/null -+++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c -@@ -0,0 +1,162 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+ -+#include -+#include -+#include -+#include -+ -+ -+#ifdef pr_fmt -+#undef pr_fmt -+#endif -+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ -+ -+ -+static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, -+ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); -+ -+#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" -+#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" -+#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" -+ -+ -+static int sb1_dgpu_sw_dsmcall(void) -+{ -+ union acpi_object *ret; -+ acpi_handle handle; -+ acpi_status status; -+ -+ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); -+ if (status) -+ return -EINVAL; -+ -+ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); -+ if (!ret) -+ return -EINVAL; -+ -+ ACPI_FREE(ret); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgon(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGON: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-on dGPU via HGON\n"); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgof(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGOF: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-off dGPU via HGOF\n"); -+ return 0; -+} -+ -+ -+static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ int status, value; -+ -+ status = kstrtoint(buf, 0, &value); -+ if (status < 0) -+ return status; -+ -+ if (value != 1) -+ return -EINVAL; -+ -+ status = sb1_dgpu_sw_dsmcall(); -+ -+ return status < 0 ? status : len; -+} -+ -+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ bool power; -+ int status; -+ -+ status = kstrtobool(buf, &power); -+ if (status < 0) -+ return status; -+ -+ if (power) -+ status = sb1_dgpu_sw_hgon(); -+ else -+ status = sb1_dgpu_sw_hgof(); -+ -+ return status < 0 ? status : len; -+} -+ -+static DEVICE_ATTR_WO(dgpu_dsmcall); -+static DEVICE_ATTR_WO(dgpu_power); -+ -+static struct attribute *sb1_dgpu_sw_attrs[] = { -+ &dev_attr_dgpu_dsmcall.attr, -+ &dev_attr_dgpu_power.attr, -+ NULL, -+}; -+ -+static const struct attribute_group sb1_dgpu_sw_attr_group = { -+ .attrs = sb1_dgpu_sw_attrs, -+}; -+ -+ -+static int sb1_dgpu_sw_probe(struct platform_device *pdev) -+{ -+ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+} -+ -+static int sb1_dgpu_sw_remove(struct platform_device *pdev) -+{ -+ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+ return 0; -+} -+ -+/* -+ * The dGPU power seems to be actually handled by MSHW0040. However, that is -+ * also the power-/volume-button device with a mainline driver. So let's use -+ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. -+ */ -+static const struct acpi_device_id sb1_dgpu_sw_match[] = { -+ { "MSHW0041", }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); -+ -+static struct platform_driver sb1_dgpu_sw = { -+ .probe = sb1_dgpu_sw_probe, -+ .remove = sb1_dgpu_sw_remove, -+ .driver = { -+ .name = "surfacebook1_dgpu_switch", -+ .acpi_match_table = sb1_dgpu_sw_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(sb1_dgpu_sw); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); -+MODULE_LICENSE("GPL"); --- -2.37.2 - diff --git a/patches/5.18/0007-surface-button.patch b/patches/5.18/0007-surface-button.patch deleted file mode 100644 index 8ed2537a5..000000000 --- a/patches/5.18/0007-surface-button.patch +++ /dev/null @@ -1,149 +0,0 @@ -From 35879d4ed707cf91c40eecfcb30609359af6c78e Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Tue, 5 Oct 2021 00:05:09 +1100 -Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices - -The power button on the AMD variant of the Surface Laptop uses the -same MSHW0040 device ID as the 5th and later generation of Surface -devices, however they report 0 for their OEM platform revision. As the -_DSM does not exist on the devices requiring special casing, check for -the existance of the _DSM to determine if soc_button_array should be -loaded. - -Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") -Co-developed-by: Maximilian Luz - -Signed-off-by: Sachi King -Patchset: surface-button ---- - drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- - 1 file changed, 8 insertions(+), 25 deletions(-) - -diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c -index 480476121c01..36e1bf7b7a01 100644 ---- a/drivers/input/misc/soc_button_array.c -+++ b/drivers/input/misc/soc_button_array.c -@@ -495,8 +495,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { - * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned - * devices use MSHW0040 for power and volume buttons, however the way they - * have to be addressed differs. Make sure that we only load this drivers -- * for the correct devices by checking the OEM Platform Revision provided by -- * the _DSM method. -+ * for the correct devices by checking if the OEM Platform Revision DSM call -+ * exists. - */ - #define MSHW0040_DSM_REVISION 0x01 - #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision -@@ -507,31 +507,14 @@ static const guid_t MSHW0040_DSM_UUID = - static int soc_device_check_MSHW0040(struct device *dev) - { - acpi_handle handle = ACPI_HANDLE(dev); -- union acpi_object *result; -- u64 oem_platform_rev = 0; // valid revisions are nonzero -- -- // get OEM platform revision -- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -- MSHW0040_DSM_REVISION, -- MSHW0040_DSM_GET_OMPR, NULL, -- ACPI_TYPE_INTEGER); -- -- if (result) { -- oem_platform_rev = result->integer.value; -- ACPI_FREE(result); -- } -- -- /* -- * If the revision is zero here, the _DSM evaluation has failed. This -- * indicates that we have a Pro 4 or Book 1 and this driver should not -- * be used. -- */ -- if (oem_platform_rev == 0) -- return -ENODEV; -+ bool exists; - -- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); -+ // check if OEM platform revision DSM call exists -+ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ BIT(MSHW0040_DSM_GET_OMPR)); - -- return 0; -+ return exists ? 0 : -ENODEV; - } - - /* --- -2.37.2 - -From 627721b4b779ab79f86fc305b9680bc8ae3258d6 Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Tue, 5 Oct 2021 00:22:57 +1100 -Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd - variant - -The AMD variant of the Surface Laptop report 0 for their OEM platform -revision. The Surface devices that require the surfacepro3_button -driver do not have the _DSM that gets the OEM platform revision. If the -method does not exist, load surfacepro3_button. - -Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") -Co-developed-by: Maximilian Luz - -Signed-off-by: Sachi King -Patchset: surface-button ---- - drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- - 1 file changed, 6 insertions(+), 24 deletions(-) - -diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c -index 242fb690dcaf..30eea54dbb47 100644 ---- a/drivers/platform/surface/surfacepro3_button.c -+++ b/drivers/platform/surface/surfacepro3_button.c -@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) - /* - * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device - * ID (MSHW0040) for the power/volume buttons. Make sure this is the right -- * device by checking for the _DSM method and OEM Platform Revision. -+ * device by checking for the _DSM method and OEM Platform Revision DSM -+ * function. - * - * Returns true if the driver should bind to this device, i.e. the device is - * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. -@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) - static bool surface_button_check_MSHW0040(struct acpi_device *dev) - { - acpi_handle handle = dev->handle; -- union acpi_object *result; -- u64 oem_platform_rev = 0; // valid revisions are nonzero -- -- // get OEM platform revision -- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -- MSHW0040_DSM_REVISION, -- MSHW0040_DSM_GET_OMPR, -- NULL, ACPI_TYPE_INTEGER); -- -- /* -- * If evaluating the _DSM fails, the method is not present. This means -- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we -- * should use this driver. We use revision 0 indicating it is -- * unavailable. -- */ -- -- if (result) { -- oem_platform_rev = result->integer.value; -- ACPI_FREE(result); -- } -- -- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); - -- return oem_platform_rev == 0; -+ // make sure that OEM platform revision DSM call does not exist -+ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ BIT(MSHW0040_DSM_GET_OMPR)); - } - - --- -2.37.2 - diff --git a/patches/5.18/0008-surface-typecover.patch b/patches/5.18/0008-surface-typecover.patch deleted file mode 100644 index a0fab235c..000000000 --- a/patches/5.18/0008-surface-typecover.patch +++ /dev/null @@ -1,533 +0,0 @@ -From 1129d8ed78f239a7d85929ae7c38f89ec51af8a1 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 5 Nov 2020 13:09:45 +0100 -Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when - suspending - -The Type Cover for Microsoft Surface devices supports a special usb -control request to disable or enable the built-in keyboard backlight. -On Windows, this request happens when putting the device into suspend or -resuming it, without it the backlight of the Type Cover will remain -enabled for some time even though the computer is suspended, which looks -weird to the user. - -So add support for this special usb control request to hid-multitouch, -which is the driver that's handling the Type Cover. - -The reason we have to use a pm_notifier for this instead of the usual -suspend/resume methods is that those won't get called in case the usb -device is already autosuspended. - -Also, if the device is autosuspended, we have to briefly autoresume it -in order to send the request. Doing that should be fine, the usb-core -driver does something similar during suspend inside choose_wakeup(). - -To make sure we don't send that request to every device but only to -devices which support it, add a new quirk -MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk -is only enabled for the usb id of the Surface Pro 2017 Type Cover, which -is where I confirmed that it's working. - -Patchset: surface-typecover ---- - drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- - 1 file changed, 98 insertions(+), 2 deletions(-) - -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index 6bb3890b0f2c..c28349e90156 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -34,7 +34,10 @@ - #include - #include - #include -+#include - #include -+#include -+#include - #include - #include - #include -@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); - MODULE_LICENSE("GPL"); - - #include "hid-ids.h" -+#include "usbhid/usbhid.h" - - /* quirks to control the device */ - #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) -@@ -71,12 +75,15 @@ MODULE_LICENSE("GPL"); - #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) - #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) - #define MT_QUIRK_DISABLE_WAKEUP BIT(21) -+#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 - - #define MT_BUTTONTYPE_CLICKPAD 0 - -+#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+ - enum latency_mode { - HID_LATENCY_NORMAL = 0, - HID_LATENCY_HIGH = 1, -@@ -168,6 +175,8 @@ struct mt_device { - - struct list_head applications; - struct list_head reports; -+ -+ struct notifier_block pm_notifier; - }; - - static void mt_post_parse_default_settings(struct mt_device *td, -@@ -211,6 +220,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); - #define MT_CLS_GOOGLE 0x0111 - #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 - #define MT_CLS_SMART_TECH 0x0113 -+#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 - - #define MT_DEFAULT_MAXCONTACT 10 - #define MT_MAX_MAXCONTACT 250 -@@ -386,6 +396,16 @@ static const struct mt_class mt_classes[] = { - MT_QUIRK_CONTACT_CNT_ACCURATE | - MT_QUIRK_SEPARATE_APP_REPORT, - }, -+ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_ALWAYS_VALID | -+ MT_QUIRK_IGNORE_DUPLICATES | -+ MT_QUIRK_HOVERING | -+ MT_QUIRK_CONTACT_CNT_ACCURATE | -+ MT_QUIRK_STICKY_FINGERS | -+ MT_QUIRK_WIN8_PTP_BUTTONS, -+ .export_all_inputs = true -+ }, - { } - }; - -@@ -1695,6 +1715,69 @@ static void mt_expired_timeout(struct timer_list *t) - clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - -+static void get_type_cover_backlight_field(struct hid_device *hdev, -+ struct hid_field **field) -+{ -+ struct hid_report_enum *rep_enum; -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid -+ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { -+ *field = cur_field; -+ return; -+ } -+ } -+ } -+ } -+} -+ -+static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) -+{ -+ struct usb_device *udev = hid_to_usb_dev(hdev); -+ struct hid_field *field = NULL; -+ -+ /* Wake up the device in case it's already suspended */ -+ pm_runtime_get_sync(&udev->dev); -+ -+ get_type_cover_backlight_field(hdev, &field); -+ if (!field) { -+ hid_err(hdev, "couldn't find backlight field\n"); -+ goto out; -+ } -+ -+ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; -+ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); -+ -+out: -+ pm_runtime_put_sync(&udev->dev); -+} -+ -+static int mt_pm_notifier(struct notifier_block *notifier, -+ unsigned long pm_event, -+ void *unused) -+{ -+ struct mt_device *td = -+ container_of(notifier, struct mt_device, pm_notifier); -+ struct hid_device *hdev = td->hdev; -+ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { -+ if (pm_event == PM_SUSPEND_PREPARE) -+ update_keyboard_backlight(hdev, 0); -+ else if (pm_event == PM_POST_SUSPEND) -+ update_keyboard_backlight(hdev, 1); -+ } -+ -+ return NOTIFY_DONE; -+} -+ - static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - { - int ret, i; -@@ -1718,6 +1801,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; - hid_set_drvdata(hdev, td); - -+ td->pm_notifier.notifier_call = mt_pm_notifier; -+ register_pm_notifier(&td->pm_notifier); -+ - INIT_LIST_HEAD(&td->applications); - INIT_LIST_HEAD(&td->reports); - -@@ -1747,15 +1833,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - timer_setup(&td->release_timer, mt_expired_timeout, 0); - - ret = hid_parse(hdev); -- if (ret != 0) -+ if (ret != 0) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) - mt_fix_const_fields(hdev, HID_DG_CONTACTID); - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); -- if (ret) -+ if (ret) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); - if (ret) -@@ -1807,6 +1897,7 @@ static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); - -+ unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); - - sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); -@@ -2180,6 +2271,11 @@ static const struct hid_device_id mt_devices[] = { - MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, - USB_DEVICE_ID_XIROKU_CSR2) }, - -+ /* Microsoft Surface type cover */ -+ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, -+ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, -+ - /* Google MT devices */ - { .driver_data = MT_CLS_GOOGLE, - HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, --- -2.37.2 - -From 2efbc56c3ce4cd31a9ca25abd8ccb7de01c16616 Mon Sep 17 00:00:00 2001 -From: PJungkamp -Date: Fri, 25 Feb 2022 12:04:25 +0100 -Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet - switch - -The Surface Pro Type Cover has several non standard HID usages in it's -hid report descriptor. -I noticed that, upon folding the typecover back, a vendor specific range -of 4 32 bit integer hid usages is transmitted. -Only the first byte of the message seems to convey reliable information -about the keyboard state. - -0x22 => Normal (keys enabled) -0x33 => Folded back (keys disabled) -0x53 => Rotated left/right side up (keys disabled) -0x13 => Cover closed (keys disabled) -0x43 => Folded back and Tablet upside down (keys disabled) -This list may not be exhaustive. - -The tablet mode switch will be disabled for a value of 0x22 and enabled -on any other value. - -Patchset: surface-typecover ---- - drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ - 1 file changed, 122 insertions(+), 26 deletions(-) - -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index c28349e90156..61142639be26 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -76,6 +76,7 @@ MODULE_LICENSE("GPL"); - #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) - #define MT_QUIRK_DISABLE_WAKEUP BIT(21) - #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) -+#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(23) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 -@@ -83,6 +84,8 @@ MODULE_LICENSE("GPL"); - #define MT_BUTTONTYPE_CLICKPAD 0 - - #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 -+#define MS_TYPE_COVER_APPLICATION 0xff050050 - - enum latency_mode { - HID_LATENCY_NORMAL = 0, -@@ -398,6 +401,7 @@ static const struct mt_class mt_classes[] = { - }, - { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, - .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | - MT_QUIRK_ALWAYS_VALID | - MT_QUIRK_IGNORE_DUPLICATES | - MT_QUIRK_HOVERING | -@@ -1357,6 +1361,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, - field->application != HID_CP_CONSUMER_CONTROL && - field->application != HID_GD_WIRELESS_RADIO_CTLS && - field->application != HID_GD_SYSTEM_MULTIAXIS && -+ !(field->application == MS_TYPE_COVER_APPLICATION && -+ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && - !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && - application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) - return -1; -@@ -1384,6 +1391,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, - return 1; - } - -+ /* -+ * The Microsoft Surface Pro Typecover has a non-standard HID -+ * tablet mode switch on a vendor specific usage page with vendor -+ * specific usage. -+ */ -+ if (field->application == MS_TYPE_COVER_APPLICATION && -+ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { -+ usage->type = EV_SW; -+ usage->code = SW_TABLET_MODE; -+ *max = SW_MAX; -+ *bit = hi->input->swbit; -+ return 1; -+ } -+ - if (rdata->is_mt_collection) - return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, - application); -@@ -1405,6 +1427,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, - { - struct mt_device *td = hid_get_drvdata(hdev); - struct mt_report_data *rdata; -+ struct input_dev *input; - - rdata = mt_find_report_data(td, field->report); - if (rdata && rdata->is_mt_collection) { -@@ -1412,6 +1435,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, - return -1; - } - -+ /* -+ * We own an input device which acts as a tablet mode switch for -+ * the Surface Pro Typecover. -+ */ -+ if (field->application == MS_TYPE_COVER_APPLICATION && -+ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { -+ input = hi->input; -+ input_set_capability(input, EV_SW, SW_TABLET_MODE); -+ input_report_switch(input, SW_TABLET_MODE, 0); -+ return -1; -+ } -+ - /* let hid-core decide for the others */ - return 0; - } -@@ -1421,11 +1457,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, - { - struct mt_device *td = hid_get_drvdata(hid); - struct mt_report_data *rdata; -+ struct input_dev *input; - - rdata = mt_find_report_data(td, field->report); - if (rdata && rdata->is_mt_collection) - return mt_touch_event(hid, field, usage, value); - -+ if (field->application == MS_TYPE_COVER_APPLICATION && -+ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { -+ input = field->hidinput->input; -+ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); -+ input_sync(input); -+ return 1; -+ } -+ - return 0; - } - -@@ -1578,6 +1624,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) - app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; - } - -+static int get_type_cover_field(struct hid_report_enum *rep_enum, -+ struct hid_field **field, int usage) -+{ -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ if (cur_field->application != MS_TYPE_COVER_APPLICATION) -+ continue; -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid == usage) { -+ *field = cur_field; -+ return true; -+ } -+ } -+ } -+ } -+ return false; -+} -+ -+static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) -+{ -+ struct hid_field *field; -+ -+ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], -+ &field, -+ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { -+ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); -+ } else { -+ hid_err(hdev, "couldn't find tablet mode field\n"); -+ } -+} -+ - static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) - { - struct mt_device *td = hid_get_drvdata(hdev); -@@ -1627,6 +1709,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) - /* force BTN_STYLUS to allow tablet matching in udev */ - __set_bit(BTN_STYLUS, hi->input->keybit); - break; -+ case MS_TYPE_COVER_APPLICATION: -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { -+ suffix = "Tablet Mode Switch"; -+ request_type_cover_tablet_mode_switch(hdev); -+ break; -+ } -+ fallthrough; - default: - suffix = "UNKNOWN"; - break; -@@ -1715,30 +1804,6 @@ static void mt_expired_timeout(struct timer_list *t) - clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - --static void get_type_cover_backlight_field(struct hid_device *hdev, -- struct hid_field **field) --{ -- struct hid_report_enum *rep_enum; -- struct hid_report *rep; -- struct hid_field *cur_field; -- int i, j; -- -- rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; -- list_for_each_entry(rep, &rep_enum->report_list, list) { -- for (i = 0; i < rep->maxfield; i++) { -- cur_field = rep->field[i]; -- -- for (j = 0; j < cur_field->maxusage; j++) { -- if (cur_field->usage[j].hid -- == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { -- *field = cur_field; -- return; -- } -- } -- } -- } --} -- - static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) - { - struct usb_device *udev = hid_to_usb_dev(hdev); -@@ -1747,8 +1812,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) - /* Wake up the device in case it's already suspended */ - pm_runtime_get_sync(&udev->dev); - -- get_type_cover_backlight_field(hdev, &field); -- if (!field) { -+ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], -+ &field, -+ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { - hid_err(hdev, "couldn't find backlight field\n"); - goto out; - } -@@ -1874,13 +1940,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) - - static int mt_reset_resume(struct hid_device *hdev) - { -+ struct mt_device *td = hid_get_drvdata(hdev); -+ - mt_release_contacts(hdev); - mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); -+ -+ /* Request an update on the typecover folding state on resume -+ * after reset. -+ */ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) -+ request_type_cover_tablet_mode_switch(hdev); -+ - return 0; - } - - static int mt_resume(struct hid_device *hdev) - { -+ struct mt_device *td = hid_get_drvdata(hdev); -+ - /* Some Elan legacy devices require SET_IDLE to be set on resume. - * It should be safe to send it to other devices too. - * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ -@@ -1889,6 +1966,10 @@ static int mt_resume(struct hid_device *hdev) - - mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); - -+ /* Request an update on the typecover folding state on resume. */ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) -+ request_type_cover_tablet_mode_switch(hdev); -+ - return 0; - } - #endif -@@ -1896,6 +1977,21 @@ static int mt_resume(struct hid_device *hdev) - static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); -+ struct hid_field *field; -+ struct input_dev *input; -+ -+ /* Reset tablet mode switch on disconnect. */ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { -+ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], -+ &field, -+ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { -+ input = field->hidinput->input; -+ input_report_switch(input, SW_TABLET_MODE, 0); -+ input_sync(input); -+ } else { -+ hid_err(hdev, "couldn't find tablet mode field\n"); -+ } -+ } - - unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); --- -2.37.2 - diff --git a/patches/5.18/0009-surface-battery.patch b/patches/5.18/0009-surface-battery.patch deleted file mode 100644 index ccae155db..000000000 --- a/patches/5.18/0009-surface-battery.patch +++ /dev/null @@ -1,83 +0,0 @@ -From 731a5eb141f0f579b74a9b0fe23b4af987c77a0b Mon Sep 17 00:00:00 2001 -From: Werner Sembach -Date: Wed, 27 Apr 2022 17:40:53 +0200 -Subject: [PATCH] ACPI: battery: Make "not-charging" the default on no charging - or full info - -When the battery is neither charging or discharging and is not full, -"not-charging" is a useful status description for the case in general. -Currently this state is set as "unknown" by default, expect when this is -explicitly replaced with "not-charging" on a per device or per vendor -basis. - -A lot of devices have this state without a BIOS specification available -explicitly describing it. e.g. some current Clevo barebones have a BIOS -setting to stop charging at a user defined battery level. - -Signed-off-by: Werner Sembach -Signed-off-by: Rafael J. Wysocki -Patchset: surface-battery ---- - drivers/acpi/battery.c | 24 +----------------------- - 1 file changed, 1 insertion(+), 23 deletions(-) - -diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c -index dc208f5f5a1f..306513fec1e1 100644 ---- a/drivers/acpi/battery.c -+++ b/drivers/acpi/battery.c -@@ -52,7 +52,6 @@ static bool battery_driver_registered; - static int battery_bix_broken_package; - static int battery_notification_delay_ms; - static int battery_ac_is_broken; --static int battery_quirk_notcharging; - static unsigned int cache_time = 1000; - module_param(cache_time, uint, 0644); - MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); -@@ -216,10 +215,8 @@ static int acpi_battery_get_property(struct power_supply *psy, - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (acpi_battery_is_charged(battery)) - val->intval = POWER_SUPPLY_STATUS_FULL; -- else if (battery_quirk_notcharging) -- val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - else -- val->intval = POWER_SUPPLY_STATUS_UNKNOWN; -+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - break; - case POWER_SUPPLY_PROP_PRESENT: - val->intval = acpi_battery_present(battery); -@@ -1105,12 +1102,6 @@ battery_ac_is_broken_quirk(const struct dmi_system_id *d) - return 0; - } - --static int __init battery_quirk_not_charging(const struct dmi_system_id *d) --{ -- battery_quirk_notcharging = 1; -- return 0; --} -- - static const struct dmi_system_id bat_dmi_table[] __initconst = { - { - /* NEC LZ750/LS */ -@@ -1139,19 +1130,6 @@ static const struct dmi_system_id bat_dmi_table[] __initconst = { - DMI_MATCH(DMI_BIOS_DATE, "08/22/2014"), - }, - }, -- { -- /* -- * On Lenovo ThinkPads the BIOS specification defines -- * a state when the bits for charging and discharging -- * are both set to 0. That state is "Not Charging". -- */ -- .callback = battery_quirk_not_charging, -- .ident = "Lenovo ThinkPad", -- .matches = { -- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), -- DMI_MATCH(DMI_PRODUCT_VERSION, "ThinkPad"), -- }, -- }, - { - /* Microsoft Surface Go 3 */ - .callback = battery_notification_delay_quirk, --- -2.37.2 - diff --git a/patches/5.18/0010-surface-gpe.patch b/patches/5.18/0010-surface-gpe.patch deleted file mode 100644 index cd8f5136f..000000000 --- a/patches/5.18/0010-surface-gpe.patch +++ /dev/null @@ -1,41 +0,0 @@ -From 21c2c2cd1985f6050f652a36f6025cc64fc2e7a7 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 21 Jul 2022 02:15:50 +0200 -Subject: [PATCH] platform/surface: gpe: Add support for 13" Intel version of - Surface Laptop 4 - -The 13" Intel version of the Surface Laptop 4 uses the same GPE as the -Surface Laptop Studio for wakeups via the lid. Set it up accordingly. - -Signed-off-by: Maximilian Luz -Patchset: surface-gpe ---- - drivers/platform/surface/surface_gpe.c | 12 ++++++++++++ - 1 file changed, 12 insertions(+) - -diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c -index 27365cbe1ee9..c219b840d491 100644 ---- a/drivers/platform/surface/surface_gpe.c -+++ b/drivers/platform/surface/surface_gpe.c -@@ -171,6 +171,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { - }, - .driver_data = (void *)lid_device_props_l4D, - }, -+ { -+ .ident = "Surface Laptop 4 (Intel 13\")", -+ .matches = { -+ /* -+ * We match for SKU here due to different variants: The -+ * AMD (15") version does not rely on GPEs. -+ */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1950:1951"), -+ }, -+ .driver_data = (void *)lid_device_props_l4B, -+ }, - { - .ident = "Surface Laptop Studio", - .matches = { --- -2.37.2 - diff --git a/patches/5.18/0011-cameras.patch b/patches/5.18/0011-cameras.patch deleted file mode 100644 index e9674b93f..000000000 --- a/patches/5.18/0011-cameras.patch +++ /dev/null @@ -1,1247 +0,0 @@ -From 5271cb0a99d23130806f0a4bd2b5440023163a62 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:56:57 +0200 -Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an - INT3472 device - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around cases where this info is not present in the firmware tables, -which is often the case on x86/ACPI devices, both frameworks allow the -provider-driver to attach info about consumers to the clks/regulators -when registering these. - -This causes problems with the probe ordering wrt drivers for consumers -of these clks/regulators. Since the lookups are only registered when the -provider-driver binds, trying to get these clks/regulators before then -results in a -ENOENT error for clks and a dummy regulator for regulators. - -One case where we hit this issue is camera sensors such as e.g. the OV8865 -sensor found on the Microsoft Surface Go. The sensor uses clks, regulators -and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 -ACPI device. There is special platform code handling this and setting -platform_data with the necessary consumer info on the MFD cells -instantiated for the PMIC under: drivers/platform/x86/intel/int3472. - -For this to work properly the ov8865 driver must not bind to the I2C-client -for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and -clk MFD cells have all been fully setup. - -The OV8865 on the Microsoft Surface Go is just one example, all X86 -devices using the Intel IPU3 camera block found on recent Intel SoCs -have similar issues where there is an INT3472 HID ACPI-device, which -describes the clks and regulators, and the driver for this INT3472 device -must be fully initialized before the sensor driver (any sensor driver) -binds for things to work properly. - -On these devices the ACPI nodes describing the sensors all have a _DEP -dependency on the matching INT3472 ACPI device (there is one per sensor). - -This allows solving the probe-ordering problem by delaying the enumeration -(instantiation of the I2C-client in the ov8865 example) of ACPI-devices -which have a _DEP dependency on an INT3472 device. - -The new acpi_dev_ready_for_enumeration() helper used for this is also -exported because for devices, which have the enumeration_by_parent flag -set, the parent-driver will do its own scan of child ACPI devices and -it will try to enumerate those during its probe(). Code doing this such -as e.g. the i2c-core-acpi.c code must call this new helper to ensure -that it too delays the enumeration until all the _DEP dependencies are -met on devices which have the new honor_deps flag set. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/acpi/scan.c | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c -index 762b61f67e6c..2c0f39a7f2a1 100644 ---- a/drivers/acpi/scan.c -+++ b/drivers/acpi/scan.c -@@ -2122,6 +2122,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, - - static void acpi_default_enumeration(struct acpi_device *device) - { -+ if (!acpi_dev_ready_for_enumeration(device)) -+ return; -+ - /* - * Do not enumerate devices with enumeration_by_parent flag set as - * they will be enumerated by their respective parents. --- -2.37.2 - -From 6596a99eddeeaa1167b7c3363036701cb5b0427c Mon Sep 17 00:00:00 2001 -From: zouxiaoh -Date: Fri, 25 Jun 2021 08:52:59 +0800 -Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs - -Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, -The IPU driver allocates its own page table that is not mapped -via the DMA, and thus the Intel IOMMU driver blocks access giving -this error: DMAR: DRHD: handling fault status reg 3 DMAR: -[DMA Read] Request device [00:05.0] PASID ffffffff -fault addr 76406000 [fault reason 06] PTE Read access is not set -As IPU is not an external facing device which is not risky, so use -IOMMU passthrough mode for Intel IPUs. - -Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b -Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 -Tracked-On: #JIITL8-411 -Signed-off-by: Bingbu Cao -Signed-off-by: zouxiaoh -Signed-off-by: Xu Chongyang -Patchset: cameras ---- - drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ - 1 file changed, 30 insertions(+) - -diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c -index 7f3699e19270..0ee615daadeb 100644 ---- a/drivers/iommu/intel/iommu.c -+++ b/drivers/iommu/intel/iommu.c -@@ -37,6 +37,12 @@ - #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) - #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) - #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) -+#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ -+ ((pdev)->device == 0x9a19 || \ -+ (pdev)->device == 0x9a39 || \ -+ (pdev)->device == 0x4e19 || \ -+ (pdev)->device == 0x465d || \ -+ (pdev)->device == 0x1919)) - #define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ - ((pdev)->device == 0x9d3e)) - #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) -@@ -310,12 +316,14 @@ EXPORT_SYMBOL_GPL(intel_iommu_enabled); - - static int dmar_map_gfx = 1; - static int dmar_map_ipts = 1; -+static int dmar_map_ipu = 1; - static int intel_iommu_superpage = 1; - static int iommu_identity_mapping; - static int iommu_skip_te_disable; - - #define IDENTMAP_GFX 2 - #define IDENTMAP_AZALIA 4 -+#define IDENTMAP_IPU 8 - #define IDENTMAP_IPTS 16 - - int intel_iommu_gfx_mapped; -@@ -2705,6 +2713,9 @@ static int device_def_domain_type(struct device *dev) - if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) - return IOMMU_DOMAIN_IDENTITY; - -+ if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) -+ return IOMMU_DOMAIN_IDENTITY; -+ - if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) - return IOMMU_DOMAIN_IDENTITY; - } -@@ -3143,6 +3154,9 @@ static int __init init_dmars(void) - if (!dmar_map_gfx) - iommu_identity_mapping |= IDENTMAP_GFX; - -+ if (!dmar_map_ipu) -+ iommu_identity_mapping |= IDENTMAP_IPU; -+ - if (!dmar_map_ipts) - iommu_identity_mapping |= IDENTMAP_IPTS; - -@@ -4917,6 +4931,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) - dmar_map_gfx = 0; - } - -+static void quirk_iommu_ipu(struct pci_dev *dev) -+{ -+ if (!IS_INTEL_IPU(dev)) -+ return; -+ -+ if (risky_device(dev)) -+ return; -+ -+ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); -+ dmar_map_ipu = 0; -+} -+ - static void quirk_iommu_ipts(struct pci_dev *dev) - { - if (!IS_IPTS(dev)) -@@ -4928,6 +4954,7 @@ static void quirk_iommu_ipts(struct pci_dev *dev) - pci_info(dev, "Passthrough IOMMU for IPTS\n"); - dmar_map_ipts = 0; - } -+ - /* G4x/GM45 integrated gfx dmar support is totally busted. */ - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); -@@ -4963,6 +4990,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); - -+/* disable IPU dmar support */ -+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); -+ - /* disable IPTS dmar support */ - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); - --- -2.37.2 - -From c6e12368fc3a4610a28f66c3f6b6ba896df7bda4 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 10 Oct 2021 20:57:02 +0200 -Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain - -The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic -can be forwarded to a device connected to the PMIC as though it were -connected directly to the system bus. Enable this mode when the chip -is initialised. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -index 22f61b47f9e5..e1de1ff40bba 100644 ---- a/drivers/platform/x86/intel/int3472/tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -45,6 +45,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) - return ret; - } - -+ /* Enable I2C daisy chain */ -+ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); -+ if (ret) { -+ dev_err(dev, "Failed to enable i2c daisy chain\n"); -+ return ret; -+ } -+ - dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); - - return 0; --- -2.37.2 - -From 03bc16d2ed07c397a051ee0f08058517e3c7e59a Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 28 Oct 2021 21:55:16 +0100 -Subject: [PATCH] media: i2c: Add driver for DW9719 VCM - -Add a driver for the DW9719 VCM. The driver creates a v4l2 subdevice -and registers a control to set the desired focus. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 7 + - drivers/media/i2c/Kconfig | 11 + - drivers/media/i2c/Makefile | 1 + - drivers/media/i2c/dw9719.c | 427 +++++++++++++++++++++++++++++++++++++ - 4 files changed, 446 insertions(+) - create mode 100644 drivers/media/i2c/dw9719.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index f96b3dba903a..03452d847a52 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -6002,6 +6002,13 @@ T: git git://linuxtv.org/media_tree.git - F: Documentation/devicetree/bindings/media/i2c/dongwoon,dw9714.txt - F: drivers/media/i2c/dw9714.c - -+DONGWOON DW9719 LENS VOICE COIL DRIVER -+M: Daniel Scally -+L: linux-media@vger.kernel.org -+S: Maintained -+T: git git://linuxtv.org/media_tree.git -+F: drivers/media/i2c/dw9719.c -+ - DONGWOON DW9768 LENS VOICE COIL DRIVER - M: Dongchun Zhu - L: linux-media@vger.kernel.org -diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig -index c926e5d43820..5c245f642ae3 100644 ---- a/drivers/media/i2c/Kconfig -+++ b/drivers/media/i2c/Kconfig -@@ -806,6 +806,17 @@ config VIDEO_DW9714 - capability. This is designed for linear control of - voice coil motors, controlled via I2C serial interface. - -+config VIDEO_DW9719 -+ tristate "DW9719 lens voice coil support" -+ depends on I2C && VIDEO_V4L2 -+ select MEDIA_CONTROLLER -+ select VIDEO_V4L2_SUBDEV_API -+ select V4L2_ASYNC -+ help -+ This is a driver for the DW9719 camera lens voice coil. -+ This is designed for linear control of voice coil motors, -+ controlled via I2C serial interface. -+ - config VIDEO_DW9768 - tristate "DW9768 lens voice coil support" - depends on I2C && VIDEO_DEV -diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile -index 3e1696963e7f..9dfda069e006 100644 ---- a/drivers/media/i2c/Makefile -+++ b/drivers/media/i2c/Makefile -@@ -29,6 +29,7 @@ obj-$(CONFIG_VIDEO_CS5345) += cs5345.o - obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o - obj-$(CONFIG_VIDEO_CX25840) += cx25840/ - obj-$(CONFIG_VIDEO_DW9714) += dw9714.o -+obj-$(CONFIG_VIDEO_DW9719) += dw9719.o - obj-$(CONFIG_VIDEO_DW9768) += dw9768.o - obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o - obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ -diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c -new file mode 100644 -index 000000000000..8451c75b696b ---- /dev/null -+++ b/drivers/media/i2c/dw9719.c -@@ -0,0 +1,427 @@ -+// SPDX-License-Identifier: GPL-2.0 -+// Copyright (c) 2012 Intel Corporation -+ -+/* -+ * Based on linux/modules/camera/drivers/media/i2c/imx/dw9719.c in this repo: -+ * https://github.com/ZenfoneArea/android_kernel_asus_zenfone5 -+ */ -+ -+#include -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+#define DW9719_MAX_FOCUS_POS 1023 -+#define DW9719_CTRL_STEPS 16 -+#define DW9719_CTRL_DELAY_US 1000 -+#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) -+ -+#define DW9719_INFO 0 -+#define DW9719_ID 0xF1 -+#define DW9719_CONTROL 2 -+#define DW9719_VCM_CURRENT 3 -+ -+#define DW9719_MODE 6 -+#define DW9719_VCM_FREQ 7 -+ -+#define DW9719_MODE_SAC3 0x40 -+#define DW9719_DEFAULT_VCM_FREQ 0x60 -+#define DW9719_ENABLE_RINGING 0x02 -+ -+#define NUM_REGULATORS 2 -+ -+#define to_dw9719_device(x) container_of(x, struct dw9719_device, sd) -+ -+struct dw9719_device { -+ struct device *dev; -+ struct i2c_client *client; -+ struct regulator_bulk_data regulators[NUM_REGULATORS]; -+ struct v4l2_subdev sd; -+ -+ struct dw9719_v4l2_ctrls { -+ struct v4l2_ctrl_handler handler; -+ struct v4l2_ctrl *focus; -+ } ctrls; -+}; -+ -+static int dw9719_i2c_rd8(struct i2c_client *client, u8 reg, u8 *val) -+{ -+ struct i2c_msg msg[2]; -+ u8 buf[2] = { reg }; -+ int ret; -+ -+ msg[0].addr = client->addr; -+ msg[0].flags = 0; -+ msg[0].len = 1; -+ msg[0].buf = buf; -+ -+ msg[1].addr = client->addr; -+ msg[1].flags = I2C_M_RD; -+ msg[1].len = 1; -+ msg[1].buf = &buf[1]; -+ *val = 0; -+ -+ ret = i2c_transfer(client->adapter, msg, 2); -+ if (ret < 0) -+ return ret; -+ -+ *val = buf[1]; -+ -+ return 0; -+} -+ -+static int dw9719_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) -+{ -+ struct i2c_msg msg; -+ int ret; -+ -+ u8 buf[2] = { reg, val }; -+ -+ msg.addr = client->addr; -+ msg.flags = 0; -+ msg.len = sizeof(buf); -+ msg.buf = buf; -+ -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ return ret < 0 ? ret : 0; -+} -+ -+static int dw9719_i2c_wr16(struct i2c_client *client, u8 reg, u16 val) -+{ -+ struct i2c_msg msg; -+ u8 buf[3] = { reg }; -+ int ret; -+ -+ put_unaligned_be16(val, buf + 1); -+ -+ msg.addr = client->addr; -+ msg.flags = 0; -+ msg.len = sizeof(buf); -+ msg.buf = buf; -+ -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ return ret < 0 ? ret : 0; -+} -+ -+static int dw9719_detect(struct dw9719_device *dw9719) -+{ -+ int ret; -+ u8 val; -+ -+ ret = dw9719_i2c_rd8(dw9719->client, DW9719_INFO, &val); -+ if (ret < 0) -+ return ret; -+ -+ if (val != DW9719_ID) { -+ dev_err(dw9719->dev, "Failed to detect correct id\n"); -+ ret = -ENXIO; -+ } -+ -+ return 0; -+} -+ -+static int dw9719_power_down(struct dw9719_device *dw9719) -+{ -+ return regulator_bulk_disable(NUM_REGULATORS, dw9719->regulators); -+} -+ -+static int dw9719_power_up(struct dw9719_device *dw9719) -+{ -+ int ret; -+ -+ ret = regulator_bulk_enable(NUM_REGULATORS, dw9719->regulators); -+ if (ret) -+ return ret; -+ -+ /* Jiggle SCL pin to wake up device */ -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, 1); -+ -+ /* Need 100us to transit from SHUTDOWN to STANDBY*/ -+ usleep_range(100, 1000); -+ -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, -+ DW9719_ENABLE_RINGING); -+ if (ret < 0) -+ goto fail_powerdown; -+ -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_MODE, DW9719_MODE_SAC3); -+ if (ret < 0) -+ goto fail_powerdown; -+ -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_VCM_FREQ, -+ DW9719_DEFAULT_VCM_FREQ); -+ if (ret < 0) -+ goto fail_powerdown; -+ -+ return 0; -+ -+fail_powerdown: -+ dw9719_power_down(dw9719); -+ return ret; -+} -+ -+static int dw9719_t_focus_abs(struct dw9719_device *dw9719, s32 value) -+{ -+ int ret; -+ -+ value = clamp(value, 0, DW9719_MAX_FOCUS_POS); -+ ret = dw9719_i2c_wr16(dw9719->client, DW9719_VCM_CURRENT, value); -+ if (ret < 0) -+ return ret; -+ -+ return 0; -+} -+ -+static int dw9719_set_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct dw9719_device *dw9719 = container_of(ctrl->handler, -+ struct dw9719_device, -+ ctrls.handler); -+ int ret; -+ -+ /* Only apply changes to the controls if the device is powered up */ -+ if (!pm_runtime_get_if_in_use(dw9719->dev)) -+ return 0; -+ -+ switch (ctrl->id) { -+ case V4L2_CID_FOCUS_ABSOLUTE: -+ ret = dw9719_t_focus_abs(dw9719, ctrl->val); -+ break; -+ default: -+ ret = -EINVAL; -+ } -+ -+ pm_runtime_put(dw9719->dev); -+ -+ return ret; -+} -+ -+static const struct v4l2_ctrl_ops dw9719_ctrl_ops = { -+ .s_ctrl = dw9719_set_ctrl, -+}; -+ -+static int __maybe_unused dw9719_suspend(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct dw9719_device *dw9719 = to_dw9719_device(sd); -+ int ret; -+ int val; -+ -+ for (val = dw9719->ctrls.focus->val; val >= 0; -+ val -= DW9719_CTRL_STEPS) { -+ ret = dw9719_t_focus_abs(dw9719, val); -+ if (ret) -+ return ret; -+ -+ usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); -+ } -+ -+ return dw9719_power_down(dw9719); -+} -+ -+static int __maybe_unused dw9719_resume(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct dw9719_device *dw9719 = to_dw9719_device(sd); -+ int current_focus = dw9719->ctrls.focus->val; -+ int ret; -+ int val; -+ -+ ret = dw9719_power_up(dw9719); -+ if (ret) -+ return ret; -+ -+ for (val = current_focus % DW9719_CTRL_STEPS; val < current_focus; -+ val += DW9719_CTRL_STEPS) { -+ ret = dw9719_t_focus_abs(dw9719, val); -+ if (ret) -+ goto err_power_down; -+ -+ usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); -+ } -+ -+ return 0; -+ -+err_power_down: -+ dw9719_power_down(dw9719); -+ return ret; -+} -+ -+static int dw9719_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -+{ -+ return pm_runtime_resume_and_get(sd->dev); -+} -+ -+static int dw9719_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -+{ -+ pm_runtime_put(sd->dev); -+ -+ return 0; -+} -+ -+static const struct v4l2_subdev_internal_ops dw9719_internal_ops = { -+ .open = dw9719_open, -+ .close = dw9719_close, -+}; -+ -+static int dw9719_init_controls(struct dw9719_device *dw9719) -+{ -+ const struct v4l2_ctrl_ops *ops = &dw9719_ctrl_ops; -+ int ret; -+ -+ ret = v4l2_ctrl_handler_init(&dw9719->ctrls.handler, 1); -+ if (ret) -+ return ret; -+ -+ dw9719->ctrls.focus = v4l2_ctrl_new_std(&dw9719->ctrls.handler, ops, -+ V4L2_CID_FOCUS_ABSOLUTE, 0, -+ DW9719_MAX_FOCUS_POS, 1, 0); -+ -+ if (dw9719->ctrls.handler.error) { -+ dev_err(dw9719->dev, "Error initialising v4l2 ctrls\n"); -+ ret = dw9719->ctrls.handler.error; -+ goto err_free_handler; -+ } -+ -+ dw9719->sd.ctrl_handler = &dw9719->ctrls.handler; -+ -+ return ret; -+ -+err_free_handler: -+ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); -+ return ret; -+} -+ -+static const struct v4l2_subdev_ops dw9719_ops = { }; -+ -+static int dw9719_probe(struct i2c_client *client) -+{ -+ struct dw9719_device *dw9719; -+ int ret; -+ -+ dw9719 = devm_kzalloc(&client->dev, sizeof(*dw9719), GFP_KERNEL); -+ if (!dw9719) -+ return -ENOMEM; -+ -+ dw9719->client = client; -+ dw9719->dev = &client->dev; -+ -+ dw9719->regulators[0].supply = "vdd"; -+ /* -+ * The DW9719 has only the 1 VDD voltage input, but some PMICs such as -+ * the TPS68470 PMIC have I2C passthrough capability, to disconnect the -+ * sensor's I2C pins from the I2C bus when the sensors VSIO (Sensor-IO) -+ * is off, because some sensors then short these pins to ground; -+ * and the DW9719 might sit behind this passthrough, this it needs to -+ * enable VSIO as that will also enable the I2C passthrough. -+ */ -+ dw9719->regulators[1].supply = "vsio"; -+ -+ ret = devm_regulator_bulk_get(&client->dev, NUM_REGULATORS, -+ dw9719->regulators); -+ if (ret) -+ return dev_err_probe(&client->dev, ret, "getting regulators\n"); -+ -+ v4l2_i2c_subdev_init(&dw9719->sd, client, &dw9719_ops); -+ dw9719->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; -+ dw9719->sd.internal_ops = &dw9719_internal_ops; -+ -+ ret = dw9719_init_controls(dw9719); -+ if (ret) -+ return ret; -+ -+ ret = media_entity_pads_init(&dw9719->sd.entity, 0, NULL); -+ if (ret < 0) -+ goto err_free_ctrl_handler; -+ -+ dw9719->sd.entity.function = MEDIA_ENT_F_LENS; -+ -+ /* -+ * We need the driver to work in the event that pm runtime is disable in -+ * the kernel, so power up and verify the chip now. In the event that -+ * runtime pm is disabled this will leave the chip on, so that the lens -+ * will work. -+ */ -+ -+ ret = dw9719_power_up(dw9719); -+ if (ret) -+ goto err_cleanup_media; -+ -+ ret = dw9719_detect(dw9719); -+ if (ret) -+ goto err_powerdown; -+ -+ pm_runtime_set_active(&client->dev); -+ pm_runtime_get_noresume(&client->dev); -+ pm_runtime_enable(&client->dev); -+ -+ ret = v4l2_async_register_subdev(&dw9719->sd); -+ if (ret < 0) -+ goto err_pm_runtime; -+ -+ pm_runtime_set_autosuspend_delay(&client->dev, 1000); -+ pm_runtime_use_autosuspend(&client->dev); -+ pm_runtime_put_autosuspend(&client->dev); -+ -+ return ret; -+ -+err_pm_runtime: -+ pm_runtime_disable(&client->dev); -+ pm_runtime_put_noidle(&client->dev); -+err_powerdown: -+ dw9719_power_down(dw9719); -+err_cleanup_media: -+ media_entity_cleanup(&dw9719->sd.entity); -+err_free_ctrl_handler: -+ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); -+ -+ return ret; -+} -+ -+static int dw9719_remove(struct i2c_client *client) -+{ -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct dw9719_device *dw9719 = container_of(sd, struct dw9719_device, -+ sd); -+ -+ pm_runtime_disable(&client->dev); -+ v4l2_async_unregister_subdev(sd); -+ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); -+ media_entity_cleanup(&dw9719->sd.entity); -+ -+ return 0; -+} -+ -+static const struct i2c_device_id dw9719_id_table[] = { -+ { "dw9719" }, -+ { } -+}; -+MODULE_DEVICE_TABLE(i2c, dw9719_id_table); -+ -+static const struct dev_pm_ops dw9719_pm_ops = { -+ SET_RUNTIME_PM_OPS(dw9719_suspend, dw9719_resume, NULL) -+}; -+ -+static struct i2c_driver dw9719_i2c_driver = { -+ .driver = { -+ .name = "dw9719", -+ .pm = &dw9719_pm_ops, -+ }, -+ .probe_new = dw9719_probe, -+ .remove = dw9719_remove, -+ .id_table = dw9719_id_table, -+}; -+module_i2c_driver(dw9719_i2c_driver); -+ -+MODULE_AUTHOR("Daniel Scally "); -+MODULE_DESCRIPTION("DW9719 VCM Driver"); -+MODULE_LICENSE("GPL"); --- -2.37.2 - -From 8792b2fe58ecf2b5a4d7195d7846dac21a740c9c Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 15 Jul 2022 23:48:00 +0200 -Subject: [PATCH] drivers/media/i2c: Fix DW9719 dependencies - -It should depend on VIDEO_DEV instead of VIDEO_V4L2. - -Signed-off-by: Maximilian Luz -Patchset: cameras ---- - drivers/media/i2c/Kconfig | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig -index 5c245f642ae3..50ea62e63784 100644 ---- a/drivers/media/i2c/Kconfig -+++ b/drivers/media/i2c/Kconfig -@@ -808,7 +808,7 @@ config VIDEO_DW9714 - - config VIDEO_DW9719 - tristate "DW9719 lens voice coil support" -- depends on I2C && VIDEO_V4L2 -+ depends on I2C && VIDEO_DEV - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - select V4L2_ASYNC --- -2.37.2 - -From 498ae9f4a0e000ab0ee3eb140466f54d22ebbbac Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 13 Dec 2021 22:38:17 +0000 -Subject: [PATCH] media: entity: Skip non-data links in graph iteration - -When iterating over the media graph, don't follow links that are not -data links. - -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/mc/mc-entity.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c -index 8ab0913d8d82..a8631f74dcee 100644 ---- a/drivers/media/mc/mc-entity.c -+++ b/drivers/media/mc/mc-entity.c -@@ -295,6 +295,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; --- -2.37.2 - -From ab99732bedda2cd5e8c97c22b682fb95f3e1dc4a Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 13 Dec 2021 22:53:09 +0000 -Subject: [PATCH] media: media.h: Add new media link type - -To describe in the kernel the connection between devices and their -supporting peripherals (for example, a camera sensor and the vcm -driving the focusing lens for it), add a new type of media link -to introduce the concept of these ancillary links. - -Add some elements to the uAPI documentation to explain the new link -type, their purpose and some aspects of their current implementation. - -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - .../media/mediactl/media-controller-model.rst | 6 ++++++ - .../media/mediactl/media-types.rst | 17 ++++++++++++----- - include/uapi/linux/media.h | 1 + - 3 files changed, 19 insertions(+), 5 deletions(-) - -diff --git a/Documentation/userspace-api/media/mediactl/media-controller-model.rst b/Documentation/userspace-api/media/mediactl/media-controller-model.rst -index 222cb99debb5..78bfdfb2a322 100644 ---- a/Documentation/userspace-api/media/mediactl/media-controller-model.rst -+++ b/Documentation/userspace-api/media/mediactl/media-controller-model.rst -@@ -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 -diff --git a/Documentation/userspace-api/media/mediactl/media-types.rst b/Documentation/userspace-api/media/mediactl/media-types.rst -index 0a26397bd01d..60747251d409 100644 ---- a/Documentation/userspace-api/media/mediactl/media-types.rst -+++ b/Documentation/userspace-api/media/mediactl/media-types.rst -@@ -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 -diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h -index 200fa8462b90..afbae7213d35 100644 ---- a/include/uapi/linux/media.h -+++ b/include/uapi/linux/media.h -@@ -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; --- -2.37.2 - -From 512b2e9c2dddd2924c95d5d9548ca89db1edefd1 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 13 Dec 2021 22:36:31 +0000 -Subject: [PATCH] media: entity: Add link_type_name() helper - -Now we have three types of media link, printing the right name during -debug output is slightly more complicated. Add a helper function to -make it easier. - -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/mc/mc-entity.c | 18 +++++++++++++++--- - 1 file changed, 15 insertions(+), 3 deletions(-) - -diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c -index a8631f74dcee..4bd80ce1a33d 100644 ---- a/drivers/media/mc/mc-entity.c -+++ b/drivers/media/mc/mc-entity.c -@@ -44,6 +44,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) - { -@@ -89,9 +103,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; --- -2.37.2 - -From ab787dfcd33ab0d1d1bc9c8faaf7b3a4b9a9c2cc Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Mon, 13 Dec 2021 22:54:10 +0000 -Subject: [PATCH] media: entity: Add support for ancillary links - -Add functions to create ancillary links, so that they don't need to -be manually created by users. - -Reviewed-by: Laurent Pinchart -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/mc/mc-entity.c | 22 ++++++++++++++++++++++ - include/media/media-entity.h | 19 +++++++++++++++++++ - 2 files changed, 41 insertions(+) - -diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c -index 4bd80ce1a33d..1ff60d411ea9 100644 ---- a/drivers/media/mc/mc-entity.c -+++ b/drivers/media/mc/mc-entity.c -@@ -1025,3 +1025,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); -diff --git a/include/media/media-entity.h b/include/media/media-entity.h -index 742918962d46..1d13b8939a11 100644 ---- a/include/media/media-entity.h -+++ b/include/media/media-entity.h -@@ -1121,4 +1121,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 --- -2.37.2 - -From ddc66e6de51574fb41902754928996c210710a0e Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Fri, 26 Nov 2021 22:55:50 +0000 -Subject: [PATCH] media: v4l2-async: Create links during - v4l2_async_match_notify() - -Upon an async fwnode match, there's some typical behaviour that the -notifier and matching subdev will want to do. For example, a notifier -representing a sensor matching to an async subdev representing its -VCM will want to create an ancillary link to expose that relationship -to userspace. - -To avoid lots of code in individual drivers, try to build these links -within v4l2 core. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/v4l2-core/v4l2-async.c | 31 ++++++++++++++++++++++++++++ - 1 file changed, 31 insertions(+) - -diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c -index 0404267f1ae4..436bd6900fd8 100644 ---- a/drivers/media/v4l2-core/v4l2-async.c -+++ b/drivers/media/v4l2-core/v4l2-async.c -@@ -275,6 +275,24 @@ v4l2_async_nf_try_complete(struct v4l2_async_notifier *notifier) - static int - v4l2_async_nf_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_nf_call_unbind(notifier, sd, asd); -+ v4l2_device_unregister_subdev(sd); -+ return ret; -+ } -+ - /* Remove from the waiting list */ - list_del(&asd->list); - sd->asd = asd; --- -2.37.2 - -From 9265a11f92a688baa1f820936e217bc8490c57c3 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 4 May 2022 23:21:45 +0100 -Subject: [PATCH] media: ipu3-cio2: Move functionality from .complete() to - .bound() - -Creating links and registering subdev nodes during the .complete() -callback has the unfortunate effect of preventing all cameras that -connect to a notifier from working if any one of their drivers fails -to probe. Moving the functionality from .complete() to .bound() allows -those camera sensor drivers that did probe correctly to work regardless. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 65 +++++++------------ - 1 file changed, 23 insertions(+), 42 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -index 0e9b0503b62a..50682a7b2a07 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -@@ -1382,7 +1382,10 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, - { - struct cio2_device *cio2 = to_cio2_device(notifier); - struct sensor_async_subdev *s_asd = to_sensor_asd(asd); -+ struct device *dev = &cio2->pci_dev->dev; - struct cio2_queue *q; -+ unsigned int pad; -+ int ret; - - if (cio2->queue[s_asd->csi2.port].sensor) - return -EBUSY; -@@ -1393,7 +1396,26 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, - q->sensor = sd; - q->csi_rx_base = cio2->base + CIO2_REG_PIPE_BASE(q->csi2.port); - -- return 0; -+ for (pad = 0; pad < q->sensor->entity.num_pads; pad++) -+ if (q->sensor->entity.pads[pad].flags & -+ MEDIA_PAD_FL_SOURCE) -+ break; -+ -+ if (pad == q->sensor->entity.num_pads) { -+ dev_err(dev, "failed to find src pad for %s\n", -+ q->sensor->name); -+ return -ENXIO; -+ } -+ -+ ret = media_create_pad_link(&q->sensor->entity, pad, &q->subdev.entity, -+ CIO2_PAD_SINK, 0); -+ if (ret) { -+ dev_err(dev, "failed to create link for %s\n", -+ q->sensor->name); -+ return ret; -+ } -+ -+ return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); - } - - /* The .unbind callback */ -@@ -1407,50 +1429,9 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, - cio2->queue[s_asd->csi2.port].sensor = NULL; - } - --/* .complete() is called after all subdevices have been located */ --static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) --{ -- struct cio2_device *cio2 = to_cio2_device(notifier); -- struct device *dev = &cio2->pci_dev->dev; -- struct sensor_async_subdev *s_asd; -- struct v4l2_async_subdev *asd; -- struct cio2_queue *q; -- unsigned int pad; -- int ret; -- -- list_for_each_entry(asd, &cio2->notifier.asd_list, asd_list) { -- s_asd = to_sensor_asd(asd); -- q = &cio2->queue[s_asd->csi2.port]; -- -- for (pad = 0; pad < q->sensor->entity.num_pads; pad++) -- if (q->sensor->entity.pads[pad].flags & -- MEDIA_PAD_FL_SOURCE) -- break; -- -- if (pad == q->sensor->entity.num_pads) { -- dev_err(dev, "failed to find src pad for %s\n", -- q->sensor->name); -- return -ENXIO; -- } -- -- ret = media_create_pad_link( -- &q->sensor->entity, pad, -- &q->subdev.entity, CIO2_PAD_SINK, -- 0); -- if (ret) { -- dev_err(dev, "failed to create link for %s\n", -- q->sensor->name); -- return ret; -- } -- } -- -- return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); --} -- - static const struct v4l2_async_notifier_operations cio2_async_ops = { - .bound = cio2_notifier_bound, - .unbind = cio2_notifier_unbind, -- .complete = cio2_notifier_complete, - }; - - static int cio2_parse_firmware(struct cio2_device *cio2) --- -2.37.2 - -From 54b2514cecc9cb79754586cfb1a716251f91b5e1 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 2 Jun 2022 22:15:56 +0100 -Subject: [PATCH] media: ipu3-cio2: Re-add .complete() to ipu3-cio2 - -Removing the .complete() callback had some unintended consequences. -Because the VCM driver is not directly linked to the ipu3-cio2 -driver .bound() never gets called for it, which means its devnode -is never created if it probes late. Because .complete() waits for -any sub-notifiers to also be complete it is captured in that call. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -index 50682a7b2a07..ff79582a583d 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -@@ -1429,9 +1429,18 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, - cio2->queue[s_asd->csi2.port].sensor = NULL; - } - -+/* .complete() is called after all subdevices have been located */ -+static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) -+{ -+ struct cio2_device *cio2 = to_cio2_device(notifier); -+ -+ return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); -+} -+ - static const struct v4l2_async_notifier_operations cio2_async_ops = { - .bound = cio2_notifier_bound, - .unbind = cio2_notifier_unbind, -+ .complete = cio2_notifier_complete, - }; - - static int cio2_parse_firmware(struct cio2_device *cio2) --- -2.37.2 - diff --git a/patches/5.18/0012-amd-gpio.patch b/patches/5.18/0012-amd-gpio.patch deleted file mode 100644 index 3a5097bca..000000000 --- a/patches/5.18/0012-amd-gpio.patch +++ /dev/null @@ -1,109 +0,0 @@ -From 8a7173b053bb41d26fade409d7c169fcfeb25914 Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Sat, 29 May 2021 17:47:38 +1000 -Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 - override - -This patch is the work of Thomas Gleixner and is -copied from: -https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ - -This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin -setup that is missing in the laptops ACPI table. - -This patch was used for validation of the issue, and is not a proper -fix, but is probably a better temporary hack than continuing to probe -the Legacy PIC and run with the PIC in an unknown state. - -Patchset: amd-gpio ---- - arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ - 1 file changed, 17 insertions(+) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 0d01e7f5078c..2b06cf5f2b1f 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -22,6 +22,7 @@ - #include - #include - #include -+#include - - #include - #include -@@ -1152,6 +1153,17 @@ static void __init mp_config_acpi_legacy_irqs(void) - } - } - -+static const struct dmi_system_id surface_quirk[] __initconst = { -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") -+ }, -+ }, -+ {} -+}; -+ - /* - * Parse IOAPIC related entries in MADT - * returns 0 on success, < 0 on error -@@ -1207,6 +1219,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) - acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, - acpi_gbl_FADT.sci_interrupt); - -+ if (dmi_check_system(surface_quirk)) { -+ pr_warn("Surface hack: Override irq 7\n"); -+ mp_override_legacy_irq(7, 3, 3, 7); -+ } -+ - /* Fill in identity legacy mappings where no override */ - mp_config_acpi_legacy_irqs(); - --- -2.37.2 - -From 4ee13359ba1a15044800b1451f3ab951cf5d462a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 3 Jun 2021 14:04:26 +0200 -Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override - quirk - -The 13" version of the Surface Laptop 4 has the same problem as the 15" -version, but uses a different SKU. Add that SKU to the quirk as well. - -Patchset: amd-gpio ---- - arch/x86/kernel/acpi/boot.c | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 2b06cf5f2b1f..caaec200bea2 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -1155,12 +1155,19 @@ static void __init mp_config_acpi_legacy_irqs(void) - - static const struct dmi_system_id surface_quirk[] __initconst = { - { -- .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") - }, - }, -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") -+ }, -+ }, - {} - }; - --- -2.37.2 - diff --git a/patches/5.19/0001-surface3-oemb.patch b/patches/5.19/0001-surface3-oemb.patch deleted file mode 100644 index 0367ed704..000000000 --- a/patches/5.19/0001-surface3-oemb.patch +++ /dev/null @@ -1,101 +0,0 @@ -From 85e9ef3cd3c785c7c5300e0ef9bc62899ea05797 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 18 Oct 2020 16:42:44 +0900 -Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI - table - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI data into dmi_system_id tables used -for quirks so that each driver can enable quirks even on the affected -systems. - -On affected systems, DMI data will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: surface3-oemb ---- - drivers/platform/surface/surface3-wmi.c | 7 +++++++ - sound/soc/codecs/rt5645.c | 9 +++++++++ - sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ - 3 files changed, 24 insertions(+) - -diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c -index ca4602bcc7de..490b9731068a 100644 ---- a/drivers/platform/surface/surface3-wmi.c -+++ b/drivers/platform/surface/surface3-wmi.c -@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - #endif - { } - }; -diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c -index 507aba8de3cc..1f8570e04083 100644 ---- a/sound/soc/codecs/rt5645.c -+++ b/sound/soc/codecs/rt5645.c -@@ -3718,6 +3718,15 @@ static const struct dmi_system_id dmi_platform_data[] = { - }, - .driver_data = (void *)&intel_braswell_platform_data, - }, -+ { -+ .ident = "Microsoft Surface 3", -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)&intel_braswell_platform_data, -+ }, - { - /* - * Match for the GPDwin which unfortunately uses somewhat -diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -index 6beb00858c33..d82d77387a0a 100644 ---- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c -+++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c -@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .callback = cht_surface_quirk_cb, -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - { } - }; - --- -2.38.0 - diff --git a/patches/5.19/0002-mwifiex.patch b/patches/5.19/0002-mwifiex.patch deleted file mode 100644 index 65360d384..000000000 --- a/patches/5.19/0002-mwifiex.patch +++ /dev/null @@ -1,704 +0,0 @@ -From 41d832142d8f7b6592237100be0b5afee131d645 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Tue, 29 Sep 2020 17:32:22 +0900 -Subject: [PATCH] mwifiex: pcie: add reset_wsid quirk for Surface 3 - -This commit adds reset_wsid quirk and uses this quirk for Surface 3 on -card reset. - -To reset mwifiex on Surface 3, it seems that calling the _DSM method -exists in \_SB.WSID [1] device is required. - -On Surface 3, calling the _DSM method removes/re-probes the card by -itself. So, need to place the reset function before performing FLR and -skip performing any other reset-related works. - -Note that Surface Pro 3 also has the WSID device [2], but it seems to need -more work. This commit only supports Surface 3 yet. - -[1] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_3/dsdt.dsl#L11947-L12011 -[2] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_pro_3/dsdt.dsl#L12164-L12216 - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 10 +++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 83 +++++++++++++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.h | 6 ++ - 3 files changed, 99 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index d5fb29400bad..033648526f16 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -2993,6 +2993,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; - -+ /* On Surface 3, reset_wsid method removes then re-probes card by -+ * itself. So, need to place it here and skip performing any other -+ * reset-related works. -+ */ -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) { -+ mwifiex_pcie_reset_wsid_quirk(card->dev); -+ /* skip performing any other reset-related works */ -+ return; -+ } -+ - /* We can't afford to wait here; remove() might be waiting on us. If we - * can't grab the device lock, maybe we'll get another chance later. - */ -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 0234cf3c2974..563dd0d5ac79 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -15,10 +15,21 @@ - * this warranty disclaimer. - */ - -+#include - #include - - #include "pcie_quirks.h" - -+/* For reset_wsid quirk */ -+#define ACPI_WSID_PATH "\\_SB.WSID" -+#define WSID_REV 0x0 -+#define WSID_FUNC_WIFI_PWR_OFF 0x1 -+#define WSID_FUNC_WIFI_PWR_ON 0x2 -+/* WSID _DSM UUID: "534ea3bf-fcc2-4e7a-908f-a13978f0c7ef" */ -+static const guid_t wsid_dsm_guid = -+ GUID_INIT(0x534ea3bf, 0xfcc2, 0x4e7a, -+ 0x90, 0x8f, 0xa1, 0x39, 0x78, 0xf0, 0xc7, 0xef); -+ - /* quirk table based on DMI matching */ - static const struct dmi_system_id mwifiex_quirk_table[] = { - { -@@ -87,6 +98,14 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_D3COLD, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - {} - }; - -@@ -103,6 +122,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - dev_info(&pdev->dev, "no quirks enabled\n"); - if (card->quirks & QUIRK_FW_RST_D3COLD) - dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+ if (card->quirks & QUIRK_FW_RST_WSID_S3) -+ dev_info(&pdev->dev, -+ "quirk reset_wsid for Surface 3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -@@ -159,3 +181,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) - - return 0; - } -+ -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev) -+{ -+ acpi_handle handle; -+ union acpi_object *obj; -+ acpi_status status; -+ -+ dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n"); -+ -+ status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&pdev->dev, "No ACPI handle for path %s\n", -+ ACPI_WSID_PATH); -+ return -ENODEV; -+ } -+ -+ if (!acpi_has_method(handle, "_DSM")) { -+ dev_err(&pdev->dev, "_DSM method not found\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power off func\n"); -+ return -ENODEV; -+ } -+ -+ if (!acpi_check_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON)) { -+ dev_err(&pdev->dev, -+ "_DSM method doesn't support wifi power on func\n"); -+ return -ENODEV; -+ } -+ -+ /* card will be removed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi off...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_OFF, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi off\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ /* card will be re-probed immediately after this call on Surface 3 */ -+ dev_info(&pdev->dev, "turning wifi on...\n"); -+ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, -+ WSID_REV, WSID_FUNC_WIFI_PWR_ON, -+ NULL); -+ if (!obj) { -+ dev_err(&pdev->dev, -+ "device _DSM execution failed for turning wifi on\n"); -+ return -EIO; -+ } -+ ACPI_FREE(obj); -+ -+ return 0; -+} -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 8ec4176d698f..25370c5a4f59 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -19,5 +19,11 @@ - - #define QUIRK_FW_RST_D3COLD BIT(0) - -+/* Surface 3 and Surface Pro 3 have the same _DSM method but need to -+ * be handled differently. Currently, only S3 is supported. -+ */ -+#define QUIRK_FW_RST_WSID_S3 BIT(1) -+ - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -+int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); --- -2.38.0 - -From f0b76fe6ed6b31445128d7979749945b0e1a71f8 Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Wed, 30 Sep 2020 18:08:24 +0900 -Subject: [PATCH] mwifiex: pcie: (OEMB) add quirk for Surface 3 with broken DMI - table - -(made referring to http://git.osdn.net/view?p=android-x86/kernel.git;a=commitdiff;h=18e2e857c57633b25b3b4120f212224a108cd883) - -On some Surface 3, the DMI table gets corrupted for unknown reasons -and breaks existing DMI matching used for device-specific quirks. - -This commit adds the (broken) DMI info for the affected Surface 3. - -On affected systems, DMI info will look like this: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:OEMB - /sys/devices/virtual/dmi/id/board_vendor:OEMB - /sys/devices/virtual/dmi/id/chassis_vendor:OEMB - /sys/devices/virtual/dmi/id/product_name:OEMB - /sys/devices/virtual/dmi/id/sys_vendor:OEMB - -Expected: - $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ - chassis_vendor,product_name,sys_vendor} - /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. - /sys/devices/virtual/dmi/id/board_name:Surface 3 - /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation - /sys/devices/virtual/dmi/id/product_name:Surface 3 - /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie_quirks.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 563dd0d5ac79..32e2f000e57b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -106,6 +106,15 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - }, - .driver_data = (void *)QUIRK_FW_RST_WSID_S3, - }, -+ { -+ .ident = "Surface 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, -+ }, - {} - }; - --- -2.38.0 - -From 7bd7485869a86cb0e58e5f1b98649769525412da Mon Sep 17 00:00:00 2001 -From: Tsuchiya Yuto -Date: Sun, 4 Oct 2020 00:11:49 +0900 -Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ - -Currently, mwifiex fw will crash after suspend on recent kernel series. -On Windows, it seems that the root port of wifi will never enter D3 state -(stay on D0 state). And on Linux, disabling the D3 state for the -bridge fixes fw crashing after suspend. - -This commit disables the D3 state of root port on driver initialization -and fixes fw crashing after suspend. - -Signed-off-by: Tsuchiya Yuto -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 27 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 033648526f16..ca6bcbe4794c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -380,6 +380,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) - { - struct pcie_service_card *card; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - int ret; - - pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", -@@ -421,6 +422,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return -1; - } - -+ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing -+ * after suspend -+ */ -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ parent_pdev->bridge_d3 = false; -+ - return 0; - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 32e2f000e57b..356401bab59c 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -38,7 +38,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5", -@@ -47,7 +48,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -56,7 +58,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 6", -@@ -64,7 +67,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 1", -@@ -72,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 2", -@@ -80,7 +85,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 1", -@@ -88,7 +94,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 2", -@@ -96,7 +103,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface 3", -@@ -134,6 +142,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_FW_RST_WSID_S3) - dev_info(&pdev->dev, - "quirk reset_wsid for Surface 3 enabled\n"); -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ dev_info(&pdev->dev, -+ "quirk no_brigde_d3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index 25370c5a4f59..a1de111ad1db 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -23,6 +23,7 @@ - * be handled differently. Currently, only S3 is supported. - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) -+#define QUIRK_NO_BRIDGE_D3 BIT(2) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.38.0 - -From 21720823a86c9e87a5df6d07ba79603a15d3a6f7 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 3 Nov 2020 13:28:04 +0100 -Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface - devices - -The most recent firmware of the 88W8897 card reports a hardcoded LTR -value to the system during initialization, probably as an (unsuccessful) -attempt of the developers to fix firmware crashes. This LTR value -prevents most of the Microsoft Surface devices from entering deep -powersaving states (either platform C-State 10 or S0ix state), because -the exit latency of that state would be higher than what the card can -tolerate. - -Turns out the card works just the same (including the firmware crashes) -no matter if that hardcoded LTR value is reported or not, so it's kind -of useless and only prevents us from saving power. - -To get rid of those hardcoded LTR reports, it's possible to reset the -PCI bridge device after initializing the cards firmware. I'm not exactly -sure why that works, maybe the power management subsystem of the PCH -resets its stored LTR values when doing a function level reset of the -bridge device. Doing the reset once after starting the wifi firmware -works very well, probably because the firmware only reports that LTR -value a single time during firmware startup. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ - .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ - .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + - 3 files changed, 31 insertions(+), 8 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index ca6bcbe4794c..24bcd22a2618 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -1781,9 +1781,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) - static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; -+ struct pci_dev *pdev = card->dev; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; - int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; - -+ /* Trigger a function level reset of the PCI bridge device, this makes -+ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value -+ * that prevents the system from entering package C10 and S0ix powersaving -+ * states. -+ * We need to do it here because it must happen after firmware -+ * initialization and this function is called after that is done. -+ */ -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ pci_reset_function(parent_pdev); -+ - /* Write the RX ring read pointer in to reg->rx_rdptr */ - if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | - tx_wrap)) { -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -index 356401bab59c..6437f067d07a 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c -@@ -39,7 +39,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5", -@@ -49,7 +50,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -59,7 +61,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Pro 6", -@@ -68,7 +71,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 1", -@@ -77,7 +81,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Book 2", -@@ -86,7 +91,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 1", -@@ -95,7 +101,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface Laptop 2", -@@ -104,7 +111,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, - .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -- QUIRK_NO_BRIDGE_D3), -+ QUIRK_NO_BRIDGE_D3 | -+ QUIRK_DO_FLR_ON_BRIDGE), - }, - { - .ident = "Surface 3", -@@ -145,6 +153,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) - if (card->quirks & QUIRK_NO_BRIDGE_D3) - dev_info(&pdev->dev, - "quirk no_brigde_d3 enabled\n"); -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -index a1de111ad1db..0e429779bb04 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -+++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h -@@ -24,6 +24,7 @@ - */ - #define QUIRK_FW_RST_WSID_S3 BIT(1) - #define QUIRK_NO_BRIDGE_D3 BIT(2) -+#define QUIRK_DO_FLR_ON_BRIDGE BIT(3) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); --- -2.38.0 - -From 14af6efcd912c7f9550ba95f5d9ea1422b3ec1df Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 25 Mar 2021 11:33:02 +0100 -Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell - 88W8897 - -The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) -is used in a lot of Microsoft Surface devices, and all those devices -suffer from very low 2.4GHz wifi connection speeds while bluetooth is -enabled. The reason for that is that the default passive scanning -interval for Bluetooth Low Energy devices is quite high in Linux -(interval of 60 msec and scan window of 30 msec, see hci_core.c), and -the Marvell chip is known for its bad bt+wifi coexisting performance. - -So decrease that passive scan interval and make the scan window shorter -on this particular device to allow for spending more time transmitting -wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and -the new scan window is 6.25 msec (0xa * 0,625 msec). - -This change has a very large impact on the 2.4GHz wifi speeds and gets -it up to performance comparable with the Windows driver, which seems to -apply a similar quirk. - -The interval and window length were tested and found to work very well -with a lot of Bluetooth Low Energy devices, including the Surface Pen, a -Bluetooth Speaker and two modern Bluetooth headphones. All devices were -discovered immediately after turning them on. Even lower values were -also tested, but they introduced longer delays until devices get -discovered. - -Patchset: mwifiex ---- - drivers/bluetooth/btusb.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index aaba2d737178..5d29e592cd34 100644 ---- a/drivers/bluetooth/btusb.c -+++ b/drivers/bluetooth/btusb.c -@@ -63,6 +63,7 @@ static struct usb_driver btusb_driver; - #define BTUSB_INTEL_BROKEN_SHUTDOWN_LED BIT(24) - #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) - #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) -+#define BTUSB_LOWER_LESCAN_INTERVAL BIT(27) - - static const struct usb_device_id btusb_table[] = { - /* Generic Bluetooth USB device */ -@@ -382,6 +383,7 @@ static const struct usb_device_id blacklist_table[] = { - { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, - { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, -+ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, - - /* Intel Bluetooth devices */ - { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, -@@ -3803,6 +3805,19 @@ static int btusb_probe(struct usb_interface *intf, - if (id->driver_info & BTUSB_MARVELL) - hdev->set_bdaddr = btusb_set_bdaddr_marvell; - -+ /* The Marvell 88W8897 combined wifi and bluetooth card is known for -+ * very bad bt+wifi coexisting performance. -+ * -+ * Decrease the passive BT Low Energy scan interval a bit -+ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter -+ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly -+ * higher wifi throughput while passively scanning for BT LE devices. -+ */ -+ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { -+ hdev->le_scan_interval = 0x0190; -+ hdev->le_scan_window = 0x000a; -+ } -+ - if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && - (id->driver_info & BTUSB_MEDIATEK)) { - hdev->setup = btusb_mtk_setup; --- -2.38.0 - -From 6961cb3b68080be915d3dd869342bb9f1bbc6251 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Tue, 10 Nov 2020 12:49:56 +0100 -Subject: [PATCH] mwifiex: Use non-posted PCI register writes - -On the 88W8897 card it's very important the TX ring write pointer is -updated correctly to its new value before setting the TX ready -interrupt, otherwise the firmware appears to crash (probably because -it's trying to DMA-read from the wrong place). - -Since PCI uses "posted writes" when writing to a register, it's not -guaranteed that a write will happen immediately. That means the pointer -might be outdated when setting the TX ready interrupt, leading to -firmware crashes especially when ASPM L1 and L1 substates are enabled -(because of the higher link latency, the write will probably take -longer). - -So fix those firmware crashes by always forcing non-posted writes. We do -that by simply reading back the register after writing it, just as a lot -of other drivers do. - -There are two reproducers that are fixed with this patch: - -1) During rx/tx traffic and with ASPM L1 substates enabled (the enabled -substates are platform dependent), the firmware crashes and eventually a -command timeout appears in the logs. That crash is fixed by using a -non-posted write in mwifiex_pcie_send_data(). - -2) When sending lots of commands to the card, waking it up from sleep in -very quick intervals, the firmware eventually crashes. That crash -appears to be fixed by some other non-posted write included here. - -Patchset: mwifiex ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 24bcd22a2618..b4ad0113a035 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -238,6 +238,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) - - iowrite32(data, card->pci_mmap1 + reg); - -+ /* Do a read-back, which makes the write non-posted, ensuring the -+ * completion before returning. -+ * The firmware of the 88W8897 card is buggy and this avoids crashes. -+ */ -+ ioread32(card->pci_mmap1 + reg); -+ - return 0; - } - --- -2.38.0 - diff --git a/patches/5.19/0003-ath10k.patch b/patches/5.19/0003-ath10k.patch deleted file mode 100644 index 29c8d5279..000000000 --- a/patches/5.19/0003-ath10k.patch +++ /dev/null @@ -1,121 +0,0 @@ -From 362abbda565532026fad14934f1049dcb10de451 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 27 Feb 2021 00:45:52 +0100 -Subject: [PATCH] ath10k: Add module parameters to override board files - -Some Surface devices, specifically the Surface Go and AMD version of the -Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better -with a different board file, as it seems that the firmeware included -upstream is buggy. - -As it is generally not a good idea to randomly overwrite files, let -alone doing so via packages, we add module parameters to override those -file names in the driver. This allows us to package/deploy the override -via a modprobe.d config. - -Signed-off-by: Maximilian Luz -Patchset: ath10k ---- - drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++ - 1 file changed, 58 insertions(+) - -diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c -index 688177453b07..e400a0318838 100644 ---- a/drivers/net/wireless/ath/ath10k/core.c -+++ b/drivers/net/wireless/ath/ath10k/core.c -@@ -36,6 +36,9 @@ static bool skip_otp; - static bool rawmode; - static bool fw_diag_log; - -+static char *override_board = ""; -+static char *override_board2 = ""; -+ - unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | - BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); - -@@ -48,6 +51,9 @@ module_param(rawmode, bool, 0644); - module_param(fw_diag_log, bool, 0644); - module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); - -+module_param(override_board, charp, 0644); -+module_param(override_board2, charp, 0644); -+ - MODULE_PARM_DESC(debug_mask, "Debugging mask"); - MODULE_PARM_DESC(uart_print, "Uart target debugging"); - MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); -@@ -56,6 +62,9 @@ MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); - MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); - MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); - -+MODULE_PARM_DESC(override_board, "Override for board.bin file"); -+MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); -+ - static const struct ath10k_hw_params ath10k_hw_params_list[] = { - { - .id = QCA988X_HW_2_0_VERSION, -@@ -876,6 +885,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) - return 0; - } - -+static const char *ath10k_override_board_fw_file(struct ath10k *ar, -+ const char *file) -+{ -+ if (strcmp(file, "board.bin") == 0) { -+ if (strcmp(override_board, "") == 0) -+ return file; -+ -+ if (strcmp(override_board, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", -+ override_board); -+ -+ return override_board; -+ } -+ -+ if (strcmp(file, "board-2.bin") == 0) { -+ if (strcmp(override_board2, "") == 0) -+ return file; -+ -+ if (strcmp(override_board2, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", -+ override_board2); -+ -+ return override_board2; -+ } -+ -+ return file; -+} -+ - static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - const char *dir, - const char *file) -@@ -890,6 +935,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - if (dir == NULL) - dir = "."; - -+ /* HACK: Override board.bin and board-2.bin files if specified. -+ * -+ * Some Surface devices perform better with a different board -+ * configuration. To this end, one would need to replace the board.bin -+ * file with the modified config and remove the board-2.bin file. -+ * Unfortunately, that's not a solution that we can easily package. So -+ * we add module options to perform these overrides here. -+ */ -+ -+ file = ath10k_override_board_fw_file(ar, file); -+ if (!file) -+ return ERR_PTR(-ENOENT); -+ - snprintf(filename, sizeof(filename), "%s/%s", dir, file); - ret = firmware_request_nowarn(&fw, filename, ar->dev); - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", --- -2.38.0 - diff --git a/patches/5.19/0004-ipts.patch b/patches/5.19/0004-ipts.patch deleted file mode 100644 index 4d3be7dae..000000000 --- a/patches/5.19/0004-ipts.patch +++ /dev/null @@ -1,1603 +0,0 @@ -From 294a27f5906afb8e0b04e9d3c12da00e2ede225f Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 30 Jul 2020 13:21:53 +0200 -Subject: [PATCH] misc: mei: Add missing IPTS device IDs - -Patchset: ipts ---- - drivers/misc/mei/hw-me-regs.h | 1 + - drivers/misc/mei/pci-me.c | 1 + - 2 files changed, 2 insertions(+) - -diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h -index 15e8e2b322b1..91587b808323 100644 ---- a/drivers/misc/mei/hw-me-regs.h -+++ b/drivers/misc/mei/hw-me-regs.h -@@ -92,6 +92,7 @@ - #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ - - #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ -+#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ - #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ - - #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ -diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c -index 5435604327a7..1165ee4f5928 100644 ---- a/drivers/misc/mei/pci-me.c -+++ b/drivers/misc/mei/pci-me.c -@@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { - {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, -+ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, --- -2.38.0 - -From d7e3c3abbcc9c6ef7edb9fc0c93264265f8ebca3 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Thu, 6 Aug 2020 11:20:41 +0200 -Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus - -Based on linux-surface/intel-precise-touch@3f362c - -Signed-off-by: Dorian Stoll -Patchset: ipts ---- - drivers/misc/Kconfig | 1 + - drivers/misc/Makefile | 1 + - drivers/misc/ipts/Kconfig | 17 ++ - drivers/misc/ipts/Makefile | 12 ++ - drivers/misc/ipts/context.h | 47 +++++ - drivers/misc/ipts/control.c | 113 +++++++++++ - drivers/misc/ipts/control.h | 24 +++ - drivers/misc/ipts/mei.c | 125 ++++++++++++ - drivers/misc/ipts/protocol.h | 347 ++++++++++++++++++++++++++++++++++ - drivers/misc/ipts/receiver.c | 224 ++++++++++++++++++++++ - drivers/misc/ipts/receiver.h | 16 ++ - drivers/misc/ipts/resources.c | 128 +++++++++++++ - drivers/misc/ipts/resources.h | 17 ++ - drivers/misc/ipts/uapi.c | 208 ++++++++++++++++++++ - drivers/misc/ipts/uapi.h | 47 +++++ - 15 files changed, 1327 insertions(+) - create mode 100644 drivers/misc/ipts/Kconfig - create mode 100644 drivers/misc/ipts/Makefile - create mode 100644 drivers/misc/ipts/context.h - create mode 100644 drivers/misc/ipts/control.c - create mode 100644 drivers/misc/ipts/control.h - create mode 100644 drivers/misc/ipts/mei.c - create mode 100644 drivers/misc/ipts/protocol.h - create mode 100644 drivers/misc/ipts/receiver.c - create mode 100644 drivers/misc/ipts/receiver.h - create mode 100644 drivers/misc/ipts/resources.c - create mode 100644 drivers/misc/ipts/resources.h - create mode 100644 drivers/misc/ipts/uapi.c - create mode 100644 drivers/misc/ipts/uapi.h - -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index 41d2bb0ae23a..effb258d4848 100644 ---- a/drivers/misc/Kconfig -+++ b/drivers/misc/Kconfig -@@ -500,4 +500,5 @@ source "drivers/misc/cardreader/Kconfig" - source "drivers/misc/habanalabs/Kconfig" - source "drivers/misc/uacce/Kconfig" - source "drivers/misc/pvpanic/Kconfig" -+source "drivers/misc/ipts/Kconfig" - endmenu -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index 70e800e9127f..a8d1e9447025 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -60,3 +60,4 @@ obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o - obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o - obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o - obj-$(CONFIG_OPEN_DICE) += open-dice.o -+obj-$(CONFIG_MISC_IPTS) += ipts/ -diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig -new file mode 100644 -index 000000000000..83e2a930c396 ---- /dev/null -+++ b/drivers/misc/ipts/Kconfig -@@ -0,0 +1,17 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+ -+config MISC_IPTS -+ tristate "Intel Precise Touch & Stylus" -+ depends on INTEL_MEI -+ help -+ Say Y here if your system has a touchscreen using Intels -+ Precise Touch & Stylus (IPTS) technology. -+ -+ If unsure say N. -+ -+ To compile this driver as a module, choose M here: the -+ module will be called ipts. -+ -+ Building this driver alone will not give you a working touchscreen. -+ It only exposed a userspace API that can be used by a daemon to -+ receive and process data from the touchscreen hardware. -diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile -new file mode 100644 -index 000000000000..8f58b9adbc94 ---- /dev/null -+++ b/drivers/misc/ipts/Makefile -@@ -0,0 +1,12 @@ -+# SPDX-License-Identifier: GPL-2.0-or-later -+# -+# Makefile for the IPTS touchscreen driver -+# -+ -+obj-$(CONFIG_MISC_IPTS) += ipts.o -+ipts-objs := control.o -+ipts-objs += mei.o -+ipts-objs += receiver.o -+ipts-objs += resources.o -+ipts-objs += uapi.o -+ -diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h -new file mode 100644 -index 000000000000..f4b06a2d3f72 ---- /dev/null -+++ b/drivers/misc/ipts/context.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTEXT_H_ -+#define _IPTS_CONTEXT_H_ -+ -+#include -+#include -+#include -+#include -+ -+#include "protocol.h" -+ -+enum ipts_host_status { -+ IPTS_HOST_STATUS_STARTING, -+ IPTS_HOST_STATUS_STARTED, -+ IPTS_HOST_STATUS_STOPPING, -+ IPTS_HOST_STATUS_STOPPED, -+}; -+ -+struct ipts_buffer_info { -+ u8 *address; -+ dma_addr_t dma_address; -+}; -+ -+struct ipts_context { -+ struct mei_cl_device *cldev; -+ struct device *dev; -+ -+ bool restart; -+ enum ipts_host_status status; -+ struct ipts_get_device_info_rsp device_info; -+ -+ struct ipts_buffer_info data[IPTS_BUFFERS]; -+ struct ipts_buffer_info doorbell; -+ -+ struct ipts_buffer_info feedback[IPTS_BUFFERS]; -+ struct ipts_buffer_info workqueue; -+ struct ipts_buffer_info host2me; -+}; -+ -+#endif /* _IPTS_CONTEXT_H_ */ -diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c -new file mode 100644 -index 000000000000..a1d1f97a13d7 ---- /dev/null -+++ b/drivers/misc/ipts/control.c -@@ -0,0 +1,113 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+#include "protocol.h" -+#include "resources.h" -+#include "uapi.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload, -+ size_t size) -+{ -+ int ret; -+ struct ipts_command cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_command)); -+ cmd.code = code; -+ -+ if (payload && size > 0) -+ memcpy(&cmd.payload, payload, size); -+ -+ ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size); -+ if (ret >= 0) -+ return 0; -+ -+ /* -+ * During shutdown the device might get pulled away from below our feet. -+ * Dont log an error in this case, because it will confuse people. -+ */ -+ if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING) -+ dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret); -+ -+ return ret; -+} -+ -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) -+{ -+ struct ipts_feedback_cmd cmd; -+ -+ memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); -+ cmd.buffer = buffer; -+ -+ return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, -+ sizeof(struct ipts_feedback_cmd)); -+} -+ -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value) -+{ -+ struct ipts_feedback_buffer *feedback; -+ -+ memset(ipts->host2me.address, 0, ipts->device_info.feedback_size); -+ feedback = (struct ipts_feedback_buffer *)ipts->host2me.address; -+ -+ feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; -+ feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; -+ feedback->buffer = IPTS_HOST2ME_BUFFER; -+ feedback->size = 2; -+ feedback->payload[0] = report; -+ feedback->payload[1] = value; -+ -+ return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER); -+} -+ -+int ipts_control_start(struct ipts_context *ipts) -+{ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Starting IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STARTING; -+ ipts->restart = false; -+ -+ ipts_uapi_link(ipts); -+ return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); -+} -+ -+int ipts_control_stop(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPING) -+ return -EBUSY; -+ -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ return -EBUSY; -+ -+ dev_info(ipts->dev, "Stopping IPTS\n"); -+ ipts->status = IPTS_HOST_STATUS_STOPPING; -+ -+ ipts_uapi_unlink(); -+ ipts_resources_free(ipts); -+ -+ ret = ipts_control_send_feedback(ipts, 0); -+ if (ret == -ENODEV) -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ return ret; -+} -+ -+int ipts_control_restart(struct ipts_context *ipts) -+{ -+ if (ipts->restart) -+ return -EBUSY; -+ -+ ipts->restart = true; -+ return ipts_control_stop(ipts); -+} -diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h -new file mode 100644 -index 000000000000..2c44e9e0e99f ---- /dev/null -+++ b/drivers/misc/ipts/control.h -@@ -0,0 +1,24 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_CONTROL_H_ -+#define _IPTS_CONTROL_H_ -+ -+#include -+ -+#include "context.h" -+ -+int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, -+ size_t size); -+int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); -+int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value); -+int ipts_control_start(struct ipts_context *ipts); -+int ipts_control_restart(struct ipts_context *ipts); -+int ipts_control_stop(struct ipts_context *ipts); -+ -+#endif /* _IPTS_CONTROL_H_ */ -diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c -new file mode 100644 -index 000000000000..59ecf13e00d2 ---- /dev/null -+++ b/drivers/misc/ipts/mei.c -@@ -0,0 +1,125 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "receiver.h" -+#include "uapi.h" -+ -+static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev) -+{ -+ int ret; -+ -+ ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)); -+ if (!ret) -+ return 0; -+ -+ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); -+} -+ -+static int ipts_mei_probe(struct mei_cl_device *cldev, -+ const struct mei_cl_device_id *id) -+{ -+ int ret; -+ struct ipts_context *ipts; -+ -+ if (ipts_mei_set_dma_mask(cldev)) { -+ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n"); -+ return -EFAULT; -+ } -+ -+ ret = mei_cldev_enable(cldev); -+ if (ret) { -+ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); -+ return ret; -+ } -+ -+ ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); -+ if (!ipts) { -+ mei_cldev_disable(cldev); -+ return -ENOMEM; -+ } -+ -+ ipts->cldev = cldev; -+ ipts->dev = &cldev->dev; -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ mei_cldev_set_drvdata(cldev, ipts); -+ mei_cldev_register_rx_cb(cldev, ipts_receiver_callback); -+ -+ return ipts_control_start(ipts); -+} -+ -+static void ipts_mei_remove(struct mei_cl_device *cldev) -+{ -+ int i; -+ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); -+ -+ ipts_control_stop(ipts); -+ -+ for (i = 0; i < 20; i++) { -+ if (ipts->status == IPTS_HOST_STATUS_STOPPED) -+ break; -+ -+ msleep(25); -+ } -+ -+ mei_cldev_disable(cldev); -+ kfree(ipts); -+} -+ -+static struct mei_cl_device_id ipts_mei_device_id_table[] = { -+ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, -+ {}, -+}; -+MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); -+ -+static struct mei_cl_driver ipts_mei_driver = { -+ .id_table = ipts_mei_device_id_table, -+ .name = "ipts", -+ .probe = ipts_mei_probe, -+ .remove = ipts_mei_remove, -+}; -+ -+static int __init ipts_mei_init(void) -+{ -+ int ret; -+ -+ ret = ipts_uapi_init(); -+ if (ret) -+ return ret; -+ -+ ret = mei_cldev_driver_register(&ipts_mei_driver); -+ if (ret) { -+ ipts_uapi_free(); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static void __exit ipts_mei_exit(void) -+{ -+ mei_cldev_driver_unregister(&ipts_mei_driver); -+ ipts_uapi_free(); -+} -+ -+MODULE_DESCRIPTION("IPTS touchscreen driver"); -+MODULE_AUTHOR("Dorian Stoll "); -+MODULE_LICENSE("GPL"); -+ -+module_init(ipts_mei_init); -+module_exit(ipts_mei_exit); -diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h -new file mode 100644 -index 000000000000..c3458904a94d ---- /dev/null -+++ b/drivers/misc/ipts/protocol.h -@@ -0,0 +1,347 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_PROTOCOL_H_ -+#define _IPTS_PROTOCOL_H_ -+ -+#include -+ -+/* -+ * The MEI client ID for IPTS functionality. -+ */ -+#define IPTS_MEI_UUID \ -+ UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, \ -+ 0x02, 0xae, 0x04) -+ -+/* -+ * Queries the device for vendor specific information. -+ * -+ * The command must not contain any payload. -+ * The response will contain struct ipts_get_device_info_rsp as payload. -+ */ -+#define IPTS_CMD_GET_DEVICE_INFO 0x00000001 -+#define IPTS_RSP_GET_DEVICE_INFO 0x80000001 -+ -+/* -+ * Sets the mode that IPTS will operate in. -+ * -+ * The command must contain struct ipts_set_mode_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MODE 0x00000002 -+#define IPTS_RSP_SET_MODE 0x80000002 -+ -+/* -+ * Configures the memory buffers that the ME will use -+ * for passing data to the host. -+ * -+ * The command must contain struct ipts_set_mem_window_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_SET_MEM_WINDOW 0x00000003 -+#define IPTS_RSP_SET_MEM_WINDOW 0x80000003 -+ -+/* -+ * Signals that the host is ready to receive data to the ME. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_READY_FOR_DATA 0x00000005 -+#define IPTS_RSP_READY_FOR_DATA 0x80000005 -+ -+/* -+ * Signals that a buffer can be refilled to the ME. -+ * -+ * The command must contain struct ipts_feedback_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_FEEDBACK 0x00000006 -+#define IPTS_RSP_FEEDBACK 0x80000006 -+ -+/* -+ * Resets the data flow from the ME to the hosts and -+ * clears the buffers that were set with SET_MEM_WINDOW. -+ * -+ * The command must not contain any payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007 -+#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007 -+ -+/* -+ * Instructs the ME to reset the touch sensor. -+ * -+ * The command must contain struct ipts_reset_sensor_cmd as payload. -+ * The response will not contain any payload. -+ */ -+#define IPTS_CMD_RESET_SENSOR 0x0000000B -+#define IPTS_RSP_RESET_SENSOR 0x8000000B -+ -+/** -+ * enum ipts_status - Possible status codes returned by IPTS commands. -+ * @IPTS_STATUS_SUCCESS: Operation completed successfully. -+ * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. -+ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. -+ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. -+ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. -+ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. -+ * The host must wait for a response before sending another -+ * command of the same type. -+ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it -+ * has not been initialized yet, or the system is improperly -+ * configured. -+ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. -+ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. -+ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. -+ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. -+ * The host can ignore this error and attempt to continue. -+ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. -+ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. -+ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. -+ * @IPTS_STATUS_TIMEOUT: The operation timed out. -+ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. -+ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. -+ * Further progress is not possible. -+ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. -+ * The host can attempt to continue. -+ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. -+ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. -+ */ -+enum ipts_status { -+ IPTS_STATUS_SUCCESS = 0, -+ IPTS_STATUS_INVALID_PARAMS = 1, -+ IPTS_STATUS_ACCESS_DENIED = 2, -+ IPTS_STATUS_CMD_SIZE_ERROR = 3, -+ IPTS_STATUS_NOT_READY = 4, -+ IPTS_STATUS_REQUEST_OUTSTANDING = 5, -+ IPTS_STATUS_NO_SENSOR_FOUND = 6, -+ IPTS_STATUS_OUT_OF_MEMORY = 7, -+ IPTS_STATUS_INTERNAL_ERROR = 8, -+ IPTS_STATUS_SENSOR_DISABLED = 9, -+ IPTS_STATUS_COMPAT_CHECK_FAIL = 10, -+ IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, -+ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, -+ IPTS_STATUS_RESET_FAILED = 13, -+ IPTS_STATUS_TIMEOUT = 14, -+ IPTS_STATUS_TEST_MODE_FAIL = 15, -+ IPTS_STATUS_SENSOR_FAIL_FATAL = 16, -+ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, -+ IPTS_STATUS_INVALID_DEVICE_CAPS = 18, -+ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, -+}; -+ -+/* -+ * The amount of buffers that is used for IPTS -+ */ -+#define IPTS_BUFFERS 16 -+ -+/* -+ * The special buffer ID that is used for direct host2me feedback. -+ */ -+#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS -+ -+/** -+ * enum ipts_mode - Operation mode for IPTS hardware -+ * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. -+ * The data is received as a HID report with ID 64. -+ * @IPTS_MODE_MULTITOUCH: The "proper" operation mode for IPTS. It will return -+ * stylus data as well as capacitive heatmap touch data. -+ * This data needs to be processed in userspace. -+ */ -+enum ipts_mode { -+ IPTS_MODE_SINGLETOUCH = 0, -+ IPTS_MODE_MULTITOUCH = 1, -+}; -+ -+/** -+ * struct ipts_set_mode_cmd - Payload for the SET_MODE command. -+ * @mode: The mode that IPTS should operate in. -+ */ -+struct ipts_set_mode_cmd { -+ enum ipts_mode mode; -+ u8 reserved[12]; -+} __packed; -+ -+#define IPTS_WORKQUEUE_SIZE 8192 -+#define IPTS_WORKQUEUE_ITEM_SIZE 16 -+ -+/** -+ * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command. -+ * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses. -+ * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses. -+ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. -+ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. -+ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. -+ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. -+ * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses. -+ * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses. -+ * @host2me_addr_lower: Lower 32 bits of the host2me buffer address. -+ * @host2me_addr_upper: Upper 32 bits of the host2me buffer address. -+ * @workqueue_item_size: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) -+ * @workqueue_size: Magic value. (IPTS_WORKQUEUE_SIZE) -+ * -+ * The data buffers are buffers that get filled with touch data by the ME. -+ * The doorbell buffer is a u32 that gets incremented by the ME once a data -+ * buffer has been filled with new data. -+ * -+ * The other buffers are required for using GuC submission with binary -+ * firmware. Since support for GuC submission has been dropped from i915, -+ * they are not used anymore, but they need to be allocated and passed, -+ * otherwise the hardware will refuse to start. -+ */ -+struct ipts_set_mem_window_cmd { -+ u32 data_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 data_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 workqueue_addr_lower; -+ u32 workqueue_addr_upper; -+ u32 doorbell_addr_lower; -+ u32 doorbell_addr_upper; -+ u32 feedback_buffer_addr_lower[IPTS_BUFFERS]; -+ u32 feedback_buffer_addr_upper[IPTS_BUFFERS]; -+ u32 host2me_addr_lower; -+ u32 host2me_addr_upper; -+ u32 host2me_size; -+ u8 reserved1; -+ u8 workqueue_item_size; -+ u16 workqueue_size; -+ u8 reserved[32]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_cmd - Payload for the FEEDBACK command. -+ * @buffer: The buffer that the ME should refill. -+ */ -+struct ipts_feedback_cmd { -+ u32 buffer; -+ u8 reserved[12]; -+} __packed; -+ -+/** -+ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. -+ */ -+enum ipts_feedback_cmd_type { -+ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, -+ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, -+ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, -+ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, -+}; -+ -+/** -+ * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains. -+ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. -+ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features command. -+ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. -+ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. -+ */ -+enum ipts_feedback_data_type { -+ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, -+ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, -+ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, -+ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, -+ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, -+}; -+ -+/** -+ * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer. -+ * @cmd_type: A command that should be executed on the sensor. -+ * @size: The size of the payload to be written. -+ * @buffer: The ID of the buffer that contains this feedback data. -+ * @protocol: The protocol version of the EDS. -+ * @data_type: The type of payload that the buffer contains. -+ * @spi_offset: The offset at which to write the payload data. -+ * @payload: Payload for the feedback command, or 0 if no payload is sent. -+ */ -+struct ipts_feedback_buffer { -+ enum ipts_feedback_cmd_type cmd_type; -+ u32 size; -+ u32 buffer; -+ u32 protocol; -+ enum ipts_feedback_data_type data_type; -+ u32 spi_offset; -+ u8 reserved[40]; -+ u8 payload[]; -+} __packed; -+ -+/** -+ * enum ipts_reset_type - Possible ways of resetting the touch sensor -+ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. -+ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. -+ */ -+enum ipts_reset_type { -+ IPTS_RESET_TYPE_HARD = 0, -+ IPTS_RESET_TYPE_SOFT = 1, -+}; -+ -+/** -+ * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. -+ * @type: What type of reset should be performed. -+ */ -+struct ipts_reset_sensor_cmd { -+ enum ipts_reset_type type; -+ u8 reserved[4]; -+} __packed; -+ -+/** -+ * struct ipts_command - A message sent from the host to the ME. -+ * @code: The message code describing the command. (see IPTS_CMD_*) -+ * @payload: Payload for the command, or 0 if no payload is required. -+ */ -+struct ipts_command { -+ u32 code; -+ u8 payload[320]; -+} __packed; -+ -+/** -+ * struct ipts_device_info - Payload for the GET_DEVICE_INFO response. -+ * @vendor_id: Vendor ID of the touch sensor. -+ * @device_id: Device ID of the touch sensor. -+ * @hw_rev: Hardware revision of the touch sensor. -+ * @fw_rev: Firmware revision of the touch sensor. -+ * @data_size: Required size of one data buffer. -+ * @feedback_size: Required size of one feedback buffer. -+ * @mode: Current operation mode of IPTS. -+ * @max_contacts: The amount of concurrent touches supported by the sensor. -+ */ -+struct ipts_get_device_info_rsp { -+ u16 vendor_id; -+ u16 device_id; -+ u32 hw_rev; -+ u32 fw_rev; -+ u32 data_size; -+ u32 feedback_size; -+ enum ipts_mode mode; -+ u8 max_contacts; -+ u8 reserved[19]; -+} __packed; -+ -+/** -+ * struct ipts_feedback_rsp - Payload for the FEEDBACK response. -+ * @buffer: The buffer that has received feedback. -+ */ -+struct ipts_feedback_rsp { -+ u32 buffer; -+} __packed; -+ -+/** -+ * struct ipts_response - A message sent from the ME to the host. -+ * @code: The message code describing the response. (see IPTS_RSP_*) -+ * @status: The status code returned by the command. -+ * @payload: Payload returned by the command. -+ */ -+struct ipts_response { -+ u32 code; -+ enum ipts_status status; -+ u8 payload[80]; -+} __packed; -+ -+#endif /* _IPTS_PROTOCOL_H_ */ -diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c -new file mode 100644 -index 000000000000..23dca13c2139 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.c -@@ -0,0 +1,224 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "resources.h" -+ -+/* -+ * Temporary parameter to guard gen7 multitouch mode. -+ * Remove once gen7 has stable iptsd support. -+ */ -+static bool gen7mt; -+module_param(gen7mt, bool, 0644); -+ -+static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_set_mode_cmd cmd; -+ -+ memcpy(&ipts->device_info, rsp->payload, -+ sizeof(struct ipts_get_device_info_rsp)); -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd)); -+ cmd.mode = IPTS_MODE_MULTITOUCH; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd, -+ sizeof(struct ipts_set_mode_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mode(struct ipts_context *ipts) -+{ -+ int i, ret; -+ struct ipts_set_mem_window_cmd cmd; -+ -+ ret = ipts_resources_alloc(ipts); -+ if (ret) { -+ dev_err(ipts->dev, "Failed to allocate resources\n"); -+ return ret; -+ } -+ -+ memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ cmd.data_buffer_addr_lower[i] = -+ lower_32_bits(ipts->data[i].dma_address); -+ -+ cmd.data_buffer_addr_upper[i] = -+ upper_32_bits(ipts->data[i].dma_address); -+ -+ cmd.feedback_buffer_addr_lower[i] = -+ lower_32_bits(ipts->feedback[i].dma_address); -+ -+ cmd.feedback_buffer_addr_upper[i] = -+ upper_32_bits(ipts->feedback[i].dma_address); -+ } -+ -+ cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); -+ cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); -+ -+ cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); -+ cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); -+ -+ cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); -+ cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); -+ -+ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; -+ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; -+ -+ return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, -+ sizeof(struct ipts_set_mem_window_cmd)); -+} -+ -+static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts) -+{ -+ int ret; -+ -+ dev_info(ipts->dev, "Device %04hX:%04hX ready\n", -+ ipts->device_info.vendor_id, ipts->device_info.device_id); -+ ipts->status = IPTS_HOST_STATUS_STARTED; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); -+ if (ret) -+ return ret; -+ -+ if (!gen7mt) -+ return 0; -+ -+ return ipts_control_set_feature(ipts, 0x5, 0x1); -+} -+ -+static int ipts_receiver_handle_feedback(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ struct ipts_feedback_rsp feedback; -+ -+ if (ipts->status != IPTS_HOST_STATUS_STOPPING) -+ return 0; -+ -+ memcpy(&feedback, rsp->payload, sizeof(feedback)); -+ -+ if (feedback.buffer < IPTS_BUFFERS - 1) -+ return ipts_control_send_feedback(ipts, feedback.buffer + 1); -+ -+ return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); -+} -+ -+static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) -+{ -+ ipts->status = IPTS_HOST_STATUS_STOPPED; -+ -+ if (ipts->restart) -+ return ipts_control_start(ipts); -+ -+ return 0; -+} -+ -+static bool ipts_receiver_sensor_was_reset(u32 status) -+{ -+ return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || -+ status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; -+} -+ -+static bool ipts_receiver_handle_error(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ bool error; -+ -+ switch (rsp->status) { -+ case IPTS_STATUS_SUCCESS: -+ case IPTS_STATUS_COMPAT_CHECK_FAIL: -+ error = false; -+ break; -+ case IPTS_STATUS_INVALID_PARAMS: -+ error = rsp->code != IPTS_RSP_FEEDBACK; -+ break; -+ case IPTS_STATUS_SENSOR_DISABLED: -+ error = ipts->status != IPTS_HOST_STATUS_STOPPING; -+ break; -+ default: -+ error = true; -+ break; -+ } -+ -+ if (!error) -+ return false; -+ -+ dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code, -+ rsp->status); -+ -+ if (ipts_receiver_sensor_was_reset(rsp->status)) { -+ dev_err(ipts->dev, "Sensor was reset\n"); -+ -+ if (ipts_control_restart(ipts)) -+ dev_err(ipts->dev, "Failed to restart IPTS\n"); -+ } -+ -+ return true; -+} -+ -+static void ipts_receiver_handle_response(struct ipts_context *ipts, -+ struct ipts_response *rsp) -+{ -+ int ret; -+ -+ if (ipts_receiver_handle_error(ipts, rsp)) -+ return; -+ -+ switch (rsp->code) { -+ case IPTS_RSP_GET_DEVICE_INFO: -+ ret = ipts_receiver_handle_get_device_info(ipts, rsp); -+ break; -+ case IPTS_RSP_SET_MODE: -+ ret = ipts_receiver_handle_set_mode(ipts); -+ break; -+ case IPTS_RSP_SET_MEM_WINDOW: -+ ret = ipts_receiver_handle_set_mem_window(ipts); -+ break; -+ case IPTS_RSP_FEEDBACK: -+ ret = ipts_receiver_handle_feedback(ipts, rsp); -+ break; -+ case IPTS_RSP_CLEAR_MEM_WINDOW: -+ ret = ipts_receiver_handle_clear_mem_window(ipts); -+ break; -+ default: -+ ret = 0; -+ break; -+ } -+ -+ if (!ret) -+ return; -+ -+ dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n", -+ rsp->code, ret); -+ -+ if (ipts_control_stop(ipts)) -+ dev_err(ipts->dev, "Failed to stop IPTS\n"); -+} -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev) -+{ -+ int ret; -+ struct ipts_response rsp; -+ struct ipts_context *ipts; -+ -+ ipts = mei_cldev_get_drvdata(cldev); -+ -+ ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response)); -+ if (ret <= 0) { -+ dev_err(ipts->dev, "Error while reading response: %d\n", ret); -+ return; -+ } -+ -+ ipts_receiver_handle_response(ipts, &rsp); -+} -diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h -new file mode 100644 -index 000000000000..7f075afa7ef8 ---- /dev/null -+++ b/drivers/misc/ipts/receiver.h -@@ -0,0 +1,16 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RECEIVER_H_ -+#define _IPTS_RECEIVER_H_ -+ -+#include -+ -+void ipts_receiver_callback(struct mei_cl_device *cldev); -+ -+#endif /* _IPTS_RECEIVER_H_ */ -diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c -new file mode 100644 -index 000000000000..8e3a2409e438 ---- /dev/null -+++ b/drivers/misc/ipts/resources.c -@@ -0,0 +1,128 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+ -+#include "context.h" -+ -+void ipts_resources_free(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, data_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ if (!buffers[i].address) -+ continue; -+ -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ buffers[i].address, buffers[i].dma_address); -+ -+ buffers[i].address = NULL; -+ buffers[i].dma_address = 0; -+ } -+ -+ if (ipts->doorbell.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->doorbell.address, -+ ipts->doorbell.dma_address); -+ -+ ipts->doorbell.address = NULL; -+ ipts->doorbell.dma_address = 0; -+ } -+ -+ if (ipts->workqueue.address) { -+ dma_free_coherent(ipts->dev, sizeof(u32), -+ ipts->workqueue.address, -+ ipts->workqueue.dma_address); -+ -+ ipts->workqueue.address = NULL; -+ ipts->workqueue.dma_address = 0; -+ } -+ -+ if (ipts->host2me.address) { -+ dma_free_coherent(ipts->dev, feedback_buffer_size, -+ ipts->host2me.address, -+ ipts->host2me.dma_address); -+ -+ ipts->host2me.address = NULL; -+ ipts->host2me.dma_address = 0; -+ } -+} -+ -+int ipts_resources_alloc(struct ipts_context *ipts) -+{ -+ int i; -+ struct ipts_buffer_info *buffers; -+ -+ u32 data_buffer_size = ipts->device_info.data_size; -+ u32 feedback_buffer_size = ipts->device_info.feedback_size; -+ -+ buffers = ipts->data; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, data_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ buffers = ipts->feedback; -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ buffers[i].address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &buffers[i].dma_address, GFP_KERNEL); -+ -+ if (!buffers[i].address) -+ goto release_resources; -+ } -+ -+ ipts->doorbell.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->doorbell.dma_address, GFP_KERNEL); -+ -+ if (!ipts->doorbell.address) -+ goto release_resources; -+ -+ ipts->workqueue.address = -+ dma_alloc_coherent(ipts->dev, sizeof(u32), -+ &ipts->workqueue.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ ipts->host2me.address = -+ dma_alloc_coherent(ipts->dev, feedback_buffer_size, -+ &ipts->host2me.dma_address, GFP_KERNEL); -+ -+ if (!ipts->workqueue.address) -+ goto release_resources; -+ -+ return 0; -+ -+release_resources: -+ -+ ipts_resources_free(ipts); -+ return -ENOMEM; -+} -diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h -new file mode 100644 -index 000000000000..fdac0eee9156 ---- /dev/null -+++ b/drivers/misc/ipts/resources.h -@@ -0,0 +1,17 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_RESOURCES_H_ -+#define _IPTS_RESOURCES_H_ -+ -+#include "context.h" -+ -+int ipts_resources_alloc(struct ipts_context *ipts); -+void ipts_resources_free(struct ipts_context *ipts); -+ -+#endif /* _IPTS_RESOURCES_H_ */ -diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c -new file mode 100644 -index 000000000000..598f0710ad64 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.c -@@ -0,0 +1,208 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "context.h" -+#include "control.h" -+#include "protocol.h" -+#include "uapi.h" -+ -+struct ipts_uapi uapi; -+ -+static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count, -+ loff_t *offset) -+{ -+ int buffer; -+ int maxbytes; -+ struct ipts_context *ipts = uapi.ipts; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ maxbytes = ipts->device_info.data_size - *offset; -+ if (maxbytes <= 0 || count > maxbytes) -+ return -EINVAL; -+ -+ if (copy_to_user(buf, ipts->data[buffer].address + *offset, count)) -+ return -EFAULT; -+ -+ return count; -+} -+ -+static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ u8 ready = 0; -+ -+ if (ipts) -+ ready = ipts->status == IPTS_HOST_STATUS_STARTED; -+ -+ if (copy_to_user(buffer, &ready, sizeof(u8))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ struct ipts_device_info info; -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ info.vendor = ipts->device_info.vendor_id; -+ info.product = ipts->device_info.device_id; -+ info.version = ipts->device_info.fw_rev; -+ info.buffer_size = ipts->device_info.data_size; -+ info.max_contacts = ipts->device_info.max_contacts; -+ -+ if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts, -+ unsigned long arg) -+{ -+ void __user *buffer = (void __user *)arg; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32))) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts, -+ struct file *file) -+{ -+ int ret; -+ u32 buffer; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); -+ -+ ret = ipts_control_send_feedback(ipts, buffer); -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) -+{ -+ int ret; -+ struct ipts_reset_sensor_cmd cmd; -+ -+ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) -+ return -ENODEV; -+ -+ memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); -+ cmd.type = IPTS_RESET_TYPE_SOFT; -+ -+ ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, -+ sizeof(struct ipts_reset_sensor_cmd)); -+ -+ if (ret) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static long ipts_uapi_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) -+{ -+ struct ipts_context *ipts = uapi.ipts; -+ -+ switch (cmd) { -+ case IPTS_IOCTL_GET_DEVICE_READY: -+ return ipts_uapi_ioctl_get_device_ready(ipts, arg); -+ case IPTS_IOCTL_GET_DEVICE_INFO: -+ return ipts_uapi_ioctl_get_device_info(ipts, arg); -+ case IPTS_IOCTL_GET_DOORBELL: -+ return ipts_uapi_ioctl_get_doorbell(ipts, arg); -+ case IPTS_IOCTL_SEND_FEEDBACK: -+ return ipts_uapi_ioctl_send_feedback(ipts, file); -+ case IPTS_IOCTL_SEND_RESET: -+ return ipts_uapi_ioctl_send_reset(ipts); -+ default: -+ return -ENOTTY; -+ } -+} -+ -+static const struct file_operations ipts_uapi_fops = { -+ .owner = THIS_MODULE, -+ .read = ipts_uapi_read, -+ .unlocked_ioctl = ipts_uapi_ioctl, -+#ifdef CONFIG_COMPAT -+ .compat_ioctl = ipts_uapi_ioctl, -+#endif -+}; -+ -+void ipts_uapi_link(struct ipts_context *ipts) -+{ -+ uapi.ipts = ipts; -+} -+ -+void ipts_uapi_unlink(void) -+{ -+ uapi.ipts = NULL; -+} -+ -+int ipts_uapi_init(void) -+{ -+ int i, major; -+ -+ alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts"); -+ uapi.class = class_create(THIS_MODULE, "ipts"); -+ -+ major = MAJOR(uapi.dev); -+ -+ cdev_init(&uapi.cdev, &ipts_uapi_fops); -+ uapi.cdev.owner = THIS_MODULE; -+ cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) { -+ device_create(uapi.class, NULL, MKDEV(major, i), NULL, -+ "ipts/%d", i); -+ } -+ -+ return 0; -+} -+ -+void ipts_uapi_free(void) -+{ -+ int i; -+ int major; -+ -+ major = MAJOR(uapi.dev); -+ -+ for (i = 0; i < IPTS_BUFFERS; i++) -+ device_destroy(uapi.class, MKDEV(major, i)); -+ -+ cdev_del(&uapi.cdev); -+ -+ unregister_chrdev_region(MKDEV(major, 0), MINORMASK); -+ class_destroy(uapi.class); -+} -diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h -new file mode 100644 -index 000000000000..53fb86a88f97 ---- /dev/null -+++ b/drivers/misc/ipts/uapi.h -@@ -0,0 +1,47 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2016 Intel Corporation -+ * Copyright (c) 2020 Dorian Stoll -+ * -+ * Linux driver for Intel Precise Touch & Stylus -+ */ -+ -+#ifndef _IPTS_UAPI_H_ -+#define _IPTS_UAPI_H_ -+ -+#include -+ -+#include "context.h" -+ -+struct ipts_uapi { -+ dev_t dev; -+ struct class *class; -+ struct cdev cdev; -+ -+ struct ipts_context *ipts; -+}; -+ -+struct ipts_device_info { -+ __u16 vendor; -+ __u16 product; -+ __u32 version; -+ __u32 buffer_size; -+ __u8 max_contacts; -+ -+ /* For future expansion */ -+ __u8 reserved[19]; -+}; -+ -+#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8) -+#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info) -+#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) -+#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04) -+#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) -+ -+void ipts_uapi_link(struct ipts_context *ipts); -+void ipts_uapi_unlink(void); -+ -+int ipts_uapi_init(void); -+void ipts_uapi_free(void); -+ -+#endif /* _IPTS_UAPI_H_ */ --- -2.38.0 - -From 7e6478bc120777031b4f547cb8a7e90711c061ba Mon Sep 17 00:00:00 2001 -From: Liban Hannan -Date: Tue, 12 Apr 2022 23:31:12 +0100 -Subject: [PATCH] iommu: ipts: use IOMMU passthrough mode for IPTS - -Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. -Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: - -DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr -0x104ea3000 [fault reason 0x06] PTE Read access is not set - -This is very similar to the bug described at: -https://bugs.launchpad.net/bugs/1958004 - -Fixed with the following patch which this patch basically copies: -https://launchpadlibrarian.net/586396847/43255ca.diff -Patchset: ipts ---- - drivers/iommu/intel/iommu.c | 24 ++++++++++++++++++++++++ - 1 file changed, 24 insertions(+) - -diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c -index 3ed15e8ca677..8c5d47c5262f 100644 ---- a/drivers/iommu/intel/iommu.c -+++ b/drivers/iommu/intel/iommu.c -@@ -37,6 +37,8 @@ - #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) - #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) - #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) -+#define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ -+ ((pdev)->device == 0x9d3e)) - #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) - - #define IOAPIC_RANGE_START (0xfee00000) -@@ -295,12 +297,14 @@ int intel_iommu_enabled = 0; - EXPORT_SYMBOL_GPL(intel_iommu_enabled); - - static int dmar_map_gfx = 1; -+static int dmar_map_ipts = 1; - static int intel_iommu_superpage = 1; - static int iommu_identity_mapping; - static int iommu_skip_te_disable; - - #define IDENTMAP_GFX 2 - #define IDENTMAP_AZALIA 4 -+#define IDENTMAP_IPTS 16 - - int intel_iommu_gfx_mapped; - EXPORT_SYMBOL_GPL(intel_iommu_gfx_mapped); -@@ -2683,6 +2687,9 @@ static int device_def_domain_type(struct device *dev) - - if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) - return IOMMU_DOMAIN_IDENTITY; -+ -+ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) -+ return IOMMU_DOMAIN_IDENTITY; - } - - return 0; -@@ -3105,6 +3112,9 @@ static int __init init_dmars(void) - if (!dmar_map_gfx) - iommu_identity_mapping |= IDENTMAP_GFX; - -+ if (!dmar_map_ipts) -+ iommu_identity_mapping |= IDENTMAP_IPTS; -+ - check_tylersburg_isoch(); - - ret = si_domain_init(hw_pass_through); -@@ -4933,6 +4943,17 @@ static void quirk_iommu_igfx(struct pci_dev *dev) - dmar_map_gfx = 0; - } - -+static void quirk_iommu_ipts(struct pci_dev *dev) -+{ -+ if (!IS_IPTS(dev)) -+ return; -+ -+ if (risky_device(dev)) -+ return; -+ -+ pci_info(dev, "Passthrough IOMMU for IPTS\n"); -+ dmar_map_ipts = 0; -+} - /* G4x/GM45 integrated gfx dmar support is totally busted. */ - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); -@@ -4968,6 +4989,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); - -+/* disable IPTS dmar support */ -+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); -+ - static void quirk_iommu_rwbf(struct pci_dev *dev) - { - if (risky_device(dev)) --- -2.38.0 - diff --git a/patches/5.19/0005-surface-sam.patch b/patches/5.19/0005-surface-sam.patch deleted file mode 100644 index dc4dfdf94..000000000 --- a/patches/5.19/0005-surface-sam.patch +++ /dev/null @@ -1,4703 +0,0 @@ -From ffca01783fe3bfb2e2fece56d1cc0d934ebeaab8 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:36 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow is_ssam_device() to be - used when CONFIG_SURFACE_AGGREGATOR_BUS is disabled - -In SSAM subsystem drivers that handle both ACPI and SSAM-native client -devices, we may want to check whether we have a SSAM (native) client -device. Further, we may want to do this even when instantiation thereof -cannot happen due to CONFIG_SURFACE_AGGREGATOR_BUS=n. Currently, doing -so causes an error due to an undefined reference error due to -ssam_device_type being placed in the bus source unit. - -Therefore, if CONFIG_SURFACE_AGGREGATOR_BUS is not defined, simply let -is_ssam_device() return false to prevent this error. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-2-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - include/linux/surface_aggregator/device.h | 11 +++++++++++ - 1 file changed, 11 insertions(+) - -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index cc257097eb05..62b38b4487eb 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -177,6 +177,8 @@ struct ssam_device_driver { - void (*remove)(struct ssam_device *sdev); - }; - -+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+ - extern struct bus_type ssam_bus_type; - extern const struct device_type ssam_device_type; - -@@ -193,6 +195,15 @@ static inline bool is_ssam_device(struct device *d) - return d->type == &ssam_device_type; - } - -+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+static inline bool is_ssam_device(struct device *d) -+{ -+ return false; -+} -+ -+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ - /** - * to_ssam_device() - Casts the given device to a SSAM client device. - * @d: The device to cast. --- -2.38.0 - -From 1e544dfb14e1f795b5d6031ddab8af2b2ce832f8 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:37 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow devices to be marked as - hot-removed - -Some SSAM devices, notably the keyboard cover (keyboard and touchpad) on -the Surface Pro 8, can be hot-removed. When this occurs, communication -with the device may fail and time out. This timeout can unnecessarily -block and slow down device removal and even cause issues when the -devices are detached and re-attached quickly. Thus, communication should -generally be avoided once hot-removal is detected. - -While we already remove a device as soon as we detect its (hot-)removal, -the corresponding device driver may still attempt to communicate with -the device during teardown. This is especially critical as communication -failure may also extend to disabling of events, which is typically done -at that stage. - -Add a flag to allow marking devices as hot-removed. This can then be -used during client driver teardown to check if any communication -attempts should be avoided. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-3-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - include/linux/surface_aggregator/device.h | 48 +++++++++++++++++++++-- - 1 file changed, 45 insertions(+), 3 deletions(-) - -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 62b38b4487eb..6df7c8d4e50e 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -148,17 +148,30 @@ struct ssam_device_uid { - #define SSAM_SDEV(cat, tid, iid, fun) \ - SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) - -+/* -+ * enum ssam_device_flags - Flags for SSAM client devices. -+ * @SSAM_DEVICE_HOT_REMOVED_BIT: -+ * The device has been hot-removed. Further communication with it may time -+ * out and should be avoided. -+ */ -+enum ssam_device_flags { -+ SSAM_DEVICE_HOT_REMOVED_BIT = 0, -+}; -+ - /** - * struct ssam_device - SSAM client device. -- * @dev: Driver model representation of the device. -- * @ctrl: SSAM controller managing this device. -- * @uid: UID identifying the device. -+ * @dev: Driver model representation of the device. -+ * @ctrl: SSAM controller managing this device. -+ * @uid: UID identifying the device. -+ * @flags: Device state flags, see &enum ssam_device_flags. - */ - struct ssam_device { - struct device dev; - struct ssam_controller *ctrl; - - struct ssam_device_uid uid; -+ -+ unsigned long flags; - }; - - /** -@@ -251,6 +264,35 @@ struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, - int ssam_device_add(struct ssam_device *sdev); - void ssam_device_remove(struct ssam_device *sdev); - -+/** -+ * ssam_device_mark_hot_removed() - Mark the given device as hot-removed. -+ * @sdev: The device to mark as hot-removed. -+ * -+ * Mark the device as having been hot-removed. This signals drivers using the -+ * device that communication with the device should be avoided and may lead to -+ * timeouts. -+ */ -+static inline void ssam_device_mark_hot_removed(struct ssam_device *sdev) -+{ -+ dev_dbg(&sdev->dev, "marking device as hot-removed\n"); -+ set_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); -+} -+ -+/** -+ * ssam_device_is_hot_removed() - Check if the given device has been -+ * hot-removed. -+ * @sdev: The device to check. -+ * -+ * Checks if the given device has been marked as hot-removed. See -+ * ssam_device_mark_hot_removed() for more details. -+ * -+ * Return: Returns ``true`` if the device has been marked as hot-removed. -+ */ -+static inline bool ssam_device_is_hot_removed(struct ssam_device *sdev) -+{ -+ return test_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); -+} -+ - /** - * ssam_device_get() - Increment reference count of SSAM client device. - * @sdev: The device to increment the reference count of. --- -2.38.0 - -From 2d632094e97756158e5593b3bef87db0ff0e5f47 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:38 +0200 -Subject: [PATCH] platform/surface: aggregator: Allow notifiers to avoid - communication on unregistering - -When SSAM client devices have been (physically) hot-removed, -communication attempts with those devices may fail and time out. This -can even extend to event notifiers, due to which timeouts may occur -during device removal, slowing down that process. - -Add a parameter to the notifier unregister function that allows skipping -communication with the EC to prevent this. Furthermore, add wrappers for -registering and unregistering notifiers belonging to SSAM client devices -that automatically check if the device has been marked as hot-removed -and communication should be avoided. - -Note that non-SSAM client devices can generally not be hot-removed, so -also add a convenience wrapper for those, defaulting to allow -communication. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-4-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../driver-api/surface_aggregator/client.rst | 6 +- - .../platform/surface/aggregator/controller.c | 53 ++++++++++----- - include/linux/surface_aggregator/controller.h | 24 ++++++- - include/linux/surface_aggregator/device.h | 66 +++++++++++++++++++ - 4 files changed, 128 insertions(+), 21 deletions(-) - -diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst -index e519d374c378..27f95abdbe99 100644 ---- a/Documentation/driver-api/surface_aggregator/client.rst -+++ b/Documentation/driver-api/surface_aggregator/client.rst -@@ -17,6 +17,8 @@ - .. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` - .. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` - .. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` -+.. |ssam_device_notifier_register| replace:: :c:func:`ssam_device_notifier_register` -+.. |ssam_device_notifier_unregister| replace:: :c:func:`ssam_device_notifier_unregister` - .. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` - .. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` - -@@ -312,7 +314,9 @@ Handling Events - To receive events from the SAM EC, an event notifier must be registered for - the desired event via |ssam_notifier_register|. The notifier must be - unregistered via |ssam_notifier_unregister| once it is not required any --more. -+more. For |ssam_device| type clients, the |ssam_device_notifier_register| and -+|ssam_device_notifier_unregister| wrappers should be preferred as they properly -+handle hot-removal of client devices. - - Event notifiers are registered by providing (at minimum) a callback to call - in case an event has been received, the registry specifying how the event -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index b8c377b3f932..6de834b52b63 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2199,16 +2199,26 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, - } - - /** -- * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is -- * no longer in use and free the corresponding entry. -+ * ssam_nf_refcount_disable_free() - Disable event for reference count entry if -+ * it is no longer in use and free the corresponding entry. - * @ctrl: The controller to disable the event on. - * @entry: The reference count entry for the event to be disabled. - * @flags: The flags used for enabling the event on the EC. -+ * @ec: Flag specifying if the event should actually be disabled on the EC. - * -- * If the reference count equals zero, i.e. the event is no longer requested by -- * any client, the event will be disabled and the corresponding reference count -- * entry freed. The reference count entry must not be used any more after a -- * call to this function. -+ * If ``ec`` equals ``true`` and the reference count equals zero (i.e. the -+ * event is no longer requested by any client), the specified event will be -+ * disabled on the EC via the corresponding request. -+ * -+ * If ``ec`` equals ``false``, no request will be sent to the EC and the event -+ * can be considered in a detached state (i.e. no longer used but still -+ * enabled). Disabling an event via this method may be required for -+ * hot-removable devices, where event disable requests may time out after the -+ * device has been physically removed. -+ * -+ * In both cases, if the reference count equals zero, the corresponding -+ * reference count entry will be freed. The reference count entry must not be -+ * used any more after a call to this function. - * - * Also checks if the flags used for disabling the event match the flags used - * for enabling the event and warns if they do not (regardless of reference -@@ -2223,7 +2233,7 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, - * returns the status of the event-enable EC command. - */ - static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, -- struct ssam_nf_refcount_entry *entry, u8 flags) -+ struct ssam_nf_refcount_entry *entry, u8 flags, bool ec) - { - const struct ssam_event_registry reg = entry->key.reg; - const struct ssam_event_id id = entry->key.id; -@@ -2232,8 +2242,9 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - - lockdep_assert_held(&nf->lock); - -- ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -- reg.target_category, id.target_category, id.instance, entry->refcount); -+ ssam_dbg(ctrl, "%s event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", -+ ec ? "disabling" : "detaching", reg.target_category, id.target_category, -+ id.instance, entry->refcount); - - if (entry->flags != flags) { - ssam_warn(ctrl, -@@ -2242,7 +2253,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, - id.instance); - } - -- if (entry->refcount == 0) { -+ if (ec && entry->refcount == 0) { - status = ssam_ssh_event_disable(ctrl, reg, id, flags); - kfree(entry); - } -@@ -2322,20 +2333,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif - EXPORT_SYMBOL_GPL(ssam_notifier_register); - - /** -- * ssam_notifier_unregister() - Unregister an event notifier. -- * @ctrl: The controller the notifier has been registered on. -- * @n: The event notifier to unregister. -+ * __ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * @disable: Whether to disable the corresponding event on the EC. - * - * Unregister an event notifier. Decrement the usage counter of the associated - * SAM event if the notifier is not marked as an observer. If the usage counter -- * reaches zero, the event will be disabled. -+ * reaches zero and ``disable`` equals ``true``, the event will be disabled. -+ * -+ * Useful for hot-removable devices, where communication may fail once the -+ * device has been physically removed. In that case, specifying ``disable`` as -+ * ``false`` avoids communication with the EC. - * - * Return: Returns zero on success, %-ENOENT if the given notifier block has - * not been registered on the controller. If the given notifier block was the - * last one associated with its specific event, returns the status of the - * event-disable EC-command. - */ --int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) -+int __ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n, -+ bool disable) - { - u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); - struct ssam_nf_refcount_entry *entry; -@@ -2373,7 +2390,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - goto remove; - } - -- status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); -+ status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags, disable); - } - - remove: -@@ -2383,7 +2400,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not - - return status; - } --EXPORT_SYMBOL_GPL(ssam_notifier_unregister); -+EXPORT_SYMBOL_GPL(__ssam_notifier_unregister); - - /** - * ssam_controller_event_enable() - Enable the specified event. -@@ -2477,7 +2494,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, - return -ENOENT; - } - -- status = ssam_nf_refcount_disable_free(ctrl, entry, flags); -+ status = ssam_nf_refcount_disable_free(ctrl, entry, flags, true); - - mutex_unlock(&nf->lock); - return status; -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index 74bfdffaf7b0..50a2b4926c06 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -835,8 +835,28 @@ struct ssam_event_notifier { - int ssam_notifier_register(struct ssam_controller *ctrl, - struct ssam_event_notifier *n); - --int ssam_notifier_unregister(struct ssam_controller *ctrl, -- struct ssam_event_notifier *n); -+int __ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n, bool disable); -+ -+/** -+ * ssam_notifier_unregister() - Unregister an event notifier. -+ * @ctrl: The controller the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+static inline int ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n) -+{ -+ return __ssam_notifier_unregister(ctrl, n, true); -+} - - int ssam_controller_event_enable(struct ssam_controller *ctrl, - struct ssam_event_registry reg, -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 6df7c8d4e50e..c418f7f2732d 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -483,4 +483,70 @@ static inline void ssam_remove_clients(struct device *dev) {} - sdev->uid.instance, ret); \ - } - -+ -+/* -- Helpers for client-device notifiers. ---------------------------------- */ -+ -+/** -+ * ssam_device_notifier_register() - Register an event notifier for the -+ * specified client device. -+ * @sdev: The device the notifier should be registered on. -+ * @n: The event notifier to register. -+ * -+ * Register an event notifier. Increment the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the event is not -+ * marked as an observer and is currently not enabled, it will be enabled -+ * during this call. If the notifier is marked as an observer, no attempt will -+ * be made at enabling any event and no reference count will be modified. -+ * -+ * Notifiers marked as observers do not need to be associated with one specific -+ * event, i.e. as long as no event matching is performed, only the event target -+ * category needs to be set. -+ * -+ * Return: Returns zero on success, %-ENOSPC if there have already been -+ * %INT_MAX notifiers for the event ID/type associated with the notifier block -+ * registered, %-ENOMEM if the corresponding event entry could not be -+ * allocated, %-ENODEV if the device is marked as hot-removed. If this is the -+ * first time that a notifier block is registered for the specific associated -+ * event, returns the status of the event-enable EC-command. -+ */ -+static inline int ssam_device_notifier_register(struct ssam_device *sdev, -+ struct ssam_event_notifier *n) -+{ -+ /* -+ * Note that this check does not provide any guarantees whatsoever as -+ * hot-removal could happen at any point and we can't protect against -+ * it. Nevertheless, if we can detect hot-removal, bail early to avoid -+ * communication timeouts. -+ */ -+ if (ssam_device_is_hot_removed(sdev)) -+ return -ENODEV; -+ -+ return ssam_notifier_register(sdev->ctrl, n); -+} -+ -+/** -+ * ssam_device_notifier_unregister() - Unregister an event notifier for the -+ * specified client device. -+ * @sdev: The device the notifier has been registered on. -+ * @n: The event notifier to unregister. -+ * -+ * Unregister an event notifier. Decrement the usage counter of the associated -+ * SAM event if the notifier is not marked as an observer. If the usage counter -+ * reaches zero, the event will be disabled. -+ * -+ * In case the device has been marked as hot-removed, the event will not be -+ * disabled on the EC, as in those cases any attempt at doing so may time out. -+ * -+ * Return: Returns zero on success, %-ENOENT if the given notifier block has -+ * not been registered on the controller. If the given notifier block was the -+ * last one associated with its specific event, returns the status of the -+ * event-disable EC-command. -+ */ -+static inline int ssam_device_notifier_unregister(struct ssam_device *sdev, -+ struct ssam_event_notifier *n) -+{ -+ return __ssam_notifier_unregister(sdev->ctrl, n, -+ !ssam_device_is_hot_removed(sdev)); -+} -+ - #endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ --- -2.38.0 - -From a9d3af89ff972413a10c89c65edcd91b535fb1d7 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:39 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Use client device - wrappers for notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-5-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 08019c6ccc9c..71fe014d3ebb 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -468,7 +468,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - - ssam_device_set_drvdata(sdev, hub); - -- status = ssam_notifier_register(sdev->ctrl, &hub->notif); -+ status = ssam_device_notifier_register(sdev, &hub->notif); - if (status) - return status; - -@@ -480,7 +480,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - return 0; - - err: -- ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_device_notifier_unregister(sdev, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); - return status; -@@ -492,7 +492,7 @@ static void ssam_base_hub_remove(struct ssam_device *sdev) - - sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); - -- ssam_notifier_unregister(sdev->ctrl, &hub->notif); -+ ssam_device_notifier_unregister(sdev, &hub->notif); - cancel_delayed_work_sync(&hub->update_work); - ssam_remove_clients(&sdev->dev); - } --- -2.38.0 - -From 46ecddab59b37900e0545f8f0711a11b970131d0 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:40 +0200 -Subject: [PATCH] power/supply: surface_charger: Use client device wrappers for - notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Acked-by: Sebastian Reichel -Link: https://lore.kernel.org/r/20220527023447.2460025-6-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/power/supply/surface_charger.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c -index a060c36c7766..59182d55742d 100644 ---- a/drivers/power/supply/surface_charger.c -+++ b/drivers/power/supply/surface_charger.c -@@ -216,7 +216,7 @@ static int spwr_ac_register(struct spwr_ac_device *ac) - if (IS_ERR(ac->psy)) - return PTR_ERR(ac->psy); - -- return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); -+ return ssam_device_notifier_register(ac->sdev, &ac->notif); - } - - -@@ -251,7 +251,7 @@ static void surface_ac_remove(struct ssam_device *sdev) - { - struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); - -- ssam_notifier_unregister(sdev->ctrl, &ac->notif); -+ ssam_device_notifier_unregister(sdev, &ac->notif); - } - - static const struct spwr_psy_properties spwr_psy_props_adp1 = { --- -2.38.0 - -From 662fdfd7d34e79f77ecb1ed5aafd641bacfff247 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:41 +0200 -Subject: [PATCH] power/supply: surface_battery: Use client device wrappers for - notifier registration - -Use newly introduced client device wrapper functions for notifier -registration and unregistration. - -Signed-off-by: Maximilian Luz -Acked-by: Sebastian Reichel -Link: https://lore.kernel.org/r/20220527023447.2460025-7-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/power/supply/surface_battery.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c -index 5ec2e6bb2465..540707882bb0 100644 ---- a/drivers/power/supply/surface_battery.c -+++ b/drivers/power/supply/surface_battery.c -@@ -802,7 +802,7 @@ static int spwr_battery_register(struct spwr_battery_device *bat) - if (IS_ERR(bat->psy)) - return PTR_ERR(bat->psy); - -- return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); -+ return ssam_device_notifier_register(bat->sdev, &bat->notif); - } - - -@@ -837,7 +837,7 @@ static void surface_battery_remove(struct ssam_device *sdev) - { - struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); - -- ssam_notifier_unregister(sdev->ctrl, &bat->notif); -+ ssam_device_notifier_unregister(sdev, &bat->notif); - cancel_delayed_work_sync(&bat->update_work); - } - --- -2.38.0 - -From ea1bb20b711abe55433b21545818fb223478a60c Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:42 +0200 -Subject: [PATCH] HID: surface-hid: Add support for hot-removal - -Add support for hot-removal of SSAM HID client devices. - -Once a device has been hot-removed, further communication with it should -be avoided as it may fail and time out. While the device will be removed -as soon as we detect hot-removal, communication may still occur during -teardown, especially when unregistering notifiers. - -While hot-removal is a surprise event that can happen at any time, try -to avoid communication as much as possible once it has been detected to -prevent timeouts that can slow down device removal and cause issues, -e.g. when quickly re-attaching the device. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-8-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/hid/surface-hid/surface_hid_core.c | 38 +++++++++++++++++++++- - 1 file changed, 37 insertions(+), 1 deletion(-) - -diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c -index e46330b2e561..87637f813de2 100644 ---- a/drivers/hid/surface-hid/surface_hid_core.c -+++ b/drivers/hid/surface-hid/surface_hid_core.c -@@ -19,12 +19,30 @@ - #include "surface_hid_core.h" - - -+/* -- Utility functions. ---------------------------------------------------- */ -+ -+static bool surface_hid_is_hot_removed(struct surface_hid_device *shid) -+{ -+ /* -+ * Non-ssam client devices, i.e. platform client devices, cannot be -+ * hot-removed. -+ */ -+ if (!is_ssam_device(shid->dev)) -+ return false; -+ -+ return ssam_device_is_hot_removed(to_ssam_device(shid->dev)); -+} -+ -+ - /* -- Device descriptor access. --------------------------------------------- */ - - static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) - { - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, - (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); - if (status) -@@ -61,6 +79,9 @@ static int surface_hid_load_device_attributes(struct surface_hid_device *shid) - { - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, - (u8 *)&shid->attrs, sizeof(shid->attrs)); - if (status) -@@ -88,9 +109,18 @@ static int surface_hid_start(struct hid_device *hid) - static void surface_hid_stop(struct hid_device *hid) - { - struct surface_hid_device *shid = hid->driver_data; -+ bool hot_removed; -+ -+ /* -+ * Communication may fail for devices that have been hot-removed. This -+ * also includes unregistration of HID events, so we need to check this -+ * here. Only if the device has not been marked as hot-removed, we can -+ * safely disable events. -+ */ -+ hot_removed = surface_hid_is_hot_removed(shid); - - /* Note: This call will log errors for us, so ignore them here. */ -- ssam_notifier_unregister(shid->ctrl, &shid->notif); -+ __ssam_notifier_unregister(shid->ctrl, &shid->notif, !hot_removed); - } - - static int surface_hid_open(struct hid_device *hid) -@@ -109,6 +139,9 @@ static int surface_hid_parse(struct hid_device *hid) - u8 *buf; - int status; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - buf = kzalloc(len, GFP_KERNEL); - if (!buf) - return -ENOMEM; -@@ -126,6 +159,9 @@ static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportn - { - struct surface_hid_device *shid = hid->driver_data; - -+ if (surface_hid_is_hot_removed(shid)) -+ return -ENODEV; -+ - if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) - return shid->ops.output_report(shid, reportnum, buf, len); - --- -2.38.0 - -From 90be63a3c352190928b0ff8b925cbd37915f43d2 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:43 +0200 -Subject: [PATCH] platform/surface: aggregator: Add comment for KIP subsystem - category - -The KIP subsystem (full name unknown, abbreviation has been obtained -through reverse engineering) handles detachable peripherals such as the -keyboard cover on the Surface Pro X and Surface Pro 8. - -It is currently not entirely clear what this subsystem entails, but at -the very least it provides event notifications for when the keyboard -cover on the Surface Pro X and Surface Pro 8 have been detached or -re-attached, as well as the state that the keyboard cover is currently -in (e.g. folded-back, folded laptop-like, closed, etc.). - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-9-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - include/linux/surface_aggregator/serial_hub.h | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h -index c3de43edcffa..26b95ec12733 100644 ---- a/include/linux/surface_aggregator/serial_hub.h -+++ b/include/linux/surface_aggregator/serial_hub.h -@@ -306,7 +306,7 @@ enum ssam_ssh_tc { - SSAM_SSH_TC_LPC = 0x0b, - SSAM_SSH_TC_TCL = 0x0c, - SSAM_SSH_TC_SFL = 0x0d, -- SSAM_SSH_TC_KIP = 0x0e, -+ SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ - SSAM_SSH_TC_EXT = 0x0f, - SSAM_SSH_TC_BLD = 0x10, - SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ --- -2.38.0 - -From dbd5817f3a5903f01d31fc5a915503951d4f7414 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:44 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Generify subsystem hub - functionality - -The Surface System Aggregator Module (SSAM) has multiple subsystems that -can manage detachable devices. At the moment, we only support the "base" -(BAS/0x11) subsystem, which is used on the Surface Book 3 to manage -devices (including keyboard, touchpad, and secondary battery) connected -to the base of the device. - -The Surface Pro 8 has a new type-cover with keyboard and touchpad, which -is managed via the KIP/0x0e subsystem. The general procedure is the -same, but with slightly different events and setup. To make -implementation of the KIP hub easier and prevent duplication, generify -the parts of the base hub that we can use for the KIP hub (or any -potential future subsystem hubs). - -This also switches over to use the newly introduced "hot-remove" -functionality, which should prevent communication issues when devices -have been detached. - -Lastly, also drop the undocumented and unused sysfs "state" attribute of -the base hub. It has at best been useful for debugging. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-10-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 269 ++++++++++-------- - 1 file changed, 153 insertions(+), 116 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 71fe014d3ebb..0b1ca1155770 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -308,30 +308,159 @@ static int ssam_hub_register_clients(struct device *parent, struct ssam_controll - } - - --/* -- SSAM base-hub driver. ------------------------------------------------- */ -+/* -- SSAM generic subsystem hub driver framework. -------------------------- */ - --/* -- * Some devices (especially battery) may need a bit of time to be fully usable -- * after being (re-)connected. This delay has been determined via -- * experimentation. -- */ --#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) -+enum ssam_hub_state { -+ SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ -+ SSAM_HUB_CONNECTED, -+ SSAM_HUB_DISCONNECTED, -+}; - --enum ssam_base_hub_state { -- SSAM_BASE_HUB_UNINITIALIZED, -- SSAM_BASE_HUB_CONNECTED, -- SSAM_BASE_HUB_DISCONNECTED, -+enum ssam_hub_flags { -+ SSAM_HUB_HOT_REMOVED, - }; - --struct ssam_base_hub { -+struct ssam_hub { - struct ssam_device *sdev; - -- enum ssam_base_hub_state state; -+ enum ssam_hub_state state; -+ unsigned long flags; -+ - struct delayed_work update_work; -+ unsigned long connect_delay; - - struct ssam_event_notifier notif; -+ -+ int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); - }; - -+static void ssam_hub_update_workfn(struct work_struct *work) -+{ -+ struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); -+ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -+ enum ssam_hub_state state; -+ int status = 0; -+ -+ status = hub->get_state(hub, &state); -+ if (status) -+ return; -+ -+ /* -+ * There is a small possibility that hub devices were hot-removed and -+ * re-added before we were able to remove them here. In that case, both -+ * the state returned by get_state() and the state of the hub will -+ * equal SSAM_HUB_CONNECTED and we would bail early below, which would -+ * leave child devices without proper (re-)initialization and the -+ * hot-remove flag set. -+ * -+ * Therefore, we check whether devices have been hot-removed via an -+ * additional flag on the hub and, in this case, override the returned -+ * hub state. In case of a missed disconnect (i.e. get_state returned -+ * "connected"), we further need to re-schedule this work (with the -+ * appropriate delay) as the actual connect work submission might have -+ * been merged with this one. -+ * -+ * This then leads to one of two cases: Either we submit an unnecessary -+ * work item (which will get ignored via either the queue or the state -+ * checks) or, in the unlikely case that the work is actually required, -+ * double the normal connect delay. -+ */ -+ if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { -+ if (state == SSAM_HUB_CONNECTED) -+ schedule_delayed_work(&hub->update_work, hub->connect_delay); -+ -+ state = SSAM_HUB_DISCONNECTED; -+ } -+ -+ if (hub->state == state) -+ return; -+ hub->state = state; -+ -+ if (hub->state == SSAM_HUB_CONNECTED) -+ status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); -+ else -+ ssam_remove_clients(&hub->sdev->dev); -+ -+ if (status) -+ dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); -+} -+ -+static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (is_ssam_device(dev)) -+ ssam_device_mark_hot_removed(sdev); -+ -+ return 0; -+} -+ -+static void ssam_hub_update(struct ssam_hub *hub, bool connected) -+{ -+ unsigned long delay; -+ -+ /* Mark devices as hot-removed before we remove any. */ -+ if (!connected) { -+ set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); -+ device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); -+ } -+ -+ /* -+ * Delay update when the base/keyboard cover is being connected to give -+ * devices/EC some time to set up. -+ */ -+ delay = connected ? hub->connect_delay : 0; -+ -+ schedule_delayed_work(&hub->update_work, delay); -+} -+ -+static int __maybe_unused ssam_hub_resume(struct device *dev) -+{ -+ struct ssam_hub *hub = dev_get_drvdata(dev); -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); -+ -+static int ssam_hub_setup(struct ssam_device *sdev, struct ssam_hub *hub) -+{ -+ int status; -+ -+ hub->sdev = sdev; -+ hub->state = SSAM_HUB_UNINITIALIZED; -+ -+ INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, hub); -+ -+ status = ssam_device_notifier_register(sdev, &hub->notif); -+ if (status) -+ return status; -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+ -+static void ssam_hub_remove(struct ssam_device *sdev) -+{ -+ struct ssam_hub *hub = ssam_device_get_drvdata(sdev); -+ -+ ssam_device_notifier_unregister(sdev, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); -+ ssam_remove_clients(&sdev->dev); -+} -+ -+ -+/* -- SSAM base-hub driver. ------------------------------------------------- */ -+ -+/* -+ * Some devices (especially battery) may need a bit of time to be fully usable -+ * after being (re-)connected. This delay has been determined via -+ * experimentation. -+ */ -+#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) -+ - SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { - .target_category = SSAM_SSH_TC_BAS, - .target_id = 0x01, -@@ -342,7 +471,7 @@ SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { - #define SSAM_BAS_OPMODE_TABLET 0x00 - #define SSAM_EVENT_BAS_CID_CONNECTION 0x0c - --static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state) -+static int ssam_base_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) - { - u8 opmode; - int status; -@@ -354,62 +483,16 @@ static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_h - } - - if (opmode != SSAM_BAS_OPMODE_TABLET) -- *state = SSAM_BASE_HUB_CONNECTED; -+ *state = SSAM_HUB_CONNECTED; - else -- *state = SSAM_BASE_HUB_DISCONNECTED; -+ *state = SSAM_HUB_DISCONNECTED; - - return 0; - } - --static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr, -- char *buf) --{ -- struct ssam_base_hub *hub = dev_get_drvdata(dev); -- bool connected = hub->state == SSAM_BASE_HUB_CONNECTED; -- -- return sysfs_emit(buf, "%d\n", connected); --} -- --static struct device_attribute ssam_base_hub_attr_state = -- __ATTR(state, 0444, ssam_base_hub_state_show, NULL); -- --static struct attribute *ssam_base_hub_attrs[] = { -- &ssam_base_hub_attr_state.attr, -- NULL, --}; -- --static const struct attribute_group ssam_base_hub_group = { -- .attrs = ssam_base_hub_attrs, --}; -- --static void ssam_base_hub_update_workfn(struct work_struct *work) --{ -- struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work); -- struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); -- enum ssam_base_hub_state state; -- int status = 0; -- -- status = ssam_base_hub_query_state(hub, &state); -- if (status) -- return; -- -- if (hub->state == state) -- return; -- hub->state = state; -- -- if (hub->state == SSAM_BASE_HUB_CONNECTED) -- status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); -- else -- ssam_remove_clients(&hub->sdev->dev); -- -- if (status) -- dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); --} -- - static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) - { -- struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif); -- unsigned long delay; -+ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); - - if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) - return 0; -@@ -419,13 +502,7 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam - return 0; - } - -- /* -- * Delay update when the base is being connected to give devices/EC -- * some time to set up. -- */ -- delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0; -- -- schedule_delayed_work(&hub->update_work, delay); -+ ssam_hub_update(hub, event->data[0]); - - /* - * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -@@ -435,27 +512,14 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam - return 0; - } - --static int __maybe_unused ssam_base_hub_resume(struct device *dev) --{ -- struct ssam_base_hub *hub = dev_get_drvdata(dev); -- -- schedule_delayed_work(&hub->update_work, 0); -- return 0; --} --static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume); -- - static int ssam_base_hub_probe(struct ssam_device *sdev) - { -- struct ssam_base_hub *hub; -- int status; -+ struct ssam_hub *hub; - - hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); - if (!hub) - return -ENOMEM; - -- hub->sdev = sdev; -- hub->state = SSAM_BASE_HUB_UNINITIALIZED; -- - hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ - hub->notif.base.fn = ssam_base_hub_notif; - hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -@@ -464,37 +528,10 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - hub->notif.event.mask = SSAM_EVENT_MASK_NONE; - hub->notif.event.flags = SSAM_EVENT_SEQUENCED; - -- INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn); -- -- ssam_device_set_drvdata(sdev, hub); -- -- status = ssam_device_notifier_register(sdev, &hub->notif); -- if (status) -- return status; -- -- status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); -- if (status) -- goto err; -- -- schedule_delayed_work(&hub->update_work, 0); -- return 0; -+ hub->connect_delay = SSAM_BASE_UPDATE_CONNECT_DELAY; -+ hub->get_state = ssam_base_hub_query_state; - --err: -- ssam_device_notifier_unregister(sdev, &hub->notif); -- cancel_delayed_work_sync(&hub->update_work); -- ssam_remove_clients(&sdev->dev); -- return status; --} -- --static void ssam_base_hub_remove(struct ssam_device *sdev) --{ -- struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); -- -- sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); -- -- ssam_device_notifier_unregister(sdev, &hub->notif); -- cancel_delayed_work_sync(&hub->update_work); -- ssam_remove_clients(&sdev->dev); -+ return ssam_hub_setup(sdev, hub); - } - - static const struct ssam_device_id ssam_base_hub_match[] = { -@@ -504,12 +541,12 @@ static const struct ssam_device_id ssam_base_hub_match[] = { - - static struct ssam_device_driver ssam_base_hub_driver = { - .probe = ssam_base_hub_probe, -- .remove = ssam_base_hub_remove, -+ .remove = ssam_hub_remove, - .match_table = ssam_base_hub_match, - .driver = { - .name = "surface_aggregator_base_hub", - .probe_type = PROBE_PREFER_ASYNCHRONOUS, -- .pm = &ssam_base_hub_pm_ops, -+ .pm = &ssam_hub_pm_ops, - }, - }; - --- -2.38.0 - -From ff7155e28075f87637e5307648210431e15117a1 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:45 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Change device ID for - base hub - -Use the target category of the (base) hub as instance id in the -(virtual) hub device UID. This makes association of the hub with the -respective subsystem easier. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-11-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 0b1ca1155770..d0e9e287c7e5 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -43,7 +43,7 @@ static const struct software_node ssam_node_root = { - - /* Base device hub (devices attached to Surface Book 3 base). */ - static const struct software_node ssam_node_hub_base = { -- .name = "ssam:00:00:02:00:00", -+ .name = "ssam:00:00:02:11:00", - .parent = &ssam_node_root, - }; - -@@ -535,7 +535,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) - } - - static const struct ssam_device_id ssam_base_hub_match[] = { -- { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) }, -+ { SSAM_VDEV(HUB, 0x02, SSAM_SSH_TC_BAS, 0x00) }, - { }, - }; - --- -2.38.0 - -From 85e6474b0c5db06213f32f02a2263b6a45722cf1 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:46 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add KIP device hub - -Add a Surface System Aggregator Module (SSAM) client device hub for -hot-removable devices managed via the KIP subsystem. - -The KIP subsystem (full name unknown, abbreviation has been obtained -through reverse engineering) is a subsystem that manages hot-removable -SSAM client devices. Specifically, it manages HID input devices -contained in the detachable keyboard cover of the Surface Pro 8 and -Surface Pro X. - -The KIP subsystem handles a single group of devices (e.g. all devices -contained in the keyboard cover) and cannot handle devices individually. -Thus we model it as a client device hub, which (hot-)removes all devices -contained under it once removal of the hub (e.g. keyboard cover) has -been detected and (re-)adds all devices once the physical hub device has -been (re-)attached. To do this, use the previously generified SSAM -subsystem hub framework. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-12-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 103 +++++++++++++++++- - 1 file changed, 101 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index d0e9e287c7e5..0b4aeba4ad93 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -551,6 +551,93 @@ static struct ssam_device_driver ssam_base_hub_driver = { - }; - - -+/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ -+ -+/* -+ * Some devices may need a bit of time to be fully usable after being -+ * (re-)connected. This delay has been determined via experimentation. -+ */ -+#define SSAM_KIP_UPDATE_CONNECT_DELAY msecs_to_jiffies(250) -+ -+#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_connection_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x2c, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_get_connection_state(struct ssam_hub *hub, enum ssam_hub_state *state) -+{ -+ int status; -+ u8 connected; -+ -+ status = ssam_retry(__ssam_kip_get_connection_state, hub->sdev->ctrl, &connected); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); -+ return status; -+ } -+ -+ *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; -+ return 0; -+} -+ -+static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) { -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ ssam_hub_update(hub, event->data[0]); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static int ssam_kip_hub_probe(struct ssam_device *sdev) -+{ -+ struct ssam_hub *hub; -+ -+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -+ if (!hub) -+ return -ENOMEM; -+ -+ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -+ hub->notif.base.fn = ssam_kip_hub_notif; -+ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ hub->notif.event.id.target_category = SSAM_SSH_TC_KIP, -+ hub->notif.event.id.instance = 0, -+ hub->notif.event.mask = SSAM_EVENT_MASK_TARGET; -+ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ hub->connect_delay = SSAM_KIP_UPDATE_CONNECT_DELAY; -+ hub->get_state = ssam_kip_get_connection_state; -+ -+ return ssam_hub_setup(sdev, hub); -+} -+ -+static const struct ssam_device_id ssam_kip_hub_match[] = { -+ { SSAM_VDEV(HUB, 0x01, SSAM_SSH_TC_KIP, 0x00) }, -+ { }, -+}; -+ -+static struct ssam_device_driver ssam_kip_hub_driver = { -+ .probe = ssam_kip_hub_probe, -+ .remove = ssam_hub_remove, -+ .match_table = ssam_kip_hub_match, -+ .driver = { -+ .name = "surface_kip_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_hub_pm_ops, -+ }, -+}; -+ -+ - /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ - - static const struct acpi_device_id ssam_platform_hub_match[] = { -@@ -676,18 +763,30 @@ static int __init ssam_device_hub_init(void) - - status = platform_driver_register(&ssam_platform_hub_driver); - if (status) -- return status; -+ goto err_platform; - - status = ssam_device_driver_register(&ssam_base_hub_driver); - if (status) -- platform_driver_unregister(&ssam_platform_hub_driver); -+ goto err_base; -+ -+ status = ssam_device_driver_register(&ssam_kip_hub_driver); -+ if (status) -+ goto err_kip; - -+ return 0; -+ -+err_kip: -+ ssam_device_driver_unregister(&ssam_base_hub_driver); -+err_base: -+ platform_driver_unregister(&ssam_platform_hub_driver); -+err_platform: - return status; - } - module_init(ssam_device_hub_init); - - static void __exit ssam_device_hub_exit(void) - { -+ ssam_device_driver_unregister(&ssam_kip_hub_driver); - ssam_device_driver_unregister(&ssam_base_hub_driver); - platform_driver_unregister(&ssam_platform_hub_driver); - } --- -2.38.0 - -From 7c19b3247b78c061347415177cb6a85cc263cf41 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 27 May 2022 04:34:47 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for - keyboard cover on Surface Pro 8 - -Add support for the detachable keyboard cover on the Surface Pro 8. - -The keyboard cover on the Surface Pro 8 is, unlike the keyboard covers -of earlier Surface Pro generations, handled via the Surface System -Aggregator Module (SSAM). The keyboard and touchpad (as well as other -HID input devices) of this cover are standard SSAM HID client devices -(just like keyboard and touchpad on e.g. the Surface Laptop 3 and 4), -however, some care needs to be taken as they can be physically detached -(similarly to the Surface Book 3). Specifically, the respective SSAM -client devices need to be removed when the keyboard cover has been -detached and (re-)initialized when the keyboard cover has been -(re-)attached. - -On the Surface Pro 8, detachment of the keyboard cover (and by extension -its devices) is managed via the KIP subsystem. Therefore, said devices -need to be registered under the KIP device hub, which in turn will -remove and re-create/re-initialize those devices as needed. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220527023447.2460025-13-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 37 ++++++++++++++++++- - 1 file changed, 36 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 0b4aeba4ad93..3b1310d61a24 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -41,6 +41,12 @@ static const struct software_node ssam_node_root = { - .name = "ssam_platform_hub", - }; - -+/* KIP device hub (connects keyboard cover devices on Surface Pro 8). */ -+static const struct software_node ssam_node_hub_kip = { -+ .name = "ssam:00:00:01:0e:00", -+ .parent = &ssam_node_root, -+}; -+ - /* Base device hub (devices attached to Surface Book 3 base). */ - static const struct software_node ssam_node_hub_base = { - .name = "ssam:00:00:02:11:00", -@@ -155,6 +161,30 @@ static const struct software_node ssam_node_hid_base_iid6 = { - .parent = &ssam_node_hub_base, - }; - -+/* HID keyboard (KIP hub). */ -+static const struct software_node ssam_node_hid_kip_keyboard = { -+ .name = "ssam:01:15:02:01:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID pen stash (KIP hub; pen taken / stashed away evens). */ -+static const struct software_node ssam_node_hid_kip_penstash = { -+ .name = "ssam:01:15:02:02:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID touchpad (KIP hub). */ -+static const struct software_node ssam_node_hid_kip_touchpad = { -+ .name = "ssam:01:15:02:03:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ -+/* HID device instance 5 (KIP hub, unknown HID device). */ -+static const struct software_node ssam_node_hid_kip_iid5 = { -+ .name = "ssam:01:15:02:05:00", -+ .parent = &ssam_node_hub_kip, -+}; -+ - /* - * Devices for 5th- and 6th-generations models: - * - Surface Book 2, -@@ -230,10 +260,15 @@ static const struct software_node *ssam_node_group_sp7[] = { - - static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_root, -+ &ssam_node_hub_kip, - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -- /* TODO: Add support for keyboard cover. */ -+ &ssam_node_hid_kip_keyboard, -+ &ssam_node_hid_kip_penstash, -+ &ssam_node_hid_kip_touchpad, -+ &ssam_node_hid_kip_iid5, -+ /* TODO: Add support for tablet mode switch. */ - NULL, - }; - --- -2.38.0 - -From 27cd714077ea9932cc4f85f4d7c7a568dd25ac63 Mon Sep 17 00:00:00 2001 -From: Tetsuo Handa -Date: Fri, 10 Jun 2022 14:41:58 +0900 -Subject: [PATCH] platform/surface: avoid flush_scheduled_work() usage - -Use local wq in order to avoid flush_scheduled_work() usage. - -Signed-off-by: Tetsuo Handa -Reviewed-by: Maximilian Luz -Tested-by: Maximilian Luz -Link: https://lore.kernel.org/r/63ec2d45-c67c-1134-f6d3-490c8ba67a01@I-love.SAKURA.ne.jp -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../platform/surface/surface_acpi_notify.c | 27 ++++++++++++++++--- - 1 file changed, 24 insertions(+), 3 deletions(-) - -diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c -index 7b758f8cc137..c0e12f0b9b79 100644 ---- a/drivers/platform/surface/surface_acpi_notify.c -+++ b/drivers/platform/surface/surface_acpi_notify.c -@@ -37,6 +37,7 @@ struct san_data { - #define to_san_data(ptr, member) \ - container_of(ptr, struct san_data, member) - -+static struct workqueue_struct *san_wq; - - /* -- dGPU notifier interface. ---------------------------------------------- */ - -@@ -356,7 +357,7 @@ static u32 san_evt_bat_nf(struct ssam_event_notifier *nf, - - memcpy(&work->event, event, sizeof(struct ssam_event) + event->length); - -- schedule_delayed_work(&work->work, delay); -+ queue_delayed_work(san_wq, &work->work, delay); - return SSAM_NOTIF_HANDLED; - } - -@@ -861,7 +862,7 @@ static int san_remove(struct platform_device *pdev) - * We have unregistered our event sources. Now we need to ensure that - * all delayed works they may have spawned are run to completion. - */ -- flush_scheduled_work(); -+ flush_workqueue(san_wq); - - return 0; - } -@@ -881,7 +882,27 @@ static struct platform_driver surface_acpi_notify = { - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - }, - }; --module_platform_driver(surface_acpi_notify); -+ -+static int __init san_init(void) -+{ -+ int ret; -+ -+ san_wq = alloc_workqueue("san_wq", 0, 0); -+ if (!san_wq) -+ return -ENOMEM; -+ ret = platform_driver_register(&surface_acpi_notify); -+ if (ret) -+ destroy_workqueue(san_wq); -+ return ret; -+} -+module_init(san_init); -+ -+static void __exit san_exit(void) -+{ -+ platform_driver_unregister(&surface_acpi_notify); -+ destroy_workqueue(san_wq); -+} -+module_exit(san_exit); - - MODULE_AUTHOR("Maximilian Luz "); - MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module"); --- -2.38.0 - -From c8c113119b4e9c34597158ba3118289d5619a172 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Tue, 14 Jun 2022 21:41:17 +0200 -Subject: [PATCH] platform/surface: aggregator: Reserve more event- and - target-categories - -With the introduction of the Surface Laptop Studio, more event- and -target categories have been added. Therefore, increase the number of -reserved events and extend the enum of know target categories to -accommodate this. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220614194117.4118897-1-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/trace.h | 80 +++++++++++-------- - include/linux/surface_aggregator/serial_hub.h | 75 +++++++++-------- - 2 files changed, 85 insertions(+), 70 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h -index de64cf169060..cc9e73fbc18e 100644 ---- a/drivers/platform/surface/aggregator/trace.h -+++ b/drivers/platform/surface/aggregator/trace.h -@@ -76,7 +76,7 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM); --TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC0); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN); -@@ -85,6 +85,11 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD); - TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SPT); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SYS); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC1); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SHB); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_POS); - - #define SSAM_PTR_UID_LEN 9 - #define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1) -@@ -229,40 +234,45 @@ static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) - - #define ssam_show_ssh_tc(rqid) \ - __print_symbolic(rqid, \ -- { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ -- { SSAM_SSH_TC_SAM, "SAM" }, \ -- { SSAM_SSH_TC_BAT, "BAT" }, \ -- { SSAM_SSH_TC_TMP, "TMP" }, \ -- { SSAM_SSH_TC_PMC, "PMC" }, \ -- { SSAM_SSH_TC_FAN, "FAN" }, \ -- { SSAM_SSH_TC_PoM, "PoM" }, \ -- { SSAM_SSH_TC_DBG, "DBG" }, \ -- { SSAM_SSH_TC_KBD, "KBD" }, \ -- { SSAM_SSH_TC_FWU, "FWU" }, \ -- { SSAM_SSH_TC_UNI, "UNI" }, \ -- { SSAM_SSH_TC_LPC, "LPC" }, \ -- { SSAM_SSH_TC_TCL, "TCL" }, \ -- { SSAM_SSH_TC_SFL, "SFL" }, \ -- { SSAM_SSH_TC_KIP, "KIP" }, \ -- { SSAM_SSH_TC_EXT, "EXT" }, \ -- { SSAM_SSH_TC_BLD, "BLD" }, \ -- { SSAM_SSH_TC_BAS, "BAS" }, \ -- { SSAM_SSH_TC_SEN, "SEN" }, \ -- { SSAM_SSH_TC_SRQ, "SRQ" }, \ -- { SSAM_SSH_TC_MCU, "MCU" }, \ -- { SSAM_SSH_TC_HID, "HID" }, \ -- { SSAM_SSH_TC_TCH, "TCH" }, \ -- { SSAM_SSH_TC_BKL, "BKL" }, \ -- { SSAM_SSH_TC_TAM, "TAM" }, \ -- { SSAM_SSH_TC_ACC, "ACC" }, \ -- { SSAM_SSH_TC_UFI, "UFI" }, \ -- { SSAM_SSH_TC_USC, "USC" }, \ -- { SSAM_SSH_TC_PEN, "PEN" }, \ -- { SSAM_SSH_TC_VID, "VID" }, \ -- { SSAM_SSH_TC_AUD, "AUD" }, \ -- { SSAM_SSH_TC_SMC, "SMC" }, \ -- { SSAM_SSH_TC_KPD, "KPD" }, \ -- { SSAM_SSH_TC_REG, "REG" } \ -+ { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ -+ { SSAM_SSH_TC_SAM, "SAM" }, \ -+ { SSAM_SSH_TC_BAT, "BAT" }, \ -+ { SSAM_SSH_TC_TMP, "TMP" }, \ -+ { SSAM_SSH_TC_PMC, "PMC" }, \ -+ { SSAM_SSH_TC_FAN, "FAN" }, \ -+ { SSAM_SSH_TC_PoM, "PoM" }, \ -+ { SSAM_SSH_TC_DBG, "DBG" }, \ -+ { SSAM_SSH_TC_KBD, "KBD" }, \ -+ { SSAM_SSH_TC_FWU, "FWU" }, \ -+ { SSAM_SSH_TC_UNI, "UNI" }, \ -+ { SSAM_SSH_TC_LPC, "LPC" }, \ -+ { SSAM_SSH_TC_TCL, "TCL" }, \ -+ { SSAM_SSH_TC_SFL, "SFL" }, \ -+ { SSAM_SSH_TC_KIP, "KIP" }, \ -+ { SSAM_SSH_TC_EXT, "EXT" }, \ -+ { SSAM_SSH_TC_BLD, "BLD" }, \ -+ { SSAM_SSH_TC_BAS, "BAS" }, \ -+ { SSAM_SSH_TC_SEN, "SEN" }, \ -+ { SSAM_SSH_TC_SRQ, "SRQ" }, \ -+ { SSAM_SSH_TC_MCU, "MCU" }, \ -+ { SSAM_SSH_TC_HID, "HID" }, \ -+ { SSAM_SSH_TC_TCH, "TCH" }, \ -+ { SSAM_SSH_TC_BKL, "BKL" }, \ -+ { SSAM_SSH_TC_TAM, "TAM" }, \ -+ { SSAM_SSH_TC_ACC0, "ACC0" }, \ -+ { SSAM_SSH_TC_UFI, "UFI" }, \ -+ { SSAM_SSH_TC_USC, "USC" }, \ -+ { SSAM_SSH_TC_PEN, "PEN" }, \ -+ { SSAM_SSH_TC_VID, "VID" }, \ -+ { SSAM_SSH_TC_AUD, "AUD" }, \ -+ { SSAM_SSH_TC_SMC, "SMC" }, \ -+ { SSAM_SSH_TC_KPD, "KPD" }, \ -+ { SSAM_SSH_TC_REG, "REG" }, \ -+ { SSAM_SSH_TC_SPT, "SPT" }, \ -+ { SSAM_SSH_TC_SYS, "SYS" }, \ -+ { SSAM_SSH_TC_ACC1, "ACC1" }, \ -+ { SSAM_SSH_TC_SHB, "SMB" }, \ -+ { SSAM_SSH_TC_POS, "POS" } \ - ) - - DECLARE_EVENT_CLASS(ssam_frame_class, -diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h -index 26b95ec12733..45501b6e54e8 100644 ---- a/include/linux/surface_aggregator/serial_hub.h -+++ b/include/linux/surface_aggregator/serial_hub.h -@@ -201,7 +201,7 @@ static inline u16 ssh_crc(const u8 *buf, size_t len) - * exception of zero, which is not an event ID. Thus, this is also the - * absolute maximum number of event handlers that can be registered. - */ --#define SSH_NUM_EVENTS 34 -+#define SSH_NUM_EVENTS 38 - - /* - * SSH_NUM_TARGETS - The number of communication targets used in the protocol. -@@ -292,40 +292,45 @@ struct ssam_span { - * Windows driver. - */ - enum ssam_ssh_tc { -- /* Category 0x00 is invalid for EC use. */ -- SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */ -- SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */ -- SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */ -- SSAM_SSH_TC_PMC = 0x04, -- SSAM_SSH_TC_FAN = 0x05, -- SSAM_SSH_TC_PoM = 0x06, -- SSAM_SSH_TC_DBG = 0x07, -- SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */ -- SSAM_SSH_TC_FWU = 0x09, -- SSAM_SSH_TC_UNI = 0x0a, -- SSAM_SSH_TC_LPC = 0x0b, -- SSAM_SSH_TC_TCL = 0x0c, -- SSAM_SSH_TC_SFL = 0x0d, -- SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ -- SSAM_SSH_TC_EXT = 0x0f, -- SSAM_SSH_TC_BLD = 0x10, -- SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ -- SSAM_SSH_TC_SEN = 0x12, -- SSAM_SSH_TC_SRQ = 0x13, -- SSAM_SSH_TC_MCU = 0x14, -- SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */ -- SSAM_SSH_TC_TCH = 0x16, -- SSAM_SSH_TC_BKL = 0x17, -- SSAM_SSH_TC_TAM = 0x18, -- SSAM_SSH_TC_ACC = 0x19, -- SSAM_SSH_TC_UFI = 0x1a, -- SSAM_SSH_TC_USC = 0x1b, -- SSAM_SSH_TC_PEN = 0x1c, -- SSAM_SSH_TC_VID = 0x1d, -- SSAM_SSH_TC_AUD = 0x1e, -- SSAM_SSH_TC_SMC = 0x1f, -- SSAM_SSH_TC_KPD = 0x20, -- SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */ -+ /* Category 0x00 is invalid for EC use. */ -+ SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */ -+ SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */ -+ SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */ -+ SSAM_SSH_TC_PMC = 0x04, -+ SSAM_SSH_TC_FAN = 0x05, -+ SSAM_SSH_TC_PoM = 0x06, -+ SSAM_SSH_TC_DBG = 0x07, -+ SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */ -+ SSAM_SSH_TC_FWU = 0x09, -+ SSAM_SSH_TC_UNI = 0x0a, -+ SSAM_SSH_TC_LPC = 0x0b, -+ SSAM_SSH_TC_TCL = 0x0c, -+ SSAM_SSH_TC_SFL = 0x0d, -+ SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ -+ SSAM_SSH_TC_EXT = 0x0f, -+ SSAM_SSH_TC_BLD = 0x10, -+ SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ -+ SSAM_SSH_TC_SEN = 0x12, -+ SSAM_SSH_TC_SRQ = 0x13, -+ SSAM_SSH_TC_MCU = 0x14, -+ SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */ -+ SSAM_SSH_TC_TCH = 0x16, -+ SSAM_SSH_TC_BKL = 0x17, -+ SSAM_SSH_TC_TAM = 0x18, -+ SSAM_SSH_TC_ACC0 = 0x19, -+ SSAM_SSH_TC_UFI = 0x1a, -+ SSAM_SSH_TC_USC = 0x1b, -+ SSAM_SSH_TC_PEN = 0x1c, -+ SSAM_SSH_TC_VID = 0x1d, -+ SSAM_SSH_TC_AUD = 0x1e, -+ SSAM_SSH_TC_SMC = 0x1f, -+ SSAM_SSH_TC_KPD = 0x20, -+ SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */ -+ SSAM_SSH_TC_SPT = 0x22, -+ SSAM_SSH_TC_SYS = 0x23, -+ SSAM_SSH_TC_ACC1 = 0x24, -+ SSAM_SSH_TC_SHB = 0x25, -+ SSAM_SSH_TC_POS = 0x26, /* For obtaining Laptop Studio screen position. */ - }; - - --- -2.38.0 - -From ca91b9e022c97699cd48fa9bf9a986ca99c4ce68 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 24 Jun 2022 20:36:39 +0200 -Subject: [PATCH] platform/surface: aggregator: Add helper macros for requests - with argument and return value - -Add helper macros for synchronous stack-allocated Surface Aggregator -request with both argument and return value, similar to the current -argument-only and return-value-only ones. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220624183642.910893-2-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - include/linux/surface_aggregator/controller.h | 125 ++++++++++++++++++ - include/linux/surface_aggregator/device.h | 36 +++++ - 2 files changed, 161 insertions(+) - -diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h -index 50a2b4926c06..d11a1c6e3186 100644 ---- a/include/linux/surface_aggregator/controller.h -+++ b/include/linux/surface_aggregator/controller.h -@@ -469,6 +469,67 @@ struct ssam_request_spec_md { - return 0; \ - } - -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_WR() - Define synchronous SAM request function with -+ * both argument and return value. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by @spec, -+ * with the request taking an argument of type @atype and having a return value -+ * of type @rtype. The generated function takes care of setting up the request -+ * and response structs, buffer allocation, as well as execution of the request -+ * itself, returning once the request has been fully completed. The required -+ * transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, const atype *arg, rtype *ret)``, returning the status -+ * of the request, which is zero on success and negative on failure. The -+ * ``ctrl`` parameter is the controller via which the request is sent. The -+ * request argument is specified via the ``arg`` pointer. The request's return -+ * value is written to the memory pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_WR(name, atype, rtype, spec...) \ -+ static int name(struct ssam_controller *ctrl, const atype *arg, rtype *ret) \ -+ { \ -+ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ -+ struct ssam_request rqst; \ -+ struct ssam_response rsp; \ -+ int status; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = s.target_id; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = s.instance_id; \ -+ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ -+ rqst.length = sizeof(atype); \ -+ rqst.payload = (u8 *)arg; \ -+ \ -+ rsp.capacity = sizeof(rtype); \ -+ rsp.length = 0; \ -+ rsp.pointer = (u8 *)ret; \ -+ \ -+ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, sizeof(atype)); \ -+ if (status) \ -+ return status; \ -+ \ -+ if (rsp.length != sizeof(rtype)) { \ -+ struct device *dev = ssam_controller_device(ctrl); \ -+ dev_err(dev, \ -+ "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ -+ sizeof(rtype), rsp.length, rqst.target_category,\ -+ rqst.command_id); \ -+ return -EIO; \ -+ } \ -+ \ -+ return 0; \ -+ } -+ - /** - * SSAM_DEFINE_SYNC_REQUEST_MD_N() - Define synchronous multi-device SAM - * request function with neither argument nor return value. -@@ -613,6 +674,70 @@ struct ssam_request_spec_md { - return 0; \ - } - -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_MD_WR() - Define synchronous multi-device SAM -+ * request function with both argument and return value. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by @spec, -+ * with the request taking an argument of type @atype and having a return value -+ * of type @rtype. Device specifying parameters are not hard-coded, but instead -+ * must be provided to the function. The generated function takes care of -+ * setting up the request and response structs, buffer allocation, as well as -+ * execution of the request itself, returning once the request has been fully -+ * completed. The required transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct -+ * ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg, rtype *ret)``, -+ * returning the status of the request, which is zero on success and negative -+ * on failure. The ``ctrl`` parameter is the controller via which the request -+ * is sent, ``tid`` the target ID for the request, and ``iid`` the instance ID. -+ * The request argument is specified via the ``arg`` pointer. The request's -+ * return value is written to the memory pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_MD_WR(name, atype, rtype, spec...) \ -+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, \ -+ const atype *arg, rtype *ret) \ -+ { \ -+ struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ -+ struct ssam_request rqst; \ -+ struct ssam_response rsp; \ -+ int status; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.target_id = tid; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = iid; \ -+ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ -+ rqst.length = sizeof(atype); \ -+ rqst.payload = (u8 *)arg; \ -+ \ -+ rsp.capacity = sizeof(rtype); \ -+ rsp.length = 0; \ -+ rsp.pointer = (u8 *)ret; \ -+ \ -+ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, sizeof(atype)); \ -+ if (status) \ -+ return status; \ -+ \ -+ if (rsp.length != sizeof(rtype)) { \ -+ struct device *dev = ssam_controller_device(ctrl); \ -+ dev_err(dev, \ -+ "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ -+ sizeof(rtype), rsp.length, rqst.target_category,\ -+ rqst.command_id); \ -+ return -EIO; \ -+ } \ -+ \ -+ return 0; \ -+ } -+ - - /* -- Event notifier/callbacks. --------------------------------------------- */ - -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index c418f7f2732d..6cf7e80312d5 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -483,6 +483,42 @@ static inline void ssam_remove_clients(struct device *dev) {} - sdev->uid.instance, ret); \ - } - -+/** -+ * SSAM_DEFINE_SYNC_REQUEST_CL_WR() - Define synchronous client-device SAM -+ * request function with argument and return value. -+ * @name: Name of the generated function. -+ * @atype: Type of the request's argument. -+ * @rtype: Type of the request's return value. -+ * @spec: Specification (&struct ssam_request_spec_md) defining the request. -+ * -+ * Defines a function executing the synchronous SAM request specified by @spec, -+ * with the request taking an argument of type @atype and having a return value -+ * of type @rtype. Device specifying parameters are not hard-coded, but instead -+ * are provided via the client device, specifically its UID, supplied when -+ * calling this function. The generated function takes care of setting up the -+ * request struct, buffer allocation, as well as execution of the request -+ * itself, returning once the request has been fully completed. The required -+ * transport buffer will be allocated on the stack. -+ * -+ * The generated function is defined as ``static int name(struct ssam_device -+ * *sdev, const atype *arg, rtype *ret)``, returning the status of the request, -+ * which is zero on success and negative on failure. The ``sdev`` parameter -+ * specifies both the target device of the request and by association the -+ * controller via which the request is sent. The request's argument is -+ * specified via the ``arg`` pointer. The request's return value is written to -+ * the memory pointed to by the ``ret`` parameter. -+ * -+ * Refer to ssam_request_sync_onstack() for more details on the behavior of -+ * the generated function. -+ */ -+#define SSAM_DEFINE_SYNC_REQUEST_CL_WR(name, atype, rtype, spec...) \ -+ SSAM_DEFINE_SYNC_REQUEST_MD_WR(__raw_##name, atype, rtype, spec) \ -+ static int name(struct ssam_device *sdev, const atype *arg, rtype *ret) \ -+ { \ -+ return __raw_##name(sdev->ctrl, sdev->uid.target, \ -+ sdev->uid.instance, arg, ret); \ -+ } -+ - - /* -- Helpers for client-device notifiers. ---------------------------------- */ - --- -2.38.0 - -From 82139187c51091787435c4ed722ddcfe423ba464 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 24 Jun 2022 20:36:40 +0200 -Subject: [PATCH] platform/surface: Add KIP/POS tablet-mode switch driver -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Add a driver providing a tablet-mode switch input device for Microsoft -Surface devices using the Surface Aggregator KIP subsystem (to manage -detachable peripherals) or POS subsystem (to obtain device posture -information). - -The KIP (full name unknown, abbreviation found through reverse -engineering) subsystem is used on the Surface Pro 8 and Surface Pro X to -manage the keyboard cover. Among other things, it provides information -on the positioning (posture) of the cover (closed, laptop-style, -detached, folded-back, ...), which can be used to implement an input -device providing the SW_TABLET_MODE event. Similarly, the POS (posture -information) subsystem provides such information on the Surface Laptop -Studio, with the difference being that the keyboard is not detachable. - -As implementing the tablet-mode switch for both subsystems is largely -similar, the driver proposed in this commit, in large, acts as a generic -tablet mode switch driver framework for the Surface Aggregator Module. -Specific implementations using this framework are provided for the KIP -and POS subsystems, adding tablet-mode switch support to the -aforementioned devices. - -A few more notes on the Surface Laptop Studio: - -A peculiarity of the Surface Laptop Studio is its "slate/tent" mode -(symbolized: user> _/\). In this mode, the screen covers the keyboard -but leaves the touchpad exposed. This is essentially a mode in-between -tablet and laptop, and it is debatable whether tablet-mode should be -enabled in this mode. We therefore let the user decide this via a module -parameter. - -In particular, tablet-mode may bring up the on-screen touch keyboard -more easily, which would be desirable in this mode. However, some -user-space software currently also decides to disable keyboard and, more -importantly, touchpad input, while the touchpad is still accessible in -the "slate/tent" mode. Furthermore, this mode shares its identifier with -"slate/flipped" mode where the screen is flipped 180° and the keyboard -points away from the user (symbolized: user> /_). In this mode we would -like to enable auto-rotation, something that user-space software may -only do when tablet-mode is enabled. We therefore default to the -slate-mode enabling the tablet-mode switch. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220624183642.910893-3-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - .../sysfs-bus-surface_aggregator-tabletsw | 57 ++ - MAINTAINERS | 6 + - drivers/platform/surface/Kconfig | 23 + - drivers/platform/surface/Makefile | 1 + - .../surface/surface_aggregator_tabletsw.c | 533 ++++++++++++++++++ - 5 files changed, 620 insertions(+) - create mode 100644 Documentation/ABI/testing/sysfs-bus-surface_aggregator-tabletsw - create mode 100644 drivers/platform/surface/surface_aggregator_tabletsw.c - -diff --git a/Documentation/ABI/testing/sysfs-bus-surface_aggregator-tabletsw b/Documentation/ABI/testing/sysfs-bus-surface_aggregator-tabletsw -new file mode 100644 -index 000000000000..74cd9d754e60 ---- /dev/null -+++ b/Documentation/ABI/testing/sysfs-bus-surface_aggregator-tabletsw -@@ -0,0 +1,57 @@ -+What: /sys/bus/surface_aggregator/devices/01:0e:01:00:01/state -+Date: July 2022 -+KernelVersion: 5.20 -+Contact: Maximilian Luz -+Description: -+ This attribute returns a string with the current type-cover -+ or device posture, as indicated by the embedded controller. -+ Currently returned posture states are: -+ -+ - "disconnected": The type-cover has been disconnected. -+ -+ - "closed": The type-cover has been folded closed and lies on -+ top of the display. -+ -+ - "laptop": The type-cover is open and in laptop-mode, i.e., -+ ready for normal use. -+ -+ - "folded-canvas": The type-cover has been folded back -+ part-ways, but does not lie flush with the back side of the -+ device. In general, this means that the kick-stand is used -+ and extended atop of the cover. -+ -+ - "folded-back": The type cover has been fully folded back and -+ lies flush with the back side of the device. -+ -+ - "": The current state is unknown to the driver, for -+ example due to newer as-of-yet unsupported hardware. -+ -+ New states may be introduced with new hardware. Users therefore -+ must not rely on this list of states being exhaustive and -+ gracefully handle unknown states. -+ -+What: /sys/bus/surface_aggregator/devices/01:26:01:00:01/state -+Date: July 2022 -+KernelVersion: 5.20 -+Contact: Maximilian Luz -+Description: -+ This attribute returns a string with the current device posture, as indicated by the embedded controller. Currently -+ returned posture states are: -+ -+ - "closed": The lid of the device is closed. -+ -+ - "laptop": The lid of the device is opened and the device -+ operates as a normal laptop. -+ -+ - "slate": The screen covers the keyboard or has been flipped -+ back and the device operates mainly based on touch input. -+ -+ - "tablet": The device operates as tablet and exclusively -+ relies on touch input (or external peripherals). -+ -+ - "": The current state is unknown to the driver, for -+ example due to newer as-of-yet unsupported hardware. -+ -+ New states may be introduced with new hardware. Users therefore -+ must not rely on this list of states being exhaustive and -+ gracefully handle unknown states. -diff --git a/MAINTAINERS b/MAINTAINERS -index 08620b9a44fc..773eb8387f4a 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -13299,6 +13299,12 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch] - F: include/linux/cciss*.h - F: include/uapi/linux/cciss*.h - -+MICROSOFT SURFACE AGGREGATOR TABLET-MODE SWITCH -+M: Maximilian Luz -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/platform/surface/surface_aggregator_tablet_switch.c -+ - MICROSOFT SURFACE BATTERY AND AC DRIVERS - M: Maximilian Luz - L: linux-pm@vger.kernel.org -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index eb79fbed8059..b152e930cc84 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -99,6 +99,29 @@ config SURFACE_AGGREGATOR_REGISTRY - the respective client devices. Drivers for these devices still need to - be selected via the other options. - -+config SURFACE_AGGREGATOR_TABLET_SWITCH -+ tristate "Surface Aggregator Generic Tablet-Mode Switch Driver" -+ depends on SURFACE_AGGREGATOR -+ depends on SURFACE_AGGREGATOR_BUS -+ depends on INPUT -+ help -+ Provides a tablet-mode switch input device on Microsoft Surface models -+ using the KIP subsystem for detachable keyboards (e.g. keyboard covers) -+ or the POS subsystem for device/screen posture changes. -+ -+ The KIP subsystem is used on newer Surface generations to handle -+ detachable input peripherals, specifically the keyboard cover (containing -+ keyboard and touchpad) on the Surface Pro 8 and Surface Pro X. The POS -+ subsystem is used for device posture change notifications on the Surface -+ Laptop Studio. This module provides a driver to let user-space know when -+ the device should be considered in tablet-mode due to the keyboard cover -+ being detached or folded back (essentially signaling when the keyboard is -+ not available for input). It does so by creating a tablet-mode switch -+ input device, sending the standard SW_TABLET_MODE event on mode change. -+ -+ Select M or Y here, if you want to provide tablet-mode switch input -+ events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. -+ - config SURFACE_DTX - tristate "Surface DTX (Detachment System) Driver" - depends on SURFACE_AGGREGATOR -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 0fc9cd3e4dd9..18b27898543e 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -10,6 +10,7 @@ obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o -+obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c -new file mode 100644 -index 000000000000..596ca6c80681 ---- /dev/null -+++ b/drivers/platform/surface/surface_aggregator_tabletsw.c -@@ -0,0 +1,533 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) tablet mode switch driver. -+ * -+ * Copyright (C) 2022 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+ -+/* -- SSAM generic tablet switch driver framework. -------------------------- */ -+ -+struct ssam_tablet_sw; -+ -+struct ssam_tablet_sw_ops { -+ int (*get_state)(struct ssam_tablet_sw *sw, u32 *state); -+ const char *(*state_name)(struct ssam_tablet_sw *sw, u32 state); -+ bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw, u32 state); -+}; -+ -+struct ssam_tablet_sw { -+ struct ssam_device *sdev; -+ -+ u32 state; -+ struct work_struct update_work; -+ struct input_dev *mode_switch; -+ -+ struct ssam_tablet_sw_ops ops; -+ struct ssam_event_notifier notif; -+}; -+ -+struct ssam_tablet_sw_desc { -+ struct { -+ const char *name; -+ const char *phys; -+ } dev; -+ -+ struct { -+ u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event); -+ int (*get_state)(struct ssam_tablet_sw *sw, u32 *state); -+ const char *(*state_name)(struct ssam_tablet_sw *sw, u32 state); -+ bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw, u32 state); -+ } ops; -+ -+ struct { -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ enum ssam_event_mask mask; -+ u8 flags; -+ } event; -+}; -+ -+static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) -+{ -+ struct ssam_tablet_sw *sw = dev_get_drvdata(dev); -+ const char *state = sw->ops.state_name(sw, sw->state); -+ -+ return sysfs_emit(buf, "%s\n", state); -+} -+static DEVICE_ATTR_RO(state); -+ -+static struct attribute *ssam_tablet_sw_attrs[] = { -+ &dev_attr_state.attr, -+ NULL, -+}; -+ -+static const struct attribute_group ssam_tablet_sw_group = { -+ .attrs = ssam_tablet_sw_attrs, -+}; -+ -+static void ssam_tablet_sw_update_workfn(struct work_struct *work) -+{ -+ struct ssam_tablet_sw *sw = container_of(work, struct ssam_tablet_sw, update_work); -+ int tablet, status; -+ u32 state; -+ -+ status = sw->ops.get_state(sw, &state); -+ if (status) -+ return; -+ -+ if (sw->state == state) -+ return; -+ sw->state = state; -+ -+ /* Send SW_TABLET_MODE event. */ -+ tablet = sw->ops.state_is_tablet_mode(sw, state); -+ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); -+ input_sync(sw->mode_switch); -+} -+ -+static int __maybe_unused ssam_tablet_sw_resume(struct device *dev) -+{ -+ struct ssam_tablet_sw *sw = dev_get_drvdata(dev); -+ -+ schedule_work(&sw->update_work); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_tablet_sw_pm_ops, NULL, ssam_tablet_sw_resume); -+ -+static int ssam_tablet_sw_probe(struct ssam_device *sdev) -+{ -+ const struct ssam_tablet_sw_desc *desc; -+ struct ssam_tablet_sw *sw; -+ int tablet, status; -+ -+ desc = ssam_device_get_match_data(sdev); -+ if (!desc) { -+ WARN(1, "no driver match data specified"); -+ return -EINVAL; -+ } -+ -+ sw = devm_kzalloc(&sdev->dev, sizeof(*sw), GFP_KERNEL); -+ if (!sw) -+ return -ENOMEM; -+ -+ sw->sdev = sdev; -+ -+ sw->ops.get_state = desc->ops.get_state; -+ sw->ops.state_name = desc->ops.state_name; -+ sw->ops.state_is_tablet_mode = desc->ops.state_is_tablet_mode; -+ -+ INIT_WORK(&sw->update_work, ssam_tablet_sw_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, sw); -+ -+ /* Get initial state. */ -+ status = sw->ops.get_state(sw, &sw->state); -+ if (status) -+ return status; -+ -+ /* Set up tablet mode switch. */ -+ sw->mode_switch = devm_input_allocate_device(&sdev->dev); -+ if (!sw->mode_switch) -+ return -ENOMEM; -+ -+ sw->mode_switch->name = desc->dev.name; -+ sw->mode_switch->phys = desc->dev.phys; -+ sw->mode_switch->id.bustype = BUS_HOST; -+ sw->mode_switch->dev.parent = &sdev->dev; -+ -+ tablet = sw->ops.state_is_tablet_mode(sw, sw->state); -+ input_set_capability(sw->mode_switch, EV_SW, SW_TABLET_MODE); -+ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); -+ -+ status = input_register_device(sw->mode_switch); -+ if (status) -+ return status; -+ -+ /* Set up notifier. */ -+ sw->notif.base.priority = 0; -+ sw->notif.base.fn = desc->ops.notify; -+ sw->notif.event.reg = desc->event.reg; -+ sw->notif.event.id = desc->event.id; -+ sw->notif.event.mask = desc->event.mask; -+ sw->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ status = ssam_device_notifier_register(sdev, &sw->notif); -+ if (status) -+ return status; -+ -+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_tablet_sw_group); -+ if (status) -+ goto err; -+ -+ /* We might have missed events during setup, so check again. */ -+ schedule_work(&sw->update_work); -+ return 0; -+ -+err: -+ ssam_device_notifier_unregister(sdev, &sw->notif); -+ cancel_work_sync(&sw->update_work); -+ return status; -+} -+ -+static void ssam_tablet_sw_remove(struct ssam_device *sdev) -+{ -+ struct ssam_tablet_sw *sw = ssam_device_get_drvdata(sdev); -+ -+ sysfs_remove_group(&sdev->dev.kobj, &ssam_tablet_sw_group); -+ -+ ssam_device_notifier_unregister(sdev, &sw->notif); -+ cancel_work_sync(&sw->update_work); -+} -+ -+ -+/* -- SSAM KIP tablet switch implementation. -------------------------------- */ -+ -+#define SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED 0x1d -+ -+enum ssam_kip_cover_state { -+ SSAM_KIP_COVER_STATE_DISCONNECTED = 0x01, -+ SSAM_KIP_COVER_STATE_CLOSED = 0x02, -+ SSAM_KIP_COVER_STATE_LAPTOP = 0x03, -+ SSAM_KIP_COVER_STATE_FOLDED_CANVAS = 0x04, -+ SSAM_KIP_COVER_STATE_FOLDED_BACK = 0x05, -+}; -+ -+static const char *ssam_kip_cover_state_name(struct ssam_tablet_sw *sw, u32 state) -+{ -+ switch (state) { -+ case SSAM_KIP_COVER_STATE_DISCONNECTED: -+ return "disconnected"; -+ -+ case SSAM_KIP_COVER_STATE_CLOSED: -+ return "closed"; -+ -+ case SSAM_KIP_COVER_STATE_LAPTOP: -+ return "laptop"; -+ -+ case SSAM_KIP_COVER_STATE_FOLDED_CANVAS: -+ return "folded-canvas"; -+ -+ case SSAM_KIP_COVER_STATE_FOLDED_BACK: -+ return "folded-back"; -+ -+ default: -+ dev_warn(&sw->sdev->dev, "unknown KIP cover state: %u\n", state); -+ return ""; -+ } -+} -+ -+static bool ssam_kip_cover_state_is_tablet_mode(struct ssam_tablet_sw *sw, u32 state) -+{ -+ switch (state) { -+ case SSAM_KIP_COVER_STATE_DISCONNECTED: -+ case SSAM_KIP_COVER_STATE_FOLDED_CANVAS: -+ case SSAM_KIP_COVER_STATE_FOLDED_BACK: -+ return true; -+ -+ case SSAM_KIP_COVER_STATE_CLOSED: -+ case SSAM_KIP_COVER_STATE_LAPTOP: -+ return false; -+ -+ default: -+ dev_warn(&sw->sdev->dev, "unknown KIP cover state: %d\n", sw->state); -+ return true; -+ } -+} -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_cover_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x1d, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_get_cover_state(struct ssam_tablet_sw *sw, u32 *state) -+{ -+ int status; -+ u8 raw; -+ -+ status = ssam_retry(__ssam_kip_get_cover_state, sw->sdev->ctrl, &raw); -+ if (status < 0) { -+ dev_err(&sw->sdev->dev, "failed to query KIP lid state: %d\n", status); -+ return status; -+ } -+ -+ *state = raw; -+ return 0; -+} -+ -+static u32 ssam_kip_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_tablet_sw *sw = container_of(nf, struct ssam_tablet_sw, notif); -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) -+ dev_warn(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); -+ -+ schedule_work(&sw->update_work); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static const struct ssam_tablet_sw_desc ssam_kip_sw_desc = { -+ .dev = { -+ .name = "Microsoft Surface KIP Tablet Mode Switch", -+ .phys = "ssam/01:0e:01:00:01/input0", -+ }, -+ .ops = { -+ .notify = ssam_kip_sw_notif, -+ .get_state = ssam_kip_get_cover_state, -+ .state_name = ssam_kip_cover_state_name, -+ .state_is_tablet_mode = ssam_kip_cover_state_is_tablet_mode, -+ }, -+ .event = { -+ .reg = SSAM_EVENT_REGISTRY_SAM, -+ .id = { -+ .target_category = SSAM_SSH_TC_KIP, -+ .instance = 0, -+ }, -+ .mask = SSAM_EVENT_MASK_TARGET, -+ }, -+}; -+ -+ -+/* -- SSAM POS tablet switch implementation. -------------------------------- */ -+ -+static bool tablet_mode_in_slate_state = true; -+module_param(tablet_mode_in_slate_state, bool, 0644); -+MODULE_PARM_DESC(tablet_mode_in_slate_state, "Enable tablet mode in slate device posture, default is 'true'"); -+ -+#define SSAM_EVENT_POS_CID_POSTURE_CHANGED 0x03 -+#define SSAM_POS_MAX_SOURCES 4 -+ -+enum ssam_pos_state { -+ SSAM_POS_POSTURE_LID_CLOSED = 0x00, -+ SSAM_POS_POSTURE_LAPTOP = 0x01, -+ SSAM_POS_POSTURE_SLATE = 0x02, -+ SSAM_POS_POSTURE_TABLET = 0x03, -+}; -+ -+struct ssam_sources_list { -+ __le32 count; -+ __le32 id[SSAM_POS_MAX_SOURCES]; -+} __packed; -+ -+static const char *ssam_pos_state_name(struct ssam_tablet_sw *sw, u32 state) -+{ -+ switch (state) { -+ case SSAM_POS_POSTURE_LID_CLOSED: -+ return "closed"; -+ -+ case SSAM_POS_POSTURE_LAPTOP: -+ return "laptop"; -+ -+ case SSAM_POS_POSTURE_SLATE: -+ return "slate"; -+ -+ case SSAM_POS_POSTURE_TABLET: -+ return "tablet"; -+ -+ default: -+ dev_warn(&sw->sdev->dev, "unknown device posture: %u\n", state); -+ return ""; -+ } -+} -+ -+static bool ssam_pos_state_is_tablet_mode(struct ssam_tablet_sw *sw, u32 state) -+{ -+ switch (state) { -+ case SSAM_POS_POSTURE_LAPTOP: -+ case SSAM_POS_POSTURE_LID_CLOSED: -+ return false; -+ -+ case SSAM_POS_POSTURE_SLATE: -+ return tablet_mode_in_slate_state; -+ -+ case SSAM_POS_POSTURE_TABLET: -+ return true; -+ -+ default: -+ dev_warn(&sw->sdev->dev, "unknown device posture: %u\n", state); -+ return true; -+ } -+} -+ -+static int ssam_pos_get_sources_list(struct ssam_tablet_sw *sw, struct ssam_sources_list *sources) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ int status; -+ -+ rqst.target_category = SSAM_SSH_TC_POS; -+ rqst.target_id = 0x01; -+ rqst.command_id = 0x01; -+ rqst.instance_id = 0x00; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = 0; -+ rqst.payload = NULL; -+ -+ rsp.capacity = sizeof(*sources); -+ rsp.length = 0; -+ rsp.pointer = (u8 *)sources; -+ -+ status = ssam_retry(ssam_request_sync_onstack, sw->sdev->ctrl, &rqst, &rsp, 0); -+ if (status) -+ return status; -+ -+ /* We need at least the 'sources->count' field. */ -+ if (rsp.length < sizeof(__le32)) { -+ dev_err(&sw->sdev->dev, "received source list response is too small\n"); -+ return -EPROTO; -+ } -+ -+ /* Make sure 'sources->count' matches with the response length. */ -+ if (get_unaligned_le32(&sources->count) * sizeof(__le32) + sizeof(__le32) != rsp.length) { -+ dev_err(&sw->sdev->dev, "mismatch between number of sources and response size\n"); -+ return -EPROTO; -+ } -+ -+ return 0; -+} -+ -+static int ssam_pos_get_source(struct ssam_tablet_sw *sw, u32 *source_id) -+{ -+ struct ssam_sources_list sources = {}; -+ int status; -+ -+ status = ssam_pos_get_sources_list(sw, &sources); -+ if (status) -+ return status; -+ -+ if (sources.count == 0) { -+ dev_err(&sw->sdev->dev, "no posture sources found\n"); -+ return -ENODEV; -+ } -+ -+ /* -+ * We currently don't know what to do with more than one posture -+ * source. At the moment, only one source seems to be used/provided. -+ * The WARN_ON() here should hopefully let us know quickly once there -+ * is a device that provides multiple sources, at which point we can -+ * then try to figure out how to handle them. -+ */ -+ WARN_ON(sources.count > 1); -+ -+ *source_id = get_unaligned_le32(&sources.id[0]); -+ return 0; -+} -+ -+SSAM_DEFINE_SYNC_REQUEST_WR(__ssam_pos_get_posture_for_source, __le32, __le32, { -+ .target_category = SSAM_SSH_TC_POS, -+ .target_id = 0x01, -+ .command_id = 0x02, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_pos_get_posture_for_source(struct ssam_tablet_sw *sw, u32 source_id, u32 *posture) -+{ -+ __le32 source_le = cpu_to_le32(source_id); -+ __le32 rspval_le = 0; -+ int status; -+ -+ status = ssam_retry(__ssam_pos_get_posture_for_source, sw->sdev->ctrl, -+ &source_le, &rspval_le); -+ if (status) -+ return status; -+ -+ *posture = le32_to_cpu(rspval_le); -+ return 0; -+} -+ -+static int ssam_pos_get_posture(struct ssam_tablet_sw *sw, u32 *state) -+{ -+ u32 source_id; -+ int status; -+ -+ status = ssam_pos_get_source(sw, &source_id); -+ if (status) { -+ dev_err(&sw->sdev->dev, "failed to get posture source ID: %d\n", status); -+ return status; -+ } -+ -+ status = ssam_pos_get_posture_for_source(sw, source_id, state); -+ if (status) { -+ dev_err(&sw->sdev->dev, "failed to get posture value for source %u: %d\n", -+ source_id, status); -+ return status; -+ } -+ -+ return 0; -+} -+ -+static u32 ssam_pos_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_tablet_sw *sw = container_of(nf, struct ssam_tablet_sw, notif); -+ -+ if (event->command_id != SSAM_EVENT_POS_CID_POSTURE_CHANGED) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length != sizeof(__le32) * 3) -+ dev_warn(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); -+ -+ schedule_work(&sw->update_work); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { -+ .dev = { -+ .name = "Microsoft Surface POS Tablet Mode Switch", -+ .phys = "ssam/01:26:01:00:01/input0", -+ }, -+ .ops = { -+ .notify = ssam_pos_sw_notif, -+ .get_state = ssam_pos_get_posture, -+ .state_name = ssam_pos_state_name, -+ .state_is_tablet_mode = ssam_pos_state_is_tablet_mode, -+ }, -+ .event = { -+ .reg = SSAM_EVENT_REGISTRY_SAM, -+ .id = { -+ .target_category = SSAM_SSH_TC_POS, -+ .instance = 0, -+ }, -+ .mask = SSAM_EVENT_MASK_TARGET, -+ }, -+}; -+ -+ -+/* -- Driver registration. -------------------------------------------------- */ -+ -+static const struct ssam_device_id ssam_tablet_sw_match[] = { -+ { SSAM_SDEV(KIP, 0x01, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, -+ { SSAM_SDEV(POS, 0x01, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); -+ -+static struct ssam_device_driver ssam_tablet_sw_driver = { -+ .probe = ssam_tablet_sw_probe, -+ .remove = ssam_tablet_sw_remove, -+ .match_table = ssam_tablet_sw_match, -+ .driver = { -+ .name = "surface_aggregator_tablet_mode_switch", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_tablet_sw_pm_ops, -+ }, -+}; -+module_ssam_device_driver(ssam_tablet_sw_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Tablet mode switch driver for Surface devices using the Surface Aggregator Module"); -+MODULE_LICENSE("GPL"); --- -2.38.0 - -From 5fb1e39e052e84e979daac239058fbce25d4d4ec Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 24 Jun 2022 20:36:41 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for tablet - mode switch on Surface Pro 8 - -Add a KIP subsystem tablet-mode switch device for the Surface Pro 8. -The respective driver for this device provides SW_TABLET_MODE input -events for user-space based on the state of the keyboard cover (e.g. -detached, folded-back, normal/laptop mode). - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220624183642.910893-4-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 8 +++++++- - 1 file changed, 7 insertions(+), 1 deletion(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 3b1310d61a24..6994168ee898 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -77,6 +77,12 @@ static const struct software_node ssam_node_tmp_pprof = { - .parent = &ssam_node_root, - }; - -+/* Tablet-mode switch via KIP subsystem. */ -+static const struct software_node ssam_node_kip_tablet_switch = { -+ .name = "ssam:01:0e:01:00:01", -+ .parent = &ssam_node_root, -+}; -+ - /* DTX / detachment-system device (Surface Book 3). */ - static const struct software_node ssam_node_bas_dtx = { - .name = "ssam:01:11:01:00:00", -@@ -264,11 +270,11 @@ static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -+ &ssam_node_kip_tablet_switch, - &ssam_node_hid_kip_keyboard, - &ssam_node_hid_kip_penstash, - &ssam_node_hid_kip_touchpad, - &ssam_node_hid_kip_iid5, -- /* TODO: Add support for tablet mode switch. */ - NULL, - }; - --- -2.38.0 - -From 025e819e284effcbd8f48b1a37c9d036271fd813 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 24 Jun 2022 20:36:42 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add support for tablet - mode switch on Surface Laptop Studio - -Add a POS subsystem tablet-mode switch device for the Surface Laptop -Studio. The respective driver for this device provides SW_TABLET_MODE -input events for user-space based on the posture of the screen. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220624183642.910893-5-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 6994168ee898..6e14dd18c7a7 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -191,6 +191,12 @@ static const struct software_node ssam_node_hid_kip_iid5 = { - .parent = &ssam_node_hub_kip, - }; - -+/* Tablet-mode switch via POS subsystem. */ -+static const struct software_node ssam_node_pos_tablet_switch = { -+ .name = "ssam:01:26:01:00:01", -+ .parent = &ssam_node_root, -+}; -+ - /* - * Devices for 5th- and 6th-generations models: - * - Surface Book 2, -@@ -237,6 +243,7 @@ static const struct software_node *ssam_node_group_sls[] = { - &ssam_node_bat_ac, - &ssam_node_bat_main, - &ssam_node_tmp_pprof, -+ &ssam_node_pos_tablet_switch, - &ssam_node_hid_tid1_keyboard, - &ssam_node_hid_tid1_penstash, - &ssam_node_hid_tid1_touchpad, --- -2.38.0 - -From f28ab37b6a80cc860f3c0507065bebd2052ec220 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 24 Jun 2022 22:57:58 +0200 -Subject: [PATCH] platform/surface: aggregator: Move device registry helper - functions to core module - -Move helper functions for client device registration to the core module. -This simplifies addition of future DT/OF support and also allows us to -split out the device hub drivers into their own module. - -At the same time, also improve device node validation a bit by not -silently skipping devices with invalid device UID specifiers. Further, -ensure proper lifetime management for the firmware/software nodes -associated with the added devices. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220624205800.1355621-2-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/bus.c | 149 ++++++++++++++++-- - .../surface/surface_aggregator_registry.c | 75 +-------- - include/linux/surface_aggregator/device.h | 52 ++++++ - 3 files changed, 187 insertions(+), 89 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -index abbbb5b08b07..e0b0381a2834 100644 ---- a/drivers/platform/surface/aggregator/bus.c -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -6,6 +6,7 @@ - */ - - #include -+#include - #include - - #include -@@ -14,6 +15,9 @@ - #include "bus.h" - #include "controller.h" - -+ -+/* -- Device and bus functions. --------------------------------------------- */ -+ - static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, - char *buf) - { -@@ -46,6 +50,7 @@ static void ssam_device_release(struct device *dev) - struct ssam_device *sdev = to_ssam_device(dev); - - ssam_controller_put(sdev->ctrl); -+ fwnode_handle_put(sdev->dev.fwnode); - kfree(sdev); - } - -@@ -363,6 +368,134 @@ void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) - } - EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); - -+ -+/* -- Bus registration. ----------------------------------------------------- */ -+ -+/** -+ * ssam_bus_register() - Register and set-up the SSAM client device bus. -+ */ -+int ssam_bus_register(void) -+{ -+ return bus_register(&ssam_bus_type); -+} -+ -+/** -+ * ssam_bus_unregister() - Unregister the SSAM client device bus. -+ */ -+void ssam_bus_unregister(void) -+{ -+ return bus_unregister(&ssam_bus_type); -+} -+ -+ -+/* -- Helpers for controller and hub devices. ------------------------------- */ -+ -+static int ssam_device_uid_from_string(const char *str, struct ssam_device_uid *uid) -+{ -+ u8 d, tc, tid, iid, fn; -+ int n; -+ -+ n = sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); -+ if (n != 5) -+ return -EINVAL; -+ -+ uid->domain = d; -+ uid->category = tc; -+ uid->target = tid; -+ uid->instance = iid; -+ uid->function = fn; -+ -+ return 0; -+} -+ -+static int ssam_get_uid_for_node(struct fwnode_handle *node, struct ssam_device_uid *uid) -+{ -+ const char *str = fwnode_get_name(node); -+ -+ /* -+ * To simplify definitions of firmware nodes, we set the device name -+ * based on the UID of the device, prefixed with "ssam:". -+ */ -+ if (strncmp(str, "ssam:", strlen("ssam:")) != 0) -+ return -ENODEV; -+ -+ str += strlen("ssam:"); -+ return ssam_device_uid_from_string(str, uid); -+} -+ -+static int ssam_add_client_device(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ struct ssam_device_uid uid; -+ struct ssam_device *sdev; -+ int status; -+ -+ status = ssam_get_uid_for_node(node, &uid); -+ if (status) -+ return status; -+ -+ sdev = ssam_device_alloc(ctrl, uid); -+ if (!sdev) -+ return -ENOMEM; -+ -+ sdev->dev.parent = parent; -+ sdev->dev.fwnode = fwnode_handle_get(node); -+ -+ status = ssam_device_add(sdev); -+ if (status) -+ ssam_device_put(sdev); -+ -+ return status; -+} -+ -+/** -+ * __ssam_register_clients() - Register client devices defined under the -+ * given firmware node as children of the given device. -+ * @parent: The parent device under which clients should be registered. -+ * @ctrl: The controller with which client should be registered. -+ * @node: The firmware node holding definitions of the devices to be added. -+ * -+ * Register all clients that have been defined as children of the given root -+ * firmware node as children of the given parent device. The respective child -+ * firmware nodes will be associated with the correspondingly created child -+ * devices. -+ * -+ * The given controller will be used to instantiate the new devices. See -+ * ssam_device_add() for details. -+ * -+ * Note that, generally, the use of either ssam_device_register_clients() or -+ * ssam_register_clients() should be preferred as they directly use the -+ * firmware node and/or controller associated with the given device. This -+ * function is only intended for use when different device specifications (e.g. -+ * ACPI and firmware nodes) need to be combined (as is done in the platform hub -+ * of the device registry). -+ * -+ * Return: Returns zero on success, nonzero on failure. -+ */ -+int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ struct fwnode_handle *child; -+ int status; -+ -+ fwnode_for_each_child_node(node, child) { -+ /* -+ * Try to add the device specified in the firmware node. If -+ * this fails with -ENODEV, the node does not specify any SSAM -+ * device, so ignore it and continue with the next one. -+ */ -+ status = ssam_add_client_device(parent, ctrl, child); -+ if (status && status != -ENODEV) -+ goto err; -+ } -+ -+ return 0; -+err: -+ ssam_remove_clients(parent); -+ return status; -+} -+EXPORT_SYMBOL_GPL(__ssam_register_clients); -+ - static int ssam_remove_device(struct device *dev, void *_data) - { - struct ssam_device *sdev = to_ssam_device(dev); -@@ -387,19 +520,3 @@ void ssam_remove_clients(struct device *dev) - device_for_each_child_reverse(dev, NULL, ssam_remove_device); - } - EXPORT_SYMBOL_GPL(ssam_remove_clients); -- --/** -- * ssam_bus_register() - Register and set-up the SSAM client device bus. -- */ --int ssam_bus_register(void) --{ -- return bus_register(&ssam_bus_type); --} -- --/** -- * ssam_bus_unregister() - Unregister the SSAM client device bus. -- */ --void ssam_bus_unregister(void) --{ -- return bus_unregister(&ssam_bus_type); --} -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 6e14dd18c7a7..eb88021c208c 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -286,76 +286,6 @@ static const struct software_node *ssam_node_group_sp8[] = { - }; - - --/* -- Device registry helper functions. ------------------------------------- */ -- --static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) --{ -- u8 d, tc, tid, iid, fn; -- int n; -- -- n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn); -- if (n != 5) -- return -EINVAL; -- -- uid->domain = d; -- uid->category = tc; -- uid->target = tid; -- uid->instance = iid; -- uid->function = fn; -- -- return 0; --} -- --static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, -- struct fwnode_handle *node) --{ -- struct ssam_device_uid uid; -- struct ssam_device *sdev; -- int status; -- -- status = ssam_uid_from_string(fwnode_get_name(node), &uid); -- if (status) -- return status; -- -- sdev = ssam_device_alloc(ctrl, uid); -- if (!sdev) -- return -ENOMEM; -- -- sdev->dev.parent = parent; -- sdev->dev.fwnode = node; -- -- status = ssam_device_add(sdev); -- if (status) -- ssam_device_put(sdev); -- -- return status; --} -- --static int ssam_hub_register_clients(struct device *parent, struct ssam_controller *ctrl, -- struct fwnode_handle *node) --{ -- struct fwnode_handle *child; -- int status; -- -- fwnode_for_each_child_node(node, child) { -- /* -- * Try to add the device specified in the firmware node. If -- * this fails with -EINVAL, the node does not specify any SSAM -- * device, so ignore it and continue with the next one. -- */ -- -- status = ssam_hub_add_device(parent, ctrl, child); -- if (status && status != -EINVAL) -- goto err; -- } -- -- return 0; --err: -- ssam_remove_clients(parent); -- return status; --} -- -- - /* -- SSAM generic subsystem hub driver framework. -------------------------- */ - - enum ssam_hub_state { -@@ -385,7 +315,6 @@ struct ssam_hub { - static void ssam_hub_update_workfn(struct work_struct *work) - { - struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); -- struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); - enum ssam_hub_state state; - int status = 0; - -@@ -425,7 +354,7 @@ static void ssam_hub_update_workfn(struct work_struct *work) - hub->state = state; - - if (hub->state == SSAM_HUB_CONNECTED) -- status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); -+ status = ssam_device_register_clients(hub->sdev); - else - ssam_remove_clients(&hub->sdev->dev); - -@@ -772,7 +701,7 @@ static int ssam_platform_hub_probe(struct platform_device *pdev) - - set_secondary_fwnode(&pdev->dev, root); - -- status = ssam_hub_register_clients(&pdev->dev, ctrl, root); -+ status = __ssam_register_clients(&pdev->dev, ctrl, root); - if (status) { - set_secondary_fwnode(&pdev->dev, NULL); - software_node_unregister_node_group(nodes); -diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h -index 6cf7e80312d5..46c45d1b6368 100644 ---- a/include/linux/surface_aggregator/device.h -+++ b/include/linux/surface_aggregator/device.h -@@ -15,6 +15,7 @@ - - #include - #include -+#include - #include - - #include -@@ -375,11 +376,62 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); - /* -- Helpers for controller and hub devices. ------------------------------- */ - - #ifdef CONFIG_SURFACE_AGGREGATOR_BUS -+ -+int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node); - void ssam_remove_clients(struct device *dev); -+ - #else /* CONFIG_SURFACE_AGGREGATOR_BUS */ -+ -+static inline int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, -+ struct fwnode_handle *node) -+{ -+ return 0; -+} -+ - static inline void ssam_remove_clients(struct device *dev) {} -+ - #endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ - -+/** -+ * ssam_register_clients() - Register all client devices defined under the -+ * given parent device. -+ * @dev: The parent device under which clients should be registered. -+ * @ctrl: The controller with which client should be registered. -+ * -+ * Register all clients that have via firmware nodes been defined as children -+ * of the given (parent) device. The respective child firmware nodes will be -+ * associated with the correspondingly created child devices. -+ * -+ * The given controller will be used to instantiate the new devices. See -+ * ssam_device_add() for details. -+ * -+ * Return: Returns zero on success, nonzero on failure. -+ */ -+static inline int ssam_register_clients(struct device *dev, struct ssam_controller *ctrl) -+{ -+ return __ssam_register_clients(dev, ctrl, dev_fwnode(dev)); -+} -+ -+/** -+ * ssam_device_register_clients() - Register all client devices defined under -+ * the given SSAM parent device. -+ * @sdev: The parent device under which clients should be registered. -+ * -+ * Register all clients that have via firmware nodes been defined as children -+ * of the given (parent) device. The respective child firmware nodes will be -+ * associated with the correspondingly created child devices. -+ * -+ * The controller used by the parent device will be used to instantiate the new -+ * devices. See ssam_device_add() for details. -+ * -+ * Return: Returns zero on success, nonzero on failure. -+ */ -+static inline int ssam_device_register_clients(struct ssam_device *sdev) -+{ -+ return ssam_register_clients(&sdev->dev, sdev->ctrl); -+} -+ - - /* -- Helpers for client-device requests. ----------------------------------- */ - --- -2.38.0 - -From 27ea729f3c973dab24ad146dcd15d240bb86a109 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 24 Jun 2022 22:57:59 +0200 -Subject: [PATCH] platform/surface: aggregator: Move subsystem hub drivers to - their own module - -Split out subsystem device hub drivers into their own module. This -allows us to load the hub drivers separately from the registry, which -will help future DT/OF support. - -While doing so, also remove a small bit of code duplication. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220624205800.1355621-3-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - MAINTAINERS | 6 + - drivers/platform/surface/Kconfig | 35 +- - drivers/platform/surface/Makefile | 1 + - .../platform/surface/surface_aggregator_hub.c | 371 ++++++++++++++++++ - .../surface/surface_aggregator_registry.c | 371 +----------------- - 5 files changed, 410 insertions(+), 374 deletions(-) - create mode 100644 drivers/platform/surface/surface_aggregator_hub.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 773eb8387f4a..55e80354a097 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -13376,6 +13376,12 @@ F: include/linux/surface_acpi_notify.h - F: include/linux/surface_aggregator/ - F: include/uapi/linux/surface_aggregator/ - -+MICROSOFT SURFACE SYSTEM AGGREGATOR HUB DRIVER -+M: Maximilian Luz -+L: platform-driver-x86@vger.kernel.org -+S: Maintained -+F: drivers/platform/surface/surface_aggregator_hub.c -+ - MICROTEK X6 SCANNER - M: Oliver Neukum - S: Maintained -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index b152e930cc84..b629e82af97c 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -72,18 +72,45 @@ config SURFACE_AGGREGATOR_CDEV - The provided interface is intended for debugging and development only, - and should not be used otherwise. - -+config SURFACE_AGGREGATOR_HUB -+ tristate "Surface System Aggregator Module Subsystem Device Hubs" -+ depends on SURFACE_AGGREGATOR -+ depends on SURFACE_AGGREGATOR_BUS -+ help -+ Device-hub drivers for Surface System Aggregator Module (SSAM) subsystem -+ devices. -+ -+ Provides subsystem hub drivers which manage client devices on various -+ SSAM subsystems. In some subsystems, notably the BAS subsystem managing -+ devices contained in the base of the Surface Book 3 and the KIP subsystem -+ managing type-cover devices in the Surface Pro 8 and Surface Pro X, -+ devices can be (hot-)removed. Hub devices and drivers are required to -+ manage these subdevices. -+ -+ Devices managed via these hubs are: -+ - Battery/AC devices (Surface Book 3). -+ - HID input devices (7th-generation and later models with detachable -+ input devices). -+ -+ Select M (recommended) or Y here if you want support for the above -+ mentioned devices on the corresponding Surface models. Without this -+ module, the respective devices mentioned above will not be instantiated -+ and thus any functionality provided by them will be missing, even when -+ drivers for these devices are present. This module only provides the -+ respective subsystem hubs. Both drivers and device specification (e.g. -+ via the Surface Aggregator Registry) for these devices still need to be -+ selected via other options. -+ - config SURFACE_AGGREGATOR_REGISTRY - tristate "Surface System Aggregator Module Device Registry" - depends on SURFACE_AGGREGATOR - depends on SURFACE_AGGREGATOR_BUS - help -- Device-registry and device-hubs for Surface System Aggregator Module -- (SSAM) devices. -+ Device-registry for Surface System Aggregator Module (SSAM) devices. - - Provides a module and driver which act as a device-registry for SSAM - client devices that cannot be detected automatically, e.g. via ACPI. -- Such devices are instead provided via this registry and attached via -- device hubs, also provided in this module. -+ Such devices are instead provided and managed via this registry. - - Devices provided via this registry are: - - Platform profile (performance-/cooling-mode) device (5th- and later -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 18b27898543e..53344330939b 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -9,6 +9,7 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o - obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ - obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o -+obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o -diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c -new file mode 100644 -index 000000000000..43061514be38 ---- /dev/null -+++ b/drivers/platform/surface/surface_aggregator_hub.c -@@ -0,0 +1,371 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Driver for Surface System Aggregator Module (SSAM) subsystem device hubs. -+ * -+ * Provides a driver for SSAM subsystems device hubs. This driver performs -+ * instantiation of the devices managed by said hubs and takes care of -+ * (hot-)removal. -+ * -+ * Copyright (C) 2020-2022 Maximilian Luz -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+ -+/* -- SSAM generic subsystem hub driver framework. -------------------------- */ -+ -+enum ssam_hub_state { -+ SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ -+ SSAM_HUB_CONNECTED, -+ SSAM_HUB_DISCONNECTED, -+}; -+ -+enum ssam_hub_flags { -+ SSAM_HUB_HOT_REMOVED, -+}; -+ -+struct ssam_hub; -+ -+struct ssam_hub_ops { -+ int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); -+}; -+ -+struct ssam_hub { -+ struct ssam_device *sdev; -+ -+ enum ssam_hub_state state; -+ unsigned long flags; -+ -+ struct delayed_work update_work; -+ unsigned long connect_delay; -+ -+ struct ssam_event_notifier notif; -+ struct ssam_hub_ops ops; -+}; -+ -+struct ssam_hub_desc { -+ struct { -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ enum ssam_event_mask mask; -+ } event; -+ -+ struct { -+ u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event); -+ int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); -+ } ops; -+ -+ unsigned long connect_delay_ms; -+}; -+ -+static void ssam_hub_update_workfn(struct work_struct *work) -+{ -+ struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); -+ enum ssam_hub_state state; -+ int status = 0; -+ -+ status = hub->ops.get_state(hub, &state); -+ if (status) -+ return; -+ -+ /* -+ * There is a small possibility that hub devices were hot-removed and -+ * re-added before we were able to remove them here. In that case, both -+ * the state returned by get_state() and the state of the hub will -+ * equal SSAM_HUB_CONNECTED and we would bail early below, which would -+ * leave child devices without proper (re-)initialization and the -+ * hot-remove flag set. -+ * -+ * Therefore, we check whether devices have been hot-removed via an -+ * additional flag on the hub and, in this case, override the returned -+ * hub state. In case of a missed disconnect (i.e. get_state returned -+ * "connected"), we further need to re-schedule this work (with the -+ * appropriate delay) as the actual connect work submission might have -+ * been merged with this one. -+ * -+ * This then leads to one of two cases: Either we submit an unnecessary -+ * work item (which will get ignored via either the queue or the state -+ * checks) or, in the unlikely case that the work is actually required, -+ * double the normal connect delay. -+ */ -+ if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { -+ if (state == SSAM_HUB_CONNECTED) -+ schedule_delayed_work(&hub->update_work, hub->connect_delay); -+ -+ state = SSAM_HUB_DISCONNECTED; -+ } -+ -+ if (hub->state == state) -+ return; -+ hub->state = state; -+ -+ if (hub->state == SSAM_HUB_CONNECTED) -+ status = ssam_device_register_clients(hub->sdev); -+ else -+ ssam_remove_clients(&hub->sdev->dev); -+ -+ if (status) -+ dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); -+} -+ -+static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ -+ if (is_ssam_device(dev)) -+ ssam_device_mark_hot_removed(sdev); -+ -+ return 0; -+} -+ -+static void ssam_hub_update(struct ssam_hub *hub, bool connected) -+{ -+ unsigned long delay; -+ -+ /* Mark devices as hot-removed before we remove any. */ -+ if (!connected) { -+ set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); -+ device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); -+ } -+ -+ /* -+ * Delay update when the base/keyboard cover is being connected to give -+ * devices/EC some time to set up. -+ */ -+ delay = connected ? hub->connect_delay : 0; -+ -+ schedule_delayed_work(&hub->update_work, delay); -+} -+ -+static int __maybe_unused ssam_hub_resume(struct device *dev) -+{ -+ struct ssam_hub *hub = dev_get_drvdata(dev); -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); -+ -+static int ssam_hub_probe(struct ssam_device *sdev) -+{ -+ const struct ssam_hub_desc *desc; -+ struct ssam_hub *hub; -+ int status; -+ -+ desc = ssam_device_get_match_data(sdev); -+ if (!desc) { -+ WARN(1, "no driver match data specified"); -+ return -EINVAL; -+ } -+ -+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -+ if (!hub) -+ return -ENOMEM; -+ -+ hub->sdev = sdev; -+ hub->state = SSAM_HUB_UNINITIALIZED; -+ -+ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -+ hub->notif.base.fn = desc->ops.notify; -+ hub->notif.event.reg = desc->event.reg; -+ hub->notif.event.id = desc->event.id; -+ hub->notif.event.mask = desc->event.mask; -+ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ hub->connect_delay = msecs_to_jiffies(desc->connect_delay_ms); -+ hub->ops.get_state = desc->ops.get_state; -+ -+ INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); -+ -+ ssam_device_set_drvdata(sdev, hub); -+ -+ status = ssam_device_notifier_register(sdev, &hub->notif); -+ if (status) -+ return status; -+ -+ schedule_delayed_work(&hub->update_work, 0); -+ return 0; -+} -+ -+static void ssam_hub_remove(struct ssam_device *sdev) -+{ -+ struct ssam_hub *hub = ssam_device_get_drvdata(sdev); -+ -+ ssam_device_notifier_unregister(sdev, &hub->notif); -+ cancel_delayed_work_sync(&hub->update_work); -+ ssam_remove_clients(&sdev->dev); -+} -+ -+ -+/* -- SSAM base-subsystem hub driver. --------------------------------------- */ -+ -+/* -+ * Some devices (especially battery) may need a bit of time to be fully usable -+ * after being (re-)connected. This delay has been determined via -+ * experimentation. -+ */ -+#define SSAM_BASE_UPDATE_CONNECT_DELAY 2500 -+ -+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { -+ .target_category = SSAM_SSH_TC_BAS, -+ .target_id = 0x01, -+ .command_id = 0x0d, -+ .instance_id = 0x00, -+}); -+ -+#define SSAM_BAS_OPMODE_TABLET 0x00 -+#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c -+ -+static int ssam_base_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) -+{ -+ u8 opmode; -+ int status; -+ -+ status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); -+ return status; -+ } -+ -+ if (opmode != SSAM_BAS_OPMODE_TABLET) -+ *state = SSAM_HUB_CONNECTED; -+ else -+ *state = SSAM_HUB_DISCONNECTED; -+ -+ return 0; -+} -+ -+static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -+ -+ if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) -+ return 0; -+ -+ if (event->length < 1) { -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ ssam_hub_update(hub, event->data[0]); -+ -+ /* -+ * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -+ * consumed by the detachment system driver. We're just a (more or less) -+ * silent observer. -+ */ -+ return 0; -+} -+ -+static const struct ssam_hub_desc base_hub = { -+ .event = { -+ .reg = SSAM_EVENT_REGISTRY_SAM, -+ .id = { -+ .target_category = SSAM_SSH_TC_BAS, -+ .instance = 0, -+ }, -+ .mask = SSAM_EVENT_MASK_NONE, -+ }, -+ .ops = { -+ .notify = ssam_base_hub_notif, -+ .get_state = ssam_base_hub_query_state, -+ }, -+ .connect_delay_ms = SSAM_BASE_UPDATE_CONNECT_DELAY, -+}; -+ -+ -+/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ -+ -+/* -+ * Some devices may need a bit of time to be fully usable after being -+ * (re-)connected. This delay has been determined via experimentation. -+ */ -+#define SSAM_KIP_UPDATE_CONNECT_DELAY 250 -+ -+#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c -+ -+SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_query_state, u8, { -+ .target_category = SSAM_SSH_TC_KIP, -+ .target_id = 0x01, -+ .command_id = 0x2c, -+ .instance_id = 0x00, -+}); -+ -+static int ssam_kip_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) -+{ -+ int status; -+ u8 connected; -+ -+ status = ssam_retry(__ssam_kip_query_state, hub->sdev->ctrl, &connected); -+ if (status < 0) { -+ dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); -+ return status; -+ } -+ -+ *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; -+ return 0; -+} -+ -+static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -+ -+ if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) -+ return 0; /* Return "unhandled". */ -+ -+ if (event->length < 1) { -+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -+ return 0; -+ } -+ -+ ssam_hub_update(hub, event->data[0]); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static const struct ssam_hub_desc kip_hub = { -+ .event = { -+ .reg = SSAM_EVENT_REGISTRY_SAM, -+ .id = { -+ .target_category = SSAM_SSH_TC_KIP, -+ .instance = 0, -+ }, -+ .mask = SSAM_EVENT_MASK_TARGET, -+ }, -+ .ops = { -+ .notify = ssam_kip_hub_notif, -+ .get_state = ssam_kip_hub_query_state, -+ }, -+ .connect_delay_ms = SSAM_KIP_UPDATE_CONNECT_DELAY, -+}; -+ -+ -+/* -- Driver registration. -------------------------------------------------- */ -+ -+static const struct ssam_device_id ssam_hub_match[] = { -+ { SSAM_VDEV(HUB, 0x01, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, -+ { SSAM_VDEV(HUB, 0x02, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, -+ { } -+}; -+MODULE_DEVICE_TABLE(ssam, ssam_hub_match); -+ -+static struct ssam_device_driver ssam_subsystem_hub_driver = { -+ .probe = ssam_hub_probe, -+ .remove = ssam_hub_remove, -+ .match_table = ssam_hub_match, -+ .driver = { -+ .name = "surface_aggregator_subsystem_hub", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ .pm = &ssam_hub_pm_ops, -+ }, -+}; -+module_ssam_device_driver(ssam_subsystem_hub_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Subsystem device hub driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index eb88021c208c..76dc9c4f108e 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -11,14 +11,11 @@ - - #include - #include --#include - #include - #include - #include - #include --#include - --#include - #include - - -@@ -286,335 +283,6 @@ static const struct software_node *ssam_node_group_sp8[] = { - }; - - --/* -- SSAM generic subsystem hub driver framework. -------------------------- */ -- --enum ssam_hub_state { -- SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ -- SSAM_HUB_CONNECTED, -- SSAM_HUB_DISCONNECTED, --}; -- --enum ssam_hub_flags { -- SSAM_HUB_HOT_REMOVED, --}; -- --struct ssam_hub { -- struct ssam_device *sdev; -- -- enum ssam_hub_state state; -- unsigned long flags; -- -- struct delayed_work update_work; -- unsigned long connect_delay; -- -- struct ssam_event_notifier notif; -- -- int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); --}; -- --static void ssam_hub_update_workfn(struct work_struct *work) --{ -- struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); -- enum ssam_hub_state state; -- int status = 0; -- -- status = hub->get_state(hub, &state); -- if (status) -- return; -- -- /* -- * There is a small possibility that hub devices were hot-removed and -- * re-added before we were able to remove them here. In that case, both -- * the state returned by get_state() and the state of the hub will -- * equal SSAM_HUB_CONNECTED and we would bail early below, which would -- * leave child devices without proper (re-)initialization and the -- * hot-remove flag set. -- * -- * Therefore, we check whether devices have been hot-removed via an -- * additional flag on the hub and, in this case, override the returned -- * hub state. In case of a missed disconnect (i.e. get_state returned -- * "connected"), we further need to re-schedule this work (with the -- * appropriate delay) as the actual connect work submission might have -- * been merged with this one. -- * -- * This then leads to one of two cases: Either we submit an unnecessary -- * work item (which will get ignored via either the queue or the state -- * checks) or, in the unlikely case that the work is actually required, -- * double the normal connect delay. -- */ -- if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { -- if (state == SSAM_HUB_CONNECTED) -- schedule_delayed_work(&hub->update_work, hub->connect_delay); -- -- state = SSAM_HUB_DISCONNECTED; -- } -- -- if (hub->state == state) -- return; -- hub->state = state; -- -- if (hub->state == SSAM_HUB_CONNECTED) -- status = ssam_device_register_clients(hub->sdev); -- else -- ssam_remove_clients(&hub->sdev->dev); -- -- if (status) -- dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); --} -- --static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) --{ -- struct ssam_device *sdev = to_ssam_device(dev); -- -- if (is_ssam_device(dev)) -- ssam_device_mark_hot_removed(sdev); -- -- return 0; --} -- --static void ssam_hub_update(struct ssam_hub *hub, bool connected) --{ -- unsigned long delay; -- -- /* Mark devices as hot-removed before we remove any. */ -- if (!connected) { -- set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); -- device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); -- } -- -- /* -- * Delay update when the base/keyboard cover is being connected to give -- * devices/EC some time to set up. -- */ -- delay = connected ? hub->connect_delay : 0; -- -- schedule_delayed_work(&hub->update_work, delay); --} -- --static int __maybe_unused ssam_hub_resume(struct device *dev) --{ -- struct ssam_hub *hub = dev_get_drvdata(dev); -- -- schedule_delayed_work(&hub->update_work, 0); -- return 0; --} --static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); -- --static int ssam_hub_setup(struct ssam_device *sdev, struct ssam_hub *hub) --{ -- int status; -- -- hub->sdev = sdev; -- hub->state = SSAM_HUB_UNINITIALIZED; -- -- INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); -- -- ssam_device_set_drvdata(sdev, hub); -- -- status = ssam_device_notifier_register(sdev, &hub->notif); -- if (status) -- return status; -- -- schedule_delayed_work(&hub->update_work, 0); -- return 0; --} -- --static void ssam_hub_remove(struct ssam_device *sdev) --{ -- struct ssam_hub *hub = ssam_device_get_drvdata(sdev); -- -- ssam_device_notifier_unregister(sdev, &hub->notif); -- cancel_delayed_work_sync(&hub->update_work); -- ssam_remove_clients(&sdev->dev); --} -- -- --/* -- SSAM base-hub driver. ------------------------------------------------- */ -- --/* -- * Some devices (especially battery) may need a bit of time to be fully usable -- * after being (re-)connected. This delay has been determined via -- * experimentation. -- */ --#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500) -- --SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { -- .target_category = SSAM_SSH_TC_BAS, -- .target_id = 0x01, -- .command_id = 0x0d, -- .instance_id = 0x00, --}); -- --#define SSAM_BAS_OPMODE_TABLET 0x00 --#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c -- --static int ssam_base_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) --{ -- u8 opmode; -- int status; -- -- status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode); -- if (status < 0) { -- dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status); -- return status; -- } -- -- if (opmode != SSAM_BAS_OPMODE_TABLET) -- *state = SSAM_HUB_CONNECTED; -- else -- *state = SSAM_HUB_DISCONNECTED; -- -- return 0; --} -- --static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) --{ -- struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -- -- if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) -- return 0; -- -- if (event->length < 1) { -- dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -- return 0; -- } -- -- ssam_hub_update(hub, event->data[0]); -- -- /* -- * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and -- * consumed by the detachment system driver. We're just a (more or less) -- * silent observer. -- */ -- return 0; --} -- --static int ssam_base_hub_probe(struct ssam_device *sdev) --{ -- struct ssam_hub *hub; -- -- hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -- if (!hub) -- return -ENOMEM; -- -- hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -- hub->notif.base.fn = ssam_base_hub_notif; -- hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -- hub->notif.event.id.target_category = SSAM_SSH_TC_BAS, -- hub->notif.event.id.instance = 0, -- hub->notif.event.mask = SSAM_EVENT_MASK_NONE; -- hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -- -- hub->connect_delay = SSAM_BASE_UPDATE_CONNECT_DELAY; -- hub->get_state = ssam_base_hub_query_state; -- -- return ssam_hub_setup(sdev, hub); --} -- --static const struct ssam_device_id ssam_base_hub_match[] = { -- { SSAM_VDEV(HUB, 0x02, SSAM_SSH_TC_BAS, 0x00) }, -- { }, --}; -- --static struct ssam_device_driver ssam_base_hub_driver = { -- .probe = ssam_base_hub_probe, -- .remove = ssam_hub_remove, -- .match_table = ssam_base_hub_match, -- .driver = { -- .name = "surface_aggregator_base_hub", -- .probe_type = PROBE_PREFER_ASYNCHRONOUS, -- .pm = &ssam_hub_pm_ops, -- }, --}; -- -- --/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ -- --/* -- * Some devices may need a bit of time to be fully usable after being -- * (re-)connected. This delay has been determined via experimentation. -- */ --#define SSAM_KIP_UPDATE_CONNECT_DELAY msecs_to_jiffies(250) -- --#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c -- --SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_connection_state, u8, { -- .target_category = SSAM_SSH_TC_KIP, -- .target_id = 0x01, -- .command_id = 0x2c, -- .instance_id = 0x00, --}); -- --static int ssam_kip_get_connection_state(struct ssam_hub *hub, enum ssam_hub_state *state) --{ -- int status; -- u8 connected; -- -- status = ssam_retry(__ssam_kip_get_connection_state, hub->sdev->ctrl, &connected); -- if (status < 0) { -- dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); -- return status; -- } -- -- *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; -- return 0; --} -- --static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) --{ -- struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); -- -- if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) -- return 0; /* Return "unhandled". */ -- -- if (event->length < 1) { -- dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); -- return 0; -- } -- -- ssam_hub_update(hub, event->data[0]); -- return SSAM_NOTIF_HANDLED; --} -- --static int ssam_kip_hub_probe(struct ssam_device *sdev) --{ -- struct ssam_hub *hub; -- -- hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); -- if (!hub) -- return -ENOMEM; -- -- hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ -- hub->notif.base.fn = ssam_kip_hub_notif; -- hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -- hub->notif.event.id.target_category = SSAM_SSH_TC_KIP, -- hub->notif.event.id.instance = 0, -- hub->notif.event.mask = SSAM_EVENT_MASK_TARGET; -- hub->notif.event.flags = SSAM_EVENT_SEQUENCED; -- -- hub->connect_delay = SSAM_KIP_UPDATE_CONNECT_DELAY; -- hub->get_state = ssam_kip_get_connection_state; -- -- return ssam_hub_setup(sdev, hub); --} -- --static const struct ssam_device_id ssam_kip_hub_match[] = { -- { SSAM_VDEV(HUB, 0x01, SSAM_SSH_TC_KIP, 0x00) }, -- { }, --}; -- --static struct ssam_device_driver ssam_kip_hub_driver = { -- .probe = ssam_kip_hub_probe, -- .remove = ssam_hub_remove, -- .match_table = ssam_kip_hub_match, -- .driver = { -- .name = "surface_kip_hub", -- .probe_type = PROBE_PREFER_ASYNCHRONOUS, -- .pm = &ssam_hub_pm_ops, -- }, --}; -- -- - /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ - - static const struct acpi_device_id ssam_platform_hub_match[] = { -@@ -730,44 +398,7 @@ static struct platform_driver ssam_platform_hub_driver = { - .probe_type = PROBE_PREFER_ASYNCHRONOUS, - }, - }; -- -- --/* -- Module initialization. ------------------------------------------------ */ -- --static int __init ssam_device_hub_init(void) --{ -- int status; -- -- status = platform_driver_register(&ssam_platform_hub_driver); -- if (status) -- goto err_platform; -- -- status = ssam_device_driver_register(&ssam_base_hub_driver); -- if (status) -- goto err_base; -- -- status = ssam_device_driver_register(&ssam_kip_hub_driver); -- if (status) -- goto err_kip; -- -- return 0; -- --err_kip: -- ssam_device_driver_unregister(&ssam_base_hub_driver); --err_base: -- platform_driver_unregister(&ssam_platform_hub_driver); --err_platform: -- return status; --} --module_init(ssam_device_hub_init); -- --static void __exit ssam_device_hub_exit(void) --{ -- ssam_device_driver_unregister(&ssam_kip_hub_driver); -- ssam_device_driver_unregister(&ssam_base_hub_driver); -- platform_driver_unregister(&ssam_platform_hub_driver); --} --module_exit(ssam_device_hub_exit); -+module_platform_driver(ssam_platform_hub_driver); - - MODULE_AUTHOR("Maximilian Luz "); - MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); --- -2.38.0 - -From cfe52b72184fcf8f375a310034f27c3971277399 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 24 Jun 2022 22:58:00 +0200 -Subject: [PATCH] platform/surface: Update copyright year of various drivers - -Update the copyright of various Surface drivers to the current year. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220624205800.1355621-4-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/aggregator/Kconfig | 2 +- - drivers/platform/surface/aggregator/Makefile | 2 +- - drivers/platform/surface/aggregator/bus.c | 2 +- - drivers/platform/surface/aggregator/bus.h | 2 +- - drivers/platform/surface/aggregator/controller.c | 2 +- - drivers/platform/surface/aggregator/controller.h | 2 +- - drivers/platform/surface/aggregator/core.c | 2 +- - drivers/platform/surface/aggregator/ssh_msgb.h | 2 +- - drivers/platform/surface/aggregator/ssh_packet_layer.c | 2 +- - drivers/platform/surface/aggregator/ssh_packet_layer.h | 2 +- - drivers/platform/surface/aggregator/ssh_parser.c | 2 +- - drivers/platform/surface/aggregator/ssh_parser.h | 2 +- - drivers/platform/surface/aggregator/ssh_request_layer.c | 2 +- - drivers/platform/surface/aggregator/ssh_request_layer.h | 2 +- - drivers/platform/surface/aggregator/trace.h | 2 +- - drivers/platform/surface/surface_acpi_notify.c | 2 +- - drivers/platform/surface/surface_aggregator_cdev.c | 2 +- - drivers/platform/surface/surface_aggregator_registry.c | 2 +- - drivers/platform/surface/surface_dtx.c | 2 +- - drivers/platform/surface/surface_gpe.c | 2 +- - drivers/platform/surface/surface_hotplug.c | 2 +- - drivers/platform/surface/surface_platform_profile.c | 2 +- - 22 files changed, 22 insertions(+), 22 deletions(-) - -diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig -index cab020324256..c114f9dd5fe1 100644 ---- a/drivers/platform/surface/aggregator/Kconfig -+++ b/drivers/platform/surface/aggregator/Kconfig -@@ -1,5 +1,5 @@ - # SPDX-License-Identifier: GPL-2.0+ --# Copyright (C) 2019-2021 Maximilian Luz -+# Copyright (C) 2019-2022 Maximilian Luz - - menuconfig SURFACE_AGGREGATOR - tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" -diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile -index c0d550eda5cd..fdf664a217f9 100644 ---- a/drivers/platform/surface/aggregator/Makefile -+++ b/drivers/platform/surface/aggregator/Makefile -@@ -1,5 +1,5 @@ - # SPDX-License-Identifier: GPL-2.0+ --# Copyright (C) 2019-2021 Maximilian Luz -+# Copyright (C) 2019-2022 Maximilian Luz - - # For include/trace/define_trace.h to include trace.h - CFLAGS_core.o = -I$(src) -diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c -index e0b0381a2834..de539938896e 100644 ---- a/drivers/platform/surface/aggregator/bus.c -+++ b/drivers/platform/surface/aggregator/bus.c -@@ -2,7 +2,7 @@ - /* - * Surface System Aggregator Module bus and device integration. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h -index 6964ee84e79c..5b4dbf21906c 100644 ---- a/drivers/platform/surface/aggregator/bus.h -+++ b/drivers/platform/surface/aggregator/bus.h -@@ -2,7 +2,7 @@ - /* - * Surface System Aggregator Module bus and device integration. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_BUS_H -diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c -index 6de834b52b63..43e765199137 100644 ---- a/drivers/platform/surface/aggregator/controller.c -+++ b/drivers/platform/surface/aggregator/controller.c -@@ -2,7 +2,7 @@ - /* - * Main SSAM/SSH controller structure and functionality. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h -index a0963c3562ff..f0d987abc51e 100644 ---- a/drivers/platform/surface/aggregator/controller.h -+++ b/drivers/platform/surface/aggregator/controller.h -@@ -2,7 +2,7 @@ - /* - * Main SSAM/SSH controller structure and functionality. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_CONTROLLER_H -diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c -index a62c5dfe42d6..1a6373dea109 100644 ---- a/drivers/platform/surface/aggregator/core.c -+++ b/drivers/platform/surface/aggregator/core.c -@@ -7,7 +7,7 @@ - * Handles communication via requests as well as enabling, disabling, and - * relaying of events. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h -index e562958ffdf0..f3ecad92eefd 100644 ---- a/drivers/platform/surface/aggregator/ssh_msgb.h -+++ b/drivers/platform/surface/aggregator/ssh_msgb.h -@@ -2,7 +2,7 @@ - /* - * SSH message builder functions. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c -index 8a4451c1ffe5..6748fe4ac5d5 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c -@@ -2,7 +2,7 @@ - /* - * SSH packet transport layer. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h -index 2eb329f0b91a..64633522f971 100644 ---- a/drivers/platform/surface/aggregator/ssh_packet_layer.h -+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h -@@ -2,7 +2,7 @@ - /* - * SSH packet transport layer. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H -diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c -index b77912f8f13b..a6f668694365 100644 ---- a/drivers/platform/surface/aggregator/ssh_parser.c -+++ b/drivers/platform/surface/aggregator/ssh_parser.c -@@ -2,7 +2,7 @@ - /* - * SSH message parser. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h -index 3bd6e180fd16..801d8fa69fb5 100644 ---- a/drivers/platform/surface/aggregator/ssh_parser.h -+++ b/drivers/platform/surface/aggregator/ssh_parser.h -@@ -2,7 +2,7 @@ - /* - * SSH message parser. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c -index 790f7f0eee98..f5565570f16c 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.c -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.c -@@ -2,7 +2,7 @@ - /* - * SSH request transport layer. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h -index 9c3cbae2d4bd..4e387a031351 100644 ---- a/drivers/platform/surface/aggregator/ssh_request_layer.h -+++ b/drivers/platform/surface/aggregator/ssh_request_layer.h -@@ -2,7 +2,7 @@ - /* - * SSH request transport layer. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H -diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h -index cc9e73fbc18e..2a2c17771d01 100644 ---- a/drivers/platform/surface/aggregator/trace.h -+++ b/drivers/platform/surface/aggregator/trace.h -@@ -2,7 +2,7 @@ - /* - * Trace points for SSAM/SSH. - * -- * Copyright (C) 2020-2021 Maximilian Luz -+ * Copyright (C) 2020-2022 Maximilian Luz - */ - - #undef TRACE_SYSTEM -diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c -index c0e12f0b9b79..44e317970557 100644 ---- a/drivers/platform/surface/surface_acpi_notify.c -+++ b/drivers/platform/surface/surface_acpi_notify.c -@@ -8,7 +8,7 @@ - * notifications sent from ACPI via the SAN interface by providing them to any - * registered external driver. - * -- * Copyright (C) 2019-2020 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c -index 30fb50fde450..492c82e69182 100644 ---- a/drivers/platform/surface/surface_aggregator_cdev.c -+++ b/drivers/platform/surface/surface_aggregator_cdev.c -@@ -3,7 +3,7 @@ - * Provides user-space access to the SSAM EC via the /dev/surface/aggregator - * misc device. Intended for debugging and development. - * -- * Copyright (C) 2020-2021 Maximilian Luz -+ * Copyright (C) 2020-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 76dc9c4f108e..93ab62eb393d 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -6,7 +6,7 @@ - * cannot be auto-detected. Provides device-hubs and performs instantiation - * for these devices. - * -- * Copyright (C) 2020-2021 Maximilian Luz -+ * Copyright (C) 2020-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c -index 1203b9a82993..ed36944467f9 100644 ---- a/drivers/platform/surface/surface_dtx.c -+++ b/drivers/platform/surface/surface_dtx.c -@@ -8,7 +8,7 @@ - * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in - * use), or request detachment via user-space. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c -index ec66fde28e75..27365cbe1ee9 100644 ---- a/drivers/platform/surface/surface_gpe.c -+++ b/drivers/platform/surface/surface_gpe.c -@@ -4,7 +4,7 @@ - * properly configuring the respective GPEs. Required for wakeup via lid on - * newer Intel-based Microsoft Surface devices. - * -- * Copyright (C) 2020 Maximilian Luz -+ * Copyright (C) 2020-2022 Maximilian Luz - */ - - #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -diff --git a/drivers/platform/surface/surface_hotplug.c b/drivers/platform/surface/surface_hotplug.c -index cfcc15cfbacb..f004a2495201 100644 ---- a/drivers/platform/surface/surface_hotplug.c -+++ b/drivers/platform/surface/surface_hotplug.c -@@ -10,7 +10,7 @@ - * Event signaling is handled via ACPI, which will generate the appropriate - * device-check notifications to be picked up by the PCIe hot-plug driver. - * -- * Copyright (C) 2019-2021 Maximilian Luz -+ * Copyright (C) 2019-2022 Maximilian Luz - */ - - #include -diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c -index 6373d3b5eb7f..fbf2e11fd6ce 100644 ---- a/drivers/platform/surface/surface_platform_profile.c -+++ b/drivers/platform/surface/surface_platform_profile.c -@@ -3,7 +3,7 @@ - * Surface Platform Profile / Performance Mode driver for Surface System - * Aggregator Module (thermal subsystem). - * -- * Copyright (C) 2021 Maximilian Luz -+ * Copyright (C) 2021-2022 Maximilian Luz - */ - - #include --- -2.38.0 - -From b86dd78758be4a4c4800a43f98555aa7c687d7dd Mon Sep 17 00:00:00 2001 -From: Lukas Bulwahn -Date: Wed, 13 Jul 2022 06:09:16 +0200 -Subject: [PATCH] MAINTAINERS: repair file entry in MICROSOFT SURFACE - AGGREGATOR TABLET-MODE SWITCH - -Commit 9f794056db5b ("platform/surface: Add KIP/POS tablet-mode switch -driver") adds the section MICROSOFT SURFACE AGGREGATOR TABLET-MODE SWITCH -with a file entry, but the file that is added with this commit is actually -named slightly differently. - - file entry name: drivers/platform/surface/surface_aggregator_tablet_switch.c - added file name: drivers/platform/surface/surface_aggregator_tabletsw.c - -Hence, ./scripts/get_maintainer.pl --self-test=patterns complains about a -broken reference. - -Repair this file entry to the actual file name added with the commit above. - -Fixes: 9f794056db5b ("platform/surface: Add KIP/POS tablet-mode switch driver") -Signed-off-by: Lukas Bulwahn -Reviewed-by: Andy Shevchenko -Reviewed-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220713040916.1767-1-lukas.bulwahn@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - MAINTAINERS | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/MAINTAINERS b/MAINTAINERS -index 55e80354a097..6772c9d0eccc 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -13303,7 +13303,7 @@ MICROSOFT SURFACE AGGREGATOR TABLET-MODE SWITCH - M: Maximilian Luz - L: platform-driver-x86@vger.kernel.org - S: Maintained --F: drivers/platform/surface/surface_aggregator_tablet_switch.c -+F: drivers/platform/surface/surface_aggregator_tabletsw.c - - MICROSOFT SURFACE BATTERY AND AC DRIVERS - M: Maximilian Luz --- -2.38.0 - -From 04b2cb4648d0fb003fee6c2df03e94390d6ecd45 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sun, 17 Jul 2022 14:07:35 +0200 -Subject: [PATCH] platform/surface: tabletsw: Fix __le32 integer access - -The sources.count field is a __le32 inside a packed struct. So use the -proper functions to access it. - -Reported-by: kernel test robot -Fixes: 9f794056db5b ("platform/surface: Add KIP/POS tablet-mode switch driver") -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220717120735.2052160-1-luzmaximilian@gmail.com -Reviewed-by: Hans de Goede -Signed-off-by: Hans de Goede -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_tabletsw.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c -index 596ca6c80681..27d95a6a7851 100644 ---- a/drivers/platform/surface/surface_aggregator_tabletsw.c -+++ b/drivers/platform/surface/surface_aggregator_tabletsw.c -@@ -410,7 +410,7 @@ static int ssam_pos_get_source(struct ssam_tablet_sw *sw, u32 *source_id) - if (status) - return status; - -- if (sources.count == 0) { -+ if (get_unaligned_le32(&sources.count) == 0) { - dev_err(&sw->sdev->dev, "no posture sources found\n"); - return -ENODEV; - } -@@ -422,7 +422,7 @@ static int ssam_pos_get_source(struct ssam_tablet_sw *sw, u32 *source_id) - * is a device that provides multiple sources, at which point we can - * then try to figure out how to handle them. - */ -- WARN_ON(sources.count > 1); -+ WARN_ON(get_unaligned_le32(&sources.count) > 1); - - *source_id = get_unaligned_le32(&sources.id[0]); - return 0; --- -2.38.0 - -From e7b0023693fc783a53c797b1b5144891a5cc2496 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jun 2022 20:42:00 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Rename HID device - nodes based on their function - -Rename HID device nodes based on their function. In particular, these -are nodes for firmware updates via the CFU mechanism (component firmware -update), HID based sensors, and a USB-C UCSI client. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 18 +++++++++--------- - 1 file changed, 9 insertions(+), 9 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 93ab62eb393d..7d82398f55b1 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -104,14 +104,14 @@ static const struct software_node ssam_node_hid_tid1_touchpad = { - .parent = &ssam_node_root, - }; - --/* HID device instance 6 (TID1, unknown HID device). */ --static const struct software_node ssam_node_hid_tid1_iid6 = { -+/* HID device instance 6 (TID1, HID sensor collection). */ -+static const struct software_node ssam_node_hid_tid1_sensors = { - .name = "ssam:01:15:01:06:00", - .parent = &ssam_node_root, - }; - --/* HID device instance 7 (TID1, unknown HID device). */ --static const struct software_node ssam_node_hid_tid1_iid7 = { -+/* HID device instance 7 (TID1, UCM UCSI HID client). */ -+static const struct software_node ssam_node_hid_tid1_ucm_ucsi = { - .name = "ssam:01:15:01:07:00", - .parent = &ssam_node_root, - }; -@@ -182,8 +182,8 @@ static const struct software_node ssam_node_hid_kip_touchpad = { - .parent = &ssam_node_hub_kip, - }; - --/* HID device instance 5 (KIP hub, unknown HID device). */ --static const struct software_node ssam_node_hid_kip_iid5 = { -+/* HID device instance 5 (KIP hub, type-cover firmware update). */ -+static const struct software_node ssam_node_hid_kip_fwupd = { - .name = "ssam:01:15:02:05:00", - .parent = &ssam_node_hub_kip, - }; -@@ -244,8 +244,8 @@ static const struct software_node *ssam_node_group_sls[] = { - &ssam_node_hid_tid1_keyboard, - &ssam_node_hid_tid1_penstash, - &ssam_node_hid_tid1_touchpad, -- &ssam_node_hid_tid1_iid6, -- &ssam_node_hid_tid1_iid7, -+ &ssam_node_hid_tid1_sensors, -+ &ssam_node_hid_tid1_ucm_ucsi, - &ssam_node_hid_tid1_sysctrl, - NULL, - }; -@@ -278,7 +278,7 @@ static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_hid_kip_keyboard, - &ssam_node_hid_kip_penstash, - &ssam_node_hid_kip_touchpad, -- &ssam_node_hid_kip_iid5, -+ &ssam_node_hid_kip_fwupd, - NULL, - }; - --- -2.38.0 - -From e731df8a45ea7a0b5e88ac114b1ace39336238f3 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jun 2022 20:52:47 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Rename HID device - nodes based on new findings - -On Windows, the HID devices with target ID 1 are grouped as "Surface Hot -Plug - SAM". Rename their device nodes in the registry to reflect that -and update the comments accordingly. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - .../surface/surface_aggregator_registry.c | 36 +++++++++---------- - 1 file changed, 18 insertions(+), 18 deletions(-) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 7d82398f55b1..9970f89b1411 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -86,38 +86,38 @@ static const struct software_node ssam_node_bas_dtx = { - .parent = &ssam_node_root, - }; - --/* HID keyboard (TID1). */ --static const struct software_node ssam_node_hid_tid1_keyboard = { -+/* HID keyboard (SAM, TID=1). */ -+static const struct software_node ssam_node_hid_sam_keyboard = { - .name = "ssam:01:15:01:01:00", - .parent = &ssam_node_root, - }; - --/* HID pen stash (TID1; pen taken / stashed away evens). */ --static const struct software_node ssam_node_hid_tid1_penstash = { -+/* HID pen stash (SAM, TID=1; pen taken / stashed away evens). */ -+static const struct software_node ssam_node_hid_sam_penstash = { - .name = "ssam:01:15:01:02:00", - .parent = &ssam_node_root, - }; - --/* HID touchpad (TID1). */ --static const struct software_node ssam_node_hid_tid1_touchpad = { -+/* HID touchpad (SAM, TID=1). */ -+static const struct software_node ssam_node_hid_sam_touchpad = { - .name = "ssam:01:15:01:03:00", - .parent = &ssam_node_root, - }; - --/* HID device instance 6 (TID1, HID sensor collection). */ --static const struct software_node ssam_node_hid_tid1_sensors = { -+/* HID device instance 6 (SAM, TID=1, HID sensor collection). */ -+static const struct software_node ssam_node_hid_sam_sensors = { - .name = "ssam:01:15:01:06:00", - .parent = &ssam_node_root, - }; - --/* HID device instance 7 (TID1, UCM UCSI HID client). */ --static const struct software_node ssam_node_hid_tid1_ucm_ucsi = { -+/* HID device instance 7 (SAM, TID=1, UCM UCSI HID client). */ -+static const struct software_node ssam_node_hid_sam_ucm_ucsi = { - .name = "ssam:01:15:01:07:00", - .parent = &ssam_node_root, - }; - --/* HID system controls (TID1). */ --static const struct software_node ssam_node_hid_tid1_sysctrl = { -+/* HID system controls (SAM, TID=1). */ -+static const struct software_node ssam_node_hid_sam_sysctrl = { - .name = "ssam:01:15:01:08:00", - .parent = &ssam_node_root, - }; -@@ -241,12 +241,12 @@ static const struct software_node *ssam_node_group_sls[] = { - &ssam_node_bat_main, - &ssam_node_tmp_pprof, - &ssam_node_pos_tablet_switch, -- &ssam_node_hid_tid1_keyboard, -- &ssam_node_hid_tid1_penstash, -- &ssam_node_hid_tid1_touchpad, -- &ssam_node_hid_tid1_sensors, -- &ssam_node_hid_tid1_ucm_ucsi, -- &ssam_node_hid_tid1_sysctrl, -+ &ssam_node_hid_sam_keyboard, -+ &ssam_node_hid_sam_penstash, -+ &ssam_node_hid_sam_touchpad, -+ &ssam_node_hid_sam_sensors, -+ &ssam_node_hid_sam_ucm_ucsi, -+ &ssam_node_hid_sam_sysctrl, - NULL, - }; - --- -2.38.0 - -From 00885f19dee8c555dbc5b7e8a739e38b15a94926 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jun 2022 20:54:59 +0200 -Subject: [PATCH] platform/surface: aggregator_registry: Add HID devices for - sensors and UCSI client to SP8 - -Add software nodes for the HID sensor collection and the UCM UCSI HID -client to the Surface Pro 8. In contrast to the type-cover devices, -these devices are directly attached to the SAM controller, without any -hub. - -This enables support for HID-based sensors, including the ones used for -automatic screen rotation, on the Surface Pro 8. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam ---- - drivers/platform/surface/surface_aggregator_registry.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c -index 9970f89b1411..585911020cea 100644 ---- a/drivers/platform/surface/surface_aggregator_registry.c -+++ b/drivers/platform/surface/surface_aggregator_registry.c -@@ -279,6 +279,8 @@ static const struct software_node *ssam_node_group_sp8[] = { - &ssam_node_hid_kip_penstash, - &ssam_node_hid_kip_touchpad, - &ssam_node_hid_kip_fwupd, -+ &ssam_node_hid_sam_sensors, -+ &ssam_node_hid_sam_ucm_ucsi, - NULL, - }; - --- -2.38.0 - diff --git a/patches/5.19/0006-surface-sam-over-hid.patch b/patches/5.19/0006-surface-sam-over-hid.patch deleted file mode 100644 index 8b82e0bcd..000000000 --- a/patches/5.19/0006-surface-sam-over-hid.patch +++ /dev/null @@ -1,335 +0,0 @@ -From 0f0ccb9eefca35e2448060a3b014edcec0fcb2c4 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 25 Jul 2020 17:19:53 +0200 -Subject: [PATCH] i2c: acpi: Implement RawBytes read access - -Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C -device via a generic serial bus operation region and RawBytes read -access. On the Surface Book 1, this access is required to turn on (and -off) the discrete GPU. - -Multiple things are to note here: - -a) The RawBytes access is device/driver dependent. The ACPI - specification states: - - > Raw accesses assume that the writer has knowledge of the bus that - > the access is made over and the device that is being accessed. The - > protocol may only ensure that the buffer is transmitted to the - > appropriate driver, but the driver must be able to interpret the - > buffer to communicate to a register. - - Thus this implementation may likely not work on other devices - accessing I2C via the RawBytes accessor type. - -b) The MSHW0030 I2C device is an HID-over-I2C device which seems to - serve multiple functions: - - 1. It is the main access point for the legacy-type Surface Aggregator - Module (also referred to as SAM-over-HID, as opposed to the newer - SAM-over-SSH/UART). It has currently not been determined on how - support for the legacy SAM should be implemented. Likely via a - custom HID driver. - - 2. It seems to serve as the HID device for the Integrated Sensor Hub. - This might complicate matters with regards to implementing a - SAM-over-HID driver required by legacy SAM. - -In light of this, the simplest approach has been chosen for now. -However, it may make more sense regarding breakage and compatibility to -either provide functionality for replacing or enhancing the default -operation region handler via some additional API functions, or even to -completely blacklist MSHW0030 from the I2C core and provide a custom -driver for it. - -Replacing/enhancing the default operation region handler would, however, -either require some sort of secondary driver and access point for it, -from which the new API functions would be called and the new handler -(part) would be installed, or hard-coding them via some sort of -quirk-like interface into the I2C core. - -Signed-off-by: Maximilian Luz -Patchset: surface-sam-over-hid ---- - drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ - 1 file changed, 35 insertions(+) - -diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c -index 08b561f0709d..d7c397bce0f0 100644 ---- a/drivers/i2c/i2c-core-acpi.c -+++ b/drivers/i2c/i2c-core-acpi.c -@@ -619,6 +619,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, - return (ret == 1) ? 0 : -EIO; - } - -+static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, -+ u8 *data, u8 data_len) -+{ -+ struct i2c_msg msgs[1]; -+ int ret = AE_OK; -+ -+ msgs[0].addr = client->addr; -+ msgs[0].flags = client->flags; -+ msgs[0].len = data_len + 1; -+ msgs[0].buf = data; -+ -+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); -+ -+ if (ret < 0) { -+ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); -+ return ret; -+ } -+ -+ /* 1 transfer must have completed successfully */ -+ return (ret == 1) ? 0 : -EIO; -+} -+ - static acpi_status - i2c_acpi_space_handler(u32 function, acpi_physical_address command, - u32 bits, u64 *value64, -@@ -720,6 +742,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, - } - break; - -+ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: -+ if (action == ACPI_READ) { -+ dev_warn(&adapter->dev, -+ "protocol 0x%02x not supported for client 0x%02x\n", -+ accessor_type, client->addr); -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } else { -+ status = acpi_gsb_i2c_write_raw_bytes(client, -+ gsb->data, info->access_length); -+ } -+ break; -+ - default: - dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", - accessor_type, client->addr); --- -2.38.0 - -From 0f01ea63e27f4c9596a7cc3f357414ae5246e850 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 13 Feb 2021 16:41:18 +0100 -Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch - -Add driver exposing the discrete GPU power-switch of the Microsoft -Surface Book 1 to user-space. - -On the Surface Book 1, the dGPU power is controlled via the Surface -System Aggregator Module (SAM). The specific SAM-over-HID command for -this is exposed via ACPI. This module provides a simple driver exposing -the ACPI call via a sysfs parameter to user-space, so that users can -easily power-on/-off the dGPU. - -Patchset: surface-sam-over-hid ---- - drivers/platform/surface/Kconfig | 7 + - drivers/platform/surface/Makefile | 1 + - .../surface/surfacebook1_dgpu_switch.c | 162 ++++++++++++++++++ - 3 files changed, 170 insertions(+) - create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c - -diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig -index b629e82af97c..68656e8f309e 100644 ---- a/drivers/platform/surface/Kconfig -+++ b/drivers/platform/surface/Kconfig -@@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH - Select M or Y here, if you want to provide tablet-mode switch input - events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. - -+config SURFACE_BOOK1_DGPU_SWITCH -+ tristate "Surface Book 1 dGPU Switch Driver" -+ depends on SYSFS -+ help -+ This driver provides a sysfs switch to set the power-state of the -+ discrete GPU found on the Microsoft Surface Book 1. -+ - config SURFACE_DTX - tristate "Surface DTX (Detachment System) Driver" - depends on SURFACE_AGGREGATOR -diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile -index 53344330939b..7efcd0cdb532 100644 ---- a/drivers/platform/surface/Makefile -+++ b/drivers/platform/surface/Makefile -@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o - obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o -+obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c -new file mode 100644 -index 000000000000..8b816ed8f35c ---- /dev/null -+++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c -@@ -0,0 +1,162 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+ -+#include -+#include -+#include -+#include -+ -+ -+#ifdef pr_fmt -+#undef pr_fmt -+#endif -+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ -+ -+ -+static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, -+ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); -+ -+#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" -+#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" -+#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" -+ -+ -+static int sb1_dgpu_sw_dsmcall(void) -+{ -+ union acpi_object *ret; -+ acpi_handle handle; -+ acpi_status status; -+ -+ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); -+ if (status) -+ return -EINVAL; -+ -+ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); -+ if (!ret) -+ return -EINVAL; -+ -+ ACPI_FREE(ret); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgon(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGON: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-on dGPU via HGON\n"); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgof(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGOF: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-off dGPU via HGOF\n"); -+ return 0; -+} -+ -+ -+static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ int status, value; -+ -+ status = kstrtoint(buf, 0, &value); -+ if (status < 0) -+ return status; -+ -+ if (value != 1) -+ return -EINVAL; -+ -+ status = sb1_dgpu_sw_dsmcall(); -+ -+ return status < 0 ? status : len; -+} -+ -+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ bool power; -+ int status; -+ -+ status = kstrtobool(buf, &power); -+ if (status < 0) -+ return status; -+ -+ if (power) -+ status = sb1_dgpu_sw_hgon(); -+ else -+ status = sb1_dgpu_sw_hgof(); -+ -+ return status < 0 ? status : len; -+} -+ -+static DEVICE_ATTR_WO(dgpu_dsmcall); -+static DEVICE_ATTR_WO(dgpu_power); -+ -+static struct attribute *sb1_dgpu_sw_attrs[] = { -+ &dev_attr_dgpu_dsmcall.attr, -+ &dev_attr_dgpu_power.attr, -+ NULL, -+}; -+ -+static const struct attribute_group sb1_dgpu_sw_attr_group = { -+ .attrs = sb1_dgpu_sw_attrs, -+}; -+ -+ -+static int sb1_dgpu_sw_probe(struct platform_device *pdev) -+{ -+ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+} -+ -+static int sb1_dgpu_sw_remove(struct platform_device *pdev) -+{ -+ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+ return 0; -+} -+ -+/* -+ * The dGPU power seems to be actually handled by MSHW0040. However, that is -+ * also the power-/volume-button device with a mainline driver. So let's use -+ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. -+ */ -+static const struct acpi_device_id sb1_dgpu_sw_match[] = { -+ { "MSHW0041", }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); -+ -+static struct platform_driver sb1_dgpu_sw = { -+ .probe = sb1_dgpu_sw_probe, -+ .remove = sb1_dgpu_sw_remove, -+ .driver = { -+ .name = "surfacebook1_dgpu_switch", -+ .acpi_match_table = sb1_dgpu_sw_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(sb1_dgpu_sw); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); -+MODULE_LICENSE("GPL"); --- -2.38.0 - diff --git a/patches/5.19/0007-surface-button.patch b/patches/5.19/0007-surface-button.patch deleted file mode 100644 index 40150ea8f..000000000 --- a/patches/5.19/0007-surface-button.patch +++ /dev/null @@ -1,149 +0,0 @@ -From eb41cb53462dfbe95f397dab8a3e3d88fe8a81b7 Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Tue, 5 Oct 2021 00:05:09 +1100 -Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices - -The power button on the AMD variant of the Surface Laptop uses the -same MSHW0040 device ID as the 5th and later generation of Surface -devices, however they report 0 for their OEM platform revision. As the -_DSM does not exist on the devices requiring special casing, check for -the existance of the _DSM to determine if soc_button_array should be -loaded. - -Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") -Co-developed-by: Maximilian Luz - -Signed-off-by: Sachi King -Patchset: surface-button ---- - drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- - 1 file changed, 8 insertions(+), 25 deletions(-) - -diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c -index 480476121c01..36e1bf7b7a01 100644 ---- a/drivers/input/misc/soc_button_array.c -+++ b/drivers/input/misc/soc_button_array.c -@@ -495,8 +495,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { - * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned - * devices use MSHW0040 for power and volume buttons, however the way they - * have to be addressed differs. Make sure that we only load this drivers -- * for the correct devices by checking the OEM Platform Revision provided by -- * the _DSM method. -+ * for the correct devices by checking if the OEM Platform Revision DSM call -+ * exists. - */ - #define MSHW0040_DSM_REVISION 0x01 - #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision -@@ -507,31 +507,14 @@ static const guid_t MSHW0040_DSM_UUID = - static int soc_device_check_MSHW0040(struct device *dev) - { - acpi_handle handle = ACPI_HANDLE(dev); -- union acpi_object *result; -- u64 oem_platform_rev = 0; // valid revisions are nonzero -- -- // get OEM platform revision -- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -- MSHW0040_DSM_REVISION, -- MSHW0040_DSM_GET_OMPR, NULL, -- ACPI_TYPE_INTEGER); -- -- if (result) { -- oem_platform_rev = result->integer.value; -- ACPI_FREE(result); -- } -- -- /* -- * If the revision is zero here, the _DSM evaluation has failed. This -- * indicates that we have a Pro 4 or Book 1 and this driver should not -- * be used. -- */ -- if (oem_platform_rev == 0) -- return -ENODEV; -+ bool exists; - -- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); -+ // check if OEM platform revision DSM call exists -+ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ BIT(MSHW0040_DSM_GET_OMPR)); - -- return 0; -+ return exists ? 0 : -ENODEV; - } - - /* --- -2.38.0 - -From 9414f032450ef2c6513e386817b7a7a2c52942ff Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Tue, 5 Oct 2021 00:22:57 +1100 -Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd - variant - -The AMD variant of the Surface Laptop report 0 for their OEM platform -revision. The Surface devices that require the surfacepro3_button -driver do not have the _DSM that gets the OEM platform revision. If the -method does not exist, load surfacepro3_button. - -Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") -Co-developed-by: Maximilian Luz - -Signed-off-by: Sachi King -Patchset: surface-button ---- - drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- - 1 file changed, 6 insertions(+), 24 deletions(-) - -diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c -index 242fb690dcaf..30eea54dbb47 100644 ---- a/drivers/platform/surface/surfacepro3_button.c -+++ b/drivers/platform/surface/surfacepro3_button.c -@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) - /* - * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device - * ID (MSHW0040) for the power/volume buttons. Make sure this is the right -- * device by checking for the _DSM method and OEM Platform Revision. -+ * device by checking for the _DSM method and OEM Platform Revision DSM -+ * function. - * - * Returns true if the driver should bind to this device, i.e. the device is - * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. -@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) - static bool surface_button_check_MSHW0040(struct acpi_device *dev) - { - acpi_handle handle = dev->handle; -- union acpi_object *result; -- u64 oem_platform_rev = 0; // valid revisions are nonzero -- -- // get OEM platform revision -- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -- MSHW0040_DSM_REVISION, -- MSHW0040_DSM_GET_OMPR, -- NULL, ACPI_TYPE_INTEGER); -- -- /* -- * If evaluating the _DSM fails, the method is not present. This means -- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we -- * should use this driver. We use revision 0 indicating it is -- * unavailable. -- */ -- -- if (result) { -- oem_platform_rev = result->integer.value; -- ACPI_FREE(result); -- } -- -- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); - -- return oem_platform_rev == 0; -+ // make sure that OEM platform revision DSM call does not exist -+ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ BIT(MSHW0040_DSM_GET_OMPR)); - } - - --- -2.38.0 - diff --git a/patches/5.19/0008-surface-typecover.patch b/patches/5.19/0008-surface-typecover.patch deleted file mode 100644 index b1858a495..000000000 --- a/patches/5.19/0008-surface-typecover.patch +++ /dev/null @@ -1,533 +0,0 @@ -From c886e5cb36fa32932279a955431205eb59273969 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= -Date: Thu, 5 Nov 2020 13:09:45 +0100 -Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when - suspending - -The Type Cover for Microsoft Surface devices supports a special usb -control request to disable or enable the built-in keyboard backlight. -On Windows, this request happens when putting the device into suspend or -resuming it, without it the backlight of the Type Cover will remain -enabled for some time even though the computer is suspended, which looks -weird to the user. - -So add support for this special usb control request to hid-multitouch, -which is the driver that's handling the Type Cover. - -The reason we have to use a pm_notifier for this instead of the usual -suspend/resume methods is that those won't get called in case the usb -device is already autosuspended. - -Also, if the device is autosuspended, we have to briefly autoresume it -in order to send the request. Doing that should be fine, the usb-core -driver does something similar during suspend inside choose_wakeup(). - -To make sure we don't send that request to every device but only to -devices which support it, add a new quirk -MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk -is only enabled for the usb id of the Surface Pro 2017 Type Cover, which -is where I confirmed that it's working. - -Patchset: surface-typecover ---- - drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- - 1 file changed, 98 insertions(+), 2 deletions(-) - -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index 2e72922e36f5..15f5f11c4b85 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -34,7 +34,10 @@ - #include - #include - #include -+#include - #include -+#include -+#include - #include - #include - #include -@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); - MODULE_LICENSE("GPL"); - - #include "hid-ids.h" -+#include "usbhid/usbhid.h" - - /* quirks to control the device */ - #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) -@@ -71,12 +75,15 @@ MODULE_LICENSE("GPL"); - #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) - #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) - #define MT_QUIRK_DISABLE_WAKEUP BIT(21) -+#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 - - #define MT_BUTTONTYPE_CLICKPAD 0 - -+#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+ - enum latency_mode { - HID_LATENCY_NORMAL = 0, - HID_LATENCY_HIGH = 1, -@@ -168,6 +175,8 @@ struct mt_device { - - struct list_head applications; - struct list_head reports; -+ -+ struct notifier_block pm_notifier; - }; - - static void mt_post_parse_default_settings(struct mt_device *td, -@@ -212,6 +221,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); - #define MT_CLS_GOOGLE 0x0111 - #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 - #define MT_CLS_SMART_TECH 0x0113 -+#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 - - #define MT_DEFAULT_MAXCONTACT 10 - #define MT_MAX_MAXCONTACT 250 -@@ -396,6 +406,16 @@ static const struct mt_class mt_classes[] = { - MT_QUIRK_CONTACT_CNT_ACCURATE | - MT_QUIRK_SEPARATE_APP_REPORT, - }, -+ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_ALWAYS_VALID | -+ MT_QUIRK_IGNORE_DUPLICATES | -+ MT_QUIRK_HOVERING | -+ MT_QUIRK_CONTACT_CNT_ACCURATE | -+ MT_QUIRK_STICKY_FINGERS | -+ MT_QUIRK_WIN8_PTP_BUTTONS, -+ .export_all_inputs = true -+ }, - { } - }; - -@@ -1706,6 +1726,69 @@ static void mt_expired_timeout(struct timer_list *t) - clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - -+static void get_type_cover_backlight_field(struct hid_device *hdev, -+ struct hid_field **field) -+{ -+ struct hid_report_enum *rep_enum; -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid -+ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { -+ *field = cur_field; -+ return; -+ } -+ } -+ } -+ } -+} -+ -+static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) -+{ -+ struct usb_device *udev = hid_to_usb_dev(hdev); -+ struct hid_field *field = NULL; -+ -+ /* Wake up the device in case it's already suspended */ -+ pm_runtime_get_sync(&udev->dev); -+ -+ get_type_cover_backlight_field(hdev, &field); -+ if (!field) { -+ hid_err(hdev, "couldn't find backlight field\n"); -+ goto out; -+ } -+ -+ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; -+ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); -+ -+out: -+ pm_runtime_put_sync(&udev->dev); -+} -+ -+static int mt_pm_notifier(struct notifier_block *notifier, -+ unsigned long pm_event, -+ void *unused) -+{ -+ struct mt_device *td = -+ container_of(notifier, struct mt_device, pm_notifier); -+ struct hid_device *hdev = td->hdev; -+ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { -+ if (pm_event == PM_SUSPEND_PREPARE) -+ update_keyboard_backlight(hdev, 0); -+ else if (pm_event == PM_POST_SUSPEND) -+ update_keyboard_backlight(hdev, 1); -+ } -+ -+ return NOTIFY_DONE; -+} -+ - static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - { - int ret, i; -@@ -1729,6 +1812,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; - hid_set_drvdata(hdev, td); - -+ td->pm_notifier.notifier_call = mt_pm_notifier; -+ register_pm_notifier(&td->pm_notifier); -+ - INIT_LIST_HEAD(&td->applications); - INIT_LIST_HEAD(&td->reports); - -@@ -1758,15 +1844,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - timer_setup(&td->release_timer, mt_expired_timeout, 0); - - ret = hid_parse(hdev); -- if (ret != 0) -+ if (ret != 0) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) - mt_fix_const_fields(hdev, HID_DG_CONTACTID); - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); -- if (ret) -+ if (ret) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); - if (ret) -@@ -1818,6 +1908,7 @@ static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); - -+ unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); - - sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); -@@ -2191,6 +2282,11 @@ static const struct hid_device_id mt_devices[] = { - MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, - USB_DEVICE_ID_XIROKU_CSR2) }, - -+ /* Microsoft Surface type cover */ -+ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, -+ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, -+ - /* Google MT devices */ - { .driver_data = MT_CLS_GOOGLE, - HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, --- -2.38.0 - -From a745c564a0c46507d947e886fcc92b92cade4b37 Mon Sep 17 00:00:00 2001 -From: PJungkamp -Date: Fri, 25 Feb 2022 12:04:25 +0100 -Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet - switch - -The Surface Pro Type Cover has several non standard HID usages in it's -hid report descriptor. -I noticed that, upon folding the typecover back, a vendor specific range -of 4 32 bit integer hid usages is transmitted. -Only the first byte of the message seems to convey reliable information -about the keyboard state. - -0x22 => Normal (keys enabled) -0x33 => Folded back (keys disabled) -0x53 => Rotated left/right side up (keys disabled) -0x13 => Cover closed (keys disabled) -0x43 => Folded back and Tablet upside down (keys disabled) -This list may not be exhaustive. - -The tablet mode switch will be disabled for a value of 0x22 and enabled -on any other value. - -Patchset: surface-typecover ---- - drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ - 1 file changed, 122 insertions(+), 26 deletions(-) - -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index 15f5f11c4b85..69202575ce19 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -76,6 +76,7 @@ MODULE_LICENSE("GPL"); - #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) - #define MT_QUIRK_DISABLE_WAKEUP BIT(21) - #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) -+#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(23) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 -@@ -83,6 +84,8 @@ MODULE_LICENSE("GPL"); - #define MT_BUTTONTYPE_CLICKPAD 0 - - #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 -+#define MS_TYPE_COVER_APPLICATION 0xff050050 - - enum latency_mode { - HID_LATENCY_NORMAL = 0, -@@ -408,6 +411,7 @@ static const struct mt_class mt_classes[] = { - }, - { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, - .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | - MT_QUIRK_ALWAYS_VALID | - MT_QUIRK_IGNORE_DUPLICATES | - MT_QUIRK_HOVERING | -@@ -1368,6 +1372,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, - field->application != HID_CP_CONSUMER_CONTROL && - field->application != HID_GD_WIRELESS_RADIO_CTLS && - field->application != HID_GD_SYSTEM_MULTIAXIS && -+ !(field->application == MS_TYPE_COVER_APPLICATION && -+ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && - !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && - application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) - return -1; -@@ -1395,6 +1402,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, - return 1; - } - -+ /* -+ * The Microsoft Surface Pro Typecover has a non-standard HID -+ * tablet mode switch on a vendor specific usage page with vendor -+ * specific usage. -+ */ -+ if (field->application == MS_TYPE_COVER_APPLICATION && -+ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { -+ usage->type = EV_SW; -+ usage->code = SW_TABLET_MODE; -+ *max = SW_MAX; -+ *bit = hi->input->swbit; -+ return 1; -+ } -+ - if (rdata->is_mt_collection) - return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, - application); -@@ -1416,6 +1438,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, - { - struct mt_device *td = hid_get_drvdata(hdev); - struct mt_report_data *rdata; -+ struct input_dev *input; - - rdata = mt_find_report_data(td, field->report); - if (rdata && rdata->is_mt_collection) { -@@ -1423,6 +1446,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, - return -1; - } - -+ /* -+ * We own an input device which acts as a tablet mode switch for -+ * the Surface Pro Typecover. -+ */ -+ if (field->application == MS_TYPE_COVER_APPLICATION && -+ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { -+ input = hi->input; -+ input_set_capability(input, EV_SW, SW_TABLET_MODE); -+ input_report_switch(input, SW_TABLET_MODE, 0); -+ return -1; -+ } -+ - /* let hid-core decide for the others */ - return 0; - } -@@ -1432,11 +1468,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, - { - struct mt_device *td = hid_get_drvdata(hid); - struct mt_report_data *rdata; -+ struct input_dev *input; - - rdata = mt_find_report_data(td, field->report); - if (rdata && rdata->is_mt_collection) - return mt_touch_event(hid, field, usage, value); - -+ if (field->application == MS_TYPE_COVER_APPLICATION && -+ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { -+ input = field->hidinput->input; -+ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); -+ input_sync(input); -+ return 1; -+ } -+ - return 0; - } - -@@ -1589,6 +1635,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) - app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; - } - -+static int get_type_cover_field(struct hid_report_enum *rep_enum, -+ struct hid_field **field, int usage) -+{ -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ if (cur_field->application != MS_TYPE_COVER_APPLICATION) -+ continue; -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid == usage) { -+ *field = cur_field; -+ return true; -+ } -+ } -+ } -+ } -+ return false; -+} -+ -+static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) -+{ -+ struct hid_field *field; -+ -+ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], -+ &field, -+ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { -+ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); -+ } else { -+ hid_err(hdev, "couldn't find tablet mode field\n"); -+ } -+} -+ - static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) - { - struct mt_device *td = hid_get_drvdata(hdev); -@@ -1638,6 +1720,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) - /* force BTN_STYLUS to allow tablet matching in udev */ - __set_bit(BTN_STYLUS, hi->input->keybit); - break; -+ case MS_TYPE_COVER_APPLICATION: -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { -+ suffix = "Tablet Mode Switch"; -+ request_type_cover_tablet_mode_switch(hdev); -+ break; -+ } -+ fallthrough; - default: - suffix = "UNKNOWN"; - break; -@@ -1726,30 +1815,6 @@ static void mt_expired_timeout(struct timer_list *t) - clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - --static void get_type_cover_backlight_field(struct hid_device *hdev, -- struct hid_field **field) --{ -- struct hid_report_enum *rep_enum; -- struct hid_report *rep; -- struct hid_field *cur_field; -- int i, j; -- -- rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; -- list_for_each_entry(rep, &rep_enum->report_list, list) { -- for (i = 0; i < rep->maxfield; i++) { -- cur_field = rep->field[i]; -- -- for (j = 0; j < cur_field->maxusage; j++) { -- if (cur_field->usage[j].hid -- == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { -- *field = cur_field; -- return; -- } -- } -- } -- } --} -- - static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) - { - struct usb_device *udev = hid_to_usb_dev(hdev); -@@ -1758,8 +1823,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) - /* Wake up the device in case it's already suspended */ - pm_runtime_get_sync(&udev->dev); - -- get_type_cover_backlight_field(hdev, &field); -- if (!field) { -+ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], -+ &field, -+ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { - hid_err(hdev, "couldn't find backlight field\n"); - goto out; - } -@@ -1885,13 +1951,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) - - static int mt_reset_resume(struct hid_device *hdev) - { -+ struct mt_device *td = hid_get_drvdata(hdev); -+ - mt_release_contacts(hdev); - mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); -+ -+ /* Request an update on the typecover folding state on resume -+ * after reset. -+ */ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) -+ request_type_cover_tablet_mode_switch(hdev); -+ - return 0; - } - - static int mt_resume(struct hid_device *hdev) - { -+ struct mt_device *td = hid_get_drvdata(hdev); -+ - /* Some Elan legacy devices require SET_IDLE to be set on resume. - * It should be safe to send it to other devices too. - * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ -@@ -1900,6 +1977,10 @@ static int mt_resume(struct hid_device *hdev) - - mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); - -+ /* Request an update on the typecover folding state on resume. */ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) -+ request_type_cover_tablet_mode_switch(hdev); -+ - return 0; - } - #endif -@@ -1907,6 +1988,21 @@ static int mt_resume(struct hid_device *hdev) - static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); -+ struct hid_field *field; -+ struct input_dev *input; -+ -+ /* Reset tablet mode switch on disconnect. */ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { -+ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], -+ &field, -+ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { -+ input = field->hidinput->input; -+ input_report_switch(input, SW_TABLET_MODE, 0); -+ input_sync(input); -+ } else { -+ hid_err(hdev, "couldn't find tablet mode field\n"); -+ } -+ } - - unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); --- -2.38.0 - diff --git a/patches/5.19/0009-surface-gpe.patch b/patches/5.19/0009-surface-gpe.patch deleted file mode 100644 index 6974b6fd1..000000000 --- a/patches/5.19/0009-surface-gpe.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 37e8dc00b9c7a0d3546ff189ed206a41d3e3b4b1 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 21 Jul 2022 14:11:20 +0200 -Subject: [PATCH] platform/surface: gpe: Add support for 13" Intel version of - Surface Laptop 4 - -The 13" Intel version of the Surface Laptop 4 uses the same GPE as the -Surface Laptop Studio for wakeups via the lid. Set it up accordingly. - -Signed-off-by: Maximilian Luz -Link: https://lore.kernel.org/r/20220721121120.2002430-1-luzmaximilian@gmail.com -Signed-off-by: Hans de Goede -Patchset: surface-gpe ---- - drivers/platform/surface/surface_gpe.c | 12 ++++++++++++ - 1 file changed, 12 insertions(+) - -diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c -index 27365cbe1ee9..c219b840d491 100644 ---- a/drivers/platform/surface/surface_gpe.c -+++ b/drivers/platform/surface/surface_gpe.c -@@ -171,6 +171,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { - }, - .driver_data = (void *)lid_device_props_l4D, - }, -+ { -+ .ident = "Surface Laptop 4 (Intel 13\")", -+ .matches = { -+ /* -+ * We match for SKU here due to different variants: The -+ * AMD (15") version does not rely on GPEs. -+ */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1950:1951"), -+ }, -+ .driver_data = (void *)lid_device_props_l4B, -+ }, - { - .ident = "Surface Laptop Studio", - .matches = { --- -2.38.0 - diff --git a/patches/5.19/0010-cameras.patch b/patches/5.19/0010-cameras.patch deleted file mode 100644 index 4813aae66..000000000 --- a/patches/5.19/0010-cameras.patch +++ /dev/null @@ -1,1135 +0,0 @@ -From 340e27b04ff893cba3c6a073db8f211b1dc54622 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Sun, 10 Oct 2021 20:56:57 +0200 -Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an - INT3472 device - -The clk and regulator frameworks expect clk/regulator consumer-devices -to have info about the consumed clks/regulators described in the device's -fw_node. - -To work around cases where this info is not present in the firmware tables, -which is often the case on x86/ACPI devices, both frameworks allow the -provider-driver to attach info about consumers to the clks/regulators -when registering these. - -This causes problems with the probe ordering wrt drivers for consumers -of these clks/regulators. Since the lookups are only registered when the -provider-driver binds, trying to get these clks/regulators before then -results in a -ENOENT error for clks and a dummy regulator for regulators. - -One case where we hit this issue is camera sensors such as e.g. the OV8865 -sensor found on the Microsoft Surface Go. The sensor uses clks, regulators -and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 -ACPI device. There is special platform code handling this and setting -platform_data with the necessary consumer info on the MFD cells -instantiated for the PMIC under: drivers/platform/x86/intel/int3472. - -For this to work properly the ov8865 driver must not bind to the I2C-client -for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and -clk MFD cells have all been fully setup. - -The OV8865 on the Microsoft Surface Go is just one example, all X86 -devices using the Intel IPU3 camera block found on recent Intel SoCs -have similar issues where there is an INT3472 HID ACPI-device, which -describes the clks and regulators, and the driver for this INT3472 device -must be fully initialized before the sensor driver (any sensor driver) -binds for things to work properly. - -On these devices the ACPI nodes describing the sensors all have a _DEP -dependency on the matching INT3472 ACPI device (there is one per sensor). - -This allows solving the probe-ordering problem by delaying the enumeration -(instantiation of the I2C-client in the ov8865 example) of ACPI-devices -which have a _DEP dependency on an INT3472 device. - -The new acpi_dev_ready_for_enumeration() helper used for this is also -exported because for devices, which have the enumeration_by_parent flag -set, the parent-driver will do its own scan of child ACPI devices and -it will try to enumerate those during its probe(). Code doing this such -as e.g. the i2c-core-acpi.c code must call this new helper to ensure -that it too delays the enumeration until all the _DEP dependencies are -met on devices which have the new honor_deps flag set. - -Signed-off-by: Hans de Goede -Patchset: cameras ---- - drivers/acpi/scan.c | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c -index 762b61f67e6c..2c0f39a7f2a1 100644 ---- a/drivers/acpi/scan.c -+++ b/drivers/acpi/scan.c -@@ -2122,6 +2122,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, - - static void acpi_default_enumeration(struct acpi_device *device) - { -+ if (!acpi_dev_ready_for_enumeration(device)) -+ return; -+ - /* - * Do not enumerate devices with enumeration_by_parent flag set as - * they will be enumerated by their respective parents. --- -2.38.0 - -From 5407f2dc3152121801ec96b578df95d128899d78 Mon Sep 17 00:00:00 2001 -From: zouxiaoh -Date: Fri, 25 Jun 2021 08:52:59 +0800 -Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs - -Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, -The IPU driver allocates its own page table that is not mapped -via the DMA, and thus the Intel IOMMU driver blocks access giving -this error: DMAR: DRHD: handling fault status reg 3 DMAR: -[DMA Read] Request device [00:05.0] PASID ffffffff -fault addr 76406000 [fault reason 06] PTE Read access is not set -As IPU is not an external facing device which is not risky, so use -IOMMU passthrough mode for Intel IPUs. - -Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b -Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 -Tracked-On: #JIITL8-411 -Signed-off-by: Bingbu Cao -Signed-off-by: zouxiaoh -Signed-off-by: Xu Chongyang -Patchset: cameras ---- - drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ - 1 file changed, 30 insertions(+) - -diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c -index 8c5d47c5262f..ba732695e21e 100644 ---- a/drivers/iommu/intel/iommu.c -+++ b/drivers/iommu/intel/iommu.c -@@ -37,6 +37,12 @@ - #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) - #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) - #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) -+#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ -+ ((pdev)->device == 0x9a19 || \ -+ (pdev)->device == 0x9a39 || \ -+ (pdev)->device == 0x4e19 || \ -+ (pdev)->device == 0x465d || \ -+ (pdev)->device == 0x1919)) - #define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ - ((pdev)->device == 0x9d3e)) - #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) -@@ -298,12 +304,14 @@ EXPORT_SYMBOL_GPL(intel_iommu_enabled); - - static int dmar_map_gfx = 1; - static int dmar_map_ipts = 1; -+static int dmar_map_ipu = 1; - static int intel_iommu_superpage = 1; - static int iommu_identity_mapping; - static int iommu_skip_te_disable; - - #define IDENTMAP_GFX 2 - #define IDENTMAP_AZALIA 4 -+#define IDENTMAP_IPU 8 - #define IDENTMAP_IPTS 16 - - int intel_iommu_gfx_mapped; -@@ -2688,6 +2696,9 @@ static int device_def_domain_type(struct device *dev) - if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) - return IOMMU_DOMAIN_IDENTITY; - -+ if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) -+ return IOMMU_DOMAIN_IDENTITY; -+ - if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) - return IOMMU_DOMAIN_IDENTITY; - } -@@ -3112,6 +3123,9 @@ static int __init init_dmars(void) - if (!dmar_map_gfx) - iommu_identity_mapping |= IDENTMAP_GFX; - -+ if (!dmar_map_ipu) -+ iommu_identity_mapping |= IDENTMAP_IPU; -+ - if (!dmar_map_ipts) - iommu_identity_mapping |= IDENTMAP_IPTS; - -@@ -4943,6 +4957,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) - dmar_map_gfx = 0; - } - -+static void quirk_iommu_ipu(struct pci_dev *dev) -+{ -+ if (!IS_INTEL_IPU(dev)) -+ return; -+ -+ if (risky_device(dev)) -+ return; -+ -+ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); -+ dmar_map_ipu = 0; -+} -+ - static void quirk_iommu_ipts(struct pci_dev *dev) - { - if (!IS_IPTS(dev)) -@@ -4954,6 +4980,7 @@ static void quirk_iommu_ipts(struct pci_dev *dev) - pci_info(dev, "Passthrough IOMMU for IPTS\n"); - dmar_map_ipts = 0; - } -+ - /* G4x/GM45 integrated gfx dmar support is totally busted. */ - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); -@@ -4989,6 +5016,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); - -+/* disable IPU dmar support */ -+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); -+ - /* disable IPTS dmar support */ - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); - --- -2.38.0 - -From 1db1176874dfe6eda331594488daf4ff042834f3 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Sun, 10 Oct 2021 20:57:02 +0200 -Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain - -The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic -can be forwarded to a device connected to the PMIC as though it were -connected directly to the system bus. Enable this mode when the chip -is initialised. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c -index 22f61b47f9e5..e1de1ff40bba 100644 ---- a/drivers/platform/x86/intel/int3472/tps68470.c -+++ b/drivers/platform/x86/intel/int3472/tps68470.c -@@ -45,6 +45,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) - return ret; - } - -+ /* Enable I2C daisy chain */ -+ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); -+ if (ret) { -+ dev_err(dev, "Failed to enable i2c daisy chain\n"); -+ return ret; -+ } -+ - dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); - - return 0; --- -2.38.0 - -From d3018a79f94f544310dd2ca787f49c33ab2f13a8 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 28 Oct 2021 21:55:16 +0100 -Subject: [PATCH] media: i2c: Add driver for DW9719 VCM - -Add a driver for the DW9719 VCM. The driver creates a v4l2 subdevice -and registers a control to set the desired focus. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - MAINTAINERS | 7 + - drivers/media/i2c/Kconfig | 11 + - drivers/media/i2c/Makefile | 1 + - drivers/media/i2c/dw9719.c | 427 +++++++++++++++++++++++++++++++++++++ - 4 files changed, 446 insertions(+) - create mode 100644 drivers/media/i2c/dw9719.c - -diff --git a/MAINTAINERS b/MAINTAINERS -index 6772c9d0eccc..a639e7ff0402 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -6163,6 +6163,13 @@ T: git git://linuxtv.org/media_tree.git - F: Documentation/devicetree/bindings/media/i2c/dongwoon,dw9714.txt - F: drivers/media/i2c/dw9714.c - -+DONGWOON DW9719 LENS VOICE COIL DRIVER -+M: Daniel Scally -+L: linux-media@vger.kernel.org -+S: Maintained -+T: git git://linuxtv.org/media_tree.git -+F: drivers/media/i2c/dw9719.c -+ - DONGWOON DW9768 LENS VOICE COIL DRIVER - M: Dongchun Zhu - L: linux-media@vger.kernel.org -diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig -index c926e5d43820..5c245f642ae3 100644 ---- a/drivers/media/i2c/Kconfig -+++ b/drivers/media/i2c/Kconfig -@@ -806,6 +806,17 @@ config VIDEO_DW9714 - capability. This is designed for linear control of - voice coil motors, controlled via I2C serial interface. - -+config VIDEO_DW9719 -+ tristate "DW9719 lens voice coil support" -+ depends on I2C && VIDEO_V4L2 -+ select MEDIA_CONTROLLER -+ select VIDEO_V4L2_SUBDEV_API -+ select V4L2_ASYNC -+ help -+ This is a driver for the DW9719 camera lens voice coil. -+ This is designed for linear control of voice coil motors, -+ controlled via I2C serial interface. -+ - config VIDEO_DW9768 - tristate "DW9768 lens voice coil support" - depends on I2C && VIDEO_DEV -diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile -index 3e1696963e7f..9dfda069e006 100644 ---- a/drivers/media/i2c/Makefile -+++ b/drivers/media/i2c/Makefile -@@ -29,6 +29,7 @@ obj-$(CONFIG_VIDEO_CS5345) += cs5345.o - obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o - obj-$(CONFIG_VIDEO_CX25840) += cx25840/ - obj-$(CONFIG_VIDEO_DW9714) += dw9714.o -+obj-$(CONFIG_VIDEO_DW9719) += dw9719.o - obj-$(CONFIG_VIDEO_DW9768) += dw9768.o - obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o - obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ -diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c -new file mode 100644 -index 000000000000..8451c75b696b ---- /dev/null -+++ b/drivers/media/i2c/dw9719.c -@@ -0,0 +1,427 @@ -+// SPDX-License-Identifier: GPL-2.0 -+// Copyright (c) 2012 Intel Corporation -+ -+/* -+ * Based on linux/modules/camera/drivers/media/i2c/imx/dw9719.c in this repo: -+ * https://github.com/ZenfoneArea/android_kernel_asus_zenfone5 -+ */ -+ -+#include -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+#define DW9719_MAX_FOCUS_POS 1023 -+#define DW9719_CTRL_STEPS 16 -+#define DW9719_CTRL_DELAY_US 1000 -+#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) -+ -+#define DW9719_INFO 0 -+#define DW9719_ID 0xF1 -+#define DW9719_CONTROL 2 -+#define DW9719_VCM_CURRENT 3 -+ -+#define DW9719_MODE 6 -+#define DW9719_VCM_FREQ 7 -+ -+#define DW9719_MODE_SAC3 0x40 -+#define DW9719_DEFAULT_VCM_FREQ 0x60 -+#define DW9719_ENABLE_RINGING 0x02 -+ -+#define NUM_REGULATORS 2 -+ -+#define to_dw9719_device(x) container_of(x, struct dw9719_device, sd) -+ -+struct dw9719_device { -+ struct device *dev; -+ struct i2c_client *client; -+ struct regulator_bulk_data regulators[NUM_REGULATORS]; -+ struct v4l2_subdev sd; -+ -+ struct dw9719_v4l2_ctrls { -+ struct v4l2_ctrl_handler handler; -+ struct v4l2_ctrl *focus; -+ } ctrls; -+}; -+ -+static int dw9719_i2c_rd8(struct i2c_client *client, u8 reg, u8 *val) -+{ -+ struct i2c_msg msg[2]; -+ u8 buf[2] = { reg }; -+ int ret; -+ -+ msg[0].addr = client->addr; -+ msg[0].flags = 0; -+ msg[0].len = 1; -+ msg[0].buf = buf; -+ -+ msg[1].addr = client->addr; -+ msg[1].flags = I2C_M_RD; -+ msg[1].len = 1; -+ msg[1].buf = &buf[1]; -+ *val = 0; -+ -+ ret = i2c_transfer(client->adapter, msg, 2); -+ if (ret < 0) -+ return ret; -+ -+ *val = buf[1]; -+ -+ return 0; -+} -+ -+static int dw9719_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) -+{ -+ struct i2c_msg msg; -+ int ret; -+ -+ u8 buf[2] = { reg, val }; -+ -+ msg.addr = client->addr; -+ msg.flags = 0; -+ msg.len = sizeof(buf); -+ msg.buf = buf; -+ -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ return ret < 0 ? ret : 0; -+} -+ -+static int dw9719_i2c_wr16(struct i2c_client *client, u8 reg, u16 val) -+{ -+ struct i2c_msg msg; -+ u8 buf[3] = { reg }; -+ int ret; -+ -+ put_unaligned_be16(val, buf + 1); -+ -+ msg.addr = client->addr; -+ msg.flags = 0; -+ msg.len = sizeof(buf); -+ msg.buf = buf; -+ -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ return ret < 0 ? ret : 0; -+} -+ -+static int dw9719_detect(struct dw9719_device *dw9719) -+{ -+ int ret; -+ u8 val; -+ -+ ret = dw9719_i2c_rd8(dw9719->client, DW9719_INFO, &val); -+ if (ret < 0) -+ return ret; -+ -+ if (val != DW9719_ID) { -+ dev_err(dw9719->dev, "Failed to detect correct id\n"); -+ ret = -ENXIO; -+ } -+ -+ return 0; -+} -+ -+static int dw9719_power_down(struct dw9719_device *dw9719) -+{ -+ return regulator_bulk_disable(NUM_REGULATORS, dw9719->regulators); -+} -+ -+static int dw9719_power_up(struct dw9719_device *dw9719) -+{ -+ int ret; -+ -+ ret = regulator_bulk_enable(NUM_REGULATORS, dw9719->regulators); -+ if (ret) -+ return ret; -+ -+ /* Jiggle SCL pin to wake up device */ -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, 1); -+ -+ /* Need 100us to transit from SHUTDOWN to STANDBY*/ -+ usleep_range(100, 1000); -+ -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, -+ DW9719_ENABLE_RINGING); -+ if (ret < 0) -+ goto fail_powerdown; -+ -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_MODE, DW9719_MODE_SAC3); -+ if (ret < 0) -+ goto fail_powerdown; -+ -+ ret = dw9719_i2c_wr8(dw9719->client, DW9719_VCM_FREQ, -+ DW9719_DEFAULT_VCM_FREQ); -+ if (ret < 0) -+ goto fail_powerdown; -+ -+ return 0; -+ -+fail_powerdown: -+ dw9719_power_down(dw9719); -+ return ret; -+} -+ -+static int dw9719_t_focus_abs(struct dw9719_device *dw9719, s32 value) -+{ -+ int ret; -+ -+ value = clamp(value, 0, DW9719_MAX_FOCUS_POS); -+ ret = dw9719_i2c_wr16(dw9719->client, DW9719_VCM_CURRENT, value); -+ if (ret < 0) -+ return ret; -+ -+ return 0; -+} -+ -+static int dw9719_set_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct dw9719_device *dw9719 = container_of(ctrl->handler, -+ struct dw9719_device, -+ ctrls.handler); -+ int ret; -+ -+ /* Only apply changes to the controls if the device is powered up */ -+ if (!pm_runtime_get_if_in_use(dw9719->dev)) -+ return 0; -+ -+ switch (ctrl->id) { -+ case V4L2_CID_FOCUS_ABSOLUTE: -+ ret = dw9719_t_focus_abs(dw9719, ctrl->val); -+ break; -+ default: -+ ret = -EINVAL; -+ } -+ -+ pm_runtime_put(dw9719->dev); -+ -+ return ret; -+} -+ -+static const struct v4l2_ctrl_ops dw9719_ctrl_ops = { -+ .s_ctrl = dw9719_set_ctrl, -+}; -+ -+static int __maybe_unused dw9719_suspend(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct dw9719_device *dw9719 = to_dw9719_device(sd); -+ int ret; -+ int val; -+ -+ for (val = dw9719->ctrls.focus->val; val >= 0; -+ val -= DW9719_CTRL_STEPS) { -+ ret = dw9719_t_focus_abs(dw9719, val); -+ if (ret) -+ return ret; -+ -+ usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); -+ } -+ -+ return dw9719_power_down(dw9719); -+} -+ -+static int __maybe_unused dw9719_resume(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct dw9719_device *dw9719 = to_dw9719_device(sd); -+ int current_focus = dw9719->ctrls.focus->val; -+ int ret; -+ int val; -+ -+ ret = dw9719_power_up(dw9719); -+ if (ret) -+ return ret; -+ -+ for (val = current_focus % DW9719_CTRL_STEPS; val < current_focus; -+ val += DW9719_CTRL_STEPS) { -+ ret = dw9719_t_focus_abs(dw9719, val); -+ if (ret) -+ goto err_power_down; -+ -+ usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); -+ } -+ -+ return 0; -+ -+err_power_down: -+ dw9719_power_down(dw9719); -+ return ret; -+} -+ -+static int dw9719_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -+{ -+ return pm_runtime_resume_and_get(sd->dev); -+} -+ -+static int dw9719_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) -+{ -+ pm_runtime_put(sd->dev); -+ -+ return 0; -+} -+ -+static const struct v4l2_subdev_internal_ops dw9719_internal_ops = { -+ .open = dw9719_open, -+ .close = dw9719_close, -+}; -+ -+static int dw9719_init_controls(struct dw9719_device *dw9719) -+{ -+ const struct v4l2_ctrl_ops *ops = &dw9719_ctrl_ops; -+ int ret; -+ -+ ret = v4l2_ctrl_handler_init(&dw9719->ctrls.handler, 1); -+ if (ret) -+ return ret; -+ -+ dw9719->ctrls.focus = v4l2_ctrl_new_std(&dw9719->ctrls.handler, ops, -+ V4L2_CID_FOCUS_ABSOLUTE, 0, -+ DW9719_MAX_FOCUS_POS, 1, 0); -+ -+ if (dw9719->ctrls.handler.error) { -+ dev_err(dw9719->dev, "Error initialising v4l2 ctrls\n"); -+ ret = dw9719->ctrls.handler.error; -+ goto err_free_handler; -+ } -+ -+ dw9719->sd.ctrl_handler = &dw9719->ctrls.handler; -+ -+ return ret; -+ -+err_free_handler: -+ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); -+ return ret; -+} -+ -+static const struct v4l2_subdev_ops dw9719_ops = { }; -+ -+static int dw9719_probe(struct i2c_client *client) -+{ -+ struct dw9719_device *dw9719; -+ int ret; -+ -+ dw9719 = devm_kzalloc(&client->dev, sizeof(*dw9719), GFP_KERNEL); -+ if (!dw9719) -+ return -ENOMEM; -+ -+ dw9719->client = client; -+ dw9719->dev = &client->dev; -+ -+ dw9719->regulators[0].supply = "vdd"; -+ /* -+ * The DW9719 has only the 1 VDD voltage input, but some PMICs such as -+ * the TPS68470 PMIC have I2C passthrough capability, to disconnect the -+ * sensor's I2C pins from the I2C bus when the sensors VSIO (Sensor-IO) -+ * is off, because some sensors then short these pins to ground; -+ * and the DW9719 might sit behind this passthrough, this it needs to -+ * enable VSIO as that will also enable the I2C passthrough. -+ */ -+ dw9719->regulators[1].supply = "vsio"; -+ -+ ret = devm_regulator_bulk_get(&client->dev, NUM_REGULATORS, -+ dw9719->regulators); -+ if (ret) -+ return dev_err_probe(&client->dev, ret, "getting regulators\n"); -+ -+ v4l2_i2c_subdev_init(&dw9719->sd, client, &dw9719_ops); -+ dw9719->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; -+ dw9719->sd.internal_ops = &dw9719_internal_ops; -+ -+ ret = dw9719_init_controls(dw9719); -+ if (ret) -+ return ret; -+ -+ ret = media_entity_pads_init(&dw9719->sd.entity, 0, NULL); -+ if (ret < 0) -+ goto err_free_ctrl_handler; -+ -+ dw9719->sd.entity.function = MEDIA_ENT_F_LENS; -+ -+ /* -+ * We need the driver to work in the event that pm runtime is disable in -+ * the kernel, so power up and verify the chip now. In the event that -+ * runtime pm is disabled this will leave the chip on, so that the lens -+ * will work. -+ */ -+ -+ ret = dw9719_power_up(dw9719); -+ if (ret) -+ goto err_cleanup_media; -+ -+ ret = dw9719_detect(dw9719); -+ if (ret) -+ goto err_powerdown; -+ -+ pm_runtime_set_active(&client->dev); -+ pm_runtime_get_noresume(&client->dev); -+ pm_runtime_enable(&client->dev); -+ -+ ret = v4l2_async_register_subdev(&dw9719->sd); -+ if (ret < 0) -+ goto err_pm_runtime; -+ -+ pm_runtime_set_autosuspend_delay(&client->dev, 1000); -+ pm_runtime_use_autosuspend(&client->dev); -+ pm_runtime_put_autosuspend(&client->dev); -+ -+ return ret; -+ -+err_pm_runtime: -+ pm_runtime_disable(&client->dev); -+ pm_runtime_put_noidle(&client->dev); -+err_powerdown: -+ dw9719_power_down(dw9719); -+err_cleanup_media: -+ media_entity_cleanup(&dw9719->sd.entity); -+err_free_ctrl_handler: -+ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); -+ -+ return ret; -+} -+ -+static int dw9719_remove(struct i2c_client *client) -+{ -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct dw9719_device *dw9719 = container_of(sd, struct dw9719_device, -+ sd); -+ -+ pm_runtime_disable(&client->dev); -+ v4l2_async_unregister_subdev(sd); -+ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); -+ media_entity_cleanup(&dw9719->sd.entity); -+ -+ return 0; -+} -+ -+static const struct i2c_device_id dw9719_id_table[] = { -+ { "dw9719" }, -+ { } -+}; -+MODULE_DEVICE_TABLE(i2c, dw9719_id_table); -+ -+static const struct dev_pm_ops dw9719_pm_ops = { -+ SET_RUNTIME_PM_OPS(dw9719_suspend, dw9719_resume, NULL) -+}; -+ -+static struct i2c_driver dw9719_i2c_driver = { -+ .driver = { -+ .name = "dw9719", -+ .pm = &dw9719_pm_ops, -+ }, -+ .probe_new = dw9719_probe, -+ .remove = dw9719_remove, -+ .id_table = dw9719_id_table, -+}; -+module_i2c_driver(dw9719_i2c_driver); -+ -+MODULE_AUTHOR("Daniel Scally "); -+MODULE_DESCRIPTION("DW9719 VCM Driver"); -+MODULE_LICENSE("GPL"); --- -2.38.0 - -From bf8bfbffae54808181f25b8fe37845a5b84bf5ed Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Wed, 4 May 2022 23:21:45 +0100 -Subject: [PATCH] media: ipu3-cio2: Move functionality from .complete() to - .bound() - -Creating links and registering subdev nodes during the .complete() -callback has the unfortunate effect of preventing all cameras that -connect to a notifier from working if any one of their drivers fails -to probe. Moving the functionality from .complete() to .bound() allows -those camera sensor drivers that did probe correctly to work regardless. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 65 +++++++------------ - 1 file changed, 23 insertions(+), 42 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -index dbdbdb648a0d..d0715144bf3e 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -@@ -1383,7 +1383,10 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, - { - struct cio2_device *cio2 = to_cio2_device(notifier); - struct sensor_async_subdev *s_asd = to_sensor_asd(asd); -+ struct device *dev = &cio2->pci_dev->dev; - struct cio2_queue *q; -+ unsigned int pad; -+ int ret; - - if (cio2->queue[s_asd->csi2.port].sensor) - return -EBUSY; -@@ -1394,7 +1397,26 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, - q->sensor = sd; - q->csi_rx_base = cio2->base + CIO2_REG_PIPE_BASE(q->csi2.port); - -- return 0; -+ for (pad = 0; pad < q->sensor->entity.num_pads; pad++) -+ if (q->sensor->entity.pads[pad].flags & -+ MEDIA_PAD_FL_SOURCE) -+ break; -+ -+ if (pad == q->sensor->entity.num_pads) { -+ dev_err(dev, "failed to find src pad for %s\n", -+ q->sensor->name); -+ return -ENXIO; -+ } -+ -+ ret = media_create_pad_link(&q->sensor->entity, pad, &q->subdev.entity, -+ CIO2_PAD_SINK, 0); -+ if (ret) { -+ dev_err(dev, "failed to create link for %s\n", -+ q->sensor->name); -+ return ret; -+ } -+ -+ return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); - } - - /* The .unbind callback */ -@@ -1408,50 +1430,9 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, - cio2->queue[s_asd->csi2.port].sensor = NULL; - } - --/* .complete() is called after all subdevices have been located */ --static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) --{ -- struct cio2_device *cio2 = to_cio2_device(notifier); -- struct device *dev = &cio2->pci_dev->dev; -- struct sensor_async_subdev *s_asd; -- struct v4l2_async_subdev *asd; -- struct cio2_queue *q; -- unsigned int pad; -- int ret; -- -- list_for_each_entry(asd, &cio2->notifier.asd_list, asd_list) { -- s_asd = to_sensor_asd(asd); -- q = &cio2->queue[s_asd->csi2.port]; -- -- for (pad = 0; pad < q->sensor->entity.num_pads; pad++) -- if (q->sensor->entity.pads[pad].flags & -- MEDIA_PAD_FL_SOURCE) -- break; -- -- if (pad == q->sensor->entity.num_pads) { -- dev_err(dev, "failed to find src pad for %s\n", -- q->sensor->name); -- return -ENXIO; -- } -- -- ret = media_create_pad_link( -- &q->sensor->entity, pad, -- &q->subdev.entity, CIO2_PAD_SINK, -- 0); -- if (ret) { -- dev_err(dev, "failed to create link for %s\n", -- q->sensor->name); -- return ret; -- } -- } -- -- return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); --} -- - static const struct v4l2_async_notifier_operations cio2_async_ops = { - .bound = cio2_notifier_bound, - .unbind = cio2_notifier_unbind, -- .complete = cio2_notifier_complete, - }; - - static int cio2_parse_firmware(struct cio2_device *cio2) --- -2.38.0 - -From bc32ccc09b6b247746da56903b93caaf56890f61 Mon Sep 17 00:00:00 2001 -From: Daniel Scally -Date: Thu, 2 Jun 2022 22:15:56 +0100 -Subject: [PATCH] media: ipu3-cio2: Re-add .complete() to ipu3-cio2 - -Removing the .complete() callback had some unintended consequences. -Because the VCM driver is not directly linked to the ipu3-cio2 -driver .bound() never gets called for it, which means its devnode -is never created if it probes late. Because .complete() waits for -any sub-notifiers to also be complete it is captured in that call. - -Signed-off-by: Daniel Scally -Patchset: cameras ---- - drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -index d0715144bf3e..3a25dfc696b2 100644 ---- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -+++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c -@@ -1430,9 +1430,18 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, - cio2->queue[s_asd->csi2.port].sensor = NULL; - } - -+/* .complete() is called after all subdevices have been located */ -+static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) -+{ -+ struct cio2_device *cio2 = to_cio2_device(notifier); -+ -+ return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); -+} -+ - static const struct v4l2_async_notifier_operations cio2_async_ops = { - .bound = cio2_notifier_bound, - .unbind = cio2_notifier_unbind, -+ .complete = cio2_notifier_complete, - }; - - static int cio2_parse_firmware(struct cio2_device *cio2) --- -2.38.0 - -From 2bcf7e736c25e712c0296b130792aec6474396f5 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 15 Jul 2022 23:48:00 +0200 -Subject: [PATCH] drivers/media/i2c: Fix DW9719 dependencies - -It should depend on VIDEO_DEV instead of VIDEO_V4L2. - -Signed-off-by: Maximilian Luz -Patchset: cameras ---- - drivers/media/i2c/Kconfig | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig -index 5c245f642ae3..50ea62e63784 100644 ---- a/drivers/media/i2c/Kconfig -+++ b/drivers/media/i2c/Kconfig -@@ -808,7 +808,7 @@ config VIDEO_DW9714 - - config VIDEO_DW9719 - tristate "DW9719 lens voice coil support" -- depends on I2C && VIDEO_V4L2 -+ depends on I2C && VIDEO_DEV - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - select V4L2_ASYNC --- -2.38.0 - -From fd929677d582ece5c706a8d37cb8732b9d628287 Mon Sep 17 00:00:00 2001 -From: Sakari Ailus -Date: Thu, 25 Aug 2022 21:36:37 +0300 -Subject: [PATCH] ipu3-imgu: Fix NULL pointer dereference in active selection - access - -What the IMGU driver did was that it first acquired the pointers to active -and try V4L2 subdev state, and only then figured out which one to use. - -The problem with that approach and a later patch (see Fixes: tag) is that -as sd_state argument to v4l2_subdev_get_try_crop() et al is NULL, there is -now an attempt to dereference that. - -Fix this. - -Also rewrap lines a little. - -Fixes: 0d346d2a6f54 ("media: v4l2-subdev: add subdev-wide state struct") -Cc: stable@vger.kernel.org # for v5.14 and later -Signed-off-by: Sakari Ailus -Reviewed-by: Bingbu Cao -Patchset: cameras ---- - drivers/staging/media/ipu3/ipu3-v4l2.c | 31 ++++++++++++-------------- - 1 file changed, 14 insertions(+), 17 deletions(-) - -diff --git a/drivers/staging/media/ipu3/ipu3-v4l2.c b/drivers/staging/media/ipu3/ipu3-v4l2.c -index d1c539cefba8..2234bb8d48b3 100644 ---- a/drivers/staging/media/ipu3/ipu3-v4l2.c -+++ b/drivers/staging/media/ipu3/ipu3-v4l2.c -@@ -192,33 +192,30 @@ static int imgu_subdev_get_selection(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_selection *sel) - { -- struct v4l2_rect *try_sel, *r; -- struct imgu_v4l2_subdev *imgu_sd = container_of(sd, -- struct imgu_v4l2_subdev, -- subdev); -+ struct imgu_v4l2_subdev *imgu_sd = -+ container_of(sd, struct imgu_v4l2_subdev, subdev); - - if (sel->pad != IMGU_NODE_IN) - return -EINVAL; - - switch (sel->target) { - case V4L2_SEL_TGT_CROP: -- try_sel = v4l2_subdev_get_try_crop(sd, sd_state, sel->pad); -- r = &imgu_sd->rect.eff; -- break; -+ if (sel->which == V4L2_SUBDEV_FORMAT_TRY) -+ sel->r = *v4l2_subdev_get_try_crop(sd, sd_state, -+ sel->pad); -+ else -+ sel->r = imgu_sd->rect.eff; -+ return 0; - case V4L2_SEL_TGT_COMPOSE: -- try_sel = v4l2_subdev_get_try_compose(sd, sd_state, sel->pad); -- r = &imgu_sd->rect.bds; -- break; -+ if (sel->which == V4L2_SUBDEV_FORMAT_TRY) -+ sel->r = *v4l2_subdev_get_try_compose(sd, sd_state, -+ sel->pad); -+ else -+ sel->r = imgu_sd->rect.bds; -+ return 0; - default: - return -EINVAL; - } -- -- if (sel->which == V4L2_SUBDEV_FORMAT_TRY) -- sel->r = *try_sel; -- else -- sel->r = *r; -- -- return 0; - } - - static int imgu_subdev_set_selection(struct v4l2_subdev *sd, --- -2.38.0 - -From b281b5d080904e4b8fe32a3fdcfb56a02ef3d483 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 7 Sep 2022 15:38:08 +0200 -Subject: [PATCH] ipu3-imgu: Fix NULL pointer dereference in - imgu_subdev_set_selection() - -Calling v4l2_subdev_get_try_crop() and v4l2_subdev_get_try_compose() -with a subdev state of NULL leads to a NULL pointer dereference. This -can currently happen in imgu_subdev_set_selection() when the state -passed in is NULL, as this method first gets pointers to both the "try" -and "active" states and only then decides which to use. - -The same issue has been addressed for imgu_subdev_get_selection() with -commit 30d03a0de650 ("ipu3-imgu: Fix NULL pointer dereference in active -selection access"). However the issue still persists in -imgu_subdev_set_selection(). - -Therefore, apply a similar fix as done in the aforementioned commit to -imgu_subdev_set_selection(). To keep things a bit cleaner, introduce -helper functions for "crop" and "compose" access and use them in both -imgu_subdev_set_selection() and imgu_subdev_get_selection(). - -Fixes: 0d346d2a6f54 ("media: v4l2-subdev: add subdev-wide state struct") -Cc: stable@vger.kernel.org # for v5.14 and later -Signed-off-by: Maximilian Luz -Patchset: cameras ---- - drivers/staging/media/ipu3/ipu3-v4l2.c | 57 +++++++++++++++----------- - 1 file changed, 34 insertions(+), 23 deletions(-) - -diff --git a/drivers/staging/media/ipu3/ipu3-v4l2.c b/drivers/staging/media/ipu3/ipu3-v4l2.c -index 2234bb8d48b3..490ba0eb249b 100644 ---- a/drivers/staging/media/ipu3/ipu3-v4l2.c -+++ b/drivers/staging/media/ipu3/ipu3-v4l2.c -@@ -188,6 +188,28 @@ static int imgu_subdev_set_fmt(struct v4l2_subdev *sd, - return 0; - } - -+static struct v4l2_rect * -+imgu_subdev_get_crop(struct imgu_v4l2_subdev *sd, -+ struct v4l2_subdev_state *sd_state, unsigned int pad, -+ enum v4l2_subdev_format_whence which) -+{ -+ if (which == V4L2_SUBDEV_FORMAT_TRY) -+ return v4l2_subdev_get_try_crop(&sd->subdev, sd_state, pad); -+ else -+ return &sd->rect.eff; -+} -+ -+static struct v4l2_rect * -+imgu_subdev_get_compose(struct imgu_v4l2_subdev *sd, -+ struct v4l2_subdev_state *sd_state, unsigned int pad, -+ enum v4l2_subdev_format_whence which) -+{ -+ if (which == V4L2_SUBDEV_FORMAT_TRY) -+ return v4l2_subdev_get_try_compose(&sd->subdev, sd_state, pad); -+ else -+ return &sd->rect.bds; -+} -+ - static int imgu_subdev_get_selection(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_selection *sel) -@@ -200,18 +222,12 @@ static int imgu_subdev_get_selection(struct v4l2_subdev *sd, - - switch (sel->target) { - case V4L2_SEL_TGT_CROP: -- if (sel->which == V4L2_SUBDEV_FORMAT_TRY) -- sel->r = *v4l2_subdev_get_try_crop(sd, sd_state, -- sel->pad); -- else -- sel->r = imgu_sd->rect.eff; -+ sel->r = *imgu_subdev_get_crop(imgu_sd, sd_state, sel->pad, -+ sel->which); - return 0; - case V4L2_SEL_TGT_COMPOSE: -- if (sel->which == V4L2_SUBDEV_FORMAT_TRY) -- sel->r = *v4l2_subdev_get_try_compose(sd, sd_state, -- sel->pad); -- else -- sel->r = imgu_sd->rect.bds; -+ sel->r = *imgu_subdev_get_compose(imgu_sd, sd_state, sel->pad, -+ sel->which); - return 0; - default: - return -EINVAL; -@@ -223,10 +239,9 @@ static int imgu_subdev_set_selection(struct v4l2_subdev *sd, - struct v4l2_subdev_selection *sel) - { - struct imgu_device *imgu = v4l2_get_subdevdata(sd); -- struct imgu_v4l2_subdev *imgu_sd = container_of(sd, -- struct imgu_v4l2_subdev, -- subdev); -- struct v4l2_rect *rect, *try_sel; -+ struct imgu_v4l2_subdev *imgu_sd = -+ container_of(sd, struct imgu_v4l2_subdev, subdev); -+ struct v4l2_rect *rect; - - dev_dbg(&imgu->pci_dev->dev, - "set subdev %u sel which %u target 0x%4x rect [%ux%u]", -@@ -238,22 +253,18 @@ static int imgu_subdev_set_selection(struct v4l2_subdev *sd, - - switch (sel->target) { - case V4L2_SEL_TGT_CROP: -- try_sel = v4l2_subdev_get_try_crop(sd, sd_state, sel->pad); -- rect = &imgu_sd->rect.eff; -+ rect = imgu_subdev_get_crop(imgu_sd, sd_state, sel->pad, -+ sel->which); - break; - case V4L2_SEL_TGT_COMPOSE: -- try_sel = v4l2_subdev_get_try_compose(sd, sd_state, sel->pad); -- rect = &imgu_sd->rect.bds; -+ rect = imgu_subdev_get_compose(imgu_sd, sd_state, sel->pad, -+ sel->which); - break; - default: - return -EINVAL; - } - -- if (sel->which == V4L2_SUBDEV_FORMAT_TRY) -- *try_sel = sel->r; -- else -- *rect = sel->r; -- -+ *rect = sel->r; - return 0; - } - --- -2.38.0 - diff --git a/patches/5.19/0011-amd-gpio.patch b/patches/5.19/0011-amd-gpio.patch deleted file mode 100644 index 5c9726ddf..000000000 --- a/patches/5.19/0011-amd-gpio.patch +++ /dev/null @@ -1,109 +0,0 @@ -From 483c1600405fea9d049a04920bcfb7115f099a9f Mon Sep 17 00:00:00 2001 -From: Sachi King -Date: Sat, 29 May 2021 17:47:38 +1000 -Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 - override - -This patch is the work of Thomas Gleixner and is -copied from: -https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ - -This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin -setup that is missing in the laptops ACPI table. - -This patch was used for validation of the issue, and is not a proper -fix, but is probably a better temporary hack than continuing to probe -the Legacy PIC and run with the PIC in an unknown state. - -Patchset: amd-gpio ---- - arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ - 1 file changed, 17 insertions(+) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 907cc98b1938..0116d27b29ea 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -22,6 +22,7 @@ - #include - #include - #include -+#include - - #include - #include -@@ -1234,6 +1235,17 @@ static void __init mp_config_acpi_legacy_irqs(void) - } - } - -+static const struct dmi_system_id surface_quirk[] __initconst = { -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") -+ }, -+ }, -+ {} -+}; -+ - /* - * Parse IOAPIC related entries in MADT - * returns 0 on success, < 0 on error -@@ -1289,6 +1301,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) - acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, - acpi_gbl_FADT.sci_interrupt); - -+ if (dmi_check_system(surface_quirk)) { -+ pr_warn("Surface hack: Override irq 7\n"); -+ mp_override_legacy_irq(7, 3, 3, 7); -+ } -+ - /* Fill in identity legacy mappings where no override */ - mp_config_acpi_legacy_irqs(); - --- -2.38.0 - -From e7e1a5e71673085d1e66eafcdfb1749024b1004d Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Thu, 3 Jun 2021 14:04:26 +0200 -Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override - quirk - -The 13" version of the Surface Laptop 4 has the same problem as the 15" -version, but uses a different SKU. Add that SKU to the quirk as well. - -Patchset: amd-gpio ---- - arch/x86/kernel/acpi/boot.c | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) - -diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c -index 0116d27b29ea..af102c6f8e5b 100644 ---- a/arch/x86/kernel/acpi/boot.c -+++ b/arch/x86/kernel/acpi/boot.c -@@ -1237,12 +1237,19 @@ static void __init mp_config_acpi_legacy_irqs(void) - - static const struct dmi_system_id surface_quirk[] __initconst = { - { -- .ident = "Microsoft Surface Laptop 4 (AMD)", -+ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") - }, - }, -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") -+ }, -+ }, - {} - }; - --- -2.38.0 - diff --git a/patches/5.2/0001-surface-acpi.patch b/patches/5.2/0001-surface-acpi.patch deleted file mode 100644 index 88f593817..000000000 --- a/patches/5.2/0001-surface-acpi.patch +++ /dev/null @@ -1,4350 +0,0 @@ -From fe620408edbe977514da914279f549e7d8fb036a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 2 Oct 2019 22:36:03 +0200 -Subject: [PATCH 01/12] surface-acpi - ---- - drivers/acpi/acpica/dsopcode.c | 2 +- - drivers/acpi/acpica/exfield.c | 12 +- - drivers/platform/x86/Kconfig | 97 + - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/surface_acpi.c | 4010 +++++++++++++++++++++++++++ - drivers/tty/serdev/core.c | 111 +- - 6 files changed, 4217 insertions(+), 16 deletions(-) - create mode 100644 drivers/platform/x86/surface_acpi.c - -diff --git a/drivers/acpi/acpica/dsopcode.c b/drivers/acpi/acpica/dsopcode.c -index 10f32b62608e..7b2a4987f050 100644 ---- a/drivers/acpi/acpica/dsopcode.c -+++ b/drivers/acpi/acpica/dsopcode.c -@@ -123,7 +123,7 @@ acpi_ds_init_buffer_field(u16 aml_opcode, - - /* Offset is in bits, count is in bits */ - -- field_flags = AML_FIELD_ACCESS_BYTE; -+ field_flags = AML_FIELD_ACCESS_BUFFER; - bit_offset = offset; - bit_count = (u32) length_desc->integer.value; - -diff --git a/drivers/acpi/acpica/exfield.c b/drivers/acpi/acpica/exfield.c -index d3d2dbfba680..0b7f617a6e9b 100644 ---- a/drivers/acpi/acpica/exfield.c -+++ b/drivers/acpi/acpica/exfield.c -@@ -109,6 +109,7 @@ acpi_ex_read_data_from_field(struct acpi_walk_state *walk_state, - union acpi_operand_object *buffer_desc; - void *buffer; - u32 buffer_length; -+ u8 field_flags; - - ACPI_FUNCTION_TRACE_PTR(ex_read_data_from_field, obj_desc); - -@@ -157,11 +158,16 @@ acpi_ex_read_data_from_field(struct acpi_walk_state *walk_state, - * Note: Field.length is in bits. - */ - buffer_length = -- (acpi_size)ACPI_ROUND_BITS_UP_TO_BYTES(obj_desc->field.bit_length); -+ (acpi_size)ACPI_ROUND_BITS_UP_TO_BYTES(obj_desc->common_field.bit_length); -+ field_flags = obj_desc->common_field.field_flags; - -- if (buffer_length > acpi_gbl_integer_byte_width) { -+ if (buffer_length > acpi_gbl_integer_byte_width || -+ (field_flags & AML_FIELD_ACCESS_TYPE_MASK) == AML_FIELD_ACCESS_BUFFER) { - -- /* Field is too large for an Integer, create a Buffer instead */ -+ /* -+ * Field is either too large for an Integer, or a actually of type -+ * buffer, so create a Buffer. -+ */ - - buffer_desc = acpi_ut_create_buffer_object(buffer_length); - if (!buffer_desc) { -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 7c2fd1d72e18..c00cb830914a 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -623,6 +623,103 @@ config THINKPAD_ACPI_HOTKEY_POLL - If you are not sure, say Y here. The driver enables polling only if - it is strictly necessary to do so. - -+config SURFACE_ACPI -+ depends on ACPI -+ tristate "Microsoft Surface ACPI/Platform Drivers" -+ ---help--- -+ ACPI and platform drivers for Microsoft Surface devices. -+ -+config SURFACE_ACPI_SSH -+ bool "Surface Serial Hub Driver" -+ depends on SURFACE_ACPI -+ depends on X86_INTEL_LPSS -+ depends on SERIAL_8250_DW -+ depends on SERIAL_8250_DMA -+ depends on SERIAL_DEV_CTRL_TTYPORT -+ select CRC_CCITT -+ default y -+ ---help--- -+ Surface Serial Hub driver for 5th generation (or later) Microsoft -+ Surface devices. -+ -+ This is the base driver for the embedded serial controller found on -+ 5th generation (and later) Microsoft Surface devices (e.g. Book 2, -+ Laptop, Laptop 2, Pro 2017, Pro 6, ...). This driver itself only -+ provides access to the embedded controller and subsequent drivers are -+ required for the respective functionalities. -+ -+ If you have a 5th generation (or later) Microsoft Surface device, say -+ Y or M here. -+ -+config SURFACE_ACPI_SSH_DEBUG_DEVICE -+ bool "Surface Serial Hub Debug Device" -+ depends on SURFACE_ACPI_SSH -+ default n -+ ---help--- -+ Debug device for direct communication with the embedded controller -+ found on 5th generation (and later) Microsoft Surface devices (e.g. -+ Book 2, Laptop, Laptop 2, Pro 2017, Pro 6, ...) via sysfs. -+ -+ If you are not sure, say N here. -+ -+config SURFACE_ACPI_SAN -+ bool "Surface ACPI Notify Driver" -+ depends on SURFACE_ACPI_SSH -+ default y -+ ---help--- -+ Surface ACPI Notify driver for 5th generation (or later) Microsoft -+ Surface devices. -+ -+ This driver enables basic ACPI events and requests, such as battery -+ status requests/events, thermal events, lid status, and possibly more, -+ which would otherwise not work on these devices. -+ -+ If you are not sure, say Y here. -+ -+config SURFACE_ACPI_VHF -+ bool "Surface Virtual HID Framework Driver" -+ depends on SURFACE_ACPI_SSH -+ depends on HID -+ default y -+ ---help--- -+ Surface Virtual HID Framework driver for 5th generation (or later) -+ Microsoft Surface devices. -+ -+ This driver provides support for the Microsoft Virtual HID framework, -+ which is required for the Surface Laptop (1 and newer) keyboard. -+ -+ If you are not sure, say Y here. -+ -+config SURFACE_ACPI_DTX -+ bool "Surface Detachment System (DTX) Driver" -+ depends on SURFACE_ACPI_SSH -+ depends on INPUT -+ default y -+ ---help--- -+ Surface Detachment System (DTX) driver for the Microsoft Surface Book -+ 2. This driver provides support for proper detachment handling in -+ user-space, status-events relating to the base and support for -+ the safe-guard keeping the base attached when the discrete GPU -+ contained in it is running via the special /dev/surface-dtx device. -+ -+ Also provides a standard input device to provide SW_TABLET_MODE events -+ upon device mode change. -+ -+ If you are not sure, say Y here. -+ -+config SURFACE_ACPI_SID -+ bool "Surface Platform Integration Driver" -+ depends on SURFACE_ACPI_SSH -+ default y -+ ---help--- -+ Surface Platform Integration Driver for the Microsoft Surface Devices. -+ Currently only supports the Surface Book 2. This driver provides suport -+ for setting performance-modes via the perf_mode sysfs attribute. -+ Performance-modes directly influence the fan-profile of the device, -+ allowing to choose between higher performance or quieter operation. -+ -+ If you are not sure, say Y here. -+ - config SENSORS_HDAPS - tristate "Thinkpad Hard Drive Active Protection System (hdaps)" - depends on INPUT -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 87b0069bd781..8b12c19dc165 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -39,6 +39,7 @@ obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o - obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o - obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o - obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o -+obj-$(CONFIG_SURFACE_ACPI) += surface_acpi.o - obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o - obj-$(CONFIG_FUJITSU_LAPTOP) += fujitsu-laptop.o - obj-$(CONFIG_FUJITSU_TABLET) += fujitsu-tablet.o -diff --git a/drivers/platform/x86/surface_acpi.c b/drivers/platform/x86/surface_acpi.c -new file mode 100644 -index 000000000000..5dbf48a3d9b3 ---- /dev/null -+++ b/drivers/platform/x86/surface_acpi.c -@@ -0,0 +1,4010 @@ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+ -+#define USB_VENDOR_ID_MICROSOFT 0x045e -+#define USB_DEVICE_ID_MS_VHF 0xf001 -+#define USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION 0x0922 -+ -+#define SG5_PARAM_PERM (S_IRUGO | S_IWUSR) -+ -+ -+/************************************************************************* -+ * Surface Serial Hub driver (cross-driver interface) -+ */ -+ -+#ifdef CONFIG_SURFACE_ACPI_SSH -+ -+/* -+ * Maximum request payload size in bytes. -+ * Value based on ACPI (255 bytes minus header/status bytes). -+ */ -+#define SURFACEGEN5_MAX_RQST_PAYLOAD (255 - 10) -+ -+/* -+ * Maximum response payload size in bytes. -+ * Value based on ACPI (255 bytes minus header/status bytes). -+ */ -+#define SURFACEGEN5_MAX_RQST_RESPONSE (255 - 4) -+ -+#define SURFACEGEN5_RQID_EVENT_BITS 5 -+ -+#define SURFACEGEN5_EVENT_IMMEDIATE ((unsigned long) -1) -+ -+ -+struct surfacegen5_buf { -+ u8 cap; -+ u8 len; -+ u8 *data; -+}; -+ -+struct surfacegen5_rqst { -+ u8 tc; -+ u8 iid; -+ u8 cid; -+ u8 snc; -+ u8 cdl; -+ u8 *pld; -+}; -+ -+struct surfacegen5_event { -+ u16 rqid; -+ u8 tc; -+ u8 iid; -+ u8 cid; -+ u8 len; -+ u8 *pld; -+}; -+ -+ -+typedef int (*surfacegen5_ec_event_handler_fn)(struct surfacegen5_event *event, void *data); -+typedef unsigned long (*surfacegen5_ec_event_handler_delay)(struct surfacegen5_event *event, void *data); -+ -+int surfacegen5_ec_consumer_register(struct device *consumer); -+ -+int surfacegen5_ec_rqst(const struct surfacegen5_rqst *rqst, struct surfacegen5_buf *result); -+ -+int surfacegen5_ec_enable_event_source(u8 tc, u8 unknown, u16 rqid); -+int surfacegen5_ec_disable_event_source(u8 tc, u8 unknown, u16 rqid); -+int surfacegen5_ec_remove_event_handler(u16 rqid); -+int surfacegen5_ec_set_event_handler(u16 rqid, surfacegen5_ec_event_handler_fn fn, void *data); -+int surfacegen5_ec_set_delayed_event_handler(u16 rqid, -+ surfacegen5_ec_event_handler_fn fn, -+ surfacegen5_ec_event_handler_delay delay, void *data); -+ -+#endif /* CONFIG_SURFACE_ACPI_SSH */ -+ -+ -+/************************************************************************* -+ * Surface Serial Hub Debug Device (cross-driver interface) -+ */ -+ -+#ifdef CONFIG_SURFACE_ACPI_SSH -+ -+int surfacegen5_ssh_sysfs_register(struct device *dev); -+void surfacegen5_ssh_sysfs_unregister(struct device *dev); -+ -+#endif /* CONFIG_SURFACE_ACPI_SSH */ -+ -+ -+/************************************************************************* -+ * Surface Serial Hub driver (private implementation) -+ */ -+ -+#ifdef CONFIG_SURFACE_ACPI_SSH -+ -+#define SG5_RQST_TAG_FULL "surfacegen5_ec_rqst: " -+#define SG5_RQST_TAG "rqst: " -+#define SG5_EVENT_TAG "event: " -+#define SG5_RECV_TAG "recv: " -+ -+#define SG5_SUPPORTED_FLOW_CONTROL_MASK (~((u8) ACPI_UART_FLOW_CONTROL_HW)) -+ -+#define SG5_BYTELEN_SYNC 2 -+#define SG5_BYTELEN_TERM 2 -+#define SG5_BYTELEN_CRC 2 -+#define SG5_BYTELEN_CTRL 4 // command-header, ACK, or RETRY -+#define SG5_BYTELEN_CMDFRAME 8 // without payload -+ -+#define SG5_MAX_WRITE ( \ -+ SG5_BYTELEN_SYNC \ -+ + SG5_BYTELEN_CTRL \ -+ + SG5_BYTELEN_CRC \ -+ + SG5_BYTELEN_CMDFRAME \ -+ + SURFACEGEN5_MAX_RQST_PAYLOAD \ -+ + SG5_BYTELEN_CRC \ -+) -+ -+#define SG5_MSG_LEN_CTRL ( \ -+ SG5_BYTELEN_SYNC \ -+ + SG5_BYTELEN_CTRL \ -+ + SG5_BYTELEN_CRC \ -+ + SG5_BYTELEN_TERM \ -+) -+ -+#define SG5_MSG_LEN_CMD_BASE ( \ -+ SG5_BYTELEN_SYNC \ -+ + SG5_BYTELEN_CTRL \ -+ + SG5_BYTELEN_CRC \ -+ + SG5_BYTELEN_CRC \ -+) // without payload and command-frame -+ -+#define SG5_WRITE_TIMEOUT msecs_to_jiffies(1000) -+#define SG5_READ_TIMEOUT msecs_to_jiffies(1000) -+#define SG5_NUM_RETRY 3 -+ -+#define SG5_WRITE_BUF_LEN SG5_MAX_WRITE -+#define SG5_READ_BUF_LEN 512 // must be power of 2 -+#define SG5_EVAL_BUF_LEN SG5_MAX_WRITE // also works for reading -+ -+#define SG5_FRAME_TYPE_CMD 0x80 -+#define SG5_FRAME_TYPE_ACK 0x40 -+#define SG5_FRAME_TYPE_RETRY 0x04 -+ -+#define SG5_FRAME_OFFS_CTRL SG5_BYTELEN_SYNC -+#define SG5_FRAME_OFFS_CTRL_CRC (SG5_FRAME_OFFS_CTRL + SG5_BYTELEN_CTRL) -+#define SG5_FRAME_OFFS_TERM (SG5_FRAME_OFFS_CTRL_CRC + SG5_BYTELEN_CRC) -+#define SG5_FRAME_OFFS_CMD SG5_FRAME_OFFS_TERM // either TERM or CMD -+#define SG5_FRAME_OFFS_CMD_PLD (SG5_FRAME_OFFS_CMD + SG5_BYTELEN_CMDFRAME) -+ -+/* -+ * A note on Request IDs (RQIDs): -+ * 0x0000 is not a valid RQID -+ * 0x0001 is valid, but reserved for Surface Laptop keyboard events -+ */ -+#define SG5_NUM_EVENT_TYPES ((1 << SURFACEGEN5_RQID_EVENT_BITS) - 1) -+ -+/* -+ * Sync: aa 55 -+ * Terminate: ff ff -+ * -+ * Request Message: sync cmd-hdr crc(cmd-hdr) cmd-rqst-frame crc(cmd-rqst-frame) -+ * Ack Message: sync ack crc(ack) terminate -+ * Retry Message: sync retry crc(retry) terminate -+ * Response Message: sync cmd-hdr crc(cmd-hdr) cmd-resp-frame crc(cmd-resp-frame) -+ * -+ * Command Header: 80 LEN 00 SEQ -+ * Ack: 40 00 00 SEQ -+ * Retry: 04 00 00 00 -+ * Command Request Frame: 80 RTC 01 00 RIID RQID RCID PLD -+ * Command Response Frame: 80 RTC 00 01 RIID RQID RCID PLD -+ */ -+ -+struct surfacegen5_frame_ctrl { -+ u8 type; -+ u8 len; // without crc -+ u8 pad; -+ u8 seq; -+} __packed; -+ -+struct surfacegen5_frame_cmd { -+ u8 type; -+ u8 tc; -+ u8 unknown1; -+ u8 unknown2; -+ u8 iid; -+ u8 rqid_lo; // id for request/response matching (low byte) -+ u8 rqid_hi; // id for request/response matching (high byte) -+ u8 cid; -+} __packed; -+ -+ -+enum surfacegen5_ec_state { -+ SG5_EC_UNINITIALIZED, -+ SG5_EC_INITIALIZED, -+ SG5_EC_SUSPENDED, -+}; -+ -+struct surfacegen5_ec_counters { -+ u8 seq; // control sequence id -+ u16 rqid; // id for request/response matching -+}; -+ -+struct surfacegen5_ec_writer { -+ u8 *data; -+ u8 *ptr; -+} __packed; -+ -+enum surfacegen5_ec_receiver_state { -+ SG5_RCV_DISCARD, -+ SG5_RCV_CONTROL, -+ SG5_RCV_COMMAND, -+}; -+ -+struct surfacegen5_ec_receiver { -+ spinlock_t lock; -+ enum surfacegen5_ec_receiver_state state; -+ struct completion signal; -+ struct kfifo fifo; -+ struct { -+ bool pld; -+ u8 seq; -+ u16 rqid; -+ } expect; -+ struct { -+ u16 cap; -+ u16 len; -+ u8 *ptr; -+ } eval_buf; -+}; -+ -+struct surfacegen5_ec_event_handler { -+ surfacegen5_ec_event_handler_fn handler; -+ surfacegen5_ec_event_handler_delay delay; -+ void *data; -+}; -+ -+struct surfacegen5_ec_events { -+ spinlock_t lock; -+ struct workqueue_struct *queue_ack; -+ struct workqueue_struct *queue_evt; -+ struct surfacegen5_ec_event_handler handler[SG5_NUM_EVENT_TYPES]; -+}; -+ -+struct surfacegen5_ec { -+ struct mutex lock; -+ enum surfacegen5_ec_state state; -+ struct serdev_device *serdev; -+ struct surfacegen5_ec_counters counter; -+ struct surfacegen5_ec_writer writer; -+ struct surfacegen5_ec_receiver receiver; -+ struct surfacegen5_ec_events events; -+}; -+ -+struct surfacegen5_fifo_packet { -+ u8 type; // packet type (ACK/RETRY/CMD) -+ u8 seq; -+ u8 len; -+}; -+ -+struct surfacegen5_event_work { -+ refcount_t refcount; -+ struct surfacegen5_ec *ec; -+ struct work_struct work_ack; -+ struct delayed_work work_evt; -+ struct surfacegen5_event event; -+ u8 seq; -+}; -+ -+ -+static struct surfacegen5_ec surfacegen5_ec = { -+ .lock = __MUTEX_INITIALIZER(surfacegen5_ec.lock), -+ .state = SG5_EC_UNINITIALIZED, -+ .serdev = NULL, -+ .counter = { -+ .seq = 0, -+ .rqid = 0, -+ }, -+ .writer = { -+ .data = NULL, -+ .ptr = NULL, -+ }, -+ .receiver = { -+ .lock = __SPIN_LOCK_UNLOCKED(), -+ .state = SG5_RCV_DISCARD, -+ .expect = {}, -+ }, -+ .events = { -+ .lock = __SPIN_LOCK_UNLOCKED(), -+ .handler = {}, -+ } -+}; -+ -+ -+static int surfacegen5_ec_rqst_unlocked(struct surfacegen5_ec *ec, -+ const struct surfacegen5_rqst *rqst, -+ struct surfacegen5_buf *result); -+ -+ -+inline static struct surfacegen5_ec *surfacegen5_ec_acquire(void) -+{ -+ struct surfacegen5_ec *ec = &surfacegen5_ec; -+ -+ mutex_lock(&ec->lock); -+ return ec; -+} -+ -+inline static void surfacegen5_ec_release(struct surfacegen5_ec *ec) -+{ -+ mutex_unlock(&ec->lock); -+} -+ -+inline static struct surfacegen5_ec *surfacegen5_ec_acquire_init(void) -+{ -+ struct surfacegen5_ec *ec = surfacegen5_ec_acquire(); -+ -+ if (ec->state == SG5_EC_UNINITIALIZED) { -+ surfacegen5_ec_release(ec); -+ return NULL; -+ } -+ -+ return ec; -+} -+ -+int surfacegen5_ec_consumer_register(struct device *consumer) -+{ -+ u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; -+ struct surfacegen5_ec *ec; -+ struct device_link *link; -+ -+ ec = surfacegen5_ec_acquire_init(); -+ if (!ec) { -+ return -ENXIO; -+ } -+ -+ link = device_link_add(consumer, &ec->serdev->dev, flags); -+ if (!link) { -+ return -EFAULT; -+ } -+ -+ surfacegen5_ec_release(ec); -+ return 0; -+} -+ -+ -+inline static u16 surfacegen5_rqid_to_rqst(u16 rqid) { -+ return rqid << SURFACEGEN5_RQID_EVENT_BITS; -+} -+ -+inline static bool surfacegen5_rqid_is_event(u16 rqid) { -+ const u16 mask = (1 << SURFACEGEN5_RQID_EVENT_BITS) - 1; -+ return rqid != 0 && (rqid | mask) == mask; -+} -+ -+int surfacegen5_ec_enable_event_source(u8 tc, u8 unknown, u16 rqid) -+{ -+ struct surfacegen5_ec *ec; -+ -+ u8 pld[4] = { tc, unknown, rqid & 0xff, rqid >> 8 }; -+ u8 buf[1] = { 0x00 }; -+ -+ struct surfacegen5_rqst rqst = { -+ .tc = 0x01, -+ .iid = 0x00, -+ .cid = 0x0b, -+ .snc = 0x01, -+ .cdl = 0x04, -+ .pld = pld, -+ }; -+ -+ struct surfacegen5_buf result = { -+ result.cap = ARRAY_SIZE(buf), -+ result.len = 0, -+ result.data = buf, -+ }; -+ -+ int status; -+ -+ // only allow RQIDs that lie within event spectrum -+ if (!surfacegen5_rqid_is_event(rqid)) { -+ return -EINVAL; -+ } -+ -+ ec = surfacegen5_ec_acquire_init(); -+ if (!ec) { -+ printk(KERN_WARNING SG5_RQST_TAG_FULL "embedded controller is uninitialized\n"); -+ return -ENXIO; -+ } -+ -+ if (ec->state == SG5_EC_SUSPENDED) { -+ dev_warn(&ec->serdev->dev, SG5_RQST_TAG "embedded controller is suspended\n"); -+ -+ surfacegen5_ec_release(ec); -+ return -EPERM; -+ } -+ -+ status = surfacegen5_ec_rqst_unlocked(ec, &rqst, &result); -+ -+ if (buf[0] != 0x00) { -+ dev_warn(&ec->serdev->dev, -+ "unexpected result while enabling event source: 0x%02x\n", -+ buf[0]); -+ } -+ -+ surfacegen5_ec_release(ec); -+ return status; -+ -+} -+ -+int surfacegen5_ec_disable_event_source(u8 tc, u8 unknown, u16 rqid) -+{ -+ struct surfacegen5_ec *ec; -+ -+ u8 pld[4] = { tc, unknown, rqid & 0xff, rqid >> 8 }; -+ u8 buf[1] = { 0x00 }; -+ -+ struct surfacegen5_rqst rqst = { -+ .tc = 0x01, -+ .iid = 0x00, -+ .cid = 0x0c, -+ .snc = 0x01, -+ .cdl = 0x04, -+ .pld = pld, -+ }; -+ -+ struct surfacegen5_buf result = { -+ result.cap = ARRAY_SIZE(buf), -+ result.len = 0, -+ result.data = buf, -+ }; -+ -+ int status; -+ -+ // only allow RQIDs that lie within event spectrum -+ if (!surfacegen5_rqid_is_event(rqid)) { -+ return -EINVAL; -+ } -+ -+ ec = surfacegen5_ec_acquire_init(); -+ if (!ec) { -+ printk(KERN_WARNING SG5_RQST_TAG_FULL "embedded controller is uninitialized\n"); -+ return -ENXIO; -+ } -+ -+ if (ec->state == SG5_EC_SUSPENDED) { -+ dev_warn(&ec->serdev->dev, SG5_RQST_TAG "embedded controller is suspended\n"); -+ -+ surfacegen5_ec_release(ec); -+ return -EPERM; -+ } -+ -+ status = surfacegen5_ec_rqst_unlocked(ec, &rqst, &result); -+ -+ if (buf[0] != 0x00) { -+ dev_warn(&ec->serdev->dev, -+ "unexpected result while disabling event source: 0x%02x\n", -+ buf[0]); -+ } -+ -+ surfacegen5_ec_release(ec); -+ return status; -+} -+ -+int surfacegen5_ec_set_delayed_event_handler( -+ u16 rqid, surfacegen5_ec_event_handler_fn fn, -+ surfacegen5_ec_event_handler_delay delay, -+ void *data) -+{ -+ struct surfacegen5_ec *ec; -+ unsigned long flags; -+ -+ if (!surfacegen5_rqid_is_event(rqid)) { -+ return -EINVAL; -+ } -+ -+ ec = surfacegen5_ec_acquire_init(); -+ if (!ec) { -+ return -ENXIO; -+ } -+ -+ spin_lock_irqsave(&ec->events.lock, flags); -+ -+ // 0 is not a valid event RQID -+ ec->events.handler[rqid - 1].handler = fn; -+ ec->events.handler[rqid - 1].delay = delay; -+ ec->events.handler[rqid - 1].data = data; -+ -+ spin_unlock_irqrestore(&ec->events.lock, flags); -+ surfacegen5_ec_release(ec); -+ -+ return 0; -+} -+ -+int surfacegen5_ec_set_event_handler( -+ u16 rqid, surfacegen5_ec_event_handler_fn fn, void *data) -+{ -+ return surfacegen5_ec_set_delayed_event_handler(rqid, fn, NULL, data); -+} -+ -+int surfacegen5_ec_remove_event_handler(u16 rqid) -+{ -+ struct surfacegen5_ec *ec; -+ unsigned long flags; -+ -+ if (!surfacegen5_rqid_is_event(rqid)) { -+ return -EINVAL; -+ } -+ -+ ec = surfacegen5_ec_acquire_init(); -+ if (!ec) { -+ return -ENXIO; -+ } -+ -+ spin_lock_irqsave(&ec->events.lock, flags); -+ -+ // 0 is not a valid event RQID -+ ec->events.handler[rqid - 1].handler = NULL; -+ ec->events.handler[rqid - 1].delay = NULL; -+ ec->events.handler[rqid - 1].data = NULL; -+ -+ spin_unlock_irqrestore(&ec->events.lock, flags); -+ surfacegen5_ec_release(ec); -+ -+ /* -+ * Make sure that the handler is not in use any more after we've -+ * removed it. -+ */ -+ flush_workqueue(ec->events.queue_evt); -+ -+ return 0; -+} -+ -+ -+inline static u16 surfacegen5_ssh_crc(const u8 *buf, size_t size) -+{ -+ return crc_ccitt_false(0xffff, buf, size); -+} -+ -+inline static void surfacegen5_ssh_write_u16(struct surfacegen5_ec_writer *writer, u16 in) -+{ -+ put_unaligned_le16(in, writer->ptr); -+ writer->ptr += 2; -+} -+ -+inline static void surfacegen5_ssh_write_crc(struct surfacegen5_ec_writer *writer, -+ const u8 *buf, size_t size) -+{ -+ surfacegen5_ssh_write_u16(writer, surfacegen5_ssh_crc(buf, size)); -+} -+ -+inline static void surfacegen5_ssh_write_syn(struct surfacegen5_ec_writer *writer) -+{ -+ u8 *w = writer->ptr; -+ -+ *w++ = 0xaa; -+ *w++ = 0x55; -+ -+ writer->ptr = w; -+} -+ -+inline static void surfacegen5_ssh_write_ter(struct surfacegen5_ec_writer *writer) -+{ -+ u8 *w = writer->ptr; -+ -+ *w++ = 0xff; -+ *w++ = 0xff; -+ -+ writer->ptr = w; -+} -+ -+inline static void surfacegen5_ssh_write_buf(struct surfacegen5_ec_writer *writer, -+ u8 *in, size_t len) -+{ -+ writer->ptr = memcpy(writer->ptr, in, len) + len; -+} -+ -+inline static void surfacegen5_ssh_write_hdr(struct surfacegen5_ec_writer *writer, -+ const struct surfacegen5_rqst *rqst, -+ struct surfacegen5_ec *ec) -+{ -+ struct surfacegen5_frame_ctrl *hdr = (struct surfacegen5_frame_ctrl *)writer->ptr; -+ u8 *begin = writer->ptr; -+ -+ hdr->type = SG5_FRAME_TYPE_CMD; -+ hdr->len = SG5_BYTELEN_CMDFRAME + rqst->cdl; // without CRC -+ hdr->pad = 0x00; -+ hdr->seq = ec->counter.seq; -+ -+ writer->ptr += sizeof(*hdr); -+ -+ surfacegen5_ssh_write_crc(writer, begin, writer->ptr - begin); -+} -+ -+inline static void surfacegen5_ssh_write_cmd(struct surfacegen5_ec_writer *writer, -+ const struct surfacegen5_rqst *rqst, -+ struct surfacegen5_ec *ec) -+{ -+ struct surfacegen5_frame_cmd *cmd = (struct surfacegen5_frame_cmd *)writer->ptr; -+ u8 *begin = writer->ptr; -+ -+ u16 rqid = surfacegen5_rqid_to_rqst(ec->counter.rqid); -+ u8 rqid_lo = rqid & 0xFF; -+ u8 rqid_hi = rqid >> 8; -+ -+ cmd->type = SG5_FRAME_TYPE_CMD; -+ cmd->tc = rqst->tc; -+ cmd->unknown1 = 0x01; -+ cmd->unknown2 = 0x00; -+ cmd->iid = rqst->iid; -+ cmd->rqid_lo = rqid_lo; -+ cmd->rqid_hi = rqid_hi; -+ cmd->cid = rqst->cid; -+ -+ writer->ptr += sizeof(*cmd); -+ -+ surfacegen5_ssh_write_buf(writer, rqst->pld, rqst->cdl); -+ surfacegen5_ssh_write_crc(writer, begin, writer->ptr - begin); -+} -+ -+inline static void surfacegen5_ssh_write_ack(struct surfacegen5_ec_writer *writer, u8 seq) -+{ -+ struct surfacegen5_frame_ctrl *ack = (struct surfacegen5_frame_ctrl *)writer->ptr; -+ u8 *begin = writer->ptr; -+ -+ ack->type = SG5_FRAME_TYPE_ACK; -+ ack->len = 0x00; -+ ack->pad = 0x00; -+ ack->seq = seq; -+ -+ writer->ptr += sizeof(*ack); -+ -+ surfacegen5_ssh_write_crc(writer, begin, writer->ptr - begin); -+} -+ -+inline static void surfacegen5_ssh_writer_reset(struct surfacegen5_ec_writer *writer) -+{ -+ writer->ptr = writer->data; -+} -+ -+inline static int surfacegen5_ssh_writer_flush(struct surfacegen5_ec *ec) -+{ -+ struct surfacegen5_ec_writer *writer = &ec->writer; -+ struct serdev_device *serdev = ec->serdev; -+ int status; -+ -+ size_t len = writer->ptr - writer->data; -+ -+ dev_dbg(&ec->serdev->dev, "sending message\n"); -+ print_hex_dump_debug("send: ", DUMP_PREFIX_OFFSET, 16, 1, -+ writer->data, writer->ptr - writer->data, false); -+ -+ status = serdev_device_write(serdev, writer->data, len, SG5_WRITE_TIMEOUT); -+ return status >= 0 ? 0 : status; -+} -+ -+inline static void surfacegen5_ssh_write_msg_cmd(struct surfacegen5_ec *ec, -+ const struct surfacegen5_rqst *rqst) -+{ -+ surfacegen5_ssh_writer_reset(&ec->writer); -+ surfacegen5_ssh_write_syn(&ec->writer); -+ surfacegen5_ssh_write_hdr(&ec->writer, rqst, ec); -+ surfacegen5_ssh_write_cmd(&ec->writer, rqst, ec); -+} -+ -+inline static void surfacegen5_ssh_write_msg_ack(struct surfacegen5_ec *ec, u8 seq) -+{ -+ surfacegen5_ssh_writer_reset(&ec->writer); -+ surfacegen5_ssh_write_syn(&ec->writer); -+ surfacegen5_ssh_write_ack(&ec->writer, seq); -+ surfacegen5_ssh_write_ter(&ec->writer); -+} -+ -+inline static void surfacegen5_ssh_receiver_restart(struct surfacegen5_ec *ec, -+ const struct surfacegen5_rqst *rqst) -+{ -+ unsigned long flags; -+ -+ spin_lock_irqsave(&ec->receiver.lock, flags); -+ reinit_completion(&ec->receiver.signal); -+ ec->receiver.state = SG5_RCV_CONTROL; -+ ec->receiver.expect.pld = rqst->snc; -+ ec->receiver.expect.seq = ec->counter.seq; -+ ec->receiver.expect.rqid = surfacegen5_rqid_to_rqst(ec->counter.rqid); -+ ec->receiver.eval_buf.len = 0; -+ spin_unlock_irqrestore(&ec->receiver.lock, flags); -+} -+ -+inline static void surfacegen5_ssh_receiver_discard(struct surfacegen5_ec *ec) -+{ -+ unsigned long flags; -+ -+ spin_lock_irqsave(&ec->receiver.lock, flags); -+ ec->receiver.state = SG5_RCV_DISCARD; -+ ec->receiver.eval_buf.len = 0; -+ kfifo_reset(&ec->receiver.fifo); -+ spin_unlock_irqrestore(&ec->receiver.lock, flags); -+} -+ -+static int surfacegen5_ec_rqst_unlocked(struct surfacegen5_ec *ec, -+ const struct surfacegen5_rqst *rqst, -+ struct surfacegen5_buf *result) -+{ -+ struct device *dev = &ec->serdev->dev; -+ struct surfacegen5_fifo_packet packet = {}; -+ int status; -+ int try; -+ unsigned int rem; -+ -+ if (rqst->cdl > SURFACEGEN5_MAX_RQST_PAYLOAD) { -+ dev_err(dev, SG5_RQST_TAG "request payload too large\n"); -+ return -EINVAL; -+ } -+ -+ // write command in buffer, we may need it multiple times -+ surfacegen5_ssh_write_msg_cmd(ec, rqst); -+ surfacegen5_ssh_receiver_restart(ec, rqst); -+ -+ // send command, try to get an ack response -+ for (try = 0; try < SG5_NUM_RETRY; try++) { -+ status = surfacegen5_ssh_writer_flush(ec); -+ if (status) { -+ goto ec_rqst_out; -+ } -+ -+ rem = wait_for_completion_timeout(&ec->receiver.signal, SG5_READ_TIMEOUT); -+ if (rem) { -+ // completion assures valid packet, thus ignore returned length -+ (void) !kfifo_out(&ec->receiver.fifo, &packet, sizeof(packet)); -+ -+ if (packet.type == SG5_FRAME_TYPE_ACK) { -+ break; -+ } -+ } -+ } -+ -+ // check if we ran out of tries? -+ if (try >= SG5_NUM_RETRY) { -+ dev_err(dev, SG5_RQST_TAG "communication failed %d times, giving up\n", try); -+ status = -EIO; -+ goto ec_rqst_out; -+ } -+ -+ ec->counter.seq += 1; -+ ec->counter.rqid += 1; -+ -+ // get command response/payload -+ if (rqst->snc && result) { -+ rem = wait_for_completion_timeout(&ec->receiver.signal, SG5_READ_TIMEOUT); -+ if (rem) { -+ // completion assures valid packet, thus ignore returned length -+ (void) !kfifo_out(&ec->receiver.fifo, &packet, sizeof(packet)); -+ -+ if (result->cap < packet.len) { -+ status = -EINVAL; -+ goto ec_rqst_out; -+ } -+ -+ // completion assures valid packet, thus ignore returned length -+ (void) !kfifo_out(&ec->receiver.fifo, result->data, packet.len); -+ result->len = packet.len; -+ } else { -+ dev_err(dev, SG5_RQST_TAG "communication timed out\n"); -+ status = -EIO; -+ goto ec_rqst_out; -+ } -+ -+ // send ACK -+ surfacegen5_ssh_write_msg_ack(ec, packet.seq); -+ status = surfacegen5_ssh_writer_flush(ec); -+ if (status) { -+ goto ec_rqst_out; -+ } -+ } -+ -+ec_rqst_out: -+ surfacegen5_ssh_receiver_discard(ec); -+ return status; -+} -+ -+int surfacegen5_ec_rqst(const struct surfacegen5_rqst *rqst, struct surfacegen5_buf *result) -+{ -+ struct surfacegen5_ec *ec; -+ int status; -+ -+ ec = surfacegen5_ec_acquire_init(); -+ if (!ec) { -+ printk(KERN_WARNING SG5_RQST_TAG_FULL "embedded controller is uninitialized\n"); -+ return -ENXIO; -+ } -+ -+ if (ec->state == SG5_EC_SUSPENDED) { -+ dev_warn(&ec->serdev->dev, SG5_RQST_TAG "embedded controller is suspended\n"); -+ -+ surfacegen5_ec_release(ec); -+ return -EPERM; -+ } -+ -+ status = surfacegen5_ec_rqst_unlocked(ec, rqst, result); -+ -+ surfacegen5_ec_release(ec); -+ return status; -+} -+ -+ -+static int surfacegen5_ssh_ec_resume(struct surfacegen5_ec *ec) -+{ -+ u8 buf[1] = { 0x00 }; -+ -+ struct surfacegen5_rqst rqst = { -+ .tc = 0x01, -+ .iid = 0x00, -+ .cid = 0x16, -+ .snc = 0x01, -+ .cdl = 0x00, -+ .pld = NULL, -+ }; -+ -+ struct surfacegen5_buf result = { -+ result.cap = ARRAY_SIZE(buf), -+ result.len = 0, -+ result.data = buf, -+ }; -+ -+ int status = surfacegen5_ec_rqst_unlocked(ec, &rqst, &result); -+ if (status) { -+ return status; -+ } -+ -+ if (buf[0] != 0x00) { -+ dev_warn(&ec->serdev->dev, -+ "unexpected result while trying to resume EC: 0x%02x\n", -+ buf[0]); -+ } -+ -+ return 0; -+} -+ -+static int surfacegen5_ssh_ec_suspend(struct surfacegen5_ec *ec) -+{ -+ u8 buf[1] = { 0x00 }; -+ -+ struct surfacegen5_rqst rqst = { -+ .tc = 0x01, -+ .iid = 0x00, -+ .cid = 0x15, -+ .snc = 0x01, -+ .cdl = 0x00, -+ .pld = NULL, -+ }; -+ -+ struct surfacegen5_buf result = { -+ result.cap = ARRAY_SIZE(buf), -+ result.len = 0, -+ result.data = buf, -+ }; -+ -+ int status = surfacegen5_ec_rqst_unlocked(ec, &rqst, &result); -+ if (status) { -+ return status; -+ } -+ -+ if (buf[0] != 0x00) { -+ dev_warn(&ec->serdev->dev, -+ "unexpected result while trying to suspend EC: 0x%02x\n", -+ buf[0]); -+ } -+ -+ return 0; -+} -+ -+ -+inline static bool surfacegen5_ssh_is_valid_syn(const u8 *ptr) -+{ -+ return ptr[0] == 0xaa && ptr[1] == 0x55; -+} -+ -+inline static bool surfacegen5_ssh_is_valid_ter(const u8 *ptr) -+{ -+ return ptr[0] == 0xff && ptr[1] == 0xff; -+} -+ -+inline static bool surfacegen5_ssh_is_valid_crc(const u8 *begin, const u8 *end) -+{ -+ u16 crc = surfacegen5_ssh_crc(begin, end - begin); -+ return (end[0] == (crc & 0xff)) && (end[1] == (crc >> 8)); -+} -+ -+ -+static int surfacegen5_ssh_send_ack(struct surfacegen5_ec *ec, u8 seq) -+{ -+ int status; -+ u8 buf[SG5_MSG_LEN_CTRL]; -+ u16 crc; -+ -+ buf[0] = 0xaa; -+ buf[1] = 0x55; -+ buf[2] = 0x40; -+ buf[3] = 0x00; -+ buf[4] = 0x00; -+ buf[5] = seq; -+ -+ crc = surfacegen5_ssh_crc(buf + SG5_FRAME_OFFS_CTRL, SG5_BYTELEN_CTRL); -+ buf[6] = crc & 0xff; -+ buf[7] = crc >> 8; -+ -+ buf[8] = 0xff; -+ buf[9] = 0xff; -+ -+ dev_dbg(&ec->serdev->dev, "sending message\n"); -+ print_hex_dump_debug("send: ", DUMP_PREFIX_OFFSET, 16, 1, -+ buf, SG5_MSG_LEN_CTRL, false); -+ -+ status = serdev_device_write(ec->serdev, buf, SG5_MSG_LEN_CTRL, SG5_WRITE_TIMEOUT); -+ return status >= 0 ? 0 : status; -+} -+ -+static void surfacegen5_event_work_ack_handler(struct work_struct *_work) -+{ -+ struct surfacegen5_event_work *work; -+ struct surfacegen5_event *event; -+ struct surfacegen5_ec *ec; -+ struct device *dev; -+ int status; -+ -+ work = container_of(_work, struct surfacegen5_event_work, work_ack); -+ event = &work->event; -+ ec = work->ec; -+ dev = &ec->serdev->dev; -+ -+ // make sure we load a fresh ec state -+ smp_mb(); -+ -+ if (ec->state == SG5_EC_INITIALIZED) { -+ status = surfacegen5_ssh_send_ack(ec, work->seq); -+ if (status) { -+ dev_err(dev, SG5_EVENT_TAG "failed to send ACK: %d\n", status); -+ } -+ } -+ -+ if (refcount_dec_and_test(&work->refcount)) { -+ kfree(work); -+ } -+} -+ -+static void surfacegen5_event_work_evt_handler(struct work_struct *_work) -+{ -+ struct delayed_work *dwork = (struct delayed_work *)_work; -+ struct surfacegen5_event_work *work; -+ struct surfacegen5_event *event; -+ struct surfacegen5_ec *ec; -+ struct device *dev; -+ unsigned long flags; -+ -+ surfacegen5_ec_event_handler_fn handler; -+ void *handler_data; -+ -+ int status = 0; -+ -+ work = container_of(dwork, struct surfacegen5_event_work, work_evt); -+ event = &work->event; -+ ec = work->ec; -+ dev = &ec->serdev->dev; -+ -+ spin_lock_irqsave(&ec->events.lock, flags); -+ handler = ec->events.handler[event->rqid - 1].handler; -+ handler_data = ec->events.handler[event->rqid - 1].data; -+ spin_unlock_irqrestore(&ec->events.lock, flags); -+ -+ /* -+ * During handler removal or driver release, we ensure every event gets -+ * handled before return of that function. Thus a handler obtained here is -+ * guaranteed to be valid at least until this function returns. -+ */ -+ -+ if (handler) { -+ status = handler(event, handler_data); -+ } else { -+ dev_warn(dev, SG5_EVENT_TAG "unhandled event (rqid: %04x)\n", event->rqid); -+ } -+ -+ if (status) { -+ dev_err(dev, SG5_EVENT_TAG "error handling event: %d\n", status); -+ } -+ -+ if (refcount_dec_and_test(&work->refcount)) { -+ kfree(work); -+ } -+} -+ -+static void surfacegen5_ssh_handle_event(struct surfacegen5_ec *ec, const u8 *buf) -+{ -+ struct device *dev = &ec->serdev->dev; -+ const struct surfacegen5_frame_ctrl *ctrl; -+ const struct surfacegen5_frame_cmd *cmd; -+ struct surfacegen5_event_work *work; -+ unsigned long flags; -+ u16 pld_len; -+ -+ surfacegen5_ec_event_handler_delay delay_fn; -+ void *handler_data; -+ unsigned long delay = 0; -+ -+ ctrl = (const struct surfacegen5_frame_ctrl *)(buf + SG5_FRAME_OFFS_CTRL); -+ cmd = (const struct surfacegen5_frame_cmd *)(buf + SG5_FRAME_OFFS_CMD); -+ -+ pld_len = ctrl->len - SG5_BYTELEN_CMDFRAME; -+ -+ work = kzalloc(sizeof(struct surfacegen5_event_work) + pld_len, GFP_ATOMIC); -+ if (!work) { -+ dev_warn(dev, SG5_EVENT_TAG "failed to allocate memory, dropping event\n"); -+ return; -+ } -+ -+ refcount_set(&work->refcount, 2); -+ work->ec = ec; -+ work->seq = ctrl->seq; -+ work->event.rqid = (cmd->rqid_hi << 8) | cmd->rqid_lo; -+ work->event.tc = cmd->tc; -+ work->event.iid = cmd->iid; -+ work->event.cid = cmd->cid; -+ work->event.len = pld_len; -+ work->event.pld = ((u8*) work) + sizeof(struct surfacegen5_event_work); -+ -+ memcpy(work->event.pld, buf + SG5_FRAME_OFFS_CMD_PLD, pld_len); -+ -+ INIT_WORK(&work->work_ack, surfacegen5_event_work_ack_handler); -+ queue_work(ec->events.queue_ack, &work->work_ack); -+ -+ spin_lock_irqsave(&ec->events.lock, flags); -+ handler_data = ec->events.handler[work->event.rqid - 1].data; -+ delay_fn = ec->events.handler[work->event.rqid - 1].delay; -+ if (delay_fn) { -+ delay = delay_fn(&work->event, handler_data); -+ } -+ spin_unlock_irqrestore(&ec->events.lock, flags); -+ -+ // immediate execution for high priority events (e.g. keyboard) -+ if (delay == SURFACEGEN5_EVENT_IMMEDIATE) { -+ surfacegen5_event_work_evt_handler(&work->work_evt.work); -+ } else { -+ INIT_DELAYED_WORK(&work->work_evt, surfacegen5_event_work_evt_handler); -+ queue_delayed_work(ec->events.queue_evt, &work->work_evt, delay); -+ } -+} -+ -+static int surfacegen5_ssh_receive_msg_ctrl(struct surfacegen5_ec *ec, -+ const u8 *buf, size_t size) -+{ -+ struct device *dev = &ec->serdev->dev; -+ struct surfacegen5_ec_receiver *rcv = &ec->receiver; -+ const struct surfacegen5_frame_ctrl *ctrl; -+ struct surfacegen5_fifo_packet packet; -+ -+ const u8 *ctrl_begin = buf + SG5_FRAME_OFFS_CTRL; -+ const u8 *ctrl_end = buf + SG5_FRAME_OFFS_CTRL_CRC; -+ -+ ctrl = (const struct surfacegen5_frame_ctrl *)(ctrl_begin); -+ -+ // actual length check -+ if (size < SG5_MSG_LEN_CTRL) { -+ return 0; // need more bytes -+ } -+ -+ // validate TERM -+ if (!surfacegen5_ssh_is_valid_ter(buf + SG5_FRAME_OFFS_TERM)) { -+ dev_err(dev, SG5_RECV_TAG "invalid end of message\n"); -+ return size; // discard everything -+ } -+ -+ // validate CRC -+ if (!surfacegen5_ssh_is_valid_crc(ctrl_begin, ctrl_end)) { -+ dev_err(dev, SG5_RECV_TAG "invalid checksum (ctrl)\n"); -+ return SG5_MSG_LEN_CTRL; // only discard message -+ } -+ -+ // check if we expect the message -+ if (rcv->state != SG5_RCV_CONTROL) { -+ dev_err(dev, SG5_RECV_TAG "discarding message: ctrl not expected\n"); -+ return SG5_MSG_LEN_CTRL; // discard message -+ } -+ -+ // check if it is for our request -+ if (ctrl->type == SG5_FRAME_TYPE_ACK && ctrl->seq != rcv->expect.seq) { -+ dev_err(dev, SG5_RECV_TAG "discarding message: ack does not match\n"); -+ return SG5_MSG_LEN_CTRL; // discard message -+ } -+ -+ // we now have a valid & expected ACK/RETRY message -+ dev_dbg(dev, SG5_RECV_TAG "valid control message received (type: 0x%02x)\n", ctrl->type); -+ -+ packet.type = ctrl->type; -+ packet.seq = ctrl->seq; -+ packet.len = 0; -+ -+ if (kfifo_avail(&rcv->fifo) >= sizeof(packet)) { -+ kfifo_in(&rcv->fifo, (u8 *) &packet, sizeof(packet)); -+ -+ } else { -+ dev_warn(dev, SG5_RECV_TAG -+ "dropping frame: not enough space in fifo (type = %d)\n", -+ SG5_FRAME_TYPE_CMD); -+ -+ return SG5_MSG_LEN_CTRL; // discard message -+ } -+ -+ // update decoder state -+ if (ctrl->type == SG5_FRAME_TYPE_ACK) { -+ rcv->state = rcv->expect.pld -+ ? SG5_RCV_COMMAND -+ : SG5_RCV_DISCARD; -+ } -+ -+ complete(&rcv->signal); -+ return SG5_MSG_LEN_CTRL; // handled message -+} -+ -+static int surfacegen5_ssh_receive_msg_cmd(struct surfacegen5_ec *ec, -+ const u8 *buf, size_t size) -+{ -+ struct device *dev = &ec->serdev->dev; -+ struct surfacegen5_ec_receiver *rcv = &ec->receiver; -+ const struct surfacegen5_frame_ctrl *ctrl; -+ const struct surfacegen5_frame_cmd *cmd; -+ struct surfacegen5_fifo_packet packet; -+ -+ const u8 *ctrl_begin = buf + SG5_FRAME_OFFS_CTRL; -+ const u8 *ctrl_end = buf + SG5_FRAME_OFFS_CTRL_CRC; -+ const u8 *cmd_begin = buf + SG5_FRAME_OFFS_CMD; -+ const u8 *cmd_begin_pld = buf + SG5_FRAME_OFFS_CMD_PLD; -+ const u8 *cmd_end; -+ -+ size_t msg_len; -+ -+ ctrl = (const struct surfacegen5_frame_ctrl *)(ctrl_begin); -+ cmd = (const struct surfacegen5_frame_cmd *)(cmd_begin); -+ -+ // we need at least a full control frame -+ if (size < (SG5_BYTELEN_SYNC + SG5_BYTELEN_CTRL + SG5_BYTELEN_CRC)) { -+ return 0; // need more bytes -+ } -+ -+ // validate control-frame CRC -+ if (!surfacegen5_ssh_is_valid_crc(ctrl_begin, ctrl_end)) { -+ dev_err(dev, SG5_RECV_TAG "invalid checksum (cmd-ctrl)\n"); -+ /* -+ * We can't be sure here if length is valid, thus -+ * discard everything. -+ */ -+ return size; -+ } -+ -+ // actual length check (ctrl->len contains command-frame but not crc) -+ msg_len = SG5_MSG_LEN_CMD_BASE + ctrl->len; -+ if (size < msg_len) { -+ return 0; // need more bytes -+ } -+ -+ cmd_end = cmd_begin + ctrl->len; -+ -+ // validate command-frame type -+ if (cmd->type != SG5_FRAME_TYPE_CMD) { -+ dev_err(dev, SG5_RECV_TAG "expected command frame type but got 0x%02x\n", cmd->type); -+ return size; // discard everything -+ } -+ -+ // validate command-frame CRC -+ if (!surfacegen5_ssh_is_valid_crc(cmd_begin, cmd_end)) { -+ dev_err(dev, SG5_RECV_TAG "invalid checksum (cmd-pld)\n"); -+ -+ /* -+ * The message length is provided in the control frame. As we -+ * already validated that, we can be sure here that it's -+ * correct, so we only need to discard the message. -+ */ -+ return msg_len; -+ } -+ -+ // check if we received an event notification -+ if (surfacegen5_rqid_is_event((cmd->rqid_hi << 8) | cmd->rqid_lo)) { -+ surfacegen5_ssh_handle_event(ec, buf); -+ return msg_len; // handled message -+ } -+ -+ // check if we expect the message -+ if (rcv->state != SG5_RCV_COMMAND) { -+ dev_dbg(dev, SG5_RECV_TAG "discarding message: command not expected\n"); -+ return msg_len; // discard message -+ } -+ -+ // check if response is for our request -+ if (rcv->expect.rqid != (cmd->rqid_lo | (cmd->rqid_hi << 8))) { -+ dev_dbg(dev, SG5_RECV_TAG "discarding message: command not a match\n"); -+ return msg_len; // discard message -+ } -+ -+ // we now have a valid & expected command message -+ dev_dbg(dev, SG5_RECV_TAG "valid command message received\n"); -+ -+ packet.type = ctrl->type; -+ packet.seq = ctrl->seq; -+ packet.len = cmd_end - cmd_begin_pld; -+ -+ if (kfifo_avail(&rcv->fifo) >= sizeof(packet) + packet.len) { -+ kfifo_in(&rcv->fifo, &packet, sizeof(packet)); -+ kfifo_in(&rcv->fifo, cmd_begin_pld, packet.len); -+ -+ } else { -+ dev_warn(dev, SG5_RECV_TAG -+ "dropping frame: not enough space in fifo (type = %d)\n", -+ SG5_FRAME_TYPE_CMD); -+ -+ return SG5_MSG_LEN_CTRL; // discard message -+ } -+ -+ rcv->state = SG5_RCV_DISCARD; -+ -+ complete(&rcv->signal); -+ return msg_len; // handled message -+} -+ -+static int surfacegen5_ssh_eval_buf(struct surfacegen5_ec *ec, -+ const u8 *buf, size_t size) -+{ -+ struct device *dev = &ec->serdev->dev; -+ struct surfacegen5_frame_ctrl *ctrl; -+ -+ // we need at least a control frame to check what to do -+ if (size < (SG5_BYTELEN_SYNC + SG5_BYTELEN_CTRL)) { -+ return 0; // need more bytes -+ } -+ -+ // make sure we're actually at the start of a new message -+ if (!surfacegen5_ssh_is_valid_syn(buf)) { -+ dev_err(dev, SG5_RECV_TAG "invalid start of message\n"); -+ return size; // discard everything -+ } -+ -+ // handle individual message types seperately -+ ctrl = (struct surfacegen5_frame_ctrl *)(buf + SG5_FRAME_OFFS_CTRL); -+ -+ switch (ctrl->type) { -+ case SG5_FRAME_TYPE_ACK: -+ case SG5_FRAME_TYPE_RETRY: -+ return surfacegen5_ssh_receive_msg_ctrl(ec, buf, size); -+ -+ case SG5_FRAME_TYPE_CMD: -+ return surfacegen5_ssh_receive_msg_cmd(ec, buf, size); -+ -+ default: -+ dev_err(dev, SG5_RECV_TAG "unknown frame type 0x%02x\n", ctrl->type); -+ return size; // discard everything -+ } -+} -+ -+static int surfacegen5_ssh_receive_buf(struct serdev_device *serdev, -+ const unsigned char *buf, size_t size) -+{ -+ struct surfacegen5_ec *ec = serdev_device_get_drvdata(serdev); -+ struct surfacegen5_ec_receiver *rcv = &ec->receiver; -+ unsigned long flags; -+ int offs = 0; -+ int used, n; -+ -+ dev_dbg(&serdev->dev, SG5_RECV_TAG "received buffer (size: %zu)\n", size); -+ print_hex_dump_debug(SG5_RECV_TAG, DUMP_PREFIX_OFFSET, 16, 1, buf, size, false); -+ -+ /* -+ * The battery _BIX message gets a bit long, thus we have to add some -+ * additional buffering here. -+ */ -+ -+ spin_lock_irqsave(&rcv->lock, flags); -+ -+ // copy to eval-buffer -+ used = min(size, (size_t)(rcv->eval_buf.cap - rcv->eval_buf.len)); -+ memcpy(rcv->eval_buf.ptr + rcv->eval_buf.len, buf, used); -+ rcv->eval_buf.len += used; -+ -+ // evaluate buffer until we need more bytes or eval-buf is empty -+ while (offs < rcv->eval_buf.len) { -+ n = rcv->eval_buf.len - offs; -+ n = surfacegen5_ssh_eval_buf(ec, rcv->eval_buf.ptr + offs, n); -+ if (n <= 0) break; // need more bytes -+ -+ offs += n; -+ } -+ -+ // throw away the evaluated parts -+ rcv->eval_buf.len -= offs; -+ memmove(rcv->eval_buf.ptr, rcv->eval_buf.ptr + offs, rcv->eval_buf.len); -+ -+ spin_unlock_irqrestore(&rcv->lock, flags); -+ -+ return used; -+} -+ -+ -+static acpi_status -+surfacegen5_ssh_setup_from_resource(struct acpi_resource *resource, void *context) -+{ -+ struct serdev_device *serdev = context; -+ struct acpi_resource_common_serialbus *serial; -+ struct acpi_resource_uart_serialbus *uart; -+ int status = 0; -+ -+ if (resource->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) { -+ return AE_OK; -+ } -+ -+ serial = &resource->data.common_serial_bus; -+ if (serial->type != ACPI_RESOURCE_SERIAL_TYPE_UART) { -+ return AE_OK; -+ } -+ -+ uart = &resource->data.uart_serial_bus; -+ -+ // set up serdev device -+ serdev_device_set_baudrate(serdev, uart->default_baud_rate); -+ -+ // serdev currently only supports RTSCTS flow control -+ if (uart->flow_control & SG5_SUPPORTED_FLOW_CONTROL_MASK) { -+ dev_warn(&serdev->dev, "unsupported flow control (value: 0x%02x)\n", uart->flow_control); -+ } -+ -+ // set RTSCTS flow control -+ serdev_device_set_flow_control(serdev, uart->flow_control & ACPI_UART_FLOW_CONTROL_HW); -+ -+ // serdev currently only supports EVEN/ODD parity -+ switch (uart->parity) { -+ case ACPI_UART_PARITY_NONE: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); -+ break; -+ case ACPI_UART_PARITY_EVEN: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_EVEN); -+ break; -+ case ACPI_UART_PARITY_ODD: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_ODD); -+ break; -+ default: -+ dev_warn(&serdev->dev, "unsupported parity (value: 0x%02x)\n", uart->parity); -+ break; -+ } -+ -+ if (status) { -+ dev_err(&serdev->dev, "failed to set parity (value: 0x%02x)\n", uart->parity); -+ return status; -+ } -+ -+ return AE_CTRL_TERMINATE; // we've found the resource and are done -+} -+ -+ -+static bool surfacegen5_idma_filter(struct dma_chan *chan, void *param) -+{ -+ // see dw8250_idma_filter -+ return param == chan->device->dev; -+} -+ -+static int surfacegen5_ssh_check_dma(struct serdev_device *serdev) -+{ -+ struct device *dev = serdev->ctrl->dev.parent; -+ struct dma_chan *rx, *tx; -+ dma_cap_mask_t mask; -+ int status = 0; -+ -+ /* -+ * The EC UART requires DMA for proper communication. If we don't use DMA, -+ * we'll drop bytes when the system has high load, e.g. during boot. This -+ * causes some ugly behaviour, i.e. battery information (_BIX) messages -+ * failing frequently. We're making sure the required DMA channels are -+ * available here so serial8250_do_startup is able to grab them later -+ * instead of silently falling back to a non-DMA approach. -+ */ -+ -+ dma_cap_zero(mask); -+ dma_cap_set(DMA_SLAVE, mask); -+ -+ rx = dma_request_slave_channel_compat(mask, surfacegen5_idma_filter, dev->parent, dev, "rx"); -+ if (IS_ERR_OR_NULL(rx)) { -+ status = rx ? PTR_ERR(rx) : -EPROBE_DEFER; -+ if (status != -EPROBE_DEFER) { -+ dev_err(&serdev->dev, "sg5_dma: error requesting rx channel: %d\n", status); -+ } else { -+ dev_dbg(&serdev->dev, "sg5_dma: rx channel not found, deferring probe\n"); -+ } -+ goto check_dma_out; -+ } -+ -+ tx = dma_request_slave_channel_compat(mask, surfacegen5_idma_filter, dev->parent, dev, "tx"); -+ if (IS_ERR_OR_NULL(tx)) { -+ status = tx ? PTR_ERR(tx) : -EPROBE_DEFER; -+ if (status != -EPROBE_DEFER) { -+ dev_err(&serdev->dev, "sg5_dma: error requesting tx channel: %d\n", status); -+ } else { -+ dev_dbg(&serdev->dev, "sg5_dma: tx channel not found, deferring probe\n"); -+ } -+ goto check_dma_release_rx; -+ } -+ -+ dma_release_channel(tx); -+check_dma_release_rx: -+ dma_release_channel(rx); -+check_dma_out: -+ return status; -+} -+ -+ -+static int surfacegen5_ssh_suspend(struct device *dev) -+{ -+ struct surfacegen5_ec *ec; -+ int status = 0; -+ -+ dev_dbg(dev, "suspending\n"); -+ -+ ec = surfacegen5_ec_acquire_init(); -+ if (ec) { -+ status = surfacegen5_ssh_ec_suspend(ec); -+ if (status) { -+ dev_err(dev, "failed to suspend EC: %d\n", status); -+ } -+ -+ ec->state = SG5_EC_SUSPENDED; -+ surfacegen5_ec_release(ec); -+ } -+ -+ return status; -+} -+ -+static int surfacegen5_ssh_resume(struct device *dev) -+{ -+ struct surfacegen5_ec *ec; -+ int status = 0; -+ -+ dev_dbg(dev, "resuming\n"); -+ -+ ec = surfacegen5_ec_acquire_init(); -+ if (ec) { -+ ec->state = SG5_EC_INITIALIZED; -+ -+ status = surfacegen5_ssh_ec_resume(ec); -+ if (status) { -+ dev_err(dev, "failed to resume EC: %d\n", status); -+ } -+ -+ surfacegen5_ec_release(ec); -+ } -+ -+ return status; -+} -+ -+static SIMPLE_DEV_PM_OPS(surfacegen5_ssh_pm_ops, surfacegen5_ssh_suspend, surfacegen5_ssh_resume); -+ -+ -+static const struct serdev_device_ops surfacegen5_ssh_device_ops = { -+ .receive_buf = surfacegen5_ssh_receive_buf, -+ .write_wakeup = serdev_device_write_wakeup, -+}; -+ -+static int surfacegen5_acpi_ssh_probe(struct serdev_device *serdev) -+{ -+ struct surfacegen5_ec *ec; -+ struct workqueue_struct *event_queue_ack; -+ struct workqueue_struct *event_queue_evt; -+ u8 *write_buf; -+ u8 *read_buf; -+ u8 *eval_buf; -+ acpi_handle *ssh = ACPI_HANDLE(&serdev->dev); -+ acpi_status status; -+ -+ dev_dbg(&serdev->dev, "probing\n"); -+ -+ // ensure DMA is ready before we set up the device -+ status = surfacegen5_ssh_check_dma(serdev); -+ if (status) { -+ return status; -+ } -+ -+ // allocate buffers -+ write_buf = kzalloc(SG5_WRITE_BUF_LEN, GFP_KERNEL); -+ if (!write_buf) { -+ status = -ENOMEM; -+ goto err_probe_write_buf; -+ } -+ -+ read_buf = kzalloc(SG5_READ_BUF_LEN, GFP_KERNEL); -+ if (!read_buf) { -+ status = -ENOMEM; -+ goto err_probe_read_buf; -+ } -+ -+ eval_buf = kzalloc(SG5_EVAL_BUF_LEN, GFP_KERNEL); -+ if (!eval_buf) { -+ status = -ENOMEM; -+ goto err_probe_eval_buf; -+ } -+ -+ event_queue_ack = create_singlethread_workqueue("sg5_ackq"); -+ if (!event_queue_ack) { -+ status = -ENOMEM; -+ goto err_probe_ackq; -+ } -+ -+ event_queue_evt = create_workqueue("sg5_evtq"); -+ if (!event_queue_evt) { -+ status = -ENOMEM; -+ goto err_probe_evtq; -+ } -+ -+ // set up EC -+ ec = surfacegen5_ec_acquire(); -+ if (ec->state != SG5_EC_UNINITIALIZED) { -+ dev_err(&serdev->dev, "embedded controller already initialized\n"); -+ surfacegen5_ec_release(ec); -+ -+ status = -EBUSY; -+ goto err_probe_busy; -+ } -+ -+ ec->serdev = serdev; -+ ec->writer.data = write_buf; -+ ec->writer.ptr = write_buf; -+ -+ // initialize receiver -+ init_completion(&ec->receiver.signal); -+ kfifo_init(&ec->receiver.fifo, read_buf, SG5_READ_BUF_LEN); -+ ec->receiver.eval_buf.ptr = eval_buf; -+ ec->receiver.eval_buf.cap = SG5_EVAL_BUF_LEN; -+ ec->receiver.eval_buf.len = 0; -+ -+ // initialize event handling -+ ec->events.queue_ack = event_queue_ack; -+ ec->events.queue_evt = event_queue_evt; -+ -+ ec->state = SG5_EC_INITIALIZED; -+ -+ serdev_device_set_drvdata(serdev, ec); -+ -+ // ensure everything is properly set-up before we open the device -+ smp_mb(); -+ -+ serdev_device_set_client_ops(serdev, &surfacegen5_ssh_device_ops); -+ status = serdev_device_open(serdev); -+ if (status) { -+ goto err_probe_open; -+ } -+ -+ status = acpi_walk_resources(ssh, METHOD_NAME__CRS, -+ surfacegen5_ssh_setup_from_resource, serdev); -+ if (ACPI_FAILURE(status)) { -+ goto err_probe_devinit; -+ } -+ -+ status = surfacegen5_ssh_ec_resume(ec); -+ if (status) { -+ goto err_probe_devinit; -+ } -+ -+ status = surfacegen5_ssh_sysfs_register(&serdev->dev); -+ if (status) { -+ goto err_probe_devinit; -+ } -+ -+ surfacegen5_ec_release(ec); -+ -+ acpi_walk_dep_device_list(ssh); -+ -+ return 0; -+ -+err_probe_devinit: -+ serdev_device_close(serdev); -+err_probe_open: -+ ec->state = SG5_EC_UNINITIALIZED; -+ serdev_device_set_drvdata(serdev, NULL); -+ surfacegen5_ec_release(ec); -+err_probe_busy: -+ destroy_workqueue(event_queue_evt); -+err_probe_evtq: -+ destroy_workqueue(event_queue_ack); -+err_probe_ackq: -+ kfree(eval_buf); -+err_probe_eval_buf: -+ kfree(read_buf); -+err_probe_read_buf: -+ kfree(write_buf); -+err_probe_write_buf: -+ return status; -+} -+ -+static void surfacegen5_acpi_ssh_remove(struct serdev_device *serdev) -+{ -+ struct surfacegen5_ec *ec; -+ unsigned long flags; -+ int status; -+ -+ ec = surfacegen5_ec_acquire_init(); -+ if (!ec) { -+ return; -+ } -+ -+ surfacegen5_ssh_sysfs_unregister(&serdev->dev); -+ -+ // suspend EC and disable events -+ status = surfacegen5_ssh_ec_suspend(ec); -+ if (status) { -+ dev_err(&serdev->dev, "failed to suspend EC: %d\n", status); -+ } -+ -+ // make sure all events (received up to now) have been properly handled -+ flush_workqueue(ec->events.queue_ack); -+ flush_workqueue(ec->events.queue_evt); -+ -+ // remove event handlers -+ spin_lock_irqsave(&ec->events.lock, flags); -+ memset(ec->events.handler, 0, -+ sizeof(struct surfacegen5_ec_event_handler) -+ * SG5_NUM_EVENT_TYPES); -+ spin_unlock_irqrestore(&ec->events.lock, flags); -+ -+ // set device to deinitialized state -+ ec->state = SG5_EC_UNINITIALIZED; -+ ec->serdev = NULL; -+ -+ // ensure state and serdev get set before continuing -+ smp_mb(); -+ -+ /* -+ * Flush any event that has not been processed yet to ensure we're not going to -+ * use the serial device any more (e.g. for ACKing). -+ */ -+ flush_workqueue(ec->events.queue_ack); -+ flush_workqueue(ec->events.queue_evt); -+ -+ serdev_device_close(serdev); -+ -+ /* -+ * Only at this point, no new events can be received. Destroying the -+ * workqueue here flushes all remaining events. Those events will be -+ * silently ignored and neither ACKed nor any handler gets called. -+ */ -+ destroy_workqueue(ec->events.queue_ack); -+ destroy_workqueue(ec->events.queue_evt); -+ -+ // free writer -+ kfree(ec->writer.data); -+ ec->writer.data = NULL; -+ ec->writer.ptr = NULL; -+ -+ // free receiver -+ spin_lock_irqsave(&ec->receiver.lock, flags); -+ ec->receiver.state = SG5_RCV_DISCARD; -+ kfifo_free(&ec->receiver.fifo); -+ -+ kfree(ec->receiver.eval_buf.ptr); -+ ec->receiver.eval_buf.ptr = NULL; -+ ec->receiver.eval_buf.cap = 0; -+ ec->receiver.eval_buf.len = 0; -+ spin_unlock_irqrestore(&ec->receiver.lock, flags); -+ -+ serdev_device_set_drvdata(serdev, NULL); -+ surfacegen5_ec_release(ec); -+} -+ -+ -+static const struct acpi_device_id surfacegen5_acpi_ssh_match[] = { -+ { "MSHW0084", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_ssh_match); -+ -+struct serdev_device_driver surfacegen5_acpi_ssh = { -+ .probe = surfacegen5_acpi_ssh_probe, -+ .remove = surfacegen5_acpi_ssh_remove, -+ .driver = { -+ .name = "surfacegen5_acpi_ssh", -+ .acpi_match_table = ACPI_PTR(surfacegen5_acpi_ssh_match), -+ .pm = &surfacegen5_ssh_pm_ops, -+ }, -+}; -+ -+inline int surfacegen5_acpi_ssh_register(void) -+{ -+ return serdev_device_driver_register(&surfacegen5_acpi_ssh); -+} -+ -+inline void surfacegen5_acpi_ssh_unregister(void) -+{ -+ serdev_device_driver_unregister(&surfacegen5_acpi_ssh); -+} -+ -+#else /* CONFIG_SURFACE_ACPI_SSH */ -+ -+inline int surfacegen5_acpi_ssh_register(void) -+{ -+ return 0; -+} -+ -+inline void surfacegen5_acpi_ssh_unregister(void) -+{ -+} -+ -+ -+#endif /* CONFIG_SURFACE_ACPI_SSH */ -+ -+ -+/************************************************************************* -+ * Surface Serial Hub Debug Device (private implementation) -+ */ -+ -+#ifdef CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE -+ -+static char sg5_ssh_debug_rqst_buf_sysfs[SURFACEGEN5_MAX_RQST_RESPONSE + 1] = { 0 }; -+static char sg5_ssh_debug_rqst_buf_pld[SURFACEGEN5_MAX_RQST_PAYLOAD] = { 0 }; -+static char sg5_ssh_debug_rqst_buf_res[SURFACEGEN5_MAX_RQST_RESPONSE] = { 0 }; -+ -+static ssize_t rqst_read(struct file *f, struct kobject *kobj, struct bin_attribute *attr, -+ char *buf, loff_t offs, size_t count) -+{ -+ if (offs < 0 || count + offs > SURFACEGEN5_MAX_RQST_RESPONSE) { -+ return -EINVAL; -+ } -+ -+ memcpy(buf, sg5_ssh_debug_rqst_buf_sysfs + offs, count); -+ return count; -+} -+ -+static ssize_t rqst_write(struct file *f, struct kobject *kobj, struct bin_attribute *attr, -+ char *buf, loff_t offs, size_t count) -+{ -+ struct surfacegen5_rqst rqst = {}; -+ struct surfacegen5_buf result = {}; -+ int status; -+ -+ // check basic write constriants -+ if (offs != 0 || count > SURFACEGEN5_MAX_RQST_PAYLOAD + 5) { -+ return -EINVAL; -+ } -+ -+ // payload length should be consistent with data provided -+ if (buf[4] + 5 != count) { -+ return -EINVAL; -+ } -+ -+ rqst.tc = buf[0]; -+ rqst.iid = buf[1]; -+ rqst.cid = buf[2]; -+ rqst.snc = buf[3]; -+ rqst.cdl = buf[4]; -+ rqst.pld = sg5_ssh_debug_rqst_buf_pld; -+ memcpy(sg5_ssh_debug_rqst_buf_pld, buf + 5, count - 5); -+ -+ result.cap = SURFACEGEN5_MAX_RQST_RESPONSE; -+ result.len = 0; -+ result.data = sg5_ssh_debug_rqst_buf_res; -+ -+ status = surfacegen5_ec_rqst(&rqst, &result); -+ if (status) { -+ return status; -+ } -+ -+ sg5_ssh_debug_rqst_buf_sysfs[0] = result.len; -+ memcpy(sg5_ssh_debug_rqst_buf_sysfs + 1, result.data, result.len); -+ memset(sg5_ssh_debug_rqst_buf_sysfs + result.len + 1, 0, -+ SURFACEGEN5_MAX_RQST_RESPONSE + 1 - result.len); -+ -+ return count; -+} -+ -+static const BIN_ATTR_RW(rqst, SURFACEGEN5_MAX_RQST_RESPONSE + 1); -+ -+ -+inline int surfacegen5_ssh_sysfs_register(struct device *dev) -+{ -+ return sysfs_create_bin_file(&dev->kobj, &bin_attr_rqst); -+} -+ -+inline void surfacegen5_ssh_sysfs_unregister(struct device *dev) -+{ -+ sysfs_remove_bin_file(&dev->kobj, &bin_attr_rqst); -+} -+ -+#elif defined(CONFIG_SURFACE_ACPI_SSH) -+ -+inline int surfacegen5_ssh_sysfs_register(struct device *dev) -+{ -+ return 0; -+} -+ -+inline void surfacegen5_ssh_sysfs_unregister(struct device *dev) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE*/ -+ -+ -+/************************************************************************* -+ * Surface ACPI Notify driver -+ */ -+ -+#ifdef CONFIG_SURFACE_ACPI_SAN -+ -+#define SG5_SAN_RQST_RETRY 5 -+ -+#define SG5_SAN_DSM_REVISION 0 -+#define SG5_SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT 0x09 -+ -+static const guid_t SG5_SAN_DSM_UUID = -+ GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d, -+ 0x48, 0x7c, 0x91, 0xab, 0x3c); -+ -+#define SG5_EVENT_DELAY_POWER msecs_to_jiffies(5000) -+ -+#define SG5_EVENT_PWR_TC 0x02 -+#define SG5_EVENT_PWR_RQID 0x0002 -+#define SG5_EVENT_PWR_CID_HWCHANGE 0x15 -+#define SG5_EVENT_PWR_CID_CHARGING 0x16 -+#define SG5_EVENT_PWR_CID_ADAPTER 0x17 -+#define SG5_EVENT_PWR_CID_STATE 0x4f -+ -+#define SG5_EVENT_TEMP_TC 0x03 -+#define SG5_EVENT_TEMP_RQID 0x0003 -+#define SG5_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT 0x0b -+ -+#define SG5_SAN_RQST_TAG "surfacegen5_ec_rqst: " -+ -+#define SG5_QUIRK_BASE_STATE_DELAY 1000 -+ -+ -+struct surfacegen5_san_acpi_consumer { -+ char *path; -+ bool required; -+ u32 flags; -+}; -+ -+struct surfacegen5_san_opreg_context { -+ struct acpi_connection_info connection; -+ struct device *dev; -+}; -+ -+struct surfacegen5_san_consumer_link { -+ const struct surfacegen5_san_acpi_consumer *properties; -+ struct device_link *link; -+}; -+ -+struct surfacegen5_san_consumers { -+ u32 num; -+ struct surfacegen5_san_consumer_link *links; -+}; -+ -+struct surfacegen5_san_drvdata { -+ struct surfacegen5_san_opreg_context opreg_ctx; -+ struct surfacegen5_san_consumers consumers; -+}; -+ -+struct gsb_data_in { -+ u8 cv; -+} __packed; -+ -+struct gsb_data_rqsx { -+ u8 cv; // command value (should be 0x01 or 0x03) -+ u8 tc; // target controller -+ u8 tid; // expected to be 0x01, could be revision -+ u8 iid; // target sub-controller (e.g. primary vs. secondary battery) -+ u8 snc; // expect-response-flag -+ u8 cid; // command ID -+ u8 cdl; // payload length -+ u8 _pad; // padding -+ u8 pld[0]; // payload -+} __packed; -+ -+struct gsb_data_etwl { -+ u8 cv; // command value (should be 0x02) -+ u8 etw3; // ? -+ u8 etw4; // ? -+ u8 msg[0]; // error message (ASCIIZ) -+} __packed; -+ -+struct gsb_data_out { -+ u8 status; // _SSH communication status -+ u8 len; // _SSH payload length -+ u8 pld[0]; // _SSH payload -+} __packed; -+ -+union gsb_buffer_data { -+ struct gsb_data_in in; // common input -+ struct gsb_data_rqsx rqsx; // RQSX input -+ struct gsb_data_etwl etwl; // ETWL input -+ struct gsb_data_out out; // output -+}; -+ -+struct gsb_buffer { -+ u8 status; // GSB AttribRawProcess status -+ u8 len; // GSB AttribRawProcess length -+ union gsb_buffer_data data; -+} __packed; -+ -+ -+enum surfacegen5_pwr_event { -+ SURFACEGEN5_PWR_EVENT_BAT1_STAT = 0x03, -+ SURFACEGEN5_PWR_EVENT_BAT1_INFO = 0x04, -+ SURFACEGEN5_PWR_EVENT_ADP1_STAT = 0x05, -+ SURFACEGEN5_PWR_EVENT_ADP1_INFO = 0x06, -+ SURFACEGEN5_PWR_EVENT_BAT2_STAT = 0x07, -+ SURFACEGEN5_PWR_EVENT_BAT2_INFO = 0x08, -+}; -+ -+ -+static int surfacegen5_acpi_notify_power_event(struct device *dev, enum surfacegen5_pwr_event event) -+{ -+ acpi_handle san = ACPI_HANDLE(dev); -+ union acpi_object *obj; -+ -+ obj = acpi_evaluate_dsm_typed(san, &SG5_SAN_DSM_UUID, SG5_SAN_DSM_REVISION, -+ (u8) event, NULL, ACPI_TYPE_BUFFER); -+ -+ if (IS_ERR_OR_NULL(obj)) { -+ return obj ? PTR_ERR(obj) : -ENXIO; -+ } -+ -+ if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) { -+ dev_err(dev, "got unexpected result from _DSM\n"); -+ return -EFAULT; -+ } -+ -+ ACPI_FREE(obj); -+ return 0; -+} -+ -+static int surfacegen5_acpi_notify_sensor_trip_point(struct device *dev, u8 iid) -+{ -+ acpi_handle san = ACPI_HANDLE(dev); -+ union acpi_object *obj; -+ union acpi_object param; -+ -+ param.type = ACPI_TYPE_INTEGER; -+ param.integer.value = iid; -+ -+ obj = acpi_evaluate_dsm_typed(san, &SG5_SAN_DSM_UUID, SG5_SAN_DSM_REVISION, -+ SG5_SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT, -+ ¶m, ACPI_TYPE_BUFFER); -+ -+ if (IS_ERR_OR_NULL(obj)) { -+ return obj ? PTR_ERR(obj) : -ENXIO; -+ } -+ -+ if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) { -+ dev_err(dev, "got unexpected result from _DSM\n"); -+ return -EFAULT; -+ } -+ -+ ACPI_FREE(obj); -+ return 0; -+} -+ -+ -+inline static int surfacegen5_evt_power_adapter(struct device *dev, struct surfacegen5_event *event) -+{ -+ int status; -+ -+ status = surfacegen5_acpi_notify_power_event(dev, SURFACEGEN5_PWR_EVENT_ADP1_STAT); -+ if (status) { -+ dev_err(dev, "error handling power event (cid = %x)\n", event->cid); -+ return status; -+ } -+ -+ return 0; -+} -+ -+inline static int surfacegen5_evt_power_hwchange(struct device *dev, struct surfacegen5_event *event) -+{ -+ enum surfacegen5_pwr_event evcode; -+ int status; -+ -+ if (event->iid == 0x02) { -+ evcode = SURFACEGEN5_PWR_EVENT_BAT2_INFO; -+ } else { -+ evcode = SURFACEGEN5_PWR_EVENT_BAT1_INFO; -+ } -+ -+ status = surfacegen5_acpi_notify_power_event(dev, evcode); -+ if (status) { -+ dev_err(dev, "error handling power event (cid = %x)\n", event->cid); -+ return status; -+ } -+ -+ return 0; -+} -+ -+inline static int surfacegen5_evt_power_state(struct device *dev, struct surfacegen5_event *event) -+{ -+ int status; -+ -+ status = surfacegen5_acpi_notify_power_event(dev, SURFACEGEN5_PWR_EVENT_BAT1_STAT); -+ if (status) { -+ dev_err(dev, "error handling power event (cid = %x)\n", event->cid); -+ return status; -+ } -+ -+ status = surfacegen5_acpi_notify_power_event(dev, SURFACEGEN5_PWR_EVENT_BAT2_STAT); -+ if (status) { -+ dev_err(dev, "error handling power event (cid = %x)\n", event->cid); -+ return status; -+ } -+ -+ return 0; -+} -+ -+static unsigned long surfacegen5_evt_power_delay(struct surfacegen5_event *event, void *data) -+{ -+ switch (event->cid) { -+ case SG5_EVENT_PWR_CID_CHARGING: -+ case SG5_EVENT_PWR_CID_STATE: -+ return SG5_EVENT_DELAY_POWER; -+ -+ case SG5_EVENT_PWR_CID_ADAPTER: -+ case SG5_EVENT_PWR_CID_HWCHANGE: -+ default: -+ return 0; -+ } -+} -+ -+static int surfacegen5_evt_power(struct surfacegen5_event *event, void *data) -+{ -+ struct device *dev = (struct device *)data; -+ -+ switch (event->cid) { -+ case SG5_EVENT_PWR_CID_HWCHANGE: -+ return surfacegen5_evt_power_hwchange(dev, event); -+ -+ case SG5_EVENT_PWR_CID_ADAPTER: -+ return surfacegen5_evt_power_adapter(dev, event); -+ -+ case SG5_EVENT_PWR_CID_CHARGING: -+ case SG5_EVENT_PWR_CID_STATE: -+ return surfacegen5_evt_power_state(dev, event); -+ -+ default: -+ dev_warn(dev, "unhandled power event (cid = %x)\n", event->cid); -+ } -+ -+ return 0; -+} -+ -+ -+inline static int surfacegen5_evt_thermal_notify(struct device *dev, struct surfacegen5_event *event) -+{ -+ int status; -+ -+ status = surfacegen5_acpi_notify_sensor_trip_point(dev, event->iid); -+ if (status) { -+ dev_err(dev, "error handling thermal event (cid = %x)\n", event->cid); -+ return status; -+ } -+ -+ return 0; -+} -+ -+static int surfacegen5_evt_thermal(struct surfacegen5_event *event, void *data) -+{ -+ struct device *dev = (struct device *)data; -+ -+ switch (event->cid) { -+ case SG5_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT: -+ return surfacegen5_evt_thermal_notify(dev, event); -+ -+ default: -+ dev_warn(dev, "unhandled thermal event (cid = %x)\n", event->cid); -+ } -+ -+ return 0; -+} -+ -+ -+static struct gsb_data_rqsx *surfacegen5_san_validate_rqsx( -+ struct device *dev, const char *type, struct gsb_buffer *buffer) -+{ -+ struct gsb_data_rqsx *rqsx = &buffer->data.rqsx; -+ -+ if (buffer->len < 8) { -+ dev_err(dev, "invalid %s package (len = %d)\n", -+ type, buffer->len); -+ return NULL; -+ } -+ -+ if (rqsx->cdl != buffer->len - 8) { -+ dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n", -+ type, buffer->len, rqsx->cdl); -+ return NULL; -+ } -+ -+ if (rqsx->tid != 0x01) { -+ dev_warn(dev, "unsupported %s package (tid = 0x%02x)\n", -+ type, rqsx->tid); -+ return NULL; -+ } -+ -+ return rqsx; -+} -+ -+static acpi_status -+surfacegen5_san_etwl(struct surfacegen5_san_opreg_context *ctx, struct gsb_buffer *buffer) -+{ -+ struct gsb_data_etwl *etwl = &buffer->data.etwl; -+ -+ if (buffer->len < 3) { -+ dev_err(ctx->dev, "invalid ETWL package (len = %d)\n", buffer->len); -+ return AE_OK; -+ } -+ -+ dev_err(ctx->dev, "ETWL(0x%02x, 0x%02x): %.*s\n", -+ etwl->etw3, etwl->etw4, -+ buffer->len - 3, (char *)etwl->msg); -+ -+ // indicate success -+ buffer->status = 0x00; -+ buffer->len = 0x00; -+ -+ return AE_OK; -+} -+ -+static acpi_status -+surfacegen5_san_rqst(struct surfacegen5_san_opreg_context *ctx, struct gsb_buffer *buffer) -+{ -+ struct gsb_data_rqsx *gsb_rqst = surfacegen5_san_validate_rqsx(ctx->dev, "RQST", buffer); -+ struct surfacegen5_rqst rqst = {}; -+ struct surfacegen5_buf result = {}; -+ int status = 0; -+ int try; -+ -+ if (!gsb_rqst) { -+ return AE_OK; -+ } -+ -+ rqst.tc = gsb_rqst->tc; -+ rqst.iid = gsb_rqst->iid; -+ rqst.cid = gsb_rqst->cid; -+ rqst.snc = gsb_rqst->snc; -+ rqst.cdl = gsb_rqst->cdl; -+ rqst.pld = &gsb_rqst->pld[0]; -+ -+ result.cap = SURFACEGEN5_MAX_RQST_RESPONSE; -+ result.len = 0; -+ result.data = kzalloc(result.cap, GFP_KERNEL); -+ -+ if (!result.data) { -+ return AE_NO_MEMORY; -+ } -+ -+ for (try = 0; try < SG5_SAN_RQST_RETRY; try++) { -+ if (try) { -+ dev_warn(ctx->dev, SG5_SAN_RQST_TAG "IO error occured, trying again\n"); -+ } -+ -+ status = surfacegen5_ec_rqst(&rqst, &result); -+ if (status != -EIO) break; -+ } -+ -+ if (rqst.tc == 0x11 && rqst.cid == 0x0D && status == -EPERM) { -+ /* Base state quirk: -+ * The base state may be queried from ACPI when the EC is still -+ * suspended. In this case it will return '-EPERM'. This query -+ * will only be triggered from the ACPI lid GPE interrupt, thus -+ * we are either in laptop or studio mode (base status 0x01 or -+ * 0x02). Furthermore, we will only get here if the device (and -+ * EC) have been suspended. -+ * -+ * We now assume that the device is in laptop mode (0x01). This -+ * has the drawback that it will wake the device when unfolding -+ * it in studio mode, but it also allows us to avoid actively -+ * waiting for the EC to wake up, which may incur a notable -+ * delay. -+ */ -+ -+ buffer->status = 0x00; -+ buffer->len = 0x03; -+ buffer->data.out.status = 0x00; -+ buffer->data.out.len = 0x01; -+ buffer->data.out.pld[0] = 0x01; -+ -+ } else if (!status) { // success -+ buffer->status = 0x00; -+ buffer->len = result.len + 2; -+ buffer->data.out.status = 0x00; -+ buffer->data.out.len = result.len; -+ memcpy(&buffer->data.out.pld[0], result.data, result.len); -+ -+ } else { // failure -+ dev_err(ctx->dev, SG5_SAN_RQST_TAG "failed with error %d\n", status); -+ buffer->status = 0x00; -+ buffer->len = 0x02; -+ buffer->data.out.status = 0x01; // indicate _SSH error -+ buffer->data.out.len = 0x00; -+ } -+ -+ kfree(result.data); -+ -+ return AE_OK; -+} -+ -+static acpi_status -+surfacegen5_san_rqsg(struct surfacegen5_san_opreg_context *ctx, struct gsb_buffer *buffer) -+{ -+ struct gsb_data_rqsx *rqsg = surfacegen5_san_validate_rqsx(ctx->dev, "RQSG", buffer); -+ -+ if (!rqsg) { -+ return AE_OK; -+ } -+ -+ // TODO: RQSG handler -+ -+ dev_warn(ctx->dev, "unsupported request: RQSG(0x%02x, 0x%02x, 0x%02x)\n", -+ rqsg->tc, rqsg->cid, rqsg->iid); -+ -+ return AE_OK; -+} -+ -+ -+static acpi_status -+surfacegen5_san_opreg_handler(u32 function, acpi_physical_address command, -+ u32 bits, u64 *value64, -+ void *opreg_context, void *region_context) -+{ -+ struct surfacegen5_san_opreg_context *context = opreg_context; -+ struct gsb_buffer *buffer = (struct gsb_buffer *)value64; -+ int accessor_type = (0xFFFF0000 & function) >> 16; -+ -+ if (command != 0) { -+ dev_warn(context->dev, "unsupported command: 0x%02llx\n", command); -+ return AE_OK; -+ } -+ -+ if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { -+ dev_err(context->dev, "invalid access type: 0x%02x\n", accessor_type); -+ return AE_OK; -+ } -+ -+ // buffer must have at least contain the command-value -+ if (buffer->len == 0) { -+ dev_err(context->dev, "request-package too small\n"); -+ return AE_OK; -+ } -+ -+ switch (buffer->data.in.cv) { -+ case 0x01: return surfacegen5_san_rqst(context, buffer); -+ case 0x02: return surfacegen5_san_etwl(context, buffer); -+ case 0x03: return surfacegen5_san_rqsg(context, buffer); -+ } -+ -+ dev_warn(context->dev, "unsupported SAN0 request (cv: 0x%02x)\n", buffer->data.in.cv); -+ return AE_OK; -+} -+ -+static int surfacegen5_san_enable_events(struct device *dev) -+{ -+ int status; -+ -+ status = surfacegen5_ec_set_delayed_event_handler( -+ SG5_EVENT_PWR_RQID, surfacegen5_evt_power, -+ surfacegen5_evt_power_delay, dev); -+ if (status) { -+ goto err_event_handler_power; -+ } -+ -+ status = surfacegen5_ec_set_event_handler( -+ SG5_EVENT_TEMP_RQID, surfacegen5_evt_thermal, -+ dev); -+ if (status) { -+ goto err_event_handler_thermal; -+ } -+ -+ status = surfacegen5_ec_enable_event_source(SG5_EVENT_PWR_TC, 0x01, SG5_EVENT_PWR_RQID); -+ if (status) { -+ goto err_event_source_power; -+ } -+ -+ status = surfacegen5_ec_enable_event_source(SG5_EVENT_TEMP_TC, 0x01, SG5_EVENT_TEMP_RQID); -+ if (status) { -+ goto err_event_source_thermal; -+ } -+ -+ return 0; -+ -+err_event_source_thermal: -+ surfacegen5_ec_disable_event_source(SG5_EVENT_PWR_TC, 0x01, SG5_EVENT_PWR_RQID); -+err_event_source_power: -+ surfacegen5_ec_remove_event_handler(SG5_EVENT_TEMP_RQID); -+err_event_handler_thermal: -+ surfacegen5_ec_remove_event_handler(SG5_EVENT_PWR_RQID); -+err_event_handler_power: -+ return status; -+} -+ -+static void surfacegen5_san_disable_events(void) -+{ -+ surfacegen5_ec_disable_event_source(SG5_EVENT_TEMP_TC, 0x01, SG5_EVENT_TEMP_RQID); -+ surfacegen5_ec_disable_event_source(SG5_EVENT_PWR_TC, 0x01, SG5_EVENT_PWR_RQID); -+ surfacegen5_ec_remove_event_handler(SG5_EVENT_TEMP_RQID); -+ surfacegen5_ec_remove_event_handler(SG5_EVENT_PWR_RQID); -+} -+ -+ -+static int surfacegen5_san_consumers_link(struct platform_device *pdev, -+ const struct surfacegen5_san_acpi_consumer *cons, -+ struct surfacegen5_san_consumers *out) -+{ -+ const struct surfacegen5_san_acpi_consumer *con; -+ struct surfacegen5_san_consumer_link *links, *link; -+ struct acpi_device *adev; -+ acpi_handle handle; -+ u32 max_links = 0; -+ int status; -+ -+ if (!cons) { -+ return 0; -+ } -+ -+ // count links -+ for (con = cons; con->path; ++con) { -+ max_links += 1; -+ } -+ -+ // allocate -+ links = kzalloc(max_links * sizeof(struct surfacegen5_san_consumer_link), GFP_KERNEL); -+ link = &links[0]; -+ -+ if (!links) { -+ return -ENOMEM; -+ } -+ -+ // create links -+ for (con = cons; con->path; ++con) { -+ status = acpi_get_handle(NULL, con->path, &handle); -+ if (status) { -+ if (con->required || status != AE_NOT_FOUND) { -+ status = -ENXIO; -+ goto consumers_link_cleanup; -+ } else { -+ continue; -+ } -+ } -+ -+ status = acpi_bus_get_device(handle, &adev); -+ if (status) { -+ goto consumers_link_cleanup; -+ } -+ -+ link->link = device_link_add(&adev->dev, &pdev->dev, con->flags); -+ if (!(link->link)) { -+ status = -EFAULT; -+ goto consumers_link_cleanup; -+ } -+ link->properties = con; -+ -+ link += 1; -+ } -+ -+ out->num = link - links; -+ out->links = links; -+ -+ return 0; -+ -+consumers_link_cleanup: -+ for (link = link - 1; link >= links; --link) { -+ if (link->properties->flags & DL_FLAG_STATELESS) { -+ device_link_del(link->link); -+ } -+ } -+ -+ return status; -+} -+ -+static void surfacegen5_san_consumers_unlink(struct surfacegen5_san_consumers *consumers) { -+ u32 i; -+ -+ if (!consumers) { -+ return; -+ } -+ -+ for (i = 0; i < consumers->num; ++i) { -+ if (consumers->links[i].properties->flags & DL_FLAG_STATELESS) { -+ device_link_del(consumers->links[i].link); -+ } -+ } -+ -+ kfree(consumers->links); -+ -+ consumers->num = 0; -+ consumers->links = NULL; -+} -+ -+static int surfacegen5_acpi_san_probe(struct platform_device *pdev) -+{ -+ const struct surfacegen5_san_acpi_consumer *cons; -+ struct surfacegen5_san_drvdata *drvdata; -+ acpi_handle san = ACPI_HANDLE(&pdev->dev); // _SAN device node -+ int status; -+ -+ /* -+ * Defer probe if the _SSH driver has not set up the controller yet. This -+ * makes sure we do not fail any initial requests (e.g. _STA request without -+ * which the battery does not get set up correctly). Otherwise register as -+ * consumer to set up a device_link. -+ */ -+ status = surfacegen5_ec_consumer_register(&pdev->dev); -+ if (status) { -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ } -+ -+ drvdata = kzalloc(sizeof(struct surfacegen5_san_drvdata), GFP_KERNEL); -+ if (!drvdata) { -+ return -ENOMEM; -+ } -+ -+ drvdata->opreg_ctx.dev = &pdev->dev; -+ -+ cons = acpi_device_get_match_data(&pdev->dev); -+ status = surfacegen5_san_consumers_link(pdev, cons, &drvdata->consumers); -+ if (status) { -+ goto err_probe_consumers; -+ } -+ -+ platform_set_drvdata(pdev, drvdata); -+ -+ status = acpi_install_address_space_handler(san, -+ ACPI_ADR_SPACE_GSBUS, -+ &surfacegen5_san_opreg_handler, -+ NULL, &drvdata->opreg_ctx); -+ -+ if (ACPI_FAILURE(status)) { -+ status = -ENODEV; -+ goto err_probe_install_handler; -+ } -+ -+ status = surfacegen5_san_enable_events(&pdev->dev); -+ if (status) { -+ goto err_probe_enable_events; -+ } -+ -+ acpi_walk_dep_device_list(san); -+ return 0; -+ -+err_probe_enable_events: -+ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &surfacegen5_san_opreg_handler); -+err_probe_install_handler: -+ platform_set_drvdata(san, NULL); -+ surfacegen5_san_consumers_unlink(&drvdata->consumers); -+err_probe_consumers: -+ kfree(drvdata); -+ return status; -+} -+ -+static int surfacegen5_acpi_san_remove(struct platform_device *pdev) -+{ -+ struct surfacegen5_san_drvdata *drvdata = platform_get_drvdata(pdev); -+ acpi_handle san = ACPI_HANDLE(&pdev->dev); // _SAN device node -+ acpi_status status = AE_OK; -+ -+ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &surfacegen5_san_opreg_handler); -+ surfacegen5_san_disable_events(); -+ -+ surfacegen5_san_consumers_unlink(&drvdata->consumers); -+ kfree(drvdata); -+ -+ platform_set_drvdata(pdev, NULL); -+ return status; -+} -+ -+ -+static const struct surfacegen5_san_acpi_consumer surfacegen5_mshw0091_consumers[] = { -+ { "\\_SB.SRTC", true, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS }, -+ { "\\ADP1", true, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS }, -+ { "\\_SB.BAT1", true, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS }, -+ { "\\_SB.BAT2", false, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS }, -+ { }, -+}; -+ -+static const struct acpi_device_id surfacegen5_acpi_san_match[] = { -+ { "MSHW0091", (long unsigned int) surfacegen5_mshw0091_consumers }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_san_match); -+ -+struct platform_driver surfacegen5_acpi_san = { -+ .probe = surfacegen5_acpi_san_probe, -+ .remove = surfacegen5_acpi_san_remove, -+ .driver = { -+ .name = "surfacegen5_acpi_san", -+ .acpi_match_table = ACPI_PTR(surfacegen5_acpi_san_match), -+ }, -+}; -+ -+ -+inline int surfacegen5_acpi_san_register(void) -+{ -+ return platform_driver_register(&surfacegen5_acpi_san); -+} -+ -+inline void surfacegen5_acpi_san_unregister(void) -+{ -+ platform_driver_unregister(&surfacegen5_acpi_san); -+} -+ -+#else /* CONFIG_SURFACE_ACPI_SAN */ -+ -+inline int surfacegen5_acpi_san_register(void) -+{ -+ return 0; -+} -+ -+inline void surfacegen5_acpi_san_unregister(void) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_ACPI_SAN */ -+ -+ -+/************************************************************************* -+ * Virtual HID Framework driver -+ */ -+ -+#ifdef CONFIG_SURFACE_ACPI_VHF -+ -+#define SG5_VHF_INPUT_NAME "Microsoft Virtual HID Framework Device" -+ -+/* -+ * Request ID for VHF events. This value is based on the output of the Surface -+ * EC and should not be changed. -+ */ -+#define SG5_EVENT_VHF_RQID 0x0001 -+#define SG5_EVENT_VHF_TC 0x08 -+ -+ -+struct surfacegen5_vhf_evtctx { -+ struct device *dev; -+ struct hid_device *hid; -+}; -+ -+struct surfacegen5_vhf_drvdata { -+ struct surfacegen5_vhf_evtctx event_ctx; -+}; -+ -+ -+/* -+ * These report descriptors have been extracted from a Surface Book 2. -+ * They seems to be similar enough to be usable on the Surface Laptop. -+ */ -+static const u8 vhf_hid_desc[] = { -+ // keyboard descriptor (event command ID 0x03) -+ 0x05, 0x01, /* Usage Page (Desktop), */ -+ 0x09, 0x06, /* Usage (Keyboard), */ -+ 0xA1, 0x01, /* Collection (Application), */ -+ 0x85, 0x01, /* Report ID (1), */ -+ 0x15, 0x00, /* Logical Minimum (0), */ -+ 0x25, 0x01, /* Logical Maximum (1), */ -+ 0x75, 0x01, /* Report Size (1), */ -+ 0x95, 0x08, /* Report Count (8), */ -+ 0x05, 0x07, /* Usage Page (Keyboard), */ -+ 0x19, 0xE0, /* Usage Minimum (KB Leftcontrol), */ -+ 0x29, 0xE7, /* Usage Maximum (KB Right GUI), */ -+ 0x81, 0x02, /* Input (Variable), */ -+ 0x75, 0x08, /* Report Size (8), */ -+ 0x95, 0x0A, /* Report Count (10), */ -+ 0x19, 0x00, /* Usage Minimum (None), */ -+ 0x29, 0x91, /* Usage Maximum (KB LANG2), */ -+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ -+ 0x81, 0x00, /* Input, */ -+ 0x05, 0x0C, /* Usage Page (Consumer), */ -+ 0x0A, 0xC0, 0x02, /* Usage (02C0h), */ -+ 0xA1, 0x02, /* Collection (Logical), */ -+ 0x1A, 0xC1, 0x02, /* Usage Minimum (02C1h), */ -+ 0x2A, 0xC6, 0x02, /* Usage Maximum (02C6h), */ -+ 0x95, 0x06, /* Report Count (6), */ -+ 0xB1, 0x03, /* Feature (Constant, Variable), */ -+ 0xC0, /* End Collection, */ -+ 0x05, 0x08, /* Usage Page (LED), */ -+ 0x19, 0x01, /* Usage Minimum (01h), */ -+ 0x29, 0x03, /* Usage Maximum (03h), */ -+ 0x75, 0x01, /* Report Size (1), */ -+ 0x95, 0x03, /* Report Count (3), */ -+ 0x25, 0x01, /* Logical Maximum (1), */ -+ 0x91, 0x02, /* Output (Variable), */ -+ 0x95, 0x05, /* Report Count (5), */ -+ 0x91, 0x01, /* Output (Constant), */ -+ 0xC0, /* End Collection, */ -+ -+ // media key descriptor (event command ID 0x04) -+ 0x05, 0x0C, /* Usage Page (Consumer), */ -+ 0x09, 0x01, /* Usage (Consumer Control), */ -+ 0xA1, 0x01, /* Collection (Application), */ -+ 0x85, 0x03, /* Report ID (3), */ -+ 0x75, 0x10, /* Report Size (16), */ -+ 0x15, 0x00, /* Logical Minimum (0), */ -+ 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ -+ 0x19, 0x00, /* Usage Minimum (00h), */ -+ 0x2A, 0xFF, 0x03, /* Usage Maximum (03FFh), */ -+ 0x81, 0x00, /* Input, */ -+ 0xC0, /* End Collection, */ -+}; -+ -+ -+static int vhf_hid_start(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+ return 0; -+} -+ -+static void vhf_hid_stop(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+} -+ -+static int vhf_hid_open(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+ return 0; -+} -+ -+static void vhf_hid_close(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+} -+ -+static int vhf_hid_parse(struct hid_device *hid) -+{ -+ return hid_parse_report(hid, (u8 *)vhf_hid_desc, ARRAY_SIZE(vhf_hid_desc)); -+} -+ -+static int vhf_hid_raw_request(struct hid_device *hid, unsigned char reportnum, -+ u8 *buf, size_t len, unsigned char rtype, -+ int reqtype) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+ return 0; -+} -+ -+static int vhf_hid_output_report(struct hid_device *hid, u8 *buf, size_t len) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+ print_hex_dump_debug("report:", DUMP_PREFIX_OFFSET, 16, 1, buf, len, false); -+ -+ return len; -+} -+ -+static struct hid_ll_driver vhf_hid_ll_driver = { -+ .start = vhf_hid_start, -+ .stop = vhf_hid_stop, -+ .open = vhf_hid_open, -+ .close = vhf_hid_close, -+ .parse = vhf_hid_parse, -+ .raw_request = vhf_hid_raw_request, -+ .output_report = vhf_hid_output_report, -+}; -+ -+ -+static struct hid_device *surfacegen5_vhf_create_hid_device(struct platform_device *pdev) -+{ -+ struct hid_device *hid; -+ -+ hid = hid_allocate_device(); -+ if (IS_ERR(hid)) { -+ return hid; -+ } -+ -+ hid->dev.parent = &pdev->dev; -+ -+ hid->bus = BUS_VIRTUAL; -+ hid->vendor = USB_VENDOR_ID_MICROSOFT; -+ hid->product = USB_DEVICE_ID_MS_VHF; -+ -+ hid->ll_driver = &vhf_hid_ll_driver; -+ -+ sprintf(hid->name, "%s", SG5_VHF_INPUT_NAME); -+ -+ return hid; -+} -+ -+static int surfacegen5_vhf_event_handler(struct surfacegen5_event *event, void *data) -+{ -+ struct surfacegen5_vhf_evtctx *ctx = (struct surfacegen5_vhf_evtctx *)data; -+ -+ if (event->tc == 0x08 && (event->cid == 0x03 || event->cid == 0x04)) { -+ return hid_input_report(ctx->hid, HID_INPUT_REPORT, event->pld, event->len, 1); -+ } -+ -+ dev_warn(ctx->dev, "unsupported event (tc = %d, cid = %d)\n", event->tc, event->cid); -+ return 0; -+} -+ -+static unsigned long surfacegen5_vhf_event_delay(struct surfacegen5_event *event, void *data) -+{ -+ // high priority immediate execution for keyboard events -+ if (event->tc == 0x08 && (event->cid == 0x03 || event->cid == 0x04)) { -+ return SURFACEGEN5_EVENT_IMMEDIATE; -+ } -+ -+ return 0; -+} -+ -+static int surfacegen5_acpi_vhf_probe(struct platform_device *pdev) -+{ -+ struct surfacegen5_vhf_drvdata *drvdata; -+ struct hid_device *hid; -+ int status; -+ -+ // add device link to EC -+ status = surfacegen5_ec_consumer_register(&pdev->dev); -+ if (status) { -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ } -+ -+ drvdata = kzalloc(sizeof(struct surfacegen5_vhf_drvdata), GFP_KERNEL); -+ if (!drvdata) { -+ return -ENOMEM; -+ } -+ -+ hid = surfacegen5_vhf_create_hid_device(pdev); -+ if (IS_ERR(hid)) { -+ status = PTR_ERR(hid); -+ goto err_probe_hid; -+ } -+ -+ status = hid_add_device(hid); -+ if (status) { -+ goto err_add_hid; -+ } -+ -+ drvdata->event_ctx.dev = &pdev->dev; -+ drvdata->event_ctx.hid = hid; -+ -+ platform_set_drvdata(pdev, drvdata); -+ -+ /* -+ * Set event hanlder for VHF events. They seem to be enabled by -+ * default, thus there should be no need to explicitly enable them. -+ */ -+ status = surfacegen5_ec_set_delayed_event_handler( -+ SG5_EVENT_VHF_RQID, -+ surfacegen5_vhf_event_handler, -+ surfacegen5_vhf_event_delay, -+ &drvdata->event_ctx); -+ if (status) { -+ goto err_add_hid; -+ } -+ -+ status = surfacegen5_ec_enable_event_source(SG5_EVENT_VHF_TC, 0x01, SG5_EVENT_VHF_RQID); -+ if (status) { -+ goto err_event_source; -+ } -+ -+ return 0; -+ -+err_event_source: -+ surfacegen5_ec_remove_event_handler(SG5_EVENT_VHF_RQID); -+err_add_hid: -+ hid_destroy_device(hid); -+ platform_set_drvdata(pdev, NULL); -+err_probe_hid: -+ kfree(drvdata); -+ return status; -+} -+ -+static int surfacegen5_acpi_vhf_remove(struct platform_device *pdev) -+{ -+ struct surfacegen5_vhf_drvdata *drvdata = platform_get_drvdata(pdev); -+ -+ surfacegen5_ec_disable_event_source(SG5_EVENT_VHF_TC, 0x01, SG5_EVENT_VHF_RQID); -+ surfacegen5_ec_remove_event_handler(SG5_EVENT_VHF_RQID); -+ -+ hid_destroy_device(drvdata->event_ctx.hid); -+ kfree(drvdata); -+ -+ platform_set_drvdata(pdev, NULL); -+ return 0; -+} -+ -+ -+static const struct acpi_device_id surfacegen5_acpi_vhf_match[] = { -+ { "MSHW0096" }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_vhf_match); -+ -+struct platform_driver surfacegen5_acpi_vhf = { -+ .probe = surfacegen5_acpi_vhf_probe, -+ .remove = surfacegen5_acpi_vhf_remove, -+ .driver = { -+ .name = "surfacegen5_acpi_vhf", -+ .acpi_match_table = ACPI_PTR(surfacegen5_acpi_vhf_match), -+ }, -+}; -+ -+ -+inline int surfacegen5_acpi_vhf_register(void) -+{ -+ return platform_driver_register(&surfacegen5_acpi_vhf); -+} -+ -+inline void surfacegen5_acpi_vhf_unregister(void) -+{ -+ platform_driver_unregister(&surfacegen5_acpi_vhf); -+} -+ -+#else /* CONFIG_SURFACE_ACPI_VHF */ -+ -+inline int surfacegen5_acpi_vhf_register(void) -+{ -+ return 0; -+} -+ -+inline void surfacegen5_acpi_vhf_unregister(void) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_ACPI_VHF */ -+ -+ -+/************************************************************************* -+ * Detachment System Driver (DTX) -+ */ -+ -+#ifdef CONFIG_SURFACE_ACPI_DTX -+ -+#define SG5_DTX_INPUT_NAME "Microsoft Surface Base 2 Integration Device" -+ -+#define DTX_CMD_LATCH_LOCK _IO(0x11, 0x01) -+#define DTX_CMD_LATCH_UNLOCK _IO(0x11, 0x02) -+#define DTX_CMD_LATCH_REQUEST _IO(0x11, 0x03) -+#define DTX_CMD_LATCH_OPEN _IO(0x11, 0x04) -+#define DTX_CMD_GET_OPMODE _IOR(0x11, 0x05, int) -+ -+#define SG5_RQST_DTX_TC 0x11 -+#define SG5_RQST_DTX_CID_LATCH_LOCK 0x06 -+#define SG5_RQST_DTX_CID_LATCH_UNLOCK 0x07 -+#define SG5_RQST_DTX_CID_LATCH_REQUEST 0x08 -+#define SG5_RQST_DTX_CID_LATCH_OPEN 0x09 -+#define SG5_RQST_DTX_CID_GET_OPMODE 0x0D -+ -+#define SG5_EVENT_DTX_TC 0x11 -+#define SG5_EVENT_DTX_RQID 0x0011 -+#define SG5_EVENT_DTX_CID_CONNECTION 0x0c -+#define SG5_EVENT_DTX_CID_BUTTON 0x0e -+#define SG5_EVENT_DTX_CID_ERROR 0x0f -+#define SG5_EVENT_DTX_CID_LATCH_STATUS 0x11 -+ -+#define DTX_OPMODE_TABLET 0x00 -+#define DTX_OPMODE_LAPTOP 0x01 -+#define DTX_OPMODE_STUDIO 0x02 -+ -+#define DTX_LATCH_CLOSED 0x00 -+#define DTX_LATCH_OPENED 0x01 -+ -+// Warning: This must always be a power of 2! -+#define SURFACE_DTX_CLIENT_BUF_SIZE 16 -+ -+#define SG5_DTX_CONNECT_OPMODE_DELAY 1000 -+ -+#define DTX_ERR KERN_ERR "surfacegen5_acpi_dtx: " -+#define DTX_WARN KERN_WARNING "surfacegen5_acpi_dtx: " -+ -+ -+struct surface_dtx_event { -+ u8 type; -+ u8 code; -+ u8 arg0; -+ u8 arg1; -+} __packed; -+ -+struct surface_dtx_dev { -+ wait_queue_head_t waitq; -+ struct miscdevice mdev; -+ spinlock_t client_lock; -+ struct list_head client_list; -+ struct mutex mutex; -+ bool active; -+ spinlock_t input_lock; -+ struct input_dev *input_dev; -+}; -+ -+struct surface_dtx_client { -+ struct list_head node; -+ struct surface_dtx_dev *ddev; -+ struct fasync_struct *fasync; -+ spinlock_t buffer_lock; -+ unsigned int buffer_head; -+ unsigned int buffer_tail; -+ struct surface_dtx_event buffer[SURFACE_DTX_CLIENT_BUF_SIZE]; -+}; -+ -+ -+static struct surface_dtx_dev surface_dtx_dev; -+ -+ -+static int sg5_ec_query_opmpde(void) -+{ -+ u8 result_buf[1]; -+ int status; -+ -+ struct surfacegen5_rqst rqst = { -+ .tc = SG5_RQST_DTX_TC, -+ .iid = 0, -+ .cid = SG5_RQST_DTX_CID_GET_OPMODE, -+ .snc = 1, -+ .cdl = 0, -+ .pld = NULL, -+ }; -+ -+ struct surfacegen5_buf result = { -+ .cap = 1, -+ .len = 0, -+ .data = result_buf, -+ }; -+ -+ status = surfacegen5_ec_rqst(&rqst, &result); -+ if (status) { -+ return status; -+ } -+ -+ if (result.len != 1) { -+ return -EFAULT; -+ } -+ -+ return result.data[0]; -+} -+ -+ -+static int dtx_cmd_simple(u8 cid) -+{ -+ struct surfacegen5_rqst rqst = { -+ .tc = SG5_RQST_DTX_TC, -+ .iid = 0, -+ .cid = cid, -+ .snc = 0, -+ .cdl = 0, -+ .pld = NULL, -+ }; -+ -+ return surfacegen5_ec_rqst(&rqst, NULL); -+} -+ -+static int dtx_cmd_get_opmode(int __user *buf) -+{ -+ int opmode = sg5_ec_query_opmpde(); -+ if (opmode < 0) { -+ return opmode; -+ } -+ -+ if (put_user(opmode, buf)) { -+ return -EACCES; -+ } -+ -+ return 0; -+} -+ -+ -+static int surface_dtx_open(struct inode *inode, struct file *file) -+{ -+ struct surface_dtx_dev *ddev = container_of(file->private_data, struct surface_dtx_dev, mdev); -+ struct surface_dtx_client *client; -+ -+ // initialize client -+ client = kzalloc(sizeof(struct surface_dtx_client), GFP_KERNEL); -+ if (!client) { -+ return -ENOMEM; -+ } -+ -+ spin_lock_init(&client->buffer_lock); -+ client->buffer_head = 0; -+ client->buffer_tail = 0; -+ client->ddev = ddev; -+ -+ // attach client -+ spin_lock(&ddev->client_lock); -+ list_add_tail_rcu(&client->node, &ddev->client_list); -+ spin_unlock(&ddev->client_lock); -+ -+ file->private_data = client; -+ nonseekable_open(inode, file); -+ -+ return 0; -+} -+ -+static int surface_dtx_release(struct inode *inode, struct file *file) -+{ -+ struct surface_dtx_client *client = file->private_data; -+ -+ // detach client -+ spin_lock(&client->ddev->client_lock); -+ list_del_rcu(&client->node); -+ spin_unlock(&client->ddev->client_lock); -+ synchronize_rcu(); -+ -+ kfree(client); -+ file->private_data = NULL; -+ -+ return 0; -+} -+ -+static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs) -+{ -+ struct surface_dtx_client *client = file->private_data; -+ struct surface_dtx_dev *ddev = client->ddev; -+ struct surface_dtx_event event; -+ size_t read = 0; -+ int status = 0; -+ -+ if (count != 0 && count < sizeof(struct surface_dtx_event)) { -+ return -EINVAL; -+ } -+ -+ if (!ddev->active) { -+ return -ENODEV; -+ } -+ -+ // check availability -+ if (client->buffer_head == client->buffer_tail){ -+ if (file->f_flags & O_NONBLOCK) { -+ return -EAGAIN; -+ } -+ -+ status = wait_event_interruptible(ddev->waitq, -+ client->buffer_head != client->buffer_tail || -+ !ddev->active); -+ if (status) { -+ return status; -+ } -+ -+ if (!ddev->active) { -+ return -ENODEV; -+ } -+ } -+ -+ // copy events one by one -+ while (read + sizeof(struct surface_dtx_event) <= count) { -+ spin_lock_irq(&client->buffer_lock); -+ -+ if(client->buffer_head == client->buffer_tail) { -+ spin_unlock_irq(&client->buffer_lock); -+ break; -+ } -+ -+ // get one event -+ event = client->buffer[client->buffer_tail]; -+ client->buffer_tail = (client->buffer_tail + 1) & (SURFACE_DTX_CLIENT_BUF_SIZE - 1); -+ spin_unlock_irq(&client->buffer_lock); -+ -+ // copy to userspace -+ if(copy_to_user(buf, &event, sizeof(struct surface_dtx_event))) { -+ return -EFAULT; -+ } -+ -+ read += sizeof(struct surface_dtx_event); -+ } -+ -+ return read; -+} -+ -+static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt) -+{ -+ struct surface_dtx_client *client = file->private_data; -+ int mask; -+ -+ poll_wait(file, &client->ddev->waitq, pt); -+ -+ if (client->ddev->active) { -+ mask = EPOLLOUT | EPOLLWRNORM; -+ } else { -+ mask = EPOLLHUP | EPOLLERR; -+ } -+ -+ if (client->buffer_head != client->buffer_tail) { -+ mask |= EPOLLIN | EPOLLRDNORM; -+ } -+ -+ return mask; -+} -+ -+static int surface_dtx_fasync(int fd, struct file *file, int on) -+{ -+ struct surface_dtx_client *client = file->private_data; -+ -+ return fasync_helper(fd, file, on, &client->fasync); -+} -+ -+static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -+{ -+ struct surface_dtx_client *client = file->private_data; -+ struct surface_dtx_dev *ddev = client->ddev; -+ int status; -+ -+ status = mutex_lock_interruptible(&ddev->mutex); -+ if (status) { -+ return status; -+ } -+ -+ if (!ddev->active) { -+ mutex_unlock(&ddev->mutex); -+ return -ENODEV; -+ } -+ -+ switch (cmd) { -+ case DTX_CMD_LATCH_LOCK: -+ status = dtx_cmd_simple(SG5_RQST_DTX_CID_LATCH_LOCK); -+ break; -+ -+ case DTX_CMD_LATCH_UNLOCK: -+ status = dtx_cmd_simple(SG5_RQST_DTX_CID_LATCH_UNLOCK); -+ break; -+ -+ case DTX_CMD_LATCH_REQUEST: -+ status = dtx_cmd_simple(SG5_RQST_DTX_CID_LATCH_REQUEST); -+ break; -+ -+ case DTX_CMD_LATCH_OPEN: -+ status = dtx_cmd_simple(SG5_RQST_DTX_CID_LATCH_OPEN); -+ break; -+ -+ case DTX_CMD_GET_OPMODE: -+ status = dtx_cmd_get_opmode((int __user *)arg); -+ break; -+ -+ default: -+ status = -EINVAL; -+ break; -+ } -+ -+ mutex_unlock(&ddev->mutex); -+ return status; -+} -+ -+static const struct file_operations surface_dtx_fops = { -+ .owner = THIS_MODULE, -+ .open = surface_dtx_open, -+ .release = surface_dtx_release, -+ .read = surface_dtx_read, -+ .poll = surface_dtx_poll, -+ .fasync = surface_dtx_fasync, -+ .unlocked_ioctl = surface_dtx_ioctl, -+ .llseek = no_llseek, -+}; -+ -+static struct surface_dtx_dev surface_dtx_dev = { -+ .mdev = { -+ .minor = MISC_DYNAMIC_MINOR, -+ .name = "surface_dtx", -+ .fops = &surface_dtx_fops, -+ }, -+ .client_lock = __SPIN_LOCK_UNLOCKED(), -+ .input_lock = __SPIN_LOCK_UNLOCKED(), -+ .mutex = __MUTEX_INITIALIZER(surface_dtx_dev.mutex), -+ .active = false, -+}; -+ -+ -+static void surface_dtx_push_event(struct surface_dtx_dev *ddev, struct surface_dtx_event *event) -+{ -+ struct surface_dtx_client *client; -+ -+ rcu_read_lock(); -+ list_for_each_entry_rcu(client, &ddev->client_list, node) { -+ spin_lock(&client->buffer_lock); -+ -+ client->buffer[client->buffer_head++] = *event; -+ client->buffer_head &= SURFACE_DTX_CLIENT_BUF_SIZE - 1; -+ -+ if (unlikely(client->buffer_head == client->buffer_tail)) { -+ printk(DTX_WARN "event buffer overrun\n"); -+ client->buffer_tail = (client->buffer_tail + 1) & (SURFACE_DTX_CLIENT_BUF_SIZE - 1); -+ } -+ -+ spin_unlock(&client->buffer_lock); -+ -+ kill_fasync(&client->fasync, SIGIO, POLL_IN); -+ } -+ rcu_read_unlock(); -+ -+ wake_up_interruptible(&ddev->waitq); -+} -+ -+ -+static void surface_dtx_update_opmpde(struct surface_dtx_dev *ddev) -+{ -+ struct surface_dtx_event event; -+ int opmode; -+ -+ // get operation mode -+ opmode = sg5_ec_query_opmpde(); -+ if (opmode < 0) { -+ printk(DTX_ERR "EC request failed with error %d\n", opmode); -+ } -+ -+ // send DTX event -+ event.type = 0x11; -+ event.code = 0x0D; -+ event.arg0 = opmode; -+ event.arg1 = 0x00; -+ -+ surface_dtx_push_event(ddev, &event); -+ -+ // send SW_TABLET_MODE event -+ spin_lock(&ddev->input_lock); -+ input_report_switch(ddev->input_dev, SW_TABLET_MODE, opmode == 0x00); -+ input_sync(ddev->input_dev); -+ spin_unlock(&ddev->input_lock); -+} -+ -+static int surface_dtx_evt_dtx(struct surfacegen5_event *in_event, void *data) -+{ -+ struct surface_dtx_dev *ddev = data; -+ struct surface_dtx_event event; -+ -+ switch (in_event->cid) { -+ case SG5_EVENT_DTX_CID_CONNECTION: -+ case SG5_EVENT_DTX_CID_BUTTON: -+ case SG5_EVENT_DTX_CID_ERROR: -+ case SG5_EVENT_DTX_CID_LATCH_STATUS: -+ if (in_event->len > 2) { -+ printk(DTX_ERR "unexpected payload size (cid: %x, len: %u)\n", -+ in_event->cid, in_event->len); -+ return 0; -+ } -+ -+ event.type = in_event->tc; -+ event.code = in_event->cid; -+ event.arg0 = in_event->len >= 1 ? in_event->pld[0] : 0x00; -+ event.arg1 = in_event->len >= 2 ? in_event->pld[1] : 0x00; -+ surface_dtx_push_event(ddev, &event); -+ break; -+ -+ default: -+ printk(DTX_WARN "unhandled dtx event (cid: %x)\n", in_event->cid); -+ } -+ -+ // update device mode -+ if (in_event->cid == SG5_EVENT_DTX_CID_CONNECTION) { -+ if (in_event->pld[0]) { -+ // Note: we're already in a workqueue task -+ msleep(SG5_DTX_CONNECT_OPMODE_DELAY); -+ } -+ -+ surface_dtx_update_opmpde(ddev); -+ } -+ -+ return 0; -+} -+ -+static int surface_dtx_events_setup(struct surface_dtx_dev *ddev) -+{ -+ int status; -+ -+ status = surfacegen5_ec_set_event_handler(SG5_EVENT_DTX_RQID, surface_dtx_evt_dtx, ddev); -+ if (status) { -+ goto err_event_handler; -+ } -+ -+ status = surfacegen5_ec_enable_event_source(SG5_EVENT_DTX_TC, 0x01, SG5_EVENT_DTX_RQID); -+ if (status) { -+ goto err_event_source; -+ } -+ -+ return 0; -+ -+err_event_source: -+ surfacegen5_ec_remove_event_handler(SG5_EVENT_DTX_RQID); -+err_event_handler: -+ return status; -+} -+ -+static void surface_dtx_events_disable(void) -+{ -+ surfacegen5_ec_disable_event_source(SG5_EVENT_DTX_TC, 0x01, SG5_EVENT_DTX_RQID); -+ surfacegen5_ec_remove_event_handler(SG5_EVENT_DTX_RQID); -+} -+ -+ -+static struct input_dev *surface_dtx_register_inputdev(struct platform_device *pdev) -+{ -+ struct input_dev *input_dev; -+ int status; -+ -+ input_dev = input_allocate_device(); -+ if (!input_dev) { -+ return ERR_PTR(-ENOMEM); -+ } -+ -+ input_dev->name = SG5_DTX_INPUT_NAME; -+ input_dev->dev.parent = &pdev->dev; -+ input_dev->id.bustype = BUS_VIRTUAL; -+ input_dev->id.vendor = USB_VENDOR_ID_MICROSOFT; -+ input_dev->id.product = USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION; -+ -+ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); -+ -+ status = sg5_ec_query_opmpde(); -+ if (status < 0) { -+ input_free_device(input_dev); -+ return ERR_PTR(status); -+ } -+ -+ input_report_switch(input_dev, SW_TABLET_MODE, status == 0x00); -+ -+ status = input_register_device(input_dev); -+ if (status) { -+ input_unregister_device(input_dev); -+ return ERR_PTR(status); -+ } -+ -+ return input_dev; -+} -+ -+ -+static int surfacegen5_acpi_dtx_probe(struct platform_device *pdev) -+{ -+ struct surface_dtx_dev *ddev = &surface_dtx_dev; -+ struct input_dev *input_dev; -+ int status; -+ -+ // link to ec -+ status = surfacegen5_ec_consumer_register(&pdev->dev); -+ if (status) { -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ } -+ -+ input_dev = surface_dtx_register_inputdev(pdev); -+ if (IS_ERR(input_dev)) { -+ return PTR_ERR(input_dev); -+ } -+ -+ // initialize device -+ mutex_lock(&ddev->mutex); -+ if (ddev->active) { -+ mutex_unlock(&ddev->mutex); -+ status = -ENODEV; -+ goto err_register; -+ } -+ -+ INIT_LIST_HEAD(&ddev->client_list); -+ init_waitqueue_head(&ddev->waitq); -+ ddev->active = true; -+ ddev->input_dev = input_dev; -+ mutex_unlock(&ddev->mutex); -+ -+ status = misc_register(&ddev->mdev); -+ if (status) { -+ goto err_register; -+ } -+ -+ // enable events -+ status = surface_dtx_events_setup(ddev); -+ if (status) { -+ goto err_events_setup; -+ } -+ -+ return 0; -+ -+err_events_setup: -+ misc_deregister(&ddev->mdev); -+err_register: -+ input_unregister_device(ddev->input_dev); -+ return status; -+} -+ -+static int surfacegen5_acpi_dtx_remove(struct platform_device *pdev) -+{ -+ struct surface_dtx_dev *ddev = &surface_dtx_dev; -+ struct surface_dtx_client *client; -+ -+ mutex_lock(&ddev->mutex); -+ if (!ddev->active) { -+ mutex_unlock(&ddev->mutex); -+ return 0; -+ } -+ -+ // mark as inactive -+ ddev->active = false; -+ mutex_unlock(&ddev->mutex); -+ -+ // After this call we're guaranteed that no more input events will arive -+ surface_dtx_events_disable(); -+ -+ // wake up clients -+ spin_lock(&ddev->client_lock); -+ list_for_each_entry(client, &ddev->client_list, node) { -+ kill_fasync(&client->fasync, SIGIO, POLL_HUP); -+ } -+ spin_unlock(&ddev->client_lock); -+ -+ wake_up_interruptible(&ddev->waitq); -+ -+ // unregister user-space devices -+ input_unregister_device(ddev->input_dev); -+ misc_deregister(&ddev->mdev); -+ -+ return 0; -+} -+ -+ -+static const struct acpi_device_id surfacegen5_acpi_dtx_match[] = { -+ { "MSHW0133", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_dtx_match); -+ -+struct platform_driver surfacegen5_acpi_dtx = { -+ .probe = surfacegen5_acpi_dtx_probe, -+ .remove = surfacegen5_acpi_dtx_remove, -+ .driver = { -+ .name = "surfacegen5_acpi_dtx", -+ .acpi_match_table = ACPI_PTR(surfacegen5_acpi_dtx_match), -+ }, -+}; -+ -+ -+inline int surfacegen5_acpi_dtx_register(void) -+{ -+ return platform_driver_register(&surfacegen5_acpi_dtx); -+} -+ -+inline void surfacegen5_acpi_dtx_unregister(void) -+{ -+ platform_driver_unregister(&surfacegen5_acpi_dtx); -+} -+ -+#else /* CONFIG_SURFACE_ACPI_DTX */ -+ -+inline int surfacegen5_acpi_dtx_register(void) -+{ -+ return 0; -+} -+ -+inline void surfacegen5_acpi_dtx_unregister(void) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_ACPI_DTX */ -+ -+ -+/************************************************************************* -+ * Surface Platform Integration Driver -+ */ -+ -+#ifdef CONFIG_SURFACE_ACPI_SID -+ -+struct si_lid_device { -+ const char *acpi_path; -+ const u32 gpe_number; -+}; -+ -+struct si_device_info { -+ const bool has_perf_mode; -+ const struct si_lid_device *lid_device; -+}; -+ -+ -+static const struct si_lid_device lid_device_l17 = { -+ .acpi_path = "\\_SB.LID0", -+ .gpe_number = 0x17, -+}; -+ -+static const struct si_lid_device lid_device_l4F = { -+ .acpi_path = "\\_SB.LID0", -+ .gpe_number = 0x4F, -+}; -+ -+static const struct si_lid_device lid_device_l57 = { -+ .acpi_path = "\\_SB.LID0", -+ .gpe_number = 0x57, -+}; -+ -+ -+static const struct si_device_info si_device_pro_4 = { -+ .has_perf_mode = false, -+ .lid_device = &lid_device_l17, -+}; -+ -+static const struct si_device_info si_device_pro_5 = { -+ .has_perf_mode = false, -+ .lid_device = &lid_device_l4F, -+}; -+ -+static const struct si_device_info si_device_pro_6 = { -+ .has_perf_mode = false, -+ .lid_device = &lid_device_l4F, -+}; -+ -+static const struct si_device_info si_device_book_1 = { -+ .has_perf_mode = false, -+ .lid_device = &lid_device_l17, -+}; -+ -+static const struct si_device_info si_device_book_2 = { -+ .has_perf_mode = true, -+ .lid_device = &lid_device_l17, -+}; -+ -+static const struct si_device_info si_device_laptop_1 = { -+ .has_perf_mode = false, -+ .lid_device = &lid_device_l57, -+}; -+ -+static const struct si_device_info si_device_laptop_2 = { -+ .has_perf_mode = false, -+ .lid_device = &lid_device_l57, -+}; -+ -+ -+static const struct dmi_system_id dmi_lid_device_table[] = { -+ { -+ .ident = "Surface Pro 4", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), -+ }, -+ .driver_data = (void *)&si_device_pro_4, -+ }, -+ { -+ .ident = "Surface Pro 5", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), -+ }, -+ .driver_data = (void *)&si_device_pro_5, -+ }, -+ { -+ .ident = "Surface Pro 5 (LTE)", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), -+ }, -+ .driver_data = (void *)&si_device_pro_5, -+ }, -+ { -+ .ident = "Surface Pro 6", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), -+ }, -+ .driver_data = (void *)&si_device_pro_6, -+ }, -+ { -+ .ident = "Surface Book 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), -+ }, -+ .driver_data = (void *)&si_device_book_1, -+ }, -+ { -+ .ident = "Surface Book 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), -+ }, -+ .driver_data = (void *)&si_device_book_2, -+ }, -+ { -+ .ident = "Surface Laptop 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), -+ }, -+ .driver_data = (void *)&si_device_laptop_1, -+ }, -+ { -+ .ident = "Surface Laptop 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), -+ }, -+ .driver_data = (void *)&si_device_laptop_2, -+ }, -+ { } -+}; -+ -+#define SG5_PARAM_PERM (S_IRUGO | S_IWUSR) -+ -+enum sg5_perf_mode { -+ SG5_PERF_MODE_NORMAL = 1, -+ SG5_PERF_MODE_BATTERY = 2, -+ SG5_PERF_MODE_PERF1 = 3, -+ SG5_PERF_MODE_PERF2 = 4, -+ -+ __SG5_PERF_MODE__START = 1, -+ __SG5_PERF_MODE__END = 4, -+}; -+ -+enum sg5_param_perf_mode { -+ SG5_PARAM_PERF_MODE_AS_IS = 0, -+ SG5_PARAM_PERF_MODE_NORMAL = SG5_PERF_MODE_NORMAL, -+ SG5_PARAM_PERF_MODE_BATTERY = SG5_PERF_MODE_BATTERY, -+ SG5_PARAM_PERF_MODE_PERF1 = SG5_PERF_MODE_PERF1, -+ SG5_PARAM_PERF_MODE_PERF2 = SG5_PERF_MODE_PERF2, -+ -+ __SG5_PARAM_PERF_MODE__START = 0, -+ __SG5_PARAM_PERF_MODE__END = 4, -+}; -+ -+ -+static int sg5_ec_perf_mode_get(void) -+{ -+ u8 result_buf[8] = { 0 }; -+ int status; -+ -+ struct surfacegen5_rqst rqst = { -+ .tc = 0x03, -+ .iid = 0x00, -+ .cid = 0x02, -+ .snc = 0x01, -+ .cdl = 0x00, -+ .pld = NULL, -+ }; -+ -+ struct surfacegen5_buf result = { -+ .cap = ARRAY_SIZE(result_buf), -+ .len = 0, -+ .data = result_buf, -+ }; -+ -+ status = surfacegen5_ec_rqst(&rqst, &result); -+ if (status) { -+ return status; -+ } -+ -+ if (result.len != 8) { -+ return -EFAULT; -+ } -+ -+ return get_unaligned_le32(&result.data[0]); -+} -+ -+static int sg5_ec_perf_mode_set(int perf_mode) -+{ -+ u8 payload[4] = { 0 }; -+ -+ struct surfacegen5_rqst rqst = { -+ .tc = 0x03, -+ .iid = 0x00, -+ .cid = 0x03, -+ .snc = 0x00, -+ .cdl = ARRAY_SIZE(payload), -+ .pld = payload, -+ }; -+ -+ if (perf_mode < __SG5_PERF_MODE__START || perf_mode > __SG5_PERF_MODE__END) { -+ return -EINVAL; -+ } -+ -+ put_unaligned_le32(perf_mode, &rqst.pld[0]); -+ return surfacegen5_ec_rqst(&rqst, NULL); -+} -+ -+ -+static int param_perf_mode_set(const char *val, const struct kernel_param *kp) -+{ -+ int perf_mode; -+ int status; -+ -+ status = kstrtoint(val, 0, &perf_mode); -+ if (status) { -+ return status; -+ } -+ -+ if (perf_mode < __SG5_PARAM_PERF_MODE__START || perf_mode > __SG5_PARAM_PERF_MODE__END) { -+ return -EINVAL; -+ } -+ -+ return param_set_int(val, kp); -+} -+ -+static const struct kernel_param_ops param_perf_mode_ops = { -+ .set = param_perf_mode_set, -+ .get = param_get_int, -+}; -+ -+static int param_perf_mode_init = SG5_PARAM_PERF_MODE_AS_IS; -+static int param_perf_mode_exit = SG5_PARAM_PERF_MODE_AS_IS; -+ -+module_param_cb(perf_mode_init, ¶m_perf_mode_ops, ¶m_perf_mode_init, SG5_PARAM_PERM); -+module_param_cb(perf_mode_exit, ¶m_perf_mode_ops, ¶m_perf_mode_exit, SG5_PARAM_PERM); -+ -+MODULE_PARM_DESC(perf_mode_init, "Performance-mode to be set on module initialization"); -+MODULE_PARM_DESC(perf_mode_exit, "Performance-mode to be set on module exit"); -+ -+static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, char *data) -+{ -+ int perf_mode; -+ -+ perf_mode = sg5_ec_perf_mode_get(); -+ if (perf_mode < 0) { -+ dev_err(dev, "failed to get current performance mode: %d", perf_mode); -+ return -EIO; -+ } -+ -+ return sprintf(data, "%d\n", perf_mode); -+} -+ -+static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr, -+ const char *data, size_t count) -+{ -+ int perf_mode; -+ int status; -+ -+ status = kstrtoint(data, 0, &perf_mode); -+ if (status) { -+ return status; -+ } -+ -+ status = sg5_ec_perf_mode_set(perf_mode); -+ if (status) { -+ return status; -+ } -+ -+ // TODO: Should we notify ACPI here? -+ // -+ // There is a _DSM call described as -+ // WSID._DSM: Notify DPTF on Slider State change -+ // which calls -+ // ODV3 = ToInteger (Arg3) -+ // Notify(IETM, 0x88) -+ // IETM is an INT3400 Intel Dynamic Power Performance Management -+ // device, part of the DPTF framework. From the corresponding -+ // kernel driver, it looks like event 0x88 is being ignored. Also -+ // it is currently unknown what the consequecnes of setting ODV3 -+ // are. -+ -+ return count; -+} -+ -+const static DEVICE_ATTR_RW(perf_mode); -+ -+static int sid_perf_mode_setup(struct platform_device *pdev, const struct si_device_info *info) -+{ -+ int status; -+ -+ if (!info->has_perf_mode) -+ return 0; -+ -+ // link to ec -+ status = surfacegen5_ec_consumer_register(&pdev->dev); -+ if (status) { -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ } -+ -+ // set initial perf_mode -+ if (param_perf_mode_init != SG5_PARAM_PERF_MODE_AS_IS) { -+ status = sg5_ec_perf_mode_set(param_perf_mode_init); -+ if (status) { -+ return status; -+ } -+ } -+ -+ // register perf_mode attribute -+ status = sysfs_create_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr); -+ if (status) { -+ goto err_sysfs; -+ } -+ -+ return 0; -+ -+err_sysfs: -+ sg5_ec_perf_mode_set(param_perf_mode_exit); -+ return status; -+} -+ -+static void sid_perf_mode_remove(struct platform_device *pdev, const struct si_device_info *info) -+{ -+ if (!info->has_perf_mode) -+ return; -+ -+ // remove perf_mode attribute -+ sysfs_remove_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr); -+ -+ // set exit perf_mode -+ sg5_ec_perf_mode_set(param_perf_mode_exit); -+} -+ -+ -+static int sid_lid_enable_wakeup(const struct si_device_info *info, bool enable) -+{ -+ int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE; -+ int status; -+ -+ if (!info->lid_device) -+ return 0; -+ -+ status = acpi_set_gpe_wake_mask(NULL, info->lid_device->gpe_number, action); -+ if (status) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static int sid_lid_device_setup(const struct si_device_info *info) -+{ -+ acpi_handle lid_handle; -+ int status; -+ -+ if (!info->lid_device) -+ return 0; -+ -+ status = acpi_get_handle(NULL, (acpi_string)info->lid_device->acpi_path, &lid_handle); -+ if (status) -+ return -EFAULT; -+ -+ status = acpi_setup_gpe_for_wake(lid_handle, NULL, info->lid_device->gpe_number); -+ if (status) -+ return -EFAULT; -+ -+ status = acpi_enable_gpe(NULL, info->lid_device->gpe_number); -+ if (status) -+ return -EFAULT; -+ -+ return sid_lid_enable_wakeup(info, false); -+} -+ -+static void sid_lid_device_remove(const struct si_device_info *info) -+{ -+ /* restore default behavior without this module */ -+ sid_lid_enable_wakeup(info, false); -+} -+ -+ -+static int surfacegen5_acpi_sid_suspend(struct device *dev) -+{ -+ const struct si_device_info *info = dev_get_drvdata(dev); -+ return sid_lid_enable_wakeup(info, true); -+} -+ -+static int surfacegen5_acpi_sid_resume(struct device *dev) -+{ -+ const struct si_device_info *info = dev_get_drvdata(dev); -+ return sid_lid_enable_wakeup(info, false); -+} -+ -+static SIMPLE_DEV_PM_OPS(surfacegen5_acpi_sid_pm, surfacegen5_acpi_sid_suspend, surfacegen5_acpi_sid_resume); -+ -+ -+static int surfacegen5_acpi_sid_probe(struct platform_device *pdev) -+{ -+ const struct dmi_system_id *dmi_match; -+ struct si_device_info *info; -+ int status; -+ -+ dmi_match = dmi_first_match(dmi_lid_device_table); -+ if (!dmi_match) -+ return -ENODEV; -+ -+ info = dmi_match->driver_data; -+ -+ platform_set_drvdata(pdev, info); -+ -+ status = sid_perf_mode_setup(pdev, info); -+ if (status) -+ goto err_perf_mode; -+ -+ status = sid_lid_device_setup(info); -+ if (status) -+ goto err_lid; -+ -+ return 0; -+ -+err_lid: -+ sid_perf_mode_remove(pdev, info); -+err_perf_mode: -+ return status; -+} -+ -+static int surfacegen5_acpi_sid_remove(struct platform_device *pdev) -+{ -+ const struct si_device_info *info = platform_get_drvdata(pdev); -+ -+ sid_perf_mode_remove(pdev, info); -+ sid_lid_device_remove(info); -+ -+ platform_set_drvdata(pdev, NULL); -+ return 0; -+} -+ -+static const struct acpi_device_id surfacegen5_acpi_sid_match[] = { -+ { "MSHW0081", }, /* Surface Pro 4, 5, and 6 */ -+ { "MSHW0080", }, /* Surface Book 1 */ -+ { "MSHW0107", }, /* Surface Book 2 */ -+ { "MSHW0086", }, /* Surface Laptop 1 */ -+ { "MSHW0112", }, /* Surface Laptop 2 */ -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_sid_match); -+ -+struct platform_driver surfacegen5_acpi_sid = { -+ .probe = surfacegen5_acpi_sid_probe, -+ .remove = surfacegen5_acpi_sid_remove, -+ .driver = { -+ .name = "surfacegen5_acpi_sid", -+ .acpi_match_table = ACPI_PTR(surfacegen5_acpi_sid_match), -+ .pm = &surfacegen5_acpi_sid_pm, -+ }, -+}; -+ -+inline int surfacegen5_acpi_sid_register(void) -+{ -+ return platform_driver_register(&surfacegen5_acpi_sid); -+} -+ -+inline void surfacegen5_acpi_sid_unregister(void) -+{ -+ platform_driver_unregister(&surfacegen5_acpi_sid); -+} -+ -+#else /* CONFIG_SURFACE_ACPI_SID */ -+ -+inline int surfacegen5_acpi_sid_register(void) -+{ -+ return 0; -+} -+ -+inline void surfacegen5_acpi_sid_unregister(void) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_ACPI_SID */ -+ -+ -+/************************************************************************* -+ * Module initialization -+ */ -+ -+int __init surface_acpi_init(void) -+{ -+ int status; -+ -+ status = surfacegen5_acpi_ssh_register(); -+ if (status) { -+ goto err_ssh; -+ } -+ -+ status = surfacegen5_acpi_san_register(); -+ if (status) { -+ goto err_san; -+ } -+ -+ status = surfacegen5_acpi_vhf_register(); -+ if (status) { -+ goto err_vhf; -+ } -+ -+ status = surfacegen5_acpi_dtx_register(); -+ if (status) { -+ goto err_dtx; -+ } -+ -+ status = surfacegen5_acpi_sid_register(); -+ if (status) { -+ goto err_sid; -+ } -+ -+ return 0; -+ -+err_sid: -+ surfacegen5_acpi_sid_unregister(); -+err_dtx: -+ surfacegen5_acpi_vhf_unregister(); -+err_vhf: -+ surfacegen5_acpi_san_unregister(); -+err_san: -+ surfacegen5_acpi_ssh_unregister(); -+err_ssh: -+ return status; -+} -+ -+void __exit surface_acpi_exit(void) -+{ -+ surfacegen5_acpi_sid_unregister(); -+ surfacegen5_acpi_dtx_unregister(); -+ surfacegen5_acpi_vhf_unregister(); -+ surfacegen5_acpi_san_unregister(); -+ surfacegen5_acpi_ssh_unregister(); -+} -+ -+module_init(surface_acpi_init) -+module_exit(surface_acpi_exit) -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("ACPI/Platform Drivers for Microsoft Surface Devices"); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c -index a0ac16ee6575..226adeec2aed 100644 ---- a/drivers/tty/serdev/core.c -+++ b/drivers/tty/serdev/core.c -@@ -552,16 +552,97 @@ static int of_serdev_register_devices(struct serdev_controller *ctrl) - } - - #ifdef CONFIG_ACPI -+ -+#define SERDEV_ACPI_MAX_SCAN_DEPTH 32 -+ -+struct acpi_serdev_lookup { -+ acpi_handle device_handle; -+ acpi_handle controller_handle; -+ int n; -+ int index; -+}; -+ -+static int acpi_serdev_parse_resource(struct acpi_resource *ares, void *data) -+{ -+ struct acpi_serdev_lookup *lookup = data; -+ struct acpi_resource_uart_serialbus *sb; -+ acpi_status status; -+ -+ if (ares->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) -+ return 1; -+ -+ if (ares->data.common_serial_bus.type != ACPI_RESOURCE_SERIAL_TYPE_UART) -+ return 1; -+ -+ if (lookup->index != -1 && lookup->n++ != lookup->index) -+ return 1; -+ -+ sb = &ares->data.uart_serial_bus; -+ -+ status = acpi_get_handle(lookup->device_handle, -+ sb->resource_source.string_ptr, -+ &lookup->controller_handle); -+ if (ACPI_FAILURE(status)) -+ return 1; -+ -+ /* -+ * NOTE: Ideally, we would also want to retreive other properties here, -+ * once setting them before opening the device is supported by serdev. -+ */ -+ -+ return 1; -+} -+ -+static int acpi_serdev_do_lookup(struct acpi_device *adev, -+ struct acpi_serdev_lookup *lookup) -+{ -+ struct list_head resource_list; -+ int ret; -+ -+ lookup->device_handle = acpi_device_handle(adev); -+ lookup->controller_handle = NULL; -+ lookup->n = 0; -+ -+ INIT_LIST_HEAD(&resource_list); -+ ret = acpi_dev_get_resources(adev, &resource_list, -+ acpi_serdev_parse_resource, lookup); -+ acpi_dev_free_resource_list(&resource_list); -+ -+ if (ret < 0) -+ return -EINVAL; -+ -+ return 0; -+} -+ -+static int acpi_serdev_check_resources(struct serdev_controller *ctrl, -+ struct acpi_device *adev) -+{ -+ struct acpi_serdev_lookup lookup; -+ int ret; -+ -+ if (acpi_bus_get_status(adev) || !adev->status.present) -+ return -EINVAL; -+ -+ /* Look for UARTSerialBusV2 resource */ -+ lookup.index = -1; // we only care for the last device -+ -+ ret = acpi_serdev_do_lookup(adev, &lookup); -+ if (ret) -+ return ret; -+ -+ /* Make sure controller and ResourceSource handle match */ -+ if (ACPI_HANDLE(ctrl->dev.parent) != lookup.controller_handle) -+ return -ENODEV; -+ -+ return 0; -+} -+ - static acpi_status acpi_serdev_register_device(struct serdev_controller *ctrl, -- struct acpi_device *adev) -+ struct acpi_device *adev) - { -- struct serdev_device *serdev = NULL; -+ struct serdev_device *serdev; - int err; - -- if (acpi_bus_get_status(adev) || !adev->status.present || -- acpi_device_enumerated(adev)) -- return AE_OK; -- - serdev = serdev_device_alloc(ctrl); - if (!serdev) { - dev_err(&ctrl->dev, "failed to allocate serdev device for %s\n", -@@ -583,7 +664,7 @@ static acpi_status acpi_serdev_register_device(struct serdev_controller *ctrl, - } - - static acpi_status acpi_serdev_add_device(acpi_handle handle, u32 level, -- void *data, void **return_value) -+ void *data, void **return_value) - { - struct serdev_controller *ctrl = data; - struct acpi_device *adev; -@@ -591,22 +672,28 @@ static acpi_status acpi_serdev_add_device(acpi_handle handle, u32 level, - if (acpi_bus_get_device(handle, &adev)) - return AE_OK; - -+ if (acpi_device_enumerated(adev)) -+ return AE_OK; -+ -+ if (acpi_serdev_check_resources(ctrl, adev)) -+ return AE_OK; -+ - return acpi_serdev_register_device(ctrl, adev); - } - -+ - static int acpi_serdev_register_devices(struct serdev_controller *ctrl) - { - acpi_status status; -- acpi_handle handle; - -- handle = ACPI_HANDLE(ctrl->dev.parent); -- if (!handle) -+ if (!has_acpi_companion(ctrl->dev.parent)) - return -ENODEV; - -- status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1, -+ status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, -+ SERDEV_ACPI_MAX_SCAN_DEPTH, - acpi_serdev_add_device, NULL, ctrl, NULL); - if (ACPI_FAILURE(status)) -- dev_dbg(&ctrl->dev, "failed to enumerate serdev slaves\n"); -+ dev_warn(&ctrl->dev, "failed to enumerate serdev slaves\n"); - - if (!ctrl->serdev) - return -ENODEV; --- -2.23.0 - diff --git a/patches/5.2/0002-suspend.patch b/patches/5.2/0002-suspend.patch deleted file mode 100644 index 1d894f0d4..000000000 --- a/patches/5.2/0002-suspend.patch +++ /dev/null @@ -1,281 +0,0 @@ -From cb14df71f8a2da98fd242a35143613c55525ca44 Mon Sep 17 00:00:00 2001 -From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com> -Date: Wed, 31 Jul 2019 08:41:30 +0900 -Subject: [PATCH 02/12] suspend - -Note: -NVMe part will be merged into Linux 5.3. Remove the part in this -patch when it arrives. ---- - drivers/nvme/host/core.c | 24 ++++++++-- - drivers/nvme/host/nvme.h | 6 +++ - drivers/nvme/host/pci.c | 95 ++++++++++++++++++++++++++++++++++++++-- - kernel/power/suspend.c | 11 +++++ - kernel/sysctl.c | 9 ++++ - 5 files changed, 139 insertions(+), 6 deletions(-) - -diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c -index 963b4c6309b9..4b8cf243c150 100644 ---- a/drivers/nvme/host/core.c -+++ b/drivers/nvme/host/core.c -@@ -1114,15 +1114,15 @@ static struct nvme_id_ns *nvme_identify_ns(struct nvme_ctrl *ctrl, - return id; - } - --static int nvme_set_features(struct nvme_ctrl *dev, unsigned fid, unsigned dword11, -- void *buffer, size_t buflen, u32 *result) -+static int nvme_features(struct nvme_ctrl *dev, u8 op, unsigned int fid, -+ unsigned int dword11, void *buffer, size_t buflen, u32 *result) - { - struct nvme_command c; - union nvme_result res; - int ret; - - memset(&c, 0, sizeof(c)); -- c.features.opcode = nvme_admin_set_features; -+ c.features.opcode = op; - c.features.fid = cpu_to_le32(fid); - c.features.dword11 = cpu_to_le32(dword11); - -@@ -1133,6 +1133,24 @@ static int nvme_set_features(struct nvme_ctrl *dev, unsigned fid, unsigned dword - return ret; - } - -+int nvme_set_features(struct nvme_ctrl *dev, unsigned int fid, -+ unsigned int dword11, void *buffer, size_t buflen, -+ u32 *result) -+{ -+ return nvme_features(dev, nvme_admin_set_features, fid, dword11, buffer, -+ buflen, result); -+} -+EXPORT_SYMBOL_GPL(nvme_set_features); -+ -+int nvme_get_features(struct nvme_ctrl *dev, unsigned int fid, -+ unsigned int dword11, void *buffer, size_t buflen, -+ u32 *result) -+{ -+ return nvme_features(dev, nvme_admin_get_features, fid, dword11, buffer, -+ buflen, result); -+} -+EXPORT_SYMBOL_GPL(nvme_get_features); -+ - int nvme_set_queue_count(struct nvme_ctrl *ctrl, int *count) - { - u32 q_count = (*count - 1) | ((*count - 1) << 16); -diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h -index 81215ca32671..9285d5f6437b 100644 ---- a/drivers/nvme/host/nvme.h -+++ b/drivers/nvme/host/nvme.h -@@ -459,6 +459,12 @@ int __nvme_submit_sync_cmd(struct request_queue *q, struct nvme_command *cmd, - union nvme_result *result, void *buffer, unsigned bufflen, - unsigned timeout, int qid, int at_head, - blk_mq_req_flags_t flags, bool poll); -+int nvme_set_features(struct nvme_ctrl *dev, unsigned int fid, -+ unsigned int dword11, void *buffer, size_t buflen, -+ u32 *result); -+int nvme_get_features(struct nvme_ctrl *dev, unsigned int fid, -+ unsigned int dword11, void *buffer, size_t buflen, -+ u32 *result); - int nvme_set_queue_count(struct nvme_ctrl *ctrl, int *count); - void nvme_stop_keep_alive(struct nvme_ctrl *ctrl); - int nvme_reset_ctrl(struct nvme_ctrl *ctrl); -diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c -index 09ffd21d1809..3e22d5f14e93 100644 ---- a/drivers/nvme/host/pci.c -+++ b/drivers/nvme/host/pci.c -@@ -18,6 +18,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -116,6 +117,7 @@ struct nvme_dev { - u32 cmbsz; - u32 cmbloc; - struct nvme_ctrl ctrl; -+ u32 last_ps; - - mempool_t *iod_mempool; - -@@ -2849,16 +2851,94 @@ static void nvme_remove(struct pci_dev *pdev) - } - - #ifdef CONFIG_PM_SLEEP -+static int nvme_get_power_state(struct nvme_ctrl *ctrl, u32 *ps) -+{ -+ return nvme_get_features(ctrl, NVME_FEAT_POWER_MGMT, 0, NULL, 0, ps); -+} -+ -+static int nvme_set_power_state(struct nvme_ctrl *ctrl, u32 ps) -+{ -+ return nvme_set_features(ctrl, NVME_FEAT_POWER_MGMT, ps, NULL, 0, NULL); -+} -+ -+static int nvme_resume(struct device *dev) -+{ -+ struct nvme_dev *ndev = pci_get_drvdata(to_pci_dev(dev)); -+ struct nvme_ctrl *ctrl = &ndev->ctrl; -+ -+ if (pm_resume_via_firmware() || !ctrl->npss || -+ nvme_set_power_state(ctrl, ndev->last_ps) != 0) -+ nvme_reset_ctrl(ctrl); -+ return 0; -+} -+ - static int nvme_suspend(struct device *dev) - { - struct pci_dev *pdev = to_pci_dev(dev); - struct nvme_dev *ndev = pci_get_drvdata(pdev); -+ struct nvme_ctrl *ctrl = &ndev->ctrl; -+ int ret = -EBUSY; -+ -+ /* -+ * The platform does not remove power for a kernel managed suspend so -+ * use host managed nvme power settings for lowest idle power if -+ * possible. This should have quicker resume latency than a full device -+ * shutdown. But if the firmware is involved after the suspend or the -+ * device does not support any non-default power states, shut down the -+ * device fully. -+ */ -+ if (pm_suspend_via_firmware() || !ctrl->npss) { -+ nvme_dev_disable(ndev, true); -+ return 0; -+ } -+ -+ nvme_start_freeze(ctrl); -+ nvme_wait_freeze(ctrl); -+ nvme_sync_queues(ctrl); -+ -+ if (ctrl->state != NVME_CTRL_LIVE && -+ ctrl->state != NVME_CTRL_ADMIN_ONLY) -+ goto unfreeze; -+ -+ ndev->last_ps = 0; -+ ret = nvme_get_power_state(ctrl, &ndev->last_ps); -+ if (ret < 0) -+ goto unfreeze; -+ -+ ret = nvme_set_power_state(ctrl, ctrl->npss); -+ if (ret < 0) -+ goto unfreeze; -+ -+ if (ret) { -+ /* -+ * Clearing npss forces a controller reset on resume. The -+ * correct value will be resdicovered then. -+ */ -+ nvme_dev_disable(ndev, true); -+ ctrl->npss = 0; -+ ret = 0; -+ goto unfreeze; -+ } -+ /* -+ * A saved state prevents pci pm from generically controlling the -+ * device's power. If we're using protocol specific settings, we don't -+ * want pci interfering. -+ */ -+ pci_save_state(pdev); -+unfreeze: -+ nvme_unfreeze(ctrl); -+ return ret; -+} -+ -+static int nvme_simple_suspend(struct device *dev) -+{ -+ struct nvme_dev *ndev = pci_get_drvdata(to_pci_dev(dev)); - - nvme_dev_disable(ndev, true); - return 0; - } - --static int nvme_resume(struct device *dev) -+static int nvme_simple_resume(struct device *dev) - { - struct pci_dev *pdev = to_pci_dev(dev); - struct nvme_dev *ndev = pci_get_drvdata(pdev); -@@ -2866,9 +2946,16 @@ static int nvme_resume(struct device *dev) - nvme_reset_ctrl(&ndev->ctrl); - return 0; - } --#endif - --static SIMPLE_DEV_PM_OPS(nvme_dev_pm_ops, nvme_suspend, nvme_resume); -+const struct dev_pm_ops nvme_dev_pm_ops = { -+ .suspend = nvme_suspend, -+ .resume = nvme_resume, -+ .freeze = nvme_simple_suspend, -+ .thaw = nvme_simple_resume, -+ .poweroff = nvme_simple_suspend, -+ .restore = nvme_simple_resume, -+}; -+#endif /* CONFIG_PM_SLEEP */ - - static pci_ers_result_t nvme_error_detected(struct pci_dev *pdev, - pci_channel_state_t state) -@@ -2975,9 +3062,11 @@ static struct pci_driver nvme_driver = { - .probe = nvme_probe, - .remove = nvme_remove, - .shutdown = nvme_shutdown, -+#ifdef CONFIG_PM_SLEEP - .driver = { - .pm = &nvme_dev_pm_ops, - }, -+#endif - .sriov_configure = pci_sriov_configure_simple, - .err_handler = &nvme_err_handler, - }; -diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c -index 096211299c07..0cb0fe170977 100644 ---- a/kernel/power/suspend.c -+++ b/kernel/power/suspend.c -@@ -533,6 +533,8 @@ int suspend_devices_and_enter(suspend_state_t state) - goto Resume_devices; - } - -+unsigned int resume_delay = 3000; -+ - /** - * suspend_finish - Clean up before finishing the suspend sequence. - * -@@ -541,6 +543,15 @@ int suspend_devices_and_enter(suspend_state_t state) - */ - static void suspend_finish(void) - { -+ if (resume_delay) { -+ /* Give kernel threads a head start, such that usb-storage -+ * can detect devices before syslog attempts to write log -+ * messages from the suspend code. -+ */ -+ thaw_kernel_threads(); -+ pr_debug("PM: Sleeping for %d milliseconds.\n", resume_delay); -+ msleep(resume_delay); -+ } - suspend_thaw_processes(); - pm_notifier_call_chain(PM_POST_SUSPEND); - pm_restore_console(); -diff --git a/kernel/sysctl.c b/kernel/sysctl.c -index 1beca96fb625..4b98db9bbc88 100644 ---- a/kernel/sysctl.c -+++ b/kernel/sysctl.c -@@ -318,7 +318,16 @@ static int min_extfrag_threshold; - static int max_extfrag_threshold = 1000; - #endif - -+extern unsigned int resume_delay; -+ - static struct ctl_table kern_table[] = { -+ { -+ .procname = "resume_delay", -+ .data = &resume_delay, -+ .maxlen = sizeof(unsigned int), -+ .mode = 0644, -+ .proc_handler = proc_dointvec, -+ }, - { - .procname = "sched_child_runs_first", - .data = &sysctl_sched_child_runs_first, --- -2.23.0 - diff --git a/patches/5.2/0003-buttons.patch b/patches/5.2/0003-buttons.patch deleted file mode 100644 index 19824ec4a..000000000 --- a/patches/5.2/0003-buttons.patch +++ /dev/null @@ -1,274 +0,0 @@ -From 2a57dacdffa4b231321c0dfd4490a0c7f5b70153 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 26 Jul 2019 04:45:10 +0200 -Subject: [PATCH 03/12] buttons - ---- - drivers/input/misc/Kconfig | 6 +- - drivers/input/misc/soc_button_array.c | 112 +++++++++++++++++++--- - drivers/platform/x86/surfacepro3_button.c | 47 +++++++++ - 3 files changed, 150 insertions(+), 15 deletions(-) - -diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig -index d07c1eb15aa6..7d9ae394e597 100644 ---- a/drivers/input/misc/Kconfig -+++ b/drivers/input/misc/Kconfig -@@ -813,10 +813,10 @@ config INPUT_IDEAPAD_SLIDEBAR - - config INPUT_SOC_BUTTON_ARRAY - tristate "Windows-compatible SoC Button Array" -- depends on KEYBOARD_GPIO -+ depends on KEYBOARD_GPIO && ACPI - help -- Say Y here if you have a SoC-based tablet that originally -- runs Windows 8. -+ Say Y here if you have a SoC-based tablet that originally runs -+ Windows 8 or a Microsoft Surface Book 2, Pro 5, Laptop 1 or later. - - To compile this driver as a module, choose M here: the - module will be called soc_button_array. -diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c -index 5e59f8e57f8e..ef89698c7d43 100644 ---- a/drivers/input/misc/soc_button_array.c -+++ b/drivers/input/misc/soc_button_array.c -@@ -25,6 +25,11 @@ struct soc_button_info { - bool wakeup; - }; - -+struct soc_device_data { -+ const struct soc_button_info *button_info; -+ int (*check)(struct device *dev); -+}; -+ - /* - * Some of the buttons like volume up/down are auto repeat, while others - * are not. To support both, we register two platform devices, and put -@@ -87,8 +92,20 @@ soc_button_device_create(struct platform_device *pdev, - continue; - - gpio = soc_button_lookup_gpio(&pdev->dev, info->acpi_index); -- if (!gpio_is_valid(gpio)) -+ if (!gpio_is_valid(gpio)) { -+ /* -+ * Skip GPIO if not present. Note we deliberately -+ * ignore -EPROBE_DEFER errors here. On some devices -+ * Intel is using so called virtual GPIOs which are not -+ * GPIOs at all but some way for AML code to check some -+ * random status bits without need a custom opregion. -+ * In some cases the resources table we parse points to -+ * such a virtual GPIO, since these are not real GPIOs -+ * we do not have a driver for these so they will never -+ * show up, therefor we ignore -EPROBE_DEFER. -+ */ - continue; -+ } - - gpio_keys[n_buttons].type = info->event_type; - gpio_keys[n_buttons].code = info->event_code; -@@ -309,23 +326,26 @@ static int soc_button_remove(struct platform_device *pdev) - static int soc_button_probe(struct platform_device *pdev) - { - struct device *dev = &pdev->dev; -- const struct acpi_device_id *id; -- struct soc_button_info *button_info; -+ const struct soc_device_data *device_data; -+ const struct soc_button_info *button_info; - struct soc_button_data *priv; - struct platform_device *pd; - int i; - int error; - -- id = acpi_match_device(dev->driver->acpi_match_table, dev); -- if (!id) -- return -ENODEV; -+ device_data = acpi_device_get_match_data(dev); -+ if (device_data && device_data->check) { -+ error = device_data->check(dev); -+ if (error) -+ return error; -+ } - -- if (!id->driver_data) { -+ if (device_data && device_data->button_info) { -+ button_info = device_data->button_info; -+ } else { - button_info = soc_button_get_button_info(dev); - if (IS_ERR(button_info)) - return PTR_ERR(button_info); -- } else { -- button_info = (struct soc_button_info *)id->driver_data; - } - - error = gpiod_count(dev, NULL); -@@ -357,7 +377,7 @@ static int soc_button_probe(struct platform_device *pdev) - if (!priv->children[0] && !priv->children[1]) - return -ENODEV; - -- if (!id->driver_data) -+ if (!device_data || !device_data->button_info) - devm_kfree(dev, button_info); - - return 0; -@@ -368,7 +388,7 @@ static int soc_button_probe(struct platform_device *pdev) - * is defined in section 2.8.7.2 of "Windows ACPI Design Guide for SoC - * Platforms" - */ --static struct soc_button_info soc_button_PNP0C40[] = { -+static const struct soc_button_info soc_button_PNP0C40[] = { - { "power", 0, EV_KEY, KEY_POWER, false, true }, - { "home", 1, EV_KEY, KEY_LEFTMETA, false, true }, - { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false }, -@@ -377,9 +397,77 @@ static struct soc_button_info soc_button_PNP0C40[] = { - { } - }; - -+static const struct soc_device_data soc_device_PNP0C40 = { -+ .button_info = soc_button_PNP0C40, -+}; -+ -+/* -+ * Special device check for Surface Book 2 and Surface Pro (2017). -+ * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned -+ * devices use MSHW0040 for power and volume buttons, however the way they -+ * have to be addressed differs. Make sure that we only load this drivers -+ * for the correct devices by checking the OEM Platform Revision provided by -+ * the _DSM method. -+ */ -+#define MSHW0040_DSM_REVISION 0x01 -+#define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision -+static const guid_t MSHW0040_DSM_UUID = -+ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65, -+ 0x49, 0x80, 0x35); -+ -+static int soc_device_check_MSHW0040(struct device *dev) -+{ -+ acpi_handle handle = ACPI_HANDLE(dev); -+ union acpi_object *result; -+ u64 oem_platform_rev = 0; // valid revisions are nonzero -+ -+ // get OEM platform revision -+ result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ MSHW0040_DSM_GET_OMPR, NULL, -+ ACPI_TYPE_INTEGER); -+ -+ if (result) { -+ oem_platform_rev = result->integer.value; -+ ACPI_FREE(result); -+ } -+ -+ /* -+ * If the revision is zero here, the _DSM evaluation has failed. This -+ * indicates that we have a Pro 4 or Book 1 and this driver should not -+ * be used. -+ */ -+ if (oem_platform_rev == 0) -+ return -ENODEV; -+ -+ dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); -+ -+ return 0; -+} -+ -+/* -+ * Button infos for Microsoft Surface Book 2 and Surface Pro (2017). -+ * Obtained from DSDT/testing. -+ */ -+static const struct soc_button_info soc_button_MSHW0040[] = { -+ { "power", 0, EV_KEY, KEY_POWER, false, true }, -+ { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false }, -+ { "volume_down", 4, EV_KEY, KEY_VOLUMEDOWN, true, false }, -+ { } -+}; -+ -+static const struct soc_device_data soc_device_MSHW0040 = { -+ .button_info = soc_button_MSHW0040, -+ .check = soc_device_check_MSHW0040, -+}; -+ - static const struct acpi_device_id soc_button_acpi_match[] = { -- { "PNP0C40", (unsigned long)soc_button_PNP0C40 }, -+ { "PNP0C40", (unsigned long)&soc_device_PNP0C40 }, - { "ACPI0011", 0 }, -+ -+ /* Microsoft Surface Devices (5th and 6th generation) */ -+ { "MSHW0040", (unsigned long)&soc_device_MSHW0040 }, -+ - { } - }; - -diff --git a/drivers/platform/x86/surfacepro3_button.c b/drivers/platform/x86/surfacepro3_button.c -index 47c6d000465a..ec515223f654 100644 ---- a/drivers/platform/x86/surfacepro3_button.c -+++ b/drivers/platform/x86/surfacepro3_button.c -@@ -20,6 +20,12 @@ - #define SURFACE_BUTTON_OBJ_NAME "VGBI" - #define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons" - -+#define MSHW0040_DSM_REVISION 0x01 -+#define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision -+static const guid_t MSHW0040_DSM_UUID = -+ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65, -+ 0x49, 0x80, 0x35); -+ - #define SURFACE_BUTTON_NOTIFY_TABLET_MODE 0xc8 - - #define SURFACE_BUTTON_NOTIFY_PRESS_POWER 0xc6 -@@ -142,6 +148,44 @@ static int surface_button_resume(struct device *dev) - } - #endif - -+/* -+ * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device -+ * ID (MSHW0040) for the power/volume buttons. Make sure this is the right -+ * device by checking for the _DSM method and OEM Platform Revision. -+ * -+ * Returns true if the driver should bind to this device, i.e. the device is -+ * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. -+ */ -+static bool surface_button_check_MSHW0040(struct acpi_device *dev) -+{ -+ acpi_handle handle = dev->handle; -+ union acpi_object *result; -+ u64 oem_platform_rev = 0; // valid revisions are nonzero -+ -+ // get OEM platform revision -+ result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ MSHW0040_DSM_GET_OMPR, -+ NULL, ACPI_TYPE_INTEGER); -+ -+ /* -+ * If evaluating the _DSM fails, the method is not present. This means -+ * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we -+ * should use this driver. We use revision 0 indicating it is -+ * unavailable. -+ */ -+ -+ if (result) { -+ oem_platform_rev = result->integer.value; -+ ACPI_FREE(result); -+ } -+ -+ dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); -+ -+ return oem_platform_rev == 0; -+} -+ -+ - static int surface_button_add(struct acpi_device *device) - { - struct surface_button *button; -@@ -154,6 +198,9 @@ static int surface_button_add(struct acpi_device *device) - strlen(SURFACE_BUTTON_OBJ_NAME))) - return -ENODEV; - -+ if (!surface_button_check_MSHW0040(device)) -+ return -ENODEV; -+ - button = kzalloc(sizeof(struct surface_button), GFP_KERNEL); - if (!button) - return -ENOMEM; --- -2.23.0 - diff --git a/patches/5.2/0004-cameras.patch b/patches/5.2/0004-cameras.patch deleted file mode 100644 index d57d5041f..000000000 --- a/patches/5.2/0004-cameras.patch +++ /dev/null @@ -1,2753 +0,0 @@ -From c4462fc162588ba31e4afc9c7865a2ee2d62afd5 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 26 Jul 2019 04:45:19 +0200 -Subject: [PATCH 04/12] cameras - ---- - drivers/media/usb/uvc/uvc_driver.c | 40 + - drivers/staging/Makefile | 1 + - drivers/staging/ov5693/Kconfig | 10 + - drivers/staging/ov5693/Makefile | 5 + - drivers/staging/ov5693/ad5823.c | 218 +++++ - drivers/staging/ov5693/ad5823.h | 90 ++ - drivers/staging/ov5693/ov5693.c | 1461 ++++++++++++++++++++++++++++ - drivers/staging/ov5693/ov5693.h | 848 ++++++++++++++++ - 8 files changed, 2673 insertions(+) - create mode 100644 drivers/staging/ov5693/Kconfig - create mode 100644 drivers/staging/ov5693/Makefile - create mode 100644 drivers/staging/ov5693/ad5823.c - create mode 100644 drivers/staging/ov5693/ad5823.h - create mode 100644 drivers/staging/ov5693/ov5693.c - create mode 100644 drivers/staging/ov5693/ov5693.h - -diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c -index 66ee168ddc7e..e4bccfab9da1 100644 ---- a/drivers/media/usb/uvc/uvc_driver.c -+++ b/drivers/media/usb/uvc/uvc_driver.c -@@ -2394,6 +2394,46 @@ static const struct uvc_device_info uvc_quirk_force_y8 = { - * though they are compliant. - */ - static const struct usb_device_id uvc_ids[] = { -+ /* Microsoft Surface Pro 3 Front */ -+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE -+ | USB_DEVICE_ID_MATCH_INT_INFO, -+ .idVendor = 0x045e, -+ .idProduct = 0x07be, -+ .bInterfaceClass = USB_CLASS_VIDEO, -+ .bInterfaceSubClass = 1, -+ .bInterfaceProtocol = 1 }, -+ /* Microsoft Surface Pro 3 Rear */ -+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE -+ | USB_DEVICE_ID_MATCH_INT_INFO, -+ .idVendor = 0x045e, -+ .idProduct = 0x07bf, -+ .bInterfaceClass = USB_CLASS_VIDEO, -+ .bInterfaceSubClass = 1, -+ .bInterfaceProtocol = 1 }, -+ /* Microsoft Surface Pro 4 Cam */ -+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE -+ | USB_DEVICE_ID_MATCH_INT_INFO, -+ .idVendor = 0x045e, -+ .idProduct = 0x090c, -+ .bInterfaceClass = USB_CLASS_VIDEO, -+ .bInterfaceSubClass = 1, -+ .bInterfaceProtocol = 1 }, -+ /* Microsoft Surface Book Cam 1 */ -+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE -+ | USB_DEVICE_ID_MATCH_INT_INFO, -+ .idVendor = 0x045e, -+ .idProduct = 0x090b, -+ .bInterfaceClass = USB_CLASS_VIDEO, -+ .bInterfaceSubClass = 1, -+ .bInterfaceProtocol = 1 }, -+ /* Microsoft Surface Book Cam 2 */ -+ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE -+ | USB_DEVICE_ID_MATCH_INT_INFO, -+ .idVendor = 0x045e, -+ .idProduct = 0x091a, -+ .bInterfaceClass = USB_CLASS_VIDEO, -+ .bInterfaceSubClass = 1, -+ .bInterfaceProtocol = 1 }, - /* LogiLink Wireless Webcam */ - { .match_flags = USB_DEVICE_ID_MATCH_DEVICE - | USB_DEVICE_ID_MATCH_INT_INFO, -diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile -index 0da0d3f0b5e4..1fffb6f5a3be 100644 ---- a/drivers/staging/Makefile -+++ b/drivers/staging/Makefile -@@ -49,3 +49,4 @@ obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/ - obj-$(CONFIG_EROFS_FS) += erofs/ - obj-$(CONFIG_FIELDBUS_DEV) += fieldbus/ - obj-$(CONFIG_KPC2000) += kpc2000/ -+obj-$(CONFIG_VIDEO_OV5693) += ov5693/ -diff --git a/drivers/staging/ov5693/Kconfig b/drivers/staging/ov5693/Kconfig -new file mode 100644 -index 000000000000..96000f112c4d ---- /dev/null -+++ b/drivers/staging/ov5693/Kconfig -@@ -0,0 +1,10 @@ -+config VIDEO_OV5693 -+ tristate "Omnivision ov5693 sensor support" -+ depends on I2C && VIDEO_V4L2 -+ ---help--- -+ This is a Video4Linux2 sensor-level driver for the Micron -+ ov5693 5 Mpixel camera. -+ -+ ov5693 is video camera sensor. -+ -+ It currently only works with the atomisp driver. -diff --git a/drivers/staging/ov5693/Makefile b/drivers/staging/ov5693/Makefile -new file mode 100644 -index 000000000000..d8a63faa591f ---- /dev/null -+++ b/drivers/staging/ov5693/Makefile -@@ -0,0 +1,5 @@ -+obj-$(CONFIG_VIDEO_OV5693) += ov569x.o -+ -+ov569x-objs := ov5693.o ad5823.o -+ -+ccflags-y += -Werror -diff --git a/drivers/staging/ov5693/ad5823.c b/drivers/staging/ov5693/ad5823.c -new file mode 100644 -index 000000000000..7c34c36e77e5 ---- /dev/null -+++ b/drivers/staging/ov5693/ad5823.c -@@ -0,0 +1,218 @@ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "ad5823.h" -+ -+static struct ad5823_device ad5823_dev; -+static int ad5823_i2c_write(struct i2c_client *client, u8 reg, u8 val) -+{ -+ struct i2c_msg msg; -+ u8 buf[2]; -+ buf[0] = reg; -+ buf[1] = val; -+ msg.addr = AD5823_VCM_ADDR; -+ msg.flags = 0; -+ msg.len = AD5823_8BIT; -+ msg.buf = &buf[0]; -+ -+ if (i2c_transfer(client->adapter, &msg, 1) != 1) -+ return -EIO; -+ return 0; -+} -+ -+static int ad5823_i2c_read(struct i2c_client *client, u8 reg, u8 *val) -+{ -+ struct i2c_msg msg[2]; -+ u8 buf[2]; -+ buf[0] = reg; -+ buf[1] = 0; -+ -+ msg[0].addr = AD5823_VCM_ADDR; -+ msg[0].flags = 0; -+ msg[0].len = AD5823_8BIT; -+ msg[0].buf = &buf[0]; -+ -+ msg[1].addr = AD5823_VCM_ADDR; -+ msg[1].flags = I2C_M_RD; -+ msg[1].len = AD5823_8BIT; -+ msg[1].buf = &buf[1]; -+ *val = 0; -+ if (i2c_transfer(client->adapter, msg, 2) != 2) -+ return -EIO; -+ *val = buf[1]; -+ return 0; -+} -+ -+int ad5823_vcm_power_up(struct v4l2_subdev *sd) -+{ -+ int ret = -ENODEV; -+ -+ /* Enable power */ -+ if (ad5823_dev.platform_data) -+ ret = ad5823_dev.platform_data->power_ctrl(sd, 1); -+ /* -+ * waiting time requested by AD5823(vcm) -+ */ -+ usleep_range(1000, 2000); -+ return ret; -+} -+ -+int ad5823_vcm_power_down(struct v4l2_subdev *sd) -+{ -+ int ret = -ENODEV; -+ -+ if (ad5823_dev.platform_data) -+ ret = ad5823_dev.platform_data->power_ctrl(sd, 0); -+ -+ return ret; -+} -+ -+ -+int ad5823_t_focus_vcm(struct v4l2_subdev *sd, u16 val) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret = -EINVAL; -+ u8 vcm_code; -+ u8 vcm_mode_reg_val[4] = { -+ AD5823_ARC_RES0, -+ AD5823_ARC_RES1, -+ AD5823_ARC_RES2, -+ AD5823_ESRC -+ }; -+ -+ if (ad5823_dev.vcm_mode != AD5823_DIRECT) { -+ ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_MSB, -+ AD5823_RING_CTRL_ENABLE); -+ if (ret) -+ return ret; -+ -+ ret = ad5823_i2c_write(client, AD5823_REG_MODE, -+ vcm_mode_reg_val[ad5823_dev.vcm_mode]); -+ if (ret) -+ return ret; -+ } else { -+ ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_MSB, -+ AD5823_RING_CTRL_DISABLE); -+ if (ret) -+ return ret; -+ } -+ -+ ret = ad5823_i2c_read(client, AD5823_REG_VCM_CODE_MSB, &vcm_code); -+ if (ret) -+ return ret; -+ -+ /* set reg VCM_CODE_MSB Bit[1:0] */ -+ vcm_code = (vcm_code & VCM_CODE_MSB_MASK) | ((val >> 8) & ~VCM_CODE_MSB_MASK); -+ ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_MSB, vcm_code); -+ if (ret) -+ return ret; -+ -+ /* set reg VCM_CODE_LSB Bit[7:0] */ -+ ret = ad5823_i2c_write(client, AD5823_REG_VCM_CODE_LSB, -+ (val & 0x0f)); -+ if (ret) -+ return ret; -+ -+ /* set required vcm move time */ -+ vcm_code = AD5823_RESONANCE_PERIOD / AD5823_RESONANCE_COEF -+ - AD5823_HIGH_FREQ_RANGE; -+ ret = ad5823_i2c_write(client, AD5823_REG_VCM_MOVE_TIME, vcm_code); -+ -+ return ret; -+} -+ -+int ad5823_t_focus_abs(struct v4l2_subdev *sd, s32 value) -+{ -+ int ret; -+ -+ value = min(value, AD5823_MAX_FOCUS_POS); -+ ret = ad5823_t_focus_vcm(sd, AD5823_MAX_FOCUS_POS - value); -+ if (ret == 0) { -+ ad5823_dev.number_of_steps = value - ad5823_dev.focus; -+ ad5823_dev.focus = value; -+ ktime_get_ts(&ad5823_dev.timestamp_t_focus_abs); -+ } -+ -+ return ret; -+} -+ -+int ad5823_t_focus_rel(struct v4l2_subdev *sd, s32 value) -+{ -+ return ad5823_t_focus_abs(sd, ad5823_dev.focus + value); -+} -+ -+int ad5823_q_focus_status(struct v4l2_subdev *sd, s32 *value) -+{ -+ u32 status = 0; -+ struct timespec temptime; -+ const struct timespec timedelay = { -+ 0, -+ min_t(u32, abs(ad5823_dev.number_of_steps)*DELAY_PER_STEP_NS, -+ DELAY_MAX_PER_STEP_NS), -+ }; -+ -+ ktime_get_ts(&temptime); -+ -+ temptime = timespec_sub(temptime, (ad5823_dev.timestamp_t_focus_abs)); -+ -+ if (timespec_compare(&temptime, &timedelay) <= 0) -+ status = ATOMISP_FOCUS_STATUS_MOVING -+ | ATOMISP_FOCUS_HP_IN_PROGRESS; -+ else -+ status = ATOMISP_FOCUS_STATUS_ACCEPTS_NEW_MOVE -+ | ATOMISP_FOCUS_HP_COMPLETE; -+ -+ *value = status; -+ -+ return 0; -+} -+ -+int ad5823_q_focus_abs(struct v4l2_subdev *sd, s32 *value) -+{ -+ s32 val; -+ -+ ad5823_q_focus_status(sd, &val); -+ -+ if (val & ATOMISP_FOCUS_STATUS_MOVING) -+ *value = ad5823_dev.focus - ad5823_dev.number_of_steps; -+ else -+ *value = ad5823_dev.focus ; -+ -+ return 0; -+} -+ -+int ad5823_t_vcm_slew(struct v4l2_subdev *sd, s32 value) -+{ -+ return 0; -+} -+ -+int ad5823_t_vcm_timing(struct v4l2_subdev *sd, s32 value) -+{ -+ return 0; -+} -+ -+int ad5823_vcm_init(struct v4l2_subdev *sd) -+{ -+ /* set vcm mode to ARC RES0.5 */ -+ ad5823_dev.vcm_mode = AD5823_ARC_RES1; -+ ad5823_dev.platform_data = camera_get_af_platform_data(); -+ return ad5823_dev.platform_data ? 0 : -ENODEV; -+} -diff --git a/drivers/staging/ov5693/ad5823.h b/drivers/staging/ov5693/ad5823.h -new file mode 100644 -index 000000000000..8b046c31f3af ---- /dev/null -+++ b/drivers/staging/ov5693/ad5823.h -@@ -0,0 +1,90 @@ -+/* -+ * Support for AD5823 VCM. -+ * -+ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. -+ * -+ * This program is free software; you can redistribute it and/or -+ * modify it under the terms of the GNU General Public License version -+ * 2 as published by the Free Software Foundation. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program; if not, write to the Free Software -+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -+ * 02110-1301, USA. -+ * -+ */ -+ -+#ifndef __AD5823_H__ -+#define __AD5823_H__ -+ -+#include -+#include -+ -+ -+#define AD5823_VCM_ADDR 0x0c -+ -+#define AD5823_REG_RESET 0x01 -+#define AD5823_REG_MODE 0x02 -+#define AD5823_REG_VCM_MOVE_TIME 0x03 -+#define AD5823_REG_VCM_CODE_MSB 0x04 -+#define AD5823_REG_VCM_CODE_LSB 0x05 -+#define AD5823_REG_VCM_THRESHOLD_MSB 0x06 -+#define AD5823_REG_VCM_THRESHOLD_LSB 0x07 -+ -+#define AD5823_RING_CTRL_ENABLE 0x04 -+#define AD5823_RING_CTRL_DISABLE 0x00 -+ -+#define AD5823_RESONANCE_PERIOD 100000 -+#define AD5823_RESONANCE_COEF 512 -+#define AD5823_HIGH_FREQ_RANGE 0x80 -+ -+#define VCM_CODE_MSB_MASK 0xfc -+ -+enum ad5823_tok_type { -+ AD5823_8BIT = 0x0001, -+ AD5823_16BIT = 0x0002, -+}; -+ -+enum ad5823_vcm_mode { -+ AD5823_ARC_RES0 = 0x0, /* Actuator response control RES1 */ -+ AD5823_ARC_RES1 = 0x1, /* Actuator response control RES0.5 */ -+ AD5823_ARC_RES2 = 0x2, /* Actuator response control RES2 */ -+ AD5823_ESRC = 0x3, /* Enhanced slew rate control */ -+ AD5823_DIRECT = 0x4, /* Direct control */ -+}; -+ -+/* ad5823 device structure */ -+struct ad5823_device { -+ struct timespec timestamp_t_focus_abs; -+ enum ad5823_vcm_mode vcm_mode; -+ s16 number_of_steps; -+ bool initialized; /* true if ad5823 is detected */ -+ s32 focus; /* Current focus value */ -+ struct timespec focus_time; /* Time when focus was last time set */ -+ __u8 buffer[4]; /* Used for i2c transactions */ -+ const struct camera_af_platform_data *platform_data; -+}; -+ -+#define AD5823_INVALID_CONFIG 0xffffffff -+#define AD5823_MAX_FOCUS_POS 1023 -+ -+ -+#define DELAY_PER_STEP_NS 1000000 -+#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) -+ -+int ad5823_vcm_power_up(struct v4l2_subdev *sd); -+int ad5823_vcm_power_down(struct v4l2_subdev *sd); -+int ad5823_vcm_init(struct v4l2_subdev *sd); -+ -+int ad5823_t_focus_vcm(struct v4l2_subdev *sd, u16 val); -+int ad5823_t_focus_abs(struct v4l2_subdev *sd, s32 value); -+int ad5823_t_focus_rel(struct v4l2_subdev *sd, s32 value); -+int ad5823_q_focus_status(struct v4l2_subdev *sd, s32 *value); -+int ad5823_q_focus_abs(struct v4l2_subdev *sd, s32 *value); -+ -+#endif -diff --git a/drivers/staging/ov5693/ov5693.c b/drivers/staging/ov5693/ov5693.c -new file mode 100644 -index 000000000000..51d218da3722 ---- /dev/null -+++ b/drivers/staging/ov5693/ov5693.c -@@ -0,0 +1,1461 @@ -+/* -+ * Support for OmniVision OV5693 5M HD camera sensor. -+ * -+ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. -+ * -+ * This program is free software; you can redistribute it and/or -+ * modify it under the terms of the GNU General Public License version -+ * 2 as published by the Free Software Foundation. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program; if not, write to the Free Software -+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -+ * 02110-1301, USA. -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "ov5693.h" -+ -+/* i2c read/write stuff */ -+static int ov5693_read_reg(struct i2c_client *client, -+ u16 data_length, u16 reg, u16 *val) -+{ -+ int err; -+ struct i2c_msg msg[2]; -+ unsigned char data[6]; -+ -+ if (!client->adapter) { -+ dev_err(&client->dev, "%s error, no client->adapter\n", -+ __func__); -+ return -ENODEV; -+ } -+ -+ if (data_length != OV5693_8BIT && data_length != OV5693_16BIT -+ && data_length != OV5693_32BIT) { -+ dev_err(&client->dev, "%s error, invalid data length\n", -+ __func__); -+ return -EINVAL; -+ } -+ -+ memset(msg, 0 , sizeof(msg)); -+ -+ msg[0].addr = client->addr; -+ msg[0].flags = 0; -+ msg[0].len = I2C_MSG_LENGTH; -+ msg[0].buf = data; -+ -+ /* high byte goes out first */ -+ data[0] = (u8)(reg >> 8); -+ data[1] = (u8)(reg & 0xff); -+ -+ msg[1].addr = client->addr; -+ msg[1].len = data_length; -+ msg[1].flags = I2C_M_RD; -+ msg[1].buf = data; -+ -+ err = i2c_transfer(client->adapter, msg, 2); -+ if (err != 2) { -+ if (err >= 0) -+ err = -EIO; -+ dev_err(&client->dev, -+ "read from offset 0x%x error %d", reg, err); -+ return err; -+ } -+ -+ *val = 0; -+ /* high byte comes first */ -+ if (data_length == OV5693_8BIT) -+ *val = (u8)data[0]; -+ else if (data_length == OV5693_16BIT) -+ *val = be16_to_cpu(*(u16 *)&data[0]); -+ else -+ *val = be32_to_cpu(*(u32 *)&data[0]); -+ -+ return 0; -+} -+ -+static int ov5693_i2c_write(struct i2c_client *client, u16 len, u8 *data) -+{ -+ struct i2c_msg msg; -+ const int num_msg = 1; -+ int ret; -+ -+ msg.addr = client->addr; -+ msg.flags = 0; -+ msg.len = len; -+ msg.buf = data; -+ ret = i2c_transfer(client->adapter, &msg, 1); -+ -+ return ret == num_msg ? 0 : -EIO; -+} -+ -+static int ov5693_write_reg(struct i2c_client *client, u16 data_length, -+ u16 reg, u16 val) -+{ -+ int ret; -+ unsigned char data[4] = {0}; -+ u16 *wreg = (u16 *)data; -+ const u16 len = data_length + sizeof(u16); /* 16-bit address + data */ -+ -+ if (data_length != OV5693_8BIT && data_length != OV5693_16BIT) { -+ dev_err(&client->dev, -+ "%s error, invalid data_length\n", __func__); -+ return -EINVAL; -+ } -+ -+ /* high byte goes out first */ -+ *wreg = cpu_to_be16(reg); -+ -+ if (data_length == OV5693_8BIT) { -+ data[2] = (u8)(val); -+ } else { -+ /* OV5693_16BIT */ -+ u16 *wdata = (u16 *)&data[2]; -+ *wdata = cpu_to_be16(val); -+ } -+ -+ ret = ov5693_i2c_write(client, len, data); -+ if (ret) -+ dev_err(&client->dev, -+ "write error: wrote 0x%x to offset 0x%x error %d", -+ val, reg, ret); -+ -+ return ret; -+} -+ -+/* -+ * ov5693_write_reg_array - Initializes a list of OV5693 registers -+ * @client: i2c driver client structure -+ * @reglist: list of registers to be written -+ * -+ * This function initializes a list of registers. When consecutive addresses -+ * are found in a row on the list, this function creates a buffer and sends -+ * consecutive data in a single i2c_transfer(). -+ * -+ * __ov5693_flush_reg_array, __ov5693_buf_reg_array() and -+ * __ov5693_write_reg_is_consecutive() are internal functions to -+ * ov5693_write_reg_array_fast() and should be not used anywhere else. -+ * -+ */ -+static int __ov5693_flush_reg_array(struct i2c_client *client, -+ struct ov5693_write_ctrl *ctrl) -+{ -+ u16 size; -+ -+ if (ctrl->index == 0) -+ return 0; -+ -+ size = sizeof(u16) + ctrl->index; /* 16-bit address + data */ -+ ctrl->buffer.addr = cpu_to_be16(ctrl->buffer.addr); -+ ctrl->index = 0; -+ -+ return ov5693_i2c_write(client, size, (u8 *)&ctrl->buffer); -+} -+ -+static int __ov5693_buf_reg_array(struct i2c_client *client, -+ struct ov5693_write_ctrl *ctrl, -+ const struct ov5693_reg *next) -+{ -+ int size; -+ u16 *data16; -+ -+ switch (next->type) { -+ case OV5693_8BIT: -+ size = 1; -+ ctrl->buffer.data[ctrl->index] = (u8)next->val; -+ break; -+ case OV5693_16BIT: -+ size = 2; -+ data16 = (u16 *)&ctrl->buffer.data[ctrl->index]; -+ *data16 = cpu_to_be16((u16)next->val); -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ /* When first item is added, we need to store its starting address */ -+ if (ctrl->index == 0) -+ ctrl->buffer.addr = next->reg; -+ -+ ctrl->index += size; -+ -+ /* -+ * Buffer cannot guarantee free space for u32? Better flush it to avoid -+ * possible lack of memory for next item. -+ */ -+ if (ctrl->index + sizeof(u16) >= OV5693_MAX_WRITE_BUF_SIZE) -+ return __ov5693_flush_reg_array(client, ctrl); -+ -+ return 0; -+} -+ -+static int __ov5693_write_reg_is_consecutive(struct i2c_client *client, -+ struct ov5693_write_ctrl *ctrl, -+ const struct ov5693_reg *next) -+{ -+ if (ctrl->index == 0) -+ return 1; -+ -+ return ctrl->buffer.addr + ctrl->index == next->reg; -+} -+ -+static int ov5693_write_reg_array(struct i2c_client *client, -+ const struct ov5693_reg *reglist) -+{ -+ const struct ov5693_reg *next = reglist; -+ struct ov5693_write_ctrl ctrl; -+ int err; -+ -+ ctrl.index = 0; -+ for (; next->type != OV5693_TOK_TERM; next++) { -+ switch (next->type & OV5693_TOK_MASK) { -+ case OV5693_TOK_DELAY: -+ err = __ov5693_flush_reg_array(client, &ctrl); -+ if (err) -+ return err; -+ usleep_range(next->val * 1000, (next->val + 1) * 1000); -+ break; -+ default: -+ /* -+ * If next address is not consecutive, data needs to be -+ * flushed before proceed. -+ */ -+ if (!__ov5693_write_reg_is_consecutive(client, &ctrl, -+ next)) { -+ err = __ov5693_flush_reg_array(client, &ctrl); -+ if (err) -+ return err; -+ } -+ err = __ov5693_buf_reg_array(client, &ctrl, next); -+ if (err) { -+ dev_err(&client->dev, "%s: write error, aborted\n", -+ __func__); -+ return err; -+ } -+ break; -+ } -+ } -+ -+ return __ov5693_flush_reg_array(client, &ctrl); -+} -+static int ov5693_g_focal(struct v4l2_subdev *sd, s32 *val) -+{ -+ *val = (OV5693_FOCAL_LENGTH_NUM << 16) | OV5693_FOCAL_LENGTH_DEM; -+ return 0; -+} -+ -+static int ov5693_g_fnumber(struct v4l2_subdev *sd, s32 *val) -+{ -+ /*const f number for ov5693*/ -+ *val = (OV5693_F_NUMBER_DEFAULT_NUM << 16) | OV5693_F_NUMBER_DEM; -+ return 0; -+} -+ -+static int ov5693_g_fnumber_range(struct v4l2_subdev *sd, s32 *val) -+{ -+ *val = (OV5693_F_NUMBER_DEFAULT_NUM << 24) | -+ (OV5693_F_NUMBER_DEM << 16) | -+ (OV5693_F_NUMBER_DEFAULT_NUM << 8) | OV5693_F_NUMBER_DEM; -+ return 0; -+} -+ -+ -+static int ov5693_get_intg_factor(struct i2c_client *client, -+ struct camera_mipi_info *info, -+ const struct ov5693_resolution *res) -+{ -+ struct atomisp_sensor_mode_data *buf = &info->data; -+ unsigned int pix_clk_freq_hz; -+ u16 reg_val; -+ int ret; -+ -+ if (info == NULL) -+ return -EINVAL; -+ -+ /* pixel clock calculattion */ -+ pix_clk_freq_hz = res->pix_clk_freq * 1000000; -+ -+ buf->vt_pix_clk_freq_mhz = pix_clk_freq_hz; -+ -+ /* get integration time */ -+ buf->coarse_integration_time_min = OV5693_COARSE_INTG_TIME_MIN; -+ buf->coarse_integration_time_max_margin = -+ OV5693_COARSE_INTG_TIME_MAX_MARGIN; -+ -+ buf->fine_integration_time_min = OV5693_FINE_INTG_TIME_MIN; -+ buf->fine_integration_time_max_margin = -+ OV5693_FINE_INTG_TIME_MAX_MARGIN; -+ -+ buf->fine_integration_time_def = OV5693_FINE_INTG_TIME_MIN; -+ buf->frame_length_lines = res->lines_per_frame; -+ buf->line_length_pck = res->pixels_per_line; -+ buf->read_mode = res->bin_mode; -+ -+ /* get the cropping and output resolution to ISP for this mode. */ -+ ret = ov5693_read_reg(client, OV5693_16BIT, -+ OV5693_H_CROP_START_H, ®_val); -+ if (ret) -+ return ret; -+ buf->crop_horizontal_start = reg_val; -+ -+ ret = ov5693_read_reg(client, OV5693_16BIT, -+ OV5693_V_CROP_START_H, ®_val); -+ if (ret) -+ return ret; -+ buf->crop_vertical_start = reg_val; -+ -+ ret = ov5693_read_reg(client, OV5693_16BIT, -+ OV5693_H_CROP_END_H, ®_val); -+ if (ret) -+ return ret; -+ buf->crop_horizontal_end = reg_val; -+ -+ ret = ov5693_read_reg(client, OV5693_16BIT, -+ OV5693_V_CROP_END_H, ®_val); -+ if (ret) -+ return ret; -+ buf->crop_vertical_end = reg_val; -+ -+ ret = ov5693_read_reg(client, OV5693_16BIT, -+ OV5693_H_OUTSIZE_H, ®_val); -+ if (ret) -+ return ret; -+ buf->output_width = reg_val; -+ -+ ret = ov5693_read_reg(client, OV5693_16BIT, -+ OV5693_V_OUTSIZE_H, ®_val); -+ if (ret) -+ return ret; -+ buf->output_height = reg_val; -+ -+ /* -+ * we can't return 0 for bin_factor, this is because camera -+ * HAL will use them as denominator, bin_factor = 0 will -+ * cause camera HAL crash. So we return bin_factor as this -+ * rules: -+ * [1]. res->bin_factor = 0, return 1 for bin_factor. -+ * [2]. res->bin_factor > 0, return res->bin_factor. -+ */ -+ buf->binning_factor_x = res->bin_factor_x ? -+ res->bin_factor_x : 1; -+ buf->binning_factor_y = res->bin_factor_y ? -+ res->bin_factor_y : 1; -+ return 0; -+} -+ -+static long __ov5693_set_exposure(struct v4l2_subdev *sd, int coarse_itg, -+ int gain, int digitgain) -+ -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ u16 vts; -+ int ret; -+ -+ /* -+ * According to spec, the low 4 bits of exposure/gain reg are -+ * fraction bits, so need to take 4 bits left shift to align -+ * reg integer bits. -+ */ -+ coarse_itg <<= 4; -+ gain <<= 4; -+ -+ ret = ov5693_read_reg(client, OV5693_16BIT, -+ OV5693_VTS_H, &vts); -+ if (ret) -+ return ret; -+ -+ if (coarse_itg + OV5693_INTEGRATION_TIME_MARGIN >= vts) -+ vts = coarse_itg + OV5693_INTEGRATION_TIME_MARGIN; -+ -+ ret = ov5693_write_reg(client, OV5693_16BIT, OV5693_VTS_H, vts); -+ if (ret) -+ return ret; -+ -+ /* group hold start */ -+ ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_GROUP_ACCESS, 0); -+ if (ret) -+ return ret; -+ -+ /* set exposure */ -+ ret = ov5693_write_reg(client, OV5693_8BIT, -+ OV5693_AEC_PK_EXPO_L, -+ coarse_itg & 0xff); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_write_reg(client, OV5693_16BIT, -+ OV5693_AEC_PK_EXPO_H, -+ (coarse_itg >> 8) & 0xfff); -+ if (ret) -+ return ret; -+ -+ /* set analog gain */ -+ ret = ov5693_write_reg(client, OV5693_16BIT, -+ OV5693_AGC_ADJ_H, gain); -+ if (ret) -+ return ret; -+ -+ /* set digital gain */ -+ ret = ov5693_write_reg(client, OV5693_16BIT, -+ OV5693_MWB_GAIN_R_H, digitgain); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_write_reg(client, OV5693_16BIT, -+ OV5693_MWB_GAIN_G_H, digitgain); -+ if (ret) -+ return ret; -+ -+ ret = ov5693_write_reg(client, OV5693_16BIT, -+ OV5693_MWB_GAIN_B_H, digitgain); -+ if (ret) -+ return ret; -+ -+ /* group hold end */ -+ ret = ov5693_write_reg(client, OV5693_8BIT, -+ OV5693_GROUP_ACCESS, 0x10); -+ if (ret) -+ return ret; -+ -+ /* group hold launch */ -+ ret = ov5693_write_reg(client, OV5693_8BIT, -+ OV5693_GROUP_ACCESS, 0xa0); -+ -+ return ret; -+} -+ -+static int ov5693_set_exposure(struct v4l2_subdev *sd, int exposure, -+ int gain, int digitgain) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ int ret; -+ -+ mutex_lock(&dev->input_lock); -+ ret = __ov5693_set_exposure(sd, exposure, gain, digitgain); -+ mutex_unlock(&dev->input_lock); -+ -+ return ret; -+} -+ -+static long ov5693_s_exposure(struct v4l2_subdev *sd, -+ struct atomisp_exposure *exposure) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int exp = exposure->integration_time[0]; -+ int gain = exposure->gain[0]; -+ int digitgain = exposure->gain[1]; -+ -+ /* we should not accept the invalid value below. */ -+ if (gain == 0) { -+ dev_err(&client->dev, "%s: invalid value\n", __func__); -+ return -EINVAL; -+ } -+ -+ return ov5693_set_exposure(sd, exp, gain, digitgain); -+} -+ -+static long ov5693_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) -+{ -+ -+ switch (cmd) { -+ case ATOMISP_IOC_S_EXPOSURE: -+ return ov5693_s_exposure(sd, arg); -+ default: -+ return -EINVAL; -+ } -+ return 0; -+} -+ -+/* This returns the exposure time being used. This should only be used -+ for filling in EXIF data, not for actual image processing. */ -+static int ov5693_q_exposure(struct v4l2_subdev *sd, s32 *value) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ u16 reg_v, reg_v2; -+ int ret; -+ -+ /* get exposure */ -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_AEC_PK_EXPO_L, -+ ®_v); -+ if (ret) -+ goto err; -+ -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_AEC_PK_EXPO_M, -+ ®_v2); -+ if (ret) -+ goto err; -+ -+ reg_v += reg_v2 << 8; -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_AEC_PK_EXPO_H, -+ ®_v2); -+ if (ret) -+ goto err; -+ -+ *value = (reg_v + (((u32)reg_v2 << 16))) >> 4; -+err: -+ return ret; -+} -+ -+/* -+ * This below focus func don't need input_lock mutex_lock -+ * since they are just called in v4l2 s_ctrl/g_ctrl framework -+ * where mutex input_lock have been done. -+ */ -+int ov5693_t_focus_abs(struct v4l2_subdev *sd, s32 value) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ int ret = 0; -+ -+ if (dev->vcm_driver && dev->vcm_driver->t_focus_abs) -+ ret = dev->vcm_driver->t_focus_abs(sd, value); -+ -+ return ret; -+} -+ -+int ov5693_t_focus_rel(struct v4l2_subdev *sd, s32 value) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ int ret = 0; -+ -+ if (dev->vcm_driver && dev->vcm_driver->t_focus_rel) -+ ret = dev->vcm_driver->t_focus_rel(sd, value); -+ -+ return ret; -+} -+ -+int ov5693_q_focus_status(struct v4l2_subdev *sd, s32 *value) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ int ret = 0; -+ -+ if (dev->vcm_driver && dev->vcm_driver->q_focus_status) -+ ret = dev->vcm_driver->q_focus_status(sd, value); -+ -+ return ret; -+} -+ -+int ov5693_q_focus_abs(struct v4l2_subdev *sd, s32 *value) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ int ret = 0; -+ -+ if (dev->vcm_driver && dev->vcm_driver->q_focus_abs) -+ ret = dev->vcm_driver->q_focus_abs(sd, value); -+ -+ return ret; -+} -+ -+/* ov5693 control set/get */ -+static int ov5693_g_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *dev = container_of( -+ ctrl->handler, struct ov5693_device, ctrl_handler); -+ int ret = 0; -+ -+ switch (ctrl->id) { -+ case V4L2_CID_EXPOSURE_ABSOLUTE: -+ ret = ov5693_q_exposure(&dev->sd, &ctrl->val); -+ break; -+ case V4L2_CID_FOCUS_ABSOLUTE: -+ ret = ov5693_q_focus_abs(&dev->sd, &ctrl->val); -+ break; -+ case V4L2_CID_FOCUS_STATUS: -+ ret = ov5693_q_focus_status(&dev->sd, &ctrl->val); -+ break; -+ case V4L2_CID_FOCAL_ABSOLUTE: -+ ret = ov5693_g_focal(&dev->sd, &ctrl->val); -+ break; -+ case V4L2_CID_FNUMBER_ABSOLUTE: -+ ret = ov5693_g_fnumber(&dev->sd, &ctrl->val); -+ break; -+ case V4L2_CID_FNUMBER_RANGE: -+ ret = ov5693_g_fnumber_range(&dev->sd, &ctrl->val); -+ break; -+ case V4L2_CID_BIN_FACTOR_HORZ: -+ ctrl->val = dev->ov5693_res[dev->fmt_idx].bin_factor_x; -+ break; -+ case V4L2_CID_BIN_FACTOR_VERT: -+ ctrl->val = dev->ov5693_res[dev->fmt_idx].bin_factor_y; -+ break; -+ default: -+ ret = -EINVAL; -+ } -+ -+ return ret; -+} -+ -+static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) -+{ -+ struct ov5693_device *dev = container_of( -+ ctrl->handler, struct ov5693_device, ctrl_handler); -+ int ret = 0; -+ -+ if (!ctrl) -+ return -EINVAL; -+ -+ switch (ctrl->id) { -+ case V4L2_CID_RUN_MODE: -+ switch (ctrl->val) { -+ case ATOMISP_RUN_MODE_VIDEO: -+ dev->ov5693_res = ov5693_res_video; -+ dev->curr_res_num = N_RES_VIDEO; -+ break; -+ case ATOMISP_RUN_MODE_STILL_CAPTURE: -+ dev->ov5693_res = ov5693_res_still; -+ dev->curr_res_num = N_RES_STILL; -+ break; -+ default: -+ dev->ov5693_res = ov5693_res_preview; -+ dev->curr_res_num = N_RES_PREVIEW; -+ } -+ break; -+ case V4L2_CID_FOCUS_ABSOLUTE: -+ ret = ov5693_t_focus_abs(&dev->sd, ctrl->val); -+ break; -+ case V4L2_CID_FOCUS_RELATIVE: -+ ret = ov5693_t_focus_rel(&dev->sd, ctrl->val); -+ break; -+ default: -+ ret = -EINVAL; -+ } -+ -+ return ret; -+} -+ -+static int ov5693_init(struct v4l2_subdev *sd) -+{ -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ int ret = 0; -+ -+ /* restore settings */ -+ dev->ov5693_res = ov5693_res_preview; -+ dev->curr_res_num = N_RES_PREVIEW; -+ -+ ret = ov5693_write_reg_array(client, ov5693_init_setting); -+ if (ret) -+ dev_err(&client->dev, "ov5693 write init setting reg err.\n"); -+ -+ return ret; -+} -+ -+ -+static int power_up(struct v4l2_subdev *sd) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret; -+ -+ if (NULL == dev->platform_data) { -+ dev_err(&client->dev, -+ "no camera_sensor_platform_data"); -+ return -ENODEV; -+ } -+ -+ /* power control */ -+ ret = dev->platform_data->power_ctrl(sd, 1); -+ if (ret) -+ goto fail_power; -+ -+ /* gpio ctrl */ -+ ret = dev->platform_data->gpio_ctrl(sd, 1); -+ if (ret) -+ goto fail_power; -+ -+ /* flis clock control */ -+ ret = dev->platform_data->flisclk_ctrl(sd, 1); -+ if (ret) -+ goto fail_clk; -+ -+ /* according to DS, 20ms is needed between PWDN and i2c access */ -+ msleep(20); -+ -+ return 0; -+ -+fail_clk: -+ dev->platform_data->gpio_ctrl(sd, 0); -+fail_power: -+ dev->platform_data->power_ctrl(sd, 0); -+ dev_err(&client->dev, "sensor power-up failed\n"); -+ -+ return ret; -+} -+ -+static int power_down(struct v4l2_subdev *sd) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret = 0; -+ -+ if (NULL == dev->platform_data) { -+ dev_err(&client->dev, -+ "no camera_sensor_platform_data"); -+ return -ENODEV; -+ } -+ -+ ret = dev->platform_data->flisclk_ctrl(sd, 0); -+ if (ret) -+ dev_err(&client->dev, "flisclk failed\n"); -+ -+ /* gpio ctrl */ -+ ret = dev->platform_data->gpio_ctrl(sd, 0); -+ if (ret) -+ dev_err(&client->dev, "gpio failed.\n"); -+ -+ /* power control */ -+ ret = dev->platform_data->power_ctrl(sd, 0); -+ if (ret) -+ dev_err(&client->dev, "vprog failed.\n"); -+ -+ return ret; -+} -+ -+static int ov5693_s_power(struct v4l2_subdev *sd, int on) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret = 0; -+ -+ mutex_lock(&dev->input_lock); -+ if (on == 0) { -+ if (dev->vcm_driver && dev->vcm_driver->power_down) -+ ret = dev->vcm_driver->power_down(sd); -+ if (ret) -+ dev_err(&client->dev, "vcm power-down failed.\n"); -+ -+ ret = power_down(sd); -+ } else { -+ if (dev->vcm_driver && dev->vcm_driver->power_up) -+ ret = dev->vcm_driver->power_up(sd); -+ if (ret) -+ dev_err(&client->dev, "vcm power-up failed.\n"); -+ -+ ret = power_up(sd); -+ if (!ret) -+ ret = ov5693_init(sd); -+ } -+ mutex_unlock(&dev->input_lock); -+ return ret; -+} -+ -+/* -+ * distance - calculate the distance -+ * @res: resolution -+ * @w: width -+ * @h: height -+ * -+ * Get the gap between resolution and w/h. -+ * res->width/height smaller than w/h wouldn't be considered. -+ * Returns the value of gap or -1 if fail. -+ */ -+static int distance(struct ov5693_resolution *res, u32 w, u32 h) -+{ -+ unsigned int w_ratio = ((res->width << RATIO_SHIFT_BITS)/w); -+ unsigned int h_ratio; -+ int match; -+ -+ if (h == 0) -+ return -1; -+ h_ratio = ((res->height << RATIO_SHIFT_BITS) / h); -+ if (h_ratio == 0) -+ return -1; -+ match = abs(((w_ratio << RATIO_SHIFT_BITS) / h_ratio) -+ - ((int)(1 << RATIO_SHIFT_BITS))); -+ -+ if ((w_ratio < (int)(1 << RATIO_SHIFT_BITS)) -+ || (h_ratio < (int)(1 << RATIO_SHIFT_BITS)) || -+ (match > LARGEST_ALLOWED_RATIO_MISMATCH)) -+ return -1; -+ -+ return w_ratio + h_ratio; -+} -+ -+/* Return the nearest higher resolution index */ -+static int nearest_resolution_index(struct v4l2_subdev *sd, -+ int w, int h) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ int i; -+ int idx = dev->curr_res_num-1; -+ int dist; -+ int min_dist = INT_MAX; -+ struct ov5693_resolution *tmp_res = NULL; -+ -+ for (i = 0; i < dev->curr_res_num; i++) { -+ tmp_res = &dev->ov5693_res[i]; -+ dist = distance(tmp_res, w, h); -+ if (dist == -1) -+ continue; -+ if (dist < min_dist) { -+ min_dist = dist; -+ idx = i; -+ } -+ } -+ -+ return idx; -+} -+ -+static int get_resolution_index(struct v4l2_subdev *sd, -+ int w, int h) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ int i; -+ -+ for (i = 0; i < dev->curr_res_num; i++) { -+ if (w != dev->ov5693_res[i].width) -+ continue; -+ if (h != dev->ov5693_res[i].height) -+ continue; -+ -+ return i; -+ } -+ -+ return -1; -+} -+ -+static int __ov5693_try_mbus_fmt(struct v4l2_subdev *sd, -+ struct v4l2_mbus_framefmt *fmt) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ int idx; -+ -+ if (!fmt) -+ return -EINVAL; -+ -+ idx = nearest_resolution_index(sd, fmt->width, fmt->height); -+ fmt->width = dev->ov5693_res[idx].width; -+ fmt->height = dev->ov5693_res[idx].height; -+ fmt->code = V4L2_MBUS_FMT_SGRBG10_1X10; -+ -+ return 0; -+} -+ -+static int ov5693_try_mbus_fmt(struct v4l2_subdev *sd, -+ struct v4l2_mbus_framefmt *fmt) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ int ret; -+ -+ mutex_lock(&dev->input_lock); -+ ret = __ov5693_try_mbus_fmt(sd, fmt); -+ mutex_unlock(&dev->input_lock); -+ -+ return ret; -+} -+ -+static int ov5693_s_mbus_fmt(struct v4l2_subdev *sd, -+ struct v4l2_mbus_framefmt *fmt) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ struct camera_mipi_info *ov5693_info = NULL; -+ int ret = 0; -+ -+ ov5693_info = v4l2_get_subdev_hostdata(sd); -+ if (ov5693_info == NULL) -+ return -EINVAL; -+ -+ mutex_lock(&dev->input_lock); -+ ret = __ov5693_try_mbus_fmt(sd, fmt); -+ if (ret == -1) { -+ dev_err(&client->dev, "try fmt fail\n"); -+ goto done; -+ } -+ -+ dev->fmt_idx = get_resolution_index(sd, fmt->width, fmt->height); -+ if (dev->fmt_idx == -1) { -+ dev_err(&client->dev, "get resolution fail\n"); -+ goto done; -+ } -+ -+ ret = ov5693_write_reg_array(client, dev->ov5693_res[dev->fmt_idx].regs); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 write fmt register err.\n"); -+ goto done; -+ } -+ -+ ret = ov5693_get_intg_factor(client, ov5693_info, -+ &dev->ov5693_res[dev->fmt_idx]); -+ if (ret) -+ dev_err(&client->dev, "failed to get integration_factor\n"); -+ -+done: -+ mutex_unlock(&dev->input_lock); -+ return ret; -+} -+static int ov5693_g_mbus_fmt(struct v4l2_subdev *sd, -+ struct v4l2_mbus_framefmt *fmt) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ -+ mutex_lock(&dev->input_lock); -+ fmt->width = dev->ov5693_res[dev->fmt_idx].width; -+ fmt->height = dev->ov5693_res[dev->fmt_idx].height; -+ fmt->code = V4L2_MBUS_FMT_SBGGR10_1X10; -+ mutex_unlock(&dev->input_lock); -+ -+ return 0; -+} -+ -+static int ov5693_detect(struct i2c_client *client) -+{ -+ struct i2c_adapter *adapter = client->adapter; -+ int ret = 0; -+ u16 id; -+ u8 revision; -+ -+ if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) -+ return -ENODEV; -+ -+ ret = ov5693_read_reg(client, OV5693_16BIT, -+ OV5693_SC_CMMN_CHIP_ID, &id); -+ if (ret) { -+ dev_err(&client->dev, "read sensor_id err.\n"); -+ return -ENODEV; -+ } -+ -+ if (id != OV5693_ID) { -+ dev_err(&client->dev, "sensor ID error\n"); -+ return -ENODEV; -+ } -+ -+ ret = ov5693_read_reg(client, OV5693_8BIT, -+ OV5693_SC_CMMN_SUB_ID, &id); -+ revision = (u8)id & 0x0f; -+ -+ dev_dbg(&client->dev, "sensor_revision = 0x%x\n", revision); -+ dev_dbg(&client->dev, "detect ov5693 success\n"); -+ return ret; -+} -+ -+static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret; -+ -+ mutex_lock(&dev->input_lock); -+ -+ ret = ov5693_write_reg(client, OV5693_8BIT, OV5693_SW_STREAM, -+ enable ? OV5693_START_STREAMING : -+ OV5693_STOP_STREAMING); -+ -+ mutex_unlock(&dev->input_lock); -+ return ret; -+} -+ -+/* ov5693 enum frame size, frame intervals */ -+static int ov5693_enum_framesizes(struct v4l2_subdev *sd, -+ struct v4l2_frmsizeenum *fsize) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ unsigned int index = fsize->index; -+ -+ mutex_lock(&dev->input_lock); -+ -+ if (index >= dev->curr_res_num) { -+ mutex_unlock(&dev->input_lock); -+ return -EINVAL; -+ } -+ -+ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; -+ fsize->discrete.width = dev->ov5693_res[index].width; -+ fsize->discrete.height = dev->ov5693_res[index].height; -+ -+ mutex_unlock(&dev->input_lock); -+ return 0; -+} -+ -+static int ov5693_enum_frameintervals(struct v4l2_subdev *sd, -+ struct v4l2_frmivalenum *fival) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ unsigned int index = fival->index; -+ -+ mutex_lock(&dev->input_lock); -+ -+ if (index >= dev->curr_res_num) { -+ mutex_unlock(&dev->input_lock); -+ return -EINVAL; -+ } -+ -+ fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; -+ fival->width = dev->ov5693_res[index].width; -+ fival->height = dev->ov5693_res[index].height; -+ fival->discrete.numerator = 1; -+ fival->discrete.denominator = dev->ov5693_res[index].fps; -+ -+ mutex_unlock(&dev->input_lock); -+ -+ return 0; -+} -+ -+static int ov5693_enum_mbus_fmt(struct v4l2_subdev *sd, -+ unsigned int index, -+ enum v4l2_mbus_pixelcode *code) -+{ -+ *code = V4L2_MBUS_FMT_SBGGR10_1X10; -+ -+ return 0; -+} -+ -+static int ov5693_s_config(struct v4l2_subdev *sd, -+ int irq, void *platform_data) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ int ret = 0; -+ -+ if (platform_data == NULL) -+ return -ENODEV; -+ -+ mutex_lock(&dev->input_lock); -+ -+ dev->platform_data = platform_data; -+ -+ ret = power_up(sd); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 power-up err.\n"); -+ goto fail_power_on; -+ } -+ -+ ret = dev->platform_data->csi_cfg(sd, 1); -+ if (ret) -+ goto fail_csi_cfg; -+ -+ /* config & detect sensor */ -+ ret = ov5693_detect(client); -+ if (ret) { -+ dev_err(&client->dev, "ov5693_detect err s_config.\n"); -+ goto fail_csi_cfg; -+ } -+ -+ /* turn off sensor, after probed */ -+ ret = power_down(sd); -+ if (ret) { -+ dev_err(&client->dev, "ov5693 power-off err.\n"); -+ goto fail_csi_cfg; -+ } -+ mutex_unlock(&dev->input_lock); -+ -+ return 0; -+ -+fail_csi_cfg: -+ dev->platform_data->csi_cfg(sd, 0); -+fail_power_on: -+ power_down(sd); -+ dev_err(&client->dev, "sensor power-gating failed\n"); -+ mutex_unlock(&dev->input_lock); -+ return ret; -+} -+ -+static int ov5693_g_parm(struct v4l2_subdev *sd, -+ struct v4l2_streamparm *param) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct i2c_client *client = v4l2_get_subdevdata(sd); -+ -+ if (param->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { -+ dev_err(&client->dev, "unsupported buffer type.\n"); -+ return -EINVAL; -+ } -+ -+ memset(param, 0, sizeof(*param)); -+ param->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; -+ -+ mutex_lock(&dev->input_lock); -+ if (dev->fmt_idx >= 0 && dev->fmt_idx < dev->curr_res_num) { -+ param->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; -+ param->parm.capture.timeperframe.numerator = 1; -+ param->parm.capture.capturemode = dev->run_mode->val; -+ param->parm.capture.timeperframe.denominator = -+ dev->ov5693_res[dev->fmt_idx].fps; -+ } -+ mutex_unlock(&dev->input_lock); -+ return 0; -+} -+ -+static int ov5693_g_frame_interval(struct v4l2_subdev *sd, -+ struct v4l2_subdev_frame_interval *interval) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ -+ mutex_lock(&dev->input_lock); -+ interval->interval.numerator = 1; -+ interval->interval.denominator = dev->ov5693_res[dev->fmt_idx].fps; -+ mutex_unlock(&dev->input_lock); -+ -+ return 0; -+} -+ -+static int ov5693_enum_mbus_code(struct v4l2_subdev *sd, -+ struct v4l2_subdev_fh *fh, -+ struct v4l2_subdev_mbus_code_enum *code) -+{ -+ code->code = V4L2_MBUS_FMT_SBGGR10_1X10; -+ return 0; -+} -+ -+static int ov5693_enum_frame_size(struct v4l2_subdev *sd, -+ struct v4l2_subdev_fh *fh, -+ struct v4l2_subdev_frame_size_enum *fse) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ int index = fse->index; -+ -+ mutex_lock(&dev->input_lock); -+ -+ if (index >= dev->curr_res_num) { -+ mutex_unlock(&dev->input_lock); -+ return -EINVAL; -+ } -+ -+ fse->min_width = dev->ov5693_res[index].width; -+ fse->min_height = dev->ov5693_res[index].height; -+ fse->max_width = dev->ov5693_res[index].width; -+ fse->max_height = dev->ov5693_res[index].height; -+ -+ mutex_unlock(&dev->input_lock); -+ return 0; -+} -+ -+static int ov5693_get_pad_format(struct v4l2_subdev *sd, -+ struct v4l2_subdev_fh *fh, -+ struct v4l2_subdev_format *fmt) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ struct v4l2_mbus_framefmt *format; -+ -+ mutex_lock(&dev->input_lock); -+ -+ switch (fmt->which) { -+ case V4L2_SUBDEV_FORMAT_TRY: -+ format = v4l2_subdev_get_try_format(fh, fmt->pad); -+ break; -+ case V4L2_SUBDEV_FORMAT_ACTIVE: -+ format = &dev->format; -+ break; -+ default: -+ format = NULL; -+ } -+ -+ mutex_unlock(&dev->input_lock); -+ -+ if (!format) -+ return -EINVAL; -+ -+ fmt->format = *format; -+ return 0; -+} -+ -+static int ov5693_set_pad_format(struct v4l2_subdev *sd, -+ struct v4l2_subdev_fh *fh, -+ struct v4l2_subdev_format *fmt) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ -+ mutex_lock(&dev->input_lock); -+ -+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) -+ dev->format = fmt->format; -+ -+ mutex_unlock(&dev->input_lock); -+ return 0; -+} -+ -+static int ov5693_g_skip_frames(struct v4l2_subdev *sd, u32 *frames) -+{ -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ -+ mutex_lock(&dev->input_lock); -+ *frames = dev->ov5693_res[dev->fmt_idx].skip_frames; -+ mutex_unlock(&dev->input_lock); -+ -+ return 0; -+} -+ -+static const struct v4l2_ctrl_ops ctrl_ops = { -+ .s_ctrl = ov5693_s_ctrl, -+ .g_volatile_ctrl = ov5693_g_ctrl, -+}; -+ -+static const char * const ctrl_run_mode_menu[] = { -+ NULL, -+ "Video", -+ "Still capture", -+ "Continuous capture", -+ "Preview", -+}; -+ -+static const struct v4l2_ctrl_config ctrl_run_mode = { -+ .ops = &ctrl_ops, -+ .id = V4L2_CID_RUN_MODE, -+ .name = "run mode", -+ .type = V4L2_CTRL_TYPE_MENU, -+ .min = 1, -+ .def = 4, -+ .max = 4, -+ .qmenu = ctrl_run_mode_menu, -+}; -+ -+static const struct v4l2_ctrl_config ctrls[] = { -+ { -+ .ops = &ctrl_ops, -+ .id = V4L2_CID_EXPOSURE_ABSOLUTE, -+ .name = "absolute exposure", -+ .type = V4L2_CTRL_TYPE_INTEGER, -+ .min = 0x0, -+ .max = 0xffff, -+ .step = 0x01, -+ .def = 0x00, -+ .flags = 0, -+ }, { -+ .ops = &ctrl_ops, -+ .id = V4L2_CID_FOCUS_ABSOLUTE, -+ .type = V4L2_CTRL_TYPE_INTEGER, -+ .name = "focus move absolute", -+ .min = 0, -+ .max = OV5693_MAX_FOCUS_POS, -+ .step = 1, -+ .def = 0, -+ .flags = 0, -+ }, { -+ .ops = &ctrl_ops, -+ .id = V4L2_CID_FOCUS_RELATIVE, -+ .type = V4L2_CTRL_TYPE_INTEGER, -+ .name = "focus move relative", -+ .min = OV5693_MAX_FOCUS_NEG, -+ .max = OV5693_MAX_FOCUS_POS, -+ .step = 1, -+ .def = 0, -+ .flags = 0, -+ }, { -+ .ops = &ctrl_ops, -+ .id = V4L2_CID_FOCUS_STATUS, -+ .type = V4L2_CTRL_TYPE_INTEGER, -+ .name = "focus status", -+ .min = 0, -+ .max = 100, -+ .step = 1, -+ .def = 0, -+ .flags = 0, -+ }, { -+ .ops = &ctrl_ops, -+ .id = V4L2_CID_FOCAL_ABSOLUTE, -+ .type = V4L2_CTRL_TYPE_INTEGER, -+ .name = "focal length", -+ .min = OV5693_FOCAL_LENGTH_DEFAULT, -+ .max = OV5693_FOCAL_LENGTH_DEFAULT, -+ .step = 0x01, -+ .def = OV5693_FOCAL_LENGTH_DEFAULT, -+ .flags = 0, -+ }, { -+ .ops = &ctrl_ops, -+ .id = V4L2_CID_FNUMBER_ABSOLUTE, -+ .type = V4L2_CTRL_TYPE_INTEGER, -+ .name = "f-number", -+ .min = OV5693_F_NUMBER_DEFAULT, -+ .max = OV5693_F_NUMBER_DEFAULT, -+ .step = 0x01, -+ .def = OV5693_F_NUMBER_DEFAULT, -+ .flags = 0, -+ }, { -+ .ops = &ctrl_ops, -+ .id = V4L2_CID_FNUMBER_RANGE, -+ .type = V4L2_CTRL_TYPE_INTEGER, -+ .name = "f-number range", -+ .min = OV5693_F_NUMBER_RANGE, -+ .max = OV5693_F_NUMBER_RANGE, -+ .step = 0x01, -+ .def = OV5693_F_NUMBER_RANGE, -+ .flags = 0, -+ }, { -+ .ops = &ctrl_ops, -+ .id = V4L2_CID_BIN_FACTOR_HORZ, -+ .name = "horizontal binning factor", -+ .type = V4L2_CTRL_TYPE_INTEGER, -+ .max = OV5693_BIN_FACTOR_MAX, -+ .step = 2, -+ .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE, -+ }, { -+ .ops = &ctrl_ops, -+ .id = V4L2_CID_BIN_FACTOR_VERT, -+ .name = "vertical binning factor", -+ .type = V4L2_CTRL_TYPE_INTEGER, -+ .max = OV5693_BIN_FACTOR_MAX, -+ .step = 2, -+ .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE, -+ } -+}; -+ -+static const struct v4l2_subdev_sensor_ops ov5693_sensor_ops = { -+ .g_skip_frames = ov5693_g_skip_frames, -+}; -+ -+static const struct v4l2_subdev_video_ops ov5693_video_ops = { -+ .s_stream = ov5693_s_stream, -+ .g_parm = ov5693_g_parm, -+ .enum_framesizes = ov5693_enum_framesizes, -+ .enum_frameintervals = ov5693_enum_frameintervals, -+ .enum_mbus_fmt = ov5693_enum_mbus_fmt, -+ .try_mbus_fmt = ov5693_try_mbus_fmt, -+ .g_mbus_fmt = ov5693_g_mbus_fmt, -+ .s_mbus_fmt = ov5693_s_mbus_fmt, -+ .g_frame_interval = ov5693_g_frame_interval, -+}; -+ -+static const struct v4l2_subdev_core_ops ov5693_core_ops = { -+ .s_power = ov5693_s_power, -+ .queryctrl = v4l2_subdev_queryctrl, -+ .g_ctrl = v4l2_subdev_g_ctrl, -+ .s_ctrl = v4l2_subdev_s_ctrl, -+ .ioctl = ov5693_ioctl, -+}; -+ -+static const struct v4l2_subdev_pad_ops ov5693_pad_ops = { -+ .enum_mbus_code = ov5693_enum_mbus_code, -+ .enum_frame_size = ov5693_enum_frame_size, -+ .get_fmt = ov5693_get_pad_format, -+ .set_fmt = ov5693_set_pad_format, -+}; -+ -+static const struct v4l2_subdev_ops ov5693_ops = { -+ .core = &ov5693_core_ops, -+ .video = &ov5693_video_ops, -+ .pad = &ov5693_pad_ops, -+ .sensor = &ov5693_sensor_ops, -+}; -+ -+static int ov5693_remove(struct i2c_client *client) -+{ -+ struct v4l2_subdev *sd = i2c_get_clientdata(client); -+ struct ov5693_device *dev = to_ov5693_sensor(sd); -+ dev_dbg(&client->dev, "ov5693_remove...\n"); -+ -+ dev->platform_data->csi_cfg(sd, 0); -+ -+ v4l2_device_unregister_subdev(sd); -+ media_entity_cleanup(&dev->sd.entity); -+ devm_kfree(&client->dev, dev); -+ -+ return 0; -+} -+ -+static int ov5693_probe(struct i2c_client *client, -+ const struct i2c_device_id *id) -+{ -+ struct ov5693_device *dev; -+ int i; -+ int ret; -+ -+ dev = devm_kzalloc(&client->dev, sizeof(*dev), GFP_KERNEL); -+ if (!dev) { -+ dev_err(&client->dev, "out of memory\n"); -+ return -ENOMEM; -+ } -+ -+ mutex_init(&dev->input_lock); -+ -+ /* -+ * Initialize related res members of dev. -+ */ -+ dev->fmt_idx = 0; -+ dev->ov5693_res = ov5693_res_preview; -+ dev->curr_res_num = N_RES_PREVIEW; -+ -+ v4l2_i2c_subdev_init(&(dev->sd), client, &ov5693_ops); -+ -+ if (client->dev.platform_data) { -+ ret = ov5693_s_config(&dev->sd, client->irq, -+ client->dev.platform_data); -+ if (ret) -+ goto out_free; -+ } -+ -+ dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; -+ dev->pad.flags = MEDIA_PAD_FL_SOURCE; -+ dev->format.code = V4L2_MBUS_FMT_SBGGR10_1X10; -+ dev->sd.entity.type = MEDIA_ENT_T_V4L2_SUBDEV_SENSOR; -+ dev->vcm_driver = &ov5693_vcm_ops; -+ -+ ret = v4l2_ctrl_handler_init(&dev->ctrl_handler, ARRAY_SIZE(ctrls) + 1); -+ if (ret) { -+ ov5693_remove(client); -+ return ret; -+ } -+ -+ dev->run_mode = v4l2_ctrl_new_custom(&dev->ctrl_handler, -+ &ctrl_run_mode, NULL); -+ -+ for (i = 0; i < ARRAY_SIZE(ctrls); i++) -+ v4l2_ctrl_new_custom(&dev->ctrl_handler, &ctrls[i], NULL); -+ -+ if (dev->ctrl_handler.error) { -+ ov5693_remove(client); -+ return dev->ctrl_handler.error; -+ } -+ -+ dev->ctrl_handler.lock = &dev->input_lock; -+ dev->sd.ctrl_handler = &dev->ctrl_handler; -+ v4l2_ctrl_handler_setup(&dev->ctrl_handler); -+ -+ ret = media_entity_init(&dev->sd.entity, 1, &dev->pad, 0); -+ if (ret) -+ ov5693_remove(client); -+ -+ /* vcm initialization */ -+ if (dev->vcm_driver && dev->vcm_driver->init) -+ ret = dev->vcm_driver->init(&dev->sd); -+ if (ret) { -+ dev_err(&client->dev, "vcm init failed.\n"); -+ ov5693_remove(client); -+ } -+ -+ return ret; -+out_free: -+ v4l2_device_unregister_subdev(&dev->sd); -+ devm_kfree(&client->dev, dev); -+ return ret; -+} -+ -+MODULE_DEVICE_TABLE(i2c, ov5693_id); -+static struct i2c_driver ov5693_driver = { -+ .driver = { -+ .owner = THIS_MODULE, -+ .name = OV5693_NAME, -+ }, -+ .probe = ov5693_probe, -+ .remove = ov5693_remove, -+ .id_table = ov5693_id, -+}; -+ -+module_i2c_driver(ov5693_driver); -+ -+MODULE_DESCRIPTION("A low-level driver for OmniVision 5693 sensors"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/staging/ov5693/ov5693.h b/drivers/staging/ov5693/ov5693.h -new file mode 100644 -index 000000000000..79aef69666e8 ---- /dev/null -+++ b/drivers/staging/ov5693/ov5693.h -@@ -0,0 +1,848 @@ -+/* -+ * Support for OmniVision OV5693 5M HD camera sensor. -+ * -+ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. -+ * -+ * This program is free software; you can redistribute it and/or -+ * modify it under the terms of the GNU General Public License version -+ * 2 as published by the Free Software Foundation. -+ * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program; if not, write to the Free Software -+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -+ * 02110-1301, USA. -+ * -+ */ -+ -+#ifndef __OV5693_H__ -+#define __OV5693_H__ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include "ad5823.h" -+ -+#define OV5693_NAME "ov5693" -+ -+/* Defines for register writes and register array processing */ -+#define I2C_MSG_LENGTH 0x2 -+ -+#define OV5693_FOCAL_LENGTH_NUM 278 /*2.78mm*/ -+#define OV5693_FOCAL_LENGTH_DEM 100 -+#define OV5693_F_NUMBER_DEFAULT_NUM 26 -+#define OV5693_F_NUMBER_DEM 10 -+ -+#define OV5693_MAX_FOCUS_POS 1023 -+#define OV5693_MAX_FOCUS_POS 1023 -+#define OV5693_MAX_FOCUS_NEG (-1023) -+ -+#define LARGEST_ALLOWED_RATIO_MISMATCH 800 -+#define RATIO_SHIFT_BITS 13 -+ -+/* -+ * focal length bits definition: -+ * bits 31-16: numerator, bits 15-0: denominator -+ */ -+#define OV5693_FOCAL_LENGTH_DEFAULT 0x1160064 -+ -+/* -+ * current f-number bits definition: -+ * bits 31-16: numerator, bits 15-0: denominator -+ */ -+#define OV5693_F_NUMBER_DEFAULT 0x1a000a -+ -+/* -+ * f-number range bits definition: -+ * bits 31-24: max f-number numerator -+ * bits 23-16: max f-number denominator -+ * bits 15-8: min f-number numerator -+ * bits 7-0: min f-number denominator -+ */ -+#define OV5693_F_NUMBER_RANGE 0x1a0a1a0a -+#define OV5693_ID 0x5690 -+ -+#define OV5693_FINE_INTG_TIME_MIN 0 -+#define OV5693_FINE_INTG_TIME_MAX_MARGIN 0 -+#define OV5693_COARSE_INTG_TIME_MIN 1 -+#define OV5693_COARSE_INTG_TIME_MAX_MARGIN (0xffff - 6) -+#define OV5693_INTEGRATION_TIME_MARGIN 8 -+ -+#define OV5693_BIN_FACTOR_MAX 2 -+ -+/* -+ * OV5693 System control registers -+ */ -+#define OV5693_SW_RESET 0x0103 -+#define OV5693_SW_STREAM 0x0100 -+ -+#define OV5693_SC_CMMN_CHIP_ID 0x300a -+#define OV5693_SC_CMMN_SUB_ID 0x302a /* process, version*/ -+ -+#define OV5693_AEC_PK_EXPO_H 0x3500 -+#define OV5693_AEC_PK_EXPO_M 0x3501 -+#define OV5693_AEC_PK_EXPO_L 0x3502 -+#define OV5693_AGC_ADJ_H 0x350a -+#define OV5693_VTS_H 0x380e -+#define OV5693_GROUP_ACCESS 0x3208 -+ -+#define OV5693_MWB_GAIN_R_H 0x3400 -+#define OV5693_MWB_GAIN_G_H 0x3402 -+#define OV5693_MWB_GAIN_B_H 0x3404 -+ -+#define OV5693_H_CROP_START_H 0x3800 -+#define OV5693_V_CROP_START_H 0x3802 -+#define OV5693_H_CROP_END_H 0x3804 -+#define OV5693_V_CROP_END_H 0x3806 -+#define OV5693_H_OUTSIZE_H 0x3808 -+#define OV5693_V_OUTSIZE_H 0x380a -+ -+#define OV5693_START_STREAMING 0x01 -+#define OV5693_STOP_STREAMING 0x00 -+ -+struct ov5693_vcm { -+ int (*power_up)(struct v4l2_subdev *sd); -+ int (*power_down)(struct v4l2_subdev *sd); -+ int (*init)(struct v4l2_subdev *sd); -+ int (*t_focus_vcm)(struct v4l2_subdev *sd, u16 val); -+ int (*t_focus_abs)(struct v4l2_subdev *sd, s32 value); -+ int (*t_focus_rel)(struct v4l2_subdev *sd, s32 value); -+ int (*q_focus_status)(struct v4l2_subdev *sd, s32 *value); -+ int (*q_focus_abs)(struct v4l2_subdev *sd, s32 *value); -+}; -+ -+struct ov5693_resolution { -+ u8 *desc; -+ const struct ov5693_reg *regs; -+ int res; -+ int width; -+ int height; -+ int fps; -+ int pix_clk_freq; -+ u16 skip_frames; -+ u16 pixels_per_line; -+ u16 lines_per_frame; -+ u8 bin_factor_x; -+ u8 bin_factor_y; -+ u8 bin_mode; -+ bool used; -+}; -+ -+struct ov5693_control { -+ struct v4l2_queryctrl qc; -+ int (*query)(struct v4l2_subdev *sd, s32 *value); -+ int (*tweak)(struct v4l2_subdev *sd, s32 value); -+}; -+ -+/* -+ * ov5693 device structure. -+ */ -+struct ov5693_device { -+ struct v4l2_subdev sd; -+ struct media_pad pad; -+ struct v4l2_mbus_framefmt format; -+ struct mutex input_lock; -+ -+ struct camera_sensor_platform_data *platform_data; -+ struct ov5693_vcm *vcm_driver; -+ int fmt_idx; -+ u8 res; -+ u8 type; -+ -+ struct ov5693_resolution *ov5693_res; -+ int curr_res_num; -+ -+ struct v4l2_ctrl_handler ctrl_handler; -+ struct v4l2_ctrl *run_mode; -+}; -+ -+enum ov5693_tok_type { -+ OV5693_8BIT = 0x0001, -+ OV5693_16BIT = 0x0002, -+ OV5693_32BIT = 0x0004, -+ OV5693_TOK_TERM = 0xf000, /* terminating token for reg list */ -+ OV5693_TOK_DELAY = 0xfe00, /* delay token for reg list */ -+ OV5693_TOK_MASK = 0xfff0 -+}; -+ -+/** -+ * struct ov5693_reg - MI sensor register format -+ * @type: type of the register -+ * @reg: 16-bit offset to register -+ * @val: 8/16/32-bit register value -+ * -+ * Define a structure for sensor register initialization values -+ */ -+struct ov5693_reg { -+ enum ov5693_tok_type type; -+ u16 reg; -+ u32 val; /* @set value for read/mod/write, @mask */ -+}; -+ -+#define to_ov5693_sensor(x) container_of(x, struct ov5693_device, sd) -+ -+#define OV5693_MAX_WRITE_BUF_SIZE 30 -+ -+struct ov5693_write_buffer { -+ u16 addr; -+ u8 data[OV5693_MAX_WRITE_BUF_SIZE]; -+}; -+ -+struct ov5693_write_ctrl { -+ int index; -+ struct ov5693_write_buffer buffer; -+}; -+ -+static const struct i2c_device_id ov5693_id[] = { -+ {OV5693_NAME, 0}, -+ {} -+}; -+ -+/* ov5693 sensor initialization setting */ -+static struct ov5693_reg const ov5693_init_setting[] = { -+ {OV5693_8BIT, 0x0103, 0x01}, -+ {OV5693_8BIT, 0x3001, 0x0a}, -+ {OV5693_8BIT, 0x3002, 0x80}, -+ {OV5693_8BIT, 0x3006, 0x00}, -+ {OV5693_8BIT, 0x3011, 0x21}, -+ {OV5693_8BIT, 0x3012, 0x09}, -+ {OV5693_8BIT, 0x3013, 0x10}, -+ {OV5693_8BIT, 0x3014, 0x00}, -+ {OV5693_8BIT, 0x3015, 0x08}, -+ {OV5693_8BIT, 0x3016, 0xf0}, -+ {OV5693_8BIT, 0x3017, 0xf0}, -+ {OV5693_8BIT, 0x3018, 0xf0}, -+ {OV5693_8BIT, 0x301b, 0xb4}, -+ {OV5693_8BIT, 0x301d, 0x02}, -+ {OV5693_8BIT, 0x3021, 0x00}, -+ {OV5693_8BIT, 0x3022, 0x01}, -+ {OV5693_8BIT, 0x3028, 0x44}, -+ {OV5693_8BIT, 0x3098, 0x02}, -+ {OV5693_8BIT, 0x3099, 0x19}, -+ {OV5693_8BIT, 0x309a, 0x02}, -+ {OV5693_8BIT, 0x309b, 0x01}, -+ {OV5693_8BIT, 0x309c, 0x00}, -+ {OV5693_8BIT, 0x30a0, 0xd2}, -+ {OV5693_8BIT, 0x30a2, 0x01}, -+ {OV5693_8BIT, 0x30b2, 0x00}, -+ {OV5693_8BIT, 0x30b3, 0x7d}, -+ {OV5693_8BIT, 0x30b4, 0x03}, -+ {OV5693_8BIT, 0x30b5, 0x04}, -+ {OV5693_8BIT, 0x30b6, 0x01}, -+ {OV5693_8BIT, 0x3104, 0x21}, -+ {OV5693_8BIT, 0x3106, 0x00}, -+ -+ /* Manual white balance */ -+ {OV5693_8BIT, 0x3400, 0x04}, -+ {OV5693_8BIT, 0x3401, 0x00}, -+ {OV5693_8BIT, 0x3402, 0x04}, -+ {OV5693_8BIT, 0x3403, 0x00}, -+ {OV5693_8BIT, 0x3404, 0x04}, -+ {OV5693_8BIT, 0x3405, 0x00}, -+ {OV5693_8BIT, 0x3406, 0x01}, -+ -+ /* Manual exposure control */ -+ {OV5693_8BIT, 0x3500, 0x00}, -+ {OV5693_8BIT, 0x3503, 0x07}, -+ {OV5693_8BIT, 0x3504, 0x00}, -+ {OV5693_8BIT, 0x3505, 0x00}, -+ {OV5693_8BIT, 0x3506, 0x00}, -+ {OV5693_8BIT, 0x3507, 0x02}, -+ {OV5693_8BIT, 0x3508, 0x00}, -+ -+ /* Manual gain control */ -+ {OV5693_8BIT, 0x3509, 0x10}, -+ {OV5693_8BIT, 0x350a, 0x00}, -+ {OV5693_8BIT, 0x350b, 0x40}, -+ -+ {OV5693_8BIT, 0x3601, 0x0a}, -+ {OV5693_8BIT, 0x3602, 0x38}, -+ {OV5693_8BIT, 0x3612, 0x80}, -+ {OV5693_8BIT, 0x3620, 0x54}, -+ {OV5693_8BIT, 0x3621, 0xc7}, -+ {OV5693_8BIT, 0x3622, 0x0f}, -+ {OV5693_8BIT, 0x3625, 0x10}, -+ {OV5693_8BIT, 0x3630, 0x55}, -+ {OV5693_8BIT, 0x3631, 0xf4}, -+ {OV5693_8BIT, 0x3632, 0x00}, -+ {OV5693_8BIT, 0x3633, 0x34}, -+ {OV5693_8BIT, 0x3634, 0x02}, -+ {OV5693_8BIT, 0x364d, 0x0d}, -+ {OV5693_8BIT, 0x364f, 0xdd}, -+ {OV5693_8BIT, 0x3660, 0x04}, -+ {OV5693_8BIT, 0x3662, 0x10}, -+ {OV5693_8BIT, 0x3663, 0xf1}, -+ {OV5693_8BIT, 0x3665, 0x00}, -+ {OV5693_8BIT, 0x3666, 0x20}, -+ {OV5693_8BIT, 0x3667, 0x00}, -+ {OV5693_8BIT, 0x366a, 0x80}, -+ {OV5693_8BIT, 0x3680, 0xe0}, -+ {OV5693_8BIT, 0x3681, 0x00}, -+ {OV5693_8BIT, 0x3700, 0x42}, -+ {OV5693_8BIT, 0x3701, 0x14}, -+ {OV5693_8BIT, 0x3702, 0xa0}, -+ {OV5693_8BIT, 0x3703, 0xd8}, -+ {OV5693_8BIT, 0x3704, 0x78}, -+ {OV5693_8BIT, 0x3705, 0x02}, -+ {OV5693_8BIT, 0x370a, 0x00}, -+ {OV5693_8BIT, 0x370b, 0x20}, -+ {OV5693_8BIT, 0x370c, 0x0c}, -+ {OV5693_8BIT, 0x370d, 0x11}, -+ {OV5693_8BIT, 0x370e, 0x00}, -+ {OV5693_8BIT, 0x370f, 0x40}, -+ {OV5693_8BIT, 0x3710, 0x00}, -+ {OV5693_8BIT, 0x371a, 0x1c}, -+ {OV5693_8BIT, 0x371b, 0x05}, -+ {OV5693_8BIT, 0x371c, 0x01}, -+ {OV5693_8BIT, 0x371e, 0xa1}, -+ {OV5693_8BIT, 0x371f, 0x0c}, -+ {OV5693_8BIT, 0x3721, 0x00}, -+ {OV5693_8BIT, 0x3724, 0x10}, -+ {OV5693_8BIT, 0x3726, 0x00}, -+ {OV5693_8BIT, 0x372a, 0x01}, -+ {OV5693_8BIT, 0x3730, 0x10}, -+ {OV5693_8BIT, 0x3738, 0x22}, -+ {OV5693_8BIT, 0x3739, 0xe5}, -+ {OV5693_8BIT, 0x373a, 0x50}, -+ {OV5693_8BIT, 0x373b, 0x02}, -+ {OV5693_8BIT, 0x373c, 0x41}, -+ {OV5693_8BIT, 0x373f, 0x02}, -+ {OV5693_8BIT, 0x3740, 0x42}, -+ {OV5693_8BIT, 0x3741, 0x02}, -+ {OV5693_8BIT, 0x3742, 0x18}, -+ {OV5693_8BIT, 0x3743, 0x01}, -+ {OV5693_8BIT, 0x3744, 0x02}, -+ {OV5693_8BIT, 0x3747, 0x10}, -+ {OV5693_8BIT, 0x374c, 0x04}, -+ {OV5693_8BIT, 0x3751, 0xf0}, -+ {OV5693_8BIT, 0x3752, 0x00}, -+ {OV5693_8BIT, 0x3753, 0x00}, -+ {OV5693_8BIT, 0x3754, 0xc0}, -+ {OV5693_8BIT, 0x3755, 0x00}, -+ {OV5693_8BIT, 0x3756, 0x1a}, -+ {OV5693_8BIT, 0x3758, 0x00}, -+ {OV5693_8BIT, 0x3759, 0x0f}, -+ {OV5693_8BIT, 0x376b, 0x44}, -+ {OV5693_8BIT, 0x375c, 0x04}, -+ {OV5693_8BIT, 0x3774, 0x10}, -+ {OV5693_8BIT, 0x3776, 0x00}, -+ {OV5693_8BIT, 0x377f, 0x08}, -+ {OV5693_8BIT, 0x3780, 0x22}, -+ {OV5693_8BIT, 0x3781, 0x0c}, -+ {OV5693_8BIT, 0x3784, 0x2c}, -+ {OV5693_8BIT, 0x3785, 0x1e}, -+ {OV5693_8BIT, 0x378f, 0xf5}, -+ {OV5693_8BIT, 0x3791, 0xb0}, -+ {OV5693_8BIT, 0x3795, 0x00}, -+ {OV5693_8BIT, 0x3796, 0x64}, -+ {OV5693_8BIT, 0x3797, 0x11}, -+ {OV5693_8BIT, 0x3798, 0x30}, -+ {OV5693_8BIT, 0x3799, 0x41}, -+ {OV5693_8BIT, 0x379a, 0x07}, -+ {OV5693_8BIT, 0x379b, 0xb0}, -+ {OV5693_8BIT, 0x379c, 0x0c}, -+ {OV5693_8BIT, 0x37c5, 0x00}, -+ {OV5693_8BIT, 0x37c6, 0x00}, -+ {OV5693_8BIT, 0x37c7, 0x00}, -+ {OV5693_8BIT, 0x37c9, 0x00}, -+ {OV5693_8BIT, 0x37ca, 0x00}, -+ {OV5693_8BIT, 0x37cb, 0x00}, -+ {OV5693_8BIT, 0x37de, 0x00}, -+ {OV5693_8BIT, 0x37df, 0x00}, -+ {OV5693_8BIT, 0x3800, 0x00}, -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, -+ {OV5693_8BIT, 0x3804, 0x0a}, -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3810, 0x00}, -+ {OV5693_8BIT, 0x3812, 0x00}, -+ {OV5693_8BIT, 0x3823, 0x00}, -+ {OV5693_8BIT, 0x3824, 0x00}, -+ {OV5693_8BIT, 0x3825, 0x00}, -+ {OV5693_8BIT, 0x3826, 0x00}, -+ {OV5693_8BIT, 0x3827, 0x00}, -+ {OV5693_8BIT, 0x382a, 0x04}, -+ {OV5693_8BIT, 0x3a04, 0x06}, -+ {OV5693_8BIT, 0x3a05, 0x14}, -+ {OV5693_8BIT, 0x3a06, 0x00}, -+ {OV5693_8BIT, 0x3a07, 0xfe}, -+ {OV5693_8BIT, 0x3b00, 0x00}, -+ {OV5693_8BIT, 0x3b02, 0x00}, -+ {OV5693_8BIT, 0x3b03, 0x00}, -+ {OV5693_8BIT, 0x3b04, 0x00}, -+ {OV5693_8BIT, 0x3b05, 0x00}, -+ {OV5693_8BIT, 0x3e07, 0x20}, -+ {OV5693_8BIT, 0x4000, 0x08}, -+ {OV5693_8BIT, 0x4001, 0x04}, -+ {OV5693_8BIT, 0x4002, 0x45}, -+ {OV5693_8BIT, 0x4004, 0x08}, -+ {OV5693_8BIT, 0x4005, 0x18}, -+ {OV5693_8BIT, 0x4006, 0x20}, -+ {OV5693_8BIT, 0x4008, 0x24}, -+ {OV5693_8BIT, 0x4009, 0x10}, -+ {OV5693_8BIT, 0x400c, 0x00}, -+ {OV5693_8BIT, 0x400d, 0x00}, -+ {OV5693_8BIT, 0x4058, 0x00}, -+ {OV5693_8BIT, 0x404e, 0x37}, -+ {OV5693_8BIT, 0x404f, 0x8f}, -+ {OV5693_8BIT, 0x4058, 0x00}, -+ {OV5693_8BIT, 0x4101, 0xb2}, -+ {OV5693_8BIT, 0x4303, 0x00}, -+ {OV5693_8BIT, 0x4304, 0x08}, -+ {OV5693_8BIT, 0x4307, 0x30}, -+ {OV5693_8BIT, 0x4311, 0x04}, -+ {OV5693_8BIT, 0x4315, 0x01}, -+ {OV5693_8BIT, 0x4511, 0x05}, -+ {OV5693_8BIT, 0x4512, 0x01}, -+ {OV5693_8BIT, 0x4806, 0x00}, -+ {OV5693_8BIT, 0x4816, 0x52}, -+ {OV5693_8BIT, 0x481f, 0x30}, -+ {OV5693_8BIT, 0x4826, 0x2c}, -+ {OV5693_8BIT, 0x4831, 0x64}, -+ {OV5693_8BIT, 0x4d00, 0x04}, -+ {OV5693_8BIT, 0x4d01, 0x71}, -+ {OV5693_8BIT, 0x4d02, 0xfd}, -+ {OV5693_8BIT, 0x4d03, 0xf5}, -+ {OV5693_8BIT, 0x4d04, 0x0c}, -+ {OV5693_8BIT, 0x4d05, 0xcc}, -+ {OV5693_8BIT, 0x4837, 0x0a}, -+ {OV5693_8BIT, 0x5000, 0x06}, -+ {OV5693_8BIT, 0x5001, 0x01}, -+ {OV5693_8BIT, 0x5003, 0x20}, -+ {OV5693_8BIT, 0x5046, 0x0a}, -+ {OV5693_8BIT, 0x5013, 0x00}, -+ {OV5693_8BIT, 0x5046, 0x0a}, -+ {OV5693_8BIT, 0x5780, 0x1c}, -+ {OV5693_8BIT, 0x5786, 0x20}, -+ {OV5693_8BIT, 0x5787, 0x10}, -+ {OV5693_8BIT, 0x5788, 0x18}, -+ {OV5693_8BIT, 0x578a, 0x04}, -+ {OV5693_8BIT, 0x578b, 0x02}, -+ {OV5693_8BIT, 0x578c, 0x02}, -+ {OV5693_8BIT, 0x578e, 0x06}, -+ {OV5693_8BIT, 0x578f, 0x02}, -+ {OV5693_8BIT, 0x5790, 0x02}, -+ {OV5693_8BIT, 0x5791, 0xff}, -+ {OV5693_8BIT, 0x5842, 0x01}, -+ {OV5693_8BIT, 0x5843, 0x2b}, -+ {OV5693_8BIT, 0x5844, 0x01}, -+ {OV5693_8BIT, 0x5845, 0x92}, -+ {OV5693_8BIT, 0x5846, 0x01}, -+ {OV5693_8BIT, 0x5847, 0x8f}, -+ {OV5693_8BIT, 0x5848, 0x01}, -+ {OV5693_8BIT, 0x5849, 0x0c}, -+ {OV5693_8BIT, 0x5e00, 0x00}, -+ {OV5693_8BIT, 0x5e10, 0x0c}, -+ {OV5693_8BIT, 0x0100, 0x00}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+ * Register settings for various resolution -+ */ -+ -+/* -+------------------------------------ -+@@ FULL QSXGA (2592x1944) 15fps 33.33ms VBlank 2lane 10Bit -+100 99 2592 1944 -+100 98 1 0 -+102 3601 bb8 ;Pather tool use only -+c8 1 f2 ; New FPGA Board -+c8 20 22 ; New FPGA Board -+c8 10 42 ; MIPI DFGA CYCY3 Board use only -+; -+c8 f 32 ; input clock to 19.2MHz -+; -+; OV5690 setting version History -+; -+; -+; V18b -+*/ -+static struct ov5693_reg const ov5693_5M_15fps[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3800, 0x00}, /* x_addr_start: 0 */ -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, /* y_addr_start: 0 */ -+ {OV5693_8BIT, 0x3803, 0x00}, -+ {OV5693_8BIT, 0x3804, 0x0a}, /* x_addr_end: 2623 */ -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3806, 0x07}, /* y_addr_end: 1955 */ -+ {OV5693_8BIT, 0x3807, 0xa3}, -+ {OV5693_8BIT, 0x3808, 0x0a}, /* x output size: 2592 */ -+ {OV5693_8BIT, 0x3809, 0x20}, -+ {OV5693_8BIT, 0x380a, 0x07}, /* y output size: 1944 */ -+ {OV5693_8BIT, 0x380b, 0x98}, -+ {OV5693_8BIT, 0x380c, 0x0e}, /* total x output size: 3688 */ -+ {OV5693_8BIT, 0x380d, 0x68}, -+ {OV5693_8BIT, 0x380e, 0x0f}, /* total y output size: 3968 */ -+ {OV5693_8BIT, 0x380f, 0x80}, -+ {OV5693_8BIT, 0x3810, 0x00}, /* x offset: 16 */ -+ {OV5693_8BIT, 0x3811, 0x10}, -+ {OV5693_8BIT, 0x3812, 0x00}, /* y offset: 6 */ -+ {OV5693_8BIT, 0x3813, 0x06}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x00}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+@@ OV5693 1940x1096 30fps 8.8ms VBlanking 2lane 10Bit(Scaling) -+100 99 1940 1096 -+100 98 1 0 -+102 3601 bb8 ;Pather tool use only -+102 40 0 ; HDR Mode off -+c8 1 f2 ; New FPGA Board -+c8 20 22 ; New FPGA Board -+c8 10 42 ; MIPI DFGA CYCY3 Board use only -+; -+c8 f 32 ; input clock to 19.2MHz -+; -+; OV5690 setting version History -+; -+; -+; V18b -+*/ -+static struct ov5693_reg const ov5693_1080p_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x7b}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe2}, -+ {OV5693_8BIT, 0x3709, 0xc3}, -+ {OV5693_8BIT, 0x3800, 0x00}, /* x_addr_start: 0 */ -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, /* y_addr_start: 240 */ -+ {OV5693_8BIT, 0x3803, 0xf0}, -+ {OV5693_8BIT, 0x3804, 0x0a}, /* x_addr_end: 2591 */ -+ {OV5693_8BIT, 0x3805, 0x1f}, -+ {OV5693_8BIT, 0x3806, 0x06}, /* y_addr_end: 1703 */ -+ {OV5693_8BIT, 0x3807, 0xa7}, -+ {OV5693_8BIT, 0x3808, 0x07}, /* x output size: 1940 */ -+ {OV5693_8BIT, 0x3809, 0x94}, -+ {OV5693_8BIT, 0x380a, 0x04}, /* y output size: 1096 */ -+ {OV5693_8BIT, 0x380b, 0x48}, -+ {OV5693_8BIT, 0x380c, 0x0e}, /* total x output size: 3688 */ -+ {OV5693_8BIT, 0x380d, 0x68}, -+ {OV5693_8BIT, 0x380e, 0x0b}, /* total y output size: 2984 */ -+ {OV5693_8BIT, 0x380f, 0xa8}, -+ {OV5693_8BIT, 0x3810, 0x00}, /* x offset: 2 */ -+ {OV5693_8BIT, 0x3811, 0x02}, -+ {OV5693_8BIT, 0x3812, 0x00}, /* y offset: 2 */ -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x11}, -+ {OV5693_8BIT, 0x3815, 0x11}, -+ {OV5693_8BIT, 0x3820, 0x00}, -+ {OV5693_8BIT, 0x3821, 0x1e}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+ * 1296x736 30fps 8.8ms VBlanking 2lane 10Bit (Scaling) -+ */ -+static struct ov5693_reg const ov5693_720p_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x3d}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe6}, -+ {OV5693_8BIT, 0x3709, 0xc7}, -+ {OV5693_8BIT, 0x3800, 0x00}, /* x_addr_start: 0 */ -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, /* y_addr_start: 0 */ -+ {OV5693_8BIT, 0x3803, 0x00}, -+ {OV5693_8BIT, 0x3804, 0x0a}, /* x_addr_end: 2623 */ -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3806, 0x07}, /* y_addr_end: 1955 */ -+ {OV5693_8BIT, 0x3807, 0xa3}, -+ {OV5693_8BIT, 0x3808, 0x05}, /* x output size: 1296 */ -+ {OV5693_8BIT, 0x3809, 0x10}, -+ {OV5693_8BIT, 0x380a, 0x02}, /* y output size: 736 */ -+ {OV5693_8BIT, 0x380b, 0xe0}, -+ {OV5693_8BIT, 0x380c, 0x0a}, /* total x output size: 2688 */ -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, /* total y output size: 1984 */ -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3810, 0x00}, /* x offset: 8 */ -+ {OV5693_8BIT, 0x3811, 0x08}, -+ {OV5693_8BIT, 0x3812, 0x00}, /* y offset: 2 */ -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x04}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+ * 736x496 30fps 8.8ms VBlanking 2lane 10Bit (Scaling) -+ */ -+static struct ov5693_reg const ov5693_480p_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x3d}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe6}, -+ {OV5693_8BIT, 0x3709, 0xc7}, -+ {OV5693_8BIT, 0x3800, 0x00}, /* x_addr_start: 0 */ -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, /* y_addr_start: 7 */ -+ {OV5693_8BIT, 0x3803, 0x07}, -+ {OV5693_8BIT, 0x3804, 0x0a}, /* x_addr_end: 2623 */ -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3806, 0x07}, /* y_addr_end: 1955 */ -+ {OV5693_8BIT, 0x3807, 0xa3}, -+ {OV5693_8BIT, 0x3808, 0x02}, /* x output size: 736 */ -+ {OV5693_8BIT, 0x3809, 0xe0}, -+ {OV5693_8BIT, 0x380a, 0x01}, /* y output size: 496 */ -+ {OV5693_8BIT, 0x380b, 0xf0}, -+ {OV5693_8BIT, 0x380c, 0x0a}, /* total x output size: 2688 */ -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, /* total y output size: 1984 */ -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3810, 0x00}, /* x offset: 8 */ -+ {OV5693_8BIT, 0x3811, 0x08}, -+ {OV5693_8BIT, 0x3812, 0x00}, /* y offset: 2 */ -+ {OV5693_8BIT, 0x3813, 0x02}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x04}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+/* -+@@ OV5693 656x496 30fps 17ms VBlanking 2lane 10Bit(Scaling) -+100 99 656 496 -+100 98 1 0 -+102 3601 BB8 ;Pather tool use only -+c8 1 f2 ; New FPGA Board -+c8 20 22 ; New FPGA Board -+; OV5690 setting version History -+; -+c8 f 32 ; input clock to 19.2MHz -+; -+; V18b -+*/ -+static struct ov5693_reg const ov5693_VGA_30fps[] = { -+ {OV5693_8BIT, 0x3501, 0x3d}, -+ {OV5693_8BIT, 0x3502, 0x00}, -+ {OV5693_8BIT, 0x3708, 0xe6}, -+ {OV5693_8BIT, 0x3709, 0xc7}, -+ {OV5693_8BIT, 0x3800, 0x00}, /* x_addr_start: 0 */ -+ {OV5693_8BIT, 0x3801, 0x00}, -+ {OV5693_8BIT, 0x3802, 0x00}, /* y_addr_start: 0 */ -+ {OV5693_8BIT, 0x3803, 0x00}, -+ {OV5693_8BIT, 0x3804, 0x0a}, /* x_addr_end: 2623 */ -+ {OV5693_8BIT, 0x3805, 0x3f}, -+ {OV5693_8BIT, 0x3806, 0x07}, /* y_addr_end: 1955 */ -+ {OV5693_8BIT, 0x3807, 0xa3}, -+ {OV5693_8BIT, 0x3808, 0x02}, /* x output size: 656 */ -+ {OV5693_8BIT, 0x3809, 0x90}, -+ {OV5693_8BIT, 0x380a, 0x01}, /* y output size: 496 */ -+ {OV5693_8BIT, 0x380b, 0xf0}, -+ {OV5693_8BIT, 0x380c, 0x0a}, /* total x output size: 2688 */ -+ {OV5693_8BIT, 0x380d, 0x80}, -+ {OV5693_8BIT, 0x380e, 0x07}, /* total y output size: 1984 */ -+ {OV5693_8BIT, 0x380f, 0xc0}, -+ {OV5693_8BIT, 0x3810, 0x00}, /* x offset: 13 */ -+ {OV5693_8BIT, 0x3811, 0x0d}, -+ {OV5693_8BIT, 0x3812, 0x00}, /* y offset: 3 */ -+ {OV5693_8BIT, 0x3813, 0x03}, -+ {OV5693_8BIT, 0x3814, 0x31}, -+ {OV5693_8BIT, 0x3815, 0x31}, -+ {OV5693_8BIT, 0x3820, 0x04}, -+ {OV5693_8BIT, 0x3821, 0x1f}, -+ {OV5693_8BIT, 0x5002, 0x80}, -+ {OV5693_TOK_TERM, 0, 0} -+}; -+ -+struct ov5693_resolution ov5693_res_preview[] = { -+ { -+ .desc = "ov5693_VGA_30fps", -+ .width = 656, -+ .height = 496, -+ .fps = 30, -+ .pix_clk_freq = 81, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 0, -+ .skip_frames = 3, -+ .regs = ov5693_VGA_30fps, -+ }, -+ { -+ .desc = "ov5693_1080P_30fps", -+ .width = 1940, -+ .height = 1096, -+ .fps = 30, -+ .pix_clk_freq = 81, -+ .used = 0, -+ .pixels_per_line = 3688, -+ .lines_per_frame = 2984, -+ .bin_factor_x = 0, -+ .bin_factor_y = 0, -+ .bin_mode = 0, -+ .skip_frames = 3, -+ .regs = ov5693_1080p_30fps, -+ }, -+ { -+ .desc = "ov5693_5M_15fps", -+ .width = 2592, -+ .height = 1944, -+ .fps = 15, -+ .pix_clk_freq = 81, -+ .used = 0, -+ .pixels_per_line = 3688, -+ .lines_per_frame = 3968, -+ .bin_factor_x = 0, -+ .bin_factor_y = 0, -+ .bin_mode = 0, -+ .skip_frames = 3, -+ .regs = ov5693_5M_15fps, -+ }, -+}; -+#define N_RES_PREVIEW (ARRAY_SIZE(ov5693_res_preview)) -+ -+struct ov5693_resolution ov5693_res_still[] = { -+ { -+ .desc = "ov5693_VGA_30fps", -+ .width = 656, -+ .height = 496, -+ .fps = 30, -+ .pix_clk_freq = 81, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 0, -+ .skip_frames = 3, -+ .regs = ov5693_VGA_30fps, -+ }, -+ { -+ .desc = "ov5693_1080P_30fps", -+ .width = 1940, -+ .height = 1096, -+ .fps = 30, -+ .pix_clk_freq = 81, -+ .used = 0, -+ .pixels_per_line = 3688, -+ .lines_per_frame = 2984, -+ .bin_factor_x = 0, -+ .bin_factor_y = 0, -+ .bin_mode = 0, -+ .skip_frames = 3, -+ .regs = ov5693_1080p_30fps, -+ }, -+ { -+ .desc = "ov5693_5M_15fps", -+ .width = 2592, -+ .height = 1944, -+ .fps = 15, -+ .pix_clk_freq = 81, -+ .used = 0, -+ .pixels_per_line = 3688, -+ .lines_per_frame = 3968, -+ .bin_factor_x = 0, -+ .bin_factor_y = 0, -+ .bin_mode = 0, -+ .skip_frames = 3, -+ .regs = ov5693_5M_15fps, -+ }, -+}; -+#define N_RES_STILL (ARRAY_SIZE(ov5693_res_still)) -+ -+struct ov5693_resolution ov5693_res_video[] = { -+ { -+ .desc = "ov5693_VGA_30fps", -+ .width = 656, -+ .height = 496, -+ .fps = 30, -+ .pix_clk_freq = 81, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 0, -+ .skip_frames = 3, -+ .regs = ov5693_VGA_30fps, -+ }, -+ { -+ .desc = "ov5693_480P_30fps", -+ .width = 736, -+ .height = 496, -+ .fps = 30, -+ .pix_clk_freq = 81, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 0, -+ .skip_frames = 1, -+ .regs = ov5693_480p_30fps, -+ }, -+ { -+ .desc = "ov5693_720p_30fps", -+ .width = 1296, -+ .height = 736, -+ .fps = 30, -+ .pix_clk_freq = 81, -+ .used = 0, -+ .pixels_per_line = 2688, -+ .lines_per_frame = 1984, -+ .bin_factor_x = 2, -+ .bin_factor_y = 2, -+ .bin_mode = 0, -+ .skip_frames = 1, -+ .regs = ov5693_720p_30fps, -+ }, -+ { -+ .desc = "ov5693_1080P_30fps", -+ .width = 1940, -+ .height = 1096, -+ .fps = 30, -+ .pix_clk_freq = 81, -+ .used = 0, -+ .pixels_per_line = 3688, -+ .lines_per_frame = 2984, -+ .bin_factor_x = 0, -+ .bin_factor_y = 0, -+ .bin_mode = 0, -+ .skip_frames = 3, -+ .regs = ov5693_1080p_30fps, -+ }, -+}; -+#define N_RES_VIDEO (ARRAY_SIZE(ov5693_res_video)) -+ -+struct ov5693_vcm ov5693_vcm_ops = { -+ .power_up = ad5823_vcm_power_up, -+ .power_down = ad5823_vcm_power_down, -+ .init = ad5823_vcm_init, -+ .t_focus_vcm = ad5823_t_focus_vcm, -+ .t_focus_abs = ad5823_t_focus_abs, -+ .t_focus_rel = ad5823_t_focus_rel, -+ .q_focus_status = ad5823_q_focus_status, -+ .q_focus_abs = ad5823_q_focus_abs, -+}; -+#endif --- -2.23.0 - diff --git a/patches/5.2/0005-ipts.patch b/patches/5.2/0005-ipts.patch deleted file mode 100644 index 8118e9bf9..000000000 --- a/patches/5.2/0005-ipts.patch +++ /dev/null @@ -1,6854 +0,0 @@ -From 80e9c51a3d3b646698a40d761fd30a8cf6b45244 Mon Sep 17 00:00:00 2001 -From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com> -Date: Tue, 10 Sep 2019 21:54:42 +0900 -Subject: [PATCH 05/12] ipts - ---- - drivers/gpu/drm/i915/Makefile | 3 + - drivers/gpu/drm/i915/i915_debugfs.c | 63 +- - drivers/gpu/drm/i915/i915_drv.c | 7 + - drivers/gpu/drm/i915/i915_drv.h | 3 + - drivers/gpu/drm/i915/i915_gem_context.c | 12 + - drivers/gpu/drm/i915/i915_irq.c | 7 +- - drivers/gpu/drm/i915/i915_params.c | 5 +- - drivers/gpu/drm/i915/i915_params.h | 5 +- - drivers/gpu/drm/i915/intel_dp.c | 4 +- - drivers/gpu/drm/i915/intel_guc.h | 1 + - drivers/gpu/drm/i915/intel_guc_submission.c | 89 +- - drivers/gpu/drm/i915/intel_guc_submission.h | 4 + - drivers/gpu/drm/i915/intel_ipts.c | 651 ++++++++++++ - drivers/gpu/drm/i915/intel_ipts.h | 34 + - drivers/gpu/drm/i915/intel_lrc.c | 15 +- - drivers/gpu/drm/i915/intel_lrc.h | 6 + - drivers/gpu/drm/i915/intel_panel.c | 7 + - drivers/hid/hid-multitouch.c | 22 +- - drivers/misc/Kconfig | 1 + - drivers/misc/Makefile | 1 + - drivers/misc/ipts/Kconfig | 11 + - drivers/misc/ipts/Makefile | 17 + - drivers/misc/ipts/companion/Kconfig | 9 + - drivers/misc/ipts/companion/Makefile | 1 + - drivers/misc/ipts/companion/ipts-surface.c | 100 ++ - drivers/misc/ipts/ipts-binary-spec.h | 118 +++ - drivers/misc/ipts/ipts-dbgfs.c | 364 +++++++ - drivers/misc/ipts/ipts-fw.c | 113 ++ - drivers/misc/ipts/ipts-fw.h | 12 + - drivers/misc/ipts/ipts-gfx.c | 185 ++++ - drivers/misc/ipts/ipts-gfx.h | 24 + - drivers/misc/ipts/ipts-hid.c | 497 +++++++++ - drivers/misc/ipts/ipts-hid.h | 34 + - drivers/misc/ipts/ipts-kernel.c | 1042 +++++++++++++++++++ - drivers/misc/ipts/ipts-kernel.h | 23 + - drivers/misc/ipts/ipts-mei-msgs.h | 585 +++++++++++ - drivers/misc/ipts/ipts-mei.c | 290 ++++++ - drivers/misc/ipts/ipts-msg-handler.c | 437 ++++++++ - drivers/misc/ipts/ipts-msg-handler.h | 33 + - drivers/misc/ipts/ipts-params.c | 21 + - drivers/misc/ipts/ipts-params.h | 14 + - drivers/misc/ipts/ipts-resource.c | 277 +++++ - drivers/misc/ipts/ipts-resource.h | 30 + - drivers/misc/ipts/ipts-sensor-regs.h | 700 +++++++++++++ - drivers/misc/ipts/ipts-state.h | 29 + - drivers/misc/ipts/ipts.h | 200 ++++ - drivers/misc/mei/hw-me-regs.h | 1 + - drivers/misc/mei/pci-me.c | 1 + - include/linux/intel_ipts_fw.h | 14 + - include/linux/intel_ipts_if.h | 76 ++ - 50 files changed, 6172 insertions(+), 26 deletions(-) - create mode 100644 drivers/gpu/drm/i915/intel_ipts.c - create mode 100644 drivers/gpu/drm/i915/intel_ipts.h - create mode 100644 drivers/misc/ipts/Kconfig - create mode 100644 drivers/misc/ipts/Makefile - create mode 100644 drivers/misc/ipts/companion/Kconfig - create mode 100644 drivers/misc/ipts/companion/Makefile - create mode 100644 drivers/misc/ipts/companion/ipts-surface.c - create mode 100644 drivers/misc/ipts/ipts-binary-spec.h - create mode 100644 drivers/misc/ipts/ipts-dbgfs.c - create mode 100644 drivers/misc/ipts/ipts-fw.c - create mode 100644 drivers/misc/ipts/ipts-fw.h - create mode 100644 drivers/misc/ipts/ipts-gfx.c - create mode 100644 drivers/misc/ipts/ipts-gfx.h - create mode 100644 drivers/misc/ipts/ipts-hid.c - create mode 100644 drivers/misc/ipts/ipts-hid.h - create mode 100644 drivers/misc/ipts/ipts-kernel.c - create mode 100644 drivers/misc/ipts/ipts-kernel.h - create mode 100644 drivers/misc/ipts/ipts-mei-msgs.h - create mode 100644 drivers/misc/ipts/ipts-mei.c - create mode 100644 drivers/misc/ipts/ipts-msg-handler.c - create mode 100644 drivers/misc/ipts/ipts-msg-handler.h - create mode 100644 drivers/misc/ipts/ipts-params.c - create mode 100644 drivers/misc/ipts/ipts-params.h - create mode 100644 drivers/misc/ipts/ipts-resource.c - create mode 100644 drivers/misc/ipts/ipts-resource.h - create mode 100644 drivers/misc/ipts/ipts-sensor-regs.h - create mode 100644 drivers/misc/ipts/ipts-state.h - create mode 100644 drivers/misc/ipts/ipts.h - create mode 100644 include/linux/intel_ipts_fw.h - create mode 100644 include/linux/intel_ipts_if.h - -diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile -index fbcb0904f4a8..1a273956b41c 100644 ---- a/drivers/gpu/drm/i915/Makefile -+++ b/drivers/gpu/drm/i915/Makefile -@@ -170,6 +170,9 @@ i915-y += dvo_ch7017.o \ - vlv_dsi_pll.o \ - intel_vdsc.o - -+# intel precise touch & stylus -+i915-y += intel_ipts.o -+ - # Post-mortem debug and GPU hang state capture - i915-$(CONFIG_DRM_I915_CAPTURE_ERROR) += i915_gpu_error.o - i915-$(CONFIG_DRM_I915_SELFTEST) += \ -diff --git a/drivers/gpu/drm/i915/i915_debugfs.c b/drivers/gpu/drm/i915/i915_debugfs.c -index 5823ffb17821..2ffad9712041 100644 ---- a/drivers/gpu/drm/i915/i915_debugfs.c -+++ b/drivers/gpu/drm/i915/i915_debugfs.c -@@ -41,6 +41,7 @@ - #include "intel_hdmi.h" - #include "intel_pm.h" - #include "intel_psr.h" -+#include "intel_ipts.h" - - static inline struct drm_i915_private *node_to_i915(struct drm_info_node *node) - { -@@ -4567,6 +4568,64 @@ static const struct file_operations i915_fifo_underrun_reset_ops = { - .llseek = default_llseek, - }; - -+static ssize_t -+i915_intel_ipts_cleanup_write(struct file *filp, -+ const char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ struct drm_i915_private *dev_priv = filp->private_data; -+ struct drm_device *dev = &dev_priv->drm; -+ int ret; -+ bool flag; -+ -+ ret = kstrtobool_from_user(ubuf, cnt, &flag); -+ if (ret) -+ return ret; -+ -+ if (!flag) -+ return cnt; -+ -+ intel_ipts_cleanup(dev); -+ -+ return cnt; -+} -+ -+static const struct file_operations i915_intel_ipts_cleanup_ops = { -+ .owner = THIS_MODULE, -+ .open = simple_open, -+ .write = i915_intel_ipts_cleanup_write, -+ .llseek = default_llseek, -+}; -+ -+static ssize_t -+i915_intel_ipts_init_write(struct file *filp, -+ const char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ struct drm_i915_private *dev_priv = filp->private_data; -+ struct drm_device *dev = &dev_priv->drm; -+ int ret; -+ bool flag; -+ -+ ret = kstrtobool_from_user(ubuf, cnt, &flag); -+ if (ret) -+ return ret; -+ -+ if (!flag) -+ return cnt; -+ -+ intel_ipts_init(dev); -+ -+ return cnt; -+} -+ -+static const struct file_operations i915_intel_ipts_init_ops = { -+ .owner = THIS_MODULE, -+ .open = simple_open, -+ .write = i915_intel_ipts_init_write, -+ .llseek = default_llseek, -+}; -+ - static const struct drm_info_list i915_debugfs_list[] = { - {"i915_capabilities", i915_capabilities, 0}, - {"i915_gem_objects", i915_gem_object_info, 0}, -@@ -4642,7 +4701,9 @@ static const struct i915_debugfs_files { - {"i915_hpd_short_storm_ctl", &i915_hpd_short_storm_ctl_fops}, - {"i915_ipc_status", &i915_ipc_status_fops}, - {"i915_drrs_ctl", &i915_drrs_ctl_fops}, -- {"i915_edp_psr_debug", &i915_edp_psr_debug_fops} -+ {"i915_edp_psr_debug", &i915_edp_psr_debug_fops}, -+ {"i915_intel_ipts_cleanup", &i915_intel_ipts_cleanup_ops}, -+ {"i915_intel_ipts_init", &i915_intel_ipts_init_ops}, - }; - - int i915_debugfs_register(struct drm_i915_private *dev_priv) -diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c -index d485d49c473b..adb7af18dc2b 100644 ---- a/drivers/gpu/drm/i915/i915_drv.c -+++ b/drivers/gpu/drm/i915/i915_drv.c -@@ -63,6 +63,7 @@ - #include "intel_sprite.h" - #include "intel_uc.h" - #include "intel_workarounds.h" -+#include "intel_ipts.h" - - static struct drm_driver driver; - -@@ -723,6 +724,9 @@ static int i915_load_modeset_init(struct drm_device *dev) - - intel_init_ipc(dev_priv); - -+ if (INTEL_GEN(dev_priv) >= 9 && i915_modparams.enable_guc && i915_modparams.enable_ipts) -+ intel_ipts_init(dev); -+ - return 0; - - cleanup_gem: -@@ -1918,6 +1922,9 @@ void i915_driver_unload(struct drm_device *dev) - - disable_rpm_wakeref_asserts(dev_priv); - -+ if (INTEL_GEN(dev_priv) >= 9 && i915_modparams.enable_guc && i915_modparams.enable_ipts) -+ intel_ipts_cleanup(dev); -+ - i915_driver_unregister(dev_priv); - - /* -diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h -index 066fd2a12851..2a872d8725b5 100644 ---- a/drivers/gpu/drm/i915/i915_drv.h -+++ b/drivers/gpu/drm/i915/i915_drv.h -@@ -3184,6 +3184,9 @@ void i915_gem_object_do_bit_17_swizzle(struct drm_i915_gem_object *obj, - void i915_gem_object_save_bit_17_swizzle(struct drm_i915_gem_object *obj, - struct sg_table *pages); - -+struct i915_gem_context * -+i915_gem_context_create_ipts(struct drm_device *dev); -+ - static inline struct i915_gem_context * - __i915_gem_context_lookup_rcu(struct drm_i915_file_private *file_priv, u32 id) - { -diff --git a/drivers/gpu/drm/i915/i915_gem_context.c b/drivers/gpu/drm/i915/i915_gem_context.c -index dd728b26b5aa..ae3209b79b25 100644 ---- a/drivers/gpu/drm/i915/i915_gem_context.c -+++ b/drivers/gpu/drm/i915/i915_gem_context.c -@@ -565,6 +565,18 @@ static bool needs_preempt_context(struct drm_i915_private *i915) - return HAS_EXECLISTS(i915); - } - -+struct i915_gem_context *i915_gem_context_create_ipts(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct i915_gem_context *ctx; -+ -+ BUG_ON(!mutex_is_locked(&dev->struct_mutex)); -+ -+ ctx = i915_gem_create_context(dev_priv, 0); -+ -+ return ctx; -+} -+ - int i915_gem_contexts_init(struct drm_i915_private *dev_priv) - { - struct i915_gem_context *ctx; -diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c -index b92cfd69134b..78fcd4b78480 100644 ---- a/drivers/gpu/drm/i915/i915_irq.c -+++ b/drivers/gpu/drm/i915/i915_irq.c -@@ -41,6 +41,7 @@ - #include "i915_trace.h" - #include "intel_drv.h" - #include "intel_psr.h" -+#include "intel_ipts.h" - - /** - * DOC: interrupt handling -@@ -1520,6 +1521,9 @@ gen8_cs_irq_handler(struct intel_engine_cs *engine, u32 iir) - tasklet |= intel_engine_needs_breadcrumb_tasklet(engine); - } - -+ if (iir & GT_RENDER_PIPECTL_NOTIFY_INTERRUPT && i915_modparams.enable_ipts) -+ intel_ipts_notify_complete(); -+ - if (tasklet) - tasklet_hi_schedule(&engine->execlists.tasklet); - } -@@ -4055,7 +4059,8 @@ static void gen8_gt_irq_postinstall(struct drm_i915_private *dev_priv) - - /* These are interrupts we'll toggle with the ring mask register */ - u32 gt_interrupts[] = { -- (GT_RENDER_USER_INTERRUPT << GEN8_RCS_IRQ_SHIFT | -+ (GT_RENDER_PIPECTL_NOTIFY_INTERRUPT << GEN8_RCS_IRQ_SHIFT | -+ GT_RENDER_USER_INTERRUPT << GEN8_RCS_IRQ_SHIFT | - GT_CONTEXT_SWITCH_INTERRUPT << GEN8_RCS_IRQ_SHIFT | - GT_RENDER_USER_INTERRUPT << GEN8_BCS_IRQ_SHIFT | - GT_CONTEXT_SWITCH_INTERRUPT << GEN8_BCS_IRQ_SHIFT), -diff --git a/drivers/gpu/drm/i915/i915_params.c b/drivers/gpu/drm/i915/i915_params.c -index b5be0abbba35..831f2bcae687 100644 ---- a/drivers/gpu/drm/i915/i915_params.c -+++ b/drivers/gpu/drm/i915/i915_params.c -@@ -143,7 +143,10 @@ i915_param_named_unsafe(edp_vswing, int, 0400, - i915_param_named_unsafe(enable_guc, int, 0400, - "Enable GuC load for GuC submission and/or HuC load. " - "Required functionality can be selected using bitmask values. " -- "(-1=auto, 0=disable [default], 1=GuC submission, 2=HuC load)"); -+ "(-1=auto [default], 0=disable, 1=GuC submission, 2=HuC load)"); -+ -+i915_param_named_unsafe(enable_ipts, int, 0400, -+ "Enable IPTS Touchscreen and Pen support (default: 1)"); - - i915_param_named(guc_log_level, int, 0400, - "GuC firmware logging level. Requires GuC to be loaded. " -diff --git a/drivers/gpu/drm/i915/i915_params.h b/drivers/gpu/drm/i915/i915_params.h -index 3f14e9881a0d..e314a2414041 100644 ---- a/drivers/gpu/drm/i915/i915_params.h -+++ b/drivers/gpu/drm/i915/i915_params.h -@@ -54,7 +54,7 @@ struct drm_printer; - param(int, disable_power_well, -1) \ - param(int, enable_ips, 1) \ - param(int, invert_brightness, 0) \ -- param(int, enable_guc, 0) \ -+ param(int, enable_guc, -1) \ - param(int, guc_log_level, -1) \ - param(char *, guc_firmware_path, NULL) \ - param(char *, huc_firmware_path, NULL) \ -@@ -76,7 +76,8 @@ struct drm_printer; - param(bool, nuclear_pageflip, false) \ - param(bool, enable_dp_mst, true) \ - param(bool, enable_dpcd_backlight, false) \ -- param(bool, enable_gvt, false) -+ param(bool, enable_gvt, false) \ -+ param(int, enable_ipts, 1) - - #define MEMBER(T, member, ...) T member; - struct i915_params { -diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c -index 560274d1c50b..e305a35de9c2 100644 ---- a/drivers/gpu/drm/i915/intel_dp.c -+++ b/drivers/gpu/drm/i915/intel_dp.c -@@ -2899,8 +2899,8 @@ void intel_dp_sink_dpms(struct intel_dp *intel_dp, int mode) - return; - - if (mode != DRM_MODE_DPMS_ON) { -- if (downstream_hpd_needs_d0(intel_dp)) -- return; -+ //if (downstream_hpd_needs_d0(intel_dp)) -+ // return; - - ret = drm_dp_dpcd_writeb(&intel_dp->aux, DP_SET_POWER, - DP_SET_POWER_D3); -diff --git a/drivers/gpu/drm/i915/intel_guc.h b/drivers/gpu/drm/i915/intel_guc.h -index 2c59ff8d9f39..d7f91693972f 100644 ---- a/drivers/gpu/drm/i915/intel_guc.h -+++ b/drivers/gpu/drm/i915/intel_guc.h -@@ -67,6 +67,7 @@ struct intel_guc { - - struct intel_guc_client *execbuf_client; - struct intel_guc_client *preempt_client; -+ struct intel_guc_client *ipts_client; - - struct guc_preempt_work preempt_work[I915_NUM_ENGINES]; - struct workqueue_struct *preempt_wq; -diff --git a/drivers/gpu/drm/i915/intel_guc_submission.c b/drivers/gpu/drm/i915/intel_guc_submission.c -index 46cd0e70aecb..e84c805f7340 100644 ---- a/drivers/gpu/drm/i915/intel_guc_submission.c -+++ b/drivers/gpu/drm/i915/intel_guc_submission.c -@@ -93,12 +93,17 @@ static inline struct i915_priolist *to_priolist(struct rb_node *rb) - - static inline bool is_high_priority(struct intel_guc_client *client) - { -- return (client->priority == GUC_CLIENT_PRIORITY_KMD_HIGH || -- client->priority == GUC_CLIENT_PRIORITY_HIGH); -+ return (client->priority == GUC_CLIENT_PRIORITY_HIGH); -+} -+ -+static inline bool is_high_priority_kmd(struct intel_guc_client *client) -+{ -+ return (client->priority == GUC_CLIENT_PRIORITY_KMD_HIGH); - } - - static int reserve_doorbell(struct intel_guc_client *client) - { -+ struct drm_i915_private *dev_priv = guc_to_i915(client->guc); - unsigned long offset; - unsigned long end; - u16 id; -@@ -111,10 +116,14 @@ static int reserve_doorbell(struct intel_guc_client *client) - * priority contexts, the second half for high-priority ones. - */ - offset = 0; -- end = GUC_NUM_DOORBELLS / 2; -- if (is_high_priority(client)) { -- offset = end; -- end += offset; -+ if (IS_SKYLAKE(dev_priv) || IS_KABYLAKE(dev_priv)) { -+ end = GUC_NUM_DOORBELLS; -+ } else { -+ end = GUC_NUM_DOORBELLS/2; -+ if (is_high_priority(client)) { -+ offset = end; -+ end += offset; -+ } - } - - id = find_next_zero_bit(client->guc->doorbell_bitmap, end, offset); -@@ -372,9 +381,15 @@ static void guc_stage_desc_init(struct intel_guc_client *client) - desc = __get_stage_desc(client); - memset(desc, 0, sizeof(*desc)); - -- desc->attribute = GUC_STAGE_DESC_ATTR_ACTIVE | -- GUC_STAGE_DESC_ATTR_KERNEL; -- if (is_high_priority(client)) -+ desc->attribute = GUC_STAGE_DESC_ATTR_ACTIVE; -+ if ((client->priority == GUC_CLIENT_PRIORITY_KMD_NORMAL) || -+ (client->priority == GUC_CLIENT_PRIORITY_KMD_HIGH)) { -+ desc->attribute |= GUC_STAGE_DESC_ATTR_KERNEL; -+ } else { -+ desc->attribute |= GUC_STAGE_DESC_ATTR_PCH; -+ } -+ -+ if (is_high_priority_kmd(client)) - desc->attribute |= GUC_STAGE_DESC_ATTR_PREEMPT; - desc->stage_id = client->stage_id; - desc->priority = client->priority; -@@ -1302,7 +1317,8 @@ static void guc_interrupts_capture(struct drm_i915_private *dev_priv) - I915_WRITE(RING_MODE_GEN7(engine), irqs); - - /* route USER_INTERRUPT to Host, all others are sent to GuC. */ -- irqs = GT_RENDER_USER_INTERRUPT << GEN8_RCS_IRQ_SHIFT | -+ irqs = (GT_RENDER_USER_INTERRUPT | GT_RENDER_PIPECTL_NOTIFY_INTERRUPT) -+ << GEN8_RCS_IRQ_SHIFT | - GT_RENDER_USER_INTERRUPT << GEN8_BCS_IRQ_SHIFT; - /* These three registers have the same bit definitions */ - I915_WRITE(GUC_BCS_RCS_IER, ~irqs); -@@ -1449,6 +1465,59 @@ void intel_guc_submission_disable(struct intel_guc *guc) - guc_clients_disable(guc); - } - -+int i915_guc_ipts_submission_enable(struct drm_i915_private *dev_priv, -+ struct i915_gem_context *ctx) -+{ -+ struct intel_guc *guc = &dev_priv->guc; -+ struct intel_guc_client *client; -+ int err; -+ int ret; -+ -+ /* client for execbuf submission */ -+ client = guc_client_alloc(dev_priv, -+ INTEL_INFO(dev_priv)->engine_mask, -+ IS_SKYLAKE(dev_priv) || IS_KABYLAKE(dev_priv) ? GUC_CLIENT_PRIORITY_HIGH : GUC_CLIENT_PRIORITY_NORMAL, -+ ctx); -+ if (IS_ERR(client)) { -+ DRM_ERROR("Failed to create normal GuC client!\n"); -+ return -ENOMEM; -+ } -+ -+ guc->ipts_client = client; -+ -+ err = intel_guc_sample_forcewake(guc); -+ if (err) -+ return err; -+ -+ ret = __guc_client_enable(guc->ipts_client); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+void i915_guc_ipts_submission_disable(struct drm_i915_private *dev_priv) -+{ -+ struct intel_guc *guc = &dev_priv->guc; -+ -+ if (!guc->ipts_client) -+ return; -+ -+ __guc_client_disable(guc->ipts_client); -+ guc_client_free(guc->ipts_client); -+ guc->ipts_client = NULL; -+} -+ -+void i915_guc_ipts_reacquire_doorbell(struct drm_i915_private *dev_priv) -+{ -+ struct intel_guc *guc = &dev_priv->guc; -+ -+ int err = __guc_allocate_doorbell(guc, guc->ipts_client->stage_id); -+ -+ if (err) -+ DRM_ERROR("Not able to reacquire IPTS doorbell\n"); -+} -+ - #if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) - #include "selftests/intel_guc.c" - #endif -diff --git a/drivers/gpu/drm/i915/intel_guc_submission.h b/drivers/gpu/drm/i915/intel_guc_submission.h -index aa5e6749c925..c9e5c14e7f67 100644 ---- a/drivers/gpu/drm/i915/intel_guc_submission.h -+++ b/drivers/gpu/drm/i915/intel_guc_submission.h -@@ -84,5 +84,9 @@ void intel_guc_submission_disable(struct intel_guc *guc); - void intel_guc_submission_fini(struct intel_guc *guc); - int intel_guc_preempt_work_create(struct intel_guc *guc); - void intel_guc_preempt_work_destroy(struct intel_guc *guc); -+int i915_guc_ipts_submission_enable(struct drm_i915_private *dev_priv, -+ struct i915_gem_context *ctx); -+void i915_guc_ipts_submission_disable(struct drm_i915_private *dev_priv); -+void i915_guc_ipts_reacquire_doorbell(struct drm_i915_private *dev_priv); - - #endif -diff --git a/drivers/gpu/drm/i915/intel_ipts.c b/drivers/gpu/drm/i915/intel_ipts.c -new file mode 100644 -index 000000000000..3d3c353986f7 ---- /dev/null -+++ b/drivers/gpu/drm/i915/intel_ipts.c -@@ -0,0 +1,651 @@ -+/* -+ * Copyright 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+#include -+#include -+#include -+#include -+#include -+ -+#include "intel_guc_submission.h" -+#include "i915_drv.h" -+ -+#define SUPPORTED_IPTS_INTERFACE_VERSION 1 -+ -+#define REACQUIRE_DB_THRESHOLD 10 -+#define DB_LOST_CHECK_STEP1_INTERVAL 2500 /* ms */ -+#define DB_LOST_CHECK_STEP2_INTERVAL 1000 /* ms */ -+ -+/* intel IPTS ctx for ipts support */ -+typedef struct intel_ipts { -+ struct drm_device *dev; -+ struct i915_gem_context *ipts_context; -+ intel_ipts_callback_t ipts_clbks; -+ -+ /* buffers' list */ -+ struct { -+ spinlock_t lock; -+ struct list_head list; -+ } buffers; -+ -+ void *data; -+ -+ struct delayed_work reacquire_db_work; -+ intel_ipts_wq_info_t wq_info; -+ u32 old_tail; -+ u32 old_head; -+ bool need_reacquire_db; -+ -+ bool connected; -+ bool initialized; -+} intel_ipts_t; -+ -+intel_ipts_t intel_ipts; -+ -+typedef struct intel_ipts_object { -+ struct list_head list; -+ struct drm_i915_gem_object *gem_obj; -+ void *cpu_addr; -+} intel_ipts_object_t; -+ -+static intel_ipts_object_t *ipts_object_create(size_t size, u32 flags) -+{ -+ struct drm_i915_private *dev_priv = to_i915(intel_ipts.dev); -+ intel_ipts_object_t *obj = NULL; -+ struct drm_i915_gem_object *gem_obj = NULL; -+ int ret = 0; -+ -+ obj = kzalloc(sizeof(*obj), GFP_KERNEL); -+ if (!obj) -+ return NULL; -+ -+ size = roundup(size, PAGE_SIZE); -+ if (size == 0) { -+ ret = -EINVAL; -+ goto err_out; -+ } -+ -+ /* Allocate the new object */ -+ gem_obj = i915_gem_object_create(dev_priv, size); -+ if (gem_obj == NULL) { -+ ret = -ENOMEM; -+ goto err_out; -+ } -+ -+ if (flags & IPTS_BUF_FLAG_CONTIGUOUS) { -+ ret = i915_gem_object_attach_phys(gem_obj, PAGE_SIZE); -+ if (ret) { -+ pr_info(">> ipts no contiguous : %d\n", ret); -+ goto err_out; -+ } -+ } -+ -+ obj->gem_obj = gem_obj; -+ -+ spin_lock(&intel_ipts.buffers.lock); -+ list_add_tail(&obj->list, &intel_ipts.buffers.list); -+ spin_unlock(&intel_ipts.buffers.lock); -+ -+ return obj; -+ -+err_out: -+ if (gem_obj) -+ i915_gem_free_object(&gem_obj->base); -+ -+ if (obj) -+ kfree(obj); -+ -+ return NULL; -+} -+ -+static void ipts_object_free(intel_ipts_object_t* obj) -+{ -+ spin_lock(&intel_ipts.buffers.lock); -+ list_del(&obj->list); -+ spin_unlock(&intel_ipts.buffers.lock); -+ -+ i915_gem_free_object(&obj->gem_obj->base); -+ kfree(obj); -+} -+ -+static int ipts_object_pin(intel_ipts_object_t* obj, -+ struct i915_gem_context *ipts_ctx) -+{ -+ struct i915_address_space *vm = NULL; -+ struct i915_vma *vma = NULL; -+ struct drm_i915_private *dev_priv = to_i915(intel_ipts.dev); -+ int ret = 0; -+ -+ if (ipts_ctx->ppgtt) { -+ vm = &ipts_ctx->ppgtt->vm; -+ } else { -+ vm = &dev_priv->ggtt.vm; -+ } -+ -+ vma = i915_vma_instance(obj->gem_obj, vm, NULL); -+ if (IS_ERR(vma)) { -+ DRM_ERROR("cannot find or create vma\n"); -+ return -1; -+ } -+ -+ ret = i915_vma_pin(vma, 0, PAGE_SIZE, PIN_USER); -+ -+ return ret; -+} -+ -+static void ipts_object_unpin(intel_ipts_object_t *obj) -+{ -+ /* TBD: Add support */ -+} -+ -+static void* ipts_object_map(intel_ipts_object_t *obj) -+{ -+ -+ return i915_gem_object_pin_map(obj->gem_obj, I915_MAP_WB); -+} -+ -+static void ipts_object_unmap(intel_ipts_object_t* obj) -+{ -+ i915_gem_object_unpin_map(obj->gem_obj); -+ obj->cpu_addr = NULL; -+} -+ -+static int create_ipts_context(void) -+{ -+ struct i915_gem_context *ipts_ctx = NULL; -+ struct drm_i915_private *dev_priv = to_i915(intel_ipts.dev); -+ struct intel_context *ce = NULL; -+ int ret = 0; -+ -+ /* Initialize the context right away.*/ -+ ret = i915_mutex_lock_interruptible(intel_ipts.dev); -+ if (ret) { -+ DRM_ERROR("i915_mutex_lock_interruptible failed \n"); -+ return ret; -+ } -+ -+ ipts_ctx = i915_gem_context_create_ipts(intel_ipts.dev); -+ if (IS_ERR(ipts_ctx)) { -+ DRM_ERROR("Failed to create IPTS context (error %ld)\n", -+ PTR_ERR(ipts_ctx)); -+ ret = PTR_ERR(ipts_ctx); -+ goto err_unlock; -+ } -+ -+ ce = intel_context_pin(ipts_ctx, dev_priv->engine[RCS0]); -+ if (IS_ERR(ce)) { -+ DRM_ERROR("Failed to create intel context (error %ld)\n", -+ PTR_ERR(ce)); -+ ret = PTR_ERR(ce); -+ goto err_unlock; -+ } -+ -+ ret = execlists_context_deferred_alloc(ce, ce->engine); -+ if (ret) { -+ DRM_DEBUG("lr context allocation failed : %d\n", ret); -+ goto err_ctx; -+ } -+ -+ ret = execlists_context_pin(ce); -+ if (ret) { -+ DRM_DEBUG("lr context pinning failed : %d\n", ret); -+ goto err_ctx; -+ } -+ -+ /* Release the mutex */ -+ mutex_unlock(&intel_ipts.dev->struct_mutex); -+ -+ spin_lock_init(&intel_ipts.buffers.lock); -+ INIT_LIST_HEAD(&intel_ipts.buffers.list); -+ -+ intel_ipts.ipts_context = ipts_ctx; -+ -+ return 0; -+ -+err_ctx: -+ if (ipts_ctx) -+ i915_gem_context_put(ipts_ctx); -+ -+err_unlock: -+ mutex_unlock(&intel_ipts.dev->struct_mutex); -+ -+ return ret; -+} -+ -+static void destroy_ipts_context(void) -+{ -+ struct i915_gem_context *ipts_ctx = NULL; -+ struct drm_i915_private *dev_priv = to_i915(intel_ipts.dev); -+ struct intel_context *ce = NULL; -+ int ret = 0; -+ -+ ipts_ctx = intel_ipts.ipts_context; -+ -+ ce = intel_context_lookup(ipts_ctx, dev_priv->engine[RCS0]); -+ -+ /* Initialize the context right away.*/ -+ ret = i915_mutex_lock_interruptible(intel_ipts.dev); -+ if (ret) { -+ DRM_ERROR("i915_mutex_lock_interruptible failed \n"); -+ return; -+ } -+ -+ execlists_context_unpin(ce); -+ intel_context_unpin(ce); -+ i915_gem_context_put(ipts_ctx); -+ -+ mutex_unlock(&intel_ipts.dev->struct_mutex); -+} -+ -+int intel_ipts_notify_complete(void) -+{ -+ if (intel_ipts.ipts_clbks.workload_complete) -+ intel_ipts.ipts_clbks.workload_complete(intel_ipts.data); -+ -+ return 0; -+} -+ -+int intel_ipts_notify_backlight_status(bool backlight_on) -+{ -+ if (intel_ipts.ipts_clbks.notify_gfx_status) { -+ if (backlight_on) { -+ intel_ipts.ipts_clbks.notify_gfx_status( -+ IPTS_NOTIFY_STA_BACKLIGHT_ON, -+ intel_ipts.data); -+ schedule_delayed_work(&intel_ipts.reacquire_db_work, -+ msecs_to_jiffies(DB_LOST_CHECK_STEP1_INTERVAL)); -+ } else { -+ intel_ipts.ipts_clbks.notify_gfx_status( -+ IPTS_NOTIFY_STA_BACKLIGHT_OFF, -+ intel_ipts.data); -+ cancel_delayed_work(&intel_ipts.reacquire_db_work); -+ } -+ } -+ -+ return 0; -+} -+ -+static void intel_ipts_reacquire_db(intel_ipts_t *intel_ipts_p) -+{ -+ int ret = 0; -+ -+ ret = i915_mutex_lock_interruptible(intel_ipts_p->dev); -+ if (ret) { -+ DRM_ERROR("i915_mutex_lock_interruptible failed \n"); -+ return; -+ } -+ -+ /* Reacquire the doorbell */ -+ i915_guc_ipts_reacquire_doorbell(intel_ipts_p->dev->dev_private); -+ -+ mutex_unlock(&intel_ipts_p->dev->struct_mutex); -+ -+ return; -+} -+ -+static int intel_ipts_get_wq_info(uint64_t gfx_handle, -+ intel_ipts_wq_info_t *wq_info) -+{ -+ if (gfx_handle != (uint64_t)&intel_ipts) { -+ DRM_ERROR("invalid gfx handle\n"); -+ return -EINVAL; -+ } -+ -+ *wq_info = intel_ipts.wq_info; -+ -+ intel_ipts_reacquire_db(&intel_ipts); -+ schedule_delayed_work(&intel_ipts.reacquire_db_work, -+ msecs_to_jiffies(DB_LOST_CHECK_STEP1_INTERVAL)); -+ -+ return 0; -+} -+ -+static int set_wq_info(void) -+{ -+ struct drm_i915_private *dev_priv = to_i915(intel_ipts.dev); -+ struct intel_guc *guc = &dev_priv->guc; -+ struct intel_guc_client *client; -+ struct guc_process_desc *desc; -+ void *base = NULL; -+ intel_ipts_wq_info_t *wq_info; -+ u64 phy_base = 0; -+ -+ wq_info = &intel_ipts.wq_info; -+ -+ client = guc->ipts_client; -+ if (!client) { -+ DRM_ERROR("IPTS GuC client is NOT available\n"); -+ return -EINVAL; -+ } -+ -+ base = client->vaddr; -+ desc = (struct guc_process_desc *)((u64)base + client->proc_desc_offset); -+ -+ desc->wq_base_addr = (u64)base + GUC_DB_SIZE; -+ desc->db_base_addr = (u64)base + client->doorbell_offset; -+ -+ /* IPTS expects physical addresses to pass it to ME */ -+ phy_base = sg_dma_address(client->vma->pages->sgl); -+ -+ wq_info->db_addr = desc->db_base_addr; -+ wq_info->db_phy_addr = phy_base + client->doorbell_offset; -+ wq_info->db_cookie_offset = offsetof(struct guc_doorbell_info, cookie); -+ wq_info->wq_addr = desc->wq_base_addr; -+ wq_info->wq_phy_addr = phy_base + GUC_DB_SIZE; -+ wq_info->wq_head_addr = (u64)&desc->head; -+ wq_info->wq_head_phy_addr = phy_base + client->proc_desc_offset + -+ offsetof(struct guc_process_desc, head); -+ wq_info->wq_tail_addr = (u64)&desc->tail; -+ wq_info->wq_tail_phy_addr = phy_base + client->proc_desc_offset + -+ offsetof(struct guc_process_desc, tail); -+ wq_info->wq_size = desc->wq_size_bytes; -+ -+ return 0; -+} -+ -+static int intel_ipts_init_wq(void) -+{ -+ int ret = 0; -+ -+ ret = i915_mutex_lock_interruptible(intel_ipts.dev); -+ if (ret) { -+ DRM_ERROR("i915_mutex_lock_interruptible failed\n"); -+ return ret; -+ } -+ -+ /* disable IPTS submission */ -+ i915_guc_ipts_submission_disable(intel_ipts.dev->dev_private); -+ -+ /* enable IPTS submission */ -+ ret = i915_guc_ipts_submission_enable(intel_ipts.dev->dev_private, -+ intel_ipts.ipts_context); -+ if (ret) { -+ DRM_ERROR("i915_guc_ipts_submission_enable failed : %d\n", ret); -+ goto out; -+ } -+ -+ ret = set_wq_info(); -+ if (ret) { -+ DRM_ERROR("set_wq_info failed\n"); -+ goto out; -+ } -+ -+out: -+ mutex_unlock(&intel_ipts.dev->struct_mutex); -+ -+ return ret; -+} -+ -+static void intel_ipts_release_wq(void) -+{ -+ int ret = 0; -+ -+ ret = i915_mutex_lock_interruptible(intel_ipts.dev); -+ if (ret) { -+ DRM_ERROR("i915_mutex_lock_interruptible failed\n"); -+ return; -+ } -+ -+ /* disable IPTS submission */ -+ i915_guc_ipts_submission_disable(intel_ipts.dev->dev_private); -+ -+ mutex_unlock(&intel_ipts.dev->struct_mutex); -+} -+ -+static int intel_ipts_map_buffer(u64 gfx_handle, intel_ipts_mapbuffer_t *mapbuf) -+{ -+ intel_ipts_object_t* obj; -+ struct i915_gem_context *ipts_ctx = NULL; -+ struct drm_i915_private *dev_priv = to_i915(intel_ipts.dev); -+ struct i915_address_space *vm = NULL; -+ struct i915_vma *vma = NULL; -+ int ret = 0; -+ -+ if (gfx_handle != (uint64_t)&intel_ipts) { -+ DRM_ERROR("invalid gfx handle\n"); -+ return -EINVAL; -+ } -+ -+ /* Acquire mutex first */ -+ ret = i915_mutex_lock_interruptible(intel_ipts.dev); -+ if (ret) { -+ DRM_ERROR("i915_mutex_lock_interruptible failed \n"); -+ return -EINVAL; -+ } -+ -+ obj = ipts_object_create(mapbuf->size, mapbuf->flags); -+ if (!obj) -+ return -ENOMEM; -+ -+ ipts_ctx = intel_ipts.ipts_context; -+ ret = ipts_object_pin(obj, ipts_ctx); -+ if (ret) { -+ DRM_ERROR("Not able to pin iTouch obj\n"); -+ ipts_object_free(obj); -+ mutex_unlock(&intel_ipts.dev->struct_mutex); -+ return -ENOMEM; -+ } -+ -+ if (mapbuf->flags & IPTS_BUF_FLAG_CONTIGUOUS) { -+ obj->cpu_addr = obj->gem_obj->phys_handle->vaddr; -+ } else { -+ obj->cpu_addr = ipts_object_map(obj); -+ } -+ -+ if (ipts_ctx->ppgtt) { -+ vm = &ipts_ctx->ppgtt->vm; -+ } else { -+ vm = &dev_priv->ggtt.vm; -+ } -+ -+ vma = i915_vma_instance(obj->gem_obj, vm, NULL); -+ if (IS_ERR(vma)) { -+ DRM_ERROR("cannot find or create vma\n"); -+ return -EINVAL; -+ } -+ -+ mapbuf->gfx_addr = (void*)vma->node.start; -+ mapbuf->cpu_addr = (void*)obj->cpu_addr; -+ mapbuf->buf_handle = (u64)obj; -+ if (mapbuf->flags & IPTS_BUF_FLAG_CONTIGUOUS) { -+ mapbuf->phy_addr = (u64)obj->gem_obj->phys_handle->busaddr; -+ } -+ -+ /* Release the mutex */ -+ mutex_unlock(&intel_ipts.dev->struct_mutex); -+ -+ return 0; -+} -+ -+static int intel_ipts_unmap_buffer(uint64_t gfx_handle, uint64_t buf_handle) -+{ -+ intel_ipts_object_t* obj = (intel_ipts_object_t*)buf_handle; -+ -+ if (gfx_handle != (uint64_t)&intel_ipts) { -+ DRM_ERROR("invalid gfx handle\n"); -+ return -EINVAL; -+ } -+ -+ if (!obj->gem_obj->phys_handle) -+ ipts_object_unmap(obj); -+ ipts_object_unpin(obj); -+ ipts_object_free(obj); -+ -+ return 0; -+} -+ -+int intel_ipts_connect(intel_ipts_connect_t *ipts_connect) -+{ -+ u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; -+ struct drm_i915_private *dev_priv = to_i915(intel_ipts.dev); -+ -+ if (!intel_ipts.initialized) -+ return -EIO; -+ -+ if (!ipts_connect) -+ return -EINVAL; -+ -+ if (ipts_connect->if_version > SUPPORTED_IPTS_INTERFACE_VERSION) -+ return -EINVAL; -+ -+ /* set up device-link for PM */ -+ if (!device_link_add(ipts_connect->client, intel_ipts.dev->dev, flags)) -+ return -EFAULT; -+ -+ /* return gpu operations for ipts */ -+ ipts_connect->ipts_ops.get_wq_info = intel_ipts_get_wq_info; -+ ipts_connect->ipts_ops.map_buffer = intel_ipts_map_buffer; -+ ipts_connect->ipts_ops.unmap_buffer = intel_ipts_unmap_buffer; -+ ipts_connect->gfx_version = INTEL_INFO(dev_priv)->gen; -+ ipts_connect->gfx_handle = (uint64_t)&intel_ipts; -+ -+ /* save callback and data */ -+ intel_ipts.data = ipts_connect->data; -+ intel_ipts.ipts_clbks = ipts_connect->ipts_cb; -+ -+ intel_ipts.connected = true; -+ -+ return 0; -+} -+EXPORT_SYMBOL_GPL(intel_ipts_connect); -+ -+void intel_ipts_disconnect(uint64_t gfx_handle) -+{ -+ if (!intel_ipts.initialized) -+ return; -+ -+ if (gfx_handle != (uint64_t)&intel_ipts || -+ intel_ipts.connected == false) { -+ DRM_ERROR("invalid gfx handle\n"); -+ return; -+ } -+ -+ intel_ipts.data = 0; -+ memset(&intel_ipts.ipts_clbks, 0, sizeof(intel_ipts_callback_t)); -+ -+ intel_ipts.connected = false; -+} -+EXPORT_SYMBOL_GPL(intel_ipts_disconnect); -+ -+static void reacquire_db_work_func(struct work_struct *work) -+{ -+ struct delayed_work *d_work = container_of(work, struct delayed_work, -+ work); -+ intel_ipts_t *intel_ipts_p = container_of(d_work, intel_ipts_t, -+ reacquire_db_work); -+ u32 head; -+ u32 tail; -+ u32 size; -+ u32 load; -+ -+ head = *(u32*)intel_ipts_p->wq_info.wq_head_addr; -+ tail = *(u32*)intel_ipts_p->wq_info.wq_tail_addr; -+ size = intel_ipts_p->wq_info.wq_size; -+ -+ if (head >= tail) -+ load = head - tail; -+ else -+ load = head + size - tail; -+ -+ if (load < REACQUIRE_DB_THRESHOLD) { -+ intel_ipts_p->need_reacquire_db = false; -+ goto reschedule_work; -+ } -+ -+ if (intel_ipts_p->need_reacquire_db) { -+ if (intel_ipts_p->old_head == head && intel_ipts_p->old_tail == tail) -+ intel_ipts_reacquire_db(intel_ipts_p); -+ intel_ipts_p->need_reacquire_db = false; -+ } else { -+ intel_ipts_p->old_head = head; -+ intel_ipts_p->old_tail = tail; -+ intel_ipts_p->need_reacquire_db = true; -+ -+ /* recheck */ -+ schedule_delayed_work(&intel_ipts_p->reacquire_db_work, -+ msecs_to_jiffies(DB_LOST_CHECK_STEP2_INTERVAL)); -+ return; -+ } -+ -+reschedule_work: -+ schedule_delayed_work(&intel_ipts_p->reacquire_db_work, -+ msecs_to_jiffies(DB_LOST_CHECK_STEP1_INTERVAL)); -+} -+ -+/** -+ * intel_ipts_init - Initialize ipts support -+ * @dev: drm device -+ * -+ * Setup the required structures for ipts. -+ */ -+int intel_ipts_init(struct drm_device *dev) -+{ -+ int ret = 0; -+ -+ pr_info("ipts: initializing ipts\n"); -+ -+ intel_ipts.dev = dev; -+ INIT_DELAYED_WORK(&intel_ipts.reacquire_db_work, reacquire_db_work_func); -+ -+ ret = create_ipts_context(); -+ if (ret) -+ return -ENOMEM; -+ -+ ret = intel_ipts_init_wq(); -+ if (ret) -+ return ret; -+ -+ intel_ipts.initialized = true; -+ pr_info("ipts: Intel iTouch framework initialized\n"); -+ -+ return ret; -+} -+ -+void intel_ipts_cleanup(struct drm_device *dev) -+{ -+ intel_ipts_object_t *obj, *n; -+ -+ if (intel_ipts.dev == dev) { -+ list_for_each_entry_safe(obj, n, &intel_ipts.buffers.list, list) { -+ struct i915_vma *vma, *vn; -+ -+ list_for_each_entry_safe(vma, vn, -+ &obj->list, obj_link) { -+ vma->flags &= ~I915_VMA_PIN_MASK; -+ i915_vma_destroy(vma); -+ } -+ -+ list_del(&obj->list); -+ -+ if (!obj->gem_obj->phys_handle) -+ ipts_object_unmap(obj); -+ ipts_object_unpin(obj); -+ i915_gem_free_object(&obj->gem_obj->base); -+ kfree(obj); -+ } -+ -+ intel_ipts_release_wq(); -+ destroy_ipts_context(); -+ cancel_delayed_work(&intel_ipts.reacquire_db_work); -+ } -+} -diff --git a/drivers/gpu/drm/i915/intel_ipts.h b/drivers/gpu/drm/i915/intel_ipts.h -new file mode 100644 -index 000000000000..a6965d102417 ---- /dev/null -+++ b/drivers/gpu/drm/i915/intel_ipts.h -@@ -0,0 +1,34 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+#ifndef _INTEL_IPTS_H_ -+#define _INTEL_IPTS_H_ -+ -+struct drm_device; -+ -+int intel_ipts_init(struct drm_device *dev); -+void intel_ipts_cleanup(struct drm_device *dev); -+int intel_ipts_notify_backlight_status(bool backlight_on); -+int intel_ipts_notify_complete(void); -+ -+#endif //_INTEL_IPTS_H_ -diff --git a/drivers/gpu/drm/i915/intel_lrc.c b/drivers/gpu/drm/i915/intel_lrc.c -index 11e5a86610bf..4adf38cad6da 100644 ---- a/drivers/gpu/drm/i915/intel_lrc.c -+++ b/drivers/gpu/drm/i915/intel_lrc.c -@@ -166,8 +166,8 @@ - - #define ACTIVE_PRIORITY (I915_PRIORITY_NOSEMAPHORE) - --static int execlists_context_deferred_alloc(struct intel_context *ce, -- struct intel_engine_cs *engine); -+int execlists_context_deferred_alloc(struct intel_context *ce, -+ struct intel_engine_cs *engine); - static void execlists_init_reg_state(u32 *reg_state, - struct intel_context *ce, - struct intel_engine_cs *engine, -@@ -1183,7 +1183,7 @@ static void __context_unpin(struct i915_vma *vma) - __i915_vma_unpin(vma); - } - --static void execlists_context_unpin(struct intel_context *ce) -+void execlists_context_unpin(struct intel_context *ce) - { - struct intel_engine_cs *engine; - -@@ -1285,7 +1285,7 @@ __execlists_context_pin(struct intel_context *ce, - return ret; - } - --static int execlists_context_pin(struct intel_context *ce) -+int execlists_context_pin(struct intel_context *ce) - { - return __execlists_context_pin(ce, ce->engine); - } -@@ -2520,6 +2520,9 @@ int logical_render_ring_init(struct intel_engine_cs *engine) - engine->emit_flush = gen8_emit_flush_render; - engine->emit_fini_breadcrumb = gen8_emit_fini_breadcrumb_rcs; - -+ engine->irq_keep_mask |= GT_RENDER_PIPECTL_NOTIFY_INTERRUPT -+ << GEN8_RCS_IRQ_SHIFT; -+ - ret = logical_ring_init(engine); - if (ret) - return ret; -@@ -2881,8 +2884,8 @@ static struct i915_timeline *get_timeline(struct i915_gem_context *ctx) - return i915_timeline_create(ctx->i915, NULL); - } - --static int execlists_context_deferred_alloc(struct intel_context *ce, -- struct intel_engine_cs *engine) -+int execlists_context_deferred_alloc(struct intel_context *ce, -+ struct intel_engine_cs *engine) - { - struct drm_i915_gem_object *ctx_obj; - struct i915_vma *vma; -diff --git a/drivers/gpu/drm/i915/intel_lrc.h b/drivers/gpu/drm/i915/intel_lrc.h -index 84aa230ea27b..0e8008eb0f3a 100644 ---- a/drivers/gpu/drm/i915/intel_lrc.h -+++ b/drivers/gpu/drm/i915/intel_lrc.h -@@ -115,6 +115,12 @@ void intel_execlists_show_requests(struct intel_engine_cs *engine, - const char *prefix), - unsigned int max); - -+int execlists_context_pin(struct intel_context *ce); -+void execlists_context_unpin(struct intel_context *ce); -+int execlists_context_deferred_alloc(struct intel_context *ce, -+ struct intel_engine_cs *engine); -+ -+ - u32 gen8_make_rpcs(struct drm_i915_private *i915, struct intel_sseu *ctx_sseu); - - #endif /* _INTEL_LRC_H_ */ -diff --git a/drivers/gpu/drm/i915/intel_panel.c b/drivers/gpu/drm/i915/intel_panel.c -index 4ab4ce6569e7..2d3c523ba5c7 100644 ---- a/drivers/gpu/drm/i915/intel_panel.c -+++ b/drivers/gpu/drm/i915/intel_panel.c -@@ -37,6 +37,7 @@ - #include "intel_connector.h" - #include "intel_drv.h" - #include "intel_panel.h" -+#include "intel_ipts.h" - - #define CRC_PMIC_PWM_PERIOD_NS 21333 - -@@ -730,6 +731,9 @@ static void lpt_disable_backlight(const struct drm_connector_state *old_conn_sta - struct drm_i915_private *dev_priv = to_i915(connector->base.dev); - u32 tmp; - -+ if (INTEL_GEN(dev_priv) >= 9 && i915_modparams.enable_guc && i915_modparams.enable_ipts) -+ intel_ipts_notify_backlight_status(false); -+ - intel_panel_actually_set_backlight(old_conn_state, 0); - - /* -@@ -917,6 +921,9 @@ static void lpt_enable_backlight(const struct intel_crtc_state *crtc_state, - - /* This won't stick until the above enable. */ - intel_panel_actually_set_backlight(conn_state, panel->backlight.level); -+ -+ if (INTEL_GEN(dev_priv) >= 9 && i915_modparams.enable_guc && i915_modparams.enable_ipts) -+ intel_ipts_notify_backlight_status(true); - } - - static void pch_enable_backlight(const struct intel_crtc_state *crtc_state, -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index b603c14d043b..03448d3a29f2 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -169,6 +169,7 @@ struct mt_device { - static void mt_post_parse_default_settings(struct mt_device *td, - struct mt_application *app); - static void mt_post_parse(struct mt_device *td, struct mt_application *app); -+static int cc_seen = 0; - - /* classes of device behavior */ - #define MT_CLS_DEFAULT 0x0001 -@@ -795,8 +796,11 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi, - app->scantime_logical_max = field->logical_maximum; - return 1; - case HID_DG_CONTACTCOUNT: -- app->have_contact_count = true; -- app->raw_cc = &field->value[usage->usage_index]; -+ if(cc_seen != 1) { -+ app->have_contact_count = true; -+ app->raw_cc = &field->value[usage->usage_index]; -+ cc_seen++; -+ } - return 1; - case HID_DG_AZIMUTH: - /* -@@ -1286,9 +1290,11 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, - field->application != HID_DG_TOUCHSCREEN && - field->application != HID_DG_PEN && - field->application != HID_DG_TOUCHPAD && -+ field->application != HID_GD_MOUSE && - field->application != HID_GD_KEYBOARD && - field->application != HID_GD_SYSTEM_CONTROL && - field->application != HID_CP_CONSUMER_CONTROL && -+ field->logical != HID_DG_TOUCHSCREEN && - field->application != HID_GD_WIRELESS_RADIO_CTLS && - field->application != HID_GD_SYSTEM_MULTIAXIS && - !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && -@@ -1340,6 +1346,14 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, - struct mt_device *td = hid_get_drvdata(hdev); - struct mt_report_data *rdata; - -+ if (field->application == HID_DG_TOUCHSCREEN || -+ field->application == HID_DG_TOUCHPAD) { -+ if (usage->type == EV_KEY || usage->type == EV_ABS) -+ set_bit(usage->type, hi->input->evbit); -+ -+ return -1; -+ } -+ - rdata = mt_find_report_data(td, field->report); - if (rdata && rdata->is_mt_collection) { - /* We own these mappings, tell hid-input to ignore them */ -@@ -1551,12 +1565,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) - /* already handled by hid core */ - break; - case HID_DG_TOUCHSCREEN: -- /* we do not set suffix = "Touchscreen" */ -+ suffix = "Touchscreen"; - hi->input->name = hdev->name; - break; - case HID_DG_STYLUS: - /* force BTN_STYLUS to allow tablet matching in udev */ - __set_bit(BTN_STYLUS, hi->input->keybit); -+ __set_bit(INPUT_PROP_DIRECT, hi->input->propbit); - break; - case HID_VD_ASUS_CUSTOM_MEDIA_KEYS: - suffix = "Custom Media Keys"; -@@ -1672,6 +1687,7 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - td->hdev = hdev; - td->mtclass = *mtclass; - td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; -+ cc_seen = 0; - hid_set_drvdata(hdev, td); - - INIT_LIST_HEAD(&td->applications); -diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index 85fc77148d19..b697f05eaf31 100644 ---- a/drivers/misc/Kconfig -+++ b/drivers/misc/Kconfig -@@ -500,6 +500,7 @@ source "drivers/misc/ti-st/Kconfig" - source "drivers/misc/lis3lv02d/Kconfig" - source "drivers/misc/altera-stapl/Kconfig" - source "drivers/misc/mei/Kconfig" -+source "drivers/misc/ipts/Kconfig" - source "drivers/misc/vmw_vmci/Kconfig" - source "drivers/misc/mic/Kconfig" - source "drivers/misc/genwqe/Kconfig" -diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index b9affcdaa3d6..e681e345a9ed 100644 ---- a/drivers/misc/Makefile -+++ b/drivers/misc/Makefile -@@ -45,6 +45,7 @@ obj-y += lis3lv02d/ - obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o - obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/ - obj-$(CONFIG_INTEL_MEI) += mei/ -+obj-$(CONFIG_INTEL_IPTS) += ipts/ - obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/ - obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o - obj-$(CONFIG_SRAM) += sram.o -diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig -new file mode 100644 -index 000000000000..992a51061b38 ---- /dev/null -+++ b/drivers/misc/ipts/Kconfig -@@ -0,0 +1,11 @@ -+config INTEL_IPTS -+ tristate "Intel Precise Touch & Stylus" -+ select INTEL_MEI -+ depends on X86 && PCI && HID && DRM_I915 -+ help -+ Intel Precise Touch & Stylus support -+ Supported SoCs: -+ Intel Skylake -+ Intel Kabylake -+ -+source "drivers/misc/ipts/companion/Kconfig" -diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile -new file mode 100644 -index 000000000000..78bb61933387 ---- /dev/null -+++ b/drivers/misc/ipts/Makefile -@@ -0,0 +1,17 @@ -+# -+# Makefile - Intel Precise Touch & Stylus device driver -+# Copyright (c) 2016, Intel Corporation. -+# -+ -+obj-$(CONFIG_INTEL_IPTS)+= intel-ipts.o -+intel-ipts-objs += ipts-fw.o -+intel-ipts-objs += ipts-mei.o -+intel-ipts-objs += ipts-hid.o -+intel-ipts-objs += ipts-msg-handler.o -+intel-ipts-objs += ipts-kernel.o -+intel-ipts-objs += ipts-params.o -+intel-ipts-objs += ipts-resource.o -+intel-ipts-objs += ipts-gfx.o -+intel-ipts-$(CONFIG_DEBUG_FS) += ipts-dbgfs.o -+ -+obj-y += companion/ -diff --git a/drivers/misc/ipts/companion/Kconfig b/drivers/misc/ipts/companion/Kconfig -new file mode 100644 -index 000000000000..877a04494779 ---- /dev/null -+++ b/drivers/misc/ipts/companion/Kconfig -@@ -0,0 +1,9 @@ -+config INTEL_IPTS_SURFACE -+ tristate "IPTS companion driver for Microsoft Surface" -+ depends on INTEL_IPTS -+ depends on ACPI -+ help -+ IPTS companion driver for Microsoft Surface. This driver is responsible -+ for loading firmware using surface-specific hardware IDs. -+ -+ If you have a Microsoft Surface using IPTS, select y or m here. -diff --git a/drivers/misc/ipts/companion/Makefile b/drivers/misc/ipts/companion/Makefile -new file mode 100644 -index 000000000000..fb4d58935f01 ---- /dev/null -+++ b/drivers/misc/ipts/companion/Makefile -@@ -0,0 +1 @@ -+obj-$(CONFIG_INTEL_IPTS_SURFACE)+= ipts-surface.o -diff --git a/drivers/misc/ipts/companion/ipts-surface.c b/drivers/misc/ipts/companion/ipts-surface.c -new file mode 100644 -index 000000000000..6f5aabb14e5a ---- /dev/null -+++ b/drivers/misc/ipts/companion/ipts-surface.c -@@ -0,0 +1,100 @@ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define IPTS_SURFACE_FW_PATH_FMT "intel/ipts/%s/%s" -+ -+#define __IPTS_SURFACE_FIRMWARE(X, Y) \ -+ MODULE_FIRMWARE("intel/ipts/" X "/" Y) -+ -+#define IPTS_SURFACE_FIRMWARE(X) \ -+ __IPTS_SURFACE_FIRMWARE(X, "config.bin"); \ -+ __IPTS_SURFACE_FIRMWARE(X, "intel_desc.bin"); \ -+ __IPTS_SURFACE_FIRMWARE(X, "intel_fw_config.bin"); \ -+ __IPTS_SURFACE_FIRMWARE(X, "vendor_desc.bin"); \ -+ __IPTS_SURFACE_FIRMWARE(X, "vendor_kernel.bin") -+ -+IPTS_SURFACE_FIRMWARE("MSHW0076"); -+IPTS_SURFACE_FIRMWARE("MSHW0078"); -+IPTS_SURFACE_FIRMWARE("MSHW0079"); -+IPTS_SURFACE_FIRMWARE("MSHW0101"); -+IPTS_SURFACE_FIRMWARE("MSHW0102"); -+IPTS_SURFACE_FIRMWARE("MSHW0103"); -+IPTS_SURFACE_FIRMWARE("MSHW0137"); -+ -+int ipts_surface_request_firmware(const struct firmware **fw, const char *name, -+ struct device *device, void *data) -+{ -+ char fw_path[MAX_IOCL_FILE_PATH_LEN]; -+ -+ if (data == NULL) { -+ return -ENOENT; -+ } -+ -+ snprintf(fw_path, MAX_IOCL_FILE_PATH_LEN, IPTS_SURFACE_FW_PATH_FMT, -+ (const char *)data, name); -+ return request_firmware(fw, fw_path, device); -+} -+ -+static int ipts_surface_probe(struct platform_device *pdev) -+{ -+ int ret; -+ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); -+ -+ if (!adev) { -+ dev_err(&pdev->dev, "Unable to find ACPI info for device\n"); -+ return -ENODEV; -+ } -+ -+ ret = intel_ipts_add_fw_handler(&ipts_surface_request_firmware, -+ (void *)acpi_device_hid(adev)); -+ if (ret) { -+ dev_info(&pdev->dev, "Adding IPTS firmware handler failed, " -+ "error: %d\n", ret); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static int ipts_surface_remove(struct platform_device *pdev) -+{ -+ int ret; -+ -+ ret = intel_ipts_rm_fw_handler(&ipts_surface_request_firmware); -+ if (ret) { -+ dev_info(&pdev->dev, "Removing IPTS firmware handler failed, " -+ "error: %d\n", ret); -+ } -+ -+ return 0; -+} -+ -+static const struct acpi_device_id ipts_surface_acpi_match[] = { -+ { "MSHW0076", 0 }, /* Surface Book 1 / Surface Studio */ -+ { "MSHW0078", 0 }, /* Surface Pro 4 */ -+ { "MSHW0079", 0 }, /* Surface Laptop 1 / 2 */ -+ { "MSHW0101", 0 }, /* Surface Book 2 15" */ -+ { "MSHW0102", 0 }, /* Surface Pro 2017 / 6 */ -+ { "MSHW0103", 0 }, /* unknown, but firmware exists */ -+ { "MSHW0137", 0 }, /* Surface Book 2 */ -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, ipts_surface_acpi_match); -+ -+static struct platform_driver ipts_surface_driver = { -+ .probe = ipts_surface_probe, -+ .remove = ipts_surface_remove, -+ .driver = { -+ .name = "ipts_surface", -+ .acpi_match_table = ACPI_PTR(ipts_surface_acpi_match), -+ }, -+}; -+module_platform_driver(ipts_surface_driver); -+ -+MODULE_AUTHOR("Dorian Stoll "); -+MODULE_DESCRIPTION("IPTS companion driver for Microsoft Surface"); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/misc/ipts/ipts-binary-spec.h b/drivers/misc/ipts/ipts-binary-spec.h -new file mode 100644 -index 000000000000..87d4bc4133c4 ---- /dev/null -+++ b/drivers/misc/ipts/ipts-binary-spec.h -@@ -0,0 +1,118 @@ -+/* -+ * -+ * Intel Precise Touch & Stylus binary spec -+ * Copyright (c) 2016 Intel Corporation. -+ * -+ * 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. -+ * -+ */ -+ -+#ifndef _IPTS_BINARY_SPEC_H -+#define _IPTS_BINARY_SPEC_H -+ -+#define IPTS_BIN_HEADER_VERSION 2 -+ -+#pragma pack(1) -+ -+/* we support 16 output buffers(1:feedback, 15:HID) */ -+#define MAX_NUM_OUTPUT_BUFFERS 16 -+ -+typedef enum { -+ IPTS_BIN_KERNEL, -+ IPTS_BIN_RO_DATA, -+ IPTS_BIN_RW_DATA, -+ IPTS_BIN_SENSOR_FRAME, -+ IPTS_BIN_OUTPUT, -+ IPTS_BIN_DYNAMIC_STATE_HEAP, -+ IPTS_BIN_PATCH_LOCATION_LIST, -+ IPTS_BIN_ALLOCATION_LIST, -+ IPTS_BIN_COMMAND_BUFFER_PACKET, -+ IPTS_BIN_TAG, -+} ipts_bin_res_type_t; -+ -+typedef struct ipts_bin_header { -+ char str[4]; -+ unsigned int version; -+ -+#if IPTS_BIN_HEADER_VERSION > 1 -+ unsigned int gfxcore; -+ unsigned int revid; -+#endif -+} ipts_bin_header_t; -+ -+typedef struct ipts_bin_alloc { -+ unsigned int handle; -+ unsigned int reserved; -+} ipts_bin_alloc_t; -+ -+typedef struct ipts_bin_alloc_list { -+ unsigned int num; -+ ipts_bin_alloc_t alloc[]; -+} ipts_bin_alloc_list_t; -+ -+typedef struct ipts_bin_cmdbuf { -+ unsigned int size; -+ char data[]; -+} ipts_bin_cmdbuf_t; -+ -+typedef struct ipts_bin_res { -+ unsigned int handle; -+ ipts_bin_res_type_t type; -+ unsigned int initialize; -+ unsigned int aligned_size; -+ unsigned int size; -+ char data[]; -+} ipts_bin_res_t; -+ -+typedef enum { -+ IPTS_INPUT, -+ IPTS_OUTPUT, -+ IPTS_CONFIGURATION, -+ IPTS_CALIBRATION, -+ IPTS_FEATURE, -+} ipts_bin_io_buffer_type_t; -+ -+typedef struct ipts_bin_io_header { -+ char str[10]; -+ unsigned short type; -+} ipts_bin_io_header_t; -+ -+typedef struct ipts_bin_res_list { -+ unsigned int num; -+ ipts_bin_res_t res[]; -+} ipts_bin_res_list_t; -+ -+typedef struct ipts_bin_patch { -+ unsigned int index; -+ unsigned int reserved1[2]; -+ unsigned int alloc_offset; -+ unsigned int patch_offset; -+ unsigned int reserved2; -+} ipts_bin_patch_t; -+ -+typedef struct ipts_bin_patch_list { -+ unsigned int num; -+ ipts_bin_patch_t patch[]; -+} ipts_bin_patch_list_t; -+ -+typedef struct ipts_bin_guc_wq_info { -+ unsigned int batch_offset; -+ unsigned int size; -+ char data[]; -+} ipts_bin_guc_wq_info_t; -+ -+typedef struct ipts_bin_bufid_patch { -+ unsigned int imm_offset; -+ unsigned int mem_offset; -+} ipts_bin_bufid_patch_t; -+ -+#pragma pack() -+ -+#endif /* _IPTS_BINARY_SPEC_H */ -diff --git a/drivers/misc/ipts/ipts-dbgfs.c b/drivers/misc/ipts/ipts-dbgfs.c -new file mode 100644 -index 000000000000..7581b21f81e0 ---- /dev/null -+++ b/drivers/misc/ipts/ipts-dbgfs.c -@@ -0,0 +1,364 @@ -+/* -+ * Intel Precise Touch & Stylus device driver -+ * Copyright (c) 2016, Intel Corporation. -+ * -+ * 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. -+ * -+ */ -+#include -+#include -+#include -+ -+#include "ipts.h" -+#include "ipts-sensor-regs.h" -+#include "ipts-msg-handler.h" -+#include "ipts-state.h" -+#include "../mei/mei_dev.h" -+ -+const char sensor_mode_fmt[] = "sensor mode : %01d\n"; -+const char ipts_status_fmt[] = "sensor mode : %01d\nipts state : %01d\n"; -+const char ipts_debug_fmt[] = ">> tdt : fw status : %s\n" -+ ">> == DB s:%x, c:%x ==\n" -+ ">> == WQ h:%u, t:%u ==\n"; -+ -+static ssize_t ipts_dbgfs_mode_read(struct file *fp, char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ ipts_info_t *ipts = fp->private_data; -+ char mode[80]; -+ int len = 0; -+ -+ if (cnt < sizeof(sensor_mode_fmt) - 3) -+ return -EINVAL; -+ -+ len = scnprintf(mode, 80, sensor_mode_fmt, ipts->sensor_mode); -+ if (len < 0) -+ return -EIO; -+ -+ return simple_read_from_buffer(ubuf, cnt, ppos, mode, len); -+} -+ -+static ssize_t ipts_dbgfs_mode_write(struct file *fp, const char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ ipts_info_t *ipts = fp->private_data; -+ ipts_state_t state; -+ int sensor_mode, len; -+ char mode[3]; -+ -+ if (cnt == 0 || cnt > 3) -+ return -EINVAL; -+ -+ state = ipts_get_state(ipts); -+ if (state != IPTS_STA_RAW_DATA_STARTED && state != IPTS_STA_HID_STARTED) { -+ return -EIO; -+ } -+ -+ len = cnt; -+ if (copy_from_user(mode, ubuf, len)) -+ return -EFAULT; -+ -+ while(len > 0 && (isspace(mode[len-1]) || mode[len-1] == '\n')) -+ len--; -+ mode[len] = '\0'; -+ -+ if (sscanf(mode, "%d", &sensor_mode) != 1) -+ return -EINVAL; -+ -+ if (sensor_mode != TOUCH_SENSOR_MODE_RAW_DATA && -+ sensor_mode != TOUCH_SENSOR_MODE_HID) { -+ return -EINVAL; -+ } -+ -+ if (sensor_mode == ipts->sensor_mode) -+ return 0; -+ -+ ipts_switch_sensor_mode(ipts, sensor_mode); -+ -+ return cnt; -+} -+ -+static const struct file_operations ipts_mode_dbgfs_fops = { -+ .open = simple_open, -+ .read = ipts_dbgfs_mode_read, -+ .write = ipts_dbgfs_mode_write, -+ .llseek = generic_file_llseek, -+}; -+ -+static ssize_t ipts_dbgfs_status_read(struct file *fp, char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ ipts_info_t *ipts = fp->private_data; -+ char status[256]; -+ int len = 0; -+ -+ if (cnt < sizeof(ipts_status_fmt) - 3) -+ return -EINVAL; -+ -+ len = scnprintf(status, 256, ipts_status_fmt, ipts->sensor_mode, -+ ipts->state); -+ if (len < 0) -+ return -EIO; -+ -+ return simple_read_from_buffer(ubuf, cnt, ppos, status, len); -+} -+ -+static const struct file_operations ipts_status_dbgfs_fops = { -+ .open = simple_open, -+ .read = ipts_dbgfs_status_read, -+ .llseek = generic_file_llseek, -+}; -+ -+static ssize_t ipts_dbgfs_quiesce_io_cmd_write(struct file *fp, const char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ ipts_info_t *ipts = fp->private_data; -+ bool result; -+ int rc; -+ -+ rc = kstrtobool_from_user(ubuf, cnt, &result); -+ if (rc) -+ return rc; -+ -+ if (!result) -+ return -EINVAL; -+ -+ ipts_send_sensor_quiesce_io_cmd(ipts); -+ -+ return cnt; -+} -+ -+static const struct file_operations ipts_quiesce_io_cmd_dbgfs_fops = { -+ .open = simple_open, -+ .write = ipts_dbgfs_quiesce_io_cmd_write, -+ .llseek = generic_file_llseek, -+}; -+ -+static ssize_t ipts_dbgfs_clear_mem_window_cmd_write(struct file *fp, const char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ ipts_info_t *ipts = fp->private_data; -+ bool result; -+ int rc; -+ -+ rc = kstrtobool_from_user(ubuf, cnt, &result); -+ if (rc) -+ return rc; -+ -+ if (!result) -+ return -EINVAL; -+ -+ ipts_send_sensor_clear_mem_window_cmd(ipts); -+ -+ return cnt; -+} -+ -+static const struct file_operations ipts_clear_mem_window_cmd_dbgfs_fops = { -+ .open = simple_open, -+ .write = ipts_dbgfs_clear_mem_window_cmd_write, -+ .llseek = generic_file_llseek, -+}; -+ -+static ssize_t ipts_dbgfs_debug_read(struct file *fp, char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ ipts_info_t *ipts = fp->private_data; -+ char dbg_info[1024]; -+ int len = 0; -+ -+ char fw_sts_str[MEI_FW_STATUS_STR_SZ]; -+ u32 *db, *head, *tail; -+ intel_ipts_wq_info_t* wq_info; -+ -+ wq_info = &ipts->resource.wq_info; -+ -+ mei_fw_status_str(ipts->cldev->bus, fw_sts_str, MEI_FW_STATUS_STR_SZ); -+ // pr_info(">> tdt : fw status : %s\n", fw_sts_str); -+ -+ db = (u32*)wq_info->db_addr; -+ head = (u32*)wq_info->wq_head_addr; -+ tail = (u32*)wq_info->wq_tail_addr; -+ // pr_info(">> == DB s:%x, c:%x ==\n", *db, *(db+1)); -+ // pr_info(">> == WQ h:%u, t:%u ==\n", *head, *tail); -+ -+ if (cnt < sizeof(ipts_debug_fmt) - 3) -+ return -EINVAL; -+ -+ len = scnprintf(dbg_info, 1024, ipts_debug_fmt, -+ fw_sts_str, -+ *db, *(db+1), -+ *head, *tail); -+ if (len < 0) -+ return -EIO; -+ -+ return simple_read_from_buffer(ubuf, cnt, ppos, dbg_info, len); -+} -+ -+static const struct file_operations ipts_debug_dbgfs_fops = { -+ .open = simple_open, -+ .read = ipts_dbgfs_debug_read, -+ .llseek = generic_file_llseek, -+}; -+ -+static ssize_t ipts_dbgfs_ipts_restart_write(struct file *fp, const char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ ipts_info_t *ipts = fp->private_data; -+ bool result; -+ int rc; -+ -+ rc = kstrtobool_from_user(ubuf, cnt, &result); -+ if (rc) -+ return rc; -+ -+ if (!result) -+ return -EINVAL; -+ -+ ipts_restart(ipts); -+ -+ return cnt; -+} -+ -+static const struct file_operations ipts_ipts_restart_dbgfs_fops = { -+ .open = simple_open, -+ .write = ipts_dbgfs_ipts_restart_write, -+ .llseek = generic_file_llseek, -+}; -+ -+static ssize_t ipts_dbgfs_ipts_stop_write(struct file *fp, const char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ ipts_info_t *ipts = fp->private_data; -+ bool result; -+ int rc; -+ -+ rc = kstrtobool_from_user(ubuf, cnt, &result); -+ if (rc) -+ return rc; -+ -+ if (!result) -+ return -EINVAL; -+ -+ ipts_stop(ipts); -+ -+ return cnt; -+} -+ -+static const struct file_operations ipts_ipts_stop_dbgfs_fops = { -+ .open = simple_open, -+ .write = ipts_dbgfs_ipts_stop_write, -+ .llseek = generic_file_llseek, -+}; -+ -+static ssize_t ipts_dbgfs_ipts_start_write(struct file *fp, const char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ ipts_info_t *ipts = fp->private_data; -+ bool result; -+ int rc; -+ -+ rc = kstrtobool_from_user(ubuf, cnt, &result); -+ if (rc) -+ return rc; -+ -+ if (!result) -+ return -EINVAL; -+ -+ ipts_start(ipts); -+ -+ return cnt; -+} -+ -+static const struct file_operations ipts_ipts_start_dbgfs_fops = { -+ .open = simple_open, -+ .write = ipts_dbgfs_ipts_start_write, -+ .llseek = generic_file_llseek, -+}; -+ -+void ipts_dbgfs_deregister(ipts_info_t* ipts) -+{ -+ if (!ipts->dbgfs_dir) -+ return; -+ -+ debugfs_remove_recursive(ipts->dbgfs_dir); -+ ipts->dbgfs_dir = NULL; -+} -+ -+int ipts_dbgfs_register(ipts_info_t* ipts, const char *name) -+{ -+ struct dentry *dir, *f; -+ -+ dir = debugfs_create_dir(name, NULL); -+ if (!dir) -+ return -ENOMEM; -+ -+ f = debugfs_create_file("mode", S_IRUSR | S_IWUSR, dir, -+ ipts, &ipts_mode_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs mode creation failed\n"); -+ goto err; -+ } -+ -+ f = debugfs_create_file("status", S_IRUSR, dir, -+ ipts, &ipts_status_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs status creation failed\n"); -+ goto err; -+ } -+ -+ f = debugfs_create_file("quiesce_io_cmd", S_IWUSR, dir, -+ ipts, &ipts_quiesce_io_cmd_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs quiesce_io_cmd creation failed\n"); -+ goto err; -+ } -+ -+ f = debugfs_create_file("clear_mem_window_cmd", S_IWUSR, dir, -+ ipts, &ipts_clear_mem_window_cmd_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs clear_mem_window_cmd creation failed\n"); -+ goto err; -+ } -+ -+ f = debugfs_create_file("debug", S_IRUSR, dir, -+ ipts, &ipts_debug_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs debug creation failed\n"); -+ goto err; -+ } -+ -+ f = debugfs_create_file("ipts_restart", S_IWUSR, dir, -+ ipts, &ipts_ipts_restart_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs ipts_restart creation failed\n"); -+ goto err; -+ } -+ -+ f = debugfs_create_file("ipts_stop", S_IWUSR, dir, -+ ipts, &ipts_ipts_stop_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs ipts_stop creation failed\n"); -+ goto err; -+ } -+ -+ f = debugfs_create_file("ipts_start", S_IWUSR, dir, -+ ipts, &ipts_ipts_start_dbgfs_fops); -+ if (!f) { -+ ipts_err(ipts, "debugfs ipts_start creation failed\n"); -+ goto err; -+ } -+ -+ ipts->dbgfs_dir = dir; -+ -+ return 0; -+err: -+ ipts_dbgfs_deregister(ipts); -+ return -ENODEV; -+} -diff --git a/drivers/misc/ipts/ipts-fw.c b/drivers/misc/ipts/ipts-fw.c -new file mode 100644 -index 000000000000..82e6e44c9908 ---- /dev/null -+++ b/drivers/misc/ipts/ipts-fw.c -@@ -0,0 +1,113 @@ -+#include -+#include -+#include -+#include -+ -+#include "ipts.h" -+#include "ipts-fw.h" -+#include "ipts-params.h" -+ -+#define IPTS_GENERIC_FW_PATH_FMT "intel/ipts/%s" -+ -+/* -+ * This function pointer allows a companion driver to register a custom logic -+ * for loading firmware files. This can be used to detect devices that can -+ * be used for IPTS versioning, but that are not connected over the MEI bus, -+ * and cannot be detected by the ME driver. -+ */ -+IPTS_FW_HANDLER(ipts_fw_handler); -+DEFINE_MUTEX(ipts_fw_handler_lock); -+void *ipts_fw_handler_data = NULL; -+ -+bool ipts_fw_handler_available(void) -+{ -+ bool ret; -+ mutex_lock(&ipts_fw_handler_lock); -+ -+ ret = ipts_fw_handler != NULL; -+ -+ mutex_unlock(&ipts_fw_handler_lock); -+ return ret; -+} -+ -+int intel_ipts_add_fw_handler(IPTS_FW_HANDLER(handler), void *data) -+{ -+ int ret = 0; -+ mutex_lock(&ipts_fw_handler_lock); -+ -+ if (ipts_fw_handler != NULL) { -+ ret = -EBUSY; -+ goto ipts_add_fw_handler_return; -+ } -+ -+ ipts_fw_handler = handler; -+ ipts_fw_handler_data = data; -+ -+ipts_add_fw_handler_return: -+ -+ mutex_unlock(&ipts_fw_handler_lock); -+ return ret; -+} -+EXPORT_SYMBOL(intel_ipts_add_fw_handler); -+ -+int intel_ipts_rm_fw_handler(IPTS_FW_HANDLER(handler)) -+{ -+ int ret = 0; -+ mutex_lock(&ipts_fw_handler_lock); -+ -+ if (ipts_fw_handler == NULL) { -+ ret = 0; -+ goto ipts_rm_fw_handler_return; -+ } -+ -+ if (*handler != *ipts_fw_handler) { -+ ret = -EPERM; -+ goto ipts_rm_fw_handler_return; -+ } -+ -+ ipts_fw_handler = NULL; -+ ipts_fw_handler_data = NULL; -+ -+ipts_rm_fw_handler_return: -+ -+ mutex_unlock(&ipts_fw_handler_lock); -+ return ret; -+} -+EXPORT_SYMBOL(intel_ipts_rm_fw_handler); -+ -+int ipts_request_firmware(const struct firmware **fw, const char *name, -+ struct device *device) -+{ -+ int ret = 0; -+ char fw_path[MAX_IOCL_FILE_PATH_LEN]; -+ mutex_lock(&ipts_fw_handler_lock); -+ -+ // Check if a firmware handler was registered. If not, skip -+ // forward and try to load the firmware from the legacy path -+ if (ipts_fw_handler == NULL || ipts_modparams.ignore_companion) { -+ goto ipts_request_firmware_fallback; -+ } -+ -+ ret = (*ipts_fw_handler)(fw, name, device, ipts_fw_handler_data); -+ if (!ret) { -+ goto ipts_request_firmware_return; -+ } -+ -+ipts_request_firmware_fallback: -+ -+ // If fallback loading for firmware was disabled, abort. -+ // Return -ENOENT as no firmware file was found. -+ if (ipts_modparams.ignore_fw_fallback) { -+ ret = -ENOENT; -+ goto ipts_request_firmware_return; -+ } -+ -+ // No firmware was found by the companion driver, try the generic path now. -+ snprintf(fw_path, MAX_IOCL_FILE_PATH_LEN, IPTS_GENERIC_FW_PATH_FMT, name); -+ ret = request_firmware(fw, fw_path, device); -+ -+ipts_request_firmware_return: -+ -+ mutex_unlock(&ipts_fw_handler_lock); -+ return ret; -+} -diff --git a/drivers/misc/ipts/ipts-fw.h b/drivers/misc/ipts/ipts-fw.h -new file mode 100644 -index 000000000000..4c1c9a0dd77f ---- /dev/null -+++ b/drivers/misc/ipts/ipts-fw.h -@@ -0,0 +1,12 @@ -+#ifndef _IPTS_FW_H_ -+#define _IPTS_FW_H_ -+ -+#include -+ -+#include "ipts.h" -+ -+int ipts_request_firmware(const struct firmware **fw, const char *name, -+ struct device *device); -+bool ipts_fw_handler_available(void); -+ -+#endif // _IPTS_FW_H_ -diff --git a/drivers/misc/ipts/ipts-gfx.c b/drivers/misc/ipts/ipts-gfx.c -new file mode 100644 -index 000000000000..4989a22227d2 ---- /dev/null -+++ b/drivers/misc/ipts/ipts-gfx.c -@@ -0,0 +1,185 @@ -+/* -+ * -+ * Intel Integrated Touch Gfx Interface Layer -+ * Copyright (c) 2016 Intel Corporation. -+ * -+ * 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. -+ * -+ */ -+#include -+#include -+#include -+ -+#include "ipts.h" -+#include "ipts-msg-handler.h" -+#include "ipts-state.h" -+ -+static void gfx_processing_complete(void *data) -+{ -+ ipts_info_t *ipts = data; -+ -+ if (ipts_get_state(ipts) == IPTS_STA_RAW_DATA_STARTED) { -+ schedule_work(&ipts->raw_data_work); -+ return; -+ } -+ -+ ipts_dbg(ipts, "not ready to handle gfx event\n"); -+} -+ -+static void notify_gfx_status(u32 status, void *data) -+{ -+ ipts_info_t *ipts = data; -+ -+ ipts->gfx_status = status; -+ schedule_work(&ipts->gfx_status_work); -+} -+ -+static int connect_gfx(ipts_info_t *ipts) -+{ -+ int ret = 0; -+ intel_ipts_connect_t ipts_connect; -+ -+ ipts_connect.client = ipts->cldev->dev.parent; -+ ipts_connect.if_version = IPTS_INTERFACE_V1; -+ ipts_connect.ipts_cb.workload_complete = gfx_processing_complete; -+ ipts_connect.ipts_cb.notify_gfx_status = notify_gfx_status; -+ ipts_connect.data = (void*)ipts; -+ -+ ret = intel_ipts_connect(&ipts_connect); -+ if (ret) -+ return ret; -+ -+ /* TODO: gfx version check */ -+ ipts->gfx_info.gfx_handle = ipts_connect.gfx_handle; -+ ipts->gfx_info.ipts_ops = ipts_connect.ipts_ops; -+ -+ return ret; -+} -+ -+static void disconnect_gfx(ipts_info_t *ipts) -+{ -+ intel_ipts_disconnect(ipts->gfx_info.gfx_handle); -+} -+ -+#ifdef RUN_DBG_THREAD -+#include "../mei/mei_dev.h" -+ -+static struct task_struct *dbg_thread; -+ -+static void ipts_print_dbg_info(ipts_info_t* ipts) -+{ -+ char fw_sts_str[MEI_FW_STATUS_STR_SZ]; -+ u32 *db, *head, *tail; -+ intel_ipts_wq_info_t* wq_info; -+ -+ wq_info = &ipts->resource.wq_info; -+ -+ mei_fw_status_str(ipts->cldev->bus, fw_sts_str, MEI_FW_STATUS_STR_SZ); -+ pr_info(">> tdt : fw status : %s\n", fw_sts_str); -+ -+ db = (u32*)wq_info->db_addr; -+ head = (u32*)wq_info->wq_head_addr; -+ tail = (u32*)wq_info->wq_tail_addr; -+ pr_info(">> == DB s:%x, c:%x ==\n", *db, *(db+1)); -+ pr_info(">> == WQ h:%u, t:%u ==\n", *head, *tail); -+} -+ -+static int ipts_dbg_thread(void *data) -+{ -+ ipts_info_t *ipts = (ipts_info_t *)data; -+ -+ pr_info(">> start debug thread\n"); -+ -+ while (!kthread_should_stop()) { -+ if (ipts_get_state(ipts) != IPTS_STA_RAW_DATA_STARTED) { -+ pr_info("state is not IPTS_STA_RAW_DATA_STARTED : %d\n", -+ ipts_get_state(ipts)); -+ msleep(5000); -+ continue; -+ } -+ -+ ipts_print_dbg_info(ipts); -+ -+ msleep(3000); -+ } -+ -+ return 0; -+} -+#endif -+ -+int ipts_open_gpu(ipts_info_t *ipts) -+{ -+ int ret = 0; -+ -+ ret = connect_gfx(ipts); -+ if (ret) { -+ ipts_dbg(ipts, "cannot connect GPU\n"); -+ return ret; -+ } -+ -+ ret = ipts->gfx_info.ipts_ops.get_wq_info(ipts->gfx_info.gfx_handle, -+ &ipts->resource.wq_info); -+ if (ret) { -+ ipts_dbg(ipts, "error in get_wq_info\n"); -+ return ret; -+ } -+ -+#ifdef RUN_DBG_THREAD -+ dbg_thread = kthread_run(ipts_dbg_thread, (void *)ipts, "ipts_debug"); -+#endif -+ -+ return 0; -+} -+ -+void ipts_close_gpu(ipts_info_t *ipts) -+{ -+ disconnect_gfx(ipts); -+ -+#ifdef RUN_DBG_THREAD -+ kthread_stop(dbg_thread); -+#endif -+} -+ -+intel_ipts_mapbuffer_t *ipts_map_buffer(ipts_info_t *ipts, u32 size, u32 flags) -+{ -+ intel_ipts_mapbuffer_t *buf; -+ u64 handle; -+ int ret; -+ -+ buf = devm_kzalloc(&ipts->cldev->dev, sizeof(*buf), GFP_KERNEL); -+ if (!buf) -+ return NULL; -+ -+ buf->size = size; -+ buf->flags = flags; -+ -+ handle = ipts->gfx_info.gfx_handle; -+ ret = ipts->gfx_info.ipts_ops.map_buffer(handle, buf); -+ if (ret) { -+ devm_kfree(&ipts->cldev->dev, buf); -+ return NULL; -+ } -+ -+ return buf; -+} -+ -+void ipts_unmap_buffer(ipts_info_t *ipts, intel_ipts_mapbuffer_t *buf) -+{ -+ u64 handle; -+ int ret; -+ -+ if (!buf) -+ return; -+ -+ handle = ipts->gfx_info.gfx_handle; -+ ret = ipts->gfx_info.ipts_ops.unmap_buffer(handle, buf->buf_handle); -+ -+ devm_kfree(&ipts->cldev->dev, buf); -+} -diff --git a/drivers/misc/ipts/ipts-gfx.h b/drivers/misc/ipts/ipts-gfx.h -new file mode 100644 -index 000000000000..03a5f3551ddf ---- /dev/null -+++ b/drivers/misc/ipts/ipts-gfx.h -@@ -0,0 +1,24 @@ -+/* -+ * Intel Precise Touch & Stylus gpu wrapper -+ * Copyright (c) 2016, Intel Corporation. -+ * -+ * 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. -+ */ -+ -+ -+#ifndef _IPTS_GFX_H_ -+#define _IPTS_GFX_H_ -+ -+int ipts_open_gpu(ipts_info_t *ipts); -+void ipts_close_gpu(ipts_info_t *ipts); -+intel_ipts_mapbuffer_t *ipts_map_buffer(ipts_info_t *ipts, u32 size, u32 flags); -+void ipts_unmap_buffer(ipts_info_t *ipts, intel_ipts_mapbuffer_t *buf); -+ -+#endif // _IPTS_GFX_H_ -diff --git a/drivers/misc/ipts/ipts-hid.c b/drivers/misc/ipts/ipts-hid.c -new file mode 100644 -index 000000000000..32cf5927f949 ---- /dev/null -+++ b/drivers/misc/ipts/ipts-hid.c -@@ -0,0 +1,497 @@ -+/* -+ * Intel Precise Touch & Stylus HID driver -+ * -+ * Copyright (c) 2016, Intel Corporation. -+ * -+ * 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. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "ipts.h" -+#include "ipts-fw.h" -+#include "ipts-params.h" -+#include "ipts-resource.h" -+#include "ipts-sensor-regs.h" -+#include "ipts-msg-handler.h" -+ -+#define BUS_MEI 0x44 -+ -+#define HID_DESC_INTEL "intel_desc.bin" -+#define HID_DESC_VENDOR "vendor_desc.bin" -+ -+typedef enum output_buffer_payload_type { -+ OUTPUT_BUFFER_PAYLOAD_ERROR = 0, -+ OUTPUT_BUFFER_PAYLOAD_HID_INPUT_REPORT, -+ OUTPUT_BUFFER_PAYLOAD_HID_FEATURE_REPORT, -+ OUTPUT_BUFFER_PAYLOAD_KERNEL_LOAD, -+ OUTPUT_BUFFER_PAYLOAD_FEEDBACK_BUFFER -+} output_buffer_payload_type_t; -+ -+typedef struct kernel_output_buffer_header { -+ u16 length; -+ u8 payload_type; -+ u8 reserved1; -+ touch_hid_private_data_t hid_private_data; -+ u8 reserved2[28]; -+ u8 data[0]; -+} kernel_output_buffer_header_t; -+ -+typedef struct kernel_output_payload_error { -+ u16 severity; -+ u16 source; -+ u8 code[4]; -+ char string[128]; -+} kernel_output_payload_error_t; -+ -+static const struct dmi_system_id no_feedback_dmi_table[] = { -+ { -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), -+ }, -+ }, -+ { -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), -+ }, -+ }, -+ { } -+}; -+ -+static int ipts_hid_get_hid_descriptor(ipts_info_t *ipts, u8 **desc, int *size) -+{ -+ u8 *buf; -+ int hid_size = 0, ret = 0; -+ const struct firmware *intel_desc = NULL; -+ const struct firmware *vendor_desc = NULL; -+ const char *intel_desc_path = HID_DESC_INTEL; -+ const char *vendor_desc_path = HID_DESC_VENDOR; -+ -+ ret = ipts_request_firmware(&intel_desc, intel_desc_path, &ipts->cldev->dev); -+ if (ret) { -+ goto no_hid; -+ } -+ hid_size = intel_desc->size; -+ -+ ret = ipts_request_firmware(&vendor_desc, vendor_desc_path, &ipts->cldev->dev); -+ if (ret) { -+ ipts_dbg(ipts, "error in reading HID Vendor Descriptor\n"); -+ } else { -+ hid_size += vendor_desc->size; -+ } -+ -+ ipts_dbg(ipts, "hid size = %d\n", hid_size); -+ buf = vmalloc(hid_size); -+ if (buf == NULL) { -+ ret = -ENOMEM; -+ goto no_mem; -+ } -+ -+ memcpy(buf, intel_desc->data, intel_desc->size); -+ if (vendor_desc) { -+ memcpy(&buf[intel_desc->size], vendor_desc->data, -+ vendor_desc->size); -+ release_firmware(vendor_desc); -+ } -+ -+ release_firmware(intel_desc); -+ -+ *desc = buf; -+ *size = hid_size; -+ -+ return 0; -+no_mem : -+ if (vendor_desc) -+ release_firmware(vendor_desc); -+ release_firmware(intel_desc); -+ -+no_hid : -+ return ret; -+} -+ -+static int ipts_hid_parse(struct hid_device *hid) -+{ -+ ipts_info_t *ipts = hid->driver_data; -+ int ret = 0, size; -+ u8 *buf; -+ -+ ipts_dbg(ipts, "ipts_hid_parse() start\n"); -+ ret = ipts_hid_get_hid_descriptor(ipts, &buf, &size); -+ if (ret != 0) { -+ ipts_dbg(ipts, "ipts_hid_ipts_get_hid_descriptor ret %d\n", ret); -+ return -EIO; -+ } -+ -+ ret = hid_parse_report(hid, buf, size); -+ vfree(buf); -+ if (ret) { -+ ipts_err(ipts, "hid_parse_report error : %d\n", ret); -+ goto out; -+ } -+ -+ ipts->hid_desc_ready = true; -+out: -+ return ret; -+} -+ -+static int ipts_hid_start(struct hid_device *hid) -+{ -+ return 0; -+} -+ -+static void ipts_hid_stop(struct hid_device *hid) -+{ -+ return; -+} -+ -+static int ipts_hid_open(struct hid_device *hid) -+{ -+ return 0; -+} -+ -+static void ipts_hid_close(struct hid_device *hid) -+{ -+ ipts_info_t *ipts = hid->driver_data; -+ -+ ipts->hid_desc_ready = false; -+ -+ return; -+} -+ -+static int ipts_hid_send_hid2me_feedback(ipts_info_t *ipts, u32 fb_data_type, -+ __u8 *buf, size_t count) -+{ -+ ipts_buffer_info_t *fb_buf; -+ touch_feedback_hdr_t *feedback; -+ u8 *payload; -+ int header_size; -+ ipts_state_t state; -+ -+ header_size = sizeof(touch_feedback_hdr_t); -+ -+ if (count > ipts->resource.hid2me_buffer_size - header_size) -+ return -EINVAL; -+ -+ state = ipts_get_state(ipts); -+ if (state != IPTS_STA_RAW_DATA_STARTED && state != IPTS_STA_HID_STARTED) -+ return 0; -+ -+ fb_buf = ipts_get_hid2me_buffer(ipts); -+ feedback = (touch_feedback_hdr_t *)fb_buf->addr; -+ payload = fb_buf->addr + header_size; -+ memset(feedback, 0, header_size); -+ -+ feedback->feedback_data_type = fb_data_type; -+ feedback->feedback_cmd_type = TOUCH_FEEDBACK_CMD_TYPE_NONE; -+ feedback->payload_size_bytes = count; -+ feedback->buffer_id = TOUCH_HID_2_ME_BUFFER_ID; -+ feedback->protocol_ver = 0; -+ feedback->reserved[0] = 0xAC; -+ -+ /* copy payload */ -+ memcpy(payload, buf, count); -+ -+ ipts_send_feedback(ipts, TOUCH_HID_2_ME_BUFFER_ID, 0); -+ -+ return 0; -+} -+ -+static int ipts_hid_raw_request(struct hid_device *hid, -+ unsigned char report_number, __u8 *buf, -+ size_t count, unsigned char report_type, -+ int reqtype) -+{ -+ ipts_info_t *ipts = hid->driver_data; -+ u32 fb_data_type; -+ -+ ipts_dbg(ipts, "hid raw request => report %d, request %d\n", -+ (int)report_type, reqtype); -+ -+ if (report_type != HID_FEATURE_REPORT) -+ return 0; -+ -+ switch (reqtype) { -+ case HID_REQ_GET_REPORT: -+ fb_data_type = TOUCH_FEEDBACK_DATA_TYPE_GET_FEATURES; -+ break; -+ case HID_REQ_SET_REPORT: -+ fb_data_type = TOUCH_FEEDBACK_DATA_TYPE_SET_FEATURES; -+ break; -+ default: -+ ipts_err(ipts, "raw request not supprted: %d\n", reqtype); -+ return -EIO; -+ } -+ -+ return ipts_hid_send_hid2me_feedback(ipts, fb_data_type, buf, count); -+} -+ -+static int ipts_hid_output_report(struct hid_device *hid, -+ __u8 *buf, size_t count) -+{ -+ ipts_info_t *ipts = hid->driver_data; -+ u32 fb_data_type; -+ -+ ipts_dbg(ipts, "hid output report\n"); -+ -+ fb_data_type = TOUCH_FEEDBACK_DATA_TYPE_OUTPUT_REPORT; -+ -+ return ipts_hid_send_hid2me_feedback(ipts, fb_data_type, buf, count); -+} -+ -+static struct hid_ll_driver ipts_hid_ll_driver = { -+ .parse = ipts_hid_parse, -+ .start = ipts_hid_start, -+ .stop = ipts_hid_stop, -+ .open = ipts_hid_open, -+ .close = ipts_hid_close, -+ .raw_request = ipts_hid_raw_request, -+ .output_report = ipts_hid_output_report, -+}; -+ -+int ipts_hid_init(ipts_info_t *ipts) -+{ -+ int ret = 0; -+ struct hid_device *hid; -+ -+ hid = hid_allocate_device(); -+ if (IS_ERR(hid)) { -+ ret = PTR_ERR(hid); -+ goto err_dev; -+ } -+ -+ hid->driver_data = ipts; -+ hid->ll_driver = &ipts_hid_ll_driver; -+ hid->dev.parent = &ipts->cldev->dev; -+ hid->bus = BUS_MEI; -+ hid->version = ipts->device_info.fw_rev; -+ hid->vendor = ipts->device_info.vendor_id; -+ hid->product = ipts->device_info.device_id; -+ -+ snprintf(hid->phys, sizeof(hid->phys), "heci3"); -+ snprintf(hid->name, sizeof(hid->name), -+ "%s %04hX:%04hX", "ipts", hid->vendor, hid->product); -+ -+ ret = hid_add_device(hid); -+ if (ret) { -+ if (ret != -ENODEV) -+ ipts_err(ipts, "can't add hid device: %d\n", ret); -+ goto err_mem_free; -+ } -+ -+ ipts->hid = hid; -+ -+ return 0; -+ -+err_mem_free: -+ hid_destroy_device(hid); -+err_dev: -+ return ret; -+} -+ -+void ipts_hid_release(ipts_info_t *ipts) -+{ -+ if (!ipts->hid) -+ return; -+ hid_destroy_device(ipts->hid); -+} -+ -+int ipts_handle_hid_data(ipts_info_t *ipts, -+ touch_sensor_hid_ready_for_data_rsp_data_t *hid_rsp) -+{ -+ touch_raw_data_hdr_t *raw_header; -+ ipts_buffer_info_t *buffer_info; -+ touch_feedback_hdr_t *feedback; -+ u8 *raw_data; -+ int touch_data_buffer_index; -+ int transaction_id; -+ int ret = 0; -+ -+ touch_data_buffer_index = (int)hid_rsp->touch_data_buffer_index; -+ buffer_info = ipts_get_touch_data_buffer_hid(ipts); -+ raw_header = (touch_raw_data_hdr_t *)buffer_info->addr; -+ transaction_id = raw_header->hid_private_data.transaction_id; -+ -+ raw_data = (u8*)raw_header + sizeof(touch_raw_data_hdr_t); -+ if (raw_header->data_type == TOUCH_RAW_DATA_TYPE_HID_REPORT) { -+ memcpy(ipts->hid_input_report, raw_data, -+ raw_header->raw_data_size_bytes); -+ -+ ret = hid_input_report(ipts->hid, HID_INPUT_REPORT, -+ (u8*)ipts->hid_input_report, -+ raw_header->raw_data_size_bytes, 1); -+ if (ret) { -+ ipts_err(ipts, "error in hid_input_report : %d\n", ret); -+ } -+ } else if (raw_header->data_type == TOUCH_RAW_DATA_TYPE_GET_FEATURES) { -+ /* TODO: implement together with "get feature ioctl" */ -+ } else if (raw_header->data_type == TOUCH_RAW_DATA_TYPE_ERROR) { -+ touch_error_t *touch_err = (touch_error_t *)raw_data; -+ -+ ipts_err(ipts, "error type : %d, me fw error : %x, err reg : %x\n", -+ touch_err->touch_error_type, -+ touch_err->touch_me_fw_error.value, -+ touch_err->touch_error_register.reg_value); -+ } -+ -+ /* send feedback data for HID mode */ -+ buffer_info = ipts_get_feedback_buffer(ipts, touch_data_buffer_index); -+ feedback = (touch_feedback_hdr_t *)buffer_info->addr; -+ memset(feedback, 0, sizeof(touch_feedback_hdr_t)); -+ feedback->feedback_cmd_type = TOUCH_FEEDBACK_CMD_TYPE_NONE; -+ feedback->payload_size_bytes = 0; -+ feedback->buffer_id = touch_data_buffer_index; -+ feedback->protocol_ver = 0; -+ feedback->reserved[0] = 0xAC; -+ -+ ret = ipts_send_feedback(ipts, touch_data_buffer_index, transaction_id); -+ -+ return ret; -+} -+ -+static int handle_outputs(ipts_info_t *ipts, int parallel_idx) -+{ -+ kernel_output_buffer_header_t *out_buf_hdr; -+ ipts_buffer_info_t *output_buf, *fb_buf = NULL; -+ u8 *input_report, *payload; -+ u32 transaction_id; -+ int i, payload_size, ret = 0, header_size; -+ -+ header_size = sizeof(kernel_output_buffer_header_t); -+ output_buf = ipts_get_output_buffers_by_parallel_id(ipts, parallel_idx); -+ for (i = 0; i < ipts->resource.num_of_outputs; i++) { -+ out_buf_hdr = (kernel_output_buffer_header_t*)output_buf[i].addr; -+ if (out_buf_hdr->length < header_size) -+ continue; -+ -+ payload_size = out_buf_hdr->length - header_size; -+ payload = out_buf_hdr->data; -+ -+ switch(out_buf_hdr->payload_type) { -+ case OUTPUT_BUFFER_PAYLOAD_HID_INPUT_REPORT: -+ input_report = ipts->hid_input_report; -+ memcpy(input_report, payload, payload_size); -+ hid_input_report(ipts->hid, HID_INPUT_REPORT, -+ input_report, payload_size, 1); -+ break; -+ case OUTPUT_BUFFER_PAYLOAD_HID_FEATURE_REPORT: -+ ipts_dbg(ipts, "output hid feature report\n"); -+ break; -+ case OUTPUT_BUFFER_PAYLOAD_KERNEL_LOAD: -+ ipts_dbg(ipts, "output kernel load\n"); -+ break; -+ case OUTPUT_BUFFER_PAYLOAD_FEEDBACK_BUFFER: -+ { -+ /* send feedback data for raw data mode */ -+ fb_buf = ipts_get_feedback_buffer(ipts, -+ parallel_idx); -+ transaction_id = out_buf_hdr-> -+ hid_private_data.transaction_id; -+ memcpy(fb_buf->addr, payload, payload_size); -+ break; -+ } -+ case OUTPUT_BUFFER_PAYLOAD_ERROR: -+ { -+ kernel_output_payload_error_t *err_payload; -+ -+ if (payload_size == 0) -+ break; -+ -+ err_payload = -+ (kernel_output_payload_error_t*)payload; -+ -+ ipts_err(ipts, "error : severity : %d," -+ " source : %d," -+ " code : %d:%d:%d:%d\n" -+ "string %s\n", -+ err_payload->severity, -+ err_payload->source, -+ err_payload->code[0], -+ err_payload->code[1], -+ err_payload->code[2], -+ err_payload->code[3], -+ err_payload->string); -+ -+ break; -+ } -+ default: -+ ipts_err(ipts, "invalid output buffer payload\n"); -+ break; -+ } -+ } -+ -+ /* -+ * XXX: Calling the "ipts_send_feedback" function repeatedly seems to be -+ * what is causing touch to crash (found by sebanc, see the link below for -+ * the comment) on some models, especially on Surface Pro 4 and -+ * Surface Book 1. -+ * The most desirable fix could be done by raising IPTS GuC priority. Until -+ * we find a better solution, use this workaround. -+ * -+ * Link to the comment where sebanc found this workaround: -+ * https://github.com/jakeday/linux-surface/issues/374#issuecomment-508234110 -+ * (Touch and pen issue persists · Issue #374 · jakeday/linux-surface) -+ * -+ * Link to the usage from kitakar5525 who made this change: -+ * https://github.com/jakeday/linux-surface/issues/374#issuecomment-517289171 -+ * (Touch and pen issue persists · Issue #374 · jakeday/linux-surface) -+ */ -+ if (fb_buf) { -+ /* A negative value means "decide by dmi table" */ -+ if (ipts_modparams.no_feedback < 0) -+ ipts_modparams.no_feedback = -+ dmi_check_system(no_feedback_dmi_table) ? true : false; -+ -+ if (ipts_modparams.no_feedback) -+ return 0; -+ -+ ret = ipts_send_feedback(ipts, parallel_idx, transaction_id); -+ if (ret) -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static int handle_output_buffers(ipts_info_t *ipts, int cur_idx, int end_idx) -+{ -+ int max_num_of_buffers = ipts_get_num_of_parallel_buffers(ipts); -+ -+ do { -+ cur_idx++; /* cur_idx has last completed so starts with +1 */ -+ cur_idx %= max_num_of_buffers; -+ handle_outputs(ipts, cur_idx); -+ } while (cur_idx != end_idx); -+ -+ return 0; -+} -+ -+int ipts_handle_processed_data(ipts_info_t *ipts) -+{ -+ int ret = 0; -+ int current_buffer_idx; -+ int last_buffer_idx; -+ -+ current_buffer_idx = *ipts->last_submitted_id; -+ last_buffer_idx = ipts->last_buffer_completed; -+ -+ if (current_buffer_idx == last_buffer_idx) -+ return 0; -+ -+ ipts->last_buffer_completed = current_buffer_idx; -+ handle_output_buffers(ipts, last_buffer_idx, current_buffer_idx); -+ -+ return ret; -+} -diff --git a/drivers/misc/ipts/ipts-hid.h b/drivers/misc/ipts/ipts-hid.h -new file mode 100644 -index 000000000000..f1b22c912df7 ---- /dev/null -+++ b/drivers/misc/ipts/ipts-hid.h -@@ -0,0 +1,34 @@ -+/* -+ * Intel Precise Touch & Stylus HID definition -+ * -+ * Copyright (c) 2016, Intel Corporation. -+ * -+ * 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. -+ */ -+ -+#ifndef _IPTS_HID_H_ -+#define _IPTS_HID_H_ -+ -+#define BUS_MEI 0x44 -+ -+#if 0 /* TODO : we have special report ID. will implement them */ -+#define WRITE_CHANNEL_REPORT_ID 0xa -+#define READ_CHANNEL_REPORT_ID 0xb -+#define CONFIG_CHANNEL_REPORT_ID 0xd -+#define VENDOR_INFO_REPORT_ID 0xF -+#define SINGLE_TOUCH_REPORT_ID 0x40 -+#endif -+ -+int ipts_hid_init(ipts_info_t *ipts); -+void ipts_hid_release(ipts_info_t *ipts); -+int ipts_handle_hid_data(ipts_info_t *ipts, -+ touch_sensor_hid_ready_for_data_rsp_data_t *hid_rsp); -+ -+#endif /* _IPTS_HID_H_ */ -diff --git a/drivers/misc/ipts/ipts-kernel.c b/drivers/misc/ipts/ipts-kernel.c -new file mode 100644 -index 000000000000..5933b190cdaf ---- /dev/null -+++ b/drivers/misc/ipts/ipts-kernel.c -@@ -0,0 +1,1042 @@ -+#include -+#include -+#include -+#include -+#include -+ -+#include "ipts.h" -+#include "ipts-fw.h" -+#include "ipts-resource.h" -+#include "ipts-binary-spec.h" -+#include "ipts-state.h" -+#include "ipts-msg-handler.h" -+#include "ipts-gfx.h" -+ -+#pragma pack(1) -+typedef struct bin_data_file_info { -+ u32 io_buffer_type; -+ u32 flags; -+ char file_name[MAX_IOCL_FILE_NAME_LEN]; -+} bin_data_file_info_t; -+ -+typedef struct bin_fw_info { -+ char fw_name[MAX_IOCL_FILE_NAME_LEN]; -+ -+ /* list of parameters to load a kernel */ -+ s32 vendor_output; /* output index. -1 for no use */ -+ u32 num_of_data_files; -+ bin_data_file_info_t data_file[]; -+} bin_fw_info_t; -+ -+typedef struct bin_fw_list { -+ u32 num_of_fws; -+ bin_fw_info_t fw_info[]; -+} bin_fw_list_t; -+#pragma pack() -+ -+/* OpenCL kernel */ -+typedef struct bin_workload { -+ int cmdbuf_index; -+ int iobuf_input; -+ int iobuf_output[MAX_NUM_OUTPUT_BUFFERS]; -+} bin_workload_t; -+ -+typedef struct bin_buffer { -+ unsigned int handle; -+ intel_ipts_mapbuffer_t *buf; -+ bool no_unmap; /* only releasing vendor kernel unmaps output buffers */ -+} bin_buffer_t; -+ -+typedef struct bin_alloc_info { -+ bin_buffer_t *buffs; -+ int num_of_allocations; -+ int num_of_outputs; -+ -+ int num_of_buffers; -+} bin_alloc_info_t; -+ -+typedef struct bin_guc_wq_item { -+ unsigned int batch_offset; -+ unsigned int size; -+ char data[]; -+} bin_guc_wq_item_t; -+ -+typedef struct bin_kernel_info { -+ bin_workload_t *wl; -+ bin_alloc_info_t *alloc_info; -+ bin_guc_wq_item_t *guc_wq_item; -+ ipts_bin_bufid_patch_t bufid_patch; -+ -+ bool is_vendor; /* 1: vendor, 0: postprocessing */ -+} bin_kernel_info_t; -+ -+typedef struct bin_kernel_list { -+ intel_ipts_mapbuffer_t *bufid_buf; -+ int num_of_kernels; -+ bin_kernel_info_t kernels[]; -+} bin_kernel_list_t; -+ -+typedef struct bin_parse_info { -+ u8 *data; -+ int size; -+ int parsed; -+ -+ bin_fw_info_t *fw_info; -+ -+ /* only used by postprocessing */ -+ bin_kernel_info_t *vendor_kernel; -+ u32 interested_vendor_output; /* interested vendor output index */ -+} bin_parse_info_t; -+ -+#define BDW_SURFACE_BASE_ADDRESS 0x6101000e -+#define SURFACE_STATE_OFFSET_WORD 4 -+#define SBA_OFFSET_BYTES 16384 -+#define LASTSUBMITID_DEFAULT_VALUE -1 -+ -+#define IPTS_FW_CONFIG_FILE "ipts_fw_config.bin" -+ -+#define IPTS_INPUT_ON ((u32)1 << IPTS_INPUT) -+#define IPTS_OUTPUT_ON ((u32)1 << IPTS_OUTPUT) -+#define IPTS_CONFIGURATION_ON ((u32)1 << IPTS_CONFIGURATION) -+#define IPTS_CALIBRATION_ON ((u32)1 << IPTS_CALIBRATION) -+#define IPTS_FEATURE_ON ((u32)1 << IPTS_FEATURE) -+ -+#define DATA_FILE_FLAG_SHARE 0x00000001 -+#define DATA_FILE_FLAG_ALLOC_CONTIGUOUS 0x00000002 -+ -+static int bin_read_fw(ipts_info_t *ipts, const char *fw_name, -+ u8* data, int size) -+{ -+ const struct firmware *fw = NULL; -+ int ret = 0; -+ -+ ret = ipts_request_firmware(&fw, fw_name, &ipts->cldev->dev); -+ if (ret) { -+ ipts_err(ipts, "cannot read fw %s\n", fw_name); -+ return ret; -+ } -+ -+ if (fw->size > size) { -+ ipts_dbg(ipts, "too small buffer to contain fw data\n"); -+ ret = -EINVAL; -+ goto rel_return; -+ } -+ -+ memcpy(data, fw->data, fw->size); -+ -+rel_return: -+ release_firmware(fw); -+ -+ return ret; -+} -+ -+ -+static bin_data_file_info_t* bin_get_data_file_info(bin_fw_info_t* fw_info, -+ u32 io_buffer_type) -+{ -+ int i; -+ -+ for (i = 0; i < fw_info->num_of_data_files; i++) { -+ if (fw_info->data_file[i].io_buffer_type == io_buffer_type) -+ break; -+ } -+ -+ if (i == fw_info->num_of_data_files) -+ return NULL; -+ -+ return &fw_info->data_file[i]; -+} -+ -+static inline bool is_shared_data(const bin_data_file_info_t *data_file) -+{ -+ if (data_file) -+ return (!!(data_file->flags & DATA_FILE_FLAG_SHARE)); -+ -+ return false; -+} -+ -+static inline bool is_alloc_cont_data(const bin_data_file_info_t *data_file) -+{ -+ if (data_file) -+ return (!!(data_file->flags & DATA_FILE_FLAG_ALLOC_CONTIGUOUS)); -+ -+ return false; -+} -+ -+static inline bool is_parsing_vendor_kernel(const bin_parse_info_t *parse_info) -+{ -+ /* vendor_kernel == null while loading itself(vendor kernel) */ -+ return parse_info->vendor_kernel == NULL; -+} -+ -+static int bin_read_allocation_list(ipts_info_t *ipts, -+ bin_parse_info_t *parse_info, -+ bin_alloc_info_t *alloc_info) -+{ -+ ipts_bin_alloc_list_t *alloc_list; -+ int alloc_idx, parallel_idx, num_of_parallels, buf_idx, num_of_buffers; -+ int parsed, size; -+ -+ parsed = parse_info->parsed; -+ size = parse_info->size; -+ -+ alloc_list = (ipts_bin_alloc_list_t *)&parse_info->data[parsed]; -+ -+ /* validation check */ -+ if (sizeof(alloc_list->num) > size - parsed) -+ return -EINVAL; -+ -+ /* read the number of aloocations */ -+ parsed += sizeof(alloc_list->num); -+ -+ /* validation check */ -+ if (sizeof(alloc_list->alloc[0]) * alloc_list->num > size - parsed) -+ return -EINVAL; -+ -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ num_of_buffers = num_of_parallels * alloc_list->num + num_of_parallels; -+ -+ alloc_info->buffs = vmalloc(sizeof(bin_buffer_t) * num_of_buffers); -+ if (alloc_info->buffs == NULL) -+ return -ENOMEM; -+ -+ memset(alloc_info->buffs, 0, sizeof(bin_buffer_t) * num_of_buffers); -+ for (alloc_idx = 0; alloc_idx < alloc_list->num; alloc_idx++) { -+ for (parallel_idx = 0; parallel_idx < num_of_parallels; -+ parallel_idx++) { -+ buf_idx = alloc_idx + (parallel_idx * alloc_list->num); -+ alloc_info->buffs[buf_idx].handle = -+ alloc_list->alloc[alloc_idx].handle; -+ -+ } -+ -+ parsed += sizeof(alloc_list->alloc[0]); -+ } -+ -+ parse_info->parsed = parsed; -+ alloc_info->num_of_allocations = alloc_list->num; -+ alloc_info->num_of_buffers = num_of_buffers; -+ -+ ipts_dbg(ipts, "number of allocations = %d, buffers = %d\n", -+ alloc_info->num_of_allocations, -+ alloc_info->num_of_buffers); -+ -+ return 0; -+} -+ -+static void patch_SBA(u32 *buf_addr, u64 gpu_addr, int size) -+{ -+ u64 *stateBase; -+ u64 SBA; -+ u32 inst; -+ int i; -+ -+ SBA = gpu_addr + SBA_OFFSET_BYTES; -+ -+ for (i = 0; i < size/4; i++) { -+ inst = buf_addr[i]; -+ if (inst == BDW_SURFACE_BASE_ADDRESS) { -+ stateBase = (u64*)&buf_addr[i + SURFACE_STATE_OFFSET_WORD]; -+ *stateBase |= SBA; -+ *stateBase |= 0x01; // enable -+ break; -+ } -+ } -+} -+ -+static int bin_read_cmd_buffer(ipts_info_t *ipts, -+ bin_parse_info_t *parse_info, -+ bin_alloc_info_t *alloc_info, -+ bin_workload_t *wl) -+{ -+ ipts_bin_cmdbuf_t *cmd; -+ intel_ipts_mapbuffer_t *buf; -+ int cmdbuf_idx, size, parsed, parallel_idx, num_of_parallels; -+ -+ size = parse_info->size; -+ parsed = parse_info->parsed; -+ -+ cmd = (ipts_bin_cmdbuf_t *)&parse_info->data[parsed]; -+ -+ if (sizeof(cmd->size) > size - parsed) -+ return -EINVAL; -+ -+ parsed += sizeof(cmd->size); -+ if (cmd->size > size - parsed) -+ return -EINVAL; -+ -+ ipts_dbg(ipts, "cmd buf size = %d\n", cmd->size); -+ -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ /* command buffers are located after the other allocations */ -+ cmdbuf_idx = num_of_parallels * alloc_info->num_of_allocations; -+ for (parallel_idx = 0; parallel_idx < num_of_parallels; parallel_idx++) { -+ buf = ipts_map_buffer(ipts, cmd->size, 0); -+ if (buf == NULL) -+ return -ENOMEM; -+ -+ ipts_dbg(ipts, "cmd_idx[%d] = %d, g:0x%p, c:0x%p\n", parallel_idx, -+ cmdbuf_idx, buf->gfx_addr, buf->cpu_addr); -+ -+ memcpy((void *)buf->cpu_addr, &(cmd->data[0]), cmd->size); -+ patch_SBA(buf->cpu_addr, (u64)buf->gfx_addr, cmd->size); -+ alloc_info->buffs[cmdbuf_idx].buf = buf; -+ wl[parallel_idx].cmdbuf_index = cmdbuf_idx; -+ -+ cmdbuf_idx++; -+ } -+ -+ parsed += cmd->size; -+ parse_info->parsed = parsed; -+ -+ return 0; -+} -+ -+static int bin_find_alloc(ipts_info_t *ipts, -+ bin_alloc_info_t *alloc_info, -+ u32 handle) -+{ -+ int i; -+ -+ for (i = 0; i < alloc_info->num_of_allocations; i++) { -+ if (alloc_info->buffs[i].handle == handle) -+ return i; -+ } -+ -+ return -1; -+} -+ -+static intel_ipts_mapbuffer_t* bin_get_vendor_kernel_output( -+ bin_parse_info_t *parse_info, -+ int parallel_idx) -+{ -+ bin_kernel_info_t *vendor = parse_info->vendor_kernel; -+ bin_alloc_info_t *alloc_info; -+ int buf_idx, vendor_output_idx; -+ -+ alloc_info = vendor->alloc_info; -+ vendor_output_idx = parse_info->interested_vendor_output; -+ -+ if (vendor_output_idx >= alloc_info->num_of_outputs) -+ return NULL; -+ -+ buf_idx = vendor->wl[parallel_idx].iobuf_output[vendor_output_idx]; -+ return alloc_info->buffs[buf_idx].buf; -+} -+ -+static int bin_read_res_list(ipts_info_t *ipts, -+ bin_parse_info_t *parse_info, -+ bin_alloc_info_t *alloc_info, -+ bin_workload_t *wl) -+{ -+ ipts_bin_res_list_t *res_list; -+ ipts_bin_res_t *res; -+ intel_ipts_mapbuffer_t *buf; -+ bin_data_file_info_t *data_file; -+ u8 *bin_data; -+ int i, size, parsed, parallel_idx, num_of_parallels, output_idx = -1; -+ int buf_idx, num_of_alloc; -+ u32 buf_size, flags, io_buf_type; -+ bool initialize; -+ -+ parsed = parse_info->parsed; -+ size = parse_info->size; -+ bin_data = parse_info->data; -+ -+ res_list = (ipts_bin_res_list_t *)&parse_info->data[parsed]; -+ if (sizeof(res_list->num) > (size - parsed)) -+ return -EINVAL; -+ parsed += sizeof(res_list->num); -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ -+ ipts_dbg(ipts, "number of resources %u\n", res_list->num); -+ for (i = 0; i < res_list->num; i++) { -+ initialize = false; -+ io_buf_type = 0; -+ flags = 0; -+ -+ /* initial data */ -+ data_file = NULL; -+ -+ res = (ipts_bin_res_t *)(&(bin_data[parsed])); -+ if (sizeof(res[0]) > (size - parsed)) { -+ return -EINVAL; -+ } -+ -+ ipts_dbg(ipts, "Resource(%d):handle 0x%08x type %u init %u" -+ " size %u alsigned %u\n", -+ i, res->handle, res->type, res->initialize, -+ res->size, res->aligned_size); -+ parsed += sizeof(res[0]); -+ -+ if (res->initialize) { -+ if (res->size > (size - parsed)) { -+ return -EINVAL; -+ } -+ parsed += res->size; -+ } -+ -+ initialize = res->initialize; -+ if (initialize && res->size > sizeof(ipts_bin_io_header_t)) { -+ ipts_bin_io_header_t *io_hdr; -+ io_hdr = (ipts_bin_io_header_t *)(&res->data[0]); -+ if (strncmp(io_hdr->str, "INTELTOUCH", 10) == 0) { -+ data_file = bin_get_data_file_info( -+ parse_info->fw_info, -+ (u32)io_hdr->type); -+ switch (io_hdr->type) { -+ case IPTS_INPUT: -+ ipts_dbg(ipts, "input detected\n"); -+ io_buf_type = IPTS_INPUT_ON; -+ flags = IPTS_BUF_FLAG_CONTIGUOUS; -+ break; -+ case IPTS_OUTPUT: -+ ipts_dbg(ipts, "output detected\n"); -+ io_buf_type = IPTS_OUTPUT_ON; -+ output_idx++; -+ break; -+ default: -+ if ((u32)io_hdr->type > 31) { -+ ipts_err(ipts, -+ "invalid io buffer : %u\n", -+ (u32)io_hdr->type); -+ continue; -+ } -+ -+ if (is_alloc_cont_data(data_file)) -+ flags = IPTS_BUF_FLAG_CONTIGUOUS; -+ -+ io_buf_type = ((u32)1 << (u32)io_hdr->type); -+ ipts_dbg(ipts, "special io buffer %u\n", -+ io_hdr->type); -+ break; -+ } -+ -+ initialize = false; -+ } -+ } -+ -+ num_of_alloc = alloc_info->num_of_allocations; -+ buf_idx = bin_find_alloc(ipts, alloc_info, res->handle); -+ if (buf_idx == -1) { -+ ipts_dbg(ipts, "cannot find alloc info\n"); -+ return -EINVAL; -+ } -+ for (parallel_idx = 0; parallel_idx < num_of_parallels; -+ parallel_idx++, buf_idx += num_of_alloc) { -+ if (!res->aligned_size) -+ continue; -+ -+ if (!(parallel_idx == 0 || -+ (io_buf_type && !is_shared_data(data_file)))) -+ continue; -+ -+ buf_size = res->aligned_size; -+ if (io_buf_type & IPTS_INPUT_ON) { -+ buf_size = max_t(u32, -+ ipts->device_info.frame_size, -+ buf_size); -+ wl[parallel_idx].iobuf_input = buf_idx; -+ } else if (io_buf_type & IPTS_OUTPUT_ON) { -+ wl[parallel_idx].iobuf_output[output_idx] = buf_idx; -+ -+ if (!is_parsing_vendor_kernel(parse_info) && -+ output_idx > 0) { -+ ipts_err(ipts, -+ "postproc with more than one inout" -+ " is not supported : %d\n", output_idx); -+ return -EINVAL; -+ } -+ } -+ -+ if (!is_parsing_vendor_kernel(parse_info) && -+ io_buf_type & IPTS_OUTPUT_ON) { -+ buf = bin_get_vendor_kernel_output( -+ parse_info, -+ parallel_idx); -+ alloc_info->buffs[buf_idx].no_unmap = true; -+ } else -+ buf = ipts_map_buffer(ipts, buf_size, flags); -+ -+ if (buf == NULL) { -+ ipts_dbg(ipts, "ipts_map_buffer failed\n"); -+ return -ENOMEM; -+ } -+ -+ if (initialize) { -+ memcpy((void *)buf->cpu_addr, &(res->data[0]), -+ res->size); -+ } else { -+ if (data_file && strlen(data_file->file_name)) { -+ bin_read_fw(ipts, data_file->file_name, -+ buf->cpu_addr, buf_size); -+ } else if (is_parsing_vendor_kernel(parse_info) || -+ !(io_buf_type & IPTS_OUTPUT_ON)) { -+ memset((void *)buf->cpu_addr, 0, res->size); -+ } -+ } -+ -+ alloc_info->buffs[buf_idx].buf = buf; -+ } -+ } -+ -+ alloc_info->num_of_outputs = output_idx + 1; -+ parse_info->parsed = parsed; -+ -+ return 0; -+} -+ -+static int bin_read_patch_list(ipts_info_t *ipts, -+ bin_parse_info_t *parse_info, -+ bin_alloc_info_t *alloc_info, -+ bin_workload_t *wl) -+{ -+ ipts_bin_patch_list_t *patch_list; -+ ipts_bin_patch_t *patch; -+ intel_ipts_mapbuffer_t *cmd = NULL; -+ u8 *batch; -+ int parsed, size, i, parallel_idx, num_of_parallels, cmd_idx, buf_idx; -+ unsigned int gtt_offset; -+ -+ parsed = parse_info->parsed; -+ size = parse_info->size; -+ patch_list = (ipts_bin_patch_list_t *)&parse_info->data[parsed]; -+ -+ if (sizeof(patch_list->num) > (size - parsed)) { -+ return -EFAULT; -+ } -+ parsed += sizeof(patch_list->num); -+ -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ patch = (ipts_bin_patch_t *)(&patch_list->patch[0]); -+ for (i = 0; i < patch_list->num; i++) { -+ if (sizeof(patch_list->patch[0]) > (size - parsed)) { -+ return -EFAULT; -+ } -+ -+ for (parallel_idx = 0; parallel_idx < num_of_parallels; -+ parallel_idx++) { -+ cmd_idx = wl[parallel_idx].cmdbuf_index; -+ buf_idx = patch[i].index + parallel_idx * -+ alloc_info->num_of_allocations; -+ -+ if (alloc_info->buffs[buf_idx].buf == NULL) { -+ /* buffer shared */ -+ buf_idx = patch[i].index; -+ } -+ -+ cmd = alloc_info->buffs[cmd_idx].buf; -+ batch = (char *)(u64)cmd->cpu_addr; -+ -+ gtt_offset = 0; -+ if(alloc_info->buffs[buf_idx].buf != NULL) { -+ gtt_offset = (u32)(u64) -+ alloc_info->buffs[buf_idx].buf->gfx_addr; -+ } -+ gtt_offset += patch[i].alloc_offset; -+ -+ batch += patch[i].patch_offset; -+ *(u32*)batch = gtt_offset; -+ } -+ -+ parsed += sizeof(patch_list->patch[0]); -+ } -+ -+ parse_info->parsed = parsed; -+ -+ return 0; -+} -+ -+static int bin_read_guc_wq_item(ipts_info_t *ipts, -+ bin_parse_info_t *parse_info, -+ bin_guc_wq_item_t **guc_wq_item) -+{ -+ ipts_bin_guc_wq_info_t *bin_guc_wq; -+ bin_guc_wq_item_t *item; -+ u8 *wi_data; -+ int size, parsed, hdr_size, wi_size; -+ int i, batch_offset; -+ -+ parsed = parse_info->parsed; -+ size = parse_info->size; -+ bin_guc_wq = (ipts_bin_guc_wq_info_t *)&parse_info->data[parsed]; -+ -+ wi_size = bin_guc_wq->size; -+ wi_data = bin_guc_wq->data; -+ batch_offset = bin_guc_wq->batch_offset; -+ ipts_dbg(ipts, "wi size = %d, bt offset = %d\n", wi_size, batch_offset); -+ for (i = 0; i < wi_size / sizeof(u32); i++) { -+ ipts_dbg(ipts, "wi[%d] = 0x%08x\n", i, *((u32*)wi_data + i)); -+ } -+ hdr_size = sizeof(bin_guc_wq->size) + sizeof(bin_guc_wq->batch_offset); -+ -+ if (hdr_size > (size - parsed)) { -+ return -EINVAL; -+ } -+ parsed += hdr_size; -+ -+ item = vmalloc(sizeof(bin_guc_wq_item_t) + wi_size); -+ if (item == NULL) -+ return -ENOMEM; -+ -+ item->size = wi_size; -+ item->batch_offset = batch_offset; -+ memcpy(item->data, wi_data, wi_size); -+ -+ *guc_wq_item = item; -+ -+ parsed += wi_size; -+ parse_info->parsed = parsed; -+ -+ return 0; -+} -+ -+static int bin_setup_guc_workqueue(ipts_info_t *ipts, -+ bin_kernel_list_t *kernel_list) -+{ -+ bin_alloc_info_t *alloc_info; -+ bin_workload_t *wl; -+ bin_kernel_info_t *kernel; -+ u8 *wq_start, *wq_addr, *wi_data; -+ bin_buffer_t *bin_buf; -+ int wq_size, wi_size, parallel_idx, cmd_idx, k_idx, iter_size; -+ int i, num_of_parallels, batch_offset, k_num, total_workload; -+ -+ wq_addr = (u8*)ipts->resource.wq_info.wq_addr; -+ wq_size = ipts->resource.wq_info.wq_size; -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ total_workload = ipts_get_wq_item_size(ipts); -+ k_num = kernel_list->num_of_kernels; -+ -+ iter_size = total_workload * num_of_parallels; -+ if (wq_size % iter_size) { -+ ipts_err(ipts, "wq item cannot fit into wq\n"); -+ return -EINVAL; -+ } -+ -+ wq_start = wq_addr; -+ for (parallel_idx = 0; parallel_idx < num_of_parallels; -+ parallel_idx++) { -+ kernel = &kernel_list->kernels[0]; -+ for (k_idx = 0; k_idx < k_num; k_idx++, kernel++) { -+ wl = kernel->wl; -+ alloc_info = kernel->alloc_info; -+ -+ batch_offset = kernel->guc_wq_item->batch_offset; -+ wi_size = kernel->guc_wq_item->size; -+ wi_data = &kernel->guc_wq_item->data[0]; -+ -+ cmd_idx = wl[parallel_idx].cmdbuf_index; -+ bin_buf = &alloc_info->buffs[cmd_idx]; -+ -+ /* Patch the WQ Data with proper batch buffer offset */ -+ *(u32*)(wi_data + batch_offset) = -+ (u32)(unsigned long)(bin_buf->buf->gfx_addr); -+ -+ memcpy(wq_addr, wi_data, wi_size); -+ -+ wq_addr += wi_size; -+ } -+ } -+ -+ for (i = 0; i < (wq_size / iter_size) - 1; i++) { -+ memcpy(wq_addr, wq_start, iter_size); -+ wq_addr += iter_size; -+ } -+ -+ return 0; -+} -+ -+static int bin_read_bufid_patch(ipts_info_t *ipts, -+ bin_parse_info_t *parse_info, -+ ipts_bin_bufid_patch_t *bufid_patch) -+{ -+ ipts_bin_bufid_patch_t *patch; -+ int size, parsed; -+ -+ parsed = parse_info->parsed; -+ size = parse_info->size; -+ patch = (ipts_bin_bufid_patch_t *)&parse_info->data[parsed]; -+ -+ if (sizeof(ipts_bin_bufid_patch_t) > (size - parsed)) { -+ ipts_dbg(ipts, "invalid bufid info\n"); -+ return -EINVAL; -+ } -+ parsed += sizeof(ipts_bin_bufid_patch_t); -+ -+ memcpy(bufid_patch, patch, sizeof(ipts_bin_bufid_patch_t)); -+ -+ parse_info->parsed = parsed; -+ -+ return 0; -+} -+ -+static int bin_setup_bufid_buffer(ipts_info_t *ipts, bin_kernel_list_t *kernel_list) -+{ -+ intel_ipts_mapbuffer_t *buf, *cmd_buf; -+ bin_kernel_info_t *last_kernel; -+ bin_alloc_info_t *alloc_info; -+ bin_workload_t *wl; -+ u8 *batch; -+ int parallel_idx, num_of_parallels, cmd_idx; -+ u32 mem_offset, imm_offset; -+ -+ buf = ipts_map_buffer(ipts, PAGE_SIZE, 0); -+ if (!buf) { -+ return -ENOMEM; -+ } -+ -+ last_kernel = &kernel_list->kernels[kernel_list->num_of_kernels - 1]; -+ -+ mem_offset = last_kernel->bufid_patch.mem_offset; -+ imm_offset = last_kernel->bufid_patch.imm_offset; -+ wl = last_kernel->wl; -+ alloc_info = last_kernel->alloc_info; -+ -+ /* Initialize the buffer with default value */ -+ *((u32*)buf->cpu_addr) = LASTSUBMITID_DEFAULT_VALUE; -+ ipts->current_buffer_index = LASTSUBMITID_DEFAULT_VALUE; -+ ipts->last_buffer_completed = LASTSUBMITID_DEFAULT_VALUE; -+ ipts->last_submitted_id = (int*)buf->cpu_addr; -+ -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ for (parallel_idx = 0; parallel_idx < num_of_parallels; parallel_idx++) { -+ cmd_idx = wl[parallel_idx].cmdbuf_index; -+ cmd_buf = alloc_info->buffs[cmd_idx].buf; -+ batch = (u8*)(u64)cmd_buf->cpu_addr; -+ -+ *((u32*)(batch + mem_offset)) = (u32)(u64)(buf->gfx_addr); -+ *((u32*)(batch + imm_offset)) = parallel_idx; -+ } -+ -+ kernel_list->bufid_buf = buf; -+ -+ return 0; -+} -+ -+static void unmap_buffers(ipts_info_t *ipts, bin_alloc_info_t *alloc_info) -+{ -+ bin_buffer_t *buffs; -+ int i, num_of_buffers; -+ -+ num_of_buffers = alloc_info->num_of_buffers; -+ buffs = &alloc_info->buffs[0]; -+ -+ for (i = 0; i < num_of_buffers; i++) { -+ if (buffs[i].no_unmap != true && buffs[i].buf != NULL) -+ ipts_unmap_buffer(ipts, buffs[i].buf); -+ } -+} -+ -+static int load_kernel(ipts_info_t *ipts, bin_parse_info_t *parse_info, -+ bin_kernel_info_t *kernel) -+{ -+ ipts_bin_header_t *hdr; -+ bin_workload_t *wl; -+ bin_alloc_info_t *alloc_info; -+ bin_guc_wq_item_t *guc_wq_item = NULL; -+ ipts_bin_bufid_patch_t bufid_patch; -+ int num_of_parallels, ret; -+ -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ -+ /* check header version and magic numbers */ -+ hdr = (ipts_bin_header_t *)parse_info->data; -+ if (hdr->version != IPTS_BIN_HEADER_VERSION || -+ strncmp(hdr->str, "IOCL", 4) != 0) { -+ ipts_err(ipts, "binary header is not correct version = %d, " -+ "string = %c%c%c%c\n", hdr->version, -+ hdr->str[0], hdr->str[1], -+ hdr->str[2], hdr->str[3] ); -+ return -EINVAL; -+ } -+ -+ parse_info->parsed = sizeof(ipts_bin_header_t); -+ wl = vmalloc(sizeof(bin_workload_t) * num_of_parallels); -+ if (wl == NULL) -+ return -ENOMEM; -+ memset(wl, 0, sizeof(bin_workload_t) * num_of_parallels); -+ -+ alloc_info = vmalloc(sizeof(bin_alloc_info_t)); -+ if (alloc_info == NULL) { -+ vfree(wl); -+ return -ENOMEM; -+ } -+ memset(alloc_info, 0, sizeof(bin_alloc_info_t)); -+ -+ ipts_dbg(ipts, "kernel setup(size : %d)\n", parse_info->size); -+ -+ ret = bin_read_allocation_list(ipts, parse_info, alloc_info); -+ if (ret) { -+ ipts_dbg(ipts, "error read_allocation_list\n"); -+ goto setup_error; -+ } -+ -+ ret = bin_read_cmd_buffer(ipts, parse_info, alloc_info, wl); -+ if (ret) { -+ ipts_dbg(ipts, "error read_cmd_buffer\n"); -+ goto setup_error; -+ } -+ -+ ret = bin_read_res_list(ipts, parse_info, alloc_info, wl); -+ if (ret) { -+ ipts_dbg(ipts, "error read_res_list\n"); -+ goto setup_error; -+ } -+ -+ ret = bin_read_patch_list(ipts, parse_info, alloc_info, wl); -+ if (ret) { -+ ipts_dbg(ipts, "error read_patch_list\n"); -+ goto setup_error; -+ } -+ -+ ret = bin_read_guc_wq_item(ipts, parse_info, &guc_wq_item); -+ if (ret) { -+ ipts_dbg(ipts, "error read_guc_workqueue\n"); -+ goto setup_error; -+ } -+ -+ memset(&bufid_patch, 0, sizeof(bufid_patch)); -+ ret = bin_read_bufid_patch(ipts, parse_info, &bufid_patch); -+ if (ret) { -+ ipts_dbg(ipts, "error read_bufid_patch\n"); -+ goto setup_error; -+ } -+ -+ kernel->wl = wl; -+ kernel->alloc_info = alloc_info; -+ kernel->is_vendor = is_parsing_vendor_kernel(parse_info); -+ kernel->guc_wq_item = guc_wq_item; -+ memcpy(&kernel->bufid_patch, &bufid_patch, sizeof(bufid_patch)); -+ -+ return 0; -+ -+setup_error: -+ vfree(guc_wq_item); -+ -+ unmap_buffers(ipts, alloc_info); -+ -+ vfree(alloc_info->buffs); -+ vfree(alloc_info); -+ vfree(wl); -+ -+ return ret; -+} -+ -+void bin_setup_input_output(ipts_info_t *ipts, bin_kernel_list_t *kernel_list) -+{ -+ bin_kernel_info_t *vendor_kernel; -+ bin_workload_t *wl; -+ intel_ipts_mapbuffer_t *buf; -+ bin_alloc_info_t *alloc_info; -+ int parallel_idx, num_of_parallels, i, buf_idx; -+ -+ vendor_kernel = &kernel_list->kernels[0]; -+ -+ wl = vendor_kernel->wl; -+ alloc_info = vendor_kernel->alloc_info; -+ ipts->resource.num_of_outputs = alloc_info->num_of_outputs; -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ -+ for (parallel_idx = 0; parallel_idx < num_of_parallels; parallel_idx++) { -+ buf_idx = wl[parallel_idx].iobuf_input; -+ buf = alloc_info->buffs[buf_idx].buf; -+ -+ ipts_dbg(ipts, "in_buf[%d](%d) c:%p, p:%p, g:%p\n", -+ parallel_idx, buf_idx, (void*)buf->cpu_addr, -+ (void*)buf->phy_addr, (void*)buf->gfx_addr); -+ -+ ipts_set_input_buffer(ipts, parallel_idx, buf->cpu_addr, -+ buf->phy_addr); -+ -+ for (i = 0; i < alloc_info->num_of_outputs; i++) { -+ buf_idx = wl[parallel_idx].iobuf_output[i]; -+ buf = alloc_info->buffs[buf_idx].buf; -+ -+ ipts_dbg(ipts, "out_buf[%d][%d] c:%p, p:%p, g:%p\n", -+ parallel_idx, i, (void*)buf->cpu_addr, -+ (void*)buf->phy_addr, (void*)buf->gfx_addr); -+ -+ ipts_set_output_buffer(ipts, parallel_idx, i, -+ buf->cpu_addr, buf->phy_addr); -+ } -+ } -+} -+ -+static void unload_kernel(ipts_info_t *ipts, bin_kernel_info_t *kernel) -+{ -+ bin_alloc_info_t *alloc_info = kernel->alloc_info; -+ bin_guc_wq_item_t *guc_wq_item = kernel->guc_wq_item; -+ -+ if (guc_wq_item) { -+ vfree(guc_wq_item); -+ } -+ -+ if (alloc_info) { -+ unmap_buffers(ipts, alloc_info); -+ -+ vfree(alloc_info->buffs); -+ vfree(alloc_info); -+ } -+} -+ -+static int setup_kernel(ipts_info_t *ipts, bin_fw_list_t *fw_list) -+{ -+ bin_kernel_list_t *kernel_list = NULL; -+ bin_kernel_info_t *kernel = NULL; -+ const struct firmware *fw = NULL; -+ bin_workload_t *wl; -+ bin_fw_info_t *fw_info; -+ char *fw_name, *fw_data; -+ bin_parse_info_t parse_info; -+ int ret = 0, kernel_idx = 0, num_of_kernels = 0; -+ int vendor_output_idx, total_workload = 0; -+ -+ num_of_kernels = fw_list->num_of_fws; -+ kernel_list = vmalloc(sizeof(*kernel) * num_of_kernels + sizeof(*kernel_list)); -+ if (kernel_list == NULL) -+ return -ENOMEM; -+ -+ memset(kernel_list, 0, sizeof(*kernel) * num_of_kernels + sizeof(*kernel_list)); -+ kernel_list->num_of_kernels = num_of_kernels; -+ kernel = &kernel_list->kernels[0]; -+ -+ fw_data = (char *)&fw_list->fw_info[0]; -+ for (kernel_idx = 0; kernel_idx < num_of_kernels; kernel_idx++) { -+ fw_info = (bin_fw_info_t *)fw_data; -+ fw_name = &fw_info->fw_name[0]; -+ vendor_output_idx = fw_info->vendor_output; -+ ret = ipts_request_firmware(&fw, fw_name, &ipts->cldev->dev); -+ if (ret) { -+ ipts_err(ipts, "cannot read fw %s\n", fw_name); -+ goto error_exit; -+ } -+ -+ parse_info.data = (u8*)fw->data; -+ parse_info.size = fw->size; -+ parse_info.parsed = 0; -+ parse_info.fw_info = fw_info; -+ parse_info.vendor_kernel = (kernel_idx == 0) ? NULL : &kernel[0]; -+ parse_info.interested_vendor_output = vendor_output_idx; -+ -+ ret = load_kernel(ipts, &parse_info, &kernel[kernel_idx]); -+ if (ret) { -+ ipts_err(ipts, "do_setup_kernel error : %d\n", ret); -+ release_firmware(fw); -+ goto error_exit; -+ } -+ -+ release_firmware(fw); -+ -+ total_workload += kernel[kernel_idx].guc_wq_item->size; -+ -+ /* advance to the next kernel */ -+ fw_data += sizeof(bin_fw_info_t); -+ fw_data += sizeof(bin_data_file_info_t) * fw_info->num_of_data_files; -+ } -+ -+ ipts_set_wq_item_size(ipts, total_workload); -+ -+ ret = bin_setup_guc_workqueue(ipts, kernel_list); -+ if (ret) { -+ ipts_dbg(ipts, "error setup_guc_workqueue\n"); -+ goto error_exit; -+ } -+ -+ ret = bin_setup_bufid_buffer(ipts, kernel_list); -+ if (ret) { -+ ipts_dbg(ipts, "error setup_lastbubmit_buffer\n"); -+ goto error_exit; -+ } -+ -+ bin_setup_input_output(ipts, kernel_list); -+ -+ /* workload is not needed during run-time so free them */ -+ for (kernel_idx = 0; kernel_idx < num_of_kernels; kernel_idx++) { -+ wl = kernel[kernel_idx].wl; -+ vfree(wl); -+ } -+ -+ ipts->kernel_handle = (u64)kernel_list; -+ -+ return 0; -+ -+error_exit: -+ -+ for (kernel_idx = 0; kernel_idx < num_of_kernels; kernel_idx++) { -+ wl = kernel[kernel_idx].wl; -+ vfree(wl); -+ unload_kernel(ipts, &kernel[kernel_idx]); -+ } -+ -+ vfree(kernel_list); -+ -+ return ret; -+} -+ -+ -+static void release_kernel(ipts_info_t *ipts) -+{ -+ bin_kernel_list_t *kernel_list; -+ bin_kernel_info_t *kernel; -+ int k_idx, k_num; -+ -+ kernel_list = (bin_kernel_list_t *)ipts->kernel_handle; -+ k_num = kernel_list->num_of_kernels; -+ kernel = &kernel_list->kernels[0]; -+ -+ for (k_idx = 0; k_idx < k_num; k_idx++) { -+ unload_kernel(ipts, kernel); -+ kernel++; -+ } -+ -+ ipts_unmap_buffer(ipts, kernel_list->bufid_buf); -+ -+ vfree(kernel_list); -+ ipts->kernel_handle = 0; -+} -+ -+int ipts_init_kernels(ipts_info_t *ipts) -+{ -+ const struct firmware *config_fw = NULL; -+ const char *config_fw_path = IPTS_FW_CONFIG_FILE; -+ bin_fw_list_t *fw_list; -+ int ret; -+ -+ ret = ipts_open_gpu(ipts); -+ if (ret) { -+ ipts_err(ipts, "open gpu error : %d\n", ret); -+ return ret; -+ } -+ -+ ret = ipts_request_firmware(&config_fw, config_fw_path, &ipts->cldev->dev); -+ if (ret) { -+ ipts_err(ipts, "request firmware error : %d\n", ret); -+ goto close_gpu; -+ } -+ -+ fw_list = (bin_fw_list_t *)config_fw->data; -+ ret = setup_kernel(ipts, fw_list); -+ if (ret) { -+ ipts_err(ipts, "setup kernel error : %d\n", ret); -+ goto close_firmware; -+ } -+ -+ release_firmware(config_fw); -+ -+ return ret; -+ -+close_firmware: -+ release_firmware(config_fw); -+ -+close_gpu: -+ ipts_close_gpu(ipts); -+ -+ return ret; -+} -+ -+void ipts_release_kernels(ipts_info_t *ipts) -+{ -+ release_kernel(ipts); -+ ipts_close_gpu(ipts); -+} -diff --git a/drivers/misc/ipts/ipts-kernel.h b/drivers/misc/ipts/ipts-kernel.h -new file mode 100644 -index 000000000000..0e7f1393b807 ---- /dev/null -+++ b/drivers/misc/ipts/ipts-kernel.h -@@ -0,0 +1,23 @@ -+/* -+ * -+ * Intel Precise Touch & Stylus Linux driver -+ * Copyright (c) 2016, Intel Corporation. -+ * -+ * 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. -+ * -+ */ -+ -+#ifndef _ITPS_GFX_H -+#define _ITPS_GFX_H -+ -+int ipts_init_kernels(ipts_info_t *ipts); -+void ipts_release_kernels(ipts_info_t *ipts); -+ -+#endif -diff --git a/drivers/misc/ipts/ipts-mei-msgs.h b/drivers/misc/ipts/ipts-mei-msgs.h -new file mode 100644 -index 000000000000..8ca146800a47 ---- /dev/null -+++ b/drivers/misc/ipts/ipts-mei-msgs.h -@@ -0,0 +1,585 @@ -+/* -+ * Precise Touch HECI Message -+ * -+ * Copyright (c) 2013-2016, Intel Corporation. -+ * -+ * 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. -+ */ -+ -+#ifndef _IPTS_MEI_MSGS_H_ -+#define _IPTS_MEI_MSGS_H_ -+ -+#include "ipts-sensor-regs.h" -+ -+#pragma pack(1) -+ -+ -+// Initial protocol version -+#define TOUCH_HECI_CLIENT_PROTOCOL_VERSION 10 -+ -+// GUID that identifies the Touch HECI client. -+#define TOUCH_HECI_CLIENT_GUID \ -+ {0x3e8d0870, 0x271a, 0x4208, {0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04}} -+ -+ -+// define C_ASSERT macro to check structure size and fail compile for unexpected mismatch -+#ifndef C_ASSERT -+#define C_ASSERT(e) typedef char __C_ASSERT__[(e)?1:-1] -+#endif -+ -+ -+// General Type Defines for compatibility with HID driver and BIOS -+#ifndef BIT0 -+#define BIT0 1 -+#endif -+#ifndef BIT1 -+#define BIT1 2 -+#endif -+#ifndef BIT2 -+#define BIT2 4 -+#endif -+ -+ -+#define TOUCH_SENSOR_GET_DEVICE_INFO_CMD 0x00000001 -+#define TOUCH_SENSOR_GET_DEVICE_INFO_RSP 0x80000001 -+ -+ -+#define TOUCH_SENSOR_SET_MODE_CMD 0x00000002 -+#define TOUCH_SENSOR_SET_MODE_RSP 0x80000002 -+ -+ -+#define TOUCH_SENSOR_SET_MEM_WINDOW_CMD 0x00000003 -+#define TOUCH_SENSOR_SET_MEM_WINDOW_RSP 0x80000003 -+ -+ -+#define TOUCH_SENSOR_QUIESCE_IO_CMD 0x00000004 -+#define TOUCH_SENSOR_QUIESCE_IO_RSP 0x80000004 -+ -+ -+#define TOUCH_SENSOR_HID_READY_FOR_DATA_CMD 0x00000005 -+#define TOUCH_SENSOR_HID_READY_FOR_DATA_RSP 0x80000005 -+ -+ -+#define TOUCH_SENSOR_FEEDBACK_READY_CMD 0x00000006 -+#define TOUCH_SENSOR_FEEDBACK_READY_RSP 0x80000006 -+ -+ -+#define TOUCH_SENSOR_CLEAR_MEM_WINDOW_CMD 0x00000007 -+#define TOUCH_SENSOR_CLEAR_MEM_WINDOW_RSP 0x80000007 -+ -+ -+#define TOUCH_SENSOR_NOTIFY_DEV_READY_CMD 0x00000008 -+#define TOUCH_SENSOR_NOTIFY_DEV_READY_RSP 0x80000008 -+ -+ -+#define TOUCH_SENSOR_SET_POLICIES_CMD 0x00000009 -+#define TOUCH_SENSOR_SET_POLICIES_RSP 0x80000009 -+ -+ -+#define TOUCH_SENSOR_GET_POLICIES_CMD 0x0000000A -+#define TOUCH_SENSOR_GET_POLICIES_RSP 0x8000000A -+ -+ -+#define TOUCH_SENSOR_RESET_CMD 0x0000000B -+#define TOUCH_SENSOR_RESET_RSP 0x8000000B -+ -+ -+#define TOUCH_SENSOR_READ_ALL_REGS_CMD 0x0000000C -+#define TOUCH_SENSOR_READ_ALL_REGS_RSP 0x8000000C -+ -+ -+#define TOUCH_SENSOR_CMD_ERROR_RSP 0x8FFFFFFF // M2H: ME sends this message to indicate previous command was unrecognized/unsupported -+ -+ -+ -+//******************************************************************* -+// -+// Touch Sensor Status Codes -+// -+//******************************************************************* -+typedef enum touch_status -+{ -+ TOUCH_STATUS_SUCCESS = 0, // 0 Requested operation was successful -+ TOUCH_STATUS_INVALID_PARAMS, // 1 Invalid parameter(s) sent -+ TOUCH_STATUS_ACCESS_DENIED, // 2 Unable to validate address range -+ TOUCH_STATUS_CMD_SIZE_ERROR, // 3 HECI message incorrect size for specified command -+ TOUCH_STATUS_NOT_READY, // 4 Memory window not set or device is not armed for operation -+ TOUCH_STATUS_REQUEST_OUTSTANDING, // 5 There is already an outstanding message of the same type, must wait for response before sending another request of that type -+ TOUCH_STATUS_NO_SENSOR_FOUND, // 6 Sensor could not be found. Either no sensor is connected, the sensor has not yet initialized, or the system is improperly configured. -+ TOUCH_STATUS_OUT_OF_MEMORY, // 7 Not enough memory/storage for requested operation -+ TOUCH_STATUS_INTERNAL_ERROR, // 8 Unexpected error occurred -+ TOUCH_STATUS_SENSOR_DISABLED, // 9 Used in TOUCH_SENSOR_HID_READY_FOR_DATA_RSP to indicate sensor has been disabled or reset and must be reinitialized. -+ TOUCH_STATUS_COMPAT_CHECK_FAIL, // 10 Used to indicate compatibility revision check between sensor and ME failed, or protocol ver between ME/HID/Kernels failed. -+ TOUCH_STATUS_SENSOR_EXPECTED_RESET, // 11 Indicates sensor went through a reset initiated by ME -+ TOUCH_STATUS_SENSOR_UNEXPECTED_RESET, // 12 Indicates sensor went through an unexpected reset -+ TOUCH_STATUS_RESET_FAILED, // 13 Requested sensor reset failed to complete -+ TOUCH_STATUS_TIMEOUT, // 14 Operation timed out -+ TOUCH_STATUS_TEST_MODE_FAIL, // 15 Test mode pattern did not match expected values -+ TOUCH_STATUS_SENSOR_FAIL_FATAL, // 16 Indicates sensor reported fatal error during reset sequence. Further progress is not possible. -+ TOUCH_STATUS_SENSOR_FAIL_NONFATAL, // 17 Indicates sensor reported non-fatal error during reset sequence. HID/BIOS logs error and attempts to continue. -+ TOUCH_STATUS_INVALID_DEVICE_CAPS, // 18 Indicates sensor reported invalid capabilities, such as not supporting required minimum frequency or I/O mode. -+ TOUCH_STATUS_QUIESCE_IO_IN_PROGRESS, // 19 Indicates that command cannot be complete until ongoing Quiesce I/O flow has completed. -+ TOUCH_STATUS_MAX // 20 Invalid value, never returned -+} touch_status_t; -+C_ASSERT(sizeof(touch_status_t) == 4); -+ -+ -+ -+//******************************************************************* -+// -+// Defines for message structures used for Host to ME communication -+// -+//******************************************************************* -+ -+ -+typedef enum touch_sensor_mode -+{ -+ TOUCH_SENSOR_MODE_HID = 0, // Set mode to HID mode -+ TOUCH_SENSOR_MODE_RAW_DATA, // Set mode to Raw Data mode -+ TOUCH_SENSOR_MODE_SENSOR_DEBUG = 4, // Used like TOUCH_SENSOR_MODE_HID but data coming from sensor is not necessarily a HID packet. -+ TOUCH_SENSOR_MODE_MAX // Invalid value -+} touch_sensor_mode_t; -+C_ASSERT(sizeof(touch_sensor_mode_t) == 4); -+ -+typedef struct touch_sensor_set_mode_cmd_data -+{ -+ touch_sensor_mode_t sensor_mode; // Indicate desired sensor mode -+ u32 Reserved[3]; // For future expansion -+} touch_sensor_set_mode_cmd_data_t; -+C_ASSERT(sizeof(touch_sensor_set_mode_cmd_data_t) == 16); -+ -+ -+#define TOUCH_SENSOR_MAX_DATA_BUFFERS 16 -+#define TOUCH_HID_2_ME_BUFFER_ID TOUCH_SENSOR_MAX_DATA_BUFFERS -+#define TOUCH_HID_2_ME_BUFFER_SIZE_MAX 1024 -+#define TOUCH_INVALID_BUFFER_ID 0xFF -+ -+typedef struct touch_sensor_set_mem_window_cmd_data -+{ -+ u32 touch_data_buffer_addr_lower[TOUCH_SENSOR_MAX_DATA_BUFFERS]; // Lower 32 bits of Touch Data Buffer physical address. Size of each buffer should be TOUCH_SENSOR_GET_DEVICE_INFO_RSP_DATA.FrameSize -+ u32 touch_data_buffer_addr_upper[TOUCH_SENSOR_MAX_DATA_BUFFERS]; // Upper 32 bits of Touch Data Buffer physical address. Size of each buffer should be TOUCH_SENSOR_GET_DEVICE_INFO_RSP_DATA.FrameSize -+ u32 tail_offset_addr_lower; // Lower 32 bits of Tail Offset physical address -+ u32 tail_offset_addr_upper; // Upper 32 bits of Tail Offset physical address, always 32 bit, increment by WorkQueueItemSize -+ u32 doorbell_cookie_addr_lower; // Lower 32 bits of Doorbell register physical address -+ u32 doorbell_cookie_addr_upper; // Upper 32 bits of Doorbell register physical address, always 32 bit, increment as integer, rollover to 1 -+ u32 feedback_buffer_addr_lower[TOUCH_SENSOR_MAX_DATA_BUFFERS]; // Lower 32 bits of Feedback Buffer physical address. Size of each buffer should be TOUCH_SENSOR_GET_DEVICE_INFO_RSP_DATA.FeedbackSize -+ u32 feedback_buffer_addr_upper[TOUCH_SENSOR_MAX_DATA_BUFFERS]; // Upper 32 bits of Feedback Buffer physical address. Size of each buffer should be TOUCH_SENSOR_GET_DEVICE_INFO_RSP_DATA.FeedbackSize -+ u32 hid2me_buffer_addr_lower; // Lower 32 bits of dedicated HID to ME communication buffer. Size is Hid2MeBufferSize. -+ u32 hid2me_buffer_addr_upper; // Upper 32 bits of dedicated HID to ME communication buffer. Size is Hid2MeBufferSize. -+ u32 hid2me_buffer_size; // Size in bytes of Hid2MeBuffer, can be no bigger than TOUCH_HID_2_ME_BUFFER_SIZE_MAX -+ u8 reserved1; // For future expansion -+ u8 work_queue_item_size; // Size in bytes of the GuC Work Queue Item pointed to by TailOffset -+ u16 work_queue_size; // Size in bytes of the entire GuC Work Queue -+ u32 reserved[8]; // For future expansion -+} touch_sensor_set_mem_window_cmd_data_t; -+C_ASSERT(sizeof(touch_sensor_set_mem_window_cmd_data_t) == 320); -+ -+ -+#define TOUCH_SENSOR_QUIESCE_FLAG_GUC_RESET BIT0 // indicates GuC got reset and ME must re-read GuC data such as TailOffset and Doorbell Cookie values -+ -+typedef struct touch_sensor_quiesce_io_cmd_data -+{ -+ u32 quiesce_flags; // Optionally set TOUCH_SENSOR_QUIESCE_FLAG_GUC_RESET -+ u32 reserved[2]; -+} touch_sensor_quiesce_io_cmd_data_t; -+C_ASSERT(sizeof(touch_sensor_quiesce_io_cmd_data_t) == 12); -+ -+ -+typedef struct touch_sensor_feedback_ready_cmd_data -+{ -+ u8 feedback_index; // Index value from 0 to TOUCH_HID_2_ME_BUFFER_ID used to indicate which Feedback Buffer to use. Using special value TOUCH_HID_2_ME_BUFFER_ID -+ // is an indication to ME to get feedback data from the Hid2Me buffer instead of one of the standard Feedback buffers. -+ u8 reserved1[3]; // For future expansion -+ u32 transaction_id; // Transaction ID that was originally passed to host in TOUCH_HID_PRIVATE_DATA. Used to track round trip of a given transaction for performance measurements. -+ u32 reserved2[2]; // For future expansion -+} touch_sensor_feedback_ready_cmd_data_t; -+C_ASSERT(sizeof(touch_sensor_feedback_ready_cmd_data_t) == 16); -+ -+ -+#define TOUCH_DEFAULT_DOZE_TIMER_SECONDS 30 -+ -+typedef enum touch_freq_override -+{ -+ TOUCH_FREQ_OVERRIDE_NONE, // Do not apply any override -+ TOUCH_FREQ_OVERRIDE_10MHZ, // Force frequency to 10MHz (not currently supported) -+ TOUCH_FREQ_OVERRIDE_17MHZ, // Force frequency to 17MHz -+ TOUCH_FREQ_OVERRIDE_30MHZ, // Force frequency to 30MHz -+ TOUCH_FREQ_OVERRIDE_50MHZ, // Force frequency to 50MHz (not currently supported) -+ TOUCH_FREQ_OVERRIDE_MAX // Invalid value -+} touch_freq_override_t; -+C_ASSERT(sizeof(touch_freq_override_t) == 4); -+ -+typedef enum touch_spi_io_mode_override -+{ -+ TOUCH_SPI_IO_MODE_OVERRIDE_NONE, // Do not apply any override -+ TOUCH_SPI_IO_MODE_OVERRIDE_SINGLE, // Force Single I/O -+ TOUCH_SPI_IO_MODE_OVERRIDE_DUAL, // Force Dual I/O -+ TOUCH_SPI_IO_MODE_OVERRIDE_QUAD, // Force Quad I/O -+ TOUCH_SPI_IO_MODE_OVERRIDE_MAX // Invalid value -+} touch_spi_io_mode_override_t; -+C_ASSERT(sizeof(touch_spi_io_mode_override_t) == 4); -+ -+// Debug Policy bits used by TOUCH_POLICY_DATA.DebugOverride -+#define TOUCH_DBG_POLICY_OVERRIDE_STARTUP_TIMER_DIS BIT0 // Disable sensor startup timer -+#define TOUCH_DBG_POLICY_OVERRIDE_SYNC_BYTE_DIS BIT1 // Disable Sync Byte check -+#define TOUCH_DBG_POLICY_OVERRIDE_ERR_RESET_DIS BIT2 // Disable error resets -+ -+typedef struct touch_policy_data -+{ -+ u32 reserved0; // For future expansion. -+ u32 doze_timer :16; // Value in seconds, after which ME will put the sensor into Doze power state if no activity occurs. Set -+ // to 0 to disable Doze mode (not recommended). Value will be set to TOUCH_DEFAULT_DOZE_TIMER_SECONDS by -+ // default. -+ touch_freq_override_t freq_override :3; // Override frequency requested by sensor -+ touch_spi_io_mode_override_t spi_io_override :3; // Override IO mode requested by sensor -+ u32 reserved1 :10; // For future expansion -+ u32 reserved2; // For future expansion -+ u32 debug_override; // Normally all bits will be zero. Bits will be defined as needed for enabling special debug features -+} touch_policy_data_t; -+C_ASSERT(sizeof(touch_policy_data_t) == 16); -+ -+typedef struct touch_sensor_set_policies_cmd_data -+{ -+ touch_policy_data_t policy_data; // Contains the desired policy to be set -+} touch_sensor_set_policies_cmd_data_t; -+C_ASSERT(sizeof(touch_sensor_set_policies_cmd_data_t) == 16); -+ -+ -+typedef enum touch_sensor_reset_type -+{ -+ TOUCH_SENSOR_RESET_TYPE_HARD, // Hardware Reset using dedicated GPIO pin -+ TOUCH_SENSOR_RESET_TYPE_SOFT, // Software Reset using command written over SPI interface -+ TOUCH_SENSOR_RESET_TYPE_MAX // Invalid value -+} touch_sensor_reset_type_t; -+C_ASSERT(sizeof(touch_sensor_reset_type_t) == 4); -+ -+typedef struct touch_sensor_reset_cmd_data -+{ -+ touch_sensor_reset_type_t reset_type; // Indicate desired reset type -+ u32 reserved; // For future expansion -+} touch_sensor_reset_cmd_data_t; -+C_ASSERT(sizeof(touch_sensor_reset_cmd_data_t) == 8); -+ -+ -+// -+// Host to ME message -+// -+typedef struct touch_sensor_msg_h2m -+{ -+ u32 command_code; -+ union -+ { -+ touch_sensor_set_mode_cmd_data_t set_mode_cmd_data; -+ touch_sensor_set_mem_window_cmd_data_t set_window_cmd_data; -+ touch_sensor_quiesce_io_cmd_data_t quiesce_io_cmd_data; -+ touch_sensor_feedback_ready_cmd_data_t feedback_ready_cmd_data; -+ touch_sensor_set_policies_cmd_data_t set_policies_cmd_data; -+ touch_sensor_reset_cmd_data_t reset_cmd_data; -+ } h2m_data; -+} touch_sensor_msg_h2m_t; -+C_ASSERT(sizeof(touch_sensor_msg_h2m_t) == 324); -+ -+ -+//******************************************************************* -+// -+// Defines for message structures used for ME to Host communication -+// -+//******************************************************************* -+ -+// I/O mode values used by TOUCH_SENSOR_GET_DEVICE_INFO_RSP_DATA. -+typedef enum touch_spi_io_mode -+{ -+ TOUCH_SPI_IO_MODE_SINGLE = 0, // Sensor set for Single I/O SPI -+ TOUCH_SPI_IO_MODE_DUAL, // Sensor set for Dual I/O SPI -+ TOUCH_SPI_IO_MODE_QUAD, // Sensor set for Quad I/O SPI -+ TOUCH_SPI_IO_MODE_MAX // Invalid value -+} touch_spi_io_mode_t; -+C_ASSERT(sizeof(touch_spi_io_mode_t) == 4); -+ -+// -+// TOUCH_SENSOR_GET_DEVICE_INFO_RSP code is sent in response to TOUCH_SENSOR_GET_DEVICE_INFO_CMD. This code will be followed -+// by TOUCH_SENSOR_GET_DEVICE_INFO_RSP_DATA. -+// -+// Possible Status values: -+// TOUCH_STATUS_SUCCESS: Command was processed successfully and sensor details are reported. -+// TOUCH_STATUS_CMD_SIZE_ERROR: Command sent did not match expected size. Other fields will not contain valid data. -+// TOUCH_STATUS_NO_SENSOR_FOUND: Sensor has not yet been detected. Other fields will not contain valid data. -+// TOUCH_STATUS_INVALID_DEVICE_CAPS: Indicates sensor does not support minimum required Frequency or I/O Mode. ME firmware will choose best possible option for the errant -+// field. Caller should attempt to continue. -+// TOUCH_STATUS_COMPAT_CHECK_FAIL: Indicates TouchIC/ME compatibility mismatch. Caller should attempt to continue. -+// -+typedef struct touch_sensor_get_device_info_rsp_data -+{ -+ u16 vendor_id; // Touch Sensor vendor ID -+ u16 device_id; // Touch Sensor device ID -+ u32 hw_rev; // Touch Sensor Hardware Revision -+ u32 fw_rev; // Touch Sensor Firmware Revision -+ u32 frame_size; // Max size of one frame returned by Touch IC in bytes. This data will be TOUCH_RAW_DATA_HDR followed -+ // by a payload. The payload can be raw data or a HID structure depending on mode. -+ u32 feedback_size; // Max size of one Feedback structure in bytes -+ touch_sensor_mode_t sensor_mode; // Current operating mode of the sensor -+ u32 max_touch_points :8; // Maximum number of simultaneous touch points that can be reported by sensor -+ touch_freq_t spi_frequency :8; // SPI bus Frequency supported by sensor and ME firmware -+ touch_spi_io_mode_t spi_io_mode :8; // SPI bus I/O Mode supported by sensor and ME firmware -+ u32 reserved0 :8; // For future expansion -+ u8 sensor_minor_eds_rev; // Minor version number of EDS spec supported by sensor (from Compat Rev ID Reg) -+ u8 sensor_major_eds_rev; // Major version number of EDS spec supported by sensor (from Compat Rev ID Reg) -+ u8 me_minor_eds_rev; // Minor version number of EDS spec supported by ME -+ u8 me_major_eds_rev; // Major version number of EDS spec supported by ME -+ u8 sensor_eds_intf_rev; // EDS Interface Revision Number supported by sensor (from Compat Rev ID Reg) -+ u8 me_eds_intf_rev; // EDS Interface Revision Number supported by ME -+ u8 kernel_compat_ver; // EU Kernel Compatibility Version (from Compat Rev ID Reg) -+ u8 reserved1; // For future expansion -+ u32 reserved2[2]; // For future expansion -+} touch_sensor_get_device_info_rsp_data_t; -+C_ASSERT(sizeof(touch_sensor_get_device_info_rsp_data_t) == 44); -+ -+ -+// -+// TOUCH_SENSOR_SET_MODE_RSP code is sent in response to TOUCH_SENSOR_SET_MODE_CMD. This code will be followed -+// by TOUCH_SENSOR_SET_MODE_RSP_DATA. -+// -+// Possible Status values: -+// TOUCH_STATUS_SUCCESS: Command was processed successfully and mode was set. -+// TOUCH_STATUS_CMD_SIZE_ERROR: Command sent did not match expected size. Other fields will not contain valid data. -+// TOUCH_STATUS_INVALID_PARAMS: Input parameters are out of range. -+// -+typedef struct touch_sensor_set_mode_rsp_data -+{ -+ u32 reserved[3]; // For future expansion -+} touch_sensor_set_mode_rsp_data_t; -+C_ASSERT(sizeof(touch_sensor_set_mode_rsp_data_t) == 12); -+ -+ -+// -+// TOUCH_SENSOR_SET_MEM_WINDOW_RSP code is sent in response to TOUCH_SENSOR_SET_MEM_WINDOW_CMD. This code will be followed -+// by TOUCH_SENSOR_SET_MEM_WINDOW_RSP_DATA. -+// -+// Possible Status values: -+// TOUCH_STATUS_SUCCESS: Command was processed successfully and memory window was set. -+// TOUCH_STATUS_CMD_SIZE_ERROR: Command sent did not match expected size. Other fields will not contain valid data. -+// TOUCH_STATUS_INVALID_PARAMS: Input parameters are out of range. -+// TOUCH_STATUS_ACCESS_DENIED: Unable to map host address ranges for DMA. -+// TOUCH_STATUS_OUT_OF_MEMORY: Unable to allocate enough space for needed buffers. -+// -+typedef struct touch_sensor_set_mem_window_rsp_data -+{ -+ u32 reserved[3]; // For future expansion -+} touch_sensor_set_mem_window_rsp_data_t; -+C_ASSERT(sizeof(touch_sensor_set_mem_window_rsp_data_t) == 12); -+ -+ -+// -+// TOUCH_SENSOR_QUIESCE_IO_RSP code is sent in response to TOUCH_SENSOR_QUIESCE_IO_CMD. This code will be followed -+// by TOUCH_SENSOR_QUIESCE_IO_RSP_DATA. -+// -+// Possible Status values: -+// TOUCH_STATUS_SUCCESS: Command was processed successfully and touch flow has stopped. -+// TOUCH_STATUS_CMD_SIZE_ERROR: Command sent did not match expected size. Other fields will not contain valid data. -+// TOUCH_STATUS_QUIESCE_IO_IN_PROGRESS: Indicates that Quiesce I/O is already in progress and this command cannot be accepted at this time. -+// TOUCH_STATIS_TIMEOUT: Indicates ME timed out waiting for Quiesce I/O flow to complete. -+// -+typedef struct touch_sensor_quiesce_io_rsp_data -+{ -+ u32 reserved[3]; // For future expansion -+} touch_sensor_quiesce_io_rsp_data_t; -+C_ASSERT(sizeof(touch_sensor_quiesce_io_rsp_data_t) == 12); -+ -+ -+// Reset Reason values used in TOUCH_SENSOR_HID_READY_FOR_DATA_RSP_DATA -+typedef enum touch_reset_reason -+{ -+ TOUCH_RESET_REASON_UNKNOWN = 0, // Reason for sensor reset is not known -+ TOUCH_RESET_REASON_FEEDBACK_REQUEST, // Reset was requested as part of TOUCH_SENSOR_FEEDBACK_READY_CMD -+ TOUCH_RESET_REASON_HECI_REQUEST, // Reset was requested via TOUCH_SENSOR_RESET_CMD -+ TOUCH_RESET_REASON_MAX -+} touch_reset_reason_t; -+C_ASSERT(sizeof(touch_reset_reason_t) == 4); -+ -+// -+// TOUCH_SENSOR_HID_READY_FOR_DATA_RSP code is sent in response to TOUCH_SENSOR_HID_READY_FOR_DATA_CMD. This code will be followed -+// by TOUCH_SENSOR_HID_READY_FOR_DATA_RSP_DATA. -+// -+// Possible Status values: -+// TOUCH_STATUS_SUCCESS: Command was processed successfully and HID data was sent by DMA. This will only be sent in HID mode. -+// TOUCH_STATUS_CMD_SIZE_ERROR: Command sent did not match expected size. Other fields will not contain valid data. -+// TOUCH_STATUS_REQUEST_OUTSTANDING: Previous request is still outstanding, ME FW cannot handle another request for the same command. -+// TOUCH_STATUS_NOT_READY: Indicates memory window has not yet been set by BIOS/HID. -+// TOUCH_STATUS_SENSOR_DISABLED: Indicates that ME to HID communication has been stopped either by TOUCH_SENSOR_QUIESCE_IO_CMD or TOUCH_SENSOR_CLEAR_MEM_WINDOW_CMD. -+// TOUCH_STATUS_SENSOR_UNEXPECTED_RESET: Sensor signaled a Reset Interrupt. ME did not expect this and has no info about why this occurred. -+// TOUCH_STATUS_SENSOR_EXPECTED_RESET: Sensor signaled a Reset Interrupt. ME either directly requested this reset, or it was expected as part of a defined flow in the EDS. -+// TOUCH_STATUS_QUIESCE_IO_IN_PROGRESS: Indicates that Quiesce I/O is already in progress and this command cannot be accepted at this time. -+// TOUCH_STATUS_TIMEOUT: Sensor did not generate a reset interrupt in the time allotted. Could indicate sensor is not connected or malfunctioning. -+// -+typedef struct touch_sensor_hid_ready_for_data_rsp_data -+{ -+ u32 data_size; // Size of the data the ME DMA'd into a RawDataBuffer. Valid only when Status == TOUCH_STATUS_SUCCESS -+ u8 touch_data_buffer_index; // Index to indicate which RawDataBuffer was used. Valid only when Status == TOUCH_STATUS_SUCCESS -+ u8 reset_reason; // If Status is TOUCH_STATUS_SENSOR_EXPECTED_RESET, ME will provide the cause. See TOUCH_RESET_REASON. -+ u8 reserved1[2]; // For future expansion -+ u32 reserved2[5]; // For future expansion -+} touch_sensor_hid_ready_for_data_rsp_data_t; -+C_ASSERT(sizeof(touch_sensor_hid_ready_for_data_rsp_data_t) == 28); -+ -+ -+// -+// TOUCH_SENSOR_FEEDBACK_READY_RSP code is sent in response to TOUCH_SENSOR_FEEDBACK_READY_CMD. This code will be followed -+// by TOUCH_SENSOR_FEEDBACK_READY_RSP_DATA. -+// -+// Possible Status values: -+// TOUCH_STATUS_SUCCESS: Command was processed successfully and any feedback or commands were sent to sensor. -+// TOUCH_STATUS_CMD_SIZE_ERROR: Command sent did not match expected size. Other fields will not contain valid data. -+// TOUCH_STATUS_INVALID_PARAMS: Input parameters are out of range. -+// TOUCH_STATUS_COMPAT_CHECK_FAIL Indicates ProtocolVer does not match ME supported version. (non-fatal error) -+// TOUCH_STATUS_INTERNAL_ERROR: Unexpected error occurred. This should not normally be seen. -+// TOUCH_STATUS_OUT_OF_MEMORY: Insufficient space to store Calibration Data -+// -+typedef struct touch_sensor_feedback_ready_rsp_data -+{ -+ u8 feedback_index; // Index value from 0 to TOUCH_SENSOR_MAX_DATA_BUFFERS used to indicate which Feedback Buffer to use -+ u8 reserved1[3]; // For future expansion -+ u32 reserved2[6]; // For future expansion -+} touch_sensor_feedback_ready_rsp_data_t; -+C_ASSERT(sizeof(touch_sensor_feedback_ready_rsp_data_t) == 28); -+ -+ -+// -+// TOUCH_SENSOR_CLEAR_MEM_WINDOW_RSP code is sent in response to TOUCH_SENSOR_CLEAR_MEM_WINDOW_CMD. This code will be followed -+// by TOUCH_SENSOR_CLEAR_MEM_WINDOW_RSP_DATA. -+// -+// Possible Status values: -+// TOUCH_STATUS_SUCCESS: Command was processed successfully and memory window was set. -+// TOUCH_STATUS_CMD_SIZE_ERROR: Command sent did not match expected size. Other fields will not contain valid data. -+// TOUCH_STATUS_INVALID_PARAMS: Input parameters are out of range. -+// TOUCH_STATUS_QUIESCE_IO_IN_PROGRESS: Indicates that Quiesce I/O is already in progress and this command cannot be accepted at this time. -+// -+typedef struct touch_sensor_clear_mem_window_rsp_data -+{ -+ u32 reserved[3]; // For future expansion -+} touch_sensor_clear_mem_window_rsp_data_t; -+C_ASSERT(sizeof(touch_sensor_clear_mem_window_rsp_data_t) == 12); -+ -+ -+// -+// TOUCH_SENSOR_NOTIFY_DEV_READY_RSP code is sent in response to TOUCH_SENSOR_NOTIFY_DEV_READY_CMD. This code will be followed -+// by TOUCH_SENSOR_NOTIFY_DEV_READY_RSP_DATA. -+// -+// Possible Status values: -+// TOUCH_STATUS_SUCCESS: Command was processed successfully and sensor has been detected by ME FW. -+// TOUCH_STATUS_CMD_SIZE_ERROR: Command sent did not match expected size. -+// TOUCH_STATUS_REQUEST_OUTSTANDING: Previous request is still outstanding, ME FW cannot handle another request for the same command. -+// TOUCH_STATUS_TIMEOUT: Sensor did not generate a reset interrupt in the time allotted. Could indicate sensor is not connected or malfunctioning. -+// TOUCH_STATUS_SENSOR_FAIL_FATAL: Sensor indicated a fatal error, further operation is not possible. Error details can be found in ErrReg. -+// TOUCH_STATUS_SENSOR_FAIL_NONFATAL: Sensor indicated a non-fatal error. Error should be logged by caller and init flow can continue. Error details can be found in ErrReg. -+// -+typedef struct touch_sensor_notify_dev_ready_rsp_data -+{ -+ touch_err_reg_t err_reg; // Value of sensor Error Register, field is only valid for Status == TOUCH_STATUS_SENSOR_FAIL_FATAL or TOUCH_STATUS_SENSOR_FAIL_NONFATAL -+ u32 reserved[2]; // For future expansion -+} touch_sensor_notify_dev_ready_rsp_data_t; -+C_ASSERT(sizeof(touch_sensor_notify_dev_ready_rsp_data_t) == 12); -+ -+ -+// -+// TOUCH_SENSOR_SET_POLICIES_RSP code is sent in response to TOUCH_SENSOR_SET_POLICIES_CMD. This code will be followed -+// by TOUCH_SENSOR_SET_POLICIES_RSP_DATA. -+// -+// Possible Status values: -+// TOUCH_STATUS_SUCCESS: Command was processed successfully and new policies were set. -+// TOUCH_STATUS_CMD_SIZE_ERROR: Command sent did not match expected size. Other fields will not contain valid data. -+// TOUCH_STATUS_INVALID_PARAMS: Input parameters are out of range. -+// -+typedef struct touch_sensor_set_policies_rsp_data -+{ -+ u32 reserved[3]; // For future expansion -+} touch_sensor_set_policies_rsp_data_t; -+C_ASSERT(sizeof(touch_sensor_set_policies_rsp_data_t) == 12); -+ -+ -+// -+// TOUCH_SENSOR_GET_POLICIES_RSP code is sent in response to TOUCH_SENSOR_GET_POLICIES_CMD. This code will be followed -+// by TOUCH_SENSOR_GET_POLICIES_RSP_DATA. -+// -+// Possible Status values: -+// TOUCH_STATUS_SUCCESS: Command was processed successfully and new policies were set. -+// TOUCH_STATUS_CMD_SIZE_ERROR: Command sent did not match expected size. Other fields will not contain valid data. -+// -+typedef struct touch_sensor_get_policies_rsp_data -+{ -+ touch_policy_data_t policy_data; // Contains the current policy -+} touch_sensor_get_policies_rsp_data_t; -+C_ASSERT(sizeof(touch_sensor_get_policies_rsp_data_t) == 16); -+ -+ -+// -+// TOUCH_SENSOR_RESET_RSP code is sent in response to TOUCH_SENSOR_RESET_CMD. This code will be followed -+// by TOUCH_SENSOR_RESET_RSP_DATA. -+// -+// Possible Status values: -+// TOUCH_STATUS_SUCCESS: Command was processed successfully and sensor reset was completed. -+// TOUCH_STATUS_CMD_SIZE_ERROR: Command sent did not match expected size. Other fields will not contain valid data. -+// TOUCH_STATUS_INVALID_PARAMS: Input parameters are out of range. -+// TOUCH_STATUS_TIMEOUT: Sensor did not generate a reset interrupt in the time allotted. Could indicate sensor is not connected or malfunctioning. -+// TOUCH_STATUS_RESET_FAILED: Sensor generated an invalid or unexpected interrupt. -+// TOUCH_STATUS_QUIESCE_IO_IN_PROGRESS: Indicates that Quiesce I/O is already in progress and this command cannot be accepted at this time. -+// -+typedef struct touch_sensor_reset_rsp_data -+{ -+ u32 reserved[3]; // For future expansion -+} touch_sensor_reset_rsp_data_t; -+C_ASSERT(sizeof(touch_sensor_reset_rsp_data_t) == 12); -+ -+ -+// -+// TOUCH_SENSOR_READ_ALL_REGS_RSP code is sent in response to TOUCH_SENSOR_READ_ALL_REGS_CMD. This code will be followed -+// by TOUCH_SENSOR_READ_ALL_REGS_RSP_DATA. -+// -+// Possible Status values: -+// TOUCH_STATUS_SUCCESS: Command was processed successfully and new policies were set. -+// TOUCH_STATUS_CMD_SIZE_ERROR: Command sent did not match expected size. Other fields will not contain valid data. -+// -+typedef struct touch_sensor_read_all_regs_rsp_data -+{ -+ touch_reg_block_t sensor_regs; // Returns first 64 bytes of register space used for normal touch operation. Does not include test mode register. -+ u32 reserved[4]; -+} touch_sensor_read_all_regs_rsp_data_t; -+C_ASSERT(sizeof(touch_sensor_read_all_regs_rsp_data_t) == 80); -+ -+// -+// ME to Host Message -+// -+typedef struct touch_sensor_msg_m2h -+{ -+ u32 command_code; -+ touch_status_t status; -+ union -+ { -+ touch_sensor_get_device_info_rsp_data_t device_info_rsp_data; -+ touch_sensor_set_mode_rsp_data_t set_mode_rsp_data; -+ touch_sensor_set_mem_window_rsp_data_t set_mem_window_rsp_data; -+ touch_sensor_quiesce_io_rsp_data_t quiesce_io_rsp_data; -+ touch_sensor_hid_ready_for_data_rsp_data_t hid_ready_for_data_rsp_data; -+ touch_sensor_feedback_ready_rsp_data_t feedback_ready_rsp_data; -+ touch_sensor_clear_mem_window_rsp_data_t clear_mem_window_rsp_data; -+ touch_sensor_notify_dev_ready_rsp_data_t notify_dev_ready_rsp_data; -+ touch_sensor_set_policies_rsp_data_t set_policies_rsp_data; -+ touch_sensor_get_policies_rsp_data_t get_policies_rsp_data; -+ touch_sensor_reset_rsp_data_t reset_rsp_data; -+ touch_sensor_read_all_regs_rsp_data_t read_all_regs_rsp_data; -+ } m2h_data; -+} touch_sensor_msg_m2h_t; -+C_ASSERT(sizeof(touch_sensor_msg_m2h_t) == 88); -+ -+ -+#define TOUCH_MSG_SIZE_MAX_BYTES (MAX(sizeof(touch_sensor_msg_m2h_t), sizeof(touch_sensor_msg_h2m_t))) -+ -+#pragma pack() -+ -+#endif // _IPTS_MEI_MSGS_H_ -diff --git a/drivers/misc/ipts/ipts-mei.c b/drivers/misc/ipts/ipts-mei.c -new file mode 100644 -index 000000000000..6fbe257bc7cc ---- /dev/null -+++ b/drivers/misc/ipts/ipts-mei.c -@@ -0,0 +1,290 @@ -+/* -+ * MEI client driver for Intel Precise Touch and Stylus -+ * -+ * Copyright (c) 2016, Intel Corporation. -+ * -+ * 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. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "ipts.h" -+#include "ipts-fw.h" -+#include "ipts-hid.h" -+#include "ipts-params.h" -+#include "ipts-msg-handler.h" -+#include "ipts-mei-msgs.h" -+#include "ipts-binary-spec.h" -+#include "ipts-state.h" -+ -+#define IPTS_DRIVER_NAME "ipts" -+#define IPTS_MEI_UUID UUID_LE(0x3e8d0870, 0x271a, 0x4208, \ -+ 0x8e, 0xb5, 0x9a, 0xcb, 0x94, 0x02, 0xae, 0x04) -+ -+static struct mei_cl_device_id ipts_mei_cl_tbl[] = { -+ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY}, -+ {} -+}; -+ -+static ssize_t sensor_mode_show(struct device *dev, -+ struct device_attribute *attr, char *buf) -+{ -+ ipts_info_t *ipts; -+ ipts = dev_get_drvdata(dev); -+ -+ return sprintf(buf, "%d\n", ipts->sensor_mode); -+} -+ -+//TODO: Verify the function implementation -+static ssize_t sensor_mode_store(struct device *dev, -+ struct device_attribute *attr, const char *buf, -+ size_t count) -+{ -+ int ret; -+ long val; -+ ipts_info_t *ipts; -+ -+ ipts = dev_get_drvdata(dev); -+ ret = kstrtol(buf, 10, &val); -+ if (ret) -+ return ret; -+ -+ ipts_dbg(ipts, "try sensor mode = %ld\n", val); -+ -+ switch (val) { -+ case TOUCH_SENSOR_MODE_HID: -+ break; -+ case TOUCH_SENSOR_MODE_RAW_DATA: -+ break; -+ default: -+ ipts_err(ipts, "sensor mode %ld is not supported\n", val); -+ } -+ -+ return count; -+} -+ -+static ssize_t device_info_show(struct device *dev, -+ struct device_attribute *attr, char *buf) -+{ -+ ipts_info_t *ipts; -+ -+ ipts = dev_get_drvdata(dev); -+ return sprintf(buf, "vendor id = 0x%04hX\n" -+ "device id = 0x%04hX\n" -+ "HW rev = 0x%08X\n" -+ "firmware rev = 0x%08X\n", -+ ipts->device_info.vendor_id, ipts->device_info.device_id, -+ ipts->device_info.hw_rev, ipts->device_info.fw_rev); -+} -+ -+static DEVICE_ATTR_RW(sensor_mode); -+static DEVICE_ATTR_RO(device_info); -+ -+static struct attribute *ipts_attrs[] = { -+ &dev_attr_sensor_mode.attr, -+ &dev_attr_device_info.attr, -+ NULL -+}; -+ -+static const struct attribute_group ipts_grp = { -+ .attrs = ipts_attrs, -+}; -+ -+MODULE_DEVICE_TABLE(mei, ipts_mei_cl_tbl); -+ -+static void raw_data_work_func(struct work_struct *work) -+{ -+ ipts_info_t *ipts = container_of(work, ipts_info_t, raw_data_work); -+ -+ ipts_handle_processed_data(ipts); -+} -+ -+static void gfx_status_work_func(struct work_struct *work) -+{ -+ ipts_info_t *ipts = container_of(work, ipts_info_t, gfx_status_work); -+ ipts_state_t state; -+ int status = ipts->gfx_status; -+ -+ ipts_dbg(ipts, "notify gfx status : %d\n", status); -+ -+ state = ipts_get_state(ipts); -+ -+ if (state == IPTS_STA_RAW_DATA_STARTED || state == IPTS_STA_HID_STARTED) { -+ if (status == IPTS_NOTIFY_STA_BACKLIGHT_ON && -+ ipts->display_status == false) { -+ ipts_send_sensor_clear_mem_window_cmd(ipts); -+ ipts->display_status = true; -+ } else if (status == IPTS_NOTIFY_STA_BACKLIGHT_OFF && -+ ipts->display_status == true) { -+ ipts_send_sensor_quiesce_io_cmd(ipts); -+ ipts->display_status = false; -+ } -+ } -+} -+ -+/* event loop */ -+static int ipts_mei_cl_event_thread(void *data) -+{ -+ ipts_info_t *ipts = (ipts_info_t *)data; -+ struct mei_cl_device *cldev = ipts->cldev; -+ ssize_t msg_len; -+ touch_sensor_msg_m2h_t m2h_msg; -+ -+ while (!kthread_should_stop()) { -+ msg_len = mei_cldev_recv(cldev, (u8*)&m2h_msg, sizeof(m2h_msg)); -+ if (msg_len <= 0) { -+ ipts_err(ipts, "error in reading m2h msg\n"); -+ continue; -+ } -+ -+ if (ipts_handle_resp(ipts, &m2h_msg, msg_len) != 0) { -+ ipts_err(ipts, "error in handling resp msg\n"); -+ } -+ } -+ -+ ipts_dbg(ipts, "!! end event loop !!\n"); -+ -+ return 0; -+} -+ -+static void init_work_func(struct work_struct *work) -+{ -+ ipts_info_t *ipts = container_of(work, ipts_info_t, init_work); -+ -+ ipts->sensor_mode = TOUCH_SENSOR_MODE_RAW_DATA; -+ ipts->display_status = true; -+ -+ ipts_start(ipts); -+} -+ -+static int ipts_mei_cl_probe(struct mei_cl_device *cldev, -+ const struct mei_cl_device_id *id) -+{ -+ int ret = 0; -+ ipts_info_t *ipts = NULL; -+ -+ // Check if a companion driver for firmware loading was registered -+ // If not, defer probing until it was properly registere -+ if (!ipts_fw_handler_available() && !ipts_modparams.ignore_companion) { -+ return -EPROBE_DEFER; -+ } -+ -+ pr_info("probing Intel Precise Touch & Stylus\n"); -+ -+ // setup the DMA BIT mask, the system will choose the best possible -+ if (dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)) == 0) { -+ pr_info("IPTS using DMA_BIT_MASK(64)\n"); -+ } else if (dma_coerce_mask_and_coherent(&cldev->dev, -+ DMA_BIT_MASK(32)) == 0) { -+ pr_info("IPTS using DMA_BIT_MASK(32)\n"); -+ } else { -+ pr_err("IPTS: No suitable DMA available\n"); -+ return -EFAULT; -+ } -+ -+ ret = mei_cldev_enable(cldev); -+ if (ret < 0) { -+ pr_err("cannot enable IPTS\n"); -+ return ret; -+ } -+ -+ ipts = devm_kzalloc(&cldev->dev, sizeof(ipts_info_t), GFP_KERNEL); -+ if (ipts == NULL) { -+ ret = -ENOMEM; -+ goto disable_mei; -+ } -+ ipts->cldev = cldev; -+ mei_cldev_set_drvdata(cldev, ipts); -+ -+ ipts->event_loop = kthread_run(ipts_mei_cl_event_thread, (void*)ipts, -+ "ipts_event_thread"); -+ -+ if(ipts_dbgfs_register(ipts, "ipts")) -+ pr_debug("cannot register debugfs for IPTS\n"); -+ -+ INIT_WORK(&ipts->init_work, init_work_func); -+ INIT_WORK(&ipts->raw_data_work, raw_data_work_func); -+ INIT_WORK(&ipts->gfx_status_work, gfx_status_work_func); -+ -+ ret = sysfs_create_group(&cldev->dev.kobj, &ipts_grp); -+ if (ret != 0) { -+ pr_debug("cannot create sysfs for IPTS\n"); -+ } -+ -+ schedule_work(&ipts->init_work); -+ -+ return 0; -+ -+disable_mei : -+ mei_cldev_disable(cldev); -+ -+ return ret; -+} -+ -+static int ipts_mei_cl_remove(struct mei_cl_device *cldev) -+{ -+ ipts_info_t *ipts = mei_cldev_get_drvdata(cldev); -+ -+ ipts_stop(ipts); -+ -+ sysfs_remove_group(&cldev->dev.kobj, &ipts_grp); -+ ipts_hid_release(ipts); -+ ipts_dbgfs_deregister(ipts); -+ mei_cldev_disable(cldev); -+ -+ kthread_stop(ipts->event_loop); -+ -+ pr_info("IPTS removed\n"); -+ -+ return 0; -+} -+ -+static struct mei_cl_driver ipts_mei_cl_driver = { -+ .id_table = ipts_mei_cl_tbl, -+ .name = IPTS_DRIVER_NAME, -+ .probe = ipts_mei_cl_probe, -+ .remove = ipts_mei_cl_remove, -+}; -+ -+static int ipts_mei_cl_init(void) -+{ -+ int ret; -+ -+ pr_info("IPTS %s() is called\n", __func__); -+ -+ ret = mei_cldev_driver_register(&ipts_mei_cl_driver); -+ if (ret) { -+ pr_err("unable to register IPTS mei client driver\n"); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static void __exit ipts_mei_cl_exit(void) -+{ -+ pr_info("IPTS %s() is called\n", __func__); -+ -+ mei_cldev_driver_unregister(&ipts_mei_cl_driver); -+} -+ -+module_init(ipts_mei_cl_init); -+module_exit(ipts_mei_cl_exit); -+ -+MODULE_DESCRIPTION -+ ("Intel(R) Management Engine Interface Client Driver for "\ -+ "Intel Precision Touch and Sylus"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/ipts/ipts-msg-handler.c b/drivers/misc/ipts/ipts-msg-handler.c -new file mode 100644 -index 000000000000..db5356a1c84e ---- /dev/null -+++ b/drivers/misc/ipts/ipts-msg-handler.c -@@ -0,0 +1,437 @@ -+#include -+ -+#include "ipts.h" -+#include "ipts-hid.h" -+#include "ipts-resource.h" -+#include "ipts-mei-msgs.h" -+ -+int ipts_handle_cmd(ipts_info_t *ipts, u32 cmd, void *data, int data_size) -+{ -+ int ret = 0; -+ touch_sensor_msg_h2m_t h2m_msg; -+ int len = 0; -+ -+ memset(&h2m_msg, 0, sizeof(h2m_msg)); -+ -+ h2m_msg.command_code = cmd; -+ len = sizeof(h2m_msg.command_code) + data_size; -+ if (data != NULL && data_size != 0) -+ memcpy(&h2m_msg.h2m_data, data, data_size); /* copy payload */ -+ -+ ret = mei_cldev_send(ipts->cldev, (u8*)&h2m_msg, len); -+ if (ret < 0) { -+ ipts_err(ipts, "mei_cldev_send() error 0x%X:%d\n", -+ cmd, ret); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+int ipts_send_feedback(ipts_info_t *ipts, int buffer_idx, u32 transaction_id) -+{ -+ int ret; -+ int cmd_len; -+ touch_sensor_feedback_ready_cmd_data_t fb_ready_cmd; -+ -+ cmd_len = sizeof(touch_sensor_feedback_ready_cmd_data_t); -+ memset(&fb_ready_cmd, 0, cmd_len); -+ -+ fb_ready_cmd.feedback_index = buffer_idx; -+ fb_ready_cmd.transaction_id = transaction_id; -+ -+ ret = ipts_handle_cmd(ipts, TOUCH_SENSOR_FEEDBACK_READY_CMD, -+ &fb_ready_cmd, cmd_len); -+ -+ return ret; -+} -+ -+int ipts_send_sensor_quiesce_io_cmd(ipts_info_t *ipts) -+{ -+ int ret; -+ int cmd_len; -+ touch_sensor_quiesce_io_cmd_data_t quiesce_io_cmd; -+ -+ cmd_len = sizeof(touch_sensor_quiesce_io_cmd_data_t); -+ memset(&quiesce_io_cmd, 0, cmd_len); -+ -+ ret = ipts_handle_cmd(ipts, TOUCH_SENSOR_QUIESCE_IO_CMD, -+ &quiesce_io_cmd, cmd_len); -+ -+ return ret; -+} -+ -+int ipts_send_sensor_hid_ready_for_data_cmd(ipts_info_t *ipts) -+{ -+ return ipts_handle_cmd(ipts, TOUCH_SENSOR_HID_READY_FOR_DATA_CMD, NULL, 0); -+} -+ -+int ipts_send_sensor_clear_mem_window_cmd(ipts_info_t *ipts) -+{ -+ return ipts_handle_cmd(ipts, TOUCH_SENSOR_CLEAR_MEM_WINDOW_CMD, NULL, 0); -+} -+ -+static int check_validity(touch_sensor_msg_m2h_t *m2h_msg, u32 msg_len) -+{ -+ int ret = 0; -+ int valid_msg_len = sizeof(m2h_msg->command_code); -+ u32 cmd_code = m2h_msg->command_code; -+ -+ switch (cmd_code) { -+ case TOUCH_SENSOR_SET_MODE_RSP: -+ valid_msg_len += -+ sizeof(touch_sensor_set_mode_rsp_data_t); -+ break; -+ case TOUCH_SENSOR_SET_MEM_WINDOW_RSP: -+ valid_msg_len += -+ sizeof(touch_sensor_set_mem_window_rsp_data_t); -+ break; -+ case TOUCH_SENSOR_QUIESCE_IO_RSP: -+ valid_msg_len += -+ sizeof(touch_sensor_quiesce_io_rsp_data_t); -+ break; -+ case TOUCH_SENSOR_HID_READY_FOR_DATA_RSP: -+ valid_msg_len += -+ sizeof(touch_sensor_hid_ready_for_data_rsp_data_t); -+ break; -+ case TOUCH_SENSOR_FEEDBACK_READY_RSP: -+ valid_msg_len += -+ sizeof(touch_sensor_feedback_ready_rsp_data_t); -+ break; -+ case TOUCH_SENSOR_CLEAR_MEM_WINDOW_RSP: -+ valid_msg_len += -+ sizeof(touch_sensor_clear_mem_window_rsp_data_t); -+ break; -+ case TOUCH_SENSOR_NOTIFY_DEV_READY_RSP: -+ valid_msg_len += -+ sizeof(touch_sensor_notify_dev_ready_rsp_data_t); -+ break; -+ case TOUCH_SENSOR_SET_POLICIES_RSP: -+ valid_msg_len += -+ sizeof(touch_sensor_set_policies_rsp_data_t); -+ break; -+ case TOUCH_SENSOR_GET_POLICIES_RSP: -+ valid_msg_len += -+ sizeof(touch_sensor_get_policies_rsp_data_t); -+ break; -+ case TOUCH_SENSOR_RESET_RSP: -+ valid_msg_len += -+ sizeof(touch_sensor_reset_rsp_data_t); -+ break; -+ } -+ -+ if (valid_msg_len != msg_len) { -+ return -EINVAL; -+ } -+ -+ return ret; -+} -+ -+int ipts_start(ipts_info_t *ipts) -+{ -+ int ret = 0; -+ /* TODO : check if we need to do SET_POLICIES_CMD -+ we need to do this when protocol version doesn't match with reported one -+ how we keep vendor specific data is the first thing to solve */ -+ -+ ipts_set_state(ipts, IPTS_STA_INIT); -+ ipts->num_of_parallel_data_buffers = TOUCH_SENSOR_MAX_DATA_BUFFERS; -+ -+ ipts->sensor_mode = TOUCH_SENSOR_MODE_RAW_DATA; /* start with RAW_DATA */ -+ -+ ret = ipts_handle_cmd(ipts, TOUCH_SENSOR_NOTIFY_DEV_READY_CMD, NULL, 0); -+ -+ return ret; -+} -+ -+void ipts_stop(ipts_info_t *ipts) -+{ -+ ipts_state_t old_state; -+ -+ old_state = ipts_get_state(ipts); -+ ipts_set_state(ipts, IPTS_STA_STOPPING); -+ -+ ipts_send_sensor_quiesce_io_cmd(ipts); -+ ipts_send_sensor_clear_mem_window_cmd(ipts); -+ -+ if (old_state < IPTS_STA_RESOURCE_READY) -+ return; -+ -+ if (old_state == IPTS_STA_RAW_DATA_STARTED || -+ old_state == IPTS_STA_HID_STARTED) { -+ ipts_free_default_resource(ipts); -+ ipts_free_raw_data_resource(ipts); -+ -+ return; -+ } -+} -+ -+int ipts_restart(ipts_info_t *ipts) -+{ -+ int ret = 0; -+ -+ ipts_dbg(ipts, "ipts restart\n"); -+ -+ ipts_stop(ipts); -+ -+ ipts->retry++; -+ if (ipts->retry == IPTS_MAX_RETRY && -+ ipts->sensor_mode == TOUCH_SENSOR_MODE_RAW_DATA) { -+ /* try with HID mode */ -+ ipts->sensor_mode = TOUCH_SENSOR_MODE_HID; -+ } else if (ipts->retry > IPTS_MAX_RETRY) { -+ return -EPERM; -+ } -+ -+ ipts_send_sensor_quiesce_io_cmd(ipts); -+ ipts->restart = true; -+ -+ return ret; -+} -+ -+int ipts_switch_sensor_mode(ipts_info_t *ipts, int new_sensor_mode) -+{ -+ int ret = 0; -+ -+ ipts->new_sensor_mode = new_sensor_mode; -+ ipts->switch_sensor_mode = true; -+ ret = ipts_send_sensor_quiesce_io_cmd(ipts); -+ -+ return ret; -+} -+ -+#define rsp_failed(ipts, cmd, status) ipts_err(ipts, \ -+ "0x%08x failed status = %d\n", cmd, status); -+ -+int ipts_handle_resp(ipts_info_t *ipts, touch_sensor_msg_m2h_t *m2h_msg, -+ u32 msg_len) -+{ -+ int ret = 0; -+ int rsp_status = 0; -+ int cmd_status = 0; -+ int cmd_len = 0; -+ u32 cmd; -+ -+ if (!check_validity(m2h_msg, msg_len)) { -+ ipts_err(ipts, "wrong rsp\n"); -+ return -EINVAL; -+ } -+ -+ rsp_status = m2h_msg->status; -+ cmd = m2h_msg->command_code; -+ -+ switch (cmd) { -+ case TOUCH_SENSOR_NOTIFY_DEV_READY_RSP: -+ if (rsp_status != 0 && -+ rsp_status != TOUCH_STATUS_SENSOR_FAIL_NONFATAL) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ cmd_status = ipts_handle_cmd(ipts, -+ TOUCH_SENSOR_GET_DEVICE_INFO_CMD, -+ NULL, 0); -+ break; -+ case TOUCH_SENSOR_GET_DEVICE_INFO_RSP: -+ if (rsp_status != 0 && -+ rsp_status != TOUCH_STATUS_COMPAT_CHECK_FAIL) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ memcpy(&ipts->device_info, -+ &m2h_msg->m2h_data.device_info_rsp_data, -+ sizeof(touch_sensor_get_device_info_rsp_data_t)); -+ -+ /* -+ TODO : support raw_request during HID init. -+ Although HID init happens here, technically most of -+ reports (for both direction) can be issued only -+ after SET_MEM_WINDOWS_CMD since they may require -+ ME or touch IC. If ipts vendor requires raw_request -+ during HID init, we need to consider to move HID init. -+ */ -+ if (ipts->hid_desc_ready == false) { -+ ret = ipts_hid_init(ipts); -+ if (ret) -+ break; -+ } -+ -+ cmd_status = ipts_send_sensor_clear_mem_window_cmd(ipts); -+ -+ break; -+ case TOUCH_SENSOR_CLEAR_MEM_WINDOW_RSP: -+ { -+ touch_sensor_set_mode_cmd_data_t sensor_mode_cmd; -+ -+ if (rsp_status != 0 && -+ rsp_status != TOUCH_STATUS_TIMEOUT) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ if (ipts_get_state(ipts) == IPTS_STA_STOPPING) -+ break; -+ -+ /* allocate default resource : common & hid only */ -+ if (!ipts_is_default_resource_ready(ipts)) { -+ ret = ipts_allocate_default_resource(ipts); -+ if (ret) -+ break; -+ } -+ -+ if (ipts->sensor_mode == TOUCH_SENSOR_MODE_RAW_DATA && -+ !ipts_is_raw_data_resource_ready(ipts)) { -+ ret = ipts_allocate_raw_data_resource(ipts); -+ if (ret) { -+ ipts_free_default_resource(ipts); -+ break; -+ } -+ } -+ -+ ipts_set_state(ipts, IPTS_STA_RESOURCE_READY); -+ -+ cmd_len = sizeof(touch_sensor_set_mode_cmd_data_t); -+ memset(&sensor_mode_cmd, 0, cmd_len); -+ sensor_mode_cmd.sensor_mode = ipts->sensor_mode; -+ cmd_status = ipts_handle_cmd(ipts, -+ TOUCH_SENSOR_SET_MODE_CMD, -+ &sensor_mode_cmd, cmd_len); -+ break; -+ } -+ case TOUCH_SENSOR_SET_MODE_RSP: -+ { -+ touch_sensor_set_mem_window_cmd_data_t smw_cmd; -+ -+ if (rsp_status != 0) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ cmd_len = sizeof(touch_sensor_set_mem_window_cmd_data_t); -+ memset(&smw_cmd, 0, cmd_len); -+ ipts_get_set_mem_window_cmd_data(ipts, &smw_cmd); -+ cmd_status = ipts_handle_cmd(ipts, -+ TOUCH_SENSOR_SET_MEM_WINDOW_CMD, -+ &smw_cmd, cmd_len); -+ break; -+ } -+ case TOUCH_SENSOR_SET_MEM_WINDOW_RSP: -+ if (rsp_status != 0) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ cmd_status = ipts_send_sensor_hid_ready_for_data_cmd(ipts); -+ if (cmd_status) -+ break; -+ -+ if (ipts->sensor_mode == TOUCH_SENSOR_MODE_HID) { -+ ipts_set_state(ipts, IPTS_STA_HID_STARTED); -+ } else if (ipts->sensor_mode == TOUCH_SENSOR_MODE_RAW_DATA) { -+ ipts_set_state(ipts, IPTS_STA_RAW_DATA_STARTED); -+ } -+ -+ ipts_err(ipts, "touch enabled %d\n", ipts_get_state(ipts)); -+ -+ break; -+ case TOUCH_SENSOR_HID_READY_FOR_DATA_RSP: -+ { -+ touch_sensor_hid_ready_for_data_rsp_data_t *hid_data; -+ ipts_state_t state; -+ -+ if (rsp_status != 0 && -+ rsp_status != TOUCH_STATUS_SENSOR_DISABLED) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ state = ipts_get_state(ipts); -+ if (ipts->sensor_mode == TOUCH_SENSOR_MODE_HID && -+ state == IPTS_STA_HID_STARTED) { -+ -+ hid_data = &m2h_msg->m2h_data.hid_ready_for_data_rsp_data; -+ -+ /* HID mode only uses buffer 0 */ -+ if (hid_data->touch_data_buffer_index != 0) -+ break; -+ -+ /* handle hid data */ -+ ipts_handle_hid_data(ipts, hid_data); -+ } -+ -+ break; -+ } -+ case TOUCH_SENSOR_FEEDBACK_READY_RSP: -+ if (rsp_status != 0 && -+ rsp_status != TOUCH_STATUS_COMPAT_CHECK_FAIL) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ if (m2h_msg->m2h_data.feedback_ready_rsp_data. -+ feedback_index == TOUCH_HID_2_ME_BUFFER_ID) -+ break; -+ -+ if (ipts->sensor_mode == TOUCH_SENSOR_MODE_HID) { -+ cmd_status = ipts_handle_cmd(ipts, -+ TOUCH_SENSOR_HID_READY_FOR_DATA_CMD, -+ NULL, 0); -+ } -+ -+ /* reset retry since we are getting touch data */ -+ ipts->retry = 0; -+ -+ break; -+ case TOUCH_SENSOR_QUIESCE_IO_RSP: -+ { -+ ipts_state_t state; -+ -+ if (rsp_status != 0) { -+ rsp_failed(ipts, cmd, rsp_status); -+ break; -+ } -+ -+ state = ipts_get_state(ipts); -+ if (state == IPTS_STA_STOPPING && ipts->restart) { -+ ipts_dbg(ipts, "restart\n"); -+ ipts_start(ipts); -+ ipts->restart = 0; -+ break; -+ } -+ -+ /* support sysfs debug node for switch sensor mode */ -+ if (ipts->switch_sensor_mode) { -+ ipts_set_state(ipts, IPTS_STA_INIT); -+ ipts->sensor_mode = ipts->new_sensor_mode; -+ ipts->switch_sensor_mode = false; -+ -+ ipts_send_sensor_clear_mem_window_cmd(ipts); -+ } -+ -+ break; -+ } -+ } -+ -+ /* handle error in rsp_status */ -+ if (rsp_status != 0) { -+ switch (rsp_status) { -+ case TOUCH_STATUS_SENSOR_EXPECTED_RESET: -+ case TOUCH_STATUS_SENSOR_UNEXPECTED_RESET: -+ ipts_dbg(ipts, "sensor reset %d\n", rsp_status); -+ ipts_restart(ipts); -+ break; -+ default: -+ ipts_dbg(ipts, "cmd : 0x%08x, status %d\n", -+ cmd, -+ rsp_status); -+ break; -+ } -+ } -+ -+ if (cmd_status) { -+ ipts_restart(ipts); -+ } -+ -+ return ret; -+} -diff --git a/drivers/misc/ipts/ipts-msg-handler.h b/drivers/misc/ipts/ipts-msg-handler.h -new file mode 100644 -index 000000000000..f37d9ad9af8c ---- /dev/null -+++ b/drivers/misc/ipts/ipts-msg-handler.h -@@ -0,0 +1,33 @@ -+/* -+ * -+ * Intel Precise Touch & Stylus ME message handler -+ * Copyright (c) 2016, Intel Corporation. -+ * -+ * 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. -+ * -+ */ -+ -+#ifndef _IPTS_MSG_HANDLER_H -+#define _IPTS_MSG_HANDLER_H -+ -+int ipts_handle_cmd(ipts_info_t *ipts, u32 cmd, void *data, int data_size); -+int ipts_start(ipts_info_t *ipts); -+void ipts_stop(ipts_info_t *ipts); -+int ipts_switch_sensor_mode(ipts_info_t *ipts, int new_sensor_mode); -+int ipts_handle_resp(ipts_info_t *ipts, touch_sensor_msg_m2h_t *m2h_msg, -+ u32 msg_len); -+int ipts_handle_processed_data(ipts_info_t *ipts); -+int ipts_send_feedback(ipts_info_t *ipts, int buffer_idx, u32 transaction_id); -+int ipts_send_sensor_quiesce_io_cmd(ipts_info_t *ipts); -+int ipts_send_sensor_hid_ready_for_data_cmd(ipts_info_t *ipts); -+int ipts_send_sensor_clear_mem_window_cmd(ipts_info_t *ipts); -+int ipts_restart(ipts_info_t *ipts); -+ -+#endif /* _IPTS_MSG_HANDLER_H */ -diff --git a/drivers/misc/ipts/ipts-params.c b/drivers/misc/ipts/ipts-params.c -new file mode 100644 -index 000000000000..b4faa1afc046 ---- /dev/null -+++ b/drivers/misc/ipts/ipts-params.c -@@ -0,0 +1,21 @@ -+#include -+ -+#include "ipts-params.h" -+ -+struct ipts_params ipts_modparams = { -+ .ignore_fw_fallback = false, -+ .ignore_companion = false, -+ .no_feedback = -1, -+}; -+ -+module_param_named(ignore_fw_fallback, ipts_modparams.ignore_fw_fallback, bool, 0400); -+MODULE_PARM_DESC(ignore_fw_fallback, "Don't use the IPTS firmware fallback path"); -+ -+module_param_named(ignore_companion, ipts_modparams.ignore_companion, bool, 0400); -+MODULE_PARM_DESC(ignore_companion, "Don't use a companion driver to load firmware"); -+ -+module_param_named(no_feedback, ipts_modparams.no_feedback, int, 0644); -+MODULE_PARM_DESC(no_feedback, "Disable sending feedback in order to work around the issue that IPTS " -+ "stops working after some amount of use. " -+ "-1=auto (true if your model is SB1/SP4, false if another model), " -+ "0=false, 1=true, (default: -1)"); -diff --git a/drivers/misc/ipts/ipts-params.h b/drivers/misc/ipts/ipts-params.h -new file mode 100644 -index 000000000000..6fd62fb46d26 ---- /dev/null -+++ b/drivers/misc/ipts/ipts-params.h -@@ -0,0 +1,14 @@ -+#ifndef _IPTS_PARAMS_H_ -+#define _IPTS_PARAMS_H_ -+ -+#include -+ -+struct ipts_params { -+ bool ignore_fw_fallback; -+ bool ignore_companion; -+ int no_feedback; -+}; -+ -+extern struct ipts_params ipts_modparams; -+ -+#endif // _IPTS_PARAMS_H_ -diff --git a/drivers/misc/ipts/ipts-resource.c b/drivers/misc/ipts/ipts-resource.c -new file mode 100644 -index 000000000000..47607ef7c461 ---- /dev/null -+++ b/drivers/misc/ipts/ipts-resource.c -@@ -0,0 +1,277 @@ -+#include -+ -+#include "ipts.h" -+#include "ipts-mei-msgs.h" -+#include "ipts-kernel.h" -+ -+static void free_common_resource(ipts_info_t *ipts) -+{ -+ char *addr; -+ ipts_buffer_info_t *feedback_buffer; -+ dma_addr_t dma_addr; -+ u32 buffer_size; -+ int i, num_of_parallels; -+ -+ if (ipts->resource.me2hid_buffer) { -+ devm_kfree(&ipts->cldev->dev, ipts->resource.me2hid_buffer); -+ ipts->resource.me2hid_buffer = 0; -+ } -+ -+ addr = ipts->resource.hid2me_buffer.addr; -+ dma_addr = ipts->resource.hid2me_buffer.dma_addr; -+ buffer_size = ipts->resource.hid2me_buffer_size; -+ -+ if (ipts->resource.hid2me_buffer.addr) { -+ dmam_free_coherent(&ipts->cldev->dev, buffer_size, addr, dma_addr); -+ ipts->resource.hid2me_buffer.addr = 0; -+ ipts->resource.hid2me_buffer.dma_addr = 0; -+ ipts->resource.hid2me_buffer_size = 0; -+ } -+ -+ feedback_buffer = ipts->resource.feedback_buffer; -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ for (i = 0; i < num_of_parallels; i++) { -+ if (feedback_buffer[i].addr) { -+ dmam_free_coherent(&ipts->cldev->dev, -+ ipts->device_info.feedback_size, -+ feedback_buffer[i].addr, -+ feedback_buffer[i].dma_addr); -+ feedback_buffer[i].addr = 0; -+ feedback_buffer[i].dma_addr = 0; -+ } -+ } -+} -+ -+static int allocate_common_resource(ipts_info_t *ipts) -+{ -+ char *addr, *me2hid_addr; -+ ipts_buffer_info_t *feedback_buffer; -+ dma_addr_t dma_addr; -+ int i, ret = 0, num_of_parallels; -+ u32 buffer_size; -+ -+ buffer_size = ipts->device_info.feedback_size; -+ -+ addr = dmam_alloc_coherent(&ipts->cldev->dev, -+ buffer_size, -+ &dma_addr, -+ GFP_ATOMIC|__GFP_ZERO); -+ if (addr == NULL) -+ return -ENOMEM; -+ -+ me2hid_addr = devm_kzalloc(&ipts->cldev->dev, buffer_size, GFP_KERNEL); -+ if (me2hid_addr == NULL) { -+ ret = -ENOMEM; -+ goto release_resource; -+ } -+ -+ ipts->resource.hid2me_buffer.addr = addr; -+ ipts->resource.hid2me_buffer.dma_addr = dma_addr; -+ ipts->resource.hid2me_buffer_size = buffer_size; -+ ipts->resource.me2hid_buffer = me2hid_addr; -+ -+ feedback_buffer = ipts->resource.feedback_buffer; -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ for (i = 0; i < num_of_parallels; i++) { -+ feedback_buffer[i].addr = dmam_alloc_coherent(&ipts->cldev->dev, -+ ipts->device_info.feedback_size, -+ &feedback_buffer[i].dma_addr, -+ GFP_ATOMIC|__GFP_ZERO); -+ -+ if (feedback_buffer[i].addr == NULL) { -+ ret = -ENOMEM; -+ goto release_resource; -+ } -+ } -+ -+ return 0; -+ -+release_resource: -+ free_common_resource(ipts); -+ -+ return ret; -+} -+ -+void ipts_free_raw_data_resource(ipts_info_t *ipts) -+{ -+ if (ipts_is_raw_data_resource_ready(ipts)) { -+ ipts->resource.raw_data_resource_ready = false; -+ -+ ipts_release_kernels(ipts); -+ } -+} -+ -+static int allocate_hid_resource(ipts_info_t *ipts) -+{ -+ ipts_buffer_info_t *buffer_hid; -+ -+ /* hid mode uses only one touch data buffer */ -+ buffer_hid = &ipts->resource.touch_data_buffer_hid; -+ buffer_hid->addr = dmam_alloc_coherent(&ipts->cldev->dev, -+ ipts->device_info.frame_size, -+ &buffer_hid->dma_addr, -+ GFP_ATOMIC|__GFP_ZERO); -+ if (buffer_hid->addr == NULL) { -+ return -ENOMEM; -+ } -+ -+ return 0; -+} -+ -+static void free_hid_resource(ipts_info_t *ipts) -+{ -+ ipts_buffer_info_t *buffer_hid; -+ -+ buffer_hid = &ipts->resource.touch_data_buffer_hid; -+ if (buffer_hid->addr) { -+ dmam_free_coherent(&ipts->cldev->dev, -+ ipts->device_info.frame_size, -+ buffer_hid->addr, -+ buffer_hid->dma_addr); -+ buffer_hid->addr = 0; -+ buffer_hid->dma_addr = 0; -+ } -+} -+ -+int ipts_allocate_default_resource(ipts_info_t *ipts) -+{ -+ int ret; -+ -+ ret = allocate_common_resource(ipts); -+ if (ret) { -+ ipts_dbg(ipts, "cannot allocate common resource\n"); -+ return ret; -+ } -+ -+ ret = allocate_hid_resource(ipts); -+ if (ret) { -+ ipts_dbg(ipts, "cannot allocate hid resource\n"); -+ free_common_resource(ipts); -+ return ret; -+ } -+ -+ ipts->resource.default_resource_ready = true; -+ -+ return 0; -+} -+ -+void ipts_free_default_resource(ipts_info_t *ipts) -+{ -+ if (ipts_is_default_resource_ready(ipts)) { -+ ipts->resource.default_resource_ready = false; -+ -+ free_hid_resource(ipts); -+ free_common_resource(ipts); -+ } -+} -+ -+int ipts_allocate_raw_data_resource(ipts_info_t *ipts) -+{ -+ int ret = 0; -+ -+ ret = ipts_init_kernels(ipts); -+ if (ret) { -+ return ret; -+ } -+ -+ ipts->resource.raw_data_resource_ready = true; -+ -+ return 0; -+} -+ -+static void get_hid_only_smw_cmd_data(ipts_info_t *ipts, -+ touch_sensor_set_mem_window_cmd_data_t *data, -+ ipts_resource_t *resrc) -+{ -+ ipts_buffer_info_t *touch_buf; -+ ipts_buffer_info_t *feedback_buf; -+ -+ touch_buf = &resrc->touch_data_buffer_hid; -+ feedback_buf = &resrc->feedback_buffer[0]; -+ -+ data->touch_data_buffer_addr_lower[0] = -+ lower_32_bits(touch_buf->dma_addr); -+ data->touch_data_buffer_addr_upper[0] = -+ upper_32_bits(touch_buf->dma_addr); -+ data->feedback_buffer_addr_lower[0] = -+ lower_32_bits(feedback_buf->dma_addr); -+ data->feedback_buffer_addr_upper[0] = -+ upper_32_bits(feedback_buf->dma_addr); -+} -+ -+static void get_raw_data_only_smw_cmd_data(ipts_info_t *ipts, -+ touch_sensor_set_mem_window_cmd_data_t *data, -+ ipts_resource_t *resrc) -+{ -+ u64 wq_tail_phy_addr; -+ u64 cookie_phy_addr; -+ ipts_buffer_info_t *touch_buf; -+ ipts_buffer_info_t *feedback_buf; -+ int i, num_of_parallels; -+ -+ touch_buf = resrc->touch_data_buffer_raw; -+ feedback_buf = resrc->feedback_buffer; -+ -+ num_of_parallels = ipts_get_num_of_parallel_buffers(ipts); -+ for (i = 0; i < num_of_parallels; i++) { -+ data->touch_data_buffer_addr_lower[i] = -+ lower_32_bits(touch_buf[i].dma_addr); -+ data->touch_data_buffer_addr_upper[i] = -+ upper_32_bits(touch_buf[i].dma_addr); -+ data->feedback_buffer_addr_lower[i] = -+ lower_32_bits(feedback_buf[i].dma_addr); -+ data->feedback_buffer_addr_upper[i] = -+ upper_32_bits(feedback_buf[i].dma_addr); -+ } -+ -+ wq_tail_phy_addr = resrc->wq_info.wq_tail_phy_addr; -+ data->tail_offset_addr_lower = lower_32_bits(wq_tail_phy_addr); -+ data->tail_offset_addr_upper = upper_32_bits(wq_tail_phy_addr); -+ -+ cookie_phy_addr = resrc->wq_info.db_phy_addr + -+ resrc->wq_info.db_cookie_offset; -+ data->doorbell_cookie_addr_lower = lower_32_bits(cookie_phy_addr); -+ data->doorbell_cookie_addr_upper = upper_32_bits(cookie_phy_addr); -+ data->work_queue_size = resrc->wq_info.wq_size; -+ -+ data->work_queue_item_size = resrc->wq_item_size; -+} -+ -+void ipts_get_set_mem_window_cmd_data(ipts_info_t *ipts, -+ touch_sensor_set_mem_window_cmd_data_t *data) -+{ -+ ipts_resource_t *resrc = &ipts->resource; -+ -+ if (ipts->sensor_mode == TOUCH_SENSOR_MODE_RAW_DATA) -+ get_raw_data_only_smw_cmd_data(ipts, data, resrc); -+ else if (ipts->sensor_mode == TOUCH_SENSOR_MODE_HID) -+ get_hid_only_smw_cmd_data(ipts, data, resrc); -+ -+ /* hid2me is common for "raw data" and "hid" */ -+ data->hid2me_buffer_addr_lower = -+ lower_32_bits(resrc->hid2me_buffer.dma_addr); -+ data->hid2me_buffer_addr_upper = -+ upper_32_bits(resrc->hid2me_buffer.dma_addr); -+ data->hid2me_buffer_size = resrc->hid2me_buffer_size; -+} -+ -+void ipts_set_input_buffer(ipts_info_t *ipts, int parallel_idx, -+ u8* cpu_addr, u64 dma_addr) -+{ -+ ipts_buffer_info_t *touch_buf; -+ -+ touch_buf = ipts->resource.touch_data_buffer_raw; -+ touch_buf[parallel_idx].dma_addr = dma_addr; -+ touch_buf[parallel_idx].addr = cpu_addr; -+} -+ -+void ipts_set_output_buffer(ipts_info_t *ipts, int parallel_idx, int output_idx, -+ u8* cpu_addr, u64 dma_addr) -+{ -+ ipts_buffer_info_t *output_buf; -+ -+ output_buf = &ipts->resource.raw_data_mode_output_buffer[parallel_idx][output_idx]; -+ -+ output_buf->dma_addr = dma_addr; -+ output_buf->addr = cpu_addr; -+} -diff --git a/drivers/misc/ipts/ipts-resource.h b/drivers/misc/ipts/ipts-resource.h -new file mode 100644 -index 000000000000..7d66ac72b475 ---- /dev/null -+++ b/drivers/misc/ipts/ipts-resource.h -@@ -0,0 +1,30 @@ -+/* -+ * Intel Precise Touch & Stylus state codes -+ * -+ * Copyright (c) 2016, Intel Corporation. -+ * -+ * 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. -+ */ -+ -+#ifndef _IPTS_RESOURCE_H_ -+#define _IPTS_RESOURCE_H_ -+ -+int ipts_allocate_default_resource(ipts_info_t *ipts); -+void ipts_free_default_resource(ipts_info_t *ipts); -+int ipts_allocate_raw_data_resource(ipts_info_t *ipts); -+void ipts_free_raw_data_resource(ipts_info_t *ipts); -+void ipts_get_set_mem_window_cmd_data(ipts_info_t *ipts, -+ touch_sensor_set_mem_window_cmd_data_t *data); -+void ipts_set_input_buffer(ipts_info_t *ipts, int parallel_idx, -+ u8* cpu_addr, u64 dma_addr); -+void ipts_set_output_buffer(ipts_info_t *ipts, int parallel_idx, int output_idx, -+ u8* cpu_addr, u64 dma_addr); -+ -+#endif // _IPTS_RESOURCE_H_ -diff --git a/drivers/misc/ipts/ipts-sensor-regs.h b/drivers/misc/ipts/ipts-sensor-regs.h -new file mode 100644 -index 000000000000..96812b0eb980 ---- /dev/null -+++ b/drivers/misc/ipts/ipts-sensor-regs.h -@@ -0,0 +1,700 @@ -+/* -+ * Touch Sensor Register definition -+ * -+ * Copyright (c) 2013-2016, Intel Corporation. -+ * -+ * 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. -+ */ -+ -+ -+#ifndef _TOUCH_SENSOR_REGS_H -+#define _TOUCH_SENSOR_REGS_H -+ -+#pragma pack(1) -+ -+// define C_ASSERT macro to check structure size and fail compile for unexpected mismatch -+#ifndef C_ASSERT -+#define C_ASSERT(e) typedef char __C_ASSERT__[(e)?1:-1] -+#endif -+ -+// -+// Compatibility versions for this header file -+// -+#define TOUCH_EDS_REV_MINOR 0 -+#define TOUCH_EDS_REV_MAJOR 1 -+#define TOUCH_EDS_INTF_REV 1 -+#define TOUCH_PROTOCOL_VER 0 -+ -+ -+// -+// Offset 00h: TOUCH_STS: Status Register -+// This register is read by the SPI Controller immediately following an interrupt. -+// -+#define TOUCH_STS_REG_OFFSET 0x00 -+ -+typedef enum touch_sts_reg_int_type -+{ -+ TOUCH_STS_REG_INT_TYPE_DATA_AVAIL = 0, // Touch Data Available -+ TOUCH_STS_REG_INT_TYPE_RESET_OCCURRED, // Reset Occurred -+ TOUCH_STS_REG_INT_TYPE_ERROR_OCCURRED, // Error Occurred -+ TOUCH_STS_REG_INT_TYPE_VENDOR_DATA, // Vendor specific data, treated same as raw frame -+ TOUCH_STS_REG_INT_TYPE_GET_FEATURES, // Get Features response data available -+ TOUCH_STS_REG_INT_TYPE_MAX -+} touch_sts_reg_int_type_t; -+C_ASSERT(sizeof(touch_sts_reg_int_type_t) == 4); -+ -+typedef enum touch_sts_reg_pwr_state -+{ -+ TOUCH_STS_REG_PWR_STATE_SLEEP = 0, // Sleep -+ TOUCH_STS_REG_PWR_STATE_DOZE, // Doze -+ TOUCH_STS_REG_PWR_STATE_ARMED, // Armed -+ TOUCH_STS_REG_PWR_STATE_SENSING, // Sensing -+ TOUCH_STS_REG_PWR_STATE_MAX -+} touch_sts_reg_pwr_state_t; -+C_ASSERT(sizeof(touch_sts_reg_pwr_state_t) == 4); -+ -+typedef enum touch_sts_reg_init_state -+{ -+ TOUCH_STS_REG_INIT_STATE_READY_FOR_OP = 0, // Ready for normal operation -+ TOUCH_STS_REG_INIT_STATE_FW_NEEDED, // Touch IC needs its Firmware loaded -+ TOUCH_STS_REG_INIT_STATE_DATA_NEEDED, // Touch IC needs its Data loaded -+ TOUCH_STS_REG_INIT_STATE_INIT_ERROR, // Error info in TOUCH_ERR_REG -+ TOUCH_STS_REG_INIT_STATE_MAX -+} touch_sts_reg_init_state_t; -+C_ASSERT(sizeof(touch_sts_reg_init_state_t) == 4); -+ -+#define TOUCH_SYNC_BYTE_VALUE 0x5A -+ -+typedef union touch_sts_reg -+{ -+ u32 reg_value; -+ -+ struct -+ { -+ // When set, this indicates the hardware has data that needs to be read. -+ u32 int_status :1; -+ // see TOUCH_STS_REG_INT_TYPE -+ u32 int_type :4; -+ // see TOUCH_STS_REG_PWR_STATE -+ u32 pwr_state :2; -+ // see TOUCH_STS_REG_INIT_STATE -+ u32 init_state :2; -+ // Busy bit indicates that sensor cannot accept writes at this time -+ u32 busy :1; -+ // Reserved -+ u32 reserved :14; -+ // Synchronization bit, should always be TOUCH_SYNC_BYTE_VALUE -+ u32 sync_byte :8; -+ } fields; -+} touch_sts_reg_t; -+C_ASSERT(sizeof(touch_sts_reg_t) == 4); -+ -+ -+// -+// Offset 04h: TOUCH_FRAME_CHAR: Frame Characteristics Register -+// This registers describes the characteristics of each data frame read by the SPI Controller in -+// response to a touch interrupt. -+// -+#define TOUCH_FRAME_CHAR_REG_OFFSET 0x04 -+ -+typedef union touch_frame_char_reg -+{ -+ u32 reg_value; -+ -+ struct -+ { -+ // Micro-Frame Size (MFS): Indicates the size of a touch micro-frame in byte increments. -+ // When a micro-frame is to be read for processing (in data mode), this is the total number of -+ // bytes that must be read per interrupt, split into multiple read commands no longer than RPS. -+ // Maximum micro-frame size is 256KB. -+ u32 microframe_size :18; -+ // Micro-Frames per Frame (MFPF): Indicates the number of micro-frames per frame. If a -+ // sensor's frame does not contain micro-frames this value will be 1. Valid values are 1-31. -+ u32 microframes_per_frame :5; -+ // Micro-Frame Index (MFI): Indicates the index of the micro-frame within a frame. This allows -+ // the SPI Controller to maintain synchronization with the sensor and determine when the final -+ // micro-frame has arrived. Valid values are 1-31. -+ u32 microframe_index :5; -+ // HID/Raw Data: This bit describes whether the data from the sensor is Raw data or a HID -+ // report. When set, the data is a HID report. -+ u32 hid_report :1; -+ // Reserved -+ u32 reserved :3; -+ } fields; -+} touch_frame_char_reg_t; -+C_ASSERT(sizeof(touch_frame_char_reg_t) == 4); -+ -+ -+// -+// Offset 08h: Touch Error Register -+// -+#define TOUCH_ERR_REG_OFFSET 0x08 -+ -+// bit definition is vendor specific -+typedef union touch_err_reg -+{ -+ u32 reg_value; -+ -+ struct -+ { -+ u32 invalid_fw :1; -+ u32 invalid_data :1; -+ u32 self_test_failed :1; -+ u32 reserved :12; -+ u32 fatal_error :1; -+ u32 vendor_errors :16; -+ } fields; -+} touch_err_reg_t; -+C_ASSERT(sizeof(touch_err_reg_t) == 4); -+ -+ -+// -+// Offset 0Ch: RESERVED -+// This register is reserved for future use. -+// -+ -+ -+// -+// Offset 10h: Touch Identification Register -+// -+#define TOUCH_ID_REG_OFFSET 0x10 -+ -+#define TOUCH_ID_REG_VALUE 0x43495424 -+ -+// expected value is "$TIC" or 0x43495424 -+typedef u32 touch_id_reg_t; -+C_ASSERT(sizeof(touch_id_reg_t) == 4); -+ -+ -+// -+// Offset 14h: TOUCH_DATA_SZ: Touch Data Size Register -+// This register describes the maximum size of frames and feedback data -+// -+#define TOUCH_DATA_SZ_REG_OFFSET 0x14 -+ -+#define TOUCH_MAX_FRAME_SIZE_INCREMENT 64 -+#define TOUCH_MAX_FEEDBACK_SIZE_INCREMENT 64 -+ -+#define TOUCH_SENSOR_MAX_FRAME_SIZE (32 * 1024) // Max allowed frame size 32KB -+#define TOUCH_SENSOR_MAX_FEEDBACK_SIZE (16 * 1024) // Max allowed feedback size 16KB -+ -+typedef union touch_data_sz_reg -+{ -+ u32 reg_value; -+ -+ struct -+ { -+ // This value describes the maximum frame size in 64byte increments. -+ u32 max_frame_size :12; -+ // This value describes the maximum feedback size in 64byte increments. -+ u32 max_feedback_size :8; -+ // Reserved -+ u32 reserved :12; -+ } fields; -+} touch_data_sz_reg_t; -+C_ASSERT(sizeof(touch_data_sz_reg_t) == 4); -+ -+ -+// -+// Offset 18h: TOUCH_CAPABILITIES: Touch Capabilities Register -+// This register informs the host as to the capabilities of the touch IC. -+// -+#define TOUCH_CAPS_REG_OFFSET 0x18 -+ -+typedef enum touch_caps_reg_read_delay_time -+{ -+ TOUCH_CAPS_REG_READ_DELAY_TIME_0, -+ TOUCH_CAPS_REG_READ_DELAY_TIME_10uS, -+ TOUCH_CAPS_REG_READ_DELAY_TIME_50uS, -+ TOUCH_CAPS_REG_READ_DELAY_TIME_100uS, -+ TOUCH_CAPS_REG_READ_DELAY_TIME_150uS, -+ TOUCH_CAPS_REG_READ_DELAY_TIME_250uS, -+ TOUCH_CAPS_REG_READ_DELAY_TIME_500uS, -+ TOUCH_CAPS_REG_READ_DELAY_TIME_1mS, -+} touch_caps_reg_read_delay_time_t; -+C_ASSERT(sizeof(touch_caps_reg_read_delay_time_t) == 4); -+ -+#define TOUCH_BULK_DATA_MAX_WRITE_INCREMENT 64 -+ -+typedef union touch_caps_reg -+{ -+ u32 reg_value; -+ -+ struct -+ { -+ // Reserved for future frequency -+ u32 reserved0 :1; -+ // 17 MHz (14 MHz on Atom) Supported: 0b - Not supported, 1b - Supported -+ u32 supported_17Mhz :1; -+ // 30 MHz (25MHz on Atom) Supported: 0b - Not supported, 1b - Supported -+ u32 supported_30Mhz :1; -+ // 50 MHz Supported: 0b - Not supported, 1b - Supported -+ u32 supported_50Mhz :1; -+ // Reserved -+ u32 reserved1 :4; -+ // Single I/O Supported: 0b - Not supported, 1b - Supported -+ u32 supported_single_io :1; -+ // Dual I/O Supported: 0b - Not supported, 1b - Supported -+ u32 supported_dual_io :1; -+ // Quad I/O Supported: 0b - Not supported, 1b - Supported -+ u32 supported_quad_io :1; -+ // Bulk Data Area Max Write Size: The amount of data the SPI Controller can write to the bulk -+ // data area before it has to poll the busy bit. This field is in multiples of 64 bytes. The -+ // SPI Controller will write the amount of data specified in this field, then check and wait -+ // for the Status.Busy bit to be zero before writing the next data chunk. This field is 6 bits -+ // long, allowing for 4KB of contiguous writes w/o a poll of the busy bit. If this field is -+ // 0x00 the Touch IC has no limit in the amount of data the SPI Controller can write to the -+ // bulk data area. -+ u32 bulk_data_max_write :6; -+ // Read Delay Timer Value: This field describes the delay the SPI Controller will initiate when -+ // a read interrupt follows a write data command. Uses values from TOUCH_CAPS_REG_READ_DELAY_TIME -+ u32 read_delay_timer_value :3; -+ // Reserved -+ u32 reserved2 :4; -+ // Maximum Touch Points: A byte value based on the HID descriptor definition. -+ u32 max_touch_points :8; -+ } fields; -+} touch_caps_reg_t; -+C_ASSERT(sizeof(touch_caps_reg_t) == 4); -+ -+ -+// -+// Offset 1Ch: TOUCH_CFG: Touch Configuration Register -+// This register allows the SPI Controller to configure the touch sensor as needed during touch -+// operations. -+// -+#define TOUCH_CFG_REG_OFFSET 0x1C -+ -+typedef enum touch_cfg_reg_bulk_xfer_size -+{ -+ TOUCH_CFG_REG_BULK_XFER_SIZE_4B = 0, // Bulk Data Transfer Size is 4 bytes -+ TOUCH_CFG_REG_BULK_XFER_SIZE_8B, // Bulk Data Transfer Size is 8 bytes -+ TOUCH_CFG_REG_BULK_XFER_SIZE_16B, // Bulk Data Transfer Size is 16 bytes -+ TOUCH_CFG_REG_BULK_XFER_SIZE_32B, // Bulk Data Transfer Size is 32 bytes -+ TOUCH_CFG_REG_BULK_XFER_SIZE_64B, // Bulk Data Transfer Size is 64 bytes -+ TOUCH_CFG_REG_BULK_XFER_SIZE_MAX -+} touch_cfg_reg_bulk_xfer_size_t; -+C_ASSERT(sizeof(touch_cfg_reg_bulk_xfer_size_t) == 4); -+ -+// Frequency values used by TOUCH_CFG_REG and TOUCH_SENSOR_GET_DEVICE_INFO_RSP_DATA. -+typedef enum touch_freq -+{ -+ TOUCH_FREQ_RSVD = 0, // Reserved value -+ TOUCH_FREQ_17MHZ, // Sensor set for 17MHz operation (14MHz on Atom) -+ TOUCH_FREQ_30MHZ, // Sensor set for 30MHz operation (25MHz on Atom) -+ TOUCH_FREQ_MAX // Invalid value -+} touch_freq_t; -+C_ASSERT(sizeof(touch_freq_t) == 4); -+ -+typedef union touch_cfg_reg -+{ -+ u32 reg_value; -+ -+ struct -+ { -+ // Touch Enable (TE): This bit is used as a HW semaphore for the Touch IC to guarantee to the -+ // SPI Controller to that (when 0) no sensing operations will occur and only the Reset -+ // interrupt will be generated. When TE is cleared by the SPI Controller: -+ // - TICs must flush all output buffers -+ // - TICs must De-assert any pending interrupt -+ // - ME must throw away any partial frame and pending interrupt must be cleared/not serviced. -+ // The SPI Controller will only modify the configuration of the TIC when TE is cleared. TE is -+ // defaulted to 0h on a power-on reset. -+ u32 touch_enable :1; -+ // Data/HID Packet Mode (DHPM): Raw Data Mode: 0h, HID Packet Mode: 1h -+ u32 dhpm :1; -+ // Bulk Data Transfer Size: This field represents the amount of data written to the Bulk Data -+ // Area (SPI Offset 0x1000-0x2FFF) in a single SPI write protocol -+ u32 bulk_xfer_size :4; -+ // Frequency Select: Frequency for the TouchIC to run at. Use values from TOUCH_FREQ -+ u32 freq_select :3; -+ // Reserved -+ u32 reserved :23; -+ } fields; -+} touch_cfg_reg_t; -+C_ASSERT(sizeof(touch_cfg_reg_t) == 4); -+ -+ -+// -+// Offset 20h: TOUCH_CMD: Touch Command Register -+// This register is used for sending commands to the Touch IC. -+// -+#define TOUCH_CMD_REG_OFFSET 0x20 -+ -+typedef enum touch_cmd_reg_code -+{ -+ TOUCH_CMD_REG_CODE_NOP = 0, // No Operation -+ TOUCH_CMD_REG_CODE_SOFT_RESET, // Soft Reset -+ TOUCH_CMD_REG_CODE_PREP_4_READ, // Prepare All Registers for Read -+ TOUCH_CMD_REG_CODE_GEN_TEST_PACKETS, // Generate Test Packets according to value in TOUCH_TEST_CTRL_REG -+ TOUCH_CMD_REG_CODE_MAX -+} touch_cmd_reg_code_t; -+C_ASSERT(sizeof(touch_cmd_reg_code_t) == 4); -+ -+typedef union touch_cmd_reg -+{ -+ u32 reg_value; -+ -+ struct -+ { -+ // Command Code: See TOUCH_CMD_REG_CODE -+ u32 command_code :8; -+ // Reserved -+ u32 reserved :24; -+ } fields; -+} touch_cmd_reg_t; -+C_ASSERT(sizeof(touch_cmd_reg_t) == 4); -+ -+ -+// -+// Offset 24h: Power Management Control -+// This register is used for active power management. The Touch IC is allowed to mover from Doze or -+// Armed to Sensing after a touch has occurred. All other transitions will be made at the request -+// of the SPI Controller. -+// -+#define TOUCH_PWR_MGMT_CTRL_REG_OFFSET 0x24 -+ -+typedef enum touch_pwr_mgmt_ctrl_reg_cmd -+{ -+ TOUCH_PWR_MGMT_CTRL_REG_CMD_NOP = 0, // No change to power state -+ TOUCH_PWR_MGMT_CTRL_REG_CMD_SLEEP, // Sleep - set when the system goes into connected standby -+ TOUCH_PWR_MGMT_CTRL_REG_CMD_DOZE, // Doze - set after 300 seconds of inactivity -+ TOUCH_PWR_MGMT_CTRL_REG_CMD_ARMED, // Armed - Set by FW when a "finger off" message is received from the EUs -+ TOUCH_PWR_MGMT_CTRL_REG_CMD_SENSING, // Sensing - not typically set by FW -+ TOUCH_PWR_MGMT_CTRL_REG_CMD_MAX // Values will result in no change to the power state of the Touch IC -+} touch_pwr_mgmt_ctrl_reg_cmd_t; -+C_ASSERT(sizeof(touch_pwr_mgmt_ctrl_reg_cmd_t) == 4); -+ -+typedef union touch_pwr_mgmt_ctrl_reg -+{ -+ u32 reg_value; -+ -+ struct -+ { -+ // Power State Command: See TOUCH_PWR_MGMT_CTRL_REG_CMD -+ u32 pwr_state_cmd :3; -+ // Reserved -+ u32 reserved :29; -+ } fields; -+} touch_pwr_mgmt_ctrl_reg_t; -+C_ASSERT(sizeof(touch_pwr_mgmt_ctrl_reg_t) == 4); -+ -+ -+// -+// Offset 28h: Vendor HW Information Register -+// This register is used to relay Intel-assigned vendor ID information to the SPI Controller, which -+// may be forwarded to SW running on the host CPU. -+// -+#define TOUCH_VEN_HW_INFO_REG_OFFSET 0x28 -+ -+typedef union touch_ven_hw_info_reg -+{ -+ u32 reg_value; -+ -+ struct -+ { -+ // Touch Sensor Vendor ID -+ u32 vendor_id :16; -+ // Touch Sensor Device ID -+ u32 device_id :16; -+ } fields; -+} touch_ven_hw_info_reg_t; -+C_ASSERT(sizeof(touch_ven_hw_info_reg_t) == 4); -+ -+ -+// -+// Offset 2Ch: HW Revision ID Register -+// This register is used to relay vendor HW revision information to the SPI Controller which may be -+// forwarded to SW running on the host CPU. -+// -+#define TOUCH_HW_REV_REG_OFFSET 0x2C -+ -+typedef u32 touch_hw_rev_reg_t; // bit definition is vendor specific -+C_ASSERT(sizeof(touch_hw_rev_reg_t) == 4); -+ -+ -+// -+// Offset 30h: FW Revision ID Register -+// This register is used to relay vendor FW revision information to the SPI Controller which may be -+// forwarded to SW running on the host CPU. -+// -+#define TOUCH_FW_REV_REG_OFFSET 0x30 -+ -+typedef u32 touch_fw_rev_reg_t; // bit definition is vendor specific -+C_ASSERT(sizeof(touch_fw_rev_reg_t) == 4); -+ -+ -+// -+// Offset 34h: Compatibility Revision ID Register -+// This register is used to relay vendor compatibility information to the SPI Controller which may -+// be forwarded to SW running on the host CPU. Compatibility Information is a numeric value given -+// by Intel to the Touch IC vendor based on the major and minor revision of the EDS supported. From -+// a nomenclature point of view in an x.y revision number of the EDS, the major version is the value -+// of x and the minor version is the value of y. For example, a Touch IC supporting an EDS version -+// of 0.61 would contain a major version of 0 and a minor version of 61 in the register. -+// -+#define TOUCH_COMPAT_REV_REG_OFFSET 0x34 -+ -+typedef union touch_compat_rev_reg -+{ -+ u32 reg_value; -+ -+ struct -+ { -+ // EDS Minor Revision -+ u8 minor; -+ // EDS Major Revision -+ u8 major; -+ // Interface Revision Number (from EDS) -+ u8 intf_rev; -+ // EU Kernel Compatibility Version - vendor specific value -+ u8 kernel_compat_ver; -+ } fields; -+} touch_compat_rev_reg_t; -+C_ASSERT(sizeof(touch_compat_rev_reg_t) == 4); -+ -+ -+// -+// Touch Register Block is the full set of registers from offset 0x00h to 0x3F -+// This is the entire set of registers needed for normal touch operation. It does not include test -+// registers such as TOUCH_TEST_CTRL_REG -+// -+#define TOUCH_REG_BLOCK_OFFSET TOUCH_STS_REG_OFFSET -+ -+typedef struct touch_reg_block -+{ -+ touch_sts_reg_t sts_reg; // 0x00 -+ touch_frame_char_reg_t frame_char_reg; // 0x04 -+ touch_err_reg_t error_reg; // 0x08 -+ u32 reserved0; // 0x0C -+ touch_id_reg_t id_reg; // 0x10 -+ touch_data_sz_reg_t data_size_reg; // 0x14 -+ touch_caps_reg_t caps_reg; // 0x18 -+ touch_cfg_reg_t cfg_reg; // 0x1C -+ touch_cmd_reg_t cmd_reg; // 0x20 -+ touch_pwr_mgmt_ctrl_reg_t pwm_mgme_ctrl_reg; // 0x24 -+ touch_ven_hw_info_reg_t ven_hw_info_reg; // 0x28 -+ touch_hw_rev_reg_t hw_rev_reg; // 0x2C -+ touch_fw_rev_reg_t fw_rev_reg; // 0x30 -+ touch_compat_rev_reg_t compat_rev_reg; // 0x34 -+ u32 reserved1; // 0x38 -+ u32 reserved2; // 0x3C -+} touch_reg_block_t; -+C_ASSERT(sizeof(touch_reg_block_t) == 64); -+ -+ -+// -+// Offset 40h: Test Control Register -+// This register -+// -+#define TOUCH_TEST_CTRL_REG_OFFSET 0x40 -+ -+typedef union touch_test_ctrl_reg -+{ -+ u32 reg_value; -+ -+ struct -+ { -+ // Size of Test Frame in Raw Data Mode: This field specifies the test frame size in raw data -+ // mode in multiple of 64 bytes. For example, if this field value is 16, the test frame size -+ // will be 16x64 = 1K. -+ u32 raw_test_frame_size :16; -+ // Number of Raw Data Frames or HID Report Packets Generation. This field represents the number -+ // of test frames or HID reports to be generated when test mode is enabled. When multiple -+ // packets/frames are generated, they need be generated at 100 Hz frequency, i.e. 10ms per -+ // packet/frame. -+ u32 num_test_frames :16; -+ } fields; -+} touch_test_ctrl_reg_t; -+C_ASSERT(sizeof(touch_test_ctrl_reg_t) == 4); -+ -+ -+// -+// Offsets 0x000 to 0xFFF are reserved for Intel-defined Registers -+// -+#define TOUCH_REGISTER_LIMIT 0xFFF -+ -+ -+// -+// Data Window: Address 0x1000-0x1FFFF -+// The data window is reserved for writing and reading large quantities of data to and from the -+// sensor. -+// -+#define TOUCH_DATA_WINDOW_OFFSET 0x1000 -+#define TOUCH_DATA_WINDOW_LIMIT 0x1FFFF -+ -+#define TOUCH_SENSOR_MAX_OFFSET TOUCH_DATA_WINDOW_LIMIT -+ -+ -+// -+// The following data structures represent the headers defined in the Data Structures chapter of the -+// Intel Integrated Touch EDS -+// -+ -+// Enumeration used in TOUCH_RAW_DATA_HDR -+typedef enum touch_raw_data_types -+{ -+ TOUCH_RAW_DATA_TYPE_FRAME = 0, -+ TOUCH_RAW_DATA_TYPE_ERROR, // RawData will be the TOUCH_ERROR struct below -+ TOUCH_RAW_DATA_TYPE_VENDOR_DATA, // Set when InterruptType is Vendor Data -+ TOUCH_RAW_DATA_TYPE_HID_REPORT, -+ TOUCH_RAW_DATA_TYPE_GET_FEATURES, -+ TOUCH_RAW_DATA_TYPE_MAX -+} touch_raw_data_types_t; -+C_ASSERT(sizeof(touch_raw_data_types_t) == 4); -+ -+// Private data structure. Kernels must copy to HID driver buffer -+typedef struct touch_hid_private_data -+{ -+ u32 transaction_id; -+ u8 reserved[28]; -+} touch_hid_private_data_t; -+C_ASSERT(sizeof(touch_hid_private_data_t) == 32); -+ -+// This is the data structure sent from the PCH FW to the EU kernel -+typedef struct touch_raw_data_hdr -+{ -+ u32 data_type; // use values from TOUCH_RAW_DATA_TYPES -+ u32 raw_data_size_bytes; // The size in bytes of the raw data read from the -+ // sensor, does not include TOUCH_RAW_DATA_HDR. Will -+ // be the sum of all uFrames, or size of TOUCH_ERROR -+ // for if DataType is TOUCH_RAW_DATA_TYPE_ERROR -+ u32 buffer_id; // An ID to qualify with the feedback data to track -+ // buffer usage -+ u32 protocol_ver; // Must match protocol version of the EDS -+ u8 kernel_compat_id; // Copied from the Compatibility Revision ID Reg -+ u8 reserved[15]; // Padding to extend header to full 64 bytes and -+ // allow for growth -+ touch_hid_private_data_t hid_private_data; // Private data structure. Kernels must copy to HID -+ // driver buffer -+} touch_raw_data_hdr_t; -+C_ASSERT(sizeof(touch_raw_data_hdr_t) == 64); -+ -+typedef struct touch_raw_data -+{ -+ touch_raw_data_hdr_t header; -+ u8 raw_data[1]; // used to access the raw data as an array and keep the -+ // compilers happy. Actual size of this array is -+ // Header.RawDataSizeBytes -+} touch_raw_data_t; -+ -+ -+// The following section describes the data passed in TOUCH_RAW_DATA.RawData when DataType equals -+// TOUCH_RAW_DATA_TYPE_ERROR -+// Note: This data structure is also applied to HID mode -+typedef enum touch_err_types -+{ -+ TOUCH_RAW_DATA_ERROR = 0, -+ TOUCH_RAW_ERROR_MAX -+} touch_err_types_t; -+C_ASSERT(sizeof(touch_err_types_t) == 4); -+ -+typedef union touch_me_fw_error -+{ -+ u32 value; -+ -+ struct -+ { -+ u32 invalid_frame_characteristics : 1; -+ u32 microframe_index_invalid : 1; -+ u32 reserved : 30; -+ } fields; -+} touch_me_fw_error_t; -+C_ASSERT(sizeof(touch_me_fw_error_t) == 4); -+ -+typedef struct touch_error -+{ -+ u8 touch_error_type; // This must be a value from TOUCH_ERROR_TYPES -+ u8 reserved[3]; -+ touch_me_fw_error_t touch_me_fw_error; -+ touch_err_reg_t touch_error_register; // Contains the value copied from the Touch Error Reg -+} touch_error_t; -+C_ASSERT(sizeof(touch_error_t) == 12); -+ -+// Enumeration used in TOUCH_FEEDBACK_BUFFER -+typedef enum touch_feedback_cmd_types -+{ -+ TOUCH_FEEDBACK_CMD_TYPE_NONE = 0, -+ TOUCH_FEEDBACK_CMD_TYPE_SOFT_RESET, -+ TOUCH_FEEDBACK_CMD_TYPE_GOTO_ARMED, -+ TOUCH_FEEDBACK_CMD_TYPE_GOTO_SENSING, -+ TOUCH_FEEDBACK_CMD_TYPE_GOTO_SLEEP, -+ TOUCH_FEEDBACK_CMD_TYPE_GOTO_DOZE, -+ TOUCH_FEEDBACK_CMD_TYPE_HARD_RESET, -+ TOUCH_FEEDBACK_CMD_TYPE_MAX -+} touch_feedback_cmd_types_t; -+C_ASSERT(sizeof(touch_feedback_cmd_types_t) == 4); -+ -+// Enumeration used in TOUCH_FEEDBACK_HDR -+typedef enum touch_feedback_data_types -+{ -+ TOUCH_FEEDBACK_DATA_TYPE_FEEDBACK = 0, // This is vendor specific feedback to be written to the sensor -+ TOUCH_FEEDBACK_DATA_TYPE_SET_FEATURES, // This is a set features command to be written to the sensor -+ TOUCH_FEEDBACK_DATA_TYPE_GET_FEATURES, // This is a get features command to be written to the sensor -+ TOUCH_FEEDBACK_DATA_TYPE_OUTPUT_REPORT, // This is a HID output report to be written to the sensor -+ TOUCH_FEEDBACK_DATA_TYPE_STORE_DATA, // This is calibration data to be written to system flash -+ TOUCH_FEEDBACK_DATA_TYPE_MAX -+} touch_feedback_data_types_t; -+C_ASSERT(sizeof(touch_feedback_data_types_t) == 4); -+ -+// This is the data structure sent from the EU kernels back to the ME FW. -+// In addition to "feedback" data, the FW can execute a "command" described by the command type parameter. -+// Any payload data will always be sent to the TIC first, then any command will be issued. -+typedef struct touch_feedback_hdr -+{ -+ u32 feedback_cmd_type; // use values from TOUCH_FEEDBACK_CMD_TYPES -+ u32 payload_size_bytes; // The amount of data to be written to the sensor, not including the header -+ u32 buffer_id; // The ID of the raw data buffer that generated this feedback data -+ u32 protocol_ver; // Must match protocol version of the EDS -+ u32 feedback_data_type; // use values from TOUCH_FEEDBACK_DATA_TYPES. This is not relevant if PayloadSizeBytes is 0 -+ u32 spi_offest; // The offset from TOUCH_DATA_WINDOW_OFFSET at which to write the Payload data. Maximum offset is 0x1EFFF. -+ u8 reserved[40]; // Padding to extend header to full 64 bytes and allow for growth -+} touch_feedback_hdr_t; -+C_ASSERT(sizeof(touch_feedback_hdr_t) == 64); -+ -+typedef struct touch_feedback_buffer -+{ -+ touch_feedback_hdr_t Header; -+ u8 feedback_data[1]; // used to access the feedback data as an array and keep the compilers happy. Actual size of this array is Header.PayloadSizeBytes -+} touch_feedback_buffer_t; -+ -+ -+// -+// This data structure describes the header prepended to all data -+// written to the touch IC at the bulk data write (TOUCH_DATA_WINDOW_OFFSET + TOUCH_FEEDBACK_HDR.SpiOffest) address. -+typedef enum touch_write_data_type -+{ -+ TOUCH_WRITE_DATA_TYPE_FW_LOAD = 0, -+ TOUCH_WRITE_DATA_TYPE_DATA_LOAD, -+ TOUCH_WRITE_DATA_TYPE_FEEDBACK, -+ TOUCH_WRITE_DATA_TYPE_SET_FEATURES, -+ TOUCH_WRITE_DATA_TYPE_GET_FEATURES, -+ TOUCH_WRITE_DATA_TYPE_OUTPUT_REPORT, -+ TOUCH_WRITE_DATA_TYPE_NO_DATA_USE_DEFAULTS, -+ TOUCH_WRITE_DATA_TYPE_MAX -+} touch_write_data_type_t; -+C_ASSERT(sizeof(touch_write_data_type_t) == 4); -+ -+typedef struct touch_write_hdr -+{ -+ u32 write_data_type; // Use values from TOUCH_WRITE_DATA_TYPE -+ u32 write_data_len; // This field designates the amount of data to follow -+} touch_write_hdr_t; -+C_ASSERT(sizeof(touch_write_hdr_t) == 8); -+ -+typedef struct touch_write_data -+{ -+ touch_write_hdr_t header; -+ u8 write_data[1]; // used to access the write data as an array and keep the compilers happy. Actual size of this array is Header.WriteDataLen -+} touch_write_data_t; -+ -+#pragma pack() -+ -+#endif // _TOUCH_SENSOR_REGS_H -diff --git a/drivers/misc/ipts/ipts-state.h b/drivers/misc/ipts/ipts-state.h -new file mode 100644 -index 000000000000..39a2eaf5f004 ---- /dev/null -+++ b/drivers/misc/ipts/ipts-state.h -@@ -0,0 +1,29 @@ -+/* -+ * Intel Precise Touch & Stylus state codes -+ * -+ * Copyright (c) 2016, Intel Corporation. -+ * -+ * 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. -+ */ -+ -+#ifndef _IPTS_STATE_H_ -+#define _IPTS_STATE_H_ -+ -+/* ipts driver states */ -+typedef enum ipts_state { -+ IPTS_STA_NONE, -+ IPTS_STA_INIT, -+ IPTS_STA_RESOURCE_READY, -+ IPTS_STA_HID_STARTED, -+ IPTS_STA_RAW_DATA_STARTED, -+ IPTS_STA_STOPPING -+} ipts_state_t; -+ -+#endif // _IPTS_STATE_H_ -diff --git a/drivers/misc/ipts/ipts.h b/drivers/misc/ipts/ipts.h -new file mode 100644 -index 000000000000..9c34b55ff036 ---- /dev/null -+++ b/drivers/misc/ipts/ipts.h -@@ -0,0 +1,200 @@ -+/* -+ * -+ * Intel Management Engine Interface (Intel MEI) Client Driver for IPTS -+ * Copyright (c) 2016, Intel Corporation. -+ * -+ * 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. -+ * -+ */ -+ -+#ifndef _IPTS_H_ -+#define _IPTS_H_ -+ -+#include -+#include -+#include -+#include -+ -+#include "ipts-mei-msgs.h" -+#include "ipts-state.h" -+#include "ipts-binary-spec.h" -+ -+#define ENABLE_IPTS_DEBUG /* enable IPTS debug */ -+ -+#ifdef ENABLE_IPTS_DEBUG -+ -+#define ipts_info(ipts, format, arg...) do {\ -+ dev_info(&ipts->cldev->dev, format, ##arg);\ -+} while (0) -+ -+#define ipts_dbg(ipts, format, arg...) do {\ -+ dev_info(&ipts->cldev->dev, format, ##arg);\ -+} while (0) -+ -+//#define RUN_DBG_THREAD -+ -+#else -+ -+#define ipts_info(ipts, format, arg...) do {} while(0); -+#define ipts_dbg(ipts, format, arg...) do {} while(0); -+ -+#endif -+ -+#define ipts_err(ipts, format, arg...) do {\ -+ dev_err(&ipts->cldev->dev, format, ##arg);\ -+} while (0) -+ -+#define HID_PARALLEL_DATA_BUFFERS TOUCH_SENSOR_MAX_DATA_BUFFERS -+ -+#define IPTS_MAX_RETRY 3 -+ -+typedef struct ipts_buffer_info { -+ char *addr; -+ dma_addr_t dma_addr; -+} ipts_buffer_info_t; -+ -+typedef struct ipts_gfx_info { -+ u64 gfx_handle; -+ intel_ipts_ops_t ipts_ops; -+} ipts_gfx_info_t; -+ -+typedef struct ipts_resource { -+ /* ME & Gfx resource */ -+ ipts_buffer_info_t touch_data_buffer_raw[HID_PARALLEL_DATA_BUFFERS]; -+ ipts_buffer_info_t touch_data_buffer_hid; -+ -+ ipts_buffer_info_t feedback_buffer[HID_PARALLEL_DATA_BUFFERS]; -+ -+ ipts_buffer_info_t hid2me_buffer; -+ u32 hid2me_buffer_size; -+ -+ u8 wq_item_size; -+ intel_ipts_wq_info_t wq_info; -+ -+ /* ME2HID buffer */ -+ char *me2hid_buffer; -+ -+ /* Gfx specific resource */ -+ ipts_buffer_info_t raw_data_mode_output_buffer -+ [HID_PARALLEL_DATA_BUFFERS][MAX_NUM_OUTPUT_BUFFERS]; -+ -+ int num_of_outputs; -+ -+ bool default_resource_ready; -+ bool raw_data_resource_ready; -+} ipts_resource_t; -+ -+typedef struct ipts_info { -+ struct mei_cl_device *cldev; -+ struct hid_device *hid; -+ -+ struct work_struct init_work; -+ struct work_struct raw_data_work; -+ struct work_struct gfx_status_work; -+ -+ struct task_struct *event_loop; -+ -+#if IS_ENABLED(CONFIG_DEBUG_FS) -+ struct dentry *dbgfs_dir; -+#endif -+ -+ ipts_state_t state; -+ -+ touch_sensor_mode_t sensor_mode; -+ touch_sensor_get_device_info_rsp_data_t device_info; -+ ipts_resource_t resource; -+ u8 hid_input_report[HID_MAX_BUFFER_SIZE]; -+ int num_of_parallel_data_buffers; -+ bool hid_desc_ready; -+ -+ int current_buffer_index; -+ int last_buffer_completed; -+ int *last_submitted_id; -+ -+ ipts_gfx_info_t gfx_info; -+ u64 kernel_handle; -+ int gfx_status; -+ bool display_status; -+ -+ bool switch_sensor_mode; -+ touch_sensor_mode_t new_sensor_mode; -+ -+ int retry; -+ bool restart; -+} ipts_info_t; -+ -+#if IS_ENABLED(CONFIG_DEBUG_FS) -+int ipts_dbgfs_register(ipts_info_t *ipts, const char *name); -+void ipts_dbgfs_deregister(ipts_info_t *ipts); -+#else -+static int ipts_dbgfs_register(ipts_info_t *ipts, const char *name); -+static void ipts_dbgfs_deregister(ipts_info_t *ipts); -+#endif /* CONFIG_DEBUG_FS */ -+ -+/* inline functions */ -+static inline void ipts_set_state(ipts_info_t *ipts, ipts_state_t state) -+{ -+ ipts->state = state; -+} -+ -+static inline ipts_state_t ipts_get_state(const ipts_info_t *ipts) -+{ -+ return ipts->state; -+} -+ -+static inline bool ipts_is_default_resource_ready(const ipts_info_t *ipts) -+{ -+ return ipts->resource.default_resource_ready; -+} -+ -+static inline bool ipts_is_raw_data_resource_ready(const ipts_info_t *ipts) -+{ -+ return ipts->resource.raw_data_resource_ready; -+} -+ -+static inline ipts_buffer_info_t* ipts_get_feedback_buffer(ipts_info_t *ipts, -+ int buffer_idx) -+{ -+ return &ipts->resource.feedback_buffer[buffer_idx]; -+} -+ -+static inline ipts_buffer_info_t* ipts_get_touch_data_buffer_hid(ipts_info_t *ipts) -+{ -+ return &ipts->resource.touch_data_buffer_hid; -+} -+ -+static inline ipts_buffer_info_t* ipts_get_output_buffers_by_parallel_id( -+ ipts_info_t *ipts, -+ int parallel_idx) -+{ -+ return &ipts->resource.raw_data_mode_output_buffer[parallel_idx][0]; -+} -+ -+static inline ipts_buffer_info_t* ipts_get_hid2me_buffer(ipts_info_t *ipts) -+{ -+ return &ipts->resource.hid2me_buffer; -+} -+ -+static inline void ipts_set_wq_item_size(ipts_info_t *ipts, u8 size) -+{ -+ ipts->resource.wq_item_size = size; -+} -+ -+static inline u8 ipts_get_wq_item_size(const ipts_info_t *ipts) -+{ -+ return ipts->resource.wq_item_size; -+} -+ -+static inline int ipts_get_num_of_parallel_buffers(const ipts_info_t *ipts) -+{ -+ return ipts->num_of_parallel_data_buffers; -+} -+ -+#endif // _IPTS_H_ -diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h -index 77f7dff7098d..fb99dafd44a1 100644 ---- a/drivers/misc/mei/hw-me-regs.h -+++ b/drivers/misc/mei/hw-me-regs.h -@@ -59,6 +59,7 @@ - - #define MEI_DEV_ID_SPT 0x9D3A /* Sunrise Point */ - #define MEI_DEV_ID_SPT_2 0x9D3B /* Sunrise Point 2 */ -+#define MEI_DEV_ID_SPT_4 0x9D3E /* Sunrise Point 4 */ - #define MEI_DEV_ID_SPT_H 0xA13A /* Sunrise Point H */ - #define MEI_DEV_ID_SPT_H_2 0xA13B /* Sunrise Point H 2 */ - -diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c -index 541538eff8b1..49ab69d7a273 100644 ---- a/drivers/misc/mei/pci-me.c -+++ b/drivers/misc/mei/pci-me.c -@@ -77,6 +77,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { - - {MEI_PCI_DEVICE(MEI_DEV_ID_SPT, MEI_ME_PCH8_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_2, MEI_ME_PCH8_CFG)}, -+ {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_4, MEI_ME_PCH8_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H, MEI_ME_PCH8_SPS_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H_2, MEI_ME_PCH8_SPS_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_LBG, MEI_ME_PCH12_CFG)}, -diff --git a/include/linux/intel_ipts_fw.h b/include/linux/intel_ipts_fw.h -new file mode 100644 -index 000000000000..adbfd29459a2 ---- /dev/null -+++ b/include/linux/intel_ipts_fw.h -@@ -0,0 +1,14 @@ -+#ifndef _INTEL_IPTS_FW_H_ -+#define _INTEL_IPTS_FW_H_ -+ -+#include -+ -+#define MAX_IOCL_FILE_NAME_LEN 80 -+#define MAX_IOCL_FILE_PATH_LEN 256 -+#define IPTS_FW_HANDLER(name) int(*name)(const struct firmware **, \ -+ const char *, struct device *, void *) -+ -+int intel_ipts_add_fw_handler(IPTS_FW_HANDLER(handler), void *data); -+int intel_ipts_rm_fw_handler(IPTS_FW_HANDLER(handler)); -+ -+#endif // _INTEL_IPTS_FW_H_ -diff --git a/include/linux/intel_ipts_if.h b/include/linux/intel_ipts_if.h -new file mode 100644 -index 000000000000..bad44fb4f233 ---- /dev/null -+++ b/include/linux/intel_ipts_if.h -@@ -0,0 +1,76 @@ -+/* -+ * -+ * GFX interface to support Intel Precise Touch & Stylus -+ * Copyright (c) 2016 Intel Corporation. -+ * -+ * 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. -+ * -+ */ -+ -+#ifndef INTEL_IPTS_IF_H -+#define INTEL_IPTS_IF_H -+ -+enum { -+ IPTS_INTERFACE_V1 = 1, -+}; -+ -+#define IPTS_BUF_FLAG_CONTIGUOUS 0x01 -+ -+#define IPTS_NOTIFY_STA_BACKLIGHT_OFF 0x00 -+#define IPTS_NOTIFY_STA_BACKLIGHT_ON 0x01 -+ -+typedef struct intel_ipts_mapbuffer { -+ u32 size; -+ u32 flags; -+ void *gfx_addr; -+ void *cpu_addr; -+ u64 buf_handle; -+ u64 phy_addr; -+} intel_ipts_mapbuffer_t; -+ -+typedef struct intel_ipts_wq_info { -+ u64 db_addr; -+ u64 db_phy_addr; -+ u32 db_cookie_offset; -+ u32 wq_size; -+ u64 wq_addr; -+ u64 wq_phy_addr; -+ u64 wq_head_addr; /* head of wq is managed by GPU */ -+ u64 wq_head_phy_addr; /* head of wq is managed by GPU */ -+ u64 wq_tail_addr; /* tail of wq is managed by CSME */ -+ u64 wq_tail_phy_addr; /* tail of wq is managed by CSME */ -+} intel_ipts_wq_info_t; -+ -+typedef struct intel_ipts_ops { -+ int (*get_wq_info)(uint64_t gfx_handle, intel_ipts_wq_info_t *wq_info); -+ int (*map_buffer)(uint64_t gfx_handle, intel_ipts_mapbuffer_t *mapbuffer); -+ int (*unmap_buffer)(uint64_t gfx_handle, uint64_t buf_handle); -+} intel_ipts_ops_t; -+ -+typedef struct intel_ipts_callback { -+ void (*workload_complete)(void *data); -+ void (*notify_gfx_status)(u32 status, void *data); -+} intel_ipts_callback_t; -+ -+typedef struct intel_ipts_connect { -+ struct device *client; /* input : client device for PM setup */ -+ intel_ipts_callback_t ipts_cb; /* input : callback addresses */ -+ void *data; /* input : callback data */ -+ u32 if_version; /* input : interface version */ -+ -+ u32 gfx_version; /* output : gfx version */ -+ u64 gfx_handle; /* output : gfx handle */ -+ intel_ipts_ops_t ipts_ops; /* output : gfx ops for IPTS */ -+} intel_ipts_connect_t; -+ -+int intel_ipts_connect(intel_ipts_connect_t *ipts_connect); -+void intel_ipts_disconnect(uint64_t gfx_handle); -+ -+#endif // INTEL_IPTS_IF_H --- -2.23.0 - diff --git a/patches/5.2/0006-hid.patch b/patches/5.2/0006-hid.patch deleted file mode 100644 index 9d821188f..000000000 --- a/patches/5.2/0006-hid.patch +++ /dev/null @@ -1,151 +0,0 @@ -From 7f34de9f7976e18b2b12c45a4be88fe4be309ac3 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 26 Jul 2019 04:45:42 +0200 -Subject: [PATCH 06/12] hid - ---- - drivers/hid/hid-ids.h | 21 +++++++++---- - drivers/hid/hid-microsoft.c | 3 +- - drivers/hid/hid-multitouch.c | 57 ++++++++++++++++++++++++++++++++++++ - drivers/hid/hid-quirks.c | 11 +++++++ - 4 files changed, 86 insertions(+), 6 deletions(-) - -diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h -index 264139be7e29..fd396b91d5a8 100644 ---- a/drivers/hid/hid-ids.h -+++ b/drivers/hid/hid-ids.h -@@ -823,11 +823,22 @@ - #define USB_DEVICE_ID_MS_DIGITAL_MEDIA_3KV1 0x0732 - #define USB_DEVICE_ID_MS_DIGITAL_MEDIA_600 0x0750 - #define USB_DEVICE_ID_MS_COMFORT_MOUSE_4500 0x076c --#define USB_DEVICE_ID_MS_COMFORT_KEYBOARD 0x00e3 --#define USB_DEVICE_ID_MS_SURFACE_PRO_2 0x0799 --#define USB_DEVICE_ID_MS_TOUCH_COVER_2 0x07a7 --#define USB_DEVICE_ID_MS_TYPE_COVER_2 0x07a9 --#define USB_DEVICE_ID_MS_POWER_COVER 0x07da -+#define USB_DEVICE_ID_MS_COMFORT_KEYBOARD 0x00e3 -+#define USB_DEVICE_ID_MS_SURFACE_PRO_2 0x0799 -+#define USB_DEVICE_ID_MS_TOUCH_COVER_2 0x07a7 -+#define USB_DEVICE_ID_MS_TYPE_COVER_2 0x07a9 -+#define USB_DEVICE_ID_MS_TYPE_COVER_3 0x07de -+#define USB_DEVICE_ID_MS_TYPE_COVER_PRO_3 0x07dc -+#define USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_1 0x07de -+#define USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2 0x07e2 -+#define USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP 0x07dd -+#define USB_DEVICE_ID_MS_TYPE_COVER_PRO_4 0x07e8 -+#define USB_DEVICE_ID_MS_TYPE_COVER_PRO_4_1 0x07e4 -+#define USB_DEVICE_ID_MS_SURFACE_BOOK 0x07cd -+#define USB_DEVICE_ID_MS_SURFACE_BOOK_2 0x0922 -+#define USB_DEVICE_ID_MS_SURFACE_GO 0x096f -+#define USB_DEVICE_ID_MS_SURFACE_VHF 0xf001 -+#define USB_DEVICE_ID_MS_POWER_COVER 0x07da - #define USB_DEVICE_ID_MS_XBOX_ONE_S_CONTROLLER 0x02fd - #define USB_DEVICE_ID_MS_PIXART_MOUSE 0x00cb - -diff --git a/drivers/hid/hid-microsoft.c b/drivers/hid/hid-microsoft.c -index 8b3a922bdad3..0290a16881e5 100644 ---- a/drivers/hid/hid-microsoft.c -+++ b/drivers/hid/hid-microsoft.c -@@ -438,7 +438,8 @@ static const struct hid_device_id ms_devices[] = { - .driver_data = MS_HIDINPUT }, - { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_COMFORT_KEYBOARD), - .driver_data = MS_ERGONOMY}, -- -+ { HID_DEVICE(BUS_VIRTUAL, 0, USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_SURFACE_VHF), -+ .driver_data = MS_HIDINPUT}, - { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_BT), - .driver_data = MS_PRESENTER }, - { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x091B), -diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c -index 03448d3a29f2..09ca4b1f2797 100644 ---- a/drivers/hid/hid-multitouch.c -+++ b/drivers/hid/hid-multitouch.c -@@ -1983,6 +1983,63 @@ static const struct hid_device_id mt_devices[] = { - HID_USB_DEVICE(USB_VENDOR_ID_LG, - USB_DEVICE_ID_LG_MELFAS_MT) }, - -+ /* Microsoft Touch Cover */ -+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, -+ MT_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, -+ USB_DEVICE_ID_MS_TOUCH_COVER_2) }, -+ -+ /* Microsoft Type Cover */ -+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, -+ MT_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, -+ USB_DEVICE_ID_MS_TYPE_COVER_2) }, -+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, -+ MT_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, -+ USB_DEVICE_ID_MS_TYPE_COVER_3) }, -+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, -+ MT_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, -+ USB_DEVICE_ID_MS_TYPE_COVER_PRO_3) }, -+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, -+ MT_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, -+ USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_1) }, -+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, -+ MT_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, -+ USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2) }, -+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, -+ MT_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, -+ USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP) }, -+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, -+ MT_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, -+ USB_DEVICE_ID_MS_TYPE_COVER_PRO_4) }, -+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, -+ MT_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, -+ USB_DEVICE_ID_MS_TYPE_COVER_PRO_4_1) }, -+ -+ /* Microsoft Surface Book */ -+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, -+ MT_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, -+ USB_DEVICE_ID_MS_SURFACE_BOOK) }, -+ -+ /* Microsoft Surface Book 2 */ -+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, -+ MT_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, -+ USB_DEVICE_ID_MS_SURFACE_BOOK_2) }, -+ -+ /* Microsoft Surface Go */ -+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, -+ MT_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, -+ USB_DEVICE_ID_MS_SURFACE_GO) }, -+ -+ /* Microsoft Surface Laptop */ -+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, -+ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, -+ USB_VENDOR_ID_MICROSOFT, -+ USB_DEVICE_ID_MS_SURFACE_VHF) }, -+ -+ /* Microsoft Power Cover */ -+ { .driver_data = MT_CLS_EXPORT_ALL_INPUTS, -+ MT_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, -+ USB_DEVICE_ID_MS_POWER_COVER) }, -+ - /* MosArt panels */ - { .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE, - MT_USB_DEVICE(USB_VENDOR_ID_ASUS, -diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c -index efeeac5af633..0949e2144dc3 100644 ---- a/drivers/hid/hid-quirks.c -+++ b/drivers/hid/hid-quirks.c -@@ -113,6 +113,17 @@ static const struct hid_device_id hid_quirks[] = { - { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_SURFACE_PRO_2), HID_QUIRK_NO_INIT_REPORTS }, - { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TOUCH_COVER_2), HID_QUIRK_NO_INIT_REPORTS }, - { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_2), HID_QUIRK_NO_INIT_REPORTS }, -+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_3), HID_QUIRK_NO_INIT_REPORTS }, -+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3), HID_QUIRK_NO_INIT_REPORTS }, -+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_1), HID_QUIRK_NO_INIT_REPORTS }, -+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_2), HID_QUIRK_NO_INIT_REPORTS }, -+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_3_JP), HID_QUIRK_NO_INIT_REPORTS }, -+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_4), HID_QUIRK_NO_INIT_REPORTS }, -+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_PRO_4_1), HID_QUIRK_NO_INIT_REPORTS }, -+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_SURFACE_BOOK), HID_QUIRK_NO_INIT_REPORTS }, -+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_SURFACE_BOOK_2), HID_QUIRK_NO_INIT_REPORTS }, -+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_SURFACE_GO), HID_QUIRK_NO_INIT_REPORTS }, -+ { HID_DEVICE(BUS_VIRTUAL, 0, USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_SURFACE_VHF), HID_QUIRK_ALWAYS_POLL }, - { HID_USB_DEVICE(USB_VENDOR_ID_MOJO, USB_DEVICE_ID_RETRO_ADAPTER), HID_QUIRK_MULTI_INPUT }, - { HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL), HID_QUIRK_NO_INIT_REPORTS }, - { HID_USB_DEVICE(USB_VENDOR_ID_MULTIPLE_1781, USB_DEVICE_ID_RAPHNET_4NES4SNES_OLD), HID_QUIRK_MULTI_INPUT }, --- -2.23.0 - diff --git a/patches/5.2/0007-sdcard-reader.patch b/patches/5.2/0007-sdcard-reader.patch deleted file mode 100644 index 28249eac8..000000000 --- a/patches/5.2/0007-sdcard-reader.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 20f5d4e58e6e6f59dd62410bae2fbe8267f6b3fb Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 26 Jul 2019 04:45:55 +0200 -Subject: [PATCH 07/12] sdcard-reader - ---- - drivers/usb/core/hub.c | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) - -diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c -index 2844366dc173..989fabd6ab39 100644 ---- a/drivers/usb/core/hub.c -+++ b/drivers/usb/core/hub.c -@@ -4205,7 +4205,8 @@ void usb_enable_lpm(struct usb_device *udev) - if (!udev || !udev->parent || - udev->speed < USB_SPEED_SUPER || - !udev->lpm_capable || -- udev->state < USB_STATE_DEFAULT) -+ udev->state < USB_STATE_DEFAULT || -+ !udev->bos || !udev->bos->ss_cap) - return; - - udev->lpm_disable_count--; --- -2.23.0 - diff --git a/patches/5.2/0008-wifi.patch b/patches/5.2/0008-wifi.patch deleted file mode 100644 index 61065c66b..000000000 --- a/patches/5.2/0008-wifi.patch +++ /dev/null @@ -1,270 +0,0 @@ -From 3dba15603a8fd4eb99f5abf3e569bae6affa4d8a Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 26 Jul 2019 04:46:16 +0200 -Subject: [PATCH 08/12] wifi - ---- - drivers/net/wireless/marvell/mwifiex/11n_aggr.c | 3 +-- - drivers/net/wireless/marvell/mwifiex/cfg80211.c | 5 ++++- - drivers/net/wireless/marvell/mwifiex/cmdevt.c | 10 ++++++---- - drivers/net/wireless/marvell/mwifiex/fw.h | 1 + - drivers/net/wireless/marvell/mwifiex/main.c | 17 +++++++++++++---- - drivers/net/wireless/marvell/mwifiex/main.h | 2 ++ - drivers/net/wireless/marvell/mwifiex/pcie.c | 9 +++++++++ - drivers/net/wireless/marvell/mwifiex/sta_cmd.c | 4 ++-- - .../net/wireless/marvell/mwifiex/sta_cmdresp.c | 11 ++++++++--- - drivers/net/wireless/marvell/mwifiex/usb.c | 2 ++ - scripts/leaking_addresses.pl | 0 - 11 files changed, 48 insertions(+), 16 deletions(-) - mode change 100755 => 100644 scripts/leaking_addresses.pl - -diff --git a/drivers/net/wireless/marvell/mwifiex/11n_aggr.c b/drivers/net/wireless/marvell/mwifiex/11n_aggr.c -index 042a1d07f686..fc9041f58e9f 100644 ---- a/drivers/net/wireless/marvell/mwifiex/11n_aggr.c -+++ b/drivers/net/wireless/marvell/mwifiex/11n_aggr.c -@@ -200,8 +200,7 @@ mwifiex_11n_aggregate_pkt(struct mwifiex_private *priv, - - do { - /* Check if AMSDU can accommodate this MSDU */ -- if ((skb_aggr->len + skb_src->len + LLC_SNAP_LEN) > -- adapter->tx_buf_size) -+ if (skb_tailroom(skb_aggr) < (skb_src->len + LLC_SNAP_LEN)) - break; - - skb_src = skb_dequeue(&pra_list->skb_head); -diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -index e11a4bb67172..c3461a203deb 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c -+++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c -@@ -437,7 +437,10 @@ mwifiex_cfg80211_set_power_mgmt(struct wiphy *wiphy, - mwifiex_dbg(priv->adapter, INFO, - "info: ignore timeout value for IEEE Power Save\n"); - -- ps_mode = enabled; -+ //ps_mode = enabled; -+ -+ mwifiex_dbg(priv->adapter, INFO, "overriding ps_mode to false\n"); -+ ps_mode = 0; - - return mwifiex_drv_set_power(priv, &ps_mode); - } -diff --git a/drivers/net/wireless/marvell/mwifiex/cmdevt.c b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -index 8c35441fd9b7..71872139931e 100644 ---- a/drivers/net/wireless/marvell/mwifiex/cmdevt.c -+++ b/drivers/net/wireless/marvell/mwifiex/cmdevt.c -@@ -1006,6 +1006,7 @@ mwifiex_cmd_timeout_func(struct timer_list *t) - if (cmd_node->wait_q_enabled) { - adapter->cmd_wait_q.status = -ETIMEDOUT; - mwifiex_cancel_pending_ioctl(adapter); -+ adapter->cmd_sent = false; - } - } - if (adapter->hw_status == MWIFIEX_HW_STATUS_INITIALIZING) { -@@ -1013,11 +1014,11 @@ mwifiex_cmd_timeout_func(struct timer_list *t) - return; - } - -- if (adapter->if_ops.device_dump) -- adapter->if_ops.device_dump(adapter); -+ //if (adapter->if_ops.device_dump) -+ // adapter->if_ops.device_dump(adapter); - -- if (adapter->if_ops.card_reset) -- adapter->if_ops.card_reset(adapter); -+ //if (adapter->if_ops.card_reset) -+ // adapter->if_ops.card_reset(adapter); - } - - void -@@ -1583,6 +1584,7 @@ int mwifiex_ret_get_hw_spec(struct mwifiex_private *priv, - adapter->key_api_minor_ver); - break; - case FW_API_VER_ID: -+ case FW_KEY_API_VER_ID: - adapter->fw_api_ver = - api_rev->major_ver; - mwifiex_dbg(adapter, INFO, -diff --git a/drivers/net/wireless/marvell/mwifiex/fw.h b/drivers/net/wireless/marvell/mwifiex/fw.h -index 1fb76d2f5d3f..fb32379da99d 100644 ---- a/drivers/net/wireless/marvell/mwifiex/fw.h -+++ b/drivers/net/wireless/marvell/mwifiex/fw.h -@@ -1052,6 +1052,7 @@ struct host_cmd_ds_802_11_ps_mode_enh { - enum API_VER_ID { - KEY_API_VER_ID = 1, - FW_API_VER_ID = 2, -+ FW_KEY_API_VER_ID = 4, - }; - - struct hw_spec_api_rev { -diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c -index f6da8edab7f1..51a65f26206b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.c -+++ b/drivers/net/wireless/marvell/mwifiex/main.c -@@ -163,6 +163,7 @@ void mwifiex_queue_main_work(struct mwifiex_adapter *adapter) - spin_lock_irqsave(&adapter->main_proc_lock, flags); - if (adapter->mwifiex_processing) { - adapter->more_task_flag = true; -+ adapter->more_rx_task_flag = true; - spin_unlock_irqrestore(&adapter->main_proc_lock, flags); - } else { - spin_unlock_irqrestore(&adapter->main_proc_lock, flags); -@@ -171,18 +172,20 @@ void mwifiex_queue_main_work(struct mwifiex_adapter *adapter) - } - EXPORT_SYMBOL_GPL(mwifiex_queue_main_work); - --static void mwifiex_queue_rx_work(struct mwifiex_adapter *adapter) -+void mwifiex_queue_rx_work(struct mwifiex_adapter *adapter) - { - unsigned long flags; - - spin_lock_irqsave(&adapter->rx_proc_lock, flags); - if (adapter->rx_processing) { -+ adapter->more_rx_task_flag = true; - spin_unlock_irqrestore(&adapter->rx_proc_lock, flags); - } else { - spin_unlock_irqrestore(&adapter->rx_proc_lock, flags); - queue_work(adapter->rx_workqueue, &adapter->rx_work); - } - } -+EXPORT_SYMBOL_GPL(mwifiex_queue_rx_work); - - static int mwifiex_process_rx(struct mwifiex_adapter *adapter) - { -@@ -192,6 +195,7 @@ static int mwifiex_process_rx(struct mwifiex_adapter *adapter) - - spin_lock_irqsave(&adapter->rx_proc_lock, flags); - if (adapter->rx_processing || adapter->rx_locked) { -+ adapter->more_rx_task_flag = true; - spin_unlock_irqrestore(&adapter->rx_proc_lock, flags); - goto exit_rx_proc; - } else { -@@ -199,6 +203,7 @@ static int mwifiex_process_rx(struct mwifiex_adapter *adapter) - spin_unlock_irqrestore(&adapter->rx_proc_lock, flags); - } - -+rx_process_start: - /* Check for Rx data */ - while ((skb = skb_dequeue(&adapter->rx_data_q))) { - atomic_dec(&adapter->rx_pending); -@@ -220,6 +225,11 @@ static int mwifiex_process_rx(struct mwifiex_adapter *adapter) - } - } - spin_lock_irqsave(&adapter->rx_proc_lock, flags); -+ if (adapter->more_rx_task_flag) { -+ adapter->more_rx_task_flag = false; -+ spin_unlock_irqrestore(&adapter->rx_proc_lock, flags); -+ goto rx_process_start; -+ } - adapter->rx_processing = false; - spin_unlock_irqrestore(&adapter->rx_proc_lock, flags); - -@@ -283,11 +293,10 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) - mwifiex_process_hs_config(adapter); - if (adapter->if_ops.process_int_status) - adapter->if_ops.process_int_status(adapter); -+ if (adapter->rx_work_enabled && adapter->data_received) -+ mwifiex_queue_rx_work(adapter); - } - -- if (adapter->rx_work_enabled && adapter->data_received) -- mwifiex_queue_rx_work(adapter); -- - /* Need to wake up the card ? */ - if ((adapter->ps_state == PS_STATE_SLEEP) && - (adapter->pm_wakeup_card_req && -diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h -index e39bb5c42c9a..8ec3275dad6d 100644 ---- a/drivers/net/wireless/marvell/mwifiex/main.h -+++ b/drivers/net/wireless/marvell/mwifiex/main.h -@@ -909,6 +909,7 @@ struct mwifiex_adapter { - spinlock_t main_proc_lock; - u32 mwifiex_processing; - u8 more_task_flag; -+ u8 more_rx_task_flag; - u16 tx_buf_size; - u16 curr_tx_buf_size; - /* sdio single port rx aggregation capability */ -@@ -1695,6 +1696,7 @@ void mwifiex_upload_device_dump(struct mwifiex_adapter *adapter); - void *mwifiex_alloc_dma_align_buf(int rx_len, gfp_t flags); - void mwifiex_fw_dump_event(struct mwifiex_private *priv); - void mwifiex_queue_main_work(struct mwifiex_adapter *adapter); -+void mwifiex_queue_rx_work(struct mwifiex_adapter *adapter); - int mwifiex_get_wakeup_reason(struct mwifiex_private *priv, u16 action, - int cmd_type, - struct mwifiex_ds_wakeup_reason *wakeup_reason); -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index 3fe81b2a929a..6e734a83e6bf 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -1743,6 +1743,15 @@ static int mwifiex_pcie_process_cmd_complete(struct mwifiex_adapter *adapter) - } - - rx_len = get_unaligned_le16(skb->data); -+ -+ if (rx_len == 0) { -+ mwifiex_dbg(adapter, ERROR, -+ "0 byte cmdrsp\n"); -+ mwifiex_map_pci_memory(adapter, skb, MWIFIEX_UPLD_SIZE, -+ PCI_DMA_FROMDEVICE); -+ return 0; -+ } -+ - skb_put(skb, MWIFIEX_UPLD_SIZE - skb->len); - skb_trim(skb, rx_len); - -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -index 4ed10cf82f9a..485360e8534b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -@@ -30,8 +30,8 @@ static bool drcs; - module_param(drcs, bool, 0644); - MODULE_PARM_DESC(drcs, "multi-channel operation:1, single-channel operation:0"); - --static bool disable_auto_ds; --module_param(disable_auto_ds, bool, 0); -+static bool disable_auto_ds = 1; -+module_param(disable_auto_ds, bool, 0644); - MODULE_PARM_DESC(disable_auto_ds, - "deepsleep enabled=0(default), deepsleep disabled=1"); - /* -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -index 24b33e20e7a9..51d0f34625e1 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -@@ -48,9 +48,14 @@ mwifiex_process_cmdresp_error(struct mwifiex_private *priv, - struct host_cmd_ds_802_11_ps_mode_enh *pm; - unsigned long flags; - -- mwifiex_dbg(adapter, ERROR, -- "CMD_RESP: cmd %#x error, result=%#x\n", -- resp->command, resp->result); -+ if (resp->command == 271 && resp->result == 2){ -+ // ignore this command as the firmware does not support it -+ } -+ else { -+ mwifiex_dbg(adapter, ERROR, -+ "CMD_RESP: cmd %#x error, result=%#x\n", -+ resp->command, resp->result); -+ } - - if (adapter->curr_cmd->wait_q_enabled) - adapter->cmd_wait_q.status = -1; -diff --git a/drivers/net/wireless/marvell/mwifiex/usb.c b/drivers/net/wireless/marvell/mwifiex/usb.c -index d445acc4786b..ae8e60cc17cb 100644 ---- a/drivers/net/wireless/marvell/mwifiex/usb.c -+++ b/drivers/net/wireless/marvell/mwifiex/usb.c -@@ -144,6 +144,8 @@ static int mwifiex_usb_recv(struct mwifiex_adapter *adapter, - skb_queue_tail(&adapter->rx_data_q, skb); - adapter->data_received = true; - atomic_inc(&adapter->rx_pending); -+ if (adapter->rx_work_enabled) -+ mwifiex_queue_rx_work(adapter); - break; - default: - mwifiex_dbg(adapter, ERROR, -diff --git a/scripts/leaking_addresses.pl b/scripts/leaking_addresses.pl -old mode 100755 -new mode 100644 --- -2.23.0 - diff --git a/patches/5.2/0009-surface3-power.patch b/patches/5.2/0009-surface3-power.patch deleted file mode 100644 index 4820d6f00..000000000 --- a/patches/5.2/0009-surface3-power.patch +++ /dev/null @@ -1,655 +0,0 @@ -From 584dca5082c73e445989cc9dd7db57ba4e8f3f70 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 26 Jul 2019 04:46:48 +0200 -Subject: [PATCH 09/12] surface3-power - ---- - drivers/platform/x86/Kconfig | 7 + - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/surface3_power.c | 604 ++++++++++++++++++++++++++ - 3 files changed, 612 insertions(+) - create mode 100644 drivers/platform/x86/surface3_power.c - -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index c00cb830914a..04421fe566ba 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -1301,6 +1301,13 @@ config SURFACE_3_BUTTON - ---help--- - This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet. - -+config SURFACE_3_POWER_OPREGION -+ tristate "Surface 3 battery platform operation region support" -+ depends on ACPI && I2C -+ help -+ Select this option to enable support for ACPI operation -+ region of the Surface 3 battery platform driver. -+ - config INTEL_PUNIT_IPC - tristate "Intel P-Unit IPC Driver" - ---help--- -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 8b12c19dc165..58b07217c3cf 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -85,6 +85,7 @@ obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o - obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o - obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o -+obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o - obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o - obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ -diff --git a/drivers/platform/x86/surface3_power.c b/drivers/platform/x86/surface3_power.c -new file mode 100644 -index 000000000000..e0af01a60302 ---- /dev/null -+++ b/drivers/platform/x86/surface3_power.c -@@ -0,0 +1,604 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+ -+/* -+ * Supports for the power IC on the Surface 3 tablet. -+ * -+ * (C) Copyright 2016-2018 Red Hat, Inc -+ * (C) Copyright 2016-2018 Benjamin Tissoires -+ * (C) Copyright 2016 Stephen Just -+ * -+ */ -+ -+/* -+ * This driver has been reverse-engineered by parsing the DSDT of the Surface 3 -+ * and looking at the registers of the chips. -+ * -+ * The DSDT allowed to find out that: -+ * - the driver is required for the ACPI BAT0 device to communicate to the chip -+ * through an operation region. -+ * - the various defines for the operation region functions to communicate with -+ * this driver -+ * - the DSM 3f99e367-6220-4955-8b0f-06ef2ae79412 allows to trigger ACPI -+ * events to BAT0 (the code is all available in the DSDT). -+ * -+ * Further findings regarding the 2 chips declared in the MSHW0011 are: -+ * - there are 2 chips declared: -+ * . 0x22 seems to control the ADP1 line status (and probably the charger) -+ * . 0x55 controls the battery directly -+ * - the battery chip uses a SMBus protocol (using plain SMBus allows non -+ * destructive commands): -+ * . the commands/registers used are in the range 0x00..0x7F -+ * . if bit 8 (0x80) is set in the SMBus command, the returned value is the -+ * same as when it is not set. There is a high chance this bit is the -+ * read/write -+ * . the various registers semantic as been deduced by observing the register -+ * dumps. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define POLL_INTERVAL (2 * HZ) -+ -+struct mshw0011_data { -+ struct i2c_client *adp1; -+ struct i2c_client *bat0; -+ unsigned short notify_mask; -+ struct task_struct *poll_task; -+ bool kthread_running; -+ -+ bool charging; -+ bool bat_charging; -+ u8 trip_point; -+ s32 full_capacity; -+}; -+ -+struct mshw0011_lookup { -+ struct mshw0011_data *cdata; -+ unsigned int n; -+ unsigned int index; -+ int addr; -+}; -+ -+struct mshw0011_handler_data { -+ struct acpi_connection_info info; -+ struct i2c_client *client; -+}; -+ -+struct bix { -+ u32 revision; -+ u32 power_unit; -+ u32 design_capacity; -+ u32 last_full_charg_capacity; -+ u32 battery_technology; -+ u32 design_voltage; -+ u32 design_capacity_of_warning; -+ u32 design_capacity_of_low; -+ u32 cycle_count; -+ u32 measurement_accuracy; -+ u32 max_sampling_time; -+ u32 min_sampling_time; -+ u32 max_average_interval; -+ u32 min_average_interval; -+ u32 battery_capacity_granularity_1; -+ u32 battery_capacity_granularity_2; -+ char model[10]; -+ char serial[10]; -+ char type[10]; -+ char OEM[10]; -+} __packed; -+ -+struct bst { -+ u32 battery_state; -+ s32 battery_present_rate; -+ u32 battery_remaining_capacity; -+ u32 battery_present_voltage; -+} __packed; -+ -+struct gsb_command { -+ u8 arg0; -+ u8 arg1; -+ u8 arg2; -+} __packed; -+ -+struct gsb_buffer { -+ u8 status; -+ u8 len; -+ u8 ret; -+ union { -+ struct gsb_command cmd; -+ struct bst bst; -+ struct bix bix; -+ } __packed; -+} __packed; -+ -+ -+#define ACPI_BATTERY_STATE_DISCHARGING BIT(0) -+#define ACPI_BATTERY_STATE_CHARGING BIT(1) -+#define ACPI_BATTERY_STATE_CRITICAL BIT(2) -+ -+#define MSHW0011_CMD_DEST_BAT0 0x01 -+#define MSHW0011_CMD_DEST_ADP1 0x03 -+ -+#define MSHW0011_CMD_BAT0_STA 0x01 -+#define MSHW0011_CMD_BAT0_BIX 0x02 -+#define MSHW0011_CMD_BAT0_BCT 0x03 -+#define MSHW0011_CMD_BAT0_BTM 0x04 -+#define MSHW0011_CMD_BAT0_BST 0x05 -+#define MSHW0011_CMD_BAT0_BTP 0x06 -+#define MSHW0011_CMD_ADP1_PSR 0x07 -+#define MSHW0011_CMD_BAT0_PSOC 0x09 -+#define MSHW0011_CMD_BAT0_PMAX 0x0a -+#define MSHW0011_CMD_BAT0_PSRC 0x0b -+#define MSHW0011_CMD_BAT0_CHGI 0x0c -+#define MSHW0011_CMD_BAT0_ARTG 0x0d -+ -+#define MSHW0011_NOTIFY_GET_VERSION 0x00 -+#define MSHW0011_NOTIFY_ADP1 0x01 -+#define MSHW0011_NOTIFY_BAT0_BST 0x02 -+#define MSHW0011_NOTIFY_BAT0_BIX 0x05 -+ -+#define MSHW0011_ADP1_REG_PSR 0x04 -+ -+#define MSHW0011_BAT0_REG_CAPACITY 0x0c -+#define MSHW0011_BAT0_REG_FULL_CHG_CAPACITY 0x0e -+#define MSHW0011_BAT0_REG_DESIGN_CAPACITY 0x40 -+#define MSHW0011_BAT0_REG_VOLTAGE 0x08 -+#define MSHW0011_BAT0_REG_RATE 0x14 -+#define MSHW0011_BAT0_REG_OEM 0x45 -+#define MSHW0011_BAT0_REG_TYPE 0x4e -+#define MSHW0011_BAT0_REG_SERIAL_NO 0x56 -+#define MSHW0011_BAT0_REG_CYCLE_CNT 0x6e -+ -+#define MSHW0011_EV_2_5 0x1ff -+ -+static int -+mshw0011_notify(struct mshw0011_data *cdata, u8 arg1, u8 arg2, -+ unsigned int *ret_value) -+{ -+ static const guid_t mshw0011_guid = -+ GUID_INIT(0x3F99E367, 0x6220, 0x4955, -+ 0x8B, 0x0F, 0x06, 0xEF, 0x2A, 0xE7, 0x94, 0x12); -+ union acpi_object *obj; -+ struct acpi_device *adev; -+ acpi_handle handle; -+ unsigned int i; -+ -+ handle = ACPI_HANDLE(&cdata->adp1->dev); -+ if (!handle || acpi_bus_get_device(handle, &adev)) -+ return -ENODEV; -+ -+ obj = acpi_evaluate_dsm_typed(handle, &mshw0011_guid, arg1, arg2, NULL, -+ ACPI_TYPE_BUFFER); -+ if (!obj) { -+ dev_err(&cdata->adp1->dev, "device _DSM execution failed\n"); -+ return -ENODEV; -+ } -+ -+ *ret_value = 0; -+ for (i = 0; i < obj->buffer.length; i++) -+ *ret_value |= obj->buffer.pointer[i] << (i * 8); -+ -+ ACPI_FREE(obj); -+ return 0; -+} -+ -+static const struct bix default_bix = { -+ .revision = 0x00, -+ .power_unit = 0x01, -+ .design_capacity = 0x1dca, -+ .last_full_charg_capacity = 0x1dca, -+ .battery_technology = 0x01, -+ .design_voltage = 0x10df, -+ .design_capacity_of_warning = 0x8f, -+ .design_capacity_of_low = 0x47, -+ .cycle_count = 0xffffffff, -+ .measurement_accuracy = 0x00015f90, -+ .max_sampling_time = 0x03e8, -+ .min_sampling_time = 0x03e8, -+ .max_average_interval = 0x03e8, -+ .min_average_interval = 0x03e8, -+ .battery_capacity_granularity_1 = 0x45, -+ .battery_capacity_granularity_2 = 0x11, -+ .model = "P11G8M", -+ .serial = "", -+ .type = "LION", -+ .OEM = "", -+}; -+ -+static int mshw0011_bix(struct mshw0011_data *cdata, struct bix *bix) -+{ -+ struct i2c_client *client = cdata->bat0; -+ char buf[10]; -+ int ret; -+ -+ *bix = default_bix; -+ -+ /* get design capacity */ -+ ret = i2c_smbus_read_word_data(client, -+ MSHW0011_BAT0_REG_DESIGN_CAPACITY); -+ if (ret < 0) { -+ dev_err(&client->dev, "Error reading design capacity: %d\n", -+ ret); -+ return ret; -+ } -+ bix->design_capacity = ret; -+ -+ /* get last full charge capacity */ -+ ret = i2c_smbus_read_word_data(client, -+ MSHW0011_BAT0_REG_FULL_CHG_CAPACITY); -+ if (ret < 0) { -+ dev_err(&client->dev, -+ "Error reading last full charge capacity: %d\n", ret); -+ return ret; -+ } -+ bix->last_full_charg_capacity = ret; -+ -+ /* get serial number */ -+ ret = i2c_smbus_read_i2c_block_data(client, MSHW0011_BAT0_REG_SERIAL_NO, -+ 10, buf); -+ if (ret != 10) { -+ dev_err(&client->dev, "Error reading serial no: %d\n", ret); -+ return ret; -+ } -+ snprintf(bix->serial, ARRAY_SIZE(bix->serial), -+ "%*pE%*pE", 3, buf + 7, 6, buf); -+ -+ /* get cycle count */ -+ ret = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_CYCLE_CNT); -+ if (ret < 0) { -+ dev_err(&client->dev, "Error reading cycle count: %d\n", ret); -+ return ret; -+ } -+ bix->cycle_count = ret; -+ -+ /* get OEM name */ -+ ret = i2c_smbus_read_i2c_block_data(client, MSHW0011_BAT0_REG_OEM, -+ 4, buf); -+ if (ret != 4) { -+ dev_err(&client->dev, "Error reading cycle count: %d\n", ret); -+ return ret; -+ } -+ snprintf(bix->OEM, ARRAY_SIZE(bix->OEM), "%*pE", 3, buf); -+ -+ return 0; -+} -+ -+static int mshw0011_bst(struct mshw0011_data *cdata, struct bst *bst) -+{ -+ struct i2c_client *client = cdata->bat0; -+ int rate, capacity, voltage, state; -+ s16 tmp; -+ -+ rate = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_RATE); -+ if (rate < 0) -+ return rate; -+ -+ capacity = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_CAPACITY); -+ if (capacity < 0) -+ return capacity; -+ -+ voltage = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_VOLTAGE); -+ if (voltage < 0) -+ return voltage; -+ -+ tmp = rate; -+ bst->battery_present_rate = abs((s32)tmp); -+ -+ state = 0; -+ if ((s32) tmp > 0) -+ state |= ACPI_BATTERY_STATE_CHARGING; -+ else if ((s32) tmp < 0) -+ state |= ACPI_BATTERY_STATE_DISCHARGING; -+ bst->battery_state = state; -+ -+ bst->battery_remaining_capacity = capacity; -+ bst->battery_present_voltage = voltage; -+ -+ return 0; -+} -+ -+static int mshw0011_adp_psr(struct mshw0011_data *cdata) -+{ -+ struct i2c_client *client = cdata->adp1; -+ int ret; -+ -+ ret = i2c_smbus_read_byte_data(client, MSHW0011_ADP1_REG_PSR); -+ if (ret < 0) -+ return ret; -+ -+ return ret; -+} -+ -+static int mshw0011_isr(struct mshw0011_data *cdata) -+{ -+ struct bst bst; -+ struct bix bix; -+ int ret; -+ bool status, bat_status; -+ -+ ret = mshw0011_adp_psr(cdata); -+ if (ret < 0) -+ return ret; -+ -+ status = ret; -+ -+ if (status != cdata->charging) -+ mshw0011_notify(cdata, cdata->notify_mask, -+ MSHW0011_NOTIFY_ADP1, &ret); -+ -+ cdata->charging = status; -+ -+ ret = mshw0011_bst(cdata, &bst); -+ if (ret < 0) -+ return ret; -+ -+ bat_status = bst.battery_state; -+ -+ if (bat_status != cdata->bat_charging) -+ mshw0011_notify(cdata, cdata->notify_mask, -+ MSHW0011_NOTIFY_BAT0_BST, &ret); -+ -+ cdata->bat_charging = bat_status; -+ -+ ret = mshw0011_bix(cdata, &bix); -+ if (ret < 0) -+ return ret; -+ if (bix.last_full_charg_capacity != cdata->full_capacity) -+ mshw0011_notify(cdata, cdata->notify_mask, -+ MSHW0011_NOTIFY_BAT0_BIX, &ret); -+ -+ cdata->full_capacity = bix.last_full_charg_capacity; -+ -+ return 0; -+} -+ -+static int mshw0011_poll_task(void *data) -+{ -+ struct mshw0011_data *cdata = data; -+ int ret = 0; -+ -+ cdata->kthread_running = true; -+ -+ set_freezable(); -+ -+ while (!kthread_should_stop()) { -+ schedule_timeout_interruptible(POLL_INTERVAL); -+ try_to_freeze(); -+ ret = mshw0011_isr(data); -+ if (ret) -+ break; -+ } -+ -+ cdata->kthread_running = false; -+ return ret; -+} -+ -+static acpi_status -+mshw0011_space_handler(u32 function, acpi_physical_address command, -+ u32 bits, u64 *value64, -+ void *handler_context, void *region_context) -+{ -+ struct gsb_buffer *gsb = (struct gsb_buffer *)value64; -+ struct mshw0011_handler_data *data = handler_context; -+ struct acpi_connection_info *info = &data->info; -+ struct acpi_resource_i2c_serialbus *sb; -+ struct i2c_client *client = data->client; -+ struct mshw0011_data *cdata = i2c_get_clientdata(client); -+ struct acpi_resource *ares; -+ u32 accessor_type = function >> 16; -+ acpi_status ret; -+ int status = 1; -+ -+ ret = acpi_buffer_to_resource(info->connection, info->length, &ares); -+ if (ACPI_FAILURE(ret)) -+ return ret; -+ -+ if (!value64 || ares->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) { -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ sb = &ares->data.i2c_serial_bus; -+ if (sb->type != ACPI_RESOURCE_SERIAL_TYPE_I2C) { -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ if (gsb->cmd.arg0 == MSHW0011_CMD_DEST_ADP1 && -+ gsb->cmd.arg1 == MSHW0011_CMD_ADP1_PSR) { -+ ret = mshw0011_adp_psr(cdata); -+ if (ret >= 0) { -+ status = ret; -+ ret = 0; -+ } -+ goto out; -+ } -+ -+ if (gsb->cmd.arg0 != MSHW0011_CMD_DEST_BAT0) { -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ switch (gsb->cmd.arg1) { -+ case MSHW0011_CMD_BAT0_STA: -+ break; -+ case MSHW0011_CMD_BAT0_BIX: -+ ret = mshw0011_bix(cdata, &gsb->bix); -+ break; -+ case MSHW0011_CMD_BAT0_BTP: -+ cdata->trip_point = gsb->cmd.arg2; -+ break; -+ case MSHW0011_CMD_BAT0_BST: -+ ret = mshw0011_bst(cdata, &gsb->bst); -+ break; -+ default: -+ pr_info("command(0x%02x) is not supported.\n", gsb->cmd.arg1); -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ out: -+ gsb->ret = status; -+ gsb->status = 0; -+ -+ err: -+ ACPI_FREE(ares); -+ return ret; -+} -+ -+static int mshw0011_install_space_handler(struct i2c_client *client) -+{ -+ acpi_handle handle; -+ struct mshw0011_handler_data *data; -+ acpi_status status; -+ -+ handle = ACPI_HANDLE(&client->dev); -+ -+ if (!handle) -+ return -ENODEV; -+ -+ data = kzalloc(sizeof(struct mshw0011_handler_data), -+ GFP_KERNEL); -+ if (!data) -+ return -ENOMEM; -+ -+ data->client = client; -+ status = acpi_bus_attach_private_data(handle, (void *)data); -+ if (ACPI_FAILURE(status)) { -+ kfree(data); -+ return -ENOMEM; -+ } -+ -+ status = acpi_install_address_space_handler(handle, -+ ACPI_ADR_SPACE_GSBUS, -+ &mshw0011_space_handler, -+ NULL, -+ data); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&client->dev, "Error installing i2c space handler\n"); -+ acpi_bus_detach_private_data(handle); -+ kfree(data); -+ return -ENOMEM; -+ } -+ -+ acpi_walk_dep_device_list(handle); -+ return 0; -+} -+ -+static void mshw0011_remove_space_handler(struct i2c_client *client) -+{ -+ acpi_handle handle = ACPI_HANDLE(&client->dev); -+ struct mshw0011_handler_data *data; -+ acpi_status status; -+ -+ if (!handle) -+ return; -+ -+ acpi_remove_address_space_handler(handle, -+ ACPI_ADR_SPACE_GSBUS, -+ &mshw0011_space_handler); -+ -+ status = acpi_bus_get_private_data(handle, (void **)&data); -+ if (ACPI_SUCCESS(status)) -+ kfree(data); -+ -+ acpi_bus_detach_private_data(handle); -+} -+ -+static int mshw0011_probe(struct i2c_client *client) -+{ -+ struct i2c_board_info board_info; -+ struct device *dev = &client->dev; -+ struct i2c_client *bat0; -+ -+ struct mshw0011_data *data; -+ int error, mask; -+ -+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); -+ if (!data) -+ return -ENOMEM; -+ -+ data->adp1 = client; -+ i2c_set_clientdata(client, data); -+ -+ memset(&board_info, 0, sizeof(board_info)); -+ strlcpy(board_info.type, "MSHW0011-bat0", I2C_NAME_SIZE); -+ -+ bat0 = i2c_acpi_new_device(dev, 1, &board_info); -+ if (!bat0) -+ return -ENOMEM; -+ -+ data->bat0 = bat0; -+ i2c_set_clientdata(bat0, data); -+ -+ error = mshw0011_notify(data, 1, MSHW0011_NOTIFY_GET_VERSION, &mask); -+ if (error) -+ goto out_err; -+ -+ data->notify_mask = mask == MSHW0011_EV_2_5; -+ -+ data->poll_task = kthread_run(mshw0011_poll_task, data, "mshw0011_adp"); -+ if (IS_ERR(data->poll_task)) { -+ error = PTR_ERR(data->poll_task); -+ dev_err(&client->dev, "Unable to run kthread err %d\n", error); -+ goto out_err; -+ } -+ -+ error = mshw0011_install_space_handler(client); -+ if (error) -+ goto out_err; -+ -+ return 0; -+ -+out_err: -+ if (data->kthread_running) -+ kthread_stop(data->poll_task); -+ i2c_unregister_device(data->bat0); -+ return error; -+} -+ -+static int mshw0011_remove(struct i2c_client *client) -+{ -+ struct mshw0011_data *cdata = i2c_get_clientdata(client); -+ -+ mshw0011_remove_space_handler(client); -+ -+ if (cdata->kthread_running) -+ kthread_stop(cdata->poll_task); -+ -+ i2c_unregister_device(cdata->bat0); -+ -+ return 0; -+} -+ -+static const struct acpi_device_id mshw0011_acpi_match[] = { -+ { "MSHW0011", 0 }, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, mshw0011_acpi_match); -+ -+static struct i2c_driver mshw0011_driver = { -+ .probe_new = mshw0011_probe, -+ .remove = mshw0011_remove, -+ .driver = { -+ .name = "mshw0011", -+ .acpi_match_table = ACPI_PTR(mshw0011_acpi_match), -+ }, -+}; -+module_i2c_driver(mshw0011_driver); -+ -+MODULE_AUTHOR("Benjamin Tissoires "); -+MODULE_DESCRIPTION("mshw0011 driver"); -+MODULE_LICENSE("GPL v2"); --- -2.23.0 - diff --git a/patches/5.2/0010-mwlwifi.patch b/patches/5.2/0010-mwlwifi.patch deleted file mode 100644 index 9dbfe243f..000000000 --- a/patches/5.2/0010-mwlwifi.patch +++ /dev/null @@ -1,19753 +0,0 @@ -From 7d59140a399019781fb485e29111a8ea02b2d29f Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 26 Jul 2019 04:47:02 +0200 -Subject: [PATCH 10/12] mwlwifi - ---- - drivers/net/wireless/marvell/Kconfig | 1 + - drivers/net/wireless/marvell/Makefile | 1 + - drivers/net/wireless/marvell/mwlwifi/Kconfig | 22 + - drivers/net/wireless/marvell/mwlwifi/Makefile | 19 + - .../wireless/marvell/mwlwifi/Makefile.module | 28 + - .../net/wireless/marvell/mwlwifi/README.md | 142 + - drivers/net/wireless/marvell/mwlwifi/core.c | 1086 +++++ - drivers/net/wireless/marvell/mwlwifi/core.h | 517 +++ - .../net/wireless/marvell/mwlwifi/debugfs.c | 2201 ++++++++++ - .../net/wireless/marvell/mwlwifi/debugfs.h | 24 + - .../net/wireless/marvell/mwlwifi/hif/fwcmd.c | 3852 +++++++++++++++++ - .../net/wireless/marvell/mwlwifi/hif/fwcmd.h | 285 ++ - .../wireless/marvell/mwlwifi/hif/hif-ops.h | 297 ++ - .../net/wireless/marvell/mwlwifi/hif/hif.h | 81 + - .../wireless/marvell/mwlwifi/hif/hostcmd.h | 1285 ++++++ - .../wireless/marvell/mwlwifi/hif/pcie/dev.h | 1032 +++++ - .../wireless/marvell/mwlwifi/hif/pcie/fwdl.c | 274 ++ - .../wireless/marvell/mwlwifi/hif/pcie/fwdl.h | 24 + - .../wireless/marvell/mwlwifi/hif/pcie/pcie.c | 1645 +++++++ - .../wireless/marvell/mwlwifi/hif/pcie/rx.c | 540 +++ - .../wireless/marvell/mwlwifi/hif/pcie/rx.h | 25 + - .../marvell/mwlwifi/hif/pcie/rx_ndp.c | 612 +++ - .../marvell/mwlwifi/hif/pcie/rx_ndp.h | 26 + - .../marvell/mwlwifi/hif/pcie/sc4_ddr.h | 965 +++++ - .../wireless/marvell/mwlwifi/hif/pcie/tx.c | 1396 ++++++ - .../wireless/marvell/mwlwifi/hif/pcie/tx.h | 38 + - .../marvell/mwlwifi/hif/pcie/tx_ndp.c | 693 +++ - .../marvell/mwlwifi/hif/pcie/tx_ndp.h | 30 + - ...-workaround-for-80+80-and-160-MHz-channels | 32 + - .../wireless/marvell/mwlwifi/hostapd/README | 26 + - .../net/wireless/marvell/mwlwifi/mac80211.c | 933 ++++ - .../net/wireless/marvell/mwlwifi/mu_mimo.c | 20 + - .../net/wireless/marvell/mwlwifi/mu_mimo.h | 23 + - .../net/wireless/marvell/mwlwifi/sysadpt.h | 86 + - .../net/wireless/marvell/mwlwifi/thermal.c | 182 + - .../net/wireless/marvell/mwlwifi/thermal.h | 42 + - drivers/net/wireless/marvell/mwlwifi/utils.c | 576 +++ - drivers/net/wireless/marvell/mwlwifi/utils.h | 158 + - .../net/wireless/marvell/mwlwifi/vendor_cmd.c | 136 + - .../net/wireless/marvell/mwlwifi/vendor_cmd.h | 60 + - 40 files changed, 19415 insertions(+) - create mode 100644 drivers/net/wireless/marvell/mwlwifi/Kconfig - create mode 100644 drivers/net/wireless/marvell/mwlwifi/Makefile - create mode 100644 drivers/net/wireless/marvell/mwlwifi/Makefile.module - create mode 100644 drivers/net/wireless/marvell/mwlwifi/README.md - create mode 100644 drivers/net/wireless/marvell/mwlwifi/core.c - create mode 100644 drivers/net/wireless/marvell/mwlwifi/core.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/debugfs.c - create mode 100644 drivers/net/wireless/marvell/mwlwifi/debugfs.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/fwcmd.c - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/fwcmd.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/hif-ops.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/hif.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/hostcmd.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/pcie/dev.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/pcie/fwdl.c - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/pcie/fwdl.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/pcie/pcie.c - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx.c - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx_ndp.c - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx_ndp.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/pcie/sc4_ddr.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx.c - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx_ndp.c - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx_ndp.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hostapd/700-interoperability-workaround-for-80+80-and-160-MHz-channels - create mode 100644 drivers/net/wireless/marvell/mwlwifi/hostapd/README - create mode 100644 drivers/net/wireless/marvell/mwlwifi/mac80211.c - create mode 100644 drivers/net/wireless/marvell/mwlwifi/mu_mimo.c - create mode 100644 drivers/net/wireless/marvell/mwlwifi/mu_mimo.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/sysadpt.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/thermal.c - create mode 100644 drivers/net/wireless/marvell/mwlwifi/thermal.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/utils.c - create mode 100644 drivers/net/wireless/marvell/mwlwifi/utils.h - create mode 100644 drivers/net/wireless/marvell/mwlwifi/vendor_cmd.c - create mode 100644 drivers/net/wireless/marvell/mwlwifi/vendor_cmd.h - -diff --git a/drivers/net/wireless/marvell/Kconfig b/drivers/net/wireless/marvell/Kconfig -index dff82fdbea78..c0790dbd52c0 100644 ---- a/drivers/net/wireless/marvell/Kconfig -+++ b/drivers/net/wireless/marvell/Kconfig -@@ -15,6 +15,7 @@ if WLAN_VENDOR_MARVELL - source "drivers/net/wireless/marvell/libertas/Kconfig" - source "drivers/net/wireless/marvell/libertas_tf/Kconfig" - source "drivers/net/wireless/marvell/mwifiex/Kconfig" -+source "drivers/net/wireless/marvell/mwlwifi/Kconfig" - - config MWL8K - tristate "Marvell 88W8xxx PCI/PCIe Wireless support" -diff --git a/drivers/net/wireless/marvell/Makefile b/drivers/net/wireless/marvell/Makefile -index 25f6d5d2fa0c..00fccce28cdd 100644 ---- a/drivers/net/wireless/marvell/Makefile -+++ b/drivers/net/wireless/marvell/Makefile -@@ -3,5 +3,6 @@ obj-$(CONFIG_LIBERTAS) += libertas/ - - obj-$(CONFIG_LIBERTAS_THINFIRM) += libertas_tf/ - obj-$(CONFIG_MWIFIEX) += mwifiex/ -+obj-$(CONFIG_MWLWIFI) += mwlwifi/ - - obj-$(CONFIG_MWL8K) += mwl8k.o -diff --git a/drivers/net/wireless/marvell/mwlwifi/Kconfig b/drivers/net/wireless/marvell/mwlwifi/Kconfig -new file mode 100644 -index 000000000000..8832217430f5 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/Kconfig -@@ -0,0 +1,22 @@ -+config MWLWIFI -+ tristate "Marvell Avastar 88W8864/88W8897 PCIe driver (mac80211 compatible)" -+ depends on PCI && MAC80211 -+ select FW_LOADER -+ ---help--- -+ Select to build the driver supporting the: -+ -+ Marvell Wireless Wi-Fi 88W8864 modules -+ Marvell Wireless Wi-Fi 88W8897 modules -+ -+ This driver uses the kernel's mac80211 subsystem. -+ -+ If you want to compile the driver as a module (= code which can be -+ inserted in and removed from the running kernel whenever you want), -+ say M here and read . The -+ module will be called mwlwifi. -+ -+ NOTE: Selecting this driver may cause conflict with MWIFIEX driver -+ that also operates on the same part number 88W8897. Users should -+ select either MWIFIEX or MWLWIFI, not both. MWIFIEX is fullmac, -+ supporting more comprehensive client functions for laptops/embedded -+ devices. MWLWIFI is mac80211-based for full AP/Wireless Bridge. -diff --git a/drivers/net/wireless/marvell/mwlwifi/Makefile b/drivers/net/wireless/marvell/mwlwifi/Makefile -new file mode 100644 -index 000000000000..061833703c7f ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/Makefile -@@ -0,0 +1,19 @@ -+obj-$(CONFIG_MWLWIFI) += mwlwifi.o -+ -+mwlwifi-objs += core.o -+mwlwifi-objs += mac80211.o -+mwlwifi-objs += mu_mimo.o -+mwlwifi-objs += vendor_cmd.o -+mwlwifi-objs += utils.o -+mwlwifi-$(CONFIG_THERMAL) += thermal.o -+mwlwifi-$(CONFIG_DEBUG_FS) += debugfs.o -+mwlwifi-objs += hif/fwcmd.o -+mwlwifi-objs += hif/pcie/pcie.o -+mwlwifi-objs += hif/pcie/fwdl.o -+mwlwifi-objs += hif/pcie/tx.o -+mwlwifi-objs += hif/pcie/rx.o -+mwlwifi-objs += hif/pcie/tx_ndp.o -+mwlwifi-objs += hif/pcie/rx_ndp.o -+ -+ccflags-y += -I$(src) -+ccflags-y += -D__CHECK_ENDIAN__ -diff --git a/drivers/net/wireless/marvell/mwlwifi/Makefile.module b/drivers/net/wireless/marvell/mwlwifi/Makefile.module -new file mode 100644 -index 000000000000..d11a1b88cab6 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/Makefile.module -@@ -0,0 +1,28 @@ -+obj-m += mwlwifi.o -+ -+mwlwifi-objs += core.o -+mwlwifi-objs += mac80211.o -+mwlwifi-objs += mu_mimo.o -+mwlwifi-objs += vendor_cmd.o -+mwlwifi-objs += utils.o -+mwlwifi-$(CONFIG_THERMAL) += thermal.o -+mwlwifi-$(CONFIG_DEBUG_FS) += debugfs.o -+mwlwifi-objs += hif/fwcmd.o -+mwlwifi-objs += hif/pcie/pcie.o -+mwlwifi-objs += hif/pcie/fwdl.o -+mwlwifi-objs += hif/pcie/tx.o -+mwlwifi-objs += hif/pcie/rx.o -+mwlwifi-objs += hif/pcie/tx_ndp.o -+mwlwifi-objs += hif/pcie/rx_ndp.o -+ -+ccflags-y += -I$(src) -+ccflags-y += -O2 -funroll-loops -D__CHECK_ENDIAN__ -+ -+all: -+ $(MAKE) -C $(KDIR) M=$(PWD) -+ -+clean: -+ rm -f *.a *.s *.ko *.ko.cmd *.mod.* .mwlwifi.* modules.order Module.symvers -+ rm -rf .tmp_versions -+ find . -name ".*.o.cmd" -exec rm -f {} \; -+ find . -name "*.o" -exec rm -f {} \; -diff --git a/drivers/net/wireless/marvell/mwlwifi/README.md b/drivers/net/wireless/marvell/mwlwifi/README.md -new file mode 100644 -index 000000000000..530dc33e7f41 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/README.md -@@ -0,0 +1,142 @@ -+# mwlwifi -+mac80211 driver for the Marvell 88W8x64 802.11ac chip -+ -+## Building mwlwifi With OpenWrt/LEDE -+1. Modify `package/kernel/mwlwifi/Makefile`: -+ ``` -+ PKG_VERSION:=10.3.0.17-20160601 -+ PKG_SOURCE_VERSION:=4bb95ba1aeccce506a95499b49b9b844ecfae8a1 -+ ``` -+ -+2. Rename `package/kernel/mwlwifi/patches` to `package/kernel/mwlwifi/patches.tmp`. -+3. Run the following commands: -+ ```sh -+ make package/kernel/mwlwifi/clean -+ make V=s (-jx) -+ ``` -+ -+### Special Considerations -+* After driver 10.3.0.17-20160603, [MAX-MPDU-7991] should be removed from vht_capab command of hostapd. -+ -+* Hostpad must include the following commit for 160 MHz operation: -+ ``` -+ commit 03a72eacda5d9a1837a74387081596a0d5466ec1 -+ Author: Jouni Malinen -+ Date: Thu Dec 17 18:39:19 2015 +0200 -+ -+ VHT: Add an interoperability workaround for 80+80 and 160 MHz channels -+ -+ Number of deployed 80 MHz capable VHT stations that do not support 80+80 -+ and 160 MHz bandwidths seem to misbehave when trying to connect to an AP -+ that advertises 80+80 or 160 MHz channel bandwidth in the VHT Operation -+ element. To avoid such issues with deployed devices, modify the design -+ based on newly proposed IEEE 802.11 standard changes. -+ -+ This allows poorly implemented VHT 80 MHz stations to connect with the -+ AP in 80 MHz mode. 80+80 and 160 MHz capable stations need to support -+ the new workaround mechanism to allow full bandwidth to be used. -+ However, there are more or less no impacted station with 80+80/160 -+ capability deployed. -+ -+ Signed-off-by: Jouni Malinen jouni@qca.qualcomm.com -+ -+ Note: After hostapd package 2016-06-15, this commit is already included. -+ ``` -+ -+* In order to let STA mode to support 160 MHz operation, mac80211 package should be 2016-10-08 or later. -+ -+* WiFi device does not use HT rates when using TKIP as the encryption cipher. If you want to have good performance, please use AES only. -+ -+* DTS parameters for mwlwifi driver (pcie@X,0): -+ ```sh -+ #Disable 2g band -+ marvell,2ghz = <0>; -+ -+ #Disable 5g band -+ marvell,5ghz = <0>; -+ -+ #Specify antenna number, default is 4x4. For WRT1200AC, you must set these values to 2x2. -+ marvell,chainmask = <4 4>; -+ -+ #Specify external power table. If your device needs external power table, you must provide the power table via this parameter, otherwise the Tx power will be pretty low. -+ marvell,powertable -+ ``` -+ -+ To see if your device needs/accepts an external power table or not, run the following: -+ ```sh -+ cat /sys/kernel/debug/ieee80211/phy0/mwlwifi/info -+ ``` -+ -+ You should see a line in the results which looks like the following: -+ ```sh -+ power table loaded from dts: no -+ ``` -+ -+ If it is "no", it does not allow you to load external power table (for newer devices due to FCC regulations). If it is "yes", you must provide power table in DTS file (for older devices). -+ -+* Changing interrupt to different CPU cores: -+ ```sh -+ #Use CPU0: -+ echo 1 > /proc/irq/irq number of phy0 or phy1/smp_affinity -+ -+ #Use CPU1: -+ echo 2 > /proc/irq/irq number of phy0 or phy1/smp_affinity -+ ``` -+ -+* Note for DFS of WRT3200ACM (88W8964): -+ -+ All WRT3200ACM devices are programmed with device power table. Mwlwifi driver will base on region code to set country code for your device and it will not allow you to change country code. There are another wifi (phy2) on WRT3200ACM which is not mwlwifi. It will allow you to change country code. Under this case, country code setting will be conflicted and it will let DFS can't work. -+ -+ There are two ways to resolve this problem: -+ * Please don't change country code and let mwlwifi set it for you. -+ * Remove phy2. Under this case, even though you change country code, mwlwifi will reject it. Because phy2 is not existed, country code setting won't be conflicted. To do this, run the following commands (for OpenWrt/LEDE): -+ -+ ```sh -+ opkg remove kmod-mwifiex-sdio -+ opkg remove mwifiex-sdio-firmware -+ reboot -+ ``` -+ -+ The better way is let mwlwifi set country code for you. -+ -+## Replacing mwlwifi on a Current OpenWrt/LEDE Build -+ -+1. Establish a symbolic link to your working mwlwifi directory with current mwlwifi package name under directory "dl": -+ ```sh -+ ls -l mwlwifi* -+ ``` -+ -+ You should see something like the following: -+ ```sh -+ lrwxrwxrwx 1 dlin dlin 48 mwlwifi-10.3.2.0-20170110 -> /home/dlin/home2/projects/github/mwlwifi -+ -+ -rw-r--r-- 1 dlin dlin 4175136 mwlwifi-10.3.2.0-20170110.tar.xz -+ ``` -+ -+2. Back up original mwlwifi package and tar your working mwlwifi to replace original mwlwifi package: -+ -+ ```sh -+ tar Jcvf mwlwifi-10.3.2.0-20170110.tar.xz mwlwifi-10.3.2.0-20170110/. -+ ``` -+ -+3. You can use `make V=s` to build the whole image or `make V=s package/kernel/mwlwifi/compile` to build mwlwifi package. The generated whole image or mwlwifi package can be found under directory "bin". -+ -+Due to package version being the same as previous one, you need to add option `--force-reinstall` when you use `opkg` to update mwlwifi package on your device. -+ -+## Monitor interface for debug -+ -+1. Create moinitor interface mon0: -+ ```sh -+ iw wlan0/wlan1 interface add mon0 type monitor -+ ifconfig mon0 up -+ ``` -+ -+2. Use tcpdump to dump dhcp packets: -+ ```sh -+ tcpdump -vvvi mon0 -n port 67 and port 68 -+ ``` -+ -+3. Use tcpdump to dump icmp packets: -+ ```sh -+ tcpdump -vvvi mon0 icmp -+ ``` -diff --git a/drivers/net/wireless/marvell/mwlwifi/core.c b/drivers/net/wireless/marvell/mwlwifi/core.c -new file mode 100644 -index 000000000000..9d2b5511607e ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/core.c -@@ -0,0 +1,1086 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file implements core layer related functions. */ -+ -+#include -+ -+#include "sysadpt.h" -+#include "core.h" -+#include "vendor_cmd.h" -+#include "thermal.h" -+#include "debugfs.h" -+#include "hif/fwcmd.h" -+#include "hif/hif-ops.h" -+ -+#define CMD_BUF_SIZE 0x4000 -+#define INVALID_WATCHDOG 0xAA -+ -+static const struct ieee80211_channel mwl_channels_24[] = { -+ { .band = NL80211_BAND_2GHZ, .center_freq = 2412, .hw_value = 1, }, -+ { .band = NL80211_BAND_2GHZ, .center_freq = 2417, .hw_value = 2, }, -+ { .band = NL80211_BAND_2GHZ, .center_freq = 2422, .hw_value = 3, }, -+ { .band = NL80211_BAND_2GHZ, .center_freq = 2427, .hw_value = 4, }, -+ { .band = NL80211_BAND_2GHZ, .center_freq = 2432, .hw_value = 5, }, -+ { .band = NL80211_BAND_2GHZ, .center_freq = 2437, .hw_value = 6, }, -+ { .band = NL80211_BAND_2GHZ, .center_freq = 2442, .hw_value = 7, }, -+ { .band = NL80211_BAND_2GHZ, .center_freq = 2447, .hw_value = 8, }, -+ { .band = NL80211_BAND_2GHZ, .center_freq = 2452, .hw_value = 9, }, -+ { .band = NL80211_BAND_2GHZ, .center_freq = 2457, .hw_value = 10, }, -+ { .band = NL80211_BAND_2GHZ, .center_freq = 2462, .hw_value = 11, }, -+ { .band = NL80211_BAND_2GHZ, .center_freq = 2467, .hw_value = 12, }, -+ { .band = NL80211_BAND_2GHZ, .center_freq = 2472, .hw_value = 13, }, -+ { .band = NL80211_BAND_2GHZ, .center_freq = 2484, .hw_value = 14, }, -+}; -+ -+static const struct ieee80211_rate mwl_rates_24[] = { -+ { .bitrate = 10, .hw_value = 2, }, -+ { .bitrate = 20, .hw_value = 4, }, -+ { .bitrate = 55, .hw_value = 11, }, -+ { .bitrate = 110, .hw_value = 22, }, -+ { .bitrate = 220, .hw_value = 44, }, -+ { .bitrate = 60, .hw_value = 12, }, -+ { .bitrate = 90, .hw_value = 18, }, -+ { .bitrate = 120, .hw_value = 24, }, -+ { .bitrate = 180, .hw_value = 36, }, -+ { .bitrate = 240, .hw_value = 48, }, -+ { .bitrate = 360, .hw_value = 72, }, -+ { .bitrate = 480, .hw_value = 96, }, -+ { .bitrate = 540, .hw_value = 108, }, -+}; -+ -+static const struct ieee80211_channel mwl_channels_50[] = { -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5180, .hw_value = 36, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5200, .hw_value = 40, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5220, .hw_value = 44, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5240, .hw_value = 48, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5260, .hw_value = 52, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5280, .hw_value = 56, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5300, .hw_value = 60, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5320, .hw_value = 64, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5500, .hw_value = 100, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5520, .hw_value = 104, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5540, .hw_value = 108, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5560, .hw_value = 112, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5580, .hw_value = 116, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5600, .hw_value = 120, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5620, .hw_value = 124, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5640, .hw_value = 128, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5660, .hw_value = 132, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5680, .hw_value = 136, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5700, .hw_value = 140, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5720, .hw_value = 144, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5745, .hw_value = 149, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5765, .hw_value = 153, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5785, .hw_value = 157, }, -+ { .band = NL80211_BAND_5GHZ, .center_freq = 5805, .hw_value = 161, }, -+}; -+ -+static const struct ieee80211_rate mwl_rates_50[] = { -+ { .bitrate = 60, .hw_value = 12, }, -+ { .bitrate = 90, .hw_value = 18, }, -+ { .bitrate = 120, .hw_value = 24, }, -+ { .bitrate = 180, .hw_value = 36, }, -+ { .bitrate = 240, .hw_value = 48, }, -+ { .bitrate = 360, .hw_value = 72, }, -+ { .bitrate = 480, .hw_value = 96, }, -+ { .bitrate = 540, .hw_value = 108, }, -+}; -+ -+static const struct ieee80211_iface_limit ap_if_limits[] = { -+ { .max = SYSADPT_NUM_OF_AP, .types = BIT(NL80211_IFTYPE_AP) }, -+#if defined(CPTCFG_MAC80211_MESH) || defined(CONFIG_MAC80211_MESH) -+ { .max = SYSADPT_NUM_OF_MESH, .types = BIT(NL80211_IFTYPE_MESH_POINT) }, -+#endif -+ { .max = SYSADPT_NUM_OF_CLIENT, .types = BIT(NL80211_IFTYPE_STATION) }, -+}; -+ -+static const struct ieee80211_iface_combination ap_if_comb = { -+ .limits = ap_if_limits, -+ .n_limits = ARRAY_SIZE(ap_if_limits), -+ .max_interfaces = SYSADPT_NUM_OF_AP, -+ .num_different_channels = 1, -+ .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | -+ BIT(NL80211_CHAN_WIDTH_20) | -+ BIT(NL80211_CHAN_WIDTH_40) | -+ BIT(NL80211_CHAN_WIDTH_80) | -+ BIT(NL80211_CHAN_WIDTH_160), -+}; -+ -+struct region_code_mapping { -+ const char *alpha2; -+ u32 region_code; -+}; -+ -+static const struct region_code_mapping regmap[] = { -+ {"US", 0x10}, /* US FCC */ -+ {"CA", 0x20}, /* Canada */ -+ {"FR", 0x30}, /* France */ -+ {"ES", 0x31}, /* Spain */ -+ {"FR", 0x32}, /* France */ -+ {"JP", 0x40}, /* Japan */ -+ {"TW", 0x80}, /* Taiwan */ -+ {"AU", 0x81}, /* Australia */ -+ {"CN", 0x90}, /* China (Asia) */ -+}; -+ -+static int mwl_prepare_cmd_buf(struct mwl_priv *priv) -+{ -+ priv->pcmd_buf = -+ (unsigned short *)dmam_alloc_coherent(priv->dev, -+ CMD_BUF_SIZE, -+ &priv->pphys_cmd_buf, -+ GFP_KERNEL); -+ if (!priv->pcmd_buf) { -+ wiphy_err(priv->hw->wiphy, -+ "cannot alloc memory for command buffer\n"); -+ goto err; -+ } -+ wiphy_debug(priv->hw->wiphy, -+ "priv->pcmd_buf = %p priv->pphys_cmd_buf = %p\n", -+ priv->pcmd_buf, -+ (void *)priv->pphys_cmd_buf); -+ memset(priv->pcmd_buf, 0x00, CMD_BUF_SIZE); -+ -+ return 0; -+ -+err: -+ wiphy_err(priv->hw->wiphy, "command buffer alloc fail\n"); -+ -+ return -EIO; -+} -+ -+static int mwl_init_firmware(struct mwl_priv *priv, const char *fw_name, -+ const char *cal_name, const char *txpwrlmt_name) -+{ -+ int rc = 0; -+ -+ rc = request_firmware((const struct firmware **)&priv->fw_ucode, -+ fw_name, priv->dev); -+ -+ if (rc) { -+ wiphy_err(priv->hw->wiphy, -+ "cannot find firmware image <%s>\n", fw_name); -+ goto err_load_fw; -+ } -+ -+ rc = mwl_hif_download_firmware(priv->hw); -+ if (rc) { -+ wiphy_err(priv->hw->wiphy, -+ "cannot download firmware image <%s>\n", fw_name); -+ goto err_download_fw; -+ } -+ -+ if (cal_name) { -+ if ((request_firmware((const struct firmware **)&priv->cal_data, -+ cal_name, priv->dev)) < 0) -+ wiphy_debug(priv->hw->wiphy, -+ "cannot find calibtration data\n"); -+ } -+ -+ if (txpwrlmt_name) { -+ if ((request_firmware( -+ (const struct firmware **)&priv->txpwrlmt_file, -+ txpwrlmt_name, priv->dev)) < 0) -+ wiphy_debug(priv->hw->wiphy, -+ "cannot find tx power limit data\n"); -+ } -+ -+ return rc; -+ -+err_download_fw: -+ -+ release_firmware(priv->fw_ucode); -+ -+err_load_fw: -+ -+ wiphy_err(priv->hw->wiphy, "firmware init fail\n"); -+ -+ return rc; -+} -+ -+static void mwl_process_of_dts(struct mwl_priv *priv) -+{ -+#ifdef CONFIG_OF -+ struct property *prop; -+ u32 prop_value; -+ -+ priv->dt_node = -+ of_find_node_by_name(mwl_hif_device_node(priv->hw), -+ "mwlwifi"); -+ if (!priv->dt_node) -+ return; -+ -+ /* look for all matching property names */ -+ for_each_property_of_node(priv->dt_node, prop) { -+ if (strcmp(prop->name, "marvell,2ghz") == 0) -+ priv->disable_2g = true; -+ if (strcmp(prop->name, "marvell,5ghz") == 0) -+ priv->disable_5g = true; -+ if (strcmp(prop->name, "marvell,chainmask") == 0) { -+ prop_value = be32_to_cpu(*((__be32 *)prop->value)); -+ if (prop_value == 2) -+ priv->antenna_tx = ANTENNA_TX_2; -+ else if (prop_value == 3) -+ priv->antenna_tx = ANTENNA_TX_3; -+ -+ prop_value = be32_to_cpu(*((__be32 *) -+ (prop->value + 4))); -+ if (prop_value == 2) -+ priv->antenna_rx = ANTENNA_RX_2; -+ else if (prop_value == 3) -+ priv->antenna_rx = ANTENNA_RX_3; -+ } -+ } -+ -+ priv->pwr_node = of_find_node_by_name(priv->dt_node, -+ "marvell,powertable"); -+#endif -+} -+ -+static void mwl_reg_notifier(struct wiphy *wiphy, -+ struct regulatory_request *request) -+{ -+ struct ieee80211_hw *hw; -+ struct mwl_priv *priv; -+#ifdef CONFIG_OF -+ struct property *prop; -+ struct property *fcc_prop = NULL; -+ struct property *etsi_prop = NULL; -+ struct property *specific_prop = NULL; -+ u32 prop_value; -+ int i, j, k; -+#endif -+ -+ hw = wiphy_to_ieee80211_hw(wiphy); -+ priv = hw->priv; -+ -+ if (priv->forbidden_setting) { -+ if (!priv->regulatory_set) { -+ regulatory_hint(wiphy, priv->fw_alpha2); -+ priv->regulatory_set = true; -+ } else { -+ if (memcmp(priv->fw_alpha2, request->alpha2, 2)) -+ regulatory_hint(wiphy, priv->fw_alpha2); -+ } -+ return; -+ } -+ -+ priv->dfs_region = request->dfs_region; -+ -+#ifdef CONFIG_OF -+ if ((priv->chip_type != MWL8997) && (priv->pwr_node)) { -+ for_each_property_of_node(priv->pwr_node, prop) { -+ if (strcmp(prop->name, "FCC") == 0) -+ fcc_prop = prop; -+ if (strcmp(prop->name, "ETSI") == 0) -+ etsi_prop = prop; -+ if ((prop->name[0] == request->alpha2[0]) && -+ (prop->name[1] == request->alpha2[1])) -+ specific_prop = prop; -+ } -+ -+ prop = NULL; -+ -+ if (specific_prop) { -+ prop = specific_prop; -+ } else { -+ if (priv->dfs_region == NL80211_DFS_ETSI) -+ prop = etsi_prop; -+ else -+ prop = fcc_prop; -+ } -+ -+ if (prop) { -+ /* Reset the whole table */ -+ for (i = 0; i < SYSADPT_MAX_NUM_CHANNELS; i++) -+ memset(&priv->tx_pwr_tbl[i], 0, -+ sizeof(struct mwl_tx_pwr_tbl)); -+ -+ /* Load related power table */ -+ i = 0; -+ j = 0; -+ while (i < prop->length) { -+ prop_value = -+ be32_to_cpu(*(__be32 *) -+ (prop->value + i)); -+ priv->tx_pwr_tbl[j].channel = prop_value; -+ i += 4; -+ prop_value = -+ be32_to_cpu(*(__be32 *) -+ (prop->value + i)); -+ priv->tx_pwr_tbl[j].setcap = prop_value; -+ i += 4; -+ for (k = 0; k < SYSADPT_TX_POWER_LEVEL_TOTAL; -+ k++) { -+ prop_value = -+ be32_to_cpu(*(__be32 *) -+ (prop->value + i)); -+ priv->tx_pwr_tbl[j].tx_power[k] = -+ prop_value; -+ i += 4; -+ } -+ prop_value = -+ be32_to_cpu(*(__be32 *) -+ (prop->value + i)); -+ priv->tx_pwr_tbl[j].cdd = -+ (prop_value == 0) ? false : true; -+ i += 4; -+ prop_value = -+ be32_to_cpu(*(__be32 *) -+ (prop->value + i)); -+ priv->tx_pwr_tbl[j].txantenna2 = prop_value; -+ i += 4; -+ j++; -+ } -+ -+ /* Dump loaded power tabel */ -+ wiphy_debug(hw->wiphy, "regdomain: %s\n", prop->name); -+ for (i = 0; i < SYSADPT_MAX_NUM_CHANNELS; i++) { -+ struct mwl_tx_pwr_tbl *pwr_tbl; -+ char disp_buf[64]; -+ char *disp_ptr; -+ -+ pwr_tbl = &priv->tx_pwr_tbl[i]; -+ if (pwr_tbl->channel == 0) -+ break; -+ wiphy_debug(hw->wiphy, -+ "Channel: %d: 0x%x 0x%x 0x%x\n", -+ pwr_tbl->channel, -+ pwr_tbl->setcap, -+ pwr_tbl->cdd, -+ pwr_tbl->txantenna2); -+ disp_ptr = disp_buf; -+ for (j = 0; j < SYSADPT_TX_POWER_LEVEL_TOTAL; -+ j++) { -+ disp_ptr += -+ sprintf(disp_ptr, "%x ", -+ pwr_tbl->tx_power[j]); -+ } -+ wiphy_debug(hw->wiphy, "%s\n", disp_buf); -+ } -+ } -+ } -+#endif -+} -+ -+static void mwl_regd_init(struct mwl_priv *priv) -+{ -+ u8 region_code; -+ int rc; -+ int i; -+ -+ /* hook regulatory domain change notification */ -+ priv->hw->wiphy->reg_notifier = mwl_reg_notifier; -+ -+ if (priv->chip_type == MWL8964) -+ rc = mwl_fwcmd_get_pwr_tbl_sc4(priv->hw, -+ &priv->device_pwr_tbl[0], -+ ®ion_code, -+ &priv->number_of_channels, -+ 0); -+ else -+ rc = mwl_fwcmd_get_device_pwr_tbl(priv->hw, -+ &priv->device_pwr_tbl[0], -+ ®ion_code, -+ &priv->number_of_channels, -+ 0); -+ if (rc) -+ return; -+ -+ priv->forbidden_setting = true; -+ -+ for (i = 1; i < priv->number_of_channels; i++) { -+ if (priv->chip_type == MWL8964) -+ mwl_fwcmd_get_pwr_tbl_sc4(priv->hw, -+ &priv->device_pwr_tbl[i], -+ ®ion_code, -+ &priv->number_of_channels, -+ i); -+ else -+ mwl_fwcmd_get_device_pwr_tbl(priv->hw, -+ &priv->device_pwr_tbl[i], -+ ®ion_code, -+ &priv->number_of_channels, -+ i); -+ } -+ -+ for (i = 0; i < ARRAY_SIZE(regmap); i++) -+ if (regmap[i].region_code == priv->fw_region_code) { -+ memcpy(priv->fw_alpha2, regmap[i].alpha2, 2); -+ break; -+ } -+} -+ -+static void mwl_set_ht_caps(struct mwl_priv *priv, -+ struct ieee80211_supported_band *band) -+{ -+ struct ieee80211_hw *hw; -+ const u8 ant_rx_no[ANTENNA_RX_MAX] = { 3, 1, 2, 3}; -+ int i; -+ -+ hw = priv->hw; -+ -+ band->ht_cap.ht_supported = 1; -+ if (priv->chip_type == MWL8964) -+ band->ht_cap.cap |= IEEE80211_HT_CAP_MAX_AMSDU; -+ band->ht_cap.cap |= IEEE80211_HT_CAP_LDPC_CODING; -+ band->ht_cap.cap |= IEEE80211_HT_CAP_SUP_WIDTH_20_40; -+ band->ht_cap.cap |= IEEE80211_HT_CAP_SM_PS; -+ band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_20; -+ band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40; -+ band->ht_cap.cap |= IEEE80211_HT_CAP_DSSSCCK40; -+ -+ if ((priv->chip_type == MWL8997) && -+ (priv->antenna_tx != ANTENNA_TX_1)) { -+ band->ht_cap.cap |= IEEE80211_HT_CAP_TX_STBC; -+ band->ht_cap.cap |= (1 << IEEE80211_HT_CAP_RX_STBC_SHIFT); -+ } -+ -+ ieee80211_hw_set(hw, AMPDU_AGGREGATION); -+ ieee80211_hw_set(hw, SUPPORTS_AMSDU_IN_AMPDU); -+ band->ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K; -+ band->ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_4; -+ -+ for (i = 0; i < ant_rx_no[priv->antenna_rx]; i++) -+ band->ht_cap.mcs.rx_mask[i] = 0xff; -+ band->ht_cap.mcs.rx_mask[4] = 0x01; -+ -+ band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED; -+} -+ -+static void mwl_set_vht_caps(struct mwl_priv *priv, -+ struct ieee80211_supported_band *band) -+{ -+ u32 antenna_num = 4; -+ -+ band->vht_cap.vht_supported = 1; -+ -+ if (priv->chip_type == MWL8964) { -+ band->vht_cap.cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454; -+ band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_160; -+ band->vht_cap.cap |= IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ; -+ } else -+ band->vht_cap.cap |= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_3895; -+ band->vht_cap.cap |= IEEE80211_VHT_CAP_RXLDPC; -+ band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80; -+ band->vht_cap.cap |= IEEE80211_VHT_CAP_RXSTBC_1; -+ if (priv->antenna_tx != ANTENNA_TX_1) { -+ band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE; -+ if (priv->chip_type == MWL8964) -+ band->vht_cap.cap |= -+ IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE; -+ } -+ band->vht_cap.cap |= IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE; -+ if (priv->chip_type == MWL8964) -+ band->vht_cap.cap |= IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE; -+ band->vht_cap.cap |= IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK; -+ band->vht_cap.cap |= IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN; -+ band->vht_cap.cap |= IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN; -+ if (priv->chip_type == MWL8997) { -+ if (priv->antenna_tx != ANTENNA_TX_1) -+ band->vht_cap.cap |= IEEE80211_VHT_CAP_TXSTBC; -+ } -+ -+ if (priv->antenna_rx == ANTENNA_RX_1) -+ band->vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(0xfffe); -+ else if (priv->antenna_rx == ANTENNA_RX_2) -+ band->vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(0xfffa); -+ else -+ band->vht_cap.vht_mcs.rx_mcs_map = cpu_to_le16(0xffea); -+ -+ if (priv->antenna_tx == ANTENNA_TX_1) { -+ band->vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(0xfffe); -+ antenna_num = 1; -+ } else if (priv->antenna_tx == ANTENNA_TX_2) { -+ band->vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(0xfffa); -+ antenna_num = 2; -+ } else -+ band->vht_cap.vht_mcs.tx_mcs_map = cpu_to_le16(0xffea); -+ -+ if (band->vht_cap.cap & (IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE | -+ IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE)) { -+ band->vht_cap.cap |= -+ ((antenna_num - 1) << -+ IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT) & -+ IEEE80211_VHT_CAP_BEAMFORMEE_STS_MASK; -+ } -+ -+ if (band->vht_cap.cap & (IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE | -+ IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE)) { -+ band->vht_cap.cap |= -+ ((antenna_num - 1) << -+ IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT) & -+ IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_MASK; -+ } -+} -+ -+static void mwl_set_caps(struct mwl_priv *priv) -+{ -+ struct ieee80211_hw *hw; -+ -+ hw = priv->hw; -+ -+ /* set up band information for 2.4G */ -+ if (!priv->disable_2g) { -+ BUILD_BUG_ON(sizeof(priv->channels_24) != -+ sizeof(mwl_channels_24)); -+ memcpy(priv->channels_24, mwl_channels_24, -+ sizeof(mwl_channels_24)); -+ -+ BUILD_BUG_ON(sizeof(priv->rates_24) != sizeof(mwl_rates_24)); -+ memcpy(priv->rates_24, mwl_rates_24, sizeof(mwl_rates_24)); -+ -+ priv->band_24.band = NL80211_BAND_2GHZ; -+ priv->band_24.channels = priv->channels_24; -+ priv->band_24.n_channels = ARRAY_SIZE(mwl_channels_24); -+ priv->band_24.bitrates = priv->rates_24; -+ priv->band_24.n_bitrates = ARRAY_SIZE(mwl_rates_24); -+ -+ mwl_set_ht_caps(priv, &priv->band_24); -+ mwl_set_vht_caps(priv, &priv->band_24); -+ -+ hw->wiphy->bands[NL80211_BAND_2GHZ] = &priv->band_24; -+ } -+ -+ /* set up band information for 5G */ -+ if (!priv->disable_5g) { -+ BUILD_BUG_ON(sizeof(priv->channels_50) != -+ sizeof(mwl_channels_50)); -+ memcpy(priv->channels_50, mwl_channels_50, -+ sizeof(mwl_channels_50)); -+ -+ BUILD_BUG_ON(sizeof(priv->rates_50) != sizeof(mwl_rates_50)); -+ memcpy(priv->rates_50, mwl_rates_50, sizeof(mwl_rates_50)); -+ -+ priv->band_50.band = NL80211_BAND_5GHZ; -+ priv->band_50.channels = priv->channels_50; -+ priv->band_50.n_channels = ARRAY_SIZE(mwl_channels_50); -+ priv->band_50.bitrates = priv->rates_50; -+ priv->band_50.n_bitrates = ARRAY_SIZE(mwl_rates_50); -+ -+ mwl_set_ht_caps(priv, &priv->band_50); -+ mwl_set_vht_caps(priv, &priv->band_50); -+ -+ hw->wiphy->bands[NL80211_BAND_5GHZ] = &priv->band_50; -+ } -+} -+ -+static void mwl_heartbeat_handle(struct work_struct *work) -+{ -+ struct mwl_priv *priv = -+ container_of(work, struct mwl_priv, heartbeat_handle); -+ u32 val; -+ -+ mwl_fwcmd_get_addr_value(priv->hw, 0, 1, &val, 0); -+ priv->heartbeating = false; -+} -+ -+static void mwl_watchdog_ba_events(struct work_struct *work) -+{ -+ int rc; -+ u8 bitmap = 0, stream_index; -+ struct mwl_ampdu_stream *streams; -+ struct mwl_priv *priv = -+ container_of(work, struct mwl_priv, watchdog_ba_handle); -+ -+ rc = mwl_fwcmd_get_watchdog_bitmap(priv->hw, &bitmap); -+ -+ if (rc) -+ return; -+ -+ spin_lock_bh(&priv->stream_lock); -+ -+ /* the bitmap is the hw queue number. Map it to the ampdu queue. */ -+ if (bitmap != INVALID_WATCHDOG) { -+ if (bitmap == priv->ampdu_num) -+ stream_index = 0; -+ else if (bitmap > priv->ampdu_num) -+ stream_index = bitmap - priv->ampdu_num; -+ else -+ stream_index = bitmap + 3; /** queue 0 is stream 3*/ -+ -+ if (bitmap != 0xFF) { -+ /* Check if the stream is in use before disabling it */ -+ streams = &priv->ampdu[stream_index]; -+ -+ if (streams->state == AMPDU_STREAM_ACTIVE) -+ ieee80211_stop_tx_ba_session(streams->sta, -+ streams->tid); -+ } else { -+ for (stream_index = 0; -+ stream_index < priv->ampdu_num; -+ stream_index++) { -+ streams = &priv->ampdu[stream_index]; -+ -+ if (streams->state != AMPDU_STREAM_ACTIVE) -+ continue; -+ -+ ieee80211_stop_tx_ba_session(streams->sta, -+ streams->tid); -+ } -+ } -+ } -+ -+ spin_unlock_bh(&priv->stream_lock); -+} -+ -+static void mwl_account_handle(struct work_struct *work) -+{ -+ struct mwl_priv *priv = -+ container_of(work, struct mwl_priv, account_handle); -+ -+ mwl_hif_process_account(priv->hw); -+} -+ -+static void mwl_wds_check_handle(struct work_struct *work) -+{ -+ struct mwl_priv *priv = -+ container_of(work, struct mwl_priv, wds_check_handle); -+ struct mwl_sta *sta_info; -+ struct ieee80211_sta *sta; -+ bool wds_sta = false; -+ -+ spin_lock_bh(&priv->sta_lock); -+ list_for_each_entry(sta_info, &priv->sta_list, list) { -+ if (sta_info->wds) -+ continue; -+ sta = container_of((void *)sta_info, struct ieee80211_sta, -+ drv_priv); -+ if (ether_addr_equal(sta->addr, priv->wds_check_sta)) { -+ wds_sta = true; -+ break; -+ } -+ } -+ spin_unlock_bh(&priv->sta_lock); -+ -+ if (wds_sta) { -+ mwl_fwcmd_set_new_stn_wds_sc4(priv->hw, sta->addr); -+ sta_info->wds = true; -+ } -+ -+ priv->wds_check = false; -+} -+ -+static void mwl_chnl_switch_event(struct work_struct *work) -+{ -+ struct mwl_priv *priv = -+ container_of(work, struct mwl_priv, chnl_switch_handle); -+ struct mwl_vif *mwl_vif; -+ struct ieee80211_vif *vif; -+ -+ if (!priv->csa_active) { -+ wiphy_err(priv->hw->wiphy, -+ "csa is not active (got channel switch event)\n"); -+ return; -+ } -+ -+ spin_lock_bh(&priv->vif_lock); -+ list_for_each_entry(mwl_vif, &priv->vif_list, list) { -+ vif = container_of((void *)mwl_vif, struct ieee80211_vif, -+ drv_priv); -+ -+ if (vif->csa_active) -+ ieee80211_csa_finish(vif); -+ } -+ spin_unlock_bh(&priv->vif_lock); -+ -+ wiphy_info(priv->hw->wiphy, "channel switch is done\n"); -+ -+ priv->csa_active = false; -+} -+ -+static irqreturn_t mwl_isr(int irq, void *dev_id) -+{ -+ struct ieee80211_hw *hw = dev_id; -+ -+ return mwl_hif_irq_handler(hw); -+} -+ -+#ifdef timer_setup -+static void timer_routine(struct timer_list *t) -+{ -+ struct mwl_priv *priv = from_timer(priv, t, period_timer); -+ struct ieee80211_hw *hw = priv->hw; -+#else -+static void timer_routine(unsigned long data) -+{ -+ struct ieee80211_hw *hw = (struct ieee80211_hw *)data; -+ struct mwl_priv *priv = hw->priv; -+#endif -+ if (priv->heartbeat) { -+ if ((jiffies - priv->pre_jiffies) >= -+ msecs_to_jiffies(priv->heartbeat * 1000)) { -+ if (!priv->heartbeating) { -+ priv->heartbeating = true; -+ ieee80211_queue_work(hw, -+ &priv->heartbeat_handle); -+ } -+ priv->pre_jiffies = jiffies; -+ } -+ } -+ -+ mwl_hif_timer_routine(hw); -+ -+ mod_timer(&priv->period_timer, jiffies + -+ msecs_to_jiffies(SYSADPT_TIMER_WAKEUP_TIME)); -+} -+ -+static int mwl_wl_init(struct mwl_priv *priv) -+{ -+ struct ieee80211_hw *hw = priv->hw; -+ int rc; -+ u16 addr_num; -+ struct mac_address *mac_addr; -+ u8 last_nibble; -+ -+ hw->extra_tx_headroom = mwl_hif_get_tx_head_room(hw); -+ hw->queues = SYSADPT_TX_WMM_QUEUES; -+ -+ /* Set rssi values to dBm */ -+ ieee80211_hw_set(hw, SIGNAL_DBM); -+ ieee80211_hw_set(hw, HAS_RATE_CONTROL); -+ -+ /* Ask mac80211 not to trigger PS mode -+ * based on PM bit of incoming frames. -+ */ -+ ieee80211_hw_set(hw, AP_LINK_PS); -+ -+ ieee80211_hw_set(hw, SUPPORTS_PER_STA_GTK); -+ ieee80211_hw_set(hw, MFP_CAPABLE); -+ -+ hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; -+ hw->wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH; -+ hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS; -+ hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD; -+ -+ hw->vif_data_size = sizeof(struct mwl_vif); -+ hw->sta_data_size = sizeof(struct mwl_sta); -+ -+ priv->ap_macids_supported = 0x0000ffff; -+ priv->sta_macids_supported = 0x00010000; -+ priv->macids_used = 0; -+ INIT_LIST_HEAD(&priv->vif_list); -+ INIT_LIST_HEAD(&priv->sta_list); -+ -+ /* Set default radio state, preamble and wmm */ -+ priv->noise = -104; -+ priv->radio_on = false; -+ priv->radio_short_preamble = false; -+ priv->wmm_enabled = false; -+ priv->powinited = 0; -+ priv->wds_check = false; -+ if (priv->chip_type == MWL8997) -+ priv->pwr_level = SYSADPT_TX_GRP_PWR_LEVEL_TOTAL; -+ else -+ priv->pwr_level = SYSADPT_TX_POWER_LEVEL_TOTAL; -+ priv->dfs_test = false; -+ priv->csa_active = false; -+ priv->dfs_chirp_count_min = 5; -+ priv->dfs_chirp_time_interval = 1000; -+ priv->dfs_pw_filter = 0; -+ priv->dfs_min_num_radar = 5; -+ priv->dfs_min_pri_count = 4; -+ priv->bf_type = TXBF_MODE_AUTO; -+ -+ /* Handle watchdog ba events */ -+ INIT_WORK(&priv->heartbeat_handle, mwl_heartbeat_handle); -+ INIT_WORK(&priv->watchdog_ba_handle, mwl_watchdog_ba_events); -+ INIT_WORK(&priv->account_handle, mwl_account_handle); -+ INIT_WORK(&priv->wds_check_handle, mwl_wds_check_handle); -+ INIT_WORK(&priv->chnl_switch_handle, mwl_chnl_switch_event); -+ -+ mutex_init(&priv->fwcmd_mutex); -+ spin_lock_init(&priv->vif_lock); -+ spin_lock_init(&priv->sta_lock); -+ spin_lock_init(&priv->stream_lock); -+ spin_lock_init(&priv->stnid_lock); -+ -+ rc = mwl_thermal_register(priv); -+ if (rc) { -+ wiphy_err(hw->wiphy, "fail to register thermal framework\n"); -+ goto err_thermal_register; -+ } -+ -+ rc = mwl_hif_init(hw); -+ if (rc) { -+ wiphy_err(hw->wiphy, "fail to initialize host interface\n"); -+ goto err_hif_init; -+ } -+ -+ SET_IEEE80211_PERM_ADDR(hw, priv->hw_data.mac_addr); -+ -+ if (priv->chip_type == MWL8964) { -+ addr_num = SYSADPT_NUM_OF_AP + SYSADPT_NUM_OF_CLIENT; -+ hw->wiphy->n_addresses = addr_num; -+ hw->wiphy->addresses = -+ kzalloc(addr_num * sizeof(*mac_addr), GFP_KERNEL); -+ -+ mac_addr = &hw->wiphy->addresses[0]; -+ ether_addr_copy(mac_addr->addr, priv->hw_data.mac_addr); -+ last_nibble = mac_addr->addr[5] & 0x0F; -+ for (addr_num = 0; addr_num < SYSADPT_NUM_OF_AP; addr_num++) { -+ mac_addr = &hw->wiphy->addresses[addr_num + 1]; -+ ether_addr_copy(mac_addr->addr, priv->hw_data.mac_addr); -+ if (!strcmp(wiphy_name(hw->wiphy), "phy0")) { -+ last_nibble++; -+ if (last_nibble == 0x10) -+ last_nibble = 0; -+ } else { -+ last_nibble--; -+ if (last_nibble == 0xFF) -+ last_nibble = 0x0F; -+ } -+ mac_addr->addr[5] = -+ (mac_addr->addr[5] & 0xF0) | last_nibble; -+ mac_addr->addr[0] |= 0x2; -+ } -+ } -+ -+ wiphy_info(hw->wiphy, -+ "firmware version: 0x%x\n", priv->hw_data.fw_release_num); -+ -+ if (priv->chip_type == MWL8997) { -+ mwl_fwcmd_set_cfg_data(hw, 2); -+ mwl_fwcmd_set_txpwrlmt_cfg_data(hw); -+ mwl_fwcmd_get_txpwrlmt_cfg_data(hw); -+ } -+ -+ if (priv->chip_type == MWL8964) -+ rc = mwl_fwcmd_get_fw_region_code_sc4(hw, -+ &priv->fw_region_code); -+ else -+ rc = mwl_fwcmd_get_fw_region_code(hw, &priv->fw_region_code); -+ if (!rc) { -+ priv->fw_device_pwrtbl = true; -+ mwl_regd_init(priv); -+ wiphy_info(hw->wiphy, -+ "firmware region code: %x\n", priv->fw_region_code); -+ } -+ -+ if (priv->chip_type == MWL8997) -+ mwl_fwcmd_dump_otp_data(hw); -+ -+ mwl_fwcmd_radio_disable(hw); -+ mwl_fwcmd_rf_antenna(hw, WL_ANTENNATYPE_TX, priv->antenna_tx); -+ mwl_fwcmd_rf_antenna(hw, WL_ANTENNATYPE_RX, priv->antenna_rx); -+ -+ hw->wiphy->interface_modes = 0; -+ hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_AP); -+#if defined(CPTCFG_MAC80211_MESH) || defined(CONFIG_MAC80211_MESH) -+ hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_MESH_POINT); -+#endif -+ hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_STATION); -+ hw->wiphy->iface_combinations = &ap_if_comb; -+ hw->wiphy->n_iface_combinations = 1; -+ -+ mwl_set_caps(priv); -+ -+ priv->led_blink_enable = 1; -+ priv->led_blink_rate = LED_BLINK_RATE_MID; -+ mwl_fwcmd_led_ctrl(hw, priv->led_blink_enable, priv->led_blink_rate); -+ -+ vendor_cmd_register(hw->wiphy); -+ -+ rc = ieee80211_register_hw(hw); -+ if (rc) { -+ wiphy_err(hw->wiphy, "fail to register device\n"); -+ goto err_register_hw; -+ } -+ -+ priv->irq = mwl_hif_get_irq_num(hw); -+ rc = request_irq(priv->irq, mwl_isr, IRQF_SHARED, -+ mwl_hif_get_driver_name(hw), hw); -+ if (rc) { -+ priv->irq = -1; -+ wiphy_err(hw->wiphy, "fail to register IRQ handler\n"); -+ goto err_register_irq; -+ } -+#ifdef timer_setup -+ timer_setup(&priv->period_timer, timer_routine, 0); -+#else -+ setup_timer(&priv->period_timer, timer_routine, (unsigned long)hw); -+#endif -+ mod_timer(&priv->period_timer, jiffies + -+ msecs_to_jiffies(SYSADPT_TIMER_WAKEUP_TIME)); -+ -+ return rc; -+ -+err_register_hw: -+err_register_irq: -+ mwl_hif_deinit(hw); -+ -+err_hif_init: -+err_thermal_register: -+ -+ wiphy_err(hw->wiphy, "init fail\n"); -+ -+ return rc; -+} -+ -+static void mwl_wl_deinit(struct mwl_priv *priv) -+{ -+ struct ieee80211_hw *hw = priv->hw; -+ -+ del_timer_sync(&priv->period_timer); -+ -+ if (priv->irq != -1) { -+ free_irq(priv->irq, hw); -+ priv->irq = -1; -+ } -+ -+ if (priv->chip_type == MWL8964) -+ kfree(hw->wiphy->addresses); -+ ieee80211_unregister_hw(hw); -+ mwl_thermal_unregister(priv); -+ cancel_work_sync(&priv->chnl_switch_handle); -+ cancel_work_sync(&priv->account_handle); -+ cancel_work_sync(&priv->wds_check_handle); -+ cancel_work_sync(&priv->watchdog_ba_handle); -+ cancel_work_sync(&priv->heartbeat_handle); -+ mwl_hif_deinit(hw); -+} -+ -+struct ieee80211_hw *mwl_alloc_hw(int bus_type, -+ int chip_type, -+ struct device *dev, -+ const struct mwl_hif_ops *ops, -+ size_t hif_data_len) -+{ -+ struct ieee80211_hw *hw; -+ struct mwl_priv *priv; -+ int priv_size; -+ -+ priv_size = ALIGN(sizeof(*priv), NETDEV_ALIGN) + hif_data_len; -+ -+ hw = ieee80211_alloc_hw(priv_size, &mwl_mac80211_ops); -+ if (!hw) { -+ pr_err("ieee80211 alloc hw failed\n"); -+ return NULL; -+ } -+ -+ priv = hw->priv; -+ priv->hw = hw; -+ priv->dev = dev; -+ priv->chip_type = chip_type; -+ priv->fw_device_pwrtbl = false; -+ priv->forbidden_setting = false; -+ priv->regulatory_set = false; -+ priv->use_short_slot = false; -+ priv->use_short_preamble = false; -+ priv->disable_2g = false; -+ priv->disable_5g = false; -+ priv->tx_amsdu = true; -+ priv->hif.bus = bus_type; -+ priv->hif.ops = ops; -+ priv->hif.priv = (char *)priv + ALIGN(sizeof(*priv), NETDEV_ALIGN); -+ priv->ampdu_num = mwl_hif_get_ampdu_num(hw); -+ priv->ampdu = -+ kzalloc(priv->ampdu_num * sizeof(*priv->ampdu), GFP_KERNEL); -+ if (!priv->ampdu) { -+ ieee80211_free_hw(hw); -+ pr_err("alloc ampdu stream failed\n"); -+ return NULL; -+ } -+ -+ if (chip_type == MWL8964) -+ priv->stnid_num = SYSADPT_MAX_STA_SC4; -+ else -+ priv->stnid_num = SYSADPT_MAX_STA; -+ priv->stnid = -+ kzalloc(priv->stnid_num * sizeof(struct mwl_stnid), GFP_KERNEL); -+ if (!priv->stnid) { -+ kfree(priv->ampdu); -+ ieee80211_free_hw(hw); -+ pr_err("alloc stnid failed\n"); -+ return NULL; -+ } -+ priv->available_stnid = 0; -+ -+ SET_IEEE80211_DEV(hw, dev); -+ -+ return hw; -+} -+ -+void mwl_free_hw(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ kfree(priv->stnid); -+ kfree(priv->ampdu); -+ ieee80211_free_hw(hw); -+} -+ -+int mwl_init_hw(struct ieee80211_hw *hw, const char *fw_name, -+ const char *cal_name, const char *txpwrlmt_name) -+{ -+ struct mwl_priv *priv = hw->priv; -+ int rc; -+ int tx_num = 4, rx_num = 4; -+ -+ -+ rc = mwl_prepare_cmd_buf(priv); -+ if (rc) { -+ wiphy_err(hw->wiphy, "fail to prepare command buffer\n"); -+ return -ENOMEM; -+ } -+ -+ rc = mwl_init_firmware(priv, fw_name, cal_name, txpwrlmt_name); -+ if (rc) { -+ wiphy_err(hw->wiphy, "fail to initialize firmware\n"); -+ return -EIO; -+ } -+ -+ /* firmware is loaded to H/W, it can be released now */ -+ release_firmware(priv->fw_ucode); -+ -+ mwl_process_of_dts(priv); -+ -+ rc = mwl_wl_init(priv); -+ if (rc) { -+ wiphy_err(hw->wiphy, "fail to initialize wireless lan\n"); -+ return -EIO; -+ } -+ -+ wiphy_info(priv->hw->wiphy, "2G %s, 5G %s\n", -+ priv->disable_2g ? "disabled" : "enabled", -+ priv->disable_5g ? "disabled" : "enabled"); -+ -+ if (priv->antenna_tx == ANTENNA_TX_2) -+ tx_num = 2; -+ else if (priv->antenna_tx == ANTENNA_TX_3) -+ tx_num = 3; -+ if (priv->antenna_rx == ANTENNA_RX_2) -+ rx_num = 2; -+ else if (priv->antenna_rx == ANTENNA_RX_3) -+ rx_num = 3; -+ wiphy_info(priv->hw->wiphy, "%d TX antennas, %d RX antennas\n", -+ tx_num, rx_num); -+ -+#ifdef CONFIG_DEBUG_FS -+ mwl_debugfs_init(hw); -+#endif -+ -+ return 0; -+} -+ -+void mwl_deinit_hw(struct ieee80211_hw *hw) -+{ -+#ifdef CONFIG_DEBUG_FS -+ mwl_debugfs_remove(hw); -+#endif -+ -+ mwl_wl_deinit(hw->priv); -+} -diff --git a/drivers/net/wireless/marvell/mwlwifi/core.h b/drivers/net/wireless/marvell/mwlwifi/core.h -new file mode 100644 -index 000000000000..00069c4f0b44 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/core.h -@@ -0,0 +1,517 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines core layer related functions. */ -+ -+#ifndef _CORE_H_ -+#define _CORE_H_ -+ -+#include -+#include -+#include -+#include -+ -+#include "hif/hif.h" -+ -+/* antenna control */ -+#define ANTENNA_TX_4_AUTO 0 -+#define ANTENNA_TX_1 1 -+#define ANTENNA_TX_2 3 -+#define ANTENNA_TX_3 7 -+#define ANTENNA_RX_4_AUTO 0 -+#define ANTENNA_RX_1 1 -+#define ANTENNA_RX_2 2 -+#define ANTENNA_RX_3 3 -+#define ANTENNA_RX_MAX 4 -+ -+/* band related constants */ -+#define BAND_24_CHANNEL_NUM 14 -+#define BAND_24_RATE_NUM 13 -+#define BAND_50_CHANNEL_NUM 24 -+#define BAND_50_RATE_NUM 8 -+ -+#define NUM_WEP_KEYS 4 -+#define MWL_MAX_TID 8 -+#define MWL_AMSDU_SIZE_4K 1 -+#define MWL_AMSDU_SIZE_8K 2 -+#define MWL_AMSDU_SIZE_11K 3 -+ -+/* power init */ -+#define MWL_POWER_INIT_1 1 -+#define MWL_POWER_INIT_2 2 -+ -+/* tx rate information constants */ -+#define TX_RATE_FORMAT_LEGACY 0 -+#define TX_RATE_FORMAT_11N 1 -+#define TX_RATE_FORMAT_11AC 2 -+ -+#define TX_RATE_BANDWIDTH_20 0 -+#define TX_RATE_BANDWIDTH_40 1 -+#define TX_RATE_BANDWIDTH_80 2 -+#define TX_RATE_BANDWIDTH_160 3 -+ -+#define TX_RATE_INFO_STD_GI 0 -+#define TX_RATE_INFO_SHORT_GI 1 -+ -+/* tx rate information */ -+/* 0: legacy format 1: 11n format 2: 11ac format */ -+#define MWL_TX_RATE_FORMAT_MASK 0x00000003 -+#define MWL_TX_RATE_STBC_MASK 0x00000004 -+#define MWL_TX_RATE_STBC_SHIFT 2 -+/* 0: 20 MHz 1: 40 MHz 2: 80 MHz 3: 160 MHz */ -+#define MWL_TX_RATE_BANDWIDTH_MASK 0x00000030 -+#define MWL_TX_RATE_BANDWIDTH_SHIFT 4 -+/* 0: normal 1: short */ -+#define MWL_TX_RATE_SHORTGI_MASK 0x00000040 -+#define MWL_TX_RATE_SHORTGI_SHIFT 6 -+#define MWL_TX_RATE_RATEIDMCS_MASK 0x00007F00 -+#define MWL_TX_RATE_RATEIDMCS_SHIFT 8 -+/* 0: long 1: short */ -+#define MWL_TX_RATE_PREAMBLE_MASK 0x00008000 -+#define MWL_TX_RATE_PREAMBLE_SHIFT 15 -+#define MWL_TX_RATE_POWERID_MASK 0x003F0000 -+#define MWL_TX_RATE_POWERID_SHIFT 16 -+#define MWL_TX_RATE_ADVCODING_MASK 0x00400000 -+#define MWL_TX_RATE_ADVCODING_SHIFT 22 -+/* 0: beam forming off 1: beam forming on */ -+#define MWL_TX_RATE_BF_MASK 0x00800000 -+#define MWL_TX_RATE_BF_SHIFT 23 -+#define MWL_TX_RATE_ANTSELECT_MASK 0xFF000000 -+#define MWL_TX_RATE_ANTSELECT_SHIFT 24 -+ -+#define ACNT_BA_SIZE 1000 -+ -+/* Q stats */ -+#define QS_MAX_DATA_RATES_G 14 -+#define QS_NUM_SUPPORTED_11N_BW 2 -+#define QS_NUM_SUPPORTED_GI 2 -+#define QS_NUM_SUPPORTED_MCS 24 -+#define QS_NUM_SUPPORTED_11AC_NSS 3 -+#define QS_NUM_SUPPORTED_11AC_BW 4 -+#define QS_NUM_SUPPORTED_11AC_MCS 10 -+#define TX_RATE_HISTO_CUSTOM_CNT 1 -+#define TX_RATE_HISTO_PER_CNT 5 -+#define MAX_DATA_RATES_G 14 -+#define MAX_SUPPORTED_MCS 24 -+#define MAX_SUPPORTED_11AC_RATES 20 -+/* MAX_DATA_RATES_G + MAX_SUPPORTED_MCS + MAX_SUPPORTED_11AC_RATES */ -+#define MAX_SUPPORTED_RATES 58 -+#define SU_MIMO 0 -+#define MU_MIMO 1 -+#define SU_MU_TYPE_CNT 2 /* traffic type, SU and MU */ -+ -+/* BF operation mode */ -+#define TXBF_MODE_OFF 0x05 -+#define TXBF_MODE_AUTO 0x06 -+#define TXBF_MODE_BFMER_AUTO 0x07 -+ -+static const u8 TX_HISTO_PER_THRES[TX_RATE_HISTO_PER_CNT - 1] = {6, 12, 20, 30}; -+ -+enum { -+ MWL8864 = 0, -+ MWL8897, -+ MWL8964, -+ MWL8997, -+ MWLUNKNOWN, -+}; -+ -+enum mwl_bus { -+ MWL_BUS_PCIE, -+ MWL_BUS_SDIO, -+}; -+ -+enum { -+ AP_MODE_11AC = 0x10, /* generic 11ac indication mode */ -+ AP_MODE_2_4GHZ_11AC_MIXED = 0x17, -+}; -+ -+enum { -+ AMPDU_NO_STREAM = 0, -+ AMPDU_STREAM_NEW, -+ AMPDU_STREAM_IN_PROGRESS, -+ AMPDU_STREAM_ACTIVE, -+}; -+ -+enum { -+ LED_BLINK_RATE_LOW = 0x1, -+ LED_BLINK_RATE_MID, -+ LED_BLINK_RATE_HIGH, -+}; -+ -+struct mwl_chip_info { -+ const char *part_name; -+ const char *fw_image; -+ const char *cal_file; -+ const char *txpwrlmt_file; -+ int antenna_tx; -+ int antenna_rx; -+}; -+ -+struct mwl_device_pwr_tbl { -+ u8 channel; -+ u8 tx_pwr[SYSADPT_TX_PWR_LEVEL_TOTAL_SC4]; -+ u8 dfs_capable; -+ u8 ax_ant; -+ u8 cdd; -+}; -+ -+struct mwl_tx_pwr_tbl { -+ u8 channel; -+ u8 setcap; -+ u16 txantenna2; -+ u16 tx_power[SYSADPT_TX_POWER_LEVEL_TOTAL]; -+ bool cdd; -+}; -+ -+struct mwl_hw_data { -+ u32 fw_release_num; /* MajNbr:MinNbr:SubMin:PatchLevel */ -+ u8 hw_version; /* plain number indicating version */ -+ unsigned char mac_addr[ETH_ALEN]; /* well known -> AA:BB:CC:DD:EE:FF */ -+}; -+ -+struct mwl_ampdu_stream { -+ struct ieee80211_sta *sta; -+ u8 tid; -+ u8 state; -+ int idx; -+}; -+ -+struct mwl_stnid { -+ int macid; /* keep macid for related stnid */ -+ u16 aid; /* keep aid for related stnid */ -+}; -+ -+struct otp_data { -+ u8 buf[SYSADPT_OTP_BUF_SIZE]; -+ u32 len; /* Actual size of data in buf[] */ -+}; -+ -+struct txpwrlmt_cfg_data { -+ u8 buf[SYSADPT_TXPWRLMT_CFG_BUF_SIZE]; -+ u32 len; /* Actual size of data in buf[] */ -+}; -+ -+struct mwl_priv { -+ struct ieee80211_hw *hw; -+ struct device *dev; -+ struct firmware *fw_ucode; -+ struct firmware *cal_data; -+ struct firmware *txpwrlmt_file; -+ struct otp_data otp_data; -+ struct txpwrlmt_cfg_data txpwrlmt_data; -+ bool fw_device_pwrtbl; -+ bool forbidden_setting; -+ bool regulatory_set; -+ u32 fw_region_code; -+ char fw_alpha2[2]; -+ u8 number_of_channels; -+ struct mwl_device_pwr_tbl device_pwr_tbl[SYSADPT_MAX_NUM_CHANNELS]; -+ int chip_type; -+ -+ bool use_short_slot; -+ bool use_short_preamble; -+ -+ struct { -+ enum mwl_bus bus; -+ const struct mwl_hif_ops *ops; -+ void *priv; -+ } hif; -+ -+ struct device_node *dt_node; -+ struct device_node *pwr_node; -+ bool disable_2g; -+ bool disable_5g; -+ int antenna_tx; -+ int antenna_rx; -+ bool tx_amsdu; -+ bool dump_hostcmd; -+ bool dump_probe; -+ -+ struct mwl_tx_pwr_tbl tx_pwr_tbl[SYSADPT_MAX_NUM_CHANNELS]; -+ bool cdd; -+ u16 txantenna2; -+ u8 powinited; -+ u8 pwr_level; -+ u16 max_tx_pow[SYSADPT_TX_GRP_PWR_LEVEL_TOTAL]; /* max tx power (dBm) */ -+ u16 target_powers[SYSADPT_TX_GRP_PWR_LEVEL_TOTAL]; /* target powers */ -+ -+ struct mutex fwcmd_mutex; /* for firmware command */ -+ unsigned short *pcmd_buf; /* pointer to CmdBuf (virtual) */ -+ dma_addr_t pphys_cmd_buf; /* pointer to CmdBuf (physical) */ -+ bool in_send_cmd; -+ bool cmd_timeout; -+ bool rmmod; -+ int heartbeat; -+ u32 pre_jiffies; -+ bool heartbeating; -+ struct work_struct heartbeat_handle; -+ -+ int irq; -+ struct mwl_hw_data hw_data; /* Adapter HW specific info */ -+ -+ struct timer_list period_timer; -+ -+ /* keep survey information */ -+ bool sw_scanning; -+ int survey_info_idx; -+ struct mwl_survey_info survey_info[SYSADPT_MAX_NUM_CHANNELS]; -+ struct mwl_survey_info cur_survey_info; -+ -+ s8 noise; /* Most recently reported noise in dBm */ -+ -+ struct ieee80211_supported_band band_24; -+ struct ieee80211_channel channels_24[BAND_24_CHANNEL_NUM]; -+ struct ieee80211_rate rates_24[BAND_24_RATE_NUM]; -+ struct ieee80211_supported_band band_50; -+ struct ieee80211_channel channels_50[BAND_50_CHANNEL_NUM]; -+ struct ieee80211_rate rates_50[BAND_50_RATE_NUM]; -+ -+ u32 ap_macids_supported; -+ u32 sta_macids_supported; -+ u32 macids_used; -+ u32 running_bsses; /* bitmap of running BSSes */ -+ -+ struct { -+ spinlock_t vif_lock; /* for private interface info */ -+ struct list_head vif_list; /* List of interfaces. */ -+ } ____cacheline_aligned_in_smp; -+ -+ struct { -+ spinlock_t sta_lock; /* for private sta info */ -+ struct list_head sta_list; /* List of stations */ -+ } ____cacheline_aligned_in_smp; -+ -+ /* ampdu stream information */ -+ /* for ampdu stream */ -+ int ampdu_num; -+ struct { -+ spinlock_t stream_lock; /* for BA stream */ -+ struct mwl_ampdu_stream *ampdu; -+ } ____cacheline_aligned_in_smp; -+ struct work_struct watchdog_ba_handle; -+ -+ /* station id */ -+ int stnid_num; -+ struct { -+ spinlock_t stnid_lock; /* for station id */ -+ struct mwl_stnid *stnid; -+ u16 available_stnid; -+ } ____cacheline_aligned_in_smp; -+ -+ bool radio_on; -+ bool radio_short_preamble; -+ bool wmm_enabled; -+ struct ieee80211_tx_queue_params wmm_params[SYSADPT_TX_WMM_QUEUES]; -+ -+ struct work_struct account_handle; -+ -+ bool wds_check; -+ struct work_struct wds_check_handle; -+ u8 wds_check_sta[ETH_ALEN]; -+ -+ bool dfs_test; -+ bool csa_active; -+ struct work_struct chnl_switch_handle; -+ enum nl80211_dfs_regions dfs_region; -+ u16 dfs_chirp_count_min; -+ u16 dfs_chirp_time_interval; -+ u16 dfs_pw_filter; -+ u16 dfs_min_num_radar; -+ u16 dfs_min_pri_count; -+ -+ u8 bf_type; -+ -+ struct thermal_cooling_device *cdev; -+ u32 throttle_state; -+ u32 quiet_period; -+ int temperature; -+ -+ u8 led_blink_enable; -+ u8 led_blink_rate; -+ -+ struct dentry *debugfs_phy; -+ u32 reg_type; -+ u32 reg_offset; -+ u32 reg_value; -+ int ra_aid; -+ int ba_aid; -+ int fixed_rate; -+ bool coredump_text; -+ u32 ra_tx_attempt[2][6]; -+}; -+ -+struct beacon_info { -+ bool valid; -+ u16 cap_info; -+ u8 power_constraint; -+ u8 b_rate_set[SYSADPT_MAX_DATA_RATES_G]; -+ u8 op_rate_set[SYSADPT_MAX_DATA_RATES_G]; -+ u8 ie_list_ht[148]; -+ u8 ie_list_vht[24]; -+ u8 *ie_wmm_ptr; -+ u8 *ie_wsc_ptr; -+ u8 *ie_rsn_ptr; -+ u8 *ie_rsn48_ptr; -+ u8 *ie_mde_ptr; -+ u8 *ie_ht_ptr; -+ u8 *ie_vht_ptr; -+ u8 *ie_country_ptr; -+ u8 *ie_meshid_ptr; -+ u8 *ie_meshcfg_ptr; -+ u8 *ie_meshchsw_ptr; -+ u8 ie_wmm_len; -+ u8 ie_wsc_len; -+ u8 ie_rsn_len; -+ u8 ie_rsn48_len; -+ u8 ie_mde_len; -+ u8 ie_ht_len; -+ u8 ie_vht_len; -+ u8 ie_country_len; -+ u8 ie_meshid_len; -+ u8 ie_meshcfg_len; -+ u8 ie_meshchsw_len; -+}; -+ -+struct mwl_vif { -+ struct list_head list; -+ enum nl80211_iftype type; -+ int macid; /* Firmware macid for this vif. */ -+ u16 seqno; /* Non AMPDU sequence number assigned by driver. */ -+ struct { /* Saved WEP keys */ -+ u8 enabled; -+ u8 key[sizeof(struct ieee80211_key_conf) + WLAN_KEY_LEN_WEP104]; -+ } wep_key_conf[NUM_WEP_KEYS]; -+ u8 bssid[ETH_ALEN]; /* BSSID */ -+ u8 sta_mac[ETH_ALEN]; /* Station mac address */ -+ /* A flag to indicate is HW crypto is enabled for this bssid */ -+ bool is_hw_crypto_enabled; -+ /* Indicate if this is station mode */ -+ struct beacon_info beacon_info; -+ bool set_beacon; -+ int basic_rate_idx; -+ u8 broadcast_ssid; -+ u16 iv16; -+ u32 iv32; -+ s8 keyidx; -+}; -+ -+struct mwl_tx_info { -+ unsigned long start_time; -+ u32 pkts; -+}; -+ -+struct mwl_amsdu_frag { -+ struct sk_buff *skb; -+ u8 *cur_pos; -+ unsigned long jiffies; -+ u8 pad; -+ u8 num; -+}; -+ -+struct mwl_amsdu_ctrl { -+ struct mwl_amsdu_frag frag[SYSADPT_TX_WMM_QUEUES]; -+ u8 cap; -+}; -+ -+struct mwl_tx_ba_stats { -+ u8 ba_hole; /* Total pkt not acked in a BA bitmap */ -+ u8 ba_expected; /* Total Tx pkt expected to be acked */ -+ u8 no_ba; /* No BA is received */ -+ u8 pad; /* Unused */ -+}; -+ -+struct mwl_tx_ba_hist { -+ u16 index; /* Current buffer index */ -+ u8 type; /* 0:SU, 1: MU */ -+ bool enable; -+ struct mwl_tx_ba_stats *ba_stats; -+}; -+ -+struct mwl_tx_hist_data { -+ u32 rateinfo; -+ u32 cnt; -+ /* store according to TX_HISTO_PER_THRES threshold */ -+ u32 per[TX_RATE_HISTO_PER_CNT]; -+}; -+ -+struct mwl_tx_hist { -+ struct mwl_tx_hist_data su_rate[MAX_SUPPORTED_RATES]; -+ struct mwl_tx_hist_data mu_rate -+ [QS_NUM_SUPPORTED_11AC_NSS - 1][QS_NUM_SUPPORTED_11AC_BW] -+ [QS_NUM_SUPPORTED_GI][QS_NUM_SUPPORTED_11AC_MCS]; -+ struct mwl_tx_hist_data custom_rate[TX_RATE_HISTO_CUSTOM_CNT]; -+ /* Current rate for 0:SU, 1:MU */ -+ u32 cur_rate_info[SU_MU_TYPE_CNT]; -+ /* Total tx attempt cnt for 0:SU, 1:MU */ -+ u32 total_tx_cnt[SU_MU_TYPE_CNT]; -+}; -+ -+struct mwl_sta { -+ struct list_head list; -+ struct mwl_vif *mwl_vif; -+ u16 stnid; -+ u16 sta_stnid; -+ bool wds; -+ bool is_mesh_node; -+ bool is_ampdu_allowed; -+ struct mwl_tx_info tx_stats[MWL_MAX_TID]; -+ u32 check_ba_failed[MWL_MAX_TID]; -+ struct mwl_tx_ba_hist ba_hist; -+ bool is_amsdu_allowed; -+ bool is_key_set; -+ /* for amsdu aggregation */ -+ struct { -+ spinlock_t amsdu_lock; /* for amsdu */ -+ struct mwl_amsdu_ctrl amsdu_ctrl; -+ } ____cacheline_aligned_in_smp; -+ struct mwl_tx_hist tx_hist; -+ u32 tx_rate_info; -+ u16 rx_format; -+ u16 rx_nss; -+ u16 rx_bw; -+ u16 rx_gi; -+ u16 rx_rate_mcs; -+ u8 rx_signal; -+ u16 iv16; -+ u32 iv32; -+}; -+ -+static inline struct mwl_vif *mwl_dev_get_vif(const struct ieee80211_vif *vif) -+{ -+ return (struct mwl_vif *)&vif->drv_priv; -+} -+ -+static inline struct mwl_sta *mwl_dev_get_sta(const struct ieee80211_sta *sta) -+{ -+ return (struct mwl_sta *)&sta->drv_priv; -+} -+ -+struct ieee80211_hw *mwl_alloc_hw(int bus_type, -+ int chip_type, -+ struct device *dev, -+ const struct mwl_hif_ops *ops, -+ size_t hif_data_len); -+ -+void mwl_free_hw(struct ieee80211_hw *hw); -+ -+int mwl_init_hw(struct ieee80211_hw *hw, const char *fw_name, -+ const char *cal_name, const char *txpwrlmt_name); -+ -+void mwl_deinit_hw(struct ieee80211_hw *hw); -+ -+/* Defined in mac80211.c. */ -+extern const struct ieee80211_ops mwl_mac80211_ops; -+ -+#endif /* _CORE_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/debugfs.c b/drivers/net/wireless/marvell/mwlwifi/debugfs.c -new file mode 100644 -index 000000000000..e375806a7111 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/debugfs.c -@@ -0,0 +1,2201 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file implements debug fs related functions. */ -+ -+#include -+#include -+ -+#include "sysadpt.h" -+#include "core.h" -+#include "utils.h" -+#include "thermal.h" -+#include "hif/fwcmd.h" -+#include "hif/hif-ops.h" -+#include "debugfs.h" -+ -+#define MWLWIFI_DEBUGFS_ADD_FILE(name) do { \ -+ if (!debugfs_create_file(#name, 0644, priv->debugfs_phy, \ -+ priv, &mwl_debugfs_##name##_fops)) \ -+ return; \ -+} while (0) -+ -+#define MWLWIFI_DEBUGFS_FILE_OPS(name) \ -+static const struct file_operations mwl_debugfs_##name##_fops = { \ -+ .read = mwl_debugfs_##name##_read, \ -+ .write = mwl_debugfs_##name##_write, \ -+ .open = simple_open, \ -+} -+ -+#define MWLWIFI_DEBUGFS_FILE_READ_OPS(name) \ -+static const struct file_operations mwl_debugfs_##name##_fops = { \ -+ .read = mwl_debugfs_##name##_read, \ -+ .open = simple_open, \ -+} -+ -+#define MWLWIFI_DEBUGFS_FILE_WRITE_OPS(name) \ -+static const struct file_operations mwl_debugfs_##name##_fops = { \ -+ .write = mwl_debugfs_##name##_write, \ -+ .open = simple_open, \ -+} -+ -+static const char chipname[MWLUNKNOWN][8] = { -+ "88W8864", -+ "88W8897", -+ "88W8964", -+ "88W8997" -+}; -+ -+static void dump_data(char *p, int size, int *len, u8 *data, -+ int data_len, char *title) -+{ -+ int cur_byte = 0; -+ int i; -+ -+ *len += scnprintf(p + *len, size - *len, "%s\n", title); -+ -+ for (cur_byte = 0; cur_byte < data_len; cur_byte += 8) { -+ if ((cur_byte + 8) < data_len) { -+ for (i = 0; i < 8; i++) -+ *len += scnprintf(p + *len, size - *len, -+ "0x%02x ", -+ *(data + cur_byte + i)); -+ *len += scnprintf(p + *len, size - *len, "\n"); -+ } else { -+ for (i = 0; i < (data_len - cur_byte); i++) -+ *len += scnprintf(p + *len, size - *len, -+ "0x%02x ", -+ *(data + cur_byte + i)); -+ *len += scnprintf(p + *len, size - *len, "\n"); -+ break; -+ } -+ } -+} -+ -+static void _dump_tx_hist_mu(char *p, int size, int *len, bool *printed, -+ u32 *total, u8 nss, u8 bw, u8 mcs, u8 sgi, -+ struct mwl_sta *sta_info) -+{ -+ char *bw_str[4] = {"ht20", "ht40", "ht80", "ht160"}; -+ char *sgi_str[2] = {"lgi", "sgi"}; -+ struct mwl_tx_hist_data *tx_hist_data; -+ u32 cnt, rateinfo, per0, per1, per2, per3, per4, ratemask; -+ -+ tx_hist_data = &sta_info->tx_hist.mu_rate[nss][bw][sgi][mcs]; -+ cnt = le32_to_cpu(tx_hist_data->cnt); -+ rateinfo = le32_to_cpu(tx_hist_data->rateinfo); -+ if (cnt && (rateinfo > 0)) { -+ *total += cnt; -+ per4 = le32_to_cpu(tx_hist_data->per[4]); -+ per3 = le32_to_cpu(tx_hist_data->per[3]); -+ per2 = le32_to_cpu(tx_hist_data->per[2]); -+ per1 = le32_to_cpu(tx_hist_data->per[1]); -+ per0 = le32_to_cpu(tx_hist_data->per[0]); -+ if (!*printed) { -+ *len += scnprintf(p + *len, size - *len, -+ "%s %26s <%2d %8s%2d%8s%2d%8s%2d%8s%2d\n", -+ "MU_MIMO rate", " PER%", TX_HISTO_PER_THRES[0], -+ ">=", TX_HISTO_PER_THRES[0], -+ ">=", TX_HISTO_PER_THRES[1], -+ ">=", TX_HISTO_PER_THRES[2], -+ ">=", TX_HISTO_PER_THRES[3]); -+ *len += scnprintf(p + *len, size - *len, -+ "TOTAL MPDU tx pkt: %d\n", -+ sta_info->tx_hist.total_tx_cnt[MU_MIMO]); -+ *printed = true; -+ } -+ if ((rateinfo & 0x3) == 0) -+ ratemask = 0xfff; -+ else -+ ratemask = 0xffff; -+ if ((sta_info->tx_hist.cur_rate_info[MU_MIMO] & ratemask) == -+ (rateinfo & ratemask)) -+ /* mark as current rate */ -+ *len += scnprintf(p + *len, size - *len, "*"); -+ else -+ *len += scnprintf(p + *len, size - *len, " "); -+ *len += scnprintf(p + *len, size - *len, -+ "%5s_%3s_%1dSS_MCS%2d: %10u, %9d, %9d, %9d, %9d, %9d\n", -+ bw_str[bw], sgi_str[sgi], (nss + 1), mcs, cnt, per0, -+ per1, per2, per3, per4); -+ } -+} -+ -+static void dump_tx_hist_mu(char *p, int size, int *len, bool *printed, -+ u32 *total, struct mwl_sta *sta_info) -+{ -+ u8 nss, bw, mcs, sgi; -+ -+ for (nss = 0; nss < (QS_NUM_SUPPORTED_11AC_NSS - 1); nss++) { -+ for (bw = 0; bw < QS_NUM_SUPPORTED_11AC_BW; bw++) { -+ for (mcs = 0; mcs < QS_NUM_SUPPORTED_11AC_MCS; mcs++) { -+ for (sgi = 0; sgi < QS_NUM_SUPPORTED_GI; -+ sgi++) { -+ _dump_tx_hist_mu(p, size, len, printed, -+ total, nss, bw, mcs, -+ sgi, sta_info); -+ } -+ } -+ } -+ } -+} -+ -+ -+static void dump_tx_hist_su(char *p, int size, int *len, bool su, bool *printed, -+ u32 *total, struct mwl_sta *sta_info) -+{ -+ int g_rate[14] = {1, 2, 5, 11, 22, 6, 9, 12, 18, 24, 36, 48, 54, 72}; -+ char *bw_str[4] = {"ht20", "ht40", "ht80", "ht160"}; -+ char *sgi_str[2] = {"lgi", "sgi"}; -+ char title_str[32]; -+ struct mwl_tx_hist *tx_hist; -+ struct mwl_tx_hist_data *tx_hist_data; -+ u32 j, loopcnt; -+ u32 cnt, rateinfo, per0, per1, per2, per3, per4, ratemask; -+ u8 format, bw, sgi, mcs, nss; -+ -+ tx_hist = &sta_info->tx_hist; -+ if (su) { -+ loopcnt = MAX_SUPPORTED_RATES; -+ tx_hist_data = &tx_hist->su_rate[0]; -+ } else { -+ loopcnt = TX_RATE_HISTO_CUSTOM_CNT; -+ tx_hist_data = &tx_hist->custom_rate[0]; -+ } -+ -+ for (j = 0; j < loopcnt; j++) { -+ cnt = le32_to_cpu(tx_hist_data[j].cnt); -+ rateinfo = le32_to_cpu(tx_hist_data[j].rateinfo); -+ if (cnt && (rateinfo > 0)) { -+ *total += cnt; -+ per4 = le32_to_cpu(tx_hist_data[j].per[4]); -+ per3 = le32_to_cpu(tx_hist_data[j].per[3]); -+ per2 = le32_to_cpu(tx_hist_data[j].per[2]); -+ per1 = le32_to_cpu(tx_hist_data[j].per[1]); -+ per0 = le32_to_cpu(tx_hist_data[j].per[0]); -+ if (!*printed) { -+ *len += scnprintf(p + *len, size - *len, -+ "%s %26s <%2d %8s%2d%8s%2d%8s%2d%8s%2d\n", -+ su ? "SU_MIMO rate" : " Custom rate", -+ " PER%", TX_HISTO_PER_THRES[0], -+ ">=", TX_HISTO_PER_THRES[0], -+ ">=", TX_HISTO_PER_THRES[1], -+ ">=", TX_HISTO_PER_THRES[2], -+ ">=", TX_HISTO_PER_THRES[3]); -+ *len += scnprintf(p + *len, size - *len, -+ "TOTAL MPDU tx pkt: %d\n", -+ tx_hist->total_tx_cnt[SU_MIMO]); -+ *printed = true; -+ } -+ format = rateinfo & MWL_TX_RATE_FORMAT_MASK; -+ bw = (rateinfo & MWL_TX_RATE_BANDWIDTH_MASK) >> -+ MWL_TX_RATE_BANDWIDTH_SHIFT; -+ sgi = (rateinfo & MWL_TX_RATE_SHORTGI_MASK) >> -+ MWL_TX_RATE_SHORTGI_SHIFT; -+ mcs = (rateinfo & MWL_TX_RATE_RATEIDMCS_MASK) >> -+ MWL_TX_RATE_RATEIDMCS_SHIFT; -+ if (format == TX_RATE_FORMAT_LEGACY) -+ ratemask = 0xfff; -+ else -+ ratemask = 0xffff; -+ if ((tx_hist->cur_rate_info[SU_MIMO] & ratemask) == -+ (rateinfo & ratemask)) -+ /* mark as current rate */ -+ *len += scnprintf(p + *len, size - *len, "*"); -+ else -+ *len += scnprintf(p + *len, size - *len, " "); -+ if (format == TX_RATE_FORMAT_LEGACY) { -+ if (mcs == 2) { -+ *len += scnprintf(p + *len, size - *len, -+ "%s %10u, %9d, %9d, %9d, %9d, %9d\n", -+ "5.5Mbps :", cnt, per0, -+ per1, per2, per3, per4); -+ } else { -+ sprintf(title_str, -+ "%-3dMbps :", -+ g_rate[mcs]); -+ *len += scnprintf(p + *len, size - *len, -+ "%s %10u, %9d, %9d, %9d, %9d, %9d\n", -+ title_str, cnt, per0, per1, per2, per3, -+ per4); -+ } -+ } else if (format == TX_RATE_FORMAT_11N) { -+ sprintf(title_str, "%4s_%3s_MCS%2d :", -+ bw_str[bw], sgi_str[sgi], mcs); -+ *len += scnprintf(p + *len, size - *len, -+ "%s %10u, %9d, %9d, %9d, %9d, %9d\n", -+ title_str, cnt, per0, per1, per2, per3, -+ per4); -+ } else { -+ nss = (mcs >> 4); -+ sprintf(title_str, "%5s_%3s_%1dSS_MCS%2d :", -+ bw_str[bw], sgi_str[sgi], (nss+1), -+ (mcs & 0xf)); -+ *len += scnprintf(p + *len, size - *len, -+ "%s %10u, %9d, %9d, %9d, %9d, %9d\n", -+ title_str, cnt, per0, per1, per2, per3, -+ per4); -+ } -+ } -+ } -+} -+ -+static void dump_tx_hist(char *p, int size, int *len, struct mwl_sta *sta_info) -+{ -+ int type; -+ bool printed, su; -+ u32 total; -+ -+ for (type = 0; type <= SU_MU_TYPE_CNT; type++) { -+ printed = false; -+ total = 0; -+ if (type == MU_MIMO) { -+ dump_tx_hist_mu(p, size, len, &printed, -+ &total, sta_info); -+ } else { -+ su = (type == SU_MIMO) ? true : false; -+ dump_tx_hist_su(p, size, len, su, &printed, -+ &total, sta_info); -+ } -+ if (printed) -+ *len += scnprintf(p + *len, size - *len, -+ " TOTAL : %10u\n\n", -+ total); -+ } -+} -+ -+static void core_dump_file(u8 *valbuf, u32 length, u32 region, u32 address, -+ u32 append, u32 totallen, bool textmode) -+{ -+ struct file *filp_core = NULL; -+ char file_name[40]; -+ u8 *buf = kmalloc(length * 3, GFP_KERNEL); -+ u8 *data_p = buf; -+ u32 i, j = 0; -+ -+ if (!buf) -+ return; -+ -+ memset(file_name, 0, sizeof(file_name)); -+ sprintf(file_name, "/dev/shm/coredump-%x-%x", -+ region, (region + totallen)); -+ -+ if (append) -+ filp_core = filp_open(file_name, O_RDWR | O_APPEND, 0); -+ else -+ filp_core = filp_open(file_name, O_RDWR | O_CREAT | O_TRUNC, 0); -+ -+ if (!IS_ERR(filp_core)) { -+ if (textmode) { -+ for (i = 0; i < length; i += 4) { -+ u32 val = 0; -+ -+ val = le32_to_cpu(*(__le32 *)(&valbuf[i])); -+ -+ if (i % 16 == 0) { -+ sprintf(buf + j, "\n0x%08x", -+ (int)(address + i)); -+ j = strlen(buf); -+ } -+ sprintf(buf + j, " %08x", val); -+ j = strlen(buf); -+ } -+ data_p = buf + j; -+ data_p += sprintf(data_p, "\n"); -+ __kernel_write(filp_core, buf, strlen(buf), -+ &filp_core->f_pos); -+ } else -+ __kernel_write(filp_core, valbuf, length, -+ &filp_core->f_pos); -+ -+ filp_close(filp_core, current->files); -+ } -+ -+ kfree(buf); -+} -+ -+static ssize_t mwl_debugfs_info_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ int tx_num = 4, rx_num = 4; -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += scnprintf(p + len, size - len, -+ "driver name: %s\n", -+ mwl_hif_get_driver_name(priv->hw)); -+ len += scnprintf(p + len, size - len, "chip type: %s\n", -+ chipname[priv->chip_type]); -+ len += scnprintf(p + len, size - len, -+ "hw version: %X\n", priv->hw_data.hw_version); -+ len += scnprintf(p + len, size - len, -+ "driver version: %s\n", -+ mwl_hif_get_driver_version(priv->hw)); -+ len += scnprintf(p + len, size - len, "firmware version: 0x%08x\n", -+ priv->hw_data.fw_release_num); -+ len += scnprintf(p + len, size - len, -+ "power table loaded from dts: %s\n", -+ priv->forbidden_setting ? "no" : "yes"); -+ len += scnprintf(p + len, size - len, "firmware region code: 0x%x\n", -+ priv->fw_region_code); -+ len += scnprintf(p + len, size - len, -+ "mac address: %pM\n", priv->hw_data.mac_addr); -+ len += scnprintf(p + len, size - len, -+ "2g: %s\n", priv->disable_2g ? "disable" : "enable"); -+ len += scnprintf(p + len, size - len, -+ "5g: %s\n", priv->disable_5g ? "disable" : "enable"); -+ if (priv->antenna_tx == ANTENNA_TX_2) -+ tx_num = 2; -+ else if (priv->antenna_tx == ANTENNA_TX_3) -+ tx_num = 3; -+ if (priv->antenna_rx == ANTENNA_RX_2) -+ rx_num = 2; -+ else if (priv->antenna_rx == ANTENNA_RX_3) -+ rx_num = 3; -+ len += scnprintf(p + len, size - len, "antenna: %d %d\n", -+ tx_num, rx_num); -+ len += scnprintf(p + len, size - len, "irq number: %d\n", priv->irq); -+ len += scnprintf(p + len, size - len, "ap macid support: %08x\n", -+ priv->ap_macids_supported); -+ len += scnprintf(p + len, size - len, "sta macid support: %08x\n", -+ priv->sta_macids_supported); -+ len += scnprintf(p + len, size - len, -+ "macid used: %08x\n", priv->macids_used); -+ len += scnprintf(p + len, size - len, -+ "radio: %s\n", priv->radio_on ? "enable" : "disable"); -+ len += mwl_hif_get_info(priv->hw, p + len, size - len); -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_tx_status_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += mwl_hif_get_tx_status(priv->hw, p + len, size - len); -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_rx_status_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += mwl_hif_get_rx_status(priv->hw, p + len, size - len); -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_vif_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ struct mwl_vif *mwl_vif; -+ struct ieee80211_vif *vif; -+ char ssid[IEEE80211_MAX_SSID_LEN + 1]; -+ struct cfg80211_chan_def *chan_def; -+ struct beacon_info *beacon_info; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ spin_lock_bh(&priv->vif_lock); -+ list_for_each_entry(mwl_vif, &priv->vif_list, list) { -+ vif = container_of((void *)mwl_vif, struct ieee80211_vif, -+ drv_priv); -+ len += scnprintf(p + len, size - len, -+ "macid: %d\n", mwl_vif->macid); -+ switch (vif->type) { -+ case NL80211_IFTYPE_AP: -+ len += scnprintf(p + len, size - len, "type: ap\n"); -+ memcpy(ssid, vif->bss_conf.ssid, -+ vif->bss_conf.ssid_len); -+ ssid[vif->bss_conf.ssid_len] = 0; -+ len += scnprintf(p + len, size - len, -+ "ssid: %s\n", ssid); -+ len += scnprintf(p + len, size - len, -+ "mac address: %pM\n", mwl_vif->bssid); -+ break; -+ case NL80211_IFTYPE_MESH_POINT: -+ len += scnprintf(p + len, size - len, "type: mesh\n"); -+ len += scnprintf(p + len, size - len, -+ "mac address: %pM\n", mwl_vif->bssid); -+ break; -+ case NL80211_IFTYPE_STATION: -+ len += scnprintf(p + len, size - len, "type: sta\n"); -+ len += scnprintf(p + len, size - len, -+ "mac address: %pM\n", -+ mwl_vif->sta_mac); -+ break; -+ default: -+ len += scnprintf(p + len, size - len, -+ "type: unknown\n"); -+ break; -+ } -+ if (vif->chanctx_conf) { -+ chan_def = &vif->chanctx_conf->def; -+ len += scnprintf(p + len, size - len, -+ "channel: %d: width: %d\n", -+ chan_def->chan->hw_value, -+ chan_def->width); -+ len += scnprintf(p + len, size - len, -+ "freq: %d freq1: %d freq2: %d\n", -+ chan_def->chan->center_freq, -+ chan_def->center_freq1, -+ chan_def->center_freq2); -+ } -+ len += scnprintf(p + len, size - len, "hw_crypto_enabled: %s\n", -+ mwl_vif->is_hw_crypto_enabled ? -+ "true" : "false"); -+ len += scnprintf(p + len, size - len, -+ "key idx: %d\n", mwl_vif->keyidx); -+ len += scnprintf(p + len, size - len, -+ "IV: %08x%04x\n", mwl_vif->iv32, -+ mwl_vif->iv16); -+ beacon_info = &mwl_vif->beacon_info; -+ dump_data(p, size, &len, beacon_info->ie_wmm_ptr, -+ beacon_info->ie_wmm_len, "WMM:"); -+ dump_data(p, size, &len, beacon_info->ie_rsn_ptr, -+ beacon_info->ie_rsn_len, "RSN:"); -+ dump_data(p, size, &len, beacon_info->ie_rsn48_ptr, -+ beacon_info->ie_rsn48_len, "RSN48:"); -+ dump_data(p, size, &len, beacon_info->ie_mde_ptr, -+ beacon_info->ie_mde_len, "MDE:"); -+ dump_data(p, size, &len, beacon_info->ie_ht_ptr, -+ beacon_info->ie_ht_len, "HT:"); -+ dump_data(p, size, &len, beacon_info->ie_vht_ptr, -+ beacon_info->ie_vht_len, "VHT:"); -+ if (vif->type == NL80211_IFTYPE_MESH_POINT) { -+ dump_data(p, size, &len, beacon_info->ie_meshid_ptr, -+ beacon_info->ie_meshid_len, "MESHID:"); -+ dump_data(p, size, &len, beacon_info->ie_meshcfg_ptr, -+ beacon_info->ie_meshcfg_len, "MESHCFG:"); -+ dump_data(p, size, &len, beacon_info->ie_meshchsw_ptr, -+ beacon_info->ie_meshchsw_len, "MESHCHSW:"); -+ } -+ len += scnprintf(p + len, size - len, "\n"); -+ } -+ spin_unlock_bh(&priv->vif_lock); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_sta_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ struct mwl_sta *sta_info; -+ struct ieee80211_sta *sta; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ spin_lock_bh(&priv->sta_lock); -+ list_for_each_entry(sta_info, &priv->sta_list, list) { -+ sta = container_of((void *)sta_info, struct ieee80211_sta, -+ drv_priv); -+ len += scnprintf(p + len, size - len, -+ "mac address: %pM\n", sta->addr); -+ len += scnprintf(p + len, size - len, "aid: %u\n", sta->aid); -+ len += scnprintf(p + len, size - len, "ampdu: %s\n", -+ sta_info->is_ampdu_allowed ? "true" : "false"); -+ len += scnprintf(p + len, size - len, "amsdu: %s\n", -+ sta_info->is_amsdu_allowed ? "true" : "false"); -+ len += scnprintf(p + len, size - len, "wds: %s\n", -+ sta_info->wds ? "true" : "false"); -+ len += scnprintf(p + len, size - len, "ba_hist: %s\n", -+ sta_info->ba_hist.enable ? -+ "enable" : "disable"); -+ if (sta_info->is_amsdu_allowed) { -+ len += scnprintf(p + len, size - len, -+ "amsdu cap: 0x%02x\n", -+ sta_info->amsdu_ctrl.cap); -+ } -+ if (sta->ht_cap.ht_supported) { -+ len += scnprintf(p + len, size - len, -+ "ht_cap: 0x%04x, ampdu: %02x, %02x\n", -+ sta->ht_cap.cap, -+ sta->ht_cap.ampdu_factor, -+ sta->ht_cap.ampdu_density); -+ len += scnprintf(p + len, size - len, -+ "rx_mask: 0x%02x, %02x, %02x, %02x\n", -+ sta->ht_cap.mcs.rx_mask[0], -+ sta->ht_cap.mcs.rx_mask[1], -+ sta->ht_cap.mcs.rx_mask[2], -+ sta->ht_cap.mcs.rx_mask[3]); -+ } -+ if (sta->vht_cap.vht_supported) { -+ len += scnprintf(p + len, size - len, -+ "vht_cap: 0x%08x, mcs: %02x, %02x\n", -+ sta->vht_cap.cap, -+ sta->vht_cap.vht_mcs.rx_mcs_map, -+ sta->vht_cap.vht_mcs.tx_mcs_map); -+ } -+ len += scnprintf(p + len, size - len, "rx_bw: %d, rx_nss: %d\n", -+ sta->bandwidth, sta->rx_nss); -+ len += scnprintf(p + len, size - len, -+ "tdls: %d, tdls_init: %d\n", -+ sta->tdls, sta->tdls_initiator); -+ len += scnprintf(p + len, size - len, "wme: %d, mfp: %d\n", -+ sta->wme, sta->mfp); -+ len += scnprintf(p + len, size - len, "IV: %08x%04x\n", -+ sta_info->iv32, sta_info->iv16); -+ len += scnprintf(p + len, size - len, "\n"); -+ } -+ spin_unlock_bh(&priv->sta_lock); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_ampdu_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ struct mwl_ampdu_stream *stream; -+ int i; -+ struct mwl_sta *sta_info; -+ struct ieee80211_sta *sta; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ spin_lock_bh(&priv->stream_lock); -+ for (i = 0; i < priv->ampdu_num; i++) { -+ stream = &priv->ampdu[i]; -+ if (!stream->state) -+ continue; -+ len += scnprintf(p + len, size - len, "stream: %d\n", i); -+ len += scnprintf(p + len, size - len, "idx: %u\n", stream->idx); -+ len += scnprintf(p + len, size - len, -+ "state: %u\n", stream->state); -+ if (stream->sta) { -+ len += scnprintf(p + len, size - len, -+ "mac address: %pM\n", -+ stream->sta->addr); -+ len += scnprintf(p + len, size - len, -+ "tid: %u\n", stream->tid); -+ } -+ } -+ spin_unlock_bh(&priv->stream_lock); -+ spin_lock_bh(&priv->sta_lock); -+ list_for_each_entry(sta_info, &priv->sta_list, list) { -+ for (i = 0; i < MWL_MAX_TID; i++) { -+ if (sta_info->check_ba_failed[i]) { -+ sta = container_of((void *)sta_info, -+ struct ieee80211_sta, -+ drv_priv); -+ len += scnprintf(p + len, size - len, -+ "%pM(%d): %d\n", -+ sta->addr, i, -+ sta_info->check_ba_failed[i]); -+ } -+ } -+ } -+ spin_unlock_bh(&priv->sta_lock); -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_stnid_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ struct mwl_stnid *stnid; -+ int i; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ spin_lock_bh(&priv->stnid_lock); -+ for (i = 0; i < priv->stnid_num; i++) { -+ stnid = &priv->stnid[i]; -+ if (!stnid->aid) -+ continue; -+ len += scnprintf(p + len, size - len, -+ "stnid: %d macid: %d aid: %d\n", -+ i + 1, stnid->macid, stnid->aid); -+ } -+ spin_unlock_bh(&priv->stnid_lock); -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_device_pwrtbl_read(struct file *file, -+ char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ int i, j; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += scnprintf(p + len, size - len, -+ "power table loaded from dts: %s\n", -+ priv->forbidden_setting ? "no" : "yes"); -+ len += scnprintf(p + len, size - len, "firmware region code: 0x%x\n", -+ priv->fw_region_code); -+ len += scnprintf(p + len, size - len, "number of channel: %d\n", -+ priv->number_of_channels); -+ for (i = 0; i < priv->number_of_channels; i++) { -+ len += scnprintf(p + len, size - len, "%3d ", -+ priv->device_pwr_tbl[i].channel); -+ for (j = 0; j < SYSADPT_TX_POWER_LEVEL_TOTAL; j++) -+ len += scnprintf(p + len, size - len, "%3d ", -+ priv->device_pwr_tbl[i].tx_pwr[j]); -+ len += scnprintf(p + len, size - len, "%3d ", -+ priv->device_pwr_tbl[i].dfs_capable); -+ len += scnprintf(p + len, size - len, "%3d ", -+ priv->device_pwr_tbl[i].ax_ant); -+ len += scnprintf(p + len, size - len, "%3d\n", -+ priv->device_pwr_tbl[i].cdd); -+ } -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_txpwrlmt_read(struct file *file, -+ char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ -+ return simple_read_from_buffer(ubuf, count, ppos, -+ priv->txpwrlmt_data.buf, -+ priv->txpwrlmt_data.len); -+} -+ -+static ssize_t mwl_debugfs_tx_amsdu_read(struct file *file, -+ char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += scnprintf(p + len, size - len, "tx amsdu: %s\n", -+ priv->tx_amsdu ? "enable" : "disable"); -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_tx_amsdu_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ int value; -+ ssize_t ret; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ if (kstrtoint(buf, 0, &value)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ priv->tx_amsdu = value ? true : false; -+ -+ ret = count; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_dump_hostcmd_read(struct file *file, -+ char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += scnprintf(p + len, size - len, "dump_hostcmd: %s\n", -+ priv->dump_hostcmd ? "enable" : "disable"); -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_dump_hostcmd_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ int value; -+ ssize_t ret; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ if (kstrtoint(buf, 0, &value)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ priv->dump_hostcmd = value ? true : false; -+ -+ ret = count; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_dump_probe_read(struct file *file, -+ char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += scnprintf(p + len, size - len, "dump_probe: %s\n", -+ priv->dump_probe ? "enable" : "disable"); -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_dump_probe_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ int value; -+ ssize_t ret; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ if (kstrtoint(buf, 0, &value)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ priv->dump_probe = value ? true : false; -+ -+ ret = count; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_heartbeat_read(struct file *file, -+ char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += scnprintf(p + len, size - len, "heartbeat: %d\n", -+ priv->heartbeat); -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_heartbeat_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ ssize_t ret; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ if (kstrtoint(buf, 0, &priv->heartbeat)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ priv->pre_jiffies = jiffies; -+ -+ ret = count; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_dfs_test_read(struct file *file, -+ char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += scnprintf(p + len, size - len, "dfs_test: %s\n", -+ priv->dfs_test ? "enable" : "disable"); -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_dfs_test_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ int value; -+ ssize_t ret; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ if (kstrtoint(buf, 0, &value)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ priv->dfs_test = value ? true : false; -+ -+ ret = count; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_dfs_channel_read(struct file *file, -+ char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ struct ieee80211_supported_band *sband; -+ struct ieee80211_channel *channel; -+ int i; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ sband = priv->hw->wiphy->bands[NL80211_BAND_5GHZ]; -+ if (!sband) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ for (i = 0; i < sband->n_channels; i++) { -+ channel = &sband->channels[i]; -+ if (channel->flags & IEEE80211_CHAN_RADAR) { -+ len += scnprintf(p + len, size - len, -+ "%d(%d): flags: %08x dfs_state: %d\n", -+ channel->hw_value, -+ channel->center_freq, -+ channel->flags, channel->dfs_state); -+ len += scnprintf(p + len, size - len, -+ "cac timer: %d ms\n", -+ channel->dfs_cac_ms); -+ } -+ } -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ -+err: -+ free_page(page); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_dfs_channel_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ struct ieee80211_supported_band *sband; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ int dfs_state = 0; -+ int cac_time = -1; -+ struct ieee80211_channel *channel; -+ int i; -+ ssize_t ret; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ sband = priv->hw->wiphy->bands[NL80211_BAND_5GHZ]; -+ if (!sband) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ ret = sscanf(buf, "%d %d", &dfs_state, &cac_time); -+ -+ if ((ret < 1) || (ret > 2)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ for (i = 0; i < sband->n_channels; i++) { -+ channel = &sband->channels[i]; -+ if (channel->flags & IEEE80211_CHAN_RADAR) { -+ channel->dfs_state = dfs_state; -+ if (cac_time != -1) -+ channel->dfs_cac_ms = cac_time * 1000; -+ } -+ } -+ ret = count; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_dfs_radar_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += scnprintf(p + len, size - len, -+ "csa_active: %d\n", priv->csa_active); -+ len += scnprintf(p + len, size - len, -+ "dfs_region: %d\n", priv->dfs_region); -+ len += scnprintf(p + len, size - len, -+ "chirp_count_min: %d\n", priv->dfs_chirp_count_min); -+ len += scnprintf(p + len, size - len, "chirp_time_interval: %d\n", -+ priv->dfs_chirp_time_interval); -+ len += scnprintf(p + len, size - len, -+ "pw_filter: %d\n", priv->dfs_pw_filter); -+ len += scnprintf(p + len, size - len, -+ "min_num_radar: %d\n", priv->dfs_min_num_radar); -+ len += scnprintf(p + len, size - len, -+ "min_pri_count: %d\n", priv->dfs_min_pri_count); -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_dfs_radar_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ -+ wiphy_info(priv->hw->wiphy, "simulate radar detected\n"); -+ ieee80211_radar_detected(priv->hw); -+ -+ return count; -+} -+ -+static ssize_t mwl_debugfs_thermal_read(struct file *file, -+ char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ mwl_fwcmd_get_temp(priv->hw, &priv->temperature); -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += scnprintf(p + len, size - len, "quiet period: %d\n", -+ priv->quiet_period); -+ len += scnprintf(p + len, size - len, "throttle state: %d\n", -+ priv->throttle_state); -+ len += scnprintf(p + len, size - len, "temperature: %d\n", -+ priv->temperature); -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_thermal_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ int throttle_state; -+ ssize_t ret; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ if (kstrtoint(buf, 0, &throttle_state)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ if (throttle_state > SYSADPT_THERMAL_THROTTLE_MAX) { -+ wiphy_warn(priv->hw->wiphy, -+ "throttle state %d is exceeding the limit %d\n", -+ throttle_state, SYSADPT_THERMAL_THROTTLE_MAX); -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ priv->throttle_state = throttle_state; -+ mwl_thermal_set_throttling(priv); -+ ret = count; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_led_ctrl_read(struct file *file, -+ char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += scnprintf(p + len, size - len, "led blink %s\n", -+ priv->led_blink_enable ? "enable" : "disable"); -+ len += scnprintf(p + len, size - len, "led blink rate: %d\n", -+ priv->led_blink_rate); -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_led_ctrl_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ int enable, rate; -+ ssize_t ret; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ ret = sscanf(buf, "%x %x", &enable, &rate); -+ -+ if ((ret != 1) && (ret != 2)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ if (enable && (ret != 2)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ ret = mwl_fwcmd_led_ctrl(priv->hw, enable, rate); -+ -+ if (ret) -+ goto err; -+ -+ priv->led_blink_enable = enable; -+ if (enable) -+ priv->led_blink_rate = rate; -+ else -+ priv->led_blink_rate = 0; -+ -+ ret = count; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_regrdwr_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ ssize_t ret; -+ -+ if (*ppos) -+ return len; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ if (!priv->reg_type) { -+ /* No command has been given */ -+ len += scnprintf(p + len, size - len, "0"); -+ ret = -EINVAL; -+ goto none; -+ } -+ -+ /* Set command has been given */ -+ if (priv->reg_value != UINT_MAX) { -+ ret = mwl_hif_reg_access(priv->hw, true); -+ goto done; -+ } -+ /* Get command has been given */ -+ ret = mwl_hif_reg_access(priv->hw, false); -+ -+done: -+ if (!ret) -+ len += scnprintf(p + len, size - len, "%u 0x%08x 0x%08x\n", -+ priv->reg_type, priv->reg_offset, -+ priv->reg_value); -+ else -+ len += scnprintf(p + len, size - len, -+ "error: %d(%u 0x%08x 0x%08x)\n", -+ ret, priv->reg_type, priv->reg_offset, -+ priv->reg_value); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ -+none: -+ free_page(page); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_regrdwr_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ ssize_t ret; -+ u32 reg_type = 0, reg_offset = 0, reg_value = UINT_MAX; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ ret = sscanf(buf, "%u %x %x", ®_type, ®_offset, ®_value); -+ -+ if (!reg_type) { -+ ret = -EINVAL; -+ goto err; -+ } else { -+ priv->reg_type = reg_type; -+ priv->reg_offset = reg_offset; -+ priv->reg_value = reg_value; -+ ret = count; -+ } -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_ratetable_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ struct mwl_sta *sta_info; -+ struct ieee80211_sta *sta; -+ u8 addr[ETH_ALEN]; -+ int table_size = (sizeof(__le32) * 2 * SYSADPT_MAX_RATE_ADAPT_RATES); -+ u8 *rate_table, *rate_idx; -+ u32 rate_info; -+ u8 fmt, stbc, bw, sgi, mcs, preamble_gf, power_id, ldpc, bf, ant; -+ int idx, rate, nss; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ if (!priv->ra_aid) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ spin_lock_bh(&priv->sta_lock); -+ list_for_each_entry(sta_info, &priv->sta_list, list) { -+ sta = container_of((void *)sta_info, struct ieee80211_sta, -+ drv_priv); -+ if (priv->ra_aid == sta->aid) { -+ ether_addr_copy(addr, sta->addr); -+ break; -+ } -+ } -+ spin_unlock_bh(&priv->sta_lock); -+ -+ rate_table = kzalloc(size, GFP_KERNEL); -+ if (!rate_table) { -+ ret = -ENOMEM; -+ goto err; -+ } -+ -+ ret = mwl_fwcmd_get_ratetable(priv->hw, addr, rate_table, -+ table_size, 0); -+ if (ret) { -+ kfree(rate_table); -+ goto err; -+ } -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += scnprintf(p + len, size - len, -+ "%3s %6s %5s %5s %5s %5s %5s %4s %2s %5s %4s %5s %5s\n", -+ "Num", "Fmt", "STBC", "BW", "SGI", "Nss", "RateId", -+ "GF/Pre", "PId", "LDPC", "BF", "TxAnt", "Rate"); -+ idx = 0; -+ rate_idx = rate_table; -+ rate_info = le32_to_cpu(*(__le32 *)rate_idx); -+ while (rate_info) { -+ fmt = rate_info & MWL_TX_RATE_FORMAT_MASK; -+ stbc = (rate_info & MWL_TX_RATE_STBC_MASK) >> -+ MWL_TX_RATE_STBC_SHIFT; -+ bw = (rate_info & MWL_TX_RATE_BANDWIDTH_MASK) >> -+ MWL_TX_RATE_BANDWIDTH_SHIFT; -+ sgi = (rate_info & MWL_TX_RATE_SHORTGI_MASK) >> -+ MWL_TX_RATE_SHORTGI_SHIFT; -+ mcs = (rate_info & MWL_TX_RATE_RATEIDMCS_MASK) >> -+ MWL_TX_RATE_RATEIDMCS_SHIFT; -+ preamble_gf = (rate_info & MWL_TX_RATE_PREAMBLE_MASK) >> -+ MWL_TX_RATE_PREAMBLE_SHIFT; -+ power_id = (rate_info & MWL_TX_RATE_POWERID_MASK) >> -+ MWL_TX_RATE_POWERID_SHIFT; -+ ldpc = (rate_info & MWL_TX_RATE_ADVCODING_MASK) >> -+ MWL_TX_RATE_ADVCODING_SHIFT; -+ bf = (rate_info & MWL_TX_RATE_BF_MASK) >> -+ MWL_TX_RATE_BF_SHIFT; -+ ant = (rate_info & MWL_TX_RATE_ANTSELECT_MASK) >> -+ MWL_TX_RATE_ANTSELECT_SHIFT; -+ -+ if (fmt == TX_RATE_FORMAT_11AC) { -+ rate = mcs & 0xf; /* 11ac, mcs[3:0]: rate */ -+ nss = mcs >> 4; /* 11ac, mcs[6:4] = nss code */ -+ nss++; /* ddd 1 to correct Nss representation */ -+ } else { -+ rate = mcs; -+ nss = 0; -+ if (fmt == TX_RATE_FORMAT_11N) { -+ if ((mcs >= 0) && (mcs < 8)) -+ nss = 1; -+ else if ((mcs >= 8) && (mcs < 16)) -+ nss = 2; -+ else if ((mcs >= 16) && (mcs < 24)) -+ nss = 3; -+ } -+ } -+ -+ len += scnprintf(p + len, size - len, -+ "%3d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d %5d\n", -+ idx, (int)fmt, (int)stbc, (int)bw, (int)sgi, nss, rate, -+ (int)preamble_gf, (int)power_id, (int)ldpc, (int)bf, -+ (int)ant, -+ utils_get_phy_rate(fmt, bw, sgi, mcs)); -+ -+ idx++; -+ rate_idx += (2 * sizeof(__le32)); -+ rate_info = le32_to_cpu(*(__le32 *)rate_idx); -+ } -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ kfree(rate_table); -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ -+err: -+ free_page(page); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_ratetable_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ int sta_aid; -+ ssize_t ret; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ if (kstrtoint(buf, 0, &sta_aid)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ if ((sta_aid <= 0) || (sta_aid > SYSADPT_MAX_STA_SC4)) { -+ wiphy_warn(priv->hw->wiphy, -+ "station aid is exceeding the limit %d\n", sta_aid); -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ priv->ra_aid = sta_aid; -+ ret = count; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_tx_hist_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ struct ieee80211_sta *sta; -+ struct mwl_sta *sta_info; -+ ssize_t ret; -+ -+ if (priv->chip_type != MWL8964) -+ return -EPERM; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += scnprintf(p + len, size - len, -+ "SU: <4:%d >=4:%d >=15:%d >=50:%d >=100:%d >=250:%d\n", -+ priv->ra_tx_attempt[SU_MIMO][0], -+ priv->ra_tx_attempt[SU_MIMO][1], -+ priv->ra_tx_attempt[SU_MIMO][2], -+ priv->ra_tx_attempt[SU_MIMO][3], -+ priv->ra_tx_attempt[SU_MIMO][4], -+ priv->ra_tx_attempt[SU_MIMO][5]); -+ len += scnprintf(p + len, size - len, -+ "MU: <4:%d >=4:%d >=15:%d >=50:%d >=100:%d >=250:%d\n", -+ priv->ra_tx_attempt[MU_MIMO][0], -+ priv->ra_tx_attempt[MU_MIMO][1], -+ priv->ra_tx_attempt[MU_MIMO][2], -+ priv->ra_tx_attempt[MU_MIMO][3], -+ priv->ra_tx_attempt[MU_MIMO][4], -+ priv->ra_tx_attempt[MU_MIMO][5]); -+ spin_lock_bh(&priv->sta_lock); -+ list_for_each_entry(sta_info, &priv->sta_list, list) { -+ sta = container_of((void *)sta_info, struct ieee80211_sta, -+ drv_priv); -+ len += scnprintf(p + len, size - len, "\nSTA %pM\n", sta->addr); -+ len += scnprintf(p + len, size - len, -+ "============================\n"); -+ dump_tx_hist(p, size, &len, sta_info); -+ len += scnprintf(p + len, size - len, -+ "============================\n"); -+ } -+ spin_unlock_bh(&priv->sta_lock); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_tx_hist_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ int reset; -+ struct mwl_sta *sta_info; -+ ssize_t ret; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ if (kstrtoint(buf, 0, &reset)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ if (!reset) { -+ memset(&priv->ra_tx_attempt, 0, 2 * 6 * sizeof(u32)); -+ spin_lock_bh(&priv->sta_lock); -+ list_for_each_entry(sta_info, &priv->sta_list, list) { -+ memset(&sta_info->tx_hist, 0, -+ sizeof(sta_info->tx_hist)); -+ } -+ spin_unlock_bh(&priv->sta_lock); -+ } -+ -+ ret = count; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_ba_hist_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ struct mwl_sta *sta_info; -+ struct mwl_tx_ba_stats *ba_stats; -+ u32 i, data; -+ u32 baholecnt, baexpcnt, bmap0cnt, nobacnt; -+ u8 bmap0flag, nobaflag; -+ char buff[500], file_location[20]; -+ struct file *filp_bahisto; -+ u8 *data_p = buff; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ if (!priv->ba_aid) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ memset(buff, 0, sizeof(buff)); -+ memset(file_location, 0, sizeof(file_location)); -+ sprintf(file_location, "/tmp/ba_histo-%d", priv->ba_aid); -+ -+ filp_bahisto = filp_open(file_location, -+ O_RDWR | O_CREAT | O_TRUNC, 0); -+ -+ if (IS_ERR(filp_bahisto)) { -+ ret = -EIO; -+ goto err; -+ } -+ -+ sta_info = utils_find_sta_by_aid(priv, priv->ba_aid); -+ if (sta_info && sta_info->ba_hist.enable && -+ sta_info->ba_hist.ba_stats) { -+ ba_stats = sta_info->ba_hist.ba_stats; -+ len += scnprintf(p + len, size - len, -+ "BA histogram aid: %d, stnid: %d type: %s\n", -+ priv->ba_aid, sta_info->stnid, -+ sta_info->ba_hist.type ? "MU" : "SU"); -+ data_p += sprintf(data_p, -+ "BA histogram aid: %d, stnid: %d type: %s\n", -+ priv->ba_aid, sta_info->stnid, -+ sta_info->ba_hist.type ? "MU" : "SU"); -+ data_p += sprintf(data_p, "%8s,%8s,%8s,%8s\n", -+ "BAhole", "Expect", "Bmap0", "NoBA"); -+ data = *(u32 *)&ba_stats[0]; -+ baholecnt = 0; -+ baexpcnt = 0; -+ bmap0cnt = 0; -+ nobacnt = 0; -+ for (i = 0; i < ACNT_BA_SIZE && data; i++) { -+ data = *(u32 *)&ba_stats[i]; -+ if (data == 0) -+ break; -+ -+ /* If no BA event does not happen, check BA hole and BA -+ * expected to mark BA bitmap all 0 event -+ */ -+ if (!ba_stats[i].no_ba) -+ bmap0flag = (ba_stats[i].ba_hole == -+ ba_stats[i].ba_expected) ? 1 : 0; -+ else -+ bmap0flag = 0; -+ nobaflag = ba_stats[i].no_ba; -+ -+ /* Buffer is full. Write to file and reset buf */ -+ if ((strlen(buff) + 36) >= 500) { -+ __kernel_write(filp_bahisto, buff, strlen(buff), -+ &filp_bahisto->f_pos); -+ mdelay(2); -+ memset(buff, 0, sizeof(buff)); -+ data_p = buff; -+ } -+ -+ data_p += sprintf(data_p, "%8d,%8d,", -+ ba_stats[i].ba_hole, -+ ba_stats[i].ba_expected); -+ -+ baholecnt += ba_stats[i].ba_hole; -+ baexpcnt += ba_stats[i].ba_expected; -+ if (bmap0flag) { -+ data_p += sprintf(data_p, " #,"); -+ bmap0cnt++; -+ } else -+ data_p += sprintf(data_p, "%8d,", bmap0flag); -+ if (nobaflag) { -+ data_p += sprintf(data_p, " *\n"); -+ nobacnt++; -+ } else -+ data_p += sprintf(data_p, "%8d\n", nobaflag); -+ } -+ -+ __kernel_write(filp_bahisto, buff, strlen(buff), -+ &filp_bahisto->f_pos); -+ len += scnprintf(p + len, size - len, -+ "hole: %d, expect: %d, bmap0: %d, noba: %d\n", -+ baholecnt, baexpcnt, bmap0cnt, nobacnt); -+ len += scnprintf(p + len, size - len, -+ "BA histogram data written to %s\n", -+ file_location); -+ } else -+ len += scnprintf(p + len, size - len, -+ "No BA histogram for sta aid: %d\n", -+ priv->ba_aid); -+ -+ filp_close(filp_bahisto, current->files); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ -+err: -+ free_page(page); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_ba_hist_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ int sta_aid; -+ struct mwl_sta *sta_info; -+ int size; -+ ssize_t ret; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ if (kstrtoint(buf, 0, &sta_aid)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ if ((sta_aid <= 0) || (sta_aid > SYSADPT_MAX_STA_SC4)) { -+ wiphy_warn(priv->hw->wiphy, -+ "station aid is exceeding the limit %d\n", sta_aid); -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ if (priv->ba_aid) { -+ sta_info = utils_find_sta_by_aid(priv, priv->ba_aid); -+ if (sta_info) { -+ sta_info->ba_hist.enable = false; -+ kfree(sta_info->ba_hist.ba_stats); -+ } -+ } -+ priv->ba_aid = 0; -+ sta_info = utils_find_sta_by_aid(priv, sta_aid); -+ if (sta_info) { -+ sta_info->ba_hist.enable = true; -+ sta_info->ba_hist.index = 0; -+ size = sizeof(struct mwl_tx_ba_stats) * ACNT_BA_SIZE; -+ sta_info->ba_hist.ba_stats = kmalloc(size, GFP_KERNEL); -+ if (sta_info->ba_hist.ba_stats) { -+ memset(sta_info->ba_hist.ba_stats, 0, size); -+ priv->ba_aid = sta_aid; -+ } -+ ret = count; -+ } else -+ ret = -EINVAL; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_fixed_rate_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ ssize_t ret; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += scnprintf(p + len, size - len, "fixed rate: 0x%08x\n", -+ priv->fixed_rate); -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ free_page(page); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_fixed_rate_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ ssize_t ret; -+ int fixed_rate = 0, fwcmd_ret; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ ret = sscanf(buf, "%08x", &fixed_rate); -+ if (!ret) { -+ ret = -EIO; -+ goto err; -+ } -+ -+ priv->fixed_rate = fixed_rate; -+ -+ if (fixed_rate != 0) -+ fwcmd_ret = mwl_fwcmd_set_rate_drop(priv->hw, 3, -+ priv->fixed_rate, 0); -+ else -+ fwcmd_ret = mwl_fwcmd_set_rate_drop(priv->hw, 1, -+ priv->fixed_rate, 0); -+ if (fwcmd_ret) -+ ret = -EIO; -+ else -+ ret = count; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_core_dump_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long page = get_zeroed_page(GFP_KERNEL); -+ char *p = (char *)page; -+ int len = 0, size = PAGE_SIZE; -+ struct coredump_cmd *core_dump = NULL; -+ struct coredump *cd = NULL; -+ char *buff = NULL; -+ u32 i, offset; -+ u32 address, length; -+ ssize_t ret; -+ -+ if (priv->chip_type != MWL8964) -+ return -EPERM; -+ -+ if (*ppos) -+ return len; -+ -+ if (!p) -+ return -ENOMEM; -+ -+ core_dump = kmalloc(sizeof(*core_dump), GFP_ATOMIC); -+ if (!core_dump) { -+ ret = -ENOMEM; -+ goto err; -+ } -+ -+ buff = kmalloc(MAX_CORE_DUMP_BUFFER, GFP_ATOMIC); -+ if (!buff) { -+ ret = -ENOMEM; -+ goto err; -+ } -+ memset((char *)buff, 0, MAX_CORE_DUMP_BUFFER); -+ -+ cd = kmalloc(sizeof(*cd), GFP_ATOMIC); -+ if (!cd) { -+ ret = -ENOMEM; -+ goto err; -+ } -+ -+ core_dump->context = 0; -+ core_dump->flags = 0; -+ core_dump->size_kb = 0; -+ if (mwl_fwcmd_get_fw_core_dump(priv->hw, core_dump, buff)) { -+ ret = -EIO; -+ goto err; -+ } -+ memcpy(cd, buff, sizeof(*cd)); -+ -+ len += scnprintf(p + len, size - len, "\n"); -+ len += scnprintf(p + len, size - len, "Major Version : %d\n", -+ cd->version_major); -+ len += scnprintf(p + len, size - len, "Minor Version : %d\n", -+ cd->version_minor); -+ len += scnprintf(p + len, size - len, "Patch Version : %d\n", -+ cd->version_patch); -+ len += scnprintf(p + len, size - len, "Num of Regions: %d\n", -+ cd->num_regions); -+ len += scnprintf(p + len, size - len, "Num of Symbols: %d\n", -+ cd->num_symbols); -+ -+ for (i = 0; i < cd->num_regions; i++) { -+ address = le32_to_cpu(cd->region[i].address); -+ length = le32_to_cpu(cd->region[i].length); -+ len += scnprintf(p + len, size - len, -+ "\ncd.region[%d]: address=%x, length=%x\n", -+ i, address, length); -+ -+ for (offset = 0; offset < length; -+ offset += MAX_CORE_DUMP_BUFFER) { -+ core_dump->context = cpu_to_le32((i << 28) | offset); -+ core_dump->flags = 0; -+ core_dump->size_kb = 0; -+ if (mwl_fwcmd_get_fw_core_dump(priv->hw, -+ core_dump, buff)) { -+ wiphy_info(priv->hw->wiphy, -+ "region:%d offset:%x\n", i, offset); -+ break; -+ } -+ core_dump_file(buff, MAX_CORE_DUMP_BUFFER, -+ address, address + offset, -+ offset, length, priv->coredump_text); -+ } -+ } -+ len += scnprintf(p + len, size - len, "\n"); -+ -+ ret = simple_read_from_buffer(ubuf, count, ppos, p, len); -+ -+err: -+ kfree(core_dump); -+ kfree(buff); -+ kfree(cd); -+ free_page(page); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_core_dump_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ int text_mode; -+ ssize_t ret; -+ -+ if (priv->chip_type != MWL8964) -+ return -EPERM; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ if (kstrtoint(buf, 0, &text_mode)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ if ((text_mode < 0) || (text_mode > 1)) { -+ wiphy_warn(priv->hw->wiphy, -+ "text mode should be 0 (false) or 1 (true): %d\n", -+ text_mode); -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ mwl_fwcmd_core_dump_diag_mode(priv->hw, 1); -+ priv->coredump_text = text_mode ? true : false; -+ ret = count; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_mcast_cts_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ int cts_enable = 0; -+ ssize_t ret; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ if (kstrtoint(buf, 0, &cts_enable)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ ret = mwl_hif_mcast_cts(priv->hw, cts_enable ? true : false); -+ if (ret) -+ goto err; -+ -+ ret = count; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+static ssize_t mwl_debugfs_wmmedcaap_write(struct file *file, -+ const char __user *ubuf, -+ size_t count, loff_t *ppos) -+{ -+ struct mwl_priv *priv = (struct mwl_priv *)file->private_data; -+ unsigned long addr = get_zeroed_page(GFP_KERNEL); -+ char *buf = (char *)addr; -+ size_t buf_size = min_t(size_t, count, PAGE_SIZE - 1); -+ u32 index = 0, cw_min = 0, cw_max = 0, aifsn = 0, txop = 0; -+ ssize_t ret; -+ -+ if (!buf) -+ return -ENOMEM; -+ -+ if (copy_from_user(buf, ubuf, buf_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ ret = sscanf(buf, "%u %x %x %u %x", &index, &cw_min, -+ &cw_max, &aifsn, &txop); -+ if (ret != 5) { -+ ret = -EINVAL; -+ goto err; -+ } -+ wiphy_info(priv->hw->wiphy, "set TCQ%d wmm edca with:\n", index); -+ wiphy_info(priv->hw->wiphy, -+ "cw_min=0x%x, cw_max=0x%x, aifs_num=%d, txop=0x%x\n", -+ cw_min, cw_max, aifsn, txop); -+ -+ ret = mwl_fwcmd_set_edca_params(priv->hw, index, -+ cw_min, cw_max, aifsn, txop); -+ if (ret) -+ goto err; -+ -+ ret = count; -+ -+err: -+ free_page(addr); -+ return ret; -+} -+ -+MWLWIFI_DEBUGFS_FILE_READ_OPS(info); -+MWLWIFI_DEBUGFS_FILE_READ_OPS(tx_status); -+MWLWIFI_DEBUGFS_FILE_READ_OPS(rx_status); -+MWLWIFI_DEBUGFS_FILE_READ_OPS(vif); -+MWLWIFI_DEBUGFS_FILE_READ_OPS(sta); -+MWLWIFI_DEBUGFS_FILE_READ_OPS(ampdu); -+MWLWIFI_DEBUGFS_FILE_READ_OPS(stnid); -+MWLWIFI_DEBUGFS_FILE_READ_OPS(device_pwrtbl); -+MWLWIFI_DEBUGFS_FILE_READ_OPS(txpwrlmt); -+MWLWIFI_DEBUGFS_FILE_OPS(tx_amsdu); -+MWLWIFI_DEBUGFS_FILE_OPS(dump_hostcmd); -+MWLWIFI_DEBUGFS_FILE_OPS(dump_probe); -+MWLWIFI_DEBUGFS_FILE_OPS(heartbeat); -+MWLWIFI_DEBUGFS_FILE_OPS(dfs_test); -+MWLWIFI_DEBUGFS_FILE_OPS(dfs_channel); -+MWLWIFI_DEBUGFS_FILE_OPS(dfs_radar); -+MWLWIFI_DEBUGFS_FILE_OPS(thermal); -+MWLWIFI_DEBUGFS_FILE_OPS(led_ctrl); -+MWLWIFI_DEBUGFS_FILE_OPS(regrdwr); -+MWLWIFI_DEBUGFS_FILE_OPS(ratetable); -+MWLWIFI_DEBUGFS_FILE_OPS(tx_hist); -+MWLWIFI_DEBUGFS_FILE_OPS(ba_hist); -+MWLWIFI_DEBUGFS_FILE_OPS(fixed_rate); -+MWLWIFI_DEBUGFS_FILE_OPS(core_dump); -+MWLWIFI_DEBUGFS_FILE_WRITE_OPS(mcast_cts); -+MWLWIFI_DEBUGFS_FILE_WRITE_OPS(wmmedcaap); -+ -+void mwl_debugfs_init(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (!priv->debugfs_phy) -+ priv->debugfs_phy = debugfs_create_dir("mwlwifi", -+ hw->wiphy->debugfsdir); -+ -+ if (!priv->debugfs_phy) -+ return; -+ -+ MWLWIFI_DEBUGFS_ADD_FILE(info); -+ MWLWIFI_DEBUGFS_ADD_FILE(tx_status); -+ MWLWIFI_DEBUGFS_ADD_FILE(rx_status); -+ MWLWIFI_DEBUGFS_ADD_FILE(vif); -+ MWLWIFI_DEBUGFS_ADD_FILE(sta); -+ MWLWIFI_DEBUGFS_ADD_FILE(ampdu); -+ MWLWIFI_DEBUGFS_ADD_FILE(stnid); -+ MWLWIFI_DEBUGFS_ADD_FILE(device_pwrtbl); -+ MWLWIFI_DEBUGFS_ADD_FILE(txpwrlmt); -+ MWLWIFI_DEBUGFS_ADD_FILE(tx_amsdu); -+ MWLWIFI_DEBUGFS_ADD_FILE(dump_hostcmd); -+ MWLWIFI_DEBUGFS_ADD_FILE(dump_probe); -+ MWLWIFI_DEBUGFS_ADD_FILE(heartbeat); -+ MWLWIFI_DEBUGFS_ADD_FILE(dfs_test); -+ MWLWIFI_DEBUGFS_ADD_FILE(dfs_channel); -+ MWLWIFI_DEBUGFS_ADD_FILE(dfs_radar); -+ MWLWIFI_DEBUGFS_ADD_FILE(thermal); -+ MWLWIFI_DEBUGFS_ADD_FILE(led_ctrl); -+ MWLWIFI_DEBUGFS_ADD_FILE(regrdwr); -+ MWLWIFI_DEBUGFS_ADD_FILE(ratetable); -+ MWLWIFI_DEBUGFS_ADD_FILE(tx_hist); -+ MWLWIFI_DEBUGFS_ADD_FILE(ba_hist); -+ MWLWIFI_DEBUGFS_ADD_FILE(fixed_rate); -+ MWLWIFI_DEBUGFS_ADD_FILE(core_dump); -+ MWLWIFI_DEBUGFS_ADD_FILE(mcast_cts); -+ MWLWIFI_DEBUGFS_ADD_FILE(wmmedcaap); -+} -+ -+void mwl_debugfs_remove(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ debugfs_remove(priv->debugfs_phy); -+ priv->debugfs_phy = NULL; -+} -diff --git a/drivers/net/wireless/marvell/mwlwifi/debugfs.h b/drivers/net/wireless/marvell/mwlwifi/debugfs.h -new file mode 100644 -index 000000000000..e7595f563348 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/debugfs.h -@@ -0,0 +1,24 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines debug fs related functions. */ -+ -+#ifndef _MWL_DEBUGFS_H_ -+#define _MWL_DEBUGFS_H_ -+ -+void mwl_debugfs_init(struct ieee80211_hw *hw); -+void mwl_debugfs_remove(struct ieee80211_hw *hw); -+ -+#endif /* _MWL_DEBUGFS_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/fwcmd.c b/drivers/net/wireless/marvell/mwlwifi/hif/fwcmd.c -new file mode 100644 -index 000000000000..ff943d6fe447 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/fwcmd.c -@@ -0,0 +1,3852 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file implements firmware host command related -+ * functions. -+ */ -+ -+#include -+#include -+ -+#include "sysadpt.h" -+#include "core.h" -+#include "utils.h" -+#include "hif/fwcmd.h" -+#include "hif/hif-ops.h" -+ -+#define MAX_WAIT_GET_HW_SPECS_ITERATONS 3 -+ -+struct cmd_header { -+ __le16 command; -+ __le16 len; -+} __packed; -+ -+char *mwl_fwcmd_get_cmd_string(unsigned short cmd) -+{ -+ int max_entries = 0; -+ int curr_cmd = 0; -+ -+ static const struct { -+ u16 cmd; -+ char *cmd_string; -+ } cmds[] = { -+ { HOSTCMD_CMD_GET_HW_SPEC, "GetHwSpecifications" }, -+ { HOSTCMD_CMD_SET_HW_SPEC, "SetHwSepcifications" }, -+ { HOSTCMD_CMD_802_11_GET_STAT, "80211GetStat" }, -+ { HOSTCMD_CMD_BBP_REG_ACCESS, "BBPRegAccess" }, -+ { HOSTCMD_CMD_RF_REG_ACCESS, "RFRegAccess" }, -+ { HOSTCMD_CMD_802_11_RADIO_CONTROL, "80211RadioControl" }, -+ { HOSTCMD_CMD_MEM_ADDR_ACCESS, "MEMAddrAccess" }, -+ { HOSTCMD_CMD_802_11_TX_POWER, "80211TxPower" }, -+ { HOSTCMD_CMD_802_11_RF_ANTENNA, "80211RfAntenna" }, -+ { HOSTCMD_CMD_BROADCAST_SSID_ENABLE, "BroadcastSsidEnable" }, -+ { HOSTCMD_CMD_SET_CFG, "SetCfg" }, -+ { HOSTCMD_CMD_SET_RF_CHANNEL, "SetRfChannel" }, -+ { HOSTCMD_CMD_SET_AID, "SetAid" }, -+ { HOSTCMD_CMD_SET_INFRA_MODE, "SetInfraMode" }, -+ { HOSTCMD_CMD_802_11_RTS_THSD, "80211RtsThreshold" }, -+ { HOSTCMD_CMD_SET_EDCA_PARAMS, "SetEDCAParams" }, -+ { HOSTCMD_CMD_802_11H_DETECT_RADAR, "80211hDetectRadar" }, -+ { HOSTCMD_CMD_SET_WMM_MODE, "SetWMMMode" }, -+ { HOSTCMD_CMD_HT_GUARD_INTERVAL, "HtGuardInterval" }, -+ { HOSTCMD_CMD_SET_FIXED_RATE, "SetFixedRate" }, -+ { HOSTCMD_CMD_SET_IES, "SetInformationElements" }, -+ { HOSTCMD_CMD_SET_LINKADAPT_CS_MODE, "LinkAdaptCsMode" }, -+ { HOSTCMD_CMD_DUMP_OTP_DATA, "DumpOtpData" }, -+ { HOSTCMD_CMD_SET_MAC_ADDR, "SetMacAddr" }, -+ { HOSTCMD_CMD_SET_RATE_ADAPT_MODE, "SetRateAdaptationMode" }, -+ { HOSTCMD_CMD_GET_WATCHDOG_BITMAP, "GetWatchdogBitMap" }, -+ { HOSTCMD_CMD_DEL_MAC_ADDR, "DelMacAddr" }, -+ { HOSTCMD_CMD_BSS_START, "BssStart" }, -+ { HOSTCMD_CMD_AP_BEACON, "SetApBeacon" }, -+ { HOSTCMD_CMD_SET_NEW_STN, "SetNewStation" }, -+ { HOSTCMD_CMD_SET_APMODE, "SetApMode" }, -+ { HOSTCMD_CMD_SET_SWITCH_CHANNEL, "SetSwitchChannel" }, -+ { HOSTCMD_CMD_UPDATE_ENCRYPTION, "UpdateEncryption" }, -+ { HOSTCMD_CMD_BASTREAM, "BAStream" }, -+ { HOSTCMD_CMD_SET_SPECTRUM_MGMT, "SetSpectrumMgmt" }, -+ { HOSTCMD_CMD_SET_POWER_CONSTRAINT, "SetPowerConstraint" }, -+ { HOSTCMD_CMD_SET_COUNTRY_CODE, "SetCountryCode" }, -+ { HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL, "SetOptimizationLevel" }, -+ { HOSTCMD_CMD_SET_WSC_IE, "SetWscIE" }, -+ { HOSTCMD_CMD_GET_RATETABLE, "GetRateTable" }, -+ { HOSTCMD_CMD_GET_SEQNO, "GetSeqno" }, -+ { HOSTCMD_CMD_DWDS_ENABLE, "DwdsEnable" }, -+ { HOSTCMD_CMD_FW_FLUSH_TIMER, "FwFlushTimer" }, -+ { HOSTCMD_CMD_SET_CDD, "SetCDD" }, -+ { HOSTCMD_CMD_SET_BFTYPE, "SetBFType" }, -+ { HOSTCMD_CMD_CAU_REG_ACCESS, "CAURegAccess" }, -+ { HOSTCMD_CMD_GET_TEMP, "GetTemp" }, -+ { HOSTCMD_CMD_LED_CTRL, "LedCtrl" }, -+ { HOSTCMD_CMD_GET_FW_REGION_CODE, "GetFwRegionCode" }, -+ { HOSTCMD_CMD_GET_DEVICE_PWR_TBL, "GetDevicePwrTbl" }, -+ { HOSTCMD_CMD_SET_RATE_DROP, "SetRateDrop" }, -+ { HOSTCMD_CMD_NEWDP_DMATHREAD_START, "NewdpDMAThreadStart" }, -+ { HOSTCMD_CMD_GET_FW_REGION_CODE_SC4, "GetFwRegionCodeSC4" }, -+ { HOSTCMD_CMD_GET_DEVICE_PWR_TBL_SC4, "GetDevicePwrTblSC4" }, -+ { HOSTCMD_CMD_QUIET_MODE, "QuietMode" }, -+ { HOSTCMD_CMD_CORE_DUMP_DIAG_MODE, "CoreDumpDiagMode" }, -+ { HOSTCMD_CMD_802_11_SLOT_TIME, "80211SlotTime" }, -+ { HOSTCMD_CMD_GET_FW_CORE_DUMP, "GetFwCoreDump" }, -+ { HOSTCMD_CMD_EDMAC_CTRL, "EDMACCtrl" }, -+ { HOSTCMD_CMD_TXPWRLMT_CFG, "TxpwrlmtCfg" }, -+ { HOSTCMD_CMD_MCAST_CTS, "McastCts" }, -+ }; -+ -+ max_entries = ARRAY_SIZE(cmds); -+ -+ for (curr_cmd = 0; curr_cmd < max_entries; curr_cmd++) -+ if ((cmd & 0x7fff) == cmds[curr_cmd].cmd) -+ return cmds[curr_cmd].cmd_string; -+ -+ return "unknown"; -+} -+ -+static int mwl_fwcmd_802_11_radio_control(struct mwl_priv *priv, -+ bool enable, bool force) -+{ -+ struct hostcmd_cmd_802_11_radio_control *pcmd; -+ -+ if (enable == priv->radio_on && !force) -+ return 0; -+ -+ pcmd = (struct hostcmd_cmd_802_11_radio_control *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_RADIO_CONTROL); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->action = cpu_to_le16(WL_SET); -+ pcmd->control = cpu_to_le16(priv->radio_short_preamble ? -+ WL_AUTO_PREAMBLE : WL_LONG_PREAMBLE); -+ pcmd->radio_on = cpu_to_le16(enable ? WL_ENABLE : WL_DISABLE); -+ -+ if (mwl_hif_exec_cmd(priv->hw, HOSTCMD_CMD_802_11_RADIO_CONTROL)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ priv->radio_on = enable; -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+static int mwl_fwcmd_get_tx_powers(struct mwl_priv *priv, u16 *powlist, -+ u8 action, u16 ch, u16 band, -+ u16 width, u16 sub_ch) -+{ -+ struct hostcmd_cmd_802_11_tx_power *pcmd; -+ int i; -+ -+ pcmd = (struct hostcmd_cmd_802_11_tx_power *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ if (priv->chip_type == MWL8997) { -+ memset(pcmd, 0x00, -+ sizeof(struct hostcmd_cmd_802_11_tx_power_kf2)); -+ pcmd->cmd_hdr.len = cpu_to_le16( -+ sizeof(struct hostcmd_cmd_802_11_tx_power_kf2)); -+ } else { -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ } -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_TX_POWER); -+ pcmd->action = cpu_to_le16(action); -+ pcmd->ch = cpu_to_le16(ch); -+ pcmd->bw = cpu_to_le16(width); -+ pcmd->band = cpu_to_le16(band); -+ pcmd->sub_ch = cpu_to_le16(sub_ch); -+ -+ if (mwl_hif_exec_cmd(priv->hw, HOSTCMD_CMD_802_11_TX_POWER)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ for (i = 0; i < priv->pwr_level; i++) -+ powlist[i] = le16_to_cpu(pcmd->power_level_list[i]); -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+static int mwl_fwcmd_set_tx_powers(struct mwl_priv *priv, u16 txpow[], -+ u8 action, u16 ch, u16 band, -+ u16 width, u16 sub_ch) -+{ -+ struct hostcmd_cmd_802_11_tx_power *pcmd; -+ int i; -+ -+ pcmd = (struct hostcmd_cmd_802_11_tx_power *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ if (priv->chip_type == MWL8997) { -+ memset(pcmd, 0x00, -+ sizeof(struct hostcmd_cmd_802_11_tx_power_kf2)); -+ pcmd->cmd_hdr.len = cpu_to_le16( -+ sizeof(struct hostcmd_cmd_802_11_tx_power_kf2)); -+ } else { -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ } -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_TX_POWER); -+ pcmd->action = cpu_to_le16(action); -+ pcmd->ch = cpu_to_le16(ch); -+ pcmd->bw = cpu_to_le16(width); -+ pcmd->band = cpu_to_le16(band); -+ pcmd->sub_ch = cpu_to_le16(sub_ch); -+ -+ for (i = 0; i < priv->pwr_level; i++) -+ pcmd->power_level_list[i] = cpu_to_le16(txpow[i]); -+ -+ if (mwl_hif_exec_cmd(priv->hw, HOSTCMD_CMD_802_11_TX_POWER)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+static u8 mwl_fwcmd_get_80m_pri_chnl(u8 channel) -+{ -+ u8 act_primary = ACT_PRIMARY_CHAN_0; -+ -+ switch (channel) { -+ case 36: -+ act_primary = ACT_PRIMARY_CHAN_0; -+ break; -+ case 40: -+ act_primary = ACT_PRIMARY_CHAN_1; -+ break; -+ case 44: -+ act_primary = ACT_PRIMARY_CHAN_2; -+ break; -+ case 48: -+ act_primary = ACT_PRIMARY_CHAN_3; -+ break; -+ case 52: -+ act_primary = ACT_PRIMARY_CHAN_0; -+ break; -+ case 56: -+ act_primary = ACT_PRIMARY_CHAN_1; -+ break; -+ case 60: -+ act_primary = ACT_PRIMARY_CHAN_2; -+ break; -+ case 64: -+ act_primary = ACT_PRIMARY_CHAN_3; -+ break; -+ case 100: -+ act_primary = ACT_PRIMARY_CHAN_0; -+ break; -+ case 104: -+ act_primary = ACT_PRIMARY_CHAN_1; -+ break; -+ case 108: -+ act_primary = ACT_PRIMARY_CHAN_2; -+ break; -+ case 112: -+ act_primary = ACT_PRIMARY_CHAN_3; -+ break; -+ case 116: -+ act_primary = ACT_PRIMARY_CHAN_0; -+ break; -+ case 120: -+ act_primary = ACT_PRIMARY_CHAN_1; -+ break; -+ case 124: -+ act_primary = ACT_PRIMARY_CHAN_2; -+ break; -+ case 128: -+ act_primary = ACT_PRIMARY_CHAN_3; -+ break; -+ case 132: -+ act_primary = ACT_PRIMARY_CHAN_0; -+ break; -+ case 136: -+ act_primary = ACT_PRIMARY_CHAN_1; -+ break; -+ case 140: -+ act_primary = ACT_PRIMARY_CHAN_2; -+ break; -+ case 144: -+ act_primary = ACT_PRIMARY_CHAN_3; -+ break; -+ case 149: -+ act_primary = ACT_PRIMARY_CHAN_0; -+ break; -+ case 153: -+ act_primary = ACT_PRIMARY_CHAN_1; -+ break; -+ case 157: -+ act_primary = ACT_PRIMARY_CHAN_2; -+ break; -+ case 161: -+ act_primary = ACT_PRIMARY_CHAN_3; -+ break; -+ } -+ -+ return act_primary; -+} -+ -+static u8 mwl_fwcmd_get_160m_pri_chnl(u8 channel) -+{ -+ u8 act_primary = ACT_PRIMARY_CHAN_0; -+ -+ switch (channel) { -+ case 36: -+ act_primary = ACT_PRIMARY_CHAN_0; -+ break; -+ case 40: -+ act_primary = ACT_PRIMARY_CHAN_1; -+ break; -+ case 44: -+ act_primary = ACT_PRIMARY_CHAN_2; -+ break; -+ case 48: -+ act_primary = ACT_PRIMARY_CHAN_3; -+ break; -+ case 52: -+ act_primary = ACT_PRIMARY_CHAN_4; -+ break; -+ case 56: -+ act_primary = ACT_PRIMARY_CHAN_5; -+ break; -+ case 60: -+ act_primary = ACT_PRIMARY_CHAN_6; -+ break; -+ case 64: -+ act_primary = ACT_PRIMARY_CHAN_7; -+ break; -+ case 100: -+ act_primary = ACT_PRIMARY_CHAN_0; -+ break; -+ case 104: -+ act_primary = ACT_PRIMARY_CHAN_1; -+ break; -+ case 108: -+ act_primary = ACT_PRIMARY_CHAN_2; -+ break; -+ case 112: -+ act_primary = ACT_PRIMARY_CHAN_3; -+ break; -+ case 116: -+ act_primary = ACT_PRIMARY_CHAN_4; -+ break; -+ case 120: -+ act_primary = ACT_PRIMARY_CHAN_5; -+ break; -+ case 124: -+ act_primary = ACT_PRIMARY_CHAN_6; -+ break; -+ case 128: -+ act_primary = ACT_PRIMARY_CHAN_7; -+ break; -+ case 149: -+ act_primary = ACT_PRIMARY_CHAN_0; -+ break; -+ case 153: -+ act_primary = ACT_PRIMARY_CHAN_1; -+ break; -+ case 157: -+ act_primary = ACT_PRIMARY_CHAN_2; -+ break; -+ case 161: -+ act_primary = ACT_PRIMARY_CHAN_3; -+ break; -+ case 165: -+ act_primary = ACT_PRIMARY_CHAN_4; -+ break; -+ case 169: -+ act_primary = ACT_PRIMARY_CHAN_5; -+ break; -+ case 173: -+ act_primary = ACT_PRIMARY_CHAN_6; -+ break; -+ case 177: -+ act_primary = ACT_PRIMARY_CHAN_7; -+ break; -+ } -+ -+ return act_primary; -+} -+ -+static void mwl_fwcmd_parse_beacon(struct mwl_priv *priv, -+ struct mwl_vif *vif, u8 *beacon, int len) -+{ -+ struct ieee80211_mgmt *mgmt; -+ struct beacon_info *beacon_info; -+ int baselen; -+ u8 *pos; -+ size_t left; -+ bool elem_parse_failed; -+ -+ mgmt = (struct ieee80211_mgmt *)beacon; -+ -+ baselen = (u8 *)mgmt->u.beacon.variable - (u8 *)mgmt; -+ if (baselen > len) -+ return; -+ -+ beacon_info = &vif->beacon_info; -+ memset(beacon_info, 0, sizeof(struct beacon_info)); -+ beacon_info->valid = false; -+ beacon_info->ie_ht_ptr = &beacon_info->ie_list_ht[0]; -+ beacon_info->ie_vht_ptr = &beacon_info->ie_list_vht[0]; -+ -+ beacon_info->cap_info = le16_to_cpu(mgmt->u.beacon.capab_info); -+ beacon_info->power_constraint = 0; -+ -+ pos = (u8 *)mgmt->u.beacon.variable; -+ left = len - baselen; -+ -+ elem_parse_failed = false; -+ -+ while (left >= 2) { -+ u8 id, elen; -+ -+ id = *pos++; -+ elen = *pos++; -+ left -= 2; -+ -+ if (elen > left) { -+ elem_parse_failed = true; -+ break; -+ } -+ -+ switch (id) { -+ case WLAN_EID_COUNTRY: -+ beacon_info->ie_country_len = (elen + 2); -+ beacon_info->ie_country_ptr = (pos - 2); -+ break; -+ case WLAN_EID_SUPP_RATES: -+ case WLAN_EID_EXT_SUPP_RATES: -+ { -+ int idx, bi, oi; -+ u8 rate; -+ -+ for (bi = 0; bi < SYSADPT_MAX_DATA_RATES_G; -+ bi++) { -+ if (beacon_info->b_rate_set[bi] == 0) -+ break; -+ } -+ -+ for (oi = 0; oi < SYSADPT_MAX_DATA_RATES_G; -+ oi++) { -+ if (beacon_info->op_rate_set[oi] == 0) -+ break; -+ } -+ -+ for (idx = 0; idx < elen; idx++) { -+ rate = pos[idx]; -+ if ((rate & 0x80) != 0) { -+ if (bi < SYSADPT_MAX_DATA_RATES_G) -+ beacon_info->b_rate_set[bi++] -+ = rate & 0x7f; -+ else { -+ elem_parse_failed = true; -+ break; -+ } -+ } -+ if (oi < SYSADPT_MAX_DATA_RATES_G) -+ beacon_info->op_rate_set[oi++] = -+ rate & 0x7f; -+ else { -+ elem_parse_failed = true; -+ break; -+ } -+ } -+ } -+ break; -+ case WLAN_EID_PWR_CONSTRAINT: -+ if (elen == 1) -+ beacon_info->power_constraint = *pos; -+ break; -+ case WLAN_EID_RSN: -+ beacon_info->ie_rsn48_len = (elen + 2); -+ beacon_info->ie_rsn48_ptr = (pos - 2); -+ break; -+ case WLAN_EID_MOBILITY_DOMAIN: -+ beacon_info->ie_mde_len = (elen + 2); -+ beacon_info->ie_mde_ptr = (pos - 2); -+ break; -+ case WLAN_EID_HT_CAPABILITY: -+ case WLAN_EID_HT_OPERATION: -+ case WLAN_EID_OVERLAP_BSS_SCAN_PARAM: -+ case WLAN_EID_EXT_CAPABILITY: -+ beacon_info->ie_ht_len += (elen + 2); -+ if (beacon_info->ie_ht_len > -+ sizeof(beacon_info->ie_list_ht)) { -+ elem_parse_failed = true; -+ } else { -+ *beacon_info->ie_ht_ptr++ = id; -+ *beacon_info->ie_ht_ptr++ = elen; -+ memcpy(beacon_info->ie_ht_ptr, pos, elen); -+ beacon_info->ie_ht_ptr += elen; -+ } -+ break; -+ case WLAN_EID_MESH_CONFIG: -+ beacon_info->ie_meshcfg_len = (elen + 2); -+ beacon_info->ie_meshcfg_ptr = (pos - 2); -+ break; -+ case WLAN_EID_MESH_ID: -+ beacon_info->ie_meshid_len = (elen + 2); -+ beacon_info->ie_meshid_ptr = (pos - 2); -+ break; -+ case WLAN_EID_CHAN_SWITCH_PARAM: -+ beacon_info->ie_meshchsw_len = (elen + 2); -+ beacon_info->ie_meshchsw_ptr = (pos - 2); -+ break; -+ case WLAN_EID_VHT_CAPABILITY: -+ case WLAN_EID_VHT_OPERATION: -+ case WLAN_EID_OPMODE_NOTIF: -+ beacon_info->ie_vht_len += (elen + 2); -+ if (beacon_info->ie_vht_len > -+ sizeof(beacon_info->ie_list_vht)) { -+ elem_parse_failed = true; -+ } else { -+ *beacon_info->ie_vht_ptr++ = id; -+ *beacon_info->ie_vht_ptr++ = elen; -+ memcpy(beacon_info->ie_vht_ptr, pos, elen); -+ beacon_info->ie_vht_ptr += elen; -+ } -+ break; -+ case WLAN_EID_VENDOR_SPECIFIC: -+ if ((pos[0] == 0x00) && (pos[1] == 0x50) && -+ (pos[2] == 0xf2)) { -+ if (pos[3] == 0x01) { -+ beacon_info->ie_rsn_len = (elen + 2); -+ beacon_info->ie_rsn_ptr = (pos - 2); -+ } -+ -+ if (pos[3] == 0x02) { -+ beacon_info->ie_wmm_len = (elen + 2); -+ beacon_info->ie_wmm_ptr = (pos - 2); -+ } -+ -+ if (pos[3] == 0x04) { -+ beacon_info->ie_wsc_len = (elen + 2); -+ beacon_info->ie_wsc_ptr = (pos - 2); -+ } -+ } -+ break; -+ default: -+ break; -+ } -+ -+ left -= elen; -+ pos += elen; -+ } -+ -+ if (!elem_parse_failed) { -+ beacon_info->ie_ht_ptr = &beacon_info->ie_list_ht[0]; -+ beacon_info->ie_vht_ptr = &beacon_info->ie_list_vht[0]; -+ beacon_info->valid = true; -+ } -+} -+ -+static int mwl_fwcmd_set_ies(struct mwl_priv *priv, struct mwl_vif *mwl_vif) -+{ -+ struct hostcmd_cmd_set_ies *pcmd; -+ struct beacon_info *beacon = &mwl_vif->beacon_info; -+ u16 ie_list_len_proprietary = 0; -+ -+ if (beacon->ie_ht_len > sizeof(pcmd->ie_list_ht)) -+ goto einval; -+ -+ if (beacon->ie_vht_len > sizeof(pcmd->ie_list_vht)) -+ goto einval; -+ -+ pcmd = (struct hostcmd_cmd_set_ies *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_IES); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ -+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_GEN_SET); -+ -+ memcpy(pcmd->ie_list_ht, beacon->ie_ht_ptr, beacon->ie_ht_len); -+ pcmd->ie_list_len_ht = cpu_to_le16(beacon->ie_ht_len); -+ -+ memcpy(pcmd->ie_list_vht, beacon->ie_vht_ptr, beacon->ie_vht_len); -+ pcmd->ie_list_len_vht = cpu_to_le16(beacon->ie_vht_len); -+ -+ memcpy(pcmd->ie_list_proprietary, beacon->ie_meshid_ptr, -+ beacon->ie_meshid_len); -+ ie_list_len_proprietary = beacon->ie_meshid_len; -+ -+ memcpy(pcmd->ie_list_proprietary + ie_list_len_proprietary, -+ beacon->ie_meshcfg_ptr, beacon->ie_meshcfg_len); -+ ie_list_len_proprietary += beacon->ie_meshcfg_len; -+ -+ memcpy(pcmd->ie_list_proprietary + ie_list_len_proprietary, -+ beacon->ie_meshchsw_ptr, beacon->ie_meshchsw_len); -+ ie_list_len_proprietary += beacon->ie_meshchsw_len; -+ -+ if (priv->chip_type == MWL8897) { -+ memcpy(pcmd->ie_list_proprietary + ie_list_len_proprietary, -+ beacon->ie_wmm_ptr, beacon->ie_wmm_len); -+ ie_list_len_proprietary += mwl_vif->beacon_info.ie_wmm_len; -+ } -+ -+ memcpy(pcmd->ie_list_proprietary + ie_list_len_proprietary, -+ beacon->ie_mde_ptr, beacon->ie_mde_len); -+ ie_list_len_proprietary += mwl_vif->beacon_info.ie_mde_len; -+ -+ pcmd->ie_list_len_proprietary = cpu_to_le16(ie_list_len_proprietary); -+ -+ if (mwl_hif_exec_cmd(priv->hw, HOSTCMD_CMD_SET_IES)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+ -+einval: -+ -+ wiphy_err(priv->hw->wiphy, "length of IE is too long\n"); -+ -+ return -EINVAL; -+} -+ -+static int mwl_fwcmd_set_ap_beacon(struct mwl_priv *priv, -+ struct mwl_vif *mwl_vif, -+ struct ieee80211_bss_conf *bss_conf) -+{ -+ struct hostcmd_cmd_ap_beacon *pcmd; -+ struct ds_params *phy_ds_param_set; -+ -+ /* wmm structure of start command is defined less one byte, -+ * due to following field country is not used, add byte one -+ * to bypass the check. -+ */ -+ if (mwl_vif->beacon_info.ie_wmm_len > -+ (sizeof(pcmd->start_cmd.wmm_param) + 1)) -+ goto ielenerr; -+ -+ if (mwl_vif->beacon_info.ie_rsn_len > sizeof(pcmd->start_cmd.rsn_ie)) -+ goto ielenerr; -+ -+ if (mwl_vif->beacon_info.ie_rsn48_len > -+ sizeof(pcmd->start_cmd.rsn48_ie)) -+ goto ielenerr; -+ -+ pcmd = (struct hostcmd_cmd_ap_beacon *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_AP_BEACON); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ -+ ether_addr_copy(pcmd->start_cmd.sta_mac_addr, mwl_vif->bssid); -+ memcpy(pcmd->start_cmd.ssid, bss_conf->ssid, bss_conf->ssid_len); -+ if (priv->chip_type == MWL8997) -+ ether_addr_copy(pcmd->start_cmd.bssid, mwl_vif->bssid); -+ pcmd->start_cmd.bss_type = 1; -+ pcmd->start_cmd.bcn_period = cpu_to_le16(bss_conf->beacon_int); -+ pcmd->start_cmd.dtim_period = bss_conf->dtim_period; /* 8bit */ -+ -+ phy_ds_param_set = &pcmd->start_cmd.phy_param_set.ds_param_set; -+ phy_ds_param_set->elem_id = WLAN_EID_DS_PARAMS; -+ phy_ds_param_set->len = sizeof(phy_ds_param_set->current_chnl); -+ phy_ds_param_set->current_chnl = bss_conf->chandef.chan->hw_value; -+ -+ pcmd->start_cmd.probe_delay = cpu_to_le16(10); -+ pcmd->start_cmd.cap_info = cpu_to_le16(mwl_vif->beacon_info.cap_info); -+ -+ memcpy(&pcmd->start_cmd.wmm_param, mwl_vif->beacon_info.ie_wmm_ptr, -+ mwl_vif->beacon_info.ie_wmm_len); -+ -+ memcpy(&pcmd->start_cmd.rsn_ie, mwl_vif->beacon_info.ie_rsn_ptr, -+ mwl_vif->beacon_info.ie_rsn_len); -+ -+ memcpy(&pcmd->start_cmd.rsn48_ie, mwl_vif->beacon_info.ie_rsn48_ptr, -+ mwl_vif->beacon_info.ie_rsn48_len); -+ -+ memcpy(pcmd->start_cmd.b_rate_set, mwl_vif->beacon_info.b_rate_set, -+ SYSADPT_MAX_DATA_RATES_G); -+ -+ memcpy(pcmd->start_cmd.op_rate_set, mwl_vif->beacon_info.op_rate_set, -+ SYSADPT_MAX_DATA_RATES_G); -+ -+ if (mwl_hif_exec_cmd(priv->hw, HOSTCMD_CMD_AP_BEACON)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+ -+ielenerr: -+ -+ wiphy_err(priv->hw->wiphy, "length of IE is too long\n"); -+ -+ return -EINVAL; -+} -+ -+static int mwl_fwcmd_set_spectrum_mgmt(struct mwl_priv *priv, bool enable) -+{ -+ struct hostcmd_cmd_set_spectrum_mgmt *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_spectrum_mgmt *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_SPECTRUM_MGMT); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->spectrum_mgmt = cpu_to_le32(enable); -+ -+ if (mwl_hif_exec_cmd(priv->hw, HOSTCMD_CMD_SET_SPECTRUM_MGMT)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+static int mwl_fwcmd_set_power_constraint(struct mwl_priv *priv, -+ u32 power_constraint) -+{ -+ struct hostcmd_cmd_set_power_constraint *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_power_constraint *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_POWER_CONSTRAINT); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->power_constraint = cpu_to_le32(power_constraint); -+ -+ if (mwl_hif_exec_cmd(priv->hw, HOSTCMD_CMD_SET_POWER_CONSTRAINT)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+static int mwl_fwcmd_set_country_code(struct mwl_priv *priv, -+ struct mwl_vif *mwl_vif, -+ struct ieee80211_bss_conf *bss_conf) -+{ -+ struct hostcmd_cmd_set_country_code *pcmd; -+ struct beacon_info *b_inf = &mwl_vif->beacon_info; -+ u8 chnl_len; -+ bool a_band; -+ bool enable = false; -+ -+ if (b_inf->ie_country_ptr) { -+ if (bss_conf->chandef.chan->band == NL80211_BAND_2GHZ) -+ a_band = false; -+ else if (bss_conf->chandef.chan->band == NL80211_BAND_5GHZ) -+ a_band = true; -+ else -+ return -EINVAL; -+ -+ chnl_len = b_inf->ie_country_len - 5; -+ if (a_band) { -+ if (chnl_len > sizeof(pcmd->domain_info.domain_entry_a)) -+ return -EINVAL; -+ } else { -+ if (chnl_len > sizeof(pcmd->domain_info.domain_entry_g)) -+ return -EINVAL; -+ } -+ -+ enable = true; -+ } -+ -+ pcmd = (struct hostcmd_cmd_set_country_code *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_COUNTRY_CODE); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->action = cpu_to_le32(enable); -+ if (enable) { -+ memcpy(pcmd->domain_info.country_string, -+ b_inf->ie_country_ptr + 2, 3); -+ if (a_band) { -+ pcmd->domain_info.g_chnl_len = 0; -+ pcmd->domain_info.a_chnl_len = chnl_len; -+ memcpy(pcmd->domain_info.domain_entry_a, -+ b_inf->ie_country_ptr + 5, chnl_len); -+ } else { -+ pcmd->domain_info.a_chnl_len = 0; -+ pcmd->domain_info.g_chnl_len = chnl_len; -+ memcpy(pcmd->domain_info.domain_entry_g, -+ b_inf->ie_country_ptr + 5, chnl_len); -+ } -+ } -+ -+ if (mwl_hif_exec_cmd(priv->hw, HOSTCMD_CMD_SET_COUNTRY_CODE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+static int mwl_fwcmd_encryption_set_cmd_info(struct hostcmd_cmd_set_key *cmd, -+ u8 *addr, -+ struct ieee80211_key_conf *key) -+{ -+ cmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); -+ cmd->cmd_hdr.len = cpu_to_le16(sizeof(*cmd)); -+ cmd->key_param.length = cpu_to_le16(sizeof(*cmd) - -+ offsetof(struct hostcmd_cmd_set_key, key_param)); -+ cmd->key_param.key_index = cpu_to_le32(key->keyidx); -+ cmd->key_param.key_len = cpu_to_le16(key->keylen); -+ ether_addr_copy(cmd->key_param.mac_addr, addr); -+ -+ switch (key->cipher) { -+ case WLAN_CIPHER_SUITE_WEP40: -+ case WLAN_CIPHER_SUITE_WEP104: -+ cmd->key_param.key_type_id = cpu_to_le16(KEY_TYPE_ID_WEP); -+ if (key->keyidx == 0) -+ cmd->key_param.key_info = -+ cpu_to_le32(ENCR_KEY_FLAG_WEP_TXKEY); -+ break; -+ case WLAN_CIPHER_SUITE_TKIP: -+ cmd->key_param.key_type_id = cpu_to_le16(KEY_TYPE_ID_TKIP); -+ cmd->key_param.key_info = -+ (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ? -+ cpu_to_le32(ENCR_KEY_FLAG_PAIRWISE) : -+ cpu_to_le32(ENCR_KEY_FLAG_TXGROUPKEY); -+ cmd->key_param.key_info |= -+ cpu_to_le32(ENCR_KEY_FLAG_MICKEY_VALID | -+ ENCR_KEY_FLAG_TSC_VALID); -+ break; -+ case WLAN_CIPHER_SUITE_CCMP: -+ cmd->key_param.key_type_id = cpu_to_le16(KEY_TYPE_ID_AES); -+ cmd->key_param.key_info = -+ (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ? -+ cpu_to_le32(ENCR_KEY_FLAG_PAIRWISE) : -+ cpu_to_le32(ENCR_KEY_FLAG_TXGROUPKEY); -+ break; -+ case WLAN_CIPHER_SUITE_AES_CMAC: -+ return 1; -+ default: -+ return -ENOTSUPP; -+ } -+ -+ return 0; -+} -+ -+static __le16 mwl_fwcmd_parse_cal_cfg(const u8 *src, size_t len, u8 *dst) -+{ -+ const u8 *ptr; -+ u8 *dptr; -+ char byte_str[3]; -+ long res; -+ -+ ptr = src; -+ dptr = dst; -+ byte_str[2] = '\0'; -+ -+ while (ptr - src < len) { -+ if (*ptr && (isspace(*ptr) || iscntrl(*ptr))) { -+ ptr++; -+ continue; -+ } -+ -+ if (isxdigit(*ptr)) { -+ byte_str[0] = *ptr++; -+ byte_str[1] = *ptr++; -+ kstrtol(byte_str, 16, &res); -+ *dptr++ = res; -+ } else { -+ ptr++; -+ } -+ } -+ -+ return cpu_to_le16(dptr - dst); -+} -+ -+static u16 mwl_fwcmd_parse_txpwrlmt_cfg(const u8 *src, size_t len, -+ u16 parse_len, u8 *dst) -+{ -+ const u8 *ptr; -+ u8 *dptr; -+ char byte_str[3]; -+ long res; -+ -+ ptr = src; -+ dptr = dst; -+ byte_str[2] = '\0'; -+ -+ while ((ptr - src < len) && (dptr - dst < parse_len)) { -+ if (*ptr && (isspace(*ptr) || iscntrl(*ptr))) { -+ ptr++; -+ continue; -+ } -+ -+ if (isxdigit(*ptr)) { -+ byte_str[0] = *ptr++; -+ byte_str[1] = *ptr++; -+ kstrtol(byte_str, 16, &res); -+ *dptr++ = res; -+ } else { -+ ptr++; -+ } -+ } -+ -+ return (ptr - src); -+} -+ -+const struct hostcmd_get_hw_spec -+*mwl_fwcmd_get_hw_specs(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_get_hw_spec *pcmd; -+ int retry; -+ -+ pcmd = (struct hostcmd_cmd_get_hw_spec *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ wiphy_debug(hw->wiphy, "pcmd = %p\n", pcmd); -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ eth_broadcast_addr(pcmd->hw_spec.permanent_addr); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_HW_SPEC); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->hw_spec.fw_awake_cookie = cpu_to_le32(priv->pphys_cmd_buf + 2048); -+ -+ retry = 0; -+ while (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_GET_HW_SPEC)) { -+ if (retry++ > MAX_WAIT_GET_HW_SPECS_ITERATONS) { -+ wiphy_err(hw->wiphy, "can't get hw specs\n"); -+ mutex_unlock(&priv->fwcmd_mutex); -+ return NULL; -+ } -+ -+ msleep(1000); -+ wiphy_debug(hw->wiphy, -+ "repeat command = %p\n", pcmd); -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return &pcmd->hw_spec; -+} -+ -+int mwl_fwcmd_set_hw_specs(struct ieee80211_hw *hw, -+ struct hostcmd_set_hw_spec *spec) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_hw_spec *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_hw_spec *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_HW_SPEC); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ memcpy(&pcmd->hw_spec, spec, sizeof(*spec)); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_HW_SPEC)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_get_stat(struct ieee80211_hw *hw, -+ struct ieee80211_low_level_stats *stats) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_802_11_get_stat *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_802_11_get_stat *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_GET_STAT); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_802_11_GET_STAT)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ stats->dot11ACKFailureCount = -+ le32_to_cpu(pcmd->ack_failures); -+ stats->dot11RTSFailureCount = -+ le32_to_cpu(pcmd->rts_failures); -+ stats->dot11FCSErrorCount = -+ le32_to_cpu(pcmd->rx_fcs_errors); -+ stats->dot11RTSSuccessCount = -+ le32_to_cpu(pcmd->rts_successes); -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_reg_bb(struct ieee80211_hw *hw, u8 flag, u32 reg, u32 *val) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_bbp_reg_access *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_bbp_reg_access *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BBP_REG_ACCESS); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->offset = cpu_to_le16(reg); -+ pcmd->action = cpu_to_le16(flag); -+ pcmd->value = *val; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_BBP_REG_ACCESS)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ *val = pcmd->value; -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_reg_rf(struct ieee80211_hw *hw, u8 flag, u32 reg, u32 *val) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_rf_reg_access *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_rf_reg_access *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_RF_REG_ACCESS); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->offset = cpu_to_le16(reg); -+ pcmd->action = cpu_to_le16(flag); -+ pcmd->value = *val; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_RF_REG_ACCESS)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ *val = pcmd->value; -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_radio_enable(struct ieee80211_hw *hw) -+{ -+ return mwl_fwcmd_802_11_radio_control(hw->priv, true, false); -+} -+ -+int mwl_fwcmd_radio_disable(struct ieee80211_hw *hw) -+{ -+ return mwl_fwcmd_802_11_radio_control(hw->priv, false, false); -+} -+ -+int mwl_fwcmd_set_radio_preamble(struct ieee80211_hw *hw, bool short_preamble) -+{ -+ struct mwl_priv *priv = hw->priv; -+ int rc; -+ -+ priv->radio_short_preamble = short_preamble; -+ rc = mwl_fwcmd_802_11_radio_control(priv, true, true); -+ -+ return rc; -+} -+ -+int mwl_fwcmd_get_addr_value(struct ieee80211_hw *hw, u32 addr, u32 len, -+ u32 *val, u16 set) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_mem_addr_access *pcmd; -+ int i; -+ -+ pcmd = (struct hostcmd_cmd_mem_addr_access *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_MEM_ADDR_ACCESS); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->address = cpu_to_le32(addr); -+ pcmd->length = cpu_to_le16(len); -+ pcmd->value[0] = cpu_to_le32(*val); -+ pcmd->reserved = cpu_to_le16(set); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_MEM_ADDR_ACCESS)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ for (i = 0; i < len; i++) -+ val[i] = le32_to_cpu(pcmd->value[i]); -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_max_tx_power(struct ieee80211_hw *hw, -+ struct ieee80211_conf *conf, u8 fraction) -+{ -+ struct ieee80211_channel *channel = conf->chandef.chan; -+ struct mwl_priv *priv = hw->priv; -+ int reduce_val = 0; -+ u16 band = 0, width = 0, sub_ch = 0; -+ u16 maxtxpow[SYSADPT_TX_GRP_PWR_LEVEL_TOTAL]; -+ int i, tmp; -+ int rc = 0; -+ -+ if ((priv->chip_type != MWL8997) && (priv->forbidden_setting)) -+ return rc; -+ -+ switch (fraction) { -+ case 0: -+ reduce_val = 0; /* Max */ -+ break; -+ case 1: -+ reduce_val = 2; /* 75% -1.25db */ -+ break; -+ case 2: -+ reduce_val = 3; /* 50% -3db */ -+ break; -+ case 3: -+ reduce_val = 6; /* 25% -6db */ -+ break; -+ default: -+ /* larger than case 3, pCmd->MaxPowerLevel is min */ -+ reduce_val = 0xff; -+ break; -+ } -+ -+ if (channel->band == NL80211_BAND_2GHZ) -+ band = FREQ_BAND_2DOT4GHZ; -+ else if (channel->band == NL80211_BAND_5GHZ) -+ band = FREQ_BAND_5GHZ; -+ -+ switch (conf->chandef.width) { -+ case NL80211_CHAN_WIDTH_20_NOHT: -+ case NL80211_CHAN_WIDTH_20: -+ width = CH_20_MHZ_WIDTH; -+ sub_ch = NO_EXT_CHANNEL; -+ break; -+ case NL80211_CHAN_WIDTH_40: -+ width = CH_40_MHZ_WIDTH; -+ if (conf->chandef.center_freq1 > channel->center_freq) -+ sub_ch = EXT_CH_ABOVE_CTRL_CH; -+ else -+ sub_ch = EXT_CH_BELOW_CTRL_CH; -+ break; -+ case NL80211_CHAN_WIDTH_80: -+ width = CH_80_MHZ_WIDTH; -+ if (conf->chandef.center_freq1 > channel->center_freq) -+ sub_ch = EXT_CH_ABOVE_CTRL_CH; -+ else -+ sub_ch = EXT_CH_BELOW_CTRL_CH; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ if (priv->chip_type == MWL8997) { -+ mwl_fwcmd_get_tx_powers(priv, priv->max_tx_pow, -+ HOSTCMD_ACT_GET_MAX_TX_PWR, -+ channel->hw_value, band, width, sub_ch); -+ -+ for (i = 0; i < priv->pwr_level; i++) { -+ tmp = priv->max_tx_pow[i]; -+ maxtxpow[i] = ((tmp - reduce_val) > 0) ? -+ (tmp - reduce_val) : 0; -+ } -+ -+ rc = mwl_fwcmd_set_tx_powers(priv, maxtxpow, -+ HOSTCMD_ACT_SET_MAX_TX_PWR, -+ channel->hw_value, band, -+ width, sub_ch); -+ return rc; -+ } -+ -+ if ((priv->powinited & MWL_POWER_INIT_2) == 0) { -+ mwl_fwcmd_get_tx_powers(priv, priv->max_tx_pow, -+ HOSTCMD_ACT_GEN_GET_LIST, -+ channel->hw_value, band, width, sub_ch); -+ priv->powinited |= MWL_POWER_INIT_2; -+ } -+ -+ if ((priv->powinited & MWL_POWER_INIT_1) == 0) { -+ mwl_fwcmd_get_tx_powers(priv, priv->target_powers, -+ HOSTCMD_ACT_GEN_GET_LIST, -+ channel->hw_value, band, width, sub_ch); -+ priv->powinited |= MWL_POWER_INIT_1; -+ } -+ -+ for (i = 0; i < priv->pwr_level; i++) { -+ if (priv->target_powers[i] > priv->max_tx_pow[i]) -+ tmp = priv->max_tx_pow[i]; -+ else -+ tmp = priv->target_powers[i]; -+ maxtxpow[i] = ((tmp - reduce_val) > 0) ? (tmp - reduce_val) : 0; -+ } -+ -+ rc = mwl_fwcmd_set_tx_powers(priv, maxtxpow, HOSTCMD_ACT_GEN_SET, -+ channel->hw_value, band, width, sub_ch); -+ -+ return rc; -+} -+ -+int mwl_fwcmd_tx_power(struct ieee80211_hw *hw, -+ struct ieee80211_conf *conf, u8 fraction) -+{ -+ struct ieee80211_channel *channel = conf->chandef.chan; -+ struct mwl_priv *priv = hw->priv; -+ int reduce_val = 0; -+ u16 band = 0, width = 0, sub_ch = 0; -+ u16 txpow[SYSADPT_TX_GRP_PWR_LEVEL_TOTAL]; -+ int index, found = 0; -+ int i, tmp; -+ int rc = 0; -+ -+ if ((priv->chip_type != MWL8997) && (priv->forbidden_setting)) -+ return rc; -+ -+ switch (fraction) { -+ case 0: -+ reduce_val = 0; /* Max */ -+ break; -+ case 1: -+ reduce_val = 2; /* 75% -1.25db */ -+ break; -+ case 2: -+ reduce_val = 3; /* 50% -3db */ -+ break; -+ case 3: -+ reduce_val = 6; /* 25% -6db */ -+ break; -+ default: -+ /* larger than case 3, pCmd->MaxPowerLevel is min */ -+ reduce_val = 0xff; -+ break; -+ } -+ -+ if (channel->band == NL80211_BAND_2GHZ) -+ band = FREQ_BAND_2DOT4GHZ; -+ else if (channel->band == NL80211_BAND_5GHZ) -+ band = FREQ_BAND_5GHZ; -+ -+ switch (conf->chandef.width) { -+ case NL80211_CHAN_WIDTH_20_NOHT: -+ case NL80211_CHAN_WIDTH_20: -+ width = CH_20_MHZ_WIDTH; -+ sub_ch = NO_EXT_CHANNEL; -+ break; -+ case NL80211_CHAN_WIDTH_40: -+ width = CH_40_MHZ_WIDTH; -+ if (conf->chandef.center_freq1 > channel->center_freq) -+ sub_ch = EXT_CH_ABOVE_CTRL_CH; -+ else -+ sub_ch = EXT_CH_BELOW_CTRL_CH; -+ break; -+ case NL80211_CHAN_WIDTH_80: -+ width = CH_80_MHZ_WIDTH; -+ if (conf->chandef.center_freq1 > channel->center_freq) -+ sub_ch = EXT_CH_ABOVE_CTRL_CH; -+ else -+ sub_ch = EXT_CH_BELOW_CTRL_CH; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ if (priv->chip_type == MWL8997) { -+ mwl_fwcmd_get_tx_powers(priv, priv->target_powers, -+ HOSTCMD_ACT_GET_TARGET_TX_PWR, -+ channel->hw_value, band, width, sub_ch); -+ -+ for (i = 0; i < priv->pwr_level; i++) { -+ tmp = priv->target_powers[i]; -+ txpow[i] = ((tmp - reduce_val) > 0) ? -+ (tmp - reduce_val) : 0; -+ } -+ -+ rc = mwl_fwcmd_set_tx_powers(priv, txpow, -+ HOSTCMD_ACT_SET_TARGET_TX_PWR, -+ channel->hw_value, band, -+ width, sub_ch); -+ -+ return rc; -+ } -+ -+ /* search tx power table if exist */ -+ for (index = 0; index < SYSADPT_MAX_NUM_CHANNELS; index++) { -+ struct mwl_tx_pwr_tbl *tx_pwr; -+ -+ tx_pwr = &priv->tx_pwr_tbl[index]; -+ -+ /* do nothing if table is not loaded */ -+ if (tx_pwr->channel == 0) -+ break; -+ -+ if (tx_pwr->channel == channel->hw_value) { -+ priv->cdd = tx_pwr->cdd; -+ priv->txantenna2 = tx_pwr->txantenna2; -+ -+ if (tx_pwr->setcap) -+ priv->powinited = MWL_POWER_INIT_1; -+ else -+ priv->powinited = MWL_POWER_INIT_2; -+ -+ for (i = 0; i < priv->pwr_level; i++) { -+ if (tx_pwr->setcap) -+ priv->max_tx_pow[i] = -+ tx_pwr->tx_power[i]; -+ else -+ priv->target_powers[i] = -+ tx_pwr->tx_power[i]; -+ } -+ -+ found = 1; -+ break; -+ } -+ } -+ -+ if ((priv->powinited & MWL_POWER_INIT_2) == 0) { -+ mwl_fwcmd_get_tx_powers(priv, priv->max_tx_pow, -+ HOSTCMD_ACT_GEN_GET_LIST, -+ channel->hw_value, band, width, sub_ch); -+ -+ priv->powinited |= MWL_POWER_INIT_2; -+ } -+ -+ if ((priv->powinited & MWL_POWER_INIT_1) == 0) { -+ mwl_fwcmd_get_tx_powers(priv, priv->target_powers, -+ HOSTCMD_ACT_GEN_GET_LIST, -+ channel->hw_value, band, width, sub_ch); -+ -+ priv->powinited |= MWL_POWER_INIT_1; -+ } -+ -+ for (i = 0; i < priv->pwr_level; i++) { -+ if (found) { -+ if ((priv->tx_pwr_tbl[index].setcap) && -+ (priv->tx_pwr_tbl[index].tx_power[i] > -+ priv->max_tx_pow[i])) -+ tmp = priv->max_tx_pow[i]; -+ else -+ tmp = priv->tx_pwr_tbl[index].tx_power[i]; -+ } else { -+ if (priv->target_powers[i] > priv->max_tx_pow[i]) -+ tmp = priv->max_tx_pow[i]; -+ else -+ tmp = priv->target_powers[i]; -+ } -+ -+ txpow[i] = ((tmp - reduce_val) > 0) ? (tmp - reduce_val) : 0; -+ } -+ -+ rc = mwl_fwcmd_set_tx_powers(priv, txpow, HOSTCMD_ACT_GEN_SET_LIST, -+ channel->hw_value, band, width, sub_ch); -+ -+ return rc; -+} -+ -+int mwl_fwcmd_rf_antenna(struct ieee80211_hw *hw, int dir, int antenna) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_802_11_rf_antenna *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_802_11_rf_antenna *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_RF_ANTENNA); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ -+ pcmd->action = cpu_to_le16(dir); -+ -+ if (dir == WL_ANTENNATYPE_RX) { -+ u8 rx_antenna; -+ -+ if (priv->chip_type == MWL8964) { -+ if (antenna == ANTENNA_RX_4_AUTO) -+ rx_antenna = 0xf; -+ else if (antenna == ANTENNA_RX_3) -+ rx_antenna = 7; -+ else if (antenna == ANTENNA_RX_2) -+ rx_antenna = 4; -+ else -+ rx_antenna = 1; -+ -+ pcmd->antenna_mode = cpu_to_le16(rx_antenna); -+ } else { -+ rx_antenna = 4; -+ -+ if (antenna != 0) -+ pcmd->antenna_mode = cpu_to_le16(antenna); -+ else -+ pcmd->antenna_mode = cpu_to_le16(rx_antenna); -+ } -+ } else { -+ u8 tx_antenna = 0xf; -+ -+ if (antenna != 0) -+ pcmd->antenna_mode = cpu_to_le16(antenna); -+ else -+ pcmd->antenna_mode = cpu_to_le16(tx_antenna); -+ } -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_802_11_RF_ANTENNA)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_broadcast_ssid_enable(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, bool enable) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct hostcmd_cmd_broadcast_ssid_enable *pcmd; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ -+ pcmd = (struct hostcmd_cmd_broadcast_ssid_enable *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BROADCAST_SSID_ENABLE); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ pcmd->enable = cpu_to_le32(enable); -+ if (priv->chip_type == MWL8997) -+ pcmd->hidden_ssid_info = enable ? 0 : 2; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_BROADCAST_SSID_ENABLE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_cfg_data(struct ieee80211_hw *hw, u16 type) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_cfg *pcmd; -+ -+ if (!priv->cal_data) -+ return 0; -+ -+ pcmd = (struct hostcmd_cmd_set_cfg *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->data_len = mwl_fwcmd_parse_cal_cfg(priv->cal_data->data, -+ priv->cal_data->size, -+ pcmd->data); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_CFG); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd) + -+ le16_to_cpu(pcmd->data_len) - sizeof(pcmd->data)); -+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_GEN_SET); -+ pcmd->type = cpu_to_le16(type); -+ -+ utils_dump_data_debug("CalData:", pcmd->data, -+ le16_to_cpu(pcmd->data_len)); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_CFG)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ release_firmware(priv->cal_data); -+ priv->cal_data = NULL; -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ release_firmware(priv->cal_data); -+ priv->cal_data = NULL; -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_rf_channel(struct ieee80211_hw *hw, -+ struct ieee80211_conf *conf) -+{ -+ struct ieee80211_channel *channel = conf->chandef.chan; -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_rf_channel *pcmd; -+ u32 chnl_flags, freq_band, chnl_width, act_primary; -+ -+ pcmd = (struct hostcmd_cmd_set_rf_channel *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ if (priv->chip_type == MWL8997) { -+ memset(pcmd, 0x00, -+ sizeof(struct hostcmd_cmd_set_rf_channel_kf2)); -+ pcmd->cmd_hdr.len = cpu_to_le16( -+ sizeof(struct hostcmd_cmd_set_rf_channel_kf2)); -+ } else { -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ } -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_RF_CHANNEL); -+ pcmd->action = cpu_to_le16(WL_SET); -+ pcmd->curr_chnl = channel->hw_value; -+ -+ if (channel->band == NL80211_BAND_2GHZ) { -+ freq_band = FREQ_BAND_2DOT4GHZ; -+ } else if (channel->band == NL80211_BAND_5GHZ) { -+ freq_band = FREQ_BAND_5GHZ; -+ } else { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EINVAL; -+ } -+ -+ switch (conf->chandef.width) { -+ case NL80211_CHAN_WIDTH_20_NOHT: -+ case NL80211_CHAN_WIDTH_20: -+ chnl_width = CH_20_MHZ_WIDTH; -+ act_primary = ACT_PRIMARY_CHAN_0; -+ break; -+ case NL80211_CHAN_WIDTH_40: -+ chnl_width = CH_40_MHZ_WIDTH; -+ if (conf->chandef.center_freq1 > channel->center_freq) -+ act_primary = ACT_PRIMARY_CHAN_0; -+ else -+ act_primary = ACT_PRIMARY_CHAN_1; -+ break; -+ case NL80211_CHAN_WIDTH_80: -+ chnl_width = CH_80_MHZ_WIDTH; -+ act_primary = -+ mwl_fwcmd_get_80m_pri_chnl(pcmd->curr_chnl); -+ break; -+ case NL80211_CHAN_WIDTH_160: -+ chnl_width = CH_160_MHZ_WIDTH; -+ act_primary = -+ mwl_fwcmd_get_160m_pri_chnl(pcmd->curr_chnl); -+ break; -+ default: -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EINVAL; -+ } -+ -+ chnl_flags = (freq_band & FREQ_BAND_MASK) | -+ ((chnl_width << CHNL_WIDTH_SHIFT) & CHNL_WIDTH_MASK) | -+ ((act_primary << ACT_PRIMARY_SHIFT) & ACT_PRIMARY_MASK); -+ -+ pcmd->chnl_flags = cpu_to_le32(chnl_flags); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_RF_CHANNEL)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ if (pcmd->cmd_hdr.result != 0) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EINVAL; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ if (priv->sw_scanning) { -+ priv->survey_info_idx++; -+ mwl_fwcmd_get_survey(hw, priv->survey_info_idx); -+ } else { -+ mwl_fwcmd_get_survey(hw, 0); -+ memset(&priv->cur_survey_info, 0, -+ sizeof(struct mwl_survey_info)); -+ } -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_aid(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, u8 *bssid, u16 aid) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct hostcmd_cmd_set_aid *pcmd; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ -+ pcmd = (struct hostcmd_cmd_set_aid *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_AID); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ pcmd->aid = cpu_to_le16(aid); -+ ether_addr_copy(pcmd->mac_addr, bssid); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_AID)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_infra_mode(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct hostcmd_cmd_set_infra_mode *pcmd; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ -+ pcmd = (struct hostcmd_cmd_set_infra_mode *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_INFRA_MODE); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_INFRA_MODE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_rts_threshold(struct ieee80211_hw *hw, int threshold) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_802_11_rts_thsd *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_802_11_rts_thsd *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_RTS_THSD); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->action = cpu_to_le16(WL_SET); -+ pcmd->threshold = cpu_to_le16(threshold); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_802_11_RTS_THSD)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_edca_params(struct ieee80211_hw *hw, u8 index, -+ u16 cw_min, u16 cw_max, u8 aifs, u16 txop) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_edca_params *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_edca_params *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_EDCA_PARAMS); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ -+ pcmd->action = cpu_to_le16(0xffff); -+ pcmd->txop = cpu_to_le16(txop); -+ pcmd->cw_max = cpu_to_le32(cw_max); -+ pcmd->cw_min = cpu_to_le32(cw_min); -+ pcmd->aifsn = aifs; -+ pcmd->txq_num = index; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_EDCA_PARAMS)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_radar_detect(struct ieee80211_hw *hw, u16 action) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_802_11h_detect_radar *pcmd; -+ u16 radar_type = RADAR_TYPE_CODE_0; -+ u8 channel = hw->conf.chandef.chan->hw_value; -+ -+ pcmd = (struct hostcmd_cmd_802_11h_detect_radar *)&priv->pcmd_buf[0]; -+ -+ if (priv->dfs_region == NL80211_DFS_JP) { -+ if (channel >= 52 && channel <= 64) -+ radar_type = RADAR_TYPE_CODE_53; -+ else if (channel >= 100 && channel <= 140) -+ radar_type = RADAR_TYPE_CODE_56; -+ else -+ radar_type = RADAR_TYPE_CODE_0; -+ } else if (priv->dfs_region == NL80211_DFS_ETSI) { -+ radar_type = RADAR_TYPE_CODE_ETSI; -+ } -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11H_DETECT_RADAR); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->action = cpu_to_le16(action); -+ pcmd->radar_type_code = cpu_to_le16(radar_type); -+ pcmd->min_chirp_cnt = cpu_to_le16(priv->dfs_chirp_count_min); -+ pcmd->chirp_time_intvl = cpu_to_le16(priv->dfs_chirp_time_interval); -+ pcmd->pw_filter = cpu_to_le16(priv->dfs_pw_filter); -+ pcmd->min_num_radar = cpu_to_le16(priv->dfs_min_num_radar); -+ pcmd->pri_min_num = cpu_to_le16(priv->dfs_min_pri_count); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_802_11H_DETECT_RADAR)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_wmm_mode(struct ieee80211_hw *hw, bool enable) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_wmm_mode *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_wmm_mode *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_WMM_MODE); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->action = cpu_to_le16(enable ? WL_ENABLE : WL_DISABLE); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_WMM_MODE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_ht_guard_interval(struct ieee80211_hw *hw, u32 gi_type) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_ht_guard_interval *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_ht_guard_interval *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_HT_GUARD_INTERVAL); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->action = cpu_to_le32(WL_SET); -+ pcmd->gi_type = cpu_to_le32(gi_type); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_HT_GUARD_INTERVAL)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_use_fixed_rate(struct ieee80211_hw *hw, int mcast, int mgmt) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_fixed_rate *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_fixed_rate *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_FIXED_RATE); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ -+ pcmd->action = cpu_to_le32(HOSTCMD_ACT_NOT_USE_FIXED_RATE); -+ pcmd->multicast_rate = mcast; -+ pcmd->management_rate = mgmt; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_FIXED_RATE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_linkadapt_cs_mode(struct ieee80211_hw *hw, u16 cs_mode) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_linkadapt_cs_mode *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_linkadapt_cs_mode *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_LINKADAPT_CS_MODE); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_GEN_SET); -+ pcmd->cs_mode = cpu_to_le16(cs_mode); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_LINKADAPT_CS_MODE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_dump_otp_data(struct ieee80211_hw *hw) -+{ -+ int otp_data_len; -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_dump_otp_data *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_dump_otp_data *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_DUMP_OTP_DATA); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_DUMP_OTP_DATA)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ otp_data_len = pcmd->cmd_hdr.len - cpu_to_le16(sizeof(*pcmd)); -+ -+ if (otp_data_len <= SYSADPT_OTP_BUF_SIZE) { -+ wiphy_info(hw->wiphy, "OTP data len = %d\n", otp_data_len); -+ priv->otp_data.len = otp_data_len; -+ memcpy(priv->otp_data.buf, pcmd->pload, otp_data_len); -+ } else { -+ wiphy_err(hw->wiphy, "Driver OTP buf size is less\n"); -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_rate_adapt_mode(struct ieee80211_hw *hw, u16 mode) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_rate_adapt_mode *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_rate_adapt_mode *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_RATE_ADAPT_MODE); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->action = cpu_to_le16(WL_SET); -+ pcmd->rate_adapt_mode = cpu_to_le16(mode); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_RATE_ADAPT_MODE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_mac_addr_client(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, u8 *mac_addr) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct hostcmd_cmd_set_mac_addr *pcmd; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ -+ pcmd = (struct hostcmd_cmd_set_mac_addr *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_MAC_ADDR); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ pcmd->mac_type = cpu_to_le16(WL_MAC_TYPE_SECONDARY_CLIENT); -+ ether_addr_copy(pcmd->mac_addr, mac_addr); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_MAC_ADDR)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_get_watchdog_bitmap(struct ieee80211_hw *hw, u8 *bitmap) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_get_watchdog_bitmap *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_get_watchdog_bitmap *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_WATCHDOG_BITMAP); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_GET_WATCHDOG_BITMAP)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ *bitmap = pcmd->watchdog_bitmap; -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_remove_mac_addr(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, u8 *mac_addr) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct hostcmd_cmd_set_mac_addr *pcmd; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ -+ pcmd = (struct hostcmd_cmd_set_mac_addr *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_DEL_MAC_ADDR); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ ether_addr_copy(pcmd->mac_addr, mac_addr); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_DEL_MAC_ADDR)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_bss_start(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, bool enable) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct hostcmd_cmd_bss_start *pcmd; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ -+ if (enable && (priv->running_bsses & (1 << mwl_vif->macid))) -+ return 0; -+ -+ if (!enable && !(priv->running_bsses & (1 << mwl_vif->macid))) -+ return 0; -+ -+ pcmd = (struct hostcmd_cmd_bss_start *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BSS_START); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ -+ if (enable) { -+ pcmd->enable = cpu_to_le32(WL_ENABLE); -+ } else { -+ if (mwl_vif->macid == 0) -+ pcmd->enable = cpu_to_le32(WL_DISABLE); -+ else -+ pcmd->enable = cpu_to_le32(WL_DISABLE_VMAC); -+ } -+ if (priv->chip_type == MWL8964) -+ pcmd->amsdu = MWL_AMSDU_SIZE_11K; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_BSS_START)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ if (enable) -+ priv->running_bsses |= (1 << mwl_vif->macid); -+ else -+ priv->running_bsses &= ~(1 << mwl_vif->macid); -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_beacon(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, u8 *beacon, int len) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct beacon_info *b_inf; -+ int rc; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ b_inf = &mwl_vif->beacon_info; -+ -+ mwl_fwcmd_parse_beacon(priv, mwl_vif, beacon, len); -+ -+ if (!b_inf->valid) -+ goto err; -+ -+ if (mwl_fwcmd_set_ies(priv, mwl_vif)) -+ goto err; -+ -+ if (mwl_fwcmd_set_wsc_ie(hw, b_inf->ie_wsc_len, b_inf->ie_wsc_ptr)) -+ goto err; -+ -+ if (mwl_fwcmd_set_ap_beacon(priv, mwl_vif, &vif->bss_conf)) -+ goto err; -+ -+ if (b_inf->cap_info & WLAN_CAPABILITY_SPECTRUM_MGMT) -+ rc = mwl_fwcmd_set_spectrum_mgmt(priv, true); -+ else -+ rc = mwl_fwcmd_set_spectrum_mgmt(priv, false); -+ if (rc) -+ goto err; -+ -+ if (b_inf->power_constraint) -+ rc = mwl_fwcmd_set_power_constraint(priv, -+ b_inf->power_constraint); -+ if (rc) -+ goto err; -+ -+ if (mwl_fwcmd_set_country_code(priv, mwl_vif, &vif->bss_conf)) -+ goto err; -+ -+ b_inf->valid = false; -+ -+ return 0; -+ -+err: -+ -+ b_inf->valid = false; -+ -+ return -EIO; -+} -+ -+int mwl_fwcmd_set_new_stn_add(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ struct ieee80211_sta *sta) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct mwl_sta *sta_info; -+ struct hostcmd_cmd_set_new_stn *pcmd; -+ u32 rates; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ sta_info = mwl_dev_get_sta(sta); -+ -+ pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ -+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_STA_ACTION_ADD); -+ pcmd->aid = cpu_to_le16(sta->aid); -+ pcmd->stn_id = cpu_to_le16(sta_info->stnid); -+ if (priv->chip_type == MWL8997) -+ pcmd->if_type = cpu_to_le16(vif->type); -+ else -+ pcmd->if_type = cpu_to_le16(1); -+ ether_addr_copy(pcmd->mac_addr, sta->addr); -+ -+ if (hw->conf.chandef.chan->band == NL80211_BAND_2GHZ) -+ rates = sta->supp_rates[NL80211_BAND_2GHZ]; -+ else -+ rates = sta->supp_rates[NL80211_BAND_5GHZ] << 5; -+ pcmd->peer_info.legacy_rate_bitmap = cpu_to_le32(rates); -+ -+ if (sta->ht_cap.ht_supported) { -+ int i; -+ -+ for (i = 0; i < 4; i++) { -+ if (i < sta->rx_nss) { -+ pcmd->peer_info.ht_rates[i] = -+ sta->ht_cap.mcs.rx_mask[i]; -+ } else { -+ pcmd->peer_info.ht_rates[i] = 0; -+ } -+ } -+ pcmd->peer_info.ht_cap_info = cpu_to_le16(sta->ht_cap.cap); -+ pcmd->peer_info.mac_ht_param_info = -+ (sta->ht_cap.ampdu_factor & 3) | -+ ((sta->ht_cap.ampdu_density & 7) << 2); -+ } -+ -+ if (sta->vht_cap.vht_supported) { -+ u32 rx_mcs_map_mask = 0; -+ -+ rx_mcs_map_mask = ((0x0000FFFF) >> (sta->rx_nss * 2)) -+ << (sta->rx_nss * 2); -+ pcmd->peer_info.vht_max_rx_mcs = -+ cpu_to_le32((*((u32 *) -+ &sta->vht_cap.vht_mcs.rx_mcs_map)) | rx_mcs_map_mask); -+ pcmd->peer_info.vht_cap = cpu_to_le32(sta->vht_cap.cap); -+ pcmd->peer_info.vht_rx_channel_width = sta->bandwidth; -+ } -+ -+ pcmd->is_qos_sta = sta->wme; -+ pcmd->qos_info = ((sta->uapsd_queues << 4) | (sta->max_sp << 1)); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_NEW_STN)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ if (vif->type == NL80211_IFTYPE_STATION) { -+ ether_addr_copy(pcmd->mac_addr, mwl_vif->sta_mac); -+ pcmd->aid = cpu_to_le16(sta->aid + 1); -+ pcmd->stn_id = cpu_to_le16(sta_info->sta_stnid); -+ pcmd->if_type = cpu_to_le16(0); -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_NEW_STN)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_new_stn_add_sc4(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ struct ieee80211_sta *sta, -+ u32 wds) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct mwl_sta *sta_info; -+ struct hostcmd_cmd_set_new_stn_sc4 *pcmd; -+ u32 rates; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ sta_info = mwl_dev_get_sta(sta); -+ -+ pcmd = (struct hostcmd_cmd_set_new_stn_sc4 *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ -+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_STA_ACTION_ADD); -+ pcmd->aid = cpu_to_le16(sta->aid); -+ pcmd->stn_id = cpu_to_le16(sta_info->stnid); -+ ether_addr_copy(pcmd->mac_addr, sta->addr); -+ -+ if (hw->conf.chandef.chan->band == NL80211_BAND_2GHZ) -+ rates = sta->supp_rates[NL80211_BAND_2GHZ]; -+ else -+ rates = sta->supp_rates[NL80211_BAND_5GHZ] << 5; -+ pcmd->peer_info.legacy_rate_bitmap = cpu_to_le32(rates); -+ -+ if (sta->ht_cap.ht_supported) { -+ int i; -+ -+ for (i = 0; i < 4; i++) { -+ if (i < sta->rx_nss) { -+ pcmd->peer_info.ht_rates[i] = -+ sta->ht_cap.mcs.rx_mask[i]; -+ } else { -+ pcmd->peer_info.ht_rates[i] = 0; -+ } -+ } -+ pcmd->peer_info.ht_cap_info = cpu_to_le16(sta->ht_cap.cap); -+ pcmd->peer_info.mac_ht_param_info = -+ (sta->ht_cap.ampdu_factor & 3) | -+ ((sta->ht_cap.ampdu_density & 7) << 2); -+ } -+ -+ if (sta->vht_cap.vht_supported) { -+ u32 rx_mcs_map_mask = 0; -+ -+ rx_mcs_map_mask = ((0x0000FFFF) >> (sta->rx_nss * 2)) -+ << (sta->rx_nss * 2); -+ pcmd->peer_info.vht_max_rx_mcs = -+ cpu_to_le32((*((u32 *) -+ &sta->vht_cap.vht_mcs.rx_mcs_map)) | rx_mcs_map_mask); -+ pcmd->peer_info.vht_cap = cpu_to_le32(sta->vht_cap.cap); -+ pcmd->peer_info.vht_rx_channel_width = sta->bandwidth; -+ } -+ -+ pcmd->is_qos_sta = sta->wme; -+ pcmd->qos_info = ((sta->uapsd_queues << 4) | (sta->max_sp << 1)); -+ pcmd->wds = cpu_to_le32(wds); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_NEW_STN)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ if (vif->type == NL80211_IFTYPE_STATION) { -+ ether_addr_copy(pcmd->mac_addr, mwl_vif->sta_mac); -+ pcmd->aid = cpu_to_le16(sta->aid + 1); -+ pcmd->stn_id = cpu_to_le16(sta_info->sta_stnid); -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_NEW_STN)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_new_stn_wds_sc4(struct ieee80211_hw *hw, u8 *addr) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_new_stn_sc4 *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_new_stn_sc4 *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ -+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_STA_ACTION_MODIFY); -+ ether_addr_copy(pcmd->mac_addr, addr); -+ pcmd->wds = cpu_to_le32(WDS_MODE); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_NEW_STN)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_new_stn_add_self(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct hostcmd_cmd_set_new_stn *pcmd; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ -+ pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ if (priv->chip_type == MWL8964) { -+ memset(pcmd, 0x00, sizeof(struct hostcmd_cmd_set_new_stn_sc4)); -+ pcmd->cmd_hdr.len = -+ cpu_to_le16(sizeof(struct hostcmd_cmd_set_new_stn_sc4)); -+ } else { -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ } -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ -+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_STA_ACTION_ADD); -+ ether_addr_copy(pcmd->mac_addr, vif->addr); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_NEW_STN)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_new_stn_del(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, u8 *addr) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct hostcmd_cmd_set_new_stn *pcmd; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ -+ pcmd = (struct hostcmd_cmd_set_new_stn *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ if (priv->chip_type == MWL8964) { -+ memset(pcmd, 0x00, sizeof(struct hostcmd_cmd_set_new_stn_sc4)); -+ pcmd->cmd_hdr.len = -+ cpu_to_le16(sizeof(struct hostcmd_cmd_set_new_stn_sc4)); -+ } else { -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ } -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_NEW_STN); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ -+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_STA_ACTION_REMOVE); -+ ether_addr_copy(pcmd->mac_addr, addr); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_NEW_STN)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ if (vif->type == NL80211_IFTYPE_STATION) { -+ ether_addr_copy(pcmd->mac_addr, mwl_vif->sta_mac); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_NEW_STN)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_apmode(struct ieee80211_hw *hw, u8 apmode) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_apmode *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_apmode *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_APMODE); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->apmode = apmode; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_APMODE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_switch_channel(struct ieee80211_hw *hw, -+ struct ieee80211_channel_switch *ch_switch) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_switch_channel *pcmd; -+ struct cfg80211_chan_def *chandef = &ch_switch->chandef; -+ struct ieee80211_channel *channel = chandef->chan; -+ u32 chnl_flags, freq_band, chnl_width, act_primary, sec_chnl_offset; -+ -+ if (priv->csa_active) -+ return 0; -+ -+ if (channel->band == NL80211_BAND_2GHZ) -+ freq_band = FREQ_BAND_2DOT4GHZ; -+ else if (channel->band == NL80211_BAND_5GHZ) -+ freq_band = FREQ_BAND_5GHZ; -+ else -+ return -EINVAL; -+ -+ switch (chandef->width) { -+ case NL80211_CHAN_WIDTH_20_NOHT: -+ case NL80211_CHAN_WIDTH_20: -+ chnl_width = CH_20_MHZ_WIDTH; -+ act_primary = ACT_PRIMARY_CHAN_0; -+ sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; -+ break; -+ case NL80211_CHAN_WIDTH_40: -+ chnl_width = CH_40_MHZ_WIDTH; -+ if (chandef->center_freq1 > channel->center_freq) { -+ act_primary = ACT_PRIMARY_CHAN_0; -+ sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; -+ } else { -+ act_primary = ACT_PRIMARY_CHAN_1; -+ sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; -+ } -+ break; -+ case NL80211_CHAN_WIDTH_80: -+ chnl_width = CH_80_MHZ_WIDTH; -+ act_primary = -+ mwl_fwcmd_get_80m_pri_chnl(channel->hw_value); -+ if ((act_primary == ACT_PRIMARY_CHAN_0) || -+ (act_primary == ACT_PRIMARY_CHAN_2)) -+ sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; -+ else -+ sec_chnl_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ chnl_flags = (freq_band & FREQ_BAND_MASK) | -+ ((chnl_width << CHNL_WIDTH_SHIFT) & CHNL_WIDTH_MASK) | -+ ((act_primary << ACT_PRIMARY_SHIFT) & ACT_PRIMARY_MASK); -+ -+ pcmd = (struct hostcmd_cmd_set_switch_channel *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_SWITCH_CHANNEL); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->next_11h_chnl = cpu_to_le32(channel->hw_value); -+ pcmd->mode = cpu_to_le32(ch_switch->block_tx); -+ pcmd->init_count = cpu_to_le32(ch_switch->count + 1); -+ pcmd->chnl_flags = cpu_to_le32(chnl_flags); -+ pcmd->next_ht_extchnl_offset = cpu_to_le32(sec_chnl_offset); -+ pcmd->dfs_test_mode = cpu_to_le32(priv->dfs_test); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_SWITCH_CHANNEL)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ priv->csa_active = true; -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_update_encryption_enable(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ u8 *addr, u8 encr_type) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct hostcmd_cmd_update_encryption *pcmd; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ -+ pcmd = (struct hostcmd_cmd_update_encryption *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ -+ pcmd->action_type = cpu_to_le32(ENCR_ACTION_ENABLE_HW_ENCR); -+ ether_addr_copy(pcmd->mac_addr, addr); -+ pcmd->action_data[0] = encr_type; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ if ((vif->type == NL80211_IFTYPE_STATION) && -+ (priv->chip_type != MWL8964)) { -+ if (ether_addr_equal(mwl_vif->bssid, addr)) -+ ether_addr_copy(pcmd->mac_addr, mwl_vif->sta_mac); -+ else -+ ether_addr_copy(pcmd->mac_addr, mwl_vif->bssid); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_encryption_set_key(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, u8 *addr, -+ struct ieee80211_key_conf *key) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct hostcmd_cmd_set_key *pcmd; -+ int rc; -+ int keymlen; -+ u32 action; -+ u8 idx; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ -+ pcmd = (struct hostcmd_cmd_set_key *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ -+ rc = mwl_fwcmd_encryption_set_cmd_info(pcmd, addr, key); -+ if (rc) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ if (rc != 1) -+ wiphy_err(hw->wiphy, "encryption not support\n"); -+ return rc; -+ } -+ -+ idx = key->keyidx; -+ -+ if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) -+ action = ENCR_ACTION_TYPE_SET_KEY; -+ else { -+ action = ENCR_ACTION_TYPE_SET_GROUP_KEY; -+ if (vif->type == NL80211_IFTYPE_MESH_POINT && -+ !ether_addr_equal(mwl_vif->bssid, addr)) -+ pcmd->key_param.key_info |= -+ cpu_to_le32(ENCR_KEY_FLAG_RXGROUPKEY); -+ } -+ -+ switch (key->cipher) { -+ case WLAN_CIPHER_SUITE_WEP40: -+ case WLAN_CIPHER_SUITE_WEP104: -+ if (!mwl_vif->wep_key_conf[idx].enabled) { -+ memcpy(mwl_vif->wep_key_conf[idx].key, key, -+ sizeof(*key) + key->keylen); -+ mwl_vif->wep_key_conf[idx].enabled = 1; -+ } -+ -+ keymlen = key->keylen; -+ action = ENCR_ACTION_TYPE_SET_KEY; -+ break; -+ case WLAN_CIPHER_SUITE_TKIP: -+ keymlen = MAX_ENCR_KEY_LENGTH + 2 * MIC_KEY_LENGTH; -+ break; -+ case WLAN_CIPHER_SUITE_CCMP: -+ keymlen = key->keylen; -+ break; -+ default: -+ mutex_unlock(&priv->fwcmd_mutex); -+ wiphy_err(hw->wiphy, "encryption not support\n"); -+ return -ENOTSUPP; -+ } -+ -+ memcpy((void *)&pcmd->key_param.key, key->key, keymlen); -+ pcmd->action_type = cpu_to_le32(action); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ if (vif->type == NL80211_IFTYPE_STATION) { -+ if (ether_addr_equal(mwl_vif->bssid, addr)) -+ ether_addr_copy(pcmd->key_param.mac_addr, -+ mwl_vif->sta_mac); -+ else -+ ether_addr_copy(pcmd->key_param.mac_addr, -+ mwl_vif->bssid); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_encryption_remove_key(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, u8 *addr, -+ struct ieee80211_key_conf *key) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct hostcmd_cmd_set_key *pcmd; -+ int rc; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ -+ pcmd = (struct hostcmd_cmd_set_key *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_UPDATE_ENCRYPTION); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ -+ rc = mwl_fwcmd_encryption_set_cmd_info(pcmd, addr, key); -+ if (rc) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ if (rc != 1) -+ wiphy_err(hw->wiphy, "encryption not support\n"); -+ return rc; -+ } -+ -+ pcmd->action_type = cpu_to_le32(ENCR_ACTION_TYPE_REMOVE_KEY); -+ -+ if (key->cipher == WLAN_CIPHER_SUITE_WEP40 || -+ key->cipher == WLAN_CIPHER_SUITE_WEP104) -+ mwl_vif->wep_key_conf[key->keyidx].enabled = 0; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_UPDATE_ENCRYPTION)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_check_ba(struct ieee80211_hw *hw, -+ struct mwl_ampdu_stream *stream, -+ struct ieee80211_vif *vif, -+ u32 direction) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct hostcmd_cmd_bastream *pcmd; -+ u32 ba_flags, ba_type, ba_direction; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ -+ pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BASTREAM); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ pcmd->cmd_hdr.result = cpu_to_le16(0xffff); -+ -+ pcmd->action_type = cpu_to_le32(BA_CHECK_STREAM); -+ ether_addr_copy(&pcmd->ba_info.create_params.peer_mac_addr[0], -+ stream->sta->addr); -+ pcmd->ba_info.create_params.tid = stream->tid; -+ ba_type = BA_FLAG_IMMEDIATE_TYPE; -+ ba_direction = direction; -+ ba_flags = (ba_type & BA_TYPE_MASK) | -+ ((ba_direction << BA_DIRECTION_SHIFT) & BA_DIRECTION_MASK); -+ pcmd->ba_info.create_params.flags = cpu_to_le32(ba_flags); -+ pcmd->ba_info.create_params.queue_id = stream->idx; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_BASTREAM)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ if (pcmd->cmd_hdr.result != 0) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EINVAL; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_create_ba(struct ieee80211_hw *hw, -+ struct mwl_ampdu_stream *stream, -+ struct ieee80211_vif *vif, -+ u32 direction, u8 buf_size, u16 seqno, bool amsdu) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ struct hostcmd_cmd_bastream *pcmd; -+ u32 ba_flags, ba_type, ba_direction; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ -+ pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BASTREAM); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_hdr.macid = mwl_vif->macid; -+ pcmd->cmd_hdr.result = cpu_to_le16(0xffff); -+ -+ pcmd->action_type = cpu_to_le32(BA_CREATE_STREAM); -+ pcmd->ba_info.create_params.bar_thrs = cpu_to_le32(buf_size); -+ pcmd->ba_info.create_params.window_size = cpu_to_le32(buf_size); -+ pcmd->ba_info.create_params.idle_thrs = cpu_to_le32(0x22000); -+ ether_addr_copy(&pcmd->ba_info.create_params.peer_mac_addr[0], -+ stream->sta->addr); -+ pcmd->ba_info.create_params.tid = stream->tid; -+ ba_direction = direction; -+ if (priv->chip_type == MWL8964) { -+ ba_type = amsdu ? MWL_AMSDU_SIZE_11K : 0; -+ ba_flags = (ba_type & BA_TYPE_MASK_NDP) | -+ ((ba_direction << BA_DIRECTION_SHIFT_NDP) & -+ BA_DIRECTION_MASK_NDP); -+ } else { -+ ba_type = BA_FLAG_IMMEDIATE_TYPE; -+ ba_flags = (ba_type & BA_TYPE_MASK) | -+ ((ba_direction << BA_DIRECTION_SHIFT) & -+ BA_DIRECTION_MASK); -+ } -+ pcmd->ba_info.create_params.flags = cpu_to_le32(ba_flags); -+ pcmd->ba_info.create_params.queue_id = stream->idx; -+ pcmd->ba_info.create_params.param_info = -+ (stream->sta->ht_cap.ampdu_factor & -+ IEEE80211_HT_AMPDU_PARM_FACTOR) | -+ ((stream->sta->ht_cap.ampdu_density << 2) & -+ IEEE80211_HT_AMPDU_PARM_DENSITY); -+ if (direction == BA_FLAG_DIRECTION_UP) { -+ pcmd->ba_info.create_params.reset_seq_no = 0; -+ pcmd->ba_info.create_params.current_seq = cpu_to_le16(seqno); -+ } else { -+ pcmd->ba_info.create_params.reset_seq_no = 1; -+ pcmd->ba_info.create_params.current_seq = cpu_to_le16(0); -+ } -+ if (priv->chip_type == MWL8964 && -+ stream->sta->vht_cap.vht_supported) { -+ pcmd->ba_info.create_params.vht_rx_factor = -+ cpu_to_le32((stream->sta->vht_cap.cap & -+ IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK) >> -+ IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT); -+ } -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_BASTREAM)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ if (pcmd->cmd_hdr.result != 0) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ wiphy_err(hw->wiphy, "create ba result error %d\n", -+ le16_to_cpu(pcmd->cmd_hdr.result)); -+ return -EINVAL; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_destroy_ba(struct ieee80211_hw *hw, -+ struct mwl_ampdu_stream *stream, -+ u32 direction) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_bastream *pcmd; -+ u32 ba_flags, ba_type, ba_direction; -+ -+ pcmd = (struct hostcmd_cmd_bastream *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_BASTREAM); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ -+ pcmd->action_type = cpu_to_le32(BA_DESTROY_STREAM); -+ ba_type = 0; -+ ba_direction = direction; -+ if (priv->chip_type == MWL8964) -+ ba_flags = (ba_type & BA_TYPE_MASK_NDP) | -+ ((ba_direction << BA_DIRECTION_SHIFT_NDP) & -+ BA_DIRECTION_MASK_NDP); -+ else -+ ba_flags = (ba_type & BA_TYPE_MASK) | -+ ((ba_direction << BA_DIRECTION_SHIFT) & -+ BA_DIRECTION_MASK); -+ pcmd->ba_info.destroy_params.flags = cpu_to_le32(ba_flags); -+ pcmd->ba_info.destroy_params.fw_ba_context.context = -+ cpu_to_le32(stream->idx); -+ pcmd->ba_info.destroy_params.tid = stream->tid; -+ ether_addr_copy(&pcmd->ba_info.destroy_params.peer_mac_addr[0], -+ stream->sta->addr); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_BASTREAM)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+/* caller must hold priv->stream_lock when calling the stream functions */ -+struct mwl_ampdu_stream *mwl_fwcmd_add_stream(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta, -+ u8 tid) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_ampdu_stream *stream; -+ struct mwl_sta *sta_info = mwl_dev_get_sta(sta); -+ int idx; -+ -+ if (priv->chip_type == MWL8964) { -+ idx = ((sta_info->stnid - 1) * SYSADPT_MAX_TID) + tid; -+ -+ if (idx < priv->ampdu_num) { -+ stream = &priv->ampdu[idx]; -+ stream->sta = sta; -+ stream->state = AMPDU_STREAM_NEW; -+ stream->tid = tid; -+ stream->idx = idx; -+ return stream; -+ } -+ } else { -+ for (idx = 0; idx < priv->ampdu_num; idx++) { -+ stream = &priv->ampdu[idx]; -+ -+ if (stream->state == AMPDU_NO_STREAM) { -+ stream->sta = sta; -+ stream->state = AMPDU_STREAM_NEW; -+ stream->tid = tid; -+ stream->idx = idx; -+ return stream; -+ } -+ } -+ } -+ -+ return NULL; -+} -+ -+void mwl_fwcmd_del_sta_streams(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_ampdu_stream *stream; -+ struct mwl_sta *sta_info = mwl_dev_get_sta(sta); -+ int i, idx; -+ -+ spin_lock_bh(&priv->stream_lock); -+ if (priv->chip_type == MWL8964) { -+ idx = (sta_info->stnid - 1) * SYSADPT_MAX_TID; -+ for (i = 0; i < SYSADPT_MAX_TID; i++) { -+ stream = &priv->ampdu[idx + i]; -+ -+ if (stream->sta == sta) { -+ spin_unlock_bh(&priv->stream_lock); -+ mwl_fwcmd_destroy_ba(hw, stream, -+ BA_FLAG_DIRECTION_UP); -+ spin_lock_bh(&priv->stream_lock); -+ mwl_fwcmd_remove_stream(hw, stream); -+ } -+ } -+ } else { -+ for (idx = 0; idx < priv->ampdu_num; idx++) { -+ stream = &priv->ampdu[idx]; -+ -+ if (stream->sta == sta) { -+ spin_unlock_bh(&priv->stream_lock); -+ mwl_fwcmd_destroy_ba(hw, stream, -+ BA_FLAG_DIRECTION_UP); -+ spin_lock_bh(&priv->stream_lock); -+ mwl_fwcmd_remove_stream(hw, stream); -+ } -+ } -+ } -+ spin_unlock_bh(&priv->stream_lock); -+} -+ -+int mwl_fwcmd_start_stream(struct ieee80211_hw *hw, -+ struct mwl_ampdu_stream *stream) -+{ -+ /* if the stream has already been started, don't start it again */ -+ if (stream->state != AMPDU_STREAM_NEW) -+ return 0; -+ -+ return ieee80211_start_tx_ba_session(stream->sta, stream->tid, 0); -+} -+ -+void mwl_fwcmd_remove_stream(struct ieee80211_hw *hw, -+ struct mwl_ampdu_stream *stream) -+{ -+ memset(stream, 0, sizeof(*stream)); -+} -+ -+struct mwl_ampdu_stream *mwl_fwcmd_lookup_stream(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta, -+ u8 tid) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_ampdu_stream *stream; -+ struct mwl_sta *sta_info = mwl_dev_get_sta(sta); -+ int idx; -+ -+ if (priv->chip_type == MWL8964) { -+ idx = ((sta_info->stnid - 1) * SYSADPT_MAX_TID) + tid; -+ if (idx < priv->ampdu_num) -+ return &priv->ampdu[idx]; -+ } else { -+ for (idx = 0; idx < priv->ampdu_num; idx++) { -+ stream = &priv->ampdu[idx]; -+ if (stream->state == AMPDU_NO_STREAM) -+ continue; -+ -+ if ((stream->sta == sta) && (stream->tid == tid)) -+ return stream; -+ } -+ } -+ -+ return NULL; -+} -+ -+bool mwl_fwcmd_ampdu_allowed(struct ieee80211_sta *sta, u8 tid) -+{ -+ struct mwl_sta *sta_info; -+ struct mwl_tx_info *tx_stats; -+ -+ if (WARN_ON(tid >= SYSADPT_MAX_TID)) -+ return false; -+ -+ sta_info = mwl_dev_get_sta(sta); -+ -+ tx_stats = &sta_info->tx_stats[tid]; -+ -+ return (sta_info->is_ampdu_allowed && -+ tx_stats->pkts > SYSADPT_AMPDU_PACKET_THRESHOLD); -+} -+ -+int mwl_fwcmd_set_optimization_level(struct ieee80211_hw *hw, u8 opt_level) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_optimization_level *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_optimization_level *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->opt_level = opt_level; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_wsc_ie(struct ieee80211_hw *hw, u8 len, u8 *data) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_wsc_ie *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_wsc_ie *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_WSC_IE); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->len = cpu_to_le16(len); -+ memcpy(pcmd->data, data, len); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_WSC_IE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ pcmd->ie_type = cpu_to_le16(WSC_IE_SET_PROBE_RESPONSE); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_WSC_IE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_get_ratetable(struct ieee80211_hw *hw, u8 *addr, u8 *rate_table, -+ u32 size, u8 type) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_get_ratetable *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_get_ratetable *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_RATETABLE); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->type = type; -+ ether_addr_copy(pcmd->addr, addr); -+ memset(rate_table, 0x00, size); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_GET_RATETABLE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ memcpy(rate_table, &pcmd->sorted_rates_idx_map, size); -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_get_seqno(struct ieee80211_hw *hw, -+ struct mwl_ampdu_stream *stream, u16 *start_seqno) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_get_seqno *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_get_seqno *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_SEQNO); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ ether_addr_copy(pcmd->mac_addr, stream->sta->addr); -+ pcmd->tid = stream->tid; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_GET_SEQNO)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ *start_seqno = le16_to_cpu(pcmd->seq_no); -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_dwds_stamode(struct ieee80211_hw *hw, bool enable) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_dwds_enable *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_dwds_enable *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_DWDS_ENABLE); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->enable = cpu_to_le32(enable); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_DWDS_ENABLE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_fw_flush_timer(struct ieee80211_hw *hw, u32 value) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_fw_flush_timer *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_fw_flush_timer *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_FW_FLUSH_TIMER); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->value = cpu_to_le32(value); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_FW_FLUSH_TIMER)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_cdd(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_cdd *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_cdd *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_CDD); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->enable = cpu_to_le32(priv->cdd); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_CDD)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_bftype(struct ieee80211_hw *hw, int mode) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_bftype *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_bftype *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_BFTYPE); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->action = cpu_to_le32(WL_SET); -+ pcmd->mode = cpu_to_le32(mode); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_BFTYPE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_reg_cau(struct ieee80211_hw *hw, u8 flag, u32 reg, u32 *val) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_bbp_reg_access *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_bbp_reg_access *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_CAU_REG_ACCESS); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->offset = cpu_to_le16(reg); -+ pcmd->action = cpu_to_le16(flag); -+ pcmd->value = *val; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_CAU_REG_ACCESS)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ *val = pcmd->value; -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_get_temp(struct ieee80211_hw *hw, u32 *temp) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_get_temp *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_get_temp *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_TEMP); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_GET_TEMP)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ *temp = le32_to_cpu(pcmd->celcius); -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_led_ctrl(struct ieee80211_hw *hw, u8 enable, u8 rate) -+{ -+ struct hostcmd_cmd_led_ctrl *pcmd; -+ struct mwl_priv *priv = hw->priv; -+ -+ pcmd = (struct hostcmd_cmd_led_ctrl *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_LED_CTRL); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->action = 1; /* 1: set */ -+ pcmd->led_enable = enable; -+ pcmd->led_control = 1; /* 1: SW */ -+ -+ switch (rate) { -+ case LED_BLINK_RATE_LOW: -+ case LED_BLINK_RATE_MID: -+ case LED_BLINK_RATE_HIGH: -+ pcmd->led_blink_rate = rate; -+ break; -+ default: -+ if (enable) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EINVAL; -+ } -+ break; -+ } -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_LED_CTRL)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_get_fw_region_code(struct ieee80211_hw *hw, -+ u32 *fw_region_code) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_get_fw_region_code *pcmd; -+ u16 cmd; -+ int status; -+ -+ pcmd = (struct hostcmd_cmd_get_fw_region_code *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ cmd = HOSTCMD_CMD_GET_FW_REGION_CODE; -+ pcmd->cmd_hdr.cmd = cpu_to_le16(cmd); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ -+ if (mwl_hif_exec_cmd(hw, cmd)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ if (pcmd->cmd_hdr.result != 0) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EINVAL; -+ } -+ -+ status = le32_to_cpu(pcmd->status); -+ -+ if (!status) -+ *fw_region_code = le32_to_cpu(pcmd->fw_region_code); -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_get_device_pwr_tbl(struct ieee80211_hw *hw, -+ struct mwl_device_pwr_tbl *device_ch_pwrtbl, -+ u8 *region_code, -+ u8 *number_of_channels, -+ u32 channel_index) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_get_device_pwr_tbl *pcmd; -+ int status; -+ u16 cmd; -+ -+ pcmd = (struct hostcmd_cmd_get_device_pwr_tbl *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ cmd = HOSTCMD_CMD_GET_DEVICE_PWR_TBL; -+ pcmd->cmd_hdr.cmd = cpu_to_le16(cmd); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->status = cpu_to_le16(cmd); -+ pcmd->current_channel_index = cpu_to_le32(channel_index); -+ -+ if (mwl_hif_exec_cmd(hw, cmd)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ device_ch_pwrtbl->channel = pcmd->channel_pwr_tbl.channel; -+ memcpy(device_ch_pwrtbl->tx_pwr, pcmd->channel_pwr_tbl.tx_pwr, -+ priv->pwr_level); -+ device_ch_pwrtbl->dfs_capable = pcmd->channel_pwr_tbl.dfs_capable; -+ device_ch_pwrtbl->ax_ant = pcmd->channel_pwr_tbl.ax_ant; -+ device_ch_pwrtbl->cdd = pcmd->channel_pwr_tbl.cdd; -+ *region_code = pcmd->region_code; -+ *number_of_channels = pcmd->number_of_channels; -+ status = le16_to_cpu(pcmd->status); -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return status; -+} -+ -+int mwl_fwcmd_set_rate_drop(struct ieee80211_hw *hw, int enable, -+ int value, int staid) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_set_rate_drop *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_set_rate_drop *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_SET_RATE_DROP); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->enable = cpu_to_le32(enable); -+ pcmd->rate_index = cpu_to_le32(value); -+ pcmd->sta_index = cpu_to_le32(staid); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_SET_RATE_DROP)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_newdp_dmathread_start(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_newdp_dmathread_start *pcmd; -+ u16 cmd; -+ -+ pcmd = (struct hostcmd_cmd_newdp_dmathread_start *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ cmd = HOSTCMD_CMD_NEWDP_DMATHREAD_START; -+ pcmd->cmd_hdr.cmd = cpu_to_le16(cmd); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ -+ if (mwl_hif_exec_cmd(hw, cmd)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+ -+int mwl_fwcmd_get_fw_region_code_sc4(struct ieee80211_hw *hw, -+ u32 *fw_region_code) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_get_fw_region_code_sc4 *pcmd; -+ u16 cmd; -+ -+ pcmd = (struct hostcmd_cmd_get_fw_region_code_sc4 *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ cmd = HOSTCMD_CMD_GET_FW_REGION_CODE_SC4; -+ pcmd->cmd_hdr.cmd = cpu_to_le16(cmd); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ -+ if (mwl_hif_exec_cmd(hw, cmd)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ if (pcmd->cmd_hdr.result != 0) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EINVAL; -+ } -+ -+ if (pcmd->status) -+ *fw_region_code = (pcmd->status == 1) ? 0 : pcmd->status; -+ else -+ *fw_region_code = le32_to_cpu(pcmd->fw_region_code); -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_get_pwr_tbl_sc4(struct ieee80211_hw *hw, -+ struct mwl_device_pwr_tbl *device_ch_pwrtbl, -+ u8 *region_code, -+ u8 *number_of_channels, -+ u32 channel_index) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_get_device_pwr_tbl_sc4 *pcmd; -+ int status; -+ u16 cmd; -+ -+ pcmd = (struct hostcmd_cmd_get_device_pwr_tbl_sc4 *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ cmd = HOSTCMD_CMD_GET_DEVICE_PWR_TBL_SC4; -+ pcmd->cmd_hdr.cmd = cpu_to_le16(cmd); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->status = cpu_to_le16(cmd); -+ pcmd->current_channel_index = cpu_to_le32(channel_index); -+ -+ if (mwl_hif_exec_cmd(hw, cmd)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ device_ch_pwrtbl->channel = pcmd->channel_pwr_tbl.channel; -+ memcpy(device_ch_pwrtbl->tx_pwr, pcmd->channel_pwr_tbl.tx_pwr, -+ SYSADPT_TX_PWR_LEVEL_TOTAL_SC4); -+ device_ch_pwrtbl->dfs_capable = pcmd->channel_pwr_tbl.dfs_capable; -+ device_ch_pwrtbl->ax_ant = pcmd->channel_pwr_tbl.ax_ant; -+ device_ch_pwrtbl->cdd = pcmd->channel_pwr_tbl.cdd; -+ *region_code = pcmd->region_code; -+ *number_of_channels = pcmd->number_of_channels; -+ status = le16_to_cpu(pcmd->status); -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return status; -+} -+ -+int mwl_fwcmd_quiet_mode(struct ieee80211_hw *hw, bool enable, u32 period, -+ u32 duration, u32 next_offset) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_quiet_mode *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_quiet_mode *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_QUIET_MODE); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->action = cpu_to_le16(WL_SET); -+ pcmd->enable = cpu_to_le32(enable); -+ if (enable) { -+ pcmd->period = cpu_to_le32(period); -+ pcmd->duration = cpu_to_le32(duration); -+ pcmd->next_offset = cpu_to_le32(next_offset); -+ } -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_QUIET_MODE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_core_dump_diag_mode(struct ieee80211_hw *hw, u16 status) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_core_dump_diag_mode *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_core_dump_diag_mode *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_CORE_DUMP_DIAG_MODE); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->status = cpu_to_le16(status); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_CORE_DUMP_DIAG_MODE)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_get_fw_core_dump(struct ieee80211_hw *hw, -+ struct coredump_cmd *core_dump, char *buff) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_get_fw_core_dump *pcmd; -+ -+ if (priv->chip_type != MWL8964) -+ return -EPERM; -+ -+ pcmd = (struct hostcmd_cmd_get_fw_core_dump *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_GET_FW_CORE_DUMP); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->cmd_data.coredump.context = core_dump->context; -+ pcmd->cmd_data.coredump.buffer = cpu_to_le32(priv->pphys_cmd_buf + -+ sizeof(struct hostcmd_cmd_get_fw_core_dump) - -+ sizeof(struct hostcmd_cmd_get_fw_core_dump_)); -+ pcmd->cmd_data.coredump.buffer_len = cpu_to_le32(MAX_CORE_DUMP_BUFFER); -+ pcmd->cmd_data.coredump.size_kb = core_dump->size_kb; -+ pcmd->cmd_data.coredump.flags = core_dump->flags; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_GET_FW_CORE_DUMP)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ /* update core dump buffer */ -+ core_dump->context = pcmd->cmd_data.coredump.context; -+ core_dump->size_kb = pcmd->cmd_data.coredump.size_kb; -+ core_dump->flags = pcmd->cmd_data.coredump.flags; -+ memcpy(buff, -+ (const void *)((u32)pcmd + -+ sizeof(struct hostcmd_cmd_get_fw_core_dump) - -+ sizeof(struct hostcmd_cmd_get_fw_core_dump_)), -+ MAX_CORE_DUMP_BUFFER); -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_slot_time(struct ieee80211_hw *hw, bool short_slot) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_802_11_slot_time *pcmd; -+ -+ wiphy_debug(priv->hw->wiphy, "%s(): short_slot_time=%d\n", -+ __func__, short_slot); -+ -+ pcmd = (struct hostcmd_cmd_802_11_slot_time *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_802_11_SLOT_TIME); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->action = cpu_to_le16(WL_SET); -+ pcmd->short_slot = cpu_to_le16(short_slot ? 1 : 0); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_802_11_SLOT_TIME)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_config_EDMACCtrl(struct ieee80211_hw *hw, int EDMAC_Ctrl) -+{ -+ struct hostcmd_cmd_edmac_ctrl *pcmd; -+ struct mwl_priv *priv = hw->priv; -+ -+ pcmd = (struct hostcmd_cmd_edmac_ctrl *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_EDMAC_CTRL); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->action = cpu_to_le16(WL_SET); -+ pcmd->ed_ctrl_2g = cpu_to_le16((EDMAC_Ctrl & EDMAC_2G_ENABLE_MASK) -+ >> EDMAC_2G_ENABLE_SHIFT); -+ pcmd->ed_ctrl_5g = cpu_to_le16((EDMAC_Ctrl & EDMAC_5G_ENABLE_MASK) -+ >> EDMAC_5G_ENABLE_SHIFT); -+ pcmd->ed_offset_2g = cpu_to_le16((EDMAC_Ctrl & -+ EDMAC_2G_THRESHOLD_OFFSET_MASK) -+ >> EDMAC_2G_THRESHOLD_OFFSET_SHIFT); -+ pcmd->ed_offset_5g = cpu_to_le16((EDMAC_Ctrl & -+ EDMAC_5G_THRESHOLD_OFFSET_MASK) -+ >> EDMAC_5G_THRESHOLD_OFFSET_SHIFT); -+ pcmd->ed_bitmap_txq_lock = cpu_to_le16((EDMAC_Ctrl & -+ EDMAC_QLOCK_BITMAP_MASK) -+ >> EDMAC_QLOCK_BITMAP_SHIFT); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_EDMAC_CTRL)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+int mwl_fwcmd_set_txpwrlmt_cfg_data(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_txpwrlmt_cfg *pcmd; -+ struct mwl_txpwrlmt_cfg_entry_hdr hdr; -+ u16 id, parsed_len, size; -+ __le32 txpwr_cfg_sig; -+ u8 version[TXPWRLMT_CFG_VERSION_INFO_LEN]; -+ const u8 *ptr; -+ -+ if (!priv->txpwrlmt_file) -+ return 0; -+ -+ ptr = priv->txpwrlmt_file->data; -+ size = priv->txpwrlmt_file->size; -+ -+ /* Parsing TxPwrLmit Conf file Signature */ -+ parsed_len = mwl_fwcmd_parse_txpwrlmt_cfg(ptr, size, -+ TXPWRLMT_CFG_SIG_LEN, -+ (u8 *)&txpwr_cfg_sig); -+ ptr += parsed_len; -+ size -= parsed_len; -+ -+ if (le32_to_cpu(txpwr_cfg_sig) != TXPWRLMT_CFG_SIGNATURE) { -+ wiphy_err(hw->wiphy, -+ "txpwrlmt config signature mismatch\n"); -+ release_firmware(priv->txpwrlmt_file); -+ priv->txpwrlmt_file = NULL; -+ return 0; -+ } -+ -+ /* Parsing TxPwrLmit Conf file Version */ -+ parsed_len = mwl_fwcmd_parse_txpwrlmt_cfg(ptr, size, -+ TXPWRLMT_CFG_VERSION_INFO_LEN, -+ version); -+ ptr += parsed_len; -+ size -= parsed_len; -+ -+ for (id = 0; id < TXPWRLMT_CFG_MAX_SUBBAND_INFO; id++) { -+ u16 data_len; -+ -+ /*Parsing tx pwr cfg subband header info*/ -+ parsed_len = sizeof(struct mwl_txpwrlmt_cfg_entry_hdr); -+ parsed_len = mwl_fwcmd_parse_txpwrlmt_cfg(ptr, size, -+ parsed_len, -+ (u8 *)&hdr); -+ ptr += parsed_len; -+ size -= parsed_len; -+ data_len = le16_to_cpu(hdr.len) - -+ sizeof(struct mwl_txpwrlmt_cfg_entry_hdr); -+ -+ pcmd = (struct hostcmd_cmd_txpwrlmt_cfg *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->action = cpu_to_le16(HOSTCMD_ACT_GEN_SET); -+ pcmd->subband_id = hdr.id; -+ pcmd->data_len = cpu_to_le16(data_len); -+ pcmd->num_entries = hdr.num_entries; -+ -+ /* Parsing tx pwr cfg subband header info */ -+ parsed_len = mwl_fwcmd_parse_txpwrlmt_cfg(ptr, size, -+ data_len, pcmd->data); -+ ptr += parsed_len; -+ size -= parsed_len; -+ -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_TXPWRLMT_CFG); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd) + -+ data_len - sizeof(pcmd->data)); -+ -+ if (size < sizeof(struct mwl_txpwrlmt_cfg_entry_hdr)) -+ pcmd->cfgComplete = 1; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_TXPWRLMT_CFG)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ release_firmware(priv->txpwrlmt_file); -+ priv->txpwrlmt_file = NULL; -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ } -+ -+ release_firmware(priv->txpwrlmt_file); -+ priv->txpwrlmt_file = NULL; -+ -+ return 0; -+} -+ -+int mwl_fwcmd_get_txpwrlmt_cfg_data(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_txpwrlmt_cfg *pcmd; -+ u16 subband_len, total_len = 0; -+ u8 id; -+ -+ for (id = 0; id < TXPWRLMT_CFG_MAX_SUBBAND_INFO; id++) { -+ pcmd = (struct hostcmd_cmd_txpwrlmt_cfg *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->action = 0; -+ pcmd->subband_id = id; -+ pcmd->data_len = 0; -+ pcmd->num_entries = 0; -+ -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_TXPWRLMT_CFG); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_TXPWRLMT_CFG)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ subband_len = le16_to_cpu(pcmd->cmd_hdr.len) - -+ sizeof(struct hostcmd_header) - 2; -+ if (total_len <= SYSADPT_TXPWRLMT_CFG_BUF_SIZE) { -+ wiphy_debug(hw->wiphy, "Subband len = %d\n", -+ subband_len); -+ memcpy(priv->txpwrlmt_data.buf + total_len, -+ &pcmd->subband_id, subband_len); -+ total_len += subband_len; -+ priv->txpwrlmt_data.buf[total_len] = '\n'; -+ total_len++; -+ priv->txpwrlmt_data.len = total_len; -+ } else { -+ wiphy_err(hw->wiphy, -+ "TxPwrLmt cfg buf size is not enough\n"); -+ } -+ } -+ -+ return 0; -+} -+ -+int mwl_fwcmd_mcast_cts(struct ieee80211_hw *hw, u8 enable) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct hostcmd_cmd_mcast_cts *pcmd; -+ -+ pcmd = (struct hostcmd_cmd_mcast_cts *)&priv->pcmd_buf[0]; -+ -+ mutex_lock(&priv->fwcmd_mutex); -+ -+ memset(pcmd, 0x00, sizeof(*pcmd)); -+ pcmd->cmd_hdr.cmd = cpu_to_le16(HOSTCMD_CMD_MCAST_CTS); -+ pcmd->cmd_hdr.len = cpu_to_le16(sizeof(*pcmd)); -+ pcmd->enable = enable; -+ -+ if (mwl_hif_exec_cmd(hw, HOSTCMD_CMD_MCAST_CTS)) { -+ mutex_unlock(&priv->fwcmd_mutex); -+ return -EIO; -+ } -+ -+ mutex_unlock(&priv->fwcmd_mutex); -+ -+ return 0; -+} -+ -+void mwl_fwcmd_get_survey(struct ieee80211_hw *hw, int idx) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct ieee80211_conf *conf = &hw->conf; -+ struct mwl_survey_info *survey_info; -+ -+ if (idx) -+ survey_info = &priv->survey_info[idx - 1]; -+ else -+ survey_info = &priv->cur_survey_info; -+ -+ memcpy(&survey_info->channel, conf->chandef.chan, -+ sizeof(struct ieee80211_channel)); -+ mwl_hif_get_survey(hw, survey_info); -+} -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/fwcmd.h b/drivers/net/wireless/marvell/mwlwifi/hif/fwcmd.h -new file mode 100644 -index 000000000000..9565cc447dc6 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/fwcmd.h -@@ -0,0 +1,285 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines firmware host command related -+ * functions. -+ */ -+ -+#ifndef _FWCMD_H_ -+#define _FWCMD_H_ -+ -+#include "hif/hostcmd.h" -+ -+/* Define OpMode for SoftAP/Station mode -+ * -+ * The following mode signature has to be written to PCI scratch register#0 -+ * right after successfully downloading the last block of firmware and -+ * before waiting for firmware ready signature -+ */ -+ -+#define HOSTCMD_STA_MODE 0x5A -+#define HOSTCMD_SOFTAP_MODE 0xA5 -+ -+#define HOSTCMD_STA_FWRDY_SIGNATURE 0xF0F1F2F4 -+#define HOSTCMD_SOFTAP_FWRDY_SIGNATURE 0xF1F2F4A5 -+ -+#define GUARD_INTERVAL_STANDARD 1 -+#define GUARD_INTERVAL_SHORT 2 -+#define GUARD_INTERVAL_AUTO 3 -+ -+#define LINK_CS_STATE_CONSERV 0 -+#define LINK_CS_STATE_AGGR 1 -+#define LINK_CS_STATE_AUTO 2 -+#define LINK_CS_STATE_AUTO_DISABLED 3 -+ -+#define STOP_DETECT_RADAR 0 -+#define CAC_START 1 -+#define MONITOR_START 3 -+ -+#define WDS_MODE 4 -+ -+enum { -+ WL_ANTENNATYPE_RX = 1, -+ WL_ANTENNATYPE_TX = 2, -+}; -+ -+enum encr_type { -+ ENCR_TYPE_WEP = 0, -+ ENCR_TYPE_DISABLE = 1, -+ ENCR_TYPE_TKIP = 4, -+ ENCR_TYPE_AES = 6, -+ ENCR_TYPE_MIX = 7, -+}; -+ -+char *mwl_fwcmd_get_cmd_string(unsigned short cmd); -+ -+const struct hostcmd_get_hw_spec -+*mwl_fwcmd_get_hw_specs(struct ieee80211_hw *hw); -+ -+int mwl_fwcmd_set_hw_specs(struct ieee80211_hw *hw, -+ struct hostcmd_set_hw_spec *spec); -+ -+int mwl_fwcmd_get_stat(struct ieee80211_hw *hw, -+ struct ieee80211_low_level_stats *stats); -+ -+int mwl_fwcmd_reg_bb(struct ieee80211_hw *hw, u8 flag, u32 reg, u32 *val); -+ -+int mwl_fwcmd_reg_rf(struct ieee80211_hw *hw, u8 flag, u32 reg, u32 *val); -+ -+int mwl_fwcmd_radio_enable(struct ieee80211_hw *hw); -+ -+int mwl_fwcmd_radio_disable(struct ieee80211_hw *hw); -+ -+int mwl_fwcmd_set_radio_preamble(struct ieee80211_hw *hw, -+ bool short_preamble); -+ -+int mwl_fwcmd_get_addr_value(struct ieee80211_hw *hw, u32 addr, u32 len, -+ u32 *val, u16 set); -+ -+int mwl_fwcmd_max_tx_power(struct ieee80211_hw *hw, -+ struct ieee80211_conf *conf, u8 fraction); -+ -+int mwl_fwcmd_tx_power(struct ieee80211_hw *hw, -+ struct ieee80211_conf *conf, u8 fraction); -+ -+int mwl_fwcmd_rf_antenna(struct ieee80211_hw *hw, int dir, int antenna); -+ -+int mwl_fwcmd_broadcast_ssid_enable(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, bool enable); -+ -+int mwl_fwcmd_set_cfg_data(struct ieee80211_hw *hw, u16 type); -+ -+int mwl_fwcmd_set_rf_channel(struct ieee80211_hw *hw, -+ struct ieee80211_conf *conf); -+ -+int mwl_fwcmd_set_aid(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, u8 *bssid, u16 aid); -+ -+int mwl_fwcmd_set_infra_mode(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif); -+ -+int mwl_fwcmd_set_rts_threshold(struct ieee80211_hw *hw, -+ int threshold); -+ -+int mwl_fwcmd_set_edca_params(struct ieee80211_hw *hw, u8 index, -+ u16 cw_min, u16 cw_max, u8 aifs, u16 txop); -+ -+int mwl_fwcmd_set_radar_detect(struct ieee80211_hw *hw, u16 action); -+ -+int mwl_fwcmd_set_wmm_mode(struct ieee80211_hw *hw, bool enable); -+ -+int mwl_fwcmd_ht_guard_interval(struct ieee80211_hw *hw, u32 gi_type); -+ -+int mwl_fwcmd_use_fixed_rate(struct ieee80211_hw *hw, -+ int mcast, int mgmt); -+ -+int mwl_fwcmd_set_linkadapt_cs_mode(struct ieee80211_hw *hw, -+ u16 cs_mode); -+ -+int mwl_fwcmd_dump_otp_data(struct ieee80211_hw *hw); -+ -+int mwl_fwcmd_set_rate_adapt_mode(struct ieee80211_hw *hw, -+ u16 mode); -+ -+int mwl_fwcmd_set_mac_addr_client(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, u8 *mac_addr); -+ -+int mwl_fwcmd_get_watchdog_bitmap(struct ieee80211_hw *hw, -+ u8 *bitmap); -+ -+int mwl_fwcmd_remove_mac_addr(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, u8 *mac_addr); -+ -+int mwl_fwcmd_bss_start(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, bool enable); -+ -+int mwl_fwcmd_set_beacon(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, u8 *beacon, int len); -+ -+int mwl_fwcmd_set_new_stn_add(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ struct ieee80211_sta *sta); -+ -+int mwl_fwcmd_set_new_stn_add_sc4(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ struct ieee80211_sta *sta, -+ u32 wds); -+ -+int mwl_fwcmd_set_new_stn_wds_sc4(struct ieee80211_hw *hw, u8 *addr); -+ -+int mwl_fwcmd_set_new_stn_add_self(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif); -+ -+int mwl_fwcmd_set_new_stn_del(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, u8 *addr); -+ -+int mwl_fwcmd_set_apmode(struct ieee80211_hw *hw, u8 apmode); -+ -+int mwl_fwcmd_set_switch_channel(struct ieee80211_hw *hw, -+ struct ieee80211_channel_switch *ch_switch); -+ -+int mwl_fwcmd_update_encryption_enable(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ u8 *addr, u8 encr_type); -+ -+int mwl_fwcmd_encryption_set_key(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, u8 *addr, -+ struct ieee80211_key_conf *key); -+ -+int mwl_fwcmd_encryption_remove_key(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, u8 *addr, -+ struct ieee80211_key_conf *key); -+ -+int mwl_fwcmd_check_ba(struct ieee80211_hw *hw, -+ struct mwl_ampdu_stream *stream, -+ struct ieee80211_vif *vif, -+ u32 direction); -+ -+int mwl_fwcmd_create_ba(struct ieee80211_hw *hw, -+ struct mwl_ampdu_stream *stream, -+ struct ieee80211_vif *vif, -+ u32 direction, u8 buf_size, u16 seqno, bool amsdu); -+ -+int mwl_fwcmd_destroy_ba(struct ieee80211_hw *hw, -+ struct mwl_ampdu_stream *stream, -+ u32 direction); -+ -+struct mwl_ampdu_stream *mwl_fwcmd_add_stream(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta, -+ u8 tid); -+ -+void mwl_fwcmd_del_sta_streams(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta); -+ -+int mwl_fwcmd_start_stream(struct ieee80211_hw *hw, -+ struct mwl_ampdu_stream *stream); -+ -+void mwl_fwcmd_remove_stream(struct ieee80211_hw *hw, -+ struct mwl_ampdu_stream *stream); -+ -+struct mwl_ampdu_stream *mwl_fwcmd_lookup_stream(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta, -+ u8 tid); -+ -+bool mwl_fwcmd_ampdu_allowed(struct ieee80211_sta *sta, u8 tid); -+ -+int mwl_fwcmd_set_optimization_level(struct ieee80211_hw *hw, u8 opt_level); -+ -+int mwl_fwcmd_set_wsc_ie(struct ieee80211_hw *hw, u8 len, u8 *data); -+ -+int mwl_fwcmd_get_ratetable(struct ieee80211_hw *hw, u8 *addr, u8 *rate_table, -+ u32 size, u8 type); -+ -+int mwl_fwcmd_get_seqno(struct ieee80211_hw *hw, -+ struct mwl_ampdu_stream *stream, u16 *start_seqno); -+ -+int mwl_fwcmd_set_dwds_stamode(struct ieee80211_hw *hw, bool enable); -+ -+int mwl_fwcmd_set_fw_flush_timer(struct ieee80211_hw *hw, u32 value); -+ -+int mwl_fwcmd_set_cdd(struct ieee80211_hw *hw); -+ -+int mwl_fwcmd_set_bftype(struct ieee80211_hw *hw, int mode); -+ -+int mwl_fwcmd_reg_cau(struct ieee80211_hw *hw, u8 flag, u32 reg, u32 *val); -+ -+int mwl_fwcmd_get_temp(struct ieee80211_hw *hw, u32 *temp); -+ -+int mwl_fwcmd_led_ctrl(struct ieee80211_hw *hw, u8 enable, u8 rate); -+ -+int mwl_fwcmd_get_fw_region_code(struct ieee80211_hw *hw, -+ u32 *fw_region_code); -+ -+int mwl_fwcmd_get_device_pwr_tbl(struct ieee80211_hw *hw, -+ struct mwl_device_pwr_tbl *device_ch_pwrtbl, -+ u8 *region_code, -+ u8 *number_of_channels, -+ u32 channel_index); -+ -+int mwl_fwcmd_set_rate_drop(struct ieee80211_hw *hw, int enable, -+ int value, int staid); -+ -+int mwl_fwcmd_newdp_dmathread_start(struct ieee80211_hw *hw); -+ -+int mwl_fwcmd_get_fw_region_code_sc4(struct ieee80211_hw *hw, -+ u32 *fw_region_code); -+ -+int mwl_fwcmd_get_pwr_tbl_sc4(struct ieee80211_hw *hw, -+ struct mwl_device_pwr_tbl *device_ch_pwrtbl, -+ u8 *region_code, -+ u8 *number_of_channels, -+ u32 channel_index); -+ -+int mwl_fwcmd_quiet_mode(struct ieee80211_hw *hw, bool enable, u32 period, -+ u32 duration, u32 next_offset); -+ -+int mwl_fwcmd_core_dump_diag_mode(struct ieee80211_hw *hw, u16 status); -+ -+int mwl_fwcmd_get_fw_core_dump(struct ieee80211_hw *hw, -+ struct coredump_cmd *core_dump, char *buff); -+ -+int mwl_fwcmd_set_slot_time(struct ieee80211_hw *hw, bool short_slot); -+ -+int mwl_fwcmd_config_EDMACCtrl(struct ieee80211_hw *hw, int EDMAC_Ctrl); -+ -+int mwl_fwcmd_set_txpwrlmt_cfg_data(struct ieee80211_hw *hw); -+ -+int mwl_fwcmd_get_txpwrlmt_cfg_data(struct ieee80211_hw *hw); -+ -+int mwl_fwcmd_mcast_cts(struct ieee80211_hw *hw, u8 enable); -+ -+void mwl_fwcmd_get_survey(struct ieee80211_hw *hw, int idx); -+ -+#endif /* _FWCMD_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/hif-ops.h b/drivers/net/wireless/marvell/mwlwifi/hif/hif-ops.h -new file mode 100644 -index 000000000000..f5c7144b3c1b ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/hif-ops.h -@@ -0,0 +1,297 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines host interface related operations. */ -+ -+#ifndef _HIF_OPS_H_ -+#define _HIF_OPS_H_ -+static inline const char *mwl_hif_get_driver_name(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ return priv->hif.ops->driver_name; -+} -+ -+static inline const char *mwl_hif_get_driver_version(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ return priv->hif.ops->driver_version; -+} -+ -+static inline unsigned int mwl_hif_get_tx_head_room(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ return priv->hif.ops->tx_head_room; -+} -+ -+static inline unsigned int mwl_hif_get_ampdu_num(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ return priv->hif.ops->ampdu_num; -+} -+ -+static inline void mwl_hif_reset(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->reset) -+ priv->hif.ops->reset(hw); -+} -+ -+static inline int mwl_hif_init(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->init) -+ return priv->hif.ops->init(hw); -+ else -+ return -ENOTSUPP; -+} -+ -+static inline void mwl_hif_deinit(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->deinit) -+ priv->hif.ops->deinit(hw); -+} -+ -+static inline int mwl_hif_get_info(struct ieee80211_hw *hw, -+ char *buf, size_t size) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->get_info) -+ return priv->hif.ops->get_info(hw, buf, size); -+ else -+ return 0; -+} -+ -+static inline int mwl_hif_get_tx_status(struct ieee80211_hw *hw, -+ char *buf, size_t size) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->get_tx_status) -+ return priv->hif.ops->get_tx_status(hw, buf, size); -+ else -+ return 0; -+} -+ -+static inline int mwl_hif_get_rx_status(struct ieee80211_hw *hw, -+ char *buf, size_t size) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->get_rx_status) -+ return priv->hif.ops->get_rx_status(hw, buf, size); -+ else -+ return 0; -+} -+ -+static inline void mwl_hif_enable_data_tasks(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->enable_data_tasks) -+ priv->hif.ops->enable_data_tasks(hw); -+} -+ -+static inline void mwl_hif_disable_data_tasks(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->disable_data_tasks) -+ priv->hif.ops->disable_data_tasks(hw); -+} -+ -+static inline int mwl_hif_exec_cmd(struct ieee80211_hw *hw, unsigned short cmd) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->exec_cmd) -+ return priv->hif.ops->exec_cmd(hw, cmd); -+ else -+ return -ENOTSUPP; -+} -+ -+static inline int mwl_hif_get_irq_num(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->get_irq_num) -+ return priv->hif.ops->get_irq_num(hw); -+ else -+ return -ENOTSUPP; -+} -+ -+static inline irqreturn_t mwl_hif_irq_handler(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->irq_handler) -+ return priv->hif.ops->irq_handler(hw); -+ else -+ return -ENOTSUPP; -+} -+ -+static inline void mwl_hif_irq_enable(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->irq_enable) -+ priv->hif.ops->irq_enable(hw); -+} -+ -+static inline void mwl_hif_irq_disable(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->irq_disable) -+ priv->hif.ops->irq_disable(hw); -+} -+ -+static inline int mwl_hif_download_firmware(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->download_firmware) -+ return priv->hif.ops->download_firmware(hw); -+ else -+ return -ENOTSUPP; -+} -+ -+static inline void mwl_hif_timer_routine(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->timer_routine) -+ priv->hif.ops->timer_routine(hw); -+} -+ -+static inline void mwl_hif_tx_xmit(struct ieee80211_hw *hw, -+ struct ieee80211_tx_control *control, -+ struct sk_buff *skb) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->tx_xmit) -+ priv->hif.ops->tx_xmit(hw, control, skb); -+} -+ -+static inline void mwl_hif_tx_del_pkts_via_vif(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->tx_del_pkts_via_vif) -+ priv->hif.ops->tx_del_pkts_via_vif(hw, vif); -+} -+ -+static inline void mwl_hif_tx_del_pkts_via_sta(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->tx_del_pkts_via_sta) -+ priv->hif.ops->tx_del_pkts_via_sta(hw, sta); -+} -+ -+static inline void mwl_hif_tx_del_ampdu_pkts(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta, u8 tid) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->tx_del_ampdu_pkts) -+ priv->hif.ops->tx_del_ampdu_pkts(hw, sta, tid); -+} -+ -+static inline void mwl_hif_tx_del_sta_amsdu_pkts(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->tx_del_sta_amsdu_pkts) -+ priv->hif.ops->tx_del_sta_amsdu_pkts(hw, sta); -+} -+ -+static inline void mwl_hif_tx_return_pkts(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->tx_return_pkts) -+ priv->hif.ops->tx_return_pkts(hw); -+} -+ -+static inline struct device_node *mwl_hif_device_node(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->get_device_node) -+ return priv->hif.ops->get_device_node(hw); -+ else -+ return NULL; -+} -+ -+static inline void mwl_hif_get_survey(struct ieee80211_hw *hw, -+ struct mwl_survey_info *survey_info) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->get_survey) -+ priv->hif.ops->get_survey(hw, survey_info); -+} -+ -+static inline int mwl_hif_reg_access(struct ieee80211_hw *hw, bool write) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->reg_access) -+ return priv->hif.ops->reg_access(hw, write); -+ else -+ return -ENOTSUPP; -+} -+ -+static inline void mwl_hif_set_sta_id(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta, -+ bool sta_mode, bool set) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->set_sta_id) -+ priv->hif.ops->set_sta_id(hw, sta, sta_mode, set); -+} -+ -+static inline void mwl_hif_process_account(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->process_account) -+ priv->hif.ops->process_account(hw); -+} -+ -+static inline int mwl_hif_mcast_cts(struct ieee80211_hw *hw, bool enable) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->hif.ops->mcast_cts) -+ return priv->hif.ops->mcast_cts(hw, enable); -+ else -+ return -ENOTSUPP; -+} -+#endif /* _HIF_OPS_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/hif.h b/drivers/net/wireless/marvell/mwlwifi/hif/hif.h -new file mode 100644 -index 000000000000..6ea6192ac5e0 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/hif.h -@@ -0,0 +1,81 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines host interface data structure. */ -+ -+#ifndef _HIF_H_ -+#define _HIF_H_ -+ -+/* memory/register access */ -+#define MWL_ACCESS_MAC 1 -+#define MWL_ACCESS_RF 2 -+#define MWL_ACCESS_BBP 3 -+#define MWL_ACCESS_CAU 4 -+#define MWL_ACCESS_ADDR0 5 -+#define MWL_ACCESS_ADDR1 6 -+#define MWL_ACCESS_ADDR 7 -+ -+struct mwl_survey_info { -+ struct ieee80211_channel channel; -+ u32 filled; -+ u32 time_period; -+ u32 time_busy; -+ u32 time_tx; -+ s8 noise; -+}; -+ -+struct mwl_hif_ops { -+ const char *driver_name; -+ const char *driver_version; -+ unsigned int tx_head_room; -+ int ampdu_num; -+ void (*reset)(struct ieee80211_hw *hw); -+ int (*init)(struct ieee80211_hw *hw); -+ void (*deinit)(struct ieee80211_hw *hw); -+ int (*get_info)(struct ieee80211_hw *hw, char *buf, size_t size); -+ int (*get_tx_status)(struct ieee80211_hw *hw, char *buf, size_t size); -+ int (*get_rx_status)(struct ieee80211_hw *hw, char *buf, size_t size); -+ void (*enable_data_tasks)(struct ieee80211_hw *hw); -+ void (*disable_data_tasks)(struct ieee80211_hw *hw); -+ int (*exec_cmd)(struct ieee80211_hw *hw, unsigned short cmd); -+ int (*get_irq_num)(struct ieee80211_hw *hw); -+ irqreturn_t (*irq_handler)(struct ieee80211_hw *hw); -+ void (*irq_enable)(struct ieee80211_hw *hw); -+ void (*irq_disable)(struct ieee80211_hw *hw); -+ int (*download_firmware)(struct ieee80211_hw *hw); -+ void (*timer_routine)(struct ieee80211_hw *hw); -+ void (*tx_xmit)(struct ieee80211_hw *hw, -+ struct ieee80211_tx_control *control, -+ struct sk_buff *skb); -+ void (*tx_del_pkts_via_vif)(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif); -+ void (*tx_del_pkts_via_sta)(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta); -+ void (*tx_del_ampdu_pkts)(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta, u8 tid); -+ void (*tx_del_sta_amsdu_pkts)(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta); -+ void (*tx_return_pkts)(struct ieee80211_hw *hw); -+ struct device_node *(*get_device_node)(struct ieee80211_hw *hw); -+ void (*get_survey)(struct ieee80211_hw *hw, -+ struct mwl_survey_info *survey_info); -+ int (*reg_access)(struct ieee80211_hw *hw, bool write); -+ void (*set_sta_id)(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta, -+ bool sta_mode, bool set); -+ void (*process_account)(struct ieee80211_hw *hw); -+ int (*mcast_cts)(struct ieee80211_hw *hw, bool enable); -+}; -+#endif /* _HIF_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/hostcmd.h b/drivers/net/wireless/marvell/mwlwifi/hif/hostcmd.h -new file mode 100644 -index 000000000000..b14f161f1410 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/hostcmd.h -@@ -0,0 +1,1285 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines firmware host command related -+ * structure. -+ */ -+ -+#ifndef _HOSTCMD_H_ -+#define _HOSTCMD_H_ -+ -+/* 16 bit host command code */ -+#define HOSTCMD_CMD_GET_HW_SPEC 0x0003 -+#define HOSTCMD_CMD_SET_HW_SPEC 0x0004 -+#define HOSTCMD_CMD_802_11_GET_STAT 0x0014 -+#define HOSTCMD_CMD_BBP_REG_ACCESS 0x001a -+#define HOSTCMD_CMD_RF_REG_ACCESS 0x001b -+#define HOSTCMD_CMD_802_11_RADIO_CONTROL 0x001c -+#define HOSTCMD_CMD_MEM_ADDR_ACCESS 0x001d -+#define HOSTCMD_CMD_802_11_TX_POWER 0x001f -+#define HOSTCMD_CMD_802_11_RF_ANTENNA 0x0020 -+#define HOSTCMD_CMD_BROADCAST_SSID_ENABLE 0x0050 /* per-vif */ -+#define HOSTCMD_CMD_SET_CFG 0x008f -+#define HOSTCMD_CMD_SET_RF_CHANNEL 0x010a -+#define HOSTCMD_CMD_SET_AID 0x010d /* per-vif */ -+#define HOSTCMD_CMD_SET_INFRA_MODE 0x010e /* per-vif */ -+#define HOSTCMD_CMD_802_11_RTS_THSD 0x0113 -+#define HOSTCMD_CMD_SET_EDCA_PARAMS 0x0115 -+#define HOSTCMD_CMD_802_11H_DETECT_RADAR 0x0120 -+#define HOSTCMD_CMD_SET_WMM_MODE 0x0123 -+#define HOSTCMD_CMD_HT_GUARD_INTERVAL 0x0124 -+#define HOSTCMD_CMD_SET_FIXED_RATE 0x0126 -+#define HOSTCMD_CMD_SET_IES 0x0127 -+#define HOSTCMD_CMD_SET_LINKADAPT_CS_MODE 0x0129 -+#define HOSTCMD_CMD_DUMP_OTP_DATA 0x0142 -+#define HOSTCMD_CMD_SET_MAC_ADDR 0x0202 /* per-vif */ -+#define HOSTCMD_CMD_SET_RATE_ADAPT_MODE 0x0203 -+#define HOSTCMD_CMD_GET_WATCHDOG_BITMAP 0x0205 -+#define HOSTCMD_CMD_DEL_MAC_ADDR 0x0206 /* per-vif */ -+#define HOSTCMD_CMD_BSS_START 0x1100 /* per-vif */ -+#define HOSTCMD_CMD_AP_BEACON 0x1101 /* per-vif */ -+#define HOSTCMD_CMD_SET_NEW_STN 0x1111 /* per-vif */ -+#define HOSTCMD_CMD_SET_APMODE 0x1114 -+#define HOSTCMD_CMD_SET_SWITCH_CHANNEL 0x1121 -+#define HOSTCMD_CMD_UPDATE_ENCRYPTION 0x1122 /* per-vif */ -+#define HOSTCMD_CMD_BASTREAM 0x1125 -+#define HOSTCMD_CMD_SET_SPECTRUM_MGMT 0x1128 -+#define HOSTCMD_CMD_SET_POWER_CONSTRAINT 0x1129 -+#define HOSTCMD_CMD_SET_COUNTRY_CODE 0x1130 -+#define HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL 0x1133 -+#define HOSTCMD_CMD_SET_WSC_IE 0x1136 /* per-vif */ -+#define HOSTCMD_CMD_GET_RATETABLE 0x1137 -+#define HOSTCMD_CMD_GET_SEQNO 0x1143 -+#define HOSTCMD_CMD_DWDS_ENABLE 0x1144 -+#define HOSTCMD_CMD_FW_FLUSH_TIMER 0x1148 -+#define HOSTCMD_CMD_SET_CDD 0x1150 -+#define HOSTCMD_CMD_SET_BFTYPE 0x1155 -+#define HOSTCMD_CMD_CAU_REG_ACCESS 0x1157 -+#define HOSTCMD_CMD_GET_TEMP 0x1159 -+#define HOSTCMD_CMD_LED_CTRL 0x1169 -+#define HOSTCMD_CMD_GET_FW_REGION_CODE 0x116A -+#define HOSTCMD_CMD_GET_DEVICE_PWR_TBL 0x116B -+#define HOSTCMD_CMD_SET_RATE_DROP 0x1172 -+#define HOSTCMD_CMD_NEWDP_DMATHREAD_START 0x1189 -+#define HOSTCMD_CMD_GET_FW_REGION_CODE_SC4 0x118A -+#define HOSTCMD_CMD_GET_DEVICE_PWR_TBL_SC4 0x118B -+#define HOSTCMD_CMD_QUIET_MODE 0x1201 -+#define HOSTCMD_CMD_CORE_DUMP_DIAG_MODE 0x1202 -+#define HOSTCMD_CMD_802_11_SLOT_TIME 0x1203 -+#define HOSTCMD_CMD_GET_FW_CORE_DUMP 0x1203 -+#define HOSTCMD_CMD_EDMAC_CTRL 0x1204 -+#define HOSTCMD_CMD_TXPWRLMT_CFG 0x1211 -+#define HOSTCMD_CMD_MCAST_CTS 0x4001 -+ -+/* Define general result code for each command */ -+#define HOSTCMD_RESULT_OK 0x0000 -+/* General error */ -+#define HOSTCMD_RESULT_ERROR 0x0001 -+/* Command is not valid */ -+#define HOSTCMD_RESULT_NOT_SUPPORT 0x0002 -+/* Command is pending (will be processed) */ -+#define HOSTCMD_RESULT_PENDING 0x0003 -+/* System is busy (command ignored) */ -+#define HOSTCMD_RESULT_BUSY 0x0004 -+/* Data buffer is not big enough */ -+#define HOSTCMD_RESULT_PARTIAL_DATA 0x0005 -+ -+/* Define channel related constants */ -+#define FREQ_BAND_2DOT4GHZ 0x1 -+#define FREQ_BAND_4DOT9GHZ 0x2 -+#define FREQ_BAND_5GHZ 0x4 -+#define FREQ_BAND_5DOT2GHZ 0x8 -+#define CH_AUTO_WIDTH 0 -+#define CH_10_MHZ_WIDTH 0x1 -+#define CH_20_MHZ_WIDTH 0x2 -+#define CH_40_MHZ_WIDTH 0x4 -+#define CH_80_MHZ_WIDTH 0x5 -+#define CH_160_MHZ_WIDTH 0x6 -+#define EXT_CH_ABOVE_CTRL_CH 0x1 -+#define EXT_CH_AUTO 0x2 -+#define EXT_CH_BELOW_CTRL_CH 0x3 -+#define NO_EXT_CHANNEL 0x0 -+ -+#define ACT_PRIMARY_CHAN_0 0 -+#define ACT_PRIMARY_CHAN_1 1 -+#define ACT_PRIMARY_CHAN_2 2 -+#define ACT_PRIMARY_CHAN_3 3 -+#define ACT_PRIMARY_CHAN_4 4 -+#define ACT_PRIMARY_CHAN_5 5 -+#define ACT_PRIMARY_CHAN_6 6 -+#define ACT_PRIMARY_CHAN_7 7 -+ -+/* Define rate related constants */ -+#define HOSTCMD_ACT_NOT_USE_FIXED_RATE 0x0002 -+ -+/* Define station related constants */ -+#define HOSTCMD_ACT_STA_ACTION_ADD 0 -+#define HOSTCMD_ACT_STA_ACTION_MODIFY 1 -+#define HOSTCMD_ACT_STA_ACTION_REMOVE 2 -+ -+/* Define key related constants */ -+#define MAX_ENCR_KEY_LENGTH 16 -+#define MIC_KEY_LENGTH 8 -+ -+#define KEY_TYPE_ID_WEP 0x00 -+#define KEY_TYPE_ID_TKIP 0x01 -+#define KEY_TYPE_ID_AES 0x02 -+ -+/* Group key for RX only */ -+#define ENCR_KEY_FLAG_RXGROUPKEY 0x00000002 -+#define ENCR_KEY_FLAG_TXGROUPKEY 0x00000004 -+#define ENCR_KEY_FLAG_PAIRWISE 0x00000008 -+#define ENCR_KEY_FLAG_TSC_VALID 0x00000040 -+#define ENCR_KEY_FLAG_WEP_TXKEY 0x01000000 -+#define ENCR_KEY_FLAG_MICKEY_VALID 0x02000000 -+ -+/* Define block ack related constants */ -+#define BA_FLAG_IMMEDIATE_TYPE 1 -+#define BA_FLAG_DIRECTION_UP 0 -+#define BA_FLAG_DIRECTION_DOWN 1 -+ -+/* Define general purpose action */ -+#define HOSTCMD_ACT_GEN_SET 0x0001 -+#define HOSTCMD_ACT_GEN_SET_LIST 0x0002 -+#define HOSTCMD_ACT_GEN_GET_LIST 0x0003 -+ -+/* Define TXPower control action*/ -+#define HOSTCMD_ACT_GET_TARGET_TX_PWR 0x0000 -+#define HOSTCMD_ACT_GET_MAX_TX_PWR 0x0001 -+#define HOSTCMD_ACT_SET_TARGET_TX_PWR 0x0002 -+#define HOSTCMD_ACT_SET_MAX_TX_PWR 0x0003 -+ -+/* Misc */ -+#define WSC_IE_MAX_LENGTH 251 -+#define WSC_IE_SET_BEACON 0 -+#define WSC_IE_SET_PROBE_RESPONSE 1 -+ -+#define HW_SET_PARMS_FEATURES_HOST_PROBE_RESP 0x00000020 -+ -+#define EDMAC_2G_ENABLE_MASK 0x00000001 -+#define EDMAC_2G_ENABLE_SHIFT 0x0 -+#define EDMAC_5G_ENABLE_MASK 0x00000002 -+#define EDMAC_5G_ENABLE_SHIFT 0x1 -+#define EDMAC_2G_THRESHOLD_OFFSET_MASK 0x00000FF0 -+#define EDMAC_2G_THRESHOLD_OFFSET_SHIFT 0x4 -+#define EDMAC_5G_THRESHOLD_OFFSET_MASK 0x000FF000 -+#define EDMAC_5G_THRESHOLD_OFFSET_SHIFT 0xC -+#define EDMAC_QLOCK_BITMAP_MASK 0x0FF00000 -+#define EDMAC_QLOCK_BITMAP_SHIFT 0x14 -+ -+enum { -+ WL_DISABLE = 0, -+ WL_ENABLE = 1, -+ WL_DISABLE_VMAC = 0x80, -+}; -+ -+enum { -+ WL_GET = 0, -+ WL_SET = 1, -+ WL_RESET = 2, -+}; -+ -+enum { -+ WL_LONG_PREAMBLE = 1, -+ WL_SHORT_PREAMBLE = 3, -+ WL_AUTO_PREAMBLE = 5, -+}; -+ -+enum encr_action_type { -+ /* request to enable/disable HW encryption */ -+ ENCR_ACTION_ENABLE_HW_ENCR, -+ /* request to set encryption key */ -+ ENCR_ACTION_TYPE_SET_KEY, -+ /* request to remove one or more keys */ -+ ENCR_ACTION_TYPE_REMOVE_KEY, -+ ENCR_ACTION_TYPE_SET_GROUP_KEY, -+}; -+ -+enum ba_action_type { -+ BA_CREATE_STREAM, -+ BA_UPDATE_STREAM, -+ BA_DESTROY_STREAM, -+ BA_FLUSH_STREAM, -+ BA_CHECK_STREAM, -+}; -+ -+enum mac_type { -+ WL_MAC_TYPE_PRIMARY_CLIENT, -+ WL_MAC_TYPE_SECONDARY_CLIENT, -+ WL_MAC_TYPE_PRIMARY_AP, -+ WL_MAC_TYPE_SECONDARY_AP, -+}; -+ -+/* General host command header */ -+struct hostcmd_header { -+ __le16 cmd; -+ __le16 len; -+ u8 seq_num; -+ u8 macid; -+ __le16 result; -+} __packed; -+ -+/* HOSTCMD_CMD_GET_HW_SPEC */ -+struct hostcmd_get_hw_spec { -+ u8 version; /* version of the HW */ -+ u8 host_if; /* host interface */ -+ __le16 num_wcb; /* Max. number of WCB FW can handle */ -+ __le16 num_mcast_addr; /* MaxNbr of MC addresses FW can handle */ -+ u8 permanent_addr[ETH_ALEN]; /* MAC address programmed in HW */ -+ __le16 region_code; -+ __le16 num_antenna; /* Number of antenna used */ -+ __le32 fw_release_num; /* 4 byte of FW release number */ -+ __le32 wcb_base0; -+ __le32 rxpd_wr_ptr; -+ __le32 rxpd_rd_ptr; -+ __le32 fw_awake_cookie; -+ __le32 wcb_base[SYSADPT_TOTAL_TX_QUEUES - 1]; -+} __packed; -+ -+struct hostcmd_cmd_get_hw_spec { -+ struct hostcmd_header cmd_hdr; -+ struct hostcmd_get_hw_spec hw_spec; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_HW_SPEC */ -+struct hostcmd_set_hw_spec { -+ /* HW revision */ -+ u8 version; -+ /* Host interface */ -+ u8 host_if; -+ /* Max. number of Multicast address FW can handle */ -+ __le16 num_mcast_addr; -+ /* MAC address */ -+ u8 permanent_addr[ETH_ALEN]; -+ /* Region Code */ -+ __le16 region_code; -+ /* 4 byte of FW release number, example 0x1234=1.2.3.4 */ -+ __le32 fw_release_num; -+ /* Firmware awake cookie - used to ensure that the device -+ * is not in sleep mode -+ */ -+ __le32 fw_awake_cookie; -+ /* Device capabilities (see above) */ -+ __le32 device_caps; -+ /* Rx shared memory queue */ -+ __le32 rxpd_wr_ptr; -+ /* Actual number of TX queues in WcbBase array */ -+ __le32 num_tx_queues; -+ /* TX WCB Rings */ -+ __le32 wcb_base[4 + SYSADPT_NUM_OF_AP]; -+ /* Max AMSDU size (00 - AMSDU Disabled, -+ * 01 - 4K, 10 - 8K, 11 - not defined) -+ */ -+ __le32 features; -+ __le32 tx_wcb_num_per_queue; -+ __le32 total_rx_wcb; -+ __le32 acnt_buf_size; -+ __le32 acnt_base_addr; -+} __packed; -+ -+struct hostcmd_cmd_set_hw_spec { -+ struct hostcmd_header cmd_hdr; -+ struct hostcmd_set_hw_spec hw_spec; -+} __packed; -+ -+/* HOSTCMD_CMD_802_11_GET_STAT */ -+struct hostcmd_cmd_802_11_get_stat { -+ struct hostcmd_header cmd_hdr; -+ __le32 tx_retry_successes; -+ __le32 tx_multiple_retry_successes; -+ __le32 tx_failures; -+ __le32 rts_successes; -+ __le32 rts_failures; -+ __le32 ack_failures; -+ __le32 rx_duplicate_frames; -+ __le32 rx_fcs_errors; -+ __le32 tx_watchdog_timeouts; -+ __le32 rx_overflows; -+ __le32 rx_frag_errors; -+ __le32 rx_mem_errors; -+ __le32 pointer_errors; -+ __le32 tx_underflows; -+ __le32 tx_done; -+ __le32 tx_done_buf_try_put; -+ __le32 tx_done_buf_put; -+ /* Put size of requested buffer in here */ -+ __le32 wait_for_tx_buf; -+ __le32 tx_attempts; -+ __le32 tx_successes; -+ __le32 tx_fragments; -+ __le32 tx_multicasts; -+ __le32 rx_non_ctl_pkts; -+ __le32 rx_multicasts; -+ __le32 rx_undecryptable_frames; -+ __le32 rx_icv_errors; -+ __le32 rx_excluded_frames; -+ __le32 rx_weak_iv_count; -+ __le32 rx_unicasts; -+ __le32 rx_bytes; -+ __le32 rx_errors; -+ __le32 rx_rts_count; -+ __le32 tx_cts_count; -+} __packed; -+ -+/* HOSTCMD_CMD_BBP_REG_ACCESS */ -+struct hostcmd_cmd_bbp_reg_access { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ __le16 offset; -+ u8 value; -+ u8 reserverd[3]; -+} __packed; -+ -+/* HOSTCMD_CMD_RF_REG_ACCESS */ -+struct hostcmd_cmd_rf_reg_access { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ __le16 offset; -+ u8 value; -+ u8 reserverd[3]; -+} __packed; -+ -+/* HOSTCMD_CMD_802_11_RADIO_CONTROL */ -+struct hostcmd_cmd_802_11_radio_control { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ /* @bit0: 1/0,on/off, @bit1: 1/0, long/short @bit2: 1/0,auto/fix */ -+ __le16 control; -+ __le16 radio_on; -+} __packed; -+ -+/* HOSTCMD_CMD_MEM_ADDR_ACCESS */ -+struct hostcmd_cmd_mem_addr_access { -+ struct hostcmd_header cmd_hdr; -+ __le32 address; -+ __le16 length; -+ __le16 reserved; -+ __le32 value[64]; -+} __packed; -+ -+/* HOSTCMD_CMD_802_11_TX_POWER */ -+struct hostcmd_cmd_802_11_tx_power { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ __le16 band; -+ __le16 ch; -+ __le16 bw; -+ __le16 sub_ch; -+ __le16 power_level_list[SYSADPT_TX_POWER_LEVEL_TOTAL]; -+} __packed; -+ -+struct hostcmd_cmd_802_11_tx_power_kf2 { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ __le16 band; -+ __le16 ch; -+ __le16 bw; -+ __le16 sub_ch; -+ __le16 power_level_list[SYSADPT_TX_GRP_PWR_LEVEL_TOTAL]; -+} __packed; -+ -+/* HOSTCMD_CMD_802_11_RF_ANTENNA */ -+struct hostcmd_cmd_802_11_rf_antenna { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ __le16 antenna_mode; /* Number of antennas or 0xffff(diversity) */ -+} __packed; -+ -+/* HOSTCMD_CMD_BROADCAST_SSID_ENABLE */ -+struct hostcmd_cmd_broadcast_ssid_enable { -+ struct hostcmd_header cmd_hdr; -+ __le32 enable; -+ __le32 hidden_ssid_info; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_CFG */ -+struct hostcmd_cmd_set_cfg { -+ struct hostcmd_header cmd_hdr; -+ /* Action */ -+ __le16 action; -+ /* Type */ -+ __le16 type; -+ /* Data length */ -+ __le16 data_len; -+ /* Data */ -+ u8 data[1]; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_RF_CHANNEL */ -+#define FREQ_BAND_MASK 0x0000003f -+#define CHNL_WIDTH_MASK 0x000007c0 -+#define CHNL_WIDTH_SHIFT 6 -+#define ACT_PRIMARY_MASK 0x00003800 -+#define ACT_PRIMARY_SHIFT 11 -+ -+struct hostcmd_cmd_set_rf_channel { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ u8 curr_chnl; -+ __le32 chnl_flags; -+} __packed; -+ -+struct hostcmd_cmd_set_rf_channel_kf2 { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ u8 curr_chnl; -+ __le32 chnl_flags; -+ u8 remain_on_chan; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_AID */ -+struct hostcmd_cmd_set_aid { -+ struct hostcmd_header cmd_hdr; -+ __le16 aid; -+ u8 mac_addr[ETH_ALEN]; /* AP's Mac Address(BSSID) */ -+ __le32 gprotect; -+ u8 ap_rates[SYSADPT_MAX_DATA_RATES_G]; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_INFRA_MODE */ -+struct hostcmd_cmd_set_infra_mode { -+ struct hostcmd_header cmd_hdr; -+} __packed; -+ -+/* HOSTCMD_CMD_802_11_RTS_THSD */ -+struct hostcmd_cmd_802_11_rts_thsd { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ __le16 threshold; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_EDCA_PARAMS */ -+struct hostcmd_cmd_set_edca_params { -+ struct hostcmd_header cmd_hdr; -+ /* 0 = get all, 0x1 =set CWMin/Max, 0x2 = set TXOP , 0x4 =set AIFSN */ -+ __le16 action; -+ __le16 txop; /* in unit of 32 us */ -+ __le32 cw_max; /* 0~15 */ -+ __le32 cw_min; /* 0~15 */ -+ u8 aifsn; -+ u8 txq_num; /* Tx Queue number. */ -+} __packed; -+ -+/* HOSTCMD_CMD_802_11H_DETECT_RADAR */ -+#define RADAR_TYPE_CODE_0 0 -+#define RADAR_TYPE_CODE_53 53 -+#define RADAR_TYPE_CODE_56 56 -+#define RADAR_TYPE_CODE_ETSI 151 -+ -+struct hostcmd_cmd_802_11h_detect_radar { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ __le16 radar_type_code; -+ __le16 min_chirp_cnt; -+ __le16 chirp_time_intvl; -+ __le16 pw_filter; -+ __le16 min_num_radar; -+ __le16 pri_min_num; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_WMM_MODE */ -+struct hostcmd_cmd_set_wmm_mode { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; /* 0->unset, 1->set */ -+} __packed; -+ -+/* HOSTCMD_CMD_HT_GUARD_INTERVAL */ -+struct hostcmd_cmd_ht_guard_interval { -+ struct hostcmd_header cmd_hdr; -+ __le32 action; -+ __le32 gi_type; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_FIXED_RATE */ -+struct fix_rate_flag { /* lower rate after the retry count */ -+ /* 0: legacy, 1: HT */ -+ __le32 fix_rate_type; -+ /* 0: retry count is not valid, 1: use retry count specified */ -+ __le32 retry_count_valid; -+} __packed; -+ -+struct fix_rate_entry { -+ struct fix_rate_flag fix_rate_type_flags; -+ /* depending on the flags above, this can be either a legacy -+ * rate(not index) or an MCS code. -+ */ -+ __le32 fixed_rate; -+ __le32 retry_count; -+} __packed; -+ -+struct hostcmd_cmd_set_fixed_rate { -+ struct hostcmd_header cmd_hdr; -+ /* HOSTCMD_ACT_NOT_USE_FIXED_RATE 0x0002 */ -+ __le32 action; -+ /* use fixed rate specified but firmware can drop to */ -+ __le32 allow_rate_drop; -+ __le32 entry_count; -+ struct fix_rate_entry fixed_rate_table[4]; -+ u8 multicast_rate; -+ u8 multi_rate_tx_type; -+ u8 management_rate; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_IES */ -+struct hostcmd_cmd_set_ies { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; /* 0->unset, 1->set */ -+ __le16 ie_list_len_ht; -+ __le16 ie_list_len_vht; -+ __le16 ie_list_len_proprietary; -+ /*Buffer size same as Generic_Beacon*/ -+ u8 ie_list_ht[148]; -+ u8 ie_list_vht[24]; -+ u8 ie_list_proprietary[112]; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_LINKADAPT_CS_MODE */ -+struct hostcmd_cmd_set_linkadapt_cs_mode { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ __le16 cs_mode; -+} __packed; -+ -+/* HOSTCMD_CMD_DUMP_OTP_DATA */ -+struct hostcmd_cmd_dump_otp_data { -+ struct hostcmd_header cmd_hdr; -+ u8 pload[0]; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_MAC_ADDR, HOSTCMD_CMD_DEL_MAC_ADDR */ -+struct hostcmd_cmd_set_mac_addr { -+ struct hostcmd_header cmd_hdr; -+ __le16 mac_type; -+ u8 mac_addr[ETH_ALEN]; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_RATE_ADAPT_MODE */ -+struct hostcmd_cmd_set_rate_adapt_mode { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ __le16 rate_adapt_mode; /* 0:Indoor, 1:Outdoor */ -+} __packed; -+ -+/* HOSTCMD_CMD_GET_WATCHDOG_BITMAP */ -+struct hostcmd_cmd_get_watchdog_bitmap { -+ struct hostcmd_header cmd_hdr; -+ u8 watchdog_bitmap; /* for SW/BA */ -+} __packed; -+ -+/* HOSTCMD_CMD_BSS_START */ -+struct hostcmd_cmd_bss_start { -+ struct hostcmd_header cmd_hdr; -+ __le32 enable; /* FALSE: Disable or TRUE: Enable */ -+ u8 amsdu; -+} __packed; -+ -+/* HOSTCMD_CMD_AP_BEACON */ -+struct cf_params { -+ u8 elem_id; -+ u8 len; -+ u8 cfp_cnt; -+ u8 cfp_period; -+ __le16 cfp_max_duration; -+ __le16 cfp_duration_remaining; -+} __packed; -+ -+struct ibss_params { -+ u8 elem_id; -+ u8 len; -+ __le16 atim_window; -+} __packed; -+ -+union ss_params { -+ struct cf_params cf_param_set; -+ struct ibss_params ibss_param_set; -+} __packed; -+ -+struct fh_params { -+ u8 elem_id; -+ u8 len; -+ __le16 dwell_time; -+ u8 hop_set; -+ u8 hop_pattern; -+ u8 hop_index; -+} __packed; -+ -+struct ds_params { -+ u8 elem_id; -+ u8 len; -+ u8 current_chnl; -+} __packed; -+ -+union phy_params { -+ struct fh_params fh_param_set; -+ struct ds_params ds_param_set; -+} __packed; -+ -+struct rsn_ie { -+ u8 elem_id; -+ u8 len; -+ u8 oui_type[4]; /* 00:50:f2:01 */ -+ u8 ver[2]; -+ u8 grp_key_cipher[4]; -+ u8 pws_key_cnt[2]; -+ u8 pws_key_cipher_list[4]; -+ u8 auth_key_cnt[2]; -+ u8 auth_key_list[4]; -+} __packed; -+ -+struct rsn48_ie { -+ u8 elem_id; -+ u8 len; -+ u8 ver[2]; -+ u8 grp_key_cipher[4]; -+ u8 pws_key_cnt[2]; -+ u8 pws_key_cipher_list[4]; -+ u8 auth_key_cnt[2]; -+ u8 auth_key_list[4]; -+ u8 rsn_cap[2]; -+ u8 pmk_id_cnt[2]; -+ u8 pmk_id_list[16]; /* Should modify to 16 * S */ -+ u8 reserved[8]; -+} __packed; -+ -+struct ac_param_rcd { -+ u8 aci_aifsn; -+ u8 ecw_min_max; -+ __le16 txop_lim; -+} __packed; -+ -+struct wmm_param_elem { -+ u8 elem_id; -+ u8 len; -+ u8 oui[3]; -+ u8 type; -+ u8 sub_type; -+ u8 version; -+ u8 qos_info; -+ u8 rsvd; -+ struct ac_param_rcd ac_be; -+ struct ac_param_rcd ac_bk; -+ struct ac_param_rcd ac_vi; -+ struct ac_param_rcd ac_vo; -+} __packed; -+ -+struct channel_info { -+ u8 first_channel_num; -+ u8 num_channels; -+ u8 max_tx_pwr_level; -+} __packed; -+ -+struct country { -+ u8 elem_id; -+ u8 len; -+ u8 country_str[3]; -+ struct channel_info channel_info[40]; -+} __packed; -+ -+struct start_cmd { -+ u8 sta_mac_addr[ETH_ALEN]; -+ u8 ssid[IEEE80211_MAX_SSID_LEN]; -+ u8 bss_type; -+ __le16 bcn_period; -+ u8 dtim_period; -+ union ss_params ss_param_set; -+ union phy_params phy_param_set; -+ __le16 probe_delay; -+ __le16 cap_info; -+ u8 b_rate_set[SYSADPT_MAX_DATA_RATES_G]; -+ u8 op_rate_set[SYSADPT_MAX_DATA_RATES_G]; -+ struct rsn_ie rsn_ie; -+ struct rsn48_ie rsn48_ie; -+ struct wmm_param_elem wmm_param; -+ struct country country; -+ __le32 ap_rf_type; /* 0->B, 1->G, 2->Mixed, 3->A, 4->11J */ -+ u8 rsvd[3]; -+ u8 bssid[ETH_ALEN]; /* only for 88W8997 */ -+} __packed; -+ -+struct hostcmd_cmd_ap_beacon { -+ struct hostcmd_header cmd_hdr; -+ struct start_cmd start_cmd; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_NEW_STN */ -+struct add_ht_info { -+ u8 control_chnl; -+ u8 add_chnl; -+ __le16 op_mode; -+ __le16 stbc; -+} __packed; -+ -+struct peer_info { -+ __le32 legacy_rate_bitmap; -+ u8 ht_rates[4]; -+ __le16 cap_info; -+ __le16 ht_cap_info; -+ u8 mac_ht_param_info; -+ u8 mrvl_sta; -+ struct add_ht_info add_ht_info; -+ __le32 tx_bf_capabilities; /* EXBF_SUPPORT */ -+ __le32 vht_max_rx_mcs; -+ __le32 vht_cap; -+ /* 0:20Mhz, 1:40Mhz, 2:80Mhz, 3:160 or 80+80Mhz */ -+ u8 vht_rx_channel_width; -+} __packed; -+ -+struct hostcmd_cmd_set_new_stn { -+ struct hostcmd_header cmd_hdr; -+ __le16 aid; -+ u8 mac_addr[ETH_ALEN]; -+ __le16 stn_id; -+ __le16 action; -+ __le16 if_type; -+ struct peer_info peer_info; -+ /* UAPSD_SUPPORT */ -+ u8 qos_info; -+ u8 is_qos_sta; -+ __le32 fw_sta_ptr; -+} __packed; -+ -+struct retry_cnt_qos { -+ u8 retry_cfg_enable; -+ u8 retry_cnt_BK; -+ u8 retry_cnt_BE; -+ u8 retry_cnt_VI; -+ u8 retry_cnt_VO; -+} __packed; -+ -+struct peer_info_sc4 { -+ __le32 legacy_rate_bitmap; -+ u8 ht_rates[4]; -+ __le16 cap_info; -+ __le16 ht_cap_info; -+ u8 mac_ht_param_info; -+ u8 mrvl_sta; -+ struct add_ht_info add_ht_info; -+ __le32 tx_bf_capabilities; /* EXBF_SUPPORT */ -+ __le32 vht_max_rx_mcs; -+ __le32 vht_cap; -+ /* 0:20Mhz, 1:40Mhz, 2:80Mhz, 3:160 or 80+80Mhz */ -+ u8 vht_rx_channel_width; -+ struct retry_cnt_qos retry_cnt_qos; -+ u8 assoc_rssi; -+} __packed; -+ -+struct hostcmd_cmd_set_new_stn_sc4 { -+ struct hostcmd_header cmd_hdr; -+ __le16 aid; -+ u8 mac_addr[ETH_ALEN]; -+ __le16 stn_id; -+ __le16 action; -+ __le16 reserved; -+ struct peer_info_sc4 peer_info; -+ /* UAPSD_SUPPORT */ -+ u8 qos_info; -+ u8 is_qos_sta; -+ __le32 fw_sta_ptr; -+ __le32 wds; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_APMODE */ -+struct hostcmd_cmd_set_apmode { -+ struct hostcmd_header cmd_hdr; -+ u8 apmode; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_SWITCH_CHANNEL */ -+struct hostcmd_cmd_set_switch_channel { -+ struct hostcmd_header cmd_hdr; -+ __le32 next_11h_chnl; -+ __le32 mode; -+ __le32 init_count; -+ __le32 chnl_flags; -+ __le32 next_ht_extchnl_offset; -+ __le32 dfs_test_mode; -+} __packed; -+ -+/* HOSTCMD_CMD_UPDATE_ENCRYPTION */ -+struct hostcmd_cmd_update_encryption { -+ struct hostcmd_header cmd_hdr; -+ /* Action type - see encr_action_type */ -+ __le32 action_type; /* encr_action_type */ -+ /* size of the data buffer attached. */ -+ __le32 data_length; -+ u8 mac_addr[ETH_ALEN]; -+ u8 action_data[1]; -+} __packed; -+ -+struct wep_type_key { -+ /* WEP key material (max 128bit) */ -+ u8 key_material[MAX_ENCR_KEY_LENGTH]; -+} __packed; -+ -+struct encr_tkip_seqcnt { -+ __le16 low; -+ __le32 high; -+} __packed; -+ -+struct tkip_type_key { -+ /* TKIP Key material. Key type (group or pairwise key) is -+ * determined by flags -+ */ -+ /* in KEY_PARAM_SET structure. */ -+ u8 key_material[MAX_ENCR_KEY_LENGTH]; -+ /* MIC keys */ -+ u8 tkip_tx_mic_key[MIC_KEY_LENGTH]; -+ u8 tkip_rx_mic_key[MIC_KEY_LENGTH]; -+ struct encr_tkip_seqcnt tkip_rsc; -+ struct encr_tkip_seqcnt tkip_tsc; -+} __packed; -+ -+struct aes_type_key { -+ /* AES Key material */ -+ u8 key_material[MAX_ENCR_KEY_LENGTH]; -+} __packed; -+ -+union mwl_key_type { -+ struct wep_type_key wep_key; -+ struct tkip_type_key tkip_key; -+ struct aes_type_key aes_key; -+} __packed; -+ -+struct key_param_set { -+ /* Total length of this structure (Key is variable size array) */ -+ __le16 length; -+ /* Key type - WEP, TKIP or AES-CCMP. */ -+ /* See definitions above */ -+ __le16 key_type_id; -+ /* key flags (ENCR_KEY_FLAG_XXX_ */ -+ __le32 key_info; -+ /* For WEP only - actual key index */ -+ __le32 key_index; -+ /* Size of the key */ -+ __le16 key_len; -+ /* Key material (variable size array) */ -+ union mwl_key_type key; -+ u8 mac_addr[ETH_ALEN]; -+} __packed; -+ -+struct hostcmd_cmd_set_key { -+ struct hostcmd_header cmd_hdr; -+ /* Action type - see encr_action_type */ -+ __le32 action_type; /* encr_action_type */ -+ /* size of the data buffer attached. */ -+ __le32 data_length; -+ /* data buffer - maps to one KEY_PARAM_SET structure */ -+ struct key_param_set key_param; -+} __packed; -+ -+/* HOSTCMD_CMD_BASTREAM */ -+#define BA_TYPE_MASK 0x00000001 -+#define BA_DIRECTION_MASK 0x0000000e -+#define BA_DIRECTION_SHIFT 1 -+ -+#define BA_TYPE_MASK_NDP 0x00000003 -+#define BA_DIRECTION_MASK_NDP 0x0000001c -+#define BA_DIRECTION_SHIFT_NDP 2 -+ -+struct ba_context { -+ __le32 context; -+} __packed; -+ -+/* parameters for block ack creation */ -+struct create_ba_params { -+ /* BA Creation flags - see above */ -+ __le32 flags; -+ /* idle threshold */ -+ __le32 idle_thrs; -+ /* block ack transmit threshold (after how many pkts should we -+ * send BAR?) -+ */ -+ __le32 bar_thrs; -+ /* receiver window size */ -+ __le32 window_size; -+ /* MAC Address of the BA partner */ -+ u8 peer_mac_addr[ETH_ALEN]; -+ /* Dialog Token */ -+ u8 dialog_token; -+ /* TID for the traffic stream in this BA */ -+ u8 tid; -+ /* shared memory queue ID (not sure if this is required) */ -+ u8 queue_id; -+ u8 param_info; -+ /* returned by firmware - firmware context pointer. */ -+ /* this context pointer will be passed to firmware for all -+ * future commands. -+ */ -+ struct ba_context fw_ba_context; -+ u8 reset_seq_no; /** 0 or 1**/ -+ __le16 current_seq; -+ __le32 vht_rx_factor; -+ /* This is for virtual station in Sta proxy mode for V6FW */ -+ u8 sta_src_mac_addr[ETH_ALEN]; -+} __packed; -+ -+/* new transmit sequence number information */ -+struct ba_update_seq_num { -+ /* BA flags - see above */ -+ __le32 flags; -+ /* returned by firmware in the create ba stream response */ -+ struct ba_context fw_ba_context; -+ /* new sequence number for this block ack stream */ -+ __le16 ba_seq_num; -+} __packed; -+ -+struct ba_stream_context { -+ /* BA Stream flags */ -+ __le32 flags; -+ /* returned by firmware in the create ba stream response */ -+ struct ba_context fw_ba_context; -+ u8 tid; -+ u8 peer_mac_addr[ETH_ALEN]; -+} __packed; -+ -+union ba_info { -+ /* information required to create BA Stream... */ -+ struct create_ba_params create_params; -+ /* update starting/new sequence number etc. */ -+ struct ba_update_seq_num updt_seq_num; -+ /* destroy an existing stream... */ -+ struct ba_stream_context destroy_params; -+ /* destroy an existing stream... */ -+ struct ba_stream_context flush_params; -+} __packed; -+ -+struct hostcmd_cmd_bastream { -+ struct hostcmd_header cmd_hdr; -+ __le32 action_type; -+ union ba_info ba_info; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_SPECTRUM_MGMT */ -+struct hostcmd_cmd_set_spectrum_mgmt { -+ struct hostcmd_header cmd_hdr; -+ __le32 spectrum_mgmt; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_POWER_CONSTRAINT */ -+struct hostcmd_cmd_set_power_constraint { -+ struct hostcmd_header cmd_hdr; -+ __le32 power_constraint; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_COUNTRY_CODE */ -+struct domain_chnl_entry { -+ u8 first_chnl_num; -+ u8 chnl_num; -+ u8 max_transmit_pw; -+} __packed; -+ -+struct domain_country_info { -+ u8 country_string[3]; -+ u8 g_chnl_len; -+ struct domain_chnl_entry domain_entry_g[1]; -+ u8 a_chnl_len; -+ struct domain_chnl_entry domain_entry_a[20]; -+} __packed; -+ -+struct hostcmd_cmd_set_country_code { -+ struct hostcmd_header cmd_hdr; -+ __le32 action ; /* 0 -> unset, 1 ->set */ -+ struct domain_country_info domain_info; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_OPTIMIZATION_LEVEL */ -+struct hostcmd_cmd_set_optimization_level { -+ struct hostcmd_header cmd_hdr; -+ u8 opt_level; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_WSC_IE */ -+struct hostcmd_cmd_set_wsc_ie { -+ struct hostcmd_header cmd_hdr; -+ __le16 ie_type; /* 0 -- beacon. or 1 -- probe response. */ -+ __le16 len; -+ u8 data[WSC_IE_MAX_LENGTH]; -+} __packed; -+ -+/* HOSTCMD_CMD_GET_RATETABLE */ -+struct hostcmd_cmd_get_ratetable { -+ struct hostcmd_header cmd_hdr; -+ u8 addr[ETH_ALEN]; -+ u8 type; /* 0: SU, 1: MU */ -+ /* multiply 2 because 2 DWORD in rate info */ -+ __le32 sorted_rates_idx_map[2 * SYSADPT_MAX_RATE_ADAPT_RATES]; -+} __packed; -+ -+/* HOSTCMD_CMD_GET_SEQNO */ -+struct hostcmd_cmd_get_seqno { -+ struct hostcmd_header cmd_hdr; -+ u8 mac_addr[ETH_ALEN]; -+ u8 tid; -+ __le16 seq_no; -+ u8 reserved; -+} __packed; -+ -+/* HOSTCMD_CMD_DWDS_ENABLE */ -+struct hostcmd_cmd_dwds_enable { -+ struct hostcmd_header cmd_hdr; -+ __le32 enable; /* 0 -- Disable. or 1 -- Enable. */ -+} __packed; -+ -+/* HOSTCMD_CMD_FW_FLUSH_TIMER */ -+struct hostcmd_cmd_fw_flush_timer { -+ struct hostcmd_header cmd_hdr; -+ /* 0 -- Disable. > 0 -- holds time value in usecs. */ -+ __le32 value; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_CDD */ -+struct hostcmd_cmd_set_cdd { -+ struct hostcmd_header cmd_hdr; -+ __le32 enable; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_BFTYPE */ -+struct hostcmd_cmd_set_bftype { -+ struct hostcmd_header cmd_hdr; -+ __le32 action; -+ __le32 mode; -+} __packed; -+ -+/* HOSTCMD_CMD_GET_TEMP */ -+struct hostcmd_cmd_get_temp { -+ struct hostcmd_header cmd_hdr; -+ __le32 celcius; -+ __le32 raw_data; -+} __packed; -+ -+/* HOSTCMD_CMD_LED_CTRL */ -+struct hostcmd_cmd_led_ctrl { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; /* 0: GET, 1: SET (only SET is supported) */ -+ u8 led_enable; /* 0: Disable, 1: Enable */ -+ u8 led_control; /* 0: HW 1: SW (only SW is supported) */ -+ u8 led_blink_rate; /* 1: Slow, 2: Medium, 3: Fast blink */ -+ u8 reserved; -+} __packed; -+ -+/* HOSTCMD_CMD_GET_FW_REGION_CODE */ -+struct hostcmd_cmd_get_fw_region_code { -+ struct hostcmd_header cmd_hdr; -+ __le32 status; /* 0 = Found, 1 = Error */ -+ __le32 fw_region_code; -+} __packed; -+ -+/* HOSTCMD_CMD_GET_DEVICE_PWR_TBL */ -+#define HAL_TRPC_ID_MAX 16 -+ -+struct channel_power_tbl { -+ u8 channel; -+ u8 tx_pwr[HAL_TRPC_ID_MAX]; -+ u8 dfs_capable; -+ u8 ax_ant; -+ u8 cdd; -+} __packed; -+ -+struct hostcmd_cmd_get_device_pwr_tbl { -+ struct hostcmd_header cmd_hdr; -+ __le16 status; /* 0 = Found, 1 = Error */ -+ u8 region_code; -+ u8 number_of_channels; -+ __le32 current_channel_index; -+ /* Only for 1 channel, so, 1 channel at a time */ -+ struct channel_power_tbl channel_pwr_tbl; -+} __packed; -+ -+/* HOSTCMD_CMD_SET_RATE_DROP */ -+struct hostcmd_cmd_set_rate_drop { -+ struct hostcmd_header cmd_hdr; -+ __le32 enable; -+ __le32 rate_index; -+ __le32 sta_index; -+} __packed; -+ -+/* HOSTCMD_CMD_NEWDP_DMATHREAD_START */ -+struct hostcmd_cmd_newdp_dmathread_start { -+ struct hostcmd_header cmd_hdr; -+} __packed; -+ -+/* HOSTCMD_CMD_GET_FW_REGION_CODE_SC4 */ -+struct hostcmd_cmd_get_fw_region_code_sc4 { -+ struct hostcmd_header cmd_hdr; -+ __le32 status; /* 0 = Found, 1 = Error */ -+ __le32 fw_region_code; -+} __packed; -+ -+/* HOSTCMD_CMD_GET_DEVICE_PWR_TBL_SC4 */ -+#define HAL_TRPC_ID_MAX_SC4 32 -+#define MAX_GROUP_PER_CHANNEL_5G 39 -+#define MAX_GROUP_PER_CHANNEL_2G 21 -+#define MAX(a, b) (((a) > (b)) ? (a) : (b)) -+#define MAX_GROUP_PER_CHANNEL_RATE \ -+ MAX(MAX_GROUP_PER_CHANNEL_5G, MAX_GROUP_PER_CHANNEL_2G) -+ -+struct channel_power_tbl_sc4 { -+ u8 channel; -+ u8 grp_pwr[MAX_GROUP_PER_CHANNEL_RATE]; -+ u8 tx_pwr[HAL_TRPC_ID_MAX_SC4]; -+ u8 dfs_capable; -+ u8 ax_ant; -+ u8 cdd; -+ u8 rsvd; -+} __packed; -+ -+struct hostcmd_cmd_get_device_pwr_tbl_sc4 { -+ struct hostcmd_header cmd_hdr; -+ __le16 status; /* 0 = Found, 1 = Error */ -+ u8 region_code; -+ u8 number_of_channels; -+ __le32 current_channel_index; -+ /* Only for 1 channel, so, 1 channel at a time */ -+ struct channel_power_tbl_sc4 channel_pwr_tbl; -+} __packed; -+ -+/* HOSTCMD_CMD_QUIET_MODE */ -+struct hostcmd_cmd_quiet_mode { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ __le32 enable; -+ __le32 period; -+ __le32 duration; -+ __le32 next_offset; -+} __packed; -+ -+/* HOSTCMD_CMD_CORE_DUMP_DIAG_MODE */ -+struct hostcmd_cmd_core_dump_diag_mode { -+ struct hostcmd_header cmd_hdr; -+ __le16 status; -+} __packed; -+ -+/* HOSTCMD_CMD_GET_FW_CORE_DUMP */ -+#define MAX_CORE_REGIONS 20 -+#define MAX_CORE_SYMBOLS 30 -+#define MVL_COREDUMP_DIAG_MODE 0x00000001 -+#define MVL_COREDUMP_INCL_EXT 0x00000002 -+#define MAX_CORE_DUMP_BUFFER 2048 -+ -+struct core_region { -+ __le32 address; -+ __le32 length; -+} __packed; -+ -+struct core_symbol { -+ u8 name[16]; -+ __le32 address; -+ __le32 length; -+ __le16 entries; -+} __packed; -+ -+struct coredump { -+ u8 version_major; -+ u8 version_minor; -+ u8 version_patch; -+ u8 hdr_version; -+ u8 num_regions; -+ u8 num_symbols; -+ u8 fill[2]; -+ struct core_region region[MAX_CORE_REGIONS]; -+ struct core_symbol symbol[MAX_CORE_SYMBOLS]; -+ __le32 fill_end[40]; -+} __packed; -+ -+struct coredump_cmd { -+ __le32 context; -+ __le32 buffer; -+ __le32 buffer_len; -+ __le16 size_kb; -+ __le16 flags; -+} __packed; -+ -+struct debug_mem_cmd { -+ __le32 set; -+ __le32 type; -+ __le32 addr; -+ __le32 val; -+} __packed; -+ -+struct hostcmd_cmd_get_fw_core_dump { -+ struct hostcmd_header cmd_hdr; -+ union { -+ struct coredump_cmd coredump; -+ struct debug_mem_cmd debug_mem; -+ } cmd_data; -+ /*Buffer where F/W Copies the Core Dump*/ -+ char buffer[MAX_CORE_DUMP_BUFFER]; -+} __packed; -+ -+struct hostcmd_cmd_get_fw_core_dump_ { -+ struct hostcmd_header cmd_hdr; -+ union { -+ struct coredump_cmd coredump; -+ struct debug_mem_cmd debug_mem; -+ } cmd_data; -+} __packed; -+ -+/* HOSTCMD_CMD_802_11_SLOT_TIME */ -+struct hostcmd_cmd_802_11_slot_time { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ /* 0:long slot; 1:short slot */ -+ __le16 short_slot; -+} __packed; -+ -+/* HOSTCMD_CMD_EDMAC_CTRL */ -+struct hostcmd_cmd_edmac_ctrl { -+ struct hostcmd_header cmd_hdr; -+ __le16 action; -+ __le16 ed_ctrl_2g; -+ __le16 ed_offset_2g; -+ __le16 ed_ctrl_5g; -+ __le16 ed_offset_5g; -+ __le16 ed_bitmap_txq_lock; -+} __packed; -+ -+/* HOSTCMD_CMD_TXPWRLMT_CFG */ -+#define TXPWRLMT_CFG_VERSION_INFO_LEN 0x4 -+#define TXPWRLMT_CFG_MAX_SUBBAND_INFO 0x5 -+#define TXPWRLMT_CFG_SIG_LEN 0x4 -+#define TXPWRLMT_CFG_SIGNATURE 0xA1240E01 -+ -+struct hostcmd_cmd_txpwrlmt_cfg { -+ struct hostcmd_header cmd_hdr; -+ /* Action */ -+ __le16 action; -+ /*Sub band id*/ -+ u8 subband_id; -+ /* Cfg Complete Info*/ -+ u8 cfgComplete; -+ /* Data length */ -+ __le16 data_len; -+ /*number of entries*/ -+ __le16 num_entries; -+ /* Data */ -+ u8 data[1]; -+} __packed; -+ -+struct mwl_txpwrlmt_cfg_entry_hdr { -+ /* subband id */ -+ __le16 id; -+ /* length */ -+ __le16 len; -+ /* number of entries */ -+ __le16 num_entries; -+} __packed; -+ -+/* HOSTCMD_CMD_MCAST_CTS */ -+struct hostcmd_cmd_mcast_cts { -+ struct hostcmd_header cmd_hdr; -+ u8 enable; /* 1:enable, 0:disable */ -+} __packed; -+ -+#endif /* _HOSTCMD_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/pcie/dev.h b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/dev.h -new file mode 100644 -index 000000000000..2c26bff80683 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/dev.h -@@ -0,0 +1,1032 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines device related information. */ -+ -+#ifndef _DEV_H_ -+#define _DEV_H_ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define PCIE_DRV_NAME KBUILD_MODNAME -+#define PCIE_DRV_VERSION "10.3.8.0-20181210" -+ -+#define PCIE_MIN_BYTES_HEADROOM 64 -+#define PCIE_MIN_TX_HEADROOM_KF2 96 -+#define PCIE_NUM_OF_DESC_DATA SYSADPT_TOTAL_TX_QUEUES -+#define PCIE_AMPDU_QUEUES 4 -+#define PCIE_MAX_NUM_TX_DESC 256 -+#define PCIE_TX_QUEUE_LIMIT (3 * PCIE_MAX_NUM_TX_DESC) -+#define PCIE_TX_WAKE_Q_THRESHOLD (2 * PCIE_MAX_NUM_TX_DESC) -+#define PCIE_DELAY_FREE_Q_LIMIT PCIE_MAX_NUM_TX_DESC -+#define PCIE_MAX_NUM_RX_DESC 256 -+#define PCIE_RECEIVE_LIMIT 64 -+ -+enum { -+ IEEE_TYPE_MANAGEMENT = 0, -+ IEEE_TYPE_CONTROL, -+ IEEE_TYPE_DATA -+}; -+ -+#define MAC_REG_ADDR(offset) (offset) -+#define MAC_REG_ADDR_PCI(offset) ((pcie_priv->iobase1 + 0xA000) + offset) -+ -+#define MCU_CCA_CNT MAC_REG_ADDR(0x06A0) -+#define MCU_TXPE_CNT MAC_REG_ADDR(0x06A4) -+#define MCU_LAST_READ MAC_REG_ADDR(0x06A8) -+ -+/* Map to 0x80000000 (Bus control) on BAR0 */ -+#define MACREG_REG_H2A_INTERRUPT_EVENTS 0x00000C18 /* (From host to ARM) */ -+#define MACREG_REG_H2A_INTERRUPT_CAUSE 0x00000C1C /* (From host to ARM) */ -+#define MACREG_REG_H2A_INTERRUPT_MASK 0x00000C20 /* (From host to ARM) */ -+#define MACREG_REG_H2A_INTERRUPT_CLEAR_SEL 0x00000C24 /* (From host to ARM) */ -+#define MACREG_REG_H2A_INTERRUPT_STATUS_MASK 0x00000C28 /* (From host to ARM) */ -+ -+#define MACREG_REG_A2H_INTERRUPT_EVENTS 0x00000C2C /* (From ARM to host) */ -+#define MACREG_REG_A2H_INTERRUPT_CAUSE 0x00000C30 /* (From ARM to host) */ -+#define MACREG_REG_A2H_INTERRUPT_MASK 0x00000C34 /* (From ARM to host) */ -+#define MACREG_REG_A2H_INTERRUPT_CLEAR_SEL 0x00000C38 /* (From ARM to host) */ -+#define MACREG_REG_A2H_INTERRUPT_STATUS_MASK 0x00000C3C /* (From ARM to host) */ -+ -+/* Map to 0x80000000 on BAR1 */ -+#define MACREG_REG_GEN_PTR 0x00000C10 -+#define MACREG_REG_INT_CODE 0x00000C14 -+ -+/* Bit definition for MACREG_REG_A2H_INTERRUPT_CAUSE (A2HRIC) */ -+#define MACREG_A2HRIC_BIT_TX_DONE BIT(0) -+#define MACREG_A2HRIC_BIT_RX_RDY BIT(1) -+#define MACREG_A2HRIC_BIT_OPC_DONE BIT(2) -+#define MACREG_A2HRIC_BIT_MAC_EVENT BIT(3) -+#define MACREG_A2HRIC_BIT_RX_PROBLEM BIT(4) -+#define MACREG_A2HRIC_BIT_RADIO_OFF BIT(5) -+#define MACREG_A2HRIC_BIT_RADIO_ON BIT(6) -+#define MACREG_A2HRIC_BIT_RADAR_DETECT BIT(7) -+#define MACREG_A2HRIC_BIT_ICV_ERROR BIT(8) -+#define MACREG_A2HRIC_BIT_WEAKIV_ERROR BIT(9) -+#define MACREG_A2HRIC_BIT_QUE_EMPTY BIT(10) -+#define MACREG_A2HRIC_BIT_QUE_FULL BIT(11) -+#define MACREG_A2HRIC_BIT_CHAN_SWITCH BIT(12) -+#define MACREG_A2HRIC_BIT_TX_WATCHDOG BIT(13) -+#define MACREG_A2HRIC_BA_WATCHDOG BIT(14) -+/* 15 taken by ISR_TXACK */ -+#define MACREG_A2HRIC_BIT_SSU_DONE BIT(16) -+#define MACREG_A2HRIC_CONSEC_TXFAIL BIT(17) -+ -+#define ISR_SRC_BITS (MACREG_A2HRIC_BIT_RX_RDY | \ -+ MACREG_A2HRIC_BIT_TX_DONE | \ -+ MACREG_A2HRIC_BIT_OPC_DONE | \ -+ MACREG_A2HRIC_BIT_MAC_EVENT | \ -+ MACREG_A2HRIC_BIT_WEAKIV_ERROR | \ -+ MACREG_A2HRIC_BIT_ICV_ERROR | \ -+ MACREG_A2HRIC_BIT_SSU_DONE | \ -+ MACREG_A2HRIC_BIT_RADAR_DETECT | \ -+ MACREG_A2HRIC_BIT_CHAN_SWITCH | \ -+ MACREG_A2HRIC_BIT_TX_WATCHDOG | \ -+ MACREG_A2HRIC_BIT_QUE_EMPTY | \ -+ MACREG_A2HRIC_BA_WATCHDOG | \ -+ MACREG_A2HRIC_CONSEC_TXFAIL) -+ -+#define MACREG_A2HRIC_BIT_MASK ISR_SRC_BITS -+ -+/* Bit definition for MACREG_REG_H2A_INTERRUPT_CAUSE (H2ARIC) */ -+#define MACREG_H2ARIC_BIT_PPA_READY BIT(0) -+#define MACREG_H2ARIC_BIT_DOOR_BELL BIT(1) -+#define MACREG_H2ARIC_BIT_PS BIT(2) -+#define MACREG_H2ARIC_BIT_PSPOLL BIT(3) -+#define ISR_RESET BIT(15) -+#define ISR_RESET_AP33 BIT(26) -+ -+/* Data descriptor related constants */ -+#define EAGLE_RXD_CTRL_DRIVER_OWN 0x00 -+#define EAGLE_RXD_CTRL_DMA_OWN 0x80 -+ -+#define EAGLE_RXD_STATUS_OK 0x01 -+ -+#define EAGLE_TXD_STATUS_IDLE 0x00000000 -+#define EAGLE_TXD_STATUS_OK 0x00000001 -+#define EAGLE_TXD_STATUS_FW_OWNED 0x80000000 -+ -+struct pcie_tx_desc { -+ u8 data_rate; -+ u8 tx_priority; -+ __le16 qos_ctrl; -+ __le32 pkt_ptr; -+ __le16 pkt_len; -+ u8 dest_addr[ETH_ALEN]; -+ __le32 pphys_next; -+ __le32 sap_pkt_info; -+ __le32 rate_info; -+ u8 type; -+ u8 xmit_control; /* bit 0: use rateinfo, bit 1: disable ampdu */ -+ __le16 reserved; -+ __le32 tcpack_sn; -+ __le32 tcpack_src_dst; -+ __le32 reserved1; -+ __le32 reserved2; -+ u8 reserved3[2]; -+ u8 packet_info; -+ u8 packet_id; -+ __le16 packet_len_and_retry; -+ __le16 packet_rate_info; -+ __le32 flags; -+ __le32 status; -+} __packed; -+ -+struct pcie_tx_hndl { -+ struct sk_buff *psk_buff; -+ struct pcie_tx_desc *pdesc; -+ struct pcie_tx_hndl *pnext; -+}; -+ -+/* Receive rate information constants */ -+#define RX_RATE_INFO_FORMAT_11A 0 -+#define RX_RATE_INFO_FORMAT_11B 1 -+#define RX_RATE_INFO_FORMAT_11N 2 -+#define RX_RATE_INFO_FORMAT_11AC 4 -+ -+#define RX_RATE_INFO_HT20 0 -+#define RX_RATE_INFO_HT40 1 -+#define RX_RATE_INFO_HT80 2 -+#define RX_RATE_INFO_HT160 3 -+ -+#define RX_RATE_INFO_LONG_INTERVAL 0 -+#define RX_RATE_INFO_SHORT_INTERVAL 1 -+ -+#define MWL_RX_RATE_FORMAT_MASK 0x0007 -+#define MWL_RX_RATE_NSS_MASK 0x0018 -+#define MWL_RX_RATE_NSS_SHIFT 3 -+#define MWL_RX_RATE_BW_MASK 0x0060 -+#define MWL_RX_RATE_BW_SHIFT 5 -+#define MWL_RX_RATE_GI_MASK 0x0080 -+#define MWL_RX_RATE_GI_SHIFT 7 -+#define MWL_RX_RATE_RT_MASK 0xFF00 -+#define MWL_RX_RATE_RT_SHIFT 8 -+ -+struct pcie_rx_desc { -+ __le16 pkt_len; /* total length of received data */ -+ __le16 rate; /* receive rate information */ -+ __le32 pphys_buff_data; /* physical address of payload data */ -+ __le32 pphys_next; /* physical address of next RX desc */ -+ __le16 qos_ctrl; /* received QosCtrl field variable */ -+ __le16 ht_sig2; /* like name states */ -+ __le32 hw_rssi_info; -+ __le32 hw_noise_floor_info; -+ u8 noise_floor; -+ u8 reserved[3]; -+ u8 rssi; /* received signal strengt indication */ -+ u8 status; /* status field containing USED bit */ -+ u8 channel; /* channel this pkt was received on */ -+ u8 rx_control; /* the control element of the desc */ -+ __le32 reserved1[3]; -+} __packed; -+ -+struct pcie_rx_hndl { -+ struct sk_buff *psk_buff; /* associated sk_buff for Linux */ -+ struct pcie_rx_desc *pdesc; -+ struct pcie_rx_hndl *pnext; -+}; -+ -+struct pcie_desc_data { -+ dma_addr_t pphys_tx_ring; /* ptr to first TX desc (phys.) */ -+ struct pcie_tx_desc *ptx_ring; /* ptr to first TX desc (virt.) */ -+ struct pcie_tx_hndl *tx_hndl; -+ struct pcie_tx_hndl *pnext_tx_hndl;/* next TX handle that can be used */ -+ struct pcie_tx_hndl *pstale_tx_hndl;/* the staled TX handle */ -+ dma_addr_t pphys_rx_ring; /* ptr to first RX desc (phys.) */ -+ struct pcie_rx_desc *prx_ring; /* ptr to first RX desc (virt.) */ -+ struct pcie_rx_hndl *rx_hndl; -+ struct pcie_rx_hndl *pnext_rx_hndl;/* next RX handle that can be used */ -+ u32 wcb_base; /* FW base offset for registers */ -+ u32 rx_desc_write; /* FW descriptor write position */ -+ u32 rx_desc_read; /* FW descriptor read position */ -+ u32 rx_buf_size; /* length of the RX buffers */ -+}; -+ -+/* DMA header used by firmware and hardware. */ -+struct pcie_dma_data { -+ __le16 fwlen; -+ struct ieee80211_hdr wh; -+ char data[0]; -+} __packed; -+ -+/* New Data Path */ -+#define MACREG_REG_SCRATCH3 0x00000C44 -+#define MACREG_REG_TXSENDHEAD 0x00000CD0 -+#define MACREG_REG_TXSEDNTAIL 0x00000CD4 -+#define MACREG_REG_TXDONEHEAD 0x00000CD8 -+#define MACREG_REG_TXDONETAIL 0x00000CDC -+#define MACREG_REG_RXDESCHEAD 0x00000CE0 -+#define MACREG_REG_RXDESCTAIL 0x00000CE4 -+#define MACREG_REG_RXDONEHEAD 0x00000CE8 -+#define MACREG_REG_RXDONETAIL 0x00000CEC -+#define MACREG_REG_ACNTHEAD 0x00000CF0 -+#define MACREG_REG_ACNTTAIL 0x00000CF4 -+ -+/* Buff removed from Tx Send Ring */ -+#define MACREG_A2HRIC_TX_DESC_TAIL_RDY (1<<9) -+/* Buff added to Tx Done Ring */ -+#define MACREG_A2HRIC_TX_DONE_HEAD_RDY (1<<10) -+/* Records added to Accounting Ring */ -+#define MACREG_A2HRIC_ACNT_HEAD_RDY (1<<12) -+/* Buff removed from Rx Desc Ring */ -+#define MACREG_A2HRIC_RX_DESC_TAIL_RDY (1<<17) -+/* Buff added to Rx Done Ring */ -+#define MACREG_A2HRIC_RX_DONE_HEAD_RDY (1<<18) -+#define MACREG_A2HRIC_NEWDP_DFS (1<<19) -+#define MACREG_A2HRIC_NEWDP_CHANNEL_SWITCH (1<<20) -+ -+#define ISR_SRC_BITS_NDP ((MACREG_A2HRIC_ACNT_HEAD_RDY) | \ -+ (MACREG_A2HRIC_RX_DONE_HEAD_RDY) | \ -+ (MACREG_A2HRIC_NEWDP_DFS) | \ -+ (MACREG_A2HRIC_NEWDP_CHANNEL_SWITCH)) -+ -+#define MACREG_A2HRIC_BIT_MASK_NDP ISR_SRC_BITS_NDP -+ -+#define MIN_BYTES_RX_HEADROOM (64 + 2) -+#define AMPDU_QUEUES_NDP (SYSADPT_MAX_STA_SC4 * \ -+ SYSADPT_MAX_TID) -+#define MAX_NUM_TX_DESC 1024 -+#define MAX_NUM_RX_DESC (1024 * 16) -+#define MAX_TX_RING_SEND_SIZE (4 * MAX_NUM_TX_DESC) -+#define MAX_TX_RING_DONE_SIZE MAX_NUM_TX_DESC -+#define MAX_RX_RING_SEND_SIZE MAX_NUM_RX_DESC -+#define MAX_RX_RING_DONE_SIZE MAX_NUM_RX_DESC -+#define DEFAULT_ACNT_RING_SIZE 0x10000 -+#define MAX_AGGR_SIZE 1900 -+#define TX_QUEUE_LIMIT MAX_NUM_TX_DESC -+#define TX_WAKE_Q_THRESHOLD (MAX_NUM_TX_DESC - 256) -+ -+/* RateCode usage notes: -+ * * General -+ * * No error checking is provided on RateCodes, so usage of invalid values -+ * or rates not supported by HW can result in undefined operation. -+ * * Some values are not allowed by Std, but are included to sanitize the -+ * table; -+ * * MaxPwr should only be used for rates that can be sent using Max Power, -+ * such as for TxEVM limits or regulatory. It is only valid for Host -+ * Generated frames, and not for DRA, etc. -+ * * VHT -+ * * Need to reconsile MU. -+ * * HT -+ * * MCS and SS are made to mimic 11ac, so MCS=mcs[2:0] and SS=mcs[4:3]; -+ * * MCS32 is selected by providing MCS=10; -+ * * Legacy -+ * * MCS0..7 = 6/9/12/18/24/36/48/54; -+ * * MCS8..15 = 1S/1L/2S/2L/5.5S/5.5L/11S/11L; -+ * * BW is used to request legacy duplicate modes; -+ */ -+#define RATECODE_DEFAULT 0xFFFF /* Don't override the Rate */ -+#define RATECODE_TYPE_MASK 0xC000 /* Mask to extract Type */ -+#define RATECODE_TYPE_SHIFT 14 /* Shift to extract Type */ -+#define RATECODE_TYPE_VHT 0x8000 /* Use VHT rates */ -+#define RATECODE_TYPE_HT 0x4000 /* Use HT rates */ -+#define RATECODE_TYPE_LEGACY 0x0000 /* Use Legacy (a/b/g) rates */ -+#define RATECODE_MAXPWR 0x2000 /* Send at Max Power / Off Channel */ -+#define RATECODE_RSVD 0x1000 /* Unused */ -+#define RATECODE_STBC 0x0800 /* Use Space Time Block Codes */ -+#define RATECODE_BFMR 0x0400 /* Use Beamforming */ -+#define RATECODE_SS_MASK 0x0300 /* Mask to extract nSS-1 */ -+#define RATECODE_SS_SHIFT 8 /* Shift to extract nSS-1 */ -+#define RATECODE_MCS_MASK 0x00F0 /* Mask to extract MCS rate */ -+#define RATECODE_MCS_SHIFT 4 /* Shift to extract MCS rate */ -+#define RATECODE_BW_MASK 0x000C /* Mask to extract Channel BW */ -+#define RATECODE_BW_SHIFT 2 /* Shift to extract Channel BW */ -+#define RATECODE_BW_160MHZ 0x000C /* Send 160M wide packet (or 80+80) */ -+#define RATECODE_BW_80MHZ 0x0008 /* Send 80M wide packet */ -+#define RATECODE_BW_40MHZ 0x0004 /* Send 40M wide packet */ -+#define RATECODE_BW_20MHZ 0x0000 /* Send 20M wide packet */ -+#define RATECODE_LDPC 0x0002 /* Use Low Density Parity Codes */ -+#define RATECODE_SGI 0x0001 /* Use Short Guard Interval */ -+ -+#define TXRING_CTRL_LEN_SHIFT 0 /* PCIe Payload size (Starts w/ SNAP) */ -+#define TXRING_CTRL_LEN_MASK 0x3FFF /* PCIe Payload size (Starts w/ SNAP) */ -+#define TXRING_CTRL_QID_SHIFT 14 /* Queue ID (STA*UP, Mcast, MC2UC, etc)*/ -+#define TXRING_CTRL_QID_MASK 0xFFF /* Queue ID (STA*UP, Mcast, MC2UC, etc)*/ -+#define TXRING_CTRL_TAG_SHIFT 26 /* Tags for special Processing */ -+#define TXRING_CTRL_TAG_MASK 0x3F /* Tags for special Processing */ -+#define TXRING_CTRL_TAG_MGMT 0x01 /* Has Host generated dot11 Header */ -+#define TXRING_CTRL_TAG_EAP 0x02 /* Tag for EAPOL frames */ -+#define TXRING_CTRL_TAG_TCP_ACK 0x4 -+#define TXRING_CTRL_TAG_RSVD 0x3C /* Unused */ -+ -+struct tx_info { /* Tx INFO used by MAC HW */ -+ __le32 reserved0[10]; -+ __le32 rate_info; -+ __le32 reserved1[14]; -+} __packed; -+ -+struct pcie_tx_desc_ndp { -+ union { /* Union for Tx DA/SA or Mgmt Overrides */ -+ struct { /* Fields for Data frames */ -+ u8 da[ETH_ALEN]; /* L2 Destination Address */ -+ u8 sa[ETH_ALEN]; /* L2 Source Address */ -+ }; -+ struct { /* Fields when marked as Mgmt */ -+ __le16 rate_code; /* Rate Code: Table + Index */ -+ u8 max_retry; -+ u8 pad[5]; /* Unused */ -+ __le32 call_back; /* Used for Packet returned to FW */ -+ }; -+ } u; -+ __le32 ctrl; /* Bit fields (TXRING_CTRL_*) */ -+ __le32 data; /* PCIe Payload Pointer (Starts w/ SNAP) */ -+ __le32 user; /* Value returned to Host when done */ -+ __le32 tcp_dst_src; -+ __le32 tcp_sn; -+} __packed; -+ -+struct tx_ring_done { -+ __le32 user; -+} __packed; -+ -+#define RXRING_CTRL_CASE_SHIFT 0 /* What is in the buffer(RXRING_CASE_*) */ -+#define RXRING_CTRL_CASE_MASK 0x1F /* What is in the buffer(RXRING_CASE_*) */ -+#define RXRING_CTRL_STA_SHIFT 5 /* STA information (or Mcast group) */ -+#define RXRING_CTRL_STA_MASK 0x1FF /* STA information (or Mcast group) */ -+#define RXRING_CTRL_STA_UNKNOWN 0x1FF /* STA Idx for packets from Unknown STA */ -+#define RXRING_CTRL_STA_FROMDS 0x1FE /* STA Idx for packets from DS */ -+#define RXRING_CTRL_TID_SHIFT 14 /* TID/UP for QoS Data frames */ -+#define RXRING_CTRL_TID_MASK 0xF /* TID/UP for QoS Data frames */ -+#define RXRING_CTRL_KEY_SHIFT 18 /* Key Type used (KEY_TYPE_*) */ -+#define RXRING_CTRL_KEY_MASK 0xF /* Key Type used (KEY_TYPE_*) */ -+#define RXRING_CTRL_TRUNC (1UL<<31) /* Packet Truncated */ -+ -+/* Rx Buffer Formats -+ * Each Case listed above will indicate the format used, and each format will -+ * carry their length in the packet buffer. Should the packet be too big for -+ * the buffer, it will be truncated, but the full length will still be -+ * indicated. Currently only a single, fixed size Rx Pool is envisioned. -+ * -+ * Fmt0 is used for Slow path, when some processing of dot11 headers may still -+ * be required, or for promiscuous mode captures. It is in the HW RxINFO -+ * (rx_info_t) format including dot11_t followed by Payload. The Length field in -+ * the dot11_t is updated to only include Payload bytes, and is in Little Endian -+ * format. If the frame is too big, it is truncated to the buffer size, and -+ * promiscuous packets may also be configured for truncation to reduce load. The -+ * mark field is replaced with software status, and the RSSI will be updated to -+ * apply Rx calibration. -+ * -+ * Fmt1 is used for fast path Data packets in the run state, where all rx -+ * processing of dot11 headers is performed from radio FW. It has an AMSDU -+ * centric format of DA/SA/Len followed by SNAP, with the Length in Big Endian -+ * Format. In most cases conversion to Ethernet format is accomplished by -+ * copying 12 bytes to drop 8 bytes in the middle. -+ * -+ * Fmt2 is used for fast path AMSDU packets that are malformed. They just -+ * contain the dot11 header (dot11_t) containing the residual Len (Little -+ * Endian) after any valid MSDU have been extracted. The header is followed by -+ * the first invalid MSDU which will be truncated to 64 bytes. -+ */ -+enum { /* What is in Rx Buffer and why it was delivered */ -+ /* Data for Assoc Clients in Run State on Channel [Fmt1] */ -+ RXRING_CASE_FAST_DATA, -+ RXRING_CASE_FAST_BAD_AMSDU, /* Fast Data with bad AMSDU Header [Fmt2] */ -+ /* Data for Assoc Clients using unconfigured queue [Fmt0] */ -+ RXRING_CASE_SLOW_NOQUEUE, -+ /* Data for Assoc Clients not matching Run State [Fmt0] */ -+ RXRING_CASE_SLOW_NORUN, -+ /* Data for filtered Multicast groups [Fmt0] */ -+ RXRING_CASE_SLOW_MCAST, -+ RXRING_CASE_SLOW_BAD_STA, /* Data for Unassoc Clients [Fmt0] */ -+ RXRING_CASE_SLOW_BAD_MIC, /* Decrypt failure [Fmt0] */ -+ RXRING_CASE_SLOW_BAD_PN, /* Decrypt PN replay [Fmt0] */ -+ RXRING_CASE_SLOW_MGMT, /* Mgmt traffic to this AP or Bcast [Fmt0]*/ -+ RXRING_CASE_SLOW_PROMISC, /* Packets captured promiscuously [Fmt0] */ -+ RXRING_CASE_SLOW_DEL_DONE, /* Client has been deleted [N/A] */ -+ RXRING_CASE_DROP, /* Buffer returned to Host [N/A] */ -+}; -+ -+enum { /* Type of Key */ -+ KEY_TYPE_NONE, /* Bypass (never stored in real keys) */ -+ KEY_TYPE_WEP40, /* WEP with 40 bit key + 24 bit IV = 64 */ -+ KEY_TYPE_WEP104, /* WEP with 104 bit key + 24 bit IV = 128 */ -+ KEY_TYPE_TKIP, /* TKIP */ -+ KEY_TYPE_CCMP128, /* CCMP with 128 bit Key */ -+ KEY_TYPE_CCMP256, /* CCMP with 256 bit Key + 16 byte MIC */ -+ KEY_TYPE_WAPI, /* WAPI */ -+ KEY_TYPE_UNKNOWN, /* Not known what key was used (Rx Only) */ -+ KEY_TYPE_GCMP128, /* GCMP with 128 bit Key */ -+ KEY_TYPE_GCMP256, /* GCMP with 256 bit Key + 16 byte MIC */ -+}; -+ -+#define RXINFO_RSSI_X_SHIFT 24 -+#define RXINFO_RSSI_X_MASK 0xFF -+#define RXINFO_HT_SIG1_SHIFT 0 -+#define RXINFO_HT_SIG1_MASK 0xFFFFFF -+#define RXINFO_HT_SIG2_SHIFT 0 -+#define RXINFO_HT_SIG2_MASK 0x3FFFF -+#define RXINFO_RATE_SHIFT 24 -+#define RXINFO_RATE_MASK 0xFF -+#define RXINFO_NF_A_SHIFT 12 -+#define RXINFO_NF_A_MASK 0xFFF -+#define RXINFO_NF_B_SHIFT 0 -+#define RXINFO_NF_B_MASK 0xFFF -+#define RXINFO_NF_C_SHIFT 12 -+#define RXINFO_NF_C_MASK 0xFFF -+#define RXINFO_NF_D_SHIFT 0 -+#define RXINFO_NF_D_MASK 0xFFF -+#define RXINFO_PARAM_SHIFT 0 -+#define RXINFO_PARAM_MASK 0xFFFFFF -+ -+struct rx_info { /* HW Rx buffer */ -+ __le32 reserved0[2]; -+ __le32 rssi_x; -+ __le32 reserved1[2]; -+ __le32 ht_sig1; -+ __le32 ht_sig2_rate; -+ __le32 reserved2[6]; -+ __le32 nf_a_b; -+ __le32 nf_c_d; -+ __le32 reserved3[6]; -+ __le32 param; -+ __le32 reserved4[2]; -+ __le32 hdr[0]; /* Len from HW includes rx_info w/ hdr */ -+} __packed; -+ -+struct pcie_rx_desc_ndp { /* ToNIC Rx Empty Buffer Ring Entry */ -+ __le32 data; /* PCIe Payload Pointer */ -+ __le32 user; /* Value returned to Host when done */ -+} __packed; -+ -+struct rx_ring_done { /* FromNIC Rx Done Ring Entry */ -+ __le32 user; /* Value returned to Host when done */ -+ __le32 tsf; /* Rx Radio Timestamp from MAC */ -+ __le32 ctrl; /* Bit fields (RXRING_CTRL_*) */ -+} __packed; -+ -+struct pcie_desc_data_ndp { -+ dma_addr_t pphys_tx_ring; /* ptr to first TX desc (phys.) */ -+ struct pcie_tx_desc_ndp *ptx_ring;/* ptr to first TX desc (virt.) */ -+ dma_addr_t pphys_rx_ring; /* ptr to first RX desc (phys.) */ -+ struct pcie_rx_desc_ndp *prx_ring;/* ptr to first RX desc (virt.) */ -+ u32 wcb_base; /* FW base offset for registers */ -+ u32 rx_buf_size; /* length of the RX buffers */ -+ u32 tx_sent_tail; /* index to the TX desc FW used */ -+ u32 tx_sent_head; /* index to next TX desc to be used*/ -+ u32 tx_done_tail; /* index to Tx Done queue tail */ -+ /* keept the skb owned by fw */ -+ dma_addr_t pphys_tx_buflist[MAX_TX_RING_SEND_SIZE]; -+ struct sk_buff *tx_vbuflist[MAX_TX_RING_SEND_SIZE]; -+ u32 tx_vbuflist_idx; /* idx to empty slot in tx_vbuflist*/ -+ struct sk_buff *rx_vbuflist[MAX_NUM_RX_DESC]; -+ struct tx_ring_done *ptx_ring_done; -+ dma_addr_t pphys_tx_ring_done; /* ptr to first TX done desc (phys.) */ -+ struct rx_ring_done *prx_ring_done; -+ dma_addr_t pphys_rx_ring_done; /* ptr to first RX done desc (phys.) */ -+ dma_addr_t pphys_acnt_ring; /* ptr to first account record (phys.)*/ -+ u8 *pacnt_ring; /* ptr to first accounting record */ -+ u32 tx_desc_busy_cnt; -+ u8 *pacnt_buf; -+ u32 acnt_ring_size; -+}; -+ -+struct ndp_rx_counter { -+ u32 fast_data_cnt; -+ u32 fast_bad_amsdu_cnt; -+ u32 slow_noqueue_cnt; -+ u32 slow_norun_cnt; -+ u32 slow_mcast_cnt; -+ u32 slow_bad_sta_cnt; -+ u32 slow_bad_mic_cnt; -+ u32 slow_bad_pn_cnt; -+ u32 slow_mgmt_cnt; -+ u32 slow_promisc_cnt; -+ u32 drop_cnt; -+ u32 offch_promisc_cnt; -+ u32 mu_pkt_cnt; -+}; -+ -+/* KF2 - 88W8997 */ -+#define PCIE_MAX_TXRX_BD 0x20 -+/* PCIE read data pointer for queue 0 and 1 */ -+#define PCIE_RD_DATA_PTR_Q0_Q1 0xC1A4 /* 0x8000C1A4 */ -+/* PCIE read data pointer for queue 2 and 3 */ -+#define PCIE_RD_DATA_PTR_Q2_Q3 0xC1A8 /* 0x8000C1A8 */ -+/* PCIE write data pointer for queue 0 and 1 */ -+#define PCIE_WR_DATA_PTR_Q0_Q1 0xC174 /* 0x8000C174 */ -+/* PCIE write data pointer for queue 2 and 3 */ -+#define PCIE_WR_DATA_PTR_Q2_Q3 0xC178 /* 0x8000C178 */ -+ -+/* TX buffer description read pointer */ -+#define REG_TXBD_RDPTR PCIE_RD_DATA_PTR_Q0_Q1 -+/* TX buffer description write pointer */ -+#define REG_TXBD_WRPTR PCIE_WR_DATA_PTR_Q0_Q1 -+ -+#define PCIE_TX_START_PTR 16 -+ -+#define PCIE_TXBD_MASK 0x0FFF0000 -+#define PCIE_TXBD_WRAP_MASK 0x1FFF0000 -+ -+#define PCIE_BD_FLAG_RX_ROLLOVER_IND BIT(12) -+#define PCIE_BD_FLAG_TX_START_PTR BIT(16) -+#define PCIE_BD_FLAG_TX_ROLLOVER_IND BIT(28) -+#define PCIE_BD_FLAG_TX2_START_PTR BIT(0) -+#define PCIE_BD_FLAG_TX2_ROLLOVER_IND BIT(12) -+ -+#define PCIE_BD_FLAG_FIRST_DESC BIT(0) -+#define PCIE_BD_FLAG_LAST_DESC BIT(1) -+ -+#define PCIE_TX_WCB_FLAGS_DONT_ENCRYPT 0x00000001 -+#define PCIE_TX_WCB_FLAGS_NO_CCK_RATE 0x00000002 -+ -+#define PCIE_TXBD_NOT_FULL(wrptr, rdptr) \ -+ (((wrptr & PCIE_TXBD_MASK) != (rdptr & PCIE_TXBD_MASK)) \ -+ || ((wrptr & PCIE_BD_FLAG_TX_ROLLOVER_IND) == \ -+ (rdptr & PCIE_BD_FLAG_TX_ROLLOVER_IND))) -+ -+struct pcie_data_buf { -+ /* Buffer descriptor flags */ -+ __le16 flags; -+ /* Offset of fragment/pkt to start of ip header */ -+ __le16 offset; -+ /* Fragment length of the buffer */ -+ __le16 frag_len; -+ /* Length of the buffer */ -+ __le16 len; -+ /* Physical address of the buffer */ -+ __le64 paddr; -+ /* Reserved */ -+ __le32 reserved; -+} __packed; -+ -+struct pcie_pfu_dma_data { -+ struct pcie_tx_desc tx_desc; -+ struct pcie_dma_data dma_data; -+} __packed; -+ -+struct pcie_priv { -+ struct mwl_priv *mwl_priv; -+ struct pci_dev *pdev; -+ void __iomem *iobase0; /* MEM Base Address Register 0 */ -+ void __iomem *iobase1; /* MEM Base Address Register 1 */ -+ u32 next_bar_num; -+ -+ struct sk_buff_head txq[PCIE_NUM_OF_DESC_DATA]; -+ -+ spinlock_t int_mask_lock ____cacheline_aligned_in_smp; -+ struct tasklet_struct tx_task; -+ struct tasklet_struct tx_done_task; -+ struct tasklet_struct rx_task; -+ struct tasklet_struct qe_task; -+ unsigned int tx_head_room; -+ int txq_limit; -+ int txq_wake_threshold; -+ bool is_tx_schedule; -+ bool is_tx_done_schedule; -+ int recv_limit; -+ bool is_rx_schedule; -+ bool is_qe_schedule; -+ u32 qe_trig_num; -+ unsigned long qe_trig_time; -+ -+ /* various descriptor data */ -+ /* for tx descriptor data */ -+ spinlock_t tx_desc_lock ____cacheline_aligned_in_smp; -+ struct pcie_desc_data desc_data[PCIE_NUM_OF_DESC_DATA]; -+ int delay_q_idx; -+ struct sk_buff *delay_q[PCIE_DELAY_FREE_Q_LIMIT]; -+ /* number of descriptors owned by fw at any one time */ -+ int fw_desc_cnt[PCIE_NUM_OF_DESC_DATA]; -+ -+ /* new data path */ -+ struct pcie_desc_data_ndp desc_data_ndp; -+ int tx_done_cnt; -+ struct ieee80211_sta *sta_link[SYSADPT_MAX_STA_SC4 + 1]; -+ struct sk_buff_head rx_skb_trace; -+ struct ndp_rx_counter rx_cnts; -+ u32 rx_skb_unlink_err; -+ u32 signature_err; -+ u32 recheck_rxringdone; -+ u32 acnt_busy; -+ u32 acnt_wrap; -+ u32 acnt_drop; -+ -+ /* KF2 - 88W8997 */ -+ struct firmware *cal_data; -+ /* Write pointer for TXBD ring */ -+ u32 txbd_wrptr; -+ /* Shadow copy of TXBD read pointer */ -+ u32 txbd_rdptr; -+ /* TXBD ring size */ -+ u32 txbd_ring_size; -+ /* Virtual base address of txbd_ring */ -+ u8 *txbd_ring_vbase; -+ /* Physical base address of txbd_ring */ -+ dma_addr_t txbd_ring_pbase; -+ /* Ring of buffer descriptors for TX */ -+ struct pcie_data_buf *txbd_ring[PCIE_MAX_TXRX_BD]; -+ struct sk_buff *tx_buf_list[PCIE_MAX_TXRX_BD]; -+}; -+ -+enum { /* Definition of accounting record codes */ -+ ACNT_CODE_BUSY = 0, /* Marked busy until filled in */ -+ ACNT_CODE_WRAP, /* Used to pad when wrapping */ -+ ACNT_CODE_DROP, /* Count of dropped records */ -+ ACNT_CODE_TX_ENQUEUE, /* TXINFO when added to TCQ (acnt_tx_s) */ -+ ACNT_CODE_RX_PPDU, /* RXINFO for each PPDu (acnt_rx_s) */ -+ ACNT_CODE_TX_FLUSH, /* Flush Tx Queue */ -+ ACNT_CODE_RX_RESET, /* Channel Change / Rx Reset */ -+ ACNT_CODE_TX_RESET, /* TCQ reset */ -+ ACNT_CODE_QUOTE_LEVEL,/* Quota Level changes */ -+ ACNT_CODE_TX_DONE, /* Tx status when done */ -+ ACNT_CODE_RA_STATS, /* rateinfo PER (acnt_ra_s) */ -+ ACNT_CODE_BA_STATS, /* BA stats (acnt_ba_s) */ -+ ACNT_CODE_BF_MIMO_CTRL,/* BF Mimo Ctrl Field Log (acnt_bf_mimo_ctrl_s)*/ -+}; -+ -+struct acnt_s { /* Baseline Accounting Record format */ -+ __le16 code; /* Unique code for each type */ -+ u8 len; /* Length in DWORDS, including header */ -+ u8 pad; /* Alignment for generic, but specific can reuse*/ -+ __le32 tsf; /* Timestamp for Entry (when len>1) */ -+} __packed; -+ -+struct acnt_tx_s { /* Accounting Record For Tx (at Enqueue time) */ -+ __le16 code; /* Unique code for each type */ -+ u8 len; /* Length in DWORDS, including header */ -+ u8 tcq; /* Which TCQ was used */ -+ __le32 tsf; /* Timestamp for Entry (when len>1) */ -+ __le64 bitmap; /* Map of SeqNr when AMPDU */ -+ __le16 air_time; /* Air Time used by PPDU */ -+ __le16 npkts; /* Number of Descriptors sent (AMPDU&AMSDU) */ -+ __le16 qid; /* Transmit Queue ID */ -+ __le16 latency; /* Latency of oldest frame in AMPDU (128us) */ -+ __le16 rate1; /* Rate Code for sending data */ -+ __le16 rate2; /* Rate Code for sending RTS/CTS protection */ -+ u8 rate_tbl_index; /* Rate table index for this TxInfo rate */ -+ u8 type; /* SU:0 or MU:1 */ -+ u8 pad[1]; /* Unused */ -+ u8 retries; /* Number of retries of oldest frame in AMPDU */ -+ __le32 tx_cnt; /* No. of pkt sent */ -+ struct tx_info tx_info;/* Transmit parameters used for 1st MPDU/AMPDU */ -+ struct pcie_dma_data hdr;/* Dot11 header used for 1st MPDU in AMPDU */ -+ u8 payload[0]; /* Variable Payload by use case */ -+} __packed; -+ -+struct acnt_rx_s { /* Accounting Record for Rx PPDU */ -+ __le16 code; /* Unique code for each type */ -+ u8 len; /* Length in DWORDS, including header */ -+ u8 flags; /* Flags (ACNTRX_*) */ -+ __le32 tsf; /* Timestamp for Entry (when len>1) */ -+ __le64 bitmap; /* Map of SeqNr when AMPDU */ -+ __le16 air_time; /* Air Time used by PPDU (no CSMA overhead) */ -+ __le16 rate; /* Rate Code for receiving data */ -+ struct rx_info rx_info;/* Receive parameters from 1st valid MPDU/AMPDU*/ -+} __packed; -+ -+struct acnt_ra_s { /* Accounting Record w/ rateinfo PER */ -+ __le16 code; /* Unique code for each type */ -+ u8 len; /* Length in DWORDS, including header */ -+ u8 per; /* PER for this rateinfo */ -+ __le32 tsf; /* Timestamp for Entry (when len>1) */ -+ __le16 stn_id; /* sta index this rateinfo is tied to */ -+ u8 type; /* SU:0 or MU:1 */ -+ u8 rate_tbl_index; /* ratetbl index */ -+ __le32 rate_info; /* rateinfo for this ratetbl index */ -+ __le32 tx_attempt_cnt;/* Total tx pkt during rate adapt interval */ -+} __packed; -+ -+struct acnt_ba_s { /* Accounting Record w/ rateinfo PER */ -+ __le16 code; /* Unique code for each type */ -+ u8 len; /* Length in DWORDS, including header */ -+ u8 ba_hole; /* Total missing pkt in a BA */ -+ __le32 tsf; /* Timestamp for Entry (when len>1) */ -+ __le16 stnid; /* sta index for this BA */ -+ u8 no_ba; /* No BA received */ -+ u8 ba_expected; /* Total expected pkt to be BA'd */ -+ u8 type; /* SU:0 or MU:1 */ -+ u8 pad[3]; /* Unused */ -+} __packed; -+ -+struct acnt_bf_mimo_ctrl_s {/* Accounting Record w/ BF MIMO Control Field Data*/ -+ __le16 code; /* Unique code for each type */ -+ u8 len; /* Length in DWORDS, including header */ -+ u8 type; /* SU:0, MU:1 */ -+ __le32 tsf; /* Timestamp for Entry (when len>1) */ -+ u8 rec_mac[6]; /* Received Packet Source MAC Address */ -+ __le16 pad; /* Padding */ -+ __le32 mimo_ctrl; /* BF MIMO Control Field Data */ -+ __le64 comp_bf_rep; /* First 8 bytes of Compressed BF Report */ -+} __packed; -+ -+static inline void pcie_tx_add_dma_header(struct mwl_priv *priv, -+ struct sk_buff *skb, -+ int head_pad, -+ int tail_pad) -+{ -+ struct ieee80211_hdr *wh; -+ int dma_hdrlen; -+ int hdrlen; -+ int reqd_hdrlen; -+ int needed_room; -+ struct pcie_dma_data *dma_data; -+ -+ dma_hdrlen = (priv->chip_type == MWL8997) ? -+ sizeof(struct pcie_pfu_dma_data) : -+ sizeof(struct pcie_dma_data); -+ -+ /* Add a firmware DMA header; the firmware requires that we -+ * present a 2-byte payload length followed by a 4-address -+ * header (without QoS field), followed (optionally) by any -+ * WEP/ExtIV header (but only filled in for CCMP). -+ */ -+ wh = (struct ieee80211_hdr *)skb->data; -+ -+ hdrlen = ieee80211_hdrlen(wh->frame_control); -+ -+ reqd_hdrlen = dma_hdrlen + head_pad; -+ -+ if (hdrlen != reqd_hdrlen) { -+ needed_room = reqd_hdrlen - hdrlen; -+ if (skb_headroom(skb) < needed_room) { -+ wiphy_debug(priv->hw->wiphy, "headroom is short: %d %d", -+ skb_headroom(skb), needed_room); -+ skb_cow(skb, needed_room); -+ } -+ skb_push(skb, needed_room); -+ } -+ -+ if (ieee80211_is_data_qos(wh->frame_control)) -+ hdrlen -= IEEE80211_QOS_CTL_LEN; -+ -+ if (priv->chip_type == MWL8997) -+ dma_data = &((struct pcie_pfu_dma_data *)skb->data)->dma_data; -+ else -+ dma_data = (struct pcie_dma_data *)skb->data; -+ -+ if (wh != &dma_data->wh) -+ memmove(&dma_data->wh, wh, hdrlen); -+ -+ if (hdrlen != sizeof(dma_data->wh)) -+ memset(((void *)&dma_data->wh) + hdrlen, 0, -+ sizeof(dma_data->wh) - hdrlen); -+ -+ /* Firmware length is the length of the fully formed "802.11 -+ * payload". That is, everything except for the 802.11 header. -+ * This includes all crypto material including the MIC. -+ */ -+ dma_data->fwlen = -+ cpu_to_le16(skb->len - dma_hdrlen + tail_pad); -+} -+ -+static inline void pcie_tx_encapsulate_frame(struct mwl_priv *priv, -+ struct sk_buff *skb, -+ struct ieee80211_key_conf *k_conf, -+ bool *ccmp) -+{ -+ int head_pad = 0; -+ int data_pad = 0; -+ -+ /* Make sure the packet header is in the DMA header format (4-address -+ * without QoS), and add head & tail padding when HW crypto is enabled. -+ * -+ * We have the following trailer padding requirements: -+ * - WEP: 4 trailer bytes (ICV) -+ * - TKIP: 12 trailer bytes (8 MIC + 4 ICV) -+ * - CCMP: 8 trailer bytes (MIC) -+ */ -+ -+ if (k_conf) { -+ head_pad = k_conf->iv_len; -+ -+ switch (k_conf->cipher) { -+ case WLAN_CIPHER_SUITE_WEP40: -+ case WLAN_CIPHER_SUITE_WEP104: -+ data_pad = 4; -+ break; -+ case WLAN_CIPHER_SUITE_TKIP: -+ data_pad = 12; -+ break; -+ case WLAN_CIPHER_SUITE_CCMP: -+ data_pad = 8; -+ if (ccmp) -+ *ccmp = true; -+ break; -+ } -+ } -+ -+ pcie_tx_add_dma_header(priv, skb, head_pad, data_pad); -+} -+ -+static inline void pcie_tx_prepare_info(struct mwl_priv *priv, u32 rate, -+ struct ieee80211_tx_info *info) -+{ -+ u32 format, bandwidth, short_gi, rate_id; -+ -+ ieee80211_tx_info_clear_status(info); -+ -+ info->status.rates[0].idx = -1; -+ info->status.rates[0].count = 0; -+ info->status.rates[0].flags = 0; -+ info->flags &= ~IEEE80211_TX_CTL_AMPDU; -+ info->flags |= IEEE80211_TX_STAT_ACK; -+ -+ if (rate) { -+ /* Prepare rate information */ -+ format = rate & MWL_TX_RATE_FORMAT_MASK; -+ bandwidth = -+ (rate & MWL_TX_RATE_BANDWIDTH_MASK) >> -+ MWL_TX_RATE_BANDWIDTH_SHIFT; -+ short_gi = (rate & MWL_TX_RATE_SHORTGI_MASK) >> -+ MWL_TX_RATE_SHORTGI_SHIFT; -+ rate_id = (rate & MWL_TX_RATE_RATEIDMCS_MASK) >> -+ MWL_TX_RATE_RATEIDMCS_SHIFT; -+ -+ info->status.rates[0].idx = rate_id; -+ if (format == TX_RATE_FORMAT_LEGACY) { -+ if (priv->hw->conf.chandef.chan->hw_value > -+ BAND_24_CHANNEL_NUM) { -+ info->status.rates[0].idx -= 5; -+ } -+ } -+ if (format == TX_RATE_FORMAT_11N) -+ info->status.rates[0].flags |= -+ IEEE80211_TX_RC_MCS; -+ if (format == TX_RATE_FORMAT_11AC) -+ info->status.rates[0].flags |= -+ IEEE80211_TX_RC_VHT_MCS; -+ if (bandwidth == TX_RATE_BANDWIDTH_40) -+ info->status.rates[0].flags |= -+ IEEE80211_TX_RC_40_MHZ_WIDTH; -+ if (bandwidth == TX_RATE_BANDWIDTH_80) -+ info->status.rates[0].flags |= -+ IEEE80211_TX_RC_80_MHZ_WIDTH; -+ if (bandwidth == TX_RATE_BANDWIDTH_160) -+ info->status.rates[0].flags |= -+ IEEE80211_TX_RC_160_MHZ_WIDTH; -+ if (short_gi == TX_RATE_INFO_SHORT_GI) -+ info->status.rates[0].flags |= -+ IEEE80211_TX_RC_SHORT_GI; -+ info->status.rates[0].count = 1; -+ info->status.rates[1].idx = -1; -+ } -+} -+ -+static inline void pcie_tx_count_packet(struct ieee80211_sta *sta, u8 tid) -+{ -+ struct mwl_sta *sta_info; -+ struct mwl_tx_info *tx_stats; -+ -+ if (WARN_ON(tid >= SYSADPT_MAX_TID)) -+ return; -+ -+ sta_info = mwl_dev_get_sta(sta); -+ -+ tx_stats = &sta_info->tx_stats[tid]; -+ -+ if (tx_stats->start_time == 0) -+ tx_stats->start_time = jiffies; -+ -+ /* reset the packet count after each second elapses. If the number of -+ * packets ever exceeds the ampdu_min_traffic threshold, we will allow -+ * an ampdu stream to be started. -+ */ -+ if (jiffies - tx_stats->start_time > HZ) { -+ tx_stats->pkts = 0; -+ tx_stats->start_time = jiffies; -+ } else { -+ tx_stats->pkts++; -+ } -+} -+ -+static inline void pcie_rx_prepare_status(struct mwl_priv *priv, u16 format, -+ u16 nss, u16 bw, u16 gi, u16 rate, -+ struct ieee80211_rx_status *status) -+{ -+#ifdef RX_ENC_FLAG_STBC_SHIFT -+ switch (format) { -+ case RX_RATE_INFO_FORMAT_11N: -+ status->encoding = RX_ENC_HT; -+ status->bw = RATE_INFO_BW_20; -+ if (bw == RX_RATE_INFO_HT40) -+ status->bw = RATE_INFO_BW_40; -+ if (gi == RX_RATE_INFO_SHORT_INTERVAL) -+ status->enc_flags |= RX_ENC_FLAG_SHORT_GI; -+ break; -+ case RX_RATE_INFO_FORMAT_11AC: -+ status->encoding = RX_ENC_VHT; -+ status->bw = RATE_INFO_BW_20; -+ if (bw == RX_RATE_INFO_HT40) -+ status->bw = RATE_INFO_BW_40; -+ if (bw == RX_RATE_INFO_HT80) -+ status->bw = RATE_INFO_BW_80; -+ if (bw == RX_RATE_INFO_HT160) -+ status->bw = RATE_INFO_BW_160; -+ if (gi == RX_RATE_INFO_SHORT_INTERVAL) -+ status->enc_flags |= RX_ENC_FLAG_SHORT_GI; -+ status->nss = (nss + 1); -+ break; -+ } -+#else -+ switch (format) { -+ case RX_RATE_INFO_FORMAT_11N: -+ status->flag |= RX_FLAG_HT; -+ if (bw == RX_RATE_INFO_HT40) -+ status->flag |= RX_FLAG_40MHZ; -+ if (gi == RX_RATE_INFO_SHORT_INTERVAL) -+ status->flag |= RX_FLAG_SHORT_GI; -+ break; -+ case RX_RATE_INFO_FORMAT_11AC: -+ status->flag |= RX_FLAG_VHT; -+ if (bw == RX_RATE_INFO_HT40) -+ status->flag |= RX_FLAG_40MHZ; -+ if (bw == RX_RATE_INFO_HT80) -+ status->vht_flag |= RX_VHT_FLAG_80MHZ; -+ if (bw == RX_RATE_INFO_HT160) -+ status->vht_flag |= RX_VHT_FLAG_160MHZ; -+ if (gi == RX_RATE_INFO_SHORT_INTERVAL) -+ status->flag |= RX_FLAG_SHORT_GI; -+ status->vht_nss = (nss + 1); -+ break; -+ } -+#endif -+ status->rate_idx = rate; -+ -+ if (priv->hw->conf.chandef.chan->hw_value > -+ BAND_24_CHANNEL_NUM) { -+ status->band = NL80211_BAND_5GHZ; -+#ifdef RX_ENC_FLAG_STBC_SHIFT -+ if ((!(status->encoding == RX_ENC_HT)) && -+ (!(status->encoding == RX_ENC_VHT))) { -+#else -+ if ((!(status->flag & RX_FLAG_HT)) && -+ (!(status->flag & RX_FLAG_VHT))) { -+#endif -+ status->rate_idx -= 5; -+ if (status->rate_idx >= BAND_50_RATE_NUM) -+ status->rate_idx = BAND_50_RATE_NUM - 1; -+ } -+ } else { -+ status->band = NL80211_BAND_2GHZ; -+#ifdef RX_ENC_FLAG_STBC_SHIFT -+ if ((!(status->encoding == RX_ENC_HT)) && -+ (!(status->encoding == RX_ENC_VHT))) { -+#else -+ if ((!(status->flag & RX_FLAG_HT)) && -+ (!(status->flag & RX_FLAG_VHT))) { -+#endif -+ if (status->rate_idx >= BAND_24_RATE_NUM) -+ status->rate_idx = BAND_24_RATE_NUM - 1; -+ } -+ } -+} -+ -+static inline void pcie_rx_remove_dma_header(struct sk_buff *skb, __le16 qos) -+{ -+ struct pcie_dma_data *dma_data; -+ int hdrlen; -+ -+ dma_data = (struct pcie_dma_data *)skb->data; -+ hdrlen = ieee80211_hdrlen(dma_data->wh.frame_control); -+ -+ if (hdrlen != sizeof(dma_data->wh)) { -+ if (ieee80211_is_data_qos(dma_data->wh.frame_control)) { -+ memmove(dma_data->data - hdrlen, -+ &dma_data->wh, hdrlen - 2); -+ *((__le16 *)(dma_data->data - 2)) = qos; -+ } else { -+ memmove(dma_data->data - hdrlen, &dma_data->wh, hdrlen); -+ } -+ } -+ -+ if (hdrlen != sizeof(*dma_data)) -+ skb_pull(skb, sizeof(*dma_data) - hdrlen); -+} -+ -+static inline void pcie_mask_int(struct pcie_priv *pcie_priv, -+ u32 mask_bit, bool set) -+{ -+ unsigned long flags; -+ void __iomem *int_status_mask; -+ u32 status; -+ -+ spin_lock_irqsave(&pcie_priv->int_mask_lock, flags); -+ int_status_mask = pcie_priv->iobase1 + -+ MACREG_REG_A2H_INTERRUPT_STATUS_MASK; -+ status = readl(int_status_mask); -+ if (set) -+ writel((status | mask_bit), int_status_mask); -+ else -+ writel((status & ~mask_bit), int_status_mask); -+ spin_unlock_irqrestore(&pcie_priv->int_mask_lock, flags); -+} -+ -+#endif /* _DEV_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/pcie/fwdl.c b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/fwdl.c -new file mode 100644 -index 000000000000..939ed54133c7 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/fwdl.c -@@ -0,0 +1,274 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file implements firmware download related -+ * functions. -+ */ -+ -+#include -+ -+#include "sysadpt.h" -+#include "core.h" -+#include "hif/fwcmd.h" -+#include "hif/pcie/dev.h" -+#include "hif/pcie/sc4_ddr.h" -+#include "hif/pcie/fwdl.h" -+ -+#define FW_DOWNLOAD_BLOCK_SIZE 256 -+#define FW_CHECK_MSECS 3 -+ -+#define FW_MAX_NUM_CHECKS 0xffff -+ -+static void pcie_trigger_pcicmd_bootcode(struct pcie_priv *pcie_priv) -+{ -+ writel(pcie_priv->mwl_priv->pphys_cmd_buf, -+ pcie_priv->iobase1 + MACREG_REG_GEN_PTR); -+ writel(0x00, pcie_priv->iobase1 + MACREG_REG_INT_CODE); -+ writel(MACREG_H2ARIC_BIT_DOOR_BELL, -+ pcie_priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); -+} -+ -+static bool pcie_download_ddr_init(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = (struct pcie_priv *)priv->hif.priv; -+ u32 size_ddr_init = sizeof(sc4_ddr_init); -+ u8 *p = (u8 *)&sc4_ddr_init[0]; -+ u32 curr_iteration = 0; -+ u32 size_ddr_init_downloaded = 0; -+ u32 int_code = 0; -+ u32 len = 0; -+ -+ /* download ddr init code */ -+ wiphy_debug(priv->hw->wiphy, "ddr init: download start\n"); -+ -+ while (size_ddr_init_downloaded < size_ddr_init) { -+ len = readl(pcie_priv->iobase1 + 0xc40); -+ -+ if (!len) -+ break; -+ -+ /* this copies the next chunk of fw binary to be delivered */ -+ memcpy((char *)&priv->pcmd_buf[0], p, len); -+ /* this is arbitrary per your platform; we use 0xffff */ -+ curr_iteration = (FW_MAX_NUM_CHECKS * 500); -+ /* this function writes pdata to c10, then write 2 to c18 */ -+ pcie_trigger_pcicmd_bootcode(pcie_priv); -+ -+ /* NOTE: the following back to back checks on C1C is time -+ * sensitive, hence may need to be tweaked dependent on host -+ * processor. Time for SC2 to go from the write of event 2 to -+ * C1C == 2 is ~1300 nSec. Hence the checkings on host has to -+ * consider how efficient your code can be to meet this timing, -+ * or you can alternatively tweak this routines to fit your -+ * platform -+ */ -+ do { -+ int_code = readl(pcie_priv->iobase1 + 0xc1c); -+ if (int_code != 0) -+ break; -+ cond_resched(); -+ curr_iteration--; -+ } while (curr_iteration); -+ -+ do { -+ int_code = readl(pcie_priv->iobase1 + 0xc1c); -+ if ((int_code & MACREG_H2ARIC_BIT_DOOR_BELL) != -+ MACREG_H2ARIC_BIT_DOOR_BELL) -+ break; -+ cond_resched(); -+ curr_iteration--; -+ } while (curr_iteration); -+ -+ if (curr_iteration == 0) { -+ /* This limited loop check allows you to exit gracefully -+ * without locking up your entire system just because fw -+ * download failed -+ */ -+ wiphy_err(priv->hw->wiphy, -+ "Exhausted curr_iteration during download\n"); -+ return false; -+ } -+ -+ p += len; -+ size_ddr_init_downloaded += len; -+ } -+ -+ wiphy_debug(priv->hw->wiphy, "ddr init: download complete\n"); -+ -+ return true; -+} -+ -+void pcie_reset(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ u32 regval; -+ -+ regval = readl(pcie_priv->iobase1 + MACREG_REG_INT_CODE); -+ if (regval == 0xffffffff) { -+ wiphy_err(priv->hw->wiphy, "adapter does not exist\n"); -+ return; -+ } -+ -+ writel(ISR_RESET, pcie_priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); -+} -+ -+int pcie_download_firmware(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ const struct firmware *fw = priv->fw_ucode; -+ u32 curr_iteration = 0; -+ u32 size_fw_downloaded = 0; -+ u32 int_code = 0; -+ u32 len = 0; -+ u32 fwreadysignature = HOSTCMD_SOFTAP_FWRDY_SIGNATURE; -+ -+ pcie_reset(hw); -+ -+ /* FW before jumping to boot rom, it will enable PCIe transaction retry, -+ * wait for boot code to stop it. -+ */ -+ usleep_range(FW_CHECK_MSECS * 1000, FW_CHECK_MSECS * 2000); -+ -+ if (priv->chip_type == MWL8964) { -+ writel(MACREG_A2HRIC_BIT_MASK_NDP, -+ pcie_priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CLEAR_SEL); -+ } else { -+ writel(MACREG_A2HRIC_BIT_MASK, -+ pcie_priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CLEAR_SEL); -+ } -+ writel(0x00, pcie_priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); -+ writel(0x00, pcie_priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); -+ if (priv->chip_type == MWL8964) { -+ writel(MACREG_A2HRIC_BIT_MASK_NDP, -+ pcie_priv->iobase1 + -+ MACREG_REG_A2H_INTERRUPT_STATUS_MASK); -+ } else { -+ writel(MACREG_A2HRIC_BIT_MASK, -+ pcie_priv->iobase1 + -+ MACREG_REG_A2H_INTERRUPT_STATUS_MASK); -+ } -+ -+ /* this routine interacts with SC2 bootrom to download firmware binary -+ * to the device. After DMA'd to SC2, the firmware could be deflated to -+ * reside on its respective blocks such as ITCM, DTCM, SQRAM, -+ * (or even DDR, AFTER DDR is init'd before fw download -+ */ -+ wiphy_debug(hw->wiphy, "fw download start\n"); -+ -+ if (priv->chip_type != MWL8997) -+ /* Disable PFU before FWDL */ -+ writel(0x100, pcie_priv->iobase1 + 0xE0E4); -+ -+ /* make sure SCRATCH2 C40 is clear, in case we are too quick */ -+ while (readl(pcie_priv->iobase1 + 0xc40) == 0) -+ cond_resched(); -+ -+ if (priv->chip_type == MWL8964) { -+ if (!pcie_download_ddr_init(priv)) { -+ wiphy_err(hw->wiphy, -+ "ddr init: code download failed\n"); -+ goto err_download; -+ } -+ } -+ -+ while (size_fw_downloaded < fw->size) { -+ len = readl(pcie_priv->iobase1 + 0xc40); -+ -+ if (!len) -+ break; -+ -+ /* this copies the next chunk of fw binary to be delivered */ -+ memcpy((char *)&priv->pcmd_buf[0], -+ (fw->data + size_fw_downloaded), len); -+ -+ /* this function writes pdata to c10, then write 2 to c18 */ -+ pcie_trigger_pcicmd_bootcode(pcie_priv); -+ -+ /* this is arbitrary per your platform; we use 0xffff */ -+ curr_iteration = FW_MAX_NUM_CHECKS; -+ -+ /* NOTE: the following back to back checks on C1C is time -+ * sensitive, hence may need to be tweaked dependent on host -+ * processor. Time for SC2 to go from the write of event 2 to -+ * C1C == 2 is ~1300 nSec. Hence the checkings on host has to -+ * consider how efficient your code can be to meet this timing, -+ * or you can alternatively tweak this routines to fit your -+ * platform -+ */ -+ do { -+ int_code = readl(pcie_priv->iobase1 + 0xc1c); -+ if ((int_code & MACREG_H2ARIC_BIT_DOOR_BELL) != -+ MACREG_H2ARIC_BIT_DOOR_BELL) -+ break; -+ cond_resched(); -+ curr_iteration--; -+ } while (curr_iteration); -+ -+ if (curr_iteration == 0) { -+ /* This limited loop check allows you to exit gracefully -+ * without locking up your entire system just because fw -+ * download failed -+ */ -+ wiphy_err(hw->wiphy, -+ "Exhausted curr_iteration for fw download\n"); -+ goto err_download; -+ } -+ -+ size_fw_downloaded += len; -+ } -+ -+ wiphy_debug(hw->wiphy, -+ "FwSize = %d downloaded Size = %d curr_iteration %d\n", -+ (int)fw->size, size_fw_downloaded, curr_iteration); -+ -+ /* Now firware is downloaded successfully, so this part is to check -+ * whether fw can properly execute to an extent that write back -+ * signature to indicate its readiness to the host. NOTE: if your -+ * downloaded fw crashes, this signature checking will fail. This -+ * part is similar as SC1 -+ */ -+ *((u32 *)&priv->pcmd_buf[1]) = 0; -+ pcie_trigger_pcicmd_bootcode(pcie_priv); -+ curr_iteration = FW_MAX_NUM_CHECKS; -+ do { -+ curr_iteration--; -+ writel(HOSTCMD_SOFTAP_MODE, -+ pcie_priv->iobase1 + MACREG_REG_GEN_PTR); -+ usleep_range(FW_CHECK_MSECS * 1000, FW_CHECK_MSECS * 2000); -+ int_code = readl(pcie_priv->iobase1 + MACREG_REG_INT_CODE); -+ if (!(curr_iteration % 0xff) && (int_code != 0)) -+ wiphy_err(hw->wiphy, "%x;", int_code); -+ } while ((curr_iteration) && -+ (int_code != fwreadysignature)); -+ -+ if (curr_iteration == 0) { -+ wiphy_err(hw->wiphy, -+ "Exhausted curr_iteration for fw signature\n"); -+ goto err_download; -+ } -+ -+ wiphy_debug(hw->wiphy, "fw download complete\n"); -+ writel(0x00, pcie_priv->iobase1 + MACREG_REG_INT_CODE); -+ -+ return 0; -+ -+err_download: -+ -+ pcie_reset(hw); -+ -+ return -EIO; -+} -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/pcie/fwdl.h b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/fwdl.h -new file mode 100644 -index 000000000000..36a3311aa678 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/fwdl.h -@@ -0,0 +1,24 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines firmware download related functions. */ -+ -+#ifndef _FWDL_H_ -+#define _FWDL_H_ -+ -+void pcie_reset(struct ieee80211_hw *hw); -+int pcie_download_firmware(struct ieee80211_hw *hw); -+ -+#endif /* _FWDL_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/pcie/pcie.c b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/pcie.c -new file mode 100644 -index 000000000000..da55913c0570 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/pcie.c -@@ -0,0 +1,1645 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file implements functions needed for PCIe module. */ -+ -+#include -+#include -+ -+#include "sysadpt.h" -+#include "core.h" -+#include "utils.h" -+#include "vendor_cmd.h" -+#include "hif/fwcmd.h" -+#include "hif/pcie/dev.h" -+#include "hif/pcie/fwdl.h" -+#include "hif/pcie/tx.h" -+#include "hif/pcie/rx.h" -+#include "hif/pcie/tx_ndp.h" -+#include "hif/pcie/rx_ndp.h" -+ -+#define PCIE_DRV_DESC "Marvell Mac80211 Wireless PCIE Network Driver" -+#define PCIE_DEV_NAME "Marvell 802.11ac PCIE Adapter" -+ -+#define MAX_WAIT_FW_COMPLETE_ITERATIONS 2000 -+#define CHECK_BA_TRAFFIC_TIME 300 /* msec */ -+#define CHECK_TX_DONE_TIME 50 /* msec */ -+ -+static struct pci_device_id pcie_id_tbl[] = { -+ { PCI_VDEVICE(MARVELL, 0x2a55), .driver_data = MWL8864, }, -+ { PCI_VDEVICE(MARVELL, 0x2b38), .driver_data = MWL8897, }, -+ { PCI_VDEVICE(MARVELL, 0x2b40), .driver_data = MWL8964, }, -+ { PCI_VDEVICE(MARVELL_EXT, 0x2b42), .driver_data = MWL8997, }, -+ { }, -+}; -+ -+static struct mwl_chip_info pcie_chip_tbl[] = { -+ [MWL8864] = { -+ .part_name = "88W8864", -+ .fw_image = "mwlwifi/88W8864.bin", -+ .cal_file = NULL, -+ .txpwrlmt_file = NULL, -+ .antenna_tx = ANTENNA_TX_4_AUTO, -+ .antenna_rx = ANTENNA_RX_4_AUTO, -+ }, -+ [MWL8897] = { -+ .part_name = "88W8897", -+ .fw_image = "mwlwifi/88W8897.bin", -+ .cal_file = NULL, -+ .txpwrlmt_file = NULL, -+ .antenna_tx = ANTENNA_TX_2, -+ .antenna_rx = ANTENNA_RX_2, -+ }, -+ [MWL8964] = { -+ .part_name = "88W8964", -+ .fw_image = "mwlwifi/88W8964.bin", -+ .cal_file = NULL, -+ .txpwrlmt_file = NULL, -+ .antenna_tx = ANTENNA_TX_4_AUTO, -+ .antenna_rx = ANTENNA_RX_4_AUTO, -+ }, -+ [MWL8997] = { -+ .part_name = "88W8997", -+ .fw_image = "mwlwifi/88W8997.bin", -+ .cal_file = "mwlwifi/WlanCalData_ext.conf", -+ .txpwrlmt_file = "mwlwifi/txpwrlmt_cfg.conf", -+ .antenna_tx = ANTENNA_TX_2, -+ .antenna_rx = ANTENNA_RX_2, -+ }, -+}; -+ -+static int pcie_alloc_resource(struct pcie_priv *pcie_priv) -+{ -+ struct pci_dev *pdev = pcie_priv->pdev; -+ struct device *dev = &pdev->dev; -+ void __iomem *addr; -+ -+ pcie_priv->next_bar_num = 1; /* 32-bit */ -+ if (pci_resource_flags(pdev, 0) & 0x04) -+ pcie_priv->next_bar_num = 2; /* 64-bit */ -+ -+ addr = devm_ioremap_resource(dev, &pdev->resource[0]); -+ if (IS_ERR(addr)) { -+ pr_err("%s: cannot reserve PCI memory region 0\n", -+ PCIE_DRV_NAME); -+ goto err; -+ } -+ pcie_priv->iobase0 = addr; -+ pr_debug("iobase0 = %p\n", pcie_priv->iobase0); -+ -+ addr = devm_ioremap_resource(dev, -+ &pdev->resource[pcie_priv->next_bar_num]); -+ if (IS_ERR(addr)) { -+ pr_err("%s: cannot reserve PCI memory region 1\n", -+ PCIE_DRV_NAME); -+ goto err; -+ } -+ pcie_priv->iobase1 = addr; -+ pr_debug("iobase1 = %p\n", pcie_priv->iobase1); -+ -+ return 0; -+ -+err: -+ pr_err("pci alloc fail\n"); -+ -+ return -EIO; -+} -+ -+static u32 pcie_read_mac_reg(struct pcie_priv *pcie_priv, u32 offset) -+{ -+ struct mwl_priv *priv = pcie_priv->mwl_priv; -+ -+ if (priv->chip_type == MWL8964) { -+ u32 *addr_val = kmalloc(64 * sizeof(u32), GFP_ATOMIC); -+ u32 val; -+ -+ if (addr_val) { -+ mwl_fwcmd_get_addr_value(priv->hw, -+ 0x8000a000 + offset, 4, -+ addr_val, 0); -+ val = addr_val[0]; -+ kfree(addr_val); -+ return val; -+ } -+ return 0; -+ } else -+ return le32_to_cpu(*(__le32 *) -+ (MAC_REG_ADDR_PCI(offset))); -+} -+ -+static bool pcie_chk_adapter(struct pcie_priv *pcie_priv) -+{ -+ struct mwl_priv *priv = pcie_priv->mwl_priv; -+ u32 regval; -+ -+ regval = readl(pcie_priv->iobase1 + MACREG_REG_INT_CODE); -+ -+ if (regval == 0xffffffff) { -+ wiphy_err(priv->hw->wiphy, "adapter does not exist\n"); -+ return false; -+ } -+ -+ if (priv->cmd_timeout) -+ wiphy_debug(priv->hw->wiphy, "MACREG_REG_INT_CODE: 0x%04x\n", -+ regval); -+ -+ return true; -+} -+ -+static void pcie_send_cmd(struct pcie_priv *pcie_priv) -+{ -+ writel(pcie_priv->mwl_priv->pphys_cmd_buf, -+ pcie_priv->iobase1 + MACREG_REG_GEN_PTR); -+ writel(MACREG_H2ARIC_BIT_DOOR_BELL, -+ pcie_priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); -+} -+ -+static int pcie_wait_complete(struct mwl_priv *priv, unsigned short cmd) -+{ -+ unsigned int curr_iteration = MAX_WAIT_FW_COMPLETE_ITERATIONS; -+ unsigned short int_code = 0; -+ -+ do { -+ int_code = le16_to_cpu(*((__le16 *)&priv->pcmd_buf[0])); -+ usleep_range(1000, 2000); -+ } while ((int_code != cmd) && (--curr_iteration) && !priv->rmmod); -+ -+ if (curr_iteration == 0) { -+ wiphy_err(priv->hw->wiphy, "cmd 0x%04x=%s timed out\n", -+ cmd, mwl_fwcmd_get_cmd_string(cmd)); -+ wiphy_err(priv->hw->wiphy, "return code: 0x%04x\n", int_code); -+ return -EIO; -+ } -+ -+ if (priv->chip_type != MWL8997) -+ usleep_range(3000, 5000); -+ -+ return 0; -+} -+ -+static int pcie_init(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ const struct hostcmd_get_hw_spec *get_hw_spec; -+ struct hostcmd_set_hw_spec set_hw_spec; -+ int rc, i; -+ -+ spin_lock_init(&pcie_priv->int_mask_lock); -+ tasklet_init(&pcie_priv->tx_task, -+ (void *)pcie_tx_skbs, (unsigned long)hw); -+ tasklet_disable(&pcie_priv->tx_task); -+ tasklet_init(&pcie_priv->tx_done_task, -+ (void *)pcie_tx_done, (unsigned long)hw); -+ tasklet_disable(&pcie_priv->tx_done_task); -+ spin_lock_init(&pcie_priv->tx_desc_lock); -+ tasklet_init(&pcie_priv->rx_task, -+ (void *)pcie_rx_recv, (unsigned long)hw); -+ tasklet_disable(&pcie_priv->rx_task); -+ tasklet_init(&pcie_priv->qe_task, -+ (void *)pcie_tx_flush_amsdu, (unsigned long)hw); -+ tasklet_disable(&pcie_priv->qe_task); -+ pcie_priv->txq_limit = PCIE_TX_QUEUE_LIMIT; -+ pcie_priv->txq_wake_threshold = PCIE_TX_WAKE_Q_THRESHOLD; -+ pcie_priv->is_tx_done_schedule = false; -+ pcie_priv->recv_limit = PCIE_RECEIVE_LIMIT; -+ pcie_priv->is_rx_schedule = false; -+ pcie_priv->is_qe_schedule = false; -+ pcie_priv->qe_trig_num = 0; -+ pcie_priv->qe_trig_time = jiffies; -+ -+ rc = pcie_tx_init(hw); -+ if (rc) { -+ wiphy_err(hw->wiphy, "%s: fail to initialize TX\n", -+ PCIE_DRV_NAME); -+ goto err_mwl_tx_init; -+ } -+ -+ rc = pcie_rx_init(hw); -+ if (rc) { -+ wiphy_err(hw->wiphy, "%s: fail to initialize RX\n", -+ PCIE_DRV_NAME); -+ goto err_mwl_rx_init; -+ } -+ -+ /* get and prepare HW specifications */ -+ get_hw_spec = mwl_fwcmd_get_hw_specs(hw); -+ if (!get_hw_spec) { -+ wiphy_err(hw->wiphy, "%s: fail to get HW specifications\n", -+ PCIE_DRV_NAME); -+ goto err_get_hw_specs; -+ } -+ ether_addr_copy(&priv->hw_data.mac_addr[0], -+ get_hw_spec->permanent_addr); -+ pcie_priv->desc_data[0].wcb_base = -+ le32_to_cpu(get_hw_spec->wcb_base0) & 0x0000ffff; -+ for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++) -+ pcie_priv->desc_data[i].wcb_base = -+ le32_to_cpu(get_hw_spec->wcb_base[i - 1]) & 0x0000ffff; -+ pcie_priv->desc_data[0].rx_desc_read = -+ le32_to_cpu(get_hw_spec->rxpd_rd_ptr) & 0x0000ffff; -+ pcie_priv->desc_data[0].rx_desc_write = -+ le32_to_cpu(get_hw_spec->rxpd_wr_ptr) & 0x0000ffff; -+ priv->hw_data.fw_release_num = le32_to_cpu(get_hw_spec->fw_release_num); -+ priv->hw_data.hw_version = get_hw_spec->version; -+ if (priv->chip_type != MWL8997) { -+ writel(pcie_priv->desc_data[0].pphys_tx_ring, -+ pcie_priv->iobase0 + pcie_priv->desc_data[0].wcb_base); -+ for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++) -+ writel(pcie_priv->desc_data[i].pphys_tx_ring, -+ pcie_priv->iobase0 + -+ pcie_priv->desc_data[i].wcb_base); -+ } -+ writel(pcie_priv->desc_data[0].pphys_rx_ring, -+ pcie_priv->iobase0 + pcie_priv->desc_data[0].rx_desc_read); -+ writel(pcie_priv->desc_data[0].pphys_rx_ring, -+ pcie_priv->iobase0 + pcie_priv->desc_data[0].rx_desc_write); -+ -+ /* prepare and set HW specifications */ -+ memset(&set_hw_spec, 0, sizeof(set_hw_spec)); -+ if (priv->chip_type == MWL8997) { -+ set_hw_spec.wcb_base[0] = -+ cpu_to_le32(pcie_priv->txbd_ring_pbase); -+ set_hw_spec.tx_wcb_num_per_queue = -+ cpu_to_le32(PCIE_MAX_TXRX_BD); -+ set_hw_spec.num_tx_queues = cpu_to_le32(1); -+ set_hw_spec.features |= HW_SET_PARMS_FEATURES_HOST_PROBE_RESP; -+ } else { -+ set_hw_spec.wcb_base[0] = -+ cpu_to_le32(pcie_priv->desc_data[0].pphys_tx_ring); -+ for (i = 1; i < SYSADPT_TOTAL_TX_QUEUES; i++) -+ set_hw_spec.wcb_base[i] = cpu_to_le32( -+ pcie_priv->desc_data[i].pphys_tx_ring); -+ set_hw_spec.tx_wcb_num_per_queue = -+ cpu_to_le32(PCIE_MAX_NUM_TX_DESC); -+ set_hw_spec.num_tx_queues = cpu_to_le32(PCIE_NUM_OF_DESC_DATA); -+ } -+ set_hw_spec.total_rx_wcb = cpu_to_le32(PCIE_MAX_NUM_RX_DESC); -+ set_hw_spec.rxpd_wr_ptr = -+ cpu_to_le32(pcie_priv->desc_data[0].pphys_rx_ring); -+ rc = mwl_fwcmd_set_hw_specs(hw, &set_hw_spec); -+ if (rc) { -+ wiphy_err(hw->wiphy, "%s: fail to set HW specifications\n", -+ PCIE_DRV_NAME); -+ goto err_set_hw_specs; -+ } -+ -+ return rc; -+ -+err_set_hw_specs: -+err_get_hw_specs: -+ -+ pcie_rx_deinit(hw); -+ -+err_mwl_rx_init: -+ -+ pcie_tx_deinit(hw); -+ -+err_mwl_tx_init: -+ -+ wiphy_err(hw->wiphy, "%s: init fail\n", PCIE_DRV_NAME); -+ -+ return rc; -+} -+ -+static void pcie_deinit(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ -+ pcie_rx_deinit(hw); -+ pcie_tx_deinit(hw); -+ tasklet_kill(&pcie_priv->qe_task); -+ tasklet_kill(&pcie_priv->rx_task); -+ tasklet_kill(&pcie_priv->tx_done_task); -+ tasklet_kill(&pcie_priv->tx_task); -+ pcie_reset(hw); -+} -+ -+static int pcie_get_info(struct ieee80211_hw *hw, char *buf, size_t size) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ char *p = buf; -+ int len = 0; -+ -+ len += scnprintf(p + len, size - len, "iobase0: %p\n", -+ pcie_priv->iobase0); -+ len += scnprintf(p + len, size - len, "iobase1: %p\n", -+ pcie_priv->iobase1); -+ len += scnprintf(p + len, size - len, -+ "tx limit: %d\n", pcie_priv->txq_limit); -+ len += scnprintf(p + len, size - len, -+ "rx limit: %d\n", pcie_priv->recv_limit); -+ len += scnprintf(p + len, size - len, -+ "qe trigger number: %d\n", pcie_priv->qe_trig_num); -+ return len; -+} -+ -+static void pcie_enable_data_tasks(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ -+ tasklet_enable(&pcie_priv->tx_task); -+ tasklet_enable(&pcie_priv->tx_done_task); -+ tasklet_enable(&pcie_priv->rx_task); -+ tasklet_enable(&pcie_priv->qe_task); -+} -+ -+static void pcie_disable_data_tasks(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ -+ tasklet_disable(&pcie_priv->tx_task); -+ tasklet_disable(&pcie_priv->tx_done_task); -+ tasklet_disable(&pcie_priv->rx_task); -+ tasklet_disable(&pcie_priv->qe_task); -+} -+ -+static int pcie_exec_cmd(struct ieee80211_hw *hw, unsigned short cmd) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ bool busy = false; -+ -+ might_sleep(); -+ -+ if (!pcie_chk_adapter(pcie_priv)) { -+ wiphy_err(priv->hw->wiphy, "adapter does not exist\n"); -+ priv->in_send_cmd = false; -+ return -EIO; -+ } -+ -+ if (!priv->in_send_cmd && !priv->rmmod) { -+ priv->in_send_cmd = true; -+ if (priv->dump_hostcmd) -+ wiphy_debug(priv->hw->wiphy, "send cmd 0x%04x=%s\n", -+ cmd, mwl_fwcmd_get_cmd_string(cmd)); -+ pcie_send_cmd(pcie_priv); -+ if (pcie_wait_complete(priv, 0x8000 | cmd)) { -+ wiphy_err(priv->hw->wiphy, "timeout: 0x%04x\n", cmd); -+ priv->in_send_cmd = false; -+ priv->cmd_timeout = true; -+ if (priv->heartbeat) -+ vendor_cmd_basic_event( -+ hw->wiphy, -+ MWL_VENDOR_EVENT_CMD_TIMEOUT); -+ return -EIO; -+ } -+ } else { -+ wiphy_warn(priv->hw->wiphy, -+ "previous command is running or module removed\n"); -+ busy = true; -+ } -+ -+ if (!busy) -+ priv->in_send_cmd = false; -+ -+ return 0; -+} -+ -+static int pcie_get_irq_num(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ -+ return pcie_priv->pdev->irq; -+} -+ -+static irqreturn_t pcie_isr(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ u32 int_status; -+ -+ int_status = readl(pcie_priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); -+ -+ if (int_status == 0x00000000) -+ return IRQ_NONE; -+ -+ if (int_status == 0xffffffff) { -+ wiphy_warn(hw->wiphy, "card unplugged?\n"); -+ } else { -+ writel(~int_status, -+ pcie_priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); -+ -+ if (int_status & MACREG_A2HRIC_BIT_TX_DONE) { -+ if (!pcie_priv->is_tx_done_schedule) { -+ pcie_mask_int(pcie_priv, -+ MACREG_A2HRIC_BIT_TX_DONE, false); -+ tasklet_schedule(&pcie_priv->tx_done_task); -+ pcie_priv->is_tx_done_schedule = true; -+ } -+ } -+ -+ if (int_status & MACREG_A2HRIC_BIT_RX_RDY) { -+ if (!pcie_priv->is_rx_schedule) { -+ pcie_mask_int(pcie_priv, -+ MACREG_A2HRIC_BIT_RX_RDY, false); -+ tasklet_schedule(&pcie_priv->rx_task); -+ pcie_priv->is_rx_schedule = true; -+ } -+ } -+ -+ if (int_status & MACREG_A2HRIC_BIT_RADAR_DETECT) { -+ wiphy_info(hw->wiphy, "radar detected by firmware\n"); -+ ieee80211_radar_detected(hw); -+ } -+ -+ if (int_status & MACREG_A2HRIC_BIT_QUE_EMPTY) { -+ if (!pcie_priv->is_qe_schedule) { -+ if (time_after(jiffies, -+ (pcie_priv->qe_trig_time + 1))) { -+ pcie_mask_int(pcie_priv, -+ MACREG_A2HRIC_BIT_QUE_EMPTY, -+ false); -+ tasklet_schedule(&pcie_priv->qe_task); -+ pcie_priv->qe_trig_num++; -+ pcie_priv->is_qe_schedule = true; -+ pcie_priv->qe_trig_time = jiffies; -+ } -+ } -+ } -+ -+ if (int_status & MACREG_A2HRIC_BIT_CHAN_SWITCH) -+ ieee80211_queue_work(hw, &priv->chnl_switch_handle); -+ -+ if (int_status & MACREG_A2HRIC_BA_WATCHDOG) -+ ieee80211_queue_work(hw, &priv->watchdog_ba_handle); -+ } -+ -+ return IRQ_HANDLED; -+} -+ -+static void pcie_irq_enable(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ -+ if (pcie_chk_adapter(pcie_priv)) { -+ writel(0x00, -+ pcie_priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); -+ writel(MACREG_A2HRIC_BIT_MASK, -+ pcie_priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); -+ } -+} -+ -+static void pcie_irq_disable(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ -+ if (pcie_chk_adapter(pcie_priv)) -+ writel(0x00, -+ pcie_priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); -+} -+ -+static void pcie_timer_routine(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ static int cnt; -+ struct mwl_ampdu_stream *stream; -+ struct mwl_sta *sta_info; -+ struct mwl_tx_info *tx_stats; -+ struct mwl_ampdu_stream *rm_stream = NULL; -+ u32 rm_pkts = 0; -+ bool ba_full = true; -+ int i; -+ -+ if ((++cnt * SYSADPT_TIMER_WAKEUP_TIME) < CHECK_BA_TRAFFIC_TIME) -+ return; -+ cnt = 0; -+ spin_lock_bh(&priv->stream_lock); -+ for (i = 0; i < priv->ampdu_num; i++) { -+ stream = &priv->ampdu[i]; -+ -+ if (stream->state == AMPDU_STREAM_ACTIVE) { -+ sta_info = mwl_dev_get_sta(stream->sta); -+ tx_stats = &sta_info->tx_stats[stream->tid]; -+ -+ if ((jiffies - tx_stats->start_time > HZ) && -+ (tx_stats->pkts < SYSADPT_AMPDU_PACKET_THRESHOLD)) { -+ if (rm_pkts) { -+ if (tx_stats->pkts < rm_pkts) { -+ rm_stream = stream; -+ rm_pkts = tx_stats->pkts; -+ } -+ } else { -+ rm_stream = stream; -+ rm_pkts = tx_stats->pkts; -+ } -+ } -+ -+ if (jiffies - tx_stats->start_time > HZ) { -+ tx_stats->pkts = 0; -+ tx_stats->start_time = jiffies; -+ } -+ } else -+ ba_full = false; -+ } -+ if (ba_full && rm_stream) { -+ ieee80211_stop_tx_ba_session(rm_stream->sta, -+ rm_stream->tid); -+ wiphy_debug(hw->wiphy, "Stop BA %pM\n", rm_stream->sta->addr); -+ } -+ spin_unlock_bh(&priv->stream_lock); -+} -+ -+static void pcie_tx_return_pkts(struct ieee80211_hw *hw) -+{ -+ pcie_tx_done((unsigned long)hw); -+} -+ -+static struct device_node *pcie_get_device_node(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct device_node *dev_node; -+ -+ dev_node = pci_bus_to_OF_node(pcie_priv->pdev->bus); -+ wiphy_info(priv->hw->wiphy, "device node: %s\n", dev_node->full_name); -+ -+ return dev_node; -+} -+ -+static void pcie_get_survey(struct ieee80211_hw *hw, -+ struct mwl_survey_info *survey_info) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ -+ survey_info->filled = SURVEY_INFO_TIME | -+ SURVEY_INFO_TIME_BUSY | -+ SURVEY_INFO_TIME_TX | -+ SURVEY_INFO_NOISE_DBM; -+ survey_info->time_period += pcie_read_mac_reg(pcie_priv, MCU_LAST_READ); -+ survey_info->time_busy += pcie_read_mac_reg(pcie_priv, MCU_CCA_CNT); -+ survey_info->time_tx += pcie_read_mac_reg(pcie_priv, MCU_TXPE_CNT); -+ survey_info->noise = priv->noise; -+} -+ -+static int pcie_reg_access(struct ieee80211_hw *hw, bool write) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ u8 set; -+ u32 *addr_val; -+ int ret = 0; -+ -+ set = write ? WL_SET : WL_GET; -+ -+ switch (priv->reg_type) { -+ case MWL_ACCESS_RF: -+ ret = mwl_fwcmd_reg_rf(hw, set, priv->reg_offset, -+ &priv->reg_value); -+ break; -+ case MWL_ACCESS_BBP: -+ ret = mwl_fwcmd_reg_bb(hw, set, priv->reg_offset, -+ &priv->reg_value); -+ break; -+ case MWL_ACCESS_CAU: -+ ret = mwl_fwcmd_reg_cau(hw, set, priv->reg_offset, -+ &priv->reg_value); -+ break; -+ case MWL_ACCESS_ADDR0: -+ if (set == WL_GET) -+ priv->reg_value = -+ readl(pcie_priv->iobase0 + priv->reg_offset); -+ else -+ writel(priv->reg_value, -+ pcie_priv->iobase0 + priv->reg_offset); -+ break; -+ case MWL_ACCESS_ADDR1: -+ if (set == WL_GET) -+ priv->reg_value = -+ readl(pcie_priv->iobase1 + priv->reg_offset); -+ else -+ writel(priv->reg_value, -+ pcie_priv->iobase1 + priv->reg_offset); -+ break; -+ case MWL_ACCESS_ADDR: -+ addr_val = kzalloc(64 * sizeof(u32), GFP_KERNEL); -+ if (addr_val) { -+ addr_val[0] = priv->reg_value; -+ ret = mwl_fwcmd_get_addr_value(hw, priv->reg_offset, -+ 4, addr_val, set); -+ if ((!ret) && (set == WL_GET)) -+ priv->reg_value = addr_val[0]; -+ kfree(addr_val); -+ } else { -+ ret = -ENOMEM; -+ } -+ break; -+ default: -+ ret = -EINVAL; -+ break; -+ } -+ -+ return ret; -+} -+ -+static struct mwl_hif_ops pcie_hif_ops = { -+ .driver_name = PCIE_DRV_NAME, -+ .driver_version = PCIE_DRV_VERSION, -+ .tx_head_room = PCIE_MIN_BYTES_HEADROOM, -+ .ampdu_num = PCIE_AMPDU_QUEUES, -+ .reset = pcie_reset, -+ .init = pcie_init, -+ .deinit = pcie_deinit, -+ .get_info = pcie_get_info, -+ .enable_data_tasks = pcie_enable_data_tasks, -+ .disable_data_tasks = pcie_disable_data_tasks, -+ .exec_cmd = pcie_exec_cmd, -+ .get_irq_num = pcie_get_irq_num, -+ .irq_handler = pcie_isr, -+ .irq_enable = pcie_irq_enable, -+ .irq_disable = pcie_irq_disable, -+ .download_firmware = pcie_download_firmware, -+ .timer_routine = pcie_timer_routine, -+ .tx_xmit = pcie_tx_xmit, -+ .tx_del_pkts_via_vif = pcie_tx_del_pkts_via_vif, -+ .tx_del_pkts_via_sta = pcie_tx_del_pkts_via_sta, -+ .tx_del_ampdu_pkts = pcie_tx_del_ampdu_pkts, -+ .tx_del_sta_amsdu_pkts = pcie_tx_del_sta_amsdu_pkts, -+ .tx_return_pkts = pcie_tx_return_pkts, -+ .get_device_node = pcie_get_device_node, -+ .get_survey = pcie_get_survey, -+ .reg_access = pcie_reg_access, -+}; -+ -+static int pcie_init_ndp(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ const struct hostcmd_get_hw_spec *get_hw_spec; -+ struct hostcmd_set_hw_spec set_hw_spec; -+ int rc; -+ -+ spin_lock_init(&pcie_priv->int_mask_lock); -+ tasklet_init(&pcie_priv->tx_task, -+ (void *)pcie_tx_skbs_ndp, (unsigned long)hw); -+ tasklet_disable(&pcie_priv->tx_task); -+ spin_lock_init(&pcie_priv->tx_desc_lock); -+ tasklet_init(&pcie_priv->rx_task, -+ (void *)pcie_rx_recv_ndp, (unsigned long)hw); -+ tasklet_disable(&pcie_priv->rx_task); -+ pcie_priv->txq_limit = TX_QUEUE_LIMIT; -+ pcie_priv->txq_wake_threshold = TX_WAKE_Q_THRESHOLD; -+ pcie_priv->is_tx_schedule = false; -+ pcie_priv->recv_limit = MAX_NUM_RX_DESC; -+ pcie_priv->is_rx_schedule = false; -+ -+ rc = pcie_tx_init_ndp(hw); -+ if (rc) { -+ wiphy_err(hw->wiphy, "%s: fail to initialize TX\n", -+ PCIE_DRV_NAME); -+ goto err_mwl_tx_init; -+ } -+ -+ rc = pcie_rx_init_ndp(hw); -+ if (rc) { -+ wiphy_err(hw->wiphy, "%s: fail to initialize RX\n", -+ PCIE_DRV_NAME); -+ goto err_mwl_rx_init; -+ } -+ -+ /* get and prepare HW specifications */ -+ get_hw_spec = mwl_fwcmd_get_hw_specs(hw); -+ if (!get_hw_spec) { -+ wiphy_err(hw->wiphy, "%s: fail to get HW specifications\n", -+ PCIE_DRV_NAME); -+ goto err_get_hw_specs; -+ } -+ ether_addr_copy(&priv->hw_data.mac_addr[0], -+ get_hw_spec->permanent_addr); -+ priv->hw_data.fw_release_num = le32_to_cpu(get_hw_spec->fw_release_num); -+ priv->hw_data.hw_version = get_hw_spec->version; -+ -+ /* prepare and set HW specifications */ -+ memset(&set_hw_spec, 0, sizeof(set_hw_spec)); -+ set_hw_spec.wcb_base[0] = -+ cpu_to_le32(pcie_priv->desc_data_ndp.pphys_tx_ring); -+ set_hw_spec.wcb_base[1] = -+ cpu_to_le32(pcie_priv->desc_data_ndp.pphys_tx_ring_done); -+ set_hw_spec.wcb_base[2] = -+ cpu_to_le32(pcie_priv->desc_data_ndp.pphys_rx_ring); -+ set_hw_spec.wcb_base[3] = -+ cpu_to_le32(pcie_priv->desc_data_ndp.pphys_rx_ring_done); -+ set_hw_spec.acnt_base_addr = -+ cpu_to_le32(pcie_priv->desc_data_ndp.pphys_acnt_ring); -+ set_hw_spec.acnt_buf_size = -+ cpu_to_le32(pcie_priv->desc_data_ndp.acnt_ring_size); -+ rc = mwl_fwcmd_set_hw_specs(hw, &set_hw_spec); -+ if (rc) { -+ wiphy_err(hw->wiphy, "%s: fail to set HW specifications\n", -+ PCIE_DRV_NAME); -+ goto err_set_hw_specs; -+ } -+ -+ return rc; -+ -+err_set_hw_specs: -+err_get_hw_specs: -+ -+ pcie_rx_deinit_ndp(hw); -+ -+err_mwl_rx_init: -+ -+ pcie_tx_deinit_ndp(hw); -+ -+err_mwl_tx_init: -+ -+ wiphy_err(hw->wiphy, "%s: init fail\n", PCIE_DRV_NAME); -+ -+ return rc; -+} -+ -+static void pcie_deinit_ndp(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ -+ pcie_rx_deinit_ndp(hw); -+ pcie_tx_deinit_ndp(hw); -+ tasklet_kill(&pcie_priv->rx_task); -+ tasklet_kill(&pcie_priv->tx_task); -+ pcie_reset(hw); -+} -+ -+static int pcie_get_info_ndp(struct ieee80211_hw *hw, char *buf, size_t size) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ char *p = buf; -+ int len = 0; -+ -+ len += scnprintf(p + len, size - len, "iobase0: %p\n", -+ pcie_priv->iobase0); -+ len += scnprintf(p + len, size - len, "iobase1: %p\n", -+ pcie_priv->iobase1); -+ len += scnprintf(p + len, size - len, -+ "tx limit: %d\n", pcie_priv->txq_limit); -+ len += scnprintf(p + len, size - len, -+ "rx limit: %d\n", pcie_priv->recv_limit); -+ return len; -+} -+ -+static int pcie_get_tx_status_ndp(struct ieee80211_hw *hw, char *buf, -+ size_t size) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ char *p = buf; -+ int len = 0; -+ -+ len += scnprintf(p + len, size - len, "tx_done_cnt: %d\n", -+ pcie_priv->tx_done_cnt); -+ len += scnprintf(p + len, size - len, "tx_desc_busy_cnt: %d\n", -+ pcie_priv->desc_data_ndp.tx_desc_busy_cnt); -+ len += scnprintf(p + len, size - len, "tx_sent_head: %d\n", -+ pcie_priv->desc_data_ndp.tx_sent_head); -+ len += scnprintf(p + len, size - len, "tx_sent_tail: %d\n", -+ pcie_priv->desc_data_ndp.tx_sent_tail); -+ len += scnprintf(p + len, size - len, "tx_done_head: %d\n", -+ readl(pcie_priv->iobase1 + MACREG_REG_TXDONEHEAD)); -+ len += scnprintf(p + len, size - len, "tx_done_tail: %d\n", -+ pcie_priv->desc_data_ndp.tx_done_tail); -+ len += scnprintf(p + len, size - len, "tx_vbuflist_idx: %d\n", -+ pcie_priv->desc_data_ndp.tx_vbuflist_idx); -+ return len; -+} -+ -+static int pcie_get_rx_status_ndp(struct ieee80211_hw *hw, char *buf, -+ size_t size) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ char *p = buf; -+ int len = 0; -+ -+ len += scnprintf(p + len, size - len, "rx_done_head: %d\n", -+ readl(pcie_priv->iobase1 + MACREG_REG_RXDONEHEAD)); -+ len += scnprintf(p + len, size - len, "rx_done_tail: %d\n", -+ readl(pcie_priv->iobase1 + MACREG_REG_RXDONETAIL)); -+ len += scnprintf(p + len, size - len, "rx_desc_head: %d\n", -+ readl(pcie_priv->iobase1 + MACREG_REG_RXDESCHEAD)); -+ len += scnprintf(p + len, size - len, "rx_skb_trace: %d\n", -+ skb_queue_len(&pcie_priv->rx_skb_trace)); -+ len += scnprintf(p + len, size - len, "rx_skb_unlink_err: %d\n", -+ pcie_priv->rx_skb_unlink_err); -+ len += scnprintf(p + len, size - len, "signature_err: %d\n", -+ pcie_priv->signature_err); -+ len += scnprintf(p + len, size - len, "recheck_rxringdone: %d\n", -+ pcie_priv->recheck_rxringdone); -+ len += scnprintf(p + len, size - len, "fast_data_cnt: %d\n", -+ pcie_priv->rx_cnts.fast_data_cnt); -+ len += scnprintf(p + len, size - len, "fast_bad_amsdu_cnt: %d\n", -+ pcie_priv->rx_cnts.fast_bad_amsdu_cnt); -+ len += scnprintf(p + len, size - len, "slow_noqueue_cnt: %d\n", -+ pcie_priv->rx_cnts.slow_noqueue_cnt); -+ len += scnprintf(p + len, size - len, "slow_norun_cnt: %d\n", -+ pcie_priv->rx_cnts.slow_norun_cnt); -+ len += scnprintf(p + len, size - len, "slow_mcast_cnt: %d\n", -+ pcie_priv->rx_cnts.slow_mcast_cnt); -+ len += scnprintf(p + len, size - len, "slow_bad_sta_cnt: %d\n", -+ pcie_priv->rx_cnts.slow_bad_sta_cnt); -+ len += scnprintf(p + len, size - len, "slow_bad_mic_cnt: %d\n", -+ pcie_priv->rx_cnts.slow_bad_mic_cnt); -+ len += scnprintf(p + len, size - len, "slow_bad_pn_cnt: %d\n", -+ pcie_priv->rx_cnts.slow_bad_pn_cnt); -+ len += scnprintf(p + len, size - len, "slow_mgmt_cnt: %d\n", -+ pcie_priv->rx_cnts.slow_mgmt_cnt); -+ len += scnprintf(p + len, size - len, "slow_promisc_cnt: %d\n", -+ pcie_priv->rx_cnts.slow_promisc_cnt); -+ len += scnprintf(p + len, size - len, "drop_cnt: %d\n", -+ pcie_priv->rx_cnts.drop_cnt); -+ len += scnprintf(p + len, size - len, "offch_promisc_cnt: %d\n", -+ pcie_priv->rx_cnts.offch_promisc_cnt); -+ len += scnprintf(p + len, size - len, "mu_pkt_cnt: %d\n", -+ pcie_priv->rx_cnts.mu_pkt_cnt); -+ return len; -+} -+ -+static void pcie_enable_data_tasks_ndp(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ -+ tasklet_enable(&pcie_priv->tx_task); -+ tasklet_enable(&pcie_priv->rx_task); -+} -+ -+static void pcie_disable_data_tasks_ndp(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ -+ tasklet_disable(&pcie_priv->tx_task); -+ tasklet_disable(&pcie_priv->rx_task); -+} -+ -+static irqreturn_t pcie_isr_ndp(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ u32 int_status; -+ -+ int_status = readl(pcie_priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); -+ -+ if (int_status == 0x00000000) -+ return IRQ_NONE; -+ -+ if (int_status == 0xffffffff) { -+ wiphy_warn(hw->wiphy, "card unplugged?\n"); -+ } else { -+ writel(~int_status, -+ pcie_priv->iobase1 + MACREG_REG_A2H_INTERRUPT_CAUSE); -+ -+ if (int_status & MACREG_A2HRIC_ACNT_HEAD_RDY) -+ ieee80211_queue_work(hw, &priv->account_handle); -+ -+ if (int_status & MACREG_A2HRIC_RX_DONE_HEAD_RDY) { -+ if (!pcie_priv->is_rx_schedule) { -+ pcie_mask_int(pcie_priv, -+ MACREG_A2HRIC_RX_DONE_HEAD_RDY, -+ false); -+ tasklet_schedule(&pcie_priv->rx_task); -+ pcie_priv->is_rx_schedule = true; -+ } -+ } -+ -+ if (int_status & MACREG_A2HRIC_NEWDP_DFS) { -+ wiphy_info(hw->wiphy, "radar detected by firmware\n"); -+ ieee80211_radar_detected(hw); -+ } -+ -+ if (int_status & MACREG_A2HRIC_NEWDP_CHANNEL_SWITCH) -+ ieee80211_queue_work(hw, &priv->chnl_switch_handle); -+ } -+ -+ return IRQ_HANDLED; -+} -+ -+static void pcie_irq_enable_ndp(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ -+ if (pcie_chk_adapter(pcie_priv)) { -+ writel(0x00, -+ pcie_priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); -+ writel(MACREG_A2HRIC_BIT_MASK_NDP, -+ pcie_priv->iobase1 + MACREG_REG_A2H_INTERRUPT_MASK); -+ } -+} -+ -+static void pcie_timer_routine_ndp(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ int num = SYSADPT_TX_WMM_QUEUES; -+ static int cnt; -+ -+ if (!pcie_priv->is_tx_schedule) { -+ while (num--) { -+ if (skb_queue_len(&pcie_priv->txq[num]) > 0) { -+ tasklet_schedule(&pcie_priv->tx_task); -+ pcie_priv->is_tx_schedule = true; -+ break; -+ } -+ } -+ } -+ -+ if ((++cnt * SYSADPT_TIMER_WAKEUP_TIME) >= CHECK_TX_DONE_TIME) { -+ pcie_tx_done_ndp(hw); -+ cnt = 0; -+ } -+} -+ -+static void pcie_tx_return_pkts_ndp(struct ieee80211_hw *hw) -+{ -+ pcie_tx_done_ndp(hw); -+} -+ -+static void pcie_set_sta_id(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta, -+ bool sta_mode, bool set) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct mwl_sta *sta_info; -+ u16 stnid; -+ -+ sta_info = mwl_dev_get_sta(sta); -+ stnid = sta_mode ? 0 : sta_info->stnid; -+ pcie_priv->sta_link[stnid] = set ? sta : NULL; -+} -+ -+static void pcie_tx_account(struct mwl_priv *priv, -+ struct mwl_sta *sta_info, -+ struct acnt_tx_s *acnt_tx) -+{ -+ u32 rate_info, tx_cnt; -+ u8 index, type, rate_ac, format, bw, gi, mcs, nss; -+ u16 ratemask; -+ u8 i, found; -+ struct mwl_tx_hist *tx_hist; -+ struct mwl_tx_hist_data *tx_hist_data; -+ -+ rate_info = le32_to_cpu(acnt_tx->tx_info.rate_info); -+ tx_cnt = le32_to_cpu(acnt_tx->tx_cnt); -+ index = acnt_tx->rate_tbl_index; -+ type = acnt_tx->type; -+ -+ if (!rate_info) -+ return; -+ sta_info->tx_rate_info = rate_info; -+ -+ tx_hist = &sta_info->tx_hist; -+ if (!tx_hist || (type >= SU_MU_TYPE_CNT)) -+ return; -+ -+ format = rate_info & MWL_TX_RATE_FORMAT_MASK; -+ bw = (rate_info & MWL_TX_RATE_BANDWIDTH_MASK) >> -+ MWL_TX_RATE_BANDWIDTH_SHIFT; -+ gi = (rate_info & MWL_TX_RATE_SHORTGI_MASK) >> -+ MWL_TX_RATE_SHORTGI_SHIFT; -+ mcs = (rate_info & MWL_TX_RATE_RATEIDMCS_MASK) >> -+ MWL_TX_RATE_RATEIDMCS_SHIFT; -+ -+ tx_hist->cur_rate_info[type] = rate_info; -+ -+ /* Rate table index is valid */ -+ if (index != 0xff) { -+ if (type == MU_MIMO) { -+ rate_ac = mcs & 0xf; -+ nss = mcs >> 4; -+ if (nss < (QS_NUM_SUPPORTED_11AC_NSS - 1)) { -+ tx_hist_data = -+ &tx_hist->mu_rate[nss][bw][gi][rate_ac]; -+ tx_hist_data->rateinfo = rate_info; -+ tx_hist_data->cnt++; -+ tx_hist->total_tx_cnt[type] += tx_cnt; -+ } -+ } else { -+ /* If legacy, skip legacy preamble bit 15 */ -+ if (format == TX_RATE_FORMAT_LEGACY) -+ ratemask = 0xfff; -+ else -+ ratemask = 0xffff; -+ tx_hist_data = &tx_hist->su_rate[0]; -+ if ((tx_hist_data[index].rateinfo & ratemask) == -+ (rate_info & ratemask)) { -+ tx_hist_data[index].cnt++; -+ tx_hist->total_tx_cnt[type] += tx_cnt; -+ } -+ } -+ } else { -+ if (type == MU_MIMO) { -+ rate_ac = mcs & 0xf; -+ nss = mcs >> 4; -+ if (nss < (QS_NUM_SUPPORTED_11AC_NSS - 1)) { -+ tx_hist_data = -+ &tx_hist->mu_rate[nss][bw][gi][rate_ac]; -+ tx_hist_data->rateinfo = rate_info; -+ tx_hist_data->cnt++; -+ tx_hist->total_tx_cnt[type] += tx_cnt; -+ } -+ } else { -+ /* If legacy, skip legacy preamble bit 15 */ -+ if (format == TX_RATE_FORMAT_LEGACY) -+ ratemask = 0xfff; -+ else -+ ratemask = 0xffff; -+ tx_hist_data = &tx_hist->custom_rate[0]; -+ /* Go through non rate table buffer to see if any has -+ * been used. If all used up, recycle by using index 0 -+ */ -+ for (i = 0; i < TX_RATE_HISTO_CUSTOM_CNT; i++) { -+ if (!tx_hist_data[i].rateinfo || -+ ((tx_hist_data[i].rateinfo & ratemask) == -+ (rate_info & ratemask))) { -+ found = 1; -+ break; -+ } -+ } -+ if (found) -+ index = i; -+ else -+ index = 0; /* reuse index 0 buffer */ -+ tx_hist_data[index].rateinfo = rate_info; -+ tx_hist_data[index].cnt++; -+ tx_hist->total_tx_cnt[type] += tx_cnt; -+ } -+ } -+} -+ -+static void pcie_rx_account(struct mwl_priv *priv, -+ struct mwl_sta *sta_info, -+ struct acnt_rx_s *acnt_rx) -+{ -+ u32 sig1, sig2, rate, param; -+ u16 format, nss, bw, gi, rate_mcs; -+ -+ sig1 = (le32_to_cpu(acnt_rx->rx_info.ht_sig1) >> -+ RXINFO_HT_SIG1_SHIFT) & RXINFO_HT_SIG1_MASK; -+ sig2 = (le32_to_cpu(acnt_rx->rx_info.ht_sig2_rate) >> -+ RXINFO_HT_SIG2_SHIFT) & RXINFO_HT_SIG2_MASK; -+ rate = (le32_to_cpu(acnt_rx->rx_info.ht_sig2_rate) >> -+ RXINFO_RATE_SHIFT) & RXINFO_RATE_MASK; -+ param = (le32_to_cpu(acnt_rx->rx_info.param) >> -+ RXINFO_PARAM_SHIFT) & RXINFO_PARAM_MASK; -+ -+ format = (param >> 3) & 0x7; -+ nss = 0; -+ bw = RX_RATE_INFO_HT20; -+ switch (format) { -+ case RX_RATE_INFO_FORMAT_11A: -+ rate_mcs = rate & 0xF; -+ if (rate_mcs == 10) -+ rate_mcs = 7; /* 12 Mbps */ -+ else -+ rate_mcs = utils_get_rate_id(rate_mcs); -+ gi = RX_RATE_INFO_SHORT_INTERVAL; -+ if ((rate_mcs == 5) || (rate_mcs == 7) || (rate_mcs == 9)) -+ return; -+ break; -+ case RX_RATE_INFO_FORMAT_11B: -+ rate_mcs = utils_get_rate_id(rate & 0xF); -+ gi = RX_RATE_INFO_LONG_INTERVAL; -+ if ((rate_mcs == 0) || (rate_mcs == 1)) -+ return; -+ break; -+ case RX_RATE_INFO_FORMAT_11N: -+ if ((sig1 & 0x3f) >= 16) -+ return; -+ bw = (sig1 >> 7) & 0x1; -+ gi = (sig2 >> 7) & 0x1; -+ rate_mcs = sig1 & 0x3F; -+ if (rate_mcs > 76) -+ return; -+ break; -+ case RX_RATE_INFO_FORMAT_11AC: -+ if (((sig2 >> 4) & 0xf) >= 10) -+ return; -+ nss = (sig1 >> 10) & 0x3; -+ if (!nss) -+ return; -+ bw = sig1 & 0x3; -+ gi = sig2 & 0x1; -+ rate_mcs = (sig2 >> 4) & 0xF; -+ if (rate_mcs > 9) -+ return; -+ break; -+ default: -+ return; -+ } -+ -+ sta_info->rx_format = format; -+ sta_info->rx_nss = nss; -+ sta_info->rx_bw = bw; -+ sta_info->rx_gi = gi; -+ sta_info->rx_rate_mcs = rate_mcs; -+ sta_info->rx_signal = ((le32_to_cpu(acnt_rx->rx_info.rssi_x) >> -+ RXINFO_RSSI_X_SHIFT) & RXINFO_RSSI_X_MASK); -+} -+ -+static void pcie_tx_per(struct mwl_priv *priv, struct mwl_sta *sta_info, -+ struct acnt_ra_s *acnt_ra) -+{ -+ u32 rate_info; -+ u8 index, per, type, rate_ac, per_index, format, bw, gi, mcs, nss; -+ u16 ratemask; -+ u8 i, found; -+ struct mwl_tx_hist *tx_hist; -+ struct mwl_tx_hist_data *tx_hist_data; -+ -+ rate_info = le32_to_cpu(acnt_ra->rate_info); -+ index = acnt_ra->rate_tbl_index; -+ per = acnt_ra->per; -+ type = acnt_ra->type; -+ -+ tx_hist = &sta_info->tx_hist; -+ -+ if (!tx_hist || !rate_info || (type >= SU_MU_TYPE_CNT)) -+ return; -+ -+ if ((type == SU_MIMO) && (index >= MAX_SUPPORTED_RATES) && -+ (index != 0xFF)) -+ return; -+ -+ if (per >= TX_HISTO_PER_THRES[3]) -+ per_index = 4; -+ else if (per >= TX_HISTO_PER_THRES[2]) -+ per_index = 3; -+ else if (per >= TX_HISTO_PER_THRES[1]) -+ per_index = 2; -+ else if (per >= TX_HISTO_PER_THRES[0]) -+ per_index = 1; -+ else -+ per_index = 0; -+ -+ format = rate_info & MWL_TX_RATE_FORMAT_MASK; -+ bw = (rate_info & MWL_TX_RATE_BANDWIDTH_MASK) >> -+ MWL_TX_RATE_BANDWIDTH_SHIFT; -+ gi = (rate_info & MWL_TX_RATE_SHORTGI_MASK) >> -+ MWL_TX_RATE_SHORTGI_SHIFT; -+ mcs = (rate_info & MWL_TX_RATE_RATEIDMCS_MASK) >> -+ MWL_TX_RATE_RATEIDMCS_SHIFT; -+ -+ /* Rate table index is valid */ -+ if (index != 0xff) { -+ if (type == MU_MIMO) { -+ rate_ac = mcs & 0xf; -+ nss = mcs >> 4; -+ if (nss < (QS_NUM_SUPPORTED_11AC_NSS - 1)) { -+ tx_hist_data = -+ &tx_hist->mu_rate[nss][bw][gi][rate_ac]; -+ tx_hist_data->rateinfo = rate_info; -+ tx_hist_data->per[per_index]++; -+ } -+ } else { -+ /* If legacy, skip legacy preamble bit 15 */ -+ if (format == TX_RATE_FORMAT_LEGACY) -+ ratemask = 0xfff; -+ else -+ ratemask = 0xffff; -+ tx_hist_data = &tx_hist->su_rate[0]; -+ if ((tx_hist_data[index].rateinfo & ratemask) == -+ (rate_info & ratemask)) -+ tx_hist_data[index].per[per_index]++; -+ } -+ } else { -+ if (type == MU_MIMO) { -+ rate_ac = mcs & 0xf; -+ nss = mcs >> 4; -+ if (nss < (QS_NUM_SUPPORTED_11AC_NSS - 1)) { -+ tx_hist_data = -+ &tx_hist->mu_rate[nss][bw][gi][rate_ac]; -+ tx_hist_data->rateinfo = rate_info; -+ tx_hist_data->per[per_index]++; -+ } -+ } else { -+ /* If legacy, skip legacy preamble bit 15 */ -+ if (format == TX_RATE_FORMAT_LEGACY) -+ ratemask = 0xfff; -+ else -+ ratemask = 0xffff; -+ tx_hist_data = &tx_hist->custom_rate[0]; -+ /* Go through non rate table buffer to see if any has -+ * been used. If all used up, recycle by using index 0 -+ */ -+ for (i = 0; i < TX_RATE_HISTO_CUSTOM_CNT; i++) { -+ if (!tx_hist_data[i].rateinfo || -+ ((tx_hist_data[i].rateinfo & ratemask) == -+ (rate_info & ratemask))) { -+ found = 1; -+ break; -+ } -+ } -+ if (found) -+ index = i; -+ else -+ index = 0; /* reuse index 0 buffer */ -+ tx_hist_data[index].rateinfo = rate_info; -+ tx_hist_data[index].per[per_index]++; -+ } -+ } -+} -+ -+static void pcie_ba_account(struct mwl_priv *priv, -+ struct mwl_sta *sta_info, -+ struct acnt_ba_s *acnt_ba) -+{ -+ struct mwl_tx_ba_hist *ba_hist = &sta_info->ba_hist; -+ -+ if (sta_info->stnid != le16_to_cpu(acnt_ba->stnid)) -+ return; -+ -+ if (ba_hist->enable && ba_hist->ba_stats && -+ (ba_hist->index < ACNT_BA_SIZE)) { -+ ba_hist->type = acnt_ba->type; -+ ba_hist->ba_stats[ba_hist->index].ba_hole = acnt_ba->ba_hole; -+ ba_hist->ba_stats[ba_hist->index].ba_expected = -+ acnt_ba->ba_expected; -+ ba_hist->ba_stats[ba_hist->index].no_ba = acnt_ba->no_ba; -+ ba_hist->index++; -+ if (ba_hist->index == ACNT_BA_SIZE) -+ wiphy_info(priv->hw->wiphy, -+ "Aid:%d BA histo collection done\n", -+ priv->ba_aid); -+ } -+} -+ -+static void pcie_bf_mimo_ctrl_decode(struct mwl_priv *priv, -+ struct acnt_bf_mimo_ctrl_s *bf_mimo_ctrl) -+{ -+ struct file *fp_data = NULL; -+ const char filename[] = "/tmp/BF_MIMO_Ctrl_Field_Output.txt"; -+ char str_buf[256]; -+ char *buf = &str_buf[0]; -+ mm_segment_t oldfs; -+ -+ oldfs = get_fs(); -+ set_fs(KERNEL_DS); -+ -+ buf += sprintf(buf, "\nMAC: %pM\n", bf_mimo_ctrl->rec_mac); -+ buf += sprintf(buf, "SU_0_MU_1: %d\n", bf_mimo_ctrl->type); -+ buf += sprintf(buf, "MIMO_Ctrl_Field: 0x%x\n", -+ le32_to_cpu(bf_mimo_ctrl->mimo_ctrl)); -+ buf += sprintf(buf, "Comp_BF_Report_First_8Bytes: 0x%llx\n", -+ le64_to_cpu(bf_mimo_ctrl->comp_bf_rep)); -+ -+ fp_data = filp_open(filename, O_RDWR | O_CREAT | O_TRUNC, 0); -+ -+ if (!IS_ERR(fp_data)) { -+ __kernel_write(fp_data, str_buf, strlen(str_buf), -+ &fp_data->f_pos); -+ filp_close(fp_data, current->files); -+ } else { -+ wiphy_err(priv->hw->wiphy, "Error opening %s! %x\n", -+ filename, (unsigned int)fp_data); -+ } -+ -+ set_fs(oldfs); -+} -+ -+static void pcie_process_account(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data_ndp *desc = &pcie_priv->desc_data_ndp; -+ u32 acnt_head, acnt_tail; -+ u32 read_size; -+ u8 *acnt_recds; -+ u8 *pstart, *pend; -+ struct acnt_s *acnt; -+ struct acnt_tx_s *acnt_tx; -+ struct acnt_rx_s *acnt_rx; -+ struct acnt_ra_s *acnt_ra; -+ struct acnt_ba_s *acnt_ba; -+ struct acnt_bf_mimo_ctrl_s *acnt_bf_mimo_ctrl; -+ struct pcie_dma_data *dma_data; -+ struct mwl_sta *sta_info; -+ u16 nf_a, nf_b, nf_c, nf_d; -+ u16 stnid; -+ u8 type; -+ -+ acnt_head = readl(pcie_priv->iobase1 + MACREG_REG_ACNTHEAD); -+ acnt_tail = readl(pcie_priv->iobase1 + MACREG_REG_ACNTTAIL); -+ -+ if (acnt_tail == acnt_head) -+ return; -+ -+ if (acnt_tail > acnt_head) { -+ read_size = desc->acnt_ring_size - acnt_tail + acnt_head; -+ if (read_size > desc->acnt_ring_size) { -+ wiphy_err(hw->wiphy, -+ "account size overflow (%d %d %d)\n", -+ acnt_head, acnt_tail, read_size); -+ goto process_next; -+ } -+ memset(desc->pacnt_buf, 0, desc->acnt_ring_size); -+ memcpy(desc->pacnt_buf, desc->pacnt_ring + acnt_tail, -+ desc->acnt_ring_size - acnt_tail); -+ memcpy(desc->pacnt_buf + desc->acnt_ring_size - acnt_tail, -+ desc->pacnt_ring, acnt_head); -+ acnt_recds = desc->pacnt_buf; -+ } else { -+ read_size = acnt_head - acnt_tail; -+ if (read_size > desc->acnt_ring_size) { -+ wiphy_err(hw->wiphy, -+ "account size overflow (%d %d %d)\n", -+ acnt_head, acnt_tail, read_size); -+ goto process_next; -+ } -+ acnt_recds = desc->pacnt_ring + acnt_tail; -+ } -+ -+ pstart = acnt_recds; -+ pend = pstart + read_size; -+ while (pstart < pend) { -+ acnt = (struct acnt_s *)pstart; -+ -+ switch (le16_to_cpu(acnt->code)) { -+ case ACNT_CODE_BUSY: -+ pcie_priv->acnt_busy++; -+ break; -+ case ACNT_CODE_WRAP: -+ pcie_priv->acnt_wrap++; -+ break; -+ case ACNT_CODE_DROP: -+ pcie_priv->acnt_drop++; -+ break; -+ case ACNT_CODE_TX_ENQUEUE: -+ acnt_tx = (struct acnt_tx_s *)pstart; -+ sta_info = utils_find_sta(priv, acnt_tx->hdr.wh.addr1); -+ if (sta_info) { -+ spin_lock_bh(&priv->sta_lock); -+ pcie_tx_account(priv, sta_info, acnt_tx); -+ spin_unlock_bh(&priv->sta_lock); -+ } -+ break; -+ case ACNT_CODE_RX_PPDU: -+ acnt_rx = (struct acnt_rx_s *)pstart; -+ nf_a = (le32_to_cpu(acnt_rx->rx_info.nf_a_b) >> -+ RXINFO_NF_A_SHIFT) & RXINFO_NF_A_MASK; -+ nf_b = (le32_to_cpu(acnt_rx->rx_info.nf_a_b) >> -+ RXINFO_NF_B_SHIFT) & RXINFO_NF_B_MASK; -+ nf_c = (le32_to_cpu(acnt_rx->rx_info.nf_c_d) >> -+ RXINFO_NF_C_SHIFT) & RXINFO_NF_C_MASK; -+ nf_d = (le32_to_cpu(acnt_rx->rx_info.nf_c_d) >> -+ RXINFO_NF_D_SHIFT) & RXINFO_NF_D_MASK; -+ if ((nf_a >= 2048) && (nf_b >= 2048) && -+ (nf_c >= 2048) && (nf_d >= 2048)) { -+ nf_a = ((4096 - nf_a) >> 4); -+ nf_b = ((4096 - nf_b) >> 4); -+ nf_c = ((4096 - nf_c) >> 4); -+ nf_d = ((4096 - nf_d) >> 4); -+ priv->noise = -+ -((nf_a + nf_b + nf_c + nf_d) / 4); -+ } -+ dma_data = (struct pcie_dma_data *) -+ &acnt_rx->rx_info.hdr[0]; -+ sta_info = utils_find_sta(priv, dma_data->wh.addr2); -+ if (sta_info) { -+ spin_lock_bh(&priv->sta_lock); -+ pcie_rx_account(priv, sta_info, acnt_rx); -+ spin_unlock_bh(&priv->sta_lock); -+ } -+ break; -+ case ACNT_CODE_RA_STATS: -+ acnt_ra = (struct acnt_ra_s *)pstart; -+ stnid = le16_to_cpu(acnt_ra->stn_id); -+ if ((stnid > 0) && (stnid <= priv->stnid_num)) { -+ type = acnt_ra->type; -+ if (type < 2) { -+ if (acnt_ra->tx_attempt_cnt >= 250) -+ priv->ra_tx_attempt[type][5]++; -+ else if (acnt_ra->tx_attempt_cnt >= 100) -+ priv->ra_tx_attempt[type][4]++; -+ else if (acnt_ra->tx_attempt_cnt >= 50) -+ priv->ra_tx_attempt[type][3]++; -+ else if (acnt_ra->tx_attempt_cnt >= 15) -+ priv->ra_tx_attempt[type][2]++; -+ else if (acnt_ra->tx_attempt_cnt >= 4) -+ priv->ra_tx_attempt[type][1]++; -+ else -+ priv->ra_tx_attempt[type][0]++; -+ } -+ sta_info = utils_find_sta_by_id(priv, stnid); -+ if (sta_info) { -+ spin_lock_bh(&priv->sta_lock); -+ pcie_tx_per(priv, sta_info, acnt_ra); -+ spin_unlock_bh(&priv->sta_lock); -+ } -+ } -+ break; -+ case ACNT_CODE_BA_STATS: -+ acnt_ba = (struct acnt_ba_s *)pstart; -+ if (priv->ba_aid) { -+ sta_info = utils_find_sta_by_aid(priv, -+ priv->ba_aid); -+ if (sta_info) { -+ spin_lock_bh(&priv->sta_lock); -+ pcie_ba_account(priv, sta_info, -+ acnt_ba); -+ spin_unlock_bh(&priv->sta_lock); -+ } -+ } -+ break; -+ case ACNT_CODE_BF_MIMO_CTRL: -+ acnt_bf_mimo_ctrl = -+ (struct acnt_bf_mimo_ctrl_s *)pstart; -+ pcie_bf_mimo_ctrl_decode(priv, acnt_bf_mimo_ctrl); -+ break; -+ default: -+ break; -+ } -+ -+ if (acnt->len) -+ pstart += acnt->len * 4; -+ else -+ goto process_next; -+ } -+process_next: -+ acnt_tail = acnt_head; -+ writel(acnt_tail, pcie_priv->iobase1 + MACREG_REG_ACNTTAIL); -+} -+ -+static int pcie_mcast_cts(struct ieee80211_hw *hw, bool enable) -+{ -+ return mwl_fwcmd_mcast_cts(hw, enable ? 1 : 0); -+} -+ -+static const struct mwl_hif_ops pcie_hif_ops_ndp = { -+ .driver_name = PCIE_DRV_NAME, -+ .driver_version = PCIE_DRV_VERSION, -+ .tx_head_room = PCIE_MIN_BYTES_HEADROOM, -+ .ampdu_num = AMPDU_QUEUES_NDP, -+ .reset = pcie_reset, -+ .init = pcie_init_ndp, -+ .deinit = pcie_deinit_ndp, -+ .get_info = pcie_get_info_ndp, -+ .get_tx_status = pcie_get_tx_status_ndp, -+ .get_rx_status = pcie_get_rx_status_ndp, -+ .enable_data_tasks = pcie_enable_data_tasks_ndp, -+ .disable_data_tasks = pcie_disable_data_tasks_ndp, -+ .exec_cmd = pcie_exec_cmd, -+ .get_irq_num = pcie_get_irq_num, -+ .irq_handler = pcie_isr_ndp, -+ .irq_enable = pcie_irq_enable_ndp, -+ .irq_disable = pcie_irq_disable, -+ .download_firmware = pcie_download_firmware, -+ .timer_routine = pcie_timer_routine_ndp, -+ .tx_xmit = pcie_tx_xmit_ndp, -+ .tx_return_pkts = pcie_tx_return_pkts_ndp, -+ .get_device_node = pcie_get_device_node, -+ .get_survey = pcie_get_survey, -+ .reg_access = pcie_reg_access, -+ .set_sta_id = pcie_set_sta_id, -+ .process_account = pcie_process_account, -+ .mcast_cts = pcie_mcast_cts, -+}; -+ -+static int pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) -+{ -+ static bool printed_version; -+ struct ieee80211_hw *hw; -+ struct mwl_priv *priv; -+ struct pcie_priv *pcie_priv; -+ const struct mwl_hif_ops *hif_ops; -+ int rc = 0; -+ -+ if (id->driver_data >= MWLUNKNOWN) -+ return -ENODEV; -+ -+ if (!printed_version) { -+ pr_info("<<%s version %s>>\n", -+ PCIE_DRV_DESC, PCIE_DRV_VERSION); -+ printed_version = true; -+ } -+ -+ rc = pci_enable_device(pdev); -+ if (rc) { -+ pr_err("%s: cannot enable new PCI device\n", -+ PCIE_DRV_NAME); -+ return rc; -+ } -+ -+ rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); -+ if (rc) { -+ pr_err("%s: 32-bit PCI DMA not supported\n", -+ PCIE_DRV_NAME); -+ goto err_pci_disable_device; -+ } -+ -+ pci_set_master(pdev); -+ -+ if (id->driver_data == MWL8964) -+ hif_ops = &pcie_hif_ops_ndp; -+ else -+ hif_ops = &pcie_hif_ops; -+ hw = mwl_alloc_hw(MWL_BUS_PCIE, id->driver_data, &pdev->dev, -+ hif_ops, sizeof(*pcie_priv)); -+ if (!hw) { -+ pr_err("%s: mwlwifi hw alloc failed\n", -+ PCIE_DRV_NAME); -+ rc = -ENOMEM; -+ goto err_pci_disable_device; -+ } -+ -+ pci_set_drvdata(pdev, hw); -+ -+ priv = hw->priv; -+ priv->antenna_tx = pcie_chip_tbl[priv->chip_type].antenna_tx; -+ priv->antenna_rx = pcie_chip_tbl[priv->chip_type].antenna_rx; -+ pcie_priv = priv->hif.priv; -+ pcie_priv->mwl_priv = priv; -+ pcie_priv->pdev = pdev; -+ if (id->driver_data != MWL8964) { -+ pcie_priv->tx_head_room = PCIE_MIN_BYTES_HEADROOM; -+ if (id->driver_data == MWL8997) { -+ if (NET_SKB_PAD < PCIE_MIN_TX_HEADROOM_KF2) { -+ pcie_priv->tx_head_room = -+ PCIE_MIN_TX_HEADROOM_KF2; -+ pcie_hif_ops.tx_head_room = -+ PCIE_MIN_TX_HEADROOM_KF2; -+ } -+ } -+ } -+ -+ rc = pcie_alloc_resource(pcie_priv); -+ if (rc) -+ goto err_alloc_pci_resource; -+ -+ rc = mwl_init_hw(hw, pcie_chip_tbl[priv->chip_type].fw_image, -+ pcie_chip_tbl[priv->chip_type].cal_file, -+ pcie_chip_tbl[priv->chip_type].txpwrlmt_file); -+ if (rc) -+ goto err_wl_init; -+ -+ vendor_cmd_basic_event(hw->wiphy, MWL_VENDOR_EVENT_DRIVER_READY); -+ -+ return rc; -+ -+err_wl_init: -+ -+ pcie_reset(hw); -+ -+err_alloc_pci_resource: -+ -+ pci_set_drvdata(pdev, NULL); -+ mwl_free_hw(hw); -+ -+err_pci_disable_device: -+ -+ pci_disable_device(pdev); -+ -+ return rc; -+} -+ -+static void pcie_remove(struct pci_dev *pdev) -+{ -+ struct ieee80211_hw *hw = pci_get_drvdata(pdev); -+ struct mwl_priv *priv = hw->priv; -+ -+ priv->rmmod = true; -+ while (priv->in_send_cmd) -+ usleep_range(1000, 2000); -+ vendor_cmd_basic_event(hw->wiphy, MWL_VENDOR_EVENT_DRIVER_START_REMOVE); -+ mwl_deinit_hw(hw); -+ pci_set_drvdata(pdev, NULL); -+ mwl_free_hw(hw); -+ pci_disable_device(pdev); -+} -+ -+static struct pci_driver mwl_pcie_driver = { -+ .name = PCIE_DRV_NAME, -+ .id_table = pcie_id_tbl, -+ .probe = pcie_probe, -+ .remove = pcie_remove -+}; -+ -+module_pci_driver(mwl_pcie_driver); -+ -+MODULE_DESCRIPTION(PCIE_DRV_DESC); -+MODULE_VERSION(PCIE_DRV_VERSION); -+MODULE_AUTHOR("Marvell Semiconductor, Inc."); -+MODULE_LICENSE("GPL v2"); -+MODULE_SUPPORTED_DEVICE(PCIE_DEV_NAME); -+MODULE_DEVICE_TABLE(pci, pcie_id_tbl); -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx.c b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx.c -new file mode 100644 -index 000000000000..25076c6d66df ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx.c -@@ -0,0 +1,540 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file implements receive related functions. */ -+ -+#include -+#include -+ -+#include "sysadpt.h" -+#include "core.h" -+#include "utils.h" -+#include "hif/pcie/dev.h" -+#include "hif/pcie/rx.h" -+ -+#define MAX_NUM_RX_RING_BYTES (PCIE_MAX_NUM_RX_DESC * \ -+ sizeof(struct pcie_rx_desc)) -+ -+#define MAX_NUM_RX_HNDL_BYTES (PCIE_MAX_NUM_RX_DESC * \ -+ sizeof(struct pcie_rx_hndl)) -+ -+#define DECRYPT_ERR_MASK 0x80 -+#define GENERAL_DECRYPT_ERR 0xFF -+#define TKIP_DECRYPT_MIC_ERR 0x02 -+#define WEP_DECRYPT_ICV_ERR 0x04 -+#define TKIP_DECRYPT_ICV_ERR 0x08 -+ -+#define W836X_RSSI_OFFSET 8 -+ -+static int pcie_rx_ring_alloc(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data *desc; -+ -+ desc = &pcie_priv->desc_data[0]; -+ -+ desc->prx_ring = (struct pcie_rx_desc *) -+ dma_alloc_coherent(priv->dev, -+ MAX_NUM_RX_RING_BYTES, -+ &desc->pphys_rx_ring, -+ GFP_KERNEL); -+ -+ if (!desc->prx_ring) { -+ wiphy_err(priv->hw->wiphy, "cannot alloc mem\n"); -+ return -ENOMEM; -+ } -+ -+ memset(desc->prx_ring, 0x00, MAX_NUM_RX_RING_BYTES); -+ -+ desc->rx_hndl = kzalloc(MAX_NUM_RX_HNDL_BYTES, GFP_KERNEL); -+ -+ if (!desc->rx_hndl) { -+ dma_free_coherent(priv->dev, -+ MAX_NUM_RX_RING_BYTES, -+ desc->prx_ring, -+ desc->pphys_rx_ring); -+ return -ENOMEM; -+ } -+ -+ return 0; -+} -+ -+static int pcie_rx_ring_init(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data *desc; -+ int i; -+ struct pcie_rx_hndl *rx_hndl; -+ dma_addr_t dma; -+ u32 val; -+ -+ desc = &pcie_priv->desc_data[0]; -+ -+ if (desc->prx_ring) { -+ desc->rx_buf_size = SYSADPT_MAX_AGGR_SIZE; -+ -+ for (i = 0; i < PCIE_MAX_NUM_RX_DESC; i++) { -+ rx_hndl = &desc->rx_hndl[i]; -+ rx_hndl->psk_buff = -+ dev_alloc_skb(desc->rx_buf_size); -+ -+ if (!rx_hndl->psk_buff) { -+ wiphy_err(priv->hw->wiphy, -+ "rxdesc %i: no skbuff available\n", -+ i); -+ return -ENOMEM; -+ } -+ -+ skb_reserve(rx_hndl->psk_buff, -+ PCIE_MIN_BYTES_HEADROOM); -+ desc->prx_ring[i].rx_control = -+ EAGLE_RXD_CTRL_DRIVER_OWN; -+ desc->prx_ring[i].status = EAGLE_RXD_STATUS_OK; -+ desc->prx_ring[i].qos_ctrl = 0x0000; -+ desc->prx_ring[i].channel = 0x00; -+ desc->prx_ring[i].rssi = 0x00; -+ desc->prx_ring[i].pkt_len = -+ cpu_to_le16(SYSADPT_MAX_AGGR_SIZE); -+ dma = pci_map_single(pcie_priv->pdev, -+ rx_hndl->psk_buff->data, -+ desc->rx_buf_size, -+ PCI_DMA_FROMDEVICE); -+ if (pci_dma_mapping_error(pcie_priv->pdev, dma)) { -+ wiphy_err(priv->hw->wiphy, -+ "failed to map pci memory!\n"); -+ return -ENOMEM; -+ } -+ desc->prx_ring[i].pphys_buff_data = cpu_to_le32(dma); -+ val = (u32)desc->pphys_rx_ring + -+ ((i + 1) * sizeof(struct pcie_rx_desc)); -+ desc->prx_ring[i].pphys_next = cpu_to_le32(val); -+ rx_hndl->pdesc = &desc->prx_ring[i]; -+ if (i < (PCIE_MAX_NUM_RX_DESC - 1)) -+ rx_hndl->pnext = &desc->rx_hndl[i + 1]; -+ } -+ desc->prx_ring[PCIE_MAX_NUM_RX_DESC - 1].pphys_next = -+ cpu_to_le32((u32)desc->pphys_rx_ring); -+ desc->rx_hndl[PCIE_MAX_NUM_RX_DESC - 1].pnext = -+ &desc->rx_hndl[0]; -+ desc->pnext_rx_hndl = &desc->rx_hndl[0]; -+ -+ return 0; -+ } -+ -+ wiphy_err(priv->hw->wiphy, "no valid RX mem\n"); -+ -+ return -ENOMEM; -+} -+ -+static void pcie_rx_ring_cleanup(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data *desc; -+ int i; -+ struct pcie_rx_hndl *rx_hndl; -+ -+ desc = &pcie_priv->desc_data[0]; -+ -+ if (desc->prx_ring) { -+ for (i = 0; i < PCIE_MAX_NUM_RX_DESC; i++) { -+ rx_hndl = &desc->rx_hndl[i]; -+ if (!rx_hndl->psk_buff) -+ continue; -+ -+ pci_unmap_single(pcie_priv->pdev, -+ le32_to_cpu -+ (rx_hndl->pdesc->pphys_buff_data), -+ desc->rx_buf_size, -+ PCI_DMA_FROMDEVICE); -+ -+ dev_kfree_skb_any(rx_hndl->psk_buff); -+ -+ wiphy_debug(priv->hw->wiphy, -+ "unmapped+free'd %i 0x%p 0x%x %i\n", -+ i, rx_hndl->psk_buff->data, -+ le32_to_cpu( -+ rx_hndl->pdesc->pphys_buff_data), -+ desc->rx_buf_size); -+ -+ rx_hndl->psk_buff = NULL; -+ } -+ } -+} -+ -+static void pcie_rx_ring_free(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data *desc; -+ -+ desc = &pcie_priv->desc_data[0]; -+ -+ if (desc->prx_ring) { -+ pcie_rx_ring_cleanup(priv); -+ -+ dma_free_coherent(priv->dev, -+ MAX_NUM_RX_RING_BYTES, -+ desc->prx_ring, -+ desc->pphys_rx_ring); -+ -+ desc->prx_ring = NULL; -+ } -+ -+ kfree(desc->rx_hndl); -+ -+ desc->pnext_rx_hndl = NULL; -+} -+ -+static inline void pcie_rx_status(struct mwl_priv *priv, -+ struct pcie_rx_desc *pdesc, -+ struct ieee80211_rx_status *status) -+{ -+ u16 rx_rate; -+ -+ memset(status, 0, sizeof(*status)); -+ -+ if (priv->chip_type == MWL8997) -+ status->signal = (s8)pdesc->rssi; -+ else -+ status->signal = -(pdesc->rssi + W836X_RSSI_OFFSET); -+ -+ rx_rate = le16_to_cpu(pdesc->rate); -+ pcie_rx_prepare_status(priv, -+ rx_rate & MWL_RX_RATE_FORMAT_MASK, -+ (rx_rate & MWL_RX_RATE_NSS_MASK) >> -+ MWL_RX_RATE_NSS_SHIFT, -+ (rx_rate & MWL_RX_RATE_BW_MASK) >> -+ MWL_RX_RATE_BW_SHIFT, -+ (rx_rate & MWL_RX_RATE_GI_MASK) >> -+ MWL_RX_RATE_GI_SHIFT, -+ (rx_rate & MWL_RX_RATE_RT_MASK) >> -+ MWL_RX_RATE_RT_SHIFT, -+ status); -+ -+ status->freq = ieee80211_channel_to_frequency(pdesc->channel, -+ status->band); -+ -+ /* check if status has a specific error bit (bit 7) set or indicates -+ * a general decrypt error -+ */ -+ if ((pdesc->status == GENERAL_DECRYPT_ERR) || -+ (pdesc->status & DECRYPT_ERR_MASK)) { -+ /* check if status is not equal to 0xFF -+ * the 0xFF check is for backward compatibility -+ */ -+ if (pdesc->status != GENERAL_DECRYPT_ERR) { -+ if (((pdesc->status & (~DECRYPT_ERR_MASK)) & -+ TKIP_DECRYPT_MIC_ERR) && !((pdesc->status & -+ (WEP_DECRYPT_ICV_ERR | TKIP_DECRYPT_ICV_ERR)))) { -+ status->flag |= RX_FLAG_MMIC_ERROR; -+ } -+ } -+ } -+} -+ -+static inline bool pcie_rx_process_mesh_amsdu(struct mwl_priv *priv, -+ struct sk_buff *skb, -+ struct ieee80211_rx_status *status) -+{ -+ struct ieee80211_hdr *wh; -+ struct mwl_sta *sta_info; -+ struct ieee80211_sta *sta; -+ u8 *qc; -+ int wh_len; -+ int len; -+ u8 pad; -+ u8 *data; -+ u16 frame_len; -+ struct sk_buff *newskb; -+ -+ wh = (struct ieee80211_hdr *)skb->data; -+ -+ spin_lock_bh(&priv->sta_lock); -+ list_for_each_entry(sta_info, &priv->sta_list, list) { -+ sta = container_of((void *)sta_info, struct ieee80211_sta, -+ drv_priv[0]); -+ if (ether_addr_equal(sta->addr, wh->addr2)) { -+ if (!sta_info->is_mesh_node) { -+ spin_unlock_bh(&priv->sta_lock); -+ return false; -+ } -+ } -+ } -+ spin_unlock_bh(&priv->sta_lock); -+ -+ qc = ieee80211_get_qos_ctl(wh); -+ *qc &= ~IEEE80211_QOS_CTL_A_MSDU_PRESENT; -+ -+ wh_len = ieee80211_hdrlen(wh->frame_control); -+ len = wh_len; -+ data = skb->data; -+ -+ while (len < skb->len) { -+ frame_len = *(u8 *)(data + len + ETH_HLEN - 1) | -+ (*(u8 *)(data + len + ETH_HLEN - 2) << 8); -+ -+ if ((len + ETH_HLEN + frame_len) > skb->len) -+ break; -+ -+ newskb = dev_alloc_skb(wh_len + frame_len); -+ if (!newskb) -+ break; -+ -+ ether_addr_copy(wh->addr3, data + len); -+ ether_addr_copy(wh->addr4, data + len + ETH_ALEN); -+ memcpy(newskb->data, wh, wh_len); -+ memcpy(newskb->data + wh_len, data + len + ETH_HLEN, frame_len); -+ skb_put(newskb, wh_len + frame_len); -+ -+ pad = ((ETH_HLEN + frame_len) % 4) ? -+ (4 - (ETH_HLEN + frame_len) % 4) : 0; -+ len += (ETH_HLEN + frame_len + pad); -+ if (len < skb->len) -+ status->flag |= RX_FLAG_AMSDU_MORE; -+ else -+ status->flag &= ~RX_FLAG_AMSDU_MORE; -+ memcpy(IEEE80211_SKB_RXCB(newskb), status, sizeof(*status)); -+ ieee80211_rx(priv->hw, newskb); -+ } -+ -+ dev_kfree_skb_any(skb); -+ -+ return true; -+} -+ -+static inline int pcie_rx_refill(struct mwl_priv *priv, -+ struct pcie_rx_hndl *rx_hndl) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data *desc; -+ dma_addr_t dma; -+ -+ desc = &pcie_priv->desc_data[0]; -+ -+ rx_hndl->psk_buff = dev_alloc_skb(desc->rx_buf_size); -+ -+ if (!rx_hndl->psk_buff) -+ return -ENOMEM; -+ -+ skb_reserve(rx_hndl->psk_buff, PCIE_MIN_BYTES_HEADROOM); -+ -+ rx_hndl->pdesc->status = EAGLE_RXD_STATUS_OK; -+ rx_hndl->pdesc->qos_ctrl = 0x0000; -+ rx_hndl->pdesc->channel = 0x00; -+ rx_hndl->pdesc->rssi = 0x00; -+ rx_hndl->pdesc->pkt_len = cpu_to_le16(desc->rx_buf_size); -+ -+ dma = pci_map_single(pcie_priv->pdev, -+ rx_hndl->psk_buff->data, -+ desc->rx_buf_size, -+ PCI_DMA_FROMDEVICE); -+ if (pci_dma_mapping_error(pcie_priv->pdev, dma)) { -+ dev_kfree_skb_any(rx_hndl->psk_buff); -+ wiphy_err(priv->hw->wiphy, -+ "failed to map pci memory!\n"); -+ return -ENOMEM; -+ } -+ -+ rx_hndl->pdesc->pphys_buff_data = cpu_to_le32(dma); -+ -+ return 0; -+} -+ -+int pcie_rx_init(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ int rc; -+ -+ rc = pcie_rx_ring_alloc(priv); -+ if (rc) { -+ wiphy_err(hw->wiphy, "allocating RX ring failed\n"); -+ return rc; -+ } -+ -+ rc = pcie_rx_ring_init(priv); -+ if (rc) { -+ pcie_rx_ring_free(priv); -+ wiphy_err(hw->wiphy, -+ "initializing RX ring failed\n"); -+ return rc; -+ } -+ -+ return 0; -+} -+ -+void pcie_rx_deinit(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ pcie_rx_ring_cleanup(priv); -+ pcie_rx_ring_free(priv); -+} -+ -+void pcie_rx_recv(unsigned long data) -+{ -+ struct ieee80211_hw *hw = (struct ieee80211_hw *)data; -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data *desc; -+ struct pcie_rx_hndl *curr_hndl; -+ int work_done = 0; -+ struct sk_buff *prx_skb = NULL; -+ int pkt_len; -+ struct ieee80211_rx_status *status; -+ struct mwl_vif *mwl_vif = NULL; -+ struct ieee80211_hdr *wh; -+ -+ desc = &pcie_priv->desc_data[0]; -+ curr_hndl = desc->pnext_rx_hndl; -+ -+ if (!curr_hndl) { -+ pcie_mask_int(pcie_priv, MACREG_A2HRIC_BIT_RX_RDY, true); -+ pcie_priv->is_rx_schedule = false; -+ wiphy_warn(hw->wiphy, "busy or no receiving packets\n"); -+ return; -+ } -+ -+ while ((curr_hndl->pdesc->rx_control == EAGLE_RXD_CTRL_DMA_OWN) && -+ (work_done < pcie_priv->recv_limit)) { -+ prx_skb = curr_hndl->psk_buff; -+ if (!prx_skb) -+ goto out; -+ pci_unmap_single(pcie_priv->pdev, -+ le32_to_cpu(curr_hndl->pdesc->pphys_buff_data), -+ desc->rx_buf_size, -+ PCI_DMA_FROMDEVICE); -+ pkt_len = le16_to_cpu(curr_hndl->pdesc->pkt_len); -+ -+ if (skb_tailroom(prx_skb) < pkt_len) { -+ dev_kfree_skb_any(prx_skb); -+ goto out; -+ } -+ -+ if (curr_hndl->pdesc->channel != -+ hw->conf.chandef.chan->hw_value) { -+ dev_kfree_skb_any(prx_skb); -+ goto out; -+ } -+ -+ status = IEEE80211_SKB_RXCB(prx_skb); -+ pcie_rx_status(priv, curr_hndl->pdesc, status); -+ -+ if (priv->chip_type == MWL8997) { -+ priv->noise = (s8)curr_hndl->pdesc->noise_floor; -+ if (priv->noise > 0) -+ priv->noise = -priv->noise; -+ } else -+ priv->noise = -curr_hndl->pdesc->noise_floor; -+ -+ wh = &((struct pcie_dma_data *)prx_skb->data)->wh; -+ -+ if (ieee80211_has_protected(wh->frame_control)) { -+ /* Check if hw crypto has been enabled for -+ * this bss. If yes, set the status flags -+ * accordingly -+ */ -+ if (ieee80211_has_tods(wh->frame_control)) { -+ mwl_vif = utils_find_vif_bss(priv, wh->addr1); -+ if (!mwl_vif && -+ ieee80211_has_a4(wh->frame_control)) -+ mwl_vif = -+ utils_find_vif_bss(priv, -+ wh->addr2); -+ } else { -+ mwl_vif = utils_find_vif_bss(priv, wh->addr2); -+ } -+ -+ if ((mwl_vif && mwl_vif->is_hw_crypto_enabled) || -+ is_multicast_ether_addr(wh->addr1) || -+ (ieee80211_is_mgmt(wh->frame_control) && -+ !is_multicast_ether_addr(wh->addr1))) { -+ /* When MMIC ERROR is encountered -+ * by the firmware, payload is -+ * dropped and only 32 bytes of -+ * mwlwifi Firmware header is sent -+ * to the host. -+ * -+ * We need to add four bytes of -+ * key information. In it -+ * MAC80211 expects keyidx set to -+ * 0 for triggering Counter -+ * Measure of MMIC failure. -+ */ -+ if (status->flag & RX_FLAG_MMIC_ERROR) { -+ struct pcie_dma_data *dma_data; -+ -+ dma_data = (struct pcie_dma_data *) -+ prx_skb->data; -+ memset((void *)&dma_data->data, 0, 4); -+ pkt_len += 4; -+ } -+ -+ if (!ieee80211_is_auth(wh->frame_control)) { -+ if (priv->chip_type != MWL8997) -+ status->flag |= -+ RX_FLAG_IV_STRIPPED | -+ RX_FLAG_DECRYPTED | -+ RX_FLAG_MMIC_STRIPPED; -+ else -+ status->flag |= -+ RX_FLAG_DECRYPTED | -+ RX_FLAG_MMIC_STRIPPED; -+ } -+ } -+ } -+ -+ skb_put(prx_skb, pkt_len); -+ pcie_rx_remove_dma_header(prx_skb, curr_hndl->pdesc->qos_ctrl); -+ -+ wh = (struct ieee80211_hdr *)prx_skb->data; -+ -+ if (ieee80211_is_data_qos(wh->frame_control)) { -+ const u8 eapol[] = {0x88, 0x8e}; -+ u8 *qc = ieee80211_get_qos_ctl(wh); -+ u8 *data; -+ -+ data = prx_skb->data + -+ ieee80211_hdrlen(wh->frame_control) + 6; -+ -+ if (!memcmp(data, eapol, sizeof(eapol))) -+ *qc |= 7; -+ } -+ -+ if (ieee80211_is_data_qos(wh->frame_control) && -+ ieee80211_has_a4(wh->frame_control)) { -+ u8 *qc = ieee80211_get_qos_ctl(wh); -+ -+ if (*qc & IEEE80211_QOS_CTL_A_MSDU_PRESENT) -+ if (pcie_rx_process_mesh_amsdu(priv, prx_skb, -+ status)) -+ goto out; -+ } -+ -+ if (ieee80211_is_probe_req(wh->frame_control) && -+ priv->dump_probe) -+ wiphy_info(hw->wiphy, "Probe Req: %pM\n", wh->addr2); -+ -+ ieee80211_rx(hw, prx_skb); -+out: -+ pcie_rx_refill(priv, curr_hndl); -+ curr_hndl->pdesc->rx_control = EAGLE_RXD_CTRL_DRIVER_OWN; -+ curr_hndl->pdesc->qos_ctrl = 0; -+ curr_hndl = curr_hndl->pnext; -+ work_done++; -+ } -+ -+ desc->pnext_rx_hndl = curr_hndl; -+ pcie_mask_int(pcie_priv, MACREG_A2HRIC_BIT_RX_RDY, true); -+ pcie_priv->is_rx_schedule = false; -+} -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx.h b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx.h -new file mode 100644 -index 000000000000..d2b580fceb0a ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx.h -@@ -0,0 +1,25 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines receive related functions. */ -+ -+#ifndef _RX_H_ -+#define _RX_H_ -+ -+int pcie_rx_init(struct ieee80211_hw *hw); -+void pcie_rx_deinit(struct ieee80211_hw *hw); -+void pcie_rx_recv(unsigned long data); -+ -+#endif /* _RX_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx_ndp.c b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx_ndp.c -new file mode 100644 -index 000000000000..d1ede588b4c1 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx_ndp.c -@@ -0,0 +1,612 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file implements receive related functions for new data -+ * path. -+ */ -+ -+#include -+#include -+ -+#include "sysadpt.h" -+#include "core.h" -+#include "utils.h" -+#include "hif/pcie/dev.h" -+#include "hif/pcie/rx_ndp.h" -+ -+#define MAX_NUM_RX_RING_BYTES (MAX_NUM_RX_DESC * \ -+ sizeof(struct pcie_rx_desc_ndp)) -+#define MAX_NUM_RX_RING_DONE_BYTES (MAX_NUM_RX_DESC * \ -+ sizeof(struct rx_ring_done)) -+ -+static int pcie_rx_ring_alloc_ndp(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data_ndp *desc = &pcie_priv->desc_data_ndp; -+ -+ desc->prx_ring = (struct pcie_rx_desc_ndp *) -+ dma_alloc_coherent(priv->dev, -+ MAX_NUM_RX_RING_BYTES, -+ &desc->pphys_rx_ring, -+ GFP_KERNEL); -+ if (!desc->prx_ring) -+ goto err_no_mem; -+ memset(desc->prx_ring, 0x00, MAX_NUM_RX_RING_BYTES); -+ -+ desc->prx_ring_done = (struct rx_ring_done *) -+ dma_alloc_coherent(priv->dev, -+ MAX_NUM_RX_RING_DONE_BYTES, -+ &desc->pphys_rx_ring_done, -+ GFP_KERNEL); -+ if (!desc->prx_ring_done) -+ goto err_no_mem; -+ memset(desc->prx_ring_done, 0x00, MAX_NUM_RX_RING_DONE_BYTES); -+ return 0; -+ -+err_no_mem: -+ -+ wiphy_err(priv->hw->wiphy, "cannot alloc mem\n"); -+ return -ENOMEM; -+} -+ -+static int pcie_rx_ring_init_ndp(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data_ndp *desc = &pcie_priv->desc_data_ndp; -+ int i; -+ struct sk_buff *psk_buff; -+ dma_addr_t dma; -+ -+ skb_queue_head_init(&pcie_priv->rx_skb_trace); -+ if (desc->prx_ring) { -+ desc->rx_buf_size = MAX_AGGR_SIZE; -+ -+ for (i = 0; i < MAX_NUM_RX_DESC; i++) { -+ psk_buff = __alloc_skb(desc->rx_buf_size + NET_SKB_PAD, -+ GFP_ATOMIC, SKB_ALLOC_RX, -+ NUMA_NO_NODE); -+ skb_reserve(psk_buff, NET_SKB_PAD); -+ if (!psk_buff) { -+ wiphy_err(priv->hw->wiphy, -+ "rxdesc %i: no skbuff available\n", -+ i); -+ return -ENOMEM; -+ } -+ skb_reserve(psk_buff, MIN_BYTES_RX_HEADROOM); -+ -+ dma = pci_map_single(pcie_priv->pdev, -+ psk_buff->data, -+ desc->rx_buf_size, -+ PCI_DMA_FROMDEVICE); -+ if (pci_dma_mapping_error(pcie_priv->pdev, dma)) { -+ wiphy_err(priv->hw->wiphy, -+ "failed to map pci memory!\n"); -+ return -ENOMEM; -+ } -+ -+ desc->rx_vbuflist[i] = psk_buff; -+ desc->prx_ring[i].user = cpu_to_le32(i); -+ desc->prx_ring[i].data = cpu_to_le32(dma); -+ *((u32 *)&psk_buff->cb[16]) = 0xdeadbeef; -+ skb_queue_tail(&pcie_priv->rx_skb_trace, psk_buff); -+ } -+ -+ writel(1023, pcie_priv->iobase1 + MACREG_REG_RXDESCHEAD); -+ return 0; -+ } -+ -+ wiphy_err(priv->hw->wiphy, "no valid RX mem\n"); -+ return -ENOMEM; -+} -+ -+static void pcie_rx_ring_cleanup_ndp(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data_ndp *desc = &pcie_priv->desc_data_ndp; -+ int i; -+ -+ if (desc->prx_ring) { -+ for (i = 0; i < MAX_NUM_RX_DESC; i++) { -+ if (desc->rx_vbuflist[i]) { -+ pci_unmap_single(pcie_priv->pdev, -+ le32_to_cpu( -+ desc->prx_ring[i].data), -+ desc->rx_buf_size, -+ PCI_DMA_FROMDEVICE); -+ desc->rx_vbuflist[i] = NULL; -+ } -+ } -+ skb_queue_purge(&pcie_priv->rx_skb_trace); -+ } -+} -+ -+static void pcie_rx_ring_free_ndp(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data_ndp *desc = &pcie_priv->desc_data_ndp; -+ -+ if (desc->prx_ring) { -+ pcie_rx_ring_cleanup_ndp(priv); -+ dma_free_coherent(priv->dev, -+ MAX_NUM_RX_RING_BYTES, -+ desc->prx_ring, -+ desc->pphys_rx_ring); -+ desc->prx_ring = NULL; -+ } -+ -+ if (desc->prx_ring_done) { -+ dma_free_coherent(priv->dev, -+ MAX_NUM_RX_RING_DONE_BYTES, -+ desc->prx_ring_done, -+ desc->pphys_rx_ring_done); -+ desc->prx_ring_done = NULL; -+ } -+} -+ -+static inline void pcie_rx_update_ndp_cnts(struct mwl_priv *priv, u32 ctrl) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ -+ switch (ctrl) { -+ case RXRING_CASE_DROP: -+ pcie_priv->rx_cnts.drop_cnt++; -+ break; -+ case RXRING_CASE_FAST_BAD_AMSDU: -+ pcie_priv->rx_cnts.fast_bad_amsdu_cnt++; -+ break; -+ case RXRING_CASE_FAST_DATA: -+ pcie_priv->rx_cnts.fast_data_cnt++; -+ break; -+ case RXRING_CASE_SLOW_BAD_MIC: -+ pcie_priv->rx_cnts.slow_bad_mic_cnt++; -+ break; -+ case RXRING_CASE_SLOW_BAD_PN: -+ pcie_priv->rx_cnts.slow_bad_pn_cnt++; -+ break; -+ case RXRING_CASE_SLOW_BAD_STA: -+ pcie_priv->rx_cnts.slow_bad_sta_cnt++; -+ break; -+ case RXRING_CASE_SLOW_MCAST: -+ pcie_priv->rx_cnts.slow_mcast_cnt++; -+ break; -+ case RXRING_CASE_SLOW_MGMT: -+ pcie_priv->rx_cnts.slow_mgmt_cnt++; -+ break; -+ case RXRING_CASE_SLOW_NOQUEUE: -+ pcie_priv->rx_cnts.slow_noqueue_cnt++; -+ break; -+ case RXRING_CASE_SLOW_NORUN: -+ pcie_priv->rx_cnts.slow_norun_cnt++; -+ break; -+ case RXRING_CASE_SLOW_PROMISC: -+ pcie_priv->rx_cnts.slow_promisc_cnt++; -+ break; -+ } -+} -+ -+static void pcie_rx_status_ndp(struct mwl_priv *priv, -+ struct mwl_sta *sta_info, -+ struct ieee80211_rx_status *status) -+{ -+ memset(status, 0, sizeof(*status)); -+ pcie_rx_prepare_status(priv, -+ sta_info->rx_format, -+ sta_info->rx_nss, -+ sta_info->rx_bw, -+ sta_info->rx_gi, -+ sta_info->rx_rate_mcs, -+ status); -+ status->signal = -sta_info->rx_signal; -+ status->band = priv->hw->conf.chandef.chan->band; -+ status->freq = ieee80211_channel_to_frequency( -+ priv->hw->conf.chandef.chan->hw_value, status->band); -+} -+ -+static inline void pcie_rx_process_fast_data(struct mwl_priv *priv, -+ struct sk_buff *skb, -+ u16 stnid) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct ieee80211_sta *sta; -+ struct mwl_sta *sta_info; -+ struct mwl_vif *mwl_vif; -+ struct ieee80211_hdr hdr; -+ u16 hdrlen, ethertype; -+ __le16 fc; -+ struct ieee80211_rx_status *status; -+ -+ if (stnid == RXRING_CTRL_STA_FROMDS) -+ stnid = 0; -+ -+ if (stnid > SYSADPT_MAX_STA_SC4) -+ goto drop_packet; -+ -+ sta = pcie_priv->sta_link[stnid]; -+ if (!sta) -+ goto drop_packet; -+ -+ sta_info = mwl_dev_get_sta(sta); -+ mwl_vif = sta_info->mwl_vif; -+ if (!mwl_vif) -+ goto drop_packet; -+ -+ ethertype = (skb->data[20] << 8) | skb->data[21]; -+ fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_DATA); -+ -+ memset(&hdr, 0, sizeof(hdr)); -+ switch (mwl_vif->type) { -+ case NL80211_IFTYPE_AP: -+ if (sta_info->wds) { -+ fc |= (cpu_to_le16(IEEE80211_FCTL_TODS) | -+ cpu_to_le16(IEEE80211_FCTL_FROMDS)); -+ /* RA TA DA SA */ -+ ether_addr_copy(hdr.addr1, mwl_vif->bssid); -+ ether_addr_copy(hdr.addr2, sta->addr); -+ ether_addr_copy(hdr.addr3, skb->data); -+ ether_addr_copy(hdr.addr4, skb->data + ETH_ALEN); -+ hdrlen = 30; -+ } else { -+ fc |= cpu_to_le16(IEEE80211_FCTL_TODS); -+ /* BSSID SA DA */ -+ ether_addr_copy(hdr.addr1, mwl_vif->bssid); -+ ether_addr_copy(hdr.addr2, skb->data + ETH_ALEN); -+ ether_addr_copy(hdr.addr3, skb->data); -+ hdrlen = 24; -+ } -+ break; -+ case NL80211_IFTYPE_STATION: -+ if (sta_info->wds) { -+ fc |= (cpu_to_le16(IEEE80211_FCTL_TODS) | -+ cpu_to_le16(IEEE80211_FCTL_FROMDS)); -+ /* RA TA DA SA */ -+ ether_addr_copy(hdr.addr1, mwl_vif->sta_mac); -+ ether_addr_copy(hdr.addr2, mwl_vif->bssid); -+ ether_addr_copy(hdr.addr3, skb->data); -+ ether_addr_copy(hdr.addr4, skb->data + ETH_ALEN); -+ hdrlen = 30; -+ } else { -+ fc |= cpu_to_le16(IEEE80211_FCTL_FROMDS); -+ /* DA BSSID SA */ -+ ether_addr_copy(hdr.addr1, skb->data); -+ ether_addr_copy(hdr.addr2, mwl_vif->bssid); -+ ether_addr_copy(hdr.addr3, skb->data + ETH_ALEN); -+ hdrlen = 24; -+ } -+ break; -+ default: -+ goto drop_packet; -+ } -+ -+ if (sta->wme) { -+ fc |= cpu_to_le16(IEEE80211_STYPE_QOS_DATA); -+ hdrlen += 2; -+ } -+ -+ status = IEEE80211_SKB_RXCB(skb); -+ pcie_rx_status_ndp(priv, sta_info, status); -+ if (mwl_vif->is_hw_crypto_enabled) { -+ fc |= cpu_to_le16(IEEE80211_FCTL_PROTECTED); -+ status->flag |= RX_FLAG_IV_STRIPPED | -+ RX_FLAG_DECRYPTED | -+ RX_FLAG_MMIC_STRIPPED; -+ } -+ -+ hdr.frame_control = fc; -+ hdr.duration_id = 0; -+ -+ skb_pull(skb, ETH_HLEN); -+ -+ if (ieee80211_is_data_qos(fc)) { -+ __le16 *qos_control; -+ -+ qos_control = (__le16 *)skb_push(skb, 2); -+ memcpy(skb_push(skb, hdrlen - 2), &hdr, hdrlen - 2); -+ if (ethertype == ETH_P_PAE) -+ *qos_control = cpu_to_le16( -+ IEEE80211_QOS_CTL_ACK_POLICY_NOACK | 7); -+ else -+ *qos_control = cpu_to_le16( -+ IEEE80211_QOS_CTL_ACK_POLICY_NOACK); -+ } else -+ memcpy(skb_push(skb, hdrlen), &hdr, hdrlen); -+ -+ status->flag |= RX_FLAG_DUP_VALIDATED; -+ ieee80211_rx(priv->hw, skb); -+ -+ return; -+drop_packet: -+ -+ dev_kfree_skb_any(skb); -+} -+ -+static inline void pcie_rx_process_slow_data(struct mwl_priv *priv, -+ struct sk_buff *skb, -+ bool bad_mic, u8 signal) -+{ -+ struct ieee80211_rx_status *status; -+ struct ieee80211_hdr *wh; -+ struct mwl_vif *mwl_vif = NULL; -+ -+ pcie_rx_remove_dma_header(skb, 0); -+ status = IEEE80211_SKB_RXCB(skb); -+ memset(status, 0, sizeof(*status)); -+ status->signal = -signal; -+ status->band = priv->hw->conf.chandef.chan->band; -+ status->freq = ieee80211_channel_to_frequency( -+ priv->hw->conf.chandef.chan->hw_value, status->band); -+ -+ if (bad_mic) -+ status->flag |= RX_FLAG_MMIC_ERROR; -+ else { -+ wh = (struct ieee80211_hdr *)skb->data; -+ -+ if (ieee80211_has_protected(wh->frame_control)) { -+ if (ieee80211_has_tods(wh->frame_control)) { -+ mwl_vif = utils_find_vif_bss(priv, wh->addr1); -+ if (!mwl_vif && -+ ieee80211_has_a4(wh->frame_control)) -+ mwl_vif = -+ utils_find_vif_bss(priv, -+ wh->addr2); -+ } else { -+ mwl_vif = utils_find_vif_bss(priv, wh->addr2); -+ } -+ -+ if ((mwl_vif && mwl_vif->is_hw_crypto_enabled) || -+ is_multicast_ether_addr(wh->addr1) || -+ (ieee80211_is_mgmt(wh->frame_control) && -+ !is_multicast_ether_addr(wh->addr1))) { -+ if (!ieee80211_is_auth(wh->frame_control)) -+ status->flag |= RX_FLAG_IV_STRIPPED | -+ RX_FLAG_DECRYPTED | -+ RX_FLAG_MMIC_STRIPPED; -+ } -+ } -+ -+ if (ieee80211_has_a4(wh->frame_control) && !priv->wds_check) { -+ ether_addr_copy(priv->wds_check_sta, wh->addr2); -+ ieee80211_queue_work(priv->hw, &priv->wds_check_handle); -+ priv->wds_check = true; -+ } -+ } -+ -+ status->flag |= RX_FLAG_DUP_VALIDATED; -+ ieee80211_rx(priv->hw, skb); -+} -+ -+static inline int pcie_rx_refill_ndp(struct mwl_priv *priv, u32 buf_idx) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data_ndp *desc = &pcie_priv->desc_data_ndp; -+ struct sk_buff *psk_buff; -+ dma_addr_t dma; -+ -+ psk_buff = __alloc_skb(desc->rx_buf_size + NET_SKB_PAD, GFP_ATOMIC, -+ SKB_ALLOC_RX, NUMA_NO_NODE); -+ skb_reserve(psk_buff, NET_SKB_PAD); -+ if (!psk_buff) -+ return -ENOMEM; -+ skb_reserve(psk_buff, MIN_BYTES_RX_HEADROOM); -+ -+ dma = pci_map_single(pcie_priv->pdev, -+ psk_buff->data, -+ desc->rx_buf_size, -+ PCI_DMA_FROMDEVICE); -+ if (pci_dma_mapping_error(pcie_priv->pdev, dma)) { -+ wiphy_err(priv->hw->wiphy, -+ "refill: failed to map pci memory!\n"); -+ return -ENOMEM; -+ } -+ -+ desc->rx_vbuflist[buf_idx] = psk_buff; -+ desc->prx_ring[buf_idx].data = cpu_to_le32(dma); -+ *((u32 *)&psk_buff->cb[16]) = 0xdeadbeef; -+ skb_queue_tail(&pcie_priv->rx_skb_trace, psk_buff); -+ -+ return 0; -+} -+ -+int pcie_rx_init_ndp(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ int rc; -+ -+ rc = pcie_rx_ring_alloc_ndp(priv); -+ if (rc) { -+ pcie_rx_ring_free_ndp(priv); -+ wiphy_err(hw->wiphy, "allocating RX ring failed\n"); -+ return rc; -+ } -+ -+ rc = pcie_rx_ring_init_ndp(priv); -+ if (rc) { -+ pcie_rx_ring_free_ndp(priv); -+ wiphy_err(hw->wiphy, -+ "initializing RX ring failed\n"); -+ return rc; -+ } -+ -+ return 0; -+} -+ -+void pcie_rx_deinit_ndp(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ pcie_rx_ring_cleanup_ndp(priv); -+ pcie_rx_ring_free_ndp(priv); -+} -+ -+void pcie_rx_recv_ndp(unsigned long data) -+{ -+ struct ieee80211_hw *hw = (struct ieee80211_hw *)data; -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data_ndp *desc = &pcie_priv->desc_data_ndp; -+ struct rx_ring_done *prx_ring_done; -+ struct pcie_rx_desc_ndp *prx_desc; -+ u32 rx_done_head; -+ u32 rx_done_tail; -+ u32 rx_desc_head; -+ struct sk_buff *psk_buff; -+ u32 buf_idx; -+ u32 rx_cnt; -+ u32 ctrl, ctrl_case; -+ bool bad_mic; -+ u16 stnid; -+ u16 pktlen; -+ struct rx_info *rx_info; -+ struct pcie_dma_data *dma_data; -+ u8 signal; -+ -+ rx_done_head = readl(pcie_priv->iobase1 + MACREG_REG_RXDONEHEAD); -+ rx_done_tail = readl(pcie_priv->iobase1 + MACREG_REG_RXDONETAIL); -+ rx_desc_head = readl(pcie_priv->iobase1 + MACREG_REG_RXDESCHEAD); -+ rx_cnt = 0; -+ -+ while ((rx_done_tail != rx_done_head) && -+ (rx_cnt < pcie_priv->recv_limit)) { -+recheck: -+ prx_ring_done = &desc->prx_ring_done[rx_done_tail]; -+ wmb(); /*Data Memory Barrier*/ -+ if (le32_to_cpu(prx_ring_done->user) == 0xdeadbeef) { -+ pcie_priv->recheck_rxringdone++; -+ udelay(1); -+ goto recheck; -+ } -+ buf_idx = le32_to_cpu(prx_ring_done->user) & 0x3fff; -+ prx_ring_done->user = cpu_to_le32(0xdeadbeef); -+ rx_done_tail++; -+ prx_desc = &desc->prx_ring[buf_idx]; -+ if (!prx_desc->data) -+ wiphy_err(hw->wiphy, "RX desc data is NULL\n"); -+ psk_buff = desc->rx_vbuflist[buf_idx]; -+ if (!psk_buff) { -+ wiphy_err(hw->wiphy, "RX socket buffer is NULL\n"); -+ goto out; -+ } -+ if (*((u32 *)&psk_buff->cb[16]) != 0xdeadbeef) { -+ pcie_priv->signature_err++; -+ break; -+ } -+ if (psk_buff->next && psk_buff->prev) { -+ skb_unlink(psk_buff, &pcie_priv->rx_skb_trace); -+ *((u32 *)&psk_buff->cb[16]) = 0xbeefdead; -+ } else { -+ pcie_priv->rx_skb_unlink_err++; -+ break; -+ } -+ -+ pci_unmap_single(pcie_priv->pdev, -+ le32_to_cpu(prx_desc->data), -+ desc->rx_buf_size, -+ PCI_DMA_FROMDEVICE); -+ -+ bad_mic = false; -+ ctrl = le32_to_cpu(prx_ring_done->ctrl); -+ ctrl_case = ctrl & RXRING_CTRL_CASE_MASK; -+ stnid = (ctrl >> RXRING_CTRL_STA_SHIFT) & RXRING_CTRL_STA_MASK; -+ pcie_rx_update_ndp_cnts(priv, ctrl_case); -+ -+ switch (ctrl_case) { -+ case RXRING_CASE_FAST_DATA: -+ if (stnid == RXRING_CTRL_STA_UNKNOWN) { -+ dev_kfree_skb_any(psk_buff); -+ break; -+ } -+ pktlen = psk_buff->data[12] << 8 | psk_buff->data[13]; -+ pktlen += ETH_HLEN; -+ -+ if (skb_tailroom(psk_buff) >= pktlen) { -+ skb_put(psk_buff, pktlen); -+ pcie_rx_process_fast_data(priv, psk_buff, -+ stnid); -+ } else { -+ wiphy_err(hw->wiphy, -+ "fast: space %d(%d) is not enough\n", -+ skb_tailroom(psk_buff), pktlen); -+ dev_kfree_skb_any(psk_buff); -+ } -+ break; -+ case RXRING_CASE_FAST_BAD_AMSDU: -+ case RXRING_CASE_SLOW_BAD_STA: -+ case RXRING_CASE_SLOW_DEL_DONE: -+ case RXRING_CASE_DROP: -+ case RXRING_CASE_SLOW_BAD_PN: -+ if (ctrl_case == RXRING_CASE_SLOW_DEL_DONE) { -+ wiphy_debug(hw->wiphy, -+ "staid %d deleted\n", -+ stnid); -+ utils_free_stnid(priv, stnid); -+ } -+ dev_kfree_skb_any(psk_buff); -+ break; -+ case RXRING_CASE_SLOW_BAD_MIC: -+ bad_mic = true; -+ case RXRING_CASE_SLOW_NOQUEUE: -+ case RXRING_CASE_SLOW_NORUN: -+ case RXRING_CASE_SLOW_MGMT: -+ case RXRING_CASE_SLOW_MCAST: -+ case RXRING_CASE_SLOW_PROMISC: -+ rx_info = (struct rx_info *)psk_buff->data; -+ dma_data = (struct pcie_dma_data *)&rx_info->hdr[0]; -+ pktlen = le16_to_cpu(dma_data->fwlen); -+ pktlen += sizeof(*rx_info); -+ pktlen += sizeof(struct pcie_dma_data); -+ if (bad_mic) { -+ memset((void *)&dma_data->data, 0, 4); -+ pktlen += 4; -+ } -+ if (skb_tailroom(psk_buff) >= pktlen) { -+ skb_put(psk_buff, pktlen); -+ skb_pull(psk_buff, sizeof(*rx_info)); -+ signal = ((le32_to_cpu(rx_info->rssi_x) >> -+ RXINFO_RSSI_X_SHIFT) & -+ RXINFO_RSSI_X_MASK); -+ pcie_rx_process_slow_data(priv, psk_buff, -+ bad_mic, signal); -+ } else { -+ wiphy_err(hw->wiphy, -+ "slow: space %d(%d) is not enough\n", -+ skb_tailroom(psk_buff), pktlen); -+ dev_kfree_skb_any(psk_buff); -+ } -+ break; -+ default: -+ wiphy_err(hw->wiphy, "unknown control case: %d\n", -+ ctrl_case); -+ dev_kfree_skb_any(psk_buff); -+ break; -+ } -+out: -+ pcie_rx_refill_ndp(priv, buf_idx); -+ -+ if (rx_done_tail >= MAX_RX_RING_DONE_SIZE) -+ rx_done_tail = 0; -+ -+ rx_done_head = -+ readl(pcie_priv->iobase1 + MACREG_REG_RXDONEHEAD); -+ rx_cnt++; -+ } -+ -+ rx_desc_head += rx_cnt; -+ if (rx_desc_head >= MAX_RX_RING_SEND_SIZE) -+ rx_desc_head = rx_desc_head - MAX_RX_RING_SEND_SIZE; -+ writel(rx_done_tail, pcie_priv->iobase1 + MACREG_REG_RXDONETAIL); -+ writel(rx_desc_head, pcie_priv->iobase1 + MACREG_REG_RXDESCHEAD); -+ -+ pcie_mask_int(pcie_priv, MACREG_A2HRIC_RX_DONE_HEAD_RDY, true); -+ pcie_priv->is_rx_schedule = false; -+} -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx_ndp.h b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx_ndp.h -new file mode 100644 -index 000000000000..7e83cedf4351 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/rx_ndp.h -@@ -0,0 +1,26 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines receive related functions for new data path. -+ */ -+ -+#ifndef _RX_NDP_H_ -+#define _RX_NDP_H_ -+ -+int pcie_rx_init_ndp(struct ieee80211_hw *hw); -+void pcie_rx_deinit_ndp(struct ieee80211_hw *hw); -+void pcie_rx_recv_ndp(unsigned long data); -+ -+#endif /* _RX_NDP_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/pcie/sc4_ddr.h b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/sc4_ddr.h -new file mode 100644 -index 000000000000..2da0257accba ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/sc4_ddr.h -@@ -0,0 +1,965 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+static unsigned char sc4_ddr_init[] = { -+0x05, -+0x00, -+0x00, -+0x00, -+0x00, -+0x00, -+0x00, -+0x00, -+0xa4, -+0x03, -+0x00, -+0x00, -+0x2a, -+0xbe, -+0xad, -+0x7e, -+0x00, -+0x00, -+0x00, -+0xa8, -+0x01, -+0x00, -+0x00, -+0x00, -+0x00, -+0x00, -+0x00, -+0x00, -+0x5c, -+0x48, -+0x5b, -+0x49, -+0x04, -+0x00, -+0x00, -+0x00, -+0x10, -+0xb5, -+0xc0, -+0xf8, -+0x08, -+0x00, -+0x00, -+0x00, -+0xe0, -+0x12, -+0xef, -+0x21, -+0x0c, -+0x00, -+0x00, -+0x00, -+0xc0, -+0xf8, -+0x20, -+0x13, -+0x10, -+0x00, -+0x00, -+0x00, -+0x02, -+0x21, -+0xc0, -+0xf8, -+0x14, -+0x00, -+0x00, -+0x00, -+0x24, -+0x13, -+0x02, -+0x01, -+0x18, -+0x00, -+0x00, -+0x00, -+0x57, -+0x49, -+0x0a, -+0x60, -+0x1c, -+0x00, -+0x00, -+0x00, -+0x00, -+0x21, -+0x56, -+0x4b, -+0x20, -+0x00, -+0x00, -+0x00, -+0xc0, -+0x3b, -+0x19, -+0x60, -+0x24, -+0x00, -+0x00, -+0x00, -+0x54, -+0x4b, -+0x1b, -+0x1d, -+0x28, -+0x00, -+0x00, -+0x00, -+0x1a, -+0x60, -+0x53, -+0x4b, -+0x2c, -+0x00, -+0x00, -+0x00, -+0xbc, -+0x3b, -+0x19, -+0x60, -+0x30, -+0x00, -+0x00, -+0x00, -+0x51, -+0x4b, -+0x08, -+0x33, -+0x34, -+0x00, -+0x00, -+0x00, -+0x1a, -+0x60, -+0x50, -+0x4b, -+0x38, -+0x00, -+0x00, -+0x00, -+0xb8, -+0x3b, -+0x19, -+0x60, -+0x3c, -+0x00, -+0x00, -+0x00, -+0x4e, -+0x4b, -+0x0c, -+0x33, -+0x40, -+0x00, -+0x00, -+0x00, -+0x1a, -+0x60, -+0x4d, -+0x4b, -+0x44, -+0x00, -+0x00, -+0x00, -+0xb4, -+0x3b, -+0x19, -+0x60, -+0x48, -+0x00, -+0x00, -+0x00, -+0x4b, -+0x4b, -+0x10, -+0x33, -+0x4c, -+0x00, -+0x00, -+0x00, -+0x1a, -+0x60, -+0x4a, -+0x4b, -+0x50, -+0x00, -+0x00, -+0x00, -+0xb0, -+0x3b, -+0x19, -+0x60, -+0x54, -+0x00, -+0x00, -+0x00, -+0x48, -+0x4b, -+0x80, -+0x33, -+0x58, -+0x00, -+0x00, -+0x00, -+0x1a, -+0x60, -+0x47, -+0x4b, -+0x5c, -+0x00, -+0x00, -+0x00, -+0x40, -+0x3b, -+0x19, -+0x60, -+0x60, -+0x00, -+0x00, -+0x00, -+0x47, -+0x4c, -+0x46, -+0x4b, -+0x64, -+0x00, -+0x00, -+0x00, -+0x23, -+0x60, -+0x24, -+0x1d, -+0x68, -+0x00, -+0x00, -+0x00, -+0x23, -+0x60, -+0x24, -+0x1d, -+0x6c, -+0x00, -+0x00, -+0x00, -+0x23, -+0x60, -+0x44, -+0x4b, -+0x70, -+0x00, -+0x00, -+0x00, -+0x40, -+0x33, -+0x19, -+0x60, -+0x74, -+0x00, -+0x00, -+0x00, -+0x1b, -+0x1d, -+0x19, -+0x60, -+0x78, -+0x00, -+0x00, -+0x00, -+0x1b, -+0x1d, -+0x19, -+0x60, -+0x7c, -+0x00, -+0x00, -+0x00, -+0x41, -+0x4b, -+0xc0, -+0xf8, -+0x80, -+0x00, -+0x00, -+0x00, -+0xe0, -+0x31, -+0x41, -+0x4b, -+0x84, -+0x00, -+0x00, -+0x00, -+0xc0, -+0xf8, -+0xf0, -+0x31, -+0x88, -+0x00, -+0x00, -+0x00, -+0x03, -+0x04, -+0xc0, -+0xf8, -+0x8c, -+0x00, -+0x00, -+0x00, -+0xf0, -+0x32, -+0x40, -+0xf2, -+0x90, -+0x00, -+0x00, -+0x00, -+0x55, -+0x13, -+0xc0, -+0xf8, -+0x94, -+0x00, -+0x00, -+0x00, -+0x60, -+0x33, -+0x3d, -+0x4b, -+0x98, -+0x00, -+0x00, -+0x00, -+0xc0, -+0xf8, -+0x64, -+0x33, -+0x9c, -+0x00, -+0x00, -+0x00, -+0x13, -+0x1d, -+0xc0, -+0xf8, -+0xa0, -+0x00, -+0x00, -+0x00, -+0x68, -+0x33, -+0x3b, -+0x4b, -+0xa4, -+0x00, -+0x00, -+0x00, -+0xc0, -+0xf8, -+0x6c, -+0x33, -+0xa8, -+0x00, -+0x00, -+0x00, -+0x3a, -+0x4b, -+0xc0, -+0xf8, -+0xac, -+0x00, -+0x00, -+0x00, -+0x70, -+0x33, -+0x3a, -+0x4b, -+0xb0, -+0x00, -+0x00, -+0x00, -+0xc0, -+0xf8, -+0x74, -+0x33, -+0xb4, -+0x00, -+0x00, -+0x00, -+0x39, -+0x4b, -+0xc0, -+0xf8, -+0xb8, -+0x00, -+0x00, -+0x00, -+0x78, -+0x33, -+0xc4, -+0x23, -+0xbc, -+0x00, -+0x00, -+0x00, -+0xc0, -+0xf8, -+0x7c, -+0x33, -+0xc0, -+0x00, -+0x00, -+0x00, -+0x37, -+0x4b, -+0xc0, -+0xf8, -+0xc4, -+0x00, -+0x00, -+0x00, -+0x80, -+0x33, -+0x37, -+0x4b, -+0xc8, -+0x00, -+0x00, -+0x00, -+0xc0, -+0xf8, -+0x84, -+0x33, -+0xcc, -+0x00, -+0x00, -+0x00, -+0x36, -+0x4b, -+0xc0, -+0xf8, -+0xd0, -+0x00, -+0x00, -+0x00, -+0x88, -+0x33, -+0x42, -+0xf2, -+0xd4, -+0x00, -+0x00, -+0x00, -+0x44, -+0x23, -+0xc0, -+0xf8, -+0xd8, -+0x00, -+0x00, -+0x00, -+0x8c, -+0x33, -+0xc0, -+0xf8, -+0xdc, -+0x00, -+0x00, -+0x00, -+0x90, -+0x13, -+0x4f, -+0xf4, -+0xe0, -+0x00, -+0x00, -+0x00, -+0x60, -+0x43, -+0xc0, -+0xf8, -+0xe4, -+0x00, -+0x00, -+0x00, -+0xa0, -+0x32, -+0xc0, -+0xf8, -+0xe8, -+0x00, -+0x00, -+0x00, -+0xa4, -+0x22, -+0xc0, -+0xf8, -+0xec, -+0x00, -+0x00, -+0x00, -+0xa8, -+0x12, -+0x44, -+0xf2, -+0xf0, -+0x00, -+0x00, -+0x00, -+0x40, -+0x02, -+0x2e, -+0x4b, -+0xf4, -+0x00, -+0x00, -+0x00, -+0x1a, -+0x60, -+0x1b, -+0x1d, -+0xf8, -+0x00, -+0x00, -+0x00, -+0x2d, -+0x4a, -+0x1a, -+0x60, -+0xfc, -+0x00, -+0x00, -+0x00, -+0x1b, -+0x1d, -+0x2d, -+0x4a, -+0x00, -+0x01, -+0x00, -+0x00, -+0x1a, -+0x60, -+0x1b, -+0x1d, -+0x04, -+0x01, -+0x00, -+0x00, -+0x2c, -+0x4a, -+0x1a, -+0x60, -+0x08, -+0x01, -+0x00, -+0x00, -+0x1b, -+0x1d, -+0x2c, -+0x4a, -+0x0c, -+0x01, -+0x00, -+0x00, -+0x1a, -+0x60, -+0x1b, -+0x1d, -+0x10, -+0x01, -+0x00, -+0x00, -+0x4f, -+0xf4, -+0x60, -+0x12, -+0x14, -+0x01, -+0x00, -+0x00, -+0x1a, -+0x60, -+0x25, -+0x4a, -+0x18, -+0x01, -+0x00, -+0x00, -+0x28, -+0x32, -+0x11, -+0x60, -+0x1c, -+0x01, -+0x00, -+0x00, -+0x23, -+0x4a, -+0x30, -+0x32, -+0x20, -+0x01, -+0x00, -+0x00, -+0x11, -+0x60, -+0x12, -+0x1d, -+0x24, -+0x01, -+0x00, -+0x00, -+0x11, -+0x60, -+0x03, -+0x22, -+0x28, -+0x01, -+0x00, -+0x00, -+0x20, -+0x4b, -+0x38, -+0x33, -+0x2c, -+0x01, -+0x00, -+0x00, -+0x1a, -+0x60, -+0x20, -+0x22, -+0x30, -+0x01, -+0x00, -+0x00, -+0x1b, -+0x1d, -+0x1a, -+0x60, -+0x34, -+0x01, -+0x00, -+0x00, -+0x1b, -+0x1d, -+0x4f, -+0xf0, -+0x38, -+0x01, -+0x00, -+0x00, -+0x04, -+0x22, -+0x1a, -+0x60, -+0x3c, -+0x01, -+0x00, -+0x00, -+0x03, -+0x06, -+0x1b, -+0x4a, -+0x40, -+0x01, -+0x00, -+0x00, -+0x20, -+0x32, -+0x13, -+0x60, -+0x44, -+0x01, -+0x00, -+0x00, -+0x43, -+0x06, -+0x13, -+0x60, -+0x48, -+0x01, -+0x00, -+0x00, -+0x83, -+0x06, -+0x13, -+0x60, -+0x4c, -+0x01, -+0x00, -+0x00, -+0x0c, -+0x4b, -+0x1c, -+0x4a, -+0x50, -+0x01, -+0x00, -+0x00, -+0x70, -+0x33, -+0x1a, -+0x60, -+0x54, -+0x01, -+0x00, -+0x00, -+0x41, -+0x62, -+0x4f, -+0xf4, -+0x58, -+0x01, -+0x00, -+0x00, -+0x7f, -+0x22, -+0x82, -+0x62, -+0x5c, -+0x01, -+0x00, -+0x00, -+0x19, -+0x4a, -+0x82, -+0x66, -+0x60, -+0x01, -+0x00, -+0x00, -+0xc1, -+0x66, -+0x40, -+0xf2, -+0x64, -+0x01, -+0x00, -+0x00, -+0x63, -+0x42, -+0x42, -+0x63, -+0x68, -+0x01, -+0x00, -+0x00, -+0x01, -+0x64, -+0x17, -+0x49, -+0x6c, -+0x01, -+0x00, -+0x00, -+0x01, -+0x60, -+0x10, -+0xbd, -+0x70, -+0x01, -+0x00, -+0x00, -+0x22, -+0x76, -+0x30, -+0x00, -+0x74, -+0x01, -+0x00, -+0x00, -+0x20, -+0x00, -+0x00, -+0xf0, -+0x78, -+0x01, -+0x00, -+0x00, -+0x40, -+0x06, -+0x00, -+0xf0, -+0x7c, -+0x01, -+0x00, -+0x00, -+0x04, -+0x00, -+0x15, -+0x15, -+0x80, -+0x01, -+0x00, -+0x00, -+0x00, -+0x05, -+0x00, -+0xf0, -+0x84, -+0x01, -+0x00, -+0x00, -+0x01, -+0x00, -+0x0d, -+0x00, -+0x88, -+0x01, -+0x00, -+0x00, -+0x32, -+0x05, -+0x00, -+0x04, -+0x8c, -+0x01, -+0x00, -+0x00, -+0xa9, -+0x02, -+0xb8, -+0x00, -+0x90, -+0x01, -+0x00, -+0x00, -+0x00, -+0x01, -+0x40, -+0x00, -+0x94, -+0x01, -+0x00, -+0x00, -+0xeb, -+0x06, -+0x77, -+0x00, -+0x98, -+0x01, -+0x00, -+0x00, -+0x00, -+0x52, -+0x7b, -+0x50, -+0x9c, -+0x01, -+0x00, -+0x00, -+0x0b, -+0x06, -+0x04, -+0x10, -+0xa0, -+0x01, -+0x00, -+0x00, -+0x10, -+0x07, -+0x17, -+0x13, -+0xa4, -+0x01, -+0x00, -+0x00, -+0x07, -+0x74, -+0x70, -+0x00, -+0xa8, -+0x01, -+0x00, -+0x00, -+0x40, -+0x00, -+0x70, -+0x50, -+0xac, -+0x01, -+0x00, -+0x00, -+0x00, -+0x04, -+0x00, -+0xf0, -+0xb0, -+0x01, -+0x00, -+0x00, -+0x79, -+0x07, -+0x70, -+0x17, -+0xb4, -+0x01, -+0x00, -+0x00, -+0x70, -+0x07, -+0xf0, -+0x0f, -+0xb8, -+0x01, -+0x00, -+0x00, -+0x77, -+0xfc, -+0x03, -+0x3f, -+0xbc, -+0x01, -+0x00, -+0x00, -+0x00, -+0x31, -+0x10, -+0x00, -+0xc0, -+0x01, -+0x00, -+0x00, -+0x01, -+0x00, -+0x00, -+0xc0, -+0xc4, -+0x01, -+0x00, -+0x00, -+0x66, -+0x66, -+0x66, -+0x00, -+0xc8, -+0x01, -+0x00, -+0x00, -+0x01, -+0x00, -+0x00, -+0x11, -+0x37, -+0x3e, -+0xfc, -+0xdc, -+}; -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx.c b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx.c -new file mode 100644 -index 000000000000..dd77589ef5a6 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx.c -@@ -0,0 +1,1396 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file implements transmit related functions. */ -+ -+#include -+#include -+ -+#include "sysadpt.h" -+#include "core.h" -+#include "utils.h" -+#include "hif/fwcmd.h" -+#include "hif/pcie/dev.h" -+#include "hif/pcie/tx.h" -+ -+#define MAX_NUM_TX_RING_BYTES (PCIE_MAX_NUM_TX_DESC * \ -+ sizeof(struct pcie_tx_desc)) -+ -+#define MAX_NUM_TX_HNDL_BYTES (PCIE_MAX_NUM_TX_DESC * \ -+ sizeof(struct pcie_tx_hndl)) -+ -+#define TOTAL_HW_QUEUES (SYSADPT_TX_WMM_QUEUES + \ -+ PCIE_AMPDU_QUEUES) -+ -+#define EAGLE_TXD_XMITCTRL_USE_MC_RATE 0x8 /* Use multicast data rate */ -+ -+#define MWL_QOS_ACK_POLICY_MASK 0x0060 -+#define MWL_QOS_ACK_POLICY_NORMAL 0x0000 -+#define MWL_QOS_ACK_POLICY_BLOCKACK 0x0060 -+ -+#define EXT_IV 0x20 -+#define INCREASE_IV(iv16, iv32) \ -+{ \ -+ (iv16)++; \ -+ if ((iv16) == 0) \ -+ (iv32)++; \ -+} -+ -+/* Transmission information to transmit a socket buffer. */ -+struct pcie_tx_ctrl { -+ void *vif; -+ void *sta; -+ void *k_conf; -+ void *amsdu_pkts; -+ u8 tx_priority; -+ u8 type; -+ u16 qos_ctrl; -+ u8 xmit_control; -+}; -+ -+struct ccmp_hdr { -+ __le16 iv16; -+ u8 rsvd; -+ u8 key_id; -+ __le32 iv32; -+} __packed; -+ -+static int pcie_tx_ring_alloc(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data *desc; -+ int num; -+ u8 *mem; -+ -+ desc = &pcie_priv->desc_data[0]; -+ -+ mem = dma_alloc_coherent(priv->dev, -+ MAX_NUM_TX_RING_BYTES * -+ PCIE_NUM_OF_DESC_DATA, -+ &desc->pphys_tx_ring, -+ GFP_KERNEL); -+ -+ if (!mem) { -+ wiphy_err(priv->hw->wiphy, "cannot alloc mem\n"); -+ return -ENOMEM; -+ } -+ -+ for (num = 0; num < PCIE_NUM_OF_DESC_DATA; num++) { -+ desc = &pcie_priv->desc_data[num]; -+ -+ desc->ptx_ring = (struct pcie_tx_desc *) -+ (mem + num * MAX_NUM_TX_RING_BYTES); -+ -+ desc->pphys_tx_ring = (dma_addr_t) -+ ((u32)pcie_priv->desc_data[0].pphys_tx_ring + -+ num * MAX_NUM_TX_RING_BYTES); -+ -+ memset(desc->ptx_ring, 0x00, -+ MAX_NUM_TX_RING_BYTES); -+ } -+ -+ mem = kzalloc(MAX_NUM_TX_HNDL_BYTES * PCIE_NUM_OF_DESC_DATA, -+ GFP_KERNEL); -+ -+ if (!mem) { -+ wiphy_err(priv->hw->wiphy, "cannot alloc mem\n"); -+ dma_free_coherent(priv->dev, -+ MAX_NUM_TX_RING_BYTES * -+ PCIE_NUM_OF_DESC_DATA, -+ pcie_priv->desc_data[0].ptx_ring, -+ pcie_priv->desc_data[0].pphys_tx_ring); -+ return -ENOMEM; -+ } -+ -+ for (num = 0; num < PCIE_NUM_OF_DESC_DATA; num++) { -+ desc = &pcie_priv->desc_data[num]; -+ -+ desc->tx_hndl = (struct pcie_tx_hndl *) -+ (mem + num * MAX_NUM_TX_HNDL_BYTES); -+ } -+ -+ return 0; -+} -+ -+static int pcie_txbd_ring_create(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ int num; -+ u8 *mem; -+ -+ /* driver maintaines the write pointer and firmware maintaines the read -+ * pointer. -+ */ -+ pcie_priv->txbd_wrptr = 0; -+ pcie_priv->txbd_rdptr = 0; -+ -+ /* allocate shared memory for the BD ring and divide the same in to -+ * several descriptors -+ */ -+ pcie_priv->txbd_ring_size = -+ sizeof(struct pcie_data_buf) * PCIE_MAX_TXRX_BD; -+ wiphy_info(priv->hw->wiphy, "TX ring: allocating %d bytes\n", -+ pcie_priv->txbd_ring_size); -+ -+ mem = dma_alloc_coherent(priv->dev, -+ pcie_priv->txbd_ring_size, -+ &pcie_priv->txbd_ring_pbase, -+ GFP_KERNEL); -+ -+ if (!mem) { -+ wiphy_err(priv->hw->wiphy, "cannot alloc mem\n"); -+ return -ENOMEM; -+ } -+ pcie_priv->txbd_ring_vbase = mem; -+ wiphy_info(priv->hw->wiphy, -+ "TX ring: - base: %p, pbase: 0x%x, len: %d\n", -+ pcie_priv->txbd_ring_vbase, -+ pcie_priv->txbd_ring_pbase, -+ pcie_priv->txbd_ring_size); -+ -+ for (num = 0; num < PCIE_MAX_TXRX_BD; num++) { -+ pcie_priv->txbd_ring[num] = -+ (struct pcie_data_buf *)(pcie_priv->txbd_ring_vbase + -+ (sizeof(struct pcie_data_buf) * num)); -+ pcie_priv->txbd_ring[num]->flags = 0; -+ pcie_priv->txbd_ring[num]->offset = 0; -+ pcie_priv->txbd_ring[num]->frag_len = 0; -+ pcie_priv->txbd_ring[num]->len = 0; -+ pcie_priv->txbd_ring[num]->paddr = 0; -+ pcie_priv->tx_buf_list[num] = NULL; -+ } -+ -+ return 0; -+} -+ -+static int pcie_tx_ring_init(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ int num, i; -+ struct pcie_desc_data *desc; -+ -+ for (num = 0; num < PCIE_NUM_OF_DESC_DATA; num++) { -+ skb_queue_head_init(&pcie_priv->txq[num]); -+ pcie_priv->fw_desc_cnt[num] = 0; -+ -+ if (priv->chip_type == MWL8997) -+ continue; -+ -+ desc = &pcie_priv->desc_data[num]; -+ -+ if (desc->ptx_ring) { -+ for (i = 0; i < PCIE_MAX_NUM_TX_DESC; i++) { -+ desc->ptx_ring[i].status = -+ cpu_to_le32(EAGLE_TXD_STATUS_IDLE); -+ desc->ptx_ring[i].pphys_next = -+ cpu_to_le32((u32)desc->pphys_tx_ring + -+ ((i + 1) * -+ sizeof(struct pcie_tx_desc))); -+ desc->tx_hndl[i].pdesc = -+ &desc->ptx_ring[i]; -+ if (i < PCIE_MAX_NUM_TX_DESC - 1) -+ desc->tx_hndl[i].pnext = -+ &desc->tx_hndl[i + 1]; -+ } -+ desc->ptx_ring[PCIE_MAX_NUM_TX_DESC - 1].pphys_next = -+ cpu_to_le32((u32)desc->pphys_tx_ring); -+ desc->tx_hndl[PCIE_MAX_NUM_TX_DESC - 1].pnext = -+ &desc->tx_hndl[0]; -+ -+ desc->pstale_tx_hndl = &desc->tx_hndl[0]; -+ desc->pnext_tx_hndl = &desc->tx_hndl[0]; -+ } else { -+ wiphy_err(priv->hw->wiphy, "no valid TX mem\n"); -+ return -ENOMEM; -+ } -+ } -+ -+ return 0; -+} -+ -+static void pcie_tx_ring_cleanup(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ int cleaned_tx_desc = 0; -+ int num, i; -+ struct pcie_desc_data *desc; -+ -+ for (num = 0; num < PCIE_NUM_OF_DESC_DATA; num++) { -+ skb_queue_purge(&pcie_priv->txq[num]); -+ pcie_priv->fw_desc_cnt[num] = 0; -+ -+ if (priv->chip_type == MWL8997) -+ continue; -+ -+ desc = &pcie_priv->desc_data[num]; -+ -+ if (desc->ptx_ring) { -+ for (i = 0; i < PCIE_MAX_NUM_TX_DESC; i++) { -+ if (!desc->tx_hndl[i].psk_buff) -+ continue; -+ -+ wiphy_debug(priv->hw->wiphy, -+ "unmapped and free'd %i %p %x\n", -+ i, -+ desc->tx_hndl[i].psk_buff->data, -+ le32_to_cpu( -+ desc->ptx_ring[i].pkt_ptr)); -+ pci_unmap_single(pcie_priv->pdev, -+ le32_to_cpu( -+ desc->ptx_ring[i].pkt_ptr), -+ desc->tx_hndl[i].psk_buff->len, -+ PCI_DMA_TODEVICE); -+ dev_kfree_skb_any(desc->tx_hndl[i].psk_buff); -+ desc->ptx_ring[i].status = -+ cpu_to_le32(EAGLE_TXD_STATUS_IDLE); -+ desc->ptx_ring[i].pkt_ptr = 0; -+ desc->ptx_ring[i].pkt_len = 0; -+ desc->tx_hndl[i].psk_buff = NULL; -+ cleaned_tx_desc++; -+ } -+ } -+ } -+ -+ wiphy_info(priv->hw->wiphy, "cleaned %i TX descr\n", cleaned_tx_desc); -+} -+ -+static void pcie_tx_ring_free(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ int num; -+ -+ if (pcie_priv->desc_data[0].ptx_ring) { -+ dma_free_coherent(priv->dev, -+ MAX_NUM_TX_RING_BYTES * -+ PCIE_NUM_OF_DESC_DATA, -+ pcie_priv->desc_data[0].ptx_ring, -+ pcie_priv->desc_data[0].pphys_tx_ring); -+ } -+ -+ for (num = 0; num < PCIE_NUM_OF_DESC_DATA; num++) { -+ if (pcie_priv->desc_data[num].ptx_ring) -+ pcie_priv->desc_data[num].ptx_ring = NULL; -+ pcie_priv->desc_data[num].pstale_tx_hndl = NULL; -+ pcie_priv->desc_data[num].pnext_tx_hndl = NULL; -+ } -+ -+ kfree(pcie_priv->desc_data[0].tx_hndl); -+} -+ -+static void pcie_txbd_ring_delete(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct sk_buff *skb; -+ struct pcie_tx_desc *tx_desc; -+ int num; -+ -+ if (pcie_priv->txbd_ring_vbase) { -+ dma_free_coherent(priv->dev, -+ pcie_priv->txbd_ring_size, -+ pcie_priv->txbd_ring_vbase, -+ pcie_priv->txbd_ring_pbase); -+ } -+ -+ for (num = 0; num < PCIE_MAX_TXRX_BD; num++) { -+ pcie_priv->txbd_ring[num] = NULL; -+ if (pcie_priv->tx_buf_list[num]) { -+ skb = pcie_priv->tx_buf_list[num]; -+ tx_desc = (struct pcie_tx_desc *)skb->data; -+ -+ pci_unmap_single(pcie_priv->pdev, -+ le32_to_cpu(tx_desc->pkt_ptr), -+ skb->len, -+ PCI_DMA_TODEVICE); -+ dev_kfree_skb_any(skb); -+ } -+ pcie_priv->tx_buf_list[num] = NULL; -+ } -+ -+ pcie_priv->txbd_wrptr = 0; -+ pcie_priv->txbd_rdptr = 0; -+ pcie_priv->txbd_ring_size = 0; -+ pcie_priv->txbd_ring_vbase = NULL; -+ pcie_priv->txbd_ring_pbase = 0; -+} -+ -+static inline void pcie_tx_add_ccmp_hdr(u8 *pccmp_hdr, -+ u8 key_id, u16 iv16, u32 iv32) -+{ -+ struct ccmp_hdr *ccmp_h = (struct ccmp_hdr *)pccmp_hdr; -+ -+ ccmp_h->iv16 = cpu_to_le16(iv16); -+ ccmp_h->rsvd = 0; -+ ccmp_h->key_id = EXT_IV | (key_id << 6); -+ ccmp_h->iv32 = cpu_to_le32(iv32); -+} -+ -+static inline bool pcie_tx_available(struct mwl_priv *priv, int desc_num) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_tx_hndl *tx_hndl; -+ -+ if (priv->chip_type == MWL8997) -+ return PCIE_TXBD_NOT_FULL(pcie_priv->txbd_wrptr, -+ pcie_priv->txbd_rdptr); -+ -+ tx_hndl = pcie_priv->desc_data[desc_num].pnext_tx_hndl; -+ -+ if (!tx_hndl->pdesc) -+ return false; -+ -+ if (tx_hndl->pdesc->status != EAGLE_TXD_STATUS_IDLE) { -+ /* Interrupt F/W anyway */ -+ if (tx_hndl->pdesc->status & -+ cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED)) -+ writel(MACREG_H2ARIC_BIT_PPA_READY, -+ pcie_priv->iobase1 + -+ MACREG_REG_H2A_INTERRUPT_EVENTS); -+ return false; -+ } -+ -+ return true; -+} -+ -+static inline void pcie_tx_skb(struct mwl_priv *priv, int desc_num, -+ struct sk_buff *tx_skb) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct ieee80211_tx_info *tx_info; -+ struct pcie_tx_ctrl *tx_ctrl; -+ struct pcie_tx_hndl *tx_hndl = NULL; -+ struct pcie_tx_desc *tx_desc; -+ struct ieee80211_sta *sta; -+ struct ieee80211_vif *vif; -+ struct mwl_vif *mwl_vif; -+ struct ieee80211_key_conf *k_conf; -+ bool ccmp = false; -+ struct pcie_pfu_dma_data *pfu_dma_data; -+ struct pcie_dma_data *dma_data; -+ struct ieee80211_hdr *wh; -+ dma_addr_t dma; -+ -+ if (WARN_ON(!tx_skb)) -+ return; -+ -+ tx_info = IEEE80211_SKB_CB(tx_skb); -+ tx_ctrl = (struct pcie_tx_ctrl *)&tx_info->status; -+ sta = (struct ieee80211_sta *)tx_ctrl->sta; -+ vif = (struct ieee80211_vif *)tx_ctrl->vif; -+ mwl_vif = mwl_dev_get_vif(vif); -+ k_conf = (struct ieee80211_key_conf *)tx_ctrl->k_conf; -+ -+ pcie_tx_encapsulate_frame(priv, tx_skb, k_conf, &ccmp); -+ -+ if (priv->chip_type == MWL8997) { -+ pfu_dma_data = (struct pcie_pfu_dma_data *)tx_skb->data; -+ tx_desc = &pfu_dma_data->tx_desc; -+ dma_data = &pfu_dma_data->dma_data; -+ } else { -+ tx_hndl = pcie_priv->desc_data[desc_num].pnext_tx_hndl; -+ tx_hndl->psk_buff = tx_skb; -+ tx_desc = tx_hndl->pdesc; -+ dma_data = (struct pcie_dma_data *)tx_skb->data; -+ } -+ wh = &dma_data->wh; -+ -+ if (ieee80211_is_probe_resp(wh->frame_control) && -+ priv->dump_probe) -+ wiphy_info(priv->hw->wiphy, -+ "Probe Resp: %pM\n", wh->addr1); -+ -+ if (ieee80211_is_data(wh->frame_control) || -+ (ieee80211_is_mgmt(wh->frame_control) && -+ ieee80211_has_protected(wh->frame_control) && -+ !is_multicast_ether_addr(wh->addr1))) { -+ if (is_multicast_ether_addr(wh->addr1)) { -+ if (ccmp) { -+ pcie_tx_add_ccmp_hdr(dma_data->data, -+ mwl_vif->keyidx, -+ mwl_vif->iv16, -+ mwl_vif->iv32); -+ INCREASE_IV(mwl_vif->iv16, mwl_vif->iv32); -+ } -+ } else { -+ if (ccmp) { -+ if (vif->type == NL80211_IFTYPE_STATION) { -+ pcie_tx_add_ccmp_hdr(dma_data->data, -+ mwl_vif->keyidx, -+ mwl_vif->iv16, -+ mwl_vif->iv32); -+ INCREASE_IV(mwl_vif->iv16, -+ mwl_vif->iv32); -+ } else { -+ struct mwl_sta *sta_info; -+ -+ sta_info = mwl_dev_get_sta(sta); -+ -+ pcie_tx_add_ccmp_hdr(dma_data->data, -+ 0, -+ sta_info->iv16, -+ sta_info->iv32); -+ INCREASE_IV(sta_info->iv16, -+ sta_info->iv32); -+ } -+ } -+ } -+ } -+ -+ if (tx_info->flags & IEEE80211_TX_INTFL_DONT_ENCRYPT) -+ tx_desc->flags |= PCIE_TX_WCB_FLAGS_DONT_ENCRYPT; -+ if (tx_info->flags & IEEE80211_TX_CTL_NO_CCK_RATE) -+ tx_desc->flags |= PCIE_TX_WCB_FLAGS_NO_CCK_RATE; -+ tx_desc->tx_priority = tx_ctrl->tx_priority; -+ tx_desc->qos_ctrl = cpu_to_le16(tx_ctrl->qos_ctrl); -+ tx_desc->pkt_len = cpu_to_le16(tx_skb->len); -+ tx_desc->packet_info = 0; -+ tx_desc->data_rate = 0; -+ tx_desc->type = tx_ctrl->type; -+ tx_desc->xmit_control = tx_ctrl->xmit_control; -+ tx_desc->sap_pkt_info = 0; -+ dma = pci_map_single(pcie_priv->pdev, tx_skb->data, -+ tx_skb->len, PCI_DMA_TODEVICE); -+ if (pci_dma_mapping_error(pcie_priv->pdev, dma)) { -+ dev_kfree_skb_any(tx_skb); -+ wiphy_err(priv->hw->wiphy, -+ "failed to map pci memory!\n"); -+ return; -+ } -+ if (priv->chip_type == MWL8997) -+ tx_desc->pkt_ptr = cpu_to_le32(sizeof(struct pcie_tx_desc)); -+ else -+ tx_desc->pkt_ptr = cpu_to_le32(dma); -+ tx_desc->status = cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED); -+ /* make sure all the memory transactions done by cpu were completed */ -+ wmb(); /*Data Memory Barrier*/ -+ -+ if (priv->chip_type == MWL8997) { -+ u32 wrindx; -+ struct pcie_data_buf *data_buf; -+ const u32 num_tx_buffs = PCIE_MAX_TXRX_BD << PCIE_TX_START_PTR; -+ -+ wrindx = (pcie_priv->txbd_wrptr & PCIE_TXBD_MASK) >> -+ PCIE_TX_START_PTR; -+ pcie_priv->tx_buf_list[wrindx] = tx_skb; -+ data_buf = pcie_priv->txbd_ring[wrindx]; -+ data_buf->paddr = cpu_to_le64(dma); -+ data_buf->len = cpu_to_le16(tx_skb->len); -+ data_buf->flags = cpu_to_le16(PCIE_BD_FLAG_FIRST_DESC | -+ PCIE_BD_FLAG_LAST_DESC); -+ data_buf->frag_len = cpu_to_le16(tx_skb->len); -+ data_buf->offset = 0; -+ pcie_priv->txbd_wrptr += PCIE_BD_FLAG_TX_START_PTR; -+ -+ if ((pcie_priv->txbd_wrptr & PCIE_TXBD_MASK) == num_tx_buffs) -+ pcie_priv->txbd_wrptr = ((pcie_priv->txbd_wrptr & -+ PCIE_BD_FLAG_TX_ROLLOVER_IND) ^ -+ PCIE_BD_FLAG_TX_ROLLOVER_IND); -+ -+ /* Write the TX ring write pointer in to REG_TXBD_WRPTR */ -+ writel(pcie_priv->txbd_wrptr, -+ pcie_priv->iobase1 + REG_TXBD_WRPTR); -+ } else { -+ writel(MACREG_H2ARIC_BIT_PPA_READY, -+ pcie_priv->iobase1 + MACREG_REG_H2A_INTERRUPT_EVENTS); -+ pcie_priv->desc_data[desc_num].pnext_tx_hndl = tx_hndl->pnext; -+ pcie_priv->fw_desc_cnt[desc_num]++; -+ } -+} -+ -+static inline -+struct sk_buff *pcie_tx_do_amsdu(struct mwl_priv *priv, -+ int desc_num, -+ struct sk_buff *tx_skb, -+ struct ieee80211_tx_info *tx_info) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct ieee80211_sta *sta; -+ struct mwl_sta *sta_info; -+ struct pcie_tx_ctrl *tx_ctrl = (struct pcie_tx_ctrl *)&tx_info->status; -+ struct ieee80211_tx_info *amsdu_info; -+ struct sk_buff_head *amsdu_pkts; -+ struct mwl_amsdu_frag *amsdu; -+ int amsdu_allow_size; -+ struct ieee80211_hdr *wh; -+ int wh_len; -+ u16 len; -+ u8 *data; -+ -+ sta = (struct ieee80211_sta *)tx_ctrl->sta; -+ sta_info = mwl_dev_get_sta(sta); -+ -+ if (!sta_info->is_amsdu_allowed) -+ return tx_skb; -+ -+ wh = (struct ieee80211_hdr *)tx_skb->data; -+ if (sta_info->is_mesh_node && is_multicast_ether_addr(wh->addr3)) -+ return tx_skb; -+ -+ if (ieee80211_is_qos_nullfunc(wh->frame_control)) -+ return tx_skb; -+ -+ if (sta_info->amsdu_ctrl.cap == MWL_AMSDU_SIZE_4K) -+ amsdu_allow_size = SYSADPT_AMSDU_4K_MAX_SIZE; -+ else if (sta_info->amsdu_ctrl.cap == MWL_AMSDU_SIZE_8K) -+ amsdu_allow_size = SYSADPT_AMSDU_8K_MAX_SIZE; -+ else -+ return tx_skb; -+ -+ spin_lock_bh(&sta_info->amsdu_lock); -+ amsdu = &sta_info->amsdu_ctrl.frag[desc_num]; -+ -+ if ((tx_skb->len > SYSADPT_AMSDU_ALLOW_SIZE) || -+ utils_is_non_amsdu_packet(tx_skb->data, true)) { -+ if (amsdu->num) { -+ pcie_tx_skb(priv, desc_num, amsdu->skb); -+ amsdu->num = 0; -+ amsdu->cur_pos = NULL; -+ } -+ spin_unlock_bh(&sta_info->amsdu_lock); -+ return tx_skb; -+ } -+ -+ /* potential amsdu size, should add amsdu header 14 bytes + -+ * maximum padding 3. -+ */ -+ wh_len = ieee80211_hdrlen(wh->frame_control); -+ len = tx_skb->len - wh_len + 17; -+ -+ if (amsdu->num) { -+ if ((amsdu->skb->len + len) > amsdu_allow_size) { -+ pcie_tx_skb(priv, desc_num, amsdu->skb); -+ amsdu->num = 0; -+ amsdu->cur_pos = NULL; -+ } -+ } -+ -+ amsdu->jiffies = jiffies; -+ len = tx_skb->len - wh_len; -+ -+ if (amsdu->num == 0) { -+ struct sk_buff *newskb; -+ int headroom; -+ -+ amsdu_pkts = (struct sk_buff_head *) -+ kmalloc(sizeof(*amsdu_pkts), GFP_ATOMIC); -+ if (!amsdu_pkts) { -+ spin_unlock_bh(&sta_info->amsdu_lock); -+ return tx_skb; -+ } -+ newskb = dev_alloc_skb(amsdu_allow_size + -+ pcie_priv->tx_head_room); -+ if (!newskb) { -+ spin_unlock_bh(&sta_info->amsdu_lock); -+ kfree(amsdu_pkts); -+ return tx_skb; -+ } -+ -+ headroom = skb_headroom(newskb); -+ if (headroom < pcie_priv->tx_head_room) -+ skb_reserve(newskb, -+ (pcie_priv->tx_head_room - headroom)); -+ -+ data = newskb->data; -+ memcpy(data, tx_skb->data, wh_len); -+ if (sta_info->is_mesh_node) { -+ ether_addr_copy(data + wh_len, wh->addr3); -+ ether_addr_copy(data + wh_len + ETH_ALEN, wh->addr4); -+ } else { -+ ether_addr_copy(data + wh_len, -+ ieee80211_get_DA(wh)); -+ ether_addr_copy(data + wh_len + ETH_ALEN, -+ ieee80211_get_SA(wh)); -+ } -+ *(u8 *)(data + wh_len + ETH_HLEN - 1) = len & 0xff; -+ *(u8 *)(data + wh_len + ETH_HLEN - 2) = (len >> 8) & 0xff; -+ memcpy(data + wh_len + ETH_HLEN, tx_skb->data + wh_len, len); -+ -+ skb_put(newskb, tx_skb->len + ETH_HLEN); -+ tx_ctrl->qos_ctrl |= IEEE80211_QOS_CTL_A_MSDU_PRESENT; -+ amsdu_info = IEEE80211_SKB_CB(newskb); -+ memcpy(amsdu_info, tx_info, sizeof(*tx_info)); -+ skb_queue_head_init(amsdu_pkts); -+ ((struct pcie_tx_ctrl *)&amsdu_info->status)->amsdu_pkts = -+ (void *)amsdu_pkts; -+ amsdu->skb = newskb; -+ } else { -+ amsdu->cur_pos += amsdu->pad; -+ data = amsdu->cur_pos; -+ -+ if (sta_info->is_mesh_node) { -+ ether_addr_copy(data, wh->addr3); -+ ether_addr_copy(data + ETH_ALEN, wh->addr4); -+ } else { -+ ether_addr_copy(data, ieee80211_get_DA(wh)); -+ ether_addr_copy(data + ETH_ALEN, ieee80211_get_SA(wh)); -+ } -+ *(u8 *)(data + ETH_HLEN - 1) = len & 0xff; -+ *(u8 *)(data + ETH_HLEN - 2) = (len >> 8) & 0xff; -+ memcpy(data + ETH_HLEN, tx_skb->data + wh_len, len); -+ -+ skb_put(amsdu->skb, len + ETH_HLEN + amsdu->pad); -+ amsdu_info = IEEE80211_SKB_CB(amsdu->skb); -+ amsdu_pkts = (struct sk_buff_head *) -+ ((struct pcie_tx_ctrl *) -+ &amsdu_info->status)->amsdu_pkts; -+ } -+ -+ amsdu->num++; -+ amsdu->pad = ((len + ETH_HLEN) % 4) ? (4 - (len + ETH_HLEN) % 4) : 0; -+ amsdu->cur_pos = amsdu->skb->data + amsdu->skb->len; -+ skb_queue_tail(amsdu_pkts, tx_skb); -+ -+ if (amsdu->num > SYSADPT_AMSDU_PACKET_THRESHOLD) { -+ amsdu->num = 0; -+ amsdu->cur_pos = NULL; -+ spin_unlock_bh(&sta_info->amsdu_lock); -+ return amsdu->skb; -+ } -+ -+ spin_unlock_bh(&sta_info->amsdu_lock); -+ return NULL; -+} -+ -+static inline void pcie_tx_ack_amsdu_pkts(struct ieee80211_hw *hw, u32 rate, -+ struct sk_buff_head *amsdu_pkts) -+{ -+ struct sk_buff *amsdu_pkt; -+ struct ieee80211_tx_info *info; -+ -+ while (skb_queue_len(amsdu_pkts) > 0) { -+ amsdu_pkt = skb_dequeue(amsdu_pkts); -+ info = IEEE80211_SKB_CB(amsdu_pkt); -+ pcie_tx_prepare_info(hw->priv, rate, info); -+ ieee80211_tx_status(hw, amsdu_pkt); -+ } -+ -+ kfree(amsdu_pkts); -+} -+ -+static void pcie_pfu_tx_done(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ u32 wrdoneidx, rdptr; -+ const u32 num_tx_buffs = PCIE_MAX_TXRX_BD << PCIE_TX_START_PTR; -+ struct pcie_data_buf *data_buf; -+ struct sk_buff *done_skb; -+ struct pcie_pfu_dma_data *pfu_dma; -+ struct pcie_tx_desc *tx_desc; -+ struct pcie_dma_data *dma_data; -+ struct ieee80211_hdr *wh; -+ struct ieee80211_tx_info *info; -+ struct pcie_tx_ctrl *tx_ctrl; -+ struct ieee80211_sta *sta; -+ struct mwl_sta *sta_info; -+ u32 rate = 0; -+ struct sk_buff_head *amsdu_pkts; -+ int hdrlen; -+ -+ spin_lock_bh(&pcie_priv->tx_desc_lock); -+ /* Read the TX ring read pointer set by firmware */ -+ rdptr = readl(pcie_priv->iobase1 + REG_TXBD_RDPTR); -+ /* free from previous txbd_rdptr to current txbd_rdptr */ -+ while (((pcie_priv->txbd_rdptr & PCIE_TXBD_MASK) != -+ (rdptr & PCIE_TXBD_MASK)) || -+ ((pcie_priv->txbd_rdptr & PCIE_BD_FLAG_TX_ROLLOVER_IND) != -+ (rdptr & PCIE_BD_FLAG_TX_ROLLOVER_IND))) { -+ wrdoneidx = pcie_priv->txbd_rdptr & PCIE_TXBD_MASK; -+ wrdoneidx >>= PCIE_TX_START_PTR; -+ -+ data_buf = pcie_priv->txbd_ring[wrdoneidx]; -+ done_skb = pcie_priv->tx_buf_list[wrdoneidx]; -+ if (done_skb) { -+ pfu_dma = (struct pcie_pfu_dma_data *)done_skb->data; -+ tx_desc = &pfu_dma->tx_desc; -+ dma_data = &pfu_dma->dma_data; -+ pci_unmap_single(pcie_priv->pdev, -+ le32_to_cpu(data_buf->paddr), -+ le16_to_cpu(data_buf->len), -+ PCI_DMA_TODEVICE); -+ tx_desc->pkt_ptr = 0; -+ tx_desc->pkt_len = 0; -+ tx_desc->status = cpu_to_le32(EAGLE_TXD_STATUS_IDLE); -+ wmb(); /* memory barrier */ -+ -+ wh = &dma_data->wh; -+ if (ieee80211_is_nullfunc(wh->frame_control) || -+ ieee80211_is_qos_nullfunc(wh->frame_control)) { -+ dev_kfree_skb_any(done_skb); -+ done_skb = NULL; -+ goto next; -+ } -+ -+ info = IEEE80211_SKB_CB(done_skb); -+ tx_ctrl = (struct pcie_tx_ctrl *)&info->status; -+ sta = (struct ieee80211_sta *)tx_ctrl->sta; -+ if (sta) { -+ sta_info = mwl_dev_get_sta(sta); -+ rate = sta_info->tx_rate_info; -+ } -+ -+ if (ieee80211_is_data(wh->frame_control) || -+ ieee80211_is_data_qos(wh->frame_control)) { -+ amsdu_pkts = (struct sk_buff_head *) -+ tx_ctrl->amsdu_pkts; -+ if (amsdu_pkts) { -+ pcie_tx_ack_amsdu_pkts(priv->hw, rate, -+ amsdu_pkts); -+ dev_kfree_skb_any(done_skb); -+ done_skb = NULL; -+ } else { -+ pcie_tx_prepare_info(priv, rate, info); -+ } -+ } else { -+ pcie_tx_prepare_info(priv, 0, info); -+ } -+ -+ if (done_skb) { -+ /* Remove H/W dma header */ -+ hdrlen = ieee80211_hdrlen( -+ dma_data->wh.frame_control); -+ memmove(dma_data->data - hdrlen, -+ &dma_data->wh, hdrlen); -+ skb_pull(done_skb, sizeof(*pfu_dma) - hdrlen); -+ ieee80211_tx_status(priv->hw, done_skb); -+ } -+ } -+next: -+ memset(data_buf, 0, sizeof(*data_buf)); -+ pcie_priv->tx_buf_list[wrdoneidx] = NULL; -+ -+ pcie_priv->txbd_rdptr += PCIE_BD_FLAG_TX_START_PTR; -+ if ((pcie_priv->txbd_rdptr & PCIE_TXBD_MASK) == num_tx_buffs) -+ pcie_priv->txbd_rdptr = ((pcie_priv->txbd_rdptr & -+ PCIE_BD_FLAG_TX_ROLLOVER_IND) ^ -+ PCIE_BD_FLAG_TX_ROLLOVER_IND); -+ } -+ spin_unlock_bh(&pcie_priv->tx_desc_lock); -+ -+ if (pcie_priv->is_tx_done_schedule) { -+ pcie_mask_int(pcie_priv, MACREG_A2HRIC_BIT_TX_DONE, true); -+ tasklet_schedule(&pcie_priv->tx_task); -+ pcie_priv->is_tx_done_schedule = false; -+ } -+} -+ -+static void pcie_non_pfu_tx_done(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ int num; -+ struct pcie_desc_data *desc; -+ struct pcie_tx_hndl *tx_hndl; -+ struct pcie_tx_desc *tx_desc; -+ struct sk_buff *done_skb; -+ int idx; -+ u32 rate; -+ struct pcie_dma_data *dma_data; -+ struct ieee80211_hdr *wh; -+ struct ieee80211_tx_info *info; -+ struct pcie_tx_ctrl *tx_ctrl; -+ struct sk_buff_head *amsdu_pkts; -+ int hdrlen; -+ -+ spin_lock_bh(&pcie_priv->tx_desc_lock); -+ for (num = 0; num < SYSADPT_TX_WMM_QUEUES; num++) { -+ desc = &pcie_priv->desc_data[num]; -+ tx_hndl = desc->pstale_tx_hndl; -+ tx_desc = tx_hndl->pdesc; -+ -+ if ((tx_desc->status & -+ cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED)) && -+ (tx_hndl->pnext->pdesc->status & -+ cpu_to_le32(EAGLE_TXD_STATUS_OK))) -+ tx_desc->status = cpu_to_le32(EAGLE_TXD_STATUS_OK); -+ -+ while (tx_hndl && -+ (tx_desc->status & cpu_to_le32(EAGLE_TXD_STATUS_OK)) && -+ (!(tx_desc->status & -+ cpu_to_le32(EAGLE_TXD_STATUS_FW_OWNED)))) { -+ pci_unmap_single(pcie_priv->pdev, -+ le32_to_cpu(tx_desc->pkt_ptr), -+ le16_to_cpu(tx_desc->pkt_len), -+ PCI_DMA_TODEVICE); -+ done_skb = tx_hndl->psk_buff; -+ rate = le32_to_cpu(tx_desc->rate_info); -+ tx_desc->pkt_ptr = 0; -+ tx_desc->pkt_len = 0; -+ tx_desc->status = -+ cpu_to_le32(EAGLE_TXD_STATUS_IDLE); -+ tx_hndl->psk_buff = NULL; -+ wmb(); /*Data Memory Barrier*/ -+ -+ skb_get(done_skb); -+ idx = pcie_priv->delay_q_idx; -+ if (pcie_priv->delay_q[idx]) -+ dev_kfree_skb_any(pcie_priv->delay_q[idx]); -+ pcie_priv->delay_q[idx] = done_skb; -+ idx++; -+ if (idx >= PCIE_DELAY_FREE_Q_LIMIT) -+ idx = 0; -+ pcie_priv->delay_q_idx = idx; -+ -+ dma_data = (struct pcie_dma_data *)done_skb->data; -+ wh = &dma_data->wh; -+ if (ieee80211_is_nullfunc(wh->frame_control) || -+ ieee80211_is_qos_nullfunc(wh->frame_control)) { -+ dev_kfree_skb_any(done_skb); -+ done_skb = NULL; -+ goto next; -+ } -+ -+ info = IEEE80211_SKB_CB(done_skb); -+ if (ieee80211_is_data(wh->frame_control) || -+ ieee80211_is_data_qos(wh->frame_control)) { -+ tx_ctrl = (struct pcie_tx_ctrl *)&info->status; -+ amsdu_pkts = (struct sk_buff_head *) -+ tx_ctrl->amsdu_pkts; -+ if (amsdu_pkts) { -+ pcie_tx_ack_amsdu_pkts(priv->hw, rate, -+ amsdu_pkts); -+ dev_kfree_skb_any(done_skb); -+ done_skb = NULL; -+ } else { -+ pcie_tx_prepare_info(priv, rate, info); -+ } -+ } else { -+ pcie_tx_prepare_info(priv, 0, info); -+ } -+ -+ if (done_skb) { -+ /* Remove H/W dma header */ -+ hdrlen = ieee80211_hdrlen( -+ dma_data->wh.frame_control); -+ memmove(dma_data->data - hdrlen, -+ &dma_data->wh, hdrlen); -+ skb_pull(done_skb, sizeof(*dma_data) - hdrlen); -+ ieee80211_tx_status(priv->hw, done_skb); -+ } -+next: -+ tx_hndl = tx_hndl->pnext; -+ tx_desc = tx_hndl->pdesc; -+ pcie_priv->fw_desc_cnt[num]--; -+ } -+ -+ desc->pstale_tx_hndl = tx_hndl; -+ } -+ spin_unlock_bh(&pcie_priv->tx_desc_lock); -+ -+ if (pcie_priv->is_tx_done_schedule) { -+ pcie_mask_int(pcie_priv, MACREG_A2HRIC_BIT_TX_DONE, true); -+ tasklet_schedule(&pcie_priv->tx_task); -+ pcie_priv->is_tx_done_schedule = false; -+ } -+} -+ -+int pcie_tx_init(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ int rc; -+ int i; -+ -+ if (priv->chip_type == MWL8997) -+ rc = pcie_txbd_ring_create(priv); -+ else -+ rc = pcie_tx_ring_alloc(priv); -+ -+ if (rc) { -+ wiphy_err(hw->wiphy, "allocating TX ring failed\n"); -+ return rc; -+ } -+ -+ rc = pcie_tx_ring_init(priv); -+ if (rc) { -+ pcie_tx_ring_free(priv); -+ wiphy_err(hw->wiphy, "initializing TX ring failed\n"); -+ return rc; -+ } -+ -+ pcie_priv->delay_q_idx = 0; -+ for (i = 0; i < PCIE_DELAY_FREE_Q_LIMIT; i++) -+ pcie_priv->delay_q[i] = NULL; -+ -+ return 0; -+} -+ -+void pcie_tx_deinit(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ int i; -+ -+ for (i = 0; i < PCIE_DELAY_FREE_Q_LIMIT; i++) -+ if (pcie_priv->delay_q[i]) -+ dev_kfree_skb_any(pcie_priv->delay_q[i]); -+ -+ pcie_tx_ring_cleanup(priv); -+ -+ if (priv->chip_type == MWL8997) -+ pcie_txbd_ring_delete(priv); -+ else -+ pcie_tx_ring_free(priv); -+} -+ -+void pcie_tx_skbs(unsigned long data) -+{ -+ struct ieee80211_hw *hw = (struct ieee80211_hw *)data; -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ int num = SYSADPT_TX_WMM_QUEUES; -+ struct sk_buff *tx_skb; -+ -+ spin_lock_bh(&pcie_priv->tx_desc_lock); -+ while (num--) { -+ while (skb_queue_len(&pcie_priv->txq[num]) > 0) { -+ struct ieee80211_tx_info *tx_info; -+ struct pcie_tx_ctrl *tx_ctrl; -+ -+ if (!pcie_tx_available(priv, num)) -+ break; -+ -+ tx_skb = skb_dequeue(&pcie_priv->txq[num]); -+ if (!tx_skb) -+ continue; -+ tx_info = IEEE80211_SKB_CB(tx_skb); -+ tx_ctrl = (struct pcie_tx_ctrl *)&tx_info->status; -+ -+ if (tx_ctrl->tx_priority >= SYSADPT_TX_WMM_QUEUES) -+ tx_skb = pcie_tx_do_amsdu(priv, num, -+ tx_skb, tx_info); -+ -+ if (tx_skb) { -+ if (pcie_tx_available(priv, num)) -+ pcie_tx_skb(priv, num, tx_skb); -+ else -+ skb_queue_head(&pcie_priv->txq[num], -+ tx_skb); -+ } -+ } -+ -+ if (skb_queue_len(&pcie_priv->txq[num]) < -+ pcie_priv->txq_wake_threshold) { -+ int queue; -+ -+ queue = SYSADPT_TX_WMM_QUEUES - num - 1; -+ if (ieee80211_queue_stopped(hw, queue)) -+ ieee80211_wake_queue(hw, queue); -+ } -+ } -+ spin_unlock_bh(&pcie_priv->tx_desc_lock); -+} -+ -+void pcie_tx_flush_amsdu(unsigned long data) -+{ -+ struct ieee80211_hw *hw = (struct ieee80211_hw *)data; -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct mwl_sta *sta_info; -+ int i; -+ struct mwl_amsdu_frag *amsdu_frag; -+ -+ spin_lock(&priv->sta_lock); -+ list_for_each_entry(sta_info, &priv->sta_list, list) { -+ spin_lock(&pcie_priv->tx_desc_lock); -+ spin_lock(&sta_info->amsdu_lock); -+ for (i = 0; i < SYSADPT_TX_WMM_QUEUES; i++) { -+ amsdu_frag = &sta_info->amsdu_ctrl.frag[i]; -+ if (amsdu_frag->num) { -+ if (time_after(jiffies, -+ (amsdu_frag->jiffies + 1))) { -+ if (pcie_tx_available(priv, i)) { -+ pcie_tx_skb(priv, i, -+ amsdu_frag->skb); -+ amsdu_frag->num = 0; -+ amsdu_frag->cur_pos = NULL; -+ } -+ } -+ } -+ } -+ spin_unlock(&sta_info->amsdu_lock); -+ spin_unlock(&pcie_priv->tx_desc_lock); -+ } -+ spin_unlock(&priv->sta_lock); -+ -+ pcie_mask_int(pcie_priv, MACREG_A2HRIC_BIT_QUE_EMPTY, true); -+ pcie_priv->is_qe_schedule = false; -+} -+ -+void pcie_tx_done(unsigned long data) -+{ -+ struct ieee80211_hw *hw = (struct ieee80211_hw *)data; -+ struct mwl_priv *priv = hw->priv; -+ -+ if (priv->chip_type == MWL8997) -+ pcie_pfu_tx_done(priv); -+ else -+ pcie_non_pfu_tx_done(priv); -+} -+ -+void pcie_tx_xmit(struct ieee80211_hw *hw, -+ struct ieee80211_tx_control *control, -+ struct sk_buff *skb) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ int index; -+ struct ieee80211_sta *sta; -+ struct ieee80211_tx_info *tx_info; -+ struct mwl_vif *mwl_vif; -+ struct ieee80211_hdr *wh; -+ u8 xmitcontrol; -+ u16 qos; -+ int txpriority; -+ u8 tid = 0; -+ struct mwl_ampdu_stream *stream = NULL; -+ bool start_ba_session = false; -+ bool mgmtframe = false; -+ struct ieee80211_mgmt *mgmt; -+ bool eapol_frame = false; -+ struct pcie_tx_ctrl *tx_ctrl; -+ struct ieee80211_key_conf *k_conf = NULL; -+ int rc; -+ -+ index = skb_get_queue_mapping(skb); -+ sta = control->sta; -+ -+ wh = (struct ieee80211_hdr *)skb->data; -+ tx_info = IEEE80211_SKB_CB(skb); -+ mwl_vif = mwl_dev_get_vif(tx_info->control.vif); -+ -+ if (ieee80211_is_data_qos(wh->frame_control)) -+ qos = le16_to_cpu(*((__le16 *)ieee80211_get_qos_ctl(wh))); -+ else -+ qos = 0; -+ -+ if (ieee80211_is_mgmt(wh->frame_control)) { -+ mgmtframe = true; -+ mgmt = (struct ieee80211_mgmt *)skb->data; -+ } else { -+ u16 pkt_type; -+ struct mwl_sta *sta_info; -+ -+ pkt_type = be16_to_cpu(*((__be16 *) -+ &skb->data[ieee80211_hdrlen(wh->frame_control) + 6])); -+ if (pkt_type == ETH_P_PAE) { -+ index = IEEE80211_AC_VO; -+ eapol_frame = true; -+ } -+ if (sta) { -+ if (mwl_vif->is_hw_crypto_enabled) { -+ sta_info = mwl_dev_get_sta(sta); -+ if (!sta_info->is_key_set && !eapol_frame) { -+ dev_kfree_skb_any(skb); -+ return; -+ } -+ } -+ } -+ } -+ -+ if (tx_info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { -+ wh->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); -+ wh->seq_ctrl |= cpu_to_le16(mwl_vif->seqno); -+ mwl_vif->seqno += 0x10; -+ } -+ -+ /* Setup firmware control bit fields for each frame type. */ -+ xmitcontrol = 0; -+ -+ if (mgmtframe || ieee80211_is_ctl(wh->frame_control)) { -+ qos = 0; -+ } else if (ieee80211_is_data(wh->frame_control)) { -+ qos &= ~MWL_QOS_ACK_POLICY_MASK; -+ -+ if (tx_info->flags & IEEE80211_TX_CTL_AMPDU) { -+ xmitcontrol &= 0xfb; -+ qos |= MWL_QOS_ACK_POLICY_BLOCKACK; -+ } else { -+ xmitcontrol |= 0x4; -+ qos |= MWL_QOS_ACK_POLICY_NORMAL; -+ } -+ -+ if (is_multicast_ether_addr(wh->addr1) || eapol_frame) -+ xmitcontrol |= EAGLE_TXD_XMITCTRL_USE_MC_RATE; -+ } -+ -+ k_conf = tx_info->control.hw_key; -+ -+ /* Queue ADDBA request in the respective data queue. While setting up -+ * the ampdu stream, mac80211 queues further packets for that -+ * particular ra/tid pair. However, packets piled up in the hardware -+ * for that ra/tid pair will still go out. ADDBA request and the -+ * related data packets going out from different queues asynchronously -+ * will cause a shift in the receiver window which might result in -+ * ampdu packets getting dropped at the receiver after the stream has -+ * been setup. -+ */ -+ if (mgmtframe) { -+ u16 capab; -+ -+ if (unlikely(ieee80211_is_action(wh->frame_control) && -+ mgmt->u.action.category == WLAN_CATEGORY_BACK && -+ mgmt->u.action.u.addba_req.action_code == -+ WLAN_ACTION_ADDBA_REQ)) { -+ capab = le16_to_cpu(mgmt->u.action.u.addba_req.capab); -+ tid = (capab & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2; -+ index = utils_tid_to_ac(tid); -+ } -+ -+ if (unlikely(ieee80211_is_assoc_req(wh->frame_control))) -+ utils_add_basic_rates(hw->conf.chandef.chan->band, skb); -+ } -+ -+ index = SYSADPT_TX_WMM_QUEUES - index - 1; -+ txpriority = index; -+ -+ if (sta && sta->ht_cap.ht_supported && !eapol_frame && -+ ieee80211_is_data_qos(wh->frame_control)) { -+ tid = qos & 0xf; -+ pcie_tx_count_packet(sta, tid); -+ -+ spin_lock_bh(&priv->stream_lock); -+ stream = mwl_fwcmd_lookup_stream(hw, sta, tid); -+ -+ if (stream) { -+ if (stream->state == AMPDU_STREAM_ACTIVE) { -+ if (WARN_ON(!(qos & -+ MWL_QOS_ACK_POLICY_BLOCKACK))) { -+ spin_unlock_bh(&priv->stream_lock); -+ dev_kfree_skb_any(skb); -+ return; -+ } -+ -+ txpriority = -+ (SYSADPT_TX_WMM_QUEUES + stream->idx) % -+ TOTAL_HW_QUEUES; -+ } else if (stream->state == AMPDU_STREAM_NEW) { -+ /* We get here if the driver sends us packets -+ * after we've initiated a stream, but before -+ * our ampdu_action routine has been called -+ * with IEEE80211_AMPDU_TX_START to get the SSN -+ * for the ADDBA request. So this packet can -+ * go out with no risk of sequence number -+ * mismatch. No special handling is required. -+ */ -+ } else { -+ /* Drop packets that would go out after the -+ * ADDBA request was sent but before the ADDBA -+ * response is received. If we don't do this, -+ * the recipient would probably receive it -+ * after the ADDBA request with SSN 0. This -+ * will cause the recipient's BA receive window -+ * to shift, which would cause the subsequent -+ * packets in the BA stream to be discarded. -+ * mac80211 queues our packets for us in this -+ * case, so this is really just a safety check. -+ */ -+ wiphy_warn(hw->wiphy, -+ "can't send packet during ADDBA\n"); -+ spin_unlock_bh(&priv->stream_lock); -+ dev_kfree_skb_any(skb); -+ return; -+ } -+ } else { -+ if (mwl_fwcmd_ampdu_allowed(sta, tid)) { -+ stream = mwl_fwcmd_add_stream(hw, sta, tid); -+ -+ if (stream) -+ start_ba_session = true; -+ } -+ } -+ -+ spin_unlock_bh(&priv->stream_lock); -+ } else { -+ qos &= ~MWL_QOS_ACK_POLICY_MASK; -+ qos |= MWL_QOS_ACK_POLICY_NORMAL; -+ } -+ -+ tx_ctrl = (struct pcie_tx_ctrl *)&tx_info->status; -+ tx_ctrl->vif = (void *)tx_info->control.vif; -+ tx_ctrl->sta = (void *)sta; -+ tx_ctrl->k_conf = (void *)k_conf; -+ tx_ctrl->amsdu_pkts = NULL; -+ tx_ctrl->tx_priority = txpriority; -+ tx_ctrl->type = (mgmtframe ? IEEE_TYPE_MANAGEMENT : IEEE_TYPE_DATA); -+ tx_ctrl->qos_ctrl = qos; -+ tx_ctrl->xmit_control = xmitcontrol; -+ -+ if (skb_queue_len(&pcie_priv->txq[index]) > pcie_priv->txq_limit) -+ ieee80211_stop_queue(hw, SYSADPT_TX_WMM_QUEUES - index - 1); -+ -+ skb_queue_tail(&pcie_priv->txq[index], skb); -+ -+ tasklet_schedule(&pcie_priv->tx_task); -+ -+ /* Initiate the ampdu session here */ -+ if (start_ba_session) { -+ spin_lock_bh(&priv->stream_lock); -+ rc = mwl_fwcmd_start_stream(hw, stream); -+ if (rc) -+ mwl_fwcmd_remove_stream(hw, stream); -+ else -+ wiphy_debug(hw->wiphy, "Mac80211 start BA %pM\n", -+ stream->sta->addr); -+ spin_unlock_bh(&priv->stream_lock); -+ } -+} -+ -+void pcie_tx_del_pkts_via_vif(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ int num; -+ struct sk_buff *skb, *tmp; -+ struct ieee80211_tx_info *tx_info; -+ struct pcie_tx_ctrl *tx_ctrl; -+ struct sk_buff_head *amsdu_pkts; -+ unsigned long flags; -+ -+ for (num = 1; num < PCIE_NUM_OF_DESC_DATA; num++) { -+ spin_lock_irqsave(&pcie_priv->txq[num].lock, flags); -+ skb_queue_walk_safe(&pcie_priv->txq[num], skb, tmp) { -+ tx_info = IEEE80211_SKB_CB(skb); -+ tx_ctrl = (struct pcie_tx_ctrl *)&tx_info->status; -+ if (tx_ctrl->vif == vif) { -+ amsdu_pkts = (struct sk_buff_head *) -+ tx_ctrl->amsdu_pkts; -+ if (amsdu_pkts) { -+ skb_queue_purge(amsdu_pkts); -+ kfree(amsdu_pkts); -+ } -+ __skb_unlink(skb, &pcie_priv->txq[num]); -+ dev_kfree_skb_any(skb); -+ } -+ } -+ spin_unlock_irqrestore(&pcie_priv->txq[num].lock, flags); -+ } -+} -+ -+void pcie_tx_del_pkts_via_sta(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ int num; -+ struct sk_buff *skb, *tmp; -+ struct ieee80211_tx_info *tx_info; -+ struct pcie_tx_ctrl *tx_ctrl; -+ struct sk_buff_head *amsdu_pkts; -+ unsigned long flags; -+ -+ for (num = 1; num < PCIE_NUM_OF_DESC_DATA; num++) { -+ spin_lock_irqsave(&pcie_priv->txq[num].lock, flags); -+ skb_queue_walk_safe(&pcie_priv->txq[num], skb, tmp) { -+ tx_info = IEEE80211_SKB_CB(skb); -+ tx_ctrl = (struct pcie_tx_ctrl *)&tx_info->status; -+ if (tx_ctrl->sta == sta) { -+ amsdu_pkts = (struct sk_buff_head *) -+ tx_ctrl->amsdu_pkts; -+ if (amsdu_pkts) { -+ skb_queue_purge(amsdu_pkts); -+ kfree(amsdu_pkts); -+ } -+ __skb_unlink(skb, &pcie_priv->txq[num]); -+ dev_kfree_skb_any(skb); -+ } -+ } -+ spin_unlock_irqrestore(&pcie_priv->txq[num].lock, flags); -+ } -+} -+ -+void pcie_tx_del_ampdu_pkts(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta, u8 tid) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct mwl_sta *sta_info = mwl_dev_get_sta(sta); -+ int ac, desc_num; -+ struct mwl_amsdu_frag *amsdu_frag; -+ struct sk_buff *skb, *tmp; -+ struct ieee80211_tx_info *tx_info; -+ struct pcie_tx_ctrl *tx_ctrl; -+ struct sk_buff_head *amsdu_pkts; -+ unsigned long flags; -+ -+ ac = utils_tid_to_ac(tid); -+ desc_num = SYSADPT_TX_WMM_QUEUES - ac - 1; -+ spin_lock_irqsave(&pcie_priv->txq[desc_num].lock, flags); -+ skb_queue_walk_safe(&pcie_priv->txq[desc_num], skb, tmp) { -+ tx_info = IEEE80211_SKB_CB(skb); -+ tx_ctrl = (struct pcie_tx_ctrl *)&tx_info->status; -+ if (tx_ctrl->sta == sta) { -+ amsdu_pkts = (struct sk_buff_head *) -+ tx_ctrl->amsdu_pkts; -+ if (amsdu_pkts) { -+ skb_queue_purge(amsdu_pkts); -+ kfree(amsdu_pkts); -+ } -+ __skb_unlink(skb, &pcie_priv->txq[desc_num]); -+ dev_kfree_skb_any(skb); -+ } -+ } -+ spin_unlock_irqrestore(&pcie_priv->txq[desc_num].lock, flags); -+ -+ spin_lock_bh(&sta_info->amsdu_lock); -+ amsdu_frag = &sta_info->amsdu_ctrl.frag[desc_num]; -+ if (amsdu_frag->num) { -+ amsdu_frag->num = 0; -+ amsdu_frag->cur_pos = NULL; -+ if (amsdu_frag->skb) { -+ tx_info = IEEE80211_SKB_CB(amsdu_frag->skb); -+ tx_ctrl = (struct pcie_tx_ctrl *)&tx_info->status; -+ amsdu_pkts = (struct sk_buff_head *) -+ tx_ctrl->amsdu_pkts; -+ if (amsdu_pkts) { -+ skb_queue_purge(amsdu_pkts); -+ kfree(amsdu_pkts); -+ } -+ dev_kfree_skb_any(amsdu_frag->skb); -+ } -+ } -+ spin_unlock_bh(&sta_info->amsdu_lock); -+} -+ -+void pcie_tx_del_sta_amsdu_pkts(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta) -+{ -+ struct mwl_sta *sta_info = mwl_dev_get_sta(sta); -+ int num; -+ struct mwl_amsdu_frag *amsdu_frag; -+ struct ieee80211_tx_info *tx_info; -+ struct pcie_tx_ctrl *tx_ctrl; -+ struct sk_buff_head *amsdu_pkts; -+ -+ spin_lock_bh(&sta_info->amsdu_lock); -+ for (num = 0; num < SYSADPT_TX_WMM_QUEUES; num++) { -+ amsdu_frag = &sta_info->amsdu_ctrl.frag[num]; -+ if (amsdu_frag->num) { -+ amsdu_frag->num = 0; -+ amsdu_frag->cur_pos = NULL; -+ if (amsdu_frag->skb) { -+ tx_info = IEEE80211_SKB_CB(amsdu_frag->skb); -+ tx_ctrl = (struct pcie_tx_ctrl *) -+ &tx_info->status; -+ amsdu_pkts = (struct sk_buff_head *) -+ tx_ctrl->amsdu_pkts; -+ if (amsdu_pkts) { -+ skb_queue_purge(amsdu_pkts); -+ kfree(amsdu_pkts); -+ } -+ dev_kfree_skb_any(amsdu_frag->skb); -+ } -+ } -+ } -+ spin_unlock_bh(&sta_info->amsdu_lock); -+} -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx.h b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx.h -new file mode 100644 -index 000000000000..c233ba1aaa9d ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx.h -@@ -0,0 +1,38 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines transmit related functions. */ -+ -+#ifndef _TX_H_ -+#define _TX_H_ -+ -+int pcie_tx_init(struct ieee80211_hw *hw); -+void pcie_tx_deinit(struct ieee80211_hw *hw); -+void pcie_tx_skbs(unsigned long data); -+void pcie_tx_done(unsigned long data); -+void pcie_tx_flush_amsdu(unsigned long data); -+void pcie_tx_xmit(struct ieee80211_hw *hw, -+ struct ieee80211_tx_control *control, -+ struct sk_buff *skb); -+void pcie_tx_del_pkts_via_vif(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif); -+void pcie_tx_del_pkts_via_sta(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta); -+void pcie_tx_del_ampdu_pkts(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta, u8 tid); -+void pcie_tx_del_sta_amsdu_pkts(struct ieee80211_hw *hw, -+ struct ieee80211_sta *sta); -+ -+#endif /* _TX_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx_ndp.c b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx_ndp.c -new file mode 100644 -index 000000000000..6758cde363c6 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx_ndp.c -@@ -0,0 +1,693 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file implements transmit related functions for new data -+ * path. -+ */ -+ -+#include -+#include -+ -+#include "sysadpt.h" -+#include "core.h" -+#include "utils.h" -+#include "hif/fwcmd.h" -+#include "hif/pcie/dev.h" -+#include "hif/pcie/tx_ndp.h" -+ -+#define MAX_NUM_TX_RING_BYTES (MAX_NUM_TX_DESC * \ -+ sizeof(struct pcie_tx_desc_ndp)) -+#define MAX_NUM_TX_RING_DONE_BYTES (MAX_NUM_TX_DESC * \ -+ sizeof(struct tx_ring_done)) -+#define QUEUE_STAOFFSET ((SYSADPT_NUM_OF_AP - 1) + \ -+ SYSADPT_NUM_OF_CLIENT) -+#define PROBE_RESPONSE_TXQNUM ((SYSADPT_MAX_STA_SC4 + SYSADPT_NUM_OF_AP + \ -+ SYSADPT_NUM_OF_CLIENT) * SYSADPT_MAX_TID) -+#define MGMT_TXQNUM ((PROBE_RESPONSE_TXQNUM + 1)) -+#define TXDONE_THRESHOLD 4 -+ -+#define TX_CTRL_TYPE_DATA BIT(0) -+#define TX_CTRL_EAPOL BIT(1) -+#define TX_CTRL_TCP_ACK BIT(2) -+ -+/* Transmission information to transmit a socket buffer. -+ */ -+struct pcie_tx_ctrl_ndp { -+ u16 tx_que_priority; -+ u8 hdrlen; -+ u8 flags; -+ u32 rate; -+ u32 tcp_dst_src; -+ u32 tcp_sn; -+} __packed; -+ -+static int pcie_tx_ring_alloc_ndp(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data_ndp *desc = &pcie_priv->desc_data_ndp; -+ u8 *mem; -+ -+ mem = dma_alloc_coherent(priv->dev, -+ MAX_NUM_TX_RING_BYTES, -+ &desc->pphys_tx_ring, -+ GFP_KERNEL); -+ if (!mem) -+ goto err_no_mem; -+ desc->ptx_ring = (struct pcie_tx_desc_ndp *)mem; -+ memset(desc->ptx_ring, 0x00, MAX_NUM_TX_RING_BYTES); -+ -+ mem = dma_alloc_coherent(priv->dev, -+ MAX_NUM_TX_RING_DONE_BYTES, -+ &desc->pphys_tx_ring_done, -+ GFP_KERNEL); -+ if (!mem) -+ goto err_no_mem; -+ desc->ptx_ring_done = (struct tx_ring_done *)mem; -+ memset(desc->ptx_ring_done, 0x00, MAX_NUM_TX_RING_DONE_BYTES); -+ -+ mem = dma_alloc_coherent(priv->dev, -+ DEFAULT_ACNT_RING_SIZE, -+ &desc->pphys_acnt_ring, -+ GFP_KERNEL); -+ if (!mem) -+ goto err_no_mem; -+ desc->pacnt_ring = (u8 *)mem; -+ memset(desc->pacnt_ring, 0x00, DEFAULT_ACNT_RING_SIZE); -+ -+ desc->pacnt_buf = kzalloc(DEFAULT_ACNT_RING_SIZE, GFP_KERNEL); -+ if (!desc->pacnt_buf) -+ goto err_no_mem; -+ desc->acnt_ring_size = DEFAULT_ACNT_RING_SIZE; -+ -+ return 0; -+ -+err_no_mem: -+ -+ wiphy_err(priv->hw->wiphy, "cannot alloc mem\n"); -+ -+ return -ENOMEM; -+} -+ -+static int pcie_tx_ring_init_ndp(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data_ndp *desc = &pcie_priv->desc_data_ndp; -+ int i; -+ -+ for (i = 0; i < PCIE_NUM_OF_DESC_DATA; i++) -+ skb_queue_head_init(&pcie_priv->txq[i]); -+ -+ if (!desc->ptx_ring) { -+ for (i = 0; i < MAX_NUM_TX_DESC; i++) -+ desc->ptx_ring[i].user = cpu_to_le32(i); -+ desc->tx_desc_busy_cnt = 0; -+ } -+ -+ return 0; -+} -+ -+static void pcie_tx_ring_cleanup_ndp(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data_ndp *desc = &pcie_priv->desc_data_ndp; -+ struct sk_buff *tx_skb; -+ int i; -+ -+ for (i = 0; i < PCIE_NUM_OF_DESC_DATA; i++) -+ skb_queue_purge(&pcie_priv->txq[i]); -+ -+ for (i = 0; i < MAX_TX_RING_SEND_SIZE; i++) { -+ tx_skb = desc->tx_vbuflist[i]; -+ if (tx_skb) { -+ pci_unmap_single(pcie_priv->pdev, -+ desc->pphys_tx_buflist[i], -+ tx_skb->len, -+ PCI_DMA_TODEVICE); -+ dev_kfree_skb_any(tx_skb); -+ desc->pphys_tx_buflist[i] = 0; -+ desc->tx_vbuflist[i] = NULL; -+ } -+ } -+ desc->tx_sent_tail = 0; -+ desc->tx_sent_head = 0; -+ desc->tx_done_tail = 0; -+ desc->tx_vbuflist_idx = 0; -+ desc->tx_desc_busy_cnt = 0; -+} -+ -+static void pcie_tx_ring_free_ndp(struct mwl_priv *priv) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data_ndp *desc = &pcie_priv->desc_data_ndp; -+ -+ if (desc->ptx_ring) { -+ dma_free_coherent(priv->dev, -+ MAX_NUM_TX_RING_BYTES, -+ desc->ptx_ring, -+ desc->pphys_tx_ring); -+ desc->ptx_ring = NULL; -+ } -+ -+ if (desc->ptx_ring_done) { -+ dma_free_coherent(priv->dev, -+ MAX_NUM_TX_RING_DONE_BYTES, -+ desc->ptx_ring_done, -+ desc->pphys_tx_ring_done); -+ desc->prx_ring_done = NULL; -+ } -+ -+ if (desc->pacnt_ring) { -+ dma_free_coherent(priv->dev, -+ DEFAULT_ACNT_RING_SIZE, -+ desc->pacnt_ring, -+ desc->pphys_acnt_ring); -+ desc->pacnt_ring = NULL; -+ } -+ -+ kfree(desc->pacnt_buf); -+} -+ -+static inline u32 pcie_tx_set_skb(struct mwl_priv *priv, struct sk_buff *skb, -+ dma_addr_t dma) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data_ndp *desc = &pcie_priv->desc_data_ndp; -+ u32 index = desc->tx_vbuflist_idx; -+ -+ while (desc->tx_vbuflist[index]) -+ index = (index + 1) % MAX_TX_RING_SEND_SIZE; -+ -+ desc->tx_vbuflist_idx = (index + 1) % MAX_TX_RING_SEND_SIZE; -+ desc->pphys_tx_buflist[index] = dma; -+ desc->tx_vbuflist[index] = skb; -+ -+ return index; -+} -+ -+static inline int pcie_tx_skb_ndp(struct mwl_priv *priv, -+ struct sk_buff *tx_skb) -+{ -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data_ndp *desc = &pcie_priv->desc_data_ndp; -+ u32 tx_send_tail; -+ u32 tx_send_head_new; -+ struct ieee80211_tx_info *tx_info; -+ struct pcie_tx_ctrl_ndp *tx_ctrl; -+ struct pcie_tx_desc_ndp *pnext_tx_desc; -+ struct ieee80211_hdr *wh; -+ u32 ctrl = 0; -+ dma_addr_t dma; -+ -+ spin_lock_bh(&pcie_priv->tx_desc_lock); -+ -+ tx_send_tail = desc->tx_sent_tail; -+ tx_send_head_new = desc->tx_sent_head; -+ -+ if (((tx_send_head_new + 1) & (MAX_NUM_TX_DESC-1)) == tx_send_tail) { -+ /* Update the tx_send_tail */ -+ tx_send_tail = readl(pcie_priv->iobase1 + -+ MACREG_REG_TXSEDNTAIL); -+ desc->tx_sent_tail = tx_send_tail; -+ -+ if (((tx_send_head_new + 1) & (MAX_NUM_TX_DESC-1)) == -+ tx_send_tail) { -+ spin_unlock_bh(&pcie_priv->tx_desc_lock); -+ return -EAGAIN; -+ } -+ } -+ -+ tx_info = IEEE80211_SKB_CB(tx_skb); -+ tx_ctrl = (struct pcie_tx_ctrl_ndp *)tx_info->status.status_driver_data; -+ pnext_tx_desc = &desc->ptx_ring[tx_send_head_new]; -+ -+ if (tx_ctrl->flags & TX_CTRL_TYPE_DATA) { -+ wh = (struct ieee80211_hdr *)tx_skb->data; -+ -+ skb_pull(tx_skb, tx_ctrl->hdrlen); -+ ether_addr_copy(pnext_tx_desc->u.sa, -+ ieee80211_get_SA(wh)); -+ ether_addr_copy(pnext_tx_desc->u.da, -+ ieee80211_get_DA(wh)); -+ -+ if (tx_ctrl->flags & TX_CTRL_EAPOL) -+ ctrl = TXRING_CTRL_TAG_EAP << TXRING_CTRL_TAG_SHIFT; -+ if (tx_ctrl->flags & TX_CTRL_TCP_ACK) { -+ pnext_tx_desc->tcp_dst_src = -+ cpu_to_le32(tx_ctrl->tcp_dst_src); -+ pnext_tx_desc->tcp_sn = cpu_to_le32(tx_ctrl->tcp_sn); -+ ctrl = TXRING_CTRL_TAG_TCP_ACK << TXRING_CTRL_TAG_SHIFT; -+ } -+ ctrl |= (((tx_ctrl->tx_que_priority & TXRING_CTRL_QID_MASK) << -+ TXRING_CTRL_QID_SHIFT) | -+ ((tx_skb->len & TXRING_CTRL_LEN_MASK) << -+ TXRING_CTRL_LEN_SHIFT)); -+ } else { -+ /* Assigning rate code; use legacy 6mbps rate. */ -+ pnext_tx_desc->u.rate_code = cpu_to_le16(RATECODE_TYPE_LEGACY + -+ (0 << RATECODE_MCS_SHIFT) + RATECODE_BW_20MHZ); -+ pnext_tx_desc->u.max_retry = 5; -+ -+ ctrl = (((tx_ctrl->tx_que_priority & TXRING_CTRL_QID_MASK) << -+ TXRING_CTRL_QID_SHIFT) | -+ (((tx_skb->len - sizeof(struct pcie_dma_data)) & -+ TXRING_CTRL_LEN_MASK) << TXRING_CTRL_LEN_SHIFT) | -+ (TXRING_CTRL_TAG_MGMT << TXRING_CTRL_TAG_SHIFT)); -+ } -+ -+ dma = pci_map_single(pcie_priv->pdev, tx_skb->data, -+ tx_skb->len, PCI_DMA_TODEVICE); -+ if (pci_dma_mapping_error(pcie_priv->pdev, dma)) { -+ dev_kfree_skb_any(tx_skb); -+ wiphy_err(priv->hw->wiphy, -+ "failed to map pci memory!\n"); -+ spin_unlock_bh(&pcie_priv->tx_desc_lock); -+ return -EIO; -+ } -+ -+ pnext_tx_desc->data = cpu_to_le32(dma); -+ pnext_tx_desc->ctrl = cpu_to_le32(ctrl); -+ pnext_tx_desc->user = cpu_to_le32(pcie_tx_set_skb(priv, tx_skb, dma)); -+ -+ if ((tx_ctrl->flags & TX_CTRL_TYPE_DATA) && -+ (tx_ctrl->rate != 0)) { -+ skb_push(tx_skb, tx_ctrl->hdrlen); -+ skb_get(tx_skb); -+ pcie_tx_prepare_info(priv, tx_ctrl->rate, tx_info); -+ tx_ctrl->flags |= TX_CTRL_TYPE_DATA; -+ ieee80211_tx_status(priv->hw, tx_skb); -+ } -+ -+ if (++tx_send_head_new >= MAX_NUM_TX_DESC) -+ tx_send_head_new = 0; -+ desc->tx_sent_head = tx_send_head_new; -+ wmb(); /*Data Memory Barrier*/ -+ writel(tx_send_head_new, pcie_priv->iobase1 + MACREG_REG_TXSENDHEAD); -+ desc->tx_desc_busy_cnt++; -+ -+ spin_unlock_bh(&pcie_priv->tx_desc_lock); -+ -+ return 0; -+} -+ -+static inline void pcie_tx_check_tcp_ack(struct sk_buff *tx_skb, -+ struct pcie_tx_ctrl_ndp *tx_ctrl) -+{ -+ struct iphdr *iph; -+ struct tcphdr *tcph; -+ -+ if (tx_ctrl->flags & TX_CTRL_TYPE_DATA) { -+ iph = (struct iphdr *)(tx_skb->data + tx_ctrl->hdrlen + 8); -+ tcph = (struct tcphdr *)((u8 *)iph + (iph->ihl * 4)); -+ if ((iph->protocol == IPPROTO_TCP) && -+ (tx_skb->protocol == htons(ETH_P_IP))) { -+ if ((tcph->ack == 1) && (ntohs(iph->tot_len) == -+ (iph->ihl * 4 + tcph->doff * 4))) { -+ if (tcph->syn || tcph->fin) -+ return; -+ -+ tx_ctrl->flags |= TX_CTRL_TCP_ACK; -+ tx_ctrl->tcp_dst_src = ntohs(tcph->source) | -+ (ntohs(tcph->dest) << 16); -+ tx_ctrl->tcp_sn = ntohl(tcph->ack_seq); -+ } -+ } -+ } -+} -+ -+int pcie_tx_init_ndp(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct sk_buff skb; -+ struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(&skb); -+ int rc; -+ -+ if (sizeof(struct pcie_tx_ctrl_ndp) > -+ sizeof(tx_info->status.status_driver_data)) { -+ wiphy_err(hw->wiphy, "driver data is not enough: %d (%d)\n", -+ sizeof(struct pcie_tx_ctrl_ndp), -+ sizeof(tx_info->status.status_driver_data)); -+ return -ENOMEM; -+ } -+ -+ rc = pcie_tx_ring_alloc_ndp(priv); -+ if (rc) { -+ pcie_tx_ring_free_ndp(priv); -+ wiphy_err(hw->wiphy, "allocating TX ring failed\n"); -+ return rc; -+ } -+ -+ rc = pcie_tx_ring_init_ndp(priv); -+ if (rc) { -+ pcie_tx_ring_free_ndp(priv); -+ wiphy_err(hw->wiphy, "initializing TX ring failed\n"); -+ return rc; -+ } -+ -+ return 0; -+} -+ -+void pcie_tx_deinit_ndp(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ pcie_tx_ring_cleanup_ndp(priv); -+ pcie_tx_ring_free_ndp(priv); -+} -+ -+void pcie_tx_skbs_ndp(unsigned long data) -+{ -+ struct ieee80211_hw *hw = (struct ieee80211_hw *)data; -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ int num = SYSADPT_TX_WMM_QUEUES; -+ struct sk_buff *tx_skb; -+ int rc; -+ -+ while (num--) { -+ while (skb_queue_len(&pcie_priv->txq[num]) > 0) { -+ if (pcie_priv->desc_data_ndp.tx_desc_busy_cnt >= -+ (MAX_TX_RING_SEND_SIZE - 1)) { -+ pcie_tx_done_ndp(hw); -+ break; -+ } -+ -+ tx_skb = skb_dequeue(&pcie_priv->txq[num]); -+ -+ rc = pcie_tx_skb_ndp(priv, tx_skb); -+ if (rc) { -+ pcie_tx_done_ndp(hw); -+ if (rc == -EAGAIN) -+ skb_queue_head(&pcie_priv->txq[num], -+ tx_skb); -+ break; -+ } -+ -+ if (++pcie_priv->tx_done_cnt > TXDONE_THRESHOLD) { -+ pcie_tx_done_ndp(hw); -+ pcie_priv->tx_done_cnt = 0; -+ } -+ } -+ -+ if (skb_queue_len(&pcie_priv->txq[num]) < -+ pcie_priv->txq_wake_threshold) { -+ int queue; -+ -+ queue = SYSADPT_TX_WMM_QUEUES - num - 1; -+ if (ieee80211_queue_stopped(hw, queue)) -+ ieee80211_wake_queue(hw, queue); -+ } -+ } -+ -+ pcie_priv->is_tx_schedule = false; -+} -+ -+void pcie_tx_done_ndp(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct pcie_desc_data_ndp *desc = &pcie_priv->desc_data_ndp; -+ u32 tx_done_head, tx_done_tail; -+ struct tx_ring_done *ptx_ring_done; -+ u32 index; -+ struct sk_buff *skb; -+ struct ieee80211_tx_info *tx_info; -+ struct pcie_tx_ctrl_ndp *tx_ctrl; -+ struct pcie_dma_data *dma_data; -+ u16 hdrlen; -+ -+ spin_lock_bh(&pcie_priv->tx_desc_lock); -+ -+ tx_done_head = readl(pcie_priv->iobase1 + -+ MACREG_REG_TXDONEHEAD); -+ tx_done_tail = desc->tx_done_tail & (MAX_TX_RING_DONE_SIZE - 1); -+ tx_done_head &= (MAX_TX_RING_DONE_SIZE - 1); -+ -+ while (tx_done_head != tx_done_tail) { -+ ptx_ring_done = &desc->ptx_ring_done[tx_done_tail]; -+ -+ index = le32_to_cpu(ptx_ring_done->user); -+ ptx_ring_done->user = 0; -+ if (index >= MAX_TX_RING_SEND_SIZE) { -+ wiphy_err(hw->wiphy, -+ "corruption for index of buffer\n"); -+ break; -+ } -+ skb = desc->tx_vbuflist[index]; -+ if (!skb) { -+ wiphy_err(hw->wiphy, -+ "buffer is NULL for tx done ring\n"); -+ break; -+ } -+ pci_unmap_single(pcie_priv->pdev, -+ desc->pphys_tx_buflist[index], -+ skb->len, -+ PCI_DMA_TODEVICE); -+ desc->pphys_tx_buflist[index] = 0; -+ desc->tx_vbuflist[index] = NULL; -+ -+ tx_info = IEEE80211_SKB_CB(skb); -+ tx_ctrl = (struct pcie_tx_ctrl_ndp *) -+ tx_info->status.status_driver_data; -+ -+ if (tx_ctrl->flags & TX_CTRL_TYPE_DATA) { -+ dev_kfree_skb_any(skb); -+ goto bypass_ack; -+ } else { -+ /* Remove H/W dma header */ -+ dma_data = (struct pcie_dma_data *)skb->data; -+ -+ if (ieee80211_is_assoc_resp( -+ dma_data->wh.frame_control) || -+ ieee80211_is_reassoc_resp( -+ dma_data->wh.frame_control)) { -+ dev_kfree_skb_any(skb); -+ goto bypass_ack; -+ } -+ hdrlen = ieee80211_hdrlen( -+ dma_data->wh.frame_control); -+ memmove(dma_data->data - hdrlen, -+ &dma_data->wh, hdrlen); -+ skb_pull(skb, sizeof(*dma_data) - hdrlen); -+ } -+ -+ pcie_tx_prepare_info(priv, 0, tx_info); -+ ieee80211_tx_status(hw, skb); -+ -+bypass_ack: -+ if (++tx_done_tail >= MAX_TX_RING_DONE_SIZE) -+ tx_done_tail = 0; -+ desc->tx_desc_busy_cnt--; -+ } -+ -+ writel(tx_done_tail, pcie_priv->iobase1 + -+ MACREG_REG_TXDONETAIL); -+ desc->tx_done_tail = tx_done_tail; -+ -+ spin_unlock_bh(&pcie_priv->tx_desc_lock); -+} -+ -+void pcie_tx_xmit_ndp(struct ieee80211_hw *hw, -+ struct ieee80211_tx_control *control, -+ struct sk_buff *skb) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct pcie_priv *pcie_priv = priv->hif.priv; -+ struct ieee80211_tx_info *tx_info; -+ struct ieee80211_key_conf *k_conf; -+ struct mwl_vif *mwl_vif; -+ int index; -+ struct ieee80211_sta *sta; -+ struct mwl_sta *sta_info; -+ struct ieee80211_hdr *wh; -+ u8 *da; -+ u16 qos; -+ u8 tid = 0; -+ struct mwl_ampdu_stream *stream = NULL; -+ u16 tx_que_priority; -+ bool mgmtframe = false; -+ struct ieee80211_mgmt *mgmt; -+ bool eapol_frame = false; -+ bool start_ba_session = false; -+ struct pcie_tx_ctrl_ndp *tx_ctrl; -+ -+ tx_info = IEEE80211_SKB_CB(skb); -+ k_conf = tx_info->control.hw_key; -+ mwl_vif = mwl_dev_get_vif(tx_info->control.vif); -+ index = skb_get_queue_mapping(skb); -+ sta = control->sta; -+ sta_info = sta ? mwl_dev_get_sta(sta) : NULL; -+ -+ wh = (struct ieee80211_hdr *)skb->data; -+ -+ if (ieee80211_is_data_qos(wh->frame_control)) -+ qos = le16_to_cpu(*((__le16 *)ieee80211_get_qos_ctl(wh))); -+ else -+ qos = 0xFFFF; -+ -+ if (skb->protocol == cpu_to_be16(ETH_P_PAE)) { -+ index = IEEE80211_AC_VO; -+ eapol_frame = true; -+ } -+ -+ if (ieee80211_is_mgmt(wh->frame_control)) { -+ mgmtframe = true; -+ mgmt = (struct ieee80211_mgmt *)skb->data; -+ } -+ -+ if (mgmtframe) { -+ u16 capab; -+ -+ if (unlikely(ieee80211_is_action(wh->frame_control) && -+ mgmt->u.action.category == WLAN_CATEGORY_BACK && -+ mgmt->u.action.u.addba_req.action_code == -+ WLAN_ACTION_ADDBA_REQ)) { -+ capab = le16_to_cpu(mgmt->u.action.u.addba_req.capab); -+ tid = (capab & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2; -+ index = utils_tid_to_ac(tid); -+ } -+ -+ if (unlikely(ieee80211_is_assoc_req(wh->frame_control))) -+ utils_add_basic_rates(hw->conf.chandef.chan->band, skb); -+ -+ if (ieee80211_is_probe_req(wh->frame_control) || -+ ieee80211_is_probe_resp(wh->frame_control)) -+ tx_que_priority = PROBE_RESPONSE_TXQNUM; -+ else { -+ if (( -+ (mwl_vif->macid == SYSADPT_NUM_OF_AP) && -+ (!ieee80211_has_protected(wh->frame_control) || -+ (ieee80211_has_protected(wh->frame_control) && -+ ieee80211_is_auth(wh->frame_control))) -+ ) || -+ !sta || -+ ieee80211_is_auth(wh->frame_control) || -+ ieee80211_is_assoc_req(wh->frame_control) || -+ ieee80211_is_assoc_resp(wh->frame_control)) -+ tx_que_priority = MGMT_TXQNUM; -+ else { -+ if (is_multicast_ether_addr(wh->addr1) && -+ (mwl_vif->macid != SYSADPT_NUM_OF_AP)) -+ tx_que_priority = mwl_vif->macid * -+ SYSADPT_MAX_TID; -+ else -+ tx_que_priority = SYSADPT_MAX_TID * -+ (sta_info->stnid + -+ QUEUE_STAOFFSET) + 6; -+ } -+ } -+ -+ if (ieee80211_is_assoc_resp(wh->frame_control) || -+ ieee80211_is_reassoc_resp(wh->frame_control)) { -+ struct sk_buff *ack_skb; -+ struct ieee80211_tx_info *ack_info; -+ -+ ack_skb = skb_copy(skb, GFP_ATOMIC); -+ ack_info = IEEE80211_SKB_CB(ack_skb); -+ pcie_tx_prepare_info(priv, 0, ack_info); -+ ieee80211_tx_status(hw, ack_skb); -+ } -+ -+ pcie_tx_encapsulate_frame(priv, skb, k_conf, NULL); -+ } else { -+ tid = qos & 0x7; -+ if (sta && sta->ht_cap.ht_supported && !eapol_frame && -+ qos != 0xFFFF) { -+ pcie_tx_count_packet(sta, tid); -+ spin_lock_bh(&priv->stream_lock); -+ stream = mwl_fwcmd_lookup_stream(hw, sta, tid); -+ if (!stream || -+ stream->state == AMPDU_STREAM_IN_PROGRESS) { -+ wiphy_warn(hw->wiphy, -+ "can't send packet during ADDBA\n"); -+ spin_unlock_bh(&priv->stream_lock); -+ dev_kfree_skb_any(skb); -+ return; -+ } -+ if ((stream->state == AMPDU_NO_STREAM) && -+ mwl_fwcmd_ampdu_allowed(sta, tid)) { -+ stream = mwl_fwcmd_add_stream(hw, sta, tid); -+ if (stream) -+ start_ba_session = true; -+ } -+ spin_unlock_bh(&priv->stream_lock); -+ } -+ -+ da = ieee80211_get_DA(wh); -+ -+ if (is_multicast_ether_addr(da) -+ && (mwl_vif->macid != SYSADPT_NUM_OF_AP)) { -+ -+ tx_que_priority = mwl_vif->macid * SYSADPT_MAX_TID; -+ -+ if (da[ETH_ALEN - 1] == 0xff) -+ tx_que_priority += 7; -+ -+ if (ieee80211_has_a4(wh->frame_control)) { -+ if (sta && sta_info->wds) -+ tx_que_priority = SYSADPT_MAX_TID * -+ (sta_info->stnid + -+ QUEUE_STAOFFSET) + 6; -+ } -+ } else { -+ if (sta) { -+ if (!eapol_frame) -+ tx_que_priority = SYSADPT_MAX_TID * -+ (sta_info->stnid + -+ QUEUE_STAOFFSET) + -+ ((qos == 0xFFFF) ? 0 : tid); -+ else -+ tx_que_priority = SYSADPT_MAX_TID * -+ (sta_info->stnid + -+ QUEUE_STAOFFSET) + -+ ((qos == 0xFFFF) ? 0 : 6); -+ } else -+ tx_que_priority = 0; -+ } -+ } -+ -+ index = SYSADPT_TX_WMM_QUEUES - index - 1; -+ -+ tx_ctrl = (struct pcie_tx_ctrl_ndp *)tx_info->status.status_driver_data; -+ tx_ctrl->tx_que_priority = tx_que_priority; -+ tx_ctrl->hdrlen = ieee80211_hdrlen(wh->frame_control); -+ tx_ctrl->flags = 0; -+ if (!mgmtframe) -+ tx_ctrl->flags |= TX_CTRL_TYPE_DATA; -+ if (eapol_frame) -+ tx_ctrl->flags |= TX_CTRL_EAPOL; -+ tx_ctrl->rate = sta ? sta_info->tx_rate_info : 0; -+ if (ieee80211_is_nullfunc(wh->frame_control) || -+ ieee80211_is_qos_nullfunc(wh->frame_control)) -+ tx_ctrl->rate = 0; -+ pcie_tx_check_tcp_ack(skb, tx_ctrl); -+ -+ if (skb_queue_len(&pcie_priv->txq[index]) > pcie_priv->txq_limit) -+ ieee80211_stop_queue(hw, SYSADPT_TX_WMM_QUEUES - index - 1); -+ -+ skb_queue_tail(&pcie_priv->txq[index], skb); -+ -+ if (!pcie_priv->is_tx_schedule) { -+ tasklet_schedule(&pcie_priv->tx_task); -+ pcie_priv->is_tx_schedule = true; -+ } -+ -+ /* Initiate the ampdu session here */ -+ if (start_ba_session) { -+ spin_lock_bh(&priv->stream_lock); -+ if (mwl_fwcmd_start_stream(hw, stream)) -+ mwl_fwcmd_remove_stream(hw, stream); -+ spin_unlock_bh(&priv->stream_lock); -+ } -+} -diff --git a/drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx_ndp.h b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx_ndp.h -new file mode 100644 -index 000000000000..2ad5f381b9ee ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hif/pcie/tx_ndp.h -@@ -0,0 +1,30 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines transmit related functions for new data path. -+ */ -+ -+#ifndef _TX_NDP_H_ -+#define _TX_NDP_H_ -+ -+int pcie_tx_init_ndp(struct ieee80211_hw *hw); -+void pcie_tx_deinit_ndp(struct ieee80211_hw *hw); -+void pcie_tx_skbs_ndp(unsigned long data); -+void pcie_tx_done_ndp(struct ieee80211_hw *hw); -+void pcie_tx_xmit_ndp(struct ieee80211_hw *hw, -+ struct ieee80211_tx_control *control, -+ struct sk_buff *skb); -+ -+#endif /* _TX_NDP_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/hostapd/700-interoperability-workaround-for-80+80-and-160-MHz-channels b/drivers/net/wireless/marvell/mwlwifi/hostapd/700-interoperability-workaround-for-80+80-and-160-MHz-channels -new file mode 100644 -index 000000000000..c73d02e31977 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hostapd/700-interoperability-workaround-for-80+80-and-160-MHz-channels -@@ -0,0 +1,32 @@ -+diff --git a/src/ap/ieee802_11_vht.c b/src/ap/ieee802_11_vht.c -+index 3236016..e923094 100644 -+--- a/src/ap/ieee802_11_vht.c -++++ b/src/ap/ieee802_11_vht.c -+@@ -82,6 +82,27 @@ u8 * hostapd_eid_vht_operation(struct hostapd_data *hapd, u8 *eid) -+ -+ oper->vht_op_info_chwidth = hapd->iconf->vht_oper_chwidth; -+ -++ if (hapd->iconf->vht_oper_chwidth == 2) { -++ /* -++ * Convert 160 MHz channel width to new style as interop -++ * workaround. -++ */ -++ oper->vht_op_info_chwidth = 1; -++ oper->vht_op_info_chan_center_freq_seg1_idx = -++ oper->vht_op_info_chan_center_freq_seg0_idx; -++ if (hapd->iconf->channel < -++ hapd->iconf->vht_oper_centr_freq_seg0_idx) -++ oper->vht_op_info_chan_center_freq_seg0_idx -= 8; -++ else -++ oper->vht_op_info_chan_center_freq_seg0_idx += 8; -++ } else if (hapd->iconf->vht_oper_chwidth == 3) { -++ /* -++ * Convert 80+80 MHz channel width to new style as interop -++ * workaround. -++ */ -++ oper->vht_op_info_chwidth = 1; -++ } -++ -+ /* VHT Basic MCS set comes from hw */ -+ /* Hard code 1 stream, MCS0-7 is a min Basic VHT MCS rates */ -+ oper->vht_basic_mcs_set = host_to_le16(0xfffc); -diff --git a/drivers/net/wireless/marvell/mwlwifi/hostapd/README b/drivers/net/wireless/marvell/mwlwifi/hostapd/README -new file mode 100644 -index 000000000000..312586e8b5a1 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/hostapd/README -@@ -0,0 +1,26 @@ -+700-interoperability-workaround-for-80+80-and-160-MHz-channels: -+ -+patch for OpenWrt hostapd package 2016-01-15 for following commit -+(move it to package/network/services/hostapd/patches). -+ -+Note: After hostapd package 2016-06-15, this commit is already included. -+ -+commit 03a72eacda5d9a1837a74387081596a0d5466ec1 -+Author: Jouni Malinen -+Date: Thu Dec 17 18:39:19 2015 +0200 -+ -+ VHT: Add an interoperability workaround for 80+80 and 160 MHz channels -+ -+ Number of deployed 80 MHz capable VHT stations that do not support 80+80 -+ and 160 MHz bandwidths seem to misbehave when trying to connect to an AP -+ that advertises 80+80 or 160 MHz channel bandwidth in the VHT Operation -+ element. To avoid such issues with deployed devices, modify the design -+ based on newly proposed IEEE 802.11 standard changes. -+ -+ This allows poorly implemented VHT 80 MHz stations to connect with the -+ AP in 80 MHz mode. 80+80 and 160 MHz capable stations need to support -+ the new workaround mechanism to allow full bandwidth to be used. -+ However, there are more or less no impacted station with 80+80/160 -+ capability deployed. -+ -+ Signed-off-by: Jouni Malinen jouni@qca.qualcomm.com -diff --git a/drivers/net/wireless/marvell/mwlwifi/mac80211.c b/drivers/net/wireless/marvell/mwlwifi/mac80211.c -new file mode 100644 -index 000000000000..725dec0f604b ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/mac80211.c -@@ -0,0 +1,933 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file implements mac80211 related functions. */ -+ -+#include -+ -+#include "sysadpt.h" -+#include "core.h" -+#include "utils.h" -+#include "hif/fwcmd.h" -+#include "hif/hif-ops.h" -+ -+#define MAX_AMPDU_ATTEMPTS 5 -+ -+static const struct ieee80211_rate mwl_rates_24[] = { -+ { .bitrate = 10, .hw_value = 2, }, -+ { .bitrate = 20, .hw_value = 4, }, -+ { .bitrate = 55, .hw_value = 11, }, -+ { .bitrate = 110, .hw_value = 22, }, -+ { .bitrate = 220, .hw_value = 44, }, -+ { .bitrate = 60, .hw_value = 12, }, -+ { .bitrate = 90, .hw_value = 18, }, -+ { .bitrate = 120, .hw_value = 24, }, -+ { .bitrate = 180, .hw_value = 36, }, -+ { .bitrate = 240, .hw_value = 48, }, -+ { .bitrate = 360, .hw_value = 72, }, -+ { .bitrate = 480, .hw_value = 96, }, -+ { .bitrate = 540, .hw_value = 108, }, -+}; -+ -+static const struct ieee80211_rate mwl_rates_50[] = { -+ { .bitrate = 60, .hw_value = 12, }, -+ { .bitrate = 90, .hw_value = 18, }, -+ { .bitrate = 120, .hw_value = 24, }, -+ { .bitrate = 180, .hw_value = 36, }, -+ { .bitrate = 240, .hw_value = 48, }, -+ { .bitrate = 360, .hw_value = 72, }, -+ { .bitrate = 480, .hw_value = 96, }, -+ { .bitrate = 540, .hw_value = 108, }, -+}; -+ -+static void mwl_get_rateinfo(struct mwl_priv *priv, u8 *addr, -+ struct mwl_sta *sta_info) -+{ -+ int table_size = (sizeof(__le32) * 2 * SYSADPT_MAX_RATE_ADAPT_RATES); -+ u8 *rate_table, *rate_idx; -+ u32 rate_info; -+ struct mwl_tx_hist_data *tx_hist_data; -+ int ret, idx; -+ -+ rate_table = kzalloc(table_size, GFP_KERNEL); -+ if (!rate_table) -+ return; -+ -+ ret = mwl_fwcmd_get_ratetable(priv->hw, addr, rate_table, -+ table_size, 0); -+ if (ret) { -+ kfree(rate_table); -+ return; -+ } -+ -+ idx = 0; -+ rate_idx = rate_table; -+ rate_info = le32_to_cpu(*(__le32 *)rate_idx); -+ tx_hist_data = &sta_info->tx_hist.su_rate[0]; -+ while (rate_info) { -+ if (idx < SYSADPT_MAX_RATE_ADAPT_RATES) -+ tx_hist_data[idx].rateinfo = rate_info; -+ idx++; -+ rate_idx += (2 * sizeof(__le32)); -+ rate_info = le32_to_cpu(*(__le32 *)rate_idx); -+ } -+ -+ kfree(rate_table); -+} -+ -+static void mwl_mac80211_tx(struct ieee80211_hw *hw, -+ struct ieee80211_tx_control *control, -+ struct sk_buff *skb) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if (!priv->radio_on) { -+ wiphy_warn(hw->wiphy, -+ "dropped TX frame since radio is disabled\n"); -+ dev_kfree_skb_any(skb); -+ return; -+ } -+ -+ mwl_hif_tx_xmit(hw, control, skb); -+} -+ -+static int mwl_mac80211_start(struct ieee80211_hw *hw) -+{ -+ struct mwl_priv *priv = hw->priv; -+ int rc; -+ -+ /* Enable TX and RX tasklets. */ -+ mwl_hif_enable_data_tasks(hw); -+ -+ /* Enable interrupts */ -+ mwl_hif_irq_enable(hw); -+ -+ rc = mwl_fwcmd_radio_enable(hw); -+ if (rc) -+ goto fwcmd_fail; -+ rc = mwl_fwcmd_set_rate_adapt_mode(hw, 0); -+ if (rc) -+ goto fwcmd_fail; -+ rc = mwl_fwcmd_set_wmm_mode(hw, true); -+ if (rc) -+ goto fwcmd_fail; -+ rc = mwl_fwcmd_ht_guard_interval(hw, GUARD_INTERVAL_AUTO); -+ if (rc) -+ goto fwcmd_fail; -+ rc = mwl_fwcmd_set_dwds_stamode(hw, true); -+ if (rc) -+ goto fwcmd_fail; -+ rc = mwl_fwcmd_set_fw_flush_timer(hw, SYSADPT_AMSDU_FLUSH_TIME); -+ if (rc) -+ goto fwcmd_fail; -+ rc = mwl_fwcmd_set_optimization_level(hw, 1); -+ if (rc) -+ goto fwcmd_fail; -+ if (priv->chip_type == MWL8997) { -+ rc = mwl_fwcmd_config_EDMACCtrl(hw, 0); -+ if (rc) -+ goto fwcmd_fail; -+ } -+ if (priv->chip_type == MWL8964) { -+ rc = mwl_fwcmd_newdp_dmathread_start(hw); -+ if (rc) -+ goto fwcmd_fail; -+ rc = mwl_fwcmd_set_bftype(hw, priv->bf_type); -+ if (rc) -+ goto fwcmd_fail; -+ } -+ -+ ieee80211_wake_queues(hw); -+ return 0; -+ -+fwcmd_fail: -+ mwl_hif_irq_disable(hw); -+ mwl_hif_disable_data_tasks(hw); -+ -+ return rc; -+} -+ -+static void mwl_mac80211_stop(struct ieee80211_hw *hw) -+{ -+ mwl_fwcmd_radio_disable(hw); -+ -+ ieee80211_stop_queues(hw); -+ -+ /* Disable interrupts */ -+ mwl_hif_irq_disable(hw); -+ -+ /* Disable TX and RX tasklets. */ -+ mwl_hif_disable_data_tasks(hw); -+ -+ /* Return all skbs to mac80211 */ -+ mwl_hif_tx_return_pkts(hw); -+} -+ -+static int mwl_mac80211_add_interface(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ u32 macids_supported; -+ int macid; -+ -+ switch (vif->type) { -+ case NL80211_IFTYPE_AP: -+ case NL80211_IFTYPE_MESH_POINT: -+ if (vif->type == NL80211_IFTYPE_MESH_POINT) -+ if (priv->chip_type != MWL8997) -+ return -EPERM; -+ macids_supported = priv->ap_macids_supported; -+ break; -+ case NL80211_IFTYPE_STATION: -+ macids_supported = priv->sta_macids_supported; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ macid = ffs(macids_supported & ~priv->macids_used); -+ -+ if (!macid) { -+ wiphy_warn(hw->wiphy, "no macid can be allocated\n"); -+ return -EBUSY; -+ } -+ macid--; -+ -+ /* Setup driver private area. */ -+ mwl_vif = mwl_dev_get_vif(vif); -+ memset(mwl_vif, 0, sizeof(*mwl_vif)); -+ mwl_vif->type = vif->type; -+ mwl_vif->macid = macid; -+ mwl_vif->seqno = 0; -+ mwl_vif->is_hw_crypto_enabled = false; -+ mwl_vif->beacon_info.valid = false; -+ mwl_vif->set_beacon = false; -+ mwl_vif->basic_rate_idx = 0; -+ mwl_vif->broadcast_ssid = 0xFF; -+ mwl_vif->iv16 = 1; -+ mwl_vif->iv32 = 0; -+ mwl_vif->keyidx = 0; -+ -+ switch (vif->type) { -+ case NL80211_IFTYPE_AP: -+ ether_addr_copy(mwl_vif->bssid, vif->addr); -+ mwl_fwcmd_set_new_stn_add_self(hw, vif); -+ if (priv->chip_type == MWL8964) { -+ /* allow firmware to really set channel */ -+ mwl_fwcmd_bss_start(hw, vif, true); -+ mwl_fwcmd_bss_start(hw, vif, false); -+ } -+ break; -+ case NL80211_IFTYPE_MESH_POINT: -+ ether_addr_copy(mwl_vif->bssid, vif->addr); -+ mwl_fwcmd_set_new_stn_add_self(hw, vif); -+ break; -+ case NL80211_IFTYPE_STATION: -+ ether_addr_copy(mwl_vif->sta_mac, vif->addr); -+ mwl_fwcmd_bss_start(hw, vif, true); -+ mwl_fwcmd_set_infra_mode(hw, vif); -+ mwl_fwcmd_set_mac_addr_client(hw, vif, vif->addr); -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ priv->macids_used |= 1 << mwl_vif->macid; -+ spin_lock_bh(&priv->vif_lock); -+ list_add_tail(&mwl_vif->list, &priv->vif_list); -+ spin_unlock_bh(&priv->vif_lock); -+ -+ return 0; -+} -+ -+static void mwl_mac80211_remove_vif(struct mwl_priv *priv, -+ struct ieee80211_vif *vif) -+{ -+ struct mwl_vif *mwl_vif = mwl_dev_get_vif(vif); -+ -+ if (!priv->macids_used) -+ return; -+ -+ mwl_hif_tx_del_pkts_via_vif(priv->hw, vif); -+ -+ priv->macids_used &= ~(1 << mwl_vif->macid); -+ spin_lock_bh(&priv->vif_lock); -+ list_del(&mwl_vif->list); -+ spin_unlock_bh(&priv->vif_lock); -+} -+ -+static void mwl_mac80211_remove_interface(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ switch (vif->type) { -+ case NL80211_IFTYPE_AP: -+ case NL80211_IFTYPE_MESH_POINT: -+ mwl_fwcmd_set_new_stn_del(hw, vif, vif->addr); -+ break; -+ case NL80211_IFTYPE_STATION: -+ mwl_fwcmd_remove_mac_addr(hw, vif, vif->addr); -+ break; -+ default: -+ break; -+ } -+ -+ mwl_mac80211_remove_vif(priv, vif); -+} -+ -+static int mwl_mac80211_config(struct ieee80211_hw *hw, -+ u32 changed) -+{ -+ struct ieee80211_conf *conf = &hw->conf; -+ int rc; -+ -+ wiphy_debug(hw->wiphy, "change: 0x%x\n", changed); -+ -+ if (conf->flags & IEEE80211_CONF_IDLE) -+ rc = mwl_fwcmd_radio_disable(hw); -+ else -+ rc = mwl_fwcmd_radio_enable(hw); -+ -+ if (rc) -+ goto out; -+ -+ if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { -+ int rate = 0; -+ -+ if (conf->chandef.chan->band == NL80211_BAND_2GHZ) { -+ mwl_fwcmd_set_apmode(hw, AP_MODE_2_4GHZ_11AC_MIXED); -+ mwl_fwcmd_set_linkadapt_cs_mode(hw, -+ LINK_CS_STATE_CONSERV); -+ rate = mwl_rates_24[0].hw_value; -+ } else if (conf->chandef.chan->band == NL80211_BAND_5GHZ) { -+ mwl_fwcmd_set_apmode(hw, AP_MODE_11AC); -+ mwl_fwcmd_set_linkadapt_cs_mode(hw, -+ LINK_CS_STATE_AUTO); -+ rate = mwl_rates_50[0].hw_value; -+ -+ if (conf->radar_enabled) -+ mwl_fwcmd_set_radar_detect(hw, MONITOR_START); -+ else -+ mwl_fwcmd_set_radar_detect(hw, -+ STOP_DETECT_RADAR); -+ } -+ -+ rc = mwl_fwcmd_set_rf_channel(hw, conf); -+ if (rc) -+ goto out; -+ rc = mwl_fwcmd_use_fixed_rate(hw, rate, rate); -+ if (rc) -+ goto out; -+ rc = mwl_fwcmd_max_tx_power(hw, conf, 0); -+ if (rc) -+ goto out; -+ rc = mwl_fwcmd_tx_power(hw, conf, 0); -+ if (rc) -+ goto out; -+ rc = mwl_fwcmd_set_cdd(hw); -+ } -+ -+out: -+ -+ return rc; -+} -+ -+static void mwl_mac80211_bss_info_changed_sta(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ struct ieee80211_bss_conf *info, -+ u32 changed) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ if ((changed & BSS_CHANGED_ERP_SLOT) && (priv->chip_type == MWL8997)) { -+ if (priv->use_short_slot != vif->bss_conf.use_short_slot) { -+ mwl_fwcmd_set_slot_time(hw, -+ vif->bss_conf.use_short_slot); -+ priv->use_short_slot = vif->bss_conf.use_short_slot; -+ } -+ } -+ -+ if (changed & BSS_CHANGED_ERP_PREAMBLE) { -+ if (priv->use_short_preamble != -+ vif->bss_conf.use_short_preamble) { -+ mwl_fwcmd_set_radio_preamble( -+ hw, vif->bss_conf.use_short_preamble); -+ priv->use_short_preamble = -+ vif->bss_conf.use_short_preamble; -+ } -+ } -+ -+ if ((changed & BSS_CHANGED_ASSOC) && vif->bss_conf.assoc) -+ mwl_fwcmd_set_aid(hw, vif, (u8 *)vif->bss_conf.bssid, -+ vif->bss_conf.aid); -+} -+ -+static void mwl_mac80211_bss_info_changed_ap(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ struct ieee80211_bss_conf *info, -+ u32 changed) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_vif *mwl_vif; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ -+ if ((changed & BSS_CHANGED_ERP_SLOT) && (priv->chip_type == MWL8997)) { -+ if (priv->use_short_slot != vif->bss_conf.use_short_slot) { -+ mwl_fwcmd_set_slot_time(hw, -+ vif->bss_conf.use_short_slot); -+ priv->use_short_slot = vif->bss_conf.use_short_slot; -+ } -+ } -+ -+ if (changed & BSS_CHANGED_ERP_PREAMBLE) { -+ if (priv->use_short_preamble != -+ vif->bss_conf.use_short_preamble) { -+ mwl_fwcmd_set_radio_preamble( -+ hw, vif->bss_conf.use_short_preamble); -+ priv->use_short_preamble = -+ vif->bss_conf.use_short_preamble; -+ } -+ } -+ -+ if (changed & BSS_CHANGED_BASIC_RATES) { -+ int idx; -+ int rate; -+ -+ /* Use lowest supported basic rate for multicasts -+ * and management frames (such as probe responses -- -+ * beacons will always go out at 1 Mb/s). -+ */ -+ idx = ffs(vif->bss_conf.basic_rates); -+ if (idx) -+ idx--; -+ if (mwl_vif->basic_rate_idx != idx) { -+ if (hw->conf.chandef.chan->band == NL80211_BAND_2GHZ) -+ rate = mwl_rates_24[idx].hw_value; -+ else -+ rate = mwl_rates_50[idx].hw_value; -+ -+ mwl_fwcmd_use_fixed_rate(hw, rate, rate); -+ mwl_vif->basic_rate_idx = idx; -+ } -+ } -+ -+ if (changed & (BSS_CHANGED_BEACON_INT | BSS_CHANGED_BEACON)) { -+ struct sk_buff *skb; -+ -+ if ((info->ssid[0] != '\0') && -+ (info->ssid_len != 0) && -+ (!info->hidden_ssid)) { -+ if (mwl_vif->broadcast_ssid != true) { -+ mwl_fwcmd_broadcast_ssid_enable(hw, vif, true); -+ mwl_vif->broadcast_ssid = true; -+ } -+ } else { -+ if (mwl_vif->broadcast_ssid != false) { -+ mwl_fwcmd_broadcast_ssid_enable(hw, vif, false); -+ mwl_vif->broadcast_ssid = false; -+ } -+ } -+ -+ if (!mwl_vif->set_beacon) { -+ skb = ieee80211_beacon_get(hw, vif); -+ -+ if (skb) { -+ mwl_fwcmd_set_beacon(hw, vif, skb->data, skb->len); -+ dev_kfree_skb_any(skb); -+ } -+ mwl_vif->set_beacon = true; -+ } -+ } -+ -+ if (changed & BSS_CHANGED_BEACON_ENABLED) -+ mwl_fwcmd_bss_start(hw, vif, info->enable_beacon); -+} -+ -+static void mwl_mac80211_bss_info_changed(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ struct ieee80211_bss_conf *info, -+ u32 changed) -+{ -+ switch (vif->type) { -+ case NL80211_IFTYPE_AP: -+ case NL80211_IFTYPE_MESH_POINT: -+ mwl_mac80211_bss_info_changed_ap(hw, vif, info, changed); -+ break; -+ case NL80211_IFTYPE_STATION: -+ mwl_mac80211_bss_info_changed_sta(hw, vif, info, changed); -+ break; -+ default: -+ break; -+ } -+} -+ -+static void mwl_mac80211_configure_filter(struct ieee80211_hw *hw, -+ unsigned int changed_flags, -+ unsigned int *total_flags, -+ u64 multicast) -+{ -+ /* AP firmware doesn't allow fine-grained control over -+ * the receive filter. -+ */ -+ *total_flags &= FIF_ALLMULTI | FIF_BCN_PRBRESP_PROMISC; -+} -+ -+static int mwl_mac80211_set_key(struct ieee80211_hw *hw, -+ enum set_key_cmd cmd_param, -+ struct ieee80211_vif *vif, -+ struct ieee80211_sta *sta, -+ struct ieee80211_key_conf *key) -+{ -+ struct mwl_vif *mwl_vif; -+ struct mwl_sta *sta_info; -+ int rc = 0; -+ u8 encr_type; -+ u8 *addr; -+ -+ mwl_vif = mwl_dev_get_vif(vif); -+ addr = sta ? sta->addr : vif->addr; -+ -+ if (cmd_param == SET_KEY) { -+ if ((key->cipher == WLAN_CIPHER_SUITE_WEP40) || -+ (key->cipher == WLAN_CIPHER_SUITE_WEP104)) { -+ encr_type = ENCR_TYPE_WEP; -+ } else if (key->cipher == WLAN_CIPHER_SUITE_CCMP) { -+ encr_type = ENCR_TYPE_AES; -+ if ((key->flags & IEEE80211_KEY_FLAG_PAIRWISE) == 0) { -+ if (vif->type != NL80211_IFTYPE_STATION) -+ mwl_vif->keyidx = key->keyidx; -+ } -+ } else if (key->cipher == WLAN_CIPHER_SUITE_TKIP) { -+ encr_type = ENCR_TYPE_TKIP; -+ } else { -+ encr_type = ENCR_TYPE_DISABLE; -+ } -+ -+ rc = mwl_fwcmd_update_encryption_enable(hw, vif, addr, -+ encr_type); -+ if (rc) -+ goto out; -+ rc = mwl_fwcmd_encryption_set_key(hw, vif, addr, key); -+ if (rc) -+ goto out; -+ -+ mwl_vif->is_hw_crypto_enabled = true; -+ if (sta) { -+ sta_info = mwl_dev_get_sta(sta); -+ sta_info->is_key_set = true; -+ } -+ } else { -+ rc = mwl_fwcmd_encryption_remove_key(hw, vif, addr, key); -+ if (rc) -+ goto out; -+ } -+ -+out: -+ -+ return rc; -+} -+ -+static int mwl_mac80211_set_rts_threshold(struct ieee80211_hw *hw, -+ u32 value) -+{ -+ return mwl_fwcmd_set_rts_threshold(hw, value); -+} -+ -+static int mwl_mac80211_sta_add(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ struct ieee80211_sta *sta) -+{ -+ struct mwl_priv *priv = hw->priv; -+ u16 stnid, sta_stnid = 0; -+ struct mwl_vif *mwl_vif; -+ struct wireless_dev *wdev = ieee80211_vif_to_wdev(vif); -+ bool use_4addr = wdev->use_4addr; -+ struct mwl_sta *sta_info; -+ struct ieee80211_key_conf *key; -+ int rc; -+ int i; -+ -+ if (vif->type == NL80211_IFTYPE_STATION) -+ sta->aid = 1; -+ mwl_vif = mwl_dev_get_vif(vif); -+ stnid = utils_assign_stnid(priv, mwl_vif->macid, sta->aid); -+ if (!stnid) -+ return -EPERM; -+ if (vif->type == NL80211_IFTYPE_STATION) { -+ sta_stnid = utils_assign_stnid(priv, mwl_vif->macid, -+ sta->aid + 1); -+ if (!sta_stnid) { -+ utils_free_stnid(priv, stnid); -+ return -EPERM; -+ } -+ ether_addr_copy(mwl_vif->bssid, sta->addr); -+ } -+ sta_info = mwl_dev_get_sta(sta); -+ memset(sta_info, 0, sizeof(*sta_info)); -+ -+ if (vif->type == NL80211_IFTYPE_MESH_POINT) -+ sta_info->is_mesh_node = true; -+ -+ if (sta->ht_cap.ht_supported) { -+ sta_info->is_ampdu_allowed = true; -+ sta_info->is_amsdu_allowed = false; -+ if (sta->ht_cap.cap & IEEE80211_HT_CAP_MAX_AMSDU) -+ sta_info->amsdu_ctrl.cap = MWL_AMSDU_SIZE_8K; -+ else -+ sta_info->amsdu_ctrl.cap = MWL_AMSDU_SIZE_4K; -+ if ((sta->tdls) && (!sta->wme)) -+ sta->wme = true; -+ } -+ sta_info->mwl_vif = mwl_vif; -+ sta_info->stnid = stnid; -+ if (vif->type == NL80211_IFTYPE_STATION) -+ sta_info->sta_stnid = sta_stnid; -+ sta_info->tx_rate_info = utils_get_init_tx_rate(priv, &hw->conf, sta); -+ sta_info->iv16 = 1; -+ sta_info->iv32 = 0; -+ spin_lock_init(&sta_info->amsdu_lock); -+ spin_lock_bh(&priv->sta_lock); -+ list_add_tail(&sta_info->list, &priv->sta_list); -+ spin_unlock_bh(&priv->sta_lock); -+ -+ if (vif->type == NL80211_IFTYPE_STATION) -+ mwl_fwcmd_set_new_stn_del(hw, vif, sta->addr); -+ -+ if (priv->chip_type == MWL8964) { -+ if (use_4addr) { -+ sta_info->wds = true; -+ rc = mwl_fwcmd_set_new_stn_add_sc4(hw, vif, sta, -+ WDS_MODE); -+ } else -+ rc = mwl_fwcmd_set_new_stn_add_sc4(hw, vif, sta, 0); -+ } else -+ rc = mwl_fwcmd_set_new_stn_add(hw, vif, sta); -+ -+ if ((vif->type == NL80211_IFTYPE_STATION) && !use_4addr) -+ mwl_hif_set_sta_id(hw, sta, true, true); -+ else -+ mwl_hif_set_sta_id(hw, sta, false, true); -+ -+ for (i = 0; i < NUM_WEP_KEYS; i++) { -+ key = (struct ieee80211_key_conf *)mwl_vif->wep_key_conf[i].key; -+ -+ if (mwl_vif->wep_key_conf[i].enabled) -+ mwl_mac80211_set_key(hw, SET_KEY, vif, sta, key); -+ } -+ -+ mwl_get_rateinfo(priv, sta->addr, sta_info); -+ -+ return rc; -+} -+ -+static int mwl_mac80211_sta_remove(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ struct ieee80211_sta *sta) -+{ -+ struct mwl_priv *priv = hw->priv; -+ int rc; -+ struct mwl_sta *sta_info = mwl_dev_get_sta(sta); -+ -+ mwl_hif_tx_del_sta_amsdu_pkts(hw, sta); -+ mwl_fwcmd_del_sta_streams(hw, sta); -+ mwl_hif_tx_del_pkts_via_sta(hw, sta); -+ -+ rc = mwl_fwcmd_set_new_stn_del(hw, vif, sta->addr); -+ -+ if (vif->type == NL80211_IFTYPE_STATION) -+ mwl_hif_set_sta_id(hw, sta, true, false); -+ else -+ mwl_hif_set_sta_id(hw, sta, false, false); -+ -+ if (priv->chip_type != MWL8964) -+ utils_free_stnid(priv, sta_info->stnid); -+ if (vif->type == NL80211_IFTYPE_STATION) -+ utils_free_stnid(priv, sta_info->sta_stnid); -+ -+ spin_lock_bh(&priv->sta_lock); -+ list_del(&sta_info->list); -+ spin_unlock_bh(&priv->sta_lock); -+ -+ return rc; -+} -+ -+static int mwl_mac80211_conf_tx(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ u16 queue, -+ const struct ieee80211_tx_queue_params *params) -+{ -+ struct mwl_priv *priv = hw->priv; -+ int rc = 0; -+ -+ if (WARN_ON(queue > SYSADPT_TX_WMM_QUEUES - 1)) -+ return -EINVAL; -+ -+ memcpy(&priv->wmm_params[queue], params, sizeof(*params)); -+ -+ if (!priv->wmm_enabled) { -+ rc = mwl_fwcmd_set_wmm_mode(hw, true); -+ priv->wmm_enabled = true; -+ } -+ -+ if (!rc) { -+ int q = SYSADPT_TX_WMM_QUEUES - 1 - queue; -+ -+ rc = mwl_fwcmd_set_edca_params(hw, q, -+ params->cw_min, params->cw_max, -+ params->aifs, params->txop); -+ } -+ -+ return rc; -+} -+ -+static int mwl_mac80211_get_stats(struct ieee80211_hw *hw, -+ struct ieee80211_low_level_stats *stats) -+{ -+ return mwl_fwcmd_get_stat(hw, stats); -+} -+ -+static int mwl_mac80211_get_survey(struct ieee80211_hw *hw, -+ int idx, -+ struct survey_info *survey) -+{ -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_survey_info *survey_info; -+ -+ if (priv->survey_info_idx) { -+ if (idx >= priv->survey_info_idx) { -+ priv->survey_info_idx = 0; -+ return -ENOENT; -+ } -+ survey_info = &priv->survey_info[idx]; -+ } else { -+ if (idx != 0) -+ return -ENOENT; -+ mwl_fwcmd_get_survey(hw, 0); -+ survey_info = &priv->cur_survey_info; -+ if (!(hw->conf.flags & IEEE80211_CONF_OFFCHANNEL)) -+ survey->filled |= SURVEY_INFO_IN_USE; -+ } -+ -+ survey->channel = &survey_info->channel; -+ survey->filled |= survey_info->filled; -+ survey->time = survey_info->time_period / 1000; -+ survey->time_busy = survey_info->time_busy / 1000; -+ survey->time_tx = survey_info->time_tx / 1000; -+ survey->noise = survey_info->noise; -+ -+ return 0; -+} -+ -+static int mwl_mac80211_ampdu_action(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ struct ieee80211_ampdu_params *params) -+{ -+ int rc = 0; -+ struct mwl_priv *priv = hw->priv; -+ struct mwl_ampdu_stream *stream; -+ enum ieee80211_ampdu_mlme_action action = params->action; -+ struct ieee80211_sta *sta = params->sta; -+ u16 tid = params->tid; -+ u8 buf_size = params->buf_size; -+ u8 *addr = sta->addr; -+ struct mwl_sta *sta_info; -+ -+ sta_info = mwl_dev_get_sta(sta); -+ -+ spin_lock_bh(&priv->stream_lock); -+ -+ stream = mwl_fwcmd_lookup_stream(hw, sta, tid); -+ -+ switch (action) { -+ case IEEE80211_AMPDU_RX_START: -+ if (priv->chip_type == MWL8964) { -+ struct mwl_ampdu_stream tmp; -+ -+ tmp.sta = sta; -+ tmp.tid = tid; -+ spin_unlock_bh(&priv->stream_lock); -+ mwl_fwcmd_create_ba(hw, &tmp, vif, -+ BA_FLAG_DIRECTION_DOWN, -+ buf_size, params->ssn, -+ params->amsdu); -+ spin_lock_bh(&priv->stream_lock); -+ break; -+ } -+ case IEEE80211_AMPDU_RX_STOP: -+ if (priv->chip_type == MWL8964) { -+ struct mwl_ampdu_stream tmp; -+ -+ tmp.sta = sta; -+ tmp.tid = tid; -+ spin_unlock_bh(&priv->stream_lock); -+ mwl_fwcmd_destroy_ba(hw, &tmp, -+ BA_FLAG_DIRECTION_DOWN); -+ spin_lock_bh(&priv->stream_lock); -+ } -+ break; -+ case IEEE80211_AMPDU_TX_START: -+ if (!sta_info->is_ampdu_allowed) { -+ wiphy_warn(hw->wiphy, "ampdu not allowed\n"); -+ rc = -EPERM; -+ break; -+ } -+ -+ if (!stream) { -+ stream = mwl_fwcmd_add_stream(hw, sta, tid); -+ if (!stream) { -+ wiphy_warn(hw->wiphy, "no stream found\n"); -+ rc = -EPERM; -+ break; -+ } -+ } -+ -+ if (priv->chip_type != MWL8964) { -+ spin_unlock_bh(&priv->stream_lock); -+ rc = mwl_fwcmd_check_ba(hw, stream, vif, -+ BA_FLAG_DIRECTION_UP); -+ spin_lock_bh(&priv->stream_lock); -+ if (rc) { -+ mwl_fwcmd_remove_stream(hw, stream); -+ sta_info->check_ba_failed[tid]++; -+ break; -+ } -+ } -+ stream->state = AMPDU_STREAM_IN_PROGRESS; -+ spin_unlock_bh(&priv->stream_lock); -+ rc = mwl_fwcmd_get_seqno(hw, stream, ¶ms->ssn); -+ spin_lock_bh(&priv->stream_lock); -+ if (rc) -+ break; -+ ieee80211_start_tx_ba_cb_irqsafe(vif, addr, tid); -+ break; -+ case IEEE80211_AMPDU_TX_STOP_CONT: -+ case IEEE80211_AMPDU_TX_STOP_FLUSH: -+ case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: -+ if (stream) { -+ if (stream->state == AMPDU_STREAM_ACTIVE) { -+ stream->state = AMPDU_STREAM_IN_PROGRESS; -+ mwl_hif_tx_del_ampdu_pkts(hw, sta, tid); -+ spin_unlock_bh(&priv->stream_lock); -+ mwl_fwcmd_destroy_ba(hw, stream, -+ BA_FLAG_DIRECTION_UP); -+ spin_lock_bh(&priv->stream_lock); -+ sta_info->is_amsdu_allowed = false; -+ } -+ -+ mwl_fwcmd_remove_stream(hw, stream); -+ ieee80211_stop_tx_ba_cb_irqsafe(vif, addr, tid); -+ } else { -+ rc = -EPERM; -+ } -+ break; -+ case IEEE80211_AMPDU_TX_OPERATIONAL: -+ if (stream) { -+ if (WARN_ON(stream->state != -+ AMPDU_STREAM_IN_PROGRESS)) { -+ rc = -EPERM; -+ break; -+ } -+ spin_unlock_bh(&priv->stream_lock); -+ rc = mwl_fwcmd_create_ba(hw, stream, vif, -+ BA_FLAG_DIRECTION_UP, -+ buf_size, params->ssn, -+ params->amsdu); -+ spin_lock_bh(&priv->stream_lock); -+ -+ if (!rc) { -+ stream->state = AMPDU_STREAM_ACTIVE; -+ sta_info->check_ba_failed[tid] = 0; -+ if (priv->tx_amsdu) -+ sta_info->is_amsdu_allowed = -+ params->amsdu; -+ else -+ sta_info->is_amsdu_allowed = false; -+ } else { -+ spin_unlock_bh(&priv->stream_lock); -+ mwl_fwcmd_destroy_ba(hw, stream, -+ BA_FLAG_DIRECTION_UP); -+ spin_lock_bh(&priv->stream_lock); -+ mwl_fwcmd_remove_stream(hw, stream); -+ wiphy_err(hw->wiphy, -+ "ampdu operation error code: %d\n", -+ rc); -+ } -+ } else { -+ rc = -EPERM; -+ } -+ break; -+ default: -+ rc = -ENOTSUPP; -+ break; -+ } -+ -+ spin_unlock_bh(&priv->stream_lock); -+ -+ return rc; -+} -+ -+static int mwl_mac80211_chnl_switch(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ struct ieee80211_channel_switch *ch_switch) -+{ -+ int rc = 0; -+ -+ rc = mwl_fwcmd_set_switch_channel(hw, ch_switch); -+ -+ return rc; -+} -+ -+static void mwl_mac80211_sw_scan_start(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif, -+ const u8 *mac_addr) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ priv->sw_scanning = true; -+ priv->survey_info_idx = 0; -+} -+ -+static void mwl_mac80211_sw_scan_complete(struct ieee80211_hw *hw, -+ struct ieee80211_vif *vif) -+{ -+ struct mwl_priv *priv = hw->priv; -+ -+ priv->sw_scanning = false; -+} -+ -+const struct ieee80211_ops mwl_mac80211_ops = { -+ .tx = mwl_mac80211_tx, -+ .start = mwl_mac80211_start, -+ .stop = mwl_mac80211_stop, -+ .add_interface = mwl_mac80211_add_interface, -+ .remove_interface = mwl_mac80211_remove_interface, -+ .config = mwl_mac80211_config, -+ .bss_info_changed = mwl_mac80211_bss_info_changed, -+ .configure_filter = mwl_mac80211_configure_filter, -+ .set_key = mwl_mac80211_set_key, -+ .set_rts_threshold = mwl_mac80211_set_rts_threshold, -+ .sta_add = mwl_mac80211_sta_add, -+ .sta_remove = mwl_mac80211_sta_remove, -+ .conf_tx = mwl_mac80211_conf_tx, -+ .get_stats = mwl_mac80211_get_stats, -+ .get_survey = mwl_mac80211_get_survey, -+ .ampdu_action = mwl_mac80211_ampdu_action, -+ .pre_channel_switch = mwl_mac80211_chnl_switch, -+ .sw_scan_start = mwl_mac80211_sw_scan_start, -+ .sw_scan_complete = mwl_mac80211_sw_scan_complete, -+}; -diff --git a/drivers/net/wireless/marvell/mwlwifi/mu_mimo.c b/drivers/net/wireless/marvell/mwlwifi/mu_mimo.c -new file mode 100644 -index 000000000000..23c70df6caa8 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/mu_mimo.c -@@ -0,0 +1,20 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file implements MU-MIMO functions. */ -+ -+#include "sysadpt.h" -+#include "core.h" -+#include "mu_mimo.h" -diff --git a/drivers/net/wireless/marvell/mwlwifi/mu_mimo.h b/drivers/net/wireless/marvell/mwlwifi/mu_mimo.h -new file mode 100644 -index 000000000000..24179f404774 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/mu_mimo.h -@@ -0,0 +1,23 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines MU-MIMO functions. */ -+ -+#ifndef _MU_MIMO_H_ -+#define _MU_MIMO_H_ -+ -+ -+ -+#endif /* _MU_MIMO_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/sysadpt.h b/drivers/net/wireless/marvell/mwlwifi/sysadpt.h -new file mode 100644 -index 000000000000..1194e5271870 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/sysadpt.h -@@ -0,0 +1,86 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines system adaptation related information. */ -+ -+#ifndef _SYSADPT_H_ -+#define _SYSADPT_H_ -+ -+#define SYSADPT_MAX_STA 64 -+ -+#define SYSADPT_MAX_STA_SC4 300 -+ -+#define SYSADPT_MAX_NUM_CHANNELS 64 -+ -+#define SYSADPT_MAX_DATA_RATES_G 14 -+ -+#define SYSADPT_MAX_MCS_RATES 24 -+ -+#define SYSADPT_MAX_11AC_RATES 20 -+ -+#define SYSADPT_MAX_RATE_ADAPT_RATES (SYSADPT_MAX_DATA_RATES_G + \ -+ SYSADPT_MAX_MCS_RATES + \ -+ SYSADPT_MAX_11AC_RATES) -+ -+#define SYSADPT_TX_POWER_LEVEL_TOTAL 16 /* SC3 */ -+ -+#define SYSADPT_TX_GRP_PWR_LEVEL_TOTAL 28 /* KF2 */ -+ -+#define SYSADPT_TX_PWR_LEVEL_TOTAL_SC4 32 /* SC4 */ -+ -+#define SYSADPT_TX_WMM_QUEUES 4 -+ -+#define SYSADPT_NUM_OF_CLIENT 1 -+ -+#define SYSADPT_NUM_OF_AP 16 -+ -+#define SYSADPT_NUM_OF_MESH 1 -+ -+#define SYSADPT_TOTAL_TX_QUEUES (SYSADPT_TX_WMM_QUEUES + \ -+ SYSADPT_NUM_OF_AP) -+ -+#define SYSADPT_MAX_AGGR_SIZE 4096 -+ -+#define SYSADPT_AMPDU_PACKET_THRESHOLD 64 -+ -+#define SYSADPT_AMSDU_FW_MAX_SIZE 3300 -+ -+#define SYSADPT_AMSDU_4K_MAX_SIZE SYSADPT_AMSDU_FW_MAX_SIZE -+ -+#define SYSADPT_AMSDU_8K_MAX_SIZE SYSADPT_AMSDU_FW_MAX_SIZE -+ -+#define SYSADPT_AMSDU_ALLOW_SIZE 1600 -+ -+#define SYSADPT_AMSDU_FLUSH_TIME 500 -+ -+#define SYSADPT_AMSDU_PACKET_THRESHOLD 10 -+ -+#define SYSADPT_MAX_TID 8 -+ -+#define SYSADPT_QUIET_PERIOD_DEFAULT 100 -+ -+#define SYSADPT_QUIET_PERIOD_MIN 25 -+ -+#define SYSADPT_QUIET_START_OFFSET 10 -+ -+#define SYSADPT_THERMAL_THROTTLE_MAX 100 -+ -+#define SYSADPT_TIMER_WAKEUP_TIME 10 /* ms */ -+ -+#define SYSADPT_OTP_BUF_SIZE (256*8) /* 258 lines * 8 bytes */ -+ -+#define SYSADPT_TXPWRLMT_CFG_BUF_SIZE (3650) -+ -+#endif /* _SYSADPT_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/thermal.c b/drivers/net/wireless/marvell/mwlwifi/thermal.c -new file mode 100644 -index 000000000000..7c59def51e7f ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/thermal.c -@@ -0,0 +1,182 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file implements thermal framework related functions. */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "sysadpt.h" -+#include "core.h" -+#include "hif/fwcmd.h" -+#include "thermal.h" -+ -+static int -+mwl_thermal_get_max_throttle_state(struct thermal_cooling_device *cdev, -+ unsigned long *state) -+{ -+ *state = SYSADPT_THERMAL_THROTTLE_MAX; -+ -+ return 0; -+} -+ -+static int -+mwl_thermal_get_cur_throttle_state(struct thermal_cooling_device *cdev, -+ unsigned long *state) -+{ -+ struct mwl_priv *priv = cdev->devdata; -+ -+ *state = priv->throttle_state; -+ -+ return 0; -+} -+ -+static int -+mwl_thermal_set_cur_throttle_state(struct thermal_cooling_device *cdev, -+ unsigned long throttle_state) -+{ -+ struct mwl_priv *priv = cdev->devdata; -+ -+ if (throttle_state > SYSADPT_THERMAL_THROTTLE_MAX) { -+ wiphy_warn(priv->hw->wiphy, -+ "throttle state %ld is exceeding the limit %d\n", -+ throttle_state, SYSADPT_THERMAL_THROTTLE_MAX); -+ return -EINVAL; -+ } -+ priv->throttle_state = throttle_state; -+ mwl_thermal_set_throttling(priv); -+ -+ return 0; -+} -+ -+static struct thermal_cooling_device_ops mwl_thermal_ops = { -+ .get_max_state = mwl_thermal_get_max_throttle_state, -+ .get_cur_state = mwl_thermal_get_cur_throttle_state, -+ .set_cur_state = mwl_thermal_set_cur_throttle_state, -+}; -+ -+static ssize_t mwl_thermal_show_temp(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ struct mwl_priv *priv = dev_get_drvdata(dev); -+ int ret, temperature; -+ -+ ret = mwl_fwcmd_get_temp(priv->hw, &priv->temperature); -+ if (ret) { -+ wiphy_warn(priv->hw->wiphy, "failed: can't get temperature\n"); -+ goto out; -+ } -+ -+ temperature = priv->temperature; -+ -+ /* display in millidegree celcius */ -+ ret = snprintf(buf, PAGE_SIZE, "%d\n", temperature * 1000); -+out: -+ return ret; -+} -+ -+static SENSOR_DEVICE_ATTR(temp1_input, 0444, mwl_thermal_show_temp, -+ NULL, 0); -+ -+static struct attribute *mwl_hwmon_attrs[] = { -+ &sensor_dev_attr_temp1_input.dev_attr.attr, -+ NULL, -+}; -+ATTRIBUTE_GROUPS(mwl_hwmon); -+ -+void mwl_thermal_set_throttling(struct mwl_priv *priv) -+{ -+ u32 period, duration, enabled; -+ int ret; -+ -+ period = priv->quiet_period; -+ duration = (period * priv->throttle_state) / 100; -+ enabled = duration ? 1 : 0; -+ -+ ret = mwl_fwcmd_quiet_mode(priv->hw, enabled, period, -+ duration, SYSADPT_QUIET_START_OFFSET); -+ if (ret) { -+ wiphy_warn(priv->hw->wiphy, -+ "failed: period %u duarion %u enabled %u ret %d\n", -+ period, duration, enabled, ret); -+ } -+} -+ -+int mwl_thermal_register(struct mwl_priv *priv) -+{ -+ struct thermal_cooling_device *cdev; -+ struct device *hwmon_dev; -+ int ret; -+ -+ if (priv->chip_type != MWL8897) -+ return 0; -+ -+ cdev = thermal_cooling_device_register("mwlwifi_thermal", priv, -+ &mwl_thermal_ops); -+ if (IS_ERR(cdev)) { -+ wiphy_err(priv->hw->wiphy, -+ "failed to setup thermal device result: %ld\n", -+ PTR_ERR(cdev)); -+ return -EINVAL; -+ } -+ -+ ret = sysfs_create_link(&priv->dev->kobj, &cdev->device.kobj, -+ "cooling_device"); -+ if (ret) { -+ wiphy_err(priv->hw->wiphy, -+ "failed to create cooling device symlink\n"); -+ goto err_cooling_destroy; -+ } -+ -+ priv->cdev = cdev; -+ priv->quiet_period = SYSADPT_QUIET_PERIOD_DEFAULT; -+ -+ if (!IS_ENABLED(CONFIG_HWMON)) -+ return 0; -+ -+ hwmon_dev = -+ devm_hwmon_device_register_with_groups(priv->dev, -+ "mwlwifi_hwmon", priv, -+ mwl_hwmon_groups); -+ if (IS_ERR(hwmon_dev)) { -+ wiphy_err(priv->hw->wiphy, -+ "failed to register hwmon device: %ld\n", -+ PTR_ERR(hwmon_dev)); -+ ret = -EINVAL; -+ goto err_remove_link; -+ } -+ -+ return 0; -+ -+err_remove_link: -+ sysfs_remove_link(&priv->dev->kobj, "cooling_device"); -+err_cooling_destroy: -+ thermal_cooling_device_unregister(cdev); -+ -+ return ret; -+} -+ -+void mwl_thermal_unregister(struct mwl_priv *priv) -+{ -+ if (priv->chip_type != MWL8897) -+ return; -+ -+ sysfs_remove_link(&priv->dev->kobj, "cooling_device"); -+ thermal_cooling_device_unregister(priv->cdev); -+} -diff --git a/drivers/net/wireless/marvell/mwlwifi/thermal.h b/drivers/net/wireless/marvell/mwlwifi/thermal.h -new file mode 100644 -index 000000000000..c7f0ad2b87eb ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/thermal.h -@@ -0,0 +1,42 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines Linux thermal framework related functions. */ -+ -+#ifndef _THERMAL_H_ -+#define _THERMAL_H_ -+ -+#include -+ -+#if IS_ENABLED(CONFIG_THERMAL) -+int mwl_thermal_register(struct mwl_priv *priv); -+void mwl_thermal_unregister(struct mwl_priv *priv); -+void mwl_thermal_set_throttling(struct mwl_priv *priv); -+#else -+static inline int mwl_thermal_register(struct mwl_priv *priv) -+{ -+ return 0; -+} -+ -+static inline void mwl_thermal_unregister(struct mwl_priv *priv) -+{ -+} -+ -+static inline void mwl_thermal_set_throttling(struct mwl_priv *priv) -+{ -+} -+#endif -+ -+#endif /* _THERMAL_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/utils.c b/drivers/net/wireless/marvell/mwlwifi/utils.c -new file mode 100644 -index 000000000000..b73054a3f55e ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/utils.c -@@ -0,0 +1,576 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file implements common utility functions. */ -+ -+#include -+ -+#include "sysadpt.h" -+#include "core.h" -+#include "utils.h" -+ -+static unsigned short phy_rate[][5] = { -+ {2, 13, 15, 27, 30}, /* 0 */ -+ {4, 26, 29, 54, 60}, /* 1 */ -+ {11, 39, 43, 81, 90}, /* 2 */ -+ {22, 52, 58, 108, 120}, /* 3 */ -+ {44, 78, 87, 162, 180}, /* 4 */ -+ {12, 104, 115, 216, 240}, /* 5 */ -+ {18, 117, 130, 243, 270}, /* 6 */ -+ {24, 130, 144, 270, 300}, /* 7 */ -+ {36, 26, 29, 54, 60}, /* 8 */ -+ {48, 52, 58, 108, 120}, /* 9 */ -+ {72, 78, 87, 162, 180}, /* 10 */ -+ {96, 104, 116, 216, 240}, /* 11 */ -+ {108, 156, 173, 324, 360}, /* 12 */ -+ {0, 208, 231, 432, 480}, /* 13 */ -+ {0, 234, 260, 486, 540}, /* 14 */ -+ {0, 260, 289, 540, 600}, /* 15 */ -+ {0, 39, 43, 81, 90}, /* 16 */ -+ {0, 78, 87, 162, 180}, /* 17 */ -+ {0, 117, 130, 243, 270}, /* 18 */ -+ {0, 156, 173, 324, 360}, /* 19 */ -+ {0, 234, 260, 486, 540}, /* 20 */ -+ {0, 312, 347, 648, 720}, /* 21 */ -+ {0, 351, 390, 729, 810}, /* 22 */ -+ {0, 390, 433, 810, 900}, /* 23 */ -+}; -+ -+/* 20Mhz: Nss1_LGI, Nss1_SGI, Nss2_LGI, Nss2_SGI, Nss3_LGI, Nss3_SGI */ -+static unsigned short phy_rate_11ac20M[][6] = { -+ {13, 15, 26, 29, 39, 44}, /* 0 */ -+ {26, 29, 52, 58, 78, 87}, /* 1 */ -+ {39, 44, 78, 87, 117, 130}, /* 2 */ -+ {52, 58, 104, 116, 156, 174}, /* 3 */ -+ {78, 87, 156, 174, 234, 260}, /* 4 */ -+ {104, 116, 208, 231, 312, 347}, /* 5 */ -+ {117, 130, 234, 260, 351, 390}, /* 6 */ -+ {130, 145, 260, 289, 390, 434}, /* 7 */ -+ {156, 174, 312, 347, 468, 520}, /* 8 */ -+ /* Nss 1 and Nss 2 mcs9 not valid */ -+ {2, 2, 2, 2, 520, 578}, /* 9 */ -+}; -+ -+/* 40Mhz: Nss1_LGI, Nss1_SGI, Nss2_LGI, Nss2_SGI, Nss3_LGI, Nss3_SGI */ -+static unsigned short phy_rate_11ac40M[][6] = { -+ {27, 30, 54, 60, 81, 90}, /* 0 */ -+ {54, 60, 108, 120, 162, 180}, /* 1 */ -+ {81, 90, 162, 180, 243, 270}, /* 2 */ -+ {108, 120, 216, 240, 324, 360}, /* 3 */ -+ {162, 180, 324, 360, 486, 540}, /* 4 */ -+ {216, 240, 432, 480, 648, 720}, /* 5 */ -+ {243, 270, 486, 540, 729, 810}, /* 6 */ -+ {270, 300, 540, 600, 810, 900}, /* 7 */ -+ {324, 360, 648, 720, 972, 1080}, /* 8 */ -+ {360, 400, 720, 800, 1080, 1200}, /* 9 */ -+}; -+ -+/* 80Mhz: Nss1_LGI, Nss1_SGI, Nss2_LGI, Nss2_SGI, Nss3_LGI, Nss3_SGI */ -+static unsigned short phy_rate_11ac80M[][6] = { -+ {59, 65, 117, 130, 175, 195}, /* 0 */ -+ {117, 130, 234, 260, 351, 390}, /* 1 */ -+ {175, 195, 351, 390, 527, 585}, /* 2 */ -+ {234, 260, 468, 520, 702, 780}, /* 3 */ -+ {351, 390, 702, 780, 1053, 1170}, /* 4 */ -+ {468, 520, 936, 1040, 1404, 1560}, /* 5 */ -+ {527, 585, 1053, 1170, 2, 2}, /* 6, Nss 3 mcs6 not valid */ -+ {585, 650, 1170, 1300, 1755, 1950}, /* 7 */ -+ {702, 780, 1404, 1560, 2106, 2340}, /* 8 */ -+ {780, 867, 1560, 1733, 2340, 2600}, /* 9 */ -+}; -+ -+/* 160Mhz: Nss1_LGI, Nss1_SGI, Nss2_LGI, Nss2_SGI, Nss3_LGI, Nss3_SGI */ -+static unsigned short phy_rate_11ac160M[][6] = { -+ {117, 130, 234, 260, 351, 390}, /* 0 */ -+ {234, 260, 468, 520, 702, 780}, /* 1 */ -+ {351, 390, 702, 780, 1053, 1170}, /* 2 */ -+ {468, 520, 936, 1040, 1404, 1560}, /* 3 */ -+ {702, 780, 1404, 1560, 2106, 2340}, /* 4 */ -+ {936, 1040, 1872, 2080, 2808, 3120}, /* 5 */ -+ {1053, 1170, 2106, 2340, 3159, 3510}, /* 6 */ -+ {1170, 1300, 2340, 2600, 3510, 3900}, /* 7 */ -+ {1404, 1560, 2808, 3120, 4212, 4680}, /* 8 */ -+ {1560, 1733, 2130, 3467, 4680, 5200}, /* 9 */ -+}; -+ -+int utils_get_phy_rate(u8 format, u8 bandwidth, u8 short_gi, u8 mcs_id) -+{ -+ u8 index = 0; -+ u8 nss_11ac = 0; -+ u8 rate_11ac = 0; -+ -+ if (format == TX_RATE_FORMAT_11N) { -+ index = (bandwidth << 1) | short_gi; -+ index++; -+ } else if (format == TX_RATE_FORMAT_11AC) { -+ rate_11ac = mcs_id & 0xf; /* 11ac, mcs_id[3:0]: rate */ -+ nss_11ac = mcs_id >> 4; /* 11ac, mcs_id[6:4]: nss code */ -+ index = (nss_11ac << 1) | short_gi; -+ } -+ -+ if (format != TX_RATE_FORMAT_11AC) -+ return (phy_rate[mcs_id][index] / 2); -+ -+ if (bandwidth == TX_RATE_BANDWIDTH_20) -+ return (phy_rate_11ac20M[rate_11ac][index] / 2); -+ else if (bandwidth == TX_RATE_BANDWIDTH_40) -+ return (phy_rate_11ac40M[rate_11ac][index] / 2); -+ else if (bandwidth == TX_RATE_BANDWIDTH_80) -+ return (phy_rate_11ac80M[rate_11ac][index] / 2); -+ else -+ return (phy_rate_11ac160M[rate_11ac][index] / 2); -+} -+ -+u8 utils_get_rate_id(u8 rate) -+{ -+ switch (rate) { -+ case 10: /* 1 Mbit/s or 12 Mbit/s */ -+ return 0; -+ case 20: /* 2 Mbit/s */ -+ return 1; -+ case 55: /* 5.5 Mbit/s */ -+ return 2; -+ case 110: /* 11 Mbit/s */ -+ return 3; -+ case 220: /* 22 Mbit/s */ -+ return 4; -+ case 0xb: /* 6 Mbit/s */ -+ return 5; -+ case 0xf: /* 9 Mbit/s */ -+ return 6; -+ case 0xe: /* 18 Mbit/s */ -+ return 8; -+ case 0x9: /* 24 Mbit/s */ -+ return 9; -+ case 0xd: /* 36 Mbit/s */ -+ return 10; -+ case 0x8: /* 48 Mbit/s */ -+ return 11; -+ case 0xc: /* 54 Mbit/s */ -+ return 12; -+ case 0x7: /* 72 Mbit/s */ -+ return 13; -+ } -+ -+ return 0; -+} -+ -+u32 utils_get_init_tx_rate(struct mwl_priv *priv, struct ieee80211_conf *conf, -+ struct ieee80211_sta *sta) -+{ -+ u32 tx_rate; -+ u16 format, nss, bw, rate_mcs; -+ -+ if (sta->vht_cap.vht_supported) -+ format = TX_RATE_FORMAT_11AC; -+ else if (sta->ht_cap.ht_supported) -+ format = TX_RATE_FORMAT_11N; -+ else -+ format = TX_RATE_FORMAT_LEGACY; -+ -+ switch (priv->antenna_tx) { -+ case ANTENNA_TX_1: -+ nss = 1; -+ break; -+ case ANTENNA_TX_2: -+ nss = 2; -+ break; -+ case ANTENNA_TX_3: -+ case ANTENNA_TX_4_AUTO: -+ nss = 3; -+ break; -+ default: -+ nss = sta->rx_nss; -+ break; -+ } -+ if (nss > sta->rx_nss) -+ nss = sta->rx_nss; -+ -+ switch (conf->chandef.width) { -+ case NL80211_CHAN_WIDTH_20_NOHT: -+ case NL80211_CHAN_WIDTH_20: -+ bw = TX_RATE_BANDWIDTH_20; -+ break; -+ case NL80211_CHAN_WIDTH_40: -+ bw = TX_RATE_BANDWIDTH_40; -+ break; -+ case NL80211_CHAN_WIDTH_80: -+ bw = TX_RATE_BANDWIDTH_80; -+ break; -+ case NL80211_CHAN_WIDTH_160: -+ bw = TX_RATE_BANDWIDTH_160; -+ break; -+ default: -+ bw = sta->bandwidth; -+ break; -+ } -+ if (bw > sta->bandwidth) -+ bw = sta->bandwidth; -+ -+ switch (format) { -+ case TX_RATE_FORMAT_LEGACY: -+ rate_mcs = 12; /* ignore 11b */ -+ break; -+ case TX_RATE_FORMAT_11N: -+ rate_mcs = (nss * 8) - 1; -+ break; -+ default: -+ rate_mcs = ((nss - 1) << 4) | 8; -+ break; -+ } -+ -+ tx_rate = (format | (bw << MWL_TX_RATE_BANDWIDTH_SHIFT) | -+ (TX_RATE_INFO_SHORT_GI << MWL_TX_RATE_SHORTGI_SHIFT) | -+ (rate_mcs << MWL_TX_RATE_RATEIDMCS_SHIFT)); -+ -+ return tx_rate; -+} -+ -+struct mwl_vif *utils_find_vif_bss(struct mwl_priv *priv, u8 *bssid) -+{ -+ struct mwl_vif *mwl_vif; -+ -+ spin_lock_bh(&priv->vif_lock); -+ list_for_each_entry(mwl_vif, &priv->vif_list, list) { -+ if (ether_addr_equal(bssid, mwl_vif->bssid)) { -+ spin_unlock_bh(&priv->vif_lock); -+ return mwl_vif; -+ } -+ } -+ spin_unlock_bh(&priv->vif_lock); -+ -+ return NULL; -+} -+ -+struct mwl_sta *utils_find_sta(struct mwl_priv *priv, u8 *addr) -+{ -+ struct mwl_sta *sta_info; -+ struct ieee80211_sta *sta; -+ -+ spin_lock_bh(&priv->sta_lock); -+ list_for_each_entry(sta_info, &priv->sta_list, list) { -+ sta = container_of((void *)sta_info, struct ieee80211_sta, -+ drv_priv); -+ if (ether_addr_equal(addr, sta->addr)) { -+ spin_unlock_bh(&priv->sta_lock); -+ return sta_info; -+ } -+ } -+ spin_unlock_bh(&priv->sta_lock); -+ -+ return NULL; -+} -+ -+struct mwl_sta *utils_find_sta_by_aid(struct mwl_priv *priv, u16 aid) -+{ -+ struct mwl_sta *sta_info; -+ struct ieee80211_sta *sta; -+ -+ spin_lock_bh(&priv->sta_lock); -+ list_for_each_entry(sta_info, &priv->sta_list, list) { -+ sta = container_of((void *)sta_info, struct ieee80211_sta, -+ drv_priv); -+ if (sta->aid == aid) { -+ spin_unlock_bh(&priv->sta_lock); -+ return sta_info; -+ } -+ } -+ spin_unlock_bh(&priv->sta_lock); -+ -+ return NULL; -+} -+ -+struct mwl_sta *utils_find_sta_by_id(struct mwl_priv *priv, u16 stnid) -+{ -+ struct mwl_sta *sta_info; -+ -+ spin_lock_bh(&priv->sta_lock); -+ list_for_each_entry(sta_info, &priv->sta_list, list) { -+ if (sta_info->stnid == stnid) { -+ spin_unlock_bh(&priv->sta_lock); -+ return sta_info; -+ } -+ } -+ spin_unlock_bh(&priv->sta_lock); -+ -+ return NULL; -+} -+ -+void utils_dump_data_info(const char *prefix_str, const void *buf, size_t len) -+{ -+ print_hex_dump(KERN_INFO, prefix_str, DUMP_PREFIX_OFFSET, -+ 16, 1, buf, len, true); -+} -+ -+void utils_dump_data_debug(const char *prefix_str, const void *buf, size_t len) -+{ -+ print_hex_dump(KERN_DEBUG, prefix_str, DUMP_PREFIX_OFFSET, -+ 16, 1, buf, len, true); -+} -+ -+bool utils_is_non_amsdu_packet(const void *packet, bool mac80211) -+{ -+ const u8 *data = packet; -+ struct ieee80211_hdr *wh; -+ __be16 *protocol; -+ struct iphdr *iph; -+ struct udphdr *udph; -+ -+ if (mac80211) { -+ /* mac80211 packet */ -+ wh = (struct ieee80211_hdr *)data; -+ data += ieee80211_hdrlen(wh->frame_control) + 6; -+ protocol = (__be16 *)data; -+ } else { -+ /* mac802.3 packet */ -+ data += (2 * ETH_ALEN); -+ protocol = (__be16 *)data; -+ } -+ -+ if (*protocol == cpu_to_be16(ETH_P_PAE)) -+ return true; -+ -+ if (*protocol == htons(ETH_P_ARP)) -+ return true; -+ -+ if (*protocol == htons(ETH_P_IP)) { -+ data += sizeof(__be16); -+ iph = (struct iphdr *)data; -+ if (iph->protocol == IPPROTO_ICMP) -+ return true; -+ if (iph->protocol == IPPROTO_UDP) { -+ data += (iph->ihl * 4); -+ udph = (struct udphdr *)data; -+ if (((udph->source == htons(68)) && -+ (udph->dest == htons(67))) || -+ ((udph->source == htons(67)) && -+ (udph->dest == htons(68)))) -+ return true; -+ } -+ } -+ -+ return false; -+} -+ -+bool utils_is_arp(const void *packet, bool mac80211, u16 *arp_op) -+{ -+ const u8 *data = packet; -+ struct ieee80211_hdr *wh; -+ __be16 *protocol; -+ struct arphdr *arph; -+ -+ if (mac80211) { -+ /* mac80211 packet */ -+ wh = (struct ieee80211_hdr *)data; -+ data += ieee80211_hdrlen(wh->frame_control) + 6; -+ protocol = (__be16 *)data; -+ } else { -+ /* mac802.3 packet */ -+ data += (2 * ETH_ALEN); -+ protocol = (__be16 *)data; -+ } -+ -+ if (*protocol == htons(ETH_P_ARP)) { -+ data += sizeof(__be16); -+ arph = (struct arphdr *)data; -+ *arp_op = ntohs(arph->ar_op); -+ return true; -+ } -+ -+ return false; -+} -+ -+bool utils_is_icmp_echo(const void *packet, bool mac80211, u8 *type) -+{ -+ const u8 *data = packet; -+ struct ieee80211_hdr *wh; -+ __be16 *protocol; -+ struct iphdr *iph; -+ struct icmphdr *icmph; -+ -+ if (mac80211) { -+ /* mac80211 packet */ -+ wh = (struct ieee80211_hdr *)data; -+ data += ieee80211_hdrlen(wh->frame_control) + 6; -+ protocol = (__be16 *)data; -+ } else { -+ /* mac802.3 packet */ -+ data += (2 * ETH_ALEN); -+ protocol = (__be16 *)data; -+ } -+ -+ if (*protocol == htons(ETH_P_IP)) { -+ data += sizeof(__be16); -+ iph = (struct iphdr *)data; -+ if (iph->protocol == IPPROTO_ICMP) { -+ data += (iph->ihl * 4); -+ icmph = (struct icmphdr *)data; -+ *type = icmph->type; -+ return true; -+ } -+ } -+ -+ return false; -+} -+ -+bool utils_is_dhcp(const void *packet, bool mac80211, u8 *op, u8 *dhcp_client) -+{ -+ const u8 *data = packet; -+ struct ieee80211_hdr *wh; -+ __be16 *protocol; -+ struct iphdr *iph; -+ struct udphdr *udph; -+ -+ if (mac80211) { -+ /* mac80211 packet */ -+ wh = (struct ieee80211_hdr *)data; -+ data += ieee80211_hdrlen(wh->frame_control) + 6; -+ protocol = (__be16 *)data; -+ } else { -+ /* mac802.3 packet */ -+ data += (2 * ETH_ALEN); -+ protocol = (__be16 *)data; -+ } -+ -+ if (*protocol == htons(ETH_P_IP)) { -+ data += sizeof(__be16); -+ iph = (struct iphdr *)data; -+ if (iph->protocol == IPPROTO_UDP) { -+ data += (iph->ihl * 4); -+ udph = (struct udphdr *)data; -+ if (((udph->source == htons(68)) && -+ (udph->dest == htons(67))) || -+ ((udph->source == htons(67)) && -+ (udph->dest == htons(68)))) { -+ data += sizeof(struct udphdr); -+ *op = *data; -+ ether_addr_copy(dhcp_client, data + 28); -+ return true; -+ } -+ } -+ } -+ -+ return false; -+} -+ -+void utils_dump_arp(const void *packet, bool mac80211, size_t len) -+{ -+ const u8 *data = packet; -+ struct ieee80211_hdr *wh; -+ __be16 *protocol; -+ struct arphdr *arph; -+ -+ if (mac80211) { -+ /* mac80211 packet */ -+ wh = (struct ieee80211_hdr *)data; -+ data += ieee80211_hdrlen(wh->frame_control) + 6; -+ protocol = (__be16 *)data; -+ } else { -+ /* mac802.3 packet */ -+ data += (2 * ETH_ALEN); -+ protocol = (__be16 *)data; -+ } -+ -+ if (*protocol == htons(ETH_P_ARP)) { -+ data += sizeof(__be16); -+ arph = (struct arphdr *)data; -+ if (arph->ar_op == htons(ARPOP_REQUEST)) -+ utils_dump_data_info("ARP REQUEST: ", packet, len); -+ else if (arph->ar_op == htons(ARPOP_REPLY)) -+ utils_dump_data_info("ARP REPLY: ", packet, len); -+ } -+} -+ -+void utils_dump_icmp_echo(const void *packet, bool mac80211, size_t len) -+{ -+ const u8 *data = packet; -+ struct ieee80211_hdr *wh; -+ __be16 *protocol; -+ struct iphdr *iph; -+ struct icmphdr *icmph; -+ -+ if (mac80211) { -+ /* mac80211 packet */ -+ wh = (struct ieee80211_hdr *)data; -+ data += ieee80211_hdrlen(wh->frame_control) + 6; -+ protocol = (__be16 *)data; -+ } else { -+ /* mac802.3 packet */ -+ data += (2 * ETH_ALEN); -+ protocol = (__be16 *)data; -+ } -+ -+ if (*protocol == htons(ETH_P_IP)) { -+ data += sizeof(__be16); -+ iph = (struct iphdr *)data; -+ if (iph->protocol == IPPROTO_ICMP) { -+ data += (iph->ihl * 4); -+ icmph = (struct icmphdr *)data; -+ if (icmph->type == ICMP_ECHO) -+ utils_dump_data_info("ECHO REQUEST: ", -+ packet, len); -+ else if (icmph->type == ICMP_ECHOREPLY) -+ utils_dump_data_info("ECHO REPLY: ", -+ packet, len); -+ } -+ } -+} -+ -+void utils_dump_dhcp(const void *packet, bool mac80211, size_t len) -+{ -+ const u8 *data = packet; -+ struct ieee80211_hdr *wh; -+ __be16 *protocol; -+ struct iphdr *iph; -+ struct udphdr *udph; -+ const char *dhcp_op[8] = { -+ "DHCPDISCOVER", -+ "DHCPOFFER", -+ "DHCPREQUEST", -+ "DHCPDECLINE", -+ "DHCPACK", -+ "DHCPNAK", -+ "DHCPRELEASE", -+ "DHCPINFORM" -+ }; -+ -+ if (mac80211) { -+ /* mac80211 packet */ -+ wh = (struct ieee80211_hdr *)data; -+ data += ieee80211_hdrlen(wh->frame_control) + 6; -+ protocol = (__be16 *)data; -+ } else { -+ /* mac802.3 packet */ -+ data += (2 * ETH_ALEN); -+ protocol = (__be16 *)data; -+ } -+ -+ if (*protocol == htons(ETH_P_IP)) { -+ data += sizeof(__be16); -+ iph = (struct iphdr *)data; -+ if (iph->protocol == IPPROTO_UDP) { -+ data += (iph->ihl * 4); -+ udph = (struct udphdr *)data; -+ if (((udph->source == htons(68)) && -+ (udph->dest == htons(67))) || -+ ((udph->source == htons(67)) && -+ (udph->dest == htons(68)))) { -+ data += sizeof(struct udphdr); -+ utils_dump_data_info(dhcp_op[*data - 1], -+ packet, len); -+ } -+ } -+ } -+} -diff --git a/drivers/net/wireless/marvell/mwlwifi/utils.h b/drivers/net/wireless/marvell/mwlwifi/utils.h -new file mode 100644 -index 000000000000..4a292e990412 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/utils.h -@@ -0,0 +1,158 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines common utility functions. */ -+ -+#ifndef _UTILS_H_ -+#define _UTILS_H_ -+ -+#include -+#include -+#include -+#include -+ -+/* DHCP message types */ -+#define DHCPDISCOVER 1 -+#define DHCPOFFER 2 -+#define DHCPREQUEST 3 -+#define DHCPDECLINE 4 -+#define DHCPACK 5 -+#define DHCPNAK 6 -+#define DHCPRELEASE 7 -+#define DHCPINFORM 8 -+ -+static inline int utils_tid_to_ac(u8 tid) -+{ -+ switch (tid) { -+ case 0: -+ case 3: -+ return IEEE80211_AC_BE; -+ case 1: -+ case 2: -+ return IEEE80211_AC_BK; -+ case 4: -+ case 5: -+ return IEEE80211_AC_VI; -+ case 6: -+ case 7: -+ return IEEE80211_AC_VO; -+ default: -+ break; -+ } -+ -+ return -1; -+} -+ -+static inline void utils_add_basic_rates(int band, struct sk_buff *skb) -+{ -+ struct ieee80211_mgmt *mgmt; -+ int len; -+ u8 *pos; -+ -+ mgmt = (struct ieee80211_mgmt *)skb->data; -+ len = skb->len - ieee80211_hdrlen(mgmt->frame_control); -+ len -= 4; -+ pos = (u8 *)cfg80211_find_ie(WLAN_EID_SUPP_RATES, -+ mgmt->u.assoc_req.variable, -+ len); -+ if (pos) { -+ pos++; -+ len = *pos++; -+ while (len) { -+ if (band == NL80211_BAND_2GHZ) { -+ if ((*pos == 2) || (*pos == 4) || -+ (*pos == 11) || (*pos == 22)) -+ *pos |= 0x80; -+ } else { -+ if ((*pos == 12) || (*pos == 24) || -+ (*pos == 48)) -+ *pos |= 0x80; -+ } -+ pos++; -+ len--; -+ } -+ } -+} -+ -+static inline int utils_assign_stnid(struct mwl_priv *priv, int macid, u16 aid) -+{ -+ int stnid; -+ int i; -+ -+ spin_lock_bh(&priv->stnid_lock); -+ stnid = priv->available_stnid; -+ if (stnid >= priv->stnid_num) { -+ spin_unlock_bh(&priv->stnid_lock); -+ return 0; -+ } -+ priv->stnid[stnid].macid = macid; -+ priv->stnid[stnid].aid = aid; -+ stnid++; -+ for (i = stnid; i < priv->stnid_num; i++) { -+ if (!priv->stnid[i].aid) -+ break; -+ } -+ priv->available_stnid = i; -+ spin_unlock_bh(&priv->stnid_lock); -+ return stnid; -+} -+ -+static inline void utils_free_stnid(struct mwl_priv *priv, u16 stnid) -+{ -+ spin_lock_bh(&priv->stnid_lock); -+ if (stnid && (stnid <= priv->stnid_num)) { -+ stnid--; -+ priv->stnid[stnid].macid = 0; -+ priv->stnid[stnid].aid = 0; -+ if (priv->available_stnid > stnid) -+ priv->available_stnid = stnid; -+ } -+ spin_unlock_bh(&priv->stnid_lock); -+} -+ -+int utils_get_phy_rate(u8 format, u8 bandwidth, u8 short_gi, u8 mcs_id); -+ -+u8 utils_get_rate_id(u8 rate); -+ -+u32 utils_get_init_tx_rate(struct mwl_priv *priv, struct ieee80211_conf *conf, -+ struct ieee80211_sta *sta); -+ -+struct mwl_vif *utils_find_vif_bss(struct mwl_priv *priv, u8 *bssid); -+ -+struct mwl_sta *utils_find_sta(struct mwl_priv *priv, u8 *addr); -+ -+struct mwl_sta *utils_find_sta_by_aid(struct mwl_priv *priv, u16 aid); -+ -+struct mwl_sta *utils_find_sta_by_id(struct mwl_priv *priv, u16 stnid); -+ -+void utils_dump_data_info(const char *prefix_str, const void *buf, size_t len); -+ -+void utils_dump_data_debug(const char *prefix_str, const void *buf, size_t len); -+ -+bool utils_is_non_amsdu_packet(const void *packet, bool mac80211); -+ -+bool utils_is_arp(const void *packet, bool mac80211, u16 *arp_op); -+ -+bool utils_is_icmp_echo(const void *packet, bool mac80211, u8 *type); -+ -+bool utils_is_dhcp(const void *packet, bool mac80211, u8 *op, u8 *dhcp_client); -+ -+void utils_dump_arp(const void *packet, bool mac80211, size_t len); -+ -+void utils_dump_icmp_echo(const void *packet, bool mac80211, size_t len); -+ -+void utils_dump_dhcp(const void *packet, bool mac80211, size_t len); -+ -+#endif /* _UTILS_H_ */ -diff --git a/drivers/net/wireless/marvell/mwlwifi/vendor_cmd.c b/drivers/net/wireless/marvell/mwlwifi/vendor_cmd.c -new file mode 100644 -index 000000000000..3e26fc42c225 ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/vendor_cmd.c -@@ -0,0 +1,136 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file implements vendor spcific functions. */ -+ -+#include -+#include -+#include -+ -+#include "sysadpt.h" -+#include "core.h" -+#include "utils.h" -+#include "hif/fwcmd.h" -+#include "vendor_cmd.h" -+ -+static const struct nla_policy mwl_vendor_attr_policy[NUM_MWL_VENDOR_ATTR] = { -+ [MWL_VENDOR_ATTR_BF_TYPE] = { .type = NLA_U8 }, -+}; -+ -+static int mwl_vendor_cmd_set_bf_type(struct wiphy *wiphy, -+ struct wireless_dev *wdev, -+ const void *data, int data_len) -+{ -+ struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); -+ struct mwl_priv *priv = hw->priv; -+ struct nlattr *tb[NUM_MWL_VENDOR_ATTR]; -+ int rc; -+ u8 val; -+ -+ if (priv->chip_type != MWL8964) -+ return -EPERM; -+ -+ rc = nla_parse(tb, MWL_VENDOR_ATTR_MAX, data, data_len, -+ mwl_vendor_attr_policy -+#if (defined(LINUX_BACKPORT) || (LINUX_VERSION_CODE >=KERNEL_VERSION(4,12,0))) -+ , NULL -+#endif -+ ); -+ if (rc) -+ return rc; -+ -+ if (!tb[MWL_VENDOR_ATTR_BF_TYPE]) -+ return -EINVAL; -+ -+ val = nla_get_u8(tb[MWL_VENDOR_ATTR_BF_TYPE]); -+ if ((val < TXBF_MODE_OFF) || (val > TXBF_MODE_BFMER_AUTO)) -+ return -EINVAL; -+ wiphy_debug(wiphy, "set bf_type: 0x%x\n", val); -+ -+ rc = mwl_fwcmd_set_bftype(hw, val); -+ if (!rc) -+ priv->bf_type = val; -+ -+ return rc; -+} -+ -+static int mwl_vendor_cmd_get_bf_type(struct wiphy *wiphy, -+ struct wireless_dev *wdev, -+ const void *data, int data_len) -+{ -+ struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); -+ struct mwl_priv *priv = hw->priv; -+ struct sk_buff *skb; -+ -+ if (priv->chip_type != MWL8964) -+ return -EPERM; -+ -+ skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, 8); -+ if (!skb) -+ return -ENOMEM; -+ -+ nla_put_u8(skb, MWL_VENDOR_ATTR_BF_TYPE, priv->bf_type); -+ -+ return cfg80211_vendor_cmd_reply(skb); -+} -+ -+static const struct wiphy_vendor_command mwl_vendor_commands[] = { -+ { -+ .info = { .vendor_id = MRVL_OUI, -+ .subcmd = MWL_VENDOR_CMD_SET_BF_TYPE}, -+ .flags = WIPHY_VENDOR_CMD_NEED_NETDEV, -+ .doit = mwl_vendor_cmd_set_bf_type, -+ }, -+ { -+ .info = { .vendor_id = MRVL_OUI, -+ .subcmd = MWL_VENDOR_CMD_GET_BF_TYPE}, -+ .flags = WIPHY_VENDOR_CMD_NEED_NETDEV, -+ .doit = mwl_vendor_cmd_get_bf_type, -+ } -+}; -+ -+static const struct nl80211_vendor_cmd_info mwl_vendor_events[] = { -+ { -+ .vendor_id = MRVL_OUI, -+ .subcmd = MWL_VENDOR_EVENT_DRIVER_READY, -+ }, -+ { -+ .vendor_id = MRVL_OUI, -+ .subcmd = MWL_VENDOR_EVENT_DRIVER_START_REMOVE, -+ }, -+ { -+ .vendor_id = MRVL_OUI, -+ .subcmd = MWL_VENDOR_EVENT_CMD_TIMEOUT, -+ } -+}; -+ -+void vendor_cmd_register(struct wiphy *wiphy) -+{ -+ wiphy->vendor_commands = mwl_vendor_commands; -+ wiphy->n_vendor_commands = ARRAY_SIZE(mwl_vendor_commands); -+ wiphy->vendor_events = mwl_vendor_events; -+ wiphy->n_vendor_events = ARRAY_SIZE(mwl_vendor_events); -+} -+ -+void vendor_cmd_basic_event(struct wiphy *wiphy, int event_idx) -+{ -+ struct sk_buff *skb; -+ -+ skb = cfg80211_vendor_event_alloc(wiphy, NULL, 0, -+ event_idx, GFP_KERNEL); -+ -+ if (skb) -+ cfg80211_vendor_event(skb, GFP_KERNEL); -+} -diff --git a/drivers/net/wireless/marvell/mwlwifi/vendor_cmd.h b/drivers/net/wireless/marvell/mwlwifi/vendor_cmd.h -new file mode 100644 -index 000000000000..b6fdf70c22fb ---- /dev/null -+++ b/drivers/net/wireless/marvell/mwlwifi/vendor_cmd.h -@@ -0,0 +1,60 @@ -+/* -+ * Copyright (C) 2006-2018, Marvell International Ltd. -+ * -+ * This software file (the "File") is distributed by Marvell International -+ * Ltd. under the terms of the GNU General Public License Version 2, June 1991 -+ * (the "License"). You may use, redistribute and/or modify this File in -+ * accordance with the terms and conditions of the License, a copy of which -+ * is available by writing to the Free Software Foundation, Inc. -+ * -+ * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE -+ * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE -+ * ARE EXPRESSLY DISCLAIMED. The License provides additional details about -+ * this warranty disclaimer. -+ */ -+ -+/* Description: This file defines vendor constants and register function. */ -+ -+#ifndef _VENDOR_CMD_H_ -+#define _VENDOR_CMD_H_ -+ -+#ifdef __KERNEL__ -+void vendor_cmd_register(struct wiphy *wiphy); -+void vendor_cmd_basic_event(struct wiphy *wiphy, int event_idx); -+#endif -+ -+#define MRVL_OUI 0x005043 -+ -+enum mwl_vendor_commands { -+ MWL_VENDOR_CMD_SET_BF_TYPE, -+ MWL_VENDOR_CMD_GET_BF_TYPE, -+ -+ /* add commands here, update the command in vendor_cmd.c */ -+ -+ __MWL_VENDOR_CMD_AFTER_LAST, -+ NUM_MWL_VENDOR_CMD = __MWL_VENDOR_CMD_AFTER_LAST, -+ MWL_VENDOR_CMD_MAX = __MWL_VENDOR_CMD_AFTER_LAST - 1 -+}; -+ -+enum mwl_vendor_attributes { -+ MWL_VENDOR_ATTR_NOT_USE, -+ MWL_VENDOR_ATTR_BF_TYPE, -+ -+ /* add attributes here, update the policy in vendor_cmd.c */ -+ -+ __MWL_VENDOR_ATTR_AFTER_LAST, -+ NUM_MWL_VENDOR_ATTR = __MWL_VENDOR_ATTR_AFTER_LAST, -+ MWL_VENDOR_ATTR_MAX = __MWL_VENDOR_ATTR_AFTER_LAST - 1 -+}; -+ -+enum mwl_vendor_events { -+ MWL_VENDOR_EVENT_DRIVER_READY, -+ MWL_VENDOR_EVENT_DRIVER_START_REMOVE, -+ MWL_VENDOR_EVENT_CMD_TIMEOUT, -+ -+ __MWL_VENDOR_EVENT_AFTER_LAST, -+ NUM_MWL_VENDOR_EVENT = __MWL_VENDOR_EVENT_AFTER_LAST, -+ MWL_VENDOR_EVENT_MAX = __MWL_VENDOR_EVENT_AFTER_LAST - 1 -+}; -+ -+#endif /* _VENDOR_CMD_H_ */ --- -2.23.0 - diff --git a/patches/5.2/0012-surfacebook2-dgpu.patch b/patches/5.2/0012-surfacebook2-dgpu.patch deleted file mode 100644 index 3dc07e2c1..000000000 --- a/patches/5.2/0012-surfacebook2-dgpu.patch +++ /dev/null @@ -1,359 +0,0 @@ -From 6d1be33af986a10d63cf4c4dbd6a2cd7532d8bde Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Fri, 26 Jul 2019 04:47:27 +0200 -Subject: [PATCH 12/12] surfacebook2-dgpu - ---- - drivers/platform/x86/Kconfig | 9 + - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/surfacebook2_dgpu_hps.c | 306 +++++++++++++++++++ - 3 files changed, 316 insertions(+) - create mode 100644 drivers/platform/x86/surfacebook2_dgpu_hps.c - -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 04421fe566ba..cb0a53da4de1 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -484,6 +484,15 @@ config SURFACE3_WMI - To compile this driver as a module, choose M here: the module will - be called surface3-wmi. - -+config SURFACE_BOOK2_DGPU_HPS -+ tristate "Surface Book 2 dGPU Hot-Plug System Driver" -+ depends on ACPI -+ ---help--- -+ This is an experimetnal driver to control the power-state of the -+ Surface Book 2 dGPU. -+ -+ If you have a Surface Book 2, say Y or M here. -+ - config THINKPAD_ACPI - tristate "ThinkPad ACPI Laptop Extras" - depends on ACPI -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 58b07217c3cf..d18d3dcc5749 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -49,6 +49,7 @@ obj-$(CONFIG_ACPI_WMI) += wmi.o - obj-$(CONFIG_MSI_WMI) += msi-wmi.o - obj-$(CONFIG_PEAQ_WMI) += peaq-wmi.o - obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o -+obj-$(CONFIG_SURFACE_BOOK2_DGPU_HPS) += surfacebook2_dgpu_hps.o - obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o - obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o - obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o -diff --git a/drivers/platform/x86/surfacebook2_dgpu_hps.c b/drivers/platform/x86/surfacebook2_dgpu_hps.c -new file mode 100644 -index 000000000000..7639fb0029d8 ---- /dev/null -+++ b/drivers/platform/x86/surfacebook2_dgpu_hps.c -@@ -0,0 +1,306 @@ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+ -+#define SB2_SHPS_DSM_REVISION 1 -+#define SB2_SHPS_DSM_GPU_STATE 0x05 -+ -+static const guid_t SB2_SHPS_DSM_UUID = -+ GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, -+ 0x32, 0x0e, 0x10, 0x36, 0x0a); -+ -+#define SB2_PARAM_PERM (S_IRUGO | S_IWUSR) -+ -+ -+static const struct acpi_gpio_params gpio_base_presence_int = { 0, 0, false }; -+static const struct acpi_gpio_params gpio_base_presence = { 1, 0, false }; -+static const struct acpi_gpio_params gpio_dgpu_power_int = { 2, 0, false }; -+static const struct acpi_gpio_params gpio_dgpu_power = { 3, 0, false }; -+static const struct acpi_gpio_params gpio_dgpu_presence_int = { 4, 0, false }; -+static const struct acpi_gpio_params gpio_dgpu_presence = { 5, 0, false }; -+ -+static const struct acpi_gpio_mapping sb2_mshw0153_acpi_gpios[] = { -+ { "base_presence-int-gpio", &gpio_base_presence_int, 1 }, -+ { "base_presence-gpio", &gpio_base_presence, 1 }, -+ { "dgpu_power-int-gpio", &gpio_dgpu_power_int, 1 }, -+ { "dgpu_power-gpio", &gpio_dgpu_power, 1 }, -+ { "dgpu_presence-int-gpio", &gpio_dgpu_presence_int, 1 }, -+ { "dgpu_presence-gpio", &gpio_dgpu_presence, 1 }, -+ { }, -+}; -+ -+ -+enum sb2_dgpu_power { -+ SB2_DGPU_POWER_OFF = 0, -+ SB2_DGPU_POWER_ON = 1, -+ -+ __SB2_DGPU_POWER__START = 0, -+ __SB2_DGPU_POWER__END = 1, -+}; -+ -+enum sb2_param_dgpu_power { -+ SB2_PARAM_DGPU_POWER_OFF = SB2_DGPU_POWER_OFF, -+ SB2_PARAM_DGPU_POWER_ON = SB2_DGPU_POWER_ON, -+ SB2_PARAM_DGPU_POWER_AS_IS = 2, -+ -+ __SB2_PARAM_DGPU_POWER__START = 0, -+ __SB2_PARAM_DGPU_POWER__END = 2, -+}; -+ -+static const char* sb2_dgpu_power_str(enum sb2_dgpu_power power) { -+ if (power == SB2_DGPU_POWER_OFF) { -+ return "off"; -+ } else if (power == SB2_DGPU_POWER_ON) { -+ return "on"; -+ } else { -+ return ""; -+ } -+} -+ -+ -+struct sb2_shps_driver_data { -+ struct mutex dgpu_power_lock; -+ enum sb2_dgpu_power dgpu_power; -+}; -+ -+ -+static int __sb2_shps_dgpu_set_power(struct platform_device *pdev, enum sb2_dgpu_power power) -+{ -+ struct sb2_shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ acpi_handle handle = ACPI_HANDLE(&pdev->dev); -+ union acpi_object *result; -+ union acpi_object param; -+ -+ param.type = ACPI_TYPE_INTEGER; -+ param.integer.value = power == SB2_DGPU_POWER_ON; -+ -+ result = acpi_evaluate_dsm_typed(handle, &SB2_SHPS_DSM_UUID, SB2_SHPS_DSM_REVISION, -+ SB2_SHPS_DSM_GPU_STATE, ¶m, ACPI_TYPE_BUFFER); -+ -+ if (IS_ERR_OR_NULL(result)) { -+ return result ? PTR_ERR(result) : -EFAULT; -+ } -+ -+ if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) { -+ return -EIO; -+ } -+ -+ drvdata->dgpu_power = power; -+ -+ printk(KERN_INFO "sb2_shps: dGPU power state set to \'%s\'\n", sb2_dgpu_power_str(power)); -+ -+ ACPI_FREE(result); -+ return 0; -+} -+ -+static int sb2_shps_dgpu_set_power(struct platform_device *pdev, enum sb2_dgpu_power power) -+{ -+ struct sb2_shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ int status = 0; -+ -+ if (power < __SB2_DGPU_POWER__START || power > __SB2_DGPU_POWER__END) { -+ return -EINVAL; -+ } -+ -+ mutex_lock(&drvdata->dgpu_power_lock); -+ if (power != drvdata->dgpu_power) { -+ status = __sb2_shps_dgpu_set_power(pdev, power); -+ } -+ mutex_unlock(&drvdata->dgpu_power_lock); -+ -+ return status; -+} -+ -+static int sb2_shps_dgpu_force_power(struct platform_device *pdev, enum sb2_dgpu_power power) -+{ -+ struct sb2_shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ int status; -+ -+ if (power < __SB2_DGPU_POWER__START || power > __SB2_DGPU_POWER__END) { -+ return -EINVAL; -+ } -+ -+ mutex_lock(&drvdata->dgpu_power_lock); -+ status = __sb2_shps_dgpu_set_power(pdev, power); -+ mutex_unlock(&drvdata->dgpu_power_lock); -+ -+ return status; -+} -+ -+ -+static int param_dgpu_power_set(const char *val, const struct kernel_param *kp) -+{ -+ int power = SB2_PARAM_DGPU_POWER_OFF; -+ int status; -+ -+ status = kstrtoint(val, 0, &power); -+ if (status) { -+ return status; -+ } -+ -+ if (power < __SB2_PARAM_DGPU_POWER__START || power > __SB2_PARAM_DGPU_POWER__END) { -+ return -EINVAL; -+ } -+ -+ return param_set_int(val, kp); -+} -+ -+static const struct kernel_param_ops param_dgpu_power_ops = { -+ .set = param_dgpu_power_set, -+ .get = param_get_int, -+}; -+ -+static int param_dgpu_power_init = SB2_PARAM_DGPU_POWER_OFF; -+static int param_dgpu_power_exit = SB2_PARAM_DGPU_POWER_OFF; -+ -+module_param_cb(dgpu_power_init, ¶m_dgpu_power_ops, ¶m_dgpu_power_init, SB2_PARAM_PERM); -+module_param_cb(dgpu_power_exit, ¶m_dgpu_power_ops, ¶m_dgpu_power_exit, SB2_PARAM_PERM); -+ -+MODULE_PARM_DESC(dgpu_power_init, "dGPU power state to be set on init (0: off / 1: on / 2: as-is)"); -+MODULE_PARM_DESC(dgpu_power_exit, "dGPU power state to be set on exit (0: off / 1: on / 2: as-is)"); -+ -+ -+static ssize_t dgpu_power_show(struct device *dev, struct device_attribute *attr, char *data) -+{ -+ struct platform_device *pdev = container_of(dev, struct platform_device, dev); -+ struct sb2_shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ -+ return sprintf(data, "%s\n", sb2_dgpu_power_str(drvdata->dgpu_power)); -+} -+ -+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, -+ const char *data, size_t count) -+{ -+ struct platform_device *pdev = container_of(dev, struct platform_device, dev); -+ bool power = false; -+ int status; -+ -+ status = kstrtobool(data, &power); -+ if (status) { -+ return status; -+ } -+ -+ if (power) { -+ status = sb2_shps_dgpu_set_power(pdev, SB2_DGPU_POWER_ON); -+ } else { -+ status = sb2_shps_dgpu_set_power(pdev, SB2_DGPU_POWER_OFF); -+ } -+ -+ return status < 0 ? status : count; -+} -+ -+const static DEVICE_ATTR_RW(dgpu_power); -+ -+ -+#ifdef CONFIG_PM -+ -+static int sb2_shps_resume(struct device *dev) -+{ -+ struct platform_device *pdev = container_of(dev, struct platform_device, dev); -+ struct sb2_shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ -+ return sb2_shps_dgpu_force_power(pdev, drvdata->dgpu_power); -+} -+ -+static SIMPLE_DEV_PM_OPS(sb2_shps_pm_ops, NULL, sb2_shps_resume); -+ -+#endif -+ -+ -+static int sb2_shps_probe(struct platform_device *pdev) -+{ -+ struct sb2_shps_driver_data *drvdata; -+ struct acpi_device *shps_dev = ACPI_COMPANION(&pdev->dev); -+ int status = 0; -+ -+ if (gpiod_count(&pdev->dev, NULL) < 0) { -+ return -ENODEV; -+ } -+ -+ status = acpi_dev_add_driver_gpios(shps_dev, sb2_mshw0153_acpi_gpios); -+ if (status) { -+ return status; -+ } -+ -+ drvdata = kzalloc(sizeof(struct sb2_shps_driver_data), GFP_KERNEL); -+ if (!drvdata) { -+ status = -ENOMEM; -+ goto err_alloc_drvdata; -+ } -+ -+ mutex_init(&drvdata->dgpu_power_lock); -+ drvdata->dgpu_power = SB2_DGPU_POWER_OFF; -+ platform_set_drvdata(pdev, drvdata); -+ -+ if (param_dgpu_power_init != SB2_PARAM_DGPU_POWER_AS_IS) { -+ status = sb2_shps_dgpu_force_power(pdev, param_dgpu_power_init); -+ if (status) { -+ goto err_set_power; -+ } -+ } -+ -+ status = sysfs_create_file(&pdev->dev.kobj, &dev_attr_dgpu_power.attr); -+ if (status) { -+ goto err_sysfs; -+ } -+ -+ return 0; -+ -+err_sysfs: -+ sb2_shps_dgpu_force_power(pdev, SB2_DGPU_POWER_OFF); -+err_set_power: -+ platform_set_drvdata(pdev, NULL); -+ kfree(drvdata); -+err_alloc_drvdata: -+ acpi_dev_remove_driver_gpios(shps_dev); -+ return status; -+} -+ -+static int sb2_shps_remove(struct platform_device *pdev) -+{ -+ struct sb2_shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ struct acpi_device *shps_dev = ACPI_COMPANION(&pdev->dev); -+ -+ sysfs_remove_file(&pdev->dev.kobj, &dev_attr_dgpu_power.attr); -+ -+ if (param_dgpu_power_exit != SB2_PARAM_DGPU_POWER_AS_IS) { -+ sb2_shps_dgpu_set_power(pdev, param_dgpu_power_exit); -+ } -+ acpi_dev_remove_driver_gpios(shps_dev); -+ -+ platform_set_drvdata(pdev, NULL); -+ kfree(drvdata); -+ -+ return 0; -+} -+ -+ -+static const struct acpi_device_id sb2_shps_acpi_match[] = { -+ { "MSHW0153", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, sb2_shps_acpi_match); -+ -+static struct platform_driver sb2_shps_driver = { -+ .probe = sb2_shps_probe, -+ .remove = sb2_shps_remove, -+ .driver = { -+ .name = "sb2_shps", -+ .acpi_match_table = ACPI_PTR(sb2_shps_acpi_match), -+#ifdef CONFIG_PM -+ .pm = &sb2_shps_pm_ops, -+#endif -+ }, -+}; -+module_platform_driver(sb2_shps_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Book 2 Hot-Plug System Driver"); -+MODULE_LICENSE("GPL v2"); --- -2.23.0 - diff --git a/patches/5.3/0001-surface-acpi.patch b/patches/5.3/0001-surface-acpi.patch deleted file mode 100644 index ea015dc17..000000000 --- a/patches/5.3/0001-surface-acpi.patch +++ /dev/null @@ -1,7590 +0,0 @@ -From fa60a6d5cdd3cac447311bf2185ce31fe13942fd Mon Sep 17 00:00:00 2001 -From: qzed -Date: Mon, 26 Aug 2019 01:11:08 +0200 -Subject: [PATCH 01/10] surface-acpi - ---- - drivers/acpi/acpica/dsopcode.c | 2 +- - drivers/acpi/acpica/exfield.c | 12 +- - drivers/platform/x86/Kconfig | 1 + - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/surface_sam/Kconfig | 166 ++ - drivers/platform/x86/surface_sam/Makefile | 10 + - .../x86/surface_sam/surface_sam_dtx.c | 623 ++++++ - .../x86/surface_sam/surface_sam_hps.c | 1110 +++++++++++ - .../x86/surface_sam/surface_sam_san.c | 901 +++++++++ - .../x86/surface_sam/surface_sam_san.h | 29 + - .../x86/surface_sam/surface_sam_sid.c | 117 ++ - .../x86/surface_sam/surface_sam_sid_gpelid.c | 219 ++ - .../surface_sam/surface_sam_sid_perfmode.c | 225 +++ - .../x86/surface_sam/surface_sam_sid_power.c | 1259 ++++++++++++ - .../x86/surface_sam/surface_sam_sid_vhf.c | 440 ++++ - .../x86/surface_sam/surface_sam_ssh.c | 1773 +++++++++++++++++ - .../x86/surface_sam/surface_sam_ssh.h | 97 + - .../x86/surface_sam/surface_sam_vhf.c | 276 +++ - drivers/tty/serdev/core.c | 111 +- - 19 files changed, 7356 insertions(+), 16 deletions(-) - create mode 100644 drivers/platform/x86/surface_sam/Kconfig - create mode 100644 drivers/platform/x86/surface_sam/Makefile - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_dtx.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_hps.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_san.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_san.h - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid_gpelid.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid_perfmode.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid_power.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid_vhf.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_ssh.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_ssh.h - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_vhf.c - -diff --git a/drivers/acpi/acpica/dsopcode.c b/drivers/acpi/acpica/dsopcode.c -index 10f32b62608e..7b2a4987f050 100644 ---- a/drivers/acpi/acpica/dsopcode.c -+++ b/drivers/acpi/acpica/dsopcode.c -@@ -123,7 +123,7 @@ acpi_ds_init_buffer_field(u16 aml_opcode, - - /* Offset is in bits, count is in bits */ - -- field_flags = AML_FIELD_ACCESS_BYTE; -+ field_flags = AML_FIELD_ACCESS_BUFFER; - bit_offset = offset; - bit_count = (u32) length_desc->integer.value; - -diff --git a/drivers/acpi/acpica/exfield.c b/drivers/acpi/acpica/exfield.c -index d3d2dbfba680..0b7f617a6e9b 100644 ---- a/drivers/acpi/acpica/exfield.c -+++ b/drivers/acpi/acpica/exfield.c -@@ -109,6 +109,7 @@ acpi_ex_read_data_from_field(struct acpi_walk_state *walk_state, - union acpi_operand_object *buffer_desc; - void *buffer; - u32 buffer_length; -+ u8 field_flags; - - ACPI_FUNCTION_TRACE_PTR(ex_read_data_from_field, obj_desc); - -@@ -157,11 +158,16 @@ acpi_ex_read_data_from_field(struct acpi_walk_state *walk_state, - * Note: Field.length is in bits. - */ - buffer_length = -- (acpi_size)ACPI_ROUND_BITS_UP_TO_BYTES(obj_desc->field.bit_length); -+ (acpi_size)ACPI_ROUND_BITS_UP_TO_BYTES(obj_desc->common_field.bit_length); -+ field_flags = obj_desc->common_field.field_flags; - -- if (buffer_length > acpi_gbl_integer_byte_width) { -+ if (buffer_length > acpi_gbl_integer_byte_width || -+ (field_flags & AML_FIELD_ACCESS_TYPE_MASK) == AML_FIELD_ACCESS_BUFFER) { - -- /* Field is too large for an Integer, create a Buffer instead */ -+ /* -+ * Field is either too large for an Integer, or a actually of type -+ * buffer, so create a Buffer. -+ */ - - buffer_desc = acpi_ut_create_buffer_object(buffer_length); - if (!buffer_desc) { -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 1b67bb578f9f..9dea69a1526a 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -1335,6 +1335,7 @@ config PCENGINES_APU2 - will be called pcengines-apuv2. - - source "drivers/platform/x86/intel_speed_select_if/Kconfig" -+source "drivers/platform/x86/surface_sam/Kconfig" - - endif # X86_PLATFORM_DEVICES - -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 415104033060..18f5a4ba7244 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -100,3 +100,4 @@ obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o - obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o - obj-$(CONFIG_PCENGINES_APU2) += pcengines-apuv2.o - obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/ -+obj-$(CONFIG_SURFACE_SAM) += surface_sam/ -diff --git a/drivers/platform/x86/surface_sam/Kconfig b/drivers/platform/x86/surface_sam/Kconfig -new file mode 100644 -index 000000000000..4eff58a121cb ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/Kconfig -@@ -0,0 +1,166 @@ -+menuconfig SURFACE_SAM -+ depends on ACPI -+ tristate "Microsoft Surface/System Aggregator Module and Platform Drivers" -+ ---help--- -+ Drivers for the Surface/System Aggregator Module (SAM) of Microsoft -+ Surface devices. -+ -+ SAM is an embedded controller that provides access to various -+ functionalities on these devices, including battery status, keyboard -+ events (on the Laptops) and many more. -+ -+ Say M/Y here if you have a Microsoft Surface device with a SAM device -+ (i.e. 5th generation or later). -+ -+config SURFACE_SAM_SSH -+ tristate "Surface Serial Hub Driver" -+ depends on SURFACE_SAM -+ depends on X86_INTEL_LPSS -+ depends on SERIAL_8250_DW -+ depends on SERIAL_8250_DMA -+ depends on SERIAL_DEV_CTRL_TTYPORT -+ select CRC_CCITT -+ default m -+ ---help--- -+ Surface Serial Hub driver for 5th generation (or later) Microsoft -+ Surface devices. -+ -+ This is the base driver for the embedded serial controller found on -+ 5th generation (and later) Microsoft Surface devices (e.g. Book 2, -+ Laptop, Laptop 2, Pro 2017, Pro 6, ...). This driver itself only -+ provides access to the embedded controller (SAM) and subsequent -+ drivers are required for the respective functionalities. -+ -+ If you have a 5th generation (or later) Microsoft Surface device, say -+ Y or M here. -+ -+config SURFACE_SAM_SSH_DEBUG_DEVICE -+ bool "Surface Serial Hub Debug Device" -+ depends on SURFACE_SAM_SSH -+ depends on SYSFS -+ default n -+ ---help--- -+ Debug device for direct communication with the embedded controller -+ found on 5th generation (and later) Microsoft Surface devices (e.g. -+ Book 2, Laptop, Laptop 2, Pro 2017, Pro 6, ...) via sysfs. -+ -+ If you are not sure, say N here. -+ -+config SURFACE_SAM_SAN -+ tristate "Surface ACPI Notify Driver" -+ depends on SURFACE_SAM_SSH -+ default m -+ ---help--- -+ Surface ACPI Notify driver for 5th generation (or later) Microsoft -+ Surface devices. -+ -+ This driver enables basic ACPI events and requests, such as battery -+ status requests/events, thermal events, lid status, and possibly more, -+ which would otherwise not work on these devices. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_VHF -+ tristate "Surface Virtual HID Framework Driver" -+ depends on SURFACE_SAM_SSH -+ depends on HID -+ default m -+ ---help--- -+ Surface Virtual HID Framework driver for 5th generation (or later) -+ Microsoft Surface devices. -+ -+ This driver provides support for the Microsoft Virtual HID framework, -+ which is required for keyboard support on the Surface Laptop 1 and 2. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_DTX -+ tristate "Surface Detachment System (DTX) Driver" -+ depends on SURFACE_SAM_SSH -+ depends on INPUT -+ default m -+ ---help--- -+ Surface Detachment System (DTX) driver for the Microsoft Surface Book -+ 2. This driver provides support for proper detachment handling in -+ user-space, status-events relating to the base and support for -+ the safe-guard keeping the base attached when the discrete GPU -+ contained in it is running via the special /dev/surface-dtx device. -+ -+ Also provides a standard input device to provide SW_TABLET_MODE events -+ upon device mode change. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_HPS -+ tristate "Surface dGPU Hot-Plug System (dGPU-HPS) Driver" -+ depends on SURFACE_SAM_SSH -+ depends on SURFACE_SAM_SAN -+ default m -+ ---help--- -+ Driver to properly handle hot-plugging and explicit power-on/power-off -+ of the discrete GPU (dGPU) on the Surface Book 2. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_SID -+ tristate "Surface Platform Integration Driver" -+ depends on SURFACE_SAM_SSH -+ default m -+ ---help--- -+ Surface Platform Integration Driver for the Microsoft Surface Devices. -+ This driver loads various model-specific sub-drivers, including -+ battery and keyboard support on 7th generation Surface devices, proper -+ lid setup to enable device wakeup when the lid is opened on multiple -+ models, as well as performance mode setting support on the Surface -+ Book 2. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_SID_GPELID -+ tristate "Surface Lid Wakeup Driver" -+ depends on SURFACE_SAM_SID -+ default m -+ ---help--- -+ Driver to set up device wake-up via lid on Intel-based Microsoft -+ Surface devices. These devices do not wake up from sleep as their GPE -+ interrupt is not configured automatically. This driver solves that -+ problem. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_SID_PERFMODE -+ tristate "Surface Performance Mode Driver" -+ depends on SURFACE_SAM_SID -+ depends on SYSFS -+ default m -+ ---help--- -+ This driver provides suport for setting performance-modes on Surface -+ devices via the perf_mode sysfs attribute. Currently only supports the -+ Surface Book 2. Performance-modes directly influence the fan-profile -+ of the device, allowing to choose between higher performance or -+ quieter operation. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_SID_VHF -+ tristate "Surface SAM HID Driver" -+ depends on SURFACE_SAM_SID -+ depends on HID -+ default m -+ ---help--- -+ This driver provides support for HID devices connected via the Surface -+ SAM embedded controller. It provides support for keyboard and touchpad -+ on the Surface Laptop 3 models. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_SID_POWER -+ tristate "Surface SAM Battery/AC Driver" -+ depends on SURFACE_SAM_SID -+ select POWER_SUPPLY -+ default m -+ ---help--- -+ This driver provides support for the battery and AC on 7th generation -+ Surface devices. -+ -+ If you are not sure, say M here. -diff --git a/drivers/platform/x86/surface_sam/Makefile b/drivers/platform/x86/surface_sam/Makefile -new file mode 100644 -index 000000000000..188975ccde5c ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/Makefile -@@ -0,0 +1,10 @@ -+obj-$(CONFIG_SURFACE_SAM_SSH) += surface_sam_ssh.o -+obj-$(CONFIG_SURFACE_SAM_SAN) += surface_sam_san.o -+obj-$(CONFIG_SURFACE_SAM_DTX) += surface_sam_dtx.o -+obj-$(CONFIG_SURFACE_SAM_HPS) += surface_sam_hps.o -+obj-$(CONFIG_SURFACE_SAM_VHF) += surface_sam_vhf.o -+obj-$(CONFIG_SURFACE_SAM_SID) += surface_sam_sid.o -+obj-$(CONFIG_SURFACE_SAM_SID_GPELID) += surface_sam_sid_gpelid.o -+obj-$(CONFIG_SURFACE_SAM_SID_PERFMODE) += surface_sam_sid_perfmode.o -+obj-$(CONFIG_SURFACE_SAM_SID_POWER) += surface_sam_sid_power.o -+obj-$(CONFIG_SURFACE_SAM_SID_VHF) += surface_sam_sid_vhf.o -diff --git a/drivers/platform/x86/surface_sam/surface_sam_dtx.c b/drivers/platform/x86/surface_sam/surface_sam_dtx.c -new file mode 100644 -index 000000000000..4b924de6ab09 ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_dtx.c -@@ -0,0 +1,623 @@ -+/* -+ * Detachment system (DTX) driver for Microsoft Surface Book 2. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "surface_sam_ssh.h" -+ -+ -+#define USB_VENDOR_ID_MICROSOFT 0x045e -+#define USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION 0x0922 -+ -+// name copied from MS device manager -+#define DTX_INPUT_NAME "Microsoft Surface Base 2 Integration Device" -+ -+ -+#define DTX_CMD_LATCH_LOCK _IO(0x11, 0x01) -+#define DTX_CMD_LATCH_UNLOCK _IO(0x11, 0x02) -+#define DTX_CMD_LATCH_REQUEST _IO(0x11, 0x03) -+#define DTX_CMD_LATCH_OPEN _IO(0x11, 0x04) -+#define DTX_CMD_GET_OPMODE _IOR(0x11, 0x05, int) -+ -+#define SAM_RQST_DTX_TC 0x11 -+#define SAM_RQST_DTX_CID_LATCH_LOCK 0x06 -+#define SAM_RQST_DTX_CID_LATCH_UNLOCK 0x07 -+#define SAM_RQST_DTX_CID_LATCH_REQUEST 0x08 -+#define SAM_RQST_DTX_CID_LATCH_OPEN 0x09 -+#define SAM_RQST_DTX_CID_GET_OPMODE 0x0D -+ -+#define SAM_EVENT_DTX_TC 0x11 -+#define SAM_EVENT_DTX_RQID 0x0011 -+#define SAM_EVENT_DTX_CID_CONNECTION 0x0c -+#define SAM_EVENT_DTX_CID_BUTTON 0x0e -+#define SAM_EVENT_DTX_CID_ERROR 0x0f -+#define SAM_EVENT_DTX_CID_LATCH_STATUS 0x11 -+ -+#define DTX_OPMODE_TABLET 0x00 -+#define DTX_OPMODE_LAPTOP 0x01 -+#define DTX_OPMODE_STUDIO 0x02 -+ -+#define DTX_LATCH_CLOSED 0x00 -+#define DTX_LATCH_OPENED 0x01 -+ -+ -+// Warning: This must always be a power of 2! -+#define DTX_CLIENT_BUF_SIZE 16 -+ -+#define DTX_CONNECT_OPMODE_DELAY 1000 -+ -+#define DTX_ERR KERN_ERR "surface_sam_dtx: " -+#define DTX_WARN KERN_WARNING "surface_sam_dtx: " -+ -+ -+struct surface_dtx_event { -+ u8 type; -+ u8 code; -+ u8 arg0; -+ u8 arg1; -+} __packed; -+ -+struct surface_dtx_dev { -+ wait_queue_head_t waitq; -+ struct miscdevice mdev; -+ spinlock_t client_lock; -+ struct list_head client_list; -+ struct mutex mutex; -+ bool active; -+ spinlock_t input_lock; -+ struct input_dev *input_dev; -+}; -+ -+struct surface_dtx_client { -+ struct list_head node; -+ struct surface_dtx_dev *ddev; -+ struct fasync_struct *fasync; -+ spinlock_t buffer_lock; -+ unsigned int buffer_head; -+ unsigned int buffer_tail; -+ struct surface_dtx_event buffer[DTX_CLIENT_BUF_SIZE]; -+}; -+ -+ -+static struct surface_dtx_dev surface_dtx_dev; -+ -+ -+static int surface_sam_query_opmpde(void) -+{ -+ u8 result_buf[1]; -+ int status; -+ -+ struct surface_sam_ssh_rqst rqst = { -+ .tc = SAM_RQST_DTX_TC, -+ .cid = SAM_RQST_DTX_CID_GET_OPMODE, -+ .iid = 0, -+ .pri = SURFACE_SAM_PRIORITY_NORMAL, -+ .snc = 1, -+ .cdl = 0, -+ .pld = NULL, -+ }; -+ -+ struct surface_sam_ssh_buf result = { -+ .cap = 1, -+ .len = 0, -+ .data = result_buf, -+ }; -+ -+ status = surface_sam_ssh_rqst(&rqst, &result); -+ if (status) { -+ return status; -+ } -+ -+ if (result.len != 1) { -+ return -EFAULT; -+ } -+ -+ return result.data[0]; -+} -+ -+ -+static int dtx_cmd_simple(u8 cid) -+{ -+ struct surface_sam_ssh_rqst rqst = { -+ .tc = SAM_RQST_DTX_TC, -+ .cid = cid, -+ .iid = 0, -+ .pri = SURFACE_SAM_PRIORITY_NORMAL, -+ .snc = 0, -+ .cdl = 0, -+ .pld = NULL, -+ }; -+ -+ return surface_sam_ssh_rqst(&rqst, NULL); -+} -+ -+static int dtx_cmd_get_opmode(int __user *buf) -+{ -+ int opmode = surface_sam_query_opmpde(); -+ if (opmode < 0) { -+ return opmode; -+ } -+ -+ if (put_user(opmode, buf)) { -+ return -EACCES; -+ } -+ -+ return 0; -+} -+ -+ -+static int surface_dtx_open(struct inode *inode, struct file *file) -+{ -+ struct surface_dtx_dev *ddev = container_of(file->private_data, struct surface_dtx_dev, mdev); -+ struct surface_dtx_client *client; -+ -+ // initialize client -+ client = kzalloc(sizeof(struct surface_dtx_client), GFP_KERNEL); -+ if (!client) { -+ return -ENOMEM; -+ } -+ -+ spin_lock_init(&client->buffer_lock); -+ client->buffer_head = 0; -+ client->buffer_tail = 0; -+ client->ddev = ddev; -+ -+ // attach client -+ spin_lock(&ddev->client_lock); -+ list_add_tail_rcu(&client->node, &ddev->client_list); -+ spin_unlock(&ddev->client_lock); -+ -+ file->private_data = client; -+ nonseekable_open(inode, file); -+ -+ return 0; -+} -+ -+static int surface_dtx_release(struct inode *inode, struct file *file) -+{ -+ struct surface_dtx_client *client = file->private_data; -+ -+ // detach client -+ spin_lock(&client->ddev->client_lock); -+ list_del_rcu(&client->node); -+ spin_unlock(&client->ddev->client_lock); -+ synchronize_rcu(); -+ -+ kfree(client); -+ file->private_data = NULL; -+ -+ return 0; -+} -+ -+static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs) -+{ -+ struct surface_dtx_client *client = file->private_data; -+ struct surface_dtx_dev *ddev = client->ddev; -+ struct surface_dtx_event event; -+ size_t read = 0; -+ int status = 0; -+ -+ if (count != 0 && count < sizeof(struct surface_dtx_event)) { -+ return -EINVAL; -+ } -+ -+ if (!ddev->active) { -+ return -ENODEV; -+ } -+ -+ // check availability -+ if (client->buffer_head == client->buffer_tail){ -+ if (file->f_flags & O_NONBLOCK) { -+ return -EAGAIN; -+ } -+ -+ status = wait_event_interruptible(ddev->waitq, -+ client->buffer_head != client->buffer_tail || -+ !ddev->active); -+ if (status) { -+ return status; -+ } -+ -+ if (!ddev->active) { -+ return -ENODEV; -+ } -+ } -+ -+ // copy events one by one -+ while (read + sizeof(struct surface_dtx_event) <= count) { -+ spin_lock_irq(&client->buffer_lock); -+ -+ if(client->buffer_head == client->buffer_tail) { -+ spin_unlock_irq(&client->buffer_lock); -+ break; -+ } -+ -+ // get one event -+ event = client->buffer[client->buffer_tail]; -+ client->buffer_tail = (client->buffer_tail + 1) & (DTX_CLIENT_BUF_SIZE - 1); -+ spin_unlock_irq(&client->buffer_lock); -+ -+ // copy to userspace -+ if(copy_to_user(buf, &event, sizeof(struct surface_dtx_event))) { -+ return -EFAULT; -+ } -+ -+ read += sizeof(struct surface_dtx_event); -+ } -+ -+ return read; -+} -+ -+static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt) -+{ -+ struct surface_dtx_client *client = file->private_data; -+ int mask; -+ -+ poll_wait(file, &client->ddev->waitq, pt); -+ -+ if (client->ddev->active) { -+ mask = EPOLLOUT | EPOLLWRNORM; -+ } else { -+ mask = EPOLLHUP | EPOLLERR; -+ } -+ -+ if (client->buffer_head != client->buffer_tail) { -+ mask |= EPOLLIN | EPOLLRDNORM; -+ } -+ -+ return mask; -+} -+ -+static int surface_dtx_fasync(int fd, struct file *file, int on) -+{ -+ struct surface_dtx_client *client = file->private_data; -+ -+ return fasync_helper(fd, file, on, &client->fasync); -+} -+ -+static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -+{ -+ struct surface_dtx_client *client = file->private_data; -+ struct surface_dtx_dev *ddev = client->ddev; -+ int status; -+ -+ status = mutex_lock_interruptible(&ddev->mutex); -+ if (status) { -+ return status; -+ } -+ -+ if (!ddev->active) { -+ mutex_unlock(&ddev->mutex); -+ return -ENODEV; -+ } -+ -+ switch (cmd) { -+ case DTX_CMD_LATCH_LOCK: -+ status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_LOCK); -+ break; -+ -+ case DTX_CMD_LATCH_UNLOCK: -+ status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_UNLOCK); -+ break; -+ -+ case DTX_CMD_LATCH_REQUEST: -+ status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_REQUEST); -+ break; -+ -+ case DTX_CMD_LATCH_OPEN: -+ status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_OPEN); -+ break; -+ -+ case DTX_CMD_GET_OPMODE: -+ status = dtx_cmd_get_opmode((int __user *)arg); -+ break; -+ -+ default: -+ status = -EINVAL; -+ break; -+ } -+ -+ mutex_unlock(&ddev->mutex); -+ return status; -+} -+ -+static const struct file_operations surface_dtx_fops = { -+ .owner = THIS_MODULE, -+ .open = surface_dtx_open, -+ .release = surface_dtx_release, -+ .read = surface_dtx_read, -+ .poll = surface_dtx_poll, -+ .fasync = surface_dtx_fasync, -+ .unlocked_ioctl = surface_dtx_ioctl, -+ .llseek = no_llseek, -+}; -+ -+static struct surface_dtx_dev surface_dtx_dev = { -+ .mdev = { -+ .minor = MISC_DYNAMIC_MINOR, -+ .name = "surface_dtx", -+ .fops = &surface_dtx_fops, -+ }, -+ .client_lock = __SPIN_LOCK_UNLOCKED(), -+ .input_lock = __SPIN_LOCK_UNLOCKED(), -+ .mutex = __MUTEX_INITIALIZER(surface_dtx_dev.mutex), -+ .active = false, -+}; -+ -+ -+static void surface_dtx_push_event(struct surface_dtx_dev *ddev, struct surface_dtx_event *event) -+{ -+ struct surface_dtx_client *client; -+ -+ rcu_read_lock(); -+ list_for_each_entry_rcu(client, &ddev->client_list, node) { -+ spin_lock(&client->buffer_lock); -+ -+ client->buffer[client->buffer_head++] = *event; -+ client->buffer_head &= DTX_CLIENT_BUF_SIZE - 1; -+ -+ if (unlikely(client->buffer_head == client->buffer_tail)) { -+ printk(DTX_WARN "event buffer overrun\n"); -+ client->buffer_tail = (client->buffer_tail + 1) & (DTX_CLIENT_BUF_SIZE - 1); -+ } -+ -+ spin_unlock(&client->buffer_lock); -+ -+ kill_fasync(&client->fasync, SIGIO, POLL_IN); -+ } -+ rcu_read_unlock(); -+ -+ wake_up_interruptible(&ddev->waitq); -+} -+ -+ -+static void surface_dtx_update_opmpde(struct surface_dtx_dev *ddev) -+{ -+ struct surface_dtx_event event; -+ int opmode; -+ -+ // get operation mode -+ opmode = surface_sam_query_opmpde(); -+ if (opmode < 0) { -+ printk(DTX_ERR "EC request failed with error %d\n", opmode); -+ } -+ -+ // send DTX event -+ event.type = 0x11; -+ event.code = 0x0D; -+ event.arg0 = opmode; -+ event.arg1 = 0x00; -+ -+ surface_dtx_push_event(ddev, &event); -+ -+ // send SW_TABLET_MODE event -+ spin_lock(&ddev->input_lock); -+ input_report_switch(ddev->input_dev, SW_TABLET_MODE, opmode == 0x00); -+ input_sync(ddev->input_dev); -+ spin_unlock(&ddev->input_lock); -+} -+ -+static int surface_dtx_evt_dtx(struct surface_sam_ssh_event *in_event, void *data) -+{ -+ struct surface_dtx_dev *ddev = data; -+ struct surface_dtx_event event; -+ -+ switch (in_event->cid) { -+ case SAM_EVENT_DTX_CID_CONNECTION: -+ case SAM_EVENT_DTX_CID_BUTTON: -+ case SAM_EVENT_DTX_CID_ERROR: -+ case SAM_EVENT_DTX_CID_LATCH_STATUS: -+ if (in_event->len > 2) { -+ printk(DTX_ERR "unexpected payload size (cid: %x, len: %u)\n", -+ in_event->cid, in_event->len); -+ return 0; -+ } -+ -+ event.type = in_event->tc; -+ event.code = in_event->cid; -+ event.arg0 = in_event->len >= 1 ? in_event->pld[0] : 0x00; -+ event.arg1 = in_event->len >= 2 ? in_event->pld[1] : 0x00; -+ surface_dtx_push_event(ddev, &event); -+ break; -+ -+ default: -+ printk(DTX_WARN "unhandled dtx event (cid: %x)\n", in_event->cid); -+ } -+ -+ // update device mode -+ if (in_event->cid == SAM_EVENT_DTX_CID_CONNECTION) { -+ if (in_event->pld[0]) { -+ // Note: we're already in a workqueue task -+ msleep(DTX_CONNECT_OPMODE_DELAY); -+ } -+ -+ surface_dtx_update_opmpde(ddev); -+ } -+ -+ return 0; -+} -+ -+static int surface_dtx_events_setup(struct surface_dtx_dev *ddev) -+{ -+ int status; -+ -+ status = surface_sam_ssh_set_event_handler(SAM_EVENT_DTX_RQID, surface_dtx_evt_dtx, ddev); -+ if (status) { -+ goto err_handler; -+ } -+ -+ status = surface_sam_ssh_enable_event_source(SAM_EVENT_DTX_TC, 0x01, SAM_EVENT_DTX_RQID); -+ if (status) { -+ goto err_source; -+ } -+ -+ return 0; -+ -+err_source: -+ surface_sam_ssh_remove_event_handler(SAM_EVENT_DTX_RQID); -+err_handler: -+ return status; -+} -+ -+static void surface_dtx_events_disable(void) -+{ -+ surface_sam_ssh_disable_event_source(SAM_EVENT_DTX_TC, 0x01, SAM_EVENT_DTX_RQID); -+ surface_sam_ssh_remove_event_handler(SAM_EVENT_DTX_RQID); -+} -+ -+ -+static struct input_dev *surface_dtx_register_inputdev(struct platform_device *pdev) -+{ -+ struct input_dev *input_dev; -+ int status; -+ -+ input_dev = input_allocate_device(); -+ if (!input_dev) { -+ return ERR_PTR(-ENOMEM); -+ } -+ -+ input_dev->name = DTX_INPUT_NAME; -+ input_dev->dev.parent = &pdev->dev; -+ input_dev->id.bustype = BUS_VIRTUAL; -+ input_dev->id.vendor = USB_VENDOR_ID_MICROSOFT; -+ input_dev->id.product = USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION; -+ -+ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); -+ -+ status = surface_sam_query_opmpde(); -+ if (status < 0) { -+ input_free_device(input_dev); -+ return ERR_PTR(status); -+ } -+ -+ input_report_switch(input_dev, SW_TABLET_MODE, status == 0x00); -+ -+ status = input_register_device(input_dev); -+ if (status) { -+ input_unregister_device(input_dev); -+ return ERR_PTR(status); -+ } -+ -+ return input_dev; -+} -+ -+ -+static int surface_sam_dtx_probe(struct platform_device *pdev) -+{ -+ struct surface_dtx_dev *ddev = &surface_dtx_dev; -+ struct input_dev *input_dev; -+ int status; -+ -+ // link to ec -+ status = surface_sam_ssh_consumer_register(&pdev->dev); -+ if (status) { -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ } -+ -+ input_dev = surface_dtx_register_inputdev(pdev); -+ if (IS_ERR(input_dev)) { -+ return PTR_ERR(input_dev); -+ } -+ -+ // initialize device -+ mutex_lock(&ddev->mutex); -+ if (ddev->active) { -+ mutex_unlock(&ddev->mutex); -+ status = -ENODEV; -+ goto err_register; -+ } -+ -+ INIT_LIST_HEAD(&ddev->client_list); -+ init_waitqueue_head(&ddev->waitq); -+ ddev->active = true; -+ ddev->input_dev = input_dev; -+ mutex_unlock(&ddev->mutex); -+ -+ status = misc_register(&ddev->mdev); -+ if (status) { -+ goto err_register; -+ } -+ -+ // enable events -+ status = surface_dtx_events_setup(ddev); -+ if (status) { -+ goto err_events_setup; -+ } -+ -+ return 0; -+ -+err_events_setup: -+ misc_deregister(&ddev->mdev); -+err_register: -+ input_unregister_device(ddev->input_dev); -+ return status; -+} -+ -+static int surface_sam_dtx_remove(struct platform_device *pdev) -+{ -+ struct surface_dtx_dev *ddev = &surface_dtx_dev; -+ struct surface_dtx_client *client; -+ -+ mutex_lock(&ddev->mutex); -+ if (!ddev->active) { -+ mutex_unlock(&ddev->mutex); -+ return 0; -+ } -+ -+ // mark as inactive -+ ddev->active = false; -+ mutex_unlock(&ddev->mutex); -+ -+ // After this call we're guaranteed that no more input events will arive -+ surface_dtx_events_disable(); -+ -+ // wake up clients -+ spin_lock(&ddev->client_lock); -+ list_for_each_entry(client, &ddev->client_list, node) { -+ kill_fasync(&client->fasync, SIGIO, POLL_HUP); -+ } -+ spin_unlock(&ddev->client_lock); -+ -+ wake_up_interruptible(&ddev->waitq); -+ -+ // unregister user-space devices -+ input_unregister_device(ddev->input_dev); -+ misc_deregister(&ddev->mdev); -+ -+ return 0; -+} -+ -+ -+static const struct acpi_device_id surface_sam_dtx_match[] = { -+ { "MSHW0133", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_sam_dtx_match); -+ -+static struct platform_driver surface_sam_dtx = { -+ .probe = surface_sam_dtx_probe, -+ .remove = surface_sam_dtx_remove, -+ .driver = { -+ .name = "surface_sam_dtx", -+ .acpi_match_table = ACPI_PTR(surface_sam_dtx_match), -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_sam_dtx); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Detachment System (DTX) Driver for 5th Generation Surface Devices"); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_hps.c b/drivers/platform/x86/surface_sam/surface_sam_hps.c -new file mode 100644 -index 000000000000..3b123bd3dcfe ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_hps.c -@@ -0,0 +1,1110 @@ -+/* -+ * Surface dGPU hot-plug system driver. -+ * Supports explicit setting of the dGPU power-state on the Surface Book 2 and -+ * properly handles hot-plugging by detaching the base. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "surface_sam_ssh.h" -+#include "surface_sam_san.h" -+ -+ -+// TODO: vgaswitcheroo integration -+ -+ -+static void dbg_dump_drvsta(struct platform_device *pdev, const char *prefix); -+ -+ -+#define SHPS_DSM_REVISION 1 -+#define SHPS_DSM_GPU_ADDRS 0x02 -+#define SHPS_DSM_GPU_POWER 0x05 -+static const guid_t SHPS_DSM_UUID = -+ GUID_INIT(0x5515a847, 0xed55, 0x4b27, 0x83, 0x52, 0xcd, -+ 0x32, 0x0e, 0x10, 0x36, 0x0a); -+ -+ -+#define SAM_DGPU_TC 0x13 -+#define SAM_DGPU_CID_POWERON 0x02 -+ -+#define SAM_DTX_TC 0x11 -+#define SAM_DTX_CID_LATCH_LOCK 0x06 -+#define SAM_DTX_CID_LATCH_UNLOCK 0x07 -+ -+#define SHPS_DSM_GPU_ADDRS_RP "RP5_PCIE" -+#define SHPS_DSM_GPU_ADDRS_DGPU "DGPU_PCIE" -+ -+ -+static const struct acpi_gpio_params gpio_base_presence_int = { 0, 0, false }; -+static const struct acpi_gpio_params gpio_base_presence = { 1, 0, false }; -+static const struct acpi_gpio_params gpio_dgpu_power_int = { 2, 0, false }; -+static const struct acpi_gpio_params gpio_dgpu_power = { 3, 0, false }; -+static const struct acpi_gpio_params gpio_dgpu_presence_int = { 4, 0, false }; -+static const struct acpi_gpio_params gpio_dgpu_presence = { 5, 0, false }; -+ -+static const struct acpi_gpio_mapping shps_acpi_gpios[] = { -+ { "base_presence-int-gpio", &gpio_base_presence_int, 1 }, -+ { "base_presence-gpio", &gpio_base_presence, 1 }, -+ { "dgpu_power-int-gpio", &gpio_dgpu_power_int, 1 }, -+ { "dgpu_power-gpio", &gpio_dgpu_power, 1 }, -+ { "dgpu_presence-int-gpio", &gpio_dgpu_presence_int, 1 }, -+ { "dgpu_presence-gpio", &gpio_dgpu_presence, 1 }, -+ { }, -+}; -+ -+ -+enum shps_dgpu_power { -+ SHPS_DGPU_POWER_OFF = 0, -+ SHPS_DGPU_POWER_ON = 1, -+ SHPS_DGPU_POWER_UNKNOWN = 2, -+}; -+ -+static const char* shps_dgpu_power_str(enum shps_dgpu_power power) { -+ if (power == SHPS_DGPU_POWER_OFF) -+ return "off"; -+ else if (power == SHPS_DGPU_POWER_ON) -+ return "on"; -+ else if (power == SHPS_DGPU_POWER_UNKNOWN) -+ return "unknown"; -+ else -+ return ""; -+} -+ -+ -+struct shps_driver_data { -+ struct mutex lock; -+ struct pci_dev *dgpu_root_port; -+ struct pci_saved_state *dgpu_root_port_state; -+ struct gpio_desc *gpio_dgpu_power; -+ struct gpio_desc *gpio_dgpu_presence; -+ struct gpio_desc *gpio_base_presence; -+ unsigned int irq_dgpu_presence; -+ unsigned int irq_base_presence; -+ unsigned long state; -+}; -+ -+#define SHPS_STATE_BIT_PWRTGT 0 /* desired power state: 1 for on, 0 for off */ -+#define SHPS_STATE_BIT_RPPWRON_SYNC 1 /* synchronous/requested power-up in progress */ -+#define SHPS_STATE_BIT_WAKE_ENABLED 2 /* wakeup via base-presence GPIO enabled */ -+ -+ -+#define SHPS_DGPU_PARAM_PERM (S_IRUGO | S_IWUSR) -+ -+enum shps_dgpu_power_mp { -+ SHPS_DGPU_MP_POWER_OFF = SHPS_DGPU_POWER_OFF, -+ SHPS_DGPU_MP_POWER_ON = SHPS_DGPU_POWER_ON, -+ SHPS_DGPU_MP_POWER_ASIS = -1, -+ -+ __SHPS_DGPU_MP_POWER_START = -1, -+ __SHPS_DGPU_MP_POWER_END = 1, -+}; -+ -+static int param_dgpu_power_set(const char *val, const struct kernel_param *kp) -+{ -+ int power = SHPS_DGPU_MP_POWER_OFF; -+ int status; -+ -+ status = kstrtoint(val, 0, &power); -+ if (status) { -+ return status; -+ } -+ -+ if (power < __SHPS_DGPU_MP_POWER_START || power > __SHPS_DGPU_MP_POWER_END) { -+ return -EINVAL; -+ } -+ -+ return param_set_int(val, kp); -+} -+ -+static const struct kernel_param_ops param_dgpu_power_ops = { -+ .set = param_dgpu_power_set, -+ .get = param_get_int, -+}; -+ -+static int param_dgpu_power_init = SHPS_DGPU_MP_POWER_OFF; -+static int param_dgpu_power_exit = SHPS_DGPU_MP_POWER_ON; -+static int param_dgpu_power_susp = SHPS_DGPU_MP_POWER_ASIS; -+static bool param_dtx_latch = true; -+ -+module_param_cb(dgpu_power_init, ¶m_dgpu_power_ops, ¶m_dgpu_power_init, SHPS_DGPU_PARAM_PERM); -+module_param_cb(dgpu_power_exit, ¶m_dgpu_power_ops, ¶m_dgpu_power_exit, SHPS_DGPU_PARAM_PERM); -+module_param_cb(dgpu_power_susp, ¶m_dgpu_power_ops, ¶m_dgpu_power_susp, SHPS_DGPU_PARAM_PERM); -+module_param_named(dtx_latch, param_dtx_latch, bool, SHPS_DGPU_PARAM_PERM); -+ -+MODULE_PARM_DESC(dgpu_power_init, "dGPU power state to be set on init (0: off / 1: on / 2: as-is, default: off)"); -+MODULE_PARM_DESC(dgpu_power_exit, "dGPU power state to be set on exit (0: off / 1: on / 2: as-is, default: on)"); -+MODULE_PARM_DESC(dgpu_power_susp, "dGPU power state to be set on exit (0: off / 1: on / 2: as-is, default: as-is)"); -+MODULE_PARM_DESC(dtx_latch, "lock/unlock DTX base latch in accordance to power-state (Y/n)"); -+ -+ -+static int dtx_cmd_simple(u8 cid) -+{ -+ struct surface_sam_ssh_rqst rqst = { -+ .tc = SAM_DTX_TC, -+ .cid = cid, -+ .iid = 0, -+ .pri = SURFACE_SAM_PRIORITY_NORMAL, -+ .snc = 0, -+ .cdl = 0, -+ .pld = NULL, -+ }; -+ -+ return surface_sam_ssh_rqst(&rqst, NULL); -+} -+ -+inline static int shps_dtx_latch_lock(void) -+{ -+ return dtx_cmd_simple(SAM_DTX_CID_LATCH_LOCK); -+} -+ -+inline static int shps_dtx_latch_unlock(void) -+{ -+ return dtx_cmd_simple(SAM_DTX_CID_LATCH_UNLOCK); -+} -+ -+ -+static int shps_dgpu_dsm_get_pci_addr(struct platform_device *pdev, const char* entry) -+{ -+ acpi_handle handle = ACPI_HANDLE(&pdev->dev); -+ union acpi_object *result; -+ union acpi_object *e0; -+ union acpi_object *e1; -+ union acpi_object *e2; -+ u64 device_addr = 0; -+ u8 bus, dev, fun; -+ int i; -+ -+ result = acpi_evaluate_dsm_typed(handle, &SHPS_DSM_UUID, SHPS_DSM_REVISION, -+ SHPS_DSM_GPU_ADDRS, NULL, ACPI_TYPE_PACKAGE); -+ -+ if (IS_ERR_OR_NULL(result)) -+ return result ? PTR_ERR(result) : -EIO; -+ -+ // three entries per device: name, address, -+ for (i = 0; i + 2 < result->package.count; i += 3) { -+ e0 = &result->package.elements[i]; -+ e1 = &result->package.elements[i + 1]; -+ e2 = &result->package.elements[i + 2]; -+ -+ if (e0->type != ACPI_TYPE_STRING) { -+ ACPI_FREE(result); -+ return -EIO; -+ } -+ -+ if (e1->type != ACPI_TYPE_INTEGER) { -+ ACPI_FREE(result); -+ return -EIO; -+ } -+ -+ if (e2->type != ACPI_TYPE_INTEGER) { -+ ACPI_FREE(result); -+ return -EIO; -+ } -+ -+ if (strncmp(e0->string.pointer, entry, 64) == 0) -+ device_addr = e1->integer.value; -+ } -+ -+ ACPI_FREE(result); -+ if (device_addr == 0) -+ return -ENODEV; -+ -+ // convert address -+ bus = (device_addr & 0x0FF00000) >> 20; -+ dev = (device_addr & 0x000F8000) >> 15; -+ fun = (device_addr & 0x00007000) >> 12; -+ -+ return bus << 8 | PCI_DEVFN(dev, fun); -+} -+ -+static struct pci_dev *shps_dgpu_dsm_get_pci_dev(struct platform_device *pdev, const char* entry) -+{ -+ struct pci_dev *dev; -+ int addr; -+ -+ addr = shps_dgpu_dsm_get_pci_addr(pdev, entry); -+ if (addr < 0) -+ return ERR_PTR(addr); -+ -+ dev = pci_get_domain_bus_and_slot(0, (addr & 0xFF00) >> 8, addr & 0xFF); -+ return dev ? dev : ERR_PTR(-ENODEV); -+} -+ -+ -+static int shps_dgpu_dsm_get_power_unlocked(struct platform_device *pdev) -+{ -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ struct gpio_desc *gpio = drvdata->gpio_dgpu_power; -+ int status; -+ -+ status = gpiod_get_value_cansleep(gpio); -+ if (status < 0) -+ return status; -+ -+ return status == 0 ? SHPS_DGPU_POWER_OFF : SHPS_DGPU_POWER_ON; -+} -+ -+static int shps_dgpu_dsm_get_power(struct platform_device *pdev) -+{ -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ int status; -+ -+ mutex_lock(&drvdata->lock); -+ status = shps_dgpu_dsm_get_power_unlocked(pdev); -+ mutex_unlock(&drvdata->lock); -+ -+ return status; -+} -+ -+static int __shps_dgpu_dsm_set_power_unlocked(struct platform_device *pdev, enum shps_dgpu_power power) -+{ -+ acpi_handle handle = ACPI_HANDLE(&pdev->dev); -+ union acpi_object *result; -+ union acpi_object param; -+ -+ dev_info(&pdev->dev, "setting dGPU direct power to \'%s\'\n", shps_dgpu_power_str(power)); -+ -+ param.type = ACPI_TYPE_INTEGER; -+ param.integer.value = power == SHPS_DGPU_POWER_ON; -+ -+ result = acpi_evaluate_dsm_typed(handle, &SHPS_DSM_UUID, SHPS_DSM_REVISION, -+ SHPS_DSM_GPU_POWER, ¶m, ACPI_TYPE_BUFFER); -+ -+ if (IS_ERR_OR_NULL(result)) -+ return result ? PTR_ERR(result) : -EIO; -+ -+ // check for the expected result -+ if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) { -+ ACPI_FREE(result); -+ return -EIO; -+ } -+ -+ ACPI_FREE(result); -+ return 0; -+} -+ -+static int shps_dgpu_dsm_set_power_unlocked(struct platform_device *pdev, enum shps_dgpu_power power) -+{ -+ int status; -+ -+ if (power != SHPS_DGPU_POWER_ON && power != SHPS_DGPU_POWER_OFF) -+ return -EINVAL; -+ -+ status = shps_dgpu_dsm_get_power_unlocked(pdev); -+ if (status < 0) -+ return status; -+ if (status == power) -+ return 0; -+ -+ return __shps_dgpu_dsm_set_power_unlocked(pdev, power); -+} -+ -+static int shps_dgpu_dsm_set_power(struct platform_device *pdev, enum shps_dgpu_power power) -+{ -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ int status; -+ -+ mutex_lock(&drvdata->lock); -+ status = shps_dgpu_dsm_set_power_unlocked(pdev, power); -+ mutex_unlock(&drvdata->lock); -+ -+ return status; -+} -+ -+ -+static bool shps_rp_link_up(struct pci_dev *rp) -+{ -+ u16 lnksta = 0, sltsta = 0; -+ -+ pcie_capability_read_word(rp, PCI_EXP_LNKSTA, &lnksta); -+ pcie_capability_read_word(rp, PCI_EXP_SLTSTA, &sltsta); -+ -+ return (lnksta & PCI_EXP_LNKSTA_DLLLA) || (sltsta & PCI_EXP_SLTSTA_PDS); -+} -+ -+ -+static int shps_dgpu_rp_get_power_unlocked(struct platform_device *pdev) -+{ -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ struct pci_dev *rp = drvdata->dgpu_root_port; -+ -+ if (rp->current_state == PCI_D3hot || rp->current_state == PCI_D3cold) -+ return SHPS_DGPU_POWER_OFF; -+ else if (rp->current_state == PCI_UNKNOWN || rp->current_state == PCI_POWER_ERROR) -+ return SHPS_DGPU_POWER_UNKNOWN; -+ else -+ return SHPS_DGPU_POWER_ON; -+} -+ -+static int shps_dgpu_rp_get_power(struct platform_device *pdev) -+{ -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ int status; -+ -+ mutex_lock(&drvdata->lock); -+ status = shps_dgpu_rp_get_power_unlocked(pdev); -+ mutex_unlock(&drvdata->lock); -+ -+ return status; -+} -+ -+static int __shps_dgpu_rp_set_power_unlocked(struct platform_device *pdev, enum shps_dgpu_power power) -+{ -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ struct pci_dev *rp = drvdata->dgpu_root_port; -+ int status, i; -+ -+ dev_info(&pdev->dev, "setting dGPU power state to \'%s\'\n", shps_dgpu_power_str(power)); -+ -+ dbg_dump_drvsta(pdev, "__shps_dgpu_rp_set_power_unlocked.1"); -+ if (power == SHPS_DGPU_POWER_ON) { -+ set_bit(SHPS_STATE_BIT_RPPWRON_SYNC, &drvdata->state); -+ pci_set_power_state(rp, PCI_D0); -+ -+ if (drvdata->dgpu_root_port_state) -+ pci_load_and_free_saved_state(rp, &drvdata->dgpu_root_port_state); -+ -+ pci_restore_state(rp); -+ -+ if (!pci_is_enabled(rp)) -+ pci_enable_device(rp); -+ -+ pci_set_master(rp); -+ clear_bit(SHPS_STATE_BIT_RPPWRON_SYNC, &drvdata->state); -+ -+ set_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state); -+ } else { -+ if (!drvdata->dgpu_root_port_state) { -+ pci_save_state(rp); -+ drvdata->dgpu_root_port_state = pci_store_saved_state(rp); -+ } -+ -+ /* -+ * To properly update the hot-plug system we need to "remove" the dGPU -+ * before disabling it and sending it to D3cold. Following this, we -+ * need to wait for the link and slot status to actually change. -+ */ -+ status = shps_dgpu_dsm_set_power_unlocked(pdev, SHPS_DGPU_POWER_OFF); -+ if (status) -+ return status; -+ -+ for (i = 0; i < 20 && shps_rp_link_up(rp); i++) -+ msleep(50); -+ -+ if (shps_rp_link_up(rp)) -+ dev_err(&pdev->dev, "dGPU removal via DSM timed out\n"); -+ -+ pci_clear_master(rp); -+ -+ if (pci_is_enabled(rp)) -+ pci_disable_device(rp); -+ -+ pci_set_power_state(rp, PCI_D3cold); -+ -+ clear_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state); -+ } -+ dbg_dump_drvsta(pdev, "__shps_dgpu_rp_set_power_unlocked.2"); -+ -+ return 0; -+} -+ -+static int shps_dgpu_rp_set_power_unlocked(struct platform_device *pdev, enum shps_dgpu_power power) -+{ -+ int status; -+ -+ if (power != SHPS_DGPU_POWER_ON && power != SHPS_DGPU_POWER_OFF) -+ return -EINVAL; -+ -+ status = shps_dgpu_rp_get_power_unlocked(pdev); -+ if (status < 0) -+ return status; -+ if (status == power) -+ return 0; -+ -+ return __shps_dgpu_rp_set_power_unlocked(pdev, power); -+} -+ -+static int shps_dgpu_rp_set_power(struct platform_device *pdev, enum shps_dgpu_power power) -+{ -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ int status; -+ -+ mutex_lock(&drvdata->lock); -+ status = shps_dgpu_rp_set_power_unlocked(pdev, power); -+ mutex_unlock(&drvdata->lock); -+ -+ return status; -+} -+ -+ -+static int shps_dgpu_set_power(struct platform_device *pdev, enum shps_dgpu_power power) -+{ -+ int status; -+ -+ if (!param_dtx_latch) -+ return shps_dgpu_rp_set_power(pdev, power); -+ -+ if (power == SHPS_DGPU_POWER_ON) { -+ status = shps_dtx_latch_lock(); -+ if (status) -+ return status; -+ -+ status = shps_dgpu_rp_set_power(pdev, power); -+ if (status) -+ shps_dtx_latch_unlock(); -+ -+ return status; -+ } else { -+ status = shps_dgpu_rp_set_power(pdev, power); -+ if (status) -+ return status; -+ -+ return shps_dtx_latch_unlock(); -+ } -+} -+ -+ -+static int shps_dgpu_is_present(struct platform_device *pdev) -+{ -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ return gpiod_get_value_cansleep(drvdata->gpio_dgpu_presence); -+} -+ -+ -+static ssize_t dgpu_power_show(struct device *dev, struct device_attribute *attr, char *data) -+{ -+ struct platform_device *pdev = to_platform_device(dev); -+ int power = shps_dgpu_rp_get_power(pdev); -+ -+ if (power < 0) -+ return power; -+ -+ return sprintf(data, "%s\n", shps_dgpu_power_str(power)); -+} -+ -+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, -+ const char *data, size_t count) -+{ -+ struct platform_device *pdev = to_platform_device(dev); -+ enum shps_dgpu_power power; -+ bool b = false; -+ int status; -+ -+ status = kstrtobool(data, &b); -+ if (status) -+ return status; -+ -+ status = shps_dgpu_is_present(pdev); -+ if (status <= 0) -+ return status < 0 ? status : -EPERM; -+ -+ power = b ? SHPS_DGPU_POWER_ON : SHPS_DGPU_POWER_OFF; -+ status = shps_dgpu_set_power(pdev, power); -+ -+ return status < 0 ? status : count; -+} -+ -+static ssize_t dgpu_power_dsm_show(struct device *dev, struct device_attribute *attr, char *data) -+{ -+ struct platform_device *pdev = to_platform_device(dev); -+ int power = shps_dgpu_dsm_get_power(pdev); -+ -+ if (power < 0) -+ return power; -+ -+ return sprintf(data, "%s\n", shps_dgpu_power_str(power)); -+} -+ -+static ssize_t dgpu_power_dsm_store(struct device *dev, struct device_attribute *attr, -+ const char *data, size_t count) -+{ -+ struct platform_device *pdev = to_platform_device(dev); -+ enum shps_dgpu_power power; -+ bool b = false; -+ int status; -+ -+ status = kstrtobool(data, &b); -+ if (status) -+ return status; -+ -+ status = shps_dgpu_is_present(pdev); -+ if (status <= 0) -+ return status < 0 ? status : -EPERM; -+ -+ power = b ? SHPS_DGPU_POWER_ON : SHPS_DGPU_POWER_OFF; -+ status = shps_dgpu_dsm_set_power(pdev, power); -+ -+ return status < 0 ? status : count; -+} -+ -+static DEVICE_ATTR_RW(dgpu_power); -+static DEVICE_ATTR_RW(dgpu_power_dsm); -+ -+static struct attribute *shps_power_attrs[] = { -+ &dev_attr_dgpu_power.attr, -+ &dev_attr_dgpu_power_dsm.attr, -+ NULL, -+}; -+ATTRIBUTE_GROUPS(shps_power); -+ -+ -+static void dbg_dump_power_states(struct platform_device *pdev, const char *prefix) -+{ -+ enum shps_dgpu_power power_dsm; -+ enum shps_dgpu_power power_rp; -+ int status; -+ -+ status = shps_dgpu_rp_get_power_unlocked(pdev); -+ if (status < 0) -+ dev_err(&pdev->dev, "%s: failed to get root-port power state: %d\n", prefix, status); -+ power_rp = status; -+ -+ status = shps_dgpu_rp_get_power_unlocked(pdev); -+ if (status < 0) -+ dev_err(&pdev->dev, "%s: failed to get direct power state: %d\n", prefix, status); -+ power_dsm = status; -+ -+ dev_dbg(&pdev->dev, "%s: root-port power state: %d\n", prefix, power_rp); -+ dev_dbg(&pdev->dev, "%s: direct power state: %d\n", prefix, power_dsm); -+} -+ -+static void dbg_dump_pciesta(struct platform_device *pdev, const char *prefix) -+{ -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ struct pci_dev *rp = drvdata->dgpu_root_port; -+ u16 lnksta, lnksta2, sltsta, sltsta2; -+ -+ pcie_capability_read_word(rp, PCI_EXP_LNKSTA, &lnksta); -+ pcie_capability_read_word(rp, PCI_EXP_LNKSTA2, &lnksta2); -+ pcie_capability_read_word(rp, PCI_EXP_SLTSTA, &sltsta); -+ pcie_capability_read_word(rp, PCI_EXP_SLTSTA2, &sltsta2); -+ -+ dev_dbg(&pdev->dev, "%s: LNKSTA: 0x%04x", prefix, lnksta); -+ dev_dbg(&pdev->dev, "%s: LNKSTA2: 0x%04x", prefix, lnksta2); -+ dev_dbg(&pdev->dev, "%s: SLTSTA: 0x%04x", prefix, sltsta); -+ dev_dbg(&pdev->dev, "%s: SLTSTA2: 0x%04x", prefix, sltsta2); -+} -+ -+static void dbg_dump_drvsta(struct platform_device *pdev, const char *prefix) -+{ -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ struct pci_dev *rp = drvdata->dgpu_root_port; -+ -+ dev_dbg(&pdev->dev, "%s: RP power: %d", prefix, rp->current_state); -+ dev_dbg(&pdev->dev, "%s: RP state saved: %d", prefix, rp->state_saved); -+ dev_dbg(&pdev->dev, "%s: RP state stored: %d", prefix, !!drvdata->dgpu_root_port_state); -+ dev_dbg(&pdev->dev, "%s: RP enabled: %d", prefix, atomic_read(&rp->enable_cnt)); -+ dev_dbg(&pdev->dev, "%s: RP mastered: %d", prefix, rp->is_busmaster); -+} -+ -+ -+static int shps_pm_prepare(struct device *dev) -+{ -+ struct platform_device *pdev = to_platform_device(dev); -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ bool pwrtgt; -+ int status = 0; -+ -+ dbg_dump_power_states(pdev, "shps_pm_prepare"); -+ -+ if (param_dgpu_power_susp != SHPS_DGPU_MP_POWER_ASIS) { -+ pwrtgt = test_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state); -+ -+ status = shps_dgpu_set_power(pdev, param_dgpu_power_susp); -+ if (status) { -+ dev_err(&pdev->dev, "failed to power %s dGPU: %d\n", -+ param_dgpu_power_susp == SHPS_DGPU_MP_POWER_OFF ? "off" : "on", -+ status); -+ return status; -+ } -+ -+ if (pwrtgt) -+ set_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state); -+ else -+ clear_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state); -+ } -+ -+ return 0; -+} -+ -+static void shps_pm_complete(struct device *dev) -+{ -+ struct platform_device *pdev = to_platform_device(dev); -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ int status; -+ -+ dbg_dump_power_states(pdev, "shps_pm_complete"); -+ dbg_dump_pciesta(pdev, "shps_pm_complete"); -+ dbg_dump_drvsta(pdev, "shps_pm_complete.1"); -+ -+ // update power target, dGPU may have been detached while suspended -+ status = shps_dgpu_is_present(pdev); -+ if (status < 0) { -+ dev_err(&pdev->dev, "failed to get dGPU presence: %d\n", status); -+ return; -+ } else if (status == 0) { -+ clear_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state); -+ } -+ -+ /* -+ * During resume, the PCIe core will power on the root-port, which in turn -+ * will power on the dGPU. Most of the state synchronization is already -+ * handled via the SAN RQSG handler, so it is in a fully consistent -+ * on-state here. If requested, turn it off here. -+ * -+ * As there seem to be some synchronization issues turning off the dGPU -+ * directly after the power-on SAN RQSG notification during the resume -+ * process, let's do this here. -+ * -+ * TODO/FIXME: -+ * This does not combat unhandled power-ons when the device is not fully -+ * resumed, i.e. re-suspended before shps_pm_complete is called. Those -+ * should normally not be an issue, but the dGPU does get hot even though -+ * it is suspended, so ideally we want to keep it off. -+ */ -+ if (!test_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state)) { -+ status = shps_dgpu_set_power(pdev, SHPS_DGPU_POWER_OFF); -+ if (status) -+ dev_err(&pdev->dev, "failed to power-off dGPU: %d\n", status); -+ } -+ -+ dbg_dump_drvsta(pdev, "shps_pm_complete.2"); -+} -+ -+static int shps_pm_suspend(struct device *dev) -+{ -+ struct platform_device *pdev = to_platform_device(dev); -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ int status; -+ -+ if (device_may_wakeup(dev)) { -+ status = enable_irq_wake(drvdata->irq_base_presence); -+ if (status) -+ return status; -+ -+ set_bit(SHPS_STATE_BIT_WAKE_ENABLED, &drvdata->state); -+ } -+ -+ return 0; -+} -+ -+static int shps_pm_resume(struct device *dev) -+{ -+ struct platform_device *pdev = to_platform_device(dev); -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ int status = 0; -+ -+ if (test_and_clear_bit(SHPS_STATE_BIT_WAKE_ENABLED, &drvdata->state)) { -+ status = disable_irq_wake(drvdata->irq_base_presence); -+ } -+ -+ return status; -+} -+ -+static void shps_shutdown(struct platform_device *pdev) -+{ -+ int status; -+ -+ /* -+ * Turn on dGPU before shutting down. This allows the core drivers to -+ * properly shut down the device. If we don't do this, the pcieport driver -+ * will complain that the device has already been disabled. -+ */ -+ status = shps_dgpu_set_power(pdev, SHPS_DGPU_POWER_ON); -+ if (status) -+ dev_err(&pdev->dev, "failed to turn on dGPU: %d\n", status); -+} -+ -+static int shps_dgpu_detached(struct platform_device *pdev) -+{ -+ dbg_dump_power_states(pdev, "shps_dgpu_detached"); -+ return shps_dgpu_set_power(pdev, SHPS_DGPU_POWER_OFF); -+} -+ -+static int shps_dgpu_attached(struct platform_device *pdev) -+{ -+ dbg_dump_power_states(pdev, "shps_dgpu_attached"); -+ return 0; -+} -+ -+static int shps_dgpu_powered_on(struct platform_device *pdev) -+{ -+ /* -+ * This function gets called directly after a power-state transition of -+ * the dGPU root port out of D3cold state, indicating a power-on of the -+ * dGPU. Specifically, this function is called from the RQSG handler of -+ * SAN, invoked by the ACPI _ON method of the dGPU root port. This means -+ * that this function is run inside `pci_set_power_state(rp, ...)` -+ * syncrhonously and thus returns before the `pci_set_power_state` call -+ * does. -+ * -+ * `pci_set_power_state` may either be called by us or when the PCI -+ * subsystem decides to power up the root port (e.g. during resume). Thus -+ * we should use this function to ensure that the dGPU and root port -+ * states are consistent when an unexpected power-up is encountered. -+ */ -+ -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ struct pci_dev *rp = drvdata->dgpu_root_port; -+ int status; -+ -+ dbg_dump_drvsta(pdev, "shps_dgpu_powered_on.1"); -+ -+ // if we caused the root port to power-on, return -+ if (test_bit(SHPS_STATE_BIT_RPPWRON_SYNC, &drvdata->state)) -+ return 0; -+ -+ // if dGPU is not present, force power-target to off and return -+ status = shps_dgpu_is_present(pdev); -+ if (status == 0) -+ clear_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state); -+ if (status <= 0) -+ return status; -+ -+ mutex_lock(&drvdata->lock); -+ -+ dbg_dump_power_states(pdev, "shps_dgpu_powered_on.1"); -+ dbg_dump_pciesta(pdev, "shps_dgpu_powered_on.1"); -+ if (drvdata->dgpu_root_port_state) -+ pci_load_and_free_saved_state(rp, &drvdata->dgpu_root_port_state); -+ pci_restore_state(rp); -+ if (!pci_is_enabled(rp)) -+ pci_enable_device(rp); -+ pci_set_master(rp); -+ dbg_dump_drvsta(pdev, "shps_dgpu_powered_on.2"); -+ dbg_dump_power_states(pdev, "shps_dgpu_powered_on.2"); -+ dbg_dump_pciesta(pdev, "shps_dgpu_powered_on.2"); -+ -+ mutex_unlock(&drvdata->lock); -+ -+ if (!test_bit(SHPS_STATE_BIT_PWRTGT, &drvdata->state)) { -+ dev_warn(&pdev->dev, "unexpected dGPU power-on detected"); -+ // TODO: schedule state re-check and update -+ } -+ -+ return 0; -+} -+ -+ -+static int shps_dgpu_handle_rqsg(struct surface_sam_san_rqsg *rqsg, void *data) -+{ -+ struct platform_device *pdev = data; -+ -+ if (rqsg->tc == SAM_DGPU_TC && rqsg->cid == SAM_DGPU_CID_POWERON) -+ return shps_dgpu_powered_on(pdev); -+ -+ dev_warn(&pdev->dev, "unimplemented dGPU request: RQSG(0x%02x, 0x%02x, 0x%02x)", -+ rqsg->tc, rqsg->cid, rqsg->iid); -+ return 0; -+} -+ -+static irqreturn_t shps_dgpu_presence_irq(int irq, void *data) -+{ -+ struct platform_device *pdev = data; -+ bool dgpu_present; -+ int status; -+ -+ status = shps_dgpu_is_present(pdev); -+ if (status < 0) { -+ dev_err(&pdev->dev, "failed to check physical dGPU presence: %d\n", status); -+ return IRQ_HANDLED; -+ } -+ -+ dgpu_present = status != 0; -+ dev_info(&pdev->dev, "dGPU physically %s\n", dgpu_present ? "attached" : "detached"); -+ -+ if (dgpu_present) -+ status = shps_dgpu_attached(pdev); -+ else -+ status = shps_dgpu_detached(pdev); -+ -+ if (status) -+ dev_err(&pdev->dev, "error handling dGPU interrupt: %d\n", status); -+ -+ return IRQ_HANDLED; -+} -+ -+static irqreturn_t shps_base_presence_irq(int irq, void *data) -+{ -+ return IRQ_HANDLED; // nothing to do, just wake -+} -+ -+ -+static int shps_gpios_setup(struct platform_device *pdev) -+{ -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ struct gpio_desc *gpio_dgpu_power; -+ struct gpio_desc *gpio_dgpu_presence; -+ struct gpio_desc *gpio_base_presence; -+ int status; -+ -+ // get GPIOs -+ gpio_dgpu_power = devm_gpiod_get(&pdev->dev, "dgpu_power", GPIOD_IN); -+ if (IS_ERR(gpio_dgpu_power)) { -+ status = PTR_ERR(gpio_dgpu_power); -+ goto err_out; -+ } -+ -+ gpio_dgpu_presence = devm_gpiod_get(&pdev->dev, "dgpu_presence", GPIOD_IN); -+ if (IS_ERR(gpio_dgpu_presence)) { -+ status = PTR_ERR(gpio_dgpu_presence); -+ goto err_out; -+ } -+ -+ gpio_base_presence = devm_gpiod_get(&pdev->dev, "base_presence", GPIOD_IN); -+ if (IS_ERR(gpio_base_presence)) { -+ status = PTR_ERR(gpio_base_presence); -+ goto err_out; -+ } -+ -+ // export GPIOs -+ status = gpiod_export(gpio_dgpu_power, false); -+ if (status) -+ goto err_out; -+ -+ status = gpiod_export(gpio_dgpu_presence, false); -+ if (status) -+ goto err_export_dgpu_presence; -+ -+ status = gpiod_export(gpio_base_presence, false); -+ if (status) -+ goto err_export_base_presence; -+ -+ // create sysfs links -+ status = gpiod_export_link(&pdev->dev, "gpio-dgpu_power", gpio_dgpu_power); -+ if (status) -+ goto err_link_dgpu_power; -+ -+ status = gpiod_export_link(&pdev->dev, "gpio-dgpu_presence", gpio_dgpu_presence); -+ if (status) -+ goto err_link_dgpu_presence; -+ -+ status = gpiod_export_link(&pdev->dev, "gpio-base_presence", gpio_base_presence); -+ if (status) -+ goto err_link_base_presence; -+ -+ drvdata->gpio_dgpu_power = gpio_dgpu_power; -+ drvdata->gpio_dgpu_presence = gpio_dgpu_presence; -+ drvdata->gpio_base_presence = gpio_base_presence; -+ return 0; -+ -+err_link_base_presence: -+ sysfs_remove_link(&pdev->dev.kobj, "gpio-dgpu_presence"); -+err_link_dgpu_presence: -+ sysfs_remove_link(&pdev->dev.kobj, "gpio-dgpu_power"); -+err_link_dgpu_power: -+ gpiod_unexport(gpio_base_presence); -+err_export_base_presence: -+ gpiod_unexport(gpio_dgpu_presence); -+err_export_dgpu_presence: -+ gpiod_unexport(gpio_dgpu_power); -+err_out: -+ return status; -+} -+ -+static void shps_gpios_remove(struct platform_device *pdev) -+{ -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ -+ sysfs_remove_link(&pdev->dev.kobj, "gpio-base_presence"); -+ sysfs_remove_link(&pdev->dev.kobj, "gpio-dgpu_presence"); -+ sysfs_remove_link(&pdev->dev.kobj, "gpio-dgpu_power"); -+ gpiod_unexport(drvdata->gpio_base_presence); -+ gpiod_unexport(drvdata->gpio_dgpu_presence); -+ gpiod_unexport(drvdata->gpio_dgpu_power); -+} -+ -+static int shps_gpios_setup_irq(struct platform_device *pdev) -+{ -+ const int irqf_dgpu = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; -+ const int irqf_base = IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ int status; -+ -+ status = gpiod_to_irq(drvdata->gpio_base_presence); -+ if (status < 0) -+ return status; -+ drvdata->irq_base_presence = status; -+ -+ status = gpiod_to_irq(drvdata->gpio_dgpu_presence); -+ if (status < 0) -+ return status; -+ drvdata->irq_dgpu_presence = status; -+ -+ status = request_irq(drvdata->irq_base_presence, -+ shps_base_presence_irq, irqf_base, -+ "shps_base_presence_irq", pdev); -+ if (status) -+ return status; -+ -+ status = request_threaded_irq(drvdata->irq_dgpu_presence, -+ NULL, shps_dgpu_presence_irq, irqf_dgpu, -+ "shps_dgpu_presence_irq", pdev); -+ if (status) { -+ free_irq(drvdata->irq_base_presence, pdev); -+ return status; -+ } -+ -+ return 0; -+} -+ -+static void shps_gpios_remove_irq(struct platform_device *pdev) -+{ -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ -+ free_irq(drvdata->irq_base_presence, pdev); -+ free_irq(drvdata->irq_dgpu_presence, pdev); -+} -+ -+static int shps_probe(struct platform_device *pdev) -+{ -+ struct acpi_device *shps_dev = ACPI_COMPANION(&pdev->dev); -+ struct shps_driver_data *drvdata; -+ struct device_link *link; -+ int power, status; -+ -+ if (gpiod_count(&pdev->dev, NULL) < 0) -+ return -ENODEV; -+ -+ // link to SSH -+ status = surface_sam_ssh_consumer_register(&pdev->dev); -+ if (status) { -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ } -+ -+ // link to SAN -+ status = surface_sam_san_consumer_register(&pdev->dev, 0); -+ if (status) { -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ } -+ -+ status = acpi_dev_add_driver_gpios(shps_dev, shps_acpi_gpios); -+ if (status) -+ return status; -+ -+ drvdata = kzalloc(sizeof(struct shps_driver_data), GFP_KERNEL); -+ if (!drvdata) { -+ status = -ENOMEM; -+ goto err_drvdata; -+ } -+ mutex_init(&drvdata->lock); -+ platform_set_drvdata(pdev, drvdata); -+ -+ drvdata->dgpu_root_port = shps_dgpu_dsm_get_pci_dev(pdev, SHPS_DSM_GPU_ADDRS_RP); -+ if (IS_ERR(drvdata->dgpu_root_port)) { -+ status = PTR_ERR(drvdata->dgpu_root_port); -+ goto err_rp_lookup; -+ } -+ -+ status = shps_gpios_setup(pdev); -+ if (status) -+ goto err_gpio; -+ -+ status = shps_gpios_setup_irq(pdev); -+ if (status) -+ goto err_gpio_irqs; -+ -+ status = device_add_groups(&pdev->dev, shps_power_groups); -+ if (status) -+ goto err_devattr; -+ -+ link = device_link_add(&pdev->dev, &drvdata->dgpu_root_port->dev, -+ DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER); -+ if (!link) -+ goto err_devlink; -+ -+ surface_sam_san_set_rqsg_handler(shps_dgpu_handle_rqsg, pdev); -+ -+ // if dGPU is not present turn-off root-port, else obey module param -+ status = shps_dgpu_is_present(pdev); -+ if (status < 0) -+ goto err_devlink; -+ -+ power = status == 0 ? SHPS_DGPU_POWER_OFF : param_dgpu_power_init; -+ if (power != SHPS_DGPU_MP_POWER_ASIS) { -+ status = shps_dgpu_set_power(pdev, power); -+ if (status) -+ goto err_devlink; -+ } -+ -+ device_init_wakeup(&pdev->dev, true); -+ return 0; -+ -+err_devlink: -+ device_remove_groups(&pdev->dev, shps_power_groups); -+err_devattr: -+ shps_gpios_remove_irq(pdev); -+err_gpio_irqs: -+ shps_gpios_remove(pdev); -+err_gpio: -+ pci_dev_put(drvdata->dgpu_root_port); -+err_rp_lookup: -+ platform_set_drvdata(pdev, NULL); -+ kfree(drvdata); -+err_drvdata: -+ acpi_dev_remove_driver_gpios(shps_dev); -+ return status; -+} -+ -+static int shps_remove(struct platform_device *pdev) -+{ -+ struct acpi_device *shps_dev = ACPI_COMPANION(&pdev->dev); -+ struct shps_driver_data *drvdata = platform_get_drvdata(pdev); -+ int status; -+ -+ if (param_dgpu_power_exit != SHPS_DGPU_MP_POWER_ASIS) { -+ status = shps_dgpu_set_power(pdev, param_dgpu_power_exit); -+ if (status) -+ dev_err(&pdev->dev, "failed to set dGPU power state: %d\n", status); -+ } -+ -+ device_set_wakeup_capable(&pdev->dev, false); -+ surface_sam_san_set_rqsg_handler(NULL, NULL); -+ device_remove_groups(&pdev->dev, shps_power_groups); -+ shps_gpios_remove_irq(pdev); -+ shps_gpios_remove(pdev); -+ pci_dev_put(drvdata->dgpu_root_port); -+ platform_set_drvdata(pdev, NULL); -+ kfree(drvdata); -+ -+ acpi_dev_remove_driver_gpios(shps_dev); -+ return 0; -+} -+ -+ -+static const struct dev_pm_ops shps_pm_ops = { -+ .prepare = shps_pm_prepare, -+ .complete = shps_pm_complete, -+ .suspend = shps_pm_suspend, -+ .resume = shps_pm_resume, -+}; -+ -+static const struct acpi_device_id shps_acpi_match[] = { -+ { "MSHW0153", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, shps_acpi_match); -+ -+struct platform_driver surface_sam_hps = { -+ .probe = shps_probe, -+ .remove = shps_remove, -+ .shutdown = shps_shutdown, -+ .driver = { -+ .name = "surface_dgpu_hps", -+ .acpi_match_table = ACPI_PTR(shps_acpi_match), -+ .pm = &shps_pm_ops, -+ }, -+}; -+module_platform_driver(surface_sam_hps); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Hot-Plug System (HPS) and dGPU power-state Driver for Surface Book 2"); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_san.c b/drivers/platform/x86/surface_sam/surface_sam_san.c -new file mode 100644 -index 000000000000..aa0cfc4262be ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_san.c -@@ -0,0 +1,901 @@ -+/* -+ * Surface ACPI Notify (SAN) and ACPI integration driver for SAM. -+ * Translates communication from ACPI to SSH and back. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "surface_sam_ssh.h" -+#include "surface_sam_san.h" -+ -+ -+#define SAN_RQST_RETRY 5 -+ -+#define SAN_DSM_REVISION 0 -+#define SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT 0x09 -+ -+static const guid_t SAN_DSM_UUID = -+ GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d, -+ 0x48, 0x7c, 0x91, 0xab, 0x3c); -+ -+#define SAM_EVENT_DELAY_PWR_ADAPTER msecs_to_jiffies(5000) -+#define SAM_EVENT_DELAY_PWR_BST msecs_to_jiffies(2500) -+ -+#define SAM_EVENT_PWR_TC 0x02 -+#define SAM_EVENT_PWR_RQID 0x0002 -+#define SAM_EVENT_PWR_CID_BIX 0x15 -+#define SAM_EVENT_PWR_CID_BST 0x16 -+#define SAM_EVENT_PWR_CID_ADAPTER 0x17 -+#define SAM_EVENT_PWR_CID_DPTF 0x4f -+ -+#define SAM_EVENT_TEMP_TC 0x03 -+#define SAM_EVENT_TEMP_RQID 0x0003 -+#define SAM_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT 0x0b -+ -+#define SAN_RQST_TAG "surface_sam_san: rqst: " -+#define SAN_RQSG_TAG "surface_sam_san: rqsg: " -+ -+#define SAN_QUIRK_BASE_STATE_DELAY 1000 -+ -+ -+struct san_acpi_consumer { -+ char *path; -+ bool required; -+ u32 flags; -+}; -+ -+struct san_opreg_context { -+ struct acpi_connection_info connection; -+ struct device *dev; -+}; -+ -+struct san_consumer_link { -+ const struct san_acpi_consumer *properties; -+ struct device_link *link; -+}; -+ -+struct san_consumers { -+ u32 num; -+ struct san_consumer_link *links; -+}; -+ -+struct san_drvdata { -+ struct san_opreg_context opreg_ctx; -+ struct san_consumers consumers; -+ bool has_power_events; -+}; -+ -+struct gsb_data_in { -+ u8 cv; -+} __packed; -+ -+struct gsb_data_rqsx { -+ u8 cv; // command value (should be 0x01 or 0x03) -+ u8 tc; // target controller -+ u8 tid; // expected to be 0x01, could be revision -+ u8 iid; // target sub-controller (e.g. primary vs. secondary battery) -+ u8 snc; // expect-response-flag -+ u8 cid; // command ID -+ u8 cdl; // payload length -+ u8 _pad; // padding -+ u8 pld[0]; // payload -+} __packed; -+ -+struct gsb_data_etwl { -+ u8 cv; // command value (should be 0x02) -+ u8 etw3; // ? -+ u8 etw4; // ? -+ u8 msg[0]; // error message (ASCIIZ) -+} __packed; -+ -+struct gsb_data_out { -+ u8 status; // _SSH communication status -+ u8 len; // _SSH payload length -+ u8 pld[0]; // _SSH payload -+} __packed; -+ -+union gsb_buffer_data { -+ struct gsb_data_in in; // common input -+ struct gsb_data_rqsx rqsx; // RQSX input -+ struct gsb_data_etwl etwl; // ETWL input -+ struct gsb_data_out out; // output -+}; -+ -+struct gsb_buffer { -+ u8 status; // GSB AttribRawProcess status -+ u8 len; // GSB AttribRawProcess length -+ union gsb_buffer_data data; -+} __packed; -+ -+ -+enum san_pwr_event { -+ SAN_PWR_EVENT_BAT1_STAT = 0x03, -+ SAN_PWR_EVENT_BAT1_INFO = 0x04, -+ SAN_PWR_EVENT_ADP1_STAT = 0x05, -+ SAN_PWR_EVENT_ADP1_INFO = 0x06, -+ SAN_PWR_EVENT_BAT2_STAT = 0x07, -+ SAN_PWR_EVENT_BAT2_INFO = 0x08, -+}; -+ -+ -+static int sam_san_default_rqsg_handler(struct surface_sam_san_rqsg *rqsg, void *data); -+ -+struct sam_san_rqsg_if { -+ struct mutex lock; -+ struct device *san_dev; -+ surface_sam_san_rqsg_handler_fn handler; -+ void *handler_data; -+}; -+ -+static struct sam_san_rqsg_if rqsg_if = { -+ .lock = __MUTEX_INITIALIZER(rqsg_if.lock), -+ .san_dev = NULL, -+ .handler = sam_san_default_rqsg_handler, -+ .handler_data = NULL, -+}; -+ -+int surface_sam_san_consumer_register(struct device *consumer, u32 flags) -+{ -+ const u32 valid = DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE; -+ int status; -+ -+ if ((flags | valid) != valid) -+ return -EINVAL; -+ -+ flags |= DL_FLAG_AUTOREMOVE_CONSUMER; -+ -+ mutex_lock(&rqsg_if.lock); -+ if (rqsg_if.san_dev) -+ status = device_link_add(consumer, rqsg_if.san_dev, flags) ? 0 : -EINVAL; -+ else -+ status = -ENXIO; -+ mutex_unlock(&rqsg_if.lock); -+ return status; -+} -+EXPORT_SYMBOL_GPL(surface_sam_san_consumer_register); -+ -+int surface_sam_san_set_rqsg_handler(surface_sam_san_rqsg_handler_fn fn, void *data) -+{ -+ int status = -EBUSY; -+ -+ mutex_lock(&rqsg_if.lock); -+ -+ if (rqsg_if.handler == sam_san_default_rqsg_handler || !fn) { -+ rqsg_if.handler = fn ? fn : sam_san_default_rqsg_handler; -+ rqsg_if.handler_data = data; -+ status = 0; -+ } -+ -+ mutex_unlock(&rqsg_if.lock); -+ return status; -+} -+EXPORT_SYMBOL_GPL(surface_sam_san_set_rqsg_handler); -+ -+int san_call_rqsg_handler(struct surface_sam_san_rqsg *rqsg) -+{ -+ int status; -+ -+ mutex_lock(&rqsg_if.lock); -+ status = rqsg_if.handler(rqsg, rqsg_if.handler_data); -+ mutex_unlock(&rqsg_if.lock); -+ -+ return status; -+} -+ -+static int sam_san_default_rqsg_handler(struct surface_sam_san_rqsg *rqsg, void *data) -+{ -+ pr_warn(SAN_RQSG_TAG "unhandled request: RQSG(0x%02x, 0x%02x, 0x%02x)\n", -+ rqsg->tc, rqsg->cid, rqsg->iid); -+ -+ return 0; -+} -+ -+ -+static int san_acpi_notify_power_event(struct device *dev, enum san_pwr_event event) -+{ -+ acpi_handle san = ACPI_HANDLE(dev); -+ union acpi_object *obj; -+ -+ dev_dbg(dev, "notify power event 0x%02x\n", event); -+ obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION, -+ (u8) event, NULL, ACPI_TYPE_BUFFER); -+ -+ if (IS_ERR_OR_NULL(obj)) { -+ return obj ? PTR_ERR(obj) : -ENXIO; -+ } -+ -+ if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) { -+ dev_err(dev, "got unexpected result from _DSM\n"); -+ return -EFAULT; -+ } -+ -+ ACPI_FREE(obj); -+ return 0; -+} -+ -+static int san_acpi_notify_sensor_trip_point(struct device *dev, u8 iid) -+{ -+ acpi_handle san = ACPI_HANDLE(dev); -+ union acpi_object *obj; -+ union acpi_object param; -+ -+ param.type = ACPI_TYPE_INTEGER; -+ param.integer.value = iid; -+ -+ obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION, -+ SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT, -+ ¶m, ACPI_TYPE_BUFFER); -+ -+ if (IS_ERR_OR_NULL(obj)) { -+ return obj ? PTR_ERR(obj) : -ENXIO; -+ } -+ -+ if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) { -+ dev_err(dev, "got unexpected result from _DSM\n"); -+ return -EFAULT; -+ } -+ -+ ACPI_FREE(obj); -+ return 0; -+} -+ -+ -+inline static int san_evt_power_adapter(struct device *dev, struct surface_sam_ssh_event *event) -+{ -+ int status; -+ -+ status = san_acpi_notify_power_event(dev, SAN_PWR_EVENT_ADP1_STAT); -+ if (status) { -+ dev_err(dev, "error handling power event (cid = %x)\n", event->cid); -+ return status; -+ } -+ -+ /* -+ * Enusre that the battery states get updated correctly. -+ * When the battery is fully charged and an adapter is plugged in, it -+ * sometimes is not updated correctly, instead showing it as charging. -+ * Explicitly trigger battery updates to fix this. -+ */ -+ -+ status = san_acpi_notify_power_event(dev, SAN_PWR_EVENT_BAT1_STAT); -+ if (status) { -+ dev_err(dev, "error handling power event (cid = %x)\n", event->cid); -+ return status; -+ } -+ -+ status = san_acpi_notify_power_event(dev, SAN_PWR_EVENT_BAT2_STAT); -+ if (status) { -+ dev_err(dev, "error handling power event (cid = %x)\n", event->cid); -+ return status; -+ } -+ -+ return 0; -+} -+ -+inline static int san_evt_power_bix(struct device *dev, struct surface_sam_ssh_event *event) -+{ -+ enum san_pwr_event evcode; -+ int status; -+ -+ if (event->iid == 0x02) { -+ evcode = SAN_PWR_EVENT_BAT2_INFO; -+ } else { -+ evcode = SAN_PWR_EVENT_BAT1_INFO; -+ } -+ -+ status = san_acpi_notify_power_event(dev, evcode); -+ if (status) { -+ dev_err(dev, "error handling power event (cid = %x)\n", event->cid); -+ return status; -+ } -+ -+ return 0; -+} -+ -+inline static int san_evt_power_bst(struct device *dev, struct surface_sam_ssh_event *event) -+{ -+ enum san_pwr_event evcode; -+ int status; -+ -+ if (event->iid == 0x02) { -+ evcode = SAN_PWR_EVENT_BAT2_STAT; -+ } else { -+ evcode = SAN_PWR_EVENT_BAT1_STAT; -+ } -+ -+ status = san_acpi_notify_power_event(dev, evcode); -+ if (status) { -+ dev_err(dev, "error handling power event (cid = %x)\n", event->cid); -+ return status; -+ } -+ -+ return 0; -+} -+ -+static unsigned long san_evt_power_delay(struct surface_sam_ssh_event *event, void *data) -+{ -+ switch (event->cid) { -+ case SAM_EVENT_PWR_CID_ADAPTER: -+ /* -+ * Wait for battery state to update before signalling adapter change. -+ */ -+ return SAM_EVENT_DELAY_PWR_ADAPTER; -+ -+ case SAM_EVENT_PWR_CID_BST: -+ /* -+ * Ensure we do not miss anything important due to caching. -+ */ -+ return SAM_EVENT_DELAY_PWR_BST; -+ -+ case SAM_EVENT_PWR_CID_BIX: -+ case SAM_EVENT_PWR_CID_DPTF: -+ default: -+ return 0; -+ } -+} -+ -+static int san_evt_power(struct surface_sam_ssh_event *event, void *data) -+{ -+ struct device *dev = (struct device *)data; -+ -+ switch (event->cid) { -+ case SAM_EVENT_PWR_CID_BIX: -+ return san_evt_power_bix(dev, event); -+ -+ case SAM_EVENT_PWR_CID_BST: -+ return san_evt_power_bst(dev, event); -+ -+ case SAM_EVENT_PWR_CID_ADAPTER: -+ return san_evt_power_adapter(dev, event); -+ -+ case SAM_EVENT_PWR_CID_DPTF: -+ /* -+ * Ignored for now. -+ * This signals a change in Intel DPTF PMAX, and possibly other -+ * fields. Ignore for now as there is no corresponding _DSM call and -+ * DPTF is implemented via a separate INT3407 device. -+ * -+ * The payload of this event is: [u32 PMAX, unknown...]. -+ */ -+ return 0; -+ -+ default: -+ dev_warn(dev, "unhandled power event (cid = %x)\n", event->cid); -+ } -+ -+ return 0; -+} -+ -+ -+inline static int san_evt_thermal_notify(struct device *dev, struct surface_sam_ssh_event *event) -+{ -+ int status; -+ -+ status = san_acpi_notify_sensor_trip_point(dev, event->iid); -+ if (status) { -+ dev_err(dev, "error handling thermal event (cid = %x)\n", event->cid); -+ return status; -+ } -+ -+ return 0; -+} -+ -+static int san_evt_thermal(struct surface_sam_ssh_event *event, void *data) -+{ -+ struct device *dev = (struct device *)data; -+ -+ switch (event->cid) { -+ case SAM_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT: -+ return san_evt_thermal_notify(dev, event); -+ -+ default: -+ dev_warn(dev, "unhandled thermal event (cid = %x)\n", event->cid); -+ } -+ -+ return 0; -+} -+ -+ -+static struct gsb_data_rqsx -+*san_validate_rqsx(struct device *dev, const char *type, struct gsb_buffer *buffer) -+{ -+ struct gsb_data_rqsx *rqsx = &buffer->data.rqsx; -+ -+ if (buffer->len < 8) { -+ dev_err(dev, "invalid %s package (len = %d)\n", -+ type, buffer->len); -+ return NULL; -+ } -+ -+ if (rqsx->cdl != buffer->len - 8) { -+ dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n", -+ type, buffer->len, rqsx->cdl); -+ return NULL; -+ } -+ -+ if (rqsx->tid != 0x01) { -+ dev_warn(dev, "unsupported %s package (tid = 0x%02x)\n", -+ type, rqsx->tid); -+ return NULL; -+ } -+ -+ return rqsx; -+} -+ -+static acpi_status -+san_etwl(struct san_opreg_context *ctx, struct gsb_buffer *buffer) -+{ -+ struct gsb_data_etwl *etwl = &buffer->data.etwl; -+ -+ if (buffer->len < 3) { -+ dev_err(ctx->dev, "invalid ETWL package (len = %d)\n", buffer->len); -+ return AE_OK; -+ } -+ -+ dev_err(ctx->dev, "ETWL(0x%02x, 0x%02x): %.*s\n", -+ etwl->etw3, etwl->etw4, -+ buffer->len - 3, (char *)etwl->msg); -+ -+ // indicate success -+ buffer->status = 0x00; -+ buffer->len = 0x00; -+ -+ return AE_OK; -+} -+ -+static acpi_status -+san_rqst(struct san_opreg_context *ctx, struct gsb_buffer *buffer) -+{ -+ struct gsb_data_rqsx *gsb_rqst = san_validate_rqsx(ctx->dev, "RQST", buffer); -+ struct surface_sam_ssh_rqst rqst = {}; -+ struct surface_sam_ssh_buf result = {}; -+ int status = 0; -+ int try; -+ -+ if (!gsb_rqst) { -+ return AE_OK; -+ } -+ -+ rqst.tc = gsb_rqst->tc; -+ rqst.cid = gsb_rqst->cid; -+ rqst.iid = gsb_rqst->iid; -+ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; -+ rqst.snc = gsb_rqst->snc; -+ rqst.cdl = gsb_rqst->cdl; -+ rqst.pld = &gsb_rqst->pld[0]; -+ -+ result.cap = SURFACE_SAM_SSH_MAX_RQST_RESPONSE; -+ result.len = 0; -+ result.data = kzalloc(result.cap, GFP_KERNEL); -+ -+ if (!result.data) { -+ return AE_NO_MEMORY; -+ } -+ -+ for (try = 0; try < SAN_RQST_RETRY; try++) { -+ if (try) { -+ dev_warn(ctx->dev, SAN_RQST_TAG "IO error occured, trying again\n"); -+ } -+ -+ status = surface_sam_ssh_rqst(&rqst, &result); -+ if (status != -EIO) break; -+ } -+ -+ if (rqst.tc == 0x11 && rqst.cid == 0x0D && status == -EPERM) { -+ /* Base state quirk: -+ * The base state may be queried from ACPI when the EC is still -+ * suspended. In this case it will return '-EPERM'. This query -+ * will only be triggered from the ACPI lid GPE interrupt, thus -+ * we are either in laptop or studio mode (base status 0x01 or -+ * 0x02). Furthermore, we will only get here if the device (and -+ * EC) have been suspended. -+ * -+ * We now assume that the device is in laptop mode (0x01). This -+ * has the drawback that it will wake the device when unfolding -+ * it in studio mode, but it also allows us to avoid actively -+ * waiting for the EC to wake up, which may incur a notable -+ * delay. -+ */ -+ -+ buffer->status = 0x00; -+ buffer->len = 0x03; -+ buffer->data.out.status = 0x00; -+ buffer->data.out.len = 0x01; -+ buffer->data.out.pld[0] = 0x01; -+ -+ } else if (!status) { // success -+ buffer->status = 0x00; -+ buffer->len = result.len + 2; -+ buffer->data.out.status = 0x00; -+ buffer->data.out.len = result.len; -+ memcpy(&buffer->data.out.pld[0], result.data, result.len); -+ -+ } else { // failure -+ dev_err(ctx->dev, SAN_RQST_TAG "failed with error %d\n", status); -+ buffer->status = 0x00; -+ buffer->len = 0x02; -+ buffer->data.out.status = 0x01; // indicate _SSH error -+ buffer->data.out.len = 0x00; -+ } -+ -+ kfree(result.data); -+ -+ return AE_OK; -+} -+ -+static acpi_status -+san_rqsg(struct san_opreg_context *ctx, struct gsb_buffer *buffer) -+{ -+ struct gsb_data_rqsx *gsb_rqsg = san_validate_rqsx(ctx->dev, "RQSG", buffer); -+ struct surface_sam_san_rqsg rqsg = {}; -+ int status; -+ -+ if (!gsb_rqsg) { -+ return AE_OK; -+ } -+ -+ rqsg.tc = gsb_rqsg->tc; -+ rqsg.cid = gsb_rqsg->cid; -+ rqsg.iid = gsb_rqsg->iid; -+ rqsg.cdl = gsb_rqsg->cdl; -+ rqsg.pld = &gsb_rqsg->pld[0]; -+ -+ status = san_call_rqsg_handler(&rqsg); -+ if (!status) { -+ buffer->status = 0x00; -+ buffer->len = 0x02; -+ buffer->data.out.status = 0x00; -+ buffer->data.out.len = 0x00; -+ } else { -+ dev_err(ctx->dev, SAN_RQSG_TAG "failed with error %d\n", status); -+ buffer->status = 0x00; -+ buffer->len = 0x02; -+ buffer->data.out.status = 0x01; // indicate _SSH error -+ buffer->data.out.len = 0x00; -+ } -+ -+ return AE_OK; -+} -+ -+ -+static acpi_status -+san_opreg_handler(u32 function, acpi_physical_address command, -+ u32 bits, u64 *value64, -+ void *opreg_context, void *region_context) -+{ -+ struct san_opreg_context *context = opreg_context; -+ struct gsb_buffer *buffer = (struct gsb_buffer *)value64; -+ int accessor_type = (0xFFFF0000 & function) >> 16; -+ -+ if (command != 0) { -+ dev_warn(context->dev, "unsupported command: 0x%02llx\n", command); -+ return AE_OK; -+ } -+ -+ if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { -+ dev_err(context->dev, "invalid access type: 0x%02x\n", accessor_type); -+ return AE_OK; -+ } -+ -+ // buffer must have at least contain the command-value -+ if (buffer->len == 0) { -+ dev_err(context->dev, "request-package too small\n"); -+ return AE_OK; -+ } -+ -+ switch (buffer->data.in.cv) { -+ case 0x01: return san_rqst(context, buffer); -+ case 0x02: return san_etwl(context, buffer); -+ case 0x03: return san_rqsg(context, buffer); -+ } -+ -+ dev_warn(context->dev, "unsupported SAN0 request (cv: 0x%02x)\n", buffer->data.in.cv); -+ return AE_OK; -+} -+ -+static int san_enable_power_events(struct platform_device *pdev) -+{ -+ int status; -+ -+ status = surface_sam_ssh_set_delayed_event_handler( -+ SAM_EVENT_PWR_RQID, san_evt_power, -+ san_evt_power_delay, &pdev->dev); -+ if (status) -+ return status; -+ -+ status = surface_sam_ssh_enable_event_source(SAM_EVENT_PWR_TC, 0x01, SAM_EVENT_PWR_RQID); -+ if (status) { -+ surface_sam_ssh_remove_event_handler(SAM_EVENT_PWR_RQID); -+ return status; -+ } -+ -+ return 0; -+} -+ -+static int san_enable_thermal_events(struct platform_device *pdev) -+{ -+ int status; -+ -+ status = surface_sam_ssh_set_event_handler( -+ SAM_EVENT_TEMP_RQID, san_evt_thermal, -+ &pdev->dev); -+ if (status) -+ return status; -+ -+ status = surface_sam_ssh_enable_event_source(SAM_EVENT_TEMP_TC, 0x01, SAM_EVENT_TEMP_RQID); -+ if (status) { -+ surface_sam_ssh_remove_event_handler(SAM_EVENT_TEMP_RQID); -+ return status; -+ } -+ -+ return 0; -+} -+ -+static void san_disable_power_events(void) -+{ -+ surface_sam_ssh_disable_event_source(SAM_EVENT_PWR_TC, 0x01, SAM_EVENT_PWR_RQID); -+ surface_sam_ssh_remove_event_handler(SAM_EVENT_PWR_RQID); -+} -+ -+static void san_disable_thermal_events(void) -+{ -+ surface_sam_ssh_disable_event_source(SAM_EVENT_TEMP_TC, 0x01, SAM_EVENT_TEMP_RQID); -+ surface_sam_ssh_remove_event_handler(SAM_EVENT_TEMP_RQID); -+} -+ -+ -+static int san_enable_events(struct platform_device *pdev) -+{ -+ struct san_drvdata *drvdata = platform_get_drvdata(pdev); -+ int status; -+ -+ status = san_enable_thermal_events(pdev); -+ if (status) -+ return status; -+ -+ /* -+ * We have to figure out if this device uses SAN or requires a separate -+ * driver for the battery. If it uses the separate driver, that driver -+ * will enable and handle power events. -+ */ -+ drvdata->has_power_events = acpi_has_method(NULL, "\\_SB.BAT1._BST"); -+ if (drvdata->has_power_events) { -+ status = san_enable_power_events(pdev); -+ if (status) -+ goto err; -+ } -+ -+ return 0; -+ -+err: -+ san_disable_thermal_events(); -+ return status; -+} -+ -+static void san_disable_events(struct platform_device *pdev) -+{ -+ struct san_drvdata *drvdata = platform_get_drvdata(pdev); -+ -+ san_disable_thermal_events(); -+ if (drvdata->has_power_events) -+ san_disable_power_events(); -+} -+ -+ -+static int san_consumers_link(struct platform_device *pdev, -+ const struct san_acpi_consumer *cons, -+ struct san_consumers *out) -+{ -+ const struct san_acpi_consumer *con; -+ struct san_consumer_link *links, *link; -+ struct acpi_device *adev; -+ acpi_handle handle; -+ u32 max_links = 0; -+ int status; -+ -+ if (!cons) { -+ return 0; -+ } -+ -+ // count links -+ for (con = cons; con->path; ++con) { -+ max_links += 1; -+ } -+ -+ // allocate -+ links = kzalloc(max_links * sizeof(struct san_consumer_link), GFP_KERNEL); -+ link = &links[0]; -+ -+ if (!links) { -+ return -ENOMEM; -+ } -+ -+ // create links -+ for (con = cons; con->path; ++con) { -+ status = acpi_get_handle(NULL, con->path, &handle); -+ if (status) { -+ if (con->required || status != AE_NOT_FOUND) { -+ status = -ENXIO; -+ goto cleanup; -+ } else { -+ continue; -+ } -+ } -+ -+ status = acpi_bus_get_device(handle, &adev); -+ if (status) { -+ goto cleanup; -+ } -+ -+ link->link = device_link_add(&adev->dev, &pdev->dev, con->flags); -+ if (!(link->link)) { -+ status = -EFAULT; -+ goto cleanup; -+ } -+ link->properties = con; -+ -+ link += 1; -+ } -+ -+ out->num = link - links; -+ out->links = links; -+ -+ return 0; -+ -+cleanup: -+ for (link = link - 1; link >= links; --link) { -+ if (link->properties->flags & DL_FLAG_STATELESS) { -+ device_link_del(link->link); -+ } -+ } -+ -+ return status; -+} -+ -+static void san_consumers_unlink(struct san_consumers *consumers) { -+ u32 i; -+ -+ if (!consumers) { -+ return; -+ } -+ -+ for (i = 0; i < consumers->num; ++i) { -+ if (consumers->links[i].properties->flags & DL_FLAG_STATELESS) { -+ device_link_del(consumers->links[i].link); -+ } -+ } -+ -+ kfree(consumers->links); -+ -+ consumers->num = 0; -+ consumers->links = NULL; -+} -+ -+static int surface_sam_san_probe(struct platform_device *pdev) -+{ -+ const struct san_acpi_consumer *cons; -+ struct san_drvdata *drvdata; -+ acpi_handle san = ACPI_HANDLE(&pdev->dev); // _SAN device node -+ int status; -+ -+ /* -+ * Defer probe if the _SSH driver has not set up the controller yet. This -+ * makes sure we do not fail any initial requests (e.g. _STA request without -+ * which the battery does not get set up correctly). Otherwise register as -+ * consumer to set up a device_link. -+ */ -+ status = surface_sam_ssh_consumer_register(&pdev->dev); -+ if (status) { -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ } -+ -+ drvdata = kzalloc(sizeof(struct san_drvdata), GFP_KERNEL); -+ if (!drvdata) { -+ return -ENOMEM; -+ } -+ -+ drvdata->opreg_ctx.dev = &pdev->dev; -+ -+ cons = acpi_device_get_match_data(&pdev->dev); -+ status = san_consumers_link(pdev, cons, &drvdata->consumers); -+ if (status) { -+ goto err_consumers; -+ } -+ -+ platform_set_drvdata(pdev, drvdata); -+ -+ status = acpi_install_address_space_handler(san, -+ ACPI_ADR_SPACE_GSBUS, -+ &san_opreg_handler, -+ NULL, &drvdata->opreg_ctx); -+ -+ if (ACPI_FAILURE(status)) { -+ status = -ENODEV; -+ goto err_install_handler; -+ } -+ -+ status = san_enable_events(pdev); -+ if (status) { -+ goto err_enable_events; -+ } -+ -+ mutex_lock(&rqsg_if.lock); -+ if (!rqsg_if.san_dev) { -+ rqsg_if.san_dev = &pdev->dev; -+ } else { -+ status = -EBUSY; -+ } -+ mutex_unlock(&rqsg_if.lock); -+ -+ if (status) { -+ goto err_install_dev; -+ } -+ -+ acpi_walk_dep_device_list(san); -+ return 0; -+ -+err_install_dev: -+ san_disable_events(pdev); -+err_enable_events: -+ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &san_opreg_handler); -+err_install_handler: -+ platform_set_drvdata(san, NULL); -+ san_consumers_unlink(&drvdata->consumers); -+err_consumers: -+ kfree(drvdata); -+ return status; -+} -+ -+static int surface_sam_san_remove(struct platform_device *pdev) -+{ -+ struct san_drvdata *drvdata = platform_get_drvdata(pdev); -+ acpi_handle san = ACPI_HANDLE(&pdev->dev); // _SAN device node -+ acpi_status status = AE_OK; -+ -+ mutex_lock(&rqsg_if.lock); -+ rqsg_if.san_dev = NULL; -+ mutex_unlock(&rqsg_if.lock); -+ -+ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &san_opreg_handler); -+ san_disable_events(pdev); -+ -+ san_consumers_unlink(&drvdata->consumers); -+ kfree(drvdata); -+ -+ platform_set_drvdata(pdev, NULL); -+ return status; -+} -+ -+ -+static const struct san_acpi_consumer san_mshw0091_consumers[] = { -+ { "\\_SB.SRTC", true, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS }, -+ { "\\ADP1", true, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS }, -+ { "\\_SB.BAT1", true, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS }, -+ { "\\_SB.BAT2", false, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS }, -+ { }, -+}; -+ -+static const struct acpi_device_id surface_sam_san_match[] = { -+ { "MSHW0091", (long unsigned int) san_mshw0091_consumers }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_sam_san_match); -+ -+static struct platform_driver surface_sam_san = { -+ .probe = surface_sam_san_probe, -+ .remove = surface_sam_san_remove, -+ .driver = { -+ .name = "surface_sam_san", -+ .acpi_match_table = ACPI_PTR(surface_sam_san_match), -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_sam_san); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface ACPI Notify Driver for 5th Generation Surface Devices"); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_san.h b/drivers/platform/x86/surface_sam/surface_sam_san.h -new file mode 100644 -index 000000000000..1ea8713db367 ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_san.h -@@ -0,0 +1,29 @@ -+/* -+ * Interface for Surface ACPI/Notify (SAN). -+ * -+ * The SAN is the main interface between the Surface Serial Hub (SSH) and the -+ * Surface/System Aggregator Module (SAM). It allows requests to be translated -+ * from ACPI to SSH/SAM. It also interfaces with the discrete GPU hot-plug -+ * driver. -+ */ -+ -+#ifndef _SURFACE_SAM_SAN_H -+#define _SURFACE_SAM_SAN_H -+ -+#include -+ -+ -+struct surface_sam_san_rqsg { -+ u8 tc; // target category -+ u8 cid; // command ID -+ u8 iid; // instance ID -+ u8 cdl; // command data length (lenght of payload) -+ u8 *pld; // pointer to payload of length cdl -+}; -+ -+typedef int (*surface_sam_san_rqsg_handler_fn)(struct surface_sam_san_rqsg *rqsg, void *data); -+ -+int surface_sam_san_consumer_register(struct device *consumer, u32 flags); -+int surface_sam_san_set_rqsg_handler(surface_sam_san_rqsg_handler_fn fn, void *data); -+ -+#endif /* _SURFACE_SAM_SAN_H */ -diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid.c b/drivers/platform/x86/surface_sam/surface_sam_sid.c -new file mode 100644 -index 000000000000..f64dcd590494 ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_sid.c -@@ -0,0 +1,117 @@ -+/* -+ * Surface Integration Driver. -+ * MFD driver to provide device/model dependent functionality. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+ -+static const struct mfd_cell sid_devs_sp4[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sp7[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { .name = "surface_sam_sid_ac", .id = -1 }, -+ { .name = "surface_sam_sid_battery", .id = -1 }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sb1[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sb2[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { .name = "surface_sam_sid_perfmode", .id = -1 }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sl1[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sl2[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sl3_13[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { .name = "surface_sam_sid_vhf", .id = -1 }, -+ { .name = "surface_sam_sid_ac", .id = -1 }, -+ { .name = "surface_sam_sid_battery", .id = -1 }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sl3_15[] = { -+ { .name = "surface_sam_sid_vhf", .id = -1 }, -+ { .name = "surface_sam_sid_ac", .id = -1 }, -+ { .name = "surface_sam_sid_battery", .id = -1 }, -+ { }, -+}; -+ -+static const struct acpi_device_id surface_sam_sid_match[] = { -+ { "MSHW0081", (unsigned long)sid_devs_sp4 }, /* Surface Pro 4, 5, and 6 */ -+ { "MSHW0116", (unsigned long)sid_devs_sp7 }, /* Surface Pro 7 */ -+ { "MSHW0080", (unsigned long)sid_devs_sb1 }, /* Surface Book 1 */ -+ { "MSHW0107", (unsigned long)sid_devs_sb2 }, /* Surface Book 2 */ -+ { "MSHW0086", (unsigned long)sid_devs_sl1 }, /* Surface Laptop 1 */ -+ { "MSHW0112", (unsigned long)sid_devs_sl2 }, /* Surface Laptop 2 */ -+ { "MSHW0114", (unsigned long)sid_devs_sl3_13 }, /* Surface Laptop 3 (13") */ -+ { "MSHW0110", (unsigned long)sid_devs_sl3_15 }, /* Surface Laptop 3 (15") */ -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_sam_sid_match); -+ -+ -+static int surface_sam_sid_probe(struct platform_device *pdev) -+{ -+ const struct acpi_device_id *match; -+ const struct mfd_cell *cells, *p; -+ -+ match = acpi_match_device(surface_sam_sid_match, &pdev->dev); -+ if (!match) -+ return -ENODEV; -+ -+ cells = (struct mfd_cell *)match->driver_data; -+ if (!cells) -+ return -ENODEV; -+ -+ for (p = cells; p->name; ++p) { -+ /* just count */ -+ } -+ -+ if (p == cells) -+ return -ENODEV; -+ -+ return mfd_add_devices(&pdev->dev, 0, cells, p - cells, NULL, 0, NULL); -+} -+ -+static int surface_sam_sid_remove(struct platform_device *pdev) -+{ -+ mfd_remove_devices(&pdev->dev); -+ return 0; -+} -+ -+static struct platform_driver surface_sam_sid = { -+ .probe = surface_sam_sid_probe, -+ .remove = surface_sam_sid_remove, -+ .driver = { -+ .name = "surface_sam_sid", -+ .acpi_match_table = ACPI_PTR(surface_sam_sid_match), -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_sam_sid); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Integration Driver for 5th Generation Surface Devices"); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid_gpelid.c b/drivers/platform/x86/surface_sam/surface_sam_sid_gpelid.c -new file mode 100644 -index 000000000000..ce32ebf4d94d ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_sid_gpelid.c -@@ -0,0 +1,219 @@ -+/* -+ * Surface Lid driver to enable wakeup from suspend via the lid. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+ -+struct sid_lid_device { -+ const char *acpi_path; -+ const u32 gpe_number; -+}; -+ -+ -+static const struct sid_lid_device lid_device_l17 = { -+ .acpi_path = "\\_SB.LID0", -+ .gpe_number = 0x17, -+}; -+ -+static const struct sid_lid_device lid_device_l4D = { -+ .acpi_path = "\\_SB.LID0", -+ .gpe_number = 0x4D, -+}; -+ -+static const struct sid_lid_device lid_device_l4F = { -+ .acpi_path = "\\_SB.LID0", -+ .gpe_number = 0x4F, -+}; -+ -+static const struct sid_lid_device lid_device_l57 = { -+ .acpi_path = "\\_SB.LID0", -+ .gpe_number = 0x57, -+}; -+ -+ -+static const struct dmi_system_id dmi_lid_device_table[] = { -+ { -+ .ident = "Surface Pro 4", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), -+ }, -+ .driver_data = (void *)&lid_device_l17, -+ }, -+ { -+ .ident = "Surface Pro 5", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), -+ }, -+ .driver_data = (void *)&lid_device_l4F, -+ }, -+ { -+ .ident = "Surface Pro 5 (LTE)", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), -+ }, -+ .driver_data = (void *)&lid_device_l4F, -+ }, -+ { -+ .ident = "Surface Pro 6", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), -+ }, -+ .driver_data = (void *)&lid_device_l4F, -+ }, -+ { -+ .ident = "Surface Pro 7", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"), -+ }, -+ .driver_data = (void *)&lid_device_l4D, -+ }, -+ { -+ .ident = "Surface Book 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), -+ }, -+ .driver_data = (void *)&lid_device_l17, -+ }, -+ { -+ .ident = "Surface Book 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), -+ }, -+ .driver_data = (void *)&lid_device_l17, -+ }, -+ { -+ .ident = "Surface Laptop 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), -+ }, -+ .driver_data = (void *)&lid_device_l57, -+ }, -+ { -+ .ident = "Surface Laptop 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), -+ }, -+ .driver_data = (void *)&lid_device_l57, -+ }, -+ { -+ .ident = "Surface Laptop 3 (13\")", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"), -+ }, -+ .driver_data = (void *)&lid_device_l4D, -+ }, -+ { } -+}; -+ -+ -+static int sid_lid_enable_wakeup(const struct sid_lid_device *dev, bool enable) -+{ -+ int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE; -+ int status; -+ -+ status = acpi_set_gpe_wake_mask(NULL, dev->gpe_number, action); -+ if (status) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+ -+static int surface_sam_sid_gpelid_suspend(struct device *dev) -+{ -+ const struct sid_lid_device *ldev = dev_get_drvdata(dev); -+ return sid_lid_enable_wakeup(ldev, true); -+} -+ -+static int surface_sam_sid_gpelid_resume(struct device *dev) -+{ -+ const struct sid_lid_device *ldev = dev_get_drvdata(dev); -+ return sid_lid_enable_wakeup(ldev, false); -+} -+ -+static SIMPLE_DEV_PM_OPS(surface_sam_sid_gpelid_pm, -+ surface_sam_sid_gpelid_suspend, -+ surface_sam_sid_gpelid_resume); -+ -+ -+static int surface_sam_sid_gpelid_probe(struct platform_device *pdev) -+{ -+ const struct dmi_system_id *match; -+ struct sid_lid_device *dev; -+ acpi_handle lid_handle; -+ int status; -+ -+ match = dmi_first_match(dmi_lid_device_table); -+ if (!match) -+ return -ENODEV; -+ -+ dev = match->driver_data; -+ if (!dev) -+ return -ENODEV; -+ -+ status = acpi_get_handle(NULL, (acpi_string)dev->acpi_path, &lid_handle); -+ if (status) -+ return -EFAULT; -+ -+ status = acpi_setup_gpe_for_wake(lid_handle, NULL, dev->gpe_number); -+ if (status) -+ return -EFAULT; -+ -+ status = acpi_enable_gpe(NULL, dev->gpe_number); -+ if (status) -+ return -EFAULT; -+ -+ status = sid_lid_enable_wakeup(dev, false); -+ if (status) { -+ acpi_disable_gpe(NULL, dev->gpe_number); -+ return status; -+ } -+ -+ platform_set_drvdata(pdev, dev); -+ return 0; -+} -+ -+static int surface_sam_sid_gpelid_remove(struct platform_device *pdev) -+{ -+ struct sid_lid_device *dev = platform_get_drvdata(pdev); -+ -+ /* restore default behavior without this module */ -+ sid_lid_enable_wakeup(dev, false); -+ acpi_disable_gpe(NULL, dev->gpe_number); -+ -+ platform_set_drvdata(pdev, NULL); -+ return 0; -+} -+ -+static struct platform_driver surface_sam_sid_gpelid = { -+ .probe = surface_sam_sid_gpelid_probe, -+ .remove = surface_sam_sid_gpelid_remove, -+ .driver = { -+ .name = "surface_sam_sid_gpelid", -+ .pm = &surface_sam_sid_gpelid_pm, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_sam_sid_gpelid); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Lid Driver for 5th Generation Surface Devices"); -+MODULE_LICENSE("GPL v2"); -+MODULE_ALIAS("platform:surface_sam_sid_gpelid"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid_perfmode.c b/drivers/platform/x86/surface_sam/surface_sam_sid_perfmode.c -new file mode 100644 -index 000000000000..880a2567cf1b ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_sid_perfmode.c -@@ -0,0 +1,225 @@ -+/* -+ * Surface Performance Mode Driver. -+ * Allows to change cooling capabilities based on user preference. -+ */ -+ -+#include -+#include -+#include -+#include -+ -+#include "surface_sam_ssh.h" -+ -+ -+#define SID_PARAM_PERM (S_IRUGO | S_IWUSR) -+ -+enum sam_perf_mode { -+ SAM_PERF_MODE_NORMAL = 1, -+ SAM_PERF_MODE_BATTERY = 2, -+ SAM_PERF_MODE_PERF1 = 3, -+ SAM_PERF_MODE_PERF2 = 4, -+ -+ __SAM_PERF_MODE__START = 1, -+ __SAM_PERF_MODE__END = 4, -+}; -+ -+enum sid_param_perf_mode { -+ SID_PARAM_PERF_MODE_AS_IS = 0, -+ SID_PARAM_PERF_MODE_NORMAL = SAM_PERF_MODE_NORMAL, -+ SID_PARAM_PERF_MODE_BATTERY = SAM_PERF_MODE_BATTERY, -+ SID_PARAM_PERF_MODE_PERF1 = SAM_PERF_MODE_PERF1, -+ SID_PARAM_PERF_MODE_PERF2 = SAM_PERF_MODE_PERF2, -+ -+ __SID_PARAM_PERF_MODE__START = 0, -+ __SID_PARAM_PERF_MODE__END = 4, -+}; -+ -+ -+static int surface_sam_perf_mode_get(void) -+{ -+ u8 result_buf[8] = { 0 }; -+ int status; -+ -+ struct surface_sam_ssh_rqst rqst = { -+ .tc = 0x03, -+ .cid = 0x02, -+ .iid = 0x00, -+ .pri = SURFACE_SAM_PRIORITY_NORMAL, -+ .snc = 0x01, -+ .cdl = 0x00, -+ .pld = NULL, -+ }; -+ -+ struct surface_sam_ssh_buf result = { -+ .cap = ARRAY_SIZE(result_buf), -+ .len = 0, -+ .data = result_buf, -+ }; -+ -+ status = surface_sam_ssh_rqst(&rqst, &result); -+ if (status) { -+ return status; -+ } -+ -+ if (result.len != 8) { -+ return -EFAULT; -+ } -+ -+ return get_unaligned_le32(&result.data[0]); -+} -+ -+static int surface_sam_perf_mode_set(int perf_mode) -+{ -+ u8 payload[4] = { 0 }; -+ -+ struct surface_sam_ssh_rqst rqst = { -+ .tc = 0x03, -+ .cid = 0x03, -+ .iid = 0x00, -+ .pri = SURFACE_SAM_PRIORITY_NORMAL, -+ .snc = 0x00, -+ .cdl = ARRAY_SIZE(payload), -+ .pld = payload, -+ }; -+ -+ if (perf_mode < __SAM_PERF_MODE__START || perf_mode > __SAM_PERF_MODE__END) { -+ return -EINVAL; -+ } -+ -+ put_unaligned_le32(perf_mode, &rqst.pld[0]); -+ return surface_sam_ssh_rqst(&rqst, NULL); -+} -+ -+ -+static int param_perf_mode_set(const char *val, const struct kernel_param *kp) -+{ -+ int perf_mode; -+ int status; -+ -+ status = kstrtoint(val, 0, &perf_mode); -+ if (status) { -+ return status; -+ } -+ -+ if (perf_mode < __SID_PARAM_PERF_MODE__START || perf_mode > __SID_PARAM_PERF_MODE__END) { -+ return -EINVAL; -+ } -+ -+ return param_set_int(val, kp); -+} -+ -+static const struct kernel_param_ops param_perf_mode_ops = { -+ .set = param_perf_mode_set, -+ .get = param_get_int, -+}; -+ -+static int param_perf_mode_init = SID_PARAM_PERF_MODE_AS_IS; -+static int param_perf_mode_exit = SID_PARAM_PERF_MODE_AS_IS; -+ -+module_param_cb(perf_mode_init, ¶m_perf_mode_ops, ¶m_perf_mode_init, SID_PARAM_PERM); -+module_param_cb(perf_mode_exit, ¶m_perf_mode_ops, ¶m_perf_mode_exit, SID_PARAM_PERM); -+ -+MODULE_PARM_DESC(perf_mode_init, "Performance-mode to be set on module initialization"); -+MODULE_PARM_DESC(perf_mode_exit, "Performance-mode to be set on module exit"); -+ -+ -+static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, char *data) -+{ -+ int perf_mode; -+ -+ perf_mode = surface_sam_perf_mode_get(); -+ if (perf_mode < 0) { -+ dev_err(dev, "failed to get current performance mode: %d", perf_mode); -+ return -EIO; -+ } -+ -+ return sprintf(data, "%d\n", perf_mode); -+} -+ -+static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr, -+ const char *data, size_t count) -+{ -+ int perf_mode; -+ int status; -+ -+ status = kstrtoint(data, 0, &perf_mode); -+ if (status) { -+ return status; -+ } -+ -+ status = surface_sam_perf_mode_set(perf_mode); -+ if (status) { -+ return status; -+ } -+ -+ // TODO: Should we notify ACPI here? -+ // -+ // There is a _DSM call described as -+ // WSID._DSM: Notify DPTF on Slider State change -+ // which calls -+ // ODV3 = ToInteger (Arg3) -+ // Notify(IETM, 0x88) -+ // IETM is an INT3400 Intel Dynamic Power Performance Management -+ // device, part of the DPTF framework. From the corresponding -+ // kernel driver, it looks like event 0x88 is being ignored. Also -+ // it is currently unknown what the consequecnes of setting ODV3 -+ // are. -+ -+ return count; -+} -+ -+const static DEVICE_ATTR_RW(perf_mode); -+ -+ -+static int surface_sam_sid_perfmode_probe(struct platform_device *pdev) -+{ -+ int status; -+ -+ // link to ec -+ status = surface_sam_ssh_consumer_register(&pdev->dev); -+ if (status) { -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ } -+ -+ // set initial perf_mode -+ if (param_perf_mode_init != SID_PARAM_PERF_MODE_AS_IS) { -+ status = surface_sam_perf_mode_set(param_perf_mode_init); -+ if (status) { -+ return status; -+ } -+ } -+ -+ // register perf_mode attribute -+ status = sysfs_create_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr); -+ if (status) { -+ goto err_sysfs; -+ } -+ -+ return 0; -+ -+err_sysfs: -+ surface_sam_perf_mode_set(param_perf_mode_exit); -+ return status; -+} -+ -+static int surface_sam_sid_perfmode_remove(struct platform_device *pdev) -+{ -+ sysfs_remove_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr); -+ surface_sam_perf_mode_set(param_perf_mode_exit); -+ return 0; -+} -+ -+static struct platform_driver surface_sam_sid_perfmode = { -+ .probe = surface_sam_sid_perfmode_probe, -+ .remove = surface_sam_sid_perfmode_remove, -+ .driver = { -+ .name = "surface_sam_sid_perfmode", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_sam_sid_perfmode); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Performance Mode Driver for 5th Generation Surface Devices"); -+MODULE_LICENSE("GPL v2"); -+MODULE_ALIAS("platform:surface_sam_sid_perfmode"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid_power.c b/drivers/platform/x86/surface_sam/surface_sam_sid_power.c -new file mode 100644 -index 000000000000..1f2c88eda394 ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_sid_power.c -@@ -0,0 +1,1259 @@ -+/* -+ * Surface SID Battery/AC Driver. -+ * Provides support for the battery and AC on 7th generation Surface devices. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "surface_sam_ssh.h" -+ -+#define SPWR_WARN KERN_WARNING KBUILD_MODNAME ": " -+#define SPWR_DEBUG KERN_DEBUG KBUILD_MODNAME ": " -+ -+ -+// TODO: check BIX/BST for unknown/unsupported 0xffffffff entries -+// TODO: DPTF (/SAN notifications)? -+// TODO: other properties? -+ -+ -+static unsigned int cache_time = 1000; -+module_param(cache_time, uint, 0644); -+MODULE_PARM_DESC(cache_time, "battery state chaching time in milliseconds [default: 1000]"); -+ -+#define SPWR_AC_BAT_UPDATE_DELAY msecs_to_jiffies(5000) -+ -+ -+/* -+ * SAM Interface. -+ */ -+ -+#define SAM_PWR_TC 0x02 -+#define SAM_PWR_RQID 0x0002 -+ -+#define SAM_RQST_PWR_CID_STA 0x01 -+#define SAM_RQST_PWR_CID_BIX 0x02 -+#define SAM_RQST_PWR_CID_BST 0x03 -+#define SAM_RQST_PWR_CID_BTP 0x04 -+ -+#define SAM_RQST_PWR_CID_PMAX 0x0b -+#define SAM_RQST_PWR_CID_PSOC 0x0c -+#define SAM_RQST_PWR_CID_PSRC 0x0d -+#define SAM_RQST_PWR_CID_CHGI 0x0e -+#define SAM_RQST_PWR_CID_ARTG 0x0f -+ -+#define SAM_EVENT_PWR_CID_BIX 0x15 -+#define SAM_EVENT_PWR_CID_BST 0x16 -+#define SAM_EVENT_PWR_CID_ADAPTER 0x17 -+#define SAM_EVENT_PWR_CID_DPTF 0x4f -+ -+#define SAM_BATTERY_STA_OK 0x0f -+#define SAM_BATTERY_STA_PRESENT 0x10 -+ -+#define SAM_BATTERY_STATE_DISCHARGING 0x01 -+#define SAM_BATTERY_STATE_CHARGING 0x02 -+#define SAM_BATTERY_STATE_CRITICAL 0x04 -+ -+#define SAM_BATTERY_POWER_UNIT_MA 1 -+ -+ -+/* Equivalent to data returned in ACPI _BIX method */ -+struct spwr_bix { -+ u8 revision; -+ u32 power_unit; -+ u32 design_cap; -+ u32 last_full_charge_cap; -+ u32 technology; -+ u32 design_voltage; -+ u32 design_cap_warn; -+ u32 design_cap_low; -+ u32 cycle_count; -+ u32 measurement_accuracy; -+ u32 max_sampling_time; -+ u32 min_sampling_time; -+ u32 max_avg_interval; -+ u32 min_avg_interval; -+ u32 bat_cap_granularity_1; -+ u32 bat_cap_granularity_2; -+ u8 model[21]; -+ u8 serial[11]; -+ u8 type[5]; -+ u8 oem_info[21]; -+} __packed; -+ -+/* Equivalent to data returned in ACPI _BST method */ -+struct spwr_bst { -+ u32 state; -+ u32 present_rate; -+ u32 remaining_cap; -+ u32 present_voltage; -+} __packed; -+ -+/* DPTF event payload */ -+struct spwr_event_dptf { -+ u32 pmax; -+ u32 _1; /* currently unknown */ -+ u32 _2; /* currently unknown */ -+} __packed; -+ -+ -+/* Get battery status (_STA) */ -+static int sam_psy_get_sta(u8 iid, u32 *sta) -+{ -+ struct surface_sam_ssh_rqst rqst; -+ struct surface_sam_ssh_buf result; -+ -+ rqst.tc = SAM_PWR_TC; -+ rqst.cid = SAM_RQST_PWR_CID_STA; -+ rqst.iid = iid; -+ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; -+ rqst.snc = 0x01; -+ rqst.cdl = 0x00; -+ rqst.pld = NULL; -+ -+ result.cap = sizeof(u32); -+ result.len = 0; -+ result.data = (u8 *)sta; -+ -+ return surface_sam_ssh_rqst(&rqst, &result); -+} -+ -+/* Get battery static information (_BIX) */ -+static int sam_psy_get_bix(u8 iid, struct spwr_bix *bix) -+{ -+ struct surface_sam_ssh_rqst rqst; -+ struct surface_sam_ssh_buf result; -+ -+ rqst.tc = SAM_PWR_TC; -+ rqst.cid = SAM_RQST_PWR_CID_BIX; -+ rqst.iid = iid; -+ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; -+ rqst.snc = 0x01; -+ rqst.cdl = 0x00; -+ rqst.pld = NULL; -+ -+ result.cap = sizeof(struct spwr_bix); -+ result.len = 0; -+ result.data = (u8 *)bix; -+ -+ return surface_sam_ssh_rqst(&rqst, &result); -+} -+ -+/* Get battery dynamic information (_BST) */ -+static int sam_psy_get_bst(u8 iid, struct spwr_bst *bst) -+{ -+ struct surface_sam_ssh_rqst rqst; -+ struct surface_sam_ssh_buf result; -+ -+ rqst.tc = SAM_PWR_TC; -+ rqst.cid = SAM_RQST_PWR_CID_BST; -+ rqst.iid = iid; -+ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; -+ rqst.snc = 0x01; -+ rqst.cdl = 0x00; -+ rqst.pld = NULL; -+ -+ result.cap = sizeof(struct spwr_bst); -+ result.len = 0; -+ result.data = (u8 *)bst; -+ -+ return surface_sam_ssh_rqst(&rqst, &result); -+} -+ -+/* Set battery trip point (_BTP) */ -+static int sam_psy_set_btp(u8 iid, u32 btp) -+{ -+ struct surface_sam_ssh_rqst rqst; -+ -+ rqst.tc = SAM_PWR_TC; -+ rqst.cid = SAM_RQST_PWR_CID_BTP; -+ rqst.iid = iid; -+ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; -+ rqst.snc = 0x00; -+ rqst.cdl = sizeof(u32); -+ rqst.pld = (u8 *)&btp; -+ -+ return surface_sam_ssh_rqst(&rqst, NULL); -+} -+ -+/* Get platform power soruce for battery (DPTF PSRC) */ -+static int sam_psy_get_psrc(u8 iid, u32 *psrc) -+{ -+ struct surface_sam_ssh_rqst rqst; -+ struct surface_sam_ssh_buf result; -+ -+ rqst.tc = SAM_PWR_TC; -+ rqst.cid = SAM_RQST_PWR_CID_PSRC; -+ rqst.iid = iid; -+ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; -+ rqst.snc = 0x01; -+ rqst.cdl = 0x00; -+ rqst.pld = NULL; -+ -+ result.cap = sizeof(u32); -+ result.len = 0; -+ result.data = (u8 *)psrc; -+ -+ return surface_sam_ssh_rqst(&rqst, &result); -+} -+ -+/* Get maximum platform power for battery (DPTF PMAX) */ -+__always_unused -+static int sam_psy_get_pmax(u8 iid, u32 *pmax) -+{ -+ struct surface_sam_ssh_rqst rqst; -+ struct surface_sam_ssh_buf result; -+ -+ rqst.tc = SAM_PWR_TC; -+ rqst.cid = SAM_RQST_PWR_CID_PMAX; -+ rqst.iid = iid; -+ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; -+ rqst.snc = 0x01; -+ rqst.cdl = 0x00; -+ rqst.pld = NULL; -+ -+ result.cap = sizeof(u32); -+ result.len = 0; -+ result.data = (u8 *)pmax; -+ -+ return surface_sam_ssh_rqst(&rqst, &result); -+} -+ -+/* Get adapter rating (DPTF ARTG) */ -+__always_unused -+static int sam_psy_get_artg(u8 iid, u32 *artg) -+{ -+ struct surface_sam_ssh_rqst rqst; -+ struct surface_sam_ssh_buf result; -+ -+ rqst.tc = SAM_PWR_TC; -+ rqst.cid = SAM_RQST_PWR_CID_ARTG; -+ rqst.iid = iid; -+ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; -+ rqst.snc = 0x01; -+ rqst.cdl = 0x00; -+ rqst.pld = NULL; -+ -+ result.cap = sizeof(u32); -+ result.len = 0; -+ result.data = (u8 *)artg; -+ -+ return surface_sam_ssh_rqst(&rqst, &result); -+} -+ -+/* Unknown (DPTF PSOC) */ -+__always_unused -+static int sam_psy_get_psoc(u8 iid, u32 *psoc) -+{ -+ struct surface_sam_ssh_rqst rqst; -+ struct surface_sam_ssh_buf result; -+ -+ rqst.tc = SAM_PWR_TC; -+ rqst.cid = SAM_RQST_PWR_CID_PSOC; -+ rqst.iid = iid; -+ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; -+ rqst.snc = 0x01; -+ rqst.cdl = 0x00; -+ rqst.pld = NULL; -+ -+ result.cap = sizeof(u32); -+ result.len = 0; -+ result.data = (u8 *)psoc; -+ -+ return surface_sam_ssh_rqst(&rqst, &result); -+} -+ -+/* Unknown (DPTF CHGI/ INT3403 SPPC) */ -+__always_unused -+static int sam_psy_set_chgi(u8 iid, u32 chgi) -+{ -+ struct surface_sam_ssh_rqst rqst; -+ -+ rqst.tc = SAM_PWR_TC; -+ rqst.cid = SAM_RQST_PWR_CID_CHGI; -+ rqst.iid = iid; -+ rqst.pri = SURFACE_SAM_PRIORITY_NORMAL; -+ rqst.snc = 0x00; -+ rqst.cdl = sizeof(u32); -+ rqst.pld = (u8 *)&chgi; -+ -+ return surface_sam_ssh_rqst(&rqst, NULL); -+} -+ -+ -+/* -+ * Common Power-Subsystem Interface. -+ */ -+ -+enum spwr_battery_id { -+ SPWR_BAT1, -+ SPWR_BAT2, -+ __SPWR_NUM_BAT, -+}; -+#define SPWR_BAT_SINGLE PLATFORM_DEVID_NONE -+ -+struct spwr_battery_device { -+ struct platform_device *pdev; -+ enum spwr_battery_id id; -+ -+ char name[32]; -+ struct power_supply *psy; -+ struct power_supply_desc psy_desc; -+ -+ struct delayed_work update_work; -+ -+ struct mutex lock; -+ unsigned long timestamp; -+ -+ u32 sta; -+ struct spwr_bix bix; -+ struct spwr_bst bst; -+ u32 alarm; -+}; -+ -+struct spwr_ac_device { -+ struct platform_device *pdev; -+ -+ char name[32]; -+ struct power_supply *psy; -+ struct power_supply_desc psy_desc; -+ -+ struct mutex lock; -+ -+ u32 state; -+}; -+ -+struct spwr_subsystem { -+ struct mutex lock; -+ -+ unsigned refcount; -+ struct spwr_ac_device *ac; -+ struct spwr_battery_device *battery[__SPWR_NUM_BAT]; -+}; -+ -+static struct spwr_subsystem spwr_subsystem = { -+ .lock = __MUTEX_INITIALIZER(spwr_subsystem.lock), -+}; -+ -+static enum power_supply_property spwr_ac_props[] = { -+ POWER_SUPPLY_PROP_ONLINE, -+}; -+ -+static enum power_supply_property spwr_battery_props_chg[] = { -+ POWER_SUPPLY_PROP_STATUS, -+ POWER_SUPPLY_PROP_PRESENT, -+ POWER_SUPPLY_PROP_TECHNOLOGY, -+ POWER_SUPPLY_PROP_CYCLE_COUNT, -+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, -+ POWER_SUPPLY_PROP_VOLTAGE_NOW, -+ POWER_SUPPLY_PROP_CURRENT_NOW, -+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, -+ POWER_SUPPLY_PROP_CHARGE_FULL, -+ POWER_SUPPLY_PROP_CHARGE_NOW, -+ POWER_SUPPLY_PROP_CAPACITY, -+ POWER_SUPPLY_PROP_CAPACITY_LEVEL, -+ POWER_SUPPLY_PROP_MODEL_NAME, -+ POWER_SUPPLY_PROP_MANUFACTURER, -+ POWER_SUPPLY_PROP_SERIAL_NUMBER, -+}; -+ -+static enum power_supply_property spwr_battery_props_eng[] = { -+ POWER_SUPPLY_PROP_STATUS, -+ POWER_SUPPLY_PROP_PRESENT, -+ POWER_SUPPLY_PROP_TECHNOLOGY, -+ POWER_SUPPLY_PROP_CYCLE_COUNT, -+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, -+ POWER_SUPPLY_PROP_VOLTAGE_NOW, -+ POWER_SUPPLY_PROP_POWER_NOW, -+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, -+ POWER_SUPPLY_PROP_ENERGY_FULL, -+ POWER_SUPPLY_PROP_ENERGY_NOW, -+ POWER_SUPPLY_PROP_CAPACITY, -+ POWER_SUPPLY_PROP_CAPACITY_LEVEL, -+ POWER_SUPPLY_PROP_MODEL_NAME, -+ POWER_SUPPLY_PROP_MANUFACTURER, -+ POWER_SUPPLY_PROP_SERIAL_NUMBER, -+}; -+ -+ -+static int spwr_battery_register(struct spwr_battery_device *bat, struct platform_device *pdev, -+ enum spwr_battery_id id); -+ -+static int spwr_battery_unregister(struct spwr_battery_device *bat); -+ -+ -+inline static bool spwr_battery_present(struct spwr_battery_device *bat) -+{ -+ return bat->sta & SAM_BATTERY_STA_PRESENT; -+} -+ -+ -+inline static int spwr_battery_load_sta(struct spwr_battery_device *bat) -+{ -+ return sam_psy_get_sta(bat->id + 1, &bat->sta); -+} -+ -+inline static int spwr_battery_load_bix(struct spwr_battery_device *bat) -+{ -+ if (!spwr_battery_present(bat)) -+ return 0; -+ -+ return sam_psy_get_bix(bat->id + 1, &bat->bix); -+} -+ -+inline static int spwr_battery_load_bst(struct spwr_battery_device *bat) -+{ -+ if (!spwr_battery_present(bat)) -+ return 0; -+ -+ return sam_psy_get_bst(bat->id + 1, &bat->bst); -+} -+ -+ -+inline static int spwr_battery_set_alarm_unlocked(struct spwr_battery_device *bat, u32 value) -+{ -+ bat->alarm = value; -+ return sam_psy_set_btp(bat->id + 1, bat->alarm); -+} -+ -+inline static int spwr_battery_set_alarm(struct spwr_battery_device *bat, u32 value) -+{ -+ int status; -+ -+ mutex_lock(&bat->lock); -+ status = spwr_battery_set_alarm_unlocked(bat, value); -+ mutex_unlock(&bat->lock); -+ -+ return status; -+} -+ -+inline static int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, bool cached) -+{ -+ unsigned long cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time); -+ int status; -+ -+ if (cached && bat->timestamp && time_is_after_jiffies(cache_deadline)) -+ return 0; -+ -+ status = spwr_battery_load_sta(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bst(bat); -+ if (status) -+ return status; -+ -+ bat->timestamp = jiffies; -+ return 0; -+} -+ -+static int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached) -+{ -+ int status; -+ -+ mutex_lock(&bat->lock); -+ status = spwr_battery_update_bst_unlocked(bat, cached); -+ mutex_unlock(&bat->lock); -+ -+ return status; -+} -+ -+inline static int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ status = spwr_battery_load_sta(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bix(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bst(bat); -+ if (status) -+ return status; -+ -+ bat->timestamp = jiffies; -+ return 0; -+} -+ -+static int spwr_battery_update_bix(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ mutex_lock(&bat->lock); -+ status = spwr_battery_update_bix_unlocked(bat); -+ mutex_unlock(&bat->lock); -+ -+ return status; -+} -+ -+inline static int spwr_ac_update_unlocked(struct spwr_ac_device *ac) -+{ -+ return sam_psy_get_psrc(0x00, &ac->state); -+} -+ -+static int spwr_ac_update(struct spwr_ac_device *ac) -+{ -+ int status; -+ -+ mutex_lock(&ac->lock); -+ status = spwr_ac_update_unlocked(ac); -+ mutex_unlock(&ac->lock); -+ -+ return status; -+} -+ -+ -+static int spwr_battery_recheck(struct spwr_battery_device *bat) -+{ -+ bool present = spwr_battery_present(bat); -+ u32 unit = bat->bix.power_unit; -+ int status; -+ -+ status = spwr_battery_update_bix(bat); -+ if (status) -+ return status; -+ -+ // if battery has been attached, (re-)initialize alarm -+ if (!present && spwr_battery_present(bat)) { -+ status = spwr_battery_set_alarm(bat, bat->bix.design_cap_warn); -+ if (status) -+ return status; -+ } -+ -+ // if the unit has changed, re-add the battery -+ if (unit != bat->bix.power_unit) { -+ mutex_unlock(&spwr_subsystem.lock); -+ -+ status = spwr_battery_unregister(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_register(bat, bat->pdev, bat->id); -+ } -+ -+ return status; -+} -+ -+ -+static int spwr_handle_event_bix(struct surface_sam_ssh_event *event) -+{ -+ struct spwr_battery_device *bat; -+ enum spwr_battery_id bat_id = event->iid - 1; -+ int status = 0; -+ -+ if (bat_id < 0 || bat_id >= __SPWR_NUM_BAT) { -+ printk(SPWR_WARN "invalid BIX event iid 0x%02x\n", event->iid); -+ bat_id = SPWR_BAT1; -+ } -+ -+ mutex_lock(&spwr_subsystem.lock); -+ bat = spwr_subsystem.battery[bat_id]; -+ if (bat) { -+ status = spwr_battery_recheck(bat); -+ if (!status) -+ power_supply_changed(bat->psy); -+ } -+ -+ mutex_unlock(&spwr_subsystem.lock); -+ return status; -+} -+ -+static int spwr_handle_event_bst(struct surface_sam_ssh_event *event) -+{ -+ struct spwr_battery_device *bat; -+ enum spwr_battery_id bat_id = event->iid - 1; -+ int status = 0; -+ -+ if (bat_id < 0 || bat_id >= __SPWR_NUM_BAT) { -+ printk(SPWR_WARN "invalid BST event iid 0x%02x\n", event->iid); -+ bat_id = SPWR_BAT1; -+ } -+ -+ mutex_lock(&spwr_subsystem.lock); -+ -+ bat = spwr_subsystem.battery[bat_id]; -+ if (bat) { -+ status = spwr_battery_update_bst(bat, false); -+ if (!status) -+ power_supply_changed(bat->psy); -+ } -+ -+ mutex_unlock(&spwr_subsystem.lock); -+ return status; -+} -+ -+static int spwr_handle_event_adapter(struct surface_sam_ssh_event *event) -+{ -+ struct spwr_battery_device *bat1 = NULL; -+ struct spwr_battery_device *bat2 = NULL; -+ struct spwr_ac_device *ac; -+ int status = 0; -+ -+ mutex_lock(&spwr_subsystem.lock); -+ -+ ac = spwr_subsystem.ac; -+ if (ac) { -+ status = spwr_ac_update(ac); -+ if (status) -+ goto out; -+ -+ power_supply_changed(ac->psy); -+ } -+ -+ /* -+ * Handle battery update quirk: -+ * When the battery is fully charged and the adapter is plugged in or -+ * removed, the EC does not send a separate event for the state -+ * (charging/discharging) change. Furthermore it may take some time until -+ * the state is updated on the battery. Schedule an update to solve this. -+ */ -+ -+ bat1 = spwr_subsystem.battery[SPWR_BAT1]; -+ if (bat1 && bat1->bst.remaining_cap >= bat1->bix.last_full_charge_cap) -+ schedule_delayed_work(&bat1->update_work, SPWR_AC_BAT_UPDATE_DELAY); -+ -+ bat2 = spwr_subsystem.battery[SPWR_BAT2]; -+ if (bat2 && bat2->bst.remaining_cap >= bat2->bix.last_full_charge_cap) -+ schedule_delayed_work(&bat2->update_work, SPWR_AC_BAT_UPDATE_DELAY); -+ -+out: -+ mutex_unlock(&spwr_subsystem.lock); -+ return status; -+} -+ -+static int spwr_handle_event_dptf(struct surface_sam_ssh_event *event) -+{ -+ return 0; // TODO: spwr_handle_event_dptf -+} -+ -+static int spwr_handle_event(struct surface_sam_ssh_event *event, void *data) -+{ -+ printk(SPWR_DEBUG "power event (cid = 0x%02x)\n", event->cid); -+ -+ switch (event->cid) { -+ case SAM_EVENT_PWR_CID_BIX: -+ return spwr_handle_event_bix(event); -+ -+ case SAM_EVENT_PWR_CID_BST: -+ return spwr_handle_event_bst(event); -+ -+ case SAM_EVENT_PWR_CID_ADAPTER: -+ return spwr_handle_event_adapter(event); -+ -+ case SAM_EVENT_PWR_CID_DPTF: -+ return spwr_handle_event_dptf(event); -+ -+ default: -+ printk(SPWR_WARN "unhandled power event (cid = 0x%02x)\n", event->cid); -+ return 0; -+ } -+} -+ -+static void spwr_battery_update_bst_workfn(struct work_struct *work) -+{ -+ struct delayed_work *dwork = to_delayed_work(work); -+ struct spwr_battery_device *bat = container_of(dwork, struct spwr_battery_device, update_work); -+ int status; -+ -+ status = spwr_battery_update_bst(bat, false); -+ if (!status) -+ power_supply_changed(bat->psy); -+ -+ if (status) -+ dev_err(&bat->pdev->dev, "failed to update battery state: %d\n", status); -+} -+ -+ -+inline static int spwr_battery_prop_status(struct spwr_battery_device *bat) -+{ -+ if (bat->bst.state & SAM_BATTERY_STATE_DISCHARGING) -+ return POWER_SUPPLY_STATUS_DISCHARGING; -+ -+ if (bat->bst.state & SAM_BATTERY_STATE_CHARGING) -+ return POWER_SUPPLY_STATUS_CHARGING; -+ -+ if (bat->bix.last_full_charge_cap == bat->bst.remaining_cap) -+ return POWER_SUPPLY_STATUS_FULL; -+ -+ if (bat->bst.present_rate == 0) -+ return POWER_SUPPLY_STATUS_NOT_CHARGING; -+ -+ return POWER_SUPPLY_STATUS_UNKNOWN; -+} -+ -+inline static int spwr_battery_prop_technology(struct spwr_battery_device *bat) -+{ -+ if (!strcasecmp("NiCd", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_NiCd; -+ -+ if (!strcasecmp("NiMH", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_NiMH; -+ -+ if (!strcasecmp("LION", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_LION; -+ -+ if (!strncasecmp("LI-ION", bat->bix.type, 6)) -+ return POWER_SUPPLY_TECHNOLOGY_LION; -+ -+ if (!strcasecmp("LiP", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_LIPO; -+ -+ return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; -+} -+ -+inline static int spwr_battery_prop_capacity(struct spwr_battery_device *bat) -+{ -+ if (bat->bst.remaining_cap && bat->bix.last_full_charge_cap) -+ return bat->bst.remaining_cap * 100 / bat->bix.last_full_charge_cap; -+ else -+ return 0; -+} -+ -+inline static int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat) -+{ -+ if (bat->bst.state & SAM_BATTERY_STATE_CRITICAL) -+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; -+ -+ if (bat->bst.remaining_cap >= bat->bix.last_full_charge_cap) -+ return POWER_SUPPLY_CAPACITY_LEVEL_FULL; -+ -+ if (bat->bst.remaining_cap <= bat->alarm) -+ return POWER_SUPPLY_CAPACITY_LEVEL_LOW; -+ -+ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; -+} -+ -+static int spwr_ac_get_property(struct power_supply *psy, -+ enum power_supply_property psp, -+ union power_supply_propval *val) -+{ -+ struct spwr_ac_device *ac = power_supply_get_drvdata(psy); -+ int status; -+ -+ mutex_lock(&ac->lock); -+ -+ status = spwr_ac_update_unlocked(ac); -+ if (status) -+ goto out; -+ -+ switch (psp) { -+ case POWER_SUPPLY_PROP_ONLINE: -+ val->intval = ac->state == 1; -+ break; -+ -+ default: -+ status = -EINVAL; -+ goto out; -+ } -+ -+out: -+ mutex_unlock(&ac->lock); -+ return status; -+} -+ -+static int spwr_battery_get_property(struct power_supply *psy, -+ enum power_supply_property psp, -+ union power_supply_propval *val) -+{ -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ int status; -+ -+ mutex_lock(&bat->lock); -+ -+ status = spwr_battery_update_bst_unlocked(bat, true); -+ if (status) -+ goto out; -+ -+ // abort if battery is not present -+ if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) { -+ status = -ENODEV; -+ goto out; -+ } -+ -+ switch (psp) { -+ case POWER_SUPPLY_PROP_STATUS: -+ val->intval = spwr_battery_prop_status(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_PRESENT: -+ val->intval = spwr_battery_present(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_TECHNOLOGY: -+ val->intval = spwr_battery_prop_technology(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_CYCLE_COUNT: -+ val->intval = bat->bix.cycle_count; -+ break; -+ -+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: -+ val->intval = bat->bix.design_voltage * 1000; -+ break; -+ -+ case POWER_SUPPLY_PROP_VOLTAGE_NOW: -+ val->intval = bat->bst.present_voltage * 1000; -+ break; -+ -+ case POWER_SUPPLY_PROP_CURRENT_NOW: -+ case POWER_SUPPLY_PROP_POWER_NOW: -+ val->intval = bat->bst.present_rate * 1000; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: -+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: -+ val->intval = bat->bix.design_cap * 1000; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_FULL: -+ case POWER_SUPPLY_PROP_ENERGY_FULL: -+ val->intval = bat->bix.last_full_charge_cap * 1000; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_NOW: -+ case POWER_SUPPLY_PROP_ENERGY_NOW: -+ val->intval = bat->bst.remaining_cap * 1000; -+ break; -+ -+ case POWER_SUPPLY_PROP_CAPACITY: -+ val->intval = spwr_battery_prop_capacity(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL: -+ val->intval = spwr_battery_prop_capacity_level(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_MODEL_NAME: -+ val->strval = bat->bix.model; -+ break; -+ -+ case POWER_SUPPLY_PROP_MANUFACTURER: -+ val->strval = bat->bix.oem_info; -+ break; -+ -+ case POWER_SUPPLY_PROP_SERIAL_NUMBER: -+ val->strval = bat->bix.serial; -+ break; -+ -+ default: -+ status = -EINVAL; -+ goto out; -+ } -+ -+out: -+ mutex_unlock(&bat->lock); -+ return status; -+} -+ -+ -+static ssize_t spwr_battery_alarm_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ struct power_supply *psy = dev_get_drvdata(dev); -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ -+ return sprintf(buf, "%d\n", bat->alarm * 1000); -+} -+ -+static ssize_t spwr_battery_alarm_store(struct device *dev, -+ struct device_attribute *attr, -+ const char *buf, size_t count) -+{ -+ struct power_supply *psy = dev_get_drvdata(dev); -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ unsigned long value; -+ int status; -+ -+ status = kstrtoul(buf, 0, &value); -+ if (status) -+ return status; -+ -+ if (!spwr_battery_present(bat)) -+ return -ENODEV; -+ -+ status = spwr_battery_set_alarm(bat, value / 1000); -+ if (status) -+ return status; -+ -+ return count; -+} -+ -+static const struct device_attribute alarm_attr = { -+ .attr = {.name = "alarm", .mode = 0644}, -+ .show = spwr_battery_alarm_show, -+ .store = spwr_battery_alarm_store, -+}; -+ -+ -+static int spwr_subsys_init_unlocked(void) -+{ -+ int status; -+ -+ status = surface_sam_ssh_set_event_handler(SAM_PWR_RQID, spwr_handle_event, NULL); -+ if (status) { -+ goto err_handler; -+ } -+ -+ status = surface_sam_ssh_enable_event_source(SAM_PWR_TC, 0x01, SAM_PWR_RQID); -+ if (status) { -+ goto err_source; -+ } -+ -+ return 0; -+ -+err_source: -+ surface_sam_ssh_remove_event_handler(SAM_PWR_RQID); -+err_handler: -+ return status; -+} -+ -+static int spwr_subsys_deinit_unlocked(void) -+{ -+ surface_sam_ssh_disable_event_source(SAM_PWR_TC, 0x01, SAM_PWR_RQID); -+ surface_sam_ssh_remove_event_handler(SAM_PWR_RQID); -+ return 0; -+} -+ -+static inline int spwr_subsys_ref_unlocked(void) -+{ -+ int status = 0; -+ -+ if (!spwr_subsystem.refcount) -+ status = spwr_subsys_init_unlocked(); -+ -+ spwr_subsystem.refcount += 1; -+ return status; -+} -+ -+static inline int spwr_subsys_unref_unlocked(void) -+{ -+ int status = 0; -+ -+ if (spwr_subsystem.refcount) -+ spwr_subsystem.refcount -= 1; -+ -+ if (!spwr_subsystem.refcount) -+ status = spwr_subsys_deinit_unlocked(); -+ -+ return status; -+} -+ -+ -+static int spwr_ac_register(struct spwr_ac_device *ac, struct platform_device *pdev) -+{ -+ struct power_supply_config psy_cfg = {}; -+ u32 sta; -+ int status; -+ -+ // make sure the device is there and functioning properly -+ status = sam_psy_get_sta(0x00, &sta); -+ if (status) -+ return status; -+ -+ if ((sta & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) -+ return -ENODEV; -+ -+ psy_cfg.drv_data = ac; -+ -+ ac->pdev = pdev; -+ mutex_init(&ac->lock); -+ -+ snprintf(ac->name, ARRAY_SIZE(ac->name), "ADP0"); -+ -+ ac->psy_desc.name = ac->name; -+ ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; -+ ac->psy_desc.properties = spwr_ac_props; -+ ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props); -+ ac->psy_desc.get_property = spwr_ac_get_property; -+ -+ mutex_lock(&spwr_subsystem.lock); -+ if (spwr_subsystem.ac) { -+ status = -EEXIST; -+ goto err; -+ } -+ -+ status = spwr_subsys_ref_unlocked(); -+ if (status) -+ goto err; -+ -+ ac->psy = power_supply_register(&ac->pdev->dev, &ac->psy_desc, &psy_cfg); -+ if (IS_ERR(ac->psy)) { -+ status = PTR_ERR(ac->psy); -+ goto err_unref; -+ } -+ -+ spwr_subsystem.ac = ac; -+ mutex_unlock(&spwr_subsystem.lock); -+ return 0; -+ -+err_unref: -+ spwr_subsys_unref_unlocked(); -+err: -+ mutex_unlock(&spwr_subsystem.lock); -+ mutex_destroy(&ac->lock); -+ return status; -+} -+ -+static int spwr_ac_unregister(struct spwr_ac_device *ac) -+{ -+ int status; -+ -+ mutex_lock(&spwr_subsystem.lock); -+ if (spwr_subsystem.ac != ac) { -+ mutex_unlock(&spwr_subsystem.lock); -+ return -EINVAL; -+ } -+ -+ spwr_subsystem.ac = NULL; -+ power_supply_unregister(ac->psy); -+ -+ status = spwr_subsys_unref_unlocked(); -+ mutex_unlock(&spwr_subsystem.lock); -+ -+ mutex_destroy(&ac->lock); -+ return status; -+} -+ -+static int spwr_battery_register(struct spwr_battery_device *bat, struct platform_device *pdev, -+ enum spwr_battery_id id) -+{ -+ struct power_supply_config psy_cfg = {}; -+ u32 sta; -+ int status; -+ -+ if ((id < 0 || id >= __SPWR_NUM_BAT) && id != SPWR_BAT_SINGLE) -+ return -EINVAL; -+ -+ bat->pdev = pdev; -+ bat->id = id != SPWR_BAT_SINGLE ? id : SPWR_BAT1; -+ -+ // make sure the device is there and functioning properly -+ status = sam_psy_get_sta(bat->id + 1, &sta); -+ if (status) -+ return status; -+ -+ if ((sta & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) -+ return -ENODEV; -+ -+ status = spwr_battery_update_bix_unlocked(bat); -+ if (status) -+ return status; -+ -+ if (spwr_battery_present(bat)) { -+ status = spwr_battery_set_alarm_unlocked(bat, bat->bix.design_cap_warn); -+ if (status) -+ return status; -+ } -+ -+ snprintf(bat->name, ARRAY_SIZE(bat->name), "BAT%d", bat->id); -+ bat->psy_desc.name = bat->name; -+ bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; -+ -+ if (bat->bix.power_unit == SAM_BATTERY_POWER_UNIT_MA) { -+ bat->psy_desc.properties = spwr_battery_props_chg; -+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg); -+ } else { -+ bat->psy_desc.properties = spwr_battery_props_eng; -+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng); -+ } -+ -+ bat->psy_desc.get_property = spwr_battery_get_property; -+ -+ mutex_init(&bat->lock); -+ psy_cfg.drv_data = bat; -+ -+ INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn); -+ -+ mutex_lock(&spwr_subsystem.lock); -+ if (spwr_subsystem.battery[bat->id]) { -+ status = -EEXIST; -+ goto err; -+ } -+ -+ status = spwr_subsys_ref_unlocked(); -+ if (status) -+ goto err; -+ -+ bat->psy = power_supply_register(&bat->pdev->dev, &bat->psy_desc, &psy_cfg); -+ if (IS_ERR(bat->psy)) { -+ status = PTR_ERR(bat->psy); -+ goto err_unref; -+ } -+ -+ status = device_create_file(&bat->psy->dev, &alarm_attr); -+ if (status) -+ goto err_dereg; -+ -+ spwr_subsystem.battery[bat->id] = bat; -+ mutex_unlock(&spwr_subsystem.lock); -+ return 0; -+ -+err_dereg: -+ power_supply_unregister(bat->psy); -+err_unref: -+ spwr_subsys_unref_unlocked(); -+err: -+ mutex_unlock(&spwr_subsystem.lock); -+ return status; -+} -+ -+static int spwr_battery_unregister(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ if (bat->id < 0 || bat->id >= __SPWR_NUM_BAT) -+ return -EINVAL ; -+ -+ mutex_lock(&spwr_subsystem.lock); -+ if (spwr_subsystem.battery[bat->id] != bat) { -+ mutex_unlock(&spwr_subsystem.lock); -+ return -EINVAL; -+ } -+ -+ spwr_subsystem.battery[bat->id] = NULL; -+ -+ status = spwr_subsys_unref_unlocked(); -+ mutex_unlock(&spwr_subsystem.lock); -+ -+ cancel_delayed_work_sync(&bat->update_work); -+ device_remove_file(&bat->psy->dev, &alarm_attr); -+ power_supply_unregister(bat->psy); -+ -+ mutex_destroy(&bat->lock); -+ return status; -+} -+ -+ -+/* -+ * Battery Driver. -+ */ -+ -+#ifdef CONFIG_PM_SLEEP -+static int surface_sam_sid_battery_resume(struct device *dev) -+{ -+ struct spwr_battery_device *bat = dev_get_drvdata(dev); -+ return spwr_battery_recheck(bat); -+} -+#else -+#define surface_sam_sid_battery_resume NULL -+#endif -+ -+SIMPLE_DEV_PM_OPS(surface_sam_sid_battery_pm, NULL, surface_sam_sid_battery_resume); -+ -+static int surface_sam_sid_battery_probe(struct platform_device *pdev) -+{ -+ int status; -+ struct spwr_battery_device *bat; -+ -+ // link to ec -+ status = surface_sam_ssh_consumer_register(&pdev->dev); -+ if (status) -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ -+ bat = devm_kzalloc(&pdev->dev, sizeof(struct spwr_battery_device), GFP_KERNEL); -+ if (!bat) -+ return -ENOMEM; -+ -+ platform_set_drvdata(pdev, bat); -+ return spwr_battery_register(bat, pdev, pdev->id); -+} -+ -+static int surface_sam_sid_battery_remove(struct platform_device *pdev) -+{ -+ struct spwr_battery_device *bat = platform_get_drvdata(pdev); -+ return spwr_battery_unregister(bat); -+} -+ -+static struct platform_driver surface_sam_sid_battery = { -+ .probe = surface_sam_sid_battery_probe, -+ .remove = surface_sam_sid_battery_remove, -+ .driver = { -+ .name = "surface_sam_sid_battery", -+ .pm = &surface_sam_sid_battery_pm, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+/* -+ * AC Driver. -+ */ -+ -+static int surface_sam_sid_ac_probe(struct platform_device *pdev) -+{ -+ int status; -+ struct spwr_ac_device *ac; -+ -+ // link to ec -+ status = surface_sam_ssh_consumer_register(&pdev->dev); -+ if (status) -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ -+ ac = devm_kzalloc(&pdev->dev, sizeof(struct spwr_ac_device), GFP_KERNEL); -+ if (!ac) -+ return -ENOMEM; -+ -+ status = spwr_ac_register(ac, pdev); -+ if (status) -+ return status; -+ -+ platform_set_drvdata(pdev, ac); -+ return 0; -+} -+ -+static int surface_sam_sid_ac_remove(struct platform_device *pdev) -+{ -+ struct spwr_ac_device *ac = platform_get_drvdata(pdev); -+ return spwr_ac_unregister(ac); -+} -+ -+static struct platform_driver surface_sam_sid_ac = { -+ .probe = surface_sam_sid_ac_probe, -+ .remove = surface_sam_sid_ac_remove, -+ .driver = { -+ .name = "surface_sam_sid_ac", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+static int __init surface_sam_sid_power_init(void) -+{ -+ int status; -+ -+ status = platform_driver_register(&surface_sam_sid_battery); -+ if (status) -+ return status; -+ -+ status = platform_driver_register(&surface_sam_sid_ac); -+ if (status) { -+ platform_driver_unregister(&surface_sam_sid_battery); -+ return status; -+ } -+ -+ return 0; -+} -+ -+static void __exit surface_sam_sid_power_exit(void) -+{ -+ platform_driver_unregister(&surface_sam_sid_battery); -+ platform_driver_unregister(&surface_sam_sid_ac); -+} -+ -+module_init(surface_sam_sid_power_init); -+module_exit(surface_sam_sid_power_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Battery/AC Driver for 7th Generation Surface Devices"); -+MODULE_LICENSE("GPL v2"); -+MODULE_ALIAS("platform:surface_sam_sid_ac"); -+MODULE_ALIAS("platform:surface_sam_sid_battery"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid_vhf.c b/drivers/platform/x86/surface_sam/surface_sam_sid_vhf.c -new file mode 100644 -index 000000000000..dc5be3a14a8c ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_sid_vhf.c -@@ -0,0 +1,440 @@ -+/* -+ * Microsofs Surface HID (VHF) driver for HID input events via SAM. -+ * Used for keyboard input events on the 7th generation Surface Laptops. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "surface_sam_ssh.h" -+ -+#define SID_VHF_INPUT_NAME "Microsoft Surface HID" -+ -+/* -+ * Request ID for VHF events. This value is based on the output of the Surface -+ * EC and should not be changed. -+ */ -+#define SAM_EVENT_SID_VHF_RQID 0x0015 -+#define SAM_EVENT_SID_VHF_TC 0x15 -+ -+#define VHF_HID_STARTED 0 -+ -+struct sid_vhf_evtctx { -+ struct device *dev; -+ struct hid_device *hid; -+ unsigned long flags; -+}; -+ -+struct sid_vhf_drvdata { -+ struct sid_vhf_evtctx event_ctx; -+}; -+ -+ -+static int sid_vhf_hid_start(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+ return 0; -+} -+ -+static void sid_vhf_hid_stop(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+} -+ -+static int sid_vhf_hid_open(struct hid_device *hid) -+{ -+ struct sid_vhf_drvdata *drvdata = platform_get_drvdata(to_platform_device(hid->dev.parent)); -+ -+ hid_dbg(hid, "%s\n", __func__); -+ -+ set_bit(VHF_HID_STARTED, &drvdata->event_ctx.flags); -+ return 0; -+} -+ -+static void sid_vhf_hid_close(struct hid_device *hid) -+{ -+ -+ struct sid_vhf_drvdata *drvdata = platform_get_drvdata(to_platform_device(hid->dev.parent)); -+ -+ hid_dbg(hid, "%s\n", __func__); -+ -+ clear_bit(VHF_HID_STARTED, &drvdata->event_ctx.flags); -+} -+ -+struct surface_sam_sid_vhf_meta_rqst { -+ u8 id; -+ u32 offset; -+ u32 length; // buffer limit on send, length of data received on receive -+ u8 end; // 0x01 if end was reached -+} __packed; -+ -+struct vhf_device_metadata_info { -+ u8 len; -+ u8 _2; -+ u8 _3; -+ u8 _4; -+ u8 _5; -+ u8 _6; -+ u8 _7; -+ u16 hid_len; // hid descriptor length -+} __packed; -+ -+struct vhf_device_metadata { -+ u32 len; -+ u16 vendor_id; -+ u16 product_id; -+ u8 _1[24]; -+} __packed; -+ -+union vhf_buffer_data { -+ struct vhf_device_metadata_info info; -+ u8 pld[0x76]; -+ struct vhf_device_metadata meta; -+}; -+ -+struct surface_sam_sid_vhf_meta_resp { -+ struct surface_sam_sid_vhf_meta_rqst rqst; -+ union vhf_buffer_data data; -+} __packed; -+ -+ -+static int vhf_get_metadata(u8 iid, struct vhf_device_metadata *meta) -+{ -+ int status; -+ -+ struct surface_sam_sid_vhf_meta_resp resp = { -+ .rqst = { -+ .id = 2, -+ .offset = 0, -+ .length = 0x76, -+ .end = 0 -+ } -+ }; -+ -+ struct surface_sam_ssh_rqst rqst = { -+ .tc = 0x15, -+ .cid = 0x04, -+ .iid = iid, -+ .pri = 0x02, -+ .snc = 0x01, -+ .cdl = sizeof(struct surface_sam_sid_vhf_meta_rqst), -+ .pld = (u8*)&resp.rqst, -+ }; -+ -+ struct surface_sam_ssh_buf result = { -+ .cap = sizeof(struct surface_sam_sid_vhf_meta_resp), -+ .len = 0, -+ .data = (u8*)&resp, -+ }; -+ -+ status = surface_sam_ssh_rqst(&rqst, &result); -+ if (status) { -+ return status; -+ } -+ -+ *meta = resp.data.meta; -+ -+ return 0; -+} -+ -+static int vhf_get_hid_descriptor(struct hid_device *hid, u8 iid, u8 **desc, int *size) -+{ -+ int status, len; -+ u8 *buf; -+ -+ struct surface_sam_sid_vhf_meta_resp resp = { -+ .rqst = { -+ .id = 0, -+ .offset = 0, -+ .length = 0x76, -+ .end = 0, -+ } -+ }; -+ -+ struct surface_sam_ssh_rqst rqst = { -+ .tc = 0x15, -+ .cid = 0x04, -+ .iid = iid, -+ .pri = 0x02, -+ .snc = 0x01, -+ .cdl = sizeof(struct surface_sam_sid_vhf_meta_rqst), -+ .pld = (u8*)&resp.rqst, -+ }; -+ -+ struct surface_sam_ssh_buf result = { -+ .cap = sizeof(struct surface_sam_sid_vhf_meta_resp), -+ .len = 0, -+ .data = (u8*)&resp, -+ }; -+ -+ // first fetch 00 to get the total length -+ status = surface_sam_ssh_rqst(&rqst, &result); -+ if (status) { -+ return status; -+ } -+ -+ len = resp.data.info.hid_len; -+ -+ // allocate a buffer for the descriptor -+ buf = kzalloc(len, GFP_KERNEL); -+ -+ // then, iterate and write into buffer, copying out bytes -+ resp.rqst.id = 1; -+ resp.rqst.offset = 0; -+ resp.rqst.length = 0x76; -+ resp.rqst.end = 0; -+ -+ while (!resp.rqst.end && resp.rqst.offset < len) { -+ status = surface_sam_ssh_rqst(&rqst, &result); -+ if (status) { -+ kfree(buf); -+ return status; -+ } -+ memcpy(buf + resp.rqst.offset, resp.data.pld, resp.rqst.length); -+ -+ resp.rqst.offset += resp.rqst.length; -+ } -+ -+ *desc = buf; -+ *size = len; -+ -+ return 0; -+} -+ -+static int sid_vhf_hid_parse(struct hid_device *hid) -+{ -+ int ret = 0, size; -+ u8 *buf; -+ -+ ret = vhf_get_hid_descriptor(hid, 0x00, &buf, &size); -+ if (ret != 0) { -+ hid_err(hid, "Failed to read HID descriptor from device: %d\n", ret); -+ return -EIO; -+ } -+ hid_dbg(hid, "HID descriptor of device:"); -+ print_hex_dump_debug("descriptor:", DUMP_PREFIX_OFFSET, 16, 1, buf, size, false); -+ -+ ret = hid_parse_report(hid, buf, size); -+ kfree(buf); -+ return ret; -+ -+} -+ -+static int sid_vhf_hid_raw_request(struct hid_device *hid, unsigned char -+ reportnum, u8 *buf, size_t len, unsigned char rtype, int -+ reqtype) -+{ -+ int status; -+ u8 cid; -+ struct surface_sam_ssh_rqst rqst = {}; -+ struct surface_sam_ssh_buf result = {}; -+ -+ hid_dbg(hid, "%s: reportnum=%#04x rtype=%i reqtype=%i\n", __func__, reportnum, rtype, reqtype); -+ print_hex_dump_debug("report:", DUMP_PREFIX_OFFSET, 16, 1, buf, len, false); -+ -+ // Byte 0 is the report number. Report data starts at byte 1. -+ buf[0] = reportnum; -+ -+ switch (rtype) { -+ case HID_OUTPUT_REPORT: -+ cid = 0x01; -+ break; -+ case HID_FEATURE_REPORT: -+ switch (reqtype) { -+ case HID_REQ_GET_REPORT: -+ // The EC doesn't respond to GET FEATURE for these touchpad reports -+ // we immediately discard to avoid waiting for a timeout. -+ if (reportnum == 6 || reportnum == 7 || reportnum == 8 || reportnum == 9 || reportnum == 0x0b) { -+ hid_dbg(hid, "%s: skipping get feature report for 0x%02x\n", __func__, reportnum); -+ return 0; -+ } -+ -+ cid = 0x02; -+ break; -+ case HID_REQ_SET_REPORT: -+ cid = 0x03; -+ break; -+ default: -+ hid_err(hid, "%s: unknown req type 0x%02x\n", __func__, rtype); -+ return -EIO; -+ } -+ break; -+ default: -+ hid_err(hid, "%s: unknown report type 0x%02x\n", __func__, reportnum); -+ return -EIO; -+ } -+ -+ rqst.tc = SAM_EVENT_SID_VHF_TC; -+ rqst.pri = SURFACE_SAM_PRIORITY_HIGH; -+ rqst.iid = 0x00; // windows tends to distinguish iids, but EC will take it -+ rqst.cid = cid; -+ rqst.snc = HID_REQ_GET_REPORT == reqtype ? 0x01 : 0x00; -+ rqst.cdl = HID_REQ_GET_REPORT == reqtype ? 0x01 : len; -+ rqst.pld = buf; -+ -+ result.cap = len; -+ result.len = 0; -+ result.data = buf; -+ -+ hid_dbg(hid, "%s: sending to cid=%#04x snc=%#04x\n", __func__, cid, HID_REQ_GET_REPORT == reqtype); -+ -+ status = surface_sam_ssh_rqst(&rqst, &result); -+ hid_dbg(hid, "%s: status %i\n", __func__, status); -+ -+ if (status) { -+ return status; -+ } -+ -+ if (result.len > 0) { -+ print_hex_dump_debug("response:", DUMP_PREFIX_OFFSET, 16, 1, result.data, result.len, false); -+ } -+ -+ return result.len; -+} -+ -+static struct hid_ll_driver sid_vhf_hid_ll_driver = { -+ .start = sid_vhf_hid_start, -+ .stop = sid_vhf_hid_stop, -+ .open = sid_vhf_hid_open, -+ .close = sid_vhf_hid_close, -+ .parse = sid_vhf_hid_parse, -+ .raw_request = sid_vhf_hid_raw_request, -+}; -+ -+ -+static struct hid_device *sid_vhf_create_hid_device(struct platform_device *pdev, struct vhf_device_metadata *meta) -+{ -+ struct hid_device *hid; -+ -+ hid = hid_allocate_device(); -+ if (IS_ERR(hid)) { -+ return hid; -+ } -+ -+ hid->dev.parent = &pdev->dev; -+ -+ hid->bus = BUS_VIRTUAL; -+ hid->vendor = meta->vendor_id; -+ hid->product = meta->product_id; -+ -+ hid->ll_driver = &sid_vhf_hid_ll_driver; -+ -+ sprintf(hid->name, "%s", SID_VHF_INPUT_NAME); -+ -+ return hid; -+} -+ -+static int sid_vhf_event_handler(struct surface_sam_ssh_event *event, void *data) -+{ -+ struct sid_vhf_evtctx *ctx = (struct sid_vhf_evtctx *)data; -+ -+ // skip if HID hasn't started yet -+ if (!test_bit(VHF_HID_STARTED, &ctx->flags)) { -+ return 0; -+ } -+ -+ if (event->tc == SAM_EVENT_SID_VHF_TC && (event->cid == 0x00 || event->cid == 0x03 || event->cid == 0x04)) { -+ return hid_input_report(ctx->hid, HID_INPUT_REPORT, event->pld, event->len, 1); -+ } -+ -+ dev_warn(ctx->dev, "unsupported event (tc = %d, cid = %d)\n", event->tc, event->cid); -+ return 0; -+} -+ -+static int surface_sam_sid_vhf_probe(struct platform_device *pdev) -+{ -+ struct sid_vhf_drvdata *drvdata; -+ struct vhf_device_metadata meta = {}; -+ struct hid_device *hid; -+ int status; -+ -+ // add device link to EC -+ status = surface_sam_ssh_consumer_register(&pdev->dev); -+ if (status) { -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ } -+ -+ drvdata = kzalloc(sizeof(struct sid_vhf_drvdata), GFP_KERNEL); -+ if (!drvdata) { -+ return -ENOMEM; -+ } -+ -+ status = vhf_get_metadata(0x00, &meta); -+ if (status) { -+ goto err_create_hid; -+ } -+ -+ hid = sid_vhf_create_hid_device(pdev, &meta); -+ if (IS_ERR(hid)) { -+ status = PTR_ERR(hid); -+ goto err_create_hid; -+ } -+ -+ drvdata->event_ctx.dev = &pdev->dev; -+ drvdata->event_ctx.hid = hid; -+ -+ platform_set_drvdata(pdev, drvdata); -+ -+ status = surface_sam_ssh_set_event_handler( -+ SAM_EVENT_SID_VHF_RQID, -+ sid_vhf_event_handler, -+ &drvdata->event_ctx); -+ if (status) { -+ goto err_event_handler; -+ } -+ -+ status = surface_sam_ssh_enable_event_source(SAM_EVENT_SID_VHF_TC, 0x01, SAM_EVENT_SID_VHF_RQID); -+ if (status) { -+ goto err_event_source; -+ } -+ -+ status = hid_add_device(hid); -+ if (status) { -+ goto err_add_hid; -+ } -+ -+ return 0; -+ -+err_add_hid: -+ surface_sam_ssh_disable_event_source(SAM_EVENT_SID_VHF_TC, 0x01, SAM_EVENT_SID_VHF_RQID); -+err_event_source: -+ surface_sam_ssh_remove_event_handler(SAM_EVENT_SID_VHF_RQID); -+err_event_handler: -+ hid_destroy_device(hid); -+ platform_set_drvdata(pdev, NULL); -+err_create_hid: -+ kfree(drvdata); -+ return status; -+} -+ -+static int surface_sam_sid_vhf_remove(struct platform_device *pdev) -+{ -+ struct sid_vhf_drvdata *drvdata = platform_get_drvdata(pdev); -+ -+ surface_sam_ssh_disable_event_source(SAM_EVENT_SID_VHF_TC, 0x01, SAM_EVENT_SID_VHF_RQID); -+ surface_sam_ssh_remove_event_handler(SAM_EVENT_SID_VHF_RQID); -+ -+ hid_destroy_device(drvdata->event_ctx.hid); -+ kfree(drvdata); -+ -+ platform_set_drvdata(pdev, NULL); -+ return 0; -+} -+ -+static struct platform_driver surface_sam_sid_vhf = { -+ .probe = surface_sam_sid_vhf_probe, -+ .remove = surface_sam_sid_vhf_remove, -+ .driver = { -+ .name = "surface_sam_sid_vhf", -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_sam_sid_vhf); -+ -+MODULE_AUTHOR("Blaž Hrastnik "); -+MODULE_DESCRIPTION("Driver for HID devices connected via Surface SAM"); -+MODULE_LICENSE("GPL v2"); -+MODULE_ALIAS("platform:surface_sam_sid_vhf"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_ssh.c b/drivers/platform/x86/surface_sam/surface_sam_ssh.c -new file mode 100644 -index 000000000000..34905cf29a51 ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_ssh.c -@@ -0,0 +1,1773 @@ -+/* -+ * Surface Serial Hub (SSH) driver for communication with the Surface/System -+ * Aggregator Module. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "surface_sam_ssh.h" -+ -+ -+#define SSH_RQST_TAG_FULL "surface_sam_ssh_rqst: " -+#define SSH_RQST_TAG "rqst: " -+#define SSH_EVENT_TAG "event: " -+#define SSH_RECV_TAG "recv: " -+ -+#define SSH_SUPPORTED_FLOW_CONTROL_MASK (~((u8) ACPI_UART_FLOW_CONTROL_HW)) -+ -+#define SSH_BYTELEN_SYNC 2 -+#define SSH_BYTELEN_TERM 2 -+#define SSH_BYTELEN_CRC 2 -+#define SSH_BYTELEN_CTRL 4 // command-header, ACK, or RETRY -+#define SSH_BYTELEN_CMDFRAME 8 // without payload -+ -+#define SSH_MAX_WRITE ( \ -+ SSH_BYTELEN_SYNC \ -+ + SSH_BYTELEN_CTRL \ -+ + SSH_BYTELEN_CRC \ -+ + SSH_BYTELEN_CMDFRAME \ -+ + SURFACE_SAM_SSH_MAX_RQST_PAYLOAD \ -+ + SSH_BYTELEN_CRC \ -+) -+ -+#define SSH_MSG_LEN_CTRL ( \ -+ SSH_BYTELEN_SYNC \ -+ + SSH_BYTELEN_CTRL \ -+ + SSH_BYTELEN_CRC \ -+ + SSH_BYTELEN_TERM \ -+) -+ -+#define SSH_MSG_LEN_CMD_BASE ( \ -+ SSH_BYTELEN_SYNC \ -+ + SSH_BYTELEN_CTRL \ -+ + SSH_BYTELEN_CRC \ -+ + SSH_BYTELEN_CRC \ -+) // without payload and command-frame -+ -+#define SSH_WRITE_TIMEOUT msecs_to_jiffies(1000) -+#define SSH_READ_TIMEOUT msecs_to_jiffies(1000) -+#define SSH_NUM_RETRY 3 -+ -+#define SSH_WRITE_BUF_LEN SSH_MAX_WRITE -+#define SSH_READ_BUF_LEN 512 // must be power of 2 -+#define SSH_EVAL_BUF_LEN SSH_MAX_WRITE // also works for reading -+ -+#define SSH_FRAME_TYPE_CMD_NOACK 0x00 // request/event that does not to be ACKed -+#define SSH_FRAME_TYPE_CMD 0x80 // request/event -+#define SSH_FRAME_TYPE_ACK 0x40 // ACK for request/event -+#define SSH_FRAME_TYPE_RETRY 0x04 // error or retry indicator -+ -+#define SSH_FRAME_OFFS_CTRL SSH_BYTELEN_SYNC -+#define SSH_FRAME_OFFS_CTRL_CRC (SSH_FRAME_OFFS_CTRL + SSH_BYTELEN_CTRL) -+#define SSH_FRAME_OFFS_TERM (SSH_FRAME_OFFS_CTRL_CRC + SSH_BYTELEN_CRC) -+#define SSH_FRAME_OFFS_CMD SSH_FRAME_OFFS_TERM // either TERM or CMD -+#define SSH_FRAME_OFFS_CMD_PLD (SSH_FRAME_OFFS_CMD + SSH_BYTELEN_CMDFRAME) -+ -+/* -+ * A note on Request IDs (RQIDs): -+ * 0x0000 is not a valid RQID -+ * 0x0001 is valid, but reserved for Surface Laptop keyboard events -+ */ -+#define SAM_NUM_EVENT_TYPES ((1 << SURFACE_SAM_SSH_RQID_EVENT_BITS) - 1) -+ -+/* -+ * Sync: aa 55 -+ * Terminate: ff ff -+ * -+ * Request Message: sync cmd-hdr crc(cmd-hdr) cmd-rqst-frame crc(cmd-rqst-frame) -+ * Ack Message: sync ack crc(ack) terminate -+ * Retry Message: sync retry crc(retry) terminate -+ * Response Message: sync cmd-hdr crc(cmd-hdr) cmd-resp-frame crc(cmd-resp-frame) -+ * -+ * Command Header: 80 LEN 00 SEQ -+ * Ack: 40 00 00 SEQ -+ * Retry: 04 00 00 00 -+ * Command Request Frame: 80 RTC 01 00 RIID RQID RCID PLD -+ * Command Response Frame: 80 RTC 00 01 RIID RQID RCID PLD -+ */ -+ -+struct ssh_frame_ctrl { -+ u8 type; -+ u8 len; // without crc -+ u8 pad; -+ u8 seq; -+} __packed; -+ -+struct ssh_frame_cmd { -+ u8 type; -+ u8 tc; -+ u8 pri_out; -+ u8 pri_in; -+ u8 iid; -+ u8 rqid_lo; // id for request/response matching (low byte) -+ u8 rqid_hi; // id for request/response matching (high byte) -+ u8 cid; -+} __packed; -+ -+ -+enum ssh_ec_state { -+ SSH_EC_UNINITIALIZED, -+ SSH_EC_INITIALIZED, -+ SSH_EC_SUSPENDED, -+}; -+ -+struct ssh_counters { -+ u8 seq; // control sequence id -+ u16 rqid; // id for request/response matching -+}; -+ -+struct ssh_writer { -+ u8 *data; -+ u8 *ptr; -+} __packed; -+ -+enum ssh_receiver_state { -+ SSH_RCV_DISCARD, -+ SSH_RCV_CONTROL, -+ SSH_RCV_COMMAND, -+}; -+ -+struct ssh_receiver { -+ spinlock_t lock; -+ enum ssh_receiver_state state; -+ struct completion signal; -+ struct kfifo fifo; -+ struct { -+ bool pld; -+ u8 seq; -+ u16 rqid; -+ } expect; -+ struct { -+ u16 cap; -+ u16 len; -+ u8 *ptr; -+ } eval_buf; -+}; -+ -+struct ssh_event_handler { -+ surface_sam_ssh_event_handler_fn handler; -+ surface_sam_ssh_event_handler_delay delay; -+ void *data; -+}; -+ -+struct ssh_events { -+ spinlock_t lock; -+ struct workqueue_struct *queue_ack; -+ struct workqueue_struct *queue_evt; -+ struct ssh_event_handler handler[SAM_NUM_EVENT_TYPES]; -+}; -+ -+struct sam_ssh_ec { -+ struct mutex lock; -+ enum ssh_ec_state state; -+ struct serdev_device *serdev; -+ struct ssh_counters counter; -+ struct ssh_writer writer; -+ struct ssh_receiver receiver; -+ struct ssh_events events; -+ int irq; -+ bool irq_wakeup_enabled; -+}; -+ -+struct ssh_fifo_packet { -+ u8 type; // packet type (ACK/RETRY/CMD) -+ u8 seq; -+ u8 len; -+}; -+ -+struct ssh_event_work { -+ refcount_t refcount; -+ struct sam_ssh_ec *ec; -+ struct work_struct work_ack; -+ struct delayed_work work_evt; -+ struct surface_sam_ssh_event event; -+ u8 seq; -+}; -+ -+ -+static struct sam_ssh_ec ssh_ec = { -+ .lock = __MUTEX_INITIALIZER(ssh_ec.lock), -+ .state = SSH_EC_UNINITIALIZED, -+ .serdev = NULL, -+ .counter = { -+ .seq = 0, -+ .rqid = 0, -+ }, -+ .writer = { -+ .data = NULL, -+ .ptr = NULL, -+ }, -+ .receiver = { -+ .lock = __SPIN_LOCK_UNLOCKED(), -+ .state = SSH_RCV_DISCARD, -+ .expect = {}, -+ }, -+ .events = { -+ .lock = __SPIN_LOCK_UNLOCKED(), -+ .handler = {}, -+ }, -+ .irq = -1, -+}; -+ -+ -+inline static struct sam_ssh_ec *surface_sam_ssh_acquire(void) -+{ -+ struct sam_ssh_ec *ec = &ssh_ec; -+ -+ mutex_lock(&ec->lock); -+ return ec; -+} -+ -+inline static void surface_sam_ssh_release(struct sam_ssh_ec *ec) -+{ -+ mutex_unlock(&ec->lock); -+} -+ -+inline static struct sam_ssh_ec *surface_sam_ssh_acquire_init(void) -+{ -+ struct sam_ssh_ec *ec = surface_sam_ssh_acquire(); -+ -+ if (ec->state == SSH_EC_UNINITIALIZED) { -+ surface_sam_ssh_release(ec); -+ return NULL; -+ } -+ -+ return ec; -+} -+ -+int surface_sam_ssh_consumer_register(struct device *consumer) -+{ -+ u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; -+ struct sam_ssh_ec *ec; -+ struct device_link *link; -+ -+ ec = surface_sam_ssh_acquire_init(); -+ if (!ec) { -+ return -ENXIO; -+ } -+ -+ link = device_link_add(consumer, &ec->serdev->dev, flags); -+ if (!link) { -+ return -EFAULT; -+ } -+ -+ surface_sam_ssh_release(ec); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(surface_sam_ssh_consumer_register); -+ -+ -+inline static u16 sam_rqid_to_rqst(u16 rqid) { -+ return rqid << SURFACE_SAM_SSH_RQID_EVENT_BITS; -+} -+ -+inline static bool sam_rqid_is_event(u16 rqid) { -+ const u16 mask = (1 << SURFACE_SAM_SSH_RQID_EVENT_BITS) - 1; -+ return rqid != 0 && (rqid | mask) == mask; -+} -+ -+int surface_sam_ssh_enable_event_source(u8 tc, u8 unknown, u16 rqid) -+{ -+ u8 pld[4] = { tc, unknown, rqid & 0xff, rqid >> 8 }; -+ u8 buf[1] = { 0x00 }; -+ -+ struct surface_sam_ssh_rqst rqst = { -+ .tc = 0x01, -+ .cid = 0x0b, -+ .iid = 0x00, -+ .pri = SURFACE_SAM_PRIORITY_NORMAL, -+ .snc = 0x01, -+ .cdl = 0x04, -+ .pld = pld, -+ }; -+ -+ struct surface_sam_ssh_buf result = { -+ result.cap = ARRAY_SIZE(buf), -+ result.len = 0, -+ result.data = buf, -+ }; -+ -+ int status; -+ -+ // only allow RQIDs that lie within event spectrum -+ if (!sam_rqid_is_event(rqid)) { -+ return -EINVAL; -+ } -+ -+ status = surface_sam_ssh_rqst(&rqst, &result); -+ -+ if (buf[0] != 0x00) { -+ printk(KERN_WARNING SSH_RQST_TAG_FULL -+ "unexpected result while enabling event source: 0x%02x\n", -+ buf[0]); -+ } -+ -+ return status; -+ -+} -+EXPORT_SYMBOL_GPL(surface_sam_ssh_enable_event_source); -+ -+int surface_sam_ssh_disable_event_source(u8 tc, u8 unknown, u16 rqid) -+{ -+ u8 pld[4] = { tc, unknown, rqid & 0xff, rqid >> 8 }; -+ u8 buf[1] = { 0x00 }; -+ -+ struct surface_sam_ssh_rqst rqst = { -+ .tc = 0x01, -+ .cid = 0x0c, -+ .iid = 0x00, -+ .pri = SURFACE_SAM_PRIORITY_NORMAL, -+ .snc = 0x01, -+ .cdl = 0x04, -+ .pld = pld, -+ }; -+ -+ struct surface_sam_ssh_buf result = { -+ result.cap = ARRAY_SIZE(buf), -+ result.len = 0, -+ result.data = buf, -+ }; -+ -+ int status; -+ -+ // only allow RQIDs that lie within event spectrum -+ if (!sam_rqid_is_event(rqid)) { -+ return -EINVAL; -+ } -+ -+ status = surface_sam_ssh_rqst(&rqst, &result); -+ -+ if (buf[0] != 0x00) { -+ printk(KERN_WARNING SSH_RQST_TAG_FULL -+ "unexpected result while disabling event source: 0x%02x\n", -+ buf[0]); -+ } -+ -+ return status; -+} -+EXPORT_SYMBOL_GPL(surface_sam_ssh_disable_event_source); -+ -+static unsigned long sam_event_default_delay(struct surface_sam_ssh_event *event, void *data) -+{ -+ return event->pri == SURFACE_SAM_PRIORITY_HIGH ? SURFACE_SAM_SSH_EVENT_IMMEDIATE : 0; -+} -+ -+int surface_sam_ssh_set_delayed_event_handler( -+ u16 rqid, surface_sam_ssh_event_handler_fn fn, -+ surface_sam_ssh_event_handler_delay delay, -+ void *data) -+{ -+ struct sam_ssh_ec *ec; -+ unsigned long flags; -+ -+ if (!sam_rqid_is_event(rqid)) { -+ return -EINVAL; -+ } -+ -+ ec = surface_sam_ssh_acquire_init(); -+ if (!ec) { -+ return -ENXIO; -+ } -+ -+ if (!delay) { -+ delay = sam_event_default_delay; -+ } -+ -+ spin_lock_irqsave(&ec->events.lock, flags); -+ // check if we already have a handler -+ if (ec->events.handler[rqid - 1].handler) { -+ spin_unlock_irqrestore(&ec->events.lock, flags); -+ return -EINVAL; -+ } -+ -+ // 0 is not a valid event RQID -+ ec->events.handler[rqid - 1].handler = fn; -+ ec->events.handler[rqid - 1].delay = delay; -+ ec->events.handler[rqid - 1].data = data; -+ -+ spin_unlock_irqrestore(&ec->events.lock, flags); -+ surface_sam_ssh_release(ec); -+ -+ return 0; -+} -+EXPORT_SYMBOL_GPL(surface_sam_ssh_set_delayed_event_handler); -+ -+int surface_sam_ssh_remove_event_handler(u16 rqid) -+{ -+ struct sam_ssh_ec *ec; -+ unsigned long flags; -+ -+ if (!sam_rqid_is_event(rqid)) { -+ return -EINVAL; -+ } -+ -+ ec = surface_sam_ssh_acquire_init(); -+ if (!ec) { -+ return -ENXIO; -+ } -+ -+ spin_lock_irqsave(&ec->events.lock, flags); -+ -+ // 0 is not a valid event RQID -+ ec->events.handler[rqid - 1].handler = NULL; -+ ec->events.handler[rqid - 1].delay = NULL; -+ ec->events.handler[rqid - 1].data = NULL; -+ -+ spin_unlock_irqrestore(&ec->events.lock, flags); -+ surface_sam_ssh_release(ec); -+ -+ /* -+ * Make sure that the handler is not in use any more after we've -+ * removed it. -+ */ -+ flush_workqueue(ec->events.queue_evt); -+ -+ return 0; -+} -+EXPORT_SYMBOL_GPL(surface_sam_ssh_remove_event_handler); -+ -+ -+inline static u16 ssh_crc(const u8 *buf, size_t size) -+{ -+ return crc_ccitt_false(0xffff, buf, size); -+} -+ -+inline static void ssh_write_u16(struct ssh_writer *writer, u16 in) -+{ -+ put_unaligned_le16(in, writer->ptr); -+ writer->ptr += 2; -+} -+ -+inline static void ssh_write_crc(struct ssh_writer *writer, -+ const u8 *buf, size_t size) -+{ -+ ssh_write_u16(writer, ssh_crc(buf, size)); -+} -+ -+inline static void ssh_write_syn(struct ssh_writer *writer) -+{ -+ u8 *w = writer->ptr; -+ -+ *w++ = 0xaa; -+ *w++ = 0x55; -+ -+ writer->ptr = w; -+} -+ -+inline static void ssh_write_ter(struct ssh_writer *writer) -+{ -+ u8 *w = writer->ptr; -+ -+ *w++ = 0xff; -+ *w++ = 0xff; -+ -+ writer->ptr = w; -+} -+ -+inline static void ssh_write_buf(struct ssh_writer *writer, -+ u8 *in, size_t len) -+{ -+ writer->ptr = memcpy(writer->ptr, in, len) + len; -+} -+ -+inline static void ssh_write_hdr(struct ssh_writer *writer, -+ const struct surface_sam_ssh_rqst *rqst, -+ struct sam_ssh_ec *ec) -+{ -+ struct ssh_frame_ctrl *hdr = (struct ssh_frame_ctrl *)writer->ptr; -+ u8 *begin = writer->ptr; -+ -+ hdr->type = SSH_FRAME_TYPE_CMD; -+ hdr->len = SSH_BYTELEN_CMDFRAME + rqst->cdl; // without CRC -+ hdr->pad = 0x00; -+ hdr->seq = ec->counter.seq; -+ -+ writer->ptr += sizeof(*hdr); -+ -+ ssh_write_crc(writer, begin, writer->ptr - begin); -+} -+ -+inline static void ssh_write_cmd(struct ssh_writer *writer, -+ const struct surface_sam_ssh_rqst *rqst, -+ struct sam_ssh_ec *ec) -+{ -+ struct ssh_frame_cmd *cmd = (struct ssh_frame_cmd *)writer->ptr; -+ u8 *begin = writer->ptr; -+ -+ u16 rqid = sam_rqid_to_rqst(ec->counter.rqid); -+ u8 rqid_lo = rqid & 0xFF; -+ u8 rqid_hi = rqid >> 8; -+ -+ cmd->type = SSH_FRAME_TYPE_CMD; -+ cmd->tc = rqst->tc; -+ cmd->pri_out = rqst->pri; -+ cmd->pri_in = 0x00; -+ cmd->iid = rqst->iid; -+ cmd->rqid_lo = rqid_lo; -+ cmd->rqid_hi = rqid_hi; -+ cmd->cid = rqst->cid; -+ -+ writer->ptr += sizeof(*cmd); -+ -+ ssh_write_buf(writer, rqst->pld, rqst->cdl); -+ ssh_write_crc(writer, begin, writer->ptr - begin); -+} -+ -+inline static void ssh_write_ack(struct ssh_writer *writer, u8 seq) -+{ -+ struct ssh_frame_ctrl *ack = (struct ssh_frame_ctrl *)writer->ptr; -+ u8 *begin = writer->ptr; -+ -+ ack->type = SSH_FRAME_TYPE_ACK; -+ ack->len = 0x00; -+ ack->pad = 0x00; -+ ack->seq = seq; -+ -+ writer->ptr += sizeof(*ack); -+ -+ ssh_write_crc(writer, begin, writer->ptr - begin); -+} -+ -+inline static void ssh_writer_reset(struct ssh_writer *writer) -+{ -+ writer->ptr = writer->data; -+} -+ -+inline static int ssh_writer_flush(struct sam_ssh_ec *ec) -+{ -+ struct ssh_writer *writer = &ec->writer; -+ struct serdev_device *serdev = ec->serdev; -+ int status; -+ -+ size_t len = writer->ptr - writer->data; -+ -+ dev_dbg(&ec->serdev->dev, "sending message\n"); -+ print_hex_dump_debug("send: ", DUMP_PREFIX_OFFSET, 16, 1, -+ writer->data, writer->ptr - writer->data, false); -+ -+ status = serdev_device_write(serdev, writer->data, len, SSH_WRITE_TIMEOUT); -+ return status >= 0 ? 0 : status; -+} -+ -+inline static void ssh_write_msg_cmd(struct sam_ssh_ec *ec, -+ const struct surface_sam_ssh_rqst *rqst) -+{ -+ ssh_writer_reset(&ec->writer); -+ ssh_write_syn(&ec->writer); -+ ssh_write_hdr(&ec->writer, rqst, ec); -+ ssh_write_cmd(&ec->writer, rqst, ec); -+} -+ -+inline static void ssh_write_msg_ack(struct sam_ssh_ec *ec, u8 seq) -+{ -+ ssh_writer_reset(&ec->writer); -+ ssh_write_syn(&ec->writer); -+ ssh_write_ack(&ec->writer, seq); -+ ssh_write_ter(&ec->writer); -+} -+ -+inline static void ssh_receiver_restart(struct sam_ssh_ec *ec, -+ const struct surface_sam_ssh_rqst *rqst) -+{ -+ unsigned long flags; -+ -+ spin_lock_irqsave(&ec->receiver.lock, flags); -+ reinit_completion(&ec->receiver.signal); -+ ec->receiver.state = SSH_RCV_CONTROL; -+ ec->receiver.expect.pld = rqst->snc; -+ ec->receiver.expect.seq = ec->counter.seq; -+ ec->receiver.expect.rqid = sam_rqid_to_rqst(ec->counter.rqid); -+ ec->receiver.eval_buf.len = 0; -+ spin_unlock_irqrestore(&ec->receiver.lock, flags); -+} -+ -+inline static void ssh_receiver_discard(struct sam_ssh_ec *ec) -+{ -+ unsigned long flags; -+ -+ spin_lock_irqsave(&ec->receiver.lock, flags); -+ ec->receiver.state = SSH_RCV_DISCARD; -+ ec->receiver.eval_buf.len = 0; -+ kfifo_reset(&ec->receiver.fifo); -+ spin_unlock_irqrestore(&ec->receiver.lock, flags); -+} -+ -+static int surface_sam_ssh_rqst_unlocked(struct sam_ssh_ec *ec, -+ const struct surface_sam_ssh_rqst *rqst, -+ struct surface_sam_ssh_buf *result) -+{ -+ struct device *dev = &ec->serdev->dev; -+ struct ssh_fifo_packet packet = {}; -+ int status; -+ int try; -+ unsigned int rem; -+ -+ if (rqst->cdl > SURFACE_SAM_SSH_MAX_RQST_PAYLOAD) { -+ dev_err(dev, SSH_RQST_TAG "request payload too large\n"); -+ return -EINVAL; -+ } -+ -+ // write command in buffer, we may need it multiple times -+ ssh_write_msg_cmd(ec, rqst); -+ ssh_receiver_restart(ec, rqst); -+ -+ // send command, try to get an ack response -+ for (try = 0; try < SSH_NUM_RETRY; try++) { -+ status = ssh_writer_flush(ec); -+ if (status) { -+ goto out; -+ } -+ -+ rem = wait_for_completion_timeout(&ec->receiver.signal, SSH_READ_TIMEOUT); -+ if (rem) { -+ // completion assures valid packet, thus ignore returned length -+ (void) !kfifo_out(&ec->receiver.fifo, &packet, sizeof(packet)); -+ -+ if (packet.type == SSH_FRAME_TYPE_ACK) { -+ break; -+ } -+ } -+ } -+ -+ // check if we ran out of tries? -+ if (try >= SSH_NUM_RETRY) { -+ dev_err(dev, SSH_RQST_TAG "communication failed %d times, giving up\n", try); -+ status = -EIO; -+ goto out; -+ } -+ -+ ec->counter.seq += 1; -+ ec->counter.rqid += 1; -+ -+ // get command response/payload -+ if (rqst->snc && result) { -+ rem = wait_for_completion_timeout(&ec->receiver.signal, SSH_READ_TIMEOUT); -+ if (rem) { -+ // completion assures valid packet, thus ignore returned length -+ (void) !kfifo_out(&ec->receiver.fifo, &packet, sizeof(packet)); -+ -+ if (result->cap < packet.len) { -+ status = -EINVAL; -+ goto out; -+ } -+ -+ // completion assures valid packet, thus ignore returned length -+ (void) !kfifo_out(&ec->receiver.fifo, result->data, packet.len); -+ result->len = packet.len; -+ } else { -+ dev_err(dev, SSH_RQST_TAG "communication timed out\n"); -+ status = -EIO; -+ goto out; -+ } -+ -+ // send ACK -+ if (packet.type == SSH_FRAME_TYPE_CMD) { -+ ssh_write_msg_ack(ec, packet.seq); -+ status = ssh_writer_flush(ec); -+ if (status) { -+ goto out; -+ } -+ } -+ } -+ -+out: -+ ssh_receiver_discard(ec); -+ return status; -+} -+ -+int surface_sam_ssh_rqst(const struct surface_sam_ssh_rqst *rqst, struct surface_sam_ssh_buf *result) -+{ -+ struct sam_ssh_ec *ec; -+ int status; -+ -+ ec = surface_sam_ssh_acquire_init(); -+ if (!ec) { -+ printk(KERN_WARNING SSH_RQST_TAG_FULL "embedded controller is uninitialized\n"); -+ return -ENXIO; -+ } -+ -+ if (ec->state == SSH_EC_SUSPENDED) { -+ dev_warn(&ec->serdev->dev, SSH_RQST_TAG "embedded controller is suspended\n"); -+ -+ surface_sam_ssh_release(ec); -+ return -EPERM; -+ } -+ -+ status = surface_sam_ssh_rqst_unlocked(ec, rqst, result); -+ -+ surface_sam_ssh_release(ec); -+ return status; -+} -+EXPORT_SYMBOL_GPL(surface_sam_ssh_rqst); -+ -+ -+static int surface_sam_ssh_ec_resume(struct sam_ssh_ec *ec) -+{ -+ u8 buf[1] = { 0x00 }; -+ -+ struct surface_sam_ssh_rqst rqst = { -+ .tc = 0x01, -+ .cid = 0x16, -+ .iid = 0x00, -+ .pri = SURFACE_SAM_PRIORITY_NORMAL, -+ .snc = 0x01, -+ .cdl = 0x00, -+ .pld = NULL, -+ }; -+ -+ struct surface_sam_ssh_buf result = { -+ result.cap = ARRAY_SIZE(buf), -+ result.len = 0, -+ result.data = buf, -+ }; -+ -+ int status = surface_sam_ssh_rqst_unlocked(ec, &rqst, &result); -+ if (status) { -+ return status; -+ } -+ -+ if (buf[0] != 0x00) { -+ dev_warn(&ec->serdev->dev, -+ "unexpected result while trying to resume EC: 0x%02x\n", -+ buf[0]); -+ } -+ -+ return 0; -+} -+ -+static int surface_sam_ssh_ec_suspend(struct sam_ssh_ec *ec) -+{ -+ u8 buf[1] = { 0x00 }; -+ -+ struct surface_sam_ssh_rqst rqst = { -+ .tc = 0x01, -+ .cid = 0x15, -+ .iid = 0x00, -+ .pri = SURFACE_SAM_PRIORITY_NORMAL, -+ .snc = 0x01, -+ .cdl = 0x00, -+ .pld = NULL, -+ }; -+ -+ struct surface_sam_ssh_buf result = { -+ result.cap = ARRAY_SIZE(buf), -+ result.len = 0, -+ result.data = buf, -+ }; -+ -+ int status = surface_sam_ssh_rqst_unlocked(ec, &rqst, &result); -+ if (status) { -+ return status; -+ } -+ -+ if (buf[0] != 0x00) { -+ dev_warn(&ec->serdev->dev, -+ "unexpected result while trying to suspend EC: 0x%02x\n", -+ buf[0]); -+ } -+ -+ return 0; -+} -+ -+ -+inline static bool ssh_is_valid_syn(const u8 *ptr) -+{ -+ return ptr[0] == 0xaa && ptr[1] == 0x55; -+} -+ -+inline static bool ssh_is_valid_ter(const u8 *ptr) -+{ -+ return ptr[0] == 0xff && ptr[1] == 0xff; -+} -+ -+inline static bool ssh_is_valid_crc(const u8 *begin, const u8 *end) -+{ -+ u16 crc = ssh_crc(begin, end - begin); -+ return (end[0] == (crc & 0xff)) && (end[1] == (crc >> 8)); -+} -+ -+ -+static int surface_sam_ssh_send_ack(struct sam_ssh_ec *ec, u8 seq) -+{ -+ int status; -+ u8 buf[SSH_MSG_LEN_CTRL]; -+ u16 crc; -+ -+ buf[0] = 0xaa; -+ buf[1] = 0x55; -+ buf[2] = 0x40; -+ buf[3] = 0x00; -+ buf[4] = 0x00; -+ buf[5] = seq; -+ -+ crc = ssh_crc(buf + SSH_FRAME_OFFS_CTRL, SSH_BYTELEN_CTRL); -+ buf[6] = crc & 0xff; -+ buf[7] = crc >> 8; -+ -+ buf[8] = 0xff; -+ buf[9] = 0xff; -+ -+ dev_dbg(&ec->serdev->dev, "sending message\n"); -+ print_hex_dump_debug("send: ", DUMP_PREFIX_OFFSET, 16, 1, -+ buf, SSH_MSG_LEN_CTRL, false); -+ -+ status = serdev_device_write(ec->serdev, buf, SSH_MSG_LEN_CTRL, SSH_WRITE_TIMEOUT); -+ return status >= 0 ? 0 : status; -+} -+ -+static void surface_sam_ssh_event_work_ack_handler(struct work_struct *_work) -+{ -+ struct surface_sam_ssh_event *event; -+ struct ssh_event_work *work; -+ struct sam_ssh_ec *ec; -+ struct device *dev; -+ int status; -+ -+ work = container_of(_work, struct ssh_event_work, work_ack); -+ event = &work->event; -+ ec = work->ec; -+ dev = &ec->serdev->dev; -+ -+ // make sure we load a fresh ec state -+ smp_mb(); -+ -+ if (ec->state == SSH_EC_INITIALIZED) { -+ status = surface_sam_ssh_send_ack(ec, work->seq); -+ if (status) { -+ dev_err(dev, SSH_EVENT_TAG "failed to send ACK: %d\n", status); -+ } -+ } -+ -+ if (refcount_dec_and_test(&work->refcount)) { -+ kfree(work); -+ } -+} -+ -+static void surface_sam_ssh_event_work_evt_handler(struct work_struct *_work) -+{ -+ struct delayed_work *dwork = (struct delayed_work *)_work; -+ struct ssh_event_work *work; -+ struct surface_sam_ssh_event *event; -+ struct sam_ssh_ec *ec; -+ struct device *dev; -+ unsigned long flags; -+ -+ surface_sam_ssh_event_handler_fn handler; -+ void *handler_data; -+ -+ int status = 0; -+ -+ work = container_of(dwork, struct ssh_event_work, work_evt); -+ event = &work->event; -+ ec = work->ec; -+ dev = &ec->serdev->dev; -+ -+ spin_lock_irqsave(&ec->events.lock, flags); -+ handler = ec->events.handler[event->rqid - 1].handler; -+ handler_data = ec->events.handler[event->rqid - 1].data; -+ spin_unlock_irqrestore(&ec->events.lock, flags); -+ -+ /* -+ * During handler removal or driver release, we ensure every event gets -+ * handled before return of that function. Thus a handler obtained here is -+ * guaranteed to be valid at least until this function returns. -+ */ -+ -+ if (handler) { -+ status = handler(event, handler_data); -+ } else { -+ dev_warn(dev, SSH_EVENT_TAG "unhandled event (rqid: %04x)\n", event->rqid); -+ } -+ -+ if (status) { -+ dev_err(dev, SSH_EVENT_TAG "error handling event: %d\n", status); -+ } -+ -+ if (refcount_dec_and_test(&work->refcount)) { -+ kfree(work); -+ } -+} -+ -+static void ssh_handle_event(struct sam_ssh_ec *ec, const u8 *buf) -+{ -+ struct device *dev = &ec->serdev->dev; -+ const struct ssh_frame_ctrl *ctrl; -+ const struct ssh_frame_cmd *cmd; -+ struct ssh_event_work *work; -+ unsigned long flags; -+ u16 pld_len; -+ -+ surface_sam_ssh_event_handler_delay delay_fn; -+ void *handler_data; -+ unsigned long delay; -+ -+ ctrl = (const struct ssh_frame_ctrl *)(buf + SSH_FRAME_OFFS_CTRL); -+ cmd = (const struct ssh_frame_cmd *)(buf + SSH_FRAME_OFFS_CMD); -+ -+ pld_len = ctrl->len - SSH_BYTELEN_CMDFRAME; -+ -+ work = kzalloc(sizeof(struct ssh_event_work) + pld_len, GFP_ATOMIC); -+ if (!work) { -+ dev_warn(dev, SSH_EVENT_TAG "failed to allocate memory, dropping event\n"); -+ return; -+ } -+ -+ refcount_set(&work->refcount, 1); -+ work->ec = ec; -+ work->seq = ctrl->seq; -+ work->event.rqid = (cmd->rqid_hi << 8) | cmd->rqid_lo; -+ work->event.tc = cmd->tc; -+ work->event.cid = cmd->cid; -+ work->event.iid = cmd->iid; -+ work->event.pri = cmd->pri_in; -+ work->event.len = pld_len; -+ work->event.pld = ((u8*) work) + sizeof(struct ssh_event_work); -+ -+ memcpy(work->event.pld, buf + SSH_FRAME_OFFS_CMD_PLD, pld_len); -+ -+ // queue ACK for if required -+ if (ctrl->type == SSH_FRAME_TYPE_CMD) { -+ refcount_set(&work->refcount, 2); -+ INIT_WORK(&work->work_ack, surface_sam_ssh_event_work_ack_handler); -+ queue_work(ec->events.queue_ack, &work->work_ack); -+ } -+ -+ spin_lock_irqsave(&ec->events.lock, flags); -+ handler_data = ec->events.handler[work->event.rqid - 1].data; -+ delay_fn = ec->events.handler[work->event.rqid - 1].delay; -+ -+ /* Note: -+ * We need to check delay_fn here: This may have never been set as we -+ * can't guarantee that events only occur when they have been enabled. -+ */ -+ delay = delay_fn ? delay_fn(&work->event, handler_data) : 0; -+ spin_unlock_irqrestore(&ec->events.lock, flags); -+ -+ // immediate execution for high priority events (e.g. keyboard) -+ if (delay == SURFACE_SAM_SSH_EVENT_IMMEDIATE) { -+ surface_sam_ssh_event_work_evt_handler(&work->work_evt.work); -+ } else { -+ INIT_DELAYED_WORK(&work->work_evt, surface_sam_ssh_event_work_evt_handler); -+ queue_delayed_work(ec->events.queue_evt, &work->work_evt, delay); -+ } -+} -+ -+static int ssh_receive_msg_ctrl(struct sam_ssh_ec *ec, const u8 *buf, size_t size) -+{ -+ struct device *dev = &ec->serdev->dev; -+ struct ssh_receiver *rcv = &ec->receiver; -+ const struct ssh_frame_ctrl *ctrl; -+ struct ssh_fifo_packet packet; -+ -+ const u8 *ctrl_begin = buf + SSH_FRAME_OFFS_CTRL; -+ const u8 *ctrl_end = buf + SSH_FRAME_OFFS_CTRL_CRC; -+ -+ ctrl = (const struct ssh_frame_ctrl *)(ctrl_begin); -+ -+ // actual length check -+ if (size < SSH_MSG_LEN_CTRL) { -+ return 0; // need more bytes -+ } -+ -+ // validate TERM -+ if (!ssh_is_valid_ter(buf + SSH_FRAME_OFFS_TERM)) { -+ dev_err(dev, SSH_RECV_TAG "invalid end of message\n"); -+ return size; // discard everything -+ } -+ -+ // validate CRC -+ if (!ssh_is_valid_crc(ctrl_begin, ctrl_end)) { -+ dev_err(dev, SSH_RECV_TAG "invalid checksum (ctrl)\n"); -+ return SSH_MSG_LEN_CTRL; // only discard message -+ } -+ -+ // check if we expect the message -+ if (rcv->state != SSH_RCV_CONTROL) { -+ dev_err(dev, SSH_RECV_TAG "discarding message: ctrl not expected\n"); -+ return SSH_MSG_LEN_CTRL; // discard message -+ } -+ -+ // check if it is for our request -+ if (ctrl->type == SSH_FRAME_TYPE_ACK && ctrl->seq != rcv->expect.seq) { -+ dev_err(dev, SSH_RECV_TAG "discarding message: ack does not match\n"); -+ return SSH_MSG_LEN_CTRL; // discard message -+ } -+ -+ // we now have a valid & expected ACK/RETRY message -+ dev_dbg(dev, SSH_RECV_TAG "valid control message received (type: 0x%02x)\n", ctrl->type); -+ -+ packet.type = ctrl->type; -+ packet.seq = ctrl->seq; -+ packet.len = 0; -+ -+ if (kfifo_avail(&rcv->fifo) >= sizeof(packet)) { -+ kfifo_in(&rcv->fifo, (u8 *) &packet, sizeof(packet)); -+ -+ } else { -+ dev_warn(dev, SSH_RECV_TAG -+ "dropping frame: not enough space in fifo (type = %d)\n", -+ ctrl->type); -+ -+ return SSH_MSG_LEN_CTRL; // discard message -+ } -+ -+ // update decoder state -+ if (ctrl->type == SSH_FRAME_TYPE_ACK) { -+ rcv->state = rcv->expect.pld -+ ? SSH_RCV_COMMAND -+ : SSH_RCV_DISCARD; -+ } -+ -+ complete(&rcv->signal); -+ return SSH_MSG_LEN_CTRL; // handled message -+} -+ -+static int ssh_receive_msg_cmd(struct sam_ssh_ec *ec, const u8 *buf, size_t size) -+{ -+ struct device *dev = &ec->serdev->dev; -+ struct ssh_receiver *rcv = &ec->receiver; -+ const struct ssh_frame_ctrl *ctrl; -+ const struct ssh_frame_cmd *cmd; -+ struct ssh_fifo_packet packet; -+ -+ const u8 *ctrl_begin = buf + SSH_FRAME_OFFS_CTRL; -+ const u8 *ctrl_end = buf + SSH_FRAME_OFFS_CTRL_CRC; -+ const u8 *cmd_begin = buf + SSH_FRAME_OFFS_CMD; -+ const u8 *cmd_begin_pld = buf + SSH_FRAME_OFFS_CMD_PLD; -+ const u8 *cmd_end; -+ -+ size_t msg_len; -+ -+ ctrl = (const struct ssh_frame_ctrl *)(ctrl_begin); -+ cmd = (const struct ssh_frame_cmd *)(cmd_begin); -+ -+ // we need at least a full control frame -+ if (size < (SSH_BYTELEN_SYNC + SSH_BYTELEN_CTRL + SSH_BYTELEN_CRC)) { -+ return 0; // need more bytes -+ } -+ -+ // validate control-frame CRC -+ if (!ssh_is_valid_crc(ctrl_begin, ctrl_end)) { -+ dev_err(dev, SSH_RECV_TAG "invalid checksum (cmd-ctrl)\n"); -+ /* -+ * We can't be sure here if length is valid, thus -+ * discard everything. -+ */ -+ return size; -+ } -+ -+ // actual length check (ctrl->len contains command-frame but not crc) -+ msg_len = SSH_MSG_LEN_CMD_BASE + ctrl->len; -+ if (size < msg_len) { -+ return 0; // need more bytes -+ } -+ -+ cmd_end = cmd_begin + ctrl->len; -+ -+ // validate command-frame type -+ if (cmd->type != SSH_FRAME_TYPE_CMD) { -+ dev_err(dev, SSH_RECV_TAG "expected command frame type but got 0x%02x\n", cmd->type); -+ return size; // discard everything -+ } -+ -+ // validate command-frame CRC -+ if (!ssh_is_valid_crc(cmd_begin, cmd_end)) { -+ dev_err(dev, SSH_RECV_TAG "invalid checksum (cmd-pld)\n"); -+ -+ /* -+ * The message length is provided in the control frame. As we -+ * already validated that, we can be sure here that it's -+ * correct, so we only need to discard the message. -+ */ -+ return msg_len; -+ } -+ -+ // check if we received an event notification -+ if (sam_rqid_is_event((cmd->rqid_hi << 8) | cmd->rqid_lo)) { -+ ssh_handle_event(ec, buf); -+ return msg_len; // handled message -+ } -+ -+ // check if we expect the message -+ if (rcv->state != SSH_RCV_COMMAND) { -+ dev_dbg(dev, SSH_RECV_TAG "discarding message: command not expected\n"); -+ return msg_len; // discard message -+ } -+ -+ // check if response is for our request -+ if (rcv->expect.rqid != (cmd->rqid_lo | (cmd->rqid_hi << 8))) { -+ dev_dbg(dev, SSH_RECV_TAG "discarding message: command not a match\n"); -+ return msg_len; // discard message -+ } -+ -+ // we now have a valid & expected command message -+ dev_dbg(dev, SSH_RECV_TAG "valid command message received\n"); -+ -+ packet.type = ctrl->type; -+ packet.seq = ctrl->seq; -+ packet.len = cmd_end - cmd_begin_pld; -+ -+ if (kfifo_avail(&rcv->fifo) >= sizeof(packet) + packet.len) { -+ kfifo_in(&rcv->fifo, &packet, sizeof(packet)); -+ kfifo_in(&rcv->fifo, cmd_begin_pld, packet.len); -+ -+ } else { -+ dev_warn(dev, SSH_RECV_TAG -+ "dropping frame: not enough space in fifo (type = %d)\n", -+ ctrl->type); -+ -+ return SSH_MSG_LEN_CTRL; // discard message -+ } -+ -+ rcv->state = SSH_RCV_DISCARD; -+ -+ complete(&rcv->signal); -+ return msg_len; // handled message -+} -+ -+static int ssh_eval_buf(struct sam_ssh_ec *ec, const u8 *buf, size_t size) -+{ -+ struct device *dev = &ec->serdev->dev; -+ struct ssh_frame_ctrl *ctrl; -+ -+ // we need at least a control frame to check what to do -+ if (size < (SSH_BYTELEN_SYNC + SSH_BYTELEN_CTRL)) { -+ return 0; // need more bytes -+ } -+ -+ // make sure we're actually at the start of a new message -+ if (!ssh_is_valid_syn(buf)) { -+ dev_err(dev, SSH_RECV_TAG "invalid start of message\n"); -+ return size; // discard everything -+ } -+ -+ // handle individual message types seperately -+ ctrl = (struct ssh_frame_ctrl *)(buf + SSH_FRAME_OFFS_CTRL); -+ -+ switch (ctrl->type) { -+ case SSH_FRAME_TYPE_ACK: -+ case SSH_FRAME_TYPE_RETRY: -+ return ssh_receive_msg_ctrl(ec, buf, size); -+ -+ case SSH_FRAME_TYPE_CMD: -+ case SSH_FRAME_TYPE_CMD_NOACK: -+ return ssh_receive_msg_cmd(ec, buf, size); -+ -+ default: -+ dev_err(dev, SSH_RECV_TAG "unknown frame type 0x%02x\n", ctrl->type); -+ return size; // discard everything -+ } -+} -+ -+static int ssh_receive_buf(struct serdev_device *serdev, -+ const unsigned char *buf, size_t size) -+{ -+ struct sam_ssh_ec *ec = serdev_device_get_drvdata(serdev); -+ struct ssh_receiver *rcv = &ec->receiver; -+ unsigned long flags; -+ int offs = 0; -+ int used, n; -+ -+ dev_dbg(&serdev->dev, SSH_RECV_TAG "received buffer (size: %zu)\n", size); -+ print_hex_dump_debug(SSH_RECV_TAG, DUMP_PREFIX_OFFSET, 16, 1, buf, size, false); -+ -+ /* -+ * The battery _BIX message gets a bit long, thus we have to add some -+ * additional buffering here. -+ */ -+ -+ spin_lock_irqsave(&rcv->lock, flags); -+ -+ // copy to eval-buffer -+ used = min(size, (size_t)(rcv->eval_buf.cap - rcv->eval_buf.len)); -+ memcpy(rcv->eval_buf.ptr + rcv->eval_buf.len, buf, used); -+ rcv->eval_buf.len += used; -+ -+ // evaluate buffer until we need more bytes or eval-buf is empty -+ while (offs < rcv->eval_buf.len) { -+ n = rcv->eval_buf.len - offs; -+ n = ssh_eval_buf(ec, rcv->eval_buf.ptr + offs, n); -+ if (n <= 0) break; // need more bytes -+ -+ offs += n; -+ } -+ -+ // throw away the evaluated parts -+ rcv->eval_buf.len -= offs; -+ memmove(rcv->eval_buf.ptr, rcv->eval_buf.ptr + offs, rcv->eval_buf.len); -+ -+ spin_unlock_irqrestore(&rcv->lock, flags); -+ -+ return used; -+} -+ -+ -+#ifdef CONFIG_SURFACE_SAM_SSH_DEBUG_DEVICE -+ -+#include -+ -+static char sam_ssh_debug_rqst_buf_sysfs[SURFACE_SAM_SSH_MAX_RQST_RESPONSE + 1] = { 0 }; -+static char sam_ssh_debug_rqst_buf_pld[SURFACE_SAM_SSH_MAX_RQST_PAYLOAD] = { 0 }; -+static char sam_ssh_debug_rqst_buf_res[SURFACE_SAM_SSH_MAX_RQST_RESPONSE] = { 0 }; -+ -+struct sysfs_rqst { -+ u8 tc; -+ u8 cid; -+ u8 iid; -+ u8 pri; -+ u8 snc; -+ u8 cdl; -+ u8 pld[0]; -+} __packed; -+ -+static ssize_t rqst_read(struct file *f, struct kobject *kobj, struct bin_attribute *attr, -+ char *buf, loff_t offs, size_t count) -+{ -+ if (offs < 0 || count + offs > SURFACE_SAM_SSH_MAX_RQST_RESPONSE) { -+ return -EINVAL; -+ } -+ -+ memcpy(buf, sam_ssh_debug_rqst_buf_sysfs + offs, count); -+ return count; -+} -+ -+static ssize_t rqst_write(struct file *f, struct kobject *kobj, struct bin_attribute *attr, -+ char *buf, loff_t offs, size_t count) -+{ -+ struct sysfs_rqst *input; -+ struct surface_sam_ssh_rqst rqst = {}; -+ struct surface_sam_ssh_buf result = {}; -+ int status; -+ -+ // check basic write constriants -+ if (offs != 0 || count > SURFACE_SAM_SSH_MAX_RQST_PAYLOAD + sizeof(struct sysfs_rqst)) { -+ return -EINVAL; -+ } -+ -+ if (count < sizeof(struct sysfs_rqst)) { -+ return -EINVAL; -+ } -+ -+ input = (struct sysfs_rqst *)buf; -+ -+ // payload length should be consistent with data provided -+ if (input->cdl + sizeof(struct sysfs_rqst) != count) { -+ return -EINVAL; -+ } -+ -+ rqst.tc = input->tc; -+ rqst.cid = input->cid; -+ rqst.iid = input->iid; -+ rqst.pri = input->pri; -+ rqst.snc = input->snc; -+ rqst.cdl = input->cdl; -+ rqst.pld = sam_ssh_debug_rqst_buf_pld; -+ memcpy(sam_ssh_debug_rqst_buf_pld, &input->pld[0], input->cdl); -+ -+ result.cap = SURFACE_SAM_SSH_MAX_RQST_RESPONSE; -+ result.len = 0; -+ result.data = sam_ssh_debug_rqst_buf_res; -+ -+ status = surface_sam_ssh_rqst(&rqst, &result); -+ if (status) { -+ return status; -+ } -+ -+ sam_ssh_debug_rqst_buf_sysfs[0] = result.len; -+ memcpy(sam_ssh_debug_rqst_buf_sysfs + 1, result.data, result.len); -+ memset(sam_ssh_debug_rqst_buf_sysfs + result.len + 1, 0, -+ SURFACE_SAM_SSH_MAX_RQST_RESPONSE + 1 - result.len); -+ -+ return count; -+} -+ -+static const BIN_ATTR_RW(rqst, SURFACE_SAM_SSH_MAX_RQST_RESPONSE + 1); -+ -+ -+int surface_sam_ssh_sysfs_register(struct device *dev) -+{ -+ return sysfs_create_bin_file(&dev->kobj, &bin_attr_rqst); -+} -+ -+void surface_sam_ssh_sysfs_unregister(struct device *dev) -+{ -+ sysfs_remove_bin_file(&dev->kobj, &bin_attr_rqst); -+} -+ -+#else /* CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE */ -+ -+int surface_sam_ssh_sysfs_register(struct device *dev) -+{ -+ return 0; -+} -+ -+void surface_sam_ssh_sysfs_unregister(struct device *dev) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_SAM_SSH_DEBUG_DEVICE */ -+ -+ -+static const struct acpi_gpio_params gpio_sam_wakeup_int = { 0, 0, false }; -+static const struct acpi_gpio_params gpio_sam_wakeup = { 1, 0, false }; -+ -+static const struct acpi_gpio_mapping surface_sam_acpi_gpios[] = { -+ { "sam_wakeup-int-gpio", &gpio_sam_wakeup_int, 1 }, -+ { "sam_wakeup-gpio", &gpio_sam_wakeup, 1 }, -+ { }, -+}; -+ -+static irqreturn_t surface_sam_irq_handler(int irq, void *dev_id) -+{ -+ struct serdev_device *serdev = dev_id; -+ -+ dev_info(&serdev->dev, "wake irq triggered\n"); -+ return IRQ_HANDLED; -+} -+ -+static int surface_sam_setup_irq(struct serdev_device *serdev) -+{ -+ const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING; -+ struct gpio_desc *gpiod; -+ int irq; -+ int status; -+ -+ gpiod = gpiod_get(&serdev->dev, "sam_wakeup-int", GPIOD_ASIS); -+ if (IS_ERR(gpiod)) -+ return PTR_ERR(gpiod); -+ -+ irq = gpiod_to_irq(gpiod); -+ gpiod_put(gpiod); -+ -+ if (irq < 0) -+ return irq; -+ -+ status = request_threaded_irq(irq, NULL, surface_sam_irq_handler, -+ irqf, "surface_sam_wakeup", serdev); -+ if (status) -+ return status; -+ -+ return irq; -+} -+ -+ -+static acpi_status -+ssh_setup_from_resource(struct acpi_resource *resource, void *context) -+{ -+ struct serdev_device *serdev = context; -+ struct acpi_resource_common_serialbus *serial; -+ struct acpi_resource_uart_serialbus *uart; -+ int status = 0; -+ -+ if (resource->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) { -+ return AE_OK; -+ } -+ -+ serial = &resource->data.common_serial_bus; -+ if (serial->type != ACPI_RESOURCE_SERIAL_TYPE_UART) { -+ return AE_OK; -+ } -+ -+ uart = &resource->data.uart_serial_bus; -+ -+ // set up serdev device -+ serdev_device_set_baudrate(serdev, uart->default_baud_rate); -+ -+ // serdev currently only supports RTSCTS flow control -+ if (uart->flow_control & SSH_SUPPORTED_FLOW_CONTROL_MASK) { -+ dev_warn(&serdev->dev, "unsupported flow control (value: 0x%02x)\n", uart->flow_control); -+ } -+ -+ // set RTSCTS flow control -+ serdev_device_set_flow_control(serdev, uart->flow_control & ACPI_UART_FLOW_CONTROL_HW); -+ -+ // serdev currently only supports EVEN/ODD parity -+ switch (uart->parity) { -+ case ACPI_UART_PARITY_NONE: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE); -+ break; -+ case ACPI_UART_PARITY_EVEN: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_EVEN); -+ break; -+ case ACPI_UART_PARITY_ODD: -+ status = serdev_device_set_parity(serdev, SERDEV_PARITY_ODD); -+ break; -+ default: -+ dev_warn(&serdev->dev, "unsupported parity (value: 0x%02x)\n", uart->parity); -+ break; -+ } -+ -+ if (status) { -+ dev_err(&serdev->dev, "failed to set parity (value: 0x%02x)\n", uart->parity); -+ return status; -+ } -+ -+ return AE_CTRL_TERMINATE; // we've found the resource and are done -+} -+ -+ -+static int surface_sam_ssh_suspend(struct device *dev) -+{ -+ struct sam_ssh_ec *ec; -+ int status; -+ -+ dev_dbg(dev, "suspending\n"); -+ -+ ec = surface_sam_ssh_acquire_init(); -+ if (ec) { -+ status = surface_sam_ssh_ec_suspend(ec); -+ if (status) { -+ surface_sam_ssh_release(ec); -+ return status; -+ } -+ -+ if (device_may_wakeup(dev)) { -+ status = enable_irq_wake(ec->irq); -+ if (status) { -+ surface_sam_ssh_release(ec); -+ return status; -+ } -+ -+ ec->irq_wakeup_enabled = true; -+ } else { -+ ec->irq_wakeup_enabled = false; -+ } -+ -+ ec->state = SSH_EC_SUSPENDED; -+ surface_sam_ssh_release(ec); -+ } -+ -+ return 0; -+} -+ -+static int surface_sam_ssh_resume(struct device *dev) -+{ -+ struct sam_ssh_ec *ec; -+ int status; -+ -+ dev_dbg(dev, "resuming\n"); -+ -+ ec = surface_sam_ssh_acquire_init(); -+ if (ec) { -+ ec->state = SSH_EC_INITIALIZED; -+ -+ if (ec->irq_wakeup_enabled) { -+ status = disable_irq_wake(ec->irq); -+ if (status) { -+ surface_sam_ssh_release(ec); -+ return status; -+ } -+ -+ ec->irq_wakeup_enabled = false; -+ } -+ -+ status = surface_sam_ssh_ec_resume(ec); -+ if (status) { -+ surface_sam_ssh_release(ec); -+ return status; -+ } -+ -+ surface_sam_ssh_release(ec); -+ } -+ -+ return 0; -+} -+ -+static SIMPLE_DEV_PM_OPS(surface_sam_ssh_pm_ops, surface_sam_ssh_suspend, surface_sam_ssh_resume); -+ -+ -+static const struct serdev_device_ops ssh_device_ops = { -+ .receive_buf = ssh_receive_buf, -+ .write_wakeup = serdev_device_write_wakeup, -+}; -+ -+ -+int surface_sam_ssh_sysfs_register(struct device *dev); -+void surface_sam_ssh_sysfs_unregister(struct device *dev); -+ -+static int surface_sam_ssh_probe(struct serdev_device *serdev) -+{ -+ struct sam_ssh_ec *ec; -+ struct workqueue_struct *event_queue_ack; -+ struct workqueue_struct *event_queue_evt; -+ u8 *write_buf; -+ u8 *read_buf; -+ u8 *eval_buf; -+ acpi_handle *ssh = ACPI_HANDLE(&serdev->dev); -+ acpi_status status; -+ int irq; -+ -+ dev_dbg(&serdev->dev, "probing\n"); -+ -+ if (gpiod_count(&serdev->dev, NULL) < 0) -+ return -ENODEV; -+ -+ status = devm_acpi_dev_add_driver_gpios(&serdev->dev, surface_sam_acpi_gpios); -+ if (status) -+ return status; -+ -+ // allocate buffers -+ write_buf = kzalloc(SSH_WRITE_BUF_LEN, GFP_KERNEL); -+ if (!write_buf) { -+ status = -ENOMEM; -+ goto err_write_buf; -+ } -+ -+ read_buf = kzalloc(SSH_READ_BUF_LEN, GFP_KERNEL); -+ if (!read_buf) { -+ status = -ENOMEM; -+ goto err_read_buf; -+ } -+ -+ eval_buf = kzalloc(SSH_EVAL_BUF_LEN, GFP_KERNEL); -+ if (!eval_buf) { -+ status = -ENOMEM; -+ goto err_eval_buf; -+ } -+ -+ event_queue_ack = create_singlethread_workqueue("surface_sh_ackq"); -+ if (!event_queue_ack) { -+ status = -ENOMEM; -+ goto err_ackq; -+ } -+ -+ event_queue_evt = create_workqueue("surface_sh_evtq"); -+ if (!event_queue_evt) { -+ status = -ENOMEM; -+ goto err_evtq; -+ } -+ -+ irq = surface_sam_setup_irq(serdev); -+ if (irq < 0) { -+ status = irq; -+ goto err_irq; -+ } -+ -+ // set up EC -+ ec = surface_sam_ssh_acquire(); -+ if (ec->state != SSH_EC_UNINITIALIZED) { -+ dev_err(&serdev->dev, "embedded controller already initialized\n"); -+ surface_sam_ssh_release(ec); -+ -+ status = -EBUSY; -+ goto err_busy; -+ } -+ -+ ec->serdev = serdev; -+ ec->irq = irq; -+ ec->writer.data = write_buf; -+ ec->writer.ptr = write_buf; -+ -+ // initialize receiver -+ init_completion(&ec->receiver.signal); -+ kfifo_init(&ec->receiver.fifo, read_buf, SSH_READ_BUF_LEN); -+ ec->receiver.eval_buf.ptr = eval_buf; -+ ec->receiver.eval_buf.cap = SSH_EVAL_BUF_LEN; -+ ec->receiver.eval_buf.len = 0; -+ -+ // initialize event handling -+ ec->events.queue_ack = event_queue_ack; -+ ec->events.queue_evt = event_queue_evt; -+ -+ ec->state = SSH_EC_INITIALIZED; -+ -+ serdev_device_set_drvdata(serdev, ec); -+ -+ // ensure everything is properly set-up before we open the device -+ smp_mb(); -+ -+ serdev_device_set_client_ops(serdev, &ssh_device_ops); -+ status = serdev_device_open(serdev); -+ if (status) { -+ goto err_open; -+ } -+ -+ status = acpi_walk_resources(ssh, METHOD_NAME__CRS, -+ ssh_setup_from_resource, serdev); -+ if (ACPI_FAILURE(status)) { -+ goto err_devinit; -+ } -+ -+ status = surface_sam_ssh_ec_resume(ec); -+ if (status) { -+ goto err_devinit; -+ } -+ -+ status = surface_sam_ssh_sysfs_register(&serdev->dev); -+ if (status) { -+ goto err_devinit; -+ } -+ -+ surface_sam_ssh_release(ec); -+ -+ // TODO: The EC can wake up the system via the associated GPIO interrupt in -+ // multiple situations. One of which is the remaining battery capacity -+ // falling below a certain threshold. Normally, we should use the -+ // device_init_wakeup function, however, the EC also seems to have other -+ // reasons for waking up the system and it seems that Windows has -+ // additional checks whether the system should be resumed. In short, this -+ // causes some spourious unwanted wake-ups. For now let's thus default -+ // power/wakeup to false. -+ device_set_wakeup_capable(&serdev->dev, true); -+ acpi_walk_dep_device_list(ssh); -+ -+ return 0; -+ -+err_devinit: -+ serdev_device_close(serdev); -+err_open: -+ ec->state = SSH_EC_UNINITIALIZED; -+ serdev_device_set_drvdata(serdev, NULL); -+ surface_sam_ssh_release(ec); -+err_busy: -+ free_irq(irq, serdev); -+err_irq: -+ destroy_workqueue(event_queue_evt); -+err_evtq: -+ destroy_workqueue(event_queue_ack); -+err_ackq: -+ kfree(eval_buf); -+err_eval_buf: -+ kfree(read_buf); -+err_read_buf: -+ kfree(write_buf); -+err_write_buf: -+ return status; -+} -+ -+static void surface_sam_ssh_remove(struct serdev_device *serdev) -+{ -+ struct sam_ssh_ec *ec; -+ unsigned long flags; -+ int status; -+ -+ ec = surface_sam_ssh_acquire_init(); -+ if (!ec) { -+ return; -+ } -+ -+ free_irq(ec->irq, serdev); -+ surface_sam_ssh_sysfs_unregister(&serdev->dev); -+ -+ // suspend EC and disable events -+ status = surface_sam_ssh_ec_suspend(ec); -+ if (status) { -+ dev_err(&serdev->dev, "failed to suspend EC: %d\n", status); -+ } -+ -+ // make sure all events (received up to now) have been properly handled -+ flush_workqueue(ec->events.queue_ack); -+ flush_workqueue(ec->events.queue_evt); -+ -+ // remove event handlers -+ spin_lock_irqsave(&ec->events.lock, flags); -+ memset(ec->events.handler, 0, -+ sizeof(struct ssh_event_handler) -+ * SAM_NUM_EVENT_TYPES); -+ spin_unlock_irqrestore(&ec->events.lock, flags); -+ -+ // set device to deinitialized state -+ ec->state = SSH_EC_UNINITIALIZED; -+ ec->serdev = NULL; -+ -+ // ensure state and serdev get set before continuing -+ smp_mb(); -+ -+ /* -+ * Flush any event that has not been processed yet to ensure we're not going to -+ * use the serial device any more (e.g. for ACKing). -+ */ -+ flush_workqueue(ec->events.queue_ack); -+ flush_workqueue(ec->events.queue_evt); -+ -+ serdev_device_close(serdev); -+ -+ /* -+ * Only at this point, no new events can be received. Destroying the -+ * workqueue here flushes all remaining events. Those events will be -+ * silently ignored and neither ACKed nor any handler gets called. -+ */ -+ destroy_workqueue(ec->events.queue_ack); -+ destroy_workqueue(ec->events.queue_evt); -+ -+ // free writer -+ kfree(ec->writer.data); -+ ec->writer.data = NULL; -+ ec->writer.ptr = NULL; -+ -+ // free receiver -+ spin_lock_irqsave(&ec->receiver.lock, flags); -+ ec->receiver.state = SSH_RCV_DISCARD; -+ kfifo_free(&ec->receiver.fifo); -+ -+ kfree(ec->receiver.eval_buf.ptr); -+ ec->receiver.eval_buf.ptr = NULL; -+ ec->receiver.eval_buf.cap = 0; -+ ec->receiver.eval_buf.len = 0; -+ spin_unlock_irqrestore(&ec->receiver.lock, flags); -+ -+ device_set_wakeup_capable(&serdev->dev, false); -+ serdev_device_set_drvdata(serdev, NULL); -+ surface_sam_ssh_release(ec); -+} -+ -+ -+static const struct acpi_device_id surface_sam_ssh_match[] = { -+ { "MSHW0084", 0 }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_sam_ssh_match); -+ -+static struct serdev_device_driver surface_sam_ssh = { -+ .probe = surface_sam_ssh_probe, -+ .remove = surface_sam_ssh_remove, -+ .driver = { -+ .name = "surface_sam_ssh", -+ .acpi_match_table = ACPI_PTR(surface_sam_ssh_match), -+ .pm = &surface_sam_ssh_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+static int __init surface_sam_ssh_init(void) -+{ -+ return serdev_device_driver_register(&surface_sam_ssh); -+} -+ -+static void __exit surface_sam_ssh_exit(void) -+{ -+ serdev_device_driver_unregister(&surface_sam_ssh); -+} -+ -+/* -+ * Ensure that the driver is loaded late due to some issues with the UART -+ * communication. Specifically, we want to ensure that DMA is ready and being -+ * used. Not using DMA can result in spurious communication failures, -+ * especially during boot, which among other things will result in wrong -+ * battery information (via ACPI _BIX) being displayed. Using a late init_call -+ * instead of the normal module_init gives the DMA subsystem time to -+ * initialize and via that results in a more stable communication, avoiding -+ * such failures. -+ */ -+late_initcall(surface_sam_ssh_init); -+module_exit(surface_sam_ssh_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Serial Hub Driver for 5th Generation Surface Devices"); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_ssh.h b/drivers/platform/x86/surface_sam/surface_sam_ssh.h -new file mode 100644 -index 000000000000..714bba6a9457 ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_ssh.h -@@ -0,0 +1,97 @@ -+/* -+ * Interface for Surface Serial Hub (SSH). -+ * -+ * The SSH is the main communication hub for communication between host and -+ * the Surface/System Aggregator Module (SAM) on newer Microsoft Surface -+ * devices (Book 2, Pro 5, Laptops, ...). Also referred to as SAM-over-SSH. -+ * Older devices (Book 1, Pro 4) use SAM-over-I2C. -+ */ -+ -+#ifndef _SURFACE_SAM_SSH_H -+#define _SURFACE_SAM_SSH_H -+ -+#include -+#include -+ -+ -+/* -+ * Maximum request payload size in bytes. -+ * Value based on ACPI (255 bytes minus header/status bytes). -+ */ -+#define SURFACE_SAM_SSH_MAX_RQST_PAYLOAD (255 - 10) -+ -+/* -+ * Maximum response payload size in bytes. -+ * Value based on ACPI (255 bytes minus header/status bytes). -+ */ -+#define SURFACE_SAM_SSH_MAX_RQST_RESPONSE (255 - 4) -+ -+/* -+ * The number of (lower) bits of the request ID (RQID) reserved for events. -+ * These bits may only be used exclusively for events sent from the EC to the -+ * host. -+ */ -+#define SURFACE_SAM_SSH_RQID_EVENT_BITS 5 -+ -+/* -+ * Special event-handler delay value indicating that the corresponding event -+ * should be handled immediately in the interrupt and not be relayed through -+ * the workqueue. Intended for low-latency events, such as keyboard events. -+ */ -+#define SURFACE_SAM_SSH_EVENT_IMMEDIATE ((unsigned long) -1) -+ -+ -+#define SURFACE_SAM_PRIORITY_NORMAL 1 -+#define SURFACE_SAM_PRIORITY_HIGH 2 -+ -+ -+struct surface_sam_ssh_buf { -+ u8 cap; -+ u8 len; -+ u8 *data; -+}; -+ -+struct surface_sam_ssh_rqst { -+ u8 tc; // target category -+ u8 cid; // command ID -+ u8 iid; // instance ID -+ u8 pri; // priority -+ u8 snc; // expect response flag -+ u8 cdl; // command data length (lenght of payload) -+ u8 *pld; // pointer to payload of length cdl -+}; -+ -+struct surface_sam_ssh_event { -+ u16 rqid; // event type/source ID -+ u8 tc; // target category -+ u8 cid; // command ID -+ u8 iid; // instance ID -+ u8 pri; // priority -+ u8 len; // length of payload -+ u8 *pld; // payload of length len -+}; -+ -+ -+typedef int (*surface_sam_ssh_event_handler_fn)(struct surface_sam_ssh_event *event, void *data); -+typedef unsigned long (*surface_sam_ssh_event_handler_delay)(struct surface_sam_ssh_event *event, void *data); -+ -+int surface_sam_ssh_consumer_register(struct device *consumer); -+ -+int surface_sam_ssh_rqst(const struct surface_sam_ssh_rqst *rqst, struct surface_sam_ssh_buf *result); -+ -+int surface_sam_ssh_enable_event_source(u8 tc, u8 unknown, u16 rqid); -+int surface_sam_ssh_disable_event_source(u8 tc, u8 unknown, u16 rqid); -+int surface_sam_ssh_remove_event_handler(u16 rqid); -+ -+int surface_sam_ssh_set_delayed_event_handler(u16 rqid, -+ surface_sam_ssh_event_handler_fn fn, -+ surface_sam_ssh_event_handler_delay delay, -+ void *data); -+ -+static inline int surface_sam_ssh_set_event_handler(u16 rqid, surface_sam_ssh_event_handler_fn fn, void *data) -+{ -+ return surface_sam_ssh_set_delayed_event_handler(rqid, fn, NULL, data); -+} -+ -+ -+#endif /* _SURFACE_SAM_SSH_H */ -diff --git a/drivers/platform/x86/surface_sam/surface_sam_vhf.c b/drivers/platform/x86/surface_sam/surface_sam_vhf.c -new file mode 100644 -index 000000000000..0ed0ebbdb3cb ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_vhf.c -@@ -0,0 +1,276 @@ -+/* -+ * Virtual HID Framwork (VHF) driver for input events via SAM. -+ * Used for keyboard input events on the Surface Laptops. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "surface_sam_ssh.h" -+ -+ -+#define USB_VENDOR_ID_MICROSOFT 0x045e -+#define USB_DEVICE_ID_MS_VHF 0xf001 -+ -+#define VHF_INPUT_NAME "Microsoft Virtual HID Framework Device" -+ -+/* -+ * Request ID for VHF events. This value is based on the output of the Surface -+ * EC and should not be changed. -+ */ -+#define SAM_EVENT_VHF_RQID 0x0001 -+#define SAM_EVENT_VHF_TC 0x08 -+ -+ -+struct vhf_evtctx { -+ struct device *dev; -+ struct hid_device *hid; -+}; -+ -+struct vhf_drvdata { -+ struct vhf_evtctx event_ctx; -+}; -+ -+ -+/* -+ * These report descriptors have been extracted from a Surface Book 2. -+ * They seems to be similar enough to be usable on the Surface Laptop. -+ */ -+static const u8 vhf_hid_desc[] = { -+ // keyboard descriptor (event command ID 0x03) -+ 0x05, 0x01, /* Usage Page (Desktop), */ -+ 0x09, 0x06, /* Usage (Keyboard), */ -+ 0xA1, 0x01, /* Collection (Application), */ -+ 0x85, 0x01, /* Report ID (1), */ -+ 0x15, 0x00, /* Logical Minimum (0), */ -+ 0x25, 0x01, /* Logical Maximum (1), */ -+ 0x75, 0x01, /* Report Size (1), */ -+ 0x95, 0x08, /* Report Count (8), */ -+ 0x05, 0x07, /* Usage Page (Keyboard), */ -+ 0x19, 0xE0, /* Usage Minimum (KB Leftcontrol), */ -+ 0x29, 0xE7, /* Usage Maximum (KB Right GUI), */ -+ 0x81, 0x02, /* Input (Variable), */ -+ 0x75, 0x08, /* Report Size (8), */ -+ 0x95, 0x0A, /* Report Count (10), */ -+ 0x19, 0x00, /* Usage Minimum (None), */ -+ 0x29, 0x91, /* Usage Maximum (KB LANG2), */ -+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ -+ 0x81, 0x00, /* Input, */ -+ 0x05, 0x0C, /* Usage Page (Consumer), */ -+ 0x0A, 0xC0, 0x02, /* Usage (02C0h), */ -+ 0xA1, 0x02, /* Collection (Logical), */ -+ 0x1A, 0xC1, 0x02, /* Usage Minimum (02C1h), */ -+ 0x2A, 0xC6, 0x02, /* Usage Maximum (02C6h), */ -+ 0x95, 0x06, /* Report Count (6), */ -+ 0xB1, 0x03, /* Feature (Constant, Variable), */ -+ 0xC0, /* End Collection, */ -+ 0x05, 0x08, /* Usage Page (LED), */ -+ 0x19, 0x01, /* Usage Minimum (01h), */ -+ 0x29, 0x03, /* Usage Maximum (03h), */ -+ 0x75, 0x01, /* Report Size (1), */ -+ 0x95, 0x03, /* Report Count (3), */ -+ 0x25, 0x01, /* Logical Maximum (1), */ -+ 0x91, 0x02, /* Output (Variable), */ -+ 0x95, 0x05, /* Report Count (5), */ -+ 0x91, 0x01, /* Output (Constant), */ -+ 0xC0, /* End Collection, */ -+ -+ // media key descriptor (event command ID 0x04) -+ 0x05, 0x0C, /* Usage Page (Consumer), */ -+ 0x09, 0x01, /* Usage (Consumer Control), */ -+ 0xA1, 0x01, /* Collection (Application), */ -+ 0x85, 0x03, /* Report ID (3), */ -+ 0x75, 0x10, /* Report Size (16), */ -+ 0x15, 0x00, /* Logical Minimum (0), */ -+ 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ -+ 0x19, 0x00, /* Usage Minimum (00h), */ -+ 0x2A, 0xFF, 0x03, /* Usage Maximum (03FFh), */ -+ 0x81, 0x00, /* Input, */ -+ 0xC0, /* End Collection, */ -+}; -+ -+ -+static int vhf_hid_start(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+ return 0; -+} -+ -+static void vhf_hid_stop(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+} -+ -+static int vhf_hid_open(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+ return 0; -+} -+ -+static void vhf_hid_close(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+} -+ -+static int vhf_hid_parse(struct hid_device *hid) -+{ -+ return hid_parse_report(hid, (u8 *)vhf_hid_desc, ARRAY_SIZE(vhf_hid_desc)); -+} -+ -+static int vhf_hid_raw_request(struct hid_device *hid, unsigned char reportnum, -+ u8 *buf, size_t len, unsigned char rtype, -+ int reqtype) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+ return 0; -+} -+ -+static int vhf_hid_output_report(struct hid_device *hid, u8 *buf, size_t len) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+ print_hex_dump_debug("report:", DUMP_PREFIX_OFFSET, 16, 1, buf, len, false); -+ -+ return len; -+} -+ -+static struct hid_ll_driver vhf_hid_ll_driver = { -+ .start = vhf_hid_start, -+ .stop = vhf_hid_stop, -+ .open = vhf_hid_open, -+ .close = vhf_hid_close, -+ .parse = vhf_hid_parse, -+ .raw_request = vhf_hid_raw_request, -+ .output_report = vhf_hid_output_report, -+}; -+ -+ -+static struct hid_device *vhf_create_hid_device(struct platform_device *pdev) -+{ -+ struct hid_device *hid; -+ -+ hid = hid_allocate_device(); -+ if (IS_ERR(hid)) { -+ return hid; -+ } -+ -+ hid->dev.parent = &pdev->dev; -+ -+ hid->bus = BUS_VIRTUAL; -+ hid->vendor = USB_VENDOR_ID_MICROSOFT; -+ hid->product = USB_DEVICE_ID_MS_VHF; -+ -+ hid->ll_driver = &vhf_hid_ll_driver; -+ -+ sprintf(hid->name, "%s", VHF_INPUT_NAME); -+ -+ return hid; -+} -+ -+static int vhf_event_handler(struct surface_sam_ssh_event *event, void *data) -+{ -+ struct vhf_evtctx *ctx = (struct vhf_evtctx *)data; -+ -+ if (event->tc == 0x08 && (event->cid == 0x03 || event->cid == 0x04)) { -+ return hid_input_report(ctx->hid, HID_INPUT_REPORT, event->pld, event->len, 1); -+ } -+ -+ dev_warn(ctx->dev, "unsupported event (tc = %d, cid = %d)\n", event->tc, event->cid); -+ return 0; -+} -+ -+static int surface_sam_vhf_probe(struct platform_device *pdev) -+{ -+ struct vhf_drvdata *drvdata; -+ struct hid_device *hid; -+ int status; -+ -+ // add device link to EC -+ status = surface_sam_ssh_consumer_register(&pdev->dev); -+ if (status) { -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ } -+ -+ drvdata = kzalloc(sizeof(struct vhf_drvdata), GFP_KERNEL); -+ if (!drvdata) { -+ return -ENOMEM; -+ } -+ -+ hid = vhf_create_hid_device(pdev); -+ if (IS_ERR(hid)) { -+ status = PTR_ERR(hid); -+ goto err_probe_hid; -+ } -+ -+ status = hid_add_device(hid); -+ if (status) { -+ goto err_add_hid; -+ } -+ -+ drvdata->event_ctx.dev = &pdev->dev; -+ drvdata->event_ctx.hid = hid; -+ -+ platform_set_drvdata(pdev, drvdata); -+ -+ status = surface_sam_ssh_set_event_handler( -+ SAM_EVENT_VHF_RQID, -+ vhf_event_handler, -+ &drvdata->event_ctx); -+ if (status) { -+ goto err_add_hid; -+ } -+ -+ status = surface_sam_ssh_enable_event_source(SAM_EVENT_VHF_TC, 0x01, SAM_EVENT_VHF_RQID); -+ if (status) { -+ goto err_event_source; -+ } -+ -+ return 0; -+ -+err_event_source: -+ surface_sam_ssh_remove_event_handler(SAM_EVENT_VHF_RQID); -+err_add_hid: -+ hid_destroy_device(hid); -+ platform_set_drvdata(pdev, NULL); -+err_probe_hid: -+ kfree(drvdata); -+ return status; -+} -+ -+static int surface_sam_vhf_remove(struct platform_device *pdev) -+{ -+ struct vhf_drvdata *drvdata = platform_get_drvdata(pdev); -+ -+ surface_sam_ssh_disable_event_source(SAM_EVENT_VHF_TC, 0x01, SAM_EVENT_VHF_RQID); -+ surface_sam_ssh_remove_event_handler(SAM_EVENT_VHF_RQID); -+ -+ hid_destroy_device(drvdata->event_ctx.hid); -+ kfree(drvdata); -+ -+ platform_set_drvdata(pdev, NULL); -+ return 0; -+} -+ -+ -+static const struct acpi_device_id surface_sam_vhf_match[] = { -+ { "MSHW0096" }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_sam_vhf_match); -+ -+static struct platform_driver surface_sam_vhf = { -+ .probe = surface_sam_vhf_probe, -+ .remove = surface_sam_vhf_remove, -+ .driver = { -+ .name = "surface_sam_vhf", -+ .acpi_match_table = ACPI_PTR(surface_sam_vhf_match), -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_sam_vhf); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Virtual HID Framework Driver for 5th Generation Surface Devices"); -+MODULE_LICENSE("GPL v2"); -diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c -index a0ac16ee6575..226adeec2aed 100644 ---- a/drivers/tty/serdev/core.c -+++ b/drivers/tty/serdev/core.c -@@ -552,16 +552,97 @@ static int of_serdev_register_devices(struct serdev_controller *ctrl) - } - - #ifdef CONFIG_ACPI -+ -+#define SERDEV_ACPI_MAX_SCAN_DEPTH 32 -+ -+struct acpi_serdev_lookup { -+ acpi_handle device_handle; -+ acpi_handle controller_handle; -+ int n; -+ int index; -+}; -+ -+static int acpi_serdev_parse_resource(struct acpi_resource *ares, void *data) -+{ -+ struct acpi_serdev_lookup *lookup = data; -+ struct acpi_resource_uart_serialbus *sb; -+ acpi_status status; -+ -+ if (ares->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) -+ return 1; -+ -+ if (ares->data.common_serial_bus.type != ACPI_RESOURCE_SERIAL_TYPE_UART) -+ return 1; -+ -+ if (lookup->index != -1 && lookup->n++ != lookup->index) -+ return 1; -+ -+ sb = &ares->data.uart_serial_bus; -+ -+ status = acpi_get_handle(lookup->device_handle, -+ sb->resource_source.string_ptr, -+ &lookup->controller_handle); -+ if (ACPI_FAILURE(status)) -+ return 1; -+ -+ /* -+ * NOTE: Ideally, we would also want to retreive other properties here, -+ * once setting them before opening the device is supported by serdev. -+ */ -+ -+ return 1; -+} -+ -+static int acpi_serdev_do_lookup(struct acpi_device *adev, -+ struct acpi_serdev_lookup *lookup) -+{ -+ struct list_head resource_list; -+ int ret; -+ -+ lookup->device_handle = acpi_device_handle(adev); -+ lookup->controller_handle = NULL; -+ lookup->n = 0; -+ -+ INIT_LIST_HEAD(&resource_list); -+ ret = acpi_dev_get_resources(adev, &resource_list, -+ acpi_serdev_parse_resource, lookup); -+ acpi_dev_free_resource_list(&resource_list); -+ -+ if (ret < 0) -+ return -EINVAL; -+ -+ return 0; -+} -+ -+static int acpi_serdev_check_resources(struct serdev_controller *ctrl, -+ struct acpi_device *adev) -+{ -+ struct acpi_serdev_lookup lookup; -+ int ret; -+ -+ if (acpi_bus_get_status(adev) || !adev->status.present) -+ return -EINVAL; -+ -+ /* Look for UARTSerialBusV2 resource */ -+ lookup.index = -1; // we only care for the last device -+ -+ ret = acpi_serdev_do_lookup(adev, &lookup); -+ if (ret) -+ return ret; -+ -+ /* Make sure controller and ResourceSource handle match */ -+ if (ACPI_HANDLE(ctrl->dev.parent) != lookup.controller_handle) -+ return -ENODEV; -+ -+ return 0; -+} -+ - static acpi_status acpi_serdev_register_device(struct serdev_controller *ctrl, -- struct acpi_device *adev) -+ struct acpi_device *adev) - { -- struct serdev_device *serdev = NULL; -+ struct serdev_device *serdev; - int err; - -- if (acpi_bus_get_status(adev) || !adev->status.present || -- acpi_device_enumerated(adev)) -- return AE_OK; -- - serdev = serdev_device_alloc(ctrl); - if (!serdev) { - dev_err(&ctrl->dev, "failed to allocate serdev device for %s\n", -@@ -583,7 +664,7 @@ static acpi_status acpi_serdev_register_device(struct serdev_controller *ctrl, - } - - static acpi_status acpi_serdev_add_device(acpi_handle handle, u32 level, -- void *data, void **return_value) -+ void *data, void **return_value) - { - struct serdev_controller *ctrl = data; - struct acpi_device *adev; -@@ -591,22 +672,28 @@ static acpi_status acpi_serdev_add_device(acpi_handle handle, u32 level, - if (acpi_bus_get_device(handle, &adev)) - return AE_OK; - -+ if (acpi_device_enumerated(adev)) -+ return AE_OK; -+ -+ if (acpi_serdev_check_resources(ctrl, adev)) -+ return AE_OK; -+ - return acpi_serdev_register_device(ctrl, adev); - } - -+ - static int acpi_serdev_register_devices(struct serdev_controller *ctrl) - { - acpi_status status; -- acpi_handle handle; - -- handle = ACPI_HANDLE(ctrl->dev.parent); -- if (!handle) -+ if (!has_acpi_companion(ctrl->dev.parent)) - return -ENODEV; - -- status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1, -+ status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, -+ SERDEV_ACPI_MAX_SCAN_DEPTH, - acpi_serdev_add_device, NULL, ctrl, NULL); - if (ACPI_FAILURE(status)) -- dev_dbg(&ctrl->dev, "failed to enumerate serdev slaves\n"); -+ dev_warn(&ctrl->dev, "failed to enumerate serdev slaves\n"); - - if (!ctrl->serdev) - return -ENODEV; --- -2.24.1 - diff --git a/patches/5.3/0002-buttons.patch b/patches/5.3/0002-buttons.patch deleted file mode 100644 index a35ed5d18..000000000 --- a/patches/5.3/0002-buttons.patch +++ /dev/null @@ -1,274 +0,0 @@ -From 5e6d16a8fb200eb0325061c7a0f10126d69c2466 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Sat, 27 Jul 2019 17:51:37 +0200 -Subject: [PATCH 02/10] buttons - ---- - drivers/input/misc/Kconfig | 6 +- - drivers/input/misc/soc_button_array.c | 112 +++++++++++++++++++--- - drivers/platform/x86/surfacepro3_button.c | 47 +++++++++ - 3 files changed, 150 insertions(+), 15 deletions(-) - -diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig -index d07c1eb15aa6..7d9ae394e597 100644 ---- a/drivers/input/misc/Kconfig -+++ b/drivers/input/misc/Kconfig -@@ -813,10 +813,10 @@ config INPUT_IDEAPAD_SLIDEBAR - - config INPUT_SOC_BUTTON_ARRAY - tristate "Windows-compatible SoC Button Array" -- depends on KEYBOARD_GPIO -+ depends on KEYBOARD_GPIO && ACPI - help -- Say Y here if you have a SoC-based tablet that originally -- runs Windows 8. -+ Say Y here if you have a SoC-based tablet that originally runs -+ Windows 8 or a Microsoft Surface Book 2, Pro 5, Laptop 1 or later. - - To compile this driver as a module, choose M here: the - module will be called soc_button_array. -diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c -index 5e59f8e57f8e..ef89698c7d43 100644 ---- a/drivers/input/misc/soc_button_array.c -+++ b/drivers/input/misc/soc_button_array.c -@@ -25,6 +25,11 @@ struct soc_button_info { - bool wakeup; - }; - -+struct soc_device_data { -+ const struct soc_button_info *button_info; -+ int (*check)(struct device *dev); -+}; -+ - /* - * Some of the buttons like volume up/down are auto repeat, while others - * are not. To support both, we register two platform devices, and put -@@ -87,8 +92,20 @@ soc_button_device_create(struct platform_device *pdev, - continue; - - gpio = soc_button_lookup_gpio(&pdev->dev, info->acpi_index); -- if (!gpio_is_valid(gpio)) -+ if (!gpio_is_valid(gpio)) { -+ /* -+ * Skip GPIO if not present. Note we deliberately -+ * ignore -EPROBE_DEFER errors here. On some devices -+ * Intel is using so called virtual GPIOs which are not -+ * GPIOs at all but some way for AML code to check some -+ * random status bits without need a custom opregion. -+ * In some cases the resources table we parse points to -+ * such a virtual GPIO, since these are not real GPIOs -+ * we do not have a driver for these so they will never -+ * show up, therefor we ignore -EPROBE_DEFER. -+ */ - continue; -+ } - - gpio_keys[n_buttons].type = info->event_type; - gpio_keys[n_buttons].code = info->event_code; -@@ -309,23 +326,26 @@ static int soc_button_remove(struct platform_device *pdev) - static int soc_button_probe(struct platform_device *pdev) - { - struct device *dev = &pdev->dev; -- const struct acpi_device_id *id; -- struct soc_button_info *button_info; -+ const struct soc_device_data *device_data; -+ const struct soc_button_info *button_info; - struct soc_button_data *priv; - struct platform_device *pd; - int i; - int error; - -- id = acpi_match_device(dev->driver->acpi_match_table, dev); -- if (!id) -- return -ENODEV; -+ device_data = acpi_device_get_match_data(dev); -+ if (device_data && device_data->check) { -+ error = device_data->check(dev); -+ if (error) -+ return error; -+ } - -- if (!id->driver_data) { -+ if (device_data && device_data->button_info) { -+ button_info = device_data->button_info; -+ } else { - button_info = soc_button_get_button_info(dev); - if (IS_ERR(button_info)) - return PTR_ERR(button_info); -- } else { -- button_info = (struct soc_button_info *)id->driver_data; - } - - error = gpiod_count(dev, NULL); -@@ -357,7 +377,7 @@ static int soc_button_probe(struct platform_device *pdev) - if (!priv->children[0] && !priv->children[1]) - return -ENODEV; - -- if (!id->driver_data) -+ if (!device_data || !device_data->button_info) - devm_kfree(dev, button_info); - - return 0; -@@ -368,7 +388,7 @@ static int soc_button_probe(struct platform_device *pdev) - * is defined in section 2.8.7.2 of "Windows ACPI Design Guide for SoC - * Platforms" - */ --static struct soc_button_info soc_button_PNP0C40[] = { -+static const struct soc_button_info soc_button_PNP0C40[] = { - { "power", 0, EV_KEY, KEY_POWER, false, true }, - { "home", 1, EV_KEY, KEY_LEFTMETA, false, true }, - { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false }, -@@ -377,9 +397,77 @@ static struct soc_button_info soc_button_PNP0C40[] = { - { } - }; - -+static const struct soc_device_data soc_device_PNP0C40 = { -+ .button_info = soc_button_PNP0C40, -+}; -+ -+/* -+ * Special device check for Surface Book 2 and Surface Pro (2017). -+ * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned -+ * devices use MSHW0040 for power and volume buttons, however the way they -+ * have to be addressed differs. Make sure that we only load this drivers -+ * for the correct devices by checking the OEM Platform Revision provided by -+ * the _DSM method. -+ */ -+#define MSHW0040_DSM_REVISION 0x01 -+#define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision -+static const guid_t MSHW0040_DSM_UUID = -+ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65, -+ 0x49, 0x80, 0x35); -+ -+static int soc_device_check_MSHW0040(struct device *dev) -+{ -+ acpi_handle handle = ACPI_HANDLE(dev); -+ union acpi_object *result; -+ u64 oem_platform_rev = 0; // valid revisions are nonzero -+ -+ // get OEM platform revision -+ result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ MSHW0040_DSM_GET_OMPR, NULL, -+ ACPI_TYPE_INTEGER); -+ -+ if (result) { -+ oem_platform_rev = result->integer.value; -+ ACPI_FREE(result); -+ } -+ -+ /* -+ * If the revision is zero here, the _DSM evaluation has failed. This -+ * indicates that we have a Pro 4 or Book 1 and this driver should not -+ * be used. -+ */ -+ if (oem_platform_rev == 0) -+ return -ENODEV; -+ -+ dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); -+ -+ return 0; -+} -+ -+/* -+ * Button infos for Microsoft Surface Book 2 and Surface Pro (2017). -+ * Obtained from DSDT/testing. -+ */ -+static const struct soc_button_info soc_button_MSHW0040[] = { -+ { "power", 0, EV_KEY, KEY_POWER, false, true }, -+ { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false }, -+ { "volume_down", 4, EV_KEY, KEY_VOLUMEDOWN, true, false }, -+ { } -+}; -+ -+static const struct soc_device_data soc_device_MSHW0040 = { -+ .button_info = soc_button_MSHW0040, -+ .check = soc_device_check_MSHW0040, -+}; -+ - static const struct acpi_device_id soc_button_acpi_match[] = { -- { "PNP0C40", (unsigned long)soc_button_PNP0C40 }, -+ { "PNP0C40", (unsigned long)&soc_device_PNP0C40 }, - { "ACPI0011", 0 }, -+ -+ /* Microsoft Surface Devices (5th and 6th generation) */ -+ { "MSHW0040", (unsigned long)&soc_device_MSHW0040 }, -+ - { } - }; - -diff --git a/drivers/platform/x86/surfacepro3_button.c b/drivers/platform/x86/surfacepro3_button.c -index 47c6d000465a..ec515223f654 100644 ---- a/drivers/platform/x86/surfacepro3_button.c -+++ b/drivers/platform/x86/surfacepro3_button.c -@@ -20,6 +20,12 @@ - #define SURFACE_BUTTON_OBJ_NAME "VGBI" - #define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons" - -+#define MSHW0040_DSM_REVISION 0x01 -+#define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision -+static const guid_t MSHW0040_DSM_UUID = -+ GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65, -+ 0x49, 0x80, 0x35); -+ - #define SURFACE_BUTTON_NOTIFY_TABLET_MODE 0xc8 - - #define SURFACE_BUTTON_NOTIFY_PRESS_POWER 0xc6 -@@ -142,6 +148,44 @@ static int surface_button_resume(struct device *dev) - } - #endif - -+/* -+ * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device -+ * ID (MSHW0040) for the power/volume buttons. Make sure this is the right -+ * device by checking for the _DSM method and OEM Platform Revision. -+ * -+ * Returns true if the driver should bind to this device, i.e. the device is -+ * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. -+ */ -+static bool surface_button_check_MSHW0040(struct acpi_device *dev) -+{ -+ acpi_handle handle = dev->handle; -+ union acpi_object *result; -+ u64 oem_platform_rev = 0; // valid revisions are nonzero -+ -+ // get OEM platform revision -+ result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ MSHW0040_DSM_GET_OMPR, -+ NULL, ACPI_TYPE_INTEGER); -+ -+ /* -+ * If evaluating the _DSM fails, the method is not present. This means -+ * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we -+ * should use this driver. We use revision 0 indicating it is -+ * unavailable. -+ */ -+ -+ if (result) { -+ oem_platform_rev = result->integer.value; -+ ACPI_FREE(result); -+ } -+ -+ dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); -+ -+ return oem_platform_rev == 0; -+} -+ -+ - static int surface_button_add(struct acpi_device *device) - { - struct surface_button *button; -@@ -154,6 +198,9 @@ static int surface_button_add(struct acpi_device *device) - strlen(SURFACE_BUTTON_OBJ_NAME))) - return -ENODEV; - -+ if (!surface_button_check_MSHW0040(device)) -+ return -ENODEV; -+ - button = kzalloc(sizeof(struct surface_button), GFP_KERNEL); - if (!button) - return -ENOMEM; --- -2.24.1 - diff --git a/patches/5.3/0003-hid.patch b/patches/5.3/0003-hid.patch deleted file mode 100644 index 69318d7ab..000000000 --- a/patches/5.3/0003-hid.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 36d79941fc26dde7702139c3df51a6f4dc54b64f Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= -Date: Wed, 6 Nov 2019 19:43:26 +0900 -Subject: [PATCH 03/10] hid - ---- - drivers/hid/hid-core.c | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c -index 12149c5c39e4..dea1f9139b5c 100644 ---- a/drivers/hid/hid-core.c -+++ b/drivers/hid/hid-core.c -@@ -781,6 +781,10 @@ static void hid_scan_feature_usage(struct hid_parser *parser, u32 usage) - if (usage == 0xff0000c5 && parser->global.report_count == 256 && - parser->global.report_size == 8) - parser->scan_flags |= HID_SCAN_FLAG_MT_WIN_8; -+ -+ if (usage == 0xff0000c6 && parser->global.report_count == 1 && -+ parser->global.report_size == 8) -+ parser->scan_flags |= HID_SCAN_FLAG_MT_WIN_8; - } - - static void hid_scan_collection(struct hid_parser *parser, unsigned type) --- -2.24.1 - diff --git a/patches/5.3/0004-surface3-power.patch b/patches/5.3/0004-surface3-power.patch deleted file mode 100644 index b12af0ea7..000000000 --- a/patches/5.3/0004-surface3-power.patch +++ /dev/null @@ -1,655 +0,0 @@ -From 40f02c1a770696c46b38c949f418c561711b6819 Mon Sep 17 00:00:00 2001 -From: qzed -Date: Tue, 17 Sep 2019 17:17:56 +0200 -Subject: [PATCH 04/10] surface3-power - ---- - drivers/platform/x86/Kconfig | 7 + - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/surface3_power.c | 604 ++++++++++++++++++++++++++ - 3 files changed, 612 insertions(+) - create mode 100644 drivers/platform/x86/surface3_power.c - -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 9dea69a1526a..275f6498e162 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -1209,6 +1209,13 @@ config SURFACE_3_BUTTON - ---help--- - This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet. - -+config SURFACE_3_POWER_OPREGION -+ tristate "Surface 3 battery platform operation region support" -+ depends on ACPI && I2C -+ help -+ Select this option to enable support for ACPI operation -+ region of the Surface 3 battery platform driver. -+ - config INTEL_PUNIT_IPC - tristate "Intel P-Unit IPC Driver" - ---help--- -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 18f5a4ba7244..19b56f2181eb 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -85,6 +85,7 @@ obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o - obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o - obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o - obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o -+obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o - obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o - obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o - obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ -diff --git a/drivers/platform/x86/surface3_power.c b/drivers/platform/x86/surface3_power.c -new file mode 100644 -index 000000000000..e0af01a60302 ---- /dev/null -+++ b/drivers/platform/x86/surface3_power.c -@@ -0,0 +1,604 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+ -+/* -+ * Supports for the power IC on the Surface 3 tablet. -+ * -+ * (C) Copyright 2016-2018 Red Hat, Inc -+ * (C) Copyright 2016-2018 Benjamin Tissoires -+ * (C) Copyright 2016 Stephen Just -+ * -+ */ -+ -+/* -+ * This driver has been reverse-engineered by parsing the DSDT of the Surface 3 -+ * and looking at the registers of the chips. -+ * -+ * The DSDT allowed to find out that: -+ * - the driver is required for the ACPI BAT0 device to communicate to the chip -+ * through an operation region. -+ * - the various defines for the operation region functions to communicate with -+ * this driver -+ * - the DSM 3f99e367-6220-4955-8b0f-06ef2ae79412 allows to trigger ACPI -+ * events to BAT0 (the code is all available in the DSDT). -+ * -+ * Further findings regarding the 2 chips declared in the MSHW0011 are: -+ * - there are 2 chips declared: -+ * . 0x22 seems to control the ADP1 line status (and probably the charger) -+ * . 0x55 controls the battery directly -+ * - the battery chip uses a SMBus protocol (using plain SMBus allows non -+ * destructive commands): -+ * . the commands/registers used are in the range 0x00..0x7F -+ * . if bit 8 (0x80) is set in the SMBus command, the returned value is the -+ * same as when it is not set. There is a high chance this bit is the -+ * read/write -+ * . the various registers semantic as been deduced by observing the register -+ * dumps. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#define POLL_INTERVAL (2 * HZ) -+ -+struct mshw0011_data { -+ struct i2c_client *adp1; -+ struct i2c_client *bat0; -+ unsigned short notify_mask; -+ struct task_struct *poll_task; -+ bool kthread_running; -+ -+ bool charging; -+ bool bat_charging; -+ u8 trip_point; -+ s32 full_capacity; -+}; -+ -+struct mshw0011_lookup { -+ struct mshw0011_data *cdata; -+ unsigned int n; -+ unsigned int index; -+ int addr; -+}; -+ -+struct mshw0011_handler_data { -+ struct acpi_connection_info info; -+ struct i2c_client *client; -+}; -+ -+struct bix { -+ u32 revision; -+ u32 power_unit; -+ u32 design_capacity; -+ u32 last_full_charg_capacity; -+ u32 battery_technology; -+ u32 design_voltage; -+ u32 design_capacity_of_warning; -+ u32 design_capacity_of_low; -+ u32 cycle_count; -+ u32 measurement_accuracy; -+ u32 max_sampling_time; -+ u32 min_sampling_time; -+ u32 max_average_interval; -+ u32 min_average_interval; -+ u32 battery_capacity_granularity_1; -+ u32 battery_capacity_granularity_2; -+ char model[10]; -+ char serial[10]; -+ char type[10]; -+ char OEM[10]; -+} __packed; -+ -+struct bst { -+ u32 battery_state; -+ s32 battery_present_rate; -+ u32 battery_remaining_capacity; -+ u32 battery_present_voltage; -+} __packed; -+ -+struct gsb_command { -+ u8 arg0; -+ u8 arg1; -+ u8 arg2; -+} __packed; -+ -+struct gsb_buffer { -+ u8 status; -+ u8 len; -+ u8 ret; -+ union { -+ struct gsb_command cmd; -+ struct bst bst; -+ struct bix bix; -+ } __packed; -+} __packed; -+ -+ -+#define ACPI_BATTERY_STATE_DISCHARGING BIT(0) -+#define ACPI_BATTERY_STATE_CHARGING BIT(1) -+#define ACPI_BATTERY_STATE_CRITICAL BIT(2) -+ -+#define MSHW0011_CMD_DEST_BAT0 0x01 -+#define MSHW0011_CMD_DEST_ADP1 0x03 -+ -+#define MSHW0011_CMD_BAT0_STA 0x01 -+#define MSHW0011_CMD_BAT0_BIX 0x02 -+#define MSHW0011_CMD_BAT0_BCT 0x03 -+#define MSHW0011_CMD_BAT0_BTM 0x04 -+#define MSHW0011_CMD_BAT0_BST 0x05 -+#define MSHW0011_CMD_BAT0_BTP 0x06 -+#define MSHW0011_CMD_ADP1_PSR 0x07 -+#define MSHW0011_CMD_BAT0_PSOC 0x09 -+#define MSHW0011_CMD_BAT0_PMAX 0x0a -+#define MSHW0011_CMD_BAT0_PSRC 0x0b -+#define MSHW0011_CMD_BAT0_CHGI 0x0c -+#define MSHW0011_CMD_BAT0_ARTG 0x0d -+ -+#define MSHW0011_NOTIFY_GET_VERSION 0x00 -+#define MSHW0011_NOTIFY_ADP1 0x01 -+#define MSHW0011_NOTIFY_BAT0_BST 0x02 -+#define MSHW0011_NOTIFY_BAT0_BIX 0x05 -+ -+#define MSHW0011_ADP1_REG_PSR 0x04 -+ -+#define MSHW0011_BAT0_REG_CAPACITY 0x0c -+#define MSHW0011_BAT0_REG_FULL_CHG_CAPACITY 0x0e -+#define MSHW0011_BAT0_REG_DESIGN_CAPACITY 0x40 -+#define MSHW0011_BAT0_REG_VOLTAGE 0x08 -+#define MSHW0011_BAT0_REG_RATE 0x14 -+#define MSHW0011_BAT0_REG_OEM 0x45 -+#define MSHW0011_BAT0_REG_TYPE 0x4e -+#define MSHW0011_BAT0_REG_SERIAL_NO 0x56 -+#define MSHW0011_BAT0_REG_CYCLE_CNT 0x6e -+ -+#define MSHW0011_EV_2_5 0x1ff -+ -+static int -+mshw0011_notify(struct mshw0011_data *cdata, u8 arg1, u8 arg2, -+ unsigned int *ret_value) -+{ -+ static const guid_t mshw0011_guid = -+ GUID_INIT(0x3F99E367, 0x6220, 0x4955, -+ 0x8B, 0x0F, 0x06, 0xEF, 0x2A, 0xE7, 0x94, 0x12); -+ union acpi_object *obj; -+ struct acpi_device *adev; -+ acpi_handle handle; -+ unsigned int i; -+ -+ handle = ACPI_HANDLE(&cdata->adp1->dev); -+ if (!handle || acpi_bus_get_device(handle, &adev)) -+ return -ENODEV; -+ -+ obj = acpi_evaluate_dsm_typed(handle, &mshw0011_guid, arg1, arg2, NULL, -+ ACPI_TYPE_BUFFER); -+ if (!obj) { -+ dev_err(&cdata->adp1->dev, "device _DSM execution failed\n"); -+ return -ENODEV; -+ } -+ -+ *ret_value = 0; -+ for (i = 0; i < obj->buffer.length; i++) -+ *ret_value |= obj->buffer.pointer[i] << (i * 8); -+ -+ ACPI_FREE(obj); -+ return 0; -+} -+ -+static const struct bix default_bix = { -+ .revision = 0x00, -+ .power_unit = 0x01, -+ .design_capacity = 0x1dca, -+ .last_full_charg_capacity = 0x1dca, -+ .battery_technology = 0x01, -+ .design_voltage = 0x10df, -+ .design_capacity_of_warning = 0x8f, -+ .design_capacity_of_low = 0x47, -+ .cycle_count = 0xffffffff, -+ .measurement_accuracy = 0x00015f90, -+ .max_sampling_time = 0x03e8, -+ .min_sampling_time = 0x03e8, -+ .max_average_interval = 0x03e8, -+ .min_average_interval = 0x03e8, -+ .battery_capacity_granularity_1 = 0x45, -+ .battery_capacity_granularity_2 = 0x11, -+ .model = "P11G8M", -+ .serial = "", -+ .type = "LION", -+ .OEM = "", -+}; -+ -+static int mshw0011_bix(struct mshw0011_data *cdata, struct bix *bix) -+{ -+ struct i2c_client *client = cdata->bat0; -+ char buf[10]; -+ int ret; -+ -+ *bix = default_bix; -+ -+ /* get design capacity */ -+ ret = i2c_smbus_read_word_data(client, -+ MSHW0011_BAT0_REG_DESIGN_CAPACITY); -+ if (ret < 0) { -+ dev_err(&client->dev, "Error reading design capacity: %d\n", -+ ret); -+ return ret; -+ } -+ bix->design_capacity = ret; -+ -+ /* get last full charge capacity */ -+ ret = i2c_smbus_read_word_data(client, -+ MSHW0011_BAT0_REG_FULL_CHG_CAPACITY); -+ if (ret < 0) { -+ dev_err(&client->dev, -+ "Error reading last full charge capacity: %d\n", ret); -+ return ret; -+ } -+ bix->last_full_charg_capacity = ret; -+ -+ /* get serial number */ -+ ret = i2c_smbus_read_i2c_block_data(client, MSHW0011_BAT0_REG_SERIAL_NO, -+ 10, buf); -+ if (ret != 10) { -+ dev_err(&client->dev, "Error reading serial no: %d\n", ret); -+ return ret; -+ } -+ snprintf(bix->serial, ARRAY_SIZE(bix->serial), -+ "%*pE%*pE", 3, buf + 7, 6, buf); -+ -+ /* get cycle count */ -+ ret = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_CYCLE_CNT); -+ if (ret < 0) { -+ dev_err(&client->dev, "Error reading cycle count: %d\n", ret); -+ return ret; -+ } -+ bix->cycle_count = ret; -+ -+ /* get OEM name */ -+ ret = i2c_smbus_read_i2c_block_data(client, MSHW0011_BAT0_REG_OEM, -+ 4, buf); -+ if (ret != 4) { -+ dev_err(&client->dev, "Error reading cycle count: %d\n", ret); -+ return ret; -+ } -+ snprintf(bix->OEM, ARRAY_SIZE(bix->OEM), "%*pE", 3, buf); -+ -+ return 0; -+} -+ -+static int mshw0011_bst(struct mshw0011_data *cdata, struct bst *bst) -+{ -+ struct i2c_client *client = cdata->bat0; -+ int rate, capacity, voltage, state; -+ s16 tmp; -+ -+ rate = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_RATE); -+ if (rate < 0) -+ return rate; -+ -+ capacity = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_CAPACITY); -+ if (capacity < 0) -+ return capacity; -+ -+ voltage = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_VOLTAGE); -+ if (voltage < 0) -+ return voltage; -+ -+ tmp = rate; -+ bst->battery_present_rate = abs((s32)tmp); -+ -+ state = 0; -+ if ((s32) tmp > 0) -+ state |= ACPI_BATTERY_STATE_CHARGING; -+ else if ((s32) tmp < 0) -+ state |= ACPI_BATTERY_STATE_DISCHARGING; -+ bst->battery_state = state; -+ -+ bst->battery_remaining_capacity = capacity; -+ bst->battery_present_voltage = voltage; -+ -+ return 0; -+} -+ -+static int mshw0011_adp_psr(struct mshw0011_data *cdata) -+{ -+ struct i2c_client *client = cdata->adp1; -+ int ret; -+ -+ ret = i2c_smbus_read_byte_data(client, MSHW0011_ADP1_REG_PSR); -+ if (ret < 0) -+ return ret; -+ -+ return ret; -+} -+ -+static int mshw0011_isr(struct mshw0011_data *cdata) -+{ -+ struct bst bst; -+ struct bix bix; -+ int ret; -+ bool status, bat_status; -+ -+ ret = mshw0011_adp_psr(cdata); -+ if (ret < 0) -+ return ret; -+ -+ status = ret; -+ -+ if (status != cdata->charging) -+ mshw0011_notify(cdata, cdata->notify_mask, -+ MSHW0011_NOTIFY_ADP1, &ret); -+ -+ cdata->charging = status; -+ -+ ret = mshw0011_bst(cdata, &bst); -+ if (ret < 0) -+ return ret; -+ -+ bat_status = bst.battery_state; -+ -+ if (bat_status != cdata->bat_charging) -+ mshw0011_notify(cdata, cdata->notify_mask, -+ MSHW0011_NOTIFY_BAT0_BST, &ret); -+ -+ cdata->bat_charging = bat_status; -+ -+ ret = mshw0011_bix(cdata, &bix); -+ if (ret < 0) -+ return ret; -+ if (bix.last_full_charg_capacity != cdata->full_capacity) -+ mshw0011_notify(cdata, cdata->notify_mask, -+ MSHW0011_NOTIFY_BAT0_BIX, &ret); -+ -+ cdata->full_capacity = bix.last_full_charg_capacity; -+ -+ return 0; -+} -+ -+static int mshw0011_poll_task(void *data) -+{ -+ struct mshw0011_data *cdata = data; -+ int ret = 0; -+ -+ cdata->kthread_running = true; -+ -+ set_freezable(); -+ -+ while (!kthread_should_stop()) { -+ schedule_timeout_interruptible(POLL_INTERVAL); -+ try_to_freeze(); -+ ret = mshw0011_isr(data); -+ if (ret) -+ break; -+ } -+ -+ cdata->kthread_running = false; -+ return ret; -+} -+ -+static acpi_status -+mshw0011_space_handler(u32 function, acpi_physical_address command, -+ u32 bits, u64 *value64, -+ void *handler_context, void *region_context) -+{ -+ struct gsb_buffer *gsb = (struct gsb_buffer *)value64; -+ struct mshw0011_handler_data *data = handler_context; -+ struct acpi_connection_info *info = &data->info; -+ struct acpi_resource_i2c_serialbus *sb; -+ struct i2c_client *client = data->client; -+ struct mshw0011_data *cdata = i2c_get_clientdata(client); -+ struct acpi_resource *ares; -+ u32 accessor_type = function >> 16; -+ acpi_status ret; -+ int status = 1; -+ -+ ret = acpi_buffer_to_resource(info->connection, info->length, &ares); -+ if (ACPI_FAILURE(ret)) -+ return ret; -+ -+ if (!value64 || ares->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) { -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ sb = &ares->data.i2c_serial_bus; -+ if (sb->type != ACPI_RESOURCE_SERIAL_TYPE_I2C) { -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ if (gsb->cmd.arg0 == MSHW0011_CMD_DEST_ADP1 && -+ gsb->cmd.arg1 == MSHW0011_CMD_ADP1_PSR) { -+ ret = mshw0011_adp_psr(cdata); -+ if (ret >= 0) { -+ status = ret; -+ ret = 0; -+ } -+ goto out; -+ } -+ -+ if (gsb->cmd.arg0 != MSHW0011_CMD_DEST_BAT0) { -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ switch (gsb->cmd.arg1) { -+ case MSHW0011_CMD_BAT0_STA: -+ break; -+ case MSHW0011_CMD_BAT0_BIX: -+ ret = mshw0011_bix(cdata, &gsb->bix); -+ break; -+ case MSHW0011_CMD_BAT0_BTP: -+ cdata->trip_point = gsb->cmd.arg2; -+ break; -+ case MSHW0011_CMD_BAT0_BST: -+ ret = mshw0011_bst(cdata, &gsb->bst); -+ break; -+ default: -+ pr_info("command(0x%02x) is not supported.\n", gsb->cmd.arg1); -+ ret = AE_BAD_PARAMETER; -+ goto err; -+ } -+ -+ out: -+ gsb->ret = status; -+ gsb->status = 0; -+ -+ err: -+ ACPI_FREE(ares); -+ return ret; -+} -+ -+static int mshw0011_install_space_handler(struct i2c_client *client) -+{ -+ acpi_handle handle; -+ struct mshw0011_handler_data *data; -+ acpi_status status; -+ -+ handle = ACPI_HANDLE(&client->dev); -+ -+ if (!handle) -+ return -ENODEV; -+ -+ data = kzalloc(sizeof(struct mshw0011_handler_data), -+ GFP_KERNEL); -+ if (!data) -+ return -ENOMEM; -+ -+ data->client = client; -+ status = acpi_bus_attach_private_data(handle, (void *)data); -+ if (ACPI_FAILURE(status)) { -+ kfree(data); -+ return -ENOMEM; -+ } -+ -+ status = acpi_install_address_space_handler(handle, -+ ACPI_ADR_SPACE_GSBUS, -+ &mshw0011_space_handler, -+ NULL, -+ data); -+ if (ACPI_FAILURE(status)) { -+ dev_err(&client->dev, "Error installing i2c space handler\n"); -+ acpi_bus_detach_private_data(handle); -+ kfree(data); -+ return -ENOMEM; -+ } -+ -+ acpi_walk_dep_device_list(handle); -+ return 0; -+} -+ -+static void mshw0011_remove_space_handler(struct i2c_client *client) -+{ -+ acpi_handle handle = ACPI_HANDLE(&client->dev); -+ struct mshw0011_handler_data *data; -+ acpi_status status; -+ -+ if (!handle) -+ return; -+ -+ acpi_remove_address_space_handler(handle, -+ ACPI_ADR_SPACE_GSBUS, -+ &mshw0011_space_handler); -+ -+ status = acpi_bus_get_private_data(handle, (void **)&data); -+ if (ACPI_SUCCESS(status)) -+ kfree(data); -+ -+ acpi_bus_detach_private_data(handle); -+} -+ -+static int mshw0011_probe(struct i2c_client *client) -+{ -+ struct i2c_board_info board_info; -+ struct device *dev = &client->dev; -+ struct i2c_client *bat0; -+ -+ struct mshw0011_data *data; -+ int error, mask; -+ -+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); -+ if (!data) -+ return -ENOMEM; -+ -+ data->adp1 = client; -+ i2c_set_clientdata(client, data); -+ -+ memset(&board_info, 0, sizeof(board_info)); -+ strlcpy(board_info.type, "MSHW0011-bat0", I2C_NAME_SIZE); -+ -+ bat0 = i2c_acpi_new_device(dev, 1, &board_info); -+ if (!bat0) -+ return -ENOMEM; -+ -+ data->bat0 = bat0; -+ i2c_set_clientdata(bat0, data); -+ -+ error = mshw0011_notify(data, 1, MSHW0011_NOTIFY_GET_VERSION, &mask); -+ if (error) -+ goto out_err; -+ -+ data->notify_mask = mask == MSHW0011_EV_2_5; -+ -+ data->poll_task = kthread_run(mshw0011_poll_task, data, "mshw0011_adp"); -+ if (IS_ERR(data->poll_task)) { -+ error = PTR_ERR(data->poll_task); -+ dev_err(&client->dev, "Unable to run kthread err %d\n", error); -+ goto out_err; -+ } -+ -+ error = mshw0011_install_space_handler(client); -+ if (error) -+ goto out_err; -+ -+ return 0; -+ -+out_err: -+ if (data->kthread_running) -+ kthread_stop(data->poll_task); -+ i2c_unregister_device(data->bat0); -+ return error; -+} -+ -+static int mshw0011_remove(struct i2c_client *client) -+{ -+ struct mshw0011_data *cdata = i2c_get_clientdata(client); -+ -+ mshw0011_remove_space_handler(client); -+ -+ if (cdata->kthread_running) -+ kthread_stop(cdata->poll_task); -+ -+ i2c_unregister_device(cdata->bat0); -+ -+ return 0; -+} -+ -+static const struct acpi_device_id mshw0011_acpi_match[] = { -+ { "MSHW0011", 0 }, -+ { } -+}; -+MODULE_DEVICE_TABLE(acpi, mshw0011_acpi_match); -+ -+static struct i2c_driver mshw0011_driver = { -+ .probe_new = mshw0011_probe, -+ .remove = mshw0011_remove, -+ .driver = { -+ .name = "mshw0011", -+ .acpi_match_table = ACPI_PTR(mshw0011_acpi_match), -+ }, -+}; -+module_i2c_driver(mshw0011_driver); -+ -+MODULE_AUTHOR("Benjamin Tissoires "); -+MODULE_DESCRIPTION("mshw0011 driver"); -+MODULE_LICENSE("GPL v2"); --- -2.24.1 - diff --git a/patches/5.3/0006-wifi.patch b/patches/5.3/0006-wifi.patch deleted file mode 100644 index da2908722..000000000 --- a/patches/5.3/0006-wifi.patch +++ /dev/null @@ -1,171 +0,0 @@ -From 1d1f574d38351171cb963be29c2eaee4b5317895 Mon Sep 17 00:00:00 2001 -From: sebanc <22224731+sebanc@users.noreply.github.com> -Date: Mon, 4 Nov 2019 09:30:57 +0100 -Subject: [PATCH 06/10] wifi - ---- - drivers/net/wireless/marvell/mwifiex/pcie.c | 75 ++++++++++--------- - .../net/wireless/marvell/mwifiex/sta_cmd.c | 15 +--- - 2 files changed, 40 insertions(+), 50 deletions(-) - -diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c -index b54f73e3d508..f0925b3d5aaf 100644 ---- a/drivers/net/wireless/marvell/mwifiex/pcie.c -+++ b/drivers/net/wireless/marvell/mwifiex/pcie.c -@@ -149,37 +149,39 @@ static bool mwifiex_pcie_ok_to_access_hw(struct mwifiex_adapter *adapter) - */ - static int mwifiex_pcie_suspend(struct device *dev) - { -- struct mwifiex_adapter *adapter; -- struct pcie_service_card *card; - struct pci_dev *pdev = to_pci_dev(dev); -+ struct pcie_service_card *card = pci_get_drvdata(pdev); -+ struct mwifiex_adapter *adapter; -+ struct mwifiex_private *priv; -+ const struct mwifiex_pcie_card_reg *reg; -+ u32 fw_status; -+ int ret; - -- card = pci_get_drvdata(pdev); - - /* Might still be loading firmware */ - wait_for_completion(&card->fw_done); - - adapter = card->adapter; -- if (!adapter) { -- dev_err(dev, "adapter is not valid\n"); -+ if (!adapter || !adapter->priv_num) - return 0; -- } - -- mwifiex_enable_wake(adapter); -+ reg = card->pcie.reg; -+ if (reg) -+ ret = mwifiex_read_reg(adapter, reg->fw_status, &fw_status); -+ else -+ fw_status = -1; -+ -+ if (fw_status == FIRMWARE_READY_PCIE && !adapter->mfg_mode) { -+ mwifiex_deauthenticate_all(adapter); - -- /* Enable the Host Sleep */ -- if (!mwifiex_enable_hs(adapter)) { -- mwifiex_dbg(adapter, ERROR, -- "cmd: failed to suspend\n"); -- clear_bit(MWIFIEX_IS_HS_ENABLING, &adapter->work_flags); -- mwifiex_disable_wake(adapter); -- return -EFAULT; -- } -+ priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY); - -- flush_workqueue(adapter->workqueue); -+ mwifiex_disable_auto_ds(priv); -+ -+ mwifiex_init_shutdown_fw(priv, MWIFIEX_FUNC_SHUTDOWN); -+ } - -- /* Indicate device suspended */ -- set_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags); -- clear_bit(MWIFIEX_IS_HS_ENABLING, &adapter->work_flags); -+ mwifiex_remove_card(adapter); - - return 0; - } -@@ -194,30 +196,29 @@ static int mwifiex_pcie_suspend(struct device *dev) - */ - static int mwifiex_pcie_resume(struct device *dev) - { -- struct mwifiex_adapter *adapter; -- struct pcie_service_card *card; - struct pci_dev *pdev = to_pci_dev(dev); -+ struct pcie_service_card *card = pci_get_drvdata(pdev); -+ int ret; - -- card = pci_get_drvdata(pdev); -+ pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", -+ pdev->vendor, pdev->device, pdev->revision); - -- if (!card->adapter) { -- dev_err(dev, "adapter structure is not valid\n"); -- return 0; -- } -+ init_completion(&card->fw_done); - -- adapter = card->adapter; -+ card->dev = pdev; - -- if (!test_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags)) { -- mwifiex_dbg(adapter, WARN, -- "Device already resumed\n"); -- return 0; -+ /* device tree node parsing and platform specific configuration */ -+ if (pdev->dev.of_node) { -+ ret = mwifiex_pcie_probe_of(&pdev->dev); -+ if (ret) -+ return ret; - } - -- clear_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags); -- -- mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA), -- MWIFIEX_ASYNC_CMD); -- mwifiex_disable_wake(adapter); -+ if (mwifiex_add_card(card, &card->fw_done, &pcie_ops, -+ MWIFIEX_PCIE, &pdev->dev)) { -+ pr_err("%s failed\n", __func__); -+ return -1; -+ } - - return 0; - } -@@ -271,6 +272,8 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, - return -1; - } - -+ pdev->bus->self->bridge_d3 = false; -+ - return 0; - } - -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -index 4ed10cf82f9a..013db4386c39 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c -@@ -2265,14 +2265,13 @@ int mwifiex_sta_prepare_cmd(struct mwifiex_private *priv, uint16_t cmd_no, - int mwifiex_sta_init_cmd(struct mwifiex_private *priv, u8 first_sta, bool init) - { - struct mwifiex_adapter *adapter = priv->adapter; -- int ret; - struct mwifiex_ds_11n_amsdu_aggr_ctrl amsdu_aggr_ctrl; -- struct mwifiex_ds_auto_ds auto_ds; - enum state_11d_t state_11d; - struct mwifiex_ds_11n_tx_cfg tx_cfg; - u8 sdio_sp_rx_aggr_enable; - u16 packet_aggr_enable; - int data; -+ int ret; - - if (first_sta) { - if (priv->adapter->iface_type == MWIFIEX_PCIE) { -@@ -2395,18 +2394,6 @@ int mwifiex_sta_init_cmd(struct mwifiex_private *priv, u8 first_sta, bool init) - if (ret) - return -1; - -- if (!disable_auto_ds && first_sta && -- priv->bss_type != MWIFIEX_BSS_TYPE_UAP) { -- /* Enable auto deep sleep */ -- auto_ds.auto_ds = DEEP_SLEEP_ON; -- auto_ds.idle_time = DEEP_SLEEP_IDLE_TIME; -- ret = mwifiex_send_cmd(priv, HostCmd_CMD_802_11_PS_MODE_ENH, -- EN_AUTO_PS, BITMAP_AUTO_DS, -- &auto_ds, true); -- if (ret) -- return -1; -- } -- - if (priv->bss_type != MWIFIEX_BSS_TYPE_UAP) { - /* Send cmd to FW to enable/disable 11D function */ - state_11d = ENABLE_11D; --- -2.24.1 - diff --git a/patches/5.3/0007-legacy-i915.patch b/patches/5.3/0007-legacy-i915.patch deleted file mode 100644 index 07e2ed22e..000000000 --- a/patches/5.3/0007-legacy-i915.patch +++ /dev/null @@ -1,247070 +0,0 @@ -From 42e6a02bc692206b0e6800278e781fc03d988b89 Mon Sep 17 00:00:00 2001 -From: Dorian Stoll -Date: Mon, 16 Sep 2019 04:10:51 +0200 -Subject: [PATCH 07/10] legacy-i915 - ---- - drivers/gpu/drm/Kconfig | 2 +- - drivers/gpu/drm/Makefile | 2 +- - drivers/gpu/drm/i915_legacy/.gitignore | 1 + - drivers/gpu/drm/i915_legacy/Kconfig | 135 + - drivers/gpu/drm/i915_legacy/Kconfig.debug | 183 + - drivers/gpu/drm/i915_legacy/Makefile | 212 + - .../gpu/drm/i915_legacy/Makefile.header-test | 47 + - drivers/gpu/drm/i915_legacy/dvo.h | 138 + - drivers/gpu/drm/i915_legacy/dvo_ch7017.c | 414 + - drivers/gpu/drm/i915_legacy/dvo_ch7xxx.c | 366 + - drivers/gpu/drm/i915_legacy/dvo_ivch.c | 502 + - drivers/gpu/drm/i915_legacy/dvo_ns2501.c | 709 + - drivers/gpu/drm/i915_legacy/dvo_sil164.c | 279 + - drivers/gpu/drm/i915_legacy/dvo_tfp410.c | 318 + - drivers/gpu/drm/i915_legacy/gvt/Makefile | 9 + - drivers/gpu/drm/i915_legacy/gvt/aperture_gm.c | 359 + - drivers/gpu/drm/i915_legacy/gvt/cfg_space.c | 424 + - drivers/gpu/drm/i915_legacy/gvt/cmd_parser.c | 2998 +++ - drivers/gpu/drm/i915_legacy/gvt/cmd_parser.h | 49 + - drivers/gpu/drm/i915_legacy/gvt/debug.h | 65 + - drivers/gpu/drm/i915_legacy/gvt/debugfs.c | 268 + - drivers/gpu/drm/i915_legacy/gvt/display.c | 531 + - drivers/gpu/drm/i915_legacy/gvt/display.h | 209 + - drivers/gpu/drm/i915_legacy/gvt/dmabuf.c | 561 + - drivers/gpu/drm/i915_legacy/gvt/dmabuf.h | 67 + - drivers/gpu/drm/i915_legacy/gvt/edid.c | 576 + - drivers/gpu/drm/i915_legacy/gvt/edid.h | 150 + - drivers/gpu/drm/i915_legacy/gvt/execlist.c | 567 + - drivers/gpu/drm/i915_legacy/gvt/execlist.h | 185 + - drivers/gpu/drm/i915_legacy/gvt/fb_decoder.c | 507 + - drivers/gpu/drm/i915_legacy/gvt/fb_decoder.h | 169 + - drivers/gpu/drm/i915_legacy/gvt/firmware.c | 276 + - drivers/gpu/drm/i915_legacy/gvt/gtt.c | 2818 +++ - drivers/gpu/drm/i915_legacy/gvt/gtt.h | 280 + - drivers/gpu/drm/i915_legacy/gvt/gvt.c | 453 + - drivers/gpu/drm/i915_legacy/gvt/gvt.h | 694 + - drivers/gpu/drm/i915_legacy/gvt/handlers.c | 3588 ++++ - drivers/gpu/drm/i915_legacy/gvt/hypercall.h | 78 + - drivers/gpu/drm/i915_legacy/gvt/interrupt.c | 710 + - drivers/gpu/drm/i915_legacy/gvt/interrupt.h | 233 + - drivers/gpu/drm/i915_legacy/gvt/kvmgt.c | 2075 ++ - drivers/gpu/drm/i915_legacy/gvt/mmio.c | 315 + - drivers/gpu/drm/i915_legacy/gvt/mmio.h | 105 + - .../gpu/drm/i915_legacy/gvt/mmio_context.c | 580 + - .../gpu/drm/i915_legacy/gvt/mmio_context.h | 60 + - drivers/gpu/drm/i915_legacy/gvt/mpt.h | 383 + - drivers/gpu/drm/i915_legacy/gvt/opregion.c | 570 + - drivers/gpu/drm/i915_legacy/gvt/page_track.c | 185 + - drivers/gpu/drm/i915_legacy/gvt/page_track.h | 56 + - drivers/gpu/drm/i915_legacy/gvt/reg.h | 131 + - .../gpu/drm/i915_legacy/gvt/sched_policy.c | 479 + - .../gpu/drm/i915_legacy/gvt/sched_policy.h | 62 + - drivers/gpu/drm/i915_legacy/gvt/scheduler.c | 1550 ++ - drivers/gpu/drm/i915_legacy/gvt/scheduler.h | 166 + - drivers/gpu/drm/i915_legacy/gvt/trace.h | 383 + - .../gpu/drm/i915_legacy/gvt/trace_points.c | 36 + - drivers/gpu/drm/i915_legacy/gvt/vgpu.c | 592 + - drivers/gpu/drm/i915_legacy/i915_active.c | 313 + - drivers/gpu/drm/i915_legacy/i915_active.h | 409 + - .../gpu/drm/i915_legacy/i915_active_types.h | 36 + - drivers/gpu/drm/i915_legacy/i915_cmd_parser.c | 1387 ++ - drivers/gpu/drm/i915_legacy/i915_debugfs.c | 4926 +++++ - drivers/gpu/drm/i915_legacy/i915_drv.c | 3195 +++ - drivers/gpu/drm/i915_legacy/i915_drv.h | 3693 ++++ - drivers/gpu/drm/i915_legacy/i915_fixed.h | 143 + - drivers/gpu/drm/i915_legacy/i915_gem.c | 5545 +++++ - drivers/gpu/drm/i915_legacy/i915_gem.h | 97 + - .../gpu/drm/i915_legacy/i915_gem_batch_pool.c | 140 + - .../gpu/drm/i915_legacy/i915_gem_batch_pool.h | 25 + - .../gpu/drm/i915_legacy/i915_gem_clflush.c | 178 + - .../gpu/drm/i915_legacy/i915_gem_clflush.h | 36 + - .../gpu/drm/i915_legacy/i915_gem_context.c | 1829 ++ - .../gpu/drm/i915_legacy/i915_gem_context.h | 185 + - .../drm/i915_legacy/i915_gem_context_types.h | 175 + - drivers/gpu/drm/i915_legacy/i915_gem_dmabuf.c | 337 + - drivers/gpu/drm/i915_legacy/i915_gem_evict.c | 444 + - .../gpu/drm/i915_legacy/i915_gem_execbuffer.c | 2722 +++ - .../gpu/drm/i915_legacy/i915_gem_fence_reg.c | 785 + - .../gpu/drm/i915_legacy/i915_gem_fence_reg.h | 52 + - drivers/gpu/drm/i915_legacy/i915_gem_gtt.c | 3922 ++++ - drivers/gpu/drm/i915_legacy/i915_gem_gtt.h | 664 + - .../gpu/drm/i915_legacy/i915_gem_internal.c | 210 + - drivers/gpu/drm/i915_legacy/i915_gem_object.c | 90 + - drivers/gpu/drm/i915_legacy/i915_gem_object.h | 509 + - .../drm/i915_legacy/i915_gem_render_state.c | 233 + - .../drm/i915_legacy/i915_gem_render_state.h | 31 + - .../gpu/drm/i915_legacy/i915_gem_shrinker.c | 556 + - drivers/gpu/drm/i915_legacy/i915_gem_stolen.c | 721 + - drivers/gpu/drm/i915_legacy/i915_gem_tiling.c | 457 + - .../gpu/drm/i915_legacy/i915_gem_userptr.c | 847 + - drivers/gpu/drm/i915_legacy/i915_gemfs.c | 75 + - drivers/gpu/drm/i915_legacy/i915_gemfs.h | 34 + - drivers/gpu/drm/i915_legacy/i915_globals.c | 125 + - drivers/gpu/drm/i915_legacy/i915_globals.h | 35 + - drivers/gpu/drm/i915_legacy/i915_gpu_error.c | 1876 ++ - drivers/gpu/drm/i915_legacy/i915_gpu_error.h | 315 + - drivers/gpu/drm/i915_legacy/i915_ioc32.c | 96 + - drivers/gpu/drm/i915_legacy/i915_irq.c | 4925 +++++ - drivers/gpu/drm/i915_legacy/i915_memcpy.c | 106 + - drivers/gpu/drm/i915_legacy/i915_mm.c | 83 + - drivers/gpu/drm/i915_legacy/i915_oa_bdw.c | 91 + - drivers/gpu/drm/i915_legacy/i915_oa_bdw.h | 15 + - drivers/gpu/drm/i915_legacy/i915_oa_bxt.c | 89 + - drivers/gpu/drm/i915_legacy/i915_oa_bxt.h | 15 + - drivers/gpu/drm/i915_legacy/i915_oa_cflgt2.c | 90 + - drivers/gpu/drm/i915_legacy/i915_oa_cflgt2.h | 15 + - drivers/gpu/drm/i915_legacy/i915_oa_cflgt3.c | 90 + - drivers/gpu/drm/i915_legacy/i915_oa_cflgt3.h | 15 + - drivers/gpu/drm/i915_legacy/i915_oa_chv.c | 90 + - drivers/gpu/drm/i915_legacy/i915_oa_chv.h | 15 + - drivers/gpu/drm/i915_legacy/i915_oa_cnl.c | 102 + - drivers/gpu/drm/i915_legacy/i915_oa_cnl.h | 15 + - drivers/gpu/drm/i915_legacy/i915_oa_glk.c | 89 + - drivers/gpu/drm/i915_legacy/i915_oa_glk.h | 15 + - drivers/gpu/drm/i915_legacy/i915_oa_hsw.c | 119 + - drivers/gpu/drm/i915_legacy/i915_oa_hsw.h | 15 + - drivers/gpu/drm/i915_legacy/i915_oa_icl.c | 99 + - drivers/gpu/drm/i915_legacy/i915_oa_icl.h | 15 + - drivers/gpu/drm/i915_legacy/i915_oa_kblgt2.c | 90 + - drivers/gpu/drm/i915_legacy/i915_oa_kblgt2.h | 15 + - drivers/gpu/drm/i915_legacy/i915_oa_kblgt3.c | 90 + - drivers/gpu/drm/i915_legacy/i915_oa_kblgt3.h | 15 + - drivers/gpu/drm/i915_legacy/i915_oa_sklgt2.c | 89 + - drivers/gpu/drm/i915_legacy/i915_oa_sklgt2.h | 15 + - drivers/gpu/drm/i915_legacy/i915_oa_sklgt3.c | 90 + - drivers/gpu/drm/i915_legacy/i915_oa_sklgt3.h | 15 + - drivers/gpu/drm/i915_legacy/i915_oa_sklgt4.c | 90 + - drivers/gpu/drm/i915_legacy/i915_oa_sklgt4.h | 15 + - drivers/gpu/drm/i915_legacy/i915_params.c | 237 + - drivers/gpu/drm/i915_legacy/i915_params.h | 94 + - drivers/gpu/drm/i915_legacy/i915_pci.c | 957 + - drivers/gpu/drm/i915_legacy/i915_perf.c | 3519 ++++ - drivers/gpu/drm/i915_legacy/i915_pmu.c | 1096 + - drivers/gpu/drm/i915_legacy/i915_pmu.h | 125 + - .../gpu/drm/i915_legacy/i915_priolist_types.h | 41 + - drivers/gpu/drm/i915_legacy/i915_pvinfo.h | 120 + - drivers/gpu/drm/i915_legacy/i915_query.c | 144 + - drivers/gpu/drm/i915_legacy/i915_query.h | 15 + - drivers/gpu/drm/i915_legacy/i915_reg.h | 11424 +++++++++++ - drivers/gpu/drm/i915_legacy/i915_request.c | 1511 ++ - drivers/gpu/drm/i915_legacy/i915_request.h | 423 + - drivers/gpu/drm/i915_legacy/i915_reset.c | 1474 ++ - drivers/gpu/drm/i915_legacy/i915_reset.h | 69 + - drivers/gpu/drm/i915_legacy/i915_scheduler.c | 492 + - drivers/gpu/drm/i915_legacy/i915_scheduler.h | 55 + - .../drm/i915_legacy/i915_scheduler_types.h | 73 + - drivers/gpu/drm/i915_legacy/i915_selftest.h | 105 + - drivers/gpu/drm/i915_legacy/i915_suspend.c | 150 + - drivers/gpu/drm/i915_legacy/i915_sw_fence.c | 576 + - drivers/gpu/drm/i915_legacy/i915_sw_fence.h | 109 + - drivers/gpu/drm/i915_legacy/i915_syncmap.c | 412 + - drivers/gpu/drm/i915_legacy/i915_syncmap.h | 38 + - drivers/gpu/drm/i915_legacy/i915_sysfs.c | 644 + - drivers/gpu/drm/i915_legacy/i915_timeline.c | 579 + - drivers/gpu/drm/i915_legacy/i915_timeline.h | 113 + - .../gpu/drm/i915_legacy/i915_timeline_types.h | 70 + - drivers/gpu/drm/i915_legacy/i915_trace.h | 1000 + - .../gpu/drm/i915_legacy/i915_trace_points.c | 14 + - .../drm/i915_legacy/i915_user_extensions.c | 61 + - .../drm/i915_legacy/i915_user_extensions.h | 20 + - drivers/gpu/drm/i915_legacy/i915_utils.h | 192 + - drivers/gpu/drm/i915_legacy/i915_vgpu.c | 279 + - drivers/gpu/drm/i915_legacy/i915_vgpu.h | 48 + - drivers/gpu/drm/i915_legacy/i915_vma.c | 1079 + - drivers/gpu/drm/i915_legacy/i915_vma.h | 446 + - drivers/gpu/drm/i915_legacy/icl_dsi.c | 1464 ++ - drivers/gpu/drm/i915_legacy/intel_acpi.c | 155 + - drivers/gpu/drm/i915_legacy/intel_atomic.c | 428 + - .../gpu/drm/i915_legacy/intel_atomic_plane.c | 373 + - .../gpu/drm/i915_legacy/intel_atomic_plane.h | 40 + - drivers/gpu/drm/i915_legacy/intel_audio.c | 1105 + - drivers/gpu/drm/i915_legacy/intel_audio.h | 24 + - drivers/gpu/drm/i915_legacy/intel_bios.c | 2298 +++ - drivers/gpu/drm/i915_legacy/intel_bios.h | 223 + - .../gpu/drm/i915_legacy/intel_breadcrumbs.c | 373 + - drivers/gpu/drm/i915_legacy/intel_cdclk.c | 2904 +++ - drivers/gpu/drm/i915_legacy/intel_cdclk.h | 46 + - drivers/gpu/drm/i915_legacy/intel_color.c | 1278 ++ - drivers/gpu/drm/i915_legacy/intel_color.h | 17 + - drivers/gpu/drm/i915_legacy/intel_combo_phy.c | 255 + - drivers/gpu/drm/i915_legacy/intel_connector.c | 282 + - drivers/gpu/drm/i915_legacy/intel_connector.h | 35 + - drivers/gpu/drm/i915_legacy/intel_context.c | 269 + - drivers/gpu/drm/i915_legacy/intel_context.h | 87 + - .../gpu/drm/i915_legacy/intel_context_types.h | 75 + - drivers/gpu/drm/i915_legacy/intel_crt.c | 1061 + - drivers/gpu/drm/i915_legacy/intel_crt.h | 21 + - drivers/gpu/drm/i915_legacy/intel_csr.c | 615 + - drivers/gpu/drm/i915_legacy/intel_csr.h | 17 + - drivers/gpu/drm/i915_legacy/intel_ddi.c | 4286 ++++ - drivers/gpu/drm/i915_legacy/intel_ddi.h | 53 + - .../gpu/drm/i915_legacy/intel_device_info.c | 1019 + - .../gpu/drm/i915_legacy/intel_device_info.h | 307 + - drivers/gpu/drm/i915_legacy/intel_display.c | 16814 ++++++++++++++++ - drivers/gpu/drm/i915_legacy/intel_display.h | 435 + - drivers/gpu/drm/i915_legacy/intel_dp.c | 7405 +++++++ - drivers/gpu/drm/i915_legacy/intel_dp.h | 122 + - .../drm/i915_legacy/intel_dp_aux_backlight.c | 280 + - .../drm/i915_legacy/intel_dp_link_training.c | 381 + - drivers/gpu/drm/i915_legacy/intel_dp_mst.c | 678 + - drivers/gpu/drm/i915_legacy/intel_dpio_phy.c | 1082 + - drivers/gpu/drm/i915_legacy/intel_dpll_mgr.c | 3382 ++++ - drivers/gpu/drm/i915_legacy/intel_dpll_mgr.h | 347 + - drivers/gpu/drm/i915_legacy/intel_drv.h | 2045 ++ - drivers/gpu/drm/i915_legacy/intel_dsi.c | 128 + - drivers/gpu/drm/i915_legacy/intel_dsi.h | 196 + - .../drm/i915_legacy/intel_dsi_dcs_backlight.c | 177 + - drivers/gpu/drm/i915_legacy/intel_dsi_vbt.c | 941 + - drivers/gpu/drm/i915_legacy/intel_dvo.c | 549 + - drivers/gpu/drm/i915_legacy/intel_dvo.h | 13 + - drivers/gpu/drm/i915_legacy/intel_engine_cs.c | 1758 ++ - .../gpu/drm/i915_legacy/intel_engine_types.h | 548 + - drivers/gpu/drm/i915_legacy/intel_fbc.c | 1341 ++ - drivers/gpu/drm/i915_legacy/intel_fbc.h | 42 + - drivers/gpu/drm/i915_legacy/intel_fbdev.c | 640 + - drivers/gpu/drm/i915_legacy/intel_fbdev.h | 53 + - .../gpu/drm/i915_legacy/intel_fifo_underrun.c | 457 + - .../gpu/drm/i915_legacy/intel_frontbuffer.c | 204 + - .../gpu/drm/i915_legacy/intel_frontbuffer.h | 98 + - .../gpu/drm/i915_legacy/intel_gpu_commands.h | 278 + - drivers/gpu/drm/i915_legacy/intel_guc.c | 723 + - drivers/gpu/drm/i915_legacy/intel_guc.h | 200 + - drivers/gpu/drm/i915_legacy/intel_guc_ads.c | 151 + - drivers/gpu/drm/i915_legacy/intel_guc_ads.h | 33 + - drivers/gpu/drm/i915_legacy/intel_guc_ct.c | 943 + - drivers/gpu/drm/i915_legacy/intel_guc_ct.h | 99 + - drivers/gpu/drm/i915_legacy/intel_guc_fw.c | 277 + - drivers/gpu/drm/i915_legacy/intel_guc_fw.h | 33 + - drivers/gpu/drm/i915_legacy/intel_guc_fwif.h | 705 + - drivers/gpu/drm/i915_legacy/intel_guc_log.c | 641 + - drivers/gpu/drm/i915_legacy/intel_guc_log.h | 100 + - drivers/gpu/drm/i915_legacy/intel_guc_reg.h | 130 + - .../drm/i915_legacy/intel_guc_submission.c | 1454 ++ - .../drm/i915_legacy/intel_guc_submission.h | 88 + - drivers/gpu/drm/i915_legacy/intel_gvt.c | 137 + - drivers/gpu/drm/i915_legacy/intel_gvt.h | 50 + - drivers/gpu/drm/i915_legacy/intel_hangcheck.c | 334 + - drivers/gpu/drm/i915_legacy/intel_hdcp.c | 1947 ++ - drivers/gpu/drm/i915_legacy/intel_hdcp.h | 33 + - drivers/gpu/drm/i915_legacy/intel_hdmi.c | 3111 +++ - drivers/gpu/drm/i915_legacy/intel_hdmi.h | 51 + - drivers/gpu/drm/i915_legacy/intel_hotplug.c | 686 + - drivers/gpu/drm/i915_legacy/intel_huc.c | 128 + - drivers/gpu/drm/i915_legacy/intel_huc.h | 54 + - drivers/gpu/drm/i915_legacy/intel_huc_fw.c | 168 + - drivers/gpu/drm/i915_legacy/intel_huc_fw.h | 15 + - drivers/gpu/drm/i915_legacy/intel_i2c.c | 933 + - drivers/gpu/drm/i915_legacy/intel_lpe_audio.c | 361 + - drivers/gpu/drm/i915_legacy/intel_lrc.c | 3041 +++ - drivers/gpu/drm/i915_legacy/intel_lrc.h | 120 + - drivers/gpu/drm/i915_legacy/intel_lrc_reg.h | 68 + - drivers/gpu/drm/i915_legacy/intel_lspcon.c | 588 + - drivers/gpu/drm/i915_legacy/intel_lspcon.h | 38 + - drivers/gpu/drm/i915_legacy/intel_lvds.c | 1006 + - drivers/gpu/drm/i915_legacy/intel_lvds.h | 22 + - drivers/gpu/drm/i915_legacy/intel_mocs.c | 564 + - drivers/gpu/drm/i915_legacy/intel_mocs.h | 58 + - drivers/gpu/drm/i915_legacy/intel_opregion.c | 1175 ++ - drivers/gpu/drm/i915_legacy/intel_opregion.h | 122 + - drivers/gpu/drm/i915_legacy/intel_overlay.c | 1495 ++ - drivers/gpu/drm/i915_legacy/intel_panel.c | 2049 ++ - drivers/gpu/drm/i915_legacy/intel_panel.h | 65 + - drivers/gpu/drm/i915_legacy/intel_pipe_crc.c | 679 + - drivers/gpu/drm/i915_legacy/intel_pipe_crc.h | 35 + - drivers/gpu/drm/i915_legacy/intel_pm.c | 10123 ++++++++++ - drivers/gpu/drm/i915_legacy/intel_pm.h | 71 + - drivers/gpu/drm/i915_legacy/intel_psr.c | 1312 ++ - drivers/gpu/drm/i915_legacy/intel_psr.h | 40 + - drivers/gpu/drm/i915_legacy/intel_quirks.c | 169 + - .../gpu/drm/i915_legacy/intel_renderstate.h | 47 + - .../drm/i915_legacy/intel_renderstate_gen6.c | 315 + - .../drm/i915_legacy/intel_renderstate_gen7.c | 279 + - .../drm/i915_legacy/intel_renderstate_gen8.c | 983 + - .../drm/i915_legacy/intel_renderstate_gen9.c | 999 + - .../gpu/drm/i915_legacy/intel_ringbuffer.c | 2345 +++ - .../gpu/drm/i915_legacy/intel_ringbuffer.h | 583 + - .../gpu/drm/i915_legacy/intel_runtime_pm.c | 4520 +++++ - drivers/gpu/drm/i915_legacy/intel_sdvo.c | 3325 +++ - drivers/gpu/drm/i915_legacy/intel_sdvo.h | 23 + - drivers/gpu/drm/i915_legacy/intel_sdvo_regs.h | 733 + - drivers/gpu/drm/i915_legacy/intel_sideband.c | 295 + - drivers/gpu/drm/i915_legacy/intel_sprite.c | 2462 +++ - drivers/gpu/drm/i915_legacy/intel_sprite.h | 55 + - drivers/gpu/drm/i915_legacy/intel_tv.c | 1992 ++ - drivers/gpu/drm/i915_legacy/intel_tv.h | 13 + - drivers/gpu/drm/i915_legacy/intel_uc.c | 511 + - drivers/gpu/drm/i915_legacy/intel_uc.h | 63 + - drivers/gpu/drm/i915_legacy/intel_uc_fw.c | 317 + - drivers/gpu/drm/i915_legacy/intel_uc_fw.h | 153 + - drivers/gpu/drm/i915_legacy/intel_uncore.c | 1958 ++ - drivers/gpu/drm/i915_legacy/intel_uncore.h | 399 + - drivers/gpu/drm/i915_legacy/intel_vbt_defs.h | 936 + - drivers/gpu/drm/i915_legacy/intel_vdsc.c | 964 + - drivers/gpu/drm/i915_legacy/intel_wopcm.c | 281 + - drivers/gpu/drm/i915_legacy/intel_wopcm.h | 31 + - .../gpu/drm/i915_legacy/intel_workarounds.c | 1344 ++ - .../gpu/drm/i915_legacy/intel_workarounds.h | 34 + - .../drm/i915_legacy/intel_workarounds_types.h | 27 + - .../i915_legacy/selftests/huge_gem_object.c | 139 + - .../i915_legacy/selftests/huge_gem_object.h | 45 + - .../drm/i915_legacy/selftests/huge_pages.c | 1792 ++ - .../drm/i915_legacy/selftests/i915_active.c | 157 + - .../gpu/drm/i915_legacy/selftests/i915_gem.c | 222 + - .../selftests/i915_gem_coherency.c | 397 + - .../i915_legacy/selftests/i915_gem_context.c | 1859 ++ - .../i915_legacy/selftests/i915_gem_dmabuf.c | 404 + - .../i915_legacy/selftests/i915_gem_evict.c | 554 + - .../drm/i915_legacy/selftests/i915_gem_gtt.c | 1733 ++ - .../i915_legacy/selftests/i915_gem_object.c | 659 + - .../selftests/i915_live_selftests.h | 28 + - .../selftests/i915_mock_selftests.h | 26 + - .../drm/i915_legacy/selftests/i915_random.c | 89 + - .../drm/i915_legacy/selftests/i915_random.h | 60 + - .../drm/i915_legacy/selftests/i915_request.c | 1246 ++ - .../drm/i915_legacy/selftests/i915_selftest.c | 299 + - .../drm/i915_legacy/selftests/i915_sw_fence.c | 757 + - .../drm/i915_legacy/selftests/i915_syncmap.c | 616 + - .../drm/i915_legacy/selftests/i915_timeline.c | 845 + - .../gpu/drm/i915_legacy/selftests/i915_vma.c | 754 + - .../i915_legacy/selftests/igt_flush_test.c | 33 + - .../i915_legacy/selftests/igt_flush_test.h | 14 + - .../drm/i915_legacy/selftests/igt_live_test.c | 78 + - .../drm/i915_legacy/selftests/igt_live_test.h | 35 + - .../gpu/drm/i915_legacy/selftests/igt_reset.c | 44 + - .../gpu/drm/i915_legacy/selftests/igt_reset.h | 15 + - .../drm/i915_legacy/selftests/igt_spinner.c | 201 + - .../drm/i915_legacy/selftests/igt_spinner.h | 37 + - .../drm/i915_legacy/selftests/igt_wedge_me.h | 58 + - .../i915_legacy/selftests/intel_engine_cs.c | 58 + - .../gpu/drm/i915_legacy/selftests/intel_guc.c | 358 + - .../i915_legacy/selftests/intel_hangcheck.c | 1919 ++ - .../gpu/drm/i915_legacy/selftests/intel_lrc.c | 1330 ++ - .../drm/i915_legacy/selftests/intel_uncore.c | 330 + - .../i915_legacy/selftests/intel_workarounds.c | 901 + - .../drm/i915_legacy/selftests/lib_sw_fence.c | 132 + - .../drm/i915_legacy/selftests/lib_sw_fence.h | 43 + - .../drm/i915_legacy/selftests/mock_context.c | 124 + - .../drm/i915_legacy/selftests/mock_context.h | 42 + - .../drm/i915_legacy/selftests/mock_dmabuf.c | 162 + - .../drm/i915_legacy/selftests/mock_dmabuf.h | 41 + - .../gpu/drm/i915_legacy/selftests/mock_drm.c | 73 + - .../gpu/drm/i915_legacy/selftests/mock_drm.h | 31 + - .../drm/i915_legacy/selftests/mock_engine.c | 321 + - .../drm/i915_legacy/selftests/mock_engine.h | 49 + - .../i915_legacy/selftests/mock_gem_device.c | 241 + - .../i915_legacy/selftests/mock_gem_device.h | 10 + - .../i915_legacy/selftests/mock_gem_object.h | 9 + - .../gpu/drm/i915_legacy/selftests/mock_gtt.c | 127 + - .../gpu/drm/i915_legacy/selftests/mock_gtt.h | 35 + - .../drm/i915_legacy/selftests/mock_request.c | 59 + - .../drm/i915_legacy/selftests/mock_request.h | 39 + - .../drm/i915_legacy/selftests/mock_timeline.c | 30 + - .../drm/i915_legacy/selftests/mock_timeline.h | 15 + - .../drm/i915_legacy/selftests/mock_uncore.c | 46 + - .../drm/i915_legacy/selftests/mock_uncore.h | 30 + - .../drm/i915_legacy/selftests/scatterlist.c | 379 + - drivers/gpu/drm/i915_legacy/vlv_dsi.c | 1830 ++ - drivers/gpu/drm/i915_legacy/vlv_dsi_pll.c | 567 + - 358 files changed, 244185 insertions(+), 2 deletions(-) - create mode 100644 drivers/gpu/drm/i915_legacy/.gitignore - create mode 100644 drivers/gpu/drm/i915_legacy/Kconfig - create mode 100644 drivers/gpu/drm/i915_legacy/Kconfig.debug - create mode 100644 drivers/gpu/drm/i915_legacy/Makefile - create mode 100644 drivers/gpu/drm/i915_legacy/Makefile.header-test - create mode 100644 drivers/gpu/drm/i915_legacy/dvo.h - create mode 100644 drivers/gpu/drm/i915_legacy/dvo_ch7017.c - create mode 100644 drivers/gpu/drm/i915_legacy/dvo_ch7xxx.c - create mode 100644 drivers/gpu/drm/i915_legacy/dvo_ivch.c - create mode 100644 drivers/gpu/drm/i915_legacy/dvo_ns2501.c - create mode 100644 drivers/gpu/drm/i915_legacy/dvo_sil164.c - create mode 100644 drivers/gpu/drm/i915_legacy/dvo_tfp410.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/Makefile - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/aperture_gm.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/cfg_space.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/cmd_parser.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/cmd_parser.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/debug.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/debugfs.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/display.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/display.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/dmabuf.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/dmabuf.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/edid.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/edid.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/execlist.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/execlist.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/fb_decoder.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/fb_decoder.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/firmware.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/gtt.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/gtt.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/gvt.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/gvt.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/handlers.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/hypercall.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/interrupt.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/interrupt.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/kvmgt.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/mmio.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/mmio.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/mmio_context.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/mmio_context.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/mpt.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/opregion.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/page_track.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/page_track.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/reg.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/sched_policy.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/sched_policy.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/scheduler.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/scheduler.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/trace.h - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/trace_points.c - create mode 100644 drivers/gpu/drm/i915_legacy/gvt/vgpu.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_active.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_active.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_active_types.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_cmd_parser.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_debugfs.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_drv.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_drv.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_fixed.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_batch_pool.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_batch_pool.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_clflush.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_clflush.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_context.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_context.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_context_types.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_dmabuf.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_evict.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_execbuffer.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_fence_reg.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_fence_reg.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_gtt.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_gtt.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_internal.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_object.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_object.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_render_state.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_render_state.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_shrinker.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_stolen.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_tiling.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gem_userptr.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gemfs.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gemfs.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_globals.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_globals.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gpu_error.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_gpu_error.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_ioc32.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_irq.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_memcpy.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_mm.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_bdw.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_bdw.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_bxt.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_bxt.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_cflgt2.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_cflgt2.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_cflgt3.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_cflgt3.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_chv.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_chv.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_cnl.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_cnl.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_glk.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_glk.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_hsw.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_hsw.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_icl.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_icl.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_kblgt2.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_kblgt2.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_kblgt3.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_kblgt3.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_sklgt2.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_sklgt2.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_sklgt3.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_sklgt3.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_sklgt4.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_oa_sklgt4.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_params.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_params.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_pci.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_perf.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_pmu.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_pmu.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_priolist_types.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_pvinfo.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_query.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_query.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_reg.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_request.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_request.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_reset.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_reset.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_scheduler.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_scheduler.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_scheduler_types.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_selftest.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_suspend.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_sw_fence.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_sw_fence.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_syncmap.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_syncmap.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_sysfs.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_timeline.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_timeline.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_timeline_types.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_trace.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_trace_points.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_user_extensions.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_user_extensions.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_utils.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_vgpu.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_vgpu.h - create mode 100644 drivers/gpu/drm/i915_legacy/i915_vma.c - create mode 100644 drivers/gpu/drm/i915_legacy/i915_vma.h - create mode 100644 drivers/gpu/drm/i915_legacy/icl_dsi.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_acpi.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_atomic.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_atomic_plane.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_atomic_plane.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_audio.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_audio.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_bios.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_bios.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_breadcrumbs.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_cdclk.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_cdclk.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_color.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_color.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_combo_phy.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_connector.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_connector.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_context.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_context.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_context_types.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_crt.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_crt.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_csr.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_csr.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_ddi.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_ddi.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_device_info.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_device_info.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_display.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_display.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_dp.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_dp.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_dp_aux_backlight.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_dp_link_training.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_dp_mst.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_dpio_phy.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_dpll_mgr.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_dpll_mgr.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_drv.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_dsi.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_dsi.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_dsi_dcs_backlight.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_dsi_vbt.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_dvo.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_dvo.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_engine_cs.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_engine_types.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_fbc.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_fbc.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_fbdev.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_fbdev.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_fifo_underrun.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_frontbuffer.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_frontbuffer.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_gpu_commands.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_guc.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_guc.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_guc_ads.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_guc_ads.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_guc_ct.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_guc_ct.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_guc_fw.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_guc_fw.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_guc_fwif.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_guc_log.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_guc_log.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_guc_reg.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_guc_submission.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_guc_submission.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_gvt.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_gvt.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_hangcheck.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_hdcp.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_hdcp.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_hdmi.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_hdmi.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_hotplug.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_huc.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_huc.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_huc_fw.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_huc_fw.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_i2c.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_lpe_audio.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_lrc.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_lrc.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_lrc_reg.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_lspcon.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_lspcon.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_lvds.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_lvds.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_mocs.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_mocs.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_opregion.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_opregion.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_overlay.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_panel.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_panel.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_pipe_crc.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_pipe_crc.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_pm.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_pm.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_psr.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_psr.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_quirks.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_renderstate.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_renderstate_gen6.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_renderstate_gen7.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_renderstate_gen8.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_renderstate_gen9.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_ringbuffer.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_ringbuffer.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_runtime_pm.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_sdvo.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_sdvo.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_sdvo_regs.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_sideband.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_sprite.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_sprite.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_tv.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_tv.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_uc.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_uc.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_uc_fw.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_uc_fw.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_uncore.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_uncore.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_vbt_defs.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_vdsc.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_wopcm.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_wopcm.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_workarounds.c - create mode 100644 drivers/gpu/drm/i915_legacy/intel_workarounds.h - create mode 100644 drivers/gpu/drm/i915_legacy/intel_workarounds_types.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/huge_gem_object.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/huge_gem_object.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/huge_pages.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_active.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_gem.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_gem_coherency.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_gem_context.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_gem_dmabuf.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_gem_evict.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_gem_gtt.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_gem_object.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_live_selftests.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_mock_selftests.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_random.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_random.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_request.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_selftest.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_sw_fence.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_syncmap.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_timeline.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/i915_vma.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/igt_flush_test.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/igt_flush_test.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/igt_live_test.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/igt_live_test.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/igt_reset.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/igt_reset.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/igt_spinner.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/igt_spinner.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/igt_wedge_me.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/intel_engine_cs.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/intel_guc.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/intel_hangcheck.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/intel_lrc.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/intel_uncore.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/intel_workarounds.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/lib_sw_fence.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/lib_sw_fence.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_context.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_context.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_dmabuf.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_dmabuf.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_drm.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_drm.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_engine.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_engine.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_gem_device.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_gem_device.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_gem_object.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_gtt.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_gtt.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_request.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_request.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_timeline.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_timeline.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_uncore.c - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/mock_uncore.h - create mode 100644 drivers/gpu/drm/i915_legacy/selftests/scatterlist.c - create mode 100644 drivers/gpu/drm/i915_legacy/vlv_dsi.c - create mode 100644 drivers/gpu/drm/i915_legacy/vlv_dsi_pll.c - -diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig -index 3c88420e3497..d95b242622b3 100644 ---- a/drivers/gpu/drm/Kconfig -+++ b/drivers/gpu/drm/Kconfig -@@ -239,7 +239,7 @@ source "drivers/gpu/drm/amd/amdgpu/Kconfig" - - source "drivers/gpu/drm/nouveau/Kconfig" - --source "drivers/gpu/drm/i915/Kconfig" -+source "drivers/gpu/drm/i915_legacy/Kconfig" - - config DRM_VGEM - tristate "Virtual GEM provider" -diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile -index 9f0d2ee35794..532f50012a65 100644 ---- a/drivers/gpu/drm/Makefile -+++ b/drivers/gpu/drm/Makefile -@@ -67,7 +67,7 @@ obj-$(CONFIG_DRM_RADEON)+= radeon/ - obj-$(CONFIG_DRM_AMDGPU)+= amd/amdgpu/ - obj-$(CONFIG_DRM_MGA) += mga/ - obj-$(CONFIG_DRM_I810) += i810/ --obj-$(CONFIG_DRM_I915) += i915/ -+obj-$(CONFIG_DRM_I915) += i915_legacy/ - obj-$(CONFIG_DRM_MGAG200) += mgag200/ - obj-$(CONFIG_DRM_V3D) += v3d/ - obj-$(CONFIG_DRM_VC4) += vc4/ -diff --git a/drivers/gpu/drm/i915_legacy/.gitignore b/drivers/gpu/drm/i915_legacy/.gitignore -new file mode 100644 -index 000000000000..cff45d81f42f ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/.gitignore -@@ -0,0 +1 @@ -+header_test_*.c -diff --git a/drivers/gpu/drm/i915_legacy/Kconfig b/drivers/gpu/drm/i915_legacy/Kconfig -new file mode 100644 -index 000000000000..255f224db64b ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/Kconfig -@@ -0,0 +1,135 @@ -+# SPDX-License-Identifier: GPL-2.0-only -+config DRM_I915 -+ tristate "Intel 8xx/9xx/G3x/G4x/HD Graphics" -+ depends on DRM -+ depends on X86 && PCI -+ select INTEL_GTT -+ select INTERVAL_TREE -+ # we need shmfs for the swappable backing store, and in particular -+ # the shmem_readpage() which depends upon tmpfs -+ select SHMEM -+ select TMPFS -+ select DRM_KMS_HELPER -+ select DRM_PANEL -+ select DRM_MIPI_DSI -+ select RELAY -+ select IRQ_WORK -+ # i915 depends on ACPI_VIDEO when ACPI is enabled -+ # but for select to work, need to select ACPI_VIDEO's dependencies, ick -+ select BACKLIGHT_CLASS_DEVICE if ACPI -+ select INPUT if ACPI -+ select ACPI_VIDEO if ACPI -+ select ACPI_BUTTON if ACPI -+ select SYNC_FILE -+ select IOSF_MBI -+ select CRC32 -+ select SND_HDA_I915 if SND_HDA_CORE -+ select CEC_CORE if CEC_NOTIFIER -+ help -+ Choose this option if you have a system that has "Intel Graphics -+ Media Accelerator" or "HD Graphics" integrated graphics, -+ including 830M, 845G, 852GM, 855GM, 865G, 915G, 945G, 965G, -+ G35, G41, G43, G45 chipsets and Celeron, Pentium, Core i3, -+ Core i5, Core i7 as well as Atom CPUs with integrated graphics. -+ -+ This driver is used by the Intel driver in X.org 6.8 and -+ XFree86 4.4 and above. It replaces the older i830 module that -+ supported a subset of the hardware in older X.org releases. -+ -+ Note that the older i810/i815 chipsets require the use of the -+ i810 driver instead, and the Atom z5xx series has an entirely -+ different implementation. -+ -+ If "M" is selected, the module will be called i915. -+ -+config DRM_I915_ALPHA_SUPPORT -+ bool "Enable alpha quality support for new Intel hardware by default" -+ depends on DRM_I915 -+ default n -+ help -+ Choose this option if you have new Intel hardware and want to enable -+ the alpha quality i915 driver support for the hardware in this kernel -+ version. You can also enable the support at runtime using the module -+ parameter i915.alpha_support=1; this option changes the default for -+ that module parameter. -+ -+ It is recommended to upgrade to a kernel version with proper support -+ as soon as it is available. Generally fixes for platforms with alpha -+ support are not backported to older kernels. -+ -+ If in doubt, say "N". -+ -+config DRM_I915_CAPTURE_ERROR -+ bool "Enable capturing GPU state following a hang" -+ depends on DRM_I915 -+ default y -+ help -+ This option enables capturing the GPU state when a hang is detected. -+ This information is vital for triaging hangs and assists in debugging. -+ Please report any hang to -+ https://bugs.freedesktop.org/enter_bug.cgi?product=DRI -+ for triaging. -+ -+ If in doubt, say "Y". -+ -+config DRM_I915_COMPRESS_ERROR -+ bool "Compress GPU error state" -+ depends on DRM_I915_CAPTURE_ERROR -+ select ZLIB_DEFLATE -+ default y -+ help -+ This option selects ZLIB_DEFLATE if it isn't already -+ selected and causes any error state captured upon a GPU hang -+ to be compressed using zlib. -+ -+ If in doubt, say "Y". -+ -+config DRM_I915_USERPTR -+ bool "Always enable userptr support" -+ depends on DRM_I915 -+ select MMU_NOTIFIER -+ default y -+ help -+ This option selects CONFIG_MMU_NOTIFIER if it isn't already -+ selected to enabled full userptr support. -+ -+ If in doubt, say "Y". -+ -+config DRM_I915_GVT -+ bool "Enable Intel GVT-g graphics virtualization host support" -+ depends on DRM_I915 -+ depends on 64BIT -+ default n -+ help -+ Choose this option if you want to enable Intel GVT-g graphics -+ virtualization technology host support with integrated graphics. -+ With GVT-g, it's possible to have one integrated graphics -+ device shared by multiple VMs under different hypervisors. -+ -+ Note that at least one hypervisor like Xen or KVM is required for -+ this driver to work, and it only supports newer device from -+ Broadwell+. For further information and setup guide, you can -+ visit: http://01.org/igvt-g. -+ -+ Now it's just a stub to support the modifications of i915 for -+ GVT device model. It requires at least one MPT modules for Xen/KVM -+ and other components of GVT device model to work. Use it under -+ you own risk. -+ -+ If in doubt, say "N". -+ -+config DRM_I915_GVT_KVMGT -+ tristate "Enable KVM/VFIO support for Intel GVT-g" -+ depends on DRM_I915_GVT -+ depends on KVM -+ depends on VFIO_MDEV && VFIO_MDEV_DEVICE -+ default n -+ help -+ Choose this option if you want to enable KVMGT support for -+ Intel GVT-g. -+ -+menu "drm/i915 Debugging" -+depends on DRM_I915 -+depends on EXPERT -+source "drivers/gpu/drm/i915/Kconfig.debug" -+endmenu -diff --git a/drivers/gpu/drm/i915_legacy/Kconfig.debug b/drivers/gpu/drm/i915_legacy/Kconfig.debug -new file mode 100644 -index 000000000000..04b686d2c2d0 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/Kconfig.debug -@@ -0,0 +1,183 @@ -+# SPDX-License-Identifier: GPL-2.0-only -+config DRM_I915_WERROR -+ bool "Force GCC to throw an error instead of a warning when compiling" -+ # As this may inadvertently break the build, only allow the user -+ # to shoot oneself in the foot iff they aim really hard -+ depends on EXPERT -+ # We use the dependency on !COMPILE_TEST to not be enabled in -+ # allmodconfig or allyesconfig configurations -+ depends on !COMPILE_TEST -+ default n -+ help -+ Add -Werror to the build flags for (and only for) i915.ko. -+ Do not enable this unless you are writing code for the i915.ko module. -+ -+ Recommended for driver developers only. -+ -+ If in doubt, say "N". -+ -+config DRM_I915_DEBUG -+ bool "Enable additional driver debugging" -+ depends on DRM_I915 -+ select DEBUG_FS -+ select PREEMPT_COUNT -+ select I2C_CHARDEV -+ select STACKDEPOT -+ select DRM_DP_AUX_CHARDEV -+ select X86_MSR # used by igt/pm_rpm -+ select DRM_VGEM # used by igt/prime_vgem (dmabuf interop checks) -+ select DRM_DEBUG_MM if DRM=y -+ select DRM_DEBUG_SELFTEST -+ select SW_SYNC # signaling validation framework (igt/syncobj*) -+ select DRM_I915_SW_FENCE_DEBUG_OBJECTS -+ select DRM_I915_SELFTEST -+ select DRM_I915_DEBUG_RUNTIME_PM -+ default n -+ help -+ Choose this option to turn on extra driver debugging that may affect -+ performance but will catch some internal issues. -+ -+ Recommended for driver developers only. -+ -+ If in doubt, say "N". -+ -+config DRM_I915_DEBUG_GEM -+ bool "Insert extra checks into the GEM internals" -+ default n -+ depends on DRM_I915_WERROR -+ help -+ Enable extra sanity checks (including BUGs) along the GEM driver -+ paths that may slow the system down and if hit hang the machine. -+ -+ Recommended for driver developers only. -+ -+ If in doubt, say "N". -+ -+config DRM_I915_ERRLOG_GEM -+ bool "Insert extra logging (very verbose) for common GEM errors" -+ default n -+ depends on DRM_I915_DEBUG_GEM -+ help -+ Enable additional logging that may help track down the cause of -+ principally userspace errors. -+ -+ Recommended for driver developers only. -+ -+ If in doubt, say "N". -+ -+config DRM_I915_TRACE_GEM -+ bool "Insert extra ftrace output from the GEM internals" -+ depends on DRM_I915_DEBUG_GEM -+ select TRACING -+ default n -+ help -+ Enable additional and verbose debugging output that will spam -+ ordinary tests, but may be vital for post-mortem debugging when -+ used with /proc/sys/kernel/ftrace_dump_on_oops -+ -+ Recommended for driver developers only. -+ -+ If in doubt, say "N". -+ -+config DRM_I915_SW_FENCE_DEBUG_OBJECTS -+ bool "Enable additional driver debugging for fence objects" -+ depends on DRM_I915 -+ select DEBUG_OBJECTS -+ default n -+ help -+ Choose this option to turn on extra driver debugging that may affect -+ performance but will catch some internal issues. -+ -+ Recommended for driver developers only. -+ -+ If in doubt, say "N". -+ -+config DRM_I915_SW_FENCE_CHECK_DAG -+ bool "Enable additional driver debugging for detecting dependency cycles" -+ depends on DRM_I915 -+ default n -+ help -+ Choose this option to turn on extra driver debugging that may affect -+ performance but will catch some internal issues. -+ -+ Recommended for driver developers only. -+ -+ If in doubt, say "N". -+ -+config DRM_I915_DEBUG_GUC -+ bool "Enable additional driver debugging for GuC" -+ depends on DRM_I915 -+ default n -+ help -+ Choose this option to turn on extra driver debugging that may affect -+ performance but will help resolve GuC related issues. -+ -+ Recommended for driver developers only. -+ -+ If in doubt, say "N". -+ -+config DRM_I915_SELFTEST -+ bool "Enable selftests upon driver load" -+ depends on DRM_I915 -+ default n -+ select FAULT_INJECTION -+ select PRIME_NUMBERS -+ help -+ Choose this option to allow the driver to perform selftests upon -+ loading; also requires the i915.selftest=1 module parameter. To -+ exit the module after running the selftests (i.e. to prevent normal -+ module initialisation afterwards) use i915.selftest=-1. -+ -+ Recommended for driver developers only. -+ -+ If in doubt, say "N". -+ -+config DRM_I915_SELFTEST_BROKEN -+ bool "Enable broken and dangerous selftests" -+ depends on DRM_I915_SELFTEST -+ depends on BROKEN -+ default n -+ help -+ This option enables the execution of selftests that are "dangerous" -+ and may trigger unintended HW side-effects as they break strict -+ rules given in the HW specification. For science. -+ -+ Recommended for masochistic driver developers only. -+ -+ If in doubt, say "N". -+ -+config DRM_I915_LOW_LEVEL_TRACEPOINTS -+ bool "Enable low level request tracing events" -+ depends on DRM_I915 -+ default n -+ help -+ Choose this option to turn on low level request tracing events. -+ This provides the ability to precisely monitor engine utilisation -+ and also analyze the request dependency resolving timeline. -+ -+ If in doubt, say "N". -+ -+config DRM_I915_DEBUG_VBLANK_EVADE -+ bool "Enable extra debug warnings for vblank evasion" -+ depends on DRM_I915 -+ default n -+ help -+ Choose this option to turn on extra debug warnings for the -+ vblank evade mechanism. This gives a warning every time the -+ the deadline allotted for the vblank evade critical section -+ is exceeded, even if there isn't an actual risk of missing -+ the vblank. -+ -+ If in doubt, say "N". -+ -+config DRM_I915_DEBUG_RUNTIME_PM -+ bool "Enable extra state checking for runtime PM" -+ depends on DRM_I915 -+ default n -+ select STACKDEPOT -+ help -+ Choose this option to turn on extra state checking for the -+ runtime PM functionality. This may introduce overhead during -+ driver loading, suspend and resume operations. -+ -+ If in doubt, say "N" -diff --git a/drivers/gpu/drm/i915_legacy/Makefile b/drivers/gpu/drm/i915_legacy/Makefile -new file mode 100644 -index 000000000000..fbcb0904f4a8 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/Makefile -@@ -0,0 +1,212 @@ -+# SPDX-License-Identifier: GPL-2.0 -+# -+# Makefile for the drm device driver. This driver provides support for the -+# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. -+ -+# Add a set of useful warning flags and enable -Werror for CI to prevent -+# trivial mistakes from creeping in. We have to do this piecemeal as we reject -+# any patch that isn't warning clean, so turning on -Wall -Wextra (or W=1) we -+# need to filter out dubious warnings. Still it is our interest -+# to keep running locally with W=1 C=1 until we are completely clean. -+# -+# Note the danger in using -Wall -Wextra is that when CI updates gcc we -+# will most likely get a sudden build breakage... Hopefully we will fix -+# new warnings before CI updates! -+subdir-ccflags-y := -Wall -Wextra -+subdir-ccflags-y += $(call cc-disable-warning, unused-parameter) -+subdir-ccflags-y += $(call cc-disable-warning, type-limits) -+subdir-ccflags-y += $(call cc-disable-warning, missing-field-initializers) -+subdir-ccflags-y += $(call cc-disable-warning, implicit-fallthrough) -+subdir-ccflags-y += $(call cc-disable-warning, unused-but-set-variable) -+# clang warnings -+subdir-ccflags-y += $(call cc-disable-warning, sign-compare) -+subdir-ccflags-y += $(call cc-disable-warning, sometimes-uninitialized) -+subdir-ccflags-y += $(call cc-disable-warning, initializer-overrides) -+subdir-ccflags-y += $(call cc-disable-warning, uninitialized) -+subdir-ccflags-$(CONFIG_DRM_I915_WERROR) += -Werror -+ -+# Fine grained warnings disable -+CFLAGS_i915_pci.o = $(call cc-disable-warning, override-init) -+CFLAGS_intel_fbdev.o = $(call cc-disable-warning, override-init) -+ -+subdir-ccflags-y += \ -+ $(call as-instr,movntdqa (%eax)$(comma)%xmm0,-DCONFIG_AS_MOVNTDQA) -+ -+# Extra header tests -+include $(src)/Makefile.header-test -+ -+# Please keep these build lists sorted! -+ -+# core driver code -+i915-y += i915_drv.o \ -+ i915_irq.o \ -+ i915_memcpy.o \ -+ i915_mm.o \ -+ i915_params.o \ -+ i915_pci.o \ -+ i915_reset.o \ -+ i915_suspend.o \ -+ i915_sw_fence.o \ -+ i915_syncmap.o \ -+ i915_sysfs.o \ -+ i915_user_extensions.o \ -+ intel_csr.o \ -+ intel_device_info.o \ -+ intel_pm.o \ -+ intel_runtime_pm.o \ -+ intel_workarounds.o -+ -+i915-$(CONFIG_COMPAT) += i915_ioc32.o -+i915-$(CONFIG_DEBUG_FS) += i915_debugfs.o intel_pipe_crc.o -+i915-$(CONFIG_PERF_EVENTS) += i915_pmu.o -+ -+# GEM code -+i915-y += \ -+ i915_active.o \ -+ i915_cmd_parser.o \ -+ i915_gem_batch_pool.o \ -+ i915_gem_clflush.o \ -+ i915_gem_context.o \ -+ i915_gem_dmabuf.o \ -+ i915_gem_evict.o \ -+ i915_gem_execbuffer.o \ -+ i915_gem_fence_reg.o \ -+ i915_gem_gtt.o \ -+ i915_gem_internal.o \ -+ i915_gem.o \ -+ i915_gem_object.o \ -+ i915_gem_render_state.o \ -+ i915_gem_shrinker.o \ -+ i915_gem_stolen.o \ -+ i915_gem_tiling.o \ -+ i915_gem_userptr.o \ -+ i915_gemfs.o \ -+ i915_globals.o \ -+ i915_query.o \ -+ i915_request.o \ -+ i915_scheduler.o \ -+ i915_timeline.o \ -+ i915_trace_points.o \ -+ i915_vma.o \ -+ intel_breadcrumbs.o \ -+ intel_context.o \ -+ intel_engine_cs.o \ -+ intel_hangcheck.o \ -+ intel_lrc.o \ -+ intel_mocs.o \ -+ intel_ringbuffer.o \ -+ intel_uncore.o \ -+ intel_wopcm.o -+ -+# general-purpose microcontroller (GuC) support -+i915-y += intel_uc.o \ -+ intel_uc_fw.o \ -+ intel_guc.o \ -+ intel_guc_ads.o \ -+ intel_guc_ct.o \ -+ intel_guc_fw.o \ -+ intel_guc_log.o \ -+ intel_guc_submission.o \ -+ intel_huc.o \ -+ intel_huc_fw.o -+ -+# autogenerated null render state -+i915-y += intel_renderstate_gen6.o \ -+ intel_renderstate_gen7.o \ -+ intel_renderstate_gen8.o \ -+ intel_renderstate_gen9.o -+ -+# modesetting core code -+i915-y += intel_audio.o \ -+ intel_atomic.o \ -+ intel_atomic_plane.o \ -+ intel_bios.o \ -+ intel_cdclk.o \ -+ intel_color.o \ -+ intel_combo_phy.o \ -+ intel_connector.o \ -+ intel_display.o \ -+ intel_dpio_phy.o \ -+ intel_dpll_mgr.o \ -+ intel_fbc.o \ -+ intel_fifo_underrun.o \ -+ intel_frontbuffer.o \ -+ intel_hdcp.o \ -+ intel_hotplug.o \ -+ intel_overlay.o \ -+ intel_psr.o \ -+ intel_quirks.o \ -+ intel_sideband.o \ -+ intel_sprite.o -+i915-$(CONFIG_ACPI) += intel_acpi.o intel_opregion.o -+i915-$(CONFIG_DRM_FBDEV_EMULATION) += intel_fbdev.o -+ -+# modesetting output/encoder code -+i915-y += dvo_ch7017.o \ -+ dvo_ch7xxx.o \ -+ dvo_ivch.o \ -+ dvo_ns2501.o \ -+ dvo_sil164.o \ -+ dvo_tfp410.o \ -+ icl_dsi.o \ -+ intel_crt.o \ -+ intel_ddi.o \ -+ intel_dp_aux_backlight.o \ -+ intel_dp_link_training.o \ -+ intel_dp_mst.o \ -+ intel_dp.o \ -+ intel_dsi.o \ -+ intel_dsi_dcs_backlight.o \ -+ intel_dsi_vbt.o \ -+ intel_dvo.o \ -+ intel_hdmi.o \ -+ intel_i2c.o \ -+ intel_lspcon.o \ -+ intel_lvds.o \ -+ intel_panel.o \ -+ intel_sdvo.o \ -+ intel_tv.o \ -+ vlv_dsi.o \ -+ vlv_dsi_pll.o \ -+ intel_vdsc.o -+ -+# Post-mortem debug and GPU hang state capture -+i915-$(CONFIG_DRM_I915_CAPTURE_ERROR) += i915_gpu_error.o -+i915-$(CONFIG_DRM_I915_SELFTEST) += \ -+ selftests/i915_random.o \ -+ selftests/i915_selftest.o \ -+ selftests/igt_flush_test.o \ -+ selftests/igt_live_test.o \ -+ selftests/igt_reset.o \ -+ selftests/igt_spinner.o -+ -+# virtual gpu code -+i915-y += i915_vgpu.o -+ -+# perf code -+i915-y += i915_perf.o \ -+ i915_oa_hsw.o \ -+ i915_oa_bdw.o \ -+ i915_oa_chv.o \ -+ i915_oa_sklgt2.o \ -+ i915_oa_sklgt3.o \ -+ i915_oa_sklgt4.o \ -+ i915_oa_bxt.o \ -+ i915_oa_kblgt2.o \ -+ i915_oa_kblgt3.o \ -+ i915_oa_glk.o \ -+ i915_oa_cflgt2.o \ -+ i915_oa_cflgt3.o \ -+ i915_oa_cnl.o \ -+ i915_oa_icl.o -+ -+ifeq ($(CONFIG_DRM_I915_GVT),y) -+i915-y += intel_gvt.o -+include $(src)/gvt/Makefile -+endif -+ -+# LPE Audio for VLV and CHT -+i915-y += intel_lpe_audio.o -+ -+obj-$(CONFIG_DRM_I915) += i915.o -+obj-$(CONFIG_DRM_I915_GVT_KVMGT) += gvt/kvmgt.o -diff --git a/drivers/gpu/drm/i915_legacy/Makefile.header-test b/drivers/gpu/drm/i915_legacy/Makefile.header-test -new file mode 100644 -index 000000000000..c1c391816fa7 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/Makefile.header-test -@@ -0,0 +1,47 @@ -+# SPDX-License-Identifier: MIT -+# Copyright © 2019 Intel Corporation -+ -+# Test the headers are compilable as standalone units -+header_test := \ -+ i915_active_types.h \ -+ i915_gem_context_types.h \ -+ i915_priolist_types.h \ -+ i915_scheduler_types.h \ -+ i915_timeline_types.h \ -+ intel_atomic_plane.h \ -+ intel_audio.h \ -+ intel_cdclk.h \ -+ intel_color.h \ -+ intel_connector.h \ -+ intel_context_types.h \ -+ intel_crt.h \ -+ intel_csr.h \ -+ intel_ddi.h \ -+ intel_dp.h \ -+ intel_dvo.h \ -+ intel_engine_types.h \ -+ intel_fbc.h \ -+ intel_fbdev.h \ -+ intel_frontbuffer.h \ -+ intel_hdcp.h \ -+ intel_hdmi.h \ -+ intel_lspcon.h \ -+ intel_lvds.h \ -+ intel_panel.h \ -+ intel_pipe_crc.h \ -+ intel_pm.h \ -+ intel_psr.h \ -+ intel_sdvo.h \ -+ intel_sprite.h \ -+ intel_tv.h \ -+ intel_workarounds_types.h -+ -+quiet_cmd_header_test = HDRTEST $@ -+ cmd_header_test = echo "\#include \"$( $@ -+ -+header_test_%.c: %.h -+ $(call cmd,header_test) -+ -+i915-$(CONFIG_DRM_I915_WERROR) += $(foreach h,$(header_test),$(patsubst %.h,header_test_%.o,$(h))) -+ -+clean-files += $(foreach h,$(header_test),$(patsubst %.h,header_test_%.c,$(h))) -diff --git a/drivers/gpu/drm/i915_legacy/dvo.h b/drivers/gpu/drm/i915_legacy/dvo.h -new file mode 100644 -index 000000000000..16e0345b711f ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/dvo.h -@@ -0,0 +1,138 @@ -+/* -+ * Copyright © 2006 Eric Anholt -+ * -+ * Permission to use, copy, modify, distribute, and sell this software and its -+ * documentation for any purpose is hereby granted without fee, provided that -+ * the above copyright notice appear in all copies and that both that copyright -+ * notice and this permission notice appear in supporting documentation, and -+ * that the name of the copyright holders not be used in advertising or -+ * publicity pertaining to distribution of the software without specific, -+ * written prior permission. The copyright holders make no representations -+ * about the suitability of this software for any purpose. It is provided "as -+ * is" without express or implied warranty. -+ * -+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, -+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO -+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR -+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, -+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE -+ * OF THIS SOFTWARE. -+ */ -+ -+#ifndef _INTEL_DVO_H -+#define _INTEL_DVO_H -+ -+#include -+#include -+#include "intel_drv.h" -+ -+struct intel_dvo_device { -+ const char *name; -+ int type; -+ /* DVOA/B/C output register */ -+ i915_reg_t dvo_reg; -+ i915_reg_t dvo_srcdim_reg; -+ /* GPIO register used for i2c bus to control this device */ -+ u32 gpio; -+ int slave_addr; -+ -+ const struct intel_dvo_dev_ops *dev_ops; -+ void *dev_priv; -+ struct i2c_adapter *i2c_bus; -+}; -+ -+struct intel_dvo_dev_ops { -+ /* -+ * Initialize the device at startup time. -+ * Returns NULL if the device does not exist. -+ */ -+ bool (*init)(struct intel_dvo_device *dvo, -+ struct i2c_adapter *i2cbus); -+ -+ /* -+ * Called to allow the output a chance to create properties after the -+ * RandR objects have been created. -+ */ -+ void (*create_resources)(struct intel_dvo_device *dvo); -+ -+ /* -+ * Turn on/off output. -+ * -+ * Because none of our dvo drivers support an intermediate power levels, -+ * we don't expose this in the interfac. -+ */ -+ void (*dpms)(struct intel_dvo_device *dvo, bool enable); -+ -+ /* -+ * Callback for testing a video mode for a given output. -+ * -+ * This function should only check for cases where a mode can't -+ * be supported on the output specifically, and not represent -+ * generic CRTC limitations. -+ * -+ * \return MODE_OK if the mode is valid, or another MODE_* otherwise. -+ */ -+ int (*mode_valid)(struct intel_dvo_device *dvo, -+ struct drm_display_mode *mode); -+ -+ /* -+ * Callback for preparing mode changes on an output -+ */ -+ void (*prepare)(struct intel_dvo_device *dvo); -+ -+ /* -+ * Callback for committing mode changes on an output -+ */ -+ void (*commit)(struct intel_dvo_device *dvo); -+ -+ /* -+ * Callback for setting up a video mode after fixups have been made. -+ * -+ * This is only called while the output is disabled. The dpms callback -+ * must be all that's necessary for the output, to turn the output on -+ * after this function is called. -+ */ -+ void (*mode_set)(struct intel_dvo_device *dvo, -+ const struct drm_display_mode *mode, -+ const struct drm_display_mode *adjusted_mode); -+ -+ /* -+ * Probe for a connected output, and return detect_status. -+ */ -+ enum drm_connector_status (*detect)(struct intel_dvo_device *dvo); -+ -+ /* -+ * Probe the current hw status, returning true if the connected output -+ * is active. -+ */ -+ bool (*get_hw_state)(struct intel_dvo_device *dev); -+ -+ /** -+ * Query the device for the modes it provides. -+ * -+ * This function may also update MonInfo, mm_width, and mm_height. -+ * -+ * \return singly-linked list of modes or NULL if no modes found. -+ */ -+ struct drm_display_mode *(*get_modes)(struct intel_dvo_device *dvo); -+ -+ /** -+ * Clean up driver-specific bits of the output -+ */ -+ void (*destroy) (struct intel_dvo_device *dvo); -+ -+ /** -+ * Debugging hook to dump device registers to log file -+ */ -+ void (*dump_regs)(struct intel_dvo_device *dvo); -+}; -+ -+extern const struct intel_dvo_dev_ops sil164_ops; -+extern const struct intel_dvo_dev_ops ch7xxx_ops; -+extern const struct intel_dvo_dev_ops ivch_ops; -+extern const struct intel_dvo_dev_ops tfp410_ops; -+extern const struct intel_dvo_dev_ops ch7017_ops; -+extern const struct intel_dvo_dev_ops ns2501_ops; -+ -+#endif /* _INTEL_DVO_H */ -diff --git a/drivers/gpu/drm/i915_legacy/dvo_ch7017.c b/drivers/gpu/drm/i915_legacy/dvo_ch7017.c -new file mode 100644 -index 000000000000..caac9942e1e3 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/dvo_ch7017.c -@@ -0,0 +1,414 @@ -+/* -+ * Copyright © 2006 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eric Anholt -+ * -+ */ -+ -+#include "dvo.h" -+ -+#define CH7017_TV_DISPLAY_MODE 0x00 -+#define CH7017_FLICKER_FILTER 0x01 -+#define CH7017_VIDEO_BANDWIDTH 0x02 -+#define CH7017_TEXT_ENHANCEMENT 0x03 -+#define CH7017_START_ACTIVE_VIDEO 0x04 -+#define CH7017_HORIZONTAL_POSITION 0x05 -+#define CH7017_VERTICAL_POSITION 0x06 -+#define CH7017_BLACK_LEVEL 0x07 -+#define CH7017_CONTRAST_ENHANCEMENT 0x08 -+#define CH7017_TV_PLL 0x09 -+#define CH7017_TV_PLL_M 0x0a -+#define CH7017_TV_PLL_N 0x0b -+#define CH7017_SUB_CARRIER_0 0x0c -+#define CH7017_CIV_CONTROL 0x10 -+#define CH7017_CIV_0 0x11 -+#define CH7017_CHROMA_BOOST 0x14 -+#define CH7017_CLOCK_MODE 0x1c -+#define CH7017_INPUT_CLOCK 0x1d -+#define CH7017_GPIO_CONTROL 0x1e -+#define CH7017_INPUT_DATA_FORMAT 0x1f -+#define CH7017_CONNECTION_DETECT 0x20 -+#define CH7017_DAC_CONTROL 0x21 -+#define CH7017_BUFFERED_CLOCK_OUTPUT 0x22 -+#define CH7017_DEFEAT_VSYNC 0x47 -+#define CH7017_TEST_PATTERN 0x48 -+ -+#define CH7017_POWER_MANAGEMENT 0x49 -+/** Enables the TV output path. */ -+#define CH7017_TV_EN (1 << 0) -+#define CH7017_DAC0_POWER_DOWN (1 << 1) -+#define CH7017_DAC1_POWER_DOWN (1 << 2) -+#define CH7017_DAC2_POWER_DOWN (1 << 3) -+#define CH7017_DAC3_POWER_DOWN (1 << 4) -+/** Powers down the TV out block, and DAC0-3 */ -+#define CH7017_TV_POWER_DOWN_EN (1 << 5) -+ -+#define CH7017_VERSION_ID 0x4a -+ -+#define CH7017_DEVICE_ID 0x4b -+#define CH7017_DEVICE_ID_VALUE 0x1b -+#define CH7018_DEVICE_ID_VALUE 0x1a -+#define CH7019_DEVICE_ID_VALUE 0x19 -+ -+#define CH7017_XCLK_D2_ADJUST 0x53 -+#define CH7017_UP_SCALER_COEFF_0 0x55 -+#define CH7017_UP_SCALER_COEFF_1 0x56 -+#define CH7017_UP_SCALER_COEFF_2 0x57 -+#define CH7017_UP_SCALER_COEFF_3 0x58 -+#define CH7017_UP_SCALER_COEFF_4 0x59 -+#define CH7017_UP_SCALER_VERTICAL_INC_0 0x5a -+#define CH7017_UP_SCALER_VERTICAL_INC_1 0x5b -+#define CH7017_GPIO_INVERT 0x5c -+#define CH7017_UP_SCALER_HORIZONTAL_INC_0 0x5d -+#define CH7017_UP_SCALER_HORIZONTAL_INC_1 0x5e -+ -+#define CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT 0x5f -+/**< Low bits of horizontal active pixel input */ -+ -+#define CH7017_ACTIVE_INPUT_LINE_OUTPUT 0x60 -+/** High bits of horizontal active pixel input */ -+#define CH7017_LVDS_HAP_INPUT_MASK (0x7 << 0) -+/** High bits of vertical active line output */ -+#define CH7017_LVDS_VAL_HIGH_MASK (0x7 << 3) -+ -+#define CH7017_VERTICAL_ACTIVE_LINE_OUTPUT 0x61 -+/**< Low bits of vertical active line output */ -+ -+#define CH7017_HORIZONTAL_ACTIVE_PIXEL_OUTPUT 0x62 -+/**< Low bits of horizontal active pixel output */ -+ -+#define CH7017_LVDS_POWER_DOWN 0x63 -+/** High bits of horizontal active pixel output */ -+#define CH7017_LVDS_HAP_HIGH_MASK (0x7 << 0) -+/** Enables the LVDS power down state transition */ -+#define CH7017_LVDS_POWER_DOWN_EN (1 << 6) -+/** Enables the LVDS upscaler */ -+#define CH7017_LVDS_UPSCALER_EN (1 << 7) -+#define CH7017_LVDS_POWER_DOWN_DEFAULT_RESERVED 0x08 -+ -+#define CH7017_LVDS_ENCODING 0x64 -+#define CH7017_LVDS_DITHER_2D (1 << 2) -+#define CH7017_LVDS_DITHER_DIS (1 << 3) -+#define CH7017_LVDS_DUAL_CHANNEL_EN (1 << 4) -+#define CH7017_LVDS_24_BIT (1 << 5) -+ -+#define CH7017_LVDS_ENCODING_2 0x65 -+ -+#define CH7017_LVDS_PLL_CONTROL 0x66 -+/** Enables the LVDS panel output path */ -+#define CH7017_LVDS_PANEN (1 << 0) -+/** Enables the LVDS panel backlight */ -+#define CH7017_LVDS_BKLEN (1 << 3) -+ -+#define CH7017_POWER_SEQUENCING_T1 0x67 -+#define CH7017_POWER_SEQUENCING_T2 0x68 -+#define CH7017_POWER_SEQUENCING_T3 0x69 -+#define CH7017_POWER_SEQUENCING_T4 0x6a -+#define CH7017_POWER_SEQUENCING_T5 0x6b -+#define CH7017_GPIO_DRIVER_TYPE 0x6c -+#define CH7017_GPIO_DATA 0x6d -+#define CH7017_GPIO_DIRECTION_CONTROL 0x6e -+ -+#define CH7017_LVDS_PLL_FEEDBACK_DIV 0x71 -+# define CH7017_LVDS_PLL_FEED_BACK_DIVIDER_SHIFT 4 -+# define CH7017_LVDS_PLL_FEED_FORWARD_DIVIDER_SHIFT 0 -+# define CH7017_LVDS_PLL_FEEDBACK_DEFAULT_RESERVED 0x80 -+ -+#define CH7017_LVDS_PLL_VCO_CONTROL 0x72 -+# define CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED 0x80 -+# define CH7017_LVDS_PLL_VCO_SHIFT 4 -+# define CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT 0 -+ -+#define CH7017_OUTPUTS_ENABLE 0x73 -+# define CH7017_CHARGE_PUMP_LOW 0x0 -+# define CH7017_CHARGE_PUMP_HIGH 0x3 -+# define CH7017_LVDS_CHANNEL_A (1 << 3) -+# define CH7017_LVDS_CHANNEL_B (1 << 4) -+# define CH7017_TV_DAC_A (1 << 5) -+# define CH7017_TV_DAC_B (1 << 6) -+# define CH7017_DDC_SELECT_DC2 (1 << 7) -+ -+#define CH7017_LVDS_OUTPUT_AMPLITUDE 0x74 -+#define CH7017_LVDS_PLL_EMI_REDUCTION 0x75 -+#define CH7017_LVDS_POWER_DOWN_FLICKER 0x76 -+ -+#define CH7017_LVDS_CONTROL_2 0x78 -+# define CH7017_LOOP_FILTER_SHIFT 5 -+# define CH7017_PHASE_DETECTOR_SHIFT 0 -+ -+#define CH7017_BANG_LIMIT_CONTROL 0x7f -+ -+struct ch7017_priv { -+ u8 dummy; -+}; -+ -+static void ch7017_dump_regs(struct intel_dvo_device *dvo); -+static void ch7017_dpms(struct intel_dvo_device *dvo, bool enable); -+ -+static bool ch7017_read(struct intel_dvo_device *dvo, u8 addr, u8 *val) -+{ -+ struct i2c_msg msgs[] = { -+ { -+ .addr = dvo->slave_addr, -+ .flags = 0, -+ .len = 1, -+ .buf = &addr, -+ }, -+ { -+ .addr = dvo->slave_addr, -+ .flags = I2C_M_RD, -+ .len = 1, -+ .buf = val, -+ } -+ }; -+ return i2c_transfer(dvo->i2c_bus, msgs, 2) == 2; -+} -+ -+static bool ch7017_write(struct intel_dvo_device *dvo, u8 addr, u8 val) -+{ -+ u8 buf[2] = { addr, val }; -+ struct i2c_msg msg = { -+ .addr = dvo->slave_addr, -+ .flags = 0, -+ .len = 2, -+ .buf = buf, -+ }; -+ return i2c_transfer(dvo->i2c_bus, &msg, 1) == 1; -+} -+ -+/** Probes for a CH7017 on the given bus and slave address. */ -+static bool ch7017_init(struct intel_dvo_device *dvo, -+ struct i2c_adapter *adapter) -+{ -+ struct ch7017_priv *priv; -+ const char *str; -+ u8 val; -+ -+ priv = kzalloc(sizeof(struct ch7017_priv), GFP_KERNEL); -+ if (priv == NULL) -+ return false; -+ -+ dvo->i2c_bus = adapter; -+ dvo->dev_priv = priv; -+ -+ if (!ch7017_read(dvo, CH7017_DEVICE_ID, &val)) -+ goto fail; -+ -+ switch (val) { -+ case CH7017_DEVICE_ID_VALUE: -+ str = "ch7017"; -+ break; -+ case CH7018_DEVICE_ID_VALUE: -+ str = "ch7018"; -+ break; -+ case CH7019_DEVICE_ID_VALUE: -+ str = "ch7019"; -+ break; -+ default: -+ DRM_DEBUG_KMS("ch701x not detected, got %d: from %s " -+ "slave %d.\n", -+ val, adapter->name, dvo->slave_addr); -+ goto fail; -+ } -+ -+ DRM_DEBUG_KMS("%s detected on %s, addr %d\n", -+ str, adapter->name, dvo->slave_addr); -+ return true; -+ -+fail: -+ kfree(priv); -+ return false; -+} -+ -+static enum drm_connector_status ch7017_detect(struct intel_dvo_device *dvo) -+{ -+ return connector_status_connected; -+} -+ -+static enum drm_mode_status ch7017_mode_valid(struct intel_dvo_device *dvo, -+ struct drm_display_mode *mode) -+{ -+ if (mode->clock > 160000) -+ return MODE_CLOCK_HIGH; -+ -+ return MODE_OK; -+} -+ -+static void ch7017_mode_set(struct intel_dvo_device *dvo, -+ const struct drm_display_mode *mode, -+ const struct drm_display_mode *adjusted_mode) -+{ -+ u8 lvds_pll_feedback_div, lvds_pll_vco_control; -+ u8 outputs_enable, lvds_control_2, lvds_power_down; -+ u8 horizontal_active_pixel_input; -+ u8 horizontal_active_pixel_output, vertical_active_line_output; -+ u8 active_input_line_output; -+ -+ DRM_DEBUG_KMS("Registers before mode setting\n"); -+ ch7017_dump_regs(dvo); -+ -+ /* LVDS PLL settings from page 75 of 7017-7017ds.pdf*/ -+ if (mode->clock < 100000) { -+ outputs_enable = CH7017_LVDS_CHANNEL_A | CH7017_CHARGE_PUMP_LOW; -+ lvds_pll_feedback_div = CH7017_LVDS_PLL_FEEDBACK_DEFAULT_RESERVED | -+ (2 << CH7017_LVDS_PLL_FEED_BACK_DIVIDER_SHIFT) | -+ (13 << CH7017_LVDS_PLL_FEED_FORWARD_DIVIDER_SHIFT); -+ lvds_pll_vco_control = CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED | -+ (2 << CH7017_LVDS_PLL_VCO_SHIFT) | -+ (3 << CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT); -+ lvds_control_2 = (1 << CH7017_LOOP_FILTER_SHIFT) | -+ (0 << CH7017_PHASE_DETECTOR_SHIFT); -+ } else { -+ outputs_enable = CH7017_LVDS_CHANNEL_A | CH7017_CHARGE_PUMP_HIGH; -+ lvds_pll_feedback_div = -+ CH7017_LVDS_PLL_FEEDBACK_DEFAULT_RESERVED | -+ (2 << CH7017_LVDS_PLL_FEED_BACK_DIVIDER_SHIFT) | -+ (3 << CH7017_LVDS_PLL_FEED_FORWARD_DIVIDER_SHIFT); -+ lvds_control_2 = (3 << CH7017_LOOP_FILTER_SHIFT) | -+ (0 << CH7017_PHASE_DETECTOR_SHIFT); -+ if (1) { /* XXX: dual channel panel detection. Assume yes for now. */ -+ outputs_enable |= CH7017_LVDS_CHANNEL_B; -+ lvds_pll_vco_control = CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED | -+ (2 << CH7017_LVDS_PLL_VCO_SHIFT) | -+ (13 << CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT); -+ } else { -+ lvds_pll_vco_control = CH7017_LVDS_PLL_VCO_DEFAULT_RESERVED | -+ (1 << CH7017_LVDS_PLL_VCO_SHIFT) | -+ (13 << CH7017_LVDS_PLL_POST_SCALE_DIV_SHIFT); -+ } -+ } -+ -+ horizontal_active_pixel_input = mode->hdisplay & 0x00ff; -+ -+ vertical_active_line_output = mode->vdisplay & 0x00ff; -+ horizontal_active_pixel_output = mode->hdisplay & 0x00ff; -+ -+ active_input_line_output = ((mode->hdisplay & 0x0700) >> 8) | -+ (((mode->vdisplay & 0x0700) >> 8) << 3); -+ -+ lvds_power_down = CH7017_LVDS_POWER_DOWN_DEFAULT_RESERVED | -+ (mode->hdisplay & 0x0700) >> 8; -+ -+ ch7017_dpms(dvo, false); -+ ch7017_write(dvo, CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT, -+ horizontal_active_pixel_input); -+ ch7017_write(dvo, CH7017_HORIZONTAL_ACTIVE_PIXEL_OUTPUT, -+ horizontal_active_pixel_output); -+ ch7017_write(dvo, CH7017_VERTICAL_ACTIVE_LINE_OUTPUT, -+ vertical_active_line_output); -+ ch7017_write(dvo, CH7017_ACTIVE_INPUT_LINE_OUTPUT, -+ active_input_line_output); -+ ch7017_write(dvo, CH7017_LVDS_PLL_VCO_CONTROL, lvds_pll_vco_control); -+ ch7017_write(dvo, CH7017_LVDS_PLL_FEEDBACK_DIV, lvds_pll_feedback_div); -+ ch7017_write(dvo, CH7017_LVDS_CONTROL_2, lvds_control_2); -+ ch7017_write(dvo, CH7017_OUTPUTS_ENABLE, outputs_enable); -+ -+ /* Turn the LVDS back on with new settings. */ -+ ch7017_write(dvo, CH7017_LVDS_POWER_DOWN, lvds_power_down); -+ -+ DRM_DEBUG_KMS("Registers after mode setting\n"); -+ ch7017_dump_regs(dvo); -+} -+ -+/* set the CH7017 power state */ -+static void ch7017_dpms(struct intel_dvo_device *dvo, bool enable) -+{ -+ u8 val; -+ -+ ch7017_read(dvo, CH7017_LVDS_POWER_DOWN, &val); -+ -+ /* Turn off TV/VGA, and never turn it on since we don't support it. */ -+ ch7017_write(dvo, CH7017_POWER_MANAGEMENT, -+ CH7017_DAC0_POWER_DOWN | -+ CH7017_DAC1_POWER_DOWN | -+ CH7017_DAC2_POWER_DOWN | -+ CH7017_DAC3_POWER_DOWN | -+ CH7017_TV_POWER_DOWN_EN); -+ -+ if (enable) { -+ /* Turn on the LVDS */ -+ ch7017_write(dvo, CH7017_LVDS_POWER_DOWN, -+ val & ~CH7017_LVDS_POWER_DOWN_EN); -+ } else { -+ /* Turn off the LVDS */ -+ ch7017_write(dvo, CH7017_LVDS_POWER_DOWN, -+ val | CH7017_LVDS_POWER_DOWN_EN); -+ } -+ -+ /* XXX: Should actually wait for update power status somehow */ -+ msleep(20); -+} -+ -+static bool ch7017_get_hw_state(struct intel_dvo_device *dvo) -+{ -+ u8 val; -+ -+ ch7017_read(dvo, CH7017_LVDS_POWER_DOWN, &val); -+ -+ if (val & CH7017_LVDS_POWER_DOWN_EN) -+ return false; -+ else -+ return true; -+} -+ -+static void ch7017_dump_regs(struct intel_dvo_device *dvo) -+{ -+ u8 val; -+ -+#define DUMP(reg) \ -+do { \ -+ ch7017_read(dvo, reg, &val); \ -+ DRM_DEBUG_KMS(#reg ": %02x\n", val); \ -+} while (0) -+ -+ DUMP(CH7017_HORIZONTAL_ACTIVE_PIXEL_INPUT); -+ DUMP(CH7017_HORIZONTAL_ACTIVE_PIXEL_OUTPUT); -+ DUMP(CH7017_VERTICAL_ACTIVE_LINE_OUTPUT); -+ DUMP(CH7017_ACTIVE_INPUT_LINE_OUTPUT); -+ DUMP(CH7017_LVDS_PLL_VCO_CONTROL); -+ DUMP(CH7017_LVDS_PLL_FEEDBACK_DIV); -+ DUMP(CH7017_LVDS_CONTROL_2); -+ DUMP(CH7017_OUTPUTS_ENABLE); -+ DUMP(CH7017_LVDS_POWER_DOWN); -+} -+ -+static void ch7017_destroy(struct intel_dvo_device *dvo) -+{ -+ struct ch7017_priv *priv = dvo->dev_priv; -+ -+ if (priv) { -+ kfree(priv); -+ dvo->dev_priv = NULL; -+ } -+} -+ -+const struct intel_dvo_dev_ops ch7017_ops = { -+ .init = ch7017_init, -+ .detect = ch7017_detect, -+ .mode_valid = ch7017_mode_valid, -+ .mode_set = ch7017_mode_set, -+ .dpms = ch7017_dpms, -+ .get_hw_state = ch7017_get_hw_state, -+ .dump_regs = ch7017_dump_regs, -+ .destroy = ch7017_destroy, -+}; -diff --git a/drivers/gpu/drm/i915_legacy/dvo_ch7xxx.c b/drivers/gpu/drm/i915_legacy/dvo_ch7xxx.c -new file mode 100644 -index 000000000000..397ac5233726 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/dvo_ch7xxx.c -@@ -0,0 +1,366 @@ -+/************************************************************************** -+ -+Copyright © 2006 Dave Airlie -+ -+All Rights Reserved. -+ -+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, sub license, 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 (including the -+next paragraph) 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 NON-INFRINGEMENT. -+IN NO EVENT SHALL THE AUTHOR 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. -+ -+**************************************************************************/ -+ -+#include "dvo.h" -+ -+#define CH7xxx_REG_VID 0x4a -+#define CH7xxx_REG_DID 0x4b -+ -+#define CH7011_VID 0x83 /* 7010 as well */ -+#define CH7010B_VID 0x05 -+#define CH7009A_VID 0x84 -+#define CH7009B_VID 0x85 -+#define CH7301_VID 0x95 -+ -+#define CH7xxx_VID 0x84 -+#define CH7xxx_DID 0x17 -+#define CH7010_DID 0x16 -+ -+#define CH7xxx_NUM_REGS 0x4c -+ -+#define CH7xxx_CM 0x1c -+#define CH7xxx_CM_XCM (1<<0) -+#define CH7xxx_CM_MCP (1<<2) -+#define CH7xxx_INPUT_CLOCK 0x1d -+#define CH7xxx_GPIO 0x1e -+#define CH7xxx_GPIO_HPIR (1<<3) -+#define CH7xxx_IDF 0x1f -+ -+#define CH7xxx_IDF_HSP (1<<3) -+#define CH7xxx_IDF_VSP (1<<4) -+ -+#define CH7xxx_CONNECTION_DETECT 0x20 -+#define CH7xxx_CDET_DVI (1<<5) -+ -+#define CH7301_DAC_CNTL 0x21 -+#define CH7301_HOTPLUG 0x23 -+#define CH7xxx_TCTL 0x31 -+#define CH7xxx_TVCO 0x32 -+#define CH7xxx_TPCP 0x33 -+#define CH7xxx_TPD 0x34 -+#define CH7xxx_TPVT 0x35 -+#define CH7xxx_TLPF 0x36 -+#define CH7xxx_TCT 0x37 -+#define CH7301_TEST_PATTERN 0x48 -+ -+#define CH7xxx_PM 0x49 -+#define CH7xxx_PM_FPD (1<<0) -+#define CH7301_PM_DACPD0 (1<<1) -+#define CH7301_PM_DACPD1 (1<<2) -+#define CH7301_PM_DACPD2 (1<<3) -+#define CH7xxx_PM_DVIL (1<<6) -+#define CH7xxx_PM_DVIP (1<<7) -+ -+#define CH7301_SYNC_POLARITY 0x56 -+#define CH7301_SYNC_RGB_YUV (1<<0) -+#define CH7301_SYNC_POL_DVI (1<<5) -+ -+/** @file -+ * driver for the Chrontel 7xxx DVI chip over DVO. -+ */ -+ -+static struct ch7xxx_id_struct { -+ u8 vid; -+ char *name; -+} ch7xxx_ids[] = { -+ { CH7011_VID, "CH7011" }, -+ { CH7010B_VID, "CH7010B" }, -+ { CH7009A_VID, "CH7009A" }, -+ { CH7009B_VID, "CH7009B" }, -+ { CH7301_VID, "CH7301" }, -+}; -+ -+static struct ch7xxx_did_struct { -+ u8 did; -+ char *name; -+} ch7xxx_dids[] = { -+ { CH7xxx_DID, "CH7XXX" }, -+ { CH7010_DID, "CH7010B" }, -+}; -+ -+struct ch7xxx_priv { -+ bool quiet; -+}; -+ -+static char *ch7xxx_get_id(u8 vid) -+{ -+ int i; -+ -+ for (i = 0; i < ARRAY_SIZE(ch7xxx_ids); i++) { -+ if (ch7xxx_ids[i].vid == vid) -+ return ch7xxx_ids[i].name; -+ } -+ -+ return NULL; -+} -+ -+static char *ch7xxx_get_did(u8 did) -+{ -+ int i; -+ -+ for (i = 0; i < ARRAY_SIZE(ch7xxx_dids); i++) { -+ if (ch7xxx_dids[i].did == did) -+ return ch7xxx_dids[i].name; -+ } -+ -+ return NULL; -+} -+ -+/** Reads an 8 bit register */ -+static bool ch7xxx_readb(struct intel_dvo_device *dvo, int addr, u8 *ch) -+{ -+ struct ch7xxx_priv *ch7xxx = dvo->dev_priv; -+ struct i2c_adapter *adapter = dvo->i2c_bus; -+ u8 out_buf[2]; -+ u8 in_buf[2]; -+ -+ struct i2c_msg msgs[] = { -+ { -+ .addr = dvo->slave_addr, -+ .flags = 0, -+ .len = 1, -+ .buf = out_buf, -+ }, -+ { -+ .addr = dvo->slave_addr, -+ .flags = I2C_M_RD, -+ .len = 1, -+ .buf = in_buf, -+ } -+ }; -+ -+ out_buf[0] = addr; -+ out_buf[1] = 0; -+ -+ if (i2c_transfer(adapter, msgs, 2) == 2) { -+ *ch = in_buf[0]; -+ return true; -+ } -+ -+ if (!ch7xxx->quiet) { -+ DRM_DEBUG_KMS("Unable to read register 0x%02x from %s:%02x.\n", -+ addr, adapter->name, dvo->slave_addr); -+ } -+ return false; -+} -+ -+/** Writes an 8 bit register */ -+static bool ch7xxx_writeb(struct intel_dvo_device *dvo, int addr, u8 ch) -+{ -+ struct ch7xxx_priv *ch7xxx = dvo->dev_priv; -+ struct i2c_adapter *adapter = dvo->i2c_bus; -+ u8 out_buf[2]; -+ struct i2c_msg msg = { -+ .addr = dvo->slave_addr, -+ .flags = 0, -+ .len = 2, -+ .buf = out_buf, -+ }; -+ -+ out_buf[0] = addr; -+ out_buf[1] = ch; -+ -+ if (i2c_transfer(adapter, &msg, 1) == 1) -+ return true; -+ -+ if (!ch7xxx->quiet) { -+ DRM_DEBUG_KMS("Unable to write register 0x%02x to %s:%d.\n", -+ addr, adapter->name, dvo->slave_addr); -+ } -+ -+ return false; -+} -+ -+static bool ch7xxx_init(struct intel_dvo_device *dvo, -+ struct i2c_adapter *adapter) -+{ -+ /* this will detect the CH7xxx chip on the specified i2c bus */ -+ struct ch7xxx_priv *ch7xxx; -+ u8 vendor, device; -+ char *name, *devid; -+ -+ ch7xxx = kzalloc(sizeof(struct ch7xxx_priv), GFP_KERNEL); -+ if (ch7xxx == NULL) -+ return false; -+ -+ dvo->i2c_bus = adapter; -+ dvo->dev_priv = ch7xxx; -+ ch7xxx->quiet = true; -+ -+ if (!ch7xxx_readb(dvo, CH7xxx_REG_VID, &vendor)) -+ goto out; -+ -+ name = ch7xxx_get_id(vendor); -+ if (!name) { -+ DRM_DEBUG_KMS("ch7xxx not detected; got VID 0x%02x from %s slave %d.\n", -+ vendor, adapter->name, dvo->slave_addr); -+ goto out; -+ } -+ -+ -+ if (!ch7xxx_readb(dvo, CH7xxx_REG_DID, &device)) -+ goto out; -+ -+ devid = ch7xxx_get_did(device); -+ if (!devid) { -+ DRM_DEBUG_KMS("ch7xxx not detected; got DID 0x%02x from %s slave %d.\n", -+ device, adapter->name, dvo->slave_addr); -+ goto out; -+ } -+ -+ ch7xxx->quiet = false; -+ DRM_DEBUG_KMS("Detected %s chipset, vendor/device ID 0x%02x/0x%02x\n", -+ name, vendor, device); -+ return true; -+out: -+ kfree(ch7xxx); -+ return false; -+} -+ -+static enum drm_connector_status ch7xxx_detect(struct intel_dvo_device *dvo) -+{ -+ u8 cdet, orig_pm, pm; -+ -+ ch7xxx_readb(dvo, CH7xxx_PM, &orig_pm); -+ -+ pm = orig_pm; -+ pm &= ~CH7xxx_PM_FPD; -+ pm |= CH7xxx_PM_DVIL | CH7xxx_PM_DVIP; -+ -+ ch7xxx_writeb(dvo, CH7xxx_PM, pm); -+ -+ ch7xxx_readb(dvo, CH7xxx_CONNECTION_DETECT, &cdet); -+ -+ ch7xxx_writeb(dvo, CH7xxx_PM, orig_pm); -+ -+ if (cdet & CH7xxx_CDET_DVI) -+ return connector_status_connected; -+ return connector_status_disconnected; -+} -+ -+static enum drm_mode_status ch7xxx_mode_valid(struct intel_dvo_device *dvo, -+ struct drm_display_mode *mode) -+{ -+ if (mode->clock > 165000) -+ return MODE_CLOCK_HIGH; -+ -+ return MODE_OK; -+} -+ -+static void ch7xxx_mode_set(struct intel_dvo_device *dvo, -+ const struct drm_display_mode *mode, -+ const struct drm_display_mode *adjusted_mode) -+{ -+ u8 tvco, tpcp, tpd, tlpf, idf; -+ -+ if (mode->clock <= 65000) { -+ tvco = 0x23; -+ tpcp = 0x08; -+ tpd = 0x16; -+ tlpf = 0x60; -+ } else { -+ tvco = 0x2d; -+ tpcp = 0x06; -+ tpd = 0x26; -+ tlpf = 0xa0; -+ } -+ -+ ch7xxx_writeb(dvo, CH7xxx_TCTL, 0x00); -+ ch7xxx_writeb(dvo, CH7xxx_TVCO, tvco); -+ ch7xxx_writeb(dvo, CH7xxx_TPCP, tpcp); -+ ch7xxx_writeb(dvo, CH7xxx_TPD, tpd); -+ ch7xxx_writeb(dvo, CH7xxx_TPVT, 0x30); -+ ch7xxx_writeb(dvo, CH7xxx_TLPF, tlpf); -+ ch7xxx_writeb(dvo, CH7xxx_TCT, 0x00); -+ -+ ch7xxx_readb(dvo, CH7xxx_IDF, &idf); -+ -+ idf &= ~(CH7xxx_IDF_HSP | CH7xxx_IDF_VSP); -+ if (mode->flags & DRM_MODE_FLAG_PHSYNC) -+ idf |= CH7xxx_IDF_HSP; -+ -+ if (mode->flags & DRM_MODE_FLAG_PVSYNC) -+ idf |= CH7xxx_IDF_VSP; -+ -+ ch7xxx_writeb(dvo, CH7xxx_IDF, idf); -+} -+ -+/* set the CH7xxx power state */ -+static void ch7xxx_dpms(struct intel_dvo_device *dvo, bool enable) -+{ -+ if (enable) -+ ch7xxx_writeb(dvo, CH7xxx_PM, CH7xxx_PM_DVIL | CH7xxx_PM_DVIP); -+ else -+ ch7xxx_writeb(dvo, CH7xxx_PM, CH7xxx_PM_FPD); -+} -+ -+static bool ch7xxx_get_hw_state(struct intel_dvo_device *dvo) -+{ -+ u8 val; -+ -+ ch7xxx_readb(dvo, CH7xxx_PM, &val); -+ -+ if (val & (CH7xxx_PM_DVIL | CH7xxx_PM_DVIP)) -+ return true; -+ else -+ return false; -+} -+ -+static void ch7xxx_dump_regs(struct intel_dvo_device *dvo) -+{ -+ int i; -+ -+ for (i = 0; i < CH7xxx_NUM_REGS; i++) { -+ u8 val; -+ if ((i % 8) == 0) -+ DRM_DEBUG_KMS("\n %02X: ", i); -+ ch7xxx_readb(dvo, i, &val); -+ DRM_DEBUG_KMS("%02X ", val); -+ } -+} -+ -+static void ch7xxx_destroy(struct intel_dvo_device *dvo) -+{ -+ struct ch7xxx_priv *ch7xxx = dvo->dev_priv; -+ -+ if (ch7xxx) { -+ kfree(ch7xxx); -+ dvo->dev_priv = NULL; -+ } -+} -+ -+const struct intel_dvo_dev_ops ch7xxx_ops = { -+ .init = ch7xxx_init, -+ .detect = ch7xxx_detect, -+ .mode_valid = ch7xxx_mode_valid, -+ .mode_set = ch7xxx_mode_set, -+ .dpms = ch7xxx_dpms, -+ .get_hw_state = ch7xxx_get_hw_state, -+ .dump_regs = ch7xxx_dump_regs, -+ .destroy = ch7xxx_destroy, -+}; -diff --git a/drivers/gpu/drm/i915_legacy/dvo_ivch.c b/drivers/gpu/drm/i915_legacy/dvo_ivch.c -new file mode 100644 -index 000000000000..24278cc49090 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/dvo_ivch.c -@@ -0,0 +1,502 @@ -+/* -+ * Copyright © 2006 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eric Anholt -+ * Thomas Richter -+ * -+ * Minor modifications (Dithering enable): -+ * Thomas Richter -+ * -+ */ -+ -+#include "dvo.h" -+ -+/* -+ * register definitions for the i82807aa. -+ * -+ * Documentation on this chipset can be found in datasheet #29069001 at -+ * intel.com. -+ */ -+ -+/* -+ * VCH Revision & GMBus Base Addr -+ */ -+#define VR00 0x00 -+# define VR00_BASE_ADDRESS_MASK 0x007f -+ -+/* -+ * Functionality Enable -+ */ -+#define VR01 0x01 -+ -+/* -+ * Enable the panel fitter -+ */ -+# define VR01_PANEL_FIT_ENABLE (1 << 3) -+/* -+ * Enables the LCD display. -+ * -+ * This must not be set while VR01_DVO_BYPASS_ENABLE is set. -+ */ -+# define VR01_LCD_ENABLE (1 << 2) -+/* Enables the DVO repeater. */ -+# define VR01_DVO_BYPASS_ENABLE (1 << 1) -+/* Enables the DVO clock */ -+# define VR01_DVO_ENABLE (1 << 0) -+/* Enable dithering for 18bpp panels. Not documented. */ -+# define VR01_DITHER_ENABLE (1 << 4) -+ -+/* -+ * LCD Interface Format -+ */ -+#define VR10 0x10 -+/* Enables LVDS output instead of CMOS */ -+# define VR10_LVDS_ENABLE (1 << 4) -+/* Enables 18-bit LVDS output. */ -+# define VR10_INTERFACE_1X18 (0 << 2) -+/* Enables 24-bit LVDS or CMOS output */ -+# define VR10_INTERFACE_1X24 (1 << 2) -+/* Enables 2x18-bit LVDS or CMOS output. */ -+# define VR10_INTERFACE_2X18 (2 << 2) -+/* Enables 2x24-bit LVDS output */ -+# define VR10_INTERFACE_2X24 (3 << 2) -+/* Mask that defines the depth of the pipeline */ -+# define VR10_INTERFACE_DEPTH_MASK (3 << 2) -+ -+/* -+ * VR20 LCD Horizontal Display Size -+ */ -+#define VR20 0x20 -+ -+/* -+ * LCD Vertical Display Size -+ */ -+#define VR21 0x21 -+ -+/* -+ * Panel power down status -+ */ -+#define VR30 0x30 -+/* Read only bit indicating that the panel is not in a safe poweroff state. */ -+# define VR30_PANEL_ON (1 << 15) -+ -+#define VR40 0x40 -+# define VR40_STALL_ENABLE (1 << 13) -+# define VR40_VERTICAL_INTERP_ENABLE (1 << 12) -+# define VR40_ENHANCED_PANEL_FITTING (1 << 11) -+# define VR40_HORIZONTAL_INTERP_ENABLE (1 << 10) -+# define VR40_AUTO_RATIO_ENABLE (1 << 9) -+# define VR40_CLOCK_GATING_ENABLE (1 << 8) -+ -+/* -+ * Panel Fitting Vertical Ratio -+ * (((image_height - 1) << 16) / ((panel_height - 1))) >> 2 -+ */ -+#define VR41 0x41 -+ -+/* -+ * Panel Fitting Horizontal Ratio -+ * (((image_width - 1) << 16) / ((panel_width - 1))) >> 2 -+ */ -+#define VR42 0x42 -+ -+/* -+ * Horizontal Image Size -+ */ -+#define VR43 0x43 -+ -+/* VR80 GPIO 0 -+ */ -+#define VR80 0x80 -+#define VR81 0x81 -+#define VR82 0x82 -+#define VR83 0x83 -+#define VR84 0x84 -+#define VR85 0x85 -+#define VR86 0x86 -+#define VR87 0x87 -+ -+/* VR88 GPIO 8 -+ */ -+#define VR88 0x88 -+ -+/* Graphics BIOS scratch 0 -+ */ -+#define VR8E 0x8E -+# define VR8E_PANEL_TYPE_MASK (0xf << 0) -+# define VR8E_PANEL_INTERFACE_CMOS (0 << 4) -+# define VR8E_PANEL_INTERFACE_LVDS (1 << 4) -+# define VR8E_FORCE_DEFAULT_PANEL (1 << 5) -+ -+/* Graphics BIOS scratch 1 -+ */ -+#define VR8F 0x8F -+# define VR8F_VCH_PRESENT (1 << 0) -+# define VR8F_DISPLAY_CONN (1 << 1) -+# define VR8F_POWER_MASK (0x3c) -+# define VR8F_POWER_POS (2) -+ -+/* Some Bios implementations do not restore the DVO state upon -+ * resume from standby. Thus, this driver has to handle it -+ * instead. The following list contains all registers that -+ * require saving. -+ */ -+static const u16 backup_addresses[] = { -+ 0x11, 0x12, -+ 0x18, 0x19, 0x1a, 0x1f, -+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, -+ 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, -+ 0x8e, 0x8f, -+ 0x10 /* this must come last */ -+}; -+ -+ -+struct ivch_priv { -+ bool quiet; -+ -+ u16 width, height; -+ -+ /* Register backup */ -+ -+ u16 reg_backup[ARRAY_SIZE(backup_addresses)]; -+}; -+ -+ -+static void ivch_dump_regs(struct intel_dvo_device *dvo); -+/* -+ * Reads a register on the ivch. -+ * -+ * Each of the 256 registers are 16 bits long. -+ */ -+static bool ivch_read(struct intel_dvo_device *dvo, int addr, u16 *data) -+{ -+ struct ivch_priv *priv = dvo->dev_priv; -+ struct i2c_adapter *adapter = dvo->i2c_bus; -+ u8 out_buf[1]; -+ u8 in_buf[2]; -+ -+ struct i2c_msg msgs[] = { -+ { -+ .addr = dvo->slave_addr, -+ .flags = I2C_M_RD, -+ .len = 0, -+ }, -+ { -+ .addr = 0, -+ .flags = I2C_M_NOSTART, -+ .len = 1, -+ .buf = out_buf, -+ }, -+ { -+ .addr = dvo->slave_addr, -+ .flags = I2C_M_RD | I2C_M_NOSTART, -+ .len = 2, -+ .buf = in_buf, -+ } -+ }; -+ -+ out_buf[0] = addr; -+ -+ if (i2c_transfer(adapter, msgs, 3) == 3) { -+ *data = (in_buf[1] << 8) | in_buf[0]; -+ return true; -+ } -+ -+ if (!priv->quiet) { -+ DRM_DEBUG_KMS("Unable to read register 0x%02x from " -+ "%s:%02x.\n", -+ addr, adapter->name, dvo->slave_addr); -+ } -+ return false; -+} -+ -+/* Writes a 16-bit register on the ivch */ -+static bool ivch_write(struct intel_dvo_device *dvo, int addr, u16 data) -+{ -+ struct ivch_priv *priv = dvo->dev_priv; -+ struct i2c_adapter *adapter = dvo->i2c_bus; -+ u8 out_buf[3]; -+ struct i2c_msg msg = { -+ .addr = dvo->slave_addr, -+ .flags = 0, -+ .len = 3, -+ .buf = out_buf, -+ }; -+ -+ out_buf[0] = addr; -+ out_buf[1] = data & 0xff; -+ out_buf[2] = data >> 8; -+ -+ if (i2c_transfer(adapter, &msg, 1) == 1) -+ return true; -+ -+ if (!priv->quiet) { -+ DRM_DEBUG_KMS("Unable to write register 0x%02x to %s:%d.\n", -+ addr, adapter->name, dvo->slave_addr); -+ } -+ -+ return false; -+} -+ -+/* Probes the given bus and slave address for an ivch */ -+static bool ivch_init(struct intel_dvo_device *dvo, -+ struct i2c_adapter *adapter) -+{ -+ struct ivch_priv *priv; -+ u16 temp; -+ int i; -+ -+ priv = kzalloc(sizeof(struct ivch_priv), GFP_KERNEL); -+ if (priv == NULL) -+ return false; -+ -+ dvo->i2c_bus = adapter; -+ dvo->dev_priv = priv; -+ priv->quiet = true; -+ -+ if (!ivch_read(dvo, VR00, &temp)) -+ goto out; -+ priv->quiet = false; -+ -+ /* Since the identification bits are probably zeroes, which doesn't seem -+ * very unique, check that the value in the base address field matches -+ * the address it's responding on. -+ */ -+ if ((temp & VR00_BASE_ADDRESS_MASK) != dvo->slave_addr) { -+ DRM_DEBUG_KMS("ivch detect failed due to address mismatch " -+ "(%d vs %d)\n", -+ (temp & VR00_BASE_ADDRESS_MASK), dvo->slave_addr); -+ goto out; -+ } -+ -+ ivch_read(dvo, VR20, &priv->width); -+ ivch_read(dvo, VR21, &priv->height); -+ -+ /* Make a backup of the registers to be able to restore them -+ * upon suspend. -+ */ -+ for (i = 0; i < ARRAY_SIZE(backup_addresses); i++) -+ ivch_read(dvo, backup_addresses[i], priv->reg_backup + i); -+ -+ ivch_dump_regs(dvo); -+ -+ return true; -+ -+out: -+ kfree(priv); -+ return false; -+} -+ -+static enum drm_connector_status ivch_detect(struct intel_dvo_device *dvo) -+{ -+ return connector_status_connected; -+} -+ -+static enum drm_mode_status ivch_mode_valid(struct intel_dvo_device *dvo, -+ struct drm_display_mode *mode) -+{ -+ if (mode->clock > 112000) -+ return MODE_CLOCK_HIGH; -+ -+ return MODE_OK; -+} -+ -+/* Restore the DVO registers after a resume -+ * from RAM. Registers have been saved during -+ * the initialization. -+ */ -+static void ivch_reset(struct intel_dvo_device *dvo) -+{ -+ struct ivch_priv *priv = dvo->dev_priv; -+ int i; -+ -+ DRM_DEBUG_KMS("Resetting the IVCH registers\n"); -+ -+ ivch_write(dvo, VR10, 0x0000); -+ -+ for (i = 0; i < ARRAY_SIZE(backup_addresses); i++) -+ ivch_write(dvo, backup_addresses[i], priv->reg_backup[i]); -+} -+ -+/* Sets the power state of the panel connected to the ivch */ -+static void ivch_dpms(struct intel_dvo_device *dvo, bool enable) -+{ -+ int i; -+ u16 vr01, vr30, backlight; -+ -+ ivch_reset(dvo); -+ -+ /* Set the new power state of the panel. */ -+ if (!ivch_read(dvo, VR01, &vr01)) -+ return; -+ -+ if (enable) -+ backlight = 1; -+ else -+ backlight = 0; -+ -+ ivch_write(dvo, VR80, backlight); -+ -+ if (enable) -+ vr01 |= VR01_LCD_ENABLE | VR01_DVO_ENABLE; -+ else -+ vr01 &= ~(VR01_LCD_ENABLE | VR01_DVO_ENABLE); -+ -+ ivch_write(dvo, VR01, vr01); -+ -+ /* Wait for the panel to make its state transition */ -+ for (i = 0; i < 100; i++) { -+ if (!ivch_read(dvo, VR30, &vr30)) -+ break; -+ -+ if (((vr30 & VR30_PANEL_ON) != 0) == enable) -+ break; -+ udelay(1000); -+ } -+ /* wait some more; vch may fail to resync sometimes without this */ -+ udelay(16 * 1000); -+} -+ -+static bool ivch_get_hw_state(struct intel_dvo_device *dvo) -+{ -+ u16 vr01; -+ -+ ivch_reset(dvo); -+ -+ /* Set the new power state of the panel. */ -+ if (!ivch_read(dvo, VR01, &vr01)) -+ return false; -+ -+ if (vr01 & VR01_LCD_ENABLE) -+ return true; -+ else -+ return false; -+} -+ -+static void ivch_mode_set(struct intel_dvo_device *dvo, -+ const struct drm_display_mode *mode, -+ const struct drm_display_mode *adjusted_mode) -+{ -+ struct ivch_priv *priv = dvo->dev_priv; -+ u16 vr40 = 0; -+ u16 vr01 = 0; -+ u16 vr10; -+ -+ ivch_reset(dvo); -+ -+ vr10 = priv->reg_backup[ARRAY_SIZE(backup_addresses) - 1]; -+ -+ /* Enable dithering for 18 bpp pipelines */ -+ vr10 &= VR10_INTERFACE_DEPTH_MASK; -+ if (vr10 == VR10_INTERFACE_2X18 || vr10 == VR10_INTERFACE_1X18) -+ vr01 = VR01_DITHER_ENABLE; -+ -+ vr40 = (VR40_STALL_ENABLE | VR40_VERTICAL_INTERP_ENABLE | -+ VR40_HORIZONTAL_INTERP_ENABLE); -+ -+ if (mode->hdisplay != adjusted_mode->crtc_hdisplay || -+ mode->vdisplay != adjusted_mode->crtc_vdisplay) { -+ u16 x_ratio, y_ratio; -+ -+ vr01 |= VR01_PANEL_FIT_ENABLE; -+ vr40 |= VR40_CLOCK_GATING_ENABLE; -+ x_ratio = (((mode->hdisplay - 1) << 16) / -+ (adjusted_mode->crtc_hdisplay - 1)) >> 2; -+ y_ratio = (((mode->vdisplay - 1) << 16) / -+ (adjusted_mode->crtc_vdisplay - 1)) >> 2; -+ ivch_write(dvo, VR42, x_ratio); -+ ivch_write(dvo, VR41, y_ratio); -+ } else { -+ vr01 &= ~VR01_PANEL_FIT_ENABLE; -+ vr40 &= ~VR40_CLOCK_GATING_ENABLE; -+ } -+ vr40 &= ~VR40_AUTO_RATIO_ENABLE; -+ -+ ivch_write(dvo, VR01, vr01); -+ ivch_write(dvo, VR40, vr40); -+} -+ -+static void ivch_dump_regs(struct intel_dvo_device *dvo) -+{ -+ u16 val; -+ -+ ivch_read(dvo, VR00, &val); -+ DRM_DEBUG_KMS("VR00: 0x%04x\n", val); -+ ivch_read(dvo, VR01, &val); -+ DRM_DEBUG_KMS("VR01: 0x%04x\n", val); -+ ivch_read(dvo, VR10, &val); -+ DRM_DEBUG_KMS("VR10: 0x%04x\n", val); -+ ivch_read(dvo, VR30, &val); -+ DRM_DEBUG_KMS("VR30: 0x%04x\n", val); -+ ivch_read(dvo, VR40, &val); -+ DRM_DEBUG_KMS("VR40: 0x%04x\n", val); -+ -+ /* GPIO registers */ -+ ivch_read(dvo, VR80, &val); -+ DRM_DEBUG_KMS("VR80: 0x%04x\n", val); -+ ivch_read(dvo, VR81, &val); -+ DRM_DEBUG_KMS("VR81: 0x%04x\n", val); -+ ivch_read(dvo, VR82, &val); -+ DRM_DEBUG_KMS("VR82: 0x%04x\n", val); -+ ivch_read(dvo, VR83, &val); -+ DRM_DEBUG_KMS("VR83: 0x%04x\n", val); -+ ivch_read(dvo, VR84, &val); -+ DRM_DEBUG_KMS("VR84: 0x%04x\n", val); -+ ivch_read(dvo, VR85, &val); -+ DRM_DEBUG_KMS("VR85: 0x%04x\n", val); -+ ivch_read(dvo, VR86, &val); -+ DRM_DEBUG_KMS("VR86: 0x%04x\n", val); -+ ivch_read(dvo, VR87, &val); -+ DRM_DEBUG_KMS("VR87: 0x%04x\n", val); -+ ivch_read(dvo, VR88, &val); -+ DRM_DEBUG_KMS("VR88: 0x%04x\n", val); -+ -+ /* Scratch register 0 - AIM Panel type */ -+ ivch_read(dvo, VR8E, &val); -+ DRM_DEBUG_KMS("VR8E: 0x%04x\n", val); -+ -+ /* Scratch register 1 - Status register */ -+ ivch_read(dvo, VR8F, &val); -+ DRM_DEBUG_KMS("VR8F: 0x%04x\n", val); -+} -+ -+static void ivch_destroy(struct intel_dvo_device *dvo) -+{ -+ struct ivch_priv *priv = dvo->dev_priv; -+ -+ if (priv) { -+ kfree(priv); -+ dvo->dev_priv = NULL; -+ } -+} -+ -+const struct intel_dvo_dev_ops ivch_ops = { -+ .init = ivch_init, -+ .dpms = ivch_dpms, -+ .get_hw_state = ivch_get_hw_state, -+ .mode_valid = ivch_mode_valid, -+ .mode_set = ivch_mode_set, -+ .detect = ivch_detect, -+ .dump_regs = ivch_dump_regs, -+ .destroy = ivch_destroy, -+}; -diff --git a/drivers/gpu/drm/i915_legacy/dvo_ns2501.c b/drivers/gpu/drm/i915_legacy/dvo_ns2501.c -new file mode 100644 -index 000000000000..c584e01dc8dc ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/dvo_ns2501.c -@@ -0,0 +1,709 @@ -+/* -+ * -+ * Copyright (c) 2012 Gilles Dartiguelongue, Thomas Richter -+ * -+ * All Rights Reserved. -+ * -+ * 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, sub license, 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 (including the -+ * next paragraph) 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 NON-INFRINGEMENT. -+ * IN NO EVENT SHALL THE AUTHOR 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. -+ * -+ */ -+ -+#include "dvo.h" -+#include "i915_reg.h" -+#include "i915_drv.h" -+ -+#define NS2501_VID 0x1305 -+#define NS2501_DID 0x6726 -+ -+#define NS2501_VID_LO 0x00 -+#define NS2501_VID_HI 0x01 -+#define NS2501_DID_LO 0x02 -+#define NS2501_DID_HI 0x03 -+#define NS2501_REV 0x04 -+#define NS2501_RSVD 0x05 -+#define NS2501_FREQ_LO 0x06 -+#define NS2501_FREQ_HI 0x07 -+ -+#define NS2501_REG8 0x08 -+#define NS2501_8_VEN (1<<5) -+#define NS2501_8_HEN (1<<4) -+#define NS2501_8_DSEL (1<<3) -+#define NS2501_8_BPAS (1<<2) -+#define NS2501_8_RSVD (1<<1) -+#define NS2501_8_PD (1<<0) -+ -+#define NS2501_REG9 0x09 -+#define NS2501_9_VLOW (1<<7) -+#define NS2501_9_MSEL_MASK (0x7<<4) -+#define NS2501_9_TSEL (1<<3) -+#define NS2501_9_RSEN (1<<2) -+#define NS2501_9_RSVD (1<<1) -+#define NS2501_9_MDI (1<<0) -+ -+#define NS2501_REGC 0x0c -+ -+/* -+ * The following registers are not part of the official datasheet -+ * and are the result of reverse engineering. -+ */ -+ -+/* -+ * Register c0 controls how the DVO synchronizes with -+ * its input. -+ */ -+#define NS2501_REGC0 0xc0 -+#define NS2501_C0_ENABLE (1<<0) /* enable the DVO sync in general */ -+#define NS2501_C0_HSYNC (1<<1) /* synchronize horizontal with input */ -+#define NS2501_C0_VSYNC (1<<2) /* synchronize vertical with input */ -+#define NS2501_C0_RESET (1<<7) /* reset the synchronization flip/flops */ -+ -+/* -+ * Register 41 is somehow related to the sync register and sync -+ * configuration. It should be 0x32 whenever regC0 is 0x05 (hsync off) -+ * and 0x00 otherwise. -+ */ -+#define NS2501_REG41 0x41 -+ -+/* -+ * this register controls the dithering of the DVO -+ * One bit enables it, the other define the dithering depth. -+ * The higher the value, the lower the dithering depth. -+ */ -+#define NS2501_F9_REG 0xf9 -+#define NS2501_F9_ENABLE (1<<0) /* if set, dithering is enabled */ -+#define NS2501_F9_DITHER_MASK (0x7f<<1) /* controls the dither depth */ -+#define NS2501_F9_DITHER_SHIFT 1 /* shifts the dither mask */ -+ -+/* -+ * PLL configuration register. This is a pair of registers, -+ * one single byte register at 1B, and a pair at 1C,1D. -+ * These registers are counters/dividers. -+ */ -+#define NS2501_REG1B 0x1b /* one byte PLL control register */ -+#define NS2501_REG1C 0x1c /* low-part of the second register */ -+#define NS2501_REG1D 0x1d /* high-part of the second register */ -+ -+/* -+ * Scaler control registers. Horizontal at b8,b9, -+ * vertical at 10,11. The scale factor is computed as -+ * 2^16/control-value. The low-byte comes first. -+ */ -+#define NS2501_REG10 0x10 /* low-byte vertical scaler */ -+#define NS2501_REG11 0x11 /* high-byte vertical scaler */ -+#define NS2501_REGB8 0xb8 /* low-byte horizontal scaler */ -+#define NS2501_REGB9 0xb9 /* high-byte horizontal scaler */ -+ -+/* -+ * Display window definition. This consists of four registers -+ * per dimension. One register pair defines the start of the -+ * display, one the end. -+ * As far as I understand, this defines the window within which -+ * the scaler samples the input. -+ */ -+#define NS2501_REGC1 0xc1 /* low-byte horizontal display start */ -+#define NS2501_REGC2 0xc2 /* high-byte horizontal display start */ -+#define NS2501_REGC3 0xc3 /* low-byte horizontal display stop */ -+#define NS2501_REGC4 0xc4 /* high-byte horizontal display stop */ -+#define NS2501_REGC5 0xc5 /* low-byte vertical display start */ -+#define NS2501_REGC6 0xc6 /* high-byte vertical display start */ -+#define NS2501_REGC7 0xc7 /* low-byte vertical display stop */ -+#define NS2501_REGC8 0xc8 /* high-byte vertical display stop */ -+ -+/* -+ * The following register pair seems to define the start of -+ * the vertical sync. If automatic syncing is enabled, and the -+ * register value defines a sync pulse that is later than the -+ * incoming sync, then the register value is ignored and the -+ * external hsync triggers the synchronization. -+ */ -+#define NS2501_REG80 0x80 /* low-byte vsync-start */ -+#define NS2501_REG81 0x81 /* high-byte vsync-start */ -+ -+/* -+ * The following register pair seems to define the total number -+ * of lines created at the output side of the scaler. -+ * This is again a low-high register pair. -+ */ -+#define NS2501_REG82 0x82 /* output display height, low byte */ -+#define NS2501_REG83 0x83 /* output display height, high byte */ -+ -+/* -+ * The following registers define the end of the front-porch -+ * in horizontal and vertical position and hence allow to shift -+ * the image left/right or up/down. -+ */ -+#define NS2501_REG98 0x98 /* horizontal start of display + 256, low */ -+#define NS2501_REG99 0x99 /* horizontal start of display + 256, high */ -+#define NS2501_REG8E 0x8e /* vertical start of the display, low byte */ -+#define NS2501_REG8F 0x8f /* vertical start of the display, high byte */ -+ -+/* -+ * The following register pair control the function of the -+ * backlight and the DVO output. To enable the corresponding -+ * function, the corresponding bit must be set in both registers. -+ */ -+#define NS2501_REG34 0x34 /* DVO enable functions, first register */ -+#define NS2501_REG35 0x35 /* DVO enable functions, second register */ -+#define NS2501_34_ENABLE_OUTPUT (1<<0) /* enable DVO output */ -+#define NS2501_34_ENABLE_BACKLIGHT (1<<1) /* enable backlight */ -+ -+/* -+ * Registers 9C and 9D define the vertical output offset -+ * of the visible region. -+ */ -+#define NS2501_REG9C 0x9c -+#define NS2501_REG9D 0x9d -+ -+/* -+ * The register 9F defines the dithering. This requires the -+ * scaler to be ON. Bit 0 enables dithering, the remaining -+ * bits control the depth of the dither. The higher the value, -+ * the LOWER the dithering amplitude. A good value seems to be -+ * 15 (total register value). -+ */ -+#define NS2501_REGF9 0xf9 -+#define NS2501_F9_ENABLE_DITHER (1<<0) /* enable dithering */ -+#define NS2501_F9_DITHER_MASK (0x7f<<1) /* dither masking */ -+#define NS2501_F9_DITHER_SHIFT 1 /* upshift of the dither mask */ -+ -+enum { -+ MODE_640x480, -+ MODE_800x600, -+ MODE_1024x768, -+}; -+ -+struct ns2501_reg { -+ u8 offset; -+ u8 value; -+}; -+ -+/* -+ * The following structure keeps the complete configuration of -+ * the DVO, given a specific output configuration. -+ * This is pretty much guess-work from reverse-engineering, so -+ * read all this with a grain of salt. -+ */ -+struct ns2501_configuration { -+ u8 sync; /* configuration of the C0 register */ -+ u8 conf; /* configuration register 8 */ -+ u8 syncb; /* configuration register 41 */ -+ u8 dither; /* configuration of the dithering */ -+ u8 pll_a; /* PLL configuration, register A, 1B */ -+ u16 pll_b; /* PLL configuration, register B, 1C/1D */ -+ u16 hstart; /* horizontal start, registers C1/C2 */ -+ u16 hstop; /* horizontal total, registers C3/C4 */ -+ u16 vstart; /* vertical start, registers C5/C6 */ -+ u16 vstop; /* vertical total, registers C7/C8 */ -+ u16 vsync; /* manual vertical sync start, 80/81 */ -+ u16 vtotal; /* number of lines generated, 82/83 */ -+ u16 hpos; /* horizontal position + 256, 98/99 */ -+ u16 vpos; /* vertical position, 8e/8f */ -+ u16 voffs; /* vertical output offset, 9c/9d */ -+ u16 hscale; /* horizontal scaling factor, b8/b9 */ -+ u16 vscale; /* vertical scaling factor, 10/11 */ -+}; -+ -+/* -+ * DVO configuration values, partially based on what the BIOS -+ * of the Fujitsu Lifebook S6010 writes into registers, -+ * partially found by manual tweaking. These configurations assume -+ * a 1024x768 panel. -+ */ -+static const struct ns2501_configuration ns2501_modes[] = { -+ [MODE_640x480] = { -+ .sync = NS2501_C0_ENABLE | NS2501_C0_VSYNC, -+ .conf = NS2501_8_VEN | NS2501_8_HEN | NS2501_8_PD, -+ .syncb = 0x32, -+ .dither = 0x0f, -+ .pll_a = 17, -+ .pll_b = 852, -+ .hstart = 144, -+ .hstop = 783, -+ .vstart = 22, -+ .vstop = 514, -+ .vsync = 2047, /* actually, ignored with this config */ -+ .vtotal = 1341, -+ .hpos = 0, -+ .vpos = 16, -+ .voffs = 36, -+ .hscale = 40960, -+ .vscale = 40960 -+ }, -+ [MODE_800x600] = { -+ .sync = NS2501_C0_ENABLE | -+ NS2501_C0_HSYNC | NS2501_C0_VSYNC, -+ .conf = NS2501_8_VEN | NS2501_8_HEN | NS2501_8_PD, -+ .syncb = 0x00, -+ .dither = 0x0f, -+ .pll_a = 25, -+ .pll_b = 612, -+ .hstart = 215, -+ .hstop = 1016, -+ .vstart = 26, -+ .vstop = 627, -+ .vsync = 807, -+ .vtotal = 1341, -+ .hpos = 0, -+ .vpos = 4, -+ .voffs = 35, -+ .hscale = 51248, -+ .vscale = 51232 -+ }, -+ [MODE_1024x768] = { -+ .sync = NS2501_C0_ENABLE | NS2501_C0_VSYNC, -+ .conf = NS2501_8_VEN | NS2501_8_HEN | NS2501_8_PD, -+ .syncb = 0x32, -+ .dither = 0x0f, -+ .pll_a = 11, -+ .pll_b = 1350, -+ .hstart = 276, -+ .hstop = 1299, -+ .vstart = 15, -+ .vstop = 1056, -+ .vsync = 2047, -+ .vtotal = 1341, -+ .hpos = 0, -+ .vpos = 7, -+ .voffs = 27, -+ .hscale = 65535, -+ .vscale = 65535 -+ } -+}; -+ -+/* -+ * Other configuration values left by the BIOS of the -+ * Fujitsu S6010 in the DVO control registers. Their -+ * value does not depend on the BIOS and their meaning -+ * is unknown. -+ */ -+ -+static const struct ns2501_reg mode_agnostic_values[] = { -+ /* 08 is mode specific */ -+ [0] = { .offset = 0x0a, .value = 0x81, }, -+ /* 10,11 are part of the mode specific configuration */ -+ [1] = { .offset = 0x12, .value = 0x02, }, -+ [2] = { .offset = 0x18, .value = 0x07, }, -+ [3] = { .offset = 0x19, .value = 0x00, }, -+ [4] = { .offset = 0x1a, .value = 0x00, }, /* PLL?, ignored */ -+ /* 1b,1c,1d are part of the mode specific configuration */ -+ [5] = { .offset = 0x1e, .value = 0x02, }, -+ [6] = { .offset = 0x1f, .value = 0x40, }, -+ [7] = { .offset = 0x20, .value = 0x00, }, -+ [8] = { .offset = 0x21, .value = 0x00, }, -+ [9] = { .offset = 0x22, .value = 0x00, }, -+ [10] = { .offset = 0x23, .value = 0x00, }, -+ [11] = { .offset = 0x24, .value = 0x00, }, -+ [12] = { .offset = 0x25, .value = 0x00, }, -+ [13] = { .offset = 0x26, .value = 0x00, }, -+ [14] = { .offset = 0x27, .value = 0x00, }, -+ [15] = { .offset = 0x7e, .value = 0x18, }, -+ /* 80-84 are part of the mode-specific configuration */ -+ [16] = { .offset = 0x84, .value = 0x00, }, -+ [17] = { .offset = 0x85, .value = 0x00, }, -+ [18] = { .offset = 0x86, .value = 0x00, }, -+ [19] = { .offset = 0x87, .value = 0x00, }, -+ [20] = { .offset = 0x88, .value = 0x00, }, -+ [21] = { .offset = 0x89, .value = 0x00, }, -+ [22] = { .offset = 0x8a, .value = 0x00, }, -+ [23] = { .offset = 0x8b, .value = 0x00, }, -+ [24] = { .offset = 0x8c, .value = 0x10, }, -+ [25] = { .offset = 0x8d, .value = 0x02, }, -+ /* 8e,8f are part of the mode-specific configuration */ -+ [26] = { .offset = 0x90, .value = 0xff, }, -+ [27] = { .offset = 0x91, .value = 0x07, }, -+ [28] = { .offset = 0x92, .value = 0xa0, }, -+ [29] = { .offset = 0x93, .value = 0x02, }, -+ [30] = { .offset = 0x94, .value = 0x00, }, -+ [31] = { .offset = 0x95, .value = 0x00, }, -+ [32] = { .offset = 0x96, .value = 0x05, }, -+ [33] = { .offset = 0x97, .value = 0x00, }, -+ /* 98,99 are part of the mode-specific configuration */ -+ [34] = { .offset = 0x9a, .value = 0x88, }, -+ [35] = { .offset = 0x9b, .value = 0x00, }, -+ /* 9c,9d are part of the mode-specific configuration */ -+ [36] = { .offset = 0x9e, .value = 0x25, }, -+ [37] = { .offset = 0x9f, .value = 0x03, }, -+ [38] = { .offset = 0xa0, .value = 0x28, }, -+ [39] = { .offset = 0xa1, .value = 0x01, }, -+ [40] = { .offset = 0xa2, .value = 0x28, }, -+ [41] = { .offset = 0xa3, .value = 0x05, }, -+ /* register 0xa4 is mode specific, but 0x80..0x84 works always */ -+ [42] = { .offset = 0xa4, .value = 0x84, }, -+ [43] = { .offset = 0xa5, .value = 0x00, }, -+ [44] = { .offset = 0xa6, .value = 0x00, }, -+ [45] = { .offset = 0xa7, .value = 0x00, }, -+ [46] = { .offset = 0xa8, .value = 0x00, }, -+ /* 0xa9 to 0xab are mode specific, but have no visible effect */ -+ [47] = { .offset = 0xa9, .value = 0x04, }, -+ [48] = { .offset = 0xaa, .value = 0x70, }, -+ [49] = { .offset = 0xab, .value = 0x4f, }, -+ [50] = { .offset = 0xac, .value = 0x00, }, -+ [51] = { .offset = 0xad, .value = 0x00, }, -+ [52] = { .offset = 0xb6, .value = 0x09, }, -+ [53] = { .offset = 0xb7, .value = 0x03, }, -+ /* b8,b9 are part of the mode-specific configuration */ -+ [54] = { .offset = 0xba, .value = 0x00, }, -+ [55] = { .offset = 0xbb, .value = 0x20, }, -+ [56] = { .offset = 0xf3, .value = 0x90, }, -+ [57] = { .offset = 0xf4, .value = 0x00, }, -+ [58] = { .offset = 0xf7, .value = 0x88, }, -+ /* f8 is mode specific, but the value does not matter */ -+ [59] = { .offset = 0xf8, .value = 0x0a, }, -+ [60] = { .offset = 0xf9, .value = 0x00, } -+}; -+ -+static const struct ns2501_reg regs_init[] = { -+ [0] = { .offset = 0x35, .value = 0xff, }, -+ [1] = { .offset = 0x34, .value = 0x00, }, -+ [2] = { .offset = 0x08, .value = 0x30, }, -+}; -+ -+struct ns2501_priv { -+ bool quiet; -+ const struct ns2501_configuration *conf; -+}; -+ -+#define NSPTR(d) ((NS2501Ptr)(d->DriverPrivate.ptr)) -+ -+/* -+** Read a register from the ns2501. -+** Returns true if successful, false otherwise. -+** If it returns false, it might be wise to enable the -+** DVO with the above function. -+*/ -+static bool ns2501_readb(struct intel_dvo_device *dvo, int addr, u8 *ch) -+{ -+ struct ns2501_priv *ns = dvo->dev_priv; -+ struct i2c_adapter *adapter = dvo->i2c_bus; -+ u8 out_buf[2]; -+ u8 in_buf[2]; -+ -+ struct i2c_msg msgs[] = { -+ { -+ .addr = dvo->slave_addr, -+ .flags = 0, -+ .len = 1, -+ .buf = out_buf, -+ }, -+ { -+ .addr = dvo->slave_addr, -+ .flags = I2C_M_RD, -+ .len = 1, -+ .buf = in_buf, -+ } -+ }; -+ -+ out_buf[0] = addr; -+ out_buf[1] = 0; -+ -+ if (i2c_transfer(adapter, msgs, 2) == 2) { -+ *ch = in_buf[0]; -+ return true; -+ } -+ -+ if (!ns->quiet) { -+ DRM_DEBUG_KMS -+ ("Unable to read register 0x%02x from %s:0x%02x.\n", addr, -+ adapter->name, dvo->slave_addr); -+ } -+ -+ return false; -+} -+ -+/* -+** Write a register to the ns2501. -+** Returns true if successful, false otherwise. -+** If it returns false, it might be wise to enable the -+** DVO with the above function. -+*/ -+static bool ns2501_writeb(struct intel_dvo_device *dvo, int addr, u8 ch) -+{ -+ struct ns2501_priv *ns = dvo->dev_priv; -+ struct i2c_adapter *adapter = dvo->i2c_bus; -+ u8 out_buf[2]; -+ -+ struct i2c_msg msg = { -+ .addr = dvo->slave_addr, -+ .flags = 0, -+ .len = 2, -+ .buf = out_buf, -+ }; -+ -+ out_buf[0] = addr; -+ out_buf[1] = ch; -+ -+ if (i2c_transfer(adapter, &msg, 1) == 1) { -+ return true; -+ } -+ -+ if (!ns->quiet) { -+ DRM_DEBUG_KMS("Unable to write register 0x%02x to %s:%d\n", -+ addr, adapter->name, dvo->slave_addr); -+ } -+ -+ return false; -+} -+ -+/* National Semiconductor 2501 driver for chip on i2c bus -+ * scan for the chip on the bus. -+ * Hope the VBIOS initialized the PLL correctly so we can -+ * talk to it. If not, it will not be seen and not detected. -+ * Bummer! -+ */ -+static bool ns2501_init(struct intel_dvo_device *dvo, -+ struct i2c_adapter *adapter) -+{ -+ /* this will detect the NS2501 chip on the specified i2c bus */ -+ struct ns2501_priv *ns; -+ unsigned char ch; -+ -+ ns = kzalloc(sizeof(struct ns2501_priv), GFP_KERNEL); -+ if (ns == NULL) -+ return false; -+ -+ dvo->i2c_bus = adapter; -+ dvo->dev_priv = ns; -+ ns->quiet = true; -+ -+ if (!ns2501_readb(dvo, NS2501_VID_LO, &ch)) -+ goto out; -+ -+ if (ch != (NS2501_VID & 0xff)) { -+ DRM_DEBUG_KMS("ns2501 not detected got %d: from %s Slave %d.\n", -+ ch, adapter->name, dvo->slave_addr); -+ goto out; -+ } -+ -+ if (!ns2501_readb(dvo, NS2501_DID_LO, &ch)) -+ goto out; -+ -+ if (ch != (NS2501_DID & 0xff)) { -+ DRM_DEBUG_KMS("ns2501 not detected got %d: from %s Slave %d.\n", -+ ch, adapter->name, dvo->slave_addr); -+ goto out; -+ } -+ ns->quiet = false; -+ -+ DRM_DEBUG_KMS("init ns2501 dvo controller successfully!\n"); -+ -+ return true; -+ -+out: -+ kfree(ns); -+ return false; -+} -+ -+static enum drm_connector_status ns2501_detect(struct intel_dvo_device *dvo) -+{ -+ /* -+ * This is a Laptop display, it doesn't have hotplugging. -+ * Even if not, the detection bit of the 2501 is unreliable as -+ * it only works for some display types. -+ * It is even more unreliable as the PLL must be active for -+ * allowing reading from the chiop. -+ */ -+ return connector_status_connected; -+} -+ -+static enum drm_mode_status ns2501_mode_valid(struct intel_dvo_device *dvo, -+ struct drm_display_mode *mode) -+{ -+ DRM_DEBUG_KMS -+ ("is mode valid (hdisplay=%d,htotal=%d,vdisplay=%d,vtotal=%d)\n", -+ mode->hdisplay, mode->htotal, mode->vdisplay, mode->vtotal); -+ -+ /* -+ * Currently, these are all the modes I have data from. -+ * More might exist. Unclear how to find the native resolution -+ * of the panel in here so we could always accept it -+ * by disabling the scaler. -+ */ -+ if ((mode->hdisplay == 640 && mode->vdisplay == 480 && mode->clock == 25175) || -+ (mode->hdisplay == 800 && mode->vdisplay == 600 && mode->clock == 40000) || -+ (mode->hdisplay == 1024 && mode->vdisplay == 768 && mode->clock == 65000)) { -+ return MODE_OK; -+ } else { -+ return MODE_ONE_SIZE; /* Is this a reasonable error? */ -+ } -+} -+ -+static void ns2501_mode_set(struct intel_dvo_device *dvo, -+ const struct drm_display_mode *mode, -+ const struct drm_display_mode *adjusted_mode) -+{ -+ const struct ns2501_configuration *conf; -+ struct ns2501_priv *ns = (struct ns2501_priv *)(dvo->dev_priv); -+ int mode_idx, i; -+ -+ DRM_DEBUG_KMS -+ ("set mode (hdisplay=%d,htotal=%d,vdisplay=%d,vtotal=%d).\n", -+ mode->hdisplay, mode->htotal, mode->vdisplay, mode->vtotal); -+ -+ DRM_DEBUG_KMS("Detailed requested mode settings are:\n" -+ "clock : %d kHz\n" -+ "hdisplay : %d\n" -+ "hblank start : %d\n" -+ "hblank end : %d\n" -+ "hsync start : %d\n" -+ "hsync end : %d\n" -+ "htotal : %d\n" -+ "hskew : %d\n" -+ "vdisplay : %d\n" -+ "vblank start : %d\n" -+ "hblank end : %d\n" -+ "vsync start : %d\n" -+ "vsync end : %d\n" -+ "vtotal : %d\n", -+ adjusted_mode->crtc_clock, -+ adjusted_mode->crtc_hdisplay, -+ adjusted_mode->crtc_hblank_start, -+ adjusted_mode->crtc_hblank_end, -+ adjusted_mode->crtc_hsync_start, -+ adjusted_mode->crtc_hsync_end, -+ adjusted_mode->crtc_htotal, -+ adjusted_mode->crtc_hskew, -+ adjusted_mode->crtc_vdisplay, -+ adjusted_mode->crtc_vblank_start, -+ adjusted_mode->crtc_vblank_end, -+ adjusted_mode->crtc_vsync_start, -+ adjusted_mode->crtc_vsync_end, -+ adjusted_mode->crtc_vtotal); -+ -+ if (mode->hdisplay == 640 && mode->vdisplay == 480) -+ mode_idx = MODE_640x480; -+ else if (mode->hdisplay == 800 && mode->vdisplay == 600) -+ mode_idx = MODE_800x600; -+ else if (mode->hdisplay == 1024 && mode->vdisplay == 768) -+ mode_idx = MODE_1024x768; -+ else -+ return; -+ -+ /* Hopefully doing it every time won't hurt... */ -+ for (i = 0; i < ARRAY_SIZE(regs_init); i++) -+ ns2501_writeb(dvo, regs_init[i].offset, regs_init[i].value); -+ -+ /* Write the mode-agnostic values */ -+ for (i = 0; i < ARRAY_SIZE(mode_agnostic_values); i++) -+ ns2501_writeb(dvo, mode_agnostic_values[i].offset, -+ mode_agnostic_values[i].value); -+ -+ /* Write now the mode-specific configuration */ -+ conf = ns2501_modes + mode_idx; -+ ns->conf = conf; -+ -+ ns2501_writeb(dvo, NS2501_REG8, conf->conf); -+ ns2501_writeb(dvo, NS2501_REG1B, conf->pll_a); -+ ns2501_writeb(dvo, NS2501_REG1C, conf->pll_b & 0xff); -+ ns2501_writeb(dvo, NS2501_REG1D, conf->pll_b >> 8); -+ ns2501_writeb(dvo, NS2501_REGC1, conf->hstart & 0xff); -+ ns2501_writeb(dvo, NS2501_REGC2, conf->hstart >> 8); -+ ns2501_writeb(dvo, NS2501_REGC3, conf->hstop & 0xff); -+ ns2501_writeb(dvo, NS2501_REGC4, conf->hstop >> 8); -+ ns2501_writeb(dvo, NS2501_REGC5, conf->vstart & 0xff); -+ ns2501_writeb(dvo, NS2501_REGC6, conf->vstart >> 8); -+ ns2501_writeb(dvo, NS2501_REGC7, conf->vstop & 0xff); -+ ns2501_writeb(dvo, NS2501_REGC8, conf->vstop >> 8); -+ ns2501_writeb(dvo, NS2501_REG80, conf->vsync & 0xff); -+ ns2501_writeb(dvo, NS2501_REG81, conf->vsync >> 8); -+ ns2501_writeb(dvo, NS2501_REG82, conf->vtotal & 0xff); -+ ns2501_writeb(dvo, NS2501_REG83, conf->vtotal >> 8); -+ ns2501_writeb(dvo, NS2501_REG98, conf->hpos & 0xff); -+ ns2501_writeb(dvo, NS2501_REG99, conf->hpos >> 8); -+ ns2501_writeb(dvo, NS2501_REG8E, conf->vpos & 0xff); -+ ns2501_writeb(dvo, NS2501_REG8F, conf->vpos >> 8); -+ ns2501_writeb(dvo, NS2501_REG9C, conf->voffs & 0xff); -+ ns2501_writeb(dvo, NS2501_REG9D, conf->voffs >> 8); -+ ns2501_writeb(dvo, NS2501_REGB8, conf->hscale & 0xff); -+ ns2501_writeb(dvo, NS2501_REGB9, conf->hscale >> 8); -+ ns2501_writeb(dvo, NS2501_REG10, conf->vscale & 0xff); -+ ns2501_writeb(dvo, NS2501_REG11, conf->vscale >> 8); -+ ns2501_writeb(dvo, NS2501_REGF9, conf->dither); -+ ns2501_writeb(dvo, NS2501_REG41, conf->syncb); -+ ns2501_writeb(dvo, NS2501_REGC0, conf->sync); -+} -+ -+/* set the NS2501 power state */ -+static bool ns2501_get_hw_state(struct intel_dvo_device *dvo) -+{ -+ unsigned char ch; -+ -+ if (!ns2501_readb(dvo, NS2501_REG8, &ch)) -+ return false; -+ -+ return ch & NS2501_8_PD; -+} -+ -+/* set the NS2501 power state */ -+static void ns2501_dpms(struct intel_dvo_device *dvo, bool enable) -+{ -+ struct ns2501_priv *ns = (struct ns2501_priv *)(dvo->dev_priv); -+ -+ DRM_DEBUG_KMS("Trying set the dpms of the DVO to %i\n", enable); -+ -+ if (enable) { -+ ns2501_writeb(dvo, NS2501_REGC0, ns->conf->sync | 0x08); -+ -+ ns2501_writeb(dvo, NS2501_REG41, ns->conf->syncb); -+ -+ ns2501_writeb(dvo, NS2501_REG34, NS2501_34_ENABLE_OUTPUT); -+ msleep(15); -+ -+ ns2501_writeb(dvo, NS2501_REG8, -+ ns->conf->conf | NS2501_8_BPAS); -+ if (!(ns->conf->conf & NS2501_8_BPAS)) -+ ns2501_writeb(dvo, NS2501_REG8, ns->conf->conf); -+ msleep(200); -+ -+ ns2501_writeb(dvo, NS2501_REG34, -+ NS2501_34_ENABLE_OUTPUT | NS2501_34_ENABLE_BACKLIGHT); -+ -+ ns2501_writeb(dvo, NS2501_REGC0, ns->conf->sync); -+ } else { -+ ns2501_writeb(dvo, NS2501_REG34, NS2501_34_ENABLE_OUTPUT); -+ msleep(200); -+ -+ ns2501_writeb(dvo, NS2501_REG8, NS2501_8_VEN | NS2501_8_HEN | -+ NS2501_8_BPAS); -+ msleep(15); -+ -+ ns2501_writeb(dvo, NS2501_REG34, 0x00); -+ } -+} -+ -+static void ns2501_destroy(struct intel_dvo_device *dvo) -+{ -+ struct ns2501_priv *ns = dvo->dev_priv; -+ -+ if (ns) { -+ kfree(ns); -+ dvo->dev_priv = NULL; -+ } -+} -+ -+const struct intel_dvo_dev_ops ns2501_ops = { -+ .init = ns2501_init, -+ .detect = ns2501_detect, -+ .mode_valid = ns2501_mode_valid, -+ .mode_set = ns2501_mode_set, -+ .dpms = ns2501_dpms, -+ .get_hw_state = ns2501_get_hw_state, -+ .destroy = ns2501_destroy, -+}; -diff --git a/drivers/gpu/drm/i915_legacy/dvo_sil164.c b/drivers/gpu/drm/i915_legacy/dvo_sil164.c -new file mode 100644 -index 000000000000..4ae5d8fd9ff0 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/dvo_sil164.c -@@ -0,0 +1,279 @@ -+/************************************************************************** -+ -+Copyright © 2006 Dave Airlie -+ -+All Rights Reserved. -+ -+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, sub license, 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 (including the -+next paragraph) 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 NON-INFRINGEMENT. -+IN NO EVENT SHALL THE AUTHOR 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. -+ -+**************************************************************************/ -+ -+#include "dvo.h" -+ -+#define SIL164_VID 0x0001 -+#define SIL164_DID 0x0006 -+ -+#define SIL164_VID_LO 0x00 -+#define SIL164_VID_HI 0x01 -+#define SIL164_DID_LO 0x02 -+#define SIL164_DID_HI 0x03 -+#define SIL164_REV 0x04 -+#define SIL164_RSVD 0x05 -+#define SIL164_FREQ_LO 0x06 -+#define SIL164_FREQ_HI 0x07 -+ -+#define SIL164_REG8 0x08 -+#define SIL164_8_VEN (1<<5) -+#define SIL164_8_HEN (1<<4) -+#define SIL164_8_DSEL (1<<3) -+#define SIL164_8_BSEL (1<<2) -+#define SIL164_8_EDGE (1<<1) -+#define SIL164_8_PD (1<<0) -+ -+#define SIL164_REG9 0x09 -+#define SIL164_9_VLOW (1<<7) -+#define SIL164_9_MSEL_MASK (0x7<<4) -+#define SIL164_9_TSEL (1<<3) -+#define SIL164_9_RSEN (1<<2) -+#define SIL164_9_HTPLG (1<<1) -+#define SIL164_9_MDI (1<<0) -+ -+#define SIL164_REGC 0x0c -+ -+struct sil164_priv { -+ //I2CDevRec d; -+ bool quiet; -+}; -+ -+#define SILPTR(d) ((SIL164Ptr)(d->DriverPrivate.ptr)) -+ -+static bool sil164_readb(struct intel_dvo_device *dvo, int addr, u8 *ch) -+{ -+ struct sil164_priv *sil = dvo->dev_priv; -+ struct i2c_adapter *adapter = dvo->i2c_bus; -+ u8 out_buf[2]; -+ u8 in_buf[2]; -+ -+ struct i2c_msg msgs[] = { -+ { -+ .addr = dvo->slave_addr, -+ .flags = 0, -+ .len = 1, -+ .buf = out_buf, -+ }, -+ { -+ .addr = dvo->slave_addr, -+ .flags = I2C_M_RD, -+ .len = 1, -+ .buf = in_buf, -+ } -+ }; -+ -+ out_buf[0] = addr; -+ out_buf[1] = 0; -+ -+ if (i2c_transfer(adapter, msgs, 2) == 2) { -+ *ch = in_buf[0]; -+ return true; -+ } -+ -+ if (!sil->quiet) { -+ DRM_DEBUG_KMS("Unable to read register 0x%02x from %s:%02x.\n", -+ addr, adapter->name, dvo->slave_addr); -+ } -+ return false; -+} -+ -+static bool sil164_writeb(struct intel_dvo_device *dvo, int addr, u8 ch) -+{ -+ struct sil164_priv *sil = dvo->dev_priv; -+ struct i2c_adapter *adapter = dvo->i2c_bus; -+ u8 out_buf[2]; -+ struct i2c_msg msg = { -+ .addr = dvo->slave_addr, -+ .flags = 0, -+ .len = 2, -+ .buf = out_buf, -+ }; -+ -+ out_buf[0] = addr; -+ out_buf[1] = ch; -+ -+ if (i2c_transfer(adapter, &msg, 1) == 1) -+ return true; -+ -+ if (!sil->quiet) { -+ DRM_DEBUG_KMS("Unable to write register 0x%02x to %s:%d.\n", -+ addr, adapter->name, dvo->slave_addr); -+ } -+ -+ return false; -+} -+ -+/* Silicon Image 164 driver for chip on i2c bus */ -+static bool sil164_init(struct intel_dvo_device *dvo, -+ struct i2c_adapter *adapter) -+{ -+ /* this will detect the SIL164 chip on the specified i2c bus */ -+ struct sil164_priv *sil; -+ unsigned char ch; -+ -+ sil = kzalloc(sizeof(struct sil164_priv), GFP_KERNEL); -+ if (sil == NULL) -+ return false; -+ -+ dvo->i2c_bus = adapter; -+ dvo->dev_priv = sil; -+ sil->quiet = true; -+ -+ if (!sil164_readb(dvo, SIL164_VID_LO, &ch)) -+ goto out; -+ -+ if (ch != (SIL164_VID & 0xff)) { -+ DRM_DEBUG_KMS("sil164 not detected got %d: from %s Slave %d.\n", -+ ch, adapter->name, dvo->slave_addr); -+ goto out; -+ } -+ -+ if (!sil164_readb(dvo, SIL164_DID_LO, &ch)) -+ goto out; -+ -+ if (ch != (SIL164_DID & 0xff)) { -+ DRM_DEBUG_KMS("sil164 not detected got %d: from %s Slave %d.\n", -+ ch, adapter->name, dvo->slave_addr); -+ goto out; -+ } -+ sil->quiet = false; -+ -+ DRM_DEBUG_KMS("init sil164 dvo controller successfully!\n"); -+ return true; -+ -+out: -+ kfree(sil); -+ return false; -+} -+ -+static enum drm_connector_status sil164_detect(struct intel_dvo_device *dvo) -+{ -+ u8 reg9; -+ -+ sil164_readb(dvo, SIL164_REG9, ®9); -+ -+ if (reg9 & SIL164_9_HTPLG) -+ return connector_status_connected; -+ else -+ return connector_status_disconnected; -+} -+ -+static enum drm_mode_status sil164_mode_valid(struct intel_dvo_device *dvo, -+ struct drm_display_mode *mode) -+{ -+ return MODE_OK; -+} -+ -+static void sil164_mode_set(struct intel_dvo_device *dvo, -+ const struct drm_display_mode *mode, -+ const struct drm_display_mode *adjusted_mode) -+{ -+ /* As long as the basics are set up, since we don't have clock -+ * dependencies in the mode setup, we can just leave the -+ * registers alone and everything will work fine. -+ */ -+ /* recommended programming sequence from doc */ -+ /*sil164_writeb(sil, 0x08, 0x30); -+ sil164_writeb(sil, 0x09, 0x00); -+ sil164_writeb(sil, 0x0a, 0x90); -+ sil164_writeb(sil, 0x0c, 0x89); -+ sil164_writeb(sil, 0x08, 0x31);*/ -+ /* don't do much */ -+ return; -+} -+ -+/* set the SIL164 power state */ -+static void sil164_dpms(struct intel_dvo_device *dvo, bool enable) -+{ -+ int ret; -+ unsigned char ch; -+ -+ ret = sil164_readb(dvo, SIL164_REG8, &ch); -+ if (ret == false) -+ return; -+ -+ if (enable) -+ ch |= SIL164_8_PD; -+ else -+ ch &= ~SIL164_8_PD; -+ -+ sil164_writeb(dvo, SIL164_REG8, ch); -+ return; -+} -+ -+static bool sil164_get_hw_state(struct intel_dvo_device *dvo) -+{ -+ int ret; -+ unsigned char ch; -+ -+ ret = sil164_readb(dvo, SIL164_REG8, &ch); -+ if (ret == false) -+ return false; -+ -+ if (ch & SIL164_8_PD) -+ return true; -+ else -+ return false; -+} -+ -+static void sil164_dump_regs(struct intel_dvo_device *dvo) -+{ -+ u8 val; -+ -+ sil164_readb(dvo, SIL164_FREQ_LO, &val); -+ DRM_DEBUG_KMS("SIL164_FREQ_LO: 0x%02x\n", val); -+ sil164_readb(dvo, SIL164_FREQ_HI, &val); -+ DRM_DEBUG_KMS("SIL164_FREQ_HI: 0x%02x\n", val); -+ sil164_readb(dvo, SIL164_REG8, &val); -+ DRM_DEBUG_KMS("SIL164_REG8: 0x%02x\n", val); -+ sil164_readb(dvo, SIL164_REG9, &val); -+ DRM_DEBUG_KMS("SIL164_REG9: 0x%02x\n", val); -+ sil164_readb(dvo, SIL164_REGC, &val); -+ DRM_DEBUG_KMS("SIL164_REGC: 0x%02x\n", val); -+} -+ -+static void sil164_destroy(struct intel_dvo_device *dvo) -+{ -+ struct sil164_priv *sil = dvo->dev_priv; -+ -+ if (sil) { -+ kfree(sil); -+ dvo->dev_priv = NULL; -+ } -+} -+ -+const struct intel_dvo_dev_ops sil164_ops = { -+ .init = sil164_init, -+ .detect = sil164_detect, -+ .mode_valid = sil164_mode_valid, -+ .mode_set = sil164_mode_set, -+ .dpms = sil164_dpms, -+ .get_hw_state = sil164_get_hw_state, -+ .dump_regs = sil164_dump_regs, -+ .destroy = sil164_destroy, -+}; -diff --git a/drivers/gpu/drm/i915_legacy/dvo_tfp410.c b/drivers/gpu/drm/i915_legacy/dvo_tfp410.c -new file mode 100644 -index 000000000000..d603bc2f2506 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/dvo_tfp410.c -@@ -0,0 +1,318 @@ -+/* -+ * Copyright © 2007 Dave Mueller -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Dave Mueller -+ * -+ */ -+ -+#include "dvo.h" -+ -+/* register definitions according to the TFP410 data sheet */ -+#define TFP410_VID 0x014C -+#define TFP410_DID 0x0410 -+ -+#define TFP410_VID_LO 0x00 -+#define TFP410_VID_HI 0x01 -+#define TFP410_DID_LO 0x02 -+#define TFP410_DID_HI 0x03 -+#define TFP410_REV 0x04 -+ -+#define TFP410_CTL_1 0x08 -+#define TFP410_CTL_1_TDIS (1<<6) -+#define TFP410_CTL_1_VEN (1<<5) -+#define TFP410_CTL_1_HEN (1<<4) -+#define TFP410_CTL_1_DSEL (1<<3) -+#define TFP410_CTL_1_BSEL (1<<2) -+#define TFP410_CTL_1_EDGE (1<<1) -+#define TFP410_CTL_1_PD (1<<0) -+ -+#define TFP410_CTL_2 0x09 -+#define TFP410_CTL_2_VLOW (1<<7) -+#define TFP410_CTL_2_MSEL_MASK (0x7<<4) -+#define TFP410_CTL_2_MSEL (1<<4) -+#define TFP410_CTL_2_TSEL (1<<3) -+#define TFP410_CTL_2_RSEN (1<<2) -+#define TFP410_CTL_2_HTPLG (1<<1) -+#define TFP410_CTL_2_MDI (1<<0) -+ -+#define TFP410_CTL_3 0x0A -+#define TFP410_CTL_3_DK_MASK (0x7<<5) -+#define TFP410_CTL_3_DK (1<<5) -+#define TFP410_CTL_3_DKEN (1<<4) -+#define TFP410_CTL_3_CTL_MASK (0x7<<1) -+#define TFP410_CTL_3_CTL (1<<1) -+ -+#define TFP410_USERCFG 0x0B -+ -+#define TFP410_DE_DLY 0x32 -+ -+#define TFP410_DE_CTL 0x33 -+#define TFP410_DE_CTL_DEGEN (1<<6) -+#define TFP410_DE_CTL_VSPOL (1<<5) -+#define TFP410_DE_CTL_HSPOL (1<<4) -+#define TFP410_DE_CTL_DEDLY8 (1<<0) -+ -+#define TFP410_DE_TOP 0x34 -+ -+#define TFP410_DE_CNT_LO 0x36 -+#define TFP410_DE_CNT_HI 0x37 -+ -+#define TFP410_DE_LIN_LO 0x38 -+#define TFP410_DE_LIN_HI 0x39 -+ -+#define TFP410_H_RES_LO 0x3A -+#define TFP410_H_RES_HI 0x3B -+ -+#define TFP410_V_RES_LO 0x3C -+#define TFP410_V_RES_HI 0x3D -+ -+struct tfp410_priv { -+ bool quiet; -+}; -+ -+static bool tfp410_readb(struct intel_dvo_device *dvo, int addr, u8 *ch) -+{ -+ struct tfp410_priv *tfp = dvo->dev_priv; -+ struct i2c_adapter *adapter = dvo->i2c_bus; -+ u8 out_buf[2]; -+ u8 in_buf[2]; -+ -+ struct i2c_msg msgs[] = { -+ { -+ .addr = dvo->slave_addr, -+ .flags = 0, -+ .len = 1, -+ .buf = out_buf, -+ }, -+ { -+ .addr = dvo->slave_addr, -+ .flags = I2C_M_RD, -+ .len = 1, -+ .buf = in_buf, -+ } -+ }; -+ -+ out_buf[0] = addr; -+ out_buf[1] = 0; -+ -+ if (i2c_transfer(adapter, msgs, 2) == 2) { -+ *ch = in_buf[0]; -+ return true; -+ } -+ -+ if (!tfp->quiet) { -+ DRM_DEBUG_KMS("Unable to read register 0x%02x from %s:%02x.\n", -+ addr, adapter->name, dvo->slave_addr); -+ } -+ return false; -+} -+ -+static bool tfp410_writeb(struct intel_dvo_device *dvo, int addr, u8 ch) -+{ -+ struct tfp410_priv *tfp = dvo->dev_priv; -+ struct i2c_adapter *adapter = dvo->i2c_bus; -+ u8 out_buf[2]; -+ struct i2c_msg msg = { -+ .addr = dvo->slave_addr, -+ .flags = 0, -+ .len = 2, -+ .buf = out_buf, -+ }; -+ -+ out_buf[0] = addr; -+ out_buf[1] = ch; -+ -+ if (i2c_transfer(adapter, &msg, 1) == 1) -+ return true; -+ -+ if (!tfp->quiet) { -+ DRM_DEBUG_KMS("Unable to write register 0x%02x to %s:%d.\n", -+ addr, adapter->name, dvo->slave_addr); -+ } -+ -+ return false; -+} -+ -+static int tfp410_getid(struct intel_dvo_device *dvo, int addr) -+{ -+ u8 ch1, ch2; -+ -+ if (tfp410_readb(dvo, addr+0, &ch1) && -+ tfp410_readb(dvo, addr+1, &ch2)) -+ return ((ch2 << 8) & 0xFF00) | (ch1 & 0x00FF); -+ -+ return -1; -+} -+ -+/* Ti TFP410 driver for chip on i2c bus */ -+static bool tfp410_init(struct intel_dvo_device *dvo, -+ struct i2c_adapter *adapter) -+{ -+ /* this will detect the tfp410 chip on the specified i2c bus */ -+ struct tfp410_priv *tfp; -+ int id; -+ -+ tfp = kzalloc(sizeof(struct tfp410_priv), GFP_KERNEL); -+ if (tfp == NULL) -+ return false; -+ -+ dvo->i2c_bus = adapter; -+ dvo->dev_priv = tfp; -+ tfp->quiet = true; -+ -+ if ((id = tfp410_getid(dvo, TFP410_VID_LO)) != TFP410_VID) { -+ DRM_DEBUG_KMS("tfp410 not detected got VID %X: from %s " -+ "Slave %d.\n", -+ id, adapter->name, dvo->slave_addr); -+ goto out; -+ } -+ -+ if ((id = tfp410_getid(dvo, TFP410_DID_LO)) != TFP410_DID) { -+ DRM_DEBUG_KMS("tfp410 not detected got DID %X: from %s " -+ "Slave %d.\n", -+ id, adapter->name, dvo->slave_addr); -+ goto out; -+ } -+ tfp->quiet = false; -+ return true; -+out: -+ kfree(tfp); -+ return false; -+} -+ -+static enum drm_connector_status tfp410_detect(struct intel_dvo_device *dvo) -+{ -+ enum drm_connector_status ret = connector_status_disconnected; -+ u8 ctl2; -+ -+ if (tfp410_readb(dvo, TFP410_CTL_2, &ctl2)) { -+ if (ctl2 & TFP410_CTL_2_RSEN) -+ ret = connector_status_connected; -+ else -+ ret = connector_status_disconnected; -+ } -+ -+ return ret; -+} -+ -+static enum drm_mode_status tfp410_mode_valid(struct intel_dvo_device *dvo, -+ struct drm_display_mode *mode) -+{ -+ return MODE_OK; -+} -+ -+static void tfp410_mode_set(struct intel_dvo_device *dvo, -+ const struct drm_display_mode *mode, -+ const struct drm_display_mode *adjusted_mode) -+{ -+ /* As long as the basics are set up, since we don't have clock dependencies -+ * in the mode setup, we can just leave the registers alone and everything -+ * will work fine. -+ */ -+ /* don't do much */ -+ return; -+} -+ -+/* set the tfp410 power state */ -+static void tfp410_dpms(struct intel_dvo_device *dvo, bool enable) -+{ -+ u8 ctl1; -+ -+ if (!tfp410_readb(dvo, TFP410_CTL_1, &ctl1)) -+ return; -+ -+ if (enable) -+ ctl1 |= TFP410_CTL_1_PD; -+ else -+ ctl1 &= ~TFP410_CTL_1_PD; -+ -+ tfp410_writeb(dvo, TFP410_CTL_1, ctl1); -+} -+ -+static bool tfp410_get_hw_state(struct intel_dvo_device *dvo) -+{ -+ u8 ctl1; -+ -+ if (!tfp410_readb(dvo, TFP410_CTL_1, &ctl1)) -+ return false; -+ -+ if (ctl1 & TFP410_CTL_1_PD) -+ return true; -+ else -+ return false; -+} -+ -+static void tfp410_dump_regs(struct intel_dvo_device *dvo) -+{ -+ u8 val, val2; -+ -+ tfp410_readb(dvo, TFP410_REV, &val); -+ DRM_DEBUG_KMS("TFP410_REV: 0x%02X\n", val); -+ tfp410_readb(dvo, TFP410_CTL_1, &val); -+ DRM_DEBUG_KMS("TFP410_CTL1: 0x%02X\n", val); -+ tfp410_readb(dvo, TFP410_CTL_2, &val); -+ DRM_DEBUG_KMS("TFP410_CTL2: 0x%02X\n", val); -+ tfp410_readb(dvo, TFP410_CTL_3, &val); -+ DRM_DEBUG_KMS("TFP410_CTL3: 0x%02X\n", val); -+ tfp410_readb(dvo, TFP410_USERCFG, &val); -+ DRM_DEBUG_KMS("TFP410_USERCFG: 0x%02X\n", val); -+ tfp410_readb(dvo, TFP410_DE_DLY, &val); -+ DRM_DEBUG_KMS("TFP410_DE_DLY: 0x%02X\n", val); -+ tfp410_readb(dvo, TFP410_DE_CTL, &val); -+ DRM_DEBUG_KMS("TFP410_DE_CTL: 0x%02X\n", val); -+ tfp410_readb(dvo, TFP410_DE_TOP, &val); -+ DRM_DEBUG_KMS("TFP410_DE_TOP: 0x%02X\n", val); -+ tfp410_readb(dvo, TFP410_DE_CNT_LO, &val); -+ tfp410_readb(dvo, TFP410_DE_CNT_HI, &val2); -+ DRM_DEBUG_KMS("TFP410_DE_CNT: 0x%02X%02X\n", val2, val); -+ tfp410_readb(dvo, TFP410_DE_LIN_LO, &val); -+ tfp410_readb(dvo, TFP410_DE_LIN_HI, &val2); -+ DRM_DEBUG_KMS("TFP410_DE_LIN: 0x%02X%02X\n", val2, val); -+ tfp410_readb(dvo, TFP410_H_RES_LO, &val); -+ tfp410_readb(dvo, TFP410_H_RES_HI, &val2); -+ DRM_DEBUG_KMS("TFP410_H_RES: 0x%02X%02X\n", val2, val); -+ tfp410_readb(dvo, TFP410_V_RES_LO, &val); -+ tfp410_readb(dvo, TFP410_V_RES_HI, &val2); -+ DRM_DEBUG_KMS("TFP410_V_RES: 0x%02X%02X\n", val2, val); -+} -+ -+static void tfp410_destroy(struct intel_dvo_device *dvo) -+{ -+ struct tfp410_priv *tfp = dvo->dev_priv; -+ -+ if (tfp) { -+ kfree(tfp); -+ dvo->dev_priv = NULL; -+ } -+} -+ -+const struct intel_dvo_dev_ops tfp410_ops = { -+ .init = tfp410_init, -+ .detect = tfp410_detect, -+ .mode_valid = tfp410_mode_valid, -+ .mode_set = tfp410_mode_set, -+ .dpms = tfp410_dpms, -+ .get_hw_state = tfp410_get_hw_state, -+ .dump_regs = tfp410_dump_regs, -+ .destroy = tfp410_destroy, -+}; -diff --git a/drivers/gpu/drm/i915_legacy/gvt/Makefile b/drivers/gpu/drm/i915_legacy/gvt/Makefile -new file mode 100644 -index 000000000000..ea8324abc784 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/Makefile -@@ -0,0 +1,9 @@ -+# SPDX-License-Identifier: GPL-2.0 -+GVT_DIR := gvt -+GVT_SOURCE := gvt.o aperture_gm.o handlers.o vgpu.o trace_points.o firmware.o \ -+ interrupt.o gtt.o cfg_space.o opregion.o mmio.o display.o edid.o \ -+ execlist.o scheduler.o sched_policy.o mmio_context.o cmd_parser.o debugfs.o \ -+ fb_decoder.o dmabuf.o page_track.o -+ -+ccflags-y += -I $(srctree)/$(src) -I $(srctree)/$(src)/$(GVT_DIR)/ -+i915-y += $(addprefix $(GVT_DIR)/, $(GVT_SOURCE)) -diff --git a/drivers/gpu/drm/i915_legacy/gvt/aperture_gm.c b/drivers/gpu/drm/i915_legacy/gvt/aperture_gm.c -new file mode 100644 -index 000000000000..1fa2f65c3cd1 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/aperture_gm.c -@@ -0,0 +1,359 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Kevin Tian -+ * Dexuan Cui -+ * -+ * Contributors: -+ * Pei Zhang -+ * Min He -+ * Niu Bing -+ * Yulei Zhang -+ * Zhenyu Wang -+ * Zhi Wang -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "gvt.h" -+ -+static int alloc_gm(struct intel_vgpu *vgpu, bool high_gm) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ unsigned int flags; -+ u64 start, end, size; -+ struct drm_mm_node *node; -+ int ret; -+ -+ if (high_gm) { -+ node = &vgpu->gm.high_gm_node; -+ size = vgpu_hidden_sz(vgpu); -+ start = ALIGN(gvt_hidden_gmadr_base(gvt), I915_GTT_PAGE_SIZE); -+ end = ALIGN(gvt_hidden_gmadr_end(gvt), I915_GTT_PAGE_SIZE); -+ flags = PIN_HIGH; -+ } else { -+ node = &vgpu->gm.low_gm_node; -+ size = vgpu_aperture_sz(vgpu); -+ start = ALIGN(gvt_aperture_gmadr_base(gvt), I915_GTT_PAGE_SIZE); -+ end = ALIGN(gvt_aperture_gmadr_end(gvt), I915_GTT_PAGE_SIZE); -+ flags = PIN_MAPPABLE; -+ } -+ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ mmio_hw_access_pre(dev_priv); -+ ret = i915_gem_gtt_insert(&dev_priv->ggtt.vm, node, -+ size, I915_GTT_PAGE_SIZE, -+ I915_COLOR_UNEVICTABLE, -+ start, end, flags); -+ mmio_hw_access_post(dev_priv); -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ if (ret) -+ gvt_err("fail to alloc %s gm space from host\n", -+ high_gm ? "high" : "low"); -+ -+ return ret; -+} -+ -+static int alloc_vgpu_gm(struct intel_vgpu *vgpu) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ int ret; -+ -+ ret = alloc_gm(vgpu, false); -+ if (ret) -+ return ret; -+ -+ ret = alloc_gm(vgpu, true); -+ if (ret) -+ goto out_free_aperture; -+ -+ gvt_dbg_core("vgpu%d: alloc low GM start %llx size %llx\n", vgpu->id, -+ vgpu_aperture_offset(vgpu), vgpu_aperture_sz(vgpu)); -+ -+ gvt_dbg_core("vgpu%d: alloc high GM start %llx size %llx\n", vgpu->id, -+ vgpu_hidden_offset(vgpu), vgpu_hidden_sz(vgpu)); -+ -+ return 0; -+out_free_aperture: -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ drm_mm_remove_node(&vgpu->gm.low_gm_node); -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ return ret; -+} -+ -+static void free_vgpu_gm(struct intel_vgpu *vgpu) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ drm_mm_remove_node(&vgpu->gm.low_gm_node); -+ drm_mm_remove_node(&vgpu->gm.high_gm_node); -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+} -+ -+/** -+ * intel_vgpu_write_fence - write fence registers owned by a vGPU -+ * @vgpu: vGPU instance -+ * @fence: vGPU fence register number -+ * @value: Fence register value to be written -+ * -+ * This function is used to write fence registers owned by a vGPU. The vGPU -+ * fence register number will be translated into HW fence register number. -+ * -+ */ -+void intel_vgpu_write_fence(struct intel_vgpu *vgpu, -+ u32 fence, u64 value) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ struct drm_i915_fence_reg *reg; -+ i915_reg_t fence_reg_lo, fence_reg_hi; -+ -+ assert_rpm_wakelock_held(dev_priv); -+ -+ if (WARN_ON(fence >= vgpu_fence_sz(vgpu))) -+ return; -+ -+ reg = vgpu->fence.regs[fence]; -+ if (WARN_ON(!reg)) -+ return; -+ -+ fence_reg_lo = FENCE_REG_GEN6_LO(reg->id); -+ fence_reg_hi = FENCE_REG_GEN6_HI(reg->id); -+ -+ I915_WRITE(fence_reg_lo, 0); -+ POSTING_READ(fence_reg_lo); -+ -+ I915_WRITE(fence_reg_hi, upper_32_bits(value)); -+ I915_WRITE(fence_reg_lo, lower_32_bits(value)); -+ POSTING_READ(fence_reg_lo); -+} -+ -+static void _clear_vgpu_fence(struct intel_vgpu *vgpu) -+{ -+ int i; -+ -+ for (i = 0; i < vgpu_fence_sz(vgpu); i++) -+ intel_vgpu_write_fence(vgpu, i, 0); -+} -+ -+static void free_vgpu_fence(struct intel_vgpu *vgpu) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ struct drm_i915_fence_reg *reg; -+ u32 i; -+ -+ if (WARN_ON(!vgpu_fence_sz(vgpu))) -+ return; -+ -+ intel_runtime_pm_get(dev_priv); -+ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ _clear_vgpu_fence(vgpu); -+ for (i = 0; i < vgpu_fence_sz(vgpu); i++) { -+ reg = vgpu->fence.regs[i]; -+ i915_unreserve_fence(reg); -+ vgpu->fence.regs[i] = NULL; -+ } -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ -+ intel_runtime_pm_put_unchecked(dev_priv); -+} -+ -+static int alloc_vgpu_fence(struct intel_vgpu *vgpu) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ struct drm_i915_fence_reg *reg; -+ int i; -+ -+ intel_runtime_pm_get(dev_priv); -+ -+ /* Request fences from host */ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ -+ for (i = 0; i < vgpu_fence_sz(vgpu); i++) { -+ reg = i915_reserve_fence(dev_priv); -+ if (IS_ERR(reg)) -+ goto out_free_fence; -+ -+ vgpu->fence.regs[i] = reg; -+ } -+ -+ _clear_vgpu_fence(vgpu); -+ -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ intel_runtime_pm_put_unchecked(dev_priv); -+ return 0; -+out_free_fence: -+ gvt_vgpu_err("Failed to alloc fences\n"); -+ /* Return fences to host, if fail */ -+ for (i = 0; i < vgpu_fence_sz(vgpu); i++) { -+ reg = vgpu->fence.regs[i]; -+ if (!reg) -+ continue; -+ i915_unreserve_fence(reg); -+ vgpu->fence.regs[i] = NULL; -+ } -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ intel_runtime_pm_put_unchecked(dev_priv); -+ return -ENOSPC; -+} -+ -+static void free_resource(struct intel_vgpu *vgpu) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ -+ gvt->gm.vgpu_allocated_low_gm_size -= vgpu_aperture_sz(vgpu); -+ gvt->gm.vgpu_allocated_high_gm_size -= vgpu_hidden_sz(vgpu); -+ gvt->fence.vgpu_allocated_fence_num -= vgpu_fence_sz(vgpu); -+} -+ -+static int alloc_resource(struct intel_vgpu *vgpu, -+ struct intel_vgpu_creation_params *param) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ unsigned long request, avail, max, taken; -+ const char *item; -+ -+ if (!param->low_gm_sz || !param->high_gm_sz || !param->fence_sz) { -+ gvt_vgpu_err("Invalid vGPU creation params\n"); -+ return -EINVAL; -+ } -+ -+ item = "low GM space"; -+ max = gvt_aperture_sz(gvt) - HOST_LOW_GM_SIZE; -+ taken = gvt->gm.vgpu_allocated_low_gm_size; -+ avail = max - taken; -+ request = MB_TO_BYTES(param->low_gm_sz); -+ -+ if (request > avail) -+ goto no_enough_resource; -+ -+ vgpu_aperture_sz(vgpu) = ALIGN(request, I915_GTT_PAGE_SIZE); -+ -+ item = "high GM space"; -+ max = gvt_hidden_sz(gvt) - HOST_HIGH_GM_SIZE; -+ taken = gvt->gm.vgpu_allocated_high_gm_size; -+ avail = max - taken; -+ request = MB_TO_BYTES(param->high_gm_sz); -+ -+ if (request > avail) -+ goto no_enough_resource; -+ -+ vgpu_hidden_sz(vgpu) = ALIGN(request, I915_GTT_PAGE_SIZE); -+ -+ item = "fence"; -+ max = gvt_fence_sz(gvt) - HOST_FENCE; -+ taken = gvt->fence.vgpu_allocated_fence_num; -+ avail = max - taken; -+ request = param->fence_sz; -+ -+ if (request > avail) -+ goto no_enough_resource; -+ -+ vgpu_fence_sz(vgpu) = request; -+ -+ gvt->gm.vgpu_allocated_low_gm_size += MB_TO_BYTES(param->low_gm_sz); -+ gvt->gm.vgpu_allocated_high_gm_size += MB_TO_BYTES(param->high_gm_sz); -+ gvt->fence.vgpu_allocated_fence_num += param->fence_sz; -+ return 0; -+ -+no_enough_resource: -+ gvt_err("fail to allocate resource %s\n", item); -+ gvt_err("request %luMB avail %luMB max %luMB taken %luMB\n", -+ BYTES_TO_MB(request), BYTES_TO_MB(avail), -+ BYTES_TO_MB(max), BYTES_TO_MB(taken)); -+ return -ENOSPC; -+} -+ -+/** -+ * inte_gvt_free_vgpu_resource - free HW resource owned by a vGPU -+ * @vgpu: a vGPU -+ * -+ * This function is used to free the HW resource owned by a vGPU. -+ * -+ */ -+void intel_vgpu_free_resource(struct intel_vgpu *vgpu) -+{ -+ free_vgpu_gm(vgpu); -+ free_vgpu_fence(vgpu); -+ free_resource(vgpu); -+} -+ -+/** -+ * intel_vgpu_reset_resource - reset resource state owned by a vGPU -+ * @vgpu: a vGPU -+ * -+ * This function is used to reset resource state owned by a vGPU. -+ * -+ */ -+void intel_vgpu_reset_resource(struct intel_vgpu *vgpu) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ -+ intel_runtime_pm_get(dev_priv); -+ _clear_vgpu_fence(vgpu); -+ intel_runtime_pm_put_unchecked(dev_priv); -+} -+ -+/** -+ * intel_alloc_vgpu_resource - allocate HW resource for a vGPU -+ * @vgpu: vGPU -+ * @param: vGPU creation params -+ * -+ * This function is used to allocate HW resource for a vGPU. User specifies -+ * the resource configuration through the creation params. -+ * -+ * Returns: -+ * zero on success, negative error code if failed. -+ * -+ */ -+int intel_vgpu_alloc_resource(struct intel_vgpu *vgpu, -+ struct intel_vgpu_creation_params *param) -+{ -+ int ret; -+ -+ ret = alloc_resource(vgpu, param); -+ if (ret) -+ return ret; -+ -+ ret = alloc_vgpu_gm(vgpu); -+ if (ret) -+ goto out_free_resource; -+ -+ ret = alloc_vgpu_fence(vgpu); -+ if (ret) -+ goto out_free_vgpu_gm; -+ -+ return 0; -+ -+out_free_vgpu_gm: -+ free_vgpu_gm(vgpu); -+out_free_resource: -+ free_resource(vgpu); -+ return ret; -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/cfg_space.c b/drivers/gpu/drm/i915_legacy/gvt/cfg_space.c -new file mode 100644 -index 000000000000..19cf1bbe059d ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/cfg_space.c -@@ -0,0 +1,424 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eddie Dong -+ * Jike Song -+ * -+ * Contributors: -+ * Zhi Wang -+ * Min He -+ * Bing Niu -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "gvt.h" -+ -+enum { -+ INTEL_GVT_PCI_BAR_GTTMMIO = 0, -+ INTEL_GVT_PCI_BAR_APERTURE, -+ INTEL_GVT_PCI_BAR_PIO, -+ INTEL_GVT_PCI_BAR_MAX, -+}; -+ -+/* bitmap for writable bits (RW or RW1C bits, but cannot co-exist in one -+ * byte) byte by byte in standard pci configuration space. (not the full -+ * 256 bytes.) -+ */ -+static const u8 pci_cfg_space_rw_bmp[PCI_INTERRUPT_LINE + 4] = { -+ [PCI_COMMAND] = 0xff, 0x07, -+ [PCI_STATUS] = 0x00, 0xf9, /* the only one RW1C byte */ -+ [PCI_CACHE_LINE_SIZE] = 0xff, -+ [PCI_BASE_ADDRESS_0 ... PCI_CARDBUS_CIS - 1] = 0xff, -+ [PCI_ROM_ADDRESS] = 0x01, 0xf8, 0xff, 0xff, -+ [PCI_INTERRUPT_LINE] = 0xff, -+}; -+ -+/** -+ * vgpu_pci_cfg_mem_write - write virtual cfg space memory -+ * @vgpu: target vgpu -+ * @off: offset -+ * @src: src ptr to write -+ * @bytes: number of bytes -+ * -+ * Use this function to write virtual cfg space memory. -+ * For standard cfg space, only RW bits can be changed, -+ * and we emulates the RW1C behavior of PCI_STATUS register. -+ */ -+static void vgpu_pci_cfg_mem_write(struct intel_vgpu *vgpu, unsigned int off, -+ u8 *src, unsigned int bytes) -+{ -+ u8 *cfg_base = vgpu_cfg_space(vgpu); -+ u8 mask, new, old; -+ int i = 0; -+ -+ for (; i < bytes && (off + i < sizeof(pci_cfg_space_rw_bmp)); i++) { -+ mask = pci_cfg_space_rw_bmp[off + i]; -+ old = cfg_base[off + i]; -+ new = src[i] & mask; -+ -+ /** -+ * The PCI_STATUS high byte has RW1C bits, here -+ * emulates clear by writing 1 for these bits. -+ * Writing a 0b to RW1C bits has no effect. -+ */ -+ if (off + i == PCI_STATUS + 1) -+ new = (~new & old) & mask; -+ -+ cfg_base[off + i] = (old & ~mask) | new; -+ } -+ -+ /* For other configuration space directly copy as it is. */ -+ if (i < bytes) -+ memcpy(cfg_base + off + i, src + i, bytes - i); -+} -+ -+/** -+ * intel_vgpu_emulate_cfg_read - emulate vGPU configuration space read -+ * @vgpu: target vgpu -+ * @offset: offset -+ * @p_data: return data ptr -+ * @bytes: number of bytes to read -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_vgpu_emulate_cfg_read(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ if (WARN_ON(bytes > 4)) -+ return -EINVAL; -+ -+ if (WARN_ON(offset + bytes > vgpu->gvt->device_info.cfg_space_size)) -+ return -EINVAL; -+ -+ memcpy(p_data, vgpu_cfg_space(vgpu) + offset, bytes); -+ return 0; -+} -+ -+static int map_aperture(struct intel_vgpu *vgpu, bool map) -+{ -+ phys_addr_t aperture_pa = vgpu_aperture_pa_base(vgpu); -+ unsigned long aperture_sz = vgpu_aperture_sz(vgpu); -+ u64 first_gfn; -+ u64 val; -+ int ret; -+ -+ if (map == vgpu->cfg_space.bar[INTEL_GVT_PCI_BAR_APERTURE].tracked) -+ return 0; -+ -+ val = vgpu_cfg_space(vgpu)[PCI_BASE_ADDRESS_2]; -+ if (val & PCI_BASE_ADDRESS_MEM_TYPE_64) -+ val = *(u64 *)(vgpu_cfg_space(vgpu) + PCI_BASE_ADDRESS_2); -+ else -+ val = *(u32 *)(vgpu_cfg_space(vgpu) + PCI_BASE_ADDRESS_2); -+ -+ first_gfn = (val + vgpu_aperture_offset(vgpu)) >> PAGE_SHIFT; -+ -+ ret = intel_gvt_hypervisor_map_gfn_to_mfn(vgpu, first_gfn, -+ aperture_pa >> PAGE_SHIFT, -+ aperture_sz >> PAGE_SHIFT, -+ map); -+ if (ret) -+ return ret; -+ -+ vgpu->cfg_space.bar[INTEL_GVT_PCI_BAR_APERTURE].tracked = map; -+ return 0; -+} -+ -+static int trap_gttmmio(struct intel_vgpu *vgpu, bool trap) -+{ -+ u64 start, end; -+ u64 val; -+ int ret; -+ -+ if (trap == vgpu->cfg_space.bar[INTEL_GVT_PCI_BAR_GTTMMIO].tracked) -+ return 0; -+ -+ val = vgpu_cfg_space(vgpu)[PCI_BASE_ADDRESS_0]; -+ if (val & PCI_BASE_ADDRESS_MEM_TYPE_64) -+ start = *(u64 *)(vgpu_cfg_space(vgpu) + PCI_BASE_ADDRESS_0); -+ else -+ start = *(u32 *)(vgpu_cfg_space(vgpu) + PCI_BASE_ADDRESS_0); -+ -+ start &= ~GENMASK(3, 0); -+ end = start + vgpu->cfg_space.bar[INTEL_GVT_PCI_BAR_GTTMMIO].size - 1; -+ -+ ret = intel_gvt_hypervisor_set_trap_area(vgpu, start, end, trap); -+ if (ret) -+ return ret; -+ -+ vgpu->cfg_space.bar[INTEL_GVT_PCI_BAR_GTTMMIO].tracked = trap; -+ return 0; -+} -+ -+static int emulate_pci_command_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u8 old = vgpu_cfg_space(vgpu)[offset]; -+ u8 new = *(u8 *)p_data; -+ u8 changed = old ^ new; -+ int ret; -+ -+ vgpu_pci_cfg_mem_write(vgpu, offset, p_data, bytes); -+ if (!(changed & PCI_COMMAND_MEMORY)) -+ return 0; -+ -+ if (old & PCI_COMMAND_MEMORY) { -+ ret = trap_gttmmio(vgpu, false); -+ if (ret) -+ return ret; -+ ret = map_aperture(vgpu, false); -+ if (ret) -+ return ret; -+ } else { -+ ret = trap_gttmmio(vgpu, true); -+ if (ret) -+ return ret; -+ ret = map_aperture(vgpu, true); -+ if (ret) -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static int emulate_pci_rom_bar_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 *pval = (u32 *)(vgpu_cfg_space(vgpu) + offset); -+ u32 new = *(u32 *)(p_data); -+ -+ if ((new & PCI_ROM_ADDRESS_MASK) == PCI_ROM_ADDRESS_MASK) -+ /* We don't have rom, return size of 0. */ -+ *pval = 0; -+ else -+ vgpu_pci_cfg_mem_write(vgpu, offset, p_data, bytes); -+ return 0; -+} -+ -+static int emulate_pci_bar_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 new = *(u32 *)(p_data); -+ bool lo = IS_ALIGNED(offset, 8); -+ u64 size; -+ int ret = 0; -+ bool mmio_enabled = -+ vgpu_cfg_space(vgpu)[PCI_COMMAND] & PCI_COMMAND_MEMORY; -+ struct intel_vgpu_pci_bar *bars = vgpu->cfg_space.bar; -+ -+ /* -+ * Power-up software can determine how much address -+ * space the device requires by writing a value of -+ * all 1's to the register and then reading the value -+ * back. The device will return 0's in all don't-care -+ * address bits. -+ */ -+ if (new == 0xffffffff) { -+ switch (offset) { -+ case PCI_BASE_ADDRESS_0: -+ case PCI_BASE_ADDRESS_1: -+ size = ~(bars[INTEL_GVT_PCI_BAR_GTTMMIO].size -1); -+ intel_vgpu_write_pci_bar(vgpu, offset, -+ size >> (lo ? 0 : 32), lo); -+ /* -+ * Untrap the BAR, since guest hasn't configured a -+ * valid GPA -+ */ -+ ret = trap_gttmmio(vgpu, false); -+ break; -+ case PCI_BASE_ADDRESS_2: -+ case PCI_BASE_ADDRESS_3: -+ size = ~(bars[INTEL_GVT_PCI_BAR_APERTURE].size -1); -+ intel_vgpu_write_pci_bar(vgpu, offset, -+ size >> (lo ? 0 : 32), lo); -+ ret = map_aperture(vgpu, false); -+ break; -+ default: -+ /* Unimplemented BARs */ -+ intel_vgpu_write_pci_bar(vgpu, offset, 0x0, false); -+ } -+ } else { -+ switch (offset) { -+ case PCI_BASE_ADDRESS_0: -+ case PCI_BASE_ADDRESS_1: -+ /* -+ * Untrap the old BAR first, since guest has -+ * re-configured the BAR -+ */ -+ trap_gttmmio(vgpu, false); -+ intel_vgpu_write_pci_bar(vgpu, offset, new, lo); -+ ret = trap_gttmmio(vgpu, mmio_enabled); -+ break; -+ case PCI_BASE_ADDRESS_2: -+ case PCI_BASE_ADDRESS_3: -+ map_aperture(vgpu, false); -+ intel_vgpu_write_pci_bar(vgpu, offset, new, lo); -+ ret = map_aperture(vgpu, mmio_enabled); -+ break; -+ default: -+ intel_vgpu_write_pci_bar(vgpu, offset, new, lo); -+ } -+ } -+ return ret; -+} -+ -+/** -+ * intel_vgpu_emulate_cfg_read - emulate vGPU configuration space write -+ * @vgpu: target vgpu -+ * @offset: offset -+ * @p_data: write data ptr -+ * @bytes: number of bytes to write -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_vgpu_emulate_cfg_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ int ret; -+ -+ if (WARN_ON(bytes > 4)) -+ return -EINVAL; -+ -+ if (WARN_ON(offset + bytes > vgpu->gvt->device_info.cfg_space_size)) -+ return -EINVAL; -+ -+ /* First check if it's PCI_COMMAND */ -+ if (IS_ALIGNED(offset, 2) && offset == PCI_COMMAND) { -+ if (WARN_ON(bytes > 2)) -+ return -EINVAL; -+ return emulate_pci_command_write(vgpu, offset, p_data, bytes); -+ } -+ -+ switch (rounddown(offset, 4)) { -+ case PCI_ROM_ADDRESS: -+ if (WARN_ON(!IS_ALIGNED(offset, 4))) -+ return -EINVAL; -+ return emulate_pci_rom_bar_write(vgpu, offset, p_data, bytes); -+ -+ case PCI_BASE_ADDRESS_0 ... PCI_BASE_ADDRESS_5: -+ if (WARN_ON(!IS_ALIGNED(offset, 4))) -+ return -EINVAL; -+ return emulate_pci_bar_write(vgpu, offset, p_data, bytes); -+ -+ case INTEL_GVT_PCI_SWSCI: -+ if (WARN_ON(!IS_ALIGNED(offset, 4))) -+ return -EINVAL; -+ ret = intel_vgpu_emulate_opregion_request(vgpu, *(u32 *)p_data); -+ if (ret) -+ return ret; -+ break; -+ -+ case INTEL_GVT_PCI_OPREGION: -+ if (WARN_ON(!IS_ALIGNED(offset, 4))) -+ return -EINVAL; -+ ret = intel_vgpu_opregion_base_write_handler(vgpu, -+ *(u32 *)p_data); -+ if (ret) -+ return ret; -+ -+ vgpu_pci_cfg_mem_write(vgpu, offset, p_data, bytes); -+ break; -+ default: -+ vgpu_pci_cfg_mem_write(vgpu, offset, p_data, bytes); -+ break; -+ } -+ return 0; -+} -+ -+/** -+ * intel_vgpu_init_cfg_space - init vGPU configuration space when create vGPU -+ * -+ * @vgpu: a vGPU -+ * @primary: is the vGPU presented as primary -+ * -+ */ -+void intel_vgpu_init_cfg_space(struct intel_vgpu *vgpu, -+ bool primary) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ const struct intel_gvt_device_info *info = &gvt->device_info; -+ u16 *gmch_ctl; -+ -+ memcpy(vgpu_cfg_space(vgpu), gvt->firmware.cfg_space, -+ info->cfg_space_size); -+ -+ if (!primary) { -+ vgpu_cfg_space(vgpu)[PCI_CLASS_DEVICE] = -+ INTEL_GVT_PCI_CLASS_VGA_OTHER; -+ vgpu_cfg_space(vgpu)[PCI_CLASS_PROG] = -+ INTEL_GVT_PCI_CLASS_VGA_OTHER; -+ } -+ -+ /* Show guest that there isn't any stolen memory.*/ -+ gmch_ctl = (u16 *)(vgpu_cfg_space(vgpu) + INTEL_GVT_PCI_GMCH_CONTROL); -+ *gmch_ctl &= ~(BDW_GMCH_GMS_MASK << BDW_GMCH_GMS_SHIFT); -+ -+ intel_vgpu_write_pci_bar(vgpu, PCI_BASE_ADDRESS_2, -+ gvt_aperture_pa_base(gvt), true); -+ -+ vgpu_cfg_space(vgpu)[PCI_COMMAND] &= ~(PCI_COMMAND_IO -+ | PCI_COMMAND_MEMORY -+ | PCI_COMMAND_MASTER); -+ /* -+ * Clear the bar upper 32bit and let guest to assign the new value -+ */ -+ memset(vgpu_cfg_space(vgpu) + PCI_BASE_ADDRESS_1, 0, 4); -+ memset(vgpu_cfg_space(vgpu) + PCI_BASE_ADDRESS_3, 0, 4); -+ memset(vgpu_cfg_space(vgpu) + PCI_BASE_ADDRESS_4, 0, 8); -+ memset(vgpu_cfg_space(vgpu) + INTEL_GVT_PCI_OPREGION, 0, 4); -+ -+ vgpu->cfg_space.bar[INTEL_GVT_PCI_BAR_GTTMMIO].size = -+ pci_resource_len(gvt->dev_priv->drm.pdev, 0); -+ vgpu->cfg_space.bar[INTEL_GVT_PCI_BAR_APERTURE].size = -+ pci_resource_len(gvt->dev_priv->drm.pdev, 2); -+ -+ memset(vgpu_cfg_space(vgpu) + PCI_ROM_ADDRESS, 0, 4); -+} -+ -+/** -+ * intel_vgpu_reset_cfg_space - reset vGPU configuration space -+ * -+ * @vgpu: a vGPU -+ * -+ */ -+void intel_vgpu_reset_cfg_space(struct intel_vgpu *vgpu) -+{ -+ u8 cmd = vgpu_cfg_space(vgpu)[PCI_COMMAND]; -+ bool primary = vgpu_cfg_space(vgpu)[PCI_CLASS_DEVICE] != -+ INTEL_GVT_PCI_CLASS_VGA_OTHER; -+ -+ if (cmd & PCI_COMMAND_MEMORY) { -+ trap_gttmmio(vgpu, false); -+ map_aperture(vgpu, false); -+ } -+ -+ /** -+ * Currently we only do such reset when vGPU is not -+ * owned by any VM, so we simply restore entire cfg -+ * space to default value. -+ */ -+ intel_vgpu_init_cfg_space(vgpu, primary); -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/cmd_parser.c b/drivers/gpu/drm/i915_legacy/gvt/cmd_parser.c -new file mode 100644 -index 000000000000..de5347725564 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/cmd_parser.c -@@ -0,0 +1,2998 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Ke Yu -+ * Kevin Tian -+ * Zhiyuan Lv -+ * -+ * Contributors: -+ * Min He -+ * Ping Gao -+ * Tina Zhang -+ * Yulei Zhang -+ * Zhi Wang -+ * -+ */ -+ -+#include -+#include "i915_drv.h" -+#include "gvt.h" -+#include "i915_pvinfo.h" -+#include "trace.h" -+ -+#define INVALID_OP (~0U) -+ -+#define OP_LEN_MI 9 -+#define OP_LEN_2D 10 -+#define OP_LEN_3D_MEDIA 16 -+#define OP_LEN_MFX_VC 16 -+#define OP_LEN_VEBOX 16 -+ -+#define CMD_TYPE(cmd) (((cmd) >> 29) & 7) -+ -+struct sub_op_bits { -+ int hi; -+ int low; -+}; -+struct decode_info { -+ const char *name; -+ int op_len; -+ int nr_sub_op; -+ const struct sub_op_bits *sub_op; -+}; -+ -+#define MAX_CMD_BUDGET 0x7fffffff -+#define MI_WAIT_FOR_PLANE_C_FLIP_PENDING (1<<15) -+#define MI_WAIT_FOR_PLANE_B_FLIP_PENDING (1<<9) -+#define MI_WAIT_FOR_PLANE_A_FLIP_PENDING (1<<1) -+ -+#define MI_WAIT_FOR_SPRITE_C_FLIP_PENDING (1<<20) -+#define MI_WAIT_FOR_SPRITE_B_FLIP_PENDING (1<<10) -+#define MI_WAIT_FOR_SPRITE_A_FLIP_PENDING (1<<2) -+ -+/* Render Command Map */ -+ -+/* MI_* command Opcode (28:23) */ -+#define OP_MI_NOOP 0x0 -+#define OP_MI_SET_PREDICATE 0x1 /* HSW+ */ -+#define OP_MI_USER_INTERRUPT 0x2 -+#define OP_MI_WAIT_FOR_EVENT 0x3 -+#define OP_MI_FLUSH 0x4 -+#define OP_MI_ARB_CHECK 0x5 -+#define OP_MI_RS_CONTROL 0x6 /* HSW+ */ -+#define OP_MI_REPORT_HEAD 0x7 -+#define OP_MI_ARB_ON_OFF 0x8 -+#define OP_MI_URB_ATOMIC_ALLOC 0x9 /* HSW+ */ -+#define OP_MI_BATCH_BUFFER_END 0xA -+#define OP_MI_SUSPEND_FLUSH 0xB -+#define OP_MI_PREDICATE 0xC /* IVB+ */ -+#define OP_MI_TOPOLOGY_FILTER 0xD /* IVB+ */ -+#define OP_MI_SET_APPID 0xE /* IVB+ */ -+#define OP_MI_RS_CONTEXT 0xF /* HSW+ */ -+#define OP_MI_LOAD_SCAN_LINES_INCL 0x12 /* HSW+ */ -+#define OP_MI_DISPLAY_FLIP 0x14 -+#define OP_MI_SEMAPHORE_MBOX 0x16 -+#define OP_MI_SET_CONTEXT 0x18 -+#define OP_MI_MATH 0x1A -+#define OP_MI_URB_CLEAR 0x19 -+#define OP_MI_SEMAPHORE_SIGNAL 0x1B /* BDW+ */ -+#define OP_MI_SEMAPHORE_WAIT 0x1C /* BDW+ */ -+ -+#define OP_MI_STORE_DATA_IMM 0x20 -+#define OP_MI_STORE_DATA_INDEX 0x21 -+#define OP_MI_LOAD_REGISTER_IMM 0x22 -+#define OP_MI_UPDATE_GTT 0x23 -+#define OP_MI_STORE_REGISTER_MEM 0x24 -+#define OP_MI_FLUSH_DW 0x26 -+#define OP_MI_CLFLUSH 0x27 -+#define OP_MI_REPORT_PERF_COUNT 0x28 -+#define OP_MI_LOAD_REGISTER_MEM 0x29 /* HSW+ */ -+#define OP_MI_LOAD_REGISTER_REG 0x2A /* HSW+ */ -+#define OP_MI_RS_STORE_DATA_IMM 0x2B /* HSW+ */ -+#define OP_MI_LOAD_URB_MEM 0x2C /* HSW+ */ -+#define OP_MI_STORE_URM_MEM 0x2D /* HSW+ */ -+#define OP_MI_2E 0x2E /* BDW+ */ -+#define OP_MI_2F 0x2F /* BDW+ */ -+#define OP_MI_BATCH_BUFFER_START 0x31 -+ -+/* Bit definition for dword 0 */ -+#define _CMDBIT_BB_START_IN_PPGTT (1UL << 8) -+ -+#define OP_MI_CONDITIONAL_BATCH_BUFFER_END 0x36 -+ -+#define BATCH_BUFFER_ADDR_MASK ((1UL << 32) - (1U << 2)) -+#define BATCH_BUFFER_ADDR_HIGH_MASK ((1UL << 16) - (1U)) -+#define BATCH_BUFFER_ADR_SPACE_BIT(x) (((x) >> 8) & 1U) -+#define BATCH_BUFFER_2ND_LEVEL_BIT(x) ((x) >> 22 & 1U) -+ -+/* 2D command: Opcode (28:22) */ -+#define OP_2D(x) ((2<<7) | x) -+ -+#define OP_XY_SETUP_BLT OP_2D(0x1) -+#define OP_XY_SETUP_CLIP_BLT OP_2D(0x3) -+#define OP_XY_SETUP_MONO_PATTERN_SL_BLT OP_2D(0x11) -+#define OP_XY_PIXEL_BLT OP_2D(0x24) -+#define OP_XY_SCANLINES_BLT OP_2D(0x25) -+#define OP_XY_TEXT_BLT OP_2D(0x26) -+#define OP_XY_TEXT_IMMEDIATE_BLT OP_2D(0x31) -+#define OP_XY_COLOR_BLT OP_2D(0x50) -+#define OP_XY_PAT_BLT OP_2D(0x51) -+#define OP_XY_MONO_PAT_BLT OP_2D(0x52) -+#define OP_XY_SRC_COPY_BLT OP_2D(0x53) -+#define OP_XY_MONO_SRC_COPY_BLT OP_2D(0x54) -+#define OP_XY_FULL_BLT OP_2D(0x55) -+#define OP_XY_FULL_MONO_SRC_BLT OP_2D(0x56) -+#define OP_XY_FULL_MONO_PATTERN_BLT OP_2D(0x57) -+#define OP_XY_FULL_MONO_PATTERN_MONO_SRC_BLT OP_2D(0x58) -+#define OP_XY_MONO_PAT_FIXED_BLT OP_2D(0x59) -+#define OP_XY_MONO_SRC_COPY_IMMEDIATE_BLT OP_2D(0x71) -+#define OP_XY_PAT_BLT_IMMEDIATE OP_2D(0x72) -+#define OP_XY_SRC_COPY_CHROMA_BLT OP_2D(0x73) -+#define OP_XY_FULL_IMMEDIATE_PATTERN_BLT OP_2D(0x74) -+#define OP_XY_FULL_MONO_SRC_IMMEDIATE_PATTERN_BLT OP_2D(0x75) -+#define OP_XY_PAT_CHROMA_BLT OP_2D(0x76) -+#define OP_XY_PAT_CHROMA_BLT_IMMEDIATE OP_2D(0x77) -+ -+/* 3D/Media Command: Pipeline Type(28:27) Opcode(26:24) Sub Opcode(23:16) */ -+#define OP_3D_MEDIA(sub_type, opcode, sub_opcode) \ -+ ((3 << 13) | ((sub_type) << 11) | ((opcode) << 8) | (sub_opcode)) -+ -+#define OP_STATE_PREFETCH OP_3D_MEDIA(0x0, 0x0, 0x03) -+ -+#define OP_STATE_BASE_ADDRESS OP_3D_MEDIA(0x0, 0x1, 0x01) -+#define OP_STATE_SIP OP_3D_MEDIA(0x0, 0x1, 0x02) -+#define OP_3D_MEDIA_0_1_4 OP_3D_MEDIA(0x0, 0x1, 0x04) -+ -+#define OP_3DSTATE_VF_STATISTICS_GM45 OP_3D_MEDIA(0x1, 0x0, 0x0B) -+ -+#define OP_PIPELINE_SELECT OP_3D_MEDIA(0x1, 0x1, 0x04) -+ -+#define OP_MEDIA_VFE_STATE OP_3D_MEDIA(0x2, 0x0, 0x0) -+#define OP_MEDIA_CURBE_LOAD OP_3D_MEDIA(0x2, 0x0, 0x1) -+#define OP_MEDIA_INTERFACE_DESCRIPTOR_LOAD OP_3D_MEDIA(0x2, 0x0, 0x2) -+#define OP_MEDIA_GATEWAY_STATE OP_3D_MEDIA(0x2, 0x0, 0x3) -+#define OP_MEDIA_STATE_FLUSH OP_3D_MEDIA(0x2, 0x0, 0x4) -+#define OP_MEDIA_POOL_STATE OP_3D_MEDIA(0x2, 0x0, 0x5) -+ -+#define OP_MEDIA_OBJECT OP_3D_MEDIA(0x2, 0x1, 0x0) -+#define OP_MEDIA_OBJECT_PRT OP_3D_MEDIA(0x2, 0x1, 0x2) -+#define OP_MEDIA_OBJECT_WALKER OP_3D_MEDIA(0x2, 0x1, 0x3) -+#define OP_GPGPU_WALKER OP_3D_MEDIA(0x2, 0x1, 0x5) -+ -+#define OP_3DSTATE_CLEAR_PARAMS OP_3D_MEDIA(0x3, 0x0, 0x04) /* IVB+ */ -+#define OP_3DSTATE_DEPTH_BUFFER OP_3D_MEDIA(0x3, 0x0, 0x05) /* IVB+ */ -+#define OP_3DSTATE_STENCIL_BUFFER OP_3D_MEDIA(0x3, 0x0, 0x06) /* IVB+ */ -+#define OP_3DSTATE_HIER_DEPTH_BUFFER OP_3D_MEDIA(0x3, 0x0, 0x07) /* IVB+ */ -+#define OP_3DSTATE_VERTEX_BUFFERS OP_3D_MEDIA(0x3, 0x0, 0x08) -+#define OP_3DSTATE_VERTEX_ELEMENTS OP_3D_MEDIA(0x3, 0x0, 0x09) -+#define OP_3DSTATE_INDEX_BUFFER OP_3D_MEDIA(0x3, 0x0, 0x0A) -+#define OP_3DSTATE_VF_STATISTICS OP_3D_MEDIA(0x3, 0x0, 0x0B) -+#define OP_3DSTATE_VF OP_3D_MEDIA(0x3, 0x0, 0x0C) /* HSW+ */ -+#define OP_3DSTATE_CC_STATE_POINTERS OP_3D_MEDIA(0x3, 0x0, 0x0E) -+#define OP_3DSTATE_SCISSOR_STATE_POINTERS OP_3D_MEDIA(0x3, 0x0, 0x0F) -+#define OP_3DSTATE_VS OP_3D_MEDIA(0x3, 0x0, 0x10) -+#define OP_3DSTATE_GS OP_3D_MEDIA(0x3, 0x0, 0x11) -+#define OP_3DSTATE_CLIP OP_3D_MEDIA(0x3, 0x0, 0x12) -+#define OP_3DSTATE_SF OP_3D_MEDIA(0x3, 0x0, 0x13) -+#define OP_3DSTATE_WM OP_3D_MEDIA(0x3, 0x0, 0x14) -+#define OP_3DSTATE_CONSTANT_VS OP_3D_MEDIA(0x3, 0x0, 0x15) -+#define OP_3DSTATE_CONSTANT_GS OP_3D_MEDIA(0x3, 0x0, 0x16) -+#define OP_3DSTATE_CONSTANT_PS OP_3D_MEDIA(0x3, 0x0, 0x17) -+#define OP_3DSTATE_SAMPLE_MASK OP_3D_MEDIA(0x3, 0x0, 0x18) -+#define OP_3DSTATE_CONSTANT_HS OP_3D_MEDIA(0x3, 0x0, 0x19) /* IVB+ */ -+#define OP_3DSTATE_CONSTANT_DS OP_3D_MEDIA(0x3, 0x0, 0x1A) /* IVB+ */ -+#define OP_3DSTATE_HS OP_3D_MEDIA(0x3, 0x0, 0x1B) /* IVB+ */ -+#define OP_3DSTATE_TE OP_3D_MEDIA(0x3, 0x0, 0x1C) /* IVB+ */ -+#define OP_3DSTATE_DS OP_3D_MEDIA(0x3, 0x0, 0x1D) /* IVB+ */ -+#define OP_3DSTATE_STREAMOUT OP_3D_MEDIA(0x3, 0x0, 0x1E) /* IVB+ */ -+#define OP_3DSTATE_SBE OP_3D_MEDIA(0x3, 0x0, 0x1F) /* IVB+ */ -+#define OP_3DSTATE_PS OP_3D_MEDIA(0x3, 0x0, 0x20) /* IVB+ */ -+#define OP_3DSTATE_VIEWPORT_STATE_POINTERS_SF_CLIP OP_3D_MEDIA(0x3, 0x0, 0x21) /* IVB+ */ -+#define OP_3DSTATE_VIEWPORT_STATE_POINTERS_CC OP_3D_MEDIA(0x3, 0x0, 0x23) /* IVB+ */ -+#define OP_3DSTATE_BLEND_STATE_POINTERS OP_3D_MEDIA(0x3, 0x0, 0x24) /* IVB+ */ -+#define OP_3DSTATE_DEPTH_STENCIL_STATE_POINTERS OP_3D_MEDIA(0x3, 0x0, 0x25) /* IVB+ */ -+#define OP_3DSTATE_BINDING_TABLE_POINTERS_VS OP_3D_MEDIA(0x3, 0x0, 0x26) /* IVB+ */ -+#define OP_3DSTATE_BINDING_TABLE_POINTERS_HS OP_3D_MEDIA(0x3, 0x0, 0x27) /* IVB+ */ -+#define OP_3DSTATE_BINDING_TABLE_POINTERS_DS OP_3D_MEDIA(0x3, 0x0, 0x28) /* IVB+ */ -+#define OP_3DSTATE_BINDING_TABLE_POINTERS_GS OP_3D_MEDIA(0x3, 0x0, 0x29) /* IVB+ */ -+#define OP_3DSTATE_BINDING_TABLE_POINTERS_PS OP_3D_MEDIA(0x3, 0x0, 0x2A) /* IVB+ */ -+#define OP_3DSTATE_SAMPLER_STATE_POINTERS_VS OP_3D_MEDIA(0x3, 0x0, 0x2B) /* IVB+ */ -+#define OP_3DSTATE_SAMPLER_STATE_POINTERS_HS OP_3D_MEDIA(0x3, 0x0, 0x2C) /* IVB+ */ -+#define OP_3DSTATE_SAMPLER_STATE_POINTERS_DS OP_3D_MEDIA(0x3, 0x0, 0x2D) /* IVB+ */ -+#define OP_3DSTATE_SAMPLER_STATE_POINTERS_GS OP_3D_MEDIA(0x3, 0x0, 0x2E) /* IVB+ */ -+#define OP_3DSTATE_SAMPLER_STATE_POINTERS_PS OP_3D_MEDIA(0x3, 0x0, 0x2F) /* IVB+ */ -+#define OP_3DSTATE_URB_VS OP_3D_MEDIA(0x3, 0x0, 0x30) /* IVB+ */ -+#define OP_3DSTATE_URB_HS OP_3D_MEDIA(0x3, 0x0, 0x31) /* IVB+ */ -+#define OP_3DSTATE_URB_DS OP_3D_MEDIA(0x3, 0x0, 0x32) /* IVB+ */ -+#define OP_3DSTATE_URB_GS OP_3D_MEDIA(0x3, 0x0, 0x33) /* IVB+ */ -+#define OP_3DSTATE_GATHER_CONSTANT_VS OP_3D_MEDIA(0x3, 0x0, 0x34) /* HSW+ */ -+#define OP_3DSTATE_GATHER_CONSTANT_GS OP_3D_MEDIA(0x3, 0x0, 0x35) /* HSW+ */ -+#define OP_3DSTATE_GATHER_CONSTANT_HS OP_3D_MEDIA(0x3, 0x0, 0x36) /* HSW+ */ -+#define OP_3DSTATE_GATHER_CONSTANT_DS OP_3D_MEDIA(0x3, 0x0, 0x37) /* HSW+ */ -+#define OP_3DSTATE_GATHER_CONSTANT_PS OP_3D_MEDIA(0x3, 0x0, 0x38) /* HSW+ */ -+#define OP_3DSTATE_DX9_CONSTANTF_VS OP_3D_MEDIA(0x3, 0x0, 0x39) /* HSW+ */ -+#define OP_3DSTATE_DX9_CONSTANTF_PS OP_3D_MEDIA(0x3, 0x0, 0x3A) /* HSW+ */ -+#define OP_3DSTATE_DX9_CONSTANTI_VS OP_3D_MEDIA(0x3, 0x0, 0x3B) /* HSW+ */ -+#define OP_3DSTATE_DX9_CONSTANTI_PS OP_3D_MEDIA(0x3, 0x0, 0x3C) /* HSW+ */ -+#define OP_3DSTATE_DX9_CONSTANTB_VS OP_3D_MEDIA(0x3, 0x0, 0x3D) /* HSW+ */ -+#define OP_3DSTATE_DX9_CONSTANTB_PS OP_3D_MEDIA(0x3, 0x0, 0x3E) /* HSW+ */ -+#define OP_3DSTATE_DX9_LOCAL_VALID_VS OP_3D_MEDIA(0x3, 0x0, 0x3F) /* HSW+ */ -+#define OP_3DSTATE_DX9_LOCAL_VALID_PS OP_3D_MEDIA(0x3, 0x0, 0x40) /* HSW+ */ -+#define OP_3DSTATE_DX9_GENERATE_ACTIVE_VS OP_3D_MEDIA(0x3, 0x0, 0x41) /* HSW+ */ -+#define OP_3DSTATE_DX9_GENERATE_ACTIVE_PS OP_3D_MEDIA(0x3, 0x0, 0x42) /* HSW+ */ -+#define OP_3DSTATE_BINDING_TABLE_EDIT_VS OP_3D_MEDIA(0x3, 0x0, 0x43) /* HSW+ */ -+#define OP_3DSTATE_BINDING_TABLE_EDIT_GS OP_3D_MEDIA(0x3, 0x0, 0x44) /* HSW+ */ -+#define OP_3DSTATE_BINDING_TABLE_EDIT_HS OP_3D_MEDIA(0x3, 0x0, 0x45) /* HSW+ */ -+#define OP_3DSTATE_BINDING_TABLE_EDIT_DS OP_3D_MEDIA(0x3, 0x0, 0x46) /* HSW+ */ -+#define OP_3DSTATE_BINDING_TABLE_EDIT_PS OP_3D_MEDIA(0x3, 0x0, 0x47) /* HSW+ */ -+ -+#define OP_3DSTATE_VF_INSTANCING OP_3D_MEDIA(0x3, 0x0, 0x49) /* BDW+ */ -+#define OP_3DSTATE_VF_SGVS OP_3D_MEDIA(0x3, 0x0, 0x4A) /* BDW+ */ -+#define OP_3DSTATE_VF_TOPOLOGY OP_3D_MEDIA(0x3, 0x0, 0x4B) /* BDW+ */ -+#define OP_3DSTATE_WM_CHROMAKEY OP_3D_MEDIA(0x3, 0x0, 0x4C) /* BDW+ */ -+#define OP_3DSTATE_PS_BLEND OP_3D_MEDIA(0x3, 0x0, 0x4D) /* BDW+ */ -+#define OP_3DSTATE_WM_DEPTH_STENCIL OP_3D_MEDIA(0x3, 0x0, 0x4E) /* BDW+ */ -+#define OP_3DSTATE_PS_EXTRA OP_3D_MEDIA(0x3, 0x0, 0x4F) /* BDW+ */ -+#define OP_3DSTATE_RASTER OP_3D_MEDIA(0x3, 0x0, 0x50) /* BDW+ */ -+#define OP_3DSTATE_SBE_SWIZ OP_3D_MEDIA(0x3, 0x0, 0x51) /* BDW+ */ -+#define OP_3DSTATE_WM_HZ_OP OP_3D_MEDIA(0x3, 0x0, 0x52) /* BDW+ */ -+#define OP_3DSTATE_COMPONENT_PACKING OP_3D_MEDIA(0x3, 0x0, 0x55) /* SKL+ */ -+ -+#define OP_3DSTATE_DRAWING_RECTANGLE OP_3D_MEDIA(0x3, 0x1, 0x00) -+#define OP_3DSTATE_SAMPLER_PALETTE_LOAD0 OP_3D_MEDIA(0x3, 0x1, 0x02) -+#define OP_3DSTATE_CHROMA_KEY OP_3D_MEDIA(0x3, 0x1, 0x04) -+#define OP_SNB_3DSTATE_DEPTH_BUFFER OP_3D_MEDIA(0x3, 0x1, 0x05) -+#define OP_3DSTATE_POLY_STIPPLE_OFFSET OP_3D_MEDIA(0x3, 0x1, 0x06) -+#define OP_3DSTATE_POLY_STIPPLE_PATTERN OP_3D_MEDIA(0x3, 0x1, 0x07) -+#define OP_3DSTATE_LINE_STIPPLE OP_3D_MEDIA(0x3, 0x1, 0x08) -+#define OP_3DSTATE_AA_LINE_PARAMS OP_3D_MEDIA(0x3, 0x1, 0x0A) -+#define OP_3DSTATE_GS_SVB_INDEX OP_3D_MEDIA(0x3, 0x1, 0x0B) -+#define OP_3DSTATE_SAMPLER_PALETTE_LOAD1 OP_3D_MEDIA(0x3, 0x1, 0x0C) -+#define OP_3DSTATE_MULTISAMPLE_BDW OP_3D_MEDIA(0x3, 0x0, 0x0D) -+#define OP_SNB_3DSTATE_STENCIL_BUFFER OP_3D_MEDIA(0x3, 0x1, 0x0E) -+#define OP_SNB_3DSTATE_HIER_DEPTH_BUFFER OP_3D_MEDIA(0x3, 0x1, 0x0F) -+#define OP_SNB_3DSTATE_CLEAR_PARAMS OP_3D_MEDIA(0x3, 0x1, 0x10) -+#define OP_3DSTATE_MONOFILTER_SIZE OP_3D_MEDIA(0x3, 0x1, 0x11) -+#define OP_3DSTATE_PUSH_CONSTANT_ALLOC_VS OP_3D_MEDIA(0x3, 0x1, 0x12) /* IVB+ */ -+#define OP_3DSTATE_PUSH_CONSTANT_ALLOC_HS OP_3D_MEDIA(0x3, 0x1, 0x13) /* IVB+ */ -+#define OP_3DSTATE_PUSH_CONSTANT_ALLOC_DS OP_3D_MEDIA(0x3, 0x1, 0x14) /* IVB+ */ -+#define OP_3DSTATE_PUSH_CONSTANT_ALLOC_GS OP_3D_MEDIA(0x3, 0x1, 0x15) /* IVB+ */ -+#define OP_3DSTATE_PUSH_CONSTANT_ALLOC_PS OP_3D_MEDIA(0x3, 0x1, 0x16) /* IVB+ */ -+#define OP_3DSTATE_SO_DECL_LIST OP_3D_MEDIA(0x3, 0x1, 0x17) -+#define OP_3DSTATE_SO_BUFFER OP_3D_MEDIA(0x3, 0x1, 0x18) -+#define OP_3DSTATE_BINDING_TABLE_POOL_ALLOC OP_3D_MEDIA(0x3, 0x1, 0x19) /* HSW+ */ -+#define OP_3DSTATE_GATHER_POOL_ALLOC OP_3D_MEDIA(0x3, 0x1, 0x1A) /* HSW+ */ -+#define OP_3DSTATE_DX9_CONSTANT_BUFFER_POOL_ALLOC OP_3D_MEDIA(0x3, 0x1, 0x1B) /* HSW+ */ -+#define OP_3DSTATE_SAMPLE_PATTERN OP_3D_MEDIA(0x3, 0x1, 0x1C) -+#define OP_PIPE_CONTROL OP_3D_MEDIA(0x3, 0x2, 0x00) -+#define OP_3DPRIMITIVE OP_3D_MEDIA(0x3, 0x3, 0x00) -+ -+/* VCCP Command Parser */ -+ -+/* -+ * Below MFX and VBE cmd definition is from vaapi intel driver project (BSD License) -+ * git://anongit.freedesktop.org/vaapi/intel-driver -+ * src/i965_defines.h -+ * -+ */ -+ -+#define OP_MFX(pipeline, op, sub_opa, sub_opb) \ -+ (3 << 13 | \ -+ (pipeline) << 11 | \ -+ (op) << 8 | \ -+ (sub_opa) << 5 | \ -+ (sub_opb)) -+ -+#define OP_MFX_PIPE_MODE_SELECT OP_MFX(2, 0, 0, 0) /* ALL */ -+#define OP_MFX_SURFACE_STATE OP_MFX(2, 0, 0, 1) /* ALL */ -+#define OP_MFX_PIPE_BUF_ADDR_STATE OP_MFX(2, 0, 0, 2) /* ALL */ -+#define OP_MFX_IND_OBJ_BASE_ADDR_STATE OP_MFX(2, 0, 0, 3) /* ALL */ -+#define OP_MFX_BSP_BUF_BASE_ADDR_STATE OP_MFX(2, 0, 0, 4) /* ALL */ -+#define OP_2_0_0_5 OP_MFX(2, 0, 0, 5) /* ALL */ -+#define OP_MFX_STATE_POINTER OP_MFX(2, 0, 0, 6) /* ALL */ -+#define OP_MFX_QM_STATE OP_MFX(2, 0, 0, 7) /* IVB+ */ -+#define OP_MFX_FQM_STATE OP_MFX(2, 0, 0, 8) /* IVB+ */ -+#define OP_MFX_PAK_INSERT_OBJECT OP_MFX(2, 0, 2, 8) /* IVB+ */ -+#define OP_MFX_STITCH_OBJECT OP_MFX(2, 0, 2, 0xA) /* IVB+ */ -+ -+#define OP_MFD_IT_OBJECT OP_MFX(2, 0, 1, 9) /* ALL */ -+ -+#define OP_MFX_WAIT OP_MFX(1, 0, 0, 0) /* IVB+ */ -+#define OP_MFX_AVC_IMG_STATE OP_MFX(2, 1, 0, 0) /* ALL */ -+#define OP_MFX_AVC_QM_STATE OP_MFX(2, 1, 0, 1) /* ALL */ -+#define OP_MFX_AVC_DIRECTMODE_STATE OP_MFX(2, 1, 0, 2) /* ALL */ -+#define OP_MFX_AVC_SLICE_STATE OP_MFX(2, 1, 0, 3) /* ALL */ -+#define OP_MFX_AVC_REF_IDX_STATE OP_MFX(2, 1, 0, 4) /* ALL */ -+#define OP_MFX_AVC_WEIGHTOFFSET_STATE OP_MFX(2, 1, 0, 5) /* ALL */ -+#define OP_MFD_AVC_PICID_STATE OP_MFX(2, 1, 1, 5) /* HSW+ */ -+#define OP_MFD_AVC_DPB_STATE OP_MFX(2, 1, 1, 6) /* IVB+ */ -+#define OP_MFD_AVC_SLICEADDR OP_MFX(2, 1, 1, 7) /* IVB+ */ -+#define OP_MFD_AVC_BSD_OBJECT OP_MFX(2, 1, 1, 8) /* ALL */ -+#define OP_MFC_AVC_PAK_OBJECT OP_MFX(2, 1, 2, 9) /* ALL */ -+ -+#define OP_MFX_VC1_PRED_PIPE_STATE OP_MFX(2, 2, 0, 1) /* ALL */ -+#define OP_MFX_VC1_DIRECTMODE_STATE OP_MFX(2, 2, 0, 2) /* ALL */ -+#define OP_MFD_VC1_SHORT_PIC_STATE OP_MFX(2, 2, 1, 0) /* IVB+ */ -+#define OP_MFD_VC1_LONG_PIC_STATE OP_MFX(2, 2, 1, 1) /* IVB+ */ -+#define OP_MFD_VC1_BSD_OBJECT OP_MFX(2, 2, 1, 8) /* ALL */ -+ -+#define OP_MFX_MPEG2_PIC_STATE OP_MFX(2, 3, 0, 0) /* ALL */ -+#define OP_MFX_MPEG2_QM_STATE OP_MFX(2, 3, 0, 1) /* ALL */ -+#define OP_MFD_MPEG2_BSD_OBJECT OP_MFX(2, 3, 1, 8) /* ALL */ -+#define OP_MFC_MPEG2_SLICEGROUP_STATE OP_MFX(2, 3, 2, 3) /* ALL */ -+#define OP_MFC_MPEG2_PAK_OBJECT OP_MFX(2, 3, 2, 9) /* ALL */ -+ -+#define OP_MFX_2_6_0_0 OP_MFX(2, 6, 0, 0) /* IVB+ */ -+#define OP_MFX_2_6_0_8 OP_MFX(2, 6, 0, 8) /* IVB+ */ -+#define OP_MFX_2_6_0_9 OP_MFX(2, 6, 0, 9) /* IVB+ */ -+ -+#define OP_MFX_JPEG_PIC_STATE OP_MFX(2, 7, 0, 0) -+#define OP_MFX_JPEG_HUFF_TABLE_STATE OP_MFX(2, 7, 0, 2) -+#define OP_MFD_JPEG_BSD_OBJECT OP_MFX(2, 7, 1, 8) -+ -+#define OP_VEB(pipeline, op, sub_opa, sub_opb) \ -+ (3 << 13 | \ -+ (pipeline) << 11 | \ -+ (op) << 8 | \ -+ (sub_opa) << 5 | \ -+ (sub_opb)) -+ -+#define OP_VEB_SURFACE_STATE OP_VEB(2, 4, 0, 0) -+#define OP_VEB_STATE OP_VEB(2, 4, 0, 2) -+#define OP_VEB_DNDI_IECP_STATE OP_VEB(2, 4, 0, 3) -+ -+struct parser_exec_state; -+ -+typedef int (*parser_cmd_handler)(struct parser_exec_state *s); -+ -+#define GVT_CMD_HASH_BITS 7 -+ -+/* which DWords need address fix */ -+#define ADDR_FIX_1(x1) (1 << (x1)) -+#define ADDR_FIX_2(x1, x2) (ADDR_FIX_1(x1) | ADDR_FIX_1(x2)) -+#define ADDR_FIX_3(x1, x2, x3) (ADDR_FIX_1(x1) | ADDR_FIX_2(x2, x3)) -+#define ADDR_FIX_4(x1, x2, x3, x4) (ADDR_FIX_1(x1) | ADDR_FIX_3(x2, x3, x4)) -+#define ADDR_FIX_5(x1, x2, x3, x4, x5) (ADDR_FIX_1(x1) | ADDR_FIX_4(x2, x3, x4, x5)) -+ -+struct cmd_info { -+ const char *name; -+ u32 opcode; -+ -+#define F_LEN_MASK (1U<<0) -+#define F_LEN_CONST 1U -+#define F_LEN_VAR 0U -+ -+/* -+ * command has its own ip advance logic -+ * e.g. MI_BATCH_START, MI_BATCH_END -+ */ -+#define F_IP_ADVANCE_CUSTOM (1<<1) -+ -+#define F_POST_HANDLE (1<<2) -+ u32 flag; -+ -+#define R_RCS BIT(RCS0) -+#define R_VCS1 BIT(VCS0) -+#define R_VCS2 BIT(VCS1) -+#define R_VCS (R_VCS1 | R_VCS2) -+#define R_BCS BIT(BCS0) -+#define R_VECS BIT(VECS0) -+#define R_ALL (R_RCS | R_VCS | R_BCS | R_VECS) -+ /* rings that support this cmd: BLT/RCS/VCS/VECS */ -+ u16 rings; -+ -+ /* devices that support this cmd: SNB/IVB/HSW/... */ -+ u16 devices; -+ -+ /* which DWords are address that need fix up. -+ * bit 0 means a 32-bit non address operand in command -+ * bit 1 means address operand, which could be 32-bit -+ * or 64-bit depending on different architectures.( -+ * defined by "gmadr_bytes_in_cmd" in intel_gvt. -+ * No matter the address length, each address only takes -+ * one bit in the bitmap. -+ */ -+ u16 addr_bitmap; -+ -+ /* flag == F_LEN_CONST : command length -+ * flag == F_LEN_VAR : length bias bits -+ * Note: length is in DWord -+ */ -+ u8 len; -+ -+ parser_cmd_handler handler; -+}; -+ -+struct cmd_entry { -+ struct hlist_node hlist; -+ const struct cmd_info *info; -+}; -+ -+enum { -+ RING_BUFFER_INSTRUCTION, -+ BATCH_BUFFER_INSTRUCTION, -+ BATCH_BUFFER_2ND_LEVEL, -+}; -+ -+enum { -+ GTT_BUFFER, -+ PPGTT_BUFFER -+}; -+ -+struct parser_exec_state { -+ struct intel_vgpu *vgpu; -+ int ring_id; -+ -+ int buf_type; -+ -+ /* batch buffer address type */ -+ int buf_addr_type; -+ -+ /* graphics memory address of ring buffer start */ -+ unsigned long ring_start; -+ unsigned long ring_size; -+ unsigned long ring_head; -+ unsigned long ring_tail; -+ -+ /* instruction graphics memory address */ -+ unsigned long ip_gma; -+ -+ /* mapped va of the instr_gma */ -+ void *ip_va; -+ void *rb_va; -+ -+ void *ret_bb_va; -+ /* next instruction when return from batch buffer to ring buffer */ -+ unsigned long ret_ip_gma_ring; -+ -+ /* next instruction when return from 2nd batch buffer to batch buffer */ -+ unsigned long ret_ip_gma_bb; -+ -+ /* batch buffer address type (GTT or PPGTT) -+ * used when ret from 2nd level batch buffer -+ */ -+ int saved_buf_addr_type; -+ bool is_ctx_wa; -+ -+ const struct cmd_info *info; -+ -+ struct intel_vgpu_workload *workload; -+}; -+ -+#define gmadr_dw_number(s) \ -+ (s->vgpu->gvt->device_info.gmadr_bytes_in_cmd >> 2) -+ -+static unsigned long bypass_scan_mask = 0; -+ -+/* ring ALL, type = 0 */ -+static const struct sub_op_bits sub_op_mi[] = { -+ {31, 29}, -+ {28, 23}, -+}; -+ -+static const struct decode_info decode_info_mi = { -+ "MI", -+ OP_LEN_MI, -+ ARRAY_SIZE(sub_op_mi), -+ sub_op_mi, -+}; -+ -+/* ring RCS, command type 2 */ -+static const struct sub_op_bits sub_op_2d[] = { -+ {31, 29}, -+ {28, 22}, -+}; -+ -+static const struct decode_info decode_info_2d = { -+ "2D", -+ OP_LEN_2D, -+ ARRAY_SIZE(sub_op_2d), -+ sub_op_2d, -+}; -+ -+/* ring RCS, command type 3 */ -+static const struct sub_op_bits sub_op_3d_media[] = { -+ {31, 29}, -+ {28, 27}, -+ {26, 24}, -+ {23, 16}, -+}; -+ -+static const struct decode_info decode_info_3d_media = { -+ "3D_Media", -+ OP_LEN_3D_MEDIA, -+ ARRAY_SIZE(sub_op_3d_media), -+ sub_op_3d_media, -+}; -+ -+/* ring VCS, command type 3 */ -+static const struct sub_op_bits sub_op_mfx_vc[] = { -+ {31, 29}, -+ {28, 27}, -+ {26, 24}, -+ {23, 21}, -+ {20, 16}, -+}; -+ -+static const struct decode_info decode_info_mfx_vc = { -+ "MFX_VC", -+ OP_LEN_MFX_VC, -+ ARRAY_SIZE(sub_op_mfx_vc), -+ sub_op_mfx_vc, -+}; -+ -+/* ring VECS, command type 3 */ -+static const struct sub_op_bits sub_op_vebox[] = { -+ {31, 29}, -+ {28, 27}, -+ {26, 24}, -+ {23, 21}, -+ {20, 16}, -+}; -+ -+static const struct decode_info decode_info_vebox = { -+ "VEBOX", -+ OP_LEN_VEBOX, -+ ARRAY_SIZE(sub_op_vebox), -+ sub_op_vebox, -+}; -+ -+static const struct decode_info *ring_decode_info[I915_NUM_ENGINES][8] = { -+ [RCS0] = { -+ &decode_info_mi, -+ NULL, -+ NULL, -+ &decode_info_3d_media, -+ NULL, -+ NULL, -+ NULL, -+ NULL, -+ }, -+ -+ [VCS0] = { -+ &decode_info_mi, -+ NULL, -+ NULL, -+ &decode_info_mfx_vc, -+ NULL, -+ NULL, -+ NULL, -+ NULL, -+ }, -+ -+ [BCS0] = { -+ &decode_info_mi, -+ NULL, -+ &decode_info_2d, -+ NULL, -+ NULL, -+ NULL, -+ NULL, -+ NULL, -+ }, -+ -+ [VECS0] = { -+ &decode_info_mi, -+ NULL, -+ NULL, -+ &decode_info_vebox, -+ NULL, -+ NULL, -+ NULL, -+ NULL, -+ }, -+ -+ [VCS1] = { -+ &decode_info_mi, -+ NULL, -+ NULL, -+ &decode_info_mfx_vc, -+ NULL, -+ NULL, -+ NULL, -+ NULL, -+ }, -+}; -+ -+static inline u32 get_opcode(u32 cmd, int ring_id) -+{ -+ const struct decode_info *d_info; -+ -+ d_info = ring_decode_info[ring_id][CMD_TYPE(cmd)]; -+ if (d_info == NULL) -+ return INVALID_OP; -+ -+ return cmd >> (32 - d_info->op_len); -+} -+ -+static inline const struct cmd_info *find_cmd_entry(struct intel_gvt *gvt, -+ unsigned int opcode, int ring_id) -+{ -+ struct cmd_entry *e; -+ -+ hash_for_each_possible(gvt->cmd_table, e, hlist, opcode) { -+ if (opcode == e->info->opcode && e->info->rings & BIT(ring_id)) -+ return e->info; -+ } -+ return NULL; -+} -+ -+static inline const struct cmd_info *get_cmd_info(struct intel_gvt *gvt, -+ u32 cmd, int ring_id) -+{ -+ u32 opcode; -+ -+ opcode = get_opcode(cmd, ring_id); -+ if (opcode == INVALID_OP) -+ return NULL; -+ -+ return find_cmd_entry(gvt, opcode, ring_id); -+} -+ -+static inline u32 sub_op_val(u32 cmd, u32 hi, u32 low) -+{ -+ return (cmd >> low) & ((1U << (hi - low + 1)) - 1); -+} -+ -+static inline void print_opcode(u32 cmd, int ring_id) -+{ -+ const struct decode_info *d_info; -+ int i; -+ -+ d_info = ring_decode_info[ring_id][CMD_TYPE(cmd)]; -+ if (d_info == NULL) -+ return; -+ -+ gvt_dbg_cmd("opcode=0x%x %s sub_ops:", -+ cmd >> (32 - d_info->op_len), d_info->name); -+ -+ for (i = 0; i < d_info->nr_sub_op; i++) -+ pr_err("0x%x ", sub_op_val(cmd, d_info->sub_op[i].hi, -+ d_info->sub_op[i].low)); -+ -+ pr_err("\n"); -+} -+ -+static inline u32 *cmd_ptr(struct parser_exec_state *s, int index) -+{ -+ return s->ip_va + (index << 2); -+} -+ -+static inline u32 cmd_val(struct parser_exec_state *s, int index) -+{ -+ return *cmd_ptr(s, index); -+} -+ -+static void parser_exec_state_dump(struct parser_exec_state *s) -+{ -+ int cnt = 0; -+ int i; -+ -+ gvt_dbg_cmd(" vgpu%d RING%d: ring_start(%08lx) ring_end(%08lx)" -+ " ring_head(%08lx) ring_tail(%08lx)\n", s->vgpu->id, -+ s->ring_id, s->ring_start, s->ring_start + s->ring_size, -+ s->ring_head, s->ring_tail); -+ -+ gvt_dbg_cmd(" %s %s ip_gma(%08lx) ", -+ s->buf_type == RING_BUFFER_INSTRUCTION ? -+ "RING_BUFFER" : "BATCH_BUFFER", -+ s->buf_addr_type == GTT_BUFFER ? -+ "GTT" : "PPGTT", s->ip_gma); -+ -+ if (s->ip_va == NULL) { -+ gvt_dbg_cmd(" ip_va(NULL)"); -+ return; -+ } -+ -+ gvt_dbg_cmd(" ip_va=%p: %08x %08x %08x %08x\n", -+ s->ip_va, cmd_val(s, 0), cmd_val(s, 1), -+ cmd_val(s, 2), cmd_val(s, 3)); -+ -+ print_opcode(cmd_val(s, 0), s->ring_id); -+ -+ s->ip_va = (u32 *)((((u64)s->ip_va) >> 12) << 12); -+ -+ while (cnt < 1024) { -+ gvt_dbg_cmd("ip_va=%p: ", s->ip_va); -+ for (i = 0; i < 8; i++) -+ gvt_dbg_cmd("%08x ", cmd_val(s, i)); -+ gvt_dbg_cmd("\n"); -+ -+ s->ip_va += 8 * sizeof(u32); -+ cnt += 8; -+ } -+} -+ -+static inline void update_ip_va(struct parser_exec_state *s) -+{ -+ unsigned long len = 0; -+ -+ if (WARN_ON(s->ring_head == s->ring_tail)) -+ return; -+ -+ if (s->buf_type == RING_BUFFER_INSTRUCTION) { -+ unsigned long ring_top = s->ring_start + s->ring_size; -+ -+ if (s->ring_head > s->ring_tail) { -+ if (s->ip_gma >= s->ring_head && s->ip_gma < ring_top) -+ len = (s->ip_gma - s->ring_head); -+ else if (s->ip_gma >= s->ring_start && -+ s->ip_gma <= s->ring_tail) -+ len = (ring_top - s->ring_head) + -+ (s->ip_gma - s->ring_start); -+ } else -+ len = (s->ip_gma - s->ring_head); -+ -+ s->ip_va = s->rb_va + len; -+ } else {/* shadow batch buffer */ -+ s->ip_va = s->ret_bb_va; -+ } -+} -+ -+static inline int ip_gma_set(struct parser_exec_state *s, -+ unsigned long ip_gma) -+{ -+ WARN_ON(!IS_ALIGNED(ip_gma, 4)); -+ -+ s->ip_gma = ip_gma; -+ update_ip_va(s); -+ return 0; -+} -+ -+static inline int ip_gma_advance(struct parser_exec_state *s, -+ unsigned int dw_len) -+{ -+ s->ip_gma += (dw_len << 2); -+ -+ if (s->buf_type == RING_BUFFER_INSTRUCTION) { -+ if (s->ip_gma >= s->ring_start + s->ring_size) -+ s->ip_gma -= s->ring_size; -+ update_ip_va(s); -+ } else { -+ s->ip_va += (dw_len << 2); -+ } -+ -+ return 0; -+} -+ -+static inline int get_cmd_length(const struct cmd_info *info, u32 cmd) -+{ -+ if ((info->flag & F_LEN_MASK) == F_LEN_CONST) -+ return info->len; -+ else -+ return (cmd & ((1U << info->len) - 1)) + 2; -+ return 0; -+} -+ -+static inline int cmd_length(struct parser_exec_state *s) -+{ -+ return get_cmd_length(s->info, cmd_val(s, 0)); -+} -+ -+/* do not remove this, some platform may need clflush here */ -+#define patch_value(s, addr, val) do { \ -+ *addr = val; \ -+} while (0) -+ -+static bool is_shadowed_mmio(unsigned int offset) -+{ -+ bool ret = false; -+ -+ if ((offset == 0x2168) || /*BB current head register UDW */ -+ (offset == 0x2140) || /*BB current header register */ -+ (offset == 0x211c) || /*second BB header register UDW */ -+ (offset == 0x2114)) { /*second BB header register UDW */ -+ ret = true; -+ } -+ return ret; -+} -+ -+static inline bool is_force_nonpriv_mmio(unsigned int offset) -+{ -+ return (offset >= 0x24d0 && offset < 0x2500); -+} -+ -+static int force_nonpriv_reg_handler(struct parser_exec_state *s, -+ unsigned int offset, unsigned int index, char *cmd) -+{ -+ struct intel_gvt *gvt = s->vgpu->gvt; -+ unsigned int data; -+ u32 ring_base; -+ u32 nopid; -+ struct drm_i915_private *dev_priv = s->vgpu->gvt->dev_priv; -+ -+ if (!strcmp(cmd, "lri")) -+ data = cmd_val(s, index + 1); -+ else { -+ gvt_err("Unexpected forcenonpriv 0x%x write from cmd %s\n", -+ offset, cmd); -+ return -EINVAL; -+ } -+ -+ ring_base = dev_priv->engine[s->ring_id]->mmio_base; -+ nopid = i915_mmio_reg_offset(RING_NOPID(ring_base)); -+ -+ if (!intel_gvt_in_force_nonpriv_whitelist(gvt, data) && -+ data != nopid) { -+ gvt_err("Unexpected forcenonpriv 0x%x LRI write, value=0x%x\n", -+ offset, data); -+ patch_value(s, cmd_ptr(s, index), nopid); -+ return 0; -+ } -+ return 0; -+} -+ -+static inline bool is_mocs_mmio(unsigned int offset) -+{ -+ return ((offset >= 0xc800) && (offset <= 0xcff8)) || -+ ((offset >= 0xb020) && (offset <= 0xb0a0)); -+} -+ -+static int mocs_cmd_reg_handler(struct parser_exec_state *s, -+ unsigned int offset, unsigned int index) -+{ -+ if (!is_mocs_mmio(offset)) -+ return -EINVAL; -+ vgpu_vreg(s->vgpu, offset) = cmd_val(s, index + 1); -+ return 0; -+} -+ -+static int cmd_reg_handler(struct parser_exec_state *s, -+ unsigned int offset, unsigned int index, char *cmd) -+{ -+ struct intel_vgpu *vgpu = s->vgpu; -+ struct intel_gvt *gvt = vgpu->gvt; -+ u32 ctx_sr_ctl; -+ -+ if (offset + 4 > gvt->device_info.mmio_size) { -+ gvt_vgpu_err("%s access to (%x) outside of MMIO range\n", -+ cmd, offset); -+ return -EFAULT; -+ } -+ -+ if (!intel_gvt_mmio_is_cmd_access(gvt, offset)) { -+ gvt_vgpu_err("%s access to non-render register (%x)\n", -+ cmd, offset); -+ return -EBADRQC; -+ } -+ -+ if (is_shadowed_mmio(offset)) { -+ gvt_vgpu_err("found access of shadowed MMIO %x\n", offset); -+ return 0; -+ } -+ -+ if (is_mocs_mmio(offset) && -+ mocs_cmd_reg_handler(s, offset, index)) -+ return -EINVAL; -+ -+ if (is_force_nonpriv_mmio(offset) && -+ force_nonpriv_reg_handler(s, offset, index, cmd)) -+ return -EPERM; -+ -+ if (offset == i915_mmio_reg_offset(DERRMR) || -+ offset == i915_mmio_reg_offset(FORCEWAKE_MT)) { -+ /* Writing to HW VGT_PVINFO_PAGE offset will be discarded */ -+ patch_value(s, cmd_ptr(s, index), VGT_PVINFO_PAGE); -+ } -+ -+ /* TODO -+ * In order to let workload with inhibit context to generate -+ * correct image data into memory, vregs values will be loaded to -+ * hw via LRIs in the workload with inhibit context. But as -+ * indirect context is loaded prior to LRIs in workload, we don't -+ * want reg values specified in indirect context overwritten by -+ * LRIs in workloads. So, when scanning an indirect context, we -+ * update reg values in it into vregs, so LRIs in workload with -+ * inhibit context will restore with correct values -+ */ -+ if (IS_GEN(gvt->dev_priv, 9) && -+ intel_gvt_mmio_is_in_ctx(gvt, offset) && -+ !strncmp(cmd, "lri", 3)) { -+ intel_gvt_hypervisor_read_gpa(s->vgpu, -+ s->workload->ring_context_gpa + 12, &ctx_sr_ctl, 4); -+ /* check inhibit context */ -+ if (ctx_sr_ctl & 1) { -+ u32 data = cmd_val(s, index + 1); -+ -+ if (intel_gvt_mmio_has_mode_mask(s->vgpu->gvt, offset)) -+ intel_vgpu_mask_mmio_write(vgpu, -+ offset, &data, 4); -+ else -+ vgpu_vreg(vgpu, offset) = data; -+ } -+ } -+ -+ /* TODO: Update the global mask if this MMIO is a masked-MMIO */ -+ intel_gvt_mmio_set_cmd_accessed(gvt, offset); -+ return 0; -+} -+ -+#define cmd_reg(s, i) \ -+ (cmd_val(s, i) & GENMASK(22, 2)) -+ -+#define cmd_reg_inhibit(s, i) \ -+ (cmd_val(s, i) & GENMASK(22, 18)) -+ -+#define cmd_gma(s, i) \ -+ (cmd_val(s, i) & GENMASK(31, 2)) -+ -+#define cmd_gma_hi(s, i) \ -+ (cmd_val(s, i) & GENMASK(15, 0)) -+ -+static int cmd_handler_lri(struct parser_exec_state *s) -+{ -+ int i, ret = 0; -+ int cmd_len = cmd_length(s); -+ struct intel_gvt *gvt = s->vgpu->gvt; -+ -+ for (i = 1; i < cmd_len; i += 2) { -+ if (IS_BROADWELL(gvt->dev_priv) && s->ring_id != RCS0) { -+ if (s->ring_id == BCS0 && -+ cmd_reg(s, i) == i915_mmio_reg_offset(DERRMR)) -+ ret |= 0; -+ else -+ ret |= cmd_reg_inhibit(s, i) ? -EBADRQC : 0; -+ } -+ if (ret) -+ break; -+ ret |= cmd_reg_handler(s, cmd_reg(s, i), i, "lri"); -+ if (ret) -+ break; -+ } -+ return ret; -+} -+ -+static int cmd_handler_lrr(struct parser_exec_state *s) -+{ -+ int i, ret = 0; -+ int cmd_len = cmd_length(s); -+ -+ for (i = 1; i < cmd_len; i += 2) { -+ if (IS_BROADWELL(s->vgpu->gvt->dev_priv)) -+ ret |= ((cmd_reg_inhibit(s, i) || -+ (cmd_reg_inhibit(s, i + 1)))) ? -+ -EBADRQC : 0; -+ if (ret) -+ break; -+ ret |= cmd_reg_handler(s, cmd_reg(s, i), i, "lrr-src"); -+ if (ret) -+ break; -+ ret |= cmd_reg_handler(s, cmd_reg(s, i + 1), i, "lrr-dst"); -+ if (ret) -+ break; -+ } -+ return ret; -+} -+ -+static inline int cmd_address_audit(struct parser_exec_state *s, -+ unsigned long guest_gma, int op_size, bool index_mode); -+ -+static int cmd_handler_lrm(struct parser_exec_state *s) -+{ -+ struct intel_gvt *gvt = s->vgpu->gvt; -+ int gmadr_bytes = gvt->device_info.gmadr_bytes_in_cmd; -+ unsigned long gma; -+ int i, ret = 0; -+ int cmd_len = cmd_length(s); -+ -+ for (i = 1; i < cmd_len;) { -+ if (IS_BROADWELL(gvt->dev_priv)) -+ ret |= (cmd_reg_inhibit(s, i)) ? -EBADRQC : 0; -+ if (ret) -+ break; -+ ret |= cmd_reg_handler(s, cmd_reg(s, i), i, "lrm"); -+ if (ret) -+ break; -+ if (cmd_val(s, 0) & (1 << 22)) { -+ gma = cmd_gma(s, i + 1); -+ if (gmadr_bytes == 8) -+ gma |= (cmd_gma_hi(s, i + 2)) << 32; -+ ret |= cmd_address_audit(s, gma, sizeof(u32), false); -+ if (ret) -+ break; -+ } -+ i += gmadr_dw_number(s) + 1; -+ } -+ return ret; -+} -+ -+static int cmd_handler_srm(struct parser_exec_state *s) -+{ -+ int gmadr_bytes = s->vgpu->gvt->device_info.gmadr_bytes_in_cmd; -+ unsigned long gma; -+ int i, ret = 0; -+ int cmd_len = cmd_length(s); -+ -+ for (i = 1; i < cmd_len;) { -+ ret |= cmd_reg_handler(s, cmd_reg(s, i), i, "srm"); -+ if (ret) -+ break; -+ if (cmd_val(s, 0) & (1 << 22)) { -+ gma = cmd_gma(s, i + 1); -+ if (gmadr_bytes == 8) -+ gma |= (cmd_gma_hi(s, i + 2)) << 32; -+ ret |= cmd_address_audit(s, gma, sizeof(u32), false); -+ if (ret) -+ break; -+ } -+ i += gmadr_dw_number(s) + 1; -+ } -+ return ret; -+} -+ -+struct cmd_interrupt_event { -+ int pipe_control_notify; -+ int mi_flush_dw; -+ int mi_user_interrupt; -+}; -+ -+static struct cmd_interrupt_event cmd_interrupt_events[] = { -+ [RCS0] = { -+ .pipe_control_notify = RCS_PIPE_CONTROL, -+ .mi_flush_dw = INTEL_GVT_EVENT_RESERVED, -+ .mi_user_interrupt = RCS_MI_USER_INTERRUPT, -+ }, -+ [BCS0] = { -+ .pipe_control_notify = INTEL_GVT_EVENT_RESERVED, -+ .mi_flush_dw = BCS_MI_FLUSH_DW, -+ .mi_user_interrupt = BCS_MI_USER_INTERRUPT, -+ }, -+ [VCS0] = { -+ .pipe_control_notify = INTEL_GVT_EVENT_RESERVED, -+ .mi_flush_dw = VCS_MI_FLUSH_DW, -+ .mi_user_interrupt = VCS_MI_USER_INTERRUPT, -+ }, -+ [VCS1] = { -+ .pipe_control_notify = INTEL_GVT_EVENT_RESERVED, -+ .mi_flush_dw = VCS2_MI_FLUSH_DW, -+ .mi_user_interrupt = VCS2_MI_USER_INTERRUPT, -+ }, -+ [VECS0] = { -+ .pipe_control_notify = INTEL_GVT_EVENT_RESERVED, -+ .mi_flush_dw = VECS_MI_FLUSH_DW, -+ .mi_user_interrupt = VECS_MI_USER_INTERRUPT, -+ }, -+}; -+ -+static int cmd_handler_pipe_control(struct parser_exec_state *s) -+{ -+ int gmadr_bytes = s->vgpu->gvt->device_info.gmadr_bytes_in_cmd; -+ unsigned long gma; -+ bool index_mode = false; -+ unsigned int post_sync; -+ int ret = 0; -+ u32 hws_pga, val; -+ -+ post_sync = (cmd_val(s, 1) & PIPE_CONTROL_POST_SYNC_OP_MASK) >> 14; -+ -+ /* LRI post sync */ -+ if (cmd_val(s, 1) & PIPE_CONTROL_MMIO_WRITE) -+ ret = cmd_reg_handler(s, cmd_reg(s, 2), 1, "pipe_ctrl"); -+ /* post sync */ -+ else if (post_sync) { -+ if (post_sync == 2) -+ ret = cmd_reg_handler(s, 0x2350, 1, "pipe_ctrl"); -+ else if (post_sync == 3) -+ ret = cmd_reg_handler(s, 0x2358, 1, "pipe_ctrl"); -+ else if (post_sync == 1) { -+ /* check ggtt*/ -+ if ((cmd_val(s, 1) & PIPE_CONTROL_GLOBAL_GTT_IVB)) { -+ gma = cmd_val(s, 2) & GENMASK(31, 3); -+ if (gmadr_bytes == 8) -+ gma |= (cmd_gma_hi(s, 3)) << 32; -+ /* Store Data Index */ -+ if (cmd_val(s, 1) & (1 << 21)) -+ index_mode = true; -+ ret |= cmd_address_audit(s, gma, sizeof(u64), -+ index_mode); -+ if (ret) -+ return ret; -+ if (index_mode) { -+ hws_pga = s->vgpu->hws_pga[s->ring_id]; -+ gma = hws_pga + gma; -+ patch_value(s, cmd_ptr(s, 2), gma); -+ val = cmd_val(s, 1) & (~(1 << 21)); -+ patch_value(s, cmd_ptr(s, 1), val); -+ } -+ } -+ } -+ } -+ -+ if (ret) -+ return ret; -+ -+ if (cmd_val(s, 1) & PIPE_CONTROL_NOTIFY) -+ set_bit(cmd_interrupt_events[s->ring_id].pipe_control_notify, -+ s->workload->pending_events); -+ return 0; -+} -+ -+static int cmd_handler_mi_user_interrupt(struct parser_exec_state *s) -+{ -+ set_bit(cmd_interrupt_events[s->ring_id].mi_user_interrupt, -+ s->workload->pending_events); -+ patch_value(s, cmd_ptr(s, 0), MI_NOOP); -+ return 0; -+} -+ -+static int cmd_advance_default(struct parser_exec_state *s) -+{ -+ return ip_gma_advance(s, cmd_length(s)); -+} -+ -+static int cmd_handler_mi_batch_buffer_end(struct parser_exec_state *s) -+{ -+ int ret; -+ -+ if (s->buf_type == BATCH_BUFFER_2ND_LEVEL) { -+ s->buf_type = BATCH_BUFFER_INSTRUCTION; -+ ret = ip_gma_set(s, s->ret_ip_gma_bb); -+ s->buf_addr_type = s->saved_buf_addr_type; -+ } else { -+ s->buf_type = RING_BUFFER_INSTRUCTION; -+ s->buf_addr_type = GTT_BUFFER; -+ if (s->ret_ip_gma_ring >= s->ring_start + s->ring_size) -+ s->ret_ip_gma_ring -= s->ring_size; -+ ret = ip_gma_set(s, s->ret_ip_gma_ring); -+ } -+ return ret; -+} -+ -+struct mi_display_flip_command_info { -+ int pipe; -+ int plane; -+ int event; -+ i915_reg_t stride_reg; -+ i915_reg_t ctrl_reg; -+ i915_reg_t surf_reg; -+ u64 stride_val; -+ u64 tile_val; -+ u64 surf_val; -+ bool async_flip; -+}; -+ -+struct plane_code_mapping { -+ int pipe; -+ int plane; -+ int event; -+}; -+ -+static int gen8_decode_mi_display_flip(struct parser_exec_state *s, -+ struct mi_display_flip_command_info *info) -+{ -+ struct drm_i915_private *dev_priv = s->vgpu->gvt->dev_priv; -+ struct plane_code_mapping gen8_plane_code[] = { -+ [0] = {PIPE_A, PLANE_A, PRIMARY_A_FLIP_DONE}, -+ [1] = {PIPE_B, PLANE_A, PRIMARY_B_FLIP_DONE}, -+ [2] = {PIPE_A, PLANE_B, SPRITE_A_FLIP_DONE}, -+ [3] = {PIPE_B, PLANE_B, SPRITE_B_FLIP_DONE}, -+ [4] = {PIPE_C, PLANE_A, PRIMARY_C_FLIP_DONE}, -+ [5] = {PIPE_C, PLANE_B, SPRITE_C_FLIP_DONE}, -+ }; -+ u32 dword0, dword1, dword2; -+ u32 v; -+ -+ dword0 = cmd_val(s, 0); -+ dword1 = cmd_val(s, 1); -+ dword2 = cmd_val(s, 2); -+ -+ v = (dword0 & GENMASK(21, 19)) >> 19; -+ if (WARN_ON(v >= ARRAY_SIZE(gen8_plane_code))) -+ return -EBADRQC; -+ -+ info->pipe = gen8_plane_code[v].pipe; -+ info->plane = gen8_plane_code[v].plane; -+ info->event = gen8_plane_code[v].event; -+ info->stride_val = (dword1 & GENMASK(15, 6)) >> 6; -+ info->tile_val = (dword1 & 0x1); -+ info->surf_val = (dword2 & GENMASK(31, 12)) >> 12; -+ info->async_flip = ((dword2 & GENMASK(1, 0)) == 0x1); -+ -+ if (info->plane == PLANE_A) { -+ info->ctrl_reg = DSPCNTR(info->pipe); -+ info->stride_reg = DSPSTRIDE(info->pipe); -+ info->surf_reg = DSPSURF(info->pipe); -+ } else if (info->plane == PLANE_B) { -+ info->ctrl_reg = SPRCTL(info->pipe); -+ info->stride_reg = SPRSTRIDE(info->pipe); -+ info->surf_reg = SPRSURF(info->pipe); -+ } else { -+ WARN_ON(1); -+ return -EBADRQC; -+ } -+ return 0; -+} -+ -+static int skl_decode_mi_display_flip(struct parser_exec_state *s, -+ struct mi_display_flip_command_info *info) -+{ -+ struct drm_i915_private *dev_priv = s->vgpu->gvt->dev_priv; -+ struct intel_vgpu *vgpu = s->vgpu; -+ u32 dword0 = cmd_val(s, 0); -+ u32 dword1 = cmd_val(s, 1); -+ u32 dword2 = cmd_val(s, 2); -+ u32 plane = (dword0 & GENMASK(12, 8)) >> 8; -+ -+ info->plane = PRIMARY_PLANE; -+ -+ switch (plane) { -+ case MI_DISPLAY_FLIP_SKL_PLANE_1_A: -+ info->pipe = PIPE_A; -+ info->event = PRIMARY_A_FLIP_DONE; -+ break; -+ case MI_DISPLAY_FLIP_SKL_PLANE_1_B: -+ info->pipe = PIPE_B; -+ info->event = PRIMARY_B_FLIP_DONE; -+ break; -+ case MI_DISPLAY_FLIP_SKL_PLANE_1_C: -+ info->pipe = PIPE_C; -+ info->event = PRIMARY_C_FLIP_DONE; -+ break; -+ -+ case MI_DISPLAY_FLIP_SKL_PLANE_2_A: -+ info->pipe = PIPE_A; -+ info->event = SPRITE_A_FLIP_DONE; -+ info->plane = SPRITE_PLANE; -+ break; -+ case MI_DISPLAY_FLIP_SKL_PLANE_2_B: -+ info->pipe = PIPE_B; -+ info->event = SPRITE_B_FLIP_DONE; -+ info->plane = SPRITE_PLANE; -+ break; -+ case MI_DISPLAY_FLIP_SKL_PLANE_2_C: -+ info->pipe = PIPE_C; -+ info->event = SPRITE_C_FLIP_DONE; -+ info->plane = SPRITE_PLANE; -+ break; -+ -+ default: -+ gvt_vgpu_err("unknown plane code %d\n", plane); -+ return -EBADRQC; -+ } -+ -+ info->stride_val = (dword1 & GENMASK(15, 6)) >> 6; -+ info->tile_val = (dword1 & GENMASK(2, 0)); -+ info->surf_val = (dword2 & GENMASK(31, 12)) >> 12; -+ info->async_flip = ((dword2 & GENMASK(1, 0)) == 0x1); -+ -+ info->ctrl_reg = DSPCNTR(info->pipe); -+ info->stride_reg = DSPSTRIDE(info->pipe); -+ info->surf_reg = DSPSURF(info->pipe); -+ -+ return 0; -+} -+ -+static int gen8_check_mi_display_flip(struct parser_exec_state *s, -+ struct mi_display_flip_command_info *info) -+{ -+ struct drm_i915_private *dev_priv = s->vgpu->gvt->dev_priv; -+ u32 stride, tile; -+ -+ if (!info->async_flip) -+ return 0; -+ -+ if (INTEL_GEN(dev_priv) >= 9) { -+ stride = vgpu_vreg_t(s->vgpu, info->stride_reg) & GENMASK(9, 0); -+ tile = (vgpu_vreg_t(s->vgpu, info->ctrl_reg) & -+ GENMASK(12, 10)) >> 10; -+ } else { -+ stride = (vgpu_vreg_t(s->vgpu, info->stride_reg) & -+ GENMASK(15, 6)) >> 6; -+ tile = (vgpu_vreg_t(s->vgpu, info->ctrl_reg) & (1 << 10)) >> 10; -+ } -+ -+ if (stride != info->stride_val) -+ gvt_dbg_cmd("cannot change stride during async flip\n"); -+ -+ if (tile != info->tile_val) -+ gvt_dbg_cmd("cannot change tile during async flip\n"); -+ -+ return 0; -+} -+ -+static int gen8_update_plane_mmio_from_mi_display_flip( -+ struct parser_exec_state *s, -+ struct mi_display_flip_command_info *info) -+{ -+ struct drm_i915_private *dev_priv = s->vgpu->gvt->dev_priv; -+ struct intel_vgpu *vgpu = s->vgpu; -+ -+ set_mask_bits(&vgpu_vreg_t(vgpu, info->surf_reg), GENMASK(31, 12), -+ info->surf_val << 12); -+ if (INTEL_GEN(dev_priv) >= 9) { -+ set_mask_bits(&vgpu_vreg_t(vgpu, info->stride_reg), GENMASK(9, 0), -+ info->stride_val); -+ set_mask_bits(&vgpu_vreg_t(vgpu, info->ctrl_reg), GENMASK(12, 10), -+ info->tile_val << 10); -+ } else { -+ set_mask_bits(&vgpu_vreg_t(vgpu, info->stride_reg), GENMASK(15, 6), -+ info->stride_val << 6); -+ set_mask_bits(&vgpu_vreg_t(vgpu, info->ctrl_reg), GENMASK(10, 10), -+ info->tile_val << 10); -+ } -+ -+ if (info->plane == PLANE_PRIMARY) -+ vgpu_vreg_t(vgpu, PIPE_FLIPCOUNT_G4X(info->pipe))++; -+ -+ if (info->async_flip) -+ intel_vgpu_trigger_virtual_event(vgpu, info->event); -+ else -+ set_bit(info->event, vgpu->irq.flip_done_event[info->pipe]); -+ -+ return 0; -+} -+ -+static int decode_mi_display_flip(struct parser_exec_state *s, -+ struct mi_display_flip_command_info *info) -+{ -+ struct drm_i915_private *dev_priv = s->vgpu->gvt->dev_priv; -+ -+ if (IS_BROADWELL(dev_priv)) -+ return gen8_decode_mi_display_flip(s, info); -+ if (INTEL_GEN(dev_priv) >= 9) -+ return skl_decode_mi_display_flip(s, info); -+ -+ return -ENODEV; -+} -+ -+static int check_mi_display_flip(struct parser_exec_state *s, -+ struct mi_display_flip_command_info *info) -+{ -+ return gen8_check_mi_display_flip(s, info); -+} -+ -+static int update_plane_mmio_from_mi_display_flip( -+ struct parser_exec_state *s, -+ struct mi_display_flip_command_info *info) -+{ -+ return gen8_update_plane_mmio_from_mi_display_flip(s, info); -+} -+ -+static int cmd_handler_mi_display_flip(struct parser_exec_state *s) -+{ -+ struct mi_display_flip_command_info info; -+ struct intel_vgpu *vgpu = s->vgpu; -+ int ret; -+ int i; -+ int len = cmd_length(s); -+ -+ ret = decode_mi_display_flip(s, &info); -+ if (ret) { -+ gvt_vgpu_err("fail to decode MI display flip command\n"); -+ return ret; -+ } -+ -+ ret = check_mi_display_flip(s, &info); -+ if (ret) { -+ gvt_vgpu_err("invalid MI display flip command\n"); -+ return ret; -+ } -+ -+ ret = update_plane_mmio_from_mi_display_flip(s, &info); -+ if (ret) { -+ gvt_vgpu_err("fail to update plane mmio\n"); -+ return ret; -+ } -+ -+ for (i = 0; i < len; i++) -+ patch_value(s, cmd_ptr(s, i), MI_NOOP); -+ return 0; -+} -+ -+static bool is_wait_for_flip_pending(u32 cmd) -+{ -+ return cmd & (MI_WAIT_FOR_PLANE_A_FLIP_PENDING | -+ MI_WAIT_FOR_PLANE_B_FLIP_PENDING | -+ MI_WAIT_FOR_PLANE_C_FLIP_PENDING | -+ MI_WAIT_FOR_SPRITE_A_FLIP_PENDING | -+ MI_WAIT_FOR_SPRITE_B_FLIP_PENDING | -+ MI_WAIT_FOR_SPRITE_C_FLIP_PENDING); -+} -+ -+static int cmd_handler_mi_wait_for_event(struct parser_exec_state *s) -+{ -+ u32 cmd = cmd_val(s, 0); -+ -+ if (!is_wait_for_flip_pending(cmd)) -+ return 0; -+ -+ patch_value(s, cmd_ptr(s, 0), MI_NOOP); -+ return 0; -+} -+ -+static unsigned long get_gma_bb_from_cmd(struct parser_exec_state *s, int index) -+{ -+ unsigned long addr; -+ unsigned long gma_high, gma_low; -+ struct intel_vgpu *vgpu = s->vgpu; -+ int gmadr_bytes = vgpu->gvt->device_info.gmadr_bytes_in_cmd; -+ -+ if (WARN_ON(gmadr_bytes != 4 && gmadr_bytes != 8)) { -+ gvt_vgpu_err("invalid gma bytes %d\n", gmadr_bytes); -+ return INTEL_GVT_INVALID_ADDR; -+ } -+ -+ gma_low = cmd_val(s, index) & BATCH_BUFFER_ADDR_MASK; -+ if (gmadr_bytes == 4) { -+ addr = gma_low; -+ } else { -+ gma_high = cmd_val(s, index + 1) & BATCH_BUFFER_ADDR_HIGH_MASK; -+ addr = (((unsigned long)gma_high) << 32) | gma_low; -+ } -+ return addr; -+} -+ -+static inline int cmd_address_audit(struct parser_exec_state *s, -+ unsigned long guest_gma, int op_size, bool index_mode) -+{ -+ struct intel_vgpu *vgpu = s->vgpu; -+ u32 max_surface_size = vgpu->gvt->device_info.max_surface_size; -+ int i; -+ int ret; -+ -+ if (op_size > max_surface_size) { -+ gvt_vgpu_err("command address audit fail name %s\n", -+ s->info->name); -+ return -EFAULT; -+ } -+ -+ if (index_mode) { -+ if (guest_gma >= I915_GTT_PAGE_SIZE) { -+ ret = -EFAULT; -+ goto err; -+ } -+ } else if (!intel_gvt_ggtt_validate_range(vgpu, guest_gma, op_size)) { -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ return 0; -+ -+err: -+ gvt_vgpu_err("cmd_parser: Malicious %s detected, addr=0x%lx, len=%d!\n", -+ s->info->name, guest_gma, op_size); -+ -+ pr_err("cmd dump: "); -+ for (i = 0; i < cmd_length(s); i++) { -+ if (!(i % 4)) -+ pr_err("\n%08x ", cmd_val(s, i)); -+ else -+ pr_err("%08x ", cmd_val(s, i)); -+ } -+ pr_err("\nvgpu%d: aperture 0x%llx - 0x%llx, hidden 0x%llx - 0x%llx\n", -+ vgpu->id, -+ vgpu_aperture_gmadr_base(vgpu), -+ vgpu_aperture_gmadr_end(vgpu), -+ vgpu_hidden_gmadr_base(vgpu), -+ vgpu_hidden_gmadr_end(vgpu)); -+ return ret; -+} -+ -+static int cmd_handler_mi_store_data_imm(struct parser_exec_state *s) -+{ -+ int gmadr_bytes = s->vgpu->gvt->device_info.gmadr_bytes_in_cmd; -+ int op_size = (cmd_length(s) - 3) * sizeof(u32); -+ int core_id = (cmd_val(s, 2) & (1 << 0)) ? 1 : 0; -+ unsigned long gma, gma_low, gma_high; -+ int ret = 0; -+ -+ /* check ppggt */ -+ if (!(cmd_val(s, 0) & (1 << 22))) -+ return 0; -+ -+ gma = cmd_val(s, 2) & GENMASK(31, 2); -+ -+ if (gmadr_bytes == 8) { -+ gma_low = cmd_val(s, 1) & GENMASK(31, 2); -+ gma_high = cmd_val(s, 2) & GENMASK(15, 0); -+ gma = (gma_high << 32) | gma_low; -+ core_id = (cmd_val(s, 1) & (1 << 0)) ? 1 : 0; -+ } -+ ret = cmd_address_audit(s, gma + op_size * core_id, op_size, false); -+ return ret; -+} -+ -+static inline int unexpected_cmd(struct parser_exec_state *s) -+{ -+ struct intel_vgpu *vgpu = s->vgpu; -+ -+ gvt_vgpu_err("Unexpected %s in command buffer!\n", s->info->name); -+ -+ return -EBADRQC; -+} -+ -+static int cmd_handler_mi_semaphore_wait(struct parser_exec_state *s) -+{ -+ return unexpected_cmd(s); -+} -+ -+static int cmd_handler_mi_report_perf_count(struct parser_exec_state *s) -+{ -+ return unexpected_cmd(s); -+} -+ -+static int cmd_handler_mi_op_2e(struct parser_exec_state *s) -+{ -+ return unexpected_cmd(s); -+} -+ -+static int cmd_handler_mi_op_2f(struct parser_exec_state *s) -+{ -+ int gmadr_bytes = s->vgpu->gvt->device_info.gmadr_bytes_in_cmd; -+ int op_size = (1 << ((cmd_val(s, 0) & GENMASK(20, 19)) >> 19)) * -+ sizeof(u32); -+ unsigned long gma, gma_high; -+ int ret = 0; -+ -+ if (!(cmd_val(s, 0) & (1 << 22))) -+ return ret; -+ -+ gma = cmd_val(s, 1) & GENMASK(31, 2); -+ if (gmadr_bytes == 8) { -+ gma_high = cmd_val(s, 2) & GENMASK(15, 0); -+ gma = (gma_high << 32) | gma; -+ } -+ ret = cmd_address_audit(s, gma, op_size, false); -+ return ret; -+} -+ -+static int cmd_handler_mi_store_data_index(struct parser_exec_state *s) -+{ -+ return unexpected_cmd(s); -+} -+ -+static int cmd_handler_mi_clflush(struct parser_exec_state *s) -+{ -+ return unexpected_cmd(s); -+} -+ -+static int cmd_handler_mi_conditional_batch_buffer_end( -+ struct parser_exec_state *s) -+{ -+ return unexpected_cmd(s); -+} -+ -+static int cmd_handler_mi_update_gtt(struct parser_exec_state *s) -+{ -+ return unexpected_cmd(s); -+} -+ -+static int cmd_handler_mi_flush_dw(struct parser_exec_state *s) -+{ -+ int gmadr_bytes = s->vgpu->gvt->device_info.gmadr_bytes_in_cmd; -+ unsigned long gma; -+ bool index_mode = false; -+ int ret = 0; -+ u32 hws_pga, val; -+ -+ /* Check post-sync and ppgtt bit */ -+ if (((cmd_val(s, 0) >> 14) & 0x3) && (cmd_val(s, 1) & (1 << 2))) { -+ gma = cmd_val(s, 1) & GENMASK(31, 3); -+ if (gmadr_bytes == 8) -+ gma |= (cmd_val(s, 2) & GENMASK(15, 0)) << 32; -+ /* Store Data Index */ -+ if (cmd_val(s, 0) & (1 << 21)) -+ index_mode = true; -+ ret = cmd_address_audit(s, gma, sizeof(u64), index_mode); -+ if (ret) -+ return ret; -+ if (index_mode) { -+ hws_pga = s->vgpu->hws_pga[s->ring_id]; -+ gma = hws_pga + gma; -+ patch_value(s, cmd_ptr(s, 1), gma); -+ val = cmd_val(s, 0) & (~(1 << 21)); -+ patch_value(s, cmd_ptr(s, 0), val); -+ } -+ } -+ /* Check notify bit */ -+ if ((cmd_val(s, 0) & (1 << 8))) -+ set_bit(cmd_interrupt_events[s->ring_id].mi_flush_dw, -+ s->workload->pending_events); -+ return ret; -+} -+ -+static void addr_type_update_snb(struct parser_exec_state *s) -+{ -+ if ((s->buf_type == RING_BUFFER_INSTRUCTION) && -+ (BATCH_BUFFER_ADR_SPACE_BIT(cmd_val(s, 0)) == 1)) { -+ s->buf_addr_type = PPGTT_BUFFER; -+ } -+} -+ -+ -+static int copy_gma_to_hva(struct intel_vgpu *vgpu, struct intel_vgpu_mm *mm, -+ unsigned long gma, unsigned long end_gma, void *va) -+{ -+ unsigned long copy_len, offset; -+ unsigned long len = 0; -+ unsigned long gpa; -+ -+ while (gma != end_gma) { -+ gpa = intel_vgpu_gma_to_gpa(mm, gma); -+ if (gpa == INTEL_GVT_INVALID_ADDR) { -+ gvt_vgpu_err("invalid gma address: %lx\n", gma); -+ return -EFAULT; -+ } -+ -+ offset = gma & (I915_GTT_PAGE_SIZE - 1); -+ -+ copy_len = (end_gma - gma) >= (I915_GTT_PAGE_SIZE - offset) ? -+ I915_GTT_PAGE_SIZE - offset : end_gma - gma; -+ -+ intel_gvt_hypervisor_read_gpa(vgpu, gpa, va + len, copy_len); -+ -+ len += copy_len; -+ gma += copy_len; -+ } -+ return len; -+} -+ -+ -+/* -+ * Check whether a batch buffer needs to be scanned. Currently -+ * the only criteria is based on privilege. -+ */ -+static int batch_buffer_needs_scan(struct parser_exec_state *s) -+{ -+ /* Decide privilege based on address space */ -+ if (cmd_val(s, 0) & (1 << 8) && -+ !(s->vgpu->scan_nonprivbb & (1 << s->ring_id))) -+ return 0; -+ return 1; -+} -+ -+static int find_bb_size(struct parser_exec_state *s, unsigned long *bb_size) -+{ -+ unsigned long gma = 0; -+ const struct cmd_info *info; -+ u32 cmd_len = 0; -+ bool bb_end = false; -+ struct intel_vgpu *vgpu = s->vgpu; -+ u32 cmd; -+ struct intel_vgpu_mm *mm = (s->buf_addr_type == GTT_BUFFER) ? -+ s->vgpu->gtt.ggtt_mm : s->workload->shadow_mm; -+ -+ *bb_size = 0; -+ -+ /* get the start gm address of the batch buffer */ -+ gma = get_gma_bb_from_cmd(s, 1); -+ if (gma == INTEL_GVT_INVALID_ADDR) -+ return -EFAULT; -+ -+ cmd = cmd_val(s, 0); -+ info = get_cmd_info(s->vgpu->gvt, cmd, s->ring_id); -+ if (info == NULL) { -+ gvt_vgpu_err("unknown cmd 0x%x, opcode=0x%x, addr_type=%s, ring %d, workload=%p\n", -+ cmd, get_opcode(cmd, s->ring_id), -+ (s->buf_addr_type == PPGTT_BUFFER) ? -+ "ppgtt" : "ggtt", s->ring_id, s->workload); -+ return -EBADRQC; -+ } -+ do { -+ if (copy_gma_to_hva(s->vgpu, mm, -+ gma, gma + 4, &cmd) < 0) -+ return -EFAULT; -+ info = get_cmd_info(s->vgpu->gvt, cmd, s->ring_id); -+ if (info == NULL) { -+ gvt_vgpu_err("unknown cmd 0x%x, opcode=0x%x, addr_type=%s, ring %d, workload=%p\n", -+ cmd, get_opcode(cmd, s->ring_id), -+ (s->buf_addr_type == PPGTT_BUFFER) ? -+ "ppgtt" : "ggtt", s->ring_id, s->workload); -+ return -EBADRQC; -+ } -+ -+ if (info->opcode == OP_MI_BATCH_BUFFER_END) { -+ bb_end = true; -+ } else if (info->opcode == OP_MI_BATCH_BUFFER_START) { -+ if (BATCH_BUFFER_2ND_LEVEL_BIT(cmd) == 0) -+ /* chained batch buffer */ -+ bb_end = true; -+ } -+ cmd_len = get_cmd_length(info, cmd) << 2; -+ *bb_size += cmd_len; -+ gma += cmd_len; -+ } while (!bb_end); -+ -+ return 0; -+} -+ -+static int perform_bb_shadow(struct parser_exec_state *s) -+{ -+ struct intel_vgpu *vgpu = s->vgpu; -+ struct intel_vgpu_shadow_bb *bb; -+ unsigned long gma = 0; -+ unsigned long bb_size; -+ int ret = 0; -+ struct intel_vgpu_mm *mm = (s->buf_addr_type == GTT_BUFFER) ? -+ s->vgpu->gtt.ggtt_mm : s->workload->shadow_mm; -+ unsigned long gma_start_offset = 0; -+ -+ /* get the start gm address of the batch buffer */ -+ gma = get_gma_bb_from_cmd(s, 1); -+ if (gma == INTEL_GVT_INVALID_ADDR) -+ return -EFAULT; -+ -+ ret = find_bb_size(s, &bb_size); -+ if (ret) -+ return ret; -+ -+ bb = kzalloc(sizeof(*bb), GFP_KERNEL); -+ if (!bb) -+ return -ENOMEM; -+ -+ bb->ppgtt = (s->buf_addr_type == GTT_BUFFER) ? false : true; -+ -+ /* the gma_start_offset stores the batch buffer's start gma's -+ * offset relative to page boundary. so for non-privileged batch -+ * buffer, the shadowed gem object holds exactly the same page -+ * layout as original gem object. This is for the convience of -+ * replacing the whole non-privilged batch buffer page to this -+ * shadowed one in PPGTT at the same gma address. (this replacing -+ * action is not implemented yet now, but may be necessary in -+ * future). -+ * for prileged batch buffer, we just change start gma address to -+ * that of shadowed page. -+ */ -+ if (bb->ppgtt) -+ gma_start_offset = gma & ~I915_GTT_PAGE_MASK; -+ -+ bb->obj = i915_gem_object_create(s->vgpu->gvt->dev_priv, -+ roundup(bb_size + gma_start_offset, PAGE_SIZE)); -+ if (IS_ERR(bb->obj)) { -+ ret = PTR_ERR(bb->obj); -+ goto err_free_bb; -+ } -+ -+ ret = i915_gem_obj_prepare_shmem_write(bb->obj, &bb->clflush); -+ if (ret) -+ goto err_free_obj; -+ -+ bb->va = i915_gem_object_pin_map(bb->obj, I915_MAP_WB); -+ if (IS_ERR(bb->va)) { -+ ret = PTR_ERR(bb->va); -+ goto err_finish_shmem_access; -+ } -+ -+ if (bb->clflush & CLFLUSH_BEFORE) { -+ drm_clflush_virt_range(bb->va, bb->obj->base.size); -+ bb->clflush &= ~CLFLUSH_BEFORE; -+ } -+ -+ ret = copy_gma_to_hva(s->vgpu, mm, -+ gma, gma + bb_size, -+ bb->va + gma_start_offset); -+ if (ret < 0) { -+ gvt_vgpu_err("fail to copy guest ring buffer\n"); -+ ret = -EFAULT; -+ goto err_unmap; -+ } -+ -+ INIT_LIST_HEAD(&bb->list); -+ list_add(&bb->list, &s->workload->shadow_bb); -+ -+ bb->accessing = true; -+ bb->bb_start_cmd_va = s->ip_va; -+ -+ if ((s->buf_type == BATCH_BUFFER_INSTRUCTION) && (!s->is_ctx_wa)) -+ bb->bb_offset = s->ip_va - s->rb_va; -+ else -+ bb->bb_offset = 0; -+ -+ /* -+ * ip_va saves the virtual address of the shadow batch buffer, while -+ * ip_gma saves the graphics address of the original batch buffer. -+ * As the shadow batch buffer is just a copy from the originial one, -+ * it should be right to use shadow batch buffer'va and original batch -+ * buffer's gma in pair. After all, we don't want to pin the shadow -+ * buffer here (too early). -+ */ -+ s->ip_va = bb->va + gma_start_offset; -+ s->ip_gma = gma; -+ return 0; -+err_unmap: -+ i915_gem_object_unpin_map(bb->obj); -+err_finish_shmem_access: -+ i915_gem_obj_finish_shmem_access(bb->obj); -+err_free_obj: -+ i915_gem_object_put(bb->obj); -+err_free_bb: -+ kfree(bb); -+ return ret; -+} -+ -+static int cmd_handler_mi_batch_buffer_start(struct parser_exec_state *s) -+{ -+ bool second_level; -+ int ret = 0; -+ struct intel_vgpu *vgpu = s->vgpu; -+ -+ if (s->buf_type == BATCH_BUFFER_2ND_LEVEL) { -+ gvt_vgpu_err("Found MI_BATCH_BUFFER_START in 2nd level BB\n"); -+ return -EFAULT; -+ } -+ -+ second_level = BATCH_BUFFER_2ND_LEVEL_BIT(cmd_val(s, 0)) == 1; -+ if (second_level && (s->buf_type != BATCH_BUFFER_INSTRUCTION)) { -+ gvt_vgpu_err("Jumping to 2nd level BB from RB is not allowed\n"); -+ return -EFAULT; -+ } -+ -+ s->saved_buf_addr_type = s->buf_addr_type; -+ addr_type_update_snb(s); -+ if (s->buf_type == RING_BUFFER_INSTRUCTION) { -+ s->ret_ip_gma_ring = s->ip_gma + cmd_length(s) * sizeof(u32); -+ s->buf_type = BATCH_BUFFER_INSTRUCTION; -+ } else if (second_level) { -+ s->buf_type = BATCH_BUFFER_2ND_LEVEL; -+ s->ret_ip_gma_bb = s->ip_gma + cmd_length(s) * sizeof(u32); -+ s->ret_bb_va = s->ip_va + cmd_length(s) * sizeof(u32); -+ } -+ -+ if (batch_buffer_needs_scan(s)) { -+ ret = perform_bb_shadow(s); -+ if (ret < 0) -+ gvt_vgpu_err("invalid shadow batch buffer\n"); -+ } else { -+ /* emulate a batch buffer end to do return right */ -+ ret = cmd_handler_mi_batch_buffer_end(s); -+ if (ret < 0) -+ return ret; -+ } -+ return ret; -+} -+ -+static int mi_noop_index; -+ -+static const struct cmd_info cmd_info[] = { -+ {"MI_NOOP", OP_MI_NOOP, F_LEN_CONST, R_ALL, D_ALL, 0, 1, NULL}, -+ -+ {"MI_SET_PREDICATE", OP_MI_SET_PREDICATE, F_LEN_CONST, R_ALL, D_ALL, -+ 0, 1, NULL}, -+ -+ {"MI_USER_INTERRUPT", OP_MI_USER_INTERRUPT, F_LEN_CONST, R_ALL, D_ALL, -+ 0, 1, cmd_handler_mi_user_interrupt}, -+ -+ {"MI_WAIT_FOR_EVENT", OP_MI_WAIT_FOR_EVENT, F_LEN_CONST, R_RCS | R_BCS, -+ D_ALL, 0, 1, cmd_handler_mi_wait_for_event}, -+ -+ {"MI_FLUSH", OP_MI_FLUSH, F_LEN_CONST, R_ALL, D_ALL, 0, 1, NULL}, -+ -+ {"MI_ARB_CHECK", OP_MI_ARB_CHECK, F_LEN_CONST, R_ALL, D_ALL, 0, 1, -+ NULL}, -+ -+ {"MI_RS_CONTROL", OP_MI_RS_CONTROL, F_LEN_CONST, R_RCS, D_ALL, 0, 1, -+ NULL}, -+ -+ {"MI_REPORT_HEAD", OP_MI_REPORT_HEAD, F_LEN_CONST, R_ALL, D_ALL, 0, 1, -+ NULL}, -+ -+ {"MI_ARB_ON_OFF", OP_MI_ARB_ON_OFF, F_LEN_CONST, R_ALL, D_ALL, 0, 1, -+ NULL}, -+ -+ {"MI_URB_ATOMIC_ALLOC", OP_MI_URB_ATOMIC_ALLOC, F_LEN_CONST, R_RCS, -+ D_ALL, 0, 1, NULL}, -+ -+ {"MI_BATCH_BUFFER_END", OP_MI_BATCH_BUFFER_END, -+ F_IP_ADVANCE_CUSTOM | F_LEN_CONST, R_ALL, D_ALL, 0, 1, -+ cmd_handler_mi_batch_buffer_end}, -+ -+ {"MI_SUSPEND_FLUSH", OP_MI_SUSPEND_FLUSH, F_LEN_CONST, R_ALL, D_ALL, -+ 0, 1, NULL}, -+ -+ {"MI_PREDICATE", OP_MI_PREDICATE, F_LEN_CONST, R_RCS, D_ALL, 0, 1, -+ NULL}, -+ -+ {"MI_TOPOLOGY_FILTER", OP_MI_TOPOLOGY_FILTER, F_LEN_CONST, R_ALL, -+ D_ALL, 0, 1, NULL}, -+ -+ {"MI_SET_APPID", OP_MI_SET_APPID, F_LEN_CONST, R_ALL, D_ALL, 0, 1, -+ NULL}, -+ -+ {"MI_RS_CONTEXT", OP_MI_RS_CONTEXT, F_LEN_CONST, R_RCS, D_ALL, 0, 1, -+ NULL}, -+ -+ {"MI_DISPLAY_FLIP", OP_MI_DISPLAY_FLIP, F_LEN_VAR | F_POST_HANDLE, -+ R_RCS | R_BCS, D_ALL, 0, 8, cmd_handler_mi_display_flip}, -+ -+ {"MI_SEMAPHORE_MBOX", OP_MI_SEMAPHORE_MBOX, F_LEN_VAR, R_ALL, D_ALL, -+ 0, 8, NULL}, -+ -+ {"MI_MATH", OP_MI_MATH, F_LEN_VAR, R_ALL, D_ALL, 0, 8, NULL}, -+ -+ {"MI_URB_CLEAR", OP_MI_URB_CLEAR, F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"MI_SEMAPHORE_SIGNAL", OP_MI_SEMAPHORE_SIGNAL, F_LEN_VAR, R_ALL, -+ D_BDW_PLUS, 0, 8, NULL}, -+ -+ {"MI_SEMAPHORE_WAIT", OP_MI_SEMAPHORE_WAIT, F_LEN_VAR, R_ALL, -+ D_BDW_PLUS, ADDR_FIX_1(2), 8, cmd_handler_mi_semaphore_wait}, -+ -+ {"MI_STORE_DATA_IMM", OP_MI_STORE_DATA_IMM, F_LEN_VAR, R_ALL, D_BDW_PLUS, -+ ADDR_FIX_1(1), 10, cmd_handler_mi_store_data_imm}, -+ -+ {"MI_STORE_DATA_INDEX", OP_MI_STORE_DATA_INDEX, F_LEN_VAR, R_ALL, D_ALL, -+ 0, 8, cmd_handler_mi_store_data_index}, -+ -+ {"MI_LOAD_REGISTER_IMM", OP_MI_LOAD_REGISTER_IMM, F_LEN_VAR, R_ALL, -+ D_ALL, 0, 8, cmd_handler_lri}, -+ -+ {"MI_UPDATE_GTT", OP_MI_UPDATE_GTT, F_LEN_VAR, R_ALL, D_BDW_PLUS, 0, 10, -+ cmd_handler_mi_update_gtt}, -+ -+ {"MI_STORE_REGISTER_MEM", OP_MI_STORE_REGISTER_MEM, F_LEN_VAR, R_ALL, -+ D_ALL, ADDR_FIX_1(2), 8, cmd_handler_srm}, -+ -+ {"MI_FLUSH_DW", OP_MI_FLUSH_DW, F_LEN_VAR, R_ALL, D_ALL, 0, 6, -+ cmd_handler_mi_flush_dw}, -+ -+ {"MI_CLFLUSH", OP_MI_CLFLUSH, F_LEN_VAR, R_ALL, D_ALL, ADDR_FIX_1(1), -+ 10, cmd_handler_mi_clflush}, -+ -+ {"MI_REPORT_PERF_COUNT", OP_MI_REPORT_PERF_COUNT, F_LEN_VAR, R_ALL, -+ D_ALL, ADDR_FIX_1(1), 6, cmd_handler_mi_report_perf_count}, -+ -+ {"MI_LOAD_REGISTER_MEM", OP_MI_LOAD_REGISTER_MEM, F_LEN_VAR, R_ALL, -+ D_ALL, ADDR_FIX_1(2), 8, cmd_handler_lrm}, -+ -+ {"MI_LOAD_REGISTER_REG", OP_MI_LOAD_REGISTER_REG, F_LEN_VAR, R_ALL, -+ D_ALL, 0, 8, cmd_handler_lrr}, -+ -+ {"MI_RS_STORE_DATA_IMM", OP_MI_RS_STORE_DATA_IMM, F_LEN_VAR, R_RCS, -+ D_ALL, 0, 8, NULL}, -+ -+ {"MI_LOAD_URB_MEM", OP_MI_LOAD_URB_MEM, F_LEN_VAR, R_RCS, D_ALL, -+ ADDR_FIX_1(2), 8, NULL}, -+ -+ {"MI_STORE_URM_MEM", OP_MI_STORE_URM_MEM, F_LEN_VAR, R_RCS, D_ALL, -+ ADDR_FIX_1(2), 8, NULL}, -+ -+ {"MI_OP_2E", OP_MI_2E, F_LEN_VAR, R_ALL, D_BDW_PLUS, ADDR_FIX_2(1, 2), -+ 8, cmd_handler_mi_op_2e}, -+ -+ {"MI_OP_2F", OP_MI_2F, F_LEN_VAR, R_ALL, D_BDW_PLUS, ADDR_FIX_1(1), -+ 8, cmd_handler_mi_op_2f}, -+ -+ {"MI_BATCH_BUFFER_START", OP_MI_BATCH_BUFFER_START, -+ F_IP_ADVANCE_CUSTOM, R_ALL, D_ALL, 0, 8, -+ cmd_handler_mi_batch_buffer_start}, -+ -+ {"MI_CONDITIONAL_BATCH_BUFFER_END", OP_MI_CONDITIONAL_BATCH_BUFFER_END, -+ F_LEN_VAR, R_ALL, D_ALL, ADDR_FIX_1(2), 8, -+ cmd_handler_mi_conditional_batch_buffer_end}, -+ -+ {"MI_LOAD_SCAN_LINES_INCL", OP_MI_LOAD_SCAN_LINES_INCL, F_LEN_CONST, -+ R_RCS | R_BCS, D_ALL, 0, 2, NULL}, -+ -+ {"XY_SETUP_BLT", OP_XY_SETUP_BLT, F_LEN_VAR, R_BCS, D_ALL, -+ ADDR_FIX_2(4, 7), 8, NULL}, -+ -+ {"XY_SETUP_CLIP_BLT", OP_XY_SETUP_CLIP_BLT, F_LEN_VAR, R_BCS, D_ALL, -+ 0, 8, NULL}, -+ -+ {"XY_SETUP_MONO_PATTERN_SL_BLT", OP_XY_SETUP_MONO_PATTERN_SL_BLT, -+ F_LEN_VAR, R_BCS, D_ALL, ADDR_FIX_1(4), 8, NULL}, -+ -+ {"XY_PIXEL_BLT", OP_XY_PIXEL_BLT, F_LEN_VAR, R_BCS, D_ALL, 0, 8, NULL}, -+ -+ {"XY_SCANLINES_BLT", OP_XY_SCANLINES_BLT, F_LEN_VAR, R_BCS, D_ALL, -+ 0, 8, NULL}, -+ -+ {"XY_TEXT_BLT", OP_XY_TEXT_BLT, F_LEN_VAR, R_BCS, D_ALL, -+ ADDR_FIX_1(3), 8, NULL}, -+ -+ {"XY_TEXT_IMMEDIATE_BLT", OP_XY_TEXT_IMMEDIATE_BLT, F_LEN_VAR, R_BCS, -+ D_ALL, 0, 8, NULL}, -+ -+ {"XY_COLOR_BLT", OP_XY_COLOR_BLT, F_LEN_VAR, R_BCS, D_ALL, -+ ADDR_FIX_1(4), 8, NULL}, -+ -+ {"XY_PAT_BLT", OP_XY_PAT_BLT, F_LEN_VAR, R_BCS, D_ALL, -+ ADDR_FIX_2(4, 5), 8, NULL}, -+ -+ {"XY_MONO_PAT_BLT", OP_XY_MONO_PAT_BLT, F_LEN_VAR, R_BCS, D_ALL, -+ ADDR_FIX_1(4), 8, NULL}, -+ -+ {"XY_SRC_COPY_BLT", OP_XY_SRC_COPY_BLT, F_LEN_VAR, R_BCS, D_ALL, -+ ADDR_FIX_2(4, 7), 8, NULL}, -+ -+ {"XY_MONO_SRC_COPY_BLT", OP_XY_MONO_SRC_COPY_BLT, F_LEN_VAR, R_BCS, -+ D_ALL, ADDR_FIX_2(4, 5), 8, NULL}, -+ -+ {"XY_FULL_BLT", OP_XY_FULL_BLT, F_LEN_VAR, R_BCS, D_ALL, 0, 8, NULL}, -+ -+ {"XY_FULL_MONO_SRC_BLT", OP_XY_FULL_MONO_SRC_BLT, F_LEN_VAR, R_BCS, -+ D_ALL, ADDR_FIX_3(4, 5, 8), 8, NULL}, -+ -+ {"XY_FULL_MONO_PATTERN_BLT", OP_XY_FULL_MONO_PATTERN_BLT, F_LEN_VAR, -+ R_BCS, D_ALL, ADDR_FIX_2(4, 7), 8, NULL}, -+ -+ {"XY_FULL_MONO_PATTERN_MONO_SRC_BLT", -+ OP_XY_FULL_MONO_PATTERN_MONO_SRC_BLT, -+ F_LEN_VAR, R_BCS, D_ALL, ADDR_FIX_2(4, 5), 8, NULL}, -+ -+ {"XY_MONO_PAT_FIXED_BLT", OP_XY_MONO_PAT_FIXED_BLT, F_LEN_VAR, R_BCS, -+ D_ALL, ADDR_FIX_1(4), 8, NULL}, -+ -+ {"XY_MONO_SRC_COPY_IMMEDIATE_BLT", OP_XY_MONO_SRC_COPY_IMMEDIATE_BLT, -+ F_LEN_VAR, R_BCS, D_ALL, ADDR_FIX_1(4), 8, NULL}, -+ -+ {"XY_PAT_BLT_IMMEDIATE", OP_XY_PAT_BLT_IMMEDIATE, F_LEN_VAR, R_BCS, -+ D_ALL, ADDR_FIX_1(4), 8, NULL}, -+ -+ {"XY_SRC_COPY_CHROMA_BLT", OP_XY_SRC_COPY_CHROMA_BLT, F_LEN_VAR, R_BCS, -+ D_ALL, ADDR_FIX_2(4, 7), 8, NULL}, -+ -+ {"XY_FULL_IMMEDIATE_PATTERN_BLT", OP_XY_FULL_IMMEDIATE_PATTERN_BLT, -+ F_LEN_VAR, R_BCS, D_ALL, ADDR_FIX_2(4, 7), 8, NULL}, -+ -+ {"XY_FULL_MONO_SRC_IMMEDIATE_PATTERN_BLT", -+ OP_XY_FULL_MONO_SRC_IMMEDIATE_PATTERN_BLT, -+ F_LEN_VAR, R_BCS, D_ALL, ADDR_FIX_2(4, 5), 8, NULL}, -+ -+ {"XY_PAT_CHROMA_BLT", OP_XY_PAT_CHROMA_BLT, F_LEN_VAR, R_BCS, D_ALL, -+ ADDR_FIX_2(4, 5), 8, NULL}, -+ -+ {"XY_PAT_CHROMA_BLT_IMMEDIATE", OP_XY_PAT_CHROMA_BLT_IMMEDIATE, -+ F_LEN_VAR, R_BCS, D_ALL, ADDR_FIX_1(4), 8, NULL}, -+ -+ {"3DSTATE_VIEWPORT_STATE_POINTERS_SF_CLIP", -+ OP_3DSTATE_VIEWPORT_STATE_POINTERS_SF_CLIP, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_VIEWPORT_STATE_POINTERS_CC", -+ OP_3DSTATE_VIEWPORT_STATE_POINTERS_CC, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_BLEND_STATE_POINTERS", -+ OP_3DSTATE_BLEND_STATE_POINTERS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_DEPTH_STENCIL_STATE_POINTERS", -+ OP_3DSTATE_DEPTH_STENCIL_STATE_POINTERS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_BINDING_TABLE_POINTERS_VS", -+ OP_3DSTATE_BINDING_TABLE_POINTERS_VS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_BINDING_TABLE_POINTERS_HS", -+ OP_3DSTATE_BINDING_TABLE_POINTERS_HS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_BINDING_TABLE_POINTERS_DS", -+ OP_3DSTATE_BINDING_TABLE_POINTERS_DS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_BINDING_TABLE_POINTERS_GS", -+ OP_3DSTATE_BINDING_TABLE_POINTERS_GS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_BINDING_TABLE_POINTERS_PS", -+ OP_3DSTATE_BINDING_TABLE_POINTERS_PS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_SAMPLER_STATE_POINTERS_VS", -+ OP_3DSTATE_SAMPLER_STATE_POINTERS_VS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_SAMPLER_STATE_POINTERS_HS", -+ OP_3DSTATE_SAMPLER_STATE_POINTERS_HS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_SAMPLER_STATE_POINTERS_DS", -+ OP_3DSTATE_SAMPLER_STATE_POINTERS_DS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_SAMPLER_STATE_POINTERS_GS", -+ OP_3DSTATE_SAMPLER_STATE_POINTERS_GS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_SAMPLER_STATE_POINTERS_PS", -+ OP_3DSTATE_SAMPLER_STATE_POINTERS_PS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_URB_VS", OP_3DSTATE_URB_VS, F_LEN_VAR, R_RCS, D_ALL, -+ 0, 8, NULL}, -+ -+ {"3DSTATE_URB_HS", OP_3DSTATE_URB_HS, F_LEN_VAR, R_RCS, D_ALL, -+ 0, 8, NULL}, -+ -+ {"3DSTATE_URB_DS", OP_3DSTATE_URB_DS, F_LEN_VAR, R_RCS, D_ALL, -+ 0, 8, NULL}, -+ -+ {"3DSTATE_URB_GS", OP_3DSTATE_URB_GS, F_LEN_VAR, R_RCS, D_ALL, -+ 0, 8, NULL}, -+ -+ {"3DSTATE_GATHER_CONSTANT_VS", OP_3DSTATE_GATHER_CONSTANT_VS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_GATHER_CONSTANT_GS", OP_3DSTATE_GATHER_CONSTANT_GS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_GATHER_CONSTANT_HS", OP_3DSTATE_GATHER_CONSTANT_HS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_GATHER_CONSTANT_DS", OP_3DSTATE_GATHER_CONSTANT_DS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_GATHER_CONSTANT_PS", OP_3DSTATE_GATHER_CONSTANT_PS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_DX9_CONSTANTF_VS", OP_3DSTATE_DX9_CONSTANTF_VS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 11, NULL}, -+ -+ {"3DSTATE_DX9_CONSTANTF_PS", OP_3DSTATE_DX9_CONSTANTF_PS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 11, NULL}, -+ -+ {"3DSTATE_DX9_CONSTANTI_VS", OP_3DSTATE_DX9_CONSTANTI_VS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_DX9_CONSTANTI_PS", OP_3DSTATE_DX9_CONSTANTI_PS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_DX9_CONSTANTB_VS", OP_3DSTATE_DX9_CONSTANTB_VS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_DX9_CONSTANTB_PS", OP_3DSTATE_DX9_CONSTANTB_PS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_DX9_LOCAL_VALID_VS", OP_3DSTATE_DX9_LOCAL_VALID_VS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_DX9_LOCAL_VALID_PS", OP_3DSTATE_DX9_LOCAL_VALID_PS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_DX9_GENERATE_ACTIVE_VS", OP_3DSTATE_DX9_GENERATE_ACTIVE_VS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_DX9_GENERATE_ACTIVE_PS", OP_3DSTATE_DX9_GENERATE_ACTIVE_PS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_BINDING_TABLE_EDIT_VS", OP_3DSTATE_BINDING_TABLE_EDIT_VS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 9, NULL}, -+ -+ {"3DSTATE_BINDING_TABLE_EDIT_GS", OP_3DSTATE_BINDING_TABLE_EDIT_GS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 9, NULL}, -+ -+ {"3DSTATE_BINDING_TABLE_EDIT_HS", OP_3DSTATE_BINDING_TABLE_EDIT_HS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 9, NULL}, -+ -+ {"3DSTATE_BINDING_TABLE_EDIT_DS", OP_3DSTATE_BINDING_TABLE_EDIT_DS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 9, NULL}, -+ -+ {"3DSTATE_BINDING_TABLE_EDIT_PS", OP_3DSTATE_BINDING_TABLE_EDIT_PS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 9, NULL}, -+ -+ {"3DSTATE_VF_INSTANCING", OP_3DSTATE_VF_INSTANCING, F_LEN_VAR, R_RCS, -+ D_BDW_PLUS, 0, 8, NULL}, -+ -+ {"3DSTATE_VF_SGVS", OP_3DSTATE_VF_SGVS, F_LEN_VAR, R_RCS, D_BDW_PLUS, 0, 8, -+ NULL}, -+ -+ {"3DSTATE_VF_TOPOLOGY", OP_3DSTATE_VF_TOPOLOGY, F_LEN_VAR, R_RCS, -+ D_BDW_PLUS, 0, 8, NULL}, -+ -+ {"3DSTATE_WM_CHROMAKEY", OP_3DSTATE_WM_CHROMAKEY, F_LEN_VAR, R_RCS, -+ D_BDW_PLUS, 0, 8, NULL}, -+ -+ {"3DSTATE_PS_BLEND", OP_3DSTATE_PS_BLEND, F_LEN_VAR, R_RCS, D_BDW_PLUS, 0, -+ 8, NULL}, -+ -+ {"3DSTATE_WM_DEPTH_STENCIL", OP_3DSTATE_WM_DEPTH_STENCIL, F_LEN_VAR, -+ R_RCS, D_BDW_PLUS, 0, 8, NULL}, -+ -+ {"3DSTATE_PS_EXTRA", OP_3DSTATE_PS_EXTRA, F_LEN_VAR, R_RCS, D_BDW_PLUS, 0, -+ 8, NULL}, -+ -+ {"3DSTATE_RASTER", OP_3DSTATE_RASTER, F_LEN_VAR, R_RCS, D_BDW_PLUS, 0, 8, -+ NULL}, -+ -+ {"3DSTATE_SBE_SWIZ", OP_3DSTATE_SBE_SWIZ, F_LEN_VAR, R_RCS, D_BDW_PLUS, 0, 8, -+ NULL}, -+ -+ {"3DSTATE_WM_HZ_OP", OP_3DSTATE_WM_HZ_OP, F_LEN_VAR, R_RCS, D_BDW_PLUS, 0, 8, -+ NULL}, -+ -+ {"3DSTATE_VERTEX_BUFFERS", OP_3DSTATE_VERTEX_BUFFERS, F_LEN_VAR, R_RCS, -+ D_BDW_PLUS, 0, 8, NULL}, -+ -+ {"3DSTATE_VERTEX_ELEMENTS", OP_3DSTATE_VERTEX_ELEMENTS, F_LEN_VAR, -+ R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_INDEX_BUFFER", OP_3DSTATE_INDEX_BUFFER, F_LEN_VAR, R_RCS, -+ D_BDW_PLUS, ADDR_FIX_1(2), 8, NULL}, -+ -+ {"3DSTATE_VF_STATISTICS", OP_3DSTATE_VF_STATISTICS, F_LEN_CONST, -+ R_RCS, D_ALL, 0, 1, NULL}, -+ -+ {"3DSTATE_VF", OP_3DSTATE_VF, F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_CC_STATE_POINTERS", OP_3DSTATE_CC_STATE_POINTERS, F_LEN_VAR, -+ R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_SCISSOR_STATE_POINTERS", OP_3DSTATE_SCISSOR_STATE_POINTERS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_GS", OP_3DSTATE_GS, F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_CLIP", OP_3DSTATE_CLIP, F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_WM", OP_3DSTATE_WM, F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_CONSTANT_GS", OP_3DSTATE_CONSTANT_GS, F_LEN_VAR, R_RCS, -+ D_BDW_PLUS, 0, 8, NULL}, -+ -+ {"3DSTATE_CONSTANT_PS", OP_3DSTATE_CONSTANT_PS, F_LEN_VAR, R_RCS, -+ D_BDW_PLUS, 0, 8, NULL}, -+ -+ {"3DSTATE_SAMPLE_MASK", OP_3DSTATE_SAMPLE_MASK, F_LEN_VAR, R_RCS, -+ D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_CONSTANT_HS", OP_3DSTATE_CONSTANT_HS, F_LEN_VAR, R_RCS, -+ D_BDW_PLUS, 0, 8, NULL}, -+ -+ {"3DSTATE_CONSTANT_DS", OP_3DSTATE_CONSTANT_DS, F_LEN_VAR, R_RCS, -+ D_BDW_PLUS, 0, 8, NULL}, -+ -+ {"3DSTATE_HS", OP_3DSTATE_HS, F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_TE", OP_3DSTATE_TE, F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_DS", OP_3DSTATE_DS, F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_STREAMOUT", OP_3DSTATE_STREAMOUT, F_LEN_VAR, R_RCS, -+ D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_SBE", OP_3DSTATE_SBE, F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_PS", OP_3DSTATE_PS, F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_DRAWING_RECTANGLE", OP_3DSTATE_DRAWING_RECTANGLE, F_LEN_VAR, -+ R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_SAMPLER_PALETTE_LOAD0", OP_3DSTATE_SAMPLER_PALETTE_LOAD0, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_CHROMA_KEY", OP_3DSTATE_CHROMA_KEY, F_LEN_VAR, R_RCS, D_ALL, -+ 0, 8, NULL}, -+ -+ {"3DSTATE_DEPTH_BUFFER", OP_3DSTATE_DEPTH_BUFFER, F_LEN_VAR, R_RCS, -+ D_ALL, ADDR_FIX_1(2), 8, NULL}, -+ -+ {"3DSTATE_POLY_STIPPLE_OFFSET", OP_3DSTATE_POLY_STIPPLE_OFFSET, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_POLY_STIPPLE_PATTERN", OP_3DSTATE_POLY_STIPPLE_PATTERN, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_LINE_STIPPLE", OP_3DSTATE_LINE_STIPPLE, F_LEN_VAR, R_RCS, -+ D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_AA_LINE_PARAMS", OP_3DSTATE_AA_LINE_PARAMS, F_LEN_VAR, R_RCS, -+ D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_GS_SVB_INDEX", OP_3DSTATE_GS_SVB_INDEX, F_LEN_VAR, R_RCS, -+ D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_SAMPLER_PALETTE_LOAD1", OP_3DSTATE_SAMPLER_PALETTE_LOAD1, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_MULTISAMPLE", OP_3DSTATE_MULTISAMPLE_BDW, F_LEN_VAR, R_RCS, -+ D_BDW_PLUS, 0, 8, NULL}, -+ -+ {"3DSTATE_STENCIL_BUFFER", OP_3DSTATE_STENCIL_BUFFER, F_LEN_VAR, R_RCS, -+ D_ALL, ADDR_FIX_1(2), 8, NULL}, -+ -+ {"3DSTATE_HIER_DEPTH_BUFFER", OP_3DSTATE_HIER_DEPTH_BUFFER, F_LEN_VAR, -+ R_RCS, D_ALL, ADDR_FIX_1(2), 8, NULL}, -+ -+ {"3DSTATE_CLEAR_PARAMS", OP_3DSTATE_CLEAR_PARAMS, F_LEN_VAR, -+ R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_PUSH_CONSTANT_ALLOC_VS", OP_3DSTATE_PUSH_CONSTANT_ALLOC_VS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_PUSH_CONSTANT_ALLOC_HS", OP_3DSTATE_PUSH_CONSTANT_ALLOC_HS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_PUSH_CONSTANT_ALLOC_DS", OP_3DSTATE_PUSH_CONSTANT_ALLOC_DS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_PUSH_CONSTANT_ALLOC_GS", OP_3DSTATE_PUSH_CONSTANT_ALLOC_GS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_PUSH_CONSTANT_ALLOC_PS", OP_3DSTATE_PUSH_CONSTANT_ALLOC_PS, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_MONOFILTER_SIZE", OP_3DSTATE_MONOFILTER_SIZE, F_LEN_VAR, -+ R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_SO_DECL_LIST", OP_3DSTATE_SO_DECL_LIST, F_LEN_VAR, R_RCS, -+ D_ALL, 0, 9, NULL}, -+ -+ {"3DSTATE_SO_BUFFER", OP_3DSTATE_SO_BUFFER, F_LEN_VAR, R_RCS, D_BDW_PLUS, -+ ADDR_FIX_2(2, 4), 8, NULL}, -+ -+ {"3DSTATE_BINDING_TABLE_POOL_ALLOC", -+ OP_3DSTATE_BINDING_TABLE_POOL_ALLOC, -+ F_LEN_VAR, R_RCS, D_BDW_PLUS, ADDR_FIX_1(1), 8, NULL}, -+ -+ {"3DSTATE_GATHER_POOL_ALLOC", OP_3DSTATE_GATHER_POOL_ALLOC, -+ F_LEN_VAR, R_RCS, D_BDW_PLUS, ADDR_FIX_1(1), 8, NULL}, -+ -+ {"3DSTATE_DX9_CONSTANT_BUFFER_POOL_ALLOC", -+ OP_3DSTATE_DX9_CONSTANT_BUFFER_POOL_ALLOC, -+ F_LEN_VAR, R_RCS, D_BDW_PLUS, ADDR_FIX_1(1), 8, NULL}, -+ -+ {"3DSTATE_SAMPLE_PATTERN", OP_3DSTATE_SAMPLE_PATTERN, F_LEN_VAR, R_RCS, -+ D_BDW_PLUS, 0, 8, NULL}, -+ -+ {"PIPE_CONTROL", OP_PIPE_CONTROL, F_LEN_VAR, R_RCS, D_ALL, -+ ADDR_FIX_1(2), 8, cmd_handler_pipe_control}, -+ -+ {"3DPRIMITIVE", OP_3DPRIMITIVE, F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"PIPELINE_SELECT", OP_PIPELINE_SELECT, F_LEN_CONST, R_RCS, D_ALL, 0, -+ 1, NULL}, -+ -+ {"STATE_PREFETCH", OP_STATE_PREFETCH, F_LEN_VAR, R_RCS, D_ALL, -+ ADDR_FIX_1(1), 8, NULL}, -+ -+ {"STATE_SIP", OP_STATE_SIP, F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"STATE_BASE_ADDRESS", OP_STATE_BASE_ADDRESS, F_LEN_VAR, R_RCS, D_BDW_PLUS, -+ ADDR_FIX_5(1, 3, 4, 5, 6), 8, NULL}, -+ -+ {"OP_3D_MEDIA_0_1_4", OP_3D_MEDIA_0_1_4, F_LEN_VAR, R_RCS, D_ALL, -+ ADDR_FIX_1(1), 8, NULL}, -+ -+ {"3DSTATE_VS", OP_3DSTATE_VS, F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_SF", OP_3DSTATE_SF, F_LEN_VAR, R_RCS, D_ALL, 0, 8, NULL}, -+ -+ {"3DSTATE_CONSTANT_VS", OP_3DSTATE_CONSTANT_VS, F_LEN_VAR, R_RCS, D_BDW_PLUS, -+ 0, 8, NULL}, -+ -+ {"3DSTATE_COMPONENT_PACKING", OP_3DSTATE_COMPONENT_PACKING, F_LEN_VAR, R_RCS, -+ D_SKL_PLUS, 0, 8, NULL}, -+ -+ {"MEDIA_INTERFACE_DESCRIPTOR_LOAD", OP_MEDIA_INTERFACE_DESCRIPTOR_LOAD, -+ F_LEN_VAR, R_RCS, D_ALL, 0, 16, NULL}, -+ -+ {"MEDIA_GATEWAY_STATE", OP_MEDIA_GATEWAY_STATE, F_LEN_VAR, R_RCS, D_ALL, -+ 0, 16, NULL}, -+ -+ {"MEDIA_STATE_FLUSH", OP_MEDIA_STATE_FLUSH, F_LEN_VAR, R_RCS, D_ALL, -+ 0, 16, NULL}, -+ -+ {"MEDIA_POOL_STATE", OP_MEDIA_POOL_STATE, F_LEN_VAR, R_RCS, D_ALL, -+ 0, 16, NULL}, -+ -+ {"MEDIA_OBJECT", OP_MEDIA_OBJECT, F_LEN_VAR, R_RCS, D_ALL, 0, 16, NULL}, -+ -+ {"MEDIA_CURBE_LOAD", OP_MEDIA_CURBE_LOAD, F_LEN_VAR, R_RCS, D_ALL, -+ 0, 16, NULL}, -+ -+ {"MEDIA_OBJECT_PRT", OP_MEDIA_OBJECT_PRT, F_LEN_VAR, R_RCS, D_ALL, -+ 0, 16, NULL}, -+ -+ {"MEDIA_OBJECT_WALKER", OP_MEDIA_OBJECT_WALKER, F_LEN_VAR, R_RCS, D_ALL, -+ 0, 16, NULL}, -+ -+ {"GPGPU_WALKER", OP_GPGPU_WALKER, F_LEN_VAR, R_RCS, D_ALL, -+ 0, 8, NULL}, -+ -+ {"MEDIA_VFE_STATE", OP_MEDIA_VFE_STATE, F_LEN_VAR, R_RCS, D_ALL, 0, 16, -+ NULL}, -+ -+ {"3DSTATE_VF_STATISTICS_GM45", OP_3DSTATE_VF_STATISTICS_GM45, -+ F_LEN_CONST, R_ALL, D_ALL, 0, 1, NULL}, -+ -+ {"MFX_PIPE_MODE_SELECT", OP_MFX_PIPE_MODE_SELECT, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_SURFACE_STATE", OP_MFX_SURFACE_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_PIPE_BUF_ADDR_STATE", OP_MFX_PIPE_BUF_ADDR_STATE, F_LEN_VAR, -+ R_VCS, D_BDW_PLUS, 0, 12, NULL}, -+ -+ {"MFX_IND_OBJ_BASE_ADDR_STATE", OP_MFX_IND_OBJ_BASE_ADDR_STATE, -+ F_LEN_VAR, R_VCS, D_BDW_PLUS, 0, 12, NULL}, -+ -+ {"MFX_BSP_BUF_BASE_ADDR_STATE", OP_MFX_BSP_BUF_BASE_ADDR_STATE, -+ F_LEN_VAR, R_VCS, D_BDW_PLUS, ADDR_FIX_3(1, 3, 5), 12, NULL}, -+ -+ {"OP_2_0_0_5", OP_2_0_0_5, F_LEN_VAR, R_VCS, D_BDW_PLUS, 0, 12, NULL}, -+ -+ {"MFX_STATE_POINTER", OP_MFX_STATE_POINTER, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_QM_STATE", OP_MFX_QM_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_FQM_STATE", OP_MFX_FQM_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_PAK_INSERT_OBJECT", OP_MFX_PAK_INSERT_OBJECT, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_STITCH_OBJECT", OP_MFX_STITCH_OBJECT, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFD_IT_OBJECT", OP_MFD_IT_OBJECT, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_WAIT", OP_MFX_WAIT, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 6, NULL}, -+ -+ {"MFX_AVC_IMG_STATE", OP_MFX_AVC_IMG_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_AVC_QM_STATE", OP_MFX_AVC_QM_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_AVC_DIRECTMODE_STATE", OP_MFX_AVC_DIRECTMODE_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_AVC_SLICE_STATE", OP_MFX_AVC_SLICE_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_AVC_REF_IDX_STATE", OP_MFX_AVC_REF_IDX_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_AVC_WEIGHTOFFSET_STATE", OP_MFX_AVC_WEIGHTOFFSET_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFD_AVC_PICID_STATE", OP_MFD_AVC_PICID_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ {"MFD_AVC_DPB_STATE", OP_MFD_AVC_DPB_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFD_AVC_BSD_OBJECT", OP_MFD_AVC_BSD_OBJECT, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFD_AVC_SLICEADDR", OP_MFD_AVC_SLICEADDR, F_LEN_VAR, -+ R_VCS, D_ALL, ADDR_FIX_1(2), 12, NULL}, -+ -+ {"MFC_AVC_PAK_OBJECT", OP_MFC_AVC_PAK_OBJECT, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_VC1_PRED_PIPE_STATE", OP_MFX_VC1_PRED_PIPE_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_VC1_DIRECTMODE_STATE", OP_MFX_VC1_DIRECTMODE_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFD_VC1_SHORT_PIC_STATE", OP_MFD_VC1_SHORT_PIC_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFD_VC1_LONG_PIC_STATE", OP_MFD_VC1_LONG_PIC_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFD_VC1_BSD_OBJECT", OP_MFD_VC1_BSD_OBJECT, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFC_MPEG2_SLICEGROUP_STATE", OP_MFC_MPEG2_SLICEGROUP_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFC_MPEG2_PAK_OBJECT", OP_MFC_MPEG2_PAK_OBJECT, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_MPEG2_PIC_STATE", OP_MFX_MPEG2_PIC_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_MPEG2_QM_STATE", OP_MFX_MPEG2_QM_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFD_MPEG2_BSD_OBJECT", OP_MFD_MPEG2_BSD_OBJECT, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_2_6_0_0", OP_MFX_2_6_0_0, F_LEN_VAR, R_VCS, D_ALL, -+ 0, 16, NULL}, -+ -+ {"MFX_2_6_0_9", OP_MFX_2_6_0_9, F_LEN_VAR, R_VCS, D_ALL, 0, 16, NULL}, -+ -+ {"MFX_2_6_0_8", OP_MFX_2_6_0_8, F_LEN_VAR, R_VCS, D_ALL, 0, 16, NULL}, -+ -+ {"MFX_JPEG_PIC_STATE", OP_MFX_JPEG_PIC_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFX_JPEG_HUFF_TABLE_STATE", OP_MFX_JPEG_HUFF_TABLE_STATE, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"MFD_JPEG_BSD_OBJECT", OP_MFD_JPEG_BSD_OBJECT, F_LEN_VAR, -+ R_VCS, D_ALL, 0, 12, NULL}, -+ -+ {"VEBOX_STATE", OP_VEB_STATE, F_LEN_VAR, R_VECS, D_ALL, 0, 12, NULL}, -+ -+ {"VEBOX_SURFACE_STATE", OP_VEB_SURFACE_STATE, F_LEN_VAR, R_VECS, D_ALL, -+ 0, 12, NULL}, -+ -+ {"VEB_DI_IECP", OP_VEB_DNDI_IECP_STATE, F_LEN_VAR, R_VECS, D_BDW_PLUS, -+ 0, 12, NULL}, -+}; -+ -+static void add_cmd_entry(struct intel_gvt *gvt, struct cmd_entry *e) -+{ -+ hash_add(gvt->cmd_table, &e->hlist, e->info->opcode); -+} -+ -+/* call the cmd handler, and advance ip */ -+static int cmd_parser_exec(struct parser_exec_state *s) -+{ -+ struct intel_vgpu *vgpu = s->vgpu; -+ const struct cmd_info *info; -+ u32 cmd; -+ int ret = 0; -+ -+ cmd = cmd_val(s, 0); -+ -+ /* fastpath for MI_NOOP */ -+ if (cmd == MI_NOOP) -+ info = &cmd_info[mi_noop_index]; -+ else -+ info = get_cmd_info(s->vgpu->gvt, cmd, s->ring_id); -+ -+ if (info == NULL) { -+ gvt_vgpu_err("unknown cmd 0x%x, opcode=0x%x, addr_type=%s, ring %d, workload=%p\n", -+ cmd, get_opcode(cmd, s->ring_id), -+ (s->buf_addr_type == PPGTT_BUFFER) ? -+ "ppgtt" : "ggtt", s->ring_id, s->workload); -+ return -EBADRQC; -+ } -+ -+ s->info = info; -+ -+ trace_gvt_command(vgpu->id, s->ring_id, s->ip_gma, s->ip_va, -+ cmd_length(s), s->buf_type, s->buf_addr_type, -+ s->workload, info->name); -+ -+ if (info->handler) { -+ ret = info->handler(s); -+ if (ret < 0) { -+ gvt_vgpu_err("%s handler error\n", info->name); -+ return ret; -+ } -+ } -+ -+ if (!(info->flag & F_IP_ADVANCE_CUSTOM)) { -+ ret = cmd_advance_default(s); -+ if (ret) { -+ gvt_vgpu_err("%s IP advance error\n", info->name); -+ return ret; -+ } -+ } -+ return 0; -+} -+ -+static inline bool gma_out_of_range(unsigned long gma, -+ unsigned long gma_head, unsigned int gma_tail) -+{ -+ if (gma_tail >= gma_head) -+ return (gma < gma_head) || (gma > gma_tail); -+ else -+ return (gma > gma_tail) && (gma < gma_head); -+} -+ -+/* Keep the consistent return type, e.g EBADRQC for unknown -+ * cmd, EFAULT for invalid address, EPERM for nonpriv. later -+ * works as the input of VM healthy status. -+ */ -+static int command_scan(struct parser_exec_state *s, -+ unsigned long rb_head, unsigned long rb_tail, -+ unsigned long rb_start, unsigned long rb_len) -+{ -+ -+ unsigned long gma_head, gma_tail, gma_bottom; -+ int ret = 0; -+ struct intel_vgpu *vgpu = s->vgpu; -+ -+ gma_head = rb_start + rb_head; -+ gma_tail = rb_start + rb_tail; -+ gma_bottom = rb_start + rb_len; -+ -+ while (s->ip_gma != gma_tail) { -+ if (s->buf_type == RING_BUFFER_INSTRUCTION) { -+ if (!(s->ip_gma >= rb_start) || -+ !(s->ip_gma < gma_bottom)) { -+ gvt_vgpu_err("ip_gma %lx out of ring scope." -+ "(base:0x%lx, bottom: 0x%lx)\n", -+ s->ip_gma, rb_start, -+ gma_bottom); -+ parser_exec_state_dump(s); -+ return -EFAULT; -+ } -+ if (gma_out_of_range(s->ip_gma, gma_head, gma_tail)) { -+ gvt_vgpu_err("ip_gma %lx out of range." -+ "base 0x%lx head 0x%lx tail 0x%lx\n", -+ s->ip_gma, rb_start, -+ rb_head, rb_tail); -+ parser_exec_state_dump(s); -+ break; -+ } -+ } -+ ret = cmd_parser_exec(s); -+ if (ret) { -+ gvt_vgpu_err("cmd parser error\n"); -+ parser_exec_state_dump(s); -+ break; -+ } -+ } -+ -+ return ret; -+} -+ -+static int scan_workload(struct intel_vgpu_workload *workload) -+{ -+ unsigned long gma_head, gma_tail, gma_bottom; -+ struct parser_exec_state s; -+ int ret = 0; -+ -+ /* ring base is page aligned */ -+ if (WARN_ON(!IS_ALIGNED(workload->rb_start, I915_GTT_PAGE_SIZE))) -+ return -EINVAL; -+ -+ gma_head = workload->rb_start + workload->rb_head; -+ gma_tail = workload->rb_start + workload->rb_tail; -+ gma_bottom = workload->rb_start + _RING_CTL_BUF_SIZE(workload->rb_ctl); -+ -+ s.buf_type = RING_BUFFER_INSTRUCTION; -+ s.buf_addr_type = GTT_BUFFER; -+ s.vgpu = workload->vgpu; -+ s.ring_id = workload->ring_id; -+ s.ring_start = workload->rb_start; -+ s.ring_size = _RING_CTL_BUF_SIZE(workload->rb_ctl); -+ s.ring_head = gma_head; -+ s.ring_tail = gma_tail; -+ s.rb_va = workload->shadow_ring_buffer_va; -+ s.workload = workload; -+ s.is_ctx_wa = false; -+ -+ if ((bypass_scan_mask & (1 << workload->ring_id)) || -+ gma_head == gma_tail) -+ return 0; -+ -+ if (!intel_gvt_ggtt_validate_range(s.vgpu, s.ring_start, s.ring_size)) { -+ ret = -EINVAL; -+ goto out; -+ } -+ -+ ret = ip_gma_set(&s, gma_head); -+ if (ret) -+ goto out; -+ -+ ret = command_scan(&s, workload->rb_head, workload->rb_tail, -+ workload->rb_start, _RING_CTL_BUF_SIZE(workload->rb_ctl)); -+ -+out: -+ return ret; -+} -+ -+static int scan_wa_ctx(struct intel_shadow_wa_ctx *wa_ctx) -+{ -+ -+ unsigned long gma_head, gma_tail, gma_bottom, ring_size, ring_tail; -+ struct parser_exec_state s; -+ int ret = 0; -+ struct intel_vgpu_workload *workload = container_of(wa_ctx, -+ struct intel_vgpu_workload, -+ wa_ctx); -+ -+ /* ring base is page aligned */ -+ if (WARN_ON(!IS_ALIGNED(wa_ctx->indirect_ctx.guest_gma, -+ I915_GTT_PAGE_SIZE))) -+ return -EINVAL; -+ -+ ring_tail = wa_ctx->indirect_ctx.size + 3 * sizeof(u32); -+ ring_size = round_up(wa_ctx->indirect_ctx.size + CACHELINE_BYTES, -+ PAGE_SIZE); -+ gma_head = wa_ctx->indirect_ctx.guest_gma; -+ gma_tail = wa_ctx->indirect_ctx.guest_gma + ring_tail; -+ gma_bottom = wa_ctx->indirect_ctx.guest_gma + ring_size; -+ -+ s.buf_type = RING_BUFFER_INSTRUCTION; -+ s.buf_addr_type = GTT_BUFFER; -+ s.vgpu = workload->vgpu; -+ s.ring_id = workload->ring_id; -+ s.ring_start = wa_ctx->indirect_ctx.guest_gma; -+ s.ring_size = ring_size; -+ s.ring_head = gma_head; -+ s.ring_tail = gma_tail; -+ s.rb_va = wa_ctx->indirect_ctx.shadow_va; -+ s.workload = workload; -+ s.is_ctx_wa = true; -+ -+ if (!intel_gvt_ggtt_validate_range(s.vgpu, s.ring_start, s.ring_size)) { -+ ret = -EINVAL; -+ goto out; -+ } -+ -+ ret = ip_gma_set(&s, gma_head); -+ if (ret) -+ goto out; -+ -+ ret = command_scan(&s, 0, ring_tail, -+ wa_ctx->indirect_ctx.guest_gma, ring_size); -+out: -+ return ret; -+} -+ -+static int shadow_workload_ring_buffer(struct intel_vgpu_workload *workload) -+{ -+ struct intel_vgpu *vgpu = workload->vgpu; -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ unsigned long gma_head, gma_tail, gma_top, guest_rb_size; -+ void *shadow_ring_buffer_va; -+ int ring_id = workload->ring_id; -+ int ret; -+ -+ guest_rb_size = _RING_CTL_BUF_SIZE(workload->rb_ctl); -+ -+ /* calculate workload ring buffer size */ -+ workload->rb_len = (workload->rb_tail + guest_rb_size - -+ workload->rb_head) % guest_rb_size; -+ -+ gma_head = workload->rb_start + workload->rb_head; -+ gma_tail = workload->rb_start + workload->rb_tail; -+ gma_top = workload->rb_start + guest_rb_size; -+ -+ if (workload->rb_len > s->ring_scan_buffer_size[ring_id]) { -+ void *p; -+ -+ /* realloc the new ring buffer if needed */ -+ p = krealloc(s->ring_scan_buffer[ring_id], workload->rb_len, -+ GFP_KERNEL); -+ if (!p) { -+ gvt_vgpu_err("fail to re-alloc ring scan buffer\n"); -+ return -ENOMEM; -+ } -+ s->ring_scan_buffer[ring_id] = p; -+ s->ring_scan_buffer_size[ring_id] = workload->rb_len; -+ } -+ -+ shadow_ring_buffer_va = s->ring_scan_buffer[ring_id]; -+ -+ /* get shadow ring buffer va */ -+ workload->shadow_ring_buffer_va = shadow_ring_buffer_va; -+ -+ /* head > tail --> copy head <-> top */ -+ if (gma_head > gma_tail) { -+ ret = copy_gma_to_hva(vgpu, vgpu->gtt.ggtt_mm, -+ gma_head, gma_top, shadow_ring_buffer_va); -+ if (ret < 0) { -+ gvt_vgpu_err("fail to copy guest ring buffer\n"); -+ return ret; -+ } -+ shadow_ring_buffer_va += ret; -+ gma_head = workload->rb_start; -+ } -+ -+ /* copy head or start <-> tail */ -+ ret = copy_gma_to_hva(vgpu, vgpu->gtt.ggtt_mm, gma_head, gma_tail, -+ shadow_ring_buffer_va); -+ if (ret < 0) { -+ gvt_vgpu_err("fail to copy guest ring buffer\n"); -+ return ret; -+ } -+ return 0; -+} -+ -+int intel_gvt_scan_and_shadow_ringbuffer(struct intel_vgpu_workload *workload) -+{ -+ int ret; -+ struct intel_vgpu *vgpu = workload->vgpu; -+ -+ ret = shadow_workload_ring_buffer(workload); -+ if (ret) { -+ gvt_vgpu_err("fail to shadow workload ring_buffer\n"); -+ return ret; -+ } -+ -+ ret = scan_workload(workload); -+ if (ret) { -+ gvt_vgpu_err("scan workload error\n"); -+ return ret; -+ } -+ return 0; -+} -+ -+static int shadow_indirect_ctx(struct intel_shadow_wa_ctx *wa_ctx) -+{ -+ int ctx_size = wa_ctx->indirect_ctx.size; -+ unsigned long guest_gma = wa_ctx->indirect_ctx.guest_gma; -+ struct intel_vgpu_workload *workload = container_of(wa_ctx, -+ struct intel_vgpu_workload, -+ wa_ctx); -+ struct intel_vgpu *vgpu = workload->vgpu; -+ struct drm_i915_gem_object *obj; -+ int ret = 0; -+ void *map; -+ -+ obj = i915_gem_object_create(workload->vgpu->gvt->dev_priv, -+ roundup(ctx_size + CACHELINE_BYTES, -+ PAGE_SIZE)); -+ if (IS_ERR(obj)) -+ return PTR_ERR(obj); -+ -+ /* get the va of the shadow batch buffer */ -+ map = i915_gem_object_pin_map(obj, I915_MAP_WB); -+ if (IS_ERR(map)) { -+ gvt_vgpu_err("failed to vmap shadow indirect ctx\n"); -+ ret = PTR_ERR(map); -+ goto put_obj; -+ } -+ -+ ret = i915_gem_object_set_to_cpu_domain(obj, false); -+ if (ret) { -+ gvt_vgpu_err("failed to set shadow indirect ctx to CPU\n"); -+ goto unmap_src; -+ } -+ -+ ret = copy_gma_to_hva(workload->vgpu, -+ workload->vgpu->gtt.ggtt_mm, -+ guest_gma, guest_gma + ctx_size, -+ map); -+ if (ret < 0) { -+ gvt_vgpu_err("fail to copy guest indirect ctx\n"); -+ goto unmap_src; -+ } -+ -+ wa_ctx->indirect_ctx.obj = obj; -+ wa_ctx->indirect_ctx.shadow_va = map; -+ return 0; -+ -+unmap_src: -+ i915_gem_object_unpin_map(obj); -+put_obj: -+ i915_gem_object_put(obj); -+ return ret; -+} -+ -+static int combine_wa_ctx(struct intel_shadow_wa_ctx *wa_ctx) -+{ -+ u32 per_ctx_start[CACHELINE_DWORDS] = {0}; -+ unsigned char *bb_start_sva; -+ -+ if (!wa_ctx->per_ctx.valid) -+ return 0; -+ -+ per_ctx_start[0] = 0x18800001; -+ per_ctx_start[1] = wa_ctx->per_ctx.guest_gma; -+ -+ bb_start_sva = (unsigned char *)wa_ctx->indirect_ctx.shadow_va + -+ wa_ctx->indirect_ctx.size; -+ -+ memcpy(bb_start_sva, per_ctx_start, CACHELINE_BYTES); -+ -+ return 0; -+} -+ -+int intel_gvt_scan_and_shadow_wa_ctx(struct intel_shadow_wa_ctx *wa_ctx) -+{ -+ int ret; -+ struct intel_vgpu_workload *workload = container_of(wa_ctx, -+ struct intel_vgpu_workload, -+ wa_ctx); -+ struct intel_vgpu *vgpu = workload->vgpu; -+ -+ if (wa_ctx->indirect_ctx.size == 0) -+ return 0; -+ -+ ret = shadow_indirect_ctx(wa_ctx); -+ if (ret) { -+ gvt_vgpu_err("fail to shadow indirect ctx\n"); -+ return ret; -+ } -+ -+ combine_wa_ctx(wa_ctx); -+ -+ ret = scan_wa_ctx(wa_ctx); -+ if (ret) { -+ gvt_vgpu_err("scan wa ctx error\n"); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static const struct cmd_info *find_cmd_entry_any_ring(struct intel_gvt *gvt, -+ unsigned int opcode, unsigned long rings) -+{ -+ const struct cmd_info *info = NULL; -+ unsigned int ring; -+ -+ for_each_set_bit(ring, &rings, I915_NUM_ENGINES) { -+ info = find_cmd_entry(gvt, opcode, ring); -+ if (info) -+ break; -+ } -+ return info; -+} -+ -+static int init_cmd_table(struct intel_gvt *gvt) -+{ -+ int i; -+ struct cmd_entry *e; -+ const struct cmd_info *info; -+ unsigned int gen_type; -+ -+ gen_type = intel_gvt_get_device_type(gvt); -+ -+ for (i = 0; i < ARRAY_SIZE(cmd_info); i++) { -+ if (!(cmd_info[i].devices & gen_type)) -+ continue; -+ -+ e = kzalloc(sizeof(*e), GFP_KERNEL); -+ if (!e) -+ return -ENOMEM; -+ -+ e->info = &cmd_info[i]; -+ info = find_cmd_entry_any_ring(gvt, -+ e->info->opcode, e->info->rings); -+ if (info) { -+ gvt_err("%s %s duplicated\n", e->info->name, -+ info->name); -+ kfree(e); -+ return -EEXIST; -+ } -+ if (cmd_info[i].opcode == OP_MI_NOOP) -+ mi_noop_index = i; -+ -+ INIT_HLIST_NODE(&e->hlist); -+ add_cmd_entry(gvt, e); -+ gvt_dbg_cmd("add %-30s op %04x flag %x devs %02x rings %02x\n", -+ e->info->name, e->info->opcode, e->info->flag, -+ e->info->devices, e->info->rings); -+ } -+ return 0; -+} -+ -+static void clean_cmd_table(struct intel_gvt *gvt) -+{ -+ struct hlist_node *tmp; -+ struct cmd_entry *e; -+ int i; -+ -+ hash_for_each_safe(gvt->cmd_table, i, tmp, e, hlist) -+ kfree(e); -+ -+ hash_init(gvt->cmd_table); -+} -+ -+void intel_gvt_clean_cmd_parser(struct intel_gvt *gvt) -+{ -+ clean_cmd_table(gvt); -+} -+ -+int intel_gvt_init_cmd_parser(struct intel_gvt *gvt) -+{ -+ int ret; -+ -+ ret = init_cmd_table(gvt); -+ if (ret) { -+ intel_gvt_clean_cmd_parser(gvt); -+ return ret; -+ } -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/cmd_parser.h b/drivers/gpu/drm/i915_legacy/gvt/cmd_parser.h -new file mode 100644 -index 000000000000..286703643002 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/cmd_parser.h -@@ -0,0 +1,49 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Ke Yu -+ * Kevin Tian -+ * Zhiyuan Lv -+ * -+ * Contributors: -+ * Min He -+ * Ping Gao -+ * Tina Zhang -+ * Yulei Zhang -+ * Zhi Wang -+ * -+ */ -+#ifndef _GVT_CMD_PARSER_H_ -+#define _GVT_CMD_PARSER_H_ -+ -+#define GVT_CMD_HASH_BITS 7 -+ -+void intel_gvt_clean_cmd_parser(struct intel_gvt *gvt); -+ -+int intel_gvt_init_cmd_parser(struct intel_gvt *gvt); -+ -+int intel_gvt_scan_and_shadow_ringbuffer(struct intel_vgpu_workload *workload); -+ -+int intel_gvt_scan_and_shadow_wa_ctx(struct intel_shadow_wa_ctx *wa_ctx); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/gvt/debug.h b/drivers/gpu/drm/i915_legacy/gvt/debug.h -new file mode 100644 -index 000000000000..c6027125c1ec ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/debug.h -@@ -0,0 +1,65 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#ifndef __GVT_DEBUG_H__ -+#define __GVT_DEBUG_H__ -+ -+#define gvt_err(fmt, args...) \ -+ pr_err("gvt: "fmt, ##args) -+ -+#define gvt_vgpu_err(fmt, args...) \ -+do { \ -+ if (IS_ERR_OR_NULL(vgpu)) \ -+ pr_err("gvt: "fmt, ##args); \ -+ else \ -+ pr_err("gvt: vgpu %d: "fmt, vgpu->id, ##args);\ -+} while (0) -+ -+#define gvt_dbg_core(fmt, args...) \ -+ pr_debug("gvt: core: "fmt, ##args) -+ -+#define gvt_dbg_irq(fmt, args...) \ -+ pr_debug("gvt: irq: "fmt, ##args) -+ -+#define gvt_dbg_mm(fmt, args...) \ -+ pr_debug("gvt: mm: "fmt, ##args) -+ -+#define gvt_dbg_mmio(fmt, args...) \ -+ pr_debug("gvt: mmio: "fmt, ##args) -+ -+#define gvt_dbg_dpy(fmt, args...) \ -+ pr_debug("gvt: dpy: "fmt, ##args) -+ -+#define gvt_dbg_el(fmt, args...) \ -+ pr_debug("gvt: el: "fmt, ##args) -+ -+#define gvt_dbg_sched(fmt, args...) \ -+ pr_debug("gvt: sched: "fmt, ##args) -+ -+#define gvt_dbg_render(fmt, args...) \ -+ pr_debug("gvt: render: "fmt, ##args) -+ -+#define gvt_dbg_cmd(fmt, args...) \ -+ pr_debug("gvt: cmd: "fmt, ##args) -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/gvt/debugfs.c b/drivers/gpu/drm/i915_legacy/gvt/debugfs.c -new file mode 100644 -index 000000000000..8a9606f91e68 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/debugfs.c -@@ -0,0 +1,268 @@ -+/* -+ * Copyright(c) 2011-2017 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+#include -+#include -+#include "i915_drv.h" -+#include "gvt.h" -+ -+struct mmio_diff_param { -+ struct intel_vgpu *vgpu; -+ int total; -+ int diff; -+ struct list_head diff_mmio_list; -+}; -+ -+struct diff_mmio { -+ struct list_head node; -+ u32 offset; -+ u32 preg; -+ u32 vreg; -+}; -+ -+/* Compare two diff_mmio items. */ -+static int mmio_offset_compare(void *priv, -+ struct list_head *a, struct list_head *b) -+{ -+ struct diff_mmio *ma; -+ struct diff_mmio *mb; -+ -+ ma = container_of(a, struct diff_mmio, node); -+ mb = container_of(b, struct diff_mmio, node); -+ if (ma->offset < mb->offset) -+ return -1; -+ else if (ma->offset > mb->offset) -+ return 1; -+ return 0; -+} -+ -+static inline int mmio_diff_handler(struct intel_gvt *gvt, -+ u32 offset, void *data) -+{ -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ struct mmio_diff_param *param = data; -+ struct diff_mmio *node; -+ u32 preg, vreg; -+ -+ preg = I915_READ_NOTRACE(_MMIO(offset)); -+ vreg = vgpu_vreg(param->vgpu, offset); -+ -+ if (preg != vreg) { -+ node = kmalloc(sizeof(*node), GFP_KERNEL); -+ if (!node) -+ return -ENOMEM; -+ -+ node->offset = offset; -+ node->preg = preg; -+ node->vreg = vreg; -+ list_add(&node->node, ¶m->diff_mmio_list); -+ param->diff++; -+ } -+ param->total++; -+ return 0; -+} -+ -+/* Show the all the different values of tracked mmio. */ -+static int vgpu_mmio_diff_show(struct seq_file *s, void *unused) -+{ -+ struct intel_vgpu *vgpu = s->private; -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct mmio_diff_param param = { -+ .vgpu = vgpu, -+ .total = 0, -+ .diff = 0, -+ }; -+ struct diff_mmio *node, *next; -+ -+ INIT_LIST_HEAD(¶m.diff_mmio_list); -+ -+ mutex_lock(&gvt->lock); -+ spin_lock_bh(&gvt->scheduler.mmio_context_lock); -+ -+ mmio_hw_access_pre(gvt->dev_priv); -+ /* Recognize all the diff mmios to list. */ -+ intel_gvt_for_each_tracked_mmio(gvt, mmio_diff_handler, ¶m); -+ mmio_hw_access_post(gvt->dev_priv); -+ -+ spin_unlock_bh(&gvt->scheduler.mmio_context_lock); -+ mutex_unlock(&gvt->lock); -+ -+ /* In an ascending order by mmio offset. */ -+ list_sort(NULL, ¶m.diff_mmio_list, mmio_offset_compare); -+ -+ seq_printf(s, "%-8s %-8s %-8s %-8s\n", "Offset", "HW", "vGPU", "Diff"); -+ list_for_each_entry_safe(node, next, ¶m.diff_mmio_list, node) { -+ u32 diff = node->preg ^ node->vreg; -+ -+ seq_printf(s, "%08x %08x %08x %*pbl\n", -+ node->offset, node->preg, node->vreg, -+ 32, &diff); -+ list_del(&node->node); -+ kfree(node); -+ } -+ seq_printf(s, "Total: %d, Diff: %d\n", param.total, param.diff); -+ return 0; -+} -+DEFINE_SHOW_ATTRIBUTE(vgpu_mmio_diff); -+ -+static int -+vgpu_scan_nonprivbb_get(void *data, u64 *val) -+{ -+ struct intel_vgpu *vgpu = (struct intel_vgpu *)data; -+ *val = vgpu->scan_nonprivbb; -+ return 0; -+} -+ -+/* -+ * set/unset bit engine_id of vgpu->scan_nonprivbb to turn on/off scanning -+ * of non-privileged batch buffer. e.g. -+ * if vgpu->scan_nonprivbb=3, then it will scan non-privileged batch buffer -+ * on engine 0 and 1. -+ */ -+static int -+vgpu_scan_nonprivbb_set(void *data, u64 val) -+{ -+ struct intel_vgpu *vgpu = (struct intel_vgpu *)data; -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ enum intel_engine_id id; -+ char buf[128], *s; -+ int len; -+ -+ val &= (1 << I915_NUM_ENGINES) - 1; -+ -+ if (vgpu->scan_nonprivbb == val) -+ return 0; -+ -+ if (!val) -+ goto done; -+ -+ len = sprintf(buf, -+ "gvt: vgpu %d turns on non-privileged batch buffers scanning on Engines:", -+ vgpu->id); -+ -+ s = buf + len; -+ -+ for (id = 0; id < I915_NUM_ENGINES; id++) { -+ struct intel_engine_cs *engine; -+ -+ engine = dev_priv->engine[id]; -+ if (engine && (val & (1 << id))) { -+ len = snprintf(s, 4, "%d, ", engine->id); -+ s += len; -+ } else -+ val &= ~(1 << id); -+ } -+ -+ if (val) -+ sprintf(s, "low performance expected."); -+ -+ pr_warn("%s\n", buf); -+ -+done: -+ vgpu->scan_nonprivbb = val; -+ return 0; -+} -+ -+DEFINE_SIMPLE_ATTRIBUTE(vgpu_scan_nonprivbb_fops, -+ vgpu_scan_nonprivbb_get, vgpu_scan_nonprivbb_set, -+ "0x%llx\n"); -+ -+/** -+ * intel_gvt_debugfs_add_vgpu - register debugfs entries for a vGPU -+ * @vgpu: a vGPU -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_gvt_debugfs_add_vgpu(struct intel_vgpu *vgpu) -+{ -+ struct dentry *ent; -+ char name[16] = ""; -+ -+ snprintf(name, 16, "vgpu%d", vgpu->id); -+ vgpu->debugfs = debugfs_create_dir(name, vgpu->gvt->debugfs_root); -+ if (!vgpu->debugfs) -+ return -ENOMEM; -+ -+ ent = debugfs_create_bool("active", 0444, vgpu->debugfs, -+ &vgpu->active); -+ if (!ent) -+ return -ENOMEM; -+ -+ ent = debugfs_create_file("mmio_diff", 0444, vgpu->debugfs, -+ vgpu, &vgpu_mmio_diff_fops); -+ if (!ent) -+ return -ENOMEM; -+ -+ ent = debugfs_create_file("scan_nonprivbb", 0644, vgpu->debugfs, -+ vgpu, &vgpu_scan_nonprivbb_fops); -+ if (!ent) -+ return -ENOMEM; -+ -+ return 0; -+} -+ -+/** -+ * intel_gvt_debugfs_remove_vgpu - remove debugfs entries of a vGPU -+ * @vgpu: a vGPU -+ */ -+void intel_gvt_debugfs_remove_vgpu(struct intel_vgpu *vgpu) -+{ -+ debugfs_remove_recursive(vgpu->debugfs); -+ vgpu->debugfs = NULL; -+} -+ -+/** -+ * intel_gvt_debugfs_init - register gvt debugfs root entry -+ * @gvt: GVT device -+ * -+ * Returns: -+ * zero on success, negative if failed. -+ */ -+int intel_gvt_debugfs_init(struct intel_gvt *gvt) -+{ -+ struct drm_minor *minor = gvt->dev_priv->drm.primary; -+ struct dentry *ent; -+ -+ gvt->debugfs_root = debugfs_create_dir("gvt", minor->debugfs_root); -+ if (!gvt->debugfs_root) { -+ gvt_err("Cannot create debugfs dir\n"); -+ return -ENOMEM; -+ } -+ -+ ent = debugfs_create_ulong("num_tracked_mmio", 0444, gvt->debugfs_root, -+ &gvt->mmio.num_tracked_mmio); -+ if (!ent) -+ return -ENOMEM; -+ -+ return 0; -+} -+ -+/** -+ * intel_gvt_debugfs_clean - remove debugfs entries -+ * @gvt: GVT device -+ */ -+void intel_gvt_debugfs_clean(struct intel_gvt *gvt) -+{ -+ debugfs_remove_recursive(gvt->debugfs_root); -+ gvt->debugfs_root = NULL; -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/display.c b/drivers/gpu/drm/i915_legacy/gvt/display.c -new file mode 100644 -index 000000000000..e1c313da6c00 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/display.c -@@ -0,0 +1,531 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Ke Yu -+ * Zhiyuan Lv -+ * -+ * Contributors: -+ * Terrence Xu -+ * Changbin Du -+ * Bing Niu -+ * Zhi Wang -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "gvt.h" -+ -+static int get_edp_pipe(struct intel_vgpu *vgpu) -+{ -+ u32 data = vgpu_vreg(vgpu, _TRANS_DDI_FUNC_CTL_EDP); -+ int pipe = -1; -+ -+ switch (data & TRANS_DDI_EDP_INPUT_MASK) { -+ case TRANS_DDI_EDP_INPUT_A_ON: -+ case TRANS_DDI_EDP_INPUT_A_ONOFF: -+ pipe = PIPE_A; -+ break; -+ case TRANS_DDI_EDP_INPUT_B_ONOFF: -+ pipe = PIPE_B; -+ break; -+ case TRANS_DDI_EDP_INPUT_C_ONOFF: -+ pipe = PIPE_C; -+ break; -+ } -+ return pipe; -+} -+ -+static int edp_pipe_is_enabled(struct intel_vgpu *vgpu) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ -+ if (!(vgpu_vreg_t(vgpu, PIPECONF(_PIPE_EDP)) & PIPECONF_ENABLE)) -+ return 0; -+ -+ if (!(vgpu_vreg(vgpu, _TRANS_DDI_FUNC_CTL_EDP) & TRANS_DDI_FUNC_ENABLE)) -+ return 0; -+ return 1; -+} -+ -+int pipe_is_enabled(struct intel_vgpu *vgpu, int pipe) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ -+ if (WARN_ON(pipe < PIPE_A || pipe >= I915_MAX_PIPES)) -+ return -EINVAL; -+ -+ if (vgpu_vreg_t(vgpu, PIPECONF(pipe)) & PIPECONF_ENABLE) -+ return 1; -+ -+ if (edp_pipe_is_enabled(vgpu) && -+ get_edp_pipe(vgpu) == pipe) -+ return 1; -+ return 0; -+} -+ -+static unsigned char virtual_dp_monitor_edid[GVT_EDID_NUM][EDID_SIZE] = { -+ { -+/* EDID with 1024x768 as its resolution */ -+ /*Header*/ -+ 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, -+ /* Vendor & Product Identification */ -+ 0x22, 0xf0, 0x54, 0x29, 0x00, 0x00, 0x00, 0x00, 0x04, 0x17, -+ /* Version & Revision */ -+ 0x01, 0x04, -+ /* Basic Display Parameters & Features */ -+ 0xa5, 0x34, 0x20, 0x78, 0x23, -+ /* Color Characteristics */ -+ 0xfc, 0x81, 0xa4, 0x55, 0x4d, 0x9d, 0x25, 0x12, 0x50, 0x54, -+ /* Established Timings: maximum resolution is 1024x768 */ -+ 0x21, 0x08, 0x00, -+ /* Standard Timings. All invalid */ -+ 0x00, 0xc0, 0x00, 0xc0, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00, -+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, -+ /* 18 Byte Data Blocks 1: invalid */ -+ 0x00, 0x00, 0x80, 0xa0, 0x70, 0xb0, -+ 0x23, 0x40, 0x30, 0x20, 0x36, 0x00, 0x06, 0x44, 0x21, 0x00, 0x00, 0x1a, -+ /* 18 Byte Data Blocks 2: invalid */ -+ 0x00, 0x00, 0x00, 0xfd, 0x00, 0x18, 0x3c, 0x18, 0x50, 0x11, 0x00, 0x0a, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ /* 18 Byte Data Blocks 3: invalid */ -+ 0x00, 0x00, 0x00, 0xfc, 0x00, 0x48, -+ 0x50, 0x20, 0x5a, 0x52, 0x32, 0x34, 0x34, 0x30, 0x77, 0x0a, 0x20, 0x20, -+ /* 18 Byte Data Blocks 4: invalid */ -+ 0x00, 0x00, 0x00, 0xff, 0x00, 0x43, 0x4e, 0x34, 0x33, 0x30, 0x34, 0x30, -+ 0x44, 0x58, 0x51, 0x0a, 0x20, 0x20, -+ /* Extension Block Count */ -+ 0x00, -+ /* Checksum */ -+ 0xef, -+ }, -+ { -+/* EDID with 1920x1200 as its resolution */ -+ /*Header*/ -+ 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, -+ /* Vendor & Product Identification */ -+ 0x22, 0xf0, 0x54, 0x29, 0x00, 0x00, 0x00, 0x00, 0x04, 0x17, -+ /* Version & Revision */ -+ 0x01, 0x04, -+ /* Basic Display Parameters & Features */ -+ 0xa5, 0x34, 0x20, 0x78, 0x23, -+ /* Color Characteristics */ -+ 0xfc, 0x81, 0xa4, 0x55, 0x4d, 0x9d, 0x25, 0x12, 0x50, 0x54, -+ /* Established Timings: maximum resolution is 1024x768 */ -+ 0x21, 0x08, 0x00, -+ /* -+ * Standard Timings. -+ * below new resolutions can be supported: -+ * 1920x1080, 1280x720, 1280x960, 1280x1024, -+ * 1440x900, 1600x1200, 1680x1050 -+ */ -+ 0xd1, 0xc0, 0x81, 0xc0, 0x81, 0x40, 0x81, 0x80, 0x95, 0x00, -+ 0xa9, 0x40, 0xb3, 0x00, 0x01, 0x01, -+ /* 18 Byte Data Blocks 1: max resolution is 1920x1200 */ -+ 0x28, 0x3c, 0x80, 0xa0, 0x70, 0xb0, -+ 0x23, 0x40, 0x30, 0x20, 0x36, 0x00, 0x06, 0x44, 0x21, 0x00, 0x00, 0x1a, -+ /* 18 Byte Data Blocks 2: invalid */ -+ 0x00, 0x00, 0x00, 0xfd, 0x00, 0x18, 0x3c, 0x18, 0x50, 0x11, 0x00, 0x0a, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ /* 18 Byte Data Blocks 3: invalid */ -+ 0x00, 0x00, 0x00, 0xfc, 0x00, 0x48, -+ 0x50, 0x20, 0x5a, 0x52, 0x32, 0x34, 0x34, 0x30, 0x77, 0x0a, 0x20, 0x20, -+ /* 18 Byte Data Blocks 4: invalid */ -+ 0x00, 0x00, 0x00, 0xff, 0x00, 0x43, 0x4e, 0x34, 0x33, 0x30, 0x34, 0x30, -+ 0x44, 0x58, 0x51, 0x0a, 0x20, 0x20, -+ /* Extension Block Count */ -+ 0x00, -+ /* Checksum */ -+ 0x45, -+ }, -+}; -+ -+#define DPCD_HEADER_SIZE 0xb -+ -+/* let the virtual display supports DP1.2 */ -+static u8 dpcd_fix_data[DPCD_HEADER_SIZE] = { -+ 0x12, 0x014, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 -+}; -+ -+static void emulate_monitor_status_change(struct intel_vgpu *vgpu) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ int pipe; -+ -+ if (IS_BROXTON(dev_priv)) { -+ vgpu_vreg_t(vgpu, GEN8_DE_PORT_ISR) &= ~(BXT_DE_PORT_HP_DDIA | -+ BXT_DE_PORT_HP_DDIB | -+ BXT_DE_PORT_HP_DDIC); -+ -+ if (intel_vgpu_has_monitor_on_port(vgpu, PORT_A)) { -+ vgpu_vreg_t(vgpu, GEN8_DE_PORT_ISR) |= -+ BXT_DE_PORT_HP_DDIA; -+ } -+ -+ if (intel_vgpu_has_monitor_on_port(vgpu, PORT_B)) { -+ vgpu_vreg_t(vgpu, GEN8_DE_PORT_ISR) |= -+ BXT_DE_PORT_HP_DDIB; -+ } -+ -+ if (intel_vgpu_has_monitor_on_port(vgpu, PORT_C)) { -+ vgpu_vreg_t(vgpu, GEN8_DE_PORT_ISR) |= -+ BXT_DE_PORT_HP_DDIC; -+ } -+ -+ return; -+ } -+ -+ vgpu_vreg_t(vgpu, SDEISR) &= ~(SDE_PORTB_HOTPLUG_CPT | -+ SDE_PORTC_HOTPLUG_CPT | -+ SDE_PORTD_HOTPLUG_CPT); -+ -+ if (IS_SKYLAKE(dev_priv) || IS_KABYLAKE(dev_priv) || -+ IS_COFFEELAKE(dev_priv)) { -+ vgpu_vreg_t(vgpu, SDEISR) &= ~(SDE_PORTA_HOTPLUG_SPT | -+ SDE_PORTE_HOTPLUG_SPT); -+ vgpu_vreg_t(vgpu, SKL_FUSE_STATUS) |= -+ SKL_FUSE_DOWNLOAD_STATUS | -+ SKL_FUSE_PG_DIST_STATUS(SKL_PG0) | -+ SKL_FUSE_PG_DIST_STATUS(SKL_PG1) | -+ SKL_FUSE_PG_DIST_STATUS(SKL_PG2); -+ vgpu_vreg_t(vgpu, LCPLL1_CTL) |= -+ LCPLL_PLL_ENABLE | -+ LCPLL_PLL_LOCK; -+ vgpu_vreg_t(vgpu, LCPLL2_CTL) |= LCPLL_PLL_ENABLE; -+ -+ } -+ -+ if (intel_vgpu_has_monitor_on_port(vgpu, PORT_B)) { -+ vgpu_vreg_t(vgpu, SFUSE_STRAP) |= SFUSE_STRAP_DDIB_DETECTED; -+ vgpu_vreg_t(vgpu, TRANS_DDI_FUNC_CTL(TRANSCODER_A)) &= -+ ~(TRANS_DDI_BPC_MASK | TRANS_DDI_MODE_SELECT_MASK | -+ TRANS_DDI_PORT_MASK); -+ vgpu_vreg_t(vgpu, TRANS_DDI_FUNC_CTL(TRANSCODER_A)) |= -+ (TRANS_DDI_BPC_8 | TRANS_DDI_MODE_SELECT_DVI | -+ (PORT_B << TRANS_DDI_PORT_SHIFT) | -+ TRANS_DDI_FUNC_ENABLE); -+ if (IS_BROADWELL(dev_priv)) { -+ vgpu_vreg_t(vgpu, PORT_CLK_SEL(PORT_B)) &= -+ ~PORT_CLK_SEL_MASK; -+ vgpu_vreg_t(vgpu, PORT_CLK_SEL(PORT_B)) |= -+ PORT_CLK_SEL_LCPLL_810; -+ } -+ vgpu_vreg_t(vgpu, DDI_BUF_CTL(PORT_B)) |= DDI_BUF_CTL_ENABLE; -+ vgpu_vreg_t(vgpu, DDI_BUF_CTL(PORT_B)) &= ~DDI_BUF_IS_IDLE; -+ vgpu_vreg_t(vgpu, SDEISR) |= SDE_PORTB_HOTPLUG_CPT; -+ } -+ -+ if (intel_vgpu_has_monitor_on_port(vgpu, PORT_C)) { -+ vgpu_vreg_t(vgpu, SDEISR) |= SDE_PORTC_HOTPLUG_CPT; -+ vgpu_vreg_t(vgpu, TRANS_DDI_FUNC_CTL(TRANSCODER_A)) &= -+ ~(TRANS_DDI_BPC_MASK | TRANS_DDI_MODE_SELECT_MASK | -+ TRANS_DDI_PORT_MASK); -+ vgpu_vreg_t(vgpu, TRANS_DDI_FUNC_CTL(TRANSCODER_A)) |= -+ (TRANS_DDI_BPC_8 | TRANS_DDI_MODE_SELECT_DVI | -+ (PORT_C << TRANS_DDI_PORT_SHIFT) | -+ TRANS_DDI_FUNC_ENABLE); -+ if (IS_BROADWELL(dev_priv)) { -+ vgpu_vreg_t(vgpu, PORT_CLK_SEL(PORT_C)) &= -+ ~PORT_CLK_SEL_MASK; -+ vgpu_vreg_t(vgpu, PORT_CLK_SEL(PORT_C)) |= -+ PORT_CLK_SEL_LCPLL_810; -+ } -+ vgpu_vreg_t(vgpu, DDI_BUF_CTL(PORT_C)) |= DDI_BUF_CTL_ENABLE; -+ vgpu_vreg_t(vgpu, DDI_BUF_CTL(PORT_C)) &= ~DDI_BUF_IS_IDLE; -+ vgpu_vreg_t(vgpu, SFUSE_STRAP) |= SFUSE_STRAP_DDIC_DETECTED; -+ } -+ -+ if (intel_vgpu_has_monitor_on_port(vgpu, PORT_D)) { -+ vgpu_vreg_t(vgpu, SDEISR) |= SDE_PORTD_HOTPLUG_CPT; -+ vgpu_vreg_t(vgpu, TRANS_DDI_FUNC_CTL(TRANSCODER_A)) &= -+ ~(TRANS_DDI_BPC_MASK | TRANS_DDI_MODE_SELECT_MASK | -+ TRANS_DDI_PORT_MASK); -+ vgpu_vreg_t(vgpu, TRANS_DDI_FUNC_CTL(TRANSCODER_A)) |= -+ (TRANS_DDI_BPC_8 | TRANS_DDI_MODE_SELECT_DVI | -+ (PORT_D << TRANS_DDI_PORT_SHIFT) | -+ TRANS_DDI_FUNC_ENABLE); -+ if (IS_BROADWELL(dev_priv)) { -+ vgpu_vreg_t(vgpu, PORT_CLK_SEL(PORT_D)) &= -+ ~PORT_CLK_SEL_MASK; -+ vgpu_vreg_t(vgpu, PORT_CLK_SEL(PORT_D)) |= -+ PORT_CLK_SEL_LCPLL_810; -+ } -+ vgpu_vreg_t(vgpu, DDI_BUF_CTL(PORT_D)) |= DDI_BUF_CTL_ENABLE; -+ vgpu_vreg_t(vgpu, DDI_BUF_CTL(PORT_D)) &= ~DDI_BUF_IS_IDLE; -+ vgpu_vreg_t(vgpu, SFUSE_STRAP) |= SFUSE_STRAP_DDID_DETECTED; -+ } -+ -+ if ((IS_SKYLAKE(dev_priv) || IS_KABYLAKE(dev_priv) || -+ IS_COFFEELAKE(dev_priv)) && -+ intel_vgpu_has_monitor_on_port(vgpu, PORT_E)) { -+ vgpu_vreg_t(vgpu, SDEISR) |= SDE_PORTE_HOTPLUG_SPT; -+ } -+ -+ if (intel_vgpu_has_monitor_on_port(vgpu, PORT_A)) { -+ if (IS_BROADWELL(dev_priv)) -+ vgpu_vreg_t(vgpu, GEN8_DE_PORT_ISR) |= -+ GEN8_PORT_DP_A_HOTPLUG; -+ else -+ vgpu_vreg_t(vgpu, SDEISR) |= SDE_PORTA_HOTPLUG_SPT; -+ -+ vgpu_vreg_t(vgpu, DDI_BUF_CTL(PORT_A)) |= DDI_INIT_DISPLAY_DETECTED; -+ } -+ -+ /* Clear host CRT status, so guest couldn't detect this host CRT. */ -+ if (IS_BROADWELL(dev_priv)) -+ vgpu_vreg_t(vgpu, PCH_ADPA) &= ~ADPA_CRT_HOTPLUG_MONITOR_MASK; -+ -+ /* Disable Primary/Sprite/Cursor plane */ -+ for_each_pipe(dev_priv, pipe) { -+ vgpu_vreg_t(vgpu, DSPCNTR(pipe)) &= ~DISPLAY_PLANE_ENABLE; -+ vgpu_vreg_t(vgpu, SPRCTL(pipe)) &= ~SPRITE_ENABLE; -+ vgpu_vreg_t(vgpu, CURCNTR(pipe)) &= ~MCURSOR_MODE; -+ vgpu_vreg_t(vgpu, CURCNTR(pipe)) |= MCURSOR_MODE_DISABLE; -+ } -+ -+ vgpu_vreg_t(vgpu, PIPECONF(PIPE_A)) |= PIPECONF_ENABLE; -+} -+ -+static void clean_virtual_dp_monitor(struct intel_vgpu *vgpu, int port_num) -+{ -+ struct intel_vgpu_port *port = intel_vgpu_port(vgpu, port_num); -+ -+ kfree(port->edid); -+ port->edid = NULL; -+ -+ kfree(port->dpcd); -+ port->dpcd = NULL; -+} -+ -+static int setup_virtual_dp_monitor(struct intel_vgpu *vgpu, int port_num, -+ int type, unsigned int resolution) -+{ -+ struct intel_vgpu_port *port = intel_vgpu_port(vgpu, port_num); -+ -+ if (WARN_ON(resolution >= GVT_EDID_NUM)) -+ return -EINVAL; -+ -+ port->edid = kzalloc(sizeof(*(port->edid)), GFP_KERNEL); -+ if (!port->edid) -+ return -ENOMEM; -+ -+ port->dpcd = kzalloc(sizeof(*(port->dpcd)), GFP_KERNEL); -+ if (!port->dpcd) { -+ kfree(port->edid); -+ return -ENOMEM; -+ } -+ -+ memcpy(port->edid->edid_block, virtual_dp_monitor_edid[resolution], -+ EDID_SIZE); -+ port->edid->data_valid = true; -+ -+ memcpy(port->dpcd->data, dpcd_fix_data, DPCD_HEADER_SIZE); -+ port->dpcd->data_valid = true; -+ port->dpcd->data[DPCD_SINK_COUNT] = 0x1; -+ port->type = type; -+ port->id = resolution; -+ -+ emulate_monitor_status_change(vgpu); -+ -+ return 0; -+} -+ -+/** -+ * intel_gvt_check_vblank_emulation - check if vblank emulation timer should -+ * be turned on/off when a virtual pipe is enabled/disabled. -+ * @gvt: a GVT device -+ * -+ * This function is used to turn on/off vblank timer according to currently -+ * enabled/disabled virtual pipes. -+ * -+ */ -+void intel_gvt_check_vblank_emulation(struct intel_gvt *gvt) -+{ -+ struct intel_gvt_irq *irq = &gvt->irq; -+ struct intel_vgpu *vgpu; -+ int pipe, id; -+ int found = false; -+ -+ mutex_lock(&gvt->lock); -+ for_each_active_vgpu(gvt, vgpu, id) { -+ for (pipe = 0; pipe < I915_MAX_PIPES; pipe++) { -+ if (pipe_is_enabled(vgpu, pipe)) { -+ found = true; -+ break; -+ } -+ } -+ if (found) -+ break; -+ } -+ -+ /* all the pipes are disabled */ -+ if (!found) -+ hrtimer_cancel(&irq->vblank_timer.timer); -+ else -+ hrtimer_start(&irq->vblank_timer.timer, -+ ktime_add_ns(ktime_get(), irq->vblank_timer.period), -+ HRTIMER_MODE_ABS); -+ mutex_unlock(&gvt->lock); -+} -+ -+static void emulate_vblank_on_pipe(struct intel_vgpu *vgpu, int pipe) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ struct intel_vgpu_irq *irq = &vgpu->irq; -+ int vblank_event[] = { -+ [PIPE_A] = PIPE_A_VBLANK, -+ [PIPE_B] = PIPE_B_VBLANK, -+ [PIPE_C] = PIPE_C_VBLANK, -+ }; -+ int event; -+ -+ if (pipe < PIPE_A || pipe > PIPE_C) -+ return; -+ -+ for_each_set_bit(event, irq->flip_done_event[pipe], -+ INTEL_GVT_EVENT_MAX) { -+ clear_bit(event, irq->flip_done_event[pipe]); -+ if (!pipe_is_enabled(vgpu, pipe)) -+ continue; -+ -+ intel_vgpu_trigger_virtual_event(vgpu, event); -+ } -+ -+ if (pipe_is_enabled(vgpu, pipe)) { -+ vgpu_vreg_t(vgpu, PIPE_FRMCOUNT_G4X(pipe))++; -+ intel_vgpu_trigger_virtual_event(vgpu, vblank_event[pipe]); -+ } -+} -+ -+static void emulate_vblank(struct intel_vgpu *vgpu) -+{ -+ int pipe; -+ -+ mutex_lock(&vgpu->vgpu_lock); -+ for_each_pipe(vgpu->gvt->dev_priv, pipe) -+ emulate_vblank_on_pipe(vgpu, pipe); -+ mutex_unlock(&vgpu->vgpu_lock); -+} -+ -+/** -+ * intel_gvt_emulate_vblank - trigger vblank events for vGPUs on GVT device -+ * @gvt: a GVT device -+ * -+ * This function is used to trigger vblank interrupts for vGPUs on GVT device -+ * -+ */ -+void intel_gvt_emulate_vblank(struct intel_gvt *gvt) -+{ -+ struct intel_vgpu *vgpu; -+ int id; -+ -+ mutex_lock(&gvt->lock); -+ for_each_active_vgpu(gvt, vgpu, id) -+ emulate_vblank(vgpu); -+ mutex_unlock(&gvt->lock); -+} -+ -+/** -+ * intel_vgpu_emulate_hotplug - trigger hotplug event for vGPU -+ * @vgpu: a vGPU -+ * @connected: link state -+ * -+ * This function is used to trigger hotplug interrupt for vGPU -+ * -+ */ -+void intel_vgpu_emulate_hotplug(struct intel_vgpu *vgpu, bool connected) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ -+ /* TODO: add more platforms support */ -+ if (IS_SKYLAKE(dev_priv) || IS_KABYLAKE(dev_priv)) { -+ if (connected) { -+ vgpu_vreg_t(vgpu, SFUSE_STRAP) |= -+ SFUSE_STRAP_DDID_DETECTED; -+ vgpu_vreg_t(vgpu, SDEISR) |= SDE_PORTD_HOTPLUG_CPT; -+ } else { -+ vgpu_vreg_t(vgpu, SFUSE_STRAP) &= -+ ~SFUSE_STRAP_DDID_DETECTED; -+ vgpu_vreg_t(vgpu, SDEISR) &= ~SDE_PORTD_HOTPLUG_CPT; -+ } -+ vgpu_vreg_t(vgpu, SDEIIR) |= SDE_PORTD_HOTPLUG_CPT; -+ vgpu_vreg_t(vgpu, PCH_PORT_HOTPLUG) |= -+ PORTD_HOTPLUG_STATUS_MASK; -+ intel_vgpu_trigger_virtual_event(vgpu, DP_D_HOTPLUG); -+ } -+} -+ -+/** -+ * intel_vgpu_clean_display - clean vGPU virtual display emulation -+ * @vgpu: a vGPU -+ * -+ * This function is used to clean vGPU virtual display emulation stuffs -+ * -+ */ -+void intel_vgpu_clean_display(struct intel_vgpu *vgpu) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ -+ if (IS_SKYLAKE(dev_priv) || IS_KABYLAKE(dev_priv) || -+ IS_COFFEELAKE(dev_priv)) -+ clean_virtual_dp_monitor(vgpu, PORT_D); -+ else -+ clean_virtual_dp_monitor(vgpu, PORT_B); -+} -+ -+/** -+ * intel_vgpu_init_display- initialize vGPU virtual display emulation -+ * @vgpu: a vGPU -+ * @resolution: resolution index for intel_vgpu_edid -+ * -+ * This function is used to initialize vGPU virtual display emulation stuffs -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ * -+ */ -+int intel_vgpu_init_display(struct intel_vgpu *vgpu, u64 resolution) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ -+ intel_vgpu_init_i2c_edid(vgpu); -+ -+ if (IS_SKYLAKE(dev_priv) || IS_KABYLAKE(dev_priv) || -+ IS_COFFEELAKE(dev_priv)) -+ return setup_virtual_dp_monitor(vgpu, PORT_D, GVT_DP_D, -+ resolution); -+ else -+ return setup_virtual_dp_monitor(vgpu, PORT_B, GVT_DP_B, -+ resolution); -+} -+ -+/** -+ * intel_vgpu_reset_display- reset vGPU virtual display emulation -+ * @vgpu: a vGPU -+ * -+ * This function is used to reset vGPU virtual display emulation stuffs -+ * -+ */ -+void intel_vgpu_reset_display(struct intel_vgpu *vgpu) -+{ -+ emulate_monitor_status_change(vgpu); -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/display.h b/drivers/gpu/drm/i915_legacy/gvt/display.h -new file mode 100644 -index 000000000000..a87f33e6a23c ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/display.h -@@ -0,0 +1,209 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Ke Yu -+ * Zhiyuan Lv -+ * -+ * Contributors: -+ * Terrence Xu -+ * Changbin Du -+ * Bing Niu -+ * Zhi Wang -+ * -+ */ -+ -+#ifndef _GVT_DISPLAY_H_ -+#define _GVT_DISPLAY_H_ -+ -+#define SBI_REG_MAX 20 -+#define DPCD_SIZE 0x700 -+ -+#define intel_vgpu_port(vgpu, port) \ -+ (&(vgpu->display.ports[port])) -+ -+#define intel_vgpu_has_monitor_on_port(vgpu, port) \ -+ (intel_vgpu_port(vgpu, port)->edid && \ -+ intel_vgpu_port(vgpu, port)->edid->data_valid) -+ -+#define intel_vgpu_port_is_dp(vgpu, port) \ -+ ((intel_vgpu_port(vgpu, port)->type == GVT_DP_A) || \ -+ (intel_vgpu_port(vgpu, port)->type == GVT_DP_B) || \ -+ (intel_vgpu_port(vgpu, port)->type == GVT_DP_C) || \ -+ (intel_vgpu_port(vgpu, port)->type == GVT_DP_D)) -+ -+#define INTEL_GVT_MAX_UEVENT_VARS 3 -+ -+/* DPCD start */ -+#define DPCD_SIZE 0x700 -+ -+/* DPCD */ -+#define DP_SET_POWER 0x600 -+#define DP_SET_POWER_D0 0x1 -+#define AUX_NATIVE_WRITE 0x8 -+#define AUX_NATIVE_READ 0x9 -+ -+#define AUX_NATIVE_REPLY_MASK (0x3 << 4) -+#define AUX_NATIVE_REPLY_ACK (0x0 << 4) -+#define AUX_NATIVE_REPLY_NAK (0x1 << 4) -+#define AUX_NATIVE_REPLY_DEFER (0x2 << 4) -+ -+#define AUX_BURST_SIZE 20 -+ -+/* DPCD addresses */ -+#define DPCD_REV 0x000 -+#define DPCD_MAX_LINK_RATE 0x001 -+#define DPCD_MAX_LANE_COUNT 0x002 -+ -+#define DPCD_TRAINING_PATTERN_SET 0x102 -+#define DPCD_SINK_COUNT 0x200 -+#define DPCD_LANE0_1_STATUS 0x202 -+#define DPCD_LANE2_3_STATUS 0x203 -+#define DPCD_LANE_ALIGN_STATUS_UPDATED 0x204 -+#define DPCD_SINK_STATUS 0x205 -+ -+/* link training */ -+#define DPCD_TRAINING_PATTERN_SET_MASK 0x03 -+#define DPCD_LINK_TRAINING_DISABLED 0x00 -+#define DPCD_TRAINING_PATTERN_1 0x01 -+#define DPCD_TRAINING_PATTERN_2 0x02 -+ -+#define DPCD_CP_READY_MASK (1 << 6) -+ -+/* lane status */ -+#define DPCD_LANES_CR_DONE 0x11 -+#define DPCD_LANES_EQ_DONE 0x22 -+#define DPCD_SYMBOL_LOCKED 0x44 -+ -+#define DPCD_INTERLANE_ALIGN_DONE 0x01 -+ -+#define DPCD_SINK_IN_SYNC 0x03 -+/* DPCD end */ -+ -+#define SBI_RESPONSE_MASK 0x3 -+#define SBI_RESPONSE_SHIFT 0x1 -+#define SBI_STAT_MASK 0x1 -+#define SBI_STAT_SHIFT 0x0 -+#define SBI_OPCODE_SHIFT 8 -+#define SBI_OPCODE_MASK (0xff << SBI_OPCODE_SHIFT) -+#define SBI_CMD_IORD 2 -+#define SBI_CMD_IOWR 3 -+#define SBI_CMD_CRRD 6 -+#define SBI_CMD_CRWR 7 -+#define SBI_ADDR_OFFSET_SHIFT 16 -+#define SBI_ADDR_OFFSET_MASK (0xffff << SBI_ADDR_OFFSET_SHIFT) -+ -+struct intel_vgpu_sbi_register { -+ unsigned int offset; -+ u32 value; -+}; -+ -+struct intel_vgpu_sbi { -+ int number; -+ struct intel_vgpu_sbi_register registers[SBI_REG_MAX]; -+}; -+ -+enum intel_gvt_plane_type { -+ PRIMARY_PLANE = 0, -+ CURSOR_PLANE, -+ SPRITE_PLANE, -+ MAX_PLANE -+}; -+ -+struct intel_vgpu_dpcd_data { -+ bool data_valid; -+ u8 data[DPCD_SIZE]; -+}; -+ -+enum intel_vgpu_port_type { -+ GVT_CRT = 0, -+ GVT_DP_A, -+ GVT_DP_B, -+ GVT_DP_C, -+ GVT_DP_D, -+ GVT_HDMI_B, -+ GVT_HDMI_C, -+ GVT_HDMI_D, -+ GVT_PORT_MAX -+}; -+ -+enum intel_vgpu_edid { -+ GVT_EDID_1024_768, -+ GVT_EDID_1920_1200, -+ GVT_EDID_NUM, -+}; -+ -+struct intel_vgpu_port { -+ /* per display EDID information */ -+ struct intel_vgpu_edid_data *edid; -+ /* per display DPCD information */ -+ struct intel_vgpu_dpcd_data *dpcd; -+ int type; -+ enum intel_vgpu_edid id; -+}; -+ -+static inline char *vgpu_edid_str(enum intel_vgpu_edid id) -+{ -+ switch (id) { -+ case GVT_EDID_1024_768: -+ return "1024x768"; -+ case GVT_EDID_1920_1200: -+ return "1920x1200"; -+ default: -+ return ""; -+ } -+} -+ -+static inline unsigned int vgpu_edid_xres(enum intel_vgpu_edid id) -+{ -+ switch (id) { -+ case GVT_EDID_1024_768: -+ return 1024; -+ case GVT_EDID_1920_1200: -+ return 1920; -+ default: -+ return 0; -+ } -+} -+ -+static inline unsigned int vgpu_edid_yres(enum intel_vgpu_edid id) -+{ -+ switch (id) { -+ case GVT_EDID_1024_768: -+ return 768; -+ case GVT_EDID_1920_1200: -+ return 1200; -+ default: -+ return 0; -+ } -+} -+ -+void intel_gvt_emulate_vblank(struct intel_gvt *gvt); -+void intel_gvt_check_vblank_emulation(struct intel_gvt *gvt); -+ -+int intel_vgpu_init_display(struct intel_vgpu *vgpu, u64 resolution); -+void intel_vgpu_reset_display(struct intel_vgpu *vgpu); -+void intel_vgpu_clean_display(struct intel_vgpu *vgpu); -+ -+int pipe_is_enabled(struct intel_vgpu *vgpu, int pipe); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/gvt/dmabuf.c b/drivers/gpu/drm/i915_legacy/gvt/dmabuf.c -new file mode 100644 -index 000000000000..41c8ebc60c63 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/dmabuf.c -@@ -0,0 +1,561 @@ -+/* -+ * Copyright 2017 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Zhiyuan Lv -+ * -+ * Contributors: -+ * Xiaoguang Chen -+ * Tina Zhang -+ */ -+ -+#include -+#include -+ -+#include "i915_drv.h" -+#include "gvt.h" -+ -+#define GEN8_DECODE_PTE(pte) (pte & GENMASK_ULL(63, 12)) -+ -+static int vgpu_gem_get_pages( -+ struct drm_i915_gem_object *obj) -+{ -+ struct drm_i915_private *dev_priv = to_i915(obj->base.dev); -+ struct sg_table *st; -+ struct scatterlist *sg; -+ int i, ret; -+ gen8_pte_t __iomem *gtt_entries; -+ struct intel_vgpu_fb_info *fb_info; -+ u32 page_num; -+ -+ fb_info = (struct intel_vgpu_fb_info *)obj->gvt_info; -+ if (WARN_ON(!fb_info)) -+ return -ENODEV; -+ -+ st = kmalloc(sizeof(*st), GFP_KERNEL); -+ if (unlikely(!st)) -+ return -ENOMEM; -+ -+ page_num = obj->base.size >> PAGE_SHIFT; -+ ret = sg_alloc_table(st, page_num, GFP_KERNEL); -+ if (ret) { -+ kfree(st); -+ return ret; -+ } -+ gtt_entries = (gen8_pte_t __iomem *)dev_priv->ggtt.gsm + -+ (fb_info->start >> PAGE_SHIFT); -+ for_each_sg(st->sgl, sg, page_num, i) { -+ sg->offset = 0; -+ sg->length = PAGE_SIZE; -+ sg_dma_address(sg) = -+ GEN8_DECODE_PTE(readq(>t_entries[i])); -+ sg_dma_len(sg) = PAGE_SIZE; -+ } -+ -+ __i915_gem_object_set_pages(obj, st, PAGE_SIZE); -+ -+ return 0; -+} -+ -+static void vgpu_gem_put_pages(struct drm_i915_gem_object *obj, -+ struct sg_table *pages) -+{ -+ sg_free_table(pages); -+ kfree(pages); -+} -+ -+static void dmabuf_gem_object_free(struct kref *kref) -+{ -+ struct intel_vgpu_dmabuf_obj *obj = -+ container_of(kref, struct intel_vgpu_dmabuf_obj, kref); -+ struct intel_vgpu *vgpu = obj->vgpu; -+ struct list_head *pos; -+ struct intel_vgpu_dmabuf_obj *dmabuf_obj; -+ -+ if (vgpu && vgpu->active && !list_empty(&vgpu->dmabuf_obj_list_head)) { -+ list_for_each(pos, &vgpu->dmabuf_obj_list_head) { -+ dmabuf_obj = container_of(pos, -+ struct intel_vgpu_dmabuf_obj, list); -+ if (dmabuf_obj == obj) { -+ intel_gvt_hypervisor_put_vfio_device(vgpu); -+ idr_remove(&vgpu->object_idr, -+ dmabuf_obj->dmabuf_id); -+ kfree(dmabuf_obj->info); -+ kfree(dmabuf_obj); -+ list_del(pos); -+ break; -+ } -+ } -+ } else { -+ /* Free the orphan dmabuf_objs here */ -+ kfree(obj->info); -+ kfree(obj); -+ } -+} -+ -+ -+static inline void dmabuf_obj_get(struct intel_vgpu_dmabuf_obj *obj) -+{ -+ kref_get(&obj->kref); -+} -+ -+static inline void dmabuf_obj_put(struct intel_vgpu_dmabuf_obj *obj) -+{ -+ kref_put(&obj->kref, dmabuf_gem_object_free); -+} -+ -+static void vgpu_gem_release(struct drm_i915_gem_object *gem_obj) -+{ -+ -+ struct intel_vgpu_fb_info *fb_info = gem_obj->gvt_info; -+ struct intel_vgpu_dmabuf_obj *obj = fb_info->obj; -+ struct intel_vgpu *vgpu = obj->vgpu; -+ -+ if (vgpu) { -+ mutex_lock(&vgpu->dmabuf_lock); -+ gem_obj->base.dma_buf = NULL; -+ dmabuf_obj_put(obj); -+ mutex_unlock(&vgpu->dmabuf_lock); -+ } else { -+ /* vgpu is NULL, as it has been removed already */ -+ gem_obj->base.dma_buf = NULL; -+ dmabuf_obj_put(obj); -+ } -+} -+ -+static const struct drm_i915_gem_object_ops intel_vgpu_gem_ops = { -+ .flags = I915_GEM_OBJECT_IS_PROXY, -+ .get_pages = vgpu_gem_get_pages, -+ .put_pages = vgpu_gem_put_pages, -+ .release = vgpu_gem_release, -+}; -+ -+static struct drm_i915_gem_object *vgpu_create_gem(struct drm_device *dev, -+ struct intel_vgpu_fb_info *info) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_i915_gem_object *obj; -+ -+ obj = i915_gem_object_alloc(); -+ if (obj == NULL) -+ return NULL; -+ -+ drm_gem_private_object_init(dev, &obj->base, -+ roundup(info->size, PAGE_SIZE)); -+ i915_gem_object_init(obj, &intel_vgpu_gem_ops); -+ -+ obj->read_domains = I915_GEM_DOMAIN_GTT; -+ obj->write_domain = 0; -+ if (INTEL_GEN(dev_priv) >= 9) { -+ unsigned int tiling_mode = 0; -+ unsigned int stride = 0; -+ -+ switch (info->drm_format_mod) { -+ case DRM_FORMAT_MOD_LINEAR: -+ tiling_mode = I915_TILING_NONE; -+ break; -+ case I915_FORMAT_MOD_X_TILED: -+ tiling_mode = I915_TILING_X; -+ stride = info->stride; -+ break; -+ case I915_FORMAT_MOD_Y_TILED: -+ case I915_FORMAT_MOD_Yf_TILED: -+ tiling_mode = I915_TILING_Y; -+ stride = info->stride; -+ break; -+ default: -+ gvt_dbg_core("invalid drm_format_mod %llx for tiling\n", -+ info->drm_format_mod); -+ } -+ obj->tiling_and_stride = tiling_mode | stride; -+ } else { -+ obj->tiling_and_stride = info->drm_format_mod ? -+ I915_TILING_X : 0; -+ } -+ -+ return obj; -+} -+ -+static bool validate_hotspot(struct intel_vgpu_cursor_plane_format *c) -+{ -+ if (c && c->x_hot <= c->width && c->y_hot <= c->height) -+ return true; -+ else -+ return false; -+} -+ -+static int vgpu_get_plane_info(struct drm_device *dev, -+ struct intel_vgpu *vgpu, -+ struct intel_vgpu_fb_info *info, -+ int plane_id) -+{ -+ struct intel_vgpu_primary_plane_format p; -+ struct intel_vgpu_cursor_plane_format c; -+ int ret, tile_height = 1; -+ -+ memset(info, 0, sizeof(*info)); -+ -+ if (plane_id == DRM_PLANE_TYPE_PRIMARY) { -+ ret = intel_vgpu_decode_primary_plane(vgpu, &p); -+ if (ret) -+ return ret; -+ info->start = p.base; -+ info->start_gpa = p.base_gpa; -+ info->width = p.width; -+ info->height = p.height; -+ info->stride = p.stride; -+ info->drm_format = p.drm_format; -+ -+ switch (p.tiled) { -+ case PLANE_CTL_TILED_LINEAR: -+ info->drm_format_mod = DRM_FORMAT_MOD_LINEAR; -+ break; -+ case PLANE_CTL_TILED_X: -+ info->drm_format_mod = I915_FORMAT_MOD_X_TILED; -+ tile_height = 8; -+ break; -+ case PLANE_CTL_TILED_Y: -+ info->drm_format_mod = I915_FORMAT_MOD_Y_TILED; -+ tile_height = 32; -+ break; -+ case PLANE_CTL_TILED_YF: -+ info->drm_format_mod = I915_FORMAT_MOD_Yf_TILED; -+ tile_height = 32; -+ break; -+ default: -+ gvt_vgpu_err("invalid tiling mode: %x\n", p.tiled); -+ } -+ } else if (plane_id == DRM_PLANE_TYPE_CURSOR) { -+ ret = intel_vgpu_decode_cursor_plane(vgpu, &c); -+ if (ret) -+ return ret; -+ info->start = c.base; -+ info->start_gpa = c.base_gpa; -+ info->width = c.width; -+ info->height = c.height; -+ info->stride = c.width * (c.bpp / 8); -+ info->drm_format = c.drm_format; -+ info->drm_format_mod = 0; -+ info->x_pos = c.x_pos; -+ info->y_pos = c.y_pos; -+ -+ if (validate_hotspot(&c)) { -+ info->x_hot = c.x_hot; -+ info->y_hot = c.y_hot; -+ } else { -+ info->x_hot = UINT_MAX; -+ info->y_hot = UINT_MAX; -+ } -+ } else { -+ gvt_vgpu_err("invalid plane id:%d\n", plane_id); -+ return -EINVAL; -+ } -+ -+ info->size = info->stride * roundup(info->height, tile_height); -+ if (info->size == 0) { -+ gvt_vgpu_err("fb size is zero\n"); -+ return -EINVAL; -+ } -+ -+ if (info->start & (PAGE_SIZE - 1)) { -+ gvt_vgpu_err("Not aligned fb address:0x%llx\n", info->start); -+ return -EFAULT; -+ } -+ -+ if (!intel_gvt_ggtt_validate_range(vgpu, info->start, info->size)) { -+ gvt_vgpu_err("invalid gma addr\n"); -+ return -EFAULT; -+ } -+ -+ return 0; -+} -+ -+static struct intel_vgpu_dmabuf_obj * -+pick_dmabuf_by_info(struct intel_vgpu *vgpu, -+ struct intel_vgpu_fb_info *latest_info) -+{ -+ struct list_head *pos; -+ struct intel_vgpu_fb_info *fb_info; -+ struct intel_vgpu_dmabuf_obj *dmabuf_obj = NULL; -+ struct intel_vgpu_dmabuf_obj *ret = NULL; -+ -+ list_for_each(pos, &vgpu->dmabuf_obj_list_head) { -+ dmabuf_obj = container_of(pos, struct intel_vgpu_dmabuf_obj, -+ list); -+ if ((dmabuf_obj == NULL) || -+ (dmabuf_obj->info == NULL)) -+ continue; -+ -+ fb_info = (struct intel_vgpu_fb_info *)dmabuf_obj->info; -+ if ((fb_info->start == latest_info->start) && -+ (fb_info->start_gpa == latest_info->start_gpa) && -+ (fb_info->size == latest_info->size) && -+ (fb_info->drm_format_mod == latest_info->drm_format_mod) && -+ (fb_info->drm_format == latest_info->drm_format) && -+ (fb_info->width == latest_info->width) && -+ (fb_info->height == latest_info->height)) { -+ ret = dmabuf_obj; -+ break; -+ } -+ } -+ -+ return ret; -+} -+ -+static struct intel_vgpu_dmabuf_obj * -+pick_dmabuf_by_num(struct intel_vgpu *vgpu, u32 id) -+{ -+ struct list_head *pos; -+ struct intel_vgpu_dmabuf_obj *dmabuf_obj = NULL; -+ struct intel_vgpu_dmabuf_obj *ret = NULL; -+ -+ list_for_each(pos, &vgpu->dmabuf_obj_list_head) { -+ dmabuf_obj = container_of(pos, struct intel_vgpu_dmabuf_obj, -+ list); -+ if (!dmabuf_obj) -+ continue; -+ -+ if (dmabuf_obj->dmabuf_id == id) { -+ ret = dmabuf_obj; -+ break; -+ } -+ } -+ -+ return ret; -+} -+ -+static void update_fb_info(struct vfio_device_gfx_plane_info *gvt_dmabuf, -+ struct intel_vgpu_fb_info *fb_info) -+{ -+ gvt_dmabuf->drm_format = fb_info->drm_format; -+ gvt_dmabuf->drm_format_mod = fb_info->drm_format_mod; -+ gvt_dmabuf->width = fb_info->width; -+ gvt_dmabuf->height = fb_info->height; -+ gvt_dmabuf->stride = fb_info->stride; -+ gvt_dmabuf->size = fb_info->size; -+ gvt_dmabuf->x_pos = fb_info->x_pos; -+ gvt_dmabuf->y_pos = fb_info->y_pos; -+ gvt_dmabuf->x_hot = fb_info->x_hot; -+ gvt_dmabuf->y_hot = fb_info->y_hot; -+} -+ -+int intel_vgpu_query_plane(struct intel_vgpu *vgpu, void *args) -+{ -+ struct drm_device *dev = &vgpu->gvt->dev_priv->drm; -+ struct vfio_device_gfx_plane_info *gfx_plane_info = args; -+ struct intel_vgpu_dmabuf_obj *dmabuf_obj; -+ struct intel_vgpu_fb_info fb_info; -+ int ret = 0; -+ -+ if (gfx_plane_info->flags == (VFIO_GFX_PLANE_TYPE_DMABUF | -+ VFIO_GFX_PLANE_TYPE_PROBE)) -+ return ret; -+ else if ((gfx_plane_info->flags & ~VFIO_GFX_PLANE_TYPE_DMABUF) || -+ (!gfx_plane_info->flags)) -+ return -EINVAL; -+ -+ ret = vgpu_get_plane_info(dev, vgpu, &fb_info, -+ gfx_plane_info->drm_plane_type); -+ if (ret != 0) -+ goto out; -+ -+ mutex_lock(&vgpu->dmabuf_lock); -+ /* If exists, pick up the exposed dmabuf_obj */ -+ dmabuf_obj = pick_dmabuf_by_info(vgpu, &fb_info); -+ if (dmabuf_obj) { -+ update_fb_info(gfx_plane_info, &fb_info); -+ gfx_plane_info->dmabuf_id = dmabuf_obj->dmabuf_id; -+ -+ /* This buffer may be released between query_plane ioctl and -+ * get_dmabuf ioctl. Add the refcount to make sure it won't -+ * be released between the two ioctls. -+ */ -+ if (!dmabuf_obj->initref) { -+ dmabuf_obj->initref = true; -+ dmabuf_obj_get(dmabuf_obj); -+ } -+ ret = 0; -+ gvt_dbg_dpy("vgpu%d: re-use dmabuf_obj ref %d, id %d\n", -+ vgpu->id, kref_read(&dmabuf_obj->kref), -+ gfx_plane_info->dmabuf_id); -+ mutex_unlock(&vgpu->dmabuf_lock); -+ goto out; -+ } -+ -+ mutex_unlock(&vgpu->dmabuf_lock); -+ -+ /* Need to allocate a new one*/ -+ dmabuf_obj = kmalloc(sizeof(struct intel_vgpu_dmabuf_obj), GFP_KERNEL); -+ if (unlikely(!dmabuf_obj)) { -+ gvt_vgpu_err("alloc dmabuf_obj failed\n"); -+ ret = -ENOMEM; -+ goto out; -+ } -+ -+ dmabuf_obj->info = kmalloc(sizeof(struct intel_vgpu_fb_info), -+ GFP_KERNEL); -+ if (unlikely(!dmabuf_obj->info)) { -+ gvt_vgpu_err("allocate intel vgpu fb info failed\n"); -+ ret = -ENOMEM; -+ goto out_free_dmabuf; -+ } -+ memcpy(dmabuf_obj->info, &fb_info, sizeof(struct intel_vgpu_fb_info)); -+ -+ ((struct intel_vgpu_fb_info *)dmabuf_obj->info)->obj = dmabuf_obj; -+ -+ dmabuf_obj->vgpu = vgpu; -+ -+ ret = idr_alloc(&vgpu->object_idr, dmabuf_obj, 1, 0, GFP_NOWAIT); -+ if (ret < 0) -+ goto out_free_info; -+ gfx_plane_info->dmabuf_id = ret; -+ dmabuf_obj->dmabuf_id = ret; -+ -+ dmabuf_obj->initref = true; -+ -+ kref_init(&dmabuf_obj->kref); -+ -+ mutex_lock(&vgpu->dmabuf_lock); -+ if (intel_gvt_hypervisor_get_vfio_device(vgpu)) { -+ gvt_vgpu_err("get vfio device failed\n"); -+ mutex_unlock(&vgpu->dmabuf_lock); -+ goto out_free_info; -+ } -+ mutex_unlock(&vgpu->dmabuf_lock); -+ -+ update_fb_info(gfx_plane_info, &fb_info); -+ -+ INIT_LIST_HEAD(&dmabuf_obj->list); -+ mutex_lock(&vgpu->dmabuf_lock); -+ list_add_tail(&dmabuf_obj->list, &vgpu->dmabuf_obj_list_head); -+ mutex_unlock(&vgpu->dmabuf_lock); -+ -+ gvt_dbg_dpy("vgpu%d: %s new dmabuf_obj ref %d, id %d\n", vgpu->id, -+ __func__, kref_read(&dmabuf_obj->kref), ret); -+ -+ return 0; -+ -+out_free_info: -+ kfree(dmabuf_obj->info); -+out_free_dmabuf: -+ kfree(dmabuf_obj); -+out: -+ /* ENODEV means plane isn't ready, which might be a normal case. */ -+ return (ret == -ENODEV) ? 0 : ret; -+} -+ -+/* To associate an exposed dmabuf with the dmabuf_obj */ -+int intel_vgpu_get_dmabuf(struct intel_vgpu *vgpu, unsigned int dmabuf_id) -+{ -+ struct drm_device *dev = &vgpu->gvt->dev_priv->drm; -+ struct intel_vgpu_dmabuf_obj *dmabuf_obj; -+ struct drm_i915_gem_object *obj; -+ struct dma_buf *dmabuf; -+ int dmabuf_fd; -+ int ret = 0; -+ -+ mutex_lock(&vgpu->dmabuf_lock); -+ -+ dmabuf_obj = pick_dmabuf_by_num(vgpu, dmabuf_id); -+ if (dmabuf_obj == NULL) { -+ gvt_vgpu_err("invalid dmabuf id:%d\n", dmabuf_id); -+ ret = -EINVAL; -+ goto out; -+ } -+ -+ obj = vgpu_create_gem(dev, dmabuf_obj->info); -+ if (obj == NULL) { -+ gvt_vgpu_err("create gvt gem obj failed\n"); -+ ret = -ENOMEM; -+ goto out; -+ } -+ -+ obj->gvt_info = dmabuf_obj->info; -+ -+ dmabuf = i915_gem_prime_export(dev, &obj->base, DRM_CLOEXEC | DRM_RDWR); -+ if (IS_ERR(dmabuf)) { -+ gvt_vgpu_err("export dma-buf failed\n"); -+ ret = PTR_ERR(dmabuf); -+ goto out_free_gem; -+ } -+ -+ i915_gem_object_put(obj); -+ -+ ret = dma_buf_fd(dmabuf, DRM_CLOEXEC | DRM_RDWR); -+ if (ret < 0) { -+ gvt_vgpu_err("create dma-buf fd failed ret:%d\n", ret); -+ goto out_free_dmabuf; -+ } -+ dmabuf_fd = ret; -+ -+ dmabuf_obj_get(dmabuf_obj); -+ -+ if (dmabuf_obj->initref) { -+ dmabuf_obj->initref = false; -+ dmabuf_obj_put(dmabuf_obj); -+ } -+ -+ mutex_unlock(&vgpu->dmabuf_lock); -+ -+ gvt_dbg_dpy("vgpu%d: dmabuf:%d, dmabuf ref %d, fd:%d\n" -+ " file count: %ld, GEM ref: %d\n", -+ vgpu->id, dmabuf_obj->dmabuf_id, -+ kref_read(&dmabuf_obj->kref), -+ dmabuf_fd, -+ file_count(dmabuf->file), -+ kref_read(&obj->base.refcount)); -+ -+ return dmabuf_fd; -+ -+out_free_dmabuf: -+ dma_buf_put(dmabuf); -+out_free_gem: -+ i915_gem_object_put(obj); -+out: -+ mutex_unlock(&vgpu->dmabuf_lock); -+ return ret; -+} -+ -+void intel_vgpu_dmabuf_cleanup(struct intel_vgpu *vgpu) -+{ -+ struct list_head *pos, *n; -+ struct intel_vgpu_dmabuf_obj *dmabuf_obj; -+ -+ mutex_lock(&vgpu->dmabuf_lock); -+ list_for_each_safe(pos, n, &vgpu->dmabuf_obj_list_head) { -+ dmabuf_obj = container_of(pos, struct intel_vgpu_dmabuf_obj, -+ list); -+ dmabuf_obj->vgpu = NULL; -+ -+ idr_remove(&vgpu->object_idr, dmabuf_obj->dmabuf_id); -+ intel_gvt_hypervisor_put_vfio_device(vgpu); -+ list_del(pos); -+ -+ /* dmabuf_obj might be freed in dmabuf_obj_put */ -+ if (dmabuf_obj->initref) { -+ dmabuf_obj->initref = false; -+ dmabuf_obj_put(dmabuf_obj); -+ } -+ -+ } -+ mutex_unlock(&vgpu->dmabuf_lock); -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/dmabuf.h b/drivers/gpu/drm/i915_legacy/gvt/dmabuf.h -new file mode 100644 -index 000000000000..5f8f03fb1d1b ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/dmabuf.h -@@ -0,0 +1,67 @@ -+/* -+ * Copyright(c) 2017 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Zhiyuan Lv -+ * -+ * Contributors: -+ * Xiaoguang Chen -+ * Tina Zhang -+ */ -+ -+#ifndef _GVT_DMABUF_H_ -+#define _GVT_DMABUF_H_ -+#include -+ -+struct intel_vgpu_fb_info { -+ __u64 start; -+ __u64 start_gpa; -+ __u64 drm_format_mod; -+ __u32 drm_format; /* drm format of plane */ -+ __u32 width; /* width of plane */ -+ __u32 height; /* height of plane */ -+ __u32 stride; /* stride of plane */ -+ __u32 size; /* size of plane in bytes, align on page */ -+ __u32 x_pos; /* horizontal position of cursor plane */ -+ __u32 y_pos; /* vertical position of cursor plane */ -+ __u32 x_hot; /* horizontal position of cursor hotspot */ -+ __u32 y_hot; /* vertical position of cursor hotspot */ -+ struct intel_vgpu_dmabuf_obj *obj; -+}; -+ -+/** -+ * struct intel_vgpu_dmabuf_obj- Intel vGPU device buffer object -+ */ -+struct intel_vgpu_dmabuf_obj { -+ struct intel_vgpu *vgpu; -+ struct intel_vgpu_fb_info *info; -+ __u32 dmabuf_id; -+ struct kref kref; -+ bool initref; -+ struct list_head list; -+}; -+ -+int intel_vgpu_query_plane(struct intel_vgpu *vgpu, void *args); -+int intel_vgpu_get_dmabuf(struct intel_vgpu *vgpu, unsigned int dmabuf_id); -+void intel_vgpu_dmabuf_cleanup(struct intel_vgpu *vgpu); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/gvt/edid.c b/drivers/gpu/drm/i915_legacy/gvt/edid.c -new file mode 100644 -index 000000000000..1fe6124918f1 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/edid.c -@@ -0,0 +1,576 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Ke Yu -+ * Zhiyuan Lv -+ * -+ * Contributors: -+ * Terrence Xu -+ * Changbin Du -+ * Bing Niu -+ * Zhi Wang -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "gvt.h" -+ -+#define GMBUS1_TOTAL_BYTES_SHIFT 16 -+#define GMBUS1_TOTAL_BYTES_MASK 0x1ff -+#define gmbus1_total_byte_count(v) (((v) >> \ -+ GMBUS1_TOTAL_BYTES_SHIFT) & GMBUS1_TOTAL_BYTES_MASK) -+#define gmbus1_slave_addr(v) (((v) & 0xff) >> 1) -+#define gmbus1_slave_index(v) (((v) >> 8) & 0xff) -+#define gmbus1_bus_cycle(v) (((v) >> 25) & 0x7) -+ -+/* GMBUS0 bits definitions */ -+#define _GMBUS_PIN_SEL_MASK (0x7) -+ -+static unsigned char edid_get_byte(struct intel_vgpu *vgpu) -+{ -+ struct intel_vgpu_i2c_edid *edid = &vgpu->display.i2c_edid; -+ unsigned char chr = 0; -+ -+ if (edid->state == I2C_NOT_SPECIFIED || !edid->slave_selected) { -+ gvt_vgpu_err("Driver tries to read EDID without proper sequence!\n"); -+ return 0; -+ } -+ if (edid->current_edid_read >= EDID_SIZE) { -+ gvt_vgpu_err("edid_get_byte() exceeds the size of EDID!\n"); -+ return 0; -+ } -+ -+ if (!edid->edid_available) { -+ gvt_vgpu_err("Reading EDID but EDID is not available!\n"); -+ return 0; -+ } -+ -+ if (intel_vgpu_has_monitor_on_port(vgpu, edid->port)) { -+ struct intel_vgpu_edid_data *edid_data = -+ intel_vgpu_port(vgpu, edid->port)->edid; -+ -+ chr = edid_data->edid_block[edid->current_edid_read]; -+ edid->current_edid_read++; -+ } else { -+ gvt_vgpu_err("No EDID available during the reading?\n"); -+ } -+ return chr; -+} -+ -+static inline int cnp_get_port_from_gmbus0(u32 gmbus0) -+{ -+ int port_select = gmbus0 & _GMBUS_PIN_SEL_MASK; -+ int port = -EINVAL; -+ -+ if (port_select == GMBUS_PIN_1_BXT) -+ port = PORT_B; -+ else if (port_select == GMBUS_PIN_2_BXT) -+ port = PORT_C; -+ else if (port_select == GMBUS_PIN_3_BXT) -+ port = PORT_D; -+ else if (port_select == GMBUS_PIN_4_CNP) -+ port = PORT_E; -+ return port; -+} -+ -+static inline int bxt_get_port_from_gmbus0(u32 gmbus0) -+{ -+ int port_select = gmbus0 & _GMBUS_PIN_SEL_MASK; -+ int port = -EINVAL; -+ -+ if (port_select == GMBUS_PIN_1_BXT) -+ port = PORT_B; -+ else if (port_select == GMBUS_PIN_2_BXT) -+ port = PORT_C; -+ else if (port_select == GMBUS_PIN_3_BXT) -+ port = PORT_D; -+ return port; -+} -+ -+static inline int get_port_from_gmbus0(u32 gmbus0) -+{ -+ int port_select = gmbus0 & _GMBUS_PIN_SEL_MASK; -+ int port = -EINVAL; -+ -+ if (port_select == GMBUS_PIN_VGADDC) -+ port = PORT_E; -+ else if (port_select == GMBUS_PIN_DPC) -+ port = PORT_C; -+ else if (port_select == GMBUS_PIN_DPB) -+ port = PORT_B; -+ else if (port_select == GMBUS_PIN_DPD) -+ port = PORT_D; -+ return port; -+} -+ -+static void reset_gmbus_controller(struct intel_vgpu *vgpu) -+{ -+ vgpu_vreg_t(vgpu, PCH_GMBUS2) = GMBUS_HW_RDY; -+ if (!vgpu->display.i2c_edid.edid_available) -+ vgpu_vreg_t(vgpu, PCH_GMBUS2) |= GMBUS_SATOER; -+ vgpu->display.i2c_edid.gmbus.phase = GMBUS_IDLE_PHASE; -+} -+ -+/* GMBUS0 */ -+static int gmbus0_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ int port, pin_select; -+ -+ memcpy(&vgpu_vreg(vgpu, offset), p_data, bytes); -+ -+ pin_select = vgpu_vreg(vgpu, offset) & _GMBUS_PIN_SEL_MASK; -+ -+ intel_vgpu_init_i2c_edid(vgpu); -+ -+ if (pin_select == 0) -+ return 0; -+ -+ if (IS_BROXTON(dev_priv)) -+ port = bxt_get_port_from_gmbus0(pin_select); -+ else if (IS_COFFEELAKE(dev_priv)) -+ port = cnp_get_port_from_gmbus0(pin_select); -+ else -+ port = get_port_from_gmbus0(pin_select); -+ if (WARN_ON(port < 0)) -+ return 0; -+ -+ vgpu->display.i2c_edid.state = I2C_GMBUS; -+ vgpu->display.i2c_edid.gmbus.phase = GMBUS_IDLE_PHASE; -+ -+ vgpu_vreg_t(vgpu, PCH_GMBUS2) &= ~GMBUS_ACTIVE; -+ vgpu_vreg_t(vgpu, PCH_GMBUS2) |= GMBUS_HW_RDY | GMBUS_HW_WAIT_PHASE; -+ -+ if (intel_vgpu_has_monitor_on_port(vgpu, port) && -+ !intel_vgpu_port_is_dp(vgpu, port)) { -+ vgpu->display.i2c_edid.port = port; -+ vgpu->display.i2c_edid.edid_available = true; -+ vgpu_vreg_t(vgpu, PCH_GMBUS2) &= ~GMBUS_SATOER; -+ } else -+ vgpu_vreg_t(vgpu, PCH_GMBUS2) |= GMBUS_SATOER; -+ return 0; -+} -+ -+static int gmbus1_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ struct intel_vgpu_i2c_edid *i2c_edid = &vgpu->display.i2c_edid; -+ u32 slave_addr; -+ u32 wvalue = *(u32 *)p_data; -+ -+ if (vgpu_vreg(vgpu, offset) & GMBUS_SW_CLR_INT) { -+ if (!(wvalue & GMBUS_SW_CLR_INT)) { -+ vgpu_vreg(vgpu, offset) &= ~GMBUS_SW_CLR_INT; -+ reset_gmbus_controller(vgpu); -+ } -+ /* -+ * TODO: "This bit is cleared to zero when an event -+ * causes the HW_RDY bit transition to occur " -+ */ -+ } else { -+ /* -+ * per bspec setting this bit can cause: -+ * 1) INT status bit cleared -+ * 2) HW_RDY bit asserted -+ */ -+ if (wvalue & GMBUS_SW_CLR_INT) { -+ vgpu_vreg_t(vgpu, PCH_GMBUS2) &= ~GMBUS_INT; -+ vgpu_vreg_t(vgpu, PCH_GMBUS2) |= GMBUS_HW_RDY; -+ } -+ -+ /* For virtualization, we suppose that HW is always ready, -+ * so GMBUS_SW_RDY should always be cleared -+ */ -+ if (wvalue & GMBUS_SW_RDY) -+ wvalue &= ~GMBUS_SW_RDY; -+ -+ i2c_edid->gmbus.total_byte_count = -+ gmbus1_total_byte_count(wvalue); -+ slave_addr = gmbus1_slave_addr(wvalue); -+ -+ /* vgpu gmbus only support EDID */ -+ if (slave_addr == EDID_ADDR) { -+ i2c_edid->slave_selected = true; -+ } else if (slave_addr != 0) { -+ gvt_dbg_dpy( -+ "vgpu%d: unsupported gmbus slave addr(0x%x)\n" -+ " gmbus operations will be ignored.\n", -+ vgpu->id, slave_addr); -+ } -+ -+ if (wvalue & GMBUS_CYCLE_INDEX) -+ i2c_edid->current_edid_read = -+ gmbus1_slave_index(wvalue); -+ -+ i2c_edid->gmbus.cycle_type = gmbus1_bus_cycle(wvalue); -+ switch (gmbus1_bus_cycle(wvalue)) { -+ case GMBUS_NOCYCLE: -+ break; -+ case GMBUS_STOP: -+ /* From spec: -+ * This can only cause a STOP to be generated -+ * if a GMBUS cycle is generated, the GMBUS is -+ * currently in a data/wait/idle phase, or it is in a -+ * WAIT phase -+ */ -+ if (gmbus1_bus_cycle(vgpu_vreg(vgpu, offset)) -+ != GMBUS_NOCYCLE) { -+ intel_vgpu_init_i2c_edid(vgpu); -+ /* After the 'stop' cycle, hw state would become -+ * 'stop phase' and then 'idle phase' after a -+ * few milliseconds. In emulation, we just set -+ * it as 'idle phase' ('stop phase' is not -+ * visible in gmbus interface) -+ */ -+ i2c_edid->gmbus.phase = GMBUS_IDLE_PHASE; -+ vgpu_vreg_t(vgpu, PCH_GMBUS2) &= ~GMBUS_ACTIVE; -+ } -+ break; -+ case NIDX_NS_W: -+ case IDX_NS_W: -+ case NIDX_STOP: -+ case IDX_STOP: -+ /* From hw spec the GMBUS phase -+ * transition like this: -+ * START (-->INDEX) -->DATA -+ */ -+ i2c_edid->gmbus.phase = GMBUS_DATA_PHASE; -+ vgpu_vreg_t(vgpu, PCH_GMBUS2) |= GMBUS_ACTIVE; -+ break; -+ default: -+ gvt_vgpu_err("Unknown/reserved GMBUS cycle detected!\n"); -+ break; -+ } -+ /* -+ * From hw spec the WAIT state will be -+ * cleared: -+ * (1) in a new GMBUS cycle -+ * (2) by generating a stop -+ */ -+ vgpu_vreg(vgpu, offset) = wvalue; -+ } -+ return 0; -+} -+ -+static int gmbus3_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ WARN_ON(1); -+ return 0; -+} -+ -+static int gmbus3_mmio_read(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ int i; -+ unsigned char byte_data; -+ struct intel_vgpu_i2c_edid *i2c_edid = &vgpu->display.i2c_edid; -+ int byte_left = i2c_edid->gmbus.total_byte_count - -+ i2c_edid->current_edid_read; -+ int byte_count = byte_left; -+ u32 reg_data = 0; -+ -+ /* Data can only be recevied if previous settings correct */ -+ if (vgpu_vreg_t(vgpu, PCH_GMBUS1) & GMBUS_SLAVE_READ) { -+ if (byte_left <= 0) { -+ memcpy(p_data, &vgpu_vreg(vgpu, offset), bytes); -+ return 0; -+ } -+ -+ if (byte_count > 4) -+ byte_count = 4; -+ for (i = 0; i < byte_count; i++) { -+ byte_data = edid_get_byte(vgpu); -+ reg_data |= (byte_data << (i << 3)); -+ } -+ -+ memcpy(&vgpu_vreg(vgpu, offset), ®_data, byte_count); -+ memcpy(p_data, &vgpu_vreg(vgpu, offset), bytes); -+ -+ if (byte_left <= 4) { -+ switch (i2c_edid->gmbus.cycle_type) { -+ case NIDX_STOP: -+ case IDX_STOP: -+ i2c_edid->gmbus.phase = GMBUS_IDLE_PHASE; -+ break; -+ case NIDX_NS_W: -+ case IDX_NS_W: -+ default: -+ i2c_edid->gmbus.phase = GMBUS_WAIT_PHASE; -+ break; -+ } -+ intel_vgpu_init_i2c_edid(vgpu); -+ } -+ /* -+ * Read GMBUS3 during send operation, -+ * return the latest written value -+ */ -+ } else { -+ memcpy(p_data, &vgpu_vreg(vgpu, offset), bytes); -+ gvt_vgpu_err("warning: gmbus3 read with nothing returned\n"); -+ } -+ return 0; -+} -+ -+static int gmbus2_mmio_read(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 value = vgpu_vreg(vgpu, offset); -+ -+ if (!(vgpu_vreg(vgpu, offset) & GMBUS_INUSE)) -+ vgpu_vreg(vgpu, offset) |= GMBUS_INUSE; -+ memcpy(p_data, (void *)&value, bytes); -+ return 0; -+} -+ -+static int gmbus2_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 wvalue = *(u32 *)p_data; -+ -+ if (wvalue & GMBUS_INUSE) -+ vgpu_vreg(vgpu, offset) &= ~GMBUS_INUSE; -+ /* All other bits are read-only */ -+ return 0; -+} -+ -+/** -+ * intel_gvt_i2c_handle_gmbus_read - emulate gmbus register mmio read -+ * @vgpu: a vGPU -+ * @offset: reg offset -+ * @p_data: data return buffer -+ * @bytes: access data length -+ * -+ * This function is used to emulate gmbus register mmio read -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ * -+ */ -+int intel_gvt_i2c_handle_gmbus_read(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ if (WARN_ON(bytes > 8 && (offset & (bytes - 1)))) -+ return -EINVAL; -+ -+ if (offset == i915_mmio_reg_offset(PCH_GMBUS2)) -+ return gmbus2_mmio_read(vgpu, offset, p_data, bytes); -+ else if (offset == i915_mmio_reg_offset(PCH_GMBUS3)) -+ return gmbus3_mmio_read(vgpu, offset, p_data, bytes); -+ -+ memcpy(p_data, &vgpu_vreg(vgpu, offset), bytes); -+ return 0; -+} -+ -+/** -+ * intel_gvt_i2c_handle_gmbus_write - emulate gmbus register mmio write -+ * @vgpu: a vGPU -+ * @offset: reg offset -+ * @p_data: data return buffer -+ * @bytes: access data length -+ * -+ * This function is used to emulate gmbus register mmio write -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ * -+ */ -+int intel_gvt_i2c_handle_gmbus_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ if (WARN_ON(bytes > 8 && (offset & (bytes - 1)))) -+ return -EINVAL; -+ -+ if (offset == i915_mmio_reg_offset(PCH_GMBUS0)) -+ return gmbus0_mmio_write(vgpu, offset, p_data, bytes); -+ else if (offset == i915_mmio_reg_offset(PCH_GMBUS1)) -+ return gmbus1_mmio_write(vgpu, offset, p_data, bytes); -+ else if (offset == i915_mmio_reg_offset(PCH_GMBUS2)) -+ return gmbus2_mmio_write(vgpu, offset, p_data, bytes); -+ else if (offset == i915_mmio_reg_offset(PCH_GMBUS3)) -+ return gmbus3_mmio_write(vgpu, offset, p_data, bytes); -+ -+ memcpy(&vgpu_vreg(vgpu, offset), p_data, bytes); -+ return 0; -+} -+ -+enum { -+ AUX_CH_CTL = 0, -+ AUX_CH_DATA1, -+ AUX_CH_DATA2, -+ AUX_CH_DATA3, -+ AUX_CH_DATA4, -+ AUX_CH_DATA5 -+}; -+ -+static inline int get_aux_ch_reg(unsigned int offset) -+{ -+ int reg; -+ -+ switch (offset & 0xff) { -+ case 0x10: -+ reg = AUX_CH_CTL; -+ break; -+ case 0x14: -+ reg = AUX_CH_DATA1; -+ break; -+ case 0x18: -+ reg = AUX_CH_DATA2; -+ break; -+ case 0x1c: -+ reg = AUX_CH_DATA3; -+ break; -+ case 0x20: -+ reg = AUX_CH_DATA4; -+ break; -+ case 0x24: -+ reg = AUX_CH_DATA5; -+ break; -+ default: -+ reg = -1; -+ break; -+ } -+ return reg; -+} -+ -+#define AUX_CTL_MSG_LENGTH(reg) \ -+ ((reg & DP_AUX_CH_CTL_MESSAGE_SIZE_MASK) >> \ -+ DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT) -+ -+/** -+ * intel_gvt_i2c_handle_aux_ch_write - emulate AUX channel register write -+ * @vgpu: a vGPU -+ * @port_idx: port index -+ * @offset: reg offset -+ * @p_data: write ptr -+ * -+ * This function is used to emulate AUX channel register write -+ * -+ */ -+void intel_gvt_i2c_handle_aux_ch_write(struct intel_vgpu *vgpu, -+ int port_idx, -+ unsigned int offset, -+ void *p_data) -+{ -+ struct intel_vgpu_i2c_edid *i2c_edid = &vgpu->display.i2c_edid; -+ int msg_length, ret_msg_size; -+ int msg, addr, ctrl, op; -+ u32 value = *(u32 *)p_data; -+ int aux_data_for_write = 0; -+ int reg = get_aux_ch_reg(offset); -+ -+ if (reg != AUX_CH_CTL) { -+ vgpu_vreg(vgpu, offset) = value; -+ return; -+ } -+ -+ msg_length = AUX_CTL_MSG_LENGTH(value); -+ // check the msg in DATA register. -+ msg = vgpu_vreg(vgpu, offset + 4); -+ addr = (msg >> 8) & 0xffff; -+ ctrl = (msg >> 24) & 0xff; -+ op = ctrl >> 4; -+ if (!(value & DP_AUX_CH_CTL_SEND_BUSY)) { -+ /* The ctl write to clear some states */ -+ return; -+ } -+ -+ /* Always set the wanted value for vms. */ -+ ret_msg_size = (((op & 0x1) == GVT_AUX_I2C_READ) ? 2 : 1); -+ vgpu_vreg(vgpu, offset) = -+ DP_AUX_CH_CTL_DONE | -+ ((ret_msg_size << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT) & -+ DP_AUX_CH_CTL_MESSAGE_SIZE_MASK); -+ -+ if (msg_length == 3) { -+ if (!(op & GVT_AUX_I2C_MOT)) { -+ /* stop */ -+ intel_vgpu_init_i2c_edid(vgpu); -+ } else { -+ /* start or restart */ -+ i2c_edid->aux_ch.i2c_over_aux_ch = true; -+ i2c_edid->aux_ch.aux_ch_mot = true; -+ if (addr == 0) { -+ /* reset the address */ -+ intel_vgpu_init_i2c_edid(vgpu); -+ } else if (addr == EDID_ADDR) { -+ i2c_edid->state = I2C_AUX_CH; -+ i2c_edid->port = port_idx; -+ i2c_edid->slave_selected = true; -+ if (intel_vgpu_has_monitor_on_port(vgpu, -+ port_idx) && -+ intel_vgpu_port_is_dp(vgpu, port_idx)) -+ i2c_edid->edid_available = true; -+ } -+ } -+ } else if ((op & 0x1) == GVT_AUX_I2C_WRITE) { -+ /* TODO -+ * We only support EDID reading from I2C_over_AUX. And -+ * we do not expect the index mode to be used. Right now -+ * the WRITE operation is ignored. It is good enough to -+ * support the gfx driver to do EDID access. -+ */ -+ } else { -+ if (WARN_ON((op & 0x1) != GVT_AUX_I2C_READ)) -+ return; -+ if (WARN_ON(msg_length != 4)) -+ return; -+ if (i2c_edid->edid_available && i2c_edid->slave_selected) { -+ unsigned char val = edid_get_byte(vgpu); -+ -+ aux_data_for_write = (val << 16); -+ } else -+ aux_data_for_write = (0xff << 16); -+ } -+ /* write the return value in AUX_CH_DATA reg which includes: -+ * ACK of I2C_WRITE -+ * returned byte if it is READ -+ */ -+ aux_data_for_write |= GVT_AUX_I2C_REPLY_ACK << 24; -+ vgpu_vreg(vgpu, offset + 4) = aux_data_for_write; -+} -+ -+/** -+ * intel_vgpu_init_i2c_edid - initialize vGPU i2c edid emulation -+ * @vgpu: a vGPU -+ * -+ * This function is used to initialize vGPU i2c edid emulation stuffs -+ * -+ */ -+void intel_vgpu_init_i2c_edid(struct intel_vgpu *vgpu) -+{ -+ struct intel_vgpu_i2c_edid *edid = &vgpu->display.i2c_edid; -+ -+ edid->state = I2C_NOT_SPECIFIED; -+ -+ edid->port = -1; -+ edid->slave_selected = false; -+ edid->edid_available = false; -+ edid->current_edid_read = 0; -+ -+ memset(&edid->gmbus, 0, sizeof(struct intel_vgpu_i2c_gmbus)); -+ -+ edid->aux_ch.i2c_over_aux_ch = false; -+ edid->aux_ch.aux_ch_mot = false; -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/edid.h b/drivers/gpu/drm/i915_legacy/gvt/edid.h -new file mode 100644 -index 000000000000..f6dfc8b795ec ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/edid.h -@@ -0,0 +1,150 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Ke Yu -+ * Zhiyuan Lv -+ * -+ * Contributors: -+ * Terrence Xu -+ * Changbin Du -+ * Bing Niu -+ * Zhi Wang -+ * -+ */ -+ -+#ifndef _GVT_EDID_H_ -+#define _GVT_EDID_H_ -+ -+#define EDID_SIZE 128 -+#define EDID_ADDR 0x50 /* Linux hvm EDID addr */ -+ -+#define GVT_AUX_NATIVE_WRITE 0x8 -+#define GVT_AUX_NATIVE_READ 0x9 -+#define GVT_AUX_I2C_WRITE 0x0 -+#define GVT_AUX_I2C_READ 0x1 -+#define GVT_AUX_I2C_STATUS 0x2 -+#define GVT_AUX_I2C_MOT 0x4 -+#define GVT_AUX_I2C_REPLY_ACK 0x0 -+ -+struct intel_vgpu_edid_data { -+ bool data_valid; -+ unsigned char edid_block[EDID_SIZE]; -+}; -+ -+enum gmbus_cycle_type { -+ GMBUS_NOCYCLE = 0x0, -+ NIDX_NS_W = 0x1, -+ IDX_NS_W = 0x3, -+ GMBUS_STOP = 0x4, -+ NIDX_STOP = 0x5, -+ IDX_STOP = 0x7 -+}; -+ -+/* -+ * States of GMBUS -+ * -+ * GMBUS0-3 could be related to the EDID virtualization. Another two GMBUS -+ * registers, GMBUS4 (interrupt mask) and GMBUS5 (2 byte indes register), are -+ * not considered here. Below describes the usage of GMBUS registers that are -+ * cared by the EDID virtualization -+ * -+ * GMBUS0: -+ * R/W -+ * port selection. value of bit0 - bit2 corresponds to the GPIO registers. -+ * -+ * GMBUS1: -+ * R/W Protect -+ * Command and Status. -+ * bit0 is the direction bit: 1 is read; 0 is write. -+ * bit1 - bit7 is slave 7-bit address. -+ * bit16 - bit24 total byte count (ignore?) -+ * -+ * GMBUS2: -+ * Most of bits are read only except bit 15 (IN_USE) -+ * Status register -+ * bit0 - bit8 current byte count -+ * bit 11: hardware ready; -+ * -+ * GMBUS3: -+ * Read/Write -+ * Data for transfer -+ */ -+ -+/* From hw specs, Other phases like START, ADDRESS, INDEX -+ * are invisible to GMBUS MMIO interface. So no definitions -+ * in below enum types -+ */ -+enum gvt_gmbus_phase { -+ GMBUS_IDLE_PHASE = 0, -+ GMBUS_DATA_PHASE, -+ GMBUS_WAIT_PHASE, -+ //GMBUS_STOP_PHASE, -+ GMBUS_MAX_PHASE -+}; -+ -+struct intel_vgpu_i2c_gmbus { -+ unsigned int total_byte_count; /* from GMBUS1 */ -+ enum gmbus_cycle_type cycle_type; -+ enum gvt_gmbus_phase phase; -+}; -+ -+struct intel_vgpu_i2c_aux_ch { -+ bool i2c_over_aux_ch; -+ bool aux_ch_mot; -+}; -+ -+enum i2c_state { -+ I2C_NOT_SPECIFIED = 0, -+ I2C_GMBUS = 1, -+ I2C_AUX_CH = 2 -+}; -+ -+/* I2C sequences cannot interleave. -+ * GMBUS and AUX_CH sequences cannot interleave. -+ */ -+struct intel_vgpu_i2c_edid { -+ enum i2c_state state; -+ -+ unsigned int port; -+ bool slave_selected; -+ bool edid_available; -+ unsigned int current_edid_read; -+ -+ struct intel_vgpu_i2c_gmbus gmbus; -+ struct intel_vgpu_i2c_aux_ch aux_ch; -+}; -+ -+void intel_vgpu_init_i2c_edid(struct intel_vgpu *vgpu); -+ -+int intel_gvt_i2c_handle_gmbus_read(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes); -+ -+int intel_gvt_i2c_handle_gmbus_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes); -+ -+void intel_gvt_i2c_handle_aux_ch_write(struct intel_vgpu *vgpu, -+ int port_idx, -+ unsigned int offset, -+ void *p_data); -+ -+#endif /*_GVT_EDID_H_*/ -diff --git a/drivers/gpu/drm/i915_legacy/gvt/execlist.c b/drivers/gpu/drm/i915_legacy/gvt/execlist.c -new file mode 100644 -index 000000000000..f21b8fb5b37e ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/execlist.c -@@ -0,0 +1,567 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Zhiyuan Lv -+ * Zhi Wang -+ * -+ * Contributors: -+ * Min He -+ * Bing Niu -+ * Ping Gao -+ * Tina Zhang -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "gvt.h" -+ -+#define _EL_OFFSET_STATUS 0x234 -+#define _EL_OFFSET_STATUS_BUF 0x370 -+#define _EL_OFFSET_STATUS_PTR 0x3A0 -+ -+#define execlist_ring_mmio(gvt, ring_id, offset) \ -+ (gvt->dev_priv->engine[ring_id]->mmio_base + (offset)) -+ -+#define valid_context(ctx) ((ctx)->valid) -+#define same_context(a, b) (((a)->context_id == (b)->context_id) && \ -+ ((a)->lrca == (b)->lrca)) -+ -+static int context_switch_events[] = { -+ [RCS0] = RCS_AS_CONTEXT_SWITCH, -+ [BCS0] = BCS_AS_CONTEXT_SWITCH, -+ [VCS0] = VCS_AS_CONTEXT_SWITCH, -+ [VCS1] = VCS2_AS_CONTEXT_SWITCH, -+ [VECS0] = VECS_AS_CONTEXT_SWITCH, -+}; -+ -+static int ring_id_to_context_switch_event(unsigned int ring_id) -+{ -+ if (WARN_ON(ring_id >= ARRAY_SIZE(context_switch_events))) -+ return -EINVAL; -+ -+ return context_switch_events[ring_id]; -+} -+ -+static void switch_virtual_execlist_slot(struct intel_vgpu_execlist *execlist) -+{ -+ gvt_dbg_el("[before] running slot %d/context %x pending slot %d\n", -+ execlist->running_slot ? -+ execlist->running_slot->index : -1, -+ execlist->running_context ? -+ execlist->running_context->context_id : 0, -+ execlist->pending_slot ? -+ execlist->pending_slot->index : -1); -+ -+ execlist->running_slot = execlist->pending_slot; -+ execlist->pending_slot = NULL; -+ execlist->running_context = execlist->running_context ? -+ &execlist->running_slot->ctx[0] : NULL; -+ -+ gvt_dbg_el("[after] running slot %d/context %x pending slot %d\n", -+ execlist->running_slot ? -+ execlist->running_slot->index : -1, -+ execlist->running_context ? -+ execlist->running_context->context_id : 0, -+ execlist->pending_slot ? -+ execlist->pending_slot->index : -1); -+} -+ -+static void emulate_execlist_status(struct intel_vgpu_execlist *execlist) -+{ -+ struct intel_vgpu_execlist_slot *running = execlist->running_slot; -+ struct intel_vgpu_execlist_slot *pending = execlist->pending_slot; -+ struct execlist_ctx_descriptor_format *desc = execlist->running_context; -+ struct intel_vgpu *vgpu = execlist->vgpu; -+ struct execlist_status_format status; -+ int ring_id = execlist->ring_id; -+ u32 status_reg = execlist_ring_mmio(vgpu->gvt, -+ ring_id, _EL_OFFSET_STATUS); -+ -+ status.ldw = vgpu_vreg(vgpu, status_reg); -+ status.udw = vgpu_vreg(vgpu, status_reg + 4); -+ -+ if (running) { -+ status.current_execlist_pointer = !!running->index; -+ status.execlist_write_pointer = !!!running->index; -+ status.execlist_0_active = status.execlist_0_valid = -+ !!!(running->index); -+ status.execlist_1_active = status.execlist_1_valid = -+ !!(running->index); -+ } else { -+ status.context_id = 0; -+ status.execlist_0_active = status.execlist_0_valid = 0; -+ status.execlist_1_active = status.execlist_1_valid = 0; -+ } -+ -+ status.context_id = desc ? desc->context_id : 0; -+ status.execlist_queue_full = !!(pending); -+ -+ vgpu_vreg(vgpu, status_reg) = status.ldw; -+ vgpu_vreg(vgpu, status_reg + 4) = status.udw; -+ -+ gvt_dbg_el("vgpu%d: status reg offset %x ldw %x udw %x\n", -+ vgpu->id, status_reg, status.ldw, status.udw); -+} -+ -+static void emulate_csb_update(struct intel_vgpu_execlist *execlist, -+ struct execlist_context_status_format *status, -+ bool trigger_interrupt_later) -+{ -+ struct intel_vgpu *vgpu = execlist->vgpu; -+ int ring_id = execlist->ring_id; -+ struct execlist_context_status_pointer_format ctx_status_ptr; -+ u32 write_pointer; -+ u32 ctx_status_ptr_reg, ctx_status_buf_reg, offset; -+ unsigned long hwsp_gpa; -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ -+ ctx_status_ptr_reg = execlist_ring_mmio(vgpu->gvt, ring_id, -+ _EL_OFFSET_STATUS_PTR); -+ ctx_status_buf_reg = execlist_ring_mmio(vgpu->gvt, ring_id, -+ _EL_OFFSET_STATUS_BUF); -+ -+ ctx_status_ptr.dw = vgpu_vreg(vgpu, ctx_status_ptr_reg); -+ -+ write_pointer = ctx_status_ptr.write_ptr; -+ -+ if (write_pointer == 0x7) -+ write_pointer = 0; -+ else { -+ ++write_pointer; -+ write_pointer %= 0x6; -+ } -+ -+ offset = ctx_status_buf_reg + write_pointer * 8; -+ -+ vgpu_vreg(vgpu, offset) = status->ldw; -+ vgpu_vreg(vgpu, offset + 4) = status->udw; -+ -+ ctx_status_ptr.write_ptr = write_pointer; -+ vgpu_vreg(vgpu, ctx_status_ptr_reg) = ctx_status_ptr.dw; -+ -+ /* Update the CSB and CSB write pointer in HWSP */ -+ hwsp_gpa = intel_vgpu_gma_to_gpa(vgpu->gtt.ggtt_mm, -+ vgpu->hws_pga[ring_id]); -+ if (hwsp_gpa != INTEL_GVT_INVALID_ADDR) { -+ intel_gvt_hypervisor_write_gpa(vgpu, -+ hwsp_gpa + I915_HWS_CSB_BUF0_INDEX * 4 + -+ write_pointer * 8, -+ status, 8); -+ intel_gvt_hypervisor_write_gpa(vgpu, -+ hwsp_gpa + -+ intel_hws_csb_write_index(dev_priv) * 4, -+ &write_pointer, 4); -+ } -+ -+ gvt_dbg_el("vgpu%d: w pointer %u reg %x csb l %x csb h %x\n", -+ vgpu->id, write_pointer, offset, status->ldw, status->udw); -+ -+ if (trigger_interrupt_later) -+ return; -+ -+ intel_vgpu_trigger_virtual_event(vgpu, -+ ring_id_to_context_switch_event(execlist->ring_id)); -+} -+ -+static int emulate_execlist_ctx_schedule_out( -+ struct intel_vgpu_execlist *execlist, -+ struct execlist_ctx_descriptor_format *ctx) -+{ -+ struct intel_vgpu *vgpu = execlist->vgpu; -+ struct intel_vgpu_execlist_slot *running = execlist->running_slot; -+ struct intel_vgpu_execlist_slot *pending = execlist->pending_slot; -+ struct execlist_ctx_descriptor_format *ctx0 = &running->ctx[0]; -+ struct execlist_ctx_descriptor_format *ctx1 = &running->ctx[1]; -+ struct execlist_context_status_format status; -+ -+ memset(&status, 0, sizeof(status)); -+ -+ gvt_dbg_el("schedule out context id %x\n", ctx->context_id); -+ -+ if (WARN_ON(!same_context(ctx, execlist->running_context))) { -+ gvt_vgpu_err("schedule out context is not running context," -+ "ctx id %x running ctx id %x\n", -+ ctx->context_id, -+ execlist->running_context->context_id); -+ return -EINVAL; -+ } -+ -+ /* ctx1 is valid, ctx0/ctx is scheduled-out -> element switch */ -+ if (valid_context(ctx1) && same_context(ctx0, ctx)) { -+ gvt_dbg_el("ctx 1 valid, ctx/ctx 0 is scheduled-out\n"); -+ -+ execlist->running_context = ctx1; -+ -+ emulate_execlist_status(execlist); -+ -+ status.context_complete = status.element_switch = 1; -+ status.context_id = ctx->context_id; -+ -+ emulate_csb_update(execlist, &status, false); -+ /* -+ * ctx1 is not valid, ctx == ctx0 -+ * ctx1 is valid, ctx1 == ctx -+ * --> last element is finished -+ * emulate: -+ * active-to-idle if there is *no* pending execlist -+ * context-complete if there *is* pending execlist -+ */ -+ } else if ((!valid_context(ctx1) && same_context(ctx0, ctx)) -+ || (valid_context(ctx1) && same_context(ctx1, ctx))) { -+ gvt_dbg_el("need to switch virtual execlist slot\n"); -+ -+ switch_virtual_execlist_slot(execlist); -+ -+ emulate_execlist_status(execlist); -+ -+ status.context_complete = status.active_to_idle = 1; -+ status.context_id = ctx->context_id; -+ -+ if (!pending) { -+ emulate_csb_update(execlist, &status, false); -+ } else { -+ emulate_csb_update(execlist, &status, true); -+ -+ memset(&status, 0, sizeof(status)); -+ -+ status.idle_to_active = 1; -+ status.context_id = 0; -+ -+ emulate_csb_update(execlist, &status, false); -+ } -+ } else { -+ WARN_ON(1); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static struct intel_vgpu_execlist_slot *get_next_execlist_slot( -+ struct intel_vgpu_execlist *execlist) -+{ -+ struct intel_vgpu *vgpu = execlist->vgpu; -+ int ring_id = execlist->ring_id; -+ u32 status_reg = execlist_ring_mmio(vgpu->gvt, ring_id, -+ _EL_OFFSET_STATUS); -+ struct execlist_status_format status; -+ -+ status.ldw = vgpu_vreg(vgpu, status_reg); -+ status.udw = vgpu_vreg(vgpu, status_reg + 4); -+ -+ if (status.execlist_queue_full) { -+ gvt_vgpu_err("virtual execlist slots are full\n"); -+ return NULL; -+ } -+ -+ return &execlist->slot[status.execlist_write_pointer]; -+} -+ -+static int emulate_execlist_schedule_in(struct intel_vgpu_execlist *execlist, -+ struct execlist_ctx_descriptor_format ctx[2]) -+{ -+ struct intel_vgpu_execlist_slot *running = execlist->running_slot; -+ struct intel_vgpu_execlist_slot *slot = -+ get_next_execlist_slot(execlist); -+ -+ struct execlist_ctx_descriptor_format *ctx0, *ctx1; -+ struct execlist_context_status_format status; -+ struct intel_vgpu *vgpu = execlist->vgpu; -+ -+ gvt_dbg_el("emulate schedule-in\n"); -+ -+ if (!slot) { -+ gvt_vgpu_err("no available execlist slot\n"); -+ return -EINVAL; -+ } -+ -+ memset(&status, 0, sizeof(status)); -+ memset(slot->ctx, 0, sizeof(slot->ctx)); -+ -+ slot->ctx[0] = ctx[0]; -+ slot->ctx[1] = ctx[1]; -+ -+ gvt_dbg_el("alloc slot index %d ctx 0 %x ctx 1 %x\n", -+ slot->index, ctx[0].context_id, -+ ctx[1].context_id); -+ -+ /* -+ * no running execlist, make this write bundle as running execlist -+ * -> idle-to-active -+ */ -+ if (!running) { -+ gvt_dbg_el("no current running execlist\n"); -+ -+ execlist->running_slot = slot; -+ execlist->pending_slot = NULL; -+ execlist->running_context = &slot->ctx[0]; -+ -+ gvt_dbg_el("running slot index %d running context %x\n", -+ execlist->running_slot->index, -+ execlist->running_context->context_id); -+ -+ emulate_execlist_status(execlist); -+ -+ status.idle_to_active = 1; -+ status.context_id = 0; -+ -+ emulate_csb_update(execlist, &status, false); -+ return 0; -+ } -+ -+ ctx0 = &running->ctx[0]; -+ ctx1 = &running->ctx[1]; -+ -+ gvt_dbg_el("current running slot index %d ctx 0 %x ctx 1 %x\n", -+ running->index, ctx0->context_id, ctx1->context_id); -+ -+ /* -+ * already has an running execlist -+ * a. running ctx1 is valid, -+ * ctx0 is finished, and running ctx1 == new execlist ctx[0] -+ * b. running ctx1 is not valid, -+ * ctx0 == new execlist ctx[0] -+ * ----> lite-restore + preempted -+ */ -+ if ((valid_context(ctx1) && same_context(ctx1, &slot->ctx[0]) && -+ /* condition a */ -+ (!same_context(ctx0, execlist->running_context))) || -+ (!valid_context(ctx1) && -+ same_context(ctx0, &slot->ctx[0]))) { /* condition b */ -+ gvt_dbg_el("need to switch virtual execlist slot\n"); -+ -+ execlist->pending_slot = slot; -+ switch_virtual_execlist_slot(execlist); -+ -+ emulate_execlist_status(execlist); -+ -+ status.lite_restore = status.preempted = 1; -+ status.context_id = ctx[0].context_id; -+ -+ emulate_csb_update(execlist, &status, false); -+ } else { -+ gvt_dbg_el("emulate as pending slot\n"); -+ /* -+ * otherwise -+ * --> emulate pending execlist exist + but no preemption case -+ */ -+ execlist->pending_slot = slot; -+ emulate_execlist_status(execlist); -+ } -+ return 0; -+} -+ -+#define get_desc_from_elsp_dwords(ed, i) \ -+ ((struct execlist_ctx_descriptor_format *)&((ed)->data[i * 2])) -+ -+static int prepare_execlist_workload(struct intel_vgpu_workload *workload) -+{ -+ struct intel_vgpu *vgpu = workload->vgpu; -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ struct execlist_ctx_descriptor_format ctx[2]; -+ int ring_id = workload->ring_id; -+ int ret; -+ -+ if (!workload->emulate_schedule_in) -+ return 0; -+ -+ ctx[0] = *get_desc_from_elsp_dwords(&workload->elsp_dwords, 0); -+ ctx[1] = *get_desc_from_elsp_dwords(&workload->elsp_dwords, 1); -+ -+ ret = emulate_execlist_schedule_in(&s->execlist[ring_id], ctx); -+ if (ret) { -+ gvt_vgpu_err("fail to emulate execlist schedule in\n"); -+ return ret; -+ } -+ return 0; -+} -+ -+static int complete_execlist_workload(struct intel_vgpu_workload *workload) -+{ -+ struct intel_vgpu *vgpu = workload->vgpu; -+ int ring_id = workload->ring_id; -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ struct intel_vgpu_execlist *execlist = &s->execlist[ring_id]; -+ struct intel_vgpu_workload *next_workload; -+ struct list_head *next = workload_q_head(vgpu, ring_id)->next; -+ bool lite_restore = false; -+ int ret = 0; -+ -+ gvt_dbg_el("complete workload %p status %d\n", workload, -+ workload->status); -+ -+ if (workload->status || (vgpu->resetting_eng & BIT(ring_id))) -+ goto out; -+ -+ if (!list_empty(workload_q_head(vgpu, ring_id))) { -+ struct execlist_ctx_descriptor_format *this_desc, *next_desc; -+ -+ next_workload = container_of(next, -+ struct intel_vgpu_workload, list); -+ this_desc = &workload->ctx_desc; -+ next_desc = &next_workload->ctx_desc; -+ -+ lite_restore = same_context(this_desc, next_desc); -+ } -+ -+ if (lite_restore) { -+ gvt_dbg_el("next context == current - no schedule-out\n"); -+ goto out; -+ } -+ -+ ret = emulate_execlist_ctx_schedule_out(execlist, &workload->ctx_desc); -+out: -+ intel_vgpu_unpin_mm(workload->shadow_mm); -+ intel_vgpu_destroy_workload(workload); -+ return ret; -+} -+ -+static int submit_context(struct intel_vgpu *vgpu, int ring_id, -+ struct execlist_ctx_descriptor_format *desc, -+ bool emulate_schedule_in) -+{ -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ struct intel_vgpu_workload *workload = NULL; -+ -+ workload = intel_vgpu_create_workload(vgpu, ring_id, desc); -+ if (IS_ERR(workload)) -+ return PTR_ERR(workload); -+ -+ workload->prepare = prepare_execlist_workload; -+ workload->complete = complete_execlist_workload; -+ workload->emulate_schedule_in = emulate_schedule_in; -+ -+ if (emulate_schedule_in) -+ workload->elsp_dwords = s->execlist[ring_id].elsp_dwords; -+ -+ gvt_dbg_el("workload %p emulate schedule_in %d\n", workload, -+ emulate_schedule_in); -+ -+ intel_vgpu_queue_workload(workload); -+ return 0; -+} -+ -+int intel_vgpu_submit_execlist(struct intel_vgpu *vgpu, int ring_id) -+{ -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ struct intel_vgpu_execlist *execlist = &s->execlist[ring_id]; -+ struct execlist_ctx_descriptor_format *desc[2]; -+ int i, ret; -+ -+ desc[0] = get_desc_from_elsp_dwords(&execlist->elsp_dwords, 0); -+ desc[1] = get_desc_from_elsp_dwords(&execlist->elsp_dwords, 1); -+ -+ if (!desc[0]->valid) { -+ gvt_vgpu_err("invalid elsp submission, desc0 is invalid\n"); -+ goto inv_desc; -+ } -+ -+ for (i = 0; i < ARRAY_SIZE(desc); i++) { -+ if (!desc[i]->valid) -+ continue; -+ if (!desc[i]->privilege_access) { -+ gvt_vgpu_err("unexpected GGTT elsp submission\n"); -+ goto inv_desc; -+ } -+ } -+ -+ /* submit workload */ -+ for (i = 0; i < ARRAY_SIZE(desc); i++) { -+ if (!desc[i]->valid) -+ continue; -+ ret = submit_context(vgpu, ring_id, desc[i], i == 0); -+ if (ret) { -+ gvt_vgpu_err("failed to submit desc %d\n", i); -+ return ret; -+ } -+ } -+ -+ return 0; -+ -+inv_desc: -+ gvt_vgpu_err("descriptors content: desc0 %08x %08x desc1 %08x %08x\n", -+ desc[0]->udw, desc[0]->ldw, desc[1]->udw, desc[1]->ldw); -+ return -EINVAL; -+} -+ -+static void init_vgpu_execlist(struct intel_vgpu *vgpu, int ring_id) -+{ -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ struct intel_vgpu_execlist *execlist = &s->execlist[ring_id]; -+ struct execlist_context_status_pointer_format ctx_status_ptr; -+ u32 ctx_status_ptr_reg; -+ -+ memset(execlist, 0, sizeof(*execlist)); -+ -+ execlist->vgpu = vgpu; -+ execlist->ring_id = ring_id; -+ execlist->slot[0].index = 0; -+ execlist->slot[1].index = 1; -+ -+ ctx_status_ptr_reg = execlist_ring_mmio(vgpu->gvt, ring_id, -+ _EL_OFFSET_STATUS_PTR); -+ ctx_status_ptr.dw = vgpu_vreg(vgpu, ctx_status_ptr_reg); -+ ctx_status_ptr.read_ptr = 0; -+ ctx_status_ptr.write_ptr = 0x7; -+ vgpu_vreg(vgpu, ctx_status_ptr_reg) = ctx_status_ptr.dw; -+} -+ -+static void clean_execlist(struct intel_vgpu *vgpu, -+ intel_engine_mask_t engine_mask) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ struct intel_engine_cs *engine; -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ intel_engine_mask_t tmp; -+ -+ for_each_engine_masked(engine, dev_priv, engine_mask, tmp) { -+ kfree(s->ring_scan_buffer[engine->id]); -+ s->ring_scan_buffer[engine->id] = NULL; -+ s->ring_scan_buffer_size[engine->id] = 0; -+ } -+} -+ -+static void reset_execlist(struct intel_vgpu *vgpu, -+ intel_engine_mask_t engine_mask) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ struct intel_engine_cs *engine; -+ intel_engine_mask_t tmp; -+ -+ for_each_engine_masked(engine, dev_priv, engine_mask, tmp) -+ init_vgpu_execlist(vgpu, engine->id); -+} -+ -+static int init_execlist(struct intel_vgpu *vgpu, -+ intel_engine_mask_t engine_mask) -+{ -+ reset_execlist(vgpu, engine_mask); -+ return 0; -+} -+ -+const struct intel_vgpu_submission_ops intel_vgpu_execlist_submission_ops = { -+ .name = "execlist", -+ .init = init_execlist, -+ .reset = reset_execlist, -+ .clean = clean_execlist, -+}; -diff --git a/drivers/gpu/drm/i915_legacy/gvt/execlist.h b/drivers/gpu/drm/i915_legacy/gvt/execlist.h -new file mode 100644 -index 000000000000..5ccc2c695848 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/execlist.h -@@ -0,0 +1,185 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Zhiyuan Lv -+ * Zhi Wang -+ * -+ * Contributors: -+ * Min He -+ * Bing Niu -+ * Ping Gao -+ * Tina Zhang -+ * -+ */ -+ -+#ifndef _GVT_EXECLIST_H_ -+#define _GVT_EXECLIST_H_ -+ -+struct execlist_ctx_descriptor_format { -+ union { -+ u32 ldw; -+ struct { -+ u32 valid : 1; -+ u32 force_pd_restore : 1; -+ u32 force_restore : 1; -+ u32 addressing_mode : 2; -+ u32 llc_coherency : 1; -+ u32 fault_handling : 2; -+ u32 privilege_access : 1; -+ u32 reserved : 3; -+ u32 lrca : 20; -+ }; -+ }; -+ union { -+ u32 udw; -+ u32 context_id; -+ }; -+}; -+ -+struct execlist_status_format { -+ union { -+ u32 ldw; -+ struct { -+ u32 current_execlist_pointer :1; -+ u32 execlist_write_pointer :1; -+ u32 execlist_queue_full :1; -+ u32 execlist_1_valid :1; -+ u32 execlist_0_valid :1; -+ u32 last_ctx_switch_reason :9; -+ u32 current_active_elm_status :2; -+ u32 arbitration_enable :1; -+ u32 execlist_1_active :1; -+ u32 execlist_0_active :1; -+ u32 reserved :13; -+ }; -+ }; -+ union { -+ u32 udw; -+ u32 context_id; -+ }; -+}; -+ -+struct execlist_context_status_pointer_format { -+ union { -+ u32 dw; -+ struct { -+ u32 write_ptr :3; -+ u32 reserved :5; -+ u32 read_ptr :3; -+ u32 reserved2 :5; -+ u32 mask :16; -+ }; -+ }; -+}; -+ -+struct execlist_context_status_format { -+ union { -+ u32 ldw; -+ struct { -+ u32 idle_to_active :1; -+ u32 preempted :1; -+ u32 element_switch :1; -+ u32 active_to_idle :1; -+ u32 context_complete :1; -+ u32 wait_on_sync_flip :1; -+ u32 wait_on_vblank :1; -+ u32 wait_on_semaphore :1; -+ u32 wait_on_scanline :1; -+ u32 reserved :2; -+ u32 semaphore_wait_mode :1; -+ u32 display_plane :3; -+ u32 lite_restore :1; -+ u32 reserved_2 :16; -+ }; -+ }; -+ union { -+ u32 udw; -+ u32 context_id; -+ }; -+}; -+ -+struct execlist_mmio_pair { -+ u32 addr; -+ u32 val; -+}; -+ -+/* The first 52 dwords in register state context */ -+struct execlist_ring_context { -+ u32 nop1; -+ u32 lri_cmd_1; -+ struct execlist_mmio_pair ctx_ctrl; -+ struct execlist_mmio_pair ring_header; -+ struct execlist_mmio_pair ring_tail; -+ struct execlist_mmio_pair rb_start; -+ struct execlist_mmio_pair rb_ctrl; -+ struct execlist_mmio_pair bb_cur_head_UDW; -+ struct execlist_mmio_pair bb_cur_head_LDW; -+ struct execlist_mmio_pair bb_state; -+ struct execlist_mmio_pair second_bb_addr_UDW; -+ struct execlist_mmio_pair second_bb_addr_LDW; -+ struct execlist_mmio_pair second_bb_state; -+ struct execlist_mmio_pair bb_per_ctx_ptr; -+ struct execlist_mmio_pair rcs_indirect_ctx; -+ struct execlist_mmio_pair rcs_indirect_ctx_offset; -+ u32 nop2; -+ u32 nop3; -+ u32 nop4; -+ u32 lri_cmd_2; -+ struct execlist_mmio_pair ctx_timestamp; -+ /* -+ * pdps[8]={ pdp3_UDW, pdp3_LDW, pdp2_UDW, pdp2_LDW, -+ * pdp1_UDW, pdp1_LDW, pdp0_UDW, pdp0_LDW} -+ */ -+ struct execlist_mmio_pair pdps[8]; -+}; -+ -+struct intel_vgpu_elsp_dwords { -+ u32 data[4]; -+ u32 index; -+}; -+ -+struct intel_vgpu_execlist_slot { -+ struct execlist_ctx_descriptor_format ctx[2]; -+ u32 index; -+}; -+ -+struct intel_vgpu_execlist { -+ struct intel_vgpu_execlist_slot slot[2]; -+ struct intel_vgpu_execlist_slot *running_slot; -+ struct intel_vgpu_execlist_slot *pending_slot; -+ struct execlist_ctx_descriptor_format *running_context; -+ int ring_id; -+ struct intel_vgpu *vgpu; -+ struct intel_vgpu_elsp_dwords elsp_dwords; -+}; -+ -+void intel_vgpu_clean_execlist(struct intel_vgpu *vgpu); -+ -+int intel_vgpu_init_execlist(struct intel_vgpu *vgpu); -+ -+int intel_vgpu_submit_execlist(struct intel_vgpu *vgpu, int ring_id); -+ -+void intel_vgpu_reset_execlist(struct intel_vgpu *vgpu, -+ intel_engine_mask_t engine_mask); -+ -+#endif /*_GVT_EXECLIST_H_*/ -diff --git a/drivers/gpu/drm/i915_legacy/gvt/fb_decoder.c b/drivers/gpu/drm/i915_legacy/gvt/fb_decoder.c -new file mode 100644 -index 000000000000..65e847392aea ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/fb_decoder.c -@@ -0,0 +1,507 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Kevin Tian -+ * -+ * Contributors: -+ * Bing Niu -+ * Xu Han -+ * Ping Gao -+ * Xiaoguang Chen -+ * Yang Liu -+ * Tina Zhang -+ * -+ */ -+ -+#include -+#include "i915_drv.h" -+#include "gvt.h" -+#include "i915_pvinfo.h" -+ -+#define PRIMARY_FORMAT_NUM 16 -+struct pixel_format { -+ int drm_format; /* Pixel format in DRM definition */ -+ int bpp; /* Bits per pixel, 0 indicates invalid */ -+ char *desc; /* The description */ -+}; -+ -+static struct pixel_format bdw_pixel_formats[] = { -+ {DRM_FORMAT_C8, 8, "8-bit Indexed"}, -+ {DRM_FORMAT_RGB565, 16, "16-bit BGRX (5:6:5 MSB-R:G:B)"}, -+ {DRM_FORMAT_XRGB8888, 32, "32-bit BGRX (8:8:8:8 MSB-X:R:G:B)"}, -+ {DRM_FORMAT_XBGR2101010, 32, "32-bit RGBX (2:10:10:10 MSB-X:B:G:R)"}, -+ -+ {DRM_FORMAT_XRGB2101010, 32, "32-bit BGRX (2:10:10:10 MSB-X:R:G:B)"}, -+ {DRM_FORMAT_XBGR8888, 32, "32-bit RGBX (8:8:8:8 MSB-X:B:G:R)"}, -+ -+ /* non-supported format has bpp default to 0 */ -+ {0, 0, NULL}, -+}; -+ -+static struct pixel_format skl_pixel_formats[] = { -+ {DRM_FORMAT_YUYV, 16, "16-bit packed YUYV (8:8:8:8 MSB-V:Y2:U:Y1)"}, -+ {DRM_FORMAT_UYVY, 16, "16-bit packed UYVY (8:8:8:8 MSB-Y2:V:Y1:U)"}, -+ {DRM_FORMAT_YVYU, 16, "16-bit packed YVYU (8:8:8:8 MSB-U:Y2:V:Y1)"}, -+ {DRM_FORMAT_VYUY, 16, "16-bit packed VYUY (8:8:8:8 MSB-Y2:U:Y1:V)"}, -+ -+ {DRM_FORMAT_C8, 8, "8-bit Indexed"}, -+ {DRM_FORMAT_RGB565, 16, "16-bit BGRX (5:6:5 MSB-R:G:B)"}, -+ {DRM_FORMAT_ABGR8888, 32, "32-bit RGBA (8:8:8:8 MSB-A:B:G:R)"}, -+ {DRM_FORMAT_XBGR8888, 32, "32-bit RGBX (8:8:8:8 MSB-X:B:G:R)"}, -+ -+ {DRM_FORMAT_ARGB8888, 32, "32-bit BGRA (8:8:8:8 MSB-A:R:G:B)"}, -+ {DRM_FORMAT_XRGB8888, 32, "32-bit BGRX (8:8:8:8 MSB-X:R:G:B)"}, -+ {DRM_FORMAT_XBGR2101010, 32, "32-bit RGBX (2:10:10:10 MSB-X:B:G:R)"}, -+ {DRM_FORMAT_XRGB2101010, 32, "32-bit BGRX (2:10:10:10 MSB-X:R:G:B)"}, -+ -+ /* non-supported format has bpp default to 0 */ -+ {0, 0, NULL}, -+}; -+ -+static int bdw_format_to_drm(int format) -+{ -+ int bdw_pixel_formats_index = 6; -+ -+ switch (format) { -+ case DISPPLANE_8BPP: -+ bdw_pixel_formats_index = 0; -+ break; -+ case DISPPLANE_BGRX565: -+ bdw_pixel_formats_index = 1; -+ break; -+ case DISPPLANE_BGRX888: -+ bdw_pixel_formats_index = 2; -+ break; -+ case DISPPLANE_RGBX101010: -+ bdw_pixel_formats_index = 3; -+ break; -+ case DISPPLANE_BGRX101010: -+ bdw_pixel_formats_index = 4; -+ break; -+ case DISPPLANE_RGBX888: -+ bdw_pixel_formats_index = 5; -+ break; -+ -+ default: -+ break; -+ } -+ -+ return bdw_pixel_formats_index; -+} -+ -+static int skl_format_to_drm(int format, bool rgb_order, bool alpha, -+ int yuv_order) -+{ -+ int skl_pixel_formats_index = 12; -+ -+ switch (format) { -+ case PLANE_CTL_FORMAT_INDEXED: -+ skl_pixel_formats_index = 4; -+ break; -+ case PLANE_CTL_FORMAT_RGB_565: -+ skl_pixel_formats_index = 5; -+ break; -+ case PLANE_CTL_FORMAT_XRGB_8888: -+ if (rgb_order) -+ skl_pixel_formats_index = alpha ? 6 : 7; -+ else -+ skl_pixel_formats_index = alpha ? 8 : 9; -+ break; -+ case PLANE_CTL_FORMAT_XRGB_2101010: -+ skl_pixel_formats_index = rgb_order ? 10 : 11; -+ break; -+ case PLANE_CTL_FORMAT_YUV422: -+ skl_pixel_formats_index = yuv_order >> 16; -+ if (skl_pixel_formats_index > 3) -+ return -EINVAL; -+ break; -+ -+ default: -+ break; -+ } -+ -+ return skl_pixel_formats_index; -+} -+ -+static u32 intel_vgpu_get_stride(struct intel_vgpu *vgpu, int pipe, -+ u32 tiled, int stride_mask, int bpp) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ -+ u32 stride_reg = vgpu_vreg_t(vgpu, DSPSTRIDE(pipe)) & stride_mask; -+ u32 stride = stride_reg; -+ -+ if (INTEL_GEN(dev_priv) >= 9) { -+ switch (tiled) { -+ case PLANE_CTL_TILED_LINEAR: -+ stride = stride_reg * 64; -+ break; -+ case PLANE_CTL_TILED_X: -+ stride = stride_reg * 512; -+ break; -+ case PLANE_CTL_TILED_Y: -+ stride = stride_reg * 128; -+ break; -+ case PLANE_CTL_TILED_YF: -+ if (bpp == 8) -+ stride = stride_reg * 64; -+ else if (bpp == 16 || bpp == 32 || bpp == 64) -+ stride = stride_reg * 128; -+ else -+ gvt_dbg_core("skl: unsupported bpp:%d\n", bpp); -+ break; -+ default: -+ gvt_dbg_core("skl: unsupported tile format:%x\n", -+ tiled); -+ } -+ } -+ -+ return stride; -+} -+ -+static int get_active_pipe(struct intel_vgpu *vgpu) -+{ -+ int i; -+ -+ for (i = 0; i < I915_MAX_PIPES; i++) -+ if (pipe_is_enabled(vgpu, i)) -+ break; -+ -+ return i; -+} -+ -+/** -+ * intel_vgpu_decode_primary_plane - Decode primary plane -+ * @vgpu: input vgpu -+ * @plane: primary plane to save decoded info -+ * This function is called for decoding plane -+ * -+ * Returns: -+ * 0 on success, non-zero if failed. -+ */ -+int intel_vgpu_decode_primary_plane(struct intel_vgpu *vgpu, -+ struct intel_vgpu_primary_plane_format *plane) -+{ -+ u32 val, fmt; -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ int pipe; -+ -+ pipe = get_active_pipe(vgpu); -+ if (pipe >= I915_MAX_PIPES) -+ return -ENODEV; -+ -+ val = vgpu_vreg_t(vgpu, DSPCNTR(pipe)); -+ plane->enabled = !!(val & DISPLAY_PLANE_ENABLE); -+ if (!plane->enabled) -+ return -ENODEV; -+ -+ if (INTEL_GEN(dev_priv) >= 9) { -+ plane->tiled = val & PLANE_CTL_TILED_MASK; -+ fmt = skl_format_to_drm( -+ val & PLANE_CTL_FORMAT_MASK, -+ val & PLANE_CTL_ORDER_RGBX, -+ val & PLANE_CTL_ALPHA_MASK, -+ val & PLANE_CTL_YUV422_ORDER_MASK); -+ -+ if (fmt >= ARRAY_SIZE(skl_pixel_formats)) { -+ gvt_vgpu_err("Out-of-bounds pixel format index\n"); -+ return -EINVAL; -+ } -+ -+ plane->bpp = skl_pixel_formats[fmt].bpp; -+ plane->drm_format = skl_pixel_formats[fmt].drm_format; -+ } else { -+ plane->tiled = val & DISPPLANE_TILED; -+ fmt = bdw_format_to_drm(val & DISPPLANE_PIXFORMAT_MASK); -+ plane->bpp = bdw_pixel_formats[fmt].bpp; -+ plane->drm_format = bdw_pixel_formats[fmt].drm_format; -+ } -+ -+ if (!plane->bpp) { -+ gvt_vgpu_err("Non-supported pixel format (0x%x)\n", fmt); -+ return -EINVAL; -+ } -+ -+ plane->hw_format = fmt; -+ -+ plane->base = vgpu_vreg_t(vgpu, DSPSURF(pipe)) & I915_GTT_PAGE_MASK; -+ if (!intel_gvt_ggtt_validate_range(vgpu, plane->base, 0)) -+ return -EINVAL; -+ -+ plane->base_gpa = intel_vgpu_gma_to_gpa(vgpu->gtt.ggtt_mm, plane->base); -+ if (plane->base_gpa == INTEL_GVT_INVALID_ADDR) { -+ gvt_vgpu_err("Translate primary plane gma 0x%x to gpa fail\n", -+ plane->base); -+ return -EINVAL; -+ } -+ -+ plane->stride = intel_vgpu_get_stride(vgpu, pipe, plane->tiled, -+ (INTEL_GEN(dev_priv) >= 9) ? -+ (_PRI_PLANE_STRIDE_MASK >> 6) : -+ _PRI_PLANE_STRIDE_MASK, plane->bpp); -+ -+ plane->width = (vgpu_vreg_t(vgpu, PIPESRC(pipe)) & _PIPE_H_SRCSZ_MASK) >> -+ _PIPE_H_SRCSZ_SHIFT; -+ plane->width += 1; -+ plane->height = (vgpu_vreg_t(vgpu, PIPESRC(pipe)) & -+ _PIPE_V_SRCSZ_MASK) >> _PIPE_V_SRCSZ_SHIFT; -+ plane->height += 1; /* raw height is one minus the real value */ -+ -+ val = vgpu_vreg_t(vgpu, DSPTILEOFF(pipe)); -+ plane->x_offset = (val & _PRI_PLANE_X_OFF_MASK) >> -+ _PRI_PLANE_X_OFF_SHIFT; -+ plane->y_offset = (val & _PRI_PLANE_Y_OFF_MASK) >> -+ _PRI_PLANE_Y_OFF_SHIFT; -+ -+ return 0; -+} -+ -+#define CURSOR_FORMAT_NUM (1 << 6) -+struct cursor_mode_format { -+ int drm_format; /* Pixel format in DRM definition */ -+ u8 bpp; /* Bits per pixel; 0 indicates invalid */ -+ u32 width; /* In pixel */ -+ u32 height; /* In lines */ -+ char *desc; /* The description */ -+}; -+ -+static struct cursor_mode_format cursor_pixel_formats[] = { -+ {DRM_FORMAT_ARGB8888, 32, 128, 128, "128x128 32bpp ARGB"}, -+ {DRM_FORMAT_ARGB8888, 32, 256, 256, "256x256 32bpp ARGB"}, -+ {DRM_FORMAT_ARGB8888, 32, 64, 64, "64x64 32bpp ARGB"}, -+ {DRM_FORMAT_ARGB8888, 32, 64, 64, "64x64 32bpp ARGB"}, -+ -+ /* non-supported format has bpp default to 0 */ -+ {0, 0, 0, 0, NULL}, -+}; -+ -+static int cursor_mode_to_drm(int mode) -+{ -+ int cursor_pixel_formats_index = 4; -+ -+ switch (mode) { -+ case MCURSOR_MODE_128_ARGB_AX: -+ cursor_pixel_formats_index = 0; -+ break; -+ case MCURSOR_MODE_256_ARGB_AX: -+ cursor_pixel_formats_index = 1; -+ break; -+ case MCURSOR_MODE_64_ARGB_AX: -+ cursor_pixel_formats_index = 2; -+ break; -+ case MCURSOR_MODE_64_32B_AX: -+ cursor_pixel_formats_index = 3; -+ break; -+ -+ default: -+ break; -+ } -+ -+ return cursor_pixel_formats_index; -+} -+ -+/** -+ * intel_vgpu_decode_cursor_plane - Decode sprite plane -+ * @vgpu: input vgpu -+ * @plane: cursor plane to save decoded info -+ * This function is called for decoding plane -+ * -+ * Returns: -+ * 0 on success, non-zero if failed. -+ */ -+int intel_vgpu_decode_cursor_plane(struct intel_vgpu *vgpu, -+ struct intel_vgpu_cursor_plane_format *plane) -+{ -+ u32 val, mode, index; -+ u32 alpha_plane, alpha_force; -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ int pipe; -+ -+ pipe = get_active_pipe(vgpu); -+ if (pipe >= I915_MAX_PIPES) -+ return -ENODEV; -+ -+ val = vgpu_vreg_t(vgpu, CURCNTR(pipe)); -+ mode = val & MCURSOR_MODE; -+ plane->enabled = (mode != MCURSOR_MODE_DISABLE); -+ if (!plane->enabled) -+ return -ENODEV; -+ -+ index = cursor_mode_to_drm(mode); -+ -+ if (!cursor_pixel_formats[index].bpp) { -+ gvt_vgpu_err("Non-supported cursor mode (0x%x)\n", mode); -+ return -EINVAL; -+ } -+ plane->mode = mode; -+ plane->bpp = cursor_pixel_formats[index].bpp; -+ plane->drm_format = cursor_pixel_formats[index].drm_format; -+ plane->width = cursor_pixel_formats[index].width; -+ plane->height = cursor_pixel_formats[index].height; -+ -+ alpha_plane = (val & _CURSOR_ALPHA_PLANE_MASK) >> -+ _CURSOR_ALPHA_PLANE_SHIFT; -+ alpha_force = (val & _CURSOR_ALPHA_FORCE_MASK) >> -+ _CURSOR_ALPHA_FORCE_SHIFT; -+ if (alpha_plane || alpha_force) -+ gvt_dbg_core("alpha_plane=0x%x, alpha_force=0x%x\n", -+ alpha_plane, alpha_force); -+ -+ plane->base = vgpu_vreg_t(vgpu, CURBASE(pipe)) & I915_GTT_PAGE_MASK; -+ if (!intel_gvt_ggtt_validate_range(vgpu, plane->base, 0)) -+ return -EINVAL; -+ -+ plane->base_gpa = intel_vgpu_gma_to_gpa(vgpu->gtt.ggtt_mm, plane->base); -+ if (plane->base_gpa == INTEL_GVT_INVALID_ADDR) { -+ gvt_vgpu_err("Translate cursor plane gma 0x%x to gpa fail\n", -+ plane->base); -+ return -EINVAL; -+ } -+ -+ val = vgpu_vreg_t(vgpu, CURPOS(pipe)); -+ plane->x_pos = (val & _CURSOR_POS_X_MASK) >> _CURSOR_POS_X_SHIFT; -+ plane->x_sign = (val & _CURSOR_SIGN_X_MASK) >> _CURSOR_SIGN_X_SHIFT; -+ plane->y_pos = (val & _CURSOR_POS_Y_MASK) >> _CURSOR_POS_Y_SHIFT; -+ plane->y_sign = (val & _CURSOR_SIGN_Y_MASK) >> _CURSOR_SIGN_Y_SHIFT; -+ -+ plane->x_hot = vgpu_vreg_t(vgpu, vgtif_reg(cursor_x_hot)); -+ plane->y_hot = vgpu_vreg_t(vgpu, vgtif_reg(cursor_y_hot)); -+ return 0; -+} -+ -+#define SPRITE_FORMAT_NUM (1 << 3) -+ -+static struct pixel_format sprite_pixel_formats[SPRITE_FORMAT_NUM] = { -+ [0x0] = {DRM_FORMAT_YUV422, 16, "YUV 16-bit 4:2:2 packed"}, -+ [0x1] = {DRM_FORMAT_XRGB2101010, 32, "RGB 32-bit 2:10:10:10"}, -+ [0x2] = {DRM_FORMAT_XRGB8888, 32, "RGB 32-bit 8:8:8:8"}, -+ [0x4] = {DRM_FORMAT_AYUV, 32, -+ "YUV 32-bit 4:4:4 packed (8:8:8:8 MSB-X:Y:U:V)"}, -+}; -+ -+/** -+ * intel_vgpu_decode_sprite_plane - Decode sprite plane -+ * @vgpu: input vgpu -+ * @plane: sprite plane to save decoded info -+ * This function is called for decoding plane -+ * -+ * Returns: -+ * 0 on success, non-zero if failed. -+ */ -+int intel_vgpu_decode_sprite_plane(struct intel_vgpu *vgpu, -+ struct intel_vgpu_sprite_plane_format *plane) -+{ -+ u32 val, fmt; -+ u32 color_order, yuv_order; -+ int drm_format; -+ int pipe; -+ -+ pipe = get_active_pipe(vgpu); -+ if (pipe >= I915_MAX_PIPES) -+ return -ENODEV; -+ -+ val = vgpu_vreg_t(vgpu, SPRCTL(pipe)); -+ plane->enabled = !!(val & SPRITE_ENABLE); -+ if (!plane->enabled) -+ return -ENODEV; -+ -+ plane->tiled = !!(val & SPRITE_TILED); -+ color_order = !!(val & SPRITE_RGB_ORDER_RGBX); -+ yuv_order = (val & SPRITE_YUV_BYTE_ORDER_MASK) >> -+ _SPRITE_YUV_ORDER_SHIFT; -+ -+ fmt = (val & SPRITE_PIXFORMAT_MASK) >> _SPRITE_FMT_SHIFT; -+ if (!sprite_pixel_formats[fmt].bpp) { -+ gvt_vgpu_err("Non-supported pixel format (0x%x)\n", fmt); -+ return -EINVAL; -+ } -+ plane->hw_format = fmt; -+ plane->bpp = sprite_pixel_formats[fmt].bpp; -+ drm_format = sprite_pixel_formats[fmt].drm_format; -+ -+ /* Order of RGB values in an RGBxxx buffer may be ordered RGB or -+ * BGR depending on the state of the color_order field -+ */ -+ if (!color_order) { -+ if (drm_format == DRM_FORMAT_XRGB2101010) -+ drm_format = DRM_FORMAT_XBGR2101010; -+ else if (drm_format == DRM_FORMAT_XRGB8888) -+ drm_format = DRM_FORMAT_XBGR8888; -+ } -+ -+ if (drm_format == DRM_FORMAT_YUV422) { -+ switch (yuv_order) { -+ case 0: -+ drm_format = DRM_FORMAT_YUYV; -+ break; -+ case 1: -+ drm_format = DRM_FORMAT_UYVY; -+ break; -+ case 2: -+ drm_format = DRM_FORMAT_YVYU; -+ break; -+ case 3: -+ drm_format = DRM_FORMAT_VYUY; -+ break; -+ default: -+ /* yuv_order has only 2 bits */ -+ break; -+ } -+ } -+ -+ plane->drm_format = drm_format; -+ -+ plane->base = vgpu_vreg_t(vgpu, SPRSURF(pipe)) & I915_GTT_PAGE_MASK; -+ if (!intel_gvt_ggtt_validate_range(vgpu, plane->base, 0)) -+ return -EINVAL; -+ -+ plane->base_gpa = intel_vgpu_gma_to_gpa(vgpu->gtt.ggtt_mm, plane->base); -+ if (plane->base_gpa == INTEL_GVT_INVALID_ADDR) { -+ gvt_vgpu_err("Translate sprite plane gma 0x%x to gpa fail\n", -+ plane->base); -+ return -EINVAL; -+ } -+ -+ plane->stride = vgpu_vreg_t(vgpu, SPRSTRIDE(pipe)) & -+ _SPRITE_STRIDE_MASK; -+ -+ val = vgpu_vreg_t(vgpu, SPRSIZE(pipe)); -+ plane->height = (val & _SPRITE_SIZE_HEIGHT_MASK) >> -+ _SPRITE_SIZE_HEIGHT_SHIFT; -+ plane->width = (val & _SPRITE_SIZE_WIDTH_MASK) >> -+ _SPRITE_SIZE_WIDTH_SHIFT; -+ plane->height += 1; /* raw height is one minus the real value */ -+ plane->width += 1; /* raw width is one minus the real value */ -+ -+ val = vgpu_vreg_t(vgpu, SPRPOS(pipe)); -+ plane->x_pos = (val & _SPRITE_POS_X_MASK) >> _SPRITE_POS_X_SHIFT; -+ plane->y_pos = (val & _SPRITE_POS_Y_MASK) >> _SPRITE_POS_Y_SHIFT; -+ -+ val = vgpu_vreg_t(vgpu, SPROFFSET(pipe)); -+ plane->x_offset = (val & _SPRITE_OFFSET_START_X_MASK) >> -+ _SPRITE_OFFSET_START_X_SHIFT; -+ plane->y_offset = (val & _SPRITE_OFFSET_START_Y_MASK) >> -+ _SPRITE_OFFSET_START_Y_SHIFT; -+ -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/fb_decoder.h b/drivers/gpu/drm/i915_legacy/gvt/fb_decoder.h -new file mode 100644 -index 000000000000..60c155085029 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/fb_decoder.h -@@ -0,0 +1,169 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Kevin Tian -+ * -+ * Contributors: -+ * Bing Niu -+ * Xu Han -+ * Ping Gao -+ * Xiaoguang Chen -+ * Yang Liu -+ * Tina Zhang -+ * -+ */ -+ -+#ifndef _GVT_FB_DECODER_H_ -+#define _GVT_FB_DECODER_H_ -+ -+#define _PLANE_CTL_FORMAT_SHIFT 24 -+#define _PLANE_CTL_TILED_SHIFT 10 -+#define _PIPE_V_SRCSZ_SHIFT 0 -+#define _PIPE_V_SRCSZ_MASK (0xfff << _PIPE_V_SRCSZ_SHIFT) -+#define _PIPE_H_SRCSZ_SHIFT 16 -+#define _PIPE_H_SRCSZ_MASK (0x1fff << _PIPE_H_SRCSZ_SHIFT) -+ -+#define _PRI_PLANE_FMT_SHIFT 26 -+#define _PRI_PLANE_STRIDE_MASK (0x3ff << 6) -+#define _PRI_PLANE_X_OFF_SHIFT 0 -+#define _PRI_PLANE_X_OFF_MASK (0x1fff << _PRI_PLANE_X_OFF_SHIFT) -+#define _PRI_PLANE_Y_OFF_SHIFT 16 -+#define _PRI_PLANE_Y_OFF_MASK (0xfff << _PRI_PLANE_Y_OFF_SHIFT) -+ -+#define _CURSOR_MODE 0x3f -+#define _CURSOR_ALPHA_FORCE_SHIFT 8 -+#define _CURSOR_ALPHA_FORCE_MASK (0x3 << _CURSOR_ALPHA_FORCE_SHIFT) -+#define _CURSOR_ALPHA_PLANE_SHIFT 10 -+#define _CURSOR_ALPHA_PLANE_MASK (0x3 << _CURSOR_ALPHA_PLANE_SHIFT) -+#define _CURSOR_POS_X_SHIFT 0 -+#define _CURSOR_POS_X_MASK (0x1fff << _CURSOR_POS_X_SHIFT) -+#define _CURSOR_SIGN_X_SHIFT 15 -+#define _CURSOR_SIGN_X_MASK (1 << _CURSOR_SIGN_X_SHIFT) -+#define _CURSOR_POS_Y_SHIFT 16 -+#define _CURSOR_POS_Y_MASK (0xfff << _CURSOR_POS_Y_SHIFT) -+#define _CURSOR_SIGN_Y_SHIFT 31 -+#define _CURSOR_SIGN_Y_MASK (1 << _CURSOR_SIGN_Y_SHIFT) -+ -+#define _SPRITE_FMT_SHIFT 25 -+#define _SPRITE_COLOR_ORDER_SHIFT 20 -+#define _SPRITE_YUV_ORDER_SHIFT 16 -+#define _SPRITE_STRIDE_SHIFT 6 -+#define _SPRITE_STRIDE_MASK (0x1ff << _SPRITE_STRIDE_SHIFT) -+#define _SPRITE_SIZE_WIDTH_SHIFT 0 -+#define _SPRITE_SIZE_HEIGHT_SHIFT 16 -+#define _SPRITE_SIZE_WIDTH_MASK (0x1fff << _SPRITE_SIZE_WIDTH_SHIFT) -+#define _SPRITE_SIZE_HEIGHT_MASK (0xfff << _SPRITE_SIZE_HEIGHT_SHIFT) -+#define _SPRITE_POS_X_SHIFT 0 -+#define _SPRITE_POS_Y_SHIFT 16 -+#define _SPRITE_POS_X_MASK (0x1fff << _SPRITE_POS_X_SHIFT) -+#define _SPRITE_POS_Y_MASK (0xfff << _SPRITE_POS_Y_SHIFT) -+#define _SPRITE_OFFSET_START_X_SHIFT 0 -+#define _SPRITE_OFFSET_START_Y_SHIFT 16 -+#define _SPRITE_OFFSET_START_X_MASK (0x1fff << _SPRITE_OFFSET_START_X_SHIFT) -+#define _SPRITE_OFFSET_START_Y_MASK (0xfff << _SPRITE_OFFSET_START_Y_SHIFT) -+ -+enum GVT_FB_EVENT { -+ FB_MODE_SET_START = 1, -+ FB_MODE_SET_END, -+ FB_DISPLAY_FLIP, -+}; -+ -+enum DDI_PORT { -+ DDI_PORT_NONE = 0, -+ DDI_PORT_B = 1, -+ DDI_PORT_C = 2, -+ DDI_PORT_D = 3, -+ DDI_PORT_E = 4 -+}; -+ -+struct intel_gvt; -+ -+/* color space conversion and gamma correction are not included */ -+struct intel_vgpu_primary_plane_format { -+ u8 enabled; /* plane is enabled */ -+ u32 tiled; /* tiling mode: linear, X-tiled, Y tiled, etc */ -+ u8 bpp; /* bits per pixel */ -+ u32 hw_format; /* format field in the PRI_CTL register */ -+ u32 drm_format; /* format in DRM definition */ -+ u32 base; /* framebuffer base in graphics memory */ -+ u64 base_gpa; -+ u32 x_offset; /* in pixels */ -+ u32 y_offset; /* in lines */ -+ u32 width; /* in pixels */ -+ u32 height; /* in lines */ -+ u32 stride; /* in bytes */ -+}; -+ -+struct intel_vgpu_sprite_plane_format { -+ u8 enabled; /* plane is enabled */ -+ u8 tiled; /* X-tiled */ -+ u8 bpp; /* bits per pixel */ -+ u32 hw_format; /* format field in the SPR_CTL register */ -+ u32 drm_format; /* format in DRM definition */ -+ u32 base; /* sprite base in graphics memory */ -+ u64 base_gpa; -+ u32 x_pos; /* in pixels */ -+ u32 y_pos; /* in lines */ -+ u32 x_offset; /* in pixels */ -+ u32 y_offset; /* in lines */ -+ u32 width; /* in pixels */ -+ u32 height; /* in lines */ -+ u32 stride; /* in bytes */ -+}; -+ -+struct intel_vgpu_cursor_plane_format { -+ u8 enabled; -+ u8 mode; /* cursor mode select */ -+ u8 bpp; /* bits per pixel */ -+ u32 drm_format; /* format in DRM definition */ -+ u32 base; /* cursor base in graphics memory */ -+ u64 base_gpa; -+ u32 x_pos; /* in pixels */ -+ u32 y_pos; /* in lines */ -+ u8 x_sign; /* X Position Sign */ -+ u8 y_sign; /* Y Position Sign */ -+ u32 width; /* in pixels */ -+ u32 height; /* in lines */ -+ u32 x_hot; /* in pixels */ -+ u32 y_hot; /* in pixels */ -+}; -+ -+struct intel_vgpu_pipe_format { -+ struct intel_vgpu_primary_plane_format primary; -+ struct intel_vgpu_sprite_plane_format sprite; -+ struct intel_vgpu_cursor_plane_format cursor; -+ enum DDI_PORT ddi_port; /* the DDI port that pipe is connected to */ -+}; -+ -+struct intel_vgpu_fb_format { -+ struct intel_vgpu_pipe_format pipes[I915_MAX_PIPES]; -+}; -+ -+int intel_vgpu_decode_primary_plane(struct intel_vgpu *vgpu, -+ struct intel_vgpu_primary_plane_format *plane); -+int intel_vgpu_decode_cursor_plane(struct intel_vgpu *vgpu, -+ struct intel_vgpu_cursor_plane_format *plane); -+int intel_vgpu_decode_sprite_plane(struct intel_vgpu *vgpu, -+ struct intel_vgpu_sprite_plane_format *plane); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/gvt/firmware.c b/drivers/gpu/drm/i915_legacy/gvt/firmware.c -new file mode 100644 -index 000000000000..4ac18b447247 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/firmware.c -@@ -0,0 +1,276 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Zhi Wang -+ * -+ * Contributors: -+ * Changbin Du -+ * -+ */ -+ -+#include -+#include -+ -+#include "i915_drv.h" -+#include "gvt.h" -+#include "i915_pvinfo.h" -+ -+#define FIRMWARE_VERSION (0x0) -+ -+struct gvt_firmware_header { -+ u64 magic; -+ u32 crc32; /* protect the data after this field */ -+ u32 version; -+ u64 cfg_space_size; -+ u64 cfg_space_offset; /* offset in the file */ -+ u64 mmio_size; -+ u64 mmio_offset; /* offset in the file */ -+ unsigned char data[1]; -+}; -+ -+#define dev_to_drm_minor(d) dev_get_drvdata((d)) -+ -+static ssize_t -+gvt_firmware_read(struct file *filp, struct kobject *kobj, -+ struct bin_attribute *attr, char *buf, -+ loff_t offset, size_t count) -+{ -+ memcpy(buf, attr->private + offset, count); -+ return count; -+} -+ -+static struct bin_attribute firmware_attr = { -+ .attr = {.name = "gvt_firmware", .mode = (S_IRUSR)}, -+ .read = gvt_firmware_read, -+ .write = NULL, -+ .mmap = NULL, -+}; -+ -+static int mmio_snapshot_handler(struct intel_gvt *gvt, u32 offset, void *data) -+{ -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ -+ *(u32 *)(data + offset) = I915_READ_NOTRACE(_MMIO(offset)); -+ return 0; -+} -+ -+static int expose_firmware_sysfs(struct intel_gvt *gvt) -+{ -+ struct intel_gvt_device_info *info = &gvt->device_info; -+ struct pci_dev *pdev = gvt->dev_priv->drm.pdev; -+ struct gvt_firmware_header *h; -+ void *firmware; -+ void *p; -+ unsigned long size, crc32_start; -+ int i, ret; -+ -+ size = sizeof(*h) + info->mmio_size + info->cfg_space_size; -+ firmware = vzalloc(size); -+ if (!firmware) -+ return -ENOMEM; -+ -+ h = firmware; -+ -+ h->magic = VGT_MAGIC; -+ h->version = FIRMWARE_VERSION; -+ h->cfg_space_size = info->cfg_space_size; -+ h->cfg_space_offset = offsetof(struct gvt_firmware_header, data); -+ h->mmio_size = info->mmio_size; -+ h->mmio_offset = h->cfg_space_offset + h->cfg_space_size; -+ -+ p = firmware + h->cfg_space_offset; -+ -+ for (i = 0; i < h->cfg_space_size; i += 4) -+ pci_read_config_dword(pdev, i, p + i); -+ -+ memcpy(gvt->firmware.cfg_space, p, info->cfg_space_size); -+ -+ p = firmware + h->mmio_offset; -+ -+ /* Take a snapshot of hw mmio registers. */ -+ intel_gvt_for_each_tracked_mmio(gvt, mmio_snapshot_handler, p); -+ -+ memcpy(gvt->firmware.mmio, p, info->mmio_size); -+ -+ crc32_start = offsetof(struct gvt_firmware_header, crc32) + 4; -+ h->crc32 = crc32_le(0, firmware + crc32_start, size - crc32_start); -+ -+ firmware_attr.size = size; -+ firmware_attr.private = firmware; -+ -+ ret = device_create_bin_file(&pdev->dev, &firmware_attr); -+ if (ret) { -+ vfree(firmware); -+ return ret; -+ } -+ return 0; -+} -+ -+static void clean_firmware_sysfs(struct intel_gvt *gvt) -+{ -+ struct pci_dev *pdev = gvt->dev_priv->drm.pdev; -+ -+ device_remove_bin_file(&pdev->dev, &firmware_attr); -+ vfree(firmware_attr.private); -+} -+ -+/** -+ * intel_gvt_free_firmware - free GVT firmware -+ * @gvt: intel gvt device -+ * -+ */ -+void intel_gvt_free_firmware(struct intel_gvt *gvt) -+{ -+ if (!gvt->firmware.firmware_loaded) -+ clean_firmware_sysfs(gvt); -+ -+ kfree(gvt->firmware.cfg_space); -+ kfree(gvt->firmware.mmio); -+} -+ -+static int verify_firmware(struct intel_gvt *gvt, -+ const struct firmware *fw) -+{ -+ struct intel_gvt_device_info *info = &gvt->device_info; -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ struct gvt_firmware_header *h; -+ unsigned long id, crc32_start; -+ const void *mem; -+ const char *item; -+ u64 file, request; -+ -+ h = (struct gvt_firmware_header *)fw->data; -+ -+ crc32_start = offsetofend(struct gvt_firmware_header, crc32); -+ mem = fw->data + crc32_start; -+ -+#define VERIFY(s, a, b) do { \ -+ item = (s); file = (u64)(a); request = (u64)(b); \ -+ if ((a) != (b)) \ -+ goto invalid_firmware; \ -+} while (0) -+ -+ VERIFY("magic number", h->magic, VGT_MAGIC); -+ VERIFY("version", h->version, FIRMWARE_VERSION); -+ VERIFY("crc32", h->crc32, crc32_le(0, mem, fw->size - crc32_start)); -+ VERIFY("cfg space size", h->cfg_space_size, info->cfg_space_size); -+ VERIFY("mmio size", h->mmio_size, info->mmio_size); -+ -+ mem = (fw->data + h->cfg_space_offset); -+ -+ id = *(u16 *)(mem + PCI_VENDOR_ID); -+ VERIFY("vender id", id, pdev->vendor); -+ -+ id = *(u16 *)(mem + PCI_DEVICE_ID); -+ VERIFY("device id", id, pdev->device); -+ -+ id = *(u8 *)(mem + PCI_REVISION_ID); -+ VERIFY("revision id", id, pdev->revision); -+ -+#undef VERIFY -+ return 0; -+ -+invalid_firmware: -+ gvt_dbg_core("Invalid firmware: %s [file] 0x%llx [request] 0x%llx\n", -+ item, file, request); -+ return -EINVAL; -+} -+ -+#define GVT_FIRMWARE_PATH "i915/gvt" -+ -+/** -+ * intel_gvt_load_firmware - load GVT firmware -+ * @gvt: intel gvt device -+ * -+ */ -+int intel_gvt_load_firmware(struct intel_gvt *gvt) -+{ -+ struct intel_gvt_device_info *info = &gvt->device_info; -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ struct intel_gvt_firmware *firmware = &gvt->firmware; -+ struct gvt_firmware_header *h; -+ const struct firmware *fw; -+ char *path; -+ void *mem; -+ int ret; -+ -+ path = kmalloc(PATH_MAX, GFP_KERNEL); -+ if (!path) -+ return -ENOMEM; -+ -+ mem = kmalloc(info->cfg_space_size, GFP_KERNEL); -+ if (!mem) { -+ kfree(path); -+ return -ENOMEM; -+ } -+ -+ firmware->cfg_space = mem; -+ -+ mem = kmalloc(info->mmio_size, GFP_KERNEL); -+ if (!mem) { -+ kfree(path); -+ kfree(firmware->cfg_space); -+ return -ENOMEM; -+ } -+ -+ firmware->mmio = mem; -+ -+ sprintf(path, "%s/vid_0x%04x_did_0x%04x_rid_0x%02x.golden_hw_state", -+ GVT_FIRMWARE_PATH, pdev->vendor, pdev->device, -+ pdev->revision); -+ -+ gvt_dbg_core("request hw state firmware %s...\n", path); -+ -+ ret = request_firmware(&fw, path, &dev_priv->drm.pdev->dev); -+ kfree(path); -+ -+ if (ret) -+ goto expose_firmware; -+ -+ gvt_dbg_core("success.\n"); -+ -+ ret = verify_firmware(gvt, fw); -+ if (ret) -+ goto out_free_fw; -+ -+ gvt_dbg_core("verified.\n"); -+ -+ h = (struct gvt_firmware_header *)fw->data; -+ -+ memcpy(firmware->cfg_space, fw->data + h->cfg_space_offset, -+ h->cfg_space_size); -+ memcpy(firmware->mmio, fw->data + h->mmio_offset, -+ h->mmio_size); -+ -+ release_firmware(fw); -+ firmware->firmware_loaded = true; -+ return 0; -+ -+out_free_fw: -+ release_firmware(fw); -+expose_firmware: -+ expose_firmware_sysfs(gvt); -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/gtt.c b/drivers/gpu/drm/i915_legacy/gvt/gtt.c -new file mode 100644 -index 000000000000..53115bdae12b ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/gtt.c -@@ -0,0 +1,2818 @@ -+/* -+ * GTT virtualization -+ * -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Zhi Wang -+ * Zhenyu Wang -+ * Xiao Zheng -+ * -+ * Contributors: -+ * Min He -+ * Bing Niu -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "gvt.h" -+#include "i915_pvinfo.h" -+#include "trace.h" -+ -+#if defined(VERBOSE_DEBUG) -+#define gvt_vdbg_mm(fmt, args...) gvt_dbg_mm(fmt, ##args) -+#else -+#define gvt_vdbg_mm(fmt, args...) -+#endif -+ -+static bool enable_out_of_sync = false; -+static int preallocated_oos_pages = 8192; -+ -+/* -+ * validate a gm address and related range size, -+ * translate it to host gm address -+ */ -+bool intel_gvt_ggtt_validate_range(struct intel_vgpu *vgpu, u64 addr, u32 size) -+{ -+ if (size == 0) -+ return vgpu_gmadr_is_valid(vgpu, addr); -+ -+ if (vgpu_gmadr_is_aperture(vgpu, addr) && -+ vgpu_gmadr_is_aperture(vgpu, addr + size - 1)) -+ return true; -+ else if (vgpu_gmadr_is_hidden(vgpu, addr) && -+ vgpu_gmadr_is_hidden(vgpu, addr + size - 1)) -+ return true; -+ -+ gvt_dbg_mm("Invalid ggtt range at 0x%llx, size: 0x%x\n", -+ addr, size); -+ return false; -+} -+ -+/* translate a guest gmadr to host gmadr */ -+int intel_gvt_ggtt_gmadr_g2h(struct intel_vgpu *vgpu, u64 g_addr, u64 *h_addr) -+{ -+ if (WARN(!vgpu_gmadr_is_valid(vgpu, g_addr), -+ "invalid guest gmadr %llx\n", g_addr)) -+ return -EACCES; -+ -+ if (vgpu_gmadr_is_aperture(vgpu, g_addr)) -+ *h_addr = vgpu_aperture_gmadr_base(vgpu) -+ + (g_addr - vgpu_aperture_offset(vgpu)); -+ else -+ *h_addr = vgpu_hidden_gmadr_base(vgpu) -+ + (g_addr - vgpu_hidden_offset(vgpu)); -+ return 0; -+} -+ -+/* translate a host gmadr to guest gmadr */ -+int intel_gvt_ggtt_gmadr_h2g(struct intel_vgpu *vgpu, u64 h_addr, u64 *g_addr) -+{ -+ if (WARN(!gvt_gmadr_is_valid(vgpu->gvt, h_addr), -+ "invalid host gmadr %llx\n", h_addr)) -+ return -EACCES; -+ -+ if (gvt_gmadr_is_aperture(vgpu->gvt, h_addr)) -+ *g_addr = vgpu_aperture_gmadr_base(vgpu) -+ + (h_addr - gvt_aperture_gmadr_base(vgpu->gvt)); -+ else -+ *g_addr = vgpu_hidden_gmadr_base(vgpu) -+ + (h_addr - gvt_hidden_gmadr_base(vgpu->gvt)); -+ return 0; -+} -+ -+int intel_gvt_ggtt_index_g2h(struct intel_vgpu *vgpu, unsigned long g_index, -+ unsigned long *h_index) -+{ -+ u64 h_addr; -+ int ret; -+ -+ ret = intel_gvt_ggtt_gmadr_g2h(vgpu, g_index << I915_GTT_PAGE_SHIFT, -+ &h_addr); -+ if (ret) -+ return ret; -+ -+ *h_index = h_addr >> I915_GTT_PAGE_SHIFT; -+ return 0; -+} -+ -+int intel_gvt_ggtt_h2g_index(struct intel_vgpu *vgpu, unsigned long h_index, -+ unsigned long *g_index) -+{ -+ u64 g_addr; -+ int ret; -+ -+ ret = intel_gvt_ggtt_gmadr_h2g(vgpu, h_index << I915_GTT_PAGE_SHIFT, -+ &g_addr); -+ if (ret) -+ return ret; -+ -+ *g_index = g_addr >> I915_GTT_PAGE_SHIFT; -+ return 0; -+} -+ -+#define gtt_type_is_entry(type) \ -+ (type > GTT_TYPE_INVALID && type < GTT_TYPE_PPGTT_ENTRY \ -+ && type != GTT_TYPE_PPGTT_PTE_ENTRY \ -+ && type != GTT_TYPE_PPGTT_ROOT_ENTRY) -+ -+#define gtt_type_is_pt(type) \ -+ (type >= GTT_TYPE_PPGTT_PTE_PT && type < GTT_TYPE_MAX) -+ -+#define gtt_type_is_pte_pt(type) \ -+ (type == GTT_TYPE_PPGTT_PTE_PT) -+ -+#define gtt_type_is_root_pointer(type) \ -+ (gtt_type_is_entry(type) && type > GTT_TYPE_PPGTT_ROOT_ENTRY) -+ -+#define gtt_init_entry(e, t, p, v) do { \ -+ (e)->type = t; \ -+ (e)->pdev = p; \ -+ memcpy(&(e)->val64, &v, sizeof(v)); \ -+} while (0) -+ -+/* -+ * Mappings between GTT_TYPE* enumerations. -+ * Following information can be found according to the given type: -+ * - type of next level page table -+ * - type of entry inside this level page table -+ * - type of entry with PSE set -+ * -+ * If the given type doesn't have such a kind of information, -+ * e.g. give a l4 root entry type, then request to get its PSE type, -+ * give a PTE page table type, then request to get its next level page -+ * table type, as we know l4 root entry doesn't have a PSE bit, -+ * and a PTE page table doesn't have a next level page table type, -+ * GTT_TYPE_INVALID will be returned. This is useful when traversing a -+ * page table. -+ */ -+ -+struct gtt_type_table_entry { -+ int entry_type; -+ int pt_type; -+ int next_pt_type; -+ int pse_entry_type; -+}; -+ -+#define GTT_TYPE_TABLE_ENTRY(type, e_type, cpt_type, npt_type, pse_type) \ -+ [type] = { \ -+ .entry_type = e_type, \ -+ .pt_type = cpt_type, \ -+ .next_pt_type = npt_type, \ -+ .pse_entry_type = pse_type, \ -+ } -+ -+static struct gtt_type_table_entry gtt_type_table[] = { -+ GTT_TYPE_TABLE_ENTRY(GTT_TYPE_PPGTT_ROOT_L4_ENTRY, -+ GTT_TYPE_PPGTT_ROOT_L4_ENTRY, -+ GTT_TYPE_INVALID, -+ GTT_TYPE_PPGTT_PML4_PT, -+ GTT_TYPE_INVALID), -+ GTT_TYPE_TABLE_ENTRY(GTT_TYPE_PPGTT_PML4_PT, -+ GTT_TYPE_PPGTT_PML4_ENTRY, -+ GTT_TYPE_PPGTT_PML4_PT, -+ GTT_TYPE_PPGTT_PDP_PT, -+ GTT_TYPE_INVALID), -+ GTT_TYPE_TABLE_ENTRY(GTT_TYPE_PPGTT_PML4_ENTRY, -+ GTT_TYPE_PPGTT_PML4_ENTRY, -+ GTT_TYPE_PPGTT_PML4_PT, -+ GTT_TYPE_PPGTT_PDP_PT, -+ GTT_TYPE_INVALID), -+ GTT_TYPE_TABLE_ENTRY(GTT_TYPE_PPGTT_PDP_PT, -+ GTT_TYPE_PPGTT_PDP_ENTRY, -+ GTT_TYPE_PPGTT_PDP_PT, -+ GTT_TYPE_PPGTT_PDE_PT, -+ GTT_TYPE_PPGTT_PTE_1G_ENTRY), -+ GTT_TYPE_TABLE_ENTRY(GTT_TYPE_PPGTT_ROOT_L3_ENTRY, -+ GTT_TYPE_PPGTT_ROOT_L3_ENTRY, -+ GTT_TYPE_INVALID, -+ GTT_TYPE_PPGTT_PDE_PT, -+ GTT_TYPE_PPGTT_PTE_1G_ENTRY), -+ GTT_TYPE_TABLE_ENTRY(GTT_TYPE_PPGTT_PDP_ENTRY, -+ GTT_TYPE_PPGTT_PDP_ENTRY, -+ GTT_TYPE_PPGTT_PDP_PT, -+ GTT_TYPE_PPGTT_PDE_PT, -+ GTT_TYPE_PPGTT_PTE_1G_ENTRY), -+ GTT_TYPE_TABLE_ENTRY(GTT_TYPE_PPGTT_PDE_PT, -+ GTT_TYPE_PPGTT_PDE_ENTRY, -+ GTT_TYPE_PPGTT_PDE_PT, -+ GTT_TYPE_PPGTT_PTE_PT, -+ GTT_TYPE_PPGTT_PTE_2M_ENTRY), -+ GTT_TYPE_TABLE_ENTRY(GTT_TYPE_PPGTT_PDE_ENTRY, -+ GTT_TYPE_PPGTT_PDE_ENTRY, -+ GTT_TYPE_PPGTT_PDE_PT, -+ GTT_TYPE_PPGTT_PTE_PT, -+ GTT_TYPE_PPGTT_PTE_2M_ENTRY), -+ /* We take IPS bit as 'PSE' for PTE level. */ -+ GTT_TYPE_TABLE_ENTRY(GTT_TYPE_PPGTT_PTE_PT, -+ GTT_TYPE_PPGTT_PTE_4K_ENTRY, -+ GTT_TYPE_PPGTT_PTE_PT, -+ GTT_TYPE_INVALID, -+ GTT_TYPE_PPGTT_PTE_64K_ENTRY), -+ GTT_TYPE_TABLE_ENTRY(GTT_TYPE_PPGTT_PTE_4K_ENTRY, -+ GTT_TYPE_PPGTT_PTE_4K_ENTRY, -+ GTT_TYPE_PPGTT_PTE_PT, -+ GTT_TYPE_INVALID, -+ GTT_TYPE_PPGTT_PTE_64K_ENTRY), -+ GTT_TYPE_TABLE_ENTRY(GTT_TYPE_PPGTT_PTE_64K_ENTRY, -+ GTT_TYPE_PPGTT_PTE_4K_ENTRY, -+ GTT_TYPE_PPGTT_PTE_PT, -+ GTT_TYPE_INVALID, -+ GTT_TYPE_PPGTT_PTE_64K_ENTRY), -+ GTT_TYPE_TABLE_ENTRY(GTT_TYPE_PPGTT_PTE_2M_ENTRY, -+ GTT_TYPE_PPGTT_PDE_ENTRY, -+ GTT_TYPE_PPGTT_PDE_PT, -+ GTT_TYPE_INVALID, -+ GTT_TYPE_PPGTT_PTE_2M_ENTRY), -+ GTT_TYPE_TABLE_ENTRY(GTT_TYPE_PPGTT_PTE_1G_ENTRY, -+ GTT_TYPE_PPGTT_PDP_ENTRY, -+ GTT_TYPE_PPGTT_PDP_PT, -+ GTT_TYPE_INVALID, -+ GTT_TYPE_PPGTT_PTE_1G_ENTRY), -+ GTT_TYPE_TABLE_ENTRY(GTT_TYPE_GGTT_PTE, -+ GTT_TYPE_GGTT_PTE, -+ GTT_TYPE_INVALID, -+ GTT_TYPE_INVALID, -+ GTT_TYPE_INVALID), -+}; -+ -+static inline int get_next_pt_type(int type) -+{ -+ return gtt_type_table[type].next_pt_type; -+} -+ -+static inline int get_pt_type(int type) -+{ -+ return gtt_type_table[type].pt_type; -+} -+ -+static inline int get_entry_type(int type) -+{ -+ return gtt_type_table[type].entry_type; -+} -+ -+static inline int get_pse_type(int type) -+{ -+ return gtt_type_table[type].pse_entry_type; -+} -+ -+static u64 read_pte64(struct drm_i915_private *dev_priv, unsigned long index) -+{ -+ void __iomem *addr = (gen8_pte_t __iomem *)dev_priv->ggtt.gsm + index; -+ -+ return readq(addr); -+} -+ -+static void ggtt_invalidate(struct drm_i915_private *dev_priv) -+{ -+ mmio_hw_access_pre(dev_priv); -+ I915_WRITE(GFX_FLSH_CNTL_GEN6, GFX_FLSH_CNTL_EN); -+ mmio_hw_access_post(dev_priv); -+} -+ -+static void write_pte64(struct drm_i915_private *dev_priv, -+ unsigned long index, u64 pte) -+{ -+ void __iomem *addr = (gen8_pte_t __iomem *)dev_priv->ggtt.gsm + index; -+ -+ writeq(pte, addr); -+} -+ -+static inline int gtt_get_entry64(void *pt, -+ struct intel_gvt_gtt_entry *e, -+ unsigned long index, bool hypervisor_access, unsigned long gpa, -+ struct intel_vgpu *vgpu) -+{ -+ const struct intel_gvt_device_info *info = &vgpu->gvt->device_info; -+ int ret; -+ -+ if (WARN_ON(info->gtt_entry_size != 8)) -+ return -EINVAL; -+ -+ if (hypervisor_access) { -+ ret = intel_gvt_hypervisor_read_gpa(vgpu, gpa + -+ (index << info->gtt_entry_size_shift), -+ &e->val64, 8); -+ if (WARN_ON(ret)) -+ return ret; -+ } else if (!pt) { -+ e->val64 = read_pte64(vgpu->gvt->dev_priv, index); -+ } else { -+ e->val64 = *((u64 *)pt + index); -+ } -+ return 0; -+} -+ -+static inline int gtt_set_entry64(void *pt, -+ struct intel_gvt_gtt_entry *e, -+ unsigned long index, bool hypervisor_access, unsigned long gpa, -+ struct intel_vgpu *vgpu) -+{ -+ const struct intel_gvt_device_info *info = &vgpu->gvt->device_info; -+ int ret; -+ -+ if (WARN_ON(info->gtt_entry_size != 8)) -+ return -EINVAL; -+ -+ if (hypervisor_access) { -+ ret = intel_gvt_hypervisor_write_gpa(vgpu, gpa + -+ (index << info->gtt_entry_size_shift), -+ &e->val64, 8); -+ if (WARN_ON(ret)) -+ return ret; -+ } else if (!pt) { -+ write_pte64(vgpu->gvt->dev_priv, index, e->val64); -+ } else { -+ *((u64 *)pt + index) = e->val64; -+ } -+ return 0; -+} -+ -+#define GTT_HAW 46 -+ -+#define ADDR_1G_MASK GENMASK_ULL(GTT_HAW - 1, 30) -+#define ADDR_2M_MASK GENMASK_ULL(GTT_HAW - 1, 21) -+#define ADDR_64K_MASK GENMASK_ULL(GTT_HAW - 1, 16) -+#define ADDR_4K_MASK GENMASK_ULL(GTT_HAW - 1, 12) -+ -+#define GTT_SPTE_FLAG_MASK GENMASK_ULL(62, 52) -+#define GTT_SPTE_FLAG_64K_SPLITED BIT(52) /* splited 64K gtt entry */ -+ -+#define GTT_64K_PTE_STRIDE 16 -+ -+static unsigned long gen8_gtt_get_pfn(struct intel_gvt_gtt_entry *e) -+{ -+ unsigned long pfn; -+ -+ if (e->type == GTT_TYPE_PPGTT_PTE_1G_ENTRY) -+ pfn = (e->val64 & ADDR_1G_MASK) >> PAGE_SHIFT; -+ else if (e->type == GTT_TYPE_PPGTT_PTE_2M_ENTRY) -+ pfn = (e->val64 & ADDR_2M_MASK) >> PAGE_SHIFT; -+ else if (e->type == GTT_TYPE_PPGTT_PTE_64K_ENTRY) -+ pfn = (e->val64 & ADDR_64K_MASK) >> PAGE_SHIFT; -+ else -+ pfn = (e->val64 & ADDR_4K_MASK) >> PAGE_SHIFT; -+ return pfn; -+} -+ -+static void gen8_gtt_set_pfn(struct intel_gvt_gtt_entry *e, unsigned long pfn) -+{ -+ if (e->type == GTT_TYPE_PPGTT_PTE_1G_ENTRY) { -+ e->val64 &= ~ADDR_1G_MASK; -+ pfn &= (ADDR_1G_MASK >> PAGE_SHIFT); -+ } else if (e->type == GTT_TYPE_PPGTT_PTE_2M_ENTRY) { -+ e->val64 &= ~ADDR_2M_MASK; -+ pfn &= (ADDR_2M_MASK >> PAGE_SHIFT); -+ } else if (e->type == GTT_TYPE_PPGTT_PTE_64K_ENTRY) { -+ e->val64 &= ~ADDR_64K_MASK; -+ pfn &= (ADDR_64K_MASK >> PAGE_SHIFT); -+ } else { -+ e->val64 &= ~ADDR_4K_MASK; -+ pfn &= (ADDR_4K_MASK >> PAGE_SHIFT); -+ } -+ -+ e->val64 |= (pfn << PAGE_SHIFT); -+} -+ -+static bool gen8_gtt_test_pse(struct intel_gvt_gtt_entry *e) -+{ -+ return !!(e->val64 & _PAGE_PSE); -+} -+ -+static void gen8_gtt_clear_pse(struct intel_gvt_gtt_entry *e) -+{ -+ if (gen8_gtt_test_pse(e)) { -+ switch (e->type) { -+ case GTT_TYPE_PPGTT_PTE_2M_ENTRY: -+ e->val64 &= ~_PAGE_PSE; -+ e->type = GTT_TYPE_PPGTT_PDE_ENTRY; -+ break; -+ case GTT_TYPE_PPGTT_PTE_1G_ENTRY: -+ e->type = GTT_TYPE_PPGTT_PDP_ENTRY; -+ e->val64 &= ~_PAGE_PSE; -+ break; -+ default: -+ WARN_ON(1); -+ } -+ } -+} -+ -+static bool gen8_gtt_test_ips(struct intel_gvt_gtt_entry *e) -+{ -+ if (GEM_WARN_ON(e->type != GTT_TYPE_PPGTT_PDE_ENTRY)) -+ return false; -+ -+ return !!(e->val64 & GEN8_PDE_IPS_64K); -+} -+ -+static void gen8_gtt_clear_ips(struct intel_gvt_gtt_entry *e) -+{ -+ if (GEM_WARN_ON(e->type != GTT_TYPE_PPGTT_PDE_ENTRY)) -+ return; -+ -+ e->val64 &= ~GEN8_PDE_IPS_64K; -+} -+ -+static bool gen8_gtt_test_present(struct intel_gvt_gtt_entry *e) -+{ -+ /* -+ * i915 writes PDP root pointer registers without present bit, -+ * it also works, so we need to treat root pointer entry -+ * specifically. -+ */ -+ if (e->type == GTT_TYPE_PPGTT_ROOT_L3_ENTRY -+ || e->type == GTT_TYPE_PPGTT_ROOT_L4_ENTRY) -+ return (e->val64 != 0); -+ else -+ return (e->val64 & _PAGE_PRESENT); -+} -+ -+static void gtt_entry_clear_present(struct intel_gvt_gtt_entry *e) -+{ -+ e->val64 &= ~_PAGE_PRESENT; -+} -+ -+static void gtt_entry_set_present(struct intel_gvt_gtt_entry *e) -+{ -+ e->val64 |= _PAGE_PRESENT; -+} -+ -+static bool gen8_gtt_test_64k_splited(struct intel_gvt_gtt_entry *e) -+{ -+ return !!(e->val64 & GTT_SPTE_FLAG_64K_SPLITED); -+} -+ -+static void gen8_gtt_set_64k_splited(struct intel_gvt_gtt_entry *e) -+{ -+ e->val64 |= GTT_SPTE_FLAG_64K_SPLITED; -+} -+ -+static void gen8_gtt_clear_64k_splited(struct intel_gvt_gtt_entry *e) -+{ -+ e->val64 &= ~GTT_SPTE_FLAG_64K_SPLITED; -+} -+ -+/* -+ * Per-platform GMA routines. -+ */ -+static unsigned long gma_to_ggtt_pte_index(unsigned long gma) -+{ -+ unsigned long x = (gma >> I915_GTT_PAGE_SHIFT); -+ -+ trace_gma_index(__func__, gma, x); -+ return x; -+} -+ -+#define DEFINE_PPGTT_GMA_TO_INDEX(prefix, ename, exp) \ -+static unsigned long prefix##_gma_to_##ename##_index(unsigned long gma) \ -+{ \ -+ unsigned long x = (exp); \ -+ trace_gma_index(__func__, gma, x); \ -+ return x; \ -+} -+ -+DEFINE_PPGTT_GMA_TO_INDEX(gen8, pte, (gma >> 12 & 0x1ff)); -+DEFINE_PPGTT_GMA_TO_INDEX(gen8, pde, (gma >> 21 & 0x1ff)); -+DEFINE_PPGTT_GMA_TO_INDEX(gen8, l3_pdp, (gma >> 30 & 0x3)); -+DEFINE_PPGTT_GMA_TO_INDEX(gen8, l4_pdp, (gma >> 30 & 0x1ff)); -+DEFINE_PPGTT_GMA_TO_INDEX(gen8, pml4, (gma >> 39 & 0x1ff)); -+ -+static struct intel_gvt_gtt_pte_ops gen8_gtt_pte_ops = { -+ .get_entry = gtt_get_entry64, -+ .set_entry = gtt_set_entry64, -+ .clear_present = gtt_entry_clear_present, -+ .set_present = gtt_entry_set_present, -+ .test_present = gen8_gtt_test_present, -+ .test_pse = gen8_gtt_test_pse, -+ .clear_pse = gen8_gtt_clear_pse, -+ .clear_ips = gen8_gtt_clear_ips, -+ .test_ips = gen8_gtt_test_ips, -+ .clear_64k_splited = gen8_gtt_clear_64k_splited, -+ .set_64k_splited = gen8_gtt_set_64k_splited, -+ .test_64k_splited = gen8_gtt_test_64k_splited, -+ .get_pfn = gen8_gtt_get_pfn, -+ .set_pfn = gen8_gtt_set_pfn, -+}; -+ -+static struct intel_gvt_gtt_gma_ops gen8_gtt_gma_ops = { -+ .gma_to_ggtt_pte_index = gma_to_ggtt_pte_index, -+ .gma_to_pte_index = gen8_gma_to_pte_index, -+ .gma_to_pde_index = gen8_gma_to_pde_index, -+ .gma_to_l3_pdp_index = gen8_gma_to_l3_pdp_index, -+ .gma_to_l4_pdp_index = gen8_gma_to_l4_pdp_index, -+ .gma_to_pml4_index = gen8_gma_to_pml4_index, -+}; -+ -+/* Update entry type per pse and ips bit. */ -+static void update_entry_type_for_real(struct intel_gvt_gtt_pte_ops *pte_ops, -+ struct intel_gvt_gtt_entry *entry, bool ips) -+{ -+ switch (entry->type) { -+ case GTT_TYPE_PPGTT_PDE_ENTRY: -+ case GTT_TYPE_PPGTT_PDP_ENTRY: -+ if (pte_ops->test_pse(entry)) -+ entry->type = get_pse_type(entry->type); -+ break; -+ case GTT_TYPE_PPGTT_PTE_4K_ENTRY: -+ if (ips) -+ entry->type = get_pse_type(entry->type); -+ break; -+ default: -+ GEM_BUG_ON(!gtt_type_is_entry(entry->type)); -+ } -+ -+ GEM_BUG_ON(entry->type == GTT_TYPE_INVALID); -+} -+ -+/* -+ * MM helpers. -+ */ -+static void _ppgtt_get_root_entry(struct intel_vgpu_mm *mm, -+ struct intel_gvt_gtt_entry *entry, unsigned long index, -+ bool guest) -+{ -+ struct intel_gvt_gtt_pte_ops *pte_ops = mm->vgpu->gvt->gtt.pte_ops; -+ -+ GEM_BUG_ON(mm->type != INTEL_GVT_MM_PPGTT); -+ -+ entry->type = mm->ppgtt_mm.root_entry_type; -+ pte_ops->get_entry(guest ? mm->ppgtt_mm.guest_pdps : -+ mm->ppgtt_mm.shadow_pdps, -+ entry, index, false, 0, mm->vgpu); -+ update_entry_type_for_real(pte_ops, entry, false); -+} -+ -+static inline void ppgtt_get_guest_root_entry(struct intel_vgpu_mm *mm, -+ struct intel_gvt_gtt_entry *entry, unsigned long index) -+{ -+ _ppgtt_get_root_entry(mm, entry, index, true); -+} -+ -+static inline void ppgtt_get_shadow_root_entry(struct intel_vgpu_mm *mm, -+ struct intel_gvt_gtt_entry *entry, unsigned long index) -+{ -+ _ppgtt_get_root_entry(mm, entry, index, false); -+} -+ -+static void _ppgtt_set_root_entry(struct intel_vgpu_mm *mm, -+ struct intel_gvt_gtt_entry *entry, unsigned long index, -+ bool guest) -+{ -+ struct intel_gvt_gtt_pte_ops *pte_ops = mm->vgpu->gvt->gtt.pte_ops; -+ -+ pte_ops->set_entry(guest ? mm->ppgtt_mm.guest_pdps : -+ mm->ppgtt_mm.shadow_pdps, -+ entry, index, false, 0, mm->vgpu); -+} -+ -+static inline void ppgtt_set_guest_root_entry(struct intel_vgpu_mm *mm, -+ struct intel_gvt_gtt_entry *entry, unsigned long index) -+{ -+ _ppgtt_set_root_entry(mm, entry, index, true); -+} -+ -+static inline void ppgtt_set_shadow_root_entry(struct intel_vgpu_mm *mm, -+ struct intel_gvt_gtt_entry *entry, unsigned long index) -+{ -+ _ppgtt_set_root_entry(mm, entry, index, false); -+} -+ -+static void ggtt_get_guest_entry(struct intel_vgpu_mm *mm, -+ struct intel_gvt_gtt_entry *entry, unsigned long index) -+{ -+ struct intel_gvt_gtt_pte_ops *pte_ops = mm->vgpu->gvt->gtt.pte_ops; -+ -+ GEM_BUG_ON(mm->type != INTEL_GVT_MM_GGTT); -+ -+ entry->type = GTT_TYPE_GGTT_PTE; -+ pte_ops->get_entry(mm->ggtt_mm.virtual_ggtt, entry, index, -+ false, 0, mm->vgpu); -+} -+ -+static void ggtt_set_guest_entry(struct intel_vgpu_mm *mm, -+ struct intel_gvt_gtt_entry *entry, unsigned long index) -+{ -+ struct intel_gvt_gtt_pte_ops *pte_ops = mm->vgpu->gvt->gtt.pte_ops; -+ -+ GEM_BUG_ON(mm->type != INTEL_GVT_MM_GGTT); -+ -+ pte_ops->set_entry(mm->ggtt_mm.virtual_ggtt, entry, index, -+ false, 0, mm->vgpu); -+} -+ -+static void ggtt_get_host_entry(struct intel_vgpu_mm *mm, -+ struct intel_gvt_gtt_entry *entry, unsigned long index) -+{ -+ struct intel_gvt_gtt_pte_ops *pte_ops = mm->vgpu->gvt->gtt.pte_ops; -+ -+ GEM_BUG_ON(mm->type != INTEL_GVT_MM_GGTT); -+ -+ pte_ops->get_entry(NULL, entry, index, false, 0, mm->vgpu); -+} -+ -+static void ggtt_set_host_entry(struct intel_vgpu_mm *mm, -+ struct intel_gvt_gtt_entry *entry, unsigned long index) -+{ -+ struct intel_gvt_gtt_pte_ops *pte_ops = mm->vgpu->gvt->gtt.pte_ops; -+ -+ GEM_BUG_ON(mm->type != INTEL_GVT_MM_GGTT); -+ -+ pte_ops->set_entry(NULL, entry, index, false, 0, mm->vgpu); -+} -+ -+/* -+ * PPGTT shadow page table helpers. -+ */ -+static inline int ppgtt_spt_get_entry( -+ struct intel_vgpu_ppgtt_spt *spt, -+ void *page_table, int type, -+ struct intel_gvt_gtt_entry *e, unsigned long index, -+ bool guest) -+{ -+ struct intel_gvt *gvt = spt->vgpu->gvt; -+ struct intel_gvt_gtt_pte_ops *ops = gvt->gtt.pte_ops; -+ int ret; -+ -+ e->type = get_entry_type(type); -+ -+ if (WARN(!gtt_type_is_entry(e->type), "invalid entry type\n")) -+ return -EINVAL; -+ -+ ret = ops->get_entry(page_table, e, index, guest, -+ spt->guest_page.gfn << I915_GTT_PAGE_SHIFT, -+ spt->vgpu); -+ if (ret) -+ return ret; -+ -+ update_entry_type_for_real(ops, e, guest ? -+ spt->guest_page.pde_ips : false); -+ -+ gvt_vdbg_mm("read ppgtt entry, spt type %d, entry type %d, index %lu, value %llx\n", -+ type, e->type, index, e->val64); -+ return 0; -+} -+ -+static inline int ppgtt_spt_set_entry( -+ struct intel_vgpu_ppgtt_spt *spt, -+ void *page_table, int type, -+ struct intel_gvt_gtt_entry *e, unsigned long index, -+ bool guest) -+{ -+ struct intel_gvt *gvt = spt->vgpu->gvt; -+ struct intel_gvt_gtt_pte_ops *ops = gvt->gtt.pte_ops; -+ -+ if (WARN(!gtt_type_is_entry(e->type), "invalid entry type\n")) -+ return -EINVAL; -+ -+ gvt_vdbg_mm("set ppgtt entry, spt type %d, entry type %d, index %lu, value %llx\n", -+ type, e->type, index, e->val64); -+ -+ return ops->set_entry(page_table, e, index, guest, -+ spt->guest_page.gfn << I915_GTT_PAGE_SHIFT, -+ spt->vgpu); -+} -+ -+#define ppgtt_get_guest_entry(spt, e, index) \ -+ ppgtt_spt_get_entry(spt, NULL, \ -+ spt->guest_page.type, e, index, true) -+ -+#define ppgtt_set_guest_entry(spt, e, index) \ -+ ppgtt_spt_set_entry(spt, NULL, \ -+ spt->guest_page.type, e, index, true) -+ -+#define ppgtt_get_shadow_entry(spt, e, index) \ -+ ppgtt_spt_get_entry(spt, spt->shadow_page.vaddr, \ -+ spt->shadow_page.type, e, index, false) -+ -+#define ppgtt_set_shadow_entry(spt, e, index) \ -+ ppgtt_spt_set_entry(spt, spt->shadow_page.vaddr, \ -+ spt->shadow_page.type, e, index, false) -+ -+static void *alloc_spt(gfp_t gfp_mask) -+{ -+ struct intel_vgpu_ppgtt_spt *spt; -+ -+ spt = kzalloc(sizeof(*spt), gfp_mask); -+ if (!spt) -+ return NULL; -+ -+ spt->shadow_page.page = alloc_page(gfp_mask); -+ if (!spt->shadow_page.page) { -+ kfree(spt); -+ return NULL; -+ } -+ return spt; -+} -+ -+static void free_spt(struct intel_vgpu_ppgtt_spt *spt) -+{ -+ __free_page(spt->shadow_page.page); -+ kfree(spt); -+} -+ -+static int detach_oos_page(struct intel_vgpu *vgpu, -+ struct intel_vgpu_oos_page *oos_page); -+ -+static void ppgtt_free_spt(struct intel_vgpu_ppgtt_spt *spt) -+{ -+ struct device *kdev = &spt->vgpu->gvt->dev_priv->drm.pdev->dev; -+ -+ trace_spt_free(spt->vgpu->id, spt, spt->guest_page.type); -+ -+ dma_unmap_page(kdev, spt->shadow_page.mfn << I915_GTT_PAGE_SHIFT, 4096, -+ PCI_DMA_BIDIRECTIONAL); -+ -+ radix_tree_delete(&spt->vgpu->gtt.spt_tree, spt->shadow_page.mfn); -+ -+ if (spt->guest_page.gfn) { -+ if (spt->guest_page.oos_page) -+ detach_oos_page(spt->vgpu, spt->guest_page.oos_page); -+ -+ intel_vgpu_unregister_page_track(spt->vgpu, spt->guest_page.gfn); -+ } -+ -+ list_del_init(&spt->post_shadow_list); -+ free_spt(spt); -+} -+ -+static void ppgtt_free_all_spt(struct intel_vgpu *vgpu) -+{ -+ struct intel_vgpu_ppgtt_spt *spt, *spn; -+ struct radix_tree_iter iter; -+ LIST_HEAD(all_spt); -+ void __rcu **slot; -+ -+ rcu_read_lock(); -+ radix_tree_for_each_slot(slot, &vgpu->gtt.spt_tree, &iter, 0) { -+ spt = radix_tree_deref_slot(slot); -+ list_move(&spt->post_shadow_list, &all_spt); -+ } -+ rcu_read_unlock(); -+ -+ list_for_each_entry_safe(spt, spn, &all_spt, post_shadow_list) -+ ppgtt_free_spt(spt); -+} -+ -+static int ppgtt_handle_guest_write_page_table_bytes( -+ struct intel_vgpu_ppgtt_spt *spt, -+ u64 pa, void *p_data, int bytes); -+ -+static int ppgtt_write_protection_handler( -+ struct intel_vgpu_page_track *page_track, -+ u64 gpa, void *data, int bytes) -+{ -+ struct intel_vgpu_ppgtt_spt *spt = page_track->priv_data; -+ -+ int ret; -+ -+ if (bytes != 4 && bytes != 8) -+ return -EINVAL; -+ -+ ret = ppgtt_handle_guest_write_page_table_bytes(spt, gpa, data, bytes); -+ if (ret) -+ return ret; -+ return ret; -+} -+ -+/* Find a spt by guest gfn. */ -+static struct intel_vgpu_ppgtt_spt *intel_vgpu_find_spt_by_gfn( -+ struct intel_vgpu *vgpu, unsigned long gfn) -+{ -+ struct intel_vgpu_page_track *track; -+ -+ track = intel_vgpu_find_page_track(vgpu, gfn); -+ if (track && track->handler == ppgtt_write_protection_handler) -+ return track->priv_data; -+ -+ return NULL; -+} -+ -+/* Find the spt by shadow page mfn. */ -+static inline struct intel_vgpu_ppgtt_spt *intel_vgpu_find_spt_by_mfn( -+ struct intel_vgpu *vgpu, unsigned long mfn) -+{ -+ return radix_tree_lookup(&vgpu->gtt.spt_tree, mfn); -+} -+ -+static int reclaim_one_ppgtt_mm(struct intel_gvt *gvt); -+ -+/* Allocate shadow page table without guest page. */ -+static struct intel_vgpu_ppgtt_spt *ppgtt_alloc_spt( -+ struct intel_vgpu *vgpu, enum intel_gvt_gtt_type type) -+{ -+ struct device *kdev = &vgpu->gvt->dev_priv->drm.pdev->dev; -+ struct intel_vgpu_ppgtt_spt *spt = NULL; -+ dma_addr_t daddr; -+ int ret; -+ -+retry: -+ spt = alloc_spt(GFP_KERNEL | __GFP_ZERO); -+ if (!spt) { -+ if (reclaim_one_ppgtt_mm(vgpu->gvt)) -+ goto retry; -+ -+ gvt_vgpu_err("fail to allocate ppgtt shadow page\n"); -+ return ERR_PTR(-ENOMEM); -+ } -+ -+ spt->vgpu = vgpu; -+ atomic_set(&spt->refcount, 1); -+ INIT_LIST_HEAD(&spt->post_shadow_list); -+ -+ /* -+ * Init shadow_page. -+ */ -+ spt->shadow_page.type = type; -+ daddr = dma_map_page(kdev, spt->shadow_page.page, -+ 0, 4096, PCI_DMA_BIDIRECTIONAL); -+ if (dma_mapping_error(kdev, daddr)) { -+ gvt_vgpu_err("fail to map dma addr\n"); -+ ret = -EINVAL; -+ goto err_free_spt; -+ } -+ spt->shadow_page.vaddr = page_address(spt->shadow_page.page); -+ spt->shadow_page.mfn = daddr >> I915_GTT_PAGE_SHIFT; -+ -+ ret = radix_tree_insert(&vgpu->gtt.spt_tree, spt->shadow_page.mfn, spt); -+ if (ret) -+ goto err_unmap_dma; -+ -+ return spt; -+ -+err_unmap_dma: -+ dma_unmap_page(kdev, daddr, PAGE_SIZE, PCI_DMA_BIDIRECTIONAL); -+err_free_spt: -+ free_spt(spt); -+ return ERR_PTR(ret); -+} -+ -+/* Allocate shadow page table associated with specific gfn. */ -+static struct intel_vgpu_ppgtt_spt *ppgtt_alloc_spt_gfn( -+ struct intel_vgpu *vgpu, enum intel_gvt_gtt_type type, -+ unsigned long gfn, bool guest_pde_ips) -+{ -+ struct intel_vgpu_ppgtt_spt *spt; -+ int ret; -+ -+ spt = ppgtt_alloc_spt(vgpu, type); -+ if (IS_ERR(spt)) -+ return spt; -+ -+ /* -+ * Init guest_page. -+ */ -+ ret = intel_vgpu_register_page_track(vgpu, gfn, -+ ppgtt_write_protection_handler, spt); -+ if (ret) { -+ ppgtt_free_spt(spt); -+ return ERR_PTR(ret); -+ } -+ -+ spt->guest_page.type = type; -+ spt->guest_page.gfn = gfn; -+ spt->guest_page.pde_ips = guest_pde_ips; -+ -+ trace_spt_alloc(vgpu->id, spt, type, spt->shadow_page.mfn, gfn); -+ -+ return spt; -+} -+ -+#define pt_entry_size_shift(spt) \ -+ ((spt)->vgpu->gvt->device_info.gtt_entry_size_shift) -+ -+#define pt_entries(spt) \ -+ (I915_GTT_PAGE_SIZE >> pt_entry_size_shift(spt)) -+ -+#define for_each_present_guest_entry(spt, e, i) \ -+ for (i = 0; i < pt_entries(spt); \ -+ i += spt->guest_page.pde_ips ? GTT_64K_PTE_STRIDE : 1) \ -+ if (!ppgtt_get_guest_entry(spt, e, i) && \ -+ spt->vgpu->gvt->gtt.pte_ops->test_present(e)) -+ -+#define for_each_present_shadow_entry(spt, e, i) \ -+ for (i = 0; i < pt_entries(spt); \ -+ i += spt->shadow_page.pde_ips ? GTT_64K_PTE_STRIDE : 1) \ -+ if (!ppgtt_get_shadow_entry(spt, e, i) && \ -+ spt->vgpu->gvt->gtt.pte_ops->test_present(e)) -+ -+#define for_each_shadow_entry(spt, e, i) \ -+ for (i = 0; i < pt_entries(spt); \ -+ i += (spt->shadow_page.pde_ips ? GTT_64K_PTE_STRIDE : 1)) \ -+ if (!ppgtt_get_shadow_entry(spt, e, i)) -+ -+static inline void ppgtt_get_spt(struct intel_vgpu_ppgtt_spt *spt) -+{ -+ int v = atomic_read(&spt->refcount); -+ -+ trace_spt_refcount(spt->vgpu->id, "inc", spt, v, (v + 1)); -+ atomic_inc(&spt->refcount); -+} -+ -+static inline int ppgtt_put_spt(struct intel_vgpu_ppgtt_spt *spt) -+{ -+ int v = atomic_read(&spt->refcount); -+ -+ trace_spt_refcount(spt->vgpu->id, "dec", spt, v, (v - 1)); -+ return atomic_dec_return(&spt->refcount); -+} -+ -+static int ppgtt_invalidate_spt(struct intel_vgpu_ppgtt_spt *spt); -+ -+static int ppgtt_invalidate_spt_by_shadow_entry(struct intel_vgpu *vgpu, -+ struct intel_gvt_gtt_entry *e) -+{ -+ struct intel_gvt_gtt_pte_ops *ops = vgpu->gvt->gtt.pte_ops; -+ struct intel_vgpu_ppgtt_spt *s; -+ enum intel_gvt_gtt_type cur_pt_type; -+ -+ GEM_BUG_ON(!gtt_type_is_pt(get_next_pt_type(e->type))); -+ -+ if (e->type != GTT_TYPE_PPGTT_ROOT_L3_ENTRY -+ && e->type != GTT_TYPE_PPGTT_ROOT_L4_ENTRY) { -+ cur_pt_type = get_next_pt_type(e->type); -+ -+ if (!gtt_type_is_pt(cur_pt_type) || -+ !gtt_type_is_pt(cur_pt_type + 1)) { -+ WARN(1, "Invalid page table type, cur_pt_type is: %d\n", cur_pt_type); -+ return -EINVAL; -+ } -+ -+ cur_pt_type += 1; -+ -+ if (ops->get_pfn(e) == -+ vgpu->gtt.scratch_pt[cur_pt_type].page_mfn) -+ return 0; -+ } -+ s = intel_vgpu_find_spt_by_mfn(vgpu, ops->get_pfn(e)); -+ if (!s) { -+ gvt_vgpu_err("fail to find shadow page: mfn: 0x%lx\n", -+ ops->get_pfn(e)); -+ return -ENXIO; -+ } -+ return ppgtt_invalidate_spt(s); -+} -+ -+static inline void ppgtt_invalidate_pte(struct intel_vgpu_ppgtt_spt *spt, -+ struct intel_gvt_gtt_entry *entry) -+{ -+ struct intel_vgpu *vgpu = spt->vgpu; -+ struct intel_gvt_gtt_pte_ops *ops = vgpu->gvt->gtt.pte_ops; -+ unsigned long pfn; -+ int type; -+ -+ pfn = ops->get_pfn(entry); -+ type = spt->shadow_page.type; -+ -+ /* Uninitialized spte or unshadowed spte. */ -+ if (!pfn || pfn == vgpu->gtt.scratch_pt[type].page_mfn) -+ return; -+ -+ intel_gvt_hypervisor_dma_unmap_guest_page(vgpu, pfn << PAGE_SHIFT); -+} -+ -+static int ppgtt_invalidate_spt(struct intel_vgpu_ppgtt_spt *spt) -+{ -+ struct intel_vgpu *vgpu = spt->vgpu; -+ struct intel_gvt_gtt_entry e; -+ unsigned long index; -+ int ret; -+ -+ trace_spt_change(spt->vgpu->id, "die", spt, -+ spt->guest_page.gfn, spt->shadow_page.type); -+ -+ if (ppgtt_put_spt(spt) > 0) -+ return 0; -+ -+ for_each_present_shadow_entry(spt, &e, index) { -+ switch (e.type) { -+ case GTT_TYPE_PPGTT_PTE_4K_ENTRY: -+ gvt_vdbg_mm("invalidate 4K entry\n"); -+ ppgtt_invalidate_pte(spt, &e); -+ break; -+ case GTT_TYPE_PPGTT_PTE_64K_ENTRY: -+ /* We don't setup 64K shadow entry so far. */ -+ WARN(1, "suspicious 64K gtt entry\n"); -+ continue; -+ case GTT_TYPE_PPGTT_PTE_2M_ENTRY: -+ gvt_vdbg_mm("invalidate 2M entry\n"); -+ continue; -+ case GTT_TYPE_PPGTT_PTE_1G_ENTRY: -+ WARN(1, "GVT doesn't support 1GB page\n"); -+ continue; -+ case GTT_TYPE_PPGTT_PML4_ENTRY: -+ case GTT_TYPE_PPGTT_PDP_ENTRY: -+ case GTT_TYPE_PPGTT_PDE_ENTRY: -+ gvt_vdbg_mm("invalidate PMUL4/PDP/PDE entry\n"); -+ ret = ppgtt_invalidate_spt_by_shadow_entry( -+ spt->vgpu, &e); -+ if (ret) -+ goto fail; -+ break; -+ default: -+ GEM_BUG_ON(1); -+ } -+ } -+ -+ trace_spt_change(spt->vgpu->id, "release", spt, -+ spt->guest_page.gfn, spt->shadow_page.type); -+ ppgtt_free_spt(spt); -+ return 0; -+fail: -+ gvt_vgpu_err("fail: shadow page %p shadow entry 0x%llx type %d\n", -+ spt, e.val64, e.type); -+ return ret; -+} -+ -+static bool vgpu_ips_enabled(struct intel_vgpu *vgpu) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ -+ if (INTEL_GEN(dev_priv) == 9 || INTEL_GEN(dev_priv) == 10) { -+ u32 ips = vgpu_vreg_t(vgpu, GEN8_GAMW_ECO_DEV_RW_IA) & -+ GAMW_ECO_ENABLE_64K_IPS_FIELD; -+ -+ return ips == GAMW_ECO_ENABLE_64K_IPS_FIELD; -+ } else if (INTEL_GEN(dev_priv) >= 11) { -+ /* 64K paging only controlled by IPS bit in PTE now. */ -+ return true; -+ } else -+ return false; -+} -+ -+static int ppgtt_populate_spt(struct intel_vgpu_ppgtt_spt *spt); -+ -+static struct intel_vgpu_ppgtt_spt *ppgtt_populate_spt_by_guest_entry( -+ struct intel_vgpu *vgpu, struct intel_gvt_gtt_entry *we) -+{ -+ struct intel_gvt_gtt_pte_ops *ops = vgpu->gvt->gtt.pte_ops; -+ struct intel_vgpu_ppgtt_spt *spt = NULL; -+ bool ips = false; -+ int ret; -+ -+ GEM_BUG_ON(!gtt_type_is_pt(get_next_pt_type(we->type))); -+ -+ if (we->type == GTT_TYPE_PPGTT_PDE_ENTRY) -+ ips = vgpu_ips_enabled(vgpu) && ops->test_ips(we); -+ -+ spt = intel_vgpu_find_spt_by_gfn(vgpu, ops->get_pfn(we)); -+ if (spt) { -+ ppgtt_get_spt(spt); -+ -+ if (ips != spt->guest_page.pde_ips) { -+ spt->guest_page.pde_ips = ips; -+ -+ gvt_dbg_mm("reshadow PDE since ips changed\n"); -+ clear_page(spt->shadow_page.vaddr); -+ ret = ppgtt_populate_spt(spt); -+ if (ret) { -+ ppgtt_put_spt(spt); -+ goto err; -+ } -+ } -+ } else { -+ int type = get_next_pt_type(we->type); -+ -+ if (!gtt_type_is_pt(type)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ spt = ppgtt_alloc_spt_gfn(vgpu, type, ops->get_pfn(we), ips); -+ if (IS_ERR(spt)) { -+ ret = PTR_ERR(spt); -+ goto err; -+ } -+ -+ ret = intel_vgpu_enable_page_track(vgpu, spt->guest_page.gfn); -+ if (ret) -+ goto err_free_spt; -+ -+ ret = ppgtt_populate_spt(spt); -+ if (ret) -+ goto err_free_spt; -+ -+ trace_spt_change(vgpu->id, "new", spt, spt->guest_page.gfn, -+ spt->shadow_page.type); -+ } -+ return spt; -+ -+err_free_spt: -+ ppgtt_free_spt(spt); -+ spt = NULL; -+err: -+ gvt_vgpu_err("fail: shadow page %p guest entry 0x%llx type %d\n", -+ spt, we->val64, we->type); -+ return ERR_PTR(ret); -+} -+ -+static inline void ppgtt_generate_shadow_entry(struct intel_gvt_gtt_entry *se, -+ struct intel_vgpu_ppgtt_spt *s, struct intel_gvt_gtt_entry *ge) -+{ -+ struct intel_gvt_gtt_pte_ops *ops = s->vgpu->gvt->gtt.pte_ops; -+ -+ se->type = ge->type; -+ se->val64 = ge->val64; -+ -+ /* Because we always split 64KB pages, so clear IPS in shadow PDE. */ -+ if (se->type == GTT_TYPE_PPGTT_PDE_ENTRY) -+ ops->clear_ips(se); -+ -+ ops->set_pfn(se, s->shadow_page.mfn); -+} -+ -+/** -+ * Check if can do 2M page -+ * @vgpu: target vgpu -+ * @entry: target pfn's gtt entry -+ * -+ * Return 1 if 2MB huge gtt shadowing is possilbe, 0 if miscondition, -+ * negtive if found err. -+ */ -+static int is_2MB_gtt_possible(struct intel_vgpu *vgpu, -+ struct intel_gvt_gtt_entry *entry) -+{ -+ struct intel_gvt_gtt_pte_ops *ops = vgpu->gvt->gtt.pte_ops; -+ unsigned long pfn; -+ -+ if (!HAS_PAGE_SIZES(vgpu->gvt->dev_priv, I915_GTT_PAGE_SIZE_2M)) -+ return 0; -+ -+ pfn = intel_gvt_hypervisor_gfn_to_mfn(vgpu, ops->get_pfn(entry)); -+ if (pfn == INTEL_GVT_INVALID_ADDR) -+ return -EINVAL; -+ -+ return PageTransHuge(pfn_to_page(pfn)); -+} -+ -+static int split_2MB_gtt_entry(struct intel_vgpu *vgpu, -+ struct intel_vgpu_ppgtt_spt *spt, unsigned long index, -+ struct intel_gvt_gtt_entry *se) -+{ -+ struct intel_gvt_gtt_pte_ops *ops = vgpu->gvt->gtt.pte_ops; -+ struct intel_vgpu_ppgtt_spt *sub_spt; -+ struct intel_gvt_gtt_entry sub_se; -+ unsigned long start_gfn; -+ dma_addr_t dma_addr; -+ unsigned long sub_index; -+ int ret; -+ -+ gvt_dbg_mm("Split 2M gtt entry, index %lu\n", index); -+ -+ start_gfn = ops->get_pfn(se); -+ -+ sub_spt = ppgtt_alloc_spt(vgpu, GTT_TYPE_PPGTT_PTE_PT); -+ if (IS_ERR(sub_spt)) -+ return PTR_ERR(sub_spt); -+ -+ for_each_shadow_entry(sub_spt, &sub_se, sub_index) { -+ ret = intel_gvt_hypervisor_dma_map_guest_page(vgpu, -+ start_gfn + sub_index, PAGE_SIZE, &dma_addr); -+ if (ret) { -+ ppgtt_invalidate_spt(spt); -+ return ret; -+ } -+ sub_se.val64 = se->val64; -+ -+ /* Copy the PAT field from PDE. */ -+ sub_se.val64 &= ~_PAGE_PAT; -+ sub_se.val64 |= (se->val64 & _PAGE_PAT_LARGE) >> 5; -+ -+ ops->set_pfn(&sub_se, dma_addr >> PAGE_SHIFT); -+ ppgtt_set_shadow_entry(sub_spt, &sub_se, sub_index); -+ } -+ -+ /* Clear dirty field. */ -+ se->val64 &= ~_PAGE_DIRTY; -+ -+ ops->clear_pse(se); -+ ops->clear_ips(se); -+ ops->set_pfn(se, sub_spt->shadow_page.mfn); -+ ppgtt_set_shadow_entry(spt, se, index); -+ return 0; -+} -+ -+static int split_64KB_gtt_entry(struct intel_vgpu *vgpu, -+ struct intel_vgpu_ppgtt_spt *spt, unsigned long index, -+ struct intel_gvt_gtt_entry *se) -+{ -+ struct intel_gvt_gtt_pte_ops *ops = vgpu->gvt->gtt.pte_ops; -+ struct intel_gvt_gtt_entry entry = *se; -+ unsigned long start_gfn; -+ dma_addr_t dma_addr; -+ int i, ret; -+ -+ gvt_vdbg_mm("Split 64K gtt entry, index %lu\n", index); -+ -+ GEM_BUG_ON(index % GTT_64K_PTE_STRIDE); -+ -+ start_gfn = ops->get_pfn(se); -+ -+ entry.type = GTT_TYPE_PPGTT_PTE_4K_ENTRY; -+ ops->set_64k_splited(&entry); -+ -+ for (i = 0; i < GTT_64K_PTE_STRIDE; i++) { -+ ret = intel_gvt_hypervisor_dma_map_guest_page(vgpu, -+ start_gfn + i, PAGE_SIZE, &dma_addr); -+ if (ret) -+ return ret; -+ -+ ops->set_pfn(&entry, dma_addr >> PAGE_SHIFT); -+ ppgtt_set_shadow_entry(spt, &entry, index + i); -+ } -+ return 0; -+} -+ -+static int ppgtt_populate_shadow_entry(struct intel_vgpu *vgpu, -+ struct intel_vgpu_ppgtt_spt *spt, unsigned long index, -+ struct intel_gvt_gtt_entry *ge) -+{ -+ struct intel_gvt_gtt_pte_ops *pte_ops = vgpu->gvt->gtt.pte_ops; -+ struct intel_gvt_gtt_entry se = *ge; -+ unsigned long gfn, page_size = PAGE_SIZE; -+ dma_addr_t dma_addr; -+ int ret; -+ -+ if (!pte_ops->test_present(ge)) -+ return 0; -+ -+ gfn = pte_ops->get_pfn(ge); -+ -+ switch (ge->type) { -+ case GTT_TYPE_PPGTT_PTE_4K_ENTRY: -+ gvt_vdbg_mm("shadow 4K gtt entry\n"); -+ break; -+ case GTT_TYPE_PPGTT_PTE_64K_ENTRY: -+ gvt_vdbg_mm("shadow 64K gtt entry\n"); -+ /* -+ * The layout of 64K page is special, the page size is -+ * controlled by uper PDE. To be simple, we always split -+ * 64K page to smaller 4K pages in shadow PT. -+ */ -+ return split_64KB_gtt_entry(vgpu, spt, index, &se); -+ case GTT_TYPE_PPGTT_PTE_2M_ENTRY: -+ gvt_vdbg_mm("shadow 2M gtt entry\n"); -+ ret = is_2MB_gtt_possible(vgpu, ge); -+ if (ret == 0) -+ return split_2MB_gtt_entry(vgpu, spt, index, &se); -+ else if (ret < 0) -+ return ret; -+ page_size = I915_GTT_PAGE_SIZE_2M; -+ break; -+ case GTT_TYPE_PPGTT_PTE_1G_ENTRY: -+ gvt_vgpu_err("GVT doesn't support 1GB entry\n"); -+ return -EINVAL; -+ default: -+ GEM_BUG_ON(1); -+ }; -+ -+ /* direct shadow */ -+ ret = intel_gvt_hypervisor_dma_map_guest_page(vgpu, gfn, page_size, -+ &dma_addr); -+ if (ret) -+ return -ENXIO; -+ -+ pte_ops->set_pfn(&se, dma_addr >> PAGE_SHIFT); -+ ppgtt_set_shadow_entry(spt, &se, index); -+ return 0; -+} -+ -+static int ppgtt_populate_spt(struct intel_vgpu_ppgtt_spt *spt) -+{ -+ struct intel_vgpu *vgpu = spt->vgpu; -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct intel_gvt_gtt_pte_ops *ops = gvt->gtt.pte_ops; -+ struct intel_vgpu_ppgtt_spt *s; -+ struct intel_gvt_gtt_entry se, ge; -+ unsigned long gfn, i; -+ int ret; -+ -+ trace_spt_change(spt->vgpu->id, "born", spt, -+ spt->guest_page.gfn, spt->shadow_page.type); -+ -+ for_each_present_guest_entry(spt, &ge, i) { -+ if (gtt_type_is_pt(get_next_pt_type(ge.type))) { -+ s = ppgtt_populate_spt_by_guest_entry(vgpu, &ge); -+ if (IS_ERR(s)) { -+ ret = PTR_ERR(s); -+ goto fail; -+ } -+ ppgtt_get_shadow_entry(spt, &se, i); -+ ppgtt_generate_shadow_entry(&se, s, &ge); -+ ppgtt_set_shadow_entry(spt, &se, i); -+ } else { -+ gfn = ops->get_pfn(&ge); -+ if (!intel_gvt_hypervisor_is_valid_gfn(vgpu, gfn)) { -+ ops->set_pfn(&se, gvt->gtt.scratch_mfn); -+ ppgtt_set_shadow_entry(spt, &se, i); -+ continue; -+ } -+ -+ ret = ppgtt_populate_shadow_entry(vgpu, spt, i, &ge); -+ if (ret) -+ goto fail; -+ } -+ } -+ return 0; -+fail: -+ gvt_vgpu_err("fail: shadow page %p guest entry 0x%llx type %d\n", -+ spt, ge.val64, ge.type); -+ return ret; -+} -+ -+static int ppgtt_handle_guest_entry_removal(struct intel_vgpu_ppgtt_spt *spt, -+ struct intel_gvt_gtt_entry *se, unsigned long index) -+{ -+ struct intel_vgpu *vgpu = spt->vgpu; -+ struct intel_gvt_gtt_pte_ops *ops = vgpu->gvt->gtt.pte_ops; -+ int ret; -+ -+ trace_spt_guest_change(spt->vgpu->id, "remove", spt, -+ spt->shadow_page.type, se->val64, index); -+ -+ gvt_vdbg_mm("destroy old shadow entry, type %d, index %lu, value %llx\n", -+ se->type, index, se->val64); -+ -+ if (!ops->test_present(se)) -+ return 0; -+ -+ if (ops->get_pfn(se) == -+ vgpu->gtt.scratch_pt[spt->shadow_page.type].page_mfn) -+ return 0; -+ -+ if (gtt_type_is_pt(get_next_pt_type(se->type))) { -+ struct intel_vgpu_ppgtt_spt *s = -+ intel_vgpu_find_spt_by_mfn(vgpu, ops->get_pfn(se)); -+ if (!s) { -+ gvt_vgpu_err("fail to find guest page\n"); -+ ret = -ENXIO; -+ goto fail; -+ } -+ ret = ppgtt_invalidate_spt(s); -+ if (ret) -+ goto fail; -+ } else { -+ /* We don't setup 64K shadow entry so far. */ -+ WARN(se->type == GTT_TYPE_PPGTT_PTE_64K_ENTRY, -+ "suspicious 64K entry\n"); -+ ppgtt_invalidate_pte(spt, se); -+ } -+ -+ return 0; -+fail: -+ gvt_vgpu_err("fail: shadow page %p guest entry 0x%llx type %d\n", -+ spt, se->val64, se->type); -+ return ret; -+} -+ -+static int ppgtt_handle_guest_entry_add(struct intel_vgpu_ppgtt_spt *spt, -+ struct intel_gvt_gtt_entry *we, unsigned long index) -+{ -+ struct intel_vgpu *vgpu = spt->vgpu; -+ struct intel_gvt_gtt_entry m; -+ struct intel_vgpu_ppgtt_spt *s; -+ int ret; -+ -+ trace_spt_guest_change(spt->vgpu->id, "add", spt, spt->shadow_page.type, -+ we->val64, index); -+ -+ gvt_vdbg_mm("add shadow entry: type %d, index %lu, value %llx\n", -+ we->type, index, we->val64); -+ -+ if (gtt_type_is_pt(get_next_pt_type(we->type))) { -+ s = ppgtt_populate_spt_by_guest_entry(vgpu, we); -+ if (IS_ERR(s)) { -+ ret = PTR_ERR(s); -+ goto fail; -+ } -+ ppgtt_get_shadow_entry(spt, &m, index); -+ ppgtt_generate_shadow_entry(&m, s, we); -+ ppgtt_set_shadow_entry(spt, &m, index); -+ } else { -+ ret = ppgtt_populate_shadow_entry(vgpu, spt, index, we); -+ if (ret) -+ goto fail; -+ } -+ return 0; -+fail: -+ gvt_vgpu_err("fail: spt %p guest entry 0x%llx type %d\n", -+ spt, we->val64, we->type); -+ return ret; -+} -+ -+static int sync_oos_page(struct intel_vgpu *vgpu, -+ struct intel_vgpu_oos_page *oos_page) -+{ -+ const struct intel_gvt_device_info *info = &vgpu->gvt->device_info; -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct intel_gvt_gtt_pte_ops *ops = gvt->gtt.pte_ops; -+ struct intel_vgpu_ppgtt_spt *spt = oos_page->spt; -+ struct intel_gvt_gtt_entry old, new; -+ int index; -+ int ret; -+ -+ trace_oos_change(vgpu->id, "sync", oos_page->id, -+ spt, spt->guest_page.type); -+ -+ old.type = new.type = get_entry_type(spt->guest_page.type); -+ old.val64 = new.val64 = 0; -+ -+ for (index = 0; index < (I915_GTT_PAGE_SIZE >> -+ info->gtt_entry_size_shift); index++) { -+ ops->get_entry(oos_page->mem, &old, index, false, 0, vgpu); -+ ops->get_entry(NULL, &new, index, true, -+ spt->guest_page.gfn << PAGE_SHIFT, vgpu); -+ -+ if (old.val64 == new.val64 -+ && !test_and_clear_bit(index, spt->post_shadow_bitmap)) -+ continue; -+ -+ trace_oos_sync(vgpu->id, oos_page->id, -+ spt, spt->guest_page.type, -+ new.val64, index); -+ -+ ret = ppgtt_populate_shadow_entry(vgpu, spt, index, &new); -+ if (ret) -+ return ret; -+ -+ ops->set_entry(oos_page->mem, &new, index, false, 0, vgpu); -+ } -+ -+ spt->guest_page.write_cnt = 0; -+ list_del_init(&spt->post_shadow_list); -+ return 0; -+} -+ -+static int detach_oos_page(struct intel_vgpu *vgpu, -+ struct intel_vgpu_oos_page *oos_page) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct intel_vgpu_ppgtt_spt *spt = oos_page->spt; -+ -+ trace_oos_change(vgpu->id, "detach", oos_page->id, -+ spt, spt->guest_page.type); -+ -+ spt->guest_page.write_cnt = 0; -+ spt->guest_page.oos_page = NULL; -+ oos_page->spt = NULL; -+ -+ list_del_init(&oos_page->vm_list); -+ list_move_tail(&oos_page->list, &gvt->gtt.oos_page_free_list_head); -+ -+ return 0; -+} -+ -+static int attach_oos_page(struct intel_vgpu_oos_page *oos_page, -+ struct intel_vgpu_ppgtt_spt *spt) -+{ -+ struct intel_gvt *gvt = spt->vgpu->gvt; -+ int ret; -+ -+ ret = intel_gvt_hypervisor_read_gpa(spt->vgpu, -+ spt->guest_page.gfn << I915_GTT_PAGE_SHIFT, -+ oos_page->mem, I915_GTT_PAGE_SIZE); -+ if (ret) -+ return ret; -+ -+ oos_page->spt = spt; -+ spt->guest_page.oos_page = oos_page; -+ -+ list_move_tail(&oos_page->list, &gvt->gtt.oos_page_use_list_head); -+ -+ trace_oos_change(spt->vgpu->id, "attach", oos_page->id, -+ spt, spt->guest_page.type); -+ return 0; -+} -+ -+static int ppgtt_set_guest_page_sync(struct intel_vgpu_ppgtt_spt *spt) -+{ -+ struct intel_vgpu_oos_page *oos_page = spt->guest_page.oos_page; -+ int ret; -+ -+ ret = intel_vgpu_enable_page_track(spt->vgpu, spt->guest_page.gfn); -+ if (ret) -+ return ret; -+ -+ trace_oos_change(spt->vgpu->id, "set page sync", oos_page->id, -+ spt, spt->guest_page.type); -+ -+ list_del_init(&oos_page->vm_list); -+ return sync_oos_page(spt->vgpu, oos_page); -+} -+ -+static int ppgtt_allocate_oos_page(struct intel_vgpu_ppgtt_spt *spt) -+{ -+ struct intel_gvt *gvt = spt->vgpu->gvt; -+ struct intel_gvt_gtt *gtt = &gvt->gtt; -+ struct intel_vgpu_oos_page *oos_page = spt->guest_page.oos_page; -+ int ret; -+ -+ WARN(oos_page, "shadow PPGTT page has already has a oos page\n"); -+ -+ if (list_empty(>t->oos_page_free_list_head)) { -+ oos_page = container_of(gtt->oos_page_use_list_head.next, -+ struct intel_vgpu_oos_page, list); -+ ret = ppgtt_set_guest_page_sync(oos_page->spt); -+ if (ret) -+ return ret; -+ ret = detach_oos_page(spt->vgpu, oos_page); -+ if (ret) -+ return ret; -+ } else -+ oos_page = container_of(gtt->oos_page_free_list_head.next, -+ struct intel_vgpu_oos_page, list); -+ return attach_oos_page(oos_page, spt); -+} -+ -+static int ppgtt_set_guest_page_oos(struct intel_vgpu_ppgtt_spt *spt) -+{ -+ struct intel_vgpu_oos_page *oos_page = spt->guest_page.oos_page; -+ -+ if (WARN(!oos_page, "shadow PPGTT page should have a oos page\n")) -+ return -EINVAL; -+ -+ trace_oos_change(spt->vgpu->id, "set page out of sync", oos_page->id, -+ spt, spt->guest_page.type); -+ -+ list_add_tail(&oos_page->vm_list, &spt->vgpu->gtt.oos_page_list_head); -+ return intel_vgpu_disable_page_track(spt->vgpu, spt->guest_page.gfn); -+} -+ -+/** -+ * intel_vgpu_sync_oos_pages - sync all the out-of-synced shadow for vGPU -+ * @vgpu: a vGPU -+ * -+ * This function is called before submitting a guest workload to host, -+ * to sync all the out-of-synced shadow for vGPU -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_vgpu_sync_oos_pages(struct intel_vgpu *vgpu) -+{ -+ struct list_head *pos, *n; -+ struct intel_vgpu_oos_page *oos_page; -+ int ret; -+ -+ if (!enable_out_of_sync) -+ return 0; -+ -+ list_for_each_safe(pos, n, &vgpu->gtt.oos_page_list_head) { -+ oos_page = container_of(pos, -+ struct intel_vgpu_oos_page, vm_list); -+ ret = ppgtt_set_guest_page_sync(oos_page->spt); -+ if (ret) -+ return ret; -+ } -+ return 0; -+} -+ -+/* -+ * The heart of PPGTT shadow page table. -+ */ -+static int ppgtt_handle_guest_write_page_table( -+ struct intel_vgpu_ppgtt_spt *spt, -+ struct intel_gvt_gtt_entry *we, unsigned long index) -+{ -+ struct intel_vgpu *vgpu = spt->vgpu; -+ int type = spt->shadow_page.type; -+ struct intel_gvt_gtt_pte_ops *ops = vgpu->gvt->gtt.pte_ops; -+ struct intel_gvt_gtt_entry old_se; -+ int new_present; -+ int i, ret; -+ -+ new_present = ops->test_present(we); -+ -+ /* -+ * Adding the new entry first and then removing the old one, that can -+ * guarantee the ppgtt table is validated during the window between -+ * adding and removal. -+ */ -+ ppgtt_get_shadow_entry(spt, &old_se, index); -+ -+ if (new_present) { -+ ret = ppgtt_handle_guest_entry_add(spt, we, index); -+ if (ret) -+ goto fail; -+ } -+ -+ ret = ppgtt_handle_guest_entry_removal(spt, &old_se, index); -+ if (ret) -+ goto fail; -+ -+ if (!new_present) { -+ /* For 64KB splited entries, we need clear them all. */ -+ if (ops->test_64k_splited(&old_se) && -+ !(index % GTT_64K_PTE_STRIDE)) { -+ gvt_vdbg_mm("remove splited 64K shadow entries\n"); -+ for (i = 0; i < GTT_64K_PTE_STRIDE; i++) { -+ ops->clear_64k_splited(&old_se); -+ ops->set_pfn(&old_se, -+ vgpu->gtt.scratch_pt[type].page_mfn); -+ ppgtt_set_shadow_entry(spt, &old_se, index + i); -+ } -+ } else if (old_se.type == GTT_TYPE_PPGTT_PTE_2M_ENTRY || -+ old_se.type == GTT_TYPE_PPGTT_PTE_1G_ENTRY) { -+ ops->clear_pse(&old_se); -+ ops->set_pfn(&old_se, -+ vgpu->gtt.scratch_pt[type].page_mfn); -+ ppgtt_set_shadow_entry(spt, &old_se, index); -+ } else { -+ ops->set_pfn(&old_se, -+ vgpu->gtt.scratch_pt[type].page_mfn); -+ ppgtt_set_shadow_entry(spt, &old_se, index); -+ } -+ } -+ -+ return 0; -+fail: -+ gvt_vgpu_err("fail: shadow page %p guest entry 0x%llx type %d.\n", -+ spt, we->val64, we->type); -+ return ret; -+} -+ -+ -+ -+static inline bool can_do_out_of_sync(struct intel_vgpu_ppgtt_spt *spt) -+{ -+ return enable_out_of_sync -+ && gtt_type_is_pte_pt(spt->guest_page.type) -+ && spt->guest_page.write_cnt >= 2; -+} -+ -+static void ppgtt_set_post_shadow(struct intel_vgpu_ppgtt_spt *spt, -+ unsigned long index) -+{ -+ set_bit(index, spt->post_shadow_bitmap); -+ if (!list_empty(&spt->post_shadow_list)) -+ return; -+ -+ list_add_tail(&spt->post_shadow_list, -+ &spt->vgpu->gtt.post_shadow_list_head); -+} -+ -+/** -+ * intel_vgpu_flush_post_shadow - flush the post shadow transactions -+ * @vgpu: a vGPU -+ * -+ * This function is called before submitting a guest workload to host, -+ * to flush all the post shadows for a vGPU. -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_vgpu_flush_post_shadow(struct intel_vgpu *vgpu) -+{ -+ struct list_head *pos, *n; -+ struct intel_vgpu_ppgtt_spt *spt; -+ struct intel_gvt_gtt_entry ge; -+ unsigned long index; -+ int ret; -+ -+ list_for_each_safe(pos, n, &vgpu->gtt.post_shadow_list_head) { -+ spt = container_of(pos, struct intel_vgpu_ppgtt_spt, -+ post_shadow_list); -+ -+ for_each_set_bit(index, spt->post_shadow_bitmap, -+ GTT_ENTRY_NUM_IN_ONE_PAGE) { -+ ppgtt_get_guest_entry(spt, &ge, index); -+ -+ ret = ppgtt_handle_guest_write_page_table(spt, -+ &ge, index); -+ if (ret) -+ return ret; -+ clear_bit(index, spt->post_shadow_bitmap); -+ } -+ list_del_init(&spt->post_shadow_list); -+ } -+ return 0; -+} -+ -+static int ppgtt_handle_guest_write_page_table_bytes( -+ struct intel_vgpu_ppgtt_spt *spt, -+ u64 pa, void *p_data, int bytes) -+{ -+ struct intel_vgpu *vgpu = spt->vgpu; -+ struct intel_gvt_gtt_pte_ops *ops = vgpu->gvt->gtt.pte_ops; -+ const struct intel_gvt_device_info *info = &vgpu->gvt->device_info; -+ struct intel_gvt_gtt_entry we, se; -+ unsigned long index; -+ int ret; -+ -+ index = (pa & (PAGE_SIZE - 1)) >> info->gtt_entry_size_shift; -+ -+ ppgtt_get_guest_entry(spt, &we, index); -+ -+ /* -+ * For page table which has 64K gtt entry, only PTE#0, PTE#16, -+ * PTE#32, ... PTE#496 are used. Unused PTEs update should be -+ * ignored. -+ */ -+ if (we.type == GTT_TYPE_PPGTT_PTE_64K_ENTRY && -+ (index % GTT_64K_PTE_STRIDE)) { -+ gvt_vdbg_mm("Ignore write to unused PTE entry, index %lu\n", -+ index); -+ return 0; -+ } -+ -+ if (bytes == info->gtt_entry_size) { -+ ret = ppgtt_handle_guest_write_page_table(spt, &we, index); -+ if (ret) -+ return ret; -+ } else { -+ if (!test_bit(index, spt->post_shadow_bitmap)) { -+ int type = spt->shadow_page.type; -+ -+ ppgtt_get_shadow_entry(spt, &se, index); -+ ret = ppgtt_handle_guest_entry_removal(spt, &se, index); -+ if (ret) -+ return ret; -+ ops->set_pfn(&se, vgpu->gtt.scratch_pt[type].page_mfn); -+ ppgtt_set_shadow_entry(spt, &se, index); -+ } -+ ppgtt_set_post_shadow(spt, index); -+ } -+ -+ if (!enable_out_of_sync) -+ return 0; -+ -+ spt->guest_page.write_cnt++; -+ -+ if (spt->guest_page.oos_page) -+ ops->set_entry(spt->guest_page.oos_page->mem, &we, index, -+ false, 0, vgpu); -+ -+ if (can_do_out_of_sync(spt)) { -+ if (!spt->guest_page.oos_page) -+ ppgtt_allocate_oos_page(spt); -+ -+ ret = ppgtt_set_guest_page_oos(spt); -+ if (ret < 0) -+ return ret; -+ } -+ return 0; -+} -+ -+static void invalidate_ppgtt_mm(struct intel_vgpu_mm *mm) -+{ -+ struct intel_vgpu *vgpu = mm->vgpu; -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct intel_gvt_gtt *gtt = &gvt->gtt; -+ struct intel_gvt_gtt_pte_ops *ops = gtt->pte_ops; -+ struct intel_gvt_gtt_entry se; -+ int index; -+ -+ if (!mm->ppgtt_mm.shadowed) -+ return; -+ -+ for (index = 0; index < ARRAY_SIZE(mm->ppgtt_mm.shadow_pdps); index++) { -+ ppgtt_get_shadow_root_entry(mm, &se, index); -+ -+ if (!ops->test_present(&se)) -+ continue; -+ -+ ppgtt_invalidate_spt_by_shadow_entry(vgpu, &se); -+ se.val64 = 0; -+ ppgtt_set_shadow_root_entry(mm, &se, index); -+ -+ trace_spt_guest_change(vgpu->id, "destroy root pointer", -+ NULL, se.type, se.val64, index); -+ } -+ -+ mm->ppgtt_mm.shadowed = false; -+} -+ -+ -+static int shadow_ppgtt_mm(struct intel_vgpu_mm *mm) -+{ -+ struct intel_vgpu *vgpu = mm->vgpu; -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct intel_gvt_gtt *gtt = &gvt->gtt; -+ struct intel_gvt_gtt_pte_ops *ops = gtt->pte_ops; -+ struct intel_vgpu_ppgtt_spt *spt; -+ struct intel_gvt_gtt_entry ge, se; -+ int index, ret; -+ -+ if (mm->ppgtt_mm.shadowed) -+ return 0; -+ -+ mm->ppgtt_mm.shadowed = true; -+ -+ for (index = 0; index < ARRAY_SIZE(mm->ppgtt_mm.guest_pdps); index++) { -+ ppgtt_get_guest_root_entry(mm, &ge, index); -+ -+ if (!ops->test_present(&ge)) -+ continue; -+ -+ trace_spt_guest_change(vgpu->id, __func__, NULL, -+ ge.type, ge.val64, index); -+ -+ spt = ppgtt_populate_spt_by_guest_entry(vgpu, &ge); -+ if (IS_ERR(spt)) { -+ gvt_vgpu_err("fail to populate guest root pointer\n"); -+ ret = PTR_ERR(spt); -+ goto fail; -+ } -+ ppgtt_generate_shadow_entry(&se, spt, &ge); -+ ppgtt_set_shadow_root_entry(mm, &se, index); -+ -+ trace_spt_guest_change(vgpu->id, "populate root pointer", -+ NULL, se.type, se.val64, index); -+ } -+ -+ return 0; -+fail: -+ invalidate_ppgtt_mm(mm); -+ return ret; -+} -+ -+static struct intel_vgpu_mm *vgpu_alloc_mm(struct intel_vgpu *vgpu) -+{ -+ struct intel_vgpu_mm *mm; -+ -+ mm = kzalloc(sizeof(*mm), GFP_KERNEL); -+ if (!mm) -+ return NULL; -+ -+ mm->vgpu = vgpu; -+ kref_init(&mm->ref); -+ atomic_set(&mm->pincount, 0); -+ -+ return mm; -+} -+ -+static void vgpu_free_mm(struct intel_vgpu_mm *mm) -+{ -+ kfree(mm); -+} -+ -+/** -+ * intel_vgpu_create_ppgtt_mm - create a ppgtt mm object for a vGPU -+ * @vgpu: a vGPU -+ * @root_entry_type: ppgtt root entry type -+ * @pdps: guest pdps. -+ * -+ * This function is used to create a ppgtt mm object for a vGPU. -+ * -+ * Returns: -+ * Zero on success, negative error code in pointer if failed. -+ */ -+struct intel_vgpu_mm *intel_vgpu_create_ppgtt_mm(struct intel_vgpu *vgpu, -+ enum intel_gvt_gtt_type root_entry_type, u64 pdps[]) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct intel_vgpu_mm *mm; -+ int ret; -+ -+ mm = vgpu_alloc_mm(vgpu); -+ if (!mm) -+ return ERR_PTR(-ENOMEM); -+ -+ mm->type = INTEL_GVT_MM_PPGTT; -+ -+ GEM_BUG_ON(root_entry_type != GTT_TYPE_PPGTT_ROOT_L3_ENTRY && -+ root_entry_type != GTT_TYPE_PPGTT_ROOT_L4_ENTRY); -+ mm->ppgtt_mm.root_entry_type = root_entry_type; -+ -+ INIT_LIST_HEAD(&mm->ppgtt_mm.list); -+ INIT_LIST_HEAD(&mm->ppgtt_mm.lru_list); -+ -+ if (root_entry_type == GTT_TYPE_PPGTT_ROOT_L4_ENTRY) -+ mm->ppgtt_mm.guest_pdps[0] = pdps[0]; -+ else -+ memcpy(mm->ppgtt_mm.guest_pdps, pdps, -+ sizeof(mm->ppgtt_mm.guest_pdps)); -+ -+ ret = shadow_ppgtt_mm(mm); -+ if (ret) { -+ gvt_vgpu_err("failed to shadow ppgtt mm\n"); -+ vgpu_free_mm(mm); -+ return ERR_PTR(ret); -+ } -+ -+ list_add_tail(&mm->ppgtt_mm.list, &vgpu->gtt.ppgtt_mm_list_head); -+ -+ mutex_lock(&gvt->gtt.ppgtt_mm_lock); -+ list_add_tail(&mm->ppgtt_mm.lru_list, &gvt->gtt.ppgtt_mm_lru_list_head); -+ mutex_unlock(&gvt->gtt.ppgtt_mm_lock); -+ -+ return mm; -+} -+ -+static struct intel_vgpu_mm *intel_vgpu_create_ggtt_mm(struct intel_vgpu *vgpu) -+{ -+ struct intel_vgpu_mm *mm; -+ unsigned long nr_entries; -+ -+ mm = vgpu_alloc_mm(vgpu); -+ if (!mm) -+ return ERR_PTR(-ENOMEM); -+ -+ mm->type = INTEL_GVT_MM_GGTT; -+ -+ nr_entries = gvt_ggtt_gm_sz(vgpu->gvt) >> I915_GTT_PAGE_SHIFT; -+ mm->ggtt_mm.virtual_ggtt = -+ vzalloc(array_size(nr_entries, -+ vgpu->gvt->device_info.gtt_entry_size)); -+ if (!mm->ggtt_mm.virtual_ggtt) { -+ vgpu_free_mm(mm); -+ return ERR_PTR(-ENOMEM); -+ } -+ -+ return mm; -+} -+ -+/** -+ * _intel_vgpu_mm_release - destroy a mm object -+ * @mm_ref: a kref object -+ * -+ * This function is used to destroy a mm object for vGPU -+ * -+ */ -+void _intel_vgpu_mm_release(struct kref *mm_ref) -+{ -+ struct intel_vgpu_mm *mm = container_of(mm_ref, typeof(*mm), ref); -+ -+ if (GEM_WARN_ON(atomic_read(&mm->pincount))) -+ gvt_err("vgpu mm pin count bug detected\n"); -+ -+ if (mm->type == INTEL_GVT_MM_PPGTT) { -+ list_del(&mm->ppgtt_mm.list); -+ list_del(&mm->ppgtt_mm.lru_list); -+ invalidate_ppgtt_mm(mm); -+ } else { -+ vfree(mm->ggtt_mm.virtual_ggtt); -+ } -+ -+ vgpu_free_mm(mm); -+} -+ -+/** -+ * intel_vgpu_unpin_mm - decrease the pin count of a vGPU mm object -+ * @mm: a vGPU mm object -+ * -+ * This function is called when user doesn't want to use a vGPU mm object -+ */ -+void intel_vgpu_unpin_mm(struct intel_vgpu_mm *mm) -+{ -+ atomic_dec_if_positive(&mm->pincount); -+} -+ -+/** -+ * intel_vgpu_pin_mm - increase the pin count of a vGPU mm object -+ * @mm: target vgpu mm -+ * -+ * This function is called when user wants to use a vGPU mm object. If this -+ * mm object hasn't been shadowed yet, the shadow will be populated at this -+ * time. -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_vgpu_pin_mm(struct intel_vgpu_mm *mm) -+{ -+ int ret; -+ -+ atomic_inc(&mm->pincount); -+ -+ if (mm->type == INTEL_GVT_MM_PPGTT) { -+ ret = shadow_ppgtt_mm(mm); -+ if (ret) -+ return ret; -+ -+ mutex_lock(&mm->vgpu->gvt->gtt.ppgtt_mm_lock); -+ list_move_tail(&mm->ppgtt_mm.lru_list, -+ &mm->vgpu->gvt->gtt.ppgtt_mm_lru_list_head); -+ mutex_unlock(&mm->vgpu->gvt->gtt.ppgtt_mm_lock); -+ } -+ -+ return 0; -+} -+ -+static int reclaim_one_ppgtt_mm(struct intel_gvt *gvt) -+{ -+ struct intel_vgpu_mm *mm; -+ struct list_head *pos, *n; -+ -+ mutex_lock(&gvt->gtt.ppgtt_mm_lock); -+ -+ list_for_each_safe(pos, n, &gvt->gtt.ppgtt_mm_lru_list_head) { -+ mm = container_of(pos, struct intel_vgpu_mm, ppgtt_mm.lru_list); -+ -+ if (atomic_read(&mm->pincount)) -+ continue; -+ -+ list_del_init(&mm->ppgtt_mm.lru_list); -+ mutex_unlock(&gvt->gtt.ppgtt_mm_lock); -+ invalidate_ppgtt_mm(mm); -+ return 1; -+ } -+ mutex_unlock(&gvt->gtt.ppgtt_mm_lock); -+ return 0; -+} -+ -+/* -+ * GMA translation APIs. -+ */ -+static inline int ppgtt_get_next_level_entry(struct intel_vgpu_mm *mm, -+ struct intel_gvt_gtt_entry *e, unsigned long index, bool guest) -+{ -+ struct intel_vgpu *vgpu = mm->vgpu; -+ struct intel_gvt_gtt_pte_ops *ops = vgpu->gvt->gtt.pte_ops; -+ struct intel_vgpu_ppgtt_spt *s; -+ -+ s = intel_vgpu_find_spt_by_mfn(vgpu, ops->get_pfn(e)); -+ if (!s) -+ return -ENXIO; -+ -+ if (!guest) -+ ppgtt_get_shadow_entry(s, e, index); -+ else -+ ppgtt_get_guest_entry(s, e, index); -+ return 0; -+} -+ -+/** -+ * intel_vgpu_gma_to_gpa - translate a gma to GPA -+ * @mm: mm object. could be a PPGTT or GGTT mm object -+ * @gma: graphics memory address in this mm object -+ * -+ * This function is used to translate a graphics memory address in specific -+ * graphics memory space to guest physical address. -+ * -+ * Returns: -+ * Guest physical address on success, INTEL_GVT_INVALID_ADDR if failed. -+ */ -+unsigned long intel_vgpu_gma_to_gpa(struct intel_vgpu_mm *mm, unsigned long gma) -+{ -+ struct intel_vgpu *vgpu = mm->vgpu; -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct intel_gvt_gtt_pte_ops *pte_ops = gvt->gtt.pte_ops; -+ struct intel_gvt_gtt_gma_ops *gma_ops = gvt->gtt.gma_ops; -+ unsigned long gpa = INTEL_GVT_INVALID_ADDR; -+ unsigned long gma_index[4]; -+ struct intel_gvt_gtt_entry e; -+ int i, levels = 0; -+ int ret; -+ -+ GEM_BUG_ON(mm->type != INTEL_GVT_MM_GGTT && -+ mm->type != INTEL_GVT_MM_PPGTT); -+ -+ if (mm->type == INTEL_GVT_MM_GGTT) { -+ if (!vgpu_gmadr_is_valid(vgpu, gma)) -+ goto err; -+ -+ ggtt_get_guest_entry(mm, &e, -+ gma_ops->gma_to_ggtt_pte_index(gma)); -+ -+ gpa = (pte_ops->get_pfn(&e) << I915_GTT_PAGE_SHIFT) -+ + (gma & ~I915_GTT_PAGE_MASK); -+ -+ trace_gma_translate(vgpu->id, "ggtt", 0, 0, gma, gpa); -+ } else { -+ switch (mm->ppgtt_mm.root_entry_type) { -+ case GTT_TYPE_PPGTT_ROOT_L4_ENTRY: -+ ppgtt_get_shadow_root_entry(mm, &e, 0); -+ -+ gma_index[0] = gma_ops->gma_to_pml4_index(gma); -+ gma_index[1] = gma_ops->gma_to_l4_pdp_index(gma); -+ gma_index[2] = gma_ops->gma_to_pde_index(gma); -+ gma_index[3] = gma_ops->gma_to_pte_index(gma); -+ levels = 4; -+ break; -+ case GTT_TYPE_PPGTT_ROOT_L3_ENTRY: -+ ppgtt_get_shadow_root_entry(mm, &e, -+ gma_ops->gma_to_l3_pdp_index(gma)); -+ -+ gma_index[0] = gma_ops->gma_to_pde_index(gma); -+ gma_index[1] = gma_ops->gma_to_pte_index(gma); -+ levels = 2; -+ break; -+ default: -+ GEM_BUG_ON(1); -+ } -+ -+ /* walk the shadow page table and get gpa from guest entry */ -+ for (i = 0; i < levels; i++) { -+ ret = ppgtt_get_next_level_entry(mm, &e, gma_index[i], -+ (i == levels - 1)); -+ if (ret) -+ goto err; -+ -+ if (!pte_ops->test_present(&e)) { -+ gvt_dbg_core("GMA 0x%lx is not present\n", gma); -+ goto err; -+ } -+ } -+ -+ gpa = (pte_ops->get_pfn(&e) << I915_GTT_PAGE_SHIFT) + -+ (gma & ~I915_GTT_PAGE_MASK); -+ trace_gma_translate(vgpu->id, "ppgtt", 0, -+ mm->ppgtt_mm.root_entry_type, gma, gpa); -+ } -+ -+ return gpa; -+err: -+ gvt_vgpu_err("invalid mm type: %d gma %lx\n", mm->type, gma); -+ return INTEL_GVT_INVALID_ADDR; -+} -+ -+static int emulate_ggtt_mmio_read(struct intel_vgpu *vgpu, -+ unsigned int off, void *p_data, unsigned int bytes) -+{ -+ struct intel_vgpu_mm *ggtt_mm = vgpu->gtt.ggtt_mm; -+ const struct intel_gvt_device_info *info = &vgpu->gvt->device_info; -+ unsigned long index = off >> info->gtt_entry_size_shift; -+ struct intel_gvt_gtt_entry e; -+ -+ if (bytes != 4 && bytes != 8) -+ return -EINVAL; -+ -+ ggtt_get_guest_entry(ggtt_mm, &e, index); -+ memcpy(p_data, (void *)&e.val64 + (off & (info->gtt_entry_size - 1)), -+ bytes); -+ return 0; -+} -+ -+/** -+ * intel_vgpu_emulate_gtt_mmio_read - emulate GTT MMIO register read -+ * @vgpu: a vGPU -+ * @off: register offset -+ * @p_data: data will be returned to guest -+ * @bytes: data length -+ * -+ * This function is used to emulate the GTT MMIO register read -+ * -+ * Returns: -+ * Zero on success, error code if failed. -+ */ -+int intel_vgpu_emulate_ggtt_mmio_read(struct intel_vgpu *vgpu, unsigned int off, -+ void *p_data, unsigned int bytes) -+{ -+ const struct intel_gvt_device_info *info = &vgpu->gvt->device_info; -+ int ret; -+ -+ if (bytes != 4 && bytes != 8) -+ return -EINVAL; -+ -+ off -= info->gtt_start_offset; -+ ret = emulate_ggtt_mmio_read(vgpu, off, p_data, bytes); -+ return ret; -+} -+ -+static void ggtt_invalidate_pte(struct intel_vgpu *vgpu, -+ struct intel_gvt_gtt_entry *entry) -+{ -+ struct intel_gvt_gtt_pte_ops *pte_ops = vgpu->gvt->gtt.pte_ops; -+ unsigned long pfn; -+ -+ pfn = pte_ops->get_pfn(entry); -+ if (pfn != vgpu->gvt->gtt.scratch_mfn) -+ intel_gvt_hypervisor_dma_unmap_guest_page(vgpu, -+ pfn << PAGE_SHIFT); -+} -+ -+static int emulate_ggtt_mmio_write(struct intel_vgpu *vgpu, unsigned int off, -+ void *p_data, unsigned int bytes) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ const struct intel_gvt_device_info *info = &gvt->device_info; -+ struct intel_vgpu_mm *ggtt_mm = vgpu->gtt.ggtt_mm; -+ struct intel_gvt_gtt_pte_ops *ops = gvt->gtt.pte_ops; -+ unsigned long g_gtt_index = off >> info->gtt_entry_size_shift; -+ unsigned long gma, gfn; -+ struct intel_gvt_gtt_entry e = {.val64 = 0, .type = GTT_TYPE_GGTT_PTE}; -+ struct intel_gvt_gtt_entry m = {.val64 = 0, .type = GTT_TYPE_GGTT_PTE}; -+ dma_addr_t dma_addr; -+ int ret; -+ struct intel_gvt_partial_pte *partial_pte, *pos, *n; -+ bool partial_update = false; -+ -+ if (bytes != 4 && bytes != 8) -+ return -EINVAL; -+ -+ gma = g_gtt_index << I915_GTT_PAGE_SHIFT; -+ -+ /* the VM may configure the whole GM space when ballooning is used */ -+ if (!vgpu_gmadr_is_valid(vgpu, gma)) -+ return 0; -+ -+ e.type = GTT_TYPE_GGTT_PTE; -+ memcpy((void *)&e.val64 + (off & (info->gtt_entry_size - 1)), p_data, -+ bytes); -+ -+ /* If ggtt entry size is 8 bytes, and it's split into two 4 bytes -+ * write, save the first 4 bytes in a list and update virtual -+ * PTE. Only update shadow PTE when the second 4 bytes comes. -+ */ -+ if (bytes < info->gtt_entry_size) { -+ bool found = false; -+ -+ list_for_each_entry_safe(pos, n, -+ &ggtt_mm->ggtt_mm.partial_pte_list, list) { -+ if (g_gtt_index == pos->offset >> -+ info->gtt_entry_size_shift) { -+ if (off != pos->offset) { -+ /* the second partial part*/ -+ int last_off = pos->offset & -+ (info->gtt_entry_size - 1); -+ -+ memcpy((void *)&e.val64 + last_off, -+ (void *)&pos->data + last_off, -+ bytes); -+ -+ list_del(&pos->list); -+ kfree(pos); -+ found = true; -+ break; -+ } -+ -+ /* update of the first partial part */ -+ pos->data = e.val64; -+ ggtt_set_guest_entry(ggtt_mm, &e, g_gtt_index); -+ return 0; -+ } -+ } -+ -+ if (!found) { -+ /* the first partial part */ -+ partial_pte = kzalloc(sizeof(*partial_pte), GFP_KERNEL); -+ if (!partial_pte) -+ return -ENOMEM; -+ partial_pte->offset = off; -+ partial_pte->data = e.val64; -+ list_add_tail(&partial_pte->list, -+ &ggtt_mm->ggtt_mm.partial_pte_list); -+ partial_update = true; -+ } -+ } -+ -+ if (!partial_update && (ops->test_present(&e))) { -+ gfn = ops->get_pfn(&e); -+ m.val64 = e.val64; -+ m.type = e.type; -+ -+ /* one PTE update may be issued in multiple writes and the -+ * first write may not construct a valid gfn -+ */ -+ if (!intel_gvt_hypervisor_is_valid_gfn(vgpu, gfn)) { -+ ops->set_pfn(&m, gvt->gtt.scratch_mfn); -+ goto out; -+ } -+ -+ ret = intel_gvt_hypervisor_dma_map_guest_page(vgpu, gfn, -+ PAGE_SIZE, &dma_addr); -+ if (ret) { -+ gvt_vgpu_err("fail to populate guest ggtt entry\n"); -+ /* guest driver may read/write the entry when partial -+ * update the entry in this situation p2m will fail -+ * settting the shadow entry to point to a scratch page -+ */ -+ ops->set_pfn(&m, gvt->gtt.scratch_mfn); -+ } else -+ ops->set_pfn(&m, dma_addr >> PAGE_SHIFT); -+ } else { -+ ops->set_pfn(&m, gvt->gtt.scratch_mfn); -+ ops->clear_present(&m); -+ } -+ -+out: -+ ggtt_set_guest_entry(ggtt_mm, &e, g_gtt_index); -+ -+ ggtt_get_host_entry(ggtt_mm, &e, g_gtt_index); -+ ggtt_invalidate_pte(vgpu, &e); -+ -+ ggtt_set_host_entry(ggtt_mm, &m, g_gtt_index); -+ ggtt_invalidate(gvt->dev_priv); -+ return 0; -+} -+ -+/* -+ * intel_vgpu_emulate_ggtt_mmio_write - emulate GTT MMIO register write -+ * @vgpu: a vGPU -+ * @off: register offset -+ * @p_data: data from guest write -+ * @bytes: data length -+ * -+ * This function is used to emulate the GTT MMIO register write -+ * -+ * Returns: -+ * Zero on success, error code if failed. -+ */ -+int intel_vgpu_emulate_ggtt_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int off, void *p_data, unsigned int bytes) -+{ -+ const struct intel_gvt_device_info *info = &vgpu->gvt->device_info; -+ int ret; -+ -+ if (bytes != 4 && bytes != 8) -+ return -EINVAL; -+ -+ off -= info->gtt_start_offset; -+ ret = emulate_ggtt_mmio_write(vgpu, off, p_data, bytes); -+ return ret; -+} -+ -+static int alloc_scratch_pages(struct intel_vgpu *vgpu, -+ enum intel_gvt_gtt_type type) -+{ -+ struct intel_vgpu_gtt *gtt = &vgpu->gtt; -+ struct intel_gvt_gtt_pte_ops *ops = vgpu->gvt->gtt.pte_ops; -+ int page_entry_num = I915_GTT_PAGE_SIZE >> -+ vgpu->gvt->device_info.gtt_entry_size_shift; -+ void *scratch_pt; -+ int i; -+ struct device *dev = &vgpu->gvt->dev_priv->drm.pdev->dev; -+ dma_addr_t daddr; -+ -+ if (WARN_ON(type < GTT_TYPE_PPGTT_PTE_PT || type >= GTT_TYPE_MAX)) -+ return -EINVAL; -+ -+ scratch_pt = (void *)get_zeroed_page(GFP_KERNEL); -+ if (!scratch_pt) { -+ gvt_vgpu_err("fail to allocate scratch page\n"); -+ return -ENOMEM; -+ } -+ -+ daddr = dma_map_page(dev, virt_to_page(scratch_pt), 0, -+ 4096, PCI_DMA_BIDIRECTIONAL); -+ if (dma_mapping_error(dev, daddr)) { -+ gvt_vgpu_err("fail to dmamap scratch_pt\n"); -+ __free_page(virt_to_page(scratch_pt)); -+ return -ENOMEM; -+ } -+ gtt->scratch_pt[type].page_mfn = -+ (unsigned long)(daddr >> I915_GTT_PAGE_SHIFT); -+ gtt->scratch_pt[type].page = virt_to_page(scratch_pt); -+ gvt_dbg_mm("vgpu%d create scratch_pt: type %d mfn=0x%lx\n", -+ vgpu->id, type, gtt->scratch_pt[type].page_mfn); -+ -+ /* Build the tree by full filled the scratch pt with the entries which -+ * point to the next level scratch pt or scratch page. The -+ * scratch_pt[type] indicate the scratch pt/scratch page used by the -+ * 'type' pt. -+ * e.g. scratch_pt[GTT_TYPE_PPGTT_PDE_PT] is used by -+ * GTT_TYPE_PPGTT_PDE_PT level pt, that means this scratch_pt it self -+ * is GTT_TYPE_PPGTT_PTE_PT, and full filled by scratch page mfn. -+ */ -+ if (type > GTT_TYPE_PPGTT_PTE_PT) { -+ struct intel_gvt_gtt_entry se; -+ -+ memset(&se, 0, sizeof(struct intel_gvt_gtt_entry)); -+ se.type = get_entry_type(type - 1); -+ ops->set_pfn(&se, gtt->scratch_pt[type - 1].page_mfn); -+ -+ /* The entry parameters like present/writeable/cache type -+ * set to the same as i915's scratch page tree. -+ */ -+ se.val64 |= _PAGE_PRESENT | _PAGE_RW; -+ if (type == GTT_TYPE_PPGTT_PDE_PT) -+ se.val64 |= PPAT_CACHED; -+ -+ for (i = 0; i < page_entry_num; i++) -+ ops->set_entry(scratch_pt, &se, i, false, 0, vgpu); -+ } -+ -+ return 0; -+} -+ -+static int release_scratch_page_tree(struct intel_vgpu *vgpu) -+{ -+ int i; -+ struct device *dev = &vgpu->gvt->dev_priv->drm.pdev->dev; -+ dma_addr_t daddr; -+ -+ for (i = GTT_TYPE_PPGTT_PTE_PT; i < GTT_TYPE_MAX; i++) { -+ if (vgpu->gtt.scratch_pt[i].page != NULL) { -+ daddr = (dma_addr_t)(vgpu->gtt.scratch_pt[i].page_mfn << -+ I915_GTT_PAGE_SHIFT); -+ dma_unmap_page(dev, daddr, 4096, PCI_DMA_BIDIRECTIONAL); -+ __free_page(vgpu->gtt.scratch_pt[i].page); -+ vgpu->gtt.scratch_pt[i].page = NULL; -+ vgpu->gtt.scratch_pt[i].page_mfn = 0; -+ } -+ } -+ -+ return 0; -+} -+ -+static int create_scratch_page_tree(struct intel_vgpu *vgpu) -+{ -+ int i, ret; -+ -+ for (i = GTT_TYPE_PPGTT_PTE_PT; i < GTT_TYPE_MAX; i++) { -+ ret = alloc_scratch_pages(vgpu, i); -+ if (ret) -+ goto err; -+ } -+ -+ return 0; -+ -+err: -+ release_scratch_page_tree(vgpu); -+ return ret; -+} -+ -+/** -+ * intel_vgpu_init_gtt - initialize per-vGPU graphics memory virulization -+ * @vgpu: a vGPU -+ * -+ * This function is used to initialize per-vGPU graphics memory virtualization -+ * components. -+ * -+ * Returns: -+ * Zero on success, error code if failed. -+ */ -+int intel_vgpu_init_gtt(struct intel_vgpu *vgpu) -+{ -+ struct intel_vgpu_gtt *gtt = &vgpu->gtt; -+ -+ INIT_RADIX_TREE(>t->spt_tree, GFP_KERNEL); -+ -+ INIT_LIST_HEAD(>t->ppgtt_mm_list_head); -+ INIT_LIST_HEAD(>t->oos_page_list_head); -+ INIT_LIST_HEAD(>t->post_shadow_list_head); -+ -+ gtt->ggtt_mm = intel_vgpu_create_ggtt_mm(vgpu); -+ if (IS_ERR(gtt->ggtt_mm)) { -+ gvt_vgpu_err("fail to create mm for ggtt.\n"); -+ return PTR_ERR(gtt->ggtt_mm); -+ } -+ -+ intel_vgpu_reset_ggtt(vgpu, false); -+ -+ INIT_LIST_HEAD(>t->ggtt_mm->ggtt_mm.partial_pte_list); -+ -+ return create_scratch_page_tree(vgpu); -+} -+ -+static void intel_vgpu_destroy_all_ppgtt_mm(struct intel_vgpu *vgpu) -+{ -+ struct list_head *pos, *n; -+ struct intel_vgpu_mm *mm; -+ -+ list_for_each_safe(pos, n, &vgpu->gtt.ppgtt_mm_list_head) { -+ mm = container_of(pos, struct intel_vgpu_mm, ppgtt_mm.list); -+ intel_vgpu_destroy_mm(mm); -+ } -+ -+ if (GEM_WARN_ON(!list_empty(&vgpu->gtt.ppgtt_mm_list_head))) -+ gvt_err("vgpu ppgtt mm is not fully destroyed\n"); -+ -+ if (GEM_WARN_ON(!radix_tree_empty(&vgpu->gtt.spt_tree))) { -+ gvt_err("Why we still has spt not freed?\n"); -+ ppgtt_free_all_spt(vgpu); -+ } -+} -+ -+static void intel_vgpu_destroy_ggtt_mm(struct intel_vgpu *vgpu) -+{ -+ struct intel_gvt_partial_pte *pos, *next; -+ -+ list_for_each_entry_safe(pos, next, -+ &vgpu->gtt.ggtt_mm->ggtt_mm.partial_pte_list, -+ list) { -+ gvt_dbg_mm("partial PTE update on hold 0x%lx : 0x%llx\n", -+ pos->offset, pos->data); -+ kfree(pos); -+ } -+ intel_vgpu_destroy_mm(vgpu->gtt.ggtt_mm); -+ vgpu->gtt.ggtt_mm = NULL; -+} -+ -+/** -+ * intel_vgpu_clean_gtt - clean up per-vGPU graphics memory virulization -+ * @vgpu: a vGPU -+ * -+ * This function is used to clean up per-vGPU graphics memory virtualization -+ * components. -+ * -+ * Returns: -+ * Zero on success, error code if failed. -+ */ -+void intel_vgpu_clean_gtt(struct intel_vgpu *vgpu) -+{ -+ intel_vgpu_destroy_all_ppgtt_mm(vgpu); -+ intel_vgpu_destroy_ggtt_mm(vgpu); -+ release_scratch_page_tree(vgpu); -+} -+ -+static void clean_spt_oos(struct intel_gvt *gvt) -+{ -+ struct intel_gvt_gtt *gtt = &gvt->gtt; -+ struct list_head *pos, *n; -+ struct intel_vgpu_oos_page *oos_page; -+ -+ WARN(!list_empty(>t->oos_page_use_list_head), -+ "someone is still using oos page\n"); -+ -+ list_for_each_safe(pos, n, >t->oos_page_free_list_head) { -+ oos_page = container_of(pos, struct intel_vgpu_oos_page, list); -+ list_del(&oos_page->list); -+ free_page((unsigned long)oos_page->mem); -+ kfree(oos_page); -+ } -+} -+ -+static int setup_spt_oos(struct intel_gvt *gvt) -+{ -+ struct intel_gvt_gtt *gtt = &gvt->gtt; -+ struct intel_vgpu_oos_page *oos_page; -+ int i; -+ int ret; -+ -+ INIT_LIST_HEAD(>t->oos_page_free_list_head); -+ INIT_LIST_HEAD(>t->oos_page_use_list_head); -+ -+ for (i = 0; i < preallocated_oos_pages; i++) { -+ oos_page = kzalloc(sizeof(*oos_page), GFP_KERNEL); -+ if (!oos_page) { -+ ret = -ENOMEM; -+ goto fail; -+ } -+ oos_page->mem = (void *)__get_free_pages(GFP_KERNEL, 0); -+ if (!oos_page->mem) { -+ ret = -ENOMEM; -+ kfree(oos_page); -+ goto fail; -+ } -+ -+ INIT_LIST_HEAD(&oos_page->list); -+ INIT_LIST_HEAD(&oos_page->vm_list); -+ oos_page->id = i; -+ list_add_tail(&oos_page->list, >t->oos_page_free_list_head); -+ } -+ -+ gvt_dbg_mm("%d oos pages preallocated\n", i); -+ -+ return 0; -+fail: -+ clean_spt_oos(gvt); -+ return ret; -+} -+ -+/** -+ * intel_vgpu_find_ppgtt_mm - find a PPGTT mm object -+ * @vgpu: a vGPU -+ * @pdps: pdp root array -+ * -+ * This function is used to find a PPGTT mm object from mm object pool -+ * -+ * Returns: -+ * pointer to mm object on success, NULL if failed. -+ */ -+struct intel_vgpu_mm *intel_vgpu_find_ppgtt_mm(struct intel_vgpu *vgpu, -+ u64 pdps[]) -+{ -+ struct intel_vgpu_mm *mm; -+ struct list_head *pos; -+ -+ list_for_each(pos, &vgpu->gtt.ppgtt_mm_list_head) { -+ mm = container_of(pos, struct intel_vgpu_mm, ppgtt_mm.list); -+ -+ switch (mm->ppgtt_mm.root_entry_type) { -+ case GTT_TYPE_PPGTT_ROOT_L4_ENTRY: -+ if (pdps[0] == mm->ppgtt_mm.guest_pdps[0]) -+ return mm; -+ break; -+ case GTT_TYPE_PPGTT_ROOT_L3_ENTRY: -+ if (!memcmp(pdps, mm->ppgtt_mm.guest_pdps, -+ sizeof(mm->ppgtt_mm.guest_pdps))) -+ return mm; -+ break; -+ default: -+ GEM_BUG_ON(1); -+ } -+ } -+ return NULL; -+} -+ -+/** -+ * intel_vgpu_get_ppgtt_mm - get or create a PPGTT mm object. -+ * @vgpu: a vGPU -+ * @root_entry_type: ppgtt root entry type -+ * @pdps: guest pdps -+ * -+ * This function is used to find or create a PPGTT mm object from a guest. -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+struct intel_vgpu_mm *intel_vgpu_get_ppgtt_mm(struct intel_vgpu *vgpu, -+ enum intel_gvt_gtt_type root_entry_type, u64 pdps[]) -+{ -+ struct intel_vgpu_mm *mm; -+ -+ mm = intel_vgpu_find_ppgtt_mm(vgpu, pdps); -+ if (mm) { -+ intel_vgpu_mm_get(mm); -+ } else { -+ mm = intel_vgpu_create_ppgtt_mm(vgpu, root_entry_type, pdps); -+ if (IS_ERR(mm)) -+ gvt_vgpu_err("fail to create mm\n"); -+ } -+ return mm; -+} -+ -+/** -+ * intel_vgpu_put_ppgtt_mm - find and put a PPGTT mm object. -+ * @vgpu: a vGPU -+ * @pdps: guest pdps -+ * -+ * This function is used to find a PPGTT mm object from a guest and destroy it. -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_vgpu_put_ppgtt_mm(struct intel_vgpu *vgpu, u64 pdps[]) -+{ -+ struct intel_vgpu_mm *mm; -+ -+ mm = intel_vgpu_find_ppgtt_mm(vgpu, pdps); -+ if (!mm) { -+ gvt_vgpu_err("fail to find ppgtt instance.\n"); -+ return -EINVAL; -+ } -+ intel_vgpu_mm_put(mm); -+ return 0; -+} -+ -+/** -+ * intel_gvt_init_gtt - initialize mm components of a GVT device -+ * @gvt: GVT device -+ * -+ * This function is called at the initialization stage, to initialize -+ * the mm components of a GVT device. -+ * -+ * Returns: -+ * zero on success, negative error code if failed. -+ */ -+int intel_gvt_init_gtt(struct intel_gvt *gvt) -+{ -+ int ret; -+ void *page; -+ struct device *dev = &gvt->dev_priv->drm.pdev->dev; -+ dma_addr_t daddr; -+ -+ gvt_dbg_core("init gtt\n"); -+ -+ gvt->gtt.pte_ops = &gen8_gtt_pte_ops; -+ gvt->gtt.gma_ops = &gen8_gtt_gma_ops; -+ -+ page = (void *)get_zeroed_page(GFP_KERNEL); -+ if (!page) { -+ gvt_err("fail to allocate scratch ggtt page\n"); -+ return -ENOMEM; -+ } -+ -+ daddr = dma_map_page(dev, virt_to_page(page), 0, -+ 4096, PCI_DMA_BIDIRECTIONAL); -+ if (dma_mapping_error(dev, daddr)) { -+ gvt_err("fail to dmamap scratch ggtt page\n"); -+ __free_page(virt_to_page(page)); -+ return -ENOMEM; -+ } -+ -+ gvt->gtt.scratch_page = virt_to_page(page); -+ gvt->gtt.scratch_mfn = (unsigned long)(daddr >> I915_GTT_PAGE_SHIFT); -+ -+ if (enable_out_of_sync) { -+ ret = setup_spt_oos(gvt); -+ if (ret) { -+ gvt_err("fail to initialize SPT oos\n"); -+ dma_unmap_page(dev, daddr, 4096, PCI_DMA_BIDIRECTIONAL); -+ __free_page(gvt->gtt.scratch_page); -+ return ret; -+ } -+ } -+ INIT_LIST_HEAD(&gvt->gtt.ppgtt_mm_lru_list_head); -+ mutex_init(&gvt->gtt.ppgtt_mm_lock); -+ return 0; -+} -+ -+/** -+ * intel_gvt_clean_gtt - clean up mm components of a GVT device -+ * @gvt: GVT device -+ * -+ * This function is called at the driver unloading stage, to clean up the -+ * the mm components of a GVT device. -+ * -+ */ -+void intel_gvt_clean_gtt(struct intel_gvt *gvt) -+{ -+ struct device *dev = &gvt->dev_priv->drm.pdev->dev; -+ dma_addr_t daddr = (dma_addr_t)(gvt->gtt.scratch_mfn << -+ I915_GTT_PAGE_SHIFT); -+ -+ dma_unmap_page(dev, daddr, 4096, PCI_DMA_BIDIRECTIONAL); -+ -+ __free_page(gvt->gtt.scratch_page); -+ -+ if (enable_out_of_sync) -+ clean_spt_oos(gvt); -+} -+ -+/** -+ * intel_vgpu_invalidate_ppgtt - invalidate PPGTT instances -+ * @vgpu: a vGPU -+ * -+ * This function is called when invalidate all PPGTT instances of a vGPU. -+ * -+ */ -+void intel_vgpu_invalidate_ppgtt(struct intel_vgpu *vgpu) -+{ -+ struct list_head *pos, *n; -+ struct intel_vgpu_mm *mm; -+ -+ list_for_each_safe(pos, n, &vgpu->gtt.ppgtt_mm_list_head) { -+ mm = container_of(pos, struct intel_vgpu_mm, ppgtt_mm.list); -+ if (mm->type == INTEL_GVT_MM_PPGTT) { -+ mutex_lock(&vgpu->gvt->gtt.ppgtt_mm_lock); -+ list_del_init(&mm->ppgtt_mm.lru_list); -+ mutex_unlock(&vgpu->gvt->gtt.ppgtt_mm_lock); -+ if (mm->ppgtt_mm.shadowed) -+ invalidate_ppgtt_mm(mm); -+ } -+ } -+} -+ -+/** -+ * intel_vgpu_reset_ggtt - reset the GGTT entry -+ * @vgpu: a vGPU -+ * @invalidate_old: invalidate old entries -+ * -+ * This function is called at the vGPU create stage -+ * to reset all the GGTT entries. -+ * -+ */ -+void intel_vgpu_reset_ggtt(struct intel_vgpu *vgpu, bool invalidate_old) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ struct intel_gvt_gtt_pte_ops *pte_ops = vgpu->gvt->gtt.pte_ops; -+ struct intel_gvt_gtt_entry entry = {.type = GTT_TYPE_GGTT_PTE}; -+ struct intel_gvt_gtt_entry old_entry; -+ u32 index; -+ u32 num_entries; -+ -+ pte_ops->set_pfn(&entry, gvt->gtt.scratch_mfn); -+ pte_ops->set_present(&entry); -+ -+ index = vgpu_aperture_gmadr_base(vgpu) >> PAGE_SHIFT; -+ num_entries = vgpu_aperture_sz(vgpu) >> PAGE_SHIFT; -+ while (num_entries--) { -+ if (invalidate_old) { -+ ggtt_get_host_entry(vgpu->gtt.ggtt_mm, &old_entry, index); -+ ggtt_invalidate_pte(vgpu, &old_entry); -+ } -+ ggtt_set_host_entry(vgpu->gtt.ggtt_mm, &entry, index++); -+ } -+ -+ index = vgpu_hidden_gmadr_base(vgpu) >> PAGE_SHIFT; -+ num_entries = vgpu_hidden_sz(vgpu) >> PAGE_SHIFT; -+ while (num_entries--) { -+ if (invalidate_old) { -+ ggtt_get_host_entry(vgpu->gtt.ggtt_mm, &old_entry, index); -+ ggtt_invalidate_pte(vgpu, &old_entry); -+ } -+ ggtt_set_host_entry(vgpu->gtt.ggtt_mm, &entry, index++); -+ } -+ -+ ggtt_invalidate(dev_priv); -+} -+ -+/** -+ * intel_vgpu_reset_gtt - reset the all GTT related status -+ * @vgpu: a vGPU -+ * -+ * This function is called from vfio core to reset reset all -+ * GTT related status, including GGTT, PPGTT, scratch page. -+ * -+ */ -+void intel_vgpu_reset_gtt(struct intel_vgpu *vgpu) -+{ -+ /* Shadow pages are only created when there is no page -+ * table tracking data, so remove page tracking data after -+ * removing the shadow pages. -+ */ -+ intel_vgpu_destroy_all_ppgtt_mm(vgpu); -+ intel_vgpu_reset_ggtt(vgpu, true); -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/gtt.h b/drivers/gpu/drm/i915_legacy/gvt/gtt.h -new file mode 100644 -index 000000000000..42d0394f0de2 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/gtt.h -@@ -0,0 +1,280 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Zhi Wang -+ * Zhenyu Wang -+ * Xiao Zheng -+ * -+ * Contributors: -+ * Min He -+ * Bing Niu -+ * -+ */ -+ -+#ifndef _GVT_GTT_H_ -+#define _GVT_GTT_H_ -+ -+#define I915_GTT_PAGE_SHIFT 12 -+ -+struct intel_vgpu_mm; -+ -+#define INTEL_GVT_INVALID_ADDR (~0UL) -+ -+struct intel_gvt_gtt_entry { -+ u64 val64; -+ int type; -+}; -+ -+struct intel_gvt_gtt_pte_ops { -+ int (*get_entry)(void *pt, -+ struct intel_gvt_gtt_entry *e, -+ unsigned long index, -+ bool hypervisor_access, -+ unsigned long gpa, -+ struct intel_vgpu *vgpu); -+ int (*set_entry)(void *pt, -+ struct intel_gvt_gtt_entry *e, -+ unsigned long index, -+ bool hypervisor_access, -+ unsigned long gpa, -+ struct intel_vgpu *vgpu); -+ bool (*test_present)(struct intel_gvt_gtt_entry *e); -+ void (*clear_present)(struct intel_gvt_gtt_entry *e); -+ void (*set_present)(struct intel_gvt_gtt_entry *e); -+ bool (*test_pse)(struct intel_gvt_gtt_entry *e); -+ void (*clear_pse)(struct intel_gvt_gtt_entry *e); -+ bool (*test_ips)(struct intel_gvt_gtt_entry *e); -+ void (*clear_ips)(struct intel_gvt_gtt_entry *e); -+ bool (*test_64k_splited)(struct intel_gvt_gtt_entry *e); -+ void (*clear_64k_splited)(struct intel_gvt_gtt_entry *e); -+ void (*set_64k_splited)(struct intel_gvt_gtt_entry *e); -+ void (*set_pfn)(struct intel_gvt_gtt_entry *e, unsigned long pfn); -+ unsigned long (*get_pfn)(struct intel_gvt_gtt_entry *e); -+}; -+ -+struct intel_gvt_gtt_gma_ops { -+ unsigned long (*gma_to_ggtt_pte_index)(unsigned long gma); -+ unsigned long (*gma_to_pte_index)(unsigned long gma); -+ unsigned long (*gma_to_pde_index)(unsigned long gma); -+ unsigned long (*gma_to_l3_pdp_index)(unsigned long gma); -+ unsigned long (*gma_to_l4_pdp_index)(unsigned long gma); -+ unsigned long (*gma_to_pml4_index)(unsigned long gma); -+}; -+ -+struct intel_gvt_gtt { -+ struct intel_gvt_gtt_pte_ops *pte_ops; -+ struct intel_gvt_gtt_gma_ops *gma_ops; -+ int (*mm_alloc_page_table)(struct intel_vgpu_mm *mm); -+ void (*mm_free_page_table)(struct intel_vgpu_mm *mm); -+ struct list_head oos_page_use_list_head; -+ struct list_head oos_page_free_list_head; -+ struct mutex ppgtt_mm_lock; -+ struct list_head ppgtt_mm_lru_list_head; -+ -+ struct page *scratch_page; -+ unsigned long scratch_mfn; -+}; -+ -+enum intel_gvt_gtt_type { -+ GTT_TYPE_INVALID = 0, -+ -+ GTT_TYPE_GGTT_PTE, -+ -+ GTT_TYPE_PPGTT_PTE_4K_ENTRY, -+ GTT_TYPE_PPGTT_PTE_64K_ENTRY, -+ GTT_TYPE_PPGTT_PTE_2M_ENTRY, -+ GTT_TYPE_PPGTT_PTE_1G_ENTRY, -+ -+ GTT_TYPE_PPGTT_PTE_ENTRY, -+ -+ GTT_TYPE_PPGTT_PDE_ENTRY, -+ GTT_TYPE_PPGTT_PDP_ENTRY, -+ GTT_TYPE_PPGTT_PML4_ENTRY, -+ -+ GTT_TYPE_PPGTT_ROOT_ENTRY, -+ -+ GTT_TYPE_PPGTT_ROOT_L3_ENTRY, -+ GTT_TYPE_PPGTT_ROOT_L4_ENTRY, -+ -+ GTT_TYPE_PPGTT_ENTRY, -+ -+ GTT_TYPE_PPGTT_PTE_PT, -+ GTT_TYPE_PPGTT_PDE_PT, -+ GTT_TYPE_PPGTT_PDP_PT, -+ GTT_TYPE_PPGTT_PML4_PT, -+ -+ GTT_TYPE_MAX, -+}; -+ -+enum intel_gvt_mm_type { -+ INTEL_GVT_MM_GGTT, -+ INTEL_GVT_MM_PPGTT, -+}; -+ -+#define GVT_RING_CTX_NR_PDPS GEN8_3LVL_PDPES -+ -+struct intel_gvt_partial_pte { -+ unsigned long offset; -+ u64 data; -+ struct list_head list; -+}; -+ -+struct intel_vgpu_mm { -+ enum intel_gvt_mm_type type; -+ struct intel_vgpu *vgpu; -+ -+ struct kref ref; -+ atomic_t pincount; -+ -+ union { -+ struct { -+ enum intel_gvt_gtt_type root_entry_type; -+ /* -+ * The 4 PDPs in ring context. For 48bit addressing, -+ * only PDP0 is valid and point to PML4. For 32it -+ * addressing, all 4 are used as true PDPs. -+ */ -+ u64 guest_pdps[GVT_RING_CTX_NR_PDPS]; -+ u64 shadow_pdps[GVT_RING_CTX_NR_PDPS]; -+ bool shadowed; -+ -+ struct list_head list; -+ struct list_head lru_list; -+ } ppgtt_mm; -+ struct { -+ void *virtual_ggtt; -+ struct list_head partial_pte_list; -+ } ggtt_mm; -+ }; -+}; -+ -+struct intel_vgpu_mm *intel_vgpu_create_ppgtt_mm(struct intel_vgpu *vgpu, -+ enum intel_gvt_gtt_type root_entry_type, u64 pdps[]); -+ -+static inline void intel_vgpu_mm_get(struct intel_vgpu_mm *mm) -+{ -+ kref_get(&mm->ref); -+} -+ -+void _intel_vgpu_mm_release(struct kref *mm_ref); -+ -+static inline void intel_vgpu_mm_put(struct intel_vgpu_mm *mm) -+{ -+ kref_put(&mm->ref, _intel_vgpu_mm_release); -+} -+ -+static inline void intel_vgpu_destroy_mm(struct intel_vgpu_mm *mm) -+{ -+ intel_vgpu_mm_put(mm); -+} -+ -+struct intel_vgpu_guest_page; -+ -+struct intel_vgpu_scratch_pt { -+ struct page *page; -+ unsigned long page_mfn; -+}; -+ -+struct intel_vgpu_gtt { -+ struct intel_vgpu_mm *ggtt_mm; -+ unsigned long active_ppgtt_mm_bitmap; -+ struct list_head ppgtt_mm_list_head; -+ struct radix_tree_root spt_tree; -+ struct list_head oos_page_list_head; -+ struct list_head post_shadow_list_head; -+ struct intel_vgpu_scratch_pt scratch_pt[GTT_TYPE_MAX]; -+}; -+ -+extern int intel_vgpu_init_gtt(struct intel_vgpu *vgpu); -+extern void intel_vgpu_clean_gtt(struct intel_vgpu *vgpu); -+void intel_vgpu_reset_ggtt(struct intel_vgpu *vgpu, bool invalidate_old); -+void intel_vgpu_invalidate_ppgtt(struct intel_vgpu *vgpu); -+ -+extern int intel_gvt_init_gtt(struct intel_gvt *gvt); -+void intel_vgpu_reset_gtt(struct intel_vgpu *vgpu); -+extern void intel_gvt_clean_gtt(struct intel_gvt *gvt); -+ -+extern struct intel_vgpu_mm *intel_gvt_find_ppgtt_mm(struct intel_vgpu *vgpu, -+ int page_table_level, void *root_entry); -+ -+struct intel_vgpu_oos_page { -+ struct intel_vgpu_ppgtt_spt *spt; -+ struct list_head list; -+ struct list_head vm_list; -+ int id; -+ void *mem; -+}; -+ -+#define GTT_ENTRY_NUM_IN_ONE_PAGE 512 -+ -+/* Represent a vgpu shadow page table. */ -+struct intel_vgpu_ppgtt_spt { -+ atomic_t refcount; -+ struct intel_vgpu *vgpu; -+ -+ struct { -+ enum intel_gvt_gtt_type type; -+ bool pde_ips; /* for 64KB PTEs */ -+ void *vaddr; -+ struct page *page; -+ unsigned long mfn; -+ } shadow_page; -+ -+ struct { -+ enum intel_gvt_gtt_type type; -+ bool pde_ips; /* for 64KB PTEs */ -+ unsigned long gfn; -+ unsigned long write_cnt; -+ struct intel_vgpu_oos_page *oos_page; -+ } guest_page; -+ -+ DECLARE_BITMAP(post_shadow_bitmap, GTT_ENTRY_NUM_IN_ONE_PAGE); -+ struct list_head post_shadow_list; -+}; -+ -+int intel_vgpu_sync_oos_pages(struct intel_vgpu *vgpu); -+ -+int intel_vgpu_flush_post_shadow(struct intel_vgpu *vgpu); -+ -+int intel_vgpu_pin_mm(struct intel_vgpu_mm *mm); -+ -+void intel_vgpu_unpin_mm(struct intel_vgpu_mm *mm); -+ -+unsigned long intel_vgpu_gma_to_gpa(struct intel_vgpu_mm *mm, -+ unsigned long gma); -+ -+struct intel_vgpu_mm *intel_vgpu_find_ppgtt_mm(struct intel_vgpu *vgpu, -+ u64 pdps[]); -+ -+struct intel_vgpu_mm *intel_vgpu_get_ppgtt_mm(struct intel_vgpu *vgpu, -+ enum intel_gvt_gtt_type root_entry_type, u64 pdps[]); -+ -+int intel_vgpu_put_ppgtt_mm(struct intel_vgpu *vgpu, u64 pdps[]); -+ -+int intel_vgpu_emulate_ggtt_mmio_read(struct intel_vgpu *vgpu, -+ unsigned int off, void *p_data, unsigned int bytes); -+ -+int intel_vgpu_emulate_ggtt_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int off, void *p_data, unsigned int bytes); -+ -+#endif /* _GVT_GTT_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/gvt/gvt.c b/drivers/gpu/drm/i915_legacy/gvt/gvt.c -new file mode 100644 -index 000000000000..43f4242062dd ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/gvt.c -@@ -0,0 +1,453 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Kevin Tian -+ * Eddie Dong -+ * -+ * Contributors: -+ * Niu Bing -+ * Zhi Wang -+ * -+ */ -+ -+#include -+#include -+#include -+ -+#include "i915_drv.h" -+#include "gvt.h" -+#include -+#include -+ -+struct intel_gvt_host intel_gvt_host; -+ -+static const char * const supported_hypervisors[] = { -+ [INTEL_GVT_HYPERVISOR_XEN] = "XEN", -+ [INTEL_GVT_HYPERVISOR_KVM] = "KVM", -+}; -+ -+static struct intel_vgpu_type *intel_gvt_find_vgpu_type(struct intel_gvt *gvt, -+ const char *name) -+{ -+ int i; -+ struct intel_vgpu_type *t; -+ const char *driver_name = dev_driver_string( -+ &gvt->dev_priv->drm.pdev->dev); -+ -+ for (i = 0; i < gvt->num_types; i++) { -+ t = &gvt->types[i]; -+ if (!strncmp(t->name, name + strlen(driver_name) + 1, -+ sizeof(t->name))) -+ return t; -+ } -+ -+ return NULL; -+} -+ -+static ssize_t available_instances_show(struct kobject *kobj, -+ struct device *dev, char *buf) -+{ -+ struct intel_vgpu_type *type; -+ unsigned int num = 0; -+ void *gvt = kdev_to_i915(dev)->gvt; -+ -+ type = intel_gvt_find_vgpu_type(gvt, kobject_name(kobj)); -+ if (!type) -+ num = 0; -+ else -+ num = type->avail_instance; -+ -+ return sprintf(buf, "%u\n", num); -+} -+ -+static ssize_t device_api_show(struct kobject *kobj, struct device *dev, -+ char *buf) -+{ -+ return sprintf(buf, "%s\n", VFIO_DEVICE_API_PCI_STRING); -+} -+ -+static ssize_t description_show(struct kobject *kobj, struct device *dev, -+ char *buf) -+{ -+ struct intel_vgpu_type *type; -+ void *gvt = kdev_to_i915(dev)->gvt; -+ -+ type = intel_gvt_find_vgpu_type(gvt, kobject_name(kobj)); -+ if (!type) -+ return 0; -+ -+ return sprintf(buf, "low_gm_size: %dMB\nhigh_gm_size: %dMB\n" -+ "fence: %d\nresolution: %s\n" -+ "weight: %d\n", -+ BYTES_TO_MB(type->low_gm_size), -+ BYTES_TO_MB(type->high_gm_size), -+ type->fence, vgpu_edid_str(type->resolution), -+ type->weight); -+} -+ -+static MDEV_TYPE_ATTR_RO(available_instances); -+static MDEV_TYPE_ATTR_RO(device_api); -+static MDEV_TYPE_ATTR_RO(description); -+ -+static struct attribute *gvt_type_attrs[] = { -+ &mdev_type_attr_available_instances.attr, -+ &mdev_type_attr_device_api.attr, -+ &mdev_type_attr_description.attr, -+ NULL, -+}; -+ -+static struct attribute_group *gvt_vgpu_type_groups[] = { -+ [0 ... NR_MAX_INTEL_VGPU_TYPES - 1] = NULL, -+}; -+ -+static bool intel_get_gvt_attrs(struct attribute ***type_attrs, -+ struct attribute_group ***intel_vgpu_type_groups) -+{ -+ *type_attrs = gvt_type_attrs; -+ *intel_vgpu_type_groups = gvt_vgpu_type_groups; -+ return true; -+} -+ -+static bool intel_gvt_init_vgpu_type_groups(struct intel_gvt *gvt) -+{ -+ int i, j; -+ struct intel_vgpu_type *type; -+ struct attribute_group *group; -+ -+ for (i = 0; i < gvt->num_types; i++) { -+ type = &gvt->types[i]; -+ -+ group = kzalloc(sizeof(struct attribute_group), GFP_KERNEL); -+ if (WARN_ON(!group)) -+ goto unwind; -+ -+ group->name = type->name; -+ group->attrs = gvt_type_attrs; -+ gvt_vgpu_type_groups[i] = group; -+ } -+ -+ return true; -+ -+unwind: -+ for (j = 0; j < i; j++) { -+ group = gvt_vgpu_type_groups[j]; -+ kfree(group); -+ } -+ -+ return false; -+} -+ -+static void intel_gvt_cleanup_vgpu_type_groups(struct intel_gvt *gvt) -+{ -+ int i; -+ struct attribute_group *group; -+ -+ for (i = 0; i < gvt->num_types; i++) { -+ group = gvt_vgpu_type_groups[i]; -+ gvt_vgpu_type_groups[i] = NULL; -+ kfree(group); -+ } -+} -+ -+static const struct intel_gvt_ops intel_gvt_ops = { -+ .emulate_cfg_read = intel_vgpu_emulate_cfg_read, -+ .emulate_cfg_write = intel_vgpu_emulate_cfg_write, -+ .emulate_mmio_read = intel_vgpu_emulate_mmio_read, -+ .emulate_mmio_write = intel_vgpu_emulate_mmio_write, -+ .vgpu_create = intel_gvt_create_vgpu, -+ .vgpu_destroy = intel_gvt_destroy_vgpu, -+ .vgpu_release = intel_gvt_release_vgpu, -+ .vgpu_reset = intel_gvt_reset_vgpu, -+ .vgpu_activate = intel_gvt_activate_vgpu, -+ .vgpu_deactivate = intel_gvt_deactivate_vgpu, -+ .gvt_find_vgpu_type = intel_gvt_find_vgpu_type, -+ .get_gvt_attrs = intel_get_gvt_attrs, -+ .vgpu_query_plane = intel_vgpu_query_plane, -+ .vgpu_get_dmabuf = intel_vgpu_get_dmabuf, -+ .write_protect_handler = intel_vgpu_page_track_handler, -+ .emulate_hotplug = intel_vgpu_emulate_hotplug, -+}; -+ -+static void init_device_info(struct intel_gvt *gvt) -+{ -+ struct intel_gvt_device_info *info = &gvt->device_info; -+ struct pci_dev *pdev = gvt->dev_priv->drm.pdev; -+ -+ info->max_support_vgpus = 8; -+ info->cfg_space_size = PCI_CFG_SPACE_EXP_SIZE; -+ info->mmio_size = 2 * 1024 * 1024; -+ info->mmio_bar = 0; -+ info->gtt_start_offset = 8 * 1024 * 1024; -+ info->gtt_entry_size = 8; -+ info->gtt_entry_size_shift = 3; -+ info->gmadr_bytes_in_cmd = 8; -+ info->max_surface_size = 36 * 1024 * 1024; -+ info->msi_cap_offset = pdev->msi_cap; -+} -+ -+static int gvt_service_thread(void *data) -+{ -+ struct intel_gvt *gvt = (struct intel_gvt *)data; -+ int ret; -+ -+ gvt_dbg_core("service thread start\n"); -+ -+ while (!kthread_should_stop()) { -+ ret = wait_event_interruptible(gvt->service_thread_wq, -+ kthread_should_stop() || gvt->service_request); -+ -+ if (kthread_should_stop()) -+ break; -+ -+ if (WARN_ONCE(ret, "service thread is waken up by signal.\n")) -+ continue; -+ -+ if (test_and_clear_bit(INTEL_GVT_REQUEST_EMULATE_VBLANK, -+ (void *)&gvt->service_request)) -+ intel_gvt_emulate_vblank(gvt); -+ -+ if (test_bit(INTEL_GVT_REQUEST_SCHED, -+ (void *)&gvt->service_request) || -+ test_bit(INTEL_GVT_REQUEST_EVENT_SCHED, -+ (void *)&gvt->service_request)) { -+ intel_gvt_schedule(gvt); -+ } -+ } -+ -+ return 0; -+} -+ -+static void clean_service_thread(struct intel_gvt *gvt) -+{ -+ kthread_stop(gvt->service_thread); -+} -+ -+static int init_service_thread(struct intel_gvt *gvt) -+{ -+ init_waitqueue_head(&gvt->service_thread_wq); -+ -+ gvt->service_thread = kthread_run(gvt_service_thread, -+ gvt, "gvt_service_thread"); -+ if (IS_ERR(gvt->service_thread)) { -+ gvt_err("fail to start service thread.\n"); -+ return PTR_ERR(gvt->service_thread); -+ } -+ return 0; -+} -+ -+/** -+ * intel_gvt_clean_device - clean a GVT device -+ * @dev_priv: i915 private -+ * -+ * This function is called at the driver unloading stage, to free the -+ * resources owned by a GVT device. -+ * -+ */ -+void intel_gvt_clean_device(struct drm_i915_private *dev_priv) -+{ -+ struct intel_gvt *gvt = to_gvt(dev_priv); -+ -+ if (WARN_ON(!gvt)) -+ return; -+ -+ intel_gvt_destroy_idle_vgpu(gvt->idle_vgpu); -+ intel_gvt_cleanup_vgpu_type_groups(gvt); -+ intel_gvt_clean_vgpu_types(gvt); -+ -+ intel_gvt_debugfs_clean(gvt); -+ clean_service_thread(gvt); -+ intel_gvt_clean_cmd_parser(gvt); -+ intel_gvt_clean_sched_policy(gvt); -+ intel_gvt_clean_workload_scheduler(gvt); -+ intel_gvt_clean_gtt(gvt); -+ intel_gvt_clean_irq(gvt); -+ intel_gvt_free_firmware(gvt); -+ intel_gvt_clean_mmio_info(gvt); -+ idr_destroy(&gvt->vgpu_idr); -+ -+ kfree(dev_priv->gvt); -+ dev_priv->gvt = NULL; -+} -+ -+/** -+ * intel_gvt_init_device - initialize a GVT device -+ * @dev_priv: drm i915 private data -+ * -+ * This function is called at the initialization stage, to initialize -+ * necessary GVT components. -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ * -+ */ -+int intel_gvt_init_device(struct drm_i915_private *dev_priv) -+{ -+ struct intel_gvt *gvt; -+ struct intel_vgpu *vgpu; -+ int ret; -+ -+ if (WARN_ON(dev_priv->gvt)) -+ return -EEXIST; -+ -+ gvt = kzalloc(sizeof(struct intel_gvt), GFP_KERNEL); -+ if (!gvt) -+ return -ENOMEM; -+ -+ gvt_dbg_core("init gvt device\n"); -+ -+ idr_init(&gvt->vgpu_idr); -+ spin_lock_init(&gvt->scheduler.mmio_context_lock); -+ mutex_init(&gvt->lock); -+ mutex_init(&gvt->sched_lock); -+ gvt->dev_priv = dev_priv; -+ -+ init_device_info(gvt); -+ -+ ret = intel_gvt_setup_mmio_info(gvt); -+ if (ret) -+ goto out_clean_idr; -+ -+ intel_gvt_init_engine_mmio_context(gvt); -+ -+ ret = intel_gvt_load_firmware(gvt); -+ if (ret) -+ goto out_clean_mmio_info; -+ -+ ret = intel_gvt_init_irq(gvt); -+ if (ret) -+ goto out_free_firmware; -+ -+ ret = intel_gvt_init_gtt(gvt); -+ if (ret) -+ goto out_clean_irq; -+ -+ ret = intel_gvt_init_workload_scheduler(gvt); -+ if (ret) -+ goto out_clean_gtt; -+ -+ ret = intel_gvt_init_sched_policy(gvt); -+ if (ret) -+ goto out_clean_workload_scheduler; -+ -+ ret = intel_gvt_init_cmd_parser(gvt); -+ if (ret) -+ goto out_clean_sched_policy; -+ -+ ret = init_service_thread(gvt); -+ if (ret) -+ goto out_clean_cmd_parser; -+ -+ ret = intel_gvt_init_vgpu_types(gvt); -+ if (ret) -+ goto out_clean_thread; -+ -+ ret = intel_gvt_init_vgpu_type_groups(gvt); -+ if (ret == false) { -+ gvt_err("failed to init vgpu type groups: %d\n", ret); -+ goto out_clean_types; -+ } -+ -+ vgpu = intel_gvt_create_idle_vgpu(gvt); -+ if (IS_ERR(vgpu)) { -+ ret = PTR_ERR(vgpu); -+ gvt_err("failed to create idle vgpu\n"); -+ goto out_clean_types; -+ } -+ gvt->idle_vgpu = vgpu; -+ -+ ret = intel_gvt_debugfs_init(gvt); -+ if (ret) -+ gvt_err("debugfs registration failed, go on.\n"); -+ -+ gvt_dbg_core("gvt device initialization is done\n"); -+ dev_priv->gvt = gvt; -+ intel_gvt_host.dev = &dev_priv->drm.pdev->dev; -+ intel_gvt_host.initialized = true; -+ return 0; -+ -+out_clean_types: -+ intel_gvt_clean_vgpu_types(gvt); -+out_clean_thread: -+ clean_service_thread(gvt); -+out_clean_cmd_parser: -+ intel_gvt_clean_cmd_parser(gvt); -+out_clean_sched_policy: -+ intel_gvt_clean_sched_policy(gvt); -+out_clean_workload_scheduler: -+ intel_gvt_clean_workload_scheduler(gvt); -+out_clean_gtt: -+ intel_gvt_clean_gtt(gvt); -+out_clean_irq: -+ intel_gvt_clean_irq(gvt); -+out_free_firmware: -+ intel_gvt_free_firmware(gvt); -+out_clean_mmio_info: -+ intel_gvt_clean_mmio_info(gvt); -+out_clean_idr: -+ idr_destroy(&gvt->vgpu_idr); -+ kfree(gvt); -+ return ret; -+} -+ -+int -+intel_gvt_register_hypervisor(struct intel_gvt_mpt *m) -+{ -+ int ret; -+ void *gvt; -+ -+ if (!intel_gvt_host.initialized) -+ return -ENODEV; -+ -+ if (m->type != INTEL_GVT_HYPERVISOR_KVM && -+ m->type != INTEL_GVT_HYPERVISOR_XEN) -+ return -EINVAL; -+ -+ /* Get a reference for device model module */ -+ if (!try_module_get(THIS_MODULE)) -+ return -ENODEV; -+ -+ intel_gvt_host.mpt = m; -+ intel_gvt_host.hypervisor_type = m->type; -+ gvt = (void *)kdev_to_i915(intel_gvt_host.dev)->gvt; -+ -+ ret = intel_gvt_hypervisor_host_init(intel_gvt_host.dev, gvt, -+ &intel_gvt_ops); -+ if (ret < 0) { -+ gvt_err("Failed to init %s hypervisor module\n", -+ supported_hypervisors[intel_gvt_host.hypervisor_type]); -+ module_put(THIS_MODULE); -+ return -ENODEV; -+ } -+ gvt_dbg_core("Running with hypervisor %s in host mode\n", -+ supported_hypervisors[intel_gvt_host.hypervisor_type]); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(intel_gvt_register_hypervisor); -+ -+void -+intel_gvt_unregister_hypervisor(void) -+{ -+ intel_gvt_hypervisor_host_exit(intel_gvt_host.dev); -+ module_put(THIS_MODULE); -+} -+EXPORT_SYMBOL_GPL(intel_gvt_unregister_hypervisor); -diff --git a/drivers/gpu/drm/i915_legacy/gvt/gvt.h b/drivers/gpu/drm/i915_legacy/gvt/gvt.h -new file mode 100644 -index 000000000000..f5a328b5290a ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/gvt.h -@@ -0,0 +1,694 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Kevin Tian -+ * Eddie Dong -+ * -+ * Contributors: -+ * Niu Bing -+ * Zhi Wang -+ * -+ */ -+ -+#ifndef _GVT_H_ -+#define _GVT_H_ -+ -+#include "debug.h" -+#include "hypercall.h" -+#include "mmio.h" -+#include "reg.h" -+#include "interrupt.h" -+#include "gtt.h" -+#include "display.h" -+#include "edid.h" -+#include "execlist.h" -+#include "scheduler.h" -+#include "sched_policy.h" -+#include "mmio_context.h" -+#include "cmd_parser.h" -+#include "fb_decoder.h" -+#include "dmabuf.h" -+#include "page_track.h" -+ -+#define GVT_MAX_VGPU 8 -+ -+struct intel_gvt_host { -+ struct device *dev; -+ bool initialized; -+ int hypervisor_type; -+ struct intel_gvt_mpt *mpt; -+}; -+ -+extern struct intel_gvt_host intel_gvt_host; -+ -+/* Describe per-platform limitations. */ -+struct intel_gvt_device_info { -+ u32 max_support_vgpus; -+ u32 cfg_space_size; -+ u32 mmio_size; -+ u32 mmio_bar; -+ unsigned long msi_cap_offset; -+ u32 gtt_start_offset; -+ u32 gtt_entry_size; -+ u32 gtt_entry_size_shift; -+ int gmadr_bytes_in_cmd; -+ u32 max_surface_size; -+}; -+ -+/* GM resources owned by a vGPU */ -+struct intel_vgpu_gm { -+ u64 aperture_sz; -+ u64 hidden_sz; -+ struct drm_mm_node low_gm_node; -+ struct drm_mm_node high_gm_node; -+}; -+ -+#define INTEL_GVT_MAX_NUM_FENCES 32 -+ -+/* Fences owned by a vGPU */ -+struct intel_vgpu_fence { -+ struct drm_i915_fence_reg *regs[INTEL_GVT_MAX_NUM_FENCES]; -+ u32 base; -+ u32 size; -+}; -+ -+struct intel_vgpu_mmio { -+ void *vreg; -+}; -+ -+#define INTEL_GVT_MAX_BAR_NUM 4 -+ -+struct intel_vgpu_pci_bar { -+ u64 size; -+ bool tracked; -+}; -+ -+struct intel_vgpu_cfg_space { -+ unsigned char virtual_cfg_space[PCI_CFG_SPACE_EXP_SIZE]; -+ struct intel_vgpu_pci_bar bar[INTEL_GVT_MAX_BAR_NUM]; -+}; -+ -+#define vgpu_cfg_space(vgpu) ((vgpu)->cfg_space.virtual_cfg_space) -+ -+struct intel_vgpu_irq { -+ bool irq_warn_once[INTEL_GVT_EVENT_MAX]; -+ DECLARE_BITMAP(flip_done_event[I915_MAX_PIPES], -+ INTEL_GVT_EVENT_MAX); -+}; -+ -+struct intel_vgpu_opregion { -+ bool mapped; -+ void *va; -+ u32 gfn[INTEL_GVT_OPREGION_PAGES]; -+}; -+ -+#define vgpu_opregion(vgpu) (&(vgpu->opregion)) -+ -+struct intel_vgpu_display { -+ struct intel_vgpu_i2c_edid i2c_edid; -+ struct intel_vgpu_port ports[I915_MAX_PORTS]; -+ struct intel_vgpu_sbi sbi; -+}; -+ -+struct vgpu_sched_ctl { -+ int weight; -+}; -+ -+enum { -+ INTEL_VGPU_EXECLIST_SUBMISSION = 1, -+ INTEL_VGPU_GUC_SUBMISSION, -+}; -+ -+struct intel_vgpu_submission_ops { -+ const char *name; -+ int (*init)(struct intel_vgpu *vgpu, intel_engine_mask_t engine_mask); -+ void (*clean)(struct intel_vgpu *vgpu, intel_engine_mask_t engine_mask); -+ void (*reset)(struct intel_vgpu *vgpu, intel_engine_mask_t engine_mask); -+}; -+ -+struct intel_vgpu_submission { -+ struct intel_vgpu_execlist execlist[I915_NUM_ENGINES]; -+ struct list_head workload_q_head[I915_NUM_ENGINES]; -+ struct kmem_cache *workloads; -+ atomic_t running_workload_num; -+ struct i915_gem_context *shadow_ctx; -+ union { -+ u64 i915_context_pml4; -+ u64 i915_context_pdps[GEN8_3LVL_PDPES]; -+ }; -+ DECLARE_BITMAP(shadow_ctx_desc_updated, I915_NUM_ENGINES); -+ DECLARE_BITMAP(tlb_handle_pending, I915_NUM_ENGINES); -+ void *ring_scan_buffer[I915_NUM_ENGINES]; -+ int ring_scan_buffer_size[I915_NUM_ENGINES]; -+ const struct intel_vgpu_submission_ops *ops; -+ int virtual_submission_interface; -+ bool active; -+}; -+ -+struct intel_vgpu { -+ struct intel_gvt *gvt; -+ struct mutex vgpu_lock; -+ int id; -+ unsigned long handle; /* vGPU handle used by hypervisor MPT modules */ -+ bool active; -+ bool pv_notified; -+ bool failsafe; -+ unsigned int resetting_eng; -+ -+ /* Both sched_data and sched_ctl can be seen a part of the global gvt -+ * scheduler structure. So below 2 vgpu data are protected -+ * by sched_lock, not vgpu_lock. -+ */ -+ void *sched_data; -+ struct vgpu_sched_ctl sched_ctl; -+ -+ struct intel_vgpu_fence fence; -+ struct intel_vgpu_gm gm; -+ struct intel_vgpu_cfg_space cfg_space; -+ struct intel_vgpu_mmio mmio; -+ struct intel_vgpu_irq irq; -+ struct intel_vgpu_gtt gtt; -+ struct intel_vgpu_opregion opregion; -+ struct intel_vgpu_display display; -+ struct intel_vgpu_submission submission; -+ struct radix_tree_root page_track_tree; -+ u32 hws_pga[I915_NUM_ENGINES]; -+ -+ struct dentry *debugfs; -+ -+#if IS_ENABLED(CONFIG_DRM_I915_GVT_KVMGT) -+ struct { -+ struct mdev_device *mdev; -+ struct vfio_region *region; -+ int num_regions; -+ struct eventfd_ctx *intx_trigger; -+ struct eventfd_ctx *msi_trigger; -+ -+ /* -+ * Two caches are used to avoid mapping duplicated pages (eg. -+ * scratch pages). This help to reduce dma setup overhead. -+ */ -+ struct rb_root gfn_cache; -+ struct rb_root dma_addr_cache; -+ unsigned long nr_cache_entries; -+ struct mutex cache_lock; -+ -+ struct notifier_block iommu_notifier; -+ struct notifier_block group_notifier; -+ struct kvm *kvm; -+ struct work_struct release_work; -+ atomic_t released; -+ struct vfio_device *vfio_device; -+ } vdev; -+#endif -+ -+ struct list_head dmabuf_obj_list_head; -+ struct mutex dmabuf_lock; -+ struct idr object_idr; -+ -+ struct completion vblank_done; -+ -+ u32 scan_nonprivbb; -+}; -+ -+/* validating GM healthy status*/ -+#define vgpu_is_vm_unhealthy(ret_val) \ -+ (((ret_val) == -EBADRQC) || ((ret_val) == -EFAULT)) -+ -+struct intel_gvt_gm { -+ unsigned long vgpu_allocated_low_gm_size; -+ unsigned long vgpu_allocated_high_gm_size; -+}; -+ -+struct intel_gvt_fence { -+ unsigned long vgpu_allocated_fence_num; -+}; -+ -+/* Special MMIO blocks. */ -+struct gvt_mmio_block { -+ unsigned int device; -+ i915_reg_t offset; -+ unsigned int size; -+ gvt_mmio_func read; -+ gvt_mmio_func write; -+}; -+ -+#define INTEL_GVT_MMIO_HASH_BITS 11 -+ -+struct intel_gvt_mmio { -+ u8 *mmio_attribute; -+/* Register contains RO bits */ -+#define F_RO (1 << 0) -+/* Register contains graphics address */ -+#define F_GMADR (1 << 1) -+/* Mode mask registers with high 16 bits as the mask bits */ -+#define F_MODE_MASK (1 << 2) -+/* This reg can be accessed by GPU commands */ -+#define F_CMD_ACCESS (1 << 3) -+/* This reg has been accessed by a VM */ -+#define F_ACCESSED (1 << 4) -+/* This reg has been accessed through GPU commands */ -+#define F_CMD_ACCESSED (1 << 5) -+/* This reg could be accessed by unaligned address */ -+#define F_UNALIGN (1 << 6) -+/* This reg is saved/restored in context */ -+#define F_IN_CTX (1 << 7) -+ -+ struct gvt_mmio_block *mmio_block; -+ unsigned int num_mmio_block; -+ -+ DECLARE_HASHTABLE(mmio_info_table, INTEL_GVT_MMIO_HASH_BITS); -+ unsigned long num_tracked_mmio; -+}; -+ -+struct intel_gvt_firmware { -+ void *cfg_space; -+ void *mmio; -+ bool firmware_loaded; -+}; -+ -+#define NR_MAX_INTEL_VGPU_TYPES 20 -+struct intel_vgpu_type { -+ char name[16]; -+ unsigned int avail_instance; -+ unsigned int low_gm_size; -+ unsigned int high_gm_size; -+ unsigned int fence; -+ unsigned int weight; -+ enum intel_vgpu_edid resolution; -+}; -+ -+struct intel_gvt { -+ /* GVT scope lock, protect GVT itself, and all resource currently -+ * not yet protected by special locks(vgpu and scheduler lock). -+ */ -+ struct mutex lock; -+ /* scheduler scope lock, protect gvt and vgpu schedule related data */ -+ struct mutex sched_lock; -+ -+ struct drm_i915_private *dev_priv; -+ struct idr vgpu_idr; /* vGPU IDR pool */ -+ -+ struct intel_gvt_device_info device_info; -+ struct intel_gvt_gm gm; -+ struct intel_gvt_fence fence; -+ struct intel_gvt_mmio mmio; -+ struct intel_gvt_firmware firmware; -+ struct intel_gvt_irq irq; -+ struct intel_gvt_gtt gtt; -+ struct intel_gvt_workload_scheduler scheduler; -+ struct notifier_block shadow_ctx_notifier_block[I915_NUM_ENGINES]; -+ DECLARE_HASHTABLE(cmd_table, GVT_CMD_HASH_BITS); -+ struct intel_vgpu_type *types; -+ unsigned int num_types; -+ struct intel_vgpu *idle_vgpu; -+ -+ struct task_struct *service_thread; -+ wait_queue_head_t service_thread_wq; -+ -+ /* service_request is always used in bit operation, we should always -+ * use it with atomic bit ops so that no need to use gvt big lock. -+ */ -+ unsigned long service_request; -+ -+ struct { -+ struct engine_mmio *mmio; -+ int ctx_mmio_count[I915_NUM_ENGINES]; -+ } engine_mmio_list; -+ -+ struct dentry *debugfs_root; -+}; -+ -+static inline struct intel_gvt *to_gvt(struct drm_i915_private *i915) -+{ -+ return i915->gvt; -+} -+ -+enum { -+ INTEL_GVT_REQUEST_EMULATE_VBLANK = 0, -+ -+ /* Scheduling trigger by timer */ -+ INTEL_GVT_REQUEST_SCHED = 1, -+ -+ /* Scheduling trigger by event */ -+ INTEL_GVT_REQUEST_EVENT_SCHED = 2, -+}; -+ -+static inline void intel_gvt_request_service(struct intel_gvt *gvt, -+ int service) -+{ -+ set_bit(service, (void *)&gvt->service_request); -+ wake_up(&gvt->service_thread_wq); -+} -+ -+void intel_gvt_free_firmware(struct intel_gvt *gvt); -+int intel_gvt_load_firmware(struct intel_gvt *gvt); -+ -+/* Aperture/GM space definitions for GVT device */ -+#define MB_TO_BYTES(mb) ((mb) << 20ULL) -+#define BYTES_TO_MB(b) ((b) >> 20ULL) -+ -+#define HOST_LOW_GM_SIZE MB_TO_BYTES(128) -+#define HOST_HIGH_GM_SIZE MB_TO_BYTES(384) -+#define HOST_FENCE 4 -+ -+/* Aperture/GM space definitions for GVT device */ -+#define gvt_aperture_sz(gvt) (gvt->dev_priv->ggtt.mappable_end) -+#define gvt_aperture_pa_base(gvt) (gvt->dev_priv->ggtt.gmadr.start) -+ -+#define gvt_ggtt_gm_sz(gvt) (gvt->dev_priv->ggtt.vm.total) -+#define gvt_ggtt_sz(gvt) \ -+ ((gvt->dev_priv->ggtt.vm.total >> PAGE_SHIFT) << 3) -+#define gvt_hidden_sz(gvt) (gvt_ggtt_gm_sz(gvt) - gvt_aperture_sz(gvt)) -+ -+#define gvt_aperture_gmadr_base(gvt) (0) -+#define gvt_aperture_gmadr_end(gvt) (gvt_aperture_gmadr_base(gvt) \ -+ + gvt_aperture_sz(gvt) - 1) -+ -+#define gvt_hidden_gmadr_base(gvt) (gvt_aperture_gmadr_base(gvt) \ -+ + gvt_aperture_sz(gvt)) -+#define gvt_hidden_gmadr_end(gvt) (gvt_hidden_gmadr_base(gvt) \ -+ + gvt_hidden_sz(gvt) - 1) -+ -+#define gvt_fence_sz(gvt) (gvt->dev_priv->num_fence_regs) -+ -+/* Aperture/GM space definitions for vGPU */ -+#define vgpu_aperture_offset(vgpu) ((vgpu)->gm.low_gm_node.start) -+#define vgpu_hidden_offset(vgpu) ((vgpu)->gm.high_gm_node.start) -+#define vgpu_aperture_sz(vgpu) ((vgpu)->gm.aperture_sz) -+#define vgpu_hidden_sz(vgpu) ((vgpu)->gm.hidden_sz) -+ -+#define vgpu_aperture_pa_base(vgpu) \ -+ (gvt_aperture_pa_base(vgpu->gvt) + vgpu_aperture_offset(vgpu)) -+ -+#define vgpu_ggtt_gm_sz(vgpu) ((vgpu)->gm.aperture_sz + (vgpu)->gm.hidden_sz) -+ -+#define vgpu_aperture_pa_end(vgpu) \ -+ (vgpu_aperture_pa_base(vgpu) + vgpu_aperture_sz(vgpu) - 1) -+ -+#define vgpu_aperture_gmadr_base(vgpu) (vgpu_aperture_offset(vgpu)) -+#define vgpu_aperture_gmadr_end(vgpu) \ -+ (vgpu_aperture_gmadr_base(vgpu) + vgpu_aperture_sz(vgpu) - 1) -+ -+#define vgpu_hidden_gmadr_base(vgpu) (vgpu_hidden_offset(vgpu)) -+#define vgpu_hidden_gmadr_end(vgpu) \ -+ (vgpu_hidden_gmadr_base(vgpu) + vgpu_hidden_sz(vgpu) - 1) -+ -+#define vgpu_fence_base(vgpu) (vgpu->fence.base) -+#define vgpu_fence_sz(vgpu) (vgpu->fence.size) -+ -+struct intel_vgpu_creation_params { -+ __u64 handle; -+ __u64 low_gm_sz; /* in MB */ -+ __u64 high_gm_sz; /* in MB */ -+ __u64 fence_sz; -+ __u64 resolution; -+ __s32 primary; -+ __u64 vgpu_id; -+ -+ __u32 weight; -+}; -+ -+int intel_vgpu_alloc_resource(struct intel_vgpu *vgpu, -+ struct intel_vgpu_creation_params *param); -+void intel_vgpu_reset_resource(struct intel_vgpu *vgpu); -+void intel_vgpu_free_resource(struct intel_vgpu *vgpu); -+void intel_vgpu_write_fence(struct intel_vgpu *vgpu, -+ u32 fence, u64 value); -+ -+/* Macros for easily accessing vGPU virtual/shadow register. -+ Explicitly seperate use for typed MMIO reg or real offset.*/ -+#define vgpu_vreg_t(vgpu, reg) \ -+ (*(u32 *)(vgpu->mmio.vreg + i915_mmio_reg_offset(reg))) -+#define vgpu_vreg(vgpu, offset) \ -+ (*(u32 *)(vgpu->mmio.vreg + (offset))) -+#define vgpu_vreg64_t(vgpu, reg) \ -+ (*(u64 *)(vgpu->mmio.vreg + i915_mmio_reg_offset(reg))) -+#define vgpu_vreg64(vgpu, offset) \ -+ (*(u64 *)(vgpu->mmio.vreg + (offset))) -+ -+#define for_each_active_vgpu(gvt, vgpu, id) \ -+ idr_for_each_entry((&(gvt)->vgpu_idr), (vgpu), (id)) \ -+ for_each_if(vgpu->active) -+ -+static inline void intel_vgpu_write_pci_bar(struct intel_vgpu *vgpu, -+ u32 offset, u32 val, bool low) -+{ -+ u32 *pval; -+ -+ /* BAR offset should be 32 bits algiend */ -+ offset = rounddown(offset, 4); -+ pval = (u32 *)(vgpu_cfg_space(vgpu) + offset); -+ -+ if (low) { -+ /* -+ * only update bit 31 - bit 4, -+ * leave the bit 3 - bit 0 unchanged. -+ */ -+ *pval = (val & GENMASK(31, 4)) | (*pval & GENMASK(3, 0)); -+ } else { -+ *pval = val; -+ } -+} -+ -+int intel_gvt_init_vgpu_types(struct intel_gvt *gvt); -+void intel_gvt_clean_vgpu_types(struct intel_gvt *gvt); -+ -+struct intel_vgpu *intel_gvt_create_idle_vgpu(struct intel_gvt *gvt); -+void intel_gvt_destroy_idle_vgpu(struct intel_vgpu *vgpu); -+struct intel_vgpu *intel_gvt_create_vgpu(struct intel_gvt *gvt, -+ struct intel_vgpu_type *type); -+void intel_gvt_destroy_vgpu(struct intel_vgpu *vgpu); -+void intel_gvt_release_vgpu(struct intel_vgpu *vgpu); -+void intel_gvt_reset_vgpu_locked(struct intel_vgpu *vgpu, bool dmlr, -+ intel_engine_mask_t engine_mask); -+void intel_gvt_reset_vgpu(struct intel_vgpu *vgpu); -+void intel_gvt_activate_vgpu(struct intel_vgpu *vgpu); -+void intel_gvt_deactivate_vgpu(struct intel_vgpu *vgpu); -+ -+/* validating GM functions */ -+#define vgpu_gmadr_is_aperture(vgpu, gmadr) \ -+ ((gmadr >= vgpu_aperture_gmadr_base(vgpu)) && \ -+ (gmadr <= vgpu_aperture_gmadr_end(vgpu))) -+ -+#define vgpu_gmadr_is_hidden(vgpu, gmadr) \ -+ ((gmadr >= vgpu_hidden_gmadr_base(vgpu)) && \ -+ (gmadr <= vgpu_hidden_gmadr_end(vgpu))) -+ -+#define vgpu_gmadr_is_valid(vgpu, gmadr) \ -+ ((vgpu_gmadr_is_aperture(vgpu, gmadr) || \ -+ (vgpu_gmadr_is_hidden(vgpu, gmadr)))) -+ -+#define gvt_gmadr_is_aperture(gvt, gmadr) \ -+ ((gmadr >= gvt_aperture_gmadr_base(gvt)) && \ -+ (gmadr <= gvt_aperture_gmadr_end(gvt))) -+ -+#define gvt_gmadr_is_hidden(gvt, gmadr) \ -+ ((gmadr >= gvt_hidden_gmadr_base(gvt)) && \ -+ (gmadr <= gvt_hidden_gmadr_end(gvt))) -+ -+#define gvt_gmadr_is_valid(gvt, gmadr) \ -+ (gvt_gmadr_is_aperture(gvt, gmadr) || \ -+ gvt_gmadr_is_hidden(gvt, gmadr)) -+ -+bool intel_gvt_ggtt_validate_range(struct intel_vgpu *vgpu, u64 addr, u32 size); -+int intel_gvt_ggtt_gmadr_g2h(struct intel_vgpu *vgpu, u64 g_addr, u64 *h_addr); -+int intel_gvt_ggtt_gmadr_h2g(struct intel_vgpu *vgpu, u64 h_addr, u64 *g_addr); -+int intel_gvt_ggtt_index_g2h(struct intel_vgpu *vgpu, unsigned long g_index, -+ unsigned long *h_index); -+int intel_gvt_ggtt_h2g_index(struct intel_vgpu *vgpu, unsigned long h_index, -+ unsigned long *g_index); -+ -+void intel_vgpu_init_cfg_space(struct intel_vgpu *vgpu, -+ bool primary); -+void intel_vgpu_reset_cfg_space(struct intel_vgpu *vgpu); -+ -+int intel_vgpu_emulate_cfg_read(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes); -+ -+int intel_vgpu_emulate_cfg_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes); -+ -+void intel_vgpu_emulate_hotplug(struct intel_vgpu *vgpu, bool connected); -+ -+static inline u64 intel_vgpu_get_bar_gpa(struct intel_vgpu *vgpu, int bar) -+{ -+ /* We are 64bit bar. */ -+ return (*(u64 *)(vgpu->cfg_space.virtual_cfg_space + bar)) & -+ PCI_BASE_ADDRESS_MEM_MASK; -+} -+ -+void intel_vgpu_clean_opregion(struct intel_vgpu *vgpu); -+int intel_vgpu_init_opregion(struct intel_vgpu *vgpu); -+int intel_vgpu_opregion_base_write_handler(struct intel_vgpu *vgpu, u32 gpa); -+ -+int intel_vgpu_emulate_opregion_request(struct intel_vgpu *vgpu, u32 swsci); -+void populate_pvinfo_page(struct intel_vgpu *vgpu); -+ -+int intel_gvt_scan_and_shadow_workload(struct intel_vgpu_workload *workload); -+void enter_failsafe_mode(struct intel_vgpu *vgpu, int reason); -+ -+struct intel_gvt_ops { -+ int (*emulate_cfg_read)(struct intel_vgpu *, unsigned int, void *, -+ unsigned int); -+ int (*emulate_cfg_write)(struct intel_vgpu *, unsigned int, void *, -+ unsigned int); -+ int (*emulate_mmio_read)(struct intel_vgpu *, u64, void *, -+ unsigned int); -+ int (*emulate_mmio_write)(struct intel_vgpu *, u64, void *, -+ unsigned int); -+ struct intel_vgpu *(*vgpu_create)(struct intel_gvt *, -+ struct intel_vgpu_type *); -+ void (*vgpu_destroy)(struct intel_vgpu *vgpu); -+ void (*vgpu_release)(struct intel_vgpu *vgpu); -+ void (*vgpu_reset)(struct intel_vgpu *); -+ void (*vgpu_activate)(struct intel_vgpu *); -+ void (*vgpu_deactivate)(struct intel_vgpu *); -+ struct intel_vgpu_type *(*gvt_find_vgpu_type)(struct intel_gvt *gvt, -+ const char *name); -+ bool (*get_gvt_attrs)(struct attribute ***type_attrs, -+ struct attribute_group ***intel_vgpu_type_groups); -+ int (*vgpu_query_plane)(struct intel_vgpu *vgpu, void *); -+ int (*vgpu_get_dmabuf)(struct intel_vgpu *vgpu, unsigned int); -+ int (*write_protect_handler)(struct intel_vgpu *, u64, void *, -+ unsigned int); -+ void (*emulate_hotplug)(struct intel_vgpu *vgpu, bool connected); -+}; -+ -+ -+enum { -+ GVT_FAILSAFE_UNSUPPORTED_GUEST, -+ GVT_FAILSAFE_INSUFFICIENT_RESOURCE, -+ GVT_FAILSAFE_GUEST_ERR, -+}; -+ -+static inline void mmio_hw_access_pre(struct drm_i915_private *dev_priv) -+{ -+ intel_runtime_pm_get(dev_priv); -+} -+ -+static inline void mmio_hw_access_post(struct drm_i915_private *dev_priv) -+{ -+ intel_runtime_pm_put_unchecked(dev_priv); -+} -+ -+/** -+ * intel_gvt_mmio_set_accessed - mark a MMIO has been accessed -+ * @gvt: a GVT device -+ * @offset: register offset -+ * -+ */ -+static inline void intel_gvt_mmio_set_accessed( -+ struct intel_gvt *gvt, unsigned int offset) -+{ -+ gvt->mmio.mmio_attribute[offset >> 2] |= F_ACCESSED; -+} -+ -+/** -+ * intel_gvt_mmio_is_cmd_accessed - mark a MMIO could be accessed by command -+ * @gvt: a GVT device -+ * @offset: register offset -+ * -+ */ -+static inline bool intel_gvt_mmio_is_cmd_access( -+ struct intel_gvt *gvt, unsigned int offset) -+{ -+ return gvt->mmio.mmio_attribute[offset >> 2] & F_CMD_ACCESS; -+} -+ -+/** -+ * intel_gvt_mmio_is_unalign - mark a MMIO could be accessed unaligned -+ * @gvt: a GVT device -+ * @offset: register offset -+ * -+ */ -+static inline bool intel_gvt_mmio_is_unalign( -+ struct intel_gvt *gvt, unsigned int offset) -+{ -+ return gvt->mmio.mmio_attribute[offset >> 2] & F_UNALIGN; -+} -+ -+/** -+ * intel_gvt_mmio_set_cmd_accessed - mark a MMIO has been accessed by command -+ * @gvt: a GVT device -+ * @offset: register offset -+ * -+ */ -+static inline void intel_gvt_mmio_set_cmd_accessed( -+ struct intel_gvt *gvt, unsigned int offset) -+{ -+ gvt->mmio.mmio_attribute[offset >> 2] |= F_CMD_ACCESSED; -+} -+ -+/** -+ * intel_gvt_mmio_has_mode_mask - if a MMIO has a mode mask -+ * @gvt: a GVT device -+ * @offset: register offset -+ * -+ * Returns: -+ * True if a MMIO has a mode mask in its higher 16 bits, false if it isn't. -+ * -+ */ -+static inline bool intel_gvt_mmio_has_mode_mask( -+ struct intel_gvt *gvt, unsigned int offset) -+{ -+ return gvt->mmio.mmio_attribute[offset >> 2] & F_MODE_MASK; -+} -+ -+/** -+ * intel_gvt_mmio_is_in_ctx - check if a MMIO has in-ctx mask -+ * @gvt: a GVT device -+ * @offset: register offset -+ * -+ * Returns: -+ * True if a MMIO has a in-context mask, false if it isn't. -+ * -+ */ -+static inline bool intel_gvt_mmio_is_in_ctx( -+ struct intel_gvt *gvt, unsigned int offset) -+{ -+ return gvt->mmio.mmio_attribute[offset >> 2] & F_IN_CTX; -+} -+ -+/** -+ * intel_gvt_mmio_set_in_ctx - mask a MMIO in logical context -+ * @gvt: a GVT device -+ * @offset: register offset -+ * -+ */ -+static inline void intel_gvt_mmio_set_in_ctx( -+ struct intel_gvt *gvt, unsigned int offset) -+{ -+ gvt->mmio.mmio_attribute[offset >> 2] |= F_IN_CTX; -+} -+ -+int intel_gvt_debugfs_add_vgpu(struct intel_vgpu *vgpu); -+void intel_gvt_debugfs_remove_vgpu(struct intel_vgpu *vgpu); -+int intel_gvt_debugfs_init(struct intel_gvt *gvt); -+void intel_gvt_debugfs_clean(struct intel_gvt *gvt); -+ -+ -+#include "trace.h" -+#include "mpt.h" -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/gvt/handlers.c b/drivers/gpu/drm/i915_legacy/gvt/handlers.c -new file mode 100644 -index 000000000000..25f78196b964 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/handlers.c -@@ -0,0 +1,3588 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Kevin Tian -+ * Eddie Dong -+ * Zhiyuan Lv -+ * -+ * Contributors: -+ * Min He -+ * Tina Zhang -+ * Pei Zhang -+ * Niu Bing -+ * Ping Gao -+ * Zhi Wang -+ * -+ -+ */ -+ -+#include "i915_drv.h" -+#include "gvt.h" -+#include "i915_pvinfo.h" -+ -+/* XXX FIXME i915 has changed PP_XXX definition */ -+#define PCH_PP_STATUS _MMIO(0xc7200) -+#define PCH_PP_CONTROL _MMIO(0xc7204) -+#define PCH_PP_ON_DELAYS _MMIO(0xc7208) -+#define PCH_PP_OFF_DELAYS _MMIO(0xc720c) -+#define PCH_PP_DIVISOR _MMIO(0xc7210) -+ -+unsigned long intel_gvt_get_device_type(struct intel_gvt *gvt) -+{ -+ if (IS_BROADWELL(gvt->dev_priv)) -+ return D_BDW; -+ else if (IS_SKYLAKE(gvt->dev_priv)) -+ return D_SKL; -+ else if (IS_KABYLAKE(gvt->dev_priv)) -+ return D_KBL; -+ else if (IS_BROXTON(gvt->dev_priv)) -+ return D_BXT; -+ else if (IS_COFFEELAKE(gvt->dev_priv)) -+ return D_CFL; -+ -+ return 0; -+} -+ -+bool intel_gvt_match_device(struct intel_gvt *gvt, -+ unsigned long device) -+{ -+ return intel_gvt_get_device_type(gvt) & device; -+} -+ -+static void read_vreg(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ memcpy(p_data, &vgpu_vreg(vgpu, offset), bytes); -+} -+ -+static void write_vreg(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ memcpy(&vgpu_vreg(vgpu, offset), p_data, bytes); -+} -+ -+static struct intel_gvt_mmio_info *find_mmio_info(struct intel_gvt *gvt, -+ unsigned int offset) -+{ -+ struct intel_gvt_mmio_info *e; -+ -+ hash_for_each_possible(gvt->mmio.mmio_info_table, e, node, offset) { -+ if (e->offset == offset) -+ return e; -+ } -+ return NULL; -+} -+ -+static int new_mmio_info(struct intel_gvt *gvt, -+ u32 offset, u8 flags, u32 size, -+ u32 addr_mask, u32 ro_mask, u32 device, -+ gvt_mmio_func read, gvt_mmio_func write) -+{ -+ struct intel_gvt_mmio_info *info, *p; -+ u32 start, end, i; -+ -+ if (!intel_gvt_match_device(gvt, device)) -+ return 0; -+ -+ if (WARN_ON(!IS_ALIGNED(offset, 4))) -+ return -EINVAL; -+ -+ start = offset; -+ end = offset + size; -+ -+ for (i = start; i < end; i += 4) { -+ info = kzalloc(sizeof(*info), GFP_KERNEL); -+ if (!info) -+ return -ENOMEM; -+ -+ info->offset = i; -+ p = find_mmio_info(gvt, info->offset); -+ if (p) { -+ WARN(1, "dup mmio definition offset %x\n", -+ info->offset); -+ kfree(info); -+ -+ /* We return -EEXIST here to make GVT-g load fail. -+ * So duplicated MMIO can be found as soon as -+ * possible. -+ */ -+ return -EEXIST; -+ } -+ -+ info->ro_mask = ro_mask; -+ info->device = device; -+ info->read = read ? read : intel_vgpu_default_mmio_read; -+ info->write = write ? write : intel_vgpu_default_mmio_write; -+ gvt->mmio.mmio_attribute[info->offset / 4] = flags; -+ INIT_HLIST_NODE(&info->node); -+ hash_add(gvt->mmio.mmio_info_table, &info->node, info->offset); -+ gvt->mmio.num_tracked_mmio++; -+ } -+ return 0; -+} -+ -+/** -+ * intel_gvt_render_mmio_to_ring_id - convert a mmio offset into ring id -+ * @gvt: a GVT device -+ * @offset: register offset -+ * -+ * Returns: -+ * Ring ID on success, negative error code if failed. -+ */ -+int intel_gvt_render_mmio_to_ring_id(struct intel_gvt *gvt, -+ unsigned int offset) -+{ -+ enum intel_engine_id id; -+ struct intel_engine_cs *engine; -+ -+ offset &= ~GENMASK(11, 0); -+ for_each_engine(engine, gvt->dev_priv, id) { -+ if (engine->mmio_base == offset) -+ return id; -+ } -+ return -ENODEV; -+} -+ -+#define offset_to_fence_num(offset) \ -+ ((offset - i915_mmio_reg_offset(FENCE_REG_GEN6_LO(0))) >> 3) -+ -+#define fence_num_to_offset(num) \ -+ (num * 8 + i915_mmio_reg_offset(FENCE_REG_GEN6_LO(0))) -+ -+ -+void enter_failsafe_mode(struct intel_vgpu *vgpu, int reason) -+{ -+ switch (reason) { -+ case GVT_FAILSAFE_UNSUPPORTED_GUEST: -+ pr_err("Detected your guest driver doesn't support GVT-g.\n"); -+ break; -+ case GVT_FAILSAFE_INSUFFICIENT_RESOURCE: -+ pr_err("Graphics resource is not enough for the guest\n"); -+ break; -+ case GVT_FAILSAFE_GUEST_ERR: -+ pr_err("GVT Internal error for the guest\n"); -+ break; -+ default: -+ break; -+ } -+ pr_err("Now vgpu %d will enter failsafe mode.\n", vgpu->id); -+ vgpu->failsafe = true; -+} -+ -+static int sanitize_fence_mmio_access(struct intel_vgpu *vgpu, -+ unsigned int fence_num, void *p_data, unsigned int bytes) -+{ -+ unsigned int max_fence = vgpu_fence_sz(vgpu); -+ -+ if (fence_num >= max_fence) { -+ gvt_vgpu_err("access oob fence reg %d/%d\n", -+ fence_num, max_fence); -+ -+ /* When guest access oob fence regs without access -+ * pv_info first, we treat guest not supporting GVT, -+ * and we will let vgpu enter failsafe mode. -+ */ -+ if (!vgpu->pv_notified) -+ enter_failsafe_mode(vgpu, -+ GVT_FAILSAFE_UNSUPPORTED_GUEST); -+ -+ memset(p_data, 0, bytes); -+ return -EINVAL; -+ } -+ return 0; -+} -+ -+static int gamw_echo_dev_rw_ia_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 ips = (*(u32 *)p_data) & GAMW_ECO_ENABLE_64K_IPS_FIELD; -+ -+ if (INTEL_GEN(vgpu->gvt->dev_priv) <= 10) { -+ if (ips == GAMW_ECO_ENABLE_64K_IPS_FIELD) -+ gvt_dbg_core("vgpu%d: ips enabled\n", vgpu->id); -+ else if (!ips) -+ gvt_dbg_core("vgpu%d: ips disabled\n", vgpu->id); -+ else { -+ /* All engines must be enabled together for vGPU, -+ * since we don't know which engine the ppgtt will -+ * bind to when shadowing. -+ */ -+ gvt_vgpu_err("Unsupported IPS setting %x, cannot enable 64K gtt.\n", -+ ips); -+ return -EINVAL; -+ } -+ } -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ return 0; -+} -+ -+static int fence_mmio_read(struct intel_vgpu *vgpu, unsigned int off, -+ void *p_data, unsigned int bytes) -+{ -+ int ret; -+ -+ ret = sanitize_fence_mmio_access(vgpu, offset_to_fence_num(off), -+ p_data, bytes); -+ if (ret) -+ return ret; -+ read_vreg(vgpu, off, p_data, bytes); -+ return 0; -+} -+ -+static int fence_mmio_write(struct intel_vgpu *vgpu, unsigned int off, -+ void *p_data, unsigned int bytes) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ unsigned int fence_num = offset_to_fence_num(off); -+ int ret; -+ -+ ret = sanitize_fence_mmio_access(vgpu, fence_num, p_data, bytes); -+ if (ret) -+ return ret; -+ write_vreg(vgpu, off, p_data, bytes); -+ -+ mmio_hw_access_pre(dev_priv); -+ intel_vgpu_write_fence(vgpu, fence_num, -+ vgpu_vreg64(vgpu, fence_num_to_offset(fence_num))); -+ mmio_hw_access_post(dev_priv); -+ return 0; -+} -+ -+#define CALC_MODE_MASK_REG(old, new) \ -+ (((new) & GENMASK(31, 16)) \ -+ | ((((old) & GENMASK(15, 0)) & ~((new) >> 16)) \ -+ | ((new) & ((new) >> 16)))) -+ -+static int mul_force_wake_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 old, new; -+ u32 ack_reg_offset; -+ -+ old = vgpu_vreg(vgpu, offset); -+ new = CALC_MODE_MASK_REG(old, *(u32 *)p_data); -+ -+ if (INTEL_GEN(vgpu->gvt->dev_priv) >= 9) { -+ switch (offset) { -+ case FORCEWAKE_RENDER_GEN9_REG: -+ ack_reg_offset = FORCEWAKE_ACK_RENDER_GEN9_REG; -+ break; -+ case FORCEWAKE_BLITTER_GEN9_REG: -+ ack_reg_offset = FORCEWAKE_ACK_BLITTER_GEN9_REG; -+ break; -+ case FORCEWAKE_MEDIA_GEN9_REG: -+ ack_reg_offset = FORCEWAKE_ACK_MEDIA_GEN9_REG; -+ break; -+ default: -+ /*should not hit here*/ -+ gvt_vgpu_err("invalid forcewake offset 0x%x\n", offset); -+ return -EINVAL; -+ } -+ } else { -+ ack_reg_offset = FORCEWAKE_ACK_HSW_REG; -+ } -+ -+ vgpu_vreg(vgpu, offset) = new; -+ vgpu_vreg(vgpu, ack_reg_offset) = (new & GENMASK(15, 0)); -+ return 0; -+} -+ -+static int gdrst_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ intel_engine_mask_t engine_mask = 0; -+ u32 data; -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ data = vgpu_vreg(vgpu, offset); -+ -+ if (data & GEN6_GRDOM_FULL) { -+ gvt_dbg_mmio("vgpu%d: request full GPU reset\n", vgpu->id); -+ engine_mask = ALL_ENGINES; -+ } else { -+ if (data & GEN6_GRDOM_RENDER) { -+ gvt_dbg_mmio("vgpu%d: request RCS reset\n", vgpu->id); -+ engine_mask |= BIT(RCS0); -+ } -+ if (data & GEN6_GRDOM_MEDIA) { -+ gvt_dbg_mmio("vgpu%d: request VCS reset\n", vgpu->id); -+ engine_mask |= BIT(VCS0); -+ } -+ if (data & GEN6_GRDOM_BLT) { -+ gvt_dbg_mmio("vgpu%d: request BCS Reset\n", vgpu->id); -+ engine_mask |= BIT(BCS0); -+ } -+ if (data & GEN6_GRDOM_VECS) { -+ gvt_dbg_mmio("vgpu%d: request VECS Reset\n", vgpu->id); -+ engine_mask |= BIT(VECS0); -+ } -+ if (data & GEN8_GRDOM_MEDIA2) { -+ gvt_dbg_mmio("vgpu%d: request VCS2 Reset\n", vgpu->id); -+ engine_mask |= BIT(VCS1); -+ } -+ engine_mask &= INTEL_INFO(vgpu->gvt->dev_priv)->engine_mask; -+ } -+ -+ /* vgpu_lock already hold by emulate mmio r/w */ -+ intel_gvt_reset_vgpu_locked(vgpu, false, engine_mask); -+ -+ /* sw will wait for the device to ack the reset request */ -+ vgpu_vreg(vgpu, offset) = 0; -+ -+ return 0; -+} -+ -+static int gmbus_mmio_read(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ return intel_gvt_i2c_handle_gmbus_read(vgpu, offset, p_data, bytes); -+} -+ -+static int gmbus_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ return intel_gvt_i2c_handle_gmbus_write(vgpu, offset, p_data, bytes); -+} -+ -+static int pch_pp_control_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ write_vreg(vgpu, offset, p_data, bytes); -+ -+ if (vgpu_vreg(vgpu, offset) & PANEL_POWER_ON) { -+ vgpu_vreg_t(vgpu, PCH_PP_STATUS) |= PP_ON; -+ vgpu_vreg_t(vgpu, PCH_PP_STATUS) |= PP_SEQUENCE_STATE_ON_IDLE; -+ vgpu_vreg_t(vgpu, PCH_PP_STATUS) &= ~PP_SEQUENCE_POWER_DOWN; -+ vgpu_vreg_t(vgpu, PCH_PP_STATUS) &= ~PP_CYCLE_DELAY_ACTIVE; -+ -+ } else -+ vgpu_vreg_t(vgpu, PCH_PP_STATUS) &= -+ ~(PP_ON | PP_SEQUENCE_POWER_DOWN -+ | PP_CYCLE_DELAY_ACTIVE); -+ return 0; -+} -+ -+static int transconf_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ write_vreg(vgpu, offset, p_data, bytes); -+ -+ if (vgpu_vreg(vgpu, offset) & TRANS_ENABLE) -+ vgpu_vreg(vgpu, offset) |= TRANS_STATE_ENABLE; -+ else -+ vgpu_vreg(vgpu, offset) &= ~TRANS_STATE_ENABLE; -+ return 0; -+} -+ -+static int lcpll_ctl_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ write_vreg(vgpu, offset, p_data, bytes); -+ -+ if (vgpu_vreg(vgpu, offset) & LCPLL_PLL_DISABLE) -+ vgpu_vreg(vgpu, offset) &= ~LCPLL_PLL_LOCK; -+ else -+ vgpu_vreg(vgpu, offset) |= LCPLL_PLL_LOCK; -+ -+ if (vgpu_vreg(vgpu, offset) & LCPLL_CD_SOURCE_FCLK) -+ vgpu_vreg(vgpu, offset) |= LCPLL_CD_SOURCE_FCLK_DONE; -+ else -+ vgpu_vreg(vgpu, offset) &= ~LCPLL_CD_SOURCE_FCLK_DONE; -+ -+ return 0; -+} -+ -+static int dpy_reg_mmio_read(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ switch (offset) { -+ case 0xe651c: -+ case 0xe661c: -+ case 0xe671c: -+ case 0xe681c: -+ vgpu_vreg(vgpu, offset) = 1 << 17; -+ break; -+ case 0xe6c04: -+ vgpu_vreg(vgpu, offset) = 0x3; -+ break; -+ case 0xe6e1c: -+ vgpu_vreg(vgpu, offset) = 0x2f << 16; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ read_vreg(vgpu, offset, p_data, bytes); -+ return 0; -+} -+ -+static int pipeconf_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 data; -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ data = vgpu_vreg(vgpu, offset); -+ -+ if (data & PIPECONF_ENABLE) -+ vgpu_vreg(vgpu, offset) |= I965_PIPECONF_ACTIVE; -+ else -+ vgpu_vreg(vgpu, offset) &= ~I965_PIPECONF_ACTIVE; -+ /* vgpu_lock already hold by emulate mmio r/w */ -+ mutex_unlock(&vgpu->vgpu_lock); -+ intel_gvt_check_vblank_emulation(vgpu->gvt); -+ mutex_lock(&vgpu->vgpu_lock); -+ return 0; -+} -+ -+/* ascendingly sorted */ -+static i915_reg_t force_nonpriv_white_list[] = { -+ GEN9_CS_DEBUG_MODE1, //_MMIO(0x20ec) -+ GEN9_CTX_PREEMPT_REG,//_MMIO(0x2248) -+ GEN8_CS_CHICKEN1,//_MMIO(0x2580) -+ _MMIO(0x2690), -+ _MMIO(0x2694), -+ _MMIO(0x2698), -+ _MMIO(0x2754), -+ _MMIO(0x28a0), -+ _MMIO(0x4de0), -+ _MMIO(0x4de4), -+ _MMIO(0x4dfc), -+ GEN7_COMMON_SLICE_CHICKEN1,//_MMIO(0x7010) -+ _MMIO(0x7014), -+ HDC_CHICKEN0,//_MMIO(0x7300) -+ GEN8_HDC_CHICKEN1,//_MMIO(0x7304) -+ _MMIO(0x7700), -+ _MMIO(0x7704), -+ _MMIO(0x7708), -+ _MMIO(0x770c), -+ _MMIO(0x83a8), -+ _MMIO(0xb110), -+ GEN8_L3SQCREG4,//_MMIO(0xb118) -+ _MMIO(0xe100), -+ _MMIO(0xe18c), -+ _MMIO(0xe48c), -+ _MMIO(0xe5f4), -+}; -+ -+/* a simple bsearch */ -+static inline bool in_whitelist(unsigned int reg) -+{ -+ int left = 0, right = ARRAY_SIZE(force_nonpriv_white_list); -+ i915_reg_t *array = force_nonpriv_white_list; -+ -+ while (left < right) { -+ int mid = (left + right)/2; -+ -+ if (reg > array[mid].reg) -+ left = mid + 1; -+ else if (reg < array[mid].reg) -+ right = mid; -+ else -+ return true; -+ } -+ return false; -+} -+ -+static int force_nonpriv_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 reg_nonpriv = *(u32 *)p_data; -+ int ring_id = intel_gvt_render_mmio_to_ring_id(vgpu->gvt, offset); -+ u32 ring_base; -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ int ret = -EINVAL; -+ -+ if ((bytes != 4) || ((offset & (bytes - 1)) != 0) || ring_id < 0) { -+ gvt_err("vgpu(%d) ring %d Invalid FORCE_NONPRIV offset %x(%dB)\n", -+ vgpu->id, ring_id, offset, bytes); -+ return ret; -+ } -+ -+ ring_base = dev_priv->engine[ring_id]->mmio_base; -+ -+ if (in_whitelist(reg_nonpriv) || -+ reg_nonpriv == i915_mmio_reg_offset(RING_NOPID(ring_base))) { -+ ret = intel_vgpu_default_mmio_write(vgpu, offset, p_data, -+ bytes); -+ } else -+ gvt_err("vgpu(%d) Invalid FORCE_NONPRIV write %x at offset %x\n", -+ vgpu->id, reg_nonpriv, offset); -+ -+ return 0; -+} -+ -+static int ddi_buf_ctl_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ write_vreg(vgpu, offset, p_data, bytes); -+ -+ if (vgpu_vreg(vgpu, offset) & DDI_BUF_CTL_ENABLE) { -+ vgpu_vreg(vgpu, offset) &= ~DDI_BUF_IS_IDLE; -+ } else { -+ vgpu_vreg(vgpu, offset) |= DDI_BUF_IS_IDLE; -+ if (offset == i915_mmio_reg_offset(DDI_BUF_CTL(PORT_E))) -+ vgpu_vreg_t(vgpu, DP_TP_STATUS(PORT_E)) -+ &= ~DP_TP_STATUS_AUTOTRAIN_DONE; -+ } -+ return 0; -+} -+ -+static int fdi_rx_iir_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ vgpu_vreg(vgpu, offset) &= ~*(u32 *)p_data; -+ return 0; -+} -+ -+#define FDI_LINK_TRAIN_PATTERN1 0 -+#define FDI_LINK_TRAIN_PATTERN2 1 -+ -+static int fdi_auto_training_started(struct intel_vgpu *vgpu) -+{ -+ u32 ddi_buf_ctl = vgpu_vreg_t(vgpu, DDI_BUF_CTL(PORT_E)); -+ u32 rx_ctl = vgpu_vreg(vgpu, _FDI_RXA_CTL); -+ u32 tx_ctl = vgpu_vreg_t(vgpu, DP_TP_CTL(PORT_E)); -+ -+ if ((ddi_buf_ctl & DDI_BUF_CTL_ENABLE) && -+ (rx_ctl & FDI_RX_ENABLE) && -+ (rx_ctl & FDI_AUTO_TRAINING) && -+ (tx_ctl & DP_TP_CTL_ENABLE) && -+ (tx_ctl & DP_TP_CTL_FDI_AUTOTRAIN)) -+ return 1; -+ else -+ return 0; -+} -+ -+static int check_fdi_rx_train_status(struct intel_vgpu *vgpu, -+ enum pipe pipe, unsigned int train_pattern) -+{ -+ i915_reg_t fdi_rx_imr, fdi_tx_ctl, fdi_rx_ctl; -+ unsigned int fdi_rx_check_bits, fdi_tx_check_bits; -+ unsigned int fdi_rx_train_bits, fdi_tx_train_bits; -+ unsigned int fdi_iir_check_bits; -+ -+ fdi_rx_imr = FDI_RX_IMR(pipe); -+ fdi_tx_ctl = FDI_TX_CTL(pipe); -+ fdi_rx_ctl = FDI_RX_CTL(pipe); -+ -+ if (train_pattern == FDI_LINK_TRAIN_PATTERN1) { -+ fdi_rx_train_bits = FDI_LINK_TRAIN_PATTERN_1_CPT; -+ fdi_tx_train_bits = FDI_LINK_TRAIN_PATTERN_1; -+ fdi_iir_check_bits = FDI_RX_BIT_LOCK; -+ } else if (train_pattern == FDI_LINK_TRAIN_PATTERN2) { -+ fdi_rx_train_bits = FDI_LINK_TRAIN_PATTERN_2_CPT; -+ fdi_tx_train_bits = FDI_LINK_TRAIN_PATTERN_2; -+ fdi_iir_check_bits = FDI_RX_SYMBOL_LOCK; -+ } else { -+ gvt_vgpu_err("Invalid train pattern %d\n", train_pattern); -+ return -EINVAL; -+ } -+ -+ fdi_rx_check_bits = FDI_RX_ENABLE | fdi_rx_train_bits; -+ fdi_tx_check_bits = FDI_TX_ENABLE | fdi_tx_train_bits; -+ -+ /* If imr bit has been masked */ -+ if (vgpu_vreg_t(vgpu, fdi_rx_imr) & fdi_iir_check_bits) -+ return 0; -+ -+ if (((vgpu_vreg_t(vgpu, fdi_tx_ctl) & fdi_tx_check_bits) -+ == fdi_tx_check_bits) -+ && ((vgpu_vreg_t(vgpu, fdi_rx_ctl) & fdi_rx_check_bits) -+ == fdi_rx_check_bits)) -+ return 1; -+ else -+ return 0; -+} -+ -+#define INVALID_INDEX (~0U) -+ -+static unsigned int calc_index(unsigned int offset, unsigned int start, -+ unsigned int next, unsigned int end, i915_reg_t i915_end) -+{ -+ unsigned int range = next - start; -+ -+ if (!end) -+ end = i915_mmio_reg_offset(i915_end); -+ if (offset < start || offset > end) -+ return INVALID_INDEX; -+ offset -= start; -+ return offset / range; -+} -+ -+#define FDI_RX_CTL_TO_PIPE(offset) \ -+ calc_index(offset, _FDI_RXA_CTL, _FDI_RXB_CTL, 0, FDI_RX_CTL(PIPE_C)) -+ -+#define FDI_TX_CTL_TO_PIPE(offset) \ -+ calc_index(offset, _FDI_TXA_CTL, _FDI_TXB_CTL, 0, FDI_TX_CTL(PIPE_C)) -+ -+#define FDI_RX_IMR_TO_PIPE(offset) \ -+ calc_index(offset, _FDI_RXA_IMR, _FDI_RXB_IMR, 0, FDI_RX_IMR(PIPE_C)) -+ -+static int update_fdi_rx_iir_status(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ i915_reg_t fdi_rx_iir; -+ unsigned int index; -+ int ret; -+ -+ if (FDI_RX_CTL_TO_PIPE(offset) != INVALID_INDEX) -+ index = FDI_RX_CTL_TO_PIPE(offset); -+ else if (FDI_TX_CTL_TO_PIPE(offset) != INVALID_INDEX) -+ index = FDI_TX_CTL_TO_PIPE(offset); -+ else if (FDI_RX_IMR_TO_PIPE(offset) != INVALID_INDEX) -+ index = FDI_RX_IMR_TO_PIPE(offset); -+ else { -+ gvt_vgpu_err("Unsupport registers %x\n", offset); -+ return -EINVAL; -+ } -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ -+ fdi_rx_iir = FDI_RX_IIR(index); -+ -+ ret = check_fdi_rx_train_status(vgpu, index, FDI_LINK_TRAIN_PATTERN1); -+ if (ret < 0) -+ return ret; -+ if (ret) -+ vgpu_vreg_t(vgpu, fdi_rx_iir) |= FDI_RX_BIT_LOCK; -+ -+ ret = check_fdi_rx_train_status(vgpu, index, FDI_LINK_TRAIN_PATTERN2); -+ if (ret < 0) -+ return ret; -+ if (ret) -+ vgpu_vreg_t(vgpu, fdi_rx_iir) |= FDI_RX_SYMBOL_LOCK; -+ -+ if (offset == _FDI_RXA_CTL) -+ if (fdi_auto_training_started(vgpu)) -+ vgpu_vreg_t(vgpu, DP_TP_STATUS(PORT_E)) |= -+ DP_TP_STATUS_AUTOTRAIN_DONE; -+ return 0; -+} -+ -+#define DP_TP_CTL_TO_PORT(offset) \ -+ calc_index(offset, _DP_TP_CTL_A, _DP_TP_CTL_B, 0, DP_TP_CTL(PORT_E)) -+ -+static int dp_tp_ctl_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ i915_reg_t status_reg; -+ unsigned int index; -+ u32 data; -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ -+ index = DP_TP_CTL_TO_PORT(offset); -+ data = (vgpu_vreg(vgpu, offset) & GENMASK(10, 8)) >> 8; -+ if (data == 0x2) { -+ status_reg = DP_TP_STATUS(index); -+ vgpu_vreg_t(vgpu, status_reg) |= (1 << 25); -+ } -+ return 0; -+} -+ -+static int dp_tp_status_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 reg_val; -+ u32 sticky_mask; -+ -+ reg_val = *((u32 *)p_data); -+ sticky_mask = GENMASK(27, 26) | (1 << 24); -+ -+ vgpu_vreg(vgpu, offset) = (reg_val & ~sticky_mask) | -+ (vgpu_vreg(vgpu, offset) & sticky_mask); -+ vgpu_vreg(vgpu, offset) &= ~(reg_val & sticky_mask); -+ return 0; -+} -+ -+static int pch_adpa_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 data; -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ data = vgpu_vreg(vgpu, offset); -+ -+ if (data & ADPA_CRT_HOTPLUG_FORCE_TRIGGER) -+ vgpu_vreg(vgpu, offset) &= ~ADPA_CRT_HOTPLUG_FORCE_TRIGGER; -+ return 0; -+} -+ -+static int south_chicken2_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 data; -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ data = vgpu_vreg(vgpu, offset); -+ -+ if (data & FDI_MPHY_IOSFSB_RESET_CTL) -+ vgpu_vreg(vgpu, offset) |= FDI_MPHY_IOSFSB_RESET_STATUS; -+ else -+ vgpu_vreg(vgpu, offset) &= ~FDI_MPHY_IOSFSB_RESET_STATUS; -+ return 0; -+} -+ -+#define DSPSURF_TO_PIPE(offset) \ -+ calc_index(offset, _DSPASURF, _DSPBSURF, 0, DSPSURF(PIPE_C)) -+ -+static int pri_surf_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ u32 pipe = DSPSURF_TO_PIPE(offset); -+ int event = SKL_FLIP_EVENT(pipe, PLANE_PRIMARY); -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ vgpu_vreg_t(vgpu, DSPSURFLIVE(pipe)) = vgpu_vreg(vgpu, offset); -+ -+ vgpu_vreg_t(vgpu, PIPE_FLIPCOUNT_G4X(pipe))++; -+ -+ if (vgpu_vreg_t(vgpu, DSPCNTR(pipe)) & PLANE_CTL_ASYNC_FLIP) -+ intel_vgpu_trigger_virtual_event(vgpu, event); -+ else -+ set_bit(event, vgpu->irq.flip_done_event[pipe]); -+ -+ return 0; -+} -+ -+#define SPRSURF_TO_PIPE(offset) \ -+ calc_index(offset, _SPRA_SURF, _SPRB_SURF, 0, SPRSURF(PIPE_C)) -+ -+static int spr_surf_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 pipe = SPRSURF_TO_PIPE(offset); -+ int event = SKL_FLIP_EVENT(pipe, PLANE_SPRITE0); -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ vgpu_vreg_t(vgpu, SPRSURFLIVE(pipe)) = vgpu_vreg(vgpu, offset); -+ -+ if (vgpu_vreg_t(vgpu, SPRCTL(pipe)) & PLANE_CTL_ASYNC_FLIP) -+ intel_vgpu_trigger_virtual_event(vgpu, event); -+ else -+ set_bit(event, vgpu->irq.flip_done_event[pipe]); -+ -+ return 0; -+} -+ -+static int reg50080_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, -+ unsigned int bytes) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ enum pipe pipe = REG_50080_TO_PIPE(offset); -+ enum plane_id plane = REG_50080_TO_PLANE(offset); -+ int event = SKL_FLIP_EVENT(pipe, plane); -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ if (plane == PLANE_PRIMARY) { -+ vgpu_vreg_t(vgpu, DSPSURFLIVE(pipe)) = vgpu_vreg(vgpu, offset); -+ vgpu_vreg_t(vgpu, PIPE_FLIPCOUNT_G4X(pipe))++; -+ } else { -+ vgpu_vreg_t(vgpu, SPRSURFLIVE(pipe)) = vgpu_vreg(vgpu, offset); -+ } -+ -+ if ((vgpu_vreg(vgpu, offset) & REG50080_FLIP_TYPE_MASK) == REG50080_FLIP_TYPE_ASYNC) -+ intel_vgpu_trigger_virtual_event(vgpu, event); -+ else -+ set_bit(event, vgpu->irq.flip_done_event[pipe]); -+ -+ return 0; -+} -+ -+static int trigger_aux_channel_interrupt(struct intel_vgpu *vgpu, -+ unsigned int reg) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ enum intel_gvt_event_type event; -+ -+ if (reg == _DPA_AUX_CH_CTL) -+ event = AUX_CHANNEL_A; -+ else if (reg == _PCH_DPB_AUX_CH_CTL || reg == _DPB_AUX_CH_CTL) -+ event = AUX_CHANNEL_B; -+ else if (reg == _PCH_DPC_AUX_CH_CTL || reg == _DPC_AUX_CH_CTL) -+ event = AUX_CHANNEL_C; -+ else if (reg == _PCH_DPD_AUX_CH_CTL || reg == _DPD_AUX_CH_CTL) -+ event = AUX_CHANNEL_D; -+ else { -+ WARN_ON(true); -+ return -EINVAL; -+ } -+ -+ intel_vgpu_trigger_virtual_event(vgpu, event); -+ return 0; -+} -+ -+static int dp_aux_ch_ctl_trans_done(struct intel_vgpu *vgpu, u32 value, -+ unsigned int reg, int len, bool data_valid) -+{ -+ /* mark transaction done */ -+ value |= DP_AUX_CH_CTL_DONE; -+ value &= ~DP_AUX_CH_CTL_SEND_BUSY; -+ value &= ~DP_AUX_CH_CTL_RECEIVE_ERROR; -+ -+ if (data_valid) -+ value &= ~DP_AUX_CH_CTL_TIME_OUT_ERROR; -+ else -+ value |= DP_AUX_CH_CTL_TIME_OUT_ERROR; -+ -+ /* message size */ -+ value &= ~(0xf << 20); -+ value |= (len << 20); -+ vgpu_vreg(vgpu, reg) = value; -+ -+ if (value & DP_AUX_CH_CTL_INTERRUPT) -+ return trigger_aux_channel_interrupt(vgpu, reg); -+ return 0; -+} -+ -+static void dp_aux_ch_ctl_link_training(struct intel_vgpu_dpcd_data *dpcd, -+ u8 t) -+{ -+ if ((t & DPCD_TRAINING_PATTERN_SET_MASK) == DPCD_TRAINING_PATTERN_1) { -+ /* training pattern 1 for CR */ -+ /* set LANE0_CR_DONE, LANE1_CR_DONE */ -+ dpcd->data[DPCD_LANE0_1_STATUS] |= DPCD_LANES_CR_DONE; -+ /* set LANE2_CR_DONE, LANE3_CR_DONE */ -+ dpcd->data[DPCD_LANE2_3_STATUS] |= DPCD_LANES_CR_DONE; -+ } else if ((t & DPCD_TRAINING_PATTERN_SET_MASK) == -+ DPCD_TRAINING_PATTERN_2) { -+ /* training pattern 2 for EQ */ -+ /* Set CHANNEL_EQ_DONE and SYMBOL_LOCKED for Lane0_1 */ -+ dpcd->data[DPCD_LANE0_1_STATUS] |= DPCD_LANES_EQ_DONE; -+ dpcd->data[DPCD_LANE0_1_STATUS] |= DPCD_SYMBOL_LOCKED; -+ /* Set CHANNEL_EQ_DONE and SYMBOL_LOCKED for Lane2_3 */ -+ dpcd->data[DPCD_LANE2_3_STATUS] |= DPCD_LANES_EQ_DONE; -+ dpcd->data[DPCD_LANE2_3_STATUS] |= DPCD_SYMBOL_LOCKED; -+ /* set INTERLANE_ALIGN_DONE */ -+ dpcd->data[DPCD_LANE_ALIGN_STATUS_UPDATED] |= -+ DPCD_INTERLANE_ALIGN_DONE; -+ } else if ((t & DPCD_TRAINING_PATTERN_SET_MASK) == -+ DPCD_LINK_TRAINING_DISABLED) { -+ /* finish link training */ -+ /* set sink status as synchronized */ -+ dpcd->data[DPCD_SINK_STATUS] = DPCD_SINK_IN_SYNC; -+ } -+} -+ -+#define _REG_HSW_DP_AUX_CH_CTL(dp) \ -+ ((dp) ? (_PCH_DPB_AUX_CH_CTL + ((dp)-1)*0x100) : 0x64010) -+ -+#define _REG_SKL_DP_AUX_CH_CTL(dp) (0x64010 + (dp) * 0x100) -+ -+#define OFFSET_TO_DP_AUX_PORT(offset) (((offset) & 0xF00) >> 8) -+ -+#define dpy_is_valid_port(port) \ -+ (((port) >= PORT_A) && ((port) < I915_MAX_PORTS)) -+ -+static int dp_aux_ch_ctl_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ struct intel_vgpu_display *display = &vgpu->display; -+ int msg, addr, ctrl, op, len; -+ int port_index = OFFSET_TO_DP_AUX_PORT(offset); -+ struct intel_vgpu_dpcd_data *dpcd = NULL; -+ struct intel_vgpu_port *port = NULL; -+ u32 data; -+ -+ if (!dpy_is_valid_port(port_index)) { -+ gvt_vgpu_err("Unsupported DP port access!\n"); -+ return 0; -+ } -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ data = vgpu_vreg(vgpu, offset); -+ -+ if ((INTEL_GEN(vgpu->gvt->dev_priv) >= 9) -+ && offset != _REG_SKL_DP_AUX_CH_CTL(port_index)) { -+ /* SKL DPB/C/D aux ctl register changed */ -+ return 0; -+ } else if (IS_BROADWELL(vgpu->gvt->dev_priv) && -+ offset != _REG_HSW_DP_AUX_CH_CTL(port_index)) { -+ /* write to the data registers */ -+ return 0; -+ } -+ -+ if (!(data & DP_AUX_CH_CTL_SEND_BUSY)) { -+ /* just want to clear the sticky bits */ -+ vgpu_vreg(vgpu, offset) = 0; -+ return 0; -+ } -+ -+ port = &display->ports[port_index]; -+ dpcd = port->dpcd; -+ -+ /* read out message from DATA1 register */ -+ msg = vgpu_vreg(vgpu, offset + 4); -+ addr = (msg >> 8) & 0xffff; -+ ctrl = (msg >> 24) & 0xff; -+ len = msg & 0xff; -+ op = ctrl >> 4; -+ -+ if (op == GVT_AUX_NATIVE_WRITE) { -+ int t; -+ u8 buf[16]; -+ -+ if ((addr + len + 1) >= DPCD_SIZE) { -+ /* -+ * Write request exceeds what we supported, -+ * DCPD spec: When a Source Device is writing a DPCD -+ * address not supported by the Sink Device, the Sink -+ * Device shall reply with AUX NACK and “M” equal to -+ * zero. -+ */ -+ -+ /* NAK the write */ -+ vgpu_vreg(vgpu, offset + 4) = AUX_NATIVE_REPLY_NAK; -+ dp_aux_ch_ctl_trans_done(vgpu, data, offset, 2, true); -+ return 0; -+ } -+ -+ /* -+ * Write request format: Headr (command + address + size) occupies -+ * 4 bytes, followed by (len + 1) bytes of data. See details at -+ * intel_dp_aux_transfer(). -+ */ -+ if ((len + 1 + 4) > AUX_BURST_SIZE) { -+ gvt_vgpu_err("dp_aux_header: len %d is too large\n", len); -+ return -EINVAL; -+ } -+ -+ /* unpack data from vreg to buf */ -+ for (t = 0; t < 4; t++) { -+ u32 r = vgpu_vreg(vgpu, offset + 8 + t * 4); -+ -+ buf[t * 4] = (r >> 24) & 0xff; -+ buf[t * 4 + 1] = (r >> 16) & 0xff; -+ buf[t * 4 + 2] = (r >> 8) & 0xff; -+ buf[t * 4 + 3] = r & 0xff; -+ } -+ -+ /* write to virtual DPCD */ -+ if (dpcd && dpcd->data_valid) { -+ for (t = 0; t <= len; t++) { -+ int p = addr + t; -+ -+ dpcd->data[p] = buf[t]; -+ /* check for link training */ -+ if (p == DPCD_TRAINING_PATTERN_SET) -+ dp_aux_ch_ctl_link_training(dpcd, -+ buf[t]); -+ } -+ } -+ -+ /* ACK the write */ -+ vgpu_vreg(vgpu, offset + 4) = 0; -+ dp_aux_ch_ctl_trans_done(vgpu, data, offset, 1, -+ dpcd && dpcd->data_valid); -+ return 0; -+ } -+ -+ if (op == GVT_AUX_NATIVE_READ) { -+ int idx, i, ret = 0; -+ -+ if ((addr + len + 1) >= DPCD_SIZE) { -+ /* -+ * read request exceeds what we supported -+ * DPCD spec: A Sink Device receiving a Native AUX CH -+ * read request for an unsupported DPCD address must -+ * reply with an AUX ACK and read data set equal to -+ * zero instead of replying with AUX NACK. -+ */ -+ -+ /* ACK the READ*/ -+ vgpu_vreg(vgpu, offset + 4) = 0; -+ vgpu_vreg(vgpu, offset + 8) = 0; -+ vgpu_vreg(vgpu, offset + 12) = 0; -+ vgpu_vreg(vgpu, offset + 16) = 0; -+ vgpu_vreg(vgpu, offset + 20) = 0; -+ -+ dp_aux_ch_ctl_trans_done(vgpu, data, offset, len + 2, -+ true); -+ return 0; -+ } -+ -+ for (idx = 1; idx <= 5; idx++) { -+ /* clear the data registers */ -+ vgpu_vreg(vgpu, offset + 4 * idx) = 0; -+ } -+ -+ /* -+ * Read reply format: ACK (1 byte) plus (len + 1) bytes of data. -+ */ -+ if ((len + 2) > AUX_BURST_SIZE) { -+ gvt_vgpu_err("dp_aux_header: len %d is too large\n", len); -+ return -EINVAL; -+ } -+ -+ /* read from virtual DPCD to vreg */ -+ /* first 4 bytes: [ACK][addr][addr+1][addr+2] */ -+ if (dpcd && dpcd->data_valid) { -+ for (i = 1; i <= (len + 1); i++) { -+ int t; -+ -+ t = dpcd->data[addr + i - 1]; -+ t <<= (24 - 8 * (i % 4)); -+ ret |= t; -+ -+ if ((i % 4 == 3) || (i == (len + 1))) { -+ vgpu_vreg(vgpu, offset + -+ (i / 4 + 1) * 4) = ret; -+ ret = 0; -+ } -+ } -+ } -+ dp_aux_ch_ctl_trans_done(vgpu, data, offset, len + 2, -+ dpcd && dpcd->data_valid); -+ return 0; -+ } -+ -+ /* i2c transaction starts */ -+ intel_gvt_i2c_handle_aux_ch_write(vgpu, port_index, offset, p_data); -+ -+ if (data & DP_AUX_CH_CTL_INTERRUPT) -+ trigger_aux_channel_interrupt(vgpu, offset); -+ return 0; -+} -+ -+static int mbctl_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ *(u32 *)p_data &= (~GEN6_MBCTL_ENABLE_BOOT_FETCH); -+ write_vreg(vgpu, offset, p_data, bytes); -+ return 0; -+} -+ -+static int vga_control_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ bool vga_disable; -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ vga_disable = vgpu_vreg(vgpu, offset) & VGA_DISP_DISABLE; -+ -+ gvt_dbg_core("vgpu%d: %s VGA mode\n", vgpu->id, -+ vga_disable ? "Disable" : "Enable"); -+ return 0; -+} -+ -+static u32 read_virtual_sbi_register(struct intel_vgpu *vgpu, -+ unsigned int sbi_offset) -+{ -+ struct intel_vgpu_display *display = &vgpu->display; -+ int num = display->sbi.number; -+ int i; -+ -+ for (i = 0; i < num; ++i) -+ if (display->sbi.registers[i].offset == sbi_offset) -+ break; -+ -+ if (i == num) -+ return 0; -+ -+ return display->sbi.registers[i].value; -+} -+ -+static void write_virtual_sbi_register(struct intel_vgpu *vgpu, -+ unsigned int offset, u32 value) -+{ -+ struct intel_vgpu_display *display = &vgpu->display; -+ int num = display->sbi.number; -+ int i; -+ -+ for (i = 0; i < num; ++i) { -+ if (display->sbi.registers[i].offset == offset) -+ break; -+ } -+ -+ if (i == num) { -+ if (num == SBI_REG_MAX) { -+ gvt_vgpu_err("SBI caching meets maximum limits\n"); -+ return; -+ } -+ display->sbi.number++; -+ } -+ -+ display->sbi.registers[i].offset = offset; -+ display->sbi.registers[i].value = value; -+} -+ -+static int sbi_data_mmio_read(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ if (((vgpu_vreg_t(vgpu, SBI_CTL_STAT) & SBI_OPCODE_MASK) >> -+ SBI_OPCODE_SHIFT) == SBI_CMD_CRRD) { -+ unsigned int sbi_offset = (vgpu_vreg_t(vgpu, SBI_ADDR) & -+ SBI_ADDR_OFFSET_MASK) >> SBI_ADDR_OFFSET_SHIFT; -+ vgpu_vreg(vgpu, offset) = read_virtual_sbi_register(vgpu, -+ sbi_offset); -+ } -+ read_vreg(vgpu, offset, p_data, bytes); -+ return 0; -+} -+ -+static int sbi_ctl_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 data; -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ data = vgpu_vreg(vgpu, offset); -+ -+ data &= ~(SBI_STAT_MASK << SBI_STAT_SHIFT); -+ data |= SBI_READY; -+ -+ data &= ~(SBI_RESPONSE_MASK << SBI_RESPONSE_SHIFT); -+ data |= SBI_RESPONSE_SUCCESS; -+ -+ vgpu_vreg(vgpu, offset) = data; -+ -+ if (((vgpu_vreg_t(vgpu, SBI_CTL_STAT) & SBI_OPCODE_MASK) >> -+ SBI_OPCODE_SHIFT) == SBI_CMD_CRWR) { -+ unsigned int sbi_offset = (vgpu_vreg_t(vgpu, SBI_ADDR) & -+ SBI_ADDR_OFFSET_MASK) >> SBI_ADDR_OFFSET_SHIFT; -+ -+ write_virtual_sbi_register(vgpu, sbi_offset, -+ vgpu_vreg_t(vgpu, SBI_DATA)); -+ } -+ return 0; -+} -+ -+#define _vgtif_reg(x) \ -+ (VGT_PVINFO_PAGE + offsetof(struct vgt_if, x)) -+ -+static int pvinfo_mmio_read(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ bool invalid_read = false; -+ -+ read_vreg(vgpu, offset, p_data, bytes); -+ -+ switch (offset) { -+ case _vgtif_reg(magic) ... _vgtif_reg(vgt_id): -+ if (offset + bytes > _vgtif_reg(vgt_id) + 4) -+ invalid_read = true; -+ break; -+ case _vgtif_reg(avail_rs.mappable_gmadr.base) ... -+ _vgtif_reg(avail_rs.fence_num): -+ if (offset + bytes > -+ _vgtif_reg(avail_rs.fence_num) + 4) -+ invalid_read = true; -+ break; -+ case 0x78010: /* vgt_caps */ -+ case 0x7881c: -+ break; -+ default: -+ invalid_read = true; -+ break; -+ } -+ if (invalid_read) -+ gvt_vgpu_err("invalid pvinfo read: [%x:%x] = %x\n", -+ offset, bytes, *(u32 *)p_data); -+ vgpu->pv_notified = true; -+ return 0; -+} -+ -+static int handle_g2v_notification(struct intel_vgpu *vgpu, int notification) -+{ -+ enum intel_gvt_gtt_type root_entry_type = GTT_TYPE_PPGTT_ROOT_L4_ENTRY; -+ struct intel_vgpu_mm *mm; -+ u64 *pdps; -+ -+ pdps = (u64 *)&vgpu_vreg64_t(vgpu, vgtif_reg(pdp[0])); -+ -+ switch (notification) { -+ case VGT_G2V_PPGTT_L3_PAGE_TABLE_CREATE: -+ root_entry_type = GTT_TYPE_PPGTT_ROOT_L3_ENTRY; -+ /* fall through */ -+ case VGT_G2V_PPGTT_L4_PAGE_TABLE_CREATE: -+ mm = intel_vgpu_get_ppgtt_mm(vgpu, root_entry_type, pdps); -+ return PTR_ERR_OR_ZERO(mm); -+ case VGT_G2V_PPGTT_L3_PAGE_TABLE_DESTROY: -+ case VGT_G2V_PPGTT_L4_PAGE_TABLE_DESTROY: -+ return intel_vgpu_put_ppgtt_mm(vgpu, pdps); -+ case VGT_G2V_EXECLIST_CONTEXT_CREATE: -+ case VGT_G2V_EXECLIST_CONTEXT_DESTROY: -+ case 1: /* Remove this in guest driver. */ -+ break; -+ default: -+ gvt_vgpu_err("Invalid PV notification %d\n", notification); -+ } -+ return 0; -+} -+ -+static int send_display_ready_uevent(struct intel_vgpu *vgpu, int ready) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ struct kobject *kobj = &dev_priv->drm.primary->kdev->kobj; -+ char *env[3] = {NULL, NULL, NULL}; -+ char vmid_str[20]; -+ char display_ready_str[20]; -+ -+ snprintf(display_ready_str, 20, "GVT_DISPLAY_READY=%d", ready); -+ env[0] = display_ready_str; -+ -+ snprintf(vmid_str, 20, "VMID=%d", vgpu->id); -+ env[1] = vmid_str; -+ -+ return kobject_uevent_env(kobj, KOBJ_ADD, env); -+} -+ -+static int pvinfo_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 data = *(u32 *)p_data; -+ bool invalid_write = false; -+ -+ switch (offset) { -+ case _vgtif_reg(display_ready): -+ send_display_ready_uevent(vgpu, data ? 1 : 0); -+ break; -+ case _vgtif_reg(g2v_notify): -+ handle_g2v_notification(vgpu, data); -+ break; -+ /* add xhot and yhot to handled list to avoid error log */ -+ case _vgtif_reg(cursor_x_hot): -+ case _vgtif_reg(cursor_y_hot): -+ case _vgtif_reg(pdp[0].lo): -+ case _vgtif_reg(pdp[0].hi): -+ case _vgtif_reg(pdp[1].lo): -+ case _vgtif_reg(pdp[1].hi): -+ case _vgtif_reg(pdp[2].lo): -+ case _vgtif_reg(pdp[2].hi): -+ case _vgtif_reg(pdp[3].lo): -+ case _vgtif_reg(pdp[3].hi): -+ case _vgtif_reg(execlist_context_descriptor_lo): -+ case _vgtif_reg(execlist_context_descriptor_hi): -+ break; -+ case _vgtif_reg(rsv5[0])..._vgtif_reg(rsv5[3]): -+ invalid_write = true; -+ enter_failsafe_mode(vgpu, GVT_FAILSAFE_INSUFFICIENT_RESOURCE); -+ break; -+ default: -+ invalid_write = true; -+ gvt_vgpu_err("invalid pvinfo write offset %x bytes %x data %x\n", -+ offset, bytes, data); -+ break; -+ } -+ -+ if (!invalid_write) -+ write_vreg(vgpu, offset, p_data, bytes); -+ -+ return 0; -+} -+ -+static int pf_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 val = *(u32 *)p_data; -+ -+ if ((offset == _PS_1A_CTRL || offset == _PS_2A_CTRL || -+ offset == _PS_1B_CTRL || offset == _PS_2B_CTRL || -+ offset == _PS_1C_CTRL) && (val & PS_PLANE_SEL_MASK) != 0) { -+ WARN_ONCE(true, "VM(%d): guest is trying to scaling a plane\n", -+ vgpu->id); -+ return 0; -+ } -+ -+ return intel_vgpu_default_mmio_write(vgpu, offset, p_data, bytes); -+} -+ -+static int power_well_ctl_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ write_vreg(vgpu, offset, p_data, bytes); -+ -+ if (vgpu_vreg(vgpu, offset) & -+ HSW_PWR_WELL_CTL_REQ(HSW_PW_CTL_IDX_GLOBAL)) -+ vgpu_vreg(vgpu, offset) |= -+ HSW_PWR_WELL_CTL_STATE(HSW_PW_CTL_IDX_GLOBAL); -+ else -+ vgpu_vreg(vgpu, offset) &= -+ ~HSW_PWR_WELL_CTL_STATE(HSW_PW_CTL_IDX_GLOBAL); -+ return 0; -+} -+ -+static int gen9_dbuf_ctl_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ write_vreg(vgpu, offset, p_data, bytes); -+ -+ if (vgpu_vreg(vgpu, offset) & DBUF_POWER_REQUEST) -+ vgpu_vreg(vgpu, offset) |= DBUF_POWER_STATE; -+ else -+ vgpu_vreg(vgpu, offset) &= ~DBUF_POWER_STATE; -+ -+ return 0; -+} -+ -+static int fpga_dbg_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ write_vreg(vgpu, offset, p_data, bytes); -+ -+ if (vgpu_vreg(vgpu, offset) & FPGA_DBG_RM_NOCLAIM) -+ vgpu_vreg(vgpu, offset) &= ~FPGA_DBG_RM_NOCLAIM; -+ return 0; -+} -+ -+static int dma_ctrl_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 mode; -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ mode = vgpu_vreg(vgpu, offset); -+ -+ if (GFX_MODE_BIT_SET_IN_MASK(mode, START_DMA)) { -+ WARN_ONCE(1, "VM(%d): iGVT-g doesn't support GuC\n", -+ vgpu->id); -+ return 0; -+ } -+ -+ return 0; -+} -+ -+static int gen9_trtte_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 trtte = *(u32 *)p_data; -+ -+ if ((trtte & 1) && (trtte & (1 << 1)) == 0) { -+ WARN(1, "VM(%d): Use physical address for TRTT!\n", -+ vgpu->id); -+ return -EINVAL; -+ } -+ write_vreg(vgpu, offset, p_data, bytes); -+ -+ return 0; -+} -+ -+static int gen9_trtt_chicken_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ write_vreg(vgpu, offset, p_data, bytes); -+ return 0; -+} -+ -+static int dpll_status_read(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 v = 0; -+ -+ if (vgpu_vreg(vgpu, 0x46010) & (1 << 31)) -+ v |= (1 << 0); -+ -+ if (vgpu_vreg(vgpu, 0x46014) & (1 << 31)) -+ v |= (1 << 8); -+ -+ if (vgpu_vreg(vgpu, 0x46040) & (1 << 31)) -+ v |= (1 << 16); -+ -+ if (vgpu_vreg(vgpu, 0x46060) & (1 << 31)) -+ v |= (1 << 24); -+ -+ vgpu_vreg(vgpu, offset) = v; -+ -+ return intel_vgpu_default_mmio_read(vgpu, offset, p_data, bytes); -+} -+ -+static int mailbox_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 value = *(u32 *)p_data; -+ u32 cmd = value & 0xff; -+ u32 *data0 = &vgpu_vreg_t(vgpu, GEN6_PCODE_DATA); -+ -+ switch (cmd) { -+ case GEN9_PCODE_READ_MEM_LATENCY: -+ if (IS_SKYLAKE(vgpu->gvt->dev_priv) -+ || IS_KABYLAKE(vgpu->gvt->dev_priv) -+ || IS_COFFEELAKE(vgpu->gvt->dev_priv)) { -+ /** -+ * "Read memory latency" command on gen9. -+ * Below memory latency values are read -+ * from skylake platform. -+ */ -+ if (!*data0) -+ *data0 = 0x1e1a1100; -+ else -+ *data0 = 0x61514b3d; -+ } else if (IS_BROXTON(vgpu->gvt->dev_priv)) { -+ /** -+ * "Read memory latency" command on gen9. -+ * Below memory latency values are read -+ * from Broxton MRB. -+ */ -+ if (!*data0) -+ *data0 = 0x16080707; -+ else -+ *data0 = 0x16161616; -+ } -+ break; -+ case SKL_PCODE_CDCLK_CONTROL: -+ if (IS_SKYLAKE(vgpu->gvt->dev_priv) -+ || IS_KABYLAKE(vgpu->gvt->dev_priv) -+ || IS_COFFEELAKE(vgpu->gvt->dev_priv)) -+ *data0 = SKL_CDCLK_READY_FOR_CHANGE; -+ break; -+ case GEN6_PCODE_READ_RC6VIDS: -+ *data0 |= 0x1; -+ break; -+ } -+ -+ gvt_dbg_core("VM(%d) write %x to mailbox, return data0 %x\n", -+ vgpu->id, value, *data0); -+ /** -+ * PCODE_READY clear means ready for pcode read/write, -+ * PCODE_ERROR_MASK clear means no error happened. In GVT-g we -+ * always emulate as pcode read/write success and ready for access -+ * anytime, since we don't touch real physical registers here. -+ */ -+ value &= ~(GEN6_PCODE_READY | GEN6_PCODE_ERROR_MASK); -+ return intel_vgpu_default_mmio_write(vgpu, offset, &value, bytes); -+} -+ -+static int hws_pga_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 value = *(u32 *)p_data; -+ int ring_id = intel_gvt_render_mmio_to_ring_id(vgpu->gvt, offset); -+ -+ if (!intel_gvt_ggtt_validate_range(vgpu, value, I915_GTT_PAGE_SIZE)) { -+ gvt_vgpu_err("write invalid HWSP address, reg:0x%x, value:0x%x\n", -+ offset, value); -+ return -EINVAL; -+ } -+ /* -+ * Need to emulate all the HWSP register write to ensure host can -+ * update the VM CSB status correctly. Here listed registers can -+ * support BDW, SKL or other platforms with same HWSP registers. -+ */ -+ if (unlikely(ring_id < 0 || ring_id >= I915_NUM_ENGINES)) { -+ gvt_vgpu_err("access unknown hardware status page register:0x%x\n", -+ offset); -+ return -EINVAL; -+ } -+ vgpu->hws_pga[ring_id] = value; -+ gvt_dbg_mmio("VM(%d) write: 0x%x to HWSP: 0x%x\n", -+ vgpu->id, value, offset); -+ -+ return intel_vgpu_default_mmio_write(vgpu, offset, &value, bytes); -+} -+ -+static int skl_power_well_ctl_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 v = *(u32 *)p_data; -+ -+ if (IS_BROXTON(vgpu->gvt->dev_priv)) -+ v &= (1 << 31) | (1 << 29); -+ else -+ v &= (1 << 31) | (1 << 29) | (1 << 9) | -+ (1 << 7) | (1 << 5) | (1 << 3) | (1 << 1); -+ v |= (v >> 1); -+ -+ return intel_vgpu_default_mmio_write(vgpu, offset, &v, bytes); -+} -+ -+static int skl_lcpll_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 v = *(u32 *)p_data; -+ -+ /* other bits are MBZ. */ -+ v &= (1 << 31) | (1 << 30); -+ v & (1 << 31) ? (v |= (1 << 30)) : (v &= ~(1 << 30)); -+ -+ vgpu_vreg(vgpu, offset) = v; -+ -+ return 0; -+} -+ -+static int bxt_de_pll_enable_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 v = *(u32 *)p_data; -+ -+ if (v & BXT_DE_PLL_PLL_ENABLE) -+ v |= BXT_DE_PLL_LOCK; -+ -+ vgpu_vreg(vgpu, offset) = v; -+ -+ return 0; -+} -+ -+static int bxt_port_pll_enable_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 v = *(u32 *)p_data; -+ -+ if (v & PORT_PLL_ENABLE) -+ v |= PORT_PLL_LOCK; -+ -+ vgpu_vreg(vgpu, offset) = v; -+ -+ return 0; -+} -+ -+static int bxt_phy_ctl_family_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 v = *(u32 *)p_data; -+ u32 data = v & COMMON_RESET_DIS ? BXT_PHY_LANE_ENABLED : 0; -+ -+ switch (offset) { -+ case _PHY_CTL_FAMILY_EDP: -+ vgpu_vreg(vgpu, _BXT_PHY_CTL_DDI_A) = data; -+ break; -+ case _PHY_CTL_FAMILY_DDI: -+ vgpu_vreg(vgpu, _BXT_PHY_CTL_DDI_B) = data; -+ vgpu_vreg(vgpu, _BXT_PHY_CTL_DDI_C) = data; -+ break; -+ } -+ -+ vgpu_vreg(vgpu, offset) = v; -+ -+ return 0; -+} -+ -+static int bxt_port_tx_dw3_read(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 v = vgpu_vreg(vgpu, offset); -+ -+ v &= ~UNIQUE_TRANGE_EN_METHOD; -+ -+ vgpu_vreg(vgpu, offset) = v; -+ -+ return intel_vgpu_default_mmio_read(vgpu, offset, p_data, bytes); -+} -+ -+static int bxt_pcs_dw12_grp_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 v = *(u32 *)p_data; -+ -+ if (offset == _PORT_PCS_DW12_GRP_A || offset == _PORT_PCS_DW12_GRP_B) { -+ vgpu_vreg(vgpu, offset - 0x600) = v; -+ vgpu_vreg(vgpu, offset - 0x800) = v; -+ } else { -+ vgpu_vreg(vgpu, offset - 0x400) = v; -+ vgpu_vreg(vgpu, offset - 0x600) = v; -+ } -+ -+ vgpu_vreg(vgpu, offset) = v; -+ -+ return 0; -+} -+ -+static int bxt_gt_disp_pwron_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 v = *(u32 *)p_data; -+ -+ if (v & BIT(0)) { -+ vgpu_vreg_t(vgpu, BXT_PORT_CL1CM_DW0(DPIO_PHY0)) &= -+ ~PHY_RESERVED; -+ vgpu_vreg_t(vgpu, BXT_PORT_CL1CM_DW0(DPIO_PHY0)) |= -+ PHY_POWER_GOOD; -+ } -+ -+ if (v & BIT(1)) { -+ vgpu_vreg_t(vgpu, BXT_PORT_CL1CM_DW0(DPIO_PHY1)) &= -+ ~PHY_RESERVED; -+ vgpu_vreg_t(vgpu, BXT_PORT_CL1CM_DW0(DPIO_PHY1)) |= -+ PHY_POWER_GOOD; -+ } -+ -+ -+ vgpu_vreg(vgpu, offset) = v; -+ -+ return 0; -+} -+ -+static int edp_psr_imr_iir_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ vgpu_vreg(vgpu, offset) = 0; -+ return 0; -+} -+ -+static int mmio_read_from_hw(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ int ring_id; -+ u32 ring_base; -+ -+ ring_id = intel_gvt_render_mmio_to_ring_id(gvt, offset); -+ /** -+ * Read HW reg in following case -+ * a. the offset isn't a ring mmio -+ * b. the offset's ring is running on hw. -+ * c. the offset is ring time stamp mmio -+ */ -+ if (ring_id >= 0) -+ ring_base = dev_priv->engine[ring_id]->mmio_base; -+ -+ if (ring_id < 0 || vgpu == gvt->scheduler.engine_owner[ring_id] || -+ offset == i915_mmio_reg_offset(RING_TIMESTAMP(ring_base)) || -+ offset == i915_mmio_reg_offset(RING_TIMESTAMP_UDW(ring_base))) { -+ mmio_hw_access_pre(dev_priv); -+ vgpu_vreg(vgpu, offset) = I915_READ(_MMIO(offset)); -+ mmio_hw_access_post(dev_priv); -+ } -+ -+ return intel_vgpu_default_mmio_read(vgpu, offset, p_data, bytes); -+} -+ -+static int elsp_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ int ring_id = intel_gvt_render_mmio_to_ring_id(vgpu->gvt, offset); -+ struct intel_vgpu_execlist *execlist; -+ u32 data = *(u32 *)p_data; -+ int ret = 0; -+ -+ if (WARN_ON(ring_id < 0 || ring_id >= I915_NUM_ENGINES)) -+ return -EINVAL; -+ -+ execlist = &vgpu->submission.execlist[ring_id]; -+ -+ execlist->elsp_dwords.data[3 - execlist->elsp_dwords.index] = data; -+ if (execlist->elsp_dwords.index == 3) { -+ ret = intel_vgpu_submit_execlist(vgpu, ring_id); -+ if(ret) -+ gvt_vgpu_err("fail submit workload on ring %d\n", -+ ring_id); -+ } -+ -+ ++execlist->elsp_dwords.index; -+ execlist->elsp_dwords.index &= 0x3; -+ return ret; -+} -+ -+static int ring_mode_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 data = *(u32 *)p_data; -+ int ring_id = intel_gvt_render_mmio_to_ring_id(vgpu->gvt, offset); -+ bool enable_execlist; -+ int ret; -+ -+ (*(u32 *)p_data) &= ~_MASKED_BIT_ENABLE(1); -+ if (IS_COFFEELAKE(vgpu->gvt->dev_priv)) -+ (*(u32 *)p_data) &= ~_MASKED_BIT_ENABLE(2); -+ write_vreg(vgpu, offset, p_data, bytes); -+ -+ if (data & _MASKED_BIT_ENABLE(1)) { -+ enter_failsafe_mode(vgpu, GVT_FAILSAFE_UNSUPPORTED_GUEST); -+ return 0; -+ } -+ -+ if (IS_COFFEELAKE(vgpu->gvt->dev_priv) && -+ data & _MASKED_BIT_ENABLE(2)) { -+ enter_failsafe_mode(vgpu, GVT_FAILSAFE_UNSUPPORTED_GUEST); -+ return 0; -+ } -+ -+ /* when PPGTT mode enabled, we will check if guest has called -+ * pvinfo, if not, we will treat this guest as non-gvtg-aware -+ * guest, and stop emulating its cfg space, mmio, gtt, etc. -+ */ -+ if (((data & _MASKED_BIT_ENABLE(GFX_PPGTT_ENABLE)) || -+ (data & _MASKED_BIT_ENABLE(GFX_RUN_LIST_ENABLE))) -+ && !vgpu->pv_notified) { -+ enter_failsafe_mode(vgpu, GVT_FAILSAFE_UNSUPPORTED_GUEST); -+ return 0; -+ } -+ if ((data & _MASKED_BIT_ENABLE(GFX_RUN_LIST_ENABLE)) -+ || (data & _MASKED_BIT_DISABLE(GFX_RUN_LIST_ENABLE))) { -+ enable_execlist = !!(data & GFX_RUN_LIST_ENABLE); -+ -+ gvt_dbg_core("EXECLIST %s on ring %d\n", -+ (enable_execlist ? "enabling" : "disabling"), -+ ring_id); -+ -+ if (!enable_execlist) -+ return 0; -+ -+ ret = intel_vgpu_select_submission_ops(vgpu, -+ BIT(ring_id), -+ INTEL_VGPU_EXECLIST_SUBMISSION); -+ if (ret) -+ return ret; -+ -+ intel_vgpu_start_schedule(vgpu); -+ } -+ return 0; -+} -+ -+static int gvt_reg_tlb_control_handler(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ unsigned int id = 0; -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ vgpu_vreg(vgpu, offset) = 0; -+ -+ switch (offset) { -+ case 0x4260: -+ id = RCS0; -+ break; -+ case 0x4264: -+ id = VCS0; -+ break; -+ case 0x4268: -+ id = VCS1; -+ break; -+ case 0x426c: -+ id = BCS0; -+ break; -+ case 0x4270: -+ id = VECS0; -+ break; -+ default: -+ return -EINVAL; -+ } -+ set_bit(id, (void *)vgpu->submission.tlb_handle_pending); -+ -+ return 0; -+} -+ -+static int ring_reset_ctl_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, unsigned int bytes) -+{ -+ u32 data; -+ -+ write_vreg(vgpu, offset, p_data, bytes); -+ data = vgpu_vreg(vgpu, offset); -+ -+ if (data & _MASKED_BIT_ENABLE(RESET_CTL_REQUEST_RESET)) -+ data |= RESET_CTL_READY_TO_RESET; -+ else if (data & _MASKED_BIT_DISABLE(RESET_CTL_REQUEST_RESET)) -+ data &= ~RESET_CTL_READY_TO_RESET; -+ -+ vgpu_vreg(vgpu, offset) = data; -+ return 0; -+} -+ -+static int csfe_chicken1_mmio_write(struct intel_vgpu *vgpu, -+ unsigned int offset, void *p_data, -+ unsigned int bytes) -+{ -+ u32 data = *(u32 *)p_data; -+ -+ (*(u32 *)p_data) &= ~_MASKED_BIT_ENABLE(0x18); -+ write_vreg(vgpu, offset, p_data, bytes); -+ -+ if (data & _MASKED_BIT_ENABLE(0x10) || data & _MASKED_BIT_ENABLE(0x8)) -+ enter_failsafe_mode(vgpu, GVT_FAILSAFE_UNSUPPORTED_GUEST); -+ -+ return 0; -+} -+ -+#define MMIO_F(reg, s, f, am, rm, d, r, w) do { \ -+ ret = new_mmio_info(gvt, i915_mmio_reg_offset(reg), \ -+ f, s, am, rm, d, r, w); \ -+ if (ret) \ -+ return ret; \ -+} while (0) -+ -+#define MMIO_D(reg, d) \ -+ MMIO_F(reg, 4, 0, 0, 0, d, NULL, NULL) -+ -+#define MMIO_DH(reg, d, r, w) \ -+ MMIO_F(reg, 4, 0, 0, 0, d, r, w) -+ -+#define MMIO_DFH(reg, d, f, r, w) \ -+ MMIO_F(reg, 4, f, 0, 0, d, r, w) -+ -+#define MMIO_GM(reg, d, r, w) \ -+ MMIO_F(reg, 4, F_GMADR, 0xFFFFF000, 0, d, r, w) -+ -+#define MMIO_GM_RDR(reg, d, r, w) \ -+ MMIO_F(reg, 4, F_GMADR | F_CMD_ACCESS, 0xFFFFF000, 0, d, r, w) -+ -+#define MMIO_RO(reg, d, f, rm, r, w) \ -+ MMIO_F(reg, 4, F_RO | f, 0, rm, d, r, w) -+ -+#define MMIO_RING_F(prefix, s, f, am, rm, d, r, w) do { \ -+ MMIO_F(prefix(RENDER_RING_BASE), s, f, am, rm, d, r, w); \ -+ MMIO_F(prefix(BLT_RING_BASE), s, f, am, rm, d, r, w); \ -+ MMIO_F(prefix(GEN6_BSD_RING_BASE), s, f, am, rm, d, r, w); \ -+ MMIO_F(prefix(VEBOX_RING_BASE), s, f, am, rm, d, r, w); \ -+ if (HAS_ENGINE(dev_priv, VCS1)) \ -+ MMIO_F(prefix(GEN8_BSD2_RING_BASE), s, f, am, rm, d, r, w); \ -+} while (0) -+ -+#define MMIO_RING_D(prefix, d) \ -+ MMIO_RING_F(prefix, 4, 0, 0, 0, d, NULL, NULL) -+ -+#define MMIO_RING_DFH(prefix, d, f, r, w) \ -+ MMIO_RING_F(prefix, 4, f, 0, 0, d, r, w) -+ -+#define MMIO_RING_GM(prefix, d, r, w) \ -+ MMIO_RING_F(prefix, 4, F_GMADR, 0xFFFF0000, 0, d, r, w) -+ -+#define MMIO_RING_GM_RDR(prefix, d, r, w) \ -+ MMIO_RING_F(prefix, 4, F_GMADR | F_CMD_ACCESS, 0xFFFF0000, 0, d, r, w) -+ -+#define MMIO_RING_RO(prefix, d, f, rm, r, w) \ -+ MMIO_RING_F(prefix, 4, F_RO | f, 0, rm, d, r, w) -+ -+static int init_generic_mmio_info(struct intel_gvt *gvt) -+{ -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ int ret; -+ -+ MMIO_RING_DFH(RING_IMR, D_ALL, F_CMD_ACCESS, NULL, -+ intel_vgpu_reg_imr_handler); -+ -+ MMIO_DFH(SDEIMR, D_ALL, 0, NULL, intel_vgpu_reg_imr_handler); -+ MMIO_DFH(SDEIER, D_ALL, 0, NULL, intel_vgpu_reg_ier_handler); -+ MMIO_DFH(SDEIIR, D_ALL, 0, NULL, intel_vgpu_reg_iir_handler); -+ MMIO_D(SDEISR, D_ALL); -+ -+ MMIO_RING_DFH(RING_HWSTAM, D_ALL, F_CMD_ACCESS, NULL, NULL); -+ -+ MMIO_DH(GEN8_GAMW_ECO_DEV_RW_IA, D_BDW_PLUS, NULL, -+ gamw_echo_dev_rw_ia_write); -+ -+ MMIO_GM_RDR(BSD_HWS_PGA_GEN7, D_ALL, NULL, NULL); -+ MMIO_GM_RDR(BLT_HWS_PGA_GEN7, D_ALL, NULL, NULL); -+ MMIO_GM_RDR(VEBOX_HWS_PGA_GEN7, D_ALL, NULL, NULL); -+ -+#define RING_REG(base) _MMIO((base) + 0x28) -+ MMIO_RING_DFH(RING_REG, D_ALL, F_CMD_ACCESS, NULL, NULL); -+#undef RING_REG -+ -+#define RING_REG(base) _MMIO((base) + 0x134) -+ MMIO_RING_DFH(RING_REG, D_ALL, F_CMD_ACCESS, NULL, NULL); -+#undef RING_REG -+ -+#define RING_REG(base) _MMIO((base) + 0x6c) -+ MMIO_RING_DFH(RING_REG, D_ALL, 0, mmio_read_from_hw, NULL); -+#undef RING_REG -+ MMIO_DH(GEN7_SC_INSTDONE, D_BDW_PLUS, mmio_read_from_hw, NULL); -+ -+ MMIO_GM_RDR(_MMIO(0x2148), D_ALL, NULL, NULL); -+ MMIO_GM_RDR(CCID(RENDER_RING_BASE), D_ALL, NULL, NULL); -+ MMIO_GM_RDR(_MMIO(0x12198), D_ALL, NULL, NULL); -+ MMIO_D(GEN7_CXT_SIZE, D_ALL); -+ -+ MMIO_RING_DFH(RING_TAIL, D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_RING_DFH(RING_HEAD, D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_RING_DFH(RING_CTL, D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_RING_DFH(RING_ACTHD, D_ALL, F_CMD_ACCESS, mmio_read_from_hw, NULL); -+ MMIO_RING_GM_RDR(RING_START, D_ALL, NULL, NULL); -+ -+ /* RING MODE */ -+#define RING_REG(base) _MMIO((base) + 0x29c) -+ MMIO_RING_DFH(RING_REG, D_ALL, F_MODE_MASK | F_CMD_ACCESS, NULL, -+ ring_mode_mmio_write); -+#undef RING_REG -+ -+ MMIO_RING_DFH(RING_MI_MODE, D_ALL, F_MODE_MASK | F_CMD_ACCESS, -+ NULL, NULL); -+ MMIO_RING_DFH(RING_INSTPM, D_ALL, F_MODE_MASK | F_CMD_ACCESS, -+ NULL, NULL); -+ MMIO_RING_DFH(RING_TIMESTAMP, D_ALL, F_CMD_ACCESS, -+ mmio_read_from_hw, NULL); -+ MMIO_RING_DFH(RING_TIMESTAMP_UDW, D_ALL, F_CMD_ACCESS, -+ mmio_read_from_hw, NULL); -+ -+ MMIO_DFH(GEN7_GT_MODE, D_ALL, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(CACHE_MODE_0_GEN7, D_ALL, F_MODE_MASK | F_CMD_ACCESS, -+ NULL, NULL); -+ MMIO_DFH(CACHE_MODE_1, D_ALL, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(CACHE_MODE_0, D_ALL, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x2124), D_ALL, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ -+ MMIO_DFH(_MMIO(0x20dc), D_ALL, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_3D_CHICKEN3, D_ALL, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x2088), D_ALL, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(FF_SLICE_CS_CHICKEN2, D_ALL, -+ F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x2470), D_ALL, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(GAM_ECOCHK, D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(GEN7_COMMON_SLICE_CHICKEN1, D_ALL, F_MODE_MASK | F_CMD_ACCESS, -+ NULL, NULL); -+ MMIO_DFH(COMMON_SLICE_CHICKEN2, D_ALL, F_MODE_MASK | F_CMD_ACCESS, -+ NULL, NULL); -+ MMIO_DFH(_MMIO(0x9030), D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x20a0), D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x2420), D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x2430), D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x2434), D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x2438), D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x243c), D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x7018), D_ALL, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(HALF_SLICE_CHICKEN3, D_ALL, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(GEN7_HALF_SLICE_CHICKEN1, D_ALL, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ -+ /* display */ -+ MMIO_F(_MMIO(0x60220), 0x20, 0, 0, 0, D_ALL, NULL, NULL); -+ MMIO_D(_MMIO(0x602a0), D_ALL); -+ -+ MMIO_D(_MMIO(0x65050), D_ALL); -+ MMIO_D(_MMIO(0x650b4), D_ALL); -+ -+ MMIO_D(_MMIO(0xc4040), D_ALL); -+ MMIO_D(DERRMR, D_ALL); -+ -+ MMIO_D(PIPEDSL(PIPE_A), D_ALL); -+ MMIO_D(PIPEDSL(PIPE_B), D_ALL); -+ MMIO_D(PIPEDSL(PIPE_C), D_ALL); -+ MMIO_D(PIPEDSL(_PIPE_EDP), D_ALL); -+ -+ MMIO_DH(PIPECONF(PIPE_A), D_ALL, NULL, pipeconf_mmio_write); -+ MMIO_DH(PIPECONF(PIPE_B), D_ALL, NULL, pipeconf_mmio_write); -+ MMIO_DH(PIPECONF(PIPE_C), D_ALL, NULL, pipeconf_mmio_write); -+ MMIO_DH(PIPECONF(_PIPE_EDP), D_ALL, NULL, pipeconf_mmio_write); -+ -+ MMIO_D(PIPESTAT(PIPE_A), D_ALL); -+ MMIO_D(PIPESTAT(PIPE_B), D_ALL); -+ MMIO_D(PIPESTAT(PIPE_C), D_ALL); -+ MMIO_D(PIPESTAT(_PIPE_EDP), D_ALL); -+ -+ MMIO_D(PIPE_FLIPCOUNT_G4X(PIPE_A), D_ALL); -+ MMIO_D(PIPE_FLIPCOUNT_G4X(PIPE_B), D_ALL); -+ MMIO_D(PIPE_FLIPCOUNT_G4X(PIPE_C), D_ALL); -+ MMIO_D(PIPE_FLIPCOUNT_G4X(_PIPE_EDP), D_ALL); -+ -+ MMIO_D(PIPE_FRMCOUNT_G4X(PIPE_A), D_ALL); -+ MMIO_D(PIPE_FRMCOUNT_G4X(PIPE_B), D_ALL); -+ MMIO_D(PIPE_FRMCOUNT_G4X(PIPE_C), D_ALL); -+ MMIO_D(PIPE_FRMCOUNT_G4X(_PIPE_EDP), D_ALL); -+ -+ MMIO_D(CURCNTR(PIPE_A), D_ALL); -+ MMIO_D(CURCNTR(PIPE_B), D_ALL); -+ MMIO_D(CURCNTR(PIPE_C), D_ALL); -+ -+ MMIO_D(CURPOS(PIPE_A), D_ALL); -+ MMIO_D(CURPOS(PIPE_B), D_ALL); -+ MMIO_D(CURPOS(PIPE_C), D_ALL); -+ -+ MMIO_D(CURBASE(PIPE_A), D_ALL); -+ MMIO_D(CURBASE(PIPE_B), D_ALL); -+ MMIO_D(CURBASE(PIPE_C), D_ALL); -+ -+ MMIO_D(CUR_FBC_CTL(PIPE_A), D_ALL); -+ MMIO_D(CUR_FBC_CTL(PIPE_B), D_ALL); -+ MMIO_D(CUR_FBC_CTL(PIPE_C), D_ALL); -+ -+ MMIO_D(_MMIO(0x700ac), D_ALL); -+ MMIO_D(_MMIO(0x710ac), D_ALL); -+ MMIO_D(_MMIO(0x720ac), D_ALL); -+ -+ MMIO_D(_MMIO(0x70090), D_ALL); -+ MMIO_D(_MMIO(0x70094), D_ALL); -+ MMIO_D(_MMIO(0x70098), D_ALL); -+ MMIO_D(_MMIO(0x7009c), D_ALL); -+ -+ MMIO_D(DSPCNTR(PIPE_A), D_ALL); -+ MMIO_D(DSPADDR(PIPE_A), D_ALL); -+ MMIO_D(DSPSTRIDE(PIPE_A), D_ALL); -+ MMIO_D(DSPPOS(PIPE_A), D_ALL); -+ MMIO_D(DSPSIZE(PIPE_A), D_ALL); -+ MMIO_DH(DSPSURF(PIPE_A), D_ALL, NULL, pri_surf_mmio_write); -+ MMIO_D(DSPOFFSET(PIPE_A), D_ALL); -+ MMIO_D(DSPSURFLIVE(PIPE_A), D_ALL); -+ MMIO_DH(REG_50080(PIPE_A, PLANE_PRIMARY), D_ALL, NULL, -+ reg50080_mmio_write); -+ -+ MMIO_D(DSPCNTR(PIPE_B), D_ALL); -+ MMIO_D(DSPADDR(PIPE_B), D_ALL); -+ MMIO_D(DSPSTRIDE(PIPE_B), D_ALL); -+ MMIO_D(DSPPOS(PIPE_B), D_ALL); -+ MMIO_D(DSPSIZE(PIPE_B), D_ALL); -+ MMIO_DH(DSPSURF(PIPE_B), D_ALL, NULL, pri_surf_mmio_write); -+ MMIO_D(DSPOFFSET(PIPE_B), D_ALL); -+ MMIO_D(DSPSURFLIVE(PIPE_B), D_ALL); -+ MMIO_DH(REG_50080(PIPE_B, PLANE_PRIMARY), D_ALL, NULL, -+ reg50080_mmio_write); -+ -+ MMIO_D(DSPCNTR(PIPE_C), D_ALL); -+ MMIO_D(DSPADDR(PIPE_C), D_ALL); -+ MMIO_D(DSPSTRIDE(PIPE_C), D_ALL); -+ MMIO_D(DSPPOS(PIPE_C), D_ALL); -+ MMIO_D(DSPSIZE(PIPE_C), D_ALL); -+ MMIO_DH(DSPSURF(PIPE_C), D_ALL, NULL, pri_surf_mmio_write); -+ MMIO_D(DSPOFFSET(PIPE_C), D_ALL); -+ MMIO_D(DSPSURFLIVE(PIPE_C), D_ALL); -+ MMIO_DH(REG_50080(PIPE_C, PLANE_PRIMARY), D_ALL, NULL, -+ reg50080_mmio_write); -+ -+ MMIO_D(SPRCTL(PIPE_A), D_ALL); -+ MMIO_D(SPRLINOFF(PIPE_A), D_ALL); -+ MMIO_D(SPRSTRIDE(PIPE_A), D_ALL); -+ MMIO_D(SPRPOS(PIPE_A), D_ALL); -+ MMIO_D(SPRSIZE(PIPE_A), D_ALL); -+ MMIO_D(SPRKEYVAL(PIPE_A), D_ALL); -+ MMIO_D(SPRKEYMSK(PIPE_A), D_ALL); -+ MMIO_DH(SPRSURF(PIPE_A), D_ALL, NULL, spr_surf_mmio_write); -+ MMIO_D(SPRKEYMAX(PIPE_A), D_ALL); -+ MMIO_D(SPROFFSET(PIPE_A), D_ALL); -+ MMIO_D(SPRSCALE(PIPE_A), D_ALL); -+ MMIO_D(SPRSURFLIVE(PIPE_A), D_ALL); -+ MMIO_DH(REG_50080(PIPE_A, PLANE_SPRITE0), D_ALL, NULL, -+ reg50080_mmio_write); -+ -+ MMIO_D(SPRCTL(PIPE_B), D_ALL); -+ MMIO_D(SPRLINOFF(PIPE_B), D_ALL); -+ MMIO_D(SPRSTRIDE(PIPE_B), D_ALL); -+ MMIO_D(SPRPOS(PIPE_B), D_ALL); -+ MMIO_D(SPRSIZE(PIPE_B), D_ALL); -+ MMIO_D(SPRKEYVAL(PIPE_B), D_ALL); -+ MMIO_D(SPRKEYMSK(PIPE_B), D_ALL); -+ MMIO_DH(SPRSURF(PIPE_B), D_ALL, NULL, spr_surf_mmio_write); -+ MMIO_D(SPRKEYMAX(PIPE_B), D_ALL); -+ MMIO_D(SPROFFSET(PIPE_B), D_ALL); -+ MMIO_D(SPRSCALE(PIPE_B), D_ALL); -+ MMIO_D(SPRSURFLIVE(PIPE_B), D_ALL); -+ MMIO_DH(REG_50080(PIPE_B, PLANE_SPRITE0), D_ALL, NULL, -+ reg50080_mmio_write); -+ -+ MMIO_D(SPRCTL(PIPE_C), D_ALL); -+ MMIO_D(SPRLINOFF(PIPE_C), D_ALL); -+ MMIO_D(SPRSTRIDE(PIPE_C), D_ALL); -+ MMIO_D(SPRPOS(PIPE_C), D_ALL); -+ MMIO_D(SPRSIZE(PIPE_C), D_ALL); -+ MMIO_D(SPRKEYVAL(PIPE_C), D_ALL); -+ MMIO_D(SPRKEYMSK(PIPE_C), D_ALL); -+ MMIO_DH(SPRSURF(PIPE_C), D_ALL, NULL, spr_surf_mmio_write); -+ MMIO_D(SPRKEYMAX(PIPE_C), D_ALL); -+ MMIO_D(SPROFFSET(PIPE_C), D_ALL); -+ MMIO_D(SPRSCALE(PIPE_C), D_ALL); -+ MMIO_D(SPRSURFLIVE(PIPE_C), D_ALL); -+ MMIO_DH(REG_50080(PIPE_C, PLANE_SPRITE0), D_ALL, NULL, -+ reg50080_mmio_write); -+ -+ MMIO_D(HTOTAL(TRANSCODER_A), D_ALL); -+ MMIO_D(HBLANK(TRANSCODER_A), D_ALL); -+ MMIO_D(HSYNC(TRANSCODER_A), D_ALL); -+ MMIO_D(VTOTAL(TRANSCODER_A), D_ALL); -+ MMIO_D(VBLANK(TRANSCODER_A), D_ALL); -+ MMIO_D(VSYNC(TRANSCODER_A), D_ALL); -+ MMIO_D(BCLRPAT(TRANSCODER_A), D_ALL); -+ MMIO_D(VSYNCSHIFT(TRANSCODER_A), D_ALL); -+ MMIO_D(PIPESRC(TRANSCODER_A), D_ALL); -+ -+ MMIO_D(HTOTAL(TRANSCODER_B), D_ALL); -+ MMIO_D(HBLANK(TRANSCODER_B), D_ALL); -+ MMIO_D(HSYNC(TRANSCODER_B), D_ALL); -+ MMIO_D(VTOTAL(TRANSCODER_B), D_ALL); -+ MMIO_D(VBLANK(TRANSCODER_B), D_ALL); -+ MMIO_D(VSYNC(TRANSCODER_B), D_ALL); -+ MMIO_D(BCLRPAT(TRANSCODER_B), D_ALL); -+ MMIO_D(VSYNCSHIFT(TRANSCODER_B), D_ALL); -+ MMIO_D(PIPESRC(TRANSCODER_B), D_ALL); -+ -+ MMIO_D(HTOTAL(TRANSCODER_C), D_ALL); -+ MMIO_D(HBLANK(TRANSCODER_C), D_ALL); -+ MMIO_D(HSYNC(TRANSCODER_C), D_ALL); -+ MMIO_D(VTOTAL(TRANSCODER_C), D_ALL); -+ MMIO_D(VBLANK(TRANSCODER_C), D_ALL); -+ MMIO_D(VSYNC(TRANSCODER_C), D_ALL); -+ MMIO_D(BCLRPAT(TRANSCODER_C), D_ALL); -+ MMIO_D(VSYNCSHIFT(TRANSCODER_C), D_ALL); -+ MMIO_D(PIPESRC(TRANSCODER_C), D_ALL); -+ -+ MMIO_D(HTOTAL(TRANSCODER_EDP), D_ALL); -+ MMIO_D(HBLANK(TRANSCODER_EDP), D_ALL); -+ MMIO_D(HSYNC(TRANSCODER_EDP), D_ALL); -+ MMIO_D(VTOTAL(TRANSCODER_EDP), D_ALL); -+ MMIO_D(VBLANK(TRANSCODER_EDP), D_ALL); -+ MMIO_D(VSYNC(TRANSCODER_EDP), D_ALL); -+ MMIO_D(BCLRPAT(TRANSCODER_EDP), D_ALL); -+ MMIO_D(VSYNCSHIFT(TRANSCODER_EDP), D_ALL); -+ -+ MMIO_D(PIPE_DATA_M1(TRANSCODER_A), D_ALL); -+ MMIO_D(PIPE_DATA_N1(TRANSCODER_A), D_ALL); -+ MMIO_D(PIPE_DATA_M2(TRANSCODER_A), D_ALL); -+ MMIO_D(PIPE_DATA_N2(TRANSCODER_A), D_ALL); -+ MMIO_D(PIPE_LINK_M1(TRANSCODER_A), D_ALL); -+ MMIO_D(PIPE_LINK_N1(TRANSCODER_A), D_ALL); -+ MMIO_D(PIPE_LINK_M2(TRANSCODER_A), D_ALL); -+ MMIO_D(PIPE_LINK_N2(TRANSCODER_A), D_ALL); -+ -+ MMIO_D(PIPE_DATA_M1(TRANSCODER_B), D_ALL); -+ MMIO_D(PIPE_DATA_N1(TRANSCODER_B), D_ALL); -+ MMIO_D(PIPE_DATA_M2(TRANSCODER_B), D_ALL); -+ MMIO_D(PIPE_DATA_N2(TRANSCODER_B), D_ALL); -+ MMIO_D(PIPE_LINK_M1(TRANSCODER_B), D_ALL); -+ MMIO_D(PIPE_LINK_N1(TRANSCODER_B), D_ALL); -+ MMIO_D(PIPE_LINK_M2(TRANSCODER_B), D_ALL); -+ MMIO_D(PIPE_LINK_N2(TRANSCODER_B), D_ALL); -+ -+ MMIO_D(PIPE_DATA_M1(TRANSCODER_C), D_ALL); -+ MMIO_D(PIPE_DATA_N1(TRANSCODER_C), D_ALL); -+ MMIO_D(PIPE_DATA_M2(TRANSCODER_C), D_ALL); -+ MMIO_D(PIPE_DATA_N2(TRANSCODER_C), D_ALL); -+ MMIO_D(PIPE_LINK_M1(TRANSCODER_C), D_ALL); -+ MMIO_D(PIPE_LINK_N1(TRANSCODER_C), D_ALL); -+ MMIO_D(PIPE_LINK_M2(TRANSCODER_C), D_ALL); -+ MMIO_D(PIPE_LINK_N2(TRANSCODER_C), D_ALL); -+ -+ MMIO_D(PIPE_DATA_M1(TRANSCODER_EDP), D_ALL); -+ MMIO_D(PIPE_DATA_N1(TRANSCODER_EDP), D_ALL); -+ MMIO_D(PIPE_DATA_M2(TRANSCODER_EDP), D_ALL); -+ MMIO_D(PIPE_DATA_N2(TRANSCODER_EDP), D_ALL); -+ MMIO_D(PIPE_LINK_M1(TRANSCODER_EDP), D_ALL); -+ MMIO_D(PIPE_LINK_N1(TRANSCODER_EDP), D_ALL); -+ MMIO_D(PIPE_LINK_M2(TRANSCODER_EDP), D_ALL); -+ MMIO_D(PIPE_LINK_N2(TRANSCODER_EDP), D_ALL); -+ -+ MMIO_D(PF_CTL(PIPE_A), D_ALL); -+ MMIO_D(PF_WIN_SZ(PIPE_A), D_ALL); -+ MMIO_D(PF_WIN_POS(PIPE_A), D_ALL); -+ MMIO_D(PF_VSCALE(PIPE_A), D_ALL); -+ MMIO_D(PF_HSCALE(PIPE_A), D_ALL); -+ -+ MMIO_D(PF_CTL(PIPE_B), D_ALL); -+ MMIO_D(PF_WIN_SZ(PIPE_B), D_ALL); -+ MMIO_D(PF_WIN_POS(PIPE_B), D_ALL); -+ MMIO_D(PF_VSCALE(PIPE_B), D_ALL); -+ MMIO_D(PF_HSCALE(PIPE_B), D_ALL); -+ -+ MMIO_D(PF_CTL(PIPE_C), D_ALL); -+ MMIO_D(PF_WIN_SZ(PIPE_C), D_ALL); -+ MMIO_D(PF_WIN_POS(PIPE_C), D_ALL); -+ MMIO_D(PF_VSCALE(PIPE_C), D_ALL); -+ MMIO_D(PF_HSCALE(PIPE_C), D_ALL); -+ -+ MMIO_D(WM0_PIPEA_ILK, D_ALL); -+ MMIO_D(WM0_PIPEB_ILK, D_ALL); -+ MMIO_D(WM0_PIPEC_IVB, D_ALL); -+ MMIO_D(WM1_LP_ILK, D_ALL); -+ MMIO_D(WM2_LP_ILK, D_ALL); -+ MMIO_D(WM3_LP_ILK, D_ALL); -+ MMIO_D(WM1S_LP_ILK, D_ALL); -+ MMIO_D(WM2S_LP_IVB, D_ALL); -+ MMIO_D(WM3S_LP_IVB, D_ALL); -+ -+ MMIO_D(BLC_PWM_CPU_CTL2, D_ALL); -+ MMIO_D(BLC_PWM_CPU_CTL, D_ALL); -+ MMIO_D(BLC_PWM_PCH_CTL1, D_ALL); -+ MMIO_D(BLC_PWM_PCH_CTL2, D_ALL); -+ -+ MMIO_D(_MMIO(0x48268), D_ALL); -+ -+ MMIO_F(PCH_GMBUS0, 4 * 4, 0, 0, 0, D_ALL, gmbus_mmio_read, -+ gmbus_mmio_write); -+ MMIO_F(PCH_GPIO_BASE, 6 * 4, F_UNALIGN, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(_MMIO(0xe4f00), 0x28, 0, 0, 0, D_ALL, NULL, NULL); -+ -+ MMIO_F(_MMIO(_PCH_DPB_AUX_CH_CTL), 6 * 4, 0, 0, 0, D_PRE_SKL, NULL, -+ dp_aux_ch_ctl_mmio_write); -+ MMIO_F(_MMIO(_PCH_DPC_AUX_CH_CTL), 6 * 4, 0, 0, 0, D_PRE_SKL, NULL, -+ dp_aux_ch_ctl_mmio_write); -+ MMIO_F(_MMIO(_PCH_DPD_AUX_CH_CTL), 6 * 4, 0, 0, 0, D_PRE_SKL, NULL, -+ dp_aux_ch_ctl_mmio_write); -+ -+ MMIO_DH(PCH_ADPA, D_PRE_SKL, NULL, pch_adpa_mmio_write); -+ -+ MMIO_DH(_MMIO(_PCH_TRANSACONF), D_ALL, NULL, transconf_mmio_write); -+ MMIO_DH(_MMIO(_PCH_TRANSBCONF), D_ALL, NULL, transconf_mmio_write); -+ -+ MMIO_DH(FDI_RX_IIR(PIPE_A), D_ALL, NULL, fdi_rx_iir_mmio_write); -+ MMIO_DH(FDI_RX_IIR(PIPE_B), D_ALL, NULL, fdi_rx_iir_mmio_write); -+ MMIO_DH(FDI_RX_IIR(PIPE_C), D_ALL, NULL, fdi_rx_iir_mmio_write); -+ MMIO_DH(FDI_RX_IMR(PIPE_A), D_ALL, NULL, update_fdi_rx_iir_status); -+ MMIO_DH(FDI_RX_IMR(PIPE_B), D_ALL, NULL, update_fdi_rx_iir_status); -+ MMIO_DH(FDI_RX_IMR(PIPE_C), D_ALL, NULL, update_fdi_rx_iir_status); -+ MMIO_DH(FDI_RX_CTL(PIPE_A), D_ALL, NULL, update_fdi_rx_iir_status); -+ MMIO_DH(FDI_RX_CTL(PIPE_B), D_ALL, NULL, update_fdi_rx_iir_status); -+ MMIO_DH(FDI_RX_CTL(PIPE_C), D_ALL, NULL, update_fdi_rx_iir_status); -+ -+ MMIO_D(_MMIO(_PCH_TRANS_HTOTAL_A), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANS_HBLANK_A), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANS_HSYNC_A), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANS_VTOTAL_A), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANS_VBLANK_A), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANS_VSYNC_A), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANS_VSYNCSHIFT_A), D_ALL); -+ -+ MMIO_D(_MMIO(_PCH_TRANS_HTOTAL_B), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANS_HBLANK_B), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANS_HSYNC_B), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANS_VTOTAL_B), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANS_VBLANK_B), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANS_VSYNC_B), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANS_VSYNCSHIFT_B), D_ALL); -+ -+ MMIO_D(_MMIO(_PCH_TRANSA_DATA_M1), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANSA_DATA_N1), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANSA_DATA_M2), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANSA_DATA_N2), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANSA_LINK_M1), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANSA_LINK_N1), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANSA_LINK_M2), D_ALL); -+ MMIO_D(_MMIO(_PCH_TRANSA_LINK_N2), D_ALL); -+ -+ MMIO_D(TRANS_DP_CTL(PIPE_A), D_ALL); -+ MMIO_D(TRANS_DP_CTL(PIPE_B), D_ALL); -+ MMIO_D(TRANS_DP_CTL(PIPE_C), D_ALL); -+ -+ MMIO_D(TVIDEO_DIP_CTL(PIPE_A), D_ALL); -+ MMIO_D(TVIDEO_DIP_DATA(PIPE_A), D_ALL); -+ MMIO_D(TVIDEO_DIP_GCP(PIPE_A), D_ALL); -+ -+ MMIO_D(TVIDEO_DIP_CTL(PIPE_B), D_ALL); -+ MMIO_D(TVIDEO_DIP_DATA(PIPE_B), D_ALL); -+ MMIO_D(TVIDEO_DIP_GCP(PIPE_B), D_ALL); -+ -+ MMIO_D(TVIDEO_DIP_CTL(PIPE_C), D_ALL); -+ MMIO_D(TVIDEO_DIP_DATA(PIPE_C), D_ALL); -+ MMIO_D(TVIDEO_DIP_GCP(PIPE_C), D_ALL); -+ -+ MMIO_D(_MMIO(_FDI_RXA_MISC), D_ALL); -+ MMIO_D(_MMIO(_FDI_RXB_MISC), D_ALL); -+ MMIO_D(_MMIO(_FDI_RXA_TUSIZE1), D_ALL); -+ MMIO_D(_MMIO(_FDI_RXA_TUSIZE2), D_ALL); -+ MMIO_D(_MMIO(_FDI_RXB_TUSIZE1), D_ALL); -+ MMIO_D(_MMIO(_FDI_RXB_TUSIZE2), D_ALL); -+ -+ MMIO_DH(PCH_PP_CONTROL, D_ALL, NULL, pch_pp_control_mmio_write); -+ MMIO_D(PCH_PP_DIVISOR, D_ALL); -+ MMIO_D(PCH_PP_STATUS, D_ALL); -+ MMIO_D(PCH_LVDS, D_ALL); -+ MMIO_D(_MMIO(_PCH_DPLL_A), D_ALL); -+ MMIO_D(_MMIO(_PCH_DPLL_B), D_ALL); -+ MMIO_D(_MMIO(_PCH_FPA0), D_ALL); -+ MMIO_D(_MMIO(_PCH_FPA1), D_ALL); -+ MMIO_D(_MMIO(_PCH_FPB0), D_ALL); -+ MMIO_D(_MMIO(_PCH_FPB1), D_ALL); -+ MMIO_D(PCH_DREF_CONTROL, D_ALL); -+ MMIO_D(PCH_RAWCLK_FREQ, D_ALL); -+ MMIO_D(PCH_DPLL_SEL, D_ALL); -+ -+ MMIO_D(_MMIO(0x61208), D_ALL); -+ MMIO_D(_MMIO(0x6120c), D_ALL); -+ MMIO_D(PCH_PP_ON_DELAYS, D_ALL); -+ MMIO_D(PCH_PP_OFF_DELAYS, D_ALL); -+ -+ MMIO_DH(_MMIO(0xe651c), D_ALL, dpy_reg_mmio_read, NULL); -+ MMIO_DH(_MMIO(0xe661c), D_ALL, dpy_reg_mmio_read, NULL); -+ MMIO_DH(_MMIO(0xe671c), D_ALL, dpy_reg_mmio_read, NULL); -+ MMIO_DH(_MMIO(0xe681c), D_ALL, dpy_reg_mmio_read, NULL); -+ MMIO_DH(_MMIO(0xe6c04), D_ALL, dpy_reg_mmio_read, NULL); -+ MMIO_DH(_MMIO(0xe6e1c), D_ALL, dpy_reg_mmio_read, NULL); -+ -+ MMIO_RO(PCH_PORT_HOTPLUG, D_ALL, 0, -+ PORTA_HOTPLUG_STATUS_MASK -+ | PORTB_HOTPLUG_STATUS_MASK -+ | PORTC_HOTPLUG_STATUS_MASK -+ | PORTD_HOTPLUG_STATUS_MASK, -+ NULL, NULL); -+ -+ MMIO_DH(LCPLL_CTL, D_ALL, NULL, lcpll_ctl_mmio_write); -+ MMIO_D(FUSE_STRAP, D_ALL); -+ MMIO_D(DIGITAL_PORT_HOTPLUG_CNTRL, D_ALL); -+ -+ MMIO_D(DISP_ARB_CTL, D_ALL); -+ MMIO_D(DISP_ARB_CTL2, D_ALL); -+ -+ MMIO_D(ILK_DISPLAY_CHICKEN1, D_ALL); -+ MMIO_D(ILK_DISPLAY_CHICKEN2, D_ALL); -+ MMIO_D(ILK_DSPCLK_GATE_D, D_ALL); -+ -+ MMIO_D(SOUTH_CHICKEN1, D_ALL); -+ MMIO_DH(SOUTH_CHICKEN2, D_ALL, NULL, south_chicken2_mmio_write); -+ MMIO_D(_MMIO(_TRANSA_CHICKEN1), D_ALL); -+ MMIO_D(_MMIO(_TRANSB_CHICKEN1), D_ALL); -+ MMIO_D(SOUTH_DSPCLK_GATE_D, D_ALL); -+ MMIO_D(_MMIO(_TRANSA_CHICKEN2), D_ALL); -+ MMIO_D(_MMIO(_TRANSB_CHICKEN2), D_ALL); -+ -+ MMIO_D(ILK_DPFC_CB_BASE, D_ALL); -+ MMIO_D(ILK_DPFC_CONTROL, D_ALL); -+ MMIO_D(ILK_DPFC_RECOMP_CTL, D_ALL); -+ MMIO_D(ILK_DPFC_STATUS, D_ALL); -+ MMIO_D(ILK_DPFC_FENCE_YOFF, D_ALL); -+ MMIO_D(ILK_DPFC_CHICKEN, D_ALL); -+ MMIO_D(ILK_FBC_RT_BASE, D_ALL); -+ -+ MMIO_D(IPS_CTL, D_ALL); -+ -+ MMIO_D(PIPE_CSC_COEFF_RY_GY(PIPE_A), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_BY(PIPE_A), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_RU_GU(PIPE_A), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_BU(PIPE_A), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_RV_GV(PIPE_A), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_BV(PIPE_A), D_ALL); -+ MMIO_D(PIPE_CSC_MODE(PIPE_A), D_ALL); -+ MMIO_D(PIPE_CSC_PREOFF_HI(PIPE_A), D_ALL); -+ MMIO_D(PIPE_CSC_PREOFF_ME(PIPE_A), D_ALL); -+ MMIO_D(PIPE_CSC_PREOFF_LO(PIPE_A), D_ALL); -+ MMIO_D(PIPE_CSC_POSTOFF_HI(PIPE_A), D_ALL); -+ MMIO_D(PIPE_CSC_POSTOFF_ME(PIPE_A), D_ALL); -+ MMIO_D(PIPE_CSC_POSTOFF_LO(PIPE_A), D_ALL); -+ -+ MMIO_D(PIPE_CSC_COEFF_RY_GY(PIPE_B), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_BY(PIPE_B), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_RU_GU(PIPE_B), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_BU(PIPE_B), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_RV_GV(PIPE_B), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_BV(PIPE_B), D_ALL); -+ MMIO_D(PIPE_CSC_MODE(PIPE_B), D_ALL); -+ MMIO_D(PIPE_CSC_PREOFF_HI(PIPE_B), D_ALL); -+ MMIO_D(PIPE_CSC_PREOFF_ME(PIPE_B), D_ALL); -+ MMIO_D(PIPE_CSC_PREOFF_LO(PIPE_B), D_ALL); -+ MMIO_D(PIPE_CSC_POSTOFF_HI(PIPE_B), D_ALL); -+ MMIO_D(PIPE_CSC_POSTOFF_ME(PIPE_B), D_ALL); -+ MMIO_D(PIPE_CSC_POSTOFF_LO(PIPE_B), D_ALL); -+ -+ MMIO_D(PIPE_CSC_COEFF_RY_GY(PIPE_C), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_BY(PIPE_C), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_RU_GU(PIPE_C), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_BU(PIPE_C), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_RV_GV(PIPE_C), D_ALL); -+ MMIO_D(PIPE_CSC_COEFF_BV(PIPE_C), D_ALL); -+ MMIO_D(PIPE_CSC_MODE(PIPE_C), D_ALL); -+ MMIO_D(PIPE_CSC_PREOFF_HI(PIPE_C), D_ALL); -+ MMIO_D(PIPE_CSC_PREOFF_ME(PIPE_C), D_ALL); -+ MMIO_D(PIPE_CSC_PREOFF_LO(PIPE_C), D_ALL); -+ MMIO_D(PIPE_CSC_POSTOFF_HI(PIPE_C), D_ALL); -+ MMIO_D(PIPE_CSC_POSTOFF_ME(PIPE_C), D_ALL); -+ MMIO_D(PIPE_CSC_POSTOFF_LO(PIPE_C), D_ALL); -+ -+ MMIO_D(PREC_PAL_INDEX(PIPE_A), D_ALL); -+ MMIO_D(PREC_PAL_DATA(PIPE_A), D_ALL); -+ MMIO_F(PREC_PAL_GC_MAX(PIPE_A, 0), 4 * 3, 0, 0, 0, D_ALL, NULL, NULL); -+ -+ MMIO_D(PREC_PAL_INDEX(PIPE_B), D_ALL); -+ MMIO_D(PREC_PAL_DATA(PIPE_B), D_ALL); -+ MMIO_F(PREC_PAL_GC_MAX(PIPE_B, 0), 4 * 3, 0, 0, 0, D_ALL, NULL, NULL); -+ -+ MMIO_D(PREC_PAL_INDEX(PIPE_C), D_ALL); -+ MMIO_D(PREC_PAL_DATA(PIPE_C), D_ALL); -+ MMIO_F(PREC_PAL_GC_MAX(PIPE_C, 0), 4 * 3, 0, 0, 0, D_ALL, NULL, NULL); -+ -+ MMIO_D(_MMIO(0x60110), D_ALL); -+ MMIO_D(_MMIO(0x61110), D_ALL); -+ MMIO_F(_MMIO(0x70400), 0x40, 0, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(_MMIO(0x71400), 0x40, 0, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(_MMIO(0x72400), 0x40, 0, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(_MMIO(0x70440), 0xc, 0, 0, 0, D_PRE_SKL, NULL, NULL); -+ MMIO_F(_MMIO(0x71440), 0xc, 0, 0, 0, D_PRE_SKL, NULL, NULL); -+ MMIO_F(_MMIO(0x72440), 0xc, 0, 0, 0, D_PRE_SKL, NULL, NULL); -+ MMIO_F(_MMIO(0x7044c), 0xc, 0, 0, 0, D_PRE_SKL, NULL, NULL); -+ MMIO_F(_MMIO(0x7144c), 0xc, 0, 0, 0, D_PRE_SKL, NULL, NULL); -+ MMIO_F(_MMIO(0x7244c), 0xc, 0, 0, 0, D_PRE_SKL, NULL, NULL); -+ -+ MMIO_D(PIPE_WM_LINETIME(PIPE_A), D_ALL); -+ MMIO_D(PIPE_WM_LINETIME(PIPE_B), D_ALL); -+ MMIO_D(PIPE_WM_LINETIME(PIPE_C), D_ALL); -+ MMIO_D(SPLL_CTL, D_ALL); -+ MMIO_D(_MMIO(_WRPLL_CTL1), D_ALL); -+ MMIO_D(_MMIO(_WRPLL_CTL2), D_ALL); -+ MMIO_D(PORT_CLK_SEL(PORT_A), D_ALL); -+ MMIO_D(PORT_CLK_SEL(PORT_B), D_ALL); -+ MMIO_D(PORT_CLK_SEL(PORT_C), D_ALL); -+ MMIO_D(PORT_CLK_SEL(PORT_D), D_ALL); -+ MMIO_D(PORT_CLK_SEL(PORT_E), D_ALL); -+ MMIO_D(TRANS_CLK_SEL(TRANSCODER_A), D_ALL); -+ MMIO_D(TRANS_CLK_SEL(TRANSCODER_B), D_ALL); -+ MMIO_D(TRANS_CLK_SEL(TRANSCODER_C), D_ALL); -+ -+ MMIO_D(HSW_NDE_RSTWRN_OPT, D_ALL); -+ MMIO_D(_MMIO(0x46508), D_ALL); -+ -+ MMIO_D(_MMIO(0x49080), D_ALL); -+ MMIO_D(_MMIO(0x49180), D_ALL); -+ MMIO_D(_MMIO(0x49280), D_ALL); -+ -+ MMIO_F(_MMIO(0x49090), 0x14, 0, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(_MMIO(0x49190), 0x14, 0, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(_MMIO(0x49290), 0x14, 0, 0, 0, D_ALL, NULL, NULL); -+ -+ MMIO_D(GAMMA_MODE(PIPE_A), D_ALL); -+ MMIO_D(GAMMA_MODE(PIPE_B), D_ALL); -+ MMIO_D(GAMMA_MODE(PIPE_C), D_ALL); -+ -+ MMIO_D(PIPE_MULT(PIPE_A), D_ALL); -+ MMIO_D(PIPE_MULT(PIPE_B), D_ALL); -+ MMIO_D(PIPE_MULT(PIPE_C), D_ALL); -+ -+ MMIO_D(HSW_TVIDEO_DIP_CTL(TRANSCODER_A), D_ALL); -+ MMIO_D(HSW_TVIDEO_DIP_CTL(TRANSCODER_B), D_ALL); -+ MMIO_D(HSW_TVIDEO_DIP_CTL(TRANSCODER_C), D_ALL); -+ -+ MMIO_DH(SFUSE_STRAP, D_ALL, NULL, NULL); -+ MMIO_D(SBI_ADDR, D_ALL); -+ MMIO_DH(SBI_DATA, D_ALL, sbi_data_mmio_read, NULL); -+ MMIO_DH(SBI_CTL_STAT, D_ALL, NULL, sbi_ctl_mmio_write); -+ MMIO_D(PIXCLK_GATE, D_ALL); -+ -+ MMIO_F(_MMIO(_DPA_AUX_CH_CTL), 6 * 4, 0, 0, 0, D_ALL, NULL, -+ dp_aux_ch_ctl_mmio_write); -+ -+ MMIO_DH(DDI_BUF_CTL(PORT_A), D_ALL, NULL, ddi_buf_ctl_mmio_write); -+ MMIO_DH(DDI_BUF_CTL(PORT_B), D_ALL, NULL, ddi_buf_ctl_mmio_write); -+ MMIO_DH(DDI_BUF_CTL(PORT_C), D_ALL, NULL, ddi_buf_ctl_mmio_write); -+ MMIO_DH(DDI_BUF_CTL(PORT_D), D_ALL, NULL, ddi_buf_ctl_mmio_write); -+ MMIO_DH(DDI_BUF_CTL(PORT_E), D_ALL, NULL, ddi_buf_ctl_mmio_write); -+ -+ MMIO_DH(DP_TP_CTL(PORT_A), D_ALL, NULL, dp_tp_ctl_mmio_write); -+ MMIO_DH(DP_TP_CTL(PORT_B), D_ALL, NULL, dp_tp_ctl_mmio_write); -+ MMIO_DH(DP_TP_CTL(PORT_C), D_ALL, NULL, dp_tp_ctl_mmio_write); -+ MMIO_DH(DP_TP_CTL(PORT_D), D_ALL, NULL, dp_tp_ctl_mmio_write); -+ MMIO_DH(DP_TP_CTL(PORT_E), D_ALL, NULL, dp_tp_ctl_mmio_write); -+ -+ MMIO_DH(DP_TP_STATUS(PORT_A), D_ALL, NULL, dp_tp_status_mmio_write); -+ MMIO_DH(DP_TP_STATUS(PORT_B), D_ALL, NULL, dp_tp_status_mmio_write); -+ MMIO_DH(DP_TP_STATUS(PORT_C), D_ALL, NULL, dp_tp_status_mmio_write); -+ MMIO_DH(DP_TP_STATUS(PORT_D), D_ALL, NULL, dp_tp_status_mmio_write); -+ MMIO_DH(DP_TP_STATUS(PORT_E), D_ALL, NULL, NULL); -+ -+ MMIO_F(_MMIO(_DDI_BUF_TRANS_A), 0x50, 0, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(_MMIO(0x64e60), 0x50, 0, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(_MMIO(0x64eC0), 0x50, 0, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(_MMIO(0x64f20), 0x50, 0, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(_MMIO(0x64f80), 0x50, 0, 0, 0, D_ALL, NULL, NULL); -+ -+ MMIO_D(HSW_AUD_CFG(PIPE_A), D_ALL); -+ MMIO_D(HSW_AUD_PIN_ELD_CP_VLD, D_ALL); -+ MMIO_D(HSW_AUD_MISC_CTRL(PIPE_A), D_ALL); -+ -+ MMIO_DH(_MMIO(_TRANS_DDI_FUNC_CTL_A), D_ALL, NULL, NULL); -+ MMIO_DH(_MMIO(_TRANS_DDI_FUNC_CTL_B), D_ALL, NULL, NULL); -+ MMIO_DH(_MMIO(_TRANS_DDI_FUNC_CTL_C), D_ALL, NULL, NULL); -+ MMIO_DH(_MMIO(_TRANS_DDI_FUNC_CTL_EDP), D_ALL, NULL, NULL); -+ -+ MMIO_D(_MMIO(_TRANSA_MSA_MISC), D_ALL); -+ MMIO_D(_MMIO(_TRANSB_MSA_MISC), D_ALL); -+ MMIO_D(_MMIO(_TRANSC_MSA_MISC), D_ALL); -+ MMIO_D(_MMIO(_TRANS_EDP_MSA_MISC), D_ALL); -+ -+ MMIO_DH(FORCEWAKE, D_ALL, NULL, NULL); -+ MMIO_D(FORCEWAKE_ACK, D_ALL); -+ MMIO_D(GEN6_GT_CORE_STATUS, D_ALL); -+ MMIO_D(GEN6_GT_THREAD_STATUS_REG, D_ALL); -+ MMIO_DFH(GTFIFODBG, D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(GTFIFOCTL, D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DH(FORCEWAKE_MT, D_PRE_SKL, NULL, mul_force_wake_write); -+ MMIO_DH(FORCEWAKE_ACK_HSW, D_BDW, NULL, NULL); -+ MMIO_D(ECOBUS, D_ALL); -+ MMIO_DH(GEN6_RC_CONTROL, D_ALL, NULL, NULL); -+ MMIO_DH(GEN6_RC_STATE, D_ALL, NULL, NULL); -+ MMIO_D(GEN6_RPNSWREQ, D_ALL); -+ MMIO_D(GEN6_RC_VIDEO_FREQ, D_ALL); -+ MMIO_D(GEN6_RP_DOWN_TIMEOUT, D_ALL); -+ MMIO_D(GEN6_RP_INTERRUPT_LIMITS, D_ALL); -+ MMIO_D(GEN6_RPSTAT1, D_ALL); -+ MMIO_D(GEN6_RP_CONTROL, D_ALL); -+ MMIO_D(GEN6_RP_UP_THRESHOLD, D_ALL); -+ MMIO_D(GEN6_RP_DOWN_THRESHOLD, D_ALL); -+ MMIO_D(GEN6_RP_CUR_UP_EI, D_ALL); -+ MMIO_D(GEN6_RP_CUR_UP, D_ALL); -+ MMIO_D(GEN6_RP_PREV_UP, D_ALL); -+ MMIO_D(GEN6_RP_CUR_DOWN_EI, D_ALL); -+ MMIO_D(GEN6_RP_CUR_DOWN, D_ALL); -+ MMIO_D(GEN6_RP_PREV_DOWN, D_ALL); -+ MMIO_D(GEN6_RP_UP_EI, D_ALL); -+ MMIO_D(GEN6_RP_DOWN_EI, D_ALL); -+ MMIO_D(GEN6_RP_IDLE_HYSTERSIS, D_ALL); -+ MMIO_D(GEN6_RC1_WAKE_RATE_LIMIT, D_ALL); -+ MMIO_D(GEN6_RC6_WAKE_RATE_LIMIT, D_ALL); -+ MMIO_D(GEN6_RC6pp_WAKE_RATE_LIMIT, D_ALL); -+ MMIO_D(GEN6_RC_EVALUATION_INTERVAL, D_ALL); -+ MMIO_D(GEN6_RC_IDLE_HYSTERSIS, D_ALL); -+ MMIO_D(GEN6_RC_SLEEP, D_ALL); -+ MMIO_D(GEN6_RC1e_THRESHOLD, D_ALL); -+ MMIO_D(GEN6_RC6_THRESHOLD, D_ALL); -+ MMIO_D(GEN6_RC6p_THRESHOLD, D_ALL); -+ MMIO_D(GEN6_RC6pp_THRESHOLD, D_ALL); -+ MMIO_D(GEN6_PMINTRMSK, D_ALL); -+ MMIO_DH(HSW_PWR_WELL_CTL1, D_BDW, NULL, power_well_ctl_mmio_write); -+ MMIO_DH(HSW_PWR_WELL_CTL2, D_BDW, NULL, power_well_ctl_mmio_write); -+ MMIO_DH(HSW_PWR_WELL_CTL3, D_BDW, NULL, power_well_ctl_mmio_write); -+ MMIO_DH(HSW_PWR_WELL_CTL4, D_BDW, NULL, power_well_ctl_mmio_write); -+ MMIO_DH(HSW_PWR_WELL_CTL5, D_BDW, NULL, power_well_ctl_mmio_write); -+ MMIO_DH(HSW_PWR_WELL_CTL6, D_BDW, NULL, power_well_ctl_mmio_write); -+ -+ MMIO_D(RSTDBYCTL, D_ALL); -+ -+ MMIO_DH(GEN6_GDRST, D_ALL, NULL, gdrst_mmio_write); -+ MMIO_F(FENCE_REG_GEN6_LO(0), 0x80, 0, 0, 0, D_ALL, fence_mmio_read, fence_mmio_write); -+ MMIO_DH(CPU_VGACNTRL, D_ALL, NULL, vga_control_mmio_write); -+ -+ MMIO_D(TILECTL, D_ALL); -+ -+ MMIO_D(GEN6_UCGCTL1, D_ALL); -+ MMIO_D(GEN6_UCGCTL2, D_ALL); -+ -+ MMIO_F(_MMIO(0x4f000), 0x90, 0, 0, 0, D_ALL, NULL, NULL); -+ -+ MMIO_D(GEN6_PCODE_DATA, D_ALL); -+ MMIO_D(_MMIO(0x13812c), D_ALL); -+ MMIO_DH(GEN7_ERR_INT, D_ALL, NULL, NULL); -+ MMIO_D(HSW_EDRAM_CAP, D_ALL); -+ MMIO_D(HSW_IDICR, D_ALL); -+ MMIO_DH(GFX_FLSH_CNTL_GEN6, D_ALL, NULL, NULL); -+ -+ MMIO_D(_MMIO(0x3c), D_ALL); -+ MMIO_D(_MMIO(0x860), D_ALL); -+ MMIO_D(ECOSKPD, D_ALL); -+ MMIO_D(_MMIO(0x121d0), D_ALL); -+ MMIO_D(GEN6_BLITTER_ECOSKPD, D_ALL); -+ MMIO_D(_MMIO(0x41d0), D_ALL); -+ MMIO_D(GAC_ECO_BITS, D_ALL); -+ MMIO_D(_MMIO(0x6200), D_ALL); -+ MMIO_D(_MMIO(0x6204), D_ALL); -+ MMIO_D(_MMIO(0x6208), D_ALL); -+ MMIO_D(_MMIO(0x7118), D_ALL); -+ MMIO_D(_MMIO(0x7180), D_ALL); -+ MMIO_D(_MMIO(0x7408), D_ALL); -+ MMIO_D(_MMIO(0x7c00), D_ALL); -+ MMIO_DH(GEN6_MBCTL, D_ALL, NULL, mbctl_write); -+ MMIO_D(_MMIO(0x911c), D_ALL); -+ MMIO_D(_MMIO(0x9120), D_ALL); -+ MMIO_DFH(GEN7_UCGCTL4, D_ALL, F_CMD_ACCESS, NULL, NULL); -+ -+ MMIO_D(GAB_CTL, D_ALL); -+ MMIO_D(_MMIO(0x48800), D_ALL); -+ MMIO_D(_MMIO(0xce044), D_ALL); -+ MMIO_D(_MMIO(0xe6500), D_ALL); -+ MMIO_D(_MMIO(0xe6504), D_ALL); -+ MMIO_D(_MMIO(0xe6600), D_ALL); -+ MMIO_D(_MMIO(0xe6604), D_ALL); -+ MMIO_D(_MMIO(0xe6700), D_ALL); -+ MMIO_D(_MMIO(0xe6704), D_ALL); -+ MMIO_D(_MMIO(0xe6800), D_ALL); -+ MMIO_D(_MMIO(0xe6804), D_ALL); -+ MMIO_D(PCH_GMBUS4, D_ALL); -+ MMIO_D(PCH_GMBUS5, D_ALL); -+ -+ MMIO_D(_MMIO(0x902c), D_ALL); -+ MMIO_D(_MMIO(0xec008), D_ALL); -+ MMIO_D(_MMIO(0xec00c), D_ALL); -+ MMIO_D(_MMIO(0xec008 + 0x18), D_ALL); -+ MMIO_D(_MMIO(0xec00c + 0x18), D_ALL); -+ MMIO_D(_MMIO(0xec008 + 0x18 * 2), D_ALL); -+ MMIO_D(_MMIO(0xec00c + 0x18 * 2), D_ALL); -+ MMIO_D(_MMIO(0xec008 + 0x18 * 3), D_ALL); -+ MMIO_D(_MMIO(0xec00c + 0x18 * 3), D_ALL); -+ MMIO_D(_MMIO(0xec408), D_ALL); -+ MMIO_D(_MMIO(0xec40c), D_ALL); -+ MMIO_D(_MMIO(0xec408 + 0x18), D_ALL); -+ MMIO_D(_MMIO(0xec40c + 0x18), D_ALL); -+ MMIO_D(_MMIO(0xec408 + 0x18 * 2), D_ALL); -+ MMIO_D(_MMIO(0xec40c + 0x18 * 2), D_ALL); -+ MMIO_D(_MMIO(0xec408 + 0x18 * 3), D_ALL); -+ MMIO_D(_MMIO(0xec40c + 0x18 * 3), D_ALL); -+ MMIO_D(_MMIO(0xfc810), D_ALL); -+ MMIO_D(_MMIO(0xfc81c), D_ALL); -+ MMIO_D(_MMIO(0xfc828), D_ALL); -+ MMIO_D(_MMIO(0xfc834), D_ALL); -+ MMIO_D(_MMIO(0xfcc00), D_ALL); -+ MMIO_D(_MMIO(0xfcc0c), D_ALL); -+ MMIO_D(_MMIO(0xfcc18), D_ALL); -+ MMIO_D(_MMIO(0xfcc24), D_ALL); -+ MMIO_D(_MMIO(0xfd000), D_ALL); -+ MMIO_D(_MMIO(0xfd00c), D_ALL); -+ MMIO_D(_MMIO(0xfd018), D_ALL); -+ MMIO_D(_MMIO(0xfd024), D_ALL); -+ MMIO_D(_MMIO(0xfd034), D_ALL); -+ -+ MMIO_DH(FPGA_DBG, D_ALL, NULL, fpga_dbg_mmio_write); -+ MMIO_D(_MMIO(0x2054), D_ALL); -+ MMIO_D(_MMIO(0x12054), D_ALL); -+ MMIO_D(_MMIO(0x22054), D_ALL); -+ MMIO_D(_MMIO(0x1a054), D_ALL); -+ -+ MMIO_D(_MMIO(0x44070), D_ALL); -+ MMIO_DFH(_MMIO(0x215c), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x2178), D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x217c), D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x12178), D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x1217c), D_ALL, F_CMD_ACCESS, NULL, NULL); -+ -+ MMIO_F(_MMIO(0x2290), 8, F_CMD_ACCESS, 0, 0, D_BDW_PLUS, NULL, NULL); -+ MMIO_D(_MMIO(0x2b00), D_BDW_PLUS); -+ MMIO_D(_MMIO(0x2360), D_BDW_PLUS); -+ MMIO_F(_MMIO(0x5200), 32, F_CMD_ACCESS, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(_MMIO(0x5240), 32, F_CMD_ACCESS, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(_MMIO(0x5280), 16, F_CMD_ACCESS, 0, 0, D_ALL, NULL, NULL); -+ -+ MMIO_DFH(_MMIO(0x1c17c), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x1c178), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(BCS_SWCTRL, D_ALL, F_CMD_ACCESS, NULL, NULL); -+ -+ MMIO_F(HS_INVOCATION_COUNT, 8, F_CMD_ACCESS, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(DS_INVOCATION_COUNT, 8, F_CMD_ACCESS, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(IA_VERTICES_COUNT, 8, F_CMD_ACCESS, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(IA_PRIMITIVES_COUNT, 8, F_CMD_ACCESS, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(VS_INVOCATION_COUNT, 8, F_CMD_ACCESS, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(GS_INVOCATION_COUNT, 8, F_CMD_ACCESS, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(GS_PRIMITIVES_COUNT, 8, F_CMD_ACCESS, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(CL_INVOCATION_COUNT, 8, F_CMD_ACCESS, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(CL_PRIMITIVES_COUNT, 8, F_CMD_ACCESS, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(PS_INVOCATION_COUNT, 8, F_CMD_ACCESS, 0, 0, D_ALL, NULL, NULL); -+ MMIO_F(PS_DEPTH_COUNT, 8, F_CMD_ACCESS, 0, 0, D_ALL, NULL, NULL); -+ MMIO_DH(_MMIO(0x4260), D_BDW_PLUS, NULL, gvt_reg_tlb_control_handler); -+ MMIO_DH(_MMIO(0x4264), D_BDW_PLUS, NULL, gvt_reg_tlb_control_handler); -+ MMIO_DH(_MMIO(0x4268), D_BDW_PLUS, NULL, gvt_reg_tlb_control_handler); -+ MMIO_DH(_MMIO(0x426c), D_BDW_PLUS, NULL, gvt_reg_tlb_control_handler); -+ MMIO_DH(_MMIO(0x4270), D_BDW_PLUS, NULL, gvt_reg_tlb_control_handler); -+ MMIO_DFH(_MMIO(0x4094), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ -+ MMIO_DFH(ARB_MODE, D_ALL, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_RING_GM_RDR(RING_BBADDR, D_ALL, NULL, NULL); -+ MMIO_DFH(_MMIO(0x2220), D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x12220), D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x22220), D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_RING_DFH(RING_SYNC_1, D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_RING_DFH(RING_SYNC_0, D_ALL, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x22178), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x1a178), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x1a17c), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x2217c), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ -+ MMIO_DH(EDP_PSR_IMR, D_BDW_PLUS, NULL, edp_psr_imr_iir_write); -+ MMIO_DH(EDP_PSR_IIR, D_BDW_PLUS, NULL, edp_psr_imr_iir_write); -+ return 0; -+} -+ -+static int init_broadwell_mmio_info(struct intel_gvt *gvt) -+{ -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ int ret; -+ -+ MMIO_DH(GEN8_GT_IMR(0), D_BDW_PLUS, NULL, intel_vgpu_reg_imr_handler); -+ MMIO_DH(GEN8_GT_IER(0), D_BDW_PLUS, NULL, intel_vgpu_reg_ier_handler); -+ MMIO_DH(GEN8_GT_IIR(0), D_BDW_PLUS, NULL, intel_vgpu_reg_iir_handler); -+ MMIO_D(GEN8_GT_ISR(0), D_BDW_PLUS); -+ -+ MMIO_DH(GEN8_GT_IMR(1), D_BDW_PLUS, NULL, intel_vgpu_reg_imr_handler); -+ MMIO_DH(GEN8_GT_IER(1), D_BDW_PLUS, NULL, intel_vgpu_reg_ier_handler); -+ MMIO_DH(GEN8_GT_IIR(1), D_BDW_PLUS, NULL, intel_vgpu_reg_iir_handler); -+ MMIO_D(GEN8_GT_ISR(1), D_BDW_PLUS); -+ -+ MMIO_DH(GEN8_GT_IMR(2), D_BDW_PLUS, NULL, intel_vgpu_reg_imr_handler); -+ MMIO_DH(GEN8_GT_IER(2), D_BDW_PLUS, NULL, intel_vgpu_reg_ier_handler); -+ MMIO_DH(GEN8_GT_IIR(2), D_BDW_PLUS, NULL, intel_vgpu_reg_iir_handler); -+ MMIO_D(GEN8_GT_ISR(2), D_BDW_PLUS); -+ -+ MMIO_DH(GEN8_GT_IMR(3), D_BDW_PLUS, NULL, intel_vgpu_reg_imr_handler); -+ MMIO_DH(GEN8_GT_IER(3), D_BDW_PLUS, NULL, intel_vgpu_reg_ier_handler); -+ MMIO_DH(GEN8_GT_IIR(3), D_BDW_PLUS, NULL, intel_vgpu_reg_iir_handler); -+ MMIO_D(GEN8_GT_ISR(3), D_BDW_PLUS); -+ -+ MMIO_DH(GEN8_DE_PIPE_IMR(PIPE_A), D_BDW_PLUS, NULL, -+ intel_vgpu_reg_imr_handler); -+ MMIO_DH(GEN8_DE_PIPE_IER(PIPE_A), D_BDW_PLUS, NULL, -+ intel_vgpu_reg_ier_handler); -+ MMIO_DH(GEN8_DE_PIPE_IIR(PIPE_A), D_BDW_PLUS, NULL, -+ intel_vgpu_reg_iir_handler); -+ MMIO_D(GEN8_DE_PIPE_ISR(PIPE_A), D_BDW_PLUS); -+ -+ MMIO_DH(GEN8_DE_PIPE_IMR(PIPE_B), D_BDW_PLUS, NULL, -+ intel_vgpu_reg_imr_handler); -+ MMIO_DH(GEN8_DE_PIPE_IER(PIPE_B), D_BDW_PLUS, NULL, -+ intel_vgpu_reg_ier_handler); -+ MMIO_DH(GEN8_DE_PIPE_IIR(PIPE_B), D_BDW_PLUS, NULL, -+ intel_vgpu_reg_iir_handler); -+ MMIO_D(GEN8_DE_PIPE_ISR(PIPE_B), D_BDW_PLUS); -+ -+ MMIO_DH(GEN8_DE_PIPE_IMR(PIPE_C), D_BDW_PLUS, NULL, -+ intel_vgpu_reg_imr_handler); -+ MMIO_DH(GEN8_DE_PIPE_IER(PIPE_C), D_BDW_PLUS, NULL, -+ intel_vgpu_reg_ier_handler); -+ MMIO_DH(GEN8_DE_PIPE_IIR(PIPE_C), D_BDW_PLUS, NULL, -+ intel_vgpu_reg_iir_handler); -+ MMIO_D(GEN8_DE_PIPE_ISR(PIPE_C), D_BDW_PLUS); -+ -+ MMIO_DH(GEN8_DE_PORT_IMR, D_BDW_PLUS, NULL, intel_vgpu_reg_imr_handler); -+ MMIO_DH(GEN8_DE_PORT_IER, D_BDW_PLUS, NULL, intel_vgpu_reg_ier_handler); -+ MMIO_DH(GEN8_DE_PORT_IIR, D_BDW_PLUS, NULL, intel_vgpu_reg_iir_handler); -+ MMIO_D(GEN8_DE_PORT_ISR, D_BDW_PLUS); -+ -+ MMIO_DH(GEN8_DE_MISC_IMR, D_BDW_PLUS, NULL, intel_vgpu_reg_imr_handler); -+ MMIO_DH(GEN8_DE_MISC_IER, D_BDW_PLUS, NULL, intel_vgpu_reg_ier_handler); -+ MMIO_DH(GEN8_DE_MISC_IIR, D_BDW_PLUS, NULL, intel_vgpu_reg_iir_handler); -+ MMIO_D(GEN8_DE_MISC_ISR, D_BDW_PLUS); -+ -+ MMIO_DH(GEN8_PCU_IMR, D_BDW_PLUS, NULL, intel_vgpu_reg_imr_handler); -+ MMIO_DH(GEN8_PCU_IER, D_BDW_PLUS, NULL, intel_vgpu_reg_ier_handler); -+ MMIO_DH(GEN8_PCU_IIR, D_BDW_PLUS, NULL, intel_vgpu_reg_iir_handler); -+ MMIO_D(GEN8_PCU_ISR, D_BDW_PLUS); -+ -+ MMIO_DH(GEN8_MASTER_IRQ, D_BDW_PLUS, NULL, -+ intel_vgpu_reg_master_irq_handler); -+ -+ MMIO_RING_DFH(RING_ACTHD_UDW, D_BDW_PLUS, F_CMD_ACCESS, -+ mmio_read_from_hw, NULL); -+ -+#define RING_REG(base) _MMIO((base) + 0xd0) -+ MMIO_RING_F(RING_REG, 4, F_RO, 0, -+ ~_MASKED_BIT_ENABLE(RESET_CTL_REQUEST_RESET), D_BDW_PLUS, NULL, -+ ring_reset_ctl_write); -+#undef RING_REG -+ -+#define RING_REG(base) _MMIO((base) + 0x230) -+ MMIO_RING_DFH(RING_REG, D_BDW_PLUS, 0, NULL, elsp_mmio_write); -+#undef RING_REG -+ -+#define RING_REG(base) _MMIO((base) + 0x234) -+ MMIO_RING_F(RING_REG, 8, F_RO | F_CMD_ACCESS, 0, ~0, D_BDW_PLUS, -+ NULL, NULL); -+#undef RING_REG -+ -+#define RING_REG(base) _MMIO((base) + 0x244) -+ MMIO_RING_DFH(RING_REG, D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+#undef RING_REG -+ -+#define RING_REG(base) _MMIO((base) + 0x370) -+ MMIO_RING_F(RING_REG, 48, F_RO, 0, ~0, D_BDW_PLUS, NULL, NULL); -+#undef RING_REG -+ -+#define RING_REG(base) _MMIO((base) + 0x3a0) -+ MMIO_RING_DFH(RING_REG, D_BDW_PLUS, F_MODE_MASK, NULL, NULL); -+#undef RING_REG -+ -+ MMIO_D(PIPEMISC(PIPE_A), D_BDW_PLUS); -+ MMIO_D(PIPEMISC(PIPE_B), D_BDW_PLUS); -+ MMIO_D(PIPEMISC(PIPE_C), D_BDW_PLUS); -+ MMIO_D(_MMIO(0x1c1d0), D_BDW_PLUS); -+ MMIO_D(GEN6_MBCUNIT_SNPCR, D_BDW_PLUS); -+ MMIO_D(GEN7_MISCCPCTL, D_BDW_PLUS); -+ MMIO_D(_MMIO(0x1c054), D_BDW_PLUS); -+ -+ MMIO_DH(GEN6_PCODE_MAILBOX, D_BDW_PLUS, NULL, mailbox_write); -+ -+ MMIO_D(GEN8_PRIVATE_PAT_LO, D_BDW_PLUS); -+ MMIO_D(GEN8_PRIVATE_PAT_HI, D_BDW_PLUS); -+ -+ MMIO_D(GAMTARBMODE, D_BDW_PLUS); -+ -+#define RING_REG(base) _MMIO((base) + 0x270) -+ MMIO_RING_F(RING_REG, 32, 0, 0, 0, D_BDW_PLUS, NULL, NULL); -+#undef RING_REG -+ -+ MMIO_RING_GM_RDR(RING_HWS_PGA, D_BDW_PLUS, NULL, hws_pga_write); -+ -+ MMIO_DFH(HDC_CHICKEN0, D_BDW_PLUS, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ -+ MMIO_D(CHICKEN_PIPESL_1(PIPE_A), D_BDW_PLUS); -+ MMIO_D(CHICKEN_PIPESL_1(PIPE_B), D_BDW_PLUS); -+ MMIO_D(CHICKEN_PIPESL_1(PIPE_C), D_BDW_PLUS); -+ -+ MMIO_D(WM_MISC, D_BDW); -+ MMIO_D(_MMIO(BDW_EDP_PSR_BASE), D_BDW); -+ -+ MMIO_D(_MMIO(0x6671c), D_BDW_PLUS); -+ MMIO_D(_MMIO(0x66c00), D_BDW_PLUS); -+ MMIO_D(_MMIO(0x66c04), D_BDW_PLUS); -+ -+ MMIO_D(HSW_GTT_CACHE_EN, D_BDW_PLUS); -+ -+ MMIO_D(GEN8_EU_DISABLE0, D_BDW_PLUS); -+ MMIO_D(GEN8_EU_DISABLE1, D_BDW_PLUS); -+ MMIO_D(GEN8_EU_DISABLE2, D_BDW_PLUS); -+ -+ MMIO_D(_MMIO(0xfdc), D_BDW_PLUS); -+ MMIO_DFH(GEN8_ROW_CHICKEN, D_BDW_PLUS, F_MODE_MASK | F_CMD_ACCESS, -+ NULL, NULL); -+ MMIO_DFH(GEN7_ROW_CHICKEN2, D_BDW_PLUS, F_MODE_MASK | F_CMD_ACCESS, -+ NULL, NULL); -+ MMIO_DFH(GEN8_UCGCTL6, D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ -+ MMIO_DFH(_MMIO(0xb1f0), D_BDW, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0xb1c0), D_BDW, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(GEN8_L3SQCREG4, D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0xb100), D_BDW, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0xb10c), D_BDW, F_CMD_ACCESS, NULL, NULL); -+ MMIO_D(_MMIO(0xb110), D_BDW); -+ -+ MMIO_F(_MMIO(0x24d0), 48, F_CMD_ACCESS, 0, 0, D_BDW_PLUS, -+ NULL, force_nonpriv_write); -+ -+ MMIO_D(_MMIO(0x44484), D_BDW_PLUS); -+ MMIO_D(_MMIO(0x4448c), D_BDW_PLUS); -+ -+ MMIO_DFH(_MMIO(0x83a4), D_BDW, F_CMD_ACCESS, NULL, NULL); -+ MMIO_D(GEN8_L3_LRA_1_GPGPU, D_BDW_PLUS); -+ -+ MMIO_DFH(_MMIO(0x8430), D_BDW, F_CMD_ACCESS, NULL, NULL); -+ -+ MMIO_D(_MMIO(0x110000), D_BDW_PLUS); -+ -+ MMIO_D(_MMIO(0x48400), D_BDW_PLUS); -+ -+ MMIO_D(_MMIO(0x6e570), D_BDW_PLUS); -+ MMIO_D(_MMIO(0x65f10), D_BDW_PLUS); -+ -+ MMIO_DFH(_MMIO(0xe194), D_BDW_PLUS, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0xe188), D_BDW_PLUS, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(HALF_SLICE_CHICKEN2, D_BDW_PLUS, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x2580), D_BDW_PLUS, F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ -+ MMIO_DFH(_MMIO(0x2248), D_BDW, F_CMD_ACCESS, NULL, NULL); -+ -+ MMIO_DFH(_MMIO(0xe220), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0xe230), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0xe240), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0xe260), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0xe270), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0xe280), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0xe2a0), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0xe2b0), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0xe2c0), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(_MMIO(0x21f0), D_BDW_PLUS, F_CMD_ACCESS, NULL, NULL); -+ return 0; -+} -+ -+static int init_skl_mmio_info(struct intel_gvt *gvt) -+{ -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ int ret; -+ -+ MMIO_DH(FORCEWAKE_RENDER_GEN9, D_SKL_PLUS, NULL, mul_force_wake_write); -+ MMIO_DH(FORCEWAKE_ACK_RENDER_GEN9, D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(FORCEWAKE_BLITTER_GEN9, D_SKL_PLUS, NULL, mul_force_wake_write); -+ MMIO_DH(FORCEWAKE_ACK_BLITTER_GEN9, D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(FORCEWAKE_MEDIA_GEN9, D_SKL_PLUS, NULL, mul_force_wake_write); -+ MMIO_DH(FORCEWAKE_ACK_MEDIA_GEN9, D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_F(_MMIO(_DPB_AUX_CH_CTL), 6 * 4, 0, 0, 0, D_SKL_PLUS, NULL, -+ dp_aux_ch_ctl_mmio_write); -+ MMIO_F(_MMIO(_DPC_AUX_CH_CTL), 6 * 4, 0, 0, 0, D_SKL_PLUS, NULL, -+ dp_aux_ch_ctl_mmio_write); -+ MMIO_F(_MMIO(_DPD_AUX_CH_CTL), 6 * 4, 0, 0, 0, D_SKL_PLUS, NULL, -+ dp_aux_ch_ctl_mmio_write); -+ -+ MMIO_D(HSW_PWR_WELL_CTL1, D_SKL_PLUS); -+ MMIO_DH(HSW_PWR_WELL_CTL2, D_SKL_PLUS, NULL, skl_power_well_ctl_write); -+ -+ MMIO_DH(DBUF_CTL, D_SKL_PLUS, NULL, gen9_dbuf_ctl_mmio_write); -+ -+ MMIO_D(GEN9_PG_ENABLE, D_SKL_PLUS); -+ MMIO_D(GEN9_MEDIA_PG_IDLE_HYSTERESIS, D_SKL_PLUS); -+ MMIO_D(GEN9_RENDER_PG_IDLE_HYSTERESIS, D_SKL_PLUS); -+ MMIO_DFH(GEN9_GAMT_ECO_REG_RW_IA, D_SKL_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DH(MMCD_MISC_CTRL, D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(CHICKEN_PAR1_1, D_SKL_PLUS, NULL, NULL); -+ MMIO_D(DC_STATE_EN, D_SKL_PLUS); -+ MMIO_D(DC_STATE_DEBUG, D_SKL_PLUS); -+ MMIO_D(CDCLK_CTL, D_SKL_PLUS); -+ MMIO_DH(LCPLL1_CTL, D_SKL_PLUS, NULL, skl_lcpll_write); -+ MMIO_DH(LCPLL2_CTL, D_SKL_PLUS, NULL, skl_lcpll_write); -+ MMIO_D(_MMIO(_DPLL1_CFGCR1), D_SKL_PLUS); -+ MMIO_D(_MMIO(_DPLL2_CFGCR1), D_SKL_PLUS); -+ MMIO_D(_MMIO(_DPLL3_CFGCR1), D_SKL_PLUS); -+ MMIO_D(_MMIO(_DPLL1_CFGCR2), D_SKL_PLUS); -+ MMIO_D(_MMIO(_DPLL2_CFGCR2), D_SKL_PLUS); -+ MMIO_D(_MMIO(_DPLL3_CFGCR2), D_SKL_PLUS); -+ MMIO_D(DPLL_CTRL1, D_SKL_PLUS); -+ MMIO_D(DPLL_CTRL2, D_SKL_PLUS); -+ MMIO_DH(DPLL_STATUS, D_SKL_PLUS, dpll_status_read, NULL); -+ -+ MMIO_DH(SKL_PS_WIN_POS(PIPE_A, 0), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_WIN_POS(PIPE_A, 1), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_WIN_POS(PIPE_B, 0), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_WIN_POS(PIPE_B, 1), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_WIN_POS(PIPE_C, 0), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_WIN_POS(PIPE_C, 1), D_SKL_PLUS, NULL, pf_write); -+ -+ MMIO_DH(SKL_PS_WIN_SZ(PIPE_A, 0), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_WIN_SZ(PIPE_A, 1), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_WIN_SZ(PIPE_B, 0), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_WIN_SZ(PIPE_B, 1), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_WIN_SZ(PIPE_C, 0), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_WIN_SZ(PIPE_C, 1), D_SKL_PLUS, NULL, pf_write); -+ -+ MMIO_DH(SKL_PS_CTRL(PIPE_A, 0), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_CTRL(PIPE_A, 1), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_CTRL(PIPE_B, 0), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_CTRL(PIPE_B, 1), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_CTRL(PIPE_C, 0), D_SKL_PLUS, NULL, pf_write); -+ MMIO_DH(SKL_PS_CTRL(PIPE_C, 1), D_SKL_PLUS, NULL, pf_write); -+ -+ MMIO_DH(PLANE_BUF_CFG(PIPE_A, 0), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_BUF_CFG(PIPE_A, 1), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_BUF_CFG(PIPE_A, 2), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_BUF_CFG(PIPE_A, 3), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(PLANE_BUF_CFG(PIPE_B, 0), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_BUF_CFG(PIPE_B, 1), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_BUF_CFG(PIPE_B, 2), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_BUF_CFG(PIPE_B, 3), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(PLANE_BUF_CFG(PIPE_C, 0), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_BUF_CFG(PIPE_C, 1), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_BUF_CFG(PIPE_C, 2), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_BUF_CFG(PIPE_C, 3), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(CUR_BUF_CFG(PIPE_A), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(CUR_BUF_CFG(PIPE_B), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(CUR_BUF_CFG(PIPE_C), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_F(PLANE_WM(PIPE_A, 0, 0), 4 * 8, 0, 0, 0, D_SKL_PLUS, NULL, NULL); -+ MMIO_F(PLANE_WM(PIPE_A, 1, 0), 4 * 8, 0, 0, 0, D_SKL_PLUS, NULL, NULL); -+ MMIO_F(PLANE_WM(PIPE_A, 2, 0), 4 * 8, 0, 0, 0, D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_F(PLANE_WM(PIPE_B, 0, 0), 4 * 8, 0, 0, 0, D_SKL_PLUS, NULL, NULL); -+ MMIO_F(PLANE_WM(PIPE_B, 1, 0), 4 * 8, 0, 0, 0, D_SKL_PLUS, NULL, NULL); -+ MMIO_F(PLANE_WM(PIPE_B, 2, 0), 4 * 8, 0, 0, 0, D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_F(PLANE_WM(PIPE_C, 0, 0), 4 * 8, 0, 0, 0, D_SKL_PLUS, NULL, NULL); -+ MMIO_F(PLANE_WM(PIPE_C, 1, 0), 4 * 8, 0, 0, 0, D_SKL_PLUS, NULL, NULL); -+ MMIO_F(PLANE_WM(PIPE_C, 2, 0), 4 * 8, 0, 0, 0, D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_F(CUR_WM(PIPE_A, 0), 4 * 8, 0, 0, 0, D_SKL_PLUS, NULL, NULL); -+ MMIO_F(CUR_WM(PIPE_B, 0), 4 * 8, 0, 0, 0, D_SKL_PLUS, NULL, NULL); -+ MMIO_F(CUR_WM(PIPE_C, 0), 4 * 8, 0, 0, 0, D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(PLANE_WM_TRANS(PIPE_A, 0), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_WM_TRANS(PIPE_A, 1), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_WM_TRANS(PIPE_A, 2), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(PLANE_WM_TRANS(PIPE_B, 0), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_WM_TRANS(PIPE_B, 1), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_WM_TRANS(PIPE_B, 2), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(PLANE_WM_TRANS(PIPE_C, 0), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_WM_TRANS(PIPE_C, 1), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_WM_TRANS(PIPE_C, 2), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(CUR_WM_TRANS(PIPE_A), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(CUR_WM_TRANS(PIPE_B), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(CUR_WM_TRANS(PIPE_C), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(PLANE_NV12_BUF_CFG(PIPE_A, 0), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_NV12_BUF_CFG(PIPE_A, 1), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_NV12_BUF_CFG(PIPE_A, 2), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_NV12_BUF_CFG(PIPE_A, 3), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(PLANE_NV12_BUF_CFG(PIPE_B, 0), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_NV12_BUF_CFG(PIPE_B, 1), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_NV12_BUF_CFG(PIPE_B, 2), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_NV12_BUF_CFG(PIPE_B, 3), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(PLANE_NV12_BUF_CFG(PIPE_C, 0), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_NV12_BUF_CFG(PIPE_C, 1), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_NV12_BUF_CFG(PIPE_C, 2), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(PLANE_NV12_BUF_CFG(PIPE_C, 3), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(_MMIO(_REG_701C0(PIPE_A, 1)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C0(PIPE_A, 2)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C0(PIPE_A, 3)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C0(PIPE_A, 4)), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(_MMIO(_REG_701C0(PIPE_B, 1)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C0(PIPE_B, 2)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C0(PIPE_B, 3)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C0(PIPE_B, 4)), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(_MMIO(_REG_701C0(PIPE_C, 1)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C0(PIPE_C, 2)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C0(PIPE_C, 3)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C0(PIPE_C, 4)), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(_MMIO(_REG_701C4(PIPE_A, 1)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C4(PIPE_A, 2)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C4(PIPE_A, 3)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C4(PIPE_A, 4)), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(_MMIO(_REG_701C4(PIPE_B, 1)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C4(PIPE_B, 2)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C4(PIPE_B, 3)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C4(PIPE_B, 4)), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_DH(_MMIO(_REG_701C4(PIPE_C, 1)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C4(PIPE_C, 2)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C4(PIPE_C, 3)), D_SKL_PLUS, NULL, NULL); -+ MMIO_DH(_MMIO(_REG_701C4(PIPE_C, 4)), D_SKL_PLUS, NULL, NULL); -+ -+ MMIO_D(_MMIO(_PLANE_CTL_3_A), D_SKL_PLUS); -+ MMIO_D(_MMIO(_PLANE_CTL_3_B), D_SKL_PLUS); -+ MMIO_D(_MMIO(0x72380), D_SKL_PLUS); -+ MMIO_D(_MMIO(0x7239c), D_SKL_PLUS); -+ MMIO_D(_MMIO(_PLANE_SURF_3_A), D_SKL_PLUS); -+ -+ MMIO_D(CSR_SSP_BASE, D_SKL_PLUS); -+ MMIO_D(CSR_HTP_SKL, D_SKL_PLUS); -+ MMIO_D(CSR_LAST_WRITE, D_SKL_PLUS); -+ -+ MMIO_DFH(BDW_SCRATCH1, D_SKL_PLUS, F_CMD_ACCESS, NULL, NULL); -+ -+ MMIO_D(SKL_DFSM, D_SKL_PLUS); -+ MMIO_D(DISPIO_CR_TX_BMU_CR0, D_SKL_PLUS); -+ -+ MMIO_F(GEN9_GFX_MOCS(0), 0x7f8, F_CMD_ACCESS, 0, 0, D_SKL_PLUS, -+ NULL, NULL); -+ MMIO_F(GEN7_L3CNTLREG2, 0x80, F_CMD_ACCESS, 0, 0, D_SKL_PLUS, -+ NULL, NULL); -+ -+ MMIO_D(RPM_CONFIG0, D_SKL_PLUS); -+ MMIO_D(_MMIO(0xd08), D_SKL_PLUS); -+ MMIO_D(RC6_LOCATION, D_SKL_PLUS); -+ MMIO_DFH(GEN7_FF_SLICE_CS_CHICKEN1, D_SKL_PLUS, -+ F_MODE_MASK | F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(GEN9_CS_DEBUG_MODE1, D_SKL_PLUS, F_MODE_MASK | F_CMD_ACCESS, -+ NULL, NULL); -+ -+ /* TRTT */ -+ MMIO_DFH(TRVATTL3PTRDW(0), D_SKL_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(TRVATTL3PTRDW(1), D_SKL_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(TRVATTL3PTRDW(2), D_SKL_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(TRVATTL3PTRDW(3), D_SKL_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(TRVADR, D_SKL_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DFH(TRTTE, D_SKL_PLUS, F_CMD_ACCESS, -+ NULL, gen9_trtte_write); -+ MMIO_DH(_MMIO(0x4dfc), D_SKL_PLUS, NULL, gen9_trtt_chicken_write); -+ -+ MMIO_D(_MMIO(0x46430), D_SKL_PLUS); -+ -+ MMIO_D(_MMIO(0x46520), D_SKL_PLUS); -+ -+ MMIO_D(_MMIO(0xc403c), D_SKL_PLUS); -+ MMIO_DFH(GEN8_GARBCNTL, D_SKL_PLUS, F_CMD_ACCESS, NULL, NULL); -+ MMIO_DH(DMA_CTRL, D_SKL_PLUS, NULL, dma_ctrl_write); -+ -+ MMIO_D(_MMIO(0x65900), D_SKL_PLUS); -+ MMIO_D(GEN6_STOLEN_RESERVED, D_SKL_PLUS); -+ MMIO_D(_MMIO(0x4068), D_SKL_PLUS); -+ MMIO_D(_MMIO(0x67054), D_SKL_PLUS); -+ MMIO_D(_MMIO(0x6e560), D_SKL_PLUS); -+ MMIO_D(_MMIO(0x6e554), D_SKL_PLUS); -+ MMIO_D(_MMIO(0x2b20), D_SKL_PLUS); -+ MMIO_D(_MMIO(0x65f00), D_SKL_PLUS); -+ MMIO_D(_MMIO(0x65f08), D_SKL_PLUS); -+ MMIO_D(_MMIO(0x320f0), D_SKL_PLUS); -+ -+ MMIO_D(_MMIO(0x70034), D_SKL_PLUS); -+ MMIO_D(_MMIO(0x71034), D_SKL_PLUS); -+ MMIO_D(_MMIO(0x72034), D_SKL_PLUS); -+ -+ MMIO_D(_MMIO(_PLANE_KEYVAL_1(PIPE_A)), D_SKL_PLUS); -+ MMIO_D(_MMIO(_PLANE_KEYVAL_1(PIPE_B)), D_SKL_PLUS); -+ MMIO_D(_MMIO(_PLANE_KEYVAL_1(PIPE_C)), D_SKL_PLUS); -+ MMIO_D(_MMIO(_PLANE_KEYMAX_1(PIPE_A)), D_SKL_PLUS); -+ MMIO_D(_MMIO(_PLANE_KEYMAX_1(PIPE_B)), D_SKL_PLUS); -+ MMIO_D(_MMIO(_PLANE_KEYMAX_1(PIPE_C)), D_SKL_PLUS); -+ MMIO_D(_MMIO(_PLANE_KEYMSK_1(PIPE_A)), D_SKL_PLUS); -+ MMIO_D(_MMIO(_PLANE_KEYMSK_1(PIPE_B)), D_SKL_PLUS); -+ MMIO_D(_MMIO(_PLANE_KEYMSK_1(PIPE_C)), D_SKL_PLUS); -+ -+ MMIO_D(_MMIO(0x44500), D_SKL_PLUS); -+#define CSFE_CHICKEN1_REG(base) _MMIO((base) + 0xD4) -+ MMIO_RING_DFH(CSFE_CHICKEN1_REG, D_SKL_PLUS, F_MODE_MASK | F_CMD_ACCESS, -+ NULL, csfe_chicken1_mmio_write); -+#undef CSFE_CHICKEN1_REG -+ MMIO_DFH(GEN8_HDC_CHICKEN1, D_SKL_PLUS, F_MODE_MASK | F_CMD_ACCESS, -+ NULL, NULL); -+ MMIO_DFH(GEN9_WM_CHICKEN3, D_SKL_PLUS, F_MODE_MASK | F_CMD_ACCESS, -+ NULL, NULL); -+ -+ MMIO_D(GAMT_CHKN_BIT_REG, D_KBL); -+ MMIO_D(GEN9_CTX_PREEMPT_REG, D_KBL | D_SKL); -+ -+ return 0; -+} -+ -+static int init_bxt_mmio_info(struct intel_gvt *gvt) -+{ -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ int ret; -+ -+ MMIO_F(_MMIO(0x80000), 0x3000, 0, 0, 0, D_BXT, NULL, NULL); -+ -+ MMIO_D(GEN7_SAMPLER_INSTDONE, D_BXT); -+ MMIO_D(GEN7_ROW_INSTDONE, D_BXT); -+ MMIO_D(GEN8_FAULT_TLB_DATA0, D_BXT); -+ MMIO_D(GEN8_FAULT_TLB_DATA1, D_BXT); -+ MMIO_D(ERROR_GEN6, D_BXT); -+ MMIO_D(DONE_REG, D_BXT); -+ MMIO_D(EIR, D_BXT); -+ MMIO_D(PGTBL_ER, D_BXT); -+ MMIO_D(_MMIO(0x4194), D_BXT); -+ MMIO_D(_MMIO(0x4294), D_BXT); -+ MMIO_D(_MMIO(0x4494), D_BXT); -+ -+ MMIO_RING_D(RING_PSMI_CTL, D_BXT); -+ MMIO_RING_D(RING_DMA_FADD, D_BXT); -+ MMIO_RING_D(RING_DMA_FADD_UDW, D_BXT); -+ MMIO_RING_D(RING_IPEHR, D_BXT); -+ MMIO_RING_D(RING_INSTPS, D_BXT); -+ MMIO_RING_D(RING_BBADDR_UDW, D_BXT); -+ MMIO_RING_D(RING_BBSTATE, D_BXT); -+ MMIO_RING_D(RING_IPEIR, D_BXT); -+ -+ MMIO_F(SOFT_SCRATCH(0), 16 * 4, 0, 0, 0, D_BXT, NULL, NULL); -+ -+ MMIO_DH(BXT_P_CR_GT_DISP_PWRON, D_BXT, NULL, bxt_gt_disp_pwron_write); -+ MMIO_D(BXT_RP_STATE_CAP, D_BXT); -+ MMIO_DH(BXT_PHY_CTL_FAMILY(DPIO_PHY0), D_BXT, -+ NULL, bxt_phy_ctl_family_write); -+ MMIO_DH(BXT_PHY_CTL_FAMILY(DPIO_PHY1), D_BXT, -+ NULL, bxt_phy_ctl_family_write); -+ MMIO_D(BXT_PHY_CTL(PORT_A), D_BXT); -+ MMIO_D(BXT_PHY_CTL(PORT_B), D_BXT); -+ MMIO_D(BXT_PHY_CTL(PORT_C), D_BXT); -+ MMIO_DH(BXT_PORT_PLL_ENABLE(PORT_A), D_BXT, -+ NULL, bxt_port_pll_enable_write); -+ MMIO_DH(BXT_PORT_PLL_ENABLE(PORT_B), D_BXT, -+ NULL, bxt_port_pll_enable_write); -+ MMIO_DH(BXT_PORT_PLL_ENABLE(PORT_C), D_BXT, NULL, -+ bxt_port_pll_enable_write); -+ -+ MMIO_D(BXT_PORT_CL1CM_DW0(DPIO_PHY0), D_BXT); -+ MMIO_D(BXT_PORT_CL1CM_DW9(DPIO_PHY0), D_BXT); -+ MMIO_D(BXT_PORT_CL1CM_DW10(DPIO_PHY0), D_BXT); -+ MMIO_D(BXT_PORT_CL1CM_DW28(DPIO_PHY0), D_BXT); -+ MMIO_D(BXT_PORT_CL1CM_DW30(DPIO_PHY0), D_BXT); -+ MMIO_D(BXT_PORT_CL2CM_DW6(DPIO_PHY0), D_BXT); -+ MMIO_D(BXT_PORT_REF_DW3(DPIO_PHY0), D_BXT); -+ MMIO_D(BXT_PORT_REF_DW6(DPIO_PHY0), D_BXT); -+ MMIO_D(BXT_PORT_REF_DW8(DPIO_PHY0), D_BXT); -+ -+ MMIO_D(BXT_PORT_CL1CM_DW0(DPIO_PHY1), D_BXT); -+ MMIO_D(BXT_PORT_CL1CM_DW9(DPIO_PHY1), D_BXT); -+ MMIO_D(BXT_PORT_CL1CM_DW10(DPIO_PHY1), D_BXT); -+ MMIO_D(BXT_PORT_CL1CM_DW28(DPIO_PHY1), D_BXT); -+ MMIO_D(BXT_PORT_CL1CM_DW30(DPIO_PHY1), D_BXT); -+ MMIO_D(BXT_PORT_CL2CM_DW6(DPIO_PHY1), D_BXT); -+ MMIO_D(BXT_PORT_REF_DW3(DPIO_PHY1), D_BXT); -+ MMIO_D(BXT_PORT_REF_DW6(DPIO_PHY1), D_BXT); -+ MMIO_D(BXT_PORT_REF_DW8(DPIO_PHY1), D_BXT); -+ -+ MMIO_D(BXT_PORT_PLL_EBB_0(DPIO_PHY0, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_PLL_EBB_4(DPIO_PHY0, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_PCS_DW10_LN01(DPIO_PHY0, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_PCS_DW10_GRP(DPIO_PHY0, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_PCS_DW12_LN01(DPIO_PHY0, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_PCS_DW12_LN23(DPIO_PHY0, DPIO_CH0), D_BXT); -+ MMIO_DH(BXT_PORT_PCS_DW12_GRP(DPIO_PHY0, DPIO_CH0), D_BXT, -+ NULL, bxt_pcs_dw12_grp_write); -+ MMIO_D(BXT_PORT_TX_DW2_LN0(DPIO_PHY0, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW2_GRP(DPIO_PHY0, DPIO_CH0), D_BXT); -+ MMIO_DH(BXT_PORT_TX_DW3_LN0(DPIO_PHY0, DPIO_CH0), D_BXT, -+ bxt_port_tx_dw3_read, NULL); -+ MMIO_D(BXT_PORT_TX_DW3_GRP(DPIO_PHY0, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW4_LN0(DPIO_PHY0, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW4_GRP(DPIO_PHY0, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW14_LN(DPIO_PHY0, DPIO_CH0, 0), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW14_LN(DPIO_PHY0, DPIO_CH0, 1), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW14_LN(DPIO_PHY0, DPIO_CH0, 2), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW14_LN(DPIO_PHY0, DPIO_CH0, 3), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH0, 0), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH0, 1), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH0, 2), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH0, 3), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH0, 6), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH0, 8), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH0, 9), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH0, 10), D_BXT); -+ -+ MMIO_D(BXT_PORT_PLL_EBB_0(DPIO_PHY0, DPIO_CH1), D_BXT); -+ MMIO_D(BXT_PORT_PLL_EBB_4(DPIO_PHY0, DPIO_CH1), D_BXT); -+ MMIO_D(BXT_PORT_PCS_DW10_LN01(DPIO_PHY0, DPIO_CH1), D_BXT); -+ MMIO_D(BXT_PORT_PCS_DW10_GRP(DPIO_PHY0, DPIO_CH1), D_BXT); -+ MMIO_D(BXT_PORT_PCS_DW12_LN01(DPIO_PHY0, DPIO_CH1), D_BXT); -+ MMIO_D(BXT_PORT_PCS_DW12_LN23(DPIO_PHY0, DPIO_CH1), D_BXT); -+ MMIO_DH(BXT_PORT_PCS_DW12_GRP(DPIO_PHY0, DPIO_CH1), D_BXT, -+ NULL, bxt_pcs_dw12_grp_write); -+ MMIO_D(BXT_PORT_TX_DW2_LN0(DPIO_PHY0, DPIO_CH1), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW2_GRP(DPIO_PHY0, DPIO_CH1), D_BXT); -+ MMIO_DH(BXT_PORT_TX_DW3_LN0(DPIO_PHY0, DPIO_CH1), D_BXT, -+ bxt_port_tx_dw3_read, NULL); -+ MMIO_D(BXT_PORT_TX_DW3_GRP(DPIO_PHY0, DPIO_CH1), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW4_LN0(DPIO_PHY0, DPIO_CH1), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW4_GRP(DPIO_PHY0, DPIO_CH1), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW14_LN(DPIO_PHY0, DPIO_CH1, 0), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW14_LN(DPIO_PHY0, DPIO_CH1, 1), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW14_LN(DPIO_PHY0, DPIO_CH1, 2), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW14_LN(DPIO_PHY0, DPIO_CH1, 3), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH1, 0), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH1, 1), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH1, 2), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH1, 3), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH1, 6), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH1, 8), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH1, 9), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY0, DPIO_CH1, 10), D_BXT); -+ -+ MMIO_D(BXT_PORT_PLL_EBB_0(DPIO_PHY1, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_PLL_EBB_4(DPIO_PHY1, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_PCS_DW10_LN01(DPIO_PHY1, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_PCS_DW10_GRP(DPIO_PHY1, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_PCS_DW12_LN01(DPIO_PHY1, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_PCS_DW12_LN23(DPIO_PHY1, DPIO_CH0), D_BXT); -+ MMIO_DH(BXT_PORT_PCS_DW12_GRP(DPIO_PHY1, DPIO_CH0), D_BXT, -+ NULL, bxt_pcs_dw12_grp_write); -+ MMIO_D(BXT_PORT_TX_DW2_LN0(DPIO_PHY1, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW2_GRP(DPIO_PHY1, DPIO_CH0), D_BXT); -+ MMIO_DH(BXT_PORT_TX_DW3_LN0(DPIO_PHY1, DPIO_CH0), D_BXT, -+ bxt_port_tx_dw3_read, NULL); -+ MMIO_D(BXT_PORT_TX_DW3_GRP(DPIO_PHY1, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW4_LN0(DPIO_PHY1, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW4_GRP(DPIO_PHY1, DPIO_CH0), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW14_LN(DPIO_PHY1, DPIO_CH0, 0), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW14_LN(DPIO_PHY1, DPIO_CH0, 1), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW14_LN(DPIO_PHY1, DPIO_CH0, 2), D_BXT); -+ MMIO_D(BXT_PORT_TX_DW14_LN(DPIO_PHY1, DPIO_CH0, 3), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY1, DPIO_CH0, 0), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY1, DPIO_CH0, 1), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY1, DPIO_CH0, 2), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY1, DPIO_CH0, 3), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY1, DPIO_CH0, 6), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY1, DPIO_CH0, 8), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY1, DPIO_CH0, 9), D_BXT); -+ MMIO_D(BXT_PORT_PLL(DPIO_PHY1, DPIO_CH0, 10), D_BXT); -+ -+ MMIO_D(BXT_DE_PLL_CTL, D_BXT); -+ MMIO_DH(BXT_DE_PLL_ENABLE, D_BXT, NULL, bxt_de_pll_enable_write); -+ MMIO_D(BXT_DSI_PLL_CTL, D_BXT); -+ MMIO_D(BXT_DSI_PLL_ENABLE, D_BXT); -+ -+ MMIO_D(GEN9_CLKGATE_DIS_0, D_BXT); -+ MMIO_D(GEN9_CLKGATE_DIS_4, D_BXT); -+ -+ MMIO_D(HSW_TVIDEO_DIP_GCP(TRANSCODER_A), D_BXT); -+ MMIO_D(HSW_TVIDEO_DIP_GCP(TRANSCODER_B), D_BXT); -+ MMIO_D(HSW_TVIDEO_DIP_GCP(TRANSCODER_C), D_BXT); -+ -+ MMIO_D(RC6_CTX_BASE, D_BXT); -+ -+ MMIO_D(GEN8_PUSHBUS_CONTROL, D_BXT); -+ MMIO_D(GEN8_PUSHBUS_ENABLE, D_BXT); -+ MMIO_D(GEN8_PUSHBUS_SHIFT, D_BXT); -+ MMIO_D(GEN6_GFXPAUSE, D_BXT); -+ MMIO_DFH(GEN8_L3SQCREG1, D_BXT, F_CMD_ACCESS, NULL, NULL); -+ -+ MMIO_DFH(GEN9_CTX_PREEMPT_REG, D_BXT, F_CMD_ACCESS, NULL, NULL); -+ -+ return 0; -+} -+ -+static struct gvt_mmio_block *find_mmio_block(struct intel_gvt *gvt, -+ unsigned int offset) -+{ -+ unsigned long device = intel_gvt_get_device_type(gvt); -+ struct gvt_mmio_block *block = gvt->mmio.mmio_block; -+ int num = gvt->mmio.num_mmio_block; -+ int i; -+ -+ for (i = 0; i < num; i++, block++) { -+ if (!(device & block->device)) -+ continue; -+ if (offset >= i915_mmio_reg_offset(block->offset) && -+ offset < i915_mmio_reg_offset(block->offset) + block->size) -+ return block; -+ } -+ return NULL; -+} -+ -+/** -+ * intel_gvt_clean_mmio_info - clean up MMIO information table for GVT device -+ * @gvt: GVT device -+ * -+ * This function is called at the driver unloading stage, to clean up the MMIO -+ * information table of GVT device -+ * -+ */ -+void intel_gvt_clean_mmio_info(struct intel_gvt *gvt) -+{ -+ struct hlist_node *tmp; -+ struct intel_gvt_mmio_info *e; -+ int i; -+ -+ hash_for_each_safe(gvt->mmio.mmio_info_table, i, tmp, e, node) -+ kfree(e); -+ -+ vfree(gvt->mmio.mmio_attribute); -+ gvt->mmio.mmio_attribute = NULL; -+} -+ -+/* Special MMIO blocks. */ -+static struct gvt_mmio_block mmio_blocks[] = { -+ {D_SKL_PLUS, _MMIO(CSR_MMIO_START_RANGE), 0x3000, NULL, NULL}, -+ {D_ALL, _MMIO(MCHBAR_MIRROR_BASE_SNB), 0x40000, NULL, NULL}, -+ {D_ALL, _MMIO(VGT_PVINFO_PAGE), VGT_PVINFO_SIZE, -+ pvinfo_mmio_read, pvinfo_mmio_write}, -+ {D_ALL, LGC_PALETTE(PIPE_A, 0), 1024, NULL, NULL}, -+ {D_ALL, LGC_PALETTE(PIPE_B, 0), 1024, NULL, NULL}, -+ {D_ALL, LGC_PALETTE(PIPE_C, 0), 1024, NULL, NULL}, -+}; -+ -+/** -+ * intel_gvt_setup_mmio_info - setup MMIO information table for GVT device -+ * @gvt: GVT device -+ * -+ * This function is called at the initialization stage, to setup the MMIO -+ * information table for GVT device -+ * -+ * Returns: -+ * zero on success, negative if failed. -+ */ -+int intel_gvt_setup_mmio_info(struct intel_gvt *gvt) -+{ -+ struct intel_gvt_device_info *info = &gvt->device_info; -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ int size = info->mmio_size / 4 * sizeof(*gvt->mmio.mmio_attribute); -+ int ret; -+ -+ gvt->mmio.mmio_attribute = vzalloc(size); -+ if (!gvt->mmio.mmio_attribute) -+ return -ENOMEM; -+ -+ ret = init_generic_mmio_info(gvt); -+ if (ret) -+ goto err; -+ -+ if (IS_BROADWELL(dev_priv)) { -+ ret = init_broadwell_mmio_info(gvt); -+ if (ret) -+ goto err; -+ } else if (IS_SKYLAKE(dev_priv) -+ || IS_KABYLAKE(dev_priv) -+ || IS_COFFEELAKE(dev_priv)) { -+ ret = init_broadwell_mmio_info(gvt); -+ if (ret) -+ goto err; -+ ret = init_skl_mmio_info(gvt); -+ if (ret) -+ goto err; -+ } else if (IS_BROXTON(dev_priv)) { -+ ret = init_broadwell_mmio_info(gvt); -+ if (ret) -+ goto err; -+ ret = init_skl_mmio_info(gvt); -+ if (ret) -+ goto err; -+ ret = init_bxt_mmio_info(gvt); -+ if (ret) -+ goto err; -+ } -+ -+ gvt->mmio.mmio_block = mmio_blocks; -+ gvt->mmio.num_mmio_block = ARRAY_SIZE(mmio_blocks); -+ -+ return 0; -+err: -+ intel_gvt_clean_mmio_info(gvt); -+ return ret; -+} -+ -+/** -+ * intel_gvt_for_each_tracked_mmio - iterate each tracked mmio -+ * @gvt: a GVT device -+ * @handler: the handler -+ * @data: private data given to handler -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_gvt_for_each_tracked_mmio(struct intel_gvt *gvt, -+ int (*handler)(struct intel_gvt *gvt, u32 offset, void *data), -+ void *data) -+{ -+ struct gvt_mmio_block *block = gvt->mmio.mmio_block; -+ struct intel_gvt_mmio_info *e; -+ int i, j, ret; -+ -+ hash_for_each(gvt->mmio.mmio_info_table, i, e, node) { -+ ret = handler(gvt, e->offset, data); -+ if (ret) -+ return ret; -+ } -+ -+ for (i = 0; i < gvt->mmio.num_mmio_block; i++, block++) { -+ for (j = 0; j < block->size; j += 4) { -+ ret = handler(gvt, -+ i915_mmio_reg_offset(block->offset) + j, -+ data); -+ if (ret) -+ return ret; -+ } -+ } -+ return 0; -+} -+ -+/** -+ * intel_vgpu_default_mmio_read - default MMIO read handler -+ * @vgpu: a vGPU -+ * @offset: access offset -+ * @p_data: data return buffer -+ * @bytes: access data length -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_vgpu_default_mmio_read(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ read_vreg(vgpu, offset, p_data, bytes); -+ return 0; -+} -+ -+/** -+ * intel_t_default_mmio_write - default MMIO write handler -+ * @vgpu: a vGPU -+ * @offset: access offset -+ * @p_data: write data buffer -+ * @bytes: access data length -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_vgpu_default_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ write_vreg(vgpu, offset, p_data, bytes); -+ return 0; -+} -+ -+/** -+ * intel_vgpu_mask_mmio_write - write mask register -+ * @vgpu: a vGPU -+ * @offset: access offset -+ * @p_data: write data buffer -+ * @bytes: access data length -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_vgpu_mask_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes) -+{ -+ u32 mask, old_vreg; -+ -+ old_vreg = vgpu_vreg(vgpu, offset); -+ write_vreg(vgpu, offset, p_data, bytes); -+ mask = vgpu_vreg(vgpu, offset) >> 16; -+ vgpu_vreg(vgpu, offset) = (old_vreg & ~mask) | -+ (vgpu_vreg(vgpu, offset) & mask); -+ -+ return 0; -+} -+ -+/** -+ * intel_gvt_in_force_nonpriv_whitelist - if a mmio is in whitelist to be -+ * force-nopriv register -+ * -+ * @gvt: a GVT device -+ * @offset: register offset -+ * -+ * Returns: -+ * True if the register is in force-nonpriv whitelist; -+ * False if outside; -+ */ -+bool intel_gvt_in_force_nonpriv_whitelist(struct intel_gvt *gvt, -+ unsigned int offset) -+{ -+ return in_whitelist(offset); -+} -+ -+/** -+ * intel_vgpu_mmio_reg_rw - emulate tracked mmio registers -+ * @vgpu: a vGPU -+ * @offset: register offset -+ * @pdata: data buffer -+ * @bytes: data length -+ * @is_read: read or write -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_vgpu_mmio_reg_rw(struct intel_vgpu *vgpu, unsigned int offset, -+ void *pdata, unsigned int bytes, bool is_read) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct intel_gvt_mmio_info *mmio_info; -+ struct gvt_mmio_block *mmio_block; -+ gvt_mmio_func func; -+ int ret; -+ -+ if (WARN_ON(bytes > 8)) -+ return -EINVAL; -+ -+ /* -+ * Handle special MMIO blocks. -+ */ -+ mmio_block = find_mmio_block(gvt, offset); -+ if (mmio_block) { -+ func = is_read ? mmio_block->read : mmio_block->write; -+ if (func) -+ return func(vgpu, offset, pdata, bytes); -+ goto default_rw; -+ } -+ -+ /* -+ * Normal tracked MMIOs. -+ */ -+ mmio_info = find_mmio_info(gvt, offset); -+ if (!mmio_info) { -+ gvt_dbg_mmio("untracked MMIO %08x len %d\n", offset, bytes); -+ goto default_rw; -+ } -+ -+ if (is_read) -+ return mmio_info->read(vgpu, offset, pdata, bytes); -+ else { -+ u64 ro_mask = mmio_info->ro_mask; -+ u32 old_vreg = 0; -+ u64 data = 0; -+ -+ if (intel_gvt_mmio_has_mode_mask(gvt, mmio_info->offset)) { -+ old_vreg = vgpu_vreg(vgpu, offset); -+ } -+ -+ if (likely(!ro_mask)) -+ ret = mmio_info->write(vgpu, offset, pdata, bytes); -+ else if (!~ro_mask) { -+ gvt_vgpu_err("try to write RO reg %x\n", offset); -+ return 0; -+ } else { -+ /* keep the RO bits in the virtual register */ -+ memcpy(&data, pdata, bytes); -+ data &= ~ro_mask; -+ data |= vgpu_vreg(vgpu, offset) & ro_mask; -+ ret = mmio_info->write(vgpu, offset, &data, bytes); -+ } -+ -+ /* higher 16bits of mode ctl regs are mask bits for change */ -+ if (intel_gvt_mmio_has_mode_mask(gvt, mmio_info->offset)) { -+ u32 mask = vgpu_vreg(vgpu, offset) >> 16; -+ -+ vgpu_vreg(vgpu, offset) = (old_vreg & ~mask) -+ | (vgpu_vreg(vgpu, offset) & mask); -+ } -+ } -+ -+ return ret; -+ -+default_rw: -+ return is_read ? -+ intel_vgpu_default_mmio_read(vgpu, offset, pdata, bytes) : -+ intel_vgpu_default_mmio_write(vgpu, offset, pdata, bytes); -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/hypercall.h b/drivers/gpu/drm/i915_legacy/gvt/hypercall.h -new file mode 100644 -index 000000000000..4862fb12778e ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/hypercall.h -@@ -0,0 +1,78 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eddie Dong -+ * Dexuan Cui -+ * Jike Song -+ * -+ * Contributors: -+ * Zhi Wang -+ * -+ */ -+ -+#ifndef _GVT_HYPERCALL_H_ -+#define _GVT_HYPERCALL_H_ -+ -+enum hypervisor_type { -+ INTEL_GVT_HYPERVISOR_XEN = 0, -+ INTEL_GVT_HYPERVISOR_KVM, -+}; -+ -+/* -+ * Specific GVT-g MPT modules function collections. Currently GVT-g supports -+ * both Xen and KVM by providing dedicated hypervisor-related MPT modules. -+ */ -+struct intel_gvt_mpt { -+ enum hypervisor_type type; -+ int (*host_init)(struct device *dev, void *gvt, const void *ops); -+ void (*host_exit)(struct device *dev); -+ int (*attach_vgpu)(void *vgpu, unsigned long *handle); -+ void (*detach_vgpu)(void *vgpu); -+ int (*inject_msi)(unsigned long handle, u32 addr, u16 data); -+ unsigned long (*from_virt_to_mfn)(void *p); -+ int (*enable_page_track)(unsigned long handle, u64 gfn); -+ int (*disable_page_track)(unsigned long handle, u64 gfn); -+ int (*read_gpa)(unsigned long handle, unsigned long gpa, void *buf, -+ unsigned long len); -+ int (*write_gpa)(unsigned long handle, unsigned long gpa, void *buf, -+ unsigned long len); -+ unsigned long (*gfn_to_mfn)(unsigned long handle, unsigned long gfn); -+ -+ int (*dma_map_guest_page)(unsigned long handle, unsigned long gfn, -+ unsigned long size, dma_addr_t *dma_addr); -+ void (*dma_unmap_guest_page)(unsigned long handle, dma_addr_t dma_addr); -+ -+ int (*map_gfn_to_mfn)(unsigned long handle, unsigned long gfn, -+ unsigned long mfn, unsigned int nr, bool map); -+ int (*set_trap_area)(unsigned long handle, u64 start, u64 end, -+ bool map); -+ int (*set_opregion)(void *vgpu); -+ int (*set_edid)(void *vgpu, int port_num); -+ int (*get_vfio_device)(void *vgpu); -+ void (*put_vfio_device)(void *vgpu); -+ bool (*is_valid_gfn)(unsigned long handle, unsigned long gfn); -+}; -+ -+extern struct intel_gvt_mpt xengt_mpt; -+ -+#endif /* _GVT_HYPERCALL_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/gvt/interrupt.c b/drivers/gpu/drm/i915_legacy/gvt/interrupt.c -new file mode 100644 -index 000000000000..951681813230 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/interrupt.c -@@ -0,0 +1,710 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Kevin Tian -+ * Zhi Wang -+ * -+ * Contributors: -+ * Min he -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "gvt.h" -+#include "trace.h" -+ -+/* common offset among interrupt control registers */ -+#define regbase_to_isr(base) (base) -+#define regbase_to_imr(base) (base + 0x4) -+#define regbase_to_iir(base) (base + 0x8) -+#define regbase_to_ier(base) (base + 0xC) -+ -+#define iir_to_regbase(iir) (iir - 0x8) -+#define ier_to_regbase(ier) (ier - 0xC) -+ -+#define get_event_virt_handler(irq, e) (irq->events[e].v_handler) -+#define get_irq_info(irq, e) (irq->events[e].info) -+ -+#define irq_to_gvt(irq) \ -+ container_of(irq, struct intel_gvt, irq) -+ -+static void update_upstream_irq(struct intel_vgpu *vgpu, -+ struct intel_gvt_irq_info *info); -+ -+static const char * const irq_name[INTEL_GVT_EVENT_MAX] = { -+ [RCS_MI_USER_INTERRUPT] = "Render CS MI USER INTERRUPT", -+ [RCS_DEBUG] = "Render EU debug from SVG", -+ [RCS_MMIO_SYNC_FLUSH] = "Render MMIO sync flush status", -+ [RCS_CMD_STREAMER_ERR] = "Render CS error interrupt", -+ [RCS_PIPE_CONTROL] = "Render PIPE CONTROL notify", -+ [RCS_WATCHDOG_EXCEEDED] = "Render CS Watchdog counter exceeded", -+ [RCS_PAGE_DIRECTORY_FAULT] = "Render page directory faults", -+ [RCS_AS_CONTEXT_SWITCH] = "Render AS Context Switch Interrupt", -+ -+ [VCS_MI_USER_INTERRUPT] = "Video CS MI USER INTERRUPT", -+ [VCS_MMIO_SYNC_FLUSH] = "Video MMIO sync flush status", -+ [VCS_CMD_STREAMER_ERR] = "Video CS error interrupt", -+ [VCS_MI_FLUSH_DW] = "Video MI FLUSH DW notify", -+ [VCS_WATCHDOG_EXCEEDED] = "Video CS Watchdog counter exceeded", -+ [VCS_PAGE_DIRECTORY_FAULT] = "Video page directory faults", -+ [VCS_AS_CONTEXT_SWITCH] = "Video AS Context Switch Interrupt", -+ [VCS2_MI_USER_INTERRUPT] = "VCS2 Video CS MI USER INTERRUPT", -+ [VCS2_MI_FLUSH_DW] = "VCS2 Video MI FLUSH DW notify", -+ [VCS2_AS_CONTEXT_SWITCH] = "VCS2 Context Switch Interrupt", -+ -+ [BCS_MI_USER_INTERRUPT] = "Blitter CS MI USER INTERRUPT", -+ [BCS_MMIO_SYNC_FLUSH] = "Billter MMIO sync flush status", -+ [BCS_CMD_STREAMER_ERR] = "Blitter CS error interrupt", -+ [BCS_MI_FLUSH_DW] = "Blitter MI FLUSH DW notify", -+ [BCS_PAGE_DIRECTORY_FAULT] = "Blitter page directory faults", -+ [BCS_AS_CONTEXT_SWITCH] = "Blitter AS Context Switch Interrupt", -+ -+ [VECS_MI_FLUSH_DW] = "Video Enhanced Streamer MI FLUSH DW notify", -+ [VECS_AS_CONTEXT_SWITCH] = "VECS Context Switch Interrupt", -+ -+ [PIPE_A_FIFO_UNDERRUN] = "Pipe A FIFO underrun", -+ [PIPE_A_CRC_ERR] = "Pipe A CRC error", -+ [PIPE_A_CRC_DONE] = "Pipe A CRC done", -+ [PIPE_A_VSYNC] = "Pipe A vsync", -+ [PIPE_A_LINE_COMPARE] = "Pipe A line compare", -+ [PIPE_A_ODD_FIELD] = "Pipe A odd field", -+ [PIPE_A_EVEN_FIELD] = "Pipe A even field", -+ [PIPE_A_VBLANK] = "Pipe A vblank", -+ [PIPE_B_FIFO_UNDERRUN] = "Pipe B FIFO underrun", -+ [PIPE_B_CRC_ERR] = "Pipe B CRC error", -+ [PIPE_B_CRC_DONE] = "Pipe B CRC done", -+ [PIPE_B_VSYNC] = "Pipe B vsync", -+ [PIPE_B_LINE_COMPARE] = "Pipe B line compare", -+ [PIPE_B_ODD_FIELD] = "Pipe B odd field", -+ [PIPE_B_EVEN_FIELD] = "Pipe B even field", -+ [PIPE_B_VBLANK] = "Pipe B vblank", -+ [PIPE_C_VBLANK] = "Pipe C vblank", -+ [DPST_PHASE_IN] = "DPST phase in event", -+ [DPST_HISTOGRAM] = "DPST histogram event", -+ [GSE] = "GSE", -+ [DP_A_HOTPLUG] = "DP A Hotplug", -+ [AUX_CHANNEL_A] = "AUX Channel A", -+ [PERF_COUNTER] = "Performance counter", -+ [POISON] = "Poison", -+ [GTT_FAULT] = "GTT fault", -+ [PRIMARY_A_FLIP_DONE] = "Primary Plane A flip done", -+ [PRIMARY_B_FLIP_DONE] = "Primary Plane B flip done", -+ [PRIMARY_C_FLIP_DONE] = "Primary Plane C flip done", -+ [SPRITE_A_FLIP_DONE] = "Sprite Plane A flip done", -+ [SPRITE_B_FLIP_DONE] = "Sprite Plane B flip done", -+ [SPRITE_C_FLIP_DONE] = "Sprite Plane C flip done", -+ -+ [PCU_THERMAL] = "PCU Thermal Event", -+ [PCU_PCODE2DRIVER_MAILBOX] = "PCU pcode2driver mailbox event", -+ -+ [FDI_RX_INTERRUPTS_TRANSCODER_A] = "FDI RX Interrupts Combined A", -+ [AUDIO_CP_CHANGE_TRANSCODER_A] = "Audio CP Change Transcoder A", -+ [AUDIO_CP_REQUEST_TRANSCODER_A] = "Audio CP Request Transcoder A", -+ [FDI_RX_INTERRUPTS_TRANSCODER_B] = "FDI RX Interrupts Combined B", -+ [AUDIO_CP_CHANGE_TRANSCODER_B] = "Audio CP Change Transcoder B", -+ [AUDIO_CP_REQUEST_TRANSCODER_B] = "Audio CP Request Transcoder B", -+ [FDI_RX_INTERRUPTS_TRANSCODER_C] = "FDI RX Interrupts Combined C", -+ [AUDIO_CP_CHANGE_TRANSCODER_C] = "Audio CP Change Transcoder C", -+ [AUDIO_CP_REQUEST_TRANSCODER_C] = "Audio CP Request Transcoder C", -+ [ERR_AND_DBG] = "South Error and Debug Interrupts Combined", -+ [GMBUS] = "Gmbus", -+ [SDVO_B_HOTPLUG] = "SDVO B hotplug", -+ [CRT_HOTPLUG] = "CRT Hotplug", -+ [DP_B_HOTPLUG] = "DisplayPort/HDMI/DVI B Hotplug", -+ [DP_C_HOTPLUG] = "DisplayPort/HDMI/DVI C Hotplug", -+ [DP_D_HOTPLUG] = "DisplayPort/HDMI/DVI D Hotplug", -+ [AUX_CHANNEL_B] = "AUX Channel B", -+ [AUX_CHANNEL_C] = "AUX Channel C", -+ [AUX_CHANNEL_D] = "AUX Channel D", -+ [AUDIO_POWER_STATE_CHANGE_B] = "Audio Power State change Port B", -+ [AUDIO_POWER_STATE_CHANGE_C] = "Audio Power State change Port C", -+ [AUDIO_POWER_STATE_CHANGE_D] = "Audio Power State change Port D", -+ -+ [INTEL_GVT_EVENT_RESERVED] = "RESERVED EVENTS!!!", -+}; -+ -+static inline struct intel_gvt_irq_info *regbase_to_irq_info( -+ struct intel_gvt *gvt, -+ unsigned int reg) -+{ -+ struct intel_gvt_irq *irq = &gvt->irq; -+ int i; -+ -+ for_each_set_bit(i, irq->irq_info_bitmap, INTEL_GVT_IRQ_INFO_MAX) { -+ if (i915_mmio_reg_offset(irq->info[i]->reg_base) == reg) -+ return irq->info[i]; -+ } -+ -+ return NULL; -+} -+ -+/** -+ * intel_vgpu_reg_imr_handler - Generic IMR register emulation write handler -+ * @vgpu: a vGPU -+ * @reg: register offset written by guest -+ * @p_data: register data written by guest -+ * @bytes: register data length -+ * -+ * This function is used to emulate the generic IMR register bit change -+ * behavior. -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ * -+ */ -+int intel_vgpu_reg_imr_handler(struct intel_vgpu *vgpu, -+ unsigned int reg, void *p_data, unsigned int bytes) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct intel_gvt_irq_ops *ops = gvt->irq.ops; -+ u32 imr = *(u32 *)p_data; -+ -+ trace_write_ir(vgpu->id, "IMR", reg, imr, vgpu_vreg(vgpu, reg), -+ (vgpu_vreg(vgpu, reg) ^ imr)); -+ -+ vgpu_vreg(vgpu, reg) = imr; -+ -+ ops->check_pending_irq(vgpu); -+ -+ return 0; -+} -+ -+/** -+ * intel_vgpu_reg_master_irq_handler - master IRQ write emulation handler -+ * @vgpu: a vGPU -+ * @reg: register offset written by guest -+ * @p_data: register data written by guest -+ * @bytes: register data length -+ * -+ * This function is used to emulate the master IRQ register on gen8+. -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ * -+ */ -+int intel_vgpu_reg_master_irq_handler(struct intel_vgpu *vgpu, -+ unsigned int reg, void *p_data, unsigned int bytes) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct intel_gvt_irq_ops *ops = gvt->irq.ops; -+ u32 ier = *(u32 *)p_data; -+ u32 virtual_ier = vgpu_vreg(vgpu, reg); -+ -+ trace_write_ir(vgpu->id, "MASTER_IRQ", reg, ier, virtual_ier, -+ (virtual_ier ^ ier)); -+ -+ /* -+ * GEN8_MASTER_IRQ is a special irq register, -+ * only bit 31 is allowed to be modified -+ * and treated as an IER bit. -+ */ -+ ier &= GEN8_MASTER_IRQ_CONTROL; -+ virtual_ier &= GEN8_MASTER_IRQ_CONTROL; -+ vgpu_vreg(vgpu, reg) &= ~GEN8_MASTER_IRQ_CONTROL; -+ vgpu_vreg(vgpu, reg) |= ier; -+ -+ ops->check_pending_irq(vgpu); -+ -+ return 0; -+} -+ -+/** -+ * intel_vgpu_reg_ier_handler - Generic IER write emulation handler -+ * @vgpu: a vGPU -+ * @reg: register offset written by guest -+ * @p_data: register data written by guest -+ * @bytes: register data length -+ * -+ * This function is used to emulate the generic IER register behavior. -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ * -+ */ -+int intel_vgpu_reg_ier_handler(struct intel_vgpu *vgpu, -+ unsigned int reg, void *p_data, unsigned int bytes) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct intel_gvt_irq_ops *ops = gvt->irq.ops; -+ struct intel_gvt_irq_info *info; -+ u32 ier = *(u32 *)p_data; -+ -+ trace_write_ir(vgpu->id, "IER", reg, ier, vgpu_vreg(vgpu, reg), -+ (vgpu_vreg(vgpu, reg) ^ ier)); -+ -+ vgpu_vreg(vgpu, reg) = ier; -+ -+ info = regbase_to_irq_info(gvt, ier_to_regbase(reg)); -+ if (WARN_ON(!info)) -+ return -EINVAL; -+ -+ if (info->has_upstream_irq) -+ update_upstream_irq(vgpu, info); -+ -+ ops->check_pending_irq(vgpu); -+ -+ return 0; -+} -+ -+/** -+ * intel_vgpu_reg_iir_handler - Generic IIR write emulation handler -+ * @vgpu: a vGPU -+ * @reg: register offset written by guest -+ * @p_data: register data written by guest -+ * @bytes: register data length -+ * -+ * This function is used to emulate the generic IIR register behavior. -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ * -+ */ -+int intel_vgpu_reg_iir_handler(struct intel_vgpu *vgpu, unsigned int reg, -+ void *p_data, unsigned int bytes) -+{ -+ struct intel_gvt_irq_info *info = regbase_to_irq_info(vgpu->gvt, -+ iir_to_regbase(reg)); -+ u32 iir = *(u32 *)p_data; -+ -+ trace_write_ir(vgpu->id, "IIR", reg, iir, vgpu_vreg(vgpu, reg), -+ (vgpu_vreg(vgpu, reg) ^ iir)); -+ -+ if (WARN_ON(!info)) -+ return -EINVAL; -+ -+ vgpu_vreg(vgpu, reg) &= ~iir; -+ -+ if (info->has_upstream_irq) -+ update_upstream_irq(vgpu, info); -+ return 0; -+} -+ -+static struct intel_gvt_irq_map gen8_irq_map[] = { -+ { INTEL_GVT_IRQ_INFO_MASTER, 0, INTEL_GVT_IRQ_INFO_GT0, 0xffff }, -+ { INTEL_GVT_IRQ_INFO_MASTER, 1, INTEL_GVT_IRQ_INFO_GT0, 0xffff0000 }, -+ { INTEL_GVT_IRQ_INFO_MASTER, 2, INTEL_GVT_IRQ_INFO_GT1, 0xffff }, -+ { INTEL_GVT_IRQ_INFO_MASTER, 3, INTEL_GVT_IRQ_INFO_GT1, 0xffff0000 }, -+ { INTEL_GVT_IRQ_INFO_MASTER, 4, INTEL_GVT_IRQ_INFO_GT2, 0xffff }, -+ { INTEL_GVT_IRQ_INFO_MASTER, 6, INTEL_GVT_IRQ_INFO_GT3, 0xffff }, -+ { INTEL_GVT_IRQ_INFO_MASTER, 16, INTEL_GVT_IRQ_INFO_DE_PIPE_A, ~0 }, -+ { INTEL_GVT_IRQ_INFO_MASTER, 17, INTEL_GVT_IRQ_INFO_DE_PIPE_B, ~0 }, -+ { INTEL_GVT_IRQ_INFO_MASTER, 18, INTEL_GVT_IRQ_INFO_DE_PIPE_C, ~0 }, -+ { INTEL_GVT_IRQ_INFO_MASTER, 20, INTEL_GVT_IRQ_INFO_DE_PORT, ~0 }, -+ { INTEL_GVT_IRQ_INFO_MASTER, 22, INTEL_GVT_IRQ_INFO_DE_MISC, ~0 }, -+ { INTEL_GVT_IRQ_INFO_MASTER, 23, INTEL_GVT_IRQ_INFO_PCH, ~0 }, -+ { INTEL_GVT_IRQ_INFO_MASTER, 30, INTEL_GVT_IRQ_INFO_PCU, ~0 }, -+ { -1, -1, ~0 }, -+}; -+ -+static void update_upstream_irq(struct intel_vgpu *vgpu, -+ struct intel_gvt_irq_info *info) -+{ -+ struct intel_gvt_irq *irq = &vgpu->gvt->irq; -+ struct intel_gvt_irq_map *map = irq->irq_map; -+ struct intel_gvt_irq_info *up_irq_info = NULL; -+ u32 set_bits = 0; -+ u32 clear_bits = 0; -+ int bit; -+ u32 val = vgpu_vreg(vgpu, -+ regbase_to_iir(i915_mmio_reg_offset(info->reg_base))) -+ & vgpu_vreg(vgpu, -+ regbase_to_ier(i915_mmio_reg_offset(info->reg_base))); -+ -+ if (!info->has_upstream_irq) -+ return; -+ -+ for (map = irq->irq_map; map->up_irq_bit != -1; map++) { -+ if (info->group != map->down_irq_group) -+ continue; -+ -+ if (!up_irq_info) -+ up_irq_info = irq->info[map->up_irq_group]; -+ else -+ WARN_ON(up_irq_info != irq->info[map->up_irq_group]); -+ -+ bit = map->up_irq_bit; -+ -+ if (val & map->down_irq_bitmask) -+ set_bits |= (1 << bit); -+ else -+ clear_bits |= (1 << bit); -+ } -+ -+ if (WARN_ON(!up_irq_info)) -+ return; -+ -+ if (up_irq_info->group == INTEL_GVT_IRQ_INFO_MASTER) { -+ u32 isr = i915_mmio_reg_offset(up_irq_info->reg_base); -+ -+ vgpu_vreg(vgpu, isr) &= ~clear_bits; -+ vgpu_vreg(vgpu, isr) |= set_bits; -+ } else { -+ u32 iir = regbase_to_iir( -+ i915_mmio_reg_offset(up_irq_info->reg_base)); -+ u32 imr = regbase_to_imr( -+ i915_mmio_reg_offset(up_irq_info->reg_base)); -+ -+ vgpu_vreg(vgpu, iir) |= (set_bits & ~vgpu_vreg(vgpu, imr)); -+ } -+ -+ if (up_irq_info->has_upstream_irq) -+ update_upstream_irq(vgpu, up_irq_info); -+} -+ -+static void init_irq_map(struct intel_gvt_irq *irq) -+{ -+ struct intel_gvt_irq_map *map; -+ struct intel_gvt_irq_info *up_info, *down_info; -+ int up_bit; -+ -+ for (map = irq->irq_map; map->up_irq_bit != -1; map++) { -+ up_info = irq->info[map->up_irq_group]; -+ up_bit = map->up_irq_bit; -+ down_info = irq->info[map->down_irq_group]; -+ -+ set_bit(up_bit, up_info->downstream_irq_bitmap); -+ down_info->has_upstream_irq = true; -+ -+ gvt_dbg_irq("[up] grp %d bit %d -> [down] grp %d bitmask %x\n", -+ up_info->group, up_bit, -+ down_info->group, map->down_irq_bitmask); -+ } -+} -+ -+/* =======================vEvent injection===================== */ -+static int inject_virtual_interrupt(struct intel_vgpu *vgpu) -+{ -+ return intel_gvt_hypervisor_inject_msi(vgpu); -+} -+ -+static void propagate_event(struct intel_gvt_irq *irq, -+ enum intel_gvt_event_type event, struct intel_vgpu *vgpu) -+{ -+ struct intel_gvt_irq_info *info; -+ unsigned int reg_base; -+ int bit; -+ -+ info = get_irq_info(irq, event); -+ if (WARN_ON(!info)) -+ return; -+ -+ reg_base = i915_mmio_reg_offset(info->reg_base); -+ bit = irq->events[event].bit; -+ -+ if (!test_bit(bit, (void *)&vgpu_vreg(vgpu, -+ regbase_to_imr(reg_base)))) { -+ trace_propagate_event(vgpu->id, irq_name[event], bit); -+ set_bit(bit, (void *)&vgpu_vreg(vgpu, -+ regbase_to_iir(reg_base))); -+ } -+} -+ -+/* =======================vEvent Handlers===================== */ -+static void handle_default_event_virt(struct intel_gvt_irq *irq, -+ enum intel_gvt_event_type event, struct intel_vgpu *vgpu) -+{ -+ if (!vgpu->irq.irq_warn_once[event]) { -+ gvt_dbg_core("vgpu%d: IRQ receive event %d (%s)\n", -+ vgpu->id, event, irq_name[event]); -+ vgpu->irq.irq_warn_once[event] = true; -+ } -+ propagate_event(irq, event, vgpu); -+} -+ -+/* =====================GEN specific logic======================= */ -+/* GEN8 interrupt routines. */ -+ -+#define DEFINE_GVT_GEN8_INTEL_GVT_IRQ_INFO(regname, regbase) \ -+static struct intel_gvt_irq_info gen8_##regname##_info = { \ -+ .name = #regname"-IRQ", \ -+ .reg_base = (regbase), \ -+ .bit_to_event = {[0 ... INTEL_GVT_IRQ_BITWIDTH-1] = \ -+ INTEL_GVT_EVENT_RESERVED}, \ -+} -+ -+DEFINE_GVT_GEN8_INTEL_GVT_IRQ_INFO(gt0, GEN8_GT_ISR(0)); -+DEFINE_GVT_GEN8_INTEL_GVT_IRQ_INFO(gt1, GEN8_GT_ISR(1)); -+DEFINE_GVT_GEN8_INTEL_GVT_IRQ_INFO(gt2, GEN8_GT_ISR(2)); -+DEFINE_GVT_GEN8_INTEL_GVT_IRQ_INFO(gt3, GEN8_GT_ISR(3)); -+DEFINE_GVT_GEN8_INTEL_GVT_IRQ_INFO(de_pipe_a, GEN8_DE_PIPE_ISR(PIPE_A)); -+DEFINE_GVT_GEN8_INTEL_GVT_IRQ_INFO(de_pipe_b, GEN8_DE_PIPE_ISR(PIPE_B)); -+DEFINE_GVT_GEN8_INTEL_GVT_IRQ_INFO(de_pipe_c, GEN8_DE_PIPE_ISR(PIPE_C)); -+DEFINE_GVT_GEN8_INTEL_GVT_IRQ_INFO(de_port, GEN8_DE_PORT_ISR); -+DEFINE_GVT_GEN8_INTEL_GVT_IRQ_INFO(de_misc, GEN8_DE_MISC_ISR); -+DEFINE_GVT_GEN8_INTEL_GVT_IRQ_INFO(pcu, GEN8_PCU_ISR); -+DEFINE_GVT_GEN8_INTEL_GVT_IRQ_INFO(master, GEN8_MASTER_IRQ); -+ -+static struct intel_gvt_irq_info gvt_base_pch_info = { -+ .name = "PCH-IRQ", -+ .reg_base = SDEISR, -+ .bit_to_event = {[0 ... INTEL_GVT_IRQ_BITWIDTH-1] = -+ INTEL_GVT_EVENT_RESERVED}, -+}; -+ -+static void gen8_check_pending_irq(struct intel_vgpu *vgpu) -+{ -+ struct intel_gvt_irq *irq = &vgpu->gvt->irq; -+ int i; -+ -+ if (!(vgpu_vreg(vgpu, i915_mmio_reg_offset(GEN8_MASTER_IRQ)) & -+ GEN8_MASTER_IRQ_CONTROL)) -+ return; -+ -+ for_each_set_bit(i, irq->irq_info_bitmap, INTEL_GVT_IRQ_INFO_MAX) { -+ struct intel_gvt_irq_info *info = irq->info[i]; -+ u32 reg_base; -+ -+ if (!info->has_upstream_irq) -+ continue; -+ -+ reg_base = i915_mmio_reg_offset(info->reg_base); -+ if ((vgpu_vreg(vgpu, regbase_to_iir(reg_base)) -+ & vgpu_vreg(vgpu, regbase_to_ier(reg_base)))) -+ update_upstream_irq(vgpu, info); -+ } -+ -+ if (vgpu_vreg(vgpu, i915_mmio_reg_offset(GEN8_MASTER_IRQ)) -+ & ~GEN8_MASTER_IRQ_CONTROL) -+ inject_virtual_interrupt(vgpu); -+} -+ -+static void gen8_init_irq( -+ struct intel_gvt_irq *irq) -+{ -+ struct intel_gvt *gvt = irq_to_gvt(irq); -+ -+#define SET_BIT_INFO(s, b, e, i) \ -+ do { \ -+ s->events[e].bit = b; \ -+ s->events[e].info = s->info[i]; \ -+ s->info[i]->bit_to_event[b] = e;\ -+ } while (0) -+ -+#define SET_IRQ_GROUP(s, g, i) \ -+ do { \ -+ s->info[g] = i; \ -+ (i)->group = g; \ -+ set_bit(g, s->irq_info_bitmap); \ -+ } while (0) -+ -+ SET_IRQ_GROUP(irq, INTEL_GVT_IRQ_INFO_MASTER, &gen8_master_info); -+ SET_IRQ_GROUP(irq, INTEL_GVT_IRQ_INFO_GT0, &gen8_gt0_info); -+ SET_IRQ_GROUP(irq, INTEL_GVT_IRQ_INFO_GT1, &gen8_gt1_info); -+ SET_IRQ_GROUP(irq, INTEL_GVT_IRQ_INFO_GT2, &gen8_gt2_info); -+ SET_IRQ_GROUP(irq, INTEL_GVT_IRQ_INFO_GT3, &gen8_gt3_info); -+ SET_IRQ_GROUP(irq, INTEL_GVT_IRQ_INFO_DE_PIPE_A, &gen8_de_pipe_a_info); -+ SET_IRQ_GROUP(irq, INTEL_GVT_IRQ_INFO_DE_PIPE_B, &gen8_de_pipe_b_info); -+ SET_IRQ_GROUP(irq, INTEL_GVT_IRQ_INFO_DE_PIPE_C, &gen8_de_pipe_c_info); -+ SET_IRQ_GROUP(irq, INTEL_GVT_IRQ_INFO_DE_PORT, &gen8_de_port_info); -+ SET_IRQ_GROUP(irq, INTEL_GVT_IRQ_INFO_DE_MISC, &gen8_de_misc_info); -+ SET_IRQ_GROUP(irq, INTEL_GVT_IRQ_INFO_PCU, &gen8_pcu_info); -+ SET_IRQ_GROUP(irq, INTEL_GVT_IRQ_INFO_PCH, &gvt_base_pch_info); -+ -+ /* GEN8 level 2 interrupts. */ -+ -+ /* GEN8 interrupt GT0 events */ -+ SET_BIT_INFO(irq, 0, RCS_MI_USER_INTERRUPT, INTEL_GVT_IRQ_INFO_GT0); -+ SET_BIT_INFO(irq, 4, RCS_PIPE_CONTROL, INTEL_GVT_IRQ_INFO_GT0); -+ SET_BIT_INFO(irq, 8, RCS_AS_CONTEXT_SWITCH, INTEL_GVT_IRQ_INFO_GT0); -+ -+ SET_BIT_INFO(irq, 16, BCS_MI_USER_INTERRUPT, INTEL_GVT_IRQ_INFO_GT0); -+ SET_BIT_INFO(irq, 20, BCS_MI_FLUSH_DW, INTEL_GVT_IRQ_INFO_GT0); -+ SET_BIT_INFO(irq, 24, BCS_AS_CONTEXT_SWITCH, INTEL_GVT_IRQ_INFO_GT0); -+ -+ /* GEN8 interrupt GT1 events */ -+ SET_BIT_INFO(irq, 0, VCS_MI_USER_INTERRUPT, INTEL_GVT_IRQ_INFO_GT1); -+ SET_BIT_INFO(irq, 4, VCS_MI_FLUSH_DW, INTEL_GVT_IRQ_INFO_GT1); -+ SET_BIT_INFO(irq, 8, VCS_AS_CONTEXT_SWITCH, INTEL_GVT_IRQ_INFO_GT1); -+ -+ if (HAS_ENGINE(gvt->dev_priv, VCS1)) { -+ SET_BIT_INFO(irq, 16, VCS2_MI_USER_INTERRUPT, -+ INTEL_GVT_IRQ_INFO_GT1); -+ SET_BIT_INFO(irq, 20, VCS2_MI_FLUSH_DW, -+ INTEL_GVT_IRQ_INFO_GT1); -+ SET_BIT_INFO(irq, 24, VCS2_AS_CONTEXT_SWITCH, -+ INTEL_GVT_IRQ_INFO_GT1); -+ } -+ -+ /* GEN8 interrupt GT3 events */ -+ SET_BIT_INFO(irq, 0, VECS_MI_USER_INTERRUPT, INTEL_GVT_IRQ_INFO_GT3); -+ SET_BIT_INFO(irq, 4, VECS_MI_FLUSH_DW, INTEL_GVT_IRQ_INFO_GT3); -+ SET_BIT_INFO(irq, 8, VECS_AS_CONTEXT_SWITCH, INTEL_GVT_IRQ_INFO_GT3); -+ -+ SET_BIT_INFO(irq, 0, PIPE_A_VBLANK, INTEL_GVT_IRQ_INFO_DE_PIPE_A); -+ SET_BIT_INFO(irq, 0, PIPE_B_VBLANK, INTEL_GVT_IRQ_INFO_DE_PIPE_B); -+ SET_BIT_INFO(irq, 0, PIPE_C_VBLANK, INTEL_GVT_IRQ_INFO_DE_PIPE_C); -+ -+ /* GEN8 interrupt DE PORT events */ -+ SET_BIT_INFO(irq, 0, AUX_CHANNEL_A, INTEL_GVT_IRQ_INFO_DE_PORT); -+ SET_BIT_INFO(irq, 3, DP_A_HOTPLUG, INTEL_GVT_IRQ_INFO_DE_PORT); -+ -+ /* GEN8 interrupt DE MISC events */ -+ SET_BIT_INFO(irq, 0, GSE, INTEL_GVT_IRQ_INFO_DE_MISC); -+ -+ /* PCH events */ -+ SET_BIT_INFO(irq, 17, GMBUS, INTEL_GVT_IRQ_INFO_PCH); -+ SET_BIT_INFO(irq, 19, CRT_HOTPLUG, INTEL_GVT_IRQ_INFO_PCH); -+ SET_BIT_INFO(irq, 21, DP_B_HOTPLUG, INTEL_GVT_IRQ_INFO_PCH); -+ SET_BIT_INFO(irq, 22, DP_C_HOTPLUG, INTEL_GVT_IRQ_INFO_PCH); -+ SET_BIT_INFO(irq, 23, DP_D_HOTPLUG, INTEL_GVT_IRQ_INFO_PCH); -+ -+ if (IS_BROADWELL(gvt->dev_priv)) { -+ SET_BIT_INFO(irq, 25, AUX_CHANNEL_B, INTEL_GVT_IRQ_INFO_PCH); -+ SET_BIT_INFO(irq, 26, AUX_CHANNEL_C, INTEL_GVT_IRQ_INFO_PCH); -+ SET_BIT_INFO(irq, 27, AUX_CHANNEL_D, INTEL_GVT_IRQ_INFO_PCH); -+ -+ SET_BIT_INFO(irq, 4, PRIMARY_A_FLIP_DONE, INTEL_GVT_IRQ_INFO_DE_PIPE_A); -+ SET_BIT_INFO(irq, 5, SPRITE_A_FLIP_DONE, INTEL_GVT_IRQ_INFO_DE_PIPE_A); -+ -+ SET_BIT_INFO(irq, 4, PRIMARY_B_FLIP_DONE, INTEL_GVT_IRQ_INFO_DE_PIPE_B); -+ SET_BIT_INFO(irq, 5, SPRITE_B_FLIP_DONE, INTEL_GVT_IRQ_INFO_DE_PIPE_B); -+ -+ SET_BIT_INFO(irq, 4, PRIMARY_C_FLIP_DONE, INTEL_GVT_IRQ_INFO_DE_PIPE_C); -+ SET_BIT_INFO(irq, 5, SPRITE_C_FLIP_DONE, INTEL_GVT_IRQ_INFO_DE_PIPE_C); -+ } else if (INTEL_GEN(gvt->dev_priv) >= 9) { -+ SET_BIT_INFO(irq, 25, AUX_CHANNEL_B, INTEL_GVT_IRQ_INFO_DE_PORT); -+ SET_BIT_INFO(irq, 26, AUX_CHANNEL_C, INTEL_GVT_IRQ_INFO_DE_PORT); -+ SET_BIT_INFO(irq, 27, AUX_CHANNEL_D, INTEL_GVT_IRQ_INFO_DE_PORT); -+ -+ SET_BIT_INFO(irq, 3, PRIMARY_A_FLIP_DONE, INTEL_GVT_IRQ_INFO_DE_PIPE_A); -+ SET_BIT_INFO(irq, 3, PRIMARY_B_FLIP_DONE, INTEL_GVT_IRQ_INFO_DE_PIPE_B); -+ SET_BIT_INFO(irq, 3, PRIMARY_C_FLIP_DONE, INTEL_GVT_IRQ_INFO_DE_PIPE_C); -+ -+ SET_BIT_INFO(irq, 4, SPRITE_A_FLIP_DONE, INTEL_GVT_IRQ_INFO_DE_PIPE_A); -+ SET_BIT_INFO(irq, 4, SPRITE_B_FLIP_DONE, INTEL_GVT_IRQ_INFO_DE_PIPE_B); -+ SET_BIT_INFO(irq, 4, SPRITE_C_FLIP_DONE, INTEL_GVT_IRQ_INFO_DE_PIPE_C); -+ } -+ -+ /* GEN8 interrupt PCU events */ -+ SET_BIT_INFO(irq, 24, PCU_THERMAL, INTEL_GVT_IRQ_INFO_PCU); -+ SET_BIT_INFO(irq, 25, PCU_PCODE2DRIVER_MAILBOX, INTEL_GVT_IRQ_INFO_PCU); -+} -+ -+static struct intel_gvt_irq_ops gen8_irq_ops = { -+ .init_irq = gen8_init_irq, -+ .check_pending_irq = gen8_check_pending_irq, -+}; -+ -+/** -+ * intel_vgpu_trigger_virtual_event - Trigger a virtual event for a vGPU -+ * @vgpu: a vGPU -+ * @event: interrupt event -+ * -+ * This function is used to trigger a virtual interrupt event for vGPU. -+ * The caller provides the event to be triggered, the framework itself -+ * will emulate the IRQ register bit change. -+ * -+ */ -+void intel_vgpu_trigger_virtual_event(struct intel_vgpu *vgpu, -+ enum intel_gvt_event_type event) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct intel_gvt_irq *irq = &gvt->irq; -+ gvt_event_virt_handler_t handler; -+ struct intel_gvt_irq_ops *ops = gvt->irq.ops; -+ -+ handler = get_event_virt_handler(irq, event); -+ WARN_ON(!handler); -+ -+ handler(irq, event, vgpu); -+ -+ ops->check_pending_irq(vgpu); -+} -+ -+static void init_events( -+ struct intel_gvt_irq *irq) -+{ -+ int i; -+ -+ for (i = 0; i < INTEL_GVT_EVENT_MAX; i++) { -+ irq->events[i].info = NULL; -+ irq->events[i].v_handler = handle_default_event_virt; -+ } -+} -+ -+static enum hrtimer_restart vblank_timer_fn(struct hrtimer *data) -+{ -+ struct intel_gvt_vblank_timer *vblank_timer; -+ struct intel_gvt_irq *irq; -+ struct intel_gvt *gvt; -+ -+ vblank_timer = container_of(data, struct intel_gvt_vblank_timer, timer); -+ irq = container_of(vblank_timer, struct intel_gvt_irq, vblank_timer); -+ gvt = container_of(irq, struct intel_gvt, irq); -+ -+ intel_gvt_request_service(gvt, INTEL_GVT_REQUEST_EMULATE_VBLANK); -+ hrtimer_add_expires_ns(&vblank_timer->timer, vblank_timer->period); -+ return HRTIMER_RESTART; -+} -+ -+/** -+ * intel_gvt_clean_irq - clean up GVT-g IRQ emulation subsystem -+ * @gvt: a GVT device -+ * -+ * This function is called at driver unloading stage, to clean up GVT-g IRQ -+ * emulation subsystem. -+ * -+ */ -+void intel_gvt_clean_irq(struct intel_gvt *gvt) -+{ -+ struct intel_gvt_irq *irq = &gvt->irq; -+ -+ hrtimer_cancel(&irq->vblank_timer.timer); -+} -+ -+#define VBLNAK_TIMER_PERIOD 16000000 -+ -+/** -+ * intel_gvt_init_irq - initialize GVT-g IRQ emulation subsystem -+ * @gvt: a GVT device -+ * -+ * This function is called at driver loading stage, to initialize the GVT-g IRQ -+ * emulation subsystem. -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_gvt_init_irq(struct intel_gvt *gvt) -+{ -+ struct intel_gvt_irq *irq = &gvt->irq; -+ struct intel_gvt_vblank_timer *vblank_timer = &irq->vblank_timer; -+ -+ gvt_dbg_core("init irq framework\n"); -+ -+ irq->ops = &gen8_irq_ops; -+ irq->irq_map = gen8_irq_map; -+ -+ /* common event initialization */ -+ init_events(irq); -+ -+ /* gen specific initialization */ -+ irq->ops->init_irq(irq); -+ -+ init_irq_map(irq); -+ -+ hrtimer_init(&vblank_timer->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); -+ vblank_timer->timer.function = vblank_timer_fn; -+ vblank_timer->period = VBLNAK_TIMER_PERIOD; -+ -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/interrupt.h b/drivers/gpu/drm/i915_legacy/gvt/interrupt.h -new file mode 100644 -index 000000000000..5313fb1b33e1 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/interrupt.h -@@ -0,0 +1,233 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Kevin Tian -+ * Zhi Wang -+ * -+ * Contributors: -+ * Min he -+ * -+ */ -+ -+#ifndef _GVT_INTERRUPT_H_ -+#define _GVT_INTERRUPT_H_ -+ -+enum intel_gvt_event_type { -+ RCS_MI_USER_INTERRUPT = 0, -+ RCS_DEBUG, -+ RCS_MMIO_SYNC_FLUSH, -+ RCS_CMD_STREAMER_ERR, -+ RCS_PIPE_CONTROL, -+ RCS_L3_PARITY_ERR, -+ RCS_WATCHDOG_EXCEEDED, -+ RCS_PAGE_DIRECTORY_FAULT, -+ RCS_AS_CONTEXT_SWITCH, -+ RCS_MONITOR_BUFF_HALF_FULL, -+ -+ VCS_MI_USER_INTERRUPT, -+ VCS_MMIO_SYNC_FLUSH, -+ VCS_CMD_STREAMER_ERR, -+ VCS_MI_FLUSH_DW, -+ VCS_WATCHDOG_EXCEEDED, -+ VCS_PAGE_DIRECTORY_FAULT, -+ VCS_AS_CONTEXT_SWITCH, -+ -+ VCS2_MI_USER_INTERRUPT, -+ VCS2_MI_FLUSH_DW, -+ VCS2_AS_CONTEXT_SWITCH, -+ -+ BCS_MI_USER_INTERRUPT, -+ BCS_MMIO_SYNC_FLUSH, -+ BCS_CMD_STREAMER_ERR, -+ BCS_MI_FLUSH_DW, -+ BCS_PAGE_DIRECTORY_FAULT, -+ BCS_AS_CONTEXT_SWITCH, -+ -+ VECS_MI_USER_INTERRUPT, -+ VECS_MI_FLUSH_DW, -+ VECS_AS_CONTEXT_SWITCH, -+ -+ PIPE_A_FIFO_UNDERRUN, -+ PIPE_B_FIFO_UNDERRUN, -+ PIPE_A_CRC_ERR, -+ PIPE_B_CRC_ERR, -+ PIPE_A_CRC_DONE, -+ PIPE_B_CRC_DONE, -+ PIPE_A_ODD_FIELD, -+ PIPE_B_ODD_FIELD, -+ PIPE_A_EVEN_FIELD, -+ PIPE_B_EVEN_FIELD, -+ PIPE_A_LINE_COMPARE, -+ PIPE_B_LINE_COMPARE, -+ PIPE_C_LINE_COMPARE, -+ PIPE_A_VBLANK, -+ PIPE_B_VBLANK, -+ PIPE_C_VBLANK, -+ PIPE_A_VSYNC, -+ PIPE_B_VSYNC, -+ PIPE_C_VSYNC, -+ PRIMARY_A_FLIP_DONE, -+ PRIMARY_B_FLIP_DONE, -+ PRIMARY_C_FLIP_DONE, -+ SPRITE_A_FLIP_DONE, -+ SPRITE_B_FLIP_DONE, -+ SPRITE_C_FLIP_DONE, -+ -+ PCU_THERMAL, -+ PCU_PCODE2DRIVER_MAILBOX, -+ -+ DPST_PHASE_IN, -+ DPST_HISTOGRAM, -+ GSE, -+ DP_A_HOTPLUG, -+ AUX_CHANNEL_A, -+ PERF_COUNTER, -+ POISON, -+ GTT_FAULT, -+ ERROR_INTERRUPT_COMBINED, -+ -+ FDI_RX_INTERRUPTS_TRANSCODER_A, -+ AUDIO_CP_CHANGE_TRANSCODER_A, -+ AUDIO_CP_REQUEST_TRANSCODER_A, -+ FDI_RX_INTERRUPTS_TRANSCODER_B, -+ AUDIO_CP_CHANGE_TRANSCODER_B, -+ AUDIO_CP_REQUEST_TRANSCODER_B, -+ FDI_RX_INTERRUPTS_TRANSCODER_C, -+ AUDIO_CP_CHANGE_TRANSCODER_C, -+ AUDIO_CP_REQUEST_TRANSCODER_C, -+ ERR_AND_DBG, -+ GMBUS, -+ SDVO_B_HOTPLUG, -+ CRT_HOTPLUG, -+ DP_B_HOTPLUG, -+ DP_C_HOTPLUG, -+ DP_D_HOTPLUG, -+ AUX_CHANNEL_B, -+ AUX_CHANNEL_C, -+ AUX_CHANNEL_D, -+ AUDIO_POWER_STATE_CHANGE_B, -+ AUDIO_POWER_STATE_CHANGE_C, -+ AUDIO_POWER_STATE_CHANGE_D, -+ -+ INTEL_GVT_EVENT_RESERVED, -+ INTEL_GVT_EVENT_MAX, -+}; -+ -+struct intel_gvt_irq; -+struct intel_gvt; -+ -+typedef void (*gvt_event_virt_handler_t)(struct intel_gvt_irq *irq, -+ enum intel_gvt_event_type event, struct intel_vgpu *vgpu); -+ -+struct intel_gvt_irq_ops { -+ void (*init_irq)(struct intel_gvt_irq *irq); -+ void (*check_pending_irq)(struct intel_vgpu *vgpu); -+}; -+ -+/* the list of physical interrupt control register groups */ -+enum intel_gvt_irq_type { -+ INTEL_GVT_IRQ_INFO_GT, -+ INTEL_GVT_IRQ_INFO_DPY, -+ INTEL_GVT_IRQ_INFO_PCH, -+ INTEL_GVT_IRQ_INFO_PM, -+ -+ INTEL_GVT_IRQ_INFO_MASTER, -+ INTEL_GVT_IRQ_INFO_GT0, -+ INTEL_GVT_IRQ_INFO_GT1, -+ INTEL_GVT_IRQ_INFO_GT2, -+ INTEL_GVT_IRQ_INFO_GT3, -+ INTEL_GVT_IRQ_INFO_DE_PIPE_A, -+ INTEL_GVT_IRQ_INFO_DE_PIPE_B, -+ INTEL_GVT_IRQ_INFO_DE_PIPE_C, -+ INTEL_GVT_IRQ_INFO_DE_PORT, -+ INTEL_GVT_IRQ_INFO_DE_MISC, -+ INTEL_GVT_IRQ_INFO_AUD, -+ INTEL_GVT_IRQ_INFO_PCU, -+ -+ INTEL_GVT_IRQ_INFO_MAX, -+}; -+ -+#define INTEL_GVT_IRQ_BITWIDTH 32 -+ -+/* device specific interrupt bit definitions */ -+struct intel_gvt_irq_info { -+ char *name; -+ i915_reg_t reg_base; -+ enum intel_gvt_event_type bit_to_event[INTEL_GVT_IRQ_BITWIDTH]; -+ unsigned long warned; -+ int group; -+ DECLARE_BITMAP(downstream_irq_bitmap, INTEL_GVT_IRQ_BITWIDTH); -+ bool has_upstream_irq; -+}; -+ -+/* per-event information */ -+struct intel_gvt_event_info { -+ int bit; /* map to register bit */ -+ int policy; /* forwarding policy */ -+ struct intel_gvt_irq_info *info; /* register info */ -+ gvt_event_virt_handler_t v_handler; /* for v_event */ -+}; -+ -+struct intel_gvt_irq_map { -+ int up_irq_group; -+ int up_irq_bit; -+ int down_irq_group; -+ u32 down_irq_bitmask; -+}; -+ -+struct intel_gvt_vblank_timer { -+ struct hrtimer timer; -+ u64 period; -+}; -+ -+/* structure containing device specific IRQ state */ -+struct intel_gvt_irq { -+ struct intel_gvt_irq_ops *ops; -+ struct intel_gvt_irq_info *info[INTEL_GVT_IRQ_INFO_MAX]; -+ DECLARE_BITMAP(irq_info_bitmap, INTEL_GVT_IRQ_INFO_MAX); -+ struct intel_gvt_event_info events[INTEL_GVT_EVENT_MAX]; -+ DECLARE_BITMAP(pending_events, INTEL_GVT_EVENT_MAX); -+ struct intel_gvt_irq_map *irq_map; -+ struct intel_gvt_vblank_timer vblank_timer; -+}; -+ -+int intel_gvt_init_irq(struct intel_gvt *gvt); -+void intel_gvt_clean_irq(struct intel_gvt *gvt); -+ -+void intel_vgpu_trigger_virtual_event(struct intel_vgpu *vgpu, -+ enum intel_gvt_event_type event); -+ -+int intel_vgpu_reg_iir_handler(struct intel_vgpu *vgpu, unsigned int reg, -+ void *p_data, unsigned int bytes); -+int intel_vgpu_reg_ier_handler(struct intel_vgpu *vgpu, -+ unsigned int reg, void *p_data, unsigned int bytes); -+int intel_vgpu_reg_master_irq_handler(struct intel_vgpu *vgpu, -+ unsigned int reg, void *p_data, unsigned int bytes); -+int intel_vgpu_reg_imr_handler(struct intel_vgpu *vgpu, -+ unsigned int reg, void *p_data, unsigned int bytes); -+ -+int gvt_ring_id_to_pipe_control_notify_event(int ring_id); -+int gvt_ring_id_to_mi_flush_dw_event(int ring_id); -+int gvt_ring_id_to_mi_user_interrupt_event(int ring_id); -+ -+#endif /* _GVT_INTERRUPT_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/gvt/kvmgt.c b/drivers/gpu/drm/i915_legacy/gvt/kvmgt.c -new file mode 100644 -index 000000000000..4a7cf8646b0d ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/kvmgt.c -@@ -0,0 +1,2075 @@ -+/* -+ * KVMGT - the implementation of Intel mediated pass-through framework for KVM -+ * -+ * Copyright(c) 2014-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Kevin Tian -+ * Jike Song -+ * Xiaoguang Chen -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "i915_drv.h" -+#include "gvt.h" -+ -+static const struct intel_gvt_ops *intel_gvt_ops; -+ -+/* helper macros copied from vfio-pci */ -+#define VFIO_PCI_OFFSET_SHIFT 40 -+#define VFIO_PCI_OFFSET_TO_INDEX(off) (off >> VFIO_PCI_OFFSET_SHIFT) -+#define VFIO_PCI_INDEX_TO_OFFSET(index) ((u64)(index) << VFIO_PCI_OFFSET_SHIFT) -+#define VFIO_PCI_OFFSET_MASK (((u64)(1) << VFIO_PCI_OFFSET_SHIFT) - 1) -+ -+#define EDID_BLOB_OFFSET (PAGE_SIZE/2) -+ -+#define OPREGION_SIGNATURE "IntelGraphicsMem" -+ -+struct vfio_region; -+struct intel_vgpu_regops { -+ size_t (*rw)(struct intel_vgpu *vgpu, char *buf, -+ size_t count, loff_t *ppos, bool iswrite); -+ void (*release)(struct intel_vgpu *vgpu, -+ struct vfio_region *region); -+}; -+ -+struct vfio_region { -+ u32 type; -+ u32 subtype; -+ size_t size; -+ u32 flags; -+ const struct intel_vgpu_regops *ops; -+ void *data; -+}; -+ -+struct vfio_edid_region { -+ struct vfio_region_gfx_edid vfio_edid_regs; -+ void *edid_blob; -+}; -+ -+struct kvmgt_pgfn { -+ gfn_t gfn; -+ struct hlist_node hnode; -+}; -+ -+struct kvmgt_guest_info { -+ struct kvm *kvm; -+ struct intel_vgpu *vgpu; -+ struct kvm_page_track_notifier_node track_node; -+#define NR_BKT (1 << 18) -+ struct hlist_head ptable[NR_BKT]; -+#undef NR_BKT -+ struct dentry *debugfs_cache_entries; -+}; -+ -+struct gvt_dma { -+ struct intel_vgpu *vgpu; -+ struct rb_node gfn_node; -+ struct rb_node dma_addr_node; -+ gfn_t gfn; -+ dma_addr_t dma_addr; -+ unsigned long size; -+ struct kref ref; -+}; -+ -+static inline bool handle_valid(unsigned long handle) -+{ -+ return !!(handle & ~0xff); -+} -+ -+static int kvmgt_guest_init(struct mdev_device *mdev); -+static void intel_vgpu_release_work(struct work_struct *work); -+static bool kvmgt_guest_exit(struct kvmgt_guest_info *info); -+ -+static void gvt_unpin_guest_page(struct intel_vgpu *vgpu, unsigned long gfn, -+ unsigned long size) -+{ -+ int total_pages; -+ int npage; -+ int ret; -+ -+ total_pages = roundup(size, PAGE_SIZE) / PAGE_SIZE; -+ -+ for (npage = 0; npage < total_pages; npage++) { -+ unsigned long cur_gfn = gfn + npage; -+ -+ ret = vfio_unpin_pages(mdev_dev(vgpu->vdev.mdev), &cur_gfn, 1); -+ WARN_ON(ret != 1); -+ } -+} -+ -+/* Pin a normal or compound guest page for dma. */ -+static int gvt_pin_guest_page(struct intel_vgpu *vgpu, unsigned long gfn, -+ unsigned long size, struct page **page) -+{ -+ unsigned long base_pfn = 0; -+ int total_pages; -+ int npage; -+ int ret; -+ -+ total_pages = roundup(size, PAGE_SIZE) / PAGE_SIZE; -+ /* -+ * We pin the pages one-by-one to avoid allocating a big arrary -+ * on stack to hold pfns. -+ */ -+ for (npage = 0; npage < total_pages; npage++) { -+ unsigned long cur_gfn = gfn + npage; -+ unsigned long pfn; -+ -+ ret = vfio_pin_pages(mdev_dev(vgpu->vdev.mdev), &cur_gfn, 1, -+ IOMMU_READ | IOMMU_WRITE, &pfn); -+ if (ret != 1) { -+ gvt_vgpu_err("vfio_pin_pages failed for gfn 0x%lx, ret %d\n", -+ cur_gfn, ret); -+ goto err; -+ } -+ -+ if (!pfn_valid(pfn)) { -+ gvt_vgpu_err("pfn 0x%lx is not mem backed\n", pfn); -+ npage++; -+ ret = -EFAULT; -+ goto err; -+ } -+ -+ if (npage == 0) -+ base_pfn = pfn; -+ else if (base_pfn + npage != pfn) { -+ gvt_vgpu_err("The pages are not continuous\n"); -+ ret = -EINVAL; -+ npage++; -+ goto err; -+ } -+ } -+ -+ *page = pfn_to_page(base_pfn); -+ return 0; -+err: -+ gvt_unpin_guest_page(vgpu, gfn, npage * PAGE_SIZE); -+ return ret; -+} -+ -+static int gvt_dma_map_page(struct intel_vgpu *vgpu, unsigned long gfn, -+ dma_addr_t *dma_addr, unsigned long size) -+{ -+ struct device *dev = &vgpu->gvt->dev_priv->drm.pdev->dev; -+ struct page *page = NULL; -+ int ret; -+ -+ ret = gvt_pin_guest_page(vgpu, gfn, size, &page); -+ if (ret) -+ return ret; -+ -+ /* Setup DMA mapping. */ -+ *dma_addr = dma_map_page(dev, page, 0, size, PCI_DMA_BIDIRECTIONAL); -+ if (dma_mapping_error(dev, *dma_addr)) { -+ gvt_vgpu_err("DMA mapping failed for pfn 0x%lx, ret %d\n", -+ page_to_pfn(page), ret); -+ gvt_unpin_guest_page(vgpu, gfn, size); -+ return -ENOMEM; -+ } -+ -+ return 0; -+} -+ -+static void gvt_dma_unmap_page(struct intel_vgpu *vgpu, unsigned long gfn, -+ dma_addr_t dma_addr, unsigned long size) -+{ -+ struct device *dev = &vgpu->gvt->dev_priv->drm.pdev->dev; -+ -+ dma_unmap_page(dev, dma_addr, size, PCI_DMA_BIDIRECTIONAL); -+ gvt_unpin_guest_page(vgpu, gfn, size); -+} -+ -+static struct gvt_dma *__gvt_cache_find_dma_addr(struct intel_vgpu *vgpu, -+ dma_addr_t dma_addr) -+{ -+ struct rb_node *node = vgpu->vdev.dma_addr_cache.rb_node; -+ struct gvt_dma *itr; -+ -+ while (node) { -+ itr = rb_entry(node, struct gvt_dma, dma_addr_node); -+ -+ if (dma_addr < itr->dma_addr) -+ node = node->rb_left; -+ else if (dma_addr > itr->dma_addr) -+ node = node->rb_right; -+ else -+ return itr; -+ } -+ return NULL; -+} -+ -+static struct gvt_dma *__gvt_cache_find_gfn(struct intel_vgpu *vgpu, gfn_t gfn) -+{ -+ struct rb_node *node = vgpu->vdev.gfn_cache.rb_node; -+ struct gvt_dma *itr; -+ -+ while (node) { -+ itr = rb_entry(node, struct gvt_dma, gfn_node); -+ -+ if (gfn < itr->gfn) -+ node = node->rb_left; -+ else if (gfn > itr->gfn) -+ node = node->rb_right; -+ else -+ return itr; -+ } -+ return NULL; -+} -+ -+static int __gvt_cache_add(struct intel_vgpu *vgpu, gfn_t gfn, -+ dma_addr_t dma_addr, unsigned long size) -+{ -+ struct gvt_dma *new, *itr; -+ struct rb_node **link, *parent = NULL; -+ -+ new = kzalloc(sizeof(struct gvt_dma), GFP_KERNEL); -+ if (!new) -+ return -ENOMEM; -+ -+ new->vgpu = vgpu; -+ new->gfn = gfn; -+ new->dma_addr = dma_addr; -+ new->size = size; -+ kref_init(&new->ref); -+ -+ /* gfn_cache maps gfn to struct gvt_dma. */ -+ link = &vgpu->vdev.gfn_cache.rb_node; -+ while (*link) { -+ parent = *link; -+ itr = rb_entry(parent, struct gvt_dma, gfn_node); -+ -+ if (gfn < itr->gfn) -+ link = &parent->rb_left; -+ else -+ link = &parent->rb_right; -+ } -+ rb_link_node(&new->gfn_node, parent, link); -+ rb_insert_color(&new->gfn_node, &vgpu->vdev.gfn_cache); -+ -+ /* dma_addr_cache maps dma addr to struct gvt_dma. */ -+ parent = NULL; -+ link = &vgpu->vdev.dma_addr_cache.rb_node; -+ while (*link) { -+ parent = *link; -+ itr = rb_entry(parent, struct gvt_dma, dma_addr_node); -+ -+ if (dma_addr < itr->dma_addr) -+ link = &parent->rb_left; -+ else -+ link = &parent->rb_right; -+ } -+ rb_link_node(&new->dma_addr_node, parent, link); -+ rb_insert_color(&new->dma_addr_node, &vgpu->vdev.dma_addr_cache); -+ -+ vgpu->vdev.nr_cache_entries++; -+ return 0; -+} -+ -+static void __gvt_cache_remove_entry(struct intel_vgpu *vgpu, -+ struct gvt_dma *entry) -+{ -+ rb_erase(&entry->gfn_node, &vgpu->vdev.gfn_cache); -+ rb_erase(&entry->dma_addr_node, &vgpu->vdev.dma_addr_cache); -+ kfree(entry); -+ vgpu->vdev.nr_cache_entries--; -+} -+ -+static void gvt_cache_destroy(struct intel_vgpu *vgpu) -+{ -+ struct gvt_dma *dma; -+ struct rb_node *node = NULL; -+ -+ for (;;) { -+ mutex_lock(&vgpu->vdev.cache_lock); -+ node = rb_first(&vgpu->vdev.gfn_cache); -+ if (!node) { -+ mutex_unlock(&vgpu->vdev.cache_lock); -+ break; -+ } -+ dma = rb_entry(node, struct gvt_dma, gfn_node); -+ gvt_dma_unmap_page(vgpu, dma->gfn, dma->dma_addr, dma->size); -+ __gvt_cache_remove_entry(vgpu, dma); -+ mutex_unlock(&vgpu->vdev.cache_lock); -+ } -+} -+ -+static void gvt_cache_init(struct intel_vgpu *vgpu) -+{ -+ vgpu->vdev.gfn_cache = RB_ROOT; -+ vgpu->vdev.dma_addr_cache = RB_ROOT; -+ vgpu->vdev.nr_cache_entries = 0; -+ mutex_init(&vgpu->vdev.cache_lock); -+} -+ -+static void kvmgt_protect_table_init(struct kvmgt_guest_info *info) -+{ -+ hash_init(info->ptable); -+} -+ -+static void kvmgt_protect_table_destroy(struct kvmgt_guest_info *info) -+{ -+ struct kvmgt_pgfn *p; -+ struct hlist_node *tmp; -+ int i; -+ -+ hash_for_each_safe(info->ptable, i, tmp, p, hnode) { -+ hash_del(&p->hnode); -+ kfree(p); -+ } -+} -+ -+static struct kvmgt_pgfn * -+__kvmgt_protect_table_find(struct kvmgt_guest_info *info, gfn_t gfn) -+{ -+ struct kvmgt_pgfn *p, *res = NULL; -+ -+ hash_for_each_possible(info->ptable, p, hnode, gfn) { -+ if (gfn == p->gfn) { -+ res = p; -+ break; -+ } -+ } -+ -+ return res; -+} -+ -+static bool kvmgt_gfn_is_write_protected(struct kvmgt_guest_info *info, -+ gfn_t gfn) -+{ -+ struct kvmgt_pgfn *p; -+ -+ p = __kvmgt_protect_table_find(info, gfn); -+ return !!p; -+} -+ -+static void kvmgt_protect_table_add(struct kvmgt_guest_info *info, gfn_t gfn) -+{ -+ struct kvmgt_pgfn *p; -+ -+ if (kvmgt_gfn_is_write_protected(info, gfn)) -+ return; -+ -+ p = kzalloc(sizeof(struct kvmgt_pgfn), GFP_ATOMIC); -+ if (WARN(!p, "gfn: 0x%llx\n", gfn)) -+ return; -+ -+ p->gfn = gfn; -+ hash_add(info->ptable, &p->hnode, gfn); -+} -+ -+static void kvmgt_protect_table_del(struct kvmgt_guest_info *info, -+ gfn_t gfn) -+{ -+ struct kvmgt_pgfn *p; -+ -+ p = __kvmgt_protect_table_find(info, gfn); -+ if (p) { -+ hash_del(&p->hnode); -+ kfree(p); -+ } -+} -+ -+static size_t intel_vgpu_reg_rw_opregion(struct intel_vgpu *vgpu, char *buf, -+ size_t count, loff_t *ppos, bool iswrite) -+{ -+ unsigned int i = VFIO_PCI_OFFSET_TO_INDEX(*ppos) - -+ VFIO_PCI_NUM_REGIONS; -+ void *base = vgpu->vdev.region[i].data; -+ loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK; -+ -+ if (pos >= vgpu->vdev.region[i].size || iswrite) { -+ gvt_vgpu_err("invalid op or offset for Intel vgpu OpRegion\n"); -+ return -EINVAL; -+ } -+ count = min(count, (size_t)(vgpu->vdev.region[i].size - pos)); -+ memcpy(buf, base + pos, count); -+ -+ return count; -+} -+ -+static void intel_vgpu_reg_release_opregion(struct intel_vgpu *vgpu, -+ struct vfio_region *region) -+{ -+} -+ -+static const struct intel_vgpu_regops intel_vgpu_regops_opregion = { -+ .rw = intel_vgpu_reg_rw_opregion, -+ .release = intel_vgpu_reg_release_opregion, -+}; -+ -+static int handle_edid_regs(struct intel_vgpu *vgpu, -+ struct vfio_edid_region *region, char *buf, -+ size_t count, u16 offset, bool is_write) -+{ -+ struct vfio_region_gfx_edid *regs = ®ion->vfio_edid_regs; -+ unsigned int data; -+ -+ if (offset + count > sizeof(*regs)) -+ return -EINVAL; -+ -+ if (count != 4) -+ return -EINVAL; -+ -+ if (is_write) { -+ data = *((unsigned int *)buf); -+ switch (offset) { -+ case offsetof(struct vfio_region_gfx_edid, link_state): -+ if (data == VFIO_DEVICE_GFX_LINK_STATE_UP) { -+ if (!drm_edid_block_valid( -+ (u8 *)region->edid_blob, -+ 0, -+ true, -+ NULL)) { -+ gvt_vgpu_err("invalid EDID blob\n"); -+ return -EINVAL; -+ } -+ intel_gvt_ops->emulate_hotplug(vgpu, true); -+ } else if (data == VFIO_DEVICE_GFX_LINK_STATE_DOWN) -+ intel_gvt_ops->emulate_hotplug(vgpu, false); -+ else { -+ gvt_vgpu_err("invalid EDID link state %d\n", -+ regs->link_state); -+ return -EINVAL; -+ } -+ regs->link_state = data; -+ break; -+ case offsetof(struct vfio_region_gfx_edid, edid_size): -+ if (data > regs->edid_max_size) { -+ gvt_vgpu_err("EDID size is bigger than %d!\n", -+ regs->edid_max_size); -+ return -EINVAL; -+ } -+ regs->edid_size = data; -+ break; -+ default: -+ /* read-only regs */ -+ gvt_vgpu_err("write read-only EDID region at offset %d\n", -+ offset); -+ return -EPERM; -+ } -+ } else { -+ memcpy(buf, (char *)regs + offset, count); -+ } -+ -+ return count; -+} -+ -+static int handle_edid_blob(struct vfio_edid_region *region, char *buf, -+ size_t count, u16 offset, bool is_write) -+{ -+ if (offset + count > region->vfio_edid_regs.edid_size) -+ return -EINVAL; -+ -+ if (is_write) -+ memcpy(region->edid_blob + offset, buf, count); -+ else -+ memcpy(buf, region->edid_blob + offset, count); -+ -+ return count; -+} -+ -+static size_t intel_vgpu_reg_rw_edid(struct intel_vgpu *vgpu, char *buf, -+ size_t count, loff_t *ppos, bool iswrite) -+{ -+ int ret; -+ unsigned int i = VFIO_PCI_OFFSET_TO_INDEX(*ppos) - -+ VFIO_PCI_NUM_REGIONS; -+ struct vfio_edid_region *region = -+ (struct vfio_edid_region *)vgpu->vdev.region[i].data; -+ loff_t pos = *ppos & VFIO_PCI_OFFSET_MASK; -+ -+ if (pos < region->vfio_edid_regs.edid_offset) { -+ ret = handle_edid_regs(vgpu, region, buf, count, pos, iswrite); -+ } else { -+ pos -= EDID_BLOB_OFFSET; -+ ret = handle_edid_blob(region, buf, count, pos, iswrite); -+ } -+ -+ if (ret < 0) -+ gvt_vgpu_err("failed to access EDID region\n"); -+ -+ return ret; -+} -+ -+static void intel_vgpu_reg_release_edid(struct intel_vgpu *vgpu, -+ struct vfio_region *region) -+{ -+ kfree(region->data); -+} -+ -+static const struct intel_vgpu_regops intel_vgpu_regops_edid = { -+ .rw = intel_vgpu_reg_rw_edid, -+ .release = intel_vgpu_reg_release_edid, -+}; -+ -+static int intel_vgpu_register_reg(struct intel_vgpu *vgpu, -+ unsigned int type, unsigned int subtype, -+ const struct intel_vgpu_regops *ops, -+ size_t size, u32 flags, void *data) -+{ -+ struct vfio_region *region; -+ -+ region = krealloc(vgpu->vdev.region, -+ (vgpu->vdev.num_regions + 1) * sizeof(*region), -+ GFP_KERNEL); -+ if (!region) -+ return -ENOMEM; -+ -+ vgpu->vdev.region = region; -+ vgpu->vdev.region[vgpu->vdev.num_regions].type = type; -+ vgpu->vdev.region[vgpu->vdev.num_regions].subtype = subtype; -+ vgpu->vdev.region[vgpu->vdev.num_regions].ops = ops; -+ vgpu->vdev.region[vgpu->vdev.num_regions].size = size; -+ vgpu->vdev.region[vgpu->vdev.num_regions].flags = flags; -+ vgpu->vdev.region[vgpu->vdev.num_regions].data = data; -+ vgpu->vdev.num_regions++; -+ return 0; -+} -+ -+static int kvmgt_get_vfio_device(void *p_vgpu) -+{ -+ struct intel_vgpu *vgpu = (struct intel_vgpu *)p_vgpu; -+ -+ vgpu->vdev.vfio_device = vfio_device_get_from_dev( -+ mdev_dev(vgpu->vdev.mdev)); -+ if (!vgpu->vdev.vfio_device) { -+ gvt_vgpu_err("failed to get vfio device\n"); -+ return -ENODEV; -+ } -+ return 0; -+} -+ -+ -+static int kvmgt_set_opregion(void *p_vgpu) -+{ -+ struct intel_vgpu *vgpu = (struct intel_vgpu *)p_vgpu; -+ void *base; -+ int ret; -+ -+ /* Each vgpu has its own opregion, although VFIO would create another -+ * one later. This one is used to expose opregion to VFIO. And the -+ * other one created by VFIO later, is used by guest actually. -+ */ -+ base = vgpu_opregion(vgpu)->va; -+ if (!base) -+ return -ENOMEM; -+ -+ if (memcmp(base, OPREGION_SIGNATURE, 16)) { -+ memunmap(base); -+ return -EINVAL; -+ } -+ -+ ret = intel_vgpu_register_reg(vgpu, -+ PCI_VENDOR_ID_INTEL | VFIO_REGION_TYPE_PCI_VENDOR_TYPE, -+ VFIO_REGION_SUBTYPE_INTEL_IGD_OPREGION, -+ &intel_vgpu_regops_opregion, OPREGION_SIZE, -+ VFIO_REGION_INFO_FLAG_READ, base); -+ -+ return ret; -+} -+ -+static int kvmgt_set_edid(void *p_vgpu, int port_num) -+{ -+ struct intel_vgpu *vgpu = (struct intel_vgpu *)p_vgpu; -+ struct intel_vgpu_port *port = intel_vgpu_port(vgpu, port_num); -+ struct vfio_edid_region *base; -+ int ret; -+ -+ base = kzalloc(sizeof(*base), GFP_KERNEL); -+ if (!base) -+ return -ENOMEM; -+ -+ /* TODO: Add multi-port and EDID extension block support */ -+ base->vfio_edid_regs.edid_offset = EDID_BLOB_OFFSET; -+ base->vfio_edid_regs.edid_max_size = EDID_SIZE; -+ base->vfio_edid_regs.edid_size = EDID_SIZE; -+ base->vfio_edid_regs.max_xres = vgpu_edid_xres(port->id); -+ base->vfio_edid_regs.max_yres = vgpu_edid_yres(port->id); -+ base->edid_blob = port->edid->edid_block; -+ -+ ret = intel_vgpu_register_reg(vgpu, -+ VFIO_REGION_TYPE_GFX, -+ VFIO_REGION_SUBTYPE_GFX_EDID, -+ &intel_vgpu_regops_edid, EDID_SIZE, -+ VFIO_REGION_INFO_FLAG_READ | -+ VFIO_REGION_INFO_FLAG_WRITE | -+ VFIO_REGION_INFO_FLAG_CAPS, base); -+ -+ return ret; -+} -+ -+static void kvmgt_put_vfio_device(void *vgpu) -+{ -+ if (WARN_ON(!((struct intel_vgpu *)vgpu)->vdev.vfio_device)) -+ return; -+ -+ vfio_device_put(((struct intel_vgpu *)vgpu)->vdev.vfio_device); -+} -+ -+static int intel_vgpu_create(struct kobject *kobj, struct mdev_device *mdev) -+{ -+ struct intel_vgpu *vgpu = NULL; -+ struct intel_vgpu_type *type; -+ struct device *pdev; -+ void *gvt; -+ int ret; -+ -+ pdev = mdev_parent_dev(mdev); -+ gvt = kdev_to_i915(pdev)->gvt; -+ -+ type = intel_gvt_ops->gvt_find_vgpu_type(gvt, kobject_name(kobj)); -+ if (!type) { -+ gvt_vgpu_err("failed to find type %s to create\n", -+ kobject_name(kobj)); -+ ret = -EINVAL; -+ goto out; -+ } -+ -+ vgpu = intel_gvt_ops->vgpu_create(gvt, type); -+ if (IS_ERR_OR_NULL(vgpu)) { -+ ret = vgpu == NULL ? -EFAULT : PTR_ERR(vgpu); -+ gvt_err("failed to create intel vgpu: %d\n", ret); -+ goto out; -+ } -+ -+ INIT_WORK(&vgpu->vdev.release_work, intel_vgpu_release_work); -+ -+ vgpu->vdev.mdev = mdev; -+ mdev_set_drvdata(mdev, vgpu); -+ -+ gvt_dbg_core("intel_vgpu_create succeeded for mdev: %s\n", -+ dev_name(mdev_dev(mdev))); -+ ret = 0; -+ -+out: -+ return ret; -+} -+ -+static int intel_vgpu_remove(struct mdev_device *mdev) -+{ -+ struct intel_vgpu *vgpu = mdev_get_drvdata(mdev); -+ -+ if (handle_valid(vgpu->handle)) -+ return -EBUSY; -+ -+ intel_gvt_ops->vgpu_destroy(vgpu); -+ return 0; -+} -+ -+static int intel_vgpu_iommu_notifier(struct notifier_block *nb, -+ unsigned long action, void *data) -+{ -+ struct intel_vgpu *vgpu = container_of(nb, -+ struct intel_vgpu, -+ vdev.iommu_notifier); -+ -+ if (action == VFIO_IOMMU_NOTIFY_DMA_UNMAP) { -+ struct vfio_iommu_type1_dma_unmap *unmap = data; -+ struct gvt_dma *entry; -+ unsigned long iov_pfn, end_iov_pfn; -+ -+ iov_pfn = unmap->iova >> PAGE_SHIFT; -+ end_iov_pfn = iov_pfn + unmap->size / PAGE_SIZE; -+ -+ mutex_lock(&vgpu->vdev.cache_lock); -+ for (; iov_pfn < end_iov_pfn; iov_pfn++) { -+ entry = __gvt_cache_find_gfn(vgpu, iov_pfn); -+ if (!entry) -+ continue; -+ -+ gvt_dma_unmap_page(vgpu, entry->gfn, entry->dma_addr, -+ entry->size); -+ __gvt_cache_remove_entry(vgpu, entry); -+ } -+ mutex_unlock(&vgpu->vdev.cache_lock); -+ } -+ -+ return NOTIFY_OK; -+} -+ -+static int intel_vgpu_group_notifier(struct notifier_block *nb, -+ unsigned long action, void *data) -+{ -+ struct intel_vgpu *vgpu = container_of(nb, -+ struct intel_vgpu, -+ vdev.group_notifier); -+ -+ /* the only action we care about */ -+ if (action == VFIO_GROUP_NOTIFY_SET_KVM) { -+ vgpu->vdev.kvm = data; -+ -+ if (!data) -+ schedule_work(&vgpu->vdev.release_work); -+ } -+ -+ return NOTIFY_OK; -+} -+ -+static int intel_vgpu_open(struct mdev_device *mdev) -+{ -+ struct intel_vgpu *vgpu = mdev_get_drvdata(mdev); -+ unsigned long events; -+ int ret; -+ -+ vgpu->vdev.iommu_notifier.notifier_call = intel_vgpu_iommu_notifier; -+ vgpu->vdev.group_notifier.notifier_call = intel_vgpu_group_notifier; -+ -+ events = VFIO_IOMMU_NOTIFY_DMA_UNMAP; -+ ret = vfio_register_notifier(mdev_dev(mdev), VFIO_IOMMU_NOTIFY, &events, -+ &vgpu->vdev.iommu_notifier); -+ if (ret != 0) { -+ gvt_vgpu_err("vfio_register_notifier for iommu failed: %d\n", -+ ret); -+ goto out; -+ } -+ -+ events = VFIO_GROUP_NOTIFY_SET_KVM; -+ ret = vfio_register_notifier(mdev_dev(mdev), VFIO_GROUP_NOTIFY, &events, -+ &vgpu->vdev.group_notifier); -+ if (ret != 0) { -+ gvt_vgpu_err("vfio_register_notifier for group failed: %d\n", -+ ret); -+ goto undo_iommu; -+ } -+ -+ /* Take a module reference as mdev core doesn't take -+ * a reference for vendor driver. -+ */ -+ if (!try_module_get(THIS_MODULE)) -+ goto undo_group; -+ -+ ret = kvmgt_guest_init(mdev); -+ if (ret) -+ goto undo_group; -+ -+ intel_gvt_ops->vgpu_activate(vgpu); -+ -+ atomic_set(&vgpu->vdev.released, 0); -+ return ret; -+ -+undo_group: -+ vfio_unregister_notifier(mdev_dev(mdev), VFIO_GROUP_NOTIFY, -+ &vgpu->vdev.group_notifier); -+ -+undo_iommu: -+ vfio_unregister_notifier(mdev_dev(mdev), VFIO_IOMMU_NOTIFY, -+ &vgpu->vdev.iommu_notifier); -+out: -+ return ret; -+} -+ -+static void intel_vgpu_release_msi_eventfd_ctx(struct intel_vgpu *vgpu) -+{ -+ struct eventfd_ctx *trigger; -+ -+ trigger = vgpu->vdev.msi_trigger; -+ if (trigger) { -+ eventfd_ctx_put(trigger); -+ vgpu->vdev.msi_trigger = NULL; -+ } -+} -+ -+static void __intel_vgpu_release(struct intel_vgpu *vgpu) -+{ -+ struct kvmgt_guest_info *info; -+ int ret; -+ -+ if (!handle_valid(vgpu->handle)) -+ return; -+ -+ if (atomic_cmpxchg(&vgpu->vdev.released, 0, 1)) -+ return; -+ -+ intel_gvt_ops->vgpu_release(vgpu); -+ -+ ret = vfio_unregister_notifier(mdev_dev(vgpu->vdev.mdev), VFIO_IOMMU_NOTIFY, -+ &vgpu->vdev.iommu_notifier); -+ WARN(ret, "vfio_unregister_notifier for iommu failed: %d\n", ret); -+ -+ ret = vfio_unregister_notifier(mdev_dev(vgpu->vdev.mdev), VFIO_GROUP_NOTIFY, -+ &vgpu->vdev.group_notifier); -+ WARN(ret, "vfio_unregister_notifier for group failed: %d\n", ret); -+ -+ /* dereference module reference taken at open */ -+ module_put(THIS_MODULE); -+ -+ info = (struct kvmgt_guest_info *)vgpu->handle; -+ kvmgt_guest_exit(info); -+ -+ intel_vgpu_release_msi_eventfd_ctx(vgpu); -+ -+ vgpu->vdev.kvm = NULL; -+ vgpu->handle = 0; -+} -+ -+static void intel_vgpu_release(struct mdev_device *mdev) -+{ -+ struct intel_vgpu *vgpu = mdev_get_drvdata(mdev); -+ -+ __intel_vgpu_release(vgpu); -+} -+ -+static void intel_vgpu_release_work(struct work_struct *work) -+{ -+ struct intel_vgpu *vgpu = container_of(work, struct intel_vgpu, -+ vdev.release_work); -+ -+ __intel_vgpu_release(vgpu); -+} -+ -+static u64 intel_vgpu_get_bar_addr(struct intel_vgpu *vgpu, int bar) -+{ -+ u32 start_lo, start_hi; -+ u32 mem_type; -+ -+ start_lo = (*(u32 *)(vgpu->cfg_space.virtual_cfg_space + bar)) & -+ PCI_BASE_ADDRESS_MEM_MASK; -+ mem_type = (*(u32 *)(vgpu->cfg_space.virtual_cfg_space + bar)) & -+ PCI_BASE_ADDRESS_MEM_TYPE_MASK; -+ -+ switch (mem_type) { -+ case PCI_BASE_ADDRESS_MEM_TYPE_64: -+ start_hi = (*(u32 *)(vgpu->cfg_space.virtual_cfg_space -+ + bar + 4)); -+ break; -+ case PCI_BASE_ADDRESS_MEM_TYPE_32: -+ case PCI_BASE_ADDRESS_MEM_TYPE_1M: -+ /* 1M mem BAR treated as 32-bit BAR */ -+ default: -+ /* mem unknown type treated as 32-bit BAR */ -+ start_hi = 0; -+ break; -+ } -+ -+ return ((u64)start_hi << 32) | start_lo; -+} -+ -+static int intel_vgpu_bar_rw(struct intel_vgpu *vgpu, int bar, u64 off, -+ void *buf, unsigned int count, bool is_write) -+{ -+ u64 bar_start = intel_vgpu_get_bar_addr(vgpu, bar); -+ int ret; -+ -+ if (is_write) -+ ret = intel_gvt_ops->emulate_mmio_write(vgpu, -+ bar_start + off, buf, count); -+ else -+ ret = intel_gvt_ops->emulate_mmio_read(vgpu, -+ bar_start + off, buf, count); -+ return ret; -+} -+ -+static inline bool intel_vgpu_in_aperture(struct intel_vgpu *vgpu, u64 off) -+{ -+ return off >= vgpu_aperture_offset(vgpu) && -+ off < vgpu_aperture_offset(vgpu) + vgpu_aperture_sz(vgpu); -+} -+ -+static int intel_vgpu_aperture_rw(struct intel_vgpu *vgpu, u64 off, -+ void *buf, unsigned long count, bool is_write) -+{ -+ void __iomem *aperture_va; -+ -+ if (!intel_vgpu_in_aperture(vgpu, off) || -+ !intel_vgpu_in_aperture(vgpu, off + count)) { -+ gvt_vgpu_err("Invalid aperture offset %llu\n", off); -+ return -EINVAL; -+ } -+ -+ aperture_va = io_mapping_map_wc(&vgpu->gvt->dev_priv->ggtt.iomap, -+ ALIGN_DOWN(off, PAGE_SIZE), -+ count + offset_in_page(off)); -+ if (!aperture_va) -+ return -EIO; -+ -+ if (is_write) -+ memcpy_toio(aperture_va + offset_in_page(off), buf, count); -+ else -+ memcpy_fromio(buf, aperture_va + offset_in_page(off), count); -+ -+ io_mapping_unmap(aperture_va); -+ -+ return 0; -+} -+ -+static ssize_t intel_vgpu_rw(struct mdev_device *mdev, char *buf, -+ size_t count, loff_t *ppos, bool is_write) -+{ -+ struct intel_vgpu *vgpu = mdev_get_drvdata(mdev); -+ unsigned int index = VFIO_PCI_OFFSET_TO_INDEX(*ppos); -+ u64 pos = *ppos & VFIO_PCI_OFFSET_MASK; -+ int ret = -EINVAL; -+ -+ -+ if (index >= VFIO_PCI_NUM_REGIONS + vgpu->vdev.num_regions) { -+ gvt_vgpu_err("invalid index: %u\n", index); -+ return -EINVAL; -+ } -+ -+ switch (index) { -+ case VFIO_PCI_CONFIG_REGION_INDEX: -+ if (is_write) -+ ret = intel_gvt_ops->emulate_cfg_write(vgpu, pos, -+ buf, count); -+ else -+ ret = intel_gvt_ops->emulate_cfg_read(vgpu, pos, -+ buf, count); -+ break; -+ case VFIO_PCI_BAR0_REGION_INDEX: -+ ret = intel_vgpu_bar_rw(vgpu, PCI_BASE_ADDRESS_0, pos, -+ buf, count, is_write); -+ break; -+ case VFIO_PCI_BAR2_REGION_INDEX: -+ ret = intel_vgpu_aperture_rw(vgpu, pos, buf, count, is_write); -+ break; -+ case VFIO_PCI_BAR1_REGION_INDEX: -+ case VFIO_PCI_BAR3_REGION_INDEX: -+ case VFIO_PCI_BAR4_REGION_INDEX: -+ case VFIO_PCI_BAR5_REGION_INDEX: -+ case VFIO_PCI_VGA_REGION_INDEX: -+ case VFIO_PCI_ROM_REGION_INDEX: -+ break; -+ default: -+ if (index >= VFIO_PCI_NUM_REGIONS + vgpu->vdev.num_regions) -+ return -EINVAL; -+ -+ index -= VFIO_PCI_NUM_REGIONS; -+ return vgpu->vdev.region[index].ops->rw(vgpu, buf, count, -+ ppos, is_write); -+ } -+ -+ return ret == 0 ? count : ret; -+} -+ -+static bool gtt_entry(struct mdev_device *mdev, loff_t *ppos) -+{ -+ struct intel_vgpu *vgpu = mdev_get_drvdata(mdev); -+ unsigned int index = VFIO_PCI_OFFSET_TO_INDEX(*ppos); -+ struct intel_gvt *gvt = vgpu->gvt; -+ int offset; -+ -+ /* Only allow MMIO GGTT entry access */ -+ if (index != PCI_BASE_ADDRESS_0) -+ return false; -+ -+ offset = (u64)(*ppos & VFIO_PCI_OFFSET_MASK) - -+ intel_vgpu_get_bar_gpa(vgpu, PCI_BASE_ADDRESS_0); -+ -+ return (offset >= gvt->device_info.gtt_start_offset && -+ offset < gvt->device_info.gtt_start_offset + gvt_ggtt_sz(gvt)) ? -+ true : false; -+} -+ -+static ssize_t intel_vgpu_read(struct mdev_device *mdev, char __user *buf, -+ size_t count, loff_t *ppos) -+{ -+ unsigned int done = 0; -+ int ret; -+ -+ while (count) { -+ size_t filled; -+ -+ /* Only support GGTT entry 8 bytes read */ -+ if (count >= 8 && !(*ppos % 8) && -+ gtt_entry(mdev, ppos)) { -+ u64 val; -+ -+ ret = intel_vgpu_rw(mdev, (char *)&val, sizeof(val), -+ ppos, false); -+ if (ret <= 0) -+ goto read_err; -+ -+ if (copy_to_user(buf, &val, sizeof(val))) -+ goto read_err; -+ -+ filled = 8; -+ } else if (count >= 4 && !(*ppos % 4)) { -+ u32 val; -+ -+ ret = intel_vgpu_rw(mdev, (char *)&val, sizeof(val), -+ ppos, false); -+ if (ret <= 0) -+ goto read_err; -+ -+ if (copy_to_user(buf, &val, sizeof(val))) -+ goto read_err; -+ -+ filled = 4; -+ } else if (count >= 2 && !(*ppos % 2)) { -+ u16 val; -+ -+ ret = intel_vgpu_rw(mdev, (char *)&val, sizeof(val), -+ ppos, false); -+ if (ret <= 0) -+ goto read_err; -+ -+ if (copy_to_user(buf, &val, sizeof(val))) -+ goto read_err; -+ -+ filled = 2; -+ } else { -+ u8 val; -+ -+ ret = intel_vgpu_rw(mdev, &val, sizeof(val), ppos, -+ false); -+ if (ret <= 0) -+ goto read_err; -+ -+ if (copy_to_user(buf, &val, sizeof(val))) -+ goto read_err; -+ -+ filled = 1; -+ } -+ -+ count -= filled; -+ done += filled; -+ *ppos += filled; -+ buf += filled; -+ } -+ -+ return done; -+ -+read_err: -+ return -EFAULT; -+} -+ -+static ssize_t intel_vgpu_write(struct mdev_device *mdev, -+ const char __user *buf, -+ size_t count, loff_t *ppos) -+{ -+ unsigned int done = 0; -+ int ret; -+ -+ while (count) { -+ size_t filled; -+ -+ /* Only support GGTT entry 8 bytes write */ -+ if (count >= 8 && !(*ppos % 8) && -+ gtt_entry(mdev, ppos)) { -+ u64 val; -+ -+ if (copy_from_user(&val, buf, sizeof(val))) -+ goto write_err; -+ -+ ret = intel_vgpu_rw(mdev, (char *)&val, sizeof(val), -+ ppos, true); -+ if (ret <= 0) -+ goto write_err; -+ -+ filled = 8; -+ } else if (count >= 4 && !(*ppos % 4)) { -+ u32 val; -+ -+ if (copy_from_user(&val, buf, sizeof(val))) -+ goto write_err; -+ -+ ret = intel_vgpu_rw(mdev, (char *)&val, sizeof(val), -+ ppos, true); -+ if (ret <= 0) -+ goto write_err; -+ -+ filled = 4; -+ } else if (count >= 2 && !(*ppos % 2)) { -+ u16 val; -+ -+ if (copy_from_user(&val, buf, sizeof(val))) -+ goto write_err; -+ -+ ret = intel_vgpu_rw(mdev, (char *)&val, -+ sizeof(val), ppos, true); -+ if (ret <= 0) -+ goto write_err; -+ -+ filled = 2; -+ } else { -+ u8 val; -+ -+ if (copy_from_user(&val, buf, sizeof(val))) -+ goto write_err; -+ -+ ret = intel_vgpu_rw(mdev, &val, sizeof(val), -+ ppos, true); -+ if (ret <= 0) -+ goto write_err; -+ -+ filled = 1; -+ } -+ -+ count -= filled; -+ done += filled; -+ *ppos += filled; -+ buf += filled; -+ } -+ -+ return done; -+write_err: -+ return -EFAULT; -+} -+ -+static int intel_vgpu_mmap(struct mdev_device *mdev, struct vm_area_struct *vma) -+{ -+ unsigned int index; -+ u64 virtaddr; -+ unsigned long req_size, pgoff, req_start; -+ pgprot_t pg_prot; -+ struct intel_vgpu *vgpu = mdev_get_drvdata(mdev); -+ -+ index = vma->vm_pgoff >> (VFIO_PCI_OFFSET_SHIFT - PAGE_SHIFT); -+ if (index >= VFIO_PCI_ROM_REGION_INDEX) -+ return -EINVAL; -+ -+ if (vma->vm_end < vma->vm_start) -+ return -EINVAL; -+ if ((vma->vm_flags & VM_SHARED) == 0) -+ return -EINVAL; -+ if (index != VFIO_PCI_BAR2_REGION_INDEX) -+ return -EINVAL; -+ -+ pg_prot = vma->vm_page_prot; -+ virtaddr = vma->vm_start; -+ req_size = vma->vm_end - vma->vm_start; -+ pgoff = vma->vm_pgoff & -+ ((1U << (VFIO_PCI_OFFSET_SHIFT - PAGE_SHIFT)) - 1); -+ req_start = pgoff << PAGE_SHIFT; -+ -+ if (!intel_vgpu_in_aperture(vgpu, req_start)) -+ return -EINVAL; -+ if (req_start + req_size > -+ vgpu_aperture_offset(vgpu) + vgpu_aperture_sz(vgpu)) -+ return -EINVAL; -+ -+ pgoff = (gvt_aperture_pa_base(vgpu->gvt) >> PAGE_SHIFT) + pgoff; -+ -+ return remap_pfn_range(vma, virtaddr, pgoff, req_size, pg_prot); -+} -+ -+static int intel_vgpu_get_irq_count(struct intel_vgpu *vgpu, int type) -+{ -+ if (type == VFIO_PCI_INTX_IRQ_INDEX || type == VFIO_PCI_MSI_IRQ_INDEX) -+ return 1; -+ -+ return 0; -+} -+ -+static int intel_vgpu_set_intx_mask(struct intel_vgpu *vgpu, -+ unsigned int index, unsigned int start, -+ unsigned int count, u32 flags, -+ void *data) -+{ -+ return 0; -+} -+ -+static int intel_vgpu_set_intx_unmask(struct intel_vgpu *vgpu, -+ unsigned int index, unsigned int start, -+ unsigned int count, u32 flags, void *data) -+{ -+ return 0; -+} -+ -+static int intel_vgpu_set_intx_trigger(struct intel_vgpu *vgpu, -+ unsigned int index, unsigned int start, unsigned int count, -+ u32 flags, void *data) -+{ -+ return 0; -+} -+ -+static int intel_vgpu_set_msi_trigger(struct intel_vgpu *vgpu, -+ unsigned int index, unsigned int start, unsigned int count, -+ u32 flags, void *data) -+{ -+ struct eventfd_ctx *trigger; -+ -+ if (flags & VFIO_IRQ_SET_DATA_EVENTFD) { -+ int fd = *(int *)data; -+ -+ trigger = eventfd_ctx_fdget(fd); -+ if (IS_ERR(trigger)) { -+ gvt_vgpu_err("eventfd_ctx_fdget failed\n"); -+ return PTR_ERR(trigger); -+ } -+ vgpu->vdev.msi_trigger = trigger; -+ } else if ((flags & VFIO_IRQ_SET_DATA_NONE) && !count) -+ intel_vgpu_release_msi_eventfd_ctx(vgpu); -+ -+ return 0; -+} -+ -+static int intel_vgpu_set_irqs(struct intel_vgpu *vgpu, u32 flags, -+ unsigned int index, unsigned int start, unsigned int count, -+ void *data) -+{ -+ int (*func)(struct intel_vgpu *vgpu, unsigned int index, -+ unsigned int start, unsigned int count, u32 flags, -+ void *data) = NULL; -+ -+ switch (index) { -+ case VFIO_PCI_INTX_IRQ_INDEX: -+ switch (flags & VFIO_IRQ_SET_ACTION_TYPE_MASK) { -+ case VFIO_IRQ_SET_ACTION_MASK: -+ func = intel_vgpu_set_intx_mask; -+ break; -+ case VFIO_IRQ_SET_ACTION_UNMASK: -+ func = intel_vgpu_set_intx_unmask; -+ break; -+ case VFIO_IRQ_SET_ACTION_TRIGGER: -+ func = intel_vgpu_set_intx_trigger; -+ break; -+ } -+ break; -+ case VFIO_PCI_MSI_IRQ_INDEX: -+ switch (flags & VFIO_IRQ_SET_ACTION_TYPE_MASK) { -+ case VFIO_IRQ_SET_ACTION_MASK: -+ case VFIO_IRQ_SET_ACTION_UNMASK: -+ /* XXX Need masking support exported */ -+ break; -+ case VFIO_IRQ_SET_ACTION_TRIGGER: -+ func = intel_vgpu_set_msi_trigger; -+ break; -+ } -+ break; -+ } -+ -+ if (!func) -+ return -ENOTTY; -+ -+ return func(vgpu, index, start, count, flags, data); -+} -+ -+static long intel_vgpu_ioctl(struct mdev_device *mdev, unsigned int cmd, -+ unsigned long arg) -+{ -+ struct intel_vgpu *vgpu = mdev_get_drvdata(mdev); -+ unsigned long minsz; -+ -+ gvt_dbg_core("vgpu%d ioctl, cmd: %d\n", vgpu->id, cmd); -+ -+ if (cmd == VFIO_DEVICE_GET_INFO) { -+ struct vfio_device_info info; -+ -+ minsz = offsetofend(struct vfio_device_info, num_irqs); -+ -+ if (copy_from_user(&info, (void __user *)arg, minsz)) -+ return -EFAULT; -+ -+ if (info.argsz < minsz) -+ return -EINVAL; -+ -+ info.flags = VFIO_DEVICE_FLAGS_PCI; -+ info.flags |= VFIO_DEVICE_FLAGS_RESET; -+ info.num_regions = VFIO_PCI_NUM_REGIONS + -+ vgpu->vdev.num_regions; -+ info.num_irqs = VFIO_PCI_NUM_IRQS; -+ -+ return copy_to_user((void __user *)arg, &info, minsz) ? -+ -EFAULT : 0; -+ -+ } else if (cmd == VFIO_DEVICE_GET_REGION_INFO) { -+ struct vfio_region_info info; -+ struct vfio_info_cap caps = { .buf = NULL, .size = 0 }; -+ unsigned int i; -+ int ret; -+ struct vfio_region_info_cap_sparse_mmap *sparse = NULL; -+ size_t size; -+ int nr_areas = 1; -+ int cap_type_id; -+ -+ minsz = offsetofend(struct vfio_region_info, offset); -+ -+ if (copy_from_user(&info, (void __user *)arg, minsz)) -+ return -EFAULT; -+ -+ if (info.argsz < minsz) -+ return -EINVAL; -+ -+ switch (info.index) { -+ case VFIO_PCI_CONFIG_REGION_INDEX: -+ info.offset = VFIO_PCI_INDEX_TO_OFFSET(info.index); -+ info.size = vgpu->gvt->device_info.cfg_space_size; -+ info.flags = VFIO_REGION_INFO_FLAG_READ | -+ VFIO_REGION_INFO_FLAG_WRITE; -+ break; -+ case VFIO_PCI_BAR0_REGION_INDEX: -+ info.offset = VFIO_PCI_INDEX_TO_OFFSET(info.index); -+ info.size = vgpu->cfg_space.bar[info.index].size; -+ if (!info.size) { -+ info.flags = 0; -+ break; -+ } -+ -+ info.flags = VFIO_REGION_INFO_FLAG_READ | -+ VFIO_REGION_INFO_FLAG_WRITE; -+ break; -+ case VFIO_PCI_BAR1_REGION_INDEX: -+ info.offset = VFIO_PCI_INDEX_TO_OFFSET(info.index); -+ info.size = 0; -+ info.flags = 0; -+ break; -+ case VFIO_PCI_BAR2_REGION_INDEX: -+ info.offset = VFIO_PCI_INDEX_TO_OFFSET(info.index); -+ info.flags = VFIO_REGION_INFO_FLAG_CAPS | -+ VFIO_REGION_INFO_FLAG_MMAP | -+ VFIO_REGION_INFO_FLAG_READ | -+ VFIO_REGION_INFO_FLAG_WRITE; -+ info.size = gvt_aperture_sz(vgpu->gvt); -+ -+ size = sizeof(*sparse) + -+ (nr_areas * sizeof(*sparse->areas)); -+ sparse = kzalloc(size, GFP_KERNEL); -+ if (!sparse) -+ return -ENOMEM; -+ -+ sparse->header.id = VFIO_REGION_INFO_CAP_SPARSE_MMAP; -+ sparse->header.version = 1; -+ sparse->nr_areas = nr_areas; -+ cap_type_id = VFIO_REGION_INFO_CAP_SPARSE_MMAP; -+ sparse->areas[0].offset = -+ PAGE_ALIGN(vgpu_aperture_offset(vgpu)); -+ sparse->areas[0].size = vgpu_aperture_sz(vgpu); -+ break; -+ -+ case VFIO_PCI_BAR3_REGION_INDEX ... VFIO_PCI_BAR5_REGION_INDEX: -+ info.offset = VFIO_PCI_INDEX_TO_OFFSET(info.index); -+ info.size = 0; -+ info.flags = 0; -+ -+ gvt_dbg_core("get region info bar:%d\n", info.index); -+ break; -+ -+ case VFIO_PCI_ROM_REGION_INDEX: -+ case VFIO_PCI_VGA_REGION_INDEX: -+ info.offset = VFIO_PCI_INDEX_TO_OFFSET(info.index); -+ info.size = 0; -+ info.flags = 0; -+ -+ gvt_dbg_core("get region info index:%d\n", info.index); -+ break; -+ default: -+ { -+ struct vfio_region_info_cap_type cap_type = { -+ .header.id = VFIO_REGION_INFO_CAP_TYPE, -+ .header.version = 1 }; -+ -+ if (info.index >= VFIO_PCI_NUM_REGIONS + -+ vgpu->vdev.num_regions) -+ return -EINVAL; -+ info.index = -+ array_index_nospec(info.index, -+ VFIO_PCI_NUM_REGIONS + -+ vgpu->vdev.num_regions); -+ -+ i = info.index - VFIO_PCI_NUM_REGIONS; -+ -+ info.offset = -+ VFIO_PCI_INDEX_TO_OFFSET(info.index); -+ info.size = vgpu->vdev.region[i].size; -+ info.flags = vgpu->vdev.region[i].flags; -+ -+ cap_type.type = vgpu->vdev.region[i].type; -+ cap_type.subtype = vgpu->vdev.region[i].subtype; -+ -+ ret = vfio_info_add_capability(&caps, -+ &cap_type.header, -+ sizeof(cap_type)); -+ if (ret) -+ return ret; -+ } -+ } -+ -+ if ((info.flags & VFIO_REGION_INFO_FLAG_CAPS) && sparse) { -+ switch (cap_type_id) { -+ case VFIO_REGION_INFO_CAP_SPARSE_MMAP: -+ ret = vfio_info_add_capability(&caps, -+ &sparse->header, sizeof(*sparse) + -+ (sparse->nr_areas * -+ sizeof(*sparse->areas))); -+ if (ret) { -+ kfree(sparse); -+ return ret; -+ } -+ break; -+ default: -+ kfree(sparse); -+ return -EINVAL; -+ } -+ } -+ -+ if (caps.size) { -+ info.flags |= VFIO_REGION_INFO_FLAG_CAPS; -+ if (info.argsz < sizeof(info) + caps.size) { -+ info.argsz = sizeof(info) + caps.size; -+ info.cap_offset = 0; -+ } else { -+ vfio_info_cap_shift(&caps, sizeof(info)); -+ if (copy_to_user((void __user *)arg + -+ sizeof(info), caps.buf, -+ caps.size)) { -+ kfree(caps.buf); -+ kfree(sparse); -+ return -EFAULT; -+ } -+ info.cap_offset = sizeof(info); -+ } -+ -+ kfree(caps.buf); -+ } -+ -+ kfree(sparse); -+ return copy_to_user((void __user *)arg, &info, minsz) ? -+ -EFAULT : 0; -+ } else if (cmd == VFIO_DEVICE_GET_IRQ_INFO) { -+ struct vfio_irq_info info; -+ -+ minsz = offsetofend(struct vfio_irq_info, count); -+ -+ if (copy_from_user(&info, (void __user *)arg, minsz)) -+ return -EFAULT; -+ -+ if (info.argsz < minsz || info.index >= VFIO_PCI_NUM_IRQS) -+ return -EINVAL; -+ -+ switch (info.index) { -+ case VFIO_PCI_INTX_IRQ_INDEX: -+ case VFIO_PCI_MSI_IRQ_INDEX: -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ info.flags = VFIO_IRQ_INFO_EVENTFD; -+ -+ info.count = intel_vgpu_get_irq_count(vgpu, info.index); -+ -+ if (info.index == VFIO_PCI_INTX_IRQ_INDEX) -+ info.flags |= (VFIO_IRQ_INFO_MASKABLE | -+ VFIO_IRQ_INFO_AUTOMASKED); -+ else -+ info.flags |= VFIO_IRQ_INFO_NORESIZE; -+ -+ return copy_to_user((void __user *)arg, &info, minsz) ? -+ -EFAULT : 0; -+ } else if (cmd == VFIO_DEVICE_SET_IRQS) { -+ struct vfio_irq_set hdr; -+ u8 *data = NULL; -+ int ret = 0; -+ size_t data_size = 0; -+ -+ minsz = offsetofend(struct vfio_irq_set, count); -+ -+ if (copy_from_user(&hdr, (void __user *)arg, minsz)) -+ return -EFAULT; -+ -+ if (!(hdr.flags & VFIO_IRQ_SET_DATA_NONE)) { -+ int max = intel_vgpu_get_irq_count(vgpu, hdr.index); -+ -+ ret = vfio_set_irqs_validate_and_prepare(&hdr, max, -+ VFIO_PCI_NUM_IRQS, &data_size); -+ if (ret) { -+ gvt_vgpu_err("intel:vfio_set_irqs_validate_and_prepare failed\n"); -+ return -EINVAL; -+ } -+ if (data_size) { -+ data = memdup_user((void __user *)(arg + minsz), -+ data_size); -+ if (IS_ERR(data)) -+ return PTR_ERR(data); -+ } -+ } -+ -+ ret = intel_vgpu_set_irqs(vgpu, hdr.flags, hdr.index, -+ hdr.start, hdr.count, data); -+ kfree(data); -+ -+ return ret; -+ } else if (cmd == VFIO_DEVICE_RESET) { -+ intel_gvt_ops->vgpu_reset(vgpu); -+ return 0; -+ } else if (cmd == VFIO_DEVICE_QUERY_GFX_PLANE) { -+ struct vfio_device_gfx_plane_info dmabuf; -+ int ret = 0; -+ -+ minsz = offsetofend(struct vfio_device_gfx_plane_info, -+ dmabuf_id); -+ if (copy_from_user(&dmabuf, (void __user *)arg, minsz)) -+ return -EFAULT; -+ if (dmabuf.argsz < minsz) -+ return -EINVAL; -+ -+ ret = intel_gvt_ops->vgpu_query_plane(vgpu, &dmabuf); -+ if (ret != 0) -+ return ret; -+ -+ return copy_to_user((void __user *)arg, &dmabuf, minsz) ? -+ -EFAULT : 0; -+ } else if (cmd == VFIO_DEVICE_GET_GFX_DMABUF) { -+ __u32 dmabuf_id; -+ __s32 dmabuf_fd; -+ -+ if (get_user(dmabuf_id, (__u32 __user *)arg)) -+ return -EFAULT; -+ -+ dmabuf_fd = intel_gvt_ops->vgpu_get_dmabuf(vgpu, dmabuf_id); -+ return dmabuf_fd; -+ -+ } -+ -+ return -ENOTTY; -+} -+ -+static ssize_t -+vgpu_id_show(struct device *dev, struct device_attribute *attr, -+ char *buf) -+{ -+ struct mdev_device *mdev = mdev_from_dev(dev); -+ -+ if (mdev) { -+ struct intel_vgpu *vgpu = (struct intel_vgpu *) -+ mdev_get_drvdata(mdev); -+ return sprintf(buf, "%d\n", vgpu->id); -+ } -+ return sprintf(buf, "\n"); -+} -+ -+static ssize_t -+hw_id_show(struct device *dev, struct device_attribute *attr, -+ char *buf) -+{ -+ struct mdev_device *mdev = mdev_from_dev(dev); -+ -+ if (mdev) { -+ struct intel_vgpu *vgpu = (struct intel_vgpu *) -+ mdev_get_drvdata(mdev); -+ return sprintf(buf, "%u\n", -+ vgpu->submission.shadow_ctx->hw_id); -+ } -+ return sprintf(buf, "\n"); -+} -+ -+static DEVICE_ATTR_RO(vgpu_id); -+static DEVICE_ATTR_RO(hw_id); -+ -+static struct attribute *intel_vgpu_attrs[] = { -+ &dev_attr_vgpu_id.attr, -+ &dev_attr_hw_id.attr, -+ NULL -+}; -+ -+static const struct attribute_group intel_vgpu_group = { -+ .name = "intel_vgpu", -+ .attrs = intel_vgpu_attrs, -+}; -+ -+static const struct attribute_group *intel_vgpu_groups[] = { -+ &intel_vgpu_group, -+ NULL, -+}; -+ -+static struct mdev_parent_ops intel_vgpu_ops = { -+ .mdev_attr_groups = intel_vgpu_groups, -+ .create = intel_vgpu_create, -+ .remove = intel_vgpu_remove, -+ -+ .open = intel_vgpu_open, -+ .release = intel_vgpu_release, -+ -+ .read = intel_vgpu_read, -+ .write = intel_vgpu_write, -+ .mmap = intel_vgpu_mmap, -+ .ioctl = intel_vgpu_ioctl, -+}; -+ -+static int kvmgt_host_init(struct device *dev, void *gvt, const void *ops) -+{ -+ struct attribute **kvm_type_attrs; -+ struct attribute_group **kvm_vgpu_type_groups; -+ -+ intel_gvt_ops = ops; -+ if (!intel_gvt_ops->get_gvt_attrs(&kvm_type_attrs, -+ &kvm_vgpu_type_groups)) -+ return -EFAULT; -+ intel_vgpu_ops.supported_type_groups = kvm_vgpu_type_groups; -+ -+ return mdev_register_device(dev, &intel_vgpu_ops); -+} -+ -+static void kvmgt_host_exit(struct device *dev) -+{ -+ mdev_unregister_device(dev); -+} -+ -+static int kvmgt_page_track_add(unsigned long handle, u64 gfn) -+{ -+ struct kvmgt_guest_info *info; -+ struct kvm *kvm; -+ struct kvm_memory_slot *slot; -+ int idx; -+ -+ if (!handle_valid(handle)) -+ return -ESRCH; -+ -+ info = (struct kvmgt_guest_info *)handle; -+ kvm = info->kvm; -+ -+ idx = srcu_read_lock(&kvm->srcu); -+ slot = gfn_to_memslot(kvm, gfn); -+ if (!slot) { -+ srcu_read_unlock(&kvm->srcu, idx); -+ return -EINVAL; -+ } -+ -+ spin_lock(&kvm->mmu_lock); -+ -+ if (kvmgt_gfn_is_write_protected(info, gfn)) -+ goto out; -+ -+ kvm_slot_page_track_add_page(kvm, slot, gfn, KVM_PAGE_TRACK_WRITE); -+ kvmgt_protect_table_add(info, gfn); -+ -+out: -+ spin_unlock(&kvm->mmu_lock); -+ srcu_read_unlock(&kvm->srcu, idx); -+ return 0; -+} -+ -+static int kvmgt_page_track_remove(unsigned long handle, u64 gfn) -+{ -+ struct kvmgt_guest_info *info; -+ struct kvm *kvm; -+ struct kvm_memory_slot *slot; -+ int idx; -+ -+ if (!handle_valid(handle)) -+ return 0; -+ -+ info = (struct kvmgt_guest_info *)handle; -+ kvm = info->kvm; -+ -+ idx = srcu_read_lock(&kvm->srcu); -+ slot = gfn_to_memslot(kvm, gfn); -+ if (!slot) { -+ srcu_read_unlock(&kvm->srcu, idx); -+ return -EINVAL; -+ } -+ -+ spin_lock(&kvm->mmu_lock); -+ -+ if (!kvmgt_gfn_is_write_protected(info, gfn)) -+ goto out; -+ -+ kvm_slot_page_track_remove_page(kvm, slot, gfn, KVM_PAGE_TRACK_WRITE); -+ kvmgt_protect_table_del(info, gfn); -+ -+out: -+ spin_unlock(&kvm->mmu_lock); -+ srcu_read_unlock(&kvm->srcu, idx); -+ return 0; -+} -+ -+static void kvmgt_page_track_write(struct kvm_vcpu *vcpu, gpa_t gpa, -+ const u8 *val, int len, -+ struct kvm_page_track_notifier_node *node) -+{ -+ struct kvmgt_guest_info *info = container_of(node, -+ struct kvmgt_guest_info, track_node); -+ -+ if (kvmgt_gfn_is_write_protected(info, gpa_to_gfn(gpa))) -+ intel_gvt_ops->write_protect_handler(info->vgpu, gpa, -+ (void *)val, len); -+} -+ -+static void kvmgt_page_track_flush_slot(struct kvm *kvm, -+ struct kvm_memory_slot *slot, -+ struct kvm_page_track_notifier_node *node) -+{ -+ int i; -+ gfn_t gfn; -+ struct kvmgt_guest_info *info = container_of(node, -+ struct kvmgt_guest_info, track_node); -+ -+ spin_lock(&kvm->mmu_lock); -+ for (i = 0; i < slot->npages; i++) { -+ gfn = slot->base_gfn + i; -+ if (kvmgt_gfn_is_write_protected(info, gfn)) { -+ kvm_slot_page_track_remove_page(kvm, slot, gfn, -+ KVM_PAGE_TRACK_WRITE); -+ kvmgt_protect_table_del(info, gfn); -+ } -+ } -+ spin_unlock(&kvm->mmu_lock); -+} -+ -+static bool __kvmgt_vgpu_exist(struct intel_vgpu *vgpu, struct kvm *kvm) -+{ -+ struct intel_vgpu *itr; -+ struct kvmgt_guest_info *info; -+ int id; -+ bool ret = false; -+ -+ mutex_lock(&vgpu->gvt->lock); -+ for_each_active_vgpu(vgpu->gvt, itr, id) { -+ if (!handle_valid(itr->handle)) -+ continue; -+ -+ info = (struct kvmgt_guest_info *)itr->handle; -+ if (kvm && kvm == info->kvm) { -+ ret = true; -+ goto out; -+ } -+ } -+out: -+ mutex_unlock(&vgpu->gvt->lock); -+ return ret; -+} -+ -+static int kvmgt_guest_init(struct mdev_device *mdev) -+{ -+ struct kvmgt_guest_info *info; -+ struct intel_vgpu *vgpu; -+ struct kvm *kvm; -+ -+ vgpu = mdev_get_drvdata(mdev); -+ if (handle_valid(vgpu->handle)) -+ return -EEXIST; -+ -+ kvm = vgpu->vdev.kvm; -+ if (!kvm || kvm->mm != current->mm) { -+ gvt_vgpu_err("KVM is required to use Intel vGPU\n"); -+ return -ESRCH; -+ } -+ -+ if (__kvmgt_vgpu_exist(vgpu, kvm)) -+ return -EEXIST; -+ -+ info = vzalloc(sizeof(struct kvmgt_guest_info)); -+ if (!info) -+ return -ENOMEM; -+ -+ vgpu->handle = (unsigned long)info; -+ info->vgpu = vgpu; -+ info->kvm = kvm; -+ kvm_get_kvm(info->kvm); -+ -+ kvmgt_protect_table_init(info); -+ gvt_cache_init(vgpu); -+ -+ init_completion(&vgpu->vblank_done); -+ -+ info->track_node.track_write = kvmgt_page_track_write; -+ info->track_node.track_flush_slot = kvmgt_page_track_flush_slot; -+ kvm_page_track_register_notifier(kvm, &info->track_node); -+ -+ info->debugfs_cache_entries = debugfs_create_ulong( -+ "kvmgt_nr_cache_entries", -+ 0444, vgpu->debugfs, -+ &vgpu->vdev.nr_cache_entries); -+ if (!info->debugfs_cache_entries) -+ gvt_vgpu_err("Cannot create kvmgt debugfs entry\n"); -+ -+ return 0; -+} -+ -+static bool kvmgt_guest_exit(struct kvmgt_guest_info *info) -+{ -+ debugfs_remove(info->debugfs_cache_entries); -+ -+ kvm_page_track_unregister_notifier(info->kvm, &info->track_node); -+ kvm_put_kvm(info->kvm); -+ kvmgt_protect_table_destroy(info); -+ gvt_cache_destroy(info->vgpu); -+ vfree(info); -+ -+ return true; -+} -+ -+static int kvmgt_attach_vgpu(void *vgpu, unsigned long *handle) -+{ -+ /* nothing to do here */ -+ return 0; -+} -+ -+static void kvmgt_detach_vgpu(void *p_vgpu) -+{ -+ int i; -+ struct intel_vgpu *vgpu = (struct intel_vgpu *)p_vgpu; -+ -+ if (!vgpu->vdev.region) -+ return; -+ -+ for (i = 0; i < vgpu->vdev.num_regions; i++) -+ if (vgpu->vdev.region[i].ops->release) -+ vgpu->vdev.region[i].ops->release(vgpu, -+ &vgpu->vdev.region[i]); -+ vgpu->vdev.num_regions = 0; -+ kfree(vgpu->vdev.region); -+ vgpu->vdev.region = NULL; -+} -+ -+static int kvmgt_inject_msi(unsigned long handle, u32 addr, u16 data) -+{ -+ struct kvmgt_guest_info *info; -+ struct intel_vgpu *vgpu; -+ -+ if (!handle_valid(handle)) -+ return -ESRCH; -+ -+ info = (struct kvmgt_guest_info *)handle; -+ vgpu = info->vgpu; -+ -+ /* -+ * When guest is poweroff, msi_trigger is set to NULL, but vgpu's -+ * config and mmio register isn't restored to default during guest -+ * poweroff. If this vgpu is still used in next vm, this vgpu's pipe -+ * may be enabled, then once this vgpu is active, it will get inject -+ * vblank interrupt request. But msi_trigger is null until msi is -+ * enabled by guest. so if msi_trigger is null, success is still -+ * returned and don't inject interrupt into guest. -+ */ -+ if (vgpu->vdev.msi_trigger == NULL) -+ return 0; -+ -+ if (eventfd_signal(vgpu->vdev.msi_trigger, 1) == 1) -+ return 0; -+ -+ return -EFAULT; -+} -+ -+static unsigned long kvmgt_gfn_to_pfn(unsigned long handle, unsigned long gfn) -+{ -+ struct kvmgt_guest_info *info; -+ kvm_pfn_t pfn; -+ -+ if (!handle_valid(handle)) -+ return INTEL_GVT_INVALID_ADDR; -+ -+ info = (struct kvmgt_guest_info *)handle; -+ -+ pfn = gfn_to_pfn(info->kvm, gfn); -+ if (is_error_noslot_pfn(pfn)) -+ return INTEL_GVT_INVALID_ADDR; -+ -+ return pfn; -+} -+ -+static int kvmgt_dma_map_guest_page(unsigned long handle, unsigned long gfn, -+ unsigned long size, dma_addr_t *dma_addr) -+{ -+ struct kvmgt_guest_info *info; -+ struct intel_vgpu *vgpu; -+ struct gvt_dma *entry; -+ int ret; -+ -+ if (!handle_valid(handle)) -+ return -EINVAL; -+ -+ info = (struct kvmgt_guest_info *)handle; -+ vgpu = info->vgpu; -+ -+ mutex_lock(&info->vgpu->vdev.cache_lock); -+ -+ entry = __gvt_cache_find_gfn(info->vgpu, gfn); -+ if (!entry) { -+ ret = gvt_dma_map_page(vgpu, gfn, dma_addr, size); -+ if (ret) -+ goto err_unlock; -+ -+ ret = __gvt_cache_add(info->vgpu, gfn, *dma_addr, size); -+ if (ret) -+ goto err_unmap; -+ } else if (entry->size != size) { -+ /* the same gfn with different size: unmap and re-map */ -+ gvt_dma_unmap_page(vgpu, gfn, entry->dma_addr, entry->size); -+ __gvt_cache_remove_entry(vgpu, entry); -+ -+ ret = gvt_dma_map_page(vgpu, gfn, dma_addr, size); -+ if (ret) -+ goto err_unlock; -+ -+ ret = __gvt_cache_add(info->vgpu, gfn, *dma_addr, size); -+ if (ret) -+ goto err_unmap; -+ } else { -+ kref_get(&entry->ref); -+ *dma_addr = entry->dma_addr; -+ } -+ -+ mutex_unlock(&info->vgpu->vdev.cache_lock); -+ return 0; -+ -+err_unmap: -+ gvt_dma_unmap_page(vgpu, gfn, *dma_addr, size); -+err_unlock: -+ mutex_unlock(&info->vgpu->vdev.cache_lock); -+ return ret; -+} -+ -+static void __gvt_dma_release(struct kref *ref) -+{ -+ struct gvt_dma *entry = container_of(ref, typeof(*entry), ref); -+ -+ gvt_dma_unmap_page(entry->vgpu, entry->gfn, entry->dma_addr, -+ entry->size); -+ __gvt_cache_remove_entry(entry->vgpu, entry); -+} -+ -+static void kvmgt_dma_unmap_guest_page(unsigned long handle, dma_addr_t dma_addr) -+{ -+ struct kvmgt_guest_info *info; -+ struct gvt_dma *entry; -+ -+ if (!handle_valid(handle)) -+ return; -+ -+ info = (struct kvmgt_guest_info *)handle; -+ -+ mutex_lock(&info->vgpu->vdev.cache_lock); -+ entry = __gvt_cache_find_dma_addr(info->vgpu, dma_addr); -+ if (entry) -+ kref_put(&entry->ref, __gvt_dma_release); -+ mutex_unlock(&info->vgpu->vdev.cache_lock); -+} -+ -+static int kvmgt_rw_gpa(unsigned long handle, unsigned long gpa, -+ void *buf, unsigned long len, bool write) -+{ -+ struct kvmgt_guest_info *info; -+ struct kvm *kvm; -+ int idx, ret; -+ bool kthread = current->mm == NULL; -+ -+ if (!handle_valid(handle)) -+ return -ESRCH; -+ -+ info = (struct kvmgt_guest_info *)handle; -+ kvm = info->kvm; -+ -+ if (kthread) { -+ if (!mmget_not_zero(kvm->mm)) -+ return -EFAULT; -+ use_mm(kvm->mm); -+ } -+ -+ idx = srcu_read_lock(&kvm->srcu); -+ ret = write ? kvm_write_guest(kvm, gpa, buf, len) : -+ kvm_read_guest(kvm, gpa, buf, len); -+ srcu_read_unlock(&kvm->srcu, idx); -+ -+ if (kthread) { -+ unuse_mm(kvm->mm); -+ mmput(kvm->mm); -+ } -+ -+ return ret; -+} -+ -+static int kvmgt_read_gpa(unsigned long handle, unsigned long gpa, -+ void *buf, unsigned long len) -+{ -+ return kvmgt_rw_gpa(handle, gpa, buf, len, false); -+} -+ -+static int kvmgt_write_gpa(unsigned long handle, unsigned long gpa, -+ void *buf, unsigned long len) -+{ -+ return kvmgt_rw_gpa(handle, gpa, buf, len, true); -+} -+ -+static unsigned long kvmgt_virt_to_pfn(void *addr) -+{ -+ return PFN_DOWN(__pa(addr)); -+} -+ -+static bool kvmgt_is_valid_gfn(unsigned long handle, unsigned long gfn) -+{ -+ struct kvmgt_guest_info *info; -+ struct kvm *kvm; -+ int idx; -+ bool ret; -+ -+ if (!handle_valid(handle)) -+ return false; -+ -+ info = (struct kvmgt_guest_info *)handle; -+ kvm = info->kvm; -+ -+ idx = srcu_read_lock(&kvm->srcu); -+ ret = kvm_is_visible_gfn(kvm, gfn); -+ srcu_read_unlock(&kvm->srcu, idx); -+ -+ return ret; -+} -+ -+static struct intel_gvt_mpt kvmgt_mpt = { -+ .type = INTEL_GVT_HYPERVISOR_KVM, -+ .host_init = kvmgt_host_init, -+ .host_exit = kvmgt_host_exit, -+ .attach_vgpu = kvmgt_attach_vgpu, -+ .detach_vgpu = kvmgt_detach_vgpu, -+ .inject_msi = kvmgt_inject_msi, -+ .from_virt_to_mfn = kvmgt_virt_to_pfn, -+ .enable_page_track = kvmgt_page_track_add, -+ .disable_page_track = kvmgt_page_track_remove, -+ .read_gpa = kvmgt_read_gpa, -+ .write_gpa = kvmgt_write_gpa, -+ .gfn_to_mfn = kvmgt_gfn_to_pfn, -+ .dma_map_guest_page = kvmgt_dma_map_guest_page, -+ .dma_unmap_guest_page = kvmgt_dma_unmap_guest_page, -+ .set_opregion = kvmgt_set_opregion, -+ .set_edid = kvmgt_set_edid, -+ .get_vfio_device = kvmgt_get_vfio_device, -+ .put_vfio_device = kvmgt_put_vfio_device, -+ .is_valid_gfn = kvmgt_is_valid_gfn, -+}; -+ -+static int __init kvmgt_init(void) -+{ -+ if (intel_gvt_register_hypervisor(&kvmgt_mpt) < 0) -+ return -ENODEV; -+ return 0; -+} -+ -+static void __exit kvmgt_exit(void) -+{ -+ intel_gvt_unregister_hypervisor(); -+} -+ -+module_init(kvmgt_init); -+module_exit(kvmgt_exit); -+ -+MODULE_LICENSE("GPL and additional rights"); -+MODULE_AUTHOR("Intel Corporation"); -diff --git a/drivers/gpu/drm/i915_legacy/gvt/mmio.c b/drivers/gpu/drm/i915_legacy/gvt/mmio.c -new file mode 100644 -index 000000000000..a55178884d67 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/mmio.c -@@ -0,0 +1,315 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Ke Yu -+ * Kevin Tian -+ * Dexuan Cui -+ * -+ * Contributors: -+ * Tina Zhang -+ * Min He -+ * Niu Bing -+ * Zhi Wang -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "gvt.h" -+ -+/** -+ * intel_vgpu_gpa_to_mmio_offset - translate a GPA to MMIO offset -+ * @vgpu: a vGPU -+ * @gpa: guest physical address -+ * -+ * Returns: -+ * Zero on success, negative error code if failed -+ */ -+int intel_vgpu_gpa_to_mmio_offset(struct intel_vgpu *vgpu, u64 gpa) -+{ -+ u64 gttmmio_gpa = intel_vgpu_get_bar_gpa(vgpu, PCI_BASE_ADDRESS_0); -+ return gpa - gttmmio_gpa; -+} -+ -+#define reg_is_mmio(gvt, reg) \ -+ (reg >= 0 && reg < gvt->device_info.mmio_size) -+ -+#define reg_is_gtt(gvt, reg) \ -+ (reg >= gvt->device_info.gtt_start_offset \ -+ && reg < gvt->device_info.gtt_start_offset + gvt_ggtt_sz(gvt)) -+ -+static void failsafe_emulate_mmio_rw(struct intel_vgpu *vgpu, u64 pa, -+ void *p_data, unsigned int bytes, bool read) -+{ -+ struct intel_gvt *gvt = NULL; -+ void *pt = NULL; -+ unsigned int offset = 0; -+ -+ if (!vgpu || !p_data) -+ return; -+ -+ gvt = vgpu->gvt; -+ mutex_lock(&vgpu->vgpu_lock); -+ offset = intel_vgpu_gpa_to_mmio_offset(vgpu, pa); -+ if (reg_is_mmio(gvt, offset)) { -+ if (read) -+ intel_vgpu_default_mmio_read(vgpu, offset, p_data, -+ bytes); -+ else -+ intel_vgpu_default_mmio_write(vgpu, offset, p_data, -+ bytes); -+ } else if (reg_is_gtt(gvt, offset)) { -+ offset -= gvt->device_info.gtt_start_offset; -+ pt = vgpu->gtt.ggtt_mm->ggtt_mm.virtual_ggtt + offset; -+ if (read) -+ memcpy(p_data, pt, bytes); -+ else -+ memcpy(pt, p_data, bytes); -+ -+ } -+ mutex_unlock(&vgpu->vgpu_lock); -+} -+ -+/** -+ * intel_vgpu_emulate_mmio_read - emulate MMIO read -+ * @vgpu: a vGPU -+ * @pa: guest physical address -+ * @p_data: data return buffer -+ * @bytes: access data length -+ * -+ * Returns: -+ * Zero on success, negative error code if failed -+ */ -+int intel_vgpu_emulate_mmio_read(struct intel_vgpu *vgpu, u64 pa, -+ void *p_data, unsigned int bytes) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ unsigned int offset = 0; -+ int ret = -EINVAL; -+ -+ if (vgpu->failsafe) { -+ failsafe_emulate_mmio_rw(vgpu, pa, p_data, bytes, true); -+ return 0; -+ } -+ mutex_lock(&vgpu->vgpu_lock); -+ -+ offset = intel_vgpu_gpa_to_mmio_offset(vgpu, pa); -+ -+ if (WARN_ON(bytes > 8)) -+ goto err; -+ -+ if (reg_is_gtt(gvt, offset)) { -+ if (WARN_ON(!IS_ALIGNED(offset, 4) && !IS_ALIGNED(offset, 8))) -+ goto err; -+ if (WARN_ON(bytes != 4 && bytes != 8)) -+ goto err; -+ if (WARN_ON(!reg_is_gtt(gvt, offset + bytes - 1))) -+ goto err; -+ -+ ret = intel_vgpu_emulate_ggtt_mmio_read(vgpu, offset, -+ p_data, bytes); -+ if (ret) -+ goto err; -+ goto out; -+ } -+ -+ if (WARN_ON_ONCE(!reg_is_mmio(gvt, offset))) { -+ ret = intel_gvt_hypervisor_read_gpa(vgpu, pa, p_data, bytes); -+ goto out; -+ } -+ -+ if (WARN_ON(!reg_is_mmio(gvt, offset + bytes - 1))) -+ goto err; -+ -+ if (!intel_gvt_mmio_is_unalign(gvt, offset)) { -+ if (WARN_ON(!IS_ALIGNED(offset, bytes))) -+ goto err; -+ } -+ -+ ret = intel_vgpu_mmio_reg_rw(vgpu, offset, p_data, bytes, true); -+ if (ret < 0) -+ goto err; -+ -+ intel_gvt_mmio_set_accessed(gvt, offset); -+ ret = 0; -+ goto out; -+ -+err: -+ gvt_vgpu_err("fail to emulate MMIO read %08x len %d\n", -+ offset, bytes); -+out: -+ mutex_unlock(&vgpu->vgpu_lock); -+ return ret; -+} -+ -+/** -+ * intel_vgpu_emulate_mmio_write - emulate MMIO write -+ * @vgpu: a vGPU -+ * @pa: guest physical address -+ * @p_data: write data buffer -+ * @bytes: access data length -+ * -+ * Returns: -+ * Zero on success, negative error code if failed -+ */ -+int intel_vgpu_emulate_mmio_write(struct intel_vgpu *vgpu, u64 pa, -+ void *p_data, unsigned int bytes) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ unsigned int offset = 0; -+ int ret = -EINVAL; -+ -+ if (vgpu->failsafe) { -+ failsafe_emulate_mmio_rw(vgpu, pa, p_data, bytes, false); -+ return 0; -+ } -+ -+ mutex_lock(&vgpu->vgpu_lock); -+ -+ offset = intel_vgpu_gpa_to_mmio_offset(vgpu, pa); -+ -+ if (WARN_ON(bytes > 8)) -+ goto err; -+ -+ if (reg_is_gtt(gvt, offset)) { -+ if (WARN_ON(!IS_ALIGNED(offset, 4) && !IS_ALIGNED(offset, 8))) -+ goto err; -+ if (WARN_ON(bytes != 4 && bytes != 8)) -+ goto err; -+ if (WARN_ON(!reg_is_gtt(gvt, offset + bytes - 1))) -+ goto err; -+ -+ ret = intel_vgpu_emulate_ggtt_mmio_write(vgpu, offset, -+ p_data, bytes); -+ if (ret) -+ goto err; -+ goto out; -+ } -+ -+ if (WARN_ON_ONCE(!reg_is_mmio(gvt, offset))) { -+ ret = intel_gvt_hypervisor_write_gpa(vgpu, pa, p_data, bytes); -+ goto out; -+ } -+ -+ ret = intel_vgpu_mmio_reg_rw(vgpu, offset, p_data, bytes, false); -+ if (ret < 0) -+ goto err; -+ -+ intel_gvt_mmio_set_accessed(gvt, offset); -+ ret = 0; -+ goto out; -+err: -+ gvt_vgpu_err("fail to emulate MMIO write %08x len %d\n", offset, -+ bytes); -+out: -+ mutex_unlock(&vgpu->vgpu_lock); -+ return ret; -+} -+ -+ -+/** -+ * intel_vgpu_reset_mmio - reset virtual MMIO space -+ * @vgpu: a vGPU -+ * @dmlr: whether this is device model level reset -+ */ -+void intel_vgpu_reset_mmio(struct intel_vgpu *vgpu, bool dmlr) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ const struct intel_gvt_device_info *info = &gvt->device_info; -+ void *mmio = gvt->firmware.mmio; -+ -+ if (dmlr) { -+ memcpy(vgpu->mmio.vreg, mmio, info->mmio_size); -+ -+ vgpu_vreg_t(vgpu, GEN6_GT_THREAD_STATUS_REG) = 0; -+ -+ /* set the bit 0:2(Core C-State ) to C0 */ -+ vgpu_vreg_t(vgpu, GEN6_GT_CORE_STATUS) = 0; -+ -+ if (IS_BROXTON(vgpu->gvt->dev_priv)) { -+ vgpu_vreg_t(vgpu, BXT_P_CR_GT_DISP_PWRON) &= -+ ~(BIT(0) | BIT(1)); -+ vgpu_vreg_t(vgpu, BXT_PORT_CL1CM_DW0(DPIO_PHY0)) &= -+ ~PHY_POWER_GOOD; -+ vgpu_vreg_t(vgpu, BXT_PORT_CL1CM_DW0(DPIO_PHY1)) &= -+ ~PHY_POWER_GOOD; -+ vgpu_vreg_t(vgpu, BXT_PHY_CTL_FAMILY(DPIO_PHY0)) &= -+ ~BIT(30); -+ vgpu_vreg_t(vgpu, BXT_PHY_CTL_FAMILY(DPIO_PHY1)) &= -+ ~BIT(30); -+ vgpu_vreg_t(vgpu, BXT_PHY_CTL(PORT_A)) &= -+ ~BXT_PHY_LANE_ENABLED; -+ vgpu_vreg_t(vgpu, BXT_PHY_CTL(PORT_A)) |= -+ BXT_PHY_CMNLANE_POWERDOWN_ACK | -+ BXT_PHY_LANE_POWERDOWN_ACK; -+ vgpu_vreg_t(vgpu, BXT_PHY_CTL(PORT_B)) &= -+ ~BXT_PHY_LANE_ENABLED; -+ vgpu_vreg_t(vgpu, BXT_PHY_CTL(PORT_B)) |= -+ BXT_PHY_CMNLANE_POWERDOWN_ACK | -+ BXT_PHY_LANE_POWERDOWN_ACK; -+ vgpu_vreg_t(vgpu, BXT_PHY_CTL(PORT_C)) &= -+ ~BXT_PHY_LANE_ENABLED; -+ vgpu_vreg_t(vgpu, BXT_PHY_CTL(PORT_C)) |= -+ BXT_PHY_CMNLANE_POWERDOWN_ACK | -+ BXT_PHY_LANE_POWERDOWN_ACK; -+ } -+ } else { -+#define GVT_GEN8_MMIO_RESET_OFFSET (0x44200) -+ /* only reset the engine related, so starting with 0x44200 -+ * interrupt include DE,display mmio related will not be -+ * touched -+ */ -+ memcpy(vgpu->mmio.vreg, mmio, GVT_GEN8_MMIO_RESET_OFFSET); -+ } -+ -+} -+ -+/** -+ * intel_vgpu_init_mmio - init MMIO space -+ * @vgpu: a vGPU -+ * -+ * Returns: -+ * Zero on success, negative error code if failed -+ */ -+int intel_vgpu_init_mmio(struct intel_vgpu *vgpu) -+{ -+ const struct intel_gvt_device_info *info = &vgpu->gvt->device_info; -+ -+ vgpu->mmio.vreg = vzalloc(info->mmio_size); -+ if (!vgpu->mmio.vreg) -+ return -ENOMEM; -+ -+ intel_vgpu_reset_mmio(vgpu, true); -+ -+ return 0; -+} -+ -+/** -+ * intel_vgpu_clean_mmio - clean MMIO space -+ * @vgpu: a vGPU -+ * -+ */ -+void intel_vgpu_clean_mmio(struct intel_vgpu *vgpu) -+{ -+ vfree(vgpu->mmio.vreg); -+ vgpu->mmio.vreg = NULL; -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/mmio.h b/drivers/gpu/drm/i915_legacy/gvt/mmio.h -new file mode 100644 -index 000000000000..5874f1cb4306 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/mmio.h -@@ -0,0 +1,105 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Ke Yu -+ * Kevin Tian -+ * Dexuan Cui -+ * -+ * Contributors: -+ * Tina Zhang -+ * Min He -+ * Niu Bing -+ * Zhi Wang -+ * -+ */ -+ -+#ifndef _GVT_MMIO_H_ -+#define _GVT_MMIO_H_ -+ -+struct intel_gvt; -+struct intel_vgpu; -+ -+#define D_BDW (1 << 0) -+#define D_SKL (1 << 1) -+#define D_KBL (1 << 2) -+#define D_BXT (1 << 3) -+#define D_CFL (1 << 4) -+ -+#define D_GEN9PLUS (D_SKL | D_KBL | D_BXT | D_CFL) -+#define D_GEN8PLUS (D_BDW | D_SKL | D_KBL | D_BXT | D_CFL) -+ -+#define D_SKL_PLUS (D_SKL | D_KBL | D_BXT | D_CFL) -+#define D_BDW_PLUS (D_BDW | D_SKL | D_KBL | D_BXT | D_CFL) -+ -+#define D_PRE_SKL (D_BDW) -+#define D_ALL (D_BDW | D_SKL | D_KBL | D_BXT | D_CFL) -+ -+typedef int (*gvt_mmio_func)(struct intel_vgpu *, unsigned int, void *, -+ unsigned int); -+ -+struct intel_gvt_mmio_info { -+ u32 offset; -+ u64 ro_mask; -+ u32 device; -+ gvt_mmio_func read; -+ gvt_mmio_func write; -+ u32 addr_range; -+ struct hlist_node node; -+}; -+ -+int intel_gvt_render_mmio_to_ring_id(struct intel_gvt *gvt, -+ unsigned int reg); -+unsigned long intel_gvt_get_device_type(struct intel_gvt *gvt); -+bool intel_gvt_match_device(struct intel_gvt *gvt, unsigned long device); -+ -+int intel_gvt_setup_mmio_info(struct intel_gvt *gvt); -+void intel_gvt_clean_mmio_info(struct intel_gvt *gvt); -+int intel_gvt_for_each_tracked_mmio(struct intel_gvt *gvt, -+ int (*handler)(struct intel_gvt *gvt, u32 offset, void *data), -+ void *data); -+ -+int intel_vgpu_init_mmio(struct intel_vgpu *vgpu); -+void intel_vgpu_reset_mmio(struct intel_vgpu *vgpu, bool dmlr); -+void intel_vgpu_clean_mmio(struct intel_vgpu *vgpu); -+ -+int intel_vgpu_gpa_to_mmio_offset(struct intel_vgpu *vgpu, u64 gpa); -+ -+int intel_vgpu_emulate_mmio_read(struct intel_vgpu *vgpu, u64 pa, -+ void *p_data, unsigned int bytes); -+int intel_vgpu_emulate_mmio_write(struct intel_vgpu *vgpu, u64 pa, -+ void *p_data, unsigned int bytes); -+ -+int intel_vgpu_default_mmio_read(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes); -+int intel_vgpu_default_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes); -+ -+bool intel_gvt_in_force_nonpriv_whitelist(struct intel_gvt *gvt, -+ unsigned int offset); -+ -+int intel_vgpu_mmio_reg_rw(struct intel_vgpu *vgpu, unsigned int offset, -+ void *pdata, unsigned int bytes, bool is_read); -+ -+int intel_vgpu_mask_mmio_write(struct intel_vgpu *vgpu, unsigned int offset, -+ void *p_data, unsigned int bytes); -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/gvt/mmio_context.c b/drivers/gpu/drm/i915_legacy/gvt/mmio_context.c -new file mode 100644 -index 000000000000..90bb3df0db50 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/mmio_context.c -@@ -0,0 +1,580 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eddie Dong -+ * Kevin Tian -+ * -+ * Contributors: -+ * Zhi Wang -+ * Changbin Du -+ * Zhenyu Wang -+ * Tina Zhang -+ * Bing Niu -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "gvt.h" -+#include "trace.h" -+ -+#define GEN9_MOCS_SIZE 64 -+ -+/* Raw offset is appened to each line for convenience. */ -+static struct engine_mmio gen8_engine_mmio_list[] __cacheline_aligned = { -+ {RCS0, GFX_MODE_GEN7, 0xffff, false}, /* 0x229c */ -+ {RCS0, GEN9_CTX_PREEMPT_REG, 0x0, false}, /* 0x2248 */ -+ {RCS0, HWSTAM, 0x0, false}, /* 0x2098 */ -+ {RCS0, INSTPM, 0xffff, true}, /* 0x20c0 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 0), 0, false}, /* 0x24d0 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 1), 0, false}, /* 0x24d4 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 2), 0, false}, /* 0x24d8 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 3), 0, false}, /* 0x24dc */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 4), 0, false}, /* 0x24e0 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 5), 0, false}, /* 0x24e4 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 6), 0, false}, /* 0x24e8 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 7), 0, false}, /* 0x24ec */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 8), 0, false}, /* 0x24f0 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 9), 0, false}, /* 0x24f4 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 10), 0, false}, /* 0x24f8 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 11), 0, false}, /* 0x24fc */ -+ {RCS0, CACHE_MODE_1, 0xffff, true}, /* 0x7004 */ -+ {RCS0, GEN7_GT_MODE, 0xffff, true}, /* 0x7008 */ -+ {RCS0, CACHE_MODE_0_GEN7, 0xffff, true}, /* 0x7000 */ -+ {RCS0, GEN7_COMMON_SLICE_CHICKEN1, 0xffff, true}, /* 0x7010 */ -+ {RCS0, HDC_CHICKEN0, 0xffff, true}, /* 0x7300 */ -+ {RCS0, VF_GUARDBAND, 0xffff, true}, /* 0x83a4 */ -+ -+ {BCS0, RING_GFX_MODE(BLT_RING_BASE), 0xffff, false}, /* 0x2229c */ -+ {BCS0, RING_MI_MODE(BLT_RING_BASE), 0xffff, false}, /* 0x2209c */ -+ {BCS0, RING_INSTPM(BLT_RING_BASE), 0xffff, false}, /* 0x220c0 */ -+ {BCS0, RING_HWSTAM(BLT_RING_BASE), 0x0, false}, /* 0x22098 */ -+ {BCS0, RING_EXCC(BLT_RING_BASE), 0xffff, false}, /* 0x22028 */ -+ {RCS0, INVALID_MMIO_REG, 0, false } /* Terminated */ -+}; -+ -+static struct engine_mmio gen9_engine_mmio_list[] __cacheline_aligned = { -+ {RCS0, GFX_MODE_GEN7, 0xffff, false}, /* 0x229c */ -+ {RCS0, GEN9_CTX_PREEMPT_REG, 0x0, false}, /* 0x2248 */ -+ {RCS0, HWSTAM, 0x0, false}, /* 0x2098 */ -+ {RCS0, INSTPM, 0xffff, true}, /* 0x20c0 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 0), 0, false}, /* 0x24d0 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 1), 0, false}, /* 0x24d4 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 2), 0, false}, /* 0x24d8 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 3), 0, false}, /* 0x24dc */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 4), 0, false}, /* 0x24e0 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 5), 0, false}, /* 0x24e4 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 6), 0, false}, /* 0x24e8 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 7), 0, false}, /* 0x24ec */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 8), 0, false}, /* 0x24f0 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 9), 0, false}, /* 0x24f4 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 10), 0, false}, /* 0x24f8 */ -+ {RCS0, RING_FORCE_TO_NONPRIV(RENDER_RING_BASE, 11), 0, false}, /* 0x24fc */ -+ {RCS0, CACHE_MODE_1, 0xffff, true}, /* 0x7004 */ -+ {RCS0, GEN7_GT_MODE, 0xffff, true}, /* 0x7008 */ -+ {RCS0, CACHE_MODE_0_GEN7, 0xffff, true}, /* 0x7000 */ -+ {RCS0, GEN7_COMMON_SLICE_CHICKEN1, 0xffff, true}, /* 0x7010 */ -+ {RCS0, HDC_CHICKEN0, 0xffff, true}, /* 0x7300 */ -+ {RCS0, VF_GUARDBAND, 0xffff, true}, /* 0x83a4 */ -+ -+ {RCS0, GEN8_PRIVATE_PAT_LO, 0, false}, /* 0x40e0 */ -+ {RCS0, GEN8_PRIVATE_PAT_HI, 0, false}, /* 0x40e4 */ -+ {RCS0, GEN8_CS_CHICKEN1, 0xffff, true}, /* 0x2580 */ -+ {RCS0, COMMON_SLICE_CHICKEN2, 0xffff, true}, /* 0x7014 */ -+ {RCS0, GEN9_CS_DEBUG_MODE1, 0xffff, false}, /* 0x20ec */ -+ {RCS0, GEN8_L3SQCREG4, 0, false}, /* 0xb118 */ -+ {RCS0, GEN7_HALF_SLICE_CHICKEN1, 0xffff, true}, /* 0xe100 */ -+ {RCS0, HALF_SLICE_CHICKEN2, 0xffff, true}, /* 0xe180 */ -+ {RCS0, HALF_SLICE_CHICKEN3, 0xffff, true}, /* 0xe184 */ -+ {RCS0, GEN9_HALF_SLICE_CHICKEN5, 0xffff, true}, /* 0xe188 */ -+ {RCS0, GEN9_HALF_SLICE_CHICKEN7, 0xffff, true}, /* 0xe194 */ -+ {RCS0, GEN8_ROW_CHICKEN, 0xffff, true}, /* 0xe4f0 */ -+ {RCS0, TRVATTL3PTRDW(0), 0, true}, /* 0x4de0 */ -+ {RCS0, TRVATTL3PTRDW(1), 0, true}, /* 0x4de4 */ -+ {RCS0, TRNULLDETCT, 0, true}, /* 0x4de8 */ -+ {RCS0, TRINVTILEDETCT, 0, true}, /* 0x4dec */ -+ {RCS0, TRVADR, 0, true}, /* 0x4df0 */ -+ {RCS0, TRTTE, 0, true}, /* 0x4df4 */ -+ {RCS0, _MMIO(0x4dfc), 0, true}, -+ -+ {BCS0, RING_GFX_MODE(BLT_RING_BASE), 0xffff, false}, /* 0x2229c */ -+ {BCS0, RING_MI_MODE(BLT_RING_BASE), 0xffff, false}, /* 0x2209c */ -+ {BCS0, RING_INSTPM(BLT_RING_BASE), 0xffff, false}, /* 0x220c0 */ -+ {BCS0, RING_HWSTAM(BLT_RING_BASE), 0x0, false}, /* 0x22098 */ -+ {BCS0, RING_EXCC(BLT_RING_BASE), 0xffff, false}, /* 0x22028 */ -+ -+ {VCS1, RING_EXCC(GEN8_BSD2_RING_BASE), 0xffff, false}, /* 0x1c028 */ -+ -+ {VECS0, RING_EXCC(VEBOX_RING_BASE), 0xffff, false}, /* 0x1a028 */ -+ -+ {RCS0, GEN8_HDC_CHICKEN1, 0xffff, true}, /* 0x7304 */ -+ {RCS0, GEN9_CTX_PREEMPT_REG, 0x0, false}, /* 0x2248 */ -+ {RCS0, GEN7_UCGCTL4, 0x0, false}, /* 0x940c */ -+ {RCS0, GAMT_CHKN_BIT_REG, 0x0, false}, /* 0x4ab8 */ -+ -+ {RCS0, GEN9_GAMT_ECO_REG_RW_IA, 0x0, false}, /* 0x4ab0 */ -+ {RCS0, GEN9_CSFE_CHICKEN1_RCS, 0xffff, false}, /* 0x20d4 */ -+ {RCS0, _MMIO(0x20D8), 0xffff, true}, /* 0x20d8 */ -+ -+ {RCS0, GEN8_GARBCNTL, 0x0, false}, /* 0xb004 */ -+ {RCS0, GEN7_FF_THREAD_MODE, 0x0, false}, /* 0x20a0 */ -+ {RCS0, FF_SLICE_CS_CHICKEN2, 0xffff, false}, /* 0x20e4 */ -+ {RCS0, INVALID_MMIO_REG, 0, false } /* Terminated */ -+}; -+ -+static struct { -+ bool initialized; -+ u32 control_table[I915_NUM_ENGINES][GEN9_MOCS_SIZE]; -+ u32 l3cc_table[GEN9_MOCS_SIZE / 2]; -+} gen9_render_mocs; -+ -+static void load_render_mocs(struct drm_i915_private *dev_priv) -+{ -+ i915_reg_t offset; -+ u32 regs[] = { -+ [RCS0] = 0xc800, -+ [VCS0] = 0xc900, -+ [VCS1] = 0xca00, -+ [BCS0] = 0xcc00, -+ [VECS0] = 0xcb00, -+ }; -+ int ring_id, i; -+ -+ for (ring_id = 0; ring_id < ARRAY_SIZE(regs); ring_id++) { -+ if (!HAS_ENGINE(dev_priv, ring_id)) -+ continue; -+ offset.reg = regs[ring_id]; -+ for (i = 0; i < GEN9_MOCS_SIZE; i++) { -+ gen9_render_mocs.control_table[ring_id][i] = -+ I915_READ_FW(offset); -+ offset.reg += 4; -+ } -+ } -+ -+ offset.reg = 0xb020; -+ for (i = 0; i < GEN9_MOCS_SIZE / 2; i++) { -+ gen9_render_mocs.l3cc_table[i] = -+ I915_READ_FW(offset); -+ offset.reg += 4; -+ } -+ gen9_render_mocs.initialized = true; -+} -+ -+static int -+restore_context_mmio_for_inhibit(struct intel_vgpu *vgpu, -+ struct i915_request *req) -+{ -+ u32 *cs; -+ int ret; -+ struct engine_mmio *mmio; -+ struct intel_gvt *gvt = vgpu->gvt; -+ int ring_id = req->engine->id; -+ int count = gvt->engine_mmio_list.ctx_mmio_count[ring_id]; -+ -+ if (count == 0) -+ return 0; -+ -+ ret = req->engine->emit_flush(req, EMIT_BARRIER); -+ if (ret) -+ return ret; -+ -+ cs = intel_ring_begin(req, count * 2 + 2); -+ if (IS_ERR(cs)) -+ return PTR_ERR(cs); -+ -+ *cs++ = MI_LOAD_REGISTER_IMM(count); -+ for (mmio = gvt->engine_mmio_list.mmio; -+ i915_mmio_reg_valid(mmio->reg); mmio++) { -+ if (mmio->ring_id != ring_id || -+ !mmio->in_context) -+ continue; -+ -+ *cs++ = i915_mmio_reg_offset(mmio->reg); -+ *cs++ = vgpu_vreg_t(vgpu, mmio->reg) | -+ (mmio->mask << 16); -+ gvt_dbg_core("add lri reg pair 0x%x:0x%x in inhibit ctx, vgpu:%d, rind_id:%d\n", -+ *(cs-2), *(cs-1), vgpu->id, ring_id); -+ } -+ -+ *cs++ = MI_NOOP; -+ intel_ring_advance(req, cs); -+ -+ ret = req->engine->emit_flush(req, EMIT_BARRIER); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int -+restore_render_mocs_control_for_inhibit(struct intel_vgpu *vgpu, -+ struct i915_request *req) -+{ -+ unsigned int index; -+ u32 *cs; -+ -+ cs = intel_ring_begin(req, 2 * GEN9_MOCS_SIZE + 2); -+ if (IS_ERR(cs)) -+ return PTR_ERR(cs); -+ -+ *cs++ = MI_LOAD_REGISTER_IMM(GEN9_MOCS_SIZE); -+ -+ for (index = 0; index < GEN9_MOCS_SIZE; index++) { -+ *cs++ = i915_mmio_reg_offset(GEN9_GFX_MOCS(index)); -+ *cs++ = vgpu_vreg_t(vgpu, GEN9_GFX_MOCS(index)); -+ gvt_dbg_core("add lri reg pair 0x%x:0x%x in inhibit ctx, vgpu:%d, rind_id:%d\n", -+ *(cs-2), *(cs-1), vgpu->id, req->engine->id); -+ -+ } -+ -+ *cs++ = MI_NOOP; -+ intel_ring_advance(req, cs); -+ -+ return 0; -+} -+ -+static int -+restore_render_mocs_l3cc_for_inhibit(struct intel_vgpu *vgpu, -+ struct i915_request *req) -+{ -+ unsigned int index; -+ u32 *cs; -+ -+ cs = intel_ring_begin(req, 2 * GEN9_MOCS_SIZE / 2 + 2); -+ if (IS_ERR(cs)) -+ return PTR_ERR(cs); -+ -+ *cs++ = MI_LOAD_REGISTER_IMM(GEN9_MOCS_SIZE / 2); -+ -+ for (index = 0; index < GEN9_MOCS_SIZE / 2; index++) { -+ *cs++ = i915_mmio_reg_offset(GEN9_LNCFCMOCS(index)); -+ *cs++ = vgpu_vreg_t(vgpu, GEN9_LNCFCMOCS(index)); -+ gvt_dbg_core("add lri reg pair 0x%x:0x%x in inhibit ctx, vgpu:%d, rind_id:%d\n", -+ *(cs-2), *(cs-1), vgpu->id, req->engine->id); -+ -+ } -+ -+ *cs++ = MI_NOOP; -+ intel_ring_advance(req, cs); -+ -+ return 0; -+} -+ -+/* -+ * Use lri command to initialize the mmio which is in context state image for -+ * inhibit context, it contains tracked engine mmio, render_mocs and -+ * render_mocs_l3cc. -+ */ -+int intel_vgpu_restore_inhibit_context(struct intel_vgpu *vgpu, -+ struct i915_request *req) -+{ -+ int ret; -+ u32 *cs; -+ -+ cs = intel_ring_begin(req, 2); -+ if (IS_ERR(cs)) -+ return PTR_ERR(cs); -+ -+ *cs++ = MI_ARB_ON_OFF | MI_ARB_DISABLE; -+ *cs++ = MI_NOOP; -+ intel_ring_advance(req, cs); -+ -+ ret = restore_context_mmio_for_inhibit(vgpu, req); -+ if (ret) -+ goto out; -+ -+ /* no MOCS register in context except render engine */ -+ if (req->engine->id != RCS0) -+ goto out; -+ -+ ret = restore_render_mocs_control_for_inhibit(vgpu, req); -+ if (ret) -+ goto out; -+ -+ ret = restore_render_mocs_l3cc_for_inhibit(vgpu, req); -+ if (ret) -+ goto out; -+ -+out: -+ cs = intel_ring_begin(req, 2); -+ if (IS_ERR(cs)) -+ return PTR_ERR(cs); -+ -+ *cs++ = MI_ARB_ON_OFF | MI_ARB_ENABLE; -+ *cs++ = MI_NOOP; -+ intel_ring_advance(req, cs); -+ -+ return ret; -+} -+ -+static void handle_tlb_pending_event(struct intel_vgpu *vgpu, int ring_id) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ enum forcewake_domains fw; -+ i915_reg_t reg; -+ u32 regs[] = { -+ [RCS0] = 0x4260, -+ [VCS0] = 0x4264, -+ [VCS1] = 0x4268, -+ [BCS0] = 0x426c, -+ [VECS0] = 0x4270, -+ }; -+ -+ if (WARN_ON(ring_id >= ARRAY_SIZE(regs))) -+ return; -+ -+ if (!test_and_clear_bit(ring_id, (void *)s->tlb_handle_pending)) -+ return; -+ -+ reg = _MMIO(regs[ring_id]); -+ -+ /* WaForceWakeRenderDuringMmioTLBInvalidate:skl -+ * we need to put a forcewake when invalidating RCS TLB caches, -+ * otherwise device can go to RC6 state and interrupt invalidation -+ * process -+ */ -+ fw = intel_uncore_forcewake_for_reg(uncore, reg, -+ FW_REG_READ | FW_REG_WRITE); -+ if (ring_id == RCS0 && INTEL_GEN(dev_priv) >= 9) -+ fw |= FORCEWAKE_RENDER; -+ -+ intel_uncore_forcewake_get(uncore, fw); -+ -+ intel_uncore_write_fw(uncore, reg, 0x1); -+ -+ if (wait_for_atomic((intel_uncore_read_fw(uncore, reg) == 0), 50)) -+ gvt_vgpu_err("timeout in invalidate ring (%d) tlb\n", ring_id); -+ else -+ vgpu_vreg_t(vgpu, reg) = 0; -+ -+ intel_uncore_forcewake_put(uncore, fw); -+ -+ gvt_dbg_core("invalidate TLB for ring %d\n", ring_id); -+} -+ -+static void switch_mocs(struct intel_vgpu *pre, struct intel_vgpu *next, -+ int ring_id) -+{ -+ struct drm_i915_private *dev_priv; -+ i915_reg_t offset, l3_offset; -+ u32 old_v, new_v; -+ -+ u32 regs[] = { -+ [RCS0] = 0xc800, -+ [VCS0] = 0xc900, -+ [VCS1] = 0xca00, -+ [BCS0] = 0xcc00, -+ [VECS0] = 0xcb00, -+ }; -+ int i; -+ -+ dev_priv = pre ? pre->gvt->dev_priv : next->gvt->dev_priv; -+ if (WARN_ON(ring_id >= ARRAY_SIZE(regs))) -+ return; -+ -+ if (ring_id == RCS0 && IS_GEN(dev_priv, 9)) -+ return; -+ -+ if (!pre && !gen9_render_mocs.initialized) -+ load_render_mocs(dev_priv); -+ -+ offset.reg = regs[ring_id]; -+ for (i = 0; i < GEN9_MOCS_SIZE; i++) { -+ if (pre) -+ old_v = vgpu_vreg_t(pre, offset); -+ else -+ old_v = gen9_render_mocs.control_table[ring_id][i]; -+ if (next) -+ new_v = vgpu_vreg_t(next, offset); -+ else -+ new_v = gen9_render_mocs.control_table[ring_id][i]; -+ -+ if (old_v != new_v) -+ I915_WRITE_FW(offset, new_v); -+ -+ offset.reg += 4; -+ } -+ -+ if (ring_id == RCS0) { -+ l3_offset.reg = 0xb020; -+ for (i = 0; i < GEN9_MOCS_SIZE / 2; i++) { -+ if (pre) -+ old_v = vgpu_vreg_t(pre, l3_offset); -+ else -+ old_v = gen9_render_mocs.l3cc_table[i]; -+ if (next) -+ new_v = vgpu_vreg_t(next, l3_offset); -+ else -+ new_v = gen9_render_mocs.l3cc_table[i]; -+ -+ if (old_v != new_v) -+ I915_WRITE_FW(l3_offset, new_v); -+ -+ l3_offset.reg += 4; -+ } -+ } -+} -+ -+#define CTX_CONTEXT_CONTROL_VAL 0x03 -+ -+bool is_inhibit_context(struct intel_context *ce) -+{ -+ const u32 *reg_state = ce->lrc_reg_state; -+ u32 inhibit_mask = -+ _MASKED_BIT_ENABLE(CTX_CTRL_ENGINE_CTX_RESTORE_INHIBIT); -+ -+ return inhibit_mask == -+ (reg_state[CTX_CONTEXT_CONTROL_VAL] & inhibit_mask); -+} -+ -+/* Switch ring mmio values (context). */ -+static void switch_mmio(struct intel_vgpu *pre, -+ struct intel_vgpu *next, -+ int ring_id) -+{ -+ struct drm_i915_private *dev_priv; -+ struct intel_vgpu_submission *s; -+ struct engine_mmio *mmio; -+ u32 old_v, new_v; -+ -+ dev_priv = pre ? pre->gvt->dev_priv : next->gvt->dev_priv; -+ if (INTEL_GEN(dev_priv) >= 9) -+ switch_mocs(pre, next, ring_id); -+ -+ for (mmio = dev_priv->gvt->engine_mmio_list.mmio; -+ i915_mmio_reg_valid(mmio->reg); mmio++) { -+ if (mmio->ring_id != ring_id) -+ continue; -+ /* -+ * No need to do save or restore of the mmio which is in context -+ * state image on gen9, it's initialized by lri command and -+ * save or restore with context together. -+ */ -+ if (IS_GEN(dev_priv, 9) && mmio->in_context) -+ continue; -+ -+ // save -+ if (pre) { -+ vgpu_vreg_t(pre, mmio->reg) = I915_READ_FW(mmio->reg); -+ if (mmio->mask) -+ vgpu_vreg_t(pre, mmio->reg) &= -+ ~(mmio->mask << 16); -+ old_v = vgpu_vreg_t(pre, mmio->reg); -+ } else -+ old_v = mmio->value = I915_READ_FW(mmio->reg); -+ -+ // restore -+ if (next) { -+ s = &next->submission; -+ /* -+ * No need to restore the mmio which is in context state -+ * image if it's not inhibit context, it will restore -+ * itself. -+ */ -+ if (mmio->in_context && -+ !is_inhibit_context(intel_context_lookup(s->shadow_ctx, -+ dev_priv->engine[ring_id]))) -+ continue; -+ -+ if (mmio->mask) -+ new_v = vgpu_vreg_t(next, mmio->reg) | -+ (mmio->mask << 16); -+ else -+ new_v = vgpu_vreg_t(next, mmio->reg); -+ } else { -+ if (mmio->in_context) -+ continue; -+ if (mmio->mask) -+ new_v = mmio->value | (mmio->mask << 16); -+ else -+ new_v = mmio->value; -+ } -+ -+ I915_WRITE_FW(mmio->reg, new_v); -+ -+ trace_render_mmio(pre ? pre->id : 0, -+ next ? next->id : 0, -+ "switch", -+ i915_mmio_reg_offset(mmio->reg), -+ old_v, new_v); -+ } -+ -+ if (next) -+ handle_tlb_pending_event(next, ring_id); -+} -+ -+/** -+ * intel_gvt_switch_render_mmio - switch mmio context of specific engine -+ * @pre: the last vGPU that own the engine -+ * @next: the vGPU to switch to -+ * @ring_id: specify the engine -+ * -+ * If pre is null indicates that host own the engine. If next is null -+ * indicates that we are switching to host workload. -+ */ -+void intel_gvt_switch_mmio(struct intel_vgpu *pre, -+ struct intel_vgpu *next, int ring_id) -+{ -+ struct drm_i915_private *dev_priv; -+ -+ if (WARN_ON(!pre && !next)) -+ return; -+ -+ gvt_dbg_render("switch ring %d from %s to %s\n", ring_id, -+ pre ? "vGPU" : "host", next ? "vGPU" : "HOST"); -+ -+ dev_priv = pre ? pre->gvt->dev_priv : next->gvt->dev_priv; -+ -+ /** -+ * We are using raw mmio access wrapper to improve the -+ * performace for batch mmio read/write, so we need -+ * handle forcewake mannually. -+ */ -+ intel_uncore_forcewake_get(&dev_priv->uncore, FORCEWAKE_ALL); -+ switch_mmio(pre, next, ring_id); -+ intel_uncore_forcewake_put(&dev_priv->uncore, FORCEWAKE_ALL); -+} -+ -+/** -+ * intel_gvt_init_engine_mmio_context - Initiate the engine mmio list -+ * @gvt: GVT device -+ * -+ */ -+void intel_gvt_init_engine_mmio_context(struct intel_gvt *gvt) -+{ -+ struct engine_mmio *mmio; -+ -+ if (INTEL_GEN(gvt->dev_priv) >= 9) -+ gvt->engine_mmio_list.mmio = gen9_engine_mmio_list; -+ else -+ gvt->engine_mmio_list.mmio = gen8_engine_mmio_list; -+ -+ for (mmio = gvt->engine_mmio_list.mmio; -+ i915_mmio_reg_valid(mmio->reg); mmio++) { -+ if (mmio->in_context) { -+ gvt->engine_mmio_list.ctx_mmio_count[mmio->ring_id]++; -+ intel_gvt_mmio_set_in_ctx(gvt, mmio->reg.reg); -+ } -+ } -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/mmio_context.h b/drivers/gpu/drm/i915_legacy/gvt/mmio_context.h -new file mode 100644 -index 000000000000..f7eaa442403f ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/mmio_context.h -@@ -0,0 +1,60 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eddie Dong -+ * Kevin Tian -+ * -+ * Contributors: -+ * Zhi Wang -+ * Changbin Du -+ * Zhenyu Wang -+ * Tina Zhang -+ * Bing Niu -+ * -+ */ -+ -+#ifndef __GVT_RENDER_H__ -+#define __GVT_RENDER_H__ -+ -+struct engine_mmio { -+ int ring_id; -+ i915_reg_t reg; -+ u32 mask; -+ bool in_context; -+ u32 value; -+}; -+ -+void intel_gvt_switch_mmio(struct intel_vgpu *pre, -+ struct intel_vgpu *next, int ring_id); -+ -+void intel_gvt_init_engine_mmio_context(struct intel_gvt *gvt); -+ -+bool is_inhibit_context(struct intel_context *ce); -+ -+int intel_vgpu_restore_inhibit_context(struct intel_vgpu *vgpu, -+ struct i915_request *req); -+#define IS_RESTORE_INHIBIT(a) \ -+ (_MASKED_BIT_ENABLE(CTX_CTRL_ENGINE_CTX_RESTORE_INHIBIT) == \ -+ ((a) & _MASKED_BIT_ENABLE(CTX_CTRL_ENGINE_CTX_RESTORE_INHIBIT))) -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/gvt/mpt.h b/drivers/gpu/drm/i915_legacy/gvt/mpt.h -new file mode 100644 -index 000000000000..0f9440128123 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/mpt.h -@@ -0,0 +1,383 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eddie Dong -+ * Dexuan Cui -+ * Jike Song -+ * -+ * Contributors: -+ * Zhi Wang -+ * -+ */ -+ -+#ifndef _GVT_MPT_H_ -+#define _GVT_MPT_H_ -+ -+/** -+ * DOC: Hypervisor Service APIs for GVT-g Core Logic -+ * -+ * This is the glue layer between specific hypervisor MPT modules and GVT-g core -+ * logic. Each kind of hypervisor MPT module provides a collection of function -+ * callbacks and will be attached to GVT host when the driver is loading. -+ * GVT-g core logic will call these APIs to request specific services from -+ * hypervisor. -+ */ -+ -+/** -+ * intel_gvt_hypervisor_host_init - init GVT-g host side -+ * -+ * Returns: -+ * Zero on success, negative error code if failed -+ */ -+static inline int intel_gvt_hypervisor_host_init(struct device *dev, -+ void *gvt, const void *ops) -+{ -+ if (!intel_gvt_host.mpt->host_init) -+ return -ENODEV; -+ -+ return intel_gvt_host.mpt->host_init(dev, gvt, ops); -+} -+ -+/** -+ * intel_gvt_hypervisor_host_exit - exit GVT-g host side -+ */ -+static inline void intel_gvt_hypervisor_host_exit(struct device *dev) -+{ -+ /* optional to provide */ -+ if (!intel_gvt_host.mpt->host_exit) -+ return; -+ -+ intel_gvt_host.mpt->host_exit(dev); -+} -+ -+/** -+ * intel_gvt_hypervisor_attach_vgpu - call hypervisor to initialize vGPU -+ * related stuffs inside hypervisor. -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+static inline int intel_gvt_hypervisor_attach_vgpu(struct intel_vgpu *vgpu) -+{ -+ /* optional to provide */ -+ if (!intel_gvt_host.mpt->attach_vgpu) -+ return 0; -+ -+ return intel_gvt_host.mpt->attach_vgpu(vgpu, &vgpu->handle); -+} -+ -+/** -+ * intel_gvt_hypervisor_detach_vgpu - call hypervisor to release vGPU -+ * related stuffs inside hypervisor. -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+static inline void intel_gvt_hypervisor_detach_vgpu(struct intel_vgpu *vgpu) -+{ -+ /* optional to provide */ -+ if (!intel_gvt_host.mpt->detach_vgpu) -+ return; -+ -+ intel_gvt_host.mpt->detach_vgpu(vgpu); -+} -+ -+#define MSI_CAP_CONTROL(offset) (offset + 2) -+#define MSI_CAP_ADDRESS(offset) (offset + 4) -+#define MSI_CAP_DATA(offset) (offset + 8) -+#define MSI_CAP_EN 0x1 -+ -+/** -+ * intel_gvt_hypervisor_inject_msi - inject a MSI interrupt into vGPU -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+static inline int intel_gvt_hypervisor_inject_msi(struct intel_vgpu *vgpu) -+{ -+ unsigned long offset = vgpu->gvt->device_info.msi_cap_offset; -+ u16 control, data; -+ u32 addr; -+ int ret; -+ -+ control = *(u16 *)(vgpu_cfg_space(vgpu) + MSI_CAP_CONTROL(offset)); -+ addr = *(u32 *)(vgpu_cfg_space(vgpu) + MSI_CAP_ADDRESS(offset)); -+ data = *(u16 *)(vgpu_cfg_space(vgpu) + MSI_CAP_DATA(offset)); -+ -+ /* Do not generate MSI if MSIEN is disable */ -+ if (!(control & MSI_CAP_EN)) -+ return 0; -+ -+ if (WARN(control & GENMASK(15, 1), "only support one MSI format\n")) -+ return -EINVAL; -+ -+ trace_inject_msi(vgpu->id, addr, data); -+ -+ ret = intel_gvt_host.mpt->inject_msi(vgpu->handle, addr, data); -+ if (ret) -+ return ret; -+ return 0; -+} -+ -+/** -+ * intel_gvt_hypervisor_set_wp_page - translate a host VA into MFN -+ * @p: host kernel virtual address -+ * -+ * Returns: -+ * MFN on success, INTEL_GVT_INVALID_ADDR if failed. -+ */ -+static inline unsigned long intel_gvt_hypervisor_virt_to_mfn(void *p) -+{ -+ return intel_gvt_host.mpt->from_virt_to_mfn(p); -+} -+ -+/** -+ * intel_gvt_hypervisor_enable_page_track - track a guest page -+ * @vgpu: a vGPU -+ * @gfn: the gfn of guest -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+static inline int intel_gvt_hypervisor_enable_page_track( -+ struct intel_vgpu *vgpu, unsigned long gfn) -+{ -+ return intel_gvt_host.mpt->enable_page_track(vgpu->handle, gfn); -+} -+ -+/** -+ * intel_gvt_hypervisor_disable_page_track - untrack a guest page -+ * @vgpu: a vGPU -+ * @gfn: the gfn of guest -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+static inline int intel_gvt_hypervisor_disable_page_track( -+ struct intel_vgpu *vgpu, unsigned long gfn) -+{ -+ return intel_gvt_host.mpt->disable_page_track(vgpu->handle, gfn); -+} -+ -+/** -+ * intel_gvt_hypervisor_read_gpa - copy data from GPA to host data buffer -+ * @vgpu: a vGPU -+ * @gpa: guest physical address -+ * @buf: host data buffer -+ * @len: data length -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+static inline int intel_gvt_hypervisor_read_gpa(struct intel_vgpu *vgpu, -+ unsigned long gpa, void *buf, unsigned long len) -+{ -+ return intel_gvt_host.mpt->read_gpa(vgpu->handle, gpa, buf, len); -+} -+ -+/** -+ * intel_gvt_hypervisor_write_gpa - copy data from host data buffer to GPA -+ * @vgpu: a vGPU -+ * @gpa: guest physical address -+ * @buf: host data buffer -+ * @len: data length -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+static inline int intel_gvt_hypervisor_write_gpa(struct intel_vgpu *vgpu, -+ unsigned long gpa, void *buf, unsigned long len) -+{ -+ return intel_gvt_host.mpt->write_gpa(vgpu->handle, gpa, buf, len); -+} -+ -+/** -+ * intel_gvt_hypervisor_gfn_to_mfn - translate a GFN to MFN -+ * @vgpu: a vGPU -+ * @gpfn: guest pfn -+ * -+ * Returns: -+ * MFN on success, INTEL_GVT_INVALID_ADDR if failed. -+ */ -+static inline unsigned long intel_gvt_hypervisor_gfn_to_mfn( -+ struct intel_vgpu *vgpu, unsigned long gfn) -+{ -+ return intel_gvt_host.mpt->gfn_to_mfn(vgpu->handle, gfn); -+} -+ -+/** -+ * intel_gvt_hypervisor_dma_map_guest_page - setup dma map for guest page -+ * @vgpu: a vGPU -+ * @gfn: guest pfn -+ * @size: page size -+ * @dma_addr: retrieve allocated dma addr -+ * -+ * Returns: -+ * 0 on success, negative error code if failed. -+ */ -+static inline int intel_gvt_hypervisor_dma_map_guest_page( -+ struct intel_vgpu *vgpu, unsigned long gfn, unsigned long size, -+ dma_addr_t *dma_addr) -+{ -+ return intel_gvt_host.mpt->dma_map_guest_page(vgpu->handle, gfn, size, -+ dma_addr); -+} -+ -+/** -+ * intel_gvt_hypervisor_dma_unmap_guest_page - cancel dma map for guest page -+ * @vgpu: a vGPU -+ * @dma_addr: the mapped dma addr -+ */ -+static inline void intel_gvt_hypervisor_dma_unmap_guest_page( -+ struct intel_vgpu *vgpu, dma_addr_t dma_addr) -+{ -+ intel_gvt_host.mpt->dma_unmap_guest_page(vgpu->handle, dma_addr); -+} -+ -+/** -+ * intel_gvt_hypervisor_map_gfn_to_mfn - map a GFN region to MFN -+ * @vgpu: a vGPU -+ * @gfn: guest PFN -+ * @mfn: host PFN -+ * @nr: amount of PFNs -+ * @map: map or unmap -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+static inline int intel_gvt_hypervisor_map_gfn_to_mfn( -+ struct intel_vgpu *vgpu, unsigned long gfn, -+ unsigned long mfn, unsigned int nr, -+ bool map) -+{ -+ /* a MPT implementation could have MMIO mapped elsewhere */ -+ if (!intel_gvt_host.mpt->map_gfn_to_mfn) -+ return 0; -+ -+ return intel_gvt_host.mpt->map_gfn_to_mfn(vgpu->handle, gfn, mfn, nr, -+ map); -+} -+ -+/** -+ * intel_gvt_hypervisor_set_trap_area - Trap a guest PA region -+ * @vgpu: a vGPU -+ * @start: the beginning of the guest physical address region -+ * @end: the end of the guest physical address region -+ * @map: map or unmap -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+static inline int intel_gvt_hypervisor_set_trap_area( -+ struct intel_vgpu *vgpu, u64 start, u64 end, bool map) -+{ -+ /* a MPT implementation could have MMIO trapped elsewhere */ -+ if (!intel_gvt_host.mpt->set_trap_area) -+ return 0; -+ -+ return intel_gvt_host.mpt->set_trap_area(vgpu->handle, start, end, map); -+} -+ -+/** -+ * intel_gvt_hypervisor_set_opregion - Set opregion for guest -+ * @vgpu: a vGPU -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+static inline int intel_gvt_hypervisor_set_opregion(struct intel_vgpu *vgpu) -+{ -+ if (!intel_gvt_host.mpt->set_opregion) -+ return 0; -+ -+ return intel_gvt_host.mpt->set_opregion(vgpu); -+} -+ -+/** -+ * intel_gvt_hypervisor_set_edid - Set EDID region for guest -+ * @vgpu: a vGPU -+ * @port_num: display port number -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+static inline int intel_gvt_hypervisor_set_edid(struct intel_vgpu *vgpu, -+ int port_num) -+{ -+ if (!intel_gvt_host.mpt->set_edid) -+ return 0; -+ -+ return intel_gvt_host.mpt->set_edid(vgpu, port_num); -+} -+ -+/** -+ * intel_gvt_hypervisor_get_vfio_device - increase vfio device ref count -+ * @vgpu: a vGPU -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+static inline int intel_gvt_hypervisor_get_vfio_device(struct intel_vgpu *vgpu) -+{ -+ if (!intel_gvt_host.mpt->get_vfio_device) -+ return 0; -+ -+ return intel_gvt_host.mpt->get_vfio_device(vgpu); -+} -+ -+/** -+ * intel_gvt_hypervisor_put_vfio_device - decrease vfio device ref count -+ * @vgpu: a vGPU -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+static inline void intel_gvt_hypervisor_put_vfio_device(struct intel_vgpu *vgpu) -+{ -+ if (!intel_gvt_host.mpt->put_vfio_device) -+ return; -+ -+ intel_gvt_host.mpt->put_vfio_device(vgpu); -+} -+ -+/** -+ * intel_gvt_hypervisor_is_valid_gfn - check if a visible gfn -+ * @vgpu: a vGPU -+ * @gfn: guest PFN -+ * -+ * Returns: -+ * true on valid gfn, false on not. -+ */ -+static inline bool intel_gvt_hypervisor_is_valid_gfn( -+ struct intel_vgpu *vgpu, unsigned long gfn) -+{ -+ if (!intel_gvt_host.mpt->is_valid_gfn) -+ return true; -+ -+ return intel_gvt_host.mpt->is_valid_gfn(vgpu->handle, gfn); -+} -+ -+int intel_gvt_register_hypervisor(struct intel_gvt_mpt *); -+void intel_gvt_unregister_hypervisor(void); -+ -+#endif /* _GVT_MPT_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/gvt/opregion.c b/drivers/gpu/drm/i915_legacy/gvt/opregion.c -new file mode 100644 -index 000000000000..276db53f1bf1 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/opregion.c -@@ -0,0 +1,570 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#include -+#include "i915_drv.h" -+#include "gvt.h" -+ -+/* -+ * Note: Only for GVT-g virtual VBT generation, other usage must -+ * not do like this. -+ */ -+#define _INTEL_BIOS_PRIVATE -+#include "intel_vbt_defs.h" -+ -+#define OPREGION_SIGNATURE "IntelGraphicsMem" -+#define MBOX_VBT (1<<3) -+ -+/* device handle */ -+#define DEVICE_TYPE_CRT 0x01 -+#define DEVICE_TYPE_EFP1 0x04 -+#define DEVICE_TYPE_EFP2 0x40 -+#define DEVICE_TYPE_EFP3 0x20 -+#define DEVICE_TYPE_EFP4 0x10 -+ -+struct opregion_header { -+ u8 signature[16]; -+ u32 size; -+ u32 opregion_ver; -+ u8 bios_ver[32]; -+ u8 vbios_ver[16]; -+ u8 driver_ver[16]; -+ u32 mboxes; -+ u32 driver_model; -+ u32 pcon; -+ u8 dver[32]; -+ u8 rsvd[124]; -+} __packed; -+ -+struct bdb_data_header { -+ u8 id; -+ u16 size; /* data size */ -+} __packed; -+ -+/* For supporting windows guest with opregion, here hardcode the emulated -+ * bdb header version as '186', and the corresponding child_device_config -+ * length should be '33' but not '38'. -+ */ -+struct efp_child_device_config { -+ u16 handle; -+ u16 device_type; -+ u16 device_class; -+ u8 i2c_speed; -+ u8 dp_onboard_redriver; /* 158 */ -+ u8 dp_ondock_redriver; /* 158 */ -+ u8 hdmi_level_shifter_value:4; /* 169 */ -+ u8 hdmi_max_data_rate:4; /* 204 */ -+ u16 dtd_buf_ptr; /* 161 */ -+ u8 edidless_efp:1; /* 161 */ -+ u8 compression_enable:1; /* 198 */ -+ u8 compression_method:1; /* 198 */ -+ u8 ganged_edp:1; /* 202 */ -+ u8 skip0:4; -+ u8 compression_structure_index:4; /* 198 */ -+ u8 skip1:4; -+ u8 slave_port; /* 202 */ -+ u8 skip2; -+ u8 dvo_port; -+ u8 i2c_pin; /* for add-in card */ -+ u8 slave_addr; /* for add-in card */ -+ u8 ddc_pin; -+ u16 edid_ptr; -+ u8 dvo_config; -+ u8 efp_docked_port:1; /* 158 */ -+ u8 lane_reversal:1; /* 184 */ -+ u8 onboard_lspcon:1; /* 192 */ -+ u8 iboost_enable:1; /* 196 */ -+ u8 hpd_invert:1; /* BXT 196 */ -+ u8 slip3:3; -+ u8 hdmi_compat:1; -+ u8 dp_compat:1; -+ u8 tmds_compat:1; -+ u8 skip4:5; -+ u8 aux_channel; -+ u8 dongle_detect; -+ u8 pipe_cap:2; -+ u8 sdvo_stall:1; /* 158 */ -+ u8 hpd_status:2; -+ u8 integrated_encoder:1; -+ u8 skip5:2; -+ u8 dvo_wiring; -+ u8 mipi_bridge_type; /* 171 */ -+ u16 device_class_ext; -+ u8 dvo_function; -+} __packed; -+ -+struct vbt { -+ /* header->bdb_offset point to bdb_header offset */ -+ struct vbt_header header; -+ struct bdb_header bdb_header; -+ -+ struct bdb_data_header general_features_header; -+ struct bdb_general_features general_features; -+ -+ struct bdb_data_header general_definitions_header; -+ struct bdb_general_definitions general_definitions; -+ -+ struct efp_child_device_config child0; -+ struct efp_child_device_config child1; -+ struct efp_child_device_config child2; -+ struct efp_child_device_config child3; -+ -+ struct bdb_data_header driver_features_header; -+ struct bdb_driver_features driver_features; -+}; -+ -+static void virt_vbt_generation(struct vbt *v) -+{ -+ int num_child; -+ -+ memset(v, 0, sizeof(struct vbt)); -+ -+ v->header.signature[0] = '$'; -+ v->header.signature[1] = 'V'; -+ v->header.signature[2] = 'B'; -+ v->header.signature[3] = 'T'; -+ -+ /* there's features depending on version! */ -+ v->header.version = 155; -+ v->header.header_size = sizeof(v->header); -+ v->header.vbt_size = sizeof(struct vbt) - sizeof(v->header); -+ v->header.bdb_offset = offsetof(struct vbt, bdb_header); -+ -+ strcpy(&v->bdb_header.signature[0], "BIOS_DATA_BLOCK"); -+ v->bdb_header.version = 186; /* child_dev_size = 33 */ -+ v->bdb_header.header_size = sizeof(v->bdb_header); -+ -+ v->bdb_header.bdb_size = sizeof(struct vbt) - sizeof(struct vbt_header) -+ - sizeof(struct bdb_header); -+ -+ /* general features */ -+ v->general_features_header.id = BDB_GENERAL_FEATURES; -+ v->general_features_header.size = sizeof(struct bdb_general_features); -+ v->general_features.int_crt_support = 0; -+ v->general_features.int_tv_support = 0; -+ -+ /* child device */ -+ num_child = 4; /* each port has one child */ -+ v->general_definitions.child_dev_size = -+ sizeof(struct efp_child_device_config); -+ v->general_definitions_header.id = BDB_GENERAL_DEFINITIONS; -+ /* size will include child devices */ -+ v->general_definitions_header.size = -+ sizeof(struct bdb_general_definitions) + -+ num_child * v->general_definitions.child_dev_size; -+ -+ /* portA */ -+ v->child0.handle = DEVICE_TYPE_EFP1; -+ v->child0.device_type = DEVICE_TYPE_DP; -+ v->child0.dvo_port = DVO_PORT_DPA; -+ v->child0.aux_channel = DP_AUX_A; -+ v->child0.dp_compat = true; -+ v->child0.integrated_encoder = true; -+ -+ /* portB */ -+ v->child1.handle = DEVICE_TYPE_EFP2; -+ v->child1.device_type = DEVICE_TYPE_DP; -+ v->child1.dvo_port = DVO_PORT_DPB; -+ v->child1.aux_channel = DP_AUX_B; -+ v->child1.dp_compat = true; -+ v->child1.integrated_encoder = true; -+ -+ /* portC */ -+ v->child2.handle = DEVICE_TYPE_EFP3; -+ v->child2.device_type = DEVICE_TYPE_DP; -+ v->child2.dvo_port = DVO_PORT_DPC; -+ v->child2.aux_channel = DP_AUX_C; -+ v->child2.dp_compat = true; -+ v->child2.integrated_encoder = true; -+ -+ /* portD */ -+ v->child3.handle = DEVICE_TYPE_EFP4; -+ v->child3.device_type = DEVICE_TYPE_DP; -+ v->child3.dvo_port = DVO_PORT_DPD; -+ v->child3.aux_channel = DP_AUX_D; -+ v->child3.dp_compat = true; -+ v->child3.integrated_encoder = true; -+ -+ /* driver features */ -+ v->driver_features_header.id = BDB_DRIVER_FEATURES; -+ v->driver_features_header.size = sizeof(struct bdb_driver_features); -+ v->driver_features.lvds_config = BDB_DRIVER_FEATURE_NO_LVDS; -+} -+ -+/** -+ * intel_vgpu_init_opregion - initialize the stuff used to emulate opregion -+ * @vgpu: a vGPU -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_vgpu_init_opregion(struct intel_vgpu *vgpu) -+{ -+ u8 *buf; -+ struct opregion_header *header; -+ struct vbt v; -+ const char opregion_signature[16] = OPREGION_SIGNATURE; -+ -+ gvt_dbg_core("init vgpu%d opregion\n", vgpu->id); -+ vgpu_opregion(vgpu)->va = (void *)__get_free_pages(GFP_KERNEL | -+ __GFP_ZERO, -+ get_order(INTEL_GVT_OPREGION_SIZE)); -+ if (!vgpu_opregion(vgpu)->va) { -+ gvt_err("fail to get memory for vgpu virt opregion\n"); -+ return -ENOMEM; -+ } -+ -+ /* emulated opregion with VBT mailbox only */ -+ buf = (u8 *)vgpu_opregion(vgpu)->va; -+ header = (struct opregion_header *)buf; -+ memcpy(header->signature, opregion_signature, -+ sizeof(opregion_signature)); -+ header->size = 0x8; -+ header->opregion_ver = 0x02000000; -+ header->mboxes = MBOX_VBT; -+ -+ /* for unknown reason, the value in LID field is incorrect -+ * which block the windows guest, so workaround it by force -+ * setting it to "OPEN" -+ */ -+ buf[INTEL_GVT_OPREGION_CLID] = 0x3; -+ -+ /* emulated vbt from virt vbt generation */ -+ virt_vbt_generation(&v); -+ memcpy(buf + INTEL_GVT_OPREGION_VBT_OFFSET, &v, sizeof(struct vbt)); -+ -+ return 0; -+} -+ -+static int map_vgpu_opregion(struct intel_vgpu *vgpu, bool map) -+{ -+ u64 mfn; -+ int i, ret; -+ -+ for (i = 0; i < INTEL_GVT_OPREGION_PAGES; i++) { -+ mfn = intel_gvt_hypervisor_virt_to_mfn(vgpu_opregion(vgpu)->va -+ + i * PAGE_SIZE); -+ if (mfn == INTEL_GVT_INVALID_ADDR) { -+ gvt_vgpu_err("fail to get MFN from VA\n"); -+ return -EINVAL; -+ } -+ ret = intel_gvt_hypervisor_map_gfn_to_mfn(vgpu, -+ vgpu_opregion(vgpu)->gfn[i], -+ mfn, 1, map); -+ if (ret) { -+ gvt_vgpu_err("fail to map GFN to MFN, errno: %d\n", -+ ret); -+ return ret; -+ } -+ } -+ -+ vgpu_opregion(vgpu)->mapped = map; -+ -+ return 0; -+} -+ -+/** -+ * intel_vgpu_opregion_base_write_handler - Opregion base register write handler -+ * -+ * @vgpu: a vGPU -+ * @gpa: guest physical address of opregion -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ */ -+int intel_vgpu_opregion_base_write_handler(struct intel_vgpu *vgpu, u32 gpa) -+{ -+ -+ int i, ret = 0; -+ -+ gvt_dbg_core("emulate opregion from kernel\n"); -+ -+ switch (intel_gvt_host.hypervisor_type) { -+ case INTEL_GVT_HYPERVISOR_KVM: -+ for (i = 0; i < INTEL_GVT_OPREGION_PAGES; i++) -+ vgpu_opregion(vgpu)->gfn[i] = (gpa >> PAGE_SHIFT) + i; -+ break; -+ case INTEL_GVT_HYPERVISOR_XEN: -+ /** -+ * Wins guest on Xengt will write this register twice: xen -+ * hvmloader and windows graphic driver. -+ */ -+ if (vgpu_opregion(vgpu)->mapped) -+ map_vgpu_opregion(vgpu, false); -+ -+ for (i = 0; i < INTEL_GVT_OPREGION_PAGES; i++) -+ vgpu_opregion(vgpu)->gfn[i] = (gpa >> PAGE_SHIFT) + i; -+ -+ ret = map_vgpu_opregion(vgpu, true); -+ break; -+ default: -+ ret = -EINVAL; -+ gvt_vgpu_err("not supported hypervisor\n"); -+ } -+ -+ return ret; -+} -+ -+/** -+ * intel_vgpu_clean_opregion - clean the stuff used to emulate opregion -+ * @vgpu: a vGPU -+ * -+ */ -+void intel_vgpu_clean_opregion(struct intel_vgpu *vgpu) -+{ -+ gvt_dbg_core("vgpu%d: clean vgpu opregion\n", vgpu->id); -+ -+ if (!vgpu_opregion(vgpu)->va) -+ return; -+ -+ if (intel_gvt_host.hypervisor_type == INTEL_GVT_HYPERVISOR_XEN) { -+ if (vgpu_opregion(vgpu)->mapped) -+ map_vgpu_opregion(vgpu, false); -+ } else if (intel_gvt_host.hypervisor_type == INTEL_GVT_HYPERVISOR_KVM) { -+ /* Guest opregion is released by VFIO */ -+ } -+ free_pages((unsigned long)vgpu_opregion(vgpu)->va, -+ get_order(INTEL_GVT_OPREGION_SIZE)); -+ -+ vgpu_opregion(vgpu)->va = NULL; -+ -+} -+ -+ -+#define GVT_OPREGION_FUNC(scic) \ -+ ({ \ -+ u32 __ret; \ -+ __ret = (scic & OPREGION_SCIC_FUNC_MASK) >> \ -+ OPREGION_SCIC_FUNC_SHIFT; \ -+ __ret; \ -+ }) -+ -+#define GVT_OPREGION_SUBFUNC(scic) \ -+ ({ \ -+ u32 __ret; \ -+ __ret = (scic & OPREGION_SCIC_SUBFUNC_MASK) >> \ -+ OPREGION_SCIC_SUBFUNC_SHIFT; \ -+ __ret; \ -+ }) -+ -+static const char *opregion_func_name(u32 func) -+{ -+ const char *name = NULL; -+ -+ switch (func) { -+ case 0 ... 3: -+ case 5: -+ case 7 ... 15: -+ name = "Reserved"; -+ break; -+ -+ case 4: -+ name = "Get BIOS Data"; -+ break; -+ -+ case 6: -+ name = "System BIOS Callbacks"; -+ break; -+ -+ default: -+ name = "Unknown"; -+ break; -+ } -+ return name; -+} -+ -+static const char *opregion_subfunc_name(u32 subfunc) -+{ -+ const char *name = NULL; -+ -+ switch (subfunc) { -+ case 0: -+ name = "Supported Calls"; -+ break; -+ -+ case 1: -+ name = "Requested Callbacks"; -+ break; -+ -+ case 2 ... 3: -+ case 8 ... 9: -+ name = "Reserved"; -+ break; -+ -+ case 5: -+ name = "Boot Display"; -+ break; -+ -+ case 6: -+ name = "TV-Standard/Video-Connector"; -+ break; -+ -+ case 7: -+ name = "Internal Graphics"; -+ break; -+ -+ case 10: -+ name = "Spread Spectrum Clocks"; -+ break; -+ -+ case 11: -+ name = "Get AKSV"; -+ break; -+ -+ default: -+ name = "Unknown"; -+ break; -+ } -+ return name; -+}; -+ -+static bool querying_capabilities(u32 scic) -+{ -+ u32 func, subfunc; -+ -+ func = GVT_OPREGION_FUNC(scic); -+ subfunc = GVT_OPREGION_SUBFUNC(scic); -+ -+ if ((func == INTEL_GVT_OPREGION_SCIC_F_GETBIOSDATA && -+ subfunc == INTEL_GVT_OPREGION_SCIC_SF_SUPPRTEDCALLS) -+ || (func == INTEL_GVT_OPREGION_SCIC_F_GETBIOSDATA && -+ subfunc == INTEL_GVT_OPREGION_SCIC_SF_REQEUSTEDCALLBACKS) -+ || (func == INTEL_GVT_OPREGION_SCIC_F_GETBIOSCALLBACKS && -+ subfunc == INTEL_GVT_OPREGION_SCIC_SF_SUPPRTEDCALLS)) { -+ return true; -+ } -+ return false; -+} -+ -+/** -+ * intel_vgpu_emulate_opregion_request - emulating OpRegion request -+ * @vgpu: a vGPU -+ * @swsci: SWSCI request -+ * -+ * Returns: -+ * Zero on success, negative error code if failed -+ */ -+int intel_vgpu_emulate_opregion_request(struct intel_vgpu *vgpu, u32 swsci) -+{ -+ u32 scic, parm; -+ u32 func, subfunc; -+ u64 scic_pa = 0, parm_pa = 0; -+ int ret; -+ -+ switch (intel_gvt_host.hypervisor_type) { -+ case INTEL_GVT_HYPERVISOR_XEN: -+ scic = *((u32 *)vgpu_opregion(vgpu)->va + -+ INTEL_GVT_OPREGION_SCIC); -+ parm = *((u32 *)vgpu_opregion(vgpu)->va + -+ INTEL_GVT_OPREGION_PARM); -+ break; -+ case INTEL_GVT_HYPERVISOR_KVM: -+ scic_pa = (vgpu_opregion(vgpu)->gfn[0] << PAGE_SHIFT) + -+ INTEL_GVT_OPREGION_SCIC; -+ parm_pa = (vgpu_opregion(vgpu)->gfn[0] << PAGE_SHIFT) + -+ INTEL_GVT_OPREGION_PARM; -+ -+ ret = intel_gvt_hypervisor_read_gpa(vgpu, scic_pa, -+ &scic, sizeof(scic)); -+ if (ret) { -+ gvt_vgpu_err("guest opregion read error %d, gpa 0x%llx, len %lu\n", -+ ret, scic_pa, sizeof(scic)); -+ return ret; -+ } -+ -+ ret = intel_gvt_hypervisor_read_gpa(vgpu, parm_pa, -+ &parm, sizeof(parm)); -+ if (ret) { -+ gvt_vgpu_err("guest opregion read error %d, gpa 0x%llx, len %lu\n", -+ ret, scic_pa, sizeof(scic)); -+ return ret; -+ } -+ -+ break; -+ default: -+ gvt_vgpu_err("not supported hypervisor\n"); -+ return -EINVAL; -+ } -+ -+ if (!(swsci & SWSCI_SCI_SELECT)) { -+ gvt_vgpu_err("requesting SMI service\n"); -+ return 0; -+ } -+ /* ignore non 0->1 trasitions */ -+ if ((vgpu_cfg_space(vgpu)[INTEL_GVT_PCI_SWSCI] -+ & SWSCI_SCI_TRIGGER) || -+ !(swsci & SWSCI_SCI_TRIGGER)) { -+ return 0; -+ } -+ -+ func = GVT_OPREGION_FUNC(scic); -+ subfunc = GVT_OPREGION_SUBFUNC(scic); -+ if (!querying_capabilities(scic)) { -+ gvt_vgpu_err("requesting runtime service: func \"%s\"," -+ " subfunc \"%s\"\n", -+ opregion_func_name(func), -+ opregion_subfunc_name(subfunc)); -+ /* -+ * emulate exit status of function call, '0' means -+ * "failure, generic, unsupported or unknown cause" -+ */ -+ scic &= ~OPREGION_SCIC_EXIT_MASK; -+ goto out; -+ } -+ -+ scic = 0; -+ parm = 0; -+ -+out: -+ switch (intel_gvt_host.hypervisor_type) { -+ case INTEL_GVT_HYPERVISOR_XEN: -+ *((u32 *)vgpu_opregion(vgpu)->va + -+ INTEL_GVT_OPREGION_SCIC) = scic; -+ *((u32 *)vgpu_opregion(vgpu)->va + -+ INTEL_GVT_OPREGION_PARM) = parm; -+ break; -+ case INTEL_GVT_HYPERVISOR_KVM: -+ ret = intel_gvt_hypervisor_write_gpa(vgpu, scic_pa, -+ &scic, sizeof(scic)); -+ if (ret) { -+ gvt_vgpu_err("guest opregion write error %d, gpa 0x%llx, len %lu\n", -+ ret, scic_pa, sizeof(scic)); -+ return ret; -+ } -+ -+ ret = intel_gvt_hypervisor_write_gpa(vgpu, parm_pa, -+ &parm, sizeof(parm)); -+ if (ret) { -+ gvt_vgpu_err("guest opregion write error %d, gpa 0x%llx, len %lu\n", -+ ret, scic_pa, sizeof(scic)); -+ return ret; -+ } -+ -+ break; -+ default: -+ gvt_vgpu_err("not supported hypervisor\n"); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/page_track.c b/drivers/gpu/drm/i915_legacy/gvt/page_track.c -new file mode 100644 -index 000000000000..84856022528e ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/page_track.c -@@ -0,0 +1,185 @@ -+/* -+ * Copyright(c) 2011-2017 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+#include "i915_drv.h" -+#include "gvt.h" -+ -+/** -+ * intel_vgpu_find_page_track - find page track rcord of guest page -+ * @vgpu: a vGPU -+ * @gfn: the gfn of guest page -+ * -+ * Returns: -+ * A pointer to struct intel_vgpu_page_track if found, else NULL returned. -+ */ -+struct intel_vgpu_page_track *intel_vgpu_find_page_track( -+ struct intel_vgpu *vgpu, unsigned long gfn) -+{ -+ return radix_tree_lookup(&vgpu->page_track_tree, gfn); -+} -+ -+/** -+ * intel_vgpu_register_page_track - register a guest page to be tacked -+ * @vgpu: a vGPU -+ * @gfn: the gfn of guest page -+ * @handler: page track handler -+ * @priv: tracker private -+ * -+ * Returns: -+ * zero on success, negative error code if failed. -+ */ -+int intel_vgpu_register_page_track(struct intel_vgpu *vgpu, unsigned long gfn, -+ gvt_page_track_handler_t handler, void *priv) -+{ -+ struct intel_vgpu_page_track *track; -+ int ret; -+ -+ track = intel_vgpu_find_page_track(vgpu, gfn); -+ if (track) -+ return -EEXIST; -+ -+ track = kzalloc(sizeof(*track), GFP_KERNEL); -+ if (!track) -+ return -ENOMEM; -+ -+ track->handler = handler; -+ track->priv_data = priv; -+ -+ ret = radix_tree_insert(&vgpu->page_track_tree, gfn, track); -+ if (ret) { -+ kfree(track); -+ return ret; -+ } -+ -+ return 0; -+} -+ -+/** -+ * intel_vgpu_unregister_page_track - unregister the tracked guest page -+ * @vgpu: a vGPU -+ * @gfn: the gfn of guest page -+ * -+ */ -+void intel_vgpu_unregister_page_track(struct intel_vgpu *vgpu, -+ unsigned long gfn) -+{ -+ struct intel_vgpu_page_track *track; -+ -+ track = radix_tree_delete(&vgpu->page_track_tree, gfn); -+ if (track) { -+ if (track->tracked) -+ intel_gvt_hypervisor_disable_page_track(vgpu, gfn); -+ kfree(track); -+ } -+} -+ -+/** -+ * intel_vgpu_enable_page_track - set write-protection on guest page -+ * @vgpu: a vGPU -+ * @gfn: the gfn of guest page -+ * -+ * Returns: -+ * zero on success, negative error code if failed. -+ */ -+int intel_vgpu_enable_page_track(struct intel_vgpu *vgpu, unsigned long gfn) -+{ -+ struct intel_vgpu_page_track *track; -+ int ret; -+ -+ track = intel_vgpu_find_page_track(vgpu, gfn); -+ if (!track) -+ return -ENXIO; -+ -+ if (track->tracked) -+ return 0; -+ -+ ret = intel_gvt_hypervisor_enable_page_track(vgpu, gfn); -+ if (ret) -+ return ret; -+ track->tracked = true; -+ return 0; -+} -+ -+/** -+ * intel_vgpu_enable_page_track - cancel write-protection on guest page -+ * @vgpu: a vGPU -+ * @gfn: the gfn of guest page -+ * -+ * Returns: -+ * zero on success, negative error code if failed. -+ */ -+int intel_vgpu_disable_page_track(struct intel_vgpu *vgpu, unsigned long gfn) -+{ -+ struct intel_vgpu_page_track *track; -+ int ret; -+ -+ track = intel_vgpu_find_page_track(vgpu, gfn); -+ if (!track) -+ return -ENXIO; -+ -+ if (!track->tracked) -+ return 0; -+ -+ ret = intel_gvt_hypervisor_disable_page_track(vgpu, gfn); -+ if (ret) -+ return ret; -+ track->tracked = false; -+ return 0; -+} -+ -+/** -+ * intel_vgpu_page_track_handler - called when write to write-protected page -+ * @vgpu: a vGPU -+ * @gpa: the gpa of this write -+ * @data: the writed data -+ * @bytes: the length of this write -+ * -+ * Returns: -+ * zero on success, negative error code if failed. -+ */ -+int intel_vgpu_page_track_handler(struct intel_vgpu *vgpu, u64 gpa, -+ void *data, unsigned int bytes) -+{ -+ struct intel_vgpu_page_track *page_track; -+ int ret = 0; -+ -+ mutex_lock(&vgpu->vgpu_lock); -+ -+ page_track = intel_vgpu_find_page_track(vgpu, gpa >> PAGE_SHIFT); -+ if (!page_track) { -+ ret = -ENXIO; -+ goto out; -+ } -+ -+ if (unlikely(vgpu->failsafe)) { -+ /* Remove write protection to prevent furture traps. */ -+ intel_vgpu_disable_page_track(vgpu, gpa >> PAGE_SHIFT); -+ } else { -+ ret = page_track->handler(page_track, gpa, data, bytes); -+ if (ret) -+ gvt_err("guest page write error, gpa %llx\n", gpa); -+ } -+ -+out: -+ mutex_unlock(&vgpu->vgpu_lock); -+ return ret; -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/page_track.h b/drivers/gpu/drm/i915_legacy/gvt/page_track.h -new file mode 100644 -index 000000000000..fa607a71c3c0 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/page_track.h -@@ -0,0 +1,56 @@ -+/* -+ * Copyright(c) 2011-2017 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef _GVT_PAGE_TRACK_H_ -+#define _GVT_PAGE_TRACK_H_ -+ -+struct intel_vgpu_page_track; -+ -+typedef int (*gvt_page_track_handler_t)( -+ struct intel_vgpu_page_track *page_track, -+ u64 gpa, void *data, int bytes); -+ -+/* Track record for a write-protected guest page. */ -+struct intel_vgpu_page_track { -+ gvt_page_track_handler_t handler; -+ bool tracked; -+ void *priv_data; -+}; -+ -+struct intel_vgpu_page_track *intel_vgpu_find_page_track( -+ struct intel_vgpu *vgpu, unsigned long gfn); -+ -+int intel_vgpu_register_page_track(struct intel_vgpu *vgpu, -+ unsigned long gfn, gvt_page_track_handler_t handler, -+ void *priv); -+void intel_vgpu_unregister_page_track(struct intel_vgpu *vgpu, -+ unsigned long gfn); -+ -+int intel_vgpu_enable_page_track(struct intel_vgpu *vgpu, unsigned long gfn); -+int intel_vgpu_disable_page_track(struct intel_vgpu *vgpu, unsigned long gfn); -+ -+int intel_vgpu_page_track_handler(struct intel_vgpu *vgpu, u64 gpa, -+ void *data, unsigned int bytes); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/gvt/reg.h b/drivers/gpu/drm/i915_legacy/gvt/reg.h -new file mode 100644 -index 000000000000..5b66e14c5b7b ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/reg.h -@@ -0,0 +1,131 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#ifndef _GVT_REG_H -+#define _GVT_REG_H -+ -+#define INTEL_GVT_PCI_CLASS_VGA_OTHER 0x80 -+ -+#define INTEL_GVT_PCI_GMCH_CONTROL 0x50 -+#define BDW_GMCH_GMS_SHIFT 8 -+#define BDW_GMCH_GMS_MASK 0xff -+ -+#define INTEL_GVT_PCI_SWSCI 0xe8 -+#define SWSCI_SCI_SELECT (1 << 15) -+#define SWSCI_SCI_TRIGGER 1 -+ -+#define INTEL_GVT_PCI_OPREGION 0xfc -+ -+#define INTEL_GVT_OPREGION_CLID 0x1AC -+#define INTEL_GVT_OPREGION_SCIC 0x200 -+#define OPREGION_SCIC_FUNC_MASK 0x1E -+#define OPREGION_SCIC_FUNC_SHIFT 1 -+#define OPREGION_SCIC_SUBFUNC_MASK 0xFF00 -+#define OPREGION_SCIC_SUBFUNC_SHIFT 8 -+#define OPREGION_SCIC_EXIT_MASK 0xE0 -+#define INTEL_GVT_OPREGION_SCIC_F_GETBIOSDATA 4 -+#define INTEL_GVT_OPREGION_SCIC_F_GETBIOSCALLBACKS 6 -+#define INTEL_GVT_OPREGION_SCIC_SF_SUPPRTEDCALLS 0 -+#define INTEL_GVT_OPREGION_SCIC_SF_REQEUSTEDCALLBACKS 1 -+#define INTEL_GVT_OPREGION_PARM 0x204 -+ -+#define INTEL_GVT_OPREGION_PAGES 2 -+#define INTEL_GVT_OPREGION_SIZE (INTEL_GVT_OPREGION_PAGES * PAGE_SIZE) -+#define INTEL_GVT_OPREGION_VBT_OFFSET 0x400 -+#define INTEL_GVT_OPREGION_VBT_SIZE \ -+ (INTEL_GVT_OPREGION_SIZE - INTEL_GVT_OPREGION_VBT_OFFSET) -+ -+#define VGT_SPRSTRIDE(pipe) _PIPE(pipe, _SPRA_STRIDE, _PLANE_STRIDE_2_B) -+ -+#define _REG_701C0(pipe, plane) (0x701c0 + pipe * 0x1000 + (plane - 1) * 0x100) -+#define _REG_701C4(pipe, plane) (0x701c4 + pipe * 0x1000 + (plane - 1) * 0x100) -+ -+#define SKL_FLIP_EVENT(pipe, plane) (PRIMARY_A_FLIP_DONE + (plane) * 3 + (pipe)) -+ -+#define PLANE_CTL_ASYNC_FLIP (1 << 9) -+#define REG50080_FLIP_TYPE_MASK 0x3 -+#define REG50080_FLIP_TYPE_ASYNC 0x1 -+ -+#define REG_50080(_pipe, _plane) ({ \ -+ typeof(_pipe) (p) = (_pipe); \ -+ typeof(_plane) (q) = (_plane); \ -+ (((p) == PIPE_A) ? (((q) == PLANE_PRIMARY) ? (_MMIO(0x50080)) : \ -+ (_MMIO(0x50090))) : \ -+ (((p) == PIPE_B) ? (((q) == PLANE_PRIMARY) ? (_MMIO(0x50088)) : \ -+ (_MMIO(0x50098))) : \ -+ (((p) == PIPE_C) ? (((q) == PLANE_PRIMARY) ? (_MMIO(0x5008C)) : \ -+ (_MMIO(0x5009C))) : \ -+ (_MMIO(0x50080))))); }) -+ -+#define REG_50080_TO_PIPE(_reg) ({ \ -+ typeof(_reg) (reg) = (_reg); \ -+ (((reg) == 0x50080 || (reg) == 0x50090) ? (PIPE_A) : \ -+ (((reg) == 0x50088 || (reg) == 0x50098) ? (PIPE_B) : \ -+ (((reg) == 0x5008C || (reg) == 0x5009C) ? (PIPE_C) : \ -+ (INVALID_PIPE)))); }) -+ -+#define REG_50080_TO_PLANE(_reg) ({ \ -+ typeof(_reg) (reg) = (_reg); \ -+ (((reg) == 0x50080 || (reg) == 0x50088 || (reg) == 0x5008C) ? \ -+ (PLANE_PRIMARY) : \ -+ (((reg) == 0x50090 || (reg) == 0x50098 || (reg) == 0x5009C) ? \ -+ (PLANE_SPRITE0) : (I915_MAX_PLANES))); }) -+ -+#define GFX_MODE_BIT_SET_IN_MASK(val, bit) \ -+ ((((bit) & 0xffff0000) == 0) && !!((val) & (((bit) << 16)))) -+ -+#define FORCEWAKE_RENDER_GEN9_REG 0xa278 -+#define FORCEWAKE_ACK_RENDER_GEN9_REG 0x0D84 -+#define FORCEWAKE_BLITTER_GEN9_REG 0xa188 -+#define FORCEWAKE_ACK_BLITTER_GEN9_REG 0x130044 -+#define FORCEWAKE_MEDIA_GEN9_REG 0xa270 -+#define FORCEWAKE_ACK_MEDIA_GEN9_REG 0x0D88 -+#define FORCEWAKE_ACK_HSW_REG 0x130044 -+ -+#define RB_HEAD_WRAP_CNT_MAX ((1 << 11) - 1) -+#define RB_HEAD_WRAP_CNT_OFF 21 -+#define RB_HEAD_OFF_MASK ((1U << 21) - (1U << 2)) -+#define RB_TAIL_OFF_MASK ((1U << 21) - (1U << 3)) -+#define RB_TAIL_SIZE_MASK ((1U << 21) - (1U << 12)) -+#define _RING_CTL_BUF_SIZE(ctl) (((ctl) & RB_TAIL_SIZE_MASK) + \ -+ I915_GTT_PAGE_SIZE) -+ -+#define PCH_GPIO_BASE _MMIO(0xc5010) -+ -+#define PCH_GMBUS0 _MMIO(0xc5100) -+#define PCH_GMBUS1 _MMIO(0xc5104) -+#define PCH_GMBUS2 _MMIO(0xc5108) -+#define PCH_GMBUS3 _MMIO(0xc510c) -+#define PCH_GMBUS4 _MMIO(0xc5110) -+#define PCH_GMBUS5 _MMIO(0xc5120) -+ -+#define TRVATTL3PTRDW(i) _MMIO(0x4de0 + (i) * 4) -+#define TRNULLDETCT _MMIO(0x4de8) -+#define TRINVTILEDETCT _MMIO(0x4dec) -+#define TRVADR _MMIO(0x4df0) -+#define TRTTE _MMIO(0x4df4) -+#define RING_EXCC(base) _MMIO((base) + 0x28) -+#define RING_GFX_MODE(base) _MMIO((base) + 0x29c) -+#define VF_GUARDBAND _MMIO(0x83a4) -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/gvt/sched_policy.c b/drivers/gpu/drm/i915_legacy/gvt/sched_policy.c -new file mode 100644 -index 000000000000..1c763a27a412 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/sched_policy.c -@@ -0,0 +1,479 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Anhua Xu -+ * Kevin Tian -+ * -+ * Contributors: -+ * Min He -+ * Bing Niu -+ * Zhi Wang -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "gvt.h" -+ -+static bool vgpu_has_pending_workload(struct intel_vgpu *vgpu) -+{ -+ enum intel_engine_id i; -+ struct intel_engine_cs *engine; -+ -+ for_each_engine(engine, vgpu->gvt->dev_priv, i) { -+ if (!list_empty(workload_q_head(vgpu, i))) -+ return true; -+ } -+ -+ return false; -+} -+ -+/* We give 2 seconds higher prio for vGPU during start */ -+#define GVT_SCHED_VGPU_PRI_TIME 2 -+ -+struct vgpu_sched_data { -+ struct list_head lru_list; -+ struct intel_vgpu *vgpu; -+ bool active; -+ bool pri_sched; -+ ktime_t pri_time; -+ ktime_t sched_in_time; -+ ktime_t sched_time; -+ ktime_t left_ts; -+ ktime_t allocated_ts; -+ -+ struct vgpu_sched_ctl sched_ctl; -+}; -+ -+struct gvt_sched_data { -+ struct intel_gvt *gvt; -+ struct hrtimer timer; -+ unsigned long period; -+ struct list_head lru_runq_head; -+ ktime_t expire_time; -+}; -+ -+static void vgpu_update_timeslice(struct intel_vgpu *vgpu, ktime_t cur_time) -+{ -+ ktime_t delta_ts; -+ struct vgpu_sched_data *vgpu_data; -+ -+ if (!vgpu || vgpu == vgpu->gvt->idle_vgpu) -+ return; -+ -+ vgpu_data = vgpu->sched_data; -+ delta_ts = ktime_sub(cur_time, vgpu_data->sched_in_time); -+ vgpu_data->sched_time = ktime_add(vgpu_data->sched_time, delta_ts); -+ vgpu_data->left_ts = ktime_sub(vgpu_data->left_ts, delta_ts); -+ vgpu_data->sched_in_time = cur_time; -+} -+ -+#define GVT_TS_BALANCE_PERIOD_MS 100 -+#define GVT_TS_BALANCE_STAGE_NUM 10 -+ -+static void gvt_balance_timeslice(struct gvt_sched_data *sched_data) -+{ -+ struct vgpu_sched_data *vgpu_data; -+ struct list_head *pos; -+ static u64 stage_check; -+ int stage = stage_check++ % GVT_TS_BALANCE_STAGE_NUM; -+ -+ /* The timeslice accumulation reset at stage 0, which is -+ * allocated again without adding previous debt. -+ */ -+ if (stage == 0) { -+ int total_weight = 0; -+ ktime_t fair_timeslice; -+ -+ list_for_each(pos, &sched_data->lru_runq_head) { -+ vgpu_data = container_of(pos, struct vgpu_sched_data, lru_list); -+ total_weight += vgpu_data->sched_ctl.weight; -+ } -+ -+ list_for_each(pos, &sched_data->lru_runq_head) { -+ vgpu_data = container_of(pos, struct vgpu_sched_data, lru_list); -+ fair_timeslice = ktime_divns(ms_to_ktime(GVT_TS_BALANCE_PERIOD_MS), -+ total_weight) * vgpu_data->sched_ctl.weight; -+ -+ vgpu_data->allocated_ts = fair_timeslice; -+ vgpu_data->left_ts = vgpu_data->allocated_ts; -+ } -+ } else { -+ list_for_each(pos, &sched_data->lru_runq_head) { -+ vgpu_data = container_of(pos, struct vgpu_sched_data, lru_list); -+ -+ /* timeslice for next 100ms should add the left/debt -+ * slice of previous stages. -+ */ -+ vgpu_data->left_ts += vgpu_data->allocated_ts; -+ } -+ } -+} -+ -+static void try_to_schedule_next_vgpu(struct intel_gvt *gvt) -+{ -+ struct intel_gvt_workload_scheduler *scheduler = &gvt->scheduler; -+ enum intel_engine_id i; -+ struct intel_engine_cs *engine; -+ struct vgpu_sched_data *vgpu_data; -+ ktime_t cur_time; -+ -+ /* no need to schedule if next_vgpu is the same with current_vgpu, -+ * let scheduler chose next_vgpu again by setting it to NULL. -+ */ -+ if (scheduler->next_vgpu == scheduler->current_vgpu) { -+ scheduler->next_vgpu = NULL; -+ return; -+ } -+ -+ /* -+ * after the flag is set, workload dispatch thread will -+ * stop dispatching workload for current vgpu -+ */ -+ scheduler->need_reschedule = true; -+ -+ /* still have uncompleted workload? */ -+ for_each_engine(engine, gvt->dev_priv, i) { -+ if (scheduler->current_workload[i]) -+ return; -+ } -+ -+ cur_time = ktime_get(); -+ vgpu_update_timeslice(scheduler->current_vgpu, cur_time); -+ vgpu_data = scheduler->next_vgpu->sched_data; -+ vgpu_data->sched_in_time = cur_time; -+ -+ /* switch current vgpu */ -+ scheduler->current_vgpu = scheduler->next_vgpu; -+ scheduler->next_vgpu = NULL; -+ -+ scheduler->need_reschedule = false; -+ -+ /* wake up workload dispatch thread */ -+ for_each_engine(engine, gvt->dev_priv, i) -+ wake_up(&scheduler->waitq[i]); -+} -+ -+static struct intel_vgpu *find_busy_vgpu(struct gvt_sched_data *sched_data) -+{ -+ struct vgpu_sched_data *vgpu_data; -+ struct intel_vgpu *vgpu = NULL; -+ struct list_head *head = &sched_data->lru_runq_head; -+ struct list_head *pos; -+ -+ /* search a vgpu with pending workload */ -+ list_for_each(pos, head) { -+ -+ vgpu_data = container_of(pos, struct vgpu_sched_data, lru_list); -+ if (!vgpu_has_pending_workload(vgpu_data->vgpu)) -+ continue; -+ -+ if (vgpu_data->pri_sched) { -+ if (ktime_before(ktime_get(), vgpu_data->pri_time)) { -+ vgpu = vgpu_data->vgpu; -+ break; -+ } else -+ vgpu_data->pri_sched = false; -+ } -+ -+ /* Return the vGPU only if it has time slice left */ -+ if (vgpu_data->left_ts > 0) { -+ vgpu = vgpu_data->vgpu; -+ break; -+ } -+ } -+ -+ return vgpu; -+} -+ -+/* in nanosecond */ -+#define GVT_DEFAULT_TIME_SLICE 1000000 -+ -+static void tbs_sched_func(struct gvt_sched_data *sched_data) -+{ -+ struct intel_gvt *gvt = sched_data->gvt; -+ struct intel_gvt_workload_scheduler *scheduler = &gvt->scheduler; -+ struct vgpu_sched_data *vgpu_data; -+ struct intel_vgpu *vgpu = NULL; -+ -+ /* no active vgpu or has already had a target */ -+ if (list_empty(&sched_data->lru_runq_head) || scheduler->next_vgpu) -+ goto out; -+ -+ vgpu = find_busy_vgpu(sched_data); -+ if (vgpu) { -+ scheduler->next_vgpu = vgpu; -+ vgpu_data = vgpu->sched_data; -+ if (!vgpu_data->pri_sched) { -+ /* Move the last used vGPU to the tail of lru_list */ -+ list_del_init(&vgpu_data->lru_list); -+ list_add_tail(&vgpu_data->lru_list, -+ &sched_data->lru_runq_head); -+ } -+ } else { -+ scheduler->next_vgpu = gvt->idle_vgpu; -+ } -+out: -+ if (scheduler->next_vgpu) -+ try_to_schedule_next_vgpu(gvt); -+} -+ -+void intel_gvt_schedule(struct intel_gvt *gvt) -+{ -+ struct gvt_sched_data *sched_data = gvt->scheduler.sched_data; -+ ktime_t cur_time; -+ -+ mutex_lock(&gvt->sched_lock); -+ cur_time = ktime_get(); -+ -+ if (test_and_clear_bit(INTEL_GVT_REQUEST_SCHED, -+ (void *)&gvt->service_request)) { -+ if (cur_time >= sched_data->expire_time) { -+ gvt_balance_timeslice(sched_data); -+ sched_data->expire_time = ktime_add_ms( -+ cur_time, GVT_TS_BALANCE_PERIOD_MS); -+ } -+ } -+ clear_bit(INTEL_GVT_REQUEST_EVENT_SCHED, (void *)&gvt->service_request); -+ -+ vgpu_update_timeslice(gvt->scheduler.current_vgpu, cur_time); -+ tbs_sched_func(sched_data); -+ -+ mutex_unlock(&gvt->sched_lock); -+} -+ -+static enum hrtimer_restart tbs_timer_fn(struct hrtimer *timer_data) -+{ -+ struct gvt_sched_data *data; -+ -+ data = container_of(timer_data, struct gvt_sched_data, timer); -+ -+ intel_gvt_request_service(data->gvt, INTEL_GVT_REQUEST_SCHED); -+ -+ hrtimer_add_expires_ns(&data->timer, data->period); -+ -+ return HRTIMER_RESTART; -+} -+ -+static int tbs_sched_init(struct intel_gvt *gvt) -+{ -+ struct intel_gvt_workload_scheduler *scheduler = -+ &gvt->scheduler; -+ -+ struct gvt_sched_data *data; -+ -+ data = kzalloc(sizeof(*data), GFP_KERNEL); -+ if (!data) -+ return -ENOMEM; -+ -+ INIT_LIST_HEAD(&data->lru_runq_head); -+ hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); -+ data->timer.function = tbs_timer_fn; -+ data->period = GVT_DEFAULT_TIME_SLICE; -+ data->gvt = gvt; -+ -+ scheduler->sched_data = data; -+ -+ return 0; -+} -+ -+static void tbs_sched_clean(struct intel_gvt *gvt) -+{ -+ struct intel_gvt_workload_scheduler *scheduler = -+ &gvt->scheduler; -+ struct gvt_sched_data *data = scheduler->sched_data; -+ -+ hrtimer_cancel(&data->timer); -+ -+ kfree(data); -+ scheduler->sched_data = NULL; -+} -+ -+static int tbs_sched_init_vgpu(struct intel_vgpu *vgpu) -+{ -+ struct vgpu_sched_data *data; -+ -+ data = kzalloc(sizeof(*data), GFP_KERNEL); -+ if (!data) -+ return -ENOMEM; -+ -+ data->sched_ctl.weight = vgpu->sched_ctl.weight; -+ data->vgpu = vgpu; -+ INIT_LIST_HEAD(&data->lru_list); -+ -+ vgpu->sched_data = data; -+ -+ return 0; -+} -+ -+static void tbs_sched_clean_vgpu(struct intel_vgpu *vgpu) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct gvt_sched_data *sched_data = gvt->scheduler.sched_data; -+ -+ kfree(vgpu->sched_data); -+ vgpu->sched_data = NULL; -+ -+ /* this vgpu id has been removed */ -+ if (idr_is_empty(&gvt->vgpu_idr)) -+ hrtimer_cancel(&sched_data->timer); -+} -+ -+static void tbs_sched_start_schedule(struct intel_vgpu *vgpu) -+{ -+ struct gvt_sched_data *sched_data = vgpu->gvt->scheduler.sched_data; -+ struct vgpu_sched_data *vgpu_data = vgpu->sched_data; -+ ktime_t now; -+ -+ if (!list_empty(&vgpu_data->lru_list)) -+ return; -+ -+ now = ktime_get(); -+ vgpu_data->pri_time = ktime_add(now, -+ ktime_set(GVT_SCHED_VGPU_PRI_TIME, 0)); -+ vgpu_data->pri_sched = true; -+ -+ list_add(&vgpu_data->lru_list, &sched_data->lru_runq_head); -+ -+ if (!hrtimer_active(&sched_data->timer)) -+ hrtimer_start(&sched_data->timer, ktime_add_ns(ktime_get(), -+ sched_data->period), HRTIMER_MODE_ABS); -+ vgpu_data->active = true; -+} -+ -+static void tbs_sched_stop_schedule(struct intel_vgpu *vgpu) -+{ -+ struct vgpu_sched_data *vgpu_data = vgpu->sched_data; -+ -+ list_del_init(&vgpu_data->lru_list); -+ vgpu_data->active = false; -+} -+ -+static struct intel_gvt_sched_policy_ops tbs_schedule_ops = { -+ .init = tbs_sched_init, -+ .clean = tbs_sched_clean, -+ .init_vgpu = tbs_sched_init_vgpu, -+ .clean_vgpu = tbs_sched_clean_vgpu, -+ .start_schedule = tbs_sched_start_schedule, -+ .stop_schedule = tbs_sched_stop_schedule, -+}; -+ -+int intel_gvt_init_sched_policy(struct intel_gvt *gvt) -+{ -+ int ret; -+ -+ mutex_lock(&gvt->sched_lock); -+ gvt->scheduler.sched_ops = &tbs_schedule_ops; -+ ret = gvt->scheduler.sched_ops->init(gvt); -+ mutex_unlock(&gvt->sched_lock); -+ -+ return ret; -+} -+ -+void intel_gvt_clean_sched_policy(struct intel_gvt *gvt) -+{ -+ mutex_lock(&gvt->sched_lock); -+ gvt->scheduler.sched_ops->clean(gvt); -+ mutex_unlock(&gvt->sched_lock); -+} -+ -+/* for per-vgpu scheduler policy, there are 2 per-vgpu data: -+ * sched_data, and sched_ctl. We see these 2 data as part of -+ * the global scheduler which are proteced by gvt->sched_lock. -+ * Caller should make their decision if the vgpu_lock should -+ * be hold outside. -+ */ -+ -+int intel_vgpu_init_sched_policy(struct intel_vgpu *vgpu) -+{ -+ int ret; -+ -+ mutex_lock(&vgpu->gvt->sched_lock); -+ ret = vgpu->gvt->scheduler.sched_ops->init_vgpu(vgpu); -+ mutex_unlock(&vgpu->gvt->sched_lock); -+ -+ return ret; -+} -+ -+void intel_vgpu_clean_sched_policy(struct intel_vgpu *vgpu) -+{ -+ mutex_lock(&vgpu->gvt->sched_lock); -+ vgpu->gvt->scheduler.sched_ops->clean_vgpu(vgpu); -+ mutex_unlock(&vgpu->gvt->sched_lock); -+} -+ -+void intel_vgpu_start_schedule(struct intel_vgpu *vgpu) -+{ -+ struct vgpu_sched_data *vgpu_data = vgpu->sched_data; -+ -+ mutex_lock(&vgpu->gvt->sched_lock); -+ if (!vgpu_data->active) { -+ gvt_dbg_core("vgpu%d: start schedule\n", vgpu->id); -+ vgpu->gvt->scheduler.sched_ops->start_schedule(vgpu); -+ } -+ mutex_unlock(&vgpu->gvt->sched_lock); -+} -+ -+void intel_gvt_kick_schedule(struct intel_gvt *gvt) -+{ -+ mutex_lock(&gvt->sched_lock); -+ intel_gvt_request_service(gvt, INTEL_GVT_REQUEST_EVENT_SCHED); -+ mutex_unlock(&gvt->sched_lock); -+} -+ -+void intel_vgpu_stop_schedule(struct intel_vgpu *vgpu) -+{ -+ struct intel_gvt_workload_scheduler *scheduler = -+ &vgpu->gvt->scheduler; -+ int ring_id; -+ struct vgpu_sched_data *vgpu_data = vgpu->sched_data; -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ -+ if (!vgpu_data->active) -+ return; -+ -+ gvt_dbg_core("vgpu%d: stop schedule\n", vgpu->id); -+ -+ mutex_lock(&vgpu->gvt->sched_lock); -+ scheduler->sched_ops->stop_schedule(vgpu); -+ -+ if (scheduler->next_vgpu == vgpu) -+ scheduler->next_vgpu = NULL; -+ -+ if (scheduler->current_vgpu == vgpu) { -+ /* stop workload dispatching */ -+ scheduler->need_reschedule = true; -+ scheduler->current_vgpu = NULL; -+ } -+ -+ intel_runtime_pm_get(dev_priv); -+ spin_lock_bh(&scheduler->mmio_context_lock); -+ for (ring_id = 0; ring_id < I915_NUM_ENGINES; ring_id++) { -+ if (scheduler->engine_owner[ring_id] == vgpu) { -+ intel_gvt_switch_mmio(vgpu, NULL, ring_id); -+ scheduler->engine_owner[ring_id] = NULL; -+ } -+ } -+ spin_unlock_bh(&scheduler->mmio_context_lock); -+ intel_runtime_pm_put_unchecked(dev_priv); -+ mutex_unlock(&vgpu->gvt->sched_lock); -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/sched_policy.h b/drivers/gpu/drm/i915_legacy/gvt/sched_policy.h -new file mode 100644 -index 000000000000..7b59e3e88b8b ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/sched_policy.h -@@ -0,0 +1,62 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Anhua Xu -+ * Kevin Tian -+ * -+ * Contributors: -+ * Min He -+ * Bing Niu -+ * Zhi Wang -+ * -+ */ -+ -+#ifndef __GVT_SCHED_POLICY__ -+#define __GVT_SCHED_POLICY__ -+ -+struct intel_gvt_sched_policy_ops { -+ int (*init)(struct intel_gvt *gvt); -+ void (*clean)(struct intel_gvt *gvt); -+ int (*init_vgpu)(struct intel_vgpu *vgpu); -+ void (*clean_vgpu)(struct intel_vgpu *vgpu); -+ void (*start_schedule)(struct intel_vgpu *vgpu); -+ void (*stop_schedule)(struct intel_vgpu *vgpu); -+}; -+ -+void intel_gvt_schedule(struct intel_gvt *gvt); -+ -+int intel_gvt_init_sched_policy(struct intel_gvt *gvt); -+ -+void intel_gvt_clean_sched_policy(struct intel_gvt *gvt); -+ -+int intel_vgpu_init_sched_policy(struct intel_vgpu *vgpu); -+ -+void intel_vgpu_clean_sched_policy(struct intel_vgpu *vgpu); -+ -+void intel_vgpu_start_schedule(struct intel_vgpu *vgpu); -+ -+void intel_vgpu_stop_schedule(struct intel_vgpu *vgpu); -+ -+void intel_gvt_kick_schedule(struct intel_gvt *gvt); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/gvt/scheduler.c b/drivers/gpu/drm/i915_legacy/gvt/scheduler.c -new file mode 100644 -index 000000000000..0f919f0a43d4 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/scheduler.c -@@ -0,0 +1,1550 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Zhi Wang -+ * -+ * Contributors: -+ * Ping Gao -+ * Tina Zhang -+ * Chanbin Du -+ * Min He -+ * Bing Niu -+ * Zhenyu Wang -+ * -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "gvt.h" -+ -+#define RING_CTX_OFF(x) \ -+ offsetof(struct execlist_ring_context, x) -+ -+static void set_context_pdp_root_pointer( -+ struct execlist_ring_context *ring_context, -+ u32 pdp[8]) -+{ -+ int i; -+ -+ for (i = 0; i < 8; i++) -+ ring_context->pdps[i].val = pdp[7 - i]; -+} -+ -+static void update_shadow_pdps(struct intel_vgpu_workload *workload) -+{ -+ struct drm_i915_gem_object *ctx_obj = -+ workload->req->hw_context->state->obj; -+ struct execlist_ring_context *shadow_ring_context; -+ struct page *page; -+ -+ if (WARN_ON(!workload->shadow_mm)) -+ return; -+ -+ if (WARN_ON(!atomic_read(&workload->shadow_mm->pincount))) -+ return; -+ -+ page = i915_gem_object_get_page(ctx_obj, LRC_STATE_PN); -+ shadow_ring_context = kmap(page); -+ set_context_pdp_root_pointer(shadow_ring_context, -+ (void *)workload->shadow_mm->ppgtt_mm.shadow_pdps); -+ kunmap(page); -+} -+ -+/* -+ * when populating shadow ctx from guest, we should not overrride oa related -+ * registers, so that they will not be overlapped by guest oa configs. Thus -+ * made it possible to capture oa data from host for both host and guests. -+ */ -+static void sr_oa_regs(struct intel_vgpu_workload *workload, -+ u32 *reg_state, bool save) -+{ -+ struct drm_i915_private *dev_priv = workload->vgpu->gvt->dev_priv; -+ u32 ctx_oactxctrl = dev_priv->perf.oa.ctx_oactxctrl_offset; -+ u32 ctx_flexeu0 = dev_priv->perf.oa.ctx_flexeu0_offset; -+ int i = 0; -+ u32 flex_mmio[] = { -+ i915_mmio_reg_offset(EU_PERF_CNTL0), -+ i915_mmio_reg_offset(EU_PERF_CNTL1), -+ i915_mmio_reg_offset(EU_PERF_CNTL2), -+ i915_mmio_reg_offset(EU_PERF_CNTL3), -+ i915_mmio_reg_offset(EU_PERF_CNTL4), -+ i915_mmio_reg_offset(EU_PERF_CNTL5), -+ i915_mmio_reg_offset(EU_PERF_CNTL6), -+ }; -+ -+ if (workload->ring_id != RCS0) -+ return; -+ -+ if (save) { -+ workload->oactxctrl = reg_state[ctx_oactxctrl + 1]; -+ -+ for (i = 0; i < ARRAY_SIZE(workload->flex_mmio); i++) { -+ u32 state_offset = ctx_flexeu0 + i * 2; -+ -+ workload->flex_mmio[i] = reg_state[state_offset + 1]; -+ } -+ } else { -+ reg_state[ctx_oactxctrl] = -+ i915_mmio_reg_offset(GEN8_OACTXCONTROL); -+ reg_state[ctx_oactxctrl + 1] = workload->oactxctrl; -+ -+ for (i = 0; i < ARRAY_SIZE(workload->flex_mmio); i++) { -+ u32 state_offset = ctx_flexeu0 + i * 2; -+ u32 mmio = flex_mmio[i]; -+ -+ reg_state[state_offset] = mmio; -+ reg_state[state_offset + 1] = workload->flex_mmio[i]; -+ } -+ } -+} -+ -+static int populate_shadow_context(struct intel_vgpu_workload *workload) -+{ -+ struct intel_vgpu *vgpu = workload->vgpu; -+ struct intel_gvt *gvt = vgpu->gvt; -+ int ring_id = workload->ring_id; -+ struct drm_i915_gem_object *ctx_obj = -+ workload->req->hw_context->state->obj; -+ struct execlist_ring_context *shadow_ring_context; -+ struct page *page; -+ void *dst; -+ unsigned long context_gpa, context_page_num; -+ int i; -+ -+ page = i915_gem_object_get_page(ctx_obj, LRC_STATE_PN); -+ shadow_ring_context = kmap(page); -+ -+ sr_oa_regs(workload, (u32 *)shadow_ring_context, true); -+#define COPY_REG(name) \ -+ intel_gvt_hypervisor_read_gpa(vgpu, workload->ring_context_gpa \ -+ + RING_CTX_OFF(name.val), &shadow_ring_context->name.val, 4) -+#define COPY_REG_MASKED(name) {\ -+ intel_gvt_hypervisor_read_gpa(vgpu, workload->ring_context_gpa \ -+ + RING_CTX_OFF(name.val),\ -+ &shadow_ring_context->name.val, 4);\ -+ shadow_ring_context->name.val |= 0xffff << 16;\ -+ } -+ -+ COPY_REG_MASKED(ctx_ctrl); -+ COPY_REG(ctx_timestamp); -+ -+ if (ring_id == RCS0) { -+ COPY_REG(bb_per_ctx_ptr); -+ COPY_REG(rcs_indirect_ctx); -+ COPY_REG(rcs_indirect_ctx_offset); -+ } -+#undef COPY_REG -+#undef COPY_REG_MASKED -+ -+ intel_gvt_hypervisor_read_gpa(vgpu, -+ workload->ring_context_gpa + -+ sizeof(*shadow_ring_context), -+ (void *)shadow_ring_context + -+ sizeof(*shadow_ring_context), -+ I915_GTT_PAGE_SIZE - sizeof(*shadow_ring_context)); -+ -+ sr_oa_regs(workload, (u32 *)shadow_ring_context, false); -+ kunmap(page); -+ -+ if (IS_RESTORE_INHIBIT(shadow_ring_context->ctx_ctrl.val)) -+ return 0; -+ -+ gvt_dbg_sched("ring id %d workload lrca %x", ring_id, -+ workload->ctx_desc.lrca); -+ -+ context_page_num = gvt->dev_priv->engine[ring_id]->context_size; -+ -+ context_page_num = context_page_num >> PAGE_SHIFT; -+ -+ if (IS_BROADWELL(gvt->dev_priv) && ring_id == RCS0) -+ context_page_num = 19; -+ -+ i = 2; -+ while (i < context_page_num) { -+ context_gpa = intel_vgpu_gma_to_gpa(vgpu->gtt.ggtt_mm, -+ (u32)((workload->ctx_desc.lrca + i) << -+ I915_GTT_PAGE_SHIFT)); -+ if (context_gpa == INTEL_GVT_INVALID_ADDR) { -+ gvt_vgpu_err("Invalid guest context descriptor\n"); -+ return -EFAULT; -+ } -+ -+ page = i915_gem_object_get_page(ctx_obj, LRC_HEADER_PAGES + i); -+ dst = kmap(page); -+ intel_gvt_hypervisor_read_gpa(vgpu, context_gpa, dst, -+ I915_GTT_PAGE_SIZE); -+ kunmap(page); -+ i++; -+ } -+ return 0; -+} -+ -+static inline bool is_gvt_request(struct i915_request *req) -+{ -+ return i915_gem_context_force_single_submission(req->gem_context); -+} -+ -+static void save_ring_hw_state(struct intel_vgpu *vgpu, int ring_id) -+{ -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ u32 ring_base = dev_priv->engine[ring_id]->mmio_base; -+ i915_reg_t reg; -+ -+ reg = RING_INSTDONE(ring_base); -+ vgpu_vreg(vgpu, i915_mmio_reg_offset(reg)) = I915_READ_FW(reg); -+ reg = RING_ACTHD(ring_base); -+ vgpu_vreg(vgpu, i915_mmio_reg_offset(reg)) = I915_READ_FW(reg); -+ reg = RING_ACTHD_UDW(ring_base); -+ vgpu_vreg(vgpu, i915_mmio_reg_offset(reg)) = I915_READ_FW(reg); -+} -+ -+static int shadow_context_status_change(struct notifier_block *nb, -+ unsigned long action, void *data) -+{ -+ struct i915_request *req = data; -+ struct intel_gvt *gvt = container_of(nb, struct intel_gvt, -+ shadow_ctx_notifier_block[req->engine->id]); -+ struct intel_gvt_workload_scheduler *scheduler = &gvt->scheduler; -+ enum intel_engine_id ring_id = req->engine->id; -+ struct intel_vgpu_workload *workload; -+ unsigned long flags; -+ -+ if (!is_gvt_request(req)) { -+ spin_lock_irqsave(&scheduler->mmio_context_lock, flags); -+ if (action == INTEL_CONTEXT_SCHEDULE_IN && -+ scheduler->engine_owner[ring_id]) { -+ /* Switch ring from vGPU to host. */ -+ intel_gvt_switch_mmio(scheduler->engine_owner[ring_id], -+ NULL, ring_id); -+ scheduler->engine_owner[ring_id] = NULL; -+ } -+ spin_unlock_irqrestore(&scheduler->mmio_context_lock, flags); -+ -+ return NOTIFY_OK; -+ } -+ -+ workload = scheduler->current_workload[ring_id]; -+ if (unlikely(!workload)) -+ return NOTIFY_OK; -+ -+ switch (action) { -+ case INTEL_CONTEXT_SCHEDULE_IN: -+ spin_lock_irqsave(&scheduler->mmio_context_lock, flags); -+ if (workload->vgpu != scheduler->engine_owner[ring_id]) { -+ /* Switch ring from host to vGPU or vGPU to vGPU. */ -+ intel_gvt_switch_mmio(scheduler->engine_owner[ring_id], -+ workload->vgpu, ring_id); -+ scheduler->engine_owner[ring_id] = workload->vgpu; -+ } else -+ gvt_dbg_sched("skip ring %d mmio switch for vgpu%d\n", -+ ring_id, workload->vgpu->id); -+ spin_unlock_irqrestore(&scheduler->mmio_context_lock, flags); -+ atomic_set(&workload->shadow_ctx_active, 1); -+ break; -+ case INTEL_CONTEXT_SCHEDULE_OUT: -+ save_ring_hw_state(workload->vgpu, ring_id); -+ atomic_set(&workload->shadow_ctx_active, 0); -+ break; -+ case INTEL_CONTEXT_SCHEDULE_PREEMPTED: -+ save_ring_hw_state(workload->vgpu, ring_id); -+ break; -+ default: -+ WARN_ON(1); -+ return NOTIFY_OK; -+ } -+ wake_up(&workload->shadow_ctx_status_wq); -+ return NOTIFY_OK; -+} -+ -+static void shadow_context_descriptor_update(struct intel_context *ce) -+{ -+ u64 desc = 0; -+ -+ desc = ce->lrc_desc; -+ -+ /* Update bits 0-11 of the context descriptor which includes flags -+ * like GEN8_CTX_* cached in desc_template -+ */ -+ desc &= U64_MAX << 12; -+ desc |= ce->gem_context->desc_template & ((1ULL << 12) - 1); -+ -+ ce->lrc_desc = desc; -+} -+ -+static int copy_workload_to_ring_buffer(struct intel_vgpu_workload *workload) -+{ -+ struct intel_vgpu *vgpu = workload->vgpu; -+ struct i915_request *req = workload->req; -+ void *shadow_ring_buffer_va; -+ u32 *cs; -+ int err; -+ -+ if (IS_GEN(req->i915, 9) && is_inhibit_context(req->hw_context)) -+ intel_vgpu_restore_inhibit_context(vgpu, req); -+ -+ /* -+ * To track whether a request has started on HW, we can emit a -+ * breadcrumb at the beginning of the request and check its -+ * timeline's HWSP to see if the breadcrumb has advanced past the -+ * start of this request. Actually, the request must have the -+ * init_breadcrumb if its timeline set has_init_bread_crumb, or the -+ * scheduler might get a wrong state of it during reset. Since the -+ * requests from gvt always set the has_init_breadcrumb flag, here -+ * need to do the emit_init_breadcrumb for all the requests. -+ */ -+ if (req->engine->emit_init_breadcrumb) { -+ err = req->engine->emit_init_breadcrumb(req); -+ if (err) { -+ gvt_vgpu_err("fail to emit init breadcrumb\n"); -+ return err; -+ } -+ } -+ -+ /* allocate shadow ring buffer */ -+ cs = intel_ring_begin(workload->req, workload->rb_len / sizeof(u32)); -+ if (IS_ERR(cs)) { -+ gvt_vgpu_err("fail to alloc size =%ld shadow ring buffer\n", -+ workload->rb_len); -+ return PTR_ERR(cs); -+ } -+ -+ shadow_ring_buffer_va = workload->shadow_ring_buffer_va; -+ -+ /* get shadow ring buffer va */ -+ workload->shadow_ring_buffer_va = cs; -+ -+ memcpy(cs, shadow_ring_buffer_va, -+ workload->rb_len); -+ -+ cs += workload->rb_len / sizeof(u32); -+ intel_ring_advance(workload->req, cs); -+ -+ return 0; -+} -+ -+static void release_shadow_wa_ctx(struct intel_shadow_wa_ctx *wa_ctx) -+{ -+ if (!wa_ctx->indirect_ctx.obj) -+ return; -+ -+ i915_gem_object_unpin_map(wa_ctx->indirect_ctx.obj); -+ i915_gem_object_put(wa_ctx->indirect_ctx.obj); -+ -+ wa_ctx->indirect_ctx.obj = NULL; -+ wa_ctx->indirect_ctx.shadow_va = NULL; -+} -+ -+static int set_context_ppgtt_from_shadow(struct intel_vgpu_workload *workload, -+ struct i915_gem_context *ctx) -+{ -+ struct intel_vgpu_mm *mm = workload->shadow_mm; -+ struct i915_hw_ppgtt *ppgtt = ctx->ppgtt; -+ int i = 0; -+ -+ if (mm->type != INTEL_GVT_MM_PPGTT || !mm->ppgtt_mm.shadowed) -+ return -EINVAL; -+ -+ if (mm->ppgtt_mm.root_entry_type == GTT_TYPE_PPGTT_ROOT_L4_ENTRY) { -+ px_dma(&ppgtt->pml4) = mm->ppgtt_mm.shadow_pdps[0]; -+ } else { -+ for (i = 0; i < GVT_RING_CTX_NR_PDPS; i++) { -+ px_dma(ppgtt->pdp.page_directory[i]) = -+ mm->ppgtt_mm.shadow_pdps[i]; -+ } -+ } -+ -+ return 0; -+} -+ -+static int -+intel_gvt_workload_req_alloc(struct intel_vgpu_workload *workload) -+{ -+ struct intel_vgpu *vgpu = workload->vgpu; -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ struct i915_gem_context *shadow_ctx = s->shadow_ctx; -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ struct intel_engine_cs *engine = dev_priv->engine[workload->ring_id]; -+ struct i915_request *rq; -+ int ret = 0; -+ -+ lockdep_assert_held(&dev_priv->drm.struct_mutex); -+ -+ if (workload->req) -+ goto out; -+ -+ rq = i915_request_alloc(engine, shadow_ctx); -+ if (IS_ERR(rq)) { -+ gvt_vgpu_err("fail to allocate gem request\n"); -+ ret = PTR_ERR(rq); -+ goto out; -+ } -+ workload->req = i915_request_get(rq); -+out: -+ return ret; -+} -+ -+/** -+ * intel_gvt_scan_and_shadow_workload - audit the workload by scanning and -+ * shadow it as well, include ringbuffer,wa_ctx and ctx. -+ * @workload: an abstract entity for each execlist submission. -+ * -+ * This function is called before the workload submitting to i915, to make -+ * sure the content of the workload is valid. -+ */ -+int intel_gvt_scan_and_shadow_workload(struct intel_vgpu_workload *workload) -+{ -+ struct intel_vgpu *vgpu = workload->vgpu; -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ struct i915_gem_context *shadow_ctx = s->shadow_ctx; -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ struct intel_engine_cs *engine = dev_priv->engine[workload->ring_id]; -+ struct intel_context *ce; -+ int ret; -+ -+ lockdep_assert_held(&dev_priv->drm.struct_mutex); -+ -+ if (workload->shadow) -+ return 0; -+ -+ /* pin shadow context by gvt even the shadow context will be pinned -+ * when i915 alloc request. That is because gvt will update the guest -+ * context from shadow context when workload is completed, and at that -+ * moment, i915 may already unpined the shadow context to make the -+ * shadow_ctx pages invalid. So gvt need to pin itself. After update -+ * the guest context, gvt can unpin the shadow_ctx safely. -+ */ -+ ce = intel_context_pin(shadow_ctx, engine); -+ if (IS_ERR(ce)) { -+ gvt_vgpu_err("fail to pin shadow context\n"); -+ return PTR_ERR(ce); -+ } -+ -+ shadow_ctx->desc_template &= ~(0x3 << GEN8_CTX_ADDRESSING_MODE_SHIFT); -+ shadow_ctx->desc_template |= workload->ctx_desc.addressing_mode << -+ GEN8_CTX_ADDRESSING_MODE_SHIFT; -+ -+ if (!test_and_set_bit(workload->ring_id, s->shadow_ctx_desc_updated)) -+ shadow_context_descriptor_update(ce); -+ -+ ret = intel_gvt_scan_and_shadow_ringbuffer(workload); -+ if (ret) -+ goto err_unpin; -+ -+ if (workload->ring_id == RCS0 && workload->wa_ctx.indirect_ctx.size) { -+ ret = intel_gvt_scan_and_shadow_wa_ctx(&workload->wa_ctx); -+ if (ret) -+ goto err_shadow; -+ } -+ -+ workload->shadow = true; -+ return 0; -+err_shadow: -+ release_shadow_wa_ctx(&workload->wa_ctx); -+err_unpin: -+ intel_context_unpin(ce); -+ return ret; -+} -+ -+static void release_shadow_batch_buffer(struct intel_vgpu_workload *workload); -+ -+static int prepare_shadow_batch_buffer(struct intel_vgpu_workload *workload) -+{ -+ struct intel_gvt *gvt = workload->vgpu->gvt; -+ const int gmadr_bytes = gvt->device_info.gmadr_bytes_in_cmd; -+ struct intel_vgpu_shadow_bb *bb; -+ int ret; -+ -+ list_for_each_entry(bb, &workload->shadow_bb, list) { -+ /* For privilge batch buffer and not wa_ctx, the bb_start_cmd_va -+ * is only updated into ring_scan_buffer, not real ring address -+ * allocated in later copy_workload_to_ring_buffer. pls be noted -+ * shadow_ring_buffer_va is now pointed to real ring buffer va -+ * in copy_workload_to_ring_buffer. -+ */ -+ -+ if (bb->bb_offset) -+ bb->bb_start_cmd_va = workload->shadow_ring_buffer_va -+ + bb->bb_offset; -+ -+ if (bb->ppgtt) { -+ /* for non-priv bb, scan&shadow is only for -+ * debugging purpose, so the content of shadow bb -+ * is the same as original bb. Therefore, -+ * here, rather than switch to shadow bb's gma -+ * address, we directly use original batch buffer's -+ * gma address, and send original bb to hardware -+ * directly -+ */ -+ if (bb->clflush & CLFLUSH_AFTER) { -+ drm_clflush_virt_range(bb->va, -+ bb->obj->base.size); -+ bb->clflush &= ~CLFLUSH_AFTER; -+ } -+ i915_gem_obj_finish_shmem_access(bb->obj); -+ bb->accessing = false; -+ -+ } else { -+ bb->vma = i915_gem_object_ggtt_pin(bb->obj, -+ NULL, 0, 0, 0); -+ if (IS_ERR(bb->vma)) { -+ ret = PTR_ERR(bb->vma); -+ goto err; -+ } -+ -+ /* relocate shadow batch buffer */ -+ bb->bb_start_cmd_va[1] = i915_ggtt_offset(bb->vma); -+ if (gmadr_bytes == 8) -+ bb->bb_start_cmd_va[2] = 0; -+ -+ /* No one is going to touch shadow bb from now on. */ -+ if (bb->clflush & CLFLUSH_AFTER) { -+ drm_clflush_virt_range(bb->va, -+ bb->obj->base.size); -+ bb->clflush &= ~CLFLUSH_AFTER; -+ } -+ -+ ret = i915_gem_object_set_to_gtt_domain(bb->obj, -+ false); -+ if (ret) -+ goto err; -+ -+ i915_gem_obj_finish_shmem_access(bb->obj); -+ bb->accessing = false; -+ -+ ret = i915_vma_move_to_active(bb->vma, -+ workload->req, -+ 0); -+ if (ret) -+ goto err; -+ } -+ } -+ return 0; -+err: -+ release_shadow_batch_buffer(workload); -+ return ret; -+} -+ -+static void update_wa_ctx_2_shadow_ctx(struct intel_shadow_wa_ctx *wa_ctx) -+{ -+ struct intel_vgpu_workload *workload = -+ container_of(wa_ctx, struct intel_vgpu_workload, wa_ctx); -+ struct i915_request *rq = workload->req; -+ struct execlist_ring_context *shadow_ring_context = -+ (struct execlist_ring_context *)rq->hw_context->lrc_reg_state; -+ -+ shadow_ring_context->bb_per_ctx_ptr.val = -+ (shadow_ring_context->bb_per_ctx_ptr.val & -+ (~PER_CTX_ADDR_MASK)) | wa_ctx->per_ctx.shadow_gma; -+ shadow_ring_context->rcs_indirect_ctx.val = -+ (shadow_ring_context->rcs_indirect_ctx.val & -+ (~INDIRECT_CTX_ADDR_MASK)) | wa_ctx->indirect_ctx.shadow_gma; -+} -+ -+static int prepare_shadow_wa_ctx(struct intel_shadow_wa_ctx *wa_ctx) -+{ -+ struct i915_vma *vma; -+ unsigned char *per_ctx_va = -+ (unsigned char *)wa_ctx->indirect_ctx.shadow_va + -+ wa_ctx->indirect_ctx.size; -+ -+ if (wa_ctx->indirect_ctx.size == 0) -+ return 0; -+ -+ vma = i915_gem_object_ggtt_pin(wa_ctx->indirect_ctx.obj, NULL, -+ 0, CACHELINE_BYTES, 0); -+ if (IS_ERR(vma)) -+ return PTR_ERR(vma); -+ -+ /* FIXME: we are not tracking our pinned VMA leaving it -+ * up to the core to fix up the stray pin_count upon -+ * free. -+ */ -+ -+ wa_ctx->indirect_ctx.shadow_gma = i915_ggtt_offset(vma); -+ -+ wa_ctx->per_ctx.shadow_gma = *((unsigned int *)per_ctx_va + 1); -+ memset(per_ctx_va, 0, CACHELINE_BYTES); -+ -+ update_wa_ctx_2_shadow_ctx(wa_ctx); -+ return 0; -+} -+ -+static void release_shadow_batch_buffer(struct intel_vgpu_workload *workload) -+{ -+ struct intel_vgpu *vgpu = workload->vgpu; -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ struct intel_vgpu_shadow_bb *bb, *pos; -+ -+ if (list_empty(&workload->shadow_bb)) -+ return; -+ -+ bb = list_first_entry(&workload->shadow_bb, -+ struct intel_vgpu_shadow_bb, list); -+ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ -+ list_for_each_entry_safe(bb, pos, &workload->shadow_bb, list) { -+ if (bb->obj) { -+ if (bb->accessing) -+ i915_gem_obj_finish_shmem_access(bb->obj); -+ -+ if (bb->va && !IS_ERR(bb->va)) -+ i915_gem_object_unpin_map(bb->obj); -+ -+ if (bb->vma && !IS_ERR(bb->vma)) { -+ i915_vma_unpin(bb->vma); -+ i915_vma_close(bb->vma); -+ } -+ __i915_gem_object_release_unless_active(bb->obj); -+ } -+ list_del(&bb->list); -+ kfree(bb); -+ } -+ -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+} -+ -+static int prepare_workload(struct intel_vgpu_workload *workload) -+{ -+ struct intel_vgpu *vgpu = workload->vgpu; -+ int ret = 0; -+ -+ ret = intel_vgpu_pin_mm(workload->shadow_mm); -+ if (ret) { -+ gvt_vgpu_err("fail to vgpu pin mm\n"); -+ return ret; -+ } -+ -+ update_shadow_pdps(workload); -+ -+ ret = intel_vgpu_sync_oos_pages(workload->vgpu); -+ if (ret) { -+ gvt_vgpu_err("fail to vgpu sync oos pages\n"); -+ goto err_unpin_mm; -+ } -+ -+ ret = intel_vgpu_flush_post_shadow(workload->vgpu); -+ if (ret) { -+ gvt_vgpu_err("fail to flush post shadow\n"); -+ goto err_unpin_mm; -+ } -+ -+ ret = copy_workload_to_ring_buffer(workload); -+ if (ret) { -+ gvt_vgpu_err("fail to generate request\n"); -+ goto err_unpin_mm; -+ } -+ -+ ret = prepare_shadow_batch_buffer(workload); -+ if (ret) { -+ gvt_vgpu_err("fail to prepare_shadow_batch_buffer\n"); -+ goto err_unpin_mm; -+ } -+ -+ ret = prepare_shadow_wa_ctx(&workload->wa_ctx); -+ if (ret) { -+ gvt_vgpu_err("fail to prepare_shadow_wa_ctx\n"); -+ goto err_shadow_batch; -+ } -+ -+ if (workload->prepare) { -+ ret = workload->prepare(workload); -+ if (ret) -+ goto err_shadow_wa_ctx; -+ } -+ -+ return 0; -+err_shadow_wa_ctx: -+ release_shadow_wa_ctx(&workload->wa_ctx); -+err_shadow_batch: -+ release_shadow_batch_buffer(workload); -+err_unpin_mm: -+ intel_vgpu_unpin_mm(workload->shadow_mm); -+ return ret; -+} -+ -+static int dispatch_workload(struct intel_vgpu_workload *workload) -+{ -+ struct intel_vgpu *vgpu = workload->vgpu; -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ struct i915_gem_context *shadow_ctx = s->shadow_ctx; -+ struct i915_request *rq; -+ int ring_id = workload->ring_id; -+ int ret; -+ -+ gvt_dbg_sched("ring id %d prepare to dispatch workload %p\n", -+ ring_id, workload); -+ -+ mutex_lock(&vgpu->vgpu_lock); -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ -+ ret = set_context_ppgtt_from_shadow(workload, shadow_ctx); -+ if (ret < 0) { -+ gvt_vgpu_err("workload shadow ppgtt isn't ready\n"); -+ goto err_req; -+ } -+ -+ ret = intel_gvt_workload_req_alloc(workload); -+ if (ret) -+ goto err_req; -+ -+ ret = intel_gvt_scan_and_shadow_workload(workload); -+ if (ret) -+ goto out; -+ -+ ret = populate_shadow_context(workload); -+ if (ret) { -+ release_shadow_wa_ctx(&workload->wa_ctx); -+ goto out; -+ } -+ -+ ret = prepare_workload(workload); -+out: -+ if (ret) { -+ /* We might still need to add request with -+ * clean ctx to retire it properly.. -+ */ -+ rq = fetch_and_zero(&workload->req); -+ i915_request_put(rq); -+ } -+ -+ if (!IS_ERR_OR_NULL(workload->req)) { -+ gvt_dbg_sched("ring id %d submit workload to i915 %p\n", -+ ring_id, workload->req); -+ i915_request_add(workload->req); -+ workload->dispatched = true; -+ } -+err_req: -+ if (ret) -+ workload->status = ret; -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ mutex_unlock(&vgpu->vgpu_lock); -+ return ret; -+} -+ -+static struct intel_vgpu_workload *pick_next_workload( -+ struct intel_gvt *gvt, int ring_id) -+{ -+ struct intel_gvt_workload_scheduler *scheduler = &gvt->scheduler; -+ struct intel_vgpu_workload *workload = NULL; -+ -+ mutex_lock(&gvt->sched_lock); -+ -+ /* -+ * no current vgpu / will be scheduled out / no workload -+ * bail out -+ */ -+ if (!scheduler->current_vgpu) { -+ gvt_dbg_sched("ring id %d stop - no current vgpu\n", ring_id); -+ goto out; -+ } -+ -+ if (scheduler->need_reschedule) { -+ gvt_dbg_sched("ring id %d stop - will reschedule\n", ring_id); -+ goto out; -+ } -+ -+ if (!scheduler->current_vgpu->active || -+ list_empty(workload_q_head(scheduler->current_vgpu, ring_id))) -+ goto out; -+ -+ /* -+ * still have current workload, maybe the workload disptacher -+ * fail to submit it for some reason, resubmit it. -+ */ -+ if (scheduler->current_workload[ring_id]) { -+ workload = scheduler->current_workload[ring_id]; -+ gvt_dbg_sched("ring id %d still have current workload %p\n", -+ ring_id, workload); -+ goto out; -+ } -+ -+ /* -+ * pick a workload as current workload -+ * once current workload is set, schedule policy routines -+ * will wait the current workload is finished when trying to -+ * schedule out a vgpu. -+ */ -+ scheduler->current_workload[ring_id] = container_of( -+ workload_q_head(scheduler->current_vgpu, ring_id)->next, -+ struct intel_vgpu_workload, list); -+ -+ workload = scheduler->current_workload[ring_id]; -+ -+ gvt_dbg_sched("ring id %d pick new workload %p\n", ring_id, workload); -+ -+ atomic_inc(&workload->vgpu->submission.running_workload_num); -+out: -+ mutex_unlock(&gvt->sched_lock); -+ return workload; -+} -+ -+static void update_guest_context(struct intel_vgpu_workload *workload) -+{ -+ struct i915_request *rq = workload->req; -+ struct intel_vgpu *vgpu = workload->vgpu; -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct drm_i915_gem_object *ctx_obj = rq->hw_context->state->obj; -+ struct execlist_ring_context *shadow_ring_context; -+ struct page *page; -+ void *src; -+ unsigned long context_gpa, context_page_num; -+ int i; -+ struct drm_i915_private *dev_priv = gvt->dev_priv; -+ u32 ring_base; -+ u32 head, tail; -+ u16 wrap_count; -+ -+ gvt_dbg_sched("ring id %d workload lrca %x\n", rq->engine->id, -+ workload->ctx_desc.lrca); -+ -+ head = workload->rb_head; -+ tail = workload->rb_tail; -+ wrap_count = workload->guest_rb_head >> RB_HEAD_WRAP_CNT_OFF; -+ -+ if (tail < head) { -+ if (wrap_count == RB_HEAD_WRAP_CNT_MAX) -+ wrap_count = 0; -+ else -+ wrap_count += 1; -+ } -+ -+ head = (wrap_count << RB_HEAD_WRAP_CNT_OFF) | tail; -+ -+ ring_base = dev_priv->engine[workload->ring_id]->mmio_base; -+ vgpu_vreg_t(vgpu, RING_TAIL(ring_base)) = tail; -+ vgpu_vreg_t(vgpu, RING_HEAD(ring_base)) = head; -+ -+ context_page_num = rq->engine->context_size; -+ context_page_num = context_page_num >> PAGE_SHIFT; -+ -+ if (IS_BROADWELL(gvt->dev_priv) && rq->engine->id == RCS0) -+ context_page_num = 19; -+ -+ i = 2; -+ -+ while (i < context_page_num) { -+ context_gpa = intel_vgpu_gma_to_gpa(vgpu->gtt.ggtt_mm, -+ (u32)((workload->ctx_desc.lrca + i) << -+ I915_GTT_PAGE_SHIFT)); -+ if (context_gpa == INTEL_GVT_INVALID_ADDR) { -+ gvt_vgpu_err("invalid guest context descriptor\n"); -+ return; -+ } -+ -+ page = i915_gem_object_get_page(ctx_obj, LRC_HEADER_PAGES + i); -+ src = kmap(page); -+ intel_gvt_hypervisor_write_gpa(vgpu, context_gpa, src, -+ I915_GTT_PAGE_SIZE); -+ kunmap(page); -+ i++; -+ } -+ -+ intel_gvt_hypervisor_write_gpa(vgpu, workload->ring_context_gpa + -+ RING_CTX_OFF(ring_header.val), &workload->rb_tail, 4); -+ -+ page = i915_gem_object_get_page(ctx_obj, LRC_STATE_PN); -+ shadow_ring_context = kmap(page); -+ -+#define COPY_REG(name) \ -+ intel_gvt_hypervisor_write_gpa(vgpu, workload->ring_context_gpa + \ -+ RING_CTX_OFF(name.val), &shadow_ring_context->name.val, 4) -+ -+ COPY_REG(ctx_ctrl); -+ COPY_REG(ctx_timestamp); -+ -+#undef COPY_REG -+ -+ intel_gvt_hypervisor_write_gpa(vgpu, -+ workload->ring_context_gpa + -+ sizeof(*shadow_ring_context), -+ (void *)shadow_ring_context + -+ sizeof(*shadow_ring_context), -+ I915_GTT_PAGE_SIZE - sizeof(*shadow_ring_context)); -+ -+ kunmap(page); -+} -+ -+void intel_vgpu_clean_workloads(struct intel_vgpu *vgpu, -+ intel_engine_mask_t engine_mask) -+{ -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ struct intel_engine_cs *engine; -+ struct intel_vgpu_workload *pos, *n; -+ intel_engine_mask_t tmp; -+ -+ /* free the unsubmited workloads in the queues. */ -+ for_each_engine_masked(engine, dev_priv, engine_mask, tmp) { -+ list_for_each_entry_safe(pos, n, -+ &s->workload_q_head[engine->id], list) { -+ list_del_init(&pos->list); -+ intel_vgpu_destroy_workload(pos); -+ } -+ clear_bit(engine->id, s->shadow_ctx_desc_updated); -+ } -+} -+ -+static void complete_current_workload(struct intel_gvt *gvt, int ring_id) -+{ -+ struct intel_gvt_workload_scheduler *scheduler = &gvt->scheduler; -+ struct intel_vgpu_workload *workload = -+ scheduler->current_workload[ring_id]; -+ struct intel_vgpu *vgpu = workload->vgpu; -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ struct i915_request *rq = workload->req; -+ int event; -+ -+ mutex_lock(&vgpu->vgpu_lock); -+ mutex_lock(&gvt->sched_lock); -+ -+ /* For the workload w/ request, needs to wait for the context -+ * switch to make sure request is completed. -+ * For the workload w/o request, directly complete the workload. -+ */ -+ if (rq) { -+ wait_event(workload->shadow_ctx_status_wq, -+ !atomic_read(&workload->shadow_ctx_active)); -+ -+ /* If this request caused GPU hang, req->fence.error will -+ * be set to -EIO. Use -EIO to set workload status so -+ * that when this request caused GPU hang, didn't trigger -+ * context switch interrupt to guest. -+ */ -+ if (likely(workload->status == -EINPROGRESS)) { -+ if (workload->req->fence.error == -EIO) -+ workload->status = -EIO; -+ else -+ workload->status = 0; -+ } -+ -+ if (!workload->status && -+ !(vgpu->resetting_eng & BIT(ring_id))) { -+ update_guest_context(workload); -+ -+ for_each_set_bit(event, workload->pending_events, -+ INTEL_GVT_EVENT_MAX) -+ intel_vgpu_trigger_virtual_event(vgpu, event); -+ } -+ -+ /* unpin shadow ctx as the shadow_ctx update is done */ -+ mutex_lock(&rq->i915->drm.struct_mutex); -+ intel_context_unpin(rq->hw_context); -+ mutex_unlock(&rq->i915->drm.struct_mutex); -+ -+ i915_request_put(fetch_and_zero(&workload->req)); -+ } -+ -+ gvt_dbg_sched("ring id %d complete workload %p status %d\n", -+ ring_id, workload, workload->status); -+ -+ scheduler->current_workload[ring_id] = NULL; -+ -+ list_del_init(&workload->list); -+ -+ if (workload->status || vgpu->resetting_eng & BIT(ring_id)) { -+ /* if workload->status is not successful means HW GPU -+ * has occurred GPU hang or something wrong with i915/GVT, -+ * and GVT won't inject context switch interrupt to guest. -+ * So this error is a vGPU hang actually to the guest. -+ * According to this we should emunlate a vGPU hang. If -+ * there are pending workloads which are already submitted -+ * from guest, we should clean them up like HW GPU does. -+ * -+ * if it is in middle of engine resetting, the pending -+ * workloads won't be submitted to HW GPU and will be -+ * cleaned up during the resetting process later, so doing -+ * the workload clean up here doesn't have any impact. -+ **/ -+ intel_vgpu_clean_workloads(vgpu, BIT(ring_id)); -+ } -+ -+ workload->complete(workload); -+ -+ atomic_dec(&s->running_workload_num); -+ wake_up(&scheduler->workload_complete_wq); -+ -+ if (gvt->scheduler.need_reschedule) -+ intel_gvt_request_service(gvt, INTEL_GVT_REQUEST_EVENT_SCHED); -+ -+ mutex_unlock(&gvt->sched_lock); -+ mutex_unlock(&vgpu->vgpu_lock); -+} -+ -+struct workload_thread_param { -+ struct intel_gvt *gvt; -+ int ring_id; -+}; -+ -+static int workload_thread(void *priv) -+{ -+ struct workload_thread_param *p = (struct workload_thread_param *)priv; -+ struct intel_gvt *gvt = p->gvt; -+ int ring_id = p->ring_id; -+ struct intel_gvt_workload_scheduler *scheduler = &gvt->scheduler; -+ struct intel_vgpu_workload *workload = NULL; -+ struct intel_vgpu *vgpu = NULL; -+ int ret; -+ bool need_force_wake = (INTEL_GEN(gvt->dev_priv) >= 9); -+ DEFINE_WAIT_FUNC(wait, woken_wake_function); -+ -+ kfree(p); -+ -+ gvt_dbg_core("workload thread for ring %d started\n", ring_id); -+ -+ while (!kthread_should_stop()) { -+ add_wait_queue(&scheduler->waitq[ring_id], &wait); -+ do { -+ workload = pick_next_workload(gvt, ring_id); -+ if (workload) -+ break; -+ wait_woken(&wait, TASK_INTERRUPTIBLE, -+ MAX_SCHEDULE_TIMEOUT); -+ } while (!kthread_should_stop()); -+ remove_wait_queue(&scheduler->waitq[ring_id], &wait); -+ -+ if (!workload) -+ break; -+ -+ gvt_dbg_sched("ring id %d next workload %p vgpu %d\n", -+ workload->ring_id, workload, -+ workload->vgpu->id); -+ -+ intel_runtime_pm_get(gvt->dev_priv); -+ -+ gvt_dbg_sched("ring id %d will dispatch workload %p\n", -+ workload->ring_id, workload); -+ -+ if (need_force_wake) -+ intel_uncore_forcewake_get(&gvt->dev_priv->uncore, -+ FORCEWAKE_ALL); -+ -+ ret = dispatch_workload(workload); -+ -+ if (ret) { -+ vgpu = workload->vgpu; -+ gvt_vgpu_err("fail to dispatch workload, skip\n"); -+ goto complete; -+ } -+ -+ gvt_dbg_sched("ring id %d wait workload %p\n", -+ workload->ring_id, workload); -+ i915_request_wait(workload->req, 0, MAX_SCHEDULE_TIMEOUT); -+ -+complete: -+ gvt_dbg_sched("will complete workload %p, status: %d\n", -+ workload, workload->status); -+ -+ complete_current_workload(gvt, ring_id); -+ -+ if (need_force_wake) -+ intel_uncore_forcewake_put(&gvt->dev_priv->uncore, -+ FORCEWAKE_ALL); -+ -+ intel_runtime_pm_put_unchecked(gvt->dev_priv); -+ if (ret && (vgpu_is_vm_unhealthy(ret))) -+ enter_failsafe_mode(vgpu, GVT_FAILSAFE_GUEST_ERR); -+ } -+ return 0; -+} -+ -+void intel_gvt_wait_vgpu_idle(struct intel_vgpu *vgpu) -+{ -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct intel_gvt_workload_scheduler *scheduler = &gvt->scheduler; -+ -+ if (atomic_read(&s->running_workload_num)) { -+ gvt_dbg_sched("wait vgpu idle\n"); -+ -+ wait_event(scheduler->workload_complete_wq, -+ !atomic_read(&s->running_workload_num)); -+ } -+} -+ -+void intel_gvt_clean_workload_scheduler(struct intel_gvt *gvt) -+{ -+ struct intel_gvt_workload_scheduler *scheduler = &gvt->scheduler; -+ struct intel_engine_cs *engine; -+ enum intel_engine_id i; -+ -+ gvt_dbg_core("clean workload scheduler\n"); -+ -+ for_each_engine(engine, gvt->dev_priv, i) { -+ atomic_notifier_chain_unregister( -+ &engine->context_status_notifier, -+ &gvt->shadow_ctx_notifier_block[i]); -+ kthread_stop(scheduler->thread[i]); -+ } -+} -+ -+int intel_gvt_init_workload_scheduler(struct intel_gvt *gvt) -+{ -+ struct intel_gvt_workload_scheduler *scheduler = &gvt->scheduler; -+ struct workload_thread_param *param = NULL; -+ struct intel_engine_cs *engine; -+ enum intel_engine_id i; -+ int ret; -+ -+ gvt_dbg_core("init workload scheduler\n"); -+ -+ init_waitqueue_head(&scheduler->workload_complete_wq); -+ -+ for_each_engine(engine, gvt->dev_priv, i) { -+ init_waitqueue_head(&scheduler->waitq[i]); -+ -+ param = kzalloc(sizeof(*param), GFP_KERNEL); -+ if (!param) { -+ ret = -ENOMEM; -+ goto err; -+ } -+ -+ param->gvt = gvt; -+ param->ring_id = i; -+ -+ scheduler->thread[i] = kthread_run(workload_thread, param, -+ "gvt workload %d", i); -+ if (IS_ERR(scheduler->thread[i])) { -+ gvt_err("fail to create workload thread\n"); -+ ret = PTR_ERR(scheduler->thread[i]); -+ goto err; -+ } -+ -+ gvt->shadow_ctx_notifier_block[i].notifier_call = -+ shadow_context_status_change; -+ atomic_notifier_chain_register(&engine->context_status_notifier, -+ &gvt->shadow_ctx_notifier_block[i]); -+ } -+ return 0; -+err: -+ intel_gvt_clean_workload_scheduler(gvt); -+ kfree(param); -+ param = NULL; -+ return ret; -+} -+ -+static void -+i915_context_ppgtt_root_restore(struct intel_vgpu_submission *s) -+{ -+ struct i915_hw_ppgtt *i915_ppgtt = s->shadow_ctx->ppgtt; -+ int i; -+ -+ if (i915_vm_is_4lvl(&i915_ppgtt->vm)) { -+ px_dma(&i915_ppgtt->pml4) = s->i915_context_pml4; -+ } else { -+ for (i = 0; i < GEN8_3LVL_PDPES; i++) -+ px_dma(i915_ppgtt->pdp.page_directory[i]) = -+ s->i915_context_pdps[i]; -+ } -+} -+ -+/** -+ * intel_vgpu_clean_submission - free submission-related resource for vGPU -+ * @vgpu: a vGPU -+ * -+ * This function is called when a vGPU is being destroyed. -+ * -+ */ -+void intel_vgpu_clean_submission(struct intel_vgpu *vgpu) -+{ -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ -+ intel_vgpu_select_submission_ops(vgpu, ALL_ENGINES, 0); -+ i915_context_ppgtt_root_restore(s); -+ i915_gem_context_put(s->shadow_ctx); -+ kmem_cache_destroy(s->workloads); -+} -+ -+ -+/** -+ * intel_vgpu_reset_submission - reset submission-related resource for vGPU -+ * @vgpu: a vGPU -+ * @engine_mask: engines expected to be reset -+ * -+ * This function is called when a vGPU is being destroyed. -+ * -+ */ -+void intel_vgpu_reset_submission(struct intel_vgpu *vgpu, -+ intel_engine_mask_t engine_mask) -+{ -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ -+ if (!s->active) -+ return; -+ -+ intel_vgpu_clean_workloads(vgpu, engine_mask); -+ s->ops->reset(vgpu, engine_mask); -+} -+ -+static void -+i915_context_ppgtt_root_save(struct intel_vgpu_submission *s) -+{ -+ struct i915_hw_ppgtt *i915_ppgtt = s->shadow_ctx->ppgtt; -+ int i; -+ -+ if (i915_vm_is_4lvl(&i915_ppgtt->vm)) -+ s->i915_context_pml4 = px_dma(&i915_ppgtt->pml4); -+ else { -+ for (i = 0; i < GEN8_3LVL_PDPES; i++) -+ s->i915_context_pdps[i] = -+ px_dma(i915_ppgtt->pdp.page_directory[i]); -+ } -+} -+ -+/** -+ * intel_vgpu_setup_submission - setup submission-related resource for vGPU -+ * @vgpu: a vGPU -+ * -+ * This function is called when a vGPU is being created. -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ * -+ */ -+int intel_vgpu_setup_submission(struct intel_vgpu *vgpu) -+{ -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ enum intel_engine_id i; -+ struct intel_engine_cs *engine; -+ int ret; -+ -+ s->shadow_ctx = i915_gem_context_create_gvt( -+ &vgpu->gvt->dev_priv->drm); -+ if (IS_ERR(s->shadow_ctx)) -+ return PTR_ERR(s->shadow_ctx); -+ -+ i915_context_ppgtt_root_save(s); -+ -+ bitmap_zero(s->shadow_ctx_desc_updated, I915_NUM_ENGINES); -+ -+ s->workloads = kmem_cache_create_usercopy("gvt-g_vgpu_workload", -+ sizeof(struct intel_vgpu_workload), 0, -+ SLAB_HWCACHE_ALIGN, -+ offsetof(struct intel_vgpu_workload, rb_tail), -+ sizeof_field(struct intel_vgpu_workload, rb_tail), -+ NULL); -+ -+ if (!s->workloads) { -+ ret = -ENOMEM; -+ goto out_shadow_ctx; -+ } -+ -+ for_each_engine(engine, vgpu->gvt->dev_priv, i) -+ INIT_LIST_HEAD(&s->workload_q_head[i]); -+ -+ atomic_set(&s->running_workload_num, 0); -+ bitmap_zero(s->tlb_handle_pending, I915_NUM_ENGINES); -+ -+ return 0; -+ -+out_shadow_ctx: -+ i915_gem_context_put(s->shadow_ctx); -+ return ret; -+} -+ -+/** -+ * intel_vgpu_select_submission_ops - select virtual submission interface -+ * @vgpu: a vGPU -+ * @engine_mask: either ALL_ENGINES or target engine mask -+ * @interface: expected vGPU virtual submission interface -+ * -+ * This function is called when guest configures submission interface. -+ * -+ * Returns: -+ * Zero on success, negative error code if failed. -+ * -+ */ -+int intel_vgpu_select_submission_ops(struct intel_vgpu *vgpu, -+ intel_engine_mask_t engine_mask, -+ unsigned int interface) -+{ -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ const struct intel_vgpu_submission_ops *ops[] = { -+ [INTEL_VGPU_EXECLIST_SUBMISSION] = -+ &intel_vgpu_execlist_submission_ops, -+ }; -+ int ret; -+ -+ if (WARN_ON(interface >= ARRAY_SIZE(ops))) -+ return -EINVAL; -+ -+ if (WARN_ON(interface == 0 && engine_mask != ALL_ENGINES)) -+ return -EINVAL; -+ -+ if (s->active) -+ s->ops->clean(vgpu, engine_mask); -+ -+ if (interface == 0) { -+ s->ops = NULL; -+ s->virtual_submission_interface = 0; -+ s->active = false; -+ gvt_dbg_core("vgpu%d: remove submission ops\n", vgpu->id); -+ return 0; -+ } -+ -+ ret = ops[interface]->init(vgpu, engine_mask); -+ if (ret) -+ return ret; -+ -+ s->ops = ops[interface]; -+ s->virtual_submission_interface = interface; -+ s->active = true; -+ -+ gvt_dbg_core("vgpu%d: activate ops [ %s ]\n", -+ vgpu->id, s->ops->name); -+ -+ return 0; -+} -+ -+/** -+ * intel_vgpu_destroy_workload - destroy a vGPU workload -+ * @workload: workload to destroy -+ * -+ * This function is called when destroy a vGPU workload. -+ * -+ */ -+void intel_vgpu_destroy_workload(struct intel_vgpu_workload *workload) -+{ -+ struct intel_vgpu_submission *s = &workload->vgpu->submission; -+ -+ release_shadow_batch_buffer(workload); -+ release_shadow_wa_ctx(&workload->wa_ctx); -+ -+ if (workload->shadow_mm) -+ intel_vgpu_mm_put(workload->shadow_mm); -+ -+ kmem_cache_free(s->workloads, workload); -+} -+ -+static struct intel_vgpu_workload * -+alloc_workload(struct intel_vgpu *vgpu) -+{ -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ struct intel_vgpu_workload *workload; -+ -+ workload = kmem_cache_zalloc(s->workloads, GFP_KERNEL); -+ if (!workload) -+ return ERR_PTR(-ENOMEM); -+ -+ INIT_LIST_HEAD(&workload->list); -+ INIT_LIST_HEAD(&workload->shadow_bb); -+ -+ init_waitqueue_head(&workload->shadow_ctx_status_wq); -+ atomic_set(&workload->shadow_ctx_active, 0); -+ -+ workload->status = -EINPROGRESS; -+ workload->vgpu = vgpu; -+ -+ return workload; -+} -+ -+#define RING_CTX_OFF(x) \ -+ offsetof(struct execlist_ring_context, x) -+ -+static void read_guest_pdps(struct intel_vgpu *vgpu, -+ u64 ring_context_gpa, u32 pdp[8]) -+{ -+ u64 gpa; -+ int i; -+ -+ gpa = ring_context_gpa + RING_CTX_OFF(pdps[0].val); -+ -+ for (i = 0; i < 8; i++) -+ intel_gvt_hypervisor_read_gpa(vgpu, -+ gpa + i * 8, &pdp[7 - i], 4); -+} -+ -+static int prepare_mm(struct intel_vgpu_workload *workload) -+{ -+ struct execlist_ctx_descriptor_format *desc = &workload->ctx_desc; -+ struct intel_vgpu_mm *mm; -+ struct intel_vgpu *vgpu = workload->vgpu; -+ enum intel_gvt_gtt_type root_entry_type; -+ u64 pdps[GVT_RING_CTX_NR_PDPS]; -+ -+ switch (desc->addressing_mode) { -+ case 1: /* legacy 32-bit */ -+ root_entry_type = GTT_TYPE_PPGTT_ROOT_L3_ENTRY; -+ break; -+ case 3: /* legacy 64-bit */ -+ root_entry_type = GTT_TYPE_PPGTT_ROOT_L4_ENTRY; -+ break; -+ default: -+ gvt_vgpu_err("Advanced Context mode(SVM) is not supported!\n"); -+ return -EINVAL; -+ } -+ -+ read_guest_pdps(workload->vgpu, workload->ring_context_gpa, (void *)pdps); -+ -+ mm = intel_vgpu_get_ppgtt_mm(workload->vgpu, root_entry_type, pdps); -+ if (IS_ERR(mm)) -+ return PTR_ERR(mm); -+ -+ workload->shadow_mm = mm; -+ return 0; -+} -+ -+#define same_context(a, b) (((a)->context_id == (b)->context_id) && \ -+ ((a)->lrca == (b)->lrca)) -+ -+#define get_last_workload(q) \ -+ (list_empty(q) ? NULL : container_of(q->prev, \ -+ struct intel_vgpu_workload, list)) -+/** -+ * intel_vgpu_create_workload - create a vGPU workload -+ * @vgpu: a vGPU -+ * @ring_id: ring index -+ * @desc: a guest context descriptor -+ * -+ * This function is called when creating a vGPU workload. -+ * -+ * Returns: -+ * struct intel_vgpu_workload * on success, negative error code in -+ * pointer if failed. -+ * -+ */ -+struct intel_vgpu_workload * -+intel_vgpu_create_workload(struct intel_vgpu *vgpu, int ring_id, -+ struct execlist_ctx_descriptor_format *desc) -+{ -+ struct intel_vgpu_submission *s = &vgpu->submission; -+ struct list_head *q = workload_q_head(vgpu, ring_id); -+ struct intel_vgpu_workload *last_workload = get_last_workload(q); -+ struct intel_vgpu_workload *workload = NULL; -+ struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv; -+ u64 ring_context_gpa; -+ u32 head, tail, start, ctl, ctx_ctl, per_ctx, indirect_ctx; -+ u32 guest_head; -+ int ret; -+ -+ ring_context_gpa = intel_vgpu_gma_to_gpa(vgpu->gtt.ggtt_mm, -+ (u32)((desc->lrca + 1) << I915_GTT_PAGE_SHIFT)); -+ if (ring_context_gpa == INTEL_GVT_INVALID_ADDR) { -+ gvt_vgpu_err("invalid guest context LRCA: %x\n", desc->lrca); -+ return ERR_PTR(-EINVAL); -+ } -+ -+ intel_gvt_hypervisor_read_gpa(vgpu, ring_context_gpa + -+ RING_CTX_OFF(ring_header.val), &head, 4); -+ -+ intel_gvt_hypervisor_read_gpa(vgpu, ring_context_gpa + -+ RING_CTX_OFF(ring_tail.val), &tail, 4); -+ -+ guest_head = head; -+ -+ head &= RB_HEAD_OFF_MASK; -+ tail &= RB_TAIL_OFF_MASK; -+ -+ if (last_workload && same_context(&last_workload->ctx_desc, desc)) { -+ gvt_dbg_el("ring id %d cur workload == last\n", ring_id); -+ gvt_dbg_el("ctx head %x real head %lx\n", head, -+ last_workload->rb_tail); -+ /* -+ * cannot use guest context head pointer here, -+ * as it might not be updated at this time -+ */ -+ head = last_workload->rb_tail; -+ } -+ -+ gvt_dbg_el("ring id %d begin a new workload\n", ring_id); -+ -+ /* record some ring buffer register values for scan and shadow */ -+ intel_gvt_hypervisor_read_gpa(vgpu, ring_context_gpa + -+ RING_CTX_OFF(rb_start.val), &start, 4); -+ intel_gvt_hypervisor_read_gpa(vgpu, ring_context_gpa + -+ RING_CTX_OFF(rb_ctrl.val), &ctl, 4); -+ intel_gvt_hypervisor_read_gpa(vgpu, ring_context_gpa + -+ RING_CTX_OFF(ctx_ctrl.val), &ctx_ctl, 4); -+ -+ workload = alloc_workload(vgpu); -+ if (IS_ERR(workload)) -+ return workload; -+ -+ workload->ring_id = ring_id; -+ workload->ctx_desc = *desc; -+ workload->ring_context_gpa = ring_context_gpa; -+ workload->rb_head = head; -+ workload->guest_rb_head = guest_head; -+ workload->rb_tail = tail; -+ workload->rb_start = start; -+ workload->rb_ctl = ctl; -+ -+ if (ring_id == RCS0) { -+ intel_gvt_hypervisor_read_gpa(vgpu, ring_context_gpa + -+ RING_CTX_OFF(bb_per_ctx_ptr.val), &per_ctx, 4); -+ intel_gvt_hypervisor_read_gpa(vgpu, ring_context_gpa + -+ RING_CTX_OFF(rcs_indirect_ctx.val), &indirect_ctx, 4); -+ -+ workload->wa_ctx.indirect_ctx.guest_gma = -+ indirect_ctx & INDIRECT_CTX_ADDR_MASK; -+ workload->wa_ctx.indirect_ctx.size = -+ (indirect_ctx & INDIRECT_CTX_SIZE_MASK) * -+ CACHELINE_BYTES; -+ workload->wa_ctx.per_ctx.guest_gma = -+ per_ctx & PER_CTX_ADDR_MASK; -+ workload->wa_ctx.per_ctx.valid = per_ctx & 1; -+ } -+ -+ gvt_dbg_el("workload %p ring id %d head %x tail %x start %x ctl %x\n", -+ workload, ring_id, head, tail, start, ctl); -+ -+ ret = prepare_mm(workload); -+ if (ret) { -+ kmem_cache_free(s->workloads, workload); -+ return ERR_PTR(ret); -+ } -+ -+ /* Only scan and shadow the first workload in the queue -+ * as there is only one pre-allocated buf-obj for shadow. -+ */ -+ if (list_empty(workload_q_head(vgpu, ring_id))) { -+ intel_runtime_pm_get(dev_priv); -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ ret = intel_gvt_scan_and_shadow_workload(workload); -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ intel_runtime_pm_put_unchecked(dev_priv); -+ } -+ -+ if (ret) { -+ if (vgpu_is_vm_unhealthy(ret)) -+ enter_failsafe_mode(vgpu, GVT_FAILSAFE_GUEST_ERR); -+ intel_vgpu_destroy_workload(workload); -+ return ERR_PTR(ret); -+ } -+ -+ return workload; -+} -+ -+/** -+ * intel_vgpu_queue_workload - Qeue a vGPU workload -+ * @workload: the workload to queue in -+ */ -+void intel_vgpu_queue_workload(struct intel_vgpu_workload *workload) -+{ -+ list_add_tail(&workload->list, -+ workload_q_head(workload->vgpu, workload->ring_id)); -+ intel_gvt_kick_schedule(workload->vgpu->gvt); -+ wake_up(&workload->vgpu->gvt->scheduler.waitq[workload->ring_id]); -+} -diff --git a/drivers/gpu/drm/i915_legacy/gvt/scheduler.h b/drivers/gpu/drm/i915_legacy/gvt/scheduler.h -new file mode 100644 -index 000000000000..c50d14a9ce85 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/scheduler.h -@@ -0,0 +1,166 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Zhi Wang -+ * -+ * Contributors: -+ * Ping Gao -+ * Tina Zhang -+ * Chanbin Du -+ * Min He -+ * Bing Niu -+ * Zhenyu Wang -+ * -+ */ -+ -+#ifndef _GVT_SCHEDULER_H_ -+#define _GVT_SCHEDULER_H_ -+ -+struct intel_gvt_workload_scheduler { -+ struct intel_vgpu *current_vgpu; -+ struct intel_vgpu *next_vgpu; -+ struct intel_vgpu_workload *current_workload[I915_NUM_ENGINES]; -+ bool need_reschedule; -+ -+ spinlock_t mmio_context_lock; -+ /* can be null when owner is host */ -+ struct intel_vgpu *engine_owner[I915_NUM_ENGINES]; -+ -+ wait_queue_head_t workload_complete_wq; -+ struct task_struct *thread[I915_NUM_ENGINES]; -+ wait_queue_head_t waitq[I915_NUM_ENGINES]; -+ -+ void *sched_data; -+ struct intel_gvt_sched_policy_ops *sched_ops; -+}; -+ -+#define INDIRECT_CTX_ADDR_MASK 0xffffffc0 -+#define INDIRECT_CTX_SIZE_MASK 0x3f -+struct shadow_indirect_ctx { -+ struct drm_i915_gem_object *obj; -+ unsigned long guest_gma; -+ unsigned long shadow_gma; -+ void *shadow_va; -+ u32 size; -+}; -+ -+#define PER_CTX_ADDR_MASK 0xfffff000 -+struct shadow_per_ctx { -+ unsigned long guest_gma; -+ unsigned long shadow_gma; -+ unsigned valid; -+}; -+ -+struct intel_shadow_wa_ctx { -+ struct shadow_indirect_ctx indirect_ctx; -+ struct shadow_per_ctx per_ctx; -+ -+}; -+ -+struct intel_vgpu_workload { -+ struct intel_vgpu *vgpu; -+ int ring_id; -+ struct i915_request *req; -+ /* if this workload has been dispatched to i915? */ -+ bool dispatched; -+ bool shadow; /* if workload has done shadow of guest request */ -+ int status; -+ -+ struct intel_vgpu_mm *shadow_mm; -+ -+ /* different submission model may need different handler */ -+ int (*prepare)(struct intel_vgpu_workload *); -+ int (*complete)(struct intel_vgpu_workload *); -+ struct list_head list; -+ -+ DECLARE_BITMAP(pending_events, INTEL_GVT_EVENT_MAX); -+ void *shadow_ring_buffer_va; -+ -+ /* execlist context information */ -+ struct execlist_ctx_descriptor_format ctx_desc; -+ struct execlist_ring_context *ring_context; -+ unsigned long rb_head, rb_tail, rb_ctl, rb_start, rb_len; -+ unsigned long guest_rb_head; -+ bool restore_inhibit; -+ struct intel_vgpu_elsp_dwords elsp_dwords; -+ bool emulate_schedule_in; -+ atomic_t shadow_ctx_active; -+ wait_queue_head_t shadow_ctx_status_wq; -+ u64 ring_context_gpa; -+ -+ /* shadow batch buffer */ -+ struct list_head shadow_bb; -+ struct intel_shadow_wa_ctx wa_ctx; -+ -+ /* oa registers */ -+ u32 oactxctrl; -+ u32 flex_mmio[7]; -+}; -+ -+struct intel_vgpu_shadow_bb { -+ struct list_head list; -+ struct drm_i915_gem_object *obj; -+ struct i915_vma *vma; -+ void *va; -+ u32 *bb_start_cmd_va; -+ unsigned int clflush; -+ bool accessing; -+ unsigned long bb_offset; -+ bool ppgtt; -+}; -+ -+#define workload_q_head(vgpu, ring_id) \ -+ (&(vgpu->submission.workload_q_head[ring_id])) -+ -+void intel_vgpu_queue_workload(struct intel_vgpu_workload *workload); -+ -+int intel_gvt_init_workload_scheduler(struct intel_gvt *gvt); -+ -+void intel_gvt_clean_workload_scheduler(struct intel_gvt *gvt); -+ -+void intel_gvt_wait_vgpu_idle(struct intel_vgpu *vgpu); -+ -+int intel_vgpu_setup_submission(struct intel_vgpu *vgpu); -+ -+void intel_vgpu_reset_submission(struct intel_vgpu *vgpu, -+ intel_engine_mask_t engine_mask); -+ -+void intel_vgpu_clean_submission(struct intel_vgpu *vgpu); -+ -+int intel_vgpu_select_submission_ops(struct intel_vgpu *vgpu, -+ intel_engine_mask_t engine_mask, -+ unsigned int interface); -+ -+extern const struct intel_vgpu_submission_ops -+intel_vgpu_execlist_submission_ops; -+ -+struct intel_vgpu_workload * -+intel_vgpu_create_workload(struct intel_vgpu *vgpu, int ring_id, -+ struct execlist_ctx_descriptor_format *desc); -+ -+void intel_vgpu_destroy_workload(struct intel_vgpu_workload *workload); -+ -+void intel_vgpu_clean_workloads(struct intel_vgpu *vgpu, -+ intel_engine_mask_t engine_mask); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/gvt/trace.h b/drivers/gpu/drm/i915_legacy/gvt/trace.h -new file mode 100644 -index 000000000000..6d787750d279 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/trace.h -@@ -0,0 +1,383 @@ -+/* -+ * Copyright © 2011-2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Jike Song -+ * -+ * Contributors: -+ * Zhi Wang -+ * -+ */ -+ -+#if !defined(_GVT_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ) -+#define _GVT_TRACE_H_ -+ -+#include -+#include -+#include -+#include -+ -+#undef TRACE_SYSTEM -+#define TRACE_SYSTEM gvt -+ -+TRACE_EVENT(spt_alloc, -+ TP_PROTO(int id, void *spt, int type, unsigned long mfn, -+ unsigned long gpt_gfn), -+ -+ TP_ARGS(id, spt, type, mfn, gpt_gfn), -+ -+ TP_STRUCT__entry( -+ __field(int, id) -+ __field(void *, spt) -+ __field(int, type) -+ __field(unsigned long, mfn) -+ __field(unsigned long, gpt_gfn) -+ ), -+ -+ TP_fast_assign( -+ __entry->id = id; -+ __entry->spt = spt; -+ __entry->type = type; -+ __entry->mfn = mfn; -+ __entry->gpt_gfn = gpt_gfn; -+ ), -+ -+ TP_printk("VM%d [alloc] spt %p type %d mfn 0x%lx gfn 0x%lx\n", -+ __entry->id, -+ __entry->spt, -+ __entry->type, -+ __entry->mfn, -+ __entry->gpt_gfn) -+); -+ -+TRACE_EVENT(spt_free, -+ TP_PROTO(int id, void *spt, int type), -+ -+ TP_ARGS(id, spt, type), -+ -+ TP_STRUCT__entry( -+ __field(int, id) -+ __field(void *, spt) -+ __field(int, type) -+ ), -+ -+ TP_fast_assign( -+ __entry->id = id; -+ __entry->spt = spt; -+ __entry->type = type; -+ ), -+ -+ TP_printk("VM%u [free] spt %p type %d\n", -+ __entry->id, -+ __entry->spt, -+ __entry->type) -+); -+ -+#define MAX_BUF_LEN 256 -+ -+TRACE_EVENT(gma_index, -+ TP_PROTO(const char *prefix, unsigned long gma, -+ unsigned long index), -+ -+ TP_ARGS(prefix, gma, index), -+ -+ TP_STRUCT__entry( -+ __array(char, buf, MAX_BUF_LEN) -+ ), -+ -+ TP_fast_assign( -+ snprintf(__entry->buf, MAX_BUF_LEN, -+ "%s gma 0x%lx index 0x%lx\n", prefix, gma, index); -+ ), -+ -+ TP_printk("%s", __entry->buf) -+); -+ -+TRACE_EVENT(gma_translate, -+ TP_PROTO(int id, char *type, int ring_id, int root_entry_type, -+ unsigned long gma, unsigned long gpa), -+ -+ TP_ARGS(id, type, ring_id, root_entry_type, gma, gpa), -+ -+ TP_STRUCT__entry( -+ __array(char, buf, MAX_BUF_LEN) -+ ), -+ -+ TP_fast_assign( -+ snprintf(__entry->buf, MAX_BUF_LEN, -+ "VM%d %s ring %d root_entry_type %d gma 0x%lx -> gpa 0x%lx\n", -+ id, type, ring_id, root_entry_type, gma, gpa); -+ ), -+ -+ TP_printk("%s", __entry->buf) -+); -+ -+TRACE_EVENT(spt_refcount, -+ TP_PROTO(int id, char *action, void *spt, int before, int after), -+ -+ TP_ARGS(id, action, spt, before, after), -+ -+ TP_STRUCT__entry( -+ __array(char, buf, MAX_BUF_LEN) -+ ), -+ -+ TP_fast_assign( -+ snprintf(__entry->buf, MAX_BUF_LEN, -+ "VM%d [%s] spt %p before %d -> after %d\n", -+ id, action, spt, before, after); -+ ), -+ -+ TP_printk("%s", __entry->buf) -+); -+ -+TRACE_EVENT(spt_change, -+ TP_PROTO(int id, char *action, void *spt, unsigned long gfn, -+ int type), -+ -+ TP_ARGS(id, action, spt, gfn, type), -+ -+ TP_STRUCT__entry( -+ __array(char, buf, MAX_BUF_LEN) -+ ), -+ -+ TP_fast_assign( -+ snprintf(__entry->buf, MAX_BUF_LEN, -+ "VM%d [%s] spt %p gfn 0x%lx type %d\n", -+ id, action, spt, gfn, type); -+ ), -+ -+ TP_printk("%s", __entry->buf) -+); -+ -+TRACE_EVENT(spt_guest_change, -+ TP_PROTO(int id, const char *tag, void *spt, int type, u64 v, -+ unsigned long index), -+ -+ TP_ARGS(id, tag, spt, type, v, index), -+ -+ TP_STRUCT__entry( -+ __array(char, buf, MAX_BUF_LEN) -+ ), -+ -+ TP_fast_assign( -+ snprintf(__entry->buf, MAX_BUF_LEN, -+ "VM%d [%s] spt %p type %d entry 0x%llx index 0x%lx\n", -+ id, tag, spt, type, v, index); -+ ), -+ -+ TP_printk("%s", __entry->buf) -+); -+ -+TRACE_EVENT(oos_change, -+ TP_PROTO(int id, const char *tag, int page_id, void *gpt, int type), -+ -+ TP_ARGS(id, tag, page_id, gpt, type), -+ -+ TP_STRUCT__entry( -+ __array(char, buf, MAX_BUF_LEN) -+ ), -+ -+ TP_fast_assign( -+ snprintf(__entry->buf, MAX_BUF_LEN, -+ "VM%d [oos %s] page id %d gpt %p type %d\n", -+ id, tag, page_id, gpt, type); -+ ), -+ -+ TP_printk("%s", __entry->buf) -+); -+ -+TRACE_EVENT(oos_sync, -+ TP_PROTO(int id, int page_id, void *gpt, int type, u64 v, -+ unsigned long index), -+ -+ TP_ARGS(id, page_id, gpt, type, v, index), -+ -+ TP_STRUCT__entry( -+ __array(char, buf, MAX_BUF_LEN) -+ ), -+ -+ TP_fast_assign( -+ snprintf(__entry->buf, MAX_BUF_LEN, -+ "VM%d [oos sync] page id %d gpt %p type %d entry 0x%llx index 0x%lx\n", -+ id, page_id, gpt, type, v, index); -+ ), -+ -+ TP_printk("%s", __entry->buf) -+); -+ -+#define GVT_CMD_STR_LEN 40 -+TRACE_EVENT(gvt_command, -+ TP_PROTO(u8 vgpu_id, u8 ring_id, u32 ip_gma, u32 *cmd_va, -+ u32 cmd_len, u32 buf_type, u32 buf_addr_type, -+ void *workload, const char *cmd_name), -+ -+ TP_ARGS(vgpu_id, ring_id, ip_gma, cmd_va, cmd_len, buf_type, -+ buf_addr_type, workload, cmd_name), -+ -+ TP_STRUCT__entry( -+ __field(u8, vgpu_id) -+ __field(u8, ring_id) -+ __field(u32, ip_gma) -+ __field(u32, buf_type) -+ __field(u32, buf_addr_type) -+ __field(u32, cmd_len) -+ __field(void*, workload) -+ __dynamic_array(u32, raw_cmd, cmd_len) -+ __array(char, cmd_name, GVT_CMD_STR_LEN) -+ ), -+ -+ TP_fast_assign( -+ __entry->vgpu_id = vgpu_id; -+ __entry->ring_id = ring_id; -+ __entry->ip_gma = ip_gma; -+ __entry->buf_type = buf_type; -+ __entry->buf_addr_type = buf_addr_type; -+ __entry->cmd_len = cmd_len; -+ __entry->workload = workload; -+ snprintf(__entry->cmd_name, GVT_CMD_STR_LEN, "%s", cmd_name); -+ memcpy(__get_dynamic_array(raw_cmd), cmd_va, cmd_len * sizeof(*cmd_va)); -+ ), -+ -+ -+ TP_printk("vgpu%d ring %d: address_type %u, buf_type %u, ip_gma %08x,cmd (name=%s,len=%u,raw cmd=%s), workload=%p\n", -+ __entry->vgpu_id, -+ __entry->ring_id, -+ __entry->buf_addr_type, -+ __entry->buf_type, -+ __entry->ip_gma, -+ __entry->cmd_name, -+ __entry->cmd_len, -+ __print_array(__get_dynamic_array(raw_cmd), -+ __entry->cmd_len, 4), -+ __entry->workload) -+); -+ -+#define GVT_TEMP_STR_LEN 10 -+TRACE_EVENT(write_ir, -+ TP_PROTO(int id, char *reg_name, unsigned int reg, unsigned int new_val, -+ unsigned int old_val, bool changed), -+ -+ TP_ARGS(id, reg_name, reg, new_val, old_val, changed), -+ -+ TP_STRUCT__entry( -+ __field(int, id) -+ __array(char, buf, GVT_TEMP_STR_LEN) -+ __field(unsigned int, reg) -+ __field(unsigned int, new_val) -+ __field(unsigned int, old_val) -+ __field(bool, changed) -+ ), -+ -+ TP_fast_assign( -+ __entry->id = id; -+ snprintf(__entry->buf, GVT_TEMP_STR_LEN, "%s", reg_name); -+ __entry->reg = reg; -+ __entry->new_val = new_val; -+ __entry->old_val = old_val; -+ __entry->changed = changed; -+ ), -+ -+ TP_printk("VM%u write [%s] %x, new %08x, old %08x, changed %08x\n", -+ __entry->id, __entry->buf, __entry->reg, __entry->new_val, -+ __entry->old_val, __entry->changed) -+); -+ -+TRACE_EVENT(propagate_event, -+ TP_PROTO(int id, const char *irq_name, int bit), -+ -+ TP_ARGS(id, irq_name, bit), -+ -+ TP_STRUCT__entry( -+ __field(int, id) -+ __array(char, buf, GVT_TEMP_STR_LEN) -+ __field(int, bit) -+ ), -+ -+ TP_fast_assign( -+ __entry->id = id; -+ snprintf(__entry->buf, GVT_TEMP_STR_LEN, "%s", irq_name); -+ __entry->bit = bit; -+ ), -+ -+ TP_printk("Set bit (%d) for (%s) for vgpu (%d)\n", -+ __entry->bit, __entry->buf, __entry->id) -+); -+ -+TRACE_EVENT(inject_msi, -+ TP_PROTO(int id, unsigned int address, unsigned int data), -+ -+ TP_ARGS(id, address, data), -+ -+ TP_STRUCT__entry( -+ __field(int, id) -+ __field(unsigned int, address) -+ __field(unsigned int, data) -+ ), -+ -+ TP_fast_assign( -+ __entry->id = id; -+ __entry->address = address; -+ __entry->data = data; -+ ), -+ -+ TP_printk("vgpu%d:inject msi address %x data %x\n", -+ __entry->id, __entry->address, __entry->data) -+); -+ -+TRACE_EVENT(render_mmio, -+ TP_PROTO(int old_id, int new_id, char *action, unsigned int reg, -+ unsigned int old_val, unsigned int new_val), -+ -+ TP_ARGS(old_id, new_id, action, reg, old_val, new_val), -+ -+ TP_STRUCT__entry( -+ __field(int, old_id) -+ __field(int, new_id) -+ __array(char, buf, GVT_TEMP_STR_LEN) -+ __field(unsigned int, reg) -+ __field(unsigned int, old_val) -+ __field(unsigned int, new_val) -+ ), -+ -+ TP_fast_assign( -+ __entry->old_id = old_id; -+ __entry->new_id = new_id; -+ snprintf(__entry->buf, GVT_TEMP_STR_LEN, "%s", action); -+ __entry->reg = reg; -+ __entry->old_val = old_val; -+ __entry->new_val = new_val; -+ ), -+ -+ TP_printk("VM%u -> VM%u %s reg %x, old %08x new %08x\n", -+ __entry->old_id, __entry->new_id, -+ __entry->buf, __entry->reg, -+ __entry->old_val, __entry->new_val) -+); -+ -+#endif /* _GVT_TRACE_H_ */ -+ -+/* This part must be out of protection */ -+#undef TRACE_INCLUDE_PATH -+#define TRACE_INCLUDE_PATH . -+#undef TRACE_INCLUDE_FILE -+#define TRACE_INCLUDE_FILE trace -+#include -diff --git a/drivers/gpu/drm/i915_legacy/gvt/trace_points.c b/drivers/gpu/drm/i915_legacy/gvt/trace_points.c -new file mode 100644 -index 000000000000..a3deed692b9c ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/trace_points.c -@@ -0,0 +1,36 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Jike Song -+ * -+ * Contributors: -+ * Zhi Wang -+ * -+ */ -+ -+#include "trace.h" -+ -+#ifndef __CHECKER__ -+#define CREATE_TRACE_POINTS -+#include "trace.h" -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/gvt/vgpu.c b/drivers/gpu/drm/i915_legacy/gvt/vgpu.c -new file mode 100644 -index 000000000000..44ce3c2b9ac1 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/gvt/vgpu.c -@@ -0,0 +1,592 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eddie Dong -+ * Kevin Tian -+ * -+ * Contributors: -+ * Ping Gao -+ * Zhi Wang -+ * Bing Niu -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "gvt.h" -+#include "i915_pvinfo.h" -+ -+void populate_pvinfo_page(struct intel_vgpu *vgpu) -+{ -+ /* setup the ballooning information */ -+ vgpu_vreg64_t(vgpu, vgtif_reg(magic)) = VGT_MAGIC; -+ vgpu_vreg_t(vgpu, vgtif_reg(version_major)) = 1; -+ vgpu_vreg_t(vgpu, vgtif_reg(version_minor)) = 0; -+ vgpu_vreg_t(vgpu, vgtif_reg(display_ready)) = 0; -+ vgpu_vreg_t(vgpu, vgtif_reg(vgt_id)) = vgpu->id; -+ -+ vgpu_vreg_t(vgpu, vgtif_reg(vgt_caps)) = VGT_CAPS_FULL_PPGTT; -+ vgpu_vreg_t(vgpu, vgtif_reg(vgt_caps)) |= VGT_CAPS_HWSP_EMULATION; -+ vgpu_vreg_t(vgpu, vgtif_reg(vgt_caps)) |= VGT_CAPS_HUGE_GTT; -+ -+ vgpu_vreg_t(vgpu, vgtif_reg(avail_rs.mappable_gmadr.base)) = -+ vgpu_aperture_gmadr_base(vgpu); -+ vgpu_vreg_t(vgpu, vgtif_reg(avail_rs.mappable_gmadr.size)) = -+ vgpu_aperture_sz(vgpu); -+ vgpu_vreg_t(vgpu, vgtif_reg(avail_rs.nonmappable_gmadr.base)) = -+ vgpu_hidden_gmadr_base(vgpu); -+ vgpu_vreg_t(vgpu, vgtif_reg(avail_rs.nonmappable_gmadr.size)) = -+ vgpu_hidden_sz(vgpu); -+ -+ vgpu_vreg_t(vgpu, vgtif_reg(avail_rs.fence_num)) = vgpu_fence_sz(vgpu); -+ -+ vgpu_vreg_t(vgpu, vgtif_reg(cursor_x_hot)) = UINT_MAX; -+ vgpu_vreg_t(vgpu, vgtif_reg(cursor_y_hot)) = UINT_MAX; -+ -+ gvt_dbg_core("Populate PVINFO PAGE for vGPU %d\n", vgpu->id); -+ gvt_dbg_core("aperture base [GMADR] 0x%llx size 0x%llx\n", -+ vgpu_aperture_gmadr_base(vgpu), vgpu_aperture_sz(vgpu)); -+ gvt_dbg_core("hidden base [GMADR] 0x%llx size=0x%llx\n", -+ vgpu_hidden_gmadr_base(vgpu), vgpu_hidden_sz(vgpu)); -+ gvt_dbg_core("fence size %d\n", vgpu_fence_sz(vgpu)); -+ -+ WARN_ON(sizeof(struct vgt_if) != VGT_PVINFO_SIZE); -+} -+ -+#define VGPU_MAX_WEIGHT 16 -+#define VGPU_WEIGHT(vgpu_num) \ -+ (VGPU_MAX_WEIGHT / (vgpu_num)) -+ -+static struct { -+ unsigned int low_mm; -+ unsigned int high_mm; -+ unsigned int fence; -+ -+ /* A vGPU with a weight of 8 will get twice as much GPU as a vGPU -+ * with a weight of 4 on a contended host, different vGPU type has -+ * different weight set. Legal weights range from 1 to 16. -+ */ -+ unsigned int weight; -+ enum intel_vgpu_edid edid; -+ char *name; -+} vgpu_types[] = { -+/* Fixed vGPU type table */ -+ { MB_TO_BYTES(64), MB_TO_BYTES(384), 4, VGPU_WEIGHT(8), GVT_EDID_1024_768, "8" }, -+ { MB_TO_BYTES(128), MB_TO_BYTES(512), 4, VGPU_WEIGHT(4), GVT_EDID_1920_1200, "4" }, -+ { MB_TO_BYTES(256), MB_TO_BYTES(1024), 4, VGPU_WEIGHT(2), GVT_EDID_1920_1200, "2" }, -+ { MB_TO_BYTES(512), MB_TO_BYTES(2048), 4, VGPU_WEIGHT(1), GVT_EDID_1920_1200, "1" }, -+}; -+ -+/** -+ * intel_gvt_init_vgpu_types - initialize vGPU type list -+ * @gvt : GVT device -+ * -+ * Initialize vGPU type list based on available resource. -+ * -+ */ -+int intel_gvt_init_vgpu_types(struct intel_gvt *gvt) -+{ -+ unsigned int num_types; -+ unsigned int i, low_avail, high_avail; -+ unsigned int min_low; -+ -+ /* vGPU type name is defined as GVTg_Vx_y which contains -+ * physical GPU generation type (e.g V4 as BDW server, V5 as -+ * SKL server). -+ * -+ * Depend on physical SKU resource, might see vGPU types like -+ * GVTg_V4_8, GVTg_V4_4, GVTg_V4_2, etc. We can create -+ * different types of vGPU on same physical GPU depending on -+ * available resource. Each vGPU type will have "avail_instance" -+ * to indicate how many vGPU instance can be created for this -+ * type. -+ * -+ */ -+ low_avail = gvt_aperture_sz(gvt) - HOST_LOW_GM_SIZE; -+ high_avail = gvt_hidden_sz(gvt) - HOST_HIGH_GM_SIZE; -+ num_types = sizeof(vgpu_types) / sizeof(vgpu_types[0]); -+ -+ gvt->types = kcalloc(num_types, sizeof(struct intel_vgpu_type), -+ GFP_KERNEL); -+ if (!gvt->types) -+ return -ENOMEM; -+ -+ min_low = MB_TO_BYTES(32); -+ for (i = 0; i < num_types; ++i) { -+ if (low_avail / vgpu_types[i].low_mm == 0) -+ break; -+ -+ gvt->types[i].low_gm_size = vgpu_types[i].low_mm; -+ gvt->types[i].high_gm_size = vgpu_types[i].high_mm; -+ gvt->types[i].fence = vgpu_types[i].fence; -+ -+ if (vgpu_types[i].weight < 1 || -+ vgpu_types[i].weight > VGPU_MAX_WEIGHT) -+ return -EINVAL; -+ -+ gvt->types[i].weight = vgpu_types[i].weight; -+ gvt->types[i].resolution = vgpu_types[i].edid; -+ gvt->types[i].avail_instance = min(low_avail / vgpu_types[i].low_mm, -+ high_avail / vgpu_types[i].high_mm); -+ -+ if (IS_GEN(gvt->dev_priv, 8)) -+ sprintf(gvt->types[i].name, "GVTg_V4_%s", -+ vgpu_types[i].name); -+ else if (IS_GEN(gvt->dev_priv, 9)) -+ sprintf(gvt->types[i].name, "GVTg_V5_%s", -+ vgpu_types[i].name); -+ -+ gvt_dbg_core("type[%d]: %s avail %u low %u high %u fence %u weight %u res %s\n", -+ i, gvt->types[i].name, -+ gvt->types[i].avail_instance, -+ gvt->types[i].low_gm_size, -+ gvt->types[i].high_gm_size, gvt->types[i].fence, -+ gvt->types[i].weight, -+ vgpu_edid_str(gvt->types[i].resolution)); -+ } -+ -+ gvt->num_types = i; -+ return 0; -+} -+ -+void intel_gvt_clean_vgpu_types(struct intel_gvt *gvt) -+{ -+ kfree(gvt->types); -+} -+ -+static void intel_gvt_update_vgpu_types(struct intel_gvt *gvt) -+{ -+ int i; -+ unsigned int low_gm_avail, high_gm_avail, fence_avail; -+ unsigned int low_gm_min, high_gm_min, fence_min; -+ -+ /* Need to depend on maxium hw resource size but keep on -+ * static config for now. -+ */ -+ low_gm_avail = gvt_aperture_sz(gvt) - HOST_LOW_GM_SIZE - -+ gvt->gm.vgpu_allocated_low_gm_size; -+ high_gm_avail = gvt_hidden_sz(gvt) - HOST_HIGH_GM_SIZE - -+ gvt->gm.vgpu_allocated_high_gm_size; -+ fence_avail = gvt_fence_sz(gvt) - HOST_FENCE - -+ gvt->fence.vgpu_allocated_fence_num; -+ -+ for (i = 0; i < gvt->num_types; i++) { -+ low_gm_min = low_gm_avail / gvt->types[i].low_gm_size; -+ high_gm_min = high_gm_avail / gvt->types[i].high_gm_size; -+ fence_min = fence_avail / gvt->types[i].fence; -+ gvt->types[i].avail_instance = min(min(low_gm_min, high_gm_min), -+ fence_min); -+ -+ gvt_dbg_core("update type[%d]: %s avail %u low %u high %u fence %u\n", -+ i, gvt->types[i].name, -+ gvt->types[i].avail_instance, gvt->types[i].low_gm_size, -+ gvt->types[i].high_gm_size, gvt->types[i].fence); -+ } -+} -+ -+/** -+ * intel_gvt_active_vgpu - activate a virtual GPU -+ * @vgpu: virtual GPU -+ * -+ * This function is called when user wants to activate a virtual GPU. -+ * -+ */ -+void intel_gvt_activate_vgpu(struct intel_vgpu *vgpu) -+{ -+ mutex_lock(&vgpu->gvt->lock); -+ vgpu->active = true; -+ mutex_unlock(&vgpu->gvt->lock); -+} -+ -+/** -+ * intel_gvt_deactive_vgpu - deactivate a virtual GPU -+ * @vgpu: virtual GPU -+ * -+ * This function is called when user wants to deactivate a virtual GPU. -+ * The virtual GPU will be stopped. -+ * -+ */ -+void intel_gvt_deactivate_vgpu(struct intel_vgpu *vgpu) -+{ -+ mutex_lock(&vgpu->vgpu_lock); -+ -+ vgpu->active = false; -+ -+ if (atomic_read(&vgpu->submission.running_workload_num)) { -+ mutex_unlock(&vgpu->vgpu_lock); -+ intel_gvt_wait_vgpu_idle(vgpu); -+ mutex_lock(&vgpu->vgpu_lock); -+ } -+ -+ intel_vgpu_stop_schedule(vgpu); -+ -+ mutex_unlock(&vgpu->vgpu_lock); -+} -+ -+/** -+ * intel_gvt_release_vgpu - release a virtual GPU -+ * @vgpu: virtual GPU -+ * -+ * This function is called when user wants to release a virtual GPU. -+ * The virtual GPU will be stopped and all runtime information will be -+ * destroyed. -+ * -+ */ -+void intel_gvt_release_vgpu(struct intel_vgpu *vgpu) -+{ -+ intel_gvt_deactivate_vgpu(vgpu); -+ -+ mutex_lock(&vgpu->vgpu_lock); -+ intel_vgpu_clean_workloads(vgpu, ALL_ENGINES); -+ intel_vgpu_dmabuf_cleanup(vgpu); -+ mutex_unlock(&vgpu->vgpu_lock); -+} -+ -+/** -+ * intel_gvt_destroy_vgpu - destroy a virtual GPU -+ * @vgpu: virtual GPU -+ * -+ * This function is called when user wants to destroy a virtual GPU. -+ * -+ */ -+void intel_gvt_destroy_vgpu(struct intel_vgpu *vgpu) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ -+ mutex_lock(&vgpu->vgpu_lock); -+ -+ WARN(vgpu->active, "vGPU is still active!\n"); -+ -+ intel_gvt_debugfs_remove_vgpu(vgpu); -+ intel_vgpu_clean_sched_policy(vgpu); -+ intel_vgpu_clean_submission(vgpu); -+ intel_vgpu_clean_display(vgpu); -+ intel_vgpu_clean_opregion(vgpu); -+ intel_vgpu_reset_ggtt(vgpu, true); -+ intel_vgpu_clean_gtt(vgpu); -+ intel_gvt_hypervisor_detach_vgpu(vgpu); -+ intel_vgpu_free_resource(vgpu); -+ intel_vgpu_clean_mmio(vgpu); -+ intel_vgpu_dmabuf_cleanup(vgpu); -+ mutex_unlock(&vgpu->vgpu_lock); -+ -+ mutex_lock(&gvt->lock); -+ idr_remove(&gvt->vgpu_idr, vgpu->id); -+ if (idr_is_empty(&gvt->vgpu_idr)) -+ intel_gvt_clean_irq(gvt); -+ intel_gvt_update_vgpu_types(gvt); -+ mutex_unlock(&gvt->lock); -+ -+ vfree(vgpu); -+} -+ -+#define IDLE_VGPU_IDR 0 -+ -+/** -+ * intel_gvt_create_idle_vgpu - create an idle virtual GPU -+ * @gvt: GVT device -+ * -+ * This function is called when user wants to create an idle virtual GPU. -+ * -+ * Returns: -+ * pointer to intel_vgpu, error pointer if failed. -+ */ -+struct intel_vgpu *intel_gvt_create_idle_vgpu(struct intel_gvt *gvt) -+{ -+ struct intel_vgpu *vgpu; -+ enum intel_engine_id i; -+ int ret; -+ -+ vgpu = vzalloc(sizeof(*vgpu)); -+ if (!vgpu) -+ return ERR_PTR(-ENOMEM); -+ -+ vgpu->id = IDLE_VGPU_IDR; -+ vgpu->gvt = gvt; -+ mutex_init(&vgpu->vgpu_lock); -+ -+ for (i = 0; i < I915_NUM_ENGINES; i++) -+ INIT_LIST_HEAD(&vgpu->submission.workload_q_head[i]); -+ -+ ret = intel_vgpu_init_sched_policy(vgpu); -+ if (ret) -+ goto out_free_vgpu; -+ -+ vgpu->active = false; -+ -+ return vgpu; -+ -+out_free_vgpu: -+ vfree(vgpu); -+ return ERR_PTR(ret); -+} -+ -+/** -+ * intel_gvt_destroy_vgpu - destroy an idle virtual GPU -+ * @vgpu: virtual GPU -+ * -+ * This function is called when user wants to destroy an idle virtual GPU. -+ * -+ */ -+void intel_gvt_destroy_idle_vgpu(struct intel_vgpu *vgpu) -+{ -+ mutex_lock(&vgpu->vgpu_lock); -+ intel_vgpu_clean_sched_policy(vgpu); -+ mutex_unlock(&vgpu->vgpu_lock); -+ -+ vfree(vgpu); -+} -+ -+static struct intel_vgpu *__intel_gvt_create_vgpu(struct intel_gvt *gvt, -+ struct intel_vgpu_creation_params *param) -+{ -+ struct intel_vgpu *vgpu; -+ int ret; -+ -+ gvt_dbg_core("handle %llu low %llu MB high %llu MB fence %llu\n", -+ param->handle, param->low_gm_sz, param->high_gm_sz, -+ param->fence_sz); -+ -+ vgpu = vzalloc(sizeof(*vgpu)); -+ if (!vgpu) -+ return ERR_PTR(-ENOMEM); -+ -+ ret = idr_alloc(&gvt->vgpu_idr, vgpu, IDLE_VGPU_IDR + 1, GVT_MAX_VGPU, -+ GFP_KERNEL); -+ if (ret < 0) -+ goto out_free_vgpu; -+ -+ vgpu->id = ret; -+ vgpu->handle = param->handle; -+ vgpu->gvt = gvt; -+ vgpu->sched_ctl.weight = param->weight; -+ mutex_init(&vgpu->vgpu_lock); -+ mutex_init(&vgpu->dmabuf_lock); -+ INIT_LIST_HEAD(&vgpu->dmabuf_obj_list_head); -+ INIT_RADIX_TREE(&vgpu->page_track_tree, GFP_KERNEL); -+ idr_init(&vgpu->object_idr); -+ intel_vgpu_init_cfg_space(vgpu, param->primary); -+ -+ ret = intel_vgpu_init_mmio(vgpu); -+ if (ret) -+ goto out_clean_idr; -+ -+ ret = intel_vgpu_alloc_resource(vgpu, param); -+ if (ret) -+ goto out_clean_vgpu_mmio; -+ -+ populate_pvinfo_page(vgpu); -+ -+ ret = intel_gvt_hypervisor_attach_vgpu(vgpu); -+ if (ret) -+ goto out_clean_vgpu_resource; -+ -+ ret = intel_vgpu_init_gtt(vgpu); -+ if (ret) -+ goto out_detach_hypervisor_vgpu; -+ -+ ret = intel_vgpu_init_opregion(vgpu); -+ if (ret) -+ goto out_clean_gtt; -+ -+ ret = intel_vgpu_init_display(vgpu, param->resolution); -+ if (ret) -+ goto out_clean_opregion; -+ -+ ret = intel_vgpu_setup_submission(vgpu); -+ if (ret) -+ goto out_clean_display; -+ -+ ret = intel_vgpu_init_sched_policy(vgpu); -+ if (ret) -+ goto out_clean_submission; -+ -+ ret = intel_gvt_debugfs_add_vgpu(vgpu); -+ if (ret) -+ goto out_clean_sched_policy; -+ -+ ret = intel_gvt_hypervisor_set_opregion(vgpu); -+ if (ret) -+ goto out_clean_sched_policy; -+ -+ /*TODO: add more platforms support */ -+ if (IS_SKYLAKE(gvt->dev_priv) || IS_KABYLAKE(gvt->dev_priv)) -+ ret = intel_gvt_hypervisor_set_edid(vgpu, PORT_D); -+ if (ret) -+ goto out_clean_sched_policy; -+ -+ return vgpu; -+ -+out_clean_sched_policy: -+ intel_vgpu_clean_sched_policy(vgpu); -+out_clean_submission: -+ intel_vgpu_clean_submission(vgpu); -+out_clean_display: -+ intel_vgpu_clean_display(vgpu); -+out_clean_opregion: -+ intel_vgpu_clean_opregion(vgpu); -+out_clean_gtt: -+ intel_vgpu_clean_gtt(vgpu); -+out_detach_hypervisor_vgpu: -+ intel_gvt_hypervisor_detach_vgpu(vgpu); -+out_clean_vgpu_resource: -+ intel_vgpu_free_resource(vgpu); -+out_clean_vgpu_mmio: -+ intel_vgpu_clean_mmio(vgpu); -+out_clean_idr: -+ idr_remove(&gvt->vgpu_idr, vgpu->id); -+out_free_vgpu: -+ vfree(vgpu); -+ return ERR_PTR(ret); -+} -+ -+/** -+ * intel_gvt_create_vgpu - create a virtual GPU -+ * @gvt: GVT device -+ * @type: type of the vGPU to create -+ * -+ * This function is called when user wants to create a virtual GPU. -+ * -+ * Returns: -+ * pointer to intel_vgpu, error pointer if failed. -+ */ -+struct intel_vgpu *intel_gvt_create_vgpu(struct intel_gvt *gvt, -+ struct intel_vgpu_type *type) -+{ -+ struct intel_vgpu_creation_params param; -+ struct intel_vgpu *vgpu; -+ -+ param.handle = 0; -+ param.primary = 1; -+ param.low_gm_sz = type->low_gm_size; -+ param.high_gm_sz = type->high_gm_size; -+ param.fence_sz = type->fence; -+ param.weight = type->weight; -+ param.resolution = type->resolution; -+ -+ /* XXX current param based on MB */ -+ param.low_gm_sz = BYTES_TO_MB(param.low_gm_sz); -+ param.high_gm_sz = BYTES_TO_MB(param.high_gm_sz); -+ -+ mutex_lock(&gvt->lock); -+ vgpu = __intel_gvt_create_vgpu(gvt, ¶m); -+ if (!IS_ERR(vgpu)) -+ /* calculate left instance change for types */ -+ intel_gvt_update_vgpu_types(gvt); -+ mutex_unlock(&gvt->lock); -+ -+ return vgpu; -+} -+ -+/** -+ * intel_gvt_reset_vgpu_locked - reset a virtual GPU by DMLR or GT reset -+ * @vgpu: virtual GPU -+ * @dmlr: vGPU Device Model Level Reset or GT Reset -+ * @engine_mask: engines to reset for GT reset -+ * -+ * This function is called when user wants to reset a virtual GPU through -+ * device model reset or GT reset. The caller should hold the vgpu lock. -+ * -+ * vGPU Device Model Level Reset (DMLR) simulates the PCI level reset to reset -+ * the whole vGPU to default state as when it is created. This vGPU function -+ * is required both for functionary and security concerns.The ultimate goal -+ * of vGPU FLR is that reuse a vGPU instance by virtual machines. When we -+ * assign a vGPU to a virtual machine we must isse such reset first. -+ * -+ * Full GT Reset and Per-Engine GT Reset are soft reset flow for GPU engines -+ * (Render, Blitter, Video, Video Enhancement). It is defined by GPU Spec. -+ * Unlike the FLR, GT reset only reset particular resource of a vGPU per -+ * the reset request. Guest driver can issue a GT reset by programming the -+ * virtual GDRST register to reset specific virtual GPU engine or all -+ * engines. -+ * -+ * The parameter dev_level is to identify if we will do DMLR or GT reset. -+ * The parameter engine_mask is to specific the engines that need to be -+ * resetted. If value ALL_ENGINES is given for engine_mask, it means -+ * the caller requests a full GT reset that we will reset all virtual -+ * GPU engines. For FLR, engine_mask is ignored. -+ */ -+void intel_gvt_reset_vgpu_locked(struct intel_vgpu *vgpu, bool dmlr, -+ intel_engine_mask_t engine_mask) -+{ -+ struct intel_gvt *gvt = vgpu->gvt; -+ struct intel_gvt_workload_scheduler *scheduler = &gvt->scheduler; -+ intel_engine_mask_t resetting_eng = dmlr ? ALL_ENGINES : engine_mask; -+ -+ gvt_dbg_core("------------------------------------------\n"); -+ gvt_dbg_core("resseting vgpu%d, dmlr %d, engine_mask %08x\n", -+ vgpu->id, dmlr, engine_mask); -+ -+ vgpu->resetting_eng = resetting_eng; -+ -+ intel_vgpu_stop_schedule(vgpu); -+ /* -+ * The current_vgpu will set to NULL after stopping the -+ * scheduler when the reset is triggered by current vgpu. -+ */ -+ if (scheduler->current_vgpu == NULL) { -+ mutex_unlock(&vgpu->vgpu_lock); -+ intel_gvt_wait_vgpu_idle(vgpu); -+ mutex_lock(&vgpu->vgpu_lock); -+ } -+ -+ intel_vgpu_reset_submission(vgpu, resetting_eng); -+ /* full GPU reset or device model level reset */ -+ if (engine_mask == ALL_ENGINES || dmlr) { -+ intel_vgpu_select_submission_ops(vgpu, ALL_ENGINES, 0); -+ intel_vgpu_invalidate_ppgtt(vgpu); -+ /*fence will not be reset during virtual reset */ -+ if (dmlr) { -+ intel_vgpu_reset_gtt(vgpu); -+ intel_vgpu_reset_resource(vgpu); -+ } -+ -+ intel_vgpu_reset_mmio(vgpu, dmlr); -+ populate_pvinfo_page(vgpu); -+ intel_vgpu_reset_display(vgpu); -+ -+ if (dmlr) { -+ intel_vgpu_reset_cfg_space(vgpu); -+ /* only reset the failsafe mode when dmlr reset */ -+ vgpu->failsafe = false; -+ vgpu->pv_notified = false; -+ } -+ } -+ -+ vgpu->resetting_eng = 0; -+ gvt_dbg_core("reset vgpu%d done\n", vgpu->id); -+ gvt_dbg_core("------------------------------------------\n"); -+} -+ -+/** -+ * intel_gvt_reset_vgpu - reset a virtual GPU (Function Level) -+ * @vgpu: virtual GPU -+ * -+ * This function is called when user wants to reset a virtual GPU. -+ * -+ */ -+void intel_gvt_reset_vgpu(struct intel_vgpu *vgpu) -+{ -+ mutex_lock(&vgpu->vgpu_lock); -+ intel_gvt_reset_vgpu_locked(vgpu, true, 0); -+ mutex_unlock(&vgpu->vgpu_lock); -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_active.c b/drivers/gpu/drm/i915_legacy/i915_active.c -new file mode 100644 -index 000000000000..863ae12707ba ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_active.c -@@ -0,0 +1,313 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#include "i915_drv.h" -+#include "i915_active.h" -+#include "i915_globals.h" -+ -+#define BKL(ref) (&(ref)->i915->drm.struct_mutex) -+ -+/* -+ * Active refs memory management -+ * -+ * To be more economical with memory, we reap all the i915_active trees as -+ * they idle (when we know the active requests are inactive) and allocate the -+ * nodes from a local slab cache to hopefully reduce the fragmentation. -+ */ -+static struct i915_global_active { -+ struct i915_global base; -+ struct kmem_cache *slab_cache; -+} global; -+ -+struct active_node { -+ struct i915_active_request base; -+ struct i915_active *ref; -+ struct rb_node node; -+ u64 timeline; -+}; -+ -+static void -+__active_park(struct i915_active *ref) -+{ -+ struct active_node *it, *n; -+ -+ rbtree_postorder_for_each_entry_safe(it, n, &ref->tree, node) { -+ GEM_BUG_ON(i915_active_request_isset(&it->base)); -+ kmem_cache_free(global.slab_cache, it); -+ } -+ ref->tree = RB_ROOT; -+} -+ -+static void -+__active_retire(struct i915_active *ref) -+{ -+ GEM_BUG_ON(!ref->count); -+ if (--ref->count) -+ return; -+ -+ /* return the unused nodes to our slabcache */ -+ __active_park(ref); -+ -+ ref->retire(ref); -+} -+ -+static void -+node_retire(struct i915_active_request *base, struct i915_request *rq) -+{ -+ __active_retire(container_of(base, struct active_node, base)->ref); -+} -+ -+static void -+last_retire(struct i915_active_request *base, struct i915_request *rq) -+{ -+ __active_retire(container_of(base, struct i915_active, last)); -+} -+ -+static struct i915_active_request * -+active_instance(struct i915_active *ref, u64 idx) -+{ -+ struct active_node *node; -+ struct rb_node **p, *parent; -+ struct i915_request *old; -+ -+ /* -+ * We track the most recently used timeline to skip a rbtree search -+ * for the common case, under typical loads we never need the rbtree -+ * at all. We can reuse the last slot if it is empty, that is -+ * after the previous activity has been retired, or if it matches the -+ * current timeline. -+ * -+ * Note that we allow the timeline to be active simultaneously in -+ * the rbtree and the last cache. We do this to avoid having -+ * to search and replace the rbtree element for a new timeline, with -+ * the cost being that we must be aware that the ref may be retired -+ * twice for the same timeline (as the older rbtree element will be -+ * retired before the new request added to last). -+ */ -+ old = i915_active_request_raw(&ref->last, BKL(ref)); -+ if (!old || old->fence.context == idx) -+ goto out; -+ -+ /* Move the currently active fence into the rbtree */ -+ idx = old->fence.context; -+ -+ parent = NULL; -+ p = &ref->tree.rb_node; -+ while (*p) { -+ parent = *p; -+ -+ node = rb_entry(parent, struct active_node, node); -+ if (node->timeline == idx) -+ goto replace; -+ -+ if (node->timeline < idx) -+ p = &parent->rb_right; -+ else -+ p = &parent->rb_left; -+ } -+ -+ node = kmem_cache_alloc(global.slab_cache, GFP_KERNEL); -+ -+ /* kmalloc may retire the ref->last (thanks shrinker)! */ -+ if (unlikely(!i915_active_request_raw(&ref->last, BKL(ref)))) { -+ kmem_cache_free(global.slab_cache, node); -+ goto out; -+ } -+ -+ if (unlikely(!node)) -+ return ERR_PTR(-ENOMEM); -+ -+ i915_active_request_init(&node->base, NULL, node_retire); -+ node->ref = ref; -+ node->timeline = idx; -+ -+ rb_link_node(&node->node, parent, p); -+ rb_insert_color(&node->node, &ref->tree); -+ -+replace: -+ /* -+ * Overwrite the previous active slot in the rbtree with last, -+ * leaving last zeroed. If the previous slot is still active, -+ * we must be careful as we now only expect to receive one retire -+ * callback not two, and so much undo the active counting for the -+ * overwritten slot. -+ */ -+ if (i915_active_request_isset(&node->base)) { -+ /* Retire ourselves from the old rq->active_list */ -+ __list_del_entry(&node->base.link); -+ ref->count--; -+ GEM_BUG_ON(!ref->count); -+ } -+ GEM_BUG_ON(list_empty(&ref->last.link)); -+ list_replace_init(&ref->last.link, &node->base.link); -+ node->base.request = fetch_and_zero(&ref->last.request); -+ -+out: -+ return &ref->last; -+} -+ -+void i915_active_init(struct drm_i915_private *i915, -+ struct i915_active *ref, -+ void (*retire)(struct i915_active *ref)) -+{ -+ ref->i915 = i915; -+ ref->retire = retire; -+ ref->tree = RB_ROOT; -+ i915_active_request_init(&ref->last, NULL, last_retire); -+ ref->count = 0; -+} -+ -+int i915_active_ref(struct i915_active *ref, -+ u64 timeline, -+ struct i915_request *rq) -+{ -+ struct i915_active_request *active; -+ int err = 0; -+ -+ /* Prevent reaping in case we malloc/wait while building the tree */ -+ i915_active_acquire(ref); -+ -+ active = active_instance(ref, timeline); -+ if (IS_ERR(active)) { -+ err = PTR_ERR(active); -+ goto out; -+ } -+ -+ if (!i915_active_request_isset(active)) -+ ref->count++; -+ __i915_active_request_set(active, rq); -+ -+ GEM_BUG_ON(!ref->count); -+out: -+ i915_active_release(ref); -+ return err; -+} -+ -+bool i915_active_acquire(struct i915_active *ref) -+{ -+ lockdep_assert_held(BKL(ref)); -+ return !ref->count++; -+} -+ -+void i915_active_release(struct i915_active *ref) -+{ -+ lockdep_assert_held(BKL(ref)); -+ __active_retire(ref); -+} -+ -+int i915_active_wait(struct i915_active *ref) -+{ -+ struct active_node *it, *n; -+ int ret = 0; -+ -+ if (i915_active_acquire(ref)) -+ goto out_release; -+ -+ ret = i915_active_request_retire(&ref->last, BKL(ref)); -+ if (ret) -+ goto out_release; -+ -+ rbtree_postorder_for_each_entry_safe(it, n, &ref->tree, node) { -+ ret = i915_active_request_retire(&it->base, BKL(ref)); -+ if (ret) -+ break; -+ } -+ -+out_release: -+ i915_active_release(ref); -+ return ret; -+} -+ -+int i915_request_await_active_request(struct i915_request *rq, -+ struct i915_active_request *active) -+{ -+ struct i915_request *barrier = -+ i915_active_request_raw(active, &rq->i915->drm.struct_mutex); -+ -+ return barrier ? i915_request_await_dma_fence(rq, &barrier->fence) : 0; -+} -+ -+int i915_request_await_active(struct i915_request *rq, struct i915_active *ref) -+{ -+ struct active_node *it, *n; -+ int err = 0; -+ -+ /* await allocates and so we need to avoid hitting the shrinker */ -+ if (i915_active_acquire(ref)) -+ goto out; /* was idle */ -+ -+ err = i915_request_await_active_request(rq, &ref->last); -+ if (err) -+ goto out; -+ -+ rbtree_postorder_for_each_entry_safe(it, n, &ref->tree, node) { -+ err = i915_request_await_active_request(rq, &it->base); -+ if (err) -+ goto out; -+ } -+ -+out: -+ i915_active_release(ref); -+ return err; -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM) -+void i915_active_fini(struct i915_active *ref) -+{ -+ GEM_BUG_ON(i915_active_request_isset(&ref->last)); -+ GEM_BUG_ON(!RB_EMPTY_ROOT(&ref->tree)); -+ GEM_BUG_ON(ref->count); -+} -+#endif -+ -+int i915_active_request_set(struct i915_active_request *active, -+ struct i915_request *rq) -+{ -+ int err; -+ -+ /* Must maintain ordering wrt previous active requests */ -+ err = i915_request_await_active_request(rq, active); -+ if (err) -+ return err; -+ -+ __i915_active_request_set(active, rq); -+ return 0; -+} -+ -+void i915_active_retire_noop(struct i915_active_request *active, -+ struct i915_request *request) -+{ -+ /* Space left intentionally blank */ -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -+#include "selftests/i915_active.c" -+#endif -+ -+static void i915_global_active_shrink(void) -+{ -+ kmem_cache_shrink(global.slab_cache); -+} -+ -+static void i915_global_active_exit(void) -+{ -+ kmem_cache_destroy(global.slab_cache); -+} -+ -+static struct i915_global_active global = { { -+ .shrink = i915_global_active_shrink, -+ .exit = i915_global_active_exit, -+} }; -+ -+int __init i915_global_active_init(void) -+{ -+ global.slab_cache = KMEM_CACHE(active_node, SLAB_HWCACHE_ALIGN); -+ if (!global.slab_cache) -+ return -ENOMEM; -+ -+ i915_global_register(&global.base); -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_active.h b/drivers/gpu/drm/i915_legacy/i915_active.h -new file mode 100644 -index 000000000000..7d758719ce39 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_active.h -@@ -0,0 +1,409 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef _I915_ACTIVE_H_ -+#define _I915_ACTIVE_H_ -+ -+#include -+ -+#include "i915_active_types.h" -+#include "i915_request.h" -+ -+/* -+ * We treat requests as fences. This is not be to confused with our -+ * "fence registers" but pipeline synchronisation objects ala GL_ARB_sync. -+ * We use the fences to synchronize access from the CPU with activity on the -+ * GPU, for example, we should not rewrite an object's PTE whilst the GPU -+ * is reading them. We also track fences at a higher level to provide -+ * implicit synchronisation around GEM objects, e.g. set-domain will wait -+ * for outstanding GPU rendering before marking the object ready for CPU -+ * access, or a pageflip will wait until the GPU is complete before showing -+ * the frame on the scanout. -+ * -+ * In order to use a fence, the object must track the fence it needs to -+ * serialise with. For example, GEM objects want to track both read and -+ * write access so that we can perform concurrent read operations between -+ * the CPU and GPU engines, as well as waiting for all rendering to -+ * complete, or waiting for the last GPU user of a "fence register". The -+ * object then embeds a #i915_active_request to track the most recent (in -+ * retirement order) request relevant for the desired mode of access. -+ * The #i915_active_request is updated with i915_active_request_set() to -+ * track the most recent fence request, typically this is done as part of -+ * i915_vma_move_to_active(). -+ * -+ * When the #i915_active_request completes (is retired), it will -+ * signal its completion to the owner through a callback as well as mark -+ * itself as idle (i915_active_request.request == NULL). The owner -+ * can then perform any action, such as delayed freeing of an active -+ * resource including itself. -+ */ -+ -+void i915_active_retire_noop(struct i915_active_request *active, -+ struct i915_request *request); -+ -+/** -+ * i915_active_request_init - prepares the activity tracker for use -+ * @active - the active tracker -+ * @rq - initial request to track, can be NULL -+ * @func - a callback when then the tracker is retired (becomes idle), -+ * can be NULL -+ * -+ * i915_active_request_init() prepares the embedded @active struct for use as -+ * an activity tracker, that is for tracking the last known active request -+ * associated with it. When the last request becomes idle, when it is retired -+ * after completion, the optional callback @func is invoked. -+ */ -+static inline void -+i915_active_request_init(struct i915_active_request *active, -+ struct i915_request *rq, -+ i915_active_retire_fn retire) -+{ -+ RCU_INIT_POINTER(active->request, rq); -+ INIT_LIST_HEAD(&active->link); -+ active->retire = retire ?: i915_active_retire_noop; -+} -+ -+#define INIT_ACTIVE_REQUEST(name) i915_active_request_init((name), NULL, NULL) -+ -+/** -+ * i915_active_request_set - updates the tracker to watch the current request -+ * @active - the active tracker -+ * @request - the request to watch -+ * -+ * __i915_active_request_set() watches the given @request for completion. Whilst -+ * that @request is busy, the @active reports busy. When that @request is -+ * retired, the @active tracker is updated to report idle. -+ */ -+static inline void -+__i915_active_request_set(struct i915_active_request *active, -+ struct i915_request *request) -+{ -+ list_move(&active->link, &request->active_list); -+ rcu_assign_pointer(active->request, request); -+} -+ -+int __must_check -+i915_active_request_set(struct i915_active_request *active, -+ struct i915_request *rq); -+ -+/** -+ * i915_active_request_set_retire_fn - updates the retirement callback -+ * @active - the active tracker -+ * @fn - the routine called when the request is retired -+ * @mutex - struct_mutex used to guard retirements -+ * -+ * i915_active_request_set_retire_fn() updates the function pointer that -+ * is called when the final request associated with the @active tracker -+ * is retired. -+ */ -+static inline void -+i915_active_request_set_retire_fn(struct i915_active_request *active, -+ i915_active_retire_fn fn, -+ struct mutex *mutex) -+{ -+ lockdep_assert_held(mutex); -+ active->retire = fn ?: i915_active_retire_noop; -+} -+ -+/** -+ * i915_active_request_raw - return the active request -+ * @active - the active tracker -+ * -+ * i915_active_request_raw() returns the current request being tracked, or NULL. -+ * It does not obtain a reference on the request for the caller, so the caller -+ * must hold struct_mutex. -+ */ -+static inline struct i915_request * -+i915_active_request_raw(const struct i915_active_request *active, -+ struct mutex *mutex) -+{ -+ return rcu_dereference_protected(active->request, -+ lockdep_is_held(mutex)); -+} -+ -+/** -+ * i915_active_request_peek - report the active request being monitored -+ * @active - the active tracker -+ * -+ * i915_active_request_peek() returns the current request being tracked if -+ * still active, or NULL. It does not obtain a reference on the request -+ * for the caller, so the caller must hold struct_mutex. -+ */ -+static inline struct i915_request * -+i915_active_request_peek(const struct i915_active_request *active, -+ struct mutex *mutex) -+{ -+ struct i915_request *request; -+ -+ request = i915_active_request_raw(active, mutex); -+ if (!request || i915_request_completed(request)) -+ return NULL; -+ -+ return request; -+} -+ -+/** -+ * i915_active_request_get - return a reference to the active request -+ * @active - the active tracker -+ * -+ * i915_active_request_get() returns a reference to the active request, or NULL -+ * if the active tracker is idle. The caller must hold struct_mutex. -+ */ -+static inline struct i915_request * -+i915_active_request_get(const struct i915_active_request *active, -+ struct mutex *mutex) -+{ -+ return i915_request_get(i915_active_request_peek(active, mutex)); -+} -+ -+/** -+ * __i915_active_request_get_rcu - return a reference to the active request -+ * @active - the active tracker -+ * -+ * __i915_active_request_get() returns a reference to the active request, -+ * or NULL if the active tracker is idle. The caller must hold the RCU read -+ * lock, but the returned pointer is safe to use outside of RCU. -+ */ -+static inline struct i915_request * -+__i915_active_request_get_rcu(const struct i915_active_request *active) -+{ -+ /* -+ * Performing a lockless retrieval of the active request is super -+ * tricky. SLAB_TYPESAFE_BY_RCU merely guarantees that the backing -+ * slab of request objects will not be freed whilst we hold the -+ * RCU read lock. It does not guarantee that the request itself -+ * will not be freed and then *reused*. Viz, -+ * -+ * Thread A Thread B -+ * -+ * rq = active.request -+ * retire(rq) -> free(rq); -+ * (rq is now first on the slab freelist) -+ * active.request = NULL -+ * -+ * rq = new submission on a new object -+ * ref(rq) -+ * -+ * To prevent the request from being reused whilst the caller -+ * uses it, we take a reference like normal. Whilst acquiring -+ * the reference we check that it is not in a destroyed state -+ * (refcnt == 0). That prevents the request being reallocated -+ * whilst the caller holds on to it. To check that the request -+ * was not reallocated as we acquired the reference we have to -+ * check that our request remains the active request across -+ * the lookup, in the same manner as a seqlock. The visibility -+ * of the pointer versus the reference counting is controlled -+ * by using RCU barriers (rcu_dereference and rcu_assign_pointer). -+ * -+ * In the middle of all that, we inspect whether the request is -+ * complete. Retiring is lazy so the request may be completed long -+ * before the active tracker is updated. Querying whether the -+ * request is complete is far cheaper (as it involves no locked -+ * instructions setting cachelines to exclusive) than acquiring -+ * the reference, so we do it first. The RCU read lock ensures the -+ * pointer dereference is valid, but does not ensure that the -+ * seqno nor HWS is the right one! However, if the request was -+ * reallocated, that means the active tracker's request was complete. -+ * If the new request is also complete, then both are and we can -+ * just report the active tracker is idle. If the new request is -+ * incomplete, then we acquire a reference on it and check that -+ * it remained the active request. -+ * -+ * It is then imperative that we do not zero the request on -+ * reallocation, so that we can chase the dangling pointers! -+ * See i915_request_alloc(). -+ */ -+ do { -+ struct i915_request *request; -+ -+ request = rcu_dereference(active->request); -+ if (!request || i915_request_completed(request)) -+ return NULL; -+ -+ /* -+ * An especially silly compiler could decide to recompute the -+ * result of i915_request_completed, more specifically -+ * re-emit the load for request->fence.seqno. A race would catch -+ * a later seqno value, which could flip the result from true to -+ * false. Which means part of the instructions below might not -+ * be executed, while later on instructions are executed. Due to -+ * barriers within the refcounting the inconsistency can't reach -+ * past the call to i915_request_get_rcu, but not executing -+ * that while still executing i915_request_put() creates -+ * havoc enough. Prevent this with a compiler barrier. -+ */ -+ barrier(); -+ -+ request = i915_request_get_rcu(request); -+ -+ /* -+ * What stops the following rcu_access_pointer() from occurring -+ * before the above i915_request_get_rcu()? If we were -+ * to read the value before pausing to get the reference to -+ * the request, we may not notice a change in the active -+ * tracker. -+ * -+ * The rcu_access_pointer() is a mere compiler barrier, which -+ * means both the CPU and compiler are free to perform the -+ * memory read without constraint. The compiler only has to -+ * ensure that any operations after the rcu_access_pointer() -+ * occur afterwards in program order. This means the read may -+ * be performed earlier by an out-of-order CPU, or adventurous -+ * compiler. -+ * -+ * The atomic operation at the heart of -+ * i915_request_get_rcu(), see dma_fence_get_rcu(), is -+ * atomic_inc_not_zero() which is only a full memory barrier -+ * when successful. That is, if i915_request_get_rcu() -+ * returns the request (and so with the reference counted -+ * incremented) then the following read for rcu_access_pointer() -+ * must occur after the atomic operation and so confirm -+ * that this request is the one currently being tracked. -+ * -+ * The corresponding write barrier is part of -+ * rcu_assign_pointer(). -+ */ -+ if (!request || request == rcu_access_pointer(active->request)) -+ return rcu_pointer_handoff(request); -+ -+ i915_request_put(request); -+ } while (1); -+} -+ -+/** -+ * i915_active_request_get_unlocked - return a reference to the active request -+ * @active - the active tracker -+ * -+ * i915_active_request_get_unlocked() returns a reference to the active request, -+ * or NULL if the active tracker is idle. The reference is obtained under RCU, -+ * so no locking is required by the caller. -+ * -+ * The reference should be freed with i915_request_put(). -+ */ -+static inline struct i915_request * -+i915_active_request_get_unlocked(const struct i915_active_request *active) -+{ -+ struct i915_request *request; -+ -+ rcu_read_lock(); -+ request = __i915_active_request_get_rcu(active); -+ rcu_read_unlock(); -+ -+ return request; -+} -+ -+/** -+ * i915_active_request_isset - report whether the active tracker is assigned -+ * @active - the active tracker -+ * -+ * i915_active_request_isset() returns true if the active tracker is currently -+ * assigned to a request. Due to the lazy retiring, that request may be idle -+ * and this may report stale information. -+ */ -+static inline bool -+i915_active_request_isset(const struct i915_active_request *active) -+{ -+ return rcu_access_pointer(active->request); -+} -+ -+/** -+ * i915_active_request_retire - waits until the request is retired -+ * @active - the active request on which to wait -+ * -+ * i915_active_request_retire() waits until the request is completed, -+ * and then ensures that at least the retirement handler for this -+ * @active tracker is called before returning. If the @active -+ * tracker is idle, the function returns immediately. -+ */ -+static inline int __must_check -+i915_active_request_retire(struct i915_active_request *active, -+ struct mutex *mutex) -+{ -+ struct i915_request *request; -+ long ret; -+ -+ request = i915_active_request_raw(active, mutex); -+ if (!request) -+ return 0; -+ -+ ret = i915_request_wait(request, -+ I915_WAIT_INTERRUPTIBLE | I915_WAIT_LOCKED, -+ MAX_SCHEDULE_TIMEOUT); -+ if (ret < 0) -+ return ret; -+ -+ list_del_init(&active->link); -+ RCU_INIT_POINTER(active->request, NULL); -+ -+ active->retire(active, request); -+ -+ return 0; -+} -+ -+/* -+ * GPU activity tracking -+ * -+ * Each set of commands submitted to the GPU compromises a single request that -+ * signals a fence upon completion. struct i915_request combines the -+ * command submission, scheduling and fence signaling roles. If we want to see -+ * if a particular task is complete, we need to grab the fence (struct -+ * i915_request) for that task and check or wait for it to be signaled. More -+ * often though we want to track the status of a bunch of tasks, for example -+ * to wait for the GPU to finish accessing some memory across a variety of -+ * different command pipelines from different clients. We could choose to -+ * track every single request associated with the task, but knowing that -+ * each request belongs to an ordered timeline (later requests within a -+ * timeline must wait for earlier requests), we need only track the -+ * latest request in each timeline to determine the overall status of the -+ * task. -+ * -+ * struct i915_active provides this tracking across timelines. It builds a -+ * composite shared-fence, and is updated as new work is submitted to the task, -+ * forming a snapshot of the current status. It should be embedded into the -+ * different resources that need to track their associated GPU activity to -+ * provide a callback when that GPU activity has ceased, or otherwise to -+ * provide a serialisation point either for request submission or for CPU -+ * synchronisation. -+ */ -+ -+void i915_active_init(struct drm_i915_private *i915, -+ struct i915_active *ref, -+ void (*retire)(struct i915_active *ref)); -+ -+int i915_active_ref(struct i915_active *ref, -+ u64 timeline, -+ struct i915_request *rq); -+ -+int i915_active_wait(struct i915_active *ref); -+ -+int i915_request_await_active(struct i915_request *rq, -+ struct i915_active *ref); -+int i915_request_await_active_request(struct i915_request *rq, -+ struct i915_active_request *active); -+ -+bool i915_active_acquire(struct i915_active *ref); -+ -+static inline void i915_active_cancel(struct i915_active *ref) -+{ -+ GEM_BUG_ON(ref->count != 1); -+ ref->count = 0; -+} -+ -+void i915_active_release(struct i915_active *ref); -+ -+static inline bool -+i915_active_is_idle(const struct i915_active *ref) -+{ -+ return !ref->count; -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM) -+void i915_active_fini(struct i915_active *ref); -+#else -+static inline void i915_active_fini(struct i915_active *ref) { } -+#endif -+ -+#endif /* _I915_ACTIVE_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_active_types.h b/drivers/gpu/drm/i915_legacy/i915_active_types.h -new file mode 100644 -index 000000000000..b679253b53a5 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_active_types.h -@@ -0,0 +1,36 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef _I915_ACTIVE_TYPES_H_ -+#define _I915_ACTIVE_TYPES_H_ -+ -+#include -+#include -+ -+struct drm_i915_private; -+struct i915_active_request; -+struct i915_request; -+ -+typedef void (*i915_active_retire_fn)(struct i915_active_request *, -+ struct i915_request *); -+ -+struct i915_active_request { -+ struct i915_request __rcu *request; -+ struct list_head link; -+ i915_active_retire_fn retire; -+}; -+ -+struct i915_active { -+ struct drm_i915_private *i915; -+ -+ struct rb_root tree; -+ struct i915_active_request last; -+ unsigned int count; -+ -+ void (*retire)(struct i915_active *ref); -+}; -+ -+#endif /* _I915_ACTIVE_TYPES_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_cmd_parser.c b/drivers/gpu/drm/i915_legacy/i915_cmd_parser.c -new file mode 100644 -index 000000000000..503d548a55f7 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_cmd_parser.c -@@ -0,0 +1,1387 @@ -+/* -+ * Copyright © 2013 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Brad Volkin -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "intel_ringbuffer.h" -+ -+/** -+ * DOC: batch buffer command parser -+ * -+ * Motivation: -+ * Certain OpenGL features (e.g. transform feedback, performance monitoring) -+ * require userspace code to submit batches containing commands such as -+ * MI_LOAD_REGISTER_IMM to access various registers. Unfortunately, some -+ * generations of the hardware will noop these commands in "unsecure" batches -+ * (which includes all userspace batches submitted via i915) even though the -+ * commands may be safe and represent the intended programming model of the -+ * device. -+ * -+ * The software command parser is similar in operation to the command parsing -+ * done in hardware for unsecure batches. However, the software parser allows -+ * some operations that would be noop'd by hardware, if the parser determines -+ * the operation is safe, and submits the batch as "secure" to prevent hardware -+ * parsing. -+ * -+ * Threats: -+ * At a high level, the hardware (and software) checks attempt to prevent -+ * granting userspace undue privileges. There are three categories of privilege. -+ * -+ * First, commands which are explicitly defined as privileged or which should -+ * only be used by the kernel driver. The parser generally rejects such -+ * commands, though it may allow some from the drm master process. -+ * -+ * Second, commands which access registers. To support correct/enhanced -+ * userspace functionality, particularly certain OpenGL extensions, the parser -+ * provides a whitelist of registers which userspace may safely access (for both -+ * normal and drm master processes). -+ * -+ * Third, commands which access privileged memory (i.e. GGTT, HWS page, etc). -+ * The parser always rejects such commands. -+ * -+ * The majority of the problematic commands fall in the MI_* range, with only a -+ * few specific commands on each engine (e.g. PIPE_CONTROL and MI_FLUSH_DW). -+ * -+ * Implementation: -+ * Each engine maintains tables of commands and registers which the parser -+ * uses in scanning batch buffers submitted to that engine. -+ * -+ * Since the set of commands that the parser must check for is significantly -+ * smaller than the number of commands supported, the parser tables contain only -+ * those commands required by the parser. This generally works because command -+ * opcode ranges have standard command length encodings. So for commands that -+ * the parser does not need to check, it can easily skip them. This is -+ * implemented via a per-engine length decoding vfunc. -+ * -+ * Unfortunately, there are a number of commands that do not follow the standard -+ * length encoding for their opcode range, primarily amongst the MI_* commands. -+ * To handle this, the parser provides a way to define explicit "skip" entries -+ * in the per-engine command tables. -+ * -+ * Other command table entries map fairly directly to high level categories -+ * mentioned above: rejected, master-only, register whitelist. The parser -+ * implements a number of checks, including the privileged memory checks, via a -+ * general bitmasking mechanism. -+ */ -+ -+/* -+ * A command that requires special handling by the command parser. -+ */ -+struct drm_i915_cmd_descriptor { -+ /* -+ * Flags describing how the command parser processes the command. -+ * -+ * CMD_DESC_FIXED: The command has a fixed length if this is set, -+ * a length mask if not set -+ * CMD_DESC_SKIP: The command is allowed but does not follow the -+ * standard length encoding for the opcode range in -+ * which it falls -+ * CMD_DESC_REJECT: The command is never allowed -+ * CMD_DESC_REGISTER: The command should be checked against the -+ * register whitelist for the appropriate ring -+ * CMD_DESC_MASTER: The command is allowed if the submitting process -+ * is the DRM master -+ */ -+ u32 flags; -+#define CMD_DESC_FIXED (1<<0) -+#define CMD_DESC_SKIP (1<<1) -+#define CMD_DESC_REJECT (1<<2) -+#define CMD_DESC_REGISTER (1<<3) -+#define CMD_DESC_BITMASK (1<<4) -+#define CMD_DESC_MASTER (1<<5) -+ -+ /* -+ * The command's unique identification bits and the bitmask to get them. -+ * This isn't strictly the opcode field as defined in the spec and may -+ * also include type, subtype, and/or subop fields. -+ */ -+ struct { -+ u32 value; -+ u32 mask; -+ } cmd; -+ -+ /* -+ * The command's length. The command is either fixed length (i.e. does -+ * not include a length field) or has a length field mask. The flag -+ * CMD_DESC_FIXED indicates a fixed length. Otherwise, the command has -+ * a length mask. All command entries in a command table must include -+ * length information. -+ */ -+ union { -+ u32 fixed; -+ u32 mask; -+ } length; -+ -+ /* -+ * Describes where to find a register address in the command to check -+ * against the ring's register whitelist. Only valid if flags has the -+ * CMD_DESC_REGISTER bit set. -+ * -+ * A non-zero step value implies that the command may access multiple -+ * registers in sequence (e.g. LRI), in that case step gives the -+ * distance in dwords between individual offset fields. -+ */ -+ struct { -+ u32 offset; -+ u32 mask; -+ u32 step; -+ } reg; -+ -+#define MAX_CMD_DESC_BITMASKS 3 -+ /* -+ * Describes command checks where a particular dword is masked and -+ * compared against an expected value. If the command does not match -+ * the expected value, the parser rejects it. Only valid if flags has -+ * the CMD_DESC_BITMASK bit set. Only entries where mask is non-zero -+ * are valid. -+ * -+ * If the check specifies a non-zero condition_mask then the parser -+ * only performs the check when the bits specified by condition_mask -+ * are non-zero. -+ */ -+ struct { -+ u32 offset; -+ u32 mask; -+ u32 expected; -+ u32 condition_offset; -+ u32 condition_mask; -+ } bits[MAX_CMD_DESC_BITMASKS]; -+}; -+ -+/* -+ * A table of commands requiring special handling by the command parser. -+ * -+ * Each engine has an array of tables. Each table consists of an array of -+ * command descriptors, which must be sorted with command opcodes in -+ * ascending order. -+ */ -+struct drm_i915_cmd_table { -+ const struct drm_i915_cmd_descriptor *table; -+ int count; -+}; -+ -+#define STD_MI_OPCODE_SHIFT (32 - 9) -+#define STD_3D_OPCODE_SHIFT (32 - 16) -+#define STD_2D_OPCODE_SHIFT (32 - 10) -+#define STD_MFX_OPCODE_SHIFT (32 - 16) -+#define MIN_OPCODE_SHIFT 16 -+ -+#define CMD(op, opm, f, lm, fl, ...) \ -+ { \ -+ .flags = (fl) | ((f) ? CMD_DESC_FIXED : 0), \ -+ .cmd = { (op), ~0u << (opm) }, \ -+ .length = { (lm) }, \ -+ __VA_ARGS__ \ -+ } -+ -+/* Convenience macros to compress the tables */ -+#define SMI STD_MI_OPCODE_SHIFT -+#define S3D STD_3D_OPCODE_SHIFT -+#define S2D STD_2D_OPCODE_SHIFT -+#define SMFX STD_MFX_OPCODE_SHIFT -+#define F true -+#define S CMD_DESC_SKIP -+#define R CMD_DESC_REJECT -+#define W CMD_DESC_REGISTER -+#define B CMD_DESC_BITMASK -+#define M CMD_DESC_MASTER -+ -+/* Command Mask Fixed Len Action -+ ---------------------------------------------------------- */ -+static const struct drm_i915_cmd_descriptor common_cmds[] = { -+ CMD( MI_NOOP, SMI, F, 1, S ), -+ CMD( MI_USER_INTERRUPT, SMI, F, 1, R ), -+ CMD( MI_WAIT_FOR_EVENT, SMI, F, 1, M ), -+ CMD( MI_ARB_CHECK, SMI, F, 1, S ), -+ CMD( MI_REPORT_HEAD, SMI, F, 1, S ), -+ CMD( MI_SUSPEND_FLUSH, SMI, F, 1, S ), -+ CMD( MI_SEMAPHORE_MBOX, SMI, !F, 0xFF, R ), -+ CMD( MI_STORE_DWORD_INDEX, SMI, !F, 0xFF, R ), -+ CMD( MI_LOAD_REGISTER_IMM(1), SMI, !F, 0xFF, W, -+ .reg = { .offset = 1, .mask = 0x007FFFFC, .step = 2 } ), -+ CMD( MI_STORE_REGISTER_MEM, SMI, F, 3, W | B, -+ .reg = { .offset = 1, .mask = 0x007FFFFC }, -+ .bits = {{ -+ .offset = 0, -+ .mask = MI_GLOBAL_GTT, -+ .expected = 0, -+ }}, ), -+ CMD( MI_LOAD_REGISTER_MEM, SMI, F, 3, W | B, -+ .reg = { .offset = 1, .mask = 0x007FFFFC }, -+ .bits = {{ -+ .offset = 0, -+ .mask = MI_GLOBAL_GTT, -+ .expected = 0, -+ }}, ), -+ /* -+ * MI_BATCH_BUFFER_START requires some special handling. It's not -+ * really a 'skip' action but it doesn't seem like it's worth adding -+ * a new action. See i915_parse_cmds(). -+ */ -+ CMD( MI_BATCH_BUFFER_START, SMI, !F, 0xFF, S ), -+}; -+ -+static const struct drm_i915_cmd_descriptor render_cmds[] = { -+ CMD( MI_FLUSH, SMI, F, 1, S ), -+ CMD( MI_ARB_ON_OFF, SMI, F, 1, R ), -+ CMD( MI_PREDICATE, SMI, F, 1, S ), -+ CMD( MI_TOPOLOGY_FILTER, SMI, F, 1, S ), -+ CMD( MI_SET_APPID, SMI, F, 1, S ), -+ CMD( MI_DISPLAY_FLIP, SMI, !F, 0xFF, R ), -+ CMD( MI_SET_CONTEXT, SMI, !F, 0xFF, R ), -+ CMD( MI_URB_CLEAR, SMI, !F, 0xFF, S ), -+ CMD( MI_STORE_DWORD_IMM, SMI, !F, 0x3F, B, -+ .bits = {{ -+ .offset = 0, -+ .mask = MI_GLOBAL_GTT, -+ .expected = 0, -+ }}, ), -+ CMD( MI_UPDATE_GTT, SMI, !F, 0xFF, R ), -+ CMD( MI_CLFLUSH, SMI, !F, 0x3FF, B, -+ .bits = {{ -+ .offset = 0, -+ .mask = MI_GLOBAL_GTT, -+ .expected = 0, -+ }}, ), -+ CMD( MI_REPORT_PERF_COUNT, SMI, !F, 0x3F, B, -+ .bits = {{ -+ .offset = 1, -+ .mask = MI_REPORT_PERF_COUNT_GGTT, -+ .expected = 0, -+ }}, ), -+ CMD( MI_CONDITIONAL_BATCH_BUFFER_END, SMI, !F, 0xFF, B, -+ .bits = {{ -+ .offset = 0, -+ .mask = MI_GLOBAL_GTT, -+ .expected = 0, -+ }}, ), -+ CMD( GFX_OP_3DSTATE_VF_STATISTICS, S3D, F, 1, S ), -+ CMD( PIPELINE_SELECT, S3D, F, 1, S ), -+ CMD( MEDIA_VFE_STATE, S3D, !F, 0xFFFF, B, -+ .bits = {{ -+ .offset = 2, -+ .mask = MEDIA_VFE_STATE_MMIO_ACCESS_MASK, -+ .expected = 0, -+ }}, ), -+ CMD( GPGPU_OBJECT, S3D, !F, 0xFF, S ), -+ CMD( GPGPU_WALKER, S3D, !F, 0xFF, S ), -+ CMD( GFX_OP_3DSTATE_SO_DECL_LIST, S3D, !F, 0x1FF, S ), -+ CMD( GFX_OP_PIPE_CONTROL(5), S3D, !F, 0xFF, B, -+ .bits = {{ -+ .offset = 1, -+ .mask = (PIPE_CONTROL_MMIO_WRITE | PIPE_CONTROL_NOTIFY), -+ .expected = 0, -+ }, -+ { -+ .offset = 1, -+ .mask = (PIPE_CONTROL_GLOBAL_GTT_IVB | -+ PIPE_CONTROL_STORE_DATA_INDEX), -+ .expected = 0, -+ .condition_offset = 1, -+ .condition_mask = PIPE_CONTROL_POST_SYNC_OP_MASK, -+ }}, ), -+}; -+ -+static const struct drm_i915_cmd_descriptor hsw_render_cmds[] = { -+ CMD( MI_SET_PREDICATE, SMI, F, 1, S ), -+ CMD( MI_RS_CONTROL, SMI, F, 1, S ), -+ CMD( MI_URB_ATOMIC_ALLOC, SMI, F, 1, S ), -+ CMD( MI_SET_APPID, SMI, F, 1, S ), -+ CMD( MI_RS_CONTEXT, SMI, F, 1, S ), -+ CMD( MI_LOAD_SCAN_LINES_INCL, SMI, !F, 0x3F, M ), -+ CMD( MI_LOAD_SCAN_LINES_EXCL, SMI, !F, 0x3F, R ), -+ CMD( MI_LOAD_REGISTER_REG, SMI, !F, 0xFF, W, -+ .reg = { .offset = 1, .mask = 0x007FFFFC, .step = 1 } ), -+ CMD( MI_RS_STORE_DATA_IMM, SMI, !F, 0xFF, S ), -+ CMD( MI_LOAD_URB_MEM, SMI, !F, 0xFF, S ), -+ CMD( MI_STORE_URB_MEM, SMI, !F, 0xFF, S ), -+ CMD( GFX_OP_3DSTATE_DX9_CONSTANTF_VS, S3D, !F, 0x7FF, S ), -+ CMD( GFX_OP_3DSTATE_DX9_CONSTANTF_PS, S3D, !F, 0x7FF, S ), -+ -+ CMD( GFX_OP_3DSTATE_BINDING_TABLE_EDIT_VS, S3D, !F, 0x1FF, S ), -+ CMD( GFX_OP_3DSTATE_BINDING_TABLE_EDIT_GS, S3D, !F, 0x1FF, S ), -+ CMD( GFX_OP_3DSTATE_BINDING_TABLE_EDIT_HS, S3D, !F, 0x1FF, S ), -+ CMD( GFX_OP_3DSTATE_BINDING_TABLE_EDIT_DS, S3D, !F, 0x1FF, S ), -+ CMD( GFX_OP_3DSTATE_BINDING_TABLE_EDIT_PS, S3D, !F, 0x1FF, S ), -+}; -+ -+static const struct drm_i915_cmd_descriptor video_cmds[] = { -+ CMD( MI_ARB_ON_OFF, SMI, F, 1, R ), -+ CMD( MI_SET_APPID, SMI, F, 1, S ), -+ CMD( MI_STORE_DWORD_IMM, SMI, !F, 0xFF, B, -+ .bits = {{ -+ .offset = 0, -+ .mask = MI_GLOBAL_GTT, -+ .expected = 0, -+ }}, ), -+ CMD( MI_UPDATE_GTT, SMI, !F, 0x3F, R ), -+ CMD( MI_FLUSH_DW, SMI, !F, 0x3F, B, -+ .bits = {{ -+ .offset = 0, -+ .mask = MI_FLUSH_DW_NOTIFY, -+ .expected = 0, -+ }, -+ { -+ .offset = 1, -+ .mask = MI_FLUSH_DW_USE_GTT, -+ .expected = 0, -+ .condition_offset = 0, -+ .condition_mask = MI_FLUSH_DW_OP_MASK, -+ }, -+ { -+ .offset = 0, -+ .mask = MI_FLUSH_DW_STORE_INDEX, -+ .expected = 0, -+ .condition_offset = 0, -+ .condition_mask = MI_FLUSH_DW_OP_MASK, -+ }}, ), -+ CMD( MI_CONDITIONAL_BATCH_BUFFER_END, SMI, !F, 0xFF, B, -+ .bits = {{ -+ .offset = 0, -+ .mask = MI_GLOBAL_GTT, -+ .expected = 0, -+ }}, ), -+ /* -+ * MFX_WAIT doesn't fit the way we handle length for most commands. -+ * It has a length field but it uses a non-standard length bias. -+ * It is always 1 dword though, so just treat it as fixed length. -+ */ -+ CMD( MFX_WAIT, SMFX, F, 1, S ), -+}; -+ -+static const struct drm_i915_cmd_descriptor vecs_cmds[] = { -+ CMD( MI_ARB_ON_OFF, SMI, F, 1, R ), -+ CMD( MI_SET_APPID, SMI, F, 1, S ), -+ CMD( MI_STORE_DWORD_IMM, SMI, !F, 0xFF, B, -+ .bits = {{ -+ .offset = 0, -+ .mask = MI_GLOBAL_GTT, -+ .expected = 0, -+ }}, ), -+ CMD( MI_UPDATE_GTT, SMI, !F, 0x3F, R ), -+ CMD( MI_FLUSH_DW, SMI, !F, 0x3F, B, -+ .bits = {{ -+ .offset = 0, -+ .mask = MI_FLUSH_DW_NOTIFY, -+ .expected = 0, -+ }, -+ { -+ .offset = 1, -+ .mask = MI_FLUSH_DW_USE_GTT, -+ .expected = 0, -+ .condition_offset = 0, -+ .condition_mask = MI_FLUSH_DW_OP_MASK, -+ }, -+ { -+ .offset = 0, -+ .mask = MI_FLUSH_DW_STORE_INDEX, -+ .expected = 0, -+ .condition_offset = 0, -+ .condition_mask = MI_FLUSH_DW_OP_MASK, -+ }}, ), -+ CMD( MI_CONDITIONAL_BATCH_BUFFER_END, SMI, !F, 0xFF, B, -+ .bits = {{ -+ .offset = 0, -+ .mask = MI_GLOBAL_GTT, -+ .expected = 0, -+ }}, ), -+}; -+ -+static const struct drm_i915_cmd_descriptor blt_cmds[] = { -+ CMD( MI_DISPLAY_FLIP, SMI, !F, 0xFF, R ), -+ CMD( MI_STORE_DWORD_IMM, SMI, !F, 0x3FF, B, -+ .bits = {{ -+ .offset = 0, -+ .mask = MI_GLOBAL_GTT, -+ .expected = 0, -+ }}, ), -+ CMD( MI_UPDATE_GTT, SMI, !F, 0x3F, R ), -+ CMD( MI_FLUSH_DW, SMI, !F, 0x3F, B, -+ .bits = {{ -+ .offset = 0, -+ .mask = MI_FLUSH_DW_NOTIFY, -+ .expected = 0, -+ }, -+ { -+ .offset = 1, -+ .mask = MI_FLUSH_DW_USE_GTT, -+ .expected = 0, -+ .condition_offset = 0, -+ .condition_mask = MI_FLUSH_DW_OP_MASK, -+ }, -+ { -+ .offset = 0, -+ .mask = MI_FLUSH_DW_STORE_INDEX, -+ .expected = 0, -+ .condition_offset = 0, -+ .condition_mask = MI_FLUSH_DW_OP_MASK, -+ }}, ), -+ CMD( COLOR_BLT, S2D, !F, 0x3F, S ), -+ CMD( SRC_COPY_BLT, S2D, !F, 0x3F, S ), -+}; -+ -+static const struct drm_i915_cmd_descriptor hsw_blt_cmds[] = { -+ CMD( MI_LOAD_SCAN_LINES_INCL, SMI, !F, 0x3F, M ), -+ CMD( MI_LOAD_SCAN_LINES_EXCL, SMI, !F, 0x3F, R ), -+}; -+ -+static const struct drm_i915_cmd_descriptor noop_desc = -+ CMD(MI_NOOP, SMI, F, 1, S); -+ -+#undef CMD -+#undef SMI -+#undef S3D -+#undef S2D -+#undef SMFX -+#undef F -+#undef S -+#undef R -+#undef W -+#undef B -+#undef M -+ -+static const struct drm_i915_cmd_table gen7_render_cmds[] = { -+ { common_cmds, ARRAY_SIZE(common_cmds) }, -+ { render_cmds, ARRAY_SIZE(render_cmds) }, -+}; -+ -+static const struct drm_i915_cmd_table hsw_render_ring_cmds[] = { -+ { common_cmds, ARRAY_SIZE(common_cmds) }, -+ { render_cmds, ARRAY_SIZE(render_cmds) }, -+ { hsw_render_cmds, ARRAY_SIZE(hsw_render_cmds) }, -+}; -+ -+static const struct drm_i915_cmd_table gen7_video_cmds[] = { -+ { common_cmds, ARRAY_SIZE(common_cmds) }, -+ { video_cmds, ARRAY_SIZE(video_cmds) }, -+}; -+ -+static const struct drm_i915_cmd_table hsw_vebox_cmds[] = { -+ { common_cmds, ARRAY_SIZE(common_cmds) }, -+ { vecs_cmds, ARRAY_SIZE(vecs_cmds) }, -+}; -+ -+static const struct drm_i915_cmd_table gen7_blt_cmds[] = { -+ { common_cmds, ARRAY_SIZE(common_cmds) }, -+ { blt_cmds, ARRAY_SIZE(blt_cmds) }, -+}; -+ -+static const struct drm_i915_cmd_table hsw_blt_ring_cmds[] = { -+ { common_cmds, ARRAY_SIZE(common_cmds) }, -+ { blt_cmds, ARRAY_SIZE(blt_cmds) }, -+ { hsw_blt_cmds, ARRAY_SIZE(hsw_blt_cmds) }, -+}; -+ -+/* -+ * Register whitelists, sorted by increasing register offset. -+ */ -+ -+/* -+ * An individual whitelist entry granting access to register addr. If -+ * mask is non-zero the argument of immediate register writes will be -+ * AND-ed with mask, and the command will be rejected if the result -+ * doesn't match value. -+ * -+ * Registers with non-zero mask are only allowed to be written using -+ * LRI. -+ */ -+struct drm_i915_reg_descriptor { -+ i915_reg_t addr; -+ u32 mask; -+ u32 value; -+}; -+ -+/* Convenience macro for adding 32-bit registers. */ -+#define REG32(_reg, ...) \ -+ { .addr = (_reg), __VA_ARGS__ } -+ -+/* -+ * Convenience macro for adding 64-bit registers. -+ * -+ * Some registers that userspace accesses are 64 bits. The register -+ * access commands only allow 32-bit accesses. Hence, we have to include -+ * entries for both halves of the 64-bit registers. -+ */ -+#define REG64(_reg) \ -+ { .addr = _reg }, \ -+ { .addr = _reg ## _UDW } -+ -+#define REG64_IDX(_reg, idx) \ -+ { .addr = _reg(idx) }, \ -+ { .addr = _reg ## _UDW(idx) } -+ -+static const struct drm_i915_reg_descriptor gen7_render_regs[] = { -+ REG64(GPGPU_THREADS_DISPATCHED), -+ REG64(HS_INVOCATION_COUNT), -+ REG64(DS_INVOCATION_COUNT), -+ REG64(IA_VERTICES_COUNT), -+ REG64(IA_PRIMITIVES_COUNT), -+ REG64(VS_INVOCATION_COUNT), -+ REG64(GS_INVOCATION_COUNT), -+ REG64(GS_PRIMITIVES_COUNT), -+ REG64(CL_INVOCATION_COUNT), -+ REG64(CL_PRIMITIVES_COUNT), -+ REG64(PS_INVOCATION_COUNT), -+ REG64(PS_DEPTH_COUNT), -+ REG64_IDX(RING_TIMESTAMP, RENDER_RING_BASE), -+ REG64(MI_PREDICATE_SRC0), -+ REG64(MI_PREDICATE_SRC1), -+ REG32(GEN7_3DPRIM_END_OFFSET), -+ REG32(GEN7_3DPRIM_START_VERTEX), -+ REG32(GEN7_3DPRIM_VERTEX_COUNT), -+ REG32(GEN7_3DPRIM_INSTANCE_COUNT), -+ REG32(GEN7_3DPRIM_START_INSTANCE), -+ REG32(GEN7_3DPRIM_BASE_VERTEX), -+ REG32(GEN7_GPGPU_DISPATCHDIMX), -+ REG32(GEN7_GPGPU_DISPATCHDIMY), -+ REG32(GEN7_GPGPU_DISPATCHDIMZ), -+ REG64_IDX(RING_TIMESTAMP, BSD_RING_BASE), -+ REG64_IDX(GEN7_SO_NUM_PRIMS_WRITTEN, 0), -+ REG64_IDX(GEN7_SO_NUM_PRIMS_WRITTEN, 1), -+ REG64_IDX(GEN7_SO_NUM_PRIMS_WRITTEN, 2), -+ REG64_IDX(GEN7_SO_NUM_PRIMS_WRITTEN, 3), -+ REG64_IDX(GEN7_SO_PRIM_STORAGE_NEEDED, 0), -+ REG64_IDX(GEN7_SO_PRIM_STORAGE_NEEDED, 1), -+ REG64_IDX(GEN7_SO_PRIM_STORAGE_NEEDED, 2), -+ REG64_IDX(GEN7_SO_PRIM_STORAGE_NEEDED, 3), -+ REG32(GEN7_SO_WRITE_OFFSET(0)), -+ REG32(GEN7_SO_WRITE_OFFSET(1)), -+ REG32(GEN7_SO_WRITE_OFFSET(2)), -+ REG32(GEN7_SO_WRITE_OFFSET(3)), -+ REG32(GEN7_L3SQCREG1), -+ REG32(GEN7_L3CNTLREG2), -+ REG32(GEN7_L3CNTLREG3), -+ REG64_IDX(RING_TIMESTAMP, BLT_RING_BASE), -+}; -+ -+static const struct drm_i915_reg_descriptor hsw_render_regs[] = { -+ REG64_IDX(HSW_CS_GPR, 0), -+ REG64_IDX(HSW_CS_GPR, 1), -+ REG64_IDX(HSW_CS_GPR, 2), -+ REG64_IDX(HSW_CS_GPR, 3), -+ REG64_IDX(HSW_CS_GPR, 4), -+ REG64_IDX(HSW_CS_GPR, 5), -+ REG64_IDX(HSW_CS_GPR, 6), -+ REG64_IDX(HSW_CS_GPR, 7), -+ REG64_IDX(HSW_CS_GPR, 8), -+ REG64_IDX(HSW_CS_GPR, 9), -+ REG64_IDX(HSW_CS_GPR, 10), -+ REG64_IDX(HSW_CS_GPR, 11), -+ REG64_IDX(HSW_CS_GPR, 12), -+ REG64_IDX(HSW_CS_GPR, 13), -+ REG64_IDX(HSW_CS_GPR, 14), -+ REG64_IDX(HSW_CS_GPR, 15), -+ REG32(HSW_SCRATCH1, -+ .mask = ~HSW_SCRATCH1_L3_DATA_ATOMICS_DISABLE, -+ .value = 0), -+ REG32(HSW_ROW_CHICKEN3, -+ .mask = ~(HSW_ROW_CHICKEN3_L3_GLOBAL_ATOMICS_DISABLE << 16 | -+ HSW_ROW_CHICKEN3_L3_GLOBAL_ATOMICS_DISABLE), -+ .value = 0), -+}; -+ -+static const struct drm_i915_reg_descriptor gen7_blt_regs[] = { -+ REG64_IDX(RING_TIMESTAMP, RENDER_RING_BASE), -+ REG64_IDX(RING_TIMESTAMP, BSD_RING_BASE), -+ REG32(BCS_SWCTRL), -+ REG64_IDX(RING_TIMESTAMP, BLT_RING_BASE), -+}; -+ -+static const struct drm_i915_reg_descriptor ivb_master_regs[] = { -+ REG32(FORCEWAKE_MT), -+ REG32(DERRMR), -+ REG32(GEN7_PIPE_DE_LOAD_SL(PIPE_A)), -+ REG32(GEN7_PIPE_DE_LOAD_SL(PIPE_B)), -+ REG32(GEN7_PIPE_DE_LOAD_SL(PIPE_C)), -+}; -+ -+static const struct drm_i915_reg_descriptor hsw_master_regs[] = { -+ REG32(FORCEWAKE_MT), -+ REG32(DERRMR), -+}; -+ -+#undef REG64 -+#undef REG32 -+ -+struct drm_i915_reg_table { -+ const struct drm_i915_reg_descriptor *regs; -+ int num_regs; -+ bool master; -+}; -+ -+static const struct drm_i915_reg_table ivb_render_reg_tables[] = { -+ { gen7_render_regs, ARRAY_SIZE(gen7_render_regs), false }, -+ { ivb_master_regs, ARRAY_SIZE(ivb_master_regs), true }, -+}; -+ -+static const struct drm_i915_reg_table ivb_blt_reg_tables[] = { -+ { gen7_blt_regs, ARRAY_SIZE(gen7_blt_regs), false }, -+ { ivb_master_regs, ARRAY_SIZE(ivb_master_regs), true }, -+}; -+ -+static const struct drm_i915_reg_table hsw_render_reg_tables[] = { -+ { gen7_render_regs, ARRAY_SIZE(gen7_render_regs), false }, -+ { hsw_render_regs, ARRAY_SIZE(hsw_render_regs), false }, -+ { hsw_master_regs, ARRAY_SIZE(hsw_master_regs), true }, -+}; -+ -+static const struct drm_i915_reg_table hsw_blt_reg_tables[] = { -+ { gen7_blt_regs, ARRAY_SIZE(gen7_blt_regs), false }, -+ { hsw_master_regs, ARRAY_SIZE(hsw_master_regs), true }, -+}; -+ -+static u32 gen7_render_get_cmd_length_mask(u32 cmd_header) -+{ -+ u32 client = cmd_header >> INSTR_CLIENT_SHIFT; -+ u32 subclient = -+ (cmd_header & INSTR_SUBCLIENT_MASK) >> INSTR_SUBCLIENT_SHIFT; -+ -+ if (client == INSTR_MI_CLIENT) -+ return 0x3F; -+ else if (client == INSTR_RC_CLIENT) { -+ if (subclient == INSTR_MEDIA_SUBCLIENT) -+ return 0xFFFF; -+ else -+ return 0xFF; -+ } -+ -+ DRM_DEBUG_DRIVER("CMD: Abnormal rcs cmd length! 0x%08X\n", cmd_header); -+ return 0; -+} -+ -+static u32 gen7_bsd_get_cmd_length_mask(u32 cmd_header) -+{ -+ u32 client = cmd_header >> INSTR_CLIENT_SHIFT; -+ u32 subclient = -+ (cmd_header & INSTR_SUBCLIENT_MASK) >> INSTR_SUBCLIENT_SHIFT; -+ u32 op = (cmd_header & INSTR_26_TO_24_MASK) >> INSTR_26_TO_24_SHIFT; -+ -+ if (client == INSTR_MI_CLIENT) -+ return 0x3F; -+ else if (client == INSTR_RC_CLIENT) { -+ if (subclient == INSTR_MEDIA_SUBCLIENT) { -+ if (op == 6) -+ return 0xFFFF; -+ else -+ return 0xFFF; -+ } else -+ return 0xFF; -+ } -+ -+ DRM_DEBUG_DRIVER("CMD: Abnormal bsd cmd length! 0x%08X\n", cmd_header); -+ return 0; -+} -+ -+static u32 gen7_blt_get_cmd_length_mask(u32 cmd_header) -+{ -+ u32 client = cmd_header >> INSTR_CLIENT_SHIFT; -+ -+ if (client == INSTR_MI_CLIENT) -+ return 0x3F; -+ else if (client == INSTR_BC_CLIENT) -+ return 0xFF; -+ -+ DRM_DEBUG_DRIVER("CMD: Abnormal blt cmd length! 0x%08X\n", cmd_header); -+ return 0; -+} -+ -+static bool validate_cmds_sorted(const struct intel_engine_cs *engine, -+ const struct drm_i915_cmd_table *cmd_tables, -+ int cmd_table_count) -+{ -+ int i; -+ bool ret = true; -+ -+ if (!cmd_tables || cmd_table_count == 0) -+ return true; -+ -+ for (i = 0; i < cmd_table_count; i++) { -+ const struct drm_i915_cmd_table *table = &cmd_tables[i]; -+ u32 previous = 0; -+ int j; -+ -+ for (j = 0; j < table->count; j++) { -+ const struct drm_i915_cmd_descriptor *desc = -+ &table->table[j]; -+ u32 curr = desc->cmd.value & desc->cmd.mask; -+ -+ if (curr < previous) { -+ DRM_ERROR("CMD: %s [%d] command table not sorted: " -+ "table=%d entry=%d cmd=0x%08X prev=0x%08X\n", -+ engine->name, engine->id, -+ i, j, curr, previous); -+ ret = false; -+ } -+ -+ previous = curr; -+ } -+ } -+ -+ return ret; -+} -+ -+static bool check_sorted(const struct intel_engine_cs *engine, -+ const struct drm_i915_reg_descriptor *reg_table, -+ int reg_count) -+{ -+ int i; -+ u32 previous = 0; -+ bool ret = true; -+ -+ for (i = 0; i < reg_count; i++) { -+ u32 curr = i915_mmio_reg_offset(reg_table[i].addr); -+ -+ if (curr < previous) { -+ DRM_ERROR("CMD: %s [%d] register table not sorted: " -+ "entry=%d reg=0x%08X prev=0x%08X\n", -+ engine->name, engine->id, -+ i, curr, previous); -+ ret = false; -+ } -+ -+ previous = curr; -+ } -+ -+ return ret; -+} -+ -+static bool validate_regs_sorted(struct intel_engine_cs *engine) -+{ -+ int i; -+ const struct drm_i915_reg_table *table; -+ -+ for (i = 0; i < engine->reg_table_count; i++) { -+ table = &engine->reg_tables[i]; -+ if (!check_sorted(engine, table->regs, table->num_regs)) -+ return false; -+ } -+ -+ return true; -+} -+ -+struct cmd_node { -+ const struct drm_i915_cmd_descriptor *desc; -+ struct hlist_node node; -+}; -+ -+/* -+ * Different command ranges have different numbers of bits for the opcode. For -+ * example, MI commands use bits 31:23 while 3D commands use bits 31:16. The -+ * problem is that, for example, MI commands use bits 22:16 for other fields -+ * such as GGTT vs PPGTT bits. If we include those bits in the mask then when -+ * we mask a command from a batch it could hash to the wrong bucket due to -+ * non-opcode bits being set. But if we don't include those bits, some 3D -+ * commands may hash to the same bucket due to not including opcode bits that -+ * make the command unique. For now, we will risk hashing to the same bucket. -+ */ -+static inline u32 cmd_header_key(u32 x) -+{ -+ switch (x >> INSTR_CLIENT_SHIFT) { -+ default: -+ case INSTR_MI_CLIENT: -+ return x >> STD_MI_OPCODE_SHIFT; -+ case INSTR_RC_CLIENT: -+ return x >> STD_3D_OPCODE_SHIFT; -+ case INSTR_BC_CLIENT: -+ return x >> STD_2D_OPCODE_SHIFT; -+ } -+} -+ -+static int init_hash_table(struct intel_engine_cs *engine, -+ const struct drm_i915_cmd_table *cmd_tables, -+ int cmd_table_count) -+{ -+ int i, j; -+ -+ hash_init(engine->cmd_hash); -+ -+ for (i = 0; i < cmd_table_count; i++) { -+ const struct drm_i915_cmd_table *table = &cmd_tables[i]; -+ -+ for (j = 0; j < table->count; j++) { -+ const struct drm_i915_cmd_descriptor *desc = -+ &table->table[j]; -+ struct cmd_node *desc_node = -+ kmalloc(sizeof(*desc_node), GFP_KERNEL); -+ -+ if (!desc_node) -+ return -ENOMEM; -+ -+ desc_node->desc = desc; -+ hash_add(engine->cmd_hash, &desc_node->node, -+ cmd_header_key(desc->cmd.value)); -+ } -+ } -+ -+ return 0; -+} -+ -+static void fini_hash_table(struct intel_engine_cs *engine) -+{ -+ struct hlist_node *tmp; -+ struct cmd_node *desc_node; -+ int i; -+ -+ hash_for_each_safe(engine->cmd_hash, i, tmp, desc_node, node) { -+ hash_del(&desc_node->node); -+ kfree(desc_node); -+ } -+} -+ -+/** -+ * intel_engine_init_cmd_parser() - set cmd parser related fields for an engine -+ * @engine: the engine to initialize -+ * -+ * Optionally initializes fields related to batch buffer command parsing in the -+ * struct intel_engine_cs based on whether the platform requires software -+ * command parsing. -+ */ -+void intel_engine_init_cmd_parser(struct intel_engine_cs *engine) -+{ -+ const struct drm_i915_cmd_table *cmd_tables; -+ int cmd_table_count; -+ int ret; -+ -+ if (!IS_GEN(engine->i915, 7)) -+ return; -+ -+ switch (engine->class) { -+ case RENDER_CLASS: -+ if (IS_HASWELL(engine->i915)) { -+ cmd_tables = hsw_render_ring_cmds; -+ cmd_table_count = -+ ARRAY_SIZE(hsw_render_ring_cmds); -+ } else { -+ cmd_tables = gen7_render_cmds; -+ cmd_table_count = ARRAY_SIZE(gen7_render_cmds); -+ } -+ -+ if (IS_HASWELL(engine->i915)) { -+ engine->reg_tables = hsw_render_reg_tables; -+ engine->reg_table_count = ARRAY_SIZE(hsw_render_reg_tables); -+ } else { -+ engine->reg_tables = ivb_render_reg_tables; -+ engine->reg_table_count = ARRAY_SIZE(ivb_render_reg_tables); -+ } -+ -+ engine->get_cmd_length_mask = gen7_render_get_cmd_length_mask; -+ break; -+ case VIDEO_DECODE_CLASS: -+ cmd_tables = gen7_video_cmds; -+ cmd_table_count = ARRAY_SIZE(gen7_video_cmds); -+ engine->get_cmd_length_mask = gen7_bsd_get_cmd_length_mask; -+ break; -+ case COPY_ENGINE_CLASS: -+ if (IS_HASWELL(engine->i915)) { -+ cmd_tables = hsw_blt_ring_cmds; -+ cmd_table_count = ARRAY_SIZE(hsw_blt_ring_cmds); -+ } else { -+ cmd_tables = gen7_blt_cmds; -+ cmd_table_count = ARRAY_SIZE(gen7_blt_cmds); -+ } -+ -+ if (IS_HASWELL(engine->i915)) { -+ engine->reg_tables = hsw_blt_reg_tables; -+ engine->reg_table_count = ARRAY_SIZE(hsw_blt_reg_tables); -+ } else { -+ engine->reg_tables = ivb_blt_reg_tables; -+ engine->reg_table_count = ARRAY_SIZE(ivb_blt_reg_tables); -+ } -+ -+ engine->get_cmd_length_mask = gen7_blt_get_cmd_length_mask; -+ break; -+ case VIDEO_ENHANCEMENT_CLASS: -+ cmd_tables = hsw_vebox_cmds; -+ cmd_table_count = ARRAY_SIZE(hsw_vebox_cmds); -+ /* VECS can use the same length_mask function as VCS */ -+ engine->get_cmd_length_mask = gen7_bsd_get_cmd_length_mask; -+ break; -+ default: -+ MISSING_CASE(engine->class); -+ return; -+ } -+ -+ if (!validate_cmds_sorted(engine, cmd_tables, cmd_table_count)) { -+ DRM_ERROR("%s: command descriptions are not sorted\n", -+ engine->name); -+ return; -+ } -+ if (!validate_regs_sorted(engine)) { -+ DRM_ERROR("%s: registers are not sorted\n", engine->name); -+ return; -+ } -+ -+ ret = init_hash_table(engine, cmd_tables, cmd_table_count); -+ if (ret) { -+ DRM_ERROR("%s: initialised failed!\n", engine->name); -+ fini_hash_table(engine); -+ return; -+ } -+ -+ engine->flags |= I915_ENGINE_NEEDS_CMD_PARSER; -+} -+ -+/** -+ * intel_engine_cleanup_cmd_parser() - clean up cmd parser related fields -+ * @engine: the engine to clean up -+ * -+ * Releases any resources related to command parsing that may have been -+ * initialized for the specified engine. -+ */ -+void intel_engine_cleanup_cmd_parser(struct intel_engine_cs *engine) -+{ -+ if (!intel_engine_needs_cmd_parser(engine)) -+ return; -+ -+ fini_hash_table(engine); -+} -+ -+static const struct drm_i915_cmd_descriptor* -+find_cmd_in_table(struct intel_engine_cs *engine, -+ u32 cmd_header) -+{ -+ struct cmd_node *desc_node; -+ -+ hash_for_each_possible(engine->cmd_hash, desc_node, node, -+ cmd_header_key(cmd_header)) { -+ const struct drm_i915_cmd_descriptor *desc = desc_node->desc; -+ if (((cmd_header ^ desc->cmd.value) & desc->cmd.mask) == 0) -+ return desc; -+ } -+ -+ return NULL; -+} -+ -+/* -+ * Returns a pointer to a descriptor for the command specified by cmd_header. -+ * -+ * The caller must supply space for a default descriptor via the default_desc -+ * parameter. If no descriptor for the specified command exists in the engine's -+ * command parser tables, this function fills in default_desc based on the -+ * engine's default length encoding and returns default_desc. -+ */ -+static const struct drm_i915_cmd_descriptor* -+find_cmd(struct intel_engine_cs *engine, -+ u32 cmd_header, -+ const struct drm_i915_cmd_descriptor *desc, -+ struct drm_i915_cmd_descriptor *default_desc) -+{ -+ u32 mask; -+ -+ if (((cmd_header ^ desc->cmd.value) & desc->cmd.mask) == 0) -+ return desc; -+ -+ desc = find_cmd_in_table(engine, cmd_header); -+ if (desc) -+ return desc; -+ -+ mask = engine->get_cmd_length_mask(cmd_header); -+ if (!mask) -+ return NULL; -+ -+ default_desc->cmd.value = cmd_header; -+ default_desc->cmd.mask = ~0u << MIN_OPCODE_SHIFT; -+ default_desc->length.mask = mask; -+ default_desc->flags = CMD_DESC_SKIP; -+ return default_desc; -+} -+ -+static const struct drm_i915_reg_descriptor * -+__find_reg(const struct drm_i915_reg_descriptor *table, int count, u32 addr) -+{ -+ int start = 0, end = count; -+ while (start < end) { -+ int mid = start + (end - start) / 2; -+ int ret = addr - i915_mmio_reg_offset(table[mid].addr); -+ if (ret < 0) -+ end = mid; -+ else if (ret > 0) -+ start = mid + 1; -+ else -+ return &table[mid]; -+ } -+ return NULL; -+} -+ -+static const struct drm_i915_reg_descriptor * -+find_reg(const struct intel_engine_cs *engine, bool is_master, u32 addr) -+{ -+ const struct drm_i915_reg_table *table = engine->reg_tables; -+ int count = engine->reg_table_count; -+ -+ for (; count > 0; ++table, --count) { -+ if (!table->master || is_master) { -+ const struct drm_i915_reg_descriptor *reg; -+ -+ reg = __find_reg(table->regs, table->num_regs, addr); -+ if (reg != NULL) -+ return reg; -+ } -+ } -+ -+ return NULL; -+} -+ -+/* Returns a vmap'd pointer to dst_obj, which the caller must unmap */ -+static u32 *copy_batch(struct drm_i915_gem_object *dst_obj, -+ struct drm_i915_gem_object *src_obj, -+ u32 batch_start_offset, -+ u32 batch_len, -+ bool *needs_clflush_after) -+{ -+ unsigned int src_needs_clflush; -+ unsigned int dst_needs_clflush; -+ void *dst, *src; -+ int ret; -+ -+ ret = i915_gem_obj_prepare_shmem_read(src_obj, &src_needs_clflush); -+ if (ret) -+ return ERR_PTR(ret); -+ -+ ret = i915_gem_obj_prepare_shmem_write(dst_obj, &dst_needs_clflush); -+ if (ret) { -+ dst = ERR_PTR(ret); -+ goto unpin_src; -+ } -+ -+ dst = i915_gem_object_pin_map(dst_obj, I915_MAP_FORCE_WB); -+ if (IS_ERR(dst)) -+ goto unpin_dst; -+ -+ src = ERR_PTR(-ENODEV); -+ if (src_needs_clflush && -+ i915_can_memcpy_from_wc(NULL, batch_start_offset, 0)) { -+ src = i915_gem_object_pin_map(src_obj, I915_MAP_WC); -+ if (!IS_ERR(src)) { -+ i915_memcpy_from_wc(dst, -+ src + batch_start_offset, -+ ALIGN(batch_len, 16)); -+ i915_gem_object_unpin_map(src_obj); -+ } -+ } -+ if (IS_ERR(src)) { -+ void *ptr; -+ int offset, n; -+ -+ offset = offset_in_page(batch_start_offset); -+ -+ /* We can avoid clflushing partial cachelines before the write -+ * if we only every write full cache-lines. Since we know that -+ * both the source and destination are in multiples of -+ * PAGE_SIZE, we can simply round up to the next cacheline. -+ * We don't care about copying too much here as we only -+ * validate up to the end of the batch. -+ */ -+ if (dst_needs_clflush & CLFLUSH_BEFORE) -+ batch_len = roundup(batch_len, -+ boot_cpu_data.x86_clflush_size); -+ -+ ptr = dst; -+ for (n = batch_start_offset >> PAGE_SHIFT; batch_len; n++) { -+ int len = min_t(int, batch_len, PAGE_SIZE - offset); -+ -+ src = kmap_atomic(i915_gem_object_get_page(src_obj, n)); -+ if (src_needs_clflush) -+ drm_clflush_virt_range(src + offset, len); -+ memcpy(ptr, src + offset, len); -+ kunmap_atomic(src); -+ -+ ptr += len; -+ batch_len -= len; -+ offset = 0; -+ } -+ } -+ -+ /* dst_obj is returned with vmap pinned */ -+ *needs_clflush_after = dst_needs_clflush & CLFLUSH_AFTER; -+ -+unpin_dst: -+ i915_gem_obj_finish_shmem_access(dst_obj); -+unpin_src: -+ i915_gem_obj_finish_shmem_access(src_obj); -+ return dst; -+} -+ -+static bool check_cmd(const struct intel_engine_cs *engine, -+ const struct drm_i915_cmd_descriptor *desc, -+ const u32 *cmd, u32 length, -+ const bool is_master) -+{ -+ if (desc->flags & CMD_DESC_SKIP) -+ return true; -+ -+ if (desc->flags & CMD_DESC_REJECT) { -+ DRM_DEBUG_DRIVER("CMD: Rejected command: 0x%08X\n", *cmd); -+ return false; -+ } -+ -+ if ((desc->flags & CMD_DESC_MASTER) && !is_master) { -+ DRM_DEBUG_DRIVER("CMD: Rejected master-only command: 0x%08X\n", -+ *cmd); -+ return false; -+ } -+ -+ if (desc->flags & CMD_DESC_REGISTER) { -+ /* -+ * Get the distance between individual register offset -+ * fields if the command can perform more than one -+ * access at a time. -+ */ -+ const u32 step = desc->reg.step ? desc->reg.step : length; -+ u32 offset; -+ -+ for (offset = desc->reg.offset; offset < length; -+ offset += step) { -+ const u32 reg_addr = cmd[offset] & desc->reg.mask; -+ const struct drm_i915_reg_descriptor *reg = -+ find_reg(engine, is_master, reg_addr); -+ -+ if (!reg) { -+ DRM_DEBUG_DRIVER("CMD: Rejected register 0x%08X in command: 0x%08X (%s)\n", -+ reg_addr, *cmd, engine->name); -+ return false; -+ } -+ -+ /* -+ * Check the value written to the register against the -+ * allowed mask/value pair given in the whitelist entry. -+ */ -+ if (reg->mask) { -+ if (desc->cmd.value == MI_LOAD_REGISTER_MEM) { -+ DRM_DEBUG_DRIVER("CMD: Rejected LRM to masked register 0x%08X\n", -+ reg_addr); -+ return false; -+ } -+ -+ if (desc->cmd.value == MI_LOAD_REGISTER_REG) { -+ DRM_DEBUG_DRIVER("CMD: Rejected LRR to masked register 0x%08X\n", -+ reg_addr); -+ return false; -+ } -+ -+ if (desc->cmd.value == MI_LOAD_REGISTER_IMM(1) && -+ (offset + 2 > length || -+ (cmd[offset + 1] & reg->mask) != reg->value)) { -+ DRM_DEBUG_DRIVER("CMD: Rejected LRI to masked register 0x%08X\n", -+ reg_addr); -+ return false; -+ } -+ } -+ } -+ } -+ -+ if (desc->flags & CMD_DESC_BITMASK) { -+ int i; -+ -+ for (i = 0; i < MAX_CMD_DESC_BITMASKS; i++) { -+ u32 dword; -+ -+ if (desc->bits[i].mask == 0) -+ break; -+ -+ if (desc->bits[i].condition_mask != 0) { -+ u32 offset = -+ desc->bits[i].condition_offset; -+ u32 condition = cmd[offset] & -+ desc->bits[i].condition_mask; -+ -+ if (condition == 0) -+ continue; -+ } -+ -+ if (desc->bits[i].offset >= length) { -+ DRM_DEBUG_DRIVER("CMD: Rejected command 0x%08X, too short to check bitmask (%s)\n", -+ *cmd, engine->name); -+ return false; -+ } -+ -+ dword = cmd[desc->bits[i].offset] & -+ desc->bits[i].mask; -+ -+ if (dword != desc->bits[i].expected) { -+ DRM_DEBUG_DRIVER("CMD: Rejected command 0x%08X for bitmask 0x%08X (exp=0x%08X act=0x%08X) (%s)\n", -+ *cmd, -+ desc->bits[i].mask, -+ desc->bits[i].expected, -+ dword, engine->name); -+ return false; -+ } -+ } -+ } -+ -+ return true; -+} -+ -+#define LENGTH_BIAS 2 -+ -+/** -+ * i915_parse_cmds() - parse a submitted batch buffer for privilege violations -+ * @engine: the engine on which the batch is to execute -+ * @batch_obj: the batch buffer in question -+ * @shadow_batch_obj: copy of the batch buffer in question -+ * @batch_start_offset: byte offset in the batch at which execution starts -+ * @batch_len: length of the commands in batch_obj -+ * @is_master: is the submitting process the drm master? -+ * -+ * Parses the specified batch buffer looking for privilege violations as -+ * described in the overview. -+ * -+ * Return: non-zero if the parser finds violations or otherwise fails; -EACCES -+ * if the batch appears legal but should use hardware parsing -+ */ -+int intel_engine_cmd_parser(struct intel_engine_cs *engine, -+ struct drm_i915_gem_object *batch_obj, -+ struct drm_i915_gem_object *shadow_batch_obj, -+ u32 batch_start_offset, -+ u32 batch_len, -+ bool is_master) -+{ -+ u32 *cmd, *batch_end; -+ struct drm_i915_cmd_descriptor default_desc = noop_desc; -+ const struct drm_i915_cmd_descriptor *desc = &default_desc; -+ bool needs_clflush_after = false; -+ int ret = 0; -+ -+ cmd = copy_batch(shadow_batch_obj, batch_obj, -+ batch_start_offset, batch_len, -+ &needs_clflush_after); -+ if (IS_ERR(cmd)) { -+ DRM_DEBUG_DRIVER("CMD: Failed to copy batch\n"); -+ return PTR_ERR(cmd); -+ } -+ -+ /* -+ * We use the batch length as size because the shadow object is as -+ * large or larger and copy_batch() will write MI_NOPs to the extra -+ * space. Parsing should be faster in some cases this way. -+ */ -+ batch_end = cmd + (batch_len / sizeof(*batch_end)); -+ do { -+ u32 length; -+ -+ if (*cmd == MI_BATCH_BUFFER_END) { -+ if (needs_clflush_after) { -+ void *ptr = page_mask_bits(shadow_batch_obj->mm.mapping); -+ drm_clflush_virt_range(ptr, -+ (void *)(cmd + 1) - ptr); -+ } -+ break; -+ } -+ -+ desc = find_cmd(engine, *cmd, desc, &default_desc); -+ if (!desc) { -+ DRM_DEBUG_DRIVER("CMD: Unrecognized command: 0x%08X\n", -+ *cmd); -+ ret = -EINVAL; -+ break; -+ } -+ -+ /* -+ * If the batch buffer contains a chained batch, return an -+ * error that tells the caller to abort and dispatch the -+ * workload as a non-secure batch. -+ */ -+ if (desc->cmd.value == MI_BATCH_BUFFER_START) { -+ ret = -EACCES; -+ break; -+ } -+ -+ if (desc->flags & CMD_DESC_FIXED) -+ length = desc->length.fixed; -+ else -+ length = ((*cmd & desc->length.mask) + LENGTH_BIAS); -+ -+ if ((batch_end - cmd) < length) { -+ DRM_DEBUG_DRIVER("CMD: Command length exceeds batch length: 0x%08X length=%u batchlen=%td\n", -+ *cmd, -+ length, -+ batch_end - cmd); -+ ret = -EINVAL; -+ break; -+ } -+ -+ if (!check_cmd(engine, desc, cmd, length, is_master)) { -+ ret = -EACCES; -+ break; -+ } -+ -+ cmd += length; -+ if (cmd >= batch_end) { -+ DRM_DEBUG_DRIVER("CMD: Got to the end of the buffer w/o a BBE cmd!\n"); -+ ret = -EINVAL; -+ break; -+ } -+ } while (1); -+ -+ i915_gem_object_unpin_map(shadow_batch_obj); -+ return ret; -+} -+ -+/** -+ * i915_cmd_parser_get_version() - get the cmd parser version number -+ * @dev_priv: i915 device private -+ * -+ * The cmd parser maintains a simple increasing integer version number suitable -+ * for passing to userspace clients to determine what operations are permitted. -+ * -+ * Return: the current version number of the cmd parser -+ */ -+int i915_cmd_parser_get_version(struct drm_i915_private *dev_priv) -+{ -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ bool active = false; -+ -+ /* If the command parser is not enabled, report 0 - unsupported */ -+ for_each_engine(engine, dev_priv, id) { -+ if (intel_engine_needs_cmd_parser(engine)) { -+ active = true; -+ break; -+ } -+ } -+ if (!active) -+ return 0; -+ -+ /* -+ * Command parser version history -+ * -+ * 1. Initial version. Checks batches and reports violations, but leaves -+ * hardware parsing enabled (so does not allow new use cases). -+ * 2. Allow access to the MI_PREDICATE_SRC0 and -+ * MI_PREDICATE_SRC1 registers. -+ * 3. Allow access to the GPGPU_THREADS_DISPATCHED register. -+ * 4. L3 atomic chicken bits of HSW_SCRATCH1 and HSW_ROW_CHICKEN3. -+ * 5. GPGPU dispatch compute indirect registers. -+ * 6. TIMESTAMP register and Haswell CS GPR registers -+ * 7. Allow MI_LOAD_REGISTER_REG between whitelisted registers. -+ * 8. Don't report cmd_check() failures as EINVAL errors to userspace; -+ * rely on the HW to NOOP disallowed commands as it would without -+ * the parser enabled. -+ * 9. Don't whitelist or handle oacontrol specially, as ownership -+ * for oacontrol state is moving to i915-perf. -+ */ -+ return 9; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_debugfs.c b/drivers/gpu/drm/i915_legacy/i915_debugfs.c -new file mode 100644 -index 000000000000..5823ffb17821 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_debugfs.c -@@ -0,0 +1,4926 @@ -+/* -+ * Copyright © 2008 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eric Anholt -+ * Keith Packard -+ * -+ */ -+ -+#include -+#include -+ -+#include -+#include -+ -+#include "i915_reset.h" -+#include "intel_dp.h" -+#include "intel_drv.h" -+#include "intel_fbc.h" -+#include "intel_guc_submission.h" -+#include "intel_hdcp.h" -+#include "intel_hdmi.h" -+#include "intel_pm.h" -+#include "intel_psr.h" -+ -+static inline struct drm_i915_private *node_to_i915(struct drm_info_node *node) -+{ -+ return to_i915(node->minor->dev); -+} -+ -+static int i915_capabilities(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ const struct intel_device_info *info = INTEL_INFO(dev_priv); -+ struct drm_printer p = drm_seq_file_printer(m); -+ -+ seq_printf(m, "gen: %d\n", INTEL_GEN(dev_priv)); -+ seq_printf(m, "platform: %s\n", intel_platform_name(info->platform)); -+ seq_printf(m, "pch: %d\n", INTEL_PCH_TYPE(dev_priv)); -+ -+ intel_device_info_dump_flags(info, &p); -+ intel_device_info_dump_runtime(RUNTIME_INFO(dev_priv), &p); -+ intel_driver_caps_print(&dev_priv->caps, &p); -+ -+ kernel_param_lock(THIS_MODULE); -+ i915_params_dump(&i915_modparams, &p); -+ kernel_param_unlock(THIS_MODULE); -+ -+ return 0; -+} -+ -+static char get_active_flag(struct drm_i915_gem_object *obj) -+{ -+ return i915_gem_object_is_active(obj) ? '*' : ' '; -+} -+ -+static char get_pin_flag(struct drm_i915_gem_object *obj) -+{ -+ return obj->pin_global ? 'p' : ' '; -+} -+ -+static char get_tiling_flag(struct drm_i915_gem_object *obj) -+{ -+ switch (i915_gem_object_get_tiling(obj)) { -+ default: -+ case I915_TILING_NONE: return ' '; -+ case I915_TILING_X: return 'X'; -+ case I915_TILING_Y: return 'Y'; -+ } -+} -+ -+static char get_global_flag(struct drm_i915_gem_object *obj) -+{ -+ return obj->userfault_count ? 'g' : ' '; -+} -+ -+static char get_pin_mapped_flag(struct drm_i915_gem_object *obj) -+{ -+ return obj->mm.mapping ? 'M' : ' '; -+} -+ -+static u64 i915_gem_obj_total_ggtt_size(struct drm_i915_gem_object *obj) -+{ -+ u64 size = 0; -+ struct i915_vma *vma; -+ -+ for_each_ggtt_vma(vma, obj) { -+ if (drm_mm_node_allocated(&vma->node)) -+ size += vma->node.size; -+ } -+ -+ return size; -+} -+ -+static const char * -+stringify_page_sizes(unsigned int page_sizes, char *buf, size_t len) -+{ -+ size_t x = 0; -+ -+ switch (page_sizes) { -+ case 0: -+ return ""; -+ case I915_GTT_PAGE_SIZE_4K: -+ return "4K"; -+ case I915_GTT_PAGE_SIZE_64K: -+ return "64K"; -+ case I915_GTT_PAGE_SIZE_2M: -+ return "2M"; -+ default: -+ if (!buf) -+ return "M"; -+ -+ if (page_sizes & I915_GTT_PAGE_SIZE_2M) -+ x += snprintf(buf + x, len - x, "2M, "); -+ if (page_sizes & I915_GTT_PAGE_SIZE_64K) -+ x += snprintf(buf + x, len - x, "64K, "); -+ if (page_sizes & I915_GTT_PAGE_SIZE_4K) -+ x += snprintf(buf + x, len - x, "4K, "); -+ buf[x-2] = '\0'; -+ -+ return buf; -+ } -+} -+ -+static void -+describe_obj(struct seq_file *m, struct drm_i915_gem_object *obj) -+{ -+ struct drm_i915_private *dev_priv = to_i915(obj->base.dev); -+ struct intel_engine_cs *engine; -+ struct i915_vma *vma; -+ unsigned int frontbuffer_bits; -+ int pin_count = 0; -+ -+ lockdep_assert_held(&obj->base.dev->struct_mutex); -+ -+ seq_printf(m, "%pK: %c%c%c%c%c %8zdKiB %02x %02x %s%s%s", -+ &obj->base, -+ get_active_flag(obj), -+ get_pin_flag(obj), -+ get_tiling_flag(obj), -+ get_global_flag(obj), -+ get_pin_mapped_flag(obj), -+ obj->base.size / 1024, -+ obj->read_domains, -+ obj->write_domain, -+ i915_cache_level_str(dev_priv, obj->cache_level), -+ obj->mm.dirty ? " dirty" : "", -+ obj->mm.madv == I915_MADV_DONTNEED ? " purgeable" : ""); -+ if (obj->base.name) -+ seq_printf(m, " (name: %d)", obj->base.name); -+ list_for_each_entry(vma, &obj->vma.list, obj_link) { -+ if (i915_vma_is_pinned(vma)) -+ pin_count++; -+ } -+ seq_printf(m, " (pinned x %d)", pin_count); -+ if (obj->pin_global) -+ seq_printf(m, " (global)"); -+ list_for_each_entry(vma, &obj->vma.list, obj_link) { -+ if (!drm_mm_node_allocated(&vma->node)) -+ continue; -+ -+ seq_printf(m, " (%sgtt offset: %08llx, size: %08llx, pages: %s", -+ i915_vma_is_ggtt(vma) ? "g" : "pp", -+ vma->node.start, vma->node.size, -+ stringify_page_sizes(vma->page_sizes.gtt, NULL, 0)); -+ if (i915_vma_is_ggtt(vma)) { -+ switch (vma->ggtt_view.type) { -+ case I915_GGTT_VIEW_NORMAL: -+ seq_puts(m, ", normal"); -+ break; -+ -+ case I915_GGTT_VIEW_PARTIAL: -+ seq_printf(m, ", partial [%08llx+%x]", -+ vma->ggtt_view.partial.offset << PAGE_SHIFT, -+ vma->ggtt_view.partial.size << PAGE_SHIFT); -+ break; -+ -+ case I915_GGTT_VIEW_ROTATED: -+ seq_printf(m, ", rotated [(%ux%u, stride=%u, offset=%u), (%ux%u, stride=%u, offset=%u)]", -+ vma->ggtt_view.rotated.plane[0].width, -+ vma->ggtt_view.rotated.plane[0].height, -+ vma->ggtt_view.rotated.plane[0].stride, -+ vma->ggtt_view.rotated.plane[0].offset, -+ vma->ggtt_view.rotated.plane[1].width, -+ vma->ggtt_view.rotated.plane[1].height, -+ vma->ggtt_view.rotated.plane[1].stride, -+ vma->ggtt_view.rotated.plane[1].offset); -+ break; -+ -+ default: -+ MISSING_CASE(vma->ggtt_view.type); -+ break; -+ } -+ } -+ if (vma->fence) -+ seq_printf(m, " , fence: %d%s", -+ vma->fence->id, -+ i915_active_request_isset(&vma->last_fence) ? "*" : ""); -+ seq_puts(m, ")"); -+ } -+ if (obj->stolen) -+ seq_printf(m, " (stolen: %08llx)", obj->stolen->start); -+ -+ engine = i915_gem_object_last_write_engine(obj); -+ if (engine) -+ seq_printf(m, " (%s)", engine->name); -+ -+ frontbuffer_bits = atomic_read(&obj->frontbuffer_bits); -+ if (frontbuffer_bits) -+ seq_printf(m, " (frontbuffer: 0x%03x)", frontbuffer_bits); -+} -+ -+static int obj_rank_by_stolen(const void *A, const void *B) -+{ -+ const struct drm_i915_gem_object *a = -+ *(const struct drm_i915_gem_object **)A; -+ const struct drm_i915_gem_object *b = -+ *(const struct drm_i915_gem_object **)B; -+ -+ if (a->stolen->start < b->stolen->start) -+ return -1; -+ if (a->stolen->start > b->stolen->start) -+ return 1; -+ return 0; -+} -+ -+static int i915_gem_stolen_list_info(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ struct drm_i915_gem_object **objects; -+ struct drm_i915_gem_object *obj; -+ u64 total_obj_size, total_gtt_size; -+ unsigned long total, count, n; -+ int ret; -+ -+ total = READ_ONCE(dev_priv->mm.object_count); -+ objects = kvmalloc_array(total, sizeof(*objects), GFP_KERNEL); -+ if (!objects) -+ return -ENOMEM; -+ -+ ret = mutex_lock_interruptible(&dev->struct_mutex); -+ if (ret) -+ goto out; -+ -+ total_obj_size = total_gtt_size = count = 0; -+ -+ spin_lock(&dev_priv->mm.obj_lock); -+ list_for_each_entry(obj, &dev_priv->mm.bound_list, mm.link) { -+ if (count == total) -+ break; -+ -+ if (obj->stolen == NULL) -+ continue; -+ -+ objects[count++] = obj; -+ total_obj_size += obj->base.size; -+ total_gtt_size += i915_gem_obj_total_ggtt_size(obj); -+ -+ } -+ list_for_each_entry(obj, &dev_priv->mm.unbound_list, mm.link) { -+ if (count == total) -+ break; -+ -+ if (obj->stolen == NULL) -+ continue; -+ -+ objects[count++] = obj; -+ total_obj_size += obj->base.size; -+ } -+ spin_unlock(&dev_priv->mm.obj_lock); -+ -+ sort(objects, count, sizeof(*objects), obj_rank_by_stolen, NULL); -+ -+ seq_puts(m, "Stolen:\n"); -+ for (n = 0; n < count; n++) { -+ seq_puts(m, " "); -+ describe_obj(m, objects[n]); -+ seq_putc(m, '\n'); -+ } -+ seq_printf(m, "Total %lu objects, %llu bytes, %llu GTT size\n", -+ count, total_obj_size, total_gtt_size); -+ -+ mutex_unlock(&dev->struct_mutex); -+out: -+ kvfree(objects); -+ return ret; -+} -+ -+struct file_stats { -+ struct i915_address_space *vm; -+ unsigned long count; -+ u64 total, unbound; -+ u64 global, shared; -+ u64 active, inactive; -+ u64 closed; -+}; -+ -+static int per_file_stats(int id, void *ptr, void *data) -+{ -+ struct drm_i915_gem_object *obj = ptr; -+ struct file_stats *stats = data; -+ struct i915_vma *vma; -+ -+ lockdep_assert_held(&obj->base.dev->struct_mutex); -+ -+ stats->count++; -+ stats->total += obj->base.size; -+ if (!obj->bind_count) -+ stats->unbound += obj->base.size; -+ if (obj->base.name || obj->base.dma_buf) -+ stats->shared += obj->base.size; -+ -+ list_for_each_entry(vma, &obj->vma.list, obj_link) { -+ if (!drm_mm_node_allocated(&vma->node)) -+ continue; -+ -+ if (i915_vma_is_ggtt(vma)) { -+ stats->global += vma->node.size; -+ } else { -+ if (vma->vm != stats->vm) -+ continue; -+ } -+ -+ if (i915_vma_is_active(vma)) -+ stats->active += vma->node.size; -+ else -+ stats->inactive += vma->node.size; -+ -+ if (i915_vma_is_closed(vma)) -+ stats->closed += vma->node.size; -+ } -+ -+ return 0; -+} -+ -+#define print_file_stats(m, name, stats) do { \ -+ if (stats.count) \ -+ seq_printf(m, "%s: %lu objects, %llu bytes (%llu active, %llu inactive, %llu global, %llu shared, %llu unbound, %llu closed)\n", \ -+ name, \ -+ stats.count, \ -+ stats.total, \ -+ stats.active, \ -+ stats.inactive, \ -+ stats.global, \ -+ stats.shared, \ -+ stats.unbound, \ -+ stats.closed); \ -+} while (0) -+ -+static void print_batch_pool_stats(struct seq_file *m, -+ struct drm_i915_private *dev_priv) -+{ -+ struct drm_i915_gem_object *obj; -+ struct intel_engine_cs *engine; -+ struct file_stats stats = {}; -+ enum intel_engine_id id; -+ int j; -+ -+ for_each_engine(engine, dev_priv, id) { -+ for (j = 0; j < ARRAY_SIZE(engine->batch_pool.cache_list); j++) { -+ list_for_each_entry(obj, -+ &engine->batch_pool.cache_list[j], -+ batch_pool_link) -+ per_file_stats(0, obj, &stats); -+ } -+ } -+ -+ print_file_stats(m, "[k]batch pool", stats); -+} -+ -+static void print_context_stats(struct seq_file *m, -+ struct drm_i915_private *i915) -+{ -+ struct file_stats kstats = {}; -+ struct i915_gem_context *ctx; -+ -+ list_for_each_entry(ctx, &i915->contexts.list, link) { -+ struct intel_context *ce; -+ -+ list_for_each_entry(ce, &ctx->active_engines, active_link) { -+ if (ce->state) -+ per_file_stats(0, ce->state->obj, &kstats); -+ if (ce->ring) -+ per_file_stats(0, ce->ring->vma->obj, &kstats); -+ } -+ -+ if (!IS_ERR_OR_NULL(ctx->file_priv)) { -+ struct file_stats stats = { .vm = &ctx->ppgtt->vm, }; -+ struct drm_file *file = ctx->file_priv->file; -+ struct task_struct *task; -+ char name[80]; -+ -+ spin_lock(&file->table_lock); -+ idr_for_each(&file->object_idr, per_file_stats, &stats); -+ spin_unlock(&file->table_lock); -+ -+ rcu_read_lock(); -+ task = pid_task(ctx->pid ?: file->pid, PIDTYPE_PID); -+ snprintf(name, sizeof(name), "%s", -+ task ? task->comm : ""); -+ rcu_read_unlock(); -+ -+ print_file_stats(m, name, stats); -+ } -+ } -+ -+ print_file_stats(m, "[k]contexts", kstats); -+} -+ -+static int i915_gem_object_info(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ struct i915_ggtt *ggtt = &dev_priv->ggtt; -+ u32 count, mapped_count, purgeable_count, dpy_count, huge_count; -+ u64 size, mapped_size, purgeable_size, dpy_size, huge_size; -+ struct drm_i915_gem_object *obj; -+ unsigned int page_sizes = 0; -+ char buf[80]; -+ int ret; -+ -+ seq_printf(m, "%u objects, %llu bytes\n", -+ dev_priv->mm.object_count, -+ dev_priv->mm.object_memory); -+ -+ size = count = 0; -+ mapped_size = mapped_count = 0; -+ purgeable_size = purgeable_count = 0; -+ huge_size = huge_count = 0; -+ -+ spin_lock(&dev_priv->mm.obj_lock); -+ list_for_each_entry(obj, &dev_priv->mm.unbound_list, mm.link) { -+ size += obj->base.size; -+ ++count; -+ -+ if (obj->mm.madv == I915_MADV_DONTNEED) { -+ purgeable_size += obj->base.size; -+ ++purgeable_count; -+ } -+ -+ if (obj->mm.mapping) { -+ mapped_count++; -+ mapped_size += obj->base.size; -+ } -+ -+ if (obj->mm.page_sizes.sg > I915_GTT_PAGE_SIZE) { -+ huge_count++; -+ huge_size += obj->base.size; -+ page_sizes |= obj->mm.page_sizes.sg; -+ } -+ } -+ seq_printf(m, "%u unbound objects, %llu bytes\n", count, size); -+ -+ size = count = dpy_size = dpy_count = 0; -+ list_for_each_entry(obj, &dev_priv->mm.bound_list, mm.link) { -+ size += obj->base.size; -+ ++count; -+ -+ if (obj->pin_global) { -+ dpy_size += obj->base.size; -+ ++dpy_count; -+ } -+ -+ if (obj->mm.madv == I915_MADV_DONTNEED) { -+ purgeable_size += obj->base.size; -+ ++purgeable_count; -+ } -+ -+ if (obj->mm.mapping) { -+ mapped_count++; -+ mapped_size += obj->base.size; -+ } -+ -+ if (obj->mm.page_sizes.sg > I915_GTT_PAGE_SIZE) { -+ huge_count++; -+ huge_size += obj->base.size; -+ page_sizes |= obj->mm.page_sizes.sg; -+ } -+ } -+ spin_unlock(&dev_priv->mm.obj_lock); -+ -+ seq_printf(m, "%u bound objects, %llu bytes\n", -+ count, size); -+ seq_printf(m, "%u purgeable objects, %llu bytes\n", -+ purgeable_count, purgeable_size); -+ seq_printf(m, "%u mapped objects, %llu bytes\n", -+ mapped_count, mapped_size); -+ seq_printf(m, "%u huge-paged objects (%s) %llu bytes\n", -+ huge_count, -+ stringify_page_sizes(page_sizes, buf, sizeof(buf)), -+ huge_size); -+ seq_printf(m, "%u display objects (globally pinned), %llu bytes\n", -+ dpy_count, dpy_size); -+ -+ seq_printf(m, "%llu [%pa] gtt total\n", -+ ggtt->vm.total, &ggtt->mappable_end); -+ seq_printf(m, "Supported page sizes: %s\n", -+ stringify_page_sizes(INTEL_INFO(dev_priv)->page_sizes, -+ buf, sizeof(buf))); -+ -+ seq_putc(m, '\n'); -+ -+ ret = mutex_lock_interruptible(&dev->struct_mutex); -+ if (ret) -+ return ret; -+ -+ print_batch_pool_stats(m, dev_priv); -+ print_context_stats(m, dev_priv); -+ mutex_unlock(&dev->struct_mutex); -+ -+ return 0; -+} -+ -+static int i915_gem_gtt_info(struct seq_file *m, void *data) -+{ -+ struct drm_info_node *node = m->private; -+ struct drm_i915_private *dev_priv = node_to_i915(node); -+ struct drm_device *dev = &dev_priv->drm; -+ struct drm_i915_gem_object **objects; -+ struct drm_i915_gem_object *obj; -+ u64 total_obj_size, total_gtt_size; -+ unsigned long nobject, n; -+ int count, ret; -+ -+ nobject = READ_ONCE(dev_priv->mm.object_count); -+ objects = kvmalloc_array(nobject, sizeof(*objects), GFP_KERNEL); -+ if (!objects) -+ return -ENOMEM; -+ -+ ret = mutex_lock_interruptible(&dev->struct_mutex); -+ if (ret) -+ return ret; -+ -+ count = 0; -+ spin_lock(&dev_priv->mm.obj_lock); -+ list_for_each_entry(obj, &dev_priv->mm.bound_list, mm.link) { -+ objects[count++] = obj; -+ if (count == nobject) -+ break; -+ } -+ spin_unlock(&dev_priv->mm.obj_lock); -+ -+ total_obj_size = total_gtt_size = 0; -+ for (n = 0; n < count; n++) { -+ obj = objects[n]; -+ -+ seq_puts(m, " "); -+ describe_obj(m, obj); -+ seq_putc(m, '\n'); -+ total_obj_size += obj->base.size; -+ total_gtt_size += i915_gem_obj_total_ggtt_size(obj); -+ } -+ -+ mutex_unlock(&dev->struct_mutex); -+ -+ seq_printf(m, "Total %d objects, %llu bytes, %llu GTT size\n", -+ count, total_obj_size, total_gtt_size); -+ kvfree(objects); -+ -+ return 0; -+} -+ -+static int i915_gem_batch_pool_info(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ struct drm_i915_gem_object *obj; -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ int total = 0; -+ int ret, j; -+ -+ ret = mutex_lock_interruptible(&dev->struct_mutex); -+ if (ret) -+ return ret; -+ -+ for_each_engine(engine, dev_priv, id) { -+ for (j = 0; j < ARRAY_SIZE(engine->batch_pool.cache_list); j++) { -+ int count; -+ -+ count = 0; -+ list_for_each_entry(obj, -+ &engine->batch_pool.cache_list[j], -+ batch_pool_link) -+ count++; -+ seq_printf(m, "%s cache[%d]: %d objects\n", -+ engine->name, j, count); -+ -+ list_for_each_entry(obj, -+ &engine->batch_pool.cache_list[j], -+ batch_pool_link) { -+ seq_puts(m, " "); -+ describe_obj(m, obj); -+ seq_putc(m, '\n'); -+ } -+ -+ total += count; -+ } -+ } -+ -+ seq_printf(m, "total: %d\n", total); -+ -+ mutex_unlock(&dev->struct_mutex); -+ -+ return 0; -+} -+ -+static void gen8_display_interrupt_info(struct seq_file *m) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ int pipe; -+ -+ for_each_pipe(dev_priv, pipe) { -+ enum intel_display_power_domain power_domain; -+ intel_wakeref_t wakeref; -+ -+ power_domain = POWER_DOMAIN_PIPE(pipe); -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ power_domain); -+ if (!wakeref) { -+ seq_printf(m, "Pipe %c power disabled\n", -+ pipe_name(pipe)); -+ continue; -+ } -+ seq_printf(m, "Pipe %c IMR:\t%08x\n", -+ pipe_name(pipe), -+ I915_READ(GEN8_DE_PIPE_IMR(pipe))); -+ seq_printf(m, "Pipe %c IIR:\t%08x\n", -+ pipe_name(pipe), -+ I915_READ(GEN8_DE_PIPE_IIR(pipe))); -+ seq_printf(m, "Pipe %c IER:\t%08x\n", -+ pipe_name(pipe), -+ I915_READ(GEN8_DE_PIPE_IER(pipe))); -+ -+ intel_display_power_put(dev_priv, power_domain, wakeref); -+ } -+ -+ seq_printf(m, "Display Engine port interrupt mask:\t%08x\n", -+ I915_READ(GEN8_DE_PORT_IMR)); -+ seq_printf(m, "Display Engine port interrupt identity:\t%08x\n", -+ I915_READ(GEN8_DE_PORT_IIR)); -+ seq_printf(m, "Display Engine port interrupt enable:\t%08x\n", -+ I915_READ(GEN8_DE_PORT_IER)); -+ -+ seq_printf(m, "Display Engine misc interrupt mask:\t%08x\n", -+ I915_READ(GEN8_DE_MISC_IMR)); -+ seq_printf(m, "Display Engine misc interrupt identity:\t%08x\n", -+ I915_READ(GEN8_DE_MISC_IIR)); -+ seq_printf(m, "Display Engine misc interrupt enable:\t%08x\n", -+ I915_READ(GEN8_DE_MISC_IER)); -+ -+ seq_printf(m, "PCU interrupt mask:\t%08x\n", -+ I915_READ(GEN8_PCU_IMR)); -+ seq_printf(m, "PCU interrupt identity:\t%08x\n", -+ I915_READ(GEN8_PCU_IIR)); -+ seq_printf(m, "PCU interrupt enable:\t%08x\n", -+ I915_READ(GEN8_PCU_IER)); -+} -+ -+static int i915_interrupt_info(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ intel_wakeref_t wakeref; -+ int i, pipe; -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ -+ if (IS_CHERRYVIEW(dev_priv)) { -+ intel_wakeref_t pref; -+ -+ seq_printf(m, "Master Interrupt Control:\t%08x\n", -+ I915_READ(GEN8_MASTER_IRQ)); -+ -+ seq_printf(m, "Display IER:\t%08x\n", -+ I915_READ(VLV_IER)); -+ seq_printf(m, "Display IIR:\t%08x\n", -+ I915_READ(VLV_IIR)); -+ seq_printf(m, "Display IIR_RW:\t%08x\n", -+ I915_READ(VLV_IIR_RW)); -+ seq_printf(m, "Display IMR:\t%08x\n", -+ I915_READ(VLV_IMR)); -+ for_each_pipe(dev_priv, pipe) { -+ enum intel_display_power_domain power_domain; -+ -+ power_domain = POWER_DOMAIN_PIPE(pipe); -+ pref = intel_display_power_get_if_enabled(dev_priv, -+ power_domain); -+ if (!pref) { -+ seq_printf(m, "Pipe %c power disabled\n", -+ pipe_name(pipe)); -+ continue; -+ } -+ -+ seq_printf(m, "Pipe %c stat:\t%08x\n", -+ pipe_name(pipe), -+ I915_READ(PIPESTAT(pipe))); -+ -+ intel_display_power_put(dev_priv, power_domain, pref); -+ } -+ -+ pref = intel_display_power_get(dev_priv, POWER_DOMAIN_INIT); -+ seq_printf(m, "Port hotplug:\t%08x\n", -+ I915_READ(PORT_HOTPLUG_EN)); -+ seq_printf(m, "DPFLIPSTAT:\t%08x\n", -+ I915_READ(VLV_DPFLIPSTAT)); -+ seq_printf(m, "DPINVGTT:\t%08x\n", -+ I915_READ(DPINVGTT)); -+ intel_display_power_put(dev_priv, POWER_DOMAIN_INIT, pref); -+ -+ for (i = 0; i < 4; i++) { -+ seq_printf(m, "GT Interrupt IMR %d:\t%08x\n", -+ i, I915_READ(GEN8_GT_IMR(i))); -+ seq_printf(m, "GT Interrupt IIR %d:\t%08x\n", -+ i, I915_READ(GEN8_GT_IIR(i))); -+ seq_printf(m, "GT Interrupt IER %d:\t%08x\n", -+ i, I915_READ(GEN8_GT_IER(i))); -+ } -+ -+ seq_printf(m, "PCU interrupt mask:\t%08x\n", -+ I915_READ(GEN8_PCU_IMR)); -+ seq_printf(m, "PCU interrupt identity:\t%08x\n", -+ I915_READ(GEN8_PCU_IIR)); -+ seq_printf(m, "PCU interrupt enable:\t%08x\n", -+ I915_READ(GEN8_PCU_IER)); -+ } else if (INTEL_GEN(dev_priv) >= 11) { -+ seq_printf(m, "Master Interrupt Control: %08x\n", -+ I915_READ(GEN11_GFX_MSTR_IRQ)); -+ -+ seq_printf(m, "Render/Copy Intr Enable: %08x\n", -+ I915_READ(GEN11_RENDER_COPY_INTR_ENABLE)); -+ seq_printf(m, "VCS/VECS Intr Enable: %08x\n", -+ I915_READ(GEN11_VCS_VECS_INTR_ENABLE)); -+ seq_printf(m, "GUC/SG Intr Enable:\t %08x\n", -+ I915_READ(GEN11_GUC_SG_INTR_ENABLE)); -+ seq_printf(m, "GPM/WGBOXPERF Intr Enable: %08x\n", -+ I915_READ(GEN11_GPM_WGBOXPERF_INTR_ENABLE)); -+ seq_printf(m, "Crypto Intr Enable:\t %08x\n", -+ I915_READ(GEN11_CRYPTO_RSVD_INTR_ENABLE)); -+ seq_printf(m, "GUnit/CSME Intr Enable:\t %08x\n", -+ I915_READ(GEN11_GUNIT_CSME_INTR_ENABLE)); -+ -+ seq_printf(m, "Display Interrupt Control:\t%08x\n", -+ I915_READ(GEN11_DISPLAY_INT_CTL)); -+ -+ gen8_display_interrupt_info(m); -+ } else if (INTEL_GEN(dev_priv) >= 8) { -+ seq_printf(m, "Master Interrupt Control:\t%08x\n", -+ I915_READ(GEN8_MASTER_IRQ)); -+ -+ for (i = 0; i < 4; i++) { -+ seq_printf(m, "GT Interrupt IMR %d:\t%08x\n", -+ i, I915_READ(GEN8_GT_IMR(i))); -+ seq_printf(m, "GT Interrupt IIR %d:\t%08x\n", -+ i, I915_READ(GEN8_GT_IIR(i))); -+ seq_printf(m, "GT Interrupt IER %d:\t%08x\n", -+ i, I915_READ(GEN8_GT_IER(i))); -+ } -+ -+ gen8_display_interrupt_info(m); -+ } else if (IS_VALLEYVIEW(dev_priv)) { -+ seq_printf(m, "Display IER:\t%08x\n", -+ I915_READ(VLV_IER)); -+ seq_printf(m, "Display IIR:\t%08x\n", -+ I915_READ(VLV_IIR)); -+ seq_printf(m, "Display IIR_RW:\t%08x\n", -+ I915_READ(VLV_IIR_RW)); -+ seq_printf(m, "Display IMR:\t%08x\n", -+ I915_READ(VLV_IMR)); -+ for_each_pipe(dev_priv, pipe) { -+ enum intel_display_power_domain power_domain; -+ intel_wakeref_t pref; -+ -+ power_domain = POWER_DOMAIN_PIPE(pipe); -+ pref = intel_display_power_get_if_enabled(dev_priv, -+ power_domain); -+ if (!pref) { -+ seq_printf(m, "Pipe %c power disabled\n", -+ pipe_name(pipe)); -+ continue; -+ } -+ -+ seq_printf(m, "Pipe %c stat:\t%08x\n", -+ pipe_name(pipe), -+ I915_READ(PIPESTAT(pipe))); -+ intel_display_power_put(dev_priv, power_domain, pref); -+ } -+ -+ seq_printf(m, "Master IER:\t%08x\n", -+ I915_READ(VLV_MASTER_IER)); -+ -+ seq_printf(m, "Render IER:\t%08x\n", -+ I915_READ(GTIER)); -+ seq_printf(m, "Render IIR:\t%08x\n", -+ I915_READ(GTIIR)); -+ seq_printf(m, "Render IMR:\t%08x\n", -+ I915_READ(GTIMR)); -+ -+ seq_printf(m, "PM IER:\t\t%08x\n", -+ I915_READ(GEN6_PMIER)); -+ seq_printf(m, "PM IIR:\t\t%08x\n", -+ I915_READ(GEN6_PMIIR)); -+ seq_printf(m, "PM IMR:\t\t%08x\n", -+ I915_READ(GEN6_PMIMR)); -+ -+ seq_printf(m, "Port hotplug:\t%08x\n", -+ I915_READ(PORT_HOTPLUG_EN)); -+ seq_printf(m, "DPFLIPSTAT:\t%08x\n", -+ I915_READ(VLV_DPFLIPSTAT)); -+ seq_printf(m, "DPINVGTT:\t%08x\n", -+ I915_READ(DPINVGTT)); -+ -+ } else if (!HAS_PCH_SPLIT(dev_priv)) { -+ seq_printf(m, "Interrupt enable: %08x\n", -+ I915_READ(GEN2_IER)); -+ seq_printf(m, "Interrupt identity: %08x\n", -+ I915_READ(GEN2_IIR)); -+ seq_printf(m, "Interrupt mask: %08x\n", -+ I915_READ(GEN2_IMR)); -+ for_each_pipe(dev_priv, pipe) -+ seq_printf(m, "Pipe %c stat: %08x\n", -+ pipe_name(pipe), -+ I915_READ(PIPESTAT(pipe))); -+ } else { -+ seq_printf(m, "North Display Interrupt enable: %08x\n", -+ I915_READ(DEIER)); -+ seq_printf(m, "North Display Interrupt identity: %08x\n", -+ I915_READ(DEIIR)); -+ seq_printf(m, "North Display Interrupt mask: %08x\n", -+ I915_READ(DEIMR)); -+ seq_printf(m, "South Display Interrupt enable: %08x\n", -+ I915_READ(SDEIER)); -+ seq_printf(m, "South Display Interrupt identity: %08x\n", -+ I915_READ(SDEIIR)); -+ seq_printf(m, "South Display Interrupt mask: %08x\n", -+ I915_READ(SDEIMR)); -+ seq_printf(m, "Graphics Interrupt enable: %08x\n", -+ I915_READ(GTIER)); -+ seq_printf(m, "Graphics Interrupt identity: %08x\n", -+ I915_READ(GTIIR)); -+ seq_printf(m, "Graphics Interrupt mask: %08x\n", -+ I915_READ(GTIMR)); -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 11) { -+ seq_printf(m, "RCS Intr Mask:\t %08x\n", -+ I915_READ(GEN11_RCS0_RSVD_INTR_MASK)); -+ seq_printf(m, "BCS Intr Mask:\t %08x\n", -+ I915_READ(GEN11_BCS_RSVD_INTR_MASK)); -+ seq_printf(m, "VCS0/VCS1 Intr Mask:\t %08x\n", -+ I915_READ(GEN11_VCS0_VCS1_INTR_MASK)); -+ seq_printf(m, "VCS2/VCS3 Intr Mask:\t %08x\n", -+ I915_READ(GEN11_VCS2_VCS3_INTR_MASK)); -+ seq_printf(m, "VECS0/VECS1 Intr Mask:\t %08x\n", -+ I915_READ(GEN11_VECS0_VECS1_INTR_MASK)); -+ seq_printf(m, "GUC/SG Intr Mask:\t %08x\n", -+ I915_READ(GEN11_GUC_SG_INTR_MASK)); -+ seq_printf(m, "GPM/WGBOXPERF Intr Mask: %08x\n", -+ I915_READ(GEN11_GPM_WGBOXPERF_INTR_MASK)); -+ seq_printf(m, "Crypto Intr Mask:\t %08x\n", -+ I915_READ(GEN11_CRYPTO_RSVD_INTR_MASK)); -+ seq_printf(m, "Gunit/CSME Intr Mask:\t %08x\n", -+ I915_READ(GEN11_GUNIT_CSME_INTR_MASK)); -+ -+ } else if (INTEL_GEN(dev_priv) >= 6) { -+ for_each_engine(engine, dev_priv, id) { -+ seq_printf(m, -+ "Graphics Interrupt mask (%s): %08x\n", -+ engine->name, ENGINE_READ(engine, RING_IMR)); -+ } -+ } -+ -+ intel_runtime_pm_put(dev_priv, wakeref); -+ -+ return 0; -+} -+ -+static int i915_gem_fence_regs_info(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ int i, ret; -+ -+ ret = mutex_lock_interruptible(&dev->struct_mutex); -+ if (ret) -+ return ret; -+ -+ seq_printf(m, "Total fences = %d\n", dev_priv->num_fence_regs); -+ for (i = 0; i < dev_priv->num_fence_regs; i++) { -+ struct i915_vma *vma = dev_priv->fence_regs[i].vma; -+ -+ seq_printf(m, "Fence %d, pin count = %d, object = ", -+ i, dev_priv->fence_regs[i].pin_count); -+ if (!vma) -+ seq_puts(m, "unused"); -+ else -+ describe_obj(m, vma->obj); -+ seq_putc(m, '\n'); -+ } -+ -+ mutex_unlock(&dev->struct_mutex); -+ return 0; -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_CAPTURE_ERROR) -+static ssize_t gpu_state_read(struct file *file, char __user *ubuf, -+ size_t count, loff_t *pos) -+{ -+ struct i915_gpu_state *error; -+ ssize_t ret; -+ void *buf; -+ -+ error = file->private_data; -+ if (!error) -+ return 0; -+ -+ /* Bounce buffer required because of kernfs __user API convenience. */ -+ buf = kmalloc(count, GFP_KERNEL); -+ if (!buf) -+ return -ENOMEM; -+ -+ ret = i915_gpu_state_copy_to_buffer(error, buf, *pos, count); -+ if (ret <= 0) -+ goto out; -+ -+ if (!copy_to_user(ubuf, buf, ret)) -+ *pos += ret; -+ else -+ ret = -EFAULT; -+ -+out: -+ kfree(buf); -+ return ret; -+} -+ -+static int gpu_state_release(struct inode *inode, struct file *file) -+{ -+ i915_gpu_state_put(file->private_data); -+ return 0; -+} -+ -+static int i915_gpu_info_open(struct inode *inode, struct file *file) -+{ -+ struct drm_i915_private *i915 = inode->i_private; -+ struct i915_gpu_state *gpu; -+ intel_wakeref_t wakeref; -+ -+ gpu = NULL; -+ with_intel_runtime_pm(i915, wakeref) -+ gpu = i915_capture_gpu_state(i915); -+ if (IS_ERR(gpu)) -+ return PTR_ERR(gpu); -+ -+ file->private_data = gpu; -+ return 0; -+} -+ -+static const struct file_operations i915_gpu_info_fops = { -+ .owner = THIS_MODULE, -+ .open = i915_gpu_info_open, -+ .read = gpu_state_read, -+ .llseek = default_llseek, -+ .release = gpu_state_release, -+}; -+ -+static ssize_t -+i915_error_state_write(struct file *filp, -+ const char __user *ubuf, -+ size_t cnt, -+ loff_t *ppos) -+{ -+ struct i915_gpu_state *error = filp->private_data; -+ -+ if (!error) -+ return 0; -+ -+ DRM_DEBUG_DRIVER("Resetting error state\n"); -+ i915_reset_error_state(error->i915); -+ -+ return cnt; -+} -+ -+static int i915_error_state_open(struct inode *inode, struct file *file) -+{ -+ struct i915_gpu_state *error; -+ -+ error = i915_first_error_state(inode->i_private); -+ if (IS_ERR(error)) -+ return PTR_ERR(error); -+ -+ file->private_data = error; -+ return 0; -+} -+ -+static const struct file_operations i915_error_state_fops = { -+ .owner = THIS_MODULE, -+ .open = i915_error_state_open, -+ .read = gpu_state_read, -+ .write = i915_error_state_write, -+ .llseek = default_llseek, -+ .release = gpu_state_release, -+}; -+#endif -+ -+static int i915_frequency_info(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct intel_rps *rps = &dev_priv->gt_pm.rps; -+ intel_wakeref_t wakeref; -+ int ret = 0; -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ -+ if (IS_GEN(dev_priv, 5)) { -+ u16 rgvswctl = I915_READ16(MEMSWCTL); -+ u16 rgvstat = I915_READ16(MEMSTAT_ILK); -+ -+ seq_printf(m, "Requested P-state: %d\n", (rgvswctl >> 8) & 0xf); -+ seq_printf(m, "Requested VID: %d\n", rgvswctl & 0x3f); -+ seq_printf(m, "Current VID: %d\n", (rgvstat & MEMSTAT_VID_MASK) >> -+ MEMSTAT_VID_SHIFT); -+ seq_printf(m, "Current P-state: %d\n", -+ (rgvstat & MEMSTAT_PSTATE_MASK) >> MEMSTAT_PSTATE_SHIFT); -+ } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ u32 rpmodectl, freq_sts; -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ -+ rpmodectl = I915_READ(GEN6_RP_CONTROL); -+ seq_printf(m, "Video Turbo Mode: %s\n", -+ yesno(rpmodectl & GEN6_RP_MEDIA_TURBO)); -+ seq_printf(m, "HW control enabled: %s\n", -+ yesno(rpmodectl & GEN6_RP_ENABLE)); -+ seq_printf(m, "SW control enabled: %s\n", -+ yesno((rpmodectl & GEN6_RP_MEDIA_MODE_MASK) == -+ GEN6_RP_MEDIA_SW_MODE)); -+ -+ freq_sts = vlv_punit_read(dev_priv, PUNIT_REG_GPU_FREQ_STS); -+ seq_printf(m, "PUNIT_REG_GPU_FREQ_STS: 0x%08x\n", freq_sts); -+ seq_printf(m, "DDR freq: %d MHz\n", dev_priv->mem_freq); -+ -+ seq_printf(m, "actual GPU freq: %d MHz\n", -+ intel_gpu_freq(dev_priv, (freq_sts >> 8) & 0xff)); -+ -+ seq_printf(m, "current GPU freq: %d MHz\n", -+ intel_gpu_freq(dev_priv, rps->cur_freq)); -+ -+ seq_printf(m, "max GPU freq: %d MHz\n", -+ intel_gpu_freq(dev_priv, rps->max_freq)); -+ -+ seq_printf(m, "min GPU freq: %d MHz\n", -+ intel_gpu_freq(dev_priv, rps->min_freq)); -+ -+ seq_printf(m, "idle GPU freq: %d MHz\n", -+ intel_gpu_freq(dev_priv, rps->idle_freq)); -+ -+ seq_printf(m, -+ "efficient (RPe) frequency: %d MHz\n", -+ intel_gpu_freq(dev_priv, rps->efficient_freq)); -+ mutex_unlock(&dev_priv->pcu_lock); -+ } else if (INTEL_GEN(dev_priv) >= 6) { -+ u32 rp_state_limits; -+ u32 gt_perf_status; -+ u32 rp_state_cap; -+ u32 rpmodectl, rpinclimit, rpdeclimit; -+ u32 rpstat, cagf, reqf; -+ u32 rpupei, rpcurup, rpprevup; -+ u32 rpdownei, rpcurdown, rpprevdown; -+ u32 pm_ier, pm_imr, pm_isr, pm_iir, pm_mask; -+ int max_freq; -+ -+ rp_state_limits = I915_READ(GEN6_RP_STATE_LIMITS); -+ if (IS_GEN9_LP(dev_priv)) { -+ rp_state_cap = I915_READ(BXT_RP_STATE_CAP); -+ gt_perf_status = I915_READ(BXT_GT_PERF_STATUS); -+ } else { -+ rp_state_cap = I915_READ(GEN6_RP_STATE_CAP); -+ gt_perf_status = I915_READ(GEN6_GT_PERF_STATUS); -+ } -+ -+ /* RPSTAT1 is in the GT power well */ -+ intel_uncore_forcewake_get(&dev_priv->uncore, FORCEWAKE_ALL); -+ -+ reqf = I915_READ(GEN6_RPNSWREQ); -+ if (INTEL_GEN(dev_priv) >= 9) -+ reqf >>= 23; -+ else { -+ reqf &= ~GEN6_TURBO_DISABLE; -+ if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) -+ reqf >>= 24; -+ else -+ reqf >>= 25; -+ } -+ reqf = intel_gpu_freq(dev_priv, reqf); -+ -+ rpmodectl = I915_READ(GEN6_RP_CONTROL); -+ rpinclimit = I915_READ(GEN6_RP_UP_THRESHOLD); -+ rpdeclimit = I915_READ(GEN6_RP_DOWN_THRESHOLD); -+ -+ rpstat = I915_READ(GEN6_RPSTAT1); -+ rpupei = I915_READ(GEN6_RP_CUR_UP_EI) & GEN6_CURICONT_MASK; -+ rpcurup = I915_READ(GEN6_RP_CUR_UP) & GEN6_CURBSYTAVG_MASK; -+ rpprevup = I915_READ(GEN6_RP_PREV_UP) & GEN6_CURBSYTAVG_MASK; -+ rpdownei = I915_READ(GEN6_RP_CUR_DOWN_EI) & GEN6_CURIAVG_MASK; -+ rpcurdown = I915_READ(GEN6_RP_CUR_DOWN) & GEN6_CURBSYTAVG_MASK; -+ rpprevdown = I915_READ(GEN6_RP_PREV_DOWN) & GEN6_CURBSYTAVG_MASK; -+ cagf = intel_gpu_freq(dev_priv, -+ intel_get_cagf(dev_priv, rpstat)); -+ -+ intel_uncore_forcewake_put(&dev_priv->uncore, FORCEWAKE_ALL); -+ -+ if (INTEL_GEN(dev_priv) >= 11) { -+ pm_ier = I915_READ(GEN11_GPM_WGBOXPERF_INTR_ENABLE); -+ pm_imr = I915_READ(GEN11_GPM_WGBOXPERF_INTR_MASK); -+ /* -+ * The equivalent to the PM ISR & IIR cannot be read -+ * without affecting the current state of the system -+ */ -+ pm_isr = 0; -+ pm_iir = 0; -+ } else if (INTEL_GEN(dev_priv) >= 8) { -+ pm_ier = I915_READ(GEN8_GT_IER(2)); -+ pm_imr = I915_READ(GEN8_GT_IMR(2)); -+ pm_isr = I915_READ(GEN8_GT_ISR(2)); -+ pm_iir = I915_READ(GEN8_GT_IIR(2)); -+ } else { -+ pm_ier = I915_READ(GEN6_PMIER); -+ pm_imr = I915_READ(GEN6_PMIMR); -+ pm_isr = I915_READ(GEN6_PMISR); -+ pm_iir = I915_READ(GEN6_PMIIR); -+ } -+ pm_mask = I915_READ(GEN6_PMINTRMSK); -+ -+ seq_printf(m, "Video Turbo Mode: %s\n", -+ yesno(rpmodectl & GEN6_RP_MEDIA_TURBO)); -+ seq_printf(m, "HW control enabled: %s\n", -+ yesno(rpmodectl & GEN6_RP_ENABLE)); -+ seq_printf(m, "SW control enabled: %s\n", -+ yesno((rpmodectl & GEN6_RP_MEDIA_MODE_MASK) == -+ GEN6_RP_MEDIA_SW_MODE)); -+ -+ seq_printf(m, "PM IER=0x%08x IMR=0x%08x, MASK=0x%08x\n", -+ pm_ier, pm_imr, pm_mask); -+ if (INTEL_GEN(dev_priv) <= 10) -+ seq_printf(m, "PM ISR=0x%08x IIR=0x%08x\n", -+ pm_isr, pm_iir); -+ seq_printf(m, "pm_intrmsk_mbz: 0x%08x\n", -+ rps->pm_intrmsk_mbz); -+ seq_printf(m, "GT_PERF_STATUS: 0x%08x\n", gt_perf_status); -+ seq_printf(m, "Render p-state ratio: %d\n", -+ (gt_perf_status & (INTEL_GEN(dev_priv) >= 9 ? 0x1ff00 : 0xff00)) >> 8); -+ seq_printf(m, "Render p-state VID: %d\n", -+ gt_perf_status & 0xff); -+ seq_printf(m, "Render p-state limit: %d\n", -+ rp_state_limits & 0xff); -+ seq_printf(m, "RPSTAT1: 0x%08x\n", rpstat); -+ seq_printf(m, "RPMODECTL: 0x%08x\n", rpmodectl); -+ seq_printf(m, "RPINCLIMIT: 0x%08x\n", rpinclimit); -+ seq_printf(m, "RPDECLIMIT: 0x%08x\n", rpdeclimit); -+ seq_printf(m, "RPNSWREQ: %dMHz\n", reqf); -+ seq_printf(m, "CAGF: %dMHz\n", cagf); -+ seq_printf(m, "RP CUR UP EI: %d (%dus)\n", -+ rpupei, GT_PM_INTERVAL_TO_US(dev_priv, rpupei)); -+ seq_printf(m, "RP CUR UP: %d (%dus)\n", -+ rpcurup, GT_PM_INTERVAL_TO_US(dev_priv, rpcurup)); -+ seq_printf(m, "RP PREV UP: %d (%dus)\n", -+ rpprevup, GT_PM_INTERVAL_TO_US(dev_priv, rpprevup)); -+ seq_printf(m, "Up threshold: %d%%\n", -+ rps->power.up_threshold); -+ -+ seq_printf(m, "RP CUR DOWN EI: %d (%dus)\n", -+ rpdownei, GT_PM_INTERVAL_TO_US(dev_priv, rpdownei)); -+ seq_printf(m, "RP CUR DOWN: %d (%dus)\n", -+ rpcurdown, GT_PM_INTERVAL_TO_US(dev_priv, rpcurdown)); -+ seq_printf(m, "RP PREV DOWN: %d (%dus)\n", -+ rpprevdown, GT_PM_INTERVAL_TO_US(dev_priv, rpprevdown)); -+ seq_printf(m, "Down threshold: %d%%\n", -+ rps->power.down_threshold); -+ -+ max_freq = (IS_GEN9_LP(dev_priv) ? rp_state_cap >> 0 : -+ rp_state_cap >> 16) & 0xff; -+ max_freq *= (IS_GEN9_BC(dev_priv) || -+ INTEL_GEN(dev_priv) >= 10 ? GEN9_FREQ_SCALER : 1); -+ seq_printf(m, "Lowest (RPN) frequency: %dMHz\n", -+ intel_gpu_freq(dev_priv, max_freq)); -+ -+ max_freq = (rp_state_cap & 0xff00) >> 8; -+ max_freq *= (IS_GEN9_BC(dev_priv) || -+ INTEL_GEN(dev_priv) >= 10 ? GEN9_FREQ_SCALER : 1); -+ seq_printf(m, "Nominal (RP1) frequency: %dMHz\n", -+ intel_gpu_freq(dev_priv, max_freq)); -+ -+ max_freq = (IS_GEN9_LP(dev_priv) ? rp_state_cap >> 16 : -+ rp_state_cap >> 0) & 0xff; -+ max_freq *= (IS_GEN9_BC(dev_priv) || -+ INTEL_GEN(dev_priv) >= 10 ? GEN9_FREQ_SCALER : 1); -+ seq_printf(m, "Max non-overclocked (RP0) frequency: %dMHz\n", -+ intel_gpu_freq(dev_priv, max_freq)); -+ seq_printf(m, "Max overclocked frequency: %dMHz\n", -+ intel_gpu_freq(dev_priv, rps->max_freq)); -+ -+ seq_printf(m, "Current freq: %d MHz\n", -+ intel_gpu_freq(dev_priv, rps->cur_freq)); -+ seq_printf(m, "Actual freq: %d MHz\n", cagf); -+ seq_printf(m, "Idle freq: %d MHz\n", -+ intel_gpu_freq(dev_priv, rps->idle_freq)); -+ seq_printf(m, "Min freq: %d MHz\n", -+ intel_gpu_freq(dev_priv, rps->min_freq)); -+ seq_printf(m, "Boost freq: %d MHz\n", -+ intel_gpu_freq(dev_priv, rps->boost_freq)); -+ seq_printf(m, "Max freq: %d MHz\n", -+ intel_gpu_freq(dev_priv, rps->max_freq)); -+ seq_printf(m, -+ "efficient (RPe) frequency: %d MHz\n", -+ intel_gpu_freq(dev_priv, rps->efficient_freq)); -+ } else { -+ seq_puts(m, "no P-state info available\n"); -+ } -+ -+ seq_printf(m, "Current CD clock frequency: %d kHz\n", dev_priv->cdclk.hw.cdclk); -+ seq_printf(m, "Max CD clock frequency: %d kHz\n", dev_priv->max_cdclk_freq); -+ seq_printf(m, "Max pixel clock frequency: %d kHz\n", dev_priv->max_dotclk_freq); -+ -+ intel_runtime_pm_put(dev_priv, wakeref); -+ return ret; -+} -+ -+static void i915_instdone_info(struct drm_i915_private *dev_priv, -+ struct seq_file *m, -+ struct intel_instdone *instdone) -+{ -+ int slice; -+ int subslice; -+ -+ seq_printf(m, "\t\tINSTDONE: 0x%08x\n", -+ instdone->instdone); -+ -+ if (INTEL_GEN(dev_priv) <= 3) -+ return; -+ -+ seq_printf(m, "\t\tSC_INSTDONE: 0x%08x\n", -+ instdone->slice_common); -+ -+ if (INTEL_GEN(dev_priv) <= 6) -+ return; -+ -+ for_each_instdone_slice_subslice(dev_priv, slice, subslice) -+ seq_printf(m, "\t\tSAMPLER_INSTDONE[%d][%d]: 0x%08x\n", -+ slice, subslice, instdone->sampler[slice][subslice]); -+ -+ for_each_instdone_slice_subslice(dev_priv, slice, subslice) -+ seq_printf(m, "\t\tROW_INSTDONE[%d][%d]: 0x%08x\n", -+ slice, subslice, instdone->row[slice][subslice]); -+} -+ -+static int i915_hangcheck_info(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct intel_engine_cs *engine; -+ u64 acthd[I915_NUM_ENGINES]; -+ u32 seqno[I915_NUM_ENGINES]; -+ struct intel_instdone instdone; -+ intel_wakeref_t wakeref; -+ enum intel_engine_id id; -+ -+ seq_printf(m, "Reset flags: %lx\n", dev_priv->gpu_error.flags); -+ if (test_bit(I915_WEDGED, &dev_priv->gpu_error.flags)) -+ seq_puts(m, "\tWedged\n"); -+ if (test_bit(I915_RESET_BACKOFF, &dev_priv->gpu_error.flags)) -+ seq_puts(m, "\tDevice (global) reset in progress\n"); -+ -+ if (!i915_modparams.enable_hangcheck) { -+ seq_puts(m, "Hangcheck disabled\n"); -+ return 0; -+ } -+ -+ with_intel_runtime_pm(dev_priv, wakeref) { -+ for_each_engine(engine, dev_priv, id) { -+ acthd[id] = intel_engine_get_active_head(engine); -+ seqno[id] = intel_engine_get_hangcheck_seqno(engine); -+ } -+ -+ intel_engine_get_instdone(dev_priv->engine[RCS0], &instdone); -+ } -+ -+ if (timer_pending(&dev_priv->gpu_error.hangcheck_work.timer)) -+ seq_printf(m, "Hangcheck active, timer fires in %dms\n", -+ jiffies_to_msecs(dev_priv->gpu_error.hangcheck_work.timer.expires - -+ jiffies)); -+ else if (delayed_work_pending(&dev_priv->gpu_error.hangcheck_work)) -+ seq_puts(m, "Hangcheck active, work pending\n"); -+ else -+ seq_puts(m, "Hangcheck inactive\n"); -+ -+ seq_printf(m, "GT active? %s\n", yesno(dev_priv->gt.awake)); -+ -+ for_each_engine(engine, dev_priv, id) { -+ seq_printf(m, "%s:\n", engine->name); -+ seq_printf(m, "\tseqno = %x [current %x, last %x], %dms ago\n", -+ engine->hangcheck.last_seqno, -+ seqno[id], -+ engine->hangcheck.next_seqno, -+ jiffies_to_msecs(jiffies - -+ engine->hangcheck.action_timestamp)); -+ -+ seq_printf(m, "\tACTHD = 0x%08llx [current 0x%08llx]\n", -+ (long long)engine->hangcheck.acthd, -+ (long long)acthd[id]); -+ -+ if (engine->id == RCS0) { -+ seq_puts(m, "\tinstdone read =\n"); -+ -+ i915_instdone_info(dev_priv, m, &instdone); -+ -+ seq_puts(m, "\tinstdone accu =\n"); -+ -+ i915_instdone_info(dev_priv, m, -+ &engine->hangcheck.instdone); -+ } -+ } -+ -+ return 0; -+} -+ -+static int i915_reset_info(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct i915_gpu_error *error = &dev_priv->gpu_error; -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ -+ seq_printf(m, "full gpu reset = %u\n", i915_reset_count(error)); -+ -+ for_each_engine(engine, dev_priv, id) { -+ seq_printf(m, "%s = %u\n", engine->name, -+ i915_reset_engine_count(error, engine)); -+ } -+ -+ return 0; -+} -+ -+static int ironlake_drpc_info(struct seq_file *m) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ u32 rgvmodectl, rstdbyctl; -+ u16 crstandvid; -+ -+ rgvmodectl = I915_READ(MEMMODECTL); -+ rstdbyctl = I915_READ(RSTDBYCTL); -+ crstandvid = I915_READ16(CRSTANDVID); -+ -+ seq_printf(m, "HD boost: %s\n", yesno(rgvmodectl & MEMMODE_BOOST_EN)); -+ seq_printf(m, "Boost freq: %d\n", -+ (rgvmodectl & MEMMODE_BOOST_FREQ_MASK) >> -+ MEMMODE_BOOST_FREQ_SHIFT); -+ seq_printf(m, "HW control enabled: %s\n", -+ yesno(rgvmodectl & MEMMODE_HWIDLE_EN)); -+ seq_printf(m, "SW control enabled: %s\n", -+ yesno(rgvmodectl & MEMMODE_SWMODE_EN)); -+ seq_printf(m, "Gated voltage change: %s\n", -+ yesno(rgvmodectl & MEMMODE_RCLK_GATE)); -+ seq_printf(m, "Starting frequency: P%d\n", -+ (rgvmodectl & MEMMODE_FSTART_MASK) >> MEMMODE_FSTART_SHIFT); -+ seq_printf(m, "Max P-state: P%d\n", -+ (rgvmodectl & MEMMODE_FMAX_MASK) >> MEMMODE_FMAX_SHIFT); -+ seq_printf(m, "Min P-state: P%d\n", (rgvmodectl & MEMMODE_FMIN_MASK)); -+ seq_printf(m, "RS1 VID: %d\n", (crstandvid & 0x3f)); -+ seq_printf(m, "RS2 VID: %d\n", ((crstandvid >> 8) & 0x3f)); -+ seq_printf(m, "Render standby enabled: %s\n", -+ yesno(!(rstdbyctl & RCX_SW_EXIT))); -+ seq_puts(m, "Current RS state: "); -+ switch (rstdbyctl & RSX_STATUS_MASK) { -+ case RSX_STATUS_ON: -+ seq_puts(m, "on\n"); -+ break; -+ case RSX_STATUS_RC1: -+ seq_puts(m, "RC1\n"); -+ break; -+ case RSX_STATUS_RC1E: -+ seq_puts(m, "RC1E\n"); -+ break; -+ case RSX_STATUS_RS1: -+ seq_puts(m, "RS1\n"); -+ break; -+ case RSX_STATUS_RS2: -+ seq_puts(m, "RS2 (RC6)\n"); -+ break; -+ case RSX_STATUS_RS3: -+ seq_puts(m, "RC3 (RC6+)\n"); -+ break; -+ default: -+ seq_puts(m, "unknown\n"); -+ break; -+ } -+ -+ return 0; -+} -+ -+static int i915_forcewake_domains(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *i915 = node_to_i915(m->private); -+ struct intel_uncore *uncore = &i915->uncore; -+ struct intel_uncore_forcewake_domain *fw_domain; -+ unsigned int tmp; -+ -+ seq_printf(m, "user.bypass_count = %u\n", -+ uncore->user_forcewake.count); -+ -+ for_each_fw_domain(fw_domain, uncore, tmp) -+ seq_printf(m, "%s.wake_count = %u\n", -+ intel_uncore_forcewake_domain_to_str(fw_domain->id), -+ READ_ONCE(fw_domain->wake_count)); -+ -+ return 0; -+} -+ -+static void print_rc6_res(struct seq_file *m, -+ const char *title, -+ const i915_reg_t reg) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ -+ seq_printf(m, "%s %u (%llu us)\n", -+ title, I915_READ(reg), -+ intel_rc6_residency_us(dev_priv, reg)); -+} -+ -+static int vlv_drpc_info(struct seq_file *m) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ u32 rcctl1, pw_status; -+ -+ pw_status = I915_READ(VLV_GTLC_PW_STATUS); -+ rcctl1 = I915_READ(GEN6_RC_CONTROL); -+ -+ seq_printf(m, "RC6 Enabled: %s\n", -+ yesno(rcctl1 & (GEN7_RC_CTL_TO_MODE | -+ GEN6_RC_CTL_EI_MODE(1)))); -+ seq_printf(m, "Render Power Well: %s\n", -+ (pw_status & VLV_GTLC_PW_RENDER_STATUS_MASK) ? "Up" : "Down"); -+ seq_printf(m, "Media Power Well: %s\n", -+ (pw_status & VLV_GTLC_PW_MEDIA_STATUS_MASK) ? "Up" : "Down"); -+ -+ print_rc6_res(m, "Render RC6 residency since boot:", VLV_GT_RENDER_RC6); -+ print_rc6_res(m, "Media RC6 residency since boot:", VLV_GT_MEDIA_RC6); -+ -+ return i915_forcewake_domains(m, NULL); -+} -+ -+static int gen6_drpc_info(struct seq_file *m) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ u32 gt_core_status, rcctl1, rc6vids = 0; -+ u32 gen9_powergate_enable = 0, gen9_powergate_status = 0; -+ -+ gt_core_status = I915_READ_FW(GEN6_GT_CORE_STATUS); -+ trace_i915_reg_rw(false, GEN6_GT_CORE_STATUS, gt_core_status, 4, true); -+ -+ rcctl1 = I915_READ(GEN6_RC_CONTROL); -+ if (INTEL_GEN(dev_priv) >= 9) { -+ gen9_powergate_enable = I915_READ(GEN9_PG_ENABLE); -+ gen9_powergate_status = I915_READ(GEN9_PWRGT_DOMAIN_STATUS); -+ } -+ -+ if (INTEL_GEN(dev_priv) <= 7) { -+ mutex_lock(&dev_priv->pcu_lock); -+ sandybridge_pcode_read(dev_priv, GEN6_PCODE_READ_RC6VIDS, -+ &rc6vids); -+ mutex_unlock(&dev_priv->pcu_lock); -+ } -+ -+ seq_printf(m, "RC1e Enabled: %s\n", -+ yesno(rcctl1 & GEN6_RC_CTL_RC1e_ENABLE)); -+ seq_printf(m, "RC6 Enabled: %s\n", -+ yesno(rcctl1 & GEN6_RC_CTL_RC6_ENABLE)); -+ if (INTEL_GEN(dev_priv) >= 9) { -+ seq_printf(m, "Render Well Gating Enabled: %s\n", -+ yesno(gen9_powergate_enable & GEN9_RENDER_PG_ENABLE)); -+ seq_printf(m, "Media Well Gating Enabled: %s\n", -+ yesno(gen9_powergate_enable & GEN9_MEDIA_PG_ENABLE)); -+ } -+ seq_printf(m, "Deep RC6 Enabled: %s\n", -+ yesno(rcctl1 & GEN6_RC_CTL_RC6p_ENABLE)); -+ seq_printf(m, "Deepest RC6 Enabled: %s\n", -+ yesno(rcctl1 & GEN6_RC_CTL_RC6pp_ENABLE)); -+ seq_puts(m, "Current RC state: "); -+ switch (gt_core_status & GEN6_RCn_MASK) { -+ case GEN6_RC0: -+ if (gt_core_status & GEN6_CORE_CPD_STATE_MASK) -+ seq_puts(m, "Core Power Down\n"); -+ else -+ seq_puts(m, "on\n"); -+ break; -+ case GEN6_RC3: -+ seq_puts(m, "RC3\n"); -+ break; -+ case GEN6_RC6: -+ seq_puts(m, "RC6\n"); -+ break; -+ case GEN6_RC7: -+ seq_puts(m, "RC7\n"); -+ break; -+ default: -+ seq_puts(m, "Unknown\n"); -+ break; -+ } -+ -+ seq_printf(m, "Core Power Down: %s\n", -+ yesno(gt_core_status & GEN6_CORE_CPD_STATE_MASK)); -+ if (INTEL_GEN(dev_priv) >= 9) { -+ seq_printf(m, "Render Power Well: %s\n", -+ (gen9_powergate_status & -+ GEN9_PWRGT_RENDER_STATUS_MASK) ? "Up" : "Down"); -+ seq_printf(m, "Media Power Well: %s\n", -+ (gen9_powergate_status & -+ GEN9_PWRGT_MEDIA_STATUS_MASK) ? "Up" : "Down"); -+ } -+ -+ /* Not exactly sure what this is */ -+ print_rc6_res(m, "RC6 \"Locked to RPn\" residency since boot:", -+ GEN6_GT_GFX_RC6_LOCKED); -+ print_rc6_res(m, "RC6 residency since boot:", GEN6_GT_GFX_RC6); -+ print_rc6_res(m, "RC6+ residency since boot:", GEN6_GT_GFX_RC6p); -+ print_rc6_res(m, "RC6++ residency since boot:", GEN6_GT_GFX_RC6pp); -+ -+ if (INTEL_GEN(dev_priv) <= 7) { -+ seq_printf(m, "RC6 voltage: %dmV\n", -+ GEN6_DECODE_RC6_VID(((rc6vids >> 0) & 0xff))); -+ seq_printf(m, "RC6+ voltage: %dmV\n", -+ GEN6_DECODE_RC6_VID(((rc6vids >> 8) & 0xff))); -+ seq_printf(m, "RC6++ voltage: %dmV\n", -+ GEN6_DECODE_RC6_VID(((rc6vids >> 16) & 0xff))); -+ } -+ -+ return i915_forcewake_domains(m, NULL); -+} -+ -+static int i915_drpc_info(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ intel_wakeref_t wakeref; -+ int err = -ENODEV; -+ -+ with_intel_runtime_pm(dev_priv, wakeref) { -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ err = vlv_drpc_info(m); -+ else if (INTEL_GEN(dev_priv) >= 6) -+ err = gen6_drpc_info(m); -+ else -+ err = ironlake_drpc_info(m); -+ } -+ -+ return err; -+} -+ -+static int i915_frontbuffer_tracking(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ -+ seq_printf(m, "FB tracking busy bits: 0x%08x\n", -+ dev_priv->fb_tracking.busy_bits); -+ -+ seq_printf(m, "FB tracking flip bits: 0x%08x\n", -+ dev_priv->fb_tracking.flip_bits); -+ -+ return 0; -+} -+ -+static int i915_fbc_status(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct intel_fbc *fbc = &dev_priv->fbc; -+ intel_wakeref_t wakeref; -+ -+ if (!HAS_FBC(dev_priv)) -+ return -ENODEV; -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ mutex_lock(&fbc->lock); -+ -+ if (intel_fbc_is_active(dev_priv)) -+ seq_puts(m, "FBC enabled\n"); -+ else -+ seq_printf(m, "FBC disabled: %s\n", fbc->no_fbc_reason); -+ -+ if (intel_fbc_is_active(dev_priv)) { -+ u32 mask; -+ -+ if (INTEL_GEN(dev_priv) >= 8) -+ mask = I915_READ(IVB_FBC_STATUS2) & BDW_FBC_COMP_SEG_MASK; -+ else if (INTEL_GEN(dev_priv) >= 7) -+ mask = I915_READ(IVB_FBC_STATUS2) & IVB_FBC_COMP_SEG_MASK; -+ else if (INTEL_GEN(dev_priv) >= 5) -+ mask = I915_READ(ILK_DPFC_STATUS) & ILK_DPFC_COMP_SEG_MASK; -+ else if (IS_G4X(dev_priv)) -+ mask = I915_READ(DPFC_STATUS) & DPFC_COMP_SEG_MASK; -+ else -+ mask = I915_READ(FBC_STATUS) & (FBC_STAT_COMPRESSING | -+ FBC_STAT_COMPRESSED); -+ -+ seq_printf(m, "Compressing: %s\n", yesno(mask)); -+ } -+ -+ mutex_unlock(&fbc->lock); -+ intel_runtime_pm_put(dev_priv, wakeref); -+ -+ return 0; -+} -+ -+static int i915_fbc_false_color_get(void *data, u64 *val) -+{ -+ struct drm_i915_private *dev_priv = data; -+ -+ if (INTEL_GEN(dev_priv) < 7 || !HAS_FBC(dev_priv)) -+ return -ENODEV; -+ -+ *val = dev_priv->fbc.false_color; -+ -+ return 0; -+} -+ -+static int i915_fbc_false_color_set(void *data, u64 val) -+{ -+ struct drm_i915_private *dev_priv = data; -+ u32 reg; -+ -+ if (INTEL_GEN(dev_priv) < 7 || !HAS_FBC(dev_priv)) -+ return -ENODEV; -+ -+ mutex_lock(&dev_priv->fbc.lock); -+ -+ reg = I915_READ(ILK_DPFC_CONTROL); -+ dev_priv->fbc.false_color = val; -+ -+ I915_WRITE(ILK_DPFC_CONTROL, val ? -+ (reg | FBC_CTL_FALSE_COLOR) : -+ (reg & ~FBC_CTL_FALSE_COLOR)); -+ -+ mutex_unlock(&dev_priv->fbc.lock); -+ return 0; -+} -+ -+DEFINE_SIMPLE_ATTRIBUTE(i915_fbc_false_color_fops, -+ i915_fbc_false_color_get, i915_fbc_false_color_set, -+ "%llu\n"); -+ -+static int i915_ips_status(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ intel_wakeref_t wakeref; -+ -+ if (!HAS_IPS(dev_priv)) -+ return -ENODEV; -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ -+ seq_printf(m, "Enabled by kernel parameter: %s\n", -+ yesno(i915_modparams.enable_ips)); -+ -+ if (INTEL_GEN(dev_priv) >= 8) { -+ seq_puts(m, "Currently: unknown\n"); -+ } else { -+ if (I915_READ(IPS_CTL) & IPS_ENABLE) -+ seq_puts(m, "Currently: enabled\n"); -+ else -+ seq_puts(m, "Currently: disabled\n"); -+ } -+ -+ intel_runtime_pm_put(dev_priv, wakeref); -+ -+ return 0; -+} -+ -+static int i915_sr_status(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ intel_wakeref_t wakeref; -+ bool sr_enabled = false; -+ -+ wakeref = intel_display_power_get(dev_priv, POWER_DOMAIN_INIT); -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ /* no global SR status; inspect per-plane WM */; -+ else if (HAS_PCH_SPLIT(dev_priv)) -+ sr_enabled = I915_READ(WM1_LP_ILK) & WM1_LP_SR_EN; -+ else if (IS_I965GM(dev_priv) || IS_G4X(dev_priv) || -+ IS_I945G(dev_priv) || IS_I945GM(dev_priv)) -+ sr_enabled = I915_READ(FW_BLC_SELF) & FW_BLC_SELF_EN; -+ else if (IS_I915GM(dev_priv)) -+ sr_enabled = I915_READ(INSTPM) & INSTPM_SELF_EN; -+ else if (IS_PINEVIEW(dev_priv)) -+ sr_enabled = I915_READ(DSPFW3) & PINEVIEW_SELF_REFRESH_EN; -+ else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ sr_enabled = I915_READ(FW_BLC_SELF_VLV) & FW_CSPWRDWNEN; -+ -+ intel_display_power_put(dev_priv, POWER_DOMAIN_INIT, wakeref); -+ -+ seq_printf(m, "self-refresh: %s\n", enableddisabled(sr_enabled)); -+ -+ return 0; -+} -+ -+static int i915_emon_status(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *i915 = node_to_i915(m->private); -+ intel_wakeref_t wakeref; -+ -+ if (!IS_GEN(i915, 5)) -+ return -ENODEV; -+ -+ with_intel_runtime_pm(i915, wakeref) { -+ unsigned long temp, chipset, gfx; -+ -+ temp = i915_mch_val(i915); -+ chipset = i915_chipset_val(i915); -+ gfx = i915_gfx_val(i915); -+ -+ seq_printf(m, "GMCH temp: %ld\n", temp); -+ seq_printf(m, "Chipset power: %ld\n", chipset); -+ seq_printf(m, "GFX power: %ld\n", gfx); -+ seq_printf(m, "Total power: %ld\n", chipset + gfx); -+ } -+ -+ return 0; -+} -+ -+static int i915_ring_freq_table(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct intel_rps *rps = &dev_priv->gt_pm.rps; -+ unsigned int max_gpu_freq, min_gpu_freq; -+ intel_wakeref_t wakeref; -+ int gpu_freq, ia_freq; -+ int ret; -+ -+ if (!HAS_LLC(dev_priv)) -+ return -ENODEV; -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ -+ ret = mutex_lock_interruptible(&dev_priv->pcu_lock); -+ if (ret) -+ goto out; -+ -+ min_gpu_freq = rps->min_freq; -+ max_gpu_freq = rps->max_freq; -+ if (IS_GEN9_BC(dev_priv) || INTEL_GEN(dev_priv) >= 10) { -+ /* Convert GT frequency to 50 HZ units */ -+ min_gpu_freq /= GEN9_FREQ_SCALER; -+ max_gpu_freq /= GEN9_FREQ_SCALER; -+ } -+ -+ seq_puts(m, "GPU freq (MHz)\tEffective CPU freq (MHz)\tEffective Ring freq (MHz)\n"); -+ -+ for (gpu_freq = min_gpu_freq; gpu_freq <= max_gpu_freq; gpu_freq++) { -+ ia_freq = gpu_freq; -+ sandybridge_pcode_read(dev_priv, -+ GEN6_PCODE_READ_MIN_FREQ_TABLE, -+ &ia_freq); -+ seq_printf(m, "%d\t\t%d\t\t\t\t%d\n", -+ intel_gpu_freq(dev_priv, (gpu_freq * -+ (IS_GEN9_BC(dev_priv) || -+ INTEL_GEN(dev_priv) >= 10 ? -+ GEN9_FREQ_SCALER : 1))), -+ ((ia_freq >> 0) & 0xff) * 100, -+ ((ia_freq >> 8) & 0xff) * 100); -+ } -+ -+ mutex_unlock(&dev_priv->pcu_lock); -+ -+out: -+ intel_runtime_pm_put(dev_priv, wakeref); -+ return ret; -+} -+ -+static int i915_opregion(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ struct intel_opregion *opregion = &dev_priv->opregion; -+ int ret; -+ -+ ret = mutex_lock_interruptible(&dev->struct_mutex); -+ if (ret) -+ goto out; -+ -+ if (opregion->header) -+ seq_write(m, opregion->header, OPREGION_SIZE); -+ -+ mutex_unlock(&dev->struct_mutex); -+ -+out: -+ return 0; -+} -+ -+static int i915_vbt(struct seq_file *m, void *unused) -+{ -+ struct intel_opregion *opregion = &node_to_i915(m->private)->opregion; -+ -+ if (opregion->vbt) -+ seq_write(m, opregion->vbt, opregion->vbt_size); -+ -+ return 0; -+} -+ -+static int i915_gem_framebuffer_info(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ struct intel_framebuffer *fbdev_fb = NULL; -+ struct drm_framebuffer *drm_fb; -+ int ret; -+ -+ ret = mutex_lock_interruptible(&dev->struct_mutex); -+ if (ret) -+ return ret; -+ -+#ifdef CONFIG_DRM_FBDEV_EMULATION -+ if (dev_priv->fbdev && dev_priv->fbdev->helper.fb) { -+ fbdev_fb = to_intel_framebuffer(dev_priv->fbdev->helper.fb); -+ -+ seq_printf(m, "fbcon size: %d x %d, depth %d, %d bpp, modifier 0x%llx, refcount %d, obj ", -+ fbdev_fb->base.width, -+ fbdev_fb->base.height, -+ fbdev_fb->base.format->depth, -+ fbdev_fb->base.format->cpp[0] * 8, -+ fbdev_fb->base.modifier, -+ drm_framebuffer_read_refcount(&fbdev_fb->base)); -+ describe_obj(m, intel_fb_obj(&fbdev_fb->base)); -+ seq_putc(m, '\n'); -+ } -+#endif -+ -+ mutex_lock(&dev->mode_config.fb_lock); -+ drm_for_each_fb(drm_fb, dev) { -+ struct intel_framebuffer *fb = to_intel_framebuffer(drm_fb); -+ if (fb == fbdev_fb) -+ continue; -+ -+ seq_printf(m, "user size: %d x %d, depth %d, %d bpp, modifier 0x%llx, refcount %d, obj ", -+ fb->base.width, -+ fb->base.height, -+ fb->base.format->depth, -+ fb->base.format->cpp[0] * 8, -+ fb->base.modifier, -+ drm_framebuffer_read_refcount(&fb->base)); -+ describe_obj(m, intel_fb_obj(&fb->base)); -+ seq_putc(m, '\n'); -+ } -+ mutex_unlock(&dev->mode_config.fb_lock); -+ mutex_unlock(&dev->struct_mutex); -+ -+ return 0; -+} -+ -+static void describe_ctx_ring(struct seq_file *m, struct intel_ring *ring) -+{ -+ seq_printf(m, " (ringbuffer, space: %d, head: %u, tail: %u, emit: %u)", -+ ring->space, ring->head, ring->tail, ring->emit); -+} -+ -+static int i915_context_status(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ struct i915_gem_context *ctx; -+ int ret; -+ -+ ret = mutex_lock_interruptible(&dev->struct_mutex); -+ if (ret) -+ return ret; -+ -+ list_for_each_entry(ctx, &dev_priv->contexts.list, link) { -+ struct intel_context *ce; -+ -+ seq_puts(m, "HW context "); -+ if (!list_empty(&ctx->hw_id_link)) -+ seq_printf(m, "%x [pin %u]", ctx->hw_id, -+ atomic_read(&ctx->hw_id_pin_count)); -+ if (ctx->pid) { -+ struct task_struct *task; -+ -+ task = get_pid_task(ctx->pid, PIDTYPE_PID); -+ if (task) { -+ seq_printf(m, "(%s [%d]) ", -+ task->comm, task->pid); -+ put_task_struct(task); -+ } -+ } else if (IS_ERR(ctx->file_priv)) { -+ seq_puts(m, "(deleted) "); -+ } else { -+ seq_puts(m, "(kernel) "); -+ } -+ -+ seq_putc(m, ctx->remap_slice ? 'R' : 'r'); -+ seq_putc(m, '\n'); -+ -+ list_for_each_entry(ce, &ctx->active_engines, active_link) { -+ seq_printf(m, "%s: ", ce->engine->name); -+ if (ce->state) -+ describe_obj(m, ce->state->obj); -+ if (ce->ring) -+ describe_ctx_ring(m, ce->ring); -+ seq_putc(m, '\n'); -+ } -+ -+ seq_putc(m, '\n'); -+ } -+ -+ mutex_unlock(&dev->struct_mutex); -+ -+ return 0; -+} -+ -+static const char *swizzle_string(unsigned swizzle) -+{ -+ switch (swizzle) { -+ case I915_BIT_6_SWIZZLE_NONE: -+ return "none"; -+ case I915_BIT_6_SWIZZLE_9: -+ return "bit9"; -+ case I915_BIT_6_SWIZZLE_9_10: -+ return "bit9/bit10"; -+ case I915_BIT_6_SWIZZLE_9_11: -+ return "bit9/bit11"; -+ case I915_BIT_6_SWIZZLE_9_10_11: -+ return "bit9/bit10/bit11"; -+ case I915_BIT_6_SWIZZLE_9_17: -+ return "bit9/bit17"; -+ case I915_BIT_6_SWIZZLE_9_10_17: -+ return "bit9/bit10/bit17"; -+ case I915_BIT_6_SWIZZLE_UNKNOWN: -+ return "unknown"; -+ } -+ -+ return "bug"; -+} -+ -+static int i915_swizzle_info(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ intel_wakeref_t wakeref; -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ -+ seq_printf(m, "bit6 swizzle for X-tiling = %s\n", -+ swizzle_string(dev_priv->mm.bit_6_swizzle_x)); -+ seq_printf(m, "bit6 swizzle for Y-tiling = %s\n", -+ swizzle_string(dev_priv->mm.bit_6_swizzle_y)); -+ -+ if (IS_GEN_RANGE(dev_priv, 3, 4)) { -+ seq_printf(m, "DDC = 0x%08x\n", -+ I915_READ(DCC)); -+ seq_printf(m, "DDC2 = 0x%08x\n", -+ I915_READ(DCC2)); -+ seq_printf(m, "C0DRB3 = 0x%04x\n", -+ I915_READ16(C0DRB3)); -+ seq_printf(m, "C1DRB3 = 0x%04x\n", -+ I915_READ16(C1DRB3)); -+ } else if (INTEL_GEN(dev_priv) >= 6) { -+ seq_printf(m, "MAD_DIMM_C0 = 0x%08x\n", -+ I915_READ(MAD_DIMM_C0)); -+ seq_printf(m, "MAD_DIMM_C1 = 0x%08x\n", -+ I915_READ(MAD_DIMM_C1)); -+ seq_printf(m, "MAD_DIMM_C2 = 0x%08x\n", -+ I915_READ(MAD_DIMM_C2)); -+ seq_printf(m, "TILECTL = 0x%08x\n", -+ I915_READ(TILECTL)); -+ if (INTEL_GEN(dev_priv) >= 8) -+ seq_printf(m, "GAMTARBMODE = 0x%08x\n", -+ I915_READ(GAMTARBMODE)); -+ else -+ seq_printf(m, "ARB_MODE = 0x%08x\n", -+ I915_READ(ARB_MODE)); -+ seq_printf(m, "DISP_ARB_CTL = 0x%08x\n", -+ I915_READ(DISP_ARB_CTL)); -+ } -+ -+ if (dev_priv->quirks & QUIRK_PIN_SWIZZLED_PAGES) -+ seq_puts(m, "L-shaped memory detected\n"); -+ -+ intel_runtime_pm_put(dev_priv, wakeref); -+ -+ return 0; -+} -+ -+static const char *rps_power_to_str(unsigned int power) -+{ -+ static const char * const strings[] = { -+ [LOW_POWER] = "low power", -+ [BETWEEN] = "mixed", -+ [HIGH_POWER] = "high power", -+ }; -+ -+ if (power >= ARRAY_SIZE(strings) || !strings[power]) -+ return "unknown"; -+ -+ return strings[power]; -+} -+ -+static int i915_rps_boost_info(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct intel_rps *rps = &dev_priv->gt_pm.rps; -+ u32 act_freq = rps->cur_freq; -+ intel_wakeref_t wakeref; -+ -+ with_intel_runtime_pm_if_in_use(dev_priv, wakeref) { -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ mutex_lock(&dev_priv->pcu_lock); -+ act_freq = vlv_punit_read(dev_priv, -+ PUNIT_REG_GPU_FREQ_STS); -+ act_freq = (act_freq >> 8) & 0xff; -+ mutex_unlock(&dev_priv->pcu_lock); -+ } else { -+ act_freq = intel_get_cagf(dev_priv, -+ I915_READ(GEN6_RPSTAT1)); -+ } -+ } -+ -+ seq_printf(m, "RPS enabled? %d\n", rps->enabled); -+ seq_printf(m, "GPU busy? %s [%d requests]\n", -+ yesno(dev_priv->gt.awake), dev_priv->gt.active_requests); -+ seq_printf(m, "Boosts outstanding? %d\n", -+ atomic_read(&rps->num_waiters)); -+ seq_printf(m, "Interactive? %d\n", READ_ONCE(rps->power.interactive)); -+ seq_printf(m, "Frequency requested %d, actual %d\n", -+ intel_gpu_freq(dev_priv, rps->cur_freq), -+ intel_gpu_freq(dev_priv, act_freq)); -+ seq_printf(m, " min hard:%d, soft:%d; max soft:%d, hard:%d\n", -+ intel_gpu_freq(dev_priv, rps->min_freq), -+ intel_gpu_freq(dev_priv, rps->min_freq_softlimit), -+ intel_gpu_freq(dev_priv, rps->max_freq_softlimit), -+ intel_gpu_freq(dev_priv, rps->max_freq)); -+ seq_printf(m, " idle:%d, efficient:%d, boost:%d\n", -+ intel_gpu_freq(dev_priv, rps->idle_freq), -+ intel_gpu_freq(dev_priv, rps->efficient_freq), -+ intel_gpu_freq(dev_priv, rps->boost_freq)); -+ -+ seq_printf(m, "Wait boosts: %d\n", atomic_read(&rps->boosts)); -+ -+ if (INTEL_GEN(dev_priv) >= 6 && -+ rps->enabled && -+ dev_priv->gt.active_requests) { -+ u32 rpup, rpupei; -+ u32 rpdown, rpdownei; -+ -+ intel_uncore_forcewake_get(&dev_priv->uncore, FORCEWAKE_ALL); -+ rpup = I915_READ_FW(GEN6_RP_CUR_UP) & GEN6_RP_EI_MASK; -+ rpupei = I915_READ_FW(GEN6_RP_CUR_UP_EI) & GEN6_RP_EI_MASK; -+ rpdown = I915_READ_FW(GEN6_RP_CUR_DOWN) & GEN6_RP_EI_MASK; -+ rpdownei = I915_READ_FW(GEN6_RP_CUR_DOWN_EI) & GEN6_RP_EI_MASK; -+ intel_uncore_forcewake_put(&dev_priv->uncore, FORCEWAKE_ALL); -+ -+ seq_printf(m, "\nRPS Autotuning (current \"%s\" window):\n", -+ rps_power_to_str(rps->power.mode)); -+ seq_printf(m, " Avg. up: %d%% [above threshold? %d%%]\n", -+ rpup && rpupei ? 100 * rpup / rpupei : 0, -+ rps->power.up_threshold); -+ seq_printf(m, " Avg. down: %d%% [below threshold? %d%%]\n", -+ rpdown && rpdownei ? 100 * rpdown / rpdownei : 0, -+ rps->power.down_threshold); -+ } else { -+ seq_puts(m, "\nRPS Autotuning inactive\n"); -+ } -+ -+ return 0; -+} -+ -+static int i915_llc(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ const bool edram = INTEL_GEN(dev_priv) > 8; -+ -+ seq_printf(m, "LLC: %s\n", yesno(HAS_LLC(dev_priv))); -+ seq_printf(m, "%s: %uMB\n", edram ? "eDRAM" : "eLLC", -+ dev_priv->edram_size_mb); -+ -+ return 0; -+} -+ -+static int i915_huc_load_status_info(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ intel_wakeref_t wakeref; -+ struct drm_printer p; -+ -+ if (!HAS_HUC(dev_priv)) -+ return -ENODEV; -+ -+ p = drm_seq_file_printer(m); -+ intel_uc_fw_dump(&dev_priv->huc.fw, &p); -+ -+ with_intel_runtime_pm(dev_priv, wakeref) -+ seq_printf(m, "\nHuC status 0x%08x:\n", I915_READ(HUC_STATUS2)); -+ -+ return 0; -+} -+ -+static int i915_guc_load_status_info(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ intel_wakeref_t wakeref; -+ struct drm_printer p; -+ -+ if (!HAS_GUC(dev_priv)) -+ return -ENODEV; -+ -+ p = drm_seq_file_printer(m); -+ intel_uc_fw_dump(&dev_priv->guc.fw, &p); -+ -+ with_intel_runtime_pm(dev_priv, wakeref) { -+ u32 tmp = I915_READ(GUC_STATUS); -+ u32 i; -+ -+ seq_printf(m, "\nGuC status 0x%08x:\n", tmp); -+ seq_printf(m, "\tBootrom status = 0x%x\n", -+ (tmp & GS_BOOTROM_MASK) >> GS_BOOTROM_SHIFT); -+ seq_printf(m, "\tuKernel status = 0x%x\n", -+ (tmp & GS_UKERNEL_MASK) >> GS_UKERNEL_SHIFT); -+ seq_printf(m, "\tMIA Core status = 0x%x\n", -+ (tmp & GS_MIA_MASK) >> GS_MIA_SHIFT); -+ seq_puts(m, "\nScratch registers:\n"); -+ for (i = 0; i < 16; i++) { -+ seq_printf(m, "\t%2d: \t0x%x\n", -+ i, I915_READ(SOFT_SCRATCH(i))); -+ } -+ } -+ -+ return 0; -+} -+ -+static const char * -+stringify_guc_log_type(enum guc_log_buffer_type type) -+{ -+ switch (type) { -+ case GUC_ISR_LOG_BUFFER: -+ return "ISR"; -+ case GUC_DPC_LOG_BUFFER: -+ return "DPC"; -+ case GUC_CRASH_DUMP_LOG_BUFFER: -+ return "CRASH"; -+ default: -+ MISSING_CASE(type); -+ } -+ -+ return ""; -+} -+ -+static void i915_guc_log_info(struct seq_file *m, -+ struct drm_i915_private *dev_priv) -+{ -+ struct intel_guc_log *log = &dev_priv->guc.log; -+ enum guc_log_buffer_type type; -+ -+ if (!intel_guc_log_relay_enabled(log)) { -+ seq_puts(m, "GuC log relay disabled\n"); -+ return; -+ } -+ -+ seq_puts(m, "GuC logging stats:\n"); -+ -+ seq_printf(m, "\tRelay full count: %u\n", -+ log->relay.full_count); -+ -+ for (type = GUC_ISR_LOG_BUFFER; type < GUC_MAX_LOG_BUFFER; type++) { -+ seq_printf(m, "\t%s:\tflush count %10u, overflow count %10u\n", -+ stringify_guc_log_type(type), -+ log->stats[type].flush, -+ log->stats[type].sampled_overflow); -+ } -+} -+ -+static void i915_guc_client_info(struct seq_file *m, -+ struct drm_i915_private *dev_priv, -+ struct intel_guc_client *client) -+{ -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ u64 tot = 0; -+ -+ seq_printf(m, "\tPriority %d, GuC stage index: %u, PD offset 0x%x\n", -+ client->priority, client->stage_id, client->proc_desc_offset); -+ seq_printf(m, "\tDoorbell id %d, offset: 0x%lx\n", -+ client->doorbell_id, client->doorbell_offset); -+ -+ for_each_engine(engine, dev_priv, id) { -+ u64 submissions = client->submissions[id]; -+ tot += submissions; -+ seq_printf(m, "\tSubmissions: %llu %s\n", -+ submissions, engine->name); -+ } -+ seq_printf(m, "\tTotal: %llu\n", tot); -+} -+ -+static int i915_guc_info(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ const struct intel_guc *guc = &dev_priv->guc; -+ -+ if (!USES_GUC(dev_priv)) -+ return -ENODEV; -+ -+ i915_guc_log_info(m, dev_priv); -+ -+ if (!USES_GUC_SUBMISSION(dev_priv)) -+ return 0; -+ -+ GEM_BUG_ON(!guc->execbuf_client); -+ -+ seq_printf(m, "\nDoorbell map:\n"); -+ seq_printf(m, "\t%*pb\n", GUC_NUM_DOORBELLS, guc->doorbell_bitmap); -+ seq_printf(m, "Doorbell next cacheline: 0x%x\n", guc->db_cacheline); -+ -+ seq_printf(m, "\nGuC execbuf client @ %p:\n", guc->execbuf_client); -+ i915_guc_client_info(m, dev_priv, guc->execbuf_client); -+ if (guc->preempt_client) { -+ seq_printf(m, "\nGuC preempt client @ %p:\n", -+ guc->preempt_client); -+ i915_guc_client_info(m, dev_priv, guc->preempt_client); -+ } -+ -+ /* Add more as required ... */ -+ -+ return 0; -+} -+ -+static int i915_guc_stage_pool(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ const struct intel_guc *guc = &dev_priv->guc; -+ struct guc_stage_desc *desc = guc->stage_desc_pool_vaddr; -+ struct intel_guc_client *client = guc->execbuf_client; -+ intel_engine_mask_t tmp; -+ int index; -+ -+ if (!USES_GUC_SUBMISSION(dev_priv)) -+ return -ENODEV; -+ -+ for (index = 0; index < GUC_MAX_STAGE_DESCRIPTORS; index++, desc++) { -+ struct intel_engine_cs *engine; -+ -+ if (!(desc->attribute & GUC_STAGE_DESC_ATTR_ACTIVE)) -+ continue; -+ -+ seq_printf(m, "GuC stage descriptor %u:\n", index); -+ seq_printf(m, "\tIndex: %u\n", desc->stage_id); -+ seq_printf(m, "\tAttribute: 0x%x\n", desc->attribute); -+ seq_printf(m, "\tPriority: %d\n", desc->priority); -+ seq_printf(m, "\tDoorbell id: %d\n", desc->db_id); -+ seq_printf(m, "\tEngines used: 0x%x\n", -+ desc->engines_used); -+ seq_printf(m, "\tDoorbell trigger phy: 0x%llx, cpu: 0x%llx, uK: 0x%x\n", -+ desc->db_trigger_phy, -+ desc->db_trigger_cpu, -+ desc->db_trigger_uk); -+ seq_printf(m, "\tProcess descriptor: 0x%x\n", -+ desc->process_desc); -+ seq_printf(m, "\tWorkqueue address: 0x%x, size: 0x%x\n", -+ desc->wq_addr, desc->wq_size); -+ seq_putc(m, '\n'); -+ -+ for_each_engine_masked(engine, dev_priv, client->engines, tmp) { -+ u32 guc_engine_id = engine->guc_id; -+ struct guc_execlist_context *lrc = -+ &desc->lrc[guc_engine_id]; -+ -+ seq_printf(m, "\t%s LRC:\n", engine->name); -+ seq_printf(m, "\t\tContext desc: 0x%x\n", -+ lrc->context_desc); -+ seq_printf(m, "\t\tContext id: 0x%x\n", lrc->context_id); -+ seq_printf(m, "\t\tLRCA: 0x%x\n", lrc->ring_lrca); -+ seq_printf(m, "\t\tRing begin: 0x%x\n", lrc->ring_begin); -+ seq_printf(m, "\t\tRing end: 0x%x\n", lrc->ring_end); -+ seq_putc(m, '\n'); -+ } -+ } -+ -+ return 0; -+} -+ -+static int i915_guc_log_dump(struct seq_file *m, void *data) -+{ -+ struct drm_info_node *node = m->private; -+ struct drm_i915_private *dev_priv = node_to_i915(node); -+ bool dump_load_err = !!node->info_ent->data; -+ struct drm_i915_gem_object *obj = NULL; -+ u32 *log; -+ int i = 0; -+ -+ if (!HAS_GUC(dev_priv)) -+ return -ENODEV; -+ -+ if (dump_load_err) -+ obj = dev_priv->guc.load_err_log; -+ else if (dev_priv->guc.log.vma) -+ obj = dev_priv->guc.log.vma->obj; -+ -+ if (!obj) -+ return 0; -+ -+ log = i915_gem_object_pin_map(obj, I915_MAP_WC); -+ if (IS_ERR(log)) { -+ DRM_DEBUG("Failed to pin object\n"); -+ seq_puts(m, "(log data unaccessible)\n"); -+ return PTR_ERR(log); -+ } -+ -+ for (i = 0; i < obj->base.size / sizeof(u32); i += 4) -+ seq_printf(m, "0x%08x 0x%08x 0x%08x 0x%08x\n", -+ *(log + i), *(log + i + 1), -+ *(log + i + 2), *(log + i + 3)); -+ -+ seq_putc(m, '\n'); -+ -+ i915_gem_object_unpin_map(obj); -+ -+ return 0; -+} -+ -+static int i915_guc_log_level_get(void *data, u64 *val) -+{ -+ struct drm_i915_private *dev_priv = data; -+ -+ if (!USES_GUC(dev_priv)) -+ return -ENODEV; -+ -+ *val = intel_guc_log_get_level(&dev_priv->guc.log); -+ -+ return 0; -+} -+ -+static int i915_guc_log_level_set(void *data, u64 val) -+{ -+ struct drm_i915_private *dev_priv = data; -+ -+ if (!USES_GUC(dev_priv)) -+ return -ENODEV; -+ -+ return intel_guc_log_set_level(&dev_priv->guc.log, val); -+} -+ -+DEFINE_SIMPLE_ATTRIBUTE(i915_guc_log_level_fops, -+ i915_guc_log_level_get, i915_guc_log_level_set, -+ "%lld\n"); -+ -+static int i915_guc_log_relay_open(struct inode *inode, struct file *file) -+{ -+ struct drm_i915_private *dev_priv = inode->i_private; -+ -+ if (!USES_GUC(dev_priv)) -+ return -ENODEV; -+ -+ file->private_data = &dev_priv->guc.log; -+ -+ return intel_guc_log_relay_open(&dev_priv->guc.log); -+} -+ -+static ssize_t -+i915_guc_log_relay_write(struct file *filp, -+ const char __user *ubuf, -+ size_t cnt, -+ loff_t *ppos) -+{ -+ struct intel_guc_log *log = filp->private_data; -+ -+ intel_guc_log_relay_flush(log); -+ -+ return cnt; -+} -+ -+static int i915_guc_log_relay_release(struct inode *inode, struct file *file) -+{ -+ struct drm_i915_private *dev_priv = inode->i_private; -+ -+ intel_guc_log_relay_close(&dev_priv->guc.log); -+ -+ return 0; -+} -+ -+static const struct file_operations i915_guc_log_relay_fops = { -+ .owner = THIS_MODULE, -+ .open = i915_guc_log_relay_open, -+ .write = i915_guc_log_relay_write, -+ .release = i915_guc_log_relay_release, -+}; -+ -+static int i915_psr_sink_status_show(struct seq_file *m, void *data) -+{ -+ u8 val; -+ static const char * const sink_status[] = { -+ "inactive", -+ "transition to active, capture and display", -+ "active, display from RFB", -+ "active, capture and display on sink device timings", -+ "transition to inactive, capture and display, timing re-sync", -+ "reserved", -+ "reserved", -+ "sink internal error", -+ }; -+ struct drm_connector *connector = m->private; -+ struct drm_i915_private *dev_priv = to_i915(connector->dev); -+ struct intel_dp *intel_dp = -+ enc_to_intel_dp(&intel_attached_encoder(connector)->base); -+ int ret; -+ -+ if (!CAN_PSR(dev_priv)) { -+ seq_puts(m, "PSR Unsupported\n"); -+ return -ENODEV; -+ } -+ -+ if (connector->status != connector_status_connected) -+ return -ENODEV; -+ -+ ret = drm_dp_dpcd_readb(&intel_dp->aux, DP_PSR_STATUS, &val); -+ -+ if (ret == 1) { -+ const char *str = "unknown"; -+ -+ val &= DP_PSR_SINK_STATE_MASK; -+ if (val < ARRAY_SIZE(sink_status)) -+ str = sink_status[val]; -+ seq_printf(m, "Sink PSR status: 0x%x [%s]\n", val, str); -+ } else { -+ return ret; -+ } -+ -+ return 0; -+} -+DEFINE_SHOW_ATTRIBUTE(i915_psr_sink_status); -+ -+static void -+psr_source_status(struct drm_i915_private *dev_priv, struct seq_file *m) -+{ -+ u32 val, status_val; -+ const char *status = "unknown"; -+ -+ if (dev_priv->psr.psr2_enabled) { -+ static const char * const live_status[] = { -+ "IDLE", -+ "CAPTURE", -+ "CAPTURE_FS", -+ "SLEEP", -+ "BUFON_FW", -+ "ML_UP", -+ "SU_STANDBY", -+ "FAST_SLEEP", -+ "DEEP_SLEEP", -+ "BUF_ON", -+ "TG_ON" -+ }; -+ val = I915_READ(EDP_PSR2_STATUS); -+ status_val = (val & EDP_PSR2_STATUS_STATE_MASK) >> -+ EDP_PSR2_STATUS_STATE_SHIFT; -+ if (status_val < ARRAY_SIZE(live_status)) -+ status = live_status[status_val]; -+ } else { -+ static const char * const live_status[] = { -+ "IDLE", -+ "SRDONACK", -+ "SRDENT", -+ "BUFOFF", -+ "BUFON", -+ "AUXACK", -+ "SRDOFFACK", -+ "SRDENT_ON", -+ }; -+ val = I915_READ(EDP_PSR_STATUS); -+ status_val = (val & EDP_PSR_STATUS_STATE_MASK) >> -+ EDP_PSR_STATUS_STATE_SHIFT; -+ if (status_val < ARRAY_SIZE(live_status)) -+ status = live_status[status_val]; -+ } -+ -+ seq_printf(m, "Source PSR status: %s [0x%08x]\n", status, val); -+} -+ -+static int i915_edp_psr_status(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct i915_psr *psr = &dev_priv->psr; -+ intel_wakeref_t wakeref; -+ const char *status; -+ bool enabled; -+ u32 val; -+ -+ if (!HAS_PSR(dev_priv)) -+ return -ENODEV; -+ -+ seq_printf(m, "Sink support: %s", yesno(psr->sink_support)); -+ if (psr->dp) -+ seq_printf(m, " [0x%02x]", psr->dp->psr_dpcd[0]); -+ seq_puts(m, "\n"); -+ -+ if (!psr->sink_support) -+ return 0; -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ mutex_lock(&psr->lock); -+ -+ if (psr->enabled) -+ status = psr->psr2_enabled ? "PSR2 enabled" : "PSR1 enabled"; -+ else -+ status = "disabled"; -+ seq_printf(m, "PSR mode: %s\n", status); -+ -+ if (!psr->enabled) -+ goto unlock; -+ -+ if (psr->psr2_enabled) { -+ val = I915_READ(EDP_PSR2_CTL); -+ enabled = val & EDP_PSR2_ENABLE; -+ } else { -+ val = I915_READ(EDP_PSR_CTL); -+ enabled = val & EDP_PSR_ENABLE; -+ } -+ seq_printf(m, "Source PSR ctl: %s [0x%08x]\n", -+ enableddisabled(enabled), val); -+ psr_source_status(dev_priv, m); -+ seq_printf(m, "Busy frontbuffer bits: 0x%08x\n", -+ psr->busy_frontbuffer_bits); -+ -+ /* -+ * SKL+ Perf counter is reset to 0 everytime DC state is entered -+ */ -+ if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) { -+ val = I915_READ(EDP_PSR_PERF_CNT) & EDP_PSR_PERF_CNT_MASK; -+ seq_printf(m, "Performance counter: %u\n", val); -+ } -+ -+ if (psr->debug & I915_PSR_DEBUG_IRQ) { -+ seq_printf(m, "Last attempted entry at: %lld\n", -+ psr->last_entry_attempt); -+ seq_printf(m, "Last exit at: %lld\n", psr->last_exit); -+ } -+ -+ if (psr->psr2_enabled) { -+ u32 su_frames_val[3]; -+ int frame; -+ -+ /* -+ * Reading all 3 registers before hand to minimize crossing a -+ * frame boundary between register reads -+ */ -+ for (frame = 0; frame < PSR2_SU_STATUS_FRAMES; frame += 3) -+ su_frames_val[frame / 3] = I915_READ(PSR2_SU_STATUS(frame)); -+ -+ seq_puts(m, "Frame:\tPSR2 SU blocks:\n"); -+ -+ for (frame = 0; frame < PSR2_SU_STATUS_FRAMES; frame++) { -+ u32 su_blocks; -+ -+ su_blocks = su_frames_val[frame / 3] & -+ PSR2_SU_STATUS_MASK(frame); -+ su_blocks = su_blocks >> PSR2_SU_STATUS_SHIFT(frame); -+ seq_printf(m, "%d\t%d\n", frame, su_blocks); -+ } -+ } -+ -+unlock: -+ mutex_unlock(&psr->lock); -+ intel_runtime_pm_put(dev_priv, wakeref); -+ -+ return 0; -+} -+ -+static int -+i915_edp_psr_debug_set(void *data, u64 val) -+{ -+ struct drm_i915_private *dev_priv = data; -+ intel_wakeref_t wakeref; -+ int ret; -+ -+ if (!CAN_PSR(dev_priv)) -+ return -ENODEV; -+ -+ DRM_DEBUG_KMS("Setting PSR debug to %llx\n", val); -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ -+ ret = intel_psr_debug_set(dev_priv, val); -+ -+ intel_runtime_pm_put(dev_priv, wakeref); -+ -+ return ret; -+} -+ -+static int -+i915_edp_psr_debug_get(void *data, u64 *val) -+{ -+ struct drm_i915_private *dev_priv = data; -+ -+ if (!CAN_PSR(dev_priv)) -+ return -ENODEV; -+ -+ *val = READ_ONCE(dev_priv->psr.debug); -+ return 0; -+} -+ -+DEFINE_SIMPLE_ATTRIBUTE(i915_edp_psr_debug_fops, -+ i915_edp_psr_debug_get, i915_edp_psr_debug_set, -+ "%llu\n"); -+ -+static int i915_energy_uJ(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ unsigned long long power; -+ intel_wakeref_t wakeref; -+ u32 units; -+ -+ if (INTEL_GEN(dev_priv) < 6) -+ return -ENODEV; -+ -+ if (rdmsrl_safe(MSR_RAPL_POWER_UNIT, &power)) -+ return -ENODEV; -+ -+ units = (power & 0x1f00) >> 8; -+ with_intel_runtime_pm(dev_priv, wakeref) -+ power = I915_READ(MCH_SECP_NRG_STTS); -+ -+ power = (1000000 * power) >> units; /* convert to uJ */ -+ seq_printf(m, "%llu", power); -+ -+ return 0; -+} -+ -+static int i915_runtime_pm_status(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ -+ if (!HAS_RUNTIME_PM(dev_priv)) -+ seq_puts(m, "Runtime power management not supported\n"); -+ -+ seq_printf(m, "Runtime power status: %s\n", -+ enableddisabled(!dev_priv->power_domains.wakeref)); -+ -+ seq_printf(m, "GPU idle: %s\n", yesno(!dev_priv->gt.awake)); -+ seq_printf(m, "IRQs disabled: %s\n", -+ yesno(!intel_irqs_enabled(dev_priv))); -+#ifdef CONFIG_PM -+ seq_printf(m, "Usage count: %d\n", -+ atomic_read(&dev_priv->drm.dev->power.usage_count)); -+#else -+ seq_printf(m, "Device Power Management (CONFIG_PM) disabled\n"); -+#endif -+ seq_printf(m, "PCI device power state: %s [%d]\n", -+ pci_power_name(pdev->current_state), -+ pdev->current_state); -+ -+ if (IS_ENABLED(CONFIG_DRM_I915_DEBUG_RUNTIME_PM)) { -+ struct drm_printer p = drm_seq_file_printer(m); -+ -+ print_intel_runtime_pm_wakeref(dev_priv, &p); -+ } -+ -+ return 0; -+} -+ -+static int i915_power_domain_info(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct i915_power_domains *power_domains = &dev_priv->power_domains; -+ int i; -+ -+ mutex_lock(&power_domains->lock); -+ -+ seq_printf(m, "%-25s %s\n", "Power well/domain", "Use count"); -+ for (i = 0; i < power_domains->power_well_count; i++) { -+ struct i915_power_well *power_well; -+ enum intel_display_power_domain power_domain; -+ -+ power_well = &power_domains->power_wells[i]; -+ seq_printf(m, "%-25s %d\n", power_well->desc->name, -+ power_well->count); -+ -+ for_each_power_domain(power_domain, power_well->desc->domains) -+ seq_printf(m, " %-23s %d\n", -+ intel_display_power_domain_str(power_domain), -+ power_domains->domain_use_count[power_domain]); -+ } -+ -+ mutex_unlock(&power_domains->lock); -+ -+ return 0; -+} -+ -+static int i915_dmc_info(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ intel_wakeref_t wakeref; -+ struct intel_csr *csr; -+ -+ if (!HAS_CSR(dev_priv)) -+ return -ENODEV; -+ -+ csr = &dev_priv->csr; -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ -+ seq_printf(m, "fw loaded: %s\n", yesno(csr->dmc_payload != NULL)); -+ seq_printf(m, "path: %s\n", csr->fw_path); -+ -+ if (!csr->dmc_payload) -+ goto out; -+ -+ seq_printf(m, "version: %d.%d\n", CSR_VERSION_MAJOR(csr->version), -+ CSR_VERSION_MINOR(csr->version)); -+ -+ if (WARN_ON(INTEL_GEN(dev_priv) > 11)) -+ goto out; -+ -+ seq_printf(m, "DC3 -> DC5 count: %d\n", -+ I915_READ(IS_BROXTON(dev_priv) ? BXT_CSR_DC3_DC5_COUNT : -+ SKL_CSR_DC3_DC5_COUNT)); -+ if (!IS_GEN9_LP(dev_priv)) -+ seq_printf(m, "DC5 -> DC6 count: %d\n", -+ I915_READ(SKL_CSR_DC5_DC6_COUNT)); -+ -+out: -+ seq_printf(m, "program base: 0x%08x\n", I915_READ(CSR_PROGRAM(0))); -+ seq_printf(m, "ssp base: 0x%08x\n", I915_READ(CSR_SSP_BASE)); -+ seq_printf(m, "htp: 0x%08x\n", I915_READ(CSR_HTP_SKL)); -+ -+ intel_runtime_pm_put(dev_priv, wakeref); -+ -+ return 0; -+} -+ -+static void intel_seq_print_mode(struct seq_file *m, int tabs, -+ struct drm_display_mode *mode) -+{ -+ int i; -+ -+ for (i = 0; i < tabs; i++) -+ seq_putc(m, '\t'); -+ -+ seq_printf(m, DRM_MODE_FMT "\n", DRM_MODE_ARG(mode)); -+} -+ -+static void intel_encoder_info(struct seq_file *m, -+ struct intel_crtc *intel_crtc, -+ struct intel_encoder *intel_encoder) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ struct drm_crtc *crtc = &intel_crtc->base; -+ struct intel_connector *intel_connector; -+ struct drm_encoder *encoder; -+ -+ encoder = &intel_encoder->base; -+ seq_printf(m, "\tencoder %d: type: %s, connectors:\n", -+ encoder->base.id, encoder->name); -+ for_each_connector_on_encoder(dev, encoder, intel_connector) { -+ struct drm_connector *connector = &intel_connector->base; -+ seq_printf(m, "\t\tconnector %d: type: %s, status: %s", -+ connector->base.id, -+ connector->name, -+ drm_get_connector_status_name(connector->status)); -+ if (connector->status == connector_status_connected) { -+ struct drm_display_mode *mode = &crtc->mode; -+ seq_printf(m, ", mode:\n"); -+ intel_seq_print_mode(m, 2, mode); -+ } else { -+ seq_putc(m, '\n'); -+ } -+ } -+} -+ -+static void intel_crtc_info(struct seq_file *m, struct intel_crtc *intel_crtc) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ struct drm_crtc *crtc = &intel_crtc->base; -+ struct intel_encoder *intel_encoder; -+ struct drm_plane_state *plane_state = crtc->primary->state; -+ struct drm_framebuffer *fb = plane_state->fb; -+ -+ if (fb) -+ seq_printf(m, "\tfb: %d, pos: %dx%d, size: %dx%d\n", -+ fb->base.id, plane_state->src_x >> 16, -+ plane_state->src_y >> 16, fb->width, fb->height); -+ else -+ seq_puts(m, "\tprimary plane disabled\n"); -+ for_each_encoder_on_crtc(dev, crtc, intel_encoder) -+ intel_encoder_info(m, intel_crtc, intel_encoder); -+} -+ -+static void intel_panel_info(struct seq_file *m, struct intel_panel *panel) -+{ -+ struct drm_display_mode *mode = panel->fixed_mode; -+ -+ seq_printf(m, "\tfixed mode:\n"); -+ intel_seq_print_mode(m, 2, mode); -+} -+ -+static void intel_dp_info(struct seq_file *m, -+ struct intel_connector *intel_connector) -+{ -+ struct intel_encoder *intel_encoder = intel_connector->encoder; -+ struct intel_dp *intel_dp = enc_to_intel_dp(&intel_encoder->base); -+ -+ seq_printf(m, "\tDPCD rev: %x\n", intel_dp->dpcd[DP_DPCD_REV]); -+ seq_printf(m, "\taudio support: %s\n", yesno(intel_dp->has_audio)); -+ if (intel_connector->base.connector_type == DRM_MODE_CONNECTOR_eDP) -+ intel_panel_info(m, &intel_connector->panel); -+ -+ drm_dp_downstream_debug(m, intel_dp->dpcd, intel_dp->downstream_ports, -+ &intel_dp->aux); -+} -+ -+static void intel_dp_mst_info(struct seq_file *m, -+ struct intel_connector *intel_connector) -+{ -+ struct intel_encoder *intel_encoder = intel_connector->encoder; -+ struct intel_dp_mst_encoder *intel_mst = -+ enc_to_mst(&intel_encoder->base); -+ struct intel_digital_port *intel_dig_port = intel_mst->primary; -+ struct intel_dp *intel_dp = &intel_dig_port->dp; -+ bool has_audio = drm_dp_mst_port_has_audio(&intel_dp->mst_mgr, -+ intel_connector->port); -+ -+ seq_printf(m, "\taudio support: %s\n", yesno(has_audio)); -+} -+ -+static void intel_hdmi_info(struct seq_file *m, -+ struct intel_connector *intel_connector) -+{ -+ struct intel_encoder *intel_encoder = intel_connector->encoder; -+ struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(&intel_encoder->base); -+ -+ seq_printf(m, "\taudio support: %s\n", yesno(intel_hdmi->has_audio)); -+} -+ -+static void intel_lvds_info(struct seq_file *m, -+ struct intel_connector *intel_connector) -+{ -+ intel_panel_info(m, &intel_connector->panel); -+} -+ -+static void intel_connector_info(struct seq_file *m, -+ struct drm_connector *connector) -+{ -+ struct intel_connector *intel_connector = to_intel_connector(connector); -+ struct intel_encoder *intel_encoder = intel_connector->encoder; -+ struct drm_display_mode *mode; -+ -+ seq_printf(m, "connector %d: type %s, status: %s\n", -+ connector->base.id, connector->name, -+ drm_get_connector_status_name(connector->status)); -+ -+ if (connector->status == connector_status_disconnected) -+ return; -+ -+ seq_printf(m, "\tphysical dimensions: %dx%dmm\n", -+ connector->display_info.width_mm, -+ connector->display_info.height_mm); -+ seq_printf(m, "\tsubpixel order: %s\n", -+ drm_get_subpixel_order_name(connector->display_info.subpixel_order)); -+ seq_printf(m, "\tCEA rev: %d\n", connector->display_info.cea_rev); -+ -+ if (!intel_encoder) -+ return; -+ -+ switch (connector->connector_type) { -+ case DRM_MODE_CONNECTOR_DisplayPort: -+ case DRM_MODE_CONNECTOR_eDP: -+ if (intel_encoder->type == INTEL_OUTPUT_DP_MST) -+ intel_dp_mst_info(m, intel_connector); -+ else -+ intel_dp_info(m, intel_connector); -+ break; -+ case DRM_MODE_CONNECTOR_LVDS: -+ if (intel_encoder->type == INTEL_OUTPUT_LVDS) -+ intel_lvds_info(m, intel_connector); -+ break; -+ case DRM_MODE_CONNECTOR_HDMIA: -+ if (intel_encoder->type == INTEL_OUTPUT_HDMI || -+ intel_encoder->type == INTEL_OUTPUT_DDI) -+ intel_hdmi_info(m, intel_connector); -+ break; -+ default: -+ break; -+ } -+ -+ seq_printf(m, "\tmodes:\n"); -+ list_for_each_entry(mode, &connector->modes, head) -+ intel_seq_print_mode(m, 2, mode); -+} -+ -+static const char *plane_type(enum drm_plane_type type) -+{ -+ switch (type) { -+ case DRM_PLANE_TYPE_OVERLAY: -+ return "OVL"; -+ case DRM_PLANE_TYPE_PRIMARY: -+ return "PRI"; -+ case DRM_PLANE_TYPE_CURSOR: -+ return "CUR"; -+ /* -+ * Deliberately omitting default: to generate compiler warnings -+ * when a new drm_plane_type gets added. -+ */ -+ } -+ -+ return "unknown"; -+} -+ -+static void plane_rotation(char *buf, size_t bufsize, unsigned int rotation) -+{ -+ /* -+ * According to doc only one DRM_MODE_ROTATE_ is allowed but this -+ * will print them all to visualize if the values are misused -+ */ -+ snprintf(buf, bufsize, -+ "%s%s%s%s%s%s(0x%08x)", -+ (rotation & DRM_MODE_ROTATE_0) ? "0 " : "", -+ (rotation & DRM_MODE_ROTATE_90) ? "90 " : "", -+ (rotation & DRM_MODE_ROTATE_180) ? "180 " : "", -+ (rotation & DRM_MODE_ROTATE_270) ? "270 " : "", -+ (rotation & DRM_MODE_REFLECT_X) ? "FLIPX " : "", -+ (rotation & DRM_MODE_REFLECT_Y) ? "FLIPY " : "", -+ rotation); -+} -+ -+static void intel_plane_info(struct seq_file *m, struct intel_crtc *intel_crtc) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ struct intel_plane *intel_plane; -+ -+ for_each_intel_plane_on_crtc(dev, intel_crtc, intel_plane) { -+ struct drm_plane_state *state; -+ struct drm_plane *plane = &intel_plane->base; -+ struct drm_format_name_buf format_name; -+ char rot_str[48]; -+ -+ if (!plane->state) { -+ seq_puts(m, "plane->state is NULL!\n"); -+ continue; -+ } -+ -+ state = plane->state; -+ -+ if (state->fb) { -+ drm_get_format_name(state->fb->format->format, -+ &format_name); -+ } else { -+ sprintf(format_name.str, "N/A"); -+ } -+ -+ plane_rotation(rot_str, sizeof(rot_str), state->rotation); -+ -+ seq_printf(m, "\t--Plane id %d: type=%s, crtc_pos=%4dx%4d, crtc_size=%4dx%4d, src_pos=%d.%04ux%d.%04u, src_size=%d.%04ux%d.%04u, format=%s, rotation=%s\n", -+ plane->base.id, -+ plane_type(intel_plane->base.type), -+ state->crtc_x, state->crtc_y, -+ state->crtc_w, state->crtc_h, -+ (state->src_x >> 16), -+ ((state->src_x & 0xffff) * 15625) >> 10, -+ (state->src_y >> 16), -+ ((state->src_y & 0xffff) * 15625) >> 10, -+ (state->src_w >> 16), -+ ((state->src_w & 0xffff) * 15625) >> 10, -+ (state->src_h >> 16), -+ ((state->src_h & 0xffff) * 15625) >> 10, -+ format_name.str, -+ rot_str); -+ } -+} -+ -+static void intel_scaler_info(struct seq_file *m, struct intel_crtc *intel_crtc) -+{ -+ struct intel_crtc_state *pipe_config; -+ int num_scalers = intel_crtc->num_scalers; -+ int i; -+ -+ pipe_config = to_intel_crtc_state(intel_crtc->base.state); -+ -+ /* Not all platformas have a scaler */ -+ if (num_scalers) { -+ seq_printf(m, "\tnum_scalers=%d, scaler_users=%x scaler_id=%d", -+ num_scalers, -+ pipe_config->scaler_state.scaler_users, -+ pipe_config->scaler_state.scaler_id); -+ -+ for (i = 0; i < num_scalers; i++) { -+ struct intel_scaler *sc = -+ &pipe_config->scaler_state.scalers[i]; -+ -+ seq_printf(m, ", scalers[%d]: use=%s, mode=%x", -+ i, yesno(sc->in_use), sc->mode); -+ } -+ seq_puts(m, "\n"); -+ } else { -+ seq_puts(m, "\tNo scalers available on this platform\n"); -+ } -+} -+ -+static int i915_display_info(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ struct intel_crtc *crtc; -+ struct drm_connector *connector; -+ struct drm_connector_list_iter conn_iter; -+ intel_wakeref_t wakeref; -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ -+ seq_printf(m, "CRTC info\n"); -+ seq_printf(m, "---------\n"); -+ for_each_intel_crtc(dev, crtc) { -+ struct intel_crtc_state *pipe_config; -+ -+ drm_modeset_lock(&crtc->base.mutex, NULL); -+ pipe_config = to_intel_crtc_state(crtc->base.state); -+ -+ seq_printf(m, "CRTC %d: pipe: %c, active=%s, (size=%dx%d), dither=%s, bpp=%d\n", -+ crtc->base.base.id, pipe_name(crtc->pipe), -+ yesno(pipe_config->base.active), -+ pipe_config->pipe_src_w, pipe_config->pipe_src_h, -+ yesno(pipe_config->dither), pipe_config->pipe_bpp); -+ -+ if (pipe_config->base.active) { -+ struct intel_plane *cursor = -+ to_intel_plane(crtc->base.cursor); -+ -+ intel_crtc_info(m, crtc); -+ -+ seq_printf(m, "\tcursor visible? %s, position (%d, %d), size %dx%d, addr 0x%08x\n", -+ yesno(cursor->base.state->visible), -+ cursor->base.state->crtc_x, -+ cursor->base.state->crtc_y, -+ cursor->base.state->crtc_w, -+ cursor->base.state->crtc_h, -+ cursor->cursor.base); -+ intel_scaler_info(m, crtc); -+ intel_plane_info(m, crtc); -+ } -+ -+ seq_printf(m, "\tunderrun reporting: cpu=%s pch=%s \n", -+ yesno(!crtc->cpu_fifo_underrun_disabled), -+ yesno(!crtc->pch_fifo_underrun_disabled)); -+ drm_modeset_unlock(&crtc->base.mutex); -+ } -+ -+ seq_printf(m, "\n"); -+ seq_printf(m, "Connector info\n"); -+ seq_printf(m, "--------------\n"); -+ mutex_lock(&dev->mode_config.mutex); -+ drm_connector_list_iter_begin(dev, &conn_iter); -+ drm_for_each_connector_iter(connector, &conn_iter) -+ intel_connector_info(m, connector); -+ drm_connector_list_iter_end(&conn_iter); -+ mutex_unlock(&dev->mode_config.mutex); -+ -+ intel_runtime_pm_put(dev_priv, wakeref); -+ -+ return 0; -+} -+ -+static int i915_engine_info(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct intel_engine_cs *engine; -+ intel_wakeref_t wakeref; -+ enum intel_engine_id id; -+ struct drm_printer p; -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ -+ seq_printf(m, "GT awake? %s\n", yesno(dev_priv->gt.awake)); -+ seq_printf(m, "Global active requests: %d\n", -+ dev_priv->gt.active_requests); -+ seq_printf(m, "CS timestamp frequency: %u kHz\n", -+ RUNTIME_INFO(dev_priv)->cs_timestamp_frequency_khz); -+ -+ p = drm_seq_file_printer(m); -+ for_each_engine(engine, dev_priv, id) -+ intel_engine_dump(engine, &p, "%s\n", engine->name); -+ -+ intel_runtime_pm_put(dev_priv, wakeref); -+ -+ return 0; -+} -+ -+static int i915_rcs_topology(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_printer p = drm_seq_file_printer(m); -+ -+ intel_device_info_dump_topology(&RUNTIME_INFO(dev_priv)->sseu, &p); -+ -+ return 0; -+} -+ -+static int i915_shrinker_info(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *i915 = node_to_i915(m->private); -+ -+ seq_printf(m, "seeks = %d\n", i915->mm.shrinker.seeks); -+ seq_printf(m, "batch = %lu\n", i915->mm.shrinker.batch); -+ -+ return 0; -+} -+ -+static int i915_shared_dplls_info(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ int i; -+ -+ drm_modeset_lock_all(dev); -+ for (i = 0; i < dev_priv->num_shared_dpll; i++) { -+ struct intel_shared_dpll *pll = &dev_priv->shared_dplls[i]; -+ -+ seq_printf(m, "DPLL%i: %s, id: %i\n", i, pll->info->name, -+ pll->info->id); -+ seq_printf(m, " crtc_mask: 0x%08x, active: 0x%x, on: %s\n", -+ pll->state.crtc_mask, pll->active_mask, yesno(pll->on)); -+ seq_printf(m, " tracked hardware state:\n"); -+ seq_printf(m, " dpll: 0x%08x\n", pll->state.hw_state.dpll); -+ seq_printf(m, " dpll_md: 0x%08x\n", -+ pll->state.hw_state.dpll_md); -+ seq_printf(m, " fp0: 0x%08x\n", pll->state.hw_state.fp0); -+ seq_printf(m, " fp1: 0x%08x\n", pll->state.hw_state.fp1); -+ seq_printf(m, " wrpll: 0x%08x\n", pll->state.hw_state.wrpll); -+ seq_printf(m, " cfgcr0: 0x%08x\n", pll->state.hw_state.cfgcr0); -+ seq_printf(m, " cfgcr1: 0x%08x\n", pll->state.hw_state.cfgcr1); -+ seq_printf(m, " mg_refclkin_ctl: 0x%08x\n", -+ pll->state.hw_state.mg_refclkin_ctl); -+ seq_printf(m, " mg_clktop2_coreclkctl1: 0x%08x\n", -+ pll->state.hw_state.mg_clktop2_coreclkctl1); -+ seq_printf(m, " mg_clktop2_hsclkctl: 0x%08x\n", -+ pll->state.hw_state.mg_clktop2_hsclkctl); -+ seq_printf(m, " mg_pll_div0: 0x%08x\n", -+ pll->state.hw_state.mg_pll_div0); -+ seq_printf(m, " mg_pll_div1: 0x%08x\n", -+ pll->state.hw_state.mg_pll_div1); -+ seq_printf(m, " mg_pll_lf: 0x%08x\n", -+ pll->state.hw_state.mg_pll_lf); -+ seq_printf(m, " mg_pll_frac_lock: 0x%08x\n", -+ pll->state.hw_state.mg_pll_frac_lock); -+ seq_printf(m, " mg_pll_ssc: 0x%08x\n", -+ pll->state.hw_state.mg_pll_ssc); -+ seq_printf(m, " mg_pll_bias: 0x%08x\n", -+ pll->state.hw_state.mg_pll_bias); -+ seq_printf(m, " mg_pll_tdc_coldst_bias: 0x%08x\n", -+ pll->state.hw_state.mg_pll_tdc_coldst_bias); -+ } -+ drm_modeset_unlock_all(dev); -+ -+ return 0; -+} -+ -+static int i915_wa_registers(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *i915 = node_to_i915(m->private); -+ const struct i915_wa_list *wal = &i915->engine[RCS0]->ctx_wa_list; -+ struct i915_wa *wa; -+ unsigned int i; -+ -+ seq_printf(m, "Workarounds applied: %u\n", wal->count); -+ for (i = 0, wa = wal->list; i < wal->count; i++, wa++) -+ seq_printf(m, "0x%X: 0x%08X, mask: 0x%08X\n", -+ i915_mmio_reg_offset(wa->reg), wa->val, wa->mask); -+ -+ return 0; -+} -+ -+static int i915_ipc_status_show(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = m->private; -+ -+ seq_printf(m, "Isochronous Priority Control: %s\n", -+ yesno(dev_priv->ipc_enabled)); -+ return 0; -+} -+ -+static int i915_ipc_status_open(struct inode *inode, struct file *file) -+{ -+ struct drm_i915_private *dev_priv = inode->i_private; -+ -+ if (!HAS_IPC(dev_priv)) -+ return -ENODEV; -+ -+ return single_open(file, i915_ipc_status_show, dev_priv); -+} -+ -+static ssize_t i915_ipc_status_write(struct file *file, const char __user *ubuf, -+ size_t len, loff_t *offp) -+{ -+ struct seq_file *m = file->private_data; -+ struct drm_i915_private *dev_priv = m->private; -+ intel_wakeref_t wakeref; -+ bool enable; -+ int ret; -+ -+ ret = kstrtobool_from_user(ubuf, len, &enable); -+ if (ret < 0) -+ return ret; -+ -+ with_intel_runtime_pm(dev_priv, wakeref) { -+ if (!dev_priv->ipc_enabled && enable) -+ DRM_INFO("Enabling IPC: WM will be proper only after next commit\n"); -+ dev_priv->wm.distrust_bios_wm = true; -+ dev_priv->ipc_enabled = enable; -+ intel_enable_ipc(dev_priv); -+ } -+ -+ return len; -+} -+ -+static const struct file_operations i915_ipc_status_fops = { -+ .owner = THIS_MODULE, -+ .open = i915_ipc_status_open, -+ .read = seq_read, -+ .llseek = seq_lseek, -+ .release = single_release, -+ .write = i915_ipc_status_write -+}; -+ -+static int i915_ddb_info(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ struct skl_ddb_entry *entry; -+ struct intel_crtc *crtc; -+ -+ if (INTEL_GEN(dev_priv) < 9) -+ return -ENODEV; -+ -+ drm_modeset_lock_all(dev); -+ -+ seq_printf(m, "%-15s%8s%8s%8s\n", "", "Start", "End", "Size"); -+ -+ for_each_intel_crtc(&dev_priv->drm, crtc) { -+ struct intel_crtc_state *crtc_state = -+ to_intel_crtc_state(crtc->base.state); -+ enum pipe pipe = crtc->pipe; -+ enum plane_id plane_id; -+ -+ seq_printf(m, "Pipe %c\n", pipe_name(pipe)); -+ -+ for_each_plane_id_on_crtc(crtc, plane_id) { -+ entry = &crtc_state->wm.skl.plane_ddb_y[plane_id]; -+ seq_printf(m, " Plane%-8d%8u%8u%8u\n", plane_id + 1, -+ entry->start, entry->end, -+ skl_ddb_entry_size(entry)); -+ } -+ -+ entry = &crtc_state->wm.skl.plane_ddb_y[PLANE_CURSOR]; -+ seq_printf(m, " %-13s%8u%8u%8u\n", "Cursor", entry->start, -+ entry->end, skl_ddb_entry_size(entry)); -+ } -+ -+ drm_modeset_unlock_all(dev); -+ -+ return 0; -+} -+ -+static void drrs_status_per_crtc(struct seq_file *m, -+ struct drm_device *dev, -+ struct intel_crtc *intel_crtc) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct i915_drrs *drrs = &dev_priv->drrs; -+ int vrefresh = 0; -+ struct drm_connector *connector; -+ struct drm_connector_list_iter conn_iter; -+ -+ drm_connector_list_iter_begin(dev, &conn_iter); -+ drm_for_each_connector_iter(connector, &conn_iter) { -+ if (connector->state->crtc != &intel_crtc->base) -+ continue; -+ -+ seq_printf(m, "%s:\n", connector->name); -+ } -+ drm_connector_list_iter_end(&conn_iter); -+ -+ if (dev_priv->vbt.drrs_type == STATIC_DRRS_SUPPORT) -+ seq_puts(m, "\tVBT: DRRS_type: Static"); -+ else if (dev_priv->vbt.drrs_type == SEAMLESS_DRRS_SUPPORT) -+ seq_puts(m, "\tVBT: DRRS_type: Seamless"); -+ else if (dev_priv->vbt.drrs_type == DRRS_NOT_SUPPORTED) -+ seq_puts(m, "\tVBT: DRRS_type: None"); -+ else -+ seq_puts(m, "\tVBT: DRRS_type: FIXME: Unrecognized Value"); -+ -+ seq_puts(m, "\n\n"); -+ -+ if (to_intel_crtc_state(intel_crtc->base.state)->has_drrs) { -+ struct intel_panel *panel; -+ -+ mutex_lock(&drrs->mutex); -+ /* DRRS Supported */ -+ seq_puts(m, "\tDRRS Supported: Yes\n"); -+ -+ /* disable_drrs() will make drrs->dp NULL */ -+ if (!drrs->dp) { -+ seq_puts(m, "Idleness DRRS: Disabled\n"); -+ if (dev_priv->psr.enabled) -+ seq_puts(m, -+ "\tAs PSR is enabled, DRRS is not enabled\n"); -+ mutex_unlock(&drrs->mutex); -+ return; -+ } -+ -+ panel = &drrs->dp->attached_connector->panel; -+ seq_printf(m, "\t\tBusy_frontbuffer_bits: 0x%X", -+ drrs->busy_frontbuffer_bits); -+ -+ seq_puts(m, "\n\t\t"); -+ if (drrs->refresh_rate_type == DRRS_HIGH_RR) { -+ seq_puts(m, "DRRS_State: DRRS_HIGH_RR\n"); -+ vrefresh = panel->fixed_mode->vrefresh; -+ } else if (drrs->refresh_rate_type == DRRS_LOW_RR) { -+ seq_puts(m, "DRRS_State: DRRS_LOW_RR\n"); -+ vrefresh = panel->downclock_mode->vrefresh; -+ } else { -+ seq_printf(m, "DRRS_State: Unknown(%d)\n", -+ drrs->refresh_rate_type); -+ mutex_unlock(&drrs->mutex); -+ return; -+ } -+ seq_printf(m, "\t\tVrefresh: %d", vrefresh); -+ -+ seq_puts(m, "\n\t\t"); -+ mutex_unlock(&drrs->mutex); -+ } else { -+ /* DRRS not supported. Print the VBT parameter*/ -+ seq_puts(m, "\tDRRS Supported : No"); -+ } -+ seq_puts(m, "\n"); -+} -+ -+static int i915_drrs_status(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ struct intel_crtc *intel_crtc; -+ int active_crtc_cnt = 0; -+ -+ drm_modeset_lock_all(dev); -+ for_each_intel_crtc(dev, intel_crtc) { -+ if (intel_crtc->base.state->active) { -+ active_crtc_cnt++; -+ seq_printf(m, "\nCRTC %d: ", active_crtc_cnt); -+ -+ drrs_status_per_crtc(m, dev, intel_crtc); -+ } -+ } -+ drm_modeset_unlock_all(dev); -+ -+ if (!active_crtc_cnt) -+ seq_puts(m, "No active crtc found\n"); -+ -+ return 0; -+} -+ -+static int i915_dp_mst_info(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct drm_device *dev = &dev_priv->drm; -+ struct intel_encoder *intel_encoder; -+ struct intel_digital_port *intel_dig_port; -+ struct drm_connector *connector; -+ struct drm_connector_list_iter conn_iter; -+ -+ drm_connector_list_iter_begin(dev, &conn_iter); -+ drm_for_each_connector_iter(connector, &conn_iter) { -+ if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort) -+ continue; -+ -+ intel_encoder = intel_attached_encoder(connector); -+ if (!intel_encoder || intel_encoder->type == INTEL_OUTPUT_DP_MST) -+ continue; -+ -+ intel_dig_port = enc_to_dig_port(&intel_encoder->base); -+ if (!intel_dig_port->dp.can_mst) -+ continue; -+ -+ seq_printf(m, "MST Source Port %c\n", -+ port_name(intel_dig_port->base.port)); -+ drm_dp_mst_dump_topology(m, &intel_dig_port->dp.mst_mgr); -+ } -+ drm_connector_list_iter_end(&conn_iter); -+ -+ return 0; -+} -+ -+static ssize_t i915_displayport_test_active_write(struct file *file, -+ const char __user *ubuf, -+ size_t len, loff_t *offp) -+{ -+ char *input_buffer; -+ int status = 0; -+ struct drm_device *dev; -+ struct drm_connector *connector; -+ struct drm_connector_list_iter conn_iter; -+ struct intel_dp *intel_dp; -+ int val = 0; -+ -+ dev = ((struct seq_file *)file->private_data)->private; -+ -+ if (len == 0) -+ return 0; -+ -+ input_buffer = memdup_user_nul(ubuf, len); -+ if (IS_ERR(input_buffer)) -+ return PTR_ERR(input_buffer); -+ -+ DRM_DEBUG_DRIVER("Copied %d bytes from user\n", (unsigned int)len); -+ -+ drm_connector_list_iter_begin(dev, &conn_iter); -+ drm_for_each_connector_iter(connector, &conn_iter) { -+ struct intel_encoder *encoder; -+ -+ if (connector->connector_type != -+ DRM_MODE_CONNECTOR_DisplayPort) -+ continue; -+ -+ encoder = to_intel_encoder(connector->encoder); -+ if (encoder && encoder->type == INTEL_OUTPUT_DP_MST) -+ continue; -+ -+ if (encoder && connector->status == connector_status_connected) { -+ intel_dp = enc_to_intel_dp(&encoder->base); -+ status = kstrtoint(input_buffer, 10, &val); -+ if (status < 0) -+ break; -+ DRM_DEBUG_DRIVER("Got %d for test active\n", val); -+ /* To prevent erroneous activation of the compliance -+ * testing code, only accept an actual value of 1 here -+ */ -+ if (val == 1) -+ intel_dp->compliance.test_active = 1; -+ else -+ intel_dp->compliance.test_active = 0; -+ } -+ } -+ drm_connector_list_iter_end(&conn_iter); -+ kfree(input_buffer); -+ if (status < 0) -+ return status; -+ -+ *offp += len; -+ return len; -+} -+ -+static int i915_displayport_test_active_show(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = m->private; -+ struct drm_device *dev = &dev_priv->drm; -+ struct drm_connector *connector; -+ struct drm_connector_list_iter conn_iter; -+ struct intel_dp *intel_dp; -+ -+ drm_connector_list_iter_begin(dev, &conn_iter); -+ drm_for_each_connector_iter(connector, &conn_iter) { -+ struct intel_encoder *encoder; -+ -+ if (connector->connector_type != -+ DRM_MODE_CONNECTOR_DisplayPort) -+ continue; -+ -+ encoder = to_intel_encoder(connector->encoder); -+ if (encoder && encoder->type == INTEL_OUTPUT_DP_MST) -+ continue; -+ -+ if (encoder && connector->status == connector_status_connected) { -+ intel_dp = enc_to_intel_dp(&encoder->base); -+ if (intel_dp->compliance.test_active) -+ seq_puts(m, "1"); -+ else -+ seq_puts(m, "0"); -+ } else -+ seq_puts(m, "0"); -+ } -+ drm_connector_list_iter_end(&conn_iter); -+ -+ return 0; -+} -+ -+static int i915_displayport_test_active_open(struct inode *inode, -+ struct file *file) -+{ -+ return single_open(file, i915_displayport_test_active_show, -+ inode->i_private); -+} -+ -+static const struct file_operations i915_displayport_test_active_fops = { -+ .owner = THIS_MODULE, -+ .open = i915_displayport_test_active_open, -+ .read = seq_read, -+ .llseek = seq_lseek, -+ .release = single_release, -+ .write = i915_displayport_test_active_write -+}; -+ -+static int i915_displayport_test_data_show(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = m->private; -+ struct drm_device *dev = &dev_priv->drm; -+ struct drm_connector *connector; -+ struct drm_connector_list_iter conn_iter; -+ struct intel_dp *intel_dp; -+ -+ drm_connector_list_iter_begin(dev, &conn_iter); -+ drm_for_each_connector_iter(connector, &conn_iter) { -+ struct intel_encoder *encoder; -+ -+ if (connector->connector_type != -+ DRM_MODE_CONNECTOR_DisplayPort) -+ continue; -+ -+ encoder = to_intel_encoder(connector->encoder); -+ if (encoder && encoder->type == INTEL_OUTPUT_DP_MST) -+ continue; -+ -+ if (encoder && connector->status == connector_status_connected) { -+ intel_dp = enc_to_intel_dp(&encoder->base); -+ if (intel_dp->compliance.test_type == -+ DP_TEST_LINK_EDID_READ) -+ seq_printf(m, "%lx", -+ intel_dp->compliance.test_data.edid); -+ else if (intel_dp->compliance.test_type == -+ DP_TEST_LINK_VIDEO_PATTERN) { -+ seq_printf(m, "hdisplay: %d\n", -+ intel_dp->compliance.test_data.hdisplay); -+ seq_printf(m, "vdisplay: %d\n", -+ intel_dp->compliance.test_data.vdisplay); -+ seq_printf(m, "bpc: %u\n", -+ intel_dp->compliance.test_data.bpc); -+ } -+ } else -+ seq_puts(m, "0"); -+ } -+ drm_connector_list_iter_end(&conn_iter); -+ -+ return 0; -+} -+DEFINE_SHOW_ATTRIBUTE(i915_displayport_test_data); -+ -+static int i915_displayport_test_type_show(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = m->private; -+ struct drm_device *dev = &dev_priv->drm; -+ struct drm_connector *connector; -+ struct drm_connector_list_iter conn_iter; -+ struct intel_dp *intel_dp; -+ -+ drm_connector_list_iter_begin(dev, &conn_iter); -+ drm_for_each_connector_iter(connector, &conn_iter) { -+ struct intel_encoder *encoder; -+ -+ if (connector->connector_type != -+ DRM_MODE_CONNECTOR_DisplayPort) -+ continue; -+ -+ encoder = to_intel_encoder(connector->encoder); -+ if (encoder && encoder->type == INTEL_OUTPUT_DP_MST) -+ continue; -+ -+ if (encoder && connector->status == connector_status_connected) { -+ intel_dp = enc_to_intel_dp(&encoder->base); -+ seq_printf(m, "%02lx", intel_dp->compliance.test_type); -+ } else -+ seq_puts(m, "0"); -+ } -+ drm_connector_list_iter_end(&conn_iter); -+ -+ return 0; -+} -+DEFINE_SHOW_ATTRIBUTE(i915_displayport_test_type); -+ -+static void wm_latency_show(struct seq_file *m, const u16 wm[8]) -+{ -+ struct drm_i915_private *dev_priv = m->private; -+ struct drm_device *dev = &dev_priv->drm; -+ int level; -+ int num_levels; -+ -+ if (IS_CHERRYVIEW(dev_priv)) -+ num_levels = 3; -+ else if (IS_VALLEYVIEW(dev_priv)) -+ num_levels = 1; -+ else if (IS_G4X(dev_priv)) -+ num_levels = 3; -+ else -+ num_levels = ilk_wm_max_level(dev_priv) + 1; -+ -+ drm_modeset_lock_all(dev); -+ -+ for (level = 0; level < num_levels; level++) { -+ unsigned int latency = wm[level]; -+ -+ /* -+ * - WM1+ latency values in 0.5us units -+ * - latencies are in us on gen9/vlv/chv -+ */ -+ if (INTEL_GEN(dev_priv) >= 9 || -+ IS_VALLEYVIEW(dev_priv) || -+ IS_CHERRYVIEW(dev_priv) || -+ IS_G4X(dev_priv)) -+ latency *= 10; -+ else if (level > 0) -+ latency *= 5; -+ -+ seq_printf(m, "WM%d %u (%u.%u usec)\n", -+ level, wm[level], latency / 10, latency % 10); -+ } -+ -+ drm_modeset_unlock_all(dev); -+} -+ -+static int pri_wm_latency_show(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = m->private; -+ const u16 *latencies; -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ latencies = dev_priv->wm.skl_latency; -+ else -+ latencies = dev_priv->wm.pri_latency; -+ -+ wm_latency_show(m, latencies); -+ -+ return 0; -+} -+ -+static int spr_wm_latency_show(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = m->private; -+ const u16 *latencies; -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ latencies = dev_priv->wm.skl_latency; -+ else -+ latencies = dev_priv->wm.spr_latency; -+ -+ wm_latency_show(m, latencies); -+ -+ return 0; -+} -+ -+static int cur_wm_latency_show(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = m->private; -+ const u16 *latencies; -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ latencies = dev_priv->wm.skl_latency; -+ else -+ latencies = dev_priv->wm.cur_latency; -+ -+ wm_latency_show(m, latencies); -+ -+ return 0; -+} -+ -+static int pri_wm_latency_open(struct inode *inode, struct file *file) -+{ -+ struct drm_i915_private *dev_priv = inode->i_private; -+ -+ if (INTEL_GEN(dev_priv) < 5 && !IS_G4X(dev_priv)) -+ return -ENODEV; -+ -+ return single_open(file, pri_wm_latency_show, dev_priv); -+} -+ -+static int spr_wm_latency_open(struct inode *inode, struct file *file) -+{ -+ struct drm_i915_private *dev_priv = inode->i_private; -+ -+ if (HAS_GMCH(dev_priv)) -+ return -ENODEV; -+ -+ return single_open(file, spr_wm_latency_show, dev_priv); -+} -+ -+static int cur_wm_latency_open(struct inode *inode, struct file *file) -+{ -+ struct drm_i915_private *dev_priv = inode->i_private; -+ -+ if (HAS_GMCH(dev_priv)) -+ return -ENODEV; -+ -+ return single_open(file, cur_wm_latency_show, dev_priv); -+} -+ -+static ssize_t wm_latency_write(struct file *file, const char __user *ubuf, -+ size_t len, loff_t *offp, u16 wm[8]) -+{ -+ struct seq_file *m = file->private_data; -+ struct drm_i915_private *dev_priv = m->private; -+ struct drm_device *dev = &dev_priv->drm; -+ u16 new[8] = { 0 }; -+ int num_levels; -+ int level; -+ int ret; -+ char tmp[32]; -+ -+ if (IS_CHERRYVIEW(dev_priv)) -+ num_levels = 3; -+ else if (IS_VALLEYVIEW(dev_priv)) -+ num_levels = 1; -+ else if (IS_G4X(dev_priv)) -+ num_levels = 3; -+ else -+ num_levels = ilk_wm_max_level(dev_priv) + 1; -+ -+ if (len >= sizeof(tmp)) -+ return -EINVAL; -+ -+ if (copy_from_user(tmp, ubuf, len)) -+ return -EFAULT; -+ -+ tmp[len] = '\0'; -+ -+ ret = sscanf(tmp, "%hu %hu %hu %hu %hu %hu %hu %hu", -+ &new[0], &new[1], &new[2], &new[3], -+ &new[4], &new[5], &new[6], &new[7]); -+ if (ret != num_levels) -+ return -EINVAL; -+ -+ drm_modeset_lock_all(dev); -+ -+ for (level = 0; level < num_levels; level++) -+ wm[level] = new[level]; -+ -+ drm_modeset_unlock_all(dev); -+ -+ return len; -+} -+ -+ -+static ssize_t pri_wm_latency_write(struct file *file, const char __user *ubuf, -+ size_t len, loff_t *offp) -+{ -+ struct seq_file *m = file->private_data; -+ struct drm_i915_private *dev_priv = m->private; -+ u16 *latencies; -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ latencies = dev_priv->wm.skl_latency; -+ else -+ latencies = dev_priv->wm.pri_latency; -+ -+ return wm_latency_write(file, ubuf, len, offp, latencies); -+} -+ -+static ssize_t spr_wm_latency_write(struct file *file, const char __user *ubuf, -+ size_t len, loff_t *offp) -+{ -+ struct seq_file *m = file->private_data; -+ struct drm_i915_private *dev_priv = m->private; -+ u16 *latencies; -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ latencies = dev_priv->wm.skl_latency; -+ else -+ latencies = dev_priv->wm.spr_latency; -+ -+ return wm_latency_write(file, ubuf, len, offp, latencies); -+} -+ -+static ssize_t cur_wm_latency_write(struct file *file, const char __user *ubuf, -+ size_t len, loff_t *offp) -+{ -+ struct seq_file *m = file->private_data; -+ struct drm_i915_private *dev_priv = m->private; -+ u16 *latencies; -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ latencies = dev_priv->wm.skl_latency; -+ else -+ latencies = dev_priv->wm.cur_latency; -+ -+ return wm_latency_write(file, ubuf, len, offp, latencies); -+} -+ -+static const struct file_operations i915_pri_wm_latency_fops = { -+ .owner = THIS_MODULE, -+ .open = pri_wm_latency_open, -+ .read = seq_read, -+ .llseek = seq_lseek, -+ .release = single_release, -+ .write = pri_wm_latency_write -+}; -+ -+static const struct file_operations i915_spr_wm_latency_fops = { -+ .owner = THIS_MODULE, -+ .open = spr_wm_latency_open, -+ .read = seq_read, -+ .llseek = seq_lseek, -+ .release = single_release, -+ .write = spr_wm_latency_write -+}; -+ -+static const struct file_operations i915_cur_wm_latency_fops = { -+ .owner = THIS_MODULE, -+ .open = cur_wm_latency_open, -+ .read = seq_read, -+ .llseek = seq_lseek, -+ .release = single_release, -+ .write = cur_wm_latency_write -+}; -+ -+static int -+i915_wedged_get(void *data, u64 *val) -+{ -+ int ret = i915_terminally_wedged(data); -+ -+ switch (ret) { -+ case -EIO: -+ *val = 1; -+ return 0; -+ case 0: -+ *val = 0; -+ return 0; -+ default: -+ return ret; -+ } -+} -+ -+static int -+i915_wedged_set(void *data, u64 val) -+{ -+ struct drm_i915_private *i915 = data; -+ -+ /* Flush any previous reset before applying for a new one */ -+ wait_event(i915->gpu_error.reset_queue, -+ !test_bit(I915_RESET_BACKOFF, &i915->gpu_error.flags)); -+ -+ i915_handle_error(i915, val, I915_ERROR_CAPTURE, -+ "Manually set wedged engine mask = %llx", val); -+ return 0; -+} -+ -+DEFINE_SIMPLE_ATTRIBUTE(i915_wedged_fops, -+ i915_wedged_get, i915_wedged_set, -+ "%llu\n"); -+ -+#define DROP_UNBOUND BIT(0) -+#define DROP_BOUND BIT(1) -+#define DROP_RETIRE BIT(2) -+#define DROP_ACTIVE BIT(3) -+#define DROP_FREED BIT(4) -+#define DROP_SHRINK_ALL BIT(5) -+#define DROP_IDLE BIT(6) -+#define DROP_RESET_ACTIVE BIT(7) -+#define DROP_RESET_SEQNO BIT(8) -+#define DROP_ALL (DROP_UNBOUND | \ -+ DROP_BOUND | \ -+ DROP_RETIRE | \ -+ DROP_ACTIVE | \ -+ DROP_FREED | \ -+ DROP_SHRINK_ALL |\ -+ DROP_IDLE | \ -+ DROP_RESET_ACTIVE | \ -+ DROP_RESET_SEQNO) -+static int -+i915_drop_caches_get(void *data, u64 *val) -+{ -+ *val = DROP_ALL; -+ -+ return 0; -+} -+ -+static int -+i915_drop_caches_set(void *data, u64 val) -+{ -+ struct drm_i915_private *i915 = data; -+ -+ DRM_DEBUG("Dropping caches: 0x%08llx [0x%08llx]\n", -+ val, val & DROP_ALL); -+ -+ if (val & DROP_RESET_ACTIVE && -+ wait_for(intel_engines_are_idle(i915), I915_IDLE_ENGINES_TIMEOUT)) -+ i915_gem_set_wedged(i915); -+ -+ /* No need to check and wait for gpu resets, only libdrm auto-restarts -+ * on ioctls on -EAGAIN. */ -+ if (val & (DROP_ACTIVE | DROP_RETIRE | DROP_RESET_SEQNO)) { -+ int ret; -+ -+ ret = mutex_lock_interruptible(&i915->drm.struct_mutex); -+ if (ret) -+ return ret; -+ -+ if (val & DROP_ACTIVE) -+ ret = i915_gem_wait_for_idle(i915, -+ I915_WAIT_INTERRUPTIBLE | -+ I915_WAIT_LOCKED, -+ MAX_SCHEDULE_TIMEOUT); -+ -+ if (val & DROP_RETIRE) -+ i915_retire_requests(i915); -+ -+ mutex_unlock(&i915->drm.struct_mutex); -+ } -+ -+ if (val & DROP_RESET_ACTIVE && i915_terminally_wedged(i915)) -+ i915_handle_error(i915, ALL_ENGINES, 0, NULL); -+ -+ fs_reclaim_acquire(GFP_KERNEL); -+ if (val & DROP_BOUND) -+ i915_gem_shrink(i915, LONG_MAX, NULL, I915_SHRINK_BOUND); -+ -+ if (val & DROP_UNBOUND) -+ i915_gem_shrink(i915, LONG_MAX, NULL, I915_SHRINK_UNBOUND); -+ -+ if (val & DROP_SHRINK_ALL) -+ i915_gem_shrink_all(i915); -+ fs_reclaim_release(GFP_KERNEL); -+ -+ if (val & DROP_IDLE) { -+ do { -+ if (READ_ONCE(i915->gt.active_requests)) -+ flush_delayed_work(&i915->gt.retire_work); -+ drain_delayed_work(&i915->gt.idle_work); -+ } while (READ_ONCE(i915->gt.awake)); -+ } -+ -+ if (val & DROP_FREED) -+ i915_gem_drain_freed_objects(i915); -+ -+ return 0; -+} -+ -+DEFINE_SIMPLE_ATTRIBUTE(i915_drop_caches_fops, -+ i915_drop_caches_get, i915_drop_caches_set, -+ "0x%08llx\n"); -+ -+static int -+i915_cache_sharing_get(void *data, u64 *val) -+{ -+ struct drm_i915_private *dev_priv = data; -+ intel_wakeref_t wakeref; -+ u32 snpcr = 0; -+ -+ if (!(IS_GEN_RANGE(dev_priv, 6, 7))) -+ return -ENODEV; -+ -+ with_intel_runtime_pm(dev_priv, wakeref) -+ snpcr = I915_READ(GEN6_MBCUNIT_SNPCR); -+ -+ *val = (snpcr & GEN6_MBC_SNPCR_MASK) >> GEN6_MBC_SNPCR_SHIFT; -+ -+ return 0; -+} -+ -+static int -+i915_cache_sharing_set(void *data, u64 val) -+{ -+ struct drm_i915_private *dev_priv = data; -+ intel_wakeref_t wakeref; -+ -+ if (!(IS_GEN_RANGE(dev_priv, 6, 7))) -+ return -ENODEV; -+ -+ if (val > 3) -+ return -EINVAL; -+ -+ DRM_DEBUG_DRIVER("Manually setting uncore sharing to %llu\n", val); -+ with_intel_runtime_pm(dev_priv, wakeref) { -+ u32 snpcr; -+ -+ /* Update the cache sharing policy here as well */ -+ snpcr = I915_READ(GEN6_MBCUNIT_SNPCR); -+ snpcr &= ~GEN6_MBC_SNPCR_MASK; -+ snpcr |= val << GEN6_MBC_SNPCR_SHIFT; -+ I915_WRITE(GEN6_MBCUNIT_SNPCR, snpcr); -+ } -+ -+ return 0; -+} -+ -+DEFINE_SIMPLE_ATTRIBUTE(i915_cache_sharing_fops, -+ i915_cache_sharing_get, i915_cache_sharing_set, -+ "%llu\n"); -+ -+static void cherryview_sseu_device_status(struct drm_i915_private *dev_priv, -+ struct sseu_dev_info *sseu) -+{ -+#define SS_MAX 2 -+ const int ss_max = SS_MAX; -+ u32 sig1[SS_MAX], sig2[SS_MAX]; -+ int ss; -+ -+ sig1[0] = I915_READ(CHV_POWER_SS0_SIG1); -+ sig1[1] = I915_READ(CHV_POWER_SS1_SIG1); -+ sig2[0] = I915_READ(CHV_POWER_SS0_SIG2); -+ sig2[1] = I915_READ(CHV_POWER_SS1_SIG2); -+ -+ for (ss = 0; ss < ss_max; ss++) { -+ unsigned int eu_cnt; -+ -+ if (sig1[ss] & CHV_SS_PG_ENABLE) -+ /* skip disabled subslice */ -+ continue; -+ -+ sseu->slice_mask = BIT(0); -+ sseu->subslice_mask[0] |= BIT(ss); -+ eu_cnt = ((sig1[ss] & CHV_EU08_PG_ENABLE) ? 0 : 2) + -+ ((sig1[ss] & CHV_EU19_PG_ENABLE) ? 0 : 2) + -+ ((sig1[ss] & CHV_EU210_PG_ENABLE) ? 0 : 2) + -+ ((sig2[ss] & CHV_EU311_PG_ENABLE) ? 0 : 2); -+ sseu->eu_total += eu_cnt; -+ sseu->eu_per_subslice = max_t(unsigned int, -+ sseu->eu_per_subslice, eu_cnt); -+ } -+#undef SS_MAX -+} -+ -+static void gen10_sseu_device_status(struct drm_i915_private *dev_priv, -+ struct sseu_dev_info *sseu) -+{ -+#define SS_MAX 6 -+ const struct intel_runtime_info *info = RUNTIME_INFO(dev_priv); -+ u32 s_reg[SS_MAX], eu_reg[2 * SS_MAX], eu_mask[2]; -+ int s, ss; -+ -+ for (s = 0; s < info->sseu.max_slices; s++) { -+ /* -+ * FIXME: Valid SS Mask respects the spec and read -+ * only valid bits for those registers, excluding reserved -+ * although this seems wrong because it would leave many -+ * subslices without ACK. -+ */ -+ s_reg[s] = I915_READ(GEN10_SLICE_PGCTL_ACK(s)) & -+ GEN10_PGCTL_VALID_SS_MASK(s); -+ eu_reg[2 * s] = I915_READ(GEN10_SS01_EU_PGCTL_ACK(s)); -+ eu_reg[2 * s + 1] = I915_READ(GEN10_SS23_EU_PGCTL_ACK(s)); -+ } -+ -+ eu_mask[0] = GEN9_PGCTL_SSA_EU08_ACK | -+ GEN9_PGCTL_SSA_EU19_ACK | -+ GEN9_PGCTL_SSA_EU210_ACK | -+ GEN9_PGCTL_SSA_EU311_ACK; -+ eu_mask[1] = GEN9_PGCTL_SSB_EU08_ACK | -+ GEN9_PGCTL_SSB_EU19_ACK | -+ GEN9_PGCTL_SSB_EU210_ACK | -+ GEN9_PGCTL_SSB_EU311_ACK; -+ -+ for (s = 0; s < info->sseu.max_slices; s++) { -+ if ((s_reg[s] & GEN9_PGCTL_SLICE_ACK) == 0) -+ /* skip disabled slice */ -+ continue; -+ -+ sseu->slice_mask |= BIT(s); -+ sseu->subslice_mask[s] = info->sseu.subslice_mask[s]; -+ -+ for (ss = 0; ss < info->sseu.max_subslices; ss++) { -+ unsigned int eu_cnt; -+ -+ if (!(s_reg[s] & (GEN9_PGCTL_SS_ACK(ss)))) -+ /* skip disabled subslice */ -+ continue; -+ -+ eu_cnt = 2 * hweight32(eu_reg[2 * s + ss / 2] & -+ eu_mask[ss % 2]); -+ sseu->eu_total += eu_cnt; -+ sseu->eu_per_subslice = max_t(unsigned int, -+ sseu->eu_per_subslice, -+ eu_cnt); -+ } -+ } -+#undef SS_MAX -+} -+ -+static void gen9_sseu_device_status(struct drm_i915_private *dev_priv, -+ struct sseu_dev_info *sseu) -+{ -+#define SS_MAX 3 -+ const struct intel_runtime_info *info = RUNTIME_INFO(dev_priv); -+ u32 s_reg[SS_MAX], eu_reg[2 * SS_MAX], eu_mask[2]; -+ int s, ss; -+ -+ for (s = 0; s < info->sseu.max_slices; s++) { -+ s_reg[s] = I915_READ(GEN9_SLICE_PGCTL_ACK(s)); -+ eu_reg[2*s] = I915_READ(GEN9_SS01_EU_PGCTL_ACK(s)); -+ eu_reg[2*s + 1] = I915_READ(GEN9_SS23_EU_PGCTL_ACK(s)); -+ } -+ -+ eu_mask[0] = GEN9_PGCTL_SSA_EU08_ACK | -+ GEN9_PGCTL_SSA_EU19_ACK | -+ GEN9_PGCTL_SSA_EU210_ACK | -+ GEN9_PGCTL_SSA_EU311_ACK; -+ eu_mask[1] = GEN9_PGCTL_SSB_EU08_ACK | -+ GEN9_PGCTL_SSB_EU19_ACK | -+ GEN9_PGCTL_SSB_EU210_ACK | -+ GEN9_PGCTL_SSB_EU311_ACK; -+ -+ for (s = 0; s < info->sseu.max_slices; s++) { -+ if ((s_reg[s] & GEN9_PGCTL_SLICE_ACK) == 0) -+ /* skip disabled slice */ -+ continue; -+ -+ sseu->slice_mask |= BIT(s); -+ -+ if (IS_GEN9_BC(dev_priv)) -+ sseu->subslice_mask[s] = -+ RUNTIME_INFO(dev_priv)->sseu.subslice_mask[s]; -+ -+ for (ss = 0; ss < info->sseu.max_subslices; ss++) { -+ unsigned int eu_cnt; -+ -+ if (IS_GEN9_LP(dev_priv)) { -+ if (!(s_reg[s] & (GEN9_PGCTL_SS_ACK(ss)))) -+ /* skip disabled subslice */ -+ continue; -+ -+ sseu->subslice_mask[s] |= BIT(ss); -+ } -+ -+ eu_cnt = 2 * hweight32(eu_reg[2*s + ss/2] & -+ eu_mask[ss%2]); -+ sseu->eu_total += eu_cnt; -+ sseu->eu_per_subslice = max_t(unsigned int, -+ sseu->eu_per_subslice, -+ eu_cnt); -+ } -+ } -+#undef SS_MAX -+} -+ -+static void broadwell_sseu_device_status(struct drm_i915_private *dev_priv, -+ struct sseu_dev_info *sseu) -+{ -+ u32 slice_info = I915_READ(GEN8_GT_SLICE_INFO); -+ int s; -+ -+ sseu->slice_mask = slice_info & GEN8_LSLICESTAT_MASK; -+ -+ if (sseu->slice_mask) { -+ sseu->eu_per_subslice = -+ RUNTIME_INFO(dev_priv)->sseu.eu_per_subslice; -+ for (s = 0; s < fls(sseu->slice_mask); s++) { -+ sseu->subslice_mask[s] = -+ RUNTIME_INFO(dev_priv)->sseu.subslice_mask[s]; -+ } -+ sseu->eu_total = sseu->eu_per_subslice * -+ sseu_subslice_total(sseu); -+ -+ /* subtract fused off EU(s) from enabled slice(s) */ -+ for (s = 0; s < fls(sseu->slice_mask); s++) { -+ u8 subslice_7eu = -+ RUNTIME_INFO(dev_priv)->sseu.subslice_7eu[s]; -+ -+ sseu->eu_total -= hweight8(subslice_7eu); -+ } -+ } -+} -+ -+static void i915_print_sseu_info(struct seq_file *m, bool is_available_info, -+ const struct sseu_dev_info *sseu) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ const char *type = is_available_info ? "Available" : "Enabled"; -+ int s; -+ -+ seq_printf(m, " %s Slice Mask: %04x\n", type, -+ sseu->slice_mask); -+ seq_printf(m, " %s Slice Total: %u\n", type, -+ hweight8(sseu->slice_mask)); -+ seq_printf(m, " %s Subslice Total: %u\n", type, -+ sseu_subslice_total(sseu)); -+ for (s = 0; s < fls(sseu->slice_mask); s++) { -+ seq_printf(m, " %s Slice%i subslices: %u\n", type, -+ s, hweight8(sseu->subslice_mask[s])); -+ } -+ seq_printf(m, " %s EU Total: %u\n", type, -+ sseu->eu_total); -+ seq_printf(m, " %s EU Per Subslice: %u\n", type, -+ sseu->eu_per_subslice); -+ -+ if (!is_available_info) -+ return; -+ -+ seq_printf(m, " Has Pooled EU: %s\n", yesno(HAS_POOLED_EU(dev_priv))); -+ if (HAS_POOLED_EU(dev_priv)) -+ seq_printf(m, " Min EU in pool: %u\n", sseu->min_eu_in_pool); -+ -+ seq_printf(m, " Has Slice Power Gating: %s\n", -+ yesno(sseu->has_slice_pg)); -+ seq_printf(m, " Has Subslice Power Gating: %s\n", -+ yesno(sseu->has_subslice_pg)); -+ seq_printf(m, " Has EU Power Gating: %s\n", -+ yesno(sseu->has_eu_pg)); -+} -+ -+static int i915_sseu_status(struct seq_file *m, void *unused) -+{ -+ struct drm_i915_private *dev_priv = node_to_i915(m->private); -+ struct sseu_dev_info sseu; -+ intel_wakeref_t wakeref; -+ -+ if (INTEL_GEN(dev_priv) < 8) -+ return -ENODEV; -+ -+ seq_puts(m, "SSEU Device Info\n"); -+ i915_print_sseu_info(m, true, &RUNTIME_INFO(dev_priv)->sseu); -+ -+ seq_puts(m, "SSEU Device Status\n"); -+ memset(&sseu, 0, sizeof(sseu)); -+ sseu.max_slices = RUNTIME_INFO(dev_priv)->sseu.max_slices; -+ sseu.max_subslices = RUNTIME_INFO(dev_priv)->sseu.max_subslices; -+ sseu.max_eus_per_subslice = -+ RUNTIME_INFO(dev_priv)->sseu.max_eus_per_subslice; -+ -+ with_intel_runtime_pm(dev_priv, wakeref) { -+ if (IS_CHERRYVIEW(dev_priv)) -+ cherryview_sseu_device_status(dev_priv, &sseu); -+ else if (IS_BROADWELL(dev_priv)) -+ broadwell_sseu_device_status(dev_priv, &sseu); -+ else if (IS_GEN(dev_priv, 9)) -+ gen9_sseu_device_status(dev_priv, &sseu); -+ else if (INTEL_GEN(dev_priv) >= 10) -+ gen10_sseu_device_status(dev_priv, &sseu); -+ } -+ -+ i915_print_sseu_info(m, false, &sseu); -+ -+ return 0; -+} -+ -+static int i915_forcewake_open(struct inode *inode, struct file *file) -+{ -+ struct drm_i915_private *i915 = inode->i_private; -+ -+ if (INTEL_GEN(i915) < 6) -+ return 0; -+ -+ file->private_data = (void *)(uintptr_t)intel_runtime_pm_get(i915); -+ intel_uncore_forcewake_user_get(&i915->uncore); -+ -+ return 0; -+} -+ -+static int i915_forcewake_release(struct inode *inode, struct file *file) -+{ -+ struct drm_i915_private *i915 = inode->i_private; -+ -+ if (INTEL_GEN(i915) < 6) -+ return 0; -+ -+ intel_uncore_forcewake_user_put(&i915->uncore); -+ intel_runtime_pm_put(i915, -+ (intel_wakeref_t)(uintptr_t)file->private_data); -+ -+ return 0; -+} -+ -+static const struct file_operations i915_forcewake_fops = { -+ .owner = THIS_MODULE, -+ .open = i915_forcewake_open, -+ .release = i915_forcewake_release, -+}; -+ -+static int i915_hpd_storm_ctl_show(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = m->private; -+ struct i915_hotplug *hotplug = &dev_priv->hotplug; -+ -+ /* Synchronize with everything first in case there's been an HPD -+ * storm, but we haven't finished handling it in the kernel yet -+ */ -+ synchronize_irq(dev_priv->drm.irq); -+ flush_work(&dev_priv->hotplug.dig_port_work); -+ flush_work(&dev_priv->hotplug.hotplug_work); -+ -+ seq_printf(m, "Threshold: %d\n", hotplug->hpd_storm_threshold); -+ seq_printf(m, "Detected: %s\n", -+ yesno(delayed_work_pending(&hotplug->reenable_work))); -+ -+ return 0; -+} -+ -+static ssize_t i915_hpd_storm_ctl_write(struct file *file, -+ const char __user *ubuf, size_t len, -+ loff_t *offp) -+{ -+ struct seq_file *m = file->private_data; -+ struct drm_i915_private *dev_priv = m->private; -+ struct i915_hotplug *hotplug = &dev_priv->hotplug; -+ unsigned int new_threshold; -+ int i; -+ char *newline; -+ char tmp[16]; -+ -+ if (len >= sizeof(tmp)) -+ return -EINVAL; -+ -+ if (copy_from_user(tmp, ubuf, len)) -+ return -EFAULT; -+ -+ tmp[len] = '\0'; -+ -+ /* Strip newline, if any */ -+ newline = strchr(tmp, '\n'); -+ if (newline) -+ *newline = '\0'; -+ -+ if (strcmp(tmp, "reset") == 0) -+ new_threshold = HPD_STORM_DEFAULT_THRESHOLD; -+ else if (kstrtouint(tmp, 10, &new_threshold) != 0) -+ return -EINVAL; -+ -+ if (new_threshold > 0) -+ DRM_DEBUG_KMS("Setting HPD storm detection threshold to %d\n", -+ new_threshold); -+ else -+ DRM_DEBUG_KMS("Disabling HPD storm detection\n"); -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ hotplug->hpd_storm_threshold = new_threshold; -+ /* Reset the HPD storm stats so we don't accidentally trigger a storm */ -+ for_each_hpd_pin(i) -+ hotplug->stats[i].count = 0; -+ spin_unlock_irq(&dev_priv->irq_lock); -+ -+ /* Re-enable hpd immediately if we were in an irq storm */ -+ flush_delayed_work(&dev_priv->hotplug.reenable_work); -+ -+ return len; -+} -+ -+static int i915_hpd_storm_ctl_open(struct inode *inode, struct file *file) -+{ -+ return single_open(file, i915_hpd_storm_ctl_show, inode->i_private); -+} -+ -+static const struct file_operations i915_hpd_storm_ctl_fops = { -+ .owner = THIS_MODULE, -+ .open = i915_hpd_storm_ctl_open, -+ .read = seq_read, -+ .llseek = seq_lseek, -+ .release = single_release, -+ .write = i915_hpd_storm_ctl_write -+}; -+ -+static int i915_hpd_short_storm_ctl_show(struct seq_file *m, void *data) -+{ -+ struct drm_i915_private *dev_priv = m->private; -+ -+ seq_printf(m, "Enabled: %s\n", -+ yesno(dev_priv->hotplug.hpd_short_storm_enabled)); -+ -+ return 0; -+} -+ -+static int -+i915_hpd_short_storm_ctl_open(struct inode *inode, struct file *file) -+{ -+ return single_open(file, i915_hpd_short_storm_ctl_show, -+ inode->i_private); -+} -+ -+static ssize_t i915_hpd_short_storm_ctl_write(struct file *file, -+ const char __user *ubuf, -+ size_t len, loff_t *offp) -+{ -+ struct seq_file *m = file->private_data; -+ struct drm_i915_private *dev_priv = m->private; -+ struct i915_hotplug *hotplug = &dev_priv->hotplug; -+ char *newline; -+ char tmp[16]; -+ int i; -+ bool new_state; -+ -+ if (len >= sizeof(tmp)) -+ return -EINVAL; -+ -+ if (copy_from_user(tmp, ubuf, len)) -+ return -EFAULT; -+ -+ tmp[len] = '\0'; -+ -+ /* Strip newline, if any */ -+ newline = strchr(tmp, '\n'); -+ if (newline) -+ *newline = '\0'; -+ -+ /* Reset to the "default" state for this system */ -+ if (strcmp(tmp, "reset") == 0) -+ new_state = !HAS_DP_MST(dev_priv); -+ else if (kstrtobool(tmp, &new_state) != 0) -+ return -EINVAL; -+ -+ DRM_DEBUG_KMS("%sabling HPD short storm detection\n", -+ new_state ? "En" : "Dis"); -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ hotplug->hpd_short_storm_enabled = new_state; -+ /* Reset the HPD storm stats so we don't accidentally trigger a storm */ -+ for_each_hpd_pin(i) -+ hotplug->stats[i].count = 0; -+ spin_unlock_irq(&dev_priv->irq_lock); -+ -+ /* Re-enable hpd immediately if we were in an irq storm */ -+ flush_delayed_work(&dev_priv->hotplug.reenable_work); -+ -+ return len; -+} -+ -+static const struct file_operations i915_hpd_short_storm_ctl_fops = { -+ .owner = THIS_MODULE, -+ .open = i915_hpd_short_storm_ctl_open, -+ .read = seq_read, -+ .llseek = seq_lseek, -+ .release = single_release, -+ .write = i915_hpd_short_storm_ctl_write, -+}; -+ -+static int i915_drrs_ctl_set(void *data, u64 val) -+{ -+ struct drm_i915_private *dev_priv = data; -+ struct drm_device *dev = &dev_priv->drm; -+ struct intel_crtc *crtc; -+ -+ if (INTEL_GEN(dev_priv) < 7) -+ return -ENODEV; -+ -+ for_each_intel_crtc(dev, crtc) { -+ struct drm_connector_list_iter conn_iter; -+ struct intel_crtc_state *crtc_state; -+ struct drm_connector *connector; -+ struct drm_crtc_commit *commit; -+ int ret; -+ -+ ret = drm_modeset_lock_single_interruptible(&crtc->base.mutex); -+ if (ret) -+ return ret; -+ -+ crtc_state = to_intel_crtc_state(crtc->base.state); -+ -+ if (!crtc_state->base.active || -+ !crtc_state->has_drrs) -+ goto out; -+ -+ commit = crtc_state->base.commit; -+ if (commit) { -+ ret = wait_for_completion_interruptible(&commit->hw_done); -+ if (ret) -+ goto out; -+ } -+ -+ drm_connector_list_iter_begin(dev, &conn_iter); -+ drm_for_each_connector_iter(connector, &conn_iter) { -+ struct intel_encoder *encoder; -+ struct intel_dp *intel_dp; -+ -+ if (!(crtc_state->base.connector_mask & -+ drm_connector_mask(connector))) -+ continue; -+ -+ encoder = intel_attached_encoder(connector); -+ if (encoder->type != INTEL_OUTPUT_EDP) -+ continue; -+ -+ DRM_DEBUG_DRIVER("Manually %sabling DRRS. %llu\n", -+ val ? "en" : "dis", val); -+ -+ intel_dp = enc_to_intel_dp(&encoder->base); -+ if (val) -+ intel_edp_drrs_enable(intel_dp, -+ crtc_state); -+ else -+ intel_edp_drrs_disable(intel_dp, -+ crtc_state); -+ } -+ drm_connector_list_iter_end(&conn_iter); -+ -+out: -+ drm_modeset_unlock(&crtc->base.mutex); -+ if (ret) -+ return ret; -+ } -+ -+ return 0; -+} -+ -+DEFINE_SIMPLE_ATTRIBUTE(i915_drrs_ctl_fops, NULL, i915_drrs_ctl_set, "%llu\n"); -+ -+static ssize_t -+i915_fifo_underrun_reset_write(struct file *filp, -+ const char __user *ubuf, -+ size_t cnt, loff_t *ppos) -+{ -+ struct drm_i915_private *dev_priv = filp->private_data; -+ struct intel_crtc *intel_crtc; -+ struct drm_device *dev = &dev_priv->drm; -+ int ret; -+ bool reset; -+ -+ ret = kstrtobool_from_user(ubuf, cnt, &reset); -+ if (ret) -+ return ret; -+ -+ if (!reset) -+ return cnt; -+ -+ for_each_intel_crtc(dev, intel_crtc) { -+ struct drm_crtc_commit *commit; -+ struct intel_crtc_state *crtc_state; -+ -+ ret = drm_modeset_lock_single_interruptible(&intel_crtc->base.mutex); -+ if (ret) -+ return ret; -+ -+ crtc_state = to_intel_crtc_state(intel_crtc->base.state); -+ commit = crtc_state->base.commit; -+ if (commit) { -+ ret = wait_for_completion_interruptible(&commit->hw_done); -+ if (!ret) -+ ret = wait_for_completion_interruptible(&commit->flip_done); -+ } -+ -+ if (!ret && crtc_state->base.active) { -+ DRM_DEBUG_KMS("Re-arming FIFO underruns on pipe %c\n", -+ pipe_name(intel_crtc->pipe)); -+ -+ intel_crtc_arm_fifo_underrun(intel_crtc, crtc_state); -+ } -+ -+ drm_modeset_unlock(&intel_crtc->base.mutex); -+ -+ if (ret) -+ return ret; -+ } -+ -+ ret = intel_fbc_reset_underrun(dev_priv); -+ if (ret) -+ return ret; -+ -+ return cnt; -+} -+ -+static const struct file_operations i915_fifo_underrun_reset_ops = { -+ .owner = THIS_MODULE, -+ .open = simple_open, -+ .write = i915_fifo_underrun_reset_write, -+ .llseek = default_llseek, -+}; -+ -+static const struct drm_info_list i915_debugfs_list[] = { -+ {"i915_capabilities", i915_capabilities, 0}, -+ {"i915_gem_objects", i915_gem_object_info, 0}, -+ {"i915_gem_gtt", i915_gem_gtt_info, 0}, -+ {"i915_gem_stolen", i915_gem_stolen_list_info }, -+ {"i915_gem_fence_regs", i915_gem_fence_regs_info, 0}, -+ {"i915_gem_interrupt", i915_interrupt_info, 0}, -+ {"i915_gem_batch_pool", i915_gem_batch_pool_info, 0}, -+ {"i915_guc_info", i915_guc_info, 0}, -+ {"i915_guc_load_status", i915_guc_load_status_info, 0}, -+ {"i915_guc_log_dump", i915_guc_log_dump, 0}, -+ {"i915_guc_load_err_log_dump", i915_guc_log_dump, 0, (void *)1}, -+ {"i915_guc_stage_pool", i915_guc_stage_pool, 0}, -+ {"i915_huc_load_status", i915_huc_load_status_info, 0}, -+ {"i915_frequency_info", i915_frequency_info, 0}, -+ {"i915_hangcheck_info", i915_hangcheck_info, 0}, -+ {"i915_reset_info", i915_reset_info, 0}, -+ {"i915_drpc_info", i915_drpc_info, 0}, -+ {"i915_emon_status", i915_emon_status, 0}, -+ {"i915_ring_freq_table", i915_ring_freq_table, 0}, -+ {"i915_frontbuffer_tracking", i915_frontbuffer_tracking, 0}, -+ {"i915_fbc_status", i915_fbc_status, 0}, -+ {"i915_ips_status", i915_ips_status, 0}, -+ {"i915_sr_status", i915_sr_status, 0}, -+ {"i915_opregion", i915_opregion, 0}, -+ {"i915_vbt", i915_vbt, 0}, -+ {"i915_gem_framebuffer", i915_gem_framebuffer_info, 0}, -+ {"i915_context_status", i915_context_status, 0}, -+ {"i915_forcewake_domains", i915_forcewake_domains, 0}, -+ {"i915_swizzle_info", i915_swizzle_info, 0}, -+ {"i915_llc", i915_llc, 0}, -+ {"i915_edp_psr_status", i915_edp_psr_status, 0}, -+ {"i915_energy_uJ", i915_energy_uJ, 0}, -+ {"i915_runtime_pm_status", i915_runtime_pm_status, 0}, -+ {"i915_power_domain_info", i915_power_domain_info, 0}, -+ {"i915_dmc_info", i915_dmc_info, 0}, -+ {"i915_display_info", i915_display_info, 0}, -+ {"i915_engine_info", i915_engine_info, 0}, -+ {"i915_rcs_topology", i915_rcs_topology, 0}, -+ {"i915_shrinker_info", i915_shrinker_info, 0}, -+ {"i915_shared_dplls_info", i915_shared_dplls_info, 0}, -+ {"i915_dp_mst_info", i915_dp_mst_info, 0}, -+ {"i915_wa_registers", i915_wa_registers, 0}, -+ {"i915_ddb_info", i915_ddb_info, 0}, -+ {"i915_sseu_status", i915_sseu_status, 0}, -+ {"i915_drrs_status", i915_drrs_status, 0}, -+ {"i915_rps_boost_info", i915_rps_boost_info, 0}, -+}; -+#define I915_DEBUGFS_ENTRIES ARRAY_SIZE(i915_debugfs_list) -+ -+static const struct i915_debugfs_files { -+ const char *name; -+ const struct file_operations *fops; -+} i915_debugfs_files[] = { -+ {"i915_wedged", &i915_wedged_fops}, -+ {"i915_cache_sharing", &i915_cache_sharing_fops}, -+ {"i915_gem_drop_caches", &i915_drop_caches_fops}, -+#if IS_ENABLED(CONFIG_DRM_I915_CAPTURE_ERROR) -+ {"i915_error_state", &i915_error_state_fops}, -+ {"i915_gpu_info", &i915_gpu_info_fops}, -+#endif -+ {"i915_fifo_underrun_reset", &i915_fifo_underrun_reset_ops}, -+ {"i915_pri_wm_latency", &i915_pri_wm_latency_fops}, -+ {"i915_spr_wm_latency", &i915_spr_wm_latency_fops}, -+ {"i915_cur_wm_latency", &i915_cur_wm_latency_fops}, -+ {"i915_fbc_false_color", &i915_fbc_false_color_fops}, -+ {"i915_dp_test_data", &i915_displayport_test_data_fops}, -+ {"i915_dp_test_type", &i915_displayport_test_type_fops}, -+ {"i915_dp_test_active", &i915_displayport_test_active_fops}, -+ {"i915_guc_log_level", &i915_guc_log_level_fops}, -+ {"i915_guc_log_relay", &i915_guc_log_relay_fops}, -+ {"i915_hpd_storm_ctl", &i915_hpd_storm_ctl_fops}, -+ {"i915_hpd_short_storm_ctl", &i915_hpd_short_storm_ctl_fops}, -+ {"i915_ipc_status", &i915_ipc_status_fops}, -+ {"i915_drrs_ctl", &i915_drrs_ctl_fops}, -+ {"i915_edp_psr_debug", &i915_edp_psr_debug_fops} -+}; -+ -+int i915_debugfs_register(struct drm_i915_private *dev_priv) -+{ -+ struct drm_minor *minor = dev_priv->drm.primary; -+ struct dentry *ent; -+ int i; -+ -+ ent = debugfs_create_file("i915_forcewake_user", S_IRUSR, -+ minor->debugfs_root, to_i915(minor->dev), -+ &i915_forcewake_fops); -+ if (!ent) -+ return -ENOMEM; -+ -+ for (i = 0; i < ARRAY_SIZE(i915_debugfs_files); i++) { -+ ent = debugfs_create_file(i915_debugfs_files[i].name, -+ S_IRUGO | S_IWUSR, -+ minor->debugfs_root, -+ to_i915(minor->dev), -+ i915_debugfs_files[i].fops); -+ if (!ent) -+ return -ENOMEM; -+ } -+ -+ return drm_debugfs_create_files(i915_debugfs_list, -+ I915_DEBUGFS_ENTRIES, -+ minor->debugfs_root, minor); -+} -+ -+struct dpcd_block { -+ /* DPCD dump start address. */ -+ unsigned int offset; -+ /* DPCD dump end address, inclusive. If unset, .size will be used. */ -+ unsigned int end; -+ /* DPCD dump size. Used if .end is unset. If unset, defaults to 1. */ -+ size_t size; -+ /* Only valid for eDP. */ -+ bool edp; -+}; -+ -+static const struct dpcd_block i915_dpcd_debug[] = { -+ { .offset = DP_DPCD_REV, .size = DP_RECEIVER_CAP_SIZE }, -+ { .offset = DP_PSR_SUPPORT, .end = DP_PSR_CAPS }, -+ { .offset = DP_DOWNSTREAM_PORT_0, .size = 16 }, -+ { .offset = DP_LINK_BW_SET, .end = DP_EDP_CONFIGURATION_SET }, -+ { .offset = DP_SINK_COUNT, .end = DP_ADJUST_REQUEST_LANE2_3 }, -+ { .offset = DP_SET_POWER }, -+ { .offset = DP_EDP_DPCD_REV }, -+ { .offset = DP_EDP_GENERAL_CAP_1, .end = DP_EDP_GENERAL_CAP_3 }, -+ { .offset = DP_EDP_DISPLAY_CONTROL_REGISTER, .end = DP_EDP_BACKLIGHT_FREQ_CAP_MAX_LSB }, -+ { .offset = DP_EDP_DBC_MINIMUM_BRIGHTNESS_SET, .end = DP_EDP_DBC_MAXIMUM_BRIGHTNESS_SET }, -+}; -+ -+static int i915_dpcd_show(struct seq_file *m, void *data) -+{ -+ struct drm_connector *connector = m->private; -+ struct intel_dp *intel_dp = -+ enc_to_intel_dp(&intel_attached_encoder(connector)->base); -+ u8 buf[16]; -+ ssize_t err; -+ int i; -+ -+ if (connector->status != connector_status_connected) -+ return -ENODEV; -+ -+ for (i = 0; i < ARRAY_SIZE(i915_dpcd_debug); i++) { -+ const struct dpcd_block *b = &i915_dpcd_debug[i]; -+ size_t size = b->end ? b->end - b->offset + 1 : (b->size ?: 1); -+ -+ if (b->edp && -+ connector->connector_type != DRM_MODE_CONNECTOR_eDP) -+ continue; -+ -+ /* low tech for now */ -+ if (WARN_ON(size > sizeof(buf))) -+ continue; -+ -+ err = drm_dp_dpcd_read(&intel_dp->aux, b->offset, buf, size); -+ if (err < 0) -+ seq_printf(m, "%04x: ERROR %d\n", b->offset, (int)err); -+ else -+ seq_printf(m, "%04x: %*ph\n", b->offset, (int)err, buf); -+ } -+ -+ return 0; -+} -+DEFINE_SHOW_ATTRIBUTE(i915_dpcd); -+ -+static int i915_panel_show(struct seq_file *m, void *data) -+{ -+ struct drm_connector *connector = m->private; -+ struct intel_dp *intel_dp = -+ enc_to_intel_dp(&intel_attached_encoder(connector)->base); -+ -+ if (connector->status != connector_status_connected) -+ return -ENODEV; -+ -+ seq_printf(m, "Panel power up delay: %d\n", -+ intel_dp->panel_power_up_delay); -+ seq_printf(m, "Panel power down delay: %d\n", -+ intel_dp->panel_power_down_delay); -+ seq_printf(m, "Backlight on delay: %d\n", -+ intel_dp->backlight_on_delay); -+ seq_printf(m, "Backlight off delay: %d\n", -+ intel_dp->backlight_off_delay); -+ -+ return 0; -+} -+DEFINE_SHOW_ATTRIBUTE(i915_panel); -+ -+static int i915_hdcp_sink_capability_show(struct seq_file *m, void *data) -+{ -+ struct drm_connector *connector = m->private; -+ struct intel_connector *intel_connector = to_intel_connector(connector); -+ -+ if (connector->status != connector_status_connected) -+ return -ENODEV; -+ -+ /* HDCP is supported by connector */ -+ if (!intel_connector->hdcp.shim) -+ return -EINVAL; -+ -+ seq_printf(m, "%s:%d HDCP version: ", connector->name, -+ connector->base.id); -+ seq_printf(m, "%s ", !intel_hdcp_capable(intel_connector) ? -+ "None" : "HDCP1.4"); -+ seq_puts(m, "\n"); -+ -+ return 0; -+} -+DEFINE_SHOW_ATTRIBUTE(i915_hdcp_sink_capability); -+ -+static int i915_dsc_fec_support_show(struct seq_file *m, void *data) -+{ -+ struct drm_connector *connector = m->private; -+ struct drm_device *dev = connector->dev; -+ struct drm_crtc *crtc; -+ struct intel_dp *intel_dp; -+ struct drm_modeset_acquire_ctx ctx; -+ struct intel_crtc_state *crtc_state = NULL; -+ int ret = 0; -+ bool try_again = false; -+ -+ drm_modeset_acquire_init(&ctx, DRM_MODESET_ACQUIRE_INTERRUPTIBLE); -+ -+ do { -+ try_again = false; -+ ret = drm_modeset_lock(&dev->mode_config.connection_mutex, -+ &ctx); -+ if (ret) { -+ if (ret == -EDEADLK && !drm_modeset_backoff(&ctx)) { -+ try_again = true; -+ continue; -+ } -+ break; -+ } -+ crtc = connector->state->crtc; -+ if (connector->status != connector_status_connected || !crtc) { -+ ret = -ENODEV; -+ break; -+ } -+ ret = drm_modeset_lock(&crtc->mutex, &ctx); -+ if (ret == -EDEADLK) { -+ ret = drm_modeset_backoff(&ctx); -+ if (!ret) { -+ try_again = true; -+ continue; -+ } -+ break; -+ } else if (ret) { -+ break; -+ } -+ intel_dp = enc_to_intel_dp(&intel_attached_encoder(connector)->base); -+ crtc_state = to_intel_crtc_state(crtc->state); -+ seq_printf(m, "DSC_Enabled: %s\n", -+ yesno(crtc_state->dsc_params.compression_enable)); -+ seq_printf(m, "DSC_Sink_Support: %s\n", -+ yesno(drm_dp_sink_supports_dsc(intel_dp->dsc_dpcd))); -+ seq_printf(m, "Force_DSC_Enable: %s\n", -+ yesno(intel_dp->force_dsc_en)); -+ if (!intel_dp_is_edp(intel_dp)) -+ seq_printf(m, "FEC_Sink_Support: %s\n", -+ yesno(drm_dp_sink_supports_fec(intel_dp->fec_capable))); -+ } while (try_again); -+ -+ drm_modeset_drop_locks(&ctx); -+ drm_modeset_acquire_fini(&ctx); -+ -+ return ret; -+} -+ -+static ssize_t i915_dsc_fec_support_write(struct file *file, -+ const char __user *ubuf, -+ size_t len, loff_t *offp) -+{ -+ bool dsc_enable = false; -+ int ret; -+ struct drm_connector *connector = -+ ((struct seq_file *)file->private_data)->private; -+ struct intel_encoder *encoder = intel_attached_encoder(connector); -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ -+ if (len == 0) -+ return 0; -+ -+ DRM_DEBUG_DRIVER("Copied %zu bytes from user to force DSC\n", -+ len); -+ -+ ret = kstrtobool_from_user(ubuf, len, &dsc_enable); -+ if (ret < 0) -+ return ret; -+ -+ DRM_DEBUG_DRIVER("Got %s for DSC Enable\n", -+ (dsc_enable) ? "true" : "false"); -+ intel_dp->force_dsc_en = dsc_enable; -+ -+ *offp += len; -+ return len; -+} -+ -+static int i915_dsc_fec_support_open(struct inode *inode, -+ struct file *file) -+{ -+ return single_open(file, i915_dsc_fec_support_show, -+ inode->i_private); -+} -+ -+static const struct file_operations i915_dsc_fec_support_fops = { -+ .owner = THIS_MODULE, -+ .open = i915_dsc_fec_support_open, -+ .read = seq_read, -+ .llseek = seq_lseek, -+ .release = single_release, -+ .write = i915_dsc_fec_support_write -+}; -+ -+/** -+ * i915_debugfs_connector_add - add i915 specific connector debugfs files -+ * @connector: pointer to a registered drm_connector -+ * -+ * Cleanup will be done by drm_connector_unregister() through a call to -+ * drm_debugfs_connector_remove(). -+ * -+ * Returns 0 on success, negative error codes on error. -+ */ -+int i915_debugfs_connector_add(struct drm_connector *connector) -+{ -+ struct dentry *root = connector->debugfs_entry; -+ struct drm_i915_private *dev_priv = to_i915(connector->dev); -+ -+ /* The connector must have been registered beforehands. */ -+ if (!root) -+ return -ENODEV; -+ -+ if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort || -+ connector->connector_type == DRM_MODE_CONNECTOR_eDP) -+ debugfs_create_file("i915_dpcd", S_IRUGO, root, -+ connector, &i915_dpcd_fops); -+ -+ if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) { -+ debugfs_create_file("i915_panel_timings", S_IRUGO, root, -+ connector, &i915_panel_fops); -+ debugfs_create_file("i915_psr_sink_status", S_IRUGO, root, -+ connector, &i915_psr_sink_status_fops); -+ } -+ -+ if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort || -+ connector->connector_type == DRM_MODE_CONNECTOR_HDMIA || -+ connector->connector_type == DRM_MODE_CONNECTOR_HDMIB) { -+ debugfs_create_file("i915_hdcp_sink_capability", S_IRUGO, root, -+ connector, &i915_hdcp_sink_capability_fops); -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 10 && -+ (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort || -+ connector->connector_type == DRM_MODE_CONNECTOR_eDP)) -+ debugfs_create_file("i915_dsc_fec_support", S_IRUGO, root, -+ connector, &i915_dsc_fec_support_fops); -+ -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_drv.c b/drivers/gpu/drm/i915_legacy/i915_drv.c -new file mode 100644 -index 000000000000..d485d49c473b ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_drv.c -@@ -0,0 +1,3195 @@ -+/* i915_drv.c -- i830,i845,i855,i865,i915 driver -*- linux-c -*- -+ */ -+/* -+ * -+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas. -+ * All Rights Reserved. -+ * -+ * 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, sub license, 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 (including the -+ * next paragraph) 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 NON-INFRINGEMENT. -+ * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS 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. -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "i915_drv.h" -+#include "i915_pmu.h" -+#include "i915_query.h" -+#include "i915_reset.h" -+#include "i915_trace.h" -+#include "i915_vgpu.h" -+#include "intel_audio.h" -+#include "intel_cdclk.h" -+#include "intel_csr.h" -+#include "intel_dp.h" -+#include "intel_drv.h" -+#include "intel_fbdev.h" -+#include "intel_pm.h" -+#include "intel_sprite.h" -+#include "intel_uc.h" -+#include "intel_workarounds.h" -+ -+static struct drm_driver driver; -+ -+#if IS_ENABLED(CONFIG_DRM_I915_DEBUG) -+static unsigned int i915_load_fail_count; -+ -+bool __i915_inject_load_failure(const char *func, int line) -+{ -+ if (i915_load_fail_count >= i915_modparams.inject_load_failure) -+ return false; -+ -+ if (++i915_load_fail_count == i915_modparams.inject_load_failure) { -+ DRM_INFO("Injecting failure at checkpoint %u [%s:%d]\n", -+ i915_modparams.inject_load_failure, func, line); -+ i915_modparams.inject_load_failure = 0; -+ return true; -+ } -+ -+ return false; -+} -+ -+bool i915_error_injected(void) -+{ -+ return i915_load_fail_count && !i915_modparams.inject_load_failure; -+} -+ -+#endif -+ -+#define FDO_BUG_URL "https://bugs.freedesktop.org/enter_bug.cgi?product=DRI" -+#define FDO_BUG_MSG "Please file a bug at " FDO_BUG_URL " against DRM/Intel " \ -+ "providing the dmesg log by booting with drm.debug=0xf" -+ -+void -+__i915_printk(struct drm_i915_private *dev_priv, const char *level, -+ const char *fmt, ...) -+{ -+ static bool shown_bug_once; -+ struct device *kdev = dev_priv->drm.dev; -+ bool is_error = level[1] <= KERN_ERR[1]; -+ bool is_debug = level[1] == KERN_DEBUG[1]; -+ struct va_format vaf; -+ va_list args; -+ -+ if (is_debug && !(drm_debug & DRM_UT_DRIVER)) -+ return; -+ -+ va_start(args, fmt); -+ -+ vaf.fmt = fmt; -+ vaf.va = &args; -+ -+ if (is_error) -+ dev_printk(level, kdev, "%pV", &vaf); -+ else -+ dev_printk(level, kdev, "[" DRM_NAME ":%ps] %pV", -+ __builtin_return_address(0), &vaf); -+ -+ va_end(args); -+ -+ if (is_error && !shown_bug_once) { -+ /* -+ * Ask the user to file a bug report for the error, except -+ * if they may have caused the bug by fiddling with unsafe -+ * module parameters. -+ */ -+ if (!test_taint(TAINT_USER)) -+ dev_notice(kdev, "%s", FDO_BUG_MSG); -+ shown_bug_once = true; -+ } -+} -+ -+/* Map PCH device id to PCH type, or PCH_NONE if unknown. */ -+static enum intel_pch -+intel_pch_type(const struct drm_i915_private *dev_priv, unsigned short id) -+{ -+ switch (id) { -+ case INTEL_PCH_IBX_DEVICE_ID_TYPE: -+ DRM_DEBUG_KMS("Found Ibex Peak PCH\n"); -+ WARN_ON(!IS_GEN(dev_priv, 5)); -+ return PCH_IBX; -+ case INTEL_PCH_CPT_DEVICE_ID_TYPE: -+ DRM_DEBUG_KMS("Found CougarPoint PCH\n"); -+ WARN_ON(!IS_GEN(dev_priv, 6) && !IS_IVYBRIDGE(dev_priv)); -+ return PCH_CPT; -+ case INTEL_PCH_PPT_DEVICE_ID_TYPE: -+ DRM_DEBUG_KMS("Found PantherPoint PCH\n"); -+ WARN_ON(!IS_GEN(dev_priv, 6) && !IS_IVYBRIDGE(dev_priv)); -+ /* PantherPoint is CPT compatible */ -+ return PCH_CPT; -+ case INTEL_PCH_LPT_DEVICE_ID_TYPE: -+ DRM_DEBUG_KMS("Found LynxPoint PCH\n"); -+ WARN_ON(!IS_HASWELL(dev_priv) && !IS_BROADWELL(dev_priv)); -+ WARN_ON(IS_HSW_ULT(dev_priv) || IS_BDW_ULT(dev_priv)); -+ return PCH_LPT; -+ case INTEL_PCH_LPT_LP_DEVICE_ID_TYPE: -+ DRM_DEBUG_KMS("Found LynxPoint LP PCH\n"); -+ WARN_ON(!IS_HASWELL(dev_priv) && !IS_BROADWELL(dev_priv)); -+ WARN_ON(!IS_HSW_ULT(dev_priv) && !IS_BDW_ULT(dev_priv)); -+ return PCH_LPT; -+ case INTEL_PCH_WPT_DEVICE_ID_TYPE: -+ DRM_DEBUG_KMS("Found WildcatPoint PCH\n"); -+ WARN_ON(!IS_HASWELL(dev_priv) && !IS_BROADWELL(dev_priv)); -+ WARN_ON(IS_HSW_ULT(dev_priv) || IS_BDW_ULT(dev_priv)); -+ /* WildcatPoint is LPT compatible */ -+ return PCH_LPT; -+ case INTEL_PCH_WPT_LP_DEVICE_ID_TYPE: -+ DRM_DEBUG_KMS("Found WildcatPoint LP PCH\n"); -+ WARN_ON(!IS_HASWELL(dev_priv) && !IS_BROADWELL(dev_priv)); -+ WARN_ON(!IS_HSW_ULT(dev_priv) && !IS_BDW_ULT(dev_priv)); -+ /* WildcatPoint is LPT compatible */ -+ return PCH_LPT; -+ case INTEL_PCH_SPT_DEVICE_ID_TYPE: -+ DRM_DEBUG_KMS("Found SunrisePoint PCH\n"); -+ WARN_ON(!IS_SKYLAKE(dev_priv) && !IS_KABYLAKE(dev_priv)); -+ return PCH_SPT; -+ case INTEL_PCH_SPT_LP_DEVICE_ID_TYPE: -+ DRM_DEBUG_KMS("Found SunrisePoint LP PCH\n"); -+ WARN_ON(!IS_SKYLAKE(dev_priv) && !IS_KABYLAKE(dev_priv)); -+ return PCH_SPT; -+ case INTEL_PCH_KBP_DEVICE_ID_TYPE: -+ DRM_DEBUG_KMS("Found Kaby Lake PCH (KBP)\n"); -+ WARN_ON(!IS_SKYLAKE(dev_priv) && !IS_KABYLAKE(dev_priv) && -+ !IS_COFFEELAKE(dev_priv)); -+ return PCH_KBP; -+ case INTEL_PCH_CNP_DEVICE_ID_TYPE: -+ DRM_DEBUG_KMS("Found Cannon Lake PCH (CNP)\n"); -+ WARN_ON(!IS_CANNONLAKE(dev_priv) && !IS_COFFEELAKE(dev_priv)); -+ return PCH_CNP; -+ case INTEL_PCH_CNP_LP_DEVICE_ID_TYPE: -+ DRM_DEBUG_KMS("Found Cannon Lake LP PCH (CNP-LP)\n"); -+ WARN_ON(!IS_CANNONLAKE(dev_priv) && !IS_COFFEELAKE(dev_priv)); -+ return PCH_CNP; -+ case INTEL_PCH_CMP_DEVICE_ID_TYPE: -+ DRM_DEBUG_KMS("Found Comet Lake PCH (CMP)\n"); -+ WARN_ON(!IS_COFFEELAKE(dev_priv)); -+ /* CometPoint is CNP Compatible */ -+ return PCH_CNP; -+ case INTEL_PCH_ICP_DEVICE_ID_TYPE: -+ DRM_DEBUG_KMS("Found Ice Lake PCH\n"); -+ WARN_ON(!IS_ICELAKE(dev_priv)); -+ return PCH_ICP; -+ default: -+ return PCH_NONE; -+ } -+} -+ -+static bool intel_is_virt_pch(unsigned short id, -+ unsigned short svendor, unsigned short sdevice) -+{ -+ return (id == INTEL_PCH_P2X_DEVICE_ID_TYPE || -+ id == INTEL_PCH_P3X_DEVICE_ID_TYPE || -+ (id == INTEL_PCH_QEMU_DEVICE_ID_TYPE && -+ svendor == PCI_SUBVENDOR_ID_REDHAT_QUMRANET && -+ sdevice == PCI_SUBDEVICE_ID_QEMU)); -+} -+ -+static unsigned short -+intel_virt_detect_pch(const struct drm_i915_private *dev_priv) -+{ -+ unsigned short id = 0; -+ -+ /* -+ * In a virtualized passthrough environment we can be in a -+ * setup where the ISA bridge is not able to be passed through. -+ * In this case, a south bridge can be emulated and we have to -+ * make an educated guess as to which PCH is really there. -+ */ -+ -+ if (IS_ICELAKE(dev_priv)) -+ id = INTEL_PCH_ICP_DEVICE_ID_TYPE; -+ else if (IS_CANNONLAKE(dev_priv) || IS_COFFEELAKE(dev_priv)) -+ id = INTEL_PCH_CNP_DEVICE_ID_TYPE; -+ else if (IS_KABYLAKE(dev_priv) || IS_SKYLAKE(dev_priv)) -+ id = INTEL_PCH_SPT_DEVICE_ID_TYPE; -+ else if (IS_HSW_ULT(dev_priv) || IS_BDW_ULT(dev_priv)) -+ id = INTEL_PCH_LPT_LP_DEVICE_ID_TYPE; -+ else if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) -+ id = INTEL_PCH_LPT_DEVICE_ID_TYPE; -+ else if (IS_GEN(dev_priv, 6) || IS_IVYBRIDGE(dev_priv)) -+ id = INTEL_PCH_CPT_DEVICE_ID_TYPE; -+ else if (IS_GEN(dev_priv, 5)) -+ id = INTEL_PCH_IBX_DEVICE_ID_TYPE; -+ -+ if (id) -+ DRM_DEBUG_KMS("Assuming PCH ID %04x\n", id); -+ else -+ DRM_DEBUG_KMS("Assuming no PCH\n"); -+ -+ return id; -+} -+ -+static void intel_detect_pch(struct drm_i915_private *dev_priv) -+{ -+ struct pci_dev *pch = NULL; -+ -+ /* -+ * The reason to probe ISA bridge instead of Dev31:Fun0 is to -+ * make graphics device passthrough work easy for VMM, that only -+ * need to expose ISA bridge to let driver know the real hardware -+ * underneath. This is a requirement from virtualization team. -+ * -+ * In some virtualized environments (e.g. XEN), there is irrelevant -+ * ISA bridge in the system. To work reliably, we should scan trhough -+ * all the ISA bridge devices and check for the first match, instead -+ * of only checking the first one. -+ */ -+ while ((pch = pci_get_class(PCI_CLASS_BRIDGE_ISA << 8, pch))) { -+ unsigned short id; -+ enum intel_pch pch_type; -+ -+ if (pch->vendor != PCI_VENDOR_ID_INTEL) -+ continue; -+ -+ id = pch->device & INTEL_PCH_DEVICE_ID_MASK; -+ -+ pch_type = intel_pch_type(dev_priv, id); -+ if (pch_type != PCH_NONE) { -+ dev_priv->pch_type = pch_type; -+ dev_priv->pch_id = id; -+ break; -+ } else if (intel_is_virt_pch(id, pch->subsystem_vendor, -+ pch->subsystem_device)) { -+ id = intel_virt_detect_pch(dev_priv); -+ pch_type = intel_pch_type(dev_priv, id); -+ -+ /* Sanity check virtual PCH id */ -+ if (WARN_ON(id && pch_type == PCH_NONE)) -+ id = 0; -+ -+ dev_priv->pch_type = pch_type; -+ dev_priv->pch_id = id; -+ break; -+ } -+ } -+ -+ /* -+ * Use PCH_NOP (PCH but no South Display) for PCH platforms without -+ * display. -+ */ -+ if (pch && !HAS_DISPLAY(dev_priv)) { -+ DRM_DEBUG_KMS("Display disabled, reverting to NOP PCH\n"); -+ dev_priv->pch_type = PCH_NOP; -+ dev_priv->pch_id = 0; -+ } -+ -+ if (!pch) -+ DRM_DEBUG_KMS("No PCH found.\n"); -+ -+ pci_dev_put(pch); -+} -+ -+static int i915_getparam_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ drm_i915_getparam_t *param = data; -+ int value; -+ -+ switch (param->param) { -+ case I915_PARAM_IRQ_ACTIVE: -+ case I915_PARAM_ALLOW_BATCHBUFFER: -+ case I915_PARAM_LAST_DISPATCH: -+ case I915_PARAM_HAS_EXEC_CONSTANTS: -+ /* Reject all old ums/dri params. */ -+ return -ENODEV; -+ case I915_PARAM_CHIPSET_ID: -+ value = pdev->device; -+ break; -+ case I915_PARAM_REVISION: -+ value = pdev->revision; -+ break; -+ case I915_PARAM_NUM_FENCES_AVAIL: -+ value = dev_priv->num_fence_regs; -+ break; -+ case I915_PARAM_HAS_OVERLAY: -+ value = dev_priv->overlay ? 1 : 0; -+ break; -+ case I915_PARAM_HAS_BSD: -+ value = !!dev_priv->engine[VCS0]; -+ break; -+ case I915_PARAM_HAS_BLT: -+ value = !!dev_priv->engine[BCS0]; -+ break; -+ case I915_PARAM_HAS_VEBOX: -+ value = !!dev_priv->engine[VECS0]; -+ break; -+ case I915_PARAM_HAS_BSD2: -+ value = !!dev_priv->engine[VCS1]; -+ break; -+ case I915_PARAM_HAS_LLC: -+ value = HAS_LLC(dev_priv); -+ break; -+ case I915_PARAM_HAS_WT: -+ value = HAS_WT(dev_priv); -+ break; -+ case I915_PARAM_HAS_ALIASING_PPGTT: -+ value = INTEL_PPGTT(dev_priv); -+ break; -+ case I915_PARAM_HAS_SEMAPHORES: -+ value = !!(dev_priv->caps.scheduler & I915_SCHEDULER_CAP_SEMAPHORES); -+ break; -+ case I915_PARAM_HAS_SECURE_BATCHES: -+ value = capable(CAP_SYS_ADMIN); -+ break; -+ case I915_PARAM_CMD_PARSER_VERSION: -+ value = i915_cmd_parser_get_version(dev_priv); -+ break; -+ case I915_PARAM_SUBSLICE_TOTAL: -+ value = sseu_subslice_total(&RUNTIME_INFO(dev_priv)->sseu); -+ if (!value) -+ return -ENODEV; -+ break; -+ case I915_PARAM_EU_TOTAL: -+ value = RUNTIME_INFO(dev_priv)->sseu.eu_total; -+ if (!value) -+ return -ENODEV; -+ break; -+ case I915_PARAM_HAS_GPU_RESET: -+ value = i915_modparams.enable_hangcheck && -+ intel_has_gpu_reset(dev_priv); -+ if (value && intel_has_reset_engine(dev_priv)) -+ value = 2; -+ break; -+ case I915_PARAM_HAS_RESOURCE_STREAMER: -+ value = 0; -+ break; -+ case I915_PARAM_HAS_POOLED_EU: -+ value = HAS_POOLED_EU(dev_priv); -+ break; -+ case I915_PARAM_MIN_EU_IN_POOL: -+ value = RUNTIME_INFO(dev_priv)->sseu.min_eu_in_pool; -+ break; -+ case I915_PARAM_HUC_STATUS: -+ value = intel_huc_check_status(&dev_priv->huc); -+ if (value < 0) -+ return value; -+ break; -+ case I915_PARAM_MMAP_GTT_VERSION: -+ /* Though we've started our numbering from 1, and so class all -+ * earlier versions as 0, in effect their value is undefined as -+ * the ioctl will report EINVAL for the unknown param! -+ */ -+ value = i915_gem_mmap_gtt_version(); -+ break; -+ case I915_PARAM_HAS_SCHEDULER: -+ value = dev_priv->caps.scheduler; -+ break; -+ -+ case I915_PARAM_MMAP_VERSION: -+ /* Remember to bump this if the version changes! */ -+ case I915_PARAM_HAS_GEM: -+ case I915_PARAM_HAS_PAGEFLIPPING: -+ case I915_PARAM_HAS_EXECBUF2: /* depends on GEM */ -+ case I915_PARAM_HAS_RELAXED_FENCING: -+ case I915_PARAM_HAS_COHERENT_RINGS: -+ case I915_PARAM_HAS_RELAXED_DELTA: -+ case I915_PARAM_HAS_GEN7_SOL_RESET: -+ case I915_PARAM_HAS_WAIT_TIMEOUT: -+ case I915_PARAM_HAS_PRIME_VMAP_FLUSH: -+ case I915_PARAM_HAS_PINNED_BATCHES: -+ case I915_PARAM_HAS_EXEC_NO_RELOC: -+ case I915_PARAM_HAS_EXEC_HANDLE_LUT: -+ case I915_PARAM_HAS_COHERENT_PHYS_GTT: -+ case I915_PARAM_HAS_EXEC_SOFTPIN: -+ case I915_PARAM_HAS_EXEC_ASYNC: -+ case I915_PARAM_HAS_EXEC_FENCE: -+ case I915_PARAM_HAS_EXEC_CAPTURE: -+ case I915_PARAM_HAS_EXEC_BATCH_FIRST: -+ case I915_PARAM_HAS_EXEC_FENCE_ARRAY: -+ /* For the time being all of these are always true; -+ * if some supported hardware does not have one of these -+ * features this value needs to be provided from -+ * INTEL_INFO(), a feature macro, or similar. -+ */ -+ value = 1; -+ break; -+ case I915_PARAM_HAS_CONTEXT_ISOLATION: -+ value = intel_engines_has_context_isolation(dev_priv); -+ break; -+ case I915_PARAM_SLICE_MASK: -+ value = RUNTIME_INFO(dev_priv)->sseu.slice_mask; -+ if (!value) -+ return -ENODEV; -+ break; -+ case I915_PARAM_SUBSLICE_MASK: -+ value = RUNTIME_INFO(dev_priv)->sseu.subslice_mask[0]; -+ if (!value) -+ return -ENODEV; -+ break; -+ case I915_PARAM_CS_TIMESTAMP_FREQUENCY: -+ value = 1000 * RUNTIME_INFO(dev_priv)->cs_timestamp_frequency_khz; -+ break; -+ case I915_PARAM_MMAP_GTT_COHERENT: -+ value = INTEL_INFO(dev_priv)->has_coherent_ggtt; -+ break; -+ default: -+ DRM_DEBUG("Unknown parameter %d\n", param->param); -+ return -EINVAL; -+ } -+ -+ if (put_user(value, param->value)) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static int i915_get_bridge_dev(struct drm_i915_private *dev_priv) -+{ -+ int domain = pci_domain_nr(dev_priv->drm.pdev->bus); -+ -+ dev_priv->bridge_dev = -+ pci_get_domain_bus_and_slot(domain, 0, PCI_DEVFN(0, 0)); -+ if (!dev_priv->bridge_dev) { -+ DRM_ERROR("bridge device not found\n"); -+ return -1; -+ } -+ return 0; -+} -+ -+/* Allocate space for the MCH regs if needed, return nonzero on error */ -+static int -+intel_alloc_mchbar_resource(struct drm_i915_private *dev_priv) -+{ -+ int reg = INTEL_GEN(dev_priv) >= 4 ? MCHBAR_I965 : MCHBAR_I915; -+ u32 temp_lo, temp_hi = 0; -+ u64 mchbar_addr; -+ int ret; -+ -+ if (INTEL_GEN(dev_priv) >= 4) -+ pci_read_config_dword(dev_priv->bridge_dev, reg + 4, &temp_hi); -+ pci_read_config_dword(dev_priv->bridge_dev, reg, &temp_lo); -+ mchbar_addr = ((u64)temp_hi << 32) | temp_lo; -+ -+ /* If ACPI doesn't have it, assume we need to allocate it ourselves */ -+#ifdef CONFIG_PNP -+ if (mchbar_addr && -+ pnp_range_reserved(mchbar_addr, mchbar_addr + MCHBAR_SIZE)) -+ return 0; -+#endif -+ -+ /* Get some space for it */ -+ dev_priv->mch_res.name = "i915 MCHBAR"; -+ dev_priv->mch_res.flags = IORESOURCE_MEM; -+ ret = pci_bus_alloc_resource(dev_priv->bridge_dev->bus, -+ &dev_priv->mch_res, -+ MCHBAR_SIZE, MCHBAR_SIZE, -+ PCIBIOS_MIN_MEM, -+ 0, pcibios_align_resource, -+ dev_priv->bridge_dev); -+ if (ret) { -+ DRM_DEBUG_DRIVER("failed bus alloc: %d\n", ret); -+ dev_priv->mch_res.start = 0; -+ return ret; -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 4) -+ pci_write_config_dword(dev_priv->bridge_dev, reg + 4, -+ upper_32_bits(dev_priv->mch_res.start)); -+ -+ pci_write_config_dword(dev_priv->bridge_dev, reg, -+ lower_32_bits(dev_priv->mch_res.start)); -+ return 0; -+} -+ -+/* Setup MCHBAR if possible, return true if we should disable it again */ -+static void -+intel_setup_mchbar(struct drm_i915_private *dev_priv) -+{ -+ int mchbar_reg = INTEL_GEN(dev_priv) >= 4 ? MCHBAR_I965 : MCHBAR_I915; -+ u32 temp; -+ bool enabled; -+ -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ return; -+ -+ dev_priv->mchbar_need_disable = false; -+ -+ if (IS_I915G(dev_priv) || IS_I915GM(dev_priv)) { -+ pci_read_config_dword(dev_priv->bridge_dev, DEVEN, &temp); -+ enabled = !!(temp & DEVEN_MCHBAR_EN); -+ } else { -+ pci_read_config_dword(dev_priv->bridge_dev, mchbar_reg, &temp); -+ enabled = temp & 1; -+ } -+ -+ /* If it's already enabled, don't have to do anything */ -+ if (enabled) -+ return; -+ -+ if (intel_alloc_mchbar_resource(dev_priv)) -+ return; -+ -+ dev_priv->mchbar_need_disable = true; -+ -+ /* Space is allocated or reserved, so enable it. */ -+ if (IS_I915G(dev_priv) || IS_I915GM(dev_priv)) { -+ pci_write_config_dword(dev_priv->bridge_dev, DEVEN, -+ temp | DEVEN_MCHBAR_EN); -+ } else { -+ pci_read_config_dword(dev_priv->bridge_dev, mchbar_reg, &temp); -+ pci_write_config_dword(dev_priv->bridge_dev, mchbar_reg, temp | 1); -+ } -+} -+ -+static void -+intel_teardown_mchbar(struct drm_i915_private *dev_priv) -+{ -+ int mchbar_reg = INTEL_GEN(dev_priv) >= 4 ? MCHBAR_I965 : MCHBAR_I915; -+ -+ if (dev_priv->mchbar_need_disable) { -+ if (IS_I915G(dev_priv) || IS_I915GM(dev_priv)) { -+ u32 deven_val; -+ -+ pci_read_config_dword(dev_priv->bridge_dev, DEVEN, -+ &deven_val); -+ deven_val &= ~DEVEN_MCHBAR_EN; -+ pci_write_config_dword(dev_priv->bridge_dev, DEVEN, -+ deven_val); -+ } else { -+ u32 mchbar_val; -+ -+ pci_read_config_dword(dev_priv->bridge_dev, mchbar_reg, -+ &mchbar_val); -+ mchbar_val &= ~1; -+ pci_write_config_dword(dev_priv->bridge_dev, mchbar_reg, -+ mchbar_val); -+ } -+ } -+ -+ if (dev_priv->mch_res.start) -+ release_resource(&dev_priv->mch_res); -+} -+ -+/* true = enable decode, false = disable decoder */ -+static unsigned int i915_vga_set_decode(void *cookie, bool state) -+{ -+ struct drm_i915_private *dev_priv = cookie; -+ -+ intel_modeset_vga_set_state(dev_priv, state); -+ if (state) -+ return VGA_RSRC_LEGACY_IO | VGA_RSRC_LEGACY_MEM | -+ VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM; -+ else -+ return VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM; -+} -+ -+static int i915_resume_switcheroo(struct drm_device *dev); -+static int i915_suspend_switcheroo(struct drm_device *dev, pm_message_t state); -+ -+static void i915_switcheroo_set_state(struct pci_dev *pdev, enum vga_switcheroo_state state) -+{ -+ struct drm_device *dev = pci_get_drvdata(pdev); -+ pm_message_t pmm = { .event = PM_EVENT_SUSPEND }; -+ -+ if (state == VGA_SWITCHEROO_ON) { -+ pr_info("switched on\n"); -+ dev->switch_power_state = DRM_SWITCH_POWER_CHANGING; -+ /* i915 resume handler doesn't set to D0 */ -+ pci_set_power_state(pdev, PCI_D0); -+ i915_resume_switcheroo(dev); -+ dev->switch_power_state = DRM_SWITCH_POWER_ON; -+ } else { -+ pr_info("switched off\n"); -+ dev->switch_power_state = DRM_SWITCH_POWER_CHANGING; -+ i915_suspend_switcheroo(dev, pmm); -+ dev->switch_power_state = DRM_SWITCH_POWER_OFF; -+ } -+} -+ -+static bool i915_switcheroo_can_switch(struct pci_dev *pdev) -+{ -+ struct drm_device *dev = pci_get_drvdata(pdev); -+ -+ /* -+ * FIXME: open_count is protected by drm_global_mutex but that would lead to -+ * locking inversion with the driver load path. And the access here is -+ * completely racy anyway. So don't bother with locking for now. -+ */ -+ return dev->open_count == 0; -+} -+ -+static const struct vga_switcheroo_client_ops i915_switcheroo_ops = { -+ .set_gpu_state = i915_switcheroo_set_state, -+ .reprobe = NULL, -+ .can_switch = i915_switcheroo_can_switch, -+}; -+ -+static int i915_load_modeset_init(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ int ret; -+ -+ if (i915_inject_load_failure()) -+ return -ENODEV; -+ -+ if (HAS_DISPLAY(dev_priv)) { -+ ret = drm_vblank_init(&dev_priv->drm, -+ INTEL_INFO(dev_priv)->num_pipes); -+ if (ret) -+ goto out; -+ } -+ -+ intel_bios_init(dev_priv); -+ -+ /* If we have > 1 VGA cards, then we need to arbitrate access -+ * to the common VGA resources. -+ * -+ * If we are a secondary display controller (!PCI_DISPLAY_CLASS_VGA), -+ * then we do not take part in VGA arbitration and the -+ * vga_client_register() fails with -ENODEV. -+ */ -+ ret = vga_client_register(pdev, dev_priv, NULL, i915_vga_set_decode); -+ if (ret && ret != -ENODEV) -+ goto out; -+ -+ intel_register_dsm_handler(); -+ -+ ret = vga_switcheroo_register_client(pdev, &i915_switcheroo_ops, false); -+ if (ret) -+ goto cleanup_vga_client; -+ -+ /* must happen before intel_power_domains_init_hw() on VLV/CHV */ -+ intel_update_rawclk(dev_priv); -+ -+ intel_power_domains_init_hw(dev_priv, false); -+ -+ intel_csr_ucode_init(dev_priv); -+ -+ ret = intel_irq_install(dev_priv); -+ if (ret) -+ goto cleanup_csr; -+ -+ intel_setup_gmbus(dev_priv); -+ -+ /* Important: The output setup functions called by modeset_init need -+ * working irqs for e.g. gmbus and dp aux transfers. */ -+ ret = intel_modeset_init(dev); -+ if (ret) -+ goto cleanup_irq; -+ -+ ret = i915_gem_init(dev_priv); -+ if (ret) -+ goto cleanup_modeset; -+ -+ intel_overlay_setup(dev_priv); -+ -+ if (!HAS_DISPLAY(dev_priv)) -+ return 0; -+ -+ ret = intel_fbdev_init(dev); -+ if (ret) -+ goto cleanup_gem; -+ -+ /* Only enable hotplug handling once the fbdev is fully set up. */ -+ intel_hpd_init(dev_priv); -+ -+ intel_init_ipc(dev_priv); -+ -+ return 0; -+ -+cleanup_gem: -+ i915_gem_suspend(dev_priv); -+ i915_gem_fini(dev_priv); -+cleanup_modeset: -+ intel_modeset_cleanup(dev); -+cleanup_irq: -+ drm_irq_uninstall(dev); -+ intel_teardown_gmbus(dev_priv); -+cleanup_csr: -+ intel_csr_ucode_fini(dev_priv); -+ intel_power_domains_fini_hw(dev_priv); -+ vga_switcheroo_unregister_client(pdev); -+cleanup_vga_client: -+ vga_client_register(pdev, NULL, NULL, NULL); -+out: -+ return ret; -+} -+ -+static int i915_kick_out_firmware_fb(struct drm_i915_private *dev_priv) -+{ -+ struct apertures_struct *ap; -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ struct i915_ggtt *ggtt = &dev_priv->ggtt; -+ bool primary; -+ int ret; -+ -+ ap = alloc_apertures(1); -+ if (!ap) -+ return -ENOMEM; -+ -+ ap->ranges[0].base = ggtt->gmadr.start; -+ ap->ranges[0].size = ggtt->mappable_end; -+ -+ primary = -+ pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW; -+ -+ ret = drm_fb_helper_remove_conflicting_framebuffers(ap, "inteldrmfb", primary); -+ -+ kfree(ap); -+ -+ return ret; -+} -+ -+static void intel_init_dpio(struct drm_i915_private *dev_priv) -+{ -+ /* -+ * IOSF_PORT_DPIO is used for VLV x2 PHY (DP/HDMI B and C), -+ * CHV x1 PHY (DP/HDMI D) -+ * IOSF_PORT_DPIO_2 is used for CHV x2 PHY (DP/HDMI B and C) -+ */ -+ if (IS_CHERRYVIEW(dev_priv)) { -+ DPIO_PHY_IOSF_PORT(DPIO_PHY0) = IOSF_PORT_DPIO_2; -+ DPIO_PHY_IOSF_PORT(DPIO_PHY1) = IOSF_PORT_DPIO; -+ } else if (IS_VALLEYVIEW(dev_priv)) { -+ DPIO_PHY_IOSF_PORT(DPIO_PHY0) = IOSF_PORT_DPIO; -+ } -+} -+ -+static int i915_workqueues_init(struct drm_i915_private *dev_priv) -+{ -+ /* -+ * The i915 workqueue is primarily used for batched retirement of -+ * requests (and thus managing bo) once the task has been completed -+ * by the GPU. i915_retire_requests() is called directly when we -+ * need high-priority retirement, such as waiting for an explicit -+ * bo. -+ * -+ * It is also used for periodic low-priority events, such as -+ * idle-timers and recording error state. -+ * -+ * All tasks on the workqueue are expected to acquire the dev mutex -+ * so there is no point in running more than one instance of the -+ * workqueue at any time. Use an ordered one. -+ */ -+ dev_priv->wq = alloc_ordered_workqueue("i915", 0); -+ if (dev_priv->wq == NULL) -+ goto out_err; -+ -+ dev_priv->hotplug.dp_wq = alloc_ordered_workqueue("i915-dp", 0); -+ if (dev_priv->hotplug.dp_wq == NULL) -+ goto out_free_wq; -+ -+ return 0; -+ -+out_free_wq: -+ destroy_workqueue(dev_priv->wq); -+out_err: -+ DRM_ERROR("Failed to allocate workqueues.\n"); -+ -+ return -ENOMEM; -+} -+ -+static void i915_engines_cleanup(struct drm_i915_private *i915) -+{ -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ -+ for_each_engine(engine, i915, id) -+ kfree(engine); -+} -+ -+static void i915_workqueues_cleanup(struct drm_i915_private *dev_priv) -+{ -+ destroy_workqueue(dev_priv->hotplug.dp_wq); -+ destroy_workqueue(dev_priv->wq); -+} -+ -+/* -+ * We don't keep the workarounds for pre-production hardware, so we expect our -+ * driver to fail on these machines in one way or another. A little warning on -+ * dmesg may help both the user and the bug triagers. -+ * -+ * Our policy for removing pre-production workarounds is to keep the -+ * current gen workarounds as a guide to the bring-up of the next gen -+ * (workarounds have a habit of persisting!). Anything older than that -+ * should be removed along with the complications they introduce. -+ */ -+static void intel_detect_preproduction_hw(struct drm_i915_private *dev_priv) -+{ -+ bool pre = false; -+ -+ pre |= IS_HSW_EARLY_SDV(dev_priv); -+ pre |= IS_SKL_REVID(dev_priv, 0, SKL_REVID_F0); -+ pre |= IS_BXT_REVID(dev_priv, 0, BXT_REVID_B_LAST); -+ pre |= IS_KBL_REVID(dev_priv, 0, KBL_REVID_A0); -+ -+ if (pre) { -+ DRM_ERROR("This is a pre-production stepping. " -+ "It may not be fully functional.\n"); -+ add_taint(TAINT_MACHINE_CHECK, LOCKDEP_STILL_OK); -+ } -+} -+ -+/** -+ * i915_driver_init_early - setup state not requiring device access -+ * @dev_priv: device private -+ * -+ * Initialize everything that is a "SW-only" state, that is state not -+ * requiring accessing the device or exposing the driver via kernel internal -+ * or userspace interfaces. Example steps belonging here: lock initialization, -+ * system memory allocation, setting up device specific attributes and -+ * function hooks not requiring accessing the device. -+ */ -+static int i915_driver_init_early(struct drm_i915_private *dev_priv) -+{ -+ int ret = 0; -+ -+ if (i915_inject_load_failure()) -+ return -ENODEV; -+ -+ intel_device_info_subplatform_init(dev_priv); -+ -+ intel_uncore_init_early(&dev_priv->uncore); -+ -+ spin_lock_init(&dev_priv->irq_lock); -+ spin_lock_init(&dev_priv->gpu_error.lock); -+ mutex_init(&dev_priv->backlight_lock); -+ -+ mutex_init(&dev_priv->sb_lock); -+ mutex_init(&dev_priv->av_mutex); -+ mutex_init(&dev_priv->wm.wm_mutex); -+ mutex_init(&dev_priv->pps_mutex); -+ mutex_init(&dev_priv->hdcp_comp_mutex); -+ -+ i915_memcpy_init_early(dev_priv); -+ intel_runtime_pm_init_early(dev_priv); -+ -+ ret = i915_workqueues_init(dev_priv); -+ if (ret < 0) -+ goto err_engines; -+ -+ ret = i915_gem_init_early(dev_priv); -+ if (ret < 0) -+ goto err_workqueues; -+ -+ /* This must be called before any calls to HAS_PCH_* */ -+ intel_detect_pch(dev_priv); -+ -+ intel_wopcm_init_early(&dev_priv->wopcm); -+ intel_uc_init_early(dev_priv); -+ intel_pm_setup(dev_priv); -+ intel_init_dpio(dev_priv); -+ ret = intel_power_domains_init(dev_priv); -+ if (ret < 0) -+ goto err_uc; -+ intel_irq_init(dev_priv); -+ intel_hangcheck_init(dev_priv); -+ intel_init_display_hooks(dev_priv); -+ intel_init_clock_gating_hooks(dev_priv); -+ intel_init_audio_hooks(dev_priv); -+ intel_display_crc_init(dev_priv); -+ -+ intel_detect_preproduction_hw(dev_priv); -+ -+ return 0; -+ -+err_uc: -+ intel_uc_cleanup_early(dev_priv); -+ i915_gem_cleanup_early(dev_priv); -+err_workqueues: -+ i915_workqueues_cleanup(dev_priv); -+err_engines: -+ i915_engines_cleanup(dev_priv); -+ return ret; -+} -+ -+/** -+ * i915_driver_cleanup_early - cleanup the setup done in i915_driver_init_early() -+ * @dev_priv: device private -+ */ -+static void i915_driver_cleanup_early(struct drm_i915_private *dev_priv) -+{ -+ intel_irq_fini(dev_priv); -+ intel_power_domains_cleanup(dev_priv); -+ intel_uc_cleanup_early(dev_priv); -+ i915_gem_cleanup_early(dev_priv); -+ i915_workqueues_cleanup(dev_priv); -+ i915_engines_cleanup(dev_priv); -+} -+ -+/** -+ * i915_driver_init_mmio - setup device MMIO -+ * @dev_priv: device private -+ * -+ * Setup minimal device state necessary for MMIO accesses later in the -+ * initialization sequence. The setup here should avoid any other device-wide -+ * side effects or exposing the driver via kernel internal or user space -+ * interfaces. -+ */ -+static int i915_driver_init_mmio(struct drm_i915_private *dev_priv) -+{ -+ int ret; -+ -+ if (i915_inject_load_failure()) -+ return -ENODEV; -+ -+ if (i915_get_bridge_dev(dev_priv)) -+ return -EIO; -+ -+ ret = intel_uncore_init_mmio(&dev_priv->uncore); -+ if (ret < 0) -+ goto err_bridge; -+ -+ /* Try to make sure MCHBAR is enabled before poking at it */ -+ intel_setup_mchbar(dev_priv); -+ -+ intel_device_info_init_mmio(dev_priv); -+ -+ intel_uncore_prune_mmio_domains(&dev_priv->uncore); -+ -+ intel_uc_init_mmio(dev_priv); -+ -+ ret = intel_engines_init_mmio(dev_priv); -+ if (ret) -+ goto err_uncore; -+ -+ i915_gem_init_mmio(dev_priv); -+ -+ return 0; -+ -+err_uncore: -+ intel_teardown_mchbar(dev_priv); -+ intel_uncore_fini_mmio(&dev_priv->uncore); -+err_bridge: -+ pci_dev_put(dev_priv->bridge_dev); -+ -+ return ret; -+} -+ -+/** -+ * i915_driver_cleanup_mmio - cleanup the setup done in i915_driver_init_mmio() -+ * @dev_priv: device private -+ */ -+static void i915_driver_cleanup_mmio(struct drm_i915_private *dev_priv) -+{ -+ intel_teardown_mchbar(dev_priv); -+ intel_uncore_fini_mmio(&dev_priv->uncore); -+ pci_dev_put(dev_priv->bridge_dev); -+} -+ -+static void intel_sanitize_options(struct drm_i915_private *dev_priv) -+{ -+ intel_gvt_sanitize_options(dev_priv); -+} -+ -+#define DRAM_TYPE_STR(type) [INTEL_DRAM_ ## type] = #type -+ -+static const char *intel_dram_type_str(enum intel_dram_type type) -+{ -+ static const char * const str[] = { -+ DRAM_TYPE_STR(UNKNOWN), -+ DRAM_TYPE_STR(DDR3), -+ DRAM_TYPE_STR(DDR4), -+ DRAM_TYPE_STR(LPDDR3), -+ DRAM_TYPE_STR(LPDDR4), -+ }; -+ -+ if (type >= ARRAY_SIZE(str)) -+ type = INTEL_DRAM_UNKNOWN; -+ -+ return str[type]; -+} -+ -+#undef DRAM_TYPE_STR -+ -+static int intel_dimm_num_devices(const struct dram_dimm_info *dimm) -+{ -+ return dimm->ranks * 64 / (dimm->width ?: 1); -+} -+ -+/* Returns total GB for the whole DIMM */ -+static int skl_get_dimm_size(u16 val) -+{ -+ return val & SKL_DRAM_SIZE_MASK; -+} -+ -+static int skl_get_dimm_width(u16 val) -+{ -+ if (skl_get_dimm_size(val) == 0) -+ return 0; -+ -+ switch (val & SKL_DRAM_WIDTH_MASK) { -+ case SKL_DRAM_WIDTH_X8: -+ case SKL_DRAM_WIDTH_X16: -+ case SKL_DRAM_WIDTH_X32: -+ val = (val & SKL_DRAM_WIDTH_MASK) >> SKL_DRAM_WIDTH_SHIFT; -+ return 8 << val; -+ default: -+ MISSING_CASE(val); -+ return 0; -+ } -+} -+ -+static int skl_get_dimm_ranks(u16 val) -+{ -+ if (skl_get_dimm_size(val) == 0) -+ return 0; -+ -+ val = (val & SKL_DRAM_RANK_MASK) >> SKL_DRAM_RANK_SHIFT; -+ -+ return val + 1; -+} -+ -+/* Returns total GB for the whole DIMM */ -+static int cnl_get_dimm_size(u16 val) -+{ -+ return (val & CNL_DRAM_SIZE_MASK) / 2; -+} -+ -+static int cnl_get_dimm_width(u16 val) -+{ -+ if (cnl_get_dimm_size(val) == 0) -+ return 0; -+ -+ switch (val & CNL_DRAM_WIDTH_MASK) { -+ case CNL_DRAM_WIDTH_X8: -+ case CNL_DRAM_WIDTH_X16: -+ case CNL_DRAM_WIDTH_X32: -+ val = (val & CNL_DRAM_WIDTH_MASK) >> CNL_DRAM_WIDTH_SHIFT; -+ return 8 << val; -+ default: -+ MISSING_CASE(val); -+ return 0; -+ } -+} -+ -+static int cnl_get_dimm_ranks(u16 val) -+{ -+ if (cnl_get_dimm_size(val) == 0) -+ return 0; -+ -+ val = (val & CNL_DRAM_RANK_MASK) >> CNL_DRAM_RANK_SHIFT; -+ -+ return val + 1; -+} -+ -+static bool -+skl_is_16gb_dimm(const struct dram_dimm_info *dimm) -+{ -+ /* Convert total GB to Gb per DRAM device */ -+ return 8 * dimm->size / (intel_dimm_num_devices(dimm) ?: 1) == 16; -+} -+ -+static void -+skl_dram_get_dimm_info(struct drm_i915_private *dev_priv, -+ struct dram_dimm_info *dimm, -+ int channel, char dimm_name, u16 val) -+{ -+ if (INTEL_GEN(dev_priv) >= 10) { -+ dimm->size = cnl_get_dimm_size(val); -+ dimm->width = cnl_get_dimm_width(val); -+ dimm->ranks = cnl_get_dimm_ranks(val); -+ } else { -+ dimm->size = skl_get_dimm_size(val); -+ dimm->width = skl_get_dimm_width(val); -+ dimm->ranks = skl_get_dimm_ranks(val); -+ } -+ -+ DRM_DEBUG_KMS("CH%u DIMM %c size: %u GB, width: X%u, ranks: %u, 16Gb DIMMs: %s\n", -+ channel, dimm_name, dimm->size, dimm->width, dimm->ranks, -+ yesno(skl_is_16gb_dimm(dimm))); -+} -+ -+static int -+skl_dram_get_channel_info(struct drm_i915_private *dev_priv, -+ struct dram_channel_info *ch, -+ int channel, u32 val) -+{ -+ skl_dram_get_dimm_info(dev_priv, &ch->dimm_l, -+ channel, 'L', val & 0xffff); -+ skl_dram_get_dimm_info(dev_priv, &ch->dimm_s, -+ channel, 'S', val >> 16); -+ -+ if (ch->dimm_l.size == 0 && ch->dimm_s.size == 0) { -+ DRM_DEBUG_KMS("CH%u not populated\n", channel); -+ return -EINVAL; -+ } -+ -+ if (ch->dimm_l.ranks == 2 || ch->dimm_s.ranks == 2) -+ ch->ranks = 2; -+ else if (ch->dimm_l.ranks == 1 && ch->dimm_s.ranks == 1) -+ ch->ranks = 2; -+ else -+ ch->ranks = 1; -+ -+ ch->is_16gb_dimm = -+ skl_is_16gb_dimm(&ch->dimm_l) || -+ skl_is_16gb_dimm(&ch->dimm_s); -+ -+ DRM_DEBUG_KMS("CH%u ranks: %u, 16Gb DIMMs: %s\n", -+ channel, ch->ranks, yesno(ch->is_16gb_dimm)); -+ -+ return 0; -+} -+ -+static bool -+intel_is_dram_symmetric(const struct dram_channel_info *ch0, -+ const struct dram_channel_info *ch1) -+{ -+ return !memcmp(ch0, ch1, sizeof(*ch0)) && -+ (ch0->dimm_s.size == 0 || -+ !memcmp(&ch0->dimm_l, &ch0->dimm_s, sizeof(ch0->dimm_l))); -+} -+ -+static int -+skl_dram_get_channels_info(struct drm_i915_private *dev_priv) -+{ -+ struct dram_info *dram_info = &dev_priv->dram_info; -+ struct dram_channel_info ch0 = {}, ch1 = {}; -+ u32 val; -+ int ret; -+ -+ val = I915_READ(SKL_MAD_DIMM_CH0_0_0_0_MCHBAR_MCMAIN); -+ ret = skl_dram_get_channel_info(dev_priv, &ch0, 0, val); -+ if (ret == 0) -+ dram_info->num_channels++; -+ -+ val = I915_READ(SKL_MAD_DIMM_CH1_0_0_0_MCHBAR_MCMAIN); -+ ret = skl_dram_get_channel_info(dev_priv, &ch1, 1, val); -+ if (ret == 0) -+ dram_info->num_channels++; -+ -+ if (dram_info->num_channels == 0) { -+ DRM_INFO("Number of memory channels is zero\n"); -+ return -EINVAL; -+ } -+ -+ /* -+ * If any of the channel is single rank channel, worst case output -+ * will be same as if single rank memory, so consider single rank -+ * memory. -+ */ -+ if (ch0.ranks == 1 || ch1.ranks == 1) -+ dram_info->ranks = 1; -+ else -+ dram_info->ranks = max(ch0.ranks, ch1.ranks); -+ -+ if (dram_info->ranks == 0) { -+ DRM_INFO("couldn't get memory rank information\n"); -+ return -EINVAL; -+ } -+ -+ dram_info->is_16gb_dimm = ch0.is_16gb_dimm || ch1.is_16gb_dimm; -+ -+ dram_info->symmetric_memory = intel_is_dram_symmetric(&ch0, &ch1); -+ -+ DRM_DEBUG_KMS("Memory configuration is symmetric? %s\n", -+ yesno(dram_info->symmetric_memory)); -+ return 0; -+} -+ -+static enum intel_dram_type -+skl_get_dram_type(struct drm_i915_private *dev_priv) -+{ -+ u32 val; -+ -+ val = I915_READ(SKL_MAD_INTER_CHANNEL_0_0_0_MCHBAR_MCMAIN); -+ -+ switch (val & SKL_DRAM_DDR_TYPE_MASK) { -+ case SKL_DRAM_DDR_TYPE_DDR3: -+ return INTEL_DRAM_DDR3; -+ case SKL_DRAM_DDR_TYPE_DDR4: -+ return INTEL_DRAM_DDR4; -+ case SKL_DRAM_DDR_TYPE_LPDDR3: -+ return INTEL_DRAM_LPDDR3; -+ case SKL_DRAM_DDR_TYPE_LPDDR4: -+ return INTEL_DRAM_LPDDR4; -+ default: -+ MISSING_CASE(val); -+ return INTEL_DRAM_UNKNOWN; -+ } -+} -+ -+static int -+skl_get_dram_info(struct drm_i915_private *dev_priv) -+{ -+ struct dram_info *dram_info = &dev_priv->dram_info; -+ u32 mem_freq_khz, val; -+ int ret; -+ -+ dram_info->type = skl_get_dram_type(dev_priv); -+ DRM_DEBUG_KMS("DRAM type: %s\n", intel_dram_type_str(dram_info->type)); -+ -+ ret = skl_dram_get_channels_info(dev_priv); -+ if (ret) -+ return ret; -+ -+ val = I915_READ(SKL_MC_BIOS_DATA_0_0_0_MCHBAR_PCU); -+ mem_freq_khz = DIV_ROUND_UP((val & SKL_REQ_DATA_MASK) * -+ SKL_MEMORY_FREQ_MULTIPLIER_HZ, 1000); -+ -+ dram_info->bandwidth_kbps = dram_info->num_channels * -+ mem_freq_khz * 8; -+ -+ if (dram_info->bandwidth_kbps == 0) { -+ DRM_INFO("Couldn't get system memory bandwidth\n"); -+ return -EINVAL; -+ } -+ -+ dram_info->valid = true; -+ return 0; -+} -+ -+/* Returns Gb per DRAM device */ -+static int bxt_get_dimm_size(u32 val) -+{ -+ switch (val & BXT_DRAM_SIZE_MASK) { -+ case BXT_DRAM_SIZE_4GBIT: -+ return 4; -+ case BXT_DRAM_SIZE_6GBIT: -+ return 6; -+ case BXT_DRAM_SIZE_8GBIT: -+ return 8; -+ case BXT_DRAM_SIZE_12GBIT: -+ return 12; -+ case BXT_DRAM_SIZE_16GBIT: -+ return 16; -+ default: -+ MISSING_CASE(val); -+ return 0; -+ } -+} -+ -+static int bxt_get_dimm_width(u32 val) -+{ -+ if (!bxt_get_dimm_size(val)) -+ return 0; -+ -+ val = (val & BXT_DRAM_WIDTH_MASK) >> BXT_DRAM_WIDTH_SHIFT; -+ -+ return 8 << val; -+} -+ -+static int bxt_get_dimm_ranks(u32 val) -+{ -+ if (!bxt_get_dimm_size(val)) -+ return 0; -+ -+ switch (val & BXT_DRAM_RANK_MASK) { -+ case BXT_DRAM_RANK_SINGLE: -+ return 1; -+ case BXT_DRAM_RANK_DUAL: -+ return 2; -+ default: -+ MISSING_CASE(val); -+ return 0; -+ } -+} -+ -+static enum intel_dram_type bxt_get_dimm_type(u32 val) -+{ -+ if (!bxt_get_dimm_size(val)) -+ return INTEL_DRAM_UNKNOWN; -+ -+ switch (val & BXT_DRAM_TYPE_MASK) { -+ case BXT_DRAM_TYPE_DDR3: -+ return INTEL_DRAM_DDR3; -+ case BXT_DRAM_TYPE_LPDDR3: -+ return INTEL_DRAM_LPDDR3; -+ case BXT_DRAM_TYPE_DDR4: -+ return INTEL_DRAM_DDR4; -+ case BXT_DRAM_TYPE_LPDDR4: -+ return INTEL_DRAM_LPDDR4; -+ default: -+ MISSING_CASE(val); -+ return INTEL_DRAM_UNKNOWN; -+ } -+} -+ -+static void bxt_get_dimm_info(struct dram_dimm_info *dimm, -+ u32 val) -+{ -+ dimm->width = bxt_get_dimm_width(val); -+ dimm->ranks = bxt_get_dimm_ranks(val); -+ -+ /* -+ * Size in register is Gb per DRAM device. Convert to total -+ * GB to match the way we report this for non-LP platforms. -+ */ -+ dimm->size = bxt_get_dimm_size(val) * intel_dimm_num_devices(dimm) / 8; -+} -+ -+static int -+bxt_get_dram_info(struct drm_i915_private *dev_priv) -+{ -+ struct dram_info *dram_info = &dev_priv->dram_info; -+ u32 dram_channels; -+ u32 mem_freq_khz, val; -+ u8 num_active_channels; -+ int i; -+ -+ val = I915_READ(BXT_P_CR_MC_BIOS_REQ_0_0_0); -+ mem_freq_khz = DIV_ROUND_UP((val & BXT_REQ_DATA_MASK) * -+ BXT_MEMORY_FREQ_MULTIPLIER_HZ, 1000); -+ -+ dram_channels = val & BXT_DRAM_CHANNEL_ACTIVE_MASK; -+ num_active_channels = hweight32(dram_channels); -+ -+ /* Each active bit represents 4-byte channel */ -+ dram_info->bandwidth_kbps = (mem_freq_khz * num_active_channels * 4); -+ -+ if (dram_info->bandwidth_kbps == 0) { -+ DRM_INFO("Couldn't get system memory bandwidth\n"); -+ return -EINVAL; -+ } -+ -+ /* -+ * Now read each DUNIT8/9/10/11 to check the rank of each dimms. -+ */ -+ for (i = BXT_D_CR_DRP0_DUNIT_START; i <= BXT_D_CR_DRP0_DUNIT_END; i++) { -+ struct dram_dimm_info dimm; -+ enum intel_dram_type type; -+ -+ val = I915_READ(BXT_D_CR_DRP0_DUNIT(i)); -+ if (val == 0xFFFFFFFF) -+ continue; -+ -+ dram_info->num_channels++; -+ -+ bxt_get_dimm_info(&dimm, val); -+ type = bxt_get_dimm_type(val); -+ -+ WARN_ON(type != INTEL_DRAM_UNKNOWN && -+ dram_info->type != INTEL_DRAM_UNKNOWN && -+ dram_info->type != type); -+ -+ DRM_DEBUG_KMS("CH%u DIMM size: %u GB, width: X%u, ranks: %u, type: %s\n", -+ i - BXT_D_CR_DRP0_DUNIT_START, -+ dimm.size, dimm.width, dimm.ranks, -+ intel_dram_type_str(type)); -+ -+ /* -+ * If any of the channel is single rank channel, -+ * worst case output will be same as if single rank -+ * memory, so consider single rank memory. -+ */ -+ if (dram_info->ranks == 0) -+ dram_info->ranks = dimm.ranks; -+ else if (dimm.ranks == 1) -+ dram_info->ranks = 1; -+ -+ if (type != INTEL_DRAM_UNKNOWN) -+ dram_info->type = type; -+ } -+ -+ if (dram_info->type == INTEL_DRAM_UNKNOWN || -+ dram_info->ranks == 0) { -+ DRM_INFO("couldn't get memory information\n"); -+ return -EINVAL; -+ } -+ -+ dram_info->valid = true; -+ return 0; -+} -+ -+static void -+intel_get_dram_info(struct drm_i915_private *dev_priv) -+{ -+ struct dram_info *dram_info = &dev_priv->dram_info; -+ int ret; -+ -+ /* -+ * Assume 16Gb DIMMs are present until proven otherwise. -+ * This is only used for the level 0 watermark latency -+ * w/a which does not apply to bxt/glk. -+ */ -+ dram_info->is_16gb_dimm = !IS_GEN9_LP(dev_priv); -+ -+ if (INTEL_GEN(dev_priv) < 9) -+ return; -+ -+ if (IS_GEN9_LP(dev_priv)) -+ ret = bxt_get_dram_info(dev_priv); -+ else -+ ret = skl_get_dram_info(dev_priv); -+ if (ret) -+ return; -+ -+ DRM_DEBUG_KMS("DRAM bandwidth: %u kBps, channels: %u\n", -+ dram_info->bandwidth_kbps, -+ dram_info->num_channels); -+ -+ DRM_DEBUG_KMS("DRAM ranks: %u, 16Gb DIMMs: %s\n", -+ dram_info->ranks, yesno(dram_info->is_16gb_dimm)); -+} -+ -+static u32 gen9_edram_size_mb(struct drm_i915_private *dev_priv, u32 cap) -+{ -+ const unsigned int ways[8] = { 4, 8, 12, 16, 16, 16, 16, 16 }; -+ const unsigned int sets[4] = { 1, 1, 2, 2 }; -+ -+ return EDRAM_NUM_BANKS(cap) * -+ ways[EDRAM_WAYS_IDX(cap)] * -+ sets[EDRAM_SETS_IDX(cap)]; -+} -+ -+static void edram_detect(struct drm_i915_private *dev_priv) -+{ -+ u32 edram_cap = 0; -+ -+ if (!(IS_HASWELL(dev_priv) || -+ IS_BROADWELL(dev_priv) || -+ INTEL_GEN(dev_priv) >= 9)) -+ return; -+ -+ edram_cap = __raw_uncore_read32(&dev_priv->uncore, HSW_EDRAM_CAP); -+ -+ /* NB: We can't write IDICR yet because we don't have gt funcs set up */ -+ -+ if (!(edram_cap & EDRAM_ENABLED)) -+ return; -+ -+ /* -+ * The needed capability bits for size calculation are not there with -+ * pre gen9 so return 128MB always. -+ */ -+ if (INTEL_GEN(dev_priv) < 9) -+ dev_priv->edram_size_mb = 128; -+ else -+ dev_priv->edram_size_mb = -+ gen9_edram_size_mb(dev_priv, edram_cap); -+ -+ DRM_INFO("Found %uMB of eDRAM\n", dev_priv->edram_size_mb); -+} -+ -+/** -+ * i915_driver_init_hw - setup state requiring device access -+ * @dev_priv: device private -+ * -+ * Setup state that requires accessing the device, but doesn't require -+ * exposing the driver via kernel internal or userspace interfaces. -+ */ -+static int i915_driver_init_hw(struct drm_i915_private *dev_priv) -+{ -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ int ret; -+ -+ if (i915_inject_load_failure()) -+ return -ENODEV; -+ -+ intel_device_info_runtime_init(dev_priv); -+ -+ if (HAS_PPGTT(dev_priv)) { -+ if (intel_vgpu_active(dev_priv) && -+ !intel_vgpu_has_full_ppgtt(dev_priv)) { -+ i915_report_error(dev_priv, -+ "incompatible vGPU found, support for isolated ppGTT required\n"); -+ return -ENXIO; -+ } -+ } -+ -+ if (HAS_EXECLISTS(dev_priv)) { -+ /* -+ * Older GVT emulation depends upon intercepting CSB mmio, -+ * which we no longer use, preferring to use the HWSP cache -+ * instead. -+ */ -+ if (intel_vgpu_active(dev_priv) && -+ !intel_vgpu_has_hwsp_emulation(dev_priv)) { -+ i915_report_error(dev_priv, -+ "old vGPU host found, support for HWSP emulation required\n"); -+ return -ENXIO; -+ } -+ } -+ -+ intel_sanitize_options(dev_priv); -+ -+ /* needs to be done before ggtt probe */ -+ edram_detect(dev_priv); -+ -+ i915_perf_init(dev_priv); -+ -+ ret = i915_ggtt_probe_hw(dev_priv); -+ if (ret) -+ goto err_perf; -+ -+ /* -+ * WARNING: Apparently we must kick fbdev drivers before vgacon, -+ * otherwise the vga fbdev driver falls over. -+ */ -+ ret = i915_kick_out_firmware_fb(dev_priv); -+ if (ret) { -+ DRM_ERROR("failed to remove conflicting framebuffer drivers\n"); -+ goto err_ggtt; -+ } -+ -+ ret = vga_remove_vgacon(pdev); -+ if (ret) { -+ DRM_ERROR("failed to remove conflicting VGA console\n"); -+ goto err_ggtt; -+ } -+ -+ ret = i915_ggtt_init_hw(dev_priv); -+ if (ret) -+ goto err_ggtt; -+ -+ ret = i915_ggtt_enable_hw(dev_priv); -+ if (ret) { -+ DRM_ERROR("failed to enable GGTT\n"); -+ goto err_ggtt; -+ } -+ -+ pci_set_master(pdev); -+ -+ /* -+ * We don't have a max segment size, so set it to the max so sg's -+ * debugging layer doesn't complain -+ */ -+ dma_set_max_seg_size(&pdev->dev, UINT_MAX); -+ -+ /* overlay on gen2 is broken and can't address above 1G */ -+ if (IS_GEN(dev_priv, 2)) { -+ ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(30)); -+ if (ret) { -+ DRM_ERROR("failed to set DMA mask\n"); -+ -+ goto err_ggtt; -+ } -+ } -+ -+ /* 965GM sometimes incorrectly writes to hardware status page (HWS) -+ * using 32bit addressing, overwriting memory if HWS is located -+ * above 4GB. -+ * -+ * The documentation also mentions an issue with undefined -+ * behaviour if any general state is accessed within a page above 4GB, -+ * which also needs to be handled carefully. -+ */ -+ if (IS_I965G(dev_priv) || IS_I965GM(dev_priv)) { -+ ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); -+ -+ if (ret) { -+ DRM_ERROR("failed to set DMA mask\n"); -+ -+ goto err_ggtt; -+ } -+ } -+ -+ pm_qos_add_request(&dev_priv->pm_qos, PM_QOS_CPU_DMA_LATENCY, -+ PM_QOS_DEFAULT_VALUE); -+ -+ intel_uncore_sanitize(dev_priv); -+ -+ intel_gt_init_workarounds(dev_priv); -+ i915_gem_load_init_fences(dev_priv); -+ -+ /* On the 945G/GM, the chipset reports the MSI capability on the -+ * integrated graphics even though the support isn't actually there -+ * according to the published specs. It doesn't appear to function -+ * correctly in testing on 945G. -+ * This may be a side effect of MSI having been made available for PEG -+ * and the registers being closely associated. -+ * -+ * According to chipset errata, on the 965GM, MSI interrupts may -+ * be lost or delayed, and was defeatured. MSI interrupts seem to -+ * get lost on g4x as well, and interrupt delivery seems to stay -+ * properly dead afterwards. So we'll just disable them for all -+ * pre-gen5 chipsets. -+ * -+ * dp aux and gmbus irq on gen4 seems to be able to generate legacy -+ * interrupts even when in MSI mode. This results in spurious -+ * interrupt warnings if the legacy irq no. is shared with another -+ * device. The kernel then disables that interrupt source and so -+ * prevents the other device from working properly. -+ */ -+ if (INTEL_GEN(dev_priv) >= 5) { -+ if (pci_enable_msi(pdev) < 0) -+ DRM_DEBUG_DRIVER("can't enable MSI"); -+ } -+ -+ ret = intel_gvt_init(dev_priv); -+ if (ret) -+ goto err_msi; -+ -+ intel_opregion_setup(dev_priv); -+ /* -+ * Fill the dram structure to get the system raw bandwidth and -+ * dram info. This will be used for memory latency calculation. -+ */ -+ intel_get_dram_info(dev_priv); -+ -+ -+ return 0; -+ -+err_msi: -+ if (pdev->msi_enabled) -+ pci_disable_msi(pdev); -+ pm_qos_remove_request(&dev_priv->pm_qos); -+err_ggtt: -+ i915_ggtt_cleanup_hw(dev_priv); -+err_perf: -+ i915_perf_fini(dev_priv); -+ return ret; -+} -+ -+/** -+ * i915_driver_cleanup_hw - cleanup the setup done in i915_driver_init_hw() -+ * @dev_priv: device private -+ */ -+static void i915_driver_cleanup_hw(struct drm_i915_private *dev_priv) -+{ -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ -+ i915_perf_fini(dev_priv); -+ -+ if (pdev->msi_enabled) -+ pci_disable_msi(pdev); -+ -+ pm_qos_remove_request(&dev_priv->pm_qos); -+ i915_ggtt_cleanup_hw(dev_priv); -+} -+ -+/** -+ * i915_driver_register - register the driver with the rest of the system -+ * @dev_priv: device private -+ * -+ * Perform any steps necessary to make the driver available via kernel -+ * internal or userspace interfaces. -+ */ -+static void i915_driver_register(struct drm_i915_private *dev_priv) -+{ -+ struct drm_device *dev = &dev_priv->drm; -+ -+ i915_gem_shrinker_register(dev_priv); -+ i915_pmu_register(dev_priv); -+ -+ /* -+ * Notify a valid surface after modesetting, -+ * when running inside a VM. -+ */ -+ if (intel_vgpu_active(dev_priv)) -+ I915_WRITE(vgtif_reg(display_ready), VGT_DRV_DISPLAY_READY); -+ -+ /* Reveal our presence to userspace */ -+ if (drm_dev_register(dev, 0) == 0) { -+ i915_debugfs_register(dev_priv); -+ i915_setup_sysfs(dev_priv); -+ -+ /* Depends on sysfs having been initialized */ -+ i915_perf_register(dev_priv); -+ } else -+ DRM_ERROR("Failed to register driver for userspace access!\n"); -+ -+ if (HAS_DISPLAY(dev_priv)) { -+ /* Must be done after probing outputs */ -+ intel_opregion_register(dev_priv); -+ acpi_video_register(); -+ } -+ -+ if (IS_GEN(dev_priv, 5)) -+ intel_gpu_ips_init(dev_priv); -+ -+ intel_audio_init(dev_priv); -+ -+ /* -+ * Some ports require correctly set-up hpd registers for detection to -+ * work properly (leading to ghost connected connector status), e.g. VGA -+ * on gm45. Hence we can only set up the initial fbdev config after hpd -+ * irqs are fully enabled. We do it last so that the async config -+ * cannot run before the connectors are registered. -+ */ -+ intel_fbdev_initial_config_async(dev); -+ -+ /* -+ * We need to coordinate the hotplugs with the asynchronous fbdev -+ * configuration, for which we use the fbdev->async_cookie. -+ */ -+ if (HAS_DISPLAY(dev_priv)) -+ drm_kms_helper_poll_init(dev); -+ -+ intel_power_domains_enable(dev_priv); -+ intel_runtime_pm_enable(dev_priv); -+} -+ -+/** -+ * i915_driver_unregister - cleanup the registration done in i915_driver_regiser() -+ * @dev_priv: device private -+ */ -+static void i915_driver_unregister(struct drm_i915_private *dev_priv) -+{ -+ intel_runtime_pm_disable(dev_priv); -+ intel_power_domains_disable(dev_priv); -+ -+ intel_fbdev_unregister(dev_priv); -+ intel_audio_deinit(dev_priv); -+ -+ /* -+ * After flushing the fbdev (incl. a late async config which will -+ * have delayed queuing of a hotplug event), then flush the hotplug -+ * events. -+ */ -+ drm_kms_helper_poll_fini(&dev_priv->drm); -+ -+ intel_gpu_ips_teardown(); -+ acpi_video_unregister(); -+ intel_opregion_unregister(dev_priv); -+ -+ i915_perf_unregister(dev_priv); -+ i915_pmu_unregister(dev_priv); -+ -+ i915_teardown_sysfs(dev_priv); -+ drm_dev_unregister(&dev_priv->drm); -+ -+ i915_gem_shrinker_unregister(dev_priv); -+} -+ -+static void i915_welcome_messages(struct drm_i915_private *dev_priv) -+{ -+ if (drm_debug & DRM_UT_DRIVER) { -+ struct drm_printer p = drm_debug_printer("i915 device info:"); -+ -+ drm_printf(&p, "pciid=0x%04x rev=0x%02x platform=%s (subplatform=0x%x) gen=%i\n", -+ INTEL_DEVID(dev_priv), -+ INTEL_REVID(dev_priv), -+ intel_platform_name(INTEL_INFO(dev_priv)->platform), -+ intel_subplatform(RUNTIME_INFO(dev_priv), -+ INTEL_INFO(dev_priv)->platform), -+ INTEL_GEN(dev_priv)); -+ -+ intel_device_info_dump_flags(INTEL_INFO(dev_priv), &p); -+ intel_device_info_dump_runtime(RUNTIME_INFO(dev_priv), &p); -+ } -+ -+ if (IS_ENABLED(CONFIG_DRM_I915_DEBUG)) -+ DRM_INFO("DRM_I915_DEBUG enabled\n"); -+ if (IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM)) -+ DRM_INFO("DRM_I915_DEBUG_GEM enabled\n"); -+ if (IS_ENABLED(CONFIG_DRM_I915_DEBUG_RUNTIME_PM)) -+ DRM_INFO("DRM_I915_DEBUG_RUNTIME_PM enabled\n"); -+} -+ -+static struct drm_i915_private * -+i915_driver_create(struct pci_dev *pdev, const struct pci_device_id *ent) -+{ -+ const struct intel_device_info *match_info = -+ (struct intel_device_info *)ent->driver_data; -+ struct intel_device_info *device_info; -+ struct drm_i915_private *i915; -+ int err; -+ -+ i915 = kzalloc(sizeof(*i915), GFP_KERNEL); -+ if (!i915) -+ return ERR_PTR(-ENOMEM); -+ -+ err = drm_dev_init(&i915->drm, &driver, &pdev->dev); -+ if (err) { -+ kfree(i915); -+ return ERR_PTR(err); -+ } -+ -+ i915->drm.pdev = pdev; -+ i915->drm.dev_private = i915; -+ pci_set_drvdata(pdev, &i915->drm); -+ -+ /* Setup the write-once "constant" device info */ -+ device_info = mkwrite_device_info(i915); -+ memcpy(device_info, match_info, sizeof(*device_info)); -+ RUNTIME_INFO(i915)->device_id = pdev->device; -+ -+ BUG_ON(device_info->gen > BITS_PER_TYPE(device_info->gen_mask)); -+ -+ return i915; -+} -+ -+static void i915_driver_destroy(struct drm_i915_private *i915) -+{ -+ struct pci_dev *pdev = i915->drm.pdev; -+ -+ drm_dev_fini(&i915->drm); -+ kfree(i915); -+ -+ /* And make sure we never chase our dangling pointer from pci_dev */ -+ pci_set_drvdata(pdev, NULL); -+} -+ -+/** -+ * i915_driver_load - setup chip and create an initial config -+ * @pdev: PCI device -+ * @ent: matching PCI ID entry -+ * -+ * The driver load routine has to do several things: -+ * - drive output discovery via intel_modeset_init() -+ * - initialize the memory manager -+ * - allocate initial config memory -+ * - setup the DRM framebuffer with the allocated memory -+ */ -+int i915_driver_load(struct pci_dev *pdev, const struct pci_device_id *ent) -+{ -+ const struct intel_device_info *match_info = -+ (struct intel_device_info *)ent->driver_data; -+ struct drm_i915_private *dev_priv; -+ int ret; -+ -+ dev_priv = i915_driver_create(pdev, ent); -+ if (IS_ERR(dev_priv)) -+ return PTR_ERR(dev_priv); -+ -+ /* Disable nuclear pageflip by default on pre-ILK */ -+ if (!i915_modparams.nuclear_pageflip && match_info->gen < 5) -+ dev_priv->drm.driver_features &= ~DRIVER_ATOMIC; -+ -+ ret = pci_enable_device(pdev); -+ if (ret) -+ goto out_fini; -+ -+ ret = i915_driver_init_early(dev_priv); -+ if (ret < 0) -+ goto out_pci_disable; -+ -+ disable_rpm_wakeref_asserts(dev_priv); -+ -+ ret = i915_driver_init_mmio(dev_priv); -+ if (ret < 0) -+ goto out_runtime_pm_put; -+ -+ ret = i915_driver_init_hw(dev_priv); -+ if (ret < 0) -+ goto out_cleanup_mmio; -+ -+ ret = i915_load_modeset_init(&dev_priv->drm); -+ if (ret < 0) -+ goto out_cleanup_hw; -+ -+ i915_driver_register(dev_priv); -+ -+ enable_rpm_wakeref_asserts(dev_priv); -+ -+ i915_welcome_messages(dev_priv); -+ -+ return 0; -+ -+out_cleanup_hw: -+ i915_driver_cleanup_hw(dev_priv); -+out_cleanup_mmio: -+ i915_driver_cleanup_mmio(dev_priv); -+out_runtime_pm_put: -+ enable_rpm_wakeref_asserts(dev_priv); -+ i915_driver_cleanup_early(dev_priv); -+out_pci_disable: -+ pci_disable_device(pdev); -+out_fini: -+ i915_load_error(dev_priv, "Device initialization failed (%d)\n", ret); -+ i915_driver_destroy(dev_priv); -+ return ret; -+} -+ -+void i915_driver_unload(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ -+ disable_rpm_wakeref_asserts(dev_priv); -+ -+ i915_driver_unregister(dev_priv); -+ -+ /* -+ * After unregistering the device to prevent any new users, cancel -+ * all in-flight requests so that we can quickly unbind the active -+ * resources. -+ */ -+ i915_gem_set_wedged(dev_priv); -+ -+ /* Flush any external code that still may be under the RCU lock */ -+ synchronize_rcu(); -+ -+ i915_gem_suspend(dev_priv); -+ -+ drm_atomic_helper_shutdown(dev); -+ -+ intel_gvt_cleanup(dev_priv); -+ -+ intel_modeset_cleanup(dev); -+ -+ intel_bios_cleanup(dev_priv); -+ -+ vga_switcheroo_unregister_client(pdev); -+ vga_client_register(pdev, NULL, NULL, NULL); -+ -+ intel_csr_ucode_fini(dev_priv); -+ -+ /* Free error state after interrupts are fully disabled. */ -+ cancel_delayed_work_sync(&dev_priv->gpu_error.hangcheck_work); -+ i915_reset_error_state(dev_priv); -+ -+ i915_gem_fini(dev_priv); -+ -+ intel_power_domains_fini_hw(dev_priv); -+ -+ i915_driver_cleanup_hw(dev_priv); -+ i915_driver_cleanup_mmio(dev_priv); -+ -+ enable_rpm_wakeref_asserts(dev_priv); -+ intel_runtime_pm_cleanup(dev_priv); -+} -+ -+static void i915_driver_release(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ i915_driver_cleanup_early(dev_priv); -+ i915_driver_destroy(dev_priv); -+} -+ -+static int i915_driver_open(struct drm_device *dev, struct drm_file *file) -+{ -+ struct drm_i915_private *i915 = to_i915(dev); -+ int ret; -+ -+ ret = i915_gem_open(i915, file); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+/** -+ * i915_driver_lastclose - clean up after all DRM clients have exited -+ * @dev: DRM device -+ * -+ * Take care of cleaning up after all DRM clients have exited. In the -+ * mode setting case, we want to restore the kernel's initial mode (just -+ * in case the last client left us in a bad state). -+ * -+ * Additionally, in the non-mode setting case, we'll tear down the GTT -+ * and DMA structures, since the kernel won't be using them, and clea -+ * up any GEM state. -+ */ -+static void i915_driver_lastclose(struct drm_device *dev) -+{ -+ intel_fbdev_restore_mode(dev); -+ vga_switcheroo_process_delayed_switch(); -+} -+ -+static void i915_driver_postclose(struct drm_device *dev, struct drm_file *file) -+{ -+ struct drm_i915_file_private *file_priv = file->driver_priv; -+ -+ mutex_lock(&dev->struct_mutex); -+ i915_gem_context_close(file); -+ i915_gem_release(dev, file); -+ mutex_unlock(&dev->struct_mutex); -+ -+ kfree(file_priv); -+} -+ -+static void intel_suspend_encoders(struct drm_i915_private *dev_priv) -+{ -+ struct drm_device *dev = &dev_priv->drm; -+ struct intel_encoder *encoder; -+ -+ drm_modeset_lock_all(dev); -+ for_each_intel_encoder(dev, encoder) -+ if (encoder->suspend) -+ encoder->suspend(encoder); -+ drm_modeset_unlock_all(dev); -+} -+ -+static int vlv_resume_prepare(struct drm_i915_private *dev_priv, -+ bool rpm_resume); -+static int vlv_suspend_complete(struct drm_i915_private *dev_priv); -+ -+static bool suspend_to_idle(struct drm_i915_private *dev_priv) -+{ -+#if IS_ENABLED(CONFIG_ACPI_SLEEP) -+ if (acpi_target_system_state() < ACPI_STATE_S3) -+ return true; -+#endif -+ return false; -+} -+ -+static int i915_drm_prepare(struct drm_device *dev) -+{ -+ struct drm_i915_private *i915 = to_i915(dev); -+ -+ /* -+ * NB intel_display_suspend() may issue new requests after we've -+ * ostensibly marked the GPU as ready-to-sleep here. We need to -+ * split out that work and pull it forward so that after point, -+ * the GPU is not woken again. -+ */ -+ i915_gem_suspend(i915); -+ -+ return 0; -+} -+ -+static int i915_drm_suspend(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ pci_power_t opregion_target_state; -+ -+ disable_rpm_wakeref_asserts(dev_priv); -+ -+ /* We do a lot of poking in a lot of registers, make sure they work -+ * properly. */ -+ intel_power_domains_disable(dev_priv); -+ -+ drm_kms_helper_poll_disable(dev); -+ -+ pci_save_state(pdev); -+ -+ intel_display_suspend(dev); -+ -+ intel_dp_mst_suspend(dev_priv); -+ -+ intel_runtime_pm_disable_interrupts(dev_priv); -+ intel_hpd_cancel_work(dev_priv); -+ -+ intel_suspend_encoders(dev_priv); -+ -+ intel_suspend_hw(dev_priv); -+ -+ i915_gem_suspend_gtt_mappings(dev_priv); -+ -+ i915_save_state(dev_priv); -+ -+ opregion_target_state = suspend_to_idle(dev_priv) ? PCI_D1 : PCI_D3cold; -+ intel_opregion_suspend(dev_priv, opregion_target_state); -+ -+ intel_fbdev_set_suspend(dev, FBINFO_STATE_SUSPENDED, true); -+ -+ dev_priv->suspend_count++; -+ -+ intel_csr_ucode_suspend(dev_priv); -+ -+ enable_rpm_wakeref_asserts(dev_priv); -+ -+ return 0; -+} -+ -+static enum i915_drm_suspend_mode -+get_suspend_mode(struct drm_i915_private *dev_priv, bool hibernate) -+{ -+ if (hibernate) -+ return I915_DRM_SUSPEND_HIBERNATE; -+ -+ if (suspend_to_idle(dev_priv)) -+ return I915_DRM_SUSPEND_IDLE; -+ -+ return I915_DRM_SUSPEND_MEM; -+} -+ -+static int i915_drm_suspend_late(struct drm_device *dev, bool hibernation) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ int ret; -+ -+ disable_rpm_wakeref_asserts(dev_priv); -+ -+ i915_gem_suspend_late(dev_priv); -+ -+ intel_uncore_suspend(&dev_priv->uncore); -+ -+ intel_power_domains_suspend(dev_priv, -+ get_suspend_mode(dev_priv, hibernation)); -+ -+ ret = 0; -+ if (INTEL_GEN(dev_priv) >= 11 || IS_GEN9_LP(dev_priv)) -+ bxt_enable_dc9(dev_priv); -+ else if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) -+ hsw_enable_pc8(dev_priv); -+ else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ ret = vlv_suspend_complete(dev_priv); -+ -+ if (ret) { -+ DRM_ERROR("Suspend complete failed: %d\n", ret); -+ intel_power_domains_resume(dev_priv); -+ -+ goto out; -+ } -+ -+ pci_disable_device(pdev); -+ /* -+ * During hibernation on some platforms the BIOS may try to access -+ * the device even though it's already in D3 and hang the machine. So -+ * leave the device in D0 on those platforms and hope the BIOS will -+ * power down the device properly. The issue was seen on multiple old -+ * GENs with different BIOS vendors, so having an explicit blacklist -+ * is inpractical; apply the workaround on everything pre GEN6. The -+ * platforms where the issue was seen: -+ * Lenovo Thinkpad X301, X61s, X60, T60, X41 -+ * Fujitsu FSC S7110 -+ * Acer Aspire 1830T -+ */ -+ if (!(hibernation && INTEL_GEN(dev_priv) < 6)) -+ pci_set_power_state(pdev, PCI_D3hot); -+ -+out: -+ enable_rpm_wakeref_asserts(dev_priv); -+ if (!dev_priv->uncore.user_forcewake.count) -+ intel_runtime_pm_cleanup(dev_priv); -+ -+ return ret; -+} -+ -+static int i915_suspend_switcheroo(struct drm_device *dev, pm_message_t state) -+{ -+ int error; -+ -+ if (!dev) { -+ DRM_ERROR("dev: %p\n", dev); -+ DRM_ERROR("DRM not initialized, aborting suspend.\n"); -+ return -ENODEV; -+ } -+ -+ if (WARN_ON_ONCE(state.event != PM_EVENT_SUSPEND && -+ state.event != PM_EVENT_FREEZE)) -+ return -EINVAL; -+ -+ if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) -+ return 0; -+ -+ error = i915_drm_suspend(dev); -+ if (error) -+ return error; -+ -+ return i915_drm_suspend_late(dev, false); -+} -+ -+static int i915_drm_resume(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int ret; -+ -+ disable_rpm_wakeref_asserts(dev_priv); -+ intel_sanitize_gt_powersave(dev_priv); -+ -+ i915_gem_sanitize(dev_priv); -+ -+ ret = i915_ggtt_enable_hw(dev_priv); -+ if (ret) -+ DRM_ERROR("failed to re-enable GGTT\n"); -+ -+ intel_csr_ucode_resume(dev_priv); -+ -+ i915_restore_state(dev_priv); -+ intel_pps_unlock_regs_wa(dev_priv); -+ -+ intel_init_pch_refclk(dev_priv); -+ -+ /* -+ * Interrupts have to be enabled before any batches are run. If not the -+ * GPU will hang. i915_gem_init_hw() will initiate batches to -+ * update/restore the context. -+ * -+ * drm_mode_config_reset() needs AUX interrupts. -+ * -+ * Modeset enabling in intel_modeset_init_hw() also needs working -+ * interrupts. -+ */ -+ intel_runtime_pm_enable_interrupts(dev_priv); -+ -+ drm_mode_config_reset(dev); -+ -+ i915_gem_resume(dev_priv); -+ -+ intel_modeset_init_hw(dev); -+ intel_init_clock_gating(dev_priv); -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ if (dev_priv->display.hpd_irq_setup) -+ dev_priv->display.hpd_irq_setup(dev_priv); -+ spin_unlock_irq(&dev_priv->irq_lock); -+ -+ intel_dp_mst_resume(dev_priv); -+ -+ intel_display_resume(dev); -+ -+ drm_kms_helper_poll_enable(dev); -+ -+ /* -+ * ... but also need to make sure that hotplug processing -+ * doesn't cause havoc. Like in the driver load code we don't -+ * bother with the tiny race here where we might lose hotplug -+ * notifications. -+ * */ -+ intel_hpd_init(dev_priv); -+ -+ intel_opregion_resume(dev_priv); -+ -+ intel_fbdev_set_suspend(dev, FBINFO_STATE_RUNNING, false); -+ -+ intel_power_domains_enable(dev_priv); -+ -+ enable_rpm_wakeref_asserts(dev_priv); -+ -+ return 0; -+} -+ -+static int i915_drm_resume_early(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ int ret; -+ -+ /* -+ * We have a resume ordering issue with the snd-hda driver also -+ * requiring our device to be power up. Due to the lack of a -+ * parent/child relationship we currently solve this with an early -+ * resume hook. -+ * -+ * FIXME: This should be solved with a special hdmi sink device or -+ * similar so that power domains can be employed. -+ */ -+ -+ /* -+ * Note that we need to set the power state explicitly, since we -+ * powered off the device during freeze and the PCI core won't power -+ * it back up for us during thaw. Powering off the device during -+ * freeze is not a hard requirement though, and during the -+ * suspend/resume phases the PCI core makes sure we get here with the -+ * device powered on. So in case we change our freeze logic and keep -+ * the device powered we can also remove the following set power state -+ * call. -+ */ -+ ret = pci_set_power_state(pdev, PCI_D0); -+ if (ret) { -+ DRM_ERROR("failed to set PCI D0 power state (%d)\n", ret); -+ return ret; -+ } -+ -+ /* -+ * Note that pci_enable_device() first enables any parent bridge -+ * device and only then sets the power state for this device. The -+ * bridge enabling is a nop though, since bridge devices are resumed -+ * first. The order of enabling power and enabling the device is -+ * imposed by the PCI core as described above, so here we preserve the -+ * same order for the freeze/thaw phases. -+ * -+ * TODO: eventually we should remove pci_disable_device() / -+ * pci_enable_enable_device() from suspend/resume. Due to how they -+ * depend on the device enable refcount we can't anyway depend on them -+ * disabling/enabling the device. -+ */ -+ if (pci_enable_device(pdev)) -+ return -EIO; -+ -+ pci_set_master(pdev); -+ -+ disable_rpm_wakeref_asserts(dev_priv); -+ -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ ret = vlv_resume_prepare(dev_priv, false); -+ if (ret) -+ DRM_ERROR("Resume prepare failed: %d, continuing anyway\n", -+ ret); -+ -+ intel_uncore_resume_early(&dev_priv->uncore); -+ -+ i915_check_and_clear_faults(dev_priv); -+ -+ if (INTEL_GEN(dev_priv) >= 11 || IS_GEN9_LP(dev_priv)) { -+ gen9_sanitize_dc_state(dev_priv); -+ bxt_disable_dc9(dev_priv); -+ } else if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) { -+ hsw_disable_pc8(dev_priv); -+ } -+ -+ intel_uncore_sanitize(dev_priv); -+ -+ intel_power_domains_resume(dev_priv); -+ -+ intel_engines_sanitize(dev_priv, true); -+ -+ enable_rpm_wakeref_asserts(dev_priv); -+ -+ return ret; -+} -+ -+static int i915_resume_switcheroo(struct drm_device *dev) -+{ -+ int ret; -+ -+ if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) -+ return 0; -+ -+ ret = i915_drm_resume_early(dev); -+ if (ret) -+ return ret; -+ -+ return i915_drm_resume(dev); -+} -+ -+static int i915_pm_prepare(struct device *kdev) -+{ -+ struct pci_dev *pdev = to_pci_dev(kdev); -+ struct drm_device *dev = pci_get_drvdata(pdev); -+ -+ if (!dev) { -+ dev_err(kdev, "DRM not initialized, aborting suspend.\n"); -+ return -ENODEV; -+ } -+ -+ if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) -+ return 0; -+ -+ return i915_drm_prepare(dev); -+} -+ -+static int i915_pm_suspend(struct device *kdev) -+{ -+ struct pci_dev *pdev = to_pci_dev(kdev); -+ struct drm_device *dev = pci_get_drvdata(pdev); -+ -+ if (!dev) { -+ dev_err(kdev, "DRM not initialized, aborting suspend.\n"); -+ return -ENODEV; -+ } -+ -+ if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) -+ return 0; -+ -+ return i915_drm_suspend(dev); -+} -+ -+static int i915_pm_suspend_late(struct device *kdev) -+{ -+ struct drm_device *dev = &kdev_to_i915(kdev)->drm; -+ -+ /* -+ * We have a suspend ordering issue with the snd-hda driver also -+ * requiring our device to be power up. Due to the lack of a -+ * parent/child relationship we currently solve this with an late -+ * suspend hook. -+ * -+ * FIXME: This should be solved with a special hdmi sink device or -+ * similar so that power domains can be employed. -+ */ -+ if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) -+ return 0; -+ -+ return i915_drm_suspend_late(dev, false); -+} -+ -+static int i915_pm_poweroff_late(struct device *kdev) -+{ -+ struct drm_device *dev = &kdev_to_i915(kdev)->drm; -+ -+ if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) -+ return 0; -+ -+ return i915_drm_suspend_late(dev, true); -+} -+ -+static int i915_pm_resume_early(struct device *kdev) -+{ -+ struct drm_device *dev = &kdev_to_i915(kdev)->drm; -+ -+ if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) -+ return 0; -+ -+ return i915_drm_resume_early(dev); -+} -+ -+static int i915_pm_resume(struct device *kdev) -+{ -+ struct drm_device *dev = &kdev_to_i915(kdev)->drm; -+ -+ if (dev->switch_power_state == DRM_SWITCH_POWER_OFF) -+ return 0; -+ -+ return i915_drm_resume(dev); -+} -+ -+/* freeze: before creating the hibernation_image */ -+static int i915_pm_freeze(struct device *kdev) -+{ -+ struct drm_device *dev = &kdev_to_i915(kdev)->drm; -+ int ret; -+ -+ if (dev->switch_power_state != DRM_SWITCH_POWER_OFF) { -+ ret = i915_drm_suspend(dev); -+ if (ret) -+ return ret; -+ } -+ -+ ret = i915_gem_freeze(kdev_to_i915(kdev)); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int i915_pm_freeze_late(struct device *kdev) -+{ -+ struct drm_device *dev = &kdev_to_i915(kdev)->drm; -+ int ret; -+ -+ if (dev->switch_power_state != DRM_SWITCH_POWER_OFF) { -+ ret = i915_drm_suspend_late(dev, true); -+ if (ret) -+ return ret; -+ } -+ -+ ret = i915_gem_freeze_late(kdev_to_i915(kdev)); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+/* thaw: called after creating the hibernation image, but before turning off. */ -+static int i915_pm_thaw_early(struct device *kdev) -+{ -+ return i915_pm_resume_early(kdev); -+} -+ -+static int i915_pm_thaw(struct device *kdev) -+{ -+ return i915_pm_resume(kdev); -+} -+ -+/* restore: called after loading the hibernation image. */ -+static int i915_pm_restore_early(struct device *kdev) -+{ -+ return i915_pm_resume_early(kdev); -+} -+ -+static int i915_pm_restore(struct device *kdev) -+{ -+ return i915_pm_resume(kdev); -+} -+ -+/* -+ * Save all Gunit registers that may be lost after a D3 and a subsequent -+ * S0i[R123] transition. The list of registers needing a save/restore is -+ * defined in the VLV2_S0IXRegs document. This documents marks all Gunit -+ * registers in the following way: -+ * - Driver: saved/restored by the driver -+ * - Punit : saved/restored by the Punit firmware -+ * - No, w/o marking: no need to save/restore, since the register is R/O or -+ * used internally by the HW in a way that doesn't depend -+ * keeping the content across a suspend/resume. -+ * - Debug : used for debugging -+ * -+ * We save/restore all registers marked with 'Driver', with the following -+ * exceptions: -+ * - Registers out of use, including also registers marked with 'Debug'. -+ * These have no effect on the driver's operation, so we don't save/restore -+ * them to reduce the overhead. -+ * - Registers that are fully setup by an initialization function called from -+ * the resume path. For example many clock gating and RPS/RC6 registers. -+ * - Registers that provide the right functionality with their reset defaults. -+ * -+ * TODO: Except for registers that based on the above 3 criteria can be safely -+ * ignored, we save/restore all others, practically treating the HW context as -+ * a black-box for the driver. Further investigation is needed to reduce the -+ * saved/restored registers even further, by following the same 3 criteria. -+ */ -+static void vlv_save_gunit_s0ix_state(struct drm_i915_private *dev_priv) -+{ -+ struct vlv_s0ix_state *s = &dev_priv->vlv_s0ix_state; -+ int i; -+ -+ /* GAM 0x4000-0x4770 */ -+ s->wr_watermark = I915_READ(GEN7_WR_WATERMARK); -+ s->gfx_prio_ctrl = I915_READ(GEN7_GFX_PRIO_CTRL); -+ s->arb_mode = I915_READ(ARB_MODE); -+ s->gfx_pend_tlb0 = I915_READ(GEN7_GFX_PEND_TLB0); -+ s->gfx_pend_tlb1 = I915_READ(GEN7_GFX_PEND_TLB1); -+ -+ for (i = 0; i < ARRAY_SIZE(s->lra_limits); i++) -+ s->lra_limits[i] = I915_READ(GEN7_LRA_LIMITS(i)); -+ -+ s->media_max_req_count = I915_READ(GEN7_MEDIA_MAX_REQ_COUNT); -+ s->gfx_max_req_count = I915_READ(GEN7_GFX_MAX_REQ_COUNT); -+ -+ s->render_hwsp = I915_READ(RENDER_HWS_PGA_GEN7); -+ s->ecochk = I915_READ(GAM_ECOCHK); -+ s->bsd_hwsp = I915_READ(BSD_HWS_PGA_GEN7); -+ s->blt_hwsp = I915_READ(BLT_HWS_PGA_GEN7); -+ -+ s->tlb_rd_addr = I915_READ(GEN7_TLB_RD_ADDR); -+ -+ /* MBC 0x9024-0x91D0, 0x8500 */ -+ s->g3dctl = I915_READ(VLV_G3DCTL); -+ s->gsckgctl = I915_READ(VLV_GSCKGCTL); -+ s->mbctl = I915_READ(GEN6_MBCTL); -+ -+ /* GCP 0x9400-0x9424, 0x8100-0x810C */ -+ s->ucgctl1 = I915_READ(GEN6_UCGCTL1); -+ s->ucgctl3 = I915_READ(GEN6_UCGCTL3); -+ s->rcgctl1 = I915_READ(GEN6_RCGCTL1); -+ s->rcgctl2 = I915_READ(GEN6_RCGCTL2); -+ s->rstctl = I915_READ(GEN6_RSTCTL); -+ s->misccpctl = I915_READ(GEN7_MISCCPCTL); -+ -+ /* GPM 0xA000-0xAA84, 0x8000-0x80FC */ -+ s->gfxpause = I915_READ(GEN6_GFXPAUSE); -+ s->rpdeuhwtc = I915_READ(GEN6_RPDEUHWTC); -+ s->rpdeuc = I915_READ(GEN6_RPDEUC); -+ s->ecobus = I915_READ(ECOBUS); -+ s->pwrdwnupctl = I915_READ(VLV_PWRDWNUPCTL); -+ s->rp_down_timeout = I915_READ(GEN6_RP_DOWN_TIMEOUT); -+ s->rp_deucsw = I915_READ(GEN6_RPDEUCSW); -+ s->rcubmabdtmr = I915_READ(GEN6_RCUBMABDTMR); -+ s->rcedata = I915_READ(VLV_RCEDATA); -+ s->spare2gh = I915_READ(VLV_SPAREG2H); -+ -+ /* Display CZ domain, 0x4400C-0x4402C, 0x4F000-0x4F11F */ -+ s->gt_imr = I915_READ(GTIMR); -+ s->gt_ier = I915_READ(GTIER); -+ s->pm_imr = I915_READ(GEN6_PMIMR); -+ s->pm_ier = I915_READ(GEN6_PMIER); -+ -+ for (i = 0; i < ARRAY_SIZE(s->gt_scratch); i++) -+ s->gt_scratch[i] = I915_READ(GEN7_GT_SCRATCH(i)); -+ -+ /* GT SA CZ domain, 0x100000-0x138124 */ -+ s->tilectl = I915_READ(TILECTL); -+ s->gt_fifoctl = I915_READ(GTFIFOCTL); -+ s->gtlc_wake_ctrl = I915_READ(VLV_GTLC_WAKE_CTRL); -+ s->gtlc_survive = I915_READ(VLV_GTLC_SURVIVABILITY_REG); -+ s->pmwgicz = I915_READ(VLV_PMWGICZ); -+ -+ /* Gunit-Display CZ domain, 0x182028-0x1821CF */ -+ s->gu_ctl0 = I915_READ(VLV_GU_CTL0); -+ s->gu_ctl1 = I915_READ(VLV_GU_CTL1); -+ s->pcbr = I915_READ(VLV_PCBR); -+ s->clock_gate_dis2 = I915_READ(VLV_GUNIT_CLOCK_GATE2); -+ -+ /* -+ * Not saving any of: -+ * DFT, 0x9800-0x9EC0 -+ * SARB, 0xB000-0xB1FC -+ * GAC, 0x5208-0x524C, 0x14000-0x14C000 -+ * PCI CFG -+ */ -+} -+ -+static void vlv_restore_gunit_s0ix_state(struct drm_i915_private *dev_priv) -+{ -+ struct vlv_s0ix_state *s = &dev_priv->vlv_s0ix_state; -+ u32 val; -+ int i; -+ -+ /* GAM 0x4000-0x4770 */ -+ I915_WRITE(GEN7_WR_WATERMARK, s->wr_watermark); -+ I915_WRITE(GEN7_GFX_PRIO_CTRL, s->gfx_prio_ctrl); -+ I915_WRITE(ARB_MODE, s->arb_mode | (0xffff << 16)); -+ I915_WRITE(GEN7_GFX_PEND_TLB0, s->gfx_pend_tlb0); -+ I915_WRITE(GEN7_GFX_PEND_TLB1, s->gfx_pend_tlb1); -+ -+ for (i = 0; i < ARRAY_SIZE(s->lra_limits); i++) -+ I915_WRITE(GEN7_LRA_LIMITS(i), s->lra_limits[i]); -+ -+ I915_WRITE(GEN7_MEDIA_MAX_REQ_COUNT, s->media_max_req_count); -+ I915_WRITE(GEN7_GFX_MAX_REQ_COUNT, s->gfx_max_req_count); -+ -+ I915_WRITE(RENDER_HWS_PGA_GEN7, s->render_hwsp); -+ I915_WRITE(GAM_ECOCHK, s->ecochk); -+ I915_WRITE(BSD_HWS_PGA_GEN7, s->bsd_hwsp); -+ I915_WRITE(BLT_HWS_PGA_GEN7, s->blt_hwsp); -+ -+ I915_WRITE(GEN7_TLB_RD_ADDR, s->tlb_rd_addr); -+ -+ /* MBC 0x9024-0x91D0, 0x8500 */ -+ I915_WRITE(VLV_G3DCTL, s->g3dctl); -+ I915_WRITE(VLV_GSCKGCTL, s->gsckgctl); -+ I915_WRITE(GEN6_MBCTL, s->mbctl); -+ -+ /* GCP 0x9400-0x9424, 0x8100-0x810C */ -+ I915_WRITE(GEN6_UCGCTL1, s->ucgctl1); -+ I915_WRITE(GEN6_UCGCTL3, s->ucgctl3); -+ I915_WRITE(GEN6_RCGCTL1, s->rcgctl1); -+ I915_WRITE(GEN6_RCGCTL2, s->rcgctl2); -+ I915_WRITE(GEN6_RSTCTL, s->rstctl); -+ I915_WRITE(GEN7_MISCCPCTL, s->misccpctl); -+ -+ /* GPM 0xA000-0xAA84, 0x8000-0x80FC */ -+ I915_WRITE(GEN6_GFXPAUSE, s->gfxpause); -+ I915_WRITE(GEN6_RPDEUHWTC, s->rpdeuhwtc); -+ I915_WRITE(GEN6_RPDEUC, s->rpdeuc); -+ I915_WRITE(ECOBUS, s->ecobus); -+ I915_WRITE(VLV_PWRDWNUPCTL, s->pwrdwnupctl); -+ I915_WRITE(GEN6_RP_DOWN_TIMEOUT,s->rp_down_timeout); -+ I915_WRITE(GEN6_RPDEUCSW, s->rp_deucsw); -+ I915_WRITE(GEN6_RCUBMABDTMR, s->rcubmabdtmr); -+ I915_WRITE(VLV_RCEDATA, s->rcedata); -+ I915_WRITE(VLV_SPAREG2H, s->spare2gh); -+ -+ /* Display CZ domain, 0x4400C-0x4402C, 0x4F000-0x4F11F */ -+ I915_WRITE(GTIMR, s->gt_imr); -+ I915_WRITE(GTIER, s->gt_ier); -+ I915_WRITE(GEN6_PMIMR, s->pm_imr); -+ I915_WRITE(GEN6_PMIER, s->pm_ier); -+ -+ for (i = 0; i < ARRAY_SIZE(s->gt_scratch); i++) -+ I915_WRITE(GEN7_GT_SCRATCH(i), s->gt_scratch[i]); -+ -+ /* GT SA CZ domain, 0x100000-0x138124 */ -+ I915_WRITE(TILECTL, s->tilectl); -+ I915_WRITE(GTFIFOCTL, s->gt_fifoctl); -+ /* -+ * Preserve the GT allow wake and GFX force clock bit, they are not -+ * be restored, as they are used to control the s0ix suspend/resume -+ * sequence by the caller. -+ */ -+ val = I915_READ(VLV_GTLC_WAKE_CTRL); -+ val &= VLV_GTLC_ALLOWWAKEREQ; -+ val |= s->gtlc_wake_ctrl & ~VLV_GTLC_ALLOWWAKEREQ; -+ I915_WRITE(VLV_GTLC_WAKE_CTRL, val); -+ -+ val = I915_READ(VLV_GTLC_SURVIVABILITY_REG); -+ val &= VLV_GFX_CLK_FORCE_ON_BIT; -+ val |= s->gtlc_survive & ~VLV_GFX_CLK_FORCE_ON_BIT; -+ I915_WRITE(VLV_GTLC_SURVIVABILITY_REG, val); -+ -+ I915_WRITE(VLV_PMWGICZ, s->pmwgicz); -+ -+ /* Gunit-Display CZ domain, 0x182028-0x1821CF */ -+ I915_WRITE(VLV_GU_CTL0, s->gu_ctl0); -+ I915_WRITE(VLV_GU_CTL1, s->gu_ctl1); -+ I915_WRITE(VLV_PCBR, s->pcbr); -+ I915_WRITE(VLV_GUNIT_CLOCK_GATE2, s->clock_gate_dis2); -+} -+ -+static int vlv_wait_for_pw_status(struct drm_i915_private *dev_priv, -+ u32 mask, u32 val) -+{ -+ i915_reg_t reg = VLV_GTLC_PW_STATUS; -+ u32 reg_value; -+ int ret; -+ -+ /* The HW does not like us polling for PW_STATUS frequently, so -+ * use the sleeping loop rather than risk the busy spin within -+ * intel_wait_for_register(). -+ * -+ * Transitioning between RC6 states should be at most 2ms (see -+ * valleyview_enable_rps) so use a 3ms timeout. -+ */ -+ ret = wait_for(((reg_value = I915_READ_NOTRACE(reg)) & mask) == val, 3); -+ -+ /* just trace the final value */ -+ trace_i915_reg_rw(false, reg, reg_value, sizeof(reg_value), true); -+ -+ return ret; -+} -+ -+int vlv_force_gfx_clock(struct drm_i915_private *dev_priv, bool force_on) -+{ -+ u32 val; -+ int err; -+ -+ val = I915_READ(VLV_GTLC_SURVIVABILITY_REG); -+ val &= ~VLV_GFX_CLK_FORCE_ON_BIT; -+ if (force_on) -+ val |= VLV_GFX_CLK_FORCE_ON_BIT; -+ I915_WRITE(VLV_GTLC_SURVIVABILITY_REG, val); -+ -+ if (!force_on) -+ return 0; -+ -+ err = intel_wait_for_register(&dev_priv->uncore, -+ VLV_GTLC_SURVIVABILITY_REG, -+ VLV_GFX_CLK_STATUS_BIT, -+ VLV_GFX_CLK_STATUS_BIT, -+ 20); -+ if (err) -+ DRM_ERROR("timeout waiting for GFX clock force-on (%08x)\n", -+ I915_READ(VLV_GTLC_SURVIVABILITY_REG)); -+ -+ return err; -+} -+ -+static int vlv_allow_gt_wake(struct drm_i915_private *dev_priv, bool allow) -+{ -+ u32 mask; -+ u32 val; -+ int err; -+ -+ val = I915_READ(VLV_GTLC_WAKE_CTRL); -+ val &= ~VLV_GTLC_ALLOWWAKEREQ; -+ if (allow) -+ val |= VLV_GTLC_ALLOWWAKEREQ; -+ I915_WRITE(VLV_GTLC_WAKE_CTRL, val); -+ POSTING_READ(VLV_GTLC_WAKE_CTRL); -+ -+ mask = VLV_GTLC_ALLOWWAKEACK; -+ val = allow ? mask : 0; -+ -+ err = vlv_wait_for_pw_status(dev_priv, mask, val); -+ if (err) -+ DRM_ERROR("timeout disabling GT waking\n"); -+ -+ return err; -+} -+ -+static void vlv_wait_for_gt_wells(struct drm_i915_private *dev_priv, -+ bool wait_for_on) -+{ -+ u32 mask; -+ u32 val; -+ -+ mask = VLV_GTLC_PW_MEDIA_STATUS_MASK | VLV_GTLC_PW_RENDER_STATUS_MASK; -+ val = wait_for_on ? mask : 0; -+ -+ /* -+ * RC6 transitioning can be delayed up to 2 msec (see -+ * valleyview_enable_rps), use 3 msec for safety. -+ * -+ * This can fail to turn off the rc6 if the GPU is stuck after a failed -+ * reset and we are trying to force the machine to sleep. -+ */ -+ if (vlv_wait_for_pw_status(dev_priv, mask, val)) -+ DRM_DEBUG_DRIVER("timeout waiting for GT wells to go %s\n", -+ onoff(wait_for_on)); -+} -+ -+static void vlv_check_no_gt_access(struct drm_i915_private *dev_priv) -+{ -+ if (!(I915_READ(VLV_GTLC_PW_STATUS) & VLV_GTLC_ALLOWWAKEERR)) -+ return; -+ -+ DRM_DEBUG_DRIVER("GT register access while GT waking disabled\n"); -+ I915_WRITE(VLV_GTLC_PW_STATUS, VLV_GTLC_ALLOWWAKEERR); -+} -+ -+static int vlv_suspend_complete(struct drm_i915_private *dev_priv) -+{ -+ u32 mask; -+ int err; -+ -+ /* -+ * Bspec defines the following GT well on flags as debug only, so -+ * don't treat them as hard failures. -+ */ -+ vlv_wait_for_gt_wells(dev_priv, false); -+ -+ mask = VLV_GTLC_RENDER_CTX_EXISTS | VLV_GTLC_MEDIA_CTX_EXISTS; -+ WARN_ON((I915_READ(VLV_GTLC_WAKE_CTRL) & mask) != mask); -+ -+ vlv_check_no_gt_access(dev_priv); -+ -+ err = vlv_force_gfx_clock(dev_priv, true); -+ if (err) -+ goto err1; -+ -+ err = vlv_allow_gt_wake(dev_priv, false); -+ if (err) -+ goto err2; -+ -+ if (!IS_CHERRYVIEW(dev_priv)) -+ vlv_save_gunit_s0ix_state(dev_priv); -+ -+ err = vlv_force_gfx_clock(dev_priv, false); -+ if (err) -+ goto err2; -+ -+ return 0; -+ -+err2: -+ /* For safety always re-enable waking and disable gfx clock forcing */ -+ vlv_allow_gt_wake(dev_priv, true); -+err1: -+ vlv_force_gfx_clock(dev_priv, false); -+ -+ return err; -+} -+ -+static int vlv_resume_prepare(struct drm_i915_private *dev_priv, -+ bool rpm_resume) -+{ -+ int err; -+ int ret; -+ -+ /* -+ * If any of the steps fail just try to continue, that's the best we -+ * can do at this point. Return the first error code (which will also -+ * leave RPM permanently disabled). -+ */ -+ ret = vlv_force_gfx_clock(dev_priv, true); -+ -+ if (!IS_CHERRYVIEW(dev_priv)) -+ vlv_restore_gunit_s0ix_state(dev_priv); -+ -+ err = vlv_allow_gt_wake(dev_priv, true); -+ if (!ret) -+ ret = err; -+ -+ err = vlv_force_gfx_clock(dev_priv, false); -+ if (!ret) -+ ret = err; -+ -+ vlv_check_no_gt_access(dev_priv); -+ -+ if (rpm_resume) -+ intel_init_clock_gating(dev_priv); -+ -+ return ret; -+} -+ -+static int intel_runtime_suspend(struct device *kdev) -+{ -+ struct pci_dev *pdev = to_pci_dev(kdev); -+ struct drm_device *dev = pci_get_drvdata(pdev); -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int ret; -+ -+ if (WARN_ON_ONCE(!(dev_priv->gt_pm.rc6.enabled && HAS_RC6(dev_priv)))) -+ return -ENODEV; -+ -+ if (WARN_ON_ONCE(!HAS_RUNTIME_PM(dev_priv))) -+ return -ENODEV; -+ -+ DRM_DEBUG_KMS("Suspending device\n"); -+ -+ disable_rpm_wakeref_asserts(dev_priv); -+ -+ /* -+ * We are safe here against re-faults, since the fault handler takes -+ * an RPM reference. -+ */ -+ i915_gem_runtime_suspend(dev_priv); -+ -+ intel_uc_suspend(dev_priv); -+ -+ intel_runtime_pm_disable_interrupts(dev_priv); -+ -+ intel_uncore_suspend(&dev_priv->uncore); -+ -+ ret = 0; -+ if (INTEL_GEN(dev_priv) >= 11) { -+ icl_display_core_uninit(dev_priv); -+ bxt_enable_dc9(dev_priv); -+ } else if (IS_GEN9_LP(dev_priv)) { -+ bxt_display_core_uninit(dev_priv); -+ bxt_enable_dc9(dev_priv); -+ } else if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) { -+ hsw_enable_pc8(dev_priv); -+ } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ ret = vlv_suspend_complete(dev_priv); -+ } -+ -+ if (ret) { -+ DRM_ERROR("Runtime suspend failed, disabling it (%d)\n", ret); -+ intel_uncore_runtime_resume(&dev_priv->uncore); -+ -+ intel_runtime_pm_enable_interrupts(dev_priv); -+ -+ intel_uc_resume(dev_priv); -+ -+ i915_gem_init_swizzling(dev_priv); -+ i915_gem_restore_fences(dev_priv); -+ -+ enable_rpm_wakeref_asserts(dev_priv); -+ -+ return ret; -+ } -+ -+ enable_rpm_wakeref_asserts(dev_priv); -+ intel_runtime_pm_cleanup(dev_priv); -+ -+ if (intel_uncore_arm_unclaimed_mmio_detection(&dev_priv->uncore)) -+ DRM_ERROR("Unclaimed access detected prior to suspending\n"); -+ -+ dev_priv->runtime_pm.suspended = true; -+ -+ /* -+ * FIXME: We really should find a document that references the arguments -+ * used below! -+ */ -+ if (IS_BROADWELL(dev_priv)) { -+ /* -+ * On Broadwell, if we use PCI_D1 the PCH DDI ports will stop -+ * being detected, and the call we do at intel_runtime_resume() -+ * won't be able to restore them. Since PCI_D3hot matches the -+ * actual specification and appears to be working, use it. -+ */ -+ intel_opregion_notify_adapter(dev_priv, PCI_D3hot); -+ } else { -+ /* -+ * current versions of firmware which depend on this opregion -+ * notification have repurposed the D1 definition to mean -+ * "runtime suspended" vs. what you would normally expect (D3) -+ * to distinguish it from notifications that might be sent via -+ * the suspend path. -+ */ -+ intel_opregion_notify_adapter(dev_priv, PCI_D1); -+ } -+ -+ assert_forcewakes_inactive(&dev_priv->uncore); -+ -+ if (!IS_VALLEYVIEW(dev_priv) && !IS_CHERRYVIEW(dev_priv)) -+ intel_hpd_poll_init(dev_priv); -+ -+ DRM_DEBUG_KMS("Device suspended\n"); -+ return 0; -+} -+ -+static int intel_runtime_resume(struct device *kdev) -+{ -+ struct pci_dev *pdev = to_pci_dev(kdev); -+ struct drm_device *dev = pci_get_drvdata(pdev); -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int ret = 0; -+ -+ if (WARN_ON_ONCE(!HAS_RUNTIME_PM(dev_priv))) -+ return -ENODEV; -+ -+ DRM_DEBUG_KMS("Resuming device\n"); -+ -+ WARN_ON_ONCE(atomic_read(&dev_priv->runtime_pm.wakeref_count)); -+ disable_rpm_wakeref_asserts(dev_priv); -+ -+ intel_opregion_notify_adapter(dev_priv, PCI_D0); -+ dev_priv->runtime_pm.suspended = false; -+ if (intel_uncore_unclaimed_mmio(&dev_priv->uncore)) -+ DRM_DEBUG_DRIVER("Unclaimed access during suspend, bios?\n"); -+ -+ if (INTEL_GEN(dev_priv) >= 11) { -+ bxt_disable_dc9(dev_priv); -+ icl_display_core_init(dev_priv, true); -+ if (dev_priv->csr.dmc_payload) { -+ if (dev_priv->csr.allowed_dc_mask & -+ DC_STATE_EN_UPTO_DC6) -+ skl_enable_dc6(dev_priv); -+ else if (dev_priv->csr.allowed_dc_mask & -+ DC_STATE_EN_UPTO_DC5) -+ gen9_enable_dc5(dev_priv); -+ } -+ } else if (IS_GEN9_LP(dev_priv)) { -+ bxt_disable_dc9(dev_priv); -+ bxt_display_core_init(dev_priv, true); -+ if (dev_priv->csr.dmc_payload && -+ (dev_priv->csr.allowed_dc_mask & DC_STATE_EN_UPTO_DC5)) -+ gen9_enable_dc5(dev_priv); -+ } else if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) { -+ hsw_disable_pc8(dev_priv); -+ } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ ret = vlv_resume_prepare(dev_priv, true); -+ } -+ -+ intel_uncore_runtime_resume(&dev_priv->uncore); -+ -+ intel_runtime_pm_enable_interrupts(dev_priv); -+ -+ intel_uc_resume(dev_priv); -+ -+ /* -+ * No point of rolling back things in case of an error, as the best -+ * we can do is to hope that things will still work (and disable RPM). -+ */ -+ i915_gem_init_swizzling(dev_priv); -+ i915_gem_restore_fences(dev_priv); -+ -+ /* -+ * On VLV/CHV display interrupts are part of the display -+ * power well, so hpd is reinitialized from there. For -+ * everyone else do it here. -+ */ -+ if (!IS_VALLEYVIEW(dev_priv) && !IS_CHERRYVIEW(dev_priv)) -+ intel_hpd_init(dev_priv); -+ -+ intel_enable_ipc(dev_priv); -+ -+ enable_rpm_wakeref_asserts(dev_priv); -+ -+ if (ret) -+ DRM_ERROR("Runtime resume failed, disabling it (%d)\n", ret); -+ else -+ DRM_DEBUG_KMS("Device resumed\n"); -+ -+ return ret; -+} -+ -+const struct dev_pm_ops i915_pm_ops = { -+ /* -+ * S0ix (via system suspend) and S3 event handlers [PMSG_SUSPEND, -+ * PMSG_RESUME] -+ */ -+ .prepare = i915_pm_prepare, -+ .suspend = i915_pm_suspend, -+ .suspend_late = i915_pm_suspend_late, -+ .resume_early = i915_pm_resume_early, -+ .resume = i915_pm_resume, -+ -+ /* -+ * S4 event handlers -+ * @freeze, @freeze_late : called (1) before creating the -+ * hibernation image [PMSG_FREEZE] and -+ * (2) after rebooting, before restoring -+ * the image [PMSG_QUIESCE] -+ * @thaw, @thaw_early : called (1) after creating the hibernation -+ * image, before writing it [PMSG_THAW] -+ * and (2) after failing to create or -+ * restore the image [PMSG_RECOVER] -+ * @poweroff, @poweroff_late: called after writing the hibernation -+ * image, before rebooting [PMSG_HIBERNATE] -+ * @restore, @restore_early : called after rebooting and restoring the -+ * hibernation image [PMSG_RESTORE] -+ */ -+ .freeze = i915_pm_freeze, -+ .freeze_late = i915_pm_freeze_late, -+ .thaw_early = i915_pm_thaw_early, -+ .thaw = i915_pm_thaw, -+ .poweroff = i915_pm_suspend, -+ .poweroff_late = i915_pm_poweroff_late, -+ .restore_early = i915_pm_restore_early, -+ .restore = i915_pm_restore, -+ -+ /* S0ix (via runtime suspend) event handlers */ -+ .runtime_suspend = intel_runtime_suspend, -+ .runtime_resume = intel_runtime_resume, -+}; -+ -+static const struct vm_operations_struct i915_gem_vm_ops = { -+ .fault = i915_gem_fault, -+ .open = drm_gem_vm_open, -+ .close = drm_gem_vm_close, -+}; -+ -+static const struct file_operations i915_driver_fops = { -+ .owner = THIS_MODULE, -+ .open = drm_open, -+ .release = drm_release, -+ .unlocked_ioctl = drm_ioctl, -+ .mmap = drm_gem_mmap, -+ .poll = drm_poll, -+ .read = drm_read, -+ .compat_ioctl = i915_compat_ioctl, -+ .llseek = noop_llseek, -+}; -+ -+static int -+i915_gem_reject_pin_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ return -ENODEV; -+} -+ -+static const struct drm_ioctl_desc i915_ioctls[] = { -+ DRM_IOCTL_DEF_DRV(I915_INIT, drm_noop, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), -+ DRM_IOCTL_DEF_DRV(I915_FLUSH, drm_noop, DRM_AUTH), -+ DRM_IOCTL_DEF_DRV(I915_FLIP, drm_noop, DRM_AUTH), -+ DRM_IOCTL_DEF_DRV(I915_BATCHBUFFER, drm_noop, DRM_AUTH), -+ DRM_IOCTL_DEF_DRV(I915_IRQ_EMIT, drm_noop, DRM_AUTH), -+ DRM_IOCTL_DEF_DRV(I915_IRQ_WAIT, drm_noop, DRM_AUTH), -+ DRM_IOCTL_DEF_DRV(I915_GETPARAM, i915_getparam_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_SETPARAM, drm_noop, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), -+ DRM_IOCTL_DEF_DRV(I915_ALLOC, drm_noop, DRM_AUTH), -+ DRM_IOCTL_DEF_DRV(I915_FREE, drm_noop, DRM_AUTH), -+ DRM_IOCTL_DEF_DRV(I915_INIT_HEAP, drm_noop, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), -+ DRM_IOCTL_DEF_DRV(I915_CMDBUFFER, drm_noop, DRM_AUTH), -+ DRM_IOCTL_DEF_DRV(I915_DESTROY_HEAP, drm_noop, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), -+ DRM_IOCTL_DEF_DRV(I915_SET_VBLANK_PIPE, drm_noop, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), -+ DRM_IOCTL_DEF_DRV(I915_GET_VBLANK_PIPE, drm_noop, DRM_AUTH), -+ DRM_IOCTL_DEF_DRV(I915_VBLANK_SWAP, drm_noop, DRM_AUTH), -+ DRM_IOCTL_DEF_DRV(I915_HWS_ADDR, drm_noop, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), -+ DRM_IOCTL_DEF_DRV(I915_GEM_INIT, drm_noop, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), -+ DRM_IOCTL_DEF_DRV(I915_GEM_EXECBUFFER, i915_gem_execbuffer_ioctl, DRM_AUTH), -+ DRM_IOCTL_DEF_DRV(I915_GEM_EXECBUFFER2_WR, i915_gem_execbuffer2_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_PIN, i915_gem_reject_pin_ioctl, DRM_AUTH|DRM_ROOT_ONLY), -+ DRM_IOCTL_DEF_DRV(I915_GEM_UNPIN, i915_gem_reject_pin_ioctl, DRM_AUTH|DRM_ROOT_ONLY), -+ DRM_IOCTL_DEF_DRV(I915_GEM_BUSY, i915_gem_busy_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_SET_CACHING, i915_gem_set_caching_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_GET_CACHING, i915_gem_get_caching_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_THROTTLE, i915_gem_throttle_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_ENTERVT, drm_noop, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), -+ DRM_IOCTL_DEF_DRV(I915_GEM_LEAVEVT, drm_noop, DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), -+ DRM_IOCTL_DEF_DRV(I915_GEM_CREATE, i915_gem_create_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_PREAD, i915_gem_pread_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_PWRITE, i915_gem_pwrite_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_MMAP, i915_gem_mmap_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_MMAP_GTT, i915_gem_mmap_gtt_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_SET_DOMAIN, i915_gem_set_domain_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_SW_FINISH, i915_gem_sw_finish_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_SET_TILING, i915_gem_set_tiling_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_GET_TILING, i915_gem_get_tiling_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_GET_APERTURE, i915_gem_get_aperture_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GET_PIPE_FROM_CRTC_ID, intel_get_pipe_from_crtc_id_ioctl, 0), -+ DRM_IOCTL_DEF_DRV(I915_GEM_MADVISE, i915_gem_madvise_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_OVERLAY_PUT_IMAGE, intel_overlay_put_image_ioctl, DRM_MASTER), -+ DRM_IOCTL_DEF_DRV(I915_OVERLAY_ATTRS, intel_overlay_attrs_ioctl, DRM_MASTER), -+ DRM_IOCTL_DEF_DRV(I915_SET_SPRITE_COLORKEY, intel_sprite_set_colorkey_ioctl, DRM_MASTER), -+ DRM_IOCTL_DEF_DRV(I915_GET_SPRITE_COLORKEY, drm_noop, DRM_MASTER), -+ DRM_IOCTL_DEF_DRV(I915_GEM_WAIT, i915_gem_wait_ioctl, DRM_AUTH|DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_CONTEXT_CREATE_EXT, i915_gem_context_create_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_CONTEXT_DESTROY, i915_gem_context_destroy_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_REG_READ, i915_reg_read_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GET_RESET_STATS, i915_gem_context_reset_stats_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_USERPTR, i915_gem_userptr_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_CONTEXT_GETPARAM, i915_gem_context_getparam_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_GEM_CONTEXT_SETPARAM, i915_gem_context_setparam_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_PERF_OPEN, i915_perf_open_ioctl, DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_PERF_ADD_CONFIG, i915_perf_add_config_ioctl, DRM_UNLOCKED|DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_PERF_REMOVE_CONFIG, i915_perf_remove_config_ioctl, DRM_UNLOCKED|DRM_RENDER_ALLOW), -+ DRM_IOCTL_DEF_DRV(I915_QUERY, i915_query_ioctl, DRM_UNLOCKED|DRM_RENDER_ALLOW), -+}; -+ -+static struct drm_driver driver = { -+ /* Don't use MTRRs here; the Xserver or userspace app should -+ * deal with them for Intel hardware. -+ */ -+ .driver_features = -+ DRIVER_GEM | DRIVER_PRIME | -+ DRIVER_RENDER | DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_SYNCOBJ, -+ .release = i915_driver_release, -+ .open = i915_driver_open, -+ .lastclose = i915_driver_lastclose, -+ .postclose = i915_driver_postclose, -+ -+ .gem_close_object = i915_gem_close_object, -+ .gem_free_object_unlocked = i915_gem_free_object, -+ .gem_vm_ops = &i915_gem_vm_ops, -+ -+ .prime_handle_to_fd = drm_gem_prime_handle_to_fd, -+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle, -+ .gem_prime_export = i915_gem_prime_export, -+ .gem_prime_import = i915_gem_prime_import, -+ -+ .dumb_create = i915_gem_dumb_create, -+ .dumb_map_offset = i915_gem_mmap_gtt, -+ .ioctls = i915_ioctls, -+ .num_ioctls = ARRAY_SIZE(i915_ioctls), -+ .fops = &i915_driver_fops, -+ .name = DRIVER_NAME, -+ .desc = DRIVER_DESC, -+ .date = DRIVER_DATE, -+ .major = DRIVER_MAJOR, -+ .minor = DRIVER_MINOR, -+ .patchlevel = DRIVER_PATCHLEVEL, -+}; -+ -+#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -+#include "selftests/mock_drm.c" -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_drv.h b/drivers/gpu/drm/i915_legacy/i915_drv.h -new file mode 100644 -index 000000000000..066fd2a12851 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_drv.h -@@ -0,0 +1,3693 @@ -+/* i915_drv.h -- Private header for the I915 driver -*- linux-c -*- -+ */ -+/* -+ * -+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas. -+ * All Rights Reserved. -+ * -+ * 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, sub license, 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 (including the -+ * next paragraph) 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 NON-INFRINGEMENT. -+ * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS 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. -+ * -+ */ -+ -+#ifndef _I915_DRV_H_ -+#define _I915_DRV_H_ -+ -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include /* for struct drm_dma_handle */ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "i915_fixed.h" -+#include "i915_params.h" -+#include "i915_reg.h" -+#include "i915_utils.h" -+ -+#include "intel_bios.h" -+#include "intel_device_info.h" -+#include "intel_display.h" -+#include "intel_dpll_mgr.h" -+#include "intel_frontbuffer.h" -+#include "intel_lrc.h" -+#include "intel_opregion.h" -+#include "intel_ringbuffer.h" -+#include "intel_uc.h" -+#include "intel_uncore.h" -+#include "intel_wopcm.h" -+#include "intel_workarounds.h" -+ -+#include "i915_gem.h" -+#include "i915_gem_context.h" -+#include "i915_gem_fence_reg.h" -+#include "i915_gem_object.h" -+#include "i915_gem_gtt.h" -+#include "i915_gpu_error.h" -+#include "i915_request.h" -+#include "i915_scheduler.h" -+#include "i915_timeline.h" -+#include "i915_vma.h" -+ -+#include "intel_gvt.h" -+ -+/* General customization: -+ */ -+ -+#define DRIVER_NAME "i915" -+#define DRIVER_DESC "Intel Graphics" -+#define DRIVER_DATE "20190417" -+#define DRIVER_TIMESTAMP 1555492067 -+ -+/* Use I915_STATE_WARN(x) and I915_STATE_WARN_ON() (rather than WARN() and -+ * WARN_ON()) for hw state sanity checks to check for unexpected conditions -+ * which may not necessarily be a user visible problem. This will either -+ * WARN() or DRM_ERROR() depending on the verbose_checks moduleparam, to -+ * enable distros and users to tailor their preferred amount of i915 abrt -+ * spam. -+ */ -+#define I915_STATE_WARN(condition, format...) ({ \ -+ int __ret_warn_on = !!(condition); \ -+ if (unlikely(__ret_warn_on)) \ -+ if (!WARN(i915_modparams.verbose_state_checks, format)) \ -+ DRM_ERROR(format); \ -+ unlikely(__ret_warn_on); \ -+}) -+ -+#define I915_STATE_WARN_ON(x) \ -+ I915_STATE_WARN((x), "%s", "WARN_ON(" __stringify(x) ")") -+ -+#if IS_ENABLED(CONFIG_DRM_I915_DEBUG) -+ -+bool __i915_inject_load_failure(const char *func, int line); -+#define i915_inject_load_failure() \ -+ __i915_inject_load_failure(__func__, __LINE__) -+ -+bool i915_error_injected(void); -+ -+#else -+ -+#define i915_inject_load_failure() false -+#define i915_error_injected() false -+ -+#endif -+ -+#define i915_load_error(i915, fmt, ...) \ -+ __i915_printk(i915, i915_error_injected() ? KERN_DEBUG : KERN_ERR, \ -+ fmt, ##__VA_ARGS__) -+ -+typedef depot_stack_handle_t intel_wakeref_t; -+ -+enum hpd_pin { -+ HPD_NONE = 0, -+ HPD_TV = HPD_NONE, /* TV is known to be unreliable */ -+ HPD_CRT, -+ HPD_SDVO_B, -+ HPD_SDVO_C, -+ HPD_PORT_A, -+ HPD_PORT_B, -+ HPD_PORT_C, -+ HPD_PORT_D, -+ HPD_PORT_E, -+ HPD_PORT_F, -+ HPD_NUM_PINS -+}; -+ -+#define for_each_hpd_pin(__pin) \ -+ for ((__pin) = (HPD_NONE + 1); (__pin) < HPD_NUM_PINS; (__pin)++) -+ -+/* Threshold == 5 for long IRQs, 50 for short */ -+#define HPD_STORM_DEFAULT_THRESHOLD 50 -+ -+struct i915_hotplug { -+ struct work_struct hotplug_work; -+ -+ struct { -+ unsigned long last_jiffies; -+ int count; -+ enum { -+ HPD_ENABLED = 0, -+ HPD_DISABLED = 1, -+ HPD_MARK_DISABLED = 2 -+ } state; -+ } stats[HPD_NUM_PINS]; -+ u32 event_bits; -+ struct delayed_work reenable_work; -+ -+ u32 long_port_mask; -+ u32 short_port_mask; -+ struct work_struct dig_port_work; -+ -+ struct work_struct poll_init_work; -+ bool poll_enabled; -+ -+ unsigned int hpd_storm_threshold; -+ /* Whether or not to count short HPD IRQs in HPD storms */ -+ u8 hpd_short_storm_enabled; -+ -+ /* -+ * if we get a HPD irq from DP and a HPD irq from non-DP -+ * the non-DP HPD could block the workqueue on a mode config -+ * mutex getting, that userspace may have taken. However -+ * userspace is waiting on the DP workqueue to run which is -+ * blocked behind the non-DP one. -+ */ -+ struct workqueue_struct *dp_wq; -+}; -+ -+#define I915_GEM_GPU_DOMAINS \ -+ (I915_GEM_DOMAIN_RENDER | \ -+ I915_GEM_DOMAIN_SAMPLER | \ -+ I915_GEM_DOMAIN_COMMAND | \ -+ I915_GEM_DOMAIN_INSTRUCTION | \ -+ I915_GEM_DOMAIN_VERTEX) -+ -+struct drm_i915_private; -+struct i915_mm_struct; -+struct i915_mmu_object; -+ -+struct drm_i915_file_private { -+ struct drm_i915_private *dev_priv; -+ struct drm_file *file; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head request_list; -+/* 20ms is a fairly arbitrary limit (greater than the average frame time) -+ * chosen to prevent the CPU getting more than a frame ahead of the GPU -+ * (when using lax throttling for the frontbuffer). We also use it to -+ * offer free GPU waitboosts for severely congested workloads. -+ */ -+#define DRM_I915_THROTTLE_JIFFIES msecs_to_jiffies(20) -+ } mm; -+ -+ struct idr context_idr; -+ struct mutex context_idr_lock; /* guards context_idr */ -+ -+ struct idr vm_idr; -+ struct mutex vm_idr_lock; /* guards vm_idr */ -+ -+ unsigned int bsd_engine; -+ -+/* -+ * Every context ban increments per client ban score. Also -+ * hangs in short succession increments ban score. If ban threshold -+ * is reached, client is considered banned and submitting more work -+ * will fail. This is a stop gap measure to limit the badly behaving -+ * clients access to gpu. Note that unbannable contexts never increment -+ * the client ban score. -+ */ -+#define I915_CLIENT_SCORE_HANG_FAST 1 -+#define I915_CLIENT_FAST_HANG_JIFFIES (60 * HZ) -+#define I915_CLIENT_SCORE_CONTEXT_BAN 3 -+#define I915_CLIENT_SCORE_BANNED 9 -+ /** ban_score: Accumulated score of all ctx bans and fast hangs. */ -+ atomic_t ban_score; -+ unsigned long hang_timestamp; -+}; -+ -+/* Interface history: -+ * -+ * 1.1: Original. -+ * 1.2: Add Power Management -+ * 1.3: Add vblank support -+ * 1.4: Fix cmdbuffer path, add heap destroy -+ * 1.5: Add vblank pipe configuration -+ * 1.6: - New ioctl for scheduling buffer swaps on vertical blank -+ * - Support vertical blank on secondary display pipe -+ */ -+#define DRIVER_MAJOR 1 -+#define DRIVER_MINOR 6 -+#define DRIVER_PATCHLEVEL 0 -+ -+struct intel_overlay; -+struct intel_overlay_error_state; -+ -+struct sdvo_device_mapping { -+ u8 initialized; -+ u8 dvo_port; -+ u8 slave_addr; -+ u8 dvo_wiring; -+ u8 i2c_pin; -+ u8 ddc_pin; -+}; -+ -+struct intel_connector; -+struct intel_encoder; -+struct intel_atomic_state; -+struct intel_crtc_state; -+struct intel_initial_plane_config; -+struct intel_crtc; -+struct intel_limit; -+struct dpll; -+struct intel_cdclk_state; -+ -+struct drm_i915_display_funcs { -+ void (*get_cdclk)(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state); -+ void (*set_cdclk)(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *cdclk_state, -+ enum pipe pipe); -+ int (*get_fifo_size)(struct drm_i915_private *dev_priv, -+ enum i9xx_plane_id i9xx_plane); -+ int (*compute_pipe_wm)(struct intel_crtc_state *cstate); -+ int (*compute_intermediate_wm)(struct intel_crtc_state *newstate); -+ void (*initial_watermarks)(struct intel_atomic_state *state, -+ struct intel_crtc_state *cstate); -+ void (*atomic_update_watermarks)(struct intel_atomic_state *state, -+ struct intel_crtc_state *cstate); -+ void (*optimize_watermarks)(struct intel_atomic_state *state, -+ struct intel_crtc_state *cstate); -+ int (*compute_global_watermarks)(struct intel_atomic_state *state); -+ void (*update_wm)(struct intel_crtc *crtc); -+ int (*modeset_calc_cdclk)(struct drm_atomic_state *state); -+ /* Returns the active state of the crtc, and if the crtc is active, -+ * fills out the pipe-config with the hw state. */ -+ bool (*get_pipe_config)(struct intel_crtc *, -+ struct intel_crtc_state *); -+ void (*get_initial_plane_config)(struct intel_crtc *, -+ struct intel_initial_plane_config *); -+ int (*crtc_compute_clock)(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state); -+ void (*crtc_enable)(struct intel_crtc_state *pipe_config, -+ struct drm_atomic_state *old_state); -+ void (*crtc_disable)(struct intel_crtc_state *old_crtc_state, -+ struct drm_atomic_state *old_state); -+ void (*update_crtcs)(struct drm_atomic_state *state); -+ void (*audio_codec_enable)(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state); -+ void (*audio_codec_disable)(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state); -+ void (*fdi_link_train)(struct intel_crtc *crtc, -+ const struct intel_crtc_state *crtc_state); -+ void (*init_clock_gating)(struct drm_i915_private *dev_priv); -+ void (*hpd_irq_setup)(struct drm_i915_private *dev_priv); -+ /* clock updates for mode set */ -+ /* cursor updates */ -+ /* render clock increase/decrease */ -+ /* display clock increase/decrease */ -+ /* pll clock increase/decrease */ -+ -+ int (*color_check)(struct intel_crtc_state *crtc_state); -+ /* -+ * Program double buffered color management registers during -+ * vblank evasion. The registers should then latch during the -+ * next vblank start, alongside any other double buffered registers -+ * involved with the same commit. -+ */ -+ void (*color_commit)(const struct intel_crtc_state *crtc_state); -+ /* -+ * Load LUTs (and other single buffered color management -+ * registers). Will (hopefully) be called during the vblank -+ * following the latching of any double buffered registers -+ * involved with the same commit. -+ */ -+ void (*load_luts)(const struct intel_crtc_state *crtc_state); -+}; -+ -+#define CSR_VERSION(major, minor) ((major) << 16 | (minor)) -+#define CSR_VERSION_MAJOR(version) ((version) >> 16) -+#define CSR_VERSION_MINOR(version) ((version) & 0xffff) -+ -+struct intel_csr { -+ struct work_struct work; -+ const char *fw_path; -+ u32 required_version; -+ u32 max_fw_size; /* bytes */ -+ u32 *dmc_payload; -+ u32 dmc_fw_size; /* dwords */ -+ u32 version; -+ u32 mmio_count; -+ i915_reg_t mmioaddr[8]; -+ u32 mmiodata[8]; -+ u32 dc_state; -+ u32 allowed_dc_mask; -+ intel_wakeref_t wakeref; -+}; -+ -+enum i915_cache_level { -+ I915_CACHE_NONE = 0, -+ I915_CACHE_LLC, /* also used for snoopable memory on non-LLC */ -+ I915_CACHE_L3_LLC, /* gen7+, L3 sits between the domain specifc -+ caches, eg sampler/render caches, and the -+ large Last-Level-Cache. LLC is coherent with -+ the CPU, but L3 is only visible to the GPU. */ -+ I915_CACHE_WT, /* hsw:gt3e WriteThrough for scanouts */ -+}; -+ -+#define I915_COLOR_UNEVICTABLE (-1) /* a non-vma sharing the address space */ -+ -+struct intel_fbc { -+ /* This is always the inner lock when overlapping with struct_mutex and -+ * it's the outer lock when overlapping with stolen_lock. */ -+ struct mutex lock; -+ unsigned threshold; -+ unsigned int possible_framebuffer_bits; -+ unsigned int busy_bits; -+ unsigned int visible_pipes_mask; -+ struct intel_crtc *crtc; -+ -+ struct drm_mm_node compressed_fb; -+ struct drm_mm_node *compressed_llb; -+ -+ bool false_color; -+ -+ bool enabled; -+ bool active; -+ bool flip_pending; -+ -+ bool underrun_detected; -+ struct work_struct underrun_work; -+ -+ /* -+ * Due to the atomic rules we can't access some structures without the -+ * appropriate locking, so we cache information here in order to avoid -+ * these problems. -+ */ -+ struct intel_fbc_state_cache { -+ struct i915_vma *vma; -+ unsigned long flags; -+ -+ struct { -+ unsigned int mode_flags; -+ u32 hsw_bdw_pixel_rate; -+ } crtc; -+ -+ struct { -+ unsigned int rotation; -+ int src_w; -+ int src_h; -+ bool visible; -+ /* -+ * Display surface base address adjustement for -+ * pageflips. Note that on gen4+ this only adjusts up -+ * to a tile, offsets within a tile are handled in -+ * the hw itself (with the TILEOFF register). -+ */ -+ int adjusted_x; -+ int adjusted_y; -+ -+ int y; -+ -+ u16 pixel_blend_mode; -+ } plane; -+ -+ struct { -+ const struct drm_format_info *format; -+ unsigned int stride; -+ } fb; -+ } state_cache; -+ -+ /* -+ * This structure contains everything that's relevant to program the -+ * hardware registers. When we want to figure out if we need to disable -+ * and re-enable FBC for a new configuration we just check if there's -+ * something different in the struct. The genx_fbc_activate functions -+ * are supposed to read from it in order to program the registers. -+ */ -+ struct intel_fbc_reg_params { -+ struct i915_vma *vma; -+ unsigned long flags; -+ -+ struct { -+ enum pipe pipe; -+ enum i9xx_plane_id i9xx_plane; -+ unsigned int fence_y_offset; -+ } crtc; -+ -+ struct { -+ const struct drm_format_info *format; -+ unsigned int stride; -+ } fb; -+ -+ int cfb_size; -+ unsigned int gen9_wa_cfb_stride; -+ } params; -+ -+ const char *no_fbc_reason; -+}; -+ -+/* -+ * HIGH_RR is the highest eDP panel refresh rate read from EDID -+ * LOW_RR is the lowest eDP panel refresh rate found from EDID -+ * parsing for same resolution. -+ */ -+enum drrs_refresh_rate_type { -+ DRRS_HIGH_RR, -+ DRRS_LOW_RR, -+ DRRS_MAX_RR, /* RR count */ -+}; -+ -+enum drrs_support_type { -+ DRRS_NOT_SUPPORTED = 0, -+ STATIC_DRRS_SUPPORT = 1, -+ SEAMLESS_DRRS_SUPPORT = 2 -+}; -+ -+struct intel_dp; -+struct i915_drrs { -+ struct mutex mutex; -+ struct delayed_work work; -+ struct intel_dp *dp; -+ unsigned busy_frontbuffer_bits; -+ enum drrs_refresh_rate_type refresh_rate_type; -+ enum drrs_support_type type; -+}; -+ -+struct i915_psr { -+ struct mutex lock; -+ -+#define I915_PSR_DEBUG_MODE_MASK 0x0f -+#define I915_PSR_DEBUG_DEFAULT 0x00 -+#define I915_PSR_DEBUG_DISABLE 0x01 -+#define I915_PSR_DEBUG_ENABLE 0x02 -+#define I915_PSR_DEBUG_FORCE_PSR1 0x03 -+#define I915_PSR_DEBUG_IRQ 0x10 -+ -+ u32 debug; -+ bool sink_support; -+ bool enabled; -+ struct intel_dp *dp; -+ enum pipe pipe; -+ bool active; -+ struct work_struct work; -+ unsigned busy_frontbuffer_bits; -+ bool sink_psr2_support; -+ bool link_standby; -+ bool colorimetry_support; -+ bool psr2_enabled; -+ u8 sink_sync_latency; -+ ktime_t last_entry_attempt; -+ ktime_t last_exit; -+ bool sink_not_reliable; -+ bool irq_aux_error; -+ u16 su_x_granularity; -+}; -+ -+/* -+ * Sorted by south display engine compatibility. -+ * If the new PCH comes with a south display engine that is not -+ * inherited from the latest item, please do not add it to the -+ * end. Instead, add it right after its "parent" PCH. -+ */ -+enum intel_pch { -+ PCH_NOP = -1, /* PCH without south display */ -+ PCH_NONE = 0, /* No PCH present */ -+ PCH_IBX, /* Ibexpeak PCH */ -+ PCH_CPT, /* Cougarpoint/Pantherpoint PCH */ -+ PCH_LPT, /* Lynxpoint/Wildcatpoint PCH */ -+ PCH_SPT, /* Sunrisepoint PCH */ -+ PCH_KBP, /* Kaby Lake PCH */ -+ PCH_CNP, /* Cannon/Comet Lake PCH */ -+ PCH_ICP, /* Ice Lake PCH */ -+}; -+ -+enum intel_sbi_destination { -+ SBI_ICLK, -+ SBI_MPHY, -+}; -+ -+#define QUIRK_LVDS_SSC_DISABLE (1<<1) -+#define QUIRK_INVERT_BRIGHTNESS (1<<2) -+#define QUIRK_BACKLIGHT_PRESENT (1<<3) -+#define QUIRK_PIN_SWIZZLED_PAGES (1<<5) -+#define QUIRK_INCREASE_T12_DELAY (1<<6) -+#define QUIRK_INCREASE_DDI_DISABLED_TIME (1<<7) -+ -+struct intel_fbdev; -+struct intel_fbc_work; -+ -+struct intel_gmbus { -+ struct i2c_adapter adapter; -+#define GMBUS_FORCE_BIT_RETRY (1U << 31) -+ u32 force_bit; -+ u32 reg0; -+ i915_reg_t gpio_reg; -+ struct i2c_algo_bit_data bit_algo; -+ struct drm_i915_private *dev_priv; -+}; -+ -+struct i915_suspend_saved_registers { -+ u32 saveDSPARB; -+ u32 saveFBC_CONTROL; -+ u32 saveCACHE_MODE_0; -+ u32 saveMI_ARB_STATE; -+ u32 saveSWF0[16]; -+ u32 saveSWF1[16]; -+ u32 saveSWF3[3]; -+ u64 saveFENCE[I915_MAX_NUM_FENCES]; -+ u32 savePCH_PORT_HOTPLUG; -+ u16 saveGCDGMBUS; -+}; -+ -+struct vlv_s0ix_state { -+ /* GAM */ -+ u32 wr_watermark; -+ u32 gfx_prio_ctrl; -+ u32 arb_mode; -+ u32 gfx_pend_tlb0; -+ u32 gfx_pend_tlb1; -+ u32 lra_limits[GEN7_LRA_LIMITS_REG_NUM]; -+ u32 media_max_req_count; -+ u32 gfx_max_req_count; -+ u32 render_hwsp; -+ u32 ecochk; -+ u32 bsd_hwsp; -+ u32 blt_hwsp; -+ u32 tlb_rd_addr; -+ -+ /* MBC */ -+ u32 g3dctl; -+ u32 gsckgctl; -+ u32 mbctl; -+ -+ /* GCP */ -+ u32 ucgctl1; -+ u32 ucgctl3; -+ u32 rcgctl1; -+ u32 rcgctl2; -+ u32 rstctl; -+ u32 misccpctl; -+ -+ /* GPM */ -+ u32 gfxpause; -+ u32 rpdeuhwtc; -+ u32 rpdeuc; -+ u32 ecobus; -+ u32 pwrdwnupctl; -+ u32 rp_down_timeout; -+ u32 rp_deucsw; -+ u32 rcubmabdtmr; -+ u32 rcedata; -+ u32 spare2gh; -+ -+ /* Display 1 CZ domain */ -+ u32 gt_imr; -+ u32 gt_ier; -+ u32 pm_imr; -+ u32 pm_ier; -+ u32 gt_scratch[GEN7_GT_SCRATCH_REG_NUM]; -+ -+ /* GT SA CZ domain */ -+ u32 tilectl; -+ u32 gt_fifoctl; -+ u32 gtlc_wake_ctrl; -+ u32 gtlc_survive; -+ u32 pmwgicz; -+ -+ /* Display 2 CZ domain */ -+ u32 gu_ctl0; -+ u32 gu_ctl1; -+ u32 pcbr; -+ u32 clock_gate_dis2; -+}; -+ -+struct intel_rps_ei { -+ ktime_t ktime; -+ u32 render_c0; -+ u32 media_c0; -+}; -+ -+struct intel_rps { -+ /* -+ * work, interrupts_enabled and pm_iir are protected by -+ * dev_priv->irq_lock -+ */ -+ struct work_struct work; -+ bool interrupts_enabled; -+ u32 pm_iir; -+ -+ /* PM interrupt bits that should never be masked */ -+ u32 pm_intrmsk_mbz; -+ -+ /* Frequencies are stored in potentially platform dependent multiples. -+ * In other words, *_freq needs to be multiplied by X to be interesting. -+ * Soft limits are those which are used for the dynamic reclocking done -+ * by the driver (raise frequencies under heavy loads, and lower for -+ * lighter loads). Hard limits are those imposed by the hardware. -+ * -+ * A distinction is made for overclocking, which is never enabled by -+ * default, and is considered to be above the hard limit if it's -+ * possible at all. -+ */ -+ u8 cur_freq; /* Current frequency (cached, may not == HW) */ -+ u8 min_freq_softlimit; /* Minimum frequency permitted by the driver */ -+ u8 max_freq_softlimit; /* Max frequency permitted by the driver */ -+ u8 max_freq; /* Maximum frequency, RP0 if not overclocking */ -+ u8 min_freq; /* AKA RPn. Minimum frequency */ -+ u8 boost_freq; /* Frequency to request when wait boosting */ -+ u8 idle_freq; /* Frequency to request when we are idle */ -+ u8 efficient_freq; /* AKA RPe. Pre-determined balanced frequency */ -+ u8 rp1_freq; /* "less than" RP0 power/freqency */ -+ u8 rp0_freq; /* Non-overclocked max frequency. */ -+ u16 gpll_ref_freq; /* vlv/chv GPLL reference frequency */ -+ -+ int last_adj; -+ -+ struct { -+ struct mutex mutex; -+ -+ enum { LOW_POWER, BETWEEN, HIGH_POWER } mode; -+ unsigned int interactive; -+ -+ u8 up_threshold; /* Current %busy required to uplock */ -+ u8 down_threshold; /* Current %busy required to downclock */ -+ } power; -+ -+ bool enabled; -+ atomic_t num_waiters; -+ atomic_t boosts; -+ -+ /* manual wa residency calculations */ -+ struct intel_rps_ei ei; -+}; -+ -+struct intel_rc6 { -+ bool enabled; -+ u64 prev_hw_residency[4]; -+ u64 cur_residency[4]; -+}; -+ -+struct intel_llc_pstate { -+ bool enabled; -+}; -+ -+struct intel_gen6_power_mgmt { -+ struct intel_rps rps; -+ struct intel_rc6 rc6; -+ struct intel_llc_pstate llc_pstate; -+}; -+ -+/* defined intel_pm.c */ -+extern spinlock_t mchdev_lock; -+ -+struct intel_ilk_power_mgmt { -+ u8 cur_delay; -+ u8 min_delay; -+ u8 max_delay; -+ u8 fmax; -+ u8 fstart; -+ -+ u64 last_count1; -+ unsigned long last_time1; -+ unsigned long chipset_power; -+ u64 last_count2; -+ u64 last_time2; -+ unsigned long gfx_power; -+ u8 corr; -+ -+ int c_m; -+ int r_t; -+}; -+ -+struct drm_i915_private; -+struct i915_power_well; -+ -+struct i915_power_well_ops { -+ /* -+ * Synchronize the well's hw state to match the current sw state, for -+ * example enable/disable it based on the current refcount. Called -+ * during driver init and resume time, possibly after first calling -+ * the enable/disable handlers. -+ */ -+ void (*sync_hw)(struct drm_i915_private *dev_priv, -+ struct i915_power_well *power_well); -+ /* -+ * Enable the well and resources that depend on it (for example -+ * interrupts located on the well). Called after the 0->1 refcount -+ * transition. -+ */ -+ void (*enable)(struct drm_i915_private *dev_priv, -+ struct i915_power_well *power_well); -+ /* -+ * Disable the well and resources that depend on it. Called after -+ * the 1->0 refcount transition. -+ */ -+ void (*disable)(struct drm_i915_private *dev_priv, -+ struct i915_power_well *power_well); -+ /* Returns the hw enabled state. */ -+ bool (*is_enabled)(struct drm_i915_private *dev_priv, -+ struct i915_power_well *power_well); -+}; -+ -+struct i915_power_well_regs { -+ i915_reg_t bios; -+ i915_reg_t driver; -+ i915_reg_t kvmr; -+ i915_reg_t debug; -+}; -+ -+/* Power well structure for haswell */ -+struct i915_power_well_desc { -+ const char *name; -+ bool always_on; -+ u64 domains; -+ /* unique identifier for this power well */ -+ enum i915_power_well_id id; -+ /* -+ * Arbitraty data associated with this power well. Platform and power -+ * well specific. -+ */ -+ union { -+ struct { -+ /* -+ * request/status flag index in the PUNIT power well -+ * control/status registers. -+ */ -+ u8 idx; -+ } vlv; -+ struct { -+ enum dpio_phy phy; -+ } bxt; -+ struct { -+ const struct i915_power_well_regs *regs; -+ /* -+ * request/status flag index in the power well -+ * constrol/status registers. -+ */ -+ u8 idx; -+ /* Mask of pipes whose IRQ logic is backed by the pw */ -+ u8 irq_pipe_mask; -+ /* The pw is backing the VGA functionality */ -+ bool has_vga:1; -+ bool has_fuses:1; -+ /* -+ * The pw is for an ICL+ TypeC PHY port in -+ * Thunderbolt mode. -+ */ -+ bool is_tc_tbt:1; -+ } hsw; -+ }; -+ const struct i915_power_well_ops *ops; -+}; -+ -+struct i915_power_well { -+ const struct i915_power_well_desc *desc; -+ /* power well enable/disable usage count */ -+ int count; -+ /* cached hw enabled state */ -+ bool hw_enabled; -+}; -+ -+struct i915_power_domains { -+ /* -+ * Power wells needed for initialization at driver init and suspend -+ * time are on. They are kept on until after the first modeset. -+ */ -+ bool initializing; -+ bool display_core_suspended; -+ int power_well_count; -+ -+ intel_wakeref_t wakeref; -+ -+ struct mutex lock; -+ int domain_use_count[POWER_DOMAIN_NUM]; -+ struct i915_power_well *power_wells; -+}; -+ -+#define MAX_L3_SLICES 2 -+struct intel_l3_parity { -+ u32 *remap_info[MAX_L3_SLICES]; -+ struct work_struct error_work; -+ int which_slice; -+}; -+ -+struct i915_gem_mm { -+ /** Memory allocator for GTT stolen memory */ -+ struct drm_mm stolen; -+ /** Protects the usage of the GTT stolen memory allocator. This is -+ * always the inner lock when overlapping with struct_mutex. */ -+ struct mutex stolen_lock; -+ -+ /* Protects bound_list/unbound_list and #drm_i915_gem_object.mm.link */ -+ spinlock_t obj_lock; -+ -+ /** List of all objects in gtt_space. Used to restore gtt -+ * mappings on resume */ -+ struct list_head bound_list; -+ /** -+ * List of objects which are not bound to the GTT (thus -+ * are idle and not used by the GPU). These objects may or may -+ * not actually have any pages attached. -+ */ -+ struct list_head unbound_list; -+ -+ /** List of all objects in gtt_space, currently mmaped by userspace. -+ * All objects within this list must also be on bound_list. -+ */ -+ struct list_head userfault_list; -+ -+ /** -+ * List of objects which are pending destruction. -+ */ -+ struct llist_head free_list; -+ struct work_struct free_work; -+ spinlock_t free_lock; -+ /** -+ * Count of objects pending destructions. Used to skip needlessly -+ * waiting on an RCU barrier if no objects are waiting to be freed. -+ */ -+ atomic_t free_count; -+ -+ /** -+ * Small stash of WC pages -+ */ -+ struct pagestash wc_stash; -+ -+ /** -+ * tmpfs instance used for shmem backed objects -+ */ -+ struct vfsmount *gemfs; -+ -+ /** PPGTT used for aliasing the PPGTT with the GTT */ -+ struct i915_hw_ppgtt *aliasing_ppgtt; -+ -+ struct notifier_block oom_notifier; -+ struct notifier_block vmap_notifier; -+ struct shrinker shrinker; -+ -+ /** LRU list of objects with fence regs on them. */ -+ struct list_head fence_list; -+ -+ /** -+ * Workqueue to fault in userptr pages, flushed by the execbuf -+ * when required but otherwise left to userspace to try again -+ * on EAGAIN. -+ */ -+ struct workqueue_struct *userptr_wq; -+ -+ u64 unordered_timeline; -+ -+ /* the indicator for dispatch video commands on two BSD rings */ -+ atomic_t bsd_engine_dispatch_index; -+ -+ /** Bit 6 swizzling required for X tiling */ -+ u32 bit_6_swizzle_x; -+ /** Bit 6 swizzling required for Y tiling */ -+ u32 bit_6_swizzle_y; -+ -+ /* accounting, useful for userland debugging */ -+ spinlock_t object_stat_lock; -+ u64 object_memory; -+ u32 object_count; -+}; -+ -+#define I915_IDLE_ENGINES_TIMEOUT (200) /* in ms */ -+ -+#define I915_RESET_TIMEOUT (10 * HZ) /* 10s */ -+#define I915_FENCE_TIMEOUT (10 * HZ) /* 10s */ -+ -+#define I915_ENGINE_DEAD_TIMEOUT (4 * HZ) /* Seqno, head and subunits dead */ -+#define I915_SEQNO_DEAD_TIMEOUT (12 * HZ) /* Seqno dead with active head */ -+ -+#define I915_ENGINE_WEDGED_TIMEOUT (60 * HZ) /* Reset but no recovery? */ -+ -+struct ddi_vbt_port_info { -+ int max_tmds_clock; -+ -+ /* -+ * This is an index in the HDMI/DVI DDI buffer translation table. -+ * The special value HDMI_LEVEL_SHIFT_UNKNOWN means the VBT didn't -+ * populate this field. -+ */ -+#define HDMI_LEVEL_SHIFT_UNKNOWN 0xff -+ u8 hdmi_level_shift; -+ -+ u8 present:1; -+ u8 supports_dvi:1; -+ u8 supports_hdmi:1; -+ u8 supports_dp:1; -+ u8 supports_edp:1; -+ u8 supports_typec_usb:1; -+ u8 supports_tbt:1; -+ -+ u8 alternate_aux_channel; -+ u8 alternate_ddc_pin; -+ -+ u8 dp_boost_level; -+ u8 hdmi_boost_level; -+ int dp_max_link_rate; /* 0 for not limited by VBT */ -+}; -+ -+enum psr_lines_to_wait { -+ PSR_0_LINES_TO_WAIT = 0, -+ PSR_1_LINE_TO_WAIT, -+ PSR_4_LINES_TO_WAIT, -+ PSR_8_LINES_TO_WAIT -+}; -+ -+struct intel_vbt_data { -+ struct drm_display_mode *lfp_lvds_vbt_mode; /* if any */ -+ struct drm_display_mode *sdvo_lvds_vbt_mode; /* if any */ -+ -+ /* Feature bits */ -+ unsigned int int_tv_support:1; -+ unsigned int lvds_dither:1; -+ unsigned int int_crt_support:1; -+ unsigned int lvds_use_ssc:1; -+ unsigned int int_lvds_support:1; -+ unsigned int display_clock_mode:1; -+ unsigned int fdi_rx_polarity_inverted:1; -+ unsigned int panel_type:4; -+ int lvds_ssc_freq; -+ unsigned int bios_lvds_val; /* initial [PCH_]LVDS reg val in VBIOS */ -+ enum drm_panel_orientation orientation; -+ -+ enum drrs_support_type drrs_type; -+ -+ struct { -+ int rate; -+ int lanes; -+ int preemphasis; -+ int vswing; -+ bool low_vswing; -+ bool initialized; -+ int bpp; -+ struct edp_power_seq pps; -+ } edp; -+ -+ struct { -+ bool enable; -+ bool full_link; -+ bool require_aux_wakeup; -+ int idle_frames; -+ enum psr_lines_to_wait lines_to_wait; -+ int tp1_wakeup_time_us; -+ int tp2_tp3_wakeup_time_us; -+ int psr2_tp2_tp3_wakeup_time_us; -+ } psr; -+ -+ struct { -+ u16 pwm_freq_hz; -+ bool present; -+ bool active_low_pwm; -+ u8 min_brightness; /* min_brightness/255 of max */ -+ u8 controller; /* brightness controller number */ -+ enum intel_backlight_type type; -+ } backlight; -+ -+ /* MIPI DSI */ -+ struct { -+ u16 panel_id; -+ struct mipi_config *config; -+ struct mipi_pps_data *pps; -+ u16 bl_ports; -+ u16 cabc_ports; -+ u8 seq_version; -+ u32 size; -+ u8 *data; -+ const u8 *sequence[MIPI_SEQ_MAX]; -+ u8 *deassert_seq; /* Used by fixup_mipi_sequences() */ -+ enum drm_panel_orientation orientation; -+ } dsi; -+ -+ int crt_ddc_pin; -+ -+ int child_dev_num; -+ struct child_device_config *child_dev; -+ -+ struct ddi_vbt_port_info ddi_port_info[I915_MAX_PORTS]; -+ struct sdvo_device_mapping sdvo_mappings[2]; -+}; -+ -+enum intel_ddb_partitioning { -+ INTEL_DDB_PART_1_2, -+ INTEL_DDB_PART_5_6, /* IVB+ */ -+}; -+ -+struct intel_wm_level { -+ bool enable; -+ u32 pri_val; -+ u32 spr_val; -+ u32 cur_val; -+ u32 fbc_val; -+}; -+ -+struct ilk_wm_values { -+ u32 wm_pipe[3]; -+ u32 wm_lp[3]; -+ u32 wm_lp_spr[3]; -+ u32 wm_linetime[3]; -+ bool enable_fbc_wm; -+ enum intel_ddb_partitioning partitioning; -+}; -+ -+struct g4x_pipe_wm { -+ u16 plane[I915_MAX_PLANES]; -+ u16 fbc; -+}; -+ -+struct g4x_sr_wm { -+ u16 plane; -+ u16 cursor; -+ u16 fbc; -+}; -+ -+struct vlv_wm_ddl_values { -+ u8 plane[I915_MAX_PLANES]; -+}; -+ -+struct vlv_wm_values { -+ struct g4x_pipe_wm pipe[3]; -+ struct g4x_sr_wm sr; -+ struct vlv_wm_ddl_values ddl[3]; -+ u8 level; -+ bool cxsr; -+}; -+ -+struct g4x_wm_values { -+ struct g4x_pipe_wm pipe[2]; -+ struct g4x_sr_wm sr; -+ struct g4x_sr_wm hpll; -+ bool cxsr; -+ bool hpll_en; -+ bool fbc_en; -+}; -+ -+struct skl_ddb_entry { -+ u16 start, end; /* in number of blocks, 'end' is exclusive */ -+}; -+ -+static inline u16 skl_ddb_entry_size(const struct skl_ddb_entry *entry) -+{ -+ return entry->end - entry->start; -+} -+ -+static inline bool skl_ddb_entry_equal(const struct skl_ddb_entry *e1, -+ const struct skl_ddb_entry *e2) -+{ -+ if (e1->start == e2->start && e1->end == e2->end) -+ return true; -+ -+ return false; -+} -+ -+struct skl_ddb_allocation { -+ u8 enabled_slices; /* GEN11 has configurable 2 slices */ -+}; -+ -+struct skl_ddb_values { -+ unsigned dirty_pipes; -+ struct skl_ddb_allocation ddb; -+}; -+ -+struct skl_wm_level { -+ u16 min_ddb_alloc; -+ u16 plane_res_b; -+ u8 plane_res_l; -+ bool plane_en; -+ bool ignore_lines; -+}; -+ -+/* Stores plane specific WM parameters */ -+struct skl_wm_params { -+ bool x_tiled, y_tiled; -+ bool rc_surface; -+ bool is_planar; -+ u32 width; -+ u8 cpp; -+ u32 plane_pixel_rate; -+ u32 y_min_scanlines; -+ u32 plane_bytes_per_line; -+ uint_fixed_16_16_t plane_blocks_per_line; -+ uint_fixed_16_16_t y_tile_minimum; -+ u32 linetime_us; -+ u32 dbuf_block_size; -+}; -+ -+/* -+ * This struct helps tracking the state needed for runtime PM, which puts the -+ * device in PCI D3 state. Notice that when this happens, nothing on the -+ * graphics device works, even register access, so we don't get interrupts nor -+ * anything else. -+ * -+ * Every piece of our code that needs to actually touch the hardware needs to -+ * either call intel_runtime_pm_get or call intel_display_power_get with the -+ * appropriate power domain. -+ * -+ * Our driver uses the autosuspend delay feature, which means we'll only really -+ * suspend if we stay with zero refcount for a certain amount of time. The -+ * default value is currently very conservative (see intel_runtime_pm_enable), but -+ * it can be changed with the standard runtime PM files from sysfs. -+ * -+ * The irqs_disabled variable becomes true exactly after we disable the IRQs and -+ * goes back to false exactly before we reenable the IRQs. We use this variable -+ * to check if someone is trying to enable/disable IRQs while they're supposed -+ * to be disabled. This shouldn't happen and we'll print some error messages in -+ * case it happens. -+ * -+ * For more, read the Documentation/power/runtime_pm.txt. -+ */ -+struct i915_runtime_pm { -+ atomic_t wakeref_count; -+ bool suspended; -+ bool irqs_enabled; -+ -+#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_RUNTIME_PM) -+ /* -+ * To aide detection of wakeref leaks and general misuse, we -+ * track all wakeref holders. With manual markup (i.e. returning -+ * a cookie to each rpm_get caller which they then supply to their -+ * paired rpm_put) we can remove corresponding pairs of and keep -+ * the array trimmed to active wakerefs. -+ */ -+ struct intel_runtime_pm_debug { -+ spinlock_t lock; -+ -+ depot_stack_handle_t last_acquire; -+ depot_stack_handle_t last_release; -+ -+ depot_stack_handle_t *owners; -+ unsigned long count; -+ } debug; -+#endif -+}; -+ -+enum intel_pipe_crc_source { -+ INTEL_PIPE_CRC_SOURCE_NONE, -+ INTEL_PIPE_CRC_SOURCE_PLANE1, -+ INTEL_PIPE_CRC_SOURCE_PLANE2, -+ INTEL_PIPE_CRC_SOURCE_PLANE3, -+ INTEL_PIPE_CRC_SOURCE_PLANE4, -+ INTEL_PIPE_CRC_SOURCE_PLANE5, -+ INTEL_PIPE_CRC_SOURCE_PLANE6, -+ INTEL_PIPE_CRC_SOURCE_PLANE7, -+ INTEL_PIPE_CRC_SOURCE_PIPE, -+ /* TV/DP on pre-gen5/vlv can't use the pipe source. */ -+ INTEL_PIPE_CRC_SOURCE_TV, -+ INTEL_PIPE_CRC_SOURCE_DP_B, -+ INTEL_PIPE_CRC_SOURCE_DP_C, -+ INTEL_PIPE_CRC_SOURCE_DP_D, -+ INTEL_PIPE_CRC_SOURCE_AUTO, -+ INTEL_PIPE_CRC_SOURCE_MAX, -+}; -+ -+#define INTEL_PIPE_CRC_ENTRIES_NR 128 -+struct intel_pipe_crc { -+ spinlock_t lock; -+ int skipped; -+ enum intel_pipe_crc_source source; -+}; -+ -+struct i915_frontbuffer_tracking { -+ spinlock_t lock; -+ -+ /* -+ * Tracking bits for delayed frontbuffer flushing du to gpu activity or -+ * scheduled flips. -+ */ -+ unsigned busy_bits; -+ unsigned flip_bits; -+}; -+ -+struct i915_virtual_gpu { -+ bool active; -+ u32 caps; -+}; -+ -+/* used in computing the new watermarks state */ -+struct intel_wm_config { -+ unsigned int num_pipes_active; -+ bool sprites_enabled; -+ bool sprites_scaled; -+}; -+ -+struct i915_oa_format { -+ u32 format; -+ int size; -+}; -+ -+struct i915_oa_reg { -+ i915_reg_t addr; -+ u32 value; -+}; -+ -+struct i915_oa_config { -+ char uuid[UUID_STRING_LEN + 1]; -+ int id; -+ -+ const struct i915_oa_reg *mux_regs; -+ u32 mux_regs_len; -+ const struct i915_oa_reg *b_counter_regs; -+ u32 b_counter_regs_len; -+ const struct i915_oa_reg *flex_regs; -+ u32 flex_regs_len; -+ -+ struct attribute_group sysfs_metric; -+ struct attribute *attrs[2]; -+ struct device_attribute sysfs_metric_id; -+ -+ atomic_t ref_count; -+}; -+ -+struct i915_perf_stream; -+ -+/** -+ * struct i915_perf_stream_ops - the OPs to support a specific stream type -+ */ -+struct i915_perf_stream_ops { -+ /** -+ * @enable: Enables the collection of HW samples, either in response to -+ * `I915_PERF_IOCTL_ENABLE` or implicitly called when stream is opened -+ * without `I915_PERF_FLAG_DISABLED`. -+ */ -+ void (*enable)(struct i915_perf_stream *stream); -+ -+ /** -+ * @disable: Disables the collection of HW samples, either in response -+ * to `I915_PERF_IOCTL_DISABLE` or implicitly called before destroying -+ * the stream. -+ */ -+ void (*disable)(struct i915_perf_stream *stream); -+ -+ /** -+ * @poll_wait: Call poll_wait, passing a wait queue that will be woken -+ * once there is something ready to read() for the stream -+ */ -+ void (*poll_wait)(struct i915_perf_stream *stream, -+ struct file *file, -+ poll_table *wait); -+ -+ /** -+ * @wait_unlocked: For handling a blocking read, wait until there is -+ * something to ready to read() for the stream. E.g. wait on the same -+ * wait queue that would be passed to poll_wait(). -+ */ -+ int (*wait_unlocked)(struct i915_perf_stream *stream); -+ -+ /** -+ * @read: Copy buffered metrics as records to userspace -+ * **buf**: the userspace, destination buffer -+ * **count**: the number of bytes to copy, requested by userspace -+ * **offset**: zero at the start of the read, updated as the read -+ * proceeds, it represents how many bytes have been copied so far and -+ * the buffer offset for copying the next record. -+ * -+ * Copy as many buffered i915 perf samples and records for this stream -+ * to userspace as will fit in the given buffer. -+ * -+ * Only write complete records; returning -%ENOSPC if there isn't room -+ * for a complete record. -+ * -+ * Return any error condition that results in a short read such as -+ * -%ENOSPC or -%EFAULT, even though these may be squashed before -+ * returning to userspace. -+ */ -+ int (*read)(struct i915_perf_stream *stream, -+ char __user *buf, -+ size_t count, -+ size_t *offset); -+ -+ /** -+ * @destroy: Cleanup any stream specific resources. -+ * -+ * The stream will always be disabled before this is called. -+ */ -+ void (*destroy)(struct i915_perf_stream *stream); -+}; -+ -+/** -+ * struct i915_perf_stream - state for a single open stream FD -+ */ -+struct i915_perf_stream { -+ /** -+ * @dev_priv: i915 drm device -+ */ -+ struct drm_i915_private *dev_priv; -+ -+ /** -+ * @link: Links the stream into ``&drm_i915_private->streams`` -+ */ -+ struct list_head link; -+ -+ /** -+ * @wakeref: As we keep the device awake while the perf stream is -+ * active, we track our runtime pm reference for later release. -+ */ -+ intel_wakeref_t wakeref; -+ -+ /** -+ * @sample_flags: Flags representing the `DRM_I915_PERF_PROP_SAMPLE_*` -+ * properties given when opening a stream, representing the contents -+ * of a single sample as read() by userspace. -+ */ -+ u32 sample_flags; -+ -+ /** -+ * @sample_size: Considering the configured contents of a sample -+ * combined with the required header size, this is the total size -+ * of a single sample record. -+ */ -+ int sample_size; -+ -+ /** -+ * @ctx: %NULL if measuring system-wide across all contexts or a -+ * specific context that is being monitored. -+ */ -+ struct i915_gem_context *ctx; -+ -+ /** -+ * @enabled: Whether the stream is currently enabled, considering -+ * whether the stream was opened in a disabled state and based -+ * on `I915_PERF_IOCTL_ENABLE` and `I915_PERF_IOCTL_DISABLE` calls. -+ */ -+ bool enabled; -+ -+ /** -+ * @ops: The callbacks providing the implementation of this specific -+ * type of configured stream. -+ */ -+ const struct i915_perf_stream_ops *ops; -+ -+ /** -+ * @oa_config: The OA configuration used by the stream. -+ */ -+ struct i915_oa_config *oa_config; -+}; -+ -+/** -+ * struct i915_oa_ops - Gen specific implementation of an OA unit stream -+ */ -+struct i915_oa_ops { -+ /** -+ * @is_valid_b_counter_reg: Validates register's address for -+ * programming boolean counters for a particular platform. -+ */ -+ bool (*is_valid_b_counter_reg)(struct drm_i915_private *dev_priv, -+ u32 addr); -+ -+ /** -+ * @is_valid_mux_reg: Validates register's address for programming mux -+ * for a particular platform. -+ */ -+ bool (*is_valid_mux_reg)(struct drm_i915_private *dev_priv, u32 addr); -+ -+ /** -+ * @is_valid_flex_reg: Validates register's address for programming -+ * flex EU filtering for a particular platform. -+ */ -+ bool (*is_valid_flex_reg)(struct drm_i915_private *dev_priv, u32 addr); -+ -+ /** -+ * @enable_metric_set: Selects and applies any MUX configuration to set -+ * up the Boolean and Custom (B/C) counters that are part of the -+ * counter reports being sampled. May apply system constraints such as -+ * disabling EU clock gating as required. -+ */ -+ int (*enable_metric_set)(struct i915_perf_stream *stream); -+ -+ /** -+ * @disable_metric_set: Remove system constraints associated with using -+ * the OA unit. -+ */ -+ void (*disable_metric_set)(struct drm_i915_private *dev_priv); -+ -+ /** -+ * @oa_enable: Enable periodic sampling -+ */ -+ void (*oa_enable)(struct i915_perf_stream *stream); -+ -+ /** -+ * @oa_disable: Disable periodic sampling -+ */ -+ void (*oa_disable)(struct i915_perf_stream *stream); -+ -+ /** -+ * @read: Copy data from the circular OA buffer into a given userspace -+ * buffer. -+ */ -+ int (*read)(struct i915_perf_stream *stream, -+ char __user *buf, -+ size_t count, -+ size_t *offset); -+ -+ /** -+ * @oa_hw_tail_read: read the OA tail pointer register -+ * -+ * In particular this enables us to share all the fiddly code for -+ * handling the OA unit tail pointer race that affects multiple -+ * generations. -+ */ -+ u32 (*oa_hw_tail_read)(struct drm_i915_private *dev_priv); -+}; -+ -+struct intel_cdclk_state { -+ unsigned int cdclk, vco, ref, bypass; -+ u8 voltage_level; -+}; -+ -+struct drm_i915_private { -+ struct drm_device drm; -+ -+ const struct intel_device_info __info; /* Use INTEL_INFO() to access. */ -+ struct intel_runtime_info __runtime; /* Use RUNTIME_INFO() to access. */ -+ struct intel_driver_caps caps; -+ -+ /** -+ * Data Stolen Memory - aka "i915 stolen memory" gives us the start and -+ * end of stolen which we can optionally use to create GEM objects -+ * backed by stolen memory. Note that stolen_usable_size tells us -+ * exactly how much of this we are actually allowed to use, given that -+ * some portion of it is in fact reserved for use by hardware functions. -+ */ -+ struct resource dsm; -+ /** -+ * Reseved portion of Data Stolen Memory -+ */ -+ struct resource dsm_reserved; -+ -+ /* -+ * Stolen memory is segmented in hardware with different portions -+ * offlimits to certain functions. -+ * -+ * The drm_mm is initialised to the total accessible range, as found -+ * from the PCI config. On Broadwell+, this is further restricted to -+ * avoid the first page! The upper end of stolen memory is reserved for -+ * hardware functions and similarly removed from the accessible range. -+ */ -+ resource_size_t stolen_usable_size; /* Total size minus reserved ranges */ -+ -+ struct intel_uncore uncore; -+ -+ struct i915_virtual_gpu vgpu; -+ -+ struct intel_gvt *gvt; -+ -+ struct intel_wopcm wopcm; -+ -+ struct intel_huc huc; -+ struct intel_guc guc; -+ -+ struct intel_csr csr; -+ -+ struct intel_gmbus gmbus[GMBUS_NUM_PINS]; -+ -+ /** gmbus_mutex protects against concurrent usage of the single hw gmbus -+ * controller on different i2c buses. */ -+ struct mutex gmbus_mutex; -+ -+ /** -+ * Base address of where the gmbus and gpio blocks are located (either -+ * on PCH or on SoC for platforms without PCH). -+ */ -+ u32 gpio_mmio_base; -+ -+ /* MMIO base address for MIPI regs */ -+ u32 mipi_mmio_base; -+ -+ u32 psr_mmio_base; -+ -+ u32 pps_mmio_base; -+ -+ wait_queue_head_t gmbus_wait_queue; -+ -+ struct pci_dev *bridge_dev; -+ struct intel_engine_cs *engine[I915_NUM_ENGINES]; -+ /* Context used internally to idle the GPU and setup initial state */ -+ struct i915_gem_context *kernel_context; -+ /* Context only to be used for injecting preemption commands */ -+ struct i915_gem_context *preempt_context; -+ struct intel_engine_cs *engine_class[MAX_ENGINE_CLASS + 1] -+ [MAX_ENGINE_INSTANCE + 1]; -+ -+ struct resource mch_res; -+ -+ /* protects the irq masks */ -+ spinlock_t irq_lock; -+ -+ bool display_irqs_enabled; -+ -+ /* To control wakeup latency, e.g. for irq-driven dp aux transfers. */ -+ struct pm_qos_request pm_qos; -+ -+ /* Sideband mailbox protection */ -+ struct mutex sb_lock; -+ -+ /** Cached value of IMR to avoid reads in updating the bitfield */ -+ union { -+ u32 irq_mask; -+ u32 de_irq_mask[I915_MAX_PIPES]; -+ }; -+ u32 gt_irq_mask; -+ u32 pm_imr; -+ u32 pm_ier; -+ u32 pm_rps_events; -+ u32 pm_guc_events; -+ u32 pipestat_irq_mask[I915_MAX_PIPES]; -+ -+ struct i915_hotplug hotplug; -+ struct intel_fbc fbc; -+ struct i915_drrs drrs; -+ struct intel_opregion opregion; -+ struct intel_vbt_data vbt; -+ -+ bool preserve_bios_swizzle; -+ -+ /* overlay */ -+ struct intel_overlay *overlay; -+ -+ /* backlight registers and fields in struct intel_panel */ -+ struct mutex backlight_lock; -+ -+ /* LVDS info */ -+ bool no_aux_handshake; -+ -+ /* protects panel power sequencer state */ -+ struct mutex pps_mutex; -+ -+ struct drm_i915_fence_reg fence_regs[I915_MAX_NUM_FENCES]; /* assume 965 */ -+ int num_fence_regs; /* 8 on pre-965, 16 otherwise */ -+ -+ unsigned int fsb_freq, mem_freq, is_ddr3; -+ unsigned int skl_preferred_vco_freq; -+ unsigned int max_cdclk_freq; -+ -+ unsigned int max_dotclk_freq; -+ unsigned int rawclk_freq; -+ unsigned int hpll_freq; -+ unsigned int fdi_pll_freq; -+ unsigned int czclk_freq; -+ -+ struct { -+ /* -+ * The current logical cdclk state. -+ * See intel_atomic_state.cdclk.logical -+ * -+ * For reading holding any crtc lock is sufficient, -+ * for writing must hold all of them. -+ */ -+ struct intel_cdclk_state logical; -+ /* -+ * The current actual cdclk state. -+ * See intel_atomic_state.cdclk.actual -+ */ -+ struct intel_cdclk_state actual; -+ /* The current hardware cdclk state */ -+ struct intel_cdclk_state hw; -+ -+ int force_min_cdclk; -+ } cdclk; -+ -+ /** -+ * wq - Driver workqueue for GEM. -+ * -+ * NOTE: Work items scheduled here are not allowed to grab any modeset -+ * locks, for otherwise the flushing done in the pageflip code will -+ * result in deadlocks. -+ */ -+ struct workqueue_struct *wq; -+ -+ /* ordered wq for modesets */ -+ struct workqueue_struct *modeset_wq; -+ -+ /* Display functions */ -+ struct drm_i915_display_funcs display; -+ -+ /* PCH chipset type */ -+ enum intel_pch pch_type; -+ unsigned short pch_id; -+ -+ unsigned long quirks; -+ -+ struct drm_atomic_state *modeset_restore_state; -+ struct drm_modeset_acquire_ctx reset_ctx; -+ -+ struct i915_ggtt ggtt; /* VM representing the global address space */ -+ -+ struct i915_gem_mm mm; -+ DECLARE_HASHTABLE(mm_structs, 7); -+ struct mutex mm_lock; -+ -+ struct intel_ppat ppat; -+ -+ /* Kernel Modesetting */ -+ -+ struct intel_crtc *plane_to_crtc_mapping[I915_MAX_PIPES]; -+ struct intel_crtc *pipe_to_crtc_mapping[I915_MAX_PIPES]; -+ -+#ifdef CONFIG_DEBUG_FS -+ struct intel_pipe_crc pipe_crc[I915_MAX_PIPES]; -+#endif -+ -+ /* dpll and cdclk state is protected by connection_mutex */ -+ int num_shared_dpll; -+ struct intel_shared_dpll shared_dplls[I915_NUM_PLLS]; -+ const struct intel_dpll_mgr *dpll_mgr; -+ -+ /* -+ * dpll_lock serializes intel_{prepare,enable,disable}_shared_dpll. -+ * Must be global rather than per dpll, because on some platforms -+ * plls share registers. -+ */ -+ struct mutex dpll_lock; -+ -+ unsigned int active_crtcs; -+ /* minimum acceptable cdclk for each pipe */ -+ int min_cdclk[I915_MAX_PIPES]; -+ /* minimum acceptable voltage level for each pipe */ -+ u8 min_voltage_level[I915_MAX_PIPES]; -+ -+ int dpio_phy_iosf_port[I915_NUM_PHYS_VLV]; -+ -+ struct i915_wa_list gt_wa_list; -+ -+ struct i915_frontbuffer_tracking fb_tracking; -+ -+ struct intel_atomic_helper { -+ struct llist_head free_list; -+ struct work_struct free_work; -+ } atomic_helper; -+ -+ u16 orig_clock; -+ -+ bool mchbar_need_disable; -+ -+ struct intel_l3_parity l3_parity; -+ -+ /* -+ * edram size in MB. -+ * Cannot be determined by PCIID. You must always read a register. -+ */ -+ u32 edram_size_mb; -+ -+ /* -+ * Protects RPS/RC6 register access and PCU communication. -+ * Must be taken after struct_mutex if nested. Note that -+ * this lock may be held for long periods of time when -+ * talking to hw - so only take it when talking to hw! -+ */ -+ struct mutex pcu_lock; -+ -+ /* gen6+ GT PM state */ -+ struct intel_gen6_power_mgmt gt_pm; -+ -+ /* ilk-only ips/rps state. Everything in here is protected by the global -+ * mchdev_lock in intel_pm.c */ -+ struct intel_ilk_power_mgmt ips; -+ -+ struct i915_power_domains power_domains; -+ -+ struct i915_psr psr; -+ -+ struct i915_gpu_error gpu_error; -+ -+ struct drm_i915_gem_object *vlv_pctx; -+ -+ /* list of fbdev register on this device */ -+ struct intel_fbdev *fbdev; -+ struct work_struct fbdev_suspend_work; -+ -+ struct drm_property *broadcast_rgb_property; -+ struct drm_property *force_audio_property; -+ -+ /* hda/i915 audio component */ -+ struct i915_audio_component *audio_component; -+ bool audio_component_registered; -+ /** -+ * av_mutex - mutex for audio/video sync -+ * -+ */ -+ struct mutex av_mutex; -+ int audio_power_refcount; -+ -+ struct { -+ struct mutex mutex; -+ struct list_head list; -+ struct llist_head free_list; -+ struct work_struct free_work; -+ -+ /* The hw wants to have a stable context identifier for the -+ * lifetime of the context (for OA, PASID, faults, etc). -+ * This is limited in execlists to 21 bits. -+ */ -+ struct ida hw_ida; -+#define MAX_CONTEXT_HW_ID (1<<21) /* exclusive */ -+#define MAX_GUC_CONTEXT_HW_ID (1 << 20) /* exclusive */ -+#define GEN11_MAX_CONTEXT_HW_ID (1<<11) /* exclusive */ -+ struct list_head hw_id_list; -+ } contexts; -+ -+ u32 fdi_rx_config; -+ -+ /* Shadow for DISPLAY_PHY_CONTROL which can't be safely read */ -+ u32 chv_phy_control; -+ /* -+ * Shadows for CHV DPLL_MD regs to keep the state -+ * checker somewhat working in the presence hardware -+ * crappiness (can't read out DPLL_MD for pipes B & C). -+ */ -+ u32 chv_dpll_md[I915_MAX_PIPES]; -+ u32 bxt_phy_grc; -+ -+ u32 suspend_count; -+ bool power_domains_suspended; -+ struct i915_suspend_saved_registers regfile; -+ struct vlv_s0ix_state vlv_s0ix_state; -+ -+ enum { -+ I915_SAGV_UNKNOWN = 0, -+ I915_SAGV_DISABLED, -+ I915_SAGV_ENABLED, -+ I915_SAGV_NOT_CONTROLLED -+ } sagv_status; -+ -+ struct { -+ /* -+ * Raw watermark latency values: -+ * in 0.1us units for WM0, -+ * in 0.5us units for WM1+. -+ */ -+ /* primary */ -+ u16 pri_latency[5]; -+ /* sprite */ -+ u16 spr_latency[5]; -+ /* cursor */ -+ u16 cur_latency[5]; -+ /* -+ * Raw watermark memory latency values -+ * for SKL for all 8 levels -+ * in 1us units. -+ */ -+ u16 skl_latency[8]; -+ -+ /* current hardware state */ -+ union { -+ struct ilk_wm_values hw; -+ struct skl_ddb_values skl_hw; -+ struct vlv_wm_values vlv; -+ struct g4x_wm_values g4x; -+ }; -+ -+ u8 max_level; -+ -+ /* -+ * Should be held around atomic WM register writing; also -+ * protects * intel_crtc->wm.active and -+ * cstate->wm.need_postvbl_update. -+ */ -+ struct mutex wm_mutex; -+ -+ /* -+ * Set during HW readout of watermarks/DDB. Some platforms -+ * need to know when we're still using BIOS-provided values -+ * (which we don't fully trust). -+ */ -+ bool distrust_bios_wm; -+ } wm; -+ -+ struct dram_info { -+ bool valid; -+ bool is_16gb_dimm; -+ u8 num_channels; -+ u8 ranks; -+ u32 bandwidth_kbps; -+ bool symmetric_memory; -+ enum intel_dram_type { -+ INTEL_DRAM_UNKNOWN, -+ INTEL_DRAM_DDR3, -+ INTEL_DRAM_DDR4, -+ INTEL_DRAM_LPDDR3, -+ INTEL_DRAM_LPDDR4 -+ } type; -+ } dram_info; -+ -+ struct i915_runtime_pm runtime_pm; -+ -+ struct { -+ bool initialized; -+ -+ struct kobject *metrics_kobj; -+ struct ctl_table_header *sysctl_header; -+ -+ /* -+ * Lock associated with adding/modifying/removing OA configs -+ * in dev_priv->perf.metrics_idr. -+ */ -+ struct mutex metrics_lock; -+ -+ /* -+ * List of dynamic configurations, you need to hold -+ * dev_priv->perf.metrics_lock to access it. -+ */ -+ struct idr metrics_idr; -+ -+ /* -+ * Lock associated with anything below within this structure -+ * except exclusive_stream. -+ */ -+ struct mutex lock; -+ struct list_head streams; -+ -+ struct { -+ /* -+ * The stream currently using the OA unit. If accessed -+ * outside a syscall associated to its file -+ * descriptor, you need to hold -+ * dev_priv->drm.struct_mutex. -+ */ -+ struct i915_perf_stream *exclusive_stream; -+ -+ struct intel_context *pinned_ctx; -+ u32 specific_ctx_id; -+ u32 specific_ctx_id_mask; -+ -+ struct hrtimer poll_check_timer; -+ wait_queue_head_t poll_wq; -+ bool pollin; -+ -+ /** -+ * For rate limiting any notifications of spurious -+ * invalid OA reports -+ */ -+ struct ratelimit_state spurious_report_rs; -+ -+ bool periodic; -+ int period_exponent; -+ -+ struct i915_oa_config test_config; -+ -+ struct { -+ struct i915_vma *vma; -+ u8 *vaddr; -+ u32 last_ctx_id; -+ int format; -+ int format_size; -+ -+ /** -+ * Locks reads and writes to all head/tail state -+ * -+ * Consider: the head and tail pointer state -+ * needs to be read consistently from a hrtimer -+ * callback (atomic context) and read() fop -+ * (user context) with tail pointer updates -+ * happening in atomic context and head updates -+ * in user context and the (unlikely) -+ * possibility of read() errors needing to -+ * reset all head/tail state. -+ * -+ * Note: Contention or performance aren't -+ * currently a significant concern here -+ * considering the relatively low frequency of -+ * hrtimer callbacks (5ms period) and that -+ * reads typically only happen in response to a -+ * hrtimer event and likely complete before the -+ * next callback. -+ * -+ * Note: This lock is not held *while* reading -+ * and copying data to userspace so the value -+ * of head observed in htrimer callbacks won't -+ * represent any partial consumption of data. -+ */ -+ spinlock_t ptr_lock; -+ -+ /** -+ * One 'aging' tail pointer and one 'aged' -+ * tail pointer ready to used for reading. -+ * -+ * Initial values of 0xffffffff are invalid -+ * and imply that an update is required -+ * (and should be ignored by an attempted -+ * read) -+ */ -+ struct { -+ u32 offset; -+ } tails[2]; -+ -+ /** -+ * Index for the aged tail ready to read() -+ * data up to. -+ */ -+ unsigned int aged_tail_idx; -+ -+ /** -+ * A monotonic timestamp for when the current -+ * aging tail pointer was read; used to -+ * determine when it is old enough to trust. -+ */ -+ u64 aging_timestamp; -+ -+ /** -+ * Although we can always read back the head -+ * pointer register, we prefer to avoid -+ * trusting the HW state, just to avoid any -+ * risk that some hardware condition could -+ * somehow bump the head pointer unpredictably -+ * and cause us to forward the wrong OA buffer -+ * data to userspace. -+ */ -+ u32 head; -+ } oa_buffer; -+ -+ u32 gen7_latched_oastatus1; -+ u32 ctx_oactxctrl_offset; -+ u32 ctx_flexeu0_offset; -+ -+ /** -+ * The RPT_ID/reason field for Gen8+ includes a bit -+ * to determine if the CTX ID in the report is valid -+ * but the specific bit differs between Gen 8 and 9 -+ */ -+ u32 gen8_valid_ctx_bit; -+ -+ struct i915_oa_ops ops; -+ const struct i915_oa_format *oa_formats; -+ } oa; -+ } perf; -+ -+ /* Abstract the submission mechanism (legacy ringbuffer or execlists) away */ -+ struct { -+ void (*cleanup_engine)(struct intel_engine_cs *engine); -+ -+ struct i915_gt_timelines { -+ struct mutex mutex; /* protects list, tainted by GPU */ -+ struct list_head active_list; -+ -+ /* Pack multiple timelines' seqnos into the same page */ -+ spinlock_t hwsp_lock; -+ struct list_head hwsp_free_list; -+ } timelines; -+ -+ intel_engine_mask_t active_engines; -+ struct list_head active_rings; -+ struct list_head closed_vma; -+ u32 active_requests; -+ -+ /** -+ * Is the GPU currently considered idle, or busy executing -+ * userspace requests? Whilst idle, we allow runtime power -+ * management to power down the hardware and display clocks. -+ * In order to reduce the effect on performance, there -+ * is a slight delay before we do so. -+ */ -+ intel_wakeref_t awake; -+ -+ /** -+ * We leave the user IRQ off as much as possible, -+ * but this means that requests will finish and never -+ * be retired once the system goes idle. Set a timer to -+ * fire periodically while the ring is running. When it -+ * fires, go retire requests. -+ */ -+ struct delayed_work retire_work; -+ -+ /** -+ * When we detect an idle GPU, we want to turn on -+ * powersaving features. So once we see that there -+ * are no more requests outstanding and no more -+ * arrive within a small period of time, we fire -+ * off the idle_work. -+ */ -+ struct delayed_work idle_work; -+ -+ ktime_t last_init_time; -+ -+ struct i915_vma *scratch; -+ } gt; -+ -+ /* For i945gm vblank irq vs. C3 workaround */ -+ struct { -+ struct work_struct work; -+ struct pm_qos_request pm_qos; -+ u8 c3_disable_latency; -+ u8 enabled; -+ } i945gm_vblank; -+ -+ /* perform PHY state sanity checks? */ -+ bool chv_phy_assert[2]; -+ -+ bool ipc_enabled; -+ -+ /* Used to save the pipe-to-encoder mapping for audio */ -+ struct intel_encoder *av_enc_map[I915_MAX_PIPES]; -+ -+ /* necessary resource sharing with HDMI LPE audio driver. */ -+ struct { -+ struct platform_device *platdev; -+ int irq; -+ } lpe_audio; -+ -+ struct i915_pmu pmu; -+ -+ struct i915_hdcp_comp_master *hdcp_master; -+ bool hdcp_comp_added; -+ -+ /* Mutex to protect the above hdcp component related values. */ -+ struct mutex hdcp_comp_mutex; -+ -+ /* -+ * NOTE: This is the dri1/ums dungeon, don't add stuff here. Your patch -+ * will be rejected. Instead look for a better place. -+ */ -+}; -+ -+struct dram_dimm_info { -+ u8 size, width, ranks; -+}; -+ -+struct dram_channel_info { -+ struct dram_dimm_info dimm_l, dimm_s; -+ u8 ranks; -+ bool is_16gb_dimm; -+}; -+ -+static inline struct drm_i915_private *to_i915(const struct drm_device *dev) -+{ -+ return container_of(dev, struct drm_i915_private, drm); -+} -+ -+static inline struct drm_i915_private *kdev_to_i915(struct device *kdev) -+{ -+ return to_i915(dev_get_drvdata(kdev)); -+} -+ -+static inline struct drm_i915_private *wopcm_to_i915(struct intel_wopcm *wopcm) -+{ -+ return container_of(wopcm, struct drm_i915_private, wopcm); -+} -+ -+static inline struct drm_i915_private *guc_to_i915(struct intel_guc *guc) -+{ -+ return container_of(guc, struct drm_i915_private, guc); -+} -+ -+static inline struct drm_i915_private *huc_to_i915(struct intel_huc *huc) -+{ -+ return container_of(huc, struct drm_i915_private, huc); -+} -+ -+static inline struct drm_i915_private *uncore_to_i915(struct intel_uncore *uncore) -+{ -+ return container_of(uncore, struct drm_i915_private, uncore); -+} -+ -+/* Simple iterator over all initialised engines */ -+#define for_each_engine(engine__, dev_priv__, id__) \ -+ for ((id__) = 0; \ -+ (id__) < I915_NUM_ENGINES; \ -+ (id__)++) \ -+ for_each_if ((engine__) = (dev_priv__)->engine[(id__)]) -+ -+/* Iterator over subset of engines selected by mask */ -+#define for_each_engine_masked(engine__, dev_priv__, mask__, tmp__) \ -+ for ((tmp__) = (mask__) & INTEL_INFO(dev_priv__)->engine_mask; \ -+ (tmp__) ? \ -+ ((engine__) = (dev_priv__)->engine[__mask_next_bit(tmp__)]), 1 : \ -+ 0;) -+ -+enum hdmi_force_audio { -+ HDMI_AUDIO_OFF_DVI = -2, /* no aux data for HDMI-DVI converter */ -+ HDMI_AUDIO_OFF, /* force turn off HDMI audio */ -+ HDMI_AUDIO_AUTO, /* trust EDID */ -+ HDMI_AUDIO_ON, /* force turn on HDMI audio */ -+}; -+ -+#define I915_GTT_OFFSET_NONE ((u32)-1) -+ -+/* -+ * Frontbuffer tracking bits. Set in obj->frontbuffer_bits while a gem bo is -+ * considered to be the frontbuffer for the given plane interface-wise. This -+ * doesn't mean that the hw necessarily already scans it out, but that any -+ * rendering (by the cpu or gpu) will land in the frontbuffer eventually. -+ * -+ * We have one bit per pipe and per scanout plane type. -+ */ -+#define INTEL_FRONTBUFFER_BITS_PER_PIPE 8 -+#define INTEL_FRONTBUFFER(pipe, plane_id) ({ \ -+ BUILD_BUG_ON(INTEL_FRONTBUFFER_BITS_PER_PIPE * I915_MAX_PIPES > 32); \ -+ BUILD_BUG_ON(I915_MAX_PLANES > INTEL_FRONTBUFFER_BITS_PER_PIPE); \ -+ BIT((plane_id) + INTEL_FRONTBUFFER_BITS_PER_PIPE * (pipe)); \ -+}) -+#define INTEL_FRONTBUFFER_OVERLAY(pipe) \ -+ BIT(INTEL_FRONTBUFFER_BITS_PER_PIPE - 1 + INTEL_FRONTBUFFER_BITS_PER_PIPE * (pipe)) -+#define INTEL_FRONTBUFFER_ALL_MASK(pipe) \ -+ GENMASK(INTEL_FRONTBUFFER_BITS_PER_PIPE * ((pipe) + 1) - 1, \ -+ INTEL_FRONTBUFFER_BITS_PER_PIPE * (pipe)) -+ -+/* -+ * Optimised SGL iterator for GEM objects -+ */ -+static __always_inline struct sgt_iter { -+ struct scatterlist *sgp; -+ union { -+ unsigned long pfn; -+ dma_addr_t dma; -+ }; -+ unsigned int curr; -+ unsigned int max; -+} __sgt_iter(struct scatterlist *sgl, bool dma) { -+ struct sgt_iter s = { .sgp = sgl }; -+ -+ if (s.sgp) { -+ s.max = s.curr = s.sgp->offset; -+ s.max += s.sgp->length; -+ if (dma) -+ s.dma = sg_dma_address(s.sgp); -+ else -+ s.pfn = page_to_pfn(sg_page(s.sgp)); -+ } -+ -+ return s; -+} -+ -+static inline struct scatterlist *____sg_next(struct scatterlist *sg) -+{ -+ ++sg; -+ if (unlikely(sg_is_chain(sg))) -+ sg = sg_chain_ptr(sg); -+ return sg; -+} -+ -+/** -+ * __sg_next - return the next scatterlist entry in a list -+ * @sg: The current sg entry -+ * -+ * Description: -+ * If the entry is the last, return NULL; otherwise, step to the next -+ * element in the array (@sg@+1). If that's a chain pointer, follow it; -+ * otherwise just return the pointer to the current element. -+ **/ -+static inline struct scatterlist *__sg_next(struct scatterlist *sg) -+{ -+ return sg_is_last(sg) ? NULL : ____sg_next(sg); -+} -+ -+/** -+ * for_each_sgt_dma - iterate over the DMA addresses of the given sg_table -+ * @__dmap: DMA address (output) -+ * @__iter: 'struct sgt_iter' (iterator state, internal) -+ * @__sgt: sg_table to iterate over (input) -+ */ -+#define for_each_sgt_dma(__dmap, __iter, __sgt) \ -+ for ((__iter) = __sgt_iter((__sgt)->sgl, true); \ -+ ((__dmap) = (__iter).dma + (__iter).curr); \ -+ (((__iter).curr += I915_GTT_PAGE_SIZE) >= (__iter).max) ? \ -+ (__iter) = __sgt_iter(__sg_next((__iter).sgp), true), 0 : 0) -+ -+/** -+ * for_each_sgt_page - iterate over the pages of the given sg_table -+ * @__pp: page pointer (output) -+ * @__iter: 'struct sgt_iter' (iterator state, internal) -+ * @__sgt: sg_table to iterate over (input) -+ */ -+#define for_each_sgt_page(__pp, __iter, __sgt) \ -+ for ((__iter) = __sgt_iter((__sgt)->sgl, false); \ -+ ((__pp) = (__iter).pfn == 0 ? NULL : \ -+ pfn_to_page((__iter).pfn + ((__iter).curr >> PAGE_SHIFT))); \ -+ (((__iter).curr += PAGE_SIZE) >= (__iter).max) ? \ -+ (__iter) = __sgt_iter(__sg_next((__iter).sgp), false), 0 : 0) -+ -+bool i915_sg_trim(struct sg_table *orig_st); -+ -+static inline unsigned int i915_sg_page_sizes(struct scatterlist *sg) -+{ -+ unsigned int page_sizes; -+ -+ page_sizes = 0; -+ while (sg) { -+ GEM_BUG_ON(sg->offset); -+ GEM_BUG_ON(!IS_ALIGNED(sg->length, PAGE_SIZE)); -+ page_sizes |= sg->length; -+ sg = __sg_next(sg); -+ } -+ -+ return page_sizes; -+} -+ -+static inline unsigned int i915_sg_segment_size(void) -+{ -+ unsigned int size = swiotlb_max_segment(); -+ -+ if (size == 0) -+ return SCATTERLIST_MAX_SEGMENT; -+ -+ size = rounddown(size, PAGE_SIZE); -+ /* swiotlb_max_segment_size can return 1 byte when it means one page. */ -+ if (size < PAGE_SIZE) -+ size = PAGE_SIZE; -+ -+ return size; -+} -+ -+#define INTEL_INFO(dev_priv) (&(dev_priv)->__info) -+#define RUNTIME_INFO(dev_priv) (&(dev_priv)->__runtime) -+#define DRIVER_CAPS(dev_priv) (&(dev_priv)->caps) -+ -+#define INTEL_GEN(dev_priv) (INTEL_INFO(dev_priv)->gen) -+#define INTEL_DEVID(dev_priv) (RUNTIME_INFO(dev_priv)->device_id) -+ -+#define REVID_FOREVER 0xff -+#define INTEL_REVID(dev_priv) ((dev_priv)->drm.pdev->revision) -+ -+#define INTEL_GEN_MASK(s, e) ( \ -+ BUILD_BUG_ON_ZERO(!__builtin_constant_p(s)) + \ -+ BUILD_BUG_ON_ZERO(!__builtin_constant_p(e)) + \ -+ GENMASK((e) - 1, (s) - 1)) -+ -+/* Returns true if Gen is in inclusive range [Start, End] */ -+#define IS_GEN_RANGE(dev_priv, s, e) \ -+ (!!(INTEL_INFO(dev_priv)->gen_mask & INTEL_GEN_MASK((s), (e)))) -+ -+#define IS_GEN(dev_priv, n) \ -+ (BUILD_BUG_ON_ZERO(!__builtin_constant_p(n)) + \ -+ INTEL_INFO(dev_priv)->gen == (n)) -+ -+/* -+ * Return true if revision is in range [since,until] inclusive. -+ * -+ * Use 0 for open-ended since, and REVID_FOREVER for open-ended until. -+ */ -+#define IS_REVID(p, since, until) \ -+ (INTEL_REVID(p) >= (since) && INTEL_REVID(p) <= (until)) -+ -+static __always_inline unsigned int -+__platform_mask_index(const struct intel_runtime_info *info, -+ enum intel_platform p) -+{ -+ const unsigned int pbits = -+ BITS_PER_TYPE(info->platform_mask[0]) - INTEL_SUBPLATFORM_BITS; -+ -+ /* Expand the platform_mask array if this fails. */ -+ BUILD_BUG_ON(INTEL_MAX_PLATFORMS > -+ pbits * ARRAY_SIZE(info->platform_mask)); -+ -+ return p / pbits; -+} -+ -+static __always_inline unsigned int -+__platform_mask_bit(const struct intel_runtime_info *info, -+ enum intel_platform p) -+{ -+ const unsigned int pbits = -+ BITS_PER_TYPE(info->platform_mask[0]) - INTEL_SUBPLATFORM_BITS; -+ -+ return p % pbits + INTEL_SUBPLATFORM_BITS; -+} -+ -+static inline u32 -+intel_subplatform(const struct intel_runtime_info *info, enum intel_platform p) -+{ -+ const unsigned int pi = __platform_mask_index(info, p); -+ -+ return info->platform_mask[pi] & INTEL_SUBPLATFORM_BITS; -+} -+ -+static __always_inline bool -+IS_PLATFORM(const struct drm_i915_private *i915, enum intel_platform p) -+{ -+ const struct intel_runtime_info *info = RUNTIME_INFO(i915); -+ const unsigned int pi = __platform_mask_index(info, p); -+ const unsigned int pb = __platform_mask_bit(info, p); -+ -+ BUILD_BUG_ON(!__builtin_constant_p(p)); -+ -+ return info->platform_mask[pi] & BIT(pb); -+} -+ -+static __always_inline bool -+IS_SUBPLATFORM(const struct drm_i915_private *i915, -+ enum intel_platform p, unsigned int s) -+{ -+ const struct intel_runtime_info *info = RUNTIME_INFO(i915); -+ const unsigned int pi = __platform_mask_index(info, p); -+ const unsigned int pb = __platform_mask_bit(info, p); -+ const unsigned int msb = BITS_PER_TYPE(info->platform_mask[0]) - 1; -+ const u32 mask = info->platform_mask[pi]; -+ -+ BUILD_BUG_ON(!__builtin_constant_p(p)); -+ BUILD_BUG_ON(!__builtin_constant_p(s)); -+ BUILD_BUG_ON((s) >= INTEL_SUBPLATFORM_BITS); -+ -+ /* Shift and test on the MSB position so sign flag can be used. */ -+ return ((mask << (msb - pb)) & (mask << (msb - s))) & BIT(msb); -+} -+ -+#define IS_MOBILE(dev_priv) (INTEL_INFO(dev_priv)->is_mobile) -+ -+#define IS_I830(dev_priv) IS_PLATFORM(dev_priv, INTEL_I830) -+#define IS_I845G(dev_priv) IS_PLATFORM(dev_priv, INTEL_I845G) -+#define IS_I85X(dev_priv) IS_PLATFORM(dev_priv, INTEL_I85X) -+#define IS_I865G(dev_priv) IS_PLATFORM(dev_priv, INTEL_I865G) -+#define IS_I915G(dev_priv) IS_PLATFORM(dev_priv, INTEL_I915G) -+#define IS_I915GM(dev_priv) IS_PLATFORM(dev_priv, INTEL_I915GM) -+#define IS_I945G(dev_priv) IS_PLATFORM(dev_priv, INTEL_I945G) -+#define IS_I945GM(dev_priv) IS_PLATFORM(dev_priv, INTEL_I945GM) -+#define IS_I965G(dev_priv) IS_PLATFORM(dev_priv, INTEL_I965G) -+#define IS_I965GM(dev_priv) IS_PLATFORM(dev_priv, INTEL_I965GM) -+#define IS_G45(dev_priv) IS_PLATFORM(dev_priv, INTEL_G45) -+#define IS_GM45(dev_priv) IS_PLATFORM(dev_priv, INTEL_GM45) -+#define IS_G4X(dev_priv) (IS_G45(dev_priv) || IS_GM45(dev_priv)) -+#define IS_PINEVIEW(dev_priv) IS_PLATFORM(dev_priv, INTEL_PINEVIEW) -+#define IS_G33(dev_priv) IS_PLATFORM(dev_priv, INTEL_G33) -+#define IS_IRONLAKE(dev_priv) IS_PLATFORM(dev_priv, INTEL_IRONLAKE) -+#define IS_IRONLAKE_M(dev_priv) \ -+ (IS_PLATFORM(dev_priv, INTEL_IRONLAKE) && IS_MOBILE(dev_priv)) -+#define IS_IVYBRIDGE(dev_priv) IS_PLATFORM(dev_priv, INTEL_IVYBRIDGE) -+#define IS_IVB_GT1(dev_priv) (IS_IVYBRIDGE(dev_priv) && \ -+ INTEL_INFO(dev_priv)->gt == 1) -+#define IS_VALLEYVIEW(dev_priv) IS_PLATFORM(dev_priv, INTEL_VALLEYVIEW) -+#define IS_CHERRYVIEW(dev_priv) IS_PLATFORM(dev_priv, INTEL_CHERRYVIEW) -+#define IS_HASWELL(dev_priv) IS_PLATFORM(dev_priv, INTEL_HASWELL) -+#define IS_BROADWELL(dev_priv) IS_PLATFORM(dev_priv, INTEL_BROADWELL) -+#define IS_SKYLAKE(dev_priv) IS_PLATFORM(dev_priv, INTEL_SKYLAKE) -+#define IS_BROXTON(dev_priv) IS_PLATFORM(dev_priv, INTEL_BROXTON) -+#define IS_KABYLAKE(dev_priv) IS_PLATFORM(dev_priv, INTEL_KABYLAKE) -+#define IS_GEMINILAKE(dev_priv) IS_PLATFORM(dev_priv, INTEL_GEMINILAKE) -+#define IS_COFFEELAKE(dev_priv) IS_PLATFORM(dev_priv, INTEL_COFFEELAKE) -+#define IS_CANNONLAKE(dev_priv) IS_PLATFORM(dev_priv, INTEL_CANNONLAKE) -+#define IS_ICELAKE(dev_priv) IS_PLATFORM(dev_priv, INTEL_ICELAKE) -+#define IS_ELKHARTLAKE(dev_priv) IS_PLATFORM(dev_priv, INTEL_ELKHARTLAKE) -+#define IS_HSW_EARLY_SDV(dev_priv) (IS_HASWELL(dev_priv) && \ -+ (INTEL_DEVID(dev_priv) & 0xFF00) == 0x0C00) -+#define IS_BDW_ULT(dev_priv) \ -+ IS_SUBPLATFORM(dev_priv, INTEL_BROADWELL, INTEL_SUBPLATFORM_ULT) -+#define IS_BDW_ULX(dev_priv) \ -+ IS_SUBPLATFORM(dev_priv, INTEL_BROADWELL, INTEL_SUBPLATFORM_ULX) -+#define IS_BDW_GT3(dev_priv) (IS_BROADWELL(dev_priv) && \ -+ INTEL_INFO(dev_priv)->gt == 3) -+#define IS_HSW_ULT(dev_priv) \ -+ IS_SUBPLATFORM(dev_priv, INTEL_HASWELL, INTEL_SUBPLATFORM_ULT) -+#define IS_HSW_GT3(dev_priv) (IS_HASWELL(dev_priv) && \ -+ INTEL_INFO(dev_priv)->gt == 3) -+#define IS_HSW_GT1(dev_priv) (IS_HASWELL(dev_priv) && \ -+ INTEL_INFO(dev_priv)->gt == 1) -+/* ULX machines are also considered ULT. */ -+#define IS_HSW_ULX(dev_priv) \ -+ IS_SUBPLATFORM(dev_priv, INTEL_HASWELL, INTEL_SUBPLATFORM_ULX) -+#define IS_SKL_ULT(dev_priv) \ -+ IS_SUBPLATFORM(dev_priv, INTEL_SKYLAKE, INTEL_SUBPLATFORM_ULT) -+#define IS_SKL_ULX(dev_priv) \ -+ IS_SUBPLATFORM(dev_priv, INTEL_SKYLAKE, INTEL_SUBPLATFORM_ULX) -+#define IS_KBL_ULT(dev_priv) \ -+ IS_SUBPLATFORM(dev_priv, INTEL_KABYLAKE, INTEL_SUBPLATFORM_ULT) -+#define IS_KBL_ULX(dev_priv) \ -+ IS_SUBPLATFORM(dev_priv, INTEL_KABYLAKE, INTEL_SUBPLATFORM_ULX) -+#define IS_AML_ULX(dev_priv) \ -+ (IS_SUBPLATFORM(dev_priv, INTEL_KABYLAKE, INTEL_SUBPLATFORM_AML) || \ -+ IS_SUBPLATFORM(dev_priv, INTEL_COFFEELAKE, INTEL_SUBPLATFORM_AML)) -+#define IS_SKL_GT2(dev_priv) (IS_SKYLAKE(dev_priv) && \ -+ INTEL_INFO(dev_priv)->gt == 2) -+#define IS_SKL_GT3(dev_priv) (IS_SKYLAKE(dev_priv) && \ -+ INTEL_INFO(dev_priv)->gt == 3) -+#define IS_SKL_GT4(dev_priv) (IS_SKYLAKE(dev_priv) && \ -+ INTEL_INFO(dev_priv)->gt == 4) -+#define IS_KBL_GT2(dev_priv) (IS_KABYLAKE(dev_priv) && \ -+ INTEL_INFO(dev_priv)->gt == 2) -+#define IS_KBL_GT3(dev_priv) (IS_KABYLAKE(dev_priv) && \ -+ INTEL_INFO(dev_priv)->gt == 3) -+#define IS_CFL_ULT(dev_priv) \ -+ IS_SUBPLATFORM(dev_priv, INTEL_COFFEELAKE, INTEL_SUBPLATFORM_ULT) -+#define IS_CFL_GT2(dev_priv) (IS_COFFEELAKE(dev_priv) && \ -+ INTEL_INFO(dev_priv)->gt == 2) -+#define IS_CFL_GT3(dev_priv) (IS_COFFEELAKE(dev_priv) && \ -+ INTEL_INFO(dev_priv)->gt == 3) -+#define IS_CNL_WITH_PORT_F(dev_priv) \ -+ IS_SUBPLATFORM(dev_priv, INTEL_CANNONLAKE, INTEL_SUBPLATFORM_PORTF) -+#define IS_ICL_WITH_PORT_F(dev_priv) \ -+ IS_SUBPLATFORM(dev_priv, INTEL_ICELAKE, INTEL_SUBPLATFORM_PORTF) -+ -+#define IS_ALPHA_SUPPORT(intel_info) ((intel_info)->is_alpha_support) -+ -+#define SKL_REVID_A0 0x0 -+#define SKL_REVID_B0 0x1 -+#define SKL_REVID_C0 0x2 -+#define SKL_REVID_D0 0x3 -+#define SKL_REVID_E0 0x4 -+#define SKL_REVID_F0 0x5 -+#define SKL_REVID_G0 0x6 -+#define SKL_REVID_H0 0x7 -+ -+#define IS_SKL_REVID(p, since, until) (IS_SKYLAKE(p) && IS_REVID(p, since, until)) -+ -+#define BXT_REVID_A0 0x0 -+#define BXT_REVID_A1 0x1 -+#define BXT_REVID_B0 0x3 -+#define BXT_REVID_B_LAST 0x8 -+#define BXT_REVID_C0 0x9 -+ -+#define IS_BXT_REVID(dev_priv, since, until) \ -+ (IS_BROXTON(dev_priv) && IS_REVID(dev_priv, since, until)) -+ -+#define KBL_REVID_A0 0x0 -+#define KBL_REVID_B0 0x1 -+#define KBL_REVID_C0 0x2 -+#define KBL_REVID_D0 0x3 -+#define KBL_REVID_E0 0x4 -+ -+#define IS_KBL_REVID(dev_priv, since, until) \ -+ (IS_KABYLAKE(dev_priv) && IS_REVID(dev_priv, since, until)) -+ -+#define GLK_REVID_A0 0x0 -+#define GLK_REVID_A1 0x1 -+ -+#define IS_GLK_REVID(dev_priv, since, until) \ -+ (IS_GEMINILAKE(dev_priv) && IS_REVID(dev_priv, since, until)) -+ -+#define CNL_REVID_A0 0x0 -+#define CNL_REVID_B0 0x1 -+#define CNL_REVID_C0 0x2 -+ -+#define IS_CNL_REVID(p, since, until) \ -+ (IS_CANNONLAKE(p) && IS_REVID(p, since, until)) -+ -+#define ICL_REVID_A0 0x0 -+#define ICL_REVID_A2 0x1 -+#define ICL_REVID_B0 0x3 -+#define ICL_REVID_B2 0x4 -+#define ICL_REVID_C0 0x5 -+ -+#define IS_ICL_REVID(p, since, until) \ -+ (IS_ICELAKE(p) && IS_REVID(p, since, until)) -+ -+#define IS_LP(dev_priv) (INTEL_INFO(dev_priv)->is_lp) -+#define IS_GEN9_LP(dev_priv) (IS_GEN(dev_priv, 9) && IS_LP(dev_priv)) -+#define IS_GEN9_BC(dev_priv) (IS_GEN(dev_priv, 9) && !IS_LP(dev_priv)) -+ -+#define HAS_ENGINE(dev_priv, id) (INTEL_INFO(dev_priv)->engine_mask & BIT(id)) -+ -+#define ENGINE_INSTANCES_MASK(dev_priv, first, count) ({ \ -+ unsigned int first__ = (first); \ -+ unsigned int count__ = (count); \ -+ (INTEL_INFO(dev_priv)->engine_mask & \ -+ GENMASK(first__ + count__ - 1, first__)) >> first__; \ -+}) -+#define VDBOX_MASK(dev_priv) \ -+ ENGINE_INSTANCES_MASK(dev_priv, VCS0, I915_MAX_VCS) -+#define VEBOX_MASK(dev_priv) \ -+ ENGINE_INSTANCES_MASK(dev_priv, VECS0, I915_MAX_VECS) -+ -+#define HAS_LLC(dev_priv) (INTEL_INFO(dev_priv)->has_llc) -+#define HAS_SNOOP(dev_priv) (INTEL_INFO(dev_priv)->has_snoop) -+#define HAS_EDRAM(dev_priv) ((dev_priv)->edram_size_mb) -+#define HAS_WT(dev_priv) ((IS_HASWELL(dev_priv) || \ -+ IS_BROADWELL(dev_priv)) && HAS_EDRAM(dev_priv)) -+ -+#define HWS_NEEDS_PHYSICAL(dev_priv) (INTEL_INFO(dev_priv)->hws_needs_physical) -+ -+#define HAS_LOGICAL_RING_CONTEXTS(dev_priv) \ -+ (INTEL_INFO(dev_priv)->has_logical_ring_contexts) -+#define HAS_LOGICAL_RING_ELSQ(dev_priv) \ -+ (INTEL_INFO(dev_priv)->has_logical_ring_elsq) -+#define HAS_LOGICAL_RING_PREEMPTION(dev_priv) \ -+ (INTEL_INFO(dev_priv)->has_logical_ring_preemption) -+ -+#define HAS_EXECLISTS(dev_priv) HAS_LOGICAL_RING_CONTEXTS(dev_priv) -+ -+#define INTEL_PPGTT(dev_priv) (INTEL_INFO(dev_priv)->ppgtt_type) -+#define HAS_PPGTT(dev_priv) \ -+ (INTEL_PPGTT(dev_priv) != INTEL_PPGTT_NONE) -+#define HAS_FULL_PPGTT(dev_priv) \ -+ (INTEL_PPGTT(dev_priv) >= INTEL_PPGTT_FULL) -+ -+#define HAS_PAGE_SIZES(dev_priv, sizes) ({ \ -+ GEM_BUG_ON((sizes) == 0); \ -+ ((sizes) & ~INTEL_INFO(dev_priv)->page_sizes) == 0; \ -+}) -+ -+#define HAS_OVERLAY(dev_priv) (INTEL_INFO(dev_priv)->display.has_overlay) -+#define OVERLAY_NEEDS_PHYSICAL(dev_priv) \ -+ (INTEL_INFO(dev_priv)->display.overlay_needs_physical) -+ -+/* Early gen2 have a totally busted CS tlb and require pinned batches. */ -+#define HAS_BROKEN_CS_TLB(dev_priv) (IS_I830(dev_priv) || IS_I845G(dev_priv)) -+ -+/* WaRsDisableCoarsePowerGating:skl,cnl */ -+#define NEEDS_WaRsDisableCoarsePowerGating(dev_priv) \ -+ (IS_CANNONLAKE(dev_priv) || \ -+ IS_SKL_GT3(dev_priv) || IS_SKL_GT4(dev_priv)) -+ -+#define HAS_GMBUS_IRQ(dev_priv) (INTEL_GEN(dev_priv) >= 4) -+#define HAS_GMBUS_BURST_READ(dev_priv) (INTEL_GEN(dev_priv) >= 10 || \ -+ IS_GEMINILAKE(dev_priv) || \ -+ IS_KABYLAKE(dev_priv)) -+ -+/* With the 945 and later, Y tiling got adjusted so that it was 32 128-byte -+ * rows, which changed the alignment requirements and fence programming. -+ */ -+#define HAS_128_BYTE_Y_TILING(dev_priv) (!IS_GEN(dev_priv, 2) && \ -+ !(IS_I915G(dev_priv) || \ -+ IS_I915GM(dev_priv))) -+#define SUPPORTS_TV(dev_priv) (INTEL_INFO(dev_priv)->display.supports_tv) -+#define I915_HAS_HOTPLUG(dev_priv) (INTEL_INFO(dev_priv)->display.has_hotplug) -+ -+#define HAS_FW_BLC(dev_priv) (INTEL_GEN(dev_priv) > 2) -+#define HAS_FBC(dev_priv) (INTEL_INFO(dev_priv)->display.has_fbc) -+#define HAS_CUR_FBC(dev_priv) (!HAS_GMCH(dev_priv) && INTEL_GEN(dev_priv) >= 7) -+ -+#define HAS_IPS(dev_priv) (IS_HSW_ULT(dev_priv) || IS_BROADWELL(dev_priv)) -+ -+#define HAS_DP_MST(dev_priv) (INTEL_INFO(dev_priv)->display.has_dp_mst) -+ -+#define HAS_DDI(dev_priv) (INTEL_INFO(dev_priv)->display.has_ddi) -+#define HAS_FPGA_DBG_UNCLAIMED(dev_priv) (INTEL_INFO(dev_priv)->has_fpga_dbg) -+#define HAS_PSR(dev_priv) (INTEL_INFO(dev_priv)->display.has_psr) -+#define HAS_TRANSCODER_EDP(dev_priv) (INTEL_INFO(dev_priv)->trans_offsets[TRANSCODER_EDP] != 0) -+ -+#define HAS_RC6(dev_priv) (INTEL_INFO(dev_priv)->has_rc6) -+#define HAS_RC6p(dev_priv) (INTEL_INFO(dev_priv)->has_rc6p) -+#define HAS_RC6pp(dev_priv) (false) /* HW was never validated */ -+ -+#define HAS_CSR(dev_priv) (INTEL_INFO(dev_priv)->display.has_csr) -+ -+#define HAS_RUNTIME_PM(dev_priv) (INTEL_INFO(dev_priv)->has_runtime_pm) -+#define HAS_64BIT_RELOC(dev_priv) (INTEL_INFO(dev_priv)->has_64bit_reloc) -+ -+#define HAS_IPC(dev_priv) (INTEL_INFO(dev_priv)->display.has_ipc) -+ -+/* -+ * For now, anything with a GuC requires uCode loading, and then supports -+ * command submission once loaded. But these are logically independent -+ * properties, so we have separate macros to test them. -+ */ -+#define HAS_GUC(dev_priv) (INTEL_INFO(dev_priv)->has_guc) -+#define HAS_GUC_CT(dev_priv) (INTEL_INFO(dev_priv)->has_guc_ct) -+#define HAS_GUC_UCODE(dev_priv) (HAS_GUC(dev_priv)) -+#define HAS_GUC_SCHED(dev_priv) (HAS_GUC(dev_priv)) -+ -+/* For now, anything with a GuC has also HuC */ -+#define HAS_HUC(dev_priv) (HAS_GUC(dev_priv)) -+#define HAS_HUC_UCODE(dev_priv) (HAS_GUC(dev_priv)) -+ -+/* Having a GuC is not the same as using a GuC */ -+#define USES_GUC(dev_priv) intel_uc_is_using_guc(dev_priv) -+#define USES_GUC_SUBMISSION(dev_priv) intel_uc_is_using_guc_submission(dev_priv) -+#define USES_HUC(dev_priv) intel_uc_is_using_huc(dev_priv) -+ -+#define HAS_POOLED_EU(dev_priv) (INTEL_INFO(dev_priv)->has_pooled_eu) -+ -+#define INTEL_PCH_DEVICE_ID_MASK 0xff80 -+#define INTEL_PCH_IBX_DEVICE_ID_TYPE 0x3b00 -+#define INTEL_PCH_CPT_DEVICE_ID_TYPE 0x1c00 -+#define INTEL_PCH_PPT_DEVICE_ID_TYPE 0x1e00 -+#define INTEL_PCH_LPT_DEVICE_ID_TYPE 0x8c00 -+#define INTEL_PCH_LPT_LP_DEVICE_ID_TYPE 0x9c00 -+#define INTEL_PCH_WPT_DEVICE_ID_TYPE 0x8c80 -+#define INTEL_PCH_WPT_LP_DEVICE_ID_TYPE 0x9c80 -+#define INTEL_PCH_SPT_DEVICE_ID_TYPE 0xA100 -+#define INTEL_PCH_SPT_LP_DEVICE_ID_TYPE 0x9D00 -+#define INTEL_PCH_KBP_DEVICE_ID_TYPE 0xA280 -+#define INTEL_PCH_CNP_DEVICE_ID_TYPE 0xA300 -+#define INTEL_PCH_CNP_LP_DEVICE_ID_TYPE 0x9D80 -+#define INTEL_PCH_CMP_DEVICE_ID_TYPE 0x0280 -+#define INTEL_PCH_ICP_DEVICE_ID_TYPE 0x3480 -+#define INTEL_PCH_P2X_DEVICE_ID_TYPE 0x7100 -+#define INTEL_PCH_P3X_DEVICE_ID_TYPE 0x7000 -+#define INTEL_PCH_QEMU_DEVICE_ID_TYPE 0x2900 /* qemu q35 has 2918 */ -+ -+#define INTEL_PCH_TYPE(dev_priv) ((dev_priv)->pch_type) -+#define INTEL_PCH_ID(dev_priv) ((dev_priv)->pch_id) -+#define HAS_PCH_ICP(dev_priv) (INTEL_PCH_TYPE(dev_priv) == PCH_ICP) -+#define HAS_PCH_CNP(dev_priv) (INTEL_PCH_TYPE(dev_priv) == PCH_CNP) -+#define HAS_PCH_KBP(dev_priv) (INTEL_PCH_TYPE(dev_priv) == PCH_KBP) -+#define HAS_PCH_SPT(dev_priv) (INTEL_PCH_TYPE(dev_priv) == PCH_SPT) -+#define HAS_PCH_LPT(dev_priv) (INTEL_PCH_TYPE(dev_priv) == PCH_LPT) -+#define HAS_PCH_LPT_LP(dev_priv) \ -+ (INTEL_PCH_ID(dev_priv) == INTEL_PCH_LPT_LP_DEVICE_ID_TYPE || \ -+ INTEL_PCH_ID(dev_priv) == INTEL_PCH_WPT_LP_DEVICE_ID_TYPE) -+#define HAS_PCH_LPT_H(dev_priv) \ -+ (INTEL_PCH_ID(dev_priv) == INTEL_PCH_LPT_DEVICE_ID_TYPE || \ -+ INTEL_PCH_ID(dev_priv) == INTEL_PCH_WPT_DEVICE_ID_TYPE) -+#define HAS_PCH_CPT(dev_priv) (INTEL_PCH_TYPE(dev_priv) == PCH_CPT) -+#define HAS_PCH_IBX(dev_priv) (INTEL_PCH_TYPE(dev_priv) == PCH_IBX) -+#define HAS_PCH_NOP(dev_priv) (INTEL_PCH_TYPE(dev_priv) == PCH_NOP) -+#define HAS_PCH_SPLIT(dev_priv) (INTEL_PCH_TYPE(dev_priv) != PCH_NONE) -+ -+#define HAS_GMCH(dev_priv) (INTEL_INFO(dev_priv)->display.has_gmch) -+ -+#define HAS_LSPCON(dev_priv) (INTEL_GEN(dev_priv) >= 9) -+ -+/* DPF == dynamic parity feature */ -+#define HAS_L3_DPF(dev_priv) (INTEL_INFO(dev_priv)->has_l3_dpf) -+#define NUM_L3_SLICES(dev_priv) (IS_HSW_GT3(dev_priv) ? \ -+ 2 : HAS_L3_DPF(dev_priv)) -+ -+#define GT_FREQUENCY_MULTIPLIER 50 -+#define GEN9_FREQ_SCALER 3 -+ -+#define HAS_DISPLAY(dev_priv) (INTEL_INFO(dev_priv)->num_pipes > 0) -+ -+#include "i915_trace.h" -+ -+static inline bool intel_vtd_active(void) -+{ -+#ifdef CONFIG_INTEL_IOMMU -+ if (intel_iommu_gfx_mapped) -+ return true; -+#endif -+ return false; -+} -+ -+static inline bool intel_scanout_needs_vtd_wa(struct drm_i915_private *dev_priv) -+{ -+ return INTEL_GEN(dev_priv) >= 6 && intel_vtd_active(); -+} -+ -+static inline bool -+intel_ggtt_update_needs_vtd_wa(struct drm_i915_private *dev_priv) -+{ -+ return IS_BROXTON(dev_priv) && intel_vtd_active(); -+} -+ -+/* i915_drv.c */ -+void __printf(3, 4) -+__i915_printk(struct drm_i915_private *dev_priv, const char *level, -+ const char *fmt, ...); -+ -+#define i915_report_error(dev_priv, fmt, ...) \ -+ __i915_printk(dev_priv, KERN_ERR, fmt, ##__VA_ARGS__) -+ -+#ifdef CONFIG_COMPAT -+extern long i915_compat_ioctl(struct file *filp, unsigned int cmd, -+ unsigned long arg); -+#else -+#define i915_compat_ioctl NULL -+#endif -+extern const struct dev_pm_ops i915_pm_ops; -+ -+extern int i915_driver_load(struct pci_dev *pdev, -+ const struct pci_device_id *ent); -+extern void i915_driver_unload(struct drm_device *dev); -+ -+extern void intel_engine_init_hangcheck(struct intel_engine_cs *engine); -+extern void intel_hangcheck_init(struct drm_i915_private *dev_priv); -+extern unsigned long i915_chipset_val(struct drm_i915_private *dev_priv); -+extern unsigned long i915_mch_val(struct drm_i915_private *dev_priv); -+extern unsigned long i915_gfx_val(struct drm_i915_private *dev_priv); -+extern void i915_update_gfx_val(struct drm_i915_private *dev_priv); -+int vlv_force_gfx_clock(struct drm_i915_private *dev_priv, bool on); -+ -+int intel_engines_init_mmio(struct drm_i915_private *dev_priv); -+int intel_engines_init(struct drm_i915_private *dev_priv); -+ -+u32 intel_calculate_mcr_s_ss_select(struct drm_i915_private *dev_priv); -+ -+/* intel_hotplug.c */ -+void intel_hpd_irq_handler(struct drm_i915_private *dev_priv, -+ u32 pin_mask, u32 long_mask); -+void intel_hpd_init(struct drm_i915_private *dev_priv); -+void intel_hpd_init_work(struct drm_i915_private *dev_priv); -+void intel_hpd_cancel_work(struct drm_i915_private *dev_priv); -+enum hpd_pin intel_hpd_pin_default(struct drm_i915_private *dev_priv, -+ enum port port); -+bool intel_hpd_disable(struct drm_i915_private *dev_priv, enum hpd_pin pin); -+void intel_hpd_enable(struct drm_i915_private *dev_priv, enum hpd_pin pin); -+ -+/* i915_irq.c */ -+static inline void i915_queue_hangcheck(struct drm_i915_private *dev_priv) -+{ -+ unsigned long delay; -+ -+ if (unlikely(!i915_modparams.enable_hangcheck)) -+ return; -+ -+ /* Don't continually defer the hangcheck so that it is always run at -+ * least once after work has been scheduled on any ring. Otherwise, -+ * we will ignore a hung ring if a second ring is kept busy. -+ */ -+ -+ delay = round_jiffies_up_relative(DRM_I915_HANGCHECK_JIFFIES); -+ queue_delayed_work(system_long_wq, -+ &dev_priv->gpu_error.hangcheck_work, delay); -+} -+ -+extern void intel_irq_init(struct drm_i915_private *dev_priv); -+extern void intel_irq_fini(struct drm_i915_private *dev_priv); -+int intel_irq_install(struct drm_i915_private *dev_priv); -+void intel_irq_uninstall(struct drm_i915_private *dev_priv); -+ -+static inline bool intel_gvt_active(struct drm_i915_private *dev_priv) -+{ -+ return dev_priv->gvt; -+} -+ -+static inline bool intel_vgpu_active(struct drm_i915_private *dev_priv) -+{ -+ return dev_priv->vgpu.active; -+} -+ -+u32 i915_pipestat_enable_mask(struct drm_i915_private *dev_priv, -+ enum pipe pipe); -+void -+i915_enable_pipestat(struct drm_i915_private *dev_priv, enum pipe pipe, -+ u32 status_mask); -+ -+void -+i915_disable_pipestat(struct drm_i915_private *dev_priv, enum pipe pipe, -+ u32 status_mask); -+ -+void valleyview_enable_display_irqs(struct drm_i915_private *dev_priv); -+void valleyview_disable_display_irqs(struct drm_i915_private *dev_priv); -+void i915_hotplug_interrupt_update(struct drm_i915_private *dev_priv, -+ u32 mask, -+ u32 bits); -+void ilk_update_display_irq(struct drm_i915_private *dev_priv, -+ u32 interrupt_mask, -+ u32 enabled_irq_mask); -+static inline void -+ilk_enable_display_irq(struct drm_i915_private *dev_priv, u32 bits) -+{ -+ ilk_update_display_irq(dev_priv, bits, bits); -+} -+static inline void -+ilk_disable_display_irq(struct drm_i915_private *dev_priv, u32 bits) -+{ -+ ilk_update_display_irq(dev_priv, bits, 0); -+} -+void bdw_update_pipe_irq(struct drm_i915_private *dev_priv, -+ enum pipe pipe, -+ u32 interrupt_mask, -+ u32 enabled_irq_mask); -+static inline void bdw_enable_pipe_irq(struct drm_i915_private *dev_priv, -+ enum pipe pipe, u32 bits) -+{ -+ bdw_update_pipe_irq(dev_priv, pipe, bits, bits); -+} -+static inline void bdw_disable_pipe_irq(struct drm_i915_private *dev_priv, -+ enum pipe pipe, u32 bits) -+{ -+ bdw_update_pipe_irq(dev_priv, pipe, bits, 0); -+} -+void ibx_display_interrupt_update(struct drm_i915_private *dev_priv, -+ u32 interrupt_mask, -+ u32 enabled_irq_mask); -+static inline void -+ibx_enable_display_interrupt(struct drm_i915_private *dev_priv, u32 bits) -+{ -+ ibx_display_interrupt_update(dev_priv, bits, bits); -+} -+static inline void -+ibx_disable_display_interrupt(struct drm_i915_private *dev_priv, u32 bits) -+{ -+ ibx_display_interrupt_update(dev_priv, bits, 0); -+} -+ -+/* i915_gem.c */ -+int i915_gem_create_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_pread_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_pwrite_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_mmap_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_mmap_gtt_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_set_domain_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_sw_finish_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_execbuffer_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_execbuffer2_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_busy_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_get_caching_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file); -+int i915_gem_set_caching_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file); -+int i915_gem_throttle_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_madvise_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_set_tiling_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_get_tiling_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_init_userptr(struct drm_i915_private *dev_priv); -+void i915_gem_cleanup_userptr(struct drm_i915_private *dev_priv); -+int i915_gem_userptr_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file); -+int i915_gem_get_aperture_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_wait_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+void i915_gem_sanitize(struct drm_i915_private *i915); -+int i915_gem_init_early(struct drm_i915_private *dev_priv); -+void i915_gem_cleanup_early(struct drm_i915_private *dev_priv); -+void i915_gem_load_init_fences(struct drm_i915_private *dev_priv); -+int i915_gem_freeze(struct drm_i915_private *dev_priv); -+int i915_gem_freeze_late(struct drm_i915_private *dev_priv); -+ -+void i915_gem_object_init(struct drm_i915_gem_object *obj, -+ const struct drm_i915_gem_object_ops *ops); -+struct drm_i915_gem_object * -+i915_gem_object_create(struct drm_i915_private *dev_priv, u64 size); -+struct drm_i915_gem_object * -+i915_gem_object_create_from_data(struct drm_i915_private *dev_priv, -+ const void *data, size_t size); -+void i915_gem_close_object(struct drm_gem_object *gem, struct drm_file *file); -+void i915_gem_free_object(struct drm_gem_object *obj); -+ -+static inline void i915_gem_drain_freed_objects(struct drm_i915_private *i915) -+{ -+ if (!atomic_read(&i915->mm.free_count)) -+ return; -+ -+ /* A single pass should suffice to release all the freed objects (along -+ * most call paths) , but be a little more paranoid in that freeing -+ * the objects does take a little amount of time, during which the rcu -+ * callbacks could have added new objects into the freed list, and -+ * armed the work again. -+ */ -+ do { -+ rcu_barrier(); -+ } while (flush_work(&i915->mm.free_work)); -+} -+ -+static inline void i915_gem_drain_workqueue(struct drm_i915_private *i915) -+{ -+ /* -+ * Similar to objects above (see i915_gem_drain_freed-objects), in -+ * general we have workers that are armed by RCU and then rearm -+ * themselves in their callbacks. To be paranoid, we need to -+ * drain the workqueue a second time after waiting for the RCU -+ * grace period so that we catch work queued via RCU from the first -+ * pass. As neither drain_workqueue() nor flush_workqueue() report -+ * a result, we make an assumption that we only don't require more -+ * than 2 passes to catch all recursive RCU delayed work. -+ * -+ */ -+ int pass = 2; -+ do { -+ rcu_barrier(); -+ i915_gem_drain_freed_objects(i915); -+ drain_workqueue(i915->wq); -+ } while (--pass); -+} -+ -+struct i915_vma * __must_check -+i915_gem_object_ggtt_pin(struct drm_i915_gem_object *obj, -+ const struct i915_ggtt_view *view, -+ u64 size, -+ u64 alignment, -+ u64 flags); -+ -+int i915_gem_object_unbind(struct drm_i915_gem_object *obj); -+void i915_gem_release_mmap(struct drm_i915_gem_object *obj); -+ -+void i915_gem_runtime_suspend(struct drm_i915_private *dev_priv); -+ -+static inline int __sg_page_count(const struct scatterlist *sg) -+{ -+ return sg->length >> PAGE_SHIFT; -+} -+ -+struct scatterlist * -+i915_gem_object_get_sg(struct drm_i915_gem_object *obj, -+ unsigned int n, unsigned int *offset); -+ -+struct page * -+i915_gem_object_get_page(struct drm_i915_gem_object *obj, -+ unsigned int n); -+ -+struct page * -+i915_gem_object_get_dirty_page(struct drm_i915_gem_object *obj, -+ unsigned int n); -+ -+dma_addr_t -+i915_gem_object_get_dma_address(struct drm_i915_gem_object *obj, -+ unsigned long n); -+ -+void __i915_gem_object_set_pages(struct drm_i915_gem_object *obj, -+ struct sg_table *pages, -+ unsigned int sg_page_sizes); -+int __i915_gem_object_get_pages(struct drm_i915_gem_object *obj); -+ -+static inline int __must_check -+i915_gem_object_pin_pages(struct drm_i915_gem_object *obj) -+{ -+ might_lock(&obj->mm.lock); -+ -+ if (atomic_inc_not_zero(&obj->mm.pages_pin_count)) -+ return 0; -+ -+ return __i915_gem_object_get_pages(obj); -+} -+ -+static inline bool -+i915_gem_object_has_pages(struct drm_i915_gem_object *obj) -+{ -+ return !IS_ERR_OR_NULL(READ_ONCE(obj->mm.pages)); -+} -+ -+static inline void -+__i915_gem_object_pin_pages(struct drm_i915_gem_object *obj) -+{ -+ GEM_BUG_ON(!i915_gem_object_has_pages(obj)); -+ -+ atomic_inc(&obj->mm.pages_pin_count); -+} -+ -+static inline bool -+i915_gem_object_has_pinned_pages(struct drm_i915_gem_object *obj) -+{ -+ return atomic_read(&obj->mm.pages_pin_count); -+} -+ -+static inline void -+__i915_gem_object_unpin_pages(struct drm_i915_gem_object *obj) -+{ -+ GEM_BUG_ON(!i915_gem_object_has_pages(obj)); -+ GEM_BUG_ON(!i915_gem_object_has_pinned_pages(obj)); -+ -+ atomic_dec(&obj->mm.pages_pin_count); -+} -+ -+static inline void -+i915_gem_object_unpin_pages(struct drm_i915_gem_object *obj) -+{ -+ __i915_gem_object_unpin_pages(obj); -+} -+ -+enum i915_mm_subclass { /* lockdep subclass for obj->mm.lock/struct_mutex */ -+ I915_MM_NORMAL = 0, -+ I915_MM_SHRINKER /* called "recursively" from direct-reclaim-esque */ -+}; -+ -+int __i915_gem_object_put_pages(struct drm_i915_gem_object *obj, -+ enum i915_mm_subclass subclass); -+void __i915_gem_object_invalidate(struct drm_i915_gem_object *obj); -+ -+enum i915_map_type { -+ I915_MAP_WB = 0, -+ I915_MAP_WC, -+#define I915_MAP_OVERRIDE BIT(31) -+ I915_MAP_FORCE_WB = I915_MAP_WB | I915_MAP_OVERRIDE, -+ I915_MAP_FORCE_WC = I915_MAP_WC | I915_MAP_OVERRIDE, -+}; -+ -+static inline enum i915_map_type -+i915_coherent_map_type(struct drm_i915_private *i915) -+{ -+ return HAS_LLC(i915) ? I915_MAP_WB : I915_MAP_WC; -+} -+ -+/** -+ * i915_gem_object_pin_map - return a contiguous mapping of the entire object -+ * @obj: the object to map into kernel address space -+ * @type: the type of mapping, used to select pgprot_t -+ * -+ * Calls i915_gem_object_pin_pages() to prevent reaping of the object's -+ * pages and then returns a contiguous mapping of the backing storage into -+ * the kernel address space. Based on the @type of mapping, the PTE will be -+ * set to either WriteBack or WriteCombine (via pgprot_t). -+ * -+ * The caller is responsible for calling i915_gem_object_unpin_map() when the -+ * mapping is no longer required. -+ * -+ * Returns the pointer through which to access the mapped object, or an -+ * ERR_PTR() on error. -+ */ -+void *__must_check i915_gem_object_pin_map(struct drm_i915_gem_object *obj, -+ enum i915_map_type type); -+ -+void __i915_gem_object_flush_map(struct drm_i915_gem_object *obj, -+ unsigned long offset, -+ unsigned long size); -+static inline void i915_gem_object_flush_map(struct drm_i915_gem_object *obj) -+{ -+ __i915_gem_object_flush_map(obj, 0, obj->base.size); -+} -+ -+/** -+ * i915_gem_object_unpin_map - releases an earlier mapping -+ * @obj: the object to unmap -+ * -+ * After pinning the object and mapping its pages, once you are finished -+ * with your access, call i915_gem_object_unpin_map() to release the pin -+ * upon the mapping. Once the pin count reaches zero, that mapping may be -+ * removed. -+ */ -+static inline void i915_gem_object_unpin_map(struct drm_i915_gem_object *obj) -+{ -+ i915_gem_object_unpin_pages(obj); -+} -+ -+int i915_gem_obj_prepare_shmem_read(struct drm_i915_gem_object *obj, -+ unsigned int *needs_clflush); -+int i915_gem_obj_prepare_shmem_write(struct drm_i915_gem_object *obj, -+ unsigned int *needs_clflush); -+#define CLFLUSH_BEFORE BIT(0) -+#define CLFLUSH_AFTER BIT(1) -+#define CLFLUSH_FLAGS (CLFLUSH_BEFORE | CLFLUSH_AFTER) -+ -+static inline void -+i915_gem_obj_finish_shmem_access(struct drm_i915_gem_object *obj) -+{ -+ i915_gem_object_unpin_pages(obj); -+} -+ -+static inline int __must_check -+i915_mutex_lock_interruptible(struct drm_device *dev) -+{ -+ return mutex_lock_interruptible(&dev->struct_mutex); -+} -+ -+int i915_gem_dumb_create(struct drm_file *file_priv, -+ struct drm_device *dev, -+ struct drm_mode_create_dumb *args); -+int i915_gem_mmap_gtt(struct drm_file *file_priv, struct drm_device *dev, -+ u32 handle, u64 *offset); -+int i915_gem_mmap_gtt_version(void); -+ -+void i915_gem_track_fb(struct drm_i915_gem_object *old, -+ struct drm_i915_gem_object *new, -+ unsigned frontbuffer_bits); -+ -+int __must_check i915_gem_set_global_seqno(struct drm_device *dev, u32 seqno); -+ -+static inline bool __i915_wedged(struct i915_gpu_error *error) -+{ -+ return unlikely(test_bit(I915_WEDGED, &error->flags)); -+} -+ -+static inline bool i915_reset_failed(struct drm_i915_private *i915) -+{ -+ return __i915_wedged(&i915->gpu_error); -+} -+ -+static inline u32 i915_reset_count(struct i915_gpu_error *error) -+{ -+ return READ_ONCE(error->reset_count); -+} -+ -+static inline u32 i915_reset_engine_count(struct i915_gpu_error *error, -+ struct intel_engine_cs *engine) -+{ -+ return READ_ONCE(error->reset_engine_count[engine->id]); -+} -+ -+void i915_gem_set_wedged(struct drm_i915_private *dev_priv); -+bool i915_gem_unset_wedged(struct drm_i915_private *dev_priv); -+ -+void i915_gem_init_mmio(struct drm_i915_private *i915); -+int __must_check i915_gem_init(struct drm_i915_private *dev_priv); -+int __must_check i915_gem_init_hw(struct drm_i915_private *dev_priv); -+void i915_gem_init_swizzling(struct drm_i915_private *dev_priv); -+void i915_gem_fini(struct drm_i915_private *dev_priv); -+void i915_gem_cleanup_engines(struct drm_i915_private *dev_priv); -+int i915_gem_wait_for_idle(struct drm_i915_private *dev_priv, -+ unsigned int flags, long timeout); -+void i915_gem_suspend(struct drm_i915_private *dev_priv); -+void i915_gem_suspend_late(struct drm_i915_private *dev_priv); -+void i915_gem_resume(struct drm_i915_private *dev_priv); -+vm_fault_t i915_gem_fault(struct vm_fault *vmf); -+int i915_gem_object_wait(struct drm_i915_gem_object *obj, -+ unsigned int flags, -+ long timeout); -+int i915_gem_object_wait_priority(struct drm_i915_gem_object *obj, -+ unsigned int flags, -+ const struct i915_sched_attr *attr); -+#define I915_PRIORITY_DISPLAY I915_USER_PRIORITY(I915_PRIORITY_MAX) -+ -+int __must_check -+i915_gem_object_set_to_wc_domain(struct drm_i915_gem_object *obj, bool write); -+int __must_check -+i915_gem_object_set_to_gtt_domain(struct drm_i915_gem_object *obj, bool write); -+int __must_check -+i915_gem_object_set_to_cpu_domain(struct drm_i915_gem_object *obj, bool write); -+struct i915_vma * __must_check -+i915_gem_object_pin_to_display_plane(struct drm_i915_gem_object *obj, -+ u32 alignment, -+ const struct i915_ggtt_view *view, -+ unsigned int flags); -+void i915_gem_object_unpin_from_display_plane(struct i915_vma *vma); -+int i915_gem_object_attach_phys(struct drm_i915_gem_object *obj, -+ int align); -+int i915_gem_open(struct drm_i915_private *i915, struct drm_file *file); -+void i915_gem_release(struct drm_device *dev, struct drm_file *file); -+ -+int i915_gem_object_set_cache_level(struct drm_i915_gem_object *obj, -+ enum i915_cache_level cache_level); -+ -+struct drm_gem_object *i915_gem_prime_import(struct drm_device *dev, -+ struct dma_buf *dma_buf); -+ -+struct dma_buf *i915_gem_prime_export(struct drm_device *dev, -+ struct drm_gem_object *gem_obj, int flags); -+ -+static inline struct i915_hw_ppgtt * -+i915_vm_to_ppgtt(struct i915_address_space *vm) -+{ -+ return container_of(vm, struct i915_hw_ppgtt, vm); -+} -+ -+/* i915_gem_fence_reg.c */ -+struct drm_i915_fence_reg * -+i915_reserve_fence(struct drm_i915_private *dev_priv); -+void i915_unreserve_fence(struct drm_i915_fence_reg *fence); -+ -+void i915_gem_restore_fences(struct drm_i915_private *dev_priv); -+ -+void i915_gem_detect_bit_6_swizzle(struct drm_i915_private *dev_priv); -+void i915_gem_object_do_bit_17_swizzle(struct drm_i915_gem_object *obj, -+ struct sg_table *pages); -+void i915_gem_object_save_bit_17_swizzle(struct drm_i915_gem_object *obj, -+ struct sg_table *pages); -+ -+static inline struct i915_gem_context * -+__i915_gem_context_lookup_rcu(struct drm_i915_file_private *file_priv, u32 id) -+{ -+ return idr_find(&file_priv->context_idr, id); -+} -+ -+static inline struct i915_gem_context * -+i915_gem_context_lookup(struct drm_i915_file_private *file_priv, u32 id) -+{ -+ struct i915_gem_context *ctx; -+ -+ rcu_read_lock(); -+ ctx = __i915_gem_context_lookup_rcu(file_priv, id); -+ if (ctx && !kref_get_unless_zero(&ctx->ref)) -+ ctx = NULL; -+ rcu_read_unlock(); -+ -+ return ctx; -+} -+ -+int i915_perf_open_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file); -+int i915_perf_add_config_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file); -+int i915_perf_remove_config_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file); -+void i915_oa_init_reg_state(struct intel_engine_cs *engine, -+ struct intel_context *ce, -+ u32 *reg_state); -+ -+/* i915_gem_evict.c */ -+int __must_check i915_gem_evict_something(struct i915_address_space *vm, -+ u64 min_size, u64 alignment, -+ unsigned cache_level, -+ u64 start, u64 end, -+ unsigned flags); -+int __must_check i915_gem_evict_for_node(struct i915_address_space *vm, -+ struct drm_mm_node *node, -+ unsigned int flags); -+int i915_gem_evict_vm(struct i915_address_space *vm); -+ -+void i915_gem_flush_ggtt_writes(struct drm_i915_private *dev_priv); -+ -+/* belongs in i915_gem_gtt.h */ -+static inline void i915_gem_chipset_flush(struct drm_i915_private *dev_priv) -+{ -+ wmb(); -+ if (INTEL_GEN(dev_priv) < 6) -+ intel_gtt_chipset_flush(); -+} -+ -+/* i915_gem_stolen.c */ -+int i915_gem_stolen_insert_node(struct drm_i915_private *dev_priv, -+ struct drm_mm_node *node, u64 size, -+ unsigned alignment); -+int i915_gem_stolen_insert_node_in_range(struct drm_i915_private *dev_priv, -+ struct drm_mm_node *node, u64 size, -+ unsigned alignment, u64 start, -+ u64 end); -+void i915_gem_stolen_remove_node(struct drm_i915_private *dev_priv, -+ struct drm_mm_node *node); -+int i915_gem_init_stolen(struct drm_i915_private *dev_priv); -+void i915_gem_cleanup_stolen(struct drm_i915_private *dev_priv); -+struct drm_i915_gem_object * -+i915_gem_object_create_stolen(struct drm_i915_private *dev_priv, -+ resource_size_t size); -+struct drm_i915_gem_object * -+i915_gem_object_create_stolen_for_preallocated(struct drm_i915_private *dev_priv, -+ resource_size_t stolen_offset, -+ resource_size_t gtt_offset, -+ resource_size_t size); -+ -+/* i915_gem_internal.c */ -+struct drm_i915_gem_object * -+i915_gem_object_create_internal(struct drm_i915_private *dev_priv, -+ phys_addr_t size); -+ -+/* i915_gem_shrinker.c */ -+unsigned long i915_gem_shrink(struct drm_i915_private *i915, -+ unsigned long target, -+ unsigned long *nr_scanned, -+ unsigned flags); -+#define I915_SHRINK_PURGEABLE 0x1 -+#define I915_SHRINK_UNBOUND 0x2 -+#define I915_SHRINK_BOUND 0x4 -+#define I915_SHRINK_ACTIVE 0x8 -+#define I915_SHRINK_VMAPS 0x10 -+unsigned long i915_gem_shrink_all(struct drm_i915_private *i915); -+void i915_gem_shrinker_register(struct drm_i915_private *i915); -+void i915_gem_shrinker_unregister(struct drm_i915_private *i915); -+void i915_gem_shrinker_taints_mutex(struct drm_i915_private *i915, -+ struct mutex *mutex); -+ -+/* i915_gem_tiling.c */ -+static inline bool i915_gem_object_needs_bit17_swizzle(struct drm_i915_gem_object *obj) -+{ -+ struct drm_i915_private *dev_priv = to_i915(obj->base.dev); -+ -+ return dev_priv->mm.bit_6_swizzle_x == I915_BIT_6_SWIZZLE_9_10_17 && -+ i915_gem_object_is_tiled(obj); -+} -+ -+u32 i915_gem_fence_size(struct drm_i915_private *dev_priv, u32 size, -+ unsigned int tiling, unsigned int stride); -+u32 i915_gem_fence_alignment(struct drm_i915_private *dev_priv, u32 size, -+ unsigned int tiling, unsigned int stride); -+ -+/* i915_debugfs.c */ -+#ifdef CONFIG_DEBUG_FS -+int i915_debugfs_register(struct drm_i915_private *dev_priv); -+int i915_debugfs_connector_add(struct drm_connector *connector); -+void intel_display_crc_init(struct drm_i915_private *dev_priv); -+#else -+static inline int i915_debugfs_register(struct drm_i915_private *dev_priv) {return 0;} -+static inline int i915_debugfs_connector_add(struct drm_connector *connector) -+{ return 0; } -+static inline void intel_display_crc_init(struct drm_i915_private *dev_priv) {} -+#endif -+ -+const char *i915_cache_level_str(struct drm_i915_private *i915, int type); -+ -+/* i915_cmd_parser.c */ -+int i915_cmd_parser_get_version(struct drm_i915_private *dev_priv); -+void intel_engine_init_cmd_parser(struct intel_engine_cs *engine); -+void intel_engine_cleanup_cmd_parser(struct intel_engine_cs *engine); -+int intel_engine_cmd_parser(struct intel_engine_cs *engine, -+ struct drm_i915_gem_object *batch_obj, -+ struct drm_i915_gem_object *shadow_batch_obj, -+ u32 batch_start_offset, -+ u32 batch_len, -+ bool is_master); -+ -+/* i915_perf.c */ -+extern void i915_perf_init(struct drm_i915_private *dev_priv); -+extern void i915_perf_fini(struct drm_i915_private *dev_priv); -+extern void i915_perf_register(struct drm_i915_private *dev_priv); -+extern void i915_perf_unregister(struct drm_i915_private *dev_priv); -+ -+/* i915_suspend.c */ -+extern int i915_save_state(struct drm_i915_private *dev_priv); -+extern int i915_restore_state(struct drm_i915_private *dev_priv); -+ -+/* i915_sysfs.c */ -+void i915_setup_sysfs(struct drm_i915_private *dev_priv); -+void i915_teardown_sysfs(struct drm_i915_private *dev_priv); -+ -+/* intel_lpe_audio.c */ -+int intel_lpe_audio_init(struct drm_i915_private *dev_priv); -+void intel_lpe_audio_teardown(struct drm_i915_private *dev_priv); -+void intel_lpe_audio_irq_handler(struct drm_i915_private *dev_priv); -+void intel_lpe_audio_notify(struct drm_i915_private *dev_priv, -+ enum pipe pipe, enum port port, -+ const void *eld, int ls_clock, bool dp_output); -+ -+/* intel_i2c.c */ -+extern int intel_setup_gmbus(struct drm_i915_private *dev_priv); -+extern void intel_teardown_gmbus(struct drm_i915_private *dev_priv); -+extern bool intel_gmbus_is_valid_pin(struct drm_i915_private *dev_priv, -+ unsigned int pin); -+extern int intel_gmbus_output_aksv(struct i2c_adapter *adapter); -+ -+extern struct i2c_adapter * -+intel_gmbus_get_adapter(struct drm_i915_private *dev_priv, unsigned int pin); -+extern void intel_gmbus_set_speed(struct i2c_adapter *adapter, int speed); -+extern void intel_gmbus_force_bit(struct i2c_adapter *adapter, bool force_bit); -+static inline bool intel_gmbus_is_forced_bit(struct i2c_adapter *adapter) -+{ -+ return container_of(adapter, struct intel_gmbus, adapter)->force_bit; -+} -+extern void intel_i2c_reset(struct drm_i915_private *dev_priv); -+ -+/* intel_bios.c */ -+void intel_bios_init(struct drm_i915_private *dev_priv); -+void intel_bios_cleanup(struct drm_i915_private *dev_priv); -+bool intel_bios_is_valid_vbt(const void *buf, size_t size); -+bool intel_bios_is_tv_present(struct drm_i915_private *dev_priv); -+bool intel_bios_is_lvds_present(struct drm_i915_private *dev_priv, u8 *i2c_pin); -+bool intel_bios_is_port_present(struct drm_i915_private *dev_priv, enum port port); -+bool intel_bios_is_port_edp(struct drm_i915_private *dev_priv, enum port port); -+bool intel_bios_is_port_dp_dual_mode(struct drm_i915_private *dev_priv, enum port port); -+bool intel_bios_is_dsi_present(struct drm_i915_private *dev_priv, enum port *port); -+bool intel_bios_is_port_hpd_inverted(struct drm_i915_private *dev_priv, -+ enum port port); -+bool intel_bios_is_lspcon_present(struct drm_i915_private *dev_priv, -+ enum port port); -+enum aux_ch intel_bios_port_aux_ch(struct drm_i915_private *dev_priv, enum port port); -+ -+/* intel_acpi.c */ -+#ifdef CONFIG_ACPI -+extern void intel_register_dsm_handler(void); -+extern void intel_unregister_dsm_handler(void); -+#else -+static inline void intel_register_dsm_handler(void) { return; } -+static inline void intel_unregister_dsm_handler(void) { return; } -+#endif /* CONFIG_ACPI */ -+ -+/* intel_device_info.c */ -+static inline struct intel_device_info * -+mkwrite_device_info(struct drm_i915_private *dev_priv) -+{ -+ return (struct intel_device_info *)INTEL_INFO(dev_priv); -+} -+ -+static inline struct intel_sseu -+intel_device_default_sseu(struct drm_i915_private *i915) -+{ -+ const struct sseu_dev_info *sseu = &RUNTIME_INFO(i915)->sseu; -+ struct intel_sseu value = { -+ .slice_mask = sseu->slice_mask, -+ .subslice_mask = sseu->subslice_mask[0], -+ .min_eus_per_subslice = sseu->max_eus_per_subslice, -+ .max_eus_per_subslice = sseu->max_eus_per_subslice, -+ }; -+ -+ return value; -+} -+ -+/* modesetting */ -+extern void intel_modeset_init_hw(struct drm_device *dev); -+extern int intel_modeset_init(struct drm_device *dev); -+extern void intel_modeset_cleanup(struct drm_device *dev); -+extern int intel_modeset_vga_set_state(struct drm_i915_private *dev_priv, -+ bool state); -+extern void intel_display_resume(struct drm_device *dev); -+extern void i915_redisable_vga(struct drm_i915_private *dev_priv); -+extern void i915_redisable_vga_power_on(struct drm_i915_private *dev_priv); -+extern bool ironlake_set_drps(struct drm_i915_private *dev_priv, u8 val); -+extern void intel_init_pch_refclk(struct drm_i915_private *dev_priv); -+extern int intel_set_rps(struct drm_i915_private *dev_priv, u8 val); -+extern void intel_rps_mark_interactive(struct drm_i915_private *i915, -+ bool interactive); -+extern bool intel_set_memory_cxsr(struct drm_i915_private *dev_priv, -+ bool enable); -+void intel_dsc_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state); -+void intel_dsc_disable(const struct intel_crtc_state *crtc_state); -+ -+int i915_reg_read_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file); -+ -+/* overlay */ -+extern struct intel_overlay_error_state * -+intel_overlay_capture_error_state(struct drm_i915_private *dev_priv); -+extern void intel_overlay_print_error_state(struct drm_i915_error_state_buf *e, -+ struct intel_overlay_error_state *error); -+ -+extern struct intel_display_error_state * -+intel_display_capture_error_state(struct drm_i915_private *dev_priv); -+extern void intel_display_print_error_state(struct drm_i915_error_state_buf *e, -+ struct intel_display_error_state *error); -+ -+int sandybridge_pcode_read(struct drm_i915_private *dev_priv, u32 mbox, u32 *val); -+int sandybridge_pcode_write_timeout(struct drm_i915_private *dev_priv, u32 mbox, -+ u32 val, int fast_timeout_us, -+ int slow_timeout_ms); -+#define sandybridge_pcode_write(dev_priv, mbox, val) \ -+ sandybridge_pcode_write_timeout(dev_priv, mbox, val, 500, 0) -+ -+int skl_pcode_request(struct drm_i915_private *dev_priv, u32 mbox, u32 request, -+ u32 reply_mask, u32 reply, int timeout_base_ms); -+ -+/* intel_sideband.c */ -+u32 vlv_punit_read(struct drm_i915_private *dev_priv, u32 addr); -+int vlv_punit_write(struct drm_i915_private *dev_priv, u32 addr, u32 val); -+u32 vlv_nc_read(struct drm_i915_private *dev_priv, u8 addr); -+u32 vlv_iosf_sb_read(struct drm_i915_private *dev_priv, u8 port, u32 reg); -+void vlv_iosf_sb_write(struct drm_i915_private *dev_priv, u8 port, u32 reg, u32 val); -+u32 vlv_cck_read(struct drm_i915_private *dev_priv, u32 reg); -+void vlv_cck_write(struct drm_i915_private *dev_priv, u32 reg, u32 val); -+u32 vlv_ccu_read(struct drm_i915_private *dev_priv, u32 reg); -+void vlv_ccu_write(struct drm_i915_private *dev_priv, u32 reg, u32 val); -+u32 vlv_bunit_read(struct drm_i915_private *dev_priv, u32 reg); -+void vlv_bunit_write(struct drm_i915_private *dev_priv, u32 reg, u32 val); -+u32 vlv_dpio_read(struct drm_i915_private *dev_priv, enum pipe pipe, int reg); -+void vlv_dpio_write(struct drm_i915_private *dev_priv, enum pipe pipe, int reg, u32 val); -+u32 intel_sbi_read(struct drm_i915_private *dev_priv, u16 reg, -+ enum intel_sbi_destination destination); -+void intel_sbi_write(struct drm_i915_private *dev_priv, u16 reg, u32 value, -+ enum intel_sbi_destination destination); -+u32 vlv_flisdsi_read(struct drm_i915_private *dev_priv, u32 reg); -+void vlv_flisdsi_write(struct drm_i915_private *dev_priv, u32 reg, u32 val); -+ -+/* intel_dpio_phy.c */ -+void bxt_port_to_phy_channel(struct drm_i915_private *dev_priv, enum port port, -+ enum dpio_phy *phy, enum dpio_channel *ch); -+void bxt_ddi_phy_set_signal_level(struct drm_i915_private *dev_priv, -+ enum port port, u32 margin, u32 scale, -+ u32 enable, u32 deemphasis); -+void bxt_ddi_phy_init(struct drm_i915_private *dev_priv, enum dpio_phy phy); -+void bxt_ddi_phy_uninit(struct drm_i915_private *dev_priv, enum dpio_phy phy); -+bool bxt_ddi_phy_is_enabled(struct drm_i915_private *dev_priv, -+ enum dpio_phy phy); -+bool bxt_ddi_phy_verify_state(struct drm_i915_private *dev_priv, -+ enum dpio_phy phy); -+u8 bxt_ddi_phy_calc_lane_lat_optim_mask(u8 lane_count); -+void bxt_ddi_phy_set_lane_optim_mask(struct intel_encoder *encoder, -+ u8 lane_lat_optim_mask); -+u8 bxt_ddi_phy_get_lane_lat_optim_mask(struct intel_encoder *encoder); -+ -+void chv_set_phy_signal_level(struct intel_encoder *encoder, -+ u32 deemph_reg_value, u32 margin_reg_value, -+ bool uniq_trans_scale); -+void chv_data_lane_soft_reset(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ bool reset); -+void chv_phy_pre_pll_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state); -+void chv_phy_pre_encoder_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state); -+void chv_phy_release_cl2_override(struct intel_encoder *encoder); -+void chv_phy_post_pll_disable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state); -+ -+void vlv_set_phy_signal_level(struct intel_encoder *encoder, -+ u32 demph_reg_value, u32 preemph_reg_value, -+ u32 uniqtranscale_reg_value, u32 tx3_demph); -+void vlv_phy_pre_pll_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state); -+void vlv_phy_pre_encoder_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state); -+void vlv_phy_reset_lanes(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state); -+ -+/* intel_combo_phy.c */ -+void icl_combo_phys_init(struct drm_i915_private *dev_priv); -+void icl_combo_phys_uninit(struct drm_i915_private *dev_priv); -+void cnl_combo_phys_init(struct drm_i915_private *dev_priv); -+void cnl_combo_phys_uninit(struct drm_i915_private *dev_priv); -+ -+int intel_gpu_freq(struct drm_i915_private *dev_priv, int val); -+int intel_freq_opcode(struct drm_i915_private *dev_priv, int val); -+u64 intel_rc6_residency_ns(struct drm_i915_private *dev_priv, -+ const i915_reg_t reg); -+ -+u32 intel_get_cagf(struct drm_i915_private *dev_priv, u32 rpstat1); -+ -+static inline u64 intel_rc6_residency_us(struct drm_i915_private *dev_priv, -+ const i915_reg_t reg) -+{ -+ return DIV_ROUND_UP_ULL(intel_rc6_residency_ns(dev_priv, reg), 1000); -+} -+ -+#define __I915_REG_OP(op__, dev_priv__, ...) \ -+ intel_uncore_##op__(&(dev_priv__)->uncore, __VA_ARGS__) -+ -+#define I915_READ8(reg__) __I915_REG_OP(read8, dev_priv, (reg__)) -+#define I915_WRITE8(reg__, val__) __I915_REG_OP(write8, dev_priv, (reg__), (val__)) -+ -+#define I915_READ16(reg__) __I915_REG_OP(read16, dev_priv, (reg__)) -+#define I915_WRITE16(reg__, val__) __I915_REG_OP(write16, dev_priv, (reg__), (val__)) -+#define I915_READ16_NOTRACE(reg__) __I915_REG_OP(read16_notrace, dev_priv, (reg__)) -+#define I915_WRITE16_NOTRACE(reg__, val__) __I915_REG_OP(write16_notrace, dev_priv, (reg__), (val__)) -+ -+#define I915_READ(reg__) __I915_REG_OP(read, dev_priv, (reg__)) -+#define I915_WRITE(reg__, val__) __I915_REG_OP(write, dev_priv, (reg__), (val__)) -+#define I915_READ_NOTRACE(reg__) __I915_REG_OP(read_notrace, dev_priv, (reg__)) -+#define I915_WRITE_NOTRACE(reg__, val__) __I915_REG_OP(write_notrace, dev_priv, (reg__), (val__)) -+ -+/* Be very careful with read/write 64-bit values. On 32-bit machines, they -+ * will be implemented using 2 32-bit writes in an arbitrary order with -+ * an arbitrary delay between them. This can cause the hardware to -+ * act upon the intermediate value, possibly leading to corruption and -+ * machine death. For this reason we do not support I915_WRITE64, or -+ * dev_priv->uncore.funcs.mmio_writeq. -+ * -+ * When reading a 64-bit value as two 32-bit values, the delay may cause -+ * the two reads to mismatch, e.g. a timestamp overflowing. Also note that -+ * occasionally a 64-bit register does not actualy support a full readq -+ * and must be read using two 32-bit reads. -+ * -+ * You have been warned. -+ */ -+#define I915_READ64(reg__) __I915_REG_OP(read64, dev_priv, (reg__)) -+#define I915_READ64_2x32(lower_reg__, upper_reg__) \ -+ __I915_REG_OP(read64_2x32, dev_priv, (lower_reg__), (upper_reg__)) -+ -+#define POSTING_READ(reg__) __I915_REG_OP(posting_read, dev_priv, (reg__)) -+#define POSTING_READ16(reg__) __I915_REG_OP(posting_read16, dev_priv, (reg__)) -+ -+/* These are untraced mmio-accessors that are only valid to be used inside -+ * critical sections, such as inside IRQ handlers, where forcewake is explicitly -+ * controlled. -+ * -+ * Think twice, and think again, before using these. -+ * -+ * As an example, these accessors can possibly be used between: -+ * -+ * spin_lock_irq(&dev_priv->uncore.lock); -+ * intel_uncore_forcewake_get__locked(); -+ * -+ * and -+ * -+ * intel_uncore_forcewake_put__locked(); -+ * spin_unlock_irq(&dev_priv->uncore.lock); -+ * -+ * -+ * Note: some registers may not need forcewake held, so -+ * intel_uncore_forcewake_{get,put} can be omitted, see -+ * intel_uncore_forcewake_for_reg(). -+ * -+ * Certain architectures will die if the same cacheline is concurrently accessed -+ * by different clients (e.g. on Ivybridge). Access to registers should -+ * therefore generally be serialised, by either the dev_priv->uncore.lock or -+ * a more localised lock guarding all access to that bank of registers. -+ */ -+#define I915_READ_FW(reg__) __I915_REG_OP(read_fw, dev_priv, (reg__)) -+#define I915_WRITE_FW(reg__, val__) __I915_REG_OP(write_fw, dev_priv, (reg__), (val__)) -+#define I915_WRITE64_FW(reg__, val__) __I915_REG_OP(write64_fw, dev_priv, (reg__), (val__)) -+#define POSTING_READ_FW(reg__) __I915_REG_OP(posting_read_fw, dev_priv, (reg__)) -+ -+/* "Broadcast RGB" property */ -+#define INTEL_BROADCAST_RGB_AUTO 0 -+#define INTEL_BROADCAST_RGB_FULL 1 -+#define INTEL_BROADCAST_RGB_LIMITED 2 -+ -+static inline i915_reg_t i915_vgacntrl_reg(struct drm_i915_private *dev_priv) -+{ -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ return VLV_VGACNTRL; -+ else if (INTEL_GEN(dev_priv) >= 5) -+ return CPU_VGACNTRL; -+ else -+ return VGACNTRL; -+} -+ -+static inline unsigned long msecs_to_jiffies_timeout(const unsigned int m) -+{ -+ unsigned long j = msecs_to_jiffies(m); -+ -+ return min_t(unsigned long, MAX_JIFFY_OFFSET, j + 1); -+} -+ -+static inline unsigned long nsecs_to_jiffies_timeout(const u64 n) -+{ -+ /* nsecs_to_jiffies64() does not guard against overflow */ -+ if (NSEC_PER_SEC % HZ && -+ div_u64(n, NSEC_PER_SEC) >= MAX_JIFFY_OFFSET / HZ) -+ return MAX_JIFFY_OFFSET; -+ -+ return min_t(u64, MAX_JIFFY_OFFSET, nsecs_to_jiffies64(n) + 1); -+} -+ -+/* -+ * If you need to wait X milliseconds between events A and B, but event B -+ * doesn't happen exactly after event A, you record the timestamp (jiffies) of -+ * when event A happened, then just before event B you call this function and -+ * pass the timestamp as the first argument, and X as the second argument. -+ */ -+static inline void -+wait_remaining_ms_from_jiffies(unsigned long timestamp_jiffies, int to_wait_ms) -+{ -+ unsigned long target_jiffies, tmp_jiffies, remaining_jiffies; -+ -+ /* -+ * Don't re-read the value of "jiffies" every time since it may change -+ * behind our back and break the math. -+ */ -+ tmp_jiffies = jiffies; -+ target_jiffies = timestamp_jiffies + -+ msecs_to_jiffies_timeout(to_wait_ms); -+ -+ if (time_after(target_jiffies, tmp_jiffies)) { -+ remaining_jiffies = target_jiffies - tmp_jiffies; -+ while (remaining_jiffies) -+ remaining_jiffies = -+ schedule_timeout_uninterruptible(remaining_jiffies); -+ } -+} -+ -+void i915_memcpy_init_early(struct drm_i915_private *dev_priv); -+bool i915_memcpy_from_wc(void *dst, const void *src, unsigned long len); -+ -+/* The movntdqa instructions used for memcpy-from-wc require 16-byte alignment, -+ * as well as SSE4.1 support. i915_memcpy_from_wc() will report if it cannot -+ * perform the operation. To check beforehand, pass in the parameters to -+ * to i915_can_memcpy_from_wc() - since we only care about the low 4 bits, -+ * you only need to pass in the minor offsets, page-aligned pointers are -+ * always valid. -+ * -+ * For just checking for SSE4.1, in the foreknowledge that the future use -+ * will be correctly aligned, just use i915_has_memcpy_from_wc(). -+ */ -+#define i915_can_memcpy_from_wc(dst, src, len) \ -+ i915_memcpy_from_wc((void *)((unsigned long)(dst) | (unsigned long)(src) | (len)), NULL, 0) -+ -+#define i915_has_memcpy_from_wc() \ -+ i915_memcpy_from_wc(NULL, NULL, 0) -+ -+/* i915_mm.c */ -+int remap_io_mapping(struct vm_area_struct *vma, -+ unsigned long addr, unsigned long pfn, unsigned long size, -+ struct io_mapping *iomap); -+ -+static inline int intel_hws_csb_write_index(struct drm_i915_private *i915) -+{ -+ if (INTEL_GEN(i915) >= 10) -+ return CNL_HWS_CSB_WRITE_INDEX; -+ else -+ return I915_HWS_CSB_WRITE_INDEX; -+} -+ -+static inline u32 i915_scratch_offset(const struct drm_i915_private *i915) -+{ -+ return i915_ggtt_offset(i915->gt.scratch); -+} -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_fixed.h b/drivers/gpu/drm/i915_legacy/i915_fixed.h -new file mode 100644 -index 000000000000..591dd89ba7af ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_fixed.h -@@ -0,0 +1,143 @@ -+/* SPDX-License-Identifier: MIT */ -+/* -+ * Copyright © 2018 Intel Corporation -+ */ -+ -+#ifndef _I915_FIXED_H_ -+#define _I915_FIXED_H_ -+ -+typedef struct { -+ u32 val; -+} uint_fixed_16_16_t; -+ -+#define FP_16_16_MAX ((uint_fixed_16_16_t){ .val = UINT_MAX }) -+ -+static inline bool is_fixed16_zero(uint_fixed_16_16_t val) -+{ -+ return val.val == 0; -+} -+ -+static inline uint_fixed_16_16_t u32_to_fixed16(u32 val) -+{ -+ uint_fixed_16_16_t fp = { .val = val << 16 }; -+ -+ WARN_ON(val > U16_MAX); -+ -+ return fp; -+} -+ -+static inline u32 fixed16_to_u32_round_up(uint_fixed_16_16_t fp) -+{ -+ return DIV_ROUND_UP(fp.val, 1 << 16); -+} -+ -+static inline u32 fixed16_to_u32(uint_fixed_16_16_t fp) -+{ -+ return fp.val >> 16; -+} -+ -+static inline uint_fixed_16_16_t min_fixed16(uint_fixed_16_16_t min1, -+ uint_fixed_16_16_t min2) -+{ -+ uint_fixed_16_16_t min = { .val = min(min1.val, min2.val) }; -+ -+ return min; -+} -+ -+static inline uint_fixed_16_16_t max_fixed16(uint_fixed_16_16_t max1, -+ uint_fixed_16_16_t max2) -+{ -+ uint_fixed_16_16_t max = { .val = max(max1.val, max2.val) }; -+ -+ return max; -+} -+ -+static inline uint_fixed_16_16_t clamp_u64_to_fixed16(u64 val) -+{ -+ uint_fixed_16_16_t fp = { .val = (u32)val }; -+ -+ WARN_ON(val > U32_MAX); -+ -+ return fp; -+} -+ -+static inline u32 div_round_up_fixed16(uint_fixed_16_16_t val, -+ uint_fixed_16_16_t d) -+{ -+ return DIV_ROUND_UP(val.val, d.val); -+} -+ -+static inline u32 mul_round_up_u32_fixed16(u32 val, uint_fixed_16_16_t mul) -+{ -+ u64 tmp; -+ -+ tmp = (u64)val * mul.val; -+ tmp = DIV_ROUND_UP_ULL(tmp, 1 << 16); -+ WARN_ON(tmp > U32_MAX); -+ -+ return (u32)tmp; -+} -+ -+static inline uint_fixed_16_16_t mul_fixed16(uint_fixed_16_16_t val, -+ uint_fixed_16_16_t mul) -+{ -+ u64 tmp; -+ -+ tmp = (u64)val.val * mul.val; -+ tmp = tmp >> 16; -+ -+ return clamp_u64_to_fixed16(tmp); -+} -+ -+static inline uint_fixed_16_16_t div_fixed16(u32 val, u32 d) -+{ -+ u64 tmp; -+ -+ tmp = (u64)val << 16; -+ tmp = DIV_ROUND_UP_ULL(tmp, d); -+ -+ return clamp_u64_to_fixed16(tmp); -+} -+ -+static inline u32 div_round_up_u32_fixed16(u32 val, uint_fixed_16_16_t d) -+{ -+ u64 tmp; -+ -+ tmp = (u64)val << 16; -+ tmp = DIV_ROUND_UP_ULL(tmp, d.val); -+ WARN_ON(tmp > U32_MAX); -+ -+ return (u32)tmp; -+} -+ -+static inline uint_fixed_16_16_t mul_u32_fixed16(u32 val, uint_fixed_16_16_t mul) -+{ -+ u64 tmp; -+ -+ tmp = (u64)val * mul.val; -+ -+ return clamp_u64_to_fixed16(tmp); -+} -+ -+static inline uint_fixed_16_16_t add_fixed16(uint_fixed_16_16_t add1, -+ uint_fixed_16_16_t add2) -+{ -+ u64 tmp; -+ -+ tmp = (u64)add1.val + add2.val; -+ -+ return clamp_u64_to_fixed16(tmp); -+} -+ -+static inline uint_fixed_16_16_t add_fixed16_u32(uint_fixed_16_16_t add1, -+ u32 add2) -+{ -+ uint_fixed_16_16_t tmp_add2 = u32_to_fixed16(add2); -+ u64 tmp; -+ -+ tmp = (u64)add1.val + tmp_add2.val; -+ -+ return clamp_u64_to_fixed16(tmp); -+} -+ -+#endif /* _I915_FIXED_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem.c b/drivers/gpu/drm/i915_legacy/i915_gem.c -new file mode 100644 -index 000000000000..ad01c92aaf74 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem.c -@@ -0,0 +1,5545 @@ -+/* -+ * Copyright © 2008-2015 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eric Anholt -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "i915_drv.h" -+#include "i915_gem_clflush.h" -+#include "i915_gemfs.h" -+#include "i915_globals.h" -+#include "i915_reset.h" -+#include "i915_trace.h" -+#include "i915_vgpu.h" -+ -+#include "intel_drv.h" -+#include "intel_frontbuffer.h" -+#include "intel_mocs.h" -+#include "intel_pm.h" -+#include "intel_workarounds.h" -+ -+static void i915_gem_flush_free_objects(struct drm_i915_private *i915); -+ -+static bool cpu_write_needs_clflush(struct drm_i915_gem_object *obj) -+{ -+ if (obj->cache_dirty) -+ return false; -+ -+ if (!(obj->cache_coherent & I915_BO_CACHE_COHERENT_FOR_WRITE)) -+ return true; -+ -+ return obj->pin_global; /* currently in use by HW, keep flushed */ -+} -+ -+static int -+insert_mappable_node(struct i915_ggtt *ggtt, -+ struct drm_mm_node *node, u32 size) -+{ -+ memset(node, 0, sizeof(*node)); -+ return drm_mm_insert_node_in_range(&ggtt->vm.mm, node, -+ size, 0, I915_COLOR_UNEVICTABLE, -+ 0, ggtt->mappable_end, -+ DRM_MM_INSERT_LOW); -+} -+ -+static void -+remove_mappable_node(struct drm_mm_node *node) -+{ -+ drm_mm_remove_node(node); -+} -+ -+/* some bookkeeping */ -+static void i915_gem_info_add_obj(struct drm_i915_private *dev_priv, -+ u64 size) -+{ -+ spin_lock(&dev_priv->mm.object_stat_lock); -+ dev_priv->mm.object_count++; -+ dev_priv->mm.object_memory += size; -+ spin_unlock(&dev_priv->mm.object_stat_lock); -+} -+ -+static void i915_gem_info_remove_obj(struct drm_i915_private *dev_priv, -+ u64 size) -+{ -+ spin_lock(&dev_priv->mm.object_stat_lock); -+ dev_priv->mm.object_count--; -+ dev_priv->mm.object_memory -= size; -+ spin_unlock(&dev_priv->mm.object_stat_lock); -+} -+ -+static void __i915_gem_park(struct drm_i915_private *i915) -+{ -+ intel_wakeref_t wakeref; -+ -+ GEM_TRACE("\n"); -+ -+ lockdep_assert_held(&i915->drm.struct_mutex); -+ GEM_BUG_ON(i915->gt.active_requests); -+ GEM_BUG_ON(!list_empty(&i915->gt.active_rings)); -+ -+ if (!i915->gt.awake) -+ return; -+ -+ /* -+ * Be paranoid and flush a concurrent interrupt to make sure -+ * we don't reactivate any irq tasklets after parking. -+ * -+ * FIXME: Note that even though we have waited for execlists to be idle, -+ * there may still be an in-flight interrupt even though the CSB -+ * is now empty. synchronize_irq() makes sure that a residual interrupt -+ * is completed before we continue, but it doesn't prevent the HW from -+ * raising a spurious interrupt later. To complete the shield we should -+ * coordinate disabling the CS irq with flushing the interrupts. -+ */ -+ synchronize_irq(i915->drm.irq); -+ -+ intel_engines_park(i915); -+ i915_timelines_park(i915); -+ -+ i915_pmu_gt_parked(i915); -+ i915_vma_parked(i915); -+ -+ wakeref = fetch_and_zero(&i915->gt.awake); -+ GEM_BUG_ON(!wakeref); -+ -+ if (INTEL_GEN(i915) >= 6) -+ gen6_rps_idle(i915); -+ -+ intel_display_power_put(i915, POWER_DOMAIN_GT_IRQ, wakeref); -+ -+ i915_globals_park(); -+} -+ -+void i915_gem_park(struct drm_i915_private *i915) -+{ -+ GEM_TRACE("\n"); -+ -+ lockdep_assert_held(&i915->drm.struct_mutex); -+ GEM_BUG_ON(i915->gt.active_requests); -+ -+ if (!i915->gt.awake) -+ return; -+ -+ /* Defer the actual call to __i915_gem_park() to prevent ping-pongs */ -+ mod_delayed_work(i915->wq, &i915->gt.idle_work, msecs_to_jiffies(100)); -+} -+ -+void i915_gem_unpark(struct drm_i915_private *i915) -+{ -+ GEM_TRACE("\n"); -+ -+ lockdep_assert_held(&i915->drm.struct_mutex); -+ GEM_BUG_ON(!i915->gt.active_requests); -+ assert_rpm_wakelock_held(i915); -+ -+ if (i915->gt.awake) -+ return; -+ -+ /* -+ * It seems that the DMC likes to transition between the DC states a lot -+ * when there are no connected displays (no active power domains) during -+ * command submission. -+ * -+ * This activity has negative impact on the performance of the chip with -+ * huge latencies observed in the interrupt handler and elsewhere. -+ * -+ * Work around it by grabbing a GT IRQ power domain whilst there is any -+ * GT activity, preventing any DC state transitions. -+ */ -+ i915->gt.awake = intel_display_power_get(i915, POWER_DOMAIN_GT_IRQ); -+ GEM_BUG_ON(!i915->gt.awake); -+ -+ i915_globals_unpark(); -+ -+ intel_enable_gt_powersave(i915); -+ i915_update_gfx_val(i915); -+ if (INTEL_GEN(i915) >= 6) -+ gen6_rps_busy(i915); -+ i915_pmu_gt_unparked(i915); -+ -+ intel_engines_unpark(i915); -+ -+ i915_queue_hangcheck(i915); -+ -+ queue_delayed_work(i915->wq, -+ &i915->gt.retire_work, -+ round_jiffies_up_relative(HZ)); -+} -+ -+int -+i915_gem_get_aperture_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct i915_ggtt *ggtt = &to_i915(dev)->ggtt; -+ struct drm_i915_gem_get_aperture *args = data; -+ struct i915_vma *vma; -+ u64 pinned; -+ -+ mutex_lock(&ggtt->vm.mutex); -+ -+ pinned = ggtt->vm.reserved; -+ list_for_each_entry(vma, &ggtt->vm.bound_list, vm_link) -+ if (i915_vma_is_pinned(vma)) -+ pinned += vma->node.size; -+ -+ mutex_unlock(&ggtt->vm.mutex); -+ -+ args->aper_size = ggtt->vm.total; -+ args->aper_available_size = args->aper_size - pinned; -+ -+ return 0; -+} -+ -+static int i915_gem_object_get_pages_phys(struct drm_i915_gem_object *obj) -+{ -+ struct address_space *mapping = obj->base.filp->f_mapping; -+ drm_dma_handle_t *phys; -+ struct sg_table *st; -+ struct scatterlist *sg; -+ char *vaddr; -+ int i; -+ int err; -+ -+ if (WARN_ON(i915_gem_object_needs_bit17_swizzle(obj))) -+ return -EINVAL; -+ -+ /* Always aligning to the object size, allows a single allocation -+ * to handle all possible callers, and given typical object sizes, -+ * the alignment of the buddy allocation will naturally match. -+ */ -+ phys = drm_pci_alloc(obj->base.dev, -+ roundup_pow_of_two(obj->base.size), -+ roundup_pow_of_two(obj->base.size)); -+ if (!phys) -+ return -ENOMEM; -+ -+ vaddr = phys->vaddr; -+ for (i = 0; i < obj->base.size / PAGE_SIZE; i++) { -+ struct page *page; -+ char *src; -+ -+ page = shmem_read_mapping_page(mapping, i); -+ if (IS_ERR(page)) { -+ err = PTR_ERR(page); -+ goto err_phys; -+ } -+ -+ src = kmap_atomic(page); -+ memcpy(vaddr, src, PAGE_SIZE); -+ drm_clflush_virt_range(vaddr, PAGE_SIZE); -+ kunmap_atomic(src); -+ -+ put_page(page); -+ vaddr += PAGE_SIZE; -+ } -+ -+ i915_gem_chipset_flush(to_i915(obj->base.dev)); -+ -+ st = kmalloc(sizeof(*st), GFP_KERNEL); -+ if (!st) { -+ err = -ENOMEM; -+ goto err_phys; -+ } -+ -+ if (sg_alloc_table(st, 1, GFP_KERNEL)) { -+ kfree(st); -+ err = -ENOMEM; -+ goto err_phys; -+ } -+ -+ sg = st->sgl; -+ sg->offset = 0; -+ sg->length = obj->base.size; -+ -+ sg_dma_address(sg) = phys->busaddr; -+ sg_dma_len(sg) = obj->base.size; -+ -+ obj->phys_handle = phys; -+ -+ __i915_gem_object_set_pages(obj, st, sg->length); -+ -+ return 0; -+ -+err_phys: -+ drm_pci_free(obj->base.dev, phys); -+ -+ return err; -+} -+ -+static void __start_cpu_write(struct drm_i915_gem_object *obj) -+{ -+ obj->read_domains = I915_GEM_DOMAIN_CPU; -+ obj->write_domain = I915_GEM_DOMAIN_CPU; -+ if (cpu_write_needs_clflush(obj)) -+ obj->cache_dirty = true; -+} -+ -+void -+__i915_gem_object_release_shmem(struct drm_i915_gem_object *obj, -+ struct sg_table *pages, -+ bool needs_clflush) -+{ -+ GEM_BUG_ON(obj->mm.madv == __I915_MADV_PURGED); -+ -+ if (obj->mm.madv == I915_MADV_DONTNEED) -+ obj->mm.dirty = false; -+ -+ if (needs_clflush && -+ (obj->read_domains & I915_GEM_DOMAIN_CPU) == 0 && -+ !(obj->cache_coherent & I915_BO_CACHE_COHERENT_FOR_READ)) -+ drm_clflush_sg(pages); -+ -+ __start_cpu_write(obj); -+} -+ -+static void -+i915_gem_object_put_pages_phys(struct drm_i915_gem_object *obj, -+ struct sg_table *pages) -+{ -+ __i915_gem_object_release_shmem(obj, pages, false); -+ -+ if (obj->mm.dirty) { -+ struct address_space *mapping = obj->base.filp->f_mapping; -+ char *vaddr = obj->phys_handle->vaddr; -+ int i; -+ -+ for (i = 0; i < obj->base.size / PAGE_SIZE; i++) { -+ struct page *page; -+ char *dst; -+ -+ page = shmem_read_mapping_page(mapping, i); -+ if (IS_ERR(page)) -+ continue; -+ -+ dst = kmap_atomic(page); -+ drm_clflush_virt_range(vaddr, PAGE_SIZE); -+ memcpy(dst, vaddr, PAGE_SIZE); -+ kunmap_atomic(dst); -+ -+ set_page_dirty(page); -+ if (obj->mm.madv == I915_MADV_WILLNEED) -+ mark_page_accessed(page); -+ put_page(page); -+ vaddr += PAGE_SIZE; -+ } -+ obj->mm.dirty = false; -+ } -+ -+ sg_free_table(pages); -+ kfree(pages); -+ -+ drm_pci_free(obj->base.dev, obj->phys_handle); -+} -+ -+static void -+i915_gem_object_release_phys(struct drm_i915_gem_object *obj) -+{ -+ i915_gem_object_unpin_pages(obj); -+} -+ -+static const struct drm_i915_gem_object_ops i915_gem_phys_ops = { -+ .get_pages = i915_gem_object_get_pages_phys, -+ .put_pages = i915_gem_object_put_pages_phys, -+ .release = i915_gem_object_release_phys, -+}; -+ -+static const struct drm_i915_gem_object_ops i915_gem_object_ops; -+ -+int i915_gem_object_unbind(struct drm_i915_gem_object *obj) -+{ -+ struct i915_vma *vma; -+ LIST_HEAD(still_in_list); -+ int ret; -+ -+ lockdep_assert_held(&obj->base.dev->struct_mutex); -+ -+ /* Closed vma are removed from the obj->vma_list - but they may -+ * still have an active binding on the object. To remove those we -+ * must wait for all rendering to complete to the object (as unbinding -+ * must anyway), and retire the requests. -+ */ -+ ret = i915_gem_object_set_to_cpu_domain(obj, false); -+ if (ret) -+ return ret; -+ -+ spin_lock(&obj->vma.lock); -+ while (!ret && (vma = list_first_entry_or_null(&obj->vma.list, -+ struct i915_vma, -+ obj_link))) { -+ list_move_tail(&vma->obj_link, &still_in_list); -+ spin_unlock(&obj->vma.lock); -+ -+ ret = i915_vma_unbind(vma); -+ -+ spin_lock(&obj->vma.lock); -+ } -+ list_splice(&still_in_list, &obj->vma.list); -+ spin_unlock(&obj->vma.lock); -+ -+ return ret; -+} -+ -+static long -+i915_gem_object_wait_fence(struct dma_fence *fence, -+ unsigned int flags, -+ long timeout) -+{ -+ struct i915_request *rq; -+ -+ BUILD_BUG_ON(I915_WAIT_INTERRUPTIBLE != 0x1); -+ -+ if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags)) -+ return timeout; -+ -+ if (!dma_fence_is_i915(fence)) -+ return dma_fence_wait_timeout(fence, -+ flags & I915_WAIT_INTERRUPTIBLE, -+ timeout); -+ -+ rq = to_request(fence); -+ if (i915_request_completed(rq)) -+ goto out; -+ -+ timeout = i915_request_wait(rq, flags, timeout); -+ -+out: -+ if (flags & I915_WAIT_LOCKED && i915_request_completed(rq)) -+ i915_request_retire_upto(rq); -+ -+ return timeout; -+} -+ -+static long -+i915_gem_object_wait_reservation(struct reservation_object *resv, -+ unsigned int flags, -+ long timeout) -+{ -+ unsigned int seq = __read_seqcount_begin(&resv->seq); -+ struct dma_fence *excl; -+ bool prune_fences = false; -+ -+ if (flags & I915_WAIT_ALL) { -+ struct dma_fence **shared; -+ unsigned int count, i; -+ int ret; -+ -+ ret = reservation_object_get_fences_rcu(resv, -+ &excl, &count, &shared); -+ if (ret) -+ return ret; -+ -+ for (i = 0; i < count; i++) { -+ timeout = i915_gem_object_wait_fence(shared[i], -+ flags, timeout); -+ if (timeout < 0) -+ break; -+ -+ dma_fence_put(shared[i]); -+ } -+ -+ for (; i < count; i++) -+ dma_fence_put(shared[i]); -+ kfree(shared); -+ -+ /* -+ * If both shared fences and an exclusive fence exist, -+ * then by construction the shared fences must be later -+ * than the exclusive fence. If we successfully wait for -+ * all the shared fences, we know that the exclusive fence -+ * must all be signaled. If all the shared fences are -+ * signaled, we can prune the array and recover the -+ * floating references on the fences/requests. -+ */ -+ prune_fences = count && timeout >= 0; -+ } else { -+ excl = reservation_object_get_excl_rcu(resv); -+ } -+ -+ if (excl && timeout >= 0) -+ timeout = i915_gem_object_wait_fence(excl, flags, timeout); -+ -+ dma_fence_put(excl); -+ -+ /* -+ * Opportunistically prune the fences iff we know they have *all* been -+ * signaled and that the reservation object has not been changed (i.e. -+ * no new fences have been added). -+ */ -+ if (prune_fences && !__read_seqcount_retry(&resv->seq, seq)) { -+ if (reservation_object_trylock(resv)) { -+ if (!__read_seqcount_retry(&resv->seq, seq)) -+ reservation_object_add_excl_fence(resv, NULL); -+ reservation_object_unlock(resv); -+ } -+ } -+ -+ return timeout; -+} -+ -+static void __fence_set_priority(struct dma_fence *fence, -+ const struct i915_sched_attr *attr) -+{ -+ struct i915_request *rq; -+ struct intel_engine_cs *engine; -+ -+ if (dma_fence_is_signaled(fence) || !dma_fence_is_i915(fence)) -+ return; -+ -+ rq = to_request(fence); -+ engine = rq->engine; -+ -+ local_bh_disable(); -+ rcu_read_lock(); /* RCU serialisation for set-wedged protection */ -+ if (engine->schedule) -+ engine->schedule(rq, attr); -+ rcu_read_unlock(); -+ local_bh_enable(); /* kick the tasklets if queues were reprioritised */ -+} -+ -+static void fence_set_priority(struct dma_fence *fence, -+ const struct i915_sched_attr *attr) -+{ -+ /* Recurse once into a fence-array */ -+ if (dma_fence_is_array(fence)) { -+ struct dma_fence_array *array = to_dma_fence_array(fence); -+ int i; -+ -+ for (i = 0; i < array->num_fences; i++) -+ __fence_set_priority(array->fences[i], attr); -+ } else { -+ __fence_set_priority(fence, attr); -+ } -+} -+ -+int -+i915_gem_object_wait_priority(struct drm_i915_gem_object *obj, -+ unsigned int flags, -+ const struct i915_sched_attr *attr) -+{ -+ struct dma_fence *excl; -+ -+ if (flags & I915_WAIT_ALL) { -+ struct dma_fence **shared; -+ unsigned int count, i; -+ int ret; -+ -+ ret = reservation_object_get_fences_rcu(obj->resv, -+ &excl, &count, &shared); -+ if (ret) -+ return ret; -+ -+ for (i = 0; i < count; i++) { -+ fence_set_priority(shared[i], attr); -+ dma_fence_put(shared[i]); -+ } -+ -+ kfree(shared); -+ } else { -+ excl = reservation_object_get_excl_rcu(obj->resv); -+ } -+ -+ if (excl) { -+ fence_set_priority(excl, attr); -+ dma_fence_put(excl); -+ } -+ return 0; -+} -+ -+/** -+ * Waits for rendering to the object to be completed -+ * @obj: i915 gem object -+ * @flags: how to wait (under a lock, for all rendering or just for writes etc) -+ * @timeout: how long to wait -+ */ -+int -+i915_gem_object_wait(struct drm_i915_gem_object *obj, -+ unsigned int flags, -+ long timeout) -+{ -+ might_sleep(); -+ GEM_BUG_ON(timeout < 0); -+ -+ timeout = i915_gem_object_wait_reservation(obj->resv, flags, timeout); -+ return timeout < 0 ? timeout : 0; -+} -+ -+static int -+i915_gem_phys_pwrite(struct drm_i915_gem_object *obj, -+ struct drm_i915_gem_pwrite *args, -+ struct drm_file *file) -+{ -+ void *vaddr = obj->phys_handle->vaddr + args->offset; -+ char __user *user_data = u64_to_user_ptr(args->data_ptr); -+ -+ /* We manually control the domain here and pretend that it -+ * remains coherent i.e. in the GTT domain, like shmem_pwrite. -+ */ -+ intel_fb_obj_invalidate(obj, ORIGIN_CPU); -+ if (copy_from_user(vaddr, user_data, args->size)) -+ return -EFAULT; -+ -+ drm_clflush_virt_range(vaddr, args->size); -+ i915_gem_chipset_flush(to_i915(obj->base.dev)); -+ -+ intel_fb_obj_flush(obj, ORIGIN_CPU); -+ return 0; -+} -+ -+static int -+i915_gem_create(struct drm_file *file, -+ struct drm_i915_private *dev_priv, -+ u64 *size_p, -+ u32 *handle_p) -+{ -+ struct drm_i915_gem_object *obj; -+ u32 handle; -+ u64 size; -+ int ret; -+ -+ size = round_up(*size_p, PAGE_SIZE); -+ if (size == 0) -+ return -EINVAL; -+ -+ /* Allocate the new object */ -+ obj = i915_gem_object_create(dev_priv, size); -+ if (IS_ERR(obj)) -+ return PTR_ERR(obj); -+ -+ ret = drm_gem_handle_create(file, &obj->base, &handle); -+ /* drop reference from allocate - handle holds it now */ -+ i915_gem_object_put(obj); -+ if (ret) -+ return ret; -+ -+ *handle_p = handle; -+ *size_p = size; -+ return 0; -+} -+ -+int -+i915_gem_dumb_create(struct drm_file *file, -+ struct drm_device *dev, -+ struct drm_mode_create_dumb *args) -+{ -+ /* have to work out size/pitch and return them */ -+ args->pitch = ALIGN(args->width * DIV_ROUND_UP(args->bpp, 8), 64); -+ args->size = args->pitch * args->height; -+ return i915_gem_create(file, to_i915(dev), -+ &args->size, &args->handle); -+} -+ -+static bool gpu_write_needs_clflush(struct drm_i915_gem_object *obj) -+{ -+ return !(obj->cache_level == I915_CACHE_NONE || -+ obj->cache_level == I915_CACHE_WT); -+} -+ -+/** -+ * Creates a new mm object and returns a handle to it. -+ * @dev: drm device pointer -+ * @data: ioctl data blob -+ * @file: drm file pointer -+ */ -+int -+i915_gem_create_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_i915_gem_create *args = data; -+ -+ i915_gem_flush_free_objects(dev_priv); -+ -+ return i915_gem_create(file, dev_priv, -+ &args->size, &args->handle); -+} -+ -+static inline enum fb_op_origin -+fb_write_origin(struct drm_i915_gem_object *obj, unsigned int domain) -+{ -+ return (domain == I915_GEM_DOMAIN_GTT ? -+ obj->frontbuffer_ggtt_origin : ORIGIN_CPU); -+} -+ -+void i915_gem_flush_ggtt_writes(struct drm_i915_private *dev_priv) -+{ -+ intel_wakeref_t wakeref; -+ -+ /* -+ * No actual flushing is required for the GTT write domain for reads -+ * from the GTT domain. Writes to it "immediately" go to main memory -+ * as far as we know, so there's no chipset flush. It also doesn't -+ * land in the GPU render cache. -+ * -+ * However, we do have to enforce the order so that all writes through -+ * the GTT land before any writes to the device, such as updates to -+ * the GATT itself. -+ * -+ * We also have to wait a bit for the writes to land from the GTT. -+ * An uncached read (i.e. mmio) seems to be ideal for the round-trip -+ * timing. This issue has only been observed when switching quickly -+ * between GTT writes and CPU reads from inside the kernel on recent hw, -+ * and it appears to only affect discrete GTT blocks (i.e. on LLC -+ * system agents we cannot reproduce this behaviour, until Cannonlake -+ * that was!). -+ */ -+ -+ wmb(); -+ -+ if (INTEL_INFO(dev_priv)->has_coherent_ggtt) -+ return; -+ -+ i915_gem_chipset_flush(dev_priv); -+ -+ with_intel_runtime_pm(dev_priv, wakeref) { -+ spin_lock_irq(&dev_priv->uncore.lock); -+ -+ POSTING_READ_FW(RING_HEAD(RENDER_RING_BASE)); -+ -+ spin_unlock_irq(&dev_priv->uncore.lock); -+ } -+} -+ -+static void -+flush_write_domain(struct drm_i915_gem_object *obj, unsigned int flush_domains) -+{ -+ struct drm_i915_private *dev_priv = to_i915(obj->base.dev); -+ struct i915_vma *vma; -+ -+ if (!(obj->write_domain & flush_domains)) -+ return; -+ -+ switch (obj->write_domain) { -+ case I915_GEM_DOMAIN_GTT: -+ i915_gem_flush_ggtt_writes(dev_priv); -+ -+ intel_fb_obj_flush(obj, -+ fb_write_origin(obj, I915_GEM_DOMAIN_GTT)); -+ -+ for_each_ggtt_vma(vma, obj) { -+ if (vma->iomap) -+ continue; -+ -+ i915_vma_unset_ggtt_write(vma); -+ } -+ break; -+ -+ case I915_GEM_DOMAIN_WC: -+ wmb(); -+ break; -+ -+ case I915_GEM_DOMAIN_CPU: -+ i915_gem_clflush_object(obj, I915_CLFLUSH_SYNC); -+ break; -+ -+ case I915_GEM_DOMAIN_RENDER: -+ if (gpu_write_needs_clflush(obj)) -+ obj->cache_dirty = true; -+ break; -+ } -+ -+ obj->write_domain = 0; -+} -+ -+/* -+ * Pins the specified object's pages and synchronizes the object with -+ * GPU accesses. Sets needs_clflush to non-zero if the caller should -+ * flush the object from the CPU cache. -+ */ -+int i915_gem_obj_prepare_shmem_read(struct drm_i915_gem_object *obj, -+ unsigned int *needs_clflush) -+{ -+ int ret; -+ -+ lockdep_assert_held(&obj->base.dev->struct_mutex); -+ -+ *needs_clflush = 0; -+ if (!i915_gem_object_has_struct_page(obj)) -+ return -ENODEV; -+ -+ ret = i915_gem_object_wait(obj, -+ I915_WAIT_INTERRUPTIBLE | -+ I915_WAIT_LOCKED, -+ MAX_SCHEDULE_TIMEOUT); -+ if (ret) -+ return ret; -+ -+ ret = i915_gem_object_pin_pages(obj); -+ if (ret) -+ return ret; -+ -+ if (obj->cache_coherent & I915_BO_CACHE_COHERENT_FOR_READ || -+ !static_cpu_has(X86_FEATURE_CLFLUSH)) { -+ ret = i915_gem_object_set_to_cpu_domain(obj, false); -+ if (ret) -+ goto err_unpin; -+ else -+ goto out; -+ } -+ -+ flush_write_domain(obj, ~I915_GEM_DOMAIN_CPU); -+ -+ /* If we're not in the cpu read domain, set ourself into the gtt -+ * read domain and manually flush cachelines (if required). This -+ * optimizes for the case when the gpu will dirty the data -+ * anyway again before the next pread happens. -+ */ -+ if (!obj->cache_dirty && -+ !(obj->read_domains & I915_GEM_DOMAIN_CPU)) -+ *needs_clflush = CLFLUSH_BEFORE; -+ -+out: -+ /* return with the pages pinned */ -+ return 0; -+ -+err_unpin: -+ i915_gem_object_unpin_pages(obj); -+ return ret; -+} -+ -+int i915_gem_obj_prepare_shmem_write(struct drm_i915_gem_object *obj, -+ unsigned int *needs_clflush) -+{ -+ int ret; -+ -+ lockdep_assert_held(&obj->base.dev->struct_mutex); -+ -+ *needs_clflush = 0; -+ if (!i915_gem_object_has_struct_page(obj)) -+ return -ENODEV; -+ -+ ret = i915_gem_object_wait(obj, -+ I915_WAIT_INTERRUPTIBLE | -+ I915_WAIT_LOCKED | -+ I915_WAIT_ALL, -+ MAX_SCHEDULE_TIMEOUT); -+ if (ret) -+ return ret; -+ -+ ret = i915_gem_object_pin_pages(obj); -+ if (ret) -+ return ret; -+ -+ if (obj->cache_coherent & I915_BO_CACHE_COHERENT_FOR_WRITE || -+ !static_cpu_has(X86_FEATURE_CLFLUSH)) { -+ ret = i915_gem_object_set_to_cpu_domain(obj, true); -+ if (ret) -+ goto err_unpin; -+ else -+ goto out; -+ } -+ -+ flush_write_domain(obj, ~I915_GEM_DOMAIN_CPU); -+ -+ /* If we're not in the cpu write domain, set ourself into the -+ * gtt write domain and manually flush cachelines (as required). -+ * This optimizes for the case when the gpu will use the data -+ * right away and we therefore have to clflush anyway. -+ */ -+ if (!obj->cache_dirty) { -+ *needs_clflush |= CLFLUSH_AFTER; -+ -+ /* -+ * Same trick applies to invalidate partially written -+ * cachelines read before writing. -+ */ -+ if (!(obj->read_domains & I915_GEM_DOMAIN_CPU)) -+ *needs_clflush |= CLFLUSH_BEFORE; -+ } -+ -+out: -+ intel_fb_obj_invalidate(obj, ORIGIN_CPU); -+ obj->mm.dirty = true; -+ /* return with the pages pinned */ -+ return 0; -+ -+err_unpin: -+ i915_gem_object_unpin_pages(obj); -+ return ret; -+} -+ -+static int -+shmem_pread(struct page *page, int offset, int len, char __user *user_data, -+ bool needs_clflush) -+{ -+ char *vaddr; -+ int ret; -+ -+ vaddr = kmap(page); -+ -+ if (needs_clflush) -+ drm_clflush_virt_range(vaddr + offset, len); -+ -+ ret = __copy_to_user(user_data, vaddr + offset, len); -+ -+ kunmap(page); -+ -+ return ret ? -EFAULT : 0; -+} -+ -+static int -+i915_gem_shmem_pread(struct drm_i915_gem_object *obj, -+ struct drm_i915_gem_pread *args) -+{ -+ char __user *user_data; -+ u64 remain; -+ unsigned int needs_clflush; -+ unsigned int idx, offset; -+ int ret; -+ -+ ret = mutex_lock_interruptible(&obj->base.dev->struct_mutex); -+ if (ret) -+ return ret; -+ -+ ret = i915_gem_obj_prepare_shmem_read(obj, &needs_clflush); -+ mutex_unlock(&obj->base.dev->struct_mutex); -+ if (ret) -+ return ret; -+ -+ remain = args->size; -+ user_data = u64_to_user_ptr(args->data_ptr); -+ offset = offset_in_page(args->offset); -+ for (idx = args->offset >> PAGE_SHIFT; remain; idx++) { -+ struct page *page = i915_gem_object_get_page(obj, idx); -+ unsigned int length = min_t(u64, remain, PAGE_SIZE - offset); -+ -+ ret = shmem_pread(page, offset, length, user_data, -+ needs_clflush); -+ if (ret) -+ break; -+ -+ remain -= length; -+ user_data += length; -+ offset = 0; -+ } -+ -+ i915_gem_obj_finish_shmem_access(obj); -+ return ret; -+} -+ -+static inline bool -+gtt_user_read(struct io_mapping *mapping, -+ loff_t base, int offset, -+ char __user *user_data, int length) -+{ -+ void __iomem *vaddr; -+ unsigned long unwritten; -+ -+ /* We can use the cpu mem copy function because this is X86. */ -+ vaddr = io_mapping_map_atomic_wc(mapping, base); -+ unwritten = __copy_to_user_inatomic(user_data, -+ (void __force *)vaddr + offset, -+ length); -+ io_mapping_unmap_atomic(vaddr); -+ if (unwritten) { -+ vaddr = io_mapping_map_wc(mapping, base, PAGE_SIZE); -+ unwritten = copy_to_user(user_data, -+ (void __force *)vaddr + offset, -+ length); -+ io_mapping_unmap(vaddr); -+ } -+ return unwritten; -+} -+ -+static int -+i915_gem_gtt_pread(struct drm_i915_gem_object *obj, -+ const struct drm_i915_gem_pread *args) -+{ -+ struct drm_i915_private *i915 = to_i915(obj->base.dev); -+ struct i915_ggtt *ggtt = &i915->ggtt; -+ intel_wakeref_t wakeref; -+ struct drm_mm_node node; -+ struct i915_vma *vma; -+ void __user *user_data; -+ u64 remain, offset; -+ int ret; -+ -+ ret = mutex_lock_interruptible(&i915->drm.struct_mutex); -+ if (ret) -+ return ret; -+ -+ wakeref = intel_runtime_pm_get(i915); -+ vma = i915_gem_object_ggtt_pin(obj, NULL, 0, 0, -+ PIN_MAPPABLE | -+ PIN_NONFAULT | -+ PIN_NONBLOCK); -+ if (!IS_ERR(vma)) { -+ node.start = i915_ggtt_offset(vma); -+ node.allocated = false; -+ ret = i915_vma_put_fence(vma); -+ if (ret) { -+ i915_vma_unpin(vma); -+ vma = ERR_PTR(ret); -+ } -+ } -+ if (IS_ERR(vma)) { -+ ret = insert_mappable_node(ggtt, &node, PAGE_SIZE); -+ if (ret) -+ goto out_unlock; -+ GEM_BUG_ON(!node.allocated); -+ } -+ -+ ret = i915_gem_object_set_to_gtt_domain(obj, false); -+ if (ret) -+ goto out_unpin; -+ -+ mutex_unlock(&i915->drm.struct_mutex); -+ -+ user_data = u64_to_user_ptr(args->data_ptr); -+ remain = args->size; -+ offset = args->offset; -+ -+ while (remain > 0) { -+ /* Operation in this page -+ * -+ * page_base = page offset within aperture -+ * page_offset = offset within page -+ * page_length = bytes to copy for this page -+ */ -+ u32 page_base = node.start; -+ unsigned page_offset = offset_in_page(offset); -+ unsigned page_length = PAGE_SIZE - page_offset; -+ page_length = remain < page_length ? remain : page_length; -+ if (node.allocated) { -+ wmb(); -+ ggtt->vm.insert_page(&ggtt->vm, -+ i915_gem_object_get_dma_address(obj, offset >> PAGE_SHIFT), -+ node.start, I915_CACHE_NONE, 0); -+ wmb(); -+ } else { -+ page_base += offset & PAGE_MASK; -+ } -+ -+ if (gtt_user_read(&ggtt->iomap, page_base, page_offset, -+ user_data, page_length)) { -+ ret = -EFAULT; -+ break; -+ } -+ -+ remain -= page_length; -+ user_data += page_length; -+ offset += page_length; -+ } -+ -+ mutex_lock(&i915->drm.struct_mutex); -+out_unpin: -+ if (node.allocated) { -+ wmb(); -+ ggtt->vm.clear_range(&ggtt->vm, node.start, node.size); -+ remove_mappable_node(&node); -+ } else { -+ i915_vma_unpin(vma); -+ } -+out_unlock: -+ intel_runtime_pm_put(i915, wakeref); -+ mutex_unlock(&i915->drm.struct_mutex); -+ -+ return ret; -+} -+ -+/** -+ * Reads data from the object referenced by handle. -+ * @dev: drm device pointer -+ * @data: ioctl data blob -+ * @file: drm file pointer -+ * -+ * On error, the contents of *data are undefined. -+ */ -+int -+i915_gem_pread_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_gem_pread *args = data; -+ struct drm_i915_gem_object *obj; -+ int ret; -+ -+ if (args->size == 0) -+ return 0; -+ -+ if (!access_ok(u64_to_user_ptr(args->data_ptr), -+ args->size)) -+ return -EFAULT; -+ -+ obj = i915_gem_object_lookup(file, args->handle); -+ if (!obj) -+ return -ENOENT; -+ -+ /* Bounds check source. */ -+ if (range_overflows_t(u64, args->offset, args->size, obj->base.size)) { -+ ret = -EINVAL; -+ goto out; -+ } -+ -+ trace_i915_gem_object_pread(obj, args->offset, args->size); -+ -+ ret = i915_gem_object_wait(obj, -+ I915_WAIT_INTERRUPTIBLE, -+ MAX_SCHEDULE_TIMEOUT); -+ if (ret) -+ goto out; -+ -+ ret = i915_gem_object_pin_pages(obj); -+ if (ret) -+ goto out; -+ -+ ret = i915_gem_shmem_pread(obj, args); -+ if (ret == -EFAULT || ret == -ENODEV) -+ ret = i915_gem_gtt_pread(obj, args); -+ -+ i915_gem_object_unpin_pages(obj); -+out: -+ i915_gem_object_put(obj); -+ return ret; -+} -+ -+/* This is the fast write path which cannot handle -+ * page faults in the source data -+ */ -+ -+static inline bool -+ggtt_write(struct io_mapping *mapping, -+ loff_t base, int offset, -+ char __user *user_data, int length) -+{ -+ void __iomem *vaddr; -+ unsigned long unwritten; -+ -+ /* We can use the cpu mem copy function because this is X86. */ -+ vaddr = io_mapping_map_atomic_wc(mapping, base); -+ unwritten = __copy_from_user_inatomic_nocache((void __force *)vaddr + offset, -+ user_data, length); -+ io_mapping_unmap_atomic(vaddr); -+ if (unwritten) { -+ vaddr = io_mapping_map_wc(mapping, base, PAGE_SIZE); -+ unwritten = copy_from_user((void __force *)vaddr + offset, -+ user_data, length); -+ io_mapping_unmap(vaddr); -+ } -+ -+ return unwritten; -+} -+ -+/** -+ * This is the fast pwrite path, where we copy the data directly from the -+ * user into the GTT, uncached. -+ * @obj: i915 GEM object -+ * @args: pwrite arguments structure -+ */ -+static int -+i915_gem_gtt_pwrite_fast(struct drm_i915_gem_object *obj, -+ const struct drm_i915_gem_pwrite *args) -+{ -+ struct drm_i915_private *i915 = to_i915(obj->base.dev); -+ struct i915_ggtt *ggtt = &i915->ggtt; -+ intel_wakeref_t wakeref; -+ struct drm_mm_node node; -+ struct i915_vma *vma; -+ u64 remain, offset; -+ void __user *user_data; -+ int ret; -+ -+ ret = mutex_lock_interruptible(&i915->drm.struct_mutex); -+ if (ret) -+ return ret; -+ -+ if (i915_gem_object_has_struct_page(obj)) { -+ /* -+ * Avoid waking the device up if we can fallback, as -+ * waking/resuming is very slow (worst-case 10-100 ms -+ * depending on PCI sleeps and our own resume time). -+ * This easily dwarfs any performance advantage from -+ * using the cache bypass of indirect GGTT access. -+ */ -+ wakeref = intel_runtime_pm_get_if_in_use(i915); -+ if (!wakeref) { -+ ret = -EFAULT; -+ goto out_unlock; -+ } -+ } else { -+ /* No backing pages, no fallback, we must force GGTT access */ -+ wakeref = intel_runtime_pm_get(i915); -+ } -+ -+ vma = i915_gem_object_ggtt_pin(obj, NULL, 0, 0, -+ PIN_MAPPABLE | -+ PIN_NONFAULT | -+ PIN_NONBLOCK); -+ if (!IS_ERR(vma)) { -+ node.start = i915_ggtt_offset(vma); -+ node.allocated = false; -+ ret = i915_vma_put_fence(vma); -+ if (ret) { -+ i915_vma_unpin(vma); -+ vma = ERR_PTR(ret); -+ } -+ } -+ if (IS_ERR(vma)) { -+ ret = insert_mappable_node(ggtt, &node, PAGE_SIZE); -+ if (ret) -+ goto out_rpm; -+ GEM_BUG_ON(!node.allocated); -+ } -+ -+ ret = i915_gem_object_set_to_gtt_domain(obj, true); -+ if (ret) -+ goto out_unpin; -+ -+ mutex_unlock(&i915->drm.struct_mutex); -+ -+ intel_fb_obj_invalidate(obj, ORIGIN_CPU); -+ -+ user_data = u64_to_user_ptr(args->data_ptr); -+ offset = args->offset; -+ remain = args->size; -+ while (remain) { -+ /* Operation in this page -+ * -+ * page_base = page offset within aperture -+ * page_offset = offset within page -+ * page_length = bytes to copy for this page -+ */ -+ u32 page_base = node.start; -+ unsigned int page_offset = offset_in_page(offset); -+ unsigned int page_length = PAGE_SIZE - page_offset; -+ page_length = remain < page_length ? remain : page_length; -+ if (node.allocated) { -+ wmb(); /* flush the write before we modify the GGTT */ -+ ggtt->vm.insert_page(&ggtt->vm, -+ i915_gem_object_get_dma_address(obj, offset >> PAGE_SHIFT), -+ node.start, I915_CACHE_NONE, 0); -+ wmb(); /* flush modifications to the GGTT (insert_page) */ -+ } else { -+ page_base += offset & PAGE_MASK; -+ } -+ /* If we get a fault while copying data, then (presumably) our -+ * source page isn't available. Return the error and we'll -+ * retry in the slow path. -+ * If the object is non-shmem backed, we retry again with the -+ * path that handles page fault. -+ */ -+ if (ggtt_write(&ggtt->iomap, page_base, page_offset, -+ user_data, page_length)) { -+ ret = -EFAULT; -+ break; -+ } -+ -+ remain -= page_length; -+ user_data += page_length; -+ offset += page_length; -+ } -+ intel_fb_obj_flush(obj, ORIGIN_CPU); -+ -+ mutex_lock(&i915->drm.struct_mutex); -+out_unpin: -+ if (node.allocated) { -+ wmb(); -+ ggtt->vm.clear_range(&ggtt->vm, node.start, node.size); -+ remove_mappable_node(&node); -+ } else { -+ i915_vma_unpin(vma); -+ } -+out_rpm: -+ intel_runtime_pm_put(i915, wakeref); -+out_unlock: -+ mutex_unlock(&i915->drm.struct_mutex); -+ return ret; -+} -+ -+/* Per-page copy function for the shmem pwrite fastpath. -+ * Flushes invalid cachelines before writing to the target if -+ * needs_clflush_before is set and flushes out any written cachelines after -+ * writing if needs_clflush is set. -+ */ -+static int -+shmem_pwrite(struct page *page, int offset, int len, char __user *user_data, -+ bool needs_clflush_before, -+ bool needs_clflush_after) -+{ -+ char *vaddr; -+ int ret; -+ -+ vaddr = kmap(page); -+ -+ if (needs_clflush_before) -+ drm_clflush_virt_range(vaddr + offset, len); -+ -+ ret = __copy_from_user(vaddr + offset, user_data, len); -+ if (!ret && needs_clflush_after) -+ drm_clflush_virt_range(vaddr + offset, len); -+ -+ kunmap(page); -+ -+ return ret ? -EFAULT : 0; -+} -+ -+static int -+i915_gem_shmem_pwrite(struct drm_i915_gem_object *obj, -+ const struct drm_i915_gem_pwrite *args) -+{ -+ struct drm_i915_private *i915 = to_i915(obj->base.dev); -+ void __user *user_data; -+ u64 remain; -+ unsigned int partial_cacheline_write; -+ unsigned int needs_clflush; -+ unsigned int offset, idx; -+ int ret; -+ -+ ret = mutex_lock_interruptible(&i915->drm.struct_mutex); -+ if (ret) -+ return ret; -+ -+ ret = i915_gem_obj_prepare_shmem_write(obj, &needs_clflush); -+ mutex_unlock(&i915->drm.struct_mutex); -+ if (ret) -+ return ret; -+ -+ /* If we don't overwrite a cacheline completely we need to be -+ * careful to have up-to-date data by first clflushing. Don't -+ * overcomplicate things and flush the entire patch. -+ */ -+ partial_cacheline_write = 0; -+ if (needs_clflush & CLFLUSH_BEFORE) -+ partial_cacheline_write = boot_cpu_data.x86_clflush_size - 1; -+ -+ user_data = u64_to_user_ptr(args->data_ptr); -+ remain = args->size; -+ offset = offset_in_page(args->offset); -+ for (idx = args->offset >> PAGE_SHIFT; remain; idx++) { -+ struct page *page = i915_gem_object_get_page(obj, idx); -+ unsigned int length = min_t(u64, remain, PAGE_SIZE - offset); -+ -+ ret = shmem_pwrite(page, offset, length, user_data, -+ (offset | length) & partial_cacheline_write, -+ needs_clflush & CLFLUSH_AFTER); -+ if (ret) -+ break; -+ -+ remain -= length; -+ user_data += length; -+ offset = 0; -+ } -+ -+ intel_fb_obj_flush(obj, ORIGIN_CPU); -+ i915_gem_obj_finish_shmem_access(obj); -+ return ret; -+} -+ -+/** -+ * Writes data to the object referenced by handle. -+ * @dev: drm device -+ * @data: ioctl data blob -+ * @file: drm file -+ * -+ * On error, the contents of the buffer that were to be modified are undefined. -+ */ -+int -+i915_gem_pwrite_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_gem_pwrite *args = data; -+ struct drm_i915_gem_object *obj; -+ int ret; -+ -+ if (args->size == 0) -+ return 0; -+ -+ if (!access_ok(u64_to_user_ptr(args->data_ptr), args->size)) -+ return -EFAULT; -+ -+ obj = i915_gem_object_lookup(file, args->handle); -+ if (!obj) -+ return -ENOENT; -+ -+ /* Bounds check destination. */ -+ if (range_overflows_t(u64, args->offset, args->size, obj->base.size)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ /* Writes not allowed into this read-only object */ -+ if (i915_gem_object_is_readonly(obj)) { -+ ret = -EINVAL; -+ goto err; -+ } -+ -+ trace_i915_gem_object_pwrite(obj, args->offset, args->size); -+ -+ ret = -ENODEV; -+ if (obj->ops->pwrite) -+ ret = obj->ops->pwrite(obj, args); -+ if (ret != -ENODEV) -+ goto err; -+ -+ ret = i915_gem_object_wait(obj, -+ I915_WAIT_INTERRUPTIBLE | -+ I915_WAIT_ALL, -+ MAX_SCHEDULE_TIMEOUT); -+ if (ret) -+ goto err; -+ -+ ret = i915_gem_object_pin_pages(obj); -+ if (ret) -+ goto err; -+ -+ ret = -EFAULT; -+ /* We can only do the GTT pwrite on untiled buffers, as otherwise -+ * it would end up going through the fenced access, and we'll get -+ * different detiling behavior between reading and writing. -+ * pread/pwrite currently are reading and writing from the CPU -+ * perspective, requiring manual detiling by the client. -+ */ -+ if (!i915_gem_object_has_struct_page(obj) || -+ cpu_write_needs_clflush(obj)) -+ /* Note that the gtt paths might fail with non-page-backed user -+ * pointers (e.g. gtt mappings when moving data between -+ * textures). Fallback to the shmem path in that case. -+ */ -+ ret = i915_gem_gtt_pwrite_fast(obj, args); -+ -+ if (ret == -EFAULT || ret == -ENOSPC) { -+ if (obj->phys_handle) -+ ret = i915_gem_phys_pwrite(obj, args, file); -+ else -+ ret = i915_gem_shmem_pwrite(obj, args); -+ } -+ -+ i915_gem_object_unpin_pages(obj); -+err: -+ i915_gem_object_put(obj); -+ return ret; -+} -+ -+static void i915_gem_object_bump_inactive_ggtt(struct drm_i915_gem_object *obj) -+{ -+ struct drm_i915_private *i915 = to_i915(obj->base.dev); -+ struct list_head *list; -+ struct i915_vma *vma; -+ -+ GEM_BUG_ON(!i915_gem_object_has_pinned_pages(obj)); -+ -+ mutex_lock(&i915->ggtt.vm.mutex); -+ for_each_ggtt_vma(vma, obj) { -+ if (!drm_mm_node_allocated(&vma->node)) -+ continue; -+ -+ list_move_tail(&vma->vm_link, &vma->vm->bound_list); -+ } -+ mutex_unlock(&i915->ggtt.vm.mutex); -+ -+ spin_lock(&i915->mm.obj_lock); -+ list = obj->bind_count ? &i915->mm.bound_list : &i915->mm.unbound_list; -+ list_move_tail(&obj->mm.link, list); -+ spin_unlock(&i915->mm.obj_lock); -+} -+ -+/** -+ * Called when user space prepares to use an object with the CPU, either -+ * through the mmap ioctl's mapping or a GTT mapping. -+ * @dev: drm device -+ * @data: ioctl data blob -+ * @file: drm file -+ */ -+int -+i915_gem_set_domain_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_gem_set_domain *args = data; -+ struct drm_i915_gem_object *obj; -+ u32 read_domains = args->read_domains; -+ u32 write_domain = args->write_domain; -+ int err; -+ -+ /* Only handle setting domains to types used by the CPU. */ -+ if ((write_domain | read_domains) & I915_GEM_GPU_DOMAINS) -+ return -EINVAL; -+ -+ /* -+ * Having something in the write domain implies it's in the read -+ * domain, and only that read domain. Enforce that in the request. -+ */ -+ if (write_domain && read_domains != write_domain) -+ return -EINVAL; -+ -+ if (!read_domains) -+ return 0; -+ -+ obj = i915_gem_object_lookup(file, args->handle); -+ if (!obj) -+ return -ENOENT; -+ -+ /* -+ * Already in the desired write domain? Nothing for us to do! -+ * -+ * We apply a little bit of cunning here to catch a broader set of -+ * no-ops. If obj->write_domain is set, we must be in the same -+ * obj->read_domains, and only that domain. Therefore, if that -+ * obj->write_domain matches the request read_domains, we are -+ * already in the same read/write domain and can skip the operation, -+ * without having to further check the requested write_domain. -+ */ -+ if (READ_ONCE(obj->write_domain) == read_domains) { -+ err = 0; -+ goto out; -+ } -+ -+ /* -+ * Try to flush the object off the GPU without holding the lock. -+ * We will repeat the flush holding the lock in the normal manner -+ * to catch cases where we are gazumped. -+ */ -+ err = i915_gem_object_wait(obj, -+ I915_WAIT_INTERRUPTIBLE | -+ I915_WAIT_PRIORITY | -+ (write_domain ? I915_WAIT_ALL : 0), -+ MAX_SCHEDULE_TIMEOUT); -+ if (err) -+ goto out; -+ -+ /* -+ * Proxy objects do not control access to the backing storage, ergo -+ * they cannot be used as a means to manipulate the cache domain -+ * tracking for that backing storage. The proxy object is always -+ * considered to be outside of any cache domain. -+ */ -+ if (i915_gem_object_is_proxy(obj)) { -+ err = -ENXIO; -+ goto out; -+ } -+ -+ /* -+ * Flush and acquire obj->pages so that we are coherent through -+ * direct access in memory with previous cached writes through -+ * shmemfs and that our cache domain tracking remains valid. -+ * For example, if the obj->filp was moved to swap without us -+ * being notified and releasing the pages, we would mistakenly -+ * continue to assume that the obj remained out of the CPU cached -+ * domain. -+ */ -+ err = i915_gem_object_pin_pages(obj); -+ if (err) -+ goto out; -+ -+ err = i915_mutex_lock_interruptible(dev); -+ if (err) -+ goto out_unpin; -+ -+ if (read_domains & I915_GEM_DOMAIN_WC) -+ err = i915_gem_object_set_to_wc_domain(obj, write_domain); -+ else if (read_domains & I915_GEM_DOMAIN_GTT) -+ err = i915_gem_object_set_to_gtt_domain(obj, write_domain); -+ else -+ err = i915_gem_object_set_to_cpu_domain(obj, write_domain); -+ -+ /* And bump the LRU for this access */ -+ i915_gem_object_bump_inactive_ggtt(obj); -+ -+ mutex_unlock(&dev->struct_mutex); -+ -+ if (write_domain != 0) -+ intel_fb_obj_invalidate(obj, -+ fb_write_origin(obj, write_domain)); -+ -+out_unpin: -+ i915_gem_object_unpin_pages(obj); -+out: -+ i915_gem_object_put(obj); -+ return err; -+} -+ -+/** -+ * Called when user space has done writes to this buffer -+ * @dev: drm device -+ * @data: ioctl data blob -+ * @file: drm file -+ */ -+int -+i915_gem_sw_finish_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_gem_sw_finish *args = data; -+ struct drm_i915_gem_object *obj; -+ -+ obj = i915_gem_object_lookup(file, args->handle); -+ if (!obj) -+ return -ENOENT; -+ -+ /* -+ * Proxy objects are barred from CPU access, so there is no -+ * need to ban sw_finish as it is a nop. -+ */ -+ -+ /* Pinned buffers may be scanout, so flush the cache */ -+ i915_gem_object_flush_if_display(obj); -+ i915_gem_object_put(obj); -+ -+ return 0; -+} -+ -+static inline bool -+__vma_matches(struct vm_area_struct *vma, struct file *filp, -+ unsigned long addr, unsigned long size) -+{ -+ if (vma->vm_file != filp) -+ return false; -+ -+ return vma->vm_start == addr && -+ (vma->vm_end - vma->vm_start) == PAGE_ALIGN(size); -+} -+ -+/** -+ * i915_gem_mmap_ioctl - Maps the contents of an object, returning the address -+ * it is mapped to. -+ * @dev: drm device -+ * @data: ioctl data blob -+ * @file: drm file -+ * -+ * While the mapping holds a reference on the contents of the object, it doesn't -+ * imply a ref on the object itself. -+ * -+ * IMPORTANT: -+ * -+ * DRM driver writers who look a this function as an example for how to do GEM -+ * mmap support, please don't implement mmap support like here. The modern way -+ * to implement DRM mmap support is with an mmap offset ioctl (like -+ * i915_gem_mmap_gtt) and then using the mmap syscall on the DRM fd directly. -+ * That way debug tooling like valgrind will understand what's going on, hiding -+ * the mmap call in a driver private ioctl will break that. The i915 driver only -+ * does cpu mmaps this way because we didn't know better. -+ */ -+int -+i915_gem_mmap_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_gem_mmap *args = data; -+ struct drm_i915_gem_object *obj; -+ unsigned long addr; -+ -+ if (args->flags & ~(I915_MMAP_WC)) -+ return -EINVAL; -+ -+ if (args->flags & I915_MMAP_WC && !boot_cpu_has(X86_FEATURE_PAT)) -+ return -ENODEV; -+ -+ obj = i915_gem_object_lookup(file, args->handle); -+ if (!obj) -+ return -ENOENT; -+ -+ /* prime objects have no backing filp to GEM mmap -+ * pages from. -+ */ -+ if (!obj->base.filp) { -+ addr = -ENXIO; -+ goto err; -+ } -+ -+ if (range_overflows(args->offset, args->size, (u64)obj->base.size)) { -+ addr = -EINVAL; -+ goto err; -+ } -+ -+ addr = vm_mmap(obj->base.filp, 0, args->size, -+ PROT_READ | PROT_WRITE, MAP_SHARED, -+ args->offset); -+ if (IS_ERR_VALUE(addr)) -+ goto err; -+ -+ if (args->flags & I915_MMAP_WC) { -+ struct mm_struct *mm = current->mm; -+ struct vm_area_struct *vma; -+ -+ if (down_write_killable(&mm->mmap_sem)) { -+ addr = -EINTR; -+ goto err; -+ } -+ vma = find_vma(mm, addr); -+ if (vma && __vma_matches(vma, obj->base.filp, addr, args->size)) -+ vma->vm_page_prot = -+ pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); -+ else -+ addr = -ENOMEM; -+ up_write(&mm->mmap_sem); -+ if (IS_ERR_VALUE(addr)) -+ goto err; -+ -+ /* This may race, but that's ok, it only gets set */ -+ WRITE_ONCE(obj->frontbuffer_ggtt_origin, ORIGIN_CPU); -+ } -+ i915_gem_object_put(obj); -+ -+ args->addr_ptr = (u64)addr; -+ return 0; -+ -+err: -+ i915_gem_object_put(obj); -+ return addr; -+} -+ -+static unsigned int tile_row_pages(const struct drm_i915_gem_object *obj) -+{ -+ return i915_gem_object_get_tile_row_size(obj) >> PAGE_SHIFT; -+} -+ -+/** -+ * i915_gem_mmap_gtt_version - report the current feature set for GTT mmaps -+ * -+ * A history of the GTT mmap interface: -+ * -+ * 0 - Everything had to fit into the GTT. Both parties of a memcpy had to -+ * aligned and suitable for fencing, and still fit into the available -+ * mappable space left by the pinned display objects. A classic problem -+ * we called the page-fault-of-doom where we would ping-pong between -+ * two objects that could not fit inside the GTT and so the memcpy -+ * would page one object in at the expense of the other between every -+ * single byte. -+ * -+ * 1 - Objects can be any size, and have any compatible fencing (X Y, or none -+ * as set via i915_gem_set_tiling() [DRM_I915_GEM_SET_TILING]). If the -+ * object is too large for the available space (or simply too large -+ * for the mappable aperture!), a view is created instead and faulted -+ * into userspace. (This view is aligned and sized appropriately for -+ * fenced access.) -+ * -+ * 2 - Recognise WC as a separate cache domain so that we can flush the -+ * delayed writes via GTT before performing direct access via WC. -+ * -+ * 3 - Remove implicit set-domain(GTT) and synchronisation on initial -+ * pagefault; swapin remains transparent. -+ * -+ * Restrictions: -+ * -+ * * snoopable objects cannot be accessed via the GTT. It can cause machine -+ * hangs on some architectures, corruption on others. An attempt to service -+ * a GTT page fault from a snoopable object will generate a SIGBUS. -+ * -+ * * the object must be able to fit into RAM (physical memory, though no -+ * limited to the mappable aperture). -+ * -+ * -+ * Caveats: -+ * -+ * * a new GTT page fault will synchronize rendering from the GPU and flush -+ * all data to system memory. Subsequent access will not be synchronized. -+ * -+ * * all mappings are revoked on runtime device suspend. -+ * -+ * * there are only 8, 16 or 32 fence registers to share between all users -+ * (older machines require fence register for display and blitter access -+ * as well). Contention of the fence registers will cause the previous users -+ * to be unmapped and any new access will generate new page faults. -+ * -+ * * running out of memory while servicing a fault may generate a SIGBUS, -+ * rather than the expected SIGSEGV. -+ */ -+int i915_gem_mmap_gtt_version(void) -+{ -+ return 3; -+} -+ -+static inline struct i915_ggtt_view -+compute_partial_view(const struct drm_i915_gem_object *obj, -+ pgoff_t page_offset, -+ unsigned int chunk) -+{ -+ struct i915_ggtt_view view; -+ -+ if (i915_gem_object_is_tiled(obj)) -+ chunk = roundup(chunk, tile_row_pages(obj)); -+ -+ view.type = I915_GGTT_VIEW_PARTIAL; -+ view.partial.offset = rounddown(page_offset, chunk); -+ view.partial.size = -+ min_t(unsigned int, chunk, -+ (obj->base.size >> PAGE_SHIFT) - view.partial.offset); -+ -+ /* If the partial covers the entire object, just create a normal VMA. */ -+ if (chunk >= obj->base.size >> PAGE_SHIFT) -+ view.type = I915_GGTT_VIEW_NORMAL; -+ -+ return view; -+} -+ -+/** -+ * i915_gem_fault - fault a page into the GTT -+ * @vmf: fault info -+ * -+ * The fault handler is set up by drm_gem_mmap() when a object is GTT mapped -+ * from userspace. The fault handler takes care of binding the object to -+ * the GTT (if needed), allocating and programming a fence register (again, -+ * only if needed based on whether the old reg is still valid or the object -+ * is tiled) and inserting a new PTE into the faulting process. -+ * -+ * Note that the faulting process may involve evicting existing objects -+ * from the GTT and/or fence registers to make room. So performance may -+ * suffer if the GTT working set is large or there are few fence registers -+ * left. -+ * -+ * The current feature set supported by i915_gem_fault() and thus GTT mmaps -+ * is exposed via I915_PARAM_MMAP_GTT_VERSION (see i915_gem_mmap_gtt_version). -+ */ -+vm_fault_t i915_gem_fault(struct vm_fault *vmf) -+{ -+#define MIN_CHUNK_PAGES (SZ_1M >> PAGE_SHIFT) -+ struct vm_area_struct *area = vmf->vma; -+ struct drm_i915_gem_object *obj = to_intel_bo(area->vm_private_data); -+ struct drm_device *dev = obj->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct i915_ggtt *ggtt = &dev_priv->ggtt; -+ bool write = area->vm_flags & VM_WRITE; -+ intel_wakeref_t wakeref; -+ struct i915_vma *vma; -+ pgoff_t page_offset; -+ int srcu; -+ int ret; -+ -+ /* Sanity check that we allow writing into this object */ -+ if (i915_gem_object_is_readonly(obj) && write) -+ return VM_FAULT_SIGBUS; -+ -+ /* We don't use vmf->pgoff since that has the fake offset */ -+ page_offset = (vmf->address - area->vm_start) >> PAGE_SHIFT; -+ -+ trace_i915_gem_object_fault(obj, page_offset, true, write); -+ -+ ret = i915_gem_object_pin_pages(obj); -+ if (ret) -+ goto err; -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ -+ srcu = i915_reset_trylock(dev_priv); -+ if (srcu < 0) { -+ ret = srcu; -+ goto err_rpm; -+ } -+ -+ ret = i915_mutex_lock_interruptible(dev); -+ if (ret) -+ goto err_reset; -+ -+ /* Access to snoopable pages through the GTT is incoherent. */ -+ if (obj->cache_level != I915_CACHE_NONE && !HAS_LLC(dev_priv)) { -+ ret = -EFAULT; -+ goto err_unlock; -+ } -+ -+ /* Now pin it into the GTT as needed */ -+ vma = i915_gem_object_ggtt_pin(obj, NULL, 0, 0, -+ PIN_MAPPABLE | -+ PIN_NONBLOCK | -+ PIN_NONFAULT); -+ if (IS_ERR(vma)) { -+ /* Use a partial view if it is bigger than available space */ -+ struct i915_ggtt_view view = -+ compute_partial_view(obj, page_offset, MIN_CHUNK_PAGES); -+ unsigned int flags; -+ -+ flags = PIN_MAPPABLE; -+ if (view.type == I915_GGTT_VIEW_NORMAL) -+ flags |= PIN_NONBLOCK; /* avoid warnings for pinned */ -+ -+ /* -+ * Userspace is now writing through an untracked VMA, abandon -+ * all hope that the hardware is able to track future writes. -+ */ -+ obj->frontbuffer_ggtt_origin = ORIGIN_CPU; -+ -+ vma = i915_gem_object_ggtt_pin(obj, &view, 0, 0, flags); -+ if (IS_ERR(vma) && !view.type) { -+ flags = PIN_MAPPABLE; -+ view.type = I915_GGTT_VIEW_PARTIAL; -+ vma = i915_gem_object_ggtt_pin(obj, &view, 0, 0, flags); -+ } -+ } -+ if (IS_ERR(vma)) { -+ ret = PTR_ERR(vma); -+ goto err_unlock; -+ } -+ -+ ret = i915_vma_pin_fence(vma); -+ if (ret) -+ goto err_unpin; -+ -+ /* Finally, remap it using the new GTT offset */ -+ ret = remap_io_mapping(area, -+ area->vm_start + (vma->ggtt_view.partial.offset << PAGE_SHIFT), -+ (ggtt->gmadr.start + vma->node.start) >> PAGE_SHIFT, -+ min_t(u64, vma->size, area->vm_end - area->vm_start), -+ &ggtt->iomap); -+ if (ret) -+ goto err_fence; -+ -+ /* Mark as being mmapped into userspace for later revocation */ -+ assert_rpm_wakelock_held(dev_priv); -+ if (!i915_vma_set_userfault(vma) && !obj->userfault_count++) -+ list_add(&obj->userfault_link, &dev_priv->mm.userfault_list); -+ GEM_BUG_ON(!obj->userfault_count); -+ -+ i915_vma_set_ggtt_write(vma); -+ -+err_fence: -+ i915_vma_unpin_fence(vma); -+err_unpin: -+ __i915_vma_unpin(vma); -+err_unlock: -+ mutex_unlock(&dev->struct_mutex); -+err_reset: -+ i915_reset_unlock(dev_priv, srcu); -+err_rpm: -+ intel_runtime_pm_put(dev_priv, wakeref); -+ i915_gem_object_unpin_pages(obj); -+err: -+ switch (ret) { -+ case -EIO: -+ /* -+ * We eat errors when the gpu is terminally wedged to avoid -+ * userspace unduly crashing (gl has no provisions for mmaps to -+ * fail). But any other -EIO isn't ours (e.g. swap in failure) -+ * and so needs to be reported. -+ */ -+ if (!i915_terminally_wedged(dev_priv)) -+ return VM_FAULT_SIGBUS; -+ /* else: fall through */ -+ case -EAGAIN: -+ /* -+ * EAGAIN means the gpu is hung and we'll wait for the error -+ * handler to reset everything when re-faulting in -+ * i915_mutex_lock_interruptible. -+ */ -+ case 0: -+ case -ERESTARTSYS: -+ case -EINTR: -+ case -EBUSY: -+ /* -+ * EBUSY is ok: this just means that another thread -+ * already did the job. -+ */ -+ return VM_FAULT_NOPAGE; -+ case -ENOMEM: -+ return VM_FAULT_OOM; -+ case -ENOSPC: -+ case -EFAULT: -+ return VM_FAULT_SIGBUS; -+ default: -+ WARN_ONCE(ret, "unhandled error in i915_gem_fault: %i\n", ret); -+ return VM_FAULT_SIGBUS; -+ } -+} -+ -+static void __i915_gem_object_release_mmap(struct drm_i915_gem_object *obj) -+{ -+ struct i915_vma *vma; -+ -+ GEM_BUG_ON(!obj->userfault_count); -+ -+ obj->userfault_count = 0; -+ list_del(&obj->userfault_link); -+ drm_vma_node_unmap(&obj->base.vma_node, -+ obj->base.dev->anon_inode->i_mapping); -+ -+ for_each_ggtt_vma(vma, obj) -+ i915_vma_unset_userfault(vma); -+} -+ -+/** -+ * i915_gem_release_mmap - remove physical page mappings -+ * @obj: obj in question -+ * -+ * Preserve the reservation of the mmapping with the DRM core code, but -+ * relinquish ownership of the pages back to the system. -+ * -+ * It is vital that we remove the page mapping if we have mapped a tiled -+ * object through the GTT and then lose the fence register due to -+ * resource pressure. Similarly if the object has been moved out of the -+ * aperture, than pages mapped into userspace must be revoked. Removing the -+ * mapping will then trigger a page fault on the next user access, allowing -+ * fixup by i915_gem_fault(). -+ */ -+void -+i915_gem_release_mmap(struct drm_i915_gem_object *obj) -+{ -+ struct drm_i915_private *i915 = to_i915(obj->base.dev); -+ intel_wakeref_t wakeref; -+ -+ /* Serialisation between user GTT access and our code depends upon -+ * revoking the CPU's PTE whilst the mutex is held. The next user -+ * pagefault then has to wait until we release the mutex. -+ * -+ * Note that RPM complicates somewhat by adding an additional -+ * requirement that operations to the GGTT be made holding the RPM -+ * wakeref. -+ */ -+ lockdep_assert_held(&i915->drm.struct_mutex); -+ wakeref = intel_runtime_pm_get(i915); -+ -+ if (!obj->userfault_count) -+ goto out; -+ -+ __i915_gem_object_release_mmap(obj); -+ -+ /* Ensure that the CPU's PTE are revoked and there are not outstanding -+ * memory transactions from userspace before we return. The TLB -+ * flushing implied above by changing the PTE above *should* be -+ * sufficient, an extra barrier here just provides us with a bit -+ * of paranoid documentation about our requirement to serialise -+ * memory writes before touching registers / GSM. -+ */ -+ wmb(); -+ -+out: -+ intel_runtime_pm_put(i915, wakeref); -+} -+ -+void i915_gem_runtime_suspend(struct drm_i915_private *dev_priv) -+{ -+ struct drm_i915_gem_object *obj, *on; -+ int i; -+ -+ /* -+ * Only called during RPM suspend. All users of the userfault_list -+ * must be holding an RPM wakeref to ensure that this can not -+ * run concurrently with themselves (and use the struct_mutex for -+ * protection between themselves). -+ */ -+ -+ list_for_each_entry_safe(obj, on, -+ &dev_priv->mm.userfault_list, userfault_link) -+ __i915_gem_object_release_mmap(obj); -+ -+ /* The fence will be lost when the device powers down. If any were -+ * in use by hardware (i.e. they are pinned), we should not be powering -+ * down! All other fences will be reacquired by the user upon waking. -+ */ -+ for (i = 0; i < dev_priv->num_fence_regs; i++) { -+ struct drm_i915_fence_reg *reg = &dev_priv->fence_regs[i]; -+ -+ /* Ideally we want to assert that the fence register is not -+ * live at this point (i.e. that no piece of code will be -+ * trying to write through fence + GTT, as that both violates -+ * our tracking of activity and associated locking/barriers, -+ * but also is illegal given that the hw is powered down). -+ * -+ * Previously we used reg->pin_count as a "liveness" indicator. -+ * That is not sufficient, and we need a more fine-grained -+ * tool if we want to have a sanity check here. -+ */ -+ -+ if (!reg->vma) -+ continue; -+ -+ GEM_BUG_ON(i915_vma_has_userfault(reg->vma)); -+ reg->dirty = true; -+ } -+} -+ -+static int i915_gem_object_create_mmap_offset(struct drm_i915_gem_object *obj) -+{ -+ struct drm_i915_private *dev_priv = to_i915(obj->base.dev); -+ int err; -+ -+ err = drm_gem_create_mmap_offset(&obj->base); -+ if (likely(!err)) -+ return 0; -+ -+ /* Attempt to reap some mmap space from dead objects */ -+ do { -+ err = i915_gem_wait_for_idle(dev_priv, -+ I915_WAIT_INTERRUPTIBLE, -+ MAX_SCHEDULE_TIMEOUT); -+ if (err) -+ break; -+ -+ i915_gem_drain_freed_objects(dev_priv); -+ err = drm_gem_create_mmap_offset(&obj->base); -+ if (!err) -+ break; -+ -+ } while (flush_delayed_work(&dev_priv->gt.retire_work)); -+ -+ return err; -+} -+ -+static void i915_gem_object_free_mmap_offset(struct drm_i915_gem_object *obj) -+{ -+ drm_gem_free_mmap_offset(&obj->base); -+} -+ -+int -+i915_gem_mmap_gtt(struct drm_file *file, -+ struct drm_device *dev, -+ u32 handle, -+ u64 *offset) -+{ -+ struct drm_i915_gem_object *obj; -+ int ret; -+ -+ obj = i915_gem_object_lookup(file, handle); -+ if (!obj) -+ return -ENOENT; -+ -+ ret = i915_gem_object_create_mmap_offset(obj); -+ if (ret == 0) -+ *offset = drm_vma_node_offset_addr(&obj->base.vma_node); -+ -+ i915_gem_object_put(obj); -+ return ret; -+} -+ -+/** -+ * i915_gem_mmap_gtt_ioctl - prepare an object for GTT mmap'ing -+ * @dev: DRM device -+ * @data: GTT mapping ioctl data -+ * @file: GEM object info -+ * -+ * Simply returns the fake offset to userspace so it can mmap it. -+ * The mmap call will end up in drm_gem_mmap(), which will set things -+ * up so we can get faults in the handler above. -+ * -+ * The fault handler will take care of binding the object into the GTT -+ * (since it may have been evicted to make room for something), allocating -+ * a fence register, and mapping the appropriate aperture address into -+ * userspace. -+ */ -+int -+i915_gem_mmap_gtt_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_gem_mmap_gtt *args = data; -+ -+ return i915_gem_mmap_gtt(file, dev, args->handle, &args->offset); -+} -+ -+/* Immediately discard the backing storage */ -+static void -+i915_gem_object_truncate(struct drm_i915_gem_object *obj) -+{ -+ i915_gem_object_free_mmap_offset(obj); -+ -+ if (obj->base.filp == NULL) -+ return; -+ -+ /* Our goal here is to return as much of the memory as -+ * is possible back to the system as we are called from OOM. -+ * To do this we must instruct the shmfs to drop all of its -+ * backing pages, *now*. -+ */ -+ shmem_truncate_range(file_inode(obj->base.filp), 0, (loff_t)-1); -+ obj->mm.madv = __I915_MADV_PURGED; -+ obj->mm.pages = ERR_PTR(-EFAULT); -+} -+ -+/* Try to discard unwanted pages */ -+void __i915_gem_object_invalidate(struct drm_i915_gem_object *obj) -+{ -+ struct address_space *mapping; -+ -+ lockdep_assert_held(&obj->mm.lock); -+ GEM_BUG_ON(i915_gem_object_has_pages(obj)); -+ -+ switch (obj->mm.madv) { -+ case I915_MADV_DONTNEED: -+ i915_gem_object_truncate(obj); -+ case __I915_MADV_PURGED: -+ return; -+ } -+ -+ if (obj->base.filp == NULL) -+ return; -+ -+ mapping = obj->base.filp->f_mapping, -+ invalidate_mapping_pages(mapping, 0, (loff_t)-1); -+} -+ -+/* -+ * Move pages to appropriate lru and release the pagevec, decrementing the -+ * ref count of those pages. -+ */ -+static void check_release_pagevec(struct pagevec *pvec) -+{ -+ check_move_unevictable_pages(pvec); -+ __pagevec_release(pvec); -+ cond_resched(); -+} -+ -+static void -+i915_gem_object_put_pages_gtt(struct drm_i915_gem_object *obj, -+ struct sg_table *pages) -+{ -+ struct sgt_iter sgt_iter; -+ struct pagevec pvec; -+ struct page *page; -+ -+ __i915_gem_object_release_shmem(obj, pages, true); -+ i915_gem_gtt_finish_pages(obj, pages); -+ -+ if (i915_gem_object_needs_bit17_swizzle(obj)) -+ i915_gem_object_save_bit_17_swizzle(obj, pages); -+ -+ mapping_clear_unevictable(file_inode(obj->base.filp)->i_mapping); -+ -+ pagevec_init(&pvec); -+ for_each_sgt_page(page, sgt_iter, pages) { -+ if (obj->mm.dirty) -+ set_page_dirty(page); -+ -+ if (obj->mm.madv == I915_MADV_WILLNEED) -+ mark_page_accessed(page); -+ -+ if (!pagevec_add(&pvec, page)) -+ check_release_pagevec(&pvec); -+ } -+ if (pagevec_count(&pvec)) -+ check_release_pagevec(&pvec); -+ obj->mm.dirty = false; -+ -+ sg_free_table(pages); -+ kfree(pages); -+} -+ -+static void __i915_gem_object_reset_page_iter(struct drm_i915_gem_object *obj) -+{ -+ struct radix_tree_iter iter; -+ void __rcu **slot; -+ -+ rcu_read_lock(); -+ radix_tree_for_each_slot(slot, &obj->mm.get_page.radix, &iter, 0) -+ radix_tree_delete(&obj->mm.get_page.radix, iter.index); -+ rcu_read_unlock(); -+} -+ -+static struct sg_table * -+__i915_gem_object_unset_pages(struct drm_i915_gem_object *obj) -+{ -+ struct drm_i915_private *i915 = to_i915(obj->base.dev); -+ struct sg_table *pages; -+ -+ pages = fetch_and_zero(&obj->mm.pages); -+ if (IS_ERR_OR_NULL(pages)) -+ return pages; -+ -+ spin_lock(&i915->mm.obj_lock); -+ list_del(&obj->mm.link); -+ spin_unlock(&i915->mm.obj_lock); -+ -+ if (obj->mm.mapping) { -+ void *ptr; -+ -+ ptr = page_mask_bits(obj->mm.mapping); -+ if (is_vmalloc_addr(ptr)) -+ vunmap(ptr); -+ else -+ kunmap(kmap_to_page(ptr)); -+ -+ obj->mm.mapping = NULL; -+ } -+ -+ __i915_gem_object_reset_page_iter(obj); -+ obj->mm.page_sizes.phys = obj->mm.page_sizes.sg = 0; -+ -+ return pages; -+} -+ -+int __i915_gem_object_put_pages(struct drm_i915_gem_object *obj, -+ enum i915_mm_subclass subclass) -+{ -+ struct sg_table *pages; -+ int ret; -+ -+ if (i915_gem_object_has_pinned_pages(obj)) -+ return -EBUSY; -+ -+ GEM_BUG_ON(obj->bind_count); -+ -+ /* May be called by shrinker from within get_pages() (on another bo) */ -+ mutex_lock_nested(&obj->mm.lock, subclass); -+ if (unlikely(atomic_read(&obj->mm.pages_pin_count))) { -+ ret = -EBUSY; -+ goto unlock; -+ } -+ -+ /* -+ * ->put_pages might need to allocate memory for the bit17 swizzle -+ * array, hence protect them from being reaped by removing them from gtt -+ * lists early. -+ */ -+ pages = __i915_gem_object_unset_pages(obj); -+ -+ /* -+ * XXX Temporary hijinx to avoid updating all backends to handle -+ * NULL pages. In the future, when we have more asynchronous -+ * get_pages backends we should be better able to handle the -+ * cancellation of the async task in a more uniform manner. -+ */ -+ if (!pages && !i915_gem_object_needs_async_cancel(obj)) -+ pages = ERR_PTR(-EINVAL); -+ -+ if (!IS_ERR(pages)) -+ obj->ops->put_pages(obj, pages); -+ -+ ret = 0; -+unlock: -+ mutex_unlock(&obj->mm.lock); -+ -+ return ret; -+} -+ -+bool i915_sg_trim(struct sg_table *orig_st) -+{ -+ struct sg_table new_st; -+ struct scatterlist *sg, *new_sg; -+ unsigned int i; -+ -+ if (orig_st->nents == orig_st->orig_nents) -+ return false; -+ -+ if (sg_alloc_table(&new_st, orig_st->nents, GFP_KERNEL | __GFP_NOWARN)) -+ return false; -+ -+ new_sg = new_st.sgl; -+ for_each_sg(orig_st->sgl, sg, orig_st->nents, i) { -+ sg_set_page(new_sg, sg_page(sg), sg->length, 0); -+ sg_dma_address(new_sg) = sg_dma_address(sg); -+ sg_dma_len(new_sg) = sg_dma_len(sg); -+ -+ new_sg = sg_next(new_sg); -+ } -+ GEM_BUG_ON(new_sg); /* Should walk exactly nents and hit the end */ -+ -+ sg_free_table(orig_st); -+ -+ *orig_st = new_st; -+ return true; -+} -+ -+static int i915_gem_object_get_pages_gtt(struct drm_i915_gem_object *obj) -+{ -+ struct drm_i915_private *dev_priv = to_i915(obj->base.dev); -+ const unsigned long page_count = obj->base.size / PAGE_SIZE; -+ unsigned long i; -+ struct address_space *mapping; -+ struct sg_table *st; -+ struct scatterlist *sg; -+ struct sgt_iter sgt_iter; -+ struct page *page; -+ unsigned long last_pfn = 0; /* suppress gcc warning */ -+ unsigned int max_segment = i915_sg_segment_size(); -+ unsigned int sg_page_sizes; -+ struct pagevec pvec; -+ gfp_t noreclaim; -+ int ret; -+ -+ /* -+ * Assert that the object is not currently in any GPU domain. As it -+ * wasn't in the GTT, there shouldn't be any way it could have been in -+ * a GPU cache -+ */ -+ GEM_BUG_ON(obj->read_domains & I915_GEM_GPU_DOMAINS); -+ GEM_BUG_ON(obj->write_domain & I915_GEM_GPU_DOMAINS); -+ -+ /* -+ * If there's no chance of allocating enough pages for the whole -+ * object, bail early. -+ */ -+ if (page_count > totalram_pages()) -+ return -ENOMEM; -+ -+ st = kmalloc(sizeof(*st), GFP_KERNEL); -+ if (st == NULL) -+ return -ENOMEM; -+ -+rebuild_st: -+ if (sg_alloc_table(st, page_count, GFP_KERNEL)) { -+ kfree(st); -+ return -ENOMEM; -+ } -+ -+ /* -+ * Get the list of pages out of our struct file. They'll be pinned -+ * at this point until we release them. -+ * -+ * Fail silently without starting the shrinker -+ */ -+ mapping = obj->base.filp->f_mapping; -+ mapping_set_unevictable(mapping); -+ noreclaim = mapping_gfp_constraint(mapping, ~__GFP_RECLAIM); -+ noreclaim |= __GFP_NORETRY | __GFP_NOWARN; -+ -+ sg = st->sgl; -+ st->nents = 0; -+ sg_page_sizes = 0; -+ for (i = 0; i < page_count; i++) { -+ const unsigned int shrink[] = { -+ I915_SHRINK_BOUND | I915_SHRINK_UNBOUND | I915_SHRINK_PURGEABLE, -+ 0, -+ }, *s = shrink; -+ gfp_t gfp = noreclaim; -+ -+ do { -+ cond_resched(); -+ page = shmem_read_mapping_page_gfp(mapping, i, gfp); -+ if (!IS_ERR(page)) -+ break; -+ -+ if (!*s) { -+ ret = PTR_ERR(page); -+ goto err_sg; -+ } -+ -+ i915_gem_shrink(dev_priv, 2 * page_count, NULL, *s++); -+ -+ /* -+ * We've tried hard to allocate the memory by reaping -+ * our own buffer, now let the real VM do its job and -+ * go down in flames if truly OOM. -+ * -+ * However, since graphics tend to be disposable, -+ * defer the oom here by reporting the ENOMEM back -+ * to userspace. -+ */ -+ if (!*s) { -+ /* reclaim and warn, but no oom */ -+ gfp = mapping_gfp_mask(mapping); -+ -+ /* -+ * Our bo are always dirty and so we require -+ * kswapd to reclaim our pages (direct reclaim -+ * does not effectively begin pageout of our -+ * buffers on its own). However, direct reclaim -+ * only waits for kswapd when under allocation -+ * congestion. So as a result __GFP_RECLAIM is -+ * unreliable and fails to actually reclaim our -+ * dirty pages -- unless you try over and over -+ * again with !__GFP_NORETRY. However, we still -+ * want to fail this allocation rather than -+ * trigger the out-of-memory killer and for -+ * this we want __GFP_RETRY_MAYFAIL. -+ */ -+ gfp |= __GFP_RETRY_MAYFAIL; -+ } -+ } while (1); -+ -+ if (!i || -+ sg->length >= max_segment || -+ page_to_pfn(page) != last_pfn + 1) { -+ if (i) { -+ sg_page_sizes |= sg->length; -+ sg = sg_next(sg); -+ } -+ st->nents++; -+ sg_set_page(sg, page, PAGE_SIZE, 0); -+ } else { -+ sg->length += PAGE_SIZE; -+ } -+ last_pfn = page_to_pfn(page); -+ -+ /* Check that the i965g/gm workaround works. */ -+ WARN_ON((gfp & __GFP_DMA32) && (last_pfn >= 0x00100000UL)); -+ } -+ if (sg) { /* loop terminated early; short sg table */ -+ sg_page_sizes |= sg->length; -+ sg_mark_end(sg); -+ } -+ -+ /* Trim unused sg entries to avoid wasting memory. */ -+ i915_sg_trim(st); -+ -+ ret = i915_gem_gtt_prepare_pages(obj, st); -+ if (ret) { -+ /* -+ * DMA remapping failed? One possible cause is that -+ * it could not reserve enough large entries, asking -+ * for PAGE_SIZE chunks instead may be helpful. -+ */ -+ if (max_segment > PAGE_SIZE) { -+ for_each_sgt_page(page, sgt_iter, st) -+ put_page(page); -+ sg_free_table(st); -+ -+ max_segment = PAGE_SIZE; -+ goto rebuild_st; -+ } else { -+ dev_warn(&dev_priv->drm.pdev->dev, -+ "Failed to DMA remap %lu pages\n", -+ page_count); -+ goto err_pages; -+ } -+ } -+ -+ if (i915_gem_object_needs_bit17_swizzle(obj)) -+ i915_gem_object_do_bit_17_swizzle(obj, st); -+ -+ __i915_gem_object_set_pages(obj, st, sg_page_sizes); -+ -+ return 0; -+ -+err_sg: -+ sg_mark_end(sg); -+err_pages: -+ mapping_clear_unevictable(mapping); -+ pagevec_init(&pvec); -+ for_each_sgt_page(page, sgt_iter, st) { -+ if (!pagevec_add(&pvec, page)) -+ check_release_pagevec(&pvec); -+ } -+ if (pagevec_count(&pvec)) -+ check_release_pagevec(&pvec); -+ sg_free_table(st); -+ kfree(st); -+ -+ /* -+ * shmemfs first checks if there is enough memory to allocate the page -+ * and reports ENOSPC should there be insufficient, along with the usual -+ * ENOMEM for a genuine allocation failure. -+ * -+ * We use ENOSPC in our driver to mean that we have run out of aperture -+ * space and so want to translate the error from shmemfs back to our -+ * usual understanding of ENOMEM. -+ */ -+ if (ret == -ENOSPC) -+ ret = -ENOMEM; -+ -+ return ret; -+} -+ -+void __i915_gem_object_set_pages(struct drm_i915_gem_object *obj, -+ struct sg_table *pages, -+ unsigned int sg_page_sizes) -+{ -+ struct drm_i915_private *i915 = to_i915(obj->base.dev); -+ unsigned long supported = INTEL_INFO(i915)->page_sizes; -+ int i; -+ -+ lockdep_assert_held(&obj->mm.lock); -+ -+ /* Make the pages coherent with the GPU (flushing any swapin). */ -+ if (obj->cache_dirty) { -+ obj->write_domain = 0; -+ if (i915_gem_object_has_struct_page(obj)) -+ drm_clflush_sg(pages); -+ obj->cache_dirty = false; -+ } -+ -+ obj->mm.get_page.sg_pos = pages->sgl; -+ obj->mm.get_page.sg_idx = 0; -+ -+ obj->mm.pages = pages; -+ -+ if (i915_gem_object_is_tiled(obj) && -+ i915->quirks & QUIRK_PIN_SWIZZLED_PAGES) { -+ GEM_BUG_ON(obj->mm.quirked); -+ __i915_gem_object_pin_pages(obj); -+ obj->mm.quirked = true; -+ } -+ -+ GEM_BUG_ON(!sg_page_sizes); -+ obj->mm.page_sizes.phys = sg_page_sizes; -+ -+ /* -+ * Calculate the supported page-sizes which fit into the given -+ * sg_page_sizes. This will give us the page-sizes which we may be able -+ * to use opportunistically when later inserting into the GTT. For -+ * example if phys=2G, then in theory we should be able to use 1G, 2M, -+ * 64K or 4K pages, although in practice this will depend on a number of -+ * other factors. -+ */ -+ obj->mm.page_sizes.sg = 0; -+ for_each_set_bit(i, &supported, ilog2(I915_GTT_MAX_PAGE_SIZE) + 1) { -+ if (obj->mm.page_sizes.phys & ~0u << i) -+ obj->mm.page_sizes.sg |= BIT(i); -+ } -+ GEM_BUG_ON(!HAS_PAGE_SIZES(i915, obj->mm.page_sizes.sg)); -+ -+ spin_lock(&i915->mm.obj_lock); -+ list_add(&obj->mm.link, &i915->mm.unbound_list); -+ spin_unlock(&i915->mm.obj_lock); -+} -+ -+static int ____i915_gem_object_get_pages(struct drm_i915_gem_object *obj) -+{ -+ int err; -+ -+ if (unlikely(obj->mm.madv != I915_MADV_WILLNEED)) { -+ DRM_DEBUG("Attempting to obtain a purgeable object\n"); -+ return -EFAULT; -+ } -+ -+ err = obj->ops->get_pages(obj); -+ GEM_BUG_ON(!err && !i915_gem_object_has_pages(obj)); -+ -+ return err; -+} -+ -+/* Ensure that the associated pages are gathered from the backing storage -+ * and pinned into our object. i915_gem_object_pin_pages() may be called -+ * multiple times before they are released by a single call to -+ * i915_gem_object_unpin_pages() - once the pages are no longer referenced -+ * either as a result of memory pressure (reaping pages under the shrinker) -+ * or as the object is itself released. -+ */ -+int __i915_gem_object_get_pages(struct drm_i915_gem_object *obj) -+{ -+ int err; -+ -+ err = mutex_lock_interruptible(&obj->mm.lock); -+ if (err) -+ return err; -+ -+ if (unlikely(!i915_gem_object_has_pages(obj))) { -+ GEM_BUG_ON(i915_gem_object_has_pinned_pages(obj)); -+ -+ err = ____i915_gem_object_get_pages(obj); -+ if (err) -+ goto unlock; -+ -+ smp_mb__before_atomic(); -+ } -+ atomic_inc(&obj->mm.pages_pin_count); -+ -+unlock: -+ mutex_unlock(&obj->mm.lock); -+ return err; -+} -+ -+/* The 'mapping' part of i915_gem_object_pin_map() below */ -+static void *i915_gem_object_map(const struct drm_i915_gem_object *obj, -+ enum i915_map_type type) -+{ -+ unsigned long n_pages = obj->base.size >> PAGE_SHIFT; -+ struct sg_table *sgt = obj->mm.pages; -+ struct sgt_iter sgt_iter; -+ struct page *page; -+ struct page *stack_pages[32]; -+ struct page **pages = stack_pages; -+ unsigned long i = 0; -+ pgprot_t pgprot; -+ void *addr; -+ -+ /* A single page can always be kmapped */ -+ if (n_pages == 1 && type == I915_MAP_WB) -+ return kmap(sg_page(sgt->sgl)); -+ -+ if (n_pages > ARRAY_SIZE(stack_pages)) { -+ /* Too big for stack -- allocate temporary array instead */ -+ pages = kvmalloc_array(n_pages, sizeof(*pages), GFP_KERNEL); -+ if (!pages) -+ return NULL; -+ } -+ -+ for_each_sgt_page(page, sgt_iter, sgt) -+ pages[i++] = page; -+ -+ /* Check that we have the expected number of pages */ -+ GEM_BUG_ON(i != n_pages); -+ -+ switch (type) { -+ default: -+ MISSING_CASE(type); -+ /* fallthrough to use PAGE_KERNEL anyway */ -+ case I915_MAP_WB: -+ pgprot = PAGE_KERNEL; -+ break; -+ case I915_MAP_WC: -+ pgprot = pgprot_writecombine(PAGE_KERNEL_IO); -+ break; -+ } -+ addr = vmap(pages, n_pages, 0, pgprot); -+ -+ if (pages != stack_pages) -+ kvfree(pages); -+ -+ return addr; -+} -+ -+/* get, pin, and map the pages of the object into kernel space */ -+void *i915_gem_object_pin_map(struct drm_i915_gem_object *obj, -+ enum i915_map_type type) -+{ -+ enum i915_map_type has_type; -+ bool pinned; -+ void *ptr; -+ int ret; -+ -+ if (unlikely(!i915_gem_object_has_struct_page(obj))) -+ return ERR_PTR(-ENXIO); -+ -+ ret = mutex_lock_interruptible(&obj->mm.lock); -+ if (ret) -+ return ERR_PTR(ret); -+ -+ pinned = !(type & I915_MAP_OVERRIDE); -+ type &= ~I915_MAP_OVERRIDE; -+ -+ if (!atomic_inc_not_zero(&obj->mm.pages_pin_count)) { -+ if (unlikely(!i915_gem_object_has_pages(obj))) { -+ GEM_BUG_ON(i915_gem_object_has_pinned_pages(obj)); -+ -+ ret = ____i915_gem_object_get_pages(obj); -+ if (ret) -+ goto err_unlock; -+ -+ smp_mb__before_atomic(); -+ } -+ atomic_inc(&obj->mm.pages_pin_count); -+ pinned = false; -+ } -+ GEM_BUG_ON(!i915_gem_object_has_pages(obj)); -+ -+ ptr = page_unpack_bits(obj->mm.mapping, &has_type); -+ if (ptr && has_type != type) { -+ if (pinned) { -+ ret = -EBUSY; -+ goto err_unpin; -+ } -+ -+ if (is_vmalloc_addr(ptr)) -+ vunmap(ptr); -+ else -+ kunmap(kmap_to_page(ptr)); -+ -+ ptr = obj->mm.mapping = NULL; -+ } -+ -+ if (!ptr) { -+ ptr = i915_gem_object_map(obj, type); -+ if (!ptr) { -+ ret = -ENOMEM; -+ goto err_unpin; -+ } -+ -+ obj->mm.mapping = page_pack_bits(ptr, type); -+ } -+ -+out_unlock: -+ mutex_unlock(&obj->mm.lock); -+ return ptr; -+ -+err_unpin: -+ atomic_dec(&obj->mm.pages_pin_count); -+err_unlock: -+ ptr = ERR_PTR(ret); -+ goto out_unlock; -+} -+ -+void __i915_gem_object_flush_map(struct drm_i915_gem_object *obj, -+ unsigned long offset, -+ unsigned long size) -+{ -+ enum i915_map_type has_type; -+ void *ptr; -+ -+ GEM_BUG_ON(!i915_gem_object_has_pinned_pages(obj)); -+ GEM_BUG_ON(range_overflows_t(typeof(obj->base.size), -+ offset, size, obj->base.size)); -+ -+ obj->mm.dirty = true; -+ -+ if (obj->cache_coherent & I915_BO_CACHE_COHERENT_FOR_WRITE) -+ return; -+ -+ ptr = page_unpack_bits(obj->mm.mapping, &has_type); -+ if (has_type == I915_MAP_WC) -+ return; -+ -+ drm_clflush_virt_range(ptr + offset, size); -+ if (size == obj->base.size) { -+ obj->write_domain &= ~I915_GEM_DOMAIN_CPU; -+ obj->cache_dirty = false; -+ } -+} -+ -+static int -+i915_gem_object_pwrite_gtt(struct drm_i915_gem_object *obj, -+ const struct drm_i915_gem_pwrite *arg) -+{ -+ struct address_space *mapping = obj->base.filp->f_mapping; -+ char __user *user_data = u64_to_user_ptr(arg->data_ptr); -+ u64 remain, offset; -+ unsigned int pg; -+ -+ /* Caller already validated user args */ -+ GEM_BUG_ON(!access_ok(user_data, arg->size)); -+ -+ /* -+ * Before we instantiate/pin the backing store for our use, we -+ * can prepopulate the shmemfs filp efficiently using a write into -+ * the pagecache. We avoid the penalty of instantiating all the -+ * pages, important if the user is just writing to a few and never -+ * uses the object on the GPU, and using a direct write into shmemfs -+ * allows it to avoid the cost of retrieving a page (either swapin -+ * or clearing-before-use) before it is overwritten. -+ */ -+ if (i915_gem_object_has_pages(obj)) -+ return -ENODEV; -+ -+ if (obj->mm.madv != I915_MADV_WILLNEED) -+ return -EFAULT; -+ -+ /* -+ * Before the pages are instantiated the object is treated as being -+ * in the CPU domain. The pages will be clflushed as required before -+ * use, and we can freely write into the pages directly. If userspace -+ * races pwrite with any other operation; corruption will ensue - -+ * that is userspace's prerogative! -+ */ -+ -+ remain = arg->size; -+ offset = arg->offset; -+ pg = offset_in_page(offset); -+ -+ do { -+ unsigned int len, unwritten; -+ struct page *page; -+ void *data, *vaddr; -+ int err; -+ char c; -+ -+ len = PAGE_SIZE - pg; -+ if (len > remain) -+ len = remain; -+ -+ /* Prefault the user page to reduce potential recursion */ -+ err = __get_user(c, user_data); -+ if (err) -+ return err; -+ -+ err = __get_user(c, user_data + len - 1); -+ if (err) -+ return err; -+ -+ err = pagecache_write_begin(obj->base.filp, mapping, -+ offset, len, 0, -+ &page, &data); -+ if (err < 0) -+ return err; -+ -+ vaddr = kmap_atomic(page); -+ unwritten = __copy_from_user_inatomic(vaddr + pg, -+ user_data, -+ len); -+ kunmap_atomic(vaddr); -+ -+ err = pagecache_write_end(obj->base.filp, mapping, -+ offset, len, len - unwritten, -+ page, data); -+ if (err < 0) -+ return err; -+ -+ /* We don't handle -EFAULT, leave it to the caller to check */ -+ if (unwritten) -+ return -ENODEV; -+ -+ remain -= len; -+ user_data += len; -+ offset += len; -+ pg = 0; -+ } while (remain); -+ -+ return 0; -+} -+ -+static void -+i915_gem_retire_work_handler(struct work_struct *work) -+{ -+ struct drm_i915_private *dev_priv = -+ container_of(work, typeof(*dev_priv), gt.retire_work.work); -+ struct drm_device *dev = &dev_priv->drm; -+ -+ /* Come back later if the device is busy... */ -+ if (mutex_trylock(&dev->struct_mutex)) { -+ i915_retire_requests(dev_priv); -+ mutex_unlock(&dev->struct_mutex); -+ } -+ -+ /* -+ * Keep the retire handler running until we are finally idle. -+ * We do not need to do this test under locking as in the worst-case -+ * we queue the retire worker once too often. -+ */ -+ if (READ_ONCE(dev_priv->gt.awake)) -+ queue_delayed_work(dev_priv->wq, -+ &dev_priv->gt.retire_work, -+ round_jiffies_up_relative(HZ)); -+} -+ -+static bool switch_to_kernel_context_sync(struct drm_i915_private *i915, -+ unsigned long mask) -+{ -+ bool result = true; -+ -+ /* -+ * Even if we fail to switch, give whatever is running a small chance -+ * to save itself before we report the failure. Yes, this may be a -+ * false positive due to e.g. ENOMEM, caveat emptor! -+ */ -+ if (i915_gem_switch_to_kernel_context(i915, mask)) -+ result = false; -+ -+ if (i915_gem_wait_for_idle(i915, -+ I915_WAIT_LOCKED | -+ I915_WAIT_FOR_IDLE_BOOST, -+ I915_GEM_IDLE_TIMEOUT)) -+ result = false; -+ -+ if (!result) { -+ if (i915_modparams.reset) { /* XXX hide warning from gem_eio */ -+ dev_err(i915->drm.dev, -+ "Failed to idle engines, declaring wedged!\n"); -+ GEM_TRACE_DUMP(); -+ } -+ -+ /* Forcibly cancel outstanding work and leave the gpu quiet. */ -+ i915_gem_set_wedged(i915); -+ } -+ -+ i915_retire_requests(i915); /* ensure we flush after wedging */ -+ return result; -+} -+ -+static bool load_power_context(struct drm_i915_private *i915) -+{ -+ /* Force loading the kernel context on all engines */ -+ if (!switch_to_kernel_context_sync(i915, ALL_ENGINES)) -+ return false; -+ -+ /* -+ * Immediately park the GPU so that we enable powersaving and -+ * treat it as idle. The next time we issue a request, we will -+ * unpark and start using the engine->pinned_default_state, otherwise -+ * it is in limbo and an early reset may fail. -+ */ -+ __i915_gem_park(i915); -+ -+ return true; -+} -+ -+static void -+i915_gem_idle_work_handler(struct work_struct *work) -+{ -+ struct drm_i915_private *i915 = -+ container_of(work, typeof(*i915), gt.idle_work.work); -+ bool rearm_hangcheck; -+ -+ if (!READ_ONCE(i915->gt.awake)) -+ return; -+ -+ if (READ_ONCE(i915->gt.active_requests)) -+ return; -+ -+ rearm_hangcheck = -+ cancel_delayed_work_sync(&i915->gpu_error.hangcheck_work); -+ -+ if (!mutex_trylock(&i915->drm.struct_mutex)) { -+ /* Currently busy, come back later */ -+ mod_delayed_work(i915->wq, -+ &i915->gt.idle_work, -+ msecs_to_jiffies(50)); -+ goto out_rearm; -+ } -+ -+ /* -+ * Flush out the last user context, leaving only the pinned -+ * kernel context resident. Should anything unfortunate happen -+ * while we are idle (such as the GPU being power cycled), no users -+ * will be harmed. -+ */ -+ if (!work_pending(&i915->gt.idle_work.work) && -+ !i915->gt.active_requests) { -+ ++i915->gt.active_requests; /* don't requeue idle */ -+ -+ switch_to_kernel_context_sync(i915, i915->gt.active_engines); -+ -+ if (!--i915->gt.active_requests) { -+ __i915_gem_park(i915); -+ rearm_hangcheck = false; -+ } -+ } -+ -+ mutex_unlock(&i915->drm.struct_mutex); -+ -+out_rearm: -+ if (rearm_hangcheck) { -+ GEM_BUG_ON(!i915->gt.awake); -+ i915_queue_hangcheck(i915); -+ } -+} -+ -+void i915_gem_close_object(struct drm_gem_object *gem, struct drm_file *file) -+{ -+ struct drm_i915_private *i915 = to_i915(gem->dev); -+ struct drm_i915_gem_object *obj = to_intel_bo(gem); -+ struct drm_i915_file_private *fpriv = file->driver_priv; -+ struct i915_lut_handle *lut, *ln; -+ -+ mutex_lock(&i915->drm.struct_mutex); -+ -+ list_for_each_entry_safe(lut, ln, &obj->lut_list, obj_link) { -+ struct i915_gem_context *ctx = lut->ctx; -+ struct i915_vma *vma; -+ -+ GEM_BUG_ON(ctx->file_priv == ERR_PTR(-EBADF)); -+ if (ctx->file_priv != fpriv) -+ continue; -+ -+ vma = radix_tree_delete(&ctx->handles_vma, lut->handle); -+ GEM_BUG_ON(vma->obj != obj); -+ -+ /* We allow the process to have multiple handles to the same -+ * vma, in the same fd namespace, by virtue of flink/open. -+ */ -+ GEM_BUG_ON(!vma->open_count); -+ if (!--vma->open_count && !i915_vma_is_ggtt(vma)) -+ i915_vma_close(vma); -+ -+ list_del(&lut->obj_link); -+ list_del(&lut->ctx_link); -+ -+ i915_lut_handle_free(lut); -+ __i915_gem_object_release_unless_active(obj); -+ } -+ -+ mutex_unlock(&i915->drm.struct_mutex); -+} -+ -+static unsigned long to_wait_timeout(s64 timeout_ns) -+{ -+ if (timeout_ns < 0) -+ return MAX_SCHEDULE_TIMEOUT; -+ -+ if (timeout_ns == 0) -+ return 0; -+ -+ return nsecs_to_jiffies_timeout(timeout_ns); -+} -+ -+/** -+ * i915_gem_wait_ioctl - implements DRM_IOCTL_I915_GEM_WAIT -+ * @dev: drm device pointer -+ * @data: ioctl data blob -+ * @file: drm file pointer -+ * -+ * Returns 0 if successful, else an error is returned with the remaining time in -+ * the timeout parameter. -+ * -ETIME: object is still busy after timeout -+ * -ERESTARTSYS: signal interrupted the wait -+ * -ENONENT: object doesn't exist -+ * Also possible, but rare: -+ * -EAGAIN: incomplete, restart syscall -+ * -ENOMEM: damn -+ * -ENODEV: Internal IRQ fail -+ * -E?: The add request failed -+ * -+ * The wait ioctl with a timeout of 0 reimplements the busy ioctl. With any -+ * non-zero timeout parameter the wait ioctl will wait for the given number of -+ * nanoseconds on an object becoming unbusy. Since the wait itself does so -+ * without holding struct_mutex the object may become re-busied before this -+ * function completes. A similar but shorter * race condition exists in the busy -+ * ioctl -+ */ -+int -+i915_gem_wait_ioctl(struct drm_device *dev, void *data, struct drm_file *file) -+{ -+ struct drm_i915_gem_wait *args = data; -+ struct drm_i915_gem_object *obj; -+ ktime_t start; -+ long ret; -+ -+ if (args->flags != 0) -+ return -EINVAL; -+ -+ obj = i915_gem_object_lookup(file, args->bo_handle); -+ if (!obj) -+ return -ENOENT; -+ -+ start = ktime_get(); -+ -+ ret = i915_gem_object_wait(obj, -+ I915_WAIT_INTERRUPTIBLE | -+ I915_WAIT_PRIORITY | -+ I915_WAIT_ALL, -+ to_wait_timeout(args->timeout_ns)); -+ -+ if (args->timeout_ns > 0) { -+ args->timeout_ns -= ktime_to_ns(ktime_sub(ktime_get(), start)); -+ if (args->timeout_ns < 0) -+ args->timeout_ns = 0; -+ -+ /* -+ * Apparently ktime isn't accurate enough and occasionally has a -+ * bit of mismatch in the jiffies<->nsecs<->ktime loop. So patch -+ * things up to make the test happy. We allow up to 1 jiffy. -+ * -+ * This is a regression from the timespec->ktime conversion. -+ */ -+ if (ret == -ETIME && !nsecs_to_jiffies(args->timeout_ns)) -+ args->timeout_ns = 0; -+ -+ /* Asked to wait beyond the jiffie/scheduler precision? */ -+ if (ret == -ETIME && args->timeout_ns) -+ ret = -EAGAIN; -+ } -+ -+ i915_gem_object_put(obj); -+ return ret; -+} -+ -+static int wait_for_engines(struct drm_i915_private *i915) -+{ -+ if (wait_for(intel_engines_are_idle(i915), I915_IDLE_ENGINES_TIMEOUT)) { -+ dev_err(i915->drm.dev, -+ "Failed to idle engines, declaring wedged!\n"); -+ GEM_TRACE_DUMP(); -+ i915_gem_set_wedged(i915); -+ return -EIO; -+ } -+ -+ return 0; -+} -+ -+static long -+wait_for_timelines(struct drm_i915_private *i915, -+ unsigned int flags, long timeout) -+{ -+ struct i915_gt_timelines *gt = &i915->gt.timelines; -+ struct i915_timeline *tl; -+ -+ if (!READ_ONCE(i915->gt.active_requests)) -+ return timeout; -+ -+ mutex_lock(>->mutex); -+ list_for_each_entry(tl, >->active_list, link) { -+ struct i915_request *rq; -+ -+ rq = i915_active_request_get_unlocked(&tl->last_request); -+ if (!rq) -+ continue; -+ -+ mutex_unlock(>->mutex); -+ -+ /* -+ * "Race-to-idle". -+ * -+ * Switching to the kernel context is often used a synchronous -+ * step prior to idling, e.g. in suspend for flushing all -+ * current operations to memory before sleeping. These we -+ * want to complete as quickly as possible to avoid prolonged -+ * stalls, so allow the gpu to boost to maximum clocks. -+ */ -+ if (flags & I915_WAIT_FOR_IDLE_BOOST) -+ gen6_rps_boost(rq); -+ -+ timeout = i915_request_wait(rq, flags, timeout); -+ i915_request_put(rq); -+ if (timeout < 0) -+ return timeout; -+ -+ /* restart after reacquiring the lock */ -+ mutex_lock(>->mutex); -+ tl = list_entry(>->active_list, typeof(*tl), link); -+ } -+ mutex_unlock(>->mutex); -+ -+ return timeout; -+} -+ -+int i915_gem_wait_for_idle(struct drm_i915_private *i915, -+ unsigned int flags, long timeout) -+{ -+ GEM_TRACE("flags=%x (%s), timeout=%ld%s\n", -+ flags, flags & I915_WAIT_LOCKED ? "locked" : "unlocked", -+ timeout, timeout == MAX_SCHEDULE_TIMEOUT ? " (forever)" : ""); -+ -+ /* If the device is asleep, we have no requests outstanding */ -+ if (!READ_ONCE(i915->gt.awake)) -+ return 0; -+ -+ timeout = wait_for_timelines(i915, flags, timeout); -+ if (timeout < 0) -+ return timeout; -+ -+ if (flags & I915_WAIT_LOCKED) { -+ int err; -+ -+ lockdep_assert_held(&i915->drm.struct_mutex); -+ -+ err = wait_for_engines(i915); -+ if (err) -+ return err; -+ -+ i915_retire_requests(i915); -+ } -+ -+ return 0; -+} -+ -+static void __i915_gem_object_flush_for_display(struct drm_i915_gem_object *obj) -+{ -+ /* -+ * We manually flush the CPU domain so that we can override and -+ * force the flush for the display, and perform it asyncrhonously. -+ */ -+ flush_write_domain(obj, ~I915_GEM_DOMAIN_CPU); -+ if (obj->cache_dirty) -+ i915_gem_clflush_object(obj, I915_CLFLUSH_FORCE); -+ obj->write_domain = 0; -+} -+ -+void i915_gem_object_flush_if_display(struct drm_i915_gem_object *obj) -+{ -+ if (!READ_ONCE(obj->pin_global)) -+ return; -+ -+ mutex_lock(&obj->base.dev->struct_mutex); -+ __i915_gem_object_flush_for_display(obj); -+ mutex_unlock(&obj->base.dev->struct_mutex); -+} -+ -+/** -+ * Moves a single object to the WC read, and possibly write domain. -+ * @obj: object to act on -+ * @write: ask for write access or read only -+ * -+ * This function returns when the move is complete, including waiting on -+ * flushes to occur. -+ */ -+int -+i915_gem_object_set_to_wc_domain(struct drm_i915_gem_object *obj, bool write) -+{ -+ int ret; -+ -+ lockdep_assert_held(&obj->base.dev->struct_mutex); -+ -+ ret = i915_gem_object_wait(obj, -+ I915_WAIT_INTERRUPTIBLE | -+ I915_WAIT_LOCKED | -+ (write ? I915_WAIT_ALL : 0), -+ MAX_SCHEDULE_TIMEOUT); -+ if (ret) -+ return ret; -+ -+ if (obj->write_domain == I915_GEM_DOMAIN_WC) -+ return 0; -+ -+ /* Flush and acquire obj->pages so that we are coherent through -+ * direct access in memory with previous cached writes through -+ * shmemfs and that our cache domain tracking remains valid. -+ * For example, if the obj->filp was moved to swap without us -+ * being notified and releasing the pages, we would mistakenly -+ * continue to assume that the obj remained out of the CPU cached -+ * domain. -+ */ -+ ret = i915_gem_object_pin_pages(obj); -+ if (ret) -+ return ret; -+ -+ flush_write_domain(obj, ~I915_GEM_DOMAIN_WC); -+ -+ /* Serialise direct access to this object with the barriers for -+ * coherent writes from the GPU, by effectively invalidating the -+ * WC domain upon first access. -+ */ -+ if ((obj->read_domains & I915_GEM_DOMAIN_WC) == 0) -+ mb(); -+ -+ /* It should now be out of any other write domains, and we can update -+ * the domain values for our changes. -+ */ -+ GEM_BUG_ON((obj->write_domain & ~I915_GEM_DOMAIN_WC) != 0); -+ obj->read_domains |= I915_GEM_DOMAIN_WC; -+ if (write) { -+ obj->read_domains = I915_GEM_DOMAIN_WC; -+ obj->write_domain = I915_GEM_DOMAIN_WC; -+ obj->mm.dirty = true; -+ } -+ -+ i915_gem_object_unpin_pages(obj); -+ return 0; -+} -+ -+/** -+ * Moves a single object to the GTT read, and possibly write domain. -+ * @obj: object to act on -+ * @write: ask for write access or read only -+ * -+ * This function returns when the move is complete, including waiting on -+ * flushes to occur. -+ */ -+int -+i915_gem_object_set_to_gtt_domain(struct drm_i915_gem_object *obj, bool write) -+{ -+ int ret; -+ -+ lockdep_assert_held(&obj->base.dev->struct_mutex); -+ -+ ret = i915_gem_object_wait(obj, -+ I915_WAIT_INTERRUPTIBLE | -+ I915_WAIT_LOCKED | -+ (write ? I915_WAIT_ALL : 0), -+ MAX_SCHEDULE_TIMEOUT); -+ if (ret) -+ return ret; -+ -+ if (obj->write_domain == I915_GEM_DOMAIN_GTT) -+ return 0; -+ -+ /* Flush and acquire obj->pages so that we are coherent through -+ * direct access in memory with previous cached writes through -+ * shmemfs and that our cache domain tracking remains valid. -+ * For example, if the obj->filp was moved to swap without us -+ * being notified and releasing the pages, we would mistakenly -+ * continue to assume that the obj remained out of the CPU cached -+ * domain. -+ */ -+ ret = i915_gem_object_pin_pages(obj); -+ if (ret) -+ return ret; -+ -+ flush_write_domain(obj, ~I915_GEM_DOMAIN_GTT); -+ -+ /* Serialise direct access to this object with the barriers for -+ * coherent writes from the GPU, by effectively invalidating the -+ * GTT domain upon first access. -+ */ -+ if ((obj->read_domains & I915_GEM_DOMAIN_GTT) == 0) -+ mb(); -+ -+ /* It should now be out of any other write domains, and we can update -+ * the domain values for our changes. -+ */ -+ GEM_BUG_ON((obj->write_domain & ~I915_GEM_DOMAIN_GTT) != 0); -+ obj->read_domains |= I915_GEM_DOMAIN_GTT; -+ if (write) { -+ obj->read_domains = I915_GEM_DOMAIN_GTT; -+ obj->write_domain = I915_GEM_DOMAIN_GTT; -+ obj->mm.dirty = true; -+ } -+ -+ i915_gem_object_unpin_pages(obj); -+ return 0; -+} -+ -+/** -+ * Changes the cache-level of an object across all VMA. -+ * @obj: object to act on -+ * @cache_level: new cache level to set for the object -+ * -+ * After this function returns, the object will be in the new cache-level -+ * across all GTT and the contents of the backing storage will be coherent, -+ * with respect to the new cache-level. In order to keep the backing storage -+ * coherent for all users, we only allow a single cache level to be set -+ * globally on the object and prevent it from being changed whilst the -+ * hardware is reading from the object. That is if the object is currently -+ * on the scanout it will be set to uncached (or equivalent display -+ * cache coherency) and all non-MOCS GPU access will also be uncached so -+ * that all direct access to the scanout remains coherent. -+ */ -+int i915_gem_object_set_cache_level(struct drm_i915_gem_object *obj, -+ enum i915_cache_level cache_level) -+{ -+ struct i915_vma *vma; -+ int ret; -+ -+ lockdep_assert_held(&obj->base.dev->struct_mutex); -+ -+ if (obj->cache_level == cache_level) -+ return 0; -+ -+ /* Inspect the list of currently bound VMA and unbind any that would -+ * be invalid given the new cache-level. This is principally to -+ * catch the issue of the CS prefetch crossing page boundaries and -+ * reading an invalid PTE on older architectures. -+ */ -+restart: -+ list_for_each_entry(vma, &obj->vma.list, obj_link) { -+ if (!drm_mm_node_allocated(&vma->node)) -+ continue; -+ -+ if (i915_vma_is_pinned(vma)) { -+ DRM_DEBUG("can not change the cache level of pinned objects\n"); -+ return -EBUSY; -+ } -+ -+ if (!i915_vma_is_closed(vma) && -+ i915_gem_valid_gtt_space(vma, cache_level)) -+ continue; -+ -+ ret = i915_vma_unbind(vma); -+ if (ret) -+ return ret; -+ -+ /* As unbinding may affect other elements in the -+ * obj->vma_list (due to side-effects from retiring -+ * an active vma), play safe and restart the iterator. -+ */ -+ goto restart; -+ } -+ -+ /* We can reuse the existing drm_mm nodes but need to change the -+ * cache-level on the PTE. We could simply unbind them all and -+ * rebind with the correct cache-level on next use. However since -+ * we already have a valid slot, dma mapping, pages etc, we may as -+ * rewrite the PTE in the belief that doing so tramples upon less -+ * state and so involves less work. -+ */ -+ if (obj->bind_count) { -+ /* Before we change the PTE, the GPU must not be accessing it. -+ * If we wait upon the object, we know that all the bound -+ * VMA are no longer active. -+ */ -+ ret = i915_gem_object_wait(obj, -+ I915_WAIT_INTERRUPTIBLE | -+ I915_WAIT_LOCKED | -+ I915_WAIT_ALL, -+ MAX_SCHEDULE_TIMEOUT); -+ if (ret) -+ return ret; -+ -+ if (!HAS_LLC(to_i915(obj->base.dev)) && -+ cache_level != I915_CACHE_NONE) { -+ /* Access to snoopable pages through the GTT is -+ * incoherent and on some machines causes a hard -+ * lockup. Relinquish the CPU mmaping to force -+ * userspace to refault in the pages and we can -+ * then double check if the GTT mapping is still -+ * valid for that pointer access. -+ */ -+ i915_gem_release_mmap(obj); -+ -+ /* As we no longer need a fence for GTT access, -+ * we can relinquish it now (and so prevent having -+ * to steal a fence from someone else on the next -+ * fence request). Note GPU activity would have -+ * dropped the fence as all snoopable access is -+ * supposed to be linear. -+ */ -+ for_each_ggtt_vma(vma, obj) { -+ ret = i915_vma_put_fence(vma); -+ if (ret) -+ return ret; -+ } -+ } else { -+ /* We either have incoherent backing store and -+ * so no GTT access or the architecture is fully -+ * coherent. In such cases, existing GTT mmaps -+ * ignore the cache bit in the PTE and we can -+ * rewrite it without confusing the GPU or having -+ * to force userspace to fault back in its mmaps. -+ */ -+ } -+ -+ list_for_each_entry(vma, &obj->vma.list, obj_link) { -+ if (!drm_mm_node_allocated(&vma->node)) -+ continue; -+ -+ ret = i915_vma_bind(vma, cache_level, PIN_UPDATE); -+ if (ret) -+ return ret; -+ } -+ } -+ -+ list_for_each_entry(vma, &obj->vma.list, obj_link) -+ vma->node.color = cache_level; -+ i915_gem_object_set_cache_coherency(obj, cache_level); -+ obj->cache_dirty = true; /* Always invalidate stale cachelines */ -+ -+ return 0; -+} -+ -+int i915_gem_get_caching_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_gem_caching *args = data; -+ struct drm_i915_gem_object *obj; -+ int err = 0; -+ -+ rcu_read_lock(); -+ obj = i915_gem_object_lookup_rcu(file, args->handle); -+ if (!obj) { -+ err = -ENOENT; -+ goto out; -+ } -+ -+ switch (obj->cache_level) { -+ case I915_CACHE_LLC: -+ case I915_CACHE_L3_LLC: -+ args->caching = I915_CACHING_CACHED; -+ break; -+ -+ case I915_CACHE_WT: -+ args->caching = I915_CACHING_DISPLAY; -+ break; -+ -+ default: -+ args->caching = I915_CACHING_NONE; -+ break; -+ } -+out: -+ rcu_read_unlock(); -+ return err; -+} -+ -+int i915_gem_set_caching_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_private *i915 = to_i915(dev); -+ struct drm_i915_gem_caching *args = data; -+ struct drm_i915_gem_object *obj; -+ enum i915_cache_level level; -+ int ret = 0; -+ -+ switch (args->caching) { -+ case I915_CACHING_NONE: -+ level = I915_CACHE_NONE; -+ break; -+ case I915_CACHING_CACHED: -+ /* -+ * Due to a HW issue on BXT A stepping, GPU stores via a -+ * snooped mapping may leave stale data in a corresponding CPU -+ * cacheline, whereas normally such cachelines would get -+ * invalidated. -+ */ -+ if (!HAS_LLC(i915) && !HAS_SNOOP(i915)) -+ return -ENODEV; -+ -+ level = I915_CACHE_LLC; -+ break; -+ case I915_CACHING_DISPLAY: -+ level = HAS_WT(i915) ? I915_CACHE_WT : I915_CACHE_NONE; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ obj = i915_gem_object_lookup(file, args->handle); -+ if (!obj) -+ return -ENOENT; -+ -+ /* -+ * The caching mode of proxy object is handled by its generator, and -+ * not allowed to be changed by userspace. -+ */ -+ if (i915_gem_object_is_proxy(obj)) { -+ ret = -ENXIO; -+ goto out; -+ } -+ -+ if (obj->cache_level == level) -+ goto out; -+ -+ ret = i915_gem_object_wait(obj, -+ I915_WAIT_INTERRUPTIBLE, -+ MAX_SCHEDULE_TIMEOUT); -+ if (ret) -+ goto out; -+ -+ ret = i915_mutex_lock_interruptible(dev); -+ if (ret) -+ goto out; -+ -+ ret = i915_gem_object_set_cache_level(obj, level); -+ mutex_unlock(&dev->struct_mutex); -+ -+out: -+ i915_gem_object_put(obj); -+ return ret; -+} -+ -+/* -+ * Prepare buffer for display plane (scanout, cursors, etc). Can be called from -+ * an uninterruptible phase (modesetting) and allows any flushes to be pipelined -+ * (for pageflips). We only flush the caches while preparing the buffer for -+ * display, the callers are responsible for frontbuffer flush. -+ */ -+struct i915_vma * -+i915_gem_object_pin_to_display_plane(struct drm_i915_gem_object *obj, -+ u32 alignment, -+ const struct i915_ggtt_view *view, -+ unsigned int flags) -+{ -+ struct i915_vma *vma; -+ int ret; -+ -+ lockdep_assert_held(&obj->base.dev->struct_mutex); -+ -+ /* Mark the global pin early so that we account for the -+ * display coherency whilst setting up the cache domains. -+ */ -+ obj->pin_global++; -+ -+ /* The display engine is not coherent with the LLC cache on gen6. As -+ * a result, we make sure that the pinning that is about to occur is -+ * done with uncached PTEs. This is lowest common denominator for all -+ * chipsets. -+ * -+ * However for gen6+, we could do better by using the GFDT bit instead -+ * of uncaching, which would allow us to flush all the LLC-cached data -+ * with that bit in the PTE to main memory with just one PIPE_CONTROL. -+ */ -+ ret = i915_gem_object_set_cache_level(obj, -+ HAS_WT(to_i915(obj->base.dev)) ? -+ I915_CACHE_WT : I915_CACHE_NONE); -+ if (ret) { -+ vma = ERR_PTR(ret); -+ goto err_unpin_global; -+ } -+ -+ /* As the user may map the buffer once pinned in the display plane -+ * (e.g. libkms for the bootup splash), we have to ensure that we -+ * always use map_and_fenceable for all scanout buffers. However, -+ * it may simply be too big to fit into mappable, in which case -+ * put it anyway and hope that userspace can cope (but always first -+ * try to preserve the existing ABI). -+ */ -+ vma = ERR_PTR(-ENOSPC); -+ if ((flags & PIN_MAPPABLE) == 0 && -+ (!view || view->type == I915_GGTT_VIEW_NORMAL)) -+ vma = i915_gem_object_ggtt_pin(obj, view, 0, alignment, -+ flags | -+ PIN_MAPPABLE | -+ PIN_NONBLOCK); -+ if (IS_ERR(vma)) -+ vma = i915_gem_object_ggtt_pin(obj, view, 0, alignment, flags); -+ if (IS_ERR(vma)) -+ goto err_unpin_global; -+ -+ vma->display_alignment = max_t(u64, vma->display_alignment, alignment); -+ -+ __i915_gem_object_flush_for_display(obj); -+ -+ /* It should now be out of any other write domains, and we can update -+ * the domain values for our changes. -+ */ -+ obj->read_domains |= I915_GEM_DOMAIN_GTT; -+ -+ return vma; -+ -+err_unpin_global: -+ obj->pin_global--; -+ return vma; -+} -+ -+void -+i915_gem_object_unpin_from_display_plane(struct i915_vma *vma) -+{ -+ lockdep_assert_held(&vma->vm->i915->drm.struct_mutex); -+ -+ if (WARN_ON(vma->obj->pin_global == 0)) -+ return; -+ -+ if (--vma->obj->pin_global == 0) -+ vma->display_alignment = I915_GTT_MIN_ALIGNMENT; -+ -+ /* Bump the LRU to try and avoid premature eviction whilst flipping */ -+ i915_gem_object_bump_inactive_ggtt(vma->obj); -+ -+ i915_vma_unpin(vma); -+} -+ -+/** -+ * Moves a single object to the CPU read, and possibly write domain. -+ * @obj: object to act on -+ * @write: requesting write or read-only access -+ * -+ * This function returns when the move is complete, including waiting on -+ * flushes to occur. -+ */ -+int -+i915_gem_object_set_to_cpu_domain(struct drm_i915_gem_object *obj, bool write) -+{ -+ int ret; -+ -+ lockdep_assert_held(&obj->base.dev->struct_mutex); -+ -+ ret = i915_gem_object_wait(obj, -+ I915_WAIT_INTERRUPTIBLE | -+ I915_WAIT_LOCKED | -+ (write ? I915_WAIT_ALL : 0), -+ MAX_SCHEDULE_TIMEOUT); -+ if (ret) -+ return ret; -+ -+ flush_write_domain(obj, ~I915_GEM_DOMAIN_CPU); -+ -+ /* Flush the CPU cache if it's still invalid. */ -+ if ((obj->read_domains & I915_GEM_DOMAIN_CPU) == 0) { -+ i915_gem_clflush_object(obj, I915_CLFLUSH_SYNC); -+ obj->read_domains |= I915_GEM_DOMAIN_CPU; -+ } -+ -+ /* It should now be out of any other write domains, and we can update -+ * the domain values for our changes. -+ */ -+ GEM_BUG_ON(obj->write_domain & ~I915_GEM_DOMAIN_CPU); -+ -+ /* If we're writing through the CPU, then the GPU read domains will -+ * need to be invalidated at next use. -+ */ -+ if (write) -+ __start_cpu_write(obj); -+ -+ return 0; -+} -+ -+/* Throttle our rendering by waiting until the ring has completed our requests -+ * emitted over 20 msec ago. -+ * -+ * Note that if we were to use the current jiffies each time around the loop, -+ * we wouldn't escape the function with any frames outstanding if the time to -+ * render a frame was over 20ms. -+ * -+ * This should get us reasonable parallelism between CPU and GPU but also -+ * relatively low latency when blocking on a particular request to finish. -+ */ -+static int -+i915_gem_ring_throttle(struct drm_device *dev, struct drm_file *file) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_i915_file_private *file_priv = file->driver_priv; -+ unsigned long recent_enough = jiffies - DRM_I915_THROTTLE_JIFFIES; -+ struct i915_request *request, *target = NULL; -+ long ret; -+ -+ /* ABI: return -EIO if already wedged */ -+ ret = i915_terminally_wedged(dev_priv); -+ if (ret) -+ return ret; -+ -+ spin_lock(&file_priv->mm.lock); -+ list_for_each_entry(request, &file_priv->mm.request_list, client_link) { -+ if (time_after_eq(request->emitted_jiffies, recent_enough)) -+ break; -+ -+ if (target) { -+ list_del(&target->client_link); -+ target->file_priv = NULL; -+ } -+ -+ target = request; -+ } -+ if (target) -+ i915_request_get(target); -+ spin_unlock(&file_priv->mm.lock); -+ -+ if (target == NULL) -+ return 0; -+ -+ ret = i915_request_wait(target, -+ I915_WAIT_INTERRUPTIBLE, -+ MAX_SCHEDULE_TIMEOUT); -+ i915_request_put(target); -+ -+ return ret < 0 ? ret : 0; -+} -+ -+struct i915_vma * -+i915_gem_object_ggtt_pin(struct drm_i915_gem_object *obj, -+ const struct i915_ggtt_view *view, -+ u64 size, -+ u64 alignment, -+ u64 flags) -+{ -+ struct drm_i915_private *dev_priv = to_i915(obj->base.dev); -+ struct i915_address_space *vm = &dev_priv->ggtt.vm; -+ struct i915_vma *vma; -+ int ret; -+ -+ lockdep_assert_held(&obj->base.dev->struct_mutex); -+ -+ if (flags & PIN_MAPPABLE && -+ (!view || view->type == I915_GGTT_VIEW_NORMAL)) { -+ /* If the required space is larger than the available -+ * aperture, we will not able to find a slot for the -+ * object and unbinding the object now will be in -+ * vain. Worse, doing so may cause us to ping-pong -+ * the object in and out of the Global GTT and -+ * waste a lot of cycles under the mutex. -+ */ -+ if (obj->base.size > dev_priv->ggtt.mappable_end) -+ return ERR_PTR(-E2BIG); -+ -+ /* If NONBLOCK is set the caller is optimistically -+ * trying to cache the full object within the mappable -+ * aperture, and *must* have a fallback in place for -+ * situations where we cannot bind the object. We -+ * can be a little more lax here and use the fallback -+ * more often to avoid costly migrations of ourselves -+ * and other objects within the aperture. -+ * -+ * Half-the-aperture is used as a simple heuristic. -+ * More interesting would to do search for a free -+ * block prior to making the commitment to unbind. -+ * That caters for the self-harm case, and with a -+ * little more heuristics (e.g. NOFAULT, NOEVICT) -+ * we could try to minimise harm to others. -+ */ -+ if (flags & PIN_NONBLOCK && -+ obj->base.size > dev_priv->ggtt.mappable_end / 2) -+ return ERR_PTR(-ENOSPC); -+ } -+ -+ vma = i915_vma_instance(obj, vm, view); -+ if (IS_ERR(vma)) -+ return vma; -+ -+ if (i915_vma_misplaced(vma, size, alignment, flags)) { -+ if (flags & PIN_NONBLOCK) { -+ if (i915_vma_is_pinned(vma) || i915_vma_is_active(vma)) -+ return ERR_PTR(-ENOSPC); -+ -+ if (flags & PIN_MAPPABLE && -+ vma->fence_size > dev_priv->ggtt.mappable_end / 2) -+ return ERR_PTR(-ENOSPC); -+ } -+ -+ WARN(i915_vma_is_pinned(vma), -+ "bo is already pinned in ggtt with incorrect alignment:" -+ " offset=%08x, req.alignment=%llx," -+ " req.map_and_fenceable=%d, vma->map_and_fenceable=%d\n", -+ i915_ggtt_offset(vma), alignment, -+ !!(flags & PIN_MAPPABLE), -+ i915_vma_is_map_and_fenceable(vma)); -+ ret = i915_vma_unbind(vma); -+ if (ret) -+ return ERR_PTR(ret); -+ } -+ -+ ret = i915_vma_pin(vma, size, alignment, flags | PIN_GLOBAL); -+ if (ret) -+ return ERR_PTR(ret); -+ -+ return vma; -+} -+ -+static __always_inline u32 __busy_read_flag(u8 id) -+{ -+ if (id == (u8)I915_ENGINE_CLASS_INVALID) -+ return 0xffff0000u; -+ -+ GEM_BUG_ON(id >= 16); -+ return 0x10000u << id; -+} -+ -+static __always_inline u32 __busy_write_id(u8 id) -+{ -+ /* -+ * The uABI guarantees an active writer is also amongst the read -+ * engines. This would be true if we accessed the activity tracking -+ * under the lock, but as we perform the lookup of the object and -+ * its activity locklessly we can not guarantee that the last_write -+ * being active implies that we have set the same engine flag from -+ * last_read - hence we always set both read and write busy for -+ * last_write. -+ */ -+ if (id == (u8)I915_ENGINE_CLASS_INVALID) -+ return 0xffffffffu; -+ -+ return (id + 1) | __busy_read_flag(id); -+} -+ -+static __always_inline unsigned int -+__busy_set_if_active(const struct dma_fence *fence, u32 (*flag)(u8 id)) -+{ -+ const struct i915_request *rq; -+ -+ /* -+ * We have to check the current hw status of the fence as the uABI -+ * guarantees forward progress. We could rely on the idle worker -+ * to eventually flush us, but to minimise latency just ask the -+ * hardware. -+ * -+ * Note we only report on the status of native fences. -+ */ -+ if (!dma_fence_is_i915(fence)) -+ return 0; -+ -+ /* opencode to_request() in order to avoid const warnings */ -+ rq = container_of(fence, const struct i915_request, fence); -+ if (i915_request_completed(rq)) -+ return 0; -+ -+ /* Beware type-expansion follies! */ -+ BUILD_BUG_ON(!typecheck(u8, rq->engine->uabi_class)); -+ return flag(rq->engine->uabi_class); -+} -+ -+static __always_inline unsigned int -+busy_check_reader(const struct dma_fence *fence) -+{ -+ return __busy_set_if_active(fence, __busy_read_flag); -+} -+ -+static __always_inline unsigned int -+busy_check_writer(const struct dma_fence *fence) -+{ -+ if (!fence) -+ return 0; -+ -+ return __busy_set_if_active(fence, __busy_write_id); -+} -+ -+int -+i915_gem_busy_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_gem_busy *args = data; -+ struct drm_i915_gem_object *obj; -+ struct reservation_object_list *list; -+ unsigned int seq; -+ int err; -+ -+ err = -ENOENT; -+ rcu_read_lock(); -+ obj = i915_gem_object_lookup_rcu(file, args->handle); -+ if (!obj) -+ goto out; -+ -+ /* -+ * A discrepancy here is that we do not report the status of -+ * non-i915 fences, i.e. even though we may report the object as idle, -+ * a call to set-domain may still stall waiting for foreign rendering. -+ * This also means that wait-ioctl may report an object as busy, -+ * where busy-ioctl considers it idle. -+ * -+ * We trade the ability to warn of foreign fences to report on which -+ * i915 engines are active for the object. -+ * -+ * Alternatively, we can trade that extra information on read/write -+ * activity with -+ * args->busy = -+ * !reservation_object_test_signaled_rcu(obj->resv, true); -+ * to report the overall busyness. This is what the wait-ioctl does. -+ * -+ */ -+retry: -+ seq = raw_read_seqcount(&obj->resv->seq); -+ -+ /* Translate the exclusive fence to the READ *and* WRITE engine */ -+ args->busy = busy_check_writer(rcu_dereference(obj->resv->fence_excl)); -+ -+ /* Translate shared fences to READ set of engines */ -+ list = rcu_dereference(obj->resv->fence); -+ if (list) { -+ unsigned int shared_count = list->shared_count, i; -+ -+ for (i = 0; i < shared_count; ++i) { -+ struct dma_fence *fence = -+ rcu_dereference(list->shared[i]); -+ -+ args->busy |= busy_check_reader(fence); -+ } -+ } -+ -+ if (args->busy && read_seqcount_retry(&obj->resv->seq, seq)) -+ goto retry; -+ -+ err = 0; -+out: -+ rcu_read_unlock(); -+ return err; -+} -+ -+int -+i915_gem_throttle_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv) -+{ -+ return i915_gem_ring_throttle(dev, file_priv); -+} -+ -+int -+i915_gem_madvise_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_i915_gem_madvise *args = data; -+ struct drm_i915_gem_object *obj; -+ int err; -+ -+ switch (args->madv) { -+ case I915_MADV_DONTNEED: -+ case I915_MADV_WILLNEED: -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ obj = i915_gem_object_lookup(file_priv, args->handle); -+ if (!obj) -+ return -ENOENT; -+ -+ err = mutex_lock_interruptible(&obj->mm.lock); -+ if (err) -+ goto out; -+ -+ if (i915_gem_object_has_pages(obj) && -+ i915_gem_object_is_tiled(obj) && -+ dev_priv->quirks & QUIRK_PIN_SWIZZLED_PAGES) { -+ if (obj->mm.madv == I915_MADV_WILLNEED) { -+ GEM_BUG_ON(!obj->mm.quirked); -+ __i915_gem_object_unpin_pages(obj); -+ obj->mm.quirked = false; -+ } -+ if (args->madv == I915_MADV_WILLNEED) { -+ GEM_BUG_ON(obj->mm.quirked); -+ __i915_gem_object_pin_pages(obj); -+ obj->mm.quirked = true; -+ } -+ } -+ -+ if (obj->mm.madv != __I915_MADV_PURGED) -+ obj->mm.madv = args->madv; -+ -+ /* if the object is no longer attached, discard its backing storage */ -+ if (obj->mm.madv == I915_MADV_DONTNEED && -+ !i915_gem_object_has_pages(obj)) -+ i915_gem_object_truncate(obj); -+ -+ args->retained = obj->mm.madv != __I915_MADV_PURGED; -+ mutex_unlock(&obj->mm.lock); -+ -+out: -+ i915_gem_object_put(obj); -+ return err; -+} -+ -+static void -+frontbuffer_retire(struct i915_active_request *active, -+ struct i915_request *request) -+{ -+ struct drm_i915_gem_object *obj = -+ container_of(active, typeof(*obj), frontbuffer_write); -+ -+ intel_fb_obj_flush(obj, ORIGIN_CS); -+} -+ -+void i915_gem_object_init(struct drm_i915_gem_object *obj, -+ const struct drm_i915_gem_object_ops *ops) -+{ -+ mutex_init(&obj->mm.lock); -+ -+ spin_lock_init(&obj->vma.lock); -+ INIT_LIST_HEAD(&obj->vma.list); -+ -+ INIT_LIST_HEAD(&obj->lut_list); -+ INIT_LIST_HEAD(&obj->batch_pool_link); -+ -+ init_rcu_head(&obj->rcu); -+ -+ obj->ops = ops; -+ -+ reservation_object_init(&obj->__builtin_resv); -+ obj->resv = &obj->__builtin_resv; -+ -+ obj->frontbuffer_ggtt_origin = ORIGIN_GTT; -+ i915_active_request_init(&obj->frontbuffer_write, -+ NULL, frontbuffer_retire); -+ -+ obj->mm.madv = I915_MADV_WILLNEED; -+ INIT_RADIX_TREE(&obj->mm.get_page.radix, GFP_KERNEL | __GFP_NOWARN); -+ mutex_init(&obj->mm.get_page.lock); -+ -+ i915_gem_info_add_obj(to_i915(obj->base.dev), obj->base.size); -+} -+ -+static const struct drm_i915_gem_object_ops i915_gem_object_ops = { -+ .flags = I915_GEM_OBJECT_HAS_STRUCT_PAGE | -+ I915_GEM_OBJECT_IS_SHRINKABLE, -+ -+ .get_pages = i915_gem_object_get_pages_gtt, -+ .put_pages = i915_gem_object_put_pages_gtt, -+ -+ .pwrite = i915_gem_object_pwrite_gtt, -+}; -+ -+static int i915_gem_object_create_shmem(struct drm_device *dev, -+ struct drm_gem_object *obj, -+ size_t size) -+{ -+ struct drm_i915_private *i915 = to_i915(dev); -+ unsigned long flags = VM_NORESERVE; -+ struct file *filp; -+ -+ drm_gem_private_object_init(dev, obj, size); -+ -+ if (i915->mm.gemfs) -+ filp = shmem_file_setup_with_mnt(i915->mm.gemfs, "i915", size, -+ flags); -+ else -+ filp = shmem_file_setup("i915", size, flags); -+ -+ if (IS_ERR(filp)) -+ return PTR_ERR(filp); -+ -+ obj->filp = filp; -+ -+ return 0; -+} -+ -+struct drm_i915_gem_object * -+i915_gem_object_create(struct drm_i915_private *dev_priv, u64 size) -+{ -+ struct drm_i915_gem_object *obj; -+ struct address_space *mapping; -+ unsigned int cache_level; -+ gfp_t mask; -+ int ret; -+ -+ /* There is a prevalence of the assumption that we fit the object's -+ * page count inside a 32bit _signed_ variable. Let's document this and -+ * catch if we ever need to fix it. In the meantime, if you do spot -+ * such a local variable, please consider fixing! -+ */ -+ if (size >> PAGE_SHIFT > INT_MAX) -+ return ERR_PTR(-E2BIG); -+ -+ if (overflows_type(size, obj->base.size)) -+ return ERR_PTR(-E2BIG); -+ -+ obj = i915_gem_object_alloc(); -+ if (obj == NULL) -+ return ERR_PTR(-ENOMEM); -+ -+ ret = i915_gem_object_create_shmem(&dev_priv->drm, &obj->base, size); -+ if (ret) -+ goto fail; -+ -+ mask = GFP_HIGHUSER | __GFP_RECLAIMABLE; -+ if (IS_I965GM(dev_priv) || IS_I965G(dev_priv)) { -+ /* 965gm cannot relocate objects above 4GiB. */ -+ mask &= ~__GFP_HIGHMEM; -+ mask |= __GFP_DMA32; -+ } -+ -+ mapping = obj->base.filp->f_mapping; -+ mapping_set_gfp_mask(mapping, mask); -+ GEM_BUG_ON(!(mapping_gfp_mask(mapping) & __GFP_RECLAIM)); -+ -+ i915_gem_object_init(obj, &i915_gem_object_ops); -+ -+ obj->write_domain = I915_GEM_DOMAIN_CPU; -+ obj->read_domains = I915_GEM_DOMAIN_CPU; -+ -+ if (HAS_LLC(dev_priv)) -+ /* On some devices, we can have the GPU use the LLC (the CPU -+ * cache) for about a 10% performance improvement -+ * compared to uncached. Graphics requests other than -+ * display scanout are coherent with the CPU in -+ * accessing this cache. This means in this mode we -+ * don't need to clflush on the CPU side, and on the -+ * GPU side we only need to flush internal caches to -+ * get data visible to the CPU. -+ * -+ * However, we maintain the display planes as UC, and so -+ * need to rebind when first used as such. -+ */ -+ cache_level = I915_CACHE_LLC; -+ else -+ cache_level = I915_CACHE_NONE; -+ -+ i915_gem_object_set_cache_coherency(obj, cache_level); -+ -+ trace_i915_gem_object_create(obj); -+ -+ return obj; -+ -+fail: -+ i915_gem_object_free(obj); -+ return ERR_PTR(ret); -+} -+ -+static bool discard_backing_storage(struct drm_i915_gem_object *obj) -+{ -+ /* If we are the last user of the backing storage (be it shmemfs -+ * pages or stolen etc), we know that the pages are going to be -+ * immediately released. In this case, we can then skip copying -+ * back the contents from the GPU. -+ */ -+ -+ if (obj->mm.madv != I915_MADV_WILLNEED) -+ return false; -+ -+ if (obj->base.filp == NULL) -+ return true; -+ -+ /* At first glance, this looks racy, but then again so would be -+ * userspace racing mmap against close. However, the first external -+ * reference to the filp can only be obtained through the -+ * i915_gem_mmap_ioctl() which safeguards us against the user -+ * acquiring such a reference whilst we are in the middle of -+ * freeing the object. -+ */ -+ return file_count(obj->base.filp) == 1; -+} -+ -+static void __i915_gem_free_objects(struct drm_i915_private *i915, -+ struct llist_node *freed) -+{ -+ struct drm_i915_gem_object *obj, *on; -+ intel_wakeref_t wakeref; -+ -+ wakeref = intel_runtime_pm_get(i915); -+ llist_for_each_entry_safe(obj, on, freed, freed) { -+ struct i915_vma *vma, *vn; -+ -+ trace_i915_gem_object_destroy(obj); -+ -+ mutex_lock(&i915->drm.struct_mutex); -+ -+ GEM_BUG_ON(i915_gem_object_is_active(obj)); -+ list_for_each_entry_safe(vma, vn, &obj->vma.list, obj_link) { -+ GEM_BUG_ON(i915_vma_is_active(vma)); -+ vma->flags &= ~I915_VMA_PIN_MASK; -+ i915_vma_destroy(vma); -+ } -+ GEM_BUG_ON(!list_empty(&obj->vma.list)); -+ GEM_BUG_ON(!RB_EMPTY_ROOT(&obj->vma.tree)); -+ -+ /* This serializes freeing with the shrinker. Since the free -+ * is delayed, first by RCU then by the workqueue, we want the -+ * shrinker to be able to free pages of unreferenced objects, -+ * or else we may oom whilst there are plenty of deferred -+ * freed objects. -+ */ -+ if (i915_gem_object_has_pages(obj)) { -+ spin_lock(&i915->mm.obj_lock); -+ list_del_init(&obj->mm.link); -+ spin_unlock(&i915->mm.obj_lock); -+ } -+ -+ mutex_unlock(&i915->drm.struct_mutex); -+ -+ GEM_BUG_ON(obj->bind_count); -+ GEM_BUG_ON(obj->userfault_count); -+ GEM_BUG_ON(atomic_read(&obj->frontbuffer_bits)); -+ GEM_BUG_ON(!list_empty(&obj->lut_list)); -+ -+ if (obj->ops->release) -+ obj->ops->release(obj); -+ -+ if (WARN_ON(i915_gem_object_has_pinned_pages(obj))) -+ atomic_set(&obj->mm.pages_pin_count, 0); -+ __i915_gem_object_put_pages(obj, I915_MM_NORMAL); -+ GEM_BUG_ON(i915_gem_object_has_pages(obj)); -+ -+ if (obj->base.import_attach) -+ drm_prime_gem_destroy(&obj->base, NULL); -+ -+ reservation_object_fini(&obj->__builtin_resv); -+ drm_gem_object_release(&obj->base); -+ i915_gem_info_remove_obj(i915, obj->base.size); -+ -+ bitmap_free(obj->bit_17); -+ i915_gem_object_free(obj); -+ -+ GEM_BUG_ON(!atomic_read(&i915->mm.free_count)); -+ atomic_dec(&i915->mm.free_count); -+ -+ if (on) -+ cond_resched(); -+ } -+ intel_runtime_pm_put(i915, wakeref); -+} -+ -+static void i915_gem_flush_free_objects(struct drm_i915_private *i915) -+{ -+ struct llist_node *freed; -+ -+ /* Free the oldest, most stale object to keep the free_list short */ -+ freed = NULL; -+ if (!llist_empty(&i915->mm.free_list)) { /* quick test for hotpath */ -+ /* Only one consumer of llist_del_first() allowed */ -+ spin_lock(&i915->mm.free_lock); -+ freed = llist_del_first(&i915->mm.free_list); -+ spin_unlock(&i915->mm.free_lock); -+ } -+ if (unlikely(freed)) { -+ freed->next = NULL; -+ __i915_gem_free_objects(i915, freed); -+ } -+} -+ -+static void __i915_gem_free_work(struct work_struct *work) -+{ -+ struct drm_i915_private *i915 = -+ container_of(work, struct drm_i915_private, mm.free_work); -+ struct llist_node *freed; -+ -+ /* -+ * All file-owned VMA should have been released by this point through -+ * i915_gem_close_object(), or earlier by i915_gem_context_close(). -+ * However, the object may also be bound into the global GTT (e.g. -+ * older GPUs without per-process support, or for direct access through -+ * the GTT either for the user or for scanout). Those VMA still need to -+ * unbound now. -+ */ -+ -+ spin_lock(&i915->mm.free_lock); -+ while ((freed = llist_del_all(&i915->mm.free_list))) { -+ spin_unlock(&i915->mm.free_lock); -+ -+ __i915_gem_free_objects(i915, freed); -+ if (need_resched()) -+ return; -+ -+ spin_lock(&i915->mm.free_lock); -+ } -+ spin_unlock(&i915->mm.free_lock); -+} -+ -+static void __i915_gem_free_object_rcu(struct rcu_head *head) -+{ -+ struct drm_i915_gem_object *obj = -+ container_of(head, typeof(*obj), rcu); -+ struct drm_i915_private *i915 = to_i915(obj->base.dev); -+ -+ /* -+ * We reuse obj->rcu for the freed list, so we had better not treat -+ * it like a rcu_head from this point forwards. And we expect all -+ * objects to be freed via this path. -+ */ -+ destroy_rcu_head(&obj->rcu); -+ -+ /* -+ * Since we require blocking on struct_mutex to unbind the freed -+ * object from the GPU before releasing resources back to the -+ * system, we can not do that directly from the RCU callback (which may -+ * be a softirq context), but must instead then defer that work onto a -+ * kthread. We use the RCU callback rather than move the freed object -+ * directly onto the work queue so that we can mix between using the -+ * worker and performing frees directly from subsequent allocations for -+ * crude but effective memory throttling. -+ */ -+ if (llist_add(&obj->freed, &i915->mm.free_list)) -+ queue_work(i915->wq, &i915->mm.free_work); -+} -+ -+void i915_gem_free_object(struct drm_gem_object *gem_obj) -+{ -+ struct drm_i915_gem_object *obj = to_intel_bo(gem_obj); -+ -+ if (obj->mm.quirked) -+ __i915_gem_object_unpin_pages(obj); -+ -+ if (discard_backing_storage(obj)) -+ obj->mm.madv = I915_MADV_DONTNEED; -+ -+ /* -+ * Before we free the object, make sure any pure RCU-only -+ * read-side critical sections are complete, e.g. -+ * i915_gem_busy_ioctl(). For the corresponding synchronized -+ * lookup see i915_gem_object_lookup_rcu(). -+ */ -+ atomic_inc(&to_i915(obj->base.dev)->mm.free_count); -+ call_rcu(&obj->rcu, __i915_gem_free_object_rcu); -+} -+ -+void __i915_gem_object_release_unless_active(struct drm_i915_gem_object *obj) -+{ -+ lockdep_assert_held(&obj->base.dev->struct_mutex); -+ -+ if (!i915_gem_object_has_active_reference(obj) && -+ i915_gem_object_is_active(obj)) -+ i915_gem_object_set_active_reference(obj); -+ else -+ i915_gem_object_put(obj); -+} -+ -+void i915_gem_sanitize(struct drm_i915_private *i915) -+{ -+ intel_wakeref_t wakeref; -+ -+ GEM_TRACE("\n"); -+ -+ wakeref = intel_runtime_pm_get(i915); -+ intel_uncore_forcewake_get(&i915->uncore, FORCEWAKE_ALL); -+ -+ /* -+ * As we have just resumed the machine and woken the device up from -+ * deep PCI sleep (presumably D3_cold), assume the HW has been reset -+ * back to defaults, recovering from whatever wedged state we left it -+ * in and so worth trying to use the device once more. -+ */ -+ if (i915_terminally_wedged(i915)) -+ i915_gem_unset_wedged(i915); -+ -+ /* -+ * If we inherit context state from the BIOS or earlier occupants -+ * of the GPU, the GPU may be in an inconsistent state when we -+ * try to take over. The only way to remove the earlier state -+ * is by resetting. However, resetting on earlier gen is tricky as -+ * it may impact the display and we are uncertain about the stability -+ * of the reset, so this could be applied to even earlier gen. -+ */ -+ intel_engines_sanitize(i915, false); -+ -+ intel_uncore_forcewake_put(&i915->uncore, FORCEWAKE_ALL); -+ intel_runtime_pm_put(i915, wakeref); -+ -+ mutex_lock(&i915->drm.struct_mutex); -+ i915_gem_contexts_lost(i915); -+ mutex_unlock(&i915->drm.struct_mutex); -+} -+ -+void i915_gem_suspend(struct drm_i915_private *i915) -+{ -+ intel_wakeref_t wakeref; -+ -+ GEM_TRACE("\n"); -+ -+ wakeref = intel_runtime_pm_get(i915); -+ -+ flush_workqueue(i915->wq); -+ -+ mutex_lock(&i915->drm.struct_mutex); -+ -+ /* -+ * We have to flush all the executing contexts to main memory so -+ * that they can saved in the hibernation image. To ensure the last -+ * context image is coherent, we have to switch away from it. That -+ * leaves the i915->kernel_context still active when -+ * we actually suspend, and its image in memory may not match the GPU -+ * state. Fortunately, the kernel_context is disposable and we do -+ * not rely on its state. -+ */ -+ switch_to_kernel_context_sync(i915, i915->gt.active_engines); -+ -+ mutex_unlock(&i915->drm.struct_mutex); -+ i915_reset_flush(i915); -+ -+ drain_delayed_work(&i915->gt.retire_work); -+ -+ /* -+ * As the idle_work is rearming if it detects a race, play safe and -+ * repeat the flush until it is definitely idle. -+ */ -+ drain_delayed_work(&i915->gt.idle_work); -+ -+ /* -+ * Assert that we successfully flushed all the work and -+ * reset the GPU back to its idle, low power state. -+ */ -+ GEM_BUG_ON(i915->gt.awake); -+ -+ intel_uc_suspend(i915); -+ -+ intel_runtime_pm_put(i915, wakeref); -+} -+ -+void i915_gem_suspend_late(struct drm_i915_private *i915) -+{ -+ struct drm_i915_gem_object *obj; -+ struct list_head *phases[] = { -+ &i915->mm.unbound_list, -+ &i915->mm.bound_list, -+ NULL -+ }, **phase; -+ -+ /* -+ * Neither the BIOS, ourselves or any other kernel -+ * expects the system to be in execlists mode on startup, -+ * so we need to reset the GPU back to legacy mode. And the only -+ * known way to disable logical contexts is through a GPU reset. -+ * -+ * So in order to leave the system in a known default configuration, -+ * always reset the GPU upon unload and suspend. Afterwards we then -+ * clean up the GEM state tracking, flushing off the requests and -+ * leaving the system in a known idle state. -+ * -+ * Note that is of the upmost importance that the GPU is idle and -+ * all stray writes are flushed *before* we dismantle the backing -+ * storage for the pinned objects. -+ * -+ * However, since we are uncertain that resetting the GPU on older -+ * machines is a good idea, we don't - just in case it leaves the -+ * machine in an unusable condition. -+ */ -+ -+ mutex_lock(&i915->drm.struct_mutex); -+ for (phase = phases; *phase; phase++) { -+ list_for_each_entry(obj, *phase, mm.link) -+ WARN_ON(i915_gem_object_set_to_gtt_domain(obj, false)); -+ } -+ mutex_unlock(&i915->drm.struct_mutex); -+ -+ intel_uc_sanitize(i915); -+ i915_gem_sanitize(i915); -+} -+ -+void i915_gem_resume(struct drm_i915_private *i915) -+{ -+ GEM_TRACE("\n"); -+ -+ WARN_ON(i915->gt.awake); -+ -+ mutex_lock(&i915->drm.struct_mutex); -+ intel_uncore_forcewake_get(&i915->uncore, FORCEWAKE_ALL); -+ -+ i915_gem_restore_gtt_mappings(i915); -+ i915_gem_restore_fences(i915); -+ -+ /* -+ * As we didn't flush the kernel context before suspend, we cannot -+ * guarantee that the context image is complete. So let's just reset -+ * it and start again. -+ */ -+ intel_gt_resume(i915); -+ -+ if (i915_gem_init_hw(i915)) -+ goto err_wedged; -+ -+ intel_uc_resume(i915); -+ -+ /* Always reload a context for powersaving. */ -+ if (!load_power_context(i915)) -+ goto err_wedged; -+ -+out_unlock: -+ intel_uncore_forcewake_put(&i915->uncore, FORCEWAKE_ALL); -+ mutex_unlock(&i915->drm.struct_mutex); -+ return; -+ -+err_wedged: -+ if (!i915_reset_failed(i915)) { -+ dev_err(i915->drm.dev, -+ "Failed to re-initialize GPU, declaring it wedged!\n"); -+ i915_gem_set_wedged(i915); -+ } -+ goto out_unlock; -+} -+ -+void i915_gem_init_swizzling(struct drm_i915_private *dev_priv) -+{ -+ if (INTEL_GEN(dev_priv) < 5 || -+ dev_priv->mm.bit_6_swizzle_x == I915_BIT_6_SWIZZLE_NONE) -+ return; -+ -+ I915_WRITE(DISP_ARB_CTL, I915_READ(DISP_ARB_CTL) | -+ DISP_TILE_SURFACE_SWIZZLING); -+ -+ if (IS_GEN(dev_priv, 5)) -+ return; -+ -+ I915_WRITE(TILECTL, I915_READ(TILECTL) | TILECTL_SWZCTL); -+ if (IS_GEN(dev_priv, 6)) -+ I915_WRITE(ARB_MODE, _MASKED_BIT_ENABLE(ARB_MODE_SWIZZLE_SNB)); -+ else if (IS_GEN(dev_priv, 7)) -+ I915_WRITE(ARB_MODE, _MASKED_BIT_ENABLE(ARB_MODE_SWIZZLE_IVB)); -+ else if (IS_GEN(dev_priv, 8)) -+ I915_WRITE(GAMTARBMODE, _MASKED_BIT_ENABLE(ARB_MODE_SWIZZLE_BDW)); -+ else -+ BUG(); -+} -+ -+static void init_unused_ring(struct drm_i915_private *dev_priv, u32 base) -+{ -+ I915_WRITE(RING_CTL(base), 0); -+ I915_WRITE(RING_HEAD(base), 0); -+ I915_WRITE(RING_TAIL(base), 0); -+ I915_WRITE(RING_START(base), 0); -+} -+ -+static void init_unused_rings(struct drm_i915_private *dev_priv) -+{ -+ if (IS_I830(dev_priv)) { -+ init_unused_ring(dev_priv, PRB1_BASE); -+ init_unused_ring(dev_priv, SRB0_BASE); -+ init_unused_ring(dev_priv, SRB1_BASE); -+ init_unused_ring(dev_priv, SRB2_BASE); -+ init_unused_ring(dev_priv, SRB3_BASE); -+ } else if (IS_GEN(dev_priv, 2)) { -+ init_unused_ring(dev_priv, SRB0_BASE); -+ init_unused_ring(dev_priv, SRB1_BASE); -+ } else if (IS_GEN(dev_priv, 3)) { -+ init_unused_ring(dev_priv, PRB1_BASE); -+ init_unused_ring(dev_priv, PRB2_BASE); -+ } -+} -+ -+static int __i915_gem_restart_engines(void *data) -+{ -+ struct drm_i915_private *i915 = data; -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ int err; -+ -+ for_each_engine(engine, i915, id) { -+ err = engine->init_hw(engine); -+ if (err) { -+ DRM_ERROR("Failed to restart %s (%d)\n", -+ engine->name, err); -+ return err; -+ } -+ } -+ -+ intel_engines_set_scheduler_caps(i915); -+ -+ return 0; -+} -+ -+int i915_gem_init_hw(struct drm_i915_private *dev_priv) -+{ -+ int ret; -+ -+ dev_priv->gt.last_init_time = ktime_get(); -+ -+ /* Double layer security blanket, see i915_gem_init() */ -+ intel_uncore_forcewake_get(&dev_priv->uncore, FORCEWAKE_ALL); -+ -+ if (HAS_EDRAM(dev_priv) && INTEL_GEN(dev_priv) < 9) -+ I915_WRITE(HSW_IDICR, I915_READ(HSW_IDICR) | IDIHASHMSK(0xf)); -+ -+ if (IS_HASWELL(dev_priv)) -+ I915_WRITE(MI_PREDICATE_RESULT_2, IS_HSW_GT3(dev_priv) ? -+ LOWER_SLICE_ENABLED : LOWER_SLICE_DISABLED); -+ -+ /* Apply the GT workarounds... */ -+ intel_gt_apply_workarounds(dev_priv); -+ /* ...and determine whether they are sticking. */ -+ intel_gt_verify_workarounds(dev_priv, "init"); -+ -+ i915_gem_init_swizzling(dev_priv); -+ -+ /* -+ * At least 830 can leave some of the unused rings -+ * "active" (ie. head != tail) after resume which -+ * will prevent c3 entry. Makes sure all unused rings -+ * are totally idle. -+ */ -+ init_unused_rings(dev_priv); -+ -+ BUG_ON(!dev_priv->kernel_context); -+ ret = i915_terminally_wedged(dev_priv); -+ if (ret) -+ goto out; -+ -+ ret = i915_ppgtt_init_hw(dev_priv); -+ if (ret) { -+ DRM_ERROR("Enabling PPGTT failed (%d)\n", ret); -+ goto out; -+ } -+ -+ ret = intel_wopcm_init_hw(&dev_priv->wopcm); -+ if (ret) { -+ DRM_ERROR("Enabling WOPCM failed (%d)\n", ret); -+ goto out; -+ } -+ -+ /* We can't enable contexts until all firmware is loaded */ -+ ret = intel_uc_init_hw(dev_priv); -+ if (ret) { -+ DRM_ERROR("Enabling uc failed (%d)\n", ret); -+ goto out; -+ } -+ -+ intel_mocs_init_l3cc_table(dev_priv); -+ -+ /* Only when the HW is re-initialised, can we replay the requests */ -+ ret = __i915_gem_restart_engines(dev_priv); -+ if (ret) -+ goto cleanup_uc; -+ -+ intel_uncore_forcewake_put(&dev_priv->uncore, FORCEWAKE_ALL); -+ -+ return 0; -+ -+cleanup_uc: -+ intel_uc_fini_hw(dev_priv); -+out: -+ intel_uncore_forcewake_put(&dev_priv->uncore, FORCEWAKE_ALL); -+ -+ return ret; -+} -+ -+static int __intel_engines_record_defaults(struct drm_i915_private *i915) -+{ -+ struct i915_gem_context *ctx; -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ int err = 0; -+ -+ /* -+ * As we reset the gpu during very early sanitisation, the current -+ * register state on the GPU should reflect its defaults values. -+ * We load a context onto the hw (with restore-inhibit), then switch -+ * over to a second context to save that default register state. We -+ * can then prime every new context with that state so they all start -+ * from the same default HW values. -+ */ -+ -+ ctx = i915_gem_context_create_kernel(i915, 0); -+ if (IS_ERR(ctx)) -+ return PTR_ERR(ctx); -+ -+ for_each_engine(engine, i915, id) { -+ struct i915_request *rq; -+ -+ rq = i915_request_alloc(engine, ctx); -+ if (IS_ERR(rq)) { -+ err = PTR_ERR(rq); -+ goto out_ctx; -+ } -+ -+ err = 0; -+ if (engine->init_context) -+ err = engine->init_context(rq); -+ -+ i915_request_add(rq); -+ if (err) -+ goto err_active; -+ } -+ -+ /* Flush the default context image to memory, and enable powersaving. */ -+ if (!load_power_context(i915)) { -+ err = -EIO; -+ goto err_active; -+ } -+ -+ for_each_engine(engine, i915, id) { -+ struct intel_context *ce; -+ struct i915_vma *state; -+ void *vaddr; -+ -+ ce = intel_context_lookup(ctx, engine); -+ if (!ce) -+ continue; -+ -+ state = ce->state; -+ if (!state) -+ continue; -+ -+ GEM_BUG_ON(intel_context_is_pinned(ce)); -+ -+ /* -+ * As we will hold a reference to the logical state, it will -+ * not be torn down with the context, and importantly the -+ * object will hold onto its vma (making it possible for a -+ * stray GTT write to corrupt our defaults). Unmap the vma -+ * from the GTT to prevent such accidents and reclaim the -+ * space. -+ */ -+ err = i915_vma_unbind(state); -+ if (err) -+ goto err_active; -+ -+ err = i915_gem_object_set_to_cpu_domain(state->obj, false); -+ if (err) -+ goto err_active; -+ -+ engine->default_state = i915_gem_object_get(state->obj); -+ i915_gem_object_set_cache_coherency(engine->default_state, -+ I915_CACHE_LLC); -+ -+ /* Check we can acquire the image of the context state */ -+ vaddr = i915_gem_object_pin_map(engine->default_state, -+ I915_MAP_FORCE_WB); -+ if (IS_ERR(vaddr)) { -+ err = PTR_ERR(vaddr); -+ goto err_active; -+ } -+ -+ i915_gem_object_unpin_map(engine->default_state); -+ } -+ -+ if (IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM)) { -+ unsigned int found = intel_engines_has_context_isolation(i915); -+ -+ /* -+ * Make sure that classes with multiple engine instances all -+ * share the same basic configuration. -+ */ -+ for_each_engine(engine, i915, id) { -+ unsigned int bit = BIT(engine->uabi_class); -+ unsigned int expected = engine->default_state ? bit : 0; -+ -+ if ((found & bit) != expected) { -+ DRM_ERROR("mismatching default context state for class %d on engine %s\n", -+ engine->uabi_class, engine->name); -+ } -+ } -+ } -+ -+out_ctx: -+ i915_gem_context_set_closed(ctx); -+ i915_gem_context_put(ctx); -+ return err; -+ -+err_active: -+ /* -+ * If we have to abandon now, we expect the engines to be idle -+ * and ready to be torn-down. The quickest way we can accomplish -+ * this is by declaring ourselves wedged. -+ */ -+ i915_gem_set_wedged(i915); -+ goto out_ctx; -+} -+ -+static int -+i915_gem_init_scratch(struct drm_i915_private *i915, unsigned int size) -+{ -+ struct drm_i915_gem_object *obj; -+ struct i915_vma *vma; -+ int ret; -+ -+ obj = i915_gem_object_create_stolen(i915, size); -+ if (!obj) -+ obj = i915_gem_object_create_internal(i915, size); -+ if (IS_ERR(obj)) { -+ DRM_ERROR("Failed to allocate scratch page\n"); -+ return PTR_ERR(obj); -+ } -+ -+ vma = i915_vma_instance(obj, &i915->ggtt.vm, NULL); -+ if (IS_ERR(vma)) { -+ ret = PTR_ERR(vma); -+ goto err_unref; -+ } -+ -+ ret = i915_vma_pin(vma, 0, 0, PIN_GLOBAL | PIN_HIGH); -+ if (ret) -+ goto err_unref; -+ -+ i915->gt.scratch = vma; -+ return 0; -+ -+err_unref: -+ i915_gem_object_put(obj); -+ return ret; -+} -+ -+static void i915_gem_fini_scratch(struct drm_i915_private *i915) -+{ -+ i915_vma_unpin_and_release(&i915->gt.scratch, 0); -+} -+ -+int i915_gem_init(struct drm_i915_private *dev_priv) -+{ -+ int ret; -+ -+ /* We need to fallback to 4K pages if host doesn't support huge gtt. */ -+ if (intel_vgpu_active(dev_priv) && !intel_vgpu_has_huge_gtt(dev_priv)) -+ mkwrite_device_info(dev_priv)->page_sizes = -+ I915_GTT_PAGE_SIZE_4K; -+ -+ dev_priv->mm.unordered_timeline = dma_fence_context_alloc(1); -+ -+ if (HAS_LOGICAL_RING_CONTEXTS(dev_priv)) -+ dev_priv->gt.cleanup_engine = intel_logical_ring_cleanup; -+ else -+ dev_priv->gt.cleanup_engine = intel_engine_cleanup; -+ -+ i915_timelines_init(dev_priv); -+ -+ ret = i915_gem_init_userptr(dev_priv); -+ if (ret) -+ return ret; -+ -+ ret = intel_uc_init_misc(dev_priv); -+ if (ret) -+ return ret; -+ -+ ret = intel_wopcm_init(&dev_priv->wopcm); -+ if (ret) -+ goto err_uc_misc; -+ -+ /* This is just a security blanket to placate dragons. -+ * On some systems, we very sporadically observe that the first TLBs -+ * used by the CS may be stale, despite us poking the TLB reset. If -+ * we hold the forcewake during initialisation these problems -+ * just magically go away. -+ */ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ intel_uncore_forcewake_get(&dev_priv->uncore, FORCEWAKE_ALL); -+ -+ ret = i915_gem_init_ggtt(dev_priv); -+ if (ret) { -+ GEM_BUG_ON(ret == -EIO); -+ goto err_unlock; -+ } -+ -+ ret = i915_gem_init_scratch(dev_priv, -+ IS_GEN(dev_priv, 2) ? SZ_256K : PAGE_SIZE); -+ if (ret) { -+ GEM_BUG_ON(ret == -EIO); -+ goto err_ggtt; -+ } -+ -+ ret = i915_gem_contexts_init(dev_priv); -+ if (ret) { -+ GEM_BUG_ON(ret == -EIO); -+ goto err_scratch; -+ } -+ -+ ret = intel_engines_init(dev_priv); -+ if (ret) { -+ GEM_BUG_ON(ret == -EIO); -+ goto err_context; -+ } -+ -+ intel_init_gt_powersave(dev_priv); -+ -+ ret = intel_uc_init(dev_priv); -+ if (ret) -+ goto err_pm; -+ -+ ret = i915_gem_init_hw(dev_priv); -+ if (ret) -+ goto err_uc_init; -+ -+ /* -+ * Despite its name intel_init_clock_gating applies both display -+ * clock gating workarounds; GT mmio workarounds and the occasional -+ * GT power context workaround. Worse, sometimes it includes a context -+ * register workaround which we need to apply before we record the -+ * default HW state for all contexts. -+ * -+ * FIXME: break up the workarounds and apply them at the right time! -+ */ -+ intel_init_clock_gating(dev_priv); -+ -+ ret = __intel_engines_record_defaults(dev_priv); -+ if (ret) -+ goto err_init_hw; -+ -+ if (i915_inject_load_failure()) { -+ ret = -ENODEV; -+ goto err_init_hw; -+ } -+ -+ if (i915_inject_load_failure()) { -+ ret = -EIO; -+ goto err_init_hw; -+ } -+ -+ intel_uncore_forcewake_put(&dev_priv->uncore, FORCEWAKE_ALL); -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ -+ return 0; -+ -+ /* -+ * Unwinding is complicated by that we want to handle -EIO to mean -+ * disable GPU submission but keep KMS alive. We want to mark the -+ * HW as irrevisibly wedged, but keep enough state around that the -+ * driver doesn't explode during runtime. -+ */ -+err_init_hw: -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ -+ i915_gem_suspend(dev_priv); -+ i915_gem_suspend_late(dev_priv); -+ -+ i915_gem_drain_workqueue(dev_priv); -+ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ intel_uc_fini_hw(dev_priv); -+err_uc_init: -+ intel_uc_fini(dev_priv); -+err_pm: -+ if (ret != -EIO) { -+ intel_cleanup_gt_powersave(dev_priv); -+ i915_gem_cleanup_engines(dev_priv); -+ } -+err_context: -+ if (ret != -EIO) -+ i915_gem_contexts_fini(dev_priv); -+err_scratch: -+ i915_gem_fini_scratch(dev_priv); -+err_ggtt: -+err_unlock: -+ intel_uncore_forcewake_put(&dev_priv->uncore, FORCEWAKE_ALL); -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ -+err_uc_misc: -+ intel_uc_fini_misc(dev_priv); -+ -+ if (ret != -EIO) { -+ i915_gem_cleanup_userptr(dev_priv); -+ i915_timelines_fini(dev_priv); -+ } -+ -+ if (ret == -EIO) { -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ -+ /* -+ * Allow engine initialisation to fail by marking the GPU as -+ * wedged. But we only want to do this where the GPU is angry, -+ * for all other failure, such as an allocation failure, bail. -+ */ -+ if (!i915_reset_failed(dev_priv)) { -+ i915_load_error(dev_priv, -+ "Failed to initialize GPU, declaring it wedged!\n"); -+ i915_gem_set_wedged(dev_priv); -+ } -+ -+ /* Minimal basic recovery for KMS */ -+ ret = i915_ggtt_enable_hw(dev_priv); -+ i915_gem_restore_gtt_mappings(dev_priv); -+ i915_gem_restore_fences(dev_priv); -+ intel_init_clock_gating(dev_priv); -+ -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ } -+ -+ i915_gem_drain_freed_objects(dev_priv); -+ return ret; -+} -+ -+void i915_gem_fini(struct drm_i915_private *dev_priv) -+{ -+ i915_gem_suspend_late(dev_priv); -+ intel_disable_gt_powersave(dev_priv); -+ -+ /* Flush any outstanding unpin_work. */ -+ i915_gem_drain_workqueue(dev_priv); -+ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ intel_uc_fini_hw(dev_priv); -+ intel_uc_fini(dev_priv); -+ i915_gem_cleanup_engines(dev_priv); -+ i915_gem_contexts_fini(dev_priv); -+ i915_gem_fini_scratch(dev_priv); -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ -+ intel_wa_list_free(&dev_priv->gt_wa_list); -+ -+ intel_cleanup_gt_powersave(dev_priv); -+ -+ intel_uc_fini_misc(dev_priv); -+ i915_gem_cleanup_userptr(dev_priv); -+ i915_timelines_fini(dev_priv); -+ -+ i915_gem_drain_freed_objects(dev_priv); -+ -+ WARN_ON(!list_empty(&dev_priv->contexts.list)); -+} -+ -+void i915_gem_init_mmio(struct drm_i915_private *i915) -+{ -+ i915_gem_sanitize(i915); -+} -+ -+void -+i915_gem_cleanup_engines(struct drm_i915_private *dev_priv) -+{ -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ -+ for_each_engine(engine, dev_priv, id) -+ dev_priv->gt.cleanup_engine(engine); -+} -+ -+void -+i915_gem_load_init_fences(struct drm_i915_private *dev_priv) -+{ -+ int i; -+ -+ if (INTEL_GEN(dev_priv) >= 7 && !IS_VALLEYVIEW(dev_priv) && -+ !IS_CHERRYVIEW(dev_priv)) -+ dev_priv->num_fence_regs = 32; -+ else if (INTEL_GEN(dev_priv) >= 4 || -+ IS_I945G(dev_priv) || IS_I945GM(dev_priv) || -+ IS_G33(dev_priv) || IS_PINEVIEW(dev_priv)) -+ dev_priv->num_fence_regs = 16; -+ else -+ dev_priv->num_fence_regs = 8; -+ -+ if (intel_vgpu_active(dev_priv)) -+ dev_priv->num_fence_regs = -+ I915_READ(vgtif_reg(avail_rs.fence_num)); -+ -+ /* Initialize fence registers to zero */ -+ for (i = 0; i < dev_priv->num_fence_regs; i++) { -+ struct drm_i915_fence_reg *fence = &dev_priv->fence_regs[i]; -+ -+ fence->i915 = dev_priv; -+ fence->id = i; -+ list_add_tail(&fence->link, &dev_priv->mm.fence_list); -+ } -+ i915_gem_restore_fences(dev_priv); -+ -+ i915_gem_detect_bit_6_swizzle(dev_priv); -+} -+ -+static void i915_gem_init__mm(struct drm_i915_private *i915) -+{ -+ spin_lock_init(&i915->mm.object_stat_lock); -+ spin_lock_init(&i915->mm.obj_lock); -+ spin_lock_init(&i915->mm.free_lock); -+ -+ init_llist_head(&i915->mm.free_list); -+ -+ INIT_LIST_HEAD(&i915->mm.unbound_list); -+ INIT_LIST_HEAD(&i915->mm.bound_list); -+ INIT_LIST_HEAD(&i915->mm.fence_list); -+ INIT_LIST_HEAD(&i915->mm.userfault_list); -+ -+ INIT_WORK(&i915->mm.free_work, __i915_gem_free_work); -+} -+ -+int i915_gem_init_early(struct drm_i915_private *dev_priv) -+{ -+ int err; -+ -+ INIT_LIST_HEAD(&dev_priv->gt.active_rings); -+ INIT_LIST_HEAD(&dev_priv->gt.closed_vma); -+ -+ i915_gem_init__mm(dev_priv); -+ -+ INIT_DELAYED_WORK(&dev_priv->gt.retire_work, -+ i915_gem_retire_work_handler); -+ INIT_DELAYED_WORK(&dev_priv->gt.idle_work, -+ i915_gem_idle_work_handler); -+ init_waitqueue_head(&dev_priv->gpu_error.wait_queue); -+ init_waitqueue_head(&dev_priv->gpu_error.reset_queue); -+ mutex_init(&dev_priv->gpu_error.wedge_mutex); -+ init_srcu_struct(&dev_priv->gpu_error.reset_backoff_srcu); -+ -+ atomic_set(&dev_priv->mm.bsd_engine_dispatch_index, 0); -+ -+ spin_lock_init(&dev_priv->fb_tracking.lock); -+ -+ err = i915_gemfs_init(dev_priv); -+ if (err) -+ DRM_NOTE("Unable to create a private tmpfs mount, hugepage support will be disabled(%d).\n", err); -+ -+ return 0; -+} -+ -+void i915_gem_cleanup_early(struct drm_i915_private *dev_priv) -+{ -+ i915_gem_drain_freed_objects(dev_priv); -+ GEM_BUG_ON(!llist_empty(&dev_priv->mm.free_list)); -+ GEM_BUG_ON(atomic_read(&dev_priv->mm.free_count)); -+ WARN_ON(dev_priv->mm.object_count); -+ -+ cleanup_srcu_struct(&dev_priv->gpu_error.reset_backoff_srcu); -+ -+ i915_gemfs_fini(dev_priv); -+} -+ -+int i915_gem_freeze(struct drm_i915_private *dev_priv) -+{ -+ /* Discard all purgeable objects, let userspace recover those as -+ * required after resuming. -+ */ -+ i915_gem_shrink_all(dev_priv); -+ -+ return 0; -+} -+ -+int i915_gem_freeze_late(struct drm_i915_private *i915) -+{ -+ struct drm_i915_gem_object *obj; -+ struct list_head *phases[] = { -+ &i915->mm.unbound_list, -+ &i915->mm.bound_list, -+ NULL -+ }, **phase; -+ -+ /* -+ * Called just before we write the hibernation image. -+ * -+ * We need to update the domain tracking to reflect that the CPU -+ * will be accessing all the pages to create and restore from the -+ * hibernation, and so upon restoration those pages will be in the -+ * CPU domain. -+ * -+ * To make sure the hibernation image contains the latest state, -+ * we update that state just before writing out the image. -+ * -+ * To try and reduce the hibernation image, we manually shrink -+ * the objects as well, see i915_gem_freeze() -+ */ -+ -+ i915_gem_shrink(i915, -1UL, NULL, I915_SHRINK_UNBOUND); -+ i915_gem_drain_freed_objects(i915); -+ -+ mutex_lock(&i915->drm.struct_mutex); -+ for (phase = phases; *phase; phase++) { -+ list_for_each_entry(obj, *phase, mm.link) -+ WARN_ON(i915_gem_object_set_to_cpu_domain(obj, true)); -+ } -+ mutex_unlock(&i915->drm.struct_mutex); -+ -+ return 0; -+} -+ -+void i915_gem_release(struct drm_device *dev, struct drm_file *file) -+{ -+ struct drm_i915_file_private *file_priv = file->driver_priv; -+ struct i915_request *request; -+ -+ /* Clean up our request list when the client is going away, so that -+ * later retire_requests won't dereference our soon-to-be-gone -+ * file_priv. -+ */ -+ spin_lock(&file_priv->mm.lock); -+ list_for_each_entry(request, &file_priv->mm.request_list, client_link) -+ request->file_priv = NULL; -+ spin_unlock(&file_priv->mm.lock); -+} -+ -+int i915_gem_open(struct drm_i915_private *i915, struct drm_file *file) -+{ -+ struct drm_i915_file_private *file_priv; -+ int ret; -+ -+ DRM_DEBUG("\n"); -+ -+ file_priv = kzalloc(sizeof(*file_priv), GFP_KERNEL); -+ if (!file_priv) -+ return -ENOMEM; -+ -+ file->driver_priv = file_priv; -+ file_priv->dev_priv = i915; -+ file_priv->file = file; -+ -+ spin_lock_init(&file_priv->mm.lock); -+ INIT_LIST_HEAD(&file_priv->mm.request_list); -+ -+ file_priv->bsd_engine = -1; -+ file_priv->hang_timestamp = jiffies; -+ -+ ret = i915_gem_context_open(i915, file); -+ if (ret) -+ kfree(file_priv); -+ -+ return ret; -+} -+ -+/** -+ * i915_gem_track_fb - update frontbuffer tracking -+ * @old: current GEM buffer for the frontbuffer slots -+ * @new: new GEM buffer for the frontbuffer slots -+ * @frontbuffer_bits: bitmask of frontbuffer slots -+ * -+ * This updates the frontbuffer tracking bits @frontbuffer_bits by clearing them -+ * from @old and setting them in @new. Both @old and @new can be NULL. -+ */ -+void i915_gem_track_fb(struct drm_i915_gem_object *old, -+ struct drm_i915_gem_object *new, -+ unsigned frontbuffer_bits) -+{ -+ /* Control of individual bits within the mask are guarded by -+ * the owning plane->mutex, i.e. we can never see concurrent -+ * manipulation of individual bits. But since the bitfield as a whole -+ * is updated using RMW, we need to use atomics in order to update -+ * the bits. -+ */ -+ BUILD_BUG_ON(INTEL_FRONTBUFFER_BITS_PER_PIPE * I915_MAX_PIPES > -+ BITS_PER_TYPE(atomic_t)); -+ -+ if (old) { -+ WARN_ON(!(atomic_read(&old->frontbuffer_bits) & frontbuffer_bits)); -+ atomic_andnot(frontbuffer_bits, &old->frontbuffer_bits); -+ } -+ -+ if (new) { -+ WARN_ON(atomic_read(&new->frontbuffer_bits) & frontbuffer_bits); -+ atomic_or(frontbuffer_bits, &new->frontbuffer_bits); -+ } -+} -+ -+/* Allocate a new GEM object and fill it with the supplied data */ -+struct drm_i915_gem_object * -+i915_gem_object_create_from_data(struct drm_i915_private *dev_priv, -+ const void *data, size_t size) -+{ -+ struct drm_i915_gem_object *obj; -+ struct file *file; -+ size_t offset; -+ int err; -+ -+ obj = i915_gem_object_create(dev_priv, round_up(size, PAGE_SIZE)); -+ if (IS_ERR(obj)) -+ return obj; -+ -+ GEM_BUG_ON(obj->write_domain != I915_GEM_DOMAIN_CPU); -+ -+ file = obj->base.filp; -+ offset = 0; -+ do { -+ unsigned int len = min_t(typeof(size), size, PAGE_SIZE); -+ struct page *page; -+ void *pgdata, *vaddr; -+ -+ err = pagecache_write_begin(file, file->f_mapping, -+ offset, len, 0, -+ &page, &pgdata); -+ if (err < 0) -+ goto fail; -+ -+ vaddr = kmap(page); -+ memcpy(vaddr, data, len); -+ kunmap(page); -+ -+ err = pagecache_write_end(file, file->f_mapping, -+ offset, len, len, -+ page, pgdata); -+ if (err < 0) -+ goto fail; -+ -+ size -= len; -+ data += len; -+ offset += len; -+ } while (size); -+ -+ return obj; -+ -+fail: -+ i915_gem_object_put(obj); -+ return ERR_PTR(err); -+} -+ -+struct scatterlist * -+i915_gem_object_get_sg(struct drm_i915_gem_object *obj, -+ unsigned int n, -+ unsigned int *offset) -+{ -+ struct i915_gem_object_page_iter *iter = &obj->mm.get_page; -+ struct scatterlist *sg; -+ unsigned int idx, count; -+ -+ might_sleep(); -+ GEM_BUG_ON(n >= obj->base.size >> PAGE_SHIFT); -+ GEM_BUG_ON(!i915_gem_object_has_pinned_pages(obj)); -+ -+ /* As we iterate forward through the sg, we record each entry in a -+ * radixtree for quick repeated (backwards) lookups. If we have seen -+ * this index previously, we will have an entry for it. -+ * -+ * Initial lookup is O(N), but this is amortized to O(1) for -+ * sequential page access (where each new request is consecutive -+ * to the previous one). Repeated lookups are O(lg(obj->base.size)), -+ * i.e. O(1) with a large constant! -+ */ -+ if (n < READ_ONCE(iter->sg_idx)) -+ goto lookup; -+ -+ mutex_lock(&iter->lock); -+ -+ /* We prefer to reuse the last sg so that repeated lookup of this -+ * (or the subsequent) sg are fast - comparing against the last -+ * sg is faster than going through the radixtree. -+ */ -+ -+ sg = iter->sg_pos; -+ idx = iter->sg_idx; -+ count = __sg_page_count(sg); -+ -+ while (idx + count <= n) { -+ void *entry; -+ unsigned long i; -+ int ret; -+ -+ /* If we cannot allocate and insert this entry, or the -+ * individual pages from this range, cancel updating the -+ * sg_idx so that on this lookup we are forced to linearly -+ * scan onwards, but on future lookups we will try the -+ * insertion again (in which case we need to be careful of -+ * the error return reporting that we have already inserted -+ * this index). -+ */ -+ ret = radix_tree_insert(&iter->radix, idx, sg); -+ if (ret && ret != -EEXIST) -+ goto scan; -+ -+ entry = xa_mk_value(idx); -+ for (i = 1; i < count; i++) { -+ ret = radix_tree_insert(&iter->radix, idx + i, entry); -+ if (ret && ret != -EEXIST) -+ goto scan; -+ } -+ -+ idx += count; -+ sg = ____sg_next(sg); -+ count = __sg_page_count(sg); -+ } -+ -+scan: -+ iter->sg_pos = sg; -+ iter->sg_idx = idx; -+ -+ mutex_unlock(&iter->lock); -+ -+ if (unlikely(n < idx)) /* insertion completed by another thread */ -+ goto lookup; -+ -+ /* In case we failed to insert the entry into the radixtree, we need -+ * to look beyond the current sg. -+ */ -+ while (idx + count <= n) { -+ idx += count; -+ sg = ____sg_next(sg); -+ count = __sg_page_count(sg); -+ } -+ -+ *offset = n - idx; -+ return sg; -+ -+lookup: -+ rcu_read_lock(); -+ -+ sg = radix_tree_lookup(&iter->radix, n); -+ GEM_BUG_ON(!sg); -+ -+ /* If this index is in the middle of multi-page sg entry, -+ * the radix tree will contain a value entry that points -+ * to the start of that range. We will return the pointer to -+ * the base page and the offset of this page within the -+ * sg entry's range. -+ */ -+ *offset = 0; -+ if (unlikely(xa_is_value(sg))) { -+ unsigned long base = xa_to_value(sg); -+ -+ sg = radix_tree_lookup(&iter->radix, base); -+ GEM_BUG_ON(!sg); -+ -+ *offset = n - base; -+ } -+ -+ rcu_read_unlock(); -+ -+ return sg; -+} -+ -+struct page * -+i915_gem_object_get_page(struct drm_i915_gem_object *obj, unsigned int n) -+{ -+ struct scatterlist *sg; -+ unsigned int offset; -+ -+ GEM_BUG_ON(!i915_gem_object_has_struct_page(obj)); -+ -+ sg = i915_gem_object_get_sg(obj, n, &offset); -+ return nth_page(sg_page(sg), offset); -+} -+ -+/* Like i915_gem_object_get_page(), but mark the returned page dirty */ -+struct page * -+i915_gem_object_get_dirty_page(struct drm_i915_gem_object *obj, -+ unsigned int n) -+{ -+ struct page *page; -+ -+ page = i915_gem_object_get_page(obj, n); -+ if (!obj->mm.dirty) -+ set_page_dirty(page); -+ -+ return page; -+} -+ -+dma_addr_t -+i915_gem_object_get_dma_address(struct drm_i915_gem_object *obj, -+ unsigned long n) -+{ -+ struct scatterlist *sg; -+ unsigned int offset; -+ -+ sg = i915_gem_object_get_sg(obj, n, &offset); -+ return sg_dma_address(sg) + (offset << PAGE_SHIFT); -+} -+ -+int i915_gem_object_attach_phys(struct drm_i915_gem_object *obj, int align) -+{ -+ struct sg_table *pages; -+ int err; -+ -+ if (align > obj->base.size) -+ return -EINVAL; -+ -+ if (obj->ops == &i915_gem_phys_ops) -+ return 0; -+ -+ if (obj->ops != &i915_gem_object_ops) -+ return -EINVAL; -+ -+ err = i915_gem_object_unbind(obj); -+ if (err) -+ return err; -+ -+ mutex_lock(&obj->mm.lock); -+ -+ if (obj->mm.madv != I915_MADV_WILLNEED) { -+ err = -EFAULT; -+ goto err_unlock; -+ } -+ -+ if (obj->mm.quirked) { -+ err = -EFAULT; -+ goto err_unlock; -+ } -+ -+ if (obj->mm.mapping) { -+ err = -EBUSY; -+ goto err_unlock; -+ } -+ -+ pages = __i915_gem_object_unset_pages(obj); -+ -+ obj->ops = &i915_gem_phys_ops; -+ -+ err = ____i915_gem_object_get_pages(obj); -+ if (err) -+ goto err_xfer; -+ -+ /* Perma-pin (until release) the physical set of pages */ -+ __i915_gem_object_pin_pages(obj); -+ -+ if (!IS_ERR_OR_NULL(pages)) -+ i915_gem_object_ops.put_pages(obj, pages); -+ mutex_unlock(&obj->mm.lock); -+ return 0; -+ -+err_xfer: -+ obj->ops = &i915_gem_object_ops; -+ if (!IS_ERR_OR_NULL(pages)) { -+ unsigned int sg_page_sizes = i915_sg_page_sizes(pages->sgl); -+ -+ __i915_gem_object_set_pages(obj, pages, sg_page_sizes); -+ } -+err_unlock: -+ mutex_unlock(&obj->mm.lock); -+ return err; -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -+#include "selftests/scatterlist.c" -+#include "selftests/mock_gem_device.c" -+#include "selftests/huge_gem_object.c" -+#include "selftests/huge_pages.c" -+#include "selftests/i915_gem_object.c" -+#include "selftests/i915_gem_coherency.c" -+#include "selftests/i915_gem.c" -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem.h b/drivers/gpu/drm/i915_legacy/i915_gem.h -new file mode 100644 -index 000000000000..9074eb1e843f ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem.h -@@ -0,0 +1,97 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef __I915_GEM_H__ -+#define __I915_GEM_H__ -+ -+#include -+#include -+ -+struct drm_i915_private; -+ -+#ifdef CONFIG_DRM_I915_DEBUG_GEM -+ -+#define GEM_SHOW_DEBUG() (drm_debug & DRM_UT_DRIVER) -+ -+#define GEM_BUG_ON(condition) do { if (unlikely((condition))) { \ -+ pr_err("%s:%d GEM_BUG_ON(%s)\n", \ -+ __func__, __LINE__, __stringify(condition)); \ -+ GEM_TRACE("%s:%d GEM_BUG_ON(%s)\n", \ -+ __func__, __LINE__, __stringify(condition)); \ -+ BUG(); \ -+ } \ -+ } while(0) -+#define GEM_WARN_ON(expr) WARN_ON(expr) -+ -+#define GEM_DEBUG_DECL(var) var -+#define GEM_DEBUG_EXEC(expr) expr -+#define GEM_DEBUG_BUG_ON(expr) GEM_BUG_ON(expr) -+#define GEM_DEBUG_WARN_ON(expr) GEM_WARN_ON(expr) -+ -+#else -+ -+#define GEM_SHOW_DEBUG() (0) -+ -+#define GEM_BUG_ON(expr) BUILD_BUG_ON_INVALID(expr) -+#define GEM_WARN_ON(expr) ({ unlikely(!!(expr)); }) -+ -+#define GEM_DEBUG_DECL(var) -+#define GEM_DEBUG_EXEC(expr) do { } while (0) -+#define GEM_DEBUG_BUG_ON(expr) -+#define GEM_DEBUG_WARN_ON(expr) ({ BUILD_BUG_ON_INVALID(expr); 0; }) -+#endif -+ -+#if IS_ENABLED(CONFIG_DRM_I915_TRACE_GEM) -+#define GEM_TRACE(...) trace_printk(__VA_ARGS__) -+#define GEM_TRACE_DUMP() ftrace_dump(DUMP_ALL) -+#define GEM_TRACE_DUMP_ON(expr) \ -+ do { if (expr) ftrace_dump(DUMP_ALL); } while (0) -+#else -+#define GEM_TRACE(...) do { } while (0) -+#define GEM_TRACE_DUMP() do { } while (0) -+#define GEM_TRACE_DUMP_ON(expr) BUILD_BUG_ON_INVALID(expr) -+#endif -+ -+#define I915_GEM_IDLE_TIMEOUT (HZ / 5) -+ -+void i915_gem_park(struct drm_i915_private *i915); -+void i915_gem_unpark(struct drm_i915_private *i915); -+ -+static inline void __tasklet_disable_sync_once(struct tasklet_struct *t) -+{ -+ if (!atomic_fetch_inc(&t->count)) -+ tasklet_unlock_wait(t); -+} -+ -+static inline bool __tasklet_is_enabled(const struct tasklet_struct *t) -+{ -+ return !atomic_read(&t->count); -+} -+ -+static inline bool __tasklet_enable(struct tasklet_struct *t) -+{ -+ return atomic_dec_and_test(&t->count); -+} -+ -+#endif /* __I915_GEM_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_batch_pool.c b/drivers/gpu/drm/i915_legacy/i915_gem_batch_pool.c -new file mode 100644 -index 000000000000..f3890b664e3f ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_batch_pool.c -@@ -0,0 +1,140 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2014-2018 Intel Corporation -+ */ -+ -+#include "i915_gem_batch_pool.h" -+#include "i915_drv.h" -+ -+/** -+ * DOC: batch pool -+ * -+ * In order to submit batch buffers as 'secure', the software command parser -+ * must ensure that a batch buffer cannot be modified after parsing. It does -+ * this by copying the user provided batch buffer contents to a kernel owned -+ * buffer from which the hardware will actually execute, and by carefully -+ * managing the address space bindings for such buffers. -+ * -+ * The batch pool framework provides a mechanism for the driver to manage a -+ * set of scratch buffers to use for this purpose. The framework can be -+ * extended to support other uses cases should they arise. -+ */ -+ -+/** -+ * i915_gem_batch_pool_init() - initialize a batch buffer pool -+ * @pool: the batch buffer pool -+ * @engine: the associated request submission engine -+ */ -+void i915_gem_batch_pool_init(struct i915_gem_batch_pool *pool, -+ struct intel_engine_cs *engine) -+{ -+ int n; -+ -+ pool->engine = engine; -+ -+ for (n = 0; n < ARRAY_SIZE(pool->cache_list); n++) -+ INIT_LIST_HEAD(&pool->cache_list[n]); -+} -+ -+/** -+ * i915_gem_batch_pool_fini() - clean up a batch buffer pool -+ * @pool: the pool to clean up -+ * -+ * Note: Callers must hold the struct_mutex. -+ */ -+void i915_gem_batch_pool_fini(struct i915_gem_batch_pool *pool) -+{ -+ int n; -+ -+ lockdep_assert_held(&pool->engine->i915->drm.struct_mutex); -+ -+ for (n = 0; n < ARRAY_SIZE(pool->cache_list); n++) { -+ struct drm_i915_gem_object *obj, *next; -+ -+ list_for_each_entry_safe(obj, next, -+ &pool->cache_list[n], -+ batch_pool_link) -+ __i915_gem_object_release_unless_active(obj); -+ -+ INIT_LIST_HEAD(&pool->cache_list[n]); -+ } -+} -+ -+/** -+ * i915_gem_batch_pool_get() - allocate a buffer from the pool -+ * @pool: the batch buffer pool -+ * @size: the minimum desired size of the returned buffer -+ * -+ * Returns an inactive buffer from @pool with at least @size bytes, -+ * with the pages pinned. The caller must i915_gem_object_unpin_pages() -+ * on the returned object. -+ * -+ * Note: Callers must hold the struct_mutex -+ * -+ * Return: the buffer object or an error pointer -+ */ -+struct drm_i915_gem_object * -+i915_gem_batch_pool_get(struct i915_gem_batch_pool *pool, -+ size_t size) -+{ -+ struct drm_i915_gem_object *obj; -+ struct list_head *list; -+ int n, ret; -+ -+ lockdep_assert_held(&pool->engine->i915->drm.struct_mutex); -+ -+ /* Compute a power-of-two bucket, but throw everything greater than -+ * 16KiB into the same bucket: i.e. the the buckets hold objects of -+ * (1 page, 2 pages, 4 pages, 8+ pages). -+ */ -+ n = fls(size >> PAGE_SHIFT) - 1; -+ if (n >= ARRAY_SIZE(pool->cache_list)) -+ n = ARRAY_SIZE(pool->cache_list) - 1; -+ list = &pool->cache_list[n]; -+ -+ list_for_each_entry(obj, list, batch_pool_link) { -+ /* The batches are strictly LRU ordered */ -+ if (i915_gem_object_is_active(obj)) { -+ struct reservation_object *resv = obj->resv; -+ -+ if (!reservation_object_test_signaled_rcu(resv, true)) -+ break; -+ -+ i915_retire_requests(pool->engine->i915); -+ GEM_BUG_ON(i915_gem_object_is_active(obj)); -+ -+ /* -+ * The object is now idle, clear the array of shared -+ * fences before we add a new request. Although, we -+ * remain on the same engine, we may be on a different -+ * timeline and so may continually grow the array, -+ * trapping a reference to all the old fences, rather -+ * than replace the existing fence. -+ */ -+ if (rcu_access_pointer(resv->fence)) { -+ reservation_object_lock(resv, NULL); -+ reservation_object_add_excl_fence(resv, NULL); -+ reservation_object_unlock(resv); -+ } -+ } -+ -+ GEM_BUG_ON(!reservation_object_test_signaled_rcu(obj->resv, -+ true)); -+ -+ if (obj->base.size >= size) -+ goto found; -+ } -+ -+ obj = i915_gem_object_create_internal(pool->engine->i915, size); -+ if (IS_ERR(obj)) -+ return obj; -+ -+found: -+ ret = i915_gem_object_pin_pages(obj); -+ if (ret) -+ return ERR_PTR(ret); -+ -+ list_move_tail(&obj->batch_pool_link, list); -+ return obj; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_batch_pool.h b/drivers/gpu/drm/i915_legacy/i915_gem_batch_pool.h -new file mode 100644 -index 000000000000..56947daaaf65 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_batch_pool.h -@@ -0,0 +1,25 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2014-2018 Intel Corporation -+ */ -+ -+#ifndef I915_GEM_BATCH_POOL_H -+#define I915_GEM_BATCH_POOL_H -+ -+#include -+ -+struct intel_engine_cs; -+ -+struct i915_gem_batch_pool { -+ struct intel_engine_cs *engine; -+ struct list_head cache_list[4]; -+}; -+ -+void i915_gem_batch_pool_init(struct i915_gem_batch_pool *pool, -+ struct intel_engine_cs *engine); -+void i915_gem_batch_pool_fini(struct i915_gem_batch_pool *pool); -+struct drm_i915_gem_object* -+i915_gem_batch_pool_get(struct i915_gem_batch_pool *pool, size_t size); -+ -+#endif /* I915_GEM_BATCH_POOL_H */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_clflush.c b/drivers/gpu/drm/i915_legacy/i915_gem_clflush.c -new file mode 100644 -index 000000000000..8e74c23cbd91 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_clflush.c -@@ -0,0 +1,178 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "intel_frontbuffer.h" -+#include "i915_gem_clflush.h" -+ -+static DEFINE_SPINLOCK(clflush_lock); -+ -+struct clflush { -+ struct dma_fence dma; /* Must be first for dma_fence_free() */ -+ struct i915_sw_fence wait; -+ struct work_struct work; -+ struct drm_i915_gem_object *obj; -+}; -+ -+static const char *i915_clflush_get_driver_name(struct dma_fence *fence) -+{ -+ return DRIVER_NAME; -+} -+ -+static const char *i915_clflush_get_timeline_name(struct dma_fence *fence) -+{ -+ return "clflush"; -+} -+ -+static void i915_clflush_release(struct dma_fence *fence) -+{ -+ struct clflush *clflush = container_of(fence, typeof(*clflush), dma); -+ -+ i915_sw_fence_fini(&clflush->wait); -+ -+ BUILD_BUG_ON(offsetof(typeof(*clflush), dma)); -+ dma_fence_free(&clflush->dma); -+} -+ -+static const struct dma_fence_ops i915_clflush_ops = { -+ .get_driver_name = i915_clflush_get_driver_name, -+ .get_timeline_name = i915_clflush_get_timeline_name, -+ .release = i915_clflush_release, -+}; -+ -+static void __i915_do_clflush(struct drm_i915_gem_object *obj) -+{ -+ GEM_BUG_ON(!i915_gem_object_has_pages(obj)); -+ drm_clflush_sg(obj->mm.pages); -+ intel_fb_obj_flush(obj, ORIGIN_CPU); -+} -+ -+static void i915_clflush_work(struct work_struct *work) -+{ -+ struct clflush *clflush = container_of(work, typeof(*clflush), work); -+ struct drm_i915_gem_object *obj = clflush->obj; -+ -+ if (i915_gem_object_pin_pages(obj)) { -+ DRM_ERROR("Failed to acquire obj->pages for clflushing\n"); -+ goto out; -+ } -+ -+ __i915_do_clflush(obj); -+ -+ i915_gem_object_unpin_pages(obj); -+ -+out: -+ i915_gem_object_put(obj); -+ -+ dma_fence_signal(&clflush->dma); -+ dma_fence_put(&clflush->dma); -+} -+ -+static int __i915_sw_fence_call -+i915_clflush_notify(struct i915_sw_fence *fence, -+ enum i915_sw_fence_notify state) -+{ -+ struct clflush *clflush = container_of(fence, typeof(*clflush), wait); -+ -+ switch (state) { -+ case FENCE_COMPLETE: -+ schedule_work(&clflush->work); -+ break; -+ -+ case FENCE_FREE: -+ dma_fence_put(&clflush->dma); -+ break; -+ } -+ -+ return NOTIFY_DONE; -+} -+ -+bool i915_gem_clflush_object(struct drm_i915_gem_object *obj, -+ unsigned int flags) -+{ -+ struct clflush *clflush; -+ -+ /* -+ * Stolen memory is always coherent with the GPU as it is explicitly -+ * marked as wc by the system, or the system is cache-coherent. -+ * Similarly, we only access struct pages through the CPU cache, so -+ * anything not backed by physical memory we consider to be always -+ * coherent and not need clflushing. -+ */ -+ if (!i915_gem_object_has_struct_page(obj)) { -+ obj->cache_dirty = false; -+ return false; -+ } -+ -+ /* If the GPU is snooping the contents of the CPU cache, -+ * we do not need to manually clear the CPU cache lines. However, -+ * the caches are only snooped when the render cache is -+ * flushed/invalidated. As we always have to emit invalidations -+ * and flushes when moving into and out of the RENDER domain, correct -+ * snooping behaviour occurs naturally as the result of our domain -+ * tracking. -+ */ -+ if (!(flags & I915_CLFLUSH_FORCE) && -+ obj->cache_coherent & I915_BO_CACHE_COHERENT_FOR_READ) -+ return false; -+ -+ trace_i915_gem_object_clflush(obj); -+ -+ clflush = NULL; -+ if (!(flags & I915_CLFLUSH_SYNC)) -+ clflush = kmalloc(sizeof(*clflush), GFP_KERNEL); -+ if (clflush) { -+ GEM_BUG_ON(!obj->cache_dirty); -+ -+ dma_fence_init(&clflush->dma, -+ &i915_clflush_ops, -+ &clflush_lock, -+ to_i915(obj->base.dev)->mm.unordered_timeline, -+ 0); -+ i915_sw_fence_init(&clflush->wait, i915_clflush_notify); -+ -+ clflush->obj = i915_gem_object_get(obj); -+ INIT_WORK(&clflush->work, i915_clflush_work); -+ -+ dma_fence_get(&clflush->dma); -+ -+ i915_sw_fence_await_reservation(&clflush->wait, -+ obj->resv, NULL, -+ true, I915_FENCE_TIMEOUT, -+ I915_FENCE_GFP); -+ -+ reservation_object_lock(obj->resv, NULL); -+ reservation_object_add_excl_fence(obj->resv, &clflush->dma); -+ reservation_object_unlock(obj->resv); -+ -+ i915_sw_fence_commit(&clflush->wait); -+ } else if (obj->mm.pages) { -+ __i915_do_clflush(obj); -+ } else { -+ GEM_BUG_ON(obj->write_domain != I915_GEM_DOMAIN_CPU); -+ } -+ -+ obj->cache_dirty = false; -+ return true; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_clflush.h b/drivers/gpu/drm/i915_legacy/i915_gem_clflush.h -new file mode 100644 -index 000000000000..f390247561b3 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_clflush.h -@@ -0,0 +1,36 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef __I915_GEM_CLFLUSH_H__ -+#define __I915_GEM_CLFLUSH_H__ -+ -+struct drm_i915_private; -+struct drm_i915_gem_object; -+ -+bool i915_gem_clflush_object(struct drm_i915_gem_object *obj, -+ unsigned int flags); -+#define I915_CLFLUSH_FORCE BIT(0) -+#define I915_CLFLUSH_SYNC BIT(1) -+ -+#endif /* __I915_GEM_CLFLUSH_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_context.c b/drivers/gpu/drm/i915_legacy/i915_gem_context.c -new file mode 100644 -index 000000000000..fb5e2784d3c7 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_context.c -@@ -0,0 +1,1829 @@ -+/* -+ * Copyright © 2011-2012 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Ben Widawsky -+ * -+ */ -+ -+/* -+ * This file implements HW context support. On gen5+ a HW context consists of an -+ * opaque GPU object which is referenced at times of context saves and restores. -+ * With RC6 enabled, the context is also referenced as the GPU enters and exists -+ * from RC6 (GPU has it's own internal power context, except on gen5). Though -+ * something like a context does exist for the media ring, the code only -+ * supports contexts for the render ring. -+ * -+ * In software, there is a distinction between contexts created by the user, -+ * and the default HW context. The default HW context is used by GPU clients -+ * that do not request setup of their own hardware context. The default -+ * context's state is never restored to help prevent programming errors. This -+ * would happen if a client ran and piggy-backed off another clients GPU state. -+ * The default context only exists to give the GPU some offset to load as the -+ * current to invoke a save of the context we actually care about. In fact, the -+ * code could likely be constructed, albeit in a more complicated fashion, to -+ * never use the default context, though that limits the driver's ability to -+ * swap out, and/or destroy other contexts. -+ * -+ * All other contexts are created as a request by the GPU client. These contexts -+ * store GPU state, and thus allow GPU clients to not re-emit state (and -+ * potentially query certain state) at any time. The kernel driver makes -+ * certain that the appropriate commands are inserted. -+ * -+ * The context life cycle is semi-complicated in that context BOs may live -+ * longer than the context itself because of the way the hardware, and object -+ * tracking works. Below is a very crude representation of the state machine -+ * describing the context life. -+ * refcount pincount active -+ * S0: initial state 0 0 0 -+ * S1: context created 1 0 0 -+ * S2: context is currently running 2 1 X -+ * S3: GPU referenced, but not current 2 0 1 -+ * S4: context is current, but destroyed 1 1 0 -+ * S5: like S3, but destroyed 1 0 1 -+ * -+ * The most common (but not all) transitions: -+ * S0->S1: client creates a context -+ * S1->S2: client submits execbuf with context -+ * S2->S3: other clients submits execbuf with context -+ * S3->S1: context object was retired -+ * S3->S2: clients submits another execbuf -+ * S2->S4: context destroy called with current context -+ * S3->S5->S0: destroy path -+ * S4->S5->S0: destroy path on current context -+ * -+ * There are two confusing terms used above: -+ * The "current context" means the context which is currently running on the -+ * GPU. The GPU has loaded its state already and has stored away the gtt -+ * offset of the BO. The GPU is not actively referencing the data at this -+ * offset, but it will on the next context switch. The only way to avoid this -+ * is to do a GPU reset. -+ * -+ * An "active context' is one which was previously the "current context" and is -+ * on the active list waiting for the next context switch to occur. Until this -+ * happens, the object must remain at the same gtt offset. It is therefore -+ * possible to destroy a context, but it is still active. -+ * -+ */ -+ -+#include -+#include -+#include "i915_drv.h" -+#include "i915_globals.h" -+#include "i915_trace.h" -+#include "i915_user_extensions.h" -+#include "intel_lrc_reg.h" -+#include "intel_workarounds.h" -+ -+#define I915_CONTEXT_PARAM_VM 0x9 -+ -+#define ALL_L3_SLICES(dev) (1 << NUM_L3_SLICES(dev)) - 1 -+ -+static struct i915_global_gem_context { -+ struct i915_global base; -+ struct kmem_cache *slab_luts; -+} global; -+ -+struct i915_lut_handle *i915_lut_handle_alloc(void) -+{ -+ return kmem_cache_alloc(global.slab_luts, GFP_KERNEL); -+} -+ -+void i915_lut_handle_free(struct i915_lut_handle *lut) -+{ -+ return kmem_cache_free(global.slab_luts, lut); -+} -+ -+static void lut_close(struct i915_gem_context *ctx) -+{ -+ struct i915_lut_handle *lut, *ln; -+ struct radix_tree_iter iter; -+ void __rcu **slot; -+ -+ list_for_each_entry_safe(lut, ln, &ctx->handles_list, ctx_link) { -+ list_del(&lut->obj_link); -+ i915_lut_handle_free(lut); -+ } -+ INIT_LIST_HEAD(&ctx->handles_list); -+ -+ rcu_read_lock(); -+ radix_tree_for_each_slot(slot, &ctx->handles_vma, &iter, 0) { -+ struct i915_vma *vma = rcu_dereference_raw(*slot); -+ -+ radix_tree_iter_delete(&ctx->handles_vma, &iter, slot); -+ -+ vma->open_count--; -+ __i915_gem_object_release_unless_active(vma->obj); -+ } -+ rcu_read_unlock(); -+} -+ -+static inline int new_hw_id(struct drm_i915_private *i915, gfp_t gfp) -+{ -+ unsigned int max; -+ -+ lockdep_assert_held(&i915->contexts.mutex); -+ -+ if (INTEL_GEN(i915) >= 11) -+ max = GEN11_MAX_CONTEXT_HW_ID; -+ else if (USES_GUC_SUBMISSION(i915)) -+ /* -+ * When using GuC in proxy submission, GuC consumes the -+ * highest bit in the context id to indicate proxy submission. -+ */ -+ max = MAX_GUC_CONTEXT_HW_ID; -+ else -+ max = MAX_CONTEXT_HW_ID; -+ -+ return ida_simple_get(&i915->contexts.hw_ida, 0, max, gfp); -+} -+ -+static int steal_hw_id(struct drm_i915_private *i915) -+{ -+ struct i915_gem_context *ctx, *cn; -+ LIST_HEAD(pinned); -+ int id = -ENOSPC; -+ -+ lockdep_assert_held(&i915->contexts.mutex); -+ -+ list_for_each_entry_safe(ctx, cn, -+ &i915->contexts.hw_id_list, hw_id_link) { -+ if (atomic_read(&ctx->hw_id_pin_count)) { -+ list_move_tail(&ctx->hw_id_link, &pinned); -+ continue; -+ } -+ -+ GEM_BUG_ON(!ctx->hw_id); /* perma-pinned kernel context */ -+ list_del_init(&ctx->hw_id_link); -+ id = ctx->hw_id; -+ break; -+ } -+ -+ /* -+ * Remember how far we got up on the last repossesion scan, so the -+ * list is kept in a "least recently scanned" order. -+ */ -+ list_splice_tail(&pinned, &i915->contexts.hw_id_list); -+ return id; -+} -+ -+static int assign_hw_id(struct drm_i915_private *i915, unsigned int *out) -+{ -+ int ret; -+ -+ lockdep_assert_held(&i915->contexts.mutex); -+ -+ /* -+ * We prefer to steal/stall ourselves and our users over that of the -+ * entire system. That may be a little unfair to our users, and -+ * even hurt high priority clients. The choice is whether to oomkill -+ * something else, or steal a context id. -+ */ -+ ret = new_hw_id(i915, GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_NOWARN); -+ if (unlikely(ret < 0)) { -+ ret = steal_hw_id(i915); -+ if (ret < 0) /* once again for the correct errno code */ -+ ret = new_hw_id(i915, GFP_KERNEL); -+ if (ret < 0) -+ return ret; -+ } -+ -+ *out = ret; -+ return 0; -+} -+ -+static void release_hw_id(struct i915_gem_context *ctx) -+{ -+ struct drm_i915_private *i915 = ctx->i915; -+ -+ if (list_empty(&ctx->hw_id_link)) -+ return; -+ -+ mutex_lock(&i915->contexts.mutex); -+ if (!list_empty(&ctx->hw_id_link)) { -+ ida_simple_remove(&i915->contexts.hw_ida, ctx->hw_id); -+ list_del_init(&ctx->hw_id_link); -+ } -+ mutex_unlock(&i915->contexts.mutex); -+} -+ -+static void i915_gem_context_free(struct i915_gem_context *ctx) -+{ -+ struct intel_context *it, *n; -+ -+ lockdep_assert_held(&ctx->i915->drm.struct_mutex); -+ GEM_BUG_ON(!i915_gem_context_is_closed(ctx)); -+ GEM_BUG_ON(!list_empty(&ctx->active_engines)); -+ -+ release_hw_id(ctx); -+ i915_ppgtt_put(ctx->ppgtt); -+ -+ rbtree_postorder_for_each_entry_safe(it, n, &ctx->hw_contexts, node) -+ intel_context_put(it); -+ -+ if (ctx->timeline) -+ i915_timeline_put(ctx->timeline); -+ -+ kfree(ctx->name); -+ put_pid(ctx->pid); -+ -+ list_del(&ctx->link); -+ mutex_destroy(&ctx->mutex); -+ -+ kfree_rcu(ctx, rcu); -+} -+ -+static void contexts_free(struct drm_i915_private *i915) -+{ -+ struct llist_node *freed = llist_del_all(&i915->contexts.free_list); -+ struct i915_gem_context *ctx, *cn; -+ -+ lockdep_assert_held(&i915->drm.struct_mutex); -+ -+ llist_for_each_entry_safe(ctx, cn, freed, free_link) -+ i915_gem_context_free(ctx); -+} -+ -+static void contexts_free_first(struct drm_i915_private *i915) -+{ -+ struct i915_gem_context *ctx; -+ struct llist_node *freed; -+ -+ lockdep_assert_held(&i915->drm.struct_mutex); -+ -+ freed = llist_del_first(&i915->contexts.free_list); -+ if (!freed) -+ return; -+ -+ ctx = container_of(freed, typeof(*ctx), free_link); -+ i915_gem_context_free(ctx); -+} -+ -+static void contexts_free_worker(struct work_struct *work) -+{ -+ struct drm_i915_private *i915 = -+ container_of(work, typeof(*i915), contexts.free_work); -+ -+ mutex_lock(&i915->drm.struct_mutex); -+ contexts_free(i915); -+ mutex_unlock(&i915->drm.struct_mutex); -+} -+ -+void i915_gem_context_release(struct kref *ref) -+{ -+ struct i915_gem_context *ctx = container_of(ref, typeof(*ctx), ref); -+ struct drm_i915_private *i915 = ctx->i915; -+ -+ trace_i915_context_free(ctx); -+ if (llist_add(&ctx->free_link, &i915->contexts.free_list)) -+ queue_work(i915->wq, &i915->contexts.free_work); -+} -+ -+static void context_close(struct i915_gem_context *ctx) -+{ -+ i915_gem_context_set_closed(ctx); -+ -+ /* -+ * This context will never again be assinged to HW, so we can -+ * reuse its ID for the next context. -+ */ -+ release_hw_id(ctx); -+ -+ /* -+ * The LUT uses the VMA as a backpointer to unref the object, -+ * so we need to clear the LUT before we close all the VMA (inside -+ * the ppgtt). -+ */ -+ lut_close(ctx); -+ -+ ctx->file_priv = ERR_PTR(-EBADF); -+ i915_gem_context_put(ctx); -+} -+ -+static u32 default_desc_template(const struct drm_i915_private *i915, -+ const struct i915_hw_ppgtt *ppgtt) -+{ -+ u32 address_mode; -+ u32 desc; -+ -+ desc = GEN8_CTX_VALID | GEN8_CTX_PRIVILEGE; -+ -+ address_mode = INTEL_LEGACY_32B_CONTEXT; -+ if (ppgtt && i915_vm_is_4lvl(&ppgtt->vm)) -+ address_mode = INTEL_LEGACY_64B_CONTEXT; -+ desc |= address_mode << GEN8_CTX_ADDRESSING_MODE_SHIFT; -+ -+ if (IS_GEN(i915, 8)) -+ desc |= GEN8_CTX_L3LLC_COHERENT; -+ -+ /* TODO: WaDisableLiteRestore when we start using semaphore -+ * signalling between Command Streamers -+ * ring->ctx_desc_template |= GEN8_CTX_FORCE_RESTORE; -+ */ -+ -+ return desc; -+} -+ -+static struct i915_gem_context * -+__create_context(struct drm_i915_private *dev_priv) -+{ -+ struct i915_gem_context *ctx; -+ int i; -+ -+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); -+ if (!ctx) -+ return ERR_PTR(-ENOMEM); -+ -+ kref_init(&ctx->ref); -+ list_add_tail(&ctx->link, &dev_priv->contexts.list); -+ ctx->i915 = dev_priv; -+ ctx->sched.priority = I915_USER_PRIORITY(I915_PRIORITY_NORMAL); -+ INIT_LIST_HEAD(&ctx->active_engines); -+ mutex_init(&ctx->mutex); -+ -+ ctx->hw_contexts = RB_ROOT; -+ spin_lock_init(&ctx->hw_contexts_lock); -+ -+ INIT_RADIX_TREE(&ctx->handles_vma, GFP_KERNEL); -+ INIT_LIST_HEAD(&ctx->handles_list); -+ INIT_LIST_HEAD(&ctx->hw_id_link); -+ -+ /* NB: Mark all slices as needing a remap so that when the context first -+ * loads it will restore whatever remap state already exists. If there -+ * is no remap info, it will be a NOP. */ -+ ctx->remap_slice = ALL_L3_SLICES(dev_priv); -+ -+ i915_gem_context_set_bannable(ctx); -+ i915_gem_context_set_recoverable(ctx); -+ -+ ctx->ring_size = 4 * PAGE_SIZE; -+ ctx->desc_template = -+ default_desc_template(dev_priv, dev_priv->mm.aliasing_ppgtt); -+ -+ for (i = 0; i < ARRAY_SIZE(ctx->hang_timestamp); i++) -+ ctx->hang_timestamp[i] = jiffies - CONTEXT_FAST_HANG_JIFFIES; -+ -+ return ctx; -+} -+ -+static struct i915_hw_ppgtt * -+__set_ppgtt(struct i915_gem_context *ctx, struct i915_hw_ppgtt *ppgtt) -+{ -+ struct i915_hw_ppgtt *old = ctx->ppgtt; -+ -+ ctx->ppgtt = i915_ppgtt_get(ppgtt); -+ ctx->desc_template = default_desc_template(ctx->i915, ppgtt); -+ -+ return old; -+} -+ -+static void __assign_ppgtt(struct i915_gem_context *ctx, -+ struct i915_hw_ppgtt *ppgtt) -+{ -+ if (ppgtt == ctx->ppgtt) -+ return; -+ -+ ppgtt = __set_ppgtt(ctx, ppgtt); -+ if (ppgtt) -+ i915_ppgtt_put(ppgtt); -+} -+ -+static struct i915_gem_context * -+i915_gem_create_context(struct drm_i915_private *dev_priv, unsigned int flags) -+{ -+ struct i915_gem_context *ctx; -+ -+ lockdep_assert_held(&dev_priv->drm.struct_mutex); -+ -+ if (flags & I915_CONTEXT_CREATE_FLAGS_SINGLE_TIMELINE && -+ !HAS_EXECLISTS(dev_priv)) -+ return ERR_PTR(-EINVAL); -+ -+ /* Reap the most stale context */ -+ contexts_free_first(dev_priv); -+ -+ ctx = __create_context(dev_priv); -+ if (IS_ERR(ctx)) -+ return ctx; -+ -+ if (HAS_FULL_PPGTT(dev_priv)) { -+ struct i915_hw_ppgtt *ppgtt; -+ -+ ppgtt = i915_ppgtt_create(dev_priv); -+ if (IS_ERR(ppgtt)) { -+ DRM_DEBUG_DRIVER("PPGTT setup failed (%ld)\n", -+ PTR_ERR(ppgtt)); -+ context_close(ctx); -+ return ERR_CAST(ppgtt); -+ } -+ -+ __assign_ppgtt(ctx, ppgtt); -+ i915_ppgtt_put(ppgtt); -+ } -+ -+ if (flags & I915_CONTEXT_CREATE_FLAGS_SINGLE_TIMELINE) { -+ struct i915_timeline *timeline; -+ -+ timeline = i915_timeline_create(dev_priv, NULL); -+ if (IS_ERR(timeline)) { -+ context_close(ctx); -+ return ERR_CAST(timeline); -+ } -+ -+ ctx->timeline = timeline; -+ } -+ -+ trace_i915_context_create(ctx); -+ -+ return ctx; -+} -+ -+/** -+ * i915_gem_context_create_gvt - create a GVT GEM context -+ * @dev: drm device * -+ * -+ * This function is used to create a GVT specific GEM context. -+ * -+ * Returns: -+ * pointer to i915_gem_context on success, error pointer if failed -+ * -+ */ -+struct i915_gem_context * -+i915_gem_context_create_gvt(struct drm_device *dev) -+{ -+ struct i915_gem_context *ctx; -+ int ret; -+ -+ if (!IS_ENABLED(CONFIG_DRM_I915_GVT)) -+ return ERR_PTR(-ENODEV); -+ -+ ret = i915_mutex_lock_interruptible(dev); -+ if (ret) -+ return ERR_PTR(ret); -+ -+ ctx = i915_gem_create_context(to_i915(dev), 0); -+ if (IS_ERR(ctx)) -+ goto out; -+ -+ ret = i915_gem_context_pin_hw_id(ctx); -+ if (ret) { -+ context_close(ctx); -+ ctx = ERR_PTR(ret); -+ goto out; -+ } -+ -+ ctx->file_priv = ERR_PTR(-EBADF); -+ i915_gem_context_set_closed(ctx); /* not user accessible */ -+ i915_gem_context_clear_bannable(ctx); -+ i915_gem_context_set_force_single_submission(ctx); -+ if (!USES_GUC_SUBMISSION(to_i915(dev))) -+ ctx->ring_size = 512 * PAGE_SIZE; /* Max ring buffer size */ -+ -+ GEM_BUG_ON(i915_gem_context_is_kernel(ctx)); -+out: -+ mutex_unlock(&dev->struct_mutex); -+ return ctx; -+} -+ -+static void -+destroy_kernel_context(struct i915_gem_context **ctxp) -+{ -+ struct i915_gem_context *ctx; -+ -+ /* Keep the context ref so that we can free it immediately ourselves */ -+ ctx = i915_gem_context_get(fetch_and_zero(ctxp)); -+ GEM_BUG_ON(!i915_gem_context_is_kernel(ctx)); -+ -+ context_close(ctx); -+ i915_gem_context_free(ctx); -+} -+ -+struct i915_gem_context * -+i915_gem_context_create_kernel(struct drm_i915_private *i915, int prio) -+{ -+ struct i915_gem_context *ctx; -+ int err; -+ -+ ctx = i915_gem_create_context(i915, 0); -+ if (IS_ERR(ctx)) -+ return ctx; -+ -+ err = i915_gem_context_pin_hw_id(ctx); -+ if (err) { -+ destroy_kernel_context(&ctx); -+ return ERR_PTR(err); -+ } -+ -+ i915_gem_context_clear_bannable(ctx); -+ ctx->sched.priority = I915_USER_PRIORITY(prio); -+ ctx->ring_size = PAGE_SIZE; -+ -+ GEM_BUG_ON(!i915_gem_context_is_kernel(ctx)); -+ -+ return ctx; -+} -+ -+static void init_contexts(struct drm_i915_private *i915) -+{ -+ mutex_init(&i915->contexts.mutex); -+ INIT_LIST_HEAD(&i915->contexts.list); -+ -+ /* Using the simple ida interface, the max is limited by sizeof(int) */ -+ BUILD_BUG_ON(MAX_CONTEXT_HW_ID > INT_MAX); -+ BUILD_BUG_ON(GEN11_MAX_CONTEXT_HW_ID > INT_MAX); -+ ida_init(&i915->contexts.hw_ida); -+ INIT_LIST_HEAD(&i915->contexts.hw_id_list); -+ -+ INIT_WORK(&i915->contexts.free_work, contexts_free_worker); -+ init_llist_head(&i915->contexts.free_list); -+} -+ -+static bool needs_preempt_context(struct drm_i915_private *i915) -+{ -+ return HAS_EXECLISTS(i915); -+} -+ -+int i915_gem_contexts_init(struct drm_i915_private *dev_priv) -+{ -+ struct i915_gem_context *ctx; -+ -+ /* Reassure ourselves we are only called once */ -+ GEM_BUG_ON(dev_priv->kernel_context); -+ GEM_BUG_ON(dev_priv->preempt_context); -+ -+ intel_engine_init_ctx_wa(dev_priv->engine[RCS0]); -+ init_contexts(dev_priv); -+ -+ /* lowest priority; idle task */ -+ ctx = i915_gem_context_create_kernel(dev_priv, I915_PRIORITY_MIN); -+ if (IS_ERR(ctx)) { -+ DRM_ERROR("Failed to create default global context\n"); -+ return PTR_ERR(ctx); -+ } -+ /* -+ * For easy recognisablity, we want the kernel context to be 0 and then -+ * all user contexts will have non-zero hw_id. Kernel contexts are -+ * permanently pinned, so that we never suffer a stall and can -+ * use them from any allocation context (e.g. for evicting other -+ * contexts and from inside the shrinker). -+ */ -+ GEM_BUG_ON(ctx->hw_id); -+ GEM_BUG_ON(!atomic_read(&ctx->hw_id_pin_count)); -+ dev_priv->kernel_context = ctx; -+ -+ /* highest priority; preempting task */ -+ if (needs_preempt_context(dev_priv)) { -+ ctx = i915_gem_context_create_kernel(dev_priv, INT_MAX); -+ if (!IS_ERR(ctx)) -+ dev_priv->preempt_context = ctx; -+ else -+ DRM_ERROR("Failed to create preempt context; disabling preemption\n"); -+ } -+ -+ DRM_DEBUG_DRIVER("%s context support initialized\n", -+ DRIVER_CAPS(dev_priv)->has_logical_contexts ? -+ "logical" : "fake"); -+ return 0; -+} -+ -+void i915_gem_contexts_lost(struct drm_i915_private *dev_priv) -+{ -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ -+ lockdep_assert_held(&dev_priv->drm.struct_mutex); -+ -+ for_each_engine(engine, dev_priv, id) -+ intel_engine_lost_context(engine); -+} -+ -+void i915_gem_contexts_fini(struct drm_i915_private *i915) -+{ -+ lockdep_assert_held(&i915->drm.struct_mutex); -+ -+ if (i915->preempt_context) -+ destroy_kernel_context(&i915->preempt_context); -+ destroy_kernel_context(&i915->kernel_context); -+ -+ /* Must free all deferred contexts (via flush_workqueue) first */ -+ GEM_BUG_ON(!list_empty(&i915->contexts.hw_id_list)); -+ ida_destroy(&i915->contexts.hw_ida); -+} -+ -+static int context_idr_cleanup(int id, void *p, void *data) -+{ -+ context_close(p); -+ return 0; -+} -+ -+static int vm_idr_cleanup(int id, void *p, void *data) -+{ -+ i915_ppgtt_put(p); -+ return 0; -+} -+ -+static int gem_context_register(struct i915_gem_context *ctx, -+ struct drm_i915_file_private *fpriv) -+{ -+ int ret; -+ -+ ctx->file_priv = fpriv; -+ if (ctx->ppgtt) -+ ctx->ppgtt->vm.file = fpriv; -+ -+ ctx->pid = get_task_pid(current, PIDTYPE_PID); -+ ctx->name = kasprintf(GFP_KERNEL, "%s[%d]", -+ current->comm, pid_nr(ctx->pid)); -+ if (!ctx->name) { -+ ret = -ENOMEM; -+ goto err_pid; -+ } -+ -+ /* And finally expose ourselves to userspace via the idr */ -+ mutex_lock(&fpriv->context_idr_lock); -+ ret = idr_alloc(&fpriv->context_idr, ctx, 0, 0, GFP_KERNEL); -+ mutex_unlock(&fpriv->context_idr_lock); -+ if (ret >= 0) -+ goto out; -+ -+ kfree(fetch_and_zero(&ctx->name)); -+err_pid: -+ put_pid(fetch_and_zero(&ctx->pid)); -+out: -+ return ret; -+} -+ -+int i915_gem_context_open(struct drm_i915_private *i915, -+ struct drm_file *file) -+{ -+ struct drm_i915_file_private *file_priv = file->driver_priv; -+ struct i915_gem_context *ctx; -+ int err; -+ -+ mutex_init(&file_priv->context_idr_lock); -+ mutex_init(&file_priv->vm_idr_lock); -+ -+ idr_init(&file_priv->context_idr); -+ idr_init_base(&file_priv->vm_idr, 1); -+ -+ mutex_lock(&i915->drm.struct_mutex); -+ ctx = i915_gem_create_context(i915, 0); -+ mutex_unlock(&i915->drm.struct_mutex); -+ if (IS_ERR(ctx)) { -+ err = PTR_ERR(ctx); -+ goto err; -+ } -+ -+ err = gem_context_register(ctx, file_priv); -+ if (err < 0) -+ goto err_ctx; -+ -+ GEM_BUG_ON(i915_gem_context_is_kernel(ctx)); -+ GEM_BUG_ON(err > 0); -+ -+ return 0; -+ -+err_ctx: -+ mutex_lock(&i915->drm.struct_mutex); -+ context_close(ctx); -+ mutex_unlock(&i915->drm.struct_mutex); -+err: -+ idr_destroy(&file_priv->vm_idr); -+ idr_destroy(&file_priv->context_idr); -+ mutex_destroy(&file_priv->vm_idr_lock); -+ mutex_destroy(&file_priv->context_idr_lock); -+ return err; -+} -+ -+void i915_gem_context_close(struct drm_file *file) -+{ -+ struct drm_i915_file_private *file_priv = file->driver_priv; -+ -+ lockdep_assert_held(&file_priv->dev_priv->drm.struct_mutex); -+ -+ idr_for_each(&file_priv->context_idr, context_idr_cleanup, NULL); -+ idr_destroy(&file_priv->context_idr); -+ mutex_destroy(&file_priv->context_idr_lock); -+ -+ idr_for_each(&file_priv->vm_idr, vm_idr_cleanup, NULL); -+ idr_destroy(&file_priv->vm_idr); -+ mutex_destroy(&file_priv->vm_idr_lock); -+} -+ -+int i915_gem_vm_create_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_private *i915 = to_i915(dev); -+ struct drm_i915_gem_vm_control *args = data; -+ struct drm_i915_file_private *file_priv = file->driver_priv; -+ struct i915_hw_ppgtt *ppgtt; -+ int err; -+ -+ if (!HAS_FULL_PPGTT(i915)) -+ return -ENODEV; -+ -+ if (args->flags) -+ return -EINVAL; -+ -+ ppgtt = i915_ppgtt_create(i915); -+ if (IS_ERR(ppgtt)) -+ return PTR_ERR(ppgtt); -+ -+ ppgtt->vm.file = file_priv; -+ -+ if (args->extensions) { -+ err = i915_user_extensions(u64_to_user_ptr(args->extensions), -+ NULL, 0, -+ ppgtt); -+ if (err) -+ goto err_put; -+ } -+ -+ err = mutex_lock_interruptible(&file_priv->vm_idr_lock); -+ if (err) -+ goto err_put; -+ -+ err = idr_alloc(&file_priv->vm_idr, ppgtt, 0, 0, GFP_KERNEL); -+ if (err < 0) -+ goto err_unlock; -+ -+ GEM_BUG_ON(err == 0); /* reserved for default/unassigned ppgtt */ -+ ppgtt->user_handle = err; -+ -+ mutex_unlock(&file_priv->vm_idr_lock); -+ -+ args->vm_id = err; -+ return 0; -+ -+err_unlock: -+ mutex_unlock(&file_priv->vm_idr_lock); -+err_put: -+ i915_ppgtt_put(ppgtt); -+ return err; -+} -+ -+int i915_gem_vm_destroy_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_file_private *file_priv = file->driver_priv; -+ struct drm_i915_gem_vm_control *args = data; -+ struct i915_hw_ppgtt *ppgtt; -+ int err; -+ u32 id; -+ -+ if (args->flags) -+ return -EINVAL; -+ -+ if (args->extensions) -+ return -EINVAL; -+ -+ id = args->vm_id; -+ if (!id) -+ return -ENOENT; -+ -+ err = mutex_lock_interruptible(&file_priv->vm_idr_lock); -+ if (err) -+ return err; -+ -+ ppgtt = idr_remove(&file_priv->vm_idr, id); -+ if (ppgtt) { -+ GEM_BUG_ON(ppgtt->user_handle != id); -+ ppgtt->user_handle = 0; -+ } -+ -+ mutex_unlock(&file_priv->vm_idr_lock); -+ if (!ppgtt) -+ return -ENOENT; -+ -+ i915_ppgtt_put(ppgtt); -+ return 0; -+} -+ -+static struct i915_request * -+last_request_on_engine(struct i915_timeline *timeline, -+ struct intel_engine_cs *engine) -+{ -+ struct i915_request *rq; -+ -+ GEM_BUG_ON(timeline == &engine->timeline); -+ -+ rq = i915_active_request_raw(&timeline->last_request, -+ &engine->i915->drm.struct_mutex); -+ if (rq && rq->engine->mask & engine->mask) { -+ GEM_TRACE("last request on engine %s: %llx:%llu\n", -+ engine->name, rq->fence.context, rq->fence.seqno); -+ GEM_BUG_ON(rq->timeline != timeline); -+ return rq; -+ } -+ -+ return NULL; -+} -+ -+struct context_barrier_task { -+ struct i915_active base; -+ void (*task)(void *data); -+ void *data; -+}; -+ -+static void cb_retire(struct i915_active *base) -+{ -+ struct context_barrier_task *cb = container_of(base, typeof(*cb), base); -+ -+ if (cb->task) -+ cb->task(cb->data); -+ -+ i915_active_fini(&cb->base); -+ kfree(cb); -+} -+ -+I915_SELFTEST_DECLARE(static intel_engine_mask_t context_barrier_inject_fault); -+static int context_barrier_task(struct i915_gem_context *ctx, -+ intel_engine_mask_t engines, -+ int (*emit)(struct i915_request *rq, void *data), -+ void (*task)(void *data), -+ void *data) -+{ -+ struct drm_i915_private *i915 = ctx->i915; -+ struct context_barrier_task *cb; -+ struct intel_context *ce, *next; -+ intel_wakeref_t wakeref; -+ int err = 0; -+ -+ lockdep_assert_held(&i915->drm.struct_mutex); -+ GEM_BUG_ON(!task); -+ -+ cb = kmalloc(sizeof(*cb), GFP_KERNEL); -+ if (!cb) -+ return -ENOMEM; -+ -+ i915_active_init(i915, &cb->base, cb_retire); -+ i915_active_acquire(&cb->base); -+ -+ wakeref = intel_runtime_pm_get(i915); -+ rbtree_postorder_for_each_entry_safe(ce, next, &ctx->hw_contexts, node) { -+ struct intel_engine_cs *engine = ce->engine; -+ struct i915_request *rq; -+ -+ if (!(engine->mask & engines)) -+ continue; -+ -+ if (I915_SELFTEST_ONLY(context_barrier_inject_fault & -+ engine->mask)) { -+ err = -ENXIO; -+ break; -+ } -+ -+ rq = i915_request_alloc(engine, ctx); -+ if (IS_ERR(rq)) { -+ err = PTR_ERR(rq); -+ break; -+ } -+ -+ err = 0; -+ if (emit) -+ err = emit(rq, data); -+ if (err == 0) -+ err = i915_active_ref(&cb->base, rq->fence.context, rq); -+ -+ i915_request_add(rq); -+ if (err) -+ break; -+ } -+ intel_runtime_pm_put(i915, wakeref); -+ -+ cb->task = err ? NULL : task; /* caller needs to unwind instead */ -+ cb->data = data; -+ -+ i915_active_release(&cb->base); -+ -+ return err; -+} -+ -+int i915_gem_switch_to_kernel_context(struct drm_i915_private *i915, -+ intel_engine_mask_t mask) -+{ -+ struct intel_engine_cs *engine; -+ -+ GEM_TRACE("awake?=%s\n", yesno(i915->gt.awake)); -+ -+ lockdep_assert_held(&i915->drm.struct_mutex); -+ GEM_BUG_ON(!i915->kernel_context); -+ -+ /* Inoperable, so presume the GPU is safely pointing into the void! */ -+ if (i915_terminally_wedged(i915)) -+ return 0; -+ -+ for_each_engine_masked(engine, i915, mask, mask) { -+ struct intel_ring *ring; -+ struct i915_request *rq; -+ -+ rq = i915_request_alloc(engine, i915->kernel_context); -+ if (IS_ERR(rq)) -+ return PTR_ERR(rq); -+ -+ /* Queue this switch after all other activity */ -+ list_for_each_entry(ring, &i915->gt.active_rings, active_link) { -+ struct i915_request *prev; -+ -+ prev = last_request_on_engine(ring->timeline, engine); -+ if (!prev) -+ continue; -+ -+ if (prev->gem_context == i915->kernel_context) -+ continue; -+ -+ GEM_TRACE("add barrier on %s for %llx:%lld\n", -+ engine->name, -+ prev->fence.context, -+ prev->fence.seqno); -+ i915_sw_fence_await_sw_fence_gfp(&rq->submit, -+ &prev->submit, -+ I915_FENCE_GFP); -+ } -+ -+ i915_request_add(rq); -+ } -+ -+ return 0; -+} -+ -+static int get_ppgtt(struct drm_i915_file_private *file_priv, -+ struct i915_gem_context *ctx, -+ struct drm_i915_gem_context_param *args) -+{ -+ struct i915_hw_ppgtt *ppgtt; -+ int ret; -+ -+ return -EINVAL; /* nothing to see here; please move along */ -+ -+ if (!ctx->ppgtt) -+ return -ENODEV; -+ -+ /* XXX rcu acquire? */ -+ ret = mutex_lock_interruptible(&ctx->i915->drm.struct_mutex); -+ if (ret) -+ return ret; -+ -+ ppgtt = i915_ppgtt_get(ctx->ppgtt); -+ mutex_unlock(&ctx->i915->drm.struct_mutex); -+ -+ ret = mutex_lock_interruptible(&file_priv->vm_idr_lock); -+ if (ret) -+ goto err_put; -+ -+ if (!ppgtt->user_handle) { -+ ret = idr_alloc(&file_priv->vm_idr, ppgtt, 0, 0, GFP_KERNEL); -+ GEM_BUG_ON(!ret); -+ if (ret < 0) -+ goto err_unlock; -+ -+ ppgtt->user_handle = ret; -+ i915_ppgtt_get(ppgtt); -+ } -+ -+ args->size = 0; -+ args->value = ppgtt->user_handle; -+ -+ ret = 0; -+err_unlock: -+ mutex_unlock(&file_priv->vm_idr_lock); -+err_put: -+ i915_ppgtt_put(ppgtt); -+ return ret; -+} -+ -+static void set_ppgtt_barrier(void *data) -+{ -+ struct i915_hw_ppgtt *old = data; -+ -+ if (INTEL_GEN(old->vm.i915) < 8) -+ gen6_ppgtt_unpin_all(old); -+ -+ i915_ppgtt_put(old); -+} -+ -+static int emit_ppgtt_update(struct i915_request *rq, void *data) -+{ -+ struct i915_hw_ppgtt *ppgtt = rq->gem_context->ppgtt; -+ struct intel_engine_cs *engine = rq->engine; -+ u32 base = engine->mmio_base; -+ u32 *cs; -+ int i; -+ -+ if (i915_vm_is_4lvl(&ppgtt->vm)) { -+ const dma_addr_t pd_daddr = px_dma(&ppgtt->pml4); -+ -+ cs = intel_ring_begin(rq, 6); -+ if (IS_ERR(cs)) -+ return PTR_ERR(cs); -+ -+ *cs++ = MI_LOAD_REGISTER_IMM(2); -+ -+ *cs++ = i915_mmio_reg_offset(GEN8_RING_PDP_UDW(base, 0)); -+ *cs++ = upper_32_bits(pd_daddr); -+ *cs++ = i915_mmio_reg_offset(GEN8_RING_PDP_LDW(base, 0)); -+ *cs++ = lower_32_bits(pd_daddr); -+ -+ *cs++ = MI_NOOP; -+ intel_ring_advance(rq, cs); -+ } else if (HAS_LOGICAL_RING_CONTEXTS(engine->i915)) { -+ cs = intel_ring_begin(rq, 4 * GEN8_3LVL_PDPES + 2); -+ if (IS_ERR(cs)) -+ return PTR_ERR(cs); -+ -+ *cs++ = MI_LOAD_REGISTER_IMM(2 * GEN8_3LVL_PDPES); -+ for (i = GEN8_3LVL_PDPES; i--; ) { -+ const dma_addr_t pd_daddr = i915_page_dir_dma_addr(ppgtt, i); -+ -+ *cs++ = i915_mmio_reg_offset(GEN8_RING_PDP_UDW(base, i)); -+ *cs++ = upper_32_bits(pd_daddr); -+ *cs++ = i915_mmio_reg_offset(GEN8_RING_PDP_LDW(base, i)); -+ *cs++ = lower_32_bits(pd_daddr); -+ } -+ *cs++ = MI_NOOP; -+ intel_ring_advance(rq, cs); -+ } else { -+ /* ppGTT is not part of the legacy context image */ -+ gen6_ppgtt_pin(ppgtt); -+ } -+ -+ return 0; -+} -+ -+static int set_ppgtt(struct drm_i915_file_private *file_priv, -+ struct i915_gem_context *ctx, -+ struct drm_i915_gem_context_param *args) -+{ -+ struct i915_hw_ppgtt *ppgtt, *old; -+ int err; -+ -+ return -EINVAL; /* nothing to see here; please move along */ -+ -+ if (args->size) -+ return -EINVAL; -+ -+ if (!ctx->ppgtt) -+ return -ENODEV; -+ -+ if (upper_32_bits(args->value)) -+ return -ENOENT; -+ -+ err = mutex_lock_interruptible(&file_priv->vm_idr_lock); -+ if (err) -+ return err; -+ -+ ppgtt = idr_find(&file_priv->vm_idr, args->value); -+ if (ppgtt) { -+ GEM_BUG_ON(ppgtt->user_handle != args->value); -+ i915_ppgtt_get(ppgtt); -+ } -+ mutex_unlock(&file_priv->vm_idr_lock); -+ if (!ppgtt) -+ return -ENOENT; -+ -+ err = mutex_lock_interruptible(&ctx->i915->drm.struct_mutex); -+ if (err) -+ goto out; -+ -+ if (ppgtt == ctx->ppgtt) -+ goto unlock; -+ -+ /* Teardown the existing obj:vma cache, it will have to be rebuilt. */ -+ lut_close(ctx); -+ -+ old = __set_ppgtt(ctx, ppgtt); -+ -+ /* -+ * We need to flush any requests using the current ppgtt before -+ * we release it as the requests do not hold a reference themselves, -+ * only indirectly through the context. -+ */ -+ err = context_barrier_task(ctx, ALL_ENGINES, -+ emit_ppgtt_update, -+ set_ppgtt_barrier, -+ old); -+ if (err) { -+ ctx->ppgtt = old; -+ ctx->desc_template = default_desc_template(ctx->i915, old); -+ i915_ppgtt_put(ppgtt); -+ } -+ -+unlock: -+ mutex_unlock(&ctx->i915->drm.struct_mutex); -+ -+out: -+ i915_ppgtt_put(ppgtt); -+ return err; -+} -+ -+static int gen8_emit_rpcs_config(struct i915_request *rq, -+ struct intel_context *ce, -+ struct intel_sseu sseu) -+{ -+ u64 offset; -+ u32 *cs; -+ -+ cs = intel_ring_begin(rq, 4); -+ if (IS_ERR(cs)) -+ return PTR_ERR(cs); -+ -+ offset = i915_ggtt_offset(ce->state) + -+ LRC_STATE_PN * PAGE_SIZE + -+ (CTX_R_PWR_CLK_STATE + 1) * 4; -+ -+ *cs++ = MI_STORE_DWORD_IMM_GEN4 | MI_USE_GGTT; -+ *cs++ = lower_32_bits(offset); -+ *cs++ = upper_32_bits(offset); -+ *cs++ = gen8_make_rpcs(rq->i915, &sseu); -+ -+ intel_ring_advance(rq, cs); -+ -+ return 0; -+} -+ -+static int -+gen8_modify_rpcs(struct intel_context *ce, struct intel_sseu sseu) -+{ -+ struct drm_i915_private *i915 = ce->engine->i915; -+ struct i915_request *rq; -+ intel_wakeref_t wakeref; -+ int ret; -+ -+ lockdep_assert_held(&ce->pin_mutex); -+ -+ /* -+ * If the context is not idle, we have to submit an ordered request to -+ * modify its context image via the kernel context (writing to our own -+ * image, or into the registers directory, does not stick). Pristine -+ * and idle contexts will be configured on pinning. -+ */ -+ if (!intel_context_is_pinned(ce)) -+ return 0; -+ -+ /* Submitting requests etc needs the hw awake. */ -+ wakeref = intel_runtime_pm_get(i915); -+ -+ rq = i915_request_alloc(ce->engine, i915->kernel_context); -+ if (IS_ERR(rq)) { -+ ret = PTR_ERR(rq); -+ goto out_put; -+ } -+ -+ /* Queue this switch after all other activity by this context. */ -+ ret = i915_active_request_set(&ce->ring->timeline->last_request, rq); -+ if (ret) -+ goto out_add; -+ -+ ret = gen8_emit_rpcs_config(rq, ce, sseu); -+ if (ret) -+ goto out_add; -+ -+ /* -+ * Guarantee context image and the timeline remains pinned until the -+ * modifying request is retired by setting the ce activity tracker. -+ * -+ * But we only need to take one pin on the account of it. Or in other -+ * words transfer the pinned ce object to tracked active request. -+ */ -+ if (!i915_active_request_isset(&ce->active_tracker)) -+ __intel_context_pin(ce); -+ __i915_active_request_set(&ce->active_tracker, rq); -+ -+out_add: -+ i915_request_add(rq); -+out_put: -+ intel_runtime_pm_put(i915, wakeref); -+ -+ return ret; -+} -+ -+static int -+__i915_gem_context_reconfigure_sseu(struct i915_gem_context *ctx, -+ struct intel_engine_cs *engine, -+ struct intel_sseu sseu) -+{ -+ struct intel_context *ce; -+ int ret = 0; -+ -+ GEM_BUG_ON(INTEL_GEN(ctx->i915) < 8); -+ GEM_BUG_ON(engine->id != RCS0); -+ -+ ce = intel_context_pin_lock(ctx, engine); -+ if (IS_ERR(ce)) -+ return PTR_ERR(ce); -+ -+ /* Nothing to do if unmodified. */ -+ if (!memcmp(&ce->sseu, &sseu, sizeof(sseu))) -+ goto unlock; -+ -+ ret = gen8_modify_rpcs(ce, sseu); -+ if (!ret) -+ ce->sseu = sseu; -+ -+unlock: -+ intel_context_pin_unlock(ce); -+ return ret; -+} -+ -+static int -+i915_gem_context_reconfigure_sseu(struct i915_gem_context *ctx, -+ struct intel_engine_cs *engine, -+ struct intel_sseu sseu) -+{ -+ int ret; -+ -+ ret = mutex_lock_interruptible(&ctx->i915->drm.struct_mutex); -+ if (ret) -+ return ret; -+ -+ ret = __i915_gem_context_reconfigure_sseu(ctx, engine, sseu); -+ -+ mutex_unlock(&ctx->i915->drm.struct_mutex); -+ -+ return ret; -+} -+ -+static int -+user_to_context_sseu(struct drm_i915_private *i915, -+ const struct drm_i915_gem_context_param_sseu *user, -+ struct intel_sseu *context) -+{ -+ const struct sseu_dev_info *device = &RUNTIME_INFO(i915)->sseu; -+ -+ /* No zeros in any field. */ -+ if (!user->slice_mask || !user->subslice_mask || -+ !user->min_eus_per_subslice || !user->max_eus_per_subslice) -+ return -EINVAL; -+ -+ /* Max > min. */ -+ if (user->max_eus_per_subslice < user->min_eus_per_subslice) -+ return -EINVAL; -+ -+ /* -+ * Some future proofing on the types since the uAPI is wider than the -+ * current internal implementation. -+ */ -+ if (overflows_type(user->slice_mask, context->slice_mask) || -+ overflows_type(user->subslice_mask, context->subslice_mask) || -+ overflows_type(user->min_eus_per_subslice, -+ context->min_eus_per_subslice) || -+ overflows_type(user->max_eus_per_subslice, -+ context->max_eus_per_subslice)) -+ return -EINVAL; -+ -+ /* Check validity against hardware. */ -+ if (user->slice_mask & ~device->slice_mask) -+ return -EINVAL; -+ -+ if (user->subslice_mask & ~device->subslice_mask[0]) -+ return -EINVAL; -+ -+ if (user->max_eus_per_subslice > device->max_eus_per_subslice) -+ return -EINVAL; -+ -+ context->slice_mask = user->slice_mask; -+ context->subslice_mask = user->subslice_mask; -+ context->min_eus_per_subslice = user->min_eus_per_subslice; -+ context->max_eus_per_subslice = user->max_eus_per_subslice; -+ -+ /* Part specific restrictions. */ -+ if (IS_GEN(i915, 11)) { -+ unsigned int hw_s = hweight8(device->slice_mask); -+ unsigned int hw_ss_per_s = hweight8(device->subslice_mask[0]); -+ unsigned int req_s = hweight8(context->slice_mask); -+ unsigned int req_ss = hweight8(context->subslice_mask); -+ -+ /* -+ * Only full subslice enablement is possible if more than one -+ * slice is turned on. -+ */ -+ if (req_s > 1 && req_ss != hw_ss_per_s) -+ return -EINVAL; -+ -+ /* -+ * If more than four (SScount bitfield limit) subslices are -+ * requested then the number has to be even. -+ */ -+ if (req_ss > 4 && (req_ss & 1)) -+ return -EINVAL; -+ -+ /* -+ * If only one slice is enabled and subslice count is below the -+ * device full enablement, it must be at most half of the all -+ * available subslices. -+ */ -+ if (req_s == 1 && req_ss < hw_ss_per_s && -+ req_ss > (hw_ss_per_s / 2)) -+ return -EINVAL; -+ -+ /* ABI restriction - VME use case only. */ -+ -+ /* All slices or one slice only. */ -+ if (req_s != 1 && req_s != hw_s) -+ return -EINVAL; -+ -+ /* -+ * Half subslices or full enablement only when one slice is -+ * enabled. -+ */ -+ if (req_s == 1 && -+ (req_ss != hw_ss_per_s && req_ss != (hw_ss_per_s / 2))) -+ return -EINVAL; -+ -+ /* No EU configuration changes. */ -+ if ((user->min_eus_per_subslice != -+ device->max_eus_per_subslice) || -+ (user->max_eus_per_subslice != -+ device->max_eus_per_subslice)) -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int set_sseu(struct i915_gem_context *ctx, -+ struct drm_i915_gem_context_param *args) -+{ -+ struct drm_i915_private *i915 = ctx->i915; -+ struct drm_i915_gem_context_param_sseu user_sseu; -+ struct intel_engine_cs *engine; -+ struct intel_sseu sseu; -+ int ret; -+ -+ if (args->size < sizeof(user_sseu)) -+ return -EINVAL; -+ -+ if (!IS_GEN(i915, 11)) -+ return -ENODEV; -+ -+ if (copy_from_user(&user_sseu, u64_to_user_ptr(args->value), -+ sizeof(user_sseu))) -+ return -EFAULT; -+ -+ if (user_sseu.flags || user_sseu.rsvd) -+ return -EINVAL; -+ -+ engine = intel_engine_lookup_user(i915, -+ user_sseu.engine.engine_class, -+ user_sseu.engine.engine_instance); -+ if (!engine) -+ return -EINVAL; -+ -+ /* Only render engine supports RPCS configuration. */ -+ if (engine->class != RENDER_CLASS) -+ return -ENODEV; -+ -+ ret = user_to_context_sseu(i915, &user_sseu, &sseu); -+ if (ret) -+ return ret; -+ -+ ret = i915_gem_context_reconfigure_sseu(ctx, engine, sseu); -+ if (ret) -+ return ret; -+ -+ args->size = sizeof(user_sseu); -+ -+ return 0; -+} -+ -+static int ctx_setparam(struct drm_i915_file_private *fpriv, -+ struct i915_gem_context *ctx, -+ struct drm_i915_gem_context_param *args) -+{ -+ int ret = 0; -+ -+ switch (args->param) { -+ case I915_CONTEXT_PARAM_NO_ZEROMAP: -+ if (args->size) -+ ret = -EINVAL; -+ else if (args->value) -+ set_bit(UCONTEXT_NO_ZEROMAP, &ctx->user_flags); -+ else -+ clear_bit(UCONTEXT_NO_ZEROMAP, &ctx->user_flags); -+ break; -+ -+ case I915_CONTEXT_PARAM_NO_ERROR_CAPTURE: -+ if (args->size) -+ ret = -EINVAL; -+ else if (args->value) -+ i915_gem_context_set_no_error_capture(ctx); -+ else -+ i915_gem_context_clear_no_error_capture(ctx); -+ break; -+ -+ case I915_CONTEXT_PARAM_BANNABLE: -+ if (args->size) -+ ret = -EINVAL; -+ else if (!capable(CAP_SYS_ADMIN) && !args->value) -+ ret = -EPERM; -+ else if (args->value) -+ i915_gem_context_set_bannable(ctx); -+ else -+ i915_gem_context_clear_bannable(ctx); -+ break; -+ -+ case I915_CONTEXT_PARAM_RECOVERABLE: -+ if (args->size) -+ ret = -EINVAL; -+ else if (args->value) -+ i915_gem_context_set_recoverable(ctx); -+ else -+ i915_gem_context_clear_recoverable(ctx); -+ break; -+ -+ case I915_CONTEXT_PARAM_PRIORITY: -+ { -+ s64 priority = args->value; -+ -+ if (args->size) -+ ret = -EINVAL; -+ else if (!(ctx->i915->caps.scheduler & I915_SCHEDULER_CAP_PRIORITY)) -+ ret = -ENODEV; -+ else if (priority > I915_CONTEXT_MAX_USER_PRIORITY || -+ priority < I915_CONTEXT_MIN_USER_PRIORITY) -+ ret = -EINVAL; -+ else if (priority > I915_CONTEXT_DEFAULT_PRIORITY && -+ !capable(CAP_SYS_NICE)) -+ ret = -EPERM; -+ else -+ ctx->sched.priority = -+ I915_USER_PRIORITY(priority); -+ } -+ break; -+ -+ case I915_CONTEXT_PARAM_SSEU: -+ ret = set_sseu(ctx, args); -+ break; -+ -+ case I915_CONTEXT_PARAM_VM: -+ ret = set_ppgtt(fpriv, ctx, args); -+ break; -+ -+ case I915_CONTEXT_PARAM_BAN_PERIOD: -+ default: -+ ret = -EINVAL; -+ break; -+ } -+ -+ return ret; -+} -+ -+struct create_ext { -+ struct i915_gem_context *ctx; -+ struct drm_i915_file_private *fpriv; -+}; -+ -+static int create_setparam(struct i915_user_extension __user *ext, void *data) -+{ -+ struct drm_i915_gem_context_create_ext_setparam local; -+ const struct create_ext *arg = data; -+ -+ if (copy_from_user(&local, ext, sizeof(local))) -+ return -EFAULT; -+ -+ if (local.param.ctx_id) -+ return -EINVAL; -+ -+ return ctx_setparam(arg->fpriv, arg->ctx, &local.param); -+} -+ -+static const i915_user_extension_fn create_extensions[] = { -+ [I915_CONTEXT_CREATE_EXT_SETPARAM] = create_setparam, -+}; -+ -+static bool client_is_banned(struct drm_i915_file_private *file_priv) -+{ -+ return atomic_read(&file_priv->ban_score) >= I915_CLIENT_SCORE_BANNED; -+} -+ -+int i915_gem_context_create_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_private *i915 = to_i915(dev); -+ struct drm_i915_gem_context_create_ext *args = data; -+ struct create_ext ext_data; -+ int ret; -+ -+ if (!DRIVER_CAPS(i915)->has_logical_contexts) -+ return -ENODEV; -+ -+ if (args->flags & I915_CONTEXT_CREATE_FLAGS_UNKNOWN) -+ return -EINVAL; -+ -+ ret = i915_terminally_wedged(i915); -+ if (ret) -+ return ret; -+ -+ ext_data.fpriv = file->driver_priv; -+ if (client_is_banned(ext_data.fpriv)) { -+ DRM_DEBUG("client %s[%d] banned from creating ctx\n", -+ current->comm, -+ pid_nr(get_task_pid(current, PIDTYPE_PID))); -+ return -EIO; -+ } -+ -+ ret = i915_mutex_lock_interruptible(dev); -+ if (ret) -+ return ret; -+ -+ ext_data.ctx = i915_gem_create_context(i915, args->flags); -+ mutex_unlock(&dev->struct_mutex); -+ if (IS_ERR(ext_data.ctx)) -+ return PTR_ERR(ext_data.ctx); -+ -+ if (args->flags & I915_CONTEXT_CREATE_FLAGS_USE_EXTENSIONS) { -+ ret = i915_user_extensions(u64_to_user_ptr(args->extensions), -+ create_extensions, -+ ARRAY_SIZE(create_extensions), -+ &ext_data); -+ if (ret) -+ goto err_ctx; -+ } -+ -+ ret = gem_context_register(ext_data.ctx, ext_data.fpriv); -+ if (ret < 0) -+ goto err_ctx; -+ -+ args->ctx_id = ret; -+ DRM_DEBUG("HW context %d created\n", args->ctx_id); -+ -+ return 0; -+ -+err_ctx: -+ mutex_lock(&dev->struct_mutex); -+ context_close(ext_data.ctx); -+ mutex_unlock(&dev->struct_mutex); -+ return ret; -+} -+ -+int i915_gem_context_destroy_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_gem_context_destroy *args = data; -+ struct drm_i915_file_private *file_priv = file->driver_priv; -+ struct i915_gem_context *ctx; -+ -+ if (args->pad != 0) -+ return -EINVAL; -+ -+ if (!args->ctx_id) -+ return -ENOENT; -+ -+ if (mutex_lock_interruptible(&file_priv->context_idr_lock)) -+ return -EINTR; -+ -+ ctx = idr_remove(&file_priv->context_idr, args->ctx_id); -+ mutex_unlock(&file_priv->context_idr_lock); -+ if (!ctx) -+ return -ENOENT; -+ -+ mutex_lock(&dev->struct_mutex); -+ context_close(ctx); -+ mutex_unlock(&dev->struct_mutex); -+ -+ return 0; -+} -+ -+static int get_sseu(struct i915_gem_context *ctx, -+ struct drm_i915_gem_context_param *args) -+{ -+ struct drm_i915_gem_context_param_sseu user_sseu; -+ struct intel_engine_cs *engine; -+ struct intel_context *ce; -+ -+ if (args->size == 0) -+ goto out; -+ else if (args->size < sizeof(user_sseu)) -+ return -EINVAL; -+ -+ if (copy_from_user(&user_sseu, u64_to_user_ptr(args->value), -+ sizeof(user_sseu))) -+ return -EFAULT; -+ -+ if (user_sseu.flags || user_sseu.rsvd) -+ return -EINVAL; -+ -+ engine = intel_engine_lookup_user(ctx->i915, -+ user_sseu.engine.engine_class, -+ user_sseu.engine.engine_instance); -+ if (!engine) -+ return -EINVAL; -+ -+ ce = intel_context_pin_lock(ctx, engine); /* serialises with set_sseu */ -+ if (IS_ERR(ce)) -+ return PTR_ERR(ce); -+ -+ user_sseu.slice_mask = ce->sseu.slice_mask; -+ user_sseu.subslice_mask = ce->sseu.subslice_mask; -+ user_sseu.min_eus_per_subslice = ce->sseu.min_eus_per_subslice; -+ user_sseu.max_eus_per_subslice = ce->sseu.max_eus_per_subslice; -+ -+ intel_context_pin_unlock(ce); -+ -+ if (copy_to_user(u64_to_user_ptr(args->value), &user_sseu, -+ sizeof(user_sseu))) -+ return -EFAULT; -+ -+out: -+ args->size = sizeof(user_sseu); -+ -+ return 0; -+} -+ -+int i915_gem_context_getparam_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_file_private *file_priv = file->driver_priv; -+ struct drm_i915_gem_context_param *args = data; -+ struct i915_gem_context *ctx; -+ int ret = 0; -+ -+ ctx = i915_gem_context_lookup(file_priv, args->ctx_id); -+ if (!ctx) -+ return -ENOENT; -+ -+ switch (args->param) { -+ case I915_CONTEXT_PARAM_NO_ZEROMAP: -+ args->size = 0; -+ args->value = test_bit(UCONTEXT_NO_ZEROMAP, &ctx->user_flags); -+ break; -+ -+ case I915_CONTEXT_PARAM_GTT_SIZE: -+ args->size = 0; -+ if (ctx->ppgtt) -+ args->value = ctx->ppgtt->vm.total; -+ else if (to_i915(dev)->mm.aliasing_ppgtt) -+ args->value = to_i915(dev)->mm.aliasing_ppgtt->vm.total; -+ else -+ args->value = to_i915(dev)->ggtt.vm.total; -+ break; -+ -+ case I915_CONTEXT_PARAM_NO_ERROR_CAPTURE: -+ args->size = 0; -+ args->value = i915_gem_context_no_error_capture(ctx); -+ break; -+ -+ case I915_CONTEXT_PARAM_BANNABLE: -+ args->size = 0; -+ args->value = i915_gem_context_is_bannable(ctx); -+ break; -+ -+ case I915_CONTEXT_PARAM_RECOVERABLE: -+ args->size = 0; -+ args->value = i915_gem_context_is_recoverable(ctx); -+ break; -+ -+ case I915_CONTEXT_PARAM_PRIORITY: -+ args->size = 0; -+ args->value = ctx->sched.priority >> I915_USER_PRIORITY_SHIFT; -+ break; -+ -+ case I915_CONTEXT_PARAM_SSEU: -+ ret = get_sseu(ctx, args); -+ break; -+ -+ case I915_CONTEXT_PARAM_VM: -+ ret = get_ppgtt(file_priv, ctx, args); -+ break; -+ -+ case I915_CONTEXT_PARAM_BAN_PERIOD: -+ default: -+ ret = -EINVAL; -+ break; -+ } -+ -+ i915_gem_context_put(ctx); -+ return ret; -+} -+ -+int i915_gem_context_setparam_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_file_private *file_priv = file->driver_priv; -+ struct drm_i915_gem_context_param *args = data; -+ struct i915_gem_context *ctx; -+ int ret; -+ -+ ctx = i915_gem_context_lookup(file_priv, args->ctx_id); -+ if (!ctx) -+ return -ENOENT; -+ -+ ret = ctx_setparam(file_priv, ctx, args); -+ -+ i915_gem_context_put(ctx); -+ return ret; -+} -+ -+int i915_gem_context_reset_stats_ioctl(struct drm_device *dev, -+ void *data, struct drm_file *file) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_i915_reset_stats *args = data; -+ struct i915_gem_context *ctx; -+ int ret; -+ -+ if (args->flags || args->pad) -+ return -EINVAL; -+ -+ ret = -ENOENT; -+ rcu_read_lock(); -+ ctx = __i915_gem_context_lookup_rcu(file->driver_priv, args->ctx_id); -+ if (!ctx) -+ goto out; -+ -+ /* -+ * We opt for unserialised reads here. This may result in tearing -+ * in the extremely unlikely event of a GPU hang on this context -+ * as we are querying them. If we need that extra layer of protection, -+ * we should wrap the hangstats with a seqlock. -+ */ -+ -+ if (capable(CAP_SYS_ADMIN)) -+ args->reset_count = i915_reset_count(&dev_priv->gpu_error); -+ else -+ args->reset_count = 0; -+ -+ args->batch_active = atomic_read(&ctx->guilty_count); -+ args->batch_pending = atomic_read(&ctx->active_count); -+ -+ ret = 0; -+out: -+ rcu_read_unlock(); -+ return ret; -+} -+ -+int __i915_gem_context_pin_hw_id(struct i915_gem_context *ctx) -+{ -+ struct drm_i915_private *i915 = ctx->i915; -+ int err = 0; -+ -+ mutex_lock(&i915->contexts.mutex); -+ -+ GEM_BUG_ON(i915_gem_context_is_closed(ctx)); -+ -+ if (list_empty(&ctx->hw_id_link)) { -+ GEM_BUG_ON(atomic_read(&ctx->hw_id_pin_count)); -+ -+ err = assign_hw_id(i915, &ctx->hw_id); -+ if (err) -+ goto out_unlock; -+ -+ list_add_tail(&ctx->hw_id_link, &i915->contexts.hw_id_list); -+ } -+ -+ GEM_BUG_ON(atomic_read(&ctx->hw_id_pin_count) == ~0u); -+ atomic_inc(&ctx->hw_id_pin_count); -+ -+out_unlock: -+ mutex_unlock(&i915->contexts.mutex); -+ return err; -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -+#include "selftests/mock_context.c" -+#include "selftests/i915_gem_context.c" -+#endif -+ -+static void i915_global_gem_context_shrink(void) -+{ -+ kmem_cache_shrink(global.slab_luts); -+} -+ -+static void i915_global_gem_context_exit(void) -+{ -+ kmem_cache_destroy(global.slab_luts); -+} -+ -+static struct i915_global_gem_context global = { { -+ .shrink = i915_global_gem_context_shrink, -+ .exit = i915_global_gem_context_exit, -+} }; -+ -+int __init i915_global_gem_context_init(void) -+{ -+ global.slab_luts = KMEM_CACHE(i915_lut_handle, 0); -+ if (!global.slab_luts) -+ return -ENOMEM; -+ -+ i915_global_register(&global.base); -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_context.h b/drivers/gpu/drm/i915_legacy/i915_gem_context.h -new file mode 100644 -index 000000000000..23dcb01bfd82 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_context.h -@@ -0,0 +1,185 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef __I915_GEM_CONTEXT_H__ -+#define __I915_GEM_CONTEXT_H__ -+ -+#include "i915_gem_context_types.h" -+ -+#include "i915_gem.h" -+#include "i915_scheduler.h" -+#include "intel_context.h" -+#include "intel_device_info.h" -+ -+struct drm_device; -+struct drm_file; -+ -+static inline bool i915_gem_context_is_closed(const struct i915_gem_context *ctx) -+{ -+ return test_bit(CONTEXT_CLOSED, &ctx->flags); -+} -+ -+static inline void i915_gem_context_set_closed(struct i915_gem_context *ctx) -+{ -+ GEM_BUG_ON(i915_gem_context_is_closed(ctx)); -+ set_bit(CONTEXT_CLOSED, &ctx->flags); -+} -+ -+static inline bool i915_gem_context_no_error_capture(const struct i915_gem_context *ctx) -+{ -+ return test_bit(UCONTEXT_NO_ERROR_CAPTURE, &ctx->user_flags); -+} -+ -+static inline void i915_gem_context_set_no_error_capture(struct i915_gem_context *ctx) -+{ -+ set_bit(UCONTEXT_NO_ERROR_CAPTURE, &ctx->user_flags); -+} -+ -+static inline void i915_gem_context_clear_no_error_capture(struct i915_gem_context *ctx) -+{ -+ clear_bit(UCONTEXT_NO_ERROR_CAPTURE, &ctx->user_flags); -+} -+ -+static inline bool i915_gem_context_is_bannable(const struct i915_gem_context *ctx) -+{ -+ return test_bit(UCONTEXT_BANNABLE, &ctx->user_flags); -+} -+ -+static inline void i915_gem_context_set_bannable(struct i915_gem_context *ctx) -+{ -+ set_bit(UCONTEXT_BANNABLE, &ctx->user_flags); -+} -+ -+static inline void i915_gem_context_clear_bannable(struct i915_gem_context *ctx) -+{ -+ clear_bit(UCONTEXT_BANNABLE, &ctx->user_flags); -+} -+ -+static inline bool i915_gem_context_is_recoverable(const struct i915_gem_context *ctx) -+{ -+ return test_bit(UCONTEXT_RECOVERABLE, &ctx->user_flags); -+} -+ -+static inline void i915_gem_context_set_recoverable(struct i915_gem_context *ctx) -+{ -+ set_bit(UCONTEXT_RECOVERABLE, &ctx->user_flags); -+} -+ -+static inline void i915_gem_context_clear_recoverable(struct i915_gem_context *ctx) -+{ -+ clear_bit(UCONTEXT_RECOVERABLE, &ctx->user_flags); -+} -+ -+static inline bool i915_gem_context_is_banned(const struct i915_gem_context *ctx) -+{ -+ return test_bit(CONTEXT_BANNED, &ctx->flags); -+} -+ -+static inline void i915_gem_context_set_banned(struct i915_gem_context *ctx) -+{ -+ set_bit(CONTEXT_BANNED, &ctx->flags); -+} -+ -+static inline bool i915_gem_context_force_single_submission(const struct i915_gem_context *ctx) -+{ -+ return test_bit(CONTEXT_FORCE_SINGLE_SUBMISSION, &ctx->flags); -+} -+ -+static inline void i915_gem_context_set_force_single_submission(struct i915_gem_context *ctx) -+{ -+ __set_bit(CONTEXT_FORCE_SINGLE_SUBMISSION, &ctx->flags); -+} -+ -+int __i915_gem_context_pin_hw_id(struct i915_gem_context *ctx); -+static inline int i915_gem_context_pin_hw_id(struct i915_gem_context *ctx) -+{ -+ if (atomic_inc_not_zero(&ctx->hw_id_pin_count)) -+ return 0; -+ -+ return __i915_gem_context_pin_hw_id(ctx); -+} -+ -+static inline void i915_gem_context_unpin_hw_id(struct i915_gem_context *ctx) -+{ -+ GEM_BUG_ON(atomic_read(&ctx->hw_id_pin_count) == 0u); -+ atomic_dec(&ctx->hw_id_pin_count); -+} -+ -+static inline bool i915_gem_context_is_kernel(struct i915_gem_context *ctx) -+{ -+ return !ctx->file_priv; -+} -+ -+/* i915_gem_context.c */ -+int __must_check i915_gem_contexts_init(struct drm_i915_private *dev_priv); -+void i915_gem_contexts_lost(struct drm_i915_private *dev_priv); -+void i915_gem_contexts_fini(struct drm_i915_private *dev_priv); -+ -+int i915_gem_context_open(struct drm_i915_private *i915, -+ struct drm_file *file); -+void i915_gem_context_close(struct drm_file *file); -+ -+int i915_switch_context(struct i915_request *rq); -+int i915_gem_switch_to_kernel_context(struct drm_i915_private *i915, -+ intel_engine_mask_t engine_mask); -+ -+void i915_gem_context_release(struct kref *ctx_ref); -+struct i915_gem_context * -+i915_gem_context_create_gvt(struct drm_device *dev); -+ -+int i915_gem_vm_create_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file); -+int i915_gem_vm_destroy_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file); -+ -+int i915_gem_context_create_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file); -+int i915_gem_context_destroy_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file); -+int i915_gem_context_getparam_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_context_setparam_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int i915_gem_context_reset_stats_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file); -+ -+struct i915_gem_context * -+i915_gem_context_create_kernel(struct drm_i915_private *i915, int prio); -+ -+static inline struct i915_gem_context * -+i915_gem_context_get(struct i915_gem_context *ctx) -+{ -+ kref_get(&ctx->ref); -+ return ctx; -+} -+ -+static inline void i915_gem_context_put(struct i915_gem_context *ctx) -+{ -+ kref_put(&ctx->ref, i915_gem_context_release); -+} -+ -+struct i915_lut_handle *i915_lut_handle_alloc(void); -+void i915_lut_handle_free(struct i915_lut_handle *lut); -+ -+#endif /* !__I915_GEM_CONTEXT_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_context_types.h b/drivers/gpu/drm/i915_legacy/i915_gem_context_types.h -new file mode 100644 -index 000000000000..e2ec58b10fb2 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_context_types.h -@@ -0,0 +1,175 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef __I915_GEM_CONTEXT_TYPES_H__ -+#define __I915_GEM_CONTEXT_TYPES_H__ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "i915_scheduler.h" -+#include "intel_context_types.h" -+ -+struct pid; -+ -+struct drm_i915_private; -+struct drm_i915_file_private; -+struct i915_hw_ppgtt; -+struct i915_timeline; -+struct intel_ring; -+ -+/** -+ * struct i915_gem_context - client state -+ * -+ * The struct i915_gem_context represents the combined view of the driver and -+ * logical hardware state for a particular client. -+ */ -+struct i915_gem_context { -+ /** i915: i915 device backpointer */ -+ struct drm_i915_private *i915; -+ -+ /** file_priv: owning file descriptor */ -+ struct drm_i915_file_private *file_priv; -+ -+ struct i915_timeline *timeline; -+ -+ /** -+ * @ppgtt: unique address space (GTT) -+ * -+ * In full-ppgtt mode, each context has its own address space ensuring -+ * complete seperation of one client from all others. -+ * -+ * In other modes, this is a NULL pointer with the expectation that -+ * the caller uses the shared global GTT. -+ */ -+ struct i915_hw_ppgtt *ppgtt; -+ -+ /** -+ * @pid: process id of creator -+ * -+ * Note that who created the context may not be the principle user, -+ * as the context may be shared across a local socket. However, -+ * that should only affect the default context, all contexts created -+ * explicitly by the client are expected to be isolated. -+ */ -+ struct pid *pid; -+ -+ /** -+ * @name: arbitrary name -+ * -+ * A name is constructed for the context from the creator's process -+ * name, pid and user handle in order to uniquely identify the -+ * context in messages. -+ */ -+ const char *name; -+ -+ /** link: place with &drm_i915_private.context_list */ -+ struct list_head link; -+ struct llist_node free_link; -+ -+ /** -+ * @ref: reference count -+ * -+ * A reference to a context is held by both the client who created it -+ * and on each request submitted to the hardware using the request -+ * (to ensure the hardware has access to the state until it has -+ * finished all pending writes). See i915_gem_context_get() and -+ * i915_gem_context_put() for access. -+ */ -+ struct kref ref; -+ -+ /** -+ * @rcu: rcu_head for deferred freeing. -+ */ -+ struct rcu_head rcu; -+ -+ /** -+ * @user_flags: small set of booleans controlled by the user -+ */ -+ unsigned long user_flags; -+#define UCONTEXT_NO_ZEROMAP 0 -+#define UCONTEXT_NO_ERROR_CAPTURE 1 -+#define UCONTEXT_BANNABLE 2 -+#define UCONTEXT_RECOVERABLE 3 -+ -+ /** -+ * @flags: small set of booleans -+ */ -+ unsigned long flags; -+#define CONTEXT_BANNED 0 -+#define CONTEXT_CLOSED 1 -+#define CONTEXT_FORCE_SINGLE_SUBMISSION 2 -+ -+ /** -+ * @hw_id: - unique identifier for the context -+ * -+ * The hardware needs to uniquely identify the context for a few -+ * functions like fault reporting, PASID, scheduling. The -+ * &drm_i915_private.context_hw_ida is used to assign a unqiue -+ * id for the lifetime of the context. -+ * -+ * @hw_id_pin_count: - number of times this context had been pinned -+ * for use (should be, at most, once per engine). -+ * -+ * @hw_id_link: - all contexts with an assigned id are tracked -+ * for possible repossession. -+ */ -+ unsigned int hw_id; -+ atomic_t hw_id_pin_count; -+ struct list_head hw_id_link; -+ -+ struct list_head active_engines; -+ struct mutex mutex; -+ -+ struct i915_sched_attr sched; -+ -+ /** hw_contexts: per-engine logical HW state */ -+ struct rb_root hw_contexts; -+ spinlock_t hw_contexts_lock; -+ -+ /** ring_size: size for allocating the per-engine ring buffer */ -+ u32 ring_size; -+ /** desc_template: invariant fields for the HW context descriptor */ -+ u32 desc_template; -+ -+ /** guilty_count: How many times this context has caused a GPU hang. */ -+ atomic_t guilty_count; -+ /** -+ * @active_count: How many times this context was active during a GPU -+ * hang, but did not cause it. -+ */ -+ atomic_t active_count; -+ -+ /** -+ * @hang_timestamp: The last time(s) this context caused a GPU hang -+ */ -+ unsigned long hang_timestamp[2]; -+#define CONTEXT_FAST_HANG_JIFFIES (120 * HZ) /* 3 hangs within 120s? Banned! */ -+ -+ /** remap_slice: Bitmask of cache lines that need remapping */ -+ u8 remap_slice; -+ -+ /** handles_vma: rbtree to look up our context specific obj/vma for -+ * the user handle. (user handles are per fd, but the binding is -+ * per vm, which may be one per context or shared with the global GTT) -+ */ -+ struct radix_tree_root handles_vma; -+ -+ /** handles_list: reverse list of all the rbtree entries in use for -+ * this context, which allows us to free all the allocations on -+ * context close. -+ */ -+ struct list_head handles_list; -+}; -+ -+#endif /* __I915_GEM_CONTEXT_TYPES_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_dmabuf.c b/drivers/gpu/drm/i915_legacy/i915_gem_dmabuf.c -new file mode 100644 -index 000000000000..5a101a9462d8 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_dmabuf.c -@@ -0,0 +1,337 @@ -+/* -+ * Copyright 2012 Red Hat Inc -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Dave Airlie -+ */ -+ -+#include -+#include -+ -+ -+#include "i915_drv.h" -+ -+static struct drm_i915_gem_object *dma_buf_to_obj(struct dma_buf *buf) -+{ -+ return to_intel_bo(buf->priv); -+} -+ -+static struct sg_table *i915_gem_map_dma_buf(struct dma_buf_attachment *attachment, -+ enum dma_data_direction dir) -+{ -+ struct drm_i915_gem_object *obj = dma_buf_to_obj(attachment->dmabuf); -+ struct sg_table *st; -+ struct scatterlist *src, *dst; -+ int ret, i; -+ -+ ret = i915_gem_object_pin_pages(obj); -+ if (ret) -+ goto err; -+ -+ /* Copy sg so that we make an independent mapping */ -+ st = kmalloc(sizeof(struct sg_table), GFP_KERNEL); -+ if (st == NULL) { -+ ret = -ENOMEM; -+ goto err_unpin_pages; -+ } -+ -+ ret = sg_alloc_table(st, obj->mm.pages->nents, GFP_KERNEL); -+ if (ret) -+ goto err_free; -+ -+ src = obj->mm.pages->sgl; -+ dst = st->sgl; -+ for (i = 0; i < obj->mm.pages->nents; i++) { -+ sg_set_page(dst, sg_page(src), src->length, 0); -+ dst = sg_next(dst); -+ src = sg_next(src); -+ } -+ -+ if (!dma_map_sg(attachment->dev, st->sgl, st->nents, dir)) { -+ ret = -ENOMEM; -+ goto err_free_sg; -+ } -+ -+ return st; -+ -+err_free_sg: -+ sg_free_table(st); -+err_free: -+ kfree(st); -+err_unpin_pages: -+ i915_gem_object_unpin_pages(obj); -+err: -+ return ERR_PTR(ret); -+} -+ -+static void i915_gem_unmap_dma_buf(struct dma_buf_attachment *attachment, -+ struct sg_table *sg, -+ enum dma_data_direction dir) -+{ -+ struct drm_i915_gem_object *obj = dma_buf_to_obj(attachment->dmabuf); -+ -+ dma_unmap_sg(attachment->dev, sg->sgl, sg->nents, dir); -+ sg_free_table(sg); -+ kfree(sg); -+ -+ i915_gem_object_unpin_pages(obj); -+} -+ -+static void *i915_gem_dmabuf_vmap(struct dma_buf *dma_buf) -+{ -+ struct drm_i915_gem_object *obj = dma_buf_to_obj(dma_buf); -+ -+ return i915_gem_object_pin_map(obj, I915_MAP_WB); -+} -+ -+static void i915_gem_dmabuf_vunmap(struct dma_buf *dma_buf, void *vaddr) -+{ -+ struct drm_i915_gem_object *obj = dma_buf_to_obj(dma_buf); -+ -+ i915_gem_object_flush_map(obj); -+ i915_gem_object_unpin_map(obj); -+} -+ -+static void *i915_gem_dmabuf_kmap(struct dma_buf *dma_buf, unsigned long page_num) -+{ -+ struct drm_i915_gem_object *obj = dma_buf_to_obj(dma_buf); -+ struct page *page; -+ -+ if (page_num >= obj->base.size >> PAGE_SHIFT) -+ return NULL; -+ -+ if (!i915_gem_object_has_struct_page(obj)) -+ return NULL; -+ -+ if (i915_gem_object_pin_pages(obj)) -+ return NULL; -+ -+ /* Synchronisation is left to the caller (via .begin_cpu_access()) */ -+ page = i915_gem_object_get_page(obj, page_num); -+ if (IS_ERR(page)) -+ goto err_unpin; -+ -+ return kmap(page); -+ -+err_unpin: -+ i915_gem_object_unpin_pages(obj); -+ return NULL; -+} -+ -+static void i915_gem_dmabuf_kunmap(struct dma_buf *dma_buf, unsigned long page_num, void *addr) -+{ -+ struct drm_i915_gem_object *obj = dma_buf_to_obj(dma_buf); -+ -+ kunmap(virt_to_page(addr)); -+ i915_gem_object_unpin_pages(obj); -+} -+ -+static int i915_gem_dmabuf_mmap(struct dma_buf *dma_buf, struct vm_area_struct *vma) -+{ -+ struct drm_i915_gem_object *obj = dma_buf_to_obj(dma_buf); -+ int ret; -+ -+ if (obj->base.size < vma->vm_end - vma->vm_start) -+ return -EINVAL; -+ -+ if (!obj->base.filp) -+ return -ENODEV; -+ -+ ret = call_mmap(obj->base.filp, vma); -+ if (ret) -+ return ret; -+ -+ fput(vma->vm_file); -+ vma->vm_file = get_file(obj->base.filp); -+ -+ return 0; -+} -+ -+static int i915_gem_begin_cpu_access(struct dma_buf *dma_buf, enum dma_data_direction direction) -+{ -+ struct drm_i915_gem_object *obj = dma_buf_to_obj(dma_buf); -+ struct drm_device *dev = obj->base.dev; -+ bool write = (direction == DMA_BIDIRECTIONAL || direction == DMA_TO_DEVICE); -+ int err; -+ -+ err = i915_gem_object_pin_pages(obj); -+ if (err) -+ return err; -+ -+ err = i915_mutex_lock_interruptible(dev); -+ if (err) -+ goto out; -+ -+ err = i915_gem_object_set_to_cpu_domain(obj, write); -+ mutex_unlock(&dev->struct_mutex); -+ -+out: -+ i915_gem_object_unpin_pages(obj); -+ return err; -+} -+ -+static int i915_gem_end_cpu_access(struct dma_buf *dma_buf, enum dma_data_direction direction) -+{ -+ struct drm_i915_gem_object *obj = dma_buf_to_obj(dma_buf); -+ struct drm_device *dev = obj->base.dev; -+ int err; -+ -+ err = i915_gem_object_pin_pages(obj); -+ if (err) -+ return err; -+ -+ err = i915_mutex_lock_interruptible(dev); -+ if (err) -+ goto out; -+ -+ err = i915_gem_object_set_to_gtt_domain(obj, false); -+ mutex_unlock(&dev->struct_mutex); -+ -+out: -+ i915_gem_object_unpin_pages(obj); -+ return err; -+} -+ -+static const struct dma_buf_ops i915_dmabuf_ops = { -+ .map_dma_buf = i915_gem_map_dma_buf, -+ .unmap_dma_buf = i915_gem_unmap_dma_buf, -+ .release = drm_gem_dmabuf_release, -+ .map = i915_gem_dmabuf_kmap, -+ .unmap = i915_gem_dmabuf_kunmap, -+ .mmap = i915_gem_dmabuf_mmap, -+ .vmap = i915_gem_dmabuf_vmap, -+ .vunmap = i915_gem_dmabuf_vunmap, -+ .begin_cpu_access = i915_gem_begin_cpu_access, -+ .end_cpu_access = i915_gem_end_cpu_access, -+}; -+ -+struct dma_buf *i915_gem_prime_export(struct drm_device *dev, -+ struct drm_gem_object *gem_obj, int flags) -+{ -+ struct drm_i915_gem_object *obj = to_intel_bo(gem_obj); -+ DEFINE_DMA_BUF_EXPORT_INFO(exp_info); -+ -+ exp_info.ops = &i915_dmabuf_ops; -+ exp_info.size = gem_obj->size; -+ exp_info.flags = flags; -+ exp_info.priv = gem_obj; -+ exp_info.resv = obj->resv; -+ -+ if (obj->ops->dmabuf_export) { -+ int ret = obj->ops->dmabuf_export(obj); -+ if (ret) -+ return ERR_PTR(ret); -+ } -+ -+ return drm_gem_dmabuf_export(dev, &exp_info); -+} -+ -+static int i915_gem_object_get_pages_dmabuf(struct drm_i915_gem_object *obj) -+{ -+ struct sg_table *pages; -+ unsigned int sg_page_sizes; -+ -+ pages = dma_buf_map_attachment(obj->base.import_attach, -+ DMA_BIDIRECTIONAL); -+ if (IS_ERR(pages)) -+ return PTR_ERR(pages); -+ -+ sg_page_sizes = i915_sg_page_sizes(pages->sgl); -+ -+ __i915_gem_object_set_pages(obj, pages, sg_page_sizes); -+ -+ return 0; -+} -+ -+static void i915_gem_object_put_pages_dmabuf(struct drm_i915_gem_object *obj, -+ struct sg_table *pages) -+{ -+ dma_buf_unmap_attachment(obj->base.import_attach, pages, -+ DMA_BIDIRECTIONAL); -+} -+ -+static const struct drm_i915_gem_object_ops i915_gem_object_dmabuf_ops = { -+ .get_pages = i915_gem_object_get_pages_dmabuf, -+ .put_pages = i915_gem_object_put_pages_dmabuf, -+}; -+ -+struct drm_gem_object *i915_gem_prime_import(struct drm_device *dev, -+ struct dma_buf *dma_buf) -+{ -+ struct dma_buf_attachment *attach; -+ struct drm_i915_gem_object *obj; -+ int ret; -+ -+ /* is this one of own objects? */ -+ if (dma_buf->ops == &i915_dmabuf_ops) { -+ obj = dma_buf_to_obj(dma_buf); -+ /* is it from our device? */ -+ if (obj->base.dev == dev) { -+ /* -+ * Importing dmabuf exported from out own gem increases -+ * refcount on gem itself instead of f_count of dmabuf. -+ */ -+ return &i915_gem_object_get(obj)->base; -+ } -+ } -+ -+ /* need to attach */ -+ attach = dma_buf_attach(dma_buf, dev->dev); -+ if (IS_ERR(attach)) -+ return ERR_CAST(attach); -+ -+ get_dma_buf(dma_buf); -+ -+ obj = i915_gem_object_alloc(); -+ if (obj == NULL) { -+ ret = -ENOMEM; -+ goto fail_detach; -+ } -+ -+ drm_gem_private_object_init(dev, &obj->base, dma_buf->size); -+ i915_gem_object_init(obj, &i915_gem_object_dmabuf_ops); -+ obj->base.import_attach = attach; -+ obj->resv = dma_buf->resv; -+ -+ /* We use GTT as shorthand for a coherent domain, one that is -+ * neither in the GPU cache nor in the CPU cache, where all -+ * writes are immediately visible in memory. (That's not strictly -+ * true, but it's close! There are internal buffers such as the -+ * write-combined buffer or a delay through the chipset for GTT -+ * writes that do require us to treat GTT as a separate cache domain.) -+ */ -+ obj->read_domains = I915_GEM_DOMAIN_GTT; -+ obj->write_domain = 0; -+ -+ return &obj->base; -+ -+fail_detach: -+ dma_buf_detach(dma_buf, attach); -+ dma_buf_put(dma_buf); -+ -+ return ERR_PTR(ret); -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -+#include "selftests/mock_dmabuf.c" -+#include "selftests/i915_gem_dmabuf.c" -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_evict.c b/drivers/gpu/drm/i915_legacy/i915_gem_evict.c -new file mode 100644 -index 000000000000..060f5903544a ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_evict.c -@@ -0,0 +1,444 @@ -+/* -+ * Copyright © 2008-2010 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eric Anholt -+ * Chris Wilson -+ * -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "intel_drv.h" -+#include "i915_trace.h" -+ -+I915_SELFTEST_DECLARE(static struct igt_evict_ctl { -+ bool fail_if_busy:1; -+} igt_evict_ctl;) -+ -+static bool ggtt_is_idle(struct drm_i915_private *i915) -+{ -+ return !i915->gt.active_requests; -+} -+ -+static int ggtt_flush(struct drm_i915_private *i915) -+{ -+ int err; -+ -+ /* -+ * Not everything in the GGTT is tracked via vma (otherwise we -+ * could evict as required with minimal stalling) so we are forced -+ * to idle the GPU and explicitly retire outstanding requests in -+ * the hopes that we can then remove contexts and the like only -+ * bound by their active reference. -+ */ -+ err = i915_gem_switch_to_kernel_context(i915, i915->gt.active_engines); -+ if (err) -+ return err; -+ -+ err = i915_gem_wait_for_idle(i915, -+ I915_WAIT_INTERRUPTIBLE | -+ I915_WAIT_LOCKED, -+ MAX_SCHEDULE_TIMEOUT); -+ if (err) -+ return err; -+ -+ GEM_BUG_ON(!ggtt_is_idle(i915)); -+ return 0; -+} -+ -+static bool -+mark_free(struct drm_mm_scan *scan, -+ struct i915_vma *vma, -+ unsigned int flags, -+ struct list_head *unwind) -+{ -+ if (i915_vma_is_pinned(vma)) -+ return false; -+ -+ if (flags & PIN_NONFAULT && i915_vma_has_userfault(vma)) -+ return false; -+ -+ list_add(&vma->evict_link, unwind); -+ return drm_mm_scan_add_block(scan, &vma->node); -+} -+ -+/** -+ * i915_gem_evict_something - Evict vmas to make room for binding a new one -+ * @vm: address space to evict from -+ * @min_size: size of the desired free space -+ * @alignment: alignment constraint of the desired free space -+ * @cache_level: cache_level for the desired space -+ * @start: start (inclusive) of the range from which to evict objects -+ * @end: end (exclusive) of the range from which to evict objects -+ * @flags: additional flags to control the eviction algorithm -+ * -+ * This function will try to evict vmas until a free space satisfying the -+ * requirements is found. Callers must check first whether any such hole exists -+ * already before calling this function. -+ * -+ * This function is used by the object/vma binding code. -+ * -+ * Since this function is only used to free up virtual address space it only -+ * ignores pinned vmas, and not object where the backing storage itself is -+ * pinned. Hence obj->pages_pin_count does not protect against eviction. -+ * -+ * To clarify: This is for freeing up virtual address space, not for freeing -+ * memory in e.g. the shrinker. -+ */ -+int -+i915_gem_evict_something(struct i915_address_space *vm, -+ u64 min_size, u64 alignment, -+ unsigned cache_level, -+ u64 start, u64 end, -+ unsigned flags) -+{ -+ struct drm_i915_private *dev_priv = vm->i915; -+ struct drm_mm_scan scan; -+ struct list_head eviction_list; -+ struct i915_vma *vma, *next; -+ struct drm_mm_node *node; -+ enum drm_mm_insert_mode mode; -+ struct i915_vma *active; -+ int ret; -+ -+ lockdep_assert_held(&vm->i915->drm.struct_mutex); -+ trace_i915_gem_evict(vm, min_size, alignment, flags); -+ -+ /* -+ * The goal is to evict objects and amalgamate space in rough LRU order. -+ * Since both active and inactive objects reside on the same list, -+ * in a mix of creation and last scanned order, as we process the list -+ * we sort it into inactive/active, which keeps the active portion -+ * in a rough MRU order. -+ * -+ * The retirement sequence is thus: -+ * 1. Inactive objects (already retired, random order) -+ * 2. Active objects (will stall on unbinding, oldest scanned first) -+ */ -+ mode = DRM_MM_INSERT_BEST; -+ if (flags & PIN_HIGH) -+ mode = DRM_MM_INSERT_HIGH; -+ if (flags & PIN_MAPPABLE) -+ mode = DRM_MM_INSERT_LOW; -+ drm_mm_scan_init_with_range(&scan, &vm->mm, -+ min_size, alignment, cache_level, -+ start, end, mode); -+ -+ /* -+ * Retire before we search the active list. Although we have -+ * reasonable accuracy in our retirement lists, we may have -+ * a stray pin (preventing eviction) that can only be resolved by -+ * retiring. -+ */ -+ if (!(flags & PIN_NONBLOCK)) -+ i915_retire_requests(dev_priv); -+ -+search_again: -+ active = NULL; -+ INIT_LIST_HEAD(&eviction_list); -+ list_for_each_entry_safe(vma, next, &vm->bound_list, vm_link) { -+ /* -+ * We keep this list in a rough least-recently scanned order -+ * of active elements (inactive elements are cheap to reap). -+ * New entries are added to the end, and we move anything we -+ * scan to the end. The assumption is that the working set -+ * of applications is either steady state (and thanks to the -+ * userspace bo cache it almost always is) or volatile and -+ * frequently replaced after a frame, which are self-evicting! -+ * Given that assumption, the MRU order of the scan list is -+ * fairly static, and keeping it in least-recently scan order -+ * is suitable. -+ * -+ * To notice when we complete one full cycle, we record the -+ * first active element seen, before moving it to the tail. -+ */ -+ if (i915_vma_is_active(vma)) { -+ if (vma == active) { -+ if (flags & PIN_NONBLOCK) -+ break; -+ -+ active = ERR_PTR(-EAGAIN); -+ } -+ -+ if (active != ERR_PTR(-EAGAIN)) { -+ if (!active) -+ active = vma; -+ -+ list_move_tail(&vma->vm_link, &vm->bound_list); -+ continue; -+ } -+ } -+ -+ if (mark_free(&scan, vma, flags, &eviction_list)) -+ goto found; -+ } -+ -+ /* Nothing found, clean up and bail out! */ -+ list_for_each_entry_safe(vma, next, &eviction_list, evict_link) { -+ ret = drm_mm_scan_remove_block(&scan, &vma->node); -+ BUG_ON(ret); -+ } -+ -+ /* -+ * Can we unpin some objects such as idle hw contents, -+ * or pending flips? But since only the GGTT has global entries -+ * such as scanouts, rinbuffers and contexts, we can skip the -+ * purge when inspecting per-process local address spaces. -+ */ -+ if (!i915_is_ggtt(vm) || flags & PIN_NONBLOCK) -+ return -ENOSPC; -+ -+ /* -+ * Not everything in the GGTT is tracked via VMA using -+ * i915_vma_move_to_active(), otherwise we could evict as required -+ * with minimal stalling. Instead we are forced to idle the GPU and -+ * explicitly retire outstanding requests which will then remove -+ * the pinning for active objects such as contexts and ring, -+ * enabling us to evict them on the next iteration. -+ * -+ * To ensure that all user contexts are evictable, we perform -+ * a switch to the perma-pinned kernel context. This all also gives -+ * us a termination condition, when the last retired context is -+ * the kernel's there is no more we can evict. -+ */ -+ if (!ggtt_is_idle(dev_priv)) { -+ if (I915_SELFTEST_ONLY(igt_evict_ctl.fail_if_busy)) -+ return -EBUSY; -+ -+ ret = ggtt_flush(dev_priv); -+ if (ret) -+ return ret; -+ -+ cond_resched(); -+ goto search_again; -+ } -+ -+ /* -+ * If we still have pending pageflip completions, drop -+ * back to userspace to give our workqueues time to -+ * acquire our locks and unpin the old scanouts. -+ */ -+ return intel_has_pending_fb_unpin(dev_priv) ? -EAGAIN : -ENOSPC; -+ -+found: -+ /* drm_mm doesn't allow any other other operations while -+ * scanning, therefore store to-be-evicted objects on a -+ * temporary list and take a reference for all before -+ * calling unbind (which may remove the active reference -+ * of any of our objects, thus corrupting the list). -+ */ -+ list_for_each_entry_safe(vma, next, &eviction_list, evict_link) { -+ if (drm_mm_scan_remove_block(&scan, &vma->node)) -+ __i915_vma_pin(vma); -+ else -+ list_del(&vma->evict_link); -+ } -+ -+ /* Unbinding will emit any required flushes */ -+ ret = 0; -+ list_for_each_entry_safe(vma, next, &eviction_list, evict_link) { -+ __i915_vma_unpin(vma); -+ if (ret == 0) -+ ret = i915_vma_unbind(vma); -+ } -+ -+ while (ret == 0 && (node = drm_mm_scan_color_evict(&scan))) { -+ vma = container_of(node, struct i915_vma, node); -+ ret = i915_vma_unbind(vma); -+ } -+ -+ return ret; -+} -+ -+/** -+ * i915_gem_evict_for_vma - Evict vmas to make room for binding a new one -+ * @vm: address space to evict from -+ * @target: range (and color) to evict for -+ * @flags: additional flags to control the eviction algorithm -+ * -+ * This function will try to evict vmas that overlap the target node. -+ * -+ * To clarify: This is for freeing up virtual address space, not for freeing -+ * memory in e.g. the shrinker. -+ */ -+int i915_gem_evict_for_node(struct i915_address_space *vm, -+ struct drm_mm_node *target, -+ unsigned int flags) -+{ -+ LIST_HEAD(eviction_list); -+ struct drm_mm_node *node; -+ u64 start = target->start; -+ u64 end = start + target->size; -+ struct i915_vma *vma, *next; -+ bool check_color; -+ int ret = 0; -+ -+ lockdep_assert_held(&vm->i915->drm.struct_mutex); -+ GEM_BUG_ON(!IS_ALIGNED(start, I915_GTT_PAGE_SIZE)); -+ GEM_BUG_ON(!IS_ALIGNED(end, I915_GTT_PAGE_SIZE)); -+ -+ trace_i915_gem_evict_node(vm, target, flags); -+ -+ /* Retire before we search the active list. Although we have -+ * reasonable accuracy in our retirement lists, we may have -+ * a stray pin (preventing eviction) that can only be resolved by -+ * retiring. -+ */ -+ if (!(flags & PIN_NONBLOCK)) -+ i915_retire_requests(vm->i915); -+ -+ check_color = vm->mm.color_adjust; -+ if (check_color) { -+ /* Expand search to cover neighbouring guard pages (or lack!) */ -+ if (start) -+ start -= I915_GTT_PAGE_SIZE; -+ -+ /* Always look at the page afterwards to avoid the end-of-GTT */ -+ end += I915_GTT_PAGE_SIZE; -+ } -+ GEM_BUG_ON(start >= end); -+ -+ drm_mm_for_each_node_in_range(node, &vm->mm, start, end) { -+ /* If we find any non-objects (!vma), we cannot evict them */ -+ if (node->color == I915_COLOR_UNEVICTABLE) { -+ ret = -ENOSPC; -+ break; -+ } -+ -+ GEM_BUG_ON(!node->allocated); -+ vma = container_of(node, typeof(*vma), node); -+ -+ /* If we are using coloring to insert guard pages between -+ * different cache domains within the address space, we have -+ * to check whether the objects on either side of our range -+ * abutt and conflict. If they are in conflict, then we evict -+ * those as well to make room for our guard pages. -+ */ -+ if (check_color) { -+ if (node->start + node->size == target->start) { -+ if (node->color == target->color) -+ continue; -+ } -+ if (node->start == target->start + target->size) { -+ if (node->color == target->color) -+ continue; -+ } -+ } -+ -+ if (flags & PIN_NONBLOCK && -+ (i915_vma_is_pinned(vma) || i915_vma_is_active(vma))) { -+ ret = -ENOSPC; -+ break; -+ } -+ -+ if (flags & PIN_NONFAULT && i915_vma_has_userfault(vma)) { -+ ret = -ENOSPC; -+ break; -+ } -+ -+ /* Overlap of objects in the same batch? */ -+ if (i915_vma_is_pinned(vma)) { -+ ret = -ENOSPC; -+ if (vma->exec_flags && -+ *vma->exec_flags & EXEC_OBJECT_PINNED) -+ ret = -EINVAL; -+ break; -+ } -+ -+ /* Never show fear in the face of dragons! -+ * -+ * We cannot directly remove this node from within this -+ * iterator and as with i915_gem_evict_something() we employ -+ * the vma pin_count in order to prevent the action of -+ * unbinding one vma from freeing (by dropping its active -+ * reference) another in our eviction list. -+ */ -+ __i915_vma_pin(vma); -+ list_add(&vma->evict_link, &eviction_list); -+ } -+ -+ list_for_each_entry_safe(vma, next, &eviction_list, evict_link) { -+ __i915_vma_unpin(vma); -+ if (ret == 0) -+ ret = i915_vma_unbind(vma); -+ } -+ -+ return ret; -+} -+ -+/** -+ * i915_gem_evict_vm - Evict all idle vmas from a vm -+ * @vm: Address space to cleanse -+ * -+ * This function evicts all vmas from a vm. -+ * -+ * This is used by the execbuf code as a last-ditch effort to defragment the -+ * address space. -+ * -+ * To clarify: This is for freeing up virtual address space, not for freeing -+ * memory in e.g. the shrinker. -+ */ -+int i915_gem_evict_vm(struct i915_address_space *vm) -+{ -+ struct list_head eviction_list; -+ struct i915_vma *vma, *next; -+ int ret; -+ -+ lockdep_assert_held(&vm->i915->drm.struct_mutex); -+ trace_i915_gem_evict_vm(vm); -+ -+ /* Switch back to the default context in order to unpin -+ * the existing context objects. However, such objects only -+ * pin themselves inside the global GTT and performing the -+ * switch otherwise is ineffective. -+ */ -+ if (i915_is_ggtt(vm)) { -+ ret = ggtt_flush(vm->i915); -+ if (ret) -+ return ret; -+ } -+ -+ INIT_LIST_HEAD(&eviction_list); -+ mutex_lock(&vm->mutex); -+ list_for_each_entry(vma, &vm->bound_list, vm_link) { -+ if (i915_vma_is_pinned(vma)) -+ continue; -+ -+ __i915_vma_pin(vma); -+ list_add(&vma->evict_link, &eviction_list); -+ } -+ mutex_unlock(&vm->mutex); -+ -+ ret = 0; -+ list_for_each_entry_safe(vma, next, &eviction_list, evict_link) { -+ __i915_vma_unpin(vma); -+ if (ret == 0) -+ ret = i915_vma_unbind(vma); -+ } -+ return ret; -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -+#include "selftests/i915_gem_evict.c" -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_execbuffer.c b/drivers/gpu/drm/i915_legacy/i915_gem_execbuffer.c -new file mode 100644 -index 000000000000..c83d2a195d15 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_execbuffer.c -@@ -0,0 +1,2722 @@ -+/* -+ * Copyright © 2008,2010 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eric Anholt -+ * Chris Wilson -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "i915_drv.h" -+#include "i915_gem_clflush.h" -+#include "i915_trace.h" -+#include "intel_drv.h" -+#include "intel_frontbuffer.h" -+ -+enum { -+ FORCE_CPU_RELOC = 1, -+ FORCE_GTT_RELOC, -+ FORCE_GPU_RELOC, -+#define DBG_FORCE_RELOC 0 /* choose one of the above! */ -+}; -+ -+#define __EXEC_OBJECT_HAS_REF BIT(31) -+#define __EXEC_OBJECT_HAS_PIN BIT(30) -+#define __EXEC_OBJECT_HAS_FENCE BIT(29) -+#define __EXEC_OBJECT_NEEDS_MAP BIT(28) -+#define __EXEC_OBJECT_NEEDS_BIAS BIT(27) -+#define __EXEC_OBJECT_INTERNAL_FLAGS (~0u << 27) /* all of the above */ -+#define __EXEC_OBJECT_RESERVED (__EXEC_OBJECT_HAS_PIN | __EXEC_OBJECT_HAS_FENCE) -+ -+#define __EXEC_HAS_RELOC BIT(31) -+#define __EXEC_VALIDATED BIT(30) -+#define __EXEC_INTERNAL_FLAGS (~0u << 30) -+#define UPDATE PIN_OFFSET_FIXED -+ -+#define BATCH_OFFSET_BIAS (256*1024) -+ -+#define __I915_EXEC_ILLEGAL_FLAGS \ -+ (__I915_EXEC_UNKNOWN_FLAGS | \ -+ I915_EXEC_CONSTANTS_MASK | \ -+ I915_EXEC_RESOURCE_STREAMER) -+ -+/* Catch emission of unexpected errors for CI! */ -+#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM) -+#undef EINVAL -+#define EINVAL ({ \ -+ DRM_DEBUG_DRIVER("EINVAL at %s:%d\n", __func__, __LINE__); \ -+ 22; \ -+}) -+#endif -+ -+/** -+ * DOC: User command execution -+ * -+ * Userspace submits commands to be executed on the GPU as an instruction -+ * stream within a GEM object we call a batchbuffer. This instructions may -+ * refer to other GEM objects containing auxiliary state such as kernels, -+ * samplers, render targets and even secondary batchbuffers. Userspace does -+ * not know where in the GPU memory these objects reside and so before the -+ * batchbuffer is passed to the GPU for execution, those addresses in the -+ * batchbuffer and auxiliary objects are updated. This is known as relocation, -+ * or patching. To try and avoid having to relocate each object on the next -+ * execution, userspace is told the location of those objects in this pass, -+ * but this remains just a hint as the kernel may choose a new location for -+ * any object in the future. -+ * -+ * At the level of talking to the hardware, submitting a batchbuffer for the -+ * GPU to execute is to add content to a buffer from which the HW -+ * command streamer is reading. -+ * -+ * 1. Add a command to load the HW context. For Logical Ring Contexts, i.e. -+ * Execlists, this command is not placed on the same buffer as the -+ * remaining items. -+ * -+ * 2. Add a command to invalidate caches to the buffer. -+ * -+ * 3. Add a batchbuffer start command to the buffer; the start command is -+ * essentially a token together with the GPU address of the batchbuffer -+ * to be executed. -+ * -+ * 4. Add a pipeline flush to the buffer. -+ * -+ * 5. Add a memory write command to the buffer to record when the GPU -+ * is done executing the batchbuffer. The memory write writes the -+ * global sequence number of the request, ``i915_request::global_seqno``; -+ * the i915 driver uses the current value in the register to determine -+ * if the GPU has completed the batchbuffer. -+ * -+ * 6. Add a user interrupt command to the buffer. This command instructs -+ * the GPU to issue an interrupt when the command, pipeline flush and -+ * memory write are completed. -+ * -+ * 7. Inform the hardware of the additional commands added to the buffer -+ * (by updating the tail pointer). -+ * -+ * Processing an execbuf ioctl is conceptually split up into a few phases. -+ * -+ * 1. Validation - Ensure all the pointers, handles and flags are valid. -+ * 2. Reservation - Assign GPU address space for every object -+ * 3. Relocation - Update any addresses to point to the final locations -+ * 4. Serialisation - Order the request with respect to its dependencies -+ * 5. Construction - Construct a request to execute the batchbuffer -+ * 6. Submission (at some point in the future execution) -+ * -+ * Reserving resources for the execbuf is the most complicated phase. We -+ * neither want to have to migrate the object in the address space, nor do -+ * we want to have to update any relocations pointing to this object. Ideally, -+ * we want to leave the object where it is and for all the existing relocations -+ * to match. If the object is given a new address, or if userspace thinks the -+ * object is elsewhere, we have to parse all the relocation entries and update -+ * the addresses. Userspace can set the I915_EXEC_NORELOC flag to hint that -+ * all the target addresses in all of its objects match the value in the -+ * relocation entries and that they all match the presumed offsets given by the -+ * list of execbuffer objects. Using this knowledge, we know that if we haven't -+ * moved any buffers, all the relocation entries are valid and we can skip -+ * the update. (If userspace is wrong, the likely outcome is an impromptu GPU -+ * hang.) The requirement for using I915_EXEC_NO_RELOC are: -+ * -+ * The addresses written in the objects must match the corresponding -+ * reloc.presumed_offset which in turn must match the corresponding -+ * execobject.offset. -+ * -+ * Any render targets written to in the batch must be flagged with -+ * EXEC_OBJECT_WRITE. -+ * -+ * To avoid stalling, execobject.offset should match the current -+ * address of that object within the active context. -+ * -+ * The reservation is done is multiple phases. First we try and keep any -+ * object already bound in its current location - so as long as meets the -+ * constraints imposed by the new execbuffer. Any object left unbound after the -+ * first pass is then fitted into any available idle space. If an object does -+ * not fit, all objects are removed from the reservation and the process rerun -+ * after sorting the objects into a priority order (more difficult to fit -+ * objects are tried first). Failing that, the entire VM is cleared and we try -+ * to fit the execbuf once last time before concluding that it simply will not -+ * fit. -+ * -+ * A small complication to all of this is that we allow userspace not only to -+ * specify an alignment and a size for the object in the address space, but -+ * we also allow userspace to specify the exact offset. This objects are -+ * simpler to place (the location is known a priori) all we have to do is make -+ * sure the space is available. -+ * -+ * Once all the objects are in place, patching up the buried pointers to point -+ * to the final locations is a fairly simple job of walking over the relocation -+ * entry arrays, looking up the right address and rewriting the value into -+ * the object. Simple! ... The relocation entries are stored in user memory -+ * and so to access them we have to copy them into a local buffer. That copy -+ * has to avoid taking any pagefaults as they may lead back to a GEM object -+ * requiring the struct_mutex (i.e. recursive deadlock). So once again we split -+ * the relocation into multiple passes. First we try to do everything within an -+ * atomic context (avoid the pagefaults) which requires that we never wait. If -+ * we detect that we may wait, or if we need to fault, then we have to fallback -+ * to a slower path. The slowpath has to drop the mutex. (Can you hear alarm -+ * bells yet?) Dropping the mutex means that we lose all the state we have -+ * built up so far for the execbuf and we must reset any global data. However, -+ * we do leave the objects pinned in their final locations - which is a -+ * potential issue for concurrent execbufs. Once we have left the mutex, we can -+ * allocate and copy all the relocation entries into a large array at our -+ * leisure, reacquire the mutex, reclaim all the objects and other state and -+ * then proceed to update any incorrect addresses with the objects. -+ * -+ * As we process the relocation entries, we maintain a record of whether the -+ * object is being written to. Using NORELOC, we expect userspace to provide -+ * this information instead. We also check whether we can skip the relocation -+ * by comparing the expected value inside the relocation entry with the target's -+ * final address. If they differ, we have to map the current object and rewrite -+ * the 4 or 8 byte pointer within. -+ * -+ * Serialising an execbuf is quite simple according to the rules of the GEM -+ * ABI. Execution within each context is ordered by the order of submission. -+ * Writes to any GEM object are in order of submission and are exclusive. Reads -+ * from a GEM object are unordered with respect to other reads, but ordered by -+ * writes. A write submitted after a read cannot occur before the read, and -+ * similarly any read submitted after a write cannot occur before the write. -+ * Writes are ordered between engines such that only one write occurs at any -+ * time (completing any reads beforehand) - using semaphores where available -+ * and CPU serialisation otherwise. Other GEM access obey the same rules, any -+ * write (either via mmaps using set-domain, or via pwrite) must flush all GPU -+ * reads before starting, and any read (either using set-domain or pread) must -+ * flush all GPU writes before starting. (Note we only employ a barrier before, -+ * we currently rely on userspace not concurrently starting a new execution -+ * whilst reading or writing to an object. This may be an advantage or not -+ * depending on how much you trust userspace not to shoot themselves in the -+ * foot.) Serialisation may just result in the request being inserted into -+ * a DAG awaiting its turn, but most simple is to wait on the CPU until -+ * all dependencies are resolved. -+ * -+ * After all of that, is just a matter of closing the request and handing it to -+ * the hardware (well, leaving it in a queue to be executed). However, we also -+ * offer the ability for batchbuffers to be run with elevated privileges so -+ * that they access otherwise hidden registers. (Used to adjust L3 cache etc.) -+ * Before any batch is given extra privileges we first must check that it -+ * contains no nefarious instructions, we check that each instruction is from -+ * our whitelist and all registers are also from an allowed list. We first -+ * copy the user's batchbuffer to a shadow (so that the user doesn't have -+ * access to it, either by the CPU or GPU as we scan it) and then parse each -+ * instruction. If everything is ok, we set a flag telling the hardware to run -+ * the batchbuffer in trusted mode, otherwise the ioctl is rejected. -+ */ -+ -+struct i915_execbuffer { -+ struct drm_i915_private *i915; /** i915 backpointer */ -+ struct drm_file *file; /** per-file lookup tables and limits */ -+ struct drm_i915_gem_execbuffer2 *args; /** ioctl parameters */ -+ struct drm_i915_gem_exec_object2 *exec; /** ioctl execobj[] */ -+ struct i915_vma **vma; -+ unsigned int *flags; -+ -+ struct intel_engine_cs *engine; /** engine to queue the request to */ -+ struct i915_gem_context *ctx; /** context for building the request */ -+ struct i915_address_space *vm; /** GTT and vma for the request */ -+ -+ struct i915_request *request; /** our request to build */ -+ struct i915_vma *batch; /** identity of the batch obj/vma */ -+ -+ /** actual size of execobj[] as we may extend it for the cmdparser */ -+ unsigned int buffer_count; -+ -+ /** list of vma not yet bound during reservation phase */ -+ struct list_head unbound; -+ -+ /** list of vma that have execobj.relocation_count */ -+ struct list_head relocs; -+ -+ /** -+ * Track the most recently used object for relocations, as we -+ * frequently have to perform multiple relocations within the same -+ * obj/page -+ */ -+ struct reloc_cache { -+ struct drm_mm_node node; /** temporary GTT binding */ -+ unsigned long vaddr; /** Current kmap address */ -+ unsigned long page; /** Currently mapped page index */ -+ unsigned int gen; /** Cached value of INTEL_GEN */ -+ bool use_64bit_reloc : 1; -+ bool has_llc : 1; -+ bool has_fence : 1; -+ bool needs_unfenced : 1; -+ -+ struct i915_request *rq; -+ u32 *rq_cmd; -+ unsigned int rq_size; -+ } reloc_cache; -+ -+ u64 invalid_flags; /** Set of execobj.flags that are invalid */ -+ u32 context_flags; /** Set of execobj.flags to insert from the ctx */ -+ -+ u32 batch_start_offset; /** Location within object of batch */ -+ u32 batch_len; /** Length of batch within object */ -+ u32 batch_flags; /** Flags composed for emit_bb_start() */ -+ -+ /** -+ * Indicate either the size of the hastable used to resolve -+ * relocation handles, or if negative that we are using a direct -+ * index into the execobj[]. -+ */ -+ int lut_size; -+ struct hlist_head *buckets; /** ht for relocation handles */ -+}; -+ -+#define exec_entry(EB, VMA) (&(EB)->exec[(VMA)->exec_flags - (EB)->flags]) -+ -+/* -+ * Used to convert any address to canonical form. -+ * Starting from gen8, some commands (e.g. STATE_BASE_ADDRESS, -+ * MI_LOAD_REGISTER_MEM and others, see Broadwell PRM Vol2a) require the -+ * addresses to be in a canonical form: -+ * "GraphicsAddress[63:48] are ignored by the HW and assumed to be in correct -+ * canonical form [63:48] == [47]." -+ */ -+#define GEN8_HIGH_ADDRESS_BIT 47 -+static inline u64 gen8_canonical_addr(u64 address) -+{ -+ return sign_extend64(address, GEN8_HIGH_ADDRESS_BIT); -+} -+ -+static inline u64 gen8_noncanonical_addr(u64 address) -+{ -+ return address & GENMASK_ULL(GEN8_HIGH_ADDRESS_BIT, 0); -+} -+ -+static inline bool eb_use_cmdparser(const struct i915_execbuffer *eb) -+{ -+ return intel_engine_needs_cmd_parser(eb->engine) && eb->batch_len; -+} -+ -+static int eb_create(struct i915_execbuffer *eb) -+{ -+ if (!(eb->args->flags & I915_EXEC_HANDLE_LUT)) { -+ unsigned int size = 1 + ilog2(eb->buffer_count); -+ -+ /* -+ * Without a 1:1 association between relocation handles and -+ * the execobject[] index, we instead create a hashtable. -+ * We size it dynamically based on available memory, starting -+ * first with 1:1 assocative hash and scaling back until -+ * the allocation succeeds. -+ * -+ * Later on we use a positive lut_size to indicate we are -+ * using this hashtable, and a negative value to indicate a -+ * direct lookup. -+ */ -+ do { -+ gfp_t flags; -+ -+ /* While we can still reduce the allocation size, don't -+ * raise a warning and allow the allocation to fail. -+ * On the last pass though, we want to try as hard -+ * as possible to perform the allocation and warn -+ * if it fails. -+ */ -+ flags = GFP_KERNEL; -+ if (size > 1) -+ flags |= __GFP_NORETRY | __GFP_NOWARN; -+ -+ eb->buckets = kzalloc(sizeof(struct hlist_head) << size, -+ flags); -+ if (eb->buckets) -+ break; -+ } while (--size); -+ -+ if (unlikely(!size)) -+ return -ENOMEM; -+ -+ eb->lut_size = size; -+ } else { -+ eb->lut_size = -eb->buffer_count; -+ } -+ -+ return 0; -+} -+ -+static bool -+eb_vma_misplaced(const struct drm_i915_gem_exec_object2 *entry, -+ const struct i915_vma *vma, -+ unsigned int flags) -+{ -+ if (vma->node.size < entry->pad_to_size) -+ return true; -+ -+ if (entry->alignment && !IS_ALIGNED(vma->node.start, entry->alignment)) -+ return true; -+ -+ if (flags & EXEC_OBJECT_PINNED && -+ vma->node.start != entry->offset) -+ return true; -+ -+ if (flags & __EXEC_OBJECT_NEEDS_BIAS && -+ vma->node.start < BATCH_OFFSET_BIAS) -+ return true; -+ -+ if (!(flags & EXEC_OBJECT_SUPPORTS_48B_ADDRESS) && -+ (vma->node.start + vma->node.size - 1) >> 32) -+ return true; -+ -+ if (flags & __EXEC_OBJECT_NEEDS_MAP && -+ !i915_vma_is_map_and_fenceable(vma)) -+ return true; -+ -+ return false; -+} -+ -+static inline bool -+eb_pin_vma(struct i915_execbuffer *eb, -+ const struct drm_i915_gem_exec_object2 *entry, -+ struct i915_vma *vma) -+{ -+ unsigned int exec_flags = *vma->exec_flags; -+ u64 pin_flags; -+ -+ if (vma->node.size) -+ pin_flags = vma->node.start; -+ else -+ pin_flags = entry->offset & PIN_OFFSET_MASK; -+ -+ pin_flags |= PIN_USER | PIN_NOEVICT | PIN_OFFSET_FIXED; -+ if (unlikely(exec_flags & EXEC_OBJECT_NEEDS_GTT)) -+ pin_flags |= PIN_GLOBAL; -+ -+ if (unlikely(i915_vma_pin(vma, 0, 0, pin_flags))) -+ return false; -+ -+ if (unlikely(exec_flags & EXEC_OBJECT_NEEDS_FENCE)) { -+ if (unlikely(i915_vma_pin_fence(vma))) { -+ i915_vma_unpin(vma); -+ return false; -+ } -+ -+ if (vma->fence) -+ exec_flags |= __EXEC_OBJECT_HAS_FENCE; -+ } -+ -+ *vma->exec_flags = exec_flags | __EXEC_OBJECT_HAS_PIN; -+ return !eb_vma_misplaced(entry, vma, exec_flags); -+} -+ -+static inline void __eb_unreserve_vma(struct i915_vma *vma, unsigned int flags) -+{ -+ GEM_BUG_ON(!(flags & __EXEC_OBJECT_HAS_PIN)); -+ -+ if (unlikely(flags & __EXEC_OBJECT_HAS_FENCE)) -+ __i915_vma_unpin_fence(vma); -+ -+ __i915_vma_unpin(vma); -+} -+ -+static inline void -+eb_unreserve_vma(struct i915_vma *vma, unsigned int *flags) -+{ -+ if (!(*flags & __EXEC_OBJECT_HAS_PIN)) -+ return; -+ -+ __eb_unreserve_vma(vma, *flags); -+ *flags &= ~__EXEC_OBJECT_RESERVED; -+} -+ -+static int -+eb_validate_vma(struct i915_execbuffer *eb, -+ struct drm_i915_gem_exec_object2 *entry, -+ struct i915_vma *vma) -+{ -+ if (unlikely(entry->flags & eb->invalid_flags)) -+ return -EINVAL; -+ -+ if (unlikely(entry->alignment && !is_power_of_2(entry->alignment))) -+ return -EINVAL; -+ -+ /* -+ * Offset can be used as input (EXEC_OBJECT_PINNED), reject -+ * any non-page-aligned or non-canonical addresses. -+ */ -+ if (unlikely(entry->flags & EXEC_OBJECT_PINNED && -+ entry->offset != gen8_canonical_addr(entry->offset & I915_GTT_PAGE_MASK))) -+ return -EINVAL; -+ -+ /* pad_to_size was once a reserved field, so sanitize it */ -+ if (entry->flags & EXEC_OBJECT_PAD_TO_SIZE) { -+ if (unlikely(offset_in_page(entry->pad_to_size))) -+ return -EINVAL; -+ } else { -+ entry->pad_to_size = 0; -+ } -+ -+ if (unlikely(vma->exec_flags)) { -+ DRM_DEBUG("Object [handle %d, index %d] appears more than once in object list\n", -+ entry->handle, (int)(entry - eb->exec)); -+ return -EINVAL; -+ } -+ -+ /* -+ * From drm_mm perspective address space is continuous, -+ * so from this point we're always using non-canonical -+ * form internally. -+ */ -+ entry->offset = gen8_noncanonical_addr(entry->offset); -+ -+ if (!eb->reloc_cache.has_fence) { -+ entry->flags &= ~EXEC_OBJECT_NEEDS_FENCE; -+ } else { -+ if ((entry->flags & EXEC_OBJECT_NEEDS_FENCE || -+ eb->reloc_cache.needs_unfenced) && -+ i915_gem_object_is_tiled(vma->obj)) -+ entry->flags |= EXEC_OBJECT_NEEDS_GTT | __EXEC_OBJECT_NEEDS_MAP; -+ } -+ -+ if (!(entry->flags & EXEC_OBJECT_PINNED)) -+ entry->flags |= eb->context_flags; -+ -+ return 0; -+} -+ -+static int -+eb_add_vma(struct i915_execbuffer *eb, -+ unsigned int i, unsigned batch_idx, -+ struct i915_vma *vma) -+{ -+ struct drm_i915_gem_exec_object2 *entry = &eb->exec[i]; -+ int err; -+ -+ GEM_BUG_ON(i915_vma_is_closed(vma)); -+ -+ if (!(eb->args->flags & __EXEC_VALIDATED)) { -+ err = eb_validate_vma(eb, entry, vma); -+ if (unlikely(err)) -+ return err; -+ } -+ -+ if (eb->lut_size > 0) { -+ vma->exec_handle = entry->handle; -+ hlist_add_head(&vma->exec_node, -+ &eb->buckets[hash_32(entry->handle, -+ eb->lut_size)]); -+ } -+ -+ if (entry->relocation_count) -+ list_add_tail(&vma->reloc_link, &eb->relocs); -+ -+ /* -+ * Stash a pointer from the vma to execobj, so we can query its flags, -+ * size, alignment etc as provided by the user. Also we stash a pointer -+ * to the vma inside the execobj so that we can use a direct lookup -+ * to find the right target VMA when doing relocations. -+ */ -+ eb->vma[i] = vma; -+ eb->flags[i] = entry->flags; -+ vma->exec_flags = &eb->flags[i]; -+ -+ /* -+ * SNA is doing fancy tricks with compressing batch buffers, which leads -+ * to negative relocation deltas. Usually that works out ok since the -+ * relocate address is still positive, except when the batch is placed -+ * very low in the GTT. Ensure this doesn't happen. -+ * -+ * Note that actual hangs have only been observed on gen7, but for -+ * paranoia do it everywhere. -+ */ -+ if (i == batch_idx) { -+ if (entry->relocation_count && -+ !(eb->flags[i] & EXEC_OBJECT_PINNED)) -+ eb->flags[i] |= __EXEC_OBJECT_NEEDS_BIAS; -+ if (eb->reloc_cache.has_fence) -+ eb->flags[i] |= EXEC_OBJECT_NEEDS_FENCE; -+ -+ eb->batch = vma; -+ } -+ -+ err = 0; -+ if (eb_pin_vma(eb, entry, vma)) { -+ if (entry->offset != vma->node.start) { -+ entry->offset = vma->node.start | UPDATE; -+ eb->args->flags |= __EXEC_HAS_RELOC; -+ } -+ } else { -+ eb_unreserve_vma(vma, vma->exec_flags); -+ -+ list_add_tail(&vma->exec_link, &eb->unbound); -+ if (drm_mm_node_allocated(&vma->node)) -+ err = i915_vma_unbind(vma); -+ if (unlikely(err)) -+ vma->exec_flags = NULL; -+ } -+ return err; -+} -+ -+static inline int use_cpu_reloc(const struct reloc_cache *cache, -+ const struct drm_i915_gem_object *obj) -+{ -+ if (!i915_gem_object_has_struct_page(obj)) -+ return false; -+ -+ if (DBG_FORCE_RELOC == FORCE_CPU_RELOC) -+ return true; -+ -+ if (DBG_FORCE_RELOC == FORCE_GTT_RELOC) -+ return false; -+ -+ return (cache->has_llc || -+ obj->cache_dirty || -+ obj->cache_level != I915_CACHE_NONE); -+} -+ -+static int eb_reserve_vma(const struct i915_execbuffer *eb, -+ struct i915_vma *vma) -+{ -+ struct drm_i915_gem_exec_object2 *entry = exec_entry(eb, vma); -+ unsigned int exec_flags = *vma->exec_flags; -+ u64 pin_flags; -+ int err; -+ -+ pin_flags = PIN_USER | PIN_NONBLOCK; -+ if (exec_flags & EXEC_OBJECT_NEEDS_GTT) -+ pin_flags |= PIN_GLOBAL; -+ -+ /* -+ * Wa32bitGeneralStateOffset & Wa32bitInstructionBaseOffset, -+ * limit address to the first 4GBs for unflagged objects. -+ */ -+ if (!(exec_flags & EXEC_OBJECT_SUPPORTS_48B_ADDRESS)) -+ pin_flags |= PIN_ZONE_4G; -+ -+ if (exec_flags & __EXEC_OBJECT_NEEDS_MAP) -+ pin_flags |= PIN_MAPPABLE; -+ -+ if (exec_flags & EXEC_OBJECT_PINNED) { -+ pin_flags |= entry->offset | PIN_OFFSET_FIXED; -+ pin_flags &= ~PIN_NONBLOCK; /* force overlapping checks */ -+ } else if (exec_flags & __EXEC_OBJECT_NEEDS_BIAS) { -+ pin_flags |= BATCH_OFFSET_BIAS | PIN_OFFSET_BIAS; -+ } -+ -+ err = i915_vma_pin(vma, -+ entry->pad_to_size, entry->alignment, -+ pin_flags); -+ if (err) -+ return err; -+ -+ if (entry->offset != vma->node.start) { -+ entry->offset = vma->node.start | UPDATE; -+ eb->args->flags |= __EXEC_HAS_RELOC; -+ } -+ -+ if (unlikely(exec_flags & EXEC_OBJECT_NEEDS_FENCE)) { -+ err = i915_vma_pin_fence(vma); -+ if (unlikely(err)) { -+ i915_vma_unpin(vma); -+ return err; -+ } -+ -+ if (vma->fence) -+ exec_flags |= __EXEC_OBJECT_HAS_FENCE; -+ } -+ -+ *vma->exec_flags = exec_flags | __EXEC_OBJECT_HAS_PIN; -+ GEM_BUG_ON(eb_vma_misplaced(entry, vma, exec_flags)); -+ -+ return 0; -+} -+ -+static int eb_reserve(struct i915_execbuffer *eb) -+{ -+ const unsigned int count = eb->buffer_count; -+ struct list_head last; -+ struct i915_vma *vma; -+ unsigned int i, pass; -+ int err; -+ -+ /* -+ * Attempt to pin all of the buffers into the GTT. -+ * This is done in 3 phases: -+ * -+ * 1a. Unbind all objects that do not match the GTT constraints for -+ * the execbuffer (fenceable, mappable, alignment etc). -+ * 1b. Increment pin count for already bound objects. -+ * 2. Bind new objects. -+ * 3. Decrement pin count. -+ * -+ * This avoid unnecessary unbinding of later objects in order to make -+ * room for the earlier objects *unless* we need to defragment. -+ */ -+ -+ pass = 0; -+ err = 0; -+ do { -+ list_for_each_entry(vma, &eb->unbound, exec_link) { -+ err = eb_reserve_vma(eb, vma); -+ if (err) -+ break; -+ } -+ if (err != -ENOSPC) -+ return err; -+ -+ /* Resort *all* the objects into priority order */ -+ INIT_LIST_HEAD(&eb->unbound); -+ INIT_LIST_HEAD(&last); -+ for (i = 0; i < count; i++) { -+ unsigned int flags = eb->flags[i]; -+ struct i915_vma *vma = eb->vma[i]; -+ -+ if (flags & EXEC_OBJECT_PINNED && -+ flags & __EXEC_OBJECT_HAS_PIN) -+ continue; -+ -+ eb_unreserve_vma(vma, &eb->flags[i]); -+ -+ if (flags & EXEC_OBJECT_PINNED) -+ /* Pinned must have their slot */ -+ list_add(&vma->exec_link, &eb->unbound); -+ else if (flags & __EXEC_OBJECT_NEEDS_MAP) -+ /* Map require the lowest 256MiB (aperture) */ -+ list_add_tail(&vma->exec_link, &eb->unbound); -+ else if (!(flags & EXEC_OBJECT_SUPPORTS_48B_ADDRESS)) -+ /* Prioritise 4GiB region for restricted bo */ -+ list_add(&vma->exec_link, &last); -+ else -+ list_add_tail(&vma->exec_link, &last); -+ } -+ list_splice_tail(&last, &eb->unbound); -+ -+ switch (pass++) { -+ case 0: -+ break; -+ -+ case 1: -+ /* Too fragmented, unbind everything and retry */ -+ err = i915_gem_evict_vm(eb->vm); -+ if (err) -+ return err; -+ break; -+ -+ default: -+ return -ENOSPC; -+ } -+ } while (1); -+} -+ -+static unsigned int eb_batch_index(const struct i915_execbuffer *eb) -+{ -+ if (eb->args->flags & I915_EXEC_BATCH_FIRST) -+ return 0; -+ else -+ return eb->buffer_count - 1; -+} -+ -+static int eb_select_context(struct i915_execbuffer *eb) -+{ -+ struct i915_gem_context *ctx; -+ -+ ctx = i915_gem_context_lookup(eb->file->driver_priv, eb->args->rsvd1); -+ if (unlikely(!ctx)) -+ return -ENOENT; -+ -+ eb->ctx = ctx; -+ if (ctx->ppgtt) { -+ eb->vm = &ctx->ppgtt->vm; -+ eb->invalid_flags |= EXEC_OBJECT_NEEDS_GTT; -+ } else { -+ eb->vm = &eb->i915->ggtt.vm; -+ } -+ -+ eb->context_flags = 0; -+ if (test_bit(UCONTEXT_NO_ZEROMAP, &ctx->user_flags)) -+ eb->context_flags |= __EXEC_OBJECT_NEEDS_BIAS; -+ -+ return 0; -+} -+ -+static struct i915_request *__eb_wait_for_ring(struct intel_ring *ring) -+{ -+ struct i915_request *rq; -+ -+ /* -+ * Completely unscientific finger-in-the-air estimates for suitable -+ * maximum user request size (to avoid blocking) and then backoff. -+ */ -+ if (intel_ring_update_space(ring) >= PAGE_SIZE) -+ return NULL; -+ -+ /* -+ * Find a request that after waiting upon, there will be at least half -+ * the ring available. The hysteresis allows us to compete for the -+ * shared ring and should mean that we sleep less often prior to -+ * claiming our resources, but not so long that the ring completely -+ * drains before we can submit our next request. -+ */ -+ list_for_each_entry(rq, &ring->request_list, ring_link) { -+ if (__intel_ring_space(rq->postfix, -+ ring->emit, ring->size) > ring->size / 2) -+ break; -+ } -+ if (&rq->ring_link == &ring->request_list) -+ return NULL; /* weird, we will check again later for real */ -+ -+ return i915_request_get(rq); -+} -+ -+static int eb_wait_for_ring(const struct i915_execbuffer *eb) -+{ -+ const struct intel_context *ce; -+ struct i915_request *rq; -+ int ret = 0; -+ -+ /* -+ * Apply a light amount of backpressure to prevent excessive hogs -+ * from blocking waiting for space whilst holding struct_mutex and -+ * keeping all of their resources pinned. -+ */ -+ -+ ce = intel_context_lookup(eb->ctx, eb->engine); -+ if (!ce || !ce->ring) /* first use, assume empty! */ -+ return 0; -+ -+ rq = __eb_wait_for_ring(ce->ring); -+ if (rq) { -+ mutex_unlock(&eb->i915->drm.struct_mutex); -+ -+ if (i915_request_wait(rq, -+ I915_WAIT_INTERRUPTIBLE, -+ MAX_SCHEDULE_TIMEOUT) < 0) -+ ret = -EINTR; -+ -+ i915_request_put(rq); -+ -+ mutex_lock(&eb->i915->drm.struct_mutex); -+ } -+ -+ return ret; -+} -+ -+static int eb_lookup_vmas(struct i915_execbuffer *eb) -+{ -+ struct radix_tree_root *handles_vma = &eb->ctx->handles_vma; -+ struct drm_i915_gem_object *obj; -+ unsigned int i, batch; -+ int err; -+ -+ if (unlikely(i915_gem_context_is_closed(eb->ctx))) -+ return -ENOENT; -+ -+ if (unlikely(i915_gem_context_is_banned(eb->ctx))) -+ return -EIO; -+ -+ INIT_LIST_HEAD(&eb->relocs); -+ INIT_LIST_HEAD(&eb->unbound); -+ -+ batch = eb_batch_index(eb); -+ -+ for (i = 0; i < eb->buffer_count; i++) { -+ u32 handle = eb->exec[i].handle; -+ struct i915_lut_handle *lut; -+ struct i915_vma *vma; -+ -+ vma = radix_tree_lookup(handles_vma, handle); -+ if (likely(vma)) -+ goto add_vma; -+ -+ obj = i915_gem_object_lookup(eb->file, handle); -+ if (unlikely(!obj)) { -+ err = -ENOENT; -+ goto err_vma; -+ } -+ -+ vma = i915_vma_instance(obj, eb->vm, NULL); -+ if (IS_ERR(vma)) { -+ err = PTR_ERR(vma); -+ goto err_obj; -+ } -+ -+ lut = i915_lut_handle_alloc(); -+ if (unlikely(!lut)) { -+ err = -ENOMEM; -+ goto err_obj; -+ } -+ -+ err = radix_tree_insert(handles_vma, handle, vma); -+ if (unlikely(err)) { -+ i915_lut_handle_free(lut); -+ goto err_obj; -+ } -+ -+ /* transfer ref to ctx */ -+ if (!vma->open_count++) -+ i915_vma_reopen(vma); -+ list_add(&lut->obj_link, &obj->lut_list); -+ list_add(&lut->ctx_link, &eb->ctx->handles_list); -+ lut->ctx = eb->ctx; -+ lut->handle = handle; -+ -+add_vma: -+ err = eb_add_vma(eb, i, batch, vma); -+ if (unlikely(err)) -+ goto err_vma; -+ -+ GEM_BUG_ON(vma != eb->vma[i]); -+ GEM_BUG_ON(vma->exec_flags != &eb->flags[i]); -+ GEM_BUG_ON(drm_mm_node_allocated(&vma->node) && -+ eb_vma_misplaced(&eb->exec[i], vma, eb->flags[i])); -+ } -+ -+ eb->args->flags |= __EXEC_VALIDATED; -+ return eb_reserve(eb); -+ -+err_obj: -+ i915_gem_object_put(obj); -+err_vma: -+ eb->vma[i] = NULL; -+ return err; -+} -+ -+static struct i915_vma * -+eb_get_vma(const struct i915_execbuffer *eb, unsigned long handle) -+{ -+ if (eb->lut_size < 0) { -+ if (handle >= -eb->lut_size) -+ return NULL; -+ return eb->vma[handle]; -+ } else { -+ struct hlist_head *head; -+ struct i915_vma *vma; -+ -+ head = &eb->buckets[hash_32(handle, eb->lut_size)]; -+ hlist_for_each_entry(vma, head, exec_node) { -+ if (vma->exec_handle == handle) -+ return vma; -+ } -+ return NULL; -+ } -+} -+ -+static void eb_release_vmas(const struct i915_execbuffer *eb) -+{ -+ const unsigned int count = eb->buffer_count; -+ unsigned int i; -+ -+ for (i = 0; i < count; i++) { -+ struct i915_vma *vma = eb->vma[i]; -+ unsigned int flags = eb->flags[i]; -+ -+ if (!vma) -+ break; -+ -+ GEM_BUG_ON(vma->exec_flags != &eb->flags[i]); -+ vma->exec_flags = NULL; -+ eb->vma[i] = NULL; -+ -+ if (flags & __EXEC_OBJECT_HAS_PIN) -+ __eb_unreserve_vma(vma, flags); -+ -+ if (flags & __EXEC_OBJECT_HAS_REF) -+ i915_vma_put(vma); -+ } -+} -+ -+static void eb_reset_vmas(const struct i915_execbuffer *eb) -+{ -+ eb_release_vmas(eb); -+ if (eb->lut_size > 0) -+ memset(eb->buckets, 0, -+ sizeof(struct hlist_head) << eb->lut_size); -+} -+ -+static void eb_destroy(const struct i915_execbuffer *eb) -+{ -+ GEM_BUG_ON(eb->reloc_cache.rq); -+ -+ if (eb->lut_size > 0) -+ kfree(eb->buckets); -+} -+ -+static inline u64 -+relocation_target(const struct drm_i915_gem_relocation_entry *reloc, -+ const struct i915_vma *target) -+{ -+ return gen8_canonical_addr((int)reloc->delta + target->node.start); -+} -+ -+static void reloc_cache_init(struct reloc_cache *cache, -+ struct drm_i915_private *i915) -+{ -+ cache->page = -1; -+ cache->vaddr = 0; -+ /* Must be a variable in the struct to allow GCC to unroll. */ -+ cache->gen = INTEL_GEN(i915); -+ cache->has_llc = HAS_LLC(i915); -+ cache->use_64bit_reloc = HAS_64BIT_RELOC(i915); -+ cache->has_fence = cache->gen < 4; -+ cache->needs_unfenced = INTEL_INFO(i915)->unfenced_needs_alignment; -+ cache->node.allocated = false; -+ cache->rq = NULL; -+ cache->rq_size = 0; -+} -+ -+static inline void *unmask_page(unsigned long p) -+{ -+ return (void *)(uintptr_t)(p & PAGE_MASK); -+} -+ -+static inline unsigned int unmask_flags(unsigned long p) -+{ -+ return p & ~PAGE_MASK; -+} -+ -+#define KMAP 0x4 /* after CLFLUSH_FLAGS */ -+ -+static inline struct i915_ggtt *cache_to_ggtt(struct reloc_cache *cache) -+{ -+ struct drm_i915_private *i915 = -+ container_of(cache, struct i915_execbuffer, reloc_cache)->i915; -+ return &i915->ggtt; -+} -+ -+static void reloc_gpu_flush(struct reloc_cache *cache) -+{ -+ GEM_BUG_ON(cache->rq_size >= cache->rq->batch->obj->base.size / sizeof(u32)); -+ cache->rq_cmd[cache->rq_size] = MI_BATCH_BUFFER_END; -+ -+ __i915_gem_object_flush_map(cache->rq->batch->obj, 0, cache->rq_size); -+ i915_gem_object_unpin_map(cache->rq->batch->obj); -+ -+ i915_gem_chipset_flush(cache->rq->i915); -+ -+ i915_request_add(cache->rq); -+ cache->rq = NULL; -+} -+ -+static void reloc_cache_reset(struct reloc_cache *cache) -+{ -+ void *vaddr; -+ -+ if (cache->rq) -+ reloc_gpu_flush(cache); -+ -+ if (!cache->vaddr) -+ return; -+ -+ vaddr = unmask_page(cache->vaddr); -+ if (cache->vaddr & KMAP) { -+ if (cache->vaddr & CLFLUSH_AFTER) -+ mb(); -+ -+ kunmap_atomic(vaddr); -+ i915_gem_obj_finish_shmem_access((struct drm_i915_gem_object *)cache->node.mm); -+ } else { -+ wmb(); -+ io_mapping_unmap_atomic((void __iomem *)vaddr); -+ if (cache->node.allocated) { -+ struct i915_ggtt *ggtt = cache_to_ggtt(cache); -+ -+ ggtt->vm.clear_range(&ggtt->vm, -+ cache->node.start, -+ cache->node.size); -+ drm_mm_remove_node(&cache->node); -+ } else { -+ i915_vma_unpin((struct i915_vma *)cache->node.mm); -+ } -+ } -+ -+ cache->vaddr = 0; -+ cache->page = -1; -+} -+ -+static void *reloc_kmap(struct drm_i915_gem_object *obj, -+ struct reloc_cache *cache, -+ unsigned long page) -+{ -+ void *vaddr; -+ -+ if (cache->vaddr) { -+ kunmap_atomic(unmask_page(cache->vaddr)); -+ } else { -+ unsigned int flushes; -+ int err; -+ -+ err = i915_gem_obj_prepare_shmem_write(obj, &flushes); -+ if (err) -+ return ERR_PTR(err); -+ -+ BUILD_BUG_ON(KMAP & CLFLUSH_FLAGS); -+ BUILD_BUG_ON((KMAP | CLFLUSH_FLAGS) & PAGE_MASK); -+ -+ cache->vaddr = flushes | KMAP; -+ cache->node.mm = (void *)obj; -+ if (flushes) -+ mb(); -+ } -+ -+ vaddr = kmap_atomic(i915_gem_object_get_dirty_page(obj, page)); -+ cache->vaddr = unmask_flags(cache->vaddr) | (unsigned long)vaddr; -+ cache->page = page; -+ -+ return vaddr; -+} -+ -+static void *reloc_iomap(struct drm_i915_gem_object *obj, -+ struct reloc_cache *cache, -+ unsigned long page) -+{ -+ struct i915_ggtt *ggtt = cache_to_ggtt(cache); -+ unsigned long offset; -+ void *vaddr; -+ -+ if (cache->vaddr) { -+ io_mapping_unmap_atomic((void __force __iomem *) unmask_page(cache->vaddr)); -+ } else { -+ struct i915_vma *vma; -+ int err; -+ -+ if (use_cpu_reloc(cache, obj)) -+ return NULL; -+ -+ err = i915_gem_object_set_to_gtt_domain(obj, true); -+ if (err) -+ return ERR_PTR(err); -+ -+ vma = i915_gem_object_ggtt_pin(obj, NULL, 0, 0, -+ PIN_MAPPABLE | -+ PIN_NONBLOCK | -+ PIN_NONFAULT); -+ if (IS_ERR(vma)) { -+ memset(&cache->node, 0, sizeof(cache->node)); -+ err = drm_mm_insert_node_in_range -+ (&ggtt->vm.mm, &cache->node, -+ PAGE_SIZE, 0, I915_COLOR_UNEVICTABLE, -+ 0, ggtt->mappable_end, -+ DRM_MM_INSERT_LOW); -+ if (err) /* no inactive aperture space, use cpu reloc */ -+ return NULL; -+ } else { -+ err = i915_vma_put_fence(vma); -+ if (err) { -+ i915_vma_unpin(vma); -+ return ERR_PTR(err); -+ } -+ -+ cache->node.start = vma->node.start; -+ cache->node.mm = (void *)vma; -+ } -+ } -+ -+ offset = cache->node.start; -+ if (cache->node.allocated) { -+ wmb(); -+ ggtt->vm.insert_page(&ggtt->vm, -+ i915_gem_object_get_dma_address(obj, page), -+ offset, I915_CACHE_NONE, 0); -+ } else { -+ offset += page << PAGE_SHIFT; -+ } -+ -+ vaddr = (void __force *)io_mapping_map_atomic_wc(&ggtt->iomap, -+ offset); -+ cache->page = page; -+ cache->vaddr = (unsigned long)vaddr; -+ -+ return vaddr; -+} -+ -+static void *reloc_vaddr(struct drm_i915_gem_object *obj, -+ struct reloc_cache *cache, -+ unsigned long page) -+{ -+ void *vaddr; -+ -+ if (cache->page == page) { -+ vaddr = unmask_page(cache->vaddr); -+ } else { -+ vaddr = NULL; -+ if ((cache->vaddr & KMAP) == 0) -+ vaddr = reloc_iomap(obj, cache, page); -+ if (!vaddr) -+ vaddr = reloc_kmap(obj, cache, page); -+ } -+ -+ return vaddr; -+} -+ -+static void clflush_write32(u32 *addr, u32 value, unsigned int flushes) -+{ -+ if (unlikely(flushes & (CLFLUSH_BEFORE | CLFLUSH_AFTER))) { -+ if (flushes & CLFLUSH_BEFORE) { -+ clflushopt(addr); -+ mb(); -+ } -+ -+ *addr = value; -+ -+ /* -+ * Writes to the same cacheline are serialised by the CPU -+ * (including clflush). On the write path, we only require -+ * that it hits memory in an orderly fashion and place -+ * mb barriers at the start and end of the relocation phase -+ * to ensure ordering of clflush wrt to the system. -+ */ -+ if (flushes & CLFLUSH_AFTER) -+ clflushopt(addr); -+ } else -+ *addr = value; -+} -+ -+static int __reloc_gpu_alloc(struct i915_execbuffer *eb, -+ struct i915_vma *vma, -+ unsigned int len) -+{ -+ struct reloc_cache *cache = &eb->reloc_cache; -+ struct drm_i915_gem_object *obj; -+ struct i915_request *rq; -+ struct i915_vma *batch; -+ u32 *cmd; -+ int err; -+ -+ if (DBG_FORCE_RELOC == FORCE_GPU_RELOC) { -+ obj = vma->obj; -+ if (obj->cache_dirty & ~obj->cache_coherent) -+ i915_gem_clflush_object(obj, 0); -+ obj->write_domain = 0; -+ } -+ -+ GEM_BUG_ON(vma->obj->write_domain & I915_GEM_DOMAIN_CPU); -+ -+ obj = i915_gem_batch_pool_get(&eb->engine->batch_pool, PAGE_SIZE); -+ if (IS_ERR(obj)) -+ return PTR_ERR(obj); -+ -+ cmd = i915_gem_object_pin_map(obj, -+ cache->has_llc ? -+ I915_MAP_FORCE_WB : -+ I915_MAP_FORCE_WC); -+ i915_gem_object_unpin_pages(obj); -+ if (IS_ERR(cmd)) -+ return PTR_ERR(cmd); -+ -+ batch = i915_vma_instance(obj, vma->vm, NULL); -+ if (IS_ERR(batch)) { -+ err = PTR_ERR(batch); -+ goto err_unmap; -+ } -+ -+ err = i915_vma_pin(batch, 0, 0, PIN_USER | PIN_NONBLOCK); -+ if (err) -+ goto err_unmap; -+ -+ rq = i915_request_alloc(eb->engine, eb->ctx); -+ if (IS_ERR(rq)) { -+ err = PTR_ERR(rq); -+ goto err_unpin; -+ } -+ -+ err = i915_request_await_object(rq, vma->obj, true); -+ if (err) -+ goto err_request; -+ -+ err = eb->engine->emit_bb_start(rq, -+ batch->node.start, PAGE_SIZE, -+ cache->gen > 5 ? 0 : I915_DISPATCH_SECURE); -+ if (err) -+ goto err_request; -+ -+ GEM_BUG_ON(!reservation_object_test_signaled_rcu(batch->resv, true)); -+ err = i915_vma_move_to_active(batch, rq, 0); -+ if (err) -+ goto skip_request; -+ -+ err = i915_vma_move_to_active(vma, rq, EXEC_OBJECT_WRITE); -+ if (err) -+ goto skip_request; -+ -+ rq->batch = batch; -+ i915_vma_unpin(batch); -+ -+ cache->rq = rq; -+ cache->rq_cmd = cmd; -+ cache->rq_size = 0; -+ -+ /* Return with batch mapping (cmd) still pinned */ -+ return 0; -+ -+skip_request: -+ i915_request_skip(rq, err); -+err_request: -+ i915_request_add(rq); -+err_unpin: -+ i915_vma_unpin(batch); -+err_unmap: -+ i915_gem_object_unpin_map(obj); -+ return err; -+} -+ -+static u32 *reloc_gpu(struct i915_execbuffer *eb, -+ struct i915_vma *vma, -+ unsigned int len) -+{ -+ struct reloc_cache *cache = &eb->reloc_cache; -+ u32 *cmd; -+ -+ if (cache->rq_size > PAGE_SIZE/sizeof(u32) - (len + 1)) -+ reloc_gpu_flush(cache); -+ -+ if (unlikely(!cache->rq)) { -+ int err; -+ -+ /* If we need to copy for the cmdparser, we will stall anyway */ -+ if (eb_use_cmdparser(eb)) -+ return ERR_PTR(-EWOULDBLOCK); -+ -+ if (!intel_engine_can_store_dword(eb->engine)) -+ return ERR_PTR(-ENODEV); -+ -+ err = __reloc_gpu_alloc(eb, vma, len); -+ if (unlikely(err)) -+ return ERR_PTR(err); -+ } -+ -+ cmd = cache->rq_cmd + cache->rq_size; -+ cache->rq_size += len; -+ -+ return cmd; -+} -+ -+static u64 -+relocate_entry(struct i915_vma *vma, -+ const struct drm_i915_gem_relocation_entry *reloc, -+ struct i915_execbuffer *eb, -+ const struct i915_vma *target) -+{ -+ u64 offset = reloc->offset; -+ u64 target_offset = relocation_target(reloc, target); -+ bool wide = eb->reloc_cache.use_64bit_reloc; -+ void *vaddr; -+ -+ if (!eb->reloc_cache.vaddr && -+ (DBG_FORCE_RELOC == FORCE_GPU_RELOC || -+ !reservation_object_test_signaled_rcu(vma->resv, true))) { -+ const unsigned int gen = eb->reloc_cache.gen; -+ unsigned int len; -+ u32 *batch; -+ u64 addr; -+ -+ if (wide) -+ len = offset & 7 ? 8 : 5; -+ else if (gen >= 4) -+ len = 4; -+ else -+ len = 3; -+ -+ batch = reloc_gpu(eb, vma, len); -+ if (IS_ERR(batch)) -+ goto repeat; -+ -+ addr = gen8_canonical_addr(vma->node.start + offset); -+ if (wide) { -+ if (offset & 7) { -+ *batch++ = MI_STORE_DWORD_IMM_GEN4; -+ *batch++ = lower_32_bits(addr); -+ *batch++ = upper_32_bits(addr); -+ *batch++ = lower_32_bits(target_offset); -+ -+ addr = gen8_canonical_addr(addr + 4); -+ -+ *batch++ = MI_STORE_DWORD_IMM_GEN4; -+ *batch++ = lower_32_bits(addr); -+ *batch++ = upper_32_bits(addr); -+ *batch++ = upper_32_bits(target_offset); -+ } else { -+ *batch++ = (MI_STORE_DWORD_IMM_GEN4 | (1 << 21)) + 1; -+ *batch++ = lower_32_bits(addr); -+ *batch++ = upper_32_bits(addr); -+ *batch++ = lower_32_bits(target_offset); -+ *batch++ = upper_32_bits(target_offset); -+ } -+ } else if (gen >= 6) { -+ *batch++ = MI_STORE_DWORD_IMM_GEN4; -+ *batch++ = 0; -+ *batch++ = addr; -+ *batch++ = target_offset; -+ } else if (gen >= 4) { -+ *batch++ = MI_STORE_DWORD_IMM_GEN4 | MI_USE_GGTT; -+ *batch++ = 0; -+ *batch++ = addr; -+ *batch++ = target_offset; -+ } else { -+ *batch++ = MI_STORE_DWORD_IMM | MI_MEM_VIRTUAL; -+ *batch++ = addr; -+ *batch++ = target_offset; -+ } -+ -+ goto out; -+ } -+ -+repeat: -+ vaddr = reloc_vaddr(vma->obj, &eb->reloc_cache, offset >> PAGE_SHIFT); -+ if (IS_ERR(vaddr)) -+ return PTR_ERR(vaddr); -+ -+ clflush_write32(vaddr + offset_in_page(offset), -+ lower_32_bits(target_offset), -+ eb->reloc_cache.vaddr); -+ -+ if (wide) { -+ offset += sizeof(u32); -+ target_offset >>= 32; -+ wide = false; -+ goto repeat; -+ } -+ -+out: -+ return target->node.start | UPDATE; -+} -+ -+static u64 -+eb_relocate_entry(struct i915_execbuffer *eb, -+ struct i915_vma *vma, -+ const struct drm_i915_gem_relocation_entry *reloc) -+{ -+ struct i915_vma *target; -+ int err; -+ -+ /* we've already hold a reference to all valid objects */ -+ target = eb_get_vma(eb, reloc->target_handle); -+ if (unlikely(!target)) -+ return -ENOENT; -+ -+ /* Validate that the target is in a valid r/w GPU domain */ -+ if (unlikely(reloc->write_domain & (reloc->write_domain - 1))) { -+ DRM_DEBUG("reloc with multiple write domains: " -+ "target %d offset %d " -+ "read %08x write %08x", -+ reloc->target_handle, -+ (int) reloc->offset, -+ reloc->read_domains, -+ reloc->write_domain); -+ return -EINVAL; -+ } -+ if (unlikely((reloc->write_domain | reloc->read_domains) -+ & ~I915_GEM_GPU_DOMAINS)) { -+ DRM_DEBUG("reloc with read/write non-GPU domains: " -+ "target %d offset %d " -+ "read %08x write %08x", -+ reloc->target_handle, -+ (int) reloc->offset, -+ reloc->read_domains, -+ reloc->write_domain); -+ return -EINVAL; -+ } -+ -+ if (reloc->write_domain) { -+ *target->exec_flags |= EXEC_OBJECT_WRITE; -+ -+ /* -+ * Sandybridge PPGTT errata: We need a global gtt mapping -+ * for MI and pipe_control writes because the gpu doesn't -+ * properly redirect them through the ppgtt for non_secure -+ * batchbuffers. -+ */ -+ if (reloc->write_domain == I915_GEM_DOMAIN_INSTRUCTION && -+ IS_GEN(eb->i915, 6)) { -+ err = i915_vma_bind(target, target->obj->cache_level, -+ PIN_GLOBAL); -+ if (WARN_ONCE(err, -+ "Unexpected failure to bind target VMA!")) -+ return err; -+ } -+ } -+ -+ /* -+ * If the relocation already has the right value in it, no -+ * more work needs to be done. -+ */ -+ if (!DBG_FORCE_RELOC && -+ gen8_canonical_addr(target->node.start) == reloc->presumed_offset) -+ return 0; -+ -+ /* Check that the relocation address is valid... */ -+ if (unlikely(reloc->offset > -+ vma->size - (eb->reloc_cache.use_64bit_reloc ? 8 : 4))) { -+ DRM_DEBUG("Relocation beyond object bounds: " -+ "target %d offset %d size %d.\n", -+ reloc->target_handle, -+ (int)reloc->offset, -+ (int)vma->size); -+ return -EINVAL; -+ } -+ if (unlikely(reloc->offset & 3)) { -+ DRM_DEBUG("Relocation not 4-byte aligned: " -+ "target %d offset %d.\n", -+ reloc->target_handle, -+ (int)reloc->offset); -+ return -EINVAL; -+ } -+ -+ /* -+ * If we write into the object, we need to force the synchronisation -+ * barrier, either with an asynchronous clflush or if we executed the -+ * patching using the GPU (though that should be serialised by the -+ * timeline). To be completely sure, and since we are required to -+ * do relocations we are already stalling, disable the user's opt -+ * out of our synchronisation. -+ */ -+ *vma->exec_flags &= ~EXEC_OBJECT_ASYNC; -+ -+ /* and update the user's relocation entry */ -+ return relocate_entry(vma, reloc, eb, target); -+} -+ -+static int eb_relocate_vma(struct i915_execbuffer *eb, struct i915_vma *vma) -+{ -+#define N_RELOC(x) ((x) / sizeof(struct drm_i915_gem_relocation_entry)) -+ struct drm_i915_gem_relocation_entry stack[N_RELOC(512)]; -+ struct drm_i915_gem_relocation_entry __user *urelocs; -+ const struct drm_i915_gem_exec_object2 *entry = exec_entry(eb, vma); -+ unsigned int remain; -+ -+ urelocs = u64_to_user_ptr(entry->relocs_ptr); -+ remain = entry->relocation_count; -+ if (unlikely(remain > N_RELOC(ULONG_MAX))) -+ return -EINVAL; -+ -+ /* -+ * We must check that the entire relocation array is safe -+ * to read. However, if the array is not writable the user loses -+ * the updated relocation values. -+ */ -+ if (unlikely(!access_ok(urelocs, remain*sizeof(*urelocs)))) -+ return -EFAULT; -+ -+ do { -+ struct drm_i915_gem_relocation_entry *r = stack; -+ unsigned int count = -+ min_t(unsigned int, remain, ARRAY_SIZE(stack)); -+ unsigned int copied; -+ -+ /* -+ * This is the fast path and we cannot handle a pagefault -+ * whilst holding the struct mutex lest the user pass in the -+ * relocations contained within a mmaped bo. For in such a case -+ * we, the page fault handler would call i915_gem_fault() and -+ * we would try to acquire the struct mutex again. Obviously -+ * this is bad and so lockdep complains vehemently. -+ */ -+ pagefault_disable(); -+ copied = __copy_from_user_inatomic(r, urelocs, count * sizeof(r[0])); -+ pagefault_enable(); -+ if (unlikely(copied)) { -+ remain = -EFAULT; -+ goto out; -+ } -+ -+ remain -= count; -+ do { -+ u64 offset = eb_relocate_entry(eb, vma, r); -+ -+ if (likely(offset == 0)) { -+ } else if ((s64)offset < 0) { -+ remain = (int)offset; -+ goto out; -+ } else { -+ /* -+ * Note that reporting an error now -+ * leaves everything in an inconsistent -+ * state as we have *already* changed -+ * the relocation value inside the -+ * object. As we have not changed the -+ * reloc.presumed_offset or will not -+ * change the execobject.offset, on the -+ * call we may not rewrite the value -+ * inside the object, leaving it -+ * dangling and causing a GPU hang. Unless -+ * userspace dynamically rebuilds the -+ * relocations on each execbuf rather than -+ * presume a static tree. -+ * -+ * We did previously check if the relocations -+ * were writable (access_ok), an error now -+ * would be a strange race with mprotect, -+ * having already demonstrated that we -+ * can read from this userspace address. -+ */ -+ offset = gen8_canonical_addr(offset & ~UPDATE); -+ if (unlikely(__put_user(offset, &urelocs[r-stack].presumed_offset))) { -+ remain = -EFAULT; -+ goto out; -+ } -+ } -+ } while (r++, --count); -+ urelocs += ARRAY_SIZE(stack); -+ } while (remain); -+out: -+ reloc_cache_reset(&eb->reloc_cache); -+ return remain; -+} -+ -+static int -+eb_relocate_vma_slow(struct i915_execbuffer *eb, struct i915_vma *vma) -+{ -+ const struct drm_i915_gem_exec_object2 *entry = exec_entry(eb, vma); -+ struct drm_i915_gem_relocation_entry *relocs = -+ u64_to_ptr(typeof(*relocs), entry->relocs_ptr); -+ unsigned int i; -+ int err; -+ -+ for (i = 0; i < entry->relocation_count; i++) { -+ u64 offset = eb_relocate_entry(eb, vma, &relocs[i]); -+ -+ if ((s64)offset < 0) { -+ err = (int)offset; -+ goto err; -+ } -+ } -+ err = 0; -+err: -+ reloc_cache_reset(&eb->reloc_cache); -+ return err; -+} -+ -+static int check_relocations(const struct drm_i915_gem_exec_object2 *entry) -+{ -+ const char __user *addr, *end; -+ unsigned long size; -+ char __maybe_unused c; -+ -+ size = entry->relocation_count; -+ if (size == 0) -+ return 0; -+ -+ if (size > N_RELOC(ULONG_MAX)) -+ return -EINVAL; -+ -+ addr = u64_to_user_ptr(entry->relocs_ptr); -+ size *= sizeof(struct drm_i915_gem_relocation_entry); -+ if (!access_ok(addr, size)) -+ return -EFAULT; -+ -+ end = addr + size; -+ for (; addr < end; addr += PAGE_SIZE) { -+ int err = __get_user(c, addr); -+ if (err) -+ return err; -+ } -+ return __get_user(c, end - 1); -+} -+ -+static int eb_copy_relocations(const struct i915_execbuffer *eb) -+{ -+ const unsigned int count = eb->buffer_count; -+ unsigned int i; -+ int err; -+ -+ for (i = 0; i < count; i++) { -+ const unsigned int nreloc = eb->exec[i].relocation_count; -+ struct drm_i915_gem_relocation_entry __user *urelocs; -+ struct drm_i915_gem_relocation_entry *relocs; -+ unsigned long size; -+ unsigned long copied; -+ -+ if (nreloc == 0) -+ continue; -+ -+ err = check_relocations(&eb->exec[i]); -+ if (err) -+ goto err; -+ -+ urelocs = u64_to_user_ptr(eb->exec[i].relocs_ptr); -+ size = nreloc * sizeof(*relocs); -+ -+ relocs = kvmalloc_array(size, 1, GFP_KERNEL); -+ if (!relocs) { -+ err = -ENOMEM; -+ goto err; -+ } -+ -+ /* copy_from_user is limited to < 4GiB */ -+ copied = 0; -+ do { -+ unsigned int len = -+ min_t(u64, BIT_ULL(31), size - copied); -+ -+ if (__copy_from_user((char *)relocs + copied, -+ (char __user *)urelocs + copied, -+ len)) { -+end_user: -+ user_access_end(); -+end: -+ kvfree(relocs); -+ err = -EFAULT; -+ goto err; -+ } -+ -+ copied += len; -+ } while (copied < size); -+ -+ /* -+ * As we do not update the known relocation offsets after -+ * relocating (due to the complexities in lock handling), -+ * we need to mark them as invalid now so that we force the -+ * relocation processing next time. Just in case the target -+ * object is evicted and then rebound into its old -+ * presumed_offset before the next execbuffer - if that -+ * happened we would make the mistake of assuming that the -+ * relocations were valid. -+ */ -+ if (!user_access_begin(urelocs, size)) -+ goto end; -+ -+ for (copied = 0; copied < nreloc; copied++) -+ unsafe_put_user(-1, -+ &urelocs[copied].presumed_offset, -+ end_user); -+ user_access_end(); -+ -+ eb->exec[i].relocs_ptr = (uintptr_t)relocs; -+ } -+ -+ return 0; -+ -+err: -+ while (i--) { -+ struct drm_i915_gem_relocation_entry *relocs = -+ u64_to_ptr(typeof(*relocs), eb->exec[i].relocs_ptr); -+ if (eb->exec[i].relocation_count) -+ kvfree(relocs); -+ } -+ return err; -+} -+ -+static int eb_prefault_relocations(const struct i915_execbuffer *eb) -+{ -+ const unsigned int count = eb->buffer_count; -+ unsigned int i; -+ -+ if (unlikely(i915_modparams.prefault_disable)) -+ return 0; -+ -+ for (i = 0; i < count; i++) { -+ int err; -+ -+ err = check_relocations(&eb->exec[i]); -+ if (err) -+ return err; -+ } -+ -+ return 0; -+} -+ -+static noinline int eb_relocate_slow(struct i915_execbuffer *eb) -+{ -+ struct drm_device *dev = &eb->i915->drm; -+ bool have_copy = false; -+ struct i915_vma *vma; -+ int err = 0; -+ -+repeat: -+ if (signal_pending(current)) { -+ err = -ERESTARTSYS; -+ goto out; -+ } -+ -+ /* We may process another execbuffer during the unlock... */ -+ eb_reset_vmas(eb); -+ mutex_unlock(&dev->struct_mutex); -+ -+ /* -+ * We take 3 passes through the slowpatch. -+ * -+ * 1 - we try to just prefault all the user relocation entries and -+ * then attempt to reuse the atomic pagefault disabled fast path again. -+ * -+ * 2 - we copy the user entries to a local buffer here outside of the -+ * local and allow ourselves to wait upon any rendering before -+ * relocations -+ * -+ * 3 - we already have a local copy of the relocation entries, but -+ * were interrupted (EAGAIN) whilst waiting for the objects, try again. -+ */ -+ if (!err) { -+ err = eb_prefault_relocations(eb); -+ } else if (!have_copy) { -+ err = eb_copy_relocations(eb); -+ have_copy = err == 0; -+ } else { -+ cond_resched(); -+ err = 0; -+ } -+ if (err) { -+ mutex_lock(&dev->struct_mutex); -+ goto out; -+ } -+ -+ /* A frequent cause for EAGAIN are currently unavailable client pages */ -+ flush_workqueue(eb->i915->mm.userptr_wq); -+ -+ err = i915_mutex_lock_interruptible(dev); -+ if (err) { -+ mutex_lock(&dev->struct_mutex); -+ goto out; -+ } -+ -+ /* reacquire the objects */ -+ err = eb_lookup_vmas(eb); -+ if (err) -+ goto err; -+ -+ GEM_BUG_ON(!eb->batch); -+ -+ list_for_each_entry(vma, &eb->relocs, reloc_link) { -+ if (!have_copy) { -+ pagefault_disable(); -+ err = eb_relocate_vma(eb, vma); -+ pagefault_enable(); -+ if (err) -+ goto repeat; -+ } else { -+ err = eb_relocate_vma_slow(eb, vma); -+ if (err) -+ goto err; -+ } -+ } -+ -+ /* -+ * Leave the user relocations as are, this is the painfully slow path, -+ * and we want to avoid the complication of dropping the lock whilst -+ * having buffers reserved in the aperture and so causing spurious -+ * ENOSPC for random operations. -+ */ -+ -+err: -+ if (err == -EAGAIN) -+ goto repeat; -+ -+out: -+ if (have_copy) { -+ const unsigned int count = eb->buffer_count; -+ unsigned int i; -+ -+ for (i = 0; i < count; i++) { -+ const struct drm_i915_gem_exec_object2 *entry = -+ &eb->exec[i]; -+ struct drm_i915_gem_relocation_entry *relocs; -+ -+ if (!entry->relocation_count) -+ continue; -+ -+ relocs = u64_to_ptr(typeof(*relocs), entry->relocs_ptr); -+ kvfree(relocs); -+ } -+ } -+ -+ return err; -+} -+ -+static int eb_relocate(struct i915_execbuffer *eb) -+{ -+ if (eb_lookup_vmas(eb)) -+ goto slow; -+ -+ /* The objects are in their final locations, apply the relocations. */ -+ if (eb->args->flags & __EXEC_HAS_RELOC) { -+ struct i915_vma *vma; -+ -+ list_for_each_entry(vma, &eb->relocs, reloc_link) { -+ if (eb_relocate_vma(eb, vma)) -+ goto slow; -+ } -+ } -+ -+ return 0; -+ -+slow: -+ return eb_relocate_slow(eb); -+} -+ -+static int eb_move_to_gpu(struct i915_execbuffer *eb) -+{ -+ const unsigned int count = eb->buffer_count; -+ unsigned int i; -+ int err; -+ -+ for (i = 0; i < count; i++) { -+ unsigned int flags = eb->flags[i]; -+ struct i915_vma *vma = eb->vma[i]; -+ struct drm_i915_gem_object *obj = vma->obj; -+ -+ if (flags & EXEC_OBJECT_CAPTURE) { -+ struct i915_capture_list *capture; -+ -+ capture = kmalloc(sizeof(*capture), GFP_KERNEL); -+ if (unlikely(!capture)) -+ return -ENOMEM; -+ -+ capture->next = eb->request->capture_list; -+ capture->vma = eb->vma[i]; -+ eb->request->capture_list = capture; -+ } -+ -+ /* -+ * If the GPU is not _reading_ through the CPU cache, we need -+ * to make sure that any writes (both previous GPU writes from -+ * before a change in snooping levels and normal CPU writes) -+ * caught in that cache are flushed to main memory. -+ * -+ * We want to say -+ * obj->cache_dirty && -+ * !(obj->cache_coherent & I915_BO_CACHE_COHERENT_FOR_READ) -+ * but gcc's optimiser doesn't handle that as well and emits -+ * two jumps instead of one. Maybe one day... -+ */ -+ if (unlikely(obj->cache_dirty & ~obj->cache_coherent)) { -+ if (i915_gem_clflush_object(obj, 0)) -+ flags &= ~EXEC_OBJECT_ASYNC; -+ } -+ -+ if (flags & EXEC_OBJECT_ASYNC) -+ continue; -+ -+ err = i915_request_await_object -+ (eb->request, obj, flags & EXEC_OBJECT_WRITE); -+ if (err) -+ return err; -+ } -+ -+ for (i = 0; i < count; i++) { -+ unsigned int flags = eb->flags[i]; -+ struct i915_vma *vma = eb->vma[i]; -+ -+ err = i915_vma_move_to_active(vma, eb->request, flags); -+ if (unlikely(err)) { -+ i915_request_skip(eb->request, err); -+ return err; -+ } -+ -+ __eb_unreserve_vma(vma, flags); -+ vma->exec_flags = NULL; -+ -+ if (unlikely(flags & __EXEC_OBJECT_HAS_REF)) -+ i915_vma_put(vma); -+ } -+ eb->exec = NULL; -+ -+ /* Unconditionally flush any chipset caches (for streaming writes). */ -+ i915_gem_chipset_flush(eb->i915); -+ -+ return 0; -+} -+ -+static bool i915_gem_check_execbuffer(struct drm_i915_gem_execbuffer2 *exec) -+{ -+ if (exec->flags & __I915_EXEC_ILLEGAL_FLAGS) -+ return false; -+ -+ /* Kernel clipping was a DRI1 misfeature */ -+ if (!(exec->flags & I915_EXEC_FENCE_ARRAY)) { -+ if (exec->num_cliprects || exec->cliprects_ptr) -+ return false; -+ } -+ -+ if (exec->DR4 == 0xffffffff) { -+ DRM_DEBUG("UXA submitting garbage DR4, fixing up\n"); -+ exec->DR4 = 0; -+ } -+ if (exec->DR1 || exec->DR4) -+ return false; -+ -+ if ((exec->batch_start_offset | exec->batch_len) & 0x7) -+ return false; -+ -+ return true; -+} -+ -+static int i915_reset_gen7_sol_offsets(struct i915_request *rq) -+{ -+ u32 *cs; -+ int i; -+ -+ if (!IS_GEN(rq->i915, 7) || rq->engine->id != RCS0) { -+ DRM_DEBUG("sol reset is gen7/rcs only\n"); -+ return -EINVAL; -+ } -+ -+ cs = intel_ring_begin(rq, 4 * 2 + 2); -+ if (IS_ERR(cs)) -+ return PTR_ERR(cs); -+ -+ *cs++ = MI_LOAD_REGISTER_IMM(4); -+ for (i = 0; i < 4; i++) { -+ *cs++ = i915_mmio_reg_offset(GEN7_SO_WRITE_OFFSET(i)); -+ *cs++ = 0; -+ } -+ *cs++ = MI_NOOP; -+ intel_ring_advance(rq, cs); -+ -+ return 0; -+} -+ -+static struct i915_vma *eb_parse(struct i915_execbuffer *eb, bool is_master) -+{ -+ struct drm_i915_gem_object *shadow_batch_obj; -+ struct i915_vma *vma; -+ int err; -+ -+ shadow_batch_obj = i915_gem_batch_pool_get(&eb->engine->batch_pool, -+ PAGE_ALIGN(eb->batch_len)); -+ if (IS_ERR(shadow_batch_obj)) -+ return ERR_CAST(shadow_batch_obj); -+ -+ err = intel_engine_cmd_parser(eb->engine, -+ eb->batch->obj, -+ shadow_batch_obj, -+ eb->batch_start_offset, -+ eb->batch_len, -+ is_master); -+ if (err) { -+ if (err == -EACCES) /* unhandled chained batch */ -+ vma = NULL; -+ else -+ vma = ERR_PTR(err); -+ goto out; -+ } -+ -+ vma = i915_gem_object_ggtt_pin(shadow_batch_obj, NULL, 0, 0, 0); -+ if (IS_ERR(vma)) -+ goto out; -+ -+ eb->vma[eb->buffer_count] = i915_vma_get(vma); -+ eb->flags[eb->buffer_count] = -+ __EXEC_OBJECT_HAS_PIN | __EXEC_OBJECT_HAS_REF; -+ vma->exec_flags = &eb->flags[eb->buffer_count]; -+ eb->buffer_count++; -+ -+out: -+ i915_gem_object_unpin_pages(shadow_batch_obj); -+ return vma; -+} -+ -+static void -+add_to_client(struct i915_request *rq, struct drm_file *file) -+{ -+ rq->file_priv = file->driver_priv; -+ list_add_tail(&rq->client_link, &rq->file_priv->mm.request_list); -+} -+ -+static int eb_submit(struct i915_execbuffer *eb) -+{ -+ int err; -+ -+ err = eb_move_to_gpu(eb); -+ if (err) -+ return err; -+ -+ if (eb->args->flags & I915_EXEC_GEN7_SOL_RESET) { -+ err = i915_reset_gen7_sol_offsets(eb->request); -+ if (err) -+ return err; -+ } -+ -+ /* -+ * After we completed waiting for other engines (using HW semaphores) -+ * then we can signal that this request/batch is ready to run. This -+ * allows us to determine if the batch is still waiting on the GPU -+ * or actually running by checking the breadcrumb. -+ */ -+ if (eb->engine->emit_init_breadcrumb) { -+ err = eb->engine->emit_init_breadcrumb(eb->request); -+ if (err) -+ return err; -+ } -+ -+ err = eb->engine->emit_bb_start(eb->request, -+ eb->batch->node.start + -+ eb->batch_start_offset, -+ eb->batch_len, -+ eb->batch_flags); -+ if (err) -+ return err; -+ -+ return 0; -+} -+ -+/* -+ * Find one BSD ring to dispatch the corresponding BSD command. -+ * The engine index is returned. -+ */ -+static unsigned int -+gen8_dispatch_bsd_engine(struct drm_i915_private *dev_priv, -+ struct drm_file *file) -+{ -+ struct drm_i915_file_private *file_priv = file->driver_priv; -+ -+ /* Check whether the file_priv has already selected one ring. */ -+ if ((int)file_priv->bsd_engine < 0) -+ file_priv->bsd_engine = atomic_fetch_xor(1, -+ &dev_priv->mm.bsd_engine_dispatch_index); -+ -+ return file_priv->bsd_engine; -+} -+ -+#define I915_USER_RINGS (4) -+ -+static const enum intel_engine_id user_ring_map[I915_USER_RINGS + 1] = { -+ [I915_EXEC_DEFAULT] = RCS0, -+ [I915_EXEC_RENDER] = RCS0, -+ [I915_EXEC_BLT] = BCS0, -+ [I915_EXEC_BSD] = VCS0, -+ [I915_EXEC_VEBOX] = VECS0 -+}; -+ -+static struct intel_engine_cs * -+eb_select_engine(struct drm_i915_private *dev_priv, -+ struct drm_file *file, -+ struct drm_i915_gem_execbuffer2 *args) -+{ -+ unsigned int user_ring_id = args->flags & I915_EXEC_RING_MASK; -+ struct intel_engine_cs *engine; -+ -+ if (user_ring_id > I915_USER_RINGS) { -+ DRM_DEBUG("execbuf with unknown ring: %u\n", user_ring_id); -+ return NULL; -+ } -+ -+ if ((user_ring_id != I915_EXEC_BSD) && -+ ((args->flags & I915_EXEC_BSD_MASK) != 0)) { -+ DRM_DEBUG("execbuf with non bsd ring but with invalid " -+ "bsd dispatch flags: %d\n", (int)(args->flags)); -+ return NULL; -+ } -+ -+ if (user_ring_id == I915_EXEC_BSD && HAS_ENGINE(dev_priv, VCS1)) { -+ unsigned int bsd_idx = args->flags & I915_EXEC_BSD_MASK; -+ -+ if (bsd_idx == I915_EXEC_BSD_DEFAULT) { -+ bsd_idx = gen8_dispatch_bsd_engine(dev_priv, file); -+ } else if (bsd_idx >= I915_EXEC_BSD_RING1 && -+ bsd_idx <= I915_EXEC_BSD_RING2) { -+ bsd_idx >>= I915_EXEC_BSD_SHIFT; -+ bsd_idx--; -+ } else { -+ DRM_DEBUG("execbuf with unknown bsd ring: %u\n", -+ bsd_idx); -+ return NULL; -+ } -+ -+ engine = dev_priv->engine[_VCS(bsd_idx)]; -+ } else { -+ engine = dev_priv->engine[user_ring_map[user_ring_id]]; -+ } -+ -+ if (!engine) { -+ DRM_DEBUG("execbuf with invalid ring: %u\n", user_ring_id); -+ return NULL; -+ } -+ -+ return engine; -+} -+ -+static void -+__free_fence_array(struct drm_syncobj **fences, unsigned int n) -+{ -+ while (n--) -+ drm_syncobj_put(ptr_mask_bits(fences[n], 2)); -+ kvfree(fences); -+} -+ -+static struct drm_syncobj ** -+get_fence_array(struct drm_i915_gem_execbuffer2 *args, -+ struct drm_file *file) -+{ -+ const unsigned long nfences = args->num_cliprects; -+ struct drm_i915_gem_exec_fence __user *user; -+ struct drm_syncobj **fences; -+ unsigned long n; -+ int err; -+ -+ if (!(args->flags & I915_EXEC_FENCE_ARRAY)) -+ return NULL; -+ -+ /* Check multiplication overflow for access_ok() and kvmalloc_array() */ -+ BUILD_BUG_ON(sizeof(size_t) > sizeof(unsigned long)); -+ if (nfences > min_t(unsigned long, -+ ULONG_MAX / sizeof(*user), -+ SIZE_MAX / sizeof(*fences))) -+ return ERR_PTR(-EINVAL); -+ -+ user = u64_to_user_ptr(args->cliprects_ptr); -+ if (!access_ok(user, nfences * sizeof(*user))) -+ return ERR_PTR(-EFAULT); -+ -+ fences = kvmalloc_array(nfences, sizeof(*fences), -+ __GFP_NOWARN | GFP_KERNEL); -+ if (!fences) -+ return ERR_PTR(-ENOMEM); -+ -+ for (n = 0; n < nfences; n++) { -+ struct drm_i915_gem_exec_fence fence; -+ struct drm_syncobj *syncobj; -+ -+ if (__copy_from_user(&fence, user++, sizeof(fence))) { -+ err = -EFAULT; -+ goto err; -+ } -+ -+ if (fence.flags & __I915_EXEC_FENCE_UNKNOWN_FLAGS) { -+ err = -EINVAL; -+ goto err; -+ } -+ -+ syncobj = drm_syncobj_find(file, fence.handle); -+ if (!syncobj) { -+ DRM_DEBUG("Invalid syncobj handle provided\n"); -+ err = -ENOENT; -+ goto err; -+ } -+ -+ BUILD_BUG_ON(~(ARCH_KMALLOC_MINALIGN - 1) & -+ ~__I915_EXEC_FENCE_UNKNOWN_FLAGS); -+ -+ fences[n] = ptr_pack_bits(syncobj, fence.flags, 2); -+ } -+ -+ return fences; -+ -+err: -+ __free_fence_array(fences, n); -+ return ERR_PTR(err); -+} -+ -+static void -+put_fence_array(struct drm_i915_gem_execbuffer2 *args, -+ struct drm_syncobj **fences) -+{ -+ if (fences) -+ __free_fence_array(fences, args->num_cliprects); -+} -+ -+static int -+await_fence_array(struct i915_execbuffer *eb, -+ struct drm_syncobj **fences) -+{ -+ const unsigned int nfences = eb->args->num_cliprects; -+ unsigned int n; -+ int err; -+ -+ for (n = 0; n < nfences; n++) { -+ struct drm_syncobj *syncobj; -+ struct dma_fence *fence; -+ unsigned int flags; -+ -+ syncobj = ptr_unpack_bits(fences[n], &flags, 2); -+ if (!(flags & I915_EXEC_FENCE_WAIT)) -+ continue; -+ -+ fence = drm_syncobj_fence_get(syncobj); -+ if (!fence) -+ return -EINVAL; -+ -+ err = i915_request_await_dma_fence(eb->request, fence); -+ dma_fence_put(fence); -+ if (err < 0) -+ return err; -+ } -+ -+ return 0; -+} -+ -+static void -+signal_fence_array(struct i915_execbuffer *eb, -+ struct drm_syncobj **fences) -+{ -+ const unsigned int nfences = eb->args->num_cliprects; -+ struct dma_fence * const fence = &eb->request->fence; -+ unsigned int n; -+ -+ for (n = 0; n < nfences; n++) { -+ struct drm_syncobj *syncobj; -+ unsigned int flags; -+ -+ syncobj = ptr_unpack_bits(fences[n], &flags, 2); -+ if (!(flags & I915_EXEC_FENCE_SIGNAL)) -+ continue; -+ -+ drm_syncobj_replace_fence(syncobj, fence); -+ } -+} -+ -+static int -+i915_gem_do_execbuffer(struct drm_device *dev, -+ struct drm_file *file, -+ struct drm_i915_gem_execbuffer2 *args, -+ struct drm_i915_gem_exec_object2 *exec, -+ struct drm_syncobj **fences) -+{ -+ struct i915_execbuffer eb; -+ struct dma_fence *in_fence = NULL; -+ struct sync_file *out_fence = NULL; -+ intel_wakeref_t wakeref; -+ int out_fence_fd = -1; -+ int err; -+ -+ BUILD_BUG_ON(__EXEC_INTERNAL_FLAGS & ~__I915_EXEC_ILLEGAL_FLAGS); -+ BUILD_BUG_ON(__EXEC_OBJECT_INTERNAL_FLAGS & -+ ~__EXEC_OBJECT_UNKNOWN_FLAGS); -+ -+ eb.i915 = to_i915(dev); -+ eb.file = file; -+ eb.args = args; -+ if (DBG_FORCE_RELOC || !(args->flags & I915_EXEC_NO_RELOC)) -+ args->flags |= __EXEC_HAS_RELOC; -+ -+ eb.exec = exec; -+ eb.vma = (struct i915_vma **)(exec + args->buffer_count + 1); -+ eb.vma[0] = NULL; -+ eb.flags = (unsigned int *)(eb.vma + args->buffer_count + 1); -+ -+ eb.invalid_flags = __EXEC_OBJECT_UNKNOWN_FLAGS; -+ reloc_cache_init(&eb.reloc_cache, eb.i915); -+ -+ eb.buffer_count = args->buffer_count; -+ eb.batch_start_offset = args->batch_start_offset; -+ eb.batch_len = args->batch_len; -+ -+ eb.batch_flags = 0; -+ if (args->flags & I915_EXEC_SECURE) { -+ if (!drm_is_current_master(file) || !capable(CAP_SYS_ADMIN)) -+ return -EPERM; -+ -+ eb.batch_flags |= I915_DISPATCH_SECURE; -+ } -+ if (args->flags & I915_EXEC_IS_PINNED) -+ eb.batch_flags |= I915_DISPATCH_PINNED; -+ -+ if (args->flags & I915_EXEC_FENCE_IN) { -+ in_fence = sync_file_get_fence(lower_32_bits(args->rsvd2)); -+ if (!in_fence) -+ return -EINVAL; -+ } -+ -+ if (args->flags & I915_EXEC_FENCE_OUT) { -+ out_fence_fd = get_unused_fd_flags(O_CLOEXEC); -+ if (out_fence_fd < 0) { -+ err = out_fence_fd; -+ goto err_in_fence; -+ } -+ } -+ -+ err = eb_create(&eb); -+ if (err) -+ goto err_out_fence; -+ -+ GEM_BUG_ON(!eb.lut_size); -+ -+ err = eb_select_context(&eb); -+ if (unlikely(err)) -+ goto err_destroy; -+ -+ eb.engine = eb_select_engine(eb.i915, file, args); -+ if (!eb.engine) { -+ err = -EINVAL; -+ goto err_engine; -+ } -+ -+ /* -+ * Take a local wakeref for preparing to dispatch the execbuf as -+ * we expect to access the hardware fairly frequently in the -+ * process. Upon first dispatch, we acquire another prolonged -+ * wakeref that we hold until the GPU has been idle for at least -+ * 100ms. -+ */ -+ wakeref = intel_runtime_pm_get(eb.i915); -+ -+ err = i915_mutex_lock_interruptible(dev); -+ if (err) -+ goto err_rpm; -+ -+ err = eb_wait_for_ring(&eb); /* may temporarily drop struct_mutex */ -+ if (unlikely(err)) -+ goto err_unlock; -+ -+ err = eb_relocate(&eb); -+ if (err) { -+ /* -+ * If the user expects the execobject.offset and -+ * reloc.presumed_offset to be an exact match, -+ * as for using NO_RELOC, then we cannot update -+ * the execobject.offset until we have completed -+ * relocation. -+ */ -+ args->flags &= ~__EXEC_HAS_RELOC; -+ goto err_vma; -+ } -+ -+ if (unlikely(*eb.batch->exec_flags & EXEC_OBJECT_WRITE)) { -+ DRM_DEBUG("Attempting to use self-modifying batch buffer\n"); -+ err = -EINVAL; -+ goto err_vma; -+ } -+ if (eb.batch_start_offset > eb.batch->size || -+ eb.batch_len > eb.batch->size - eb.batch_start_offset) { -+ DRM_DEBUG("Attempting to use out-of-bounds batch\n"); -+ err = -EINVAL; -+ goto err_vma; -+ } -+ -+ if (eb_use_cmdparser(&eb)) { -+ struct i915_vma *vma; -+ -+ vma = eb_parse(&eb, drm_is_current_master(file)); -+ if (IS_ERR(vma)) { -+ err = PTR_ERR(vma); -+ goto err_vma; -+ } -+ -+ if (vma) { -+ /* -+ * Batch parsed and accepted: -+ * -+ * Set the DISPATCH_SECURE bit to remove the NON_SECURE -+ * bit from MI_BATCH_BUFFER_START commands issued in -+ * the dispatch_execbuffer implementations. We -+ * specifically don't want that set on batches the -+ * command parser has accepted. -+ */ -+ eb.batch_flags |= I915_DISPATCH_SECURE; -+ eb.batch_start_offset = 0; -+ eb.batch = vma; -+ } -+ } -+ -+ if (eb.batch_len == 0) -+ eb.batch_len = eb.batch->size - eb.batch_start_offset; -+ -+ /* -+ * snb/ivb/vlv conflate the "batch in ppgtt" bit with the "non-secure -+ * batch" bit. Hence we need to pin secure batches into the global gtt. -+ * hsw should have this fixed, but bdw mucks it up again. */ -+ if (eb.batch_flags & I915_DISPATCH_SECURE) { -+ struct i915_vma *vma; -+ -+ /* -+ * So on first glance it looks freaky that we pin the batch here -+ * outside of the reservation loop. But: -+ * - The batch is already pinned into the relevant ppgtt, so we -+ * already have the backing storage fully allocated. -+ * - No other BO uses the global gtt (well contexts, but meh), -+ * so we don't really have issues with multiple objects not -+ * fitting due to fragmentation. -+ * So this is actually safe. -+ */ -+ vma = i915_gem_object_ggtt_pin(eb.batch->obj, NULL, 0, 0, 0); -+ if (IS_ERR(vma)) { -+ err = PTR_ERR(vma); -+ goto err_vma; -+ } -+ -+ eb.batch = vma; -+ } -+ -+ /* All GPU relocation batches must be submitted prior to the user rq */ -+ GEM_BUG_ON(eb.reloc_cache.rq); -+ -+ /* Allocate a request for this batch buffer nice and early. */ -+ eb.request = i915_request_alloc(eb.engine, eb.ctx); -+ if (IS_ERR(eb.request)) { -+ err = PTR_ERR(eb.request); -+ goto err_batch_unpin; -+ } -+ -+ if (in_fence) { -+ err = i915_request_await_dma_fence(eb.request, in_fence); -+ if (err < 0) -+ goto err_request; -+ } -+ -+ if (fences) { -+ err = await_fence_array(&eb, fences); -+ if (err) -+ goto err_request; -+ } -+ -+ if (out_fence_fd != -1) { -+ out_fence = sync_file_create(&eb.request->fence); -+ if (!out_fence) { -+ err = -ENOMEM; -+ goto err_request; -+ } -+ } -+ -+ /* -+ * Whilst this request exists, batch_obj will be on the -+ * active_list, and so will hold the active reference. Only when this -+ * request is retired will the the batch_obj be moved onto the -+ * inactive_list and lose its active reference. Hence we do not need -+ * to explicitly hold another reference here. -+ */ -+ eb.request->batch = eb.batch; -+ -+ trace_i915_request_queue(eb.request, eb.batch_flags); -+ err = eb_submit(&eb); -+err_request: -+ i915_request_add(eb.request); -+ add_to_client(eb.request, file); -+ -+ if (fences) -+ signal_fence_array(&eb, fences); -+ -+ if (out_fence) { -+ if (err == 0) { -+ fd_install(out_fence_fd, out_fence->file); -+ args->rsvd2 &= GENMASK_ULL(31, 0); /* keep in-fence */ -+ args->rsvd2 |= (u64)out_fence_fd << 32; -+ out_fence_fd = -1; -+ } else { -+ fput(out_fence->file); -+ } -+ } -+ -+err_batch_unpin: -+ if (eb.batch_flags & I915_DISPATCH_SECURE) -+ i915_vma_unpin(eb.batch); -+err_vma: -+ if (eb.exec) -+ eb_release_vmas(&eb); -+err_unlock: -+ mutex_unlock(&dev->struct_mutex); -+err_rpm: -+ intel_runtime_pm_put(eb.i915, wakeref); -+err_engine: -+ i915_gem_context_put(eb.ctx); -+err_destroy: -+ eb_destroy(&eb); -+err_out_fence: -+ if (out_fence_fd != -1) -+ put_unused_fd(out_fence_fd); -+err_in_fence: -+ dma_fence_put(in_fence); -+ return err; -+} -+ -+static size_t eb_element_size(void) -+{ -+ return (sizeof(struct drm_i915_gem_exec_object2) + -+ sizeof(struct i915_vma *) + -+ sizeof(unsigned int)); -+} -+ -+static bool check_buffer_count(size_t count) -+{ -+ const size_t sz = eb_element_size(); -+ -+ /* -+ * When using LUT_HANDLE, we impose a limit of INT_MAX for the lookup -+ * array size (see eb_create()). Otherwise, we can accept an array as -+ * large as can be addressed (though use large arrays at your peril)! -+ */ -+ -+ return !(count < 1 || count > INT_MAX || count > SIZE_MAX / sz - 1); -+} -+ -+/* -+ * Legacy execbuffer just creates an exec2 list from the original exec object -+ * list array and passes it to the real function. -+ */ -+int -+i915_gem_execbuffer_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_gem_execbuffer *args = data; -+ struct drm_i915_gem_execbuffer2 exec2; -+ struct drm_i915_gem_exec_object *exec_list = NULL; -+ struct drm_i915_gem_exec_object2 *exec2_list = NULL; -+ const size_t count = args->buffer_count; -+ unsigned int i; -+ int err; -+ -+ if (!check_buffer_count(count)) { -+ DRM_DEBUG("execbuf2 with %zd buffers\n", count); -+ return -EINVAL; -+ } -+ -+ exec2.buffers_ptr = args->buffers_ptr; -+ exec2.buffer_count = args->buffer_count; -+ exec2.batch_start_offset = args->batch_start_offset; -+ exec2.batch_len = args->batch_len; -+ exec2.DR1 = args->DR1; -+ exec2.DR4 = args->DR4; -+ exec2.num_cliprects = args->num_cliprects; -+ exec2.cliprects_ptr = args->cliprects_ptr; -+ exec2.flags = I915_EXEC_RENDER; -+ i915_execbuffer2_set_context_id(exec2, 0); -+ -+ if (!i915_gem_check_execbuffer(&exec2)) -+ return -EINVAL; -+ -+ /* Copy in the exec list from userland */ -+ exec_list = kvmalloc_array(count, sizeof(*exec_list), -+ __GFP_NOWARN | GFP_KERNEL); -+ exec2_list = kvmalloc_array(count + 1, eb_element_size(), -+ __GFP_NOWARN | GFP_KERNEL); -+ if (exec_list == NULL || exec2_list == NULL) { -+ DRM_DEBUG("Failed to allocate exec list for %d buffers\n", -+ args->buffer_count); -+ kvfree(exec_list); -+ kvfree(exec2_list); -+ return -ENOMEM; -+ } -+ err = copy_from_user(exec_list, -+ u64_to_user_ptr(args->buffers_ptr), -+ sizeof(*exec_list) * count); -+ if (err) { -+ DRM_DEBUG("copy %d exec entries failed %d\n", -+ args->buffer_count, err); -+ kvfree(exec_list); -+ kvfree(exec2_list); -+ return -EFAULT; -+ } -+ -+ for (i = 0; i < args->buffer_count; i++) { -+ exec2_list[i].handle = exec_list[i].handle; -+ exec2_list[i].relocation_count = exec_list[i].relocation_count; -+ exec2_list[i].relocs_ptr = exec_list[i].relocs_ptr; -+ exec2_list[i].alignment = exec_list[i].alignment; -+ exec2_list[i].offset = exec_list[i].offset; -+ if (INTEL_GEN(to_i915(dev)) < 4) -+ exec2_list[i].flags = EXEC_OBJECT_NEEDS_FENCE; -+ else -+ exec2_list[i].flags = 0; -+ } -+ -+ err = i915_gem_do_execbuffer(dev, file, &exec2, exec2_list, NULL); -+ if (exec2.flags & __EXEC_HAS_RELOC) { -+ struct drm_i915_gem_exec_object __user *user_exec_list = -+ u64_to_user_ptr(args->buffers_ptr); -+ -+ /* Copy the new buffer offsets back to the user's exec list. */ -+ for (i = 0; i < args->buffer_count; i++) { -+ if (!(exec2_list[i].offset & UPDATE)) -+ continue; -+ -+ exec2_list[i].offset = -+ gen8_canonical_addr(exec2_list[i].offset & PIN_OFFSET_MASK); -+ exec2_list[i].offset &= PIN_OFFSET_MASK; -+ if (__copy_to_user(&user_exec_list[i].offset, -+ &exec2_list[i].offset, -+ sizeof(user_exec_list[i].offset))) -+ break; -+ } -+ } -+ -+ kvfree(exec_list); -+ kvfree(exec2_list); -+ return err; -+} -+ -+int -+i915_gem_execbuffer2_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_gem_execbuffer2 *args = data; -+ struct drm_i915_gem_exec_object2 *exec2_list; -+ struct drm_syncobj **fences = NULL; -+ const size_t count = args->buffer_count; -+ int err; -+ -+ if (!check_buffer_count(count)) { -+ DRM_DEBUG("execbuf2 with %zd buffers\n", count); -+ return -EINVAL; -+ } -+ -+ if (!i915_gem_check_execbuffer(args)) -+ return -EINVAL; -+ -+ /* Allocate an extra slot for use by the command parser */ -+ exec2_list = kvmalloc_array(count + 1, eb_element_size(), -+ __GFP_NOWARN | GFP_KERNEL); -+ if (exec2_list == NULL) { -+ DRM_DEBUG("Failed to allocate exec list for %zd buffers\n", -+ count); -+ return -ENOMEM; -+ } -+ if (copy_from_user(exec2_list, -+ u64_to_user_ptr(args->buffers_ptr), -+ sizeof(*exec2_list) * count)) { -+ DRM_DEBUG("copy %zd exec entries failed\n", count); -+ kvfree(exec2_list); -+ return -EFAULT; -+ } -+ -+ if (args->flags & I915_EXEC_FENCE_ARRAY) { -+ fences = get_fence_array(args, file); -+ if (IS_ERR(fences)) { -+ kvfree(exec2_list); -+ return PTR_ERR(fences); -+ } -+ } -+ -+ err = i915_gem_do_execbuffer(dev, file, args, exec2_list, fences); -+ -+ /* -+ * Now that we have begun execution of the batchbuffer, we ignore -+ * any new error after this point. Also given that we have already -+ * updated the associated relocations, we try to write out the current -+ * object locations irrespective of any error. -+ */ -+ if (args->flags & __EXEC_HAS_RELOC) { -+ struct drm_i915_gem_exec_object2 __user *user_exec_list = -+ u64_to_user_ptr(args->buffers_ptr); -+ unsigned int i; -+ -+ /* Copy the new buffer offsets back to the user's exec list. */ -+ /* -+ * Note: count * sizeof(*user_exec_list) does not overflow, -+ * because we checked 'count' in check_buffer_count(). -+ * -+ * And this range already got effectively checked earlier -+ * when we did the "copy_from_user()" above. -+ */ -+ if (!user_access_begin(user_exec_list, count * sizeof(*user_exec_list))) -+ goto end; -+ -+ for (i = 0; i < args->buffer_count; i++) { -+ if (!(exec2_list[i].offset & UPDATE)) -+ continue; -+ -+ exec2_list[i].offset = -+ gen8_canonical_addr(exec2_list[i].offset & PIN_OFFSET_MASK); -+ unsafe_put_user(exec2_list[i].offset, -+ &user_exec_list[i].offset, -+ end_user); -+ } -+end_user: -+ user_access_end(); -+end:; -+ } -+ -+ args->flags &= ~__I915_EXEC_UNKNOWN_FLAGS; -+ put_fence_array(args, fences); -+ kvfree(exec2_list); -+ return err; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_fence_reg.c b/drivers/gpu/drm/i915_legacy/i915_gem_fence_reg.c -new file mode 100644 -index 000000000000..3084f52e3372 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_fence_reg.c -@@ -0,0 +1,785 @@ -+/* -+ * Copyright © 2008-2015 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#include -+#include "i915_drv.h" -+ -+/** -+ * DOC: fence register handling -+ * -+ * Important to avoid confusions: "fences" in the i915 driver are not execution -+ * fences used to track command completion but hardware detiler objects which -+ * wrap a given range of the global GTT. Each platform has only a fairly limited -+ * set of these objects. -+ * -+ * Fences are used to detile GTT memory mappings. They're also connected to the -+ * hardware frontbuffer render tracking and hence interact with frontbuffer -+ * compression. Furthermore on older platforms fences are required for tiled -+ * objects used by the display engine. They can also be used by the render -+ * engine - they're required for blitter commands and are optional for render -+ * commands. But on gen4+ both display (with the exception of fbc) and rendering -+ * have their own tiling state bits and don't need fences. -+ * -+ * Also note that fences only support X and Y tiling and hence can't be used for -+ * the fancier new tiling formats like W, Ys and Yf. -+ * -+ * Finally note that because fences are such a restricted resource they're -+ * dynamically associated with objects. Furthermore fence state is committed to -+ * the hardware lazily to avoid unnecessary stalls on gen2/3. Therefore code must -+ * explicitly call i915_gem_object_get_fence() to synchronize fencing status -+ * for cpu access. Also note that some code wants an unfenced view, for those -+ * cases the fence can be removed forcefully with i915_gem_object_put_fence(). -+ * -+ * Internally these functions will synchronize with userspace access by removing -+ * CPU ptes into GTT mmaps (not the GTT ptes themselves) as needed. -+ */ -+ -+#define pipelined 0 -+ -+static void i965_write_fence_reg(struct drm_i915_fence_reg *fence, -+ struct i915_vma *vma) -+{ -+ i915_reg_t fence_reg_lo, fence_reg_hi; -+ int fence_pitch_shift; -+ u64 val; -+ -+ if (INTEL_GEN(fence->i915) >= 6) { -+ fence_reg_lo = FENCE_REG_GEN6_LO(fence->id); -+ fence_reg_hi = FENCE_REG_GEN6_HI(fence->id); -+ fence_pitch_shift = GEN6_FENCE_PITCH_SHIFT; -+ -+ } else { -+ fence_reg_lo = FENCE_REG_965_LO(fence->id); -+ fence_reg_hi = FENCE_REG_965_HI(fence->id); -+ fence_pitch_shift = I965_FENCE_PITCH_SHIFT; -+ } -+ -+ val = 0; -+ if (vma) { -+ unsigned int stride = i915_gem_object_get_stride(vma->obj); -+ -+ GEM_BUG_ON(!i915_vma_is_map_and_fenceable(vma)); -+ GEM_BUG_ON(!IS_ALIGNED(vma->node.start, I965_FENCE_PAGE)); -+ GEM_BUG_ON(!IS_ALIGNED(vma->fence_size, I965_FENCE_PAGE)); -+ GEM_BUG_ON(!IS_ALIGNED(stride, 128)); -+ -+ val = (vma->node.start + vma->fence_size - I965_FENCE_PAGE) << 32; -+ val |= vma->node.start; -+ val |= (u64)((stride / 128) - 1) << fence_pitch_shift; -+ if (i915_gem_object_get_tiling(vma->obj) == I915_TILING_Y) -+ val |= BIT(I965_FENCE_TILING_Y_SHIFT); -+ val |= I965_FENCE_REG_VALID; -+ } -+ -+ if (!pipelined) { -+ struct drm_i915_private *dev_priv = fence->i915; -+ -+ /* To w/a incoherency with non-atomic 64-bit register updates, -+ * we split the 64-bit update into two 32-bit writes. In order -+ * for a partial fence not to be evaluated between writes, we -+ * precede the update with write to turn off the fence register, -+ * and only enable the fence as the last step. -+ * -+ * For extra levels of paranoia, we make sure each step lands -+ * before applying the next step. -+ */ -+ I915_WRITE(fence_reg_lo, 0); -+ POSTING_READ(fence_reg_lo); -+ -+ I915_WRITE(fence_reg_hi, upper_32_bits(val)); -+ I915_WRITE(fence_reg_lo, lower_32_bits(val)); -+ POSTING_READ(fence_reg_lo); -+ } -+} -+ -+static void i915_write_fence_reg(struct drm_i915_fence_reg *fence, -+ struct i915_vma *vma) -+{ -+ u32 val; -+ -+ val = 0; -+ if (vma) { -+ unsigned int tiling = i915_gem_object_get_tiling(vma->obj); -+ bool is_y_tiled = tiling == I915_TILING_Y; -+ unsigned int stride = i915_gem_object_get_stride(vma->obj); -+ -+ GEM_BUG_ON(!i915_vma_is_map_and_fenceable(vma)); -+ GEM_BUG_ON(vma->node.start & ~I915_FENCE_START_MASK); -+ GEM_BUG_ON(!is_power_of_2(vma->fence_size)); -+ GEM_BUG_ON(!IS_ALIGNED(vma->node.start, vma->fence_size)); -+ -+ if (is_y_tiled && HAS_128_BYTE_Y_TILING(fence->i915)) -+ stride /= 128; -+ else -+ stride /= 512; -+ GEM_BUG_ON(!is_power_of_2(stride)); -+ -+ val = vma->node.start; -+ if (is_y_tiled) -+ val |= BIT(I830_FENCE_TILING_Y_SHIFT); -+ val |= I915_FENCE_SIZE_BITS(vma->fence_size); -+ val |= ilog2(stride) << I830_FENCE_PITCH_SHIFT; -+ -+ val |= I830_FENCE_REG_VALID; -+ } -+ -+ if (!pipelined) { -+ struct drm_i915_private *dev_priv = fence->i915; -+ i915_reg_t reg = FENCE_REG(fence->id); -+ -+ I915_WRITE(reg, val); -+ POSTING_READ(reg); -+ } -+} -+ -+static void i830_write_fence_reg(struct drm_i915_fence_reg *fence, -+ struct i915_vma *vma) -+{ -+ u32 val; -+ -+ val = 0; -+ if (vma) { -+ unsigned int stride = i915_gem_object_get_stride(vma->obj); -+ -+ GEM_BUG_ON(!i915_vma_is_map_and_fenceable(vma)); -+ GEM_BUG_ON(vma->node.start & ~I830_FENCE_START_MASK); -+ GEM_BUG_ON(!is_power_of_2(vma->fence_size)); -+ GEM_BUG_ON(!is_power_of_2(stride / 128)); -+ GEM_BUG_ON(!IS_ALIGNED(vma->node.start, vma->fence_size)); -+ -+ val = vma->node.start; -+ if (i915_gem_object_get_tiling(vma->obj) == I915_TILING_Y) -+ val |= BIT(I830_FENCE_TILING_Y_SHIFT); -+ val |= I830_FENCE_SIZE_BITS(vma->fence_size); -+ val |= ilog2(stride / 128) << I830_FENCE_PITCH_SHIFT; -+ val |= I830_FENCE_REG_VALID; -+ } -+ -+ if (!pipelined) { -+ struct drm_i915_private *dev_priv = fence->i915; -+ i915_reg_t reg = FENCE_REG(fence->id); -+ -+ I915_WRITE(reg, val); -+ POSTING_READ(reg); -+ } -+} -+ -+static void fence_write(struct drm_i915_fence_reg *fence, -+ struct i915_vma *vma) -+{ -+ /* Previous access through the fence register is marshalled by -+ * the mb() inside the fault handlers (i915_gem_release_mmaps) -+ * and explicitly managed for internal users. -+ */ -+ -+ if (IS_GEN(fence->i915, 2)) -+ i830_write_fence_reg(fence, vma); -+ else if (IS_GEN(fence->i915, 3)) -+ i915_write_fence_reg(fence, vma); -+ else -+ i965_write_fence_reg(fence, vma); -+ -+ /* Access through the fenced region afterwards is -+ * ordered by the posting reads whilst writing the registers. -+ */ -+ -+ fence->dirty = false; -+} -+ -+static int fence_update(struct drm_i915_fence_reg *fence, -+ struct i915_vma *vma) -+{ -+ intel_wakeref_t wakeref; -+ struct i915_vma *old; -+ int ret; -+ -+ if (vma) { -+ if (!i915_vma_is_map_and_fenceable(vma)) -+ return -EINVAL; -+ -+ if (WARN(!i915_gem_object_get_stride(vma->obj) || -+ !i915_gem_object_get_tiling(vma->obj), -+ "bogus fence setup with stride: 0x%x, tiling mode: %i\n", -+ i915_gem_object_get_stride(vma->obj), -+ i915_gem_object_get_tiling(vma->obj))) -+ return -EINVAL; -+ -+ ret = i915_active_request_retire(&vma->last_fence, -+ &vma->obj->base.dev->struct_mutex); -+ if (ret) -+ return ret; -+ } -+ -+ old = xchg(&fence->vma, NULL); -+ if (old) { -+ ret = i915_active_request_retire(&old->last_fence, -+ &old->obj->base.dev->struct_mutex); -+ if (ret) { -+ fence->vma = old; -+ return ret; -+ } -+ -+ i915_vma_flush_writes(old); -+ -+ /* -+ * Ensure that all userspace CPU access is completed before -+ * stealing the fence. -+ */ -+ if (old != vma) { -+ GEM_BUG_ON(old->fence != fence); -+ i915_vma_revoke_mmap(old); -+ old->fence = NULL; -+ } -+ -+ list_move(&fence->link, &fence->i915->mm.fence_list); -+ } -+ -+ /* -+ * We only need to update the register itself if the device is awake. -+ * If the device is currently powered down, we will defer the write -+ * to the runtime resume, see i915_gem_restore_fences(). -+ * -+ * This only works for removing the fence register, on acquisition -+ * the caller must hold the rpm wakeref. The fence register must -+ * be cleared before we can use any other fences to ensure that -+ * the new fences do not overlap the elided clears, confusing HW. -+ */ -+ wakeref = intel_runtime_pm_get_if_in_use(fence->i915); -+ if (!wakeref) { -+ GEM_BUG_ON(vma); -+ return 0; -+ } -+ -+ WRITE_ONCE(fence->vma, vma); -+ fence_write(fence, vma); -+ -+ if (vma) { -+ vma->fence = fence; -+ list_move_tail(&fence->link, &fence->i915->mm.fence_list); -+ } -+ -+ intel_runtime_pm_put(fence->i915, wakeref); -+ return 0; -+} -+ -+/** -+ * i915_vma_put_fence - force-remove fence for a VMA -+ * @vma: vma to map linearly (not through a fence reg) -+ * -+ * This function force-removes any fence from the given object, which is useful -+ * if the kernel wants to do untiled GTT access. -+ * -+ * Returns: -+ * -+ * 0 on success, negative error code on failure. -+ */ -+int i915_vma_put_fence(struct i915_vma *vma) -+{ -+ struct drm_i915_fence_reg *fence = vma->fence; -+ -+ if (!fence) -+ return 0; -+ -+ if (fence->pin_count) -+ return -EBUSY; -+ -+ return fence_update(fence, NULL); -+} -+ -+static struct drm_i915_fence_reg *fence_find(struct drm_i915_private *dev_priv) -+{ -+ struct drm_i915_fence_reg *fence; -+ -+ list_for_each_entry(fence, &dev_priv->mm.fence_list, link) { -+ GEM_BUG_ON(fence->vma && fence->vma->fence != fence); -+ -+ if (fence->pin_count) -+ continue; -+ -+ return fence; -+ } -+ -+ /* Wait for completion of pending flips which consume fences */ -+ if (intel_has_pending_fb_unpin(dev_priv)) -+ return ERR_PTR(-EAGAIN); -+ -+ return ERR_PTR(-EDEADLK); -+} -+ -+/** -+ * i915_vma_pin_fence - set up fencing for a vma -+ * @vma: vma to map through a fence reg -+ * -+ * When mapping objects through the GTT, userspace wants to be able to write -+ * to them without having to worry about swizzling if the object is tiled. -+ * This function walks the fence regs looking for a free one for @obj, -+ * stealing one if it can't find any. -+ * -+ * It then sets up the reg based on the object's properties: address, pitch -+ * and tiling format. -+ * -+ * For an untiled surface, this removes any existing fence. -+ * -+ * Returns: -+ * -+ * 0 on success, negative error code on failure. -+ */ -+int -+i915_vma_pin_fence(struct i915_vma *vma) -+{ -+ struct drm_i915_fence_reg *fence; -+ struct i915_vma *set = i915_gem_object_is_tiled(vma->obj) ? vma : NULL; -+ int err; -+ -+ /* Note that we revoke fences on runtime suspend. Therefore the user -+ * must keep the device awake whilst using the fence. -+ */ -+ assert_rpm_wakelock_held(vma->vm->i915); -+ -+ /* Just update our place in the LRU if our fence is getting reused. */ -+ if (vma->fence) { -+ fence = vma->fence; -+ GEM_BUG_ON(fence->vma != vma); -+ fence->pin_count++; -+ if (!fence->dirty) { -+ list_move_tail(&fence->link, -+ &fence->i915->mm.fence_list); -+ return 0; -+ } -+ } else if (set) { -+ fence = fence_find(vma->vm->i915); -+ if (IS_ERR(fence)) -+ return PTR_ERR(fence); -+ -+ GEM_BUG_ON(fence->pin_count); -+ fence->pin_count++; -+ } else -+ return 0; -+ -+ err = fence_update(fence, set); -+ if (err) -+ goto out_unpin; -+ -+ GEM_BUG_ON(fence->vma != set); -+ GEM_BUG_ON(vma->fence != (set ? fence : NULL)); -+ -+ if (set) -+ return 0; -+ -+out_unpin: -+ fence->pin_count--; -+ return err; -+} -+ -+/** -+ * i915_reserve_fence - Reserve a fence for vGPU -+ * @dev_priv: i915 device private -+ * -+ * This function walks the fence regs looking for a free one and remove -+ * it from the fence_list. It is used to reserve fence for vGPU to use. -+ */ -+struct drm_i915_fence_reg * -+i915_reserve_fence(struct drm_i915_private *dev_priv) -+{ -+ struct drm_i915_fence_reg *fence; -+ int count; -+ int ret; -+ -+ lockdep_assert_held(&dev_priv->drm.struct_mutex); -+ -+ /* Keep at least one fence available for the display engine. */ -+ count = 0; -+ list_for_each_entry(fence, &dev_priv->mm.fence_list, link) -+ count += !fence->pin_count; -+ if (count <= 1) -+ return ERR_PTR(-ENOSPC); -+ -+ fence = fence_find(dev_priv); -+ if (IS_ERR(fence)) -+ return fence; -+ -+ if (fence->vma) { -+ /* Force-remove fence from VMA */ -+ ret = fence_update(fence, NULL); -+ if (ret) -+ return ERR_PTR(ret); -+ } -+ -+ list_del(&fence->link); -+ return fence; -+} -+ -+/** -+ * i915_unreserve_fence - Reclaim a reserved fence -+ * @fence: the fence reg -+ * -+ * This function add a reserved fence register from vGPU to the fence_list. -+ */ -+void i915_unreserve_fence(struct drm_i915_fence_reg *fence) -+{ -+ lockdep_assert_held(&fence->i915->drm.struct_mutex); -+ -+ list_add(&fence->link, &fence->i915->mm.fence_list); -+} -+ -+/** -+ * i915_gem_restore_fences - restore fence state -+ * @dev_priv: i915 device private -+ * -+ * Restore the hw fence state to match the software tracking again, to be called -+ * after a gpu reset and on resume. Note that on runtime suspend we only cancel -+ * the fences, to be reacquired by the user later. -+ */ -+void i915_gem_restore_fences(struct drm_i915_private *dev_priv) -+{ -+ int i; -+ -+ rcu_read_lock(); /* keep obj alive as we dereference */ -+ for (i = 0; i < dev_priv->num_fence_regs; i++) { -+ struct drm_i915_fence_reg *reg = &dev_priv->fence_regs[i]; -+ struct i915_vma *vma = READ_ONCE(reg->vma); -+ -+ GEM_BUG_ON(vma && vma->fence != reg); -+ -+ /* -+ * Commit delayed tiling changes if we have an object still -+ * attached to the fence, otherwise just clear the fence. -+ */ -+ if (vma && !i915_gem_object_is_tiled(vma->obj)) -+ vma = NULL; -+ -+ fence_write(reg, vma); -+ } -+ rcu_read_unlock(); -+} -+ -+/** -+ * DOC: tiling swizzling details -+ * -+ * The idea behind tiling is to increase cache hit rates by rearranging -+ * pixel data so that a group of pixel accesses are in the same cacheline. -+ * Performance improvement from doing this on the back/depth buffer are on -+ * the order of 30%. -+ * -+ * Intel architectures make this somewhat more complicated, though, by -+ * adjustments made to addressing of data when the memory is in interleaved -+ * mode (matched pairs of DIMMS) to improve memory bandwidth. -+ * For interleaved memory, the CPU sends every sequential 64 bytes -+ * to an alternate memory channel so it can get the bandwidth from both. -+ * -+ * The GPU also rearranges its accesses for increased bandwidth to interleaved -+ * memory, and it matches what the CPU does for non-tiled. However, when tiled -+ * it does it a little differently, since one walks addresses not just in the -+ * X direction but also Y. So, along with alternating channels when bit -+ * 6 of the address flips, it also alternates when other bits flip -- Bits 9 -+ * (every 512 bytes, an X tile scanline) and 10 (every two X tile scanlines) -+ * are common to both the 915 and 965-class hardware. -+ * -+ * The CPU also sometimes XORs in higher bits as well, to improve -+ * bandwidth doing strided access like we do so frequently in graphics. This -+ * is called "Channel XOR Randomization" in the MCH documentation. The result -+ * is that the CPU is XORing in either bit 11 or bit 17 to bit 6 of its address -+ * decode. -+ * -+ * All of this bit 6 XORing has an effect on our memory management, -+ * as we need to make sure that the 3d driver can correctly address object -+ * contents. -+ * -+ * If we don't have interleaved memory, all tiling is safe and no swizzling is -+ * required. -+ * -+ * When bit 17 is XORed in, we simply refuse to tile at all. Bit -+ * 17 is not just a page offset, so as we page an object out and back in, -+ * individual pages in it will have different bit 17 addresses, resulting in -+ * each 64 bytes being swapped with its neighbor! -+ * -+ * Otherwise, if interleaved, we have to tell the 3d driver what the address -+ * swizzling it needs to do is, since it's writing with the CPU to the pages -+ * (bit 6 and potentially bit 11 XORed in), and the GPU is reading from the -+ * pages (bit 6, 9, and 10 XORed in), resulting in a cumulative bit swizzling -+ * required by the CPU of XORing in bit 6, 9, 10, and potentially 11, in order -+ * to match what the GPU expects. -+ */ -+ -+/** -+ * i915_gem_detect_bit_6_swizzle - detect bit 6 swizzling pattern -+ * @dev_priv: i915 device private -+ * -+ * Detects bit 6 swizzling of address lookup between IGD access and CPU -+ * access through main memory. -+ */ -+void -+i915_gem_detect_bit_6_swizzle(struct drm_i915_private *dev_priv) -+{ -+ u32 swizzle_x = I915_BIT_6_SWIZZLE_UNKNOWN; -+ u32 swizzle_y = I915_BIT_6_SWIZZLE_UNKNOWN; -+ -+ if (INTEL_GEN(dev_priv) >= 8 || IS_VALLEYVIEW(dev_priv)) { -+ /* -+ * On BDW+, swizzling is not used. We leave the CPU memory -+ * controller in charge of optimizing memory accesses without -+ * the extra address manipulation GPU side. -+ * -+ * VLV and CHV don't have GPU swizzling. -+ */ -+ swizzle_x = I915_BIT_6_SWIZZLE_NONE; -+ swizzle_y = I915_BIT_6_SWIZZLE_NONE; -+ } else if (INTEL_GEN(dev_priv) >= 6) { -+ if (dev_priv->preserve_bios_swizzle) { -+ if (I915_READ(DISP_ARB_CTL) & -+ DISP_TILE_SURFACE_SWIZZLING) { -+ swizzle_x = I915_BIT_6_SWIZZLE_9_10; -+ swizzle_y = I915_BIT_6_SWIZZLE_9; -+ } else { -+ swizzle_x = I915_BIT_6_SWIZZLE_NONE; -+ swizzle_y = I915_BIT_6_SWIZZLE_NONE; -+ } -+ } else { -+ u32 dimm_c0, dimm_c1; -+ dimm_c0 = I915_READ(MAD_DIMM_C0); -+ dimm_c1 = I915_READ(MAD_DIMM_C1); -+ dimm_c0 &= MAD_DIMM_A_SIZE_MASK | MAD_DIMM_B_SIZE_MASK; -+ dimm_c1 &= MAD_DIMM_A_SIZE_MASK | MAD_DIMM_B_SIZE_MASK; -+ /* Enable swizzling when the channels are populated -+ * with identically sized dimms. We don't need to check -+ * the 3rd channel because no cpu with gpu attached -+ * ships in that configuration. Also, swizzling only -+ * makes sense for 2 channels anyway. */ -+ if (dimm_c0 == dimm_c1) { -+ swizzle_x = I915_BIT_6_SWIZZLE_9_10; -+ swizzle_y = I915_BIT_6_SWIZZLE_9; -+ } else { -+ swizzle_x = I915_BIT_6_SWIZZLE_NONE; -+ swizzle_y = I915_BIT_6_SWIZZLE_NONE; -+ } -+ } -+ } else if (IS_GEN(dev_priv, 5)) { -+ /* On Ironlake whatever DRAM config, GPU always do -+ * same swizzling setup. -+ */ -+ swizzle_x = I915_BIT_6_SWIZZLE_9_10; -+ swizzle_y = I915_BIT_6_SWIZZLE_9; -+ } else if (IS_GEN(dev_priv, 2)) { -+ /* As far as we know, the 865 doesn't have these bit 6 -+ * swizzling issues. -+ */ -+ swizzle_x = I915_BIT_6_SWIZZLE_NONE; -+ swizzle_y = I915_BIT_6_SWIZZLE_NONE; -+ } else if (IS_G45(dev_priv) || IS_I965G(dev_priv) || IS_G33(dev_priv)) { -+ /* The 965, G33, and newer, have a very flexible memory -+ * configuration. It will enable dual-channel mode -+ * (interleaving) on as much memory as it can, and the GPU -+ * will additionally sometimes enable different bit 6 -+ * swizzling for tiled objects from the CPU. -+ * -+ * Here's what I found on the G965: -+ * slot fill memory size swizzling -+ * 0A 0B 1A 1B 1-ch 2-ch -+ * 512 0 0 0 512 0 O -+ * 512 0 512 0 16 1008 X -+ * 512 0 0 512 16 1008 X -+ * 0 512 0 512 16 1008 X -+ * 1024 1024 1024 0 2048 1024 O -+ * -+ * We could probably detect this based on either the DRB -+ * matching, which was the case for the swizzling required in -+ * the table above, or from the 1-ch value being less than -+ * the minimum size of a rank. -+ * -+ * Reports indicate that the swizzling actually -+ * varies depending upon page placement inside the -+ * channels, i.e. we see swizzled pages where the -+ * banks of memory are paired and unswizzled on the -+ * uneven portion, so leave that as unknown. -+ */ -+ if (I915_READ16(C0DRB3) == I915_READ16(C1DRB3)) { -+ swizzle_x = I915_BIT_6_SWIZZLE_9_10; -+ swizzle_y = I915_BIT_6_SWIZZLE_9; -+ } -+ } else { -+ u32 dcc; -+ -+ /* On 9xx chipsets, channel interleave by the CPU is -+ * determined by DCC. For single-channel, neither the CPU -+ * nor the GPU do swizzling. For dual channel interleaved, -+ * the GPU's interleave is bit 9 and 10 for X tiled, and bit -+ * 9 for Y tiled. The CPU's interleave is independent, and -+ * can be based on either bit 11 (haven't seen this yet) or -+ * bit 17 (common). -+ */ -+ dcc = I915_READ(DCC); -+ switch (dcc & DCC_ADDRESSING_MODE_MASK) { -+ case DCC_ADDRESSING_MODE_SINGLE_CHANNEL: -+ case DCC_ADDRESSING_MODE_DUAL_CHANNEL_ASYMMETRIC: -+ swizzle_x = I915_BIT_6_SWIZZLE_NONE; -+ swizzle_y = I915_BIT_6_SWIZZLE_NONE; -+ break; -+ case DCC_ADDRESSING_MODE_DUAL_CHANNEL_INTERLEAVED: -+ if (dcc & DCC_CHANNEL_XOR_DISABLE) { -+ /* This is the base swizzling by the GPU for -+ * tiled buffers. -+ */ -+ swizzle_x = I915_BIT_6_SWIZZLE_9_10; -+ swizzle_y = I915_BIT_6_SWIZZLE_9; -+ } else if ((dcc & DCC_CHANNEL_XOR_BIT_17) == 0) { -+ /* Bit 11 swizzling by the CPU in addition. */ -+ swizzle_x = I915_BIT_6_SWIZZLE_9_10_11; -+ swizzle_y = I915_BIT_6_SWIZZLE_9_11; -+ } else { -+ /* Bit 17 swizzling by the CPU in addition. */ -+ swizzle_x = I915_BIT_6_SWIZZLE_9_10_17; -+ swizzle_y = I915_BIT_6_SWIZZLE_9_17; -+ } -+ break; -+ } -+ -+ /* check for L-shaped memory aka modified enhanced addressing */ -+ if (IS_GEN(dev_priv, 4) && -+ !(I915_READ(DCC2) & DCC2_MODIFIED_ENHANCED_DISABLE)) { -+ swizzle_x = I915_BIT_6_SWIZZLE_UNKNOWN; -+ swizzle_y = I915_BIT_6_SWIZZLE_UNKNOWN; -+ } -+ -+ if (dcc == 0xffffffff) { -+ DRM_ERROR("Couldn't read from MCHBAR. " -+ "Disabling tiling.\n"); -+ swizzle_x = I915_BIT_6_SWIZZLE_UNKNOWN; -+ swizzle_y = I915_BIT_6_SWIZZLE_UNKNOWN; -+ } -+ } -+ -+ if (swizzle_x == I915_BIT_6_SWIZZLE_UNKNOWN || -+ swizzle_y == I915_BIT_6_SWIZZLE_UNKNOWN) { -+ /* Userspace likes to explode if it sees unknown swizzling, -+ * so lie. We will finish the lie when reporting through -+ * the get-tiling-ioctl by reporting the physical swizzle -+ * mode as unknown instead. -+ * -+ * As we don't strictly know what the swizzling is, it may be -+ * bit17 dependent, and so we need to also prevent the pages -+ * from being moved. -+ */ -+ dev_priv->quirks |= QUIRK_PIN_SWIZZLED_PAGES; -+ swizzle_x = I915_BIT_6_SWIZZLE_NONE; -+ swizzle_y = I915_BIT_6_SWIZZLE_NONE; -+ } -+ -+ dev_priv->mm.bit_6_swizzle_x = swizzle_x; -+ dev_priv->mm.bit_6_swizzle_y = swizzle_y; -+} -+ -+/* -+ * Swap every 64 bytes of this page around, to account for it having a new -+ * bit 17 of its physical address and therefore being interpreted differently -+ * by the GPU. -+ */ -+static void -+i915_gem_swizzle_page(struct page *page) -+{ -+ char temp[64]; -+ char *vaddr; -+ int i; -+ -+ vaddr = kmap(page); -+ -+ for (i = 0; i < PAGE_SIZE; i += 128) { -+ memcpy(temp, &vaddr[i], 64); -+ memcpy(&vaddr[i], &vaddr[i + 64], 64); -+ memcpy(&vaddr[i + 64], temp, 64); -+ } -+ -+ kunmap(page); -+} -+ -+/** -+ * i915_gem_object_do_bit_17_swizzle - fixup bit 17 swizzling -+ * @obj: i915 GEM buffer object -+ * @pages: the scattergather list of physical pages -+ * -+ * This function fixes up the swizzling in case any page frame number for this -+ * object has changed in bit 17 since that state has been saved with -+ * i915_gem_object_save_bit_17_swizzle(). -+ * -+ * This is called when pinning backing storage again, since the kernel is free -+ * to move unpinned backing storage around (either by directly moving pages or -+ * by swapping them out and back in again). -+ */ -+void -+i915_gem_object_do_bit_17_swizzle(struct drm_i915_gem_object *obj, -+ struct sg_table *pages) -+{ -+ struct sgt_iter sgt_iter; -+ struct page *page; -+ int i; -+ -+ if (obj->bit_17 == NULL) -+ return; -+ -+ i = 0; -+ for_each_sgt_page(page, sgt_iter, pages) { -+ char new_bit_17 = page_to_phys(page) >> 17; -+ if ((new_bit_17 & 0x1) != (test_bit(i, obj->bit_17) != 0)) { -+ i915_gem_swizzle_page(page); -+ set_page_dirty(page); -+ } -+ i++; -+ } -+} -+ -+/** -+ * i915_gem_object_save_bit_17_swizzle - save bit 17 swizzling -+ * @obj: i915 GEM buffer object -+ * @pages: the scattergather list of physical pages -+ * -+ * This function saves the bit 17 of each page frame number so that swizzling -+ * can be fixed up later on with i915_gem_object_do_bit_17_swizzle(). This must -+ * be called before the backing storage can be unpinned. -+ */ -+void -+i915_gem_object_save_bit_17_swizzle(struct drm_i915_gem_object *obj, -+ struct sg_table *pages) -+{ -+ const unsigned int page_count = obj->base.size >> PAGE_SHIFT; -+ struct sgt_iter sgt_iter; -+ struct page *page; -+ int i; -+ -+ if (obj->bit_17 == NULL) { -+ obj->bit_17 = bitmap_zalloc(page_count, GFP_KERNEL); -+ if (obj->bit_17 == NULL) { -+ DRM_ERROR("Failed to allocate memory for bit 17 " -+ "record\n"); -+ return; -+ } -+ } -+ -+ i = 0; -+ -+ for_each_sgt_page(page, sgt_iter, pages) { -+ if (page_to_phys(page) & (1 << 17)) -+ __set_bit(i, obj->bit_17); -+ else -+ __clear_bit(i, obj->bit_17); -+ i++; -+ } -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_fence_reg.h b/drivers/gpu/drm/i915_legacy/i915_gem_fence_reg.h -new file mode 100644 -index 000000000000..09dcaf14121b ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_fence_reg.h -@@ -0,0 +1,52 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef __I915_FENCE_REG_H__ -+#define __I915_FENCE_REG_H__ -+ -+#include -+ -+struct drm_i915_private; -+struct i915_vma; -+ -+#define I965_FENCE_PAGE 4096UL -+ -+struct drm_i915_fence_reg { -+ struct list_head link; -+ struct drm_i915_private *i915; -+ struct i915_vma *vma; -+ int pin_count; -+ int id; -+ /** -+ * Whether the tiling parameters for the currently -+ * associated fence register have changed. Note that -+ * for the purposes of tracking tiling changes we also -+ * treat the unfenced register, the register slot that -+ * the object occupies whilst it executes a fenced -+ * command (such as BLT on gen2/3), as a "fence". -+ */ -+ bool dirty; -+}; -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_gtt.c b/drivers/gpu/drm/i915_legacy/i915_gem_gtt.c -new file mode 100644 -index 000000000000..8f460cc4cc1f ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_gtt.c -@@ -0,0 +1,3922 @@ -+/* -+ * Copyright © 2010 Daniel Vetter -+ * Copyright © 2011-2014 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include /* fault-inject.h is not standalone! */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_vgpu.h" -+#include "i915_reset.h" -+#include "i915_trace.h" -+#include "intel_drv.h" -+#include "intel_frontbuffer.h" -+ -+#define I915_GFP_ALLOW_FAIL (GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_NOWARN) -+ -+/** -+ * DOC: Global GTT views -+ * -+ * Background and previous state -+ * -+ * Historically objects could exists (be bound) in global GTT space only as -+ * singular instances with a view representing all of the object's backing pages -+ * in a linear fashion. This view will be called a normal view. -+ * -+ * To support multiple views of the same object, where the number of mapped -+ * pages is not equal to the backing store, or where the layout of the pages -+ * is not linear, concept of a GGTT view was added. -+ * -+ * One example of an alternative view is a stereo display driven by a single -+ * image. In this case we would have a framebuffer looking like this -+ * (2x2 pages): -+ * -+ * 12 -+ * 34 -+ * -+ * Above would represent a normal GGTT view as normally mapped for GPU or CPU -+ * rendering. In contrast, fed to the display engine would be an alternative -+ * view which could look something like this: -+ * -+ * 1212 -+ * 3434 -+ * -+ * In this example both the size and layout of pages in the alternative view is -+ * different from the normal view. -+ * -+ * Implementation and usage -+ * -+ * GGTT views are implemented using VMAs and are distinguished via enum -+ * i915_ggtt_view_type and struct i915_ggtt_view. -+ * -+ * A new flavour of core GEM functions which work with GGTT bound objects were -+ * added with the _ggtt_ infix, and sometimes with _view postfix to avoid -+ * renaming in large amounts of code. They take the struct i915_ggtt_view -+ * parameter encapsulating all metadata required to implement a view. -+ * -+ * As a helper for callers which are only interested in the normal view, -+ * globally const i915_ggtt_view_normal singleton instance exists. All old core -+ * GEM API functions, the ones not taking the view parameter, are operating on, -+ * or with the normal GGTT view. -+ * -+ * Code wanting to add or use a new GGTT view needs to: -+ * -+ * 1. Add a new enum with a suitable name. -+ * 2. Extend the metadata in the i915_ggtt_view structure if required. -+ * 3. Add support to i915_get_vma_pages(). -+ * -+ * New views are required to build a scatter-gather table from within the -+ * i915_get_vma_pages function. This table is stored in the vma.ggtt_view and -+ * exists for the lifetime of an VMA. -+ * -+ * Core API is designed to have copy semantics which means that passed in -+ * struct i915_ggtt_view does not need to be persistent (left around after -+ * calling the core API functions). -+ * -+ */ -+ -+static int -+i915_get_ggtt_vma_pages(struct i915_vma *vma); -+ -+static void gen6_ggtt_invalidate(struct drm_i915_private *dev_priv) -+{ -+ /* -+ * Note that as an uncached mmio write, this will flush the -+ * WCB of the writes into the GGTT before it triggers the invalidate. -+ */ -+ I915_WRITE(GFX_FLSH_CNTL_GEN6, GFX_FLSH_CNTL_EN); -+} -+ -+static void guc_ggtt_invalidate(struct drm_i915_private *dev_priv) -+{ -+ gen6_ggtt_invalidate(dev_priv); -+ I915_WRITE(GEN8_GTCR, GEN8_GTCR_INVALIDATE); -+} -+ -+static void gmch_ggtt_invalidate(struct drm_i915_private *dev_priv) -+{ -+ intel_gtt_chipset_flush(); -+} -+ -+static inline void i915_ggtt_invalidate(struct drm_i915_private *i915) -+{ -+ i915->ggtt.invalidate(i915); -+} -+ -+static int ppgtt_bind_vma(struct i915_vma *vma, -+ enum i915_cache_level cache_level, -+ u32 unused) -+{ -+ u32 pte_flags; -+ int err; -+ -+ if (!(vma->flags & I915_VMA_LOCAL_BIND)) { -+ err = vma->vm->allocate_va_range(vma->vm, -+ vma->node.start, vma->size); -+ if (err) -+ return err; -+ } -+ -+ /* Applicable to VLV, and gen8+ */ -+ pte_flags = 0; -+ if (i915_gem_object_is_readonly(vma->obj)) -+ pte_flags |= PTE_READ_ONLY; -+ -+ vma->vm->insert_entries(vma->vm, vma, cache_level, pte_flags); -+ -+ return 0; -+} -+ -+static void ppgtt_unbind_vma(struct i915_vma *vma) -+{ -+ vma->vm->clear_range(vma->vm, vma->node.start, vma->size); -+} -+ -+static int ppgtt_set_pages(struct i915_vma *vma) -+{ -+ GEM_BUG_ON(vma->pages); -+ -+ vma->pages = vma->obj->mm.pages; -+ -+ vma->page_sizes = vma->obj->mm.page_sizes; -+ -+ return 0; -+} -+ -+static void clear_pages(struct i915_vma *vma) -+{ -+ GEM_BUG_ON(!vma->pages); -+ -+ if (vma->pages != vma->obj->mm.pages) { -+ sg_free_table(vma->pages); -+ kfree(vma->pages); -+ } -+ vma->pages = NULL; -+ -+ memset(&vma->page_sizes, 0, sizeof(vma->page_sizes)); -+} -+ -+static u64 gen8_pte_encode(dma_addr_t addr, -+ enum i915_cache_level level, -+ u32 flags) -+{ -+ gen8_pte_t pte = addr | _PAGE_PRESENT | _PAGE_RW; -+ -+ if (unlikely(flags & PTE_READ_ONLY)) -+ pte &= ~_PAGE_RW; -+ -+ switch (level) { -+ case I915_CACHE_NONE: -+ pte |= PPAT_UNCACHED; -+ break; -+ case I915_CACHE_WT: -+ pte |= PPAT_DISPLAY_ELLC; -+ break; -+ default: -+ pte |= PPAT_CACHED; -+ break; -+ } -+ -+ return pte; -+} -+ -+static gen8_pde_t gen8_pde_encode(const dma_addr_t addr, -+ const enum i915_cache_level level) -+{ -+ gen8_pde_t pde = _PAGE_PRESENT | _PAGE_RW; -+ pde |= addr; -+ if (level != I915_CACHE_NONE) -+ pde |= PPAT_CACHED_PDE; -+ else -+ pde |= PPAT_UNCACHED; -+ return pde; -+} -+ -+#define gen8_pdpe_encode gen8_pde_encode -+#define gen8_pml4e_encode gen8_pde_encode -+ -+static u64 snb_pte_encode(dma_addr_t addr, -+ enum i915_cache_level level, -+ u32 flags) -+{ -+ gen6_pte_t pte = GEN6_PTE_VALID; -+ pte |= GEN6_PTE_ADDR_ENCODE(addr); -+ -+ switch (level) { -+ case I915_CACHE_L3_LLC: -+ case I915_CACHE_LLC: -+ pte |= GEN6_PTE_CACHE_LLC; -+ break; -+ case I915_CACHE_NONE: -+ pte |= GEN6_PTE_UNCACHED; -+ break; -+ default: -+ MISSING_CASE(level); -+ } -+ -+ return pte; -+} -+ -+static u64 ivb_pte_encode(dma_addr_t addr, -+ enum i915_cache_level level, -+ u32 flags) -+{ -+ gen6_pte_t pte = GEN6_PTE_VALID; -+ pte |= GEN6_PTE_ADDR_ENCODE(addr); -+ -+ switch (level) { -+ case I915_CACHE_L3_LLC: -+ pte |= GEN7_PTE_CACHE_L3_LLC; -+ break; -+ case I915_CACHE_LLC: -+ pte |= GEN6_PTE_CACHE_LLC; -+ break; -+ case I915_CACHE_NONE: -+ pte |= GEN6_PTE_UNCACHED; -+ break; -+ default: -+ MISSING_CASE(level); -+ } -+ -+ return pte; -+} -+ -+static u64 byt_pte_encode(dma_addr_t addr, -+ enum i915_cache_level level, -+ u32 flags) -+{ -+ gen6_pte_t pte = GEN6_PTE_VALID; -+ pte |= GEN6_PTE_ADDR_ENCODE(addr); -+ -+ if (!(flags & PTE_READ_ONLY)) -+ pte |= BYT_PTE_WRITEABLE; -+ -+ if (level != I915_CACHE_NONE) -+ pte |= BYT_PTE_SNOOPED_BY_CPU_CACHES; -+ -+ return pte; -+} -+ -+static u64 hsw_pte_encode(dma_addr_t addr, -+ enum i915_cache_level level, -+ u32 flags) -+{ -+ gen6_pte_t pte = GEN6_PTE_VALID; -+ pte |= HSW_PTE_ADDR_ENCODE(addr); -+ -+ if (level != I915_CACHE_NONE) -+ pte |= HSW_WB_LLC_AGE3; -+ -+ return pte; -+} -+ -+static u64 iris_pte_encode(dma_addr_t addr, -+ enum i915_cache_level level, -+ u32 flags) -+{ -+ gen6_pte_t pte = GEN6_PTE_VALID; -+ pte |= HSW_PTE_ADDR_ENCODE(addr); -+ -+ switch (level) { -+ case I915_CACHE_NONE: -+ break; -+ case I915_CACHE_WT: -+ pte |= HSW_WT_ELLC_LLC_AGE3; -+ break; -+ default: -+ pte |= HSW_WB_ELLC_LLC_AGE3; -+ break; -+ } -+ -+ return pte; -+} -+ -+static void stash_init(struct pagestash *stash) -+{ -+ pagevec_init(&stash->pvec); -+ spin_lock_init(&stash->lock); -+} -+ -+static struct page *stash_pop_page(struct pagestash *stash) -+{ -+ struct page *page = NULL; -+ -+ spin_lock(&stash->lock); -+ if (likely(stash->pvec.nr)) -+ page = stash->pvec.pages[--stash->pvec.nr]; -+ spin_unlock(&stash->lock); -+ -+ return page; -+} -+ -+static void stash_push_pagevec(struct pagestash *stash, struct pagevec *pvec) -+{ -+ int nr; -+ -+ spin_lock_nested(&stash->lock, SINGLE_DEPTH_NESTING); -+ -+ nr = min_t(int, pvec->nr, pagevec_space(&stash->pvec)); -+ memcpy(stash->pvec.pages + stash->pvec.nr, -+ pvec->pages + pvec->nr - nr, -+ sizeof(pvec->pages[0]) * nr); -+ stash->pvec.nr += nr; -+ -+ spin_unlock(&stash->lock); -+ -+ pvec->nr -= nr; -+} -+ -+static struct page *vm_alloc_page(struct i915_address_space *vm, gfp_t gfp) -+{ -+ struct pagevec stack; -+ struct page *page; -+ -+ if (I915_SELFTEST_ONLY(should_fail(&vm->fault_attr, 1))) -+ i915_gem_shrink_all(vm->i915); -+ -+ page = stash_pop_page(&vm->free_pages); -+ if (page) -+ return page; -+ -+ if (!vm->pt_kmap_wc) -+ return alloc_page(gfp); -+ -+ /* Look in our global stash of WC pages... */ -+ page = stash_pop_page(&vm->i915->mm.wc_stash); -+ if (page) -+ return page; -+ -+ /* -+ * Otherwise batch allocate pages to amortize cost of set_pages_wc. -+ * -+ * We have to be careful as page allocation may trigger the shrinker -+ * (via direct reclaim) which will fill up the WC stash underneath us. -+ * So we add our WB pages into a temporary pvec on the stack and merge -+ * them into the WC stash after all the allocations are complete. -+ */ -+ pagevec_init(&stack); -+ do { -+ struct page *page; -+ -+ page = alloc_page(gfp); -+ if (unlikely(!page)) -+ break; -+ -+ stack.pages[stack.nr++] = page; -+ } while (pagevec_space(&stack)); -+ -+ if (stack.nr && !set_pages_array_wc(stack.pages, stack.nr)) { -+ page = stack.pages[--stack.nr]; -+ -+ /* Merge spare WC pages to the global stash */ -+ stash_push_pagevec(&vm->i915->mm.wc_stash, &stack); -+ -+ /* Push any surplus WC pages onto the local VM stash */ -+ if (stack.nr) -+ stash_push_pagevec(&vm->free_pages, &stack); -+ } -+ -+ /* Return unwanted leftovers */ -+ if (unlikely(stack.nr)) { -+ WARN_ON_ONCE(set_pages_array_wb(stack.pages, stack.nr)); -+ __pagevec_release(&stack); -+ } -+ -+ return page; -+} -+ -+static void vm_free_pages_release(struct i915_address_space *vm, -+ bool immediate) -+{ -+ struct pagevec *pvec = &vm->free_pages.pvec; -+ struct pagevec stack; -+ -+ lockdep_assert_held(&vm->free_pages.lock); -+ GEM_BUG_ON(!pagevec_count(pvec)); -+ -+ if (vm->pt_kmap_wc) { -+ /* -+ * When we use WC, first fill up the global stash and then -+ * only if full immediately free the overflow. -+ */ -+ stash_push_pagevec(&vm->i915->mm.wc_stash, pvec); -+ -+ /* -+ * As we have made some room in the VM's free_pages, -+ * we can wait for it to fill again. Unless we are -+ * inside i915_address_space_fini() and must -+ * immediately release the pages! -+ */ -+ if (pvec->nr <= (immediate ? 0 : PAGEVEC_SIZE - 1)) -+ return; -+ -+ /* -+ * We have to drop the lock to allow ourselves to sleep, -+ * so take a copy of the pvec and clear the stash for -+ * others to use it as we sleep. -+ */ -+ stack = *pvec; -+ pagevec_reinit(pvec); -+ spin_unlock(&vm->free_pages.lock); -+ -+ pvec = &stack; -+ set_pages_array_wb(pvec->pages, pvec->nr); -+ -+ spin_lock(&vm->free_pages.lock); -+ } -+ -+ __pagevec_release(pvec); -+} -+ -+static void vm_free_page(struct i915_address_space *vm, struct page *page) -+{ -+ /* -+ * On !llc, we need to change the pages back to WB. We only do so -+ * in bulk, so we rarely need to change the page attributes here, -+ * but doing so requires a stop_machine() from deep inside arch/x86/mm. -+ * To make detection of the possible sleep more likely, use an -+ * unconditional might_sleep() for everybody. -+ */ -+ might_sleep(); -+ spin_lock(&vm->free_pages.lock); -+ if (!pagevec_add(&vm->free_pages.pvec, page)) -+ vm_free_pages_release(vm, false); -+ spin_unlock(&vm->free_pages.lock); -+} -+ -+static void i915_address_space_init(struct i915_address_space *vm, int subclass) -+{ -+ /* -+ * The vm->mutex must be reclaim safe (for use in the shrinker). -+ * Do a dummy acquire now under fs_reclaim so that any allocation -+ * attempt holding the lock is immediately reported by lockdep. -+ */ -+ mutex_init(&vm->mutex); -+ lockdep_set_subclass(&vm->mutex, subclass); -+ i915_gem_shrinker_taints_mutex(vm->i915, &vm->mutex); -+ -+ GEM_BUG_ON(!vm->total); -+ drm_mm_init(&vm->mm, 0, vm->total); -+ vm->mm.head_node.color = I915_COLOR_UNEVICTABLE; -+ -+ stash_init(&vm->free_pages); -+ -+ INIT_LIST_HEAD(&vm->unbound_list); -+ INIT_LIST_HEAD(&vm->bound_list); -+} -+ -+static void i915_address_space_fini(struct i915_address_space *vm) -+{ -+ spin_lock(&vm->free_pages.lock); -+ if (pagevec_count(&vm->free_pages.pvec)) -+ vm_free_pages_release(vm, true); -+ GEM_BUG_ON(pagevec_count(&vm->free_pages.pvec)); -+ spin_unlock(&vm->free_pages.lock); -+ -+ drm_mm_takedown(&vm->mm); -+ -+ mutex_destroy(&vm->mutex); -+} -+ -+static int __setup_page_dma(struct i915_address_space *vm, -+ struct i915_page_dma *p, -+ gfp_t gfp) -+{ -+ p->page = vm_alloc_page(vm, gfp | I915_GFP_ALLOW_FAIL); -+ if (unlikely(!p->page)) -+ return -ENOMEM; -+ -+ p->daddr = dma_map_page_attrs(vm->dma, -+ p->page, 0, PAGE_SIZE, -+ PCI_DMA_BIDIRECTIONAL, -+ DMA_ATTR_SKIP_CPU_SYNC | -+ DMA_ATTR_NO_WARN); -+ if (unlikely(dma_mapping_error(vm->dma, p->daddr))) { -+ vm_free_page(vm, p->page); -+ return -ENOMEM; -+ } -+ -+ return 0; -+} -+ -+static int setup_page_dma(struct i915_address_space *vm, -+ struct i915_page_dma *p) -+{ -+ return __setup_page_dma(vm, p, __GFP_HIGHMEM); -+} -+ -+static void cleanup_page_dma(struct i915_address_space *vm, -+ struct i915_page_dma *p) -+{ -+ dma_unmap_page(vm->dma, p->daddr, PAGE_SIZE, PCI_DMA_BIDIRECTIONAL); -+ vm_free_page(vm, p->page); -+} -+ -+#define kmap_atomic_px(px) kmap_atomic(px_base(px)->page) -+ -+#define setup_px(vm, px) setup_page_dma((vm), px_base(px)) -+#define cleanup_px(vm, px) cleanup_page_dma((vm), px_base(px)) -+#define fill_px(vm, px, v) fill_page_dma((vm), px_base(px), (v)) -+#define fill32_px(vm, px, v) fill_page_dma_32((vm), px_base(px), (v)) -+ -+static void fill_page_dma(struct i915_address_space *vm, -+ struct i915_page_dma *p, -+ const u64 val) -+{ -+ u64 * const vaddr = kmap_atomic(p->page); -+ -+ memset64(vaddr, val, PAGE_SIZE / sizeof(val)); -+ -+ kunmap_atomic(vaddr); -+} -+ -+static void fill_page_dma_32(struct i915_address_space *vm, -+ struct i915_page_dma *p, -+ const u32 v) -+{ -+ fill_page_dma(vm, p, (u64)v << 32 | v); -+} -+ -+static int -+setup_scratch_page(struct i915_address_space *vm, gfp_t gfp) -+{ -+ unsigned long size; -+ -+ /* -+ * In order to utilize 64K pages for an object with a size < 2M, we will -+ * need to support a 64K scratch page, given that every 16th entry for a -+ * page-table operating in 64K mode must point to a properly aligned 64K -+ * region, including any PTEs which happen to point to scratch. -+ * -+ * This is only relevant for the 48b PPGTT where we support -+ * huge-gtt-pages, see also i915_vma_insert(). However, as we share the -+ * scratch (read-only) between all vm, we create one 64k scratch page -+ * for all. -+ */ -+ size = I915_GTT_PAGE_SIZE_4K; -+ if (i915_vm_is_4lvl(vm) && -+ HAS_PAGE_SIZES(vm->i915, I915_GTT_PAGE_SIZE_64K)) { -+ size = I915_GTT_PAGE_SIZE_64K; -+ gfp |= __GFP_NOWARN; -+ } -+ gfp |= __GFP_ZERO | __GFP_RETRY_MAYFAIL; -+ -+ do { -+ int order = get_order(size); -+ struct page *page; -+ dma_addr_t addr; -+ -+ page = alloc_pages(gfp, order); -+ if (unlikely(!page)) -+ goto skip; -+ -+ addr = dma_map_page_attrs(vm->dma, -+ page, 0, size, -+ PCI_DMA_BIDIRECTIONAL, -+ DMA_ATTR_SKIP_CPU_SYNC | -+ DMA_ATTR_NO_WARN); -+ if (unlikely(dma_mapping_error(vm->dma, addr))) -+ goto free_page; -+ -+ if (unlikely(!IS_ALIGNED(addr, size))) -+ goto unmap_page; -+ -+ vm->scratch_page.page = page; -+ vm->scratch_page.daddr = addr; -+ vm->scratch_order = order; -+ return 0; -+ -+unmap_page: -+ dma_unmap_page(vm->dma, addr, size, PCI_DMA_BIDIRECTIONAL); -+free_page: -+ __free_pages(page, order); -+skip: -+ if (size == I915_GTT_PAGE_SIZE_4K) -+ return -ENOMEM; -+ -+ size = I915_GTT_PAGE_SIZE_4K; -+ gfp &= ~__GFP_NOWARN; -+ } while (1); -+} -+ -+static void cleanup_scratch_page(struct i915_address_space *vm) -+{ -+ struct i915_page_dma *p = &vm->scratch_page; -+ int order = vm->scratch_order; -+ -+ dma_unmap_page(vm->dma, p->daddr, BIT(order) << PAGE_SHIFT, -+ PCI_DMA_BIDIRECTIONAL); -+ __free_pages(p->page, order); -+} -+ -+static struct i915_page_table *alloc_pt(struct i915_address_space *vm) -+{ -+ struct i915_page_table *pt; -+ -+ pt = kmalloc(sizeof(*pt), I915_GFP_ALLOW_FAIL); -+ if (unlikely(!pt)) -+ return ERR_PTR(-ENOMEM); -+ -+ if (unlikely(setup_px(vm, pt))) { -+ kfree(pt); -+ return ERR_PTR(-ENOMEM); -+ } -+ -+ pt->used_ptes = 0; -+ return pt; -+} -+ -+static void free_pt(struct i915_address_space *vm, struct i915_page_table *pt) -+{ -+ cleanup_px(vm, pt); -+ kfree(pt); -+} -+ -+static void gen8_initialize_pt(struct i915_address_space *vm, -+ struct i915_page_table *pt) -+{ -+ fill_px(vm, pt, vm->scratch_pte); -+} -+ -+static void gen6_initialize_pt(struct i915_address_space *vm, -+ struct i915_page_table *pt) -+{ -+ fill32_px(vm, pt, vm->scratch_pte); -+} -+ -+static struct i915_page_directory *alloc_pd(struct i915_address_space *vm) -+{ -+ struct i915_page_directory *pd; -+ -+ pd = kzalloc(sizeof(*pd), I915_GFP_ALLOW_FAIL); -+ if (unlikely(!pd)) -+ return ERR_PTR(-ENOMEM); -+ -+ if (unlikely(setup_px(vm, pd))) { -+ kfree(pd); -+ return ERR_PTR(-ENOMEM); -+ } -+ -+ pd->used_pdes = 0; -+ return pd; -+} -+ -+static void free_pd(struct i915_address_space *vm, -+ struct i915_page_directory *pd) -+{ -+ cleanup_px(vm, pd); -+ kfree(pd); -+} -+ -+static void gen8_initialize_pd(struct i915_address_space *vm, -+ struct i915_page_directory *pd) -+{ -+ fill_px(vm, pd, -+ gen8_pde_encode(px_dma(vm->scratch_pt), I915_CACHE_LLC)); -+ memset_p((void **)pd->page_table, vm->scratch_pt, I915_PDES); -+} -+ -+static int __pdp_init(struct i915_address_space *vm, -+ struct i915_page_directory_pointer *pdp) -+{ -+ const unsigned int pdpes = i915_pdpes_per_pdp(vm); -+ -+ pdp->page_directory = kmalloc_array(pdpes, sizeof(*pdp->page_directory), -+ I915_GFP_ALLOW_FAIL); -+ if (unlikely(!pdp->page_directory)) -+ return -ENOMEM; -+ -+ memset_p((void **)pdp->page_directory, vm->scratch_pd, pdpes); -+ -+ return 0; -+} -+ -+static void __pdp_fini(struct i915_page_directory_pointer *pdp) -+{ -+ kfree(pdp->page_directory); -+ pdp->page_directory = NULL; -+} -+ -+static struct i915_page_directory_pointer * -+alloc_pdp(struct i915_address_space *vm) -+{ -+ struct i915_page_directory_pointer *pdp; -+ int ret = -ENOMEM; -+ -+ GEM_BUG_ON(!i915_vm_is_4lvl(vm)); -+ -+ pdp = kzalloc(sizeof(*pdp), GFP_KERNEL); -+ if (!pdp) -+ return ERR_PTR(-ENOMEM); -+ -+ ret = __pdp_init(vm, pdp); -+ if (ret) -+ goto fail_bitmap; -+ -+ ret = setup_px(vm, pdp); -+ if (ret) -+ goto fail_page_m; -+ -+ return pdp; -+ -+fail_page_m: -+ __pdp_fini(pdp); -+fail_bitmap: -+ kfree(pdp); -+ -+ return ERR_PTR(ret); -+} -+ -+static void free_pdp(struct i915_address_space *vm, -+ struct i915_page_directory_pointer *pdp) -+{ -+ __pdp_fini(pdp); -+ -+ if (!i915_vm_is_4lvl(vm)) -+ return; -+ -+ cleanup_px(vm, pdp); -+ kfree(pdp); -+} -+ -+static void gen8_initialize_pdp(struct i915_address_space *vm, -+ struct i915_page_directory_pointer *pdp) -+{ -+ gen8_ppgtt_pdpe_t scratch_pdpe; -+ -+ scratch_pdpe = gen8_pdpe_encode(px_dma(vm->scratch_pd), I915_CACHE_LLC); -+ -+ fill_px(vm, pdp, scratch_pdpe); -+} -+ -+static void gen8_initialize_pml4(struct i915_address_space *vm, -+ struct i915_pml4 *pml4) -+{ -+ fill_px(vm, pml4, -+ gen8_pml4e_encode(px_dma(vm->scratch_pdp), I915_CACHE_LLC)); -+ memset_p((void **)pml4->pdps, vm->scratch_pdp, GEN8_PML4ES_PER_PML4); -+} -+ -+/* -+ * PDE TLBs are a pain to invalidate on GEN8+. When we modify -+ * the page table structures, we mark them dirty so that -+ * context switching/execlist queuing code takes extra steps -+ * to ensure that tlbs are flushed. -+ */ -+static void mark_tlbs_dirty(struct i915_hw_ppgtt *ppgtt) -+{ -+ ppgtt->pd_dirty_engines = ALL_ENGINES; -+} -+ -+/* Removes entries from a single page table, releasing it if it's empty. -+ * Caller can use the return value to update higher-level entries. -+ */ -+static bool gen8_ppgtt_clear_pt(const struct i915_address_space *vm, -+ struct i915_page_table *pt, -+ u64 start, u64 length) -+{ -+ unsigned int num_entries = gen8_pte_count(start, length); -+ gen8_pte_t *vaddr; -+ -+ GEM_BUG_ON(num_entries > pt->used_ptes); -+ -+ pt->used_ptes -= num_entries; -+ if (!pt->used_ptes) -+ return true; -+ -+ vaddr = kmap_atomic_px(pt); -+ memset64(vaddr + gen8_pte_index(start), vm->scratch_pte, num_entries); -+ kunmap_atomic(vaddr); -+ -+ return false; -+} -+ -+static void gen8_ppgtt_set_pde(struct i915_address_space *vm, -+ struct i915_page_directory *pd, -+ struct i915_page_table *pt, -+ unsigned int pde) -+{ -+ gen8_pde_t *vaddr; -+ -+ pd->page_table[pde] = pt; -+ -+ vaddr = kmap_atomic_px(pd); -+ vaddr[pde] = gen8_pde_encode(px_dma(pt), I915_CACHE_LLC); -+ kunmap_atomic(vaddr); -+} -+ -+static bool gen8_ppgtt_clear_pd(struct i915_address_space *vm, -+ struct i915_page_directory *pd, -+ u64 start, u64 length) -+{ -+ struct i915_page_table *pt; -+ u32 pde; -+ -+ gen8_for_each_pde(pt, pd, start, length, pde) { -+ GEM_BUG_ON(pt == vm->scratch_pt); -+ -+ if (!gen8_ppgtt_clear_pt(vm, pt, start, length)) -+ continue; -+ -+ gen8_ppgtt_set_pde(vm, pd, vm->scratch_pt, pde); -+ GEM_BUG_ON(!pd->used_pdes); -+ pd->used_pdes--; -+ -+ free_pt(vm, pt); -+ } -+ -+ return !pd->used_pdes; -+} -+ -+static void gen8_ppgtt_set_pdpe(struct i915_address_space *vm, -+ struct i915_page_directory_pointer *pdp, -+ struct i915_page_directory *pd, -+ unsigned int pdpe) -+{ -+ gen8_ppgtt_pdpe_t *vaddr; -+ -+ pdp->page_directory[pdpe] = pd; -+ if (!i915_vm_is_4lvl(vm)) -+ return; -+ -+ vaddr = kmap_atomic_px(pdp); -+ vaddr[pdpe] = gen8_pdpe_encode(px_dma(pd), I915_CACHE_LLC); -+ kunmap_atomic(vaddr); -+} -+ -+/* Removes entries from a single page dir pointer, releasing it if it's empty. -+ * Caller can use the return value to update higher-level entries -+ */ -+static bool gen8_ppgtt_clear_pdp(struct i915_address_space *vm, -+ struct i915_page_directory_pointer *pdp, -+ u64 start, u64 length) -+{ -+ struct i915_page_directory *pd; -+ unsigned int pdpe; -+ -+ gen8_for_each_pdpe(pd, pdp, start, length, pdpe) { -+ GEM_BUG_ON(pd == vm->scratch_pd); -+ -+ if (!gen8_ppgtt_clear_pd(vm, pd, start, length)) -+ continue; -+ -+ gen8_ppgtt_set_pdpe(vm, pdp, vm->scratch_pd, pdpe); -+ GEM_BUG_ON(!pdp->used_pdpes); -+ pdp->used_pdpes--; -+ -+ free_pd(vm, pd); -+ } -+ -+ return !pdp->used_pdpes; -+} -+ -+static void gen8_ppgtt_clear_3lvl(struct i915_address_space *vm, -+ u64 start, u64 length) -+{ -+ gen8_ppgtt_clear_pdp(vm, &i915_vm_to_ppgtt(vm)->pdp, start, length); -+} -+ -+static void gen8_ppgtt_set_pml4e(struct i915_pml4 *pml4, -+ struct i915_page_directory_pointer *pdp, -+ unsigned int pml4e) -+{ -+ gen8_ppgtt_pml4e_t *vaddr; -+ -+ pml4->pdps[pml4e] = pdp; -+ -+ vaddr = kmap_atomic_px(pml4); -+ vaddr[pml4e] = gen8_pml4e_encode(px_dma(pdp), I915_CACHE_LLC); -+ kunmap_atomic(vaddr); -+} -+ -+/* Removes entries from a single pml4. -+ * This is the top-level structure in 4-level page tables used on gen8+. -+ * Empty entries are always scratch pml4e. -+ */ -+static void gen8_ppgtt_clear_4lvl(struct i915_address_space *vm, -+ u64 start, u64 length) -+{ -+ struct i915_hw_ppgtt *ppgtt = i915_vm_to_ppgtt(vm); -+ struct i915_pml4 *pml4 = &ppgtt->pml4; -+ struct i915_page_directory_pointer *pdp; -+ unsigned int pml4e; -+ -+ GEM_BUG_ON(!i915_vm_is_4lvl(vm)); -+ -+ gen8_for_each_pml4e(pdp, pml4, start, length, pml4e) { -+ GEM_BUG_ON(pdp == vm->scratch_pdp); -+ -+ if (!gen8_ppgtt_clear_pdp(vm, pdp, start, length)) -+ continue; -+ -+ gen8_ppgtt_set_pml4e(pml4, vm->scratch_pdp, pml4e); -+ -+ free_pdp(vm, pdp); -+ } -+} -+ -+static inline struct sgt_dma { -+ struct scatterlist *sg; -+ dma_addr_t dma, max; -+} sgt_dma(struct i915_vma *vma) { -+ struct scatterlist *sg = vma->pages->sgl; -+ dma_addr_t addr = sg_dma_address(sg); -+ return (struct sgt_dma) { sg, addr, addr + sg->length }; -+} -+ -+struct gen8_insert_pte { -+ u16 pml4e; -+ u16 pdpe; -+ u16 pde; -+ u16 pte; -+}; -+ -+static __always_inline struct gen8_insert_pte gen8_insert_pte(u64 start) -+{ -+ return (struct gen8_insert_pte) { -+ gen8_pml4e_index(start), -+ gen8_pdpe_index(start), -+ gen8_pde_index(start), -+ gen8_pte_index(start), -+ }; -+} -+ -+static __always_inline bool -+gen8_ppgtt_insert_pte_entries(struct i915_hw_ppgtt *ppgtt, -+ struct i915_page_directory_pointer *pdp, -+ struct sgt_dma *iter, -+ struct gen8_insert_pte *idx, -+ enum i915_cache_level cache_level, -+ u32 flags) -+{ -+ struct i915_page_directory *pd; -+ const gen8_pte_t pte_encode = gen8_pte_encode(0, cache_level, flags); -+ gen8_pte_t *vaddr; -+ bool ret; -+ -+ GEM_BUG_ON(idx->pdpe >= i915_pdpes_per_pdp(&ppgtt->vm)); -+ pd = pdp->page_directory[idx->pdpe]; -+ vaddr = kmap_atomic_px(pd->page_table[idx->pde]); -+ do { -+ vaddr[idx->pte] = pte_encode | iter->dma; -+ -+ iter->dma += I915_GTT_PAGE_SIZE; -+ if (iter->dma >= iter->max) { -+ iter->sg = __sg_next(iter->sg); -+ if (!iter->sg) { -+ ret = false; -+ break; -+ } -+ -+ iter->dma = sg_dma_address(iter->sg); -+ iter->max = iter->dma + iter->sg->length; -+ } -+ -+ if (++idx->pte == GEN8_PTES) { -+ idx->pte = 0; -+ -+ if (++idx->pde == I915_PDES) { -+ idx->pde = 0; -+ -+ /* Limited by sg length for 3lvl */ -+ if (++idx->pdpe == GEN8_PML4ES_PER_PML4) { -+ idx->pdpe = 0; -+ ret = true; -+ break; -+ } -+ -+ GEM_BUG_ON(idx->pdpe >= i915_pdpes_per_pdp(&ppgtt->vm)); -+ pd = pdp->page_directory[idx->pdpe]; -+ } -+ -+ kunmap_atomic(vaddr); -+ vaddr = kmap_atomic_px(pd->page_table[idx->pde]); -+ } -+ } while (1); -+ kunmap_atomic(vaddr); -+ -+ return ret; -+} -+ -+static void gen8_ppgtt_insert_3lvl(struct i915_address_space *vm, -+ struct i915_vma *vma, -+ enum i915_cache_level cache_level, -+ u32 flags) -+{ -+ struct i915_hw_ppgtt *ppgtt = i915_vm_to_ppgtt(vm); -+ struct sgt_dma iter = sgt_dma(vma); -+ struct gen8_insert_pte idx = gen8_insert_pte(vma->node.start); -+ -+ gen8_ppgtt_insert_pte_entries(ppgtt, &ppgtt->pdp, &iter, &idx, -+ cache_level, flags); -+ -+ vma->page_sizes.gtt = I915_GTT_PAGE_SIZE; -+} -+ -+static void gen8_ppgtt_insert_huge_entries(struct i915_vma *vma, -+ struct i915_page_directory_pointer **pdps, -+ struct sgt_dma *iter, -+ enum i915_cache_level cache_level, -+ u32 flags) -+{ -+ const gen8_pte_t pte_encode = gen8_pte_encode(0, cache_level, flags); -+ u64 start = vma->node.start; -+ dma_addr_t rem = iter->sg->length; -+ -+ do { -+ struct gen8_insert_pte idx = gen8_insert_pte(start); -+ struct i915_page_directory_pointer *pdp = pdps[idx.pml4e]; -+ struct i915_page_directory *pd = pdp->page_directory[idx.pdpe]; -+ unsigned int page_size; -+ bool maybe_64K = false; -+ gen8_pte_t encode = pte_encode; -+ gen8_pte_t *vaddr; -+ u16 index, max; -+ -+ if (vma->page_sizes.sg & I915_GTT_PAGE_SIZE_2M && -+ IS_ALIGNED(iter->dma, I915_GTT_PAGE_SIZE_2M) && -+ rem >= I915_GTT_PAGE_SIZE_2M && !idx.pte) { -+ index = idx.pde; -+ max = I915_PDES; -+ page_size = I915_GTT_PAGE_SIZE_2M; -+ -+ encode |= GEN8_PDE_PS_2M; -+ -+ vaddr = kmap_atomic_px(pd); -+ } else { -+ struct i915_page_table *pt = pd->page_table[idx.pde]; -+ -+ index = idx.pte; -+ max = GEN8_PTES; -+ page_size = I915_GTT_PAGE_SIZE; -+ -+ if (!index && -+ vma->page_sizes.sg & I915_GTT_PAGE_SIZE_64K && -+ IS_ALIGNED(iter->dma, I915_GTT_PAGE_SIZE_64K) && -+ (IS_ALIGNED(rem, I915_GTT_PAGE_SIZE_64K) || -+ rem >= (max - index) * I915_GTT_PAGE_SIZE)) -+ maybe_64K = true; -+ -+ vaddr = kmap_atomic_px(pt); -+ } -+ -+ do { -+ GEM_BUG_ON(iter->sg->length < page_size); -+ vaddr[index++] = encode | iter->dma; -+ -+ start += page_size; -+ iter->dma += page_size; -+ rem -= page_size; -+ if (iter->dma >= iter->max) { -+ iter->sg = __sg_next(iter->sg); -+ if (!iter->sg) -+ break; -+ -+ rem = iter->sg->length; -+ iter->dma = sg_dma_address(iter->sg); -+ iter->max = iter->dma + rem; -+ -+ if (maybe_64K && index < max && -+ !(IS_ALIGNED(iter->dma, I915_GTT_PAGE_SIZE_64K) && -+ (IS_ALIGNED(rem, I915_GTT_PAGE_SIZE_64K) || -+ rem >= (max - index) * I915_GTT_PAGE_SIZE))) -+ maybe_64K = false; -+ -+ if (unlikely(!IS_ALIGNED(iter->dma, page_size))) -+ break; -+ } -+ } while (rem >= page_size && index < max); -+ -+ kunmap_atomic(vaddr); -+ -+ /* -+ * Is it safe to mark the 2M block as 64K? -- Either we have -+ * filled whole page-table with 64K entries, or filled part of -+ * it and have reached the end of the sg table and we have -+ * enough padding. -+ */ -+ if (maybe_64K && -+ (index == max || -+ (i915_vm_has_scratch_64K(vma->vm) && -+ !iter->sg && IS_ALIGNED(vma->node.start + -+ vma->node.size, -+ I915_GTT_PAGE_SIZE_2M)))) { -+ vaddr = kmap_atomic_px(pd); -+ vaddr[idx.pde] |= GEN8_PDE_IPS_64K; -+ kunmap_atomic(vaddr); -+ page_size = I915_GTT_PAGE_SIZE_64K; -+ -+ /* -+ * We write all 4K page entries, even when using 64K -+ * pages. In order to verify that the HW isn't cheating -+ * by using the 4K PTE instead of the 64K PTE, we want -+ * to remove all the surplus entries. If the HW skipped -+ * the 64K PTE, it will read/write into the scratch page -+ * instead - which we detect as missing results during -+ * selftests. -+ */ -+ if (I915_SELFTEST_ONLY(vma->vm->scrub_64K)) { -+ u16 i; -+ -+ encode = vma->vm->scratch_pte; -+ vaddr = kmap_atomic_px(pd->page_table[idx.pde]); -+ -+ for (i = 1; i < index; i += 16) -+ memset64(vaddr + i, encode, 15); -+ -+ kunmap_atomic(vaddr); -+ } -+ } -+ -+ vma->page_sizes.gtt |= page_size; -+ } while (iter->sg); -+} -+ -+static void gen8_ppgtt_insert_4lvl(struct i915_address_space *vm, -+ struct i915_vma *vma, -+ enum i915_cache_level cache_level, -+ u32 flags) -+{ -+ struct i915_hw_ppgtt *ppgtt = i915_vm_to_ppgtt(vm); -+ struct sgt_dma iter = sgt_dma(vma); -+ struct i915_page_directory_pointer **pdps = ppgtt->pml4.pdps; -+ -+ if (vma->page_sizes.sg > I915_GTT_PAGE_SIZE) { -+ gen8_ppgtt_insert_huge_entries(vma, pdps, &iter, cache_level, -+ flags); -+ } else { -+ struct gen8_insert_pte idx = gen8_insert_pte(vma->node.start); -+ -+ while (gen8_ppgtt_insert_pte_entries(ppgtt, pdps[idx.pml4e++], -+ &iter, &idx, cache_level, -+ flags)) -+ GEM_BUG_ON(idx.pml4e >= GEN8_PML4ES_PER_PML4); -+ -+ vma->page_sizes.gtt = I915_GTT_PAGE_SIZE; -+ } -+} -+ -+static void gen8_free_page_tables(struct i915_address_space *vm, -+ struct i915_page_directory *pd) -+{ -+ int i; -+ -+ for (i = 0; i < I915_PDES; i++) { -+ if (pd->page_table[i] != vm->scratch_pt) -+ free_pt(vm, pd->page_table[i]); -+ } -+} -+ -+static int gen8_init_scratch(struct i915_address_space *vm) -+{ -+ int ret; -+ -+ /* -+ * If everybody agrees to not to write into the scratch page, -+ * we can reuse it for all vm, keeping contexts and processes separate. -+ */ -+ if (vm->has_read_only && -+ vm->i915->kernel_context && -+ vm->i915->kernel_context->ppgtt) { -+ struct i915_address_space *clone = -+ &vm->i915->kernel_context->ppgtt->vm; -+ -+ GEM_BUG_ON(!clone->has_read_only); -+ -+ vm->scratch_order = clone->scratch_order; -+ vm->scratch_pte = clone->scratch_pte; -+ vm->scratch_pt = clone->scratch_pt; -+ vm->scratch_pd = clone->scratch_pd; -+ vm->scratch_pdp = clone->scratch_pdp; -+ return 0; -+ } -+ -+ ret = setup_scratch_page(vm, __GFP_HIGHMEM); -+ if (ret) -+ return ret; -+ -+ vm->scratch_pte = -+ gen8_pte_encode(vm->scratch_page.daddr, -+ I915_CACHE_LLC, -+ vm->has_read_only); -+ -+ vm->scratch_pt = alloc_pt(vm); -+ if (IS_ERR(vm->scratch_pt)) { -+ ret = PTR_ERR(vm->scratch_pt); -+ goto free_scratch_page; -+ } -+ -+ vm->scratch_pd = alloc_pd(vm); -+ if (IS_ERR(vm->scratch_pd)) { -+ ret = PTR_ERR(vm->scratch_pd); -+ goto free_pt; -+ } -+ -+ if (i915_vm_is_4lvl(vm)) { -+ vm->scratch_pdp = alloc_pdp(vm); -+ if (IS_ERR(vm->scratch_pdp)) { -+ ret = PTR_ERR(vm->scratch_pdp); -+ goto free_pd; -+ } -+ } -+ -+ gen8_initialize_pt(vm, vm->scratch_pt); -+ gen8_initialize_pd(vm, vm->scratch_pd); -+ if (i915_vm_is_4lvl(vm)) -+ gen8_initialize_pdp(vm, vm->scratch_pdp); -+ -+ return 0; -+ -+free_pd: -+ free_pd(vm, vm->scratch_pd); -+free_pt: -+ free_pt(vm, vm->scratch_pt); -+free_scratch_page: -+ cleanup_scratch_page(vm); -+ -+ return ret; -+} -+ -+static int gen8_ppgtt_notify_vgt(struct i915_hw_ppgtt *ppgtt, bool create) -+{ -+ struct i915_address_space *vm = &ppgtt->vm; -+ struct drm_i915_private *dev_priv = vm->i915; -+ enum vgt_g2v_type msg; -+ int i; -+ -+ if (i915_vm_is_4lvl(vm)) { -+ const u64 daddr = px_dma(&ppgtt->pml4); -+ -+ I915_WRITE(vgtif_reg(pdp[0].lo), lower_32_bits(daddr)); -+ I915_WRITE(vgtif_reg(pdp[0].hi), upper_32_bits(daddr)); -+ -+ msg = (create ? VGT_G2V_PPGTT_L4_PAGE_TABLE_CREATE : -+ VGT_G2V_PPGTT_L4_PAGE_TABLE_DESTROY); -+ } else { -+ for (i = 0; i < GEN8_3LVL_PDPES; i++) { -+ const u64 daddr = i915_page_dir_dma_addr(ppgtt, i); -+ -+ I915_WRITE(vgtif_reg(pdp[i].lo), lower_32_bits(daddr)); -+ I915_WRITE(vgtif_reg(pdp[i].hi), upper_32_bits(daddr)); -+ } -+ -+ msg = (create ? VGT_G2V_PPGTT_L3_PAGE_TABLE_CREATE : -+ VGT_G2V_PPGTT_L3_PAGE_TABLE_DESTROY); -+ } -+ -+ I915_WRITE(vgtif_reg(g2v_notify), msg); -+ -+ return 0; -+} -+ -+static void gen8_free_scratch(struct i915_address_space *vm) -+{ -+ if (!vm->scratch_page.daddr) -+ return; -+ -+ if (i915_vm_is_4lvl(vm)) -+ free_pdp(vm, vm->scratch_pdp); -+ free_pd(vm, vm->scratch_pd); -+ free_pt(vm, vm->scratch_pt); -+ cleanup_scratch_page(vm); -+} -+ -+static void gen8_ppgtt_cleanup_3lvl(struct i915_address_space *vm, -+ struct i915_page_directory_pointer *pdp) -+{ -+ const unsigned int pdpes = i915_pdpes_per_pdp(vm); -+ int i; -+ -+ for (i = 0; i < pdpes; i++) { -+ if (pdp->page_directory[i] == vm->scratch_pd) -+ continue; -+ -+ gen8_free_page_tables(vm, pdp->page_directory[i]); -+ free_pd(vm, pdp->page_directory[i]); -+ } -+ -+ free_pdp(vm, pdp); -+} -+ -+static void gen8_ppgtt_cleanup_4lvl(struct i915_hw_ppgtt *ppgtt) -+{ -+ int i; -+ -+ for (i = 0; i < GEN8_PML4ES_PER_PML4; i++) { -+ if (ppgtt->pml4.pdps[i] == ppgtt->vm.scratch_pdp) -+ continue; -+ -+ gen8_ppgtt_cleanup_3lvl(&ppgtt->vm, ppgtt->pml4.pdps[i]); -+ } -+ -+ cleanup_px(&ppgtt->vm, &ppgtt->pml4); -+} -+ -+static void gen8_ppgtt_cleanup(struct i915_address_space *vm) -+{ -+ struct drm_i915_private *dev_priv = vm->i915; -+ struct i915_hw_ppgtt *ppgtt = i915_vm_to_ppgtt(vm); -+ -+ if (intel_vgpu_active(dev_priv)) -+ gen8_ppgtt_notify_vgt(ppgtt, false); -+ -+ if (i915_vm_is_4lvl(vm)) -+ gen8_ppgtt_cleanup_4lvl(ppgtt); -+ else -+ gen8_ppgtt_cleanup_3lvl(&ppgtt->vm, &ppgtt->pdp); -+ -+ gen8_free_scratch(vm); -+} -+ -+static int gen8_ppgtt_alloc_pd(struct i915_address_space *vm, -+ struct i915_page_directory *pd, -+ u64 start, u64 length) -+{ -+ struct i915_page_table *pt; -+ u64 from = start; -+ unsigned int pde; -+ -+ gen8_for_each_pde(pt, pd, start, length, pde) { -+ int count = gen8_pte_count(start, length); -+ -+ if (pt == vm->scratch_pt) { -+ pd->used_pdes++; -+ -+ pt = alloc_pt(vm); -+ if (IS_ERR(pt)) { -+ pd->used_pdes--; -+ goto unwind; -+ } -+ -+ if (count < GEN8_PTES || intel_vgpu_active(vm->i915)) -+ gen8_initialize_pt(vm, pt); -+ -+ gen8_ppgtt_set_pde(vm, pd, pt, pde); -+ GEM_BUG_ON(pd->used_pdes > I915_PDES); -+ } -+ -+ pt->used_ptes += count; -+ } -+ return 0; -+ -+unwind: -+ gen8_ppgtt_clear_pd(vm, pd, from, start - from); -+ return -ENOMEM; -+} -+ -+static int gen8_ppgtt_alloc_pdp(struct i915_address_space *vm, -+ struct i915_page_directory_pointer *pdp, -+ u64 start, u64 length) -+{ -+ struct i915_page_directory *pd; -+ u64 from = start; -+ unsigned int pdpe; -+ int ret; -+ -+ gen8_for_each_pdpe(pd, pdp, start, length, pdpe) { -+ if (pd == vm->scratch_pd) { -+ pdp->used_pdpes++; -+ -+ pd = alloc_pd(vm); -+ if (IS_ERR(pd)) { -+ pdp->used_pdpes--; -+ goto unwind; -+ } -+ -+ gen8_initialize_pd(vm, pd); -+ gen8_ppgtt_set_pdpe(vm, pdp, pd, pdpe); -+ GEM_BUG_ON(pdp->used_pdpes > i915_pdpes_per_pdp(vm)); -+ } -+ -+ ret = gen8_ppgtt_alloc_pd(vm, pd, start, length); -+ if (unlikely(ret)) -+ goto unwind_pd; -+ } -+ -+ return 0; -+ -+unwind_pd: -+ if (!pd->used_pdes) { -+ gen8_ppgtt_set_pdpe(vm, pdp, vm->scratch_pd, pdpe); -+ GEM_BUG_ON(!pdp->used_pdpes); -+ pdp->used_pdpes--; -+ free_pd(vm, pd); -+ } -+unwind: -+ gen8_ppgtt_clear_pdp(vm, pdp, from, start - from); -+ return -ENOMEM; -+} -+ -+static int gen8_ppgtt_alloc_3lvl(struct i915_address_space *vm, -+ u64 start, u64 length) -+{ -+ return gen8_ppgtt_alloc_pdp(vm, -+ &i915_vm_to_ppgtt(vm)->pdp, start, length); -+} -+ -+static int gen8_ppgtt_alloc_4lvl(struct i915_address_space *vm, -+ u64 start, u64 length) -+{ -+ struct i915_hw_ppgtt *ppgtt = i915_vm_to_ppgtt(vm); -+ struct i915_pml4 *pml4 = &ppgtt->pml4; -+ struct i915_page_directory_pointer *pdp; -+ u64 from = start; -+ u32 pml4e; -+ int ret; -+ -+ gen8_for_each_pml4e(pdp, pml4, start, length, pml4e) { -+ if (pml4->pdps[pml4e] == vm->scratch_pdp) { -+ pdp = alloc_pdp(vm); -+ if (IS_ERR(pdp)) -+ goto unwind; -+ -+ gen8_initialize_pdp(vm, pdp); -+ gen8_ppgtt_set_pml4e(pml4, pdp, pml4e); -+ } -+ -+ ret = gen8_ppgtt_alloc_pdp(vm, pdp, start, length); -+ if (unlikely(ret)) -+ goto unwind_pdp; -+ } -+ -+ return 0; -+ -+unwind_pdp: -+ if (!pdp->used_pdpes) { -+ gen8_ppgtt_set_pml4e(pml4, vm->scratch_pdp, pml4e); -+ free_pdp(vm, pdp); -+ } -+unwind: -+ gen8_ppgtt_clear_4lvl(vm, from, start - from); -+ return -ENOMEM; -+} -+ -+static int gen8_preallocate_top_level_pdp(struct i915_hw_ppgtt *ppgtt) -+{ -+ struct i915_address_space *vm = &ppgtt->vm; -+ struct i915_page_directory_pointer *pdp = &ppgtt->pdp; -+ struct i915_page_directory *pd; -+ u64 start = 0, length = ppgtt->vm.total; -+ u64 from = start; -+ unsigned int pdpe; -+ -+ gen8_for_each_pdpe(pd, pdp, start, length, pdpe) { -+ pd = alloc_pd(vm); -+ if (IS_ERR(pd)) -+ goto unwind; -+ -+ gen8_initialize_pd(vm, pd); -+ gen8_ppgtt_set_pdpe(vm, pdp, pd, pdpe); -+ pdp->used_pdpes++; -+ } -+ -+ pdp->used_pdpes++; /* never remove */ -+ return 0; -+ -+unwind: -+ start -= from; -+ gen8_for_each_pdpe(pd, pdp, from, start, pdpe) { -+ gen8_ppgtt_set_pdpe(vm, pdp, vm->scratch_pd, pdpe); -+ free_pd(vm, pd); -+ } -+ pdp->used_pdpes = 0; -+ return -ENOMEM; -+} -+ -+static void ppgtt_init(struct drm_i915_private *i915, -+ struct i915_hw_ppgtt *ppgtt) -+{ -+ kref_init(&ppgtt->ref); -+ -+ ppgtt->vm.i915 = i915; -+ ppgtt->vm.dma = &i915->drm.pdev->dev; -+ ppgtt->vm.total = BIT_ULL(INTEL_INFO(i915)->ppgtt_size); -+ -+ i915_address_space_init(&ppgtt->vm, VM_CLASS_PPGTT); -+ -+ ppgtt->vm.vma_ops.bind_vma = ppgtt_bind_vma; -+ ppgtt->vm.vma_ops.unbind_vma = ppgtt_unbind_vma; -+ ppgtt->vm.vma_ops.set_pages = ppgtt_set_pages; -+ ppgtt->vm.vma_ops.clear_pages = clear_pages; -+} -+ -+/* -+ * GEN8 legacy ppgtt programming is accomplished through a max 4 PDP registers -+ * with a net effect resembling a 2-level page table in normal x86 terms. Each -+ * PDP represents 1GB of memory 4 * 512 * 512 * 4096 = 4GB legacy 32b address -+ * space. -+ * -+ */ -+static struct i915_hw_ppgtt *gen8_ppgtt_create(struct drm_i915_private *i915) -+{ -+ struct i915_hw_ppgtt *ppgtt; -+ int err; -+ -+ ppgtt = kzalloc(sizeof(*ppgtt), GFP_KERNEL); -+ if (!ppgtt) -+ return ERR_PTR(-ENOMEM); -+ -+ ppgtt_init(i915, ppgtt); -+ -+ /* -+ * From bdw, there is hw support for read-only pages in the PPGTT. -+ * -+ * Gen11 has HSDES#:1807136187 unresolved. Disable ro support -+ * for now. -+ */ -+ ppgtt->vm.has_read_only = INTEL_GEN(i915) != 11; -+ -+ /* There are only few exceptions for gen >=6. chv and bxt. -+ * And we are not sure about the latter so play safe for now. -+ */ -+ if (IS_CHERRYVIEW(i915) || IS_BROXTON(i915)) -+ ppgtt->vm.pt_kmap_wc = true; -+ -+ err = gen8_init_scratch(&ppgtt->vm); -+ if (err) -+ goto err_free; -+ -+ if (i915_vm_is_4lvl(&ppgtt->vm)) { -+ err = setup_px(&ppgtt->vm, &ppgtt->pml4); -+ if (err) -+ goto err_scratch; -+ -+ gen8_initialize_pml4(&ppgtt->vm, &ppgtt->pml4); -+ -+ ppgtt->vm.allocate_va_range = gen8_ppgtt_alloc_4lvl; -+ ppgtt->vm.insert_entries = gen8_ppgtt_insert_4lvl; -+ ppgtt->vm.clear_range = gen8_ppgtt_clear_4lvl; -+ } else { -+ err = __pdp_init(&ppgtt->vm, &ppgtt->pdp); -+ if (err) -+ goto err_scratch; -+ -+ if (intel_vgpu_active(i915)) { -+ err = gen8_preallocate_top_level_pdp(ppgtt); -+ if (err) { -+ __pdp_fini(&ppgtt->pdp); -+ goto err_scratch; -+ } -+ } -+ -+ ppgtt->vm.allocate_va_range = gen8_ppgtt_alloc_3lvl; -+ ppgtt->vm.insert_entries = gen8_ppgtt_insert_3lvl; -+ ppgtt->vm.clear_range = gen8_ppgtt_clear_3lvl; -+ } -+ -+ if (intel_vgpu_active(i915)) -+ gen8_ppgtt_notify_vgt(ppgtt, true); -+ -+ ppgtt->vm.cleanup = gen8_ppgtt_cleanup; -+ -+ return ppgtt; -+ -+err_scratch: -+ gen8_free_scratch(&ppgtt->vm); -+err_free: -+ kfree(ppgtt); -+ return ERR_PTR(err); -+} -+ -+/* Write pde (index) from the page directory @pd to the page table @pt */ -+static inline void gen6_write_pde(const struct gen6_hw_ppgtt *ppgtt, -+ const unsigned int pde, -+ const struct i915_page_table *pt) -+{ -+ /* Caller needs to make sure the write completes if necessary */ -+ iowrite32(GEN6_PDE_ADDR_ENCODE(px_dma(pt)) | GEN6_PDE_VALID, -+ ppgtt->pd_addr + pde); -+} -+ -+static void gen7_ppgtt_enable(struct drm_i915_private *dev_priv) -+{ -+ struct intel_engine_cs *engine; -+ u32 ecochk, ecobits; -+ enum intel_engine_id id; -+ -+ ecobits = I915_READ(GAC_ECO_BITS); -+ I915_WRITE(GAC_ECO_BITS, ecobits | ECOBITS_PPGTT_CACHE64B); -+ -+ ecochk = I915_READ(GAM_ECOCHK); -+ if (IS_HASWELL(dev_priv)) { -+ ecochk |= ECOCHK_PPGTT_WB_HSW; -+ } else { -+ ecochk |= ECOCHK_PPGTT_LLC_IVB; -+ ecochk &= ~ECOCHK_PPGTT_GFDT_IVB; -+ } -+ I915_WRITE(GAM_ECOCHK, ecochk); -+ -+ for_each_engine(engine, dev_priv, id) { -+ /* GFX_MODE is per-ring on gen7+ */ -+ I915_WRITE(RING_MODE_GEN7(engine), -+ _MASKED_BIT_ENABLE(GFX_PPGTT_ENABLE)); -+ } -+} -+ -+static void gen6_ppgtt_enable(struct drm_i915_private *dev_priv) -+{ -+ u32 ecochk, gab_ctl, ecobits; -+ -+ ecobits = I915_READ(GAC_ECO_BITS); -+ I915_WRITE(GAC_ECO_BITS, ecobits | ECOBITS_SNB_BIT | -+ ECOBITS_PPGTT_CACHE64B); -+ -+ gab_ctl = I915_READ(GAB_CTL); -+ I915_WRITE(GAB_CTL, gab_ctl | GAB_CTL_CONT_AFTER_PAGEFAULT); -+ -+ ecochk = I915_READ(GAM_ECOCHK); -+ I915_WRITE(GAM_ECOCHK, ecochk | ECOCHK_SNB_BIT | ECOCHK_PPGTT_CACHE64B); -+ -+ if (HAS_PPGTT(dev_priv)) /* may be disabled for VT-d */ -+ I915_WRITE(GFX_MODE, _MASKED_BIT_ENABLE(GFX_PPGTT_ENABLE)); -+} -+ -+/* PPGTT support for Sandybdrige/Gen6 and later */ -+static void gen6_ppgtt_clear_range(struct i915_address_space *vm, -+ u64 start, u64 length) -+{ -+ struct gen6_hw_ppgtt *ppgtt = to_gen6_ppgtt(i915_vm_to_ppgtt(vm)); -+ unsigned int first_entry = start / I915_GTT_PAGE_SIZE; -+ unsigned int pde = first_entry / GEN6_PTES; -+ unsigned int pte = first_entry % GEN6_PTES; -+ unsigned int num_entries = length / I915_GTT_PAGE_SIZE; -+ const gen6_pte_t scratch_pte = vm->scratch_pte; -+ -+ while (num_entries) { -+ struct i915_page_table *pt = ppgtt->base.pd.page_table[pde++]; -+ const unsigned int count = min(num_entries, GEN6_PTES - pte); -+ gen6_pte_t *vaddr; -+ -+ GEM_BUG_ON(pt == vm->scratch_pt); -+ -+ num_entries -= count; -+ -+ GEM_BUG_ON(count > pt->used_ptes); -+ pt->used_ptes -= count; -+ if (!pt->used_ptes) -+ ppgtt->scan_for_unused_pt = true; -+ -+ /* -+ * Note that the hw doesn't support removing PDE on the fly -+ * (they are cached inside the context with no means to -+ * invalidate the cache), so we can only reset the PTE -+ * entries back to scratch. -+ */ -+ -+ vaddr = kmap_atomic_px(pt); -+ memset32(vaddr + pte, scratch_pte, count); -+ kunmap_atomic(vaddr); -+ -+ pte = 0; -+ } -+} -+ -+static void gen6_ppgtt_insert_entries(struct i915_address_space *vm, -+ struct i915_vma *vma, -+ enum i915_cache_level cache_level, -+ u32 flags) -+{ -+ struct i915_hw_ppgtt *ppgtt = i915_vm_to_ppgtt(vm); -+ unsigned first_entry = vma->node.start / I915_GTT_PAGE_SIZE; -+ unsigned act_pt = first_entry / GEN6_PTES; -+ unsigned act_pte = first_entry % GEN6_PTES; -+ const u32 pte_encode = vm->pte_encode(0, cache_level, flags); -+ struct sgt_dma iter = sgt_dma(vma); -+ gen6_pte_t *vaddr; -+ -+ GEM_BUG_ON(ppgtt->pd.page_table[act_pt] == vm->scratch_pt); -+ -+ vaddr = kmap_atomic_px(ppgtt->pd.page_table[act_pt]); -+ do { -+ vaddr[act_pte] = pte_encode | GEN6_PTE_ADDR_ENCODE(iter.dma); -+ -+ iter.dma += I915_GTT_PAGE_SIZE; -+ if (iter.dma == iter.max) { -+ iter.sg = __sg_next(iter.sg); -+ if (!iter.sg) -+ break; -+ -+ iter.dma = sg_dma_address(iter.sg); -+ iter.max = iter.dma + iter.sg->length; -+ } -+ -+ if (++act_pte == GEN6_PTES) { -+ kunmap_atomic(vaddr); -+ vaddr = kmap_atomic_px(ppgtt->pd.page_table[++act_pt]); -+ act_pte = 0; -+ } -+ } while (1); -+ kunmap_atomic(vaddr); -+ -+ vma->page_sizes.gtt = I915_GTT_PAGE_SIZE; -+} -+ -+static int gen6_alloc_va_range(struct i915_address_space *vm, -+ u64 start, u64 length) -+{ -+ struct gen6_hw_ppgtt *ppgtt = to_gen6_ppgtt(i915_vm_to_ppgtt(vm)); -+ struct i915_page_table *pt; -+ u64 from = start; -+ unsigned int pde; -+ bool flush = false; -+ -+ gen6_for_each_pde(pt, &ppgtt->base.pd, start, length, pde) { -+ const unsigned int count = gen6_pte_count(start, length); -+ -+ if (pt == vm->scratch_pt) { -+ pt = alloc_pt(vm); -+ if (IS_ERR(pt)) -+ goto unwind_out; -+ -+ gen6_initialize_pt(vm, pt); -+ ppgtt->base.pd.page_table[pde] = pt; -+ -+ if (i915_vma_is_bound(ppgtt->vma, -+ I915_VMA_GLOBAL_BIND)) { -+ gen6_write_pde(ppgtt, pde, pt); -+ flush = true; -+ } -+ -+ GEM_BUG_ON(pt->used_ptes); -+ } -+ -+ pt->used_ptes += count; -+ } -+ -+ if (flush) { -+ mark_tlbs_dirty(&ppgtt->base); -+ gen6_ggtt_invalidate(ppgtt->base.vm.i915); -+ } -+ -+ return 0; -+ -+unwind_out: -+ gen6_ppgtt_clear_range(vm, from, start - from); -+ return -ENOMEM; -+} -+ -+static int gen6_ppgtt_init_scratch(struct gen6_hw_ppgtt *ppgtt) -+{ -+ struct i915_address_space * const vm = &ppgtt->base.vm; -+ struct i915_page_table *unused; -+ u32 pde; -+ int ret; -+ -+ ret = setup_scratch_page(vm, __GFP_HIGHMEM); -+ if (ret) -+ return ret; -+ -+ vm->scratch_pte = vm->pte_encode(vm->scratch_page.daddr, -+ I915_CACHE_NONE, -+ PTE_READ_ONLY); -+ -+ vm->scratch_pt = alloc_pt(vm); -+ if (IS_ERR(vm->scratch_pt)) { -+ cleanup_scratch_page(vm); -+ return PTR_ERR(vm->scratch_pt); -+ } -+ -+ gen6_initialize_pt(vm, vm->scratch_pt); -+ gen6_for_all_pdes(unused, &ppgtt->base.pd, pde) -+ ppgtt->base.pd.page_table[pde] = vm->scratch_pt; -+ -+ return 0; -+} -+ -+static void gen6_ppgtt_free_scratch(struct i915_address_space *vm) -+{ -+ free_pt(vm, vm->scratch_pt); -+ cleanup_scratch_page(vm); -+} -+ -+static void gen6_ppgtt_free_pd(struct gen6_hw_ppgtt *ppgtt) -+{ -+ struct i915_page_table *pt; -+ u32 pde; -+ -+ gen6_for_all_pdes(pt, &ppgtt->base.pd, pde) -+ if (pt != ppgtt->base.vm.scratch_pt) -+ free_pt(&ppgtt->base.vm, pt); -+} -+ -+static void gen6_ppgtt_cleanup(struct i915_address_space *vm) -+{ -+ struct gen6_hw_ppgtt *ppgtt = to_gen6_ppgtt(i915_vm_to_ppgtt(vm)); -+ -+ i915_vma_destroy(ppgtt->vma); -+ -+ gen6_ppgtt_free_pd(ppgtt); -+ gen6_ppgtt_free_scratch(vm); -+} -+ -+static int pd_vma_set_pages(struct i915_vma *vma) -+{ -+ vma->pages = ERR_PTR(-ENODEV); -+ return 0; -+} -+ -+static void pd_vma_clear_pages(struct i915_vma *vma) -+{ -+ GEM_BUG_ON(!vma->pages); -+ -+ vma->pages = NULL; -+} -+ -+static int pd_vma_bind(struct i915_vma *vma, -+ enum i915_cache_level cache_level, -+ u32 unused) -+{ -+ struct i915_ggtt *ggtt = i915_vm_to_ggtt(vma->vm); -+ struct gen6_hw_ppgtt *ppgtt = vma->private; -+ u32 ggtt_offset = i915_ggtt_offset(vma) / I915_GTT_PAGE_SIZE; -+ struct i915_page_table *pt; -+ unsigned int pde; -+ -+ ppgtt->base.pd.base.ggtt_offset = ggtt_offset * sizeof(gen6_pte_t); -+ ppgtt->pd_addr = (gen6_pte_t __iomem *)ggtt->gsm + ggtt_offset; -+ -+ gen6_for_all_pdes(pt, &ppgtt->base.pd, pde) -+ gen6_write_pde(ppgtt, pde, pt); -+ -+ mark_tlbs_dirty(&ppgtt->base); -+ gen6_ggtt_invalidate(ppgtt->base.vm.i915); -+ -+ return 0; -+} -+ -+static void pd_vma_unbind(struct i915_vma *vma) -+{ -+ struct gen6_hw_ppgtt *ppgtt = vma->private; -+ struct i915_page_table * const scratch_pt = ppgtt->base.vm.scratch_pt; -+ struct i915_page_table *pt; -+ unsigned int pde; -+ -+ if (!ppgtt->scan_for_unused_pt) -+ return; -+ -+ /* Free all no longer used page tables */ -+ gen6_for_all_pdes(pt, &ppgtt->base.pd, pde) { -+ if (pt->used_ptes || pt == scratch_pt) -+ continue; -+ -+ free_pt(&ppgtt->base.vm, pt); -+ ppgtt->base.pd.page_table[pde] = scratch_pt; -+ } -+ -+ ppgtt->scan_for_unused_pt = false; -+} -+ -+static const struct i915_vma_ops pd_vma_ops = { -+ .set_pages = pd_vma_set_pages, -+ .clear_pages = pd_vma_clear_pages, -+ .bind_vma = pd_vma_bind, -+ .unbind_vma = pd_vma_unbind, -+}; -+ -+static struct i915_vma *pd_vma_create(struct gen6_hw_ppgtt *ppgtt, int size) -+{ -+ struct drm_i915_private *i915 = ppgtt->base.vm.i915; -+ struct i915_ggtt *ggtt = &i915->ggtt; -+ struct i915_vma *vma; -+ -+ GEM_BUG_ON(!IS_ALIGNED(size, I915_GTT_PAGE_SIZE)); -+ GEM_BUG_ON(size > ggtt->vm.total); -+ -+ vma = i915_vma_alloc(); -+ if (!vma) -+ return ERR_PTR(-ENOMEM); -+ -+ i915_active_init(i915, &vma->active, NULL); -+ INIT_ACTIVE_REQUEST(&vma->last_fence); -+ -+ vma->vm = &ggtt->vm; -+ vma->ops = &pd_vma_ops; -+ vma->private = ppgtt; -+ -+ vma->size = size; -+ vma->fence_size = size; -+ vma->flags = I915_VMA_GGTT; -+ vma->ggtt_view.type = I915_GGTT_VIEW_ROTATED; /* prevent fencing */ -+ -+ INIT_LIST_HEAD(&vma->obj_link); -+ -+ mutex_lock(&vma->vm->mutex); -+ list_add(&vma->vm_link, &vma->vm->unbound_list); -+ mutex_unlock(&vma->vm->mutex); -+ -+ return vma; -+} -+ -+int gen6_ppgtt_pin(struct i915_hw_ppgtt *base) -+{ -+ struct gen6_hw_ppgtt *ppgtt = to_gen6_ppgtt(base); -+ int err; -+ -+ GEM_BUG_ON(ppgtt->base.vm.closed); -+ -+ /* -+ * Workaround the limited maximum vma->pin_count and the aliasing_ppgtt -+ * which will be pinned into every active context. -+ * (When vma->pin_count becomes atomic, I expect we will naturally -+ * need a larger, unpacked, type and kill this redundancy.) -+ */ -+ if (ppgtt->pin_count++) -+ return 0; -+ -+ /* -+ * PPGTT PDEs reside in the GGTT and consists of 512 entries. The -+ * allocator works in address space sizes, so it's multiplied by page -+ * size. We allocate at the top of the GTT to avoid fragmentation. -+ */ -+ err = i915_vma_pin(ppgtt->vma, -+ 0, GEN6_PD_ALIGN, -+ PIN_GLOBAL | PIN_HIGH); -+ if (err) -+ goto unpin; -+ -+ return 0; -+ -+unpin: -+ ppgtt->pin_count = 0; -+ return err; -+} -+ -+void gen6_ppgtt_unpin(struct i915_hw_ppgtt *base) -+{ -+ struct gen6_hw_ppgtt *ppgtt = to_gen6_ppgtt(base); -+ -+ GEM_BUG_ON(!ppgtt->pin_count); -+ if (--ppgtt->pin_count) -+ return; -+ -+ i915_vma_unpin(ppgtt->vma); -+} -+ -+void gen6_ppgtt_unpin_all(struct i915_hw_ppgtt *base) -+{ -+ struct gen6_hw_ppgtt *ppgtt = to_gen6_ppgtt(base); -+ -+ if (!ppgtt->pin_count) -+ return; -+ -+ ppgtt->pin_count = 0; -+ i915_vma_unpin(ppgtt->vma); -+} -+ -+static struct i915_hw_ppgtt *gen6_ppgtt_create(struct drm_i915_private *i915) -+{ -+ struct i915_ggtt * const ggtt = &i915->ggtt; -+ struct gen6_hw_ppgtt *ppgtt; -+ int err; -+ -+ ppgtt = kzalloc(sizeof(*ppgtt), GFP_KERNEL); -+ if (!ppgtt) -+ return ERR_PTR(-ENOMEM); -+ -+ ppgtt_init(i915, &ppgtt->base); -+ -+ ppgtt->base.vm.allocate_va_range = gen6_alloc_va_range; -+ ppgtt->base.vm.clear_range = gen6_ppgtt_clear_range; -+ ppgtt->base.vm.insert_entries = gen6_ppgtt_insert_entries; -+ ppgtt->base.vm.cleanup = gen6_ppgtt_cleanup; -+ -+ ppgtt->base.vm.pte_encode = ggtt->vm.pte_encode; -+ -+ err = gen6_ppgtt_init_scratch(ppgtt); -+ if (err) -+ goto err_free; -+ -+ ppgtt->vma = pd_vma_create(ppgtt, GEN6_PD_SIZE); -+ if (IS_ERR(ppgtt->vma)) { -+ err = PTR_ERR(ppgtt->vma); -+ goto err_scratch; -+ } -+ -+ return &ppgtt->base; -+ -+err_scratch: -+ gen6_ppgtt_free_scratch(&ppgtt->base.vm); -+err_free: -+ kfree(ppgtt); -+ return ERR_PTR(err); -+} -+ -+static void gtt_write_workarounds(struct drm_i915_private *dev_priv) -+{ -+ /* This function is for gtt related workarounds. This function is -+ * called on driver load and after a GPU reset, so you can place -+ * workarounds here even if they get overwritten by GPU reset. -+ */ -+ /* WaIncreaseDefaultTLBEntries:chv,bdw,skl,bxt,kbl,glk,cfl,cnl,icl */ -+ if (IS_BROADWELL(dev_priv)) -+ I915_WRITE(GEN8_L3_LRA_1_GPGPU, GEN8_L3_LRA_1_GPGPU_DEFAULT_VALUE_BDW); -+ else if (IS_CHERRYVIEW(dev_priv)) -+ I915_WRITE(GEN8_L3_LRA_1_GPGPU, GEN8_L3_LRA_1_GPGPU_DEFAULT_VALUE_CHV); -+ else if (IS_GEN9_LP(dev_priv)) -+ I915_WRITE(GEN8_L3_LRA_1_GPGPU, GEN9_L3_LRA_1_GPGPU_DEFAULT_VALUE_BXT); -+ else if (INTEL_GEN(dev_priv) >= 9) -+ I915_WRITE(GEN8_L3_LRA_1_GPGPU, GEN9_L3_LRA_1_GPGPU_DEFAULT_VALUE_SKL); -+ -+ /* -+ * To support 64K PTEs we need to first enable the use of the -+ * Intermediate-Page-Size(IPS) bit of the PDE field via some magical -+ * mmio, otherwise the page-walker will simply ignore the IPS bit. This -+ * shouldn't be needed after GEN10. -+ * -+ * 64K pages were first introduced from BDW+, although technically they -+ * only *work* from gen9+. For pre-BDW we instead have the option for -+ * 32K pages, but we don't currently have any support for it in our -+ * driver. -+ */ -+ if (HAS_PAGE_SIZES(dev_priv, I915_GTT_PAGE_SIZE_64K) && -+ INTEL_GEN(dev_priv) <= 10) -+ I915_WRITE(GEN8_GAMW_ECO_DEV_RW_IA, -+ I915_READ(GEN8_GAMW_ECO_DEV_RW_IA) | -+ GAMW_ECO_ENABLE_64K_IPS_FIELD); -+} -+ -+int i915_ppgtt_init_hw(struct drm_i915_private *dev_priv) -+{ -+ gtt_write_workarounds(dev_priv); -+ -+ if (IS_GEN(dev_priv, 6)) -+ gen6_ppgtt_enable(dev_priv); -+ else if (IS_GEN(dev_priv, 7)) -+ gen7_ppgtt_enable(dev_priv); -+ -+ return 0; -+} -+ -+static struct i915_hw_ppgtt * -+__hw_ppgtt_create(struct drm_i915_private *i915) -+{ -+ if (INTEL_GEN(i915) < 8) -+ return gen6_ppgtt_create(i915); -+ else -+ return gen8_ppgtt_create(i915); -+} -+ -+struct i915_hw_ppgtt * -+i915_ppgtt_create(struct drm_i915_private *i915) -+{ -+ struct i915_hw_ppgtt *ppgtt; -+ -+ ppgtt = __hw_ppgtt_create(i915); -+ if (IS_ERR(ppgtt)) -+ return ppgtt; -+ -+ trace_i915_ppgtt_create(&ppgtt->vm); -+ -+ return ppgtt; -+} -+ -+static void ppgtt_destroy_vma(struct i915_address_space *vm) -+{ -+ struct list_head *phases[] = { -+ &vm->bound_list, -+ &vm->unbound_list, -+ NULL, -+ }, **phase; -+ -+ vm->closed = true; -+ for (phase = phases; *phase; phase++) { -+ struct i915_vma *vma, *vn; -+ -+ list_for_each_entry_safe(vma, vn, *phase, vm_link) -+ i915_vma_destroy(vma); -+ } -+} -+ -+void i915_ppgtt_release(struct kref *kref) -+{ -+ struct i915_hw_ppgtt *ppgtt = -+ container_of(kref, struct i915_hw_ppgtt, ref); -+ -+ trace_i915_ppgtt_release(&ppgtt->vm); -+ -+ ppgtt_destroy_vma(&ppgtt->vm); -+ -+ GEM_BUG_ON(!list_empty(&ppgtt->vm.bound_list)); -+ GEM_BUG_ON(!list_empty(&ppgtt->vm.unbound_list)); -+ -+ ppgtt->vm.cleanup(&ppgtt->vm); -+ i915_address_space_fini(&ppgtt->vm); -+ kfree(ppgtt); -+} -+ -+/* Certain Gen5 chipsets require require idling the GPU before -+ * unmapping anything from the GTT when VT-d is enabled. -+ */ -+static bool needs_idle_maps(struct drm_i915_private *dev_priv) -+{ -+ /* Query intel_iommu to see if we need the workaround. Presumably that -+ * was loaded first. -+ */ -+ return IS_GEN(dev_priv, 5) && IS_MOBILE(dev_priv) && intel_vtd_active(); -+} -+ -+static void gen6_check_faults(struct drm_i915_private *dev_priv) -+{ -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ u32 fault; -+ -+ for_each_engine(engine, dev_priv, id) { -+ fault = I915_READ(RING_FAULT_REG(engine)); -+ if (fault & RING_FAULT_VALID) { -+ DRM_DEBUG_DRIVER("Unexpected fault\n" -+ "\tAddr: 0x%08lx\n" -+ "\tAddress space: %s\n" -+ "\tSource ID: %d\n" -+ "\tType: %d\n", -+ fault & PAGE_MASK, -+ fault & RING_FAULT_GTTSEL_MASK ? "GGTT" : "PPGTT", -+ RING_FAULT_SRCID(fault), -+ RING_FAULT_FAULT_TYPE(fault)); -+ } -+ } -+} -+ -+static void gen8_check_faults(struct drm_i915_private *dev_priv) -+{ -+ u32 fault = I915_READ(GEN8_RING_FAULT_REG); -+ -+ if (fault & RING_FAULT_VALID) { -+ u32 fault_data0, fault_data1; -+ u64 fault_addr; -+ -+ fault_data0 = I915_READ(GEN8_FAULT_TLB_DATA0); -+ fault_data1 = I915_READ(GEN8_FAULT_TLB_DATA1); -+ fault_addr = ((u64)(fault_data1 & FAULT_VA_HIGH_BITS) << 44) | -+ ((u64)fault_data0 << 12); -+ -+ DRM_DEBUG_DRIVER("Unexpected fault\n" -+ "\tAddr: 0x%08x_%08x\n" -+ "\tAddress space: %s\n" -+ "\tEngine ID: %d\n" -+ "\tSource ID: %d\n" -+ "\tType: %d\n", -+ upper_32_bits(fault_addr), -+ lower_32_bits(fault_addr), -+ fault_data1 & FAULT_GTT_SEL ? "GGTT" : "PPGTT", -+ GEN8_RING_FAULT_ENGINE_ID(fault), -+ RING_FAULT_SRCID(fault), -+ RING_FAULT_FAULT_TYPE(fault)); -+ } -+} -+ -+void i915_check_and_clear_faults(struct drm_i915_private *dev_priv) -+{ -+ /* From GEN8 onwards we only have one 'All Engine Fault Register' */ -+ if (INTEL_GEN(dev_priv) >= 8) -+ gen8_check_faults(dev_priv); -+ else if (INTEL_GEN(dev_priv) >= 6) -+ gen6_check_faults(dev_priv); -+ else -+ return; -+ -+ i915_clear_error_registers(dev_priv); -+} -+ -+void i915_gem_suspend_gtt_mappings(struct drm_i915_private *dev_priv) -+{ -+ struct i915_ggtt *ggtt = &dev_priv->ggtt; -+ -+ /* Don't bother messing with faults pre GEN6 as we have little -+ * documentation supporting that it's a good idea. -+ */ -+ if (INTEL_GEN(dev_priv) < 6) -+ return; -+ -+ i915_check_and_clear_faults(dev_priv); -+ -+ ggtt->vm.clear_range(&ggtt->vm, 0, ggtt->vm.total); -+ -+ i915_ggtt_invalidate(dev_priv); -+} -+ -+int i915_gem_gtt_prepare_pages(struct drm_i915_gem_object *obj, -+ struct sg_table *pages) -+{ -+ do { -+ if (dma_map_sg_attrs(&obj->base.dev->pdev->dev, -+ pages->sgl, pages->nents, -+ PCI_DMA_BIDIRECTIONAL, -+ DMA_ATTR_NO_WARN)) -+ return 0; -+ -+ /* -+ * If the DMA remap fails, one cause can be that we have -+ * too many objects pinned in a small remapping table, -+ * such as swiotlb. Incrementally purge all other objects and -+ * try again - if there are no more pages to remove from -+ * the DMA remapper, i915_gem_shrink will return 0. -+ */ -+ GEM_BUG_ON(obj->mm.pages == pages); -+ } while (i915_gem_shrink(to_i915(obj->base.dev), -+ obj->base.size >> PAGE_SHIFT, NULL, -+ I915_SHRINK_BOUND | -+ I915_SHRINK_UNBOUND)); -+ -+ return -ENOSPC; -+} -+ -+static void gen8_set_pte(void __iomem *addr, gen8_pte_t pte) -+{ -+ writeq(pte, addr); -+} -+ -+static void gen8_ggtt_insert_page(struct i915_address_space *vm, -+ dma_addr_t addr, -+ u64 offset, -+ enum i915_cache_level level, -+ u32 unused) -+{ -+ struct i915_ggtt *ggtt = i915_vm_to_ggtt(vm); -+ gen8_pte_t __iomem *pte = -+ (gen8_pte_t __iomem *)ggtt->gsm + offset / I915_GTT_PAGE_SIZE; -+ -+ gen8_set_pte(pte, gen8_pte_encode(addr, level, 0)); -+ -+ ggtt->invalidate(vm->i915); -+} -+ -+static void gen8_ggtt_insert_entries(struct i915_address_space *vm, -+ struct i915_vma *vma, -+ enum i915_cache_level level, -+ u32 flags) -+{ -+ struct i915_ggtt *ggtt = i915_vm_to_ggtt(vm); -+ struct sgt_iter sgt_iter; -+ gen8_pte_t __iomem *gtt_entries; -+ const gen8_pte_t pte_encode = gen8_pte_encode(0, level, 0); -+ dma_addr_t addr; -+ -+ /* -+ * Note that we ignore PTE_READ_ONLY here. The caller must be careful -+ * not to allow the user to override access to a read only page. -+ */ -+ -+ gtt_entries = (gen8_pte_t __iomem *)ggtt->gsm; -+ gtt_entries += vma->node.start / I915_GTT_PAGE_SIZE; -+ for_each_sgt_dma(addr, sgt_iter, vma->pages) -+ gen8_set_pte(gtt_entries++, pte_encode | addr); -+ -+ /* -+ * We want to flush the TLBs only after we're certain all the PTE -+ * updates have finished. -+ */ -+ ggtt->invalidate(vm->i915); -+} -+ -+static void gen6_ggtt_insert_page(struct i915_address_space *vm, -+ dma_addr_t addr, -+ u64 offset, -+ enum i915_cache_level level, -+ u32 flags) -+{ -+ struct i915_ggtt *ggtt = i915_vm_to_ggtt(vm); -+ gen6_pte_t __iomem *pte = -+ (gen6_pte_t __iomem *)ggtt->gsm + offset / I915_GTT_PAGE_SIZE; -+ -+ iowrite32(vm->pte_encode(addr, level, flags), pte); -+ -+ ggtt->invalidate(vm->i915); -+} -+ -+/* -+ * Binds an object into the global gtt with the specified cache level. The object -+ * will be accessible to the GPU via commands whose operands reference offsets -+ * within the global GTT as well as accessible by the GPU through the GMADR -+ * mapped BAR (dev_priv->mm.gtt->gtt). -+ */ -+static void gen6_ggtt_insert_entries(struct i915_address_space *vm, -+ struct i915_vma *vma, -+ enum i915_cache_level level, -+ u32 flags) -+{ -+ struct i915_ggtt *ggtt = i915_vm_to_ggtt(vm); -+ gen6_pte_t __iomem *entries = (gen6_pte_t __iomem *)ggtt->gsm; -+ unsigned int i = vma->node.start / I915_GTT_PAGE_SIZE; -+ struct sgt_iter iter; -+ dma_addr_t addr; -+ for_each_sgt_dma(addr, iter, vma->pages) -+ iowrite32(vm->pte_encode(addr, level, flags), &entries[i++]); -+ -+ /* -+ * We want to flush the TLBs only after we're certain all the PTE -+ * updates have finished. -+ */ -+ ggtt->invalidate(vm->i915); -+} -+ -+static void nop_clear_range(struct i915_address_space *vm, -+ u64 start, u64 length) -+{ -+} -+ -+static void gen8_ggtt_clear_range(struct i915_address_space *vm, -+ u64 start, u64 length) -+{ -+ struct i915_ggtt *ggtt = i915_vm_to_ggtt(vm); -+ unsigned first_entry = start / I915_GTT_PAGE_SIZE; -+ unsigned num_entries = length / I915_GTT_PAGE_SIZE; -+ const gen8_pte_t scratch_pte = vm->scratch_pte; -+ gen8_pte_t __iomem *gtt_base = -+ (gen8_pte_t __iomem *)ggtt->gsm + first_entry; -+ const int max_entries = ggtt_total_entries(ggtt) - first_entry; -+ int i; -+ -+ if (WARN(num_entries > max_entries, -+ "First entry = %d; Num entries = %d (max=%d)\n", -+ first_entry, num_entries, max_entries)) -+ num_entries = max_entries; -+ -+ for (i = 0; i < num_entries; i++) -+ gen8_set_pte(>t_base[i], scratch_pte); -+} -+ -+static void bxt_vtd_ggtt_wa(struct i915_address_space *vm) -+{ -+ struct drm_i915_private *dev_priv = vm->i915; -+ -+ /* -+ * Make sure the internal GAM fifo has been cleared of all GTT -+ * writes before exiting stop_machine(). This guarantees that -+ * any aperture accesses waiting to start in another process -+ * cannot back up behind the GTT writes causing a hang. -+ * The register can be any arbitrary GAM register. -+ */ -+ POSTING_READ(GFX_FLSH_CNTL_GEN6); -+} -+ -+struct insert_page { -+ struct i915_address_space *vm; -+ dma_addr_t addr; -+ u64 offset; -+ enum i915_cache_level level; -+}; -+ -+static int bxt_vtd_ggtt_insert_page__cb(void *_arg) -+{ -+ struct insert_page *arg = _arg; -+ -+ gen8_ggtt_insert_page(arg->vm, arg->addr, arg->offset, arg->level, 0); -+ bxt_vtd_ggtt_wa(arg->vm); -+ -+ return 0; -+} -+ -+static void bxt_vtd_ggtt_insert_page__BKL(struct i915_address_space *vm, -+ dma_addr_t addr, -+ u64 offset, -+ enum i915_cache_level level, -+ u32 unused) -+{ -+ struct insert_page arg = { vm, addr, offset, level }; -+ -+ stop_machine(bxt_vtd_ggtt_insert_page__cb, &arg, NULL); -+} -+ -+struct insert_entries { -+ struct i915_address_space *vm; -+ struct i915_vma *vma; -+ enum i915_cache_level level; -+ u32 flags; -+}; -+ -+static int bxt_vtd_ggtt_insert_entries__cb(void *_arg) -+{ -+ struct insert_entries *arg = _arg; -+ -+ gen8_ggtt_insert_entries(arg->vm, arg->vma, arg->level, arg->flags); -+ bxt_vtd_ggtt_wa(arg->vm); -+ -+ return 0; -+} -+ -+static void bxt_vtd_ggtt_insert_entries__BKL(struct i915_address_space *vm, -+ struct i915_vma *vma, -+ enum i915_cache_level level, -+ u32 flags) -+{ -+ struct insert_entries arg = { vm, vma, level, flags }; -+ -+ stop_machine(bxt_vtd_ggtt_insert_entries__cb, &arg, NULL); -+} -+ -+struct clear_range { -+ struct i915_address_space *vm; -+ u64 start; -+ u64 length; -+}; -+ -+static int bxt_vtd_ggtt_clear_range__cb(void *_arg) -+{ -+ struct clear_range *arg = _arg; -+ -+ gen8_ggtt_clear_range(arg->vm, arg->start, arg->length); -+ bxt_vtd_ggtt_wa(arg->vm); -+ -+ return 0; -+} -+ -+static void bxt_vtd_ggtt_clear_range__BKL(struct i915_address_space *vm, -+ u64 start, -+ u64 length) -+{ -+ struct clear_range arg = { vm, start, length }; -+ -+ stop_machine(bxt_vtd_ggtt_clear_range__cb, &arg, NULL); -+} -+ -+static void gen6_ggtt_clear_range(struct i915_address_space *vm, -+ u64 start, u64 length) -+{ -+ struct i915_ggtt *ggtt = i915_vm_to_ggtt(vm); -+ unsigned first_entry = start / I915_GTT_PAGE_SIZE; -+ unsigned num_entries = length / I915_GTT_PAGE_SIZE; -+ gen6_pte_t scratch_pte, __iomem *gtt_base = -+ (gen6_pte_t __iomem *)ggtt->gsm + first_entry; -+ const int max_entries = ggtt_total_entries(ggtt) - first_entry; -+ int i; -+ -+ if (WARN(num_entries > max_entries, -+ "First entry = %d; Num entries = %d (max=%d)\n", -+ first_entry, num_entries, max_entries)) -+ num_entries = max_entries; -+ -+ scratch_pte = vm->scratch_pte; -+ -+ for (i = 0; i < num_entries; i++) -+ iowrite32(scratch_pte, >t_base[i]); -+} -+ -+static void i915_ggtt_insert_page(struct i915_address_space *vm, -+ dma_addr_t addr, -+ u64 offset, -+ enum i915_cache_level cache_level, -+ u32 unused) -+{ -+ unsigned int flags = (cache_level == I915_CACHE_NONE) ? -+ AGP_USER_MEMORY : AGP_USER_CACHED_MEMORY; -+ -+ intel_gtt_insert_page(addr, offset >> PAGE_SHIFT, flags); -+} -+ -+static void i915_ggtt_insert_entries(struct i915_address_space *vm, -+ struct i915_vma *vma, -+ enum i915_cache_level cache_level, -+ u32 unused) -+{ -+ unsigned int flags = (cache_level == I915_CACHE_NONE) ? -+ AGP_USER_MEMORY : AGP_USER_CACHED_MEMORY; -+ -+ intel_gtt_insert_sg_entries(vma->pages, vma->node.start >> PAGE_SHIFT, -+ flags); -+} -+ -+static void i915_ggtt_clear_range(struct i915_address_space *vm, -+ u64 start, u64 length) -+{ -+ intel_gtt_clear_range(start >> PAGE_SHIFT, length >> PAGE_SHIFT); -+} -+ -+static int ggtt_bind_vma(struct i915_vma *vma, -+ enum i915_cache_level cache_level, -+ u32 flags) -+{ -+ struct drm_i915_private *i915 = vma->vm->i915; -+ struct drm_i915_gem_object *obj = vma->obj; -+ intel_wakeref_t wakeref; -+ u32 pte_flags; -+ -+ /* Applicable to VLV (gen8+ do not support RO in the GGTT) */ -+ pte_flags = 0; -+ if (i915_gem_object_is_readonly(obj)) -+ pte_flags |= PTE_READ_ONLY; -+ -+ with_intel_runtime_pm(i915, wakeref) -+ vma->vm->insert_entries(vma->vm, vma, cache_level, pte_flags); -+ -+ vma->page_sizes.gtt = I915_GTT_PAGE_SIZE; -+ -+ /* -+ * Without aliasing PPGTT there's no difference between -+ * GLOBAL/LOCAL_BIND, it's all the same ptes. Hence unconditionally -+ * upgrade to both bound if we bind either to avoid double-binding. -+ */ -+ vma->flags |= I915_VMA_GLOBAL_BIND | I915_VMA_LOCAL_BIND; -+ -+ return 0; -+} -+ -+static void ggtt_unbind_vma(struct i915_vma *vma) -+{ -+ struct drm_i915_private *i915 = vma->vm->i915; -+ intel_wakeref_t wakeref; -+ -+ with_intel_runtime_pm(i915, wakeref) -+ vma->vm->clear_range(vma->vm, vma->node.start, vma->size); -+} -+ -+static int aliasing_gtt_bind_vma(struct i915_vma *vma, -+ enum i915_cache_level cache_level, -+ u32 flags) -+{ -+ struct drm_i915_private *i915 = vma->vm->i915; -+ u32 pte_flags; -+ int ret; -+ -+ /* Currently applicable only to VLV */ -+ pte_flags = 0; -+ if (i915_gem_object_is_readonly(vma->obj)) -+ pte_flags |= PTE_READ_ONLY; -+ -+ if (flags & I915_VMA_LOCAL_BIND) { -+ struct i915_hw_ppgtt *appgtt = i915->mm.aliasing_ppgtt; -+ -+ if (!(vma->flags & I915_VMA_LOCAL_BIND)) { -+ ret = appgtt->vm.allocate_va_range(&appgtt->vm, -+ vma->node.start, -+ vma->size); -+ if (ret) -+ return ret; -+ } -+ -+ appgtt->vm.insert_entries(&appgtt->vm, vma, cache_level, -+ pte_flags); -+ } -+ -+ if (flags & I915_VMA_GLOBAL_BIND) { -+ intel_wakeref_t wakeref; -+ -+ with_intel_runtime_pm(i915, wakeref) { -+ vma->vm->insert_entries(vma->vm, vma, -+ cache_level, pte_flags); -+ } -+ } -+ -+ return 0; -+} -+ -+static void aliasing_gtt_unbind_vma(struct i915_vma *vma) -+{ -+ struct drm_i915_private *i915 = vma->vm->i915; -+ -+ if (vma->flags & I915_VMA_GLOBAL_BIND) { -+ struct i915_address_space *vm = vma->vm; -+ intel_wakeref_t wakeref; -+ -+ with_intel_runtime_pm(i915, wakeref) -+ vm->clear_range(vm, vma->node.start, vma->size); -+ } -+ -+ if (vma->flags & I915_VMA_LOCAL_BIND) { -+ struct i915_address_space *vm = &i915->mm.aliasing_ppgtt->vm; -+ -+ vm->clear_range(vm, vma->node.start, vma->size); -+ } -+} -+ -+void i915_gem_gtt_finish_pages(struct drm_i915_gem_object *obj, -+ struct sg_table *pages) -+{ -+ struct drm_i915_private *dev_priv = to_i915(obj->base.dev); -+ struct device *kdev = &dev_priv->drm.pdev->dev; -+ struct i915_ggtt *ggtt = &dev_priv->ggtt; -+ -+ if (unlikely(ggtt->do_idle_maps)) { -+ if (i915_gem_wait_for_idle(dev_priv, 0, MAX_SCHEDULE_TIMEOUT)) { -+ DRM_ERROR("Failed to wait for idle; VT'd may hang.\n"); -+ /* Wait a bit, in hopes it avoids the hang */ -+ udelay(10); -+ } -+ } -+ -+ dma_unmap_sg(kdev, pages->sgl, pages->nents, PCI_DMA_BIDIRECTIONAL); -+} -+ -+static int ggtt_set_pages(struct i915_vma *vma) -+{ -+ int ret; -+ -+ GEM_BUG_ON(vma->pages); -+ -+ ret = i915_get_ggtt_vma_pages(vma); -+ if (ret) -+ return ret; -+ -+ vma->page_sizes = vma->obj->mm.page_sizes; -+ -+ return 0; -+} -+ -+static void i915_gtt_color_adjust(const struct drm_mm_node *node, -+ unsigned long color, -+ u64 *start, -+ u64 *end) -+{ -+ if (node->allocated && node->color != color) -+ *start += I915_GTT_PAGE_SIZE; -+ -+ /* Also leave a space between the unallocated reserved node after the -+ * GTT and any objects within the GTT, i.e. we use the color adjustment -+ * to insert a guard page to prevent prefetches crossing over the -+ * GTT boundary. -+ */ -+ node = list_next_entry(node, node_list); -+ if (node->color != color) -+ *end -= I915_GTT_PAGE_SIZE; -+} -+ -+int i915_gem_init_aliasing_ppgtt(struct drm_i915_private *i915) -+{ -+ struct i915_ggtt *ggtt = &i915->ggtt; -+ struct i915_hw_ppgtt *ppgtt; -+ int err; -+ -+ ppgtt = i915_ppgtt_create(i915); -+ if (IS_ERR(ppgtt)) -+ return PTR_ERR(ppgtt); -+ -+ if (GEM_WARN_ON(ppgtt->vm.total < ggtt->vm.total)) { -+ err = -ENODEV; -+ goto err_ppgtt; -+ } -+ -+ /* -+ * Note we only pre-allocate as far as the end of the global -+ * GTT. On 48b / 4-level page-tables, the difference is very, -+ * very significant! We have to preallocate as GVT/vgpu does -+ * not like the page directory disappearing. -+ */ -+ err = ppgtt->vm.allocate_va_range(&ppgtt->vm, 0, ggtt->vm.total); -+ if (err) -+ goto err_ppgtt; -+ -+ i915->mm.aliasing_ppgtt = ppgtt; -+ -+ GEM_BUG_ON(ggtt->vm.vma_ops.bind_vma != ggtt_bind_vma); -+ ggtt->vm.vma_ops.bind_vma = aliasing_gtt_bind_vma; -+ -+ GEM_BUG_ON(ggtt->vm.vma_ops.unbind_vma != ggtt_unbind_vma); -+ ggtt->vm.vma_ops.unbind_vma = aliasing_gtt_unbind_vma; -+ -+ return 0; -+ -+err_ppgtt: -+ i915_ppgtt_put(ppgtt); -+ return err; -+} -+ -+void i915_gem_fini_aliasing_ppgtt(struct drm_i915_private *i915) -+{ -+ struct i915_ggtt *ggtt = &i915->ggtt; -+ struct i915_hw_ppgtt *ppgtt; -+ -+ ppgtt = fetch_and_zero(&i915->mm.aliasing_ppgtt); -+ if (!ppgtt) -+ return; -+ -+ i915_ppgtt_put(ppgtt); -+ -+ ggtt->vm.vma_ops.bind_vma = ggtt_bind_vma; -+ ggtt->vm.vma_ops.unbind_vma = ggtt_unbind_vma; -+} -+ -+int i915_gem_init_ggtt(struct drm_i915_private *dev_priv) -+{ -+ /* Let GEM Manage all of the aperture. -+ * -+ * However, leave one page at the end still bound to the scratch page. -+ * There are a number of places where the hardware apparently prefetches -+ * past the end of the object, and we've seen multiple hangs with the -+ * GPU head pointer stuck in a batchbuffer bound at the last page of the -+ * aperture. One page should be enough to keep any prefetching inside -+ * of the aperture. -+ */ -+ struct i915_ggtt *ggtt = &dev_priv->ggtt; -+ unsigned long hole_start, hole_end; -+ struct drm_mm_node *entry; -+ int ret; -+ -+ /* -+ * GuC requires all resources that we're sharing with it to be placed in -+ * non-WOPCM memory. If GuC is not present or not in use we still need a -+ * small bias as ring wraparound at offset 0 sometimes hangs. No idea -+ * why. -+ */ -+ ggtt->pin_bias = max_t(u32, I915_GTT_PAGE_SIZE, -+ intel_guc_reserved_gtt_size(&dev_priv->guc)); -+ -+ ret = intel_vgt_balloon(dev_priv); -+ if (ret) -+ return ret; -+ -+ /* Reserve a mappable slot for our lockless error capture */ -+ ret = drm_mm_insert_node_in_range(&ggtt->vm.mm, &ggtt->error_capture, -+ PAGE_SIZE, 0, I915_COLOR_UNEVICTABLE, -+ 0, ggtt->mappable_end, -+ DRM_MM_INSERT_LOW); -+ if (ret) -+ return ret; -+ -+ /* Clear any non-preallocated blocks */ -+ drm_mm_for_each_hole(entry, &ggtt->vm.mm, hole_start, hole_end) { -+ DRM_DEBUG_KMS("clearing unused GTT space: [%lx, %lx]\n", -+ hole_start, hole_end); -+ ggtt->vm.clear_range(&ggtt->vm, hole_start, -+ hole_end - hole_start); -+ } -+ -+ /* And finally clear the reserved guard page */ -+ ggtt->vm.clear_range(&ggtt->vm, ggtt->vm.total - PAGE_SIZE, PAGE_SIZE); -+ -+ if (INTEL_PPGTT(dev_priv) == INTEL_PPGTT_ALIASING) { -+ ret = i915_gem_init_aliasing_ppgtt(dev_priv); -+ if (ret) -+ goto err; -+ } -+ -+ return 0; -+ -+err: -+ drm_mm_remove_node(&ggtt->error_capture); -+ return ret; -+} -+ -+/** -+ * i915_ggtt_cleanup_hw - Clean up GGTT hardware initialization -+ * @dev_priv: i915 device -+ */ -+void i915_ggtt_cleanup_hw(struct drm_i915_private *dev_priv) -+{ -+ struct i915_ggtt *ggtt = &dev_priv->ggtt; -+ struct i915_vma *vma, *vn; -+ struct pagevec *pvec; -+ -+ ggtt->vm.closed = true; -+ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ i915_gem_fini_aliasing_ppgtt(dev_priv); -+ -+ list_for_each_entry_safe(vma, vn, &ggtt->vm.bound_list, vm_link) -+ WARN_ON(i915_vma_unbind(vma)); -+ -+ if (drm_mm_node_allocated(&ggtt->error_capture)) -+ drm_mm_remove_node(&ggtt->error_capture); -+ -+ if (drm_mm_initialized(&ggtt->vm.mm)) { -+ intel_vgt_deballoon(dev_priv); -+ i915_address_space_fini(&ggtt->vm); -+ } -+ -+ ggtt->vm.cleanup(&ggtt->vm); -+ -+ pvec = &dev_priv->mm.wc_stash.pvec; -+ if (pvec->nr) { -+ set_pages_array_wb(pvec->pages, pvec->nr); -+ __pagevec_release(pvec); -+ } -+ -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ -+ arch_phys_wc_del(ggtt->mtrr); -+ io_mapping_fini(&ggtt->iomap); -+ -+ i915_gem_cleanup_stolen(dev_priv); -+} -+ -+static unsigned int gen6_get_total_gtt_size(u16 snb_gmch_ctl) -+{ -+ snb_gmch_ctl >>= SNB_GMCH_GGMS_SHIFT; -+ snb_gmch_ctl &= SNB_GMCH_GGMS_MASK; -+ return snb_gmch_ctl << 20; -+} -+ -+static unsigned int gen8_get_total_gtt_size(u16 bdw_gmch_ctl) -+{ -+ bdw_gmch_ctl >>= BDW_GMCH_GGMS_SHIFT; -+ bdw_gmch_ctl &= BDW_GMCH_GGMS_MASK; -+ if (bdw_gmch_ctl) -+ bdw_gmch_ctl = 1 << bdw_gmch_ctl; -+ -+#ifdef CONFIG_X86_32 -+ /* Limit 32b platforms to a 2GB GGTT: 4 << 20 / pte size * I915_GTT_PAGE_SIZE */ -+ if (bdw_gmch_ctl > 4) -+ bdw_gmch_ctl = 4; -+#endif -+ -+ return bdw_gmch_ctl << 20; -+} -+ -+static unsigned int chv_get_total_gtt_size(u16 gmch_ctrl) -+{ -+ gmch_ctrl >>= SNB_GMCH_GGMS_SHIFT; -+ gmch_ctrl &= SNB_GMCH_GGMS_MASK; -+ -+ if (gmch_ctrl) -+ return 1 << (20 + gmch_ctrl); -+ -+ return 0; -+} -+ -+static int ggtt_probe_common(struct i915_ggtt *ggtt, u64 size) -+{ -+ struct drm_i915_private *dev_priv = ggtt->vm.i915; -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ phys_addr_t phys_addr; -+ int ret; -+ -+ /* For Modern GENs the PTEs and register space are split in the BAR */ -+ phys_addr = pci_resource_start(pdev, 0) + pci_resource_len(pdev, 0) / 2; -+ -+ /* -+ * On BXT+/CNL+ writes larger than 64 bit to the GTT pagetable range -+ * will be dropped. For WC mappings in general we have 64 byte burst -+ * writes when the WC buffer is flushed, so we can't use it, but have to -+ * resort to an uncached mapping. The WC issue is easily caught by the -+ * readback check when writing GTT PTE entries. -+ */ -+ if (IS_GEN9_LP(dev_priv) || INTEL_GEN(dev_priv) >= 10) -+ ggtt->gsm = ioremap_nocache(phys_addr, size); -+ else -+ ggtt->gsm = ioremap_wc(phys_addr, size); -+ if (!ggtt->gsm) { -+ DRM_ERROR("Failed to map the ggtt page table\n"); -+ return -ENOMEM; -+ } -+ -+ ret = setup_scratch_page(&ggtt->vm, GFP_DMA32); -+ if (ret) { -+ DRM_ERROR("Scratch setup failed\n"); -+ /* iounmap will also get called at remove, but meh */ -+ iounmap(ggtt->gsm); -+ return ret; -+ } -+ -+ ggtt->vm.scratch_pte = -+ ggtt->vm.pte_encode(ggtt->vm.scratch_page.daddr, -+ I915_CACHE_NONE, 0); -+ -+ return 0; -+} -+ -+static struct intel_ppat_entry * -+__alloc_ppat_entry(struct intel_ppat *ppat, unsigned int index, u8 value) -+{ -+ struct intel_ppat_entry *entry = &ppat->entries[index]; -+ -+ GEM_BUG_ON(index >= ppat->max_entries); -+ GEM_BUG_ON(test_bit(index, ppat->used)); -+ -+ entry->ppat = ppat; -+ entry->value = value; -+ kref_init(&entry->ref); -+ set_bit(index, ppat->used); -+ set_bit(index, ppat->dirty); -+ -+ return entry; -+} -+ -+static void __free_ppat_entry(struct intel_ppat_entry *entry) -+{ -+ struct intel_ppat *ppat = entry->ppat; -+ unsigned int index = entry - ppat->entries; -+ -+ GEM_BUG_ON(index >= ppat->max_entries); -+ GEM_BUG_ON(!test_bit(index, ppat->used)); -+ -+ entry->value = ppat->clear_value; -+ clear_bit(index, ppat->used); -+ set_bit(index, ppat->dirty); -+} -+ -+/** -+ * intel_ppat_get - get a usable PPAT entry -+ * @i915: i915 device instance -+ * @value: the PPAT value required by the caller -+ * -+ * The function tries to search if there is an existing PPAT entry which -+ * matches with the required value. If perfectly matched, the existing PPAT -+ * entry will be used. If only partially matched, it will try to check if -+ * there is any available PPAT index. If yes, it will allocate a new PPAT -+ * index for the required entry and update the HW. If not, the partially -+ * matched entry will be used. -+ */ -+const struct intel_ppat_entry * -+intel_ppat_get(struct drm_i915_private *i915, u8 value) -+{ -+ struct intel_ppat *ppat = &i915->ppat; -+ struct intel_ppat_entry *entry = NULL; -+ unsigned int scanned, best_score; -+ int i; -+ -+ GEM_BUG_ON(!ppat->max_entries); -+ -+ scanned = best_score = 0; -+ for_each_set_bit(i, ppat->used, ppat->max_entries) { -+ unsigned int score; -+ -+ score = ppat->match(ppat->entries[i].value, value); -+ if (score > best_score) { -+ entry = &ppat->entries[i]; -+ if (score == INTEL_PPAT_PERFECT_MATCH) { -+ kref_get(&entry->ref); -+ return entry; -+ } -+ best_score = score; -+ } -+ scanned++; -+ } -+ -+ if (scanned == ppat->max_entries) { -+ if (!entry) -+ return ERR_PTR(-ENOSPC); -+ -+ kref_get(&entry->ref); -+ return entry; -+ } -+ -+ i = find_first_zero_bit(ppat->used, ppat->max_entries); -+ entry = __alloc_ppat_entry(ppat, i, value); -+ ppat->update_hw(i915); -+ return entry; -+} -+ -+static void release_ppat(struct kref *kref) -+{ -+ struct intel_ppat_entry *entry = -+ container_of(kref, struct intel_ppat_entry, ref); -+ struct drm_i915_private *i915 = entry->ppat->i915; -+ -+ __free_ppat_entry(entry); -+ entry->ppat->update_hw(i915); -+} -+ -+/** -+ * intel_ppat_put - put back the PPAT entry got from intel_ppat_get() -+ * @entry: an intel PPAT entry -+ * -+ * Put back the PPAT entry got from intel_ppat_get(). If the PPAT index of the -+ * entry is dynamically allocated, its reference count will be decreased. Once -+ * the reference count becomes into zero, the PPAT index becomes free again. -+ */ -+void intel_ppat_put(const struct intel_ppat_entry *entry) -+{ -+ struct intel_ppat *ppat = entry->ppat; -+ unsigned int index = entry - ppat->entries; -+ -+ GEM_BUG_ON(!ppat->max_entries); -+ -+ kref_put(&ppat->entries[index].ref, release_ppat); -+} -+ -+static void cnl_private_pat_update_hw(struct drm_i915_private *dev_priv) -+{ -+ struct intel_ppat *ppat = &dev_priv->ppat; -+ int i; -+ -+ for_each_set_bit(i, ppat->dirty, ppat->max_entries) { -+ I915_WRITE(GEN10_PAT_INDEX(i), ppat->entries[i].value); -+ clear_bit(i, ppat->dirty); -+ } -+} -+ -+static void bdw_private_pat_update_hw(struct drm_i915_private *dev_priv) -+{ -+ struct intel_ppat *ppat = &dev_priv->ppat; -+ u64 pat = 0; -+ int i; -+ -+ for (i = 0; i < ppat->max_entries; i++) -+ pat |= GEN8_PPAT(i, ppat->entries[i].value); -+ -+ bitmap_clear(ppat->dirty, 0, ppat->max_entries); -+ -+ I915_WRITE(GEN8_PRIVATE_PAT_LO, lower_32_bits(pat)); -+ I915_WRITE(GEN8_PRIVATE_PAT_HI, upper_32_bits(pat)); -+} -+ -+static unsigned int bdw_private_pat_match(u8 src, u8 dst) -+{ -+ unsigned int score = 0; -+ enum { -+ AGE_MATCH = BIT(0), -+ TC_MATCH = BIT(1), -+ CA_MATCH = BIT(2), -+ }; -+ -+ /* Cache attribute has to be matched. */ -+ if (GEN8_PPAT_GET_CA(src) != GEN8_PPAT_GET_CA(dst)) -+ return 0; -+ -+ score |= CA_MATCH; -+ -+ if (GEN8_PPAT_GET_TC(src) == GEN8_PPAT_GET_TC(dst)) -+ score |= TC_MATCH; -+ -+ if (GEN8_PPAT_GET_AGE(src) == GEN8_PPAT_GET_AGE(dst)) -+ score |= AGE_MATCH; -+ -+ if (score == (AGE_MATCH | TC_MATCH | CA_MATCH)) -+ return INTEL_PPAT_PERFECT_MATCH; -+ -+ return score; -+} -+ -+static unsigned int chv_private_pat_match(u8 src, u8 dst) -+{ -+ return (CHV_PPAT_GET_SNOOP(src) == CHV_PPAT_GET_SNOOP(dst)) ? -+ INTEL_PPAT_PERFECT_MATCH : 0; -+} -+ -+static void cnl_setup_private_ppat(struct intel_ppat *ppat) -+{ -+ ppat->max_entries = 8; -+ ppat->update_hw = cnl_private_pat_update_hw; -+ ppat->match = bdw_private_pat_match; -+ ppat->clear_value = GEN8_PPAT_WB | GEN8_PPAT_LLCELLC | GEN8_PPAT_AGE(3); -+ -+ __alloc_ppat_entry(ppat, 0, GEN8_PPAT_WB | GEN8_PPAT_LLC); -+ __alloc_ppat_entry(ppat, 1, GEN8_PPAT_WC | GEN8_PPAT_LLCELLC); -+ __alloc_ppat_entry(ppat, 2, GEN8_PPAT_WT | GEN8_PPAT_LLCELLC); -+ __alloc_ppat_entry(ppat, 3, GEN8_PPAT_UC); -+ __alloc_ppat_entry(ppat, 4, GEN8_PPAT_WB | GEN8_PPAT_LLCELLC | GEN8_PPAT_AGE(0)); -+ __alloc_ppat_entry(ppat, 5, GEN8_PPAT_WB | GEN8_PPAT_LLCELLC | GEN8_PPAT_AGE(1)); -+ __alloc_ppat_entry(ppat, 6, GEN8_PPAT_WB | GEN8_PPAT_LLCELLC | GEN8_PPAT_AGE(2)); -+ __alloc_ppat_entry(ppat, 7, GEN8_PPAT_WB | GEN8_PPAT_LLCELLC | GEN8_PPAT_AGE(3)); -+} -+ -+/* The GGTT and PPGTT need a private PPAT setup in order to handle cacheability -+ * bits. When using advanced contexts each context stores its own PAT, but -+ * writing this data shouldn't be harmful even in those cases. */ -+static void bdw_setup_private_ppat(struct intel_ppat *ppat) -+{ -+ ppat->max_entries = 8; -+ ppat->update_hw = bdw_private_pat_update_hw; -+ ppat->match = bdw_private_pat_match; -+ ppat->clear_value = GEN8_PPAT_WB | GEN8_PPAT_LLCELLC | GEN8_PPAT_AGE(3); -+ -+ if (!HAS_PPGTT(ppat->i915)) { -+ /* Spec: "For GGTT, there is NO pat_sel[2:0] from the entry, -+ * so RTL will always use the value corresponding to -+ * pat_sel = 000". -+ * So let's disable cache for GGTT to avoid screen corruptions. -+ * MOCS still can be used though. -+ * - System agent ggtt writes (i.e. cpu gtt mmaps) already work -+ * before this patch, i.e. the same uncached + snooping access -+ * like on gen6/7 seems to be in effect. -+ * - So this just fixes blitter/render access. Again it looks -+ * like it's not just uncached access, but uncached + snooping. -+ * So we can still hold onto all our assumptions wrt cpu -+ * clflushing on LLC machines. -+ */ -+ __alloc_ppat_entry(ppat, 0, GEN8_PPAT_UC); -+ return; -+ } -+ -+ __alloc_ppat_entry(ppat, 0, GEN8_PPAT_WB | GEN8_PPAT_LLC); /* for normal objects, no eLLC */ -+ __alloc_ppat_entry(ppat, 1, GEN8_PPAT_WC | GEN8_PPAT_LLCELLC); /* for something pointing to ptes? */ -+ __alloc_ppat_entry(ppat, 2, GEN8_PPAT_WT | GEN8_PPAT_LLCELLC); /* for scanout with eLLC */ -+ __alloc_ppat_entry(ppat, 3, GEN8_PPAT_UC); /* Uncached objects, mostly for scanout */ -+ __alloc_ppat_entry(ppat, 4, GEN8_PPAT_WB | GEN8_PPAT_LLCELLC | GEN8_PPAT_AGE(0)); -+ __alloc_ppat_entry(ppat, 5, GEN8_PPAT_WB | GEN8_PPAT_LLCELLC | GEN8_PPAT_AGE(1)); -+ __alloc_ppat_entry(ppat, 6, GEN8_PPAT_WB | GEN8_PPAT_LLCELLC | GEN8_PPAT_AGE(2)); -+ __alloc_ppat_entry(ppat, 7, GEN8_PPAT_WB | GEN8_PPAT_LLCELLC | GEN8_PPAT_AGE(3)); -+} -+ -+static void chv_setup_private_ppat(struct intel_ppat *ppat) -+{ -+ ppat->max_entries = 8; -+ ppat->update_hw = bdw_private_pat_update_hw; -+ ppat->match = chv_private_pat_match; -+ ppat->clear_value = CHV_PPAT_SNOOP; -+ -+ /* -+ * Map WB on BDW to snooped on CHV. -+ * -+ * Only the snoop bit has meaning for CHV, the rest is -+ * ignored. -+ * -+ * The hardware will never snoop for certain types of accesses: -+ * - CPU GTT (GMADR->GGTT->no snoop->memory) -+ * - PPGTT page tables -+ * - some other special cycles -+ * -+ * As with BDW, we also need to consider the following for GT accesses: -+ * "For GGTT, there is NO pat_sel[2:0] from the entry, -+ * so RTL will always use the value corresponding to -+ * pat_sel = 000". -+ * Which means we must set the snoop bit in PAT entry 0 -+ * in order to keep the global status page working. -+ */ -+ -+ __alloc_ppat_entry(ppat, 0, CHV_PPAT_SNOOP); -+ __alloc_ppat_entry(ppat, 1, 0); -+ __alloc_ppat_entry(ppat, 2, 0); -+ __alloc_ppat_entry(ppat, 3, 0); -+ __alloc_ppat_entry(ppat, 4, CHV_PPAT_SNOOP); -+ __alloc_ppat_entry(ppat, 5, CHV_PPAT_SNOOP); -+ __alloc_ppat_entry(ppat, 6, CHV_PPAT_SNOOP); -+ __alloc_ppat_entry(ppat, 7, CHV_PPAT_SNOOP); -+} -+ -+static void gen6_gmch_remove(struct i915_address_space *vm) -+{ -+ struct i915_ggtt *ggtt = i915_vm_to_ggtt(vm); -+ -+ iounmap(ggtt->gsm); -+ cleanup_scratch_page(vm); -+} -+ -+static void setup_private_pat(struct drm_i915_private *dev_priv) -+{ -+ struct intel_ppat *ppat = &dev_priv->ppat; -+ int i; -+ -+ ppat->i915 = dev_priv; -+ -+ if (INTEL_GEN(dev_priv) >= 10) -+ cnl_setup_private_ppat(ppat); -+ else if (IS_CHERRYVIEW(dev_priv) || IS_GEN9_LP(dev_priv)) -+ chv_setup_private_ppat(ppat); -+ else -+ bdw_setup_private_ppat(ppat); -+ -+ GEM_BUG_ON(ppat->max_entries > INTEL_MAX_PPAT_ENTRIES); -+ -+ for_each_clear_bit(i, ppat->used, ppat->max_entries) { -+ ppat->entries[i].value = ppat->clear_value; -+ ppat->entries[i].ppat = ppat; -+ set_bit(i, ppat->dirty); -+ } -+ -+ ppat->update_hw(dev_priv); -+} -+ -+static int gen8_gmch_probe(struct i915_ggtt *ggtt) -+{ -+ struct drm_i915_private *dev_priv = ggtt->vm.i915; -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ unsigned int size; -+ u16 snb_gmch_ctl; -+ int err; -+ -+ /* TODO: We're not aware of mappable constraints on gen8 yet */ -+ ggtt->gmadr = -+ (struct resource) DEFINE_RES_MEM(pci_resource_start(pdev, 2), -+ pci_resource_len(pdev, 2)); -+ ggtt->mappable_end = resource_size(&ggtt->gmadr); -+ -+ err = pci_set_dma_mask(pdev, DMA_BIT_MASK(39)); -+ if (!err) -+ err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(39)); -+ if (err) -+ DRM_ERROR("Can't set DMA mask/consistent mask (%d)\n", err); -+ -+ pci_read_config_word(pdev, SNB_GMCH_CTRL, &snb_gmch_ctl); -+ if (IS_CHERRYVIEW(dev_priv)) -+ size = chv_get_total_gtt_size(snb_gmch_ctl); -+ else -+ size = gen8_get_total_gtt_size(snb_gmch_ctl); -+ -+ ggtt->vm.total = (size / sizeof(gen8_pte_t)) * I915_GTT_PAGE_SIZE; -+ ggtt->vm.cleanup = gen6_gmch_remove; -+ ggtt->vm.insert_page = gen8_ggtt_insert_page; -+ ggtt->vm.clear_range = nop_clear_range; -+ if (intel_scanout_needs_vtd_wa(dev_priv)) -+ ggtt->vm.clear_range = gen8_ggtt_clear_range; -+ -+ ggtt->vm.insert_entries = gen8_ggtt_insert_entries; -+ -+ /* Serialize GTT updates with aperture access on BXT if VT-d is on. */ -+ if (intel_ggtt_update_needs_vtd_wa(dev_priv) || -+ IS_CHERRYVIEW(dev_priv) /* fails with concurrent use/update */) { -+ ggtt->vm.insert_entries = bxt_vtd_ggtt_insert_entries__BKL; -+ ggtt->vm.insert_page = bxt_vtd_ggtt_insert_page__BKL; -+ if (ggtt->vm.clear_range != nop_clear_range) -+ ggtt->vm.clear_range = bxt_vtd_ggtt_clear_range__BKL; -+ -+ /* Prevent recursively calling stop_machine() and deadlocks. */ -+ dev_info(dev_priv->drm.dev, -+ "Disabling error capture for VT-d workaround\n"); -+ i915_disable_error_state(dev_priv, -ENODEV); -+ } -+ -+ ggtt->invalidate = gen6_ggtt_invalidate; -+ -+ ggtt->vm.vma_ops.bind_vma = ggtt_bind_vma; -+ ggtt->vm.vma_ops.unbind_vma = ggtt_unbind_vma; -+ ggtt->vm.vma_ops.set_pages = ggtt_set_pages; -+ ggtt->vm.vma_ops.clear_pages = clear_pages; -+ -+ ggtt->vm.pte_encode = gen8_pte_encode; -+ -+ setup_private_pat(dev_priv); -+ -+ return ggtt_probe_common(ggtt, size); -+} -+ -+static int gen6_gmch_probe(struct i915_ggtt *ggtt) -+{ -+ struct drm_i915_private *dev_priv = ggtt->vm.i915; -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ unsigned int size; -+ u16 snb_gmch_ctl; -+ int err; -+ -+ ggtt->gmadr = -+ (struct resource) DEFINE_RES_MEM(pci_resource_start(pdev, 2), -+ pci_resource_len(pdev, 2)); -+ ggtt->mappable_end = resource_size(&ggtt->gmadr); -+ -+ /* 64/512MB is the current min/max we actually know of, but this is just -+ * a coarse sanity check. -+ */ -+ if (ggtt->mappable_end < (64<<20) || ggtt->mappable_end > (512<<20)) { -+ DRM_ERROR("Unknown GMADR size (%pa)\n", &ggtt->mappable_end); -+ return -ENXIO; -+ } -+ -+ err = pci_set_dma_mask(pdev, DMA_BIT_MASK(40)); -+ if (!err) -+ err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(40)); -+ if (err) -+ DRM_ERROR("Can't set DMA mask/consistent mask (%d)\n", err); -+ pci_read_config_word(pdev, SNB_GMCH_CTRL, &snb_gmch_ctl); -+ -+ size = gen6_get_total_gtt_size(snb_gmch_ctl); -+ ggtt->vm.total = (size / sizeof(gen6_pte_t)) * I915_GTT_PAGE_SIZE; -+ -+ ggtt->vm.clear_range = gen6_ggtt_clear_range; -+ ggtt->vm.insert_page = gen6_ggtt_insert_page; -+ ggtt->vm.insert_entries = gen6_ggtt_insert_entries; -+ ggtt->vm.cleanup = gen6_gmch_remove; -+ -+ ggtt->invalidate = gen6_ggtt_invalidate; -+ -+ if (HAS_EDRAM(dev_priv)) -+ ggtt->vm.pte_encode = iris_pte_encode; -+ else if (IS_HASWELL(dev_priv)) -+ ggtt->vm.pte_encode = hsw_pte_encode; -+ else if (IS_VALLEYVIEW(dev_priv)) -+ ggtt->vm.pte_encode = byt_pte_encode; -+ else if (INTEL_GEN(dev_priv) >= 7) -+ ggtt->vm.pte_encode = ivb_pte_encode; -+ else -+ ggtt->vm.pte_encode = snb_pte_encode; -+ -+ ggtt->vm.vma_ops.bind_vma = ggtt_bind_vma; -+ ggtt->vm.vma_ops.unbind_vma = ggtt_unbind_vma; -+ ggtt->vm.vma_ops.set_pages = ggtt_set_pages; -+ ggtt->vm.vma_ops.clear_pages = clear_pages; -+ -+ return ggtt_probe_common(ggtt, size); -+} -+ -+static void i915_gmch_remove(struct i915_address_space *vm) -+{ -+ intel_gmch_remove(); -+} -+ -+static int i915_gmch_probe(struct i915_ggtt *ggtt) -+{ -+ struct drm_i915_private *dev_priv = ggtt->vm.i915; -+ phys_addr_t gmadr_base; -+ int ret; -+ -+ ret = intel_gmch_probe(dev_priv->bridge_dev, dev_priv->drm.pdev, NULL); -+ if (!ret) { -+ DRM_ERROR("failed to set up gmch\n"); -+ return -EIO; -+ } -+ -+ intel_gtt_get(&ggtt->vm.total, &gmadr_base, &ggtt->mappable_end); -+ -+ ggtt->gmadr = -+ (struct resource) DEFINE_RES_MEM(gmadr_base, -+ ggtt->mappable_end); -+ -+ ggtt->do_idle_maps = needs_idle_maps(dev_priv); -+ ggtt->vm.insert_page = i915_ggtt_insert_page; -+ ggtt->vm.insert_entries = i915_ggtt_insert_entries; -+ ggtt->vm.clear_range = i915_ggtt_clear_range; -+ ggtt->vm.cleanup = i915_gmch_remove; -+ -+ ggtt->invalidate = gmch_ggtt_invalidate; -+ -+ ggtt->vm.vma_ops.bind_vma = ggtt_bind_vma; -+ ggtt->vm.vma_ops.unbind_vma = ggtt_unbind_vma; -+ ggtt->vm.vma_ops.set_pages = ggtt_set_pages; -+ ggtt->vm.vma_ops.clear_pages = clear_pages; -+ -+ if (unlikely(ggtt->do_idle_maps)) -+ DRM_INFO("applying Ironlake quirks for intel_iommu\n"); -+ -+ return 0; -+} -+ -+/** -+ * i915_ggtt_probe_hw - Probe GGTT hardware location -+ * @dev_priv: i915 device -+ */ -+int i915_ggtt_probe_hw(struct drm_i915_private *dev_priv) -+{ -+ struct i915_ggtt *ggtt = &dev_priv->ggtt; -+ int ret; -+ -+ ggtt->vm.i915 = dev_priv; -+ ggtt->vm.dma = &dev_priv->drm.pdev->dev; -+ -+ if (INTEL_GEN(dev_priv) <= 5) -+ ret = i915_gmch_probe(ggtt); -+ else if (INTEL_GEN(dev_priv) < 8) -+ ret = gen6_gmch_probe(ggtt); -+ else -+ ret = gen8_gmch_probe(ggtt); -+ if (ret) -+ return ret; -+ -+ /* Trim the GGTT to fit the GuC mappable upper range (when enabled). -+ * This is easier than doing range restriction on the fly, as we -+ * currently don't have any bits spare to pass in this upper -+ * restriction! -+ */ -+ if (USES_GUC(dev_priv)) { -+ ggtt->vm.total = min_t(u64, ggtt->vm.total, GUC_GGTT_TOP); -+ ggtt->mappable_end = -+ min_t(u64, ggtt->mappable_end, ggtt->vm.total); -+ } -+ -+ if ((ggtt->vm.total - 1) >> 32) { -+ DRM_ERROR("We never expected a Global GTT with more than 32bits" -+ " of address space! Found %lldM!\n", -+ ggtt->vm.total >> 20); -+ ggtt->vm.total = 1ULL << 32; -+ ggtt->mappable_end = -+ min_t(u64, ggtt->mappable_end, ggtt->vm.total); -+ } -+ -+ if (ggtt->mappable_end > ggtt->vm.total) { -+ DRM_ERROR("mappable aperture extends past end of GGTT," -+ " aperture=%pa, total=%llx\n", -+ &ggtt->mappable_end, ggtt->vm.total); -+ ggtt->mappable_end = ggtt->vm.total; -+ } -+ -+ /* GMADR is the PCI mmio aperture into the global GTT. */ -+ DRM_DEBUG_DRIVER("GGTT size = %lluM\n", ggtt->vm.total >> 20); -+ DRM_DEBUG_DRIVER("GMADR size = %lluM\n", (u64)ggtt->mappable_end >> 20); -+ DRM_DEBUG_DRIVER("DSM size = %lluM\n", -+ (u64)resource_size(&intel_graphics_stolen_res) >> 20); -+ if (intel_vtd_active()) -+ DRM_INFO("VT-d active for gfx access\n"); -+ -+ return 0; -+} -+ -+/** -+ * i915_ggtt_init_hw - Initialize GGTT hardware -+ * @dev_priv: i915 device -+ */ -+int i915_ggtt_init_hw(struct drm_i915_private *dev_priv) -+{ -+ struct i915_ggtt *ggtt = &dev_priv->ggtt; -+ int ret; -+ -+ stash_init(&dev_priv->mm.wc_stash); -+ -+ /* Note that we use page colouring to enforce a guard page at the -+ * end of the address space. This is required as the CS may prefetch -+ * beyond the end of the batch buffer, across the page boundary, -+ * and beyond the end of the GTT if we do not provide a guard. -+ */ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ i915_address_space_init(&ggtt->vm, VM_CLASS_GGTT); -+ -+ ggtt->vm.is_ggtt = true; -+ -+ /* Only VLV supports read-only GGTT mappings */ -+ ggtt->vm.has_read_only = IS_VALLEYVIEW(dev_priv); -+ -+ if (!HAS_LLC(dev_priv) && !HAS_PPGTT(dev_priv)) -+ ggtt->vm.mm.color_adjust = i915_gtt_color_adjust; -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ -+ if (!io_mapping_init_wc(&dev_priv->ggtt.iomap, -+ dev_priv->ggtt.gmadr.start, -+ dev_priv->ggtt.mappable_end)) { -+ ret = -EIO; -+ goto out_gtt_cleanup; -+ } -+ -+ ggtt->mtrr = arch_phys_wc_add(ggtt->gmadr.start, ggtt->mappable_end); -+ -+ /* -+ * Initialise stolen early so that we may reserve preallocated -+ * objects for the BIOS to KMS transition. -+ */ -+ ret = i915_gem_init_stolen(dev_priv); -+ if (ret) -+ goto out_gtt_cleanup; -+ -+ return 0; -+ -+out_gtt_cleanup: -+ ggtt->vm.cleanup(&ggtt->vm); -+ return ret; -+} -+ -+int i915_ggtt_enable_hw(struct drm_i915_private *dev_priv) -+{ -+ if (INTEL_GEN(dev_priv) < 6 && !intel_enable_gtt()) -+ return -EIO; -+ -+ return 0; -+} -+ -+void i915_ggtt_enable_guc(struct drm_i915_private *i915) -+{ -+ GEM_BUG_ON(i915->ggtt.invalidate != gen6_ggtt_invalidate); -+ -+ i915->ggtt.invalidate = guc_ggtt_invalidate; -+ -+ i915_ggtt_invalidate(i915); -+} -+ -+void i915_ggtt_disable_guc(struct drm_i915_private *i915) -+{ -+ /* XXX Temporary pardon for error unload */ -+ if (i915->ggtt.invalidate == gen6_ggtt_invalidate) -+ return; -+ -+ /* We should only be called after i915_ggtt_enable_guc() */ -+ GEM_BUG_ON(i915->ggtt.invalidate != guc_ggtt_invalidate); -+ -+ i915->ggtt.invalidate = gen6_ggtt_invalidate; -+ -+ i915_ggtt_invalidate(i915); -+} -+ -+void i915_gem_restore_gtt_mappings(struct drm_i915_private *dev_priv) -+{ -+ struct i915_ggtt *ggtt = &dev_priv->ggtt; -+ struct i915_vma *vma, *vn; -+ -+ i915_check_and_clear_faults(dev_priv); -+ -+ mutex_lock(&ggtt->vm.mutex); -+ -+ /* First fill our portion of the GTT with scratch pages */ -+ ggtt->vm.clear_range(&ggtt->vm, 0, ggtt->vm.total); -+ ggtt->vm.closed = true; /* skip rewriting PTE on VMA unbind */ -+ -+ /* clflush objects bound into the GGTT and rebind them. */ -+ list_for_each_entry_safe(vma, vn, &ggtt->vm.bound_list, vm_link) { -+ struct drm_i915_gem_object *obj = vma->obj; -+ -+ if (!(vma->flags & I915_VMA_GLOBAL_BIND)) -+ continue; -+ -+ mutex_unlock(&ggtt->vm.mutex); -+ -+ if (!i915_vma_unbind(vma)) -+ goto lock; -+ -+ WARN_ON(i915_vma_bind(vma, -+ obj ? obj->cache_level : 0, -+ PIN_UPDATE)); -+ if (obj) -+ WARN_ON(i915_gem_object_set_to_gtt_domain(obj, false)); -+ -+lock: -+ mutex_lock(&ggtt->vm.mutex); -+ } -+ -+ ggtt->vm.closed = false; -+ i915_ggtt_invalidate(dev_priv); -+ -+ mutex_unlock(&ggtt->vm.mutex); -+ -+ if (INTEL_GEN(dev_priv) >= 8) { -+ struct intel_ppat *ppat = &dev_priv->ppat; -+ -+ bitmap_set(ppat->dirty, 0, ppat->max_entries); -+ dev_priv->ppat.update_hw(dev_priv); -+ return; -+ } -+} -+ -+static struct scatterlist * -+rotate_pages(struct drm_i915_gem_object *obj, unsigned int offset, -+ unsigned int width, unsigned int height, -+ unsigned int stride, -+ struct sg_table *st, struct scatterlist *sg) -+{ -+ unsigned int column, row; -+ unsigned int src_idx; -+ -+ for (column = 0; column < width; column++) { -+ src_idx = stride * (height - 1) + column + offset; -+ for (row = 0; row < height; row++) { -+ st->nents++; -+ /* We don't need the pages, but need to initialize -+ * the entries so the sg list can be happily traversed. -+ * The only thing we need are DMA addresses. -+ */ -+ sg_set_page(sg, NULL, I915_GTT_PAGE_SIZE, 0); -+ sg_dma_address(sg) = -+ i915_gem_object_get_dma_address(obj, src_idx); -+ sg_dma_len(sg) = I915_GTT_PAGE_SIZE; -+ sg = sg_next(sg); -+ src_idx -= stride; -+ } -+ } -+ -+ return sg; -+} -+ -+static noinline struct sg_table * -+intel_rotate_pages(struct intel_rotation_info *rot_info, -+ struct drm_i915_gem_object *obj) -+{ -+ unsigned int size = intel_rotation_info_size(rot_info); -+ struct sg_table *st; -+ struct scatterlist *sg; -+ int ret = -ENOMEM; -+ int i; -+ -+ /* Allocate target SG list. */ -+ st = kmalloc(sizeof(*st), GFP_KERNEL); -+ if (!st) -+ goto err_st_alloc; -+ -+ ret = sg_alloc_table(st, size, GFP_KERNEL); -+ if (ret) -+ goto err_sg_alloc; -+ -+ st->nents = 0; -+ sg = st->sgl; -+ -+ for (i = 0 ; i < ARRAY_SIZE(rot_info->plane); i++) { -+ sg = rotate_pages(obj, rot_info->plane[i].offset, -+ rot_info->plane[i].width, rot_info->plane[i].height, -+ rot_info->plane[i].stride, st, sg); -+ } -+ -+ return st; -+ -+err_sg_alloc: -+ kfree(st); -+err_st_alloc: -+ -+ DRM_DEBUG_DRIVER("Failed to create rotated mapping for object size %zu! (%ux%u tiles, %u pages)\n", -+ obj->base.size, rot_info->plane[0].width, rot_info->plane[0].height, size); -+ -+ return ERR_PTR(ret); -+} -+ -+static noinline struct sg_table * -+intel_partial_pages(const struct i915_ggtt_view *view, -+ struct drm_i915_gem_object *obj) -+{ -+ struct sg_table *st; -+ struct scatterlist *sg, *iter; -+ unsigned int count = view->partial.size; -+ unsigned int offset; -+ int ret = -ENOMEM; -+ -+ st = kmalloc(sizeof(*st), GFP_KERNEL); -+ if (!st) -+ goto err_st_alloc; -+ -+ ret = sg_alloc_table(st, count, GFP_KERNEL); -+ if (ret) -+ goto err_sg_alloc; -+ -+ iter = i915_gem_object_get_sg(obj, view->partial.offset, &offset); -+ GEM_BUG_ON(!iter); -+ -+ sg = st->sgl; -+ st->nents = 0; -+ do { -+ unsigned int len; -+ -+ len = min(iter->length - (offset << PAGE_SHIFT), -+ count << PAGE_SHIFT); -+ sg_set_page(sg, NULL, len, 0); -+ sg_dma_address(sg) = -+ sg_dma_address(iter) + (offset << PAGE_SHIFT); -+ sg_dma_len(sg) = len; -+ -+ st->nents++; -+ count -= len >> PAGE_SHIFT; -+ if (count == 0) { -+ sg_mark_end(sg); -+ i915_sg_trim(st); /* Drop any unused tail entries. */ -+ -+ return st; -+ } -+ -+ sg = __sg_next(sg); -+ iter = __sg_next(iter); -+ offset = 0; -+ } while (1); -+ -+err_sg_alloc: -+ kfree(st); -+err_st_alloc: -+ return ERR_PTR(ret); -+} -+ -+static int -+i915_get_ggtt_vma_pages(struct i915_vma *vma) -+{ -+ int ret; -+ -+ /* The vma->pages are only valid within the lifespan of the borrowed -+ * obj->mm.pages. When the obj->mm.pages sg_table is regenerated, so -+ * must be the vma->pages. A simple rule is that vma->pages must only -+ * be accessed when the obj->mm.pages are pinned. -+ */ -+ GEM_BUG_ON(!i915_gem_object_has_pinned_pages(vma->obj)); -+ -+ switch (vma->ggtt_view.type) { -+ default: -+ GEM_BUG_ON(vma->ggtt_view.type); -+ /* fall through */ -+ case I915_GGTT_VIEW_NORMAL: -+ vma->pages = vma->obj->mm.pages; -+ return 0; -+ -+ case I915_GGTT_VIEW_ROTATED: -+ vma->pages = -+ intel_rotate_pages(&vma->ggtt_view.rotated, vma->obj); -+ break; -+ -+ case I915_GGTT_VIEW_PARTIAL: -+ vma->pages = intel_partial_pages(&vma->ggtt_view, vma->obj); -+ break; -+ } -+ -+ ret = 0; -+ if (IS_ERR(vma->pages)) { -+ ret = PTR_ERR(vma->pages); -+ vma->pages = NULL; -+ DRM_ERROR("Failed to get pages for VMA view type %u (%d)!\n", -+ vma->ggtt_view.type, ret); -+ } -+ return ret; -+} -+ -+/** -+ * i915_gem_gtt_reserve - reserve a node in an address_space (GTT) -+ * @vm: the &struct i915_address_space -+ * @node: the &struct drm_mm_node (typically i915_vma.mode) -+ * @size: how much space to allocate inside the GTT, -+ * must be #I915_GTT_PAGE_SIZE aligned -+ * @offset: where to insert inside the GTT, -+ * must be #I915_GTT_MIN_ALIGNMENT aligned, and the node -+ * (@offset + @size) must fit within the address space -+ * @color: color to apply to node, if this node is not from a VMA, -+ * color must be #I915_COLOR_UNEVICTABLE -+ * @flags: control search and eviction behaviour -+ * -+ * i915_gem_gtt_reserve() tries to insert the @node at the exact @offset inside -+ * the address space (using @size and @color). If the @node does not fit, it -+ * tries to evict any overlapping nodes from the GTT, including any -+ * neighbouring nodes if the colors do not match (to ensure guard pages between -+ * differing domains). See i915_gem_evict_for_node() for the gory details -+ * on the eviction algorithm. #PIN_NONBLOCK may used to prevent waiting on -+ * evicting active overlapping objects, and any overlapping node that is pinned -+ * or marked as unevictable will also result in failure. -+ * -+ * Returns: 0 on success, -ENOSPC if no suitable hole is found, -EINTR if -+ * asked to wait for eviction and interrupted. -+ */ -+int i915_gem_gtt_reserve(struct i915_address_space *vm, -+ struct drm_mm_node *node, -+ u64 size, u64 offset, unsigned long color, -+ unsigned int flags) -+{ -+ int err; -+ -+ GEM_BUG_ON(!size); -+ GEM_BUG_ON(!IS_ALIGNED(size, I915_GTT_PAGE_SIZE)); -+ GEM_BUG_ON(!IS_ALIGNED(offset, I915_GTT_MIN_ALIGNMENT)); -+ GEM_BUG_ON(range_overflows(offset, size, vm->total)); -+ GEM_BUG_ON(vm == &vm->i915->mm.aliasing_ppgtt->vm); -+ GEM_BUG_ON(drm_mm_node_allocated(node)); -+ -+ node->size = size; -+ node->start = offset; -+ node->color = color; -+ -+ err = drm_mm_reserve_node(&vm->mm, node); -+ if (err != -ENOSPC) -+ return err; -+ -+ if (flags & PIN_NOEVICT) -+ return -ENOSPC; -+ -+ err = i915_gem_evict_for_node(vm, node, flags); -+ if (err == 0) -+ err = drm_mm_reserve_node(&vm->mm, node); -+ -+ return err; -+} -+ -+static u64 random_offset(u64 start, u64 end, u64 len, u64 align) -+{ -+ u64 range, addr; -+ -+ GEM_BUG_ON(range_overflows(start, len, end)); -+ GEM_BUG_ON(round_up(start, align) > round_down(end - len, align)); -+ -+ range = round_down(end - len, align) - round_up(start, align); -+ if (range) { -+ if (sizeof(unsigned long) == sizeof(u64)) { -+ addr = get_random_long(); -+ } else { -+ addr = get_random_int(); -+ if (range > U32_MAX) { -+ addr <<= 32; -+ addr |= get_random_int(); -+ } -+ } -+ div64_u64_rem(addr, range, &addr); -+ start += addr; -+ } -+ -+ return round_up(start, align); -+} -+ -+/** -+ * i915_gem_gtt_insert - insert a node into an address_space (GTT) -+ * @vm: the &struct i915_address_space -+ * @node: the &struct drm_mm_node (typically i915_vma.node) -+ * @size: how much space to allocate inside the GTT, -+ * must be #I915_GTT_PAGE_SIZE aligned -+ * @alignment: required alignment of starting offset, may be 0 but -+ * if specified, this must be a power-of-two and at least -+ * #I915_GTT_MIN_ALIGNMENT -+ * @color: color to apply to node -+ * @start: start of any range restriction inside GTT (0 for all), -+ * must be #I915_GTT_PAGE_SIZE aligned -+ * @end: end of any range restriction inside GTT (U64_MAX for all), -+ * must be #I915_GTT_PAGE_SIZE aligned if not U64_MAX -+ * @flags: control search and eviction behaviour -+ * -+ * i915_gem_gtt_insert() first searches for an available hole into which -+ * is can insert the node. The hole address is aligned to @alignment and -+ * its @size must then fit entirely within the [@start, @end] bounds. The -+ * nodes on either side of the hole must match @color, or else a guard page -+ * will be inserted between the two nodes (or the node evicted). If no -+ * suitable hole is found, first a victim is randomly selected and tested -+ * for eviction, otherwise then the LRU list of objects within the GTT -+ * is scanned to find the first set of replacement nodes to create the hole. -+ * Those old overlapping nodes are evicted from the GTT (and so must be -+ * rebound before any future use). Any node that is currently pinned cannot -+ * be evicted (see i915_vma_pin()). Similar if the node's VMA is currently -+ * active and #PIN_NONBLOCK is specified, that node is also skipped when -+ * searching for an eviction candidate. See i915_gem_evict_something() for -+ * the gory details on the eviction algorithm. -+ * -+ * Returns: 0 on success, -ENOSPC if no suitable hole is found, -EINTR if -+ * asked to wait for eviction and interrupted. -+ */ -+int i915_gem_gtt_insert(struct i915_address_space *vm, -+ struct drm_mm_node *node, -+ u64 size, u64 alignment, unsigned long color, -+ u64 start, u64 end, unsigned int flags) -+{ -+ enum drm_mm_insert_mode mode; -+ u64 offset; -+ int err; -+ -+ lockdep_assert_held(&vm->i915->drm.struct_mutex); -+ GEM_BUG_ON(!size); -+ GEM_BUG_ON(!IS_ALIGNED(size, I915_GTT_PAGE_SIZE)); -+ GEM_BUG_ON(alignment && !is_power_of_2(alignment)); -+ GEM_BUG_ON(alignment && !IS_ALIGNED(alignment, I915_GTT_MIN_ALIGNMENT)); -+ GEM_BUG_ON(start >= end); -+ GEM_BUG_ON(start > 0 && !IS_ALIGNED(start, I915_GTT_PAGE_SIZE)); -+ GEM_BUG_ON(end < U64_MAX && !IS_ALIGNED(end, I915_GTT_PAGE_SIZE)); -+ GEM_BUG_ON(vm == &vm->i915->mm.aliasing_ppgtt->vm); -+ GEM_BUG_ON(drm_mm_node_allocated(node)); -+ -+ if (unlikely(range_overflows(start, size, end))) -+ return -ENOSPC; -+ -+ if (unlikely(round_up(start, alignment) > round_down(end - size, alignment))) -+ return -ENOSPC; -+ -+ mode = DRM_MM_INSERT_BEST; -+ if (flags & PIN_HIGH) -+ mode = DRM_MM_INSERT_HIGHEST; -+ if (flags & PIN_MAPPABLE) -+ mode = DRM_MM_INSERT_LOW; -+ -+ /* We only allocate in PAGE_SIZE/GTT_PAGE_SIZE (4096) chunks, -+ * so we know that we always have a minimum alignment of 4096. -+ * The drm_mm range manager is optimised to return results -+ * with zero alignment, so where possible use the optimal -+ * path. -+ */ -+ BUILD_BUG_ON(I915_GTT_MIN_ALIGNMENT > I915_GTT_PAGE_SIZE); -+ if (alignment <= I915_GTT_MIN_ALIGNMENT) -+ alignment = 0; -+ -+ err = drm_mm_insert_node_in_range(&vm->mm, node, -+ size, alignment, color, -+ start, end, mode); -+ if (err != -ENOSPC) -+ return err; -+ -+ if (mode & DRM_MM_INSERT_ONCE) { -+ err = drm_mm_insert_node_in_range(&vm->mm, node, -+ size, alignment, color, -+ start, end, -+ DRM_MM_INSERT_BEST); -+ if (err != -ENOSPC) -+ return err; -+ } -+ -+ if (flags & PIN_NOEVICT) -+ return -ENOSPC; -+ -+ /* No free space, pick a slot at random. -+ * -+ * There is a pathological case here using a GTT shared between -+ * mmap and GPU (i.e. ggtt/aliasing_ppgtt but not full-ppgtt): -+ * -+ * |<-- 256 MiB aperture -->||<-- 1792 MiB unmappable -->| -+ * (64k objects) (448k objects) -+ * -+ * Now imagine that the eviction LRU is ordered top-down (just because -+ * pathology meets real life), and that we need to evict an object to -+ * make room inside the aperture. The eviction scan then has to walk -+ * the 448k list before it finds one within range. And now imagine that -+ * it has to search for a new hole between every byte inside the memcpy, -+ * for several simultaneous clients. -+ * -+ * On a full-ppgtt system, if we have run out of available space, there -+ * will be lots and lots of objects in the eviction list! Again, -+ * searching that LRU list may be slow if we are also applying any -+ * range restrictions (e.g. restriction to low 4GiB) and so, for -+ * simplicity and similarilty between different GTT, try the single -+ * random replacement first. -+ */ -+ offset = random_offset(start, end, -+ size, alignment ?: I915_GTT_MIN_ALIGNMENT); -+ err = i915_gem_gtt_reserve(vm, node, size, offset, color, flags); -+ if (err != -ENOSPC) -+ return err; -+ -+ /* Randomly selected placement is pinned, do a search */ -+ err = i915_gem_evict_something(vm, size, alignment, color, -+ start, end, flags); -+ if (err) -+ return err; -+ -+ return drm_mm_insert_node_in_range(&vm->mm, node, -+ size, alignment, color, -+ start, end, DRM_MM_INSERT_EVICT); -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -+#include "selftests/mock_gtt.c" -+#include "selftests/i915_gem_gtt.c" -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_gtt.h b/drivers/gpu/drm/i915_legacy/i915_gem_gtt.h -new file mode 100644 -index 000000000000..f597f35b109b ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_gtt.h -@@ -0,0 +1,664 @@ -+/* -+ * Copyright © 2014 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Please try to maintain the following order within this file unless it makes -+ * sense to do otherwise. From top to bottom: -+ * 1. typedefs -+ * 2. #defines, and macros -+ * 3. structure definitions -+ * 4. function prototypes -+ * -+ * Within each section, please try to order by generation in ascending order, -+ * from top to bottom (ie. gen6 on the top, gen8 on the bottom). -+ */ -+ -+#ifndef __I915_GEM_GTT_H__ -+#define __I915_GEM_GTT_H__ -+ -+#include -+#include -+#include -+ -+#include "i915_request.h" -+#include "i915_reset.h" -+#include "i915_selftest.h" -+#include "i915_timeline.h" -+ -+#define I915_GTT_PAGE_SIZE_4K BIT_ULL(12) -+#define I915_GTT_PAGE_SIZE_64K BIT_ULL(16) -+#define I915_GTT_PAGE_SIZE_2M BIT_ULL(21) -+ -+#define I915_GTT_PAGE_SIZE I915_GTT_PAGE_SIZE_4K -+#define I915_GTT_MAX_PAGE_SIZE I915_GTT_PAGE_SIZE_2M -+ -+#define I915_GTT_PAGE_MASK -I915_GTT_PAGE_SIZE -+ -+#define I915_GTT_MIN_ALIGNMENT I915_GTT_PAGE_SIZE -+ -+#define I915_FENCE_REG_NONE -1 -+#define I915_MAX_NUM_FENCES 32 -+/* 32 fences + sign bit for FENCE_REG_NONE */ -+#define I915_MAX_NUM_FENCE_BITS 6 -+ -+struct drm_i915_file_private; -+struct drm_i915_fence_reg; -+struct i915_vma; -+ -+typedef u32 gen6_pte_t; -+typedef u64 gen8_pte_t; -+typedef u64 gen8_pde_t; -+typedef u64 gen8_ppgtt_pdpe_t; -+typedef u64 gen8_ppgtt_pml4e_t; -+ -+#define ggtt_total_entries(ggtt) ((ggtt)->vm.total >> PAGE_SHIFT) -+ -+/* gen6-hsw has bit 11-4 for physical addr bit 39-32 */ -+#define GEN6_GTT_ADDR_ENCODE(addr) ((addr) | (((addr) >> 28) & 0xff0)) -+#define GEN6_PTE_ADDR_ENCODE(addr) GEN6_GTT_ADDR_ENCODE(addr) -+#define GEN6_PDE_ADDR_ENCODE(addr) GEN6_GTT_ADDR_ENCODE(addr) -+#define GEN6_PTE_CACHE_LLC (2 << 1) -+#define GEN6_PTE_UNCACHED (1 << 1) -+#define GEN6_PTE_VALID (1 << 0) -+ -+#define I915_PTES(pte_len) ((unsigned int)(PAGE_SIZE / (pte_len))) -+#define I915_PTE_MASK(pte_len) (I915_PTES(pte_len) - 1) -+#define I915_PDES 512 -+#define I915_PDE_MASK (I915_PDES - 1) -+#define NUM_PTE(pde_shift) (1 << (pde_shift - PAGE_SHIFT)) -+ -+#define GEN6_PTES I915_PTES(sizeof(gen6_pte_t)) -+#define GEN6_PD_SIZE (I915_PDES * PAGE_SIZE) -+#define GEN6_PD_ALIGN (PAGE_SIZE * 16) -+#define GEN6_PDE_SHIFT 22 -+#define GEN6_PDE_VALID (1 << 0) -+ -+#define GEN7_PTE_CACHE_L3_LLC (3 << 1) -+ -+#define BYT_PTE_SNOOPED_BY_CPU_CACHES (1 << 2) -+#define BYT_PTE_WRITEABLE (1 << 1) -+ -+/* Cacheability Control is a 4-bit value. The low three bits are stored in bits -+ * 3:1 of the PTE, while the fourth bit is stored in bit 11 of the PTE. -+ */ -+#define HSW_CACHEABILITY_CONTROL(bits) ((((bits) & 0x7) << 1) | \ -+ (((bits) & 0x8) << (11 - 3))) -+#define HSW_WB_LLC_AGE3 HSW_CACHEABILITY_CONTROL(0x2) -+#define HSW_WB_LLC_AGE0 HSW_CACHEABILITY_CONTROL(0x3) -+#define HSW_WB_ELLC_LLC_AGE3 HSW_CACHEABILITY_CONTROL(0x8) -+#define HSW_WB_ELLC_LLC_AGE0 HSW_CACHEABILITY_CONTROL(0xb) -+#define HSW_WT_ELLC_LLC_AGE3 HSW_CACHEABILITY_CONTROL(0x7) -+#define HSW_WT_ELLC_LLC_AGE0 HSW_CACHEABILITY_CONTROL(0x6) -+#define HSW_PTE_UNCACHED (0) -+#define HSW_GTT_ADDR_ENCODE(addr) ((addr) | (((addr) >> 28) & 0x7f0)) -+#define HSW_PTE_ADDR_ENCODE(addr) HSW_GTT_ADDR_ENCODE(addr) -+ -+/* GEN8 32b style address is defined as a 3 level page table: -+ * 31:30 | 29:21 | 20:12 | 11:0 -+ * PDPE | PDE | PTE | offset -+ * The difference as compared to normal x86 3 level page table is the PDPEs are -+ * programmed via register. -+ */ -+#define GEN8_3LVL_PDPES 4 -+#define GEN8_PDE_SHIFT 21 -+#define GEN8_PDE_MASK 0x1ff -+#define GEN8_PTE_SHIFT 12 -+#define GEN8_PTE_MASK 0x1ff -+#define GEN8_PTES I915_PTES(sizeof(gen8_pte_t)) -+ -+/* GEN8 48b style address is defined as a 4 level page table: -+ * 47:39 | 38:30 | 29:21 | 20:12 | 11:0 -+ * PML4E | PDPE | PDE | PTE | offset -+ */ -+#define GEN8_PML4ES_PER_PML4 512 -+#define GEN8_PML4E_SHIFT 39 -+#define GEN8_PML4E_MASK (GEN8_PML4ES_PER_PML4 - 1) -+#define GEN8_PDPE_SHIFT 30 -+/* NB: GEN8_PDPE_MASK is untrue for 32b platforms, but it has no impact on 32b page -+ * tables */ -+#define GEN8_PDPE_MASK 0x1ff -+ -+#define PPAT_UNCACHED (_PAGE_PWT | _PAGE_PCD) -+#define PPAT_CACHED_PDE 0 /* WB LLC */ -+#define PPAT_CACHED _PAGE_PAT /* WB LLCeLLC */ -+#define PPAT_DISPLAY_ELLC _PAGE_PCD /* WT eLLC */ -+ -+#define CHV_PPAT_SNOOP (1<<6) -+#define GEN8_PPAT_AGE(x) ((x)<<4) -+#define GEN8_PPAT_LLCeLLC (3<<2) -+#define GEN8_PPAT_LLCELLC (2<<2) -+#define GEN8_PPAT_LLC (1<<2) -+#define GEN8_PPAT_WB (3<<0) -+#define GEN8_PPAT_WT (2<<0) -+#define GEN8_PPAT_WC (1<<0) -+#define GEN8_PPAT_UC (0<<0) -+#define GEN8_PPAT_ELLC_OVERRIDE (0<<2) -+#define GEN8_PPAT(i, x) ((u64)(x) << ((i) * 8)) -+ -+#define GEN8_PPAT_GET_CA(x) ((x) & 3) -+#define GEN8_PPAT_GET_TC(x) ((x) & (3 << 2)) -+#define GEN8_PPAT_GET_AGE(x) ((x) & (3 << 4)) -+#define CHV_PPAT_GET_SNOOP(x) ((x) & (1 << 6)) -+ -+#define GEN8_PDE_IPS_64K BIT(11) -+#define GEN8_PDE_PS_2M BIT(7) -+ -+struct sg_table; -+ -+struct intel_rotation_info { -+ struct intel_rotation_plane_info { -+ /* tiles */ -+ unsigned int width, height, stride, offset; -+ } plane[2]; -+} __packed; -+ -+struct intel_partial_info { -+ u64 offset; -+ unsigned int size; -+} __packed; -+ -+enum i915_ggtt_view_type { -+ I915_GGTT_VIEW_NORMAL = 0, -+ I915_GGTT_VIEW_ROTATED = sizeof(struct intel_rotation_info), -+ I915_GGTT_VIEW_PARTIAL = sizeof(struct intel_partial_info), -+}; -+ -+static inline void assert_i915_gem_gtt_types(void) -+{ -+ BUILD_BUG_ON(sizeof(struct intel_rotation_info) != 8*sizeof(unsigned int)); -+ BUILD_BUG_ON(sizeof(struct intel_partial_info) != sizeof(u64) + sizeof(unsigned int)); -+ -+ /* As we encode the size of each branch inside the union into its type, -+ * we have to be careful that each branch has a unique size. -+ */ -+ switch ((enum i915_ggtt_view_type)0) { -+ case I915_GGTT_VIEW_NORMAL: -+ case I915_GGTT_VIEW_PARTIAL: -+ case I915_GGTT_VIEW_ROTATED: -+ /* gcc complains if these are identical cases */ -+ break; -+ } -+} -+ -+struct i915_ggtt_view { -+ enum i915_ggtt_view_type type; -+ union { -+ /* Members need to contain no holes/padding */ -+ struct intel_partial_info partial; -+ struct intel_rotation_info rotated; -+ }; -+}; -+ -+enum i915_cache_level; -+ -+struct i915_vma; -+ -+struct i915_page_dma { -+ struct page *page; -+ union { -+ dma_addr_t daddr; -+ -+ /* For gen6/gen7 only. This is the offset in the GGTT -+ * where the page directory entries for PPGTT begin -+ */ -+ u32 ggtt_offset; -+ }; -+}; -+ -+#define px_base(px) (&(px)->base) -+#define px_dma(px) (px_base(px)->daddr) -+ -+struct i915_page_table { -+ struct i915_page_dma base; -+ unsigned int used_ptes; -+}; -+ -+struct i915_page_directory { -+ struct i915_page_dma base; -+ -+ struct i915_page_table *page_table[I915_PDES]; /* PDEs */ -+ unsigned int used_pdes; -+}; -+ -+struct i915_page_directory_pointer { -+ struct i915_page_dma base; -+ struct i915_page_directory **page_directory; -+ unsigned int used_pdpes; -+}; -+ -+struct i915_pml4 { -+ struct i915_page_dma base; -+ struct i915_page_directory_pointer *pdps[GEN8_PML4ES_PER_PML4]; -+}; -+ -+struct i915_vma_ops { -+ /* Map an object into an address space with the given cache flags. */ -+ int (*bind_vma)(struct i915_vma *vma, -+ enum i915_cache_level cache_level, -+ u32 flags); -+ /* -+ * Unmap an object from an address space. This usually consists of -+ * setting the valid PTE entries to a reserved scratch page. -+ */ -+ void (*unbind_vma)(struct i915_vma *vma); -+ -+ int (*set_pages)(struct i915_vma *vma); -+ void (*clear_pages)(struct i915_vma *vma); -+}; -+ -+struct pagestash { -+ spinlock_t lock; -+ struct pagevec pvec; -+}; -+ -+struct i915_address_space { -+ struct drm_mm mm; -+ struct drm_i915_private *i915; -+ struct device *dma; -+ /* Every address space belongs to a struct file - except for the global -+ * GTT that is owned by the driver (and so @file is set to NULL). In -+ * principle, no information should leak from one context to another -+ * (or between files/processes etc) unless explicitly shared by the -+ * owner. Tracking the owner is important in order to free up per-file -+ * objects along with the file, to aide resource tracking, and to -+ * assign blame. -+ */ -+ struct drm_i915_file_private *file; -+ u64 total; /* size addr space maps (ex. 2GB for ggtt) */ -+ u64 reserved; /* size addr space reserved */ -+ -+ bool closed; -+ -+ struct mutex mutex; /* protects vma and our lists */ -+#define VM_CLASS_GGTT 0 -+#define VM_CLASS_PPGTT 1 -+ -+ u64 scratch_pte; -+ int scratch_order; -+ struct i915_page_dma scratch_page; -+ struct i915_page_table *scratch_pt; -+ struct i915_page_directory *scratch_pd; -+ struct i915_page_directory_pointer *scratch_pdp; /* GEN8+ & 48b PPGTT */ -+ -+ /** -+ * List of vma currently bound. -+ */ -+ struct list_head bound_list; -+ -+ /** -+ * List of vma that are not unbound. -+ */ -+ struct list_head unbound_list; -+ -+ struct pagestash free_pages; -+ -+ /* Global GTT */ -+ bool is_ggtt:1; -+ -+ /* Some systems require uncached updates of the page directories */ -+ bool pt_kmap_wc:1; -+ -+ /* Some systems support read-only mappings for GGTT and/or PPGTT */ -+ bool has_read_only:1; -+ -+ u64 (*pte_encode)(dma_addr_t addr, -+ enum i915_cache_level level, -+ u32 flags); /* Create a valid PTE */ -+#define PTE_READ_ONLY (1<<0) -+ -+ int (*allocate_va_range)(struct i915_address_space *vm, -+ u64 start, u64 length); -+ void (*clear_range)(struct i915_address_space *vm, -+ u64 start, u64 length); -+ void (*insert_page)(struct i915_address_space *vm, -+ dma_addr_t addr, -+ u64 offset, -+ enum i915_cache_level cache_level, -+ u32 flags); -+ void (*insert_entries)(struct i915_address_space *vm, -+ struct i915_vma *vma, -+ enum i915_cache_level cache_level, -+ u32 flags); -+ void (*cleanup)(struct i915_address_space *vm); -+ -+ struct i915_vma_ops vma_ops; -+ -+ I915_SELFTEST_DECLARE(struct fault_attr fault_attr); -+ I915_SELFTEST_DECLARE(bool scrub_64K); -+}; -+ -+#define i915_is_ggtt(vm) ((vm)->is_ggtt) -+ -+static inline bool -+i915_vm_is_4lvl(const struct i915_address_space *vm) -+{ -+ return (vm->total - 1) >> 32; -+} -+ -+static inline bool -+i915_vm_has_scratch_64K(struct i915_address_space *vm) -+{ -+ return vm->scratch_order == get_order(I915_GTT_PAGE_SIZE_64K); -+} -+ -+/* The Graphics Translation Table is the way in which GEN hardware translates a -+ * Graphics Virtual Address into a Physical Address. In addition to the normal -+ * collateral associated with any va->pa translations GEN hardware also has a -+ * portion of the GTT which can be mapped by the CPU and remain both coherent -+ * and correct (in cases like swizzling). That region is referred to as GMADR in -+ * the spec. -+ */ -+struct i915_ggtt { -+ struct i915_address_space vm; -+ -+ struct io_mapping iomap; /* Mapping to our CPU mappable region */ -+ struct resource gmadr; /* GMADR resource */ -+ resource_size_t mappable_end; /* End offset that we can CPU map */ -+ -+ /** "Graphics Stolen Memory" holds the global PTEs */ -+ void __iomem *gsm; -+ void (*invalidate)(struct drm_i915_private *dev_priv); -+ -+ bool do_idle_maps; -+ -+ int mtrr; -+ -+ u32 pin_bias; -+ -+ struct drm_mm_node error_capture; -+}; -+ -+struct i915_hw_ppgtt { -+ struct i915_address_space vm; -+ struct kref ref; -+ -+ intel_engine_mask_t pd_dirty_engines; -+ union { -+ struct i915_pml4 pml4; /* GEN8+ & 48b PPGTT */ -+ struct i915_page_directory_pointer pdp; /* GEN8+ */ -+ struct i915_page_directory pd; /* GEN6-7 */ -+ }; -+ -+ u32 user_handle; -+}; -+ -+struct gen6_hw_ppgtt { -+ struct i915_hw_ppgtt base; -+ -+ struct i915_vma *vma; -+ gen6_pte_t __iomem *pd_addr; -+ -+ unsigned int pin_count; -+ bool scan_for_unused_pt; -+}; -+ -+#define __to_gen6_ppgtt(base) container_of(base, struct gen6_hw_ppgtt, base) -+ -+static inline struct gen6_hw_ppgtt *to_gen6_ppgtt(struct i915_hw_ppgtt *base) -+{ -+ BUILD_BUG_ON(offsetof(struct gen6_hw_ppgtt, base)); -+ return __to_gen6_ppgtt(base); -+} -+ -+/* -+ * gen6_for_each_pde() iterates over every pde from start until start+length. -+ * If start and start+length are not perfectly divisible, the macro will round -+ * down and up as needed. Start=0 and length=2G effectively iterates over -+ * every PDE in the system. The macro modifies ALL its parameters except 'pd', -+ * so each of the other parameters should preferably be a simple variable, or -+ * at most an lvalue with no side-effects! -+ */ -+#define gen6_for_each_pde(pt, pd, start, length, iter) \ -+ for (iter = gen6_pde_index(start); \ -+ length > 0 && iter < I915_PDES && \ -+ (pt = (pd)->page_table[iter], true); \ -+ ({ u32 temp = ALIGN(start+1, 1 << GEN6_PDE_SHIFT); \ -+ temp = min(temp - start, length); \ -+ start += temp, length -= temp; }), ++iter) -+ -+#define gen6_for_all_pdes(pt, pd, iter) \ -+ for (iter = 0; \ -+ iter < I915_PDES && \ -+ (pt = (pd)->page_table[iter], true); \ -+ ++iter) -+ -+static inline u32 i915_pte_index(u64 address, unsigned int pde_shift) -+{ -+ const u32 mask = NUM_PTE(pde_shift) - 1; -+ -+ return (address >> PAGE_SHIFT) & mask; -+} -+ -+/* Helper to counts the number of PTEs within the given length. This count -+ * does not cross a page table boundary, so the max value would be -+ * GEN6_PTES for GEN6, and GEN8_PTES for GEN8. -+*/ -+static inline u32 i915_pte_count(u64 addr, u64 length, unsigned int pde_shift) -+{ -+ const u64 mask = ~((1ULL << pde_shift) - 1); -+ u64 end; -+ -+ GEM_BUG_ON(length == 0); -+ GEM_BUG_ON(offset_in_page(addr | length)); -+ -+ end = addr + length; -+ -+ if ((addr & mask) != (end & mask)) -+ return NUM_PTE(pde_shift) - i915_pte_index(addr, pde_shift); -+ -+ return i915_pte_index(end, pde_shift) - i915_pte_index(addr, pde_shift); -+} -+ -+static inline u32 i915_pde_index(u64 addr, u32 shift) -+{ -+ return (addr >> shift) & I915_PDE_MASK; -+} -+ -+static inline u32 gen6_pte_index(u32 addr) -+{ -+ return i915_pte_index(addr, GEN6_PDE_SHIFT); -+} -+ -+static inline u32 gen6_pte_count(u32 addr, u32 length) -+{ -+ return i915_pte_count(addr, length, GEN6_PDE_SHIFT); -+} -+ -+static inline u32 gen6_pde_index(u32 addr) -+{ -+ return i915_pde_index(addr, GEN6_PDE_SHIFT); -+} -+ -+static inline unsigned int -+i915_pdpes_per_pdp(const struct i915_address_space *vm) -+{ -+ if (i915_vm_is_4lvl(vm)) -+ return GEN8_PML4ES_PER_PML4; -+ -+ return GEN8_3LVL_PDPES; -+} -+ -+/* Equivalent to the gen6 version, For each pde iterates over every pde -+ * between from start until start + length. On gen8+ it simply iterates -+ * over every page directory entry in a page directory. -+ */ -+#define gen8_for_each_pde(pt, pd, start, length, iter) \ -+ for (iter = gen8_pde_index(start); \ -+ length > 0 && iter < I915_PDES && \ -+ (pt = (pd)->page_table[iter], true); \ -+ ({ u64 temp = ALIGN(start+1, 1 << GEN8_PDE_SHIFT); \ -+ temp = min(temp - start, length); \ -+ start += temp, length -= temp; }), ++iter) -+ -+#define gen8_for_each_pdpe(pd, pdp, start, length, iter) \ -+ for (iter = gen8_pdpe_index(start); \ -+ length > 0 && iter < i915_pdpes_per_pdp(vm) && \ -+ (pd = (pdp)->page_directory[iter], true); \ -+ ({ u64 temp = ALIGN(start+1, 1 << GEN8_PDPE_SHIFT); \ -+ temp = min(temp - start, length); \ -+ start += temp, length -= temp; }), ++iter) -+ -+#define gen8_for_each_pml4e(pdp, pml4, start, length, iter) \ -+ for (iter = gen8_pml4e_index(start); \ -+ length > 0 && iter < GEN8_PML4ES_PER_PML4 && \ -+ (pdp = (pml4)->pdps[iter], true); \ -+ ({ u64 temp = ALIGN(start+1, 1ULL << GEN8_PML4E_SHIFT); \ -+ temp = min(temp - start, length); \ -+ start += temp, length -= temp; }), ++iter) -+ -+static inline u32 gen8_pte_index(u64 address) -+{ -+ return i915_pte_index(address, GEN8_PDE_SHIFT); -+} -+ -+static inline u32 gen8_pde_index(u64 address) -+{ -+ return i915_pde_index(address, GEN8_PDE_SHIFT); -+} -+ -+static inline u32 gen8_pdpe_index(u64 address) -+{ -+ return (address >> GEN8_PDPE_SHIFT) & GEN8_PDPE_MASK; -+} -+ -+static inline u32 gen8_pml4e_index(u64 address) -+{ -+ return (address >> GEN8_PML4E_SHIFT) & GEN8_PML4E_MASK; -+} -+ -+static inline u64 gen8_pte_count(u64 address, u64 length) -+{ -+ return i915_pte_count(address, length, GEN8_PDE_SHIFT); -+} -+ -+static inline dma_addr_t -+i915_page_dir_dma_addr(const struct i915_hw_ppgtt *ppgtt, const unsigned n) -+{ -+ return px_dma(ppgtt->pdp.page_directory[n]); -+} -+ -+static inline struct i915_ggtt * -+i915_vm_to_ggtt(struct i915_address_space *vm) -+{ -+ GEM_BUG_ON(!i915_is_ggtt(vm)); -+ return container_of(vm, struct i915_ggtt, vm); -+} -+ -+#define INTEL_MAX_PPAT_ENTRIES 8 -+#define INTEL_PPAT_PERFECT_MATCH (~0U) -+ -+struct intel_ppat; -+ -+struct intel_ppat_entry { -+ struct intel_ppat *ppat; -+ struct kref ref; -+ u8 value; -+}; -+ -+struct intel_ppat { -+ struct intel_ppat_entry entries[INTEL_MAX_PPAT_ENTRIES]; -+ DECLARE_BITMAP(used, INTEL_MAX_PPAT_ENTRIES); -+ DECLARE_BITMAP(dirty, INTEL_MAX_PPAT_ENTRIES); -+ unsigned int max_entries; -+ u8 clear_value; -+ /* -+ * Return a score to show how two PPAT values match, -+ * a INTEL_PPAT_PERFECT_MATCH indicates a perfect match -+ */ -+ unsigned int (*match)(u8 src, u8 dst); -+ void (*update_hw)(struct drm_i915_private *i915); -+ -+ struct drm_i915_private *i915; -+}; -+ -+const struct intel_ppat_entry * -+intel_ppat_get(struct drm_i915_private *i915, u8 value); -+void intel_ppat_put(const struct intel_ppat_entry *entry); -+ -+int i915_gem_init_aliasing_ppgtt(struct drm_i915_private *i915); -+void i915_gem_fini_aliasing_ppgtt(struct drm_i915_private *i915); -+ -+int i915_ggtt_probe_hw(struct drm_i915_private *dev_priv); -+int i915_ggtt_init_hw(struct drm_i915_private *dev_priv); -+int i915_ggtt_enable_hw(struct drm_i915_private *dev_priv); -+void i915_ggtt_enable_guc(struct drm_i915_private *i915); -+void i915_ggtt_disable_guc(struct drm_i915_private *i915); -+int i915_gem_init_ggtt(struct drm_i915_private *dev_priv); -+void i915_ggtt_cleanup_hw(struct drm_i915_private *dev_priv); -+ -+int i915_ppgtt_init_hw(struct drm_i915_private *dev_priv); -+ -+struct i915_hw_ppgtt *i915_ppgtt_create(struct drm_i915_private *dev_priv); -+void i915_ppgtt_release(struct kref *kref); -+ -+static inline struct i915_hw_ppgtt *i915_ppgtt_get(struct i915_hw_ppgtt *ppgtt) -+{ -+ kref_get(&ppgtt->ref); -+ return ppgtt; -+} -+ -+static inline void i915_ppgtt_put(struct i915_hw_ppgtt *ppgtt) -+{ -+ if (ppgtt) -+ kref_put(&ppgtt->ref, i915_ppgtt_release); -+} -+ -+int gen6_ppgtt_pin(struct i915_hw_ppgtt *base); -+void gen6_ppgtt_unpin(struct i915_hw_ppgtt *base); -+void gen6_ppgtt_unpin_all(struct i915_hw_ppgtt *base); -+ -+void i915_check_and_clear_faults(struct drm_i915_private *dev_priv); -+void i915_gem_suspend_gtt_mappings(struct drm_i915_private *dev_priv); -+void i915_gem_restore_gtt_mappings(struct drm_i915_private *dev_priv); -+ -+int __must_check i915_gem_gtt_prepare_pages(struct drm_i915_gem_object *obj, -+ struct sg_table *pages); -+void i915_gem_gtt_finish_pages(struct drm_i915_gem_object *obj, -+ struct sg_table *pages); -+ -+int i915_gem_gtt_reserve(struct i915_address_space *vm, -+ struct drm_mm_node *node, -+ u64 size, u64 offset, unsigned long color, -+ unsigned int flags); -+ -+int i915_gem_gtt_insert(struct i915_address_space *vm, -+ struct drm_mm_node *node, -+ u64 size, u64 alignment, unsigned long color, -+ u64 start, u64 end, unsigned int flags); -+ -+/* Flags used by pin/bind&friends. */ -+#define PIN_NONBLOCK BIT_ULL(0) -+#define PIN_NONFAULT BIT_ULL(1) -+#define PIN_NOEVICT BIT_ULL(2) -+#define PIN_MAPPABLE BIT_ULL(3) -+#define PIN_ZONE_4G BIT_ULL(4) -+#define PIN_HIGH BIT_ULL(5) -+#define PIN_OFFSET_BIAS BIT_ULL(6) -+#define PIN_OFFSET_FIXED BIT_ULL(7) -+ -+#define PIN_MBZ BIT_ULL(8) /* I915_VMA_PIN_OVERFLOW */ -+#define PIN_GLOBAL BIT_ULL(9) /* I915_VMA_GLOBAL_BIND */ -+#define PIN_USER BIT_ULL(10) /* I915_VMA_LOCAL_BIND */ -+#define PIN_UPDATE BIT_ULL(11) -+ -+#define PIN_OFFSET_MASK (-I915_GTT_PAGE_SIZE) -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_internal.c b/drivers/gpu/drm/i915_legacy/i915_gem_internal.c -new file mode 100644 -index 000000000000..ab627ed1269c ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_internal.c -@@ -0,0 +1,210 @@ -+/* -+ * Copyright © 2014-2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include -+#include "i915_drv.h" -+ -+#define QUIET (__GFP_NORETRY | __GFP_NOWARN) -+#define MAYFAIL (__GFP_RETRY_MAYFAIL | __GFP_NOWARN) -+ -+/* convert swiotlb segment size into sensible units (pages)! */ -+#define IO_TLB_SEGPAGES (IO_TLB_SEGSIZE << IO_TLB_SHIFT >> PAGE_SHIFT) -+ -+static void internal_free_pages(struct sg_table *st) -+{ -+ struct scatterlist *sg; -+ -+ for (sg = st->sgl; sg; sg = __sg_next(sg)) { -+ if (sg_page(sg)) -+ __free_pages(sg_page(sg), get_order(sg->length)); -+ } -+ -+ sg_free_table(st); -+ kfree(st); -+} -+ -+static int i915_gem_object_get_pages_internal(struct drm_i915_gem_object *obj) -+{ -+ struct drm_i915_private *i915 = to_i915(obj->base.dev); -+ struct sg_table *st; -+ struct scatterlist *sg; -+ unsigned int sg_page_sizes; -+ unsigned int npages; -+ int max_order; -+ gfp_t gfp; -+ -+ max_order = MAX_ORDER; -+#ifdef CONFIG_SWIOTLB -+ if (swiotlb_nr_tbl()) { -+ unsigned int max_segment; -+ -+ max_segment = swiotlb_max_segment(); -+ if (max_segment) { -+ max_segment = max_t(unsigned int, max_segment, -+ PAGE_SIZE) >> PAGE_SHIFT; -+ max_order = min(max_order, ilog2(max_segment)); -+ } -+ } -+#endif -+ -+ gfp = GFP_KERNEL | __GFP_HIGHMEM | __GFP_RECLAIMABLE; -+ if (IS_I965GM(i915) || IS_I965G(i915)) { -+ /* 965gm cannot relocate objects above 4GiB. */ -+ gfp &= ~__GFP_HIGHMEM; -+ gfp |= __GFP_DMA32; -+ } -+ -+create_st: -+ st = kmalloc(sizeof(*st), GFP_KERNEL); -+ if (!st) -+ return -ENOMEM; -+ -+ npages = obj->base.size / PAGE_SIZE; -+ if (sg_alloc_table(st, npages, GFP_KERNEL)) { -+ kfree(st); -+ return -ENOMEM; -+ } -+ -+ sg = st->sgl; -+ st->nents = 0; -+ sg_page_sizes = 0; -+ -+ do { -+ int order = min(fls(npages) - 1, max_order); -+ struct page *page; -+ -+ do { -+ page = alloc_pages(gfp | (order ? QUIET : MAYFAIL), -+ order); -+ if (page) -+ break; -+ if (!order--) -+ goto err; -+ -+ /* Limit subsequent allocations as well */ -+ max_order = order; -+ } while (1); -+ -+ sg_set_page(sg, page, PAGE_SIZE << order, 0); -+ sg_page_sizes |= PAGE_SIZE << order; -+ st->nents++; -+ -+ npages -= 1 << order; -+ if (!npages) { -+ sg_mark_end(sg); -+ break; -+ } -+ -+ sg = __sg_next(sg); -+ } while (1); -+ -+ if (i915_gem_gtt_prepare_pages(obj, st)) { -+ /* Failed to dma-map try again with single page sg segments */ -+ if (get_order(st->sgl->length)) { -+ internal_free_pages(st); -+ max_order = 0; -+ goto create_st; -+ } -+ goto err; -+ } -+ -+ /* Mark the pages as dontneed whilst they are still pinned. As soon -+ * as they are unpinned they are allowed to be reaped by the shrinker, -+ * and the caller is expected to repopulate - the contents of this -+ * object are only valid whilst active and pinned. -+ */ -+ obj->mm.madv = I915_MADV_DONTNEED; -+ -+ __i915_gem_object_set_pages(obj, st, sg_page_sizes); -+ -+ return 0; -+ -+err: -+ sg_set_page(sg, NULL, 0, 0); -+ sg_mark_end(sg); -+ internal_free_pages(st); -+ -+ return -ENOMEM; -+} -+ -+static void i915_gem_object_put_pages_internal(struct drm_i915_gem_object *obj, -+ struct sg_table *pages) -+{ -+ i915_gem_gtt_finish_pages(obj, pages); -+ internal_free_pages(pages); -+ -+ obj->mm.dirty = false; -+ obj->mm.madv = I915_MADV_WILLNEED; -+} -+ -+static const struct drm_i915_gem_object_ops i915_gem_object_internal_ops = { -+ .flags = I915_GEM_OBJECT_HAS_STRUCT_PAGE | -+ I915_GEM_OBJECT_IS_SHRINKABLE, -+ .get_pages = i915_gem_object_get_pages_internal, -+ .put_pages = i915_gem_object_put_pages_internal, -+}; -+ -+/** -+ * i915_gem_object_create_internal: create an object with volatile pages -+ * @i915: the i915 device -+ * @size: the size in bytes of backing storage to allocate for the object -+ * -+ * Creates a new object that wraps some internal memory for private use. -+ * This object is not backed by swappable storage, and as such its contents -+ * are volatile and only valid whilst pinned. If the object is reaped by the -+ * shrinker, its pages and data will be discarded. Equally, it is not a full -+ * GEM object and so not valid for access from userspace. This makes it useful -+ * for hardware interfaces like ringbuffers (which are pinned from the time -+ * the request is written to the time the hardware stops accessing it), but -+ * not for contexts (which need to be preserved when not active for later -+ * reuse). Note that it is not cleared upon allocation. -+ */ -+struct drm_i915_gem_object * -+i915_gem_object_create_internal(struct drm_i915_private *i915, -+ phys_addr_t size) -+{ -+ struct drm_i915_gem_object *obj; -+ unsigned int cache_level; -+ -+ GEM_BUG_ON(!size); -+ GEM_BUG_ON(!IS_ALIGNED(size, PAGE_SIZE)); -+ -+ if (overflows_type(size, obj->base.size)) -+ return ERR_PTR(-E2BIG); -+ -+ obj = i915_gem_object_alloc(); -+ if (!obj) -+ return ERR_PTR(-ENOMEM); -+ -+ drm_gem_private_object_init(&i915->drm, &obj->base, size); -+ i915_gem_object_init(obj, &i915_gem_object_internal_ops); -+ -+ obj->read_domains = I915_GEM_DOMAIN_CPU; -+ obj->write_domain = I915_GEM_DOMAIN_CPU; -+ -+ cache_level = HAS_LLC(i915) ? I915_CACHE_LLC : I915_CACHE_NONE; -+ i915_gem_object_set_cache_coherency(obj, cache_level); -+ -+ return obj; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_object.c b/drivers/gpu/drm/i915_legacy/i915_gem_object.c -new file mode 100644 -index 000000000000..ac6a5ab84586 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_object.c -@@ -0,0 +1,90 @@ -+/* -+ * Copyright © 2017 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "i915_gem_object.h" -+#include "i915_globals.h" -+ -+static struct i915_global_object { -+ struct i915_global base; -+ struct kmem_cache *slab_objects; -+} global; -+ -+struct drm_i915_gem_object *i915_gem_object_alloc(void) -+{ -+ return kmem_cache_zalloc(global.slab_objects, GFP_KERNEL); -+} -+ -+void i915_gem_object_free(struct drm_i915_gem_object *obj) -+{ -+ return kmem_cache_free(global.slab_objects, obj); -+} -+ -+/** -+ * Mark up the object's coherency levels for a given cache_level -+ * @obj: #drm_i915_gem_object -+ * @cache_level: cache level -+ */ -+void i915_gem_object_set_cache_coherency(struct drm_i915_gem_object *obj, -+ unsigned int cache_level) -+{ -+ obj->cache_level = cache_level; -+ -+ if (cache_level != I915_CACHE_NONE) -+ obj->cache_coherent = (I915_BO_CACHE_COHERENT_FOR_READ | -+ I915_BO_CACHE_COHERENT_FOR_WRITE); -+ else if (HAS_LLC(to_i915(obj->base.dev))) -+ obj->cache_coherent = I915_BO_CACHE_COHERENT_FOR_READ; -+ else -+ obj->cache_coherent = 0; -+ -+ obj->cache_dirty = -+ !(obj->cache_coherent & I915_BO_CACHE_COHERENT_FOR_WRITE); -+} -+ -+static void i915_global_objects_shrink(void) -+{ -+ kmem_cache_shrink(global.slab_objects); -+} -+ -+static void i915_global_objects_exit(void) -+{ -+ kmem_cache_destroy(global.slab_objects); -+} -+ -+static struct i915_global_object global = { { -+ .shrink = i915_global_objects_shrink, -+ .exit = i915_global_objects_exit, -+} }; -+ -+int __init i915_global_objects_init(void) -+{ -+ global.slab_objects = -+ KMEM_CACHE(drm_i915_gem_object, SLAB_HWCACHE_ALIGN); -+ if (!global.slab_objects) -+ return -ENOMEM; -+ -+ i915_global_register(&global.base); -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_object.h b/drivers/gpu/drm/i915_legacy/i915_gem_object.h -new file mode 100644 -index 000000000000..ca93a40c0c87 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_object.h -@@ -0,0 +1,509 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef __I915_GEM_OBJECT_H__ -+#define __I915_GEM_OBJECT_H__ -+ -+#include -+ -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "i915_request.h" -+#include "i915_selftest.h" -+ -+struct drm_i915_gem_object; -+ -+/* -+ * struct i915_lut_handle tracks the fast lookups from handle to vma used -+ * for execbuf. Although we use a radixtree for that mapping, in order to -+ * remove them as the object or context is closed, we need a secondary list -+ * and a translation entry (i915_lut_handle). -+ */ -+struct i915_lut_handle { -+ struct list_head obj_link; -+ struct list_head ctx_link; -+ struct i915_gem_context *ctx; -+ u32 handle; -+}; -+ -+struct drm_i915_gem_object_ops { -+ unsigned int flags; -+#define I915_GEM_OBJECT_HAS_STRUCT_PAGE BIT(0) -+#define I915_GEM_OBJECT_IS_SHRINKABLE BIT(1) -+#define I915_GEM_OBJECT_IS_PROXY BIT(2) -+#define I915_GEM_OBJECT_ASYNC_CANCEL BIT(3) -+ -+ /* Interface between the GEM object and its backing storage. -+ * get_pages() is called once prior to the use of the associated set -+ * of pages before to binding them into the GTT, and put_pages() is -+ * called after we no longer need them. As we expect there to be -+ * associated cost with migrating pages between the backing storage -+ * and making them available for the GPU (e.g. clflush), we may hold -+ * onto the pages after they are no longer referenced by the GPU -+ * in case they may be used again shortly (for example migrating the -+ * pages to a different memory domain within the GTT). put_pages() -+ * will therefore most likely be called when the object itself is -+ * being released or under memory pressure (where we attempt to -+ * reap pages for the shrinker). -+ */ -+ int (*get_pages)(struct drm_i915_gem_object *); -+ void (*put_pages)(struct drm_i915_gem_object *, struct sg_table *); -+ -+ int (*pwrite)(struct drm_i915_gem_object *, -+ const struct drm_i915_gem_pwrite *); -+ -+ int (*dmabuf_export)(struct drm_i915_gem_object *); -+ void (*release)(struct drm_i915_gem_object *); -+}; -+ -+struct drm_i915_gem_object { -+ struct drm_gem_object base; -+ -+ const struct drm_i915_gem_object_ops *ops; -+ -+ struct { -+ /** -+ * @vma.lock: protect the list/tree of vmas -+ */ -+ spinlock_t lock; -+ -+ /** -+ * @vma.list: List of VMAs backed by this object -+ * -+ * The VMA on this list are ordered by type, all GGTT vma are -+ * placed at the head and all ppGTT vma are placed at the tail. -+ * The different types of GGTT vma are unordered between -+ * themselves, use the @vma.tree (which has a defined order -+ * between all VMA) to quickly find an exact match. -+ */ -+ struct list_head list; -+ -+ /** -+ * @vma.tree: Ordered tree of VMAs backed by this object -+ * -+ * All VMA created for this object are placed in the @vma.tree -+ * for fast retrieval via a binary search in -+ * i915_vma_instance(). They are also added to @vma.list for -+ * easy iteration. -+ */ -+ struct rb_root tree; -+ } vma; -+ -+ /** -+ * @lut_list: List of vma lookup entries in use for this object. -+ * -+ * If this object is closed, we need to remove all of its VMA from -+ * the fast lookup index in associated contexts; @lut_list provides -+ * this translation from object to context->handles_vma. -+ */ -+ struct list_head lut_list; -+ -+ /** Stolen memory for this object, instead of being backed by shmem. */ -+ struct drm_mm_node *stolen; -+ union { -+ struct rcu_head rcu; -+ struct llist_node freed; -+ }; -+ -+ /** -+ * Whether the object is currently in the GGTT mmap. -+ */ -+ unsigned int userfault_count; -+ struct list_head userfault_link; -+ -+ struct list_head batch_pool_link; -+ I915_SELFTEST_DECLARE(struct list_head st_link); -+ -+ unsigned long flags; -+ -+ /** -+ * Have we taken a reference for the object for incomplete GPU -+ * activity? -+ */ -+#define I915_BO_ACTIVE_REF 0 -+ -+ /* -+ * Is the object to be mapped as read-only to the GPU -+ * Only honoured if hardware has relevant pte bit -+ */ -+ unsigned int cache_level:3; -+ unsigned int cache_coherent:2; -+#define I915_BO_CACHE_COHERENT_FOR_READ BIT(0) -+#define I915_BO_CACHE_COHERENT_FOR_WRITE BIT(1) -+ unsigned int cache_dirty:1; -+ -+ /** -+ * @read_domains: Read memory domains. -+ * -+ * These monitor which caches contain read/write data related to the -+ * object. When transitioning from one set of domains to another, -+ * the driver is called to ensure that caches are suitably flushed and -+ * invalidated. -+ */ -+ u16 read_domains; -+ -+ /** -+ * @write_domain: Corresponding unique write memory domain. -+ */ -+ u16 write_domain; -+ -+ atomic_t frontbuffer_bits; -+ unsigned int frontbuffer_ggtt_origin; /* write once */ -+ struct i915_active_request frontbuffer_write; -+ -+ /** Current tiling stride for the object, if it's tiled. */ -+ unsigned int tiling_and_stride; -+#define FENCE_MINIMUM_STRIDE 128 /* See i915_tiling_ok() */ -+#define TILING_MASK (FENCE_MINIMUM_STRIDE-1) -+#define STRIDE_MASK (~TILING_MASK) -+ -+ /** Count of VMA actually bound by this object */ -+ unsigned int bind_count; -+ unsigned int active_count; -+ /** Count of how many global VMA are currently pinned for use by HW */ -+ unsigned int pin_global; -+ -+ struct { -+ struct mutex lock; /* protects the pages and their use */ -+ atomic_t pages_pin_count; -+ -+ struct sg_table *pages; -+ void *mapping; -+ -+ /* TODO: whack some of this into the error state */ -+ struct i915_page_sizes { -+ /** -+ * The sg mask of the pages sg_table. i.e the mask of -+ * of the lengths for each sg entry. -+ */ -+ unsigned int phys; -+ -+ /** -+ * The gtt page sizes we are allowed to use given the -+ * sg mask and the supported page sizes. This will -+ * express the smallest unit we can use for the whole -+ * object, as well as the larger sizes we may be able -+ * to use opportunistically. -+ */ -+ unsigned int sg; -+ -+ /** -+ * The actual gtt page size usage. Since we can have -+ * multiple vma associated with this object we need to -+ * prevent any trampling of state, hence a copy of this -+ * struct also lives in each vma, therefore the gtt -+ * value here should only be read/write through the vma. -+ */ -+ unsigned int gtt; -+ } page_sizes; -+ -+ I915_SELFTEST_DECLARE(unsigned int page_mask); -+ -+ struct i915_gem_object_page_iter { -+ struct scatterlist *sg_pos; -+ unsigned int sg_idx; /* in pages, but 32bit eek! */ -+ -+ struct radix_tree_root radix; -+ struct mutex lock; /* protects this cache */ -+ } get_page; -+ -+ /** -+ * Element within i915->mm.unbound_list or i915->mm.bound_list, -+ * locked by i915->mm.obj_lock. -+ */ -+ struct list_head link; -+ -+ /** -+ * Advice: are the backing pages purgeable? -+ */ -+ unsigned int madv:2; -+ -+ /** -+ * This is set if the object has been written to since the -+ * pages were last acquired. -+ */ -+ bool dirty:1; -+ -+ /** -+ * This is set if the object has been pinned due to unknown -+ * swizzling. -+ */ -+ bool quirked:1; -+ } mm; -+ -+ /** Breadcrumb of last rendering to the buffer. -+ * There can only be one writer, but we allow for multiple readers. -+ * If there is a writer that necessarily implies that all other -+ * read requests are complete - but we may only be lazily clearing -+ * the read requests. A read request is naturally the most recent -+ * request on a ring, so we may have two different write and read -+ * requests on one ring where the write request is older than the -+ * read request. This allows for the CPU to read from an active -+ * buffer by only waiting for the write to complete. -+ */ -+ struct reservation_object *resv; -+ -+ /** References from framebuffers, locks out tiling changes. */ -+ unsigned int framebuffer_references; -+ -+ /** Record of address bit 17 of each page at last unbind. */ -+ unsigned long *bit_17; -+ -+ union { -+ struct i915_gem_userptr { -+ uintptr_t ptr; -+ -+ struct i915_mm_struct *mm; -+ struct i915_mmu_object *mmu_object; -+ struct work_struct *work; -+ } userptr; -+ -+ unsigned long scratch; -+ -+ void *gvt_info; -+ }; -+ -+ /** for phys allocated objects */ -+ struct drm_dma_handle *phys_handle; -+ -+ struct reservation_object __builtin_resv; -+}; -+ -+static inline struct drm_i915_gem_object * -+to_intel_bo(struct drm_gem_object *gem) -+{ -+ /* Assert that to_intel_bo(NULL) == NULL */ -+ BUILD_BUG_ON(offsetof(struct drm_i915_gem_object, base)); -+ -+ return container_of(gem, struct drm_i915_gem_object, base); -+} -+ -+struct drm_i915_gem_object *i915_gem_object_alloc(void); -+void i915_gem_object_free(struct drm_i915_gem_object *obj); -+ -+/** -+ * i915_gem_object_lookup_rcu - look up a temporary GEM object from its handle -+ * @filp: DRM file private date -+ * @handle: userspace handle -+ * -+ * Returns: -+ * -+ * A pointer to the object named by the handle if such exists on @filp, NULL -+ * otherwise. This object is only valid whilst under the RCU read lock, and -+ * note carefully the object may be in the process of being destroyed. -+ */ -+static inline struct drm_i915_gem_object * -+i915_gem_object_lookup_rcu(struct drm_file *file, u32 handle) -+{ -+#ifdef CONFIG_LOCKDEP -+ WARN_ON(debug_locks && !lock_is_held(&rcu_lock_map)); -+#endif -+ return idr_find(&file->object_idr, handle); -+} -+ -+static inline struct drm_i915_gem_object * -+i915_gem_object_lookup(struct drm_file *file, u32 handle) -+{ -+ struct drm_i915_gem_object *obj; -+ -+ rcu_read_lock(); -+ obj = i915_gem_object_lookup_rcu(file, handle); -+ if (obj && !kref_get_unless_zero(&obj->base.refcount)) -+ obj = NULL; -+ rcu_read_unlock(); -+ -+ return obj; -+} -+ -+__deprecated -+extern struct drm_gem_object * -+drm_gem_object_lookup(struct drm_file *file, u32 handle); -+ -+__attribute__((nonnull)) -+static inline struct drm_i915_gem_object * -+i915_gem_object_get(struct drm_i915_gem_object *obj) -+{ -+ drm_gem_object_get(&obj->base); -+ return obj; -+} -+ -+__attribute__((nonnull)) -+static inline void -+i915_gem_object_put(struct drm_i915_gem_object *obj) -+{ -+ __drm_gem_object_put(&obj->base); -+} -+ -+static inline void i915_gem_object_lock(struct drm_i915_gem_object *obj) -+{ -+ reservation_object_lock(obj->resv, NULL); -+} -+ -+static inline void i915_gem_object_unlock(struct drm_i915_gem_object *obj) -+{ -+ reservation_object_unlock(obj->resv); -+} -+ -+static inline void -+i915_gem_object_set_readonly(struct drm_i915_gem_object *obj) -+{ -+ obj->base.vma_node.readonly = true; -+} -+ -+static inline bool -+i915_gem_object_is_readonly(const struct drm_i915_gem_object *obj) -+{ -+ return obj->base.vma_node.readonly; -+} -+ -+static inline bool -+i915_gem_object_has_struct_page(const struct drm_i915_gem_object *obj) -+{ -+ return obj->ops->flags & I915_GEM_OBJECT_HAS_STRUCT_PAGE; -+} -+ -+static inline bool -+i915_gem_object_is_shrinkable(const struct drm_i915_gem_object *obj) -+{ -+ return obj->ops->flags & I915_GEM_OBJECT_IS_SHRINKABLE; -+} -+ -+static inline bool -+i915_gem_object_is_proxy(const struct drm_i915_gem_object *obj) -+{ -+ return obj->ops->flags & I915_GEM_OBJECT_IS_PROXY; -+} -+ -+static inline bool -+i915_gem_object_needs_async_cancel(const struct drm_i915_gem_object *obj) -+{ -+ return obj->ops->flags & I915_GEM_OBJECT_ASYNC_CANCEL; -+} -+ -+static inline bool -+i915_gem_object_is_active(const struct drm_i915_gem_object *obj) -+{ -+ return obj->active_count; -+} -+ -+static inline bool -+i915_gem_object_has_active_reference(const struct drm_i915_gem_object *obj) -+{ -+ return test_bit(I915_BO_ACTIVE_REF, &obj->flags); -+} -+ -+static inline void -+i915_gem_object_set_active_reference(struct drm_i915_gem_object *obj) -+{ -+ lockdep_assert_held(&obj->base.dev->struct_mutex); -+ __set_bit(I915_BO_ACTIVE_REF, &obj->flags); -+} -+ -+static inline void -+i915_gem_object_clear_active_reference(struct drm_i915_gem_object *obj) -+{ -+ lockdep_assert_held(&obj->base.dev->struct_mutex); -+ __clear_bit(I915_BO_ACTIVE_REF, &obj->flags); -+} -+ -+void __i915_gem_object_release_unless_active(struct drm_i915_gem_object *obj); -+ -+static inline bool -+i915_gem_object_is_framebuffer(const struct drm_i915_gem_object *obj) -+{ -+ return READ_ONCE(obj->framebuffer_references); -+} -+ -+static inline unsigned int -+i915_gem_object_get_tiling(const struct drm_i915_gem_object *obj) -+{ -+ return obj->tiling_and_stride & TILING_MASK; -+} -+ -+static inline bool -+i915_gem_object_is_tiled(const struct drm_i915_gem_object *obj) -+{ -+ return i915_gem_object_get_tiling(obj) != I915_TILING_NONE; -+} -+ -+static inline unsigned int -+i915_gem_object_get_stride(const struct drm_i915_gem_object *obj) -+{ -+ return obj->tiling_and_stride & STRIDE_MASK; -+} -+ -+static inline unsigned int -+i915_gem_tile_height(unsigned int tiling) -+{ -+ GEM_BUG_ON(!tiling); -+ return tiling == I915_TILING_Y ? 32 : 8; -+} -+ -+static inline unsigned int -+i915_gem_object_get_tile_height(const struct drm_i915_gem_object *obj) -+{ -+ return i915_gem_tile_height(i915_gem_object_get_tiling(obj)); -+} -+ -+static inline unsigned int -+i915_gem_object_get_tile_row_size(const struct drm_i915_gem_object *obj) -+{ -+ return (i915_gem_object_get_stride(obj) * -+ i915_gem_object_get_tile_height(obj)); -+} -+ -+int i915_gem_object_set_tiling(struct drm_i915_gem_object *obj, -+ unsigned int tiling, unsigned int stride); -+ -+static inline struct intel_engine_cs * -+i915_gem_object_last_write_engine(struct drm_i915_gem_object *obj) -+{ -+ struct intel_engine_cs *engine = NULL; -+ struct dma_fence *fence; -+ -+ rcu_read_lock(); -+ fence = reservation_object_get_excl_rcu(obj->resv); -+ rcu_read_unlock(); -+ -+ if (fence && dma_fence_is_i915(fence) && !dma_fence_is_signaled(fence)) -+ engine = to_request(fence)->engine; -+ dma_fence_put(fence); -+ -+ return engine; -+} -+ -+void i915_gem_object_set_cache_coherency(struct drm_i915_gem_object *obj, -+ unsigned int cache_level); -+void i915_gem_object_flush_if_display(struct drm_i915_gem_object *obj); -+ -+void __i915_gem_object_release_shmem(struct drm_i915_gem_object *obj, -+ struct sg_table *pages, -+ bool needs_clflush); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_render_state.c b/drivers/gpu/drm/i915_legacy/i915_gem_render_state.c -new file mode 100644 -index 000000000000..9440024c763f ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_render_state.c -@@ -0,0 +1,233 @@ -+/* -+ * Copyright © 2014 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Mika Kuoppala -+ * -+ */ -+ -+#include "i915_drv.h" -+#include "i915_gem_render_state.h" -+#include "intel_renderstate.h" -+ -+struct intel_render_state { -+ const struct intel_renderstate_rodata *rodata; -+ struct drm_i915_gem_object *obj; -+ struct i915_vma *vma; -+ u32 batch_offset; -+ u32 batch_size; -+ u32 aux_offset; -+ u32 aux_size; -+}; -+ -+static const struct intel_renderstate_rodata * -+render_state_get_rodata(const struct intel_engine_cs *engine) -+{ -+ if (engine->id != RCS0) -+ return NULL; -+ -+ switch (INTEL_GEN(engine->i915)) { -+ case 6: -+ return &gen6_null_state; -+ case 7: -+ return &gen7_null_state; -+ case 8: -+ return &gen8_null_state; -+ case 9: -+ return &gen9_null_state; -+ } -+ -+ return NULL; -+} -+ -+/* -+ * Macro to add commands to auxiliary batch. -+ * This macro only checks for page overflow before inserting the commands, -+ * this is sufficient as the null state generator makes the final batch -+ * with two passes to build command and state separately. At this point -+ * the size of both are known and it compacts them by relocating the state -+ * right after the commands taking care of alignment so we should sufficient -+ * space below them for adding new commands. -+ */ -+#define OUT_BATCH(batch, i, val) \ -+ do { \ -+ if ((i) >= PAGE_SIZE / sizeof(u32)) \ -+ goto err; \ -+ (batch)[(i)++] = (val); \ -+ } while(0) -+ -+static int render_state_setup(struct intel_render_state *so, -+ struct drm_i915_private *i915) -+{ -+ const struct intel_renderstate_rodata *rodata = so->rodata; -+ unsigned int i = 0, reloc_index = 0; -+ unsigned int needs_clflush; -+ u32 *d; -+ int ret; -+ -+ ret = i915_gem_obj_prepare_shmem_write(so->obj, &needs_clflush); -+ if (ret) -+ return ret; -+ -+ d = kmap_atomic(i915_gem_object_get_dirty_page(so->obj, 0)); -+ -+ while (i < rodata->batch_items) { -+ u32 s = rodata->batch[i]; -+ -+ if (i * 4 == rodata->reloc[reloc_index]) { -+ u64 r = s + so->vma->node.start; -+ s = lower_32_bits(r); -+ if (HAS_64BIT_RELOC(i915)) { -+ if (i + 1 >= rodata->batch_items || -+ rodata->batch[i + 1] != 0) -+ goto err; -+ -+ d[i++] = s; -+ s = upper_32_bits(r); -+ } -+ -+ reloc_index++; -+ } -+ -+ d[i++] = s; -+ } -+ -+ if (rodata->reloc[reloc_index] != -1) { -+ DRM_ERROR("only %d relocs resolved\n", reloc_index); -+ goto err; -+ } -+ -+ so->batch_offset = i915_ggtt_offset(so->vma); -+ so->batch_size = rodata->batch_items * sizeof(u32); -+ -+ while (i % CACHELINE_DWORDS) -+ OUT_BATCH(d, i, MI_NOOP); -+ -+ so->aux_offset = i * sizeof(u32); -+ -+ if (HAS_POOLED_EU(i915)) { -+ /* -+ * We always program 3x6 pool config but depending upon which -+ * subslice is disabled HW drops down to appropriate config -+ * shown below. -+ * -+ * In the below table 2x6 config always refers to -+ * fused-down version, native 2x6 is not available and can -+ * be ignored -+ * -+ * SNo subslices config eu pool configuration -+ * ----------------------------------------------------------- -+ * 1 3 subslices enabled (3x6) - 0x00777000 (9+9) -+ * 2 ss0 disabled (2x6) - 0x00777000 (3+9) -+ * 3 ss1 disabled (2x6) - 0x00770000 (6+6) -+ * 4 ss2 disabled (2x6) - 0x00007000 (9+3) -+ */ -+ u32 eu_pool_config = 0x00777000; -+ -+ OUT_BATCH(d, i, GEN9_MEDIA_POOL_STATE); -+ OUT_BATCH(d, i, GEN9_MEDIA_POOL_ENABLE); -+ OUT_BATCH(d, i, eu_pool_config); -+ OUT_BATCH(d, i, 0); -+ OUT_BATCH(d, i, 0); -+ OUT_BATCH(d, i, 0); -+ } -+ -+ OUT_BATCH(d, i, MI_BATCH_BUFFER_END); -+ so->aux_size = i * sizeof(u32) - so->aux_offset; -+ so->aux_offset += so->batch_offset; -+ /* -+ * Since we are sending length, we need to strictly conform to -+ * all requirements. For Gen2 this must be a multiple of 8. -+ */ -+ so->aux_size = ALIGN(so->aux_size, 8); -+ -+ if (needs_clflush) -+ drm_clflush_virt_range(d, i * sizeof(u32)); -+ kunmap_atomic(d); -+ -+ ret = 0; -+out: -+ i915_gem_obj_finish_shmem_access(so->obj); -+ return ret; -+ -+err: -+ kunmap_atomic(d); -+ ret = -EINVAL; -+ goto out; -+} -+ -+#undef OUT_BATCH -+ -+int i915_gem_render_state_emit(struct i915_request *rq) -+{ -+ struct intel_engine_cs *engine = rq->engine; -+ struct intel_render_state so = {}; /* keep the compiler happy */ -+ int err; -+ -+ so.rodata = render_state_get_rodata(engine); -+ if (!so.rodata) -+ return 0; -+ -+ if (so.rodata->batch_items * 4 > PAGE_SIZE) -+ return -EINVAL; -+ -+ so.obj = i915_gem_object_create_internal(engine->i915, PAGE_SIZE); -+ if (IS_ERR(so.obj)) -+ return PTR_ERR(so.obj); -+ -+ so.vma = i915_vma_instance(so.obj, &engine->i915->ggtt.vm, NULL); -+ if (IS_ERR(so.vma)) { -+ err = PTR_ERR(so.vma); -+ goto err_obj; -+ } -+ -+ err = i915_vma_pin(so.vma, 0, 0, PIN_GLOBAL | PIN_HIGH); -+ if (err) -+ goto err_vma; -+ -+ err = render_state_setup(&so, rq->i915); -+ if (err) -+ goto err_unpin; -+ -+ err = engine->emit_bb_start(rq, -+ so.batch_offset, so.batch_size, -+ I915_DISPATCH_SECURE); -+ if (err) -+ goto err_unpin; -+ -+ if (so.aux_size > 8) { -+ err = engine->emit_bb_start(rq, -+ so.aux_offset, so.aux_size, -+ I915_DISPATCH_SECURE); -+ if (err) -+ goto err_unpin; -+ } -+ -+ err = i915_vma_move_to_active(so.vma, rq, 0); -+err_unpin: -+ i915_vma_unpin(so.vma); -+err_vma: -+ i915_vma_close(so.vma); -+err_obj: -+ __i915_gem_object_release_unless_active(so.obj); -+ return err; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_render_state.h b/drivers/gpu/drm/i915_legacy/i915_gem_render_state.h -new file mode 100644 -index 000000000000..112cda8fa1a8 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_render_state.h -@@ -0,0 +1,31 @@ -+/* -+ * Copyright © 2014 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#ifndef _I915_GEM_RENDER_STATE_H_ -+#define _I915_GEM_RENDER_STATE_H_ -+ -+struct i915_request; -+ -+int i915_gem_render_state_emit(struct i915_request *rq); -+ -+#endif /* _I915_GEM_RENDER_STATE_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_shrinker.c b/drivers/gpu/drm/i915_legacy/i915_gem_shrinker.c -new file mode 100644 -index 000000000000..6da795c7e62e ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_shrinker.c -@@ -0,0 +1,556 @@ -+/* -+ * Copyright © 2008-2015 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "i915_drv.h" -+#include "i915_trace.h" -+ -+static bool shrinker_lock(struct drm_i915_private *i915, -+ unsigned int flags, -+ bool *unlock) -+{ -+ struct mutex *m = &i915->drm.struct_mutex; -+ -+ switch (mutex_trylock_recursive(m)) { -+ case MUTEX_TRYLOCK_RECURSIVE: -+ *unlock = false; -+ return true; -+ -+ case MUTEX_TRYLOCK_FAILED: -+ *unlock = false; -+ if (flags & I915_SHRINK_ACTIVE && -+ mutex_lock_killable_nested(m, I915_MM_SHRINKER) == 0) -+ *unlock = true; -+ return *unlock; -+ -+ case MUTEX_TRYLOCK_SUCCESS: -+ *unlock = true; -+ return true; -+ } -+ -+ BUG(); -+} -+ -+static void shrinker_unlock(struct drm_i915_private *i915, bool unlock) -+{ -+ if (!unlock) -+ return; -+ -+ mutex_unlock(&i915->drm.struct_mutex); -+} -+ -+static bool swap_available(void) -+{ -+ return get_nr_swap_pages() > 0; -+} -+ -+static bool can_release_pages(struct drm_i915_gem_object *obj) -+{ -+ /* Consider only shrinkable ojects. */ -+ if (!i915_gem_object_is_shrinkable(obj)) -+ return false; -+ -+ /* Only report true if by unbinding the object and putting its pages -+ * we can actually make forward progress towards freeing physical -+ * pages. -+ * -+ * If the pages are pinned for any other reason than being bound -+ * to the GPU, simply unbinding from the GPU is not going to succeed -+ * in releasing our pin count on the pages themselves. -+ */ -+ if (atomic_read(&obj->mm.pages_pin_count) > obj->bind_count) -+ return false; -+ -+ /* If any vma are "permanently" pinned, it will prevent us from -+ * reclaiming the obj->mm.pages. We only allow scanout objects to claim -+ * a permanent pin, along with a few others like the context objects. -+ * To simplify the scan, and to avoid walking the list of vma under the -+ * object, we just check the count of its permanently pinned. -+ */ -+ if (READ_ONCE(obj->pin_global)) -+ return false; -+ -+ /* We can only return physical pages to the system if we can either -+ * discard the contents (because the user has marked them as being -+ * purgeable) or if we can move their contents out to swap. -+ */ -+ return swap_available() || obj->mm.madv == I915_MADV_DONTNEED; -+} -+ -+static bool unsafe_drop_pages(struct drm_i915_gem_object *obj) -+{ -+ if (i915_gem_object_unbind(obj) == 0) -+ __i915_gem_object_put_pages(obj, I915_MM_SHRINKER); -+ return !i915_gem_object_has_pages(obj); -+} -+ -+/** -+ * i915_gem_shrink - Shrink buffer object caches -+ * @i915: i915 device -+ * @target: amount of memory to make available, in pages -+ * @nr_scanned: optional output for number of pages scanned (incremental) -+ * @flags: control flags for selecting cache types -+ * -+ * This function is the main interface to the shrinker. It will try to release -+ * up to @target pages of main memory backing storage from buffer objects. -+ * Selection of the specific caches can be done with @flags. This is e.g. useful -+ * when purgeable objects should be removed from caches preferentially. -+ * -+ * Note that it's not guaranteed that released amount is actually available as -+ * free system memory - the pages might still be in-used to due to other reasons -+ * (like cpu mmaps) or the mm core has reused them before we could grab them. -+ * Therefore code that needs to explicitly shrink buffer objects caches (e.g. to -+ * avoid deadlocks in memory reclaim) must fall back to i915_gem_shrink_all(). -+ * -+ * Also note that any kind of pinning (both per-vma address space pins and -+ * backing storage pins at the buffer object level) result in the shrinker code -+ * having to skip the object. -+ * -+ * Returns: -+ * The number of pages of backing storage actually released. -+ */ -+unsigned long -+i915_gem_shrink(struct drm_i915_private *i915, -+ unsigned long target, -+ unsigned long *nr_scanned, -+ unsigned flags) -+{ -+ const struct { -+ struct list_head *list; -+ unsigned int bit; -+ } phases[] = { -+ { &i915->mm.unbound_list, I915_SHRINK_UNBOUND }, -+ { &i915->mm.bound_list, I915_SHRINK_BOUND }, -+ { NULL, 0 }, -+ }, *phase; -+ intel_wakeref_t wakeref = 0; -+ unsigned long count = 0; -+ unsigned long scanned = 0; -+ bool unlock; -+ -+ if (!shrinker_lock(i915, flags, &unlock)) -+ return 0; -+ -+ /* -+ * When shrinking the active list, also consider active contexts. -+ * Active contexts are pinned until they are retired, and so can -+ * not be simply unbound to retire and unpin their pages. To shrink -+ * the contexts, we must wait until the gpu is idle. -+ * -+ * We don't care about errors here; if we cannot wait upon the GPU, -+ * we will free as much as we can and hope to get a second chance. -+ */ -+ if (flags & I915_SHRINK_ACTIVE) -+ i915_gem_wait_for_idle(i915, -+ I915_WAIT_LOCKED, -+ MAX_SCHEDULE_TIMEOUT); -+ -+ trace_i915_gem_shrink(i915, target, flags); -+ i915_retire_requests(i915); -+ -+ /* -+ * Unbinding of objects will require HW access; Let us not wake the -+ * device just to recover a little memory. If absolutely necessary, -+ * we will force the wake during oom-notifier. -+ */ -+ if (flags & I915_SHRINK_BOUND) { -+ wakeref = intel_runtime_pm_get_if_in_use(i915); -+ if (!wakeref) -+ flags &= ~I915_SHRINK_BOUND; -+ } -+ -+ /* -+ * As we may completely rewrite the (un)bound list whilst unbinding -+ * (due to retiring requests) we have to strictly process only -+ * one element of the list at the time, and recheck the list -+ * on every iteration. -+ * -+ * In particular, we must hold a reference whilst removing the -+ * object as we may end up waiting for and/or retiring the objects. -+ * This might release the final reference (held by the active list) -+ * and result in the object being freed from under us. This is -+ * similar to the precautions the eviction code must take whilst -+ * removing objects. -+ * -+ * Also note that although these lists do not hold a reference to -+ * the object we can safely grab one here: The final object -+ * unreferencing and the bound_list are both protected by the -+ * dev->struct_mutex and so we won't ever be able to observe an -+ * object on the bound_list with a reference count equals 0. -+ */ -+ for (phase = phases; phase->list; phase++) { -+ struct list_head still_in_list; -+ struct drm_i915_gem_object *obj; -+ -+ if ((flags & phase->bit) == 0) -+ continue; -+ -+ INIT_LIST_HEAD(&still_in_list); -+ -+ /* -+ * We serialize our access to unreferenced objects through -+ * the use of the struct_mutex. While the objects are not -+ * yet freed (due to RCU then a workqueue) we still want -+ * to be able to shrink their pages, so they remain on -+ * the unbound/bound list until actually freed. -+ */ -+ spin_lock(&i915->mm.obj_lock); -+ while (count < target && -+ (obj = list_first_entry_or_null(phase->list, -+ typeof(*obj), -+ mm.link))) { -+ list_move_tail(&obj->mm.link, &still_in_list); -+ -+ if (flags & I915_SHRINK_PURGEABLE && -+ obj->mm.madv != I915_MADV_DONTNEED) -+ continue; -+ -+ if (flags & I915_SHRINK_VMAPS && -+ !is_vmalloc_addr(obj->mm.mapping)) -+ continue; -+ -+ if (!(flags & I915_SHRINK_ACTIVE) && -+ (i915_gem_object_is_active(obj) || -+ i915_gem_object_is_framebuffer(obj))) -+ continue; -+ -+ if (!can_release_pages(obj)) -+ continue; -+ -+ spin_unlock(&i915->mm.obj_lock); -+ -+ if (unsafe_drop_pages(obj)) { -+ /* May arrive from get_pages on another bo */ -+ mutex_lock_nested(&obj->mm.lock, -+ I915_MM_SHRINKER); -+ if (!i915_gem_object_has_pages(obj)) { -+ __i915_gem_object_invalidate(obj); -+ count += obj->base.size >> PAGE_SHIFT; -+ } -+ mutex_unlock(&obj->mm.lock); -+ } -+ scanned += obj->base.size >> PAGE_SHIFT; -+ -+ spin_lock(&i915->mm.obj_lock); -+ } -+ list_splice_tail(&still_in_list, phase->list); -+ spin_unlock(&i915->mm.obj_lock); -+ } -+ -+ if (flags & I915_SHRINK_BOUND) -+ intel_runtime_pm_put(i915, wakeref); -+ -+ i915_retire_requests(i915); -+ -+ shrinker_unlock(i915, unlock); -+ -+ if (nr_scanned) -+ *nr_scanned += scanned; -+ return count; -+} -+ -+/** -+ * i915_gem_shrink_all - Shrink buffer object caches completely -+ * @i915: i915 device -+ * -+ * This is a simple wraper around i915_gem_shrink() to aggressively shrink all -+ * caches completely. It also first waits for and retires all outstanding -+ * requests to also be able to release backing storage for active objects. -+ * -+ * This should only be used in code to intentionally quiescent the gpu or as a -+ * last-ditch effort when memory seems to have run out. -+ * -+ * Returns: -+ * The number of pages of backing storage actually released. -+ */ -+unsigned long i915_gem_shrink_all(struct drm_i915_private *i915) -+{ -+ intel_wakeref_t wakeref; -+ unsigned long freed = 0; -+ -+ with_intel_runtime_pm(i915, wakeref) { -+ freed = i915_gem_shrink(i915, -1UL, NULL, -+ I915_SHRINK_BOUND | -+ I915_SHRINK_UNBOUND | -+ I915_SHRINK_ACTIVE); -+ } -+ -+ return freed; -+} -+ -+static unsigned long -+i915_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc) -+{ -+ struct drm_i915_private *i915 = -+ container_of(shrinker, struct drm_i915_private, mm.shrinker); -+ struct drm_i915_gem_object *obj; -+ unsigned long num_objects = 0; -+ unsigned long count = 0; -+ -+ spin_lock(&i915->mm.obj_lock); -+ list_for_each_entry(obj, &i915->mm.unbound_list, mm.link) -+ if (can_release_pages(obj)) { -+ count += obj->base.size >> PAGE_SHIFT; -+ num_objects++; -+ } -+ -+ list_for_each_entry(obj, &i915->mm.bound_list, mm.link) -+ if (!i915_gem_object_is_active(obj) && can_release_pages(obj)) { -+ count += obj->base.size >> PAGE_SHIFT; -+ num_objects++; -+ } -+ spin_unlock(&i915->mm.obj_lock); -+ -+ /* Update our preferred vmscan batch size for the next pass. -+ * Our rough guess for an effective batch size is roughly 2 -+ * available GEM objects worth of pages. That is we don't want -+ * the shrinker to fire, until it is worth the cost of freeing an -+ * entire GEM object. -+ */ -+ if (num_objects) { -+ unsigned long avg = 2 * count / num_objects; -+ -+ i915->mm.shrinker.batch = -+ max((i915->mm.shrinker.batch + avg) >> 1, -+ 128ul /* default SHRINK_BATCH */); -+ } -+ -+ return count; -+} -+ -+static unsigned long -+i915_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc) -+{ -+ struct drm_i915_private *i915 = -+ container_of(shrinker, struct drm_i915_private, mm.shrinker); -+ unsigned long freed; -+ bool unlock; -+ -+ sc->nr_scanned = 0; -+ -+ if (!shrinker_lock(i915, 0, &unlock)) -+ return SHRINK_STOP; -+ -+ freed = i915_gem_shrink(i915, -+ sc->nr_to_scan, -+ &sc->nr_scanned, -+ I915_SHRINK_BOUND | -+ I915_SHRINK_UNBOUND | -+ I915_SHRINK_PURGEABLE); -+ if (sc->nr_scanned < sc->nr_to_scan) -+ freed += i915_gem_shrink(i915, -+ sc->nr_to_scan - sc->nr_scanned, -+ &sc->nr_scanned, -+ I915_SHRINK_BOUND | -+ I915_SHRINK_UNBOUND); -+ if (sc->nr_scanned < sc->nr_to_scan && current_is_kswapd()) { -+ intel_wakeref_t wakeref; -+ -+ with_intel_runtime_pm(i915, wakeref) { -+ freed += i915_gem_shrink(i915, -+ sc->nr_to_scan - sc->nr_scanned, -+ &sc->nr_scanned, -+ I915_SHRINK_ACTIVE | -+ I915_SHRINK_BOUND | -+ I915_SHRINK_UNBOUND); -+ } -+ } -+ -+ shrinker_unlock(i915, unlock); -+ -+ return sc->nr_scanned ? freed : SHRINK_STOP; -+} -+ -+static int -+i915_gem_shrinker_oom(struct notifier_block *nb, unsigned long event, void *ptr) -+{ -+ struct drm_i915_private *i915 = -+ container_of(nb, struct drm_i915_private, mm.oom_notifier); -+ struct drm_i915_gem_object *obj; -+ unsigned long unevictable, bound, unbound, freed_pages; -+ intel_wakeref_t wakeref; -+ -+ freed_pages = 0; -+ with_intel_runtime_pm(i915, wakeref) -+ freed_pages += i915_gem_shrink(i915, -1UL, NULL, -+ I915_SHRINK_BOUND | -+ I915_SHRINK_UNBOUND); -+ -+ /* Because we may be allocating inside our own driver, we cannot -+ * assert that there are no objects with pinned pages that are not -+ * being pointed to by hardware. -+ */ -+ unbound = bound = unevictable = 0; -+ spin_lock(&i915->mm.obj_lock); -+ list_for_each_entry(obj, &i915->mm.unbound_list, mm.link) { -+ if (!can_release_pages(obj)) -+ unevictable += obj->base.size >> PAGE_SHIFT; -+ else -+ unbound += obj->base.size >> PAGE_SHIFT; -+ } -+ list_for_each_entry(obj, &i915->mm.bound_list, mm.link) { -+ if (!can_release_pages(obj)) -+ unevictable += obj->base.size >> PAGE_SHIFT; -+ else -+ bound += obj->base.size >> PAGE_SHIFT; -+ } -+ spin_unlock(&i915->mm.obj_lock); -+ -+ if (freed_pages || unbound || bound) -+ pr_info("Purging GPU memory, %lu pages freed, " -+ "%lu pages still pinned.\n", -+ freed_pages, unevictable); -+ -+ *(unsigned long *)ptr += freed_pages; -+ return NOTIFY_DONE; -+} -+ -+static int -+i915_gem_shrinker_vmap(struct notifier_block *nb, unsigned long event, void *ptr) -+{ -+ struct drm_i915_private *i915 = -+ container_of(nb, struct drm_i915_private, mm.vmap_notifier); -+ struct i915_vma *vma, *next; -+ unsigned long freed_pages = 0; -+ intel_wakeref_t wakeref; -+ bool unlock; -+ -+ if (!shrinker_lock(i915, 0, &unlock)) -+ return NOTIFY_DONE; -+ -+ /* Force everything onto the inactive lists */ -+ if (i915_gem_wait_for_idle(i915, -+ I915_WAIT_LOCKED, -+ MAX_SCHEDULE_TIMEOUT)) -+ goto out; -+ -+ with_intel_runtime_pm(i915, wakeref) -+ freed_pages += i915_gem_shrink(i915, -1UL, NULL, -+ I915_SHRINK_BOUND | -+ I915_SHRINK_UNBOUND | -+ I915_SHRINK_VMAPS); -+ -+ /* We also want to clear any cached iomaps as they wrap vmap */ -+ mutex_lock(&i915->ggtt.vm.mutex); -+ list_for_each_entry_safe(vma, next, -+ &i915->ggtt.vm.bound_list, vm_link) { -+ unsigned long count = vma->node.size >> PAGE_SHIFT; -+ -+ if (!vma->iomap || i915_vma_is_active(vma)) -+ continue; -+ -+ mutex_unlock(&i915->ggtt.vm.mutex); -+ if (i915_vma_unbind(vma) == 0) -+ freed_pages += count; -+ mutex_lock(&i915->ggtt.vm.mutex); -+ } -+ mutex_unlock(&i915->ggtt.vm.mutex); -+ -+out: -+ shrinker_unlock(i915, unlock); -+ -+ *(unsigned long *)ptr += freed_pages; -+ return NOTIFY_DONE; -+} -+ -+/** -+ * i915_gem_shrinker_register - Register the i915 shrinker -+ * @i915: i915 device -+ * -+ * This function registers and sets up the i915 shrinker and OOM handler. -+ */ -+void i915_gem_shrinker_register(struct drm_i915_private *i915) -+{ -+ i915->mm.shrinker.scan_objects = i915_gem_shrinker_scan; -+ i915->mm.shrinker.count_objects = i915_gem_shrinker_count; -+ i915->mm.shrinker.seeks = DEFAULT_SEEKS; -+ i915->mm.shrinker.batch = 4096; -+ WARN_ON(register_shrinker(&i915->mm.shrinker)); -+ -+ i915->mm.oom_notifier.notifier_call = i915_gem_shrinker_oom; -+ WARN_ON(register_oom_notifier(&i915->mm.oom_notifier)); -+ -+ i915->mm.vmap_notifier.notifier_call = i915_gem_shrinker_vmap; -+ WARN_ON(register_vmap_purge_notifier(&i915->mm.vmap_notifier)); -+} -+ -+/** -+ * i915_gem_shrinker_unregister - Unregisters the i915 shrinker -+ * @i915: i915 device -+ * -+ * This function unregisters the i915 shrinker and OOM handler. -+ */ -+void i915_gem_shrinker_unregister(struct drm_i915_private *i915) -+{ -+ WARN_ON(unregister_vmap_purge_notifier(&i915->mm.vmap_notifier)); -+ WARN_ON(unregister_oom_notifier(&i915->mm.oom_notifier)); -+ unregister_shrinker(&i915->mm.shrinker); -+} -+ -+void i915_gem_shrinker_taints_mutex(struct drm_i915_private *i915, -+ struct mutex *mutex) -+{ -+ bool unlock = false; -+ -+ if (!IS_ENABLED(CONFIG_LOCKDEP)) -+ return; -+ -+ if (!lockdep_is_held_type(&i915->drm.struct_mutex, -1)) { -+ mutex_acquire(&i915->drm.struct_mutex.dep_map, -+ I915_MM_NORMAL, 0, _RET_IP_); -+ unlock = true; -+ } -+ -+ fs_reclaim_acquire(GFP_KERNEL); -+ -+ /* -+ * As we invariably rely on the struct_mutex within the shrinker, -+ * but have a complicated recursion dance, taint all the mutexes used -+ * within the shrinker with the struct_mutex. For completeness, we -+ * taint with all subclass of struct_mutex, even though we should -+ * only need tainting by I915_MM_NORMAL to catch possible ABBA -+ * deadlocks from using struct_mutex inside @mutex. -+ */ -+ mutex_acquire(&i915->drm.struct_mutex.dep_map, -+ I915_MM_SHRINKER, 0, _RET_IP_); -+ -+ mutex_acquire(&mutex->dep_map, 0, 0, _RET_IP_); -+ mutex_release(&mutex->dep_map, 0, _RET_IP_); -+ -+ mutex_release(&i915->drm.struct_mutex.dep_map, 0, _RET_IP_); -+ -+ fs_reclaim_release(GFP_KERNEL); -+ -+ if (unlock) -+ mutex_release(&i915->drm.struct_mutex.dep_map, 0, _RET_IP_); -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_stolen.c b/drivers/gpu/drm/i915_legacy/i915_gem_stolen.c -new file mode 100644 -index 000000000000..0a8082cfc761 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_stolen.c -@@ -0,0 +1,721 @@ -+/* -+ * Copyright © 2008-2012 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eric Anholt -+ * Chris Wilson -+ * -+ */ -+ -+#include -+#include "i915_drv.h" -+ -+/* -+ * The BIOS typically reserves some of the system's memory for the exclusive -+ * use of the integrated graphics. This memory is no longer available for -+ * use by the OS and so the user finds that his system has less memory -+ * available than he put in. We refer to this memory as stolen. -+ * -+ * The BIOS will allocate its framebuffer from the stolen memory. Our -+ * goal is try to reuse that object for our own fbcon which must always -+ * be available for panics. Anything else we can reuse the stolen memory -+ * for is a boon. -+ */ -+ -+int i915_gem_stolen_insert_node_in_range(struct drm_i915_private *dev_priv, -+ struct drm_mm_node *node, u64 size, -+ unsigned alignment, u64 start, u64 end) -+{ -+ int ret; -+ -+ if (!drm_mm_initialized(&dev_priv->mm.stolen)) -+ return -ENODEV; -+ -+ /* WaSkipStolenMemoryFirstPage:bdw+ */ -+ if (INTEL_GEN(dev_priv) >= 8 && start < 4096) -+ start = 4096; -+ -+ mutex_lock(&dev_priv->mm.stolen_lock); -+ ret = drm_mm_insert_node_in_range(&dev_priv->mm.stolen, node, -+ size, alignment, 0, -+ start, end, DRM_MM_INSERT_BEST); -+ mutex_unlock(&dev_priv->mm.stolen_lock); -+ -+ return ret; -+} -+ -+int i915_gem_stolen_insert_node(struct drm_i915_private *dev_priv, -+ struct drm_mm_node *node, u64 size, -+ unsigned alignment) -+{ -+ return i915_gem_stolen_insert_node_in_range(dev_priv, node, size, -+ alignment, 0, U64_MAX); -+} -+ -+void i915_gem_stolen_remove_node(struct drm_i915_private *dev_priv, -+ struct drm_mm_node *node) -+{ -+ mutex_lock(&dev_priv->mm.stolen_lock); -+ drm_mm_remove_node(node); -+ mutex_unlock(&dev_priv->mm.stolen_lock); -+} -+ -+static int i915_adjust_stolen(struct drm_i915_private *dev_priv, -+ struct resource *dsm) -+{ -+ struct i915_ggtt *ggtt = &dev_priv->ggtt; -+ struct resource *r; -+ -+ if (dsm->start == 0 || dsm->end <= dsm->start) -+ return -EINVAL; -+ -+ /* -+ * TODO: We have yet too encounter the case where the GTT wasn't at the -+ * end of stolen. With that assumption we could simplify this. -+ */ -+ -+ /* Make sure we don't clobber the GTT if it's within stolen memory */ -+ if (INTEL_GEN(dev_priv) <= 4 && -+ !IS_G33(dev_priv) && !IS_PINEVIEW(dev_priv) && !IS_G4X(dev_priv)) { -+ struct resource stolen[2] = {*dsm, *dsm}; -+ struct resource ggtt_res; -+ resource_size_t ggtt_start; -+ -+ ggtt_start = I915_READ(PGTBL_CTL); -+ if (IS_GEN(dev_priv, 4)) -+ ggtt_start = (ggtt_start & PGTBL_ADDRESS_LO_MASK) | -+ (ggtt_start & PGTBL_ADDRESS_HI_MASK) << 28; -+ else -+ ggtt_start &= PGTBL_ADDRESS_LO_MASK; -+ -+ ggtt_res = -+ (struct resource) DEFINE_RES_MEM(ggtt_start, -+ ggtt_total_entries(ggtt) * 4); -+ -+ if (ggtt_res.start >= stolen[0].start && ggtt_res.start < stolen[0].end) -+ stolen[0].end = ggtt_res.start; -+ if (ggtt_res.end > stolen[1].start && ggtt_res.end <= stolen[1].end) -+ stolen[1].start = ggtt_res.end; -+ -+ /* Pick the larger of the two chunks */ -+ if (resource_size(&stolen[0]) > resource_size(&stolen[1])) -+ *dsm = stolen[0]; -+ else -+ *dsm = stolen[1]; -+ -+ if (stolen[0].start != stolen[1].start || -+ stolen[0].end != stolen[1].end) { -+ DRM_DEBUG_DRIVER("GTT within stolen memory at %pR\n", &ggtt_res); -+ DRM_DEBUG_DRIVER("Stolen memory adjusted to %pR\n", dsm); -+ } -+ } -+ -+ /* -+ * Verify that nothing else uses this physical address. Stolen -+ * memory should be reserved by the BIOS and hidden from the -+ * kernel. So if the region is already marked as busy, something -+ * is seriously wrong. -+ */ -+ r = devm_request_mem_region(dev_priv->drm.dev, dsm->start, -+ resource_size(dsm), -+ "Graphics Stolen Memory"); -+ if (r == NULL) { -+ /* -+ * One more attempt but this time requesting region from -+ * start + 1, as we have seen that this resolves the region -+ * conflict with the PCI Bus. -+ * This is a BIOS w/a: Some BIOS wrap stolen in the root -+ * PCI bus, but have an off-by-one error. Hence retry the -+ * reservation starting from 1 instead of 0. -+ * There's also BIOS with off-by-one on the other end. -+ */ -+ r = devm_request_mem_region(dev_priv->drm.dev, dsm->start + 1, -+ resource_size(dsm) - 2, -+ "Graphics Stolen Memory"); -+ /* -+ * GEN3 firmware likes to smash pci bridges into the stolen -+ * range. Apparently this works. -+ */ -+ if (r == NULL && !IS_GEN(dev_priv, 3)) { -+ DRM_ERROR("conflict detected with stolen region: %pR\n", -+ dsm); -+ -+ return -EBUSY; -+ } -+ } -+ -+ return 0; -+} -+ -+void i915_gem_cleanup_stolen(struct drm_i915_private *dev_priv) -+{ -+ if (!drm_mm_initialized(&dev_priv->mm.stolen)) -+ return; -+ -+ drm_mm_takedown(&dev_priv->mm.stolen); -+} -+ -+static void g4x_get_stolen_reserved(struct drm_i915_private *dev_priv, -+ resource_size_t *base, -+ resource_size_t *size) -+{ -+ u32 reg_val = I915_READ(IS_GM45(dev_priv) ? -+ CTG_STOLEN_RESERVED : -+ ELK_STOLEN_RESERVED); -+ resource_size_t stolen_top = dev_priv->dsm.end + 1; -+ -+ DRM_DEBUG_DRIVER("%s_STOLEN_RESERVED = %08x\n", -+ IS_GM45(dev_priv) ? "CTG" : "ELK", reg_val); -+ -+ if ((reg_val & G4X_STOLEN_RESERVED_ENABLE) == 0) -+ return; -+ -+ /* -+ * Whether ILK really reuses the ELK register for this is unclear. -+ * Let's see if we catch anyone with this supposedly enabled on ILK. -+ */ -+ WARN(IS_GEN(dev_priv, 5), "ILK stolen reserved found? 0x%08x\n", -+ reg_val); -+ -+ if (!(reg_val & G4X_STOLEN_RESERVED_ADDR2_MASK)) -+ return; -+ -+ *base = (reg_val & G4X_STOLEN_RESERVED_ADDR2_MASK) << 16; -+ WARN_ON((reg_val & G4X_STOLEN_RESERVED_ADDR1_MASK) < *base); -+ -+ *size = stolen_top - *base; -+} -+ -+static void gen6_get_stolen_reserved(struct drm_i915_private *dev_priv, -+ resource_size_t *base, -+ resource_size_t *size) -+{ -+ u32 reg_val = I915_READ(GEN6_STOLEN_RESERVED); -+ -+ DRM_DEBUG_DRIVER("GEN6_STOLEN_RESERVED = %08x\n", reg_val); -+ -+ if (!(reg_val & GEN6_STOLEN_RESERVED_ENABLE)) -+ return; -+ -+ *base = reg_val & GEN6_STOLEN_RESERVED_ADDR_MASK; -+ -+ switch (reg_val & GEN6_STOLEN_RESERVED_SIZE_MASK) { -+ case GEN6_STOLEN_RESERVED_1M: -+ *size = 1024 * 1024; -+ break; -+ case GEN6_STOLEN_RESERVED_512K: -+ *size = 512 * 1024; -+ break; -+ case GEN6_STOLEN_RESERVED_256K: -+ *size = 256 * 1024; -+ break; -+ case GEN6_STOLEN_RESERVED_128K: -+ *size = 128 * 1024; -+ break; -+ default: -+ *size = 1024 * 1024; -+ MISSING_CASE(reg_val & GEN6_STOLEN_RESERVED_SIZE_MASK); -+ } -+} -+ -+static void vlv_get_stolen_reserved(struct drm_i915_private *dev_priv, -+ resource_size_t *base, -+ resource_size_t *size) -+{ -+ u32 reg_val = I915_READ(GEN6_STOLEN_RESERVED); -+ resource_size_t stolen_top = dev_priv->dsm.end + 1; -+ -+ DRM_DEBUG_DRIVER("GEN6_STOLEN_RESERVED = %08x\n", reg_val); -+ -+ if (!(reg_val & GEN6_STOLEN_RESERVED_ENABLE)) -+ return; -+ -+ switch (reg_val & GEN7_STOLEN_RESERVED_SIZE_MASK) { -+ default: -+ MISSING_CASE(reg_val & GEN7_STOLEN_RESERVED_SIZE_MASK); -+ /* fall through */ -+ case GEN7_STOLEN_RESERVED_1M: -+ *size = 1024 * 1024; -+ break; -+ } -+ -+ /* -+ * On vlv, the ADDR_MASK portion is left as 0 and HW deduces the -+ * reserved location as (top - size). -+ */ -+ *base = stolen_top - *size; -+} -+ -+static void gen7_get_stolen_reserved(struct drm_i915_private *dev_priv, -+ resource_size_t *base, -+ resource_size_t *size) -+{ -+ u32 reg_val = I915_READ(GEN6_STOLEN_RESERVED); -+ -+ DRM_DEBUG_DRIVER("GEN6_STOLEN_RESERVED = %08x\n", reg_val); -+ -+ if (!(reg_val & GEN6_STOLEN_RESERVED_ENABLE)) -+ return; -+ -+ *base = reg_val & GEN7_STOLEN_RESERVED_ADDR_MASK; -+ -+ switch (reg_val & GEN7_STOLEN_RESERVED_SIZE_MASK) { -+ case GEN7_STOLEN_RESERVED_1M: -+ *size = 1024 * 1024; -+ break; -+ case GEN7_STOLEN_RESERVED_256K: -+ *size = 256 * 1024; -+ break; -+ default: -+ *size = 1024 * 1024; -+ MISSING_CASE(reg_val & GEN7_STOLEN_RESERVED_SIZE_MASK); -+ } -+} -+ -+static void chv_get_stolen_reserved(struct drm_i915_private *dev_priv, -+ resource_size_t *base, -+ resource_size_t *size) -+{ -+ u32 reg_val = I915_READ(GEN6_STOLEN_RESERVED); -+ -+ DRM_DEBUG_DRIVER("GEN6_STOLEN_RESERVED = %08x\n", reg_val); -+ -+ if (!(reg_val & GEN6_STOLEN_RESERVED_ENABLE)) -+ return; -+ -+ *base = reg_val & GEN6_STOLEN_RESERVED_ADDR_MASK; -+ -+ switch (reg_val & GEN8_STOLEN_RESERVED_SIZE_MASK) { -+ case GEN8_STOLEN_RESERVED_1M: -+ *size = 1024 * 1024; -+ break; -+ case GEN8_STOLEN_RESERVED_2M: -+ *size = 2 * 1024 * 1024; -+ break; -+ case GEN8_STOLEN_RESERVED_4M: -+ *size = 4 * 1024 * 1024; -+ break; -+ case GEN8_STOLEN_RESERVED_8M: -+ *size = 8 * 1024 * 1024; -+ break; -+ default: -+ *size = 8 * 1024 * 1024; -+ MISSING_CASE(reg_val & GEN8_STOLEN_RESERVED_SIZE_MASK); -+ } -+} -+ -+static void bdw_get_stolen_reserved(struct drm_i915_private *dev_priv, -+ resource_size_t *base, -+ resource_size_t *size) -+{ -+ u32 reg_val = I915_READ(GEN6_STOLEN_RESERVED); -+ resource_size_t stolen_top = dev_priv->dsm.end + 1; -+ -+ DRM_DEBUG_DRIVER("GEN6_STOLEN_RESERVED = %08x\n", reg_val); -+ -+ if (!(reg_val & GEN6_STOLEN_RESERVED_ENABLE)) -+ return; -+ -+ if (!(reg_val & GEN6_STOLEN_RESERVED_ADDR_MASK)) -+ return; -+ -+ *base = reg_val & GEN6_STOLEN_RESERVED_ADDR_MASK; -+ *size = stolen_top - *base; -+} -+ -+static void icl_get_stolen_reserved(struct drm_i915_private *dev_priv, -+ resource_size_t *base, -+ resource_size_t *size) -+{ -+ u64 reg_val = I915_READ64(GEN6_STOLEN_RESERVED); -+ -+ DRM_DEBUG_DRIVER("GEN6_STOLEN_RESERVED = 0x%016llx\n", reg_val); -+ -+ *base = reg_val & GEN11_STOLEN_RESERVED_ADDR_MASK; -+ -+ switch (reg_val & GEN8_STOLEN_RESERVED_SIZE_MASK) { -+ case GEN8_STOLEN_RESERVED_1M: -+ *size = 1024 * 1024; -+ break; -+ case GEN8_STOLEN_RESERVED_2M: -+ *size = 2 * 1024 * 1024; -+ break; -+ case GEN8_STOLEN_RESERVED_4M: -+ *size = 4 * 1024 * 1024; -+ break; -+ case GEN8_STOLEN_RESERVED_8M: -+ *size = 8 * 1024 * 1024; -+ break; -+ default: -+ *size = 8 * 1024 * 1024; -+ MISSING_CASE(reg_val & GEN8_STOLEN_RESERVED_SIZE_MASK); -+ } -+} -+ -+int i915_gem_init_stolen(struct drm_i915_private *dev_priv) -+{ -+ resource_size_t reserved_base, stolen_top; -+ resource_size_t reserved_total, reserved_size; -+ -+ mutex_init(&dev_priv->mm.stolen_lock); -+ -+ if (intel_vgpu_active(dev_priv)) { -+ DRM_INFO("iGVT-g active, disabling use of stolen memory\n"); -+ return 0; -+ } -+ -+ if (intel_vtd_active() && INTEL_GEN(dev_priv) < 8) { -+ DRM_INFO("DMAR active, disabling use of stolen memory\n"); -+ return 0; -+ } -+ -+ if (resource_size(&intel_graphics_stolen_res) == 0) -+ return 0; -+ -+ dev_priv->dsm = intel_graphics_stolen_res; -+ -+ if (i915_adjust_stolen(dev_priv, &dev_priv->dsm)) -+ return 0; -+ -+ GEM_BUG_ON(dev_priv->dsm.start == 0); -+ GEM_BUG_ON(dev_priv->dsm.end <= dev_priv->dsm.start); -+ -+ stolen_top = dev_priv->dsm.end + 1; -+ reserved_base = stolen_top; -+ reserved_size = 0; -+ -+ switch (INTEL_GEN(dev_priv)) { -+ case 2: -+ case 3: -+ break; -+ case 4: -+ if (!IS_G4X(dev_priv)) -+ break; -+ /* fall through */ -+ case 5: -+ g4x_get_stolen_reserved(dev_priv, -+ &reserved_base, &reserved_size); -+ break; -+ case 6: -+ gen6_get_stolen_reserved(dev_priv, -+ &reserved_base, &reserved_size); -+ break; -+ case 7: -+ if (IS_VALLEYVIEW(dev_priv)) -+ vlv_get_stolen_reserved(dev_priv, -+ &reserved_base, &reserved_size); -+ else -+ gen7_get_stolen_reserved(dev_priv, -+ &reserved_base, &reserved_size); -+ break; -+ case 8: -+ case 9: -+ case 10: -+ if (IS_LP(dev_priv)) -+ chv_get_stolen_reserved(dev_priv, -+ &reserved_base, &reserved_size); -+ else -+ bdw_get_stolen_reserved(dev_priv, -+ &reserved_base, &reserved_size); -+ break; -+ case 11: -+ default: -+ icl_get_stolen_reserved(dev_priv, &reserved_base, -+ &reserved_size); -+ break; -+ } -+ -+ /* -+ * Our expectation is that the reserved space is at the top of the -+ * stolen region and *never* at the bottom. If we see !reserved_base, -+ * it likely means we failed to read the registers correctly. -+ */ -+ if (!reserved_base) { -+ DRM_ERROR("inconsistent reservation %pa + %pa; ignoring\n", -+ &reserved_base, &reserved_size); -+ reserved_base = stolen_top; -+ reserved_size = 0; -+ } -+ -+ dev_priv->dsm_reserved = -+ (struct resource) DEFINE_RES_MEM(reserved_base, reserved_size); -+ -+ if (!resource_contains(&dev_priv->dsm, &dev_priv->dsm_reserved)) { -+ DRM_ERROR("Stolen reserved area %pR outside stolen memory %pR\n", -+ &dev_priv->dsm_reserved, &dev_priv->dsm); -+ return 0; -+ } -+ -+ /* It is possible for the reserved area to end before the end of stolen -+ * memory, so just consider the start. */ -+ reserved_total = stolen_top - reserved_base; -+ -+ DRM_DEBUG_DRIVER("Memory reserved for graphics device: %lluK, usable: %lluK\n", -+ (u64)resource_size(&dev_priv->dsm) >> 10, -+ ((u64)resource_size(&dev_priv->dsm) - reserved_total) >> 10); -+ -+ dev_priv->stolen_usable_size = -+ resource_size(&dev_priv->dsm) - reserved_total; -+ -+ /* Basic memrange allocator for stolen space. */ -+ drm_mm_init(&dev_priv->mm.stolen, 0, dev_priv->stolen_usable_size); -+ -+ return 0; -+} -+ -+static struct sg_table * -+i915_pages_create_for_stolen(struct drm_device *dev, -+ resource_size_t offset, resource_size_t size) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct sg_table *st; -+ struct scatterlist *sg; -+ -+ GEM_BUG_ON(range_overflows(offset, size, resource_size(&dev_priv->dsm))); -+ -+ /* We hide that we have no struct page backing our stolen object -+ * by wrapping the contiguous physical allocation with a fake -+ * dma mapping in a single scatterlist. -+ */ -+ -+ st = kmalloc(sizeof(*st), GFP_KERNEL); -+ if (st == NULL) -+ return ERR_PTR(-ENOMEM); -+ -+ if (sg_alloc_table(st, 1, GFP_KERNEL)) { -+ kfree(st); -+ return ERR_PTR(-ENOMEM); -+ } -+ -+ sg = st->sgl; -+ sg->offset = 0; -+ sg->length = size; -+ -+ sg_dma_address(sg) = (dma_addr_t)dev_priv->dsm.start + offset; -+ sg_dma_len(sg) = size; -+ -+ return st; -+} -+ -+static int i915_gem_object_get_pages_stolen(struct drm_i915_gem_object *obj) -+{ -+ struct sg_table *pages = -+ i915_pages_create_for_stolen(obj->base.dev, -+ obj->stolen->start, -+ obj->stolen->size); -+ if (IS_ERR(pages)) -+ return PTR_ERR(pages); -+ -+ __i915_gem_object_set_pages(obj, pages, obj->stolen->size); -+ -+ return 0; -+} -+ -+static void i915_gem_object_put_pages_stolen(struct drm_i915_gem_object *obj, -+ struct sg_table *pages) -+{ -+ /* Should only be called from i915_gem_object_release_stolen() */ -+ sg_free_table(pages); -+ kfree(pages); -+} -+ -+static void -+i915_gem_object_release_stolen(struct drm_i915_gem_object *obj) -+{ -+ struct drm_i915_private *dev_priv = to_i915(obj->base.dev); -+ struct drm_mm_node *stolen = fetch_and_zero(&obj->stolen); -+ -+ GEM_BUG_ON(!stolen); -+ -+ __i915_gem_object_unpin_pages(obj); -+ -+ i915_gem_stolen_remove_node(dev_priv, stolen); -+ kfree(stolen); -+} -+ -+static const struct drm_i915_gem_object_ops i915_gem_object_stolen_ops = { -+ .get_pages = i915_gem_object_get_pages_stolen, -+ .put_pages = i915_gem_object_put_pages_stolen, -+ .release = i915_gem_object_release_stolen, -+}; -+ -+static struct drm_i915_gem_object * -+_i915_gem_object_create_stolen(struct drm_i915_private *dev_priv, -+ struct drm_mm_node *stolen) -+{ -+ struct drm_i915_gem_object *obj; -+ unsigned int cache_level; -+ -+ obj = i915_gem_object_alloc(); -+ if (obj == NULL) -+ return NULL; -+ -+ drm_gem_private_object_init(&dev_priv->drm, &obj->base, stolen->size); -+ i915_gem_object_init(obj, &i915_gem_object_stolen_ops); -+ -+ obj->stolen = stolen; -+ obj->read_domains = I915_GEM_DOMAIN_CPU | I915_GEM_DOMAIN_GTT; -+ cache_level = HAS_LLC(dev_priv) ? I915_CACHE_LLC : I915_CACHE_NONE; -+ i915_gem_object_set_cache_coherency(obj, cache_level); -+ -+ if (i915_gem_object_pin_pages(obj)) -+ goto cleanup; -+ -+ return obj; -+ -+cleanup: -+ i915_gem_object_free(obj); -+ return NULL; -+} -+ -+struct drm_i915_gem_object * -+i915_gem_object_create_stolen(struct drm_i915_private *dev_priv, -+ resource_size_t size) -+{ -+ struct drm_i915_gem_object *obj; -+ struct drm_mm_node *stolen; -+ int ret; -+ -+ if (!drm_mm_initialized(&dev_priv->mm.stolen)) -+ return NULL; -+ -+ if (size == 0) -+ return NULL; -+ -+ stolen = kzalloc(sizeof(*stolen), GFP_KERNEL); -+ if (!stolen) -+ return NULL; -+ -+ ret = i915_gem_stolen_insert_node(dev_priv, stolen, size, 4096); -+ if (ret) { -+ kfree(stolen); -+ return NULL; -+ } -+ -+ obj = _i915_gem_object_create_stolen(dev_priv, stolen); -+ if (obj) -+ return obj; -+ -+ i915_gem_stolen_remove_node(dev_priv, stolen); -+ kfree(stolen); -+ return NULL; -+} -+ -+struct drm_i915_gem_object * -+i915_gem_object_create_stolen_for_preallocated(struct drm_i915_private *dev_priv, -+ resource_size_t stolen_offset, -+ resource_size_t gtt_offset, -+ resource_size_t size) -+{ -+ struct i915_ggtt *ggtt = &dev_priv->ggtt; -+ struct drm_i915_gem_object *obj; -+ struct drm_mm_node *stolen; -+ struct i915_vma *vma; -+ int ret; -+ -+ if (!drm_mm_initialized(&dev_priv->mm.stolen)) -+ return NULL; -+ -+ lockdep_assert_held(&dev_priv->drm.struct_mutex); -+ -+ DRM_DEBUG_DRIVER("creating preallocated stolen object: stolen_offset=%pa, gtt_offset=%pa, size=%pa\n", -+ &stolen_offset, >t_offset, &size); -+ -+ /* KISS and expect everything to be page-aligned */ -+ if (WARN_ON(size == 0) || -+ WARN_ON(!IS_ALIGNED(size, I915_GTT_PAGE_SIZE)) || -+ WARN_ON(!IS_ALIGNED(stolen_offset, I915_GTT_MIN_ALIGNMENT))) -+ return NULL; -+ -+ stolen = kzalloc(sizeof(*stolen), GFP_KERNEL); -+ if (!stolen) -+ return NULL; -+ -+ stolen->start = stolen_offset; -+ stolen->size = size; -+ mutex_lock(&dev_priv->mm.stolen_lock); -+ ret = drm_mm_reserve_node(&dev_priv->mm.stolen, stolen); -+ mutex_unlock(&dev_priv->mm.stolen_lock); -+ if (ret) { -+ DRM_DEBUG_DRIVER("failed to allocate stolen space\n"); -+ kfree(stolen); -+ return NULL; -+ } -+ -+ obj = _i915_gem_object_create_stolen(dev_priv, stolen); -+ if (obj == NULL) { -+ DRM_DEBUG_DRIVER("failed to allocate stolen object\n"); -+ i915_gem_stolen_remove_node(dev_priv, stolen); -+ kfree(stolen); -+ return NULL; -+ } -+ -+ /* Some objects just need physical mem from stolen space */ -+ if (gtt_offset == I915_GTT_OFFSET_NONE) -+ return obj; -+ -+ ret = i915_gem_object_pin_pages(obj); -+ if (ret) -+ goto err; -+ -+ vma = i915_vma_instance(obj, &ggtt->vm, NULL); -+ if (IS_ERR(vma)) { -+ ret = PTR_ERR(vma); -+ goto err_pages; -+ } -+ -+ /* To simplify the initialisation sequence between KMS and GTT, -+ * we allow construction of the stolen object prior to -+ * setting up the GTT space. The actual reservation will occur -+ * later. -+ */ -+ ret = i915_gem_gtt_reserve(&ggtt->vm, &vma->node, -+ size, gtt_offset, obj->cache_level, -+ 0); -+ if (ret) { -+ DRM_DEBUG_DRIVER("failed to allocate stolen GTT space\n"); -+ goto err_pages; -+ } -+ -+ GEM_BUG_ON(!drm_mm_node_allocated(&vma->node)); -+ -+ vma->pages = obj->mm.pages; -+ vma->flags |= I915_VMA_GLOBAL_BIND; -+ __i915_vma_set_map_and_fenceable(vma); -+ -+ mutex_lock(&ggtt->vm.mutex); -+ list_move_tail(&vma->vm_link, &ggtt->vm.bound_list); -+ mutex_unlock(&ggtt->vm.mutex); -+ -+ spin_lock(&dev_priv->mm.obj_lock); -+ list_move_tail(&obj->mm.link, &dev_priv->mm.bound_list); -+ obj->bind_count++; -+ spin_unlock(&dev_priv->mm.obj_lock); -+ -+ return obj; -+ -+err_pages: -+ i915_gem_object_unpin_pages(obj); -+err: -+ i915_gem_object_put(obj); -+ return NULL; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_tiling.c b/drivers/gpu/drm/i915_legacy/i915_gem_tiling.c -new file mode 100644 -index 000000000000..a9b5329dae3b ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_tiling.c -@@ -0,0 +1,457 @@ -+/* -+ * Copyright © 2008 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eric Anholt -+ * -+ */ -+ -+#include -+#include -+#include -+#include "i915_drv.h" -+ -+/** -+ * DOC: buffer object tiling -+ * -+ * i915_gem_set_tiling_ioctl() and i915_gem_get_tiling_ioctl() is the userspace -+ * interface to declare fence register requirements. -+ * -+ * In principle GEM doesn't care at all about the internal data layout of an -+ * object, and hence it also doesn't care about tiling or swizzling. There's two -+ * exceptions: -+ * -+ * - For X and Y tiling the hardware provides detilers for CPU access, so called -+ * fences. Since there's only a limited amount of them the kernel must manage -+ * these, and therefore userspace must tell the kernel the object tiling if it -+ * wants to use fences for detiling. -+ * - On gen3 and gen4 platforms have a swizzling pattern for tiled objects which -+ * depends upon the physical page frame number. When swapping such objects the -+ * page frame number might change and the kernel must be able to fix this up -+ * and hence now the tiling. Note that on a subset of platforms with -+ * asymmetric memory channel population the swizzling pattern changes in an -+ * unknown way, and for those the kernel simply forbids swapping completely. -+ * -+ * Since neither of this applies for new tiling layouts on modern platforms like -+ * W, Ys and Yf tiling GEM only allows object tiling to be set to X or Y tiled. -+ * Anything else can be handled in userspace entirely without the kernel's -+ * invovlement. -+ */ -+ -+/** -+ * i915_gem_fence_size - required global GTT size for a fence -+ * @i915: i915 device -+ * @size: object size -+ * @tiling: tiling mode -+ * @stride: tiling stride -+ * -+ * Return the required global GTT size for a fence (view of a tiled object), -+ * taking into account potential fence register mapping. -+ */ -+u32 i915_gem_fence_size(struct drm_i915_private *i915, -+ u32 size, unsigned int tiling, unsigned int stride) -+{ -+ u32 ggtt_size; -+ -+ GEM_BUG_ON(!size); -+ -+ if (tiling == I915_TILING_NONE) -+ return size; -+ -+ GEM_BUG_ON(!stride); -+ -+ if (INTEL_GEN(i915) >= 4) { -+ stride *= i915_gem_tile_height(tiling); -+ GEM_BUG_ON(!IS_ALIGNED(stride, I965_FENCE_PAGE)); -+ return roundup(size, stride); -+ } -+ -+ /* Previous chips need a power-of-two fence region when tiling */ -+ if (IS_GEN(i915, 3)) -+ ggtt_size = 1024*1024; -+ else -+ ggtt_size = 512*1024; -+ -+ while (ggtt_size < size) -+ ggtt_size <<= 1; -+ -+ return ggtt_size; -+} -+ -+/** -+ * i915_gem_fence_alignment - required global GTT alignment for a fence -+ * @i915: i915 device -+ * @size: object size -+ * @tiling: tiling mode -+ * @stride: tiling stride -+ * -+ * Return the required global GTT alignment for a fence (a view of a tiled -+ * object), taking into account potential fence register mapping. -+ */ -+u32 i915_gem_fence_alignment(struct drm_i915_private *i915, u32 size, -+ unsigned int tiling, unsigned int stride) -+{ -+ GEM_BUG_ON(!size); -+ -+ /* -+ * Minimum alignment is 4k (GTT page size), but might be greater -+ * if a fence register is needed for the object. -+ */ -+ if (tiling == I915_TILING_NONE) -+ return I915_GTT_MIN_ALIGNMENT; -+ -+ if (INTEL_GEN(i915) >= 4) -+ return I965_FENCE_PAGE; -+ -+ /* -+ * Previous chips need to be aligned to the size of the smallest -+ * fence register that can contain the object. -+ */ -+ return i915_gem_fence_size(i915, size, tiling, stride); -+} -+ -+/* Check pitch constriants for all chips & tiling formats */ -+static bool -+i915_tiling_ok(struct drm_i915_gem_object *obj, -+ unsigned int tiling, unsigned int stride) -+{ -+ struct drm_i915_private *i915 = to_i915(obj->base.dev); -+ unsigned int tile_width; -+ -+ /* Linear is always fine */ -+ if (tiling == I915_TILING_NONE) -+ return true; -+ -+ if (tiling > I915_TILING_LAST) -+ return false; -+ -+ /* check maximum stride & object size */ -+ /* i965+ stores the end address of the gtt mapping in the fence -+ * reg, so dont bother to check the size */ -+ if (INTEL_GEN(i915) >= 7) { -+ if (stride / 128 > GEN7_FENCE_MAX_PITCH_VAL) -+ return false; -+ } else if (INTEL_GEN(i915) >= 4) { -+ if (stride / 128 > I965_FENCE_MAX_PITCH_VAL) -+ return false; -+ } else { -+ if (stride > 8192) -+ return false; -+ -+ if (!is_power_of_2(stride)) -+ return false; -+ } -+ -+ if (IS_GEN(i915, 2) || -+ (tiling == I915_TILING_Y && HAS_128_BYTE_Y_TILING(i915))) -+ tile_width = 128; -+ else -+ tile_width = 512; -+ -+ if (!stride || !IS_ALIGNED(stride, tile_width)) -+ return false; -+ -+ return true; -+} -+ -+static bool i915_vma_fence_prepare(struct i915_vma *vma, -+ int tiling_mode, unsigned int stride) -+{ -+ struct drm_i915_private *i915 = vma->vm->i915; -+ u32 size, alignment; -+ -+ if (!i915_vma_is_map_and_fenceable(vma)) -+ return true; -+ -+ size = i915_gem_fence_size(i915, vma->size, tiling_mode, stride); -+ if (vma->node.size < size) -+ return false; -+ -+ alignment = i915_gem_fence_alignment(i915, vma->size, tiling_mode, stride); -+ if (!IS_ALIGNED(vma->node.start, alignment)) -+ return false; -+ -+ return true; -+} -+ -+/* Make the current GTT allocation valid for the change in tiling. */ -+static int -+i915_gem_object_fence_prepare(struct drm_i915_gem_object *obj, -+ int tiling_mode, unsigned int stride) -+{ -+ struct i915_vma *vma; -+ int ret; -+ -+ if (tiling_mode == I915_TILING_NONE) -+ return 0; -+ -+ for_each_ggtt_vma(vma, obj) { -+ if (i915_vma_fence_prepare(vma, tiling_mode, stride)) -+ continue; -+ -+ ret = i915_vma_unbind(vma); -+ if (ret) -+ return ret; -+ } -+ -+ return 0; -+} -+ -+int -+i915_gem_object_set_tiling(struct drm_i915_gem_object *obj, -+ unsigned int tiling, unsigned int stride) -+{ -+ struct drm_i915_private *i915 = to_i915(obj->base.dev); -+ struct i915_vma *vma; -+ int err; -+ -+ /* Make sure we don't cross-contaminate obj->tiling_and_stride */ -+ BUILD_BUG_ON(I915_TILING_LAST & STRIDE_MASK); -+ -+ GEM_BUG_ON(!i915_tiling_ok(obj, tiling, stride)); -+ GEM_BUG_ON(!stride ^ (tiling == I915_TILING_NONE)); -+ lockdep_assert_held(&i915->drm.struct_mutex); -+ -+ if ((tiling | stride) == obj->tiling_and_stride) -+ return 0; -+ -+ if (i915_gem_object_is_framebuffer(obj)) -+ return -EBUSY; -+ -+ /* We need to rebind the object if its current allocation -+ * no longer meets the alignment restrictions for its new -+ * tiling mode. Otherwise we can just leave it alone, but -+ * need to ensure that any fence register is updated before -+ * the next fenced (either through the GTT or by the BLT unit -+ * on older GPUs) access. -+ * -+ * After updating the tiling parameters, we then flag whether -+ * we need to update an associated fence register. Note this -+ * has to also include the unfenced register the GPU uses -+ * whilst executing a fenced command for an untiled object. -+ */ -+ -+ err = i915_gem_object_fence_prepare(obj, tiling, stride); -+ if (err) -+ return err; -+ -+ i915_gem_object_lock(obj); -+ if (i915_gem_object_is_framebuffer(obj)) { -+ i915_gem_object_unlock(obj); -+ return -EBUSY; -+ } -+ -+ /* If the memory has unknown (i.e. varying) swizzling, we pin the -+ * pages to prevent them being swapped out and causing corruption -+ * due to the change in swizzling. -+ */ -+ mutex_lock(&obj->mm.lock); -+ if (i915_gem_object_has_pages(obj) && -+ obj->mm.madv == I915_MADV_WILLNEED && -+ i915->quirks & QUIRK_PIN_SWIZZLED_PAGES) { -+ if (tiling == I915_TILING_NONE) { -+ GEM_BUG_ON(!obj->mm.quirked); -+ __i915_gem_object_unpin_pages(obj); -+ obj->mm.quirked = false; -+ } -+ if (!i915_gem_object_is_tiled(obj)) { -+ GEM_BUG_ON(obj->mm.quirked); -+ __i915_gem_object_pin_pages(obj); -+ obj->mm.quirked = true; -+ } -+ } -+ mutex_unlock(&obj->mm.lock); -+ -+ for_each_ggtt_vma(vma, obj) { -+ vma->fence_size = -+ i915_gem_fence_size(i915, vma->size, tiling, stride); -+ vma->fence_alignment = -+ i915_gem_fence_alignment(i915, -+ vma->size, tiling, stride); -+ -+ if (vma->fence) -+ vma->fence->dirty = true; -+ } -+ -+ obj->tiling_and_stride = tiling | stride; -+ i915_gem_object_unlock(obj); -+ -+ /* Force the fence to be reacquired for GTT access */ -+ i915_gem_release_mmap(obj); -+ -+ /* Try to preallocate memory required to save swizzling on put-pages */ -+ if (i915_gem_object_needs_bit17_swizzle(obj)) { -+ if (!obj->bit_17) { -+ obj->bit_17 = bitmap_zalloc(obj->base.size >> PAGE_SHIFT, -+ GFP_KERNEL); -+ } -+ } else { -+ bitmap_free(obj->bit_17); -+ obj->bit_17 = NULL; -+ } -+ -+ return 0; -+} -+ -+/** -+ * i915_gem_set_tiling_ioctl - IOCTL handler to set tiling mode -+ * @dev: DRM device -+ * @data: data pointer for the ioctl -+ * @file: DRM file for the ioctl call -+ * -+ * Sets the tiling mode of an object, returning the required swizzling of -+ * bit 6 of addresses in the object. -+ * -+ * Called by the user via ioctl. -+ * -+ * Returns: -+ * Zero on success, negative errno on failure. -+ */ -+int -+i915_gem_set_tiling_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_gem_set_tiling *args = data; -+ struct drm_i915_gem_object *obj; -+ int err; -+ -+ obj = i915_gem_object_lookup(file, args->handle); -+ if (!obj) -+ return -ENOENT; -+ -+ /* -+ * The tiling mode of proxy objects is handled by its generator, and -+ * not allowed to be changed by userspace. -+ */ -+ if (i915_gem_object_is_proxy(obj)) { -+ err = -ENXIO; -+ goto err; -+ } -+ -+ if (!i915_tiling_ok(obj, args->tiling_mode, args->stride)) { -+ err = -EINVAL; -+ goto err; -+ } -+ -+ if (args->tiling_mode == I915_TILING_NONE) { -+ args->swizzle_mode = I915_BIT_6_SWIZZLE_NONE; -+ args->stride = 0; -+ } else { -+ if (args->tiling_mode == I915_TILING_X) -+ args->swizzle_mode = to_i915(dev)->mm.bit_6_swizzle_x; -+ else -+ args->swizzle_mode = to_i915(dev)->mm.bit_6_swizzle_y; -+ -+ /* Hide bit 17 swizzling from the user. This prevents old Mesa -+ * from aborting the application on sw fallbacks to bit 17, -+ * and we use the pread/pwrite bit17 paths to swizzle for it. -+ * If there was a user that was relying on the swizzle -+ * information for drm_intel_bo_map()ed reads/writes this would -+ * break it, but we don't have any of those. -+ */ -+ if (args->swizzle_mode == I915_BIT_6_SWIZZLE_9_17) -+ args->swizzle_mode = I915_BIT_6_SWIZZLE_9; -+ if (args->swizzle_mode == I915_BIT_6_SWIZZLE_9_10_17) -+ args->swizzle_mode = I915_BIT_6_SWIZZLE_9_10; -+ -+ /* If we can't handle the swizzling, make it untiled. */ -+ if (args->swizzle_mode == I915_BIT_6_SWIZZLE_UNKNOWN) { -+ args->tiling_mode = I915_TILING_NONE; -+ args->swizzle_mode = I915_BIT_6_SWIZZLE_NONE; -+ args->stride = 0; -+ } -+ } -+ -+ err = mutex_lock_interruptible(&dev->struct_mutex); -+ if (err) -+ goto err; -+ -+ err = i915_gem_object_set_tiling(obj, args->tiling_mode, args->stride); -+ mutex_unlock(&dev->struct_mutex); -+ -+ /* We have to maintain this existing ABI... */ -+ args->stride = i915_gem_object_get_stride(obj); -+ args->tiling_mode = i915_gem_object_get_tiling(obj); -+ -+err: -+ i915_gem_object_put(obj); -+ return err; -+} -+ -+/** -+ * i915_gem_get_tiling_ioctl - IOCTL handler to get tiling mode -+ * @dev: DRM device -+ * @data: data pointer for the ioctl -+ * @file: DRM file for the ioctl call -+ * -+ * Returns the current tiling mode and required bit 6 swizzling for the object. -+ * -+ * Called by the user via ioctl. -+ * -+ * Returns: -+ * Zero on success, negative errno on failure. -+ */ -+int -+i915_gem_get_tiling_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_gem_get_tiling *args = data; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_i915_gem_object *obj; -+ int err = -ENOENT; -+ -+ rcu_read_lock(); -+ obj = i915_gem_object_lookup_rcu(file, args->handle); -+ if (obj) { -+ args->tiling_mode = -+ READ_ONCE(obj->tiling_and_stride) & TILING_MASK; -+ err = 0; -+ } -+ rcu_read_unlock(); -+ if (unlikely(err)) -+ return err; -+ -+ switch (args->tiling_mode) { -+ case I915_TILING_X: -+ args->swizzle_mode = dev_priv->mm.bit_6_swizzle_x; -+ break; -+ case I915_TILING_Y: -+ args->swizzle_mode = dev_priv->mm.bit_6_swizzle_y; -+ break; -+ default: -+ case I915_TILING_NONE: -+ args->swizzle_mode = I915_BIT_6_SWIZZLE_NONE; -+ break; -+ } -+ -+ /* Hide bit 17 from the user -- see comment in i915_gem_set_tiling */ -+ if (dev_priv->quirks & QUIRK_PIN_SWIZZLED_PAGES) -+ args->phys_swizzle_mode = I915_BIT_6_SWIZZLE_UNKNOWN; -+ else -+ args->phys_swizzle_mode = args->swizzle_mode; -+ if (args->swizzle_mode == I915_BIT_6_SWIZZLE_9_17) -+ args->swizzle_mode = I915_BIT_6_SWIZZLE_9; -+ if (args->swizzle_mode == I915_BIT_6_SWIZZLE_9_10_17) -+ args->swizzle_mode = I915_BIT_6_SWIZZLE_9_10; -+ -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_gem_userptr.c b/drivers/gpu/drm/i915_legacy/i915_gem_userptr.c -new file mode 100644 -index 000000000000..8079ea3af103 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gem_userptr.c -@@ -0,0 +1,847 @@ -+/* -+ * Copyright © 2012-2014 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include -+#include "i915_drv.h" -+#include "i915_trace.h" -+#include "intel_drv.h" -+#include -+#include -+#include -+#include -+#include -+ -+struct i915_mm_struct { -+ struct mm_struct *mm; -+ struct drm_i915_private *i915; -+ struct i915_mmu_notifier *mn; -+ struct hlist_node node; -+ struct kref kref; -+ struct work_struct work; -+}; -+ -+#if defined(CONFIG_MMU_NOTIFIER) -+#include -+ -+struct i915_mmu_notifier { -+ spinlock_t lock; -+ struct hlist_node node; -+ struct mmu_notifier mn; -+ struct rb_root_cached objects; -+ struct i915_mm_struct *mm; -+}; -+ -+struct i915_mmu_object { -+ struct i915_mmu_notifier *mn; -+ struct drm_i915_gem_object *obj; -+ struct interval_tree_node it; -+}; -+ -+static void add_object(struct i915_mmu_object *mo) -+{ -+ GEM_BUG_ON(!RB_EMPTY_NODE(&mo->it.rb)); -+ interval_tree_insert(&mo->it, &mo->mn->objects); -+} -+ -+static void del_object(struct i915_mmu_object *mo) -+{ -+ if (RB_EMPTY_NODE(&mo->it.rb)) -+ return; -+ -+ interval_tree_remove(&mo->it, &mo->mn->objects); -+ RB_CLEAR_NODE(&mo->it.rb); -+} -+ -+static void -+__i915_gem_userptr_set_active(struct drm_i915_gem_object *obj, bool value) -+{ -+ struct i915_mmu_object *mo = obj->userptr.mmu_object; -+ -+ /* -+ * During mm_invalidate_range we need to cancel any userptr that -+ * overlaps the range being invalidated. Doing so requires the -+ * struct_mutex, and that risks recursion. In order to cause -+ * recursion, the user must alias the userptr address space with -+ * a GTT mmapping (possible with a MAP_FIXED) - then when we have -+ * to invalidate that mmaping, mm_invalidate_range is called with -+ * the userptr address *and* the struct_mutex held. To prevent that -+ * we set a flag under the i915_mmu_notifier spinlock to indicate -+ * whether this object is valid. -+ */ -+ if (!mo) -+ return; -+ -+ spin_lock(&mo->mn->lock); -+ if (value) -+ add_object(mo); -+ else -+ del_object(mo); -+ spin_unlock(&mo->mn->lock); -+} -+ -+static int -+userptr_mn_invalidate_range_start(struct mmu_notifier *_mn, -+ const struct mmu_notifier_range *range) -+{ -+ struct i915_mmu_notifier *mn = -+ container_of(_mn, struct i915_mmu_notifier, mn); -+ struct interval_tree_node *it; -+ struct mutex *unlock = NULL; -+ unsigned long end; -+ int ret = 0; -+ -+ if (RB_EMPTY_ROOT(&mn->objects.rb_root)) -+ return 0; -+ -+ /* interval ranges are inclusive, but invalidate range is exclusive */ -+ end = range->end - 1; -+ -+ spin_lock(&mn->lock); -+ it = interval_tree_iter_first(&mn->objects, range->start, end); -+ while (it) { -+ struct drm_i915_gem_object *obj; -+ -+ if (!mmu_notifier_range_blockable(range)) { -+ ret = -EAGAIN; -+ break; -+ } -+ -+ /* -+ * The mmu_object is released late when destroying the -+ * GEM object so it is entirely possible to gain a -+ * reference on an object in the process of being freed -+ * since our serialisation is via the spinlock and not -+ * the struct_mutex - and consequently use it after it -+ * is freed and then double free it. To prevent that -+ * use-after-free we only acquire a reference on the -+ * object if it is not in the process of being destroyed. -+ */ -+ obj = container_of(it, struct i915_mmu_object, it)->obj; -+ if (!kref_get_unless_zero(&obj->base.refcount)) { -+ it = interval_tree_iter_next(it, range->start, end); -+ continue; -+ } -+ spin_unlock(&mn->lock); -+ -+ if (!unlock) { -+ unlock = &mn->mm->i915->drm.struct_mutex; -+ -+ switch (mutex_trylock_recursive(unlock)) { -+ default: -+ case MUTEX_TRYLOCK_FAILED: -+ if (mutex_lock_killable_nested(unlock, I915_MM_SHRINKER)) { -+ i915_gem_object_put(obj); -+ return -EINTR; -+ } -+ /* fall through */ -+ case MUTEX_TRYLOCK_SUCCESS: -+ break; -+ -+ case MUTEX_TRYLOCK_RECURSIVE: -+ unlock = ERR_PTR(-EEXIST); -+ break; -+ } -+ } -+ -+ ret = i915_gem_object_unbind(obj); -+ if (ret == 0) -+ ret = __i915_gem_object_put_pages(obj, I915_MM_SHRINKER); -+ i915_gem_object_put(obj); -+ if (ret) -+ goto unlock; -+ -+ spin_lock(&mn->lock); -+ -+ /* -+ * As we do not (yet) protect the mmu from concurrent insertion -+ * over this range, there is no guarantee that this search will -+ * terminate given a pathologic workload. -+ */ -+ it = interval_tree_iter_first(&mn->objects, range->start, end); -+ } -+ spin_unlock(&mn->lock); -+ -+unlock: -+ if (!IS_ERR_OR_NULL(unlock)) -+ mutex_unlock(unlock); -+ -+ return ret; -+ -+} -+ -+static const struct mmu_notifier_ops i915_gem_userptr_notifier = { -+ .invalidate_range_start = userptr_mn_invalidate_range_start, -+}; -+ -+static struct i915_mmu_notifier * -+i915_mmu_notifier_create(struct i915_mm_struct *mm) -+{ -+ struct i915_mmu_notifier *mn; -+ -+ mn = kmalloc(sizeof(*mn), GFP_KERNEL); -+ if (mn == NULL) -+ return ERR_PTR(-ENOMEM); -+ -+ spin_lock_init(&mn->lock); -+ mn->mn.ops = &i915_gem_userptr_notifier; -+ mn->objects = RB_ROOT_CACHED; -+ mn->mm = mm; -+ -+ return mn; -+} -+ -+static void -+i915_gem_userptr_release__mmu_notifier(struct drm_i915_gem_object *obj) -+{ -+ struct i915_mmu_object *mo; -+ -+ mo = fetch_and_zero(&obj->userptr.mmu_object); -+ if (!mo) -+ return; -+ -+ spin_lock(&mo->mn->lock); -+ del_object(mo); -+ spin_unlock(&mo->mn->lock); -+ kfree(mo); -+} -+ -+static struct i915_mmu_notifier * -+i915_mmu_notifier_find(struct i915_mm_struct *mm) -+{ -+ struct i915_mmu_notifier *mn; -+ int err = 0; -+ -+ mn = mm->mn; -+ if (mn) -+ return mn; -+ -+ mn = i915_mmu_notifier_create(mm); -+ if (IS_ERR(mn)) -+ err = PTR_ERR(mn); -+ -+ down_write(&mm->mm->mmap_sem); -+ mutex_lock(&mm->i915->mm_lock); -+ if (mm->mn == NULL && !err) { -+ /* Protected by mmap_sem (write-lock) */ -+ err = __mmu_notifier_register(&mn->mn, mm->mm); -+ if (!err) { -+ /* Protected by mm_lock */ -+ mm->mn = fetch_and_zero(&mn); -+ } -+ } else if (mm->mn) { -+ /* -+ * Someone else raced and successfully installed the mmu -+ * notifier, we can cancel our own errors. -+ */ -+ err = 0; -+ } -+ mutex_unlock(&mm->i915->mm_lock); -+ up_write(&mm->mm->mmap_sem); -+ -+ if (mn && !IS_ERR(mn)) -+ kfree(mn); -+ -+ return err ? ERR_PTR(err) : mm->mn; -+} -+ -+static int -+i915_gem_userptr_init__mmu_notifier(struct drm_i915_gem_object *obj, -+ unsigned flags) -+{ -+ struct i915_mmu_notifier *mn; -+ struct i915_mmu_object *mo; -+ -+ if (flags & I915_USERPTR_UNSYNCHRONIZED) -+ return capable(CAP_SYS_ADMIN) ? 0 : -EPERM; -+ -+ if (WARN_ON(obj->userptr.mm == NULL)) -+ return -EINVAL; -+ -+ mn = i915_mmu_notifier_find(obj->userptr.mm); -+ if (IS_ERR(mn)) -+ return PTR_ERR(mn); -+ -+ mo = kzalloc(sizeof(*mo), GFP_KERNEL); -+ if (!mo) -+ return -ENOMEM; -+ -+ mo->mn = mn; -+ mo->obj = obj; -+ mo->it.start = obj->userptr.ptr; -+ mo->it.last = obj->userptr.ptr + obj->base.size - 1; -+ RB_CLEAR_NODE(&mo->it.rb); -+ -+ obj->userptr.mmu_object = mo; -+ return 0; -+} -+ -+static void -+i915_mmu_notifier_free(struct i915_mmu_notifier *mn, -+ struct mm_struct *mm) -+{ -+ if (mn == NULL) -+ return; -+ -+ mmu_notifier_unregister(&mn->mn, mm); -+ kfree(mn); -+} -+ -+#else -+ -+static void -+__i915_gem_userptr_set_active(struct drm_i915_gem_object *obj, bool value) -+{ -+} -+ -+static void -+i915_gem_userptr_release__mmu_notifier(struct drm_i915_gem_object *obj) -+{ -+} -+ -+static int -+i915_gem_userptr_init__mmu_notifier(struct drm_i915_gem_object *obj, -+ unsigned flags) -+{ -+ if ((flags & I915_USERPTR_UNSYNCHRONIZED) == 0) -+ return -ENODEV; -+ -+ if (!capable(CAP_SYS_ADMIN)) -+ return -EPERM; -+ -+ return 0; -+} -+ -+static void -+i915_mmu_notifier_free(struct i915_mmu_notifier *mn, -+ struct mm_struct *mm) -+{ -+} -+ -+#endif -+ -+static struct i915_mm_struct * -+__i915_mm_struct_find(struct drm_i915_private *dev_priv, struct mm_struct *real) -+{ -+ struct i915_mm_struct *mm; -+ -+ /* Protected by dev_priv->mm_lock */ -+ hash_for_each_possible(dev_priv->mm_structs, mm, node, (unsigned long)real) -+ if (mm->mm == real) -+ return mm; -+ -+ return NULL; -+} -+ -+static int -+i915_gem_userptr_init__mm_struct(struct drm_i915_gem_object *obj) -+{ -+ struct drm_i915_private *dev_priv = to_i915(obj->base.dev); -+ struct i915_mm_struct *mm; -+ int ret = 0; -+ -+ /* During release of the GEM object we hold the struct_mutex. This -+ * precludes us from calling mmput() at that time as that may be -+ * the last reference and so call exit_mmap(). exit_mmap() will -+ * attempt to reap the vma, and if we were holding a GTT mmap -+ * would then call drm_gem_vm_close() and attempt to reacquire -+ * the struct mutex. So in order to avoid that recursion, we have -+ * to defer releasing the mm reference until after we drop the -+ * struct_mutex, i.e. we need to schedule a worker to do the clean -+ * up. -+ */ -+ mutex_lock(&dev_priv->mm_lock); -+ mm = __i915_mm_struct_find(dev_priv, current->mm); -+ if (mm == NULL) { -+ mm = kmalloc(sizeof(*mm), GFP_KERNEL); -+ if (mm == NULL) { -+ ret = -ENOMEM; -+ goto out; -+ } -+ -+ kref_init(&mm->kref); -+ mm->i915 = to_i915(obj->base.dev); -+ -+ mm->mm = current->mm; -+ mmgrab(current->mm); -+ -+ mm->mn = NULL; -+ -+ /* Protected by dev_priv->mm_lock */ -+ hash_add(dev_priv->mm_structs, -+ &mm->node, (unsigned long)mm->mm); -+ } else -+ kref_get(&mm->kref); -+ -+ obj->userptr.mm = mm; -+out: -+ mutex_unlock(&dev_priv->mm_lock); -+ return ret; -+} -+ -+static void -+__i915_mm_struct_free__worker(struct work_struct *work) -+{ -+ struct i915_mm_struct *mm = container_of(work, typeof(*mm), work); -+ i915_mmu_notifier_free(mm->mn, mm->mm); -+ mmdrop(mm->mm); -+ kfree(mm); -+} -+ -+static void -+__i915_mm_struct_free(struct kref *kref) -+{ -+ struct i915_mm_struct *mm = container_of(kref, typeof(*mm), kref); -+ -+ /* Protected by dev_priv->mm_lock */ -+ hash_del(&mm->node); -+ mutex_unlock(&mm->i915->mm_lock); -+ -+ INIT_WORK(&mm->work, __i915_mm_struct_free__worker); -+ queue_work(mm->i915->mm.userptr_wq, &mm->work); -+} -+ -+static void -+i915_gem_userptr_release__mm_struct(struct drm_i915_gem_object *obj) -+{ -+ if (obj->userptr.mm == NULL) -+ return; -+ -+ kref_put_mutex(&obj->userptr.mm->kref, -+ __i915_mm_struct_free, -+ &to_i915(obj->base.dev)->mm_lock); -+ obj->userptr.mm = NULL; -+} -+ -+struct get_pages_work { -+ struct work_struct work; -+ struct drm_i915_gem_object *obj; -+ struct task_struct *task; -+}; -+ -+static struct sg_table * -+__i915_gem_userptr_alloc_pages(struct drm_i915_gem_object *obj, -+ struct page **pvec, int num_pages) -+{ -+ unsigned int max_segment = i915_sg_segment_size(); -+ struct sg_table *st; -+ unsigned int sg_page_sizes; -+ int ret; -+ -+ st = kmalloc(sizeof(*st), GFP_KERNEL); -+ if (!st) -+ return ERR_PTR(-ENOMEM); -+ -+alloc_table: -+ ret = __sg_alloc_table_from_pages(st, pvec, num_pages, -+ 0, num_pages << PAGE_SHIFT, -+ max_segment, -+ GFP_KERNEL); -+ if (ret) { -+ kfree(st); -+ return ERR_PTR(ret); -+ } -+ -+ ret = i915_gem_gtt_prepare_pages(obj, st); -+ if (ret) { -+ sg_free_table(st); -+ -+ if (max_segment > PAGE_SIZE) { -+ max_segment = PAGE_SIZE; -+ goto alloc_table; -+ } -+ -+ kfree(st); -+ return ERR_PTR(ret); -+ } -+ -+ sg_page_sizes = i915_sg_page_sizes(st->sgl); -+ -+ __i915_gem_object_set_pages(obj, st, sg_page_sizes); -+ -+ return st; -+} -+ -+static void -+__i915_gem_userptr_get_pages_worker(struct work_struct *_work) -+{ -+ struct get_pages_work *work = container_of(_work, typeof(*work), work); -+ struct drm_i915_gem_object *obj = work->obj; -+ const int npages = obj->base.size >> PAGE_SHIFT; -+ struct page **pvec; -+ int pinned, ret; -+ -+ ret = -ENOMEM; -+ pinned = 0; -+ -+ pvec = kvmalloc_array(npages, sizeof(struct page *), GFP_KERNEL); -+ if (pvec != NULL) { -+ struct mm_struct *mm = obj->userptr.mm->mm; -+ unsigned int flags = 0; -+ -+ if (!i915_gem_object_is_readonly(obj)) -+ flags |= FOLL_WRITE; -+ -+ ret = -EFAULT; -+ if (mmget_not_zero(mm)) { -+ down_read(&mm->mmap_sem); -+ while (pinned < npages) { -+ ret = get_user_pages_remote -+ (work->task, mm, -+ obj->userptr.ptr + pinned * PAGE_SIZE, -+ npages - pinned, -+ flags, -+ pvec + pinned, NULL, NULL); -+ if (ret < 0) -+ break; -+ -+ pinned += ret; -+ } -+ up_read(&mm->mmap_sem); -+ mmput(mm); -+ } -+ } -+ -+ mutex_lock(&obj->mm.lock); -+ if (obj->userptr.work == &work->work) { -+ struct sg_table *pages = ERR_PTR(ret); -+ -+ if (pinned == npages) { -+ pages = __i915_gem_userptr_alloc_pages(obj, pvec, -+ npages); -+ if (!IS_ERR(pages)) { -+ pinned = 0; -+ pages = NULL; -+ } -+ } -+ -+ obj->userptr.work = ERR_CAST(pages); -+ if (IS_ERR(pages)) -+ __i915_gem_userptr_set_active(obj, false); -+ } -+ mutex_unlock(&obj->mm.lock); -+ -+ release_pages(pvec, pinned); -+ kvfree(pvec); -+ -+ i915_gem_object_put(obj); -+ put_task_struct(work->task); -+ kfree(work); -+} -+ -+static struct sg_table * -+__i915_gem_userptr_get_pages_schedule(struct drm_i915_gem_object *obj) -+{ -+ struct get_pages_work *work; -+ -+ /* Spawn a worker so that we can acquire the -+ * user pages without holding our mutex. Access -+ * to the user pages requires mmap_sem, and we have -+ * a strict lock ordering of mmap_sem, struct_mutex - -+ * we already hold struct_mutex here and so cannot -+ * call gup without encountering a lock inversion. -+ * -+ * Userspace will keep on repeating the operation -+ * (thanks to EAGAIN) until either we hit the fast -+ * path or the worker completes. If the worker is -+ * cancelled or superseded, the task is still run -+ * but the results ignored. (This leads to -+ * complications that we may have a stray object -+ * refcount that we need to be wary of when -+ * checking for existing objects during creation.) -+ * If the worker encounters an error, it reports -+ * that error back to this function through -+ * obj->userptr.work = ERR_PTR. -+ */ -+ work = kmalloc(sizeof(*work), GFP_KERNEL); -+ if (work == NULL) -+ return ERR_PTR(-ENOMEM); -+ -+ obj->userptr.work = &work->work; -+ -+ work->obj = i915_gem_object_get(obj); -+ -+ work->task = current; -+ get_task_struct(work->task); -+ -+ INIT_WORK(&work->work, __i915_gem_userptr_get_pages_worker); -+ queue_work(to_i915(obj->base.dev)->mm.userptr_wq, &work->work); -+ -+ return ERR_PTR(-EAGAIN); -+} -+ -+static int i915_gem_userptr_get_pages(struct drm_i915_gem_object *obj) -+{ -+ const int num_pages = obj->base.size >> PAGE_SHIFT; -+ struct mm_struct *mm = obj->userptr.mm->mm; -+ struct page **pvec; -+ struct sg_table *pages; -+ bool active; -+ int pinned; -+ -+ /* If userspace should engineer that these pages are replaced in -+ * the vma between us binding this page into the GTT and completion -+ * of rendering... Their loss. If they change the mapping of their -+ * pages they need to create a new bo to point to the new vma. -+ * -+ * However, that still leaves open the possibility of the vma -+ * being copied upon fork. Which falls under the same userspace -+ * synchronisation issue as a regular bo, except that this time -+ * the process may not be expecting that a particular piece of -+ * memory is tied to the GPU. -+ * -+ * Fortunately, we can hook into the mmu_notifier in order to -+ * discard the page references prior to anything nasty happening -+ * to the vma (discard or cloning) which should prevent the more -+ * egregious cases from causing harm. -+ */ -+ -+ if (obj->userptr.work) { -+ /* active flag should still be held for the pending work */ -+ if (IS_ERR(obj->userptr.work)) -+ return PTR_ERR(obj->userptr.work); -+ else -+ return -EAGAIN; -+ } -+ -+ pvec = NULL; -+ pinned = 0; -+ -+ if (mm == current->mm) { -+ pvec = kvmalloc_array(num_pages, sizeof(struct page *), -+ GFP_KERNEL | -+ __GFP_NORETRY | -+ __GFP_NOWARN); -+ if (pvec) /* defer to worker if malloc fails */ -+ pinned = __get_user_pages_fast(obj->userptr.ptr, -+ num_pages, -+ !i915_gem_object_is_readonly(obj), -+ pvec); -+ } -+ -+ active = false; -+ if (pinned < 0) { -+ pages = ERR_PTR(pinned); -+ pinned = 0; -+ } else if (pinned < num_pages) { -+ pages = __i915_gem_userptr_get_pages_schedule(obj); -+ active = pages == ERR_PTR(-EAGAIN); -+ } else { -+ pages = __i915_gem_userptr_alloc_pages(obj, pvec, num_pages); -+ active = !IS_ERR(pages); -+ } -+ if (active) -+ __i915_gem_userptr_set_active(obj, true); -+ -+ if (IS_ERR(pages)) -+ release_pages(pvec, pinned); -+ kvfree(pvec); -+ -+ return PTR_ERR_OR_ZERO(pages); -+} -+ -+static void -+i915_gem_userptr_put_pages(struct drm_i915_gem_object *obj, -+ struct sg_table *pages) -+{ -+ struct sgt_iter sgt_iter; -+ struct page *page; -+ -+ /* Cancel any inflight work and force them to restart their gup */ -+ obj->userptr.work = NULL; -+ __i915_gem_userptr_set_active(obj, false); -+ if (!pages) -+ return; -+ -+ __i915_gem_object_release_shmem(obj, pages, true); -+ i915_gem_gtt_finish_pages(obj, pages); -+ -+ for_each_sgt_page(page, sgt_iter, pages) { -+ if (obj->mm.dirty) -+ set_page_dirty(page); -+ -+ mark_page_accessed(page); -+ put_page(page); -+ } -+ obj->mm.dirty = false; -+ -+ sg_free_table(pages); -+ kfree(pages); -+} -+ -+static void -+i915_gem_userptr_release(struct drm_i915_gem_object *obj) -+{ -+ i915_gem_userptr_release__mmu_notifier(obj); -+ i915_gem_userptr_release__mm_struct(obj); -+} -+ -+static int -+i915_gem_userptr_dmabuf_export(struct drm_i915_gem_object *obj) -+{ -+ if (obj->userptr.mmu_object) -+ return 0; -+ -+ return i915_gem_userptr_init__mmu_notifier(obj, 0); -+} -+ -+static const struct drm_i915_gem_object_ops i915_gem_userptr_ops = { -+ .flags = I915_GEM_OBJECT_HAS_STRUCT_PAGE | -+ I915_GEM_OBJECT_IS_SHRINKABLE | -+ I915_GEM_OBJECT_ASYNC_CANCEL, -+ .get_pages = i915_gem_userptr_get_pages, -+ .put_pages = i915_gem_userptr_put_pages, -+ .dmabuf_export = i915_gem_userptr_dmabuf_export, -+ .release = i915_gem_userptr_release, -+}; -+ -+/* -+ * Creates a new mm object that wraps some normal memory from the process -+ * context - user memory. -+ * -+ * We impose several restrictions upon the memory being mapped -+ * into the GPU. -+ * 1. It must be page aligned (both start/end addresses, i.e ptr and size). -+ * 2. It must be normal system memory, not a pointer into another map of IO -+ * space (e.g. it must not be a GTT mmapping of another object). -+ * 3. We only allow a bo as large as we could in theory map into the GTT, -+ * that is we limit the size to the total size of the GTT. -+ * 4. The bo is marked as being snoopable. The backing pages are left -+ * accessible directly by the CPU, but reads and writes by the GPU may -+ * incur the cost of a snoop (unless you have an LLC architecture). -+ * -+ * Synchronisation between multiple users and the GPU is left to userspace -+ * through the normal set-domain-ioctl. The kernel will enforce that the -+ * GPU relinquishes the VMA before it is returned back to the system -+ * i.e. upon free(), munmap() or process termination. However, the userspace -+ * malloc() library may not immediately relinquish the VMA after free() and -+ * instead reuse it whilst the GPU is still reading and writing to the VMA. -+ * Caveat emptor. -+ * -+ * Also note, that the object created here is not currently a "first class" -+ * object, in that several ioctls are banned. These are the CPU access -+ * ioctls: mmap(), pwrite and pread. In practice, you are expected to use -+ * direct access via your pointer rather than use those ioctls. Another -+ * restriction is that we do not allow userptr surfaces to be pinned to the -+ * hardware and so we reject any attempt to create a framebuffer out of a -+ * userptr. -+ * -+ * If you think this is a good interface to use to pass GPU memory between -+ * drivers, please use dma-buf instead. In fact, wherever possible use -+ * dma-buf instead. -+ */ -+int -+i915_gem_userptr_ioctl(struct drm_device *dev, -+ void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_i915_gem_userptr *args = data; -+ struct drm_i915_gem_object *obj; -+ int ret; -+ u32 handle; -+ -+ if (!HAS_LLC(dev_priv) && !HAS_SNOOP(dev_priv)) { -+ /* We cannot support coherent userptr objects on hw without -+ * LLC and broken snooping. -+ */ -+ return -ENODEV; -+ } -+ -+ if (args->flags & ~(I915_USERPTR_READ_ONLY | -+ I915_USERPTR_UNSYNCHRONIZED)) -+ return -EINVAL; -+ -+ if (!args->user_size) -+ return -EINVAL; -+ -+ if (offset_in_page(args->user_ptr | args->user_size)) -+ return -EINVAL; -+ -+ if (!access_ok((char __user *)(unsigned long)args->user_ptr, args->user_size)) -+ return -EFAULT; -+ -+ if (args->flags & I915_USERPTR_READ_ONLY) { -+ struct i915_hw_ppgtt *ppgtt; -+ -+ /* -+ * On almost all of the older hw, we cannot tell the GPU that -+ * a page is readonly. -+ */ -+ ppgtt = dev_priv->kernel_context->ppgtt; -+ if (!ppgtt || !ppgtt->vm.has_read_only) -+ return -ENODEV; -+ } -+ -+ obj = i915_gem_object_alloc(); -+ if (obj == NULL) -+ return -ENOMEM; -+ -+ drm_gem_private_object_init(dev, &obj->base, args->user_size); -+ i915_gem_object_init(obj, &i915_gem_userptr_ops); -+ obj->read_domains = I915_GEM_DOMAIN_CPU; -+ obj->write_domain = I915_GEM_DOMAIN_CPU; -+ i915_gem_object_set_cache_coherency(obj, I915_CACHE_LLC); -+ -+ obj->userptr.ptr = args->user_ptr; -+ if (args->flags & I915_USERPTR_READ_ONLY) -+ i915_gem_object_set_readonly(obj); -+ -+ /* And keep a pointer to the current->mm for resolving the user pages -+ * at binding. This means that we need to hook into the mmu_notifier -+ * in order to detect if the mmu is destroyed. -+ */ -+ ret = i915_gem_userptr_init__mm_struct(obj); -+ if (ret == 0) -+ ret = i915_gem_userptr_init__mmu_notifier(obj, args->flags); -+ if (ret == 0) -+ ret = drm_gem_handle_create(file, &obj->base, &handle); -+ -+ /* drop reference from allocate - handle holds it now */ -+ i915_gem_object_put(obj); -+ if (ret) -+ return ret; -+ -+ args->handle = handle; -+ return 0; -+} -+ -+int i915_gem_init_userptr(struct drm_i915_private *dev_priv) -+{ -+ mutex_init(&dev_priv->mm_lock); -+ hash_init(dev_priv->mm_structs); -+ -+ dev_priv->mm.userptr_wq = -+ alloc_workqueue("i915-userptr-acquire", -+ WQ_HIGHPRI | WQ_UNBOUND, -+ 0); -+ if (!dev_priv->mm.userptr_wq) -+ return -ENOMEM; -+ -+ return 0; -+} -+ -+void i915_gem_cleanup_userptr(struct drm_i915_private *dev_priv) -+{ -+ destroy_workqueue(dev_priv->mm.userptr_wq); -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_gemfs.c b/drivers/gpu/drm/i915_legacy/i915_gemfs.c -new file mode 100644 -index 000000000000..888b7d3f04c3 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gemfs.c -@@ -0,0 +1,75 @@ -+/* -+ * Copyright © 2017 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include -+#include -+#include -+ -+#include "i915_drv.h" -+#include "i915_gemfs.h" -+ -+int i915_gemfs_init(struct drm_i915_private *i915) -+{ -+ struct file_system_type *type; -+ struct vfsmount *gemfs; -+ -+ type = get_fs_type("tmpfs"); -+ if (!type) -+ return -ENODEV; -+ -+ gemfs = kern_mount(type); -+ if (IS_ERR(gemfs)) -+ return PTR_ERR(gemfs); -+ -+ /* -+ * Enable huge-pages for objects that are at least HPAGE_PMD_SIZE, most -+ * likely 2M. Note that within_size may overallocate huge-pages, if say -+ * we allocate an object of size 2M + 4K, we may get 2M + 2M, but under -+ * memory pressure shmem should split any huge-pages which can be -+ * shrunk. -+ */ -+ -+ if (has_transparent_hugepage()) { -+ struct super_block *sb = gemfs->mnt_sb; -+ /* FIXME: Disabled until we get W/A for read BW issue. */ -+ char options[] = "huge=never"; -+ int flags = 0; -+ int err; -+ -+ err = sb->s_op->remount_fs(sb, &flags, options); -+ if (err) { -+ kern_unmount(gemfs); -+ return err; -+ } -+ } -+ -+ i915->mm.gemfs = gemfs; -+ -+ return 0; -+} -+ -+void i915_gemfs_fini(struct drm_i915_private *i915) -+{ -+ kern_unmount(i915->mm.gemfs); -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_gemfs.h b/drivers/gpu/drm/i915_legacy/i915_gemfs.h -new file mode 100644 -index 000000000000..cca8bdc5b93e ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gemfs.h -@@ -0,0 +1,34 @@ -+/* -+ * Copyright © 2017 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef __I915_GEMFS_H__ -+#define __I915_GEMFS_H__ -+ -+struct drm_i915_private; -+ -+int i915_gemfs_init(struct drm_i915_private *i915); -+ -+void i915_gemfs_fini(struct drm_i915_private *i915); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_globals.c b/drivers/gpu/drm/i915_legacy/i915_globals.c -new file mode 100644 -index 000000000000..81e5c2ce336b ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_globals.c -@@ -0,0 +1,125 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#include -+#include -+ -+#include "i915_active.h" -+#include "i915_gem_context.h" -+#include "i915_gem_object.h" -+#include "i915_globals.h" -+#include "i915_request.h" -+#include "i915_scheduler.h" -+#include "i915_vma.h" -+ -+static LIST_HEAD(globals); -+ -+static atomic_t active; -+static atomic_t epoch; -+static struct park_work { -+ struct rcu_work work; -+ int epoch; -+} park; -+ -+static void i915_globals_shrink(void) -+{ -+ struct i915_global *global; -+ -+ /* -+ * kmem_cache_shrink() discards empty slabs and reorders partially -+ * filled slabs to prioritise allocating from the mostly full slabs, -+ * with the aim of reducing fragmentation. -+ */ -+ list_for_each_entry(global, &globals, link) -+ global->shrink(); -+} -+ -+static void __i915_globals_park(struct work_struct *work) -+{ -+ /* Confirm nothing woke up in the last grace period */ -+ if (park.epoch == atomic_read(&epoch)) -+ i915_globals_shrink(); -+} -+ -+void __init i915_global_register(struct i915_global *global) -+{ -+ GEM_BUG_ON(!global->shrink); -+ GEM_BUG_ON(!global->exit); -+ -+ list_add_tail(&global->link, &globals); -+} -+ -+static void __i915_globals_cleanup(void) -+{ -+ struct i915_global *global, *next; -+ -+ list_for_each_entry_safe_reverse(global, next, &globals, link) -+ global->exit(); -+} -+ -+static __initconst int (* const initfn[])(void) = { -+ i915_global_active_init, -+ i915_global_context_init, -+ i915_global_gem_context_init, -+ i915_global_objects_init, -+ i915_global_request_init, -+ i915_global_scheduler_init, -+ i915_global_vma_init, -+}; -+ -+int __init i915_globals_init(void) -+{ -+ int i; -+ -+ for (i = 0; i < ARRAY_SIZE(initfn); i++) { -+ int err; -+ -+ err = initfn[i](); -+ if (err) { -+ __i915_globals_cleanup(); -+ return err; -+ } -+ } -+ -+ INIT_RCU_WORK(&park.work, __i915_globals_park); -+ return 0; -+} -+ -+void i915_globals_park(void) -+{ -+ /* -+ * Defer shrinking the global slab caches (and other work) until -+ * after a RCU grace period has completed with no activity. This -+ * is to try and reduce the latency impact on the consumers caused -+ * by us shrinking the caches the same time as they are trying to -+ * allocate, with the assumption being that if we idle long enough -+ * for an RCU grace period to elapse since the last use, it is likely -+ * to be longer until we need the caches again. -+ */ -+ if (!atomic_dec_and_test(&active)) -+ return; -+ -+ park.epoch = atomic_inc_return(&epoch); -+ queue_rcu_work(system_wq, &park.work); -+} -+ -+void i915_globals_unpark(void) -+{ -+ atomic_inc(&epoch); -+ atomic_inc(&active); -+} -+ -+void __exit i915_globals_exit(void) -+{ -+ /* Flush any residual park_work */ -+ atomic_inc(&epoch); -+ flush_rcu_work(&park.work); -+ -+ __i915_globals_cleanup(); -+ -+ /* And ensure that our DESTROY_BY_RCU slabs are truly destroyed */ -+ rcu_barrier(); -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_globals.h b/drivers/gpu/drm/i915_legacy/i915_globals.h -new file mode 100644 -index 000000000000..04c1ce107fc0 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_globals.h -@@ -0,0 +1,35 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef _I915_GLOBALS_H_ -+#define _I915_GLOBALS_H_ -+ -+typedef void (*i915_global_func_t)(void); -+ -+struct i915_global { -+ struct list_head link; -+ -+ i915_global_func_t shrink; -+ i915_global_func_t exit; -+}; -+ -+void i915_global_register(struct i915_global *global); -+ -+int i915_globals_init(void); -+void i915_globals_park(void); -+void i915_globals_unpark(void); -+void i915_globals_exit(void); -+ -+/* constructors */ -+int i915_global_active_init(void); -+int i915_global_context_init(void); -+int i915_global_gem_context_init(void); -+int i915_global_objects_init(void); -+int i915_global_request_init(void); -+int i915_global_scheduler_init(void); -+int i915_global_vma_init(void); -+ -+#endif /* _I915_GLOBALS_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_gpu_error.c b/drivers/gpu/drm/i915_legacy/i915_gpu_error.c -new file mode 100644 -index 000000000000..f51ff683dd2e ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gpu_error.c -@@ -0,0 +1,1876 @@ -+/* -+ * Copyright (c) 2008 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eric Anholt -+ * Keith Packard -+ * Mika Kuoppala -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include "i915_gpu_error.h" -+#include "i915_drv.h" -+ -+static inline const struct intel_engine_cs * -+engine_lookup(const struct drm_i915_private *i915, unsigned int id) -+{ -+ if (id >= I915_NUM_ENGINES) -+ return NULL; -+ -+ return i915->engine[id]; -+} -+ -+static inline const char * -+__engine_name(const struct intel_engine_cs *engine) -+{ -+ return engine ? engine->name : ""; -+} -+ -+static const char * -+engine_name(const struct drm_i915_private *i915, unsigned int id) -+{ -+ return __engine_name(engine_lookup(i915, id)); -+} -+ -+static const char *tiling_flag(int tiling) -+{ -+ switch (tiling) { -+ default: -+ case I915_TILING_NONE: return ""; -+ case I915_TILING_X: return " X"; -+ case I915_TILING_Y: return " Y"; -+ } -+} -+ -+static const char *dirty_flag(int dirty) -+{ -+ return dirty ? " dirty" : ""; -+} -+ -+static const char *purgeable_flag(int purgeable) -+{ -+ return purgeable ? " purgeable" : ""; -+} -+ -+static void __sg_set_buf(struct scatterlist *sg, -+ void *addr, unsigned int len, loff_t it) -+{ -+ sg->page_link = (unsigned long)virt_to_page(addr); -+ sg->offset = offset_in_page(addr); -+ sg->length = len; -+ sg->dma_address = it; -+} -+ -+static bool __i915_error_grow(struct drm_i915_error_state_buf *e, size_t len) -+{ -+ if (!len) -+ return false; -+ -+ if (e->bytes + len + 1 <= e->size) -+ return true; -+ -+ if (e->bytes) { -+ __sg_set_buf(e->cur++, e->buf, e->bytes, e->iter); -+ e->iter += e->bytes; -+ e->buf = NULL; -+ e->bytes = 0; -+ } -+ -+ if (e->cur == e->end) { -+ struct scatterlist *sgl; -+ -+ sgl = (typeof(sgl))__get_free_page(GFP_KERNEL); -+ if (!sgl) { -+ e->err = -ENOMEM; -+ return false; -+ } -+ -+ if (e->cur) { -+ e->cur->offset = 0; -+ e->cur->length = 0; -+ e->cur->page_link = -+ (unsigned long)sgl | SG_CHAIN; -+ } else { -+ e->sgl = sgl; -+ } -+ -+ e->cur = sgl; -+ e->end = sgl + SG_MAX_SINGLE_ALLOC - 1; -+ } -+ -+ e->size = ALIGN(len + 1, SZ_64K); -+ e->buf = kmalloc(e->size, GFP_KERNEL | __GFP_NOWARN | __GFP_NORETRY); -+ if (!e->buf) { -+ e->size = PAGE_ALIGN(len + 1); -+ e->buf = kmalloc(e->size, GFP_KERNEL); -+ } -+ if (!e->buf) { -+ e->err = -ENOMEM; -+ return false; -+ } -+ -+ return true; -+} -+ -+__printf(2, 0) -+static void i915_error_vprintf(struct drm_i915_error_state_buf *e, -+ const char *fmt, va_list args) -+{ -+ va_list ap; -+ int len; -+ -+ if (e->err) -+ return; -+ -+ va_copy(ap, args); -+ len = vsnprintf(NULL, 0, fmt, ap); -+ va_end(ap); -+ if (len <= 0) { -+ e->err = len; -+ return; -+ } -+ -+ if (!__i915_error_grow(e, len)) -+ return; -+ -+ GEM_BUG_ON(e->bytes >= e->size); -+ len = vscnprintf(e->buf + e->bytes, e->size - e->bytes, fmt, args); -+ if (len < 0) { -+ e->err = len; -+ return; -+ } -+ e->bytes += len; -+} -+ -+static void i915_error_puts(struct drm_i915_error_state_buf *e, const char *str) -+{ -+ unsigned len; -+ -+ if (e->err || !str) -+ return; -+ -+ len = strlen(str); -+ if (!__i915_error_grow(e, len)) -+ return; -+ -+ GEM_BUG_ON(e->bytes + len > e->size); -+ memcpy(e->buf + e->bytes, str, len); -+ e->bytes += len; -+} -+ -+#define err_printf(e, ...) i915_error_printf(e, __VA_ARGS__) -+#define err_puts(e, s) i915_error_puts(e, s) -+ -+static void __i915_printfn_error(struct drm_printer *p, struct va_format *vaf) -+{ -+ i915_error_vprintf(p->arg, vaf->fmt, *vaf->va); -+} -+ -+static inline struct drm_printer -+i915_error_printer(struct drm_i915_error_state_buf *e) -+{ -+ struct drm_printer p = { -+ .printfn = __i915_printfn_error, -+ .arg = e, -+ }; -+ return p; -+} -+ -+#ifdef CONFIG_DRM_I915_COMPRESS_ERROR -+ -+struct compress { -+ struct z_stream_s zstream; -+ void *tmp; -+}; -+ -+static bool compress_init(struct compress *c) -+{ -+ struct z_stream_s *zstream = memset(&c->zstream, 0, sizeof(c->zstream)); -+ -+ zstream->workspace = -+ kmalloc(zlib_deflate_workspacesize(MAX_WBITS, MAX_MEM_LEVEL), -+ GFP_ATOMIC | __GFP_NOWARN); -+ if (!zstream->workspace) -+ return false; -+ -+ if (zlib_deflateInit(zstream, Z_DEFAULT_COMPRESSION) != Z_OK) { -+ kfree(zstream->workspace); -+ return false; -+ } -+ -+ c->tmp = NULL; -+ if (i915_has_memcpy_from_wc()) -+ c->tmp = (void *)__get_free_page(GFP_ATOMIC | __GFP_NOWARN); -+ -+ return true; -+} -+ -+static void *compress_next_page(struct drm_i915_error_object *dst) -+{ -+ unsigned long page; -+ -+ if (dst->page_count >= dst->num_pages) -+ return ERR_PTR(-ENOSPC); -+ -+ page = __get_free_page(GFP_ATOMIC | __GFP_NOWARN); -+ if (!page) -+ return ERR_PTR(-ENOMEM); -+ -+ return dst->pages[dst->page_count++] = (void *)page; -+} -+ -+static int compress_page(struct compress *c, -+ void *src, -+ struct drm_i915_error_object *dst) -+{ -+ struct z_stream_s *zstream = &c->zstream; -+ -+ zstream->next_in = src; -+ if (c->tmp && i915_memcpy_from_wc(c->tmp, src, PAGE_SIZE)) -+ zstream->next_in = c->tmp; -+ zstream->avail_in = PAGE_SIZE; -+ -+ do { -+ if (zstream->avail_out == 0) { -+ zstream->next_out = compress_next_page(dst); -+ if (IS_ERR(zstream->next_out)) -+ return PTR_ERR(zstream->next_out); -+ -+ zstream->avail_out = PAGE_SIZE; -+ } -+ -+ if (zlib_deflate(zstream, Z_NO_FLUSH) != Z_OK) -+ return -EIO; -+ -+ touch_nmi_watchdog(); -+ } while (zstream->avail_in); -+ -+ /* Fallback to uncompressed if we increase size? */ -+ if (0 && zstream->total_out > zstream->total_in) -+ return -E2BIG; -+ -+ return 0; -+} -+ -+static int compress_flush(struct compress *c, -+ struct drm_i915_error_object *dst) -+{ -+ struct z_stream_s *zstream = &c->zstream; -+ -+ do { -+ switch (zlib_deflate(zstream, Z_FINISH)) { -+ case Z_OK: /* more space requested */ -+ zstream->next_out = compress_next_page(dst); -+ if (IS_ERR(zstream->next_out)) -+ return PTR_ERR(zstream->next_out); -+ -+ zstream->avail_out = PAGE_SIZE; -+ break; -+ -+ case Z_STREAM_END: -+ goto end; -+ -+ default: /* any error */ -+ return -EIO; -+ } -+ } while (1); -+ -+end: -+ memset(zstream->next_out, 0, zstream->avail_out); -+ dst->unused = zstream->avail_out; -+ return 0; -+} -+ -+static void compress_fini(struct compress *c, -+ struct drm_i915_error_object *dst) -+{ -+ struct z_stream_s *zstream = &c->zstream; -+ -+ zlib_deflateEnd(zstream); -+ kfree(zstream->workspace); -+ if (c->tmp) -+ free_page((unsigned long)c->tmp); -+} -+ -+static void err_compression_marker(struct drm_i915_error_state_buf *m) -+{ -+ err_puts(m, ":"); -+} -+ -+#else -+ -+struct compress { -+}; -+ -+static bool compress_init(struct compress *c) -+{ -+ return true; -+} -+ -+static int compress_page(struct compress *c, -+ void *src, -+ struct drm_i915_error_object *dst) -+{ -+ unsigned long page; -+ void *ptr; -+ -+ page = __get_free_page(GFP_ATOMIC | __GFP_NOWARN); -+ if (!page) -+ return -ENOMEM; -+ -+ ptr = (void *)page; -+ if (!i915_memcpy_from_wc(ptr, src, PAGE_SIZE)) -+ memcpy(ptr, src, PAGE_SIZE); -+ dst->pages[dst->page_count++] = ptr; -+ -+ return 0; -+} -+ -+static int compress_flush(struct compress *c, -+ struct drm_i915_error_object *dst) -+{ -+ return 0; -+} -+ -+static void compress_fini(struct compress *c, -+ struct drm_i915_error_object *dst) -+{ -+} -+ -+static void err_compression_marker(struct drm_i915_error_state_buf *m) -+{ -+ err_puts(m, "~"); -+} -+ -+#endif -+ -+static void print_error_buffers(struct drm_i915_error_state_buf *m, -+ const char *name, -+ struct drm_i915_error_buffer *err, -+ int count) -+{ -+ err_printf(m, "%s [%d]:\n", name, count); -+ -+ while (count--) { -+ err_printf(m, " %08x_%08x %8u %02x %02x", -+ upper_32_bits(err->gtt_offset), -+ lower_32_bits(err->gtt_offset), -+ err->size, -+ err->read_domains, -+ err->write_domain); -+ err_puts(m, tiling_flag(err->tiling)); -+ err_puts(m, dirty_flag(err->dirty)); -+ err_puts(m, purgeable_flag(err->purgeable)); -+ err_puts(m, err->userptr ? " userptr" : ""); -+ err_puts(m, i915_cache_level_str(m->i915, err->cache_level)); -+ -+ if (err->name) -+ err_printf(m, " (name: %d)", err->name); -+ if (err->fence_reg != I915_FENCE_REG_NONE) -+ err_printf(m, " (fence: %d)", err->fence_reg); -+ -+ err_puts(m, "\n"); -+ err++; -+ } -+} -+ -+static void error_print_instdone(struct drm_i915_error_state_buf *m, -+ const struct drm_i915_error_engine *ee) -+{ -+ int slice; -+ int subslice; -+ -+ err_printf(m, " INSTDONE: 0x%08x\n", -+ ee->instdone.instdone); -+ -+ if (ee->engine_id != RCS0 || INTEL_GEN(m->i915) <= 3) -+ return; -+ -+ err_printf(m, " SC_INSTDONE: 0x%08x\n", -+ ee->instdone.slice_common); -+ -+ if (INTEL_GEN(m->i915) <= 6) -+ return; -+ -+ for_each_instdone_slice_subslice(m->i915, slice, subslice) -+ err_printf(m, " SAMPLER_INSTDONE[%d][%d]: 0x%08x\n", -+ slice, subslice, -+ ee->instdone.sampler[slice][subslice]); -+ -+ for_each_instdone_slice_subslice(m->i915, slice, subslice) -+ err_printf(m, " ROW_INSTDONE[%d][%d]: 0x%08x\n", -+ slice, subslice, -+ ee->instdone.row[slice][subslice]); -+} -+ -+static void error_print_request(struct drm_i915_error_state_buf *m, -+ const char *prefix, -+ const struct drm_i915_error_request *erq, -+ const unsigned long epoch) -+{ -+ if (!erq->seqno) -+ return; -+ -+ err_printf(m, "%s pid %d, seqno %8x:%08x%s%s, prio %d, emitted %dms, start %08x, head %08x, tail %08x\n", -+ prefix, erq->pid, erq->context, erq->seqno, -+ test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, -+ &erq->flags) ? "!" : "", -+ test_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, -+ &erq->flags) ? "+" : "", -+ erq->sched_attr.priority, -+ jiffies_to_msecs(erq->jiffies - epoch), -+ erq->start, erq->head, erq->tail); -+} -+ -+static void error_print_context(struct drm_i915_error_state_buf *m, -+ const char *header, -+ const struct drm_i915_error_context *ctx) -+{ -+ err_printf(m, "%s%s[%d] hw_id %d, prio %d, guilty %d active %d\n", -+ header, ctx->comm, ctx->pid, ctx->hw_id, -+ ctx->sched_attr.priority, ctx->guilty, ctx->active); -+} -+ -+static void error_print_engine(struct drm_i915_error_state_buf *m, -+ const struct drm_i915_error_engine *ee, -+ const unsigned long epoch) -+{ -+ int n; -+ -+ err_printf(m, "%s command stream:\n", -+ engine_name(m->i915, ee->engine_id)); -+ err_printf(m, " IDLE?: %s\n", yesno(ee->idle)); -+ err_printf(m, " START: 0x%08x\n", ee->start); -+ err_printf(m, " HEAD: 0x%08x [0x%08x]\n", ee->head, ee->rq_head); -+ err_printf(m, " TAIL: 0x%08x [0x%08x, 0x%08x]\n", -+ ee->tail, ee->rq_post, ee->rq_tail); -+ err_printf(m, " CTL: 0x%08x\n", ee->ctl); -+ err_printf(m, " MODE: 0x%08x\n", ee->mode); -+ err_printf(m, " HWS: 0x%08x\n", ee->hws); -+ err_printf(m, " ACTHD: 0x%08x %08x\n", -+ (u32)(ee->acthd>>32), (u32)ee->acthd); -+ err_printf(m, " IPEIR: 0x%08x\n", ee->ipeir); -+ err_printf(m, " IPEHR: 0x%08x\n", ee->ipehr); -+ -+ error_print_instdone(m, ee); -+ -+ if (ee->batchbuffer) { -+ u64 start = ee->batchbuffer->gtt_offset; -+ u64 end = start + ee->batchbuffer->gtt_size; -+ -+ err_printf(m, " batch: [0x%08x_%08x, 0x%08x_%08x]\n", -+ upper_32_bits(start), lower_32_bits(start), -+ upper_32_bits(end), lower_32_bits(end)); -+ } -+ if (INTEL_GEN(m->i915) >= 4) { -+ err_printf(m, " BBADDR: 0x%08x_%08x\n", -+ (u32)(ee->bbaddr>>32), (u32)ee->bbaddr); -+ err_printf(m, " BB_STATE: 0x%08x\n", ee->bbstate); -+ err_printf(m, " INSTPS: 0x%08x\n", ee->instps); -+ } -+ err_printf(m, " INSTPM: 0x%08x\n", ee->instpm); -+ err_printf(m, " FADDR: 0x%08x %08x\n", upper_32_bits(ee->faddr), -+ lower_32_bits(ee->faddr)); -+ if (INTEL_GEN(m->i915) >= 6) { -+ err_printf(m, " RC PSMI: 0x%08x\n", ee->rc_psmi); -+ err_printf(m, " FAULT_REG: 0x%08x\n", ee->fault_reg); -+ } -+ if (HAS_PPGTT(m->i915)) { -+ err_printf(m, " GFX_MODE: 0x%08x\n", ee->vm_info.gfx_mode); -+ -+ if (INTEL_GEN(m->i915) >= 8) { -+ int i; -+ for (i = 0; i < 4; i++) -+ err_printf(m, " PDP%d: 0x%016llx\n", -+ i, ee->vm_info.pdp[i]); -+ } else { -+ err_printf(m, " PP_DIR_BASE: 0x%08x\n", -+ ee->vm_info.pp_dir_base); -+ } -+ } -+ err_printf(m, " ring->head: 0x%08x\n", ee->cpu_ring_head); -+ err_printf(m, " ring->tail: 0x%08x\n", ee->cpu_ring_tail); -+ err_printf(m, " hangcheck timestamp: %dms (%lu%s)\n", -+ jiffies_to_msecs(ee->hangcheck_timestamp - epoch), -+ ee->hangcheck_timestamp, -+ ee->hangcheck_timestamp == epoch ? "; epoch" : ""); -+ err_printf(m, " engine reset count: %u\n", ee->reset_count); -+ -+ for (n = 0; n < ee->num_ports; n++) { -+ err_printf(m, " ELSP[%d]:", n); -+ error_print_request(m, " ", &ee->execlist[n], epoch); -+ } -+ -+ error_print_context(m, " Active context: ", &ee->context); -+} -+ -+void i915_error_printf(struct drm_i915_error_state_buf *e, const char *f, ...) -+{ -+ va_list args; -+ -+ va_start(args, f); -+ i915_error_vprintf(e, f, args); -+ va_end(args); -+} -+ -+static void print_error_obj(struct drm_i915_error_state_buf *m, -+ struct intel_engine_cs *engine, -+ const char *name, -+ struct drm_i915_error_object *obj) -+{ -+ char out[ASCII85_BUFSZ]; -+ int page; -+ -+ if (!obj) -+ return; -+ -+ if (name) { -+ err_printf(m, "%s --- %s = 0x%08x %08x\n", -+ engine ? engine->name : "global", name, -+ upper_32_bits(obj->gtt_offset), -+ lower_32_bits(obj->gtt_offset)); -+ } -+ -+ err_compression_marker(m); -+ for (page = 0; page < obj->page_count; page++) { -+ int i, len; -+ -+ len = PAGE_SIZE; -+ if (page == obj->page_count - 1) -+ len -= obj->unused; -+ len = ascii85_encode_len(len); -+ -+ for (i = 0; i < len; i++) -+ err_puts(m, ascii85_encode(obj->pages[page][i], out)); -+ } -+ err_puts(m, "\n"); -+} -+ -+static void err_print_capabilities(struct drm_i915_error_state_buf *m, -+ const struct intel_device_info *info, -+ const struct intel_runtime_info *runtime, -+ const struct intel_driver_caps *caps) -+{ -+ struct drm_printer p = i915_error_printer(m); -+ -+ intel_device_info_dump_flags(info, &p); -+ intel_driver_caps_print(caps, &p); -+ intel_device_info_dump_topology(&runtime->sseu, &p); -+} -+ -+static void err_print_params(struct drm_i915_error_state_buf *m, -+ const struct i915_params *params) -+{ -+ struct drm_printer p = i915_error_printer(m); -+ -+ i915_params_dump(params, &p); -+} -+ -+static void err_print_pciid(struct drm_i915_error_state_buf *m, -+ struct drm_i915_private *i915) -+{ -+ struct pci_dev *pdev = i915->drm.pdev; -+ -+ err_printf(m, "PCI ID: 0x%04x\n", pdev->device); -+ err_printf(m, "PCI Revision: 0x%02x\n", pdev->revision); -+ err_printf(m, "PCI Subsystem: %04x:%04x\n", -+ pdev->subsystem_vendor, -+ pdev->subsystem_device); -+} -+ -+static void err_print_uc(struct drm_i915_error_state_buf *m, -+ const struct i915_error_uc *error_uc) -+{ -+ struct drm_printer p = i915_error_printer(m); -+ const struct i915_gpu_state *error = -+ container_of(error_uc, typeof(*error), uc); -+ -+ if (!error->device_info.has_guc) -+ return; -+ -+ intel_uc_fw_dump(&error_uc->guc_fw, &p); -+ intel_uc_fw_dump(&error_uc->huc_fw, &p); -+ print_error_obj(m, NULL, "GuC log buffer", error_uc->guc_log); -+} -+ -+static void err_free_sgl(struct scatterlist *sgl) -+{ -+ while (sgl) { -+ struct scatterlist *sg; -+ -+ for (sg = sgl; !sg_is_chain(sg); sg++) { -+ kfree(sg_virt(sg)); -+ if (sg_is_last(sg)) -+ break; -+ } -+ -+ sg = sg_is_last(sg) ? NULL : sg_chain_ptr(sg); -+ free_page((unsigned long)sgl); -+ sgl = sg; -+ } -+} -+ -+static void __err_print_to_sgl(struct drm_i915_error_state_buf *m, -+ struct i915_gpu_state *error) -+{ -+ struct drm_i915_error_object *obj; -+ struct timespec64 ts; -+ int i, j; -+ -+ if (*error->error_msg) -+ err_printf(m, "%s\n", error->error_msg); -+ err_printf(m, "Kernel: %s %s\n", -+ init_utsname()->release, -+ init_utsname()->machine); -+ ts = ktime_to_timespec64(error->time); -+ err_printf(m, "Time: %lld s %ld us\n", -+ (s64)ts.tv_sec, ts.tv_nsec / NSEC_PER_USEC); -+ ts = ktime_to_timespec64(error->boottime); -+ err_printf(m, "Boottime: %lld s %ld us\n", -+ (s64)ts.tv_sec, ts.tv_nsec / NSEC_PER_USEC); -+ ts = ktime_to_timespec64(error->uptime); -+ err_printf(m, "Uptime: %lld s %ld us\n", -+ (s64)ts.tv_sec, ts.tv_nsec / NSEC_PER_USEC); -+ err_printf(m, "Epoch: %lu jiffies (%u HZ)\n", error->epoch, HZ); -+ err_printf(m, "Capture: %lu jiffies; %d ms ago, %d ms after epoch\n", -+ error->capture, -+ jiffies_to_msecs(jiffies - error->capture), -+ jiffies_to_msecs(error->capture - error->epoch)); -+ -+ for (i = 0; i < ARRAY_SIZE(error->engine); i++) { -+ if (!error->engine[i].context.pid) -+ continue; -+ -+ err_printf(m, "Active process (on ring %s): %s [%d]\n", -+ engine_name(m->i915, i), -+ error->engine[i].context.comm, -+ error->engine[i].context.pid); -+ } -+ err_printf(m, "Reset count: %u\n", error->reset_count); -+ err_printf(m, "Suspend count: %u\n", error->suspend_count); -+ err_printf(m, "Platform: %s\n", intel_platform_name(error->device_info.platform)); -+ err_printf(m, "Subplatform: 0x%x\n", -+ intel_subplatform(&error->runtime_info, -+ error->device_info.platform)); -+ err_print_pciid(m, m->i915); -+ -+ err_printf(m, "IOMMU enabled?: %d\n", error->iommu); -+ -+ if (HAS_CSR(m->i915)) { -+ struct intel_csr *csr = &m->i915->csr; -+ -+ err_printf(m, "DMC loaded: %s\n", -+ yesno(csr->dmc_payload != NULL)); -+ err_printf(m, "DMC fw version: %d.%d\n", -+ CSR_VERSION_MAJOR(csr->version), -+ CSR_VERSION_MINOR(csr->version)); -+ } -+ -+ err_printf(m, "GT awake: %s\n", yesno(error->awake)); -+ err_printf(m, "RPM wakelock: %s\n", yesno(error->wakelock)); -+ err_printf(m, "PM suspended: %s\n", yesno(error->suspended)); -+ err_printf(m, "EIR: 0x%08x\n", error->eir); -+ err_printf(m, "IER: 0x%08x\n", error->ier); -+ for (i = 0; i < error->ngtier; i++) -+ err_printf(m, "GTIER[%d]: 0x%08x\n", i, error->gtier[i]); -+ err_printf(m, "PGTBL_ER: 0x%08x\n", error->pgtbl_er); -+ err_printf(m, "FORCEWAKE: 0x%08x\n", error->forcewake); -+ err_printf(m, "DERRMR: 0x%08x\n", error->derrmr); -+ err_printf(m, "CCID: 0x%08x\n", error->ccid); -+ -+ for (i = 0; i < error->nfence; i++) -+ err_printf(m, " fence[%d] = %08llx\n", i, error->fence[i]); -+ -+ if (INTEL_GEN(m->i915) >= 6) { -+ err_printf(m, "ERROR: 0x%08x\n", error->error); -+ -+ if (INTEL_GEN(m->i915) >= 8) -+ err_printf(m, "FAULT_TLB_DATA: 0x%08x 0x%08x\n", -+ error->fault_data1, error->fault_data0); -+ -+ err_printf(m, "DONE_REG: 0x%08x\n", error->done_reg); -+ } -+ -+ if (IS_GEN(m->i915, 7)) -+ err_printf(m, "ERR_INT: 0x%08x\n", error->err_int); -+ -+ for (i = 0; i < ARRAY_SIZE(error->engine); i++) { -+ if (error->engine[i].engine_id != -1) -+ error_print_engine(m, &error->engine[i], error->epoch); -+ } -+ -+ for (i = 0; i < ARRAY_SIZE(error->active_vm); i++) { -+ char buf[128]; -+ int len, first = 1; -+ -+ if (!error->active_vm[i]) -+ break; -+ -+ len = scnprintf(buf, sizeof(buf), "Active ("); -+ for (j = 0; j < ARRAY_SIZE(error->engine); j++) { -+ if (error->engine[j].vm != error->active_vm[i]) -+ continue; -+ -+ len += scnprintf(buf + len, sizeof(buf), "%s%s", -+ first ? "" : ", ", -+ m->i915->engine[j]->name); -+ first = 0; -+ } -+ scnprintf(buf + len, sizeof(buf), ")"); -+ print_error_buffers(m, buf, -+ error->active_bo[i], -+ error->active_bo_count[i]); -+ } -+ -+ print_error_buffers(m, "Pinned (global)", -+ error->pinned_bo, -+ error->pinned_bo_count); -+ -+ for (i = 0; i < ARRAY_SIZE(error->engine); i++) { -+ const struct drm_i915_error_engine *ee = &error->engine[i]; -+ -+ obj = ee->batchbuffer; -+ if (obj) { -+ err_puts(m, m->i915->engine[i]->name); -+ if (ee->context.pid) -+ err_printf(m, " (submitted by %s [%d])", -+ ee->context.comm, -+ ee->context.pid); -+ err_printf(m, " --- gtt_offset = 0x%08x %08x\n", -+ upper_32_bits(obj->gtt_offset), -+ lower_32_bits(obj->gtt_offset)); -+ print_error_obj(m, m->i915->engine[i], NULL, obj); -+ } -+ -+ for (j = 0; j < ee->user_bo_count; j++) -+ print_error_obj(m, m->i915->engine[i], -+ "user", ee->user_bo[j]); -+ -+ if (ee->num_requests) { -+ err_printf(m, "%s --- %d requests\n", -+ m->i915->engine[i]->name, -+ ee->num_requests); -+ for (j = 0; j < ee->num_requests; j++) -+ error_print_request(m, " ", -+ &ee->requests[j], -+ error->epoch); -+ } -+ -+ print_error_obj(m, m->i915->engine[i], -+ "ringbuffer", ee->ringbuffer); -+ -+ print_error_obj(m, m->i915->engine[i], -+ "HW Status", ee->hws_page); -+ -+ print_error_obj(m, m->i915->engine[i], -+ "HW context", ee->ctx); -+ -+ print_error_obj(m, m->i915->engine[i], -+ "WA context", ee->wa_ctx); -+ -+ print_error_obj(m, m->i915->engine[i], -+ "WA batchbuffer", ee->wa_batchbuffer); -+ -+ print_error_obj(m, m->i915->engine[i], -+ "NULL context", ee->default_state); -+ } -+ -+ if (error->overlay) -+ intel_overlay_print_error_state(m, error->overlay); -+ -+ if (error->display) -+ intel_display_print_error_state(m, error->display); -+ -+ err_print_capabilities(m, &error->device_info, &error->runtime_info, -+ &error->driver_caps); -+ err_print_params(m, &error->params); -+ err_print_uc(m, &error->uc); -+} -+ -+static int err_print_to_sgl(struct i915_gpu_state *error) -+{ -+ struct drm_i915_error_state_buf m; -+ -+ if (IS_ERR(error)) -+ return PTR_ERR(error); -+ -+ if (READ_ONCE(error->sgl)) -+ return 0; -+ -+ memset(&m, 0, sizeof(m)); -+ m.i915 = error->i915; -+ -+ __err_print_to_sgl(&m, error); -+ -+ if (m.buf) { -+ __sg_set_buf(m.cur++, m.buf, m.bytes, m.iter); -+ m.bytes = 0; -+ m.buf = NULL; -+ } -+ if (m.cur) { -+ GEM_BUG_ON(m.end < m.cur); -+ sg_mark_end(m.cur - 1); -+ } -+ GEM_BUG_ON(m.sgl && !m.cur); -+ -+ if (m.err) { -+ err_free_sgl(m.sgl); -+ return m.err; -+ } -+ -+ if (cmpxchg(&error->sgl, NULL, m.sgl)) -+ err_free_sgl(m.sgl); -+ -+ return 0; -+} -+ -+ssize_t i915_gpu_state_copy_to_buffer(struct i915_gpu_state *error, -+ char *buf, loff_t off, size_t rem) -+{ -+ struct scatterlist *sg; -+ size_t count; -+ loff_t pos; -+ int err; -+ -+ if (!error || !rem) -+ return 0; -+ -+ err = err_print_to_sgl(error); -+ if (err) -+ return err; -+ -+ sg = READ_ONCE(error->fit); -+ if (!sg || off < sg->dma_address) -+ sg = error->sgl; -+ if (!sg) -+ return 0; -+ -+ pos = sg->dma_address; -+ count = 0; -+ do { -+ size_t len, start; -+ -+ if (sg_is_chain(sg)) { -+ sg = sg_chain_ptr(sg); -+ GEM_BUG_ON(sg_is_chain(sg)); -+ } -+ -+ len = sg->length; -+ if (pos + len <= off) { -+ pos += len; -+ continue; -+ } -+ -+ start = sg->offset; -+ if (pos < off) { -+ GEM_BUG_ON(off - pos > len); -+ len -= off - pos; -+ start += off - pos; -+ pos = off; -+ } -+ -+ len = min(len, rem); -+ GEM_BUG_ON(!len || len > sg->length); -+ -+ memcpy(buf, page_address(sg_page(sg)) + start, len); -+ -+ count += len; -+ pos += len; -+ -+ buf += len; -+ rem -= len; -+ if (!rem) { -+ WRITE_ONCE(error->fit, sg); -+ break; -+ } -+ } while (!sg_is_last(sg++)); -+ -+ return count; -+} -+ -+static void i915_error_object_free(struct drm_i915_error_object *obj) -+{ -+ int page; -+ -+ if (obj == NULL) -+ return; -+ -+ for (page = 0; page < obj->page_count; page++) -+ free_page((unsigned long)obj->pages[page]); -+ -+ kfree(obj); -+} -+ -+ -+static void cleanup_params(struct i915_gpu_state *error) -+{ -+ i915_params_free(&error->params); -+} -+ -+static void cleanup_uc_state(struct i915_gpu_state *error) -+{ -+ struct i915_error_uc *error_uc = &error->uc; -+ -+ kfree(error_uc->guc_fw.path); -+ kfree(error_uc->huc_fw.path); -+ i915_error_object_free(error_uc->guc_log); -+} -+ -+void __i915_gpu_state_free(struct kref *error_ref) -+{ -+ struct i915_gpu_state *error = -+ container_of(error_ref, typeof(*error), ref); -+ long i, j; -+ -+ for (i = 0; i < ARRAY_SIZE(error->engine); i++) { -+ struct drm_i915_error_engine *ee = &error->engine[i]; -+ -+ for (j = 0; j < ee->user_bo_count; j++) -+ i915_error_object_free(ee->user_bo[j]); -+ kfree(ee->user_bo); -+ -+ i915_error_object_free(ee->batchbuffer); -+ i915_error_object_free(ee->wa_batchbuffer); -+ i915_error_object_free(ee->ringbuffer); -+ i915_error_object_free(ee->hws_page); -+ i915_error_object_free(ee->ctx); -+ i915_error_object_free(ee->wa_ctx); -+ -+ kfree(ee->requests); -+ } -+ -+ for (i = 0; i < ARRAY_SIZE(error->active_bo); i++) -+ kfree(error->active_bo[i]); -+ kfree(error->pinned_bo); -+ -+ kfree(error->overlay); -+ kfree(error->display); -+ -+ cleanup_params(error); -+ cleanup_uc_state(error); -+ -+ err_free_sgl(error->sgl); -+ kfree(error); -+} -+ -+static struct drm_i915_error_object * -+i915_error_object_create(struct drm_i915_private *i915, -+ struct i915_vma *vma) -+{ -+ struct i915_ggtt *ggtt = &i915->ggtt; -+ const u64 slot = ggtt->error_capture.start; -+ struct drm_i915_error_object *dst; -+ struct compress compress; -+ unsigned long num_pages; -+ struct sgt_iter iter; -+ dma_addr_t dma; -+ int ret; -+ -+ if (!vma || !vma->pages) -+ return NULL; -+ -+ num_pages = min_t(u64, vma->size, vma->obj->base.size) >> PAGE_SHIFT; -+ num_pages = DIV_ROUND_UP(10 * num_pages, 8); /* worstcase zlib growth */ -+ dst = kmalloc(sizeof(*dst) + num_pages * sizeof(u32 *), -+ GFP_ATOMIC | __GFP_NOWARN); -+ if (!dst) -+ return NULL; -+ -+ dst->gtt_offset = vma->node.start; -+ dst->gtt_size = vma->node.size; -+ dst->num_pages = num_pages; -+ dst->page_count = 0; -+ dst->unused = 0; -+ -+ if (!compress_init(&compress)) { -+ kfree(dst); -+ return NULL; -+ } -+ -+ ret = -EINVAL; -+ for_each_sgt_dma(dma, iter, vma->pages) { -+ void __iomem *s; -+ -+ ggtt->vm.insert_page(&ggtt->vm, dma, slot, I915_CACHE_NONE, 0); -+ -+ s = io_mapping_map_atomic_wc(&ggtt->iomap, slot); -+ ret = compress_page(&compress, (void __force *)s, dst); -+ io_mapping_unmap_atomic(s); -+ if (ret) -+ break; -+ } -+ -+ if (ret || compress_flush(&compress, dst)) { -+ while (dst->page_count--) -+ free_page((unsigned long)dst->pages[dst->page_count]); -+ kfree(dst); -+ dst = NULL; -+ } -+ -+ compress_fini(&compress, dst); -+ return dst; -+} -+ -+static void capture_bo(struct drm_i915_error_buffer *err, -+ struct i915_vma *vma) -+{ -+ struct drm_i915_gem_object *obj = vma->obj; -+ -+ err->size = obj->base.size; -+ err->name = obj->base.name; -+ -+ err->gtt_offset = vma->node.start; -+ err->read_domains = obj->read_domains; -+ err->write_domain = obj->write_domain; -+ err->fence_reg = vma->fence ? vma->fence->id : -1; -+ err->tiling = i915_gem_object_get_tiling(obj); -+ err->dirty = obj->mm.dirty; -+ err->purgeable = obj->mm.madv != I915_MADV_WILLNEED; -+ err->userptr = obj->userptr.mm != NULL; -+ err->cache_level = obj->cache_level; -+} -+ -+static u32 capture_error_bo(struct drm_i915_error_buffer *err, -+ int count, struct list_head *head, -+ unsigned int flags) -+#define ACTIVE_ONLY BIT(0) -+#define PINNED_ONLY BIT(1) -+{ -+ struct i915_vma *vma; -+ int i = 0; -+ -+ list_for_each_entry(vma, head, vm_link) { -+ if (!vma->obj) -+ continue; -+ -+ if (flags & ACTIVE_ONLY && !i915_vma_is_active(vma)) -+ continue; -+ -+ if (flags & PINNED_ONLY && !i915_vma_is_pinned(vma)) -+ continue; -+ -+ capture_bo(err++, vma); -+ if (++i == count) -+ break; -+ } -+ -+ return i; -+} -+ -+/* -+ * Generate a semi-unique error code. The code is not meant to have meaning, The -+ * code's only purpose is to try to prevent false duplicated bug reports by -+ * grossly estimating a GPU error state. -+ * -+ * TODO Ideally, hashing the batchbuffer would be a very nice way to determine -+ * the hang if we could strip the GTT offset information from it. -+ * -+ * It's only a small step better than a random number in its current form. -+ */ -+static u32 i915_error_generate_code(struct i915_gpu_state *error, -+ intel_engine_mask_t engine_mask) -+{ -+ /* -+ * IPEHR would be an ideal way to detect errors, as it's the gross -+ * measure of "the command that hung." However, has some very common -+ * synchronization commands which almost always appear in the case -+ * strictly a client bug. Use instdone to differentiate those some. -+ */ -+ if (engine_mask) { -+ struct drm_i915_error_engine *ee = -+ &error->engine[ffs(engine_mask)]; -+ -+ return ee->ipehr ^ ee->instdone.instdone; -+ } -+ -+ return 0; -+} -+ -+static void gem_record_fences(struct i915_gpu_state *error) -+{ -+ struct drm_i915_private *dev_priv = error->i915; -+ int i; -+ -+ if (INTEL_GEN(dev_priv) >= 6) { -+ for (i = 0; i < dev_priv->num_fence_regs; i++) -+ error->fence[i] = I915_READ64(FENCE_REG_GEN6_LO(i)); -+ } else if (INTEL_GEN(dev_priv) >= 4) { -+ for (i = 0; i < dev_priv->num_fence_regs; i++) -+ error->fence[i] = I915_READ64(FENCE_REG_965_LO(i)); -+ } else { -+ for (i = 0; i < dev_priv->num_fence_regs; i++) -+ error->fence[i] = I915_READ(FENCE_REG(i)); -+ } -+ error->nfence = i; -+} -+ -+static void error_record_engine_registers(struct i915_gpu_state *error, -+ struct intel_engine_cs *engine, -+ struct drm_i915_error_engine *ee) -+{ -+ struct drm_i915_private *dev_priv = engine->i915; -+ -+ if (INTEL_GEN(dev_priv) >= 6) { -+ ee->rc_psmi = ENGINE_READ(engine, RING_PSMI_CTL); -+ if (INTEL_GEN(dev_priv) >= 8) -+ ee->fault_reg = I915_READ(GEN8_RING_FAULT_REG); -+ else -+ ee->fault_reg = I915_READ(RING_FAULT_REG(engine)); -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 4) { -+ ee->faddr = ENGINE_READ(engine, RING_DMA_FADD); -+ ee->ipeir = ENGINE_READ(engine, RING_IPEIR); -+ ee->ipehr = ENGINE_READ(engine, RING_IPEHR); -+ ee->instps = ENGINE_READ(engine, RING_INSTPS); -+ ee->bbaddr = ENGINE_READ(engine, RING_BBADDR); -+ if (INTEL_GEN(dev_priv) >= 8) { -+ ee->faddr |= (u64)ENGINE_READ(engine, RING_DMA_FADD_UDW) << 32; -+ ee->bbaddr |= (u64)ENGINE_READ(engine, RING_BBADDR_UDW) << 32; -+ } -+ ee->bbstate = ENGINE_READ(engine, RING_BBSTATE); -+ } else { -+ ee->faddr = ENGINE_READ(engine, DMA_FADD_I8XX); -+ ee->ipeir = ENGINE_READ(engine, IPEIR); -+ ee->ipehr = ENGINE_READ(engine, IPEHR); -+ } -+ -+ intel_engine_get_instdone(engine, &ee->instdone); -+ -+ ee->instpm = ENGINE_READ(engine, RING_INSTPM); -+ ee->acthd = intel_engine_get_active_head(engine); -+ ee->start = ENGINE_READ(engine, RING_START); -+ ee->head = ENGINE_READ(engine, RING_HEAD); -+ ee->tail = ENGINE_READ(engine, RING_TAIL); -+ ee->ctl = ENGINE_READ(engine, RING_CTL); -+ if (INTEL_GEN(dev_priv) > 2) -+ ee->mode = ENGINE_READ(engine, RING_MI_MODE); -+ -+ if (!HWS_NEEDS_PHYSICAL(dev_priv)) { -+ i915_reg_t mmio; -+ -+ if (IS_GEN(dev_priv, 7)) { -+ switch (engine->id) { -+ default: -+ MISSING_CASE(engine->id); -+ case RCS0: -+ mmio = RENDER_HWS_PGA_GEN7; -+ break; -+ case BCS0: -+ mmio = BLT_HWS_PGA_GEN7; -+ break; -+ case VCS0: -+ mmio = BSD_HWS_PGA_GEN7; -+ break; -+ case VECS0: -+ mmio = VEBOX_HWS_PGA_GEN7; -+ break; -+ } -+ } else if (IS_GEN(engine->i915, 6)) { -+ mmio = RING_HWS_PGA_GEN6(engine->mmio_base); -+ } else { -+ /* XXX: gen8 returns to sanity */ -+ mmio = RING_HWS_PGA(engine->mmio_base); -+ } -+ -+ ee->hws = I915_READ(mmio); -+ } -+ -+ ee->idle = intel_engine_is_idle(engine); -+ if (!ee->idle) -+ ee->hangcheck_timestamp = engine->hangcheck.action_timestamp; -+ ee->reset_count = i915_reset_engine_count(&dev_priv->gpu_error, -+ engine); -+ -+ if (HAS_PPGTT(dev_priv)) { -+ int i; -+ -+ ee->vm_info.gfx_mode = I915_READ(RING_MODE_GEN7(engine)); -+ -+ if (IS_GEN(dev_priv, 6)) { -+ ee->vm_info.pp_dir_base = -+ ENGINE_READ(engine, RING_PP_DIR_BASE_READ); -+ } else if (IS_GEN(dev_priv, 7)) { -+ ee->vm_info.pp_dir_base = -+ ENGINE_READ(engine, RING_PP_DIR_BASE); -+ } else if (INTEL_GEN(dev_priv) >= 8) { -+ u32 base = engine->mmio_base; -+ -+ for (i = 0; i < 4; i++) { -+ ee->vm_info.pdp[i] = -+ I915_READ(GEN8_RING_PDP_UDW(base, i)); -+ ee->vm_info.pdp[i] <<= 32; -+ ee->vm_info.pdp[i] |= -+ I915_READ(GEN8_RING_PDP_LDW(base, i)); -+ } -+ } -+ } -+} -+ -+static void record_request(struct i915_request *request, -+ struct drm_i915_error_request *erq) -+{ -+ struct i915_gem_context *ctx = request->gem_context; -+ -+ erq->flags = request->fence.flags; -+ erq->context = request->fence.context; -+ erq->seqno = request->fence.seqno; -+ erq->sched_attr = request->sched.attr; -+ erq->jiffies = request->emitted_jiffies; -+ erq->start = i915_ggtt_offset(request->ring->vma); -+ erq->head = request->head; -+ erq->tail = request->tail; -+ -+ rcu_read_lock(); -+ erq->pid = ctx->pid ? pid_nr(ctx->pid) : 0; -+ rcu_read_unlock(); -+} -+ -+static void engine_record_requests(struct intel_engine_cs *engine, -+ struct i915_request *first, -+ struct drm_i915_error_engine *ee) -+{ -+ struct i915_request *request; -+ int count; -+ -+ count = 0; -+ request = first; -+ list_for_each_entry_from(request, &engine->timeline.requests, link) -+ count++; -+ if (!count) -+ return; -+ -+ ee->requests = kcalloc(count, sizeof(*ee->requests), GFP_ATOMIC); -+ if (!ee->requests) -+ return; -+ -+ ee->num_requests = count; -+ -+ count = 0; -+ request = first; -+ list_for_each_entry_from(request, &engine->timeline.requests, link) { -+ if (count >= ee->num_requests) { -+ /* -+ * If the ring request list was changed in -+ * between the point where the error request -+ * list was created and dimensioned and this -+ * point then just exit early to avoid crashes. -+ * -+ * We don't need to communicate that the -+ * request list changed state during error -+ * state capture and that the error state is -+ * slightly incorrect as a consequence since we -+ * are typically only interested in the request -+ * list state at the point of error state -+ * capture, not in any changes happening during -+ * the capture. -+ */ -+ break; -+ } -+ -+ record_request(request, &ee->requests[count++]); -+ } -+ ee->num_requests = count; -+} -+ -+static void error_record_engine_execlists(struct intel_engine_cs *engine, -+ struct drm_i915_error_engine *ee) -+{ -+ const struct intel_engine_execlists * const execlists = &engine->execlists; -+ unsigned int n; -+ -+ for (n = 0; n < execlists_num_ports(execlists); n++) { -+ struct i915_request *rq = port_request(&execlists->port[n]); -+ -+ if (!rq) -+ break; -+ -+ record_request(rq, &ee->execlist[n]); -+ } -+ -+ ee->num_ports = n; -+} -+ -+static void record_context(struct drm_i915_error_context *e, -+ struct i915_gem_context *ctx) -+{ -+ if (ctx->pid) { -+ struct task_struct *task; -+ -+ rcu_read_lock(); -+ task = pid_task(ctx->pid, PIDTYPE_PID); -+ if (task) { -+ strcpy(e->comm, task->comm); -+ e->pid = task->pid; -+ } -+ rcu_read_unlock(); -+ } -+ -+ e->hw_id = ctx->hw_id; -+ e->sched_attr = ctx->sched; -+ e->guilty = atomic_read(&ctx->guilty_count); -+ e->active = atomic_read(&ctx->active_count); -+} -+ -+static void request_record_user_bo(struct i915_request *request, -+ struct drm_i915_error_engine *ee) -+{ -+ struct i915_capture_list *c; -+ struct drm_i915_error_object **bo; -+ long count, max; -+ -+ max = 0; -+ for (c = request->capture_list; c; c = c->next) -+ max++; -+ if (!max) -+ return; -+ -+ bo = kmalloc_array(max, sizeof(*bo), GFP_ATOMIC); -+ if (!bo) { -+ /* If we can't capture everything, try to capture something. */ -+ max = min_t(long, max, PAGE_SIZE / sizeof(*bo)); -+ bo = kmalloc_array(max, sizeof(*bo), GFP_ATOMIC); -+ } -+ if (!bo) -+ return; -+ -+ count = 0; -+ for (c = request->capture_list; c; c = c->next) { -+ bo[count] = i915_error_object_create(request->i915, c->vma); -+ if (!bo[count]) -+ break; -+ if (++count == max) -+ break; -+ } -+ -+ ee->user_bo = bo; -+ ee->user_bo_count = count; -+} -+ -+static struct drm_i915_error_object * -+capture_object(struct drm_i915_private *dev_priv, -+ struct drm_i915_gem_object *obj) -+{ -+ if (obj && i915_gem_object_has_pages(obj)) { -+ struct i915_vma fake = { -+ .node = { .start = U64_MAX, .size = obj->base.size }, -+ .size = obj->base.size, -+ .pages = obj->mm.pages, -+ .obj = obj, -+ }; -+ -+ return i915_error_object_create(dev_priv, &fake); -+ } else { -+ return NULL; -+ } -+} -+ -+static void gem_record_rings(struct i915_gpu_state *error) -+{ -+ struct drm_i915_private *i915 = error->i915; -+ struct i915_ggtt *ggtt = &i915->ggtt; -+ int i; -+ -+ for (i = 0; i < I915_NUM_ENGINES; i++) { -+ struct intel_engine_cs *engine = i915->engine[i]; -+ struct drm_i915_error_engine *ee = &error->engine[i]; -+ struct i915_request *request; -+ -+ ee->engine_id = -1; -+ -+ if (!engine) -+ continue; -+ -+ ee->engine_id = i; -+ -+ error_record_engine_registers(error, engine, ee); -+ error_record_engine_execlists(engine, ee); -+ -+ request = intel_engine_find_active_request(engine); -+ if (request) { -+ struct i915_gem_context *ctx = request->gem_context; -+ struct intel_ring *ring; -+ -+ ee->vm = ctx->ppgtt ? &ctx->ppgtt->vm : &ggtt->vm; -+ -+ record_context(&ee->context, ctx); -+ -+ /* We need to copy these to an anonymous buffer -+ * as the simplest method to avoid being overwritten -+ * by userspace. -+ */ -+ ee->batchbuffer = -+ i915_error_object_create(i915, request->batch); -+ -+ if (HAS_BROKEN_CS_TLB(i915)) -+ ee->wa_batchbuffer = -+ i915_error_object_create(i915, -+ i915->gt.scratch); -+ request_record_user_bo(request, ee); -+ -+ ee->ctx = -+ i915_error_object_create(i915, -+ request->hw_context->state); -+ -+ error->simulated |= -+ i915_gem_context_no_error_capture(ctx); -+ -+ ee->rq_head = request->head; -+ ee->rq_post = request->postfix; -+ ee->rq_tail = request->tail; -+ -+ ring = request->ring; -+ ee->cpu_ring_head = ring->head; -+ ee->cpu_ring_tail = ring->tail; -+ ee->ringbuffer = -+ i915_error_object_create(i915, ring->vma); -+ -+ engine_record_requests(engine, request, ee); -+ } -+ -+ ee->hws_page = -+ i915_error_object_create(i915, -+ engine->status_page.vma); -+ -+ ee->wa_ctx = i915_error_object_create(i915, engine->wa_ctx.vma); -+ -+ ee->default_state = capture_object(i915, engine->default_state); -+ } -+} -+ -+static void gem_capture_vm(struct i915_gpu_state *error, -+ struct i915_address_space *vm, -+ int idx) -+{ -+ struct drm_i915_error_buffer *active_bo; -+ struct i915_vma *vma; -+ int count; -+ -+ count = 0; -+ list_for_each_entry(vma, &vm->bound_list, vm_link) -+ if (i915_vma_is_active(vma)) -+ count++; -+ -+ active_bo = NULL; -+ if (count) -+ active_bo = kcalloc(count, sizeof(*active_bo), GFP_ATOMIC); -+ if (active_bo) -+ count = capture_error_bo(active_bo, -+ count, &vm->bound_list, -+ ACTIVE_ONLY); -+ else -+ count = 0; -+ -+ error->active_vm[idx] = vm; -+ error->active_bo[idx] = active_bo; -+ error->active_bo_count[idx] = count; -+} -+ -+static void capture_active_buffers(struct i915_gpu_state *error) -+{ -+ int cnt = 0, i, j; -+ -+ BUILD_BUG_ON(ARRAY_SIZE(error->engine) > ARRAY_SIZE(error->active_bo)); -+ BUILD_BUG_ON(ARRAY_SIZE(error->active_bo) != ARRAY_SIZE(error->active_vm)); -+ BUILD_BUG_ON(ARRAY_SIZE(error->active_bo) != ARRAY_SIZE(error->active_bo_count)); -+ -+ /* Scan each engine looking for unique active contexts/vm */ -+ for (i = 0; i < ARRAY_SIZE(error->engine); i++) { -+ struct drm_i915_error_engine *ee = &error->engine[i]; -+ bool found; -+ -+ if (!ee->vm) -+ continue; -+ -+ found = false; -+ for (j = 0; j < i && !found; j++) -+ found = error->engine[j].vm == ee->vm; -+ if (!found) -+ gem_capture_vm(error, ee->vm, cnt++); -+ } -+} -+ -+static void capture_pinned_buffers(struct i915_gpu_state *error) -+{ -+ struct i915_address_space *vm = &error->i915->ggtt.vm; -+ struct drm_i915_error_buffer *bo; -+ struct i915_vma *vma; -+ int count; -+ -+ count = 0; -+ list_for_each_entry(vma, &vm->bound_list, vm_link) -+ count++; -+ -+ bo = NULL; -+ if (count) -+ bo = kcalloc(count, sizeof(*bo), GFP_ATOMIC); -+ if (!bo) -+ return; -+ -+ error->pinned_bo_count = -+ capture_error_bo(bo, count, &vm->bound_list, PINNED_ONLY); -+ error->pinned_bo = bo; -+} -+ -+static void capture_uc_state(struct i915_gpu_state *error) -+{ -+ struct drm_i915_private *i915 = error->i915; -+ struct i915_error_uc *error_uc = &error->uc; -+ -+ /* Capturing uC state won't be useful if there is no GuC */ -+ if (!error->device_info.has_guc) -+ return; -+ -+ error_uc->guc_fw = i915->guc.fw; -+ error_uc->huc_fw = i915->huc.fw; -+ -+ /* Non-default firmware paths will be specified by the modparam. -+ * As modparams are generally accesible from the userspace make -+ * explicit copies of the firmware paths. -+ */ -+ error_uc->guc_fw.path = kstrdup(i915->guc.fw.path, GFP_ATOMIC); -+ error_uc->huc_fw.path = kstrdup(i915->huc.fw.path, GFP_ATOMIC); -+ error_uc->guc_log = i915_error_object_create(i915, i915->guc.log.vma); -+} -+ -+/* Capture all registers which don't fit into another category. */ -+static void capture_reg_state(struct i915_gpu_state *error) -+{ -+ struct drm_i915_private *dev_priv = error->i915; -+ int i; -+ -+ /* General organization -+ * 1. Registers specific to a single generation -+ * 2. Registers which belong to multiple generations -+ * 3. Feature specific registers. -+ * 4. Everything else -+ * Please try to follow the order. -+ */ -+ -+ /* 1: Registers specific to a single generation */ -+ if (IS_VALLEYVIEW(dev_priv)) { -+ error->gtier[0] = I915_READ(GTIER); -+ error->ier = I915_READ(VLV_IER); -+ error->forcewake = I915_READ_FW(FORCEWAKE_VLV); -+ } -+ -+ if (IS_GEN(dev_priv, 7)) -+ error->err_int = I915_READ(GEN7_ERR_INT); -+ -+ if (INTEL_GEN(dev_priv) >= 8) { -+ error->fault_data0 = I915_READ(GEN8_FAULT_TLB_DATA0); -+ error->fault_data1 = I915_READ(GEN8_FAULT_TLB_DATA1); -+ } -+ -+ if (IS_GEN(dev_priv, 6)) { -+ error->forcewake = I915_READ_FW(FORCEWAKE); -+ error->gab_ctl = I915_READ(GAB_CTL); -+ error->gfx_mode = I915_READ(GFX_MODE); -+ } -+ -+ /* 2: Registers which belong to multiple generations */ -+ if (INTEL_GEN(dev_priv) >= 7) -+ error->forcewake = I915_READ_FW(FORCEWAKE_MT); -+ -+ if (INTEL_GEN(dev_priv) >= 6) { -+ error->derrmr = I915_READ(DERRMR); -+ error->error = I915_READ(ERROR_GEN6); -+ error->done_reg = I915_READ(DONE_REG); -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 5) -+ error->ccid = I915_READ(CCID(RENDER_RING_BASE)); -+ -+ /* 3: Feature specific registers */ -+ if (IS_GEN_RANGE(dev_priv, 6, 7)) { -+ error->gam_ecochk = I915_READ(GAM_ECOCHK); -+ error->gac_eco = I915_READ(GAC_ECO_BITS); -+ } -+ -+ /* 4: Everything else */ -+ if (INTEL_GEN(dev_priv) >= 11) { -+ error->ier = I915_READ(GEN8_DE_MISC_IER); -+ error->gtier[0] = I915_READ(GEN11_RENDER_COPY_INTR_ENABLE); -+ error->gtier[1] = I915_READ(GEN11_VCS_VECS_INTR_ENABLE); -+ error->gtier[2] = I915_READ(GEN11_GUC_SG_INTR_ENABLE); -+ error->gtier[3] = I915_READ(GEN11_GPM_WGBOXPERF_INTR_ENABLE); -+ error->gtier[4] = I915_READ(GEN11_CRYPTO_RSVD_INTR_ENABLE); -+ error->gtier[5] = I915_READ(GEN11_GUNIT_CSME_INTR_ENABLE); -+ error->ngtier = 6; -+ } else if (INTEL_GEN(dev_priv) >= 8) { -+ error->ier = I915_READ(GEN8_DE_MISC_IER); -+ for (i = 0; i < 4; i++) -+ error->gtier[i] = I915_READ(GEN8_GT_IER(i)); -+ error->ngtier = 4; -+ } else if (HAS_PCH_SPLIT(dev_priv)) { -+ error->ier = I915_READ(DEIER); -+ error->gtier[0] = I915_READ(GTIER); -+ error->ngtier = 1; -+ } else if (IS_GEN(dev_priv, 2)) { -+ error->ier = I915_READ16(GEN2_IER); -+ } else if (!IS_VALLEYVIEW(dev_priv)) { -+ error->ier = I915_READ(GEN2_IER); -+ } -+ error->eir = I915_READ(EIR); -+ error->pgtbl_er = I915_READ(PGTBL_ER); -+} -+ -+static const char * -+error_msg(struct i915_gpu_state *error, -+ intel_engine_mask_t engines, const char *msg) -+{ -+ int len; -+ int i; -+ -+ for (i = 0; i < ARRAY_SIZE(error->engine); i++) -+ if (!error->engine[i].context.pid) -+ engines &= ~BIT(i); -+ -+ len = scnprintf(error->error_msg, sizeof(error->error_msg), -+ "GPU HANG: ecode %d:%x:0x%08x", -+ INTEL_GEN(error->i915), engines, -+ i915_error_generate_code(error, engines)); -+ if (engines) { -+ /* Just show the first executing process, more is confusing */ -+ i = __ffs(engines); -+ len += scnprintf(error->error_msg + len, -+ sizeof(error->error_msg) - len, -+ ", in %s [%d]", -+ error->engine[i].context.comm, -+ error->engine[i].context.pid); -+ } -+ if (msg) -+ len += scnprintf(error->error_msg + len, -+ sizeof(error->error_msg) - len, -+ ", %s", msg); -+ -+ return error->error_msg; -+} -+ -+static void capture_gen_state(struct i915_gpu_state *error) -+{ -+ struct drm_i915_private *i915 = error->i915; -+ -+ error->awake = i915->gt.awake; -+ error->wakelock = atomic_read(&i915->runtime_pm.wakeref_count); -+ error->suspended = i915->runtime_pm.suspended; -+ -+ error->iommu = -1; -+#ifdef CONFIG_INTEL_IOMMU -+ error->iommu = intel_iommu_gfx_mapped; -+#endif -+ error->reset_count = i915_reset_count(&i915->gpu_error); -+ error->suspend_count = i915->suspend_count; -+ -+ memcpy(&error->device_info, -+ INTEL_INFO(i915), -+ sizeof(error->device_info)); -+ memcpy(&error->runtime_info, -+ RUNTIME_INFO(i915), -+ sizeof(error->runtime_info)); -+ error->driver_caps = i915->caps; -+} -+ -+static void capture_params(struct i915_gpu_state *error) -+{ -+ i915_params_copy(&error->params, &i915_modparams); -+} -+ -+static unsigned long capture_find_epoch(const struct i915_gpu_state *error) -+{ -+ unsigned long epoch = error->capture; -+ int i; -+ -+ for (i = 0; i < ARRAY_SIZE(error->engine); i++) { -+ const struct drm_i915_error_engine *ee = &error->engine[i]; -+ -+ if (ee->hangcheck_timestamp && -+ time_before(ee->hangcheck_timestamp, epoch)) -+ epoch = ee->hangcheck_timestamp; -+ } -+ -+ return epoch; -+} -+ -+static void capture_finish(struct i915_gpu_state *error) -+{ -+ struct i915_ggtt *ggtt = &error->i915->ggtt; -+ const u64 slot = ggtt->error_capture.start; -+ -+ ggtt->vm.clear_range(&ggtt->vm, slot, PAGE_SIZE); -+} -+ -+static int capture(void *data) -+{ -+ struct i915_gpu_state *error = data; -+ -+ error->time = ktime_get_real(); -+ error->boottime = ktime_get_boottime(); -+ error->uptime = ktime_sub(ktime_get(), -+ error->i915->gt.last_init_time); -+ error->capture = jiffies; -+ -+ capture_params(error); -+ capture_gen_state(error); -+ capture_uc_state(error); -+ capture_reg_state(error); -+ gem_record_fences(error); -+ gem_record_rings(error); -+ capture_active_buffers(error); -+ capture_pinned_buffers(error); -+ -+ error->overlay = intel_overlay_capture_error_state(error->i915); -+ error->display = intel_display_capture_error_state(error->i915); -+ -+ error->epoch = capture_find_epoch(error); -+ -+ capture_finish(error); -+ return 0; -+} -+ -+#define DAY_AS_SECONDS(x) (24 * 60 * 60 * (x)) -+ -+struct i915_gpu_state * -+i915_capture_gpu_state(struct drm_i915_private *i915) -+{ -+ struct i915_gpu_state *error; -+ -+ /* Check if GPU capture has been disabled */ -+ error = READ_ONCE(i915->gpu_error.first_error); -+ if (IS_ERR(error)) -+ return error; -+ -+ error = kzalloc(sizeof(*error), GFP_ATOMIC); -+ if (!error) { -+ i915_disable_error_state(i915, -ENOMEM); -+ return ERR_PTR(-ENOMEM); -+ } -+ -+ kref_init(&error->ref); -+ error->i915 = i915; -+ -+ stop_machine(capture, error, NULL); -+ -+ return error; -+} -+ -+/** -+ * i915_capture_error_state - capture an error record for later analysis -+ * @i915: i915 device -+ * @engine_mask: the mask of engines triggering the hang -+ * @msg: a message to insert into the error capture header -+ * -+ * Should be called when an error is detected (either a hang or an error -+ * interrupt) to capture error state from the time of the error. Fills -+ * out a structure which becomes available in debugfs for user level tools -+ * to pick up. -+ */ -+void i915_capture_error_state(struct drm_i915_private *i915, -+ intel_engine_mask_t engine_mask, -+ const char *msg) -+{ -+ static bool warned; -+ struct i915_gpu_state *error; -+ unsigned long flags; -+ -+ if (!i915_modparams.error_capture) -+ return; -+ -+ if (READ_ONCE(i915->gpu_error.first_error)) -+ return; -+ -+ error = i915_capture_gpu_state(i915); -+ if (IS_ERR(error)) -+ return; -+ -+ dev_info(i915->drm.dev, "%s\n", error_msg(error, engine_mask, msg)); -+ -+ if (!error->simulated) { -+ spin_lock_irqsave(&i915->gpu_error.lock, flags); -+ if (!i915->gpu_error.first_error) { -+ i915->gpu_error.first_error = error; -+ error = NULL; -+ } -+ spin_unlock_irqrestore(&i915->gpu_error.lock, flags); -+ } -+ -+ if (error) { -+ __i915_gpu_state_free(&error->ref); -+ return; -+ } -+ -+ if (!warned && -+ ktime_get_real_seconds() - DRIVER_TIMESTAMP < DAY_AS_SECONDS(180)) { -+ DRM_INFO("GPU hangs can indicate a bug anywhere in the entire gfx stack, including userspace.\n"); -+ DRM_INFO("Please file a _new_ bug report on bugs.freedesktop.org against DRI -> DRM/Intel\n"); -+ DRM_INFO("drm/i915 developers can then reassign to the right component if it's not a kernel issue.\n"); -+ DRM_INFO("The gpu crash dump is required to analyze gpu hangs, so please always attach it.\n"); -+ DRM_INFO("GPU crash dump saved to /sys/class/drm/card%d/error\n", -+ i915->drm.primary->index); -+ warned = true; -+ } -+} -+ -+struct i915_gpu_state * -+i915_first_error_state(struct drm_i915_private *i915) -+{ -+ struct i915_gpu_state *error; -+ -+ spin_lock_irq(&i915->gpu_error.lock); -+ error = i915->gpu_error.first_error; -+ if (!IS_ERR_OR_NULL(error)) -+ i915_gpu_state_get(error); -+ spin_unlock_irq(&i915->gpu_error.lock); -+ -+ return error; -+} -+ -+void i915_reset_error_state(struct drm_i915_private *i915) -+{ -+ struct i915_gpu_state *error; -+ -+ spin_lock_irq(&i915->gpu_error.lock); -+ error = i915->gpu_error.first_error; -+ if (error != ERR_PTR(-ENODEV)) /* if disabled, always disabled */ -+ i915->gpu_error.first_error = NULL; -+ spin_unlock_irq(&i915->gpu_error.lock); -+ -+ if (!IS_ERR_OR_NULL(error)) -+ i915_gpu_state_put(error); -+} -+ -+void i915_disable_error_state(struct drm_i915_private *i915, int err) -+{ -+ spin_lock_irq(&i915->gpu_error.lock); -+ if (!i915->gpu_error.first_error) -+ i915->gpu_error.first_error = ERR_PTR(err); -+ spin_unlock_irq(&i915->gpu_error.lock); -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_gpu_error.h b/drivers/gpu/drm/i915_legacy/i915_gpu_error.h -new file mode 100644 -index 000000000000..5dc761e85d9d ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_gpu_error.h -@@ -0,0 +1,315 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright � 2008-2018 Intel Corporation -+ */ -+ -+#ifndef _I915_GPU_ERROR_H_ -+#define _I915_GPU_ERROR_H_ -+ -+#include -+#include -+#include -+ -+#include -+ -+#include "intel_device_info.h" -+#include "intel_ringbuffer.h" -+#include "intel_uc_fw.h" -+ -+#include "i915_gem.h" -+#include "i915_gem_gtt.h" -+#include "i915_params.h" -+#include "i915_scheduler.h" -+ -+struct drm_i915_private; -+struct intel_overlay_error_state; -+struct intel_display_error_state; -+ -+struct i915_gpu_state { -+ struct kref ref; -+ ktime_t time; -+ ktime_t boottime; -+ ktime_t uptime; -+ unsigned long capture; -+ unsigned long epoch; -+ -+ struct drm_i915_private *i915; -+ -+ char error_msg[128]; -+ bool simulated; -+ bool awake; -+ bool wakelock; -+ bool suspended; -+ int iommu; -+ u32 reset_count; -+ u32 suspend_count; -+ struct intel_device_info device_info; -+ struct intel_runtime_info runtime_info; -+ struct intel_driver_caps driver_caps; -+ struct i915_params params; -+ -+ struct i915_error_uc { -+ struct intel_uc_fw guc_fw; -+ struct intel_uc_fw huc_fw; -+ struct drm_i915_error_object *guc_log; -+ } uc; -+ -+ /* Generic register state */ -+ u32 eir; -+ u32 pgtbl_er; -+ u32 ier; -+ u32 gtier[6], ngtier; -+ u32 ccid; -+ u32 derrmr; -+ u32 forcewake; -+ u32 error; /* gen6+ */ -+ u32 err_int; /* gen7 */ -+ u32 fault_data0; /* gen8, gen9 */ -+ u32 fault_data1; /* gen8, gen9 */ -+ u32 done_reg; -+ u32 gac_eco; -+ u32 gam_ecochk; -+ u32 gab_ctl; -+ u32 gfx_mode; -+ -+ u32 nfence; -+ u64 fence[I915_MAX_NUM_FENCES]; -+ struct intel_overlay_error_state *overlay; -+ struct intel_display_error_state *display; -+ -+ struct drm_i915_error_engine { -+ int engine_id; -+ /* Software tracked state */ -+ bool idle; -+ unsigned long hangcheck_timestamp; -+ struct i915_address_space *vm; -+ int num_requests; -+ u32 reset_count; -+ -+ /* position of active request inside the ring */ -+ u32 rq_head, rq_post, rq_tail; -+ -+ /* our own tracking of ring head and tail */ -+ u32 cpu_ring_head; -+ u32 cpu_ring_tail; -+ -+ /* Register state */ -+ u32 start; -+ u32 tail; -+ u32 head; -+ u32 ctl; -+ u32 mode; -+ u32 hws; -+ u32 ipeir; -+ u32 ipehr; -+ u32 bbstate; -+ u32 instpm; -+ u32 instps; -+ u64 bbaddr; -+ u64 acthd; -+ u32 fault_reg; -+ u64 faddr; -+ u32 rc_psmi; /* sleep state */ -+ struct intel_instdone instdone; -+ -+ struct drm_i915_error_context { -+ char comm[TASK_COMM_LEN]; -+ pid_t pid; -+ u32 hw_id; -+ int active; -+ int guilty; -+ struct i915_sched_attr sched_attr; -+ } context; -+ -+ struct drm_i915_error_object { -+ u64 gtt_offset; -+ u64 gtt_size; -+ int num_pages; -+ int page_count; -+ int unused; -+ u32 *pages[0]; -+ } *ringbuffer, *batchbuffer, *wa_batchbuffer, *ctx, *hws_page; -+ -+ struct drm_i915_error_object **user_bo; -+ long user_bo_count; -+ -+ struct drm_i915_error_object *wa_ctx; -+ struct drm_i915_error_object *default_state; -+ -+ struct drm_i915_error_request { -+ unsigned long flags; -+ long jiffies; -+ pid_t pid; -+ u32 context; -+ u32 seqno; -+ u32 start; -+ u32 head; -+ u32 tail; -+ struct i915_sched_attr sched_attr; -+ } *requests, execlist[EXECLIST_MAX_PORTS]; -+ unsigned int num_ports; -+ -+ struct { -+ u32 gfx_mode; -+ union { -+ u64 pdp[4]; -+ u32 pp_dir_base; -+ }; -+ } vm_info; -+ } engine[I915_NUM_ENGINES]; -+ -+ struct drm_i915_error_buffer { -+ u32 size; -+ u32 name; -+ u64 gtt_offset; -+ u32 read_domains; -+ u32 write_domain; -+ s32 fence_reg:I915_MAX_NUM_FENCE_BITS; -+ u32 tiling:2; -+ u32 dirty:1; -+ u32 purgeable:1; -+ u32 userptr:1; -+ u32 cache_level:3; -+ } *active_bo[I915_NUM_ENGINES], *pinned_bo; -+ u32 active_bo_count[I915_NUM_ENGINES], pinned_bo_count; -+ struct i915_address_space *active_vm[I915_NUM_ENGINES]; -+ -+ struct scatterlist *sgl, *fit; -+}; -+ -+struct i915_gpu_restart; -+ -+struct i915_gpu_error { -+ /* For hangcheck timer */ -+#define DRM_I915_HANGCHECK_PERIOD 1500 /* in ms */ -+#define DRM_I915_HANGCHECK_JIFFIES msecs_to_jiffies(DRM_I915_HANGCHECK_PERIOD) -+ -+ struct delayed_work hangcheck_work; -+ -+ /* For reset and error_state handling. */ -+ spinlock_t lock; -+ /* Protected by the above dev->gpu_error.lock. */ -+ struct i915_gpu_state *first_error; -+ -+ atomic_t pending_fb_pin; -+ -+ /** -+ * flags: Control various stages of the GPU reset -+ * -+ * #I915_RESET_BACKOFF - When we start a global reset, we need to -+ * serialise with any other users attempting to do the same, and -+ * any global resources that may be clobber by the reset (such as -+ * FENCE registers). -+ * -+ * #I915_RESET_ENGINE[num_engines] - Since the driver doesn't need to -+ * acquire the struct_mutex to reset an engine, we need an explicit -+ * flag to prevent two concurrent reset attempts in the same engine. -+ * As the number of engines continues to grow, allocate the flags from -+ * the most significant bits. -+ * -+ * #I915_WEDGED - If reset fails and we can no longer use the GPU, -+ * we set the #I915_WEDGED bit. Prior to command submission, e.g. -+ * i915_request_alloc(), this bit is checked and the sequence -+ * aborted (with -EIO reported to userspace) if set. -+ */ -+ unsigned long flags; -+#define I915_RESET_BACKOFF 0 -+#define I915_RESET_MODESET 1 -+#define I915_RESET_ENGINE 2 -+#define I915_WEDGED (BITS_PER_LONG - 1) -+ -+ /** Number of times the device has been reset (global) */ -+ u32 reset_count; -+ -+ /** Number of times an engine has been reset */ -+ u32 reset_engine_count[I915_NUM_ENGINES]; -+ -+ struct mutex wedge_mutex; /* serialises wedging/unwedging */ -+ -+ /** -+ * Waitqueue to signal when a hang is detected. Used to for waiters -+ * to release the struct_mutex for the reset to procede. -+ */ -+ wait_queue_head_t wait_queue; -+ -+ /** -+ * Waitqueue to signal when the reset has completed. Used by clients -+ * that wait for dev_priv->mm.wedged to settle. -+ */ -+ wait_queue_head_t reset_queue; -+ -+ struct srcu_struct reset_backoff_srcu; -+ -+ struct i915_gpu_restart *restart; -+}; -+ -+struct drm_i915_error_state_buf { -+ struct drm_i915_private *i915; -+ struct scatterlist *sgl, *cur, *end; -+ -+ char *buf; -+ size_t bytes; -+ size_t size; -+ loff_t iter; -+ -+ int err; -+}; -+ -+#if IS_ENABLED(CONFIG_DRM_I915_CAPTURE_ERROR) -+ -+__printf(2, 3) -+void i915_error_printf(struct drm_i915_error_state_buf *e, const char *f, ...); -+ -+struct i915_gpu_state *i915_capture_gpu_state(struct drm_i915_private *i915); -+void i915_capture_error_state(struct drm_i915_private *dev_priv, -+ intel_engine_mask_t engine_mask, -+ const char *error_msg); -+ -+static inline struct i915_gpu_state * -+i915_gpu_state_get(struct i915_gpu_state *gpu) -+{ -+ kref_get(&gpu->ref); -+ return gpu; -+} -+ -+ssize_t i915_gpu_state_copy_to_buffer(struct i915_gpu_state *error, -+ char *buf, loff_t offset, size_t count); -+ -+void __i915_gpu_state_free(struct kref *kref); -+static inline void i915_gpu_state_put(struct i915_gpu_state *gpu) -+{ -+ if (gpu) -+ kref_put(&gpu->ref, __i915_gpu_state_free); -+} -+ -+struct i915_gpu_state *i915_first_error_state(struct drm_i915_private *i915); -+void i915_reset_error_state(struct drm_i915_private *i915); -+void i915_disable_error_state(struct drm_i915_private *i915, int err); -+ -+#else -+ -+static inline void i915_capture_error_state(struct drm_i915_private *dev_priv, -+ u32 engine_mask, -+ const char *error_msg) -+{ -+} -+ -+static inline struct i915_gpu_state * -+i915_first_error_state(struct drm_i915_private *i915) -+{ -+ return ERR_PTR(-ENODEV); -+} -+ -+static inline void i915_reset_error_state(struct drm_i915_private *i915) -+{ -+} -+ -+static inline void i915_disable_error_state(struct drm_i915_private *i915, -+ int err) -+{ -+} -+ -+#endif /* IS_ENABLED(CONFIG_DRM_I915_CAPTURE_ERROR) */ -+ -+#endif /* _I915_GPU_ERROR_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_ioc32.c b/drivers/gpu/drm/i915_legacy/i915_ioc32.c -new file mode 100644 -index 000000000000..c1007245f46d ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_ioc32.c -@@ -0,0 +1,96 @@ -+/* -+ * 32-bit ioctl compatibility routines for the i915 DRM. -+ * -+ * Copyright (C) Paul Mackerras 2005 -+ * Copyright (C) Alan Hourihane 2005 -+ * All Rights Reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHOR 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. -+ * -+ * Author: Alan Hourihane -+ */ -+#include -+ -+#include -+#include -+#include "i915_drv.h" -+ -+struct drm_i915_getparam32 { -+ s32 param; -+ /* -+ * We screwed up the generic ioctl struct here and used a variable-sized -+ * pointer. Use u32 in the compat struct to match the 32bit pointer -+ * userspace expects. -+ */ -+ u32 value; -+}; -+ -+static int compat_i915_getparam(struct file *file, unsigned int cmd, -+ unsigned long arg) -+{ -+ struct drm_i915_getparam32 req32; -+ drm_i915_getparam_t __user *request; -+ -+ if (copy_from_user(&req32, (void __user *)arg, sizeof(req32))) -+ return -EFAULT; -+ -+ request = compat_alloc_user_space(sizeof(*request)); -+ if (!access_ok(request, sizeof(*request)) || -+ __put_user(req32.param, &request->param) || -+ __put_user((void __user *)(unsigned long)req32.value, -+ &request->value)) -+ return -EFAULT; -+ -+ return drm_ioctl(file, DRM_IOCTL_I915_GETPARAM, -+ (unsigned long)request); -+} -+ -+static drm_ioctl_compat_t *i915_compat_ioctls[] = { -+ [DRM_I915_GETPARAM] = compat_i915_getparam, -+}; -+ -+/** -+ * i915_compat_ioctl - handle the mistakes of the past -+ * @filp: the file pointer -+ * @cmd: the ioctl command (and encoded flags) -+ * @arg: the ioctl argument (from userspace) -+ * -+ * Called whenever a 32-bit process running under a 64-bit kernel -+ * performs an ioctl on /dev/dri/card. -+ */ -+long i915_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) -+{ -+ unsigned int nr = DRM_IOCTL_NR(cmd); -+ drm_ioctl_compat_t *fn = NULL; -+ int ret; -+ -+ if (nr < DRM_COMMAND_BASE || nr >= DRM_COMMAND_END) -+ return drm_compat_ioctl(filp, cmd, arg); -+ -+ if (nr < DRM_COMMAND_BASE + ARRAY_SIZE(i915_compat_ioctls)) -+ fn = i915_compat_ioctls[nr - DRM_COMMAND_BASE]; -+ -+ if (fn != NULL) -+ ret = (*fn) (filp, cmd, arg); -+ else -+ ret = drm_ioctl(filp, cmd, arg); -+ -+ return ret; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_irq.c b/drivers/gpu/drm/i915_legacy/i915_irq.c -new file mode 100644 -index 000000000000..b92cfd69134b ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_irq.c -@@ -0,0 +1,4925 @@ -+/* i915_irq.c -- IRQ support for the I915 -*- linux-c -*- -+ */ -+/* -+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas. -+ * All Rights Reserved. -+ * -+ * 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, sub license, 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 (including the -+ * next paragraph) 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 NON-INFRINGEMENT. -+ * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS 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. -+ * -+ */ -+ -+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -+ -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+#include "i915_drv.h" -+#include "i915_trace.h" -+#include "intel_drv.h" -+#include "intel_psr.h" -+ -+/** -+ * DOC: interrupt handling -+ * -+ * These functions provide the basic support for enabling and disabling the -+ * interrupt handling support. There's a lot more functionality in i915_irq.c -+ * and related files, but that will be described in separate chapters. -+ */ -+ -+static const u32 hpd_ilk[HPD_NUM_PINS] = { -+ [HPD_PORT_A] = DE_DP_A_HOTPLUG, -+}; -+ -+static const u32 hpd_ivb[HPD_NUM_PINS] = { -+ [HPD_PORT_A] = DE_DP_A_HOTPLUG_IVB, -+}; -+ -+static const u32 hpd_bdw[HPD_NUM_PINS] = { -+ [HPD_PORT_A] = GEN8_PORT_DP_A_HOTPLUG, -+}; -+ -+static const u32 hpd_ibx[HPD_NUM_PINS] = { -+ [HPD_CRT] = SDE_CRT_HOTPLUG, -+ [HPD_SDVO_B] = SDE_SDVOB_HOTPLUG, -+ [HPD_PORT_B] = SDE_PORTB_HOTPLUG, -+ [HPD_PORT_C] = SDE_PORTC_HOTPLUG, -+ [HPD_PORT_D] = SDE_PORTD_HOTPLUG -+}; -+ -+static const u32 hpd_cpt[HPD_NUM_PINS] = { -+ [HPD_CRT] = SDE_CRT_HOTPLUG_CPT, -+ [HPD_SDVO_B] = SDE_SDVOB_HOTPLUG_CPT, -+ [HPD_PORT_B] = SDE_PORTB_HOTPLUG_CPT, -+ [HPD_PORT_C] = SDE_PORTC_HOTPLUG_CPT, -+ [HPD_PORT_D] = SDE_PORTD_HOTPLUG_CPT -+}; -+ -+static const u32 hpd_spt[HPD_NUM_PINS] = { -+ [HPD_PORT_A] = SDE_PORTA_HOTPLUG_SPT, -+ [HPD_PORT_B] = SDE_PORTB_HOTPLUG_CPT, -+ [HPD_PORT_C] = SDE_PORTC_HOTPLUG_CPT, -+ [HPD_PORT_D] = SDE_PORTD_HOTPLUG_CPT, -+ [HPD_PORT_E] = SDE_PORTE_HOTPLUG_SPT -+}; -+ -+static const u32 hpd_mask_i915[HPD_NUM_PINS] = { -+ [HPD_CRT] = CRT_HOTPLUG_INT_EN, -+ [HPD_SDVO_B] = SDVOB_HOTPLUG_INT_EN, -+ [HPD_SDVO_C] = SDVOC_HOTPLUG_INT_EN, -+ [HPD_PORT_B] = PORTB_HOTPLUG_INT_EN, -+ [HPD_PORT_C] = PORTC_HOTPLUG_INT_EN, -+ [HPD_PORT_D] = PORTD_HOTPLUG_INT_EN -+}; -+ -+static const u32 hpd_status_g4x[HPD_NUM_PINS] = { -+ [HPD_CRT] = CRT_HOTPLUG_INT_STATUS, -+ [HPD_SDVO_B] = SDVOB_HOTPLUG_INT_STATUS_G4X, -+ [HPD_SDVO_C] = SDVOC_HOTPLUG_INT_STATUS_G4X, -+ [HPD_PORT_B] = PORTB_HOTPLUG_INT_STATUS, -+ [HPD_PORT_C] = PORTC_HOTPLUG_INT_STATUS, -+ [HPD_PORT_D] = PORTD_HOTPLUG_INT_STATUS -+}; -+ -+static const u32 hpd_status_i915[HPD_NUM_PINS] = { -+ [HPD_CRT] = CRT_HOTPLUG_INT_STATUS, -+ [HPD_SDVO_B] = SDVOB_HOTPLUG_INT_STATUS_I915, -+ [HPD_SDVO_C] = SDVOC_HOTPLUG_INT_STATUS_I915, -+ [HPD_PORT_B] = PORTB_HOTPLUG_INT_STATUS, -+ [HPD_PORT_C] = PORTC_HOTPLUG_INT_STATUS, -+ [HPD_PORT_D] = PORTD_HOTPLUG_INT_STATUS -+}; -+ -+/* BXT hpd list */ -+static const u32 hpd_bxt[HPD_NUM_PINS] = { -+ [HPD_PORT_A] = BXT_DE_PORT_HP_DDIA, -+ [HPD_PORT_B] = BXT_DE_PORT_HP_DDIB, -+ [HPD_PORT_C] = BXT_DE_PORT_HP_DDIC -+}; -+ -+static const u32 hpd_gen11[HPD_NUM_PINS] = { -+ [HPD_PORT_C] = GEN11_TC1_HOTPLUG | GEN11_TBT1_HOTPLUG, -+ [HPD_PORT_D] = GEN11_TC2_HOTPLUG | GEN11_TBT2_HOTPLUG, -+ [HPD_PORT_E] = GEN11_TC3_HOTPLUG | GEN11_TBT3_HOTPLUG, -+ [HPD_PORT_F] = GEN11_TC4_HOTPLUG | GEN11_TBT4_HOTPLUG -+}; -+ -+static const u32 hpd_icp[HPD_NUM_PINS] = { -+ [HPD_PORT_A] = SDE_DDIA_HOTPLUG_ICP, -+ [HPD_PORT_B] = SDE_DDIB_HOTPLUG_ICP, -+ [HPD_PORT_C] = SDE_TC1_HOTPLUG_ICP, -+ [HPD_PORT_D] = SDE_TC2_HOTPLUG_ICP, -+ [HPD_PORT_E] = SDE_TC3_HOTPLUG_ICP, -+ [HPD_PORT_F] = SDE_TC4_HOTPLUG_ICP -+}; -+ -+static void gen3_irq_reset(struct intel_uncore *uncore, i915_reg_t imr, -+ i915_reg_t iir, i915_reg_t ier) -+{ -+ intel_uncore_write(uncore, imr, 0xffffffff); -+ intel_uncore_posting_read(uncore, imr); -+ -+ intel_uncore_write(uncore, ier, 0); -+ -+ /* IIR can theoretically queue up two events. Be paranoid. */ -+ intel_uncore_write(uncore, iir, 0xffffffff); -+ intel_uncore_posting_read(uncore, iir); -+ intel_uncore_write(uncore, iir, 0xffffffff); -+ intel_uncore_posting_read(uncore, iir); -+} -+ -+static void gen2_irq_reset(struct intel_uncore *uncore) -+{ -+ intel_uncore_write16(uncore, GEN2_IMR, 0xffff); -+ intel_uncore_posting_read16(uncore, GEN2_IMR); -+ -+ intel_uncore_write16(uncore, GEN2_IER, 0); -+ -+ /* IIR can theoretically queue up two events. Be paranoid. */ -+ intel_uncore_write16(uncore, GEN2_IIR, 0xffff); -+ intel_uncore_posting_read16(uncore, GEN2_IIR); -+ intel_uncore_write16(uncore, GEN2_IIR, 0xffff); -+ intel_uncore_posting_read16(uncore, GEN2_IIR); -+} -+ -+#define GEN8_IRQ_RESET_NDX(uncore, type, which) \ -+({ \ -+ unsigned int which_ = which; \ -+ gen3_irq_reset((uncore), GEN8_##type##_IMR(which_), \ -+ GEN8_##type##_IIR(which_), GEN8_##type##_IER(which_)); \ -+}) -+ -+#define GEN3_IRQ_RESET(uncore, type) \ -+ gen3_irq_reset((uncore), type##IMR, type##IIR, type##IER) -+ -+#define GEN2_IRQ_RESET(uncore) \ -+ gen2_irq_reset(uncore) -+ -+/* -+ * We should clear IMR at preinstall/uninstall, and just check at postinstall. -+ */ -+static void gen3_assert_iir_is_zero(struct intel_uncore *uncore, i915_reg_t reg) -+{ -+ u32 val = intel_uncore_read(uncore, reg); -+ -+ if (val == 0) -+ return; -+ -+ WARN(1, "Interrupt register 0x%x is not zero: 0x%08x\n", -+ i915_mmio_reg_offset(reg), val); -+ intel_uncore_write(uncore, reg, 0xffffffff); -+ intel_uncore_posting_read(uncore, reg); -+ intel_uncore_write(uncore, reg, 0xffffffff); -+ intel_uncore_posting_read(uncore, reg); -+} -+ -+static void gen2_assert_iir_is_zero(struct intel_uncore *uncore) -+{ -+ u16 val = intel_uncore_read16(uncore, GEN2_IIR); -+ -+ if (val == 0) -+ return; -+ -+ WARN(1, "Interrupt register 0x%x is not zero: 0x%08x\n", -+ i915_mmio_reg_offset(GEN2_IIR), val); -+ intel_uncore_write16(uncore, GEN2_IIR, 0xffff); -+ intel_uncore_posting_read16(uncore, GEN2_IIR); -+ intel_uncore_write16(uncore, GEN2_IIR, 0xffff); -+ intel_uncore_posting_read16(uncore, GEN2_IIR); -+} -+ -+static void gen3_irq_init(struct intel_uncore *uncore, -+ i915_reg_t imr, u32 imr_val, -+ i915_reg_t ier, u32 ier_val, -+ i915_reg_t iir) -+{ -+ gen3_assert_iir_is_zero(uncore, iir); -+ -+ intel_uncore_write(uncore, ier, ier_val); -+ intel_uncore_write(uncore, imr, imr_val); -+ intel_uncore_posting_read(uncore, imr); -+} -+ -+static void gen2_irq_init(struct intel_uncore *uncore, -+ u32 imr_val, u32 ier_val) -+{ -+ gen2_assert_iir_is_zero(uncore); -+ -+ intel_uncore_write16(uncore, GEN2_IER, ier_val); -+ intel_uncore_write16(uncore, GEN2_IMR, imr_val); -+ intel_uncore_posting_read16(uncore, GEN2_IMR); -+} -+ -+#define GEN8_IRQ_INIT_NDX(uncore, type, which, imr_val, ier_val) \ -+({ \ -+ unsigned int which_ = which; \ -+ gen3_irq_init((uncore), \ -+ GEN8_##type##_IMR(which_), imr_val, \ -+ GEN8_##type##_IER(which_), ier_val, \ -+ GEN8_##type##_IIR(which_)); \ -+}) -+ -+#define GEN3_IRQ_INIT(uncore, type, imr_val, ier_val) \ -+ gen3_irq_init((uncore), \ -+ type##IMR, imr_val, \ -+ type##IER, ier_val, \ -+ type##IIR) -+ -+#define GEN2_IRQ_INIT(uncore, imr_val, ier_val) \ -+ gen2_irq_init((uncore), imr_val, ier_val) -+ -+static void gen6_rps_irq_handler(struct drm_i915_private *dev_priv, u32 pm_iir); -+static void gen9_guc_irq_handler(struct drm_i915_private *dev_priv, u32 pm_iir); -+ -+/* For display hotplug interrupt */ -+static inline void -+i915_hotplug_interrupt_update_locked(struct drm_i915_private *dev_priv, -+ u32 mask, -+ u32 bits) -+{ -+ u32 val; -+ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ WARN_ON(bits & ~mask); -+ -+ val = I915_READ(PORT_HOTPLUG_EN); -+ val &= ~mask; -+ val |= bits; -+ I915_WRITE(PORT_HOTPLUG_EN, val); -+} -+ -+/** -+ * i915_hotplug_interrupt_update - update hotplug interrupt enable -+ * @dev_priv: driver private -+ * @mask: bits to update -+ * @bits: bits to enable -+ * NOTE: the HPD enable bits are modified both inside and outside -+ * of an interrupt context. To avoid that read-modify-write cycles -+ * interfer, these bits are protected by a spinlock. Since this -+ * function is usually not called from a context where the lock is -+ * held already, this function acquires the lock itself. A non-locking -+ * version is also available. -+ */ -+void i915_hotplug_interrupt_update(struct drm_i915_private *dev_priv, -+ u32 mask, -+ u32 bits) -+{ -+ spin_lock_irq(&dev_priv->irq_lock); -+ i915_hotplug_interrupt_update_locked(dev_priv, mask, bits); -+ spin_unlock_irq(&dev_priv->irq_lock); -+} -+ -+static u32 -+gen11_gt_engine_identity(struct drm_i915_private * const i915, -+ const unsigned int bank, const unsigned int bit); -+ -+static bool gen11_reset_one_iir(struct drm_i915_private * const i915, -+ const unsigned int bank, -+ const unsigned int bit) -+{ -+ void __iomem * const regs = i915->uncore.regs; -+ u32 dw; -+ -+ lockdep_assert_held(&i915->irq_lock); -+ -+ dw = raw_reg_read(regs, GEN11_GT_INTR_DW(bank)); -+ if (dw & BIT(bit)) { -+ /* -+ * According to the BSpec, DW_IIR bits cannot be cleared without -+ * first servicing the Selector & Shared IIR registers. -+ */ -+ gen11_gt_engine_identity(i915, bank, bit); -+ -+ /* -+ * We locked GT INT DW by reading it. If we want to (try -+ * to) recover from this succesfully, we need to clear -+ * our bit, otherwise we are locking the register for -+ * everybody. -+ */ -+ raw_reg_write(regs, GEN11_GT_INTR_DW(bank), BIT(bit)); -+ -+ return true; -+ } -+ -+ return false; -+} -+ -+/** -+ * ilk_update_display_irq - update DEIMR -+ * @dev_priv: driver private -+ * @interrupt_mask: mask of interrupt bits to update -+ * @enabled_irq_mask: mask of interrupt bits to enable -+ */ -+void ilk_update_display_irq(struct drm_i915_private *dev_priv, -+ u32 interrupt_mask, -+ u32 enabled_irq_mask) -+{ -+ u32 new_val; -+ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ -+ WARN_ON(enabled_irq_mask & ~interrupt_mask); -+ -+ if (WARN_ON(!intel_irqs_enabled(dev_priv))) -+ return; -+ -+ new_val = dev_priv->irq_mask; -+ new_val &= ~interrupt_mask; -+ new_val |= (~enabled_irq_mask & interrupt_mask); -+ -+ if (new_val != dev_priv->irq_mask) { -+ dev_priv->irq_mask = new_val; -+ I915_WRITE(DEIMR, dev_priv->irq_mask); -+ POSTING_READ(DEIMR); -+ } -+} -+ -+/** -+ * ilk_update_gt_irq - update GTIMR -+ * @dev_priv: driver private -+ * @interrupt_mask: mask of interrupt bits to update -+ * @enabled_irq_mask: mask of interrupt bits to enable -+ */ -+static void ilk_update_gt_irq(struct drm_i915_private *dev_priv, -+ u32 interrupt_mask, -+ u32 enabled_irq_mask) -+{ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ -+ WARN_ON(enabled_irq_mask & ~interrupt_mask); -+ -+ if (WARN_ON(!intel_irqs_enabled(dev_priv))) -+ return; -+ -+ dev_priv->gt_irq_mask &= ~interrupt_mask; -+ dev_priv->gt_irq_mask |= (~enabled_irq_mask & interrupt_mask); -+ I915_WRITE(GTIMR, dev_priv->gt_irq_mask); -+} -+ -+void gen5_enable_gt_irq(struct drm_i915_private *dev_priv, u32 mask) -+{ -+ ilk_update_gt_irq(dev_priv, mask, mask); -+ POSTING_READ_FW(GTIMR); -+} -+ -+void gen5_disable_gt_irq(struct drm_i915_private *dev_priv, u32 mask) -+{ -+ ilk_update_gt_irq(dev_priv, mask, 0); -+} -+ -+static i915_reg_t gen6_pm_iir(struct drm_i915_private *dev_priv) -+{ -+ WARN_ON_ONCE(INTEL_GEN(dev_priv) >= 11); -+ -+ return INTEL_GEN(dev_priv) >= 8 ? GEN8_GT_IIR(2) : GEN6_PMIIR; -+} -+ -+static void write_pm_imr(struct drm_i915_private *dev_priv) -+{ -+ i915_reg_t reg; -+ u32 mask = dev_priv->pm_imr; -+ -+ if (INTEL_GEN(dev_priv) >= 11) { -+ reg = GEN11_GPM_WGBOXPERF_INTR_MASK; -+ /* pm is in upper half */ -+ mask = mask << 16; -+ } else if (INTEL_GEN(dev_priv) >= 8) { -+ reg = GEN8_GT_IMR(2); -+ } else { -+ reg = GEN6_PMIMR; -+ } -+ -+ I915_WRITE(reg, mask); -+ POSTING_READ(reg); -+} -+ -+static void write_pm_ier(struct drm_i915_private *dev_priv) -+{ -+ i915_reg_t reg; -+ u32 mask = dev_priv->pm_ier; -+ -+ if (INTEL_GEN(dev_priv) >= 11) { -+ reg = GEN11_GPM_WGBOXPERF_INTR_ENABLE; -+ /* pm is in upper half */ -+ mask = mask << 16; -+ } else if (INTEL_GEN(dev_priv) >= 8) { -+ reg = GEN8_GT_IER(2); -+ } else { -+ reg = GEN6_PMIER; -+ } -+ -+ I915_WRITE(reg, mask); -+} -+ -+/** -+ * snb_update_pm_irq - update GEN6_PMIMR -+ * @dev_priv: driver private -+ * @interrupt_mask: mask of interrupt bits to update -+ * @enabled_irq_mask: mask of interrupt bits to enable -+ */ -+static void snb_update_pm_irq(struct drm_i915_private *dev_priv, -+ u32 interrupt_mask, -+ u32 enabled_irq_mask) -+{ -+ u32 new_val; -+ -+ WARN_ON(enabled_irq_mask & ~interrupt_mask); -+ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ -+ new_val = dev_priv->pm_imr; -+ new_val &= ~interrupt_mask; -+ new_val |= (~enabled_irq_mask & interrupt_mask); -+ -+ if (new_val != dev_priv->pm_imr) { -+ dev_priv->pm_imr = new_val; -+ write_pm_imr(dev_priv); -+ } -+} -+ -+void gen6_unmask_pm_irq(struct drm_i915_private *dev_priv, u32 mask) -+{ -+ if (WARN_ON(!intel_irqs_enabled(dev_priv))) -+ return; -+ -+ snb_update_pm_irq(dev_priv, mask, mask); -+} -+ -+static void __gen6_mask_pm_irq(struct drm_i915_private *dev_priv, u32 mask) -+{ -+ snb_update_pm_irq(dev_priv, mask, 0); -+} -+ -+void gen6_mask_pm_irq(struct drm_i915_private *dev_priv, u32 mask) -+{ -+ if (WARN_ON(!intel_irqs_enabled(dev_priv))) -+ return; -+ -+ __gen6_mask_pm_irq(dev_priv, mask); -+} -+ -+static void gen6_reset_pm_iir(struct drm_i915_private *dev_priv, u32 reset_mask) -+{ -+ i915_reg_t reg = gen6_pm_iir(dev_priv); -+ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ -+ I915_WRITE(reg, reset_mask); -+ I915_WRITE(reg, reset_mask); -+ POSTING_READ(reg); -+} -+ -+static void gen6_enable_pm_irq(struct drm_i915_private *dev_priv, u32 enable_mask) -+{ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ -+ dev_priv->pm_ier |= enable_mask; -+ write_pm_ier(dev_priv); -+ gen6_unmask_pm_irq(dev_priv, enable_mask); -+ /* unmask_pm_irq provides an implicit barrier (POSTING_READ) */ -+} -+ -+static void gen6_disable_pm_irq(struct drm_i915_private *dev_priv, u32 disable_mask) -+{ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ -+ dev_priv->pm_ier &= ~disable_mask; -+ __gen6_mask_pm_irq(dev_priv, disable_mask); -+ write_pm_ier(dev_priv); -+ /* though a barrier is missing here, but don't really need a one */ -+} -+ -+void gen11_reset_rps_interrupts(struct drm_i915_private *dev_priv) -+{ -+ spin_lock_irq(&dev_priv->irq_lock); -+ -+ while (gen11_reset_one_iir(dev_priv, 0, GEN11_GTPM)) -+ ; -+ -+ dev_priv->gt_pm.rps.pm_iir = 0; -+ -+ spin_unlock_irq(&dev_priv->irq_lock); -+} -+ -+void gen6_reset_rps_interrupts(struct drm_i915_private *dev_priv) -+{ -+ spin_lock_irq(&dev_priv->irq_lock); -+ gen6_reset_pm_iir(dev_priv, GEN6_PM_RPS_EVENTS); -+ dev_priv->gt_pm.rps.pm_iir = 0; -+ spin_unlock_irq(&dev_priv->irq_lock); -+} -+ -+void gen6_enable_rps_interrupts(struct drm_i915_private *dev_priv) -+{ -+ struct intel_rps *rps = &dev_priv->gt_pm.rps; -+ -+ if (READ_ONCE(rps->interrupts_enabled)) -+ return; -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ WARN_ON_ONCE(rps->pm_iir); -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ WARN_ON_ONCE(gen11_reset_one_iir(dev_priv, 0, GEN11_GTPM)); -+ else -+ WARN_ON_ONCE(I915_READ(gen6_pm_iir(dev_priv)) & dev_priv->pm_rps_events); -+ -+ rps->interrupts_enabled = true; -+ gen6_enable_pm_irq(dev_priv, dev_priv->pm_rps_events); -+ -+ spin_unlock_irq(&dev_priv->irq_lock); -+} -+ -+void gen6_disable_rps_interrupts(struct drm_i915_private *dev_priv) -+{ -+ struct intel_rps *rps = &dev_priv->gt_pm.rps; -+ -+ if (!READ_ONCE(rps->interrupts_enabled)) -+ return; -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ rps->interrupts_enabled = false; -+ -+ I915_WRITE(GEN6_PMINTRMSK, gen6_sanitize_rps_pm_mask(dev_priv, ~0u)); -+ -+ gen6_disable_pm_irq(dev_priv, GEN6_PM_RPS_EVENTS); -+ -+ spin_unlock_irq(&dev_priv->irq_lock); -+ synchronize_irq(dev_priv->drm.irq); -+ -+ /* Now that we will not be generating any more work, flush any -+ * outstanding tasks. As we are called on the RPS idle path, -+ * we will reset the GPU to minimum frequencies, so the current -+ * state of the worker can be discarded. -+ */ -+ cancel_work_sync(&rps->work); -+ if (INTEL_GEN(dev_priv) >= 11) -+ gen11_reset_rps_interrupts(dev_priv); -+ else -+ gen6_reset_rps_interrupts(dev_priv); -+} -+ -+void gen9_reset_guc_interrupts(struct drm_i915_private *dev_priv) -+{ -+ assert_rpm_wakelock_held(dev_priv); -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ gen6_reset_pm_iir(dev_priv, dev_priv->pm_guc_events); -+ spin_unlock_irq(&dev_priv->irq_lock); -+} -+ -+void gen9_enable_guc_interrupts(struct drm_i915_private *dev_priv) -+{ -+ assert_rpm_wakelock_held(dev_priv); -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ if (!dev_priv->guc.interrupts_enabled) { -+ WARN_ON_ONCE(I915_READ(gen6_pm_iir(dev_priv)) & -+ dev_priv->pm_guc_events); -+ dev_priv->guc.interrupts_enabled = true; -+ gen6_enable_pm_irq(dev_priv, dev_priv->pm_guc_events); -+ } -+ spin_unlock_irq(&dev_priv->irq_lock); -+} -+ -+void gen9_disable_guc_interrupts(struct drm_i915_private *dev_priv) -+{ -+ assert_rpm_wakelock_held(dev_priv); -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ dev_priv->guc.interrupts_enabled = false; -+ -+ gen6_disable_pm_irq(dev_priv, dev_priv->pm_guc_events); -+ -+ spin_unlock_irq(&dev_priv->irq_lock); -+ synchronize_irq(dev_priv->drm.irq); -+ -+ gen9_reset_guc_interrupts(dev_priv); -+} -+ -+/** -+ * bdw_update_port_irq - update DE port interrupt -+ * @dev_priv: driver private -+ * @interrupt_mask: mask of interrupt bits to update -+ * @enabled_irq_mask: mask of interrupt bits to enable -+ */ -+static void bdw_update_port_irq(struct drm_i915_private *dev_priv, -+ u32 interrupt_mask, -+ u32 enabled_irq_mask) -+{ -+ u32 new_val; -+ u32 old_val; -+ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ -+ WARN_ON(enabled_irq_mask & ~interrupt_mask); -+ -+ if (WARN_ON(!intel_irqs_enabled(dev_priv))) -+ return; -+ -+ old_val = I915_READ(GEN8_DE_PORT_IMR); -+ -+ new_val = old_val; -+ new_val &= ~interrupt_mask; -+ new_val |= (~enabled_irq_mask & interrupt_mask); -+ -+ if (new_val != old_val) { -+ I915_WRITE(GEN8_DE_PORT_IMR, new_val); -+ POSTING_READ(GEN8_DE_PORT_IMR); -+ } -+} -+ -+/** -+ * bdw_update_pipe_irq - update DE pipe interrupt -+ * @dev_priv: driver private -+ * @pipe: pipe whose interrupt to update -+ * @interrupt_mask: mask of interrupt bits to update -+ * @enabled_irq_mask: mask of interrupt bits to enable -+ */ -+void bdw_update_pipe_irq(struct drm_i915_private *dev_priv, -+ enum pipe pipe, -+ u32 interrupt_mask, -+ u32 enabled_irq_mask) -+{ -+ u32 new_val; -+ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ -+ WARN_ON(enabled_irq_mask & ~interrupt_mask); -+ -+ if (WARN_ON(!intel_irqs_enabled(dev_priv))) -+ return; -+ -+ new_val = dev_priv->de_irq_mask[pipe]; -+ new_val &= ~interrupt_mask; -+ new_val |= (~enabled_irq_mask & interrupt_mask); -+ -+ if (new_val != dev_priv->de_irq_mask[pipe]) { -+ dev_priv->de_irq_mask[pipe] = new_val; -+ I915_WRITE(GEN8_DE_PIPE_IMR(pipe), dev_priv->de_irq_mask[pipe]); -+ POSTING_READ(GEN8_DE_PIPE_IMR(pipe)); -+ } -+} -+ -+/** -+ * ibx_display_interrupt_update - update SDEIMR -+ * @dev_priv: driver private -+ * @interrupt_mask: mask of interrupt bits to update -+ * @enabled_irq_mask: mask of interrupt bits to enable -+ */ -+void ibx_display_interrupt_update(struct drm_i915_private *dev_priv, -+ u32 interrupt_mask, -+ u32 enabled_irq_mask) -+{ -+ u32 sdeimr = I915_READ(SDEIMR); -+ sdeimr &= ~interrupt_mask; -+ sdeimr |= (~enabled_irq_mask & interrupt_mask); -+ -+ WARN_ON(enabled_irq_mask & ~interrupt_mask); -+ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ -+ if (WARN_ON(!intel_irqs_enabled(dev_priv))) -+ return; -+ -+ I915_WRITE(SDEIMR, sdeimr); -+ POSTING_READ(SDEIMR); -+} -+ -+u32 i915_pipestat_enable_mask(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ u32 status_mask = dev_priv->pipestat_irq_mask[pipe]; -+ u32 enable_mask = status_mask << 16; -+ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ -+ if (INTEL_GEN(dev_priv) < 5) -+ goto out; -+ -+ /* -+ * On pipe A we don't support the PSR interrupt yet, -+ * on pipe B and C the same bit MBZ. -+ */ -+ if (WARN_ON_ONCE(status_mask & PIPE_A_PSR_STATUS_VLV)) -+ return 0; -+ /* -+ * On pipe B and C we don't support the PSR interrupt yet, on pipe -+ * A the same bit is for perf counters which we don't use either. -+ */ -+ if (WARN_ON_ONCE(status_mask & PIPE_B_PSR_STATUS_VLV)) -+ return 0; -+ -+ enable_mask &= ~(PIPE_FIFO_UNDERRUN_STATUS | -+ SPRITE0_FLIP_DONE_INT_EN_VLV | -+ SPRITE1_FLIP_DONE_INT_EN_VLV); -+ if (status_mask & SPRITE0_FLIP_DONE_INT_STATUS_VLV) -+ enable_mask |= SPRITE0_FLIP_DONE_INT_EN_VLV; -+ if (status_mask & SPRITE1_FLIP_DONE_INT_STATUS_VLV) -+ enable_mask |= SPRITE1_FLIP_DONE_INT_EN_VLV; -+ -+out: -+ WARN_ONCE(enable_mask & ~PIPESTAT_INT_ENABLE_MASK || -+ status_mask & ~PIPESTAT_INT_STATUS_MASK, -+ "pipe %c: enable_mask=0x%x, status_mask=0x%x\n", -+ pipe_name(pipe), enable_mask, status_mask); -+ -+ return enable_mask; -+} -+ -+void i915_enable_pipestat(struct drm_i915_private *dev_priv, -+ enum pipe pipe, u32 status_mask) -+{ -+ i915_reg_t reg = PIPESTAT(pipe); -+ u32 enable_mask; -+ -+ WARN_ONCE(status_mask & ~PIPESTAT_INT_STATUS_MASK, -+ "pipe %c: status_mask=0x%x\n", -+ pipe_name(pipe), status_mask); -+ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ WARN_ON(!intel_irqs_enabled(dev_priv)); -+ -+ if ((dev_priv->pipestat_irq_mask[pipe] & status_mask) == status_mask) -+ return; -+ -+ dev_priv->pipestat_irq_mask[pipe] |= status_mask; -+ enable_mask = i915_pipestat_enable_mask(dev_priv, pipe); -+ -+ I915_WRITE(reg, enable_mask | status_mask); -+ POSTING_READ(reg); -+} -+ -+void i915_disable_pipestat(struct drm_i915_private *dev_priv, -+ enum pipe pipe, u32 status_mask) -+{ -+ i915_reg_t reg = PIPESTAT(pipe); -+ u32 enable_mask; -+ -+ WARN_ONCE(status_mask & ~PIPESTAT_INT_STATUS_MASK, -+ "pipe %c: status_mask=0x%x\n", -+ pipe_name(pipe), status_mask); -+ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ WARN_ON(!intel_irqs_enabled(dev_priv)); -+ -+ if ((dev_priv->pipestat_irq_mask[pipe] & status_mask) == 0) -+ return; -+ -+ dev_priv->pipestat_irq_mask[pipe] &= ~status_mask; -+ enable_mask = i915_pipestat_enable_mask(dev_priv, pipe); -+ -+ I915_WRITE(reg, enable_mask | status_mask); -+ POSTING_READ(reg); -+} -+ -+static bool i915_has_asle(struct drm_i915_private *dev_priv) -+{ -+ if (!dev_priv->opregion.asle) -+ return false; -+ -+ return IS_PINEVIEW(dev_priv) || IS_MOBILE(dev_priv); -+} -+ -+/** -+ * i915_enable_asle_pipestat - enable ASLE pipestat for OpRegion -+ * @dev_priv: i915 device private -+ */ -+static void i915_enable_asle_pipestat(struct drm_i915_private *dev_priv) -+{ -+ if (!i915_has_asle(dev_priv)) -+ return; -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ -+ i915_enable_pipestat(dev_priv, PIPE_B, PIPE_LEGACY_BLC_EVENT_STATUS); -+ if (INTEL_GEN(dev_priv) >= 4) -+ i915_enable_pipestat(dev_priv, PIPE_A, -+ PIPE_LEGACY_BLC_EVENT_STATUS); -+ -+ spin_unlock_irq(&dev_priv->irq_lock); -+} -+ -+/* -+ * This timing diagram depicts the video signal in and -+ * around the vertical blanking period. -+ * -+ * Assumptions about the fictitious mode used in this example: -+ * vblank_start >= 3 -+ * vsync_start = vblank_start + 1 -+ * vsync_end = vblank_start + 2 -+ * vtotal = vblank_start + 3 -+ * -+ * start of vblank: -+ * latch double buffered registers -+ * increment frame counter (ctg+) -+ * generate start of vblank interrupt (gen4+) -+ * | -+ * | frame start: -+ * | generate frame start interrupt (aka. vblank interrupt) (gmch) -+ * | may be shifted forward 1-3 extra lines via PIPECONF -+ * | | -+ * | | start of vsync: -+ * | | generate vsync interrupt -+ * | | | -+ * ___xxxx___ ___xxxx___ ___xxxx___ ___xxxx___ ___xxxx___ ___xxxx -+ * . \hs/ . \hs/ \hs/ \hs/ . \hs/ -+ * ----va---> <-----------------vb--------------------> <--------va------------- -+ * | | <----vs-----> | -+ * -vbs-----> <---vbs+1---> <---vbs+2---> <-----0-----> <-----1-----> <-----2--- (scanline counter gen2) -+ * -vbs-2---> <---vbs-1---> <---vbs-----> <---vbs+1---> <---vbs+2---> <-----0--- (scanline counter gen3+) -+ * -vbs-2---> <---vbs-2---> <---vbs-1---> <---vbs-----> <---vbs+1---> <---vbs+2- (scanline counter hsw+ hdmi) -+ * | | | -+ * last visible pixel first visible pixel -+ * | increment frame counter (gen3/4) -+ * pixel counter = vblank_start * htotal pixel counter = 0 (gen3/4) -+ * -+ * x = horizontal active -+ * _ = horizontal blanking -+ * hs = horizontal sync -+ * va = vertical active -+ * vb = vertical blanking -+ * vs = vertical sync -+ * vbs = vblank_start (number) -+ * -+ * Summary: -+ * - most events happen at the start of horizontal sync -+ * - frame start happens at the start of horizontal blank, 1-4 lines -+ * (depending on PIPECONF settings) after the start of vblank -+ * - gen3/4 pixel and frame counter are synchronized with the start -+ * of horizontal active on the first line of vertical active -+ */ -+ -+/* Called from drm generic code, passed a 'crtc', which -+ * we use as a pipe index -+ */ -+static u32 i915_get_vblank_counter(struct drm_device *dev, unsigned int pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; -+ const struct drm_display_mode *mode = &vblank->hwmode; -+ i915_reg_t high_frame, low_frame; -+ u32 high1, high2, low, pixel, vbl_start, hsync_start, htotal; -+ unsigned long irqflags; -+ -+ /* -+ * On i965gm TV output the frame counter only works up to -+ * the point when we enable the TV encoder. After that the -+ * frame counter ceases to work and reads zero. We need a -+ * vblank wait before enabling the TV encoder and so we -+ * have to enable vblank interrupts while the frame counter -+ * is still in a working state. However the core vblank code -+ * does not like us returning non-zero frame counter values -+ * when we've told it that we don't have a working frame -+ * counter. Thus we must stop non-zero values leaking out. -+ */ -+ if (!vblank->max_vblank_count) -+ return 0; -+ -+ htotal = mode->crtc_htotal; -+ hsync_start = mode->crtc_hsync_start; -+ vbl_start = mode->crtc_vblank_start; -+ if (mode->flags & DRM_MODE_FLAG_INTERLACE) -+ vbl_start = DIV_ROUND_UP(vbl_start, 2); -+ -+ /* Convert to pixel count */ -+ vbl_start *= htotal; -+ -+ /* Start of vblank event occurs at start of hsync */ -+ vbl_start -= htotal - hsync_start; -+ -+ high_frame = PIPEFRAME(pipe); -+ low_frame = PIPEFRAMEPIXEL(pipe); -+ -+ spin_lock_irqsave(&dev_priv->uncore.lock, irqflags); -+ -+ /* -+ * High & low register fields aren't synchronized, so make sure -+ * we get a low value that's stable across two reads of the high -+ * register. -+ */ -+ do { -+ high1 = I915_READ_FW(high_frame) & PIPE_FRAME_HIGH_MASK; -+ low = I915_READ_FW(low_frame); -+ high2 = I915_READ_FW(high_frame) & PIPE_FRAME_HIGH_MASK; -+ } while (high1 != high2); -+ -+ spin_unlock_irqrestore(&dev_priv->uncore.lock, irqflags); -+ -+ high1 >>= PIPE_FRAME_HIGH_SHIFT; -+ pixel = low & PIPE_PIXEL_MASK; -+ low >>= PIPE_FRAME_LOW_SHIFT; -+ -+ /* -+ * The frame counter increments at beginning of active. -+ * Cook up a vblank counter by also checking the pixel -+ * counter against vblank start. -+ */ -+ return (((high1 << 8) | low) + (pixel >= vbl_start)) & 0xffffff; -+} -+ -+static u32 g4x_get_vblank_counter(struct drm_device *dev, unsigned int pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ return I915_READ(PIPE_FRMCOUNT_G4X(pipe)); -+} -+ -+/* -+ * On certain encoders on certain platforms, pipe -+ * scanline register will not work to get the scanline, -+ * since the timings are driven from the PORT or issues -+ * with scanline register updates. -+ * This function will use Framestamp and current -+ * timestamp registers to calculate the scanline. -+ */ -+static u32 __intel_get_crtc_scanline_from_timestamp(struct intel_crtc *crtc) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct drm_vblank_crtc *vblank = -+ &crtc->base.dev->vblank[drm_crtc_index(&crtc->base)]; -+ const struct drm_display_mode *mode = &vblank->hwmode; -+ u32 vblank_start = mode->crtc_vblank_start; -+ u32 vtotal = mode->crtc_vtotal; -+ u32 htotal = mode->crtc_htotal; -+ u32 clock = mode->crtc_clock; -+ u32 scanline, scan_prev_time, scan_curr_time, scan_post_time; -+ -+ /* -+ * To avoid the race condition where we might cross into the -+ * next vblank just between the PIPE_FRMTMSTMP and TIMESTAMP_CTR -+ * reads. We make sure we read PIPE_FRMTMSTMP and TIMESTAMP_CTR -+ * during the same frame. -+ */ -+ do { -+ /* -+ * This field provides read back of the display -+ * pipe frame time stamp. The time stamp value -+ * is sampled at every start of vertical blank. -+ */ -+ scan_prev_time = I915_READ_FW(PIPE_FRMTMSTMP(crtc->pipe)); -+ -+ /* -+ * The TIMESTAMP_CTR register has the current -+ * time stamp value. -+ */ -+ scan_curr_time = I915_READ_FW(IVB_TIMESTAMP_CTR); -+ -+ scan_post_time = I915_READ_FW(PIPE_FRMTMSTMP(crtc->pipe)); -+ } while (scan_post_time != scan_prev_time); -+ -+ scanline = div_u64(mul_u32_u32(scan_curr_time - scan_prev_time, -+ clock), 1000 * htotal); -+ scanline = min(scanline, vtotal - 1); -+ scanline = (scanline + vblank_start) % vtotal; -+ -+ return scanline; -+} -+ -+/* I915_READ_FW, only for fast reads of display block, no need for forcewake etc. */ -+static int __intel_get_crtc_scanline(struct intel_crtc *crtc) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ const struct drm_display_mode *mode; -+ struct drm_vblank_crtc *vblank; -+ enum pipe pipe = crtc->pipe; -+ int position, vtotal; -+ -+ if (!crtc->active) -+ return -1; -+ -+ vblank = &crtc->base.dev->vblank[drm_crtc_index(&crtc->base)]; -+ mode = &vblank->hwmode; -+ -+ if (mode->private_flags & I915_MODE_FLAG_GET_SCANLINE_FROM_TIMESTAMP) -+ return __intel_get_crtc_scanline_from_timestamp(crtc); -+ -+ vtotal = mode->crtc_vtotal; -+ if (mode->flags & DRM_MODE_FLAG_INTERLACE) -+ vtotal /= 2; -+ -+ if (IS_GEN(dev_priv, 2)) -+ position = I915_READ_FW(PIPEDSL(pipe)) & DSL_LINEMASK_GEN2; -+ else -+ position = I915_READ_FW(PIPEDSL(pipe)) & DSL_LINEMASK_GEN3; -+ -+ /* -+ * On HSW, the DSL reg (0x70000) appears to return 0 if we -+ * read it just before the start of vblank. So try it again -+ * so we don't accidentally end up spanning a vblank frame -+ * increment, causing the pipe_update_end() code to squak at us. -+ * -+ * The nature of this problem means we can't simply check the ISR -+ * bit and return the vblank start value; nor can we use the scanline -+ * debug register in the transcoder as it appears to have the same -+ * problem. We may need to extend this to include other platforms, -+ * but so far testing only shows the problem on HSW. -+ */ -+ if (HAS_DDI(dev_priv) && !position) { -+ int i, temp; -+ -+ for (i = 0; i < 100; i++) { -+ udelay(1); -+ temp = I915_READ_FW(PIPEDSL(pipe)) & DSL_LINEMASK_GEN3; -+ if (temp != position) { -+ position = temp; -+ break; -+ } -+ } -+ } -+ -+ /* -+ * See update_scanline_offset() for the details on the -+ * scanline_offset adjustment. -+ */ -+ return (position + crtc->scanline_offset) % vtotal; -+} -+ -+static bool i915_get_crtc_scanoutpos(struct drm_device *dev, unsigned int pipe, -+ bool in_vblank_irq, int *vpos, int *hpos, -+ ktime_t *stime, ktime_t *etime, -+ const struct drm_display_mode *mode) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crtc *intel_crtc = intel_get_crtc_for_pipe(dev_priv, -+ pipe); -+ int position; -+ int vbl_start, vbl_end, hsync_start, htotal, vtotal; -+ unsigned long irqflags; -+ bool use_scanline_counter = INTEL_GEN(dev_priv) >= 5 || -+ IS_G4X(dev_priv) || IS_GEN(dev_priv, 2) || -+ mode->private_flags & I915_MODE_FLAG_USE_SCANLINE_COUNTER; -+ -+ if (WARN_ON(!mode->crtc_clock)) { -+ DRM_DEBUG_DRIVER("trying to get scanoutpos for disabled " -+ "pipe %c\n", pipe_name(pipe)); -+ return false; -+ } -+ -+ htotal = mode->crtc_htotal; -+ hsync_start = mode->crtc_hsync_start; -+ vtotal = mode->crtc_vtotal; -+ vbl_start = mode->crtc_vblank_start; -+ vbl_end = mode->crtc_vblank_end; -+ -+ if (mode->flags & DRM_MODE_FLAG_INTERLACE) { -+ vbl_start = DIV_ROUND_UP(vbl_start, 2); -+ vbl_end /= 2; -+ vtotal /= 2; -+ } -+ -+ /* -+ * Lock uncore.lock, as we will do multiple timing critical raw -+ * register reads, potentially with preemption disabled, so the -+ * following code must not block on uncore.lock. -+ */ -+ spin_lock_irqsave(&dev_priv->uncore.lock, irqflags); -+ -+ /* preempt_disable_rt() should go right here in PREEMPT_RT patchset. */ -+ -+ /* Get optional system timestamp before query. */ -+ if (stime) -+ *stime = ktime_get(); -+ -+ if (use_scanline_counter) { -+ /* No obvious pixelcount register. Only query vertical -+ * scanout position from Display scan line register. -+ */ -+ position = __intel_get_crtc_scanline(intel_crtc); -+ } else { -+ /* Have access to pixelcount since start of frame. -+ * We can split this into vertical and horizontal -+ * scanout position. -+ */ -+ position = (I915_READ_FW(PIPEFRAMEPIXEL(pipe)) & PIPE_PIXEL_MASK) >> PIPE_PIXEL_SHIFT; -+ -+ /* convert to pixel counts */ -+ vbl_start *= htotal; -+ vbl_end *= htotal; -+ vtotal *= htotal; -+ -+ /* -+ * In interlaced modes, the pixel counter counts all pixels, -+ * so one field will have htotal more pixels. In order to avoid -+ * the reported position from jumping backwards when the pixel -+ * counter is beyond the length of the shorter field, just -+ * clamp the position the length of the shorter field. This -+ * matches how the scanline counter based position works since -+ * the scanline counter doesn't count the two half lines. -+ */ -+ if (position >= vtotal) -+ position = vtotal - 1; -+ -+ /* -+ * Start of vblank interrupt is triggered at start of hsync, -+ * just prior to the first active line of vblank. However we -+ * consider lines to start at the leading edge of horizontal -+ * active. So, should we get here before we've crossed into -+ * the horizontal active of the first line in vblank, we would -+ * not set the DRM_SCANOUTPOS_INVBL flag. In order to fix that, -+ * always add htotal-hsync_start to the current pixel position. -+ */ -+ position = (position + htotal - hsync_start) % vtotal; -+ } -+ -+ /* Get optional system timestamp after query. */ -+ if (etime) -+ *etime = ktime_get(); -+ -+ /* preempt_enable_rt() should go right here in PREEMPT_RT patchset. */ -+ -+ spin_unlock_irqrestore(&dev_priv->uncore.lock, irqflags); -+ -+ /* -+ * While in vblank, position will be negative -+ * counting up towards 0 at vbl_end. And outside -+ * vblank, position will be positive counting -+ * up since vbl_end. -+ */ -+ if (position >= vbl_start) -+ position -= vbl_end; -+ else -+ position += vtotal - vbl_end; -+ -+ if (use_scanline_counter) { -+ *vpos = position; -+ *hpos = 0; -+ } else { -+ *vpos = position / htotal; -+ *hpos = position - (*vpos * htotal); -+ } -+ -+ return true; -+} -+ -+int intel_get_crtc_scanline(struct intel_crtc *crtc) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ unsigned long irqflags; -+ int position; -+ -+ spin_lock_irqsave(&dev_priv->uncore.lock, irqflags); -+ position = __intel_get_crtc_scanline(crtc); -+ spin_unlock_irqrestore(&dev_priv->uncore.lock, irqflags); -+ -+ return position; -+} -+ -+static void ironlake_rps_change_irq_handler(struct drm_i915_private *dev_priv) -+{ -+ u32 busy_up, busy_down, max_avg, min_avg; -+ u8 new_delay; -+ -+ spin_lock(&mchdev_lock); -+ -+ I915_WRITE16(MEMINTRSTS, I915_READ(MEMINTRSTS)); -+ -+ new_delay = dev_priv->ips.cur_delay; -+ -+ I915_WRITE16(MEMINTRSTS, MEMINT_EVAL_CHG); -+ busy_up = I915_READ(RCPREVBSYTUPAVG); -+ busy_down = I915_READ(RCPREVBSYTDNAVG); -+ max_avg = I915_READ(RCBMAXAVG); -+ min_avg = I915_READ(RCBMINAVG); -+ -+ /* Handle RCS change request from hw */ -+ if (busy_up > max_avg) { -+ if (dev_priv->ips.cur_delay != dev_priv->ips.max_delay) -+ new_delay = dev_priv->ips.cur_delay - 1; -+ if (new_delay < dev_priv->ips.max_delay) -+ new_delay = dev_priv->ips.max_delay; -+ } else if (busy_down < min_avg) { -+ if (dev_priv->ips.cur_delay != dev_priv->ips.min_delay) -+ new_delay = dev_priv->ips.cur_delay + 1; -+ if (new_delay > dev_priv->ips.min_delay) -+ new_delay = dev_priv->ips.min_delay; -+ } -+ -+ if (ironlake_set_drps(dev_priv, new_delay)) -+ dev_priv->ips.cur_delay = new_delay; -+ -+ spin_unlock(&mchdev_lock); -+ -+ return; -+} -+ -+static void vlv_c0_read(struct drm_i915_private *dev_priv, -+ struct intel_rps_ei *ei) -+{ -+ ei->ktime = ktime_get_raw(); -+ ei->render_c0 = I915_READ(VLV_RENDER_C0_COUNT); -+ ei->media_c0 = I915_READ(VLV_MEDIA_C0_COUNT); -+} -+ -+void gen6_rps_reset_ei(struct drm_i915_private *dev_priv) -+{ -+ memset(&dev_priv->gt_pm.rps.ei, 0, sizeof(dev_priv->gt_pm.rps.ei)); -+} -+ -+static u32 vlv_wa_c0_ei(struct drm_i915_private *dev_priv, u32 pm_iir) -+{ -+ struct intel_rps *rps = &dev_priv->gt_pm.rps; -+ const struct intel_rps_ei *prev = &rps->ei; -+ struct intel_rps_ei now; -+ u32 events = 0; -+ -+ if ((pm_iir & GEN6_PM_RP_UP_EI_EXPIRED) == 0) -+ return 0; -+ -+ vlv_c0_read(dev_priv, &now); -+ -+ if (prev->ktime) { -+ u64 time, c0; -+ u32 render, media; -+ -+ time = ktime_us_delta(now.ktime, prev->ktime); -+ -+ time *= dev_priv->czclk_freq; -+ -+ /* Workload can be split between render + media, -+ * e.g. SwapBuffers being blitted in X after being rendered in -+ * mesa. To account for this we need to combine both engines -+ * into our activity counter. -+ */ -+ render = now.render_c0 - prev->render_c0; -+ media = now.media_c0 - prev->media_c0; -+ c0 = max(render, media); -+ c0 *= 1000 * 100 << 8; /* to usecs and scale to threshold% */ -+ -+ if (c0 > time * rps->power.up_threshold) -+ events = GEN6_PM_RP_UP_THRESHOLD; -+ else if (c0 < time * rps->power.down_threshold) -+ events = GEN6_PM_RP_DOWN_THRESHOLD; -+ } -+ -+ rps->ei = now; -+ return events; -+} -+ -+static void gen6_pm_rps_work(struct work_struct *work) -+{ -+ struct drm_i915_private *dev_priv = -+ container_of(work, struct drm_i915_private, gt_pm.rps.work); -+ struct intel_rps *rps = &dev_priv->gt_pm.rps; -+ bool client_boost = false; -+ int new_delay, adj, min, max; -+ u32 pm_iir = 0; -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ if (rps->interrupts_enabled) { -+ pm_iir = fetch_and_zero(&rps->pm_iir); -+ client_boost = atomic_read(&rps->num_waiters); -+ } -+ spin_unlock_irq(&dev_priv->irq_lock); -+ -+ /* Make sure we didn't queue anything we're not going to process. */ -+ WARN_ON(pm_iir & ~dev_priv->pm_rps_events); -+ if ((pm_iir & dev_priv->pm_rps_events) == 0 && !client_boost) -+ goto out; -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ -+ pm_iir |= vlv_wa_c0_ei(dev_priv, pm_iir); -+ -+ adj = rps->last_adj; -+ new_delay = rps->cur_freq; -+ min = rps->min_freq_softlimit; -+ max = rps->max_freq_softlimit; -+ if (client_boost) -+ max = rps->max_freq; -+ if (client_boost && new_delay < rps->boost_freq) { -+ new_delay = rps->boost_freq; -+ adj = 0; -+ } else if (pm_iir & GEN6_PM_RP_UP_THRESHOLD) { -+ if (adj > 0) -+ adj *= 2; -+ else /* CHV needs even encode values */ -+ adj = IS_CHERRYVIEW(dev_priv) ? 2 : 1; -+ -+ if (new_delay >= rps->max_freq_softlimit) -+ adj = 0; -+ } else if (client_boost) { -+ adj = 0; -+ } else if (pm_iir & GEN6_PM_RP_DOWN_TIMEOUT) { -+ if (rps->cur_freq > rps->efficient_freq) -+ new_delay = rps->efficient_freq; -+ else if (rps->cur_freq > rps->min_freq_softlimit) -+ new_delay = rps->min_freq_softlimit; -+ adj = 0; -+ } else if (pm_iir & GEN6_PM_RP_DOWN_THRESHOLD) { -+ if (adj < 0) -+ adj *= 2; -+ else /* CHV needs even encode values */ -+ adj = IS_CHERRYVIEW(dev_priv) ? -2 : -1; -+ -+ if (new_delay <= rps->min_freq_softlimit) -+ adj = 0; -+ } else { /* unknown event */ -+ adj = 0; -+ } -+ -+ rps->last_adj = adj; -+ -+ /* -+ * Limit deboosting and boosting to keep ourselves at the extremes -+ * when in the respective power modes (i.e. slowly decrease frequencies -+ * while in the HIGH_POWER zone and slowly increase frequencies while -+ * in the LOW_POWER zone). On idle, we will hit the timeout and drop -+ * to the next level quickly, and conversely if busy we expect to -+ * hit a waitboost and rapidly switch into max power. -+ */ -+ if ((adj < 0 && rps->power.mode == HIGH_POWER) || -+ (adj > 0 && rps->power.mode == LOW_POWER)) -+ rps->last_adj = 0; -+ -+ /* sysfs frequency interfaces may have snuck in while servicing the -+ * interrupt -+ */ -+ new_delay += adj; -+ new_delay = clamp_t(int, new_delay, min, max); -+ -+ if (intel_set_rps(dev_priv, new_delay)) { -+ DRM_DEBUG_DRIVER("Failed to set new GPU frequency\n"); -+ rps->last_adj = 0; -+ } -+ -+ mutex_unlock(&dev_priv->pcu_lock); -+ -+out: -+ /* Make sure not to corrupt PMIMR state used by ringbuffer on GEN6 */ -+ spin_lock_irq(&dev_priv->irq_lock); -+ if (rps->interrupts_enabled) -+ gen6_unmask_pm_irq(dev_priv, dev_priv->pm_rps_events); -+ spin_unlock_irq(&dev_priv->irq_lock); -+} -+ -+ -+/** -+ * ivybridge_parity_work - Workqueue called when a parity error interrupt -+ * occurred. -+ * @work: workqueue struct -+ * -+ * Doesn't actually do anything except notify userspace. As a consequence of -+ * this event, userspace should try to remap the bad rows since statistically -+ * it is likely the same row is more likely to go bad again. -+ */ -+static void ivybridge_parity_work(struct work_struct *work) -+{ -+ struct drm_i915_private *dev_priv = -+ container_of(work, typeof(*dev_priv), l3_parity.error_work); -+ u32 error_status, row, bank, subbank; -+ char *parity_event[6]; -+ u32 misccpctl; -+ u8 slice = 0; -+ -+ /* We must turn off DOP level clock gating to access the L3 registers. -+ * In order to prevent a get/put style interface, acquire struct mutex -+ * any time we access those registers. -+ */ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ -+ /* If we've screwed up tracking, just let the interrupt fire again */ -+ if (WARN_ON(!dev_priv->l3_parity.which_slice)) -+ goto out; -+ -+ misccpctl = I915_READ(GEN7_MISCCPCTL); -+ I915_WRITE(GEN7_MISCCPCTL, misccpctl & ~GEN7_DOP_CLOCK_GATE_ENABLE); -+ POSTING_READ(GEN7_MISCCPCTL); -+ -+ while ((slice = ffs(dev_priv->l3_parity.which_slice)) != 0) { -+ i915_reg_t reg; -+ -+ slice--; -+ if (WARN_ON_ONCE(slice >= NUM_L3_SLICES(dev_priv))) -+ break; -+ -+ dev_priv->l3_parity.which_slice &= ~(1<drm.primary->kdev->kobj, -+ KOBJ_CHANGE, parity_event); -+ -+ DRM_DEBUG("Parity error: Slice = %d, Row = %d, Bank = %d, Sub bank = %d.\n", -+ slice, row, bank, subbank); -+ -+ kfree(parity_event[4]); -+ kfree(parity_event[3]); -+ kfree(parity_event[2]); -+ kfree(parity_event[1]); -+ } -+ -+ I915_WRITE(GEN7_MISCCPCTL, misccpctl); -+ -+out: -+ WARN_ON(dev_priv->l3_parity.which_slice); -+ spin_lock_irq(&dev_priv->irq_lock); -+ gen5_enable_gt_irq(dev_priv, GT_PARITY_ERROR(dev_priv)); -+ spin_unlock_irq(&dev_priv->irq_lock); -+ -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+} -+ -+static void ivybridge_parity_error_irq_handler(struct drm_i915_private *dev_priv, -+ u32 iir) -+{ -+ if (!HAS_L3_DPF(dev_priv)) -+ return; -+ -+ spin_lock(&dev_priv->irq_lock); -+ gen5_disable_gt_irq(dev_priv, GT_PARITY_ERROR(dev_priv)); -+ spin_unlock(&dev_priv->irq_lock); -+ -+ iir &= GT_PARITY_ERROR(dev_priv); -+ if (iir & GT_RENDER_L3_PARITY_ERROR_INTERRUPT_S1) -+ dev_priv->l3_parity.which_slice |= 1 << 1; -+ -+ if (iir & GT_RENDER_L3_PARITY_ERROR_INTERRUPT) -+ dev_priv->l3_parity.which_slice |= 1 << 0; -+ -+ queue_work(dev_priv->wq, &dev_priv->l3_parity.error_work); -+} -+ -+static void ilk_gt_irq_handler(struct drm_i915_private *dev_priv, -+ u32 gt_iir) -+{ -+ if (gt_iir & GT_RENDER_USER_INTERRUPT) -+ intel_engine_breadcrumbs_irq(dev_priv->engine[RCS0]); -+ if (gt_iir & ILK_BSD_USER_INTERRUPT) -+ intel_engine_breadcrumbs_irq(dev_priv->engine[VCS0]); -+} -+ -+static void snb_gt_irq_handler(struct drm_i915_private *dev_priv, -+ u32 gt_iir) -+{ -+ if (gt_iir & GT_RENDER_USER_INTERRUPT) -+ intel_engine_breadcrumbs_irq(dev_priv->engine[RCS0]); -+ if (gt_iir & GT_BSD_USER_INTERRUPT) -+ intel_engine_breadcrumbs_irq(dev_priv->engine[VCS0]); -+ if (gt_iir & GT_BLT_USER_INTERRUPT) -+ intel_engine_breadcrumbs_irq(dev_priv->engine[BCS0]); -+ -+ if (gt_iir & (GT_BLT_CS_ERROR_INTERRUPT | -+ GT_BSD_CS_ERROR_INTERRUPT | -+ GT_RENDER_CS_MASTER_ERROR_INTERRUPT)) -+ DRM_DEBUG("Command parser error, gt_iir 0x%08x\n", gt_iir); -+ -+ if (gt_iir & GT_PARITY_ERROR(dev_priv)) -+ ivybridge_parity_error_irq_handler(dev_priv, gt_iir); -+} -+ -+static void -+gen8_cs_irq_handler(struct intel_engine_cs *engine, u32 iir) -+{ -+ bool tasklet = false; -+ -+ if (iir & GT_CONTEXT_SWITCH_INTERRUPT) -+ tasklet = true; -+ -+ if (iir & GT_RENDER_USER_INTERRUPT) { -+ intel_engine_breadcrumbs_irq(engine); -+ tasklet |= intel_engine_needs_breadcrumb_tasklet(engine); -+ } -+ -+ if (tasklet) -+ tasklet_hi_schedule(&engine->execlists.tasklet); -+} -+ -+static void gen8_gt_irq_ack(struct drm_i915_private *i915, -+ u32 master_ctl, u32 gt_iir[4]) -+{ -+ void __iomem * const regs = i915->uncore.regs; -+ -+#define GEN8_GT_IRQS (GEN8_GT_RCS_IRQ | \ -+ GEN8_GT_BCS_IRQ | \ -+ GEN8_GT_VCS0_IRQ | \ -+ GEN8_GT_VCS1_IRQ | \ -+ GEN8_GT_VECS_IRQ | \ -+ GEN8_GT_PM_IRQ | \ -+ GEN8_GT_GUC_IRQ) -+ -+ if (master_ctl & (GEN8_GT_RCS_IRQ | GEN8_GT_BCS_IRQ)) { -+ gt_iir[0] = raw_reg_read(regs, GEN8_GT_IIR(0)); -+ if (likely(gt_iir[0])) -+ raw_reg_write(regs, GEN8_GT_IIR(0), gt_iir[0]); -+ } -+ -+ if (master_ctl & (GEN8_GT_VCS0_IRQ | GEN8_GT_VCS1_IRQ)) { -+ gt_iir[1] = raw_reg_read(regs, GEN8_GT_IIR(1)); -+ if (likely(gt_iir[1])) -+ raw_reg_write(regs, GEN8_GT_IIR(1), gt_iir[1]); -+ } -+ -+ if (master_ctl & (GEN8_GT_PM_IRQ | GEN8_GT_GUC_IRQ)) { -+ gt_iir[2] = raw_reg_read(regs, GEN8_GT_IIR(2)); -+ if (likely(gt_iir[2])) -+ raw_reg_write(regs, GEN8_GT_IIR(2), gt_iir[2]); -+ } -+ -+ if (master_ctl & GEN8_GT_VECS_IRQ) { -+ gt_iir[3] = raw_reg_read(regs, GEN8_GT_IIR(3)); -+ if (likely(gt_iir[3])) -+ raw_reg_write(regs, GEN8_GT_IIR(3), gt_iir[3]); -+ } -+} -+ -+static void gen8_gt_irq_handler(struct drm_i915_private *i915, -+ u32 master_ctl, u32 gt_iir[4]) -+{ -+ if (master_ctl & (GEN8_GT_RCS_IRQ | GEN8_GT_BCS_IRQ)) { -+ gen8_cs_irq_handler(i915->engine[RCS0], -+ gt_iir[0] >> GEN8_RCS_IRQ_SHIFT); -+ gen8_cs_irq_handler(i915->engine[BCS0], -+ gt_iir[0] >> GEN8_BCS_IRQ_SHIFT); -+ } -+ -+ if (master_ctl & (GEN8_GT_VCS0_IRQ | GEN8_GT_VCS1_IRQ)) { -+ gen8_cs_irq_handler(i915->engine[VCS0], -+ gt_iir[1] >> GEN8_VCS0_IRQ_SHIFT); -+ gen8_cs_irq_handler(i915->engine[VCS1], -+ gt_iir[1] >> GEN8_VCS1_IRQ_SHIFT); -+ } -+ -+ if (master_ctl & GEN8_GT_VECS_IRQ) { -+ gen8_cs_irq_handler(i915->engine[VECS0], -+ gt_iir[3] >> GEN8_VECS_IRQ_SHIFT); -+ } -+ -+ if (master_ctl & (GEN8_GT_PM_IRQ | GEN8_GT_GUC_IRQ)) { -+ gen6_rps_irq_handler(i915, gt_iir[2]); -+ gen9_guc_irq_handler(i915, gt_iir[2]); -+ } -+} -+ -+static bool gen11_port_hotplug_long_detect(enum hpd_pin pin, u32 val) -+{ -+ switch (pin) { -+ case HPD_PORT_C: -+ return val & GEN11_HOTPLUG_CTL_LONG_DETECT(PORT_TC1); -+ case HPD_PORT_D: -+ return val & GEN11_HOTPLUG_CTL_LONG_DETECT(PORT_TC2); -+ case HPD_PORT_E: -+ return val & GEN11_HOTPLUG_CTL_LONG_DETECT(PORT_TC3); -+ case HPD_PORT_F: -+ return val & GEN11_HOTPLUG_CTL_LONG_DETECT(PORT_TC4); -+ default: -+ return false; -+ } -+} -+ -+static bool bxt_port_hotplug_long_detect(enum hpd_pin pin, u32 val) -+{ -+ switch (pin) { -+ case HPD_PORT_A: -+ return val & PORTA_HOTPLUG_LONG_DETECT; -+ case HPD_PORT_B: -+ return val & PORTB_HOTPLUG_LONG_DETECT; -+ case HPD_PORT_C: -+ return val & PORTC_HOTPLUG_LONG_DETECT; -+ default: -+ return false; -+ } -+} -+ -+static bool icp_ddi_port_hotplug_long_detect(enum hpd_pin pin, u32 val) -+{ -+ switch (pin) { -+ case HPD_PORT_A: -+ return val & ICP_DDIA_HPD_LONG_DETECT; -+ case HPD_PORT_B: -+ return val & ICP_DDIB_HPD_LONG_DETECT; -+ default: -+ return false; -+ } -+} -+ -+static bool icp_tc_port_hotplug_long_detect(enum hpd_pin pin, u32 val) -+{ -+ switch (pin) { -+ case HPD_PORT_C: -+ return val & ICP_TC_HPD_LONG_DETECT(PORT_TC1); -+ case HPD_PORT_D: -+ return val & ICP_TC_HPD_LONG_DETECT(PORT_TC2); -+ case HPD_PORT_E: -+ return val & ICP_TC_HPD_LONG_DETECT(PORT_TC3); -+ case HPD_PORT_F: -+ return val & ICP_TC_HPD_LONG_DETECT(PORT_TC4); -+ default: -+ return false; -+ } -+} -+ -+static bool spt_port_hotplug2_long_detect(enum hpd_pin pin, u32 val) -+{ -+ switch (pin) { -+ case HPD_PORT_E: -+ return val & PORTE_HOTPLUG_LONG_DETECT; -+ default: -+ return false; -+ } -+} -+ -+static bool spt_port_hotplug_long_detect(enum hpd_pin pin, u32 val) -+{ -+ switch (pin) { -+ case HPD_PORT_A: -+ return val & PORTA_HOTPLUG_LONG_DETECT; -+ case HPD_PORT_B: -+ return val & PORTB_HOTPLUG_LONG_DETECT; -+ case HPD_PORT_C: -+ return val & PORTC_HOTPLUG_LONG_DETECT; -+ case HPD_PORT_D: -+ return val & PORTD_HOTPLUG_LONG_DETECT; -+ default: -+ return false; -+ } -+} -+ -+static bool ilk_port_hotplug_long_detect(enum hpd_pin pin, u32 val) -+{ -+ switch (pin) { -+ case HPD_PORT_A: -+ return val & DIGITAL_PORTA_HOTPLUG_LONG_DETECT; -+ default: -+ return false; -+ } -+} -+ -+static bool pch_port_hotplug_long_detect(enum hpd_pin pin, u32 val) -+{ -+ switch (pin) { -+ case HPD_PORT_B: -+ return val & PORTB_HOTPLUG_LONG_DETECT; -+ case HPD_PORT_C: -+ return val & PORTC_HOTPLUG_LONG_DETECT; -+ case HPD_PORT_D: -+ return val & PORTD_HOTPLUG_LONG_DETECT; -+ default: -+ return false; -+ } -+} -+ -+static bool i9xx_port_hotplug_long_detect(enum hpd_pin pin, u32 val) -+{ -+ switch (pin) { -+ case HPD_PORT_B: -+ return val & PORTB_HOTPLUG_INT_LONG_PULSE; -+ case HPD_PORT_C: -+ return val & PORTC_HOTPLUG_INT_LONG_PULSE; -+ case HPD_PORT_D: -+ return val & PORTD_HOTPLUG_INT_LONG_PULSE; -+ default: -+ return false; -+ } -+} -+ -+/* -+ * Get a bit mask of pins that have triggered, and which ones may be long. -+ * This can be called multiple times with the same masks to accumulate -+ * hotplug detection results from several registers. -+ * -+ * Note that the caller is expected to zero out the masks initially. -+ */ -+static void intel_get_hpd_pins(struct drm_i915_private *dev_priv, -+ u32 *pin_mask, u32 *long_mask, -+ u32 hotplug_trigger, u32 dig_hotplug_reg, -+ const u32 hpd[HPD_NUM_PINS], -+ bool long_pulse_detect(enum hpd_pin pin, u32 val)) -+{ -+ enum hpd_pin pin; -+ -+ for_each_hpd_pin(pin) { -+ if ((hpd[pin] & hotplug_trigger) == 0) -+ continue; -+ -+ *pin_mask |= BIT(pin); -+ -+ if (long_pulse_detect(pin, dig_hotplug_reg)) -+ *long_mask |= BIT(pin); -+ } -+ -+ DRM_DEBUG_DRIVER("hotplug event received, stat 0x%08x, dig 0x%08x, pins 0x%08x, long 0x%08x\n", -+ hotplug_trigger, dig_hotplug_reg, *pin_mask, *long_mask); -+ -+} -+ -+static void gmbus_irq_handler(struct drm_i915_private *dev_priv) -+{ -+ wake_up_all(&dev_priv->gmbus_wait_queue); -+} -+ -+static void dp_aux_irq_handler(struct drm_i915_private *dev_priv) -+{ -+ wake_up_all(&dev_priv->gmbus_wait_queue); -+} -+ -+#if defined(CONFIG_DEBUG_FS) -+static void display_pipe_crc_irq_handler(struct drm_i915_private *dev_priv, -+ enum pipe pipe, -+ u32 crc0, u32 crc1, -+ u32 crc2, u32 crc3, -+ u32 crc4) -+{ -+ struct intel_pipe_crc *pipe_crc = &dev_priv->pipe_crc[pipe]; -+ struct intel_crtc *crtc = intel_get_crtc_for_pipe(dev_priv, pipe); -+ u32 crcs[5] = { crc0, crc1, crc2, crc3, crc4 }; -+ -+ trace_intel_pipe_crc(crtc, crcs); -+ -+ spin_lock(&pipe_crc->lock); -+ /* -+ * For some not yet identified reason, the first CRC is -+ * bonkers. So let's just wait for the next vblank and read -+ * out the buggy result. -+ * -+ * On GEN8+ sometimes the second CRC is bonkers as well, so -+ * don't trust that one either. -+ */ -+ if (pipe_crc->skipped <= 0 || -+ (INTEL_GEN(dev_priv) >= 8 && pipe_crc->skipped == 1)) { -+ pipe_crc->skipped++; -+ spin_unlock(&pipe_crc->lock); -+ return; -+ } -+ spin_unlock(&pipe_crc->lock); -+ -+ drm_crtc_add_crc_entry(&crtc->base, true, -+ drm_crtc_accurate_vblank_count(&crtc->base), -+ crcs); -+} -+#else -+static inline void -+display_pipe_crc_irq_handler(struct drm_i915_private *dev_priv, -+ enum pipe pipe, -+ u32 crc0, u32 crc1, -+ u32 crc2, u32 crc3, -+ u32 crc4) {} -+#endif -+ -+ -+static void hsw_pipe_crc_irq_handler(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ display_pipe_crc_irq_handler(dev_priv, pipe, -+ I915_READ(PIPE_CRC_RES_1_IVB(pipe)), -+ 0, 0, 0, 0); -+} -+ -+static void ivb_pipe_crc_irq_handler(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ display_pipe_crc_irq_handler(dev_priv, pipe, -+ I915_READ(PIPE_CRC_RES_1_IVB(pipe)), -+ I915_READ(PIPE_CRC_RES_2_IVB(pipe)), -+ I915_READ(PIPE_CRC_RES_3_IVB(pipe)), -+ I915_READ(PIPE_CRC_RES_4_IVB(pipe)), -+ I915_READ(PIPE_CRC_RES_5_IVB(pipe))); -+} -+ -+static void i9xx_pipe_crc_irq_handler(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ u32 res1, res2; -+ -+ if (INTEL_GEN(dev_priv) >= 3) -+ res1 = I915_READ(PIPE_CRC_RES_RES1_I915(pipe)); -+ else -+ res1 = 0; -+ -+ if (INTEL_GEN(dev_priv) >= 5 || IS_G4X(dev_priv)) -+ res2 = I915_READ(PIPE_CRC_RES_RES2_G4X(pipe)); -+ else -+ res2 = 0; -+ -+ display_pipe_crc_irq_handler(dev_priv, pipe, -+ I915_READ(PIPE_CRC_RES_RED(pipe)), -+ I915_READ(PIPE_CRC_RES_GREEN(pipe)), -+ I915_READ(PIPE_CRC_RES_BLUE(pipe)), -+ res1, res2); -+} -+ -+/* The RPS events need forcewake, so we add them to a work queue and mask their -+ * IMR bits until the work is done. Other interrupts can be processed without -+ * the work queue. */ -+static void gen11_rps_irq_handler(struct drm_i915_private *i915, u32 pm_iir) -+{ -+ struct intel_rps *rps = &i915->gt_pm.rps; -+ const u32 events = i915->pm_rps_events & pm_iir; -+ -+ lockdep_assert_held(&i915->irq_lock); -+ -+ if (unlikely(!events)) -+ return; -+ -+ gen6_mask_pm_irq(i915, events); -+ -+ if (!rps->interrupts_enabled) -+ return; -+ -+ rps->pm_iir |= events; -+ schedule_work(&rps->work); -+} -+ -+static void gen6_rps_irq_handler(struct drm_i915_private *dev_priv, u32 pm_iir) -+{ -+ struct intel_rps *rps = &dev_priv->gt_pm.rps; -+ -+ if (pm_iir & dev_priv->pm_rps_events) { -+ spin_lock(&dev_priv->irq_lock); -+ gen6_mask_pm_irq(dev_priv, pm_iir & dev_priv->pm_rps_events); -+ if (rps->interrupts_enabled) { -+ rps->pm_iir |= pm_iir & dev_priv->pm_rps_events; -+ schedule_work(&rps->work); -+ } -+ spin_unlock(&dev_priv->irq_lock); -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 8) -+ return; -+ -+ if (pm_iir & PM_VEBOX_USER_INTERRUPT) -+ intel_engine_breadcrumbs_irq(dev_priv->engine[VECS0]); -+ -+ if (pm_iir & PM_VEBOX_CS_ERROR_INTERRUPT) -+ DRM_DEBUG("Command parser error, pm_iir 0x%08x\n", pm_iir); -+} -+ -+static void gen9_guc_irq_handler(struct drm_i915_private *dev_priv, u32 gt_iir) -+{ -+ if (gt_iir & GEN9_GUC_TO_HOST_INT_EVENT) -+ intel_guc_to_host_event_handler(&dev_priv->guc); -+} -+ -+static void i9xx_pipestat_irq_reset(struct drm_i915_private *dev_priv) -+{ -+ enum pipe pipe; -+ -+ for_each_pipe(dev_priv, pipe) { -+ I915_WRITE(PIPESTAT(pipe), -+ PIPESTAT_INT_STATUS_MASK | -+ PIPE_FIFO_UNDERRUN_STATUS); -+ -+ dev_priv->pipestat_irq_mask[pipe] = 0; -+ } -+} -+ -+static void i9xx_pipestat_irq_ack(struct drm_i915_private *dev_priv, -+ u32 iir, u32 pipe_stats[I915_MAX_PIPES]) -+{ -+ int pipe; -+ -+ spin_lock(&dev_priv->irq_lock); -+ -+ if (!dev_priv->display_irqs_enabled) { -+ spin_unlock(&dev_priv->irq_lock); -+ return; -+ } -+ -+ for_each_pipe(dev_priv, pipe) { -+ i915_reg_t reg; -+ u32 status_mask, enable_mask, iir_bit = 0; -+ -+ /* -+ * PIPESTAT bits get signalled even when the interrupt is -+ * disabled with the mask bits, and some of the status bits do -+ * not generate interrupts at all (like the underrun bit). Hence -+ * we need to be careful that we only handle what we want to -+ * handle. -+ */ -+ -+ /* fifo underruns are filterered in the underrun handler. */ -+ status_mask = PIPE_FIFO_UNDERRUN_STATUS; -+ -+ switch (pipe) { -+ case PIPE_A: -+ iir_bit = I915_DISPLAY_PIPE_A_EVENT_INTERRUPT; -+ break; -+ case PIPE_B: -+ iir_bit = I915_DISPLAY_PIPE_B_EVENT_INTERRUPT; -+ break; -+ case PIPE_C: -+ iir_bit = I915_DISPLAY_PIPE_C_EVENT_INTERRUPT; -+ break; -+ } -+ if (iir & iir_bit) -+ status_mask |= dev_priv->pipestat_irq_mask[pipe]; -+ -+ if (!status_mask) -+ continue; -+ -+ reg = PIPESTAT(pipe); -+ pipe_stats[pipe] = I915_READ(reg) & status_mask; -+ enable_mask = i915_pipestat_enable_mask(dev_priv, pipe); -+ -+ /* -+ * Clear the PIPE*STAT regs before the IIR -+ * -+ * Toggle the enable bits to make sure we get an -+ * edge in the ISR pipe event bit if we don't clear -+ * all the enabled status bits. Otherwise the edge -+ * triggered IIR on i965/g4x wouldn't notice that -+ * an interrupt is still pending. -+ */ -+ if (pipe_stats[pipe]) { -+ I915_WRITE(reg, pipe_stats[pipe]); -+ I915_WRITE(reg, enable_mask); -+ } -+ } -+ spin_unlock(&dev_priv->irq_lock); -+} -+ -+static void i8xx_pipestat_irq_handler(struct drm_i915_private *dev_priv, -+ u16 iir, u32 pipe_stats[I915_MAX_PIPES]) -+{ -+ enum pipe pipe; -+ -+ for_each_pipe(dev_priv, pipe) { -+ if (pipe_stats[pipe] & PIPE_VBLANK_INTERRUPT_STATUS) -+ drm_handle_vblank(&dev_priv->drm, pipe); -+ -+ if (pipe_stats[pipe] & PIPE_CRC_DONE_INTERRUPT_STATUS) -+ i9xx_pipe_crc_irq_handler(dev_priv, pipe); -+ -+ if (pipe_stats[pipe] & PIPE_FIFO_UNDERRUN_STATUS) -+ intel_cpu_fifo_underrun_irq_handler(dev_priv, pipe); -+ } -+} -+ -+static void i915_pipestat_irq_handler(struct drm_i915_private *dev_priv, -+ u32 iir, u32 pipe_stats[I915_MAX_PIPES]) -+{ -+ bool blc_event = false; -+ enum pipe pipe; -+ -+ for_each_pipe(dev_priv, pipe) { -+ if (pipe_stats[pipe] & PIPE_VBLANK_INTERRUPT_STATUS) -+ drm_handle_vblank(&dev_priv->drm, pipe); -+ -+ if (pipe_stats[pipe] & PIPE_LEGACY_BLC_EVENT_STATUS) -+ blc_event = true; -+ -+ if (pipe_stats[pipe] & PIPE_CRC_DONE_INTERRUPT_STATUS) -+ i9xx_pipe_crc_irq_handler(dev_priv, pipe); -+ -+ if (pipe_stats[pipe] & PIPE_FIFO_UNDERRUN_STATUS) -+ intel_cpu_fifo_underrun_irq_handler(dev_priv, pipe); -+ } -+ -+ if (blc_event || (iir & I915_ASLE_INTERRUPT)) -+ intel_opregion_asle_intr(dev_priv); -+} -+ -+static void i965_pipestat_irq_handler(struct drm_i915_private *dev_priv, -+ u32 iir, u32 pipe_stats[I915_MAX_PIPES]) -+{ -+ bool blc_event = false; -+ enum pipe pipe; -+ -+ for_each_pipe(dev_priv, pipe) { -+ if (pipe_stats[pipe] & PIPE_START_VBLANK_INTERRUPT_STATUS) -+ drm_handle_vblank(&dev_priv->drm, pipe); -+ -+ if (pipe_stats[pipe] & PIPE_LEGACY_BLC_EVENT_STATUS) -+ blc_event = true; -+ -+ if (pipe_stats[pipe] & PIPE_CRC_DONE_INTERRUPT_STATUS) -+ i9xx_pipe_crc_irq_handler(dev_priv, pipe); -+ -+ if (pipe_stats[pipe] & PIPE_FIFO_UNDERRUN_STATUS) -+ intel_cpu_fifo_underrun_irq_handler(dev_priv, pipe); -+ } -+ -+ if (blc_event || (iir & I915_ASLE_INTERRUPT)) -+ intel_opregion_asle_intr(dev_priv); -+ -+ if (pipe_stats[0] & PIPE_GMBUS_INTERRUPT_STATUS) -+ gmbus_irq_handler(dev_priv); -+} -+ -+static void valleyview_pipestat_irq_handler(struct drm_i915_private *dev_priv, -+ u32 pipe_stats[I915_MAX_PIPES]) -+{ -+ enum pipe pipe; -+ -+ for_each_pipe(dev_priv, pipe) { -+ if (pipe_stats[pipe] & PIPE_START_VBLANK_INTERRUPT_STATUS) -+ drm_handle_vblank(&dev_priv->drm, pipe); -+ -+ if (pipe_stats[pipe] & PIPE_CRC_DONE_INTERRUPT_STATUS) -+ i9xx_pipe_crc_irq_handler(dev_priv, pipe); -+ -+ if (pipe_stats[pipe] & PIPE_FIFO_UNDERRUN_STATUS) -+ intel_cpu_fifo_underrun_irq_handler(dev_priv, pipe); -+ } -+ -+ if (pipe_stats[0] & PIPE_GMBUS_INTERRUPT_STATUS) -+ gmbus_irq_handler(dev_priv); -+} -+ -+static u32 i9xx_hpd_irq_ack(struct drm_i915_private *dev_priv) -+{ -+ u32 hotplug_status = 0, hotplug_status_mask; -+ int i; -+ -+ if (IS_G4X(dev_priv) || -+ IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ hotplug_status_mask = HOTPLUG_INT_STATUS_G4X | -+ DP_AUX_CHANNEL_MASK_INT_STATUS_G4X; -+ else -+ hotplug_status_mask = HOTPLUG_INT_STATUS_I915; -+ -+ /* -+ * We absolutely have to clear all the pending interrupt -+ * bits in PORT_HOTPLUG_STAT. Otherwise the ISR port -+ * interrupt bit won't have an edge, and the i965/g4x -+ * edge triggered IIR will not notice that an interrupt -+ * is still pending. We can't use PORT_HOTPLUG_EN to -+ * guarantee the edge as the act of toggling the enable -+ * bits can itself generate a new hotplug interrupt :( -+ */ -+ for (i = 0; i < 10; i++) { -+ u32 tmp = I915_READ(PORT_HOTPLUG_STAT) & hotplug_status_mask; -+ -+ if (tmp == 0) -+ return hotplug_status; -+ -+ hotplug_status |= tmp; -+ I915_WRITE(PORT_HOTPLUG_STAT, hotplug_status); -+ } -+ -+ WARN_ONCE(1, -+ "PORT_HOTPLUG_STAT did not clear (0x%08x)\n", -+ I915_READ(PORT_HOTPLUG_STAT)); -+ -+ return hotplug_status; -+} -+ -+static void i9xx_hpd_irq_handler(struct drm_i915_private *dev_priv, -+ u32 hotplug_status) -+{ -+ u32 pin_mask = 0, long_mask = 0; -+ -+ if (IS_G4X(dev_priv) || IS_VALLEYVIEW(dev_priv) || -+ IS_CHERRYVIEW(dev_priv)) { -+ u32 hotplug_trigger = hotplug_status & HOTPLUG_INT_STATUS_G4X; -+ -+ if (hotplug_trigger) { -+ intel_get_hpd_pins(dev_priv, &pin_mask, &long_mask, -+ hotplug_trigger, hotplug_trigger, -+ hpd_status_g4x, -+ i9xx_port_hotplug_long_detect); -+ -+ intel_hpd_irq_handler(dev_priv, pin_mask, long_mask); -+ } -+ -+ if (hotplug_status & DP_AUX_CHANNEL_MASK_INT_STATUS_G4X) -+ dp_aux_irq_handler(dev_priv); -+ } else { -+ u32 hotplug_trigger = hotplug_status & HOTPLUG_INT_STATUS_I915; -+ -+ if (hotplug_trigger) { -+ intel_get_hpd_pins(dev_priv, &pin_mask, &long_mask, -+ hotplug_trigger, hotplug_trigger, -+ hpd_status_i915, -+ i9xx_port_hotplug_long_detect); -+ intel_hpd_irq_handler(dev_priv, pin_mask, long_mask); -+ } -+ } -+} -+ -+static irqreturn_t valleyview_irq_handler(int irq, void *arg) -+{ -+ struct drm_device *dev = arg; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ irqreturn_t ret = IRQ_NONE; -+ -+ if (!intel_irqs_enabled(dev_priv)) -+ return IRQ_NONE; -+ -+ /* IRQs are synced during runtime_suspend, we don't require a wakeref */ -+ disable_rpm_wakeref_asserts(dev_priv); -+ -+ do { -+ u32 iir, gt_iir, pm_iir; -+ u32 pipe_stats[I915_MAX_PIPES] = {}; -+ u32 hotplug_status = 0; -+ u32 ier = 0; -+ -+ gt_iir = I915_READ(GTIIR); -+ pm_iir = I915_READ(GEN6_PMIIR); -+ iir = I915_READ(VLV_IIR); -+ -+ if (gt_iir == 0 && pm_iir == 0 && iir == 0) -+ break; -+ -+ ret = IRQ_HANDLED; -+ -+ /* -+ * Theory on interrupt generation, based on empirical evidence: -+ * -+ * x = ((VLV_IIR & VLV_IER) || -+ * (((GT_IIR & GT_IER) || (GEN6_PMIIR & GEN6_PMIER)) && -+ * (VLV_MASTER_IER & MASTER_INTERRUPT_ENABLE))); -+ * -+ * A CPU interrupt will only be raised when 'x' has a 0->1 edge. -+ * Hence we clear MASTER_INTERRUPT_ENABLE and VLV_IER to -+ * guarantee the CPU interrupt will be raised again even if we -+ * don't end up clearing all the VLV_IIR, GT_IIR, GEN6_PMIIR -+ * bits this time around. -+ */ -+ I915_WRITE(VLV_MASTER_IER, 0); -+ ier = I915_READ(VLV_IER); -+ I915_WRITE(VLV_IER, 0); -+ -+ if (gt_iir) -+ I915_WRITE(GTIIR, gt_iir); -+ if (pm_iir) -+ I915_WRITE(GEN6_PMIIR, pm_iir); -+ -+ if (iir & I915_DISPLAY_PORT_INTERRUPT) -+ hotplug_status = i9xx_hpd_irq_ack(dev_priv); -+ -+ /* Call regardless, as some status bits might not be -+ * signalled in iir */ -+ i9xx_pipestat_irq_ack(dev_priv, iir, pipe_stats); -+ -+ if (iir & (I915_LPE_PIPE_A_INTERRUPT | -+ I915_LPE_PIPE_B_INTERRUPT)) -+ intel_lpe_audio_irq_handler(dev_priv); -+ -+ /* -+ * VLV_IIR is single buffered, and reflects the level -+ * from PIPESTAT/PORT_HOTPLUG_STAT, hence clear it last. -+ */ -+ if (iir) -+ I915_WRITE(VLV_IIR, iir); -+ -+ I915_WRITE(VLV_IER, ier); -+ I915_WRITE(VLV_MASTER_IER, MASTER_INTERRUPT_ENABLE); -+ -+ if (gt_iir) -+ snb_gt_irq_handler(dev_priv, gt_iir); -+ if (pm_iir) -+ gen6_rps_irq_handler(dev_priv, pm_iir); -+ -+ if (hotplug_status) -+ i9xx_hpd_irq_handler(dev_priv, hotplug_status); -+ -+ valleyview_pipestat_irq_handler(dev_priv, pipe_stats); -+ } while (0); -+ -+ enable_rpm_wakeref_asserts(dev_priv); -+ -+ return ret; -+} -+ -+static irqreturn_t cherryview_irq_handler(int irq, void *arg) -+{ -+ struct drm_device *dev = arg; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ irqreturn_t ret = IRQ_NONE; -+ -+ if (!intel_irqs_enabled(dev_priv)) -+ return IRQ_NONE; -+ -+ /* IRQs are synced during runtime_suspend, we don't require a wakeref */ -+ disable_rpm_wakeref_asserts(dev_priv); -+ -+ do { -+ u32 master_ctl, iir; -+ u32 pipe_stats[I915_MAX_PIPES] = {}; -+ u32 hotplug_status = 0; -+ u32 gt_iir[4]; -+ u32 ier = 0; -+ -+ master_ctl = I915_READ(GEN8_MASTER_IRQ) & ~GEN8_MASTER_IRQ_CONTROL; -+ iir = I915_READ(VLV_IIR); -+ -+ if (master_ctl == 0 && iir == 0) -+ break; -+ -+ ret = IRQ_HANDLED; -+ -+ /* -+ * Theory on interrupt generation, based on empirical evidence: -+ * -+ * x = ((VLV_IIR & VLV_IER) || -+ * ((GEN8_MASTER_IRQ & ~GEN8_MASTER_IRQ_CONTROL) && -+ * (GEN8_MASTER_IRQ & GEN8_MASTER_IRQ_CONTROL))); -+ * -+ * A CPU interrupt will only be raised when 'x' has a 0->1 edge. -+ * Hence we clear GEN8_MASTER_IRQ_CONTROL and VLV_IER to -+ * guarantee the CPU interrupt will be raised again even if we -+ * don't end up clearing all the VLV_IIR and GEN8_MASTER_IRQ_CONTROL -+ * bits this time around. -+ */ -+ I915_WRITE(GEN8_MASTER_IRQ, 0); -+ ier = I915_READ(VLV_IER); -+ I915_WRITE(VLV_IER, 0); -+ -+ gen8_gt_irq_ack(dev_priv, master_ctl, gt_iir); -+ -+ if (iir & I915_DISPLAY_PORT_INTERRUPT) -+ hotplug_status = i9xx_hpd_irq_ack(dev_priv); -+ -+ /* Call regardless, as some status bits might not be -+ * signalled in iir */ -+ i9xx_pipestat_irq_ack(dev_priv, iir, pipe_stats); -+ -+ if (iir & (I915_LPE_PIPE_A_INTERRUPT | -+ I915_LPE_PIPE_B_INTERRUPT | -+ I915_LPE_PIPE_C_INTERRUPT)) -+ intel_lpe_audio_irq_handler(dev_priv); -+ -+ /* -+ * VLV_IIR is single buffered, and reflects the level -+ * from PIPESTAT/PORT_HOTPLUG_STAT, hence clear it last. -+ */ -+ if (iir) -+ I915_WRITE(VLV_IIR, iir); -+ -+ I915_WRITE(VLV_IER, ier); -+ I915_WRITE(GEN8_MASTER_IRQ, GEN8_MASTER_IRQ_CONTROL); -+ -+ gen8_gt_irq_handler(dev_priv, master_ctl, gt_iir); -+ -+ if (hotplug_status) -+ i9xx_hpd_irq_handler(dev_priv, hotplug_status); -+ -+ valleyview_pipestat_irq_handler(dev_priv, pipe_stats); -+ } while (0); -+ -+ enable_rpm_wakeref_asserts(dev_priv); -+ -+ return ret; -+} -+ -+static void ibx_hpd_irq_handler(struct drm_i915_private *dev_priv, -+ u32 hotplug_trigger, -+ const u32 hpd[HPD_NUM_PINS]) -+{ -+ u32 dig_hotplug_reg, pin_mask = 0, long_mask = 0; -+ -+ /* -+ * Somehow the PCH doesn't seem to really ack the interrupt to the CPU -+ * unless we touch the hotplug register, even if hotplug_trigger is -+ * zero. Not acking leads to "The master control interrupt lied (SDE)!" -+ * errors. -+ */ -+ dig_hotplug_reg = I915_READ(PCH_PORT_HOTPLUG); -+ if (!hotplug_trigger) { -+ u32 mask = PORTA_HOTPLUG_STATUS_MASK | -+ PORTD_HOTPLUG_STATUS_MASK | -+ PORTC_HOTPLUG_STATUS_MASK | -+ PORTB_HOTPLUG_STATUS_MASK; -+ dig_hotplug_reg &= ~mask; -+ } -+ -+ I915_WRITE(PCH_PORT_HOTPLUG, dig_hotplug_reg); -+ if (!hotplug_trigger) -+ return; -+ -+ intel_get_hpd_pins(dev_priv, &pin_mask, &long_mask, hotplug_trigger, -+ dig_hotplug_reg, hpd, -+ pch_port_hotplug_long_detect); -+ -+ intel_hpd_irq_handler(dev_priv, pin_mask, long_mask); -+} -+ -+static void ibx_irq_handler(struct drm_i915_private *dev_priv, u32 pch_iir) -+{ -+ int pipe; -+ u32 hotplug_trigger = pch_iir & SDE_HOTPLUG_MASK; -+ -+ ibx_hpd_irq_handler(dev_priv, hotplug_trigger, hpd_ibx); -+ -+ if (pch_iir & SDE_AUDIO_POWER_MASK) { -+ int port = ffs((pch_iir & SDE_AUDIO_POWER_MASK) >> -+ SDE_AUDIO_POWER_SHIFT); -+ DRM_DEBUG_DRIVER("PCH audio power change on port %d\n", -+ port_name(port)); -+ } -+ -+ if (pch_iir & SDE_AUX_MASK) -+ dp_aux_irq_handler(dev_priv); -+ -+ if (pch_iir & SDE_GMBUS) -+ gmbus_irq_handler(dev_priv); -+ -+ if (pch_iir & SDE_AUDIO_HDCP_MASK) -+ DRM_DEBUG_DRIVER("PCH HDCP audio interrupt\n"); -+ -+ if (pch_iir & SDE_AUDIO_TRANS_MASK) -+ DRM_DEBUG_DRIVER("PCH transcoder audio interrupt\n"); -+ -+ if (pch_iir & SDE_POISON) -+ DRM_ERROR("PCH poison interrupt\n"); -+ -+ if (pch_iir & SDE_FDI_MASK) -+ for_each_pipe(dev_priv, pipe) -+ DRM_DEBUG_DRIVER(" pipe %c FDI IIR: 0x%08x\n", -+ pipe_name(pipe), -+ I915_READ(FDI_RX_IIR(pipe))); -+ -+ if (pch_iir & (SDE_TRANSB_CRC_DONE | SDE_TRANSA_CRC_DONE)) -+ DRM_DEBUG_DRIVER("PCH transcoder CRC done interrupt\n"); -+ -+ if (pch_iir & (SDE_TRANSB_CRC_ERR | SDE_TRANSA_CRC_ERR)) -+ DRM_DEBUG_DRIVER("PCH transcoder CRC error interrupt\n"); -+ -+ if (pch_iir & SDE_TRANSA_FIFO_UNDER) -+ intel_pch_fifo_underrun_irq_handler(dev_priv, PIPE_A); -+ -+ if (pch_iir & SDE_TRANSB_FIFO_UNDER) -+ intel_pch_fifo_underrun_irq_handler(dev_priv, PIPE_B); -+} -+ -+static void ivb_err_int_handler(struct drm_i915_private *dev_priv) -+{ -+ u32 err_int = I915_READ(GEN7_ERR_INT); -+ enum pipe pipe; -+ -+ if (err_int & ERR_INT_POISON) -+ DRM_ERROR("Poison interrupt\n"); -+ -+ for_each_pipe(dev_priv, pipe) { -+ if (err_int & ERR_INT_FIFO_UNDERRUN(pipe)) -+ intel_cpu_fifo_underrun_irq_handler(dev_priv, pipe); -+ -+ if (err_int & ERR_INT_PIPE_CRC_DONE(pipe)) { -+ if (IS_IVYBRIDGE(dev_priv)) -+ ivb_pipe_crc_irq_handler(dev_priv, pipe); -+ else -+ hsw_pipe_crc_irq_handler(dev_priv, pipe); -+ } -+ } -+ -+ I915_WRITE(GEN7_ERR_INT, err_int); -+} -+ -+static void cpt_serr_int_handler(struct drm_i915_private *dev_priv) -+{ -+ u32 serr_int = I915_READ(SERR_INT); -+ enum pipe pipe; -+ -+ if (serr_int & SERR_INT_POISON) -+ DRM_ERROR("PCH poison interrupt\n"); -+ -+ for_each_pipe(dev_priv, pipe) -+ if (serr_int & SERR_INT_TRANS_FIFO_UNDERRUN(pipe)) -+ intel_pch_fifo_underrun_irq_handler(dev_priv, pipe); -+ -+ I915_WRITE(SERR_INT, serr_int); -+} -+ -+static void cpt_irq_handler(struct drm_i915_private *dev_priv, u32 pch_iir) -+{ -+ int pipe; -+ u32 hotplug_trigger = pch_iir & SDE_HOTPLUG_MASK_CPT; -+ -+ ibx_hpd_irq_handler(dev_priv, hotplug_trigger, hpd_cpt); -+ -+ if (pch_iir & SDE_AUDIO_POWER_MASK_CPT) { -+ int port = ffs((pch_iir & SDE_AUDIO_POWER_MASK_CPT) >> -+ SDE_AUDIO_POWER_SHIFT_CPT); -+ DRM_DEBUG_DRIVER("PCH audio power change on port %c\n", -+ port_name(port)); -+ } -+ -+ if (pch_iir & SDE_AUX_MASK_CPT) -+ dp_aux_irq_handler(dev_priv); -+ -+ if (pch_iir & SDE_GMBUS_CPT) -+ gmbus_irq_handler(dev_priv); -+ -+ if (pch_iir & SDE_AUDIO_CP_REQ_CPT) -+ DRM_DEBUG_DRIVER("Audio CP request interrupt\n"); -+ -+ if (pch_iir & SDE_AUDIO_CP_CHG_CPT) -+ DRM_DEBUG_DRIVER("Audio CP change interrupt\n"); -+ -+ if (pch_iir & SDE_FDI_MASK_CPT) -+ for_each_pipe(dev_priv, pipe) -+ DRM_DEBUG_DRIVER(" pipe %c FDI IIR: 0x%08x\n", -+ pipe_name(pipe), -+ I915_READ(FDI_RX_IIR(pipe))); -+ -+ if (pch_iir & SDE_ERROR_CPT) -+ cpt_serr_int_handler(dev_priv); -+} -+ -+static void icp_irq_handler(struct drm_i915_private *dev_priv, u32 pch_iir) -+{ -+ u32 ddi_hotplug_trigger = pch_iir & SDE_DDI_MASK_ICP; -+ u32 tc_hotplug_trigger = pch_iir & SDE_TC_MASK_ICP; -+ u32 pin_mask = 0, long_mask = 0; -+ -+ if (ddi_hotplug_trigger) { -+ u32 dig_hotplug_reg; -+ -+ dig_hotplug_reg = I915_READ(SHOTPLUG_CTL_DDI); -+ I915_WRITE(SHOTPLUG_CTL_DDI, dig_hotplug_reg); -+ -+ intel_get_hpd_pins(dev_priv, &pin_mask, &long_mask, -+ ddi_hotplug_trigger, -+ dig_hotplug_reg, hpd_icp, -+ icp_ddi_port_hotplug_long_detect); -+ } -+ -+ if (tc_hotplug_trigger) { -+ u32 dig_hotplug_reg; -+ -+ dig_hotplug_reg = I915_READ(SHOTPLUG_CTL_TC); -+ I915_WRITE(SHOTPLUG_CTL_TC, dig_hotplug_reg); -+ -+ intel_get_hpd_pins(dev_priv, &pin_mask, &long_mask, -+ tc_hotplug_trigger, -+ dig_hotplug_reg, hpd_icp, -+ icp_tc_port_hotplug_long_detect); -+ } -+ -+ if (pin_mask) -+ intel_hpd_irq_handler(dev_priv, pin_mask, long_mask); -+ -+ if (pch_iir & SDE_GMBUS_ICP) -+ gmbus_irq_handler(dev_priv); -+} -+ -+static void spt_irq_handler(struct drm_i915_private *dev_priv, u32 pch_iir) -+{ -+ u32 hotplug_trigger = pch_iir & SDE_HOTPLUG_MASK_SPT & -+ ~SDE_PORTE_HOTPLUG_SPT; -+ u32 hotplug2_trigger = pch_iir & SDE_PORTE_HOTPLUG_SPT; -+ u32 pin_mask = 0, long_mask = 0; -+ -+ if (hotplug_trigger) { -+ u32 dig_hotplug_reg; -+ -+ dig_hotplug_reg = I915_READ(PCH_PORT_HOTPLUG); -+ I915_WRITE(PCH_PORT_HOTPLUG, dig_hotplug_reg); -+ -+ intel_get_hpd_pins(dev_priv, &pin_mask, &long_mask, -+ hotplug_trigger, dig_hotplug_reg, hpd_spt, -+ spt_port_hotplug_long_detect); -+ } -+ -+ if (hotplug2_trigger) { -+ u32 dig_hotplug_reg; -+ -+ dig_hotplug_reg = I915_READ(PCH_PORT_HOTPLUG2); -+ I915_WRITE(PCH_PORT_HOTPLUG2, dig_hotplug_reg); -+ -+ intel_get_hpd_pins(dev_priv, &pin_mask, &long_mask, -+ hotplug2_trigger, dig_hotplug_reg, hpd_spt, -+ spt_port_hotplug2_long_detect); -+ } -+ -+ if (pin_mask) -+ intel_hpd_irq_handler(dev_priv, pin_mask, long_mask); -+ -+ if (pch_iir & SDE_GMBUS_CPT) -+ gmbus_irq_handler(dev_priv); -+} -+ -+static void ilk_hpd_irq_handler(struct drm_i915_private *dev_priv, -+ u32 hotplug_trigger, -+ const u32 hpd[HPD_NUM_PINS]) -+{ -+ u32 dig_hotplug_reg, pin_mask = 0, long_mask = 0; -+ -+ dig_hotplug_reg = I915_READ(DIGITAL_PORT_HOTPLUG_CNTRL); -+ I915_WRITE(DIGITAL_PORT_HOTPLUG_CNTRL, dig_hotplug_reg); -+ -+ intel_get_hpd_pins(dev_priv, &pin_mask, &long_mask, hotplug_trigger, -+ dig_hotplug_reg, hpd, -+ ilk_port_hotplug_long_detect); -+ -+ intel_hpd_irq_handler(dev_priv, pin_mask, long_mask); -+} -+ -+static void ilk_display_irq_handler(struct drm_i915_private *dev_priv, -+ u32 de_iir) -+{ -+ enum pipe pipe; -+ u32 hotplug_trigger = de_iir & DE_DP_A_HOTPLUG; -+ -+ if (hotplug_trigger) -+ ilk_hpd_irq_handler(dev_priv, hotplug_trigger, hpd_ilk); -+ -+ if (de_iir & DE_AUX_CHANNEL_A) -+ dp_aux_irq_handler(dev_priv); -+ -+ if (de_iir & DE_GSE) -+ intel_opregion_asle_intr(dev_priv); -+ -+ if (de_iir & DE_POISON) -+ DRM_ERROR("Poison interrupt\n"); -+ -+ for_each_pipe(dev_priv, pipe) { -+ if (de_iir & DE_PIPE_VBLANK(pipe)) -+ drm_handle_vblank(&dev_priv->drm, pipe); -+ -+ if (de_iir & DE_PIPE_FIFO_UNDERRUN(pipe)) -+ intel_cpu_fifo_underrun_irq_handler(dev_priv, pipe); -+ -+ if (de_iir & DE_PIPE_CRC_DONE(pipe)) -+ i9xx_pipe_crc_irq_handler(dev_priv, pipe); -+ } -+ -+ /* check event from PCH */ -+ if (de_iir & DE_PCH_EVENT) { -+ u32 pch_iir = I915_READ(SDEIIR); -+ -+ if (HAS_PCH_CPT(dev_priv)) -+ cpt_irq_handler(dev_priv, pch_iir); -+ else -+ ibx_irq_handler(dev_priv, pch_iir); -+ -+ /* should clear PCH hotplug event before clear CPU irq */ -+ I915_WRITE(SDEIIR, pch_iir); -+ } -+ -+ if (IS_GEN(dev_priv, 5) && de_iir & DE_PCU_EVENT) -+ ironlake_rps_change_irq_handler(dev_priv); -+} -+ -+static void ivb_display_irq_handler(struct drm_i915_private *dev_priv, -+ u32 de_iir) -+{ -+ enum pipe pipe; -+ u32 hotplug_trigger = de_iir & DE_DP_A_HOTPLUG_IVB; -+ -+ if (hotplug_trigger) -+ ilk_hpd_irq_handler(dev_priv, hotplug_trigger, hpd_ivb); -+ -+ if (de_iir & DE_ERR_INT_IVB) -+ ivb_err_int_handler(dev_priv); -+ -+ if (de_iir & DE_EDP_PSR_INT_HSW) { -+ u32 psr_iir = I915_READ(EDP_PSR_IIR); -+ -+ intel_psr_irq_handler(dev_priv, psr_iir); -+ I915_WRITE(EDP_PSR_IIR, psr_iir); -+ } -+ -+ if (de_iir & DE_AUX_CHANNEL_A_IVB) -+ dp_aux_irq_handler(dev_priv); -+ -+ if (de_iir & DE_GSE_IVB) -+ intel_opregion_asle_intr(dev_priv); -+ -+ for_each_pipe(dev_priv, pipe) { -+ if (de_iir & (DE_PIPE_VBLANK_IVB(pipe))) -+ drm_handle_vblank(&dev_priv->drm, pipe); -+ } -+ -+ /* check event from PCH */ -+ if (!HAS_PCH_NOP(dev_priv) && (de_iir & DE_PCH_EVENT_IVB)) { -+ u32 pch_iir = I915_READ(SDEIIR); -+ -+ cpt_irq_handler(dev_priv, pch_iir); -+ -+ /* clear PCH hotplug event before clear CPU irq */ -+ I915_WRITE(SDEIIR, pch_iir); -+ } -+} -+ -+/* -+ * To handle irqs with the minimum potential races with fresh interrupts, we: -+ * 1 - Disable Master Interrupt Control. -+ * 2 - Find the source(s) of the interrupt. -+ * 3 - Clear the Interrupt Identity bits (IIR). -+ * 4 - Process the interrupt(s) that had bits set in the IIRs. -+ * 5 - Re-enable Master Interrupt Control. -+ */ -+static irqreturn_t ironlake_irq_handler(int irq, void *arg) -+{ -+ struct drm_device *dev = arg; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ u32 de_iir, gt_iir, de_ier, sde_ier = 0; -+ irqreturn_t ret = IRQ_NONE; -+ -+ if (!intel_irqs_enabled(dev_priv)) -+ return IRQ_NONE; -+ -+ /* IRQs are synced during runtime_suspend, we don't require a wakeref */ -+ disable_rpm_wakeref_asserts(dev_priv); -+ -+ /* disable master interrupt before clearing iir */ -+ de_ier = I915_READ(DEIER); -+ I915_WRITE(DEIER, de_ier & ~DE_MASTER_IRQ_CONTROL); -+ -+ /* Disable south interrupts. We'll only write to SDEIIR once, so further -+ * interrupts will will be stored on its back queue, and then we'll be -+ * able to process them after we restore SDEIER (as soon as we restore -+ * it, we'll get an interrupt if SDEIIR still has something to process -+ * due to its back queue). */ -+ if (!HAS_PCH_NOP(dev_priv)) { -+ sde_ier = I915_READ(SDEIER); -+ I915_WRITE(SDEIER, 0); -+ } -+ -+ /* Find, clear, then process each source of interrupt */ -+ -+ gt_iir = I915_READ(GTIIR); -+ if (gt_iir) { -+ I915_WRITE(GTIIR, gt_iir); -+ ret = IRQ_HANDLED; -+ if (INTEL_GEN(dev_priv) >= 6) -+ snb_gt_irq_handler(dev_priv, gt_iir); -+ else -+ ilk_gt_irq_handler(dev_priv, gt_iir); -+ } -+ -+ de_iir = I915_READ(DEIIR); -+ if (de_iir) { -+ I915_WRITE(DEIIR, de_iir); -+ ret = IRQ_HANDLED; -+ if (INTEL_GEN(dev_priv) >= 7) -+ ivb_display_irq_handler(dev_priv, de_iir); -+ else -+ ilk_display_irq_handler(dev_priv, de_iir); -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 6) { -+ u32 pm_iir = I915_READ(GEN6_PMIIR); -+ if (pm_iir) { -+ I915_WRITE(GEN6_PMIIR, pm_iir); -+ ret = IRQ_HANDLED; -+ gen6_rps_irq_handler(dev_priv, pm_iir); -+ } -+ } -+ -+ I915_WRITE(DEIER, de_ier); -+ if (!HAS_PCH_NOP(dev_priv)) -+ I915_WRITE(SDEIER, sde_ier); -+ -+ /* IRQs are synced during runtime_suspend, we don't require a wakeref */ -+ enable_rpm_wakeref_asserts(dev_priv); -+ -+ return ret; -+} -+ -+static void bxt_hpd_irq_handler(struct drm_i915_private *dev_priv, -+ u32 hotplug_trigger, -+ const u32 hpd[HPD_NUM_PINS]) -+{ -+ u32 dig_hotplug_reg, pin_mask = 0, long_mask = 0; -+ -+ dig_hotplug_reg = I915_READ(PCH_PORT_HOTPLUG); -+ I915_WRITE(PCH_PORT_HOTPLUG, dig_hotplug_reg); -+ -+ intel_get_hpd_pins(dev_priv, &pin_mask, &long_mask, hotplug_trigger, -+ dig_hotplug_reg, hpd, -+ bxt_port_hotplug_long_detect); -+ -+ intel_hpd_irq_handler(dev_priv, pin_mask, long_mask); -+} -+ -+static void gen11_hpd_irq_handler(struct drm_i915_private *dev_priv, u32 iir) -+{ -+ u32 pin_mask = 0, long_mask = 0; -+ u32 trigger_tc = iir & GEN11_DE_TC_HOTPLUG_MASK; -+ u32 trigger_tbt = iir & GEN11_DE_TBT_HOTPLUG_MASK; -+ -+ if (trigger_tc) { -+ u32 dig_hotplug_reg; -+ -+ dig_hotplug_reg = I915_READ(GEN11_TC_HOTPLUG_CTL); -+ I915_WRITE(GEN11_TC_HOTPLUG_CTL, dig_hotplug_reg); -+ -+ intel_get_hpd_pins(dev_priv, &pin_mask, &long_mask, trigger_tc, -+ dig_hotplug_reg, hpd_gen11, -+ gen11_port_hotplug_long_detect); -+ } -+ -+ if (trigger_tbt) { -+ u32 dig_hotplug_reg; -+ -+ dig_hotplug_reg = I915_READ(GEN11_TBT_HOTPLUG_CTL); -+ I915_WRITE(GEN11_TBT_HOTPLUG_CTL, dig_hotplug_reg); -+ -+ intel_get_hpd_pins(dev_priv, &pin_mask, &long_mask, trigger_tbt, -+ dig_hotplug_reg, hpd_gen11, -+ gen11_port_hotplug_long_detect); -+ } -+ -+ if (pin_mask) -+ intel_hpd_irq_handler(dev_priv, pin_mask, long_mask); -+ else -+ DRM_ERROR("Unexpected DE HPD interrupt 0x%08x\n", iir); -+} -+ -+static u32 gen8_de_port_aux_mask(struct drm_i915_private *dev_priv) -+{ -+ u32 mask = GEN8_AUX_CHANNEL_A; -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ mask |= GEN9_AUX_CHANNEL_B | -+ GEN9_AUX_CHANNEL_C | -+ GEN9_AUX_CHANNEL_D; -+ -+ if (IS_CNL_WITH_PORT_F(dev_priv)) -+ mask |= CNL_AUX_CHANNEL_F; -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ mask |= ICL_AUX_CHANNEL_E | -+ CNL_AUX_CHANNEL_F; -+ -+ return mask; -+} -+ -+static irqreturn_t -+gen8_de_irq_handler(struct drm_i915_private *dev_priv, u32 master_ctl) -+{ -+ irqreturn_t ret = IRQ_NONE; -+ u32 iir; -+ enum pipe pipe; -+ -+ if (master_ctl & GEN8_DE_MISC_IRQ) { -+ iir = I915_READ(GEN8_DE_MISC_IIR); -+ if (iir) { -+ bool found = false; -+ -+ I915_WRITE(GEN8_DE_MISC_IIR, iir); -+ ret = IRQ_HANDLED; -+ -+ if (iir & GEN8_DE_MISC_GSE) { -+ intel_opregion_asle_intr(dev_priv); -+ found = true; -+ } -+ -+ if (iir & GEN8_DE_EDP_PSR) { -+ u32 psr_iir = I915_READ(EDP_PSR_IIR); -+ -+ intel_psr_irq_handler(dev_priv, psr_iir); -+ I915_WRITE(EDP_PSR_IIR, psr_iir); -+ found = true; -+ } -+ -+ if (!found) -+ DRM_ERROR("Unexpected DE Misc interrupt\n"); -+ } -+ else -+ DRM_ERROR("The master control interrupt lied (DE MISC)!\n"); -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 11 && (master_ctl & GEN11_DE_HPD_IRQ)) { -+ iir = I915_READ(GEN11_DE_HPD_IIR); -+ if (iir) { -+ I915_WRITE(GEN11_DE_HPD_IIR, iir); -+ ret = IRQ_HANDLED; -+ gen11_hpd_irq_handler(dev_priv, iir); -+ } else { -+ DRM_ERROR("The master control interrupt lied, (DE HPD)!\n"); -+ } -+ } -+ -+ if (master_ctl & GEN8_DE_PORT_IRQ) { -+ iir = I915_READ(GEN8_DE_PORT_IIR); -+ if (iir) { -+ u32 tmp_mask; -+ bool found = false; -+ -+ I915_WRITE(GEN8_DE_PORT_IIR, iir); -+ ret = IRQ_HANDLED; -+ -+ if (iir & gen8_de_port_aux_mask(dev_priv)) { -+ dp_aux_irq_handler(dev_priv); -+ found = true; -+ } -+ -+ if (IS_GEN9_LP(dev_priv)) { -+ tmp_mask = iir & BXT_DE_PORT_HOTPLUG_MASK; -+ if (tmp_mask) { -+ bxt_hpd_irq_handler(dev_priv, tmp_mask, -+ hpd_bxt); -+ found = true; -+ } -+ } else if (IS_BROADWELL(dev_priv)) { -+ tmp_mask = iir & GEN8_PORT_DP_A_HOTPLUG; -+ if (tmp_mask) { -+ ilk_hpd_irq_handler(dev_priv, -+ tmp_mask, hpd_bdw); -+ found = true; -+ } -+ } -+ -+ if (IS_GEN9_LP(dev_priv) && (iir & BXT_DE_PORT_GMBUS)) { -+ gmbus_irq_handler(dev_priv); -+ found = true; -+ } -+ -+ if (!found) -+ DRM_ERROR("Unexpected DE Port interrupt\n"); -+ } -+ else -+ DRM_ERROR("The master control interrupt lied (DE PORT)!\n"); -+ } -+ -+ for_each_pipe(dev_priv, pipe) { -+ u32 fault_errors; -+ -+ if (!(master_ctl & GEN8_DE_PIPE_IRQ(pipe))) -+ continue; -+ -+ iir = I915_READ(GEN8_DE_PIPE_IIR(pipe)); -+ if (!iir) { -+ DRM_ERROR("The master control interrupt lied (DE PIPE)!\n"); -+ continue; -+ } -+ -+ ret = IRQ_HANDLED; -+ I915_WRITE(GEN8_DE_PIPE_IIR(pipe), iir); -+ -+ if (iir & GEN8_PIPE_VBLANK) -+ drm_handle_vblank(&dev_priv->drm, pipe); -+ -+ if (iir & GEN8_PIPE_CDCLK_CRC_DONE) -+ hsw_pipe_crc_irq_handler(dev_priv, pipe); -+ -+ if (iir & GEN8_PIPE_FIFO_UNDERRUN) -+ intel_cpu_fifo_underrun_irq_handler(dev_priv, pipe); -+ -+ fault_errors = iir; -+ if (INTEL_GEN(dev_priv) >= 9) -+ fault_errors &= GEN9_DE_PIPE_IRQ_FAULT_ERRORS; -+ else -+ fault_errors &= GEN8_DE_PIPE_IRQ_FAULT_ERRORS; -+ -+ if (fault_errors) -+ DRM_ERROR("Fault errors on pipe %c: 0x%08x\n", -+ pipe_name(pipe), -+ fault_errors); -+ } -+ -+ if (HAS_PCH_SPLIT(dev_priv) && !HAS_PCH_NOP(dev_priv) && -+ master_ctl & GEN8_DE_PCH_IRQ) { -+ /* -+ * FIXME(BDW): Assume for now that the new interrupt handling -+ * scheme also closed the SDE interrupt handling race we've seen -+ * on older pch-split platforms. But this needs testing. -+ */ -+ iir = I915_READ(SDEIIR); -+ if (iir) { -+ I915_WRITE(SDEIIR, iir); -+ ret = IRQ_HANDLED; -+ -+ if (INTEL_PCH_TYPE(dev_priv) >= PCH_ICP) -+ icp_irq_handler(dev_priv, iir); -+ else if (INTEL_PCH_TYPE(dev_priv) >= PCH_SPT) -+ spt_irq_handler(dev_priv, iir); -+ else -+ cpt_irq_handler(dev_priv, iir); -+ } else { -+ /* -+ * Like on previous PCH there seems to be something -+ * fishy going on with forwarding PCH interrupts. -+ */ -+ DRM_DEBUG_DRIVER("The master control interrupt lied (SDE)!\n"); -+ } -+ } -+ -+ return ret; -+} -+ -+static inline u32 gen8_master_intr_disable(void __iomem * const regs) -+{ -+ raw_reg_write(regs, GEN8_MASTER_IRQ, 0); -+ -+ /* -+ * Now with master disabled, get a sample of level indications -+ * for this interrupt. Indications will be cleared on related acks. -+ * New indications can and will light up during processing, -+ * and will generate new interrupt after enabling master. -+ */ -+ return raw_reg_read(regs, GEN8_MASTER_IRQ); -+} -+ -+static inline void gen8_master_intr_enable(void __iomem * const regs) -+{ -+ raw_reg_write(regs, GEN8_MASTER_IRQ, GEN8_MASTER_IRQ_CONTROL); -+} -+ -+static irqreturn_t gen8_irq_handler(int irq, void *arg) -+{ -+ struct drm_i915_private *dev_priv = to_i915(arg); -+ void __iomem * const regs = dev_priv->uncore.regs; -+ u32 master_ctl; -+ u32 gt_iir[4]; -+ -+ if (!intel_irqs_enabled(dev_priv)) -+ return IRQ_NONE; -+ -+ master_ctl = gen8_master_intr_disable(regs); -+ if (!master_ctl) { -+ gen8_master_intr_enable(regs); -+ return IRQ_NONE; -+ } -+ -+ /* Find, clear, then process each source of interrupt */ -+ gen8_gt_irq_ack(dev_priv, master_ctl, gt_iir); -+ -+ /* IRQs are synced during runtime_suspend, we don't require a wakeref */ -+ if (master_ctl & ~GEN8_GT_IRQS) { -+ disable_rpm_wakeref_asserts(dev_priv); -+ gen8_de_irq_handler(dev_priv, master_ctl); -+ enable_rpm_wakeref_asserts(dev_priv); -+ } -+ -+ gen8_master_intr_enable(regs); -+ -+ gen8_gt_irq_handler(dev_priv, master_ctl, gt_iir); -+ -+ return IRQ_HANDLED; -+} -+ -+static u32 -+gen11_gt_engine_identity(struct drm_i915_private * const i915, -+ const unsigned int bank, const unsigned int bit) -+{ -+ void __iomem * const regs = i915->uncore.regs; -+ u32 timeout_ts; -+ u32 ident; -+ -+ lockdep_assert_held(&i915->irq_lock); -+ -+ raw_reg_write(regs, GEN11_IIR_REG_SELECTOR(bank), BIT(bit)); -+ -+ /* -+ * NB: Specs do not specify how long to spin wait, -+ * so we do ~100us as an educated guess. -+ */ -+ timeout_ts = (local_clock() >> 10) + 100; -+ do { -+ ident = raw_reg_read(regs, GEN11_INTR_IDENTITY_REG(bank)); -+ } while (!(ident & GEN11_INTR_DATA_VALID) && -+ !time_after32(local_clock() >> 10, timeout_ts)); -+ -+ if (unlikely(!(ident & GEN11_INTR_DATA_VALID))) { -+ DRM_ERROR("INTR_IDENTITY_REG%u:%u 0x%08x not valid!\n", -+ bank, bit, ident); -+ return 0; -+ } -+ -+ raw_reg_write(regs, GEN11_INTR_IDENTITY_REG(bank), -+ GEN11_INTR_DATA_VALID); -+ -+ return ident; -+} -+ -+static void -+gen11_other_irq_handler(struct drm_i915_private * const i915, -+ const u8 instance, const u16 iir) -+{ -+ if (instance == OTHER_GTPM_INSTANCE) -+ return gen11_rps_irq_handler(i915, iir); -+ -+ WARN_ONCE(1, "unhandled other interrupt instance=0x%x, iir=0x%x\n", -+ instance, iir); -+} -+ -+static void -+gen11_engine_irq_handler(struct drm_i915_private * const i915, -+ const u8 class, const u8 instance, const u16 iir) -+{ -+ struct intel_engine_cs *engine; -+ -+ if (instance <= MAX_ENGINE_INSTANCE) -+ engine = i915->engine_class[class][instance]; -+ else -+ engine = NULL; -+ -+ if (likely(engine)) -+ return gen8_cs_irq_handler(engine, iir); -+ -+ WARN_ONCE(1, "unhandled engine interrupt class=0x%x, instance=0x%x\n", -+ class, instance); -+} -+ -+static void -+gen11_gt_identity_handler(struct drm_i915_private * const i915, -+ const u32 identity) -+{ -+ const u8 class = GEN11_INTR_ENGINE_CLASS(identity); -+ const u8 instance = GEN11_INTR_ENGINE_INSTANCE(identity); -+ const u16 intr = GEN11_INTR_ENGINE_INTR(identity); -+ -+ if (unlikely(!intr)) -+ return; -+ -+ if (class <= COPY_ENGINE_CLASS) -+ return gen11_engine_irq_handler(i915, class, instance, intr); -+ -+ if (class == OTHER_CLASS) -+ return gen11_other_irq_handler(i915, instance, intr); -+ -+ WARN_ONCE(1, "unknown interrupt class=0x%x, instance=0x%x, intr=0x%x\n", -+ class, instance, intr); -+} -+ -+static void -+gen11_gt_bank_handler(struct drm_i915_private * const i915, -+ const unsigned int bank) -+{ -+ void __iomem * const regs = i915->uncore.regs; -+ unsigned long intr_dw; -+ unsigned int bit; -+ -+ lockdep_assert_held(&i915->irq_lock); -+ -+ intr_dw = raw_reg_read(regs, GEN11_GT_INTR_DW(bank)); -+ -+ for_each_set_bit(bit, &intr_dw, 32) { -+ const u32 ident = gen11_gt_engine_identity(i915, bank, bit); -+ -+ gen11_gt_identity_handler(i915, ident); -+ } -+ -+ /* Clear must be after shared has been served for engine */ -+ raw_reg_write(regs, GEN11_GT_INTR_DW(bank), intr_dw); -+} -+ -+static void -+gen11_gt_irq_handler(struct drm_i915_private * const i915, -+ const u32 master_ctl) -+{ -+ unsigned int bank; -+ -+ spin_lock(&i915->irq_lock); -+ -+ for (bank = 0; bank < 2; bank++) { -+ if (master_ctl & GEN11_GT_DW_IRQ(bank)) -+ gen11_gt_bank_handler(i915, bank); -+ } -+ -+ spin_unlock(&i915->irq_lock); -+} -+ -+static u32 -+gen11_gu_misc_irq_ack(struct drm_i915_private *dev_priv, const u32 master_ctl) -+{ -+ void __iomem * const regs = dev_priv->uncore.regs; -+ u32 iir; -+ -+ if (!(master_ctl & GEN11_GU_MISC_IRQ)) -+ return 0; -+ -+ iir = raw_reg_read(regs, GEN11_GU_MISC_IIR); -+ if (likely(iir)) -+ raw_reg_write(regs, GEN11_GU_MISC_IIR, iir); -+ -+ return iir; -+} -+ -+static void -+gen11_gu_misc_irq_handler(struct drm_i915_private *dev_priv, const u32 iir) -+{ -+ if (iir & GEN11_GU_MISC_GSE) -+ intel_opregion_asle_intr(dev_priv); -+} -+ -+static inline u32 gen11_master_intr_disable(void __iomem * const regs) -+{ -+ raw_reg_write(regs, GEN11_GFX_MSTR_IRQ, 0); -+ -+ /* -+ * Now with master disabled, get a sample of level indications -+ * for this interrupt. Indications will be cleared on related acks. -+ * New indications can and will light up during processing, -+ * and will generate new interrupt after enabling master. -+ */ -+ return raw_reg_read(regs, GEN11_GFX_MSTR_IRQ); -+} -+ -+static inline void gen11_master_intr_enable(void __iomem * const regs) -+{ -+ raw_reg_write(regs, GEN11_GFX_MSTR_IRQ, GEN11_MASTER_IRQ); -+} -+ -+static irqreturn_t gen11_irq_handler(int irq, void *arg) -+{ -+ struct drm_i915_private * const i915 = to_i915(arg); -+ void __iomem * const regs = i915->uncore.regs; -+ u32 master_ctl; -+ u32 gu_misc_iir; -+ -+ if (!intel_irqs_enabled(i915)) -+ return IRQ_NONE; -+ -+ master_ctl = gen11_master_intr_disable(regs); -+ if (!master_ctl) { -+ gen11_master_intr_enable(regs); -+ return IRQ_NONE; -+ } -+ -+ /* Find, clear, then process each source of interrupt. */ -+ gen11_gt_irq_handler(i915, master_ctl); -+ -+ /* IRQs are synced during runtime_suspend, we don't require a wakeref */ -+ if (master_ctl & GEN11_DISPLAY_IRQ) { -+ const u32 disp_ctl = raw_reg_read(regs, GEN11_DISPLAY_INT_CTL); -+ -+ disable_rpm_wakeref_asserts(i915); -+ /* -+ * GEN11_DISPLAY_INT_CTL has same format as GEN8_MASTER_IRQ -+ * for the display related bits. -+ */ -+ gen8_de_irq_handler(i915, disp_ctl); -+ enable_rpm_wakeref_asserts(i915); -+ } -+ -+ gu_misc_iir = gen11_gu_misc_irq_ack(i915, master_ctl); -+ -+ gen11_master_intr_enable(regs); -+ -+ gen11_gu_misc_irq_handler(i915, gu_misc_iir); -+ -+ return IRQ_HANDLED; -+} -+ -+/* Called from drm generic code, passed 'crtc' which -+ * we use as a pipe index -+ */ -+static int i8xx_enable_vblank(struct drm_device *dev, unsigned int pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ unsigned long irqflags; -+ -+ spin_lock_irqsave(&dev_priv->irq_lock, irqflags); -+ i915_enable_pipestat(dev_priv, pipe, PIPE_VBLANK_INTERRUPT_STATUS); -+ spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); -+ -+ return 0; -+} -+ -+static int i945gm_enable_vblank(struct drm_device *dev, unsigned int pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ if (dev_priv->i945gm_vblank.enabled++ == 0) -+ schedule_work(&dev_priv->i945gm_vblank.work); -+ -+ return i8xx_enable_vblank(dev, pipe); -+} -+ -+static int i965_enable_vblank(struct drm_device *dev, unsigned int pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ unsigned long irqflags; -+ -+ spin_lock_irqsave(&dev_priv->irq_lock, irqflags); -+ i915_enable_pipestat(dev_priv, pipe, -+ PIPE_START_VBLANK_INTERRUPT_STATUS); -+ spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); -+ -+ return 0; -+} -+ -+static int ironlake_enable_vblank(struct drm_device *dev, unsigned int pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ unsigned long irqflags; -+ u32 bit = INTEL_GEN(dev_priv) >= 7 ? -+ DE_PIPE_VBLANK_IVB(pipe) : DE_PIPE_VBLANK(pipe); -+ -+ spin_lock_irqsave(&dev_priv->irq_lock, irqflags); -+ ilk_enable_display_irq(dev_priv, bit); -+ spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); -+ -+ /* Even though there is no DMC, frame counter can get stuck when -+ * PSR is active as no frames are generated. -+ */ -+ if (HAS_PSR(dev_priv)) -+ drm_vblank_restore(dev, pipe); -+ -+ return 0; -+} -+ -+static int gen8_enable_vblank(struct drm_device *dev, unsigned int pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ unsigned long irqflags; -+ -+ spin_lock_irqsave(&dev_priv->irq_lock, irqflags); -+ bdw_enable_pipe_irq(dev_priv, pipe, GEN8_PIPE_VBLANK); -+ spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); -+ -+ /* Even if there is no DMC, frame counter can get stuck when -+ * PSR is active as no frames are generated, so check only for PSR. -+ */ -+ if (HAS_PSR(dev_priv)) -+ drm_vblank_restore(dev, pipe); -+ -+ return 0; -+} -+ -+/* Called from drm generic code, passed 'crtc' which -+ * we use as a pipe index -+ */ -+static void i8xx_disable_vblank(struct drm_device *dev, unsigned int pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ unsigned long irqflags; -+ -+ spin_lock_irqsave(&dev_priv->irq_lock, irqflags); -+ i915_disable_pipestat(dev_priv, pipe, PIPE_VBLANK_INTERRUPT_STATUS); -+ spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); -+} -+ -+static void i945gm_disable_vblank(struct drm_device *dev, unsigned int pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ i8xx_disable_vblank(dev, pipe); -+ -+ if (--dev_priv->i945gm_vblank.enabled == 0) -+ schedule_work(&dev_priv->i945gm_vblank.work); -+} -+ -+static void i965_disable_vblank(struct drm_device *dev, unsigned int pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ unsigned long irqflags; -+ -+ spin_lock_irqsave(&dev_priv->irq_lock, irqflags); -+ i915_disable_pipestat(dev_priv, pipe, -+ PIPE_START_VBLANK_INTERRUPT_STATUS); -+ spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); -+} -+ -+static void ironlake_disable_vblank(struct drm_device *dev, unsigned int pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ unsigned long irqflags; -+ u32 bit = INTEL_GEN(dev_priv) >= 7 ? -+ DE_PIPE_VBLANK_IVB(pipe) : DE_PIPE_VBLANK(pipe); -+ -+ spin_lock_irqsave(&dev_priv->irq_lock, irqflags); -+ ilk_disable_display_irq(dev_priv, bit); -+ spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); -+} -+ -+static void gen8_disable_vblank(struct drm_device *dev, unsigned int pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ unsigned long irqflags; -+ -+ spin_lock_irqsave(&dev_priv->irq_lock, irqflags); -+ bdw_disable_pipe_irq(dev_priv, pipe, GEN8_PIPE_VBLANK); -+ spin_unlock_irqrestore(&dev_priv->irq_lock, irqflags); -+} -+ -+static void i945gm_vblank_work_func(struct work_struct *work) -+{ -+ struct drm_i915_private *dev_priv = -+ container_of(work, struct drm_i915_private, i945gm_vblank.work); -+ -+ /* -+ * Vblank interrupts fail to wake up the device from C3, -+ * hence we want to prevent C3 usage while vblank interrupts -+ * are enabled. -+ */ -+ pm_qos_update_request(&dev_priv->i945gm_vblank.pm_qos, -+ READ_ONCE(dev_priv->i945gm_vblank.enabled) ? -+ dev_priv->i945gm_vblank.c3_disable_latency : -+ PM_QOS_DEFAULT_VALUE); -+} -+ -+static int cstate_disable_latency(const char *name) -+{ -+ const struct cpuidle_driver *drv; -+ int i; -+ -+ drv = cpuidle_get_driver(); -+ if (!drv) -+ return 0; -+ -+ for (i = 0; i < drv->state_count; i++) { -+ const struct cpuidle_state *state = &drv->states[i]; -+ -+ if (!strcmp(state->name, name)) -+ return state->exit_latency ? -+ state->exit_latency - 1 : 0; -+ } -+ -+ return 0; -+} -+ -+static void i945gm_vblank_work_init(struct drm_i915_private *dev_priv) -+{ -+ INIT_WORK(&dev_priv->i945gm_vblank.work, -+ i945gm_vblank_work_func); -+ -+ dev_priv->i945gm_vblank.c3_disable_latency = -+ cstate_disable_latency("C3"); -+ pm_qos_add_request(&dev_priv->i945gm_vblank.pm_qos, -+ PM_QOS_CPU_DMA_LATENCY, -+ PM_QOS_DEFAULT_VALUE); -+} -+ -+static void i945gm_vblank_work_fini(struct drm_i915_private *dev_priv) -+{ -+ cancel_work_sync(&dev_priv->i945gm_vblank.work); -+ pm_qos_remove_request(&dev_priv->i945gm_vblank.pm_qos); -+} -+ -+static void ibx_irq_reset(struct drm_i915_private *dev_priv) -+{ -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ -+ if (HAS_PCH_NOP(dev_priv)) -+ return; -+ -+ GEN3_IRQ_RESET(uncore, SDE); -+ -+ if (HAS_PCH_CPT(dev_priv) || HAS_PCH_LPT(dev_priv)) -+ I915_WRITE(SERR_INT, 0xffffffff); -+} -+ -+/* -+ * SDEIER is also touched by the interrupt handler to work around missed PCH -+ * interrupts. Hence we can't update it after the interrupt handler is enabled - -+ * instead we unconditionally enable all PCH interrupt sources here, but then -+ * only unmask them as needed with SDEIMR. -+ * -+ * This function needs to be called before interrupts are enabled. -+ */ -+static void ibx_irq_pre_postinstall(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ if (HAS_PCH_NOP(dev_priv)) -+ return; -+ -+ WARN_ON(I915_READ(SDEIER) != 0); -+ I915_WRITE(SDEIER, 0xffffffff); -+ POSTING_READ(SDEIER); -+} -+ -+static void gen5_gt_irq_reset(struct drm_i915_private *dev_priv) -+{ -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ -+ GEN3_IRQ_RESET(uncore, GT); -+ if (INTEL_GEN(dev_priv) >= 6) -+ GEN3_IRQ_RESET(uncore, GEN6_PM); -+} -+ -+static void vlv_display_irq_reset(struct drm_i915_private *dev_priv) -+{ -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ -+ if (IS_CHERRYVIEW(dev_priv)) -+ I915_WRITE(DPINVGTT, DPINVGTT_STATUS_MASK_CHV); -+ else -+ I915_WRITE(DPINVGTT, DPINVGTT_STATUS_MASK); -+ -+ i915_hotplug_interrupt_update_locked(dev_priv, 0xffffffff, 0); -+ I915_WRITE(PORT_HOTPLUG_STAT, I915_READ(PORT_HOTPLUG_STAT)); -+ -+ i9xx_pipestat_irq_reset(dev_priv); -+ -+ GEN3_IRQ_RESET(uncore, VLV_); -+ dev_priv->irq_mask = ~0u; -+} -+ -+static void vlv_display_irq_postinstall(struct drm_i915_private *dev_priv) -+{ -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ -+ u32 pipestat_mask; -+ u32 enable_mask; -+ enum pipe pipe; -+ -+ pipestat_mask = PIPE_CRC_DONE_INTERRUPT_STATUS; -+ -+ i915_enable_pipestat(dev_priv, PIPE_A, PIPE_GMBUS_INTERRUPT_STATUS); -+ for_each_pipe(dev_priv, pipe) -+ i915_enable_pipestat(dev_priv, pipe, pipestat_mask); -+ -+ enable_mask = I915_DISPLAY_PORT_INTERRUPT | -+ I915_DISPLAY_PIPE_A_EVENT_INTERRUPT | -+ I915_DISPLAY_PIPE_B_EVENT_INTERRUPT | -+ I915_LPE_PIPE_A_INTERRUPT | -+ I915_LPE_PIPE_B_INTERRUPT; -+ -+ if (IS_CHERRYVIEW(dev_priv)) -+ enable_mask |= I915_DISPLAY_PIPE_C_EVENT_INTERRUPT | -+ I915_LPE_PIPE_C_INTERRUPT; -+ -+ WARN_ON(dev_priv->irq_mask != ~0u); -+ -+ dev_priv->irq_mask = ~enable_mask; -+ -+ GEN3_IRQ_INIT(uncore, VLV_, dev_priv->irq_mask, enable_mask); -+} -+ -+/* drm_dma.h hooks -+*/ -+static void ironlake_irq_reset(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ -+ GEN3_IRQ_RESET(uncore, DE); -+ if (IS_GEN(dev_priv, 7)) -+ I915_WRITE(GEN7_ERR_INT, 0xffffffff); -+ -+ if (IS_HASWELL(dev_priv)) { -+ I915_WRITE(EDP_PSR_IMR, 0xffffffff); -+ I915_WRITE(EDP_PSR_IIR, 0xffffffff); -+ } -+ -+ gen5_gt_irq_reset(dev_priv); -+ -+ ibx_irq_reset(dev_priv); -+} -+ -+static void valleyview_irq_reset(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ I915_WRITE(VLV_MASTER_IER, 0); -+ POSTING_READ(VLV_MASTER_IER); -+ -+ gen5_gt_irq_reset(dev_priv); -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ if (dev_priv->display_irqs_enabled) -+ vlv_display_irq_reset(dev_priv); -+ spin_unlock_irq(&dev_priv->irq_lock); -+} -+ -+static void gen8_gt_irq_reset(struct drm_i915_private *dev_priv) -+{ -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ -+ GEN8_IRQ_RESET_NDX(uncore, GT, 0); -+ GEN8_IRQ_RESET_NDX(uncore, GT, 1); -+ GEN8_IRQ_RESET_NDX(uncore, GT, 2); -+ GEN8_IRQ_RESET_NDX(uncore, GT, 3); -+} -+ -+static void gen8_irq_reset(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ int pipe; -+ -+ gen8_master_intr_disable(dev_priv->uncore.regs); -+ -+ gen8_gt_irq_reset(dev_priv); -+ -+ I915_WRITE(EDP_PSR_IMR, 0xffffffff); -+ I915_WRITE(EDP_PSR_IIR, 0xffffffff); -+ -+ for_each_pipe(dev_priv, pipe) -+ if (intel_display_power_is_enabled(dev_priv, -+ POWER_DOMAIN_PIPE(pipe))) -+ GEN8_IRQ_RESET_NDX(uncore, DE_PIPE, pipe); -+ -+ GEN3_IRQ_RESET(uncore, GEN8_DE_PORT_); -+ GEN3_IRQ_RESET(uncore, GEN8_DE_MISC_); -+ GEN3_IRQ_RESET(uncore, GEN8_PCU_); -+ -+ if (HAS_PCH_SPLIT(dev_priv)) -+ ibx_irq_reset(dev_priv); -+} -+ -+static void gen11_gt_irq_reset(struct drm_i915_private *dev_priv) -+{ -+ /* Disable RCS, BCS, VCS and VECS class engines. */ -+ I915_WRITE(GEN11_RENDER_COPY_INTR_ENABLE, 0); -+ I915_WRITE(GEN11_VCS_VECS_INTR_ENABLE, 0); -+ -+ /* Restore masks irqs on RCS, BCS, VCS and VECS engines. */ -+ I915_WRITE(GEN11_RCS0_RSVD_INTR_MASK, ~0); -+ I915_WRITE(GEN11_BCS_RSVD_INTR_MASK, ~0); -+ I915_WRITE(GEN11_VCS0_VCS1_INTR_MASK, ~0); -+ I915_WRITE(GEN11_VCS2_VCS3_INTR_MASK, ~0); -+ I915_WRITE(GEN11_VECS0_VECS1_INTR_MASK, ~0); -+ -+ I915_WRITE(GEN11_GPM_WGBOXPERF_INTR_ENABLE, 0); -+ I915_WRITE(GEN11_GPM_WGBOXPERF_INTR_MASK, ~0); -+} -+ -+static void gen11_irq_reset(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = dev->dev_private; -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ int pipe; -+ -+ gen11_master_intr_disable(dev_priv->uncore.regs); -+ -+ gen11_gt_irq_reset(dev_priv); -+ -+ I915_WRITE(GEN11_DISPLAY_INT_CTL, 0); -+ -+ I915_WRITE(EDP_PSR_IMR, 0xffffffff); -+ I915_WRITE(EDP_PSR_IIR, 0xffffffff); -+ -+ for_each_pipe(dev_priv, pipe) -+ if (intel_display_power_is_enabled(dev_priv, -+ POWER_DOMAIN_PIPE(pipe))) -+ GEN8_IRQ_RESET_NDX(uncore, DE_PIPE, pipe); -+ -+ GEN3_IRQ_RESET(uncore, GEN8_DE_PORT_); -+ GEN3_IRQ_RESET(uncore, GEN8_DE_MISC_); -+ GEN3_IRQ_RESET(uncore, GEN11_DE_HPD_); -+ GEN3_IRQ_RESET(uncore, GEN11_GU_MISC_); -+ GEN3_IRQ_RESET(uncore, GEN8_PCU_); -+ -+ if (INTEL_PCH_TYPE(dev_priv) >= PCH_ICP) -+ GEN3_IRQ_RESET(uncore, SDE); -+} -+ -+void gen8_irq_power_well_post_enable(struct drm_i915_private *dev_priv, -+ u8 pipe_mask) -+{ -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ -+ u32 extra_ier = GEN8_PIPE_VBLANK | GEN8_PIPE_FIFO_UNDERRUN; -+ enum pipe pipe; -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ -+ if (!intel_irqs_enabled(dev_priv)) { -+ spin_unlock_irq(&dev_priv->irq_lock); -+ return; -+ } -+ -+ for_each_pipe_masked(dev_priv, pipe, pipe_mask) -+ GEN8_IRQ_INIT_NDX(uncore, DE_PIPE, pipe, -+ dev_priv->de_irq_mask[pipe], -+ ~dev_priv->de_irq_mask[pipe] | extra_ier); -+ -+ spin_unlock_irq(&dev_priv->irq_lock); -+} -+ -+void gen8_irq_power_well_pre_disable(struct drm_i915_private *dev_priv, -+ u8 pipe_mask) -+{ -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ enum pipe pipe; -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ -+ if (!intel_irqs_enabled(dev_priv)) { -+ spin_unlock_irq(&dev_priv->irq_lock); -+ return; -+ } -+ -+ for_each_pipe_masked(dev_priv, pipe, pipe_mask) -+ GEN8_IRQ_RESET_NDX(uncore, DE_PIPE, pipe); -+ -+ spin_unlock_irq(&dev_priv->irq_lock); -+ -+ /* make sure we're done processing display irqs */ -+ synchronize_irq(dev_priv->drm.irq); -+} -+ -+static void cherryview_irq_reset(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ -+ I915_WRITE(GEN8_MASTER_IRQ, 0); -+ POSTING_READ(GEN8_MASTER_IRQ); -+ -+ gen8_gt_irq_reset(dev_priv); -+ -+ GEN3_IRQ_RESET(uncore, GEN8_PCU_); -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ if (dev_priv->display_irqs_enabled) -+ vlv_display_irq_reset(dev_priv); -+ spin_unlock_irq(&dev_priv->irq_lock); -+} -+ -+static u32 intel_hpd_enabled_irqs(struct drm_i915_private *dev_priv, -+ const u32 hpd[HPD_NUM_PINS]) -+{ -+ struct intel_encoder *encoder; -+ u32 enabled_irqs = 0; -+ -+ for_each_intel_encoder(&dev_priv->drm, encoder) -+ if (dev_priv->hotplug.stats[encoder->hpd_pin].state == HPD_ENABLED) -+ enabled_irqs |= hpd[encoder->hpd_pin]; -+ -+ return enabled_irqs; -+} -+ -+static void ibx_hpd_detection_setup(struct drm_i915_private *dev_priv) -+{ -+ u32 hotplug; -+ -+ /* -+ * Enable digital hotplug on the PCH, and configure the DP short pulse -+ * duration to 2ms (which is the minimum in the Display Port spec). -+ * The pulse duration bits are reserved on LPT+. -+ */ -+ hotplug = I915_READ(PCH_PORT_HOTPLUG); -+ hotplug &= ~(PORTB_PULSE_DURATION_MASK | -+ PORTC_PULSE_DURATION_MASK | -+ PORTD_PULSE_DURATION_MASK); -+ hotplug |= PORTB_HOTPLUG_ENABLE | PORTB_PULSE_DURATION_2ms; -+ hotplug |= PORTC_HOTPLUG_ENABLE | PORTC_PULSE_DURATION_2ms; -+ hotplug |= PORTD_HOTPLUG_ENABLE | PORTD_PULSE_DURATION_2ms; -+ /* -+ * When CPU and PCH are on the same package, port A -+ * HPD must be enabled in both north and south. -+ */ -+ if (HAS_PCH_LPT_LP(dev_priv)) -+ hotplug |= PORTA_HOTPLUG_ENABLE; -+ I915_WRITE(PCH_PORT_HOTPLUG, hotplug); -+} -+ -+static void ibx_hpd_irq_setup(struct drm_i915_private *dev_priv) -+{ -+ u32 hotplug_irqs, enabled_irqs; -+ -+ if (HAS_PCH_IBX(dev_priv)) { -+ hotplug_irqs = SDE_HOTPLUG_MASK; -+ enabled_irqs = intel_hpd_enabled_irqs(dev_priv, hpd_ibx); -+ } else { -+ hotplug_irqs = SDE_HOTPLUG_MASK_CPT; -+ enabled_irqs = intel_hpd_enabled_irqs(dev_priv, hpd_cpt); -+ } -+ -+ ibx_display_interrupt_update(dev_priv, hotplug_irqs, enabled_irqs); -+ -+ ibx_hpd_detection_setup(dev_priv); -+} -+ -+static void icp_hpd_detection_setup(struct drm_i915_private *dev_priv) -+{ -+ u32 hotplug; -+ -+ hotplug = I915_READ(SHOTPLUG_CTL_DDI); -+ hotplug |= ICP_DDIA_HPD_ENABLE | -+ ICP_DDIB_HPD_ENABLE; -+ I915_WRITE(SHOTPLUG_CTL_DDI, hotplug); -+ -+ hotplug = I915_READ(SHOTPLUG_CTL_TC); -+ hotplug |= ICP_TC_HPD_ENABLE(PORT_TC1) | -+ ICP_TC_HPD_ENABLE(PORT_TC2) | -+ ICP_TC_HPD_ENABLE(PORT_TC3) | -+ ICP_TC_HPD_ENABLE(PORT_TC4); -+ I915_WRITE(SHOTPLUG_CTL_TC, hotplug); -+} -+ -+static void icp_hpd_irq_setup(struct drm_i915_private *dev_priv) -+{ -+ u32 hotplug_irqs, enabled_irqs; -+ -+ hotplug_irqs = SDE_DDI_MASK_ICP | SDE_TC_MASK_ICP; -+ enabled_irqs = intel_hpd_enabled_irqs(dev_priv, hpd_icp); -+ -+ ibx_display_interrupt_update(dev_priv, hotplug_irqs, enabled_irqs); -+ -+ icp_hpd_detection_setup(dev_priv); -+} -+ -+static void gen11_hpd_detection_setup(struct drm_i915_private *dev_priv) -+{ -+ u32 hotplug; -+ -+ hotplug = I915_READ(GEN11_TC_HOTPLUG_CTL); -+ hotplug |= GEN11_HOTPLUG_CTL_ENABLE(PORT_TC1) | -+ GEN11_HOTPLUG_CTL_ENABLE(PORT_TC2) | -+ GEN11_HOTPLUG_CTL_ENABLE(PORT_TC3) | -+ GEN11_HOTPLUG_CTL_ENABLE(PORT_TC4); -+ I915_WRITE(GEN11_TC_HOTPLUG_CTL, hotplug); -+ -+ hotplug = I915_READ(GEN11_TBT_HOTPLUG_CTL); -+ hotplug |= GEN11_HOTPLUG_CTL_ENABLE(PORT_TC1) | -+ GEN11_HOTPLUG_CTL_ENABLE(PORT_TC2) | -+ GEN11_HOTPLUG_CTL_ENABLE(PORT_TC3) | -+ GEN11_HOTPLUG_CTL_ENABLE(PORT_TC4); -+ I915_WRITE(GEN11_TBT_HOTPLUG_CTL, hotplug); -+} -+ -+static void gen11_hpd_irq_setup(struct drm_i915_private *dev_priv) -+{ -+ u32 hotplug_irqs, enabled_irqs; -+ u32 val; -+ -+ enabled_irqs = intel_hpd_enabled_irqs(dev_priv, hpd_gen11); -+ hotplug_irqs = GEN11_DE_TC_HOTPLUG_MASK | GEN11_DE_TBT_HOTPLUG_MASK; -+ -+ val = I915_READ(GEN11_DE_HPD_IMR); -+ val &= ~hotplug_irqs; -+ I915_WRITE(GEN11_DE_HPD_IMR, val); -+ POSTING_READ(GEN11_DE_HPD_IMR); -+ -+ gen11_hpd_detection_setup(dev_priv); -+ -+ if (INTEL_PCH_TYPE(dev_priv) >= PCH_ICP) -+ icp_hpd_irq_setup(dev_priv); -+} -+ -+static void spt_hpd_detection_setup(struct drm_i915_private *dev_priv) -+{ -+ u32 val, hotplug; -+ -+ /* Display WA #1179 WaHardHangonHotPlug: cnp */ -+ if (HAS_PCH_CNP(dev_priv)) { -+ val = I915_READ(SOUTH_CHICKEN1); -+ val &= ~CHASSIS_CLK_REQ_DURATION_MASK; -+ val |= CHASSIS_CLK_REQ_DURATION(0xf); -+ I915_WRITE(SOUTH_CHICKEN1, val); -+ } -+ -+ /* Enable digital hotplug on the PCH */ -+ hotplug = I915_READ(PCH_PORT_HOTPLUG); -+ hotplug |= PORTA_HOTPLUG_ENABLE | -+ PORTB_HOTPLUG_ENABLE | -+ PORTC_HOTPLUG_ENABLE | -+ PORTD_HOTPLUG_ENABLE; -+ I915_WRITE(PCH_PORT_HOTPLUG, hotplug); -+ -+ hotplug = I915_READ(PCH_PORT_HOTPLUG2); -+ hotplug |= PORTE_HOTPLUG_ENABLE; -+ I915_WRITE(PCH_PORT_HOTPLUG2, hotplug); -+} -+ -+static void spt_hpd_irq_setup(struct drm_i915_private *dev_priv) -+{ -+ u32 hotplug_irqs, enabled_irqs; -+ -+ hotplug_irqs = SDE_HOTPLUG_MASK_SPT; -+ enabled_irqs = intel_hpd_enabled_irqs(dev_priv, hpd_spt); -+ -+ ibx_display_interrupt_update(dev_priv, hotplug_irqs, enabled_irqs); -+ -+ spt_hpd_detection_setup(dev_priv); -+} -+ -+static void ilk_hpd_detection_setup(struct drm_i915_private *dev_priv) -+{ -+ u32 hotplug; -+ -+ /* -+ * Enable digital hotplug on the CPU, and configure the DP short pulse -+ * duration to 2ms (which is the minimum in the Display Port spec) -+ * The pulse duration bits are reserved on HSW+. -+ */ -+ hotplug = I915_READ(DIGITAL_PORT_HOTPLUG_CNTRL); -+ hotplug &= ~DIGITAL_PORTA_PULSE_DURATION_MASK; -+ hotplug |= DIGITAL_PORTA_HOTPLUG_ENABLE | -+ DIGITAL_PORTA_PULSE_DURATION_2ms; -+ I915_WRITE(DIGITAL_PORT_HOTPLUG_CNTRL, hotplug); -+} -+ -+static void ilk_hpd_irq_setup(struct drm_i915_private *dev_priv) -+{ -+ u32 hotplug_irqs, enabled_irqs; -+ -+ if (INTEL_GEN(dev_priv) >= 8) { -+ hotplug_irqs = GEN8_PORT_DP_A_HOTPLUG; -+ enabled_irqs = intel_hpd_enabled_irqs(dev_priv, hpd_bdw); -+ -+ bdw_update_port_irq(dev_priv, hotplug_irqs, enabled_irqs); -+ } else if (INTEL_GEN(dev_priv) >= 7) { -+ hotplug_irqs = DE_DP_A_HOTPLUG_IVB; -+ enabled_irqs = intel_hpd_enabled_irqs(dev_priv, hpd_ivb); -+ -+ ilk_update_display_irq(dev_priv, hotplug_irqs, enabled_irqs); -+ } else { -+ hotplug_irqs = DE_DP_A_HOTPLUG; -+ enabled_irqs = intel_hpd_enabled_irqs(dev_priv, hpd_ilk); -+ -+ ilk_update_display_irq(dev_priv, hotplug_irqs, enabled_irqs); -+ } -+ -+ ilk_hpd_detection_setup(dev_priv); -+ -+ ibx_hpd_irq_setup(dev_priv); -+} -+ -+static void __bxt_hpd_detection_setup(struct drm_i915_private *dev_priv, -+ u32 enabled_irqs) -+{ -+ u32 hotplug; -+ -+ hotplug = I915_READ(PCH_PORT_HOTPLUG); -+ hotplug |= PORTA_HOTPLUG_ENABLE | -+ PORTB_HOTPLUG_ENABLE | -+ PORTC_HOTPLUG_ENABLE; -+ -+ DRM_DEBUG_KMS("Invert bit setting: hp_ctl:%x hp_port:%x\n", -+ hotplug, enabled_irqs); -+ hotplug &= ~BXT_DDI_HPD_INVERT_MASK; -+ -+ /* -+ * For BXT invert bit has to be set based on AOB design -+ * for HPD detection logic, update it based on VBT fields. -+ */ -+ if ((enabled_irqs & BXT_DE_PORT_HP_DDIA) && -+ intel_bios_is_port_hpd_inverted(dev_priv, PORT_A)) -+ hotplug |= BXT_DDIA_HPD_INVERT; -+ if ((enabled_irqs & BXT_DE_PORT_HP_DDIB) && -+ intel_bios_is_port_hpd_inverted(dev_priv, PORT_B)) -+ hotplug |= BXT_DDIB_HPD_INVERT; -+ if ((enabled_irqs & BXT_DE_PORT_HP_DDIC) && -+ intel_bios_is_port_hpd_inverted(dev_priv, PORT_C)) -+ hotplug |= BXT_DDIC_HPD_INVERT; -+ -+ I915_WRITE(PCH_PORT_HOTPLUG, hotplug); -+} -+ -+static void bxt_hpd_detection_setup(struct drm_i915_private *dev_priv) -+{ -+ __bxt_hpd_detection_setup(dev_priv, BXT_DE_PORT_HOTPLUG_MASK); -+} -+ -+static void bxt_hpd_irq_setup(struct drm_i915_private *dev_priv) -+{ -+ u32 hotplug_irqs, enabled_irqs; -+ -+ enabled_irqs = intel_hpd_enabled_irqs(dev_priv, hpd_bxt); -+ hotplug_irqs = BXT_DE_PORT_HOTPLUG_MASK; -+ -+ bdw_update_port_irq(dev_priv, hotplug_irqs, enabled_irqs); -+ -+ __bxt_hpd_detection_setup(dev_priv, enabled_irqs); -+} -+ -+static void ibx_irq_postinstall(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ u32 mask; -+ -+ if (HAS_PCH_NOP(dev_priv)) -+ return; -+ -+ if (HAS_PCH_IBX(dev_priv)) -+ mask = SDE_GMBUS | SDE_AUX_MASK | SDE_POISON; -+ else if (HAS_PCH_CPT(dev_priv) || HAS_PCH_LPT(dev_priv)) -+ mask = SDE_GMBUS_CPT | SDE_AUX_MASK_CPT; -+ else -+ mask = SDE_GMBUS_CPT; -+ -+ gen3_assert_iir_is_zero(&dev_priv->uncore, SDEIIR); -+ I915_WRITE(SDEIMR, ~mask); -+ -+ if (HAS_PCH_IBX(dev_priv) || HAS_PCH_CPT(dev_priv) || -+ HAS_PCH_LPT(dev_priv)) -+ ibx_hpd_detection_setup(dev_priv); -+ else -+ spt_hpd_detection_setup(dev_priv); -+} -+ -+static void gen5_gt_irq_postinstall(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ u32 pm_irqs, gt_irqs; -+ -+ pm_irqs = gt_irqs = 0; -+ -+ dev_priv->gt_irq_mask = ~0; -+ if (HAS_L3_DPF(dev_priv)) { -+ /* L3 parity interrupt is always unmasked. */ -+ dev_priv->gt_irq_mask = ~GT_PARITY_ERROR(dev_priv); -+ gt_irqs |= GT_PARITY_ERROR(dev_priv); -+ } -+ -+ gt_irqs |= GT_RENDER_USER_INTERRUPT; -+ if (IS_GEN(dev_priv, 5)) { -+ gt_irqs |= ILK_BSD_USER_INTERRUPT; -+ } else { -+ gt_irqs |= GT_BLT_USER_INTERRUPT | GT_BSD_USER_INTERRUPT; -+ } -+ -+ GEN3_IRQ_INIT(uncore, GT, dev_priv->gt_irq_mask, gt_irqs); -+ -+ if (INTEL_GEN(dev_priv) >= 6) { -+ /* -+ * RPS interrupts will get enabled/disabled on demand when RPS -+ * itself is enabled/disabled. -+ */ -+ if (HAS_ENGINE(dev_priv, VECS0)) { -+ pm_irqs |= PM_VEBOX_USER_INTERRUPT; -+ dev_priv->pm_ier |= PM_VEBOX_USER_INTERRUPT; -+ } -+ -+ dev_priv->pm_imr = 0xffffffff; -+ GEN3_IRQ_INIT(uncore, GEN6_PM, dev_priv->pm_imr, pm_irqs); -+ } -+} -+ -+static int ironlake_irq_postinstall(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ u32 display_mask, extra_mask; -+ -+ if (INTEL_GEN(dev_priv) >= 7) { -+ display_mask = (DE_MASTER_IRQ_CONTROL | DE_GSE_IVB | -+ DE_PCH_EVENT_IVB | DE_AUX_CHANNEL_A_IVB); -+ extra_mask = (DE_PIPEC_VBLANK_IVB | DE_PIPEB_VBLANK_IVB | -+ DE_PIPEA_VBLANK_IVB | DE_ERR_INT_IVB | -+ DE_DP_A_HOTPLUG_IVB); -+ } else { -+ display_mask = (DE_MASTER_IRQ_CONTROL | DE_GSE | DE_PCH_EVENT | -+ DE_AUX_CHANNEL_A | DE_PIPEB_CRC_DONE | -+ DE_PIPEA_CRC_DONE | DE_POISON); -+ extra_mask = (DE_PIPEA_VBLANK | DE_PIPEB_VBLANK | DE_PCU_EVENT | -+ DE_PIPEB_FIFO_UNDERRUN | DE_PIPEA_FIFO_UNDERRUN | -+ DE_DP_A_HOTPLUG); -+ } -+ -+ if (IS_HASWELL(dev_priv)) { -+ gen3_assert_iir_is_zero(uncore, EDP_PSR_IIR); -+ intel_psr_irq_control(dev_priv, dev_priv->psr.debug); -+ display_mask |= DE_EDP_PSR_INT_HSW; -+ } -+ -+ dev_priv->irq_mask = ~display_mask; -+ -+ ibx_irq_pre_postinstall(dev); -+ -+ GEN3_IRQ_INIT(uncore, DE, dev_priv->irq_mask, -+ display_mask | extra_mask); -+ -+ gen5_gt_irq_postinstall(dev); -+ -+ ilk_hpd_detection_setup(dev_priv); -+ -+ ibx_irq_postinstall(dev); -+ -+ if (IS_IRONLAKE_M(dev_priv)) { -+ /* Enable PCU event interrupts -+ * -+ * spinlocking not required here for correctness since interrupt -+ * setup is guaranteed to run in single-threaded context. But we -+ * need it to make the assert_spin_locked happy. */ -+ spin_lock_irq(&dev_priv->irq_lock); -+ ilk_enable_display_irq(dev_priv, DE_PCU_EVENT); -+ spin_unlock_irq(&dev_priv->irq_lock); -+ } -+ -+ return 0; -+} -+ -+void valleyview_enable_display_irqs(struct drm_i915_private *dev_priv) -+{ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ -+ if (dev_priv->display_irqs_enabled) -+ return; -+ -+ dev_priv->display_irqs_enabled = true; -+ -+ if (intel_irqs_enabled(dev_priv)) { -+ vlv_display_irq_reset(dev_priv); -+ vlv_display_irq_postinstall(dev_priv); -+ } -+} -+ -+void valleyview_disable_display_irqs(struct drm_i915_private *dev_priv) -+{ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ -+ if (!dev_priv->display_irqs_enabled) -+ return; -+ -+ dev_priv->display_irqs_enabled = false; -+ -+ if (intel_irqs_enabled(dev_priv)) -+ vlv_display_irq_reset(dev_priv); -+} -+ -+ -+static int valleyview_irq_postinstall(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ gen5_gt_irq_postinstall(dev); -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ if (dev_priv->display_irqs_enabled) -+ vlv_display_irq_postinstall(dev_priv); -+ spin_unlock_irq(&dev_priv->irq_lock); -+ -+ I915_WRITE(VLV_MASTER_IER, MASTER_INTERRUPT_ENABLE); -+ POSTING_READ(VLV_MASTER_IER); -+ -+ return 0; -+} -+ -+static void gen8_gt_irq_postinstall(struct drm_i915_private *dev_priv) -+{ -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ -+ /* These are interrupts we'll toggle with the ring mask register */ -+ u32 gt_interrupts[] = { -+ (GT_RENDER_USER_INTERRUPT << GEN8_RCS_IRQ_SHIFT | -+ GT_CONTEXT_SWITCH_INTERRUPT << GEN8_RCS_IRQ_SHIFT | -+ GT_RENDER_USER_INTERRUPT << GEN8_BCS_IRQ_SHIFT | -+ GT_CONTEXT_SWITCH_INTERRUPT << GEN8_BCS_IRQ_SHIFT), -+ -+ (GT_RENDER_USER_INTERRUPT << GEN8_VCS0_IRQ_SHIFT | -+ GT_CONTEXT_SWITCH_INTERRUPT << GEN8_VCS0_IRQ_SHIFT | -+ GT_RENDER_USER_INTERRUPT << GEN8_VCS1_IRQ_SHIFT | -+ GT_CONTEXT_SWITCH_INTERRUPT << GEN8_VCS1_IRQ_SHIFT), -+ -+ 0, -+ -+ (GT_RENDER_USER_INTERRUPT << GEN8_VECS_IRQ_SHIFT | -+ GT_CONTEXT_SWITCH_INTERRUPT << GEN8_VECS_IRQ_SHIFT) -+ }; -+ -+ dev_priv->pm_ier = 0x0; -+ dev_priv->pm_imr = ~dev_priv->pm_ier; -+ GEN8_IRQ_INIT_NDX(uncore, GT, 0, ~gt_interrupts[0], gt_interrupts[0]); -+ GEN8_IRQ_INIT_NDX(uncore, GT, 1, ~gt_interrupts[1], gt_interrupts[1]); -+ /* -+ * RPS interrupts will get enabled/disabled on demand when RPS itself -+ * is enabled/disabled. Same wil be the case for GuC interrupts. -+ */ -+ GEN8_IRQ_INIT_NDX(uncore, GT, 2, dev_priv->pm_imr, dev_priv->pm_ier); -+ GEN8_IRQ_INIT_NDX(uncore, GT, 3, ~gt_interrupts[3], gt_interrupts[3]); -+} -+ -+static void gen8_de_irq_postinstall(struct drm_i915_private *dev_priv) -+{ -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ -+ u32 de_pipe_masked = GEN8_PIPE_CDCLK_CRC_DONE; -+ u32 de_pipe_enables; -+ u32 de_port_masked = GEN8_AUX_CHANNEL_A; -+ u32 de_port_enables; -+ u32 de_misc_masked = GEN8_DE_EDP_PSR; -+ enum pipe pipe; -+ -+ if (INTEL_GEN(dev_priv) <= 10) -+ de_misc_masked |= GEN8_DE_MISC_GSE; -+ -+ if (INTEL_GEN(dev_priv) >= 9) { -+ de_pipe_masked |= GEN9_DE_PIPE_IRQ_FAULT_ERRORS; -+ de_port_masked |= GEN9_AUX_CHANNEL_B | GEN9_AUX_CHANNEL_C | -+ GEN9_AUX_CHANNEL_D; -+ if (IS_GEN9_LP(dev_priv)) -+ de_port_masked |= BXT_DE_PORT_GMBUS; -+ } else { -+ de_pipe_masked |= GEN8_DE_PIPE_IRQ_FAULT_ERRORS; -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ de_port_masked |= ICL_AUX_CHANNEL_E; -+ -+ if (IS_CNL_WITH_PORT_F(dev_priv) || INTEL_GEN(dev_priv) >= 11) -+ de_port_masked |= CNL_AUX_CHANNEL_F; -+ -+ de_pipe_enables = de_pipe_masked | GEN8_PIPE_VBLANK | -+ GEN8_PIPE_FIFO_UNDERRUN; -+ -+ de_port_enables = de_port_masked; -+ if (IS_GEN9_LP(dev_priv)) -+ de_port_enables |= BXT_DE_PORT_HOTPLUG_MASK; -+ else if (IS_BROADWELL(dev_priv)) -+ de_port_enables |= GEN8_PORT_DP_A_HOTPLUG; -+ -+ gen3_assert_iir_is_zero(uncore, EDP_PSR_IIR); -+ intel_psr_irq_control(dev_priv, dev_priv->psr.debug); -+ -+ for_each_pipe(dev_priv, pipe) { -+ dev_priv->de_irq_mask[pipe] = ~de_pipe_masked; -+ -+ if (intel_display_power_is_enabled(dev_priv, -+ POWER_DOMAIN_PIPE(pipe))) -+ GEN8_IRQ_INIT_NDX(uncore, DE_PIPE, pipe, -+ dev_priv->de_irq_mask[pipe], -+ de_pipe_enables); -+ } -+ -+ GEN3_IRQ_INIT(uncore, GEN8_DE_PORT_, ~de_port_masked, de_port_enables); -+ GEN3_IRQ_INIT(uncore, GEN8_DE_MISC_, ~de_misc_masked, de_misc_masked); -+ -+ if (INTEL_GEN(dev_priv) >= 11) { -+ u32 de_hpd_masked = 0; -+ u32 de_hpd_enables = GEN11_DE_TC_HOTPLUG_MASK | -+ GEN11_DE_TBT_HOTPLUG_MASK; -+ -+ GEN3_IRQ_INIT(uncore, GEN11_DE_HPD_, ~de_hpd_masked, -+ de_hpd_enables); -+ gen11_hpd_detection_setup(dev_priv); -+ } else if (IS_GEN9_LP(dev_priv)) { -+ bxt_hpd_detection_setup(dev_priv); -+ } else if (IS_BROADWELL(dev_priv)) { -+ ilk_hpd_detection_setup(dev_priv); -+ } -+} -+ -+static int gen8_irq_postinstall(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ if (HAS_PCH_SPLIT(dev_priv)) -+ ibx_irq_pre_postinstall(dev); -+ -+ gen8_gt_irq_postinstall(dev_priv); -+ gen8_de_irq_postinstall(dev_priv); -+ -+ if (HAS_PCH_SPLIT(dev_priv)) -+ ibx_irq_postinstall(dev); -+ -+ gen8_master_intr_enable(dev_priv->uncore.regs); -+ -+ return 0; -+} -+ -+static void gen11_gt_irq_postinstall(struct drm_i915_private *dev_priv) -+{ -+ const u32 irqs = GT_RENDER_USER_INTERRUPT | GT_CONTEXT_SWITCH_INTERRUPT; -+ -+ BUILD_BUG_ON(irqs & 0xffff0000); -+ -+ /* Enable RCS, BCS, VCS and VECS class interrupts. */ -+ I915_WRITE(GEN11_RENDER_COPY_INTR_ENABLE, irqs << 16 | irqs); -+ I915_WRITE(GEN11_VCS_VECS_INTR_ENABLE, irqs << 16 | irqs); -+ -+ /* Unmask irqs on RCS, BCS, VCS and VECS engines. */ -+ I915_WRITE(GEN11_RCS0_RSVD_INTR_MASK, ~(irqs << 16)); -+ I915_WRITE(GEN11_BCS_RSVD_INTR_MASK, ~(irqs << 16)); -+ I915_WRITE(GEN11_VCS0_VCS1_INTR_MASK, ~(irqs | irqs << 16)); -+ I915_WRITE(GEN11_VCS2_VCS3_INTR_MASK, ~(irqs | irqs << 16)); -+ I915_WRITE(GEN11_VECS0_VECS1_INTR_MASK, ~(irqs | irqs << 16)); -+ -+ /* -+ * RPS interrupts will get enabled/disabled on demand when RPS itself -+ * is enabled/disabled. -+ */ -+ dev_priv->pm_ier = 0x0; -+ dev_priv->pm_imr = ~dev_priv->pm_ier; -+ I915_WRITE(GEN11_GPM_WGBOXPERF_INTR_ENABLE, 0); -+ I915_WRITE(GEN11_GPM_WGBOXPERF_INTR_MASK, ~0); -+} -+ -+static void icp_irq_postinstall(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ u32 mask = SDE_GMBUS_ICP; -+ -+ WARN_ON(I915_READ(SDEIER) != 0); -+ I915_WRITE(SDEIER, 0xffffffff); -+ POSTING_READ(SDEIER); -+ -+ gen3_assert_iir_is_zero(&dev_priv->uncore, SDEIIR); -+ I915_WRITE(SDEIMR, ~mask); -+ -+ icp_hpd_detection_setup(dev_priv); -+} -+ -+static int gen11_irq_postinstall(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = dev->dev_private; -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ u32 gu_misc_masked = GEN11_GU_MISC_GSE; -+ -+ if (INTEL_PCH_TYPE(dev_priv) >= PCH_ICP) -+ icp_irq_postinstall(dev); -+ -+ gen11_gt_irq_postinstall(dev_priv); -+ gen8_de_irq_postinstall(dev_priv); -+ -+ GEN3_IRQ_INIT(uncore, GEN11_GU_MISC_, ~gu_misc_masked, gu_misc_masked); -+ -+ I915_WRITE(GEN11_DISPLAY_INT_CTL, GEN11_DISPLAY_IRQ_ENABLE); -+ -+ gen11_master_intr_enable(dev_priv->uncore.regs); -+ POSTING_READ(GEN11_GFX_MSTR_IRQ); -+ -+ return 0; -+} -+ -+static int cherryview_irq_postinstall(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ gen8_gt_irq_postinstall(dev_priv); -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ if (dev_priv->display_irqs_enabled) -+ vlv_display_irq_postinstall(dev_priv); -+ spin_unlock_irq(&dev_priv->irq_lock); -+ -+ I915_WRITE(GEN8_MASTER_IRQ, GEN8_MASTER_IRQ_CONTROL); -+ POSTING_READ(GEN8_MASTER_IRQ); -+ -+ return 0; -+} -+ -+static void i8xx_irq_reset(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ -+ i9xx_pipestat_irq_reset(dev_priv); -+ -+ GEN2_IRQ_RESET(uncore); -+} -+ -+static int i8xx_irq_postinstall(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ u16 enable_mask; -+ -+ I915_WRITE16(EMR, ~(I915_ERROR_PAGE_TABLE | -+ I915_ERROR_MEMORY_REFRESH)); -+ -+ /* Unmask the interrupts that we always want on. */ -+ dev_priv->irq_mask = -+ ~(I915_DISPLAY_PIPE_A_EVENT_INTERRUPT | -+ I915_DISPLAY_PIPE_B_EVENT_INTERRUPT | -+ I915_MASTER_ERROR_INTERRUPT); -+ -+ enable_mask = -+ I915_DISPLAY_PIPE_A_EVENT_INTERRUPT | -+ I915_DISPLAY_PIPE_B_EVENT_INTERRUPT | -+ I915_MASTER_ERROR_INTERRUPT | -+ I915_USER_INTERRUPT; -+ -+ GEN2_IRQ_INIT(uncore, dev_priv->irq_mask, enable_mask); -+ -+ /* Interrupt setup is already guaranteed to be single-threaded, this is -+ * just to make the assert_spin_locked check happy. */ -+ spin_lock_irq(&dev_priv->irq_lock); -+ i915_enable_pipestat(dev_priv, PIPE_A, PIPE_CRC_DONE_INTERRUPT_STATUS); -+ i915_enable_pipestat(dev_priv, PIPE_B, PIPE_CRC_DONE_INTERRUPT_STATUS); -+ spin_unlock_irq(&dev_priv->irq_lock); -+ -+ return 0; -+} -+ -+static void i8xx_error_irq_ack(struct drm_i915_private *dev_priv, -+ u16 *eir, u16 *eir_stuck) -+{ -+ u16 emr; -+ -+ *eir = I915_READ16(EIR); -+ -+ if (*eir) -+ I915_WRITE16(EIR, *eir); -+ -+ *eir_stuck = I915_READ16(EIR); -+ if (*eir_stuck == 0) -+ return; -+ -+ /* -+ * Toggle all EMR bits to make sure we get an edge -+ * in the ISR master error bit if we don't clear -+ * all the EIR bits. Otherwise the edge triggered -+ * IIR on i965/g4x wouldn't notice that an interrupt -+ * is still pending. Also some EIR bits can't be -+ * cleared except by handling the underlying error -+ * (or by a GPU reset) so we mask any bit that -+ * remains set. -+ */ -+ emr = I915_READ16(EMR); -+ I915_WRITE16(EMR, 0xffff); -+ I915_WRITE16(EMR, emr | *eir_stuck); -+} -+ -+static void i8xx_error_irq_handler(struct drm_i915_private *dev_priv, -+ u16 eir, u16 eir_stuck) -+{ -+ DRM_DEBUG("Master Error: EIR 0x%04x\n", eir); -+ -+ if (eir_stuck) -+ DRM_DEBUG_DRIVER("EIR stuck: 0x%04x, masked\n", eir_stuck); -+} -+ -+static void i9xx_error_irq_ack(struct drm_i915_private *dev_priv, -+ u32 *eir, u32 *eir_stuck) -+{ -+ u32 emr; -+ -+ *eir = I915_READ(EIR); -+ -+ I915_WRITE(EIR, *eir); -+ -+ *eir_stuck = I915_READ(EIR); -+ if (*eir_stuck == 0) -+ return; -+ -+ /* -+ * Toggle all EMR bits to make sure we get an edge -+ * in the ISR master error bit if we don't clear -+ * all the EIR bits. Otherwise the edge triggered -+ * IIR on i965/g4x wouldn't notice that an interrupt -+ * is still pending. Also some EIR bits can't be -+ * cleared except by handling the underlying error -+ * (or by a GPU reset) so we mask any bit that -+ * remains set. -+ */ -+ emr = I915_READ(EMR); -+ I915_WRITE(EMR, 0xffffffff); -+ I915_WRITE(EMR, emr | *eir_stuck); -+} -+ -+static void i9xx_error_irq_handler(struct drm_i915_private *dev_priv, -+ u32 eir, u32 eir_stuck) -+{ -+ DRM_DEBUG("Master Error, EIR 0x%08x\n", eir); -+ -+ if (eir_stuck) -+ DRM_DEBUG_DRIVER("EIR stuck: 0x%08x, masked\n", eir_stuck); -+} -+ -+static irqreturn_t i8xx_irq_handler(int irq, void *arg) -+{ -+ struct drm_device *dev = arg; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ irqreturn_t ret = IRQ_NONE; -+ -+ if (!intel_irqs_enabled(dev_priv)) -+ return IRQ_NONE; -+ -+ /* IRQs are synced during runtime_suspend, we don't require a wakeref */ -+ disable_rpm_wakeref_asserts(dev_priv); -+ -+ do { -+ u32 pipe_stats[I915_MAX_PIPES] = {}; -+ u16 eir = 0, eir_stuck = 0; -+ u16 iir; -+ -+ iir = I915_READ16(GEN2_IIR); -+ if (iir == 0) -+ break; -+ -+ ret = IRQ_HANDLED; -+ -+ /* Call regardless, as some status bits might not be -+ * signalled in iir */ -+ i9xx_pipestat_irq_ack(dev_priv, iir, pipe_stats); -+ -+ if (iir & I915_MASTER_ERROR_INTERRUPT) -+ i8xx_error_irq_ack(dev_priv, &eir, &eir_stuck); -+ -+ I915_WRITE16(GEN2_IIR, iir); -+ -+ if (iir & I915_USER_INTERRUPT) -+ intel_engine_breadcrumbs_irq(dev_priv->engine[RCS0]); -+ -+ if (iir & I915_MASTER_ERROR_INTERRUPT) -+ i8xx_error_irq_handler(dev_priv, eir, eir_stuck); -+ -+ i8xx_pipestat_irq_handler(dev_priv, iir, pipe_stats); -+ } while (0); -+ -+ enable_rpm_wakeref_asserts(dev_priv); -+ -+ return ret; -+} -+ -+static void i915_irq_reset(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ -+ if (I915_HAS_HOTPLUG(dev_priv)) { -+ i915_hotplug_interrupt_update(dev_priv, 0xffffffff, 0); -+ I915_WRITE(PORT_HOTPLUG_STAT, I915_READ(PORT_HOTPLUG_STAT)); -+ } -+ -+ i9xx_pipestat_irq_reset(dev_priv); -+ -+ GEN3_IRQ_RESET(uncore, GEN2_); -+} -+ -+static int i915_irq_postinstall(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ u32 enable_mask; -+ -+ I915_WRITE(EMR, ~(I915_ERROR_PAGE_TABLE | -+ I915_ERROR_MEMORY_REFRESH)); -+ -+ /* Unmask the interrupts that we always want on. */ -+ dev_priv->irq_mask = -+ ~(I915_ASLE_INTERRUPT | -+ I915_DISPLAY_PIPE_A_EVENT_INTERRUPT | -+ I915_DISPLAY_PIPE_B_EVENT_INTERRUPT | -+ I915_MASTER_ERROR_INTERRUPT); -+ -+ enable_mask = -+ I915_ASLE_INTERRUPT | -+ I915_DISPLAY_PIPE_A_EVENT_INTERRUPT | -+ I915_DISPLAY_PIPE_B_EVENT_INTERRUPT | -+ I915_MASTER_ERROR_INTERRUPT | -+ I915_USER_INTERRUPT; -+ -+ if (I915_HAS_HOTPLUG(dev_priv)) { -+ /* Enable in IER... */ -+ enable_mask |= I915_DISPLAY_PORT_INTERRUPT; -+ /* and unmask in IMR */ -+ dev_priv->irq_mask &= ~I915_DISPLAY_PORT_INTERRUPT; -+ } -+ -+ GEN3_IRQ_INIT(uncore, GEN2_, dev_priv->irq_mask, enable_mask); -+ -+ /* Interrupt setup is already guaranteed to be single-threaded, this is -+ * just to make the assert_spin_locked check happy. */ -+ spin_lock_irq(&dev_priv->irq_lock); -+ i915_enable_pipestat(dev_priv, PIPE_A, PIPE_CRC_DONE_INTERRUPT_STATUS); -+ i915_enable_pipestat(dev_priv, PIPE_B, PIPE_CRC_DONE_INTERRUPT_STATUS); -+ spin_unlock_irq(&dev_priv->irq_lock); -+ -+ i915_enable_asle_pipestat(dev_priv); -+ -+ return 0; -+} -+ -+static irqreturn_t i915_irq_handler(int irq, void *arg) -+{ -+ struct drm_device *dev = arg; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ irqreturn_t ret = IRQ_NONE; -+ -+ if (!intel_irqs_enabled(dev_priv)) -+ return IRQ_NONE; -+ -+ /* IRQs are synced during runtime_suspend, we don't require a wakeref */ -+ disable_rpm_wakeref_asserts(dev_priv); -+ -+ do { -+ u32 pipe_stats[I915_MAX_PIPES] = {}; -+ u32 eir = 0, eir_stuck = 0; -+ u32 hotplug_status = 0; -+ u32 iir; -+ -+ iir = I915_READ(GEN2_IIR); -+ if (iir == 0) -+ break; -+ -+ ret = IRQ_HANDLED; -+ -+ if (I915_HAS_HOTPLUG(dev_priv) && -+ iir & I915_DISPLAY_PORT_INTERRUPT) -+ hotplug_status = i9xx_hpd_irq_ack(dev_priv); -+ -+ /* Call regardless, as some status bits might not be -+ * signalled in iir */ -+ i9xx_pipestat_irq_ack(dev_priv, iir, pipe_stats); -+ -+ if (iir & I915_MASTER_ERROR_INTERRUPT) -+ i9xx_error_irq_ack(dev_priv, &eir, &eir_stuck); -+ -+ I915_WRITE(GEN2_IIR, iir); -+ -+ if (iir & I915_USER_INTERRUPT) -+ intel_engine_breadcrumbs_irq(dev_priv->engine[RCS0]); -+ -+ if (iir & I915_MASTER_ERROR_INTERRUPT) -+ i9xx_error_irq_handler(dev_priv, eir, eir_stuck); -+ -+ if (hotplug_status) -+ i9xx_hpd_irq_handler(dev_priv, hotplug_status); -+ -+ i915_pipestat_irq_handler(dev_priv, iir, pipe_stats); -+ } while (0); -+ -+ enable_rpm_wakeref_asserts(dev_priv); -+ -+ return ret; -+} -+ -+static void i965_irq_reset(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ -+ i915_hotplug_interrupt_update(dev_priv, 0xffffffff, 0); -+ I915_WRITE(PORT_HOTPLUG_STAT, I915_READ(PORT_HOTPLUG_STAT)); -+ -+ i9xx_pipestat_irq_reset(dev_priv); -+ -+ GEN3_IRQ_RESET(uncore, GEN2_); -+} -+ -+static int i965_irq_postinstall(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ u32 enable_mask; -+ u32 error_mask; -+ -+ /* -+ * Enable some error detection, note the instruction error mask -+ * bit is reserved, so we leave it masked. -+ */ -+ if (IS_G4X(dev_priv)) { -+ error_mask = ~(GM45_ERROR_PAGE_TABLE | -+ GM45_ERROR_MEM_PRIV | -+ GM45_ERROR_CP_PRIV | -+ I915_ERROR_MEMORY_REFRESH); -+ } else { -+ error_mask = ~(I915_ERROR_PAGE_TABLE | -+ I915_ERROR_MEMORY_REFRESH); -+ } -+ I915_WRITE(EMR, error_mask); -+ -+ /* Unmask the interrupts that we always want on. */ -+ dev_priv->irq_mask = -+ ~(I915_ASLE_INTERRUPT | -+ I915_DISPLAY_PORT_INTERRUPT | -+ I915_DISPLAY_PIPE_A_EVENT_INTERRUPT | -+ I915_DISPLAY_PIPE_B_EVENT_INTERRUPT | -+ I915_MASTER_ERROR_INTERRUPT); -+ -+ enable_mask = -+ I915_ASLE_INTERRUPT | -+ I915_DISPLAY_PORT_INTERRUPT | -+ I915_DISPLAY_PIPE_A_EVENT_INTERRUPT | -+ I915_DISPLAY_PIPE_B_EVENT_INTERRUPT | -+ I915_MASTER_ERROR_INTERRUPT | -+ I915_USER_INTERRUPT; -+ -+ if (IS_G4X(dev_priv)) -+ enable_mask |= I915_BSD_USER_INTERRUPT; -+ -+ GEN3_IRQ_INIT(uncore, GEN2_, dev_priv->irq_mask, enable_mask); -+ -+ /* Interrupt setup is already guaranteed to be single-threaded, this is -+ * just to make the assert_spin_locked check happy. */ -+ spin_lock_irq(&dev_priv->irq_lock); -+ i915_enable_pipestat(dev_priv, PIPE_A, PIPE_GMBUS_INTERRUPT_STATUS); -+ i915_enable_pipestat(dev_priv, PIPE_A, PIPE_CRC_DONE_INTERRUPT_STATUS); -+ i915_enable_pipestat(dev_priv, PIPE_B, PIPE_CRC_DONE_INTERRUPT_STATUS); -+ spin_unlock_irq(&dev_priv->irq_lock); -+ -+ i915_enable_asle_pipestat(dev_priv); -+ -+ return 0; -+} -+ -+static void i915_hpd_irq_setup(struct drm_i915_private *dev_priv) -+{ -+ u32 hotplug_en; -+ -+ lockdep_assert_held(&dev_priv->irq_lock); -+ -+ /* Note HDMI and DP share hotplug bits */ -+ /* enable bits are the same for all generations */ -+ hotplug_en = intel_hpd_enabled_irqs(dev_priv, hpd_mask_i915); -+ /* Programming the CRT detection parameters tends -+ to generate a spurious hotplug event about three -+ seconds later. So just do it once. -+ */ -+ if (IS_G4X(dev_priv)) -+ hotplug_en |= CRT_HOTPLUG_ACTIVATION_PERIOD_64; -+ hotplug_en |= CRT_HOTPLUG_VOLTAGE_COMPARE_50; -+ -+ /* Ignore TV since it's buggy */ -+ i915_hotplug_interrupt_update_locked(dev_priv, -+ HOTPLUG_INT_EN_MASK | -+ CRT_HOTPLUG_VOLTAGE_COMPARE_MASK | -+ CRT_HOTPLUG_ACTIVATION_PERIOD_64, -+ hotplug_en); -+} -+ -+static irqreturn_t i965_irq_handler(int irq, void *arg) -+{ -+ struct drm_device *dev = arg; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ irqreturn_t ret = IRQ_NONE; -+ -+ if (!intel_irqs_enabled(dev_priv)) -+ return IRQ_NONE; -+ -+ /* IRQs are synced during runtime_suspend, we don't require a wakeref */ -+ disable_rpm_wakeref_asserts(dev_priv); -+ -+ do { -+ u32 pipe_stats[I915_MAX_PIPES] = {}; -+ u32 eir = 0, eir_stuck = 0; -+ u32 hotplug_status = 0; -+ u32 iir; -+ -+ iir = I915_READ(GEN2_IIR); -+ if (iir == 0) -+ break; -+ -+ ret = IRQ_HANDLED; -+ -+ if (iir & I915_DISPLAY_PORT_INTERRUPT) -+ hotplug_status = i9xx_hpd_irq_ack(dev_priv); -+ -+ /* Call regardless, as some status bits might not be -+ * signalled in iir */ -+ i9xx_pipestat_irq_ack(dev_priv, iir, pipe_stats); -+ -+ if (iir & I915_MASTER_ERROR_INTERRUPT) -+ i9xx_error_irq_ack(dev_priv, &eir, &eir_stuck); -+ -+ I915_WRITE(GEN2_IIR, iir); -+ -+ if (iir & I915_USER_INTERRUPT) -+ intel_engine_breadcrumbs_irq(dev_priv->engine[RCS0]); -+ -+ if (iir & I915_BSD_USER_INTERRUPT) -+ intel_engine_breadcrumbs_irq(dev_priv->engine[VCS0]); -+ -+ if (iir & I915_MASTER_ERROR_INTERRUPT) -+ i9xx_error_irq_handler(dev_priv, eir, eir_stuck); -+ -+ if (hotplug_status) -+ i9xx_hpd_irq_handler(dev_priv, hotplug_status); -+ -+ i965_pipestat_irq_handler(dev_priv, iir, pipe_stats); -+ } while (0); -+ -+ enable_rpm_wakeref_asserts(dev_priv); -+ -+ return ret; -+} -+ -+/** -+ * intel_irq_init - initializes irq support -+ * @dev_priv: i915 device instance -+ * -+ * This function initializes all the irq support including work items, timers -+ * and all the vtables. It does not setup the interrupt itself though. -+ */ -+void intel_irq_init(struct drm_i915_private *dev_priv) -+{ -+ struct drm_device *dev = &dev_priv->drm; -+ struct intel_rps *rps = &dev_priv->gt_pm.rps; -+ int i; -+ -+ if (IS_I945GM(dev_priv)) -+ i945gm_vblank_work_init(dev_priv); -+ -+ intel_hpd_init_work(dev_priv); -+ -+ INIT_WORK(&rps->work, gen6_pm_rps_work); -+ -+ INIT_WORK(&dev_priv->l3_parity.error_work, ivybridge_parity_work); -+ for (i = 0; i < MAX_L3_SLICES; ++i) -+ dev_priv->l3_parity.remap_info[i] = NULL; -+ -+ if (HAS_GUC_SCHED(dev_priv)) -+ dev_priv->pm_guc_events = GEN9_GUC_TO_HOST_INT_EVENT; -+ -+ /* Let's track the enabled rps events */ -+ if (IS_VALLEYVIEW(dev_priv)) -+ /* WaGsvRC0ResidencyMethod:vlv */ -+ dev_priv->pm_rps_events = GEN6_PM_RP_UP_EI_EXPIRED; -+ else -+ dev_priv->pm_rps_events = (GEN6_PM_RP_UP_THRESHOLD | -+ GEN6_PM_RP_DOWN_THRESHOLD | -+ GEN6_PM_RP_DOWN_TIMEOUT); -+ -+ /* We share the register with other engine */ -+ if (INTEL_GEN(dev_priv) > 9) -+ GEM_WARN_ON(dev_priv->pm_rps_events & 0xffff0000); -+ -+ rps->pm_intrmsk_mbz = 0; -+ -+ /* -+ * SNB,IVB,HSW can while VLV,CHV may hard hang on looping batchbuffer -+ * if GEN6_PM_UP_EI_EXPIRED is masked. -+ * -+ * TODO: verify if this can be reproduced on VLV,CHV. -+ */ -+ if (INTEL_GEN(dev_priv) <= 7) -+ rps->pm_intrmsk_mbz |= GEN6_PM_RP_UP_EI_EXPIRED; -+ -+ if (INTEL_GEN(dev_priv) >= 8) -+ rps->pm_intrmsk_mbz |= GEN8_PMINTR_DISABLE_REDIRECT_TO_GUC; -+ -+ if (INTEL_GEN(dev_priv) >= 5 || IS_G4X(dev_priv)) -+ dev->driver->get_vblank_counter = g4x_get_vblank_counter; -+ else if (INTEL_GEN(dev_priv) >= 3) -+ dev->driver->get_vblank_counter = i915_get_vblank_counter; -+ -+ dev->vblank_disable_immediate = true; -+ -+ /* Most platforms treat the display irq block as an always-on -+ * power domain. vlv/chv can disable it at runtime and need -+ * special care to avoid writing any of the display block registers -+ * outside of the power domain. We defer setting up the display irqs -+ * in this case to the runtime pm. -+ */ -+ dev_priv->display_irqs_enabled = true; -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ dev_priv->display_irqs_enabled = false; -+ -+ dev_priv->hotplug.hpd_storm_threshold = HPD_STORM_DEFAULT_THRESHOLD; -+ /* If we have MST support, we want to avoid doing short HPD IRQ storm -+ * detection, as short HPD storms will occur as a natural part of -+ * sideband messaging with MST. -+ * On older platforms however, IRQ storms can occur with both long and -+ * short pulses, as seen on some G4x systems. -+ */ -+ dev_priv->hotplug.hpd_short_storm_enabled = !HAS_DP_MST(dev_priv); -+ -+ dev->driver->get_vblank_timestamp = drm_calc_vbltimestamp_from_scanoutpos; -+ dev->driver->get_scanout_position = i915_get_crtc_scanoutpos; -+ -+ if (IS_CHERRYVIEW(dev_priv)) { -+ dev->driver->irq_handler = cherryview_irq_handler; -+ dev->driver->irq_preinstall = cherryview_irq_reset; -+ dev->driver->irq_postinstall = cherryview_irq_postinstall; -+ dev->driver->irq_uninstall = cherryview_irq_reset; -+ dev->driver->enable_vblank = i965_enable_vblank; -+ dev->driver->disable_vblank = i965_disable_vblank; -+ dev_priv->display.hpd_irq_setup = i915_hpd_irq_setup; -+ } else if (IS_VALLEYVIEW(dev_priv)) { -+ dev->driver->irq_handler = valleyview_irq_handler; -+ dev->driver->irq_preinstall = valleyview_irq_reset; -+ dev->driver->irq_postinstall = valleyview_irq_postinstall; -+ dev->driver->irq_uninstall = valleyview_irq_reset; -+ dev->driver->enable_vblank = i965_enable_vblank; -+ dev->driver->disable_vblank = i965_disable_vblank; -+ dev_priv->display.hpd_irq_setup = i915_hpd_irq_setup; -+ } else if (INTEL_GEN(dev_priv) >= 11) { -+ dev->driver->irq_handler = gen11_irq_handler; -+ dev->driver->irq_preinstall = gen11_irq_reset; -+ dev->driver->irq_postinstall = gen11_irq_postinstall; -+ dev->driver->irq_uninstall = gen11_irq_reset; -+ dev->driver->enable_vblank = gen8_enable_vblank; -+ dev->driver->disable_vblank = gen8_disable_vblank; -+ dev_priv->display.hpd_irq_setup = gen11_hpd_irq_setup; -+ } else if (INTEL_GEN(dev_priv) >= 8) { -+ dev->driver->irq_handler = gen8_irq_handler; -+ dev->driver->irq_preinstall = gen8_irq_reset; -+ dev->driver->irq_postinstall = gen8_irq_postinstall; -+ dev->driver->irq_uninstall = gen8_irq_reset; -+ dev->driver->enable_vblank = gen8_enable_vblank; -+ dev->driver->disable_vblank = gen8_disable_vblank; -+ if (IS_GEN9_LP(dev_priv)) -+ dev_priv->display.hpd_irq_setup = bxt_hpd_irq_setup; -+ else if (INTEL_PCH_TYPE(dev_priv) >= PCH_SPT) -+ dev_priv->display.hpd_irq_setup = spt_hpd_irq_setup; -+ else -+ dev_priv->display.hpd_irq_setup = ilk_hpd_irq_setup; -+ } else if (HAS_PCH_SPLIT(dev_priv)) { -+ dev->driver->irq_handler = ironlake_irq_handler; -+ dev->driver->irq_preinstall = ironlake_irq_reset; -+ dev->driver->irq_postinstall = ironlake_irq_postinstall; -+ dev->driver->irq_uninstall = ironlake_irq_reset; -+ dev->driver->enable_vblank = ironlake_enable_vblank; -+ dev->driver->disable_vblank = ironlake_disable_vblank; -+ dev_priv->display.hpd_irq_setup = ilk_hpd_irq_setup; -+ } else { -+ if (IS_GEN(dev_priv, 2)) { -+ dev->driver->irq_preinstall = i8xx_irq_reset; -+ dev->driver->irq_postinstall = i8xx_irq_postinstall; -+ dev->driver->irq_handler = i8xx_irq_handler; -+ dev->driver->irq_uninstall = i8xx_irq_reset; -+ dev->driver->enable_vblank = i8xx_enable_vblank; -+ dev->driver->disable_vblank = i8xx_disable_vblank; -+ } else if (IS_I945GM(dev_priv)) { -+ dev->driver->irq_preinstall = i915_irq_reset; -+ dev->driver->irq_postinstall = i915_irq_postinstall; -+ dev->driver->irq_uninstall = i915_irq_reset; -+ dev->driver->irq_handler = i915_irq_handler; -+ dev->driver->enable_vblank = i945gm_enable_vblank; -+ dev->driver->disable_vblank = i945gm_disable_vblank; -+ } else if (IS_GEN(dev_priv, 3)) { -+ dev->driver->irq_preinstall = i915_irq_reset; -+ dev->driver->irq_postinstall = i915_irq_postinstall; -+ dev->driver->irq_uninstall = i915_irq_reset; -+ dev->driver->irq_handler = i915_irq_handler; -+ dev->driver->enable_vblank = i8xx_enable_vblank; -+ dev->driver->disable_vblank = i8xx_disable_vblank; -+ } else { -+ dev->driver->irq_preinstall = i965_irq_reset; -+ dev->driver->irq_postinstall = i965_irq_postinstall; -+ dev->driver->irq_uninstall = i965_irq_reset; -+ dev->driver->irq_handler = i965_irq_handler; -+ dev->driver->enable_vblank = i965_enable_vblank; -+ dev->driver->disable_vblank = i965_disable_vblank; -+ } -+ if (I915_HAS_HOTPLUG(dev_priv)) -+ dev_priv->display.hpd_irq_setup = i915_hpd_irq_setup; -+ } -+} -+ -+/** -+ * intel_irq_fini - deinitializes IRQ support -+ * @i915: i915 device instance -+ * -+ * This function deinitializes all the IRQ support. -+ */ -+void intel_irq_fini(struct drm_i915_private *i915) -+{ -+ int i; -+ -+ if (IS_I945GM(i915)) -+ i945gm_vblank_work_fini(i915); -+ -+ for (i = 0; i < MAX_L3_SLICES; ++i) -+ kfree(i915->l3_parity.remap_info[i]); -+} -+ -+/** -+ * intel_irq_install - enables the hardware interrupt -+ * @dev_priv: i915 device instance -+ * -+ * This function enables the hardware interrupt handling, but leaves the hotplug -+ * handling still disabled. It is called after intel_irq_init(). -+ * -+ * In the driver load and resume code we need working interrupts in a few places -+ * but don't want to deal with the hassle of concurrent probe and hotplug -+ * workers. Hence the split into this two-stage approach. -+ */ -+int intel_irq_install(struct drm_i915_private *dev_priv) -+{ -+ /* -+ * We enable some interrupt sources in our postinstall hooks, so mark -+ * interrupts as enabled _before_ actually enabling them to avoid -+ * special cases in our ordering checks. -+ */ -+ dev_priv->runtime_pm.irqs_enabled = true; -+ -+ return drm_irq_install(&dev_priv->drm, dev_priv->drm.pdev->irq); -+} -+ -+/** -+ * intel_irq_uninstall - finilizes all irq handling -+ * @dev_priv: i915 device instance -+ * -+ * This stops interrupt and hotplug handling and unregisters and frees all -+ * resources acquired in the init functions. -+ */ -+void intel_irq_uninstall(struct drm_i915_private *dev_priv) -+{ -+ drm_irq_uninstall(&dev_priv->drm); -+ intel_hpd_cancel_work(dev_priv); -+ dev_priv->runtime_pm.irqs_enabled = false; -+} -+ -+/** -+ * intel_runtime_pm_disable_interrupts - runtime interrupt disabling -+ * @dev_priv: i915 device instance -+ * -+ * This function is used to disable interrupts at runtime, both in the runtime -+ * pm and the system suspend/resume code. -+ */ -+void intel_runtime_pm_disable_interrupts(struct drm_i915_private *dev_priv) -+{ -+ dev_priv->drm.driver->irq_uninstall(&dev_priv->drm); -+ dev_priv->runtime_pm.irqs_enabled = false; -+ synchronize_irq(dev_priv->drm.irq); -+} -+ -+/** -+ * intel_runtime_pm_enable_interrupts - runtime interrupt enabling -+ * @dev_priv: i915 device instance -+ * -+ * This function is used to enable interrupts at runtime, both in the runtime -+ * pm and the system suspend/resume code. -+ */ -+void intel_runtime_pm_enable_interrupts(struct drm_i915_private *dev_priv) -+{ -+ dev_priv->runtime_pm.irqs_enabled = true; -+ dev_priv->drm.driver->irq_preinstall(&dev_priv->drm); -+ dev_priv->drm.driver->irq_postinstall(&dev_priv->drm); -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_memcpy.c b/drivers/gpu/drm/i915_legacy/i915_memcpy.c -new file mode 100644 -index 000000000000..79f8ec756362 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_memcpy.c -@@ -0,0 +1,106 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include -+#include -+ -+#include "i915_drv.h" -+ -+static DEFINE_STATIC_KEY_FALSE(has_movntdqa); -+ -+#ifdef CONFIG_AS_MOVNTDQA -+static void __memcpy_ntdqa(void *dst, const void *src, unsigned long len) -+{ -+ kernel_fpu_begin(); -+ -+ len >>= 4; -+ while (len >= 4) { -+ asm("movntdqa (%0), %%xmm0\n" -+ "movntdqa 16(%0), %%xmm1\n" -+ "movntdqa 32(%0), %%xmm2\n" -+ "movntdqa 48(%0), %%xmm3\n" -+ "movaps %%xmm0, (%1)\n" -+ "movaps %%xmm1, 16(%1)\n" -+ "movaps %%xmm2, 32(%1)\n" -+ "movaps %%xmm3, 48(%1)\n" -+ :: "r" (src), "r" (dst) : "memory"); -+ src += 64; -+ dst += 64; -+ len -= 4; -+ } -+ while (len--) { -+ asm("movntdqa (%0), %%xmm0\n" -+ "movaps %%xmm0, (%1)\n" -+ :: "r" (src), "r" (dst) : "memory"); -+ src += 16; -+ dst += 16; -+ } -+ -+ kernel_fpu_end(); -+} -+#endif -+ -+/** -+ * i915_memcpy_from_wc: perform an accelerated *aligned* read from WC -+ * @dst: destination pointer -+ * @src: source pointer -+ * @len: how many bytes to copy -+ * -+ * i915_memcpy_from_wc copies @len bytes from @src to @dst using -+ * non-temporal instructions where available. Note that all arguments -+ * (@src, @dst) must be aligned to 16 bytes and @len must be a multiple -+ * of 16. -+ * -+ * To test whether accelerated reads from WC are supported, use -+ * i915_memcpy_from_wc(NULL, NULL, 0); -+ * -+ * Returns true if the copy was successful, false if the preconditions -+ * are not met. -+ */ -+bool i915_memcpy_from_wc(void *dst, const void *src, unsigned long len) -+{ -+ if (unlikely(((unsigned long)dst | (unsigned long)src | len) & 15)) -+ return false; -+ -+#ifdef CONFIG_AS_MOVNTDQA -+ if (static_branch_likely(&has_movntdqa)) { -+ if (likely(len)) -+ __memcpy_ntdqa(dst, src, len); -+ return true; -+ } -+#endif -+ -+ return false; -+} -+ -+void i915_memcpy_init_early(struct drm_i915_private *dev_priv) -+{ -+ /* -+ * Some hypervisors (e.g. KVM) don't support VEX-prefix instructions -+ * emulation. So don't enable movntdqa in hypervisor guest. -+ */ -+ if (static_cpu_has(X86_FEATURE_XMM4_1) && -+ !boot_cpu_has(X86_FEATURE_HYPERVISOR)) -+ static_branch_enable(&has_movntdqa); -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_mm.c b/drivers/gpu/drm/i915_legacy/i915_mm.c -new file mode 100644 -index 000000000000..c23bb29e6d3e ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_mm.c -@@ -0,0 +1,83 @@ -+/* -+ * Copyright © 2014 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include -+#include -+ -+#include -+ -+#include "i915_drv.h" -+ -+struct remap_pfn { -+ struct mm_struct *mm; -+ unsigned long pfn; -+ pgprot_t prot; -+}; -+ -+static int remap_pfn(pte_t *pte, unsigned long addr, void *data) -+{ -+ struct remap_pfn *r = data; -+ -+ /* Special PTE are not associated with any struct page */ -+ set_pte_at(r->mm, addr, pte, pte_mkspecial(pfn_pte(r->pfn, r->prot))); -+ r->pfn++; -+ -+ return 0; -+} -+ -+/** -+ * remap_io_mapping - remap an IO mapping to userspace -+ * @vma: user vma to map to -+ * @addr: target user address to start at -+ * @pfn: physical address of kernel memory -+ * @size: size of map area -+ * @iomap: the source io_mapping -+ * -+ * Note: this is only safe if the mm semaphore is held when called. -+ */ -+int remap_io_mapping(struct vm_area_struct *vma, -+ unsigned long addr, unsigned long pfn, unsigned long size, -+ struct io_mapping *iomap) -+{ -+ struct remap_pfn r; -+ int err; -+ -+ GEM_BUG_ON((vma->vm_flags & -+ (VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP)) != -+ (VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP)); -+ -+ /* We rely on prevalidation of the io-mapping to skip track_pfn(). */ -+ r.mm = vma->vm_mm; -+ r.pfn = pfn; -+ r.prot = __pgprot((pgprot_val(iomap->prot) & _PAGE_CACHE_MASK) | -+ (pgprot_val(vma->vm_page_prot) & ~_PAGE_CACHE_MASK)); -+ -+ err = apply_to_page_range(r.mm, addr, size, remap_pfn, &r); -+ if (unlikely(err)) { -+ zap_vma_ptes(vma, addr, (r.pfn - pfn) << PAGE_SHIFT); -+ return err; -+ } -+ -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_bdw.c b/drivers/gpu/drm/i915_legacy/i915_oa_bdw.c -new file mode 100644 -index 000000000000..4acdb94555b7 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_bdw.c -@@ -0,0 +1,91 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_bdw.h" -+ -+static const struct i915_oa_reg b_counter_config_test_oa[] = { -+ { _MMIO(0x2740), 0x00000000 }, -+ { _MMIO(0x2744), 0x00800000 }, -+ { _MMIO(0x2714), 0xf0800000 }, -+ { _MMIO(0x2710), 0x00000000 }, -+ { _MMIO(0x2724), 0xf0800000 }, -+ { _MMIO(0x2720), 0x00000000 }, -+ { _MMIO(0x2770), 0x00000004 }, -+ { _MMIO(0x2774), 0x00000000 }, -+ { _MMIO(0x2778), 0x00000003 }, -+ { _MMIO(0x277c), 0x00000000 }, -+ { _MMIO(0x2780), 0x00000007 }, -+ { _MMIO(0x2784), 0x00000000 }, -+ { _MMIO(0x2788), 0x00100002 }, -+ { _MMIO(0x278c), 0x0000fff7 }, -+ { _MMIO(0x2790), 0x00100002 }, -+ { _MMIO(0x2794), 0x0000ffcf }, -+ { _MMIO(0x2798), 0x00100082 }, -+ { _MMIO(0x279c), 0x0000ffef }, -+ { _MMIO(0x27a0), 0x001000c2 }, -+ { _MMIO(0x27a4), 0x0000ffe7 }, -+ { _MMIO(0x27a8), 0x00100001 }, -+ { _MMIO(0x27ac), 0x0000ffe7 }, -+}; -+ -+static const struct i915_oa_reg flex_eu_config_test_oa[] = { -+}; -+ -+static const struct i915_oa_reg mux_config_test_oa[] = { -+ { _MMIO(0x9840), 0x000000a0 }, -+ { _MMIO(0x9888), 0x198b0000 }, -+ { _MMIO(0x9888), 0x078b0066 }, -+ { _MMIO(0x9888), 0x118b0000 }, -+ { _MMIO(0x9888), 0x258b0000 }, -+ { _MMIO(0x9888), 0x21850008 }, -+ { _MMIO(0x9888), 0x0d834000 }, -+ { _MMIO(0x9888), 0x07844000 }, -+ { _MMIO(0x9888), 0x17804000 }, -+ { _MMIO(0x9888), 0x21800000 }, -+ { _MMIO(0x9888), 0x4f800000 }, -+ { _MMIO(0x9888), 0x41800000 }, -+ { _MMIO(0x9888), 0x31800000 }, -+ { _MMIO(0x9840), 0x00000080 }, -+}; -+ -+static ssize_t -+show_test_oa_id(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ return sprintf(buf, "1\n"); -+} -+ -+void -+i915_perf_load_test_config_bdw(struct drm_i915_private *dev_priv) -+{ -+ strlcpy(dev_priv->perf.oa.test_config.uuid, -+ "d6de6f55-e526-4f79-a6a6-d7315c09044e", -+ sizeof(dev_priv->perf.oa.test_config.uuid)); -+ dev_priv->perf.oa.test_config.id = 1; -+ -+ dev_priv->perf.oa.test_config.mux_regs = mux_config_test_oa; -+ dev_priv->perf.oa.test_config.mux_regs_len = ARRAY_SIZE(mux_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.b_counter_regs = b_counter_config_test_oa; -+ dev_priv->perf.oa.test_config.b_counter_regs_len = ARRAY_SIZE(b_counter_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.flex_regs = flex_eu_config_test_oa; -+ dev_priv->perf.oa.test_config.flex_regs_len = ARRAY_SIZE(flex_eu_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.sysfs_metric.name = "d6de6f55-e526-4f79-a6a6-d7315c09044e"; -+ dev_priv->perf.oa.test_config.sysfs_metric.attrs = dev_priv->perf.oa.test_config.attrs; -+ -+ dev_priv->perf.oa.test_config.attrs[0] = &dev_priv->perf.oa.test_config.sysfs_metric_id.attr; -+ -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.name = "id"; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.mode = 0444; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.show = show_test_oa_id; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_bdw.h b/drivers/gpu/drm/i915_legacy/i915_oa_bdw.h -new file mode 100644 -index 000000000000..0e667f1a8aa1 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_bdw.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#ifndef __I915_OA_BDW_H__ -+#define __I915_OA_BDW_H__ -+ -+extern void i915_perf_load_test_config_bdw(struct drm_i915_private *dev_priv); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_bxt.c b/drivers/gpu/drm/i915_legacy/i915_oa_bxt.c -new file mode 100644 -index 000000000000..a44195c39923 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_bxt.c -@@ -0,0 +1,89 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_bxt.h" -+ -+static const struct i915_oa_reg b_counter_config_test_oa[] = { -+ { _MMIO(0x2740), 0x00000000 }, -+ { _MMIO(0x2744), 0x00800000 }, -+ { _MMIO(0x2714), 0xf0800000 }, -+ { _MMIO(0x2710), 0x00000000 }, -+ { _MMIO(0x2724), 0xf0800000 }, -+ { _MMIO(0x2720), 0x00000000 }, -+ { _MMIO(0x2770), 0x00000004 }, -+ { _MMIO(0x2774), 0x00000000 }, -+ { _MMIO(0x2778), 0x00000003 }, -+ { _MMIO(0x277c), 0x00000000 }, -+ { _MMIO(0x2780), 0x00000007 }, -+ { _MMIO(0x2784), 0x00000000 }, -+ { _MMIO(0x2788), 0x00100002 }, -+ { _MMIO(0x278c), 0x0000fff7 }, -+ { _MMIO(0x2790), 0x00100002 }, -+ { _MMIO(0x2794), 0x0000ffcf }, -+ { _MMIO(0x2798), 0x00100082 }, -+ { _MMIO(0x279c), 0x0000ffef }, -+ { _MMIO(0x27a0), 0x001000c2 }, -+ { _MMIO(0x27a4), 0x0000ffe7 }, -+ { _MMIO(0x27a8), 0x00100001 }, -+ { _MMIO(0x27ac), 0x0000ffe7 }, -+}; -+ -+static const struct i915_oa_reg flex_eu_config_test_oa[] = { -+}; -+ -+static const struct i915_oa_reg mux_config_test_oa[] = { -+ { _MMIO(0x9840), 0x00000080 }, -+ { _MMIO(0x9888), 0x19800000 }, -+ { _MMIO(0x9888), 0x07800063 }, -+ { _MMIO(0x9888), 0x11800000 }, -+ { _MMIO(0x9888), 0x23810008 }, -+ { _MMIO(0x9888), 0x1d950400 }, -+ { _MMIO(0x9888), 0x0f922000 }, -+ { _MMIO(0x9888), 0x1f908000 }, -+ { _MMIO(0x9888), 0x37900000 }, -+ { _MMIO(0x9888), 0x55900000 }, -+ { _MMIO(0x9888), 0x47900000 }, -+ { _MMIO(0x9888), 0x33900000 }, -+}; -+ -+static ssize_t -+show_test_oa_id(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ return sprintf(buf, "1\n"); -+} -+ -+void -+i915_perf_load_test_config_bxt(struct drm_i915_private *dev_priv) -+{ -+ strlcpy(dev_priv->perf.oa.test_config.uuid, -+ "5ee72f5c-092f-421e-8b70-225f7c3e9612", -+ sizeof(dev_priv->perf.oa.test_config.uuid)); -+ dev_priv->perf.oa.test_config.id = 1; -+ -+ dev_priv->perf.oa.test_config.mux_regs = mux_config_test_oa; -+ dev_priv->perf.oa.test_config.mux_regs_len = ARRAY_SIZE(mux_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.b_counter_regs = b_counter_config_test_oa; -+ dev_priv->perf.oa.test_config.b_counter_regs_len = ARRAY_SIZE(b_counter_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.flex_regs = flex_eu_config_test_oa; -+ dev_priv->perf.oa.test_config.flex_regs_len = ARRAY_SIZE(flex_eu_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.sysfs_metric.name = "5ee72f5c-092f-421e-8b70-225f7c3e9612"; -+ dev_priv->perf.oa.test_config.sysfs_metric.attrs = dev_priv->perf.oa.test_config.attrs; -+ -+ dev_priv->perf.oa.test_config.attrs[0] = &dev_priv->perf.oa.test_config.sysfs_metric_id.attr; -+ -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.name = "id"; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.mode = 0444; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.show = show_test_oa_id; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_bxt.h b/drivers/gpu/drm/i915_legacy/i915_oa_bxt.h -new file mode 100644 -index 000000000000..679e92cf4f1d ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_bxt.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#ifndef __I915_OA_BXT_H__ -+#define __I915_OA_BXT_H__ -+ -+extern void i915_perf_load_test_config_bxt(struct drm_i915_private *dev_priv); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_cflgt2.c b/drivers/gpu/drm/i915_legacy/i915_oa_cflgt2.c -new file mode 100644 -index 000000000000..7f60d51b8761 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_cflgt2.c -@@ -0,0 +1,90 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_cflgt2.h" -+ -+static const struct i915_oa_reg b_counter_config_test_oa[] = { -+ { _MMIO(0x2740), 0x00000000 }, -+ { _MMIO(0x2744), 0x00800000 }, -+ { _MMIO(0x2714), 0xf0800000 }, -+ { _MMIO(0x2710), 0x00000000 }, -+ { _MMIO(0x2724), 0xf0800000 }, -+ { _MMIO(0x2720), 0x00000000 }, -+ { _MMIO(0x2770), 0x00000004 }, -+ { _MMIO(0x2774), 0x00000000 }, -+ { _MMIO(0x2778), 0x00000003 }, -+ { _MMIO(0x277c), 0x00000000 }, -+ { _MMIO(0x2780), 0x00000007 }, -+ { _MMIO(0x2784), 0x00000000 }, -+ { _MMIO(0x2788), 0x00100002 }, -+ { _MMIO(0x278c), 0x0000fff7 }, -+ { _MMIO(0x2790), 0x00100002 }, -+ { _MMIO(0x2794), 0x0000ffcf }, -+ { _MMIO(0x2798), 0x00100082 }, -+ { _MMIO(0x279c), 0x0000ffef }, -+ { _MMIO(0x27a0), 0x001000c2 }, -+ { _MMIO(0x27a4), 0x0000ffe7 }, -+ { _MMIO(0x27a8), 0x00100001 }, -+ { _MMIO(0x27ac), 0x0000ffe7 }, -+}; -+ -+static const struct i915_oa_reg flex_eu_config_test_oa[] = { -+}; -+ -+static const struct i915_oa_reg mux_config_test_oa[] = { -+ { _MMIO(0x9840), 0x00000080 }, -+ { _MMIO(0x9888), 0x11810000 }, -+ { _MMIO(0x9888), 0x07810013 }, -+ { _MMIO(0x9888), 0x1f810000 }, -+ { _MMIO(0x9888), 0x1d810000 }, -+ { _MMIO(0x9888), 0x1b930040 }, -+ { _MMIO(0x9888), 0x07e54000 }, -+ { _MMIO(0x9888), 0x1f908000 }, -+ { _MMIO(0x9888), 0x11900000 }, -+ { _MMIO(0x9888), 0x37900000 }, -+ { _MMIO(0x9888), 0x53900000 }, -+ { _MMIO(0x9888), 0x45900000 }, -+ { _MMIO(0x9888), 0x33900000 }, -+}; -+ -+static ssize_t -+show_test_oa_id(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ return sprintf(buf, "1\n"); -+} -+ -+void -+i915_perf_load_test_config_cflgt2(struct drm_i915_private *dev_priv) -+{ -+ strlcpy(dev_priv->perf.oa.test_config.uuid, -+ "74fb4902-d3d3-4237-9e90-cbdc68d0a446", -+ sizeof(dev_priv->perf.oa.test_config.uuid)); -+ dev_priv->perf.oa.test_config.id = 1; -+ -+ dev_priv->perf.oa.test_config.mux_regs = mux_config_test_oa; -+ dev_priv->perf.oa.test_config.mux_regs_len = ARRAY_SIZE(mux_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.b_counter_regs = b_counter_config_test_oa; -+ dev_priv->perf.oa.test_config.b_counter_regs_len = ARRAY_SIZE(b_counter_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.flex_regs = flex_eu_config_test_oa; -+ dev_priv->perf.oa.test_config.flex_regs_len = ARRAY_SIZE(flex_eu_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.sysfs_metric.name = "74fb4902-d3d3-4237-9e90-cbdc68d0a446"; -+ dev_priv->perf.oa.test_config.sysfs_metric.attrs = dev_priv->perf.oa.test_config.attrs; -+ -+ dev_priv->perf.oa.test_config.attrs[0] = &dev_priv->perf.oa.test_config.sysfs_metric_id.attr; -+ -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.name = "id"; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.mode = 0444; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.show = show_test_oa_id; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_cflgt2.h b/drivers/gpu/drm/i915_legacy/i915_oa_cflgt2.h -new file mode 100644 -index 000000000000..4d6025559bbe ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_cflgt2.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#ifndef __I915_OA_CFLGT2_H__ -+#define __I915_OA_CFLGT2_H__ -+ -+extern void i915_perf_load_test_config_cflgt2(struct drm_i915_private *dev_priv); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_cflgt3.c b/drivers/gpu/drm/i915_legacy/i915_oa_cflgt3.c -new file mode 100644 -index 000000000000..a92c38e3a0ce ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_cflgt3.c -@@ -0,0 +1,90 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_cflgt3.h" -+ -+static const struct i915_oa_reg b_counter_config_test_oa[] = { -+ { _MMIO(0x2740), 0x00000000 }, -+ { _MMIO(0x2744), 0x00800000 }, -+ { _MMIO(0x2714), 0xf0800000 }, -+ { _MMIO(0x2710), 0x00000000 }, -+ { _MMIO(0x2724), 0xf0800000 }, -+ { _MMIO(0x2720), 0x00000000 }, -+ { _MMIO(0x2770), 0x00000004 }, -+ { _MMIO(0x2774), 0x00000000 }, -+ { _MMIO(0x2778), 0x00000003 }, -+ { _MMIO(0x277c), 0x00000000 }, -+ { _MMIO(0x2780), 0x00000007 }, -+ { _MMIO(0x2784), 0x00000000 }, -+ { _MMIO(0x2788), 0x00100002 }, -+ { _MMIO(0x278c), 0x0000fff7 }, -+ { _MMIO(0x2790), 0x00100002 }, -+ { _MMIO(0x2794), 0x0000ffcf }, -+ { _MMIO(0x2798), 0x00100082 }, -+ { _MMIO(0x279c), 0x0000ffef }, -+ { _MMIO(0x27a0), 0x001000c2 }, -+ { _MMIO(0x27a4), 0x0000ffe7 }, -+ { _MMIO(0x27a8), 0x00100001 }, -+ { _MMIO(0x27ac), 0x0000ffe7 }, -+}; -+ -+static const struct i915_oa_reg flex_eu_config_test_oa[] = { -+}; -+ -+static const struct i915_oa_reg mux_config_test_oa[] = { -+ { _MMIO(0x9840), 0x00000080 }, -+ { _MMIO(0x9888), 0x11810000 }, -+ { _MMIO(0x9888), 0x07810013 }, -+ { _MMIO(0x9888), 0x1f810000 }, -+ { _MMIO(0x9888), 0x1d810000 }, -+ { _MMIO(0x9888), 0x1b930040 }, -+ { _MMIO(0x9888), 0x07e54000 }, -+ { _MMIO(0x9888), 0x1f908000 }, -+ { _MMIO(0x9888), 0x11900000 }, -+ { _MMIO(0x9888), 0x37900000 }, -+ { _MMIO(0x9888), 0x53900000 }, -+ { _MMIO(0x9888), 0x45900000 }, -+ { _MMIO(0x9888), 0x33900000 }, -+}; -+ -+static ssize_t -+show_test_oa_id(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ return sprintf(buf, "1\n"); -+} -+ -+void -+i915_perf_load_test_config_cflgt3(struct drm_i915_private *dev_priv) -+{ -+ strlcpy(dev_priv->perf.oa.test_config.uuid, -+ "577e8e2c-3fa0-4875-8743-3538d585e3b0", -+ sizeof(dev_priv->perf.oa.test_config.uuid)); -+ dev_priv->perf.oa.test_config.id = 1; -+ -+ dev_priv->perf.oa.test_config.mux_regs = mux_config_test_oa; -+ dev_priv->perf.oa.test_config.mux_regs_len = ARRAY_SIZE(mux_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.b_counter_regs = b_counter_config_test_oa; -+ dev_priv->perf.oa.test_config.b_counter_regs_len = ARRAY_SIZE(b_counter_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.flex_regs = flex_eu_config_test_oa; -+ dev_priv->perf.oa.test_config.flex_regs_len = ARRAY_SIZE(flex_eu_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.sysfs_metric.name = "577e8e2c-3fa0-4875-8743-3538d585e3b0"; -+ dev_priv->perf.oa.test_config.sysfs_metric.attrs = dev_priv->perf.oa.test_config.attrs; -+ -+ dev_priv->perf.oa.test_config.attrs[0] = &dev_priv->perf.oa.test_config.sysfs_metric_id.attr; -+ -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.name = "id"; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.mode = 0444; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.show = show_test_oa_id; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_cflgt3.h b/drivers/gpu/drm/i915_legacy/i915_oa_cflgt3.h -new file mode 100644 -index 000000000000..0697f4077402 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_cflgt3.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#ifndef __I915_OA_CFLGT3_H__ -+#define __I915_OA_CFLGT3_H__ -+ -+extern void i915_perf_load_test_config_cflgt3(struct drm_i915_private *dev_priv); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_chv.c b/drivers/gpu/drm/i915_legacy/i915_oa_chv.c -new file mode 100644 -index 000000000000..71ec889a0114 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_chv.c -@@ -0,0 +1,90 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_chv.h" -+ -+static const struct i915_oa_reg b_counter_config_test_oa[] = { -+ { _MMIO(0x2740), 0x00000000 }, -+ { _MMIO(0x2744), 0x00800000 }, -+ { _MMIO(0x2714), 0xf0800000 }, -+ { _MMIO(0x2710), 0x00000000 }, -+ { _MMIO(0x2724), 0xf0800000 }, -+ { _MMIO(0x2720), 0x00000000 }, -+ { _MMIO(0x2770), 0x00000004 }, -+ { _MMIO(0x2774), 0x00000000 }, -+ { _MMIO(0x2778), 0x00000003 }, -+ { _MMIO(0x277c), 0x00000000 }, -+ { _MMIO(0x2780), 0x00000007 }, -+ { _MMIO(0x2784), 0x00000000 }, -+ { _MMIO(0x2788), 0x00100002 }, -+ { _MMIO(0x278c), 0x0000fff7 }, -+ { _MMIO(0x2790), 0x00100002 }, -+ { _MMIO(0x2794), 0x0000ffcf }, -+ { _MMIO(0x2798), 0x00100082 }, -+ { _MMIO(0x279c), 0x0000ffef }, -+ { _MMIO(0x27a0), 0x001000c2 }, -+ { _MMIO(0x27a4), 0x0000ffe7 }, -+ { _MMIO(0x27a8), 0x00100001 }, -+ { _MMIO(0x27ac), 0x0000ffe7 }, -+}; -+ -+static const struct i915_oa_reg flex_eu_config_test_oa[] = { -+}; -+ -+static const struct i915_oa_reg mux_config_test_oa[] = { -+ { _MMIO(0x9840), 0x000000a0 }, -+ { _MMIO(0x9888), 0x59800000 }, -+ { _MMIO(0x9888), 0x59800001 }, -+ { _MMIO(0x9888), 0x338b0000 }, -+ { _MMIO(0x9888), 0x258b0066 }, -+ { _MMIO(0x9888), 0x058b0000 }, -+ { _MMIO(0x9888), 0x038b0000 }, -+ { _MMIO(0x9888), 0x03844000 }, -+ { _MMIO(0x9888), 0x47800080 }, -+ { _MMIO(0x9888), 0x57800000 }, -+ { _MMIO(0x1823a4), 0x00000000 }, -+ { _MMIO(0x9888), 0x59800000 }, -+ { _MMIO(0x9840), 0x00000080 }, -+}; -+ -+static ssize_t -+show_test_oa_id(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ return sprintf(buf, "1\n"); -+} -+ -+void -+i915_perf_load_test_config_chv(struct drm_i915_private *dev_priv) -+{ -+ strlcpy(dev_priv->perf.oa.test_config.uuid, -+ "4a534b07-cba3-414d-8d60-874830e883aa", -+ sizeof(dev_priv->perf.oa.test_config.uuid)); -+ dev_priv->perf.oa.test_config.id = 1; -+ -+ dev_priv->perf.oa.test_config.mux_regs = mux_config_test_oa; -+ dev_priv->perf.oa.test_config.mux_regs_len = ARRAY_SIZE(mux_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.b_counter_regs = b_counter_config_test_oa; -+ dev_priv->perf.oa.test_config.b_counter_regs_len = ARRAY_SIZE(b_counter_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.flex_regs = flex_eu_config_test_oa; -+ dev_priv->perf.oa.test_config.flex_regs_len = ARRAY_SIZE(flex_eu_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.sysfs_metric.name = "4a534b07-cba3-414d-8d60-874830e883aa"; -+ dev_priv->perf.oa.test_config.sysfs_metric.attrs = dev_priv->perf.oa.test_config.attrs; -+ -+ dev_priv->perf.oa.test_config.attrs[0] = &dev_priv->perf.oa.test_config.sysfs_metric_id.attr; -+ -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.name = "id"; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.mode = 0444; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.show = show_test_oa_id; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_chv.h b/drivers/gpu/drm/i915_legacy/i915_oa_chv.h -new file mode 100644 -index 000000000000..0986eae3135f ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_chv.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#ifndef __I915_OA_CHV_H__ -+#define __I915_OA_CHV_H__ -+ -+extern void i915_perf_load_test_config_chv(struct drm_i915_private *dev_priv); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_cnl.c b/drivers/gpu/drm/i915_legacy/i915_oa_cnl.c -new file mode 100644 -index 000000000000..5c23d883d6c9 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_cnl.c -@@ -0,0 +1,102 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_cnl.h" -+ -+static const struct i915_oa_reg b_counter_config_test_oa[] = { -+ { _MMIO(0x2740), 0x00000000 }, -+ { _MMIO(0x2710), 0x00000000 }, -+ { _MMIO(0x2714), 0xf0800000 }, -+ { _MMIO(0x2720), 0x00000000 }, -+ { _MMIO(0x2724), 0xf0800000 }, -+ { _MMIO(0x2770), 0x00000004 }, -+ { _MMIO(0x2774), 0x0000ffff }, -+ { _MMIO(0x2778), 0x00000003 }, -+ { _MMIO(0x277c), 0x0000ffff }, -+ { _MMIO(0x2780), 0x00000007 }, -+ { _MMIO(0x2784), 0x0000ffff }, -+ { _MMIO(0x2788), 0x00100002 }, -+ { _MMIO(0x278c), 0x0000fff7 }, -+ { _MMIO(0x2790), 0x00100002 }, -+ { _MMIO(0x2794), 0x0000ffcf }, -+ { _MMIO(0x2798), 0x00100082 }, -+ { _MMIO(0x279c), 0x0000ffef }, -+ { _MMIO(0x27a0), 0x001000c2 }, -+ { _MMIO(0x27a4), 0x0000ffe7 }, -+ { _MMIO(0x27a8), 0x00100001 }, -+ { _MMIO(0x27ac), 0x0000ffe7 }, -+}; -+ -+static const struct i915_oa_reg flex_eu_config_test_oa[] = { -+}; -+ -+static const struct i915_oa_reg mux_config_test_oa[] = { -+ { _MMIO(0xd04), 0x00000200 }, -+ { _MMIO(0x9884), 0x00000007 }, -+ { _MMIO(0x9888), 0x17060000 }, -+ { _MMIO(0x9840), 0x00000000 }, -+ { _MMIO(0x9884), 0x00000007 }, -+ { _MMIO(0x9888), 0x13034000 }, -+ { _MMIO(0x9884), 0x00000007 }, -+ { _MMIO(0x9888), 0x07060066 }, -+ { _MMIO(0x9884), 0x00000007 }, -+ { _MMIO(0x9888), 0x05060000 }, -+ { _MMIO(0x9884), 0x00000007 }, -+ { _MMIO(0x9888), 0x0f080040 }, -+ { _MMIO(0x9884), 0x00000007 }, -+ { _MMIO(0x9888), 0x07091000 }, -+ { _MMIO(0x9884), 0x00000007 }, -+ { _MMIO(0x9888), 0x0f041000 }, -+ { _MMIO(0x9884), 0x00000007 }, -+ { _MMIO(0x9888), 0x1d004000 }, -+ { _MMIO(0x9884), 0x00000007 }, -+ { _MMIO(0x9888), 0x35000000 }, -+ { _MMIO(0x9884), 0x00000007 }, -+ { _MMIO(0x9888), 0x49000000 }, -+ { _MMIO(0x9884), 0x00000007 }, -+ { _MMIO(0x9888), 0x3d000000 }, -+ { _MMIO(0x9884), 0x00000007 }, -+ { _MMIO(0x9888), 0x31000000 }, -+}; -+ -+static ssize_t -+show_test_oa_id(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ return sprintf(buf, "1\n"); -+} -+ -+void -+i915_perf_load_test_config_cnl(struct drm_i915_private *dev_priv) -+{ -+ strlcpy(dev_priv->perf.oa.test_config.uuid, -+ "db41edd4-d8e7-4730-ad11-b9a2d6833503", -+ sizeof(dev_priv->perf.oa.test_config.uuid)); -+ dev_priv->perf.oa.test_config.id = 1; -+ -+ dev_priv->perf.oa.test_config.mux_regs = mux_config_test_oa; -+ dev_priv->perf.oa.test_config.mux_regs_len = ARRAY_SIZE(mux_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.b_counter_regs = b_counter_config_test_oa; -+ dev_priv->perf.oa.test_config.b_counter_regs_len = ARRAY_SIZE(b_counter_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.flex_regs = flex_eu_config_test_oa; -+ dev_priv->perf.oa.test_config.flex_regs_len = ARRAY_SIZE(flex_eu_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.sysfs_metric.name = "db41edd4-d8e7-4730-ad11-b9a2d6833503"; -+ dev_priv->perf.oa.test_config.sysfs_metric.attrs = dev_priv->perf.oa.test_config.attrs; -+ -+ dev_priv->perf.oa.test_config.attrs[0] = &dev_priv->perf.oa.test_config.sysfs_metric_id.attr; -+ -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.name = "id"; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.mode = 0444; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.show = show_test_oa_id; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_cnl.h b/drivers/gpu/drm/i915_legacy/i915_oa_cnl.h -new file mode 100644 -index 000000000000..e830a406aff2 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_cnl.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#ifndef __I915_OA_CNL_H__ -+#define __I915_OA_CNL_H__ -+ -+extern void i915_perf_load_test_config_cnl(struct drm_i915_private *dev_priv); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_glk.c b/drivers/gpu/drm/i915_legacy/i915_oa_glk.c -new file mode 100644 -index 000000000000..4bdda66df7d2 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_glk.c -@@ -0,0 +1,89 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_glk.h" -+ -+static const struct i915_oa_reg b_counter_config_test_oa[] = { -+ { _MMIO(0x2740), 0x00000000 }, -+ { _MMIO(0x2744), 0x00800000 }, -+ { _MMIO(0x2714), 0xf0800000 }, -+ { _MMIO(0x2710), 0x00000000 }, -+ { _MMIO(0x2724), 0xf0800000 }, -+ { _MMIO(0x2720), 0x00000000 }, -+ { _MMIO(0x2770), 0x00000004 }, -+ { _MMIO(0x2774), 0x00000000 }, -+ { _MMIO(0x2778), 0x00000003 }, -+ { _MMIO(0x277c), 0x00000000 }, -+ { _MMIO(0x2780), 0x00000007 }, -+ { _MMIO(0x2784), 0x00000000 }, -+ { _MMIO(0x2788), 0x00100002 }, -+ { _MMIO(0x278c), 0x0000fff7 }, -+ { _MMIO(0x2790), 0x00100002 }, -+ { _MMIO(0x2794), 0x0000ffcf }, -+ { _MMIO(0x2798), 0x00100082 }, -+ { _MMIO(0x279c), 0x0000ffef }, -+ { _MMIO(0x27a0), 0x001000c2 }, -+ { _MMIO(0x27a4), 0x0000ffe7 }, -+ { _MMIO(0x27a8), 0x00100001 }, -+ { _MMIO(0x27ac), 0x0000ffe7 }, -+}; -+ -+static const struct i915_oa_reg flex_eu_config_test_oa[] = { -+}; -+ -+static const struct i915_oa_reg mux_config_test_oa[] = { -+ { _MMIO(0x9840), 0x00000080 }, -+ { _MMIO(0x9888), 0x19800000 }, -+ { _MMIO(0x9888), 0x07800063 }, -+ { _MMIO(0x9888), 0x11800000 }, -+ { _MMIO(0x9888), 0x23810008 }, -+ { _MMIO(0x9888), 0x1d950400 }, -+ { _MMIO(0x9888), 0x0f922000 }, -+ { _MMIO(0x9888), 0x1f908000 }, -+ { _MMIO(0x9888), 0x37900000 }, -+ { _MMIO(0x9888), 0x55900000 }, -+ { _MMIO(0x9888), 0x47900000 }, -+ { _MMIO(0x9888), 0x33900000 }, -+}; -+ -+static ssize_t -+show_test_oa_id(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ return sprintf(buf, "1\n"); -+} -+ -+void -+i915_perf_load_test_config_glk(struct drm_i915_private *dev_priv) -+{ -+ strlcpy(dev_priv->perf.oa.test_config.uuid, -+ "dd3fd789-e783-4204-8cd0-b671bbccb0cf", -+ sizeof(dev_priv->perf.oa.test_config.uuid)); -+ dev_priv->perf.oa.test_config.id = 1; -+ -+ dev_priv->perf.oa.test_config.mux_regs = mux_config_test_oa; -+ dev_priv->perf.oa.test_config.mux_regs_len = ARRAY_SIZE(mux_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.b_counter_regs = b_counter_config_test_oa; -+ dev_priv->perf.oa.test_config.b_counter_regs_len = ARRAY_SIZE(b_counter_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.flex_regs = flex_eu_config_test_oa; -+ dev_priv->perf.oa.test_config.flex_regs_len = ARRAY_SIZE(flex_eu_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.sysfs_metric.name = "dd3fd789-e783-4204-8cd0-b671bbccb0cf"; -+ dev_priv->perf.oa.test_config.sysfs_metric.attrs = dev_priv->perf.oa.test_config.attrs; -+ -+ dev_priv->perf.oa.test_config.attrs[0] = &dev_priv->perf.oa.test_config.sysfs_metric_id.attr; -+ -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.name = "id"; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.mode = 0444; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.show = show_test_oa_id; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_glk.h b/drivers/gpu/drm/i915_legacy/i915_oa_glk.h -new file mode 100644 -index 000000000000..06dedf991edb ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_glk.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#ifndef __I915_OA_GLK_H__ -+#define __I915_OA_GLK_H__ -+ -+extern void i915_perf_load_test_config_glk(struct drm_i915_private *dev_priv); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_hsw.c b/drivers/gpu/drm/i915_legacy/i915_oa_hsw.c -new file mode 100644 -index 000000000000..cc6526fdd2bd ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_hsw.c -@@ -0,0 +1,119 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_hsw.h" -+ -+static const struct i915_oa_reg b_counter_config_render_basic[] = { -+ { _MMIO(0x2724), 0x00800000 }, -+ { _MMIO(0x2720), 0x00000000 }, -+ { _MMIO(0x2714), 0x00800000 }, -+ { _MMIO(0x2710), 0x00000000 }, -+}; -+ -+static const struct i915_oa_reg flex_eu_config_render_basic[] = { -+}; -+ -+static const struct i915_oa_reg mux_config_render_basic[] = { -+ { _MMIO(0x9840), 0x00000080 }, -+ { _MMIO(0x253a4), 0x01600000 }, -+ { _MMIO(0x25440), 0x00100000 }, -+ { _MMIO(0x25128), 0x00000000 }, -+ { _MMIO(0x2691c), 0x00000800 }, -+ { _MMIO(0x26aa0), 0x01500000 }, -+ { _MMIO(0x26b9c), 0x00006000 }, -+ { _MMIO(0x2791c), 0x00000800 }, -+ { _MMIO(0x27aa0), 0x01500000 }, -+ { _MMIO(0x27b9c), 0x00006000 }, -+ { _MMIO(0x2641c), 0x00000400 }, -+ { _MMIO(0x25380), 0x00000010 }, -+ { _MMIO(0x2538c), 0x00000000 }, -+ { _MMIO(0x25384), 0x0800aaaa }, -+ { _MMIO(0x25400), 0x00000004 }, -+ { _MMIO(0x2540c), 0x06029000 }, -+ { _MMIO(0x25410), 0x00000002 }, -+ { _MMIO(0x25404), 0x5c30ffff }, -+ { _MMIO(0x25100), 0x00000016 }, -+ { _MMIO(0x25110), 0x00000400 }, -+ { _MMIO(0x25104), 0x00000000 }, -+ { _MMIO(0x26804), 0x00001211 }, -+ { _MMIO(0x26884), 0x00000100 }, -+ { _MMIO(0x26900), 0x00000002 }, -+ { _MMIO(0x26908), 0x00700000 }, -+ { _MMIO(0x26904), 0x00000000 }, -+ { _MMIO(0x26984), 0x00001022 }, -+ { _MMIO(0x26a04), 0x00000011 }, -+ { _MMIO(0x26a80), 0x00000006 }, -+ { _MMIO(0x26a88), 0x00000c02 }, -+ { _MMIO(0x26a84), 0x00000000 }, -+ { _MMIO(0x26b04), 0x00001000 }, -+ { _MMIO(0x26b80), 0x00000002 }, -+ { _MMIO(0x26b8c), 0x00000007 }, -+ { _MMIO(0x26b84), 0x00000000 }, -+ { _MMIO(0x27804), 0x00004844 }, -+ { _MMIO(0x27884), 0x00000400 }, -+ { _MMIO(0x27900), 0x00000002 }, -+ { _MMIO(0x27908), 0x0e000000 }, -+ { _MMIO(0x27904), 0x00000000 }, -+ { _MMIO(0x27984), 0x00004088 }, -+ { _MMIO(0x27a04), 0x00000044 }, -+ { _MMIO(0x27a80), 0x00000006 }, -+ { _MMIO(0x27a88), 0x00018040 }, -+ { _MMIO(0x27a84), 0x00000000 }, -+ { _MMIO(0x27b04), 0x00004000 }, -+ { _MMIO(0x27b80), 0x00000002 }, -+ { _MMIO(0x27b8c), 0x000000e0 }, -+ { _MMIO(0x27b84), 0x00000000 }, -+ { _MMIO(0x26104), 0x00002222 }, -+ { _MMIO(0x26184), 0x0c006666 }, -+ { _MMIO(0x26284), 0x04000000 }, -+ { _MMIO(0x26304), 0x04000000 }, -+ { _MMIO(0x26400), 0x00000002 }, -+ { _MMIO(0x26410), 0x000000a0 }, -+ { _MMIO(0x26404), 0x00000000 }, -+ { _MMIO(0x25420), 0x04108020 }, -+ { _MMIO(0x25424), 0x1284a420 }, -+ { _MMIO(0x2541c), 0x00000000 }, -+ { _MMIO(0x25428), 0x00042049 }, -+}; -+ -+static ssize_t -+show_render_basic_id(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ return sprintf(buf, "1\n"); -+} -+ -+void -+i915_perf_load_test_config_hsw(struct drm_i915_private *dev_priv) -+{ -+ strlcpy(dev_priv->perf.oa.test_config.uuid, -+ "403d8832-1a27-4aa6-a64e-f5389ce7b212", -+ sizeof(dev_priv->perf.oa.test_config.uuid)); -+ dev_priv->perf.oa.test_config.id = 1; -+ -+ dev_priv->perf.oa.test_config.mux_regs = mux_config_render_basic; -+ dev_priv->perf.oa.test_config.mux_regs_len = ARRAY_SIZE(mux_config_render_basic); -+ -+ dev_priv->perf.oa.test_config.b_counter_regs = b_counter_config_render_basic; -+ dev_priv->perf.oa.test_config.b_counter_regs_len = ARRAY_SIZE(b_counter_config_render_basic); -+ -+ dev_priv->perf.oa.test_config.flex_regs = flex_eu_config_render_basic; -+ dev_priv->perf.oa.test_config.flex_regs_len = ARRAY_SIZE(flex_eu_config_render_basic); -+ -+ dev_priv->perf.oa.test_config.sysfs_metric.name = "403d8832-1a27-4aa6-a64e-f5389ce7b212"; -+ dev_priv->perf.oa.test_config.sysfs_metric.attrs = dev_priv->perf.oa.test_config.attrs; -+ -+ dev_priv->perf.oa.test_config.attrs[0] = &dev_priv->perf.oa.test_config.sysfs_metric_id.attr; -+ -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.name = "id"; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.mode = 0444; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.show = show_render_basic_id; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_hsw.h b/drivers/gpu/drm/i915_legacy/i915_oa_hsw.h -new file mode 100644 -index 000000000000..3d0c870cd0bd ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_hsw.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#ifndef __I915_OA_HSW_H__ -+#define __I915_OA_HSW_H__ -+ -+extern void i915_perf_load_test_config_hsw(struct drm_i915_private *dev_priv); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_icl.c b/drivers/gpu/drm/i915_legacy/i915_oa_icl.c -new file mode 100644 -index 000000000000..baa51427a543 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_icl.c -@@ -0,0 +1,99 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_icl.h" -+ -+static const struct i915_oa_reg b_counter_config_test_oa[] = { -+ { _MMIO(0x2740), 0x00000000 }, -+ { _MMIO(0x2710), 0x00000000 }, -+ { _MMIO(0x2714), 0xf0800000 }, -+ { _MMIO(0x2720), 0x00000000 }, -+ { _MMIO(0x2724), 0xf0800000 }, -+ { _MMIO(0x2770), 0x00000004 }, -+ { _MMIO(0x2774), 0x0000ffff }, -+ { _MMIO(0x2778), 0x00000003 }, -+ { _MMIO(0x277c), 0x0000ffff }, -+ { _MMIO(0x2780), 0x00000007 }, -+ { _MMIO(0x2784), 0x0000ffff }, -+ { _MMIO(0x2788), 0x00100002 }, -+ { _MMIO(0x278c), 0x0000fff7 }, -+ { _MMIO(0x2790), 0x00100002 }, -+ { _MMIO(0x2794), 0x0000ffcf }, -+ { _MMIO(0x2798), 0x00100082 }, -+ { _MMIO(0x279c), 0x0000ffef }, -+ { _MMIO(0x27a0), 0x001000c2 }, -+ { _MMIO(0x27a4), 0x0000ffe7 }, -+ { _MMIO(0x27a8), 0x00100001 }, -+ { _MMIO(0x27ac), 0x0000ffe7 }, -+}; -+ -+static const struct i915_oa_reg flex_eu_config_test_oa[] = { -+}; -+ -+static const struct i915_oa_reg mux_config_test_oa[] = { -+ { _MMIO(0xd04), 0x00000200 }, -+ { _MMIO(0x9840), 0x00000000 }, -+ { _MMIO(0x9884), 0x00000000 }, -+ { _MMIO(0x9888), 0x10060000 }, -+ { _MMIO(0x9888), 0x22060000 }, -+ { _MMIO(0x9888), 0x16060000 }, -+ { _MMIO(0x9888), 0x24060000 }, -+ { _MMIO(0x9888), 0x18060000 }, -+ { _MMIO(0x9888), 0x1a060000 }, -+ { _MMIO(0x9888), 0x12060000 }, -+ { _MMIO(0x9888), 0x14060000 }, -+ { _MMIO(0x9888), 0x10060000 }, -+ { _MMIO(0x9888), 0x22060000 }, -+ { _MMIO(0x9884), 0x00000003 }, -+ { _MMIO(0x9888), 0x16130000 }, -+ { _MMIO(0x9888), 0x24000001 }, -+ { _MMIO(0x9888), 0x0e130056 }, -+ { _MMIO(0x9888), 0x10130000 }, -+ { _MMIO(0x9888), 0x1a130000 }, -+ { _MMIO(0x9888), 0x541f0001 }, -+ { _MMIO(0x9888), 0x181f0000 }, -+ { _MMIO(0x9888), 0x4c1f0000 }, -+ { _MMIO(0x9888), 0x301f0000 }, -+}; -+ -+static ssize_t -+show_test_oa_id(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ return sprintf(buf, "1\n"); -+} -+ -+void -+i915_perf_load_test_config_icl(struct drm_i915_private *dev_priv) -+{ -+ strlcpy(dev_priv->perf.oa.test_config.uuid, -+ "a291665e-244b-4b76-9b9a-01de9d3c8068", -+ sizeof(dev_priv->perf.oa.test_config.uuid)); -+ dev_priv->perf.oa.test_config.id = 1; -+ -+ dev_priv->perf.oa.test_config.mux_regs = mux_config_test_oa; -+ dev_priv->perf.oa.test_config.mux_regs_len = ARRAY_SIZE(mux_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.b_counter_regs = b_counter_config_test_oa; -+ dev_priv->perf.oa.test_config.b_counter_regs_len = ARRAY_SIZE(b_counter_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.flex_regs = flex_eu_config_test_oa; -+ dev_priv->perf.oa.test_config.flex_regs_len = ARRAY_SIZE(flex_eu_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.sysfs_metric.name = "a291665e-244b-4b76-9b9a-01de9d3c8068"; -+ dev_priv->perf.oa.test_config.sysfs_metric.attrs = dev_priv->perf.oa.test_config.attrs; -+ -+ dev_priv->perf.oa.test_config.attrs[0] = &dev_priv->perf.oa.test_config.sysfs_metric_id.attr; -+ -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.name = "id"; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.mode = 0444; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.show = show_test_oa_id; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_icl.h b/drivers/gpu/drm/i915_legacy/i915_oa_icl.h -new file mode 100644 -index 000000000000..24eaa97d61ba ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_icl.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#ifndef __I915_OA_ICL_H__ -+#define __I915_OA_ICL_H__ -+ -+extern void i915_perf_load_test_config_icl(struct drm_i915_private *dev_priv); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_kblgt2.c b/drivers/gpu/drm/i915_legacy/i915_oa_kblgt2.c -new file mode 100644 -index 000000000000..168e49ab0d4d ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_kblgt2.c -@@ -0,0 +1,90 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_kblgt2.h" -+ -+static const struct i915_oa_reg b_counter_config_test_oa[] = { -+ { _MMIO(0x2740), 0x00000000 }, -+ { _MMIO(0x2744), 0x00800000 }, -+ { _MMIO(0x2714), 0xf0800000 }, -+ { _MMIO(0x2710), 0x00000000 }, -+ { _MMIO(0x2724), 0xf0800000 }, -+ { _MMIO(0x2720), 0x00000000 }, -+ { _MMIO(0x2770), 0x00000004 }, -+ { _MMIO(0x2774), 0x00000000 }, -+ { _MMIO(0x2778), 0x00000003 }, -+ { _MMIO(0x277c), 0x00000000 }, -+ { _MMIO(0x2780), 0x00000007 }, -+ { _MMIO(0x2784), 0x00000000 }, -+ { _MMIO(0x2788), 0x00100002 }, -+ { _MMIO(0x278c), 0x0000fff7 }, -+ { _MMIO(0x2790), 0x00100002 }, -+ { _MMIO(0x2794), 0x0000ffcf }, -+ { _MMIO(0x2798), 0x00100082 }, -+ { _MMIO(0x279c), 0x0000ffef }, -+ { _MMIO(0x27a0), 0x001000c2 }, -+ { _MMIO(0x27a4), 0x0000ffe7 }, -+ { _MMIO(0x27a8), 0x00100001 }, -+ { _MMIO(0x27ac), 0x0000ffe7 }, -+}; -+ -+static const struct i915_oa_reg flex_eu_config_test_oa[] = { -+}; -+ -+static const struct i915_oa_reg mux_config_test_oa[] = { -+ { _MMIO(0x9840), 0x00000080 }, -+ { _MMIO(0x9888), 0x11810000 }, -+ { _MMIO(0x9888), 0x07810013 }, -+ { _MMIO(0x9888), 0x1f810000 }, -+ { _MMIO(0x9888), 0x1d810000 }, -+ { _MMIO(0x9888), 0x1b930040 }, -+ { _MMIO(0x9888), 0x07e54000 }, -+ { _MMIO(0x9888), 0x1f908000 }, -+ { _MMIO(0x9888), 0x11900000 }, -+ { _MMIO(0x9888), 0x37900000 }, -+ { _MMIO(0x9888), 0x53900000 }, -+ { _MMIO(0x9888), 0x45900000 }, -+ { _MMIO(0x9888), 0x33900000 }, -+}; -+ -+static ssize_t -+show_test_oa_id(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ return sprintf(buf, "1\n"); -+} -+ -+void -+i915_perf_load_test_config_kblgt2(struct drm_i915_private *dev_priv) -+{ -+ strlcpy(dev_priv->perf.oa.test_config.uuid, -+ "baa3c7e4-52b6-4b85-801e-465a94b746dd", -+ sizeof(dev_priv->perf.oa.test_config.uuid)); -+ dev_priv->perf.oa.test_config.id = 1; -+ -+ dev_priv->perf.oa.test_config.mux_regs = mux_config_test_oa; -+ dev_priv->perf.oa.test_config.mux_regs_len = ARRAY_SIZE(mux_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.b_counter_regs = b_counter_config_test_oa; -+ dev_priv->perf.oa.test_config.b_counter_regs_len = ARRAY_SIZE(b_counter_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.flex_regs = flex_eu_config_test_oa; -+ dev_priv->perf.oa.test_config.flex_regs_len = ARRAY_SIZE(flex_eu_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.sysfs_metric.name = "baa3c7e4-52b6-4b85-801e-465a94b746dd"; -+ dev_priv->perf.oa.test_config.sysfs_metric.attrs = dev_priv->perf.oa.test_config.attrs; -+ -+ dev_priv->perf.oa.test_config.attrs[0] = &dev_priv->perf.oa.test_config.sysfs_metric_id.attr; -+ -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.name = "id"; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.mode = 0444; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.show = show_test_oa_id; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_kblgt2.h b/drivers/gpu/drm/i915_legacy/i915_oa_kblgt2.h -new file mode 100644 -index 000000000000..a55398a904de ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_kblgt2.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#ifndef __I915_OA_KBLGT2_H__ -+#define __I915_OA_KBLGT2_H__ -+ -+extern void i915_perf_load_test_config_kblgt2(struct drm_i915_private *dev_priv); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_kblgt3.c b/drivers/gpu/drm/i915_legacy/i915_oa_kblgt3.c -new file mode 100644 -index 000000000000..6ffa553c388e ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_kblgt3.c -@@ -0,0 +1,90 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_kblgt3.h" -+ -+static const struct i915_oa_reg b_counter_config_test_oa[] = { -+ { _MMIO(0x2740), 0x00000000 }, -+ { _MMIO(0x2744), 0x00800000 }, -+ { _MMIO(0x2714), 0xf0800000 }, -+ { _MMIO(0x2710), 0x00000000 }, -+ { _MMIO(0x2724), 0xf0800000 }, -+ { _MMIO(0x2720), 0x00000000 }, -+ { _MMIO(0x2770), 0x00000004 }, -+ { _MMIO(0x2774), 0x00000000 }, -+ { _MMIO(0x2778), 0x00000003 }, -+ { _MMIO(0x277c), 0x00000000 }, -+ { _MMIO(0x2780), 0x00000007 }, -+ { _MMIO(0x2784), 0x00000000 }, -+ { _MMIO(0x2788), 0x00100002 }, -+ { _MMIO(0x278c), 0x0000fff7 }, -+ { _MMIO(0x2790), 0x00100002 }, -+ { _MMIO(0x2794), 0x0000ffcf }, -+ { _MMIO(0x2798), 0x00100082 }, -+ { _MMIO(0x279c), 0x0000ffef }, -+ { _MMIO(0x27a0), 0x001000c2 }, -+ { _MMIO(0x27a4), 0x0000ffe7 }, -+ { _MMIO(0x27a8), 0x00100001 }, -+ { _MMIO(0x27ac), 0x0000ffe7 }, -+}; -+ -+static const struct i915_oa_reg flex_eu_config_test_oa[] = { -+}; -+ -+static const struct i915_oa_reg mux_config_test_oa[] = { -+ { _MMIO(0x9840), 0x00000080 }, -+ { _MMIO(0x9888), 0x11810000 }, -+ { _MMIO(0x9888), 0x07810013 }, -+ { _MMIO(0x9888), 0x1f810000 }, -+ { _MMIO(0x9888), 0x1d810000 }, -+ { _MMIO(0x9888), 0x1b930040 }, -+ { _MMIO(0x9888), 0x07e54000 }, -+ { _MMIO(0x9888), 0x1f908000 }, -+ { _MMIO(0x9888), 0x11900000 }, -+ { _MMIO(0x9888), 0x37900000 }, -+ { _MMIO(0x9888), 0x53900000 }, -+ { _MMIO(0x9888), 0x45900000 }, -+ { _MMIO(0x9888), 0x33900000 }, -+}; -+ -+static ssize_t -+show_test_oa_id(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ return sprintf(buf, "1\n"); -+} -+ -+void -+i915_perf_load_test_config_kblgt3(struct drm_i915_private *dev_priv) -+{ -+ strlcpy(dev_priv->perf.oa.test_config.uuid, -+ "f1792f32-6db2-4b50-b4b2-557128f1688d", -+ sizeof(dev_priv->perf.oa.test_config.uuid)); -+ dev_priv->perf.oa.test_config.id = 1; -+ -+ dev_priv->perf.oa.test_config.mux_regs = mux_config_test_oa; -+ dev_priv->perf.oa.test_config.mux_regs_len = ARRAY_SIZE(mux_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.b_counter_regs = b_counter_config_test_oa; -+ dev_priv->perf.oa.test_config.b_counter_regs_len = ARRAY_SIZE(b_counter_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.flex_regs = flex_eu_config_test_oa; -+ dev_priv->perf.oa.test_config.flex_regs_len = ARRAY_SIZE(flex_eu_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.sysfs_metric.name = "f1792f32-6db2-4b50-b4b2-557128f1688d"; -+ dev_priv->perf.oa.test_config.sysfs_metric.attrs = dev_priv->perf.oa.test_config.attrs; -+ -+ dev_priv->perf.oa.test_config.attrs[0] = &dev_priv->perf.oa.test_config.sysfs_metric_id.attr; -+ -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.name = "id"; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.mode = 0444; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.show = show_test_oa_id; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_kblgt3.h b/drivers/gpu/drm/i915_legacy/i915_oa_kblgt3.h -new file mode 100644 -index 000000000000..3ddd3483b7cc ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_kblgt3.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#ifndef __I915_OA_KBLGT3_H__ -+#define __I915_OA_KBLGT3_H__ -+ -+extern void i915_perf_load_test_config_kblgt3(struct drm_i915_private *dev_priv); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_sklgt2.c b/drivers/gpu/drm/i915_legacy/i915_oa_sklgt2.c -new file mode 100644 -index 000000000000..7ce6ee851d43 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_sklgt2.c -@@ -0,0 +1,89 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_sklgt2.h" -+ -+static const struct i915_oa_reg b_counter_config_test_oa[] = { -+ { _MMIO(0x2740), 0x00000000 }, -+ { _MMIO(0x2714), 0xf0800000 }, -+ { _MMIO(0x2710), 0x00000000 }, -+ { _MMIO(0x2724), 0xf0800000 }, -+ { _MMIO(0x2720), 0x00000000 }, -+ { _MMIO(0x2770), 0x00000004 }, -+ { _MMIO(0x2774), 0x00000000 }, -+ { _MMIO(0x2778), 0x00000003 }, -+ { _MMIO(0x277c), 0x00000000 }, -+ { _MMIO(0x2780), 0x00000007 }, -+ { _MMIO(0x2784), 0x00000000 }, -+ { _MMIO(0x2788), 0x00100002 }, -+ { _MMIO(0x278c), 0x0000fff7 }, -+ { _MMIO(0x2790), 0x00100002 }, -+ { _MMIO(0x2794), 0x0000ffcf }, -+ { _MMIO(0x2798), 0x00100082 }, -+ { _MMIO(0x279c), 0x0000ffef }, -+ { _MMIO(0x27a0), 0x001000c2 }, -+ { _MMIO(0x27a4), 0x0000ffe7 }, -+ { _MMIO(0x27a8), 0x00100001 }, -+ { _MMIO(0x27ac), 0x0000ffe7 }, -+}; -+ -+static const struct i915_oa_reg flex_eu_config_test_oa[] = { -+}; -+ -+static const struct i915_oa_reg mux_config_test_oa[] = { -+ { _MMIO(0x9840), 0x00000080 }, -+ { _MMIO(0x9888), 0x11810000 }, -+ { _MMIO(0x9888), 0x07810016 }, -+ { _MMIO(0x9888), 0x1f810000 }, -+ { _MMIO(0x9888), 0x1d810000 }, -+ { _MMIO(0x9888), 0x1b930040 }, -+ { _MMIO(0x9888), 0x07e54000 }, -+ { _MMIO(0x9888), 0x1f908000 }, -+ { _MMIO(0x9888), 0x11900000 }, -+ { _MMIO(0x9888), 0x37900000 }, -+ { _MMIO(0x9888), 0x53900000 }, -+ { _MMIO(0x9888), 0x45900000 }, -+ { _MMIO(0x9888), 0x33900000 }, -+}; -+ -+static ssize_t -+show_test_oa_id(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ return sprintf(buf, "1\n"); -+} -+ -+void -+i915_perf_load_test_config_sklgt2(struct drm_i915_private *dev_priv) -+{ -+ strlcpy(dev_priv->perf.oa.test_config.uuid, -+ "1651949f-0ac0-4cb1-a06f-dafd74a407d1", -+ sizeof(dev_priv->perf.oa.test_config.uuid)); -+ dev_priv->perf.oa.test_config.id = 1; -+ -+ dev_priv->perf.oa.test_config.mux_regs = mux_config_test_oa; -+ dev_priv->perf.oa.test_config.mux_regs_len = ARRAY_SIZE(mux_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.b_counter_regs = b_counter_config_test_oa; -+ dev_priv->perf.oa.test_config.b_counter_regs_len = ARRAY_SIZE(b_counter_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.flex_regs = flex_eu_config_test_oa; -+ dev_priv->perf.oa.test_config.flex_regs_len = ARRAY_SIZE(flex_eu_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.sysfs_metric.name = "1651949f-0ac0-4cb1-a06f-dafd74a407d1"; -+ dev_priv->perf.oa.test_config.sysfs_metric.attrs = dev_priv->perf.oa.test_config.attrs; -+ -+ dev_priv->perf.oa.test_config.attrs[0] = &dev_priv->perf.oa.test_config.sysfs_metric_id.attr; -+ -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.name = "id"; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.mode = 0444; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.show = show_test_oa_id; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_sklgt2.h b/drivers/gpu/drm/i915_legacy/i915_oa_sklgt2.h -new file mode 100644 -index 000000000000..be6256037239 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_sklgt2.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#ifndef __I915_OA_SKLGT2_H__ -+#define __I915_OA_SKLGT2_H__ -+ -+extern void i915_perf_load_test_config_sklgt2(struct drm_i915_private *dev_priv); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_sklgt3.c b/drivers/gpu/drm/i915_legacy/i915_oa_sklgt3.c -new file mode 100644 -index 000000000000..086ca2631e1c ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_sklgt3.c -@@ -0,0 +1,90 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_sklgt3.h" -+ -+static const struct i915_oa_reg b_counter_config_test_oa[] = { -+ { _MMIO(0x2740), 0x00000000 }, -+ { _MMIO(0x2744), 0x00800000 }, -+ { _MMIO(0x2714), 0xf0800000 }, -+ { _MMIO(0x2710), 0x00000000 }, -+ { _MMIO(0x2724), 0xf0800000 }, -+ { _MMIO(0x2720), 0x00000000 }, -+ { _MMIO(0x2770), 0x00000004 }, -+ { _MMIO(0x2774), 0x00000000 }, -+ { _MMIO(0x2778), 0x00000003 }, -+ { _MMIO(0x277c), 0x00000000 }, -+ { _MMIO(0x2780), 0x00000007 }, -+ { _MMIO(0x2784), 0x00000000 }, -+ { _MMIO(0x2788), 0x00100002 }, -+ { _MMIO(0x278c), 0x0000fff7 }, -+ { _MMIO(0x2790), 0x00100002 }, -+ { _MMIO(0x2794), 0x0000ffcf }, -+ { _MMIO(0x2798), 0x00100082 }, -+ { _MMIO(0x279c), 0x0000ffef }, -+ { _MMIO(0x27a0), 0x001000c2 }, -+ { _MMIO(0x27a4), 0x0000ffe7 }, -+ { _MMIO(0x27a8), 0x00100001 }, -+ { _MMIO(0x27ac), 0x0000ffe7 }, -+}; -+ -+static const struct i915_oa_reg flex_eu_config_test_oa[] = { -+}; -+ -+static const struct i915_oa_reg mux_config_test_oa[] = { -+ { _MMIO(0x9840), 0x00000080 }, -+ { _MMIO(0x9888), 0x11810000 }, -+ { _MMIO(0x9888), 0x07810013 }, -+ { _MMIO(0x9888), 0x1f810000 }, -+ { _MMIO(0x9888), 0x1d810000 }, -+ { _MMIO(0x9888), 0x1b930040 }, -+ { _MMIO(0x9888), 0x07e54000 }, -+ { _MMIO(0x9888), 0x1f908000 }, -+ { _MMIO(0x9888), 0x11900000 }, -+ { _MMIO(0x9888), 0x37900000 }, -+ { _MMIO(0x9888), 0x53900000 }, -+ { _MMIO(0x9888), 0x45900000 }, -+ { _MMIO(0x9888), 0x33900000 }, -+}; -+ -+static ssize_t -+show_test_oa_id(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ return sprintf(buf, "1\n"); -+} -+ -+void -+i915_perf_load_test_config_sklgt3(struct drm_i915_private *dev_priv) -+{ -+ strlcpy(dev_priv->perf.oa.test_config.uuid, -+ "2b985803-d3c9-4629-8a4f-634bfecba0e8", -+ sizeof(dev_priv->perf.oa.test_config.uuid)); -+ dev_priv->perf.oa.test_config.id = 1; -+ -+ dev_priv->perf.oa.test_config.mux_regs = mux_config_test_oa; -+ dev_priv->perf.oa.test_config.mux_regs_len = ARRAY_SIZE(mux_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.b_counter_regs = b_counter_config_test_oa; -+ dev_priv->perf.oa.test_config.b_counter_regs_len = ARRAY_SIZE(b_counter_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.flex_regs = flex_eu_config_test_oa; -+ dev_priv->perf.oa.test_config.flex_regs_len = ARRAY_SIZE(flex_eu_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.sysfs_metric.name = "2b985803-d3c9-4629-8a4f-634bfecba0e8"; -+ dev_priv->perf.oa.test_config.sysfs_metric.attrs = dev_priv->perf.oa.test_config.attrs; -+ -+ dev_priv->perf.oa.test_config.attrs[0] = &dev_priv->perf.oa.test_config.sysfs_metric_id.attr; -+ -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.name = "id"; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.mode = 0444; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.show = show_test_oa_id; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_sklgt3.h b/drivers/gpu/drm/i915_legacy/i915_oa_sklgt3.h -new file mode 100644 -index 000000000000..650beb068e56 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_sklgt3.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#ifndef __I915_OA_SKLGT3_H__ -+#define __I915_OA_SKLGT3_H__ -+ -+extern void i915_perf_load_test_config_sklgt3(struct drm_i915_private *dev_priv); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_sklgt4.c b/drivers/gpu/drm/i915_legacy/i915_oa_sklgt4.c -new file mode 100644 -index 000000000000..b291a6eb8a87 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_sklgt4.c -@@ -0,0 +1,90 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_sklgt4.h" -+ -+static const struct i915_oa_reg b_counter_config_test_oa[] = { -+ { _MMIO(0x2740), 0x00000000 }, -+ { _MMIO(0x2744), 0x00800000 }, -+ { _MMIO(0x2714), 0xf0800000 }, -+ { _MMIO(0x2710), 0x00000000 }, -+ { _MMIO(0x2724), 0xf0800000 }, -+ { _MMIO(0x2720), 0x00000000 }, -+ { _MMIO(0x2770), 0x00000004 }, -+ { _MMIO(0x2774), 0x00000000 }, -+ { _MMIO(0x2778), 0x00000003 }, -+ { _MMIO(0x277c), 0x00000000 }, -+ { _MMIO(0x2780), 0x00000007 }, -+ { _MMIO(0x2784), 0x00000000 }, -+ { _MMIO(0x2788), 0x00100002 }, -+ { _MMIO(0x278c), 0x0000fff7 }, -+ { _MMIO(0x2790), 0x00100002 }, -+ { _MMIO(0x2794), 0x0000ffcf }, -+ { _MMIO(0x2798), 0x00100082 }, -+ { _MMIO(0x279c), 0x0000ffef }, -+ { _MMIO(0x27a0), 0x001000c2 }, -+ { _MMIO(0x27a4), 0x0000ffe7 }, -+ { _MMIO(0x27a8), 0x00100001 }, -+ { _MMIO(0x27ac), 0x0000ffe7 }, -+}; -+ -+static const struct i915_oa_reg flex_eu_config_test_oa[] = { -+}; -+ -+static const struct i915_oa_reg mux_config_test_oa[] = { -+ { _MMIO(0x9840), 0x00000080 }, -+ { _MMIO(0x9888), 0x11810000 }, -+ { _MMIO(0x9888), 0x07810013 }, -+ { _MMIO(0x9888), 0x1f810000 }, -+ { _MMIO(0x9888), 0x1d810000 }, -+ { _MMIO(0x9888), 0x1b930040 }, -+ { _MMIO(0x9888), 0x07e54000 }, -+ { _MMIO(0x9888), 0x1f908000 }, -+ { _MMIO(0x9888), 0x11900000 }, -+ { _MMIO(0x9888), 0x37900000 }, -+ { _MMIO(0x9888), 0x53900000 }, -+ { _MMIO(0x9888), 0x45900000 }, -+ { _MMIO(0x9888), 0x33900000 }, -+}; -+ -+static ssize_t -+show_test_oa_id(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ return sprintf(buf, "1\n"); -+} -+ -+void -+i915_perf_load_test_config_sklgt4(struct drm_i915_private *dev_priv) -+{ -+ strlcpy(dev_priv->perf.oa.test_config.uuid, -+ "882fa433-1f4a-4a67-a962-c741888fe5f5", -+ sizeof(dev_priv->perf.oa.test_config.uuid)); -+ dev_priv->perf.oa.test_config.id = 1; -+ -+ dev_priv->perf.oa.test_config.mux_regs = mux_config_test_oa; -+ dev_priv->perf.oa.test_config.mux_regs_len = ARRAY_SIZE(mux_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.b_counter_regs = b_counter_config_test_oa; -+ dev_priv->perf.oa.test_config.b_counter_regs_len = ARRAY_SIZE(b_counter_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.flex_regs = flex_eu_config_test_oa; -+ dev_priv->perf.oa.test_config.flex_regs_len = ARRAY_SIZE(flex_eu_config_test_oa); -+ -+ dev_priv->perf.oa.test_config.sysfs_metric.name = "882fa433-1f4a-4a67-a962-c741888fe5f5"; -+ dev_priv->perf.oa.test_config.sysfs_metric.attrs = dev_priv->perf.oa.test_config.attrs; -+ -+ dev_priv->perf.oa.test_config.attrs[0] = &dev_priv->perf.oa.test_config.sysfs_metric_id.attr; -+ -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.name = "id"; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.attr.mode = 0444; -+ dev_priv->perf.oa.test_config.sysfs_metric_id.show = show_test_oa_id; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_oa_sklgt4.h b/drivers/gpu/drm/i915_legacy/i915_oa_sklgt4.h -new file mode 100644 -index 000000000000..8dcf849d131e ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_oa_sklgt4.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ * -+ * Autogenerated file by GPU Top : https://github.com/rib/gputop -+ * DO NOT EDIT manually! -+ */ -+ -+#ifndef __I915_OA_SKLGT4_H__ -+#define __I915_OA_SKLGT4_H__ -+ -+extern void i915_perf_load_test_config_sklgt4(struct drm_i915_private *dev_priv); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_params.c b/drivers/gpu/drm/i915_legacy/i915_params.c -new file mode 100644 -index 000000000000..b5be0abbba35 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_params.c -@@ -0,0 +1,237 @@ -+/* -+ * Copyright © 2014 Intel Corporation -+ * -+ * 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, sub license, 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 (including the -+ * next paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#include -+ -+#include "i915_params.h" -+#include "i915_drv.h" -+ -+#define i915_param_named(name, T, perm, desc) \ -+ module_param_named(name, i915_modparams.name, T, perm); \ -+ MODULE_PARM_DESC(name, desc) -+#define i915_param_named_unsafe(name, T, perm, desc) \ -+ module_param_named_unsafe(name, i915_modparams.name, T, perm); \ -+ MODULE_PARM_DESC(name, desc) -+ -+struct i915_params i915_modparams __read_mostly = { -+#define MEMBER(T, member, value) .member = (value), -+ I915_PARAMS_FOR_EACH(MEMBER) -+#undef MEMBER -+}; -+ -+i915_param_named(modeset, int, 0400, -+ "Use kernel modesetting [KMS] (0=disable, " -+ "1=on, -1=force vga console preference [default])"); -+ -+i915_param_named_unsafe(enable_dc, int, 0400, -+ "Enable power-saving display C-states. " -+ "(-1=auto [default]; 0=disable; 1=up to DC5; 2=up to DC6)"); -+ -+i915_param_named_unsafe(enable_fbc, int, 0600, -+ "Enable frame buffer compression for power savings " -+ "(default: -1 (use per-chip default))"); -+ -+i915_param_named_unsafe(lvds_channel_mode, int, 0400, -+ "Specify LVDS channel mode " -+ "(0=probe BIOS [default], 1=single-channel, 2=dual-channel)"); -+ -+i915_param_named_unsafe(panel_use_ssc, int, 0600, -+ "Use Spread Spectrum Clock with panels [LVDS/eDP] " -+ "(default: auto from VBT)"); -+ -+i915_param_named_unsafe(vbt_sdvo_panel_type, int, 0400, -+ "Override/Ignore selection of SDVO panel mode in the VBT " -+ "(-2=ignore, -1=auto [default], index in VBT BIOS table)"); -+ -+i915_param_named_unsafe(reset, int, 0600, -+ "Attempt GPU resets (0=disabled, 1=full gpu reset, 2=engine reset [default])"); -+ -+i915_param_named_unsafe(vbt_firmware, charp, 0400, -+ "Load VBT from specified file under /lib/firmware"); -+ -+#if IS_ENABLED(CONFIG_DRM_I915_CAPTURE_ERROR) -+i915_param_named(error_capture, bool, 0600, -+ "Record the GPU state following a hang. " -+ "This information in /sys/class/drm/card/error is vital for " -+ "triaging and debugging hangs."); -+#endif -+ -+i915_param_named_unsafe(enable_hangcheck, bool, 0600, -+ "Periodically check GPU activity for detecting hangs. " -+ "WARNING: Disabling this can cause system wide hangs. " -+ "(default: true)"); -+ -+i915_param_named_unsafe(enable_psr, int, 0600, -+ "Enable PSR " -+ "(0=disabled, 1=enabled) " -+ "Default: -1 (use per-chip default)"); -+ -+i915_param_named_unsafe(alpha_support, bool, 0400, -+ "Enable alpha quality driver support for latest hardware. " -+ "See also CONFIG_DRM_I915_ALPHA_SUPPORT."); -+ -+i915_param_named_unsafe(disable_power_well, int, 0400, -+ "Disable display power wells when possible " -+ "(-1=auto [default], 0=power wells always on, 1=power wells disabled when possible)"); -+ -+i915_param_named_unsafe(enable_ips, int, 0600, "Enable IPS (default: true)"); -+ -+i915_param_named(fastboot, int, 0600, -+ "Try to skip unnecessary mode sets at boot time " -+ "(0=disabled, 1=enabled) " -+ "Default: -1 (use per-chip default)"); -+ -+i915_param_named_unsafe(prefault_disable, bool, 0600, -+ "Disable page prefaulting for pread/pwrite/reloc (default:false). " -+ "For developers only."); -+ -+i915_param_named_unsafe(load_detect_test, bool, 0600, -+ "Force-enable the VGA load detect code for testing (default:false). " -+ "For developers only."); -+ -+i915_param_named_unsafe(force_reset_modeset_test, bool, 0600, -+ "Force a modeset during gpu reset for testing (default:false). " -+ "For developers only."); -+ -+i915_param_named_unsafe(invert_brightness, int, 0600, -+ "Invert backlight brightness " -+ "(-1 force normal, 0 machine defaults, 1 force inversion), please " -+ "report PCI device ID, subsystem vendor and subsystem device ID " -+ "to dri-devel@lists.freedesktop.org, if your machine needs it. " -+ "It will then be included in an upcoming module version."); -+ -+i915_param_named(disable_display, bool, 0400, -+ "Disable display (default: false)"); -+ -+i915_param_named(mmio_debug, int, 0600, -+ "Enable the MMIO debug code for the first N failures (default: off). " -+ "This may negatively affect performance."); -+ -+i915_param_named(verbose_state_checks, bool, 0600, -+ "Enable verbose logs (ie. WARN_ON()) in case of unexpected hw state conditions."); -+ -+i915_param_named_unsafe(nuclear_pageflip, bool, 0400, -+ "Force enable atomic functionality on platforms that don't have full support yet."); -+ -+/* WA to get away with the default setting in VBT for early platforms.Will be removed */ -+i915_param_named_unsafe(edp_vswing, int, 0400, -+ "Ignore/Override vswing pre-emph table selection from VBT " -+ "(0=use value from vbt [default], 1=low power swing(200mV)," -+ "2=default swing(400mV))"); -+ -+i915_param_named_unsafe(enable_guc, int, 0400, -+ "Enable GuC load for GuC submission and/or HuC load. " -+ "Required functionality can be selected using bitmask values. " -+ "(-1=auto, 0=disable [default], 1=GuC submission, 2=HuC load)"); -+ -+i915_param_named(guc_log_level, int, 0400, -+ "GuC firmware logging level. Requires GuC to be loaded. " -+ "(-1=auto [default], 0=disable, 1..4=enable with verbosity min..max)"); -+ -+i915_param_named_unsafe(guc_firmware_path, charp, 0400, -+ "GuC firmware path to use instead of the default one"); -+ -+i915_param_named_unsafe(huc_firmware_path, charp, 0400, -+ "HuC firmware path to use instead of the default one"); -+ -+i915_param_named_unsafe(dmc_firmware_path, charp, 0400, -+ "DMC firmware path to use instead of the default one"); -+ -+i915_param_named_unsafe(enable_dp_mst, bool, 0600, -+ "Enable multi-stream transport (MST) for new DisplayPort sinks. (default: true)"); -+ -+#if IS_ENABLED(CONFIG_DRM_I915_DEBUG) -+i915_param_named_unsafe(inject_load_failure, uint, 0400, -+ "Force an error after a number of failure check points (0:disabled (default), N:force failure at the Nth failure check point)"); -+#endif -+ -+i915_param_named(enable_dpcd_backlight, bool, 0600, -+ "Enable support for DPCD backlight control (default:false)"); -+ -+#if IS_ENABLED(CONFIG_DRM_I915_GVT) -+i915_param_named(enable_gvt, bool, 0400, -+ "Enable support for Intel GVT-g graphics virtualization host support(default:false)"); -+#endif -+ -+static __always_inline void _print_param(struct drm_printer *p, -+ const char *name, -+ const char *type, -+ const void *x) -+{ -+ if (!__builtin_strcmp(type, "bool")) -+ drm_printf(p, "i915.%s=%s\n", name, yesno(*(const bool *)x)); -+ else if (!__builtin_strcmp(type, "int")) -+ drm_printf(p, "i915.%s=%d\n", name, *(const int *)x); -+ else if (!__builtin_strcmp(type, "unsigned int")) -+ drm_printf(p, "i915.%s=%u\n", name, *(const unsigned int *)x); -+ else if (!__builtin_strcmp(type, "char *")) -+ drm_printf(p, "i915.%s=%s\n", name, *(const char **)x); -+ else -+ WARN_ONCE(1, "no printer defined for param type %s (i915.%s)\n", -+ type, name); -+} -+ -+/** -+ * i915_params_dump - dump i915 modparams -+ * @params: i915 modparams -+ * @p: the &drm_printer -+ * -+ * Pretty printer for i915 modparams. -+ */ -+void i915_params_dump(const struct i915_params *params, struct drm_printer *p) -+{ -+#define PRINT(T, x, ...) _print_param(p, #x, #T, ¶ms->x); -+ I915_PARAMS_FOR_EACH(PRINT); -+#undef PRINT -+} -+ -+static __always_inline void dup_param(const char *type, void *x) -+{ -+ if (!__builtin_strcmp(type, "char *")) -+ *(void **)x = kstrdup(*(void **)x, GFP_ATOMIC); -+} -+ -+void i915_params_copy(struct i915_params *dest, const struct i915_params *src) -+{ -+ *dest = *src; -+#define DUP(T, x, ...) dup_param(#T, &dest->x); -+ I915_PARAMS_FOR_EACH(DUP); -+#undef DUP -+} -+ -+static __always_inline void free_param(const char *type, void *x) -+{ -+ if (!__builtin_strcmp(type, "char *")) { -+ kfree(*(void **)x); -+ *(void **)x = NULL; -+ } -+} -+ -+/* free the allocated members, *not* the passed in params itself */ -+void i915_params_free(struct i915_params *params) -+{ -+#define FREE(T, x, ...) free_param(#T, ¶ms->x); -+ I915_PARAMS_FOR_EACH(FREE); -+#undef FREE -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_params.h b/drivers/gpu/drm/i915_legacy/i915_params.h -new file mode 100644 -index 000000000000..3f14e9881a0d ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_params.h -@@ -0,0 +1,94 @@ -+/* -+ * Copyright © 2015 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef _I915_PARAMS_H_ -+#define _I915_PARAMS_H_ -+ -+#include -+#include /* for __read_mostly */ -+ -+struct drm_printer; -+ -+#define ENABLE_GUC_SUBMISSION BIT(0) -+#define ENABLE_GUC_LOAD_HUC BIT(1) -+ -+/* -+ * Invoke param, a function-like macro, for each i915 param, with arguments: -+ * -+ * param(type, name, value) -+ * -+ * type: parameter type, one of {bool, int, unsigned int, char *} -+ * name: name of the parameter -+ * value: initial/default value of the parameter -+ */ -+#define I915_PARAMS_FOR_EACH(param) \ -+ param(char *, vbt_firmware, NULL) \ -+ param(int, modeset, -1) \ -+ param(int, lvds_channel_mode, 0) \ -+ param(int, panel_use_ssc, -1) \ -+ param(int, vbt_sdvo_panel_type, -1) \ -+ param(int, enable_dc, -1) \ -+ param(int, enable_fbc, -1) \ -+ param(int, enable_psr, -1) \ -+ param(int, disable_power_well, -1) \ -+ param(int, enable_ips, 1) \ -+ param(int, invert_brightness, 0) \ -+ param(int, enable_guc, 0) \ -+ param(int, guc_log_level, -1) \ -+ param(char *, guc_firmware_path, NULL) \ -+ param(char *, huc_firmware_path, NULL) \ -+ param(char *, dmc_firmware_path, NULL) \ -+ param(int, mmio_debug, 0) \ -+ param(int, edp_vswing, 0) \ -+ param(int, reset, 2) \ -+ param(unsigned int, inject_load_failure, 0) \ -+ param(int, fastboot, -1) \ -+ /* leave bools at the end to not create holes */ \ -+ param(bool, alpha_support, IS_ENABLED(CONFIG_DRM_I915_ALPHA_SUPPORT)) \ -+ param(bool, enable_hangcheck, true) \ -+ param(bool, prefault_disable, false) \ -+ param(bool, load_detect_test, false) \ -+ param(bool, force_reset_modeset_test, false) \ -+ param(bool, error_capture, true) \ -+ param(bool, disable_display, false) \ -+ param(bool, verbose_state_checks, true) \ -+ param(bool, nuclear_pageflip, false) \ -+ param(bool, enable_dp_mst, true) \ -+ param(bool, enable_dpcd_backlight, false) \ -+ param(bool, enable_gvt, false) -+ -+#define MEMBER(T, member, ...) T member; -+struct i915_params { -+ I915_PARAMS_FOR_EACH(MEMBER); -+}; -+#undef MEMBER -+ -+extern struct i915_params i915_modparams __read_mostly; -+ -+void i915_params_dump(const struct i915_params *params, struct drm_printer *p); -+void i915_params_copy(struct i915_params *dest, const struct i915_params *src); -+void i915_params_free(struct i915_params *params); -+ -+#endif -+ -diff --git a/drivers/gpu/drm/i915_legacy/i915_pci.c b/drivers/gpu/drm/i915_legacy/i915_pci.c -new file mode 100644 -index 000000000000..f893c2cbce15 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_pci.c -@@ -0,0 +1,957 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include -+#include -+#include -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_globals.h" -+#include "i915_selftest.h" -+#include "intel_fbdev.h" -+ -+#define PLATFORM(x) .platform = (x) -+#define GEN(x) .gen = (x), .gen_mask = BIT((x) - 1) -+ -+#define I845_PIPE_OFFSETS \ -+ .pipe_offsets = { \ -+ [TRANSCODER_A] = PIPE_A_OFFSET, \ -+ }, \ -+ .trans_offsets = { \ -+ [TRANSCODER_A] = TRANSCODER_A_OFFSET, \ -+ } -+ -+#define I9XX_PIPE_OFFSETS \ -+ .pipe_offsets = { \ -+ [TRANSCODER_A] = PIPE_A_OFFSET, \ -+ [TRANSCODER_B] = PIPE_B_OFFSET, \ -+ }, \ -+ .trans_offsets = { \ -+ [TRANSCODER_A] = TRANSCODER_A_OFFSET, \ -+ [TRANSCODER_B] = TRANSCODER_B_OFFSET, \ -+ } -+ -+#define IVB_PIPE_OFFSETS \ -+ .pipe_offsets = { \ -+ [TRANSCODER_A] = PIPE_A_OFFSET, \ -+ [TRANSCODER_B] = PIPE_B_OFFSET, \ -+ [TRANSCODER_C] = PIPE_C_OFFSET, \ -+ }, \ -+ .trans_offsets = { \ -+ [TRANSCODER_A] = TRANSCODER_A_OFFSET, \ -+ [TRANSCODER_B] = TRANSCODER_B_OFFSET, \ -+ [TRANSCODER_C] = TRANSCODER_C_OFFSET, \ -+ } -+ -+#define HSW_PIPE_OFFSETS \ -+ .pipe_offsets = { \ -+ [TRANSCODER_A] = PIPE_A_OFFSET, \ -+ [TRANSCODER_B] = PIPE_B_OFFSET, \ -+ [TRANSCODER_C] = PIPE_C_OFFSET, \ -+ [TRANSCODER_EDP] = PIPE_EDP_OFFSET, \ -+ }, \ -+ .trans_offsets = { \ -+ [TRANSCODER_A] = TRANSCODER_A_OFFSET, \ -+ [TRANSCODER_B] = TRANSCODER_B_OFFSET, \ -+ [TRANSCODER_C] = TRANSCODER_C_OFFSET, \ -+ [TRANSCODER_EDP] = TRANSCODER_EDP_OFFSET, \ -+ } -+ -+#define CHV_PIPE_OFFSETS \ -+ .pipe_offsets = { \ -+ [TRANSCODER_A] = PIPE_A_OFFSET, \ -+ [TRANSCODER_B] = PIPE_B_OFFSET, \ -+ [TRANSCODER_C] = CHV_PIPE_C_OFFSET, \ -+ }, \ -+ .trans_offsets = { \ -+ [TRANSCODER_A] = TRANSCODER_A_OFFSET, \ -+ [TRANSCODER_B] = TRANSCODER_B_OFFSET, \ -+ [TRANSCODER_C] = CHV_TRANSCODER_C_OFFSET, \ -+ } -+ -+#define I845_CURSOR_OFFSETS \ -+ .cursor_offsets = { \ -+ [PIPE_A] = CURSOR_A_OFFSET, \ -+ } -+ -+#define I9XX_CURSOR_OFFSETS \ -+ .cursor_offsets = { \ -+ [PIPE_A] = CURSOR_A_OFFSET, \ -+ [PIPE_B] = CURSOR_B_OFFSET, \ -+ } -+ -+#define CHV_CURSOR_OFFSETS \ -+ .cursor_offsets = { \ -+ [PIPE_A] = CURSOR_A_OFFSET, \ -+ [PIPE_B] = CURSOR_B_OFFSET, \ -+ [PIPE_C] = CHV_CURSOR_C_OFFSET, \ -+ } -+ -+#define IVB_CURSOR_OFFSETS \ -+ .cursor_offsets = { \ -+ [PIPE_A] = CURSOR_A_OFFSET, \ -+ [PIPE_B] = IVB_CURSOR_B_OFFSET, \ -+ [PIPE_C] = IVB_CURSOR_C_OFFSET, \ -+ } -+ -+#define I9XX_COLORS \ -+ .color = { .gamma_lut_size = 256 } -+#define I965_COLORS \ -+ .color = { .gamma_lut_size = 129, \ -+ .gamma_lut_tests = DRM_COLOR_LUT_NON_DECREASING, \ -+ } -+#define ILK_COLORS \ -+ .color = { .gamma_lut_size = 1024 } -+#define IVB_COLORS \ -+ .color = { .degamma_lut_size = 1024, .gamma_lut_size = 1024 } -+#define CHV_COLORS \ -+ .color = { .degamma_lut_size = 65, .gamma_lut_size = 257, \ -+ .degamma_lut_tests = DRM_COLOR_LUT_NON_DECREASING, \ -+ .gamma_lut_tests = DRM_COLOR_LUT_NON_DECREASING, \ -+ } -+#define GLK_COLORS \ -+ .color = { .degamma_lut_size = 33, .gamma_lut_size = 1024, \ -+ .degamma_lut_tests = DRM_COLOR_LUT_NON_DECREASING | \ -+ DRM_COLOR_LUT_EQUAL_CHANNELS, \ -+ } -+ -+/* Keep in gen based order, and chronological order within a gen */ -+ -+#define GEN_DEFAULT_PAGE_SIZES \ -+ .page_sizes = I915_GTT_PAGE_SIZE_4K -+ -+#define I830_FEATURES \ -+ GEN(2), \ -+ .is_mobile = 1, \ -+ .num_pipes = 2, \ -+ .display.has_overlay = 1, \ -+ .display.cursor_needs_physical = 1, \ -+ .display.overlay_needs_physical = 1, \ -+ .display.has_gmch = 1, \ -+ .gpu_reset_clobbers_display = true, \ -+ .hws_needs_physical = 1, \ -+ .unfenced_needs_alignment = 1, \ -+ .engine_mask = BIT(RCS0), \ -+ .has_snoop = true, \ -+ .has_coherent_ggtt = false, \ -+ I9XX_PIPE_OFFSETS, \ -+ I9XX_CURSOR_OFFSETS, \ -+ I9XX_COLORS, \ -+ GEN_DEFAULT_PAGE_SIZES -+ -+#define I845_FEATURES \ -+ GEN(2), \ -+ .num_pipes = 1, \ -+ .display.has_overlay = 1, \ -+ .display.overlay_needs_physical = 1, \ -+ .display.has_gmch = 1, \ -+ .gpu_reset_clobbers_display = true, \ -+ .hws_needs_physical = 1, \ -+ .unfenced_needs_alignment = 1, \ -+ .engine_mask = BIT(RCS0), \ -+ .has_snoop = true, \ -+ .has_coherent_ggtt = false, \ -+ I845_PIPE_OFFSETS, \ -+ I845_CURSOR_OFFSETS, \ -+ I9XX_COLORS, \ -+ GEN_DEFAULT_PAGE_SIZES -+ -+static const struct intel_device_info intel_i830_info = { -+ I830_FEATURES, -+ PLATFORM(INTEL_I830), -+}; -+ -+static const struct intel_device_info intel_i845g_info = { -+ I845_FEATURES, -+ PLATFORM(INTEL_I845G), -+}; -+ -+static const struct intel_device_info intel_i85x_info = { -+ I830_FEATURES, -+ PLATFORM(INTEL_I85X), -+ .display.has_fbc = 1, -+}; -+ -+static const struct intel_device_info intel_i865g_info = { -+ I845_FEATURES, -+ PLATFORM(INTEL_I865G), -+}; -+ -+#define GEN3_FEATURES \ -+ GEN(3), \ -+ .num_pipes = 2, \ -+ .display.has_gmch = 1, \ -+ .gpu_reset_clobbers_display = true, \ -+ .engine_mask = BIT(RCS0), \ -+ .has_snoop = true, \ -+ .has_coherent_ggtt = true, \ -+ I9XX_PIPE_OFFSETS, \ -+ I9XX_CURSOR_OFFSETS, \ -+ I9XX_COLORS, \ -+ GEN_DEFAULT_PAGE_SIZES -+ -+static const struct intel_device_info intel_i915g_info = { -+ GEN3_FEATURES, -+ PLATFORM(INTEL_I915G), -+ .has_coherent_ggtt = false, -+ .display.cursor_needs_physical = 1, -+ .display.has_overlay = 1, -+ .display.overlay_needs_physical = 1, -+ .hws_needs_physical = 1, -+ .unfenced_needs_alignment = 1, -+}; -+ -+static const struct intel_device_info intel_i915gm_info = { -+ GEN3_FEATURES, -+ PLATFORM(INTEL_I915GM), -+ .is_mobile = 1, -+ .display.cursor_needs_physical = 1, -+ .display.has_overlay = 1, -+ .display.overlay_needs_physical = 1, -+ .display.supports_tv = 1, -+ .display.has_fbc = 1, -+ .hws_needs_physical = 1, -+ .unfenced_needs_alignment = 1, -+}; -+ -+static const struct intel_device_info intel_i945g_info = { -+ GEN3_FEATURES, -+ PLATFORM(INTEL_I945G), -+ .display.has_hotplug = 1, -+ .display.cursor_needs_physical = 1, -+ .display.has_overlay = 1, -+ .display.overlay_needs_physical = 1, -+ .hws_needs_physical = 1, -+ .unfenced_needs_alignment = 1, -+}; -+ -+static const struct intel_device_info intel_i945gm_info = { -+ GEN3_FEATURES, -+ PLATFORM(INTEL_I945GM), -+ .is_mobile = 1, -+ .display.has_hotplug = 1, -+ .display.cursor_needs_physical = 1, -+ .display.has_overlay = 1, -+ .display.overlay_needs_physical = 1, -+ .display.supports_tv = 1, -+ .display.has_fbc = 1, -+ .hws_needs_physical = 1, -+ .unfenced_needs_alignment = 1, -+}; -+ -+static const struct intel_device_info intel_g33_info = { -+ GEN3_FEATURES, -+ PLATFORM(INTEL_G33), -+ .display.has_hotplug = 1, -+ .display.has_overlay = 1, -+}; -+ -+static const struct intel_device_info intel_pineview_g_info = { -+ GEN3_FEATURES, -+ PLATFORM(INTEL_PINEVIEW), -+ .display.has_hotplug = 1, -+ .display.has_overlay = 1, -+}; -+ -+static const struct intel_device_info intel_pineview_m_info = { -+ GEN3_FEATURES, -+ PLATFORM(INTEL_PINEVIEW), -+ .is_mobile = 1, -+ .display.has_hotplug = 1, -+ .display.has_overlay = 1, -+}; -+ -+#define GEN4_FEATURES \ -+ GEN(4), \ -+ .num_pipes = 2, \ -+ .display.has_hotplug = 1, \ -+ .display.has_gmch = 1, \ -+ .gpu_reset_clobbers_display = true, \ -+ .engine_mask = BIT(RCS0), \ -+ .has_snoop = true, \ -+ .has_coherent_ggtt = true, \ -+ I9XX_PIPE_OFFSETS, \ -+ I9XX_CURSOR_OFFSETS, \ -+ I965_COLORS, \ -+ GEN_DEFAULT_PAGE_SIZES -+ -+static const struct intel_device_info intel_i965g_info = { -+ GEN4_FEATURES, -+ PLATFORM(INTEL_I965G), -+ .display.has_overlay = 1, -+ .hws_needs_physical = 1, -+ .has_snoop = false, -+}; -+ -+static const struct intel_device_info intel_i965gm_info = { -+ GEN4_FEATURES, -+ PLATFORM(INTEL_I965GM), -+ .is_mobile = 1, -+ .display.has_fbc = 1, -+ .display.has_overlay = 1, -+ .display.supports_tv = 1, -+ .hws_needs_physical = 1, -+ .has_snoop = false, -+}; -+ -+static const struct intel_device_info intel_g45_info = { -+ GEN4_FEATURES, -+ PLATFORM(INTEL_G45), -+ .engine_mask = BIT(RCS0) | BIT(VCS0), -+ .gpu_reset_clobbers_display = false, -+}; -+ -+static const struct intel_device_info intel_gm45_info = { -+ GEN4_FEATURES, -+ PLATFORM(INTEL_GM45), -+ .is_mobile = 1, -+ .display.has_fbc = 1, -+ .display.supports_tv = 1, -+ .engine_mask = BIT(RCS0) | BIT(VCS0), -+ .gpu_reset_clobbers_display = false, -+}; -+ -+#define GEN5_FEATURES \ -+ GEN(5), \ -+ .num_pipes = 2, \ -+ .display.has_hotplug = 1, \ -+ .engine_mask = BIT(RCS0) | BIT(VCS0), \ -+ .has_snoop = true, \ -+ .has_coherent_ggtt = true, \ -+ /* ilk does support rc6, but we do not implement [power] contexts */ \ -+ .has_rc6 = 0, \ -+ I9XX_PIPE_OFFSETS, \ -+ I9XX_CURSOR_OFFSETS, \ -+ ILK_COLORS, \ -+ GEN_DEFAULT_PAGE_SIZES -+ -+static const struct intel_device_info intel_ironlake_d_info = { -+ GEN5_FEATURES, -+ PLATFORM(INTEL_IRONLAKE), -+}; -+ -+static const struct intel_device_info intel_ironlake_m_info = { -+ GEN5_FEATURES, -+ PLATFORM(INTEL_IRONLAKE), -+ .is_mobile = 1, -+ .display.has_fbc = 1, -+}; -+ -+#define GEN6_FEATURES \ -+ GEN(6), \ -+ .num_pipes = 2, \ -+ .display.has_hotplug = 1, \ -+ .display.has_fbc = 1, \ -+ .engine_mask = BIT(RCS0) | BIT(VCS0) | BIT(BCS0), \ -+ .has_coherent_ggtt = true, \ -+ .has_llc = 1, \ -+ .has_rc6 = 1, \ -+ .has_rc6p = 1, \ -+ .ppgtt_type = INTEL_PPGTT_ALIASING, \ -+ .ppgtt_size = 31, \ -+ I9XX_PIPE_OFFSETS, \ -+ I9XX_CURSOR_OFFSETS, \ -+ ILK_COLORS, \ -+ GEN_DEFAULT_PAGE_SIZES -+ -+#define SNB_D_PLATFORM \ -+ GEN6_FEATURES, \ -+ PLATFORM(INTEL_SANDYBRIDGE) -+ -+static const struct intel_device_info intel_sandybridge_d_gt1_info = { -+ SNB_D_PLATFORM, -+ .gt = 1, -+}; -+ -+static const struct intel_device_info intel_sandybridge_d_gt2_info = { -+ SNB_D_PLATFORM, -+ .gt = 2, -+}; -+ -+#define SNB_M_PLATFORM \ -+ GEN6_FEATURES, \ -+ PLATFORM(INTEL_SANDYBRIDGE), \ -+ .is_mobile = 1 -+ -+ -+static const struct intel_device_info intel_sandybridge_m_gt1_info = { -+ SNB_M_PLATFORM, -+ .gt = 1, -+}; -+ -+static const struct intel_device_info intel_sandybridge_m_gt2_info = { -+ SNB_M_PLATFORM, -+ .gt = 2, -+}; -+ -+#define GEN7_FEATURES \ -+ GEN(7), \ -+ .num_pipes = 3, \ -+ .display.has_hotplug = 1, \ -+ .display.has_fbc = 1, \ -+ .engine_mask = BIT(RCS0) | BIT(VCS0) | BIT(BCS0), \ -+ .has_coherent_ggtt = true, \ -+ .has_llc = 1, \ -+ .has_rc6 = 1, \ -+ .has_rc6p = 1, \ -+ .ppgtt_type = INTEL_PPGTT_FULL, \ -+ .ppgtt_size = 31, \ -+ IVB_PIPE_OFFSETS, \ -+ IVB_CURSOR_OFFSETS, \ -+ IVB_COLORS, \ -+ GEN_DEFAULT_PAGE_SIZES -+ -+#define IVB_D_PLATFORM \ -+ GEN7_FEATURES, \ -+ PLATFORM(INTEL_IVYBRIDGE), \ -+ .has_l3_dpf = 1 -+ -+static const struct intel_device_info intel_ivybridge_d_gt1_info = { -+ IVB_D_PLATFORM, -+ .gt = 1, -+}; -+ -+static const struct intel_device_info intel_ivybridge_d_gt2_info = { -+ IVB_D_PLATFORM, -+ .gt = 2, -+}; -+ -+#define IVB_M_PLATFORM \ -+ GEN7_FEATURES, \ -+ PLATFORM(INTEL_IVYBRIDGE), \ -+ .is_mobile = 1, \ -+ .has_l3_dpf = 1 -+ -+static const struct intel_device_info intel_ivybridge_m_gt1_info = { -+ IVB_M_PLATFORM, -+ .gt = 1, -+}; -+ -+static const struct intel_device_info intel_ivybridge_m_gt2_info = { -+ IVB_M_PLATFORM, -+ .gt = 2, -+}; -+ -+static const struct intel_device_info intel_ivybridge_q_info = { -+ GEN7_FEATURES, -+ PLATFORM(INTEL_IVYBRIDGE), -+ .gt = 2, -+ .num_pipes = 0, /* legal, last one wins */ -+ .has_l3_dpf = 1, -+}; -+ -+static const struct intel_device_info intel_valleyview_info = { -+ PLATFORM(INTEL_VALLEYVIEW), -+ GEN(7), -+ .is_lp = 1, -+ .num_pipes = 2, -+ .has_runtime_pm = 1, -+ .has_rc6 = 1, -+ .display.has_gmch = 1, -+ .display.has_hotplug = 1, -+ .ppgtt_type = INTEL_PPGTT_FULL, -+ .ppgtt_size = 31, -+ .has_snoop = true, -+ .has_coherent_ggtt = false, -+ .engine_mask = BIT(RCS0) | BIT(VCS0) | BIT(BCS0), -+ .display_mmio_offset = VLV_DISPLAY_BASE, -+ I9XX_PIPE_OFFSETS, -+ I9XX_CURSOR_OFFSETS, -+ I965_COLORS, -+ GEN_DEFAULT_PAGE_SIZES, -+}; -+ -+#define G75_FEATURES \ -+ GEN7_FEATURES, \ -+ .engine_mask = BIT(RCS0) | BIT(VCS0) | BIT(BCS0) | BIT(VECS0), \ -+ .display.has_ddi = 1, \ -+ .has_fpga_dbg = 1, \ -+ .display.has_psr = 1, \ -+ .display.has_dp_mst = 1, \ -+ .has_rc6p = 0 /* RC6p removed-by HSW */, \ -+ HSW_PIPE_OFFSETS, \ -+ .has_runtime_pm = 1 -+ -+#define HSW_PLATFORM \ -+ G75_FEATURES, \ -+ PLATFORM(INTEL_HASWELL), \ -+ .has_l3_dpf = 1 -+ -+static const struct intel_device_info intel_haswell_gt1_info = { -+ HSW_PLATFORM, -+ .gt = 1, -+}; -+ -+static const struct intel_device_info intel_haswell_gt2_info = { -+ HSW_PLATFORM, -+ .gt = 2, -+}; -+ -+static const struct intel_device_info intel_haswell_gt3_info = { -+ HSW_PLATFORM, -+ .gt = 3, -+}; -+ -+#define GEN8_FEATURES \ -+ G75_FEATURES, \ -+ GEN(8), \ -+ .page_sizes = I915_GTT_PAGE_SIZE_4K | \ -+ I915_GTT_PAGE_SIZE_2M, \ -+ .has_logical_ring_contexts = 1, \ -+ .ppgtt_type = INTEL_PPGTT_FULL, \ -+ .ppgtt_size = 48, \ -+ .has_64bit_reloc = 1, \ -+ .has_reset_engine = 1 -+ -+#define BDW_PLATFORM \ -+ GEN8_FEATURES, \ -+ PLATFORM(INTEL_BROADWELL) -+ -+static const struct intel_device_info intel_broadwell_gt1_info = { -+ BDW_PLATFORM, -+ .gt = 1, -+}; -+ -+static const struct intel_device_info intel_broadwell_gt2_info = { -+ BDW_PLATFORM, -+ .gt = 2, -+}; -+ -+static const struct intel_device_info intel_broadwell_rsvd_info = { -+ BDW_PLATFORM, -+ .gt = 3, -+ /* According to the device ID those devices are GT3, they were -+ * previously treated as not GT3, keep it like that. -+ */ -+}; -+ -+static const struct intel_device_info intel_broadwell_gt3_info = { -+ BDW_PLATFORM, -+ .gt = 3, -+ .engine_mask = -+ BIT(RCS0) | BIT(VCS0) | BIT(BCS0) | BIT(VECS0) | BIT(VCS1), -+}; -+ -+static const struct intel_device_info intel_cherryview_info = { -+ PLATFORM(INTEL_CHERRYVIEW), -+ GEN(8), -+ .num_pipes = 3, -+ .display.has_hotplug = 1, -+ .is_lp = 1, -+ .engine_mask = BIT(RCS0) | BIT(VCS0) | BIT(BCS0) | BIT(VECS0), -+ .has_64bit_reloc = 1, -+ .has_runtime_pm = 1, -+ .has_rc6 = 1, -+ .has_logical_ring_contexts = 1, -+ .display.has_gmch = 1, -+ .ppgtt_type = INTEL_PPGTT_FULL, -+ .ppgtt_size = 32, -+ .has_reset_engine = 1, -+ .has_snoop = true, -+ .has_coherent_ggtt = false, -+ .display_mmio_offset = VLV_DISPLAY_BASE, -+ CHV_PIPE_OFFSETS, -+ CHV_CURSOR_OFFSETS, -+ CHV_COLORS, -+ GEN_DEFAULT_PAGE_SIZES, -+}; -+ -+#define GEN9_DEFAULT_PAGE_SIZES \ -+ .page_sizes = I915_GTT_PAGE_SIZE_4K | \ -+ I915_GTT_PAGE_SIZE_64K | \ -+ I915_GTT_PAGE_SIZE_2M -+ -+#define GEN9_FEATURES \ -+ GEN8_FEATURES, \ -+ GEN(9), \ -+ GEN9_DEFAULT_PAGE_SIZES, \ -+ .has_logical_ring_preemption = 1, \ -+ .display.has_csr = 1, \ -+ .has_guc = 1, \ -+ .display.has_ipc = 1, \ -+ .ddb_size = 896 -+ -+#define SKL_PLATFORM \ -+ GEN9_FEATURES, \ -+ /* Display WA #0477 WaDisableIPC: skl */ \ -+ .display.has_ipc = 0, \ -+ PLATFORM(INTEL_SKYLAKE) -+ -+static const struct intel_device_info intel_skylake_gt1_info = { -+ SKL_PLATFORM, -+ .gt = 1, -+}; -+ -+static const struct intel_device_info intel_skylake_gt2_info = { -+ SKL_PLATFORM, -+ .gt = 2, -+}; -+ -+#define SKL_GT3_PLUS_PLATFORM \ -+ SKL_PLATFORM, \ -+ .engine_mask = \ -+ BIT(RCS0) | BIT(VCS0) | BIT(BCS0) | BIT(VECS0) | BIT(VCS1) -+ -+ -+static const struct intel_device_info intel_skylake_gt3_info = { -+ SKL_GT3_PLUS_PLATFORM, -+ .gt = 3, -+}; -+ -+static const struct intel_device_info intel_skylake_gt4_info = { -+ SKL_GT3_PLUS_PLATFORM, -+ .gt = 4, -+}; -+ -+#define GEN9_LP_FEATURES \ -+ GEN(9), \ -+ .is_lp = 1, \ -+ .display.has_hotplug = 1, \ -+ .engine_mask = BIT(RCS0) | BIT(VCS0) | BIT(BCS0) | BIT(VECS0), \ -+ .num_pipes = 3, \ -+ .has_64bit_reloc = 1, \ -+ .display.has_ddi = 1, \ -+ .has_fpga_dbg = 1, \ -+ .display.has_fbc = 1, \ -+ .display.has_psr = 1, \ -+ .has_runtime_pm = 1, \ -+ .display.has_csr = 1, \ -+ .has_rc6 = 1, \ -+ .display.has_dp_mst = 1, \ -+ .has_logical_ring_contexts = 1, \ -+ .has_logical_ring_preemption = 1, \ -+ .has_guc = 1, \ -+ .ppgtt_type = INTEL_PPGTT_FULL, \ -+ .ppgtt_size = 48, \ -+ .has_reset_engine = 1, \ -+ .has_snoop = true, \ -+ .has_coherent_ggtt = false, \ -+ .display.has_ipc = 1, \ -+ HSW_PIPE_OFFSETS, \ -+ IVB_CURSOR_OFFSETS, \ -+ IVB_COLORS, \ -+ GEN9_DEFAULT_PAGE_SIZES -+ -+static const struct intel_device_info intel_broxton_info = { -+ GEN9_LP_FEATURES, -+ PLATFORM(INTEL_BROXTON), -+ .ddb_size = 512, -+}; -+ -+static const struct intel_device_info intel_geminilake_info = { -+ GEN9_LP_FEATURES, -+ PLATFORM(INTEL_GEMINILAKE), -+ .ddb_size = 1024, -+ GLK_COLORS, -+}; -+ -+#define KBL_PLATFORM \ -+ GEN9_FEATURES, \ -+ PLATFORM(INTEL_KABYLAKE) -+ -+static const struct intel_device_info intel_kabylake_gt1_info = { -+ KBL_PLATFORM, -+ .gt = 1, -+}; -+ -+static const struct intel_device_info intel_kabylake_gt2_info = { -+ KBL_PLATFORM, -+ .gt = 2, -+}; -+ -+static const struct intel_device_info intel_kabylake_gt3_info = { -+ KBL_PLATFORM, -+ .gt = 3, -+ .engine_mask = -+ BIT(RCS0) | BIT(VCS0) | BIT(BCS0) | BIT(VECS0) | BIT(VCS1), -+}; -+ -+#define CFL_PLATFORM \ -+ GEN9_FEATURES, \ -+ PLATFORM(INTEL_COFFEELAKE) -+ -+static const struct intel_device_info intel_coffeelake_gt1_info = { -+ CFL_PLATFORM, -+ .gt = 1, -+}; -+ -+static const struct intel_device_info intel_coffeelake_gt2_info = { -+ CFL_PLATFORM, -+ .gt = 2, -+}; -+ -+static const struct intel_device_info intel_coffeelake_gt3_info = { -+ CFL_PLATFORM, -+ .gt = 3, -+ .engine_mask = -+ BIT(RCS0) | BIT(VCS0) | BIT(BCS0) | BIT(VECS0) | BIT(VCS1), -+}; -+ -+#define GEN10_FEATURES \ -+ GEN9_FEATURES, \ -+ GEN(10), \ -+ .ddb_size = 1024, \ -+ .has_coherent_ggtt = false, \ -+ GLK_COLORS -+ -+static const struct intel_device_info intel_cannonlake_info = { -+ GEN10_FEATURES, -+ PLATFORM(INTEL_CANNONLAKE), -+ .gt = 2, -+}; -+ -+#define GEN11_FEATURES \ -+ GEN10_FEATURES, \ -+ .pipe_offsets = { \ -+ [TRANSCODER_A] = PIPE_A_OFFSET, \ -+ [TRANSCODER_B] = PIPE_B_OFFSET, \ -+ [TRANSCODER_C] = PIPE_C_OFFSET, \ -+ [TRANSCODER_EDP] = PIPE_EDP_OFFSET, \ -+ [TRANSCODER_DSI_0] = PIPE_DSI0_OFFSET, \ -+ [TRANSCODER_DSI_1] = PIPE_DSI1_OFFSET, \ -+ }, \ -+ .trans_offsets = { \ -+ [TRANSCODER_A] = TRANSCODER_A_OFFSET, \ -+ [TRANSCODER_B] = TRANSCODER_B_OFFSET, \ -+ [TRANSCODER_C] = TRANSCODER_C_OFFSET, \ -+ [TRANSCODER_EDP] = TRANSCODER_EDP_OFFSET, \ -+ [TRANSCODER_DSI_0] = TRANSCODER_DSI0_OFFSET, \ -+ [TRANSCODER_DSI_1] = TRANSCODER_DSI1_OFFSET, \ -+ }, \ -+ GEN(11), \ -+ .ddb_size = 2048, \ -+ .has_logical_ring_elsq = 1, \ -+ .color = { .degamma_lut_size = 33, .gamma_lut_size = 1024 } -+ -+static const struct intel_device_info intel_icelake_11_info = { -+ GEN11_FEATURES, -+ PLATFORM(INTEL_ICELAKE), -+ .engine_mask = -+ BIT(RCS0) | BIT(BCS0) | BIT(VECS0) | BIT(VCS0) | BIT(VCS2), -+}; -+ -+static const struct intel_device_info intel_elkhartlake_info = { -+ GEN11_FEATURES, -+ PLATFORM(INTEL_ELKHARTLAKE), -+ .is_alpha_support = 1, -+ .engine_mask = BIT(RCS0) | BIT(BCS0) | BIT(VCS0), -+ .ppgtt_size = 36, -+}; -+ -+#undef GEN -+#undef PLATFORM -+ -+/* -+ * Make sure any device matches here are from most specific to most -+ * general. For example, since the Quanta match is based on the subsystem -+ * and subvendor IDs, we need it to come before the more general IVB -+ * PCI ID matches, otherwise we'll use the wrong info struct above. -+ */ -+static const struct pci_device_id pciidlist[] = { -+ INTEL_I830_IDS(&intel_i830_info), -+ INTEL_I845G_IDS(&intel_i845g_info), -+ INTEL_I85X_IDS(&intel_i85x_info), -+ INTEL_I865G_IDS(&intel_i865g_info), -+ INTEL_I915G_IDS(&intel_i915g_info), -+ INTEL_I915GM_IDS(&intel_i915gm_info), -+ INTEL_I945G_IDS(&intel_i945g_info), -+ INTEL_I945GM_IDS(&intel_i945gm_info), -+ INTEL_I965G_IDS(&intel_i965g_info), -+ INTEL_G33_IDS(&intel_g33_info), -+ INTEL_I965GM_IDS(&intel_i965gm_info), -+ INTEL_GM45_IDS(&intel_gm45_info), -+ INTEL_G45_IDS(&intel_g45_info), -+ INTEL_PINEVIEW_G_IDS(&intel_pineview_g_info), -+ INTEL_PINEVIEW_M_IDS(&intel_pineview_m_info), -+ INTEL_IRONLAKE_D_IDS(&intel_ironlake_d_info), -+ INTEL_IRONLAKE_M_IDS(&intel_ironlake_m_info), -+ INTEL_SNB_D_GT1_IDS(&intel_sandybridge_d_gt1_info), -+ INTEL_SNB_D_GT2_IDS(&intel_sandybridge_d_gt2_info), -+ INTEL_SNB_M_GT1_IDS(&intel_sandybridge_m_gt1_info), -+ INTEL_SNB_M_GT2_IDS(&intel_sandybridge_m_gt2_info), -+ INTEL_IVB_Q_IDS(&intel_ivybridge_q_info), /* must be first IVB */ -+ INTEL_IVB_M_GT1_IDS(&intel_ivybridge_m_gt1_info), -+ INTEL_IVB_M_GT2_IDS(&intel_ivybridge_m_gt2_info), -+ INTEL_IVB_D_GT1_IDS(&intel_ivybridge_d_gt1_info), -+ INTEL_IVB_D_GT2_IDS(&intel_ivybridge_d_gt2_info), -+ INTEL_HSW_GT1_IDS(&intel_haswell_gt1_info), -+ INTEL_HSW_GT2_IDS(&intel_haswell_gt2_info), -+ INTEL_HSW_GT3_IDS(&intel_haswell_gt3_info), -+ INTEL_VLV_IDS(&intel_valleyview_info), -+ INTEL_BDW_GT1_IDS(&intel_broadwell_gt1_info), -+ INTEL_BDW_GT2_IDS(&intel_broadwell_gt2_info), -+ INTEL_BDW_GT3_IDS(&intel_broadwell_gt3_info), -+ INTEL_BDW_RSVD_IDS(&intel_broadwell_rsvd_info), -+ INTEL_CHV_IDS(&intel_cherryview_info), -+ INTEL_SKL_GT1_IDS(&intel_skylake_gt1_info), -+ INTEL_SKL_GT2_IDS(&intel_skylake_gt2_info), -+ INTEL_SKL_GT3_IDS(&intel_skylake_gt3_info), -+ INTEL_SKL_GT4_IDS(&intel_skylake_gt4_info), -+ INTEL_BXT_IDS(&intel_broxton_info), -+ INTEL_GLK_IDS(&intel_geminilake_info), -+ INTEL_KBL_GT1_IDS(&intel_kabylake_gt1_info), -+ INTEL_KBL_GT2_IDS(&intel_kabylake_gt2_info), -+ INTEL_KBL_GT3_IDS(&intel_kabylake_gt3_info), -+ INTEL_KBL_GT4_IDS(&intel_kabylake_gt3_info), -+ INTEL_AML_KBL_GT2_IDS(&intel_kabylake_gt2_info), -+ INTEL_CFL_S_GT1_IDS(&intel_coffeelake_gt1_info), -+ INTEL_CFL_S_GT2_IDS(&intel_coffeelake_gt2_info), -+ INTEL_CFL_H_GT1_IDS(&intel_coffeelake_gt1_info), -+ INTEL_CFL_H_GT2_IDS(&intel_coffeelake_gt2_info), -+ INTEL_CFL_U_GT2_IDS(&intel_coffeelake_gt2_info), -+ INTEL_CFL_U_GT3_IDS(&intel_coffeelake_gt3_info), -+ INTEL_WHL_U_GT1_IDS(&intel_coffeelake_gt1_info), -+ INTEL_WHL_U_GT2_IDS(&intel_coffeelake_gt2_info), -+ INTEL_AML_CFL_GT2_IDS(&intel_coffeelake_gt2_info), -+ INTEL_WHL_U_GT3_IDS(&intel_coffeelake_gt3_info), -+ INTEL_CML_GT1_IDS(&intel_coffeelake_gt1_info), -+ INTEL_CML_GT2_IDS(&intel_coffeelake_gt2_info), -+ INTEL_CNL_IDS(&intel_cannonlake_info), -+ INTEL_ICL_11_IDS(&intel_icelake_11_info), -+ INTEL_EHL_IDS(&intel_elkhartlake_info), -+ {0, 0, 0} -+}; -+MODULE_DEVICE_TABLE(pci, pciidlist); -+ -+static void i915_pci_remove(struct pci_dev *pdev) -+{ -+ struct drm_device *dev; -+ -+ dev = pci_get_drvdata(pdev); -+ if (!dev) /* driver load aborted, nothing to cleanup */ -+ return; -+ -+ i915_driver_unload(dev); -+ drm_dev_put(dev); -+ -+ pci_set_drvdata(pdev, NULL); -+} -+ -+static int i915_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) -+{ -+ struct intel_device_info *intel_info = -+ (struct intel_device_info *) ent->driver_data; -+ int err; -+ -+ if (IS_ALPHA_SUPPORT(intel_info) && !i915_modparams.alpha_support) { -+ DRM_INFO("The driver support for your hardware in this kernel version is alpha quality\n" -+ "See CONFIG_DRM_I915_ALPHA_SUPPORT or i915.alpha_support module parameter\n" -+ "to enable support in this kernel version, or check for kernel updates.\n"); -+ return -ENODEV; -+ } -+ -+ /* Only bind to function 0 of the device. Early generations -+ * used function 1 as a placeholder for multi-head. This causes -+ * us confusion instead, especially on the systems where both -+ * functions have the same PCI-ID! -+ */ -+ if (PCI_FUNC(pdev->devfn)) -+ return -ENODEV; -+ -+ /* -+ * apple-gmux is needed on dual GPU MacBook Pro -+ * to probe the panel if we're the inactive GPU. -+ */ -+ if (vga_switcheroo_client_probe_defer(pdev)) -+ return -EPROBE_DEFER; -+ -+ err = i915_driver_load(pdev, ent); -+ if (err) -+ return err; -+ -+ if (i915_inject_load_failure()) { -+ i915_pci_remove(pdev); -+ return -ENODEV; -+ } -+ -+ err = i915_live_selftests(pdev); -+ if (err) { -+ i915_pci_remove(pdev); -+ return err > 0 ? -ENOTTY : err; -+ } -+ -+ return 0; -+} -+ -+static struct pci_driver i915_pci_driver = { -+ .name = DRIVER_NAME, -+ .id_table = pciidlist, -+ .probe = i915_pci_probe, -+ .remove = i915_pci_remove, -+ .driver.pm = &i915_pm_ops, -+}; -+ -+static int __init i915_init(void) -+{ -+ bool use_kms = true; -+ int err; -+ -+ err = i915_globals_init(); -+ if (err) -+ return err; -+ -+ err = i915_mock_selftests(); -+ if (err) -+ return err > 0 ? 0 : err; -+ -+ /* -+ * Enable KMS by default, unless explicitly overriden by -+ * either the i915.modeset prarameter or by the -+ * vga_text_mode_force boot option. -+ */ -+ -+ if (i915_modparams.modeset == 0) -+ use_kms = false; -+ -+ if (vgacon_text_force() && i915_modparams.modeset == -1) -+ use_kms = false; -+ -+ if (!use_kms) { -+ /* Silently fail loading to not upset userspace. */ -+ DRM_DEBUG_DRIVER("KMS disabled.\n"); -+ return 0; -+ } -+ -+ return pci_register_driver(&i915_pci_driver); -+} -+ -+static void __exit i915_exit(void) -+{ -+ if (!i915_pci_driver.driver.owner) -+ return; -+ -+ pci_unregister_driver(&i915_pci_driver); -+ i915_globals_exit(); -+} -+ -+module_init(i915_init); -+module_exit(i915_exit); -+ -+MODULE_AUTHOR("Tungsten Graphics, Inc."); -+MODULE_AUTHOR("Intel Corporation"); -+ -+MODULE_DESCRIPTION(DRIVER_DESC); -+MODULE_LICENSE("GPL and additional rights"); -diff --git a/drivers/gpu/drm/i915_legacy/i915_perf.c b/drivers/gpu/drm/i915_legacy/i915_perf.c -new file mode 100644 -index 000000000000..235aedc62b4c ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_perf.c -@@ -0,0 +1,3519 @@ -+/* -+ * Copyright © 2015-2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Robert Bragg -+ */ -+ -+ -+/** -+ * DOC: i915 Perf Overview -+ * -+ * Gen graphics supports a large number of performance counters that can help -+ * driver and application developers understand and optimize their use of the -+ * GPU. -+ * -+ * This i915 perf interface enables userspace to configure and open a file -+ * descriptor representing a stream of GPU metrics which can then be read() as -+ * a stream of sample records. -+ * -+ * The interface is particularly suited to exposing buffered metrics that are -+ * captured by DMA from the GPU, unsynchronized with and unrelated to the CPU. -+ * -+ * Streams representing a single context are accessible to applications with a -+ * corresponding drm file descriptor, such that OpenGL can use the interface -+ * without special privileges. Access to system-wide metrics requires root -+ * privileges by default, unless changed via the dev.i915.perf_event_paranoid -+ * sysctl option. -+ * -+ */ -+ -+/** -+ * DOC: i915 Perf History and Comparison with Core Perf -+ * -+ * The interface was initially inspired by the core Perf infrastructure but -+ * some notable differences are: -+ * -+ * i915 perf file descriptors represent a "stream" instead of an "event"; where -+ * a perf event primarily corresponds to a single 64bit value, while a stream -+ * might sample sets of tightly-coupled counters, depending on the -+ * configuration. For example the Gen OA unit isn't designed to support -+ * orthogonal configurations of individual counters; it's configured for a set -+ * of related counters. Samples for an i915 perf stream capturing OA metrics -+ * will include a set of counter values packed in a compact HW specific format. -+ * The OA unit supports a number of different packing formats which can be -+ * selected by the user opening the stream. Perf has support for grouping -+ * events, but each event in the group is configured, validated and -+ * authenticated individually with separate system calls. -+ * -+ * i915 perf stream configurations are provided as an array of u64 (key,value) -+ * pairs, instead of a fixed struct with multiple miscellaneous config members, -+ * interleaved with event-type specific members. -+ * -+ * i915 perf doesn't support exposing metrics via an mmap'd circular buffer. -+ * The supported metrics are being written to memory by the GPU unsynchronized -+ * with the CPU, using HW specific packing formats for counter sets. Sometimes -+ * the constraints on HW configuration require reports to be filtered before it -+ * would be acceptable to expose them to unprivileged applications - to hide -+ * the metrics of other processes/contexts. For these use cases a read() based -+ * interface is a good fit, and provides an opportunity to filter data as it -+ * gets copied from the GPU mapped buffers to userspace buffers. -+ * -+ * -+ * Issues hit with first prototype based on Core Perf -+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -+ * -+ * The first prototype of this driver was based on the core perf -+ * infrastructure, and while we did make that mostly work, with some changes to -+ * perf, we found we were breaking or working around too many assumptions baked -+ * into perf's currently cpu centric design. -+ * -+ * In the end we didn't see a clear benefit to making perf's implementation and -+ * interface more complex by changing design assumptions while we knew we still -+ * wouldn't be able to use any existing perf based userspace tools. -+ * -+ * Also considering the Gen specific nature of the Observability hardware and -+ * how userspace will sometimes need to combine i915 perf OA metrics with -+ * side-band OA data captured via MI_REPORT_PERF_COUNT commands; we're -+ * expecting the interface to be used by a platform specific userspace such as -+ * OpenGL or tools. This is to say; we aren't inherently missing out on having -+ * a standard vendor/architecture agnostic interface by not using perf. -+ * -+ * -+ * For posterity, in case we might re-visit trying to adapt core perf to be -+ * better suited to exposing i915 metrics these were the main pain points we -+ * hit: -+ * -+ * - The perf based OA PMU driver broke some significant design assumptions: -+ * -+ * Existing perf pmus are used for profiling work on a cpu and we were -+ * introducing the idea of _IS_DEVICE pmus with different security -+ * implications, the need to fake cpu-related data (such as user/kernel -+ * registers) to fit with perf's current design, and adding _DEVICE records -+ * as a way to forward device-specific status records. -+ * -+ * The OA unit writes reports of counters into a circular buffer, without -+ * involvement from the CPU, making our PMU driver the first of a kind. -+ * -+ * Given the way we were periodically forward data from the GPU-mapped, OA -+ * buffer to perf's buffer, those bursts of sample writes looked to perf like -+ * we were sampling too fast and so we had to subvert its throttling checks. -+ * -+ * Perf supports groups of counters and allows those to be read via -+ * transactions internally but transactions currently seem designed to be -+ * explicitly initiated from the cpu (say in response to a userspace read()) -+ * and while we could pull a report out of the OA buffer we can't -+ * trigger a report from the cpu on demand. -+ * -+ * Related to being report based; the OA counters are configured in HW as a -+ * set while perf generally expects counter configurations to be orthogonal. -+ * Although counters can be associated with a group leader as they are -+ * opened, there's no clear precedent for being able to provide group-wide -+ * configuration attributes (for example we want to let userspace choose the -+ * OA unit report format used to capture all counters in a set, or specify a -+ * GPU context to filter metrics on). We avoided using perf's grouping -+ * feature and forwarded OA reports to userspace via perf's 'raw' sample -+ * field. This suited our userspace well considering how coupled the counters -+ * are when dealing with normalizing. It would be inconvenient to split -+ * counters up into separate events, only to require userspace to recombine -+ * them. For Mesa it's also convenient to be forwarded raw, periodic reports -+ * for combining with the side-band raw reports it captures using -+ * MI_REPORT_PERF_COUNT commands. -+ * -+ * - As a side note on perf's grouping feature; there was also some concern -+ * that using PERF_FORMAT_GROUP as a way to pack together counter values -+ * would quite drastically inflate our sample sizes, which would likely -+ * lower the effective sampling resolutions we could use when the available -+ * memory bandwidth is limited. -+ * -+ * With the OA unit's report formats, counters are packed together as 32 -+ * or 40bit values, with the largest report size being 256 bytes. -+ * -+ * PERF_FORMAT_GROUP values are 64bit, but there doesn't appear to be a -+ * documented ordering to the values, implying PERF_FORMAT_ID must also be -+ * used to add a 64bit ID before each value; giving 16 bytes per counter. -+ * -+ * Related to counter orthogonality; we can't time share the OA unit, while -+ * event scheduling is a central design idea within perf for allowing -+ * userspace to open + enable more events than can be configured in HW at any -+ * one time. The OA unit is not designed to allow re-configuration while in -+ * use. We can't reconfigure the OA unit without losing internal OA unit -+ * state which we can't access explicitly to save and restore. Reconfiguring -+ * the OA unit is also relatively slow, involving ~100 register writes. From -+ * userspace Mesa also depends on a stable OA configuration when emitting -+ * MI_REPORT_PERF_COUNT commands and importantly the OA unit can't be -+ * disabled while there are outstanding MI_RPC commands lest we hang the -+ * command streamer. -+ * -+ * The contents of sample records aren't extensible by device drivers (i.e. -+ * the sample_type bits). As an example; Sourab Gupta had been looking to -+ * attach GPU timestamps to our OA samples. We were shoehorning OA reports -+ * into sample records by using the 'raw' field, but it's tricky to pack more -+ * than one thing into this field because events/core.c currently only lets a -+ * pmu give a single raw data pointer plus len which will be copied into the -+ * ring buffer. To include more than the OA report we'd have to copy the -+ * report into an intermediate larger buffer. I'd been considering allowing a -+ * vector of data+len values to be specified for copying the raw data, but -+ * it felt like a kludge to being using the raw field for this purpose. -+ * -+ * - It felt like our perf based PMU was making some technical compromises -+ * just for the sake of using perf: -+ * -+ * perf_event_open() requires events to either relate to a pid or a specific -+ * cpu core, while our device pmu related to neither. Events opened with a -+ * pid will be automatically enabled/disabled according to the scheduling of -+ * that process - so not appropriate for us. When an event is related to a -+ * cpu id, perf ensures pmu methods will be invoked via an inter process -+ * interrupt on that core. To avoid invasive changes our userspace opened OA -+ * perf events for a specific cpu. This was workable but it meant the -+ * majority of the OA driver ran in atomic context, including all OA report -+ * forwarding, which wasn't really necessary in our case and seems to make -+ * our locking requirements somewhat complex as we handled the interaction -+ * with the rest of the i915 driver. -+ */ -+ -+#include -+#include -+#include -+ -+#include "i915_drv.h" -+#include "i915_oa_hsw.h" -+#include "i915_oa_bdw.h" -+#include "i915_oa_chv.h" -+#include "i915_oa_sklgt2.h" -+#include "i915_oa_sklgt3.h" -+#include "i915_oa_sklgt4.h" -+#include "i915_oa_bxt.h" -+#include "i915_oa_kblgt2.h" -+#include "i915_oa_kblgt3.h" -+#include "i915_oa_glk.h" -+#include "i915_oa_cflgt2.h" -+#include "i915_oa_cflgt3.h" -+#include "i915_oa_cnl.h" -+#include "i915_oa_icl.h" -+#include "intel_lrc_reg.h" -+ -+/* HW requires this to be a power of two, between 128k and 16M, though driver -+ * is currently generally designed assuming the largest 16M size is used such -+ * that the overflow cases are unlikely in normal operation. -+ */ -+#define OA_BUFFER_SIZE SZ_16M -+ -+#define OA_TAKEN(tail, head) ((tail - head) & (OA_BUFFER_SIZE - 1)) -+ -+/** -+ * DOC: OA Tail Pointer Race -+ * -+ * There's a HW race condition between OA unit tail pointer register updates and -+ * writes to memory whereby the tail pointer can sometimes get ahead of what's -+ * been written out to the OA buffer so far (in terms of what's visible to the -+ * CPU). -+ * -+ * Although this can be observed explicitly while copying reports to userspace -+ * by checking for a zeroed report-id field in tail reports, we want to account -+ * for this earlier, as part of the oa_buffer_check to avoid lots of redundant -+ * read() attempts. -+ * -+ * In effect we define a tail pointer for reading that lags the real tail -+ * pointer by at least %OA_TAIL_MARGIN_NSEC nanoseconds, which gives enough -+ * time for the corresponding reports to become visible to the CPU. -+ * -+ * To manage this we actually track two tail pointers: -+ * 1) An 'aging' tail with an associated timestamp that is tracked until we -+ * can trust the corresponding data is visible to the CPU; at which point -+ * it is considered 'aged'. -+ * 2) An 'aged' tail that can be used for read()ing. -+ * -+ * The two separate pointers let us decouple read()s from tail pointer aging. -+ * -+ * The tail pointers are checked and updated at a limited rate within a hrtimer -+ * callback (the same callback that is used for delivering EPOLLIN events) -+ * -+ * Initially the tails are marked invalid with %INVALID_TAIL_PTR which -+ * indicates that an updated tail pointer is needed. -+ * -+ * Most of the implementation details for this workaround are in -+ * oa_buffer_check_unlocked() and _append_oa_reports() -+ * -+ * Note for posterity: previously the driver used to define an effective tail -+ * pointer that lagged the real pointer by a 'tail margin' measured in bytes -+ * derived from %OA_TAIL_MARGIN_NSEC and the configured sampling frequency. -+ * This was flawed considering that the OA unit may also automatically generate -+ * non-periodic reports (such as on context switch) or the OA unit may be -+ * enabled without any periodic sampling. -+ */ -+#define OA_TAIL_MARGIN_NSEC 100000ULL -+#define INVALID_TAIL_PTR 0xffffffff -+ -+/* frequency for checking whether the OA unit has written new reports to the -+ * circular OA buffer... -+ */ -+#define POLL_FREQUENCY 200 -+#define POLL_PERIOD (NSEC_PER_SEC / POLL_FREQUENCY) -+ -+/* for sysctl proc_dointvec_minmax of dev.i915.perf_stream_paranoid */ -+static int zero; -+static int one = 1; -+static u32 i915_perf_stream_paranoid = true; -+ -+/* The maximum exponent the hardware accepts is 63 (essentially it selects one -+ * of the 64bit timestamp bits to trigger reports from) but there's currently -+ * no known use case for sampling as infrequently as once per 47 thousand years. -+ * -+ * Since the timestamps included in OA reports are only 32bits it seems -+ * reasonable to limit the OA exponent where it's still possible to account for -+ * overflow in OA report timestamps. -+ */ -+#define OA_EXPONENT_MAX 31 -+ -+#define INVALID_CTX_ID 0xffffffff -+ -+/* On Gen8+ automatically triggered OA reports include a 'reason' field... */ -+#define OAREPORT_REASON_MASK 0x3f -+#define OAREPORT_REASON_SHIFT 19 -+#define OAREPORT_REASON_TIMER (1<<0) -+#define OAREPORT_REASON_CTX_SWITCH (1<<3) -+#define OAREPORT_REASON_CLK_RATIO (1<<5) -+ -+ -+/* For sysctl proc_dointvec_minmax of i915_oa_max_sample_rate -+ * -+ * The highest sampling frequency we can theoretically program the OA unit -+ * with is always half the timestamp frequency: E.g. 6.25Mhz for Haswell. -+ * -+ * Initialized just before we register the sysctl parameter. -+ */ -+static int oa_sample_rate_hard_limit; -+ -+/* Theoretically we can program the OA unit to sample every 160ns but don't -+ * allow that by default unless root... -+ * -+ * The default threshold of 100000Hz is based on perf's similar -+ * kernel.perf_event_max_sample_rate sysctl parameter. -+ */ -+static u32 i915_oa_max_sample_rate = 100000; -+ -+/* XXX: beware if future OA HW adds new report formats that the current -+ * code assumes all reports have a power-of-two size and ~(size - 1) can -+ * be used as a mask to align the OA tail pointer. -+ */ -+static const struct i915_oa_format hsw_oa_formats[I915_OA_FORMAT_MAX] = { -+ [I915_OA_FORMAT_A13] = { 0, 64 }, -+ [I915_OA_FORMAT_A29] = { 1, 128 }, -+ [I915_OA_FORMAT_A13_B8_C8] = { 2, 128 }, -+ /* A29_B8_C8 Disallowed as 192 bytes doesn't factor into buffer size */ -+ [I915_OA_FORMAT_B4_C8] = { 4, 64 }, -+ [I915_OA_FORMAT_A45_B8_C8] = { 5, 256 }, -+ [I915_OA_FORMAT_B4_C8_A16] = { 6, 128 }, -+ [I915_OA_FORMAT_C4_B8] = { 7, 64 }, -+}; -+ -+static const struct i915_oa_format gen8_plus_oa_formats[I915_OA_FORMAT_MAX] = { -+ [I915_OA_FORMAT_A12] = { 0, 64 }, -+ [I915_OA_FORMAT_A12_B8_C8] = { 2, 128 }, -+ [I915_OA_FORMAT_A32u40_A4u32_B8_C8] = { 5, 256 }, -+ [I915_OA_FORMAT_C4_B8] = { 7, 64 }, -+}; -+ -+#define SAMPLE_OA_REPORT (1<<0) -+ -+/** -+ * struct perf_open_properties - for validated properties given to open a stream -+ * @sample_flags: `DRM_I915_PERF_PROP_SAMPLE_*` properties are tracked as flags -+ * @single_context: Whether a single or all gpu contexts should be monitored -+ * @ctx_handle: A gem ctx handle for use with @single_context -+ * @metrics_set: An ID for an OA unit metric set advertised via sysfs -+ * @oa_format: An OA unit HW report format -+ * @oa_periodic: Whether to enable periodic OA unit sampling -+ * @oa_period_exponent: The OA unit sampling period is derived from this -+ * -+ * As read_properties_unlocked() enumerates and validates the properties given -+ * to open a stream of metrics the configuration is built up in the structure -+ * which starts out zero initialized. -+ */ -+struct perf_open_properties { -+ u32 sample_flags; -+ -+ u64 single_context:1; -+ u64 ctx_handle; -+ -+ /* OA sampling state */ -+ int metrics_set; -+ int oa_format; -+ bool oa_periodic; -+ int oa_period_exponent; -+}; -+ -+static void free_oa_config(struct drm_i915_private *dev_priv, -+ struct i915_oa_config *oa_config) -+{ -+ if (!PTR_ERR(oa_config->flex_regs)) -+ kfree(oa_config->flex_regs); -+ if (!PTR_ERR(oa_config->b_counter_regs)) -+ kfree(oa_config->b_counter_regs); -+ if (!PTR_ERR(oa_config->mux_regs)) -+ kfree(oa_config->mux_regs); -+ kfree(oa_config); -+} -+ -+static void put_oa_config(struct drm_i915_private *dev_priv, -+ struct i915_oa_config *oa_config) -+{ -+ if (!atomic_dec_and_test(&oa_config->ref_count)) -+ return; -+ -+ free_oa_config(dev_priv, oa_config); -+} -+ -+static int get_oa_config(struct drm_i915_private *dev_priv, -+ int metrics_set, -+ struct i915_oa_config **out_config) -+{ -+ int ret; -+ -+ if (metrics_set == 1) { -+ *out_config = &dev_priv->perf.oa.test_config; -+ atomic_inc(&dev_priv->perf.oa.test_config.ref_count); -+ return 0; -+ } -+ -+ ret = mutex_lock_interruptible(&dev_priv->perf.metrics_lock); -+ if (ret) -+ return ret; -+ -+ *out_config = idr_find(&dev_priv->perf.metrics_idr, metrics_set); -+ if (!*out_config) -+ ret = -EINVAL; -+ else -+ atomic_inc(&(*out_config)->ref_count); -+ -+ mutex_unlock(&dev_priv->perf.metrics_lock); -+ -+ return ret; -+} -+ -+static u32 gen8_oa_hw_tail_read(struct drm_i915_private *dev_priv) -+{ -+ return I915_READ(GEN8_OATAILPTR) & GEN8_OATAILPTR_MASK; -+} -+ -+static u32 gen7_oa_hw_tail_read(struct drm_i915_private *dev_priv) -+{ -+ u32 oastatus1 = I915_READ(GEN7_OASTATUS1); -+ -+ return oastatus1 & GEN7_OASTATUS1_TAIL_MASK; -+} -+ -+/** -+ * oa_buffer_check_unlocked - check for data and update tail ptr state -+ * @dev_priv: i915 device instance -+ * -+ * This is either called via fops (for blocking reads in user ctx) or the poll -+ * check hrtimer (atomic ctx) to check the OA buffer tail pointer and check -+ * if there is data available for userspace to read. -+ * -+ * This function is central to providing a workaround for the OA unit tail -+ * pointer having a race with respect to what data is visible to the CPU. -+ * It is responsible for reading tail pointers from the hardware and giving -+ * the pointers time to 'age' before they are made available for reading. -+ * (See description of OA_TAIL_MARGIN_NSEC above for further details.) -+ * -+ * Besides returning true when there is data available to read() this function -+ * also has the side effect of updating the oa_buffer.tails[], .aging_timestamp -+ * and .aged_tail_idx state used for reading. -+ * -+ * Note: It's safe to read OA config state here unlocked, assuming that this is -+ * only called while the stream is enabled, while the global OA configuration -+ * can't be modified. -+ * -+ * Returns: %true if the OA buffer contains data, else %false -+ */ -+static bool oa_buffer_check_unlocked(struct drm_i915_private *dev_priv) -+{ -+ int report_size = dev_priv->perf.oa.oa_buffer.format_size; -+ unsigned long flags; -+ unsigned int aged_idx; -+ u32 head, hw_tail, aged_tail, aging_tail; -+ u64 now; -+ -+ /* We have to consider the (unlikely) possibility that read() errors -+ * could result in an OA buffer reset which might reset the head, -+ * tails[] and aged_tail state. -+ */ -+ spin_lock_irqsave(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); -+ -+ /* NB: The head we observe here might effectively be a little out of -+ * date (between head and tails[aged_idx].offset if there is currently -+ * a read() in progress. -+ */ -+ head = dev_priv->perf.oa.oa_buffer.head; -+ -+ aged_idx = dev_priv->perf.oa.oa_buffer.aged_tail_idx; -+ aged_tail = dev_priv->perf.oa.oa_buffer.tails[aged_idx].offset; -+ aging_tail = dev_priv->perf.oa.oa_buffer.tails[!aged_idx].offset; -+ -+ hw_tail = dev_priv->perf.oa.ops.oa_hw_tail_read(dev_priv); -+ -+ /* The tail pointer increases in 64 byte increments, -+ * not in report_size steps... -+ */ -+ hw_tail &= ~(report_size - 1); -+ -+ now = ktime_get_mono_fast_ns(); -+ -+ /* Update the aged tail -+ * -+ * Flip the tail pointer available for read()s once the aging tail is -+ * old enough to trust that the corresponding data will be visible to -+ * the CPU... -+ * -+ * Do this before updating the aging pointer in case we may be able to -+ * immediately start aging a new pointer too (if new data has become -+ * available) without needing to wait for a later hrtimer callback. -+ */ -+ if (aging_tail != INVALID_TAIL_PTR && -+ ((now - dev_priv->perf.oa.oa_buffer.aging_timestamp) > -+ OA_TAIL_MARGIN_NSEC)) { -+ -+ aged_idx ^= 1; -+ dev_priv->perf.oa.oa_buffer.aged_tail_idx = aged_idx; -+ -+ aged_tail = aging_tail; -+ -+ /* Mark that we need a new pointer to start aging... */ -+ dev_priv->perf.oa.oa_buffer.tails[!aged_idx].offset = INVALID_TAIL_PTR; -+ aging_tail = INVALID_TAIL_PTR; -+ } -+ -+ /* Update the aging tail -+ * -+ * We throttle aging tail updates until we have a new tail that -+ * represents >= one report more data than is already available for -+ * reading. This ensures there will be enough data for a successful -+ * read once this new pointer has aged and ensures we will give the new -+ * pointer time to age. -+ */ -+ if (aging_tail == INVALID_TAIL_PTR && -+ (aged_tail == INVALID_TAIL_PTR || -+ OA_TAKEN(hw_tail, aged_tail) >= report_size)) { -+ struct i915_vma *vma = dev_priv->perf.oa.oa_buffer.vma; -+ u32 gtt_offset = i915_ggtt_offset(vma); -+ -+ /* Be paranoid and do a bounds check on the pointer read back -+ * from hardware, just in case some spurious hardware condition -+ * could put the tail out of bounds... -+ */ -+ if (hw_tail >= gtt_offset && -+ hw_tail < (gtt_offset + OA_BUFFER_SIZE)) { -+ dev_priv->perf.oa.oa_buffer.tails[!aged_idx].offset = -+ aging_tail = hw_tail; -+ dev_priv->perf.oa.oa_buffer.aging_timestamp = now; -+ } else { -+ DRM_ERROR("Ignoring spurious out of range OA buffer tail pointer = %u\n", -+ hw_tail); -+ } -+ } -+ -+ spin_unlock_irqrestore(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); -+ -+ return aged_tail == INVALID_TAIL_PTR ? -+ false : OA_TAKEN(aged_tail, head) >= report_size; -+} -+ -+/** -+ * append_oa_status - Appends a status record to a userspace read() buffer. -+ * @stream: An i915-perf stream opened for OA metrics -+ * @buf: destination buffer given by userspace -+ * @count: the number of bytes userspace wants to read -+ * @offset: (inout): the current position for writing into @buf -+ * @type: The kind of status to report to userspace -+ * -+ * Writes a status record (such as `DRM_I915_PERF_RECORD_OA_REPORT_LOST`) -+ * into the userspace read() buffer. -+ * -+ * The @buf @offset will only be updated on success. -+ * -+ * Returns: 0 on success, negative error code on failure. -+ */ -+static int append_oa_status(struct i915_perf_stream *stream, -+ char __user *buf, -+ size_t count, -+ size_t *offset, -+ enum drm_i915_perf_record_type type) -+{ -+ struct drm_i915_perf_record_header header = { type, 0, sizeof(header) }; -+ -+ if ((count - *offset) < header.size) -+ return -ENOSPC; -+ -+ if (copy_to_user(buf + *offset, &header, sizeof(header))) -+ return -EFAULT; -+ -+ (*offset) += header.size; -+ -+ return 0; -+} -+ -+/** -+ * append_oa_sample - Copies single OA report into userspace read() buffer. -+ * @stream: An i915-perf stream opened for OA metrics -+ * @buf: destination buffer given by userspace -+ * @count: the number of bytes userspace wants to read -+ * @offset: (inout): the current position for writing into @buf -+ * @report: A single OA report to (optionally) include as part of the sample -+ * -+ * The contents of a sample are configured through `DRM_I915_PERF_PROP_SAMPLE_*` -+ * properties when opening a stream, tracked as `stream->sample_flags`. This -+ * function copies the requested components of a single sample to the given -+ * read() @buf. -+ * -+ * The @buf @offset will only be updated on success. -+ * -+ * Returns: 0 on success, negative error code on failure. -+ */ -+static int append_oa_sample(struct i915_perf_stream *stream, -+ char __user *buf, -+ size_t count, -+ size_t *offset, -+ const u8 *report) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ int report_size = dev_priv->perf.oa.oa_buffer.format_size; -+ struct drm_i915_perf_record_header header; -+ u32 sample_flags = stream->sample_flags; -+ -+ header.type = DRM_I915_PERF_RECORD_SAMPLE; -+ header.pad = 0; -+ header.size = stream->sample_size; -+ -+ if ((count - *offset) < header.size) -+ return -ENOSPC; -+ -+ buf += *offset; -+ if (copy_to_user(buf, &header, sizeof(header))) -+ return -EFAULT; -+ buf += sizeof(header); -+ -+ if (sample_flags & SAMPLE_OA_REPORT) { -+ if (copy_to_user(buf, report, report_size)) -+ return -EFAULT; -+ } -+ -+ (*offset) += header.size; -+ -+ return 0; -+} -+ -+/** -+ * Copies all buffered OA reports into userspace read() buffer. -+ * @stream: An i915-perf stream opened for OA metrics -+ * @buf: destination buffer given by userspace -+ * @count: the number of bytes userspace wants to read -+ * @offset: (inout): the current position for writing into @buf -+ * -+ * Notably any error condition resulting in a short read (-%ENOSPC or -+ * -%EFAULT) will be returned even though one or more records may -+ * have been successfully copied. In this case it's up to the caller -+ * to decide if the error should be squashed before returning to -+ * userspace. -+ * -+ * Note: reports are consumed from the head, and appended to the -+ * tail, so the tail chases the head?... If you think that's mad -+ * and back-to-front you're not alone, but this follows the -+ * Gen PRM naming convention. -+ * -+ * Returns: 0 on success, negative error code on failure. -+ */ -+static int gen8_append_oa_reports(struct i915_perf_stream *stream, -+ char __user *buf, -+ size_t count, -+ size_t *offset) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ int report_size = dev_priv->perf.oa.oa_buffer.format_size; -+ u8 *oa_buf_base = dev_priv->perf.oa.oa_buffer.vaddr; -+ u32 gtt_offset = i915_ggtt_offset(dev_priv->perf.oa.oa_buffer.vma); -+ u32 mask = (OA_BUFFER_SIZE - 1); -+ size_t start_offset = *offset; -+ unsigned long flags; -+ unsigned int aged_tail_idx; -+ u32 head, tail; -+ u32 taken; -+ int ret = 0; -+ -+ if (WARN_ON(!stream->enabled)) -+ return -EIO; -+ -+ spin_lock_irqsave(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); -+ -+ head = dev_priv->perf.oa.oa_buffer.head; -+ aged_tail_idx = dev_priv->perf.oa.oa_buffer.aged_tail_idx; -+ tail = dev_priv->perf.oa.oa_buffer.tails[aged_tail_idx].offset; -+ -+ spin_unlock_irqrestore(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); -+ -+ /* -+ * An invalid tail pointer here means we're still waiting for the poll -+ * hrtimer callback to give us a pointer -+ */ -+ if (tail == INVALID_TAIL_PTR) -+ return -EAGAIN; -+ -+ /* -+ * NB: oa_buffer.head/tail include the gtt_offset which we don't want -+ * while indexing relative to oa_buf_base. -+ */ -+ head -= gtt_offset; -+ tail -= gtt_offset; -+ -+ /* -+ * An out of bounds or misaligned head or tail pointer implies a driver -+ * bug since we validate + align the tail pointers we read from the -+ * hardware and we are in full control of the head pointer which should -+ * only be incremented by multiples of the report size (notably also -+ * all a power of two). -+ */ -+ if (WARN_ONCE(head > OA_BUFFER_SIZE || head % report_size || -+ tail > OA_BUFFER_SIZE || tail % report_size, -+ "Inconsistent OA buffer pointers: head = %u, tail = %u\n", -+ head, tail)) -+ return -EIO; -+ -+ -+ for (/* none */; -+ (taken = OA_TAKEN(tail, head)); -+ head = (head + report_size) & mask) { -+ u8 *report = oa_buf_base + head; -+ u32 *report32 = (void *)report; -+ u32 ctx_id; -+ u32 reason; -+ -+ /* -+ * All the report sizes factor neatly into the buffer -+ * size so we never expect to see a report split -+ * between the beginning and end of the buffer. -+ * -+ * Given the initial alignment check a misalignment -+ * here would imply a driver bug that would result -+ * in an overrun. -+ */ -+ if (WARN_ON((OA_BUFFER_SIZE - head) < report_size)) { -+ DRM_ERROR("Spurious OA head ptr: non-integral report offset\n"); -+ break; -+ } -+ -+ /* -+ * The reason field includes flags identifying what -+ * triggered this specific report (mostly timer -+ * triggered or e.g. due to a context switch). -+ * -+ * This field is never expected to be zero so we can -+ * check that the report isn't invalid before copying -+ * it to userspace... -+ */ -+ reason = ((report32[0] >> OAREPORT_REASON_SHIFT) & -+ OAREPORT_REASON_MASK); -+ if (reason == 0) { -+ if (__ratelimit(&dev_priv->perf.oa.spurious_report_rs)) -+ DRM_NOTE("Skipping spurious, invalid OA report\n"); -+ continue; -+ } -+ -+ ctx_id = report32[2] & dev_priv->perf.oa.specific_ctx_id_mask; -+ -+ /* -+ * Squash whatever is in the CTX_ID field if it's marked as -+ * invalid to be sure we avoid false-positive, single-context -+ * filtering below... -+ * -+ * Note: that we don't clear the valid_ctx_bit so userspace can -+ * understand that the ID has been squashed by the kernel. -+ */ -+ if (!(report32[0] & dev_priv->perf.oa.gen8_valid_ctx_bit)) -+ ctx_id = report32[2] = INVALID_CTX_ID; -+ -+ /* -+ * NB: For Gen 8 the OA unit no longer supports clock gating -+ * off for a specific context and the kernel can't securely -+ * stop the counters from updating as system-wide / global -+ * values. -+ * -+ * Automatic reports now include a context ID so reports can be -+ * filtered on the cpu but it's not worth trying to -+ * automatically subtract/hide counter progress for other -+ * contexts while filtering since we can't stop userspace -+ * issuing MI_REPORT_PERF_COUNT commands which would still -+ * provide a side-band view of the real values. -+ * -+ * To allow userspace (such as Mesa/GL_INTEL_performance_query) -+ * to normalize counters for a single filtered context then it -+ * needs be forwarded bookend context-switch reports so that it -+ * can track switches in between MI_REPORT_PERF_COUNT commands -+ * and can itself subtract/ignore the progress of counters -+ * associated with other contexts. Note that the hardware -+ * automatically triggers reports when switching to a new -+ * context which are tagged with the ID of the newly active -+ * context. To avoid the complexity (and likely fragility) of -+ * reading ahead while parsing reports to try and minimize -+ * forwarding redundant context switch reports (i.e. between -+ * other, unrelated contexts) we simply elect to forward them -+ * all. -+ * -+ * We don't rely solely on the reason field to identify context -+ * switches since it's not-uncommon for periodic samples to -+ * identify a switch before any 'context switch' report. -+ */ -+ if (!dev_priv->perf.oa.exclusive_stream->ctx || -+ dev_priv->perf.oa.specific_ctx_id == ctx_id || -+ (dev_priv->perf.oa.oa_buffer.last_ctx_id == -+ dev_priv->perf.oa.specific_ctx_id) || -+ reason & OAREPORT_REASON_CTX_SWITCH) { -+ -+ /* -+ * While filtering for a single context we avoid -+ * leaking the IDs of other contexts. -+ */ -+ if (dev_priv->perf.oa.exclusive_stream->ctx && -+ dev_priv->perf.oa.specific_ctx_id != ctx_id) { -+ report32[2] = INVALID_CTX_ID; -+ } -+ -+ ret = append_oa_sample(stream, buf, count, offset, -+ report); -+ if (ret) -+ break; -+ -+ dev_priv->perf.oa.oa_buffer.last_ctx_id = ctx_id; -+ } -+ -+ /* -+ * The above reason field sanity check is based on -+ * the assumption that the OA buffer is initially -+ * zeroed and we reset the field after copying so the -+ * check is still meaningful once old reports start -+ * being overwritten. -+ */ -+ report32[0] = 0; -+ } -+ -+ if (start_offset != *offset) { -+ spin_lock_irqsave(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); -+ -+ /* -+ * We removed the gtt_offset for the copy loop above, indexing -+ * relative to oa_buf_base so put back here... -+ */ -+ head += gtt_offset; -+ -+ I915_WRITE(GEN8_OAHEADPTR, head & GEN8_OAHEADPTR_MASK); -+ dev_priv->perf.oa.oa_buffer.head = head; -+ -+ spin_unlock_irqrestore(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); -+ } -+ -+ return ret; -+} -+ -+/** -+ * gen8_oa_read - copy status records then buffered OA reports -+ * @stream: An i915-perf stream opened for OA metrics -+ * @buf: destination buffer given by userspace -+ * @count: the number of bytes userspace wants to read -+ * @offset: (inout): the current position for writing into @buf -+ * -+ * Checks OA unit status registers and if necessary appends corresponding -+ * status records for userspace (such as for a buffer full condition) and then -+ * initiate appending any buffered OA reports. -+ * -+ * Updates @offset according to the number of bytes successfully copied into -+ * the userspace buffer. -+ * -+ * NB: some data may be successfully copied to the userspace buffer -+ * even if an error is returned, and this is reflected in the -+ * updated @offset. -+ * -+ * Returns: zero on success or a negative error code -+ */ -+static int gen8_oa_read(struct i915_perf_stream *stream, -+ char __user *buf, -+ size_t count, -+ size_t *offset) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ u32 oastatus; -+ int ret; -+ -+ if (WARN_ON(!dev_priv->perf.oa.oa_buffer.vaddr)) -+ return -EIO; -+ -+ oastatus = I915_READ(GEN8_OASTATUS); -+ -+ /* -+ * We treat OABUFFER_OVERFLOW as a significant error: -+ * -+ * Although theoretically we could handle this more gracefully -+ * sometimes, some Gens don't correctly suppress certain -+ * automatically triggered reports in this condition and so we -+ * have to assume that old reports are now being trampled -+ * over. -+ * -+ * Considering how we don't currently give userspace control -+ * over the OA buffer size and always configure a large 16MB -+ * buffer, then a buffer overflow does anyway likely indicate -+ * that something has gone quite badly wrong. -+ */ -+ if (oastatus & GEN8_OASTATUS_OABUFFER_OVERFLOW) { -+ ret = append_oa_status(stream, buf, count, offset, -+ DRM_I915_PERF_RECORD_OA_BUFFER_LOST); -+ if (ret) -+ return ret; -+ -+ DRM_DEBUG("OA buffer overflow (exponent = %d): force restart\n", -+ dev_priv->perf.oa.period_exponent); -+ -+ dev_priv->perf.oa.ops.oa_disable(stream); -+ dev_priv->perf.oa.ops.oa_enable(stream); -+ -+ /* -+ * Note: .oa_enable() is expected to re-init the oabuffer and -+ * reset GEN8_OASTATUS for us -+ */ -+ oastatus = I915_READ(GEN8_OASTATUS); -+ } -+ -+ if (oastatus & GEN8_OASTATUS_REPORT_LOST) { -+ ret = append_oa_status(stream, buf, count, offset, -+ DRM_I915_PERF_RECORD_OA_REPORT_LOST); -+ if (ret) -+ return ret; -+ I915_WRITE(GEN8_OASTATUS, -+ oastatus & ~GEN8_OASTATUS_REPORT_LOST); -+ } -+ -+ return gen8_append_oa_reports(stream, buf, count, offset); -+} -+ -+/** -+ * Copies all buffered OA reports into userspace read() buffer. -+ * @stream: An i915-perf stream opened for OA metrics -+ * @buf: destination buffer given by userspace -+ * @count: the number of bytes userspace wants to read -+ * @offset: (inout): the current position for writing into @buf -+ * -+ * Notably any error condition resulting in a short read (-%ENOSPC or -+ * -%EFAULT) will be returned even though one or more records may -+ * have been successfully copied. In this case it's up to the caller -+ * to decide if the error should be squashed before returning to -+ * userspace. -+ * -+ * Note: reports are consumed from the head, and appended to the -+ * tail, so the tail chases the head?... If you think that's mad -+ * and back-to-front you're not alone, but this follows the -+ * Gen PRM naming convention. -+ * -+ * Returns: 0 on success, negative error code on failure. -+ */ -+static int gen7_append_oa_reports(struct i915_perf_stream *stream, -+ char __user *buf, -+ size_t count, -+ size_t *offset) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ int report_size = dev_priv->perf.oa.oa_buffer.format_size; -+ u8 *oa_buf_base = dev_priv->perf.oa.oa_buffer.vaddr; -+ u32 gtt_offset = i915_ggtt_offset(dev_priv->perf.oa.oa_buffer.vma); -+ u32 mask = (OA_BUFFER_SIZE - 1); -+ size_t start_offset = *offset; -+ unsigned long flags; -+ unsigned int aged_tail_idx; -+ u32 head, tail; -+ u32 taken; -+ int ret = 0; -+ -+ if (WARN_ON(!stream->enabled)) -+ return -EIO; -+ -+ spin_lock_irqsave(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); -+ -+ head = dev_priv->perf.oa.oa_buffer.head; -+ aged_tail_idx = dev_priv->perf.oa.oa_buffer.aged_tail_idx; -+ tail = dev_priv->perf.oa.oa_buffer.tails[aged_tail_idx].offset; -+ -+ spin_unlock_irqrestore(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); -+ -+ /* An invalid tail pointer here means we're still waiting for the poll -+ * hrtimer callback to give us a pointer -+ */ -+ if (tail == INVALID_TAIL_PTR) -+ return -EAGAIN; -+ -+ /* NB: oa_buffer.head/tail include the gtt_offset which we don't want -+ * while indexing relative to oa_buf_base. -+ */ -+ head -= gtt_offset; -+ tail -= gtt_offset; -+ -+ /* An out of bounds or misaligned head or tail pointer implies a driver -+ * bug since we validate + align the tail pointers we read from the -+ * hardware and we are in full control of the head pointer which should -+ * only be incremented by multiples of the report size (notably also -+ * all a power of two). -+ */ -+ if (WARN_ONCE(head > OA_BUFFER_SIZE || head % report_size || -+ tail > OA_BUFFER_SIZE || tail % report_size, -+ "Inconsistent OA buffer pointers: head = %u, tail = %u\n", -+ head, tail)) -+ return -EIO; -+ -+ -+ for (/* none */; -+ (taken = OA_TAKEN(tail, head)); -+ head = (head + report_size) & mask) { -+ u8 *report = oa_buf_base + head; -+ u32 *report32 = (void *)report; -+ -+ /* All the report sizes factor neatly into the buffer -+ * size so we never expect to see a report split -+ * between the beginning and end of the buffer. -+ * -+ * Given the initial alignment check a misalignment -+ * here would imply a driver bug that would result -+ * in an overrun. -+ */ -+ if (WARN_ON((OA_BUFFER_SIZE - head) < report_size)) { -+ DRM_ERROR("Spurious OA head ptr: non-integral report offset\n"); -+ break; -+ } -+ -+ /* The report-ID field for periodic samples includes -+ * some undocumented flags related to what triggered -+ * the report and is never expected to be zero so we -+ * can check that the report isn't invalid before -+ * copying it to userspace... -+ */ -+ if (report32[0] == 0) { -+ if (__ratelimit(&dev_priv->perf.oa.spurious_report_rs)) -+ DRM_NOTE("Skipping spurious, invalid OA report\n"); -+ continue; -+ } -+ -+ ret = append_oa_sample(stream, buf, count, offset, report); -+ if (ret) -+ break; -+ -+ /* The above report-id field sanity check is based on -+ * the assumption that the OA buffer is initially -+ * zeroed and we reset the field after copying so the -+ * check is still meaningful once old reports start -+ * being overwritten. -+ */ -+ report32[0] = 0; -+ } -+ -+ if (start_offset != *offset) { -+ spin_lock_irqsave(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); -+ -+ /* We removed the gtt_offset for the copy loop above, indexing -+ * relative to oa_buf_base so put back here... -+ */ -+ head += gtt_offset; -+ -+ I915_WRITE(GEN7_OASTATUS2, -+ ((head & GEN7_OASTATUS2_HEAD_MASK) | -+ GEN7_OASTATUS2_MEM_SELECT_GGTT)); -+ dev_priv->perf.oa.oa_buffer.head = head; -+ -+ spin_unlock_irqrestore(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); -+ } -+ -+ return ret; -+} -+ -+/** -+ * gen7_oa_read - copy status records then buffered OA reports -+ * @stream: An i915-perf stream opened for OA metrics -+ * @buf: destination buffer given by userspace -+ * @count: the number of bytes userspace wants to read -+ * @offset: (inout): the current position for writing into @buf -+ * -+ * Checks Gen 7 specific OA unit status registers and if necessary appends -+ * corresponding status records for userspace (such as for a buffer full -+ * condition) and then initiate appending any buffered OA reports. -+ * -+ * Updates @offset according to the number of bytes successfully copied into -+ * the userspace buffer. -+ * -+ * Returns: zero on success or a negative error code -+ */ -+static int gen7_oa_read(struct i915_perf_stream *stream, -+ char __user *buf, -+ size_t count, -+ size_t *offset) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ u32 oastatus1; -+ int ret; -+ -+ if (WARN_ON(!dev_priv->perf.oa.oa_buffer.vaddr)) -+ return -EIO; -+ -+ oastatus1 = I915_READ(GEN7_OASTATUS1); -+ -+ /* XXX: On Haswell we don't have a safe way to clear oastatus1 -+ * bits while the OA unit is enabled (while the tail pointer -+ * may be updated asynchronously) so we ignore status bits -+ * that have already been reported to userspace. -+ */ -+ oastatus1 &= ~dev_priv->perf.oa.gen7_latched_oastatus1; -+ -+ /* We treat OABUFFER_OVERFLOW as a significant error: -+ * -+ * - The status can be interpreted to mean that the buffer is -+ * currently full (with a higher precedence than OA_TAKEN() -+ * which will start to report a near-empty buffer after an -+ * overflow) but it's awkward that we can't clear the status -+ * on Haswell, so without a reset we won't be able to catch -+ * the state again. -+ * -+ * - Since it also implies the HW has started overwriting old -+ * reports it may also affect our sanity checks for invalid -+ * reports when copying to userspace that assume new reports -+ * are being written to cleared memory. -+ * -+ * - In the future we may want to introduce a flight recorder -+ * mode where the driver will automatically maintain a safe -+ * guard band between head/tail, avoiding this overflow -+ * condition, but we avoid the added driver complexity for -+ * now. -+ */ -+ if (unlikely(oastatus1 & GEN7_OASTATUS1_OABUFFER_OVERFLOW)) { -+ ret = append_oa_status(stream, buf, count, offset, -+ DRM_I915_PERF_RECORD_OA_BUFFER_LOST); -+ if (ret) -+ return ret; -+ -+ DRM_DEBUG("OA buffer overflow (exponent = %d): force restart\n", -+ dev_priv->perf.oa.period_exponent); -+ -+ dev_priv->perf.oa.ops.oa_disable(stream); -+ dev_priv->perf.oa.ops.oa_enable(stream); -+ -+ oastatus1 = I915_READ(GEN7_OASTATUS1); -+ } -+ -+ if (unlikely(oastatus1 & GEN7_OASTATUS1_REPORT_LOST)) { -+ ret = append_oa_status(stream, buf, count, offset, -+ DRM_I915_PERF_RECORD_OA_REPORT_LOST); -+ if (ret) -+ return ret; -+ dev_priv->perf.oa.gen7_latched_oastatus1 |= -+ GEN7_OASTATUS1_REPORT_LOST; -+ } -+ -+ return gen7_append_oa_reports(stream, buf, count, offset); -+} -+ -+/** -+ * i915_oa_wait_unlocked - handles blocking IO until OA data available -+ * @stream: An i915-perf stream opened for OA metrics -+ * -+ * Called when userspace tries to read() from a blocking stream FD opened -+ * for OA metrics. It waits until the hrtimer callback finds a non-empty -+ * OA buffer and wakes us. -+ * -+ * Note: it's acceptable to have this return with some false positives -+ * since any subsequent read handling will return -EAGAIN if there isn't -+ * really data ready for userspace yet. -+ * -+ * Returns: zero on success or a negative error code -+ */ -+static int i915_oa_wait_unlocked(struct i915_perf_stream *stream) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ -+ /* We would wait indefinitely if periodic sampling is not enabled */ -+ if (!dev_priv->perf.oa.periodic) -+ return -EIO; -+ -+ return wait_event_interruptible(dev_priv->perf.oa.poll_wq, -+ oa_buffer_check_unlocked(dev_priv)); -+} -+ -+/** -+ * i915_oa_poll_wait - call poll_wait() for an OA stream poll() -+ * @stream: An i915-perf stream opened for OA metrics -+ * @file: An i915 perf stream file -+ * @wait: poll() state table -+ * -+ * For handling userspace polling on an i915 perf stream opened for OA metrics, -+ * this starts a poll_wait with the wait queue that our hrtimer callback wakes -+ * when it sees data ready to read in the circular OA buffer. -+ */ -+static void i915_oa_poll_wait(struct i915_perf_stream *stream, -+ struct file *file, -+ poll_table *wait) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ -+ poll_wait(file, &dev_priv->perf.oa.poll_wq, wait); -+} -+ -+/** -+ * i915_oa_read - just calls through to &i915_oa_ops->read -+ * @stream: An i915-perf stream opened for OA metrics -+ * @buf: destination buffer given by userspace -+ * @count: the number of bytes userspace wants to read -+ * @offset: (inout): the current position for writing into @buf -+ * -+ * Updates @offset according to the number of bytes successfully copied into -+ * the userspace buffer. -+ * -+ * Returns: zero on success or a negative error code -+ */ -+static int i915_oa_read(struct i915_perf_stream *stream, -+ char __user *buf, -+ size_t count, -+ size_t *offset) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ -+ return dev_priv->perf.oa.ops.read(stream, buf, count, offset); -+} -+ -+static struct intel_context *oa_pin_context(struct drm_i915_private *i915, -+ struct i915_gem_context *ctx) -+{ -+ struct intel_engine_cs *engine = i915->engine[RCS0]; -+ struct intel_context *ce; -+ int ret; -+ -+ ret = i915_mutex_lock_interruptible(&i915->drm); -+ if (ret) -+ return ERR_PTR(ret); -+ -+ /* -+ * As the ID is the gtt offset of the context's vma we -+ * pin the vma to ensure the ID remains fixed. -+ * -+ * NB: implied RCS engine... -+ */ -+ ce = intel_context_pin(ctx, engine); -+ mutex_unlock(&i915->drm.struct_mutex); -+ if (IS_ERR(ce)) -+ return ce; -+ -+ i915->perf.oa.pinned_ctx = ce; -+ -+ return ce; -+} -+ -+/** -+ * oa_get_render_ctx_id - determine and hold ctx hw id -+ * @stream: An i915-perf stream opened for OA metrics -+ * -+ * Determine the render context hw id, and ensure it remains fixed for the -+ * lifetime of the stream. This ensures that we don't have to worry about -+ * updating the context ID in OACONTROL on the fly. -+ * -+ * Returns: zero on success or a negative error code -+ */ -+static int oa_get_render_ctx_id(struct i915_perf_stream *stream) -+{ -+ struct drm_i915_private *i915 = stream->dev_priv; -+ struct intel_context *ce; -+ -+ ce = oa_pin_context(i915, stream->ctx); -+ if (IS_ERR(ce)) -+ return PTR_ERR(ce); -+ -+ switch (INTEL_GEN(i915)) { -+ case 7: { -+ /* -+ * On Haswell we don't do any post processing of the reports -+ * and don't need to use the mask. -+ */ -+ i915->perf.oa.specific_ctx_id = i915_ggtt_offset(ce->state); -+ i915->perf.oa.specific_ctx_id_mask = 0; -+ break; -+ } -+ -+ case 8: -+ case 9: -+ case 10: -+ if (USES_GUC_SUBMISSION(i915)) { -+ /* -+ * When using GuC, the context descriptor we write in -+ * i915 is read by GuC and rewritten before it's -+ * actually written into the hardware. The LRCA is -+ * what is put into the context id field of the -+ * context descriptor by GuC. Because it's aligned to -+ * a page, the lower 12bits are always at 0 and -+ * dropped by GuC. They won't be part of the context -+ * ID in the OA reports, so squash those lower bits. -+ */ -+ i915->perf.oa.specific_ctx_id = -+ lower_32_bits(ce->lrc_desc) >> 12; -+ -+ /* -+ * GuC uses the top bit to signal proxy submission, so -+ * ignore that bit. -+ */ -+ i915->perf.oa.specific_ctx_id_mask = -+ (1U << (GEN8_CTX_ID_WIDTH - 1)) - 1; -+ } else { -+ i915->perf.oa.specific_ctx_id_mask = -+ (1U << GEN8_CTX_ID_WIDTH) - 1; -+ i915->perf.oa.specific_ctx_id = -+ upper_32_bits(ce->lrc_desc); -+ i915->perf.oa.specific_ctx_id &= -+ i915->perf.oa.specific_ctx_id_mask; -+ } -+ break; -+ -+ case 11: { -+ i915->perf.oa.specific_ctx_id_mask = -+ ((1U << GEN11_SW_CTX_ID_WIDTH) - 1) << (GEN11_SW_CTX_ID_SHIFT - 32) | -+ ((1U << GEN11_ENGINE_INSTANCE_WIDTH) - 1) << (GEN11_ENGINE_INSTANCE_SHIFT - 32) | -+ ((1 << GEN11_ENGINE_CLASS_WIDTH) - 1) << (GEN11_ENGINE_CLASS_SHIFT - 32); -+ i915->perf.oa.specific_ctx_id = upper_32_bits(ce->lrc_desc); -+ i915->perf.oa.specific_ctx_id &= -+ i915->perf.oa.specific_ctx_id_mask; -+ break; -+ } -+ -+ default: -+ MISSING_CASE(INTEL_GEN(i915)); -+ } -+ -+ DRM_DEBUG_DRIVER("filtering on ctx_id=0x%x ctx_id_mask=0x%x\n", -+ i915->perf.oa.specific_ctx_id, -+ i915->perf.oa.specific_ctx_id_mask); -+ -+ return 0; -+} -+ -+/** -+ * oa_put_render_ctx_id - counterpart to oa_get_render_ctx_id releases hold -+ * @stream: An i915-perf stream opened for OA metrics -+ * -+ * In case anything needed doing to ensure the context HW ID would remain valid -+ * for the lifetime of the stream, then that can be undone here. -+ */ -+static void oa_put_render_ctx_id(struct i915_perf_stream *stream) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ struct intel_context *ce; -+ -+ dev_priv->perf.oa.specific_ctx_id = INVALID_CTX_ID; -+ dev_priv->perf.oa.specific_ctx_id_mask = 0; -+ -+ ce = fetch_and_zero(&dev_priv->perf.oa.pinned_ctx); -+ if (ce) { -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ intel_context_unpin(ce); -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ } -+} -+ -+static void -+free_oa_buffer(struct drm_i915_private *i915) -+{ -+ mutex_lock(&i915->drm.struct_mutex); -+ -+ i915_vma_unpin_and_release(&i915->perf.oa.oa_buffer.vma, -+ I915_VMA_RELEASE_MAP); -+ -+ mutex_unlock(&i915->drm.struct_mutex); -+ -+ i915->perf.oa.oa_buffer.vaddr = NULL; -+} -+ -+static void i915_oa_stream_destroy(struct i915_perf_stream *stream) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ -+ BUG_ON(stream != dev_priv->perf.oa.exclusive_stream); -+ -+ /* -+ * Unset exclusive_stream first, it will be checked while disabling -+ * the metric set on gen8+. -+ */ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ dev_priv->perf.oa.exclusive_stream = NULL; -+ dev_priv->perf.oa.ops.disable_metric_set(dev_priv); -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ -+ free_oa_buffer(dev_priv); -+ -+ intel_uncore_forcewake_put(&dev_priv->uncore, FORCEWAKE_ALL); -+ intel_runtime_pm_put(dev_priv, stream->wakeref); -+ -+ if (stream->ctx) -+ oa_put_render_ctx_id(stream); -+ -+ put_oa_config(dev_priv, stream->oa_config); -+ -+ if (dev_priv->perf.oa.spurious_report_rs.missed) { -+ DRM_NOTE("%d spurious OA report notices suppressed due to ratelimiting\n", -+ dev_priv->perf.oa.spurious_report_rs.missed); -+ } -+} -+ -+static void gen7_init_oa_buffer(struct drm_i915_private *dev_priv) -+{ -+ u32 gtt_offset = i915_ggtt_offset(dev_priv->perf.oa.oa_buffer.vma); -+ unsigned long flags; -+ -+ spin_lock_irqsave(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); -+ -+ /* Pre-DevBDW: OABUFFER must be set with counters off, -+ * before OASTATUS1, but after OASTATUS2 -+ */ -+ I915_WRITE(GEN7_OASTATUS2, -+ gtt_offset | GEN7_OASTATUS2_MEM_SELECT_GGTT); /* head */ -+ dev_priv->perf.oa.oa_buffer.head = gtt_offset; -+ -+ I915_WRITE(GEN7_OABUFFER, gtt_offset); -+ -+ I915_WRITE(GEN7_OASTATUS1, gtt_offset | OABUFFER_SIZE_16M); /* tail */ -+ -+ /* Mark that we need updated tail pointers to read from... */ -+ dev_priv->perf.oa.oa_buffer.tails[0].offset = INVALID_TAIL_PTR; -+ dev_priv->perf.oa.oa_buffer.tails[1].offset = INVALID_TAIL_PTR; -+ -+ spin_unlock_irqrestore(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); -+ -+ /* On Haswell we have to track which OASTATUS1 flags we've -+ * already seen since they can't be cleared while periodic -+ * sampling is enabled. -+ */ -+ dev_priv->perf.oa.gen7_latched_oastatus1 = 0; -+ -+ /* NB: although the OA buffer will initially be allocated -+ * zeroed via shmfs (and so this memset is redundant when -+ * first allocating), we may re-init the OA buffer, either -+ * when re-enabling a stream or in error/reset paths. -+ * -+ * The reason we clear the buffer for each re-init is for the -+ * sanity check in gen7_append_oa_reports() that looks at the -+ * report-id field to make sure it's non-zero which relies on -+ * the assumption that new reports are being written to zeroed -+ * memory... -+ */ -+ memset(dev_priv->perf.oa.oa_buffer.vaddr, 0, OA_BUFFER_SIZE); -+ -+ /* Maybe make ->pollin per-stream state if we support multiple -+ * concurrent streams in the future. -+ */ -+ dev_priv->perf.oa.pollin = false; -+} -+ -+static void gen8_init_oa_buffer(struct drm_i915_private *dev_priv) -+{ -+ u32 gtt_offset = i915_ggtt_offset(dev_priv->perf.oa.oa_buffer.vma); -+ unsigned long flags; -+ -+ spin_lock_irqsave(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); -+ -+ I915_WRITE(GEN8_OASTATUS, 0); -+ I915_WRITE(GEN8_OAHEADPTR, gtt_offset); -+ dev_priv->perf.oa.oa_buffer.head = gtt_offset; -+ -+ I915_WRITE(GEN8_OABUFFER_UDW, 0); -+ -+ /* -+ * PRM says: -+ * -+ * "This MMIO must be set before the OATAILPTR -+ * register and after the OAHEADPTR register. This is -+ * to enable proper functionality of the overflow -+ * bit." -+ */ -+ I915_WRITE(GEN8_OABUFFER, gtt_offset | -+ OABUFFER_SIZE_16M | GEN8_OABUFFER_MEM_SELECT_GGTT); -+ I915_WRITE(GEN8_OATAILPTR, gtt_offset & GEN8_OATAILPTR_MASK); -+ -+ /* Mark that we need updated tail pointers to read from... */ -+ dev_priv->perf.oa.oa_buffer.tails[0].offset = INVALID_TAIL_PTR; -+ dev_priv->perf.oa.oa_buffer.tails[1].offset = INVALID_TAIL_PTR; -+ -+ /* -+ * Reset state used to recognise context switches, affecting which -+ * reports we will forward to userspace while filtering for a single -+ * context. -+ */ -+ dev_priv->perf.oa.oa_buffer.last_ctx_id = INVALID_CTX_ID; -+ -+ spin_unlock_irqrestore(&dev_priv->perf.oa.oa_buffer.ptr_lock, flags); -+ -+ /* -+ * NB: although the OA buffer will initially be allocated -+ * zeroed via shmfs (and so this memset is redundant when -+ * first allocating), we may re-init the OA buffer, either -+ * when re-enabling a stream or in error/reset paths. -+ * -+ * The reason we clear the buffer for each re-init is for the -+ * sanity check in gen8_append_oa_reports() that looks at the -+ * reason field to make sure it's non-zero which relies on -+ * the assumption that new reports are being written to zeroed -+ * memory... -+ */ -+ memset(dev_priv->perf.oa.oa_buffer.vaddr, 0, OA_BUFFER_SIZE); -+ -+ /* -+ * Maybe make ->pollin per-stream state if we support multiple -+ * concurrent streams in the future. -+ */ -+ dev_priv->perf.oa.pollin = false; -+} -+ -+static int alloc_oa_buffer(struct drm_i915_private *dev_priv) -+{ -+ struct drm_i915_gem_object *bo; -+ struct i915_vma *vma; -+ int ret; -+ -+ if (WARN_ON(dev_priv->perf.oa.oa_buffer.vma)) -+ return -ENODEV; -+ -+ ret = i915_mutex_lock_interruptible(&dev_priv->drm); -+ if (ret) -+ return ret; -+ -+ BUILD_BUG_ON_NOT_POWER_OF_2(OA_BUFFER_SIZE); -+ BUILD_BUG_ON(OA_BUFFER_SIZE < SZ_128K || OA_BUFFER_SIZE > SZ_16M); -+ -+ bo = i915_gem_object_create(dev_priv, OA_BUFFER_SIZE); -+ if (IS_ERR(bo)) { -+ DRM_ERROR("Failed to allocate OA buffer\n"); -+ ret = PTR_ERR(bo); -+ goto unlock; -+ } -+ -+ i915_gem_object_set_cache_coherency(bo, I915_CACHE_LLC); -+ -+ /* PreHSW required 512K alignment, HSW requires 16M */ -+ vma = i915_gem_object_ggtt_pin(bo, NULL, 0, SZ_16M, 0); -+ if (IS_ERR(vma)) { -+ ret = PTR_ERR(vma); -+ goto err_unref; -+ } -+ dev_priv->perf.oa.oa_buffer.vma = vma; -+ -+ dev_priv->perf.oa.oa_buffer.vaddr = -+ i915_gem_object_pin_map(bo, I915_MAP_WB); -+ if (IS_ERR(dev_priv->perf.oa.oa_buffer.vaddr)) { -+ ret = PTR_ERR(dev_priv->perf.oa.oa_buffer.vaddr); -+ goto err_unpin; -+ } -+ -+ DRM_DEBUG_DRIVER("OA Buffer initialized, gtt offset = 0x%x, vaddr = %p\n", -+ i915_ggtt_offset(dev_priv->perf.oa.oa_buffer.vma), -+ dev_priv->perf.oa.oa_buffer.vaddr); -+ -+ goto unlock; -+ -+err_unpin: -+ __i915_vma_unpin(vma); -+ -+err_unref: -+ i915_gem_object_put(bo); -+ -+ dev_priv->perf.oa.oa_buffer.vaddr = NULL; -+ dev_priv->perf.oa.oa_buffer.vma = NULL; -+ -+unlock: -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ return ret; -+} -+ -+static void config_oa_regs(struct drm_i915_private *dev_priv, -+ const struct i915_oa_reg *regs, -+ u32 n_regs) -+{ -+ u32 i; -+ -+ for (i = 0; i < n_regs; i++) { -+ const struct i915_oa_reg *reg = regs + i; -+ -+ I915_WRITE(reg->addr, reg->value); -+ } -+} -+ -+static int hsw_enable_metric_set(struct i915_perf_stream *stream) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ const struct i915_oa_config *oa_config = stream->oa_config; -+ -+ /* PRM: -+ * -+ * OA unit is using “crclk” for its functionality. When trunk -+ * level clock gating takes place, OA clock would be gated, -+ * unable to count the events from non-render clock domain. -+ * Render clock gating must be disabled when OA is enabled to -+ * count the events from non-render domain. Unit level clock -+ * gating for RCS should also be disabled. -+ */ -+ I915_WRITE(GEN7_MISCCPCTL, (I915_READ(GEN7_MISCCPCTL) & -+ ~GEN7_DOP_CLOCK_GATE_ENABLE)); -+ I915_WRITE(GEN6_UCGCTL1, (I915_READ(GEN6_UCGCTL1) | -+ GEN6_CSUNIT_CLOCK_GATE_DISABLE)); -+ -+ config_oa_regs(dev_priv, oa_config->mux_regs, oa_config->mux_regs_len); -+ -+ /* It apparently takes a fairly long time for a new MUX -+ * configuration to be be applied after these register writes. -+ * This delay duration was derived empirically based on the -+ * render_basic config but hopefully it covers the maximum -+ * configuration latency. -+ * -+ * As a fallback, the checks in _append_oa_reports() to skip -+ * invalid OA reports do also seem to work to discard reports -+ * generated before this config has completed - albeit not -+ * silently. -+ * -+ * Unfortunately this is essentially a magic number, since we -+ * don't currently know of a reliable mechanism for predicting -+ * how long the MUX config will take to apply and besides -+ * seeing invalid reports we don't know of a reliable way to -+ * explicitly check that the MUX config has landed. -+ * -+ * It's even possible we've miss characterized the underlying -+ * problem - it just seems like the simplest explanation why -+ * a delay at this location would mitigate any invalid reports. -+ */ -+ usleep_range(15000, 20000); -+ -+ config_oa_regs(dev_priv, oa_config->b_counter_regs, -+ oa_config->b_counter_regs_len); -+ -+ return 0; -+} -+ -+static void hsw_disable_metric_set(struct drm_i915_private *dev_priv) -+{ -+ I915_WRITE(GEN6_UCGCTL1, (I915_READ(GEN6_UCGCTL1) & -+ ~GEN6_CSUNIT_CLOCK_GATE_DISABLE)); -+ I915_WRITE(GEN7_MISCCPCTL, (I915_READ(GEN7_MISCCPCTL) | -+ GEN7_DOP_CLOCK_GATE_ENABLE)); -+ -+ I915_WRITE(GDT_CHICKEN_BITS, (I915_READ(GDT_CHICKEN_BITS) & -+ ~GT_NOA_ENABLE)); -+} -+ -+/* -+ * NB: It must always remain pointer safe to run this even if the OA unit -+ * has been disabled. -+ * -+ * It's fine to put out-of-date values into these per-context registers -+ * in the case that the OA unit has been disabled. -+ */ -+static void -+gen8_update_reg_state_unlocked(struct intel_context *ce, -+ u32 *reg_state, -+ const struct i915_oa_config *oa_config) -+{ -+ struct drm_i915_private *i915 = ce->gem_context->i915; -+ u32 ctx_oactxctrl = i915->perf.oa.ctx_oactxctrl_offset; -+ u32 ctx_flexeu0 = i915->perf.oa.ctx_flexeu0_offset; -+ /* The MMIO offsets for Flex EU registers aren't contiguous */ -+ i915_reg_t flex_regs[] = { -+ EU_PERF_CNTL0, -+ EU_PERF_CNTL1, -+ EU_PERF_CNTL2, -+ EU_PERF_CNTL3, -+ EU_PERF_CNTL4, -+ EU_PERF_CNTL5, -+ EU_PERF_CNTL6, -+ }; -+ int i; -+ -+ CTX_REG(reg_state, ctx_oactxctrl, GEN8_OACTXCONTROL, -+ (i915->perf.oa.period_exponent << GEN8_OA_TIMER_PERIOD_SHIFT) | -+ (i915->perf.oa.periodic ? GEN8_OA_TIMER_ENABLE : 0) | -+ GEN8_OA_COUNTER_RESUME); -+ -+ for (i = 0; i < ARRAY_SIZE(flex_regs); i++) { -+ u32 state_offset = ctx_flexeu0 + i * 2; -+ u32 mmio = i915_mmio_reg_offset(flex_regs[i]); -+ -+ /* -+ * This arbitrary default will select the 'EU FPU0 Pipeline -+ * Active' event. In the future it's anticipated that there -+ * will be an explicit 'No Event' we can select, but not yet... -+ */ -+ u32 value = 0; -+ -+ if (oa_config) { -+ u32 j; -+ -+ for (j = 0; j < oa_config->flex_regs_len; j++) { -+ if (i915_mmio_reg_offset(oa_config->flex_regs[j].addr) == mmio) { -+ value = oa_config->flex_regs[j].value; -+ break; -+ } -+ } -+ } -+ -+ CTX_REG(reg_state, state_offset, flex_regs[i], value); -+ } -+ -+ CTX_REG(reg_state, -+ CTX_R_PWR_CLK_STATE, GEN8_R_PWR_CLK_STATE, -+ gen8_make_rpcs(i915, &ce->sseu)); -+} -+ -+/* -+ * Manages updating the per-context aspects of the OA stream -+ * configuration across all contexts. -+ * -+ * The awkward consideration here is that OACTXCONTROL controls the -+ * exponent for periodic sampling which is primarily used for system -+ * wide profiling where we'd like a consistent sampling period even in -+ * the face of context switches. -+ * -+ * Our approach of updating the register state context (as opposed to -+ * say using a workaround batch buffer) ensures that the hardware -+ * won't automatically reload an out-of-date timer exponent even -+ * transiently before a WA BB could be parsed. -+ * -+ * This function needs to: -+ * - Ensure the currently running context's per-context OA state is -+ * updated -+ * - Ensure that all existing contexts will have the correct per-context -+ * OA state if they are scheduled for use. -+ * - Ensure any new contexts will be initialized with the correct -+ * per-context OA state. -+ * -+ * Note: it's only the RCS/Render context that has any OA state. -+ */ -+static int gen8_configure_all_contexts(struct drm_i915_private *dev_priv, -+ const struct i915_oa_config *oa_config) -+{ -+ struct intel_engine_cs *engine = dev_priv->engine[RCS0]; -+ unsigned int map_type = i915_coherent_map_type(dev_priv); -+ struct i915_gem_context *ctx; -+ struct i915_request *rq; -+ int ret; -+ -+ lockdep_assert_held(&dev_priv->drm.struct_mutex); -+ -+ /* -+ * The OA register config is setup through the context image. This image -+ * might be written to by the GPU on context switch (in particular on -+ * lite-restore). This means we can't safely update a context's image, -+ * if this context is scheduled/submitted to run on the GPU. -+ * -+ * We could emit the OA register config through the batch buffer but -+ * this might leave small interval of time where the OA unit is -+ * configured at an invalid sampling period. -+ * -+ * So far the best way to work around this issue seems to be draining -+ * the GPU from any submitted work. -+ */ -+ ret = i915_gem_wait_for_idle(dev_priv, -+ I915_WAIT_LOCKED, -+ MAX_SCHEDULE_TIMEOUT); -+ if (ret) -+ return ret; -+ -+ /* Update all contexts now that we've stalled the submission. */ -+ list_for_each_entry(ctx, &dev_priv->contexts.list, link) { -+ struct intel_context *ce = intel_context_lookup(ctx, engine); -+ u32 *regs; -+ -+ /* OA settings will be set upon first use */ -+ if (!ce || !ce->state) -+ continue; -+ -+ regs = i915_gem_object_pin_map(ce->state->obj, map_type); -+ if (IS_ERR(regs)) -+ return PTR_ERR(regs); -+ -+ ce->state->obj->mm.dirty = true; -+ regs += LRC_STATE_PN * PAGE_SIZE / sizeof(*regs); -+ -+ gen8_update_reg_state_unlocked(ce, regs, oa_config); -+ -+ i915_gem_object_unpin_map(ce->state->obj); -+ } -+ -+ /* -+ * Apply the configuration by doing one context restore of the edited -+ * context image. -+ */ -+ rq = i915_request_alloc(engine, dev_priv->kernel_context); -+ if (IS_ERR(rq)) -+ return PTR_ERR(rq); -+ -+ i915_request_add(rq); -+ -+ return 0; -+} -+ -+static int gen8_enable_metric_set(struct i915_perf_stream *stream) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ const struct i915_oa_config *oa_config = stream->oa_config; -+ int ret; -+ -+ /* -+ * We disable slice/unslice clock ratio change reports on SKL since -+ * they are too noisy. The HW generates a lot of redundant reports -+ * where the ratio hasn't really changed causing a lot of redundant -+ * work to processes and increasing the chances we'll hit buffer -+ * overruns. -+ * -+ * Although we don't currently use the 'disable overrun' OABUFFER -+ * feature it's worth noting that clock ratio reports have to be -+ * disabled before considering to use that feature since the HW doesn't -+ * correctly block these reports. -+ * -+ * Currently none of the high-level metrics we have depend on knowing -+ * this ratio to normalize. -+ * -+ * Note: This register is not power context saved and restored, but -+ * that's OK considering that we disable RC6 while the OA unit is -+ * enabled. -+ * -+ * The _INCLUDE_CLK_RATIO bit allows the slice/unslice frequency to -+ * be read back from automatically triggered reports, as part of the -+ * RPT_ID field. -+ */ -+ if (IS_GEN_RANGE(dev_priv, 9, 11)) { -+ I915_WRITE(GEN8_OA_DEBUG, -+ _MASKED_BIT_ENABLE(GEN9_OA_DEBUG_DISABLE_CLK_RATIO_REPORTS | -+ GEN9_OA_DEBUG_INCLUDE_CLK_RATIO)); -+ } -+ -+ /* -+ * Update all contexts prior writing the mux configurations as we need -+ * to make sure all slices/subslices are ON before writing to NOA -+ * registers. -+ */ -+ ret = gen8_configure_all_contexts(dev_priv, oa_config); -+ if (ret) -+ return ret; -+ -+ config_oa_regs(dev_priv, oa_config->mux_regs, oa_config->mux_regs_len); -+ -+ config_oa_regs(dev_priv, oa_config->b_counter_regs, -+ oa_config->b_counter_regs_len); -+ -+ return 0; -+} -+ -+static void gen8_disable_metric_set(struct drm_i915_private *dev_priv) -+{ -+ /* Reset all contexts' slices/subslices configurations. */ -+ gen8_configure_all_contexts(dev_priv, NULL); -+ -+ I915_WRITE(GDT_CHICKEN_BITS, (I915_READ(GDT_CHICKEN_BITS) & -+ ~GT_NOA_ENABLE)); -+} -+ -+static void gen10_disable_metric_set(struct drm_i915_private *dev_priv) -+{ -+ /* Reset all contexts' slices/subslices configurations. */ -+ gen8_configure_all_contexts(dev_priv, NULL); -+ -+ /* Make sure we disable noa to save power. */ -+ I915_WRITE(RPM_CONFIG1, -+ I915_READ(RPM_CONFIG1) & ~GEN10_GT_NOA_ENABLE); -+} -+ -+static void gen7_oa_enable(struct i915_perf_stream *stream) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ struct i915_gem_context *ctx = stream->ctx; -+ u32 ctx_id = dev_priv->perf.oa.specific_ctx_id; -+ bool periodic = dev_priv->perf.oa.periodic; -+ u32 period_exponent = dev_priv->perf.oa.period_exponent; -+ u32 report_format = dev_priv->perf.oa.oa_buffer.format; -+ -+ /* -+ * Reset buf pointers so we don't forward reports from before now. -+ * -+ * Think carefully if considering trying to avoid this, since it -+ * also ensures status flags and the buffer itself are cleared -+ * in error paths, and we have checks for invalid reports based -+ * on the assumption that certain fields are written to zeroed -+ * memory which this helps maintains. -+ */ -+ gen7_init_oa_buffer(dev_priv); -+ -+ I915_WRITE(GEN7_OACONTROL, -+ (ctx_id & GEN7_OACONTROL_CTX_MASK) | -+ (period_exponent << -+ GEN7_OACONTROL_TIMER_PERIOD_SHIFT) | -+ (periodic ? GEN7_OACONTROL_TIMER_ENABLE : 0) | -+ (report_format << GEN7_OACONTROL_FORMAT_SHIFT) | -+ (ctx ? GEN7_OACONTROL_PER_CTX_ENABLE : 0) | -+ GEN7_OACONTROL_ENABLE); -+} -+ -+static void gen8_oa_enable(struct i915_perf_stream *stream) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ u32 report_format = dev_priv->perf.oa.oa_buffer.format; -+ -+ /* -+ * Reset buf pointers so we don't forward reports from before now. -+ * -+ * Think carefully if considering trying to avoid this, since it -+ * also ensures status flags and the buffer itself are cleared -+ * in error paths, and we have checks for invalid reports based -+ * on the assumption that certain fields are written to zeroed -+ * memory which this helps maintains. -+ */ -+ gen8_init_oa_buffer(dev_priv); -+ -+ /* -+ * Note: we don't rely on the hardware to perform single context -+ * filtering and instead filter on the cpu based on the context-id -+ * field of reports -+ */ -+ I915_WRITE(GEN8_OACONTROL, (report_format << -+ GEN8_OA_REPORT_FORMAT_SHIFT) | -+ GEN8_OA_COUNTER_ENABLE); -+} -+ -+/** -+ * i915_oa_stream_enable - handle `I915_PERF_IOCTL_ENABLE` for OA stream -+ * @stream: An i915 perf stream opened for OA metrics -+ * -+ * [Re]enables hardware periodic sampling according to the period configured -+ * when opening the stream. This also starts a hrtimer that will periodically -+ * check for data in the circular OA buffer for notifying userspace (e.g. -+ * during a read() or poll()). -+ */ -+static void i915_oa_stream_enable(struct i915_perf_stream *stream) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ -+ dev_priv->perf.oa.ops.oa_enable(stream); -+ -+ if (dev_priv->perf.oa.periodic) -+ hrtimer_start(&dev_priv->perf.oa.poll_check_timer, -+ ns_to_ktime(POLL_PERIOD), -+ HRTIMER_MODE_REL_PINNED); -+} -+ -+static void gen7_oa_disable(struct i915_perf_stream *stream) -+{ -+ struct intel_uncore *uncore = &stream->dev_priv->uncore; -+ -+ intel_uncore_write(uncore, GEN7_OACONTROL, 0); -+ if (intel_wait_for_register(uncore, -+ GEN7_OACONTROL, GEN7_OACONTROL_ENABLE, 0, -+ 50)) -+ DRM_ERROR("wait for OA to be disabled timed out\n"); -+} -+ -+static void gen8_oa_disable(struct i915_perf_stream *stream) -+{ -+ struct intel_uncore *uncore = &stream->dev_priv->uncore; -+ -+ intel_uncore_write(uncore, GEN8_OACONTROL, 0); -+ if (intel_wait_for_register(uncore, -+ GEN8_OACONTROL, GEN8_OA_COUNTER_ENABLE, 0, -+ 50)) -+ DRM_ERROR("wait for OA to be disabled timed out\n"); -+} -+ -+/** -+ * i915_oa_stream_disable - handle `I915_PERF_IOCTL_DISABLE` for OA stream -+ * @stream: An i915 perf stream opened for OA metrics -+ * -+ * Stops the OA unit from periodically writing counter reports into the -+ * circular OA buffer. This also stops the hrtimer that periodically checks for -+ * data in the circular OA buffer, for notifying userspace. -+ */ -+static void i915_oa_stream_disable(struct i915_perf_stream *stream) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ -+ dev_priv->perf.oa.ops.oa_disable(stream); -+ -+ if (dev_priv->perf.oa.periodic) -+ hrtimer_cancel(&dev_priv->perf.oa.poll_check_timer); -+} -+ -+static const struct i915_perf_stream_ops i915_oa_stream_ops = { -+ .destroy = i915_oa_stream_destroy, -+ .enable = i915_oa_stream_enable, -+ .disable = i915_oa_stream_disable, -+ .wait_unlocked = i915_oa_wait_unlocked, -+ .poll_wait = i915_oa_poll_wait, -+ .read = i915_oa_read, -+}; -+ -+/** -+ * i915_oa_stream_init - validate combined props for OA stream and init -+ * @stream: An i915 perf stream -+ * @param: The open parameters passed to `DRM_I915_PERF_OPEN` -+ * @props: The property state that configures stream (individually validated) -+ * -+ * While read_properties_unlocked() validates properties in isolation it -+ * doesn't ensure that the combination necessarily makes sense. -+ * -+ * At this point it has been determined that userspace wants a stream of -+ * OA metrics, but still we need to further validate the combined -+ * properties are OK. -+ * -+ * If the configuration makes sense then we can allocate memory for -+ * a circular OA buffer and apply the requested metric set configuration. -+ * -+ * Returns: zero on success or a negative error code. -+ */ -+static int i915_oa_stream_init(struct i915_perf_stream *stream, -+ struct drm_i915_perf_open_param *param, -+ struct perf_open_properties *props) -+{ -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ int format_size; -+ int ret; -+ -+ /* If the sysfs metrics/ directory wasn't registered for some -+ * reason then don't let userspace try their luck with config -+ * IDs -+ */ -+ if (!dev_priv->perf.metrics_kobj) { -+ DRM_DEBUG("OA metrics weren't advertised via sysfs\n"); -+ return -EINVAL; -+ } -+ -+ if (!(props->sample_flags & SAMPLE_OA_REPORT)) { -+ DRM_DEBUG("Only OA report sampling supported\n"); -+ return -EINVAL; -+ } -+ -+ if (!dev_priv->perf.oa.ops.enable_metric_set) { -+ DRM_DEBUG("OA unit not supported\n"); -+ return -ENODEV; -+ } -+ -+ /* To avoid the complexity of having to accurately filter -+ * counter reports and marshal to the appropriate client -+ * we currently only allow exclusive access -+ */ -+ if (dev_priv->perf.oa.exclusive_stream) { -+ DRM_DEBUG("OA unit already in use\n"); -+ return -EBUSY; -+ } -+ -+ if (!props->oa_format) { -+ DRM_DEBUG("OA report format not specified\n"); -+ return -EINVAL; -+ } -+ -+ /* We set up some ratelimit state to potentially throttle any _NOTES -+ * about spurious, invalid OA reports which we don't forward to -+ * userspace. -+ * -+ * The initialization is associated with opening the stream (not driver -+ * init) considering we print a _NOTE about any throttling when closing -+ * the stream instead of waiting until driver _fini which no one would -+ * ever see. -+ * -+ * Using the same limiting factors as printk_ratelimit() -+ */ -+ ratelimit_state_init(&dev_priv->perf.oa.spurious_report_rs, -+ 5 * HZ, 10); -+ /* Since we use a DRM_NOTE for spurious reports it would be -+ * inconsistent to let __ratelimit() automatically print a warning for -+ * throttling. -+ */ -+ ratelimit_set_flags(&dev_priv->perf.oa.spurious_report_rs, -+ RATELIMIT_MSG_ON_RELEASE); -+ -+ stream->sample_size = sizeof(struct drm_i915_perf_record_header); -+ -+ format_size = dev_priv->perf.oa.oa_formats[props->oa_format].size; -+ -+ stream->sample_flags |= SAMPLE_OA_REPORT; -+ stream->sample_size += format_size; -+ -+ dev_priv->perf.oa.oa_buffer.format_size = format_size; -+ if (WARN_ON(dev_priv->perf.oa.oa_buffer.format_size == 0)) -+ return -EINVAL; -+ -+ dev_priv->perf.oa.oa_buffer.format = -+ dev_priv->perf.oa.oa_formats[props->oa_format].format; -+ -+ dev_priv->perf.oa.periodic = props->oa_periodic; -+ if (dev_priv->perf.oa.periodic) -+ dev_priv->perf.oa.period_exponent = props->oa_period_exponent; -+ -+ if (stream->ctx) { -+ ret = oa_get_render_ctx_id(stream); -+ if (ret) { -+ DRM_DEBUG("Invalid context id to filter with\n"); -+ return ret; -+ } -+ } -+ -+ ret = get_oa_config(dev_priv, props->metrics_set, &stream->oa_config); -+ if (ret) { -+ DRM_DEBUG("Invalid OA config id=%i\n", props->metrics_set); -+ goto err_config; -+ } -+ -+ /* PRM - observability performance counters: -+ * -+ * OACONTROL, performance counter enable, note: -+ * -+ * "When this bit is set, in order to have coherent counts, -+ * RC6 power state and trunk clock gating must be disabled. -+ * This can be achieved by programming MMIO registers as -+ * 0xA094=0 and 0xA090[31]=1" -+ * -+ * In our case we are expecting that taking pm + FORCEWAKE -+ * references will effectively disable RC6. -+ */ -+ stream->wakeref = intel_runtime_pm_get(dev_priv); -+ intel_uncore_forcewake_get(&dev_priv->uncore, FORCEWAKE_ALL); -+ -+ ret = alloc_oa_buffer(dev_priv); -+ if (ret) -+ goto err_oa_buf_alloc; -+ -+ ret = i915_mutex_lock_interruptible(&dev_priv->drm); -+ if (ret) -+ goto err_lock; -+ -+ stream->ops = &i915_oa_stream_ops; -+ dev_priv->perf.oa.exclusive_stream = stream; -+ -+ ret = dev_priv->perf.oa.ops.enable_metric_set(stream); -+ if (ret) { -+ DRM_DEBUG("Unable to enable metric set\n"); -+ goto err_enable; -+ } -+ -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ -+ return 0; -+ -+err_enable: -+ dev_priv->perf.oa.exclusive_stream = NULL; -+ dev_priv->perf.oa.ops.disable_metric_set(dev_priv); -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ -+err_lock: -+ free_oa_buffer(dev_priv); -+ -+err_oa_buf_alloc: -+ put_oa_config(dev_priv, stream->oa_config); -+ -+ intel_uncore_forcewake_put(&dev_priv->uncore, FORCEWAKE_ALL); -+ intel_runtime_pm_put(dev_priv, stream->wakeref); -+ -+err_config: -+ if (stream->ctx) -+ oa_put_render_ctx_id(stream); -+ -+ return ret; -+} -+ -+void i915_oa_init_reg_state(struct intel_engine_cs *engine, -+ struct intel_context *ce, -+ u32 *regs) -+{ -+ struct i915_perf_stream *stream; -+ -+ if (engine->class != RENDER_CLASS) -+ return; -+ -+ stream = engine->i915->perf.oa.exclusive_stream; -+ if (stream) -+ gen8_update_reg_state_unlocked(ce, regs, stream->oa_config); -+} -+ -+/** -+ * i915_perf_read_locked - &i915_perf_stream_ops->read with error normalisation -+ * @stream: An i915 perf stream -+ * @file: An i915 perf stream file -+ * @buf: destination buffer given by userspace -+ * @count: the number of bytes userspace wants to read -+ * @ppos: (inout) file seek position (unused) -+ * -+ * Besides wrapping &i915_perf_stream_ops->read this provides a common place to -+ * ensure that if we've successfully copied any data then reporting that takes -+ * precedence over any internal error status, so the data isn't lost. -+ * -+ * For example ret will be -ENOSPC whenever there is more buffered data than -+ * can be copied to userspace, but that's only interesting if we weren't able -+ * to copy some data because it implies the userspace buffer is too small to -+ * receive a single record (and we never split records). -+ * -+ * Another case with ret == -EFAULT is more of a grey area since it would seem -+ * like bad form for userspace to ask us to overrun its buffer, but the user -+ * knows best: -+ * -+ * http://yarchive.net/comp/linux/partial_reads_writes.html -+ * -+ * Returns: The number of bytes copied or a negative error code on failure. -+ */ -+static ssize_t i915_perf_read_locked(struct i915_perf_stream *stream, -+ struct file *file, -+ char __user *buf, -+ size_t count, -+ loff_t *ppos) -+{ -+ /* Note we keep the offset (aka bytes read) separate from any -+ * error status so that the final check for whether we return -+ * the bytes read with a higher precedence than any error (see -+ * comment below) doesn't need to be handled/duplicated in -+ * stream->ops->read() implementations. -+ */ -+ size_t offset = 0; -+ int ret = stream->ops->read(stream, buf, count, &offset); -+ -+ return offset ?: (ret ?: -EAGAIN); -+} -+ -+/** -+ * i915_perf_read - handles read() FOP for i915 perf stream FDs -+ * @file: An i915 perf stream file -+ * @buf: destination buffer given by userspace -+ * @count: the number of bytes userspace wants to read -+ * @ppos: (inout) file seek position (unused) -+ * -+ * The entry point for handling a read() on a stream file descriptor from -+ * userspace. Most of the work is left to the i915_perf_read_locked() and -+ * &i915_perf_stream_ops->read but to save having stream implementations (of -+ * which we might have multiple later) we handle blocking read here. -+ * -+ * We can also consistently treat trying to read from a disabled stream -+ * as an IO error so implementations can assume the stream is enabled -+ * while reading. -+ * -+ * Returns: The number of bytes copied or a negative error code on failure. -+ */ -+static ssize_t i915_perf_read(struct file *file, -+ char __user *buf, -+ size_t count, -+ loff_t *ppos) -+{ -+ struct i915_perf_stream *stream = file->private_data; -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ ssize_t ret; -+ -+ /* To ensure it's handled consistently we simply treat all reads of a -+ * disabled stream as an error. In particular it might otherwise lead -+ * to a deadlock for blocking file descriptors... -+ */ -+ if (!stream->enabled) -+ return -EIO; -+ -+ if (!(file->f_flags & O_NONBLOCK)) { -+ /* There's the small chance of false positives from -+ * stream->ops->wait_unlocked. -+ * -+ * E.g. with single context filtering since we only wait until -+ * oabuffer has >= 1 report we don't immediately know whether -+ * any reports really belong to the current context -+ */ -+ do { -+ ret = stream->ops->wait_unlocked(stream); -+ if (ret) -+ return ret; -+ -+ mutex_lock(&dev_priv->perf.lock); -+ ret = i915_perf_read_locked(stream, file, -+ buf, count, ppos); -+ mutex_unlock(&dev_priv->perf.lock); -+ } while (ret == -EAGAIN); -+ } else { -+ mutex_lock(&dev_priv->perf.lock); -+ ret = i915_perf_read_locked(stream, file, buf, count, ppos); -+ mutex_unlock(&dev_priv->perf.lock); -+ } -+ -+ /* We allow the poll checking to sometimes report false positive EPOLLIN -+ * events where we might actually report EAGAIN on read() if there's -+ * not really any data available. In this situation though we don't -+ * want to enter a busy loop between poll() reporting a EPOLLIN event -+ * and read() returning -EAGAIN. Clearing the oa.pollin state here -+ * effectively ensures we back off until the next hrtimer callback -+ * before reporting another EPOLLIN event. -+ */ -+ if (ret >= 0 || ret == -EAGAIN) { -+ /* Maybe make ->pollin per-stream state if we support multiple -+ * concurrent streams in the future. -+ */ -+ dev_priv->perf.oa.pollin = false; -+ } -+ -+ return ret; -+} -+ -+static enum hrtimer_restart oa_poll_check_timer_cb(struct hrtimer *hrtimer) -+{ -+ struct drm_i915_private *dev_priv = -+ container_of(hrtimer, typeof(*dev_priv), -+ perf.oa.poll_check_timer); -+ -+ if (oa_buffer_check_unlocked(dev_priv)) { -+ dev_priv->perf.oa.pollin = true; -+ wake_up(&dev_priv->perf.oa.poll_wq); -+ } -+ -+ hrtimer_forward_now(hrtimer, ns_to_ktime(POLL_PERIOD)); -+ -+ return HRTIMER_RESTART; -+} -+ -+/** -+ * i915_perf_poll_locked - poll_wait() with a suitable wait queue for stream -+ * @dev_priv: i915 device instance -+ * @stream: An i915 perf stream -+ * @file: An i915 perf stream file -+ * @wait: poll() state table -+ * -+ * For handling userspace polling on an i915 perf stream, this calls through to -+ * &i915_perf_stream_ops->poll_wait to call poll_wait() with a wait queue that -+ * will be woken for new stream data. -+ * -+ * Note: The &drm_i915_private->perf.lock mutex has been taken to serialize -+ * with any non-file-operation driver hooks. -+ * -+ * Returns: any poll events that are ready without sleeping -+ */ -+static __poll_t i915_perf_poll_locked(struct drm_i915_private *dev_priv, -+ struct i915_perf_stream *stream, -+ struct file *file, -+ poll_table *wait) -+{ -+ __poll_t events = 0; -+ -+ stream->ops->poll_wait(stream, file, wait); -+ -+ /* Note: we don't explicitly check whether there's something to read -+ * here since this path may be very hot depending on what else -+ * userspace is polling, or on the timeout in use. We rely solely on -+ * the hrtimer/oa_poll_check_timer_cb to notify us when there are -+ * samples to read. -+ */ -+ if (dev_priv->perf.oa.pollin) -+ events |= EPOLLIN; -+ -+ return events; -+} -+ -+/** -+ * i915_perf_poll - call poll_wait() with a suitable wait queue for stream -+ * @file: An i915 perf stream file -+ * @wait: poll() state table -+ * -+ * For handling userspace polling on an i915 perf stream, this ensures -+ * poll_wait() gets called with a wait queue that will be woken for new stream -+ * data. -+ * -+ * Note: Implementation deferred to i915_perf_poll_locked() -+ * -+ * Returns: any poll events that are ready without sleeping -+ */ -+static __poll_t i915_perf_poll(struct file *file, poll_table *wait) -+{ -+ struct i915_perf_stream *stream = file->private_data; -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ __poll_t ret; -+ -+ mutex_lock(&dev_priv->perf.lock); -+ ret = i915_perf_poll_locked(dev_priv, stream, file, wait); -+ mutex_unlock(&dev_priv->perf.lock); -+ -+ return ret; -+} -+ -+/** -+ * i915_perf_enable_locked - handle `I915_PERF_IOCTL_ENABLE` ioctl -+ * @stream: A disabled i915 perf stream -+ * -+ * [Re]enables the associated capture of data for this stream. -+ * -+ * If a stream was previously enabled then there's currently no intention -+ * to provide userspace any guarantee about the preservation of previously -+ * buffered data. -+ */ -+static void i915_perf_enable_locked(struct i915_perf_stream *stream) -+{ -+ if (stream->enabled) -+ return; -+ -+ /* Allow stream->ops->enable() to refer to this */ -+ stream->enabled = true; -+ -+ if (stream->ops->enable) -+ stream->ops->enable(stream); -+} -+ -+/** -+ * i915_perf_disable_locked - handle `I915_PERF_IOCTL_DISABLE` ioctl -+ * @stream: An enabled i915 perf stream -+ * -+ * Disables the associated capture of data for this stream. -+ * -+ * The intention is that disabling an re-enabling a stream will ideally be -+ * cheaper than destroying and re-opening a stream with the same configuration, -+ * though there are no formal guarantees about what state or buffered data -+ * must be retained between disabling and re-enabling a stream. -+ * -+ * Note: while a stream is disabled it's considered an error for userspace -+ * to attempt to read from the stream (-EIO). -+ */ -+static void i915_perf_disable_locked(struct i915_perf_stream *stream) -+{ -+ if (!stream->enabled) -+ return; -+ -+ /* Allow stream->ops->disable() to refer to this */ -+ stream->enabled = false; -+ -+ if (stream->ops->disable) -+ stream->ops->disable(stream); -+} -+ -+/** -+ * i915_perf_ioctl - support ioctl() usage with i915 perf stream FDs -+ * @stream: An i915 perf stream -+ * @cmd: the ioctl request -+ * @arg: the ioctl data -+ * -+ * Note: The &drm_i915_private->perf.lock mutex has been taken to serialize -+ * with any non-file-operation driver hooks. -+ * -+ * Returns: zero on success or a negative error code. Returns -EINVAL for -+ * an unknown ioctl request. -+ */ -+static long i915_perf_ioctl_locked(struct i915_perf_stream *stream, -+ unsigned int cmd, -+ unsigned long arg) -+{ -+ switch (cmd) { -+ case I915_PERF_IOCTL_ENABLE: -+ i915_perf_enable_locked(stream); -+ return 0; -+ case I915_PERF_IOCTL_DISABLE: -+ i915_perf_disable_locked(stream); -+ return 0; -+ } -+ -+ return -EINVAL; -+} -+ -+/** -+ * i915_perf_ioctl - support ioctl() usage with i915 perf stream FDs -+ * @file: An i915 perf stream file -+ * @cmd: the ioctl request -+ * @arg: the ioctl data -+ * -+ * Implementation deferred to i915_perf_ioctl_locked(). -+ * -+ * Returns: zero on success or a negative error code. Returns -EINVAL for -+ * an unknown ioctl request. -+ */ -+static long i915_perf_ioctl(struct file *file, -+ unsigned int cmd, -+ unsigned long arg) -+{ -+ struct i915_perf_stream *stream = file->private_data; -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ long ret; -+ -+ mutex_lock(&dev_priv->perf.lock); -+ ret = i915_perf_ioctl_locked(stream, cmd, arg); -+ mutex_unlock(&dev_priv->perf.lock); -+ -+ return ret; -+} -+ -+/** -+ * i915_perf_destroy_locked - destroy an i915 perf stream -+ * @stream: An i915 perf stream -+ * -+ * Frees all resources associated with the given i915 perf @stream, disabling -+ * any associated data capture in the process. -+ * -+ * Note: The &drm_i915_private->perf.lock mutex has been taken to serialize -+ * with any non-file-operation driver hooks. -+ */ -+static void i915_perf_destroy_locked(struct i915_perf_stream *stream) -+{ -+ if (stream->enabled) -+ i915_perf_disable_locked(stream); -+ -+ if (stream->ops->destroy) -+ stream->ops->destroy(stream); -+ -+ list_del(&stream->link); -+ -+ if (stream->ctx) -+ i915_gem_context_put(stream->ctx); -+ -+ kfree(stream); -+} -+ -+/** -+ * i915_perf_release - handles userspace close() of a stream file -+ * @inode: anonymous inode associated with file -+ * @file: An i915 perf stream file -+ * -+ * Cleans up any resources associated with an open i915 perf stream file. -+ * -+ * NB: close() can't really fail from the userspace point of view. -+ * -+ * Returns: zero on success or a negative error code. -+ */ -+static int i915_perf_release(struct inode *inode, struct file *file) -+{ -+ struct i915_perf_stream *stream = file->private_data; -+ struct drm_i915_private *dev_priv = stream->dev_priv; -+ -+ mutex_lock(&dev_priv->perf.lock); -+ i915_perf_destroy_locked(stream); -+ mutex_unlock(&dev_priv->perf.lock); -+ -+ return 0; -+} -+ -+ -+static const struct file_operations fops = { -+ .owner = THIS_MODULE, -+ .llseek = no_llseek, -+ .release = i915_perf_release, -+ .poll = i915_perf_poll, -+ .read = i915_perf_read, -+ .unlocked_ioctl = i915_perf_ioctl, -+ /* Our ioctl have no arguments, so it's safe to use the same function -+ * to handle 32bits compatibility. -+ */ -+ .compat_ioctl = i915_perf_ioctl, -+}; -+ -+ -+/** -+ * i915_perf_open_ioctl_locked - DRM ioctl() for userspace to open a stream FD -+ * @dev_priv: i915 device instance -+ * @param: The open parameters passed to 'DRM_I915_PERF_OPEN` -+ * @props: individually validated u64 property value pairs -+ * @file: drm file -+ * -+ * See i915_perf_ioctl_open() for interface details. -+ * -+ * Implements further stream config validation and stream initialization on -+ * behalf of i915_perf_open_ioctl() with the &drm_i915_private->perf.lock mutex -+ * taken to serialize with any non-file-operation driver hooks. -+ * -+ * Note: at this point the @props have only been validated in isolation and -+ * it's still necessary to validate that the combination of properties makes -+ * sense. -+ * -+ * In the case where userspace is interested in OA unit metrics then further -+ * config validation and stream initialization details will be handled by -+ * i915_oa_stream_init(). The code here should only validate config state that -+ * will be relevant to all stream types / backends. -+ * -+ * Returns: zero on success or a negative error code. -+ */ -+static int -+i915_perf_open_ioctl_locked(struct drm_i915_private *dev_priv, -+ struct drm_i915_perf_open_param *param, -+ struct perf_open_properties *props, -+ struct drm_file *file) -+{ -+ struct i915_gem_context *specific_ctx = NULL; -+ struct i915_perf_stream *stream = NULL; -+ unsigned long f_flags = 0; -+ bool privileged_op = true; -+ int stream_fd; -+ int ret; -+ -+ if (props->single_context) { -+ u32 ctx_handle = props->ctx_handle; -+ struct drm_i915_file_private *file_priv = file->driver_priv; -+ -+ specific_ctx = i915_gem_context_lookup(file_priv, ctx_handle); -+ if (!specific_ctx) { -+ DRM_DEBUG("Failed to look up context with ID %u for opening perf stream\n", -+ ctx_handle); -+ ret = -ENOENT; -+ goto err; -+ } -+ } -+ -+ /* -+ * On Haswell the OA unit supports clock gating off for a specific -+ * context and in this mode there's no visibility of metrics for the -+ * rest of the system, which we consider acceptable for a -+ * non-privileged client. -+ * -+ * For Gen8+ the OA unit no longer supports clock gating off for a -+ * specific context and the kernel can't securely stop the counters -+ * from updating as system-wide / global values. Even though we can -+ * filter reports based on the included context ID we can't block -+ * clients from seeing the raw / global counter values via -+ * MI_REPORT_PERF_COUNT commands and so consider it a privileged op to -+ * enable the OA unit by default. -+ */ -+ if (IS_HASWELL(dev_priv) && specific_ctx) -+ privileged_op = false; -+ -+ /* Similar to perf's kernel.perf_paranoid_cpu sysctl option -+ * we check a dev.i915.perf_stream_paranoid sysctl option -+ * to determine if it's ok to access system wide OA counters -+ * without CAP_SYS_ADMIN privileges. -+ */ -+ if (privileged_op && -+ i915_perf_stream_paranoid && !capable(CAP_SYS_ADMIN)) { -+ DRM_DEBUG("Insufficient privileges to open system-wide i915 perf stream\n"); -+ ret = -EACCES; -+ goto err_ctx; -+ } -+ -+ stream = kzalloc(sizeof(*stream), GFP_KERNEL); -+ if (!stream) { -+ ret = -ENOMEM; -+ goto err_ctx; -+ } -+ -+ stream->dev_priv = dev_priv; -+ stream->ctx = specific_ctx; -+ -+ ret = i915_oa_stream_init(stream, param, props); -+ if (ret) -+ goto err_alloc; -+ -+ /* we avoid simply assigning stream->sample_flags = props->sample_flags -+ * to have _stream_init check the combination of sample flags more -+ * thoroughly, but still this is the expected result at this point. -+ */ -+ if (WARN_ON(stream->sample_flags != props->sample_flags)) { -+ ret = -ENODEV; -+ goto err_flags; -+ } -+ -+ list_add(&stream->link, &dev_priv->perf.streams); -+ -+ if (param->flags & I915_PERF_FLAG_FD_CLOEXEC) -+ f_flags |= O_CLOEXEC; -+ if (param->flags & I915_PERF_FLAG_FD_NONBLOCK) -+ f_flags |= O_NONBLOCK; -+ -+ stream_fd = anon_inode_getfd("[i915_perf]", &fops, stream, f_flags); -+ if (stream_fd < 0) { -+ ret = stream_fd; -+ goto err_open; -+ } -+ -+ if (!(param->flags & I915_PERF_FLAG_DISABLED)) -+ i915_perf_enable_locked(stream); -+ -+ return stream_fd; -+ -+err_open: -+ list_del(&stream->link); -+err_flags: -+ if (stream->ops->destroy) -+ stream->ops->destroy(stream); -+err_alloc: -+ kfree(stream); -+err_ctx: -+ if (specific_ctx) -+ i915_gem_context_put(specific_ctx); -+err: -+ return ret; -+} -+ -+static u64 oa_exponent_to_ns(struct drm_i915_private *dev_priv, int exponent) -+{ -+ return div64_u64(1000000000ULL * (2ULL << exponent), -+ 1000ULL * RUNTIME_INFO(dev_priv)->cs_timestamp_frequency_khz); -+} -+ -+/** -+ * read_properties_unlocked - validate + copy userspace stream open properties -+ * @dev_priv: i915 device instance -+ * @uprops: The array of u64 key value pairs given by userspace -+ * @n_props: The number of key value pairs expected in @uprops -+ * @props: The stream configuration built up while validating properties -+ * -+ * Note this function only validates properties in isolation it doesn't -+ * validate that the combination of properties makes sense or that all -+ * properties necessary for a particular kind of stream have been set. -+ * -+ * Note that there currently aren't any ordering requirements for properties so -+ * we shouldn't validate or assume anything about ordering here. This doesn't -+ * rule out defining new properties with ordering requirements in the future. -+ */ -+static int read_properties_unlocked(struct drm_i915_private *dev_priv, -+ u64 __user *uprops, -+ u32 n_props, -+ struct perf_open_properties *props) -+{ -+ u64 __user *uprop = uprops; -+ u32 i; -+ -+ memset(props, 0, sizeof(struct perf_open_properties)); -+ -+ if (!n_props) { -+ DRM_DEBUG("No i915 perf properties given\n"); -+ return -EINVAL; -+ } -+ -+ /* Considering that ID = 0 is reserved and assuming that we don't -+ * (currently) expect any configurations to ever specify duplicate -+ * values for a particular property ID then the last _PROP_MAX value is -+ * one greater than the maximum number of properties we expect to get -+ * from userspace. -+ */ -+ if (n_props >= DRM_I915_PERF_PROP_MAX) { -+ DRM_DEBUG("More i915 perf properties specified than exist\n"); -+ return -EINVAL; -+ } -+ -+ for (i = 0; i < n_props; i++) { -+ u64 oa_period, oa_freq_hz; -+ u64 id, value; -+ int ret; -+ -+ ret = get_user(id, uprop); -+ if (ret) -+ return ret; -+ -+ ret = get_user(value, uprop + 1); -+ if (ret) -+ return ret; -+ -+ if (id == 0 || id >= DRM_I915_PERF_PROP_MAX) { -+ DRM_DEBUG("Unknown i915 perf property ID\n"); -+ return -EINVAL; -+ } -+ -+ switch ((enum drm_i915_perf_property_id)id) { -+ case DRM_I915_PERF_PROP_CTX_HANDLE: -+ props->single_context = 1; -+ props->ctx_handle = value; -+ break; -+ case DRM_I915_PERF_PROP_SAMPLE_OA: -+ if (value) -+ props->sample_flags |= SAMPLE_OA_REPORT; -+ break; -+ case DRM_I915_PERF_PROP_OA_METRICS_SET: -+ if (value == 0) { -+ DRM_DEBUG("Unknown OA metric set ID\n"); -+ return -EINVAL; -+ } -+ props->metrics_set = value; -+ break; -+ case DRM_I915_PERF_PROP_OA_FORMAT: -+ if (value == 0 || value >= I915_OA_FORMAT_MAX) { -+ DRM_DEBUG("Out-of-range OA report format %llu\n", -+ value); -+ return -EINVAL; -+ } -+ if (!dev_priv->perf.oa.oa_formats[value].size) { -+ DRM_DEBUG("Unsupported OA report format %llu\n", -+ value); -+ return -EINVAL; -+ } -+ props->oa_format = value; -+ break; -+ case DRM_I915_PERF_PROP_OA_EXPONENT: -+ if (value > OA_EXPONENT_MAX) { -+ DRM_DEBUG("OA timer exponent too high (> %u)\n", -+ OA_EXPONENT_MAX); -+ return -EINVAL; -+ } -+ -+ /* Theoretically we can program the OA unit to sample -+ * e.g. every 160ns for HSW, 167ns for BDW/SKL or 104ns -+ * for BXT. We don't allow such high sampling -+ * frequencies by default unless root. -+ */ -+ -+ BUILD_BUG_ON(sizeof(oa_period) != 8); -+ oa_period = oa_exponent_to_ns(dev_priv, value); -+ -+ /* This check is primarily to ensure that oa_period <= -+ * UINT32_MAX (before passing to do_div which only -+ * accepts a u32 denominator), but we can also skip -+ * checking anything < 1Hz which implicitly can't be -+ * limited via an integer oa_max_sample_rate. -+ */ -+ if (oa_period <= NSEC_PER_SEC) { -+ u64 tmp = NSEC_PER_SEC; -+ do_div(tmp, oa_period); -+ oa_freq_hz = tmp; -+ } else -+ oa_freq_hz = 0; -+ -+ if (oa_freq_hz > i915_oa_max_sample_rate && -+ !capable(CAP_SYS_ADMIN)) { -+ DRM_DEBUG("OA exponent would exceed the max sampling frequency (sysctl dev.i915.oa_max_sample_rate) %uHz without root privileges\n", -+ i915_oa_max_sample_rate); -+ return -EACCES; -+ } -+ -+ props->oa_periodic = true; -+ props->oa_period_exponent = value; -+ break; -+ case DRM_I915_PERF_PROP_MAX: -+ MISSING_CASE(id); -+ return -EINVAL; -+ } -+ -+ uprop += 2; -+ } -+ -+ return 0; -+} -+ -+/** -+ * i915_perf_open_ioctl - DRM ioctl() for userspace to open a stream FD -+ * @dev: drm device -+ * @data: ioctl data copied from userspace (unvalidated) -+ * @file: drm file -+ * -+ * Validates the stream open parameters given by userspace including flags -+ * and an array of u64 key, value pair properties. -+ * -+ * Very little is assumed up front about the nature of the stream being -+ * opened (for instance we don't assume it's for periodic OA unit metrics). An -+ * i915-perf stream is expected to be a suitable interface for other forms of -+ * buffered data written by the GPU besides periodic OA metrics. -+ * -+ * Note we copy the properties from userspace outside of the i915 perf -+ * mutex to avoid an awkward lockdep with mmap_sem. -+ * -+ * Most of the implementation details are handled by -+ * i915_perf_open_ioctl_locked() after taking the &drm_i915_private->perf.lock -+ * mutex for serializing with any non-file-operation driver hooks. -+ * -+ * Return: A newly opened i915 Perf stream file descriptor or negative -+ * error code on failure. -+ */ -+int i915_perf_open_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_private *dev_priv = dev->dev_private; -+ struct drm_i915_perf_open_param *param = data; -+ struct perf_open_properties props; -+ u32 known_open_flags; -+ int ret; -+ -+ if (!dev_priv->perf.initialized) { -+ DRM_DEBUG("i915 perf interface not available for this system\n"); -+ return -ENOTSUPP; -+ } -+ -+ known_open_flags = I915_PERF_FLAG_FD_CLOEXEC | -+ I915_PERF_FLAG_FD_NONBLOCK | -+ I915_PERF_FLAG_DISABLED; -+ if (param->flags & ~known_open_flags) { -+ DRM_DEBUG("Unknown drm_i915_perf_open_param flag\n"); -+ return -EINVAL; -+ } -+ -+ ret = read_properties_unlocked(dev_priv, -+ u64_to_user_ptr(param->properties_ptr), -+ param->num_properties, -+ &props); -+ if (ret) -+ return ret; -+ -+ mutex_lock(&dev_priv->perf.lock); -+ ret = i915_perf_open_ioctl_locked(dev_priv, param, &props, file); -+ mutex_unlock(&dev_priv->perf.lock); -+ -+ return ret; -+} -+ -+/** -+ * i915_perf_register - exposes i915-perf to userspace -+ * @dev_priv: i915 device instance -+ * -+ * In particular OA metric sets are advertised under a sysfs metrics/ -+ * directory allowing userspace to enumerate valid IDs that can be -+ * used to open an i915-perf stream. -+ */ -+void i915_perf_register(struct drm_i915_private *dev_priv) -+{ -+ int ret; -+ -+ if (!dev_priv->perf.initialized) -+ return; -+ -+ /* To be sure we're synchronized with an attempted -+ * i915_perf_open_ioctl(); considering that we register after -+ * being exposed to userspace. -+ */ -+ mutex_lock(&dev_priv->perf.lock); -+ -+ dev_priv->perf.metrics_kobj = -+ kobject_create_and_add("metrics", -+ &dev_priv->drm.primary->kdev->kobj); -+ if (!dev_priv->perf.metrics_kobj) -+ goto exit; -+ -+ sysfs_attr_init(&dev_priv->perf.oa.test_config.sysfs_metric_id.attr); -+ -+ if (INTEL_GEN(dev_priv) >= 11) { -+ i915_perf_load_test_config_icl(dev_priv); -+ } else if (IS_CANNONLAKE(dev_priv)) { -+ i915_perf_load_test_config_cnl(dev_priv); -+ } else if (IS_COFFEELAKE(dev_priv)) { -+ if (IS_CFL_GT2(dev_priv)) -+ i915_perf_load_test_config_cflgt2(dev_priv); -+ if (IS_CFL_GT3(dev_priv)) -+ i915_perf_load_test_config_cflgt3(dev_priv); -+ } else if (IS_GEMINILAKE(dev_priv)) { -+ i915_perf_load_test_config_glk(dev_priv); -+ } else if (IS_KABYLAKE(dev_priv)) { -+ if (IS_KBL_GT2(dev_priv)) -+ i915_perf_load_test_config_kblgt2(dev_priv); -+ else if (IS_KBL_GT3(dev_priv)) -+ i915_perf_load_test_config_kblgt3(dev_priv); -+ } else if (IS_BROXTON(dev_priv)) { -+ i915_perf_load_test_config_bxt(dev_priv); -+ } else if (IS_SKYLAKE(dev_priv)) { -+ if (IS_SKL_GT2(dev_priv)) -+ i915_perf_load_test_config_sklgt2(dev_priv); -+ else if (IS_SKL_GT3(dev_priv)) -+ i915_perf_load_test_config_sklgt3(dev_priv); -+ else if (IS_SKL_GT4(dev_priv)) -+ i915_perf_load_test_config_sklgt4(dev_priv); -+ } else if (IS_CHERRYVIEW(dev_priv)) { -+ i915_perf_load_test_config_chv(dev_priv); -+ } else if (IS_BROADWELL(dev_priv)) { -+ i915_perf_load_test_config_bdw(dev_priv); -+ } else if (IS_HASWELL(dev_priv)) { -+ i915_perf_load_test_config_hsw(dev_priv); -+} -+ -+ if (dev_priv->perf.oa.test_config.id == 0) -+ goto sysfs_error; -+ -+ ret = sysfs_create_group(dev_priv->perf.metrics_kobj, -+ &dev_priv->perf.oa.test_config.sysfs_metric); -+ if (ret) -+ goto sysfs_error; -+ -+ atomic_set(&dev_priv->perf.oa.test_config.ref_count, 1); -+ -+ goto exit; -+ -+sysfs_error: -+ kobject_put(dev_priv->perf.metrics_kobj); -+ dev_priv->perf.metrics_kobj = NULL; -+ -+exit: -+ mutex_unlock(&dev_priv->perf.lock); -+} -+ -+/** -+ * i915_perf_unregister - hide i915-perf from userspace -+ * @dev_priv: i915 device instance -+ * -+ * i915-perf state cleanup is split up into an 'unregister' and -+ * 'deinit' phase where the interface is first hidden from -+ * userspace by i915_perf_unregister() before cleaning up -+ * remaining state in i915_perf_fini(). -+ */ -+void i915_perf_unregister(struct drm_i915_private *dev_priv) -+{ -+ if (!dev_priv->perf.metrics_kobj) -+ return; -+ -+ sysfs_remove_group(dev_priv->perf.metrics_kobj, -+ &dev_priv->perf.oa.test_config.sysfs_metric); -+ -+ kobject_put(dev_priv->perf.metrics_kobj); -+ dev_priv->perf.metrics_kobj = NULL; -+} -+ -+static bool gen8_is_valid_flex_addr(struct drm_i915_private *dev_priv, u32 addr) -+{ -+ static const i915_reg_t flex_eu_regs[] = { -+ EU_PERF_CNTL0, -+ EU_PERF_CNTL1, -+ EU_PERF_CNTL2, -+ EU_PERF_CNTL3, -+ EU_PERF_CNTL4, -+ EU_PERF_CNTL5, -+ EU_PERF_CNTL6, -+ }; -+ int i; -+ -+ for (i = 0; i < ARRAY_SIZE(flex_eu_regs); i++) { -+ if (i915_mmio_reg_offset(flex_eu_regs[i]) == addr) -+ return true; -+ } -+ return false; -+} -+ -+static bool gen7_is_valid_b_counter_addr(struct drm_i915_private *dev_priv, u32 addr) -+{ -+ return (addr >= i915_mmio_reg_offset(OASTARTTRIG1) && -+ addr <= i915_mmio_reg_offset(OASTARTTRIG8)) || -+ (addr >= i915_mmio_reg_offset(OAREPORTTRIG1) && -+ addr <= i915_mmio_reg_offset(OAREPORTTRIG8)) || -+ (addr >= i915_mmio_reg_offset(OACEC0_0) && -+ addr <= i915_mmio_reg_offset(OACEC7_1)); -+} -+ -+static bool gen7_is_valid_mux_addr(struct drm_i915_private *dev_priv, u32 addr) -+{ -+ return addr == i915_mmio_reg_offset(HALF_SLICE_CHICKEN2) || -+ (addr >= i915_mmio_reg_offset(MICRO_BP0_0) && -+ addr <= i915_mmio_reg_offset(NOA_WRITE)) || -+ (addr >= i915_mmio_reg_offset(OA_PERFCNT1_LO) && -+ addr <= i915_mmio_reg_offset(OA_PERFCNT2_HI)) || -+ (addr >= i915_mmio_reg_offset(OA_PERFMATRIX_LO) && -+ addr <= i915_mmio_reg_offset(OA_PERFMATRIX_HI)); -+} -+ -+static bool gen8_is_valid_mux_addr(struct drm_i915_private *dev_priv, u32 addr) -+{ -+ return gen7_is_valid_mux_addr(dev_priv, addr) || -+ addr == i915_mmio_reg_offset(WAIT_FOR_RC6_EXIT) || -+ (addr >= i915_mmio_reg_offset(RPM_CONFIG0) && -+ addr <= i915_mmio_reg_offset(NOA_CONFIG(8))); -+} -+ -+static bool gen10_is_valid_mux_addr(struct drm_i915_private *dev_priv, u32 addr) -+{ -+ return gen8_is_valid_mux_addr(dev_priv, addr) || -+ addr == i915_mmio_reg_offset(GEN10_NOA_WRITE_HIGH) || -+ (addr >= i915_mmio_reg_offset(OA_PERFCNT3_LO) && -+ addr <= i915_mmio_reg_offset(OA_PERFCNT4_HI)); -+} -+ -+static bool hsw_is_valid_mux_addr(struct drm_i915_private *dev_priv, u32 addr) -+{ -+ return gen7_is_valid_mux_addr(dev_priv, addr) || -+ (addr >= 0x25100 && addr <= 0x2FF90) || -+ (addr >= i915_mmio_reg_offset(HSW_MBVID2_NOA0) && -+ addr <= i915_mmio_reg_offset(HSW_MBVID2_NOA9)) || -+ addr == i915_mmio_reg_offset(HSW_MBVID2_MISR0); -+} -+ -+static bool chv_is_valid_mux_addr(struct drm_i915_private *dev_priv, u32 addr) -+{ -+ return gen7_is_valid_mux_addr(dev_priv, addr) || -+ (addr >= 0x182300 && addr <= 0x1823A4); -+} -+ -+static u32 mask_reg_value(u32 reg, u32 val) -+{ -+ /* HALF_SLICE_CHICKEN2 is programmed with a the -+ * WaDisableSTUnitPowerOptimization workaround. Make sure the value -+ * programmed by userspace doesn't change this. -+ */ -+ if (i915_mmio_reg_offset(HALF_SLICE_CHICKEN2) == reg) -+ val = val & ~_MASKED_BIT_ENABLE(GEN8_ST_PO_DISABLE); -+ -+ /* WAIT_FOR_RC6_EXIT has only one bit fullfilling the function -+ * indicated by its name and a bunch of selection fields used by OA -+ * configs. -+ */ -+ if (i915_mmio_reg_offset(WAIT_FOR_RC6_EXIT) == reg) -+ val = val & ~_MASKED_BIT_ENABLE(HSW_WAIT_FOR_RC6_EXIT_ENABLE); -+ -+ return val; -+} -+ -+static struct i915_oa_reg *alloc_oa_regs(struct drm_i915_private *dev_priv, -+ bool (*is_valid)(struct drm_i915_private *dev_priv, u32 addr), -+ u32 __user *regs, -+ u32 n_regs) -+{ -+ struct i915_oa_reg *oa_regs; -+ int err; -+ u32 i; -+ -+ if (!n_regs) -+ return NULL; -+ -+ if (!access_ok(regs, n_regs * sizeof(u32) * 2)) -+ return ERR_PTR(-EFAULT); -+ -+ /* No is_valid function means we're not allowing any register to be programmed. */ -+ GEM_BUG_ON(!is_valid); -+ if (!is_valid) -+ return ERR_PTR(-EINVAL); -+ -+ oa_regs = kmalloc_array(n_regs, sizeof(*oa_regs), GFP_KERNEL); -+ if (!oa_regs) -+ return ERR_PTR(-ENOMEM); -+ -+ for (i = 0; i < n_regs; i++) { -+ u32 addr, value; -+ -+ err = get_user(addr, regs); -+ if (err) -+ goto addr_err; -+ -+ if (!is_valid(dev_priv, addr)) { -+ DRM_DEBUG("Invalid oa_reg address: %X\n", addr); -+ err = -EINVAL; -+ goto addr_err; -+ } -+ -+ err = get_user(value, regs + 1); -+ if (err) -+ goto addr_err; -+ -+ oa_regs[i].addr = _MMIO(addr); -+ oa_regs[i].value = mask_reg_value(addr, value); -+ -+ regs += 2; -+ } -+ -+ return oa_regs; -+ -+addr_err: -+ kfree(oa_regs); -+ return ERR_PTR(err); -+} -+ -+static ssize_t show_dynamic_id(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ struct i915_oa_config *oa_config = -+ container_of(attr, typeof(*oa_config), sysfs_metric_id); -+ -+ return sprintf(buf, "%d\n", oa_config->id); -+} -+ -+static int create_dynamic_oa_sysfs_entry(struct drm_i915_private *dev_priv, -+ struct i915_oa_config *oa_config) -+{ -+ sysfs_attr_init(&oa_config->sysfs_metric_id.attr); -+ oa_config->sysfs_metric_id.attr.name = "id"; -+ oa_config->sysfs_metric_id.attr.mode = S_IRUGO; -+ oa_config->sysfs_metric_id.show = show_dynamic_id; -+ oa_config->sysfs_metric_id.store = NULL; -+ -+ oa_config->attrs[0] = &oa_config->sysfs_metric_id.attr; -+ oa_config->attrs[1] = NULL; -+ -+ oa_config->sysfs_metric.name = oa_config->uuid; -+ oa_config->sysfs_metric.attrs = oa_config->attrs; -+ -+ return sysfs_create_group(dev_priv->perf.metrics_kobj, -+ &oa_config->sysfs_metric); -+} -+ -+/** -+ * i915_perf_add_config_ioctl - DRM ioctl() for userspace to add a new OA config -+ * @dev: drm device -+ * @data: ioctl data (pointer to struct drm_i915_perf_oa_config) copied from -+ * userspace (unvalidated) -+ * @file: drm file -+ * -+ * Validates the submitted OA register to be saved into a new OA config that -+ * can then be used for programming the OA unit and its NOA network. -+ * -+ * Returns: A new allocated config number to be used with the perf open ioctl -+ * or a negative error code on failure. -+ */ -+int i915_perf_add_config_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_private *dev_priv = dev->dev_private; -+ struct drm_i915_perf_oa_config *args = data; -+ struct i915_oa_config *oa_config, *tmp; -+ int err, id; -+ -+ if (!dev_priv->perf.initialized) { -+ DRM_DEBUG("i915 perf interface not available for this system\n"); -+ return -ENOTSUPP; -+ } -+ -+ if (!dev_priv->perf.metrics_kobj) { -+ DRM_DEBUG("OA metrics weren't advertised via sysfs\n"); -+ return -EINVAL; -+ } -+ -+ if (i915_perf_stream_paranoid && !capable(CAP_SYS_ADMIN)) { -+ DRM_DEBUG("Insufficient privileges to add i915 OA config\n"); -+ return -EACCES; -+ } -+ -+ if ((!args->mux_regs_ptr || !args->n_mux_regs) && -+ (!args->boolean_regs_ptr || !args->n_boolean_regs) && -+ (!args->flex_regs_ptr || !args->n_flex_regs)) { -+ DRM_DEBUG("No OA registers given\n"); -+ return -EINVAL; -+ } -+ -+ oa_config = kzalloc(sizeof(*oa_config), GFP_KERNEL); -+ if (!oa_config) { -+ DRM_DEBUG("Failed to allocate memory for the OA config\n"); -+ return -ENOMEM; -+ } -+ -+ atomic_set(&oa_config->ref_count, 1); -+ -+ if (!uuid_is_valid(args->uuid)) { -+ DRM_DEBUG("Invalid uuid format for OA config\n"); -+ err = -EINVAL; -+ goto reg_err; -+ } -+ -+ /* Last character in oa_config->uuid will be 0 because oa_config is -+ * kzalloc. -+ */ -+ memcpy(oa_config->uuid, args->uuid, sizeof(args->uuid)); -+ -+ oa_config->mux_regs_len = args->n_mux_regs; -+ oa_config->mux_regs = -+ alloc_oa_regs(dev_priv, -+ dev_priv->perf.oa.ops.is_valid_mux_reg, -+ u64_to_user_ptr(args->mux_regs_ptr), -+ args->n_mux_regs); -+ -+ if (IS_ERR(oa_config->mux_regs)) { -+ DRM_DEBUG("Failed to create OA config for mux_regs\n"); -+ err = PTR_ERR(oa_config->mux_regs); -+ goto reg_err; -+ } -+ -+ oa_config->b_counter_regs_len = args->n_boolean_regs; -+ oa_config->b_counter_regs = -+ alloc_oa_regs(dev_priv, -+ dev_priv->perf.oa.ops.is_valid_b_counter_reg, -+ u64_to_user_ptr(args->boolean_regs_ptr), -+ args->n_boolean_regs); -+ -+ if (IS_ERR(oa_config->b_counter_regs)) { -+ DRM_DEBUG("Failed to create OA config for b_counter_regs\n"); -+ err = PTR_ERR(oa_config->b_counter_regs); -+ goto reg_err; -+ } -+ -+ if (INTEL_GEN(dev_priv) < 8) { -+ if (args->n_flex_regs != 0) { -+ err = -EINVAL; -+ goto reg_err; -+ } -+ } else { -+ oa_config->flex_regs_len = args->n_flex_regs; -+ oa_config->flex_regs = -+ alloc_oa_regs(dev_priv, -+ dev_priv->perf.oa.ops.is_valid_flex_reg, -+ u64_to_user_ptr(args->flex_regs_ptr), -+ args->n_flex_regs); -+ -+ if (IS_ERR(oa_config->flex_regs)) { -+ DRM_DEBUG("Failed to create OA config for flex_regs\n"); -+ err = PTR_ERR(oa_config->flex_regs); -+ goto reg_err; -+ } -+ } -+ -+ err = mutex_lock_interruptible(&dev_priv->perf.metrics_lock); -+ if (err) -+ goto reg_err; -+ -+ /* We shouldn't have too many configs, so this iteration shouldn't be -+ * too costly. -+ */ -+ idr_for_each_entry(&dev_priv->perf.metrics_idr, tmp, id) { -+ if (!strcmp(tmp->uuid, oa_config->uuid)) { -+ DRM_DEBUG("OA config already exists with this uuid\n"); -+ err = -EADDRINUSE; -+ goto sysfs_err; -+ } -+ } -+ -+ err = create_dynamic_oa_sysfs_entry(dev_priv, oa_config); -+ if (err) { -+ DRM_DEBUG("Failed to create sysfs entry for OA config\n"); -+ goto sysfs_err; -+ } -+ -+ /* Config id 0 is invalid, id 1 for kernel stored test config. */ -+ oa_config->id = idr_alloc(&dev_priv->perf.metrics_idr, -+ oa_config, 2, -+ 0, GFP_KERNEL); -+ if (oa_config->id < 0) { -+ DRM_DEBUG("Failed to create sysfs entry for OA config\n"); -+ err = oa_config->id; -+ goto sysfs_err; -+ } -+ -+ mutex_unlock(&dev_priv->perf.metrics_lock); -+ -+ DRM_DEBUG("Added config %s id=%i\n", oa_config->uuid, oa_config->id); -+ -+ return oa_config->id; -+ -+sysfs_err: -+ mutex_unlock(&dev_priv->perf.metrics_lock); -+reg_err: -+ put_oa_config(dev_priv, oa_config); -+ DRM_DEBUG("Failed to add new OA config\n"); -+ return err; -+} -+ -+/** -+ * i915_perf_remove_config_ioctl - DRM ioctl() for userspace to remove an OA config -+ * @dev: drm device -+ * @data: ioctl data (pointer to u64 integer) copied from userspace -+ * @file: drm file -+ * -+ * Configs can be removed while being used, the will stop appearing in sysfs -+ * and their content will be freed when the stream using the config is closed. -+ * -+ * Returns: 0 on success or a negative error code on failure. -+ */ -+int i915_perf_remove_config_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_private *dev_priv = dev->dev_private; -+ u64 *arg = data; -+ struct i915_oa_config *oa_config; -+ int ret; -+ -+ if (!dev_priv->perf.initialized) { -+ DRM_DEBUG("i915 perf interface not available for this system\n"); -+ return -ENOTSUPP; -+ } -+ -+ if (i915_perf_stream_paranoid && !capable(CAP_SYS_ADMIN)) { -+ DRM_DEBUG("Insufficient privileges to remove i915 OA config\n"); -+ return -EACCES; -+ } -+ -+ ret = mutex_lock_interruptible(&dev_priv->perf.metrics_lock); -+ if (ret) -+ goto lock_err; -+ -+ oa_config = idr_find(&dev_priv->perf.metrics_idr, *arg); -+ if (!oa_config) { -+ DRM_DEBUG("Failed to remove unknown OA config\n"); -+ ret = -ENOENT; -+ goto config_err; -+ } -+ -+ GEM_BUG_ON(*arg != oa_config->id); -+ -+ sysfs_remove_group(dev_priv->perf.metrics_kobj, -+ &oa_config->sysfs_metric); -+ -+ idr_remove(&dev_priv->perf.metrics_idr, *arg); -+ -+ DRM_DEBUG("Removed config %s id=%i\n", oa_config->uuid, oa_config->id); -+ -+ put_oa_config(dev_priv, oa_config); -+ -+config_err: -+ mutex_unlock(&dev_priv->perf.metrics_lock); -+lock_err: -+ return ret; -+} -+ -+static struct ctl_table oa_table[] = { -+ { -+ .procname = "perf_stream_paranoid", -+ .data = &i915_perf_stream_paranoid, -+ .maxlen = sizeof(i915_perf_stream_paranoid), -+ .mode = 0644, -+ .proc_handler = proc_dointvec_minmax, -+ .extra1 = &zero, -+ .extra2 = &one, -+ }, -+ { -+ .procname = "oa_max_sample_rate", -+ .data = &i915_oa_max_sample_rate, -+ .maxlen = sizeof(i915_oa_max_sample_rate), -+ .mode = 0644, -+ .proc_handler = proc_dointvec_minmax, -+ .extra1 = &zero, -+ .extra2 = &oa_sample_rate_hard_limit, -+ }, -+ {} -+}; -+ -+static struct ctl_table i915_root[] = { -+ { -+ .procname = "i915", -+ .maxlen = 0, -+ .mode = 0555, -+ .child = oa_table, -+ }, -+ {} -+}; -+ -+static struct ctl_table dev_root[] = { -+ { -+ .procname = "dev", -+ .maxlen = 0, -+ .mode = 0555, -+ .child = i915_root, -+ }, -+ {} -+}; -+ -+/** -+ * i915_perf_init - initialize i915-perf state on module load -+ * @dev_priv: i915 device instance -+ * -+ * Initializes i915-perf state without exposing anything to userspace. -+ * -+ * Note: i915-perf initialization is split into an 'init' and 'register' -+ * phase with the i915_perf_register() exposing state to userspace. -+ */ -+void i915_perf_init(struct drm_i915_private *dev_priv) -+{ -+ if (IS_HASWELL(dev_priv)) { -+ dev_priv->perf.oa.ops.is_valid_b_counter_reg = -+ gen7_is_valid_b_counter_addr; -+ dev_priv->perf.oa.ops.is_valid_mux_reg = -+ hsw_is_valid_mux_addr; -+ dev_priv->perf.oa.ops.is_valid_flex_reg = NULL; -+ dev_priv->perf.oa.ops.enable_metric_set = hsw_enable_metric_set; -+ dev_priv->perf.oa.ops.disable_metric_set = hsw_disable_metric_set; -+ dev_priv->perf.oa.ops.oa_enable = gen7_oa_enable; -+ dev_priv->perf.oa.ops.oa_disable = gen7_oa_disable; -+ dev_priv->perf.oa.ops.read = gen7_oa_read; -+ dev_priv->perf.oa.ops.oa_hw_tail_read = -+ gen7_oa_hw_tail_read; -+ -+ dev_priv->perf.oa.oa_formats = hsw_oa_formats; -+ } else if (HAS_LOGICAL_RING_CONTEXTS(dev_priv)) { -+ /* Note: that although we could theoretically also support the -+ * legacy ringbuffer mode on BDW (and earlier iterations of -+ * this driver, before upstreaming did this) it didn't seem -+ * worth the complexity to maintain now that BDW+ enable -+ * execlist mode by default. -+ */ -+ dev_priv->perf.oa.oa_formats = gen8_plus_oa_formats; -+ -+ dev_priv->perf.oa.ops.oa_enable = gen8_oa_enable; -+ dev_priv->perf.oa.ops.oa_disable = gen8_oa_disable; -+ dev_priv->perf.oa.ops.read = gen8_oa_read; -+ dev_priv->perf.oa.ops.oa_hw_tail_read = gen8_oa_hw_tail_read; -+ -+ if (IS_GEN_RANGE(dev_priv, 8, 9)) { -+ dev_priv->perf.oa.ops.is_valid_b_counter_reg = -+ gen7_is_valid_b_counter_addr; -+ dev_priv->perf.oa.ops.is_valid_mux_reg = -+ gen8_is_valid_mux_addr; -+ dev_priv->perf.oa.ops.is_valid_flex_reg = -+ gen8_is_valid_flex_addr; -+ -+ if (IS_CHERRYVIEW(dev_priv)) { -+ dev_priv->perf.oa.ops.is_valid_mux_reg = -+ chv_is_valid_mux_addr; -+ } -+ -+ dev_priv->perf.oa.ops.enable_metric_set = gen8_enable_metric_set; -+ dev_priv->perf.oa.ops.disable_metric_set = gen8_disable_metric_set; -+ -+ if (IS_GEN(dev_priv, 8)) { -+ dev_priv->perf.oa.ctx_oactxctrl_offset = 0x120; -+ dev_priv->perf.oa.ctx_flexeu0_offset = 0x2ce; -+ -+ dev_priv->perf.oa.gen8_valid_ctx_bit = (1<<25); -+ } else { -+ dev_priv->perf.oa.ctx_oactxctrl_offset = 0x128; -+ dev_priv->perf.oa.ctx_flexeu0_offset = 0x3de; -+ -+ dev_priv->perf.oa.gen8_valid_ctx_bit = (1<<16); -+ } -+ } else if (IS_GEN_RANGE(dev_priv, 10, 11)) { -+ dev_priv->perf.oa.ops.is_valid_b_counter_reg = -+ gen7_is_valid_b_counter_addr; -+ dev_priv->perf.oa.ops.is_valid_mux_reg = -+ gen10_is_valid_mux_addr; -+ dev_priv->perf.oa.ops.is_valid_flex_reg = -+ gen8_is_valid_flex_addr; -+ -+ dev_priv->perf.oa.ops.enable_metric_set = gen8_enable_metric_set; -+ dev_priv->perf.oa.ops.disable_metric_set = gen10_disable_metric_set; -+ -+ if (IS_GEN(dev_priv, 10)) { -+ dev_priv->perf.oa.ctx_oactxctrl_offset = 0x128; -+ dev_priv->perf.oa.ctx_flexeu0_offset = 0x3de; -+ } else { -+ dev_priv->perf.oa.ctx_oactxctrl_offset = 0x124; -+ dev_priv->perf.oa.ctx_flexeu0_offset = 0x78e; -+ } -+ dev_priv->perf.oa.gen8_valid_ctx_bit = (1<<16); -+ } -+ } -+ -+ if (dev_priv->perf.oa.ops.enable_metric_set) { -+ hrtimer_init(&dev_priv->perf.oa.poll_check_timer, -+ CLOCK_MONOTONIC, HRTIMER_MODE_REL); -+ dev_priv->perf.oa.poll_check_timer.function = oa_poll_check_timer_cb; -+ init_waitqueue_head(&dev_priv->perf.oa.poll_wq); -+ -+ INIT_LIST_HEAD(&dev_priv->perf.streams); -+ mutex_init(&dev_priv->perf.lock); -+ spin_lock_init(&dev_priv->perf.oa.oa_buffer.ptr_lock); -+ -+ oa_sample_rate_hard_limit = 1000 * -+ (RUNTIME_INFO(dev_priv)->cs_timestamp_frequency_khz / 2); -+ dev_priv->perf.sysctl_header = register_sysctl_table(dev_root); -+ -+ mutex_init(&dev_priv->perf.metrics_lock); -+ idr_init(&dev_priv->perf.metrics_idr); -+ -+ dev_priv->perf.initialized = true; -+ } -+} -+ -+static int destroy_config(int id, void *p, void *data) -+{ -+ struct drm_i915_private *dev_priv = data; -+ struct i915_oa_config *oa_config = p; -+ -+ put_oa_config(dev_priv, oa_config); -+ -+ return 0; -+} -+ -+/** -+ * i915_perf_fini - Counter part to i915_perf_init() -+ * @dev_priv: i915 device instance -+ */ -+void i915_perf_fini(struct drm_i915_private *dev_priv) -+{ -+ if (!dev_priv->perf.initialized) -+ return; -+ -+ idr_for_each(&dev_priv->perf.metrics_idr, destroy_config, dev_priv); -+ idr_destroy(&dev_priv->perf.metrics_idr); -+ -+ unregister_sysctl_table(dev_priv->perf.sysctl_header); -+ -+ memset(&dev_priv->perf.oa.ops, 0, sizeof(dev_priv->perf.oa.ops)); -+ -+ dev_priv->perf.initialized = false; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_pmu.c b/drivers/gpu/drm/i915_legacy/i915_pmu.c -new file mode 100644 -index 000000000000..46a52da3db29 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_pmu.c -@@ -0,0 +1,1096 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2017-2018 Intel Corporation -+ */ -+ -+#include -+#include -+#include "i915_pmu.h" -+#include "intel_ringbuffer.h" -+#include "i915_drv.h" -+ -+/* Frequency for the sampling timer for events which need it. */ -+#define FREQUENCY 200 -+#define PERIOD max_t(u64, 10000, NSEC_PER_SEC / FREQUENCY) -+ -+#define ENGINE_SAMPLE_MASK \ -+ (BIT(I915_SAMPLE_BUSY) | \ -+ BIT(I915_SAMPLE_WAIT) | \ -+ BIT(I915_SAMPLE_SEMA)) -+ -+#define ENGINE_SAMPLE_BITS (1 << I915_PMU_SAMPLE_BITS) -+ -+static cpumask_t i915_pmu_cpumask; -+ -+static u8 engine_config_sample(u64 config) -+{ -+ return config & I915_PMU_SAMPLE_MASK; -+} -+ -+static u8 engine_event_sample(struct perf_event *event) -+{ -+ return engine_config_sample(event->attr.config); -+} -+ -+static u8 engine_event_class(struct perf_event *event) -+{ -+ return (event->attr.config >> I915_PMU_CLASS_SHIFT) & 0xff; -+} -+ -+static u8 engine_event_instance(struct perf_event *event) -+{ -+ return (event->attr.config >> I915_PMU_SAMPLE_BITS) & 0xff; -+} -+ -+static bool is_engine_config(u64 config) -+{ -+ return config < __I915_PMU_OTHER(0); -+} -+ -+static unsigned int config_enabled_bit(u64 config) -+{ -+ if (is_engine_config(config)) -+ return engine_config_sample(config); -+ else -+ return ENGINE_SAMPLE_BITS + (config - __I915_PMU_OTHER(0)); -+} -+ -+static u64 config_enabled_mask(u64 config) -+{ -+ return BIT_ULL(config_enabled_bit(config)); -+} -+ -+static bool is_engine_event(struct perf_event *event) -+{ -+ return is_engine_config(event->attr.config); -+} -+ -+static unsigned int event_enabled_bit(struct perf_event *event) -+{ -+ return config_enabled_bit(event->attr.config); -+} -+ -+static bool pmu_needs_timer(struct drm_i915_private *i915, bool gpu_active) -+{ -+ u64 enable; -+ -+ /* -+ * Only some counters need the sampling timer. -+ * -+ * We start with a bitmask of all currently enabled events. -+ */ -+ enable = i915->pmu.enable; -+ -+ /* -+ * Mask out all the ones which do not need the timer, or in -+ * other words keep all the ones that could need the timer. -+ */ -+ enable &= config_enabled_mask(I915_PMU_ACTUAL_FREQUENCY) | -+ config_enabled_mask(I915_PMU_REQUESTED_FREQUENCY) | -+ ENGINE_SAMPLE_MASK; -+ -+ /* -+ * When the GPU is idle per-engine counters do not need to be -+ * running so clear those bits out. -+ */ -+ if (!gpu_active) -+ enable &= ~ENGINE_SAMPLE_MASK; -+ /* -+ * Also there is software busyness tracking available we do not -+ * need the timer for I915_SAMPLE_BUSY counter. -+ * -+ * Use RCS as proxy for all engines. -+ */ -+ else if (intel_engine_supports_stats(i915->engine[RCS0])) -+ enable &= ~BIT(I915_SAMPLE_BUSY); -+ -+ /* -+ * If some bits remain it means we need the sampling timer running. -+ */ -+ return enable; -+} -+ -+void i915_pmu_gt_parked(struct drm_i915_private *i915) -+{ -+ if (!i915->pmu.base.event_init) -+ return; -+ -+ spin_lock_irq(&i915->pmu.lock); -+ /* -+ * Signal sampling timer to stop if only engine events are enabled and -+ * GPU went idle. -+ */ -+ i915->pmu.timer_enabled = pmu_needs_timer(i915, false); -+ spin_unlock_irq(&i915->pmu.lock); -+} -+ -+static void __i915_pmu_maybe_start_timer(struct drm_i915_private *i915) -+{ -+ if (!i915->pmu.timer_enabled && pmu_needs_timer(i915, true)) { -+ i915->pmu.timer_enabled = true; -+ i915->pmu.timer_last = ktime_get(); -+ hrtimer_start_range_ns(&i915->pmu.timer, -+ ns_to_ktime(PERIOD), 0, -+ HRTIMER_MODE_REL_PINNED); -+ } -+} -+ -+void i915_pmu_gt_unparked(struct drm_i915_private *i915) -+{ -+ if (!i915->pmu.base.event_init) -+ return; -+ -+ spin_lock_irq(&i915->pmu.lock); -+ /* -+ * Re-enable sampling timer when GPU goes active. -+ */ -+ __i915_pmu_maybe_start_timer(i915); -+ spin_unlock_irq(&i915->pmu.lock); -+} -+ -+static void -+add_sample(struct i915_pmu_sample *sample, u32 val) -+{ -+ sample->cur += val; -+} -+ -+static void -+engines_sample(struct drm_i915_private *dev_priv, unsigned int period_ns) -+{ -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ intel_wakeref_t wakeref; -+ unsigned long flags; -+ -+ if ((dev_priv->pmu.enable & ENGINE_SAMPLE_MASK) == 0) -+ return; -+ -+ wakeref = 0; -+ if (READ_ONCE(dev_priv->gt.awake)) -+ wakeref = intel_runtime_pm_get_if_in_use(dev_priv); -+ if (!wakeref) -+ return; -+ -+ spin_lock_irqsave(&dev_priv->uncore.lock, flags); -+ for_each_engine(engine, dev_priv, id) { -+ struct intel_engine_pmu *pmu = &engine->pmu; -+ bool busy; -+ u32 val; -+ -+ val = I915_READ_FW(RING_CTL(engine->mmio_base)); -+ if (val == 0) /* powerwell off => engine idle */ -+ continue; -+ -+ if (val & RING_WAIT) -+ add_sample(&pmu->sample[I915_SAMPLE_WAIT], period_ns); -+ if (val & RING_WAIT_SEMAPHORE) -+ add_sample(&pmu->sample[I915_SAMPLE_SEMA], period_ns); -+ -+ /* -+ * While waiting on a semaphore or event, MI_MODE reports the -+ * ring as idle. However, previously using the seqno, and with -+ * execlists sampling, we account for the ring waiting as the -+ * engine being busy. Therefore, we record the sample as being -+ * busy if either waiting or !idle. -+ */ -+ busy = val & (RING_WAIT_SEMAPHORE | RING_WAIT); -+ if (!busy) { -+ val = I915_READ_FW(RING_MI_MODE(engine->mmio_base)); -+ busy = !(val & MODE_IDLE); -+ } -+ if (busy) -+ add_sample(&pmu->sample[I915_SAMPLE_BUSY], period_ns); -+ } -+ spin_unlock_irqrestore(&dev_priv->uncore.lock, flags); -+ -+ intel_runtime_pm_put(dev_priv, wakeref); -+} -+ -+static void -+add_sample_mult(struct i915_pmu_sample *sample, u32 val, u32 mul) -+{ -+ sample->cur += mul_u32_u32(val, mul); -+} -+ -+static void -+frequency_sample(struct drm_i915_private *dev_priv, unsigned int period_ns) -+{ -+ if (dev_priv->pmu.enable & -+ config_enabled_mask(I915_PMU_ACTUAL_FREQUENCY)) { -+ u32 val; -+ -+ val = dev_priv->gt_pm.rps.cur_freq; -+ if (dev_priv->gt.awake) { -+ intel_wakeref_t wakeref; -+ -+ with_intel_runtime_pm_if_in_use(dev_priv, wakeref) -+ val = intel_get_cagf(dev_priv, -+ I915_READ_NOTRACE(GEN6_RPSTAT1)); -+ } -+ -+ add_sample_mult(&dev_priv->pmu.sample[__I915_SAMPLE_FREQ_ACT], -+ intel_gpu_freq(dev_priv, val), -+ period_ns / 1000); -+ } -+ -+ if (dev_priv->pmu.enable & -+ config_enabled_mask(I915_PMU_REQUESTED_FREQUENCY)) { -+ add_sample_mult(&dev_priv->pmu.sample[__I915_SAMPLE_FREQ_REQ], -+ intel_gpu_freq(dev_priv, -+ dev_priv->gt_pm.rps.cur_freq), -+ period_ns / 1000); -+ } -+} -+ -+static enum hrtimer_restart i915_sample(struct hrtimer *hrtimer) -+{ -+ struct drm_i915_private *i915 = -+ container_of(hrtimer, struct drm_i915_private, pmu.timer); -+ unsigned int period_ns; -+ ktime_t now; -+ -+ if (!READ_ONCE(i915->pmu.timer_enabled)) -+ return HRTIMER_NORESTART; -+ -+ now = ktime_get(); -+ period_ns = ktime_to_ns(ktime_sub(now, i915->pmu.timer_last)); -+ i915->pmu.timer_last = now; -+ -+ /* -+ * Strictly speaking the passed in period may not be 100% accurate for -+ * all internal calculation, since some amount of time can be spent on -+ * grabbing the forcewake. However the potential error from timer call- -+ * back delay greatly dominates this so we keep it simple. -+ */ -+ engines_sample(i915, period_ns); -+ frequency_sample(i915, period_ns); -+ -+ hrtimer_forward(hrtimer, now, ns_to_ktime(PERIOD)); -+ -+ return HRTIMER_RESTART; -+} -+ -+static u64 count_interrupts(struct drm_i915_private *i915) -+{ -+ /* open-coded kstat_irqs() */ -+ struct irq_desc *desc = irq_to_desc(i915->drm.pdev->irq); -+ u64 sum = 0; -+ int cpu; -+ -+ if (!desc || !desc->kstat_irqs) -+ return 0; -+ -+ for_each_possible_cpu(cpu) -+ sum += *per_cpu_ptr(desc->kstat_irqs, cpu); -+ -+ return sum; -+} -+ -+static void engine_event_destroy(struct perf_event *event) -+{ -+ struct drm_i915_private *i915 = -+ container_of(event->pmu, typeof(*i915), pmu.base); -+ struct intel_engine_cs *engine; -+ -+ engine = intel_engine_lookup_user(i915, -+ engine_event_class(event), -+ engine_event_instance(event)); -+ if (WARN_ON_ONCE(!engine)) -+ return; -+ -+ if (engine_event_sample(event) == I915_SAMPLE_BUSY && -+ intel_engine_supports_stats(engine)) -+ intel_disable_engine_stats(engine); -+} -+ -+static void i915_pmu_event_destroy(struct perf_event *event) -+{ -+ WARN_ON(event->parent); -+ -+ if (is_engine_event(event)) -+ engine_event_destroy(event); -+} -+ -+static int -+engine_event_status(struct intel_engine_cs *engine, -+ enum drm_i915_pmu_engine_sample sample) -+{ -+ switch (sample) { -+ case I915_SAMPLE_BUSY: -+ case I915_SAMPLE_WAIT: -+ break; -+ case I915_SAMPLE_SEMA: -+ if (INTEL_GEN(engine->i915) < 6) -+ return -ENODEV; -+ break; -+ default: -+ return -ENOENT; -+ } -+ -+ return 0; -+} -+ -+static int -+config_status(struct drm_i915_private *i915, u64 config) -+{ -+ switch (config) { -+ case I915_PMU_ACTUAL_FREQUENCY: -+ if (IS_VALLEYVIEW(i915) || IS_CHERRYVIEW(i915)) -+ /* Requires a mutex for sampling! */ -+ return -ENODEV; -+ /* Fall-through. */ -+ case I915_PMU_REQUESTED_FREQUENCY: -+ if (INTEL_GEN(i915) < 6) -+ return -ENODEV; -+ break; -+ case I915_PMU_INTERRUPTS: -+ break; -+ case I915_PMU_RC6_RESIDENCY: -+ if (!HAS_RC6(i915)) -+ return -ENODEV; -+ break; -+ default: -+ return -ENOENT; -+ } -+ -+ return 0; -+} -+ -+static int engine_event_init(struct perf_event *event) -+{ -+ struct drm_i915_private *i915 = -+ container_of(event->pmu, typeof(*i915), pmu.base); -+ struct intel_engine_cs *engine; -+ u8 sample; -+ int ret; -+ -+ engine = intel_engine_lookup_user(i915, engine_event_class(event), -+ engine_event_instance(event)); -+ if (!engine) -+ return -ENODEV; -+ -+ sample = engine_event_sample(event); -+ ret = engine_event_status(engine, sample); -+ if (ret) -+ return ret; -+ -+ if (sample == I915_SAMPLE_BUSY && intel_engine_supports_stats(engine)) -+ ret = intel_enable_engine_stats(engine); -+ -+ return ret; -+} -+ -+static int i915_pmu_event_init(struct perf_event *event) -+{ -+ struct drm_i915_private *i915 = -+ container_of(event->pmu, typeof(*i915), pmu.base); -+ int ret; -+ -+ if (event->attr.type != event->pmu->type) -+ return -ENOENT; -+ -+ /* unsupported modes and filters */ -+ if (event->attr.sample_period) /* no sampling */ -+ return -EINVAL; -+ -+ if (has_branch_stack(event)) -+ return -EOPNOTSUPP; -+ -+ if (event->cpu < 0) -+ return -EINVAL; -+ -+ /* only allow running on one cpu at a time */ -+ if (!cpumask_test_cpu(event->cpu, &i915_pmu_cpumask)) -+ return -EINVAL; -+ -+ if (is_engine_event(event)) -+ ret = engine_event_init(event); -+ else -+ ret = config_status(i915, event->attr.config); -+ if (ret) -+ return ret; -+ -+ if (!event->parent) -+ event->destroy = i915_pmu_event_destroy; -+ -+ return 0; -+} -+ -+static u64 __get_rc6(struct drm_i915_private *i915) -+{ -+ u64 val; -+ -+ val = intel_rc6_residency_ns(i915, -+ IS_VALLEYVIEW(i915) ? -+ VLV_GT_RENDER_RC6 : -+ GEN6_GT_GFX_RC6); -+ -+ if (HAS_RC6p(i915)) -+ val += intel_rc6_residency_ns(i915, GEN6_GT_GFX_RC6p); -+ -+ if (HAS_RC6pp(i915)) -+ val += intel_rc6_residency_ns(i915, GEN6_GT_GFX_RC6pp); -+ -+ return val; -+} -+ -+static u64 get_rc6(struct drm_i915_private *i915) -+{ -+#if IS_ENABLED(CONFIG_PM) -+ intel_wakeref_t wakeref; -+ unsigned long flags; -+ u64 val; -+ -+ wakeref = intel_runtime_pm_get_if_in_use(i915); -+ if (wakeref) { -+ val = __get_rc6(i915); -+ intel_runtime_pm_put(i915, wakeref); -+ -+ /* -+ * If we are coming back from being runtime suspended we must -+ * be careful not to report a larger value than returned -+ * previously. -+ */ -+ -+ spin_lock_irqsave(&i915->pmu.lock, flags); -+ -+ if (val >= i915->pmu.sample[__I915_SAMPLE_RC6_ESTIMATED].cur) { -+ i915->pmu.sample[__I915_SAMPLE_RC6_ESTIMATED].cur = 0; -+ i915->pmu.sample[__I915_SAMPLE_RC6].cur = val; -+ } else { -+ val = i915->pmu.sample[__I915_SAMPLE_RC6_ESTIMATED].cur; -+ } -+ -+ spin_unlock_irqrestore(&i915->pmu.lock, flags); -+ } else { -+ struct pci_dev *pdev = i915->drm.pdev; -+ struct device *kdev = &pdev->dev; -+ -+ /* -+ * We are runtime suspended. -+ * -+ * Report the delta from when the device was suspended to now, -+ * on top of the last known real value, as the approximated RC6 -+ * counter value. -+ */ -+ spin_lock_irqsave(&i915->pmu.lock, flags); -+ -+ /* -+ * After the above branch intel_runtime_pm_get_if_in_use failed -+ * to get the runtime PM reference we cannot assume we are in -+ * runtime suspend since we can either: a) race with coming out -+ * of it before we took the power.lock, or b) there are other -+ * states than suspended which can bring us here. -+ * -+ * We need to double-check that we are indeed currently runtime -+ * suspended and if not we cannot do better than report the last -+ * known RC6 value. -+ */ -+ if (pm_runtime_status_suspended(kdev)) { -+ val = pm_runtime_suspended_time(kdev); -+ -+ if (!i915->pmu.sample[__I915_SAMPLE_RC6_ESTIMATED].cur) -+ i915->pmu.suspended_time_last = val; -+ -+ val -= i915->pmu.suspended_time_last; -+ val += i915->pmu.sample[__I915_SAMPLE_RC6].cur; -+ -+ i915->pmu.sample[__I915_SAMPLE_RC6_ESTIMATED].cur = val; -+ } else if (i915->pmu.sample[__I915_SAMPLE_RC6_ESTIMATED].cur) { -+ val = i915->pmu.sample[__I915_SAMPLE_RC6_ESTIMATED].cur; -+ } else { -+ val = i915->pmu.sample[__I915_SAMPLE_RC6].cur; -+ } -+ -+ spin_unlock_irqrestore(&i915->pmu.lock, flags); -+ } -+ -+ return val; -+#else -+ return __get_rc6(i915); -+#endif -+} -+ -+static u64 __i915_pmu_event_read(struct perf_event *event) -+{ -+ struct drm_i915_private *i915 = -+ container_of(event->pmu, typeof(*i915), pmu.base); -+ u64 val = 0; -+ -+ if (is_engine_event(event)) { -+ u8 sample = engine_event_sample(event); -+ struct intel_engine_cs *engine; -+ -+ engine = intel_engine_lookup_user(i915, -+ engine_event_class(event), -+ engine_event_instance(event)); -+ -+ if (WARN_ON_ONCE(!engine)) { -+ /* Do nothing */ -+ } else if (sample == I915_SAMPLE_BUSY && -+ intel_engine_supports_stats(engine)) { -+ val = ktime_to_ns(intel_engine_get_busy_time(engine)); -+ } else { -+ val = engine->pmu.sample[sample].cur; -+ } -+ } else { -+ switch (event->attr.config) { -+ case I915_PMU_ACTUAL_FREQUENCY: -+ val = -+ div_u64(i915->pmu.sample[__I915_SAMPLE_FREQ_ACT].cur, -+ USEC_PER_SEC /* to MHz */); -+ break; -+ case I915_PMU_REQUESTED_FREQUENCY: -+ val = -+ div_u64(i915->pmu.sample[__I915_SAMPLE_FREQ_REQ].cur, -+ USEC_PER_SEC /* to MHz */); -+ break; -+ case I915_PMU_INTERRUPTS: -+ val = count_interrupts(i915); -+ break; -+ case I915_PMU_RC6_RESIDENCY: -+ val = get_rc6(i915); -+ break; -+ } -+ } -+ -+ return val; -+} -+ -+static void i915_pmu_event_read(struct perf_event *event) -+{ -+ struct hw_perf_event *hwc = &event->hw; -+ u64 prev, new; -+ -+again: -+ prev = local64_read(&hwc->prev_count); -+ new = __i915_pmu_event_read(event); -+ -+ if (local64_cmpxchg(&hwc->prev_count, prev, new) != prev) -+ goto again; -+ -+ local64_add(new - prev, &event->count); -+} -+ -+static void i915_pmu_enable(struct perf_event *event) -+{ -+ struct drm_i915_private *i915 = -+ container_of(event->pmu, typeof(*i915), pmu.base); -+ unsigned int bit = event_enabled_bit(event); -+ unsigned long flags; -+ -+ spin_lock_irqsave(&i915->pmu.lock, flags); -+ -+ /* -+ * Update the bitmask of enabled events and increment -+ * the event reference counter. -+ */ -+ BUILD_BUG_ON(ARRAY_SIZE(i915->pmu.enable_count) != I915_PMU_MASK_BITS); -+ GEM_BUG_ON(bit >= ARRAY_SIZE(i915->pmu.enable_count)); -+ GEM_BUG_ON(i915->pmu.enable_count[bit] == ~0); -+ i915->pmu.enable |= BIT_ULL(bit); -+ i915->pmu.enable_count[bit]++; -+ -+ /* -+ * Start the sampling timer if needed and not already enabled. -+ */ -+ __i915_pmu_maybe_start_timer(i915); -+ -+ /* -+ * For per-engine events the bitmask and reference counting -+ * is stored per engine. -+ */ -+ if (is_engine_event(event)) { -+ u8 sample = engine_event_sample(event); -+ struct intel_engine_cs *engine; -+ -+ engine = intel_engine_lookup_user(i915, -+ engine_event_class(event), -+ engine_event_instance(event)); -+ -+ BUILD_BUG_ON(ARRAY_SIZE(engine->pmu.enable_count) != -+ I915_ENGINE_SAMPLE_COUNT); -+ BUILD_BUG_ON(ARRAY_SIZE(engine->pmu.sample) != -+ I915_ENGINE_SAMPLE_COUNT); -+ GEM_BUG_ON(sample >= ARRAY_SIZE(engine->pmu.enable_count)); -+ GEM_BUG_ON(sample >= ARRAY_SIZE(engine->pmu.sample)); -+ GEM_BUG_ON(engine->pmu.enable_count[sample] == ~0); -+ -+ engine->pmu.enable |= BIT(sample); -+ engine->pmu.enable_count[sample]++; -+ } -+ -+ spin_unlock_irqrestore(&i915->pmu.lock, flags); -+ -+ /* -+ * Store the current counter value so we can report the correct delta -+ * for all listeners. Even when the event was already enabled and has -+ * an existing non-zero value. -+ */ -+ local64_set(&event->hw.prev_count, __i915_pmu_event_read(event)); -+} -+ -+static void i915_pmu_disable(struct perf_event *event) -+{ -+ struct drm_i915_private *i915 = -+ container_of(event->pmu, typeof(*i915), pmu.base); -+ unsigned int bit = event_enabled_bit(event); -+ unsigned long flags; -+ -+ spin_lock_irqsave(&i915->pmu.lock, flags); -+ -+ if (is_engine_event(event)) { -+ u8 sample = engine_event_sample(event); -+ struct intel_engine_cs *engine; -+ -+ engine = intel_engine_lookup_user(i915, -+ engine_event_class(event), -+ engine_event_instance(event)); -+ -+ GEM_BUG_ON(sample >= ARRAY_SIZE(engine->pmu.enable_count)); -+ GEM_BUG_ON(sample >= ARRAY_SIZE(engine->pmu.sample)); -+ GEM_BUG_ON(engine->pmu.enable_count[sample] == 0); -+ -+ /* -+ * Decrement the reference count and clear the enabled -+ * bitmask when the last listener on an event goes away. -+ */ -+ if (--engine->pmu.enable_count[sample] == 0) -+ engine->pmu.enable &= ~BIT(sample); -+ } -+ -+ GEM_BUG_ON(bit >= ARRAY_SIZE(i915->pmu.enable_count)); -+ GEM_BUG_ON(i915->pmu.enable_count[bit] == 0); -+ /* -+ * Decrement the reference count and clear the enabled -+ * bitmask when the last listener on an event goes away. -+ */ -+ if (--i915->pmu.enable_count[bit] == 0) { -+ i915->pmu.enable &= ~BIT_ULL(bit); -+ i915->pmu.timer_enabled &= pmu_needs_timer(i915, true); -+ } -+ -+ spin_unlock_irqrestore(&i915->pmu.lock, flags); -+} -+ -+static void i915_pmu_event_start(struct perf_event *event, int flags) -+{ -+ i915_pmu_enable(event); -+ event->hw.state = 0; -+} -+ -+static void i915_pmu_event_stop(struct perf_event *event, int flags) -+{ -+ if (flags & PERF_EF_UPDATE) -+ i915_pmu_event_read(event); -+ i915_pmu_disable(event); -+ event->hw.state = PERF_HES_STOPPED; -+} -+ -+static int i915_pmu_event_add(struct perf_event *event, int flags) -+{ -+ if (flags & PERF_EF_START) -+ i915_pmu_event_start(event, flags); -+ -+ return 0; -+} -+ -+static void i915_pmu_event_del(struct perf_event *event, int flags) -+{ -+ i915_pmu_event_stop(event, PERF_EF_UPDATE); -+} -+ -+static int i915_pmu_event_event_idx(struct perf_event *event) -+{ -+ return 0; -+} -+ -+struct i915_str_attribute { -+ struct device_attribute attr; -+ const char *str; -+}; -+ -+static ssize_t i915_pmu_format_show(struct device *dev, -+ struct device_attribute *attr, char *buf) -+{ -+ struct i915_str_attribute *eattr; -+ -+ eattr = container_of(attr, struct i915_str_attribute, attr); -+ return sprintf(buf, "%s\n", eattr->str); -+} -+ -+#define I915_PMU_FORMAT_ATTR(_name, _config) \ -+ (&((struct i915_str_attribute[]) { \ -+ { .attr = __ATTR(_name, 0444, i915_pmu_format_show, NULL), \ -+ .str = _config, } \ -+ })[0].attr.attr) -+ -+static struct attribute *i915_pmu_format_attrs[] = { -+ I915_PMU_FORMAT_ATTR(i915_eventid, "config:0-20"), -+ NULL, -+}; -+ -+static const struct attribute_group i915_pmu_format_attr_group = { -+ .name = "format", -+ .attrs = i915_pmu_format_attrs, -+}; -+ -+struct i915_ext_attribute { -+ struct device_attribute attr; -+ unsigned long val; -+}; -+ -+static ssize_t i915_pmu_event_show(struct device *dev, -+ struct device_attribute *attr, char *buf) -+{ -+ struct i915_ext_attribute *eattr; -+ -+ eattr = container_of(attr, struct i915_ext_attribute, attr); -+ return sprintf(buf, "config=0x%lx\n", eattr->val); -+} -+ -+static struct attribute_group i915_pmu_events_attr_group = { -+ .name = "events", -+ /* Patch in attrs at runtime. */ -+}; -+ -+static ssize_t -+i915_pmu_get_attr_cpumask(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ return cpumap_print_to_pagebuf(true, buf, &i915_pmu_cpumask); -+} -+ -+static DEVICE_ATTR(cpumask, 0444, i915_pmu_get_attr_cpumask, NULL); -+ -+static struct attribute *i915_cpumask_attrs[] = { -+ &dev_attr_cpumask.attr, -+ NULL, -+}; -+ -+static const struct attribute_group i915_pmu_cpumask_attr_group = { -+ .attrs = i915_cpumask_attrs, -+}; -+ -+static const struct attribute_group *i915_pmu_attr_groups[] = { -+ &i915_pmu_format_attr_group, -+ &i915_pmu_events_attr_group, -+ &i915_pmu_cpumask_attr_group, -+ NULL -+}; -+ -+#define __event(__config, __name, __unit) \ -+{ \ -+ .config = (__config), \ -+ .name = (__name), \ -+ .unit = (__unit), \ -+} -+ -+#define __engine_event(__sample, __name) \ -+{ \ -+ .sample = (__sample), \ -+ .name = (__name), \ -+} -+ -+static struct i915_ext_attribute * -+add_i915_attr(struct i915_ext_attribute *attr, const char *name, u64 config) -+{ -+ sysfs_attr_init(&attr->attr.attr); -+ attr->attr.attr.name = name; -+ attr->attr.attr.mode = 0444; -+ attr->attr.show = i915_pmu_event_show; -+ attr->val = config; -+ -+ return ++attr; -+} -+ -+static struct perf_pmu_events_attr * -+add_pmu_attr(struct perf_pmu_events_attr *attr, const char *name, -+ const char *str) -+{ -+ sysfs_attr_init(&attr->attr.attr); -+ attr->attr.attr.name = name; -+ attr->attr.attr.mode = 0444; -+ attr->attr.show = perf_event_sysfs_show; -+ attr->event_str = str; -+ -+ return ++attr; -+} -+ -+static struct attribute ** -+create_event_attributes(struct drm_i915_private *i915) -+{ -+ static const struct { -+ u64 config; -+ const char *name; -+ const char *unit; -+ } events[] = { -+ __event(I915_PMU_ACTUAL_FREQUENCY, "actual-frequency", "MHz"), -+ __event(I915_PMU_REQUESTED_FREQUENCY, "requested-frequency", "MHz"), -+ __event(I915_PMU_INTERRUPTS, "interrupts", NULL), -+ __event(I915_PMU_RC6_RESIDENCY, "rc6-residency", "ns"), -+ }; -+ static const struct { -+ enum drm_i915_pmu_engine_sample sample; -+ char *name; -+ } engine_events[] = { -+ __engine_event(I915_SAMPLE_BUSY, "busy"), -+ __engine_event(I915_SAMPLE_SEMA, "sema"), -+ __engine_event(I915_SAMPLE_WAIT, "wait"), -+ }; -+ unsigned int count = 0; -+ struct perf_pmu_events_attr *pmu_attr = NULL, *pmu_iter; -+ struct i915_ext_attribute *i915_attr = NULL, *i915_iter; -+ struct attribute **attr = NULL, **attr_iter; -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ unsigned int i; -+ -+ /* Count how many counters we will be exposing. */ -+ for (i = 0; i < ARRAY_SIZE(events); i++) { -+ if (!config_status(i915, events[i].config)) -+ count++; -+ } -+ -+ for_each_engine(engine, i915, id) { -+ for (i = 0; i < ARRAY_SIZE(engine_events); i++) { -+ if (!engine_event_status(engine, -+ engine_events[i].sample)) -+ count++; -+ } -+ } -+ -+ /* Allocate attribute objects and table. */ -+ i915_attr = kcalloc(count, sizeof(*i915_attr), GFP_KERNEL); -+ if (!i915_attr) -+ goto err_alloc; -+ -+ pmu_attr = kcalloc(count, sizeof(*pmu_attr), GFP_KERNEL); -+ if (!pmu_attr) -+ goto err_alloc; -+ -+ /* Max one pointer of each attribute type plus a termination entry. */ -+ attr = kcalloc(count * 2 + 1, sizeof(*attr), GFP_KERNEL); -+ if (!attr) -+ goto err_alloc; -+ -+ i915_iter = i915_attr; -+ pmu_iter = pmu_attr; -+ attr_iter = attr; -+ -+ /* Initialize supported non-engine counters. */ -+ for (i = 0; i < ARRAY_SIZE(events); i++) { -+ char *str; -+ -+ if (config_status(i915, events[i].config)) -+ continue; -+ -+ str = kstrdup(events[i].name, GFP_KERNEL); -+ if (!str) -+ goto err; -+ -+ *attr_iter++ = &i915_iter->attr.attr; -+ i915_iter = add_i915_attr(i915_iter, str, events[i].config); -+ -+ if (events[i].unit) { -+ str = kasprintf(GFP_KERNEL, "%s.unit", events[i].name); -+ if (!str) -+ goto err; -+ -+ *attr_iter++ = &pmu_iter->attr.attr; -+ pmu_iter = add_pmu_attr(pmu_iter, str, events[i].unit); -+ } -+ } -+ -+ /* Initialize supported engine counters. */ -+ for_each_engine(engine, i915, id) { -+ for (i = 0; i < ARRAY_SIZE(engine_events); i++) { -+ char *str; -+ -+ if (engine_event_status(engine, -+ engine_events[i].sample)) -+ continue; -+ -+ str = kasprintf(GFP_KERNEL, "%s-%s", -+ engine->name, engine_events[i].name); -+ if (!str) -+ goto err; -+ -+ *attr_iter++ = &i915_iter->attr.attr; -+ i915_iter = -+ add_i915_attr(i915_iter, str, -+ __I915_PMU_ENGINE(engine->uabi_class, -+ engine->instance, -+ engine_events[i].sample)); -+ -+ str = kasprintf(GFP_KERNEL, "%s-%s.unit", -+ engine->name, engine_events[i].name); -+ if (!str) -+ goto err; -+ -+ *attr_iter++ = &pmu_iter->attr.attr; -+ pmu_iter = add_pmu_attr(pmu_iter, str, "ns"); -+ } -+ } -+ -+ i915->pmu.i915_attr = i915_attr; -+ i915->pmu.pmu_attr = pmu_attr; -+ -+ return attr; -+ -+err:; -+ for (attr_iter = attr; *attr_iter; attr_iter++) -+ kfree((*attr_iter)->name); -+ -+err_alloc: -+ kfree(attr); -+ kfree(i915_attr); -+ kfree(pmu_attr); -+ -+ return NULL; -+} -+ -+static void free_event_attributes(struct drm_i915_private *i915) -+{ -+ struct attribute **attr_iter = i915_pmu_events_attr_group.attrs; -+ -+ for (; *attr_iter; attr_iter++) -+ kfree((*attr_iter)->name); -+ -+ kfree(i915_pmu_events_attr_group.attrs); -+ kfree(i915->pmu.i915_attr); -+ kfree(i915->pmu.pmu_attr); -+ -+ i915_pmu_events_attr_group.attrs = NULL; -+ i915->pmu.i915_attr = NULL; -+ i915->pmu.pmu_attr = NULL; -+} -+ -+static int i915_pmu_cpu_online(unsigned int cpu, struct hlist_node *node) -+{ -+ struct i915_pmu *pmu = hlist_entry_safe(node, typeof(*pmu), node); -+ -+ GEM_BUG_ON(!pmu->base.event_init); -+ -+ /* Select the first online CPU as a designated reader. */ -+ if (!cpumask_weight(&i915_pmu_cpumask)) -+ cpumask_set_cpu(cpu, &i915_pmu_cpumask); -+ -+ return 0; -+} -+ -+static int i915_pmu_cpu_offline(unsigned int cpu, struct hlist_node *node) -+{ -+ struct i915_pmu *pmu = hlist_entry_safe(node, typeof(*pmu), node); -+ unsigned int target; -+ -+ GEM_BUG_ON(!pmu->base.event_init); -+ -+ if (cpumask_test_and_clear_cpu(cpu, &i915_pmu_cpumask)) { -+ target = cpumask_any_but(topology_sibling_cpumask(cpu), cpu); -+ /* Migrate events if there is a valid target */ -+ if (target < nr_cpu_ids) { -+ cpumask_set_cpu(target, &i915_pmu_cpumask); -+ perf_pmu_migrate_context(&pmu->base, cpu, target); -+ } -+ } -+ -+ return 0; -+} -+ -+static enum cpuhp_state cpuhp_slot = CPUHP_INVALID; -+ -+static int i915_pmu_register_cpuhp_state(struct drm_i915_private *i915) -+{ -+ enum cpuhp_state slot; -+ int ret; -+ -+ ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, -+ "perf/x86/intel/i915:online", -+ i915_pmu_cpu_online, -+ i915_pmu_cpu_offline); -+ if (ret < 0) -+ return ret; -+ -+ slot = ret; -+ ret = cpuhp_state_add_instance(slot, &i915->pmu.node); -+ if (ret) { -+ cpuhp_remove_multi_state(slot); -+ return ret; -+ } -+ -+ cpuhp_slot = slot; -+ return 0; -+} -+ -+static void i915_pmu_unregister_cpuhp_state(struct drm_i915_private *i915) -+{ -+ WARN_ON(cpuhp_slot == CPUHP_INVALID); -+ WARN_ON(cpuhp_state_remove_instance(cpuhp_slot, &i915->pmu.node)); -+ cpuhp_remove_multi_state(cpuhp_slot); -+} -+ -+void i915_pmu_register(struct drm_i915_private *i915) -+{ -+ int ret; -+ -+ if (INTEL_GEN(i915) <= 2) { -+ DRM_INFO("PMU not supported for this GPU."); -+ return; -+ } -+ -+ i915_pmu_events_attr_group.attrs = create_event_attributes(i915); -+ if (!i915_pmu_events_attr_group.attrs) { -+ ret = -ENOMEM; -+ goto err; -+ } -+ -+ i915->pmu.base.attr_groups = i915_pmu_attr_groups; -+ i915->pmu.base.task_ctx_nr = perf_invalid_context; -+ i915->pmu.base.event_init = i915_pmu_event_init; -+ i915->pmu.base.add = i915_pmu_event_add; -+ i915->pmu.base.del = i915_pmu_event_del; -+ i915->pmu.base.start = i915_pmu_event_start; -+ i915->pmu.base.stop = i915_pmu_event_stop; -+ i915->pmu.base.read = i915_pmu_event_read; -+ i915->pmu.base.event_idx = i915_pmu_event_event_idx; -+ -+ spin_lock_init(&i915->pmu.lock); -+ hrtimer_init(&i915->pmu.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); -+ i915->pmu.timer.function = i915_sample; -+ -+ ret = perf_pmu_register(&i915->pmu.base, "i915", -1); -+ if (ret) -+ goto err; -+ -+ ret = i915_pmu_register_cpuhp_state(i915); -+ if (ret) -+ goto err_unreg; -+ -+ return; -+ -+err_unreg: -+ perf_pmu_unregister(&i915->pmu.base); -+err: -+ i915->pmu.base.event_init = NULL; -+ free_event_attributes(i915); -+ DRM_NOTE("Failed to register PMU! (err=%d)\n", ret); -+} -+ -+void i915_pmu_unregister(struct drm_i915_private *i915) -+{ -+ if (!i915->pmu.base.event_init) -+ return; -+ -+ WARN_ON(i915->pmu.enable); -+ -+ hrtimer_cancel(&i915->pmu.timer); -+ -+ i915_pmu_unregister_cpuhp_state(i915); -+ -+ perf_pmu_unregister(&i915->pmu.base); -+ i915->pmu.base.event_init = NULL; -+ free_event_attributes(i915); -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_pmu.h b/drivers/gpu/drm/i915_legacy/i915_pmu.h -new file mode 100644 -index 000000000000..4fc4f2478301 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_pmu.h -@@ -0,0 +1,125 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2017-2018 Intel Corporation -+ */ -+ -+#ifndef __I915_PMU_H__ -+#define __I915_PMU_H__ -+ -+#include -+#include -+#include -+#include -+ -+struct drm_i915_private; -+ -+enum { -+ __I915_SAMPLE_FREQ_ACT = 0, -+ __I915_SAMPLE_FREQ_REQ, -+ __I915_SAMPLE_RC6, -+ __I915_SAMPLE_RC6_ESTIMATED, -+ __I915_NUM_PMU_SAMPLERS -+}; -+ -+/** -+ * How many different events we track in the global PMU mask. -+ * -+ * It is also used to know to needed number of event reference counters. -+ */ -+#define I915_PMU_MASK_BITS \ -+ ((1 << I915_PMU_SAMPLE_BITS) + \ -+ (I915_PMU_LAST + 1 - __I915_PMU_OTHER(0))) -+ -+#define I915_ENGINE_SAMPLE_COUNT (I915_SAMPLE_SEMA + 1) -+ -+struct i915_pmu_sample { -+ u64 cur; -+}; -+ -+struct i915_pmu { -+ /** -+ * @node: List node for CPU hotplug handling. -+ */ -+ struct hlist_node node; -+ /** -+ * @base: PMU base. -+ */ -+ struct pmu base; -+ /** -+ * @lock: Lock protecting enable mask and ref count handling. -+ */ -+ spinlock_t lock; -+ /** -+ * @timer: Timer for internal i915 PMU sampling. -+ */ -+ struct hrtimer timer; -+ /** -+ * @enable: Bitmask of all currently enabled events. -+ * -+ * Bits are derived from uAPI event numbers in a way that low 16 bits -+ * correspond to engine event _sample_ _type_ (I915_SAMPLE_QUEUED is -+ * bit 0), and higher bits correspond to other events (for instance -+ * I915_PMU_ACTUAL_FREQUENCY is bit 16 etc). -+ * -+ * In other words, low 16 bits are not per engine but per engine -+ * sampler type, while the upper bits are directly mapped to other -+ * event types. -+ */ -+ u64 enable; -+ -+ /** -+ * @timer_last: -+ * -+ * Timestmap of the previous timer invocation. -+ */ -+ ktime_t timer_last; -+ -+ /** -+ * @enable_count: Reference counts for the enabled events. -+ * -+ * Array indices are mapped in the same way as bits in the @enable field -+ * and they are used to control sampling on/off when multiple clients -+ * are using the PMU API. -+ */ -+ unsigned int enable_count[I915_PMU_MASK_BITS]; -+ /** -+ * @timer_enabled: Should the internal sampling timer be running. -+ */ -+ bool timer_enabled; -+ /** -+ * @sample: Current and previous (raw) counters for sampling events. -+ * -+ * These counters are updated from the i915 PMU sampling timer. -+ * -+ * Only global counters are held here, while the per-engine ones are in -+ * struct intel_engine_cs. -+ */ -+ struct i915_pmu_sample sample[__I915_NUM_PMU_SAMPLERS]; -+ /** -+ * @suspended_time_last: Cached suspend time from PM core. -+ */ -+ u64 suspended_time_last; -+ /** -+ * @i915_attr: Memory block holding device attributes. -+ */ -+ void *i915_attr; -+ /** -+ * @pmu_attr: Memory block holding device attributes. -+ */ -+ void *pmu_attr; -+}; -+ -+#ifdef CONFIG_PERF_EVENTS -+void i915_pmu_register(struct drm_i915_private *i915); -+void i915_pmu_unregister(struct drm_i915_private *i915); -+void i915_pmu_gt_parked(struct drm_i915_private *i915); -+void i915_pmu_gt_unparked(struct drm_i915_private *i915); -+#else -+static inline void i915_pmu_register(struct drm_i915_private *i915) {} -+static inline void i915_pmu_unregister(struct drm_i915_private *i915) {} -+static inline void i915_pmu_gt_parked(struct drm_i915_private *i915) {} -+static inline void i915_pmu_gt_unparked(struct drm_i915_private *i915) {} -+#endif -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_priolist_types.h b/drivers/gpu/drm/i915_legacy/i915_priolist_types.h -new file mode 100644 -index 000000000000..49709de69875 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_priolist_types.h -@@ -0,0 +1,41 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ */ -+ -+#ifndef _I915_PRIOLIST_TYPES_H_ -+#define _I915_PRIOLIST_TYPES_H_ -+ -+#include -+#include -+ -+#include -+ -+enum { -+ I915_PRIORITY_MIN = I915_CONTEXT_MIN_USER_PRIORITY - 1, -+ I915_PRIORITY_NORMAL = I915_CONTEXT_DEFAULT_PRIORITY, -+ I915_PRIORITY_MAX = I915_CONTEXT_MAX_USER_PRIORITY + 1, -+ -+ I915_PRIORITY_INVALID = INT_MIN -+}; -+ -+#define I915_USER_PRIORITY_SHIFT 2 -+#define I915_USER_PRIORITY(x) ((x) << I915_USER_PRIORITY_SHIFT) -+ -+#define I915_PRIORITY_COUNT BIT(I915_USER_PRIORITY_SHIFT) -+#define I915_PRIORITY_MASK (I915_PRIORITY_COUNT - 1) -+ -+#define I915_PRIORITY_WAIT ((u8)BIT(0)) -+#define I915_PRIORITY_NOSEMAPHORE ((u8)BIT(1)) -+ -+#define __NO_PREEMPTION (I915_PRIORITY_WAIT) -+ -+struct i915_priolist { -+ struct list_head requests[I915_PRIORITY_COUNT]; -+ struct rb_node node; -+ unsigned long used; -+ int priority; -+}; -+ -+#endif /* _I915_PRIOLIST_TYPES_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_pvinfo.h b/drivers/gpu/drm/i915_legacy/i915_pvinfo.h -new file mode 100644 -index 000000000000..969e514916ab ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_pvinfo.h -@@ -0,0 +1,120 @@ -+/* -+ * Copyright(c) 2011-2016 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#ifndef _I915_PVINFO_H_ -+#define _I915_PVINFO_H_ -+ -+/* The MMIO offset of the shared info between guest and host emulator */ -+#define VGT_PVINFO_PAGE 0x78000 -+#define VGT_PVINFO_SIZE 0x1000 -+ -+/* -+ * The following structure pages are defined in GEN MMIO space -+ * for virtualization. (One page for now) -+ */ -+#define VGT_MAGIC 0x4776544776544776ULL /* 'vGTvGTvG' */ -+#define VGT_VERSION_MAJOR 1 -+#define VGT_VERSION_MINOR 0 -+ -+/* -+ * notifications from guest to vgpu device model -+ */ -+enum vgt_g2v_type { -+ VGT_G2V_PPGTT_L3_PAGE_TABLE_CREATE = 2, -+ VGT_G2V_PPGTT_L3_PAGE_TABLE_DESTROY, -+ VGT_G2V_PPGTT_L4_PAGE_TABLE_CREATE, -+ VGT_G2V_PPGTT_L4_PAGE_TABLE_DESTROY, -+ VGT_G2V_EXECLIST_CONTEXT_CREATE, -+ VGT_G2V_EXECLIST_CONTEXT_DESTROY, -+ VGT_G2V_MAX, -+}; -+ -+/* -+ * VGT capabilities type -+ */ -+#define VGT_CAPS_FULL_PPGTT BIT(2) -+#define VGT_CAPS_HWSP_EMULATION BIT(3) -+#define VGT_CAPS_HUGE_GTT BIT(4) -+ -+struct vgt_if { -+ u64 magic; /* VGT_MAGIC */ -+ u16 version_major; -+ u16 version_minor; -+ u32 vgt_id; /* ID of vGT instance */ -+ u32 vgt_caps; /* VGT capabilities */ -+ u32 rsv1[11]; /* pad to offset 0x40 */ -+ /* -+ * Data structure to describe the balooning info of resources. -+ * Each VM can only have one portion of continuous area for now. -+ * (May support scattered resource in future) -+ * (starting from offset 0x40) -+ */ -+ struct { -+ /* Aperture register balooning */ -+ struct { -+ u32 base; -+ u32 size; -+ } mappable_gmadr; /* aperture */ -+ /* GMADR register balooning */ -+ struct { -+ u32 base; -+ u32 size; -+ } nonmappable_gmadr; /* non aperture */ -+ /* allowed fence registers */ -+ u32 fence_num; -+ u32 rsv2[3]; -+ } avail_rs; /* available/assigned resource */ -+ u32 rsv3[0x200 - 24]; /* pad to half page */ -+ /* -+ * The bottom half page is for response from Gfx driver to hypervisor. -+ */ -+ u32 rsv4; -+ u32 display_ready; /* ready for display owner switch */ -+ -+ u32 rsv5[4]; -+ -+ u32 g2v_notify; -+ u32 rsv6[5]; -+ -+ u32 cursor_x_hot; -+ u32 cursor_y_hot; -+ -+ struct { -+ u32 lo; -+ u32 hi; -+ } pdp[4]; -+ -+ u32 execlist_context_descriptor_lo; -+ u32 execlist_context_descriptor_hi; -+ -+ u32 rsv7[0x200 - 24]; /* pad to one page */ -+} __packed; -+ -+#define vgtif_reg(x) \ -+ _MMIO((VGT_PVINFO_PAGE + offsetof(struct vgt_if, x))) -+ -+/* vGPU display status to be used by the host side */ -+#define VGT_DRV_DISPLAY_NOT_READY 0 -+#define VGT_DRV_DISPLAY_READY 1 /* ready for display switch */ -+ -+#endif /* _I915_PVINFO_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_query.c b/drivers/gpu/drm/i915_legacy/i915_query.c -new file mode 100644 -index 000000000000..782183b78f49 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_query.c -@@ -0,0 +1,144 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_query.h" -+#include -+ -+static int copy_query_item(void *query_hdr, size_t query_sz, -+ u32 total_length, -+ struct drm_i915_query_item *query_item) -+{ -+ if (query_item->length == 0) -+ return total_length; -+ -+ if (query_item->length < total_length) -+ return -EINVAL; -+ -+ if (copy_from_user(query_hdr, u64_to_user_ptr(query_item->data_ptr), -+ query_sz)) -+ return -EFAULT; -+ -+ if (!access_ok(u64_to_user_ptr(query_item->data_ptr), -+ total_length)) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+static int query_topology_info(struct drm_i915_private *dev_priv, -+ struct drm_i915_query_item *query_item) -+{ -+ const struct sseu_dev_info *sseu = &RUNTIME_INFO(dev_priv)->sseu; -+ struct drm_i915_query_topology_info topo; -+ u32 slice_length, subslice_length, eu_length, total_length; -+ int ret; -+ -+ if (query_item->flags != 0) -+ return -EINVAL; -+ -+ if (sseu->max_slices == 0) -+ return -ENODEV; -+ -+ BUILD_BUG_ON(sizeof(u8) != sizeof(sseu->slice_mask)); -+ -+ slice_length = sizeof(sseu->slice_mask); -+ subslice_length = sseu->max_slices * -+ DIV_ROUND_UP(sseu->max_subslices, BITS_PER_BYTE); -+ eu_length = sseu->max_slices * sseu->max_subslices * -+ DIV_ROUND_UP(sseu->max_eus_per_subslice, BITS_PER_BYTE); -+ -+ total_length = sizeof(topo) + slice_length + subslice_length + eu_length; -+ -+ ret = copy_query_item(&topo, sizeof(topo), total_length, -+ query_item); -+ if (ret != 0) -+ return ret; -+ -+ if (topo.flags != 0) -+ return -EINVAL; -+ -+ memset(&topo, 0, sizeof(topo)); -+ topo.max_slices = sseu->max_slices; -+ topo.max_subslices = sseu->max_subslices; -+ topo.max_eus_per_subslice = sseu->max_eus_per_subslice; -+ -+ topo.subslice_offset = slice_length; -+ topo.subslice_stride = DIV_ROUND_UP(sseu->max_subslices, BITS_PER_BYTE); -+ topo.eu_offset = slice_length + subslice_length; -+ topo.eu_stride = -+ DIV_ROUND_UP(sseu->max_eus_per_subslice, BITS_PER_BYTE); -+ -+ if (__copy_to_user(u64_to_user_ptr(query_item->data_ptr), -+ &topo, sizeof(topo))) -+ return -EFAULT; -+ -+ if (__copy_to_user(u64_to_user_ptr(query_item->data_ptr + sizeof(topo)), -+ &sseu->slice_mask, slice_length)) -+ return -EFAULT; -+ -+ if (__copy_to_user(u64_to_user_ptr(query_item->data_ptr + -+ sizeof(topo) + slice_length), -+ sseu->subslice_mask, subslice_length)) -+ return -EFAULT; -+ -+ if (__copy_to_user(u64_to_user_ptr(query_item->data_ptr + -+ sizeof(topo) + -+ slice_length + subslice_length), -+ sseu->eu_mask, eu_length)) -+ return -EFAULT; -+ -+ return total_length; -+} -+ -+static int (* const i915_query_funcs[])(struct drm_i915_private *dev_priv, -+ struct drm_i915_query_item *query_item) = { -+ query_topology_info, -+}; -+ -+int i915_query_ioctl(struct drm_device *dev, void *data, struct drm_file *file) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_i915_query *args = data; -+ struct drm_i915_query_item __user *user_item_ptr = -+ u64_to_user_ptr(args->items_ptr); -+ u32 i; -+ -+ if (args->flags != 0) -+ return -EINVAL; -+ -+ for (i = 0; i < args->num_items; i++, user_item_ptr++) { -+ struct drm_i915_query_item item; -+ unsigned long func_idx; -+ int ret; -+ -+ if (copy_from_user(&item, user_item_ptr, sizeof(item))) -+ return -EFAULT; -+ -+ if (item.query_id == 0) -+ return -EINVAL; -+ -+ if (overflows_type(item.query_id - 1, unsigned long)) -+ return -EINVAL; -+ -+ func_idx = item.query_id - 1; -+ -+ ret = -EINVAL; -+ if (func_idx < ARRAY_SIZE(i915_query_funcs)) { -+ func_idx = array_index_nospec(func_idx, -+ ARRAY_SIZE(i915_query_funcs)); -+ ret = i915_query_funcs[func_idx](dev_priv, &item); -+ } -+ -+ /* Only write the length back to userspace if they differ. */ -+ if (ret != item.length && put_user(ret, &user_item_ptr->length)) -+ return -EFAULT; -+ } -+ -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_query.h b/drivers/gpu/drm/i915_legacy/i915_query.h -new file mode 100644 -index 000000000000..31dcef181f63 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_query.h -@@ -0,0 +1,15 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ */ -+ -+#ifndef _I915_QUERY_H_ -+#define _I915_QUERY_H_ -+ -+struct drm_device; -+struct drm_file; -+ -+int i915_query_ioctl(struct drm_device *dev, void *data, struct drm_file *file); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_reg.h b/drivers/gpu/drm/i915_legacy/i915_reg.h -new file mode 100644 -index 000000000000..cf748b80e640 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_reg.h -@@ -0,0 +1,11424 @@ -+/* Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas. -+ * All Rights Reserved. -+ * -+ * 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, sub license, 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 (including the -+ * next paragraph) 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 NON-INFRINGEMENT. -+ * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS 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. -+ */ -+ -+#ifndef _I915_REG_H_ -+#define _I915_REG_H_ -+ -+#include -+#include -+ -+/** -+ * DOC: The i915 register macro definition style guide -+ * -+ * Follow the style described here for new macros, and while changing existing -+ * macros. Do **not** mass change existing definitions just to update the style. -+ * -+ * Layout -+ * ~~~~~~ -+ * -+ * Keep helper macros near the top. For example, _PIPE() and friends. -+ * -+ * Prefix macros that generally should not be used outside of this file with -+ * underscore '_'. For example, _PIPE() and friends, single instances of -+ * registers that are defined solely for the use by function-like macros. -+ * -+ * Avoid using the underscore prefixed macros outside of this file. There are -+ * exceptions, but keep them to a minimum. -+ * -+ * There are two basic types of register definitions: Single registers and -+ * register groups. Register groups are registers which have two or more -+ * instances, for example one per pipe, port, transcoder, etc. Register groups -+ * should be defined using function-like macros. -+ * -+ * For single registers, define the register offset first, followed by register -+ * contents. -+ * -+ * For register groups, define the register instance offsets first, prefixed -+ * with underscore, followed by a function-like macro choosing the right -+ * instance based on the parameter, followed by register contents. -+ * -+ * Define the register contents (i.e. bit and bit field macros) from most -+ * significant to least significant bit. Indent the register content macros -+ * using two extra spaces between ``#define`` and the macro name. -+ * -+ * Define bit fields using ``REG_GENMASK(h, l)``. Define bit field contents -+ * using ``REG_FIELD_PREP(mask, value)``. This will define the values already -+ * shifted in place, so they can be directly OR'd together. For convenience, -+ * function-like macros may be used to define bit fields, but do note that the -+ * macros may be needed to read as well as write the register contents. -+ * -+ * Define bits using ``REG_BIT(N)``. Do **not** add ``_BIT`` suffix to the name. -+ * -+ * Group the register and its contents together without blank lines, separate -+ * from other registers and their contents with one blank line. -+ * -+ * Indent macro values from macro names using TABs. Align values vertically. Use -+ * braces in macro values as needed to avoid unintended precedence after macro -+ * substitution. Use spaces in macro values according to kernel coding -+ * style. Use lower case in hexadecimal values. -+ * -+ * Naming -+ * ~~~~~~ -+ * -+ * Try to name registers according to the specs. If the register name changes in -+ * the specs from platform to another, stick to the original name. -+ * -+ * Try to re-use existing register macro definitions. Only add new macros for -+ * new register offsets, or when the register contents have changed enough to -+ * warrant a full redefinition. -+ * -+ * When a register macro changes for a new platform, prefix the new macro using -+ * the platform acronym or generation. For example, ``SKL_`` or ``GEN8_``. The -+ * prefix signifies the start platform/generation using the register. -+ * -+ * When a bit (field) macro changes or gets added for a new platform, while -+ * retaining the existing register macro, add a platform acronym or generation -+ * suffix to the name. For example, ``_SKL`` or ``_GEN8``. -+ * -+ * Examples -+ * ~~~~~~~~ -+ * -+ * (Note that the values in the example are indented using spaces instead of -+ * TABs to avoid misalignment in generated documentation. Use TABs in the -+ * definitions.):: -+ * -+ * #define _FOO_A 0xf000 -+ * #define _FOO_B 0xf001 -+ * #define FOO(pipe) _MMIO_PIPE(pipe, _FOO_A, _FOO_B) -+ * #define FOO_ENABLE REG_BIT(31) -+ * #define FOO_MODE_MASK REG_GENMASK(19, 16) -+ * #define FOO_MODE_BAR REG_FIELD_PREP(FOO_MODE_MASK, 0) -+ * #define FOO_MODE_BAZ REG_FIELD_PREP(FOO_MODE_MASK, 1) -+ * #define FOO_MODE_QUX_SNB REG_FIELD_PREP(FOO_MODE_MASK, 2) -+ * -+ * #define BAR _MMIO(0xb000) -+ * #define GEN8_BAR _MMIO(0xb888) -+ */ -+ -+/** -+ * REG_BIT() - Prepare a u32 bit value -+ * @__n: 0-based bit number -+ * -+ * Local wrapper for BIT() to force u32, with compile time checks. -+ * -+ * @return: Value with bit @__n set. -+ */ -+#define REG_BIT(__n) \ -+ ((u32)(BIT(__n) + \ -+ BUILD_BUG_ON_ZERO(__builtin_constant_p(__n) && \ -+ ((__n) < 0 || (__n) > 31)))) -+ -+/** -+ * REG_GENMASK() - Prepare a continuous u32 bitmask -+ * @__high: 0-based high bit -+ * @__low: 0-based low bit -+ * -+ * Local wrapper for GENMASK() to force u32, with compile time checks. -+ * -+ * @return: Continuous bitmask from @__high to @__low, inclusive. -+ */ -+#define REG_GENMASK(__high, __low) \ -+ ((u32)(GENMASK(__high, __low) + \ -+ BUILD_BUG_ON_ZERO(__builtin_constant_p(__high) && \ -+ __builtin_constant_p(__low) && \ -+ ((__low) < 0 || (__high) > 31 || (__low) > (__high))))) -+ -+/* -+ * Local integer constant expression version of is_power_of_2(). -+ */ -+#define IS_POWER_OF_2(__x) ((__x) && (((__x) & ((__x) - 1)) == 0)) -+ -+/** -+ * REG_FIELD_PREP() - Prepare a u32 bitfield value -+ * @__mask: shifted mask defining the field's length and position -+ * @__val: value to put in the field -+ -+ * Local copy of FIELD_PREP() to generate an integer constant expression, force -+ * u32 and for consistency with REG_FIELD_GET(), REG_BIT() and REG_GENMASK(). -+ * -+ * @return: @__val masked and shifted into the field defined by @__mask. -+ */ -+#define REG_FIELD_PREP(__mask, __val) \ -+ ((u32)((((typeof(__mask))(__val) << __bf_shf(__mask)) & (__mask)) + \ -+ BUILD_BUG_ON_ZERO(!__is_constexpr(__mask)) + \ -+ BUILD_BUG_ON_ZERO((__mask) == 0 || (__mask) > U32_MAX) + \ -+ BUILD_BUG_ON_ZERO(!IS_POWER_OF_2((__mask) + (1ULL << __bf_shf(__mask)))) + \ -+ BUILD_BUG_ON_ZERO(__builtin_choose_expr(__is_constexpr(__val), (~((__mask) >> __bf_shf(__mask)) & (__val)), 0)))) -+ -+/** -+ * REG_FIELD_GET() - Extract a u32 bitfield value -+ * @__mask: shifted mask defining the field's length and position -+ * @__val: value to extract the bitfield value from -+ * -+ * Local wrapper for FIELD_GET() to force u32 and for consistency with -+ * REG_FIELD_PREP(), REG_BIT() and REG_GENMASK(). -+ * -+ * @return: Masked and shifted value of the field defined by @__mask in @__val. -+ */ -+#define REG_FIELD_GET(__mask, __val) ((u32)FIELD_GET(__mask, __val)) -+ -+typedef struct { -+ u32 reg; -+} i915_reg_t; -+ -+#define _MMIO(r) ((const i915_reg_t){ .reg = (r) }) -+ -+#define INVALID_MMIO_REG _MMIO(0) -+ -+static inline u32 i915_mmio_reg_offset(i915_reg_t reg) -+{ -+ return reg.reg; -+} -+ -+static inline bool i915_mmio_reg_equal(i915_reg_t a, i915_reg_t b) -+{ -+ return i915_mmio_reg_offset(a) == i915_mmio_reg_offset(b); -+} -+ -+static inline bool i915_mmio_reg_valid(i915_reg_t reg) -+{ -+ return !i915_mmio_reg_equal(reg, INVALID_MMIO_REG); -+} -+ -+#define VLV_DISPLAY_BASE 0x180000 -+#define VLV_MIPI_BASE VLV_DISPLAY_BASE -+#define BXT_MIPI_BASE 0x60000 -+ -+#define DISPLAY_MMIO_BASE(dev_priv) (INTEL_INFO(dev_priv)->display_mmio_offset) -+ -+/* -+ * Given the first two numbers __a and __b of arbitrarily many evenly spaced -+ * numbers, pick the 0-based __index'th value. -+ * -+ * Always prefer this over _PICK() if the numbers are evenly spaced. -+ */ -+#define _PICK_EVEN(__index, __a, __b) ((__a) + (__index) * ((__b) - (__a))) -+ -+/* -+ * Given the arbitrary numbers in varargs, pick the 0-based __index'th number. -+ * -+ * Always prefer _PICK_EVEN() over this if the numbers are evenly spaced. -+ */ -+#define _PICK(__index, ...) (((const u32 []){ __VA_ARGS__ })[__index]) -+ -+/* -+ * Named helper wrappers around _PICK_EVEN() and _PICK(). -+ */ -+#define _PIPE(pipe, a, b) _PICK_EVEN(pipe, a, b) -+#define _PLANE(plane, a, b) _PICK_EVEN(plane, a, b) -+#define _TRANS(tran, a, b) _PICK_EVEN(tran, a, b) -+#define _PORT(port, a, b) _PICK_EVEN(port, a, b) -+#define _PLL(pll, a, b) _PICK_EVEN(pll, a, b) -+ -+#define _MMIO_PIPE(pipe, a, b) _MMIO(_PIPE(pipe, a, b)) -+#define _MMIO_PLANE(plane, a, b) _MMIO(_PLANE(plane, a, b)) -+#define _MMIO_TRANS(tran, a, b) _MMIO(_TRANS(tran, a, b)) -+#define _MMIO_PORT(port, a, b) _MMIO(_PORT(port, a, b)) -+#define _MMIO_PLL(pll, a, b) _MMIO(_PLL(pll, a, b)) -+ -+#define _PHY3(phy, ...) _PICK(phy, __VA_ARGS__) -+ -+#define _MMIO_PIPE3(pipe, a, b, c) _MMIO(_PICK(pipe, a, b, c)) -+#define _MMIO_PORT3(pipe, a, b, c) _MMIO(_PICK(pipe, a, b, c)) -+#define _MMIO_PHY3(phy, a, b, c) _MMIO(_PHY3(phy, a, b, c)) -+ -+/* -+ * Device info offset array based helpers for groups of registers with unevenly -+ * spaced base offsets. -+ */ -+#define _MMIO_PIPE2(pipe, reg) _MMIO(INTEL_INFO(dev_priv)->pipe_offsets[pipe] - \ -+ INTEL_INFO(dev_priv)->pipe_offsets[PIPE_A] + (reg) + \ -+ DISPLAY_MMIO_BASE(dev_priv)) -+#define _MMIO_TRANS2(pipe, reg) _MMIO(INTEL_INFO(dev_priv)->trans_offsets[(pipe)] - \ -+ INTEL_INFO(dev_priv)->trans_offsets[TRANSCODER_A] + (reg) + \ -+ DISPLAY_MMIO_BASE(dev_priv)) -+#define _CURSOR2(pipe, reg) _MMIO(INTEL_INFO(dev_priv)->cursor_offsets[(pipe)] - \ -+ INTEL_INFO(dev_priv)->cursor_offsets[PIPE_A] + (reg) + \ -+ DISPLAY_MMIO_BASE(dev_priv)) -+ -+#define __MASKED_FIELD(mask, value) ((mask) << 16 | (value)) -+#define _MASKED_FIELD(mask, value) ({ \ -+ if (__builtin_constant_p(mask)) \ -+ BUILD_BUG_ON_MSG(((mask) & 0xffff0000), "Incorrect mask"); \ -+ if (__builtin_constant_p(value)) \ -+ BUILD_BUG_ON_MSG((value) & 0xffff0000, "Incorrect value"); \ -+ if (__builtin_constant_p(mask) && __builtin_constant_p(value)) \ -+ BUILD_BUG_ON_MSG((value) & ~(mask), \ -+ "Incorrect value for mask"); \ -+ __MASKED_FIELD(mask, value); }) -+#define _MASKED_BIT_ENABLE(a) ({ typeof(a) _a = (a); _MASKED_FIELD(_a, _a); }) -+#define _MASKED_BIT_DISABLE(a) (_MASKED_FIELD((a), 0)) -+ -+/* Engine ID */ -+ -+#define RCS0_HW 0 -+#define VCS0_HW 1 -+#define BCS0_HW 2 -+#define VECS0_HW 3 -+#define VCS1_HW 4 -+#define VCS2_HW 6 -+#define VCS3_HW 7 -+#define VECS1_HW 12 -+ -+/* Engine class */ -+ -+#define RENDER_CLASS 0 -+#define VIDEO_DECODE_CLASS 1 -+#define VIDEO_ENHANCEMENT_CLASS 2 -+#define COPY_ENGINE_CLASS 3 -+#define OTHER_CLASS 4 -+#define MAX_ENGINE_CLASS 4 -+ -+#define OTHER_GTPM_INSTANCE 1 -+#define MAX_ENGINE_INSTANCE 3 -+ -+/* PCI config space */ -+ -+#define MCHBAR_I915 0x44 -+#define MCHBAR_I965 0x48 -+#define MCHBAR_SIZE (4 * 4096) -+ -+#define DEVEN 0x54 -+#define DEVEN_MCHBAR_EN (1 << 28) -+ -+/* BSM in include/drm/i915_drm.h */ -+ -+#define HPLLCC 0xc0 /* 85x only */ -+#define GC_CLOCK_CONTROL_MASK (0x7 << 0) -+#define GC_CLOCK_133_200 (0 << 0) -+#define GC_CLOCK_100_200 (1 << 0) -+#define GC_CLOCK_100_133 (2 << 0) -+#define GC_CLOCK_133_266 (3 << 0) -+#define GC_CLOCK_133_200_2 (4 << 0) -+#define GC_CLOCK_133_266_2 (5 << 0) -+#define GC_CLOCK_166_266 (6 << 0) -+#define GC_CLOCK_166_250 (7 << 0) -+ -+#define I915_GDRST 0xc0 /* PCI config register */ -+#define GRDOM_FULL (0 << 2) -+#define GRDOM_RENDER (1 << 2) -+#define GRDOM_MEDIA (3 << 2) -+#define GRDOM_MASK (3 << 2) -+#define GRDOM_RESET_STATUS (1 << 1) -+#define GRDOM_RESET_ENABLE (1 << 0) -+ -+/* BSpec only has register offset, PCI device and bit found empirically */ -+#define I830_CLOCK_GATE 0xc8 /* device 0 */ -+#define I830_L2_CACHE_CLOCK_GATE_DISABLE (1 << 2) -+ -+#define GCDGMBUS 0xcc -+ -+#define GCFGC2 0xda -+#define GCFGC 0xf0 /* 915+ only */ -+#define GC_LOW_FREQUENCY_ENABLE (1 << 7) -+#define GC_DISPLAY_CLOCK_190_200_MHZ (0 << 4) -+#define GC_DISPLAY_CLOCK_333_320_MHZ (4 << 4) -+#define GC_DISPLAY_CLOCK_267_MHZ_PNV (0 << 4) -+#define GC_DISPLAY_CLOCK_333_MHZ_PNV (1 << 4) -+#define GC_DISPLAY_CLOCK_444_MHZ_PNV (2 << 4) -+#define GC_DISPLAY_CLOCK_200_MHZ_PNV (5 << 4) -+#define GC_DISPLAY_CLOCK_133_MHZ_PNV (6 << 4) -+#define GC_DISPLAY_CLOCK_167_MHZ_PNV (7 << 4) -+#define GC_DISPLAY_CLOCK_MASK (7 << 4) -+#define GM45_GC_RENDER_CLOCK_MASK (0xf << 0) -+#define GM45_GC_RENDER_CLOCK_266_MHZ (8 << 0) -+#define GM45_GC_RENDER_CLOCK_320_MHZ (9 << 0) -+#define GM45_GC_RENDER_CLOCK_400_MHZ (0xb << 0) -+#define GM45_GC_RENDER_CLOCK_533_MHZ (0xc << 0) -+#define I965_GC_RENDER_CLOCK_MASK (0xf << 0) -+#define I965_GC_RENDER_CLOCK_267_MHZ (2 << 0) -+#define I965_GC_RENDER_CLOCK_333_MHZ (3 << 0) -+#define I965_GC_RENDER_CLOCK_444_MHZ (4 << 0) -+#define I965_GC_RENDER_CLOCK_533_MHZ (5 << 0) -+#define I945_GC_RENDER_CLOCK_MASK (7 << 0) -+#define I945_GC_RENDER_CLOCK_166_MHZ (0 << 0) -+#define I945_GC_RENDER_CLOCK_200_MHZ (1 << 0) -+#define I945_GC_RENDER_CLOCK_250_MHZ (3 << 0) -+#define I945_GC_RENDER_CLOCK_400_MHZ (5 << 0) -+#define I915_GC_RENDER_CLOCK_MASK (7 << 0) -+#define I915_GC_RENDER_CLOCK_166_MHZ (0 << 0) -+#define I915_GC_RENDER_CLOCK_200_MHZ (1 << 0) -+#define I915_GC_RENDER_CLOCK_333_MHZ (4 << 0) -+ -+#define ASLE 0xe4 -+#define ASLS 0xfc -+ -+#define SWSCI 0xe8 -+#define SWSCI_SCISEL (1 << 15) -+#define SWSCI_GSSCIE (1 << 0) -+ -+#define LBPC 0xf4 /* legacy/combination backlight modes, also called LBB */ -+ -+ -+#define ILK_GDSR _MMIO(MCHBAR_MIRROR_BASE + 0x2ca4) -+#define ILK_GRDOM_FULL (0 << 1) -+#define ILK_GRDOM_RENDER (1 << 1) -+#define ILK_GRDOM_MEDIA (3 << 1) -+#define ILK_GRDOM_MASK (3 << 1) -+#define ILK_GRDOM_RESET_ENABLE (1 << 0) -+ -+#define GEN6_MBCUNIT_SNPCR _MMIO(0x900c) /* for LLC config */ -+#define GEN6_MBC_SNPCR_SHIFT 21 -+#define GEN6_MBC_SNPCR_MASK (3 << 21) -+#define GEN6_MBC_SNPCR_MAX (0 << 21) -+#define GEN6_MBC_SNPCR_MED (1 << 21) -+#define GEN6_MBC_SNPCR_LOW (2 << 21) -+#define GEN6_MBC_SNPCR_MIN (3 << 21) /* only 1/16th of the cache is shared */ -+ -+#define VLV_G3DCTL _MMIO(0x9024) -+#define VLV_GSCKGCTL _MMIO(0x9028) -+ -+#define GEN6_MBCTL _MMIO(0x0907c) -+#define GEN6_MBCTL_ENABLE_BOOT_FETCH (1 << 4) -+#define GEN6_MBCTL_CTX_FETCH_NEEDED (1 << 3) -+#define GEN6_MBCTL_BME_UPDATE_ENABLE (1 << 2) -+#define GEN6_MBCTL_MAE_UPDATE_ENABLE (1 << 1) -+#define GEN6_MBCTL_BOOT_FETCH_MECH (1 << 0) -+ -+#define GEN6_GDRST _MMIO(0x941c) -+#define GEN6_GRDOM_FULL (1 << 0) -+#define GEN6_GRDOM_RENDER (1 << 1) -+#define GEN6_GRDOM_MEDIA (1 << 2) -+#define GEN6_GRDOM_BLT (1 << 3) -+#define GEN6_GRDOM_VECS (1 << 4) -+#define GEN9_GRDOM_GUC (1 << 5) -+#define GEN8_GRDOM_MEDIA2 (1 << 7) -+/* GEN11 changed all bit defs except for FULL & RENDER */ -+#define GEN11_GRDOM_FULL GEN6_GRDOM_FULL -+#define GEN11_GRDOM_RENDER GEN6_GRDOM_RENDER -+#define GEN11_GRDOM_BLT (1 << 2) -+#define GEN11_GRDOM_GUC (1 << 3) -+#define GEN11_GRDOM_MEDIA (1 << 5) -+#define GEN11_GRDOM_MEDIA2 (1 << 6) -+#define GEN11_GRDOM_MEDIA3 (1 << 7) -+#define GEN11_GRDOM_MEDIA4 (1 << 8) -+#define GEN11_GRDOM_VECS (1 << 13) -+#define GEN11_GRDOM_VECS2 (1 << 14) -+#define GEN11_GRDOM_SFC0 (1 << 17) -+#define GEN11_GRDOM_SFC1 (1 << 18) -+ -+#define GEN11_VCS_SFC_RESET_BIT(instance) (GEN11_GRDOM_SFC0 << ((instance) >> 1)) -+#define GEN11_VECS_SFC_RESET_BIT(instance) (GEN11_GRDOM_SFC0 << (instance)) -+ -+#define GEN11_VCS_SFC_FORCED_LOCK(engine) _MMIO((engine)->mmio_base + 0x88C) -+#define GEN11_VCS_SFC_FORCED_LOCK_BIT (1 << 0) -+#define GEN11_VCS_SFC_LOCK_STATUS(engine) _MMIO((engine)->mmio_base + 0x890) -+#define GEN11_VCS_SFC_USAGE_BIT (1 << 0) -+#define GEN11_VCS_SFC_LOCK_ACK_BIT (1 << 1) -+ -+#define GEN11_VECS_SFC_FORCED_LOCK(engine) _MMIO((engine)->mmio_base + 0x201C) -+#define GEN11_VECS_SFC_FORCED_LOCK_BIT (1 << 0) -+#define GEN11_VECS_SFC_LOCK_ACK(engine) _MMIO((engine)->mmio_base + 0x2018) -+#define GEN11_VECS_SFC_LOCK_ACK_BIT (1 << 0) -+#define GEN11_VECS_SFC_USAGE(engine) _MMIO((engine)->mmio_base + 0x2014) -+#define GEN11_VECS_SFC_USAGE_BIT (1 << 0) -+ -+#define RING_PP_DIR_BASE(base) _MMIO((base) + 0x228) -+#define RING_PP_DIR_BASE_READ(base) _MMIO((base) + 0x518) -+#define RING_PP_DIR_DCLV(base) _MMIO((base) + 0x220) -+#define PP_DIR_DCLV_2G 0xffffffff -+ -+#define GEN8_RING_PDP_UDW(base, n) _MMIO((base) + 0x270 + (n) * 8 + 4) -+#define GEN8_RING_PDP_LDW(base, n) _MMIO((base) + 0x270 + (n) * 8) -+ -+#define GEN8_R_PWR_CLK_STATE _MMIO(0x20C8) -+#define GEN8_RPCS_ENABLE (1 << 31) -+#define GEN8_RPCS_S_CNT_ENABLE (1 << 18) -+#define GEN8_RPCS_S_CNT_SHIFT 15 -+#define GEN8_RPCS_S_CNT_MASK (0x7 << GEN8_RPCS_S_CNT_SHIFT) -+#define GEN11_RPCS_S_CNT_SHIFT 12 -+#define GEN11_RPCS_S_CNT_MASK (0x3f << GEN11_RPCS_S_CNT_SHIFT) -+#define GEN8_RPCS_SS_CNT_ENABLE (1 << 11) -+#define GEN8_RPCS_SS_CNT_SHIFT 8 -+#define GEN8_RPCS_SS_CNT_MASK (0x7 << GEN8_RPCS_SS_CNT_SHIFT) -+#define GEN8_RPCS_EU_MAX_SHIFT 4 -+#define GEN8_RPCS_EU_MAX_MASK (0xf << GEN8_RPCS_EU_MAX_SHIFT) -+#define GEN8_RPCS_EU_MIN_SHIFT 0 -+#define GEN8_RPCS_EU_MIN_MASK (0xf << GEN8_RPCS_EU_MIN_SHIFT) -+ -+#define WAIT_FOR_RC6_EXIT _MMIO(0x20CC) -+/* HSW only */ -+#define HSW_SELECTIVE_READ_ADDRESSING_SHIFT 2 -+#define HSW_SELECTIVE_READ_ADDRESSING_MASK (0x3 << HSW_SLECTIVE_READ_ADDRESSING_SHIFT) -+#define HSW_SELECTIVE_WRITE_ADDRESS_SHIFT 4 -+#define HSW_SELECTIVE_WRITE_ADDRESS_MASK (0x7 << HSW_SELECTIVE_WRITE_ADDRESS_SHIFT) -+/* HSW+ */ -+#define HSW_WAIT_FOR_RC6_EXIT_ENABLE (1 << 0) -+#define HSW_RCS_CONTEXT_ENABLE (1 << 7) -+#define HSW_RCS_INHIBIT (1 << 8) -+/* Gen8 */ -+#define GEN8_SELECTIVE_WRITE_ADDRESS_SHIFT 4 -+#define GEN8_SELECTIVE_WRITE_ADDRESS_MASK (0x3 << GEN8_SELECTIVE_WRITE_ADDRESS_SHIFT) -+#define GEN8_SELECTIVE_WRITE_ADDRESS_SHIFT 4 -+#define GEN8_SELECTIVE_WRITE_ADDRESS_MASK (0x3 << GEN8_SELECTIVE_WRITE_ADDRESS_SHIFT) -+#define GEN8_SELECTIVE_WRITE_ADDRESSING_ENABLE (1 << 6) -+#define GEN8_SELECTIVE_READ_SUBSLICE_SELECT_SHIFT 9 -+#define GEN8_SELECTIVE_READ_SUBSLICE_SELECT_MASK (0x3 << GEN8_SELECTIVE_READ_SUBSLICE_SELECT_SHIFT) -+#define GEN8_SELECTIVE_READ_SLICE_SELECT_SHIFT 11 -+#define GEN8_SELECTIVE_READ_SLICE_SELECT_MASK (0x3 << GEN8_SELECTIVE_READ_SLICE_SELECT_SHIFT) -+#define GEN8_SELECTIVE_READ_ADDRESSING_ENABLE (1 << 13) -+ -+#define GAM_ECOCHK _MMIO(0x4090) -+#define BDW_DISABLE_HDC_INVALIDATION (1 << 25) -+#define ECOCHK_SNB_BIT (1 << 10) -+#define ECOCHK_DIS_TLB (1 << 8) -+#define HSW_ECOCHK_ARB_PRIO_SOL (1 << 6) -+#define ECOCHK_PPGTT_CACHE64B (0x3 << 3) -+#define ECOCHK_PPGTT_CACHE4B (0x0 << 3) -+#define ECOCHK_PPGTT_GFDT_IVB (0x1 << 4) -+#define ECOCHK_PPGTT_LLC_IVB (0x1 << 3) -+#define ECOCHK_PPGTT_UC_HSW (0x1 << 3) -+#define ECOCHK_PPGTT_WT_HSW (0x2 << 3) -+#define ECOCHK_PPGTT_WB_HSW (0x3 << 3) -+ -+#define GAC_ECO_BITS _MMIO(0x14090) -+#define ECOBITS_SNB_BIT (1 << 13) -+#define ECOBITS_PPGTT_CACHE64B (3 << 8) -+#define ECOBITS_PPGTT_CACHE4B (0 << 8) -+ -+#define GAB_CTL _MMIO(0x24000) -+#define GAB_CTL_CONT_AFTER_PAGEFAULT (1 << 8) -+ -+#define GEN6_STOLEN_RESERVED _MMIO(0x1082C0) -+#define GEN6_STOLEN_RESERVED_ADDR_MASK (0xFFF << 20) -+#define GEN7_STOLEN_RESERVED_ADDR_MASK (0x3FFF << 18) -+#define GEN6_STOLEN_RESERVED_SIZE_MASK (3 << 4) -+#define GEN6_STOLEN_RESERVED_1M (0 << 4) -+#define GEN6_STOLEN_RESERVED_512K (1 << 4) -+#define GEN6_STOLEN_RESERVED_256K (2 << 4) -+#define GEN6_STOLEN_RESERVED_128K (3 << 4) -+#define GEN7_STOLEN_RESERVED_SIZE_MASK (1 << 5) -+#define GEN7_STOLEN_RESERVED_1M (0 << 5) -+#define GEN7_STOLEN_RESERVED_256K (1 << 5) -+#define GEN8_STOLEN_RESERVED_SIZE_MASK (3 << 7) -+#define GEN8_STOLEN_RESERVED_1M (0 << 7) -+#define GEN8_STOLEN_RESERVED_2M (1 << 7) -+#define GEN8_STOLEN_RESERVED_4M (2 << 7) -+#define GEN8_STOLEN_RESERVED_8M (3 << 7) -+#define GEN6_STOLEN_RESERVED_ENABLE (1 << 0) -+#define GEN11_STOLEN_RESERVED_ADDR_MASK (0xFFFFFFFFFFFULL << 20) -+ -+/* VGA stuff */ -+ -+#define VGA_ST01_MDA 0x3ba -+#define VGA_ST01_CGA 0x3da -+ -+#define _VGA_MSR_WRITE _MMIO(0x3c2) -+#define VGA_MSR_WRITE 0x3c2 -+#define VGA_MSR_READ 0x3cc -+#define VGA_MSR_MEM_EN (1 << 1) -+#define VGA_MSR_CGA_MODE (1 << 0) -+ -+#define VGA_SR_INDEX 0x3c4 -+#define SR01 1 -+#define VGA_SR_DATA 0x3c5 -+ -+#define VGA_AR_INDEX 0x3c0 -+#define VGA_AR_VID_EN (1 << 5) -+#define VGA_AR_DATA_WRITE 0x3c0 -+#define VGA_AR_DATA_READ 0x3c1 -+ -+#define VGA_GR_INDEX 0x3ce -+#define VGA_GR_DATA 0x3cf -+/* GR05 */ -+#define VGA_GR_MEM_READ_MODE_SHIFT 3 -+#define VGA_GR_MEM_READ_MODE_PLANE 1 -+/* GR06 */ -+#define VGA_GR_MEM_MODE_MASK 0xc -+#define VGA_GR_MEM_MODE_SHIFT 2 -+#define VGA_GR_MEM_A0000_AFFFF 0 -+#define VGA_GR_MEM_A0000_BFFFF 1 -+#define VGA_GR_MEM_B0000_B7FFF 2 -+#define VGA_GR_MEM_B0000_BFFFF 3 -+ -+#define VGA_DACMASK 0x3c6 -+#define VGA_DACRX 0x3c7 -+#define VGA_DACWX 0x3c8 -+#define VGA_DACDATA 0x3c9 -+ -+#define VGA_CR_INDEX_MDA 0x3b4 -+#define VGA_CR_DATA_MDA 0x3b5 -+#define VGA_CR_INDEX_CGA 0x3d4 -+#define VGA_CR_DATA_CGA 0x3d5 -+ -+#define MI_PREDICATE_SRC0 _MMIO(0x2400) -+#define MI_PREDICATE_SRC0_UDW _MMIO(0x2400 + 4) -+#define MI_PREDICATE_SRC1 _MMIO(0x2408) -+#define MI_PREDICATE_SRC1_UDW _MMIO(0x2408 + 4) -+ -+#define MI_PREDICATE_RESULT_2 _MMIO(0x2214) -+#define LOWER_SLICE_ENABLED (1 << 0) -+#define LOWER_SLICE_DISABLED (0 << 0) -+ -+/* -+ * Registers used only by the command parser -+ */ -+#define BCS_SWCTRL _MMIO(0x22200) -+ -+#define GPGPU_THREADS_DISPATCHED _MMIO(0x2290) -+#define GPGPU_THREADS_DISPATCHED_UDW _MMIO(0x2290 + 4) -+#define HS_INVOCATION_COUNT _MMIO(0x2300) -+#define HS_INVOCATION_COUNT_UDW _MMIO(0x2300 + 4) -+#define DS_INVOCATION_COUNT _MMIO(0x2308) -+#define DS_INVOCATION_COUNT_UDW _MMIO(0x2308 + 4) -+#define IA_VERTICES_COUNT _MMIO(0x2310) -+#define IA_VERTICES_COUNT_UDW _MMIO(0x2310 + 4) -+#define IA_PRIMITIVES_COUNT _MMIO(0x2318) -+#define IA_PRIMITIVES_COUNT_UDW _MMIO(0x2318 + 4) -+#define VS_INVOCATION_COUNT _MMIO(0x2320) -+#define VS_INVOCATION_COUNT_UDW _MMIO(0x2320 + 4) -+#define GS_INVOCATION_COUNT _MMIO(0x2328) -+#define GS_INVOCATION_COUNT_UDW _MMIO(0x2328 + 4) -+#define GS_PRIMITIVES_COUNT _MMIO(0x2330) -+#define GS_PRIMITIVES_COUNT_UDW _MMIO(0x2330 + 4) -+#define CL_INVOCATION_COUNT _MMIO(0x2338) -+#define CL_INVOCATION_COUNT_UDW _MMIO(0x2338 + 4) -+#define CL_PRIMITIVES_COUNT _MMIO(0x2340) -+#define CL_PRIMITIVES_COUNT_UDW _MMIO(0x2340 + 4) -+#define PS_INVOCATION_COUNT _MMIO(0x2348) -+#define PS_INVOCATION_COUNT_UDW _MMIO(0x2348 + 4) -+#define PS_DEPTH_COUNT _MMIO(0x2350) -+#define PS_DEPTH_COUNT_UDW _MMIO(0x2350 + 4) -+ -+/* There are the 4 64-bit counter registers, one for each stream output */ -+#define GEN7_SO_NUM_PRIMS_WRITTEN(n) _MMIO(0x5200 + (n) * 8) -+#define GEN7_SO_NUM_PRIMS_WRITTEN_UDW(n) _MMIO(0x5200 + (n) * 8 + 4) -+ -+#define GEN7_SO_PRIM_STORAGE_NEEDED(n) _MMIO(0x5240 + (n) * 8) -+#define GEN7_SO_PRIM_STORAGE_NEEDED_UDW(n) _MMIO(0x5240 + (n) * 8 + 4) -+ -+#define GEN7_3DPRIM_END_OFFSET _MMIO(0x2420) -+#define GEN7_3DPRIM_START_VERTEX _MMIO(0x2430) -+#define GEN7_3DPRIM_VERTEX_COUNT _MMIO(0x2434) -+#define GEN7_3DPRIM_INSTANCE_COUNT _MMIO(0x2438) -+#define GEN7_3DPRIM_START_INSTANCE _MMIO(0x243C) -+#define GEN7_3DPRIM_BASE_VERTEX _MMIO(0x2440) -+ -+#define GEN7_GPGPU_DISPATCHDIMX _MMIO(0x2500) -+#define GEN7_GPGPU_DISPATCHDIMY _MMIO(0x2504) -+#define GEN7_GPGPU_DISPATCHDIMZ _MMIO(0x2508) -+ -+/* There are the 16 64-bit CS General Purpose Registers */ -+#define HSW_CS_GPR(n) _MMIO(0x2600 + (n) * 8) -+#define HSW_CS_GPR_UDW(n) _MMIO(0x2600 + (n) * 8 + 4) -+ -+#define GEN7_OACONTROL _MMIO(0x2360) -+#define GEN7_OACONTROL_CTX_MASK 0xFFFFF000 -+#define GEN7_OACONTROL_TIMER_PERIOD_MASK 0x3F -+#define GEN7_OACONTROL_TIMER_PERIOD_SHIFT 6 -+#define GEN7_OACONTROL_TIMER_ENABLE (1 << 5) -+#define GEN7_OACONTROL_FORMAT_A13 (0 << 2) -+#define GEN7_OACONTROL_FORMAT_A29 (1 << 2) -+#define GEN7_OACONTROL_FORMAT_A13_B8_C8 (2 << 2) -+#define GEN7_OACONTROL_FORMAT_A29_B8_C8 (3 << 2) -+#define GEN7_OACONTROL_FORMAT_B4_C8 (4 << 2) -+#define GEN7_OACONTROL_FORMAT_A45_B8_C8 (5 << 2) -+#define GEN7_OACONTROL_FORMAT_B4_C8_A16 (6 << 2) -+#define GEN7_OACONTROL_FORMAT_C4_B8 (7 << 2) -+#define GEN7_OACONTROL_FORMAT_SHIFT 2 -+#define GEN7_OACONTROL_PER_CTX_ENABLE (1 << 1) -+#define GEN7_OACONTROL_ENABLE (1 << 0) -+ -+#define GEN8_OACTXID _MMIO(0x2364) -+ -+#define GEN8_OA_DEBUG _MMIO(0x2B04) -+#define GEN9_OA_DEBUG_DISABLE_CLK_RATIO_REPORTS (1 << 5) -+#define GEN9_OA_DEBUG_INCLUDE_CLK_RATIO (1 << 6) -+#define GEN9_OA_DEBUG_DISABLE_GO_1_0_REPORTS (1 << 2) -+#define GEN9_OA_DEBUG_DISABLE_CTX_SWITCH_REPORTS (1 << 1) -+ -+#define GEN8_OACONTROL _MMIO(0x2B00) -+#define GEN8_OA_REPORT_FORMAT_A12 (0 << 2) -+#define GEN8_OA_REPORT_FORMAT_A12_B8_C8 (2 << 2) -+#define GEN8_OA_REPORT_FORMAT_A36_B8_C8 (5 << 2) -+#define GEN8_OA_REPORT_FORMAT_C4_B8 (7 << 2) -+#define GEN8_OA_REPORT_FORMAT_SHIFT 2 -+#define GEN8_OA_SPECIFIC_CONTEXT_ENABLE (1 << 1) -+#define GEN8_OA_COUNTER_ENABLE (1 << 0) -+ -+#define GEN8_OACTXCONTROL _MMIO(0x2360) -+#define GEN8_OA_TIMER_PERIOD_MASK 0x3F -+#define GEN8_OA_TIMER_PERIOD_SHIFT 2 -+#define GEN8_OA_TIMER_ENABLE (1 << 1) -+#define GEN8_OA_COUNTER_RESUME (1 << 0) -+ -+#define GEN7_OABUFFER _MMIO(0x23B0) /* R/W */ -+#define GEN7_OABUFFER_OVERRUN_DISABLE (1 << 3) -+#define GEN7_OABUFFER_EDGE_TRIGGER (1 << 2) -+#define GEN7_OABUFFER_STOP_RESUME_ENABLE (1 << 1) -+#define GEN7_OABUFFER_RESUME (1 << 0) -+ -+#define GEN8_OABUFFER_UDW _MMIO(0x23b4) -+#define GEN8_OABUFFER _MMIO(0x2b14) -+#define GEN8_OABUFFER_MEM_SELECT_GGTT (1 << 0) /* 0: PPGTT, 1: GGTT */ -+ -+#define GEN7_OASTATUS1 _MMIO(0x2364) -+#define GEN7_OASTATUS1_TAIL_MASK 0xffffffc0 -+#define GEN7_OASTATUS1_COUNTER_OVERFLOW (1 << 2) -+#define GEN7_OASTATUS1_OABUFFER_OVERFLOW (1 << 1) -+#define GEN7_OASTATUS1_REPORT_LOST (1 << 0) -+ -+#define GEN7_OASTATUS2 _MMIO(0x2368) -+#define GEN7_OASTATUS2_HEAD_MASK 0xffffffc0 -+#define GEN7_OASTATUS2_MEM_SELECT_GGTT (1 << 0) /* 0: PPGTT, 1: GGTT */ -+ -+#define GEN8_OASTATUS _MMIO(0x2b08) -+#define GEN8_OASTATUS_OVERRUN_STATUS (1 << 3) -+#define GEN8_OASTATUS_COUNTER_OVERFLOW (1 << 2) -+#define GEN8_OASTATUS_OABUFFER_OVERFLOW (1 << 1) -+#define GEN8_OASTATUS_REPORT_LOST (1 << 0) -+ -+#define GEN8_OAHEADPTR _MMIO(0x2B0C) -+#define GEN8_OAHEADPTR_MASK 0xffffffc0 -+#define GEN8_OATAILPTR _MMIO(0x2B10) -+#define GEN8_OATAILPTR_MASK 0xffffffc0 -+ -+#define OABUFFER_SIZE_128K (0 << 3) -+#define OABUFFER_SIZE_256K (1 << 3) -+#define OABUFFER_SIZE_512K (2 << 3) -+#define OABUFFER_SIZE_1M (3 << 3) -+#define OABUFFER_SIZE_2M (4 << 3) -+#define OABUFFER_SIZE_4M (5 << 3) -+#define OABUFFER_SIZE_8M (6 << 3) -+#define OABUFFER_SIZE_16M (7 << 3) -+ -+/* -+ * Flexible, Aggregate EU Counter Registers. -+ * Note: these aren't contiguous -+ */ -+#define EU_PERF_CNTL0 _MMIO(0xe458) -+#define EU_PERF_CNTL1 _MMIO(0xe558) -+#define EU_PERF_CNTL2 _MMIO(0xe658) -+#define EU_PERF_CNTL3 _MMIO(0xe758) -+#define EU_PERF_CNTL4 _MMIO(0xe45c) -+#define EU_PERF_CNTL5 _MMIO(0xe55c) -+#define EU_PERF_CNTL6 _MMIO(0xe65c) -+ -+/* -+ * OA Boolean state -+ */ -+ -+#define OASTARTTRIG1 _MMIO(0x2710) -+#define OASTARTTRIG1_THRESHOLD_COUNT_MASK_MBZ 0xffff0000 -+#define OASTARTTRIG1_THRESHOLD_MASK 0xffff -+ -+#define OASTARTTRIG2 _MMIO(0x2714) -+#define OASTARTTRIG2_INVERT_A_0 (1 << 0) -+#define OASTARTTRIG2_INVERT_A_1 (1 << 1) -+#define OASTARTTRIG2_INVERT_A_2 (1 << 2) -+#define OASTARTTRIG2_INVERT_A_3 (1 << 3) -+#define OASTARTTRIG2_INVERT_A_4 (1 << 4) -+#define OASTARTTRIG2_INVERT_A_5 (1 << 5) -+#define OASTARTTRIG2_INVERT_A_6 (1 << 6) -+#define OASTARTTRIG2_INVERT_A_7 (1 << 7) -+#define OASTARTTRIG2_INVERT_A_8 (1 << 8) -+#define OASTARTTRIG2_INVERT_A_9 (1 << 9) -+#define OASTARTTRIG2_INVERT_A_10 (1 << 10) -+#define OASTARTTRIG2_INVERT_A_11 (1 << 11) -+#define OASTARTTRIG2_INVERT_A_12 (1 << 12) -+#define OASTARTTRIG2_INVERT_A_13 (1 << 13) -+#define OASTARTTRIG2_INVERT_A_14 (1 << 14) -+#define OASTARTTRIG2_INVERT_A_15 (1 << 15) -+#define OASTARTTRIG2_INVERT_B_0 (1 << 16) -+#define OASTARTTRIG2_INVERT_B_1 (1 << 17) -+#define OASTARTTRIG2_INVERT_B_2 (1 << 18) -+#define OASTARTTRIG2_INVERT_B_3 (1 << 19) -+#define OASTARTTRIG2_INVERT_C_0 (1 << 20) -+#define OASTARTTRIG2_INVERT_C_1 (1 << 21) -+#define OASTARTTRIG2_INVERT_D_0 (1 << 22) -+#define OASTARTTRIG2_THRESHOLD_ENABLE (1 << 23) -+#define OASTARTTRIG2_START_TRIG_FLAG_MBZ (1 << 24) -+#define OASTARTTRIG2_EVENT_SELECT_0 (1 << 28) -+#define OASTARTTRIG2_EVENT_SELECT_1 (1 << 29) -+#define OASTARTTRIG2_EVENT_SELECT_2 (1 << 30) -+#define OASTARTTRIG2_EVENT_SELECT_3 (1 << 31) -+ -+#define OASTARTTRIG3 _MMIO(0x2718) -+#define OASTARTTRIG3_NOA_SELECT_MASK 0xf -+#define OASTARTTRIG3_NOA_SELECT_8_SHIFT 0 -+#define OASTARTTRIG3_NOA_SELECT_9_SHIFT 4 -+#define OASTARTTRIG3_NOA_SELECT_10_SHIFT 8 -+#define OASTARTTRIG3_NOA_SELECT_11_SHIFT 12 -+#define OASTARTTRIG3_NOA_SELECT_12_SHIFT 16 -+#define OASTARTTRIG3_NOA_SELECT_13_SHIFT 20 -+#define OASTARTTRIG3_NOA_SELECT_14_SHIFT 24 -+#define OASTARTTRIG3_NOA_SELECT_15_SHIFT 28 -+ -+#define OASTARTTRIG4 _MMIO(0x271c) -+#define OASTARTTRIG4_NOA_SELECT_MASK 0xf -+#define OASTARTTRIG4_NOA_SELECT_0_SHIFT 0 -+#define OASTARTTRIG4_NOA_SELECT_1_SHIFT 4 -+#define OASTARTTRIG4_NOA_SELECT_2_SHIFT 8 -+#define OASTARTTRIG4_NOA_SELECT_3_SHIFT 12 -+#define OASTARTTRIG4_NOA_SELECT_4_SHIFT 16 -+#define OASTARTTRIG4_NOA_SELECT_5_SHIFT 20 -+#define OASTARTTRIG4_NOA_SELECT_6_SHIFT 24 -+#define OASTARTTRIG4_NOA_SELECT_7_SHIFT 28 -+ -+#define OASTARTTRIG5 _MMIO(0x2720) -+#define OASTARTTRIG5_THRESHOLD_COUNT_MASK_MBZ 0xffff0000 -+#define OASTARTTRIG5_THRESHOLD_MASK 0xffff -+ -+#define OASTARTTRIG6 _MMIO(0x2724) -+#define OASTARTTRIG6_INVERT_A_0 (1 << 0) -+#define OASTARTTRIG6_INVERT_A_1 (1 << 1) -+#define OASTARTTRIG6_INVERT_A_2 (1 << 2) -+#define OASTARTTRIG6_INVERT_A_3 (1 << 3) -+#define OASTARTTRIG6_INVERT_A_4 (1 << 4) -+#define OASTARTTRIG6_INVERT_A_5 (1 << 5) -+#define OASTARTTRIG6_INVERT_A_6 (1 << 6) -+#define OASTARTTRIG6_INVERT_A_7 (1 << 7) -+#define OASTARTTRIG6_INVERT_A_8 (1 << 8) -+#define OASTARTTRIG6_INVERT_A_9 (1 << 9) -+#define OASTARTTRIG6_INVERT_A_10 (1 << 10) -+#define OASTARTTRIG6_INVERT_A_11 (1 << 11) -+#define OASTARTTRIG6_INVERT_A_12 (1 << 12) -+#define OASTARTTRIG6_INVERT_A_13 (1 << 13) -+#define OASTARTTRIG6_INVERT_A_14 (1 << 14) -+#define OASTARTTRIG6_INVERT_A_15 (1 << 15) -+#define OASTARTTRIG6_INVERT_B_0 (1 << 16) -+#define OASTARTTRIG6_INVERT_B_1 (1 << 17) -+#define OASTARTTRIG6_INVERT_B_2 (1 << 18) -+#define OASTARTTRIG6_INVERT_B_3 (1 << 19) -+#define OASTARTTRIG6_INVERT_C_0 (1 << 20) -+#define OASTARTTRIG6_INVERT_C_1 (1 << 21) -+#define OASTARTTRIG6_INVERT_D_0 (1 << 22) -+#define OASTARTTRIG6_THRESHOLD_ENABLE (1 << 23) -+#define OASTARTTRIG6_START_TRIG_FLAG_MBZ (1 << 24) -+#define OASTARTTRIG6_EVENT_SELECT_4 (1 << 28) -+#define OASTARTTRIG6_EVENT_SELECT_5 (1 << 29) -+#define OASTARTTRIG6_EVENT_SELECT_6 (1 << 30) -+#define OASTARTTRIG6_EVENT_SELECT_7 (1 << 31) -+ -+#define OASTARTTRIG7 _MMIO(0x2728) -+#define OASTARTTRIG7_NOA_SELECT_MASK 0xf -+#define OASTARTTRIG7_NOA_SELECT_8_SHIFT 0 -+#define OASTARTTRIG7_NOA_SELECT_9_SHIFT 4 -+#define OASTARTTRIG7_NOA_SELECT_10_SHIFT 8 -+#define OASTARTTRIG7_NOA_SELECT_11_SHIFT 12 -+#define OASTARTTRIG7_NOA_SELECT_12_SHIFT 16 -+#define OASTARTTRIG7_NOA_SELECT_13_SHIFT 20 -+#define OASTARTTRIG7_NOA_SELECT_14_SHIFT 24 -+#define OASTARTTRIG7_NOA_SELECT_15_SHIFT 28 -+ -+#define OASTARTTRIG8 _MMIO(0x272c) -+#define OASTARTTRIG8_NOA_SELECT_MASK 0xf -+#define OASTARTTRIG8_NOA_SELECT_0_SHIFT 0 -+#define OASTARTTRIG8_NOA_SELECT_1_SHIFT 4 -+#define OASTARTTRIG8_NOA_SELECT_2_SHIFT 8 -+#define OASTARTTRIG8_NOA_SELECT_3_SHIFT 12 -+#define OASTARTTRIG8_NOA_SELECT_4_SHIFT 16 -+#define OASTARTTRIG8_NOA_SELECT_5_SHIFT 20 -+#define OASTARTTRIG8_NOA_SELECT_6_SHIFT 24 -+#define OASTARTTRIG8_NOA_SELECT_7_SHIFT 28 -+ -+#define OAREPORTTRIG1 _MMIO(0x2740) -+#define OAREPORTTRIG1_THRESHOLD_MASK 0xffff -+#define OAREPORTTRIG1_EDGE_LEVEL_TRIGER_SELECT_MASK 0xffff0000 /* 0=level */ -+ -+#define OAREPORTTRIG2 _MMIO(0x2744) -+#define OAREPORTTRIG2_INVERT_A_0 (1 << 0) -+#define OAREPORTTRIG2_INVERT_A_1 (1 << 1) -+#define OAREPORTTRIG2_INVERT_A_2 (1 << 2) -+#define OAREPORTTRIG2_INVERT_A_3 (1 << 3) -+#define OAREPORTTRIG2_INVERT_A_4 (1 << 4) -+#define OAREPORTTRIG2_INVERT_A_5 (1 << 5) -+#define OAREPORTTRIG2_INVERT_A_6 (1 << 6) -+#define OAREPORTTRIG2_INVERT_A_7 (1 << 7) -+#define OAREPORTTRIG2_INVERT_A_8 (1 << 8) -+#define OAREPORTTRIG2_INVERT_A_9 (1 << 9) -+#define OAREPORTTRIG2_INVERT_A_10 (1 << 10) -+#define OAREPORTTRIG2_INVERT_A_11 (1 << 11) -+#define OAREPORTTRIG2_INVERT_A_12 (1 << 12) -+#define OAREPORTTRIG2_INVERT_A_13 (1 << 13) -+#define OAREPORTTRIG2_INVERT_A_14 (1 << 14) -+#define OAREPORTTRIG2_INVERT_A_15 (1 << 15) -+#define OAREPORTTRIG2_INVERT_B_0 (1 << 16) -+#define OAREPORTTRIG2_INVERT_B_1 (1 << 17) -+#define OAREPORTTRIG2_INVERT_B_2 (1 << 18) -+#define OAREPORTTRIG2_INVERT_B_3 (1 << 19) -+#define OAREPORTTRIG2_INVERT_C_0 (1 << 20) -+#define OAREPORTTRIG2_INVERT_C_1 (1 << 21) -+#define OAREPORTTRIG2_INVERT_D_0 (1 << 22) -+#define OAREPORTTRIG2_THRESHOLD_ENABLE (1 << 23) -+#define OAREPORTTRIG2_REPORT_TRIGGER_ENABLE (1 << 31) -+ -+#define OAREPORTTRIG3 _MMIO(0x2748) -+#define OAREPORTTRIG3_NOA_SELECT_MASK 0xf -+#define OAREPORTTRIG3_NOA_SELECT_8_SHIFT 0 -+#define OAREPORTTRIG3_NOA_SELECT_9_SHIFT 4 -+#define OAREPORTTRIG3_NOA_SELECT_10_SHIFT 8 -+#define OAREPORTTRIG3_NOA_SELECT_11_SHIFT 12 -+#define OAREPORTTRIG3_NOA_SELECT_12_SHIFT 16 -+#define OAREPORTTRIG3_NOA_SELECT_13_SHIFT 20 -+#define OAREPORTTRIG3_NOA_SELECT_14_SHIFT 24 -+#define OAREPORTTRIG3_NOA_SELECT_15_SHIFT 28 -+ -+#define OAREPORTTRIG4 _MMIO(0x274c) -+#define OAREPORTTRIG4_NOA_SELECT_MASK 0xf -+#define OAREPORTTRIG4_NOA_SELECT_0_SHIFT 0 -+#define OAREPORTTRIG4_NOA_SELECT_1_SHIFT 4 -+#define OAREPORTTRIG4_NOA_SELECT_2_SHIFT 8 -+#define OAREPORTTRIG4_NOA_SELECT_3_SHIFT 12 -+#define OAREPORTTRIG4_NOA_SELECT_4_SHIFT 16 -+#define OAREPORTTRIG4_NOA_SELECT_5_SHIFT 20 -+#define OAREPORTTRIG4_NOA_SELECT_6_SHIFT 24 -+#define OAREPORTTRIG4_NOA_SELECT_7_SHIFT 28 -+ -+#define OAREPORTTRIG5 _MMIO(0x2750) -+#define OAREPORTTRIG5_THRESHOLD_MASK 0xffff -+#define OAREPORTTRIG5_EDGE_LEVEL_TRIGER_SELECT_MASK 0xffff0000 /* 0=level */ -+ -+#define OAREPORTTRIG6 _MMIO(0x2754) -+#define OAREPORTTRIG6_INVERT_A_0 (1 << 0) -+#define OAREPORTTRIG6_INVERT_A_1 (1 << 1) -+#define OAREPORTTRIG6_INVERT_A_2 (1 << 2) -+#define OAREPORTTRIG6_INVERT_A_3 (1 << 3) -+#define OAREPORTTRIG6_INVERT_A_4 (1 << 4) -+#define OAREPORTTRIG6_INVERT_A_5 (1 << 5) -+#define OAREPORTTRIG6_INVERT_A_6 (1 << 6) -+#define OAREPORTTRIG6_INVERT_A_7 (1 << 7) -+#define OAREPORTTRIG6_INVERT_A_8 (1 << 8) -+#define OAREPORTTRIG6_INVERT_A_9 (1 << 9) -+#define OAREPORTTRIG6_INVERT_A_10 (1 << 10) -+#define OAREPORTTRIG6_INVERT_A_11 (1 << 11) -+#define OAREPORTTRIG6_INVERT_A_12 (1 << 12) -+#define OAREPORTTRIG6_INVERT_A_13 (1 << 13) -+#define OAREPORTTRIG6_INVERT_A_14 (1 << 14) -+#define OAREPORTTRIG6_INVERT_A_15 (1 << 15) -+#define OAREPORTTRIG6_INVERT_B_0 (1 << 16) -+#define OAREPORTTRIG6_INVERT_B_1 (1 << 17) -+#define OAREPORTTRIG6_INVERT_B_2 (1 << 18) -+#define OAREPORTTRIG6_INVERT_B_3 (1 << 19) -+#define OAREPORTTRIG6_INVERT_C_0 (1 << 20) -+#define OAREPORTTRIG6_INVERT_C_1 (1 << 21) -+#define OAREPORTTRIG6_INVERT_D_0 (1 << 22) -+#define OAREPORTTRIG6_THRESHOLD_ENABLE (1 << 23) -+#define OAREPORTTRIG6_REPORT_TRIGGER_ENABLE (1 << 31) -+ -+#define OAREPORTTRIG7 _MMIO(0x2758) -+#define OAREPORTTRIG7_NOA_SELECT_MASK 0xf -+#define OAREPORTTRIG7_NOA_SELECT_8_SHIFT 0 -+#define OAREPORTTRIG7_NOA_SELECT_9_SHIFT 4 -+#define OAREPORTTRIG7_NOA_SELECT_10_SHIFT 8 -+#define OAREPORTTRIG7_NOA_SELECT_11_SHIFT 12 -+#define OAREPORTTRIG7_NOA_SELECT_12_SHIFT 16 -+#define OAREPORTTRIG7_NOA_SELECT_13_SHIFT 20 -+#define OAREPORTTRIG7_NOA_SELECT_14_SHIFT 24 -+#define OAREPORTTRIG7_NOA_SELECT_15_SHIFT 28 -+ -+#define OAREPORTTRIG8 _MMIO(0x275c) -+#define OAREPORTTRIG8_NOA_SELECT_MASK 0xf -+#define OAREPORTTRIG8_NOA_SELECT_0_SHIFT 0 -+#define OAREPORTTRIG8_NOA_SELECT_1_SHIFT 4 -+#define OAREPORTTRIG8_NOA_SELECT_2_SHIFT 8 -+#define OAREPORTTRIG8_NOA_SELECT_3_SHIFT 12 -+#define OAREPORTTRIG8_NOA_SELECT_4_SHIFT 16 -+#define OAREPORTTRIG8_NOA_SELECT_5_SHIFT 20 -+#define OAREPORTTRIG8_NOA_SELECT_6_SHIFT 24 -+#define OAREPORTTRIG8_NOA_SELECT_7_SHIFT 28 -+ -+/* CECX_0 */ -+#define OACEC_COMPARE_LESS_OR_EQUAL 6 -+#define OACEC_COMPARE_NOT_EQUAL 5 -+#define OACEC_COMPARE_LESS_THAN 4 -+#define OACEC_COMPARE_GREATER_OR_EQUAL 3 -+#define OACEC_COMPARE_EQUAL 2 -+#define OACEC_COMPARE_GREATER_THAN 1 -+#define OACEC_COMPARE_ANY_EQUAL 0 -+ -+#define OACEC_COMPARE_VALUE_MASK 0xffff -+#define OACEC_COMPARE_VALUE_SHIFT 3 -+ -+#define OACEC_SELECT_NOA (0 << 19) -+#define OACEC_SELECT_PREV (1 << 19) -+#define OACEC_SELECT_BOOLEAN (2 << 19) -+ -+/* CECX_1 */ -+#define OACEC_MASK_MASK 0xffff -+#define OACEC_CONSIDERATIONS_MASK 0xffff -+#define OACEC_CONSIDERATIONS_SHIFT 16 -+ -+#define OACEC0_0 _MMIO(0x2770) -+#define OACEC0_1 _MMIO(0x2774) -+#define OACEC1_0 _MMIO(0x2778) -+#define OACEC1_1 _MMIO(0x277c) -+#define OACEC2_0 _MMIO(0x2780) -+#define OACEC2_1 _MMIO(0x2784) -+#define OACEC3_0 _MMIO(0x2788) -+#define OACEC3_1 _MMIO(0x278c) -+#define OACEC4_0 _MMIO(0x2790) -+#define OACEC4_1 _MMIO(0x2794) -+#define OACEC5_0 _MMIO(0x2798) -+#define OACEC5_1 _MMIO(0x279c) -+#define OACEC6_0 _MMIO(0x27a0) -+#define OACEC6_1 _MMIO(0x27a4) -+#define OACEC7_0 _MMIO(0x27a8) -+#define OACEC7_1 _MMIO(0x27ac) -+ -+/* OA perf counters */ -+#define OA_PERFCNT1_LO _MMIO(0x91B8) -+#define OA_PERFCNT1_HI _MMIO(0x91BC) -+#define OA_PERFCNT2_LO _MMIO(0x91C0) -+#define OA_PERFCNT2_HI _MMIO(0x91C4) -+#define OA_PERFCNT3_LO _MMIO(0x91C8) -+#define OA_PERFCNT3_HI _MMIO(0x91CC) -+#define OA_PERFCNT4_LO _MMIO(0x91D8) -+#define OA_PERFCNT4_HI _MMIO(0x91DC) -+ -+#define OA_PERFMATRIX_LO _MMIO(0x91C8) -+#define OA_PERFMATRIX_HI _MMIO(0x91CC) -+ -+/* RPM unit config (Gen8+) */ -+#define RPM_CONFIG0 _MMIO(0x0D00) -+#define GEN9_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_SHIFT 3 -+#define GEN9_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_MASK (1 << GEN9_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_SHIFT) -+#define GEN9_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_19_2_MHZ 0 -+#define GEN9_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_24_MHZ 1 -+#define GEN11_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_SHIFT 3 -+#define GEN11_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_MASK (0x7 << GEN11_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_SHIFT) -+#define GEN11_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_24_MHZ 0 -+#define GEN11_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_19_2_MHZ 1 -+#define GEN11_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_38_4_MHZ 2 -+#define GEN11_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_25_MHZ 3 -+#define GEN10_RPM_CONFIG0_CTC_SHIFT_PARAMETER_SHIFT 1 -+#define GEN10_RPM_CONFIG0_CTC_SHIFT_PARAMETER_MASK (0x3 << GEN10_RPM_CONFIG0_CTC_SHIFT_PARAMETER_SHIFT) -+ -+#define RPM_CONFIG1 _MMIO(0x0D04) -+#define GEN10_GT_NOA_ENABLE (1 << 9) -+ -+/* GPM unit config (Gen9+) */ -+#define CTC_MODE _MMIO(0xA26C) -+#define CTC_SOURCE_PARAMETER_MASK 1 -+#define CTC_SOURCE_CRYSTAL_CLOCK 0 -+#define CTC_SOURCE_DIVIDE_LOGIC 1 -+#define CTC_SHIFT_PARAMETER_SHIFT 1 -+#define CTC_SHIFT_PARAMETER_MASK (0x3 << CTC_SHIFT_PARAMETER_SHIFT) -+ -+/* RCP unit config (Gen8+) */ -+#define RCP_CONFIG _MMIO(0x0D08) -+ -+/* NOA (HSW) */ -+#define HSW_MBVID2_NOA0 _MMIO(0x9E80) -+#define HSW_MBVID2_NOA1 _MMIO(0x9E84) -+#define HSW_MBVID2_NOA2 _MMIO(0x9E88) -+#define HSW_MBVID2_NOA3 _MMIO(0x9E8C) -+#define HSW_MBVID2_NOA4 _MMIO(0x9E90) -+#define HSW_MBVID2_NOA5 _MMIO(0x9E94) -+#define HSW_MBVID2_NOA6 _MMIO(0x9E98) -+#define HSW_MBVID2_NOA7 _MMIO(0x9E9C) -+#define HSW_MBVID2_NOA8 _MMIO(0x9EA0) -+#define HSW_MBVID2_NOA9 _MMIO(0x9EA4) -+ -+#define HSW_MBVID2_MISR0 _MMIO(0x9EC0) -+ -+/* NOA (Gen8+) */ -+#define NOA_CONFIG(i) _MMIO(0x0D0C + (i) * 4) -+ -+#define MICRO_BP0_0 _MMIO(0x9800) -+#define MICRO_BP0_2 _MMIO(0x9804) -+#define MICRO_BP0_1 _MMIO(0x9808) -+ -+#define MICRO_BP1_0 _MMIO(0x980C) -+#define MICRO_BP1_2 _MMIO(0x9810) -+#define MICRO_BP1_1 _MMIO(0x9814) -+ -+#define MICRO_BP2_0 _MMIO(0x9818) -+#define MICRO_BP2_2 _MMIO(0x981C) -+#define MICRO_BP2_1 _MMIO(0x9820) -+ -+#define MICRO_BP3_0 _MMIO(0x9824) -+#define MICRO_BP3_2 _MMIO(0x9828) -+#define MICRO_BP3_1 _MMIO(0x982C) -+ -+#define MICRO_BP_TRIGGER _MMIO(0x9830) -+#define MICRO_BP3_COUNT_STATUS01 _MMIO(0x9834) -+#define MICRO_BP3_COUNT_STATUS23 _MMIO(0x9838) -+#define MICRO_BP_FIRED_ARMED _MMIO(0x983C) -+ -+#define GDT_CHICKEN_BITS _MMIO(0x9840) -+#define GT_NOA_ENABLE 0x00000080 -+ -+#define NOA_DATA _MMIO(0x986C) -+#define NOA_WRITE _MMIO(0x9888) -+#define GEN10_NOA_WRITE_HIGH _MMIO(0x9884) -+ -+#define _GEN7_PIPEA_DE_LOAD_SL 0x70068 -+#define _GEN7_PIPEB_DE_LOAD_SL 0x71068 -+#define GEN7_PIPE_DE_LOAD_SL(pipe) _MMIO_PIPE(pipe, _GEN7_PIPEA_DE_LOAD_SL, _GEN7_PIPEB_DE_LOAD_SL) -+ -+/* -+ * Reset registers -+ */ -+#define DEBUG_RESET_I830 _MMIO(0x6070) -+#define DEBUG_RESET_FULL (1 << 7) -+#define DEBUG_RESET_RENDER (1 << 8) -+#define DEBUG_RESET_DISPLAY (1 << 9) -+ -+/* -+ * IOSF sideband -+ */ -+#define VLV_IOSF_DOORBELL_REQ _MMIO(VLV_DISPLAY_BASE + 0x2100) -+#define IOSF_DEVFN_SHIFT 24 -+#define IOSF_OPCODE_SHIFT 16 -+#define IOSF_PORT_SHIFT 8 -+#define IOSF_BYTE_ENABLES_SHIFT 4 -+#define IOSF_BAR_SHIFT 1 -+#define IOSF_SB_BUSY (1 << 0) -+#define IOSF_PORT_BUNIT 0x03 -+#define IOSF_PORT_PUNIT 0x04 -+#define IOSF_PORT_NC 0x11 -+#define IOSF_PORT_DPIO 0x12 -+#define IOSF_PORT_GPIO_NC 0x13 -+#define IOSF_PORT_CCK 0x14 -+#define IOSF_PORT_DPIO_2 0x1a -+#define IOSF_PORT_FLISDSI 0x1b -+#define IOSF_PORT_GPIO_SC 0x48 -+#define IOSF_PORT_GPIO_SUS 0xa8 -+#define IOSF_PORT_CCU 0xa9 -+#define CHV_IOSF_PORT_GPIO_N 0x13 -+#define CHV_IOSF_PORT_GPIO_SE 0x48 -+#define CHV_IOSF_PORT_GPIO_E 0xa8 -+#define CHV_IOSF_PORT_GPIO_SW 0xb2 -+#define VLV_IOSF_DATA _MMIO(VLV_DISPLAY_BASE + 0x2104) -+#define VLV_IOSF_ADDR _MMIO(VLV_DISPLAY_BASE + 0x2108) -+ -+/* See configdb bunit SB addr map */ -+#define BUNIT_REG_BISOC 0x11 -+ -+/* PUNIT_REG_*SSPM0 */ -+#define _SSPM0_SSC(val) ((val) << 0) -+#define SSPM0_SSC_MASK _SSPM0_SSC(0x3) -+#define SSPM0_SSC_PWR_ON _SSPM0_SSC(0x0) -+#define SSPM0_SSC_CLK_GATE _SSPM0_SSC(0x1) -+#define SSPM0_SSC_RESET _SSPM0_SSC(0x2) -+#define SSPM0_SSC_PWR_GATE _SSPM0_SSC(0x3) -+#define _SSPM0_SSS(val) ((val) << 24) -+#define SSPM0_SSS_MASK _SSPM0_SSS(0x3) -+#define SSPM0_SSS_PWR_ON _SSPM0_SSS(0x0) -+#define SSPM0_SSS_CLK_GATE _SSPM0_SSS(0x1) -+#define SSPM0_SSS_RESET _SSPM0_SSS(0x2) -+#define SSPM0_SSS_PWR_GATE _SSPM0_SSS(0x3) -+ -+/* PUNIT_REG_*SSPM1 */ -+#define SSPM1_FREQSTAT_SHIFT 24 -+#define SSPM1_FREQSTAT_MASK (0x1f << SSPM1_FREQSTAT_SHIFT) -+#define SSPM1_FREQGUAR_SHIFT 8 -+#define SSPM1_FREQGUAR_MASK (0x1f << SSPM1_FREQGUAR_SHIFT) -+#define SSPM1_FREQ_SHIFT 0 -+#define SSPM1_FREQ_MASK (0x1f << SSPM1_FREQ_SHIFT) -+ -+#define PUNIT_REG_VEDSSPM0 0x32 -+#define PUNIT_REG_VEDSSPM1 0x33 -+ -+#define PUNIT_REG_DSPSSPM 0x36 -+#define DSPFREQSTAT_SHIFT_CHV 24 -+#define DSPFREQSTAT_MASK_CHV (0x1f << DSPFREQSTAT_SHIFT_CHV) -+#define DSPFREQGUAR_SHIFT_CHV 8 -+#define DSPFREQGUAR_MASK_CHV (0x1f << DSPFREQGUAR_SHIFT_CHV) -+#define DSPFREQSTAT_SHIFT 30 -+#define DSPFREQSTAT_MASK (0x3 << DSPFREQSTAT_SHIFT) -+#define DSPFREQGUAR_SHIFT 14 -+#define DSPFREQGUAR_MASK (0x3 << DSPFREQGUAR_SHIFT) -+#define DSP_MAXFIFO_PM5_STATUS (1 << 22) /* chv */ -+#define DSP_AUTO_CDCLK_GATE_DISABLE (1 << 7) /* chv */ -+#define DSP_MAXFIFO_PM5_ENABLE (1 << 6) /* chv */ -+#define _DP_SSC(val, pipe) ((val) << (2 * (pipe))) -+#define DP_SSC_MASK(pipe) _DP_SSC(0x3, (pipe)) -+#define DP_SSC_PWR_ON(pipe) _DP_SSC(0x0, (pipe)) -+#define DP_SSC_CLK_GATE(pipe) _DP_SSC(0x1, (pipe)) -+#define DP_SSC_RESET(pipe) _DP_SSC(0x2, (pipe)) -+#define DP_SSC_PWR_GATE(pipe) _DP_SSC(0x3, (pipe)) -+#define _DP_SSS(val, pipe) ((val) << (2 * (pipe) + 16)) -+#define DP_SSS_MASK(pipe) _DP_SSS(0x3, (pipe)) -+#define DP_SSS_PWR_ON(pipe) _DP_SSS(0x0, (pipe)) -+#define DP_SSS_CLK_GATE(pipe) _DP_SSS(0x1, (pipe)) -+#define DP_SSS_RESET(pipe) _DP_SSS(0x2, (pipe)) -+#define DP_SSS_PWR_GATE(pipe) _DP_SSS(0x3, (pipe)) -+ -+#define PUNIT_REG_ISPSSPM0 0x39 -+#define PUNIT_REG_ISPSSPM1 0x3a -+ -+/* -+ * i915_power_well_id: -+ * -+ * IDs used to look up power wells. Power wells accessed directly bypassing -+ * the power domains framework must be assigned a unique ID. The rest of power -+ * wells must be assigned DISP_PW_ID_NONE. -+ */ -+enum i915_power_well_id { -+ DISP_PW_ID_NONE, -+ -+ VLV_DISP_PW_DISP2D, -+ BXT_DISP_PW_DPIO_CMN_A, -+ VLV_DISP_PW_DPIO_CMN_BC, -+ GLK_DISP_PW_DPIO_CMN_C, -+ CHV_DISP_PW_DPIO_CMN_D, -+ HSW_DISP_PW_GLOBAL, -+ SKL_DISP_PW_MISC_IO, -+ SKL_DISP_PW_1, -+ SKL_DISP_PW_2, -+}; -+ -+#define PUNIT_REG_PWRGT_CTRL 0x60 -+#define PUNIT_REG_PWRGT_STATUS 0x61 -+#define PUNIT_PWRGT_MASK(pw_idx) (3 << ((pw_idx) * 2)) -+#define PUNIT_PWRGT_PWR_ON(pw_idx) (0 << ((pw_idx) * 2)) -+#define PUNIT_PWRGT_CLK_GATE(pw_idx) (1 << ((pw_idx) * 2)) -+#define PUNIT_PWRGT_RESET(pw_idx) (2 << ((pw_idx) * 2)) -+#define PUNIT_PWRGT_PWR_GATE(pw_idx) (3 << ((pw_idx) * 2)) -+ -+#define PUNIT_PWGT_IDX_RENDER 0 -+#define PUNIT_PWGT_IDX_MEDIA 1 -+#define PUNIT_PWGT_IDX_DISP2D 3 -+#define PUNIT_PWGT_IDX_DPIO_CMN_BC 5 -+#define PUNIT_PWGT_IDX_DPIO_TX_B_LANES_01 6 -+#define PUNIT_PWGT_IDX_DPIO_TX_B_LANES_23 7 -+#define PUNIT_PWGT_IDX_DPIO_TX_C_LANES_01 8 -+#define PUNIT_PWGT_IDX_DPIO_TX_C_LANES_23 9 -+#define PUNIT_PWGT_IDX_DPIO_RX0 10 -+#define PUNIT_PWGT_IDX_DPIO_RX1 11 -+#define PUNIT_PWGT_IDX_DPIO_CMN_D 12 -+ -+#define PUNIT_REG_GPU_LFM 0xd3 -+#define PUNIT_REG_GPU_FREQ_REQ 0xd4 -+#define PUNIT_REG_GPU_FREQ_STS 0xd8 -+#define GPLLENABLE (1 << 4) -+#define GENFREQSTATUS (1 << 0) -+#define PUNIT_REG_MEDIA_TURBO_FREQ_REQ 0xdc -+#define PUNIT_REG_CZ_TIMESTAMP 0xce -+ -+#define PUNIT_FUSE_BUS2 0xf6 /* bits 47:40 */ -+#define PUNIT_FUSE_BUS1 0xf5 /* bits 55:48 */ -+ -+#define FB_GFX_FMAX_AT_VMAX_FUSE 0x136 -+#define FB_GFX_FREQ_FUSE_MASK 0xff -+#define FB_GFX_FMAX_AT_VMAX_2SS4EU_FUSE_SHIFT 24 -+#define FB_GFX_FMAX_AT_VMAX_2SS6EU_FUSE_SHIFT 16 -+#define FB_GFX_FMAX_AT_VMAX_2SS8EU_FUSE_SHIFT 8 -+ -+#define FB_GFX_FMIN_AT_VMIN_FUSE 0x137 -+#define FB_GFX_FMIN_AT_VMIN_FUSE_SHIFT 8 -+ -+#define PUNIT_REG_DDR_SETUP2 0x139 -+#define FORCE_DDR_FREQ_REQ_ACK (1 << 8) -+#define FORCE_DDR_LOW_FREQ (1 << 1) -+#define FORCE_DDR_HIGH_FREQ (1 << 0) -+ -+#define PUNIT_GPU_STATUS_REG 0xdb -+#define PUNIT_GPU_STATUS_MAX_FREQ_SHIFT 16 -+#define PUNIT_GPU_STATUS_MAX_FREQ_MASK 0xff -+#define PUNIT_GPU_STATIS_GFX_MIN_FREQ_SHIFT 8 -+#define PUNIT_GPU_STATUS_GFX_MIN_FREQ_MASK 0xff -+ -+#define PUNIT_GPU_DUTYCYCLE_REG 0xdf -+#define PUNIT_GPU_DUTYCYCLE_RPE_FREQ_SHIFT 8 -+#define PUNIT_GPU_DUTYCYCLE_RPE_FREQ_MASK 0xff -+ -+#define IOSF_NC_FB_GFX_FREQ_FUSE 0x1c -+#define FB_GFX_MAX_FREQ_FUSE_SHIFT 3 -+#define FB_GFX_MAX_FREQ_FUSE_MASK 0x000007f8 -+#define FB_GFX_FGUARANTEED_FREQ_FUSE_SHIFT 11 -+#define FB_GFX_FGUARANTEED_FREQ_FUSE_MASK 0x0007f800 -+#define IOSF_NC_FB_GFX_FMAX_FUSE_HI 0x34 -+#define FB_FMAX_VMIN_FREQ_HI_MASK 0x00000007 -+#define IOSF_NC_FB_GFX_FMAX_FUSE_LO 0x30 -+#define FB_FMAX_VMIN_FREQ_LO_SHIFT 27 -+#define FB_FMAX_VMIN_FREQ_LO_MASK 0xf8000000 -+ -+#define VLV_TURBO_SOC_OVERRIDE 0x04 -+#define VLV_OVERRIDE_EN 1 -+#define VLV_SOC_TDP_EN (1 << 1) -+#define VLV_BIAS_CPU_125_SOC_875 (6 << 2) -+#define CHV_BIAS_CPU_50_SOC_50 (3 << 2) -+ -+/* vlv2 north clock has */ -+#define CCK_FUSE_REG 0x8 -+#define CCK_FUSE_HPLL_FREQ_MASK 0x3 -+#define CCK_REG_DSI_PLL_FUSE 0x44 -+#define CCK_REG_DSI_PLL_CONTROL 0x48 -+#define DSI_PLL_VCO_EN (1 << 31) -+#define DSI_PLL_LDO_GATE (1 << 30) -+#define DSI_PLL_P1_POST_DIV_SHIFT 17 -+#define DSI_PLL_P1_POST_DIV_MASK (0x1ff << 17) -+#define DSI_PLL_P2_MUX_DSI0_DIV2 (1 << 13) -+#define DSI_PLL_P3_MUX_DSI1_DIV2 (1 << 12) -+#define DSI_PLL_MUX_MASK (3 << 9) -+#define DSI_PLL_MUX_DSI0_DSIPLL (0 << 10) -+#define DSI_PLL_MUX_DSI0_CCK (1 << 10) -+#define DSI_PLL_MUX_DSI1_DSIPLL (0 << 9) -+#define DSI_PLL_MUX_DSI1_CCK (1 << 9) -+#define DSI_PLL_CLK_GATE_MASK (0xf << 5) -+#define DSI_PLL_CLK_GATE_DSI0_DSIPLL (1 << 8) -+#define DSI_PLL_CLK_GATE_DSI1_DSIPLL (1 << 7) -+#define DSI_PLL_CLK_GATE_DSI0_CCK (1 << 6) -+#define DSI_PLL_CLK_GATE_DSI1_CCK (1 << 5) -+#define DSI_PLL_LOCK (1 << 0) -+#define CCK_REG_DSI_PLL_DIVIDER 0x4c -+#define DSI_PLL_LFSR (1 << 31) -+#define DSI_PLL_FRACTION_EN (1 << 30) -+#define DSI_PLL_FRAC_COUNTER_SHIFT 27 -+#define DSI_PLL_FRAC_COUNTER_MASK (7 << 27) -+#define DSI_PLL_USYNC_CNT_SHIFT 18 -+#define DSI_PLL_USYNC_CNT_MASK (0x1ff << 18) -+#define DSI_PLL_N1_DIV_SHIFT 16 -+#define DSI_PLL_N1_DIV_MASK (3 << 16) -+#define DSI_PLL_M1_DIV_SHIFT 0 -+#define DSI_PLL_M1_DIV_MASK (0x1ff << 0) -+#define CCK_CZ_CLOCK_CONTROL 0x62 -+#define CCK_GPLL_CLOCK_CONTROL 0x67 -+#define CCK_DISPLAY_CLOCK_CONTROL 0x6b -+#define CCK_DISPLAY_REF_CLOCK_CONTROL 0x6c -+#define CCK_TRUNK_FORCE_ON (1 << 17) -+#define CCK_TRUNK_FORCE_OFF (1 << 16) -+#define CCK_FREQUENCY_STATUS (0x1f << 8) -+#define CCK_FREQUENCY_STATUS_SHIFT 8 -+#define CCK_FREQUENCY_VALUES (0x1f << 0) -+ -+/* DPIO registers */ -+#define DPIO_DEVFN 0 -+ -+#define DPIO_CTL _MMIO(VLV_DISPLAY_BASE + 0x2110) -+#define DPIO_MODSEL1 (1 << 3) /* if ref clk b == 27 */ -+#define DPIO_MODSEL0 (1 << 2) /* if ref clk a == 27 */ -+#define DPIO_SFR_BYPASS (1 << 1) -+#define DPIO_CMNRST (1 << 0) -+ -+#define DPIO_PHY(pipe) ((pipe) >> 1) -+#define DPIO_PHY_IOSF_PORT(phy) (dev_priv->dpio_phy_iosf_port[phy]) -+ -+/* -+ * Per pipe/PLL DPIO regs -+ */ -+#define _VLV_PLL_DW3_CH0 0x800c -+#define DPIO_POST_DIV_SHIFT (28) /* 3 bits */ -+#define DPIO_POST_DIV_DAC 0 -+#define DPIO_POST_DIV_HDMIDP 1 /* DAC 225-400M rate */ -+#define DPIO_POST_DIV_LVDS1 2 -+#define DPIO_POST_DIV_LVDS2 3 -+#define DPIO_K_SHIFT (24) /* 4 bits */ -+#define DPIO_P1_SHIFT (21) /* 3 bits */ -+#define DPIO_P2_SHIFT (16) /* 5 bits */ -+#define DPIO_N_SHIFT (12) /* 4 bits */ -+#define DPIO_ENABLE_CALIBRATION (1 << 11) -+#define DPIO_M1DIV_SHIFT (8) /* 3 bits */ -+#define DPIO_M2DIV_MASK 0xff -+#define _VLV_PLL_DW3_CH1 0x802c -+#define VLV_PLL_DW3(ch) _PIPE(ch, _VLV_PLL_DW3_CH0, _VLV_PLL_DW3_CH1) -+ -+#define _VLV_PLL_DW5_CH0 0x8014 -+#define DPIO_REFSEL_OVERRIDE 27 -+#define DPIO_PLL_MODESEL_SHIFT 24 /* 3 bits */ -+#define DPIO_BIAS_CURRENT_CTL_SHIFT 21 /* 3 bits, always 0x7 */ -+#define DPIO_PLL_REFCLK_SEL_SHIFT 16 /* 2 bits */ -+#define DPIO_PLL_REFCLK_SEL_MASK 3 -+#define DPIO_DRIVER_CTL_SHIFT 12 /* always set to 0x8 */ -+#define DPIO_CLK_BIAS_CTL_SHIFT 8 /* always set to 0x5 */ -+#define _VLV_PLL_DW5_CH1 0x8034 -+#define VLV_PLL_DW5(ch) _PIPE(ch, _VLV_PLL_DW5_CH0, _VLV_PLL_DW5_CH1) -+ -+#define _VLV_PLL_DW7_CH0 0x801c -+#define _VLV_PLL_DW7_CH1 0x803c -+#define VLV_PLL_DW7(ch) _PIPE(ch, _VLV_PLL_DW7_CH0, _VLV_PLL_DW7_CH1) -+ -+#define _VLV_PLL_DW8_CH0 0x8040 -+#define _VLV_PLL_DW8_CH1 0x8060 -+#define VLV_PLL_DW8(ch) _PIPE(ch, _VLV_PLL_DW8_CH0, _VLV_PLL_DW8_CH1) -+ -+#define VLV_PLL_DW9_BCAST 0xc044 -+#define _VLV_PLL_DW9_CH0 0x8044 -+#define _VLV_PLL_DW9_CH1 0x8064 -+#define VLV_PLL_DW9(ch) _PIPE(ch, _VLV_PLL_DW9_CH0, _VLV_PLL_DW9_CH1) -+ -+#define _VLV_PLL_DW10_CH0 0x8048 -+#define _VLV_PLL_DW10_CH1 0x8068 -+#define VLV_PLL_DW10(ch) _PIPE(ch, _VLV_PLL_DW10_CH0, _VLV_PLL_DW10_CH1) -+ -+#define _VLV_PLL_DW11_CH0 0x804c -+#define _VLV_PLL_DW11_CH1 0x806c -+#define VLV_PLL_DW11(ch) _PIPE(ch, _VLV_PLL_DW11_CH0, _VLV_PLL_DW11_CH1) -+ -+/* Spec for ref block start counts at DW10 */ -+#define VLV_REF_DW13 0x80ac -+ -+#define VLV_CMN_DW0 0x8100 -+ -+/* -+ * Per DDI channel DPIO regs -+ */ -+ -+#define _VLV_PCS_DW0_CH0 0x8200 -+#define _VLV_PCS_DW0_CH1 0x8400 -+#define DPIO_PCS_TX_LANE2_RESET (1 << 16) -+#define DPIO_PCS_TX_LANE1_RESET (1 << 7) -+#define DPIO_LEFT_TXFIFO_RST_MASTER2 (1 << 4) -+#define DPIO_RIGHT_TXFIFO_RST_MASTER2 (1 << 3) -+#define VLV_PCS_DW0(ch) _PORT(ch, _VLV_PCS_DW0_CH0, _VLV_PCS_DW0_CH1) -+ -+#define _VLV_PCS01_DW0_CH0 0x200 -+#define _VLV_PCS23_DW0_CH0 0x400 -+#define _VLV_PCS01_DW0_CH1 0x2600 -+#define _VLV_PCS23_DW0_CH1 0x2800 -+#define VLV_PCS01_DW0(ch) _PORT(ch, _VLV_PCS01_DW0_CH0, _VLV_PCS01_DW0_CH1) -+#define VLV_PCS23_DW0(ch) _PORT(ch, _VLV_PCS23_DW0_CH0, _VLV_PCS23_DW0_CH1) -+ -+#define _VLV_PCS_DW1_CH0 0x8204 -+#define _VLV_PCS_DW1_CH1 0x8404 -+#define CHV_PCS_REQ_SOFTRESET_EN (1 << 23) -+#define DPIO_PCS_CLK_CRI_RXEB_EIOS_EN (1 << 22) -+#define DPIO_PCS_CLK_CRI_RXDIGFILTSG_EN (1 << 21) -+#define DPIO_PCS_CLK_DATAWIDTH_SHIFT (6) -+#define DPIO_PCS_CLK_SOFT_RESET (1 << 5) -+#define VLV_PCS_DW1(ch) _PORT(ch, _VLV_PCS_DW1_CH0, _VLV_PCS_DW1_CH1) -+ -+#define _VLV_PCS01_DW1_CH0 0x204 -+#define _VLV_PCS23_DW1_CH0 0x404 -+#define _VLV_PCS01_DW1_CH1 0x2604 -+#define _VLV_PCS23_DW1_CH1 0x2804 -+#define VLV_PCS01_DW1(ch) _PORT(ch, _VLV_PCS01_DW1_CH0, _VLV_PCS01_DW1_CH1) -+#define VLV_PCS23_DW1(ch) _PORT(ch, _VLV_PCS23_DW1_CH0, _VLV_PCS23_DW1_CH1) -+ -+#define _VLV_PCS_DW8_CH0 0x8220 -+#define _VLV_PCS_DW8_CH1 0x8420 -+#define CHV_PCS_USEDCLKCHANNEL_OVRRIDE (1 << 20) -+#define CHV_PCS_USEDCLKCHANNEL (1 << 21) -+#define VLV_PCS_DW8(ch) _PORT(ch, _VLV_PCS_DW8_CH0, _VLV_PCS_DW8_CH1) -+ -+#define _VLV_PCS01_DW8_CH0 0x0220 -+#define _VLV_PCS23_DW8_CH0 0x0420 -+#define _VLV_PCS01_DW8_CH1 0x2620 -+#define _VLV_PCS23_DW8_CH1 0x2820 -+#define VLV_PCS01_DW8(port) _PORT(port, _VLV_PCS01_DW8_CH0, _VLV_PCS01_DW8_CH1) -+#define VLV_PCS23_DW8(port) _PORT(port, _VLV_PCS23_DW8_CH0, _VLV_PCS23_DW8_CH1) -+ -+#define _VLV_PCS_DW9_CH0 0x8224 -+#define _VLV_PCS_DW9_CH1 0x8424 -+#define DPIO_PCS_TX2MARGIN_MASK (0x7 << 13) -+#define DPIO_PCS_TX2MARGIN_000 (0 << 13) -+#define DPIO_PCS_TX2MARGIN_101 (1 << 13) -+#define DPIO_PCS_TX1MARGIN_MASK (0x7 << 10) -+#define DPIO_PCS_TX1MARGIN_000 (0 << 10) -+#define DPIO_PCS_TX1MARGIN_101 (1 << 10) -+#define VLV_PCS_DW9(ch) _PORT(ch, _VLV_PCS_DW9_CH0, _VLV_PCS_DW9_CH1) -+ -+#define _VLV_PCS01_DW9_CH0 0x224 -+#define _VLV_PCS23_DW9_CH0 0x424 -+#define _VLV_PCS01_DW9_CH1 0x2624 -+#define _VLV_PCS23_DW9_CH1 0x2824 -+#define VLV_PCS01_DW9(ch) _PORT(ch, _VLV_PCS01_DW9_CH0, _VLV_PCS01_DW9_CH1) -+#define VLV_PCS23_DW9(ch) _PORT(ch, _VLV_PCS23_DW9_CH0, _VLV_PCS23_DW9_CH1) -+ -+#define _CHV_PCS_DW10_CH0 0x8228 -+#define _CHV_PCS_DW10_CH1 0x8428 -+#define DPIO_PCS_SWING_CALC_TX0_TX2 (1 << 30) -+#define DPIO_PCS_SWING_CALC_TX1_TX3 (1 << 31) -+#define DPIO_PCS_TX2DEEMP_MASK (0xf << 24) -+#define DPIO_PCS_TX2DEEMP_9P5 (0 << 24) -+#define DPIO_PCS_TX2DEEMP_6P0 (2 << 24) -+#define DPIO_PCS_TX1DEEMP_MASK (0xf << 16) -+#define DPIO_PCS_TX1DEEMP_9P5 (0 << 16) -+#define DPIO_PCS_TX1DEEMP_6P0 (2 << 16) -+#define CHV_PCS_DW10(ch) _PORT(ch, _CHV_PCS_DW10_CH0, _CHV_PCS_DW10_CH1) -+ -+#define _VLV_PCS01_DW10_CH0 0x0228 -+#define _VLV_PCS23_DW10_CH0 0x0428 -+#define _VLV_PCS01_DW10_CH1 0x2628 -+#define _VLV_PCS23_DW10_CH1 0x2828 -+#define VLV_PCS01_DW10(port) _PORT(port, _VLV_PCS01_DW10_CH0, _VLV_PCS01_DW10_CH1) -+#define VLV_PCS23_DW10(port) _PORT(port, _VLV_PCS23_DW10_CH0, _VLV_PCS23_DW10_CH1) -+ -+#define _VLV_PCS_DW11_CH0 0x822c -+#define _VLV_PCS_DW11_CH1 0x842c -+#define DPIO_TX2_STAGGER_MASK(x) ((x) << 24) -+#define DPIO_LANEDESKEW_STRAP_OVRD (1 << 3) -+#define DPIO_LEFT_TXFIFO_RST_MASTER (1 << 1) -+#define DPIO_RIGHT_TXFIFO_RST_MASTER (1 << 0) -+#define VLV_PCS_DW11(ch) _PORT(ch, _VLV_PCS_DW11_CH0, _VLV_PCS_DW11_CH1) -+ -+#define _VLV_PCS01_DW11_CH0 0x022c -+#define _VLV_PCS23_DW11_CH0 0x042c -+#define _VLV_PCS01_DW11_CH1 0x262c -+#define _VLV_PCS23_DW11_CH1 0x282c -+#define VLV_PCS01_DW11(ch) _PORT(ch, _VLV_PCS01_DW11_CH0, _VLV_PCS01_DW11_CH1) -+#define VLV_PCS23_DW11(ch) _PORT(ch, _VLV_PCS23_DW11_CH0, _VLV_PCS23_DW11_CH1) -+ -+#define _VLV_PCS01_DW12_CH0 0x0230 -+#define _VLV_PCS23_DW12_CH0 0x0430 -+#define _VLV_PCS01_DW12_CH1 0x2630 -+#define _VLV_PCS23_DW12_CH1 0x2830 -+#define VLV_PCS01_DW12(ch) _PORT(ch, _VLV_PCS01_DW12_CH0, _VLV_PCS01_DW12_CH1) -+#define VLV_PCS23_DW12(ch) _PORT(ch, _VLV_PCS23_DW12_CH0, _VLV_PCS23_DW12_CH1) -+ -+#define _VLV_PCS_DW12_CH0 0x8230 -+#define _VLV_PCS_DW12_CH1 0x8430 -+#define DPIO_TX2_STAGGER_MULT(x) ((x) << 20) -+#define DPIO_TX1_STAGGER_MULT(x) ((x) << 16) -+#define DPIO_TX1_STAGGER_MASK(x) ((x) << 8) -+#define DPIO_LANESTAGGER_STRAP_OVRD (1 << 6) -+#define DPIO_LANESTAGGER_STRAP(x) ((x) << 0) -+#define VLV_PCS_DW12(ch) _PORT(ch, _VLV_PCS_DW12_CH0, _VLV_PCS_DW12_CH1) -+ -+#define _VLV_PCS_DW14_CH0 0x8238 -+#define _VLV_PCS_DW14_CH1 0x8438 -+#define VLV_PCS_DW14(ch) _PORT(ch, _VLV_PCS_DW14_CH0, _VLV_PCS_DW14_CH1) -+ -+#define _VLV_PCS_DW23_CH0 0x825c -+#define _VLV_PCS_DW23_CH1 0x845c -+#define VLV_PCS_DW23(ch) _PORT(ch, _VLV_PCS_DW23_CH0, _VLV_PCS_DW23_CH1) -+ -+#define _VLV_TX_DW2_CH0 0x8288 -+#define _VLV_TX_DW2_CH1 0x8488 -+#define DPIO_SWING_MARGIN000_SHIFT 16 -+#define DPIO_SWING_MARGIN000_MASK (0xff << DPIO_SWING_MARGIN000_SHIFT) -+#define DPIO_UNIQ_TRANS_SCALE_SHIFT 8 -+#define VLV_TX_DW2(ch) _PORT(ch, _VLV_TX_DW2_CH0, _VLV_TX_DW2_CH1) -+ -+#define _VLV_TX_DW3_CH0 0x828c -+#define _VLV_TX_DW3_CH1 0x848c -+/* The following bit for CHV phy */ -+#define DPIO_TX_UNIQ_TRANS_SCALE_EN (1 << 27) -+#define DPIO_SWING_MARGIN101_SHIFT 16 -+#define DPIO_SWING_MARGIN101_MASK (0xff << DPIO_SWING_MARGIN101_SHIFT) -+#define VLV_TX_DW3(ch) _PORT(ch, _VLV_TX_DW3_CH0, _VLV_TX_DW3_CH1) -+ -+#define _VLV_TX_DW4_CH0 0x8290 -+#define _VLV_TX_DW4_CH1 0x8490 -+#define DPIO_SWING_DEEMPH9P5_SHIFT 24 -+#define DPIO_SWING_DEEMPH9P5_MASK (0xff << DPIO_SWING_DEEMPH9P5_SHIFT) -+#define DPIO_SWING_DEEMPH6P0_SHIFT 16 -+#define DPIO_SWING_DEEMPH6P0_MASK (0xff << DPIO_SWING_DEEMPH6P0_SHIFT) -+#define VLV_TX_DW4(ch) _PORT(ch, _VLV_TX_DW4_CH0, _VLV_TX_DW4_CH1) -+ -+#define _VLV_TX3_DW4_CH0 0x690 -+#define _VLV_TX3_DW4_CH1 0x2a90 -+#define VLV_TX3_DW4(ch) _PORT(ch, _VLV_TX3_DW4_CH0, _VLV_TX3_DW4_CH1) -+ -+#define _VLV_TX_DW5_CH0 0x8294 -+#define _VLV_TX_DW5_CH1 0x8494 -+#define DPIO_TX_OCALINIT_EN (1 << 31) -+#define VLV_TX_DW5(ch) _PORT(ch, _VLV_TX_DW5_CH0, _VLV_TX_DW5_CH1) -+ -+#define _VLV_TX_DW11_CH0 0x82ac -+#define _VLV_TX_DW11_CH1 0x84ac -+#define VLV_TX_DW11(ch) _PORT(ch, _VLV_TX_DW11_CH0, _VLV_TX_DW11_CH1) -+ -+#define _VLV_TX_DW14_CH0 0x82b8 -+#define _VLV_TX_DW14_CH1 0x84b8 -+#define VLV_TX_DW14(ch) _PORT(ch, _VLV_TX_DW14_CH0, _VLV_TX_DW14_CH1) -+ -+/* CHV dpPhy registers */ -+#define _CHV_PLL_DW0_CH0 0x8000 -+#define _CHV_PLL_DW0_CH1 0x8180 -+#define CHV_PLL_DW0(ch) _PIPE(ch, _CHV_PLL_DW0_CH0, _CHV_PLL_DW0_CH1) -+ -+#define _CHV_PLL_DW1_CH0 0x8004 -+#define _CHV_PLL_DW1_CH1 0x8184 -+#define DPIO_CHV_N_DIV_SHIFT 8 -+#define DPIO_CHV_M1_DIV_BY_2 (0 << 0) -+#define CHV_PLL_DW1(ch) _PIPE(ch, _CHV_PLL_DW1_CH0, _CHV_PLL_DW1_CH1) -+ -+#define _CHV_PLL_DW2_CH0 0x8008 -+#define _CHV_PLL_DW2_CH1 0x8188 -+#define CHV_PLL_DW2(ch) _PIPE(ch, _CHV_PLL_DW2_CH0, _CHV_PLL_DW2_CH1) -+ -+#define _CHV_PLL_DW3_CH0 0x800c -+#define _CHV_PLL_DW3_CH1 0x818c -+#define DPIO_CHV_FRAC_DIV_EN (1 << 16) -+#define DPIO_CHV_FIRST_MOD (0 << 8) -+#define DPIO_CHV_SECOND_MOD (1 << 8) -+#define DPIO_CHV_FEEDFWD_GAIN_SHIFT 0 -+#define DPIO_CHV_FEEDFWD_GAIN_MASK (0xF << 0) -+#define CHV_PLL_DW3(ch) _PIPE(ch, _CHV_PLL_DW3_CH0, _CHV_PLL_DW3_CH1) -+ -+#define _CHV_PLL_DW6_CH0 0x8018 -+#define _CHV_PLL_DW6_CH1 0x8198 -+#define DPIO_CHV_GAIN_CTRL_SHIFT 16 -+#define DPIO_CHV_INT_COEFF_SHIFT 8 -+#define DPIO_CHV_PROP_COEFF_SHIFT 0 -+#define CHV_PLL_DW6(ch) _PIPE(ch, _CHV_PLL_DW6_CH0, _CHV_PLL_DW6_CH1) -+ -+#define _CHV_PLL_DW8_CH0 0x8020 -+#define _CHV_PLL_DW8_CH1 0x81A0 -+#define DPIO_CHV_TDC_TARGET_CNT_SHIFT 0 -+#define DPIO_CHV_TDC_TARGET_CNT_MASK (0x3FF << 0) -+#define CHV_PLL_DW8(ch) _PIPE(ch, _CHV_PLL_DW8_CH0, _CHV_PLL_DW8_CH1) -+ -+#define _CHV_PLL_DW9_CH0 0x8024 -+#define _CHV_PLL_DW9_CH1 0x81A4 -+#define DPIO_CHV_INT_LOCK_THRESHOLD_SHIFT 1 /* 3 bits */ -+#define DPIO_CHV_INT_LOCK_THRESHOLD_MASK (7 << 1) -+#define DPIO_CHV_INT_LOCK_THRESHOLD_SEL_COARSE 1 /* 1: coarse & 0 : fine */ -+#define CHV_PLL_DW9(ch) _PIPE(ch, _CHV_PLL_DW9_CH0, _CHV_PLL_DW9_CH1) -+ -+#define _CHV_CMN_DW0_CH0 0x8100 -+#define DPIO_ALLDL_POWERDOWN_SHIFT_CH0 19 -+#define DPIO_ANYDL_POWERDOWN_SHIFT_CH0 18 -+#define DPIO_ALLDL_POWERDOWN (1 << 1) -+#define DPIO_ANYDL_POWERDOWN (1 << 0) -+ -+#define _CHV_CMN_DW5_CH0 0x8114 -+#define CHV_BUFRIGHTENA1_DISABLE (0 << 20) -+#define CHV_BUFRIGHTENA1_NORMAL (1 << 20) -+#define CHV_BUFRIGHTENA1_FORCE (3 << 20) -+#define CHV_BUFRIGHTENA1_MASK (3 << 20) -+#define CHV_BUFLEFTENA1_DISABLE (0 << 22) -+#define CHV_BUFLEFTENA1_NORMAL (1 << 22) -+#define CHV_BUFLEFTENA1_FORCE (3 << 22) -+#define CHV_BUFLEFTENA1_MASK (3 << 22) -+ -+#define _CHV_CMN_DW13_CH0 0x8134 -+#define _CHV_CMN_DW0_CH1 0x8080 -+#define DPIO_CHV_S1_DIV_SHIFT 21 -+#define DPIO_CHV_P1_DIV_SHIFT 13 /* 3 bits */ -+#define DPIO_CHV_P2_DIV_SHIFT 8 /* 5 bits */ -+#define DPIO_CHV_K_DIV_SHIFT 4 -+#define DPIO_PLL_FREQLOCK (1 << 1) -+#define DPIO_PLL_LOCK (1 << 0) -+#define CHV_CMN_DW13(ch) _PIPE(ch, _CHV_CMN_DW13_CH0, _CHV_CMN_DW0_CH1) -+ -+#define _CHV_CMN_DW14_CH0 0x8138 -+#define _CHV_CMN_DW1_CH1 0x8084 -+#define DPIO_AFC_RECAL (1 << 14) -+#define DPIO_DCLKP_EN (1 << 13) -+#define CHV_BUFLEFTENA2_DISABLE (0 << 17) /* CL2 DW1 only */ -+#define CHV_BUFLEFTENA2_NORMAL (1 << 17) /* CL2 DW1 only */ -+#define CHV_BUFLEFTENA2_FORCE (3 << 17) /* CL2 DW1 only */ -+#define CHV_BUFLEFTENA2_MASK (3 << 17) /* CL2 DW1 only */ -+#define CHV_BUFRIGHTENA2_DISABLE (0 << 19) /* CL2 DW1 only */ -+#define CHV_BUFRIGHTENA2_NORMAL (1 << 19) /* CL2 DW1 only */ -+#define CHV_BUFRIGHTENA2_FORCE (3 << 19) /* CL2 DW1 only */ -+#define CHV_BUFRIGHTENA2_MASK (3 << 19) /* CL2 DW1 only */ -+#define CHV_CMN_DW14(ch) _PIPE(ch, _CHV_CMN_DW14_CH0, _CHV_CMN_DW1_CH1) -+ -+#define _CHV_CMN_DW19_CH0 0x814c -+#define _CHV_CMN_DW6_CH1 0x8098 -+#define DPIO_ALLDL_POWERDOWN_SHIFT_CH1 30 /* CL2 DW6 only */ -+#define DPIO_ANYDL_POWERDOWN_SHIFT_CH1 29 /* CL2 DW6 only */ -+#define DPIO_DYNPWRDOWNEN_CH1 (1 << 28) /* CL2 DW6 only */ -+#define CHV_CMN_USEDCLKCHANNEL (1 << 13) -+ -+#define CHV_CMN_DW19(ch) _PIPE(ch, _CHV_CMN_DW19_CH0, _CHV_CMN_DW6_CH1) -+ -+#define CHV_CMN_DW28 0x8170 -+#define DPIO_CL1POWERDOWNEN (1 << 23) -+#define DPIO_DYNPWRDOWNEN_CH0 (1 << 22) -+#define DPIO_SUS_CLK_CONFIG_ON (0 << 0) -+#define DPIO_SUS_CLK_CONFIG_CLKREQ (1 << 0) -+#define DPIO_SUS_CLK_CONFIG_GATE (2 << 0) -+#define DPIO_SUS_CLK_CONFIG_GATE_CLKREQ (3 << 0) -+ -+#define CHV_CMN_DW30 0x8178 -+#define DPIO_CL2_LDOFUSE_PWRENB (1 << 6) -+#define DPIO_LRC_BYPASS (1 << 3) -+ -+#define _TXLANE(ch, lane, offset) ((ch ? 0x2400 : 0) + \ -+ (lane) * 0x200 + (offset)) -+ -+#define CHV_TX_DW0(ch, lane) _TXLANE(ch, lane, 0x80) -+#define CHV_TX_DW1(ch, lane) _TXLANE(ch, lane, 0x84) -+#define CHV_TX_DW2(ch, lane) _TXLANE(ch, lane, 0x88) -+#define CHV_TX_DW3(ch, lane) _TXLANE(ch, lane, 0x8c) -+#define CHV_TX_DW4(ch, lane) _TXLANE(ch, lane, 0x90) -+#define CHV_TX_DW5(ch, lane) _TXLANE(ch, lane, 0x94) -+#define CHV_TX_DW6(ch, lane) _TXLANE(ch, lane, 0x98) -+#define CHV_TX_DW7(ch, lane) _TXLANE(ch, lane, 0x9c) -+#define CHV_TX_DW8(ch, lane) _TXLANE(ch, lane, 0xa0) -+#define CHV_TX_DW9(ch, lane) _TXLANE(ch, lane, 0xa4) -+#define CHV_TX_DW10(ch, lane) _TXLANE(ch, lane, 0xa8) -+#define CHV_TX_DW11(ch, lane) _TXLANE(ch, lane, 0xac) -+#define DPIO_FRC_LATENCY_SHFIT 8 -+#define CHV_TX_DW14(ch, lane) _TXLANE(ch, lane, 0xb8) -+#define DPIO_UPAR_SHIFT 30 -+ -+/* BXT PHY registers */ -+#define _BXT_PHY0_BASE 0x6C000 -+#define _BXT_PHY1_BASE 0x162000 -+#define _BXT_PHY2_BASE 0x163000 -+#define BXT_PHY_BASE(phy) _PHY3((phy), _BXT_PHY0_BASE, \ -+ _BXT_PHY1_BASE, \ -+ _BXT_PHY2_BASE) -+ -+#define _BXT_PHY(phy, reg) \ -+ _MMIO(BXT_PHY_BASE(phy) - _BXT_PHY0_BASE + (reg)) -+ -+#define _BXT_PHY_CH(phy, ch, reg_ch0, reg_ch1) \ -+ (BXT_PHY_BASE(phy) + _PIPE((ch), (reg_ch0) - _BXT_PHY0_BASE, \ -+ (reg_ch1) - _BXT_PHY0_BASE)) -+#define _MMIO_BXT_PHY_CH(phy, ch, reg_ch0, reg_ch1) \ -+ _MMIO(_BXT_PHY_CH(phy, ch, reg_ch0, reg_ch1)) -+ -+#define BXT_P_CR_GT_DISP_PWRON _MMIO(0x138090) -+#define MIPIO_RST_CTRL (1 << 2) -+ -+#define _BXT_PHY_CTL_DDI_A 0x64C00 -+#define _BXT_PHY_CTL_DDI_B 0x64C10 -+#define _BXT_PHY_CTL_DDI_C 0x64C20 -+#define BXT_PHY_CMNLANE_POWERDOWN_ACK (1 << 10) -+#define BXT_PHY_LANE_POWERDOWN_ACK (1 << 9) -+#define BXT_PHY_LANE_ENABLED (1 << 8) -+#define BXT_PHY_CTL(port) _MMIO_PORT(port, _BXT_PHY_CTL_DDI_A, \ -+ _BXT_PHY_CTL_DDI_B) -+ -+#define _PHY_CTL_FAMILY_EDP 0x64C80 -+#define _PHY_CTL_FAMILY_DDI 0x64C90 -+#define _PHY_CTL_FAMILY_DDI_C 0x64CA0 -+#define COMMON_RESET_DIS (1 << 31) -+#define BXT_PHY_CTL_FAMILY(phy) _MMIO_PHY3((phy), _PHY_CTL_FAMILY_DDI, \ -+ _PHY_CTL_FAMILY_EDP, \ -+ _PHY_CTL_FAMILY_DDI_C) -+ -+/* BXT PHY PLL registers */ -+#define _PORT_PLL_A 0x46074 -+#define _PORT_PLL_B 0x46078 -+#define _PORT_PLL_C 0x4607c -+#define PORT_PLL_ENABLE (1 << 31) -+#define PORT_PLL_LOCK (1 << 30) -+#define PORT_PLL_REF_SEL (1 << 27) -+#define PORT_PLL_POWER_ENABLE (1 << 26) -+#define PORT_PLL_POWER_STATE (1 << 25) -+#define BXT_PORT_PLL_ENABLE(port) _MMIO_PORT(port, _PORT_PLL_A, _PORT_PLL_B) -+ -+#define _PORT_PLL_EBB_0_A 0x162034 -+#define _PORT_PLL_EBB_0_B 0x6C034 -+#define _PORT_PLL_EBB_0_C 0x6C340 -+#define PORT_PLL_P1_SHIFT 13 -+#define PORT_PLL_P1_MASK (0x07 << PORT_PLL_P1_SHIFT) -+#define PORT_PLL_P1(x) ((x) << PORT_PLL_P1_SHIFT) -+#define PORT_PLL_P2_SHIFT 8 -+#define PORT_PLL_P2_MASK (0x1f << PORT_PLL_P2_SHIFT) -+#define PORT_PLL_P2(x) ((x) << PORT_PLL_P2_SHIFT) -+#define BXT_PORT_PLL_EBB_0(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_PLL_EBB_0_B, \ -+ _PORT_PLL_EBB_0_C) -+ -+#define _PORT_PLL_EBB_4_A 0x162038 -+#define _PORT_PLL_EBB_4_B 0x6C038 -+#define _PORT_PLL_EBB_4_C 0x6C344 -+#define PORT_PLL_10BIT_CLK_ENABLE (1 << 13) -+#define PORT_PLL_RECALIBRATE (1 << 14) -+#define BXT_PORT_PLL_EBB_4(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_PLL_EBB_4_B, \ -+ _PORT_PLL_EBB_4_C) -+ -+#define _PORT_PLL_0_A 0x162100 -+#define _PORT_PLL_0_B 0x6C100 -+#define _PORT_PLL_0_C 0x6C380 -+/* PORT_PLL_0_A */ -+#define PORT_PLL_M2_MASK 0xFF -+/* PORT_PLL_1_A */ -+#define PORT_PLL_N_SHIFT 8 -+#define PORT_PLL_N_MASK (0x0F << PORT_PLL_N_SHIFT) -+#define PORT_PLL_N(x) ((x) << PORT_PLL_N_SHIFT) -+/* PORT_PLL_2_A */ -+#define PORT_PLL_M2_FRAC_MASK 0x3FFFFF -+/* PORT_PLL_3_A */ -+#define PORT_PLL_M2_FRAC_ENABLE (1 << 16) -+/* PORT_PLL_6_A */ -+#define PORT_PLL_PROP_COEFF_MASK 0xF -+#define PORT_PLL_INT_COEFF_MASK (0x1F << 8) -+#define PORT_PLL_INT_COEFF(x) ((x) << 8) -+#define PORT_PLL_GAIN_CTL_MASK (0x07 << 16) -+#define PORT_PLL_GAIN_CTL(x) ((x) << 16) -+/* PORT_PLL_8_A */ -+#define PORT_PLL_TARGET_CNT_MASK 0x3FF -+/* PORT_PLL_9_A */ -+#define PORT_PLL_LOCK_THRESHOLD_SHIFT 1 -+#define PORT_PLL_LOCK_THRESHOLD_MASK (0x7 << PORT_PLL_LOCK_THRESHOLD_SHIFT) -+/* PORT_PLL_10_A */ -+#define PORT_PLL_DCO_AMP_OVR_EN_H (1 << 27) -+#define PORT_PLL_DCO_AMP_DEFAULT 15 -+#define PORT_PLL_DCO_AMP_MASK 0x3c00 -+#define PORT_PLL_DCO_AMP(x) ((x) << 10) -+#define _PORT_PLL_BASE(phy, ch) _BXT_PHY_CH(phy, ch, \ -+ _PORT_PLL_0_B, \ -+ _PORT_PLL_0_C) -+#define BXT_PORT_PLL(phy, ch, idx) _MMIO(_PORT_PLL_BASE(phy, ch) + \ -+ (idx) * 4) -+ -+/* BXT PHY common lane registers */ -+#define _PORT_CL1CM_DW0_A 0x162000 -+#define _PORT_CL1CM_DW0_BC 0x6C000 -+#define PHY_POWER_GOOD (1 << 16) -+#define PHY_RESERVED (1 << 7) -+#define BXT_PORT_CL1CM_DW0(phy) _BXT_PHY((phy), _PORT_CL1CM_DW0_BC) -+ -+#define _PORT_CL1CM_DW9_A 0x162024 -+#define _PORT_CL1CM_DW9_BC 0x6C024 -+#define IREF0RC_OFFSET_SHIFT 8 -+#define IREF0RC_OFFSET_MASK (0xFF << IREF0RC_OFFSET_SHIFT) -+#define BXT_PORT_CL1CM_DW9(phy) _BXT_PHY((phy), _PORT_CL1CM_DW9_BC) -+ -+#define _PORT_CL1CM_DW10_A 0x162028 -+#define _PORT_CL1CM_DW10_BC 0x6C028 -+#define IREF1RC_OFFSET_SHIFT 8 -+#define IREF1RC_OFFSET_MASK (0xFF << IREF1RC_OFFSET_SHIFT) -+#define BXT_PORT_CL1CM_DW10(phy) _BXT_PHY((phy), _PORT_CL1CM_DW10_BC) -+ -+#define _PORT_CL1CM_DW28_A 0x162070 -+#define _PORT_CL1CM_DW28_BC 0x6C070 -+#define OCL1_POWER_DOWN_EN (1 << 23) -+#define DW28_OLDO_DYN_PWR_DOWN_EN (1 << 22) -+#define SUS_CLK_CONFIG 0x3 -+#define BXT_PORT_CL1CM_DW28(phy) _BXT_PHY((phy), _PORT_CL1CM_DW28_BC) -+ -+#define _PORT_CL1CM_DW30_A 0x162078 -+#define _PORT_CL1CM_DW30_BC 0x6C078 -+#define OCL2_LDOFUSE_PWR_DIS (1 << 6) -+#define BXT_PORT_CL1CM_DW30(phy) _BXT_PHY((phy), _PORT_CL1CM_DW30_BC) -+ -+/* -+ * CNL/ICL Port/COMBO-PHY Registers -+ */ -+#define _ICL_COMBOPHY_A 0x162000 -+#define _ICL_COMBOPHY_B 0x6C000 -+#define _ICL_COMBOPHY(port) _PICK(port, _ICL_COMBOPHY_A, \ -+ _ICL_COMBOPHY_B) -+ -+/* CNL/ICL Port CL_DW registers */ -+#define _ICL_PORT_CL_DW(dw, port) (_ICL_COMBOPHY(port) + \ -+ 4 * (dw)) -+ -+#define CNL_PORT_CL1CM_DW5 _MMIO(0x162014) -+#define ICL_PORT_CL_DW5(port) _MMIO(_ICL_PORT_CL_DW(5, port)) -+#define CL_POWER_DOWN_ENABLE (1 << 4) -+#define SUS_CLOCK_CONFIG (3 << 0) -+ -+#define ICL_PORT_CL_DW10(port) _MMIO(_ICL_PORT_CL_DW(10, port)) -+#define PG_SEQ_DELAY_OVERRIDE_MASK (3 << 25) -+#define PG_SEQ_DELAY_OVERRIDE_SHIFT 25 -+#define PG_SEQ_DELAY_OVERRIDE_ENABLE (1 << 24) -+#define PWR_UP_ALL_LANES (0x0 << 4) -+#define PWR_DOWN_LN_3_2_1 (0xe << 4) -+#define PWR_DOWN_LN_3_2 (0xc << 4) -+#define PWR_DOWN_LN_3 (0x8 << 4) -+#define PWR_DOWN_LN_2_1_0 (0x7 << 4) -+#define PWR_DOWN_LN_1_0 (0x3 << 4) -+#define PWR_DOWN_LN_1 (0x2 << 4) -+#define PWR_DOWN_LN_3_1 (0xa << 4) -+#define PWR_DOWN_LN_3_1_0 (0xb << 4) -+#define PWR_DOWN_LN_MASK (0xf << 4) -+#define PWR_DOWN_LN_SHIFT 4 -+ -+#define ICL_PORT_CL_DW12(port) _MMIO(_ICL_PORT_CL_DW(12, port)) -+#define ICL_LANE_ENABLE_AUX (1 << 0) -+ -+/* CNL/ICL Port COMP_DW registers */ -+#define _ICL_PORT_COMP 0x100 -+#define _ICL_PORT_COMP_DW(dw, port) (_ICL_COMBOPHY(port) + \ -+ _ICL_PORT_COMP + 4 * (dw)) -+ -+#define CNL_PORT_COMP_DW0 _MMIO(0x162100) -+#define ICL_PORT_COMP_DW0(port) _MMIO(_ICL_PORT_COMP_DW(0, port)) -+#define COMP_INIT (1 << 31) -+ -+#define CNL_PORT_COMP_DW1 _MMIO(0x162104) -+#define ICL_PORT_COMP_DW1(port) _MMIO(_ICL_PORT_COMP_DW(1, port)) -+ -+#define CNL_PORT_COMP_DW3 _MMIO(0x16210c) -+#define ICL_PORT_COMP_DW3(port) _MMIO(_ICL_PORT_COMP_DW(3, port)) -+#define PROCESS_INFO_DOT_0 (0 << 26) -+#define PROCESS_INFO_DOT_1 (1 << 26) -+#define PROCESS_INFO_DOT_4 (2 << 26) -+#define PROCESS_INFO_MASK (7 << 26) -+#define PROCESS_INFO_SHIFT 26 -+#define VOLTAGE_INFO_0_85V (0 << 24) -+#define VOLTAGE_INFO_0_95V (1 << 24) -+#define VOLTAGE_INFO_1_05V (2 << 24) -+#define VOLTAGE_INFO_MASK (3 << 24) -+#define VOLTAGE_INFO_SHIFT 24 -+ -+#define CNL_PORT_COMP_DW9 _MMIO(0x162124) -+#define ICL_PORT_COMP_DW9(port) _MMIO(_ICL_PORT_COMP_DW(9, port)) -+ -+#define CNL_PORT_COMP_DW10 _MMIO(0x162128) -+#define ICL_PORT_COMP_DW10(port) _MMIO(_ICL_PORT_COMP_DW(10, port)) -+ -+/* CNL/ICL Port PCS registers */ -+#define _CNL_PORT_PCS_DW1_GRP_AE 0x162304 -+#define _CNL_PORT_PCS_DW1_GRP_B 0x162384 -+#define _CNL_PORT_PCS_DW1_GRP_C 0x162B04 -+#define _CNL_PORT_PCS_DW1_GRP_D 0x162B84 -+#define _CNL_PORT_PCS_DW1_GRP_F 0x162A04 -+#define _CNL_PORT_PCS_DW1_LN0_AE 0x162404 -+#define _CNL_PORT_PCS_DW1_LN0_B 0x162604 -+#define _CNL_PORT_PCS_DW1_LN0_C 0x162C04 -+#define _CNL_PORT_PCS_DW1_LN0_D 0x162E04 -+#define _CNL_PORT_PCS_DW1_LN0_F 0x162804 -+#define CNL_PORT_PCS_DW1_GRP(port) _MMIO(_PICK(port, \ -+ _CNL_PORT_PCS_DW1_GRP_AE, \ -+ _CNL_PORT_PCS_DW1_GRP_B, \ -+ _CNL_PORT_PCS_DW1_GRP_C, \ -+ _CNL_PORT_PCS_DW1_GRP_D, \ -+ _CNL_PORT_PCS_DW1_GRP_AE, \ -+ _CNL_PORT_PCS_DW1_GRP_F)) -+#define CNL_PORT_PCS_DW1_LN0(port) _MMIO(_PICK(port, \ -+ _CNL_PORT_PCS_DW1_LN0_AE, \ -+ _CNL_PORT_PCS_DW1_LN0_B, \ -+ _CNL_PORT_PCS_DW1_LN0_C, \ -+ _CNL_PORT_PCS_DW1_LN0_D, \ -+ _CNL_PORT_PCS_DW1_LN0_AE, \ -+ _CNL_PORT_PCS_DW1_LN0_F)) -+ -+#define _ICL_PORT_PCS_AUX 0x300 -+#define _ICL_PORT_PCS_GRP 0x600 -+#define _ICL_PORT_PCS_LN(ln) (0x800 + (ln) * 0x100) -+#define _ICL_PORT_PCS_DW_AUX(dw, port) (_ICL_COMBOPHY(port) + \ -+ _ICL_PORT_PCS_AUX + 4 * (dw)) -+#define _ICL_PORT_PCS_DW_GRP(dw, port) (_ICL_COMBOPHY(port) + \ -+ _ICL_PORT_PCS_GRP + 4 * (dw)) -+#define _ICL_PORT_PCS_DW_LN(dw, ln, port) (_ICL_COMBOPHY(port) + \ -+ _ICL_PORT_PCS_LN(ln) + 4 * (dw)) -+#define ICL_PORT_PCS_DW1_AUX(port) _MMIO(_ICL_PORT_PCS_DW_AUX(1, port)) -+#define ICL_PORT_PCS_DW1_GRP(port) _MMIO(_ICL_PORT_PCS_DW_GRP(1, port)) -+#define ICL_PORT_PCS_DW1_LN0(port) _MMIO(_ICL_PORT_PCS_DW_LN(1, 0, port)) -+#define COMMON_KEEPER_EN (1 << 26) -+ -+/* CNL/ICL Port TX registers */ -+#define _CNL_PORT_TX_AE_GRP_OFFSET 0x162340 -+#define _CNL_PORT_TX_B_GRP_OFFSET 0x1623C0 -+#define _CNL_PORT_TX_C_GRP_OFFSET 0x162B40 -+#define _CNL_PORT_TX_D_GRP_OFFSET 0x162BC0 -+#define _CNL_PORT_TX_F_GRP_OFFSET 0x162A40 -+#define _CNL_PORT_TX_AE_LN0_OFFSET 0x162440 -+#define _CNL_PORT_TX_B_LN0_OFFSET 0x162640 -+#define _CNL_PORT_TX_C_LN0_OFFSET 0x162C40 -+#define _CNL_PORT_TX_D_LN0_OFFSET 0x162E40 -+#define _CNL_PORT_TX_F_LN0_OFFSET 0x162840 -+#define _CNL_PORT_TX_DW_GRP(dw, port) (_PICK((port), \ -+ _CNL_PORT_TX_AE_GRP_OFFSET, \ -+ _CNL_PORT_TX_B_GRP_OFFSET, \ -+ _CNL_PORT_TX_B_GRP_OFFSET, \ -+ _CNL_PORT_TX_D_GRP_OFFSET, \ -+ _CNL_PORT_TX_AE_GRP_OFFSET, \ -+ _CNL_PORT_TX_F_GRP_OFFSET) + \ -+ 4 * (dw)) -+#define _CNL_PORT_TX_DW_LN0(dw, port) (_PICK((port), \ -+ _CNL_PORT_TX_AE_LN0_OFFSET, \ -+ _CNL_PORT_TX_B_LN0_OFFSET, \ -+ _CNL_PORT_TX_B_LN0_OFFSET, \ -+ _CNL_PORT_TX_D_LN0_OFFSET, \ -+ _CNL_PORT_TX_AE_LN0_OFFSET, \ -+ _CNL_PORT_TX_F_LN0_OFFSET) + \ -+ 4 * (dw)) -+ -+#define _ICL_PORT_TX_AUX 0x380 -+#define _ICL_PORT_TX_GRP 0x680 -+#define _ICL_PORT_TX_LN(ln) (0x880 + (ln) * 0x100) -+ -+#define _ICL_PORT_TX_DW_AUX(dw, port) (_ICL_COMBOPHY(port) + \ -+ _ICL_PORT_TX_AUX + 4 * (dw)) -+#define _ICL_PORT_TX_DW_GRP(dw, port) (_ICL_COMBOPHY(port) + \ -+ _ICL_PORT_TX_GRP + 4 * (dw)) -+#define _ICL_PORT_TX_DW_LN(dw, ln, port) (_ICL_COMBOPHY(port) + \ -+ _ICL_PORT_TX_LN(ln) + 4 * (dw)) -+ -+#define CNL_PORT_TX_DW2_GRP(port) _MMIO(_CNL_PORT_TX_DW_GRP(2, port)) -+#define CNL_PORT_TX_DW2_LN0(port) _MMIO(_CNL_PORT_TX_DW_LN0(2, port)) -+#define ICL_PORT_TX_DW2_AUX(port) _MMIO(_ICL_PORT_TX_DW_AUX(2, port)) -+#define ICL_PORT_TX_DW2_GRP(port) _MMIO(_ICL_PORT_TX_DW_GRP(2, port)) -+#define ICL_PORT_TX_DW2_LN0(port) _MMIO(_ICL_PORT_TX_DW_LN(2, 0, port)) -+#define SWING_SEL_UPPER(x) (((x) >> 3) << 15) -+#define SWING_SEL_UPPER_MASK (1 << 15) -+#define SWING_SEL_LOWER(x) (((x) & 0x7) << 11) -+#define SWING_SEL_LOWER_MASK (0x7 << 11) -+#define FRC_LATENCY_OPTIM_MASK (0x7 << 8) -+#define FRC_LATENCY_OPTIM_VAL(x) ((x) << 8) -+#define RCOMP_SCALAR(x) ((x) << 0) -+#define RCOMP_SCALAR_MASK (0xFF << 0) -+ -+#define _CNL_PORT_TX_DW4_LN0_AE 0x162450 -+#define _CNL_PORT_TX_DW4_LN1_AE 0x1624D0 -+#define CNL_PORT_TX_DW4_GRP(port) _MMIO(_CNL_PORT_TX_DW_GRP(4, (port))) -+#define CNL_PORT_TX_DW4_LN0(port) _MMIO(_CNL_PORT_TX_DW_LN0(4, (port))) -+#define CNL_PORT_TX_DW4_LN(ln, port) _MMIO(_CNL_PORT_TX_DW_LN0(4, (port)) + \ -+ ((ln) * (_CNL_PORT_TX_DW4_LN1_AE - \ -+ _CNL_PORT_TX_DW4_LN0_AE))) -+#define ICL_PORT_TX_DW4_AUX(port) _MMIO(_ICL_PORT_TX_DW_AUX(4, port)) -+#define ICL_PORT_TX_DW4_GRP(port) _MMIO(_ICL_PORT_TX_DW_GRP(4, port)) -+#define ICL_PORT_TX_DW4_LN0(port) _MMIO(_ICL_PORT_TX_DW_LN(4, 0, port)) -+#define ICL_PORT_TX_DW4_LN(ln, port) _MMIO(_ICL_PORT_TX_DW_LN(4, ln, port)) -+#define LOADGEN_SELECT (1 << 31) -+#define POST_CURSOR_1(x) ((x) << 12) -+#define POST_CURSOR_1_MASK (0x3F << 12) -+#define POST_CURSOR_2(x) ((x) << 6) -+#define POST_CURSOR_2_MASK (0x3F << 6) -+#define CURSOR_COEFF(x) ((x) << 0) -+#define CURSOR_COEFF_MASK (0x3F << 0) -+ -+#define CNL_PORT_TX_DW5_GRP(port) _MMIO(_CNL_PORT_TX_DW_GRP(5, port)) -+#define CNL_PORT_TX_DW5_LN0(port) _MMIO(_CNL_PORT_TX_DW_LN0(5, port)) -+#define ICL_PORT_TX_DW5_AUX(port) _MMIO(_ICL_PORT_TX_DW_AUX(5, port)) -+#define ICL_PORT_TX_DW5_GRP(port) _MMIO(_ICL_PORT_TX_DW_GRP(5, port)) -+#define ICL_PORT_TX_DW5_LN0(port) _MMIO(_ICL_PORT_TX_DW_LN(5, 0, port)) -+#define TX_TRAINING_EN (1 << 31) -+#define TAP2_DISABLE (1 << 30) -+#define TAP3_DISABLE (1 << 29) -+#define SCALING_MODE_SEL(x) ((x) << 18) -+#define SCALING_MODE_SEL_MASK (0x7 << 18) -+#define RTERM_SELECT(x) ((x) << 3) -+#define RTERM_SELECT_MASK (0x7 << 3) -+ -+#define CNL_PORT_TX_DW7_GRP(port) _MMIO(_CNL_PORT_TX_DW_GRP(7, (port))) -+#define CNL_PORT_TX_DW7_LN0(port) _MMIO(_CNL_PORT_TX_DW_LN0(7, (port))) -+#define ICL_PORT_TX_DW7_AUX(port) _MMIO(_ICL_PORT_TX_DW_AUX(7, port)) -+#define ICL_PORT_TX_DW7_GRP(port) _MMIO(_ICL_PORT_TX_DW_GRP(7, port)) -+#define ICL_PORT_TX_DW7_LN0(port) _MMIO(_ICL_PORT_TX_DW_LN(7, 0, port)) -+#define ICL_PORT_TX_DW7_LN(ln, port) _MMIO(_ICL_PORT_TX_DW_LN(7, ln, port)) -+#define N_SCALAR(x) ((x) << 24) -+#define N_SCALAR_MASK (0x7F << 24) -+ -+#define MG_PHY_PORT_LN(ln, port, ln0p1, ln0p2, ln1p1) \ -+ _MMIO(_PORT((port) - PORT_C, ln0p1, ln0p2) + (ln) * ((ln1p1) - (ln0p1))) -+ -+#define MG_TX_LINK_PARAMS_TX1LN0_PORT1 0x16812C -+#define MG_TX_LINK_PARAMS_TX1LN1_PORT1 0x16852C -+#define MG_TX_LINK_PARAMS_TX1LN0_PORT2 0x16912C -+#define MG_TX_LINK_PARAMS_TX1LN1_PORT2 0x16952C -+#define MG_TX_LINK_PARAMS_TX1LN0_PORT3 0x16A12C -+#define MG_TX_LINK_PARAMS_TX1LN1_PORT3 0x16A52C -+#define MG_TX_LINK_PARAMS_TX1LN0_PORT4 0x16B12C -+#define MG_TX_LINK_PARAMS_TX1LN1_PORT4 0x16B52C -+#define MG_TX1_LINK_PARAMS(ln, port) \ -+ MG_PHY_PORT_LN(ln, port, MG_TX_LINK_PARAMS_TX1LN0_PORT1, \ -+ MG_TX_LINK_PARAMS_TX1LN0_PORT2, \ -+ MG_TX_LINK_PARAMS_TX1LN1_PORT1) -+ -+#define MG_TX_LINK_PARAMS_TX2LN0_PORT1 0x1680AC -+#define MG_TX_LINK_PARAMS_TX2LN1_PORT1 0x1684AC -+#define MG_TX_LINK_PARAMS_TX2LN0_PORT2 0x1690AC -+#define MG_TX_LINK_PARAMS_TX2LN1_PORT2 0x1694AC -+#define MG_TX_LINK_PARAMS_TX2LN0_PORT3 0x16A0AC -+#define MG_TX_LINK_PARAMS_TX2LN1_PORT3 0x16A4AC -+#define MG_TX_LINK_PARAMS_TX2LN0_PORT4 0x16B0AC -+#define MG_TX_LINK_PARAMS_TX2LN1_PORT4 0x16B4AC -+#define MG_TX2_LINK_PARAMS(ln, port) \ -+ MG_PHY_PORT_LN(ln, port, MG_TX_LINK_PARAMS_TX2LN0_PORT1, \ -+ MG_TX_LINK_PARAMS_TX2LN0_PORT2, \ -+ MG_TX_LINK_PARAMS_TX2LN1_PORT1) -+#define CRI_USE_FS32 (1 << 5) -+ -+#define MG_TX_PISO_READLOAD_TX1LN0_PORT1 0x16814C -+#define MG_TX_PISO_READLOAD_TX1LN1_PORT1 0x16854C -+#define MG_TX_PISO_READLOAD_TX1LN0_PORT2 0x16914C -+#define MG_TX_PISO_READLOAD_TX1LN1_PORT2 0x16954C -+#define MG_TX_PISO_READLOAD_TX1LN0_PORT3 0x16A14C -+#define MG_TX_PISO_READLOAD_TX1LN1_PORT3 0x16A54C -+#define MG_TX_PISO_READLOAD_TX1LN0_PORT4 0x16B14C -+#define MG_TX_PISO_READLOAD_TX1LN1_PORT4 0x16B54C -+#define MG_TX1_PISO_READLOAD(ln, port) \ -+ MG_PHY_PORT_LN(ln, port, MG_TX_PISO_READLOAD_TX1LN0_PORT1, \ -+ MG_TX_PISO_READLOAD_TX1LN0_PORT2, \ -+ MG_TX_PISO_READLOAD_TX1LN1_PORT1) -+ -+#define MG_TX_PISO_READLOAD_TX2LN0_PORT1 0x1680CC -+#define MG_TX_PISO_READLOAD_TX2LN1_PORT1 0x1684CC -+#define MG_TX_PISO_READLOAD_TX2LN0_PORT2 0x1690CC -+#define MG_TX_PISO_READLOAD_TX2LN1_PORT2 0x1694CC -+#define MG_TX_PISO_READLOAD_TX2LN0_PORT3 0x16A0CC -+#define MG_TX_PISO_READLOAD_TX2LN1_PORT3 0x16A4CC -+#define MG_TX_PISO_READLOAD_TX2LN0_PORT4 0x16B0CC -+#define MG_TX_PISO_READLOAD_TX2LN1_PORT4 0x16B4CC -+#define MG_TX2_PISO_READLOAD(ln, port) \ -+ MG_PHY_PORT_LN(ln, port, MG_TX_PISO_READLOAD_TX2LN0_PORT1, \ -+ MG_TX_PISO_READLOAD_TX2LN0_PORT2, \ -+ MG_TX_PISO_READLOAD_TX2LN1_PORT1) -+#define CRI_CALCINIT (1 << 1) -+ -+#define MG_TX_SWINGCTRL_TX1LN0_PORT1 0x168148 -+#define MG_TX_SWINGCTRL_TX1LN1_PORT1 0x168548 -+#define MG_TX_SWINGCTRL_TX1LN0_PORT2 0x169148 -+#define MG_TX_SWINGCTRL_TX1LN1_PORT2 0x169548 -+#define MG_TX_SWINGCTRL_TX1LN0_PORT3 0x16A148 -+#define MG_TX_SWINGCTRL_TX1LN1_PORT3 0x16A548 -+#define MG_TX_SWINGCTRL_TX1LN0_PORT4 0x16B148 -+#define MG_TX_SWINGCTRL_TX1LN1_PORT4 0x16B548 -+#define MG_TX1_SWINGCTRL(ln, port) \ -+ MG_PHY_PORT_LN(ln, port, MG_TX_SWINGCTRL_TX1LN0_PORT1, \ -+ MG_TX_SWINGCTRL_TX1LN0_PORT2, \ -+ MG_TX_SWINGCTRL_TX1LN1_PORT1) -+ -+#define MG_TX_SWINGCTRL_TX2LN0_PORT1 0x1680C8 -+#define MG_TX_SWINGCTRL_TX2LN1_PORT1 0x1684C8 -+#define MG_TX_SWINGCTRL_TX2LN0_PORT2 0x1690C8 -+#define MG_TX_SWINGCTRL_TX2LN1_PORT2 0x1694C8 -+#define MG_TX_SWINGCTRL_TX2LN0_PORT3 0x16A0C8 -+#define MG_TX_SWINGCTRL_TX2LN1_PORT3 0x16A4C8 -+#define MG_TX_SWINGCTRL_TX2LN0_PORT4 0x16B0C8 -+#define MG_TX_SWINGCTRL_TX2LN1_PORT4 0x16B4C8 -+#define MG_TX2_SWINGCTRL(ln, port) \ -+ MG_PHY_PORT_LN(ln, port, MG_TX_SWINGCTRL_TX2LN0_PORT1, \ -+ MG_TX_SWINGCTRL_TX2LN0_PORT2, \ -+ MG_TX_SWINGCTRL_TX2LN1_PORT1) -+#define CRI_TXDEEMPH_OVERRIDE_17_12(x) ((x) << 0) -+#define CRI_TXDEEMPH_OVERRIDE_17_12_MASK (0x3F << 0) -+ -+#define MG_TX_DRVCTRL_TX1LN0_TXPORT1 0x168144 -+#define MG_TX_DRVCTRL_TX1LN1_TXPORT1 0x168544 -+#define MG_TX_DRVCTRL_TX1LN0_TXPORT2 0x169144 -+#define MG_TX_DRVCTRL_TX1LN1_TXPORT2 0x169544 -+#define MG_TX_DRVCTRL_TX1LN0_TXPORT3 0x16A144 -+#define MG_TX_DRVCTRL_TX1LN1_TXPORT3 0x16A544 -+#define MG_TX_DRVCTRL_TX1LN0_TXPORT4 0x16B144 -+#define MG_TX_DRVCTRL_TX1LN1_TXPORT4 0x16B544 -+#define MG_TX1_DRVCTRL(ln, port) \ -+ MG_PHY_PORT_LN(ln, port, MG_TX_DRVCTRL_TX1LN0_TXPORT1, \ -+ MG_TX_DRVCTRL_TX1LN0_TXPORT2, \ -+ MG_TX_DRVCTRL_TX1LN1_TXPORT1) -+ -+#define MG_TX_DRVCTRL_TX2LN0_PORT1 0x1680C4 -+#define MG_TX_DRVCTRL_TX2LN1_PORT1 0x1684C4 -+#define MG_TX_DRVCTRL_TX2LN0_PORT2 0x1690C4 -+#define MG_TX_DRVCTRL_TX2LN1_PORT2 0x1694C4 -+#define MG_TX_DRVCTRL_TX2LN0_PORT3 0x16A0C4 -+#define MG_TX_DRVCTRL_TX2LN1_PORT3 0x16A4C4 -+#define MG_TX_DRVCTRL_TX2LN0_PORT4 0x16B0C4 -+#define MG_TX_DRVCTRL_TX2LN1_PORT4 0x16B4C4 -+#define MG_TX2_DRVCTRL(ln, port) \ -+ MG_PHY_PORT_LN(ln, port, MG_TX_DRVCTRL_TX2LN0_PORT1, \ -+ MG_TX_DRVCTRL_TX2LN0_PORT2, \ -+ MG_TX_DRVCTRL_TX2LN1_PORT1) -+#define CRI_TXDEEMPH_OVERRIDE_11_6(x) ((x) << 24) -+#define CRI_TXDEEMPH_OVERRIDE_11_6_MASK (0x3F << 24) -+#define CRI_TXDEEMPH_OVERRIDE_EN (1 << 22) -+#define CRI_TXDEEMPH_OVERRIDE_5_0(x) ((x) << 16) -+#define CRI_TXDEEMPH_OVERRIDE_5_0_MASK (0x3F << 16) -+#define CRI_LOADGEN_SEL(x) ((x) << 12) -+#define CRI_LOADGEN_SEL_MASK (0x3 << 12) -+ -+#define MG_CLKHUB_LN0_PORT1 0x16839C -+#define MG_CLKHUB_LN1_PORT1 0x16879C -+#define MG_CLKHUB_LN0_PORT2 0x16939C -+#define MG_CLKHUB_LN1_PORT2 0x16979C -+#define MG_CLKHUB_LN0_PORT3 0x16A39C -+#define MG_CLKHUB_LN1_PORT3 0x16A79C -+#define MG_CLKHUB_LN0_PORT4 0x16B39C -+#define MG_CLKHUB_LN1_PORT4 0x16B79C -+#define MG_CLKHUB(ln, port) \ -+ MG_PHY_PORT_LN(ln, port, MG_CLKHUB_LN0_PORT1, \ -+ MG_CLKHUB_LN0_PORT2, \ -+ MG_CLKHUB_LN1_PORT1) -+#define CFG_LOW_RATE_LKREN_EN (1 << 11) -+ -+#define MG_TX_DCC_TX1LN0_PORT1 0x168110 -+#define MG_TX_DCC_TX1LN1_PORT1 0x168510 -+#define MG_TX_DCC_TX1LN0_PORT2 0x169110 -+#define MG_TX_DCC_TX1LN1_PORT2 0x169510 -+#define MG_TX_DCC_TX1LN0_PORT3 0x16A110 -+#define MG_TX_DCC_TX1LN1_PORT3 0x16A510 -+#define MG_TX_DCC_TX1LN0_PORT4 0x16B110 -+#define MG_TX_DCC_TX1LN1_PORT4 0x16B510 -+#define MG_TX1_DCC(ln, port) \ -+ MG_PHY_PORT_LN(ln, port, MG_TX_DCC_TX1LN0_PORT1, \ -+ MG_TX_DCC_TX1LN0_PORT2, \ -+ MG_TX_DCC_TX1LN1_PORT1) -+#define MG_TX_DCC_TX2LN0_PORT1 0x168090 -+#define MG_TX_DCC_TX2LN1_PORT1 0x168490 -+#define MG_TX_DCC_TX2LN0_PORT2 0x169090 -+#define MG_TX_DCC_TX2LN1_PORT2 0x169490 -+#define MG_TX_DCC_TX2LN0_PORT3 0x16A090 -+#define MG_TX_DCC_TX2LN1_PORT3 0x16A490 -+#define MG_TX_DCC_TX2LN0_PORT4 0x16B090 -+#define MG_TX_DCC_TX2LN1_PORT4 0x16B490 -+#define MG_TX2_DCC(ln, port) \ -+ MG_PHY_PORT_LN(ln, port, MG_TX_DCC_TX2LN0_PORT1, \ -+ MG_TX_DCC_TX2LN0_PORT2, \ -+ MG_TX_DCC_TX2LN1_PORT1) -+#define CFG_AMI_CK_DIV_OVERRIDE_VAL(x) ((x) << 25) -+#define CFG_AMI_CK_DIV_OVERRIDE_VAL_MASK (0x3 << 25) -+#define CFG_AMI_CK_DIV_OVERRIDE_EN (1 << 24) -+ -+#define MG_DP_MODE_LN0_ACU_PORT1 0x1683A0 -+#define MG_DP_MODE_LN1_ACU_PORT1 0x1687A0 -+#define MG_DP_MODE_LN0_ACU_PORT2 0x1693A0 -+#define MG_DP_MODE_LN1_ACU_PORT2 0x1697A0 -+#define MG_DP_MODE_LN0_ACU_PORT3 0x16A3A0 -+#define MG_DP_MODE_LN1_ACU_PORT3 0x16A7A0 -+#define MG_DP_MODE_LN0_ACU_PORT4 0x16B3A0 -+#define MG_DP_MODE_LN1_ACU_PORT4 0x16B7A0 -+#define MG_DP_MODE(ln, port) \ -+ MG_PHY_PORT_LN(ln, port, MG_DP_MODE_LN0_ACU_PORT1, \ -+ MG_DP_MODE_LN0_ACU_PORT2, \ -+ MG_DP_MODE_LN1_ACU_PORT1) -+#define MG_DP_MODE_CFG_DP_X2_MODE (1 << 7) -+#define MG_DP_MODE_CFG_DP_X1_MODE (1 << 6) -+#define MG_DP_MODE_CFG_TR2PWR_GATING (1 << 5) -+#define MG_DP_MODE_CFG_TRPWR_GATING (1 << 4) -+#define MG_DP_MODE_CFG_CLNPWR_GATING (1 << 3) -+#define MG_DP_MODE_CFG_DIGPWR_GATING (1 << 2) -+#define MG_DP_MODE_CFG_GAONPWR_GATING (1 << 1) -+ -+#define MG_MISC_SUS0_PORT1 0x168814 -+#define MG_MISC_SUS0_PORT2 0x169814 -+#define MG_MISC_SUS0_PORT3 0x16A814 -+#define MG_MISC_SUS0_PORT4 0x16B814 -+#define MG_MISC_SUS0(tc_port) \ -+ _MMIO(_PORT(tc_port, MG_MISC_SUS0_PORT1, MG_MISC_SUS0_PORT2)) -+#define MG_MISC_SUS0_SUSCLK_DYNCLKGATE_MODE_MASK (3 << 14) -+#define MG_MISC_SUS0_SUSCLK_DYNCLKGATE_MODE(x) ((x) << 14) -+#define MG_MISC_SUS0_CFG_TR2PWR_GATING (1 << 12) -+#define MG_MISC_SUS0_CFG_CL2PWR_GATING (1 << 11) -+#define MG_MISC_SUS0_CFG_GAONPWR_GATING (1 << 10) -+#define MG_MISC_SUS0_CFG_TRPWR_GATING (1 << 7) -+#define MG_MISC_SUS0_CFG_CL1PWR_GATING (1 << 6) -+#define MG_MISC_SUS0_CFG_DGPWR_GATING (1 << 5) -+ -+/* The spec defines this only for BXT PHY0, but lets assume that this -+ * would exist for PHY1 too if it had a second channel. -+ */ -+#define _PORT_CL2CM_DW6_A 0x162358 -+#define _PORT_CL2CM_DW6_BC 0x6C358 -+#define BXT_PORT_CL2CM_DW6(phy) _BXT_PHY((phy), _PORT_CL2CM_DW6_BC) -+#define DW6_OLDO_DYN_PWR_DOWN_EN (1 << 28) -+ -+#define FIA1_BASE 0x163000 -+ -+/* ICL PHY DFLEX registers */ -+#define PORT_TX_DFLEXDPMLE1 _MMIO(FIA1_BASE + 0x008C0) -+#define DFLEXDPMLE1_DPMLETC_MASK(tc_port) (0xf << (4 * (tc_port))) -+#define DFLEXDPMLE1_DPMLETC_ML0(tc_port) (1 << (4 * (tc_port))) -+#define DFLEXDPMLE1_DPMLETC_ML1_0(tc_port) (3 << (4 * (tc_port))) -+#define DFLEXDPMLE1_DPMLETC_ML3(tc_port) (8 << (4 * (tc_port))) -+#define DFLEXDPMLE1_DPMLETC_ML3_2(tc_port) (12 << (4 * (tc_port))) -+#define DFLEXDPMLE1_DPMLETC_ML3_0(tc_port) (15 << (4 * (tc_port))) -+ -+/* BXT PHY Ref registers */ -+#define _PORT_REF_DW3_A 0x16218C -+#define _PORT_REF_DW3_BC 0x6C18C -+#define GRC_DONE (1 << 22) -+#define BXT_PORT_REF_DW3(phy) _BXT_PHY((phy), _PORT_REF_DW3_BC) -+ -+#define _PORT_REF_DW6_A 0x162198 -+#define _PORT_REF_DW6_BC 0x6C198 -+#define GRC_CODE_SHIFT 24 -+#define GRC_CODE_MASK (0xFF << GRC_CODE_SHIFT) -+#define GRC_CODE_FAST_SHIFT 16 -+#define GRC_CODE_FAST_MASK (0xFF << GRC_CODE_FAST_SHIFT) -+#define GRC_CODE_SLOW_SHIFT 8 -+#define GRC_CODE_SLOW_MASK (0xFF << GRC_CODE_SLOW_SHIFT) -+#define GRC_CODE_NOM_MASK 0xFF -+#define BXT_PORT_REF_DW6(phy) _BXT_PHY((phy), _PORT_REF_DW6_BC) -+ -+#define _PORT_REF_DW8_A 0x1621A0 -+#define _PORT_REF_DW8_BC 0x6C1A0 -+#define GRC_DIS (1 << 15) -+#define GRC_RDY_OVRD (1 << 1) -+#define BXT_PORT_REF_DW8(phy) _BXT_PHY((phy), _PORT_REF_DW8_BC) -+ -+/* BXT PHY PCS registers */ -+#define _PORT_PCS_DW10_LN01_A 0x162428 -+#define _PORT_PCS_DW10_LN01_B 0x6C428 -+#define _PORT_PCS_DW10_LN01_C 0x6C828 -+#define _PORT_PCS_DW10_GRP_A 0x162C28 -+#define _PORT_PCS_DW10_GRP_B 0x6CC28 -+#define _PORT_PCS_DW10_GRP_C 0x6CE28 -+#define BXT_PORT_PCS_DW10_LN01(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_PCS_DW10_LN01_B, \ -+ _PORT_PCS_DW10_LN01_C) -+#define BXT_PORT_PCS_DW10_GRP(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_PCS_DW10_GRP_B, \ -+ _PORT_PCS_DW10_GRP_C) -+ -+#define TX2_SWING_CALC_INIT (1 << 31) -+#define TX1_SWING_CALC_INIT (1 << 30) -+ -+#define _PORT_PCS_DW12_LN01_A 0x162430 -+#define _PORT_PCS_DW12_LN01_B 0x6C430 -+#define _PORT_PCS_DW12_LN01_C 0x6C830 -+#define _PORT_PCS_DW12_LN23_A 0x162630 -+#define _PORT_PCS_DW12_LN23_B 0x6C630 -+#define _PORT_PCS_DW12_LN23_C 0x6CA30 -+#define _PORT_PCS_DW12_GRP_A 0x162c30 -+#define _PORT_PCS_DW12_GRP_B 0x6CC30 -+#define _PORT_PCS_DW12_GRP_C 0x6CE30 -+#define LANESTAGGER_STRAP_OVRD (1 << 6) -+#define LANE_STAGGER_MASK 0x1F -+#define BXT_PORT_PCS_DW12_LN01(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_PCS_DW12_LN01_B, \ -+ _PORT_PCS_DW12_LN01_C) -+#define BXT_PORT_PCS_DW12_LN23(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_PCS_DW12_LN23_B, \ -+ _PORT_PCS_DW12_LN23_C) -+#define BXT_PORT_PCS_DW12_GRP(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_PCS_DW12_GRP_B, \ -+ _PORT_PCS_DW12_GRP_C) -+ -+/* BXT PHY TX registers */ -+#define _BXT_LANE_OFFSET(lane) (((lane) >> 1) * 0x200 + \ -+ ((lane) & 1) * 0x80) -+ -+#define _PORT_TX_DW2_LN0_A 0x162508 -+#define _PORT_TX_DW2_LN0_B 0x6C508 -+#define _PORT_TX_DW2_LN0_C 0x6C908 -+#define _PORT_TX_DW2_GRP_A 0x162D08 -+#define _PORT_TX_DW2_GRP_B 0x6CD08 -+#define _PORT_TX_DW2_GRP_C 0x6CF08 -+#define BXT_PORT_TX_DW2_LN0(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_TX_DW2_LN0_B, \ -+ _PORT_TX_DW2_LN0_C) -+#define BXT_PORT_TX_DW2_GRP(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_TX_DW2_GRP_B, \ -+ _PORT_TX_DW2_GRP_C) -+#define MARGIN_000_SHIFT 16 -+#define MARGIN_000 (0xFF << MARGIN_000_SHIFT) -+#define UNIQ_TRANS_SCALE_SHIFT 8 -+#define UNIQ_TRANS_SCALE (0xFF << UNIQ_TRANS_SCALE_SHIFT) -+ -+#define _PORT_TX_DW3_LN0_A 0x16250C -+#define _PORT_TX_DW3_LN0_B 0x6C50C -+#define _PORT_TX_DW3_LN0_C 0x6C90C -+#define _PORT_TX_DW3_GRP_A 0x162D0C -+#define _PORT_TX_DW3_GRP_B 0x6CD0C -+#define _PORT_TX_DW3_GRP_C 0x6CF0C -+#define BXT_PORT_TX_DW3_LN0(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_TX_DW3_LN0_B, \ -+ _PORT_TX_DW3_LN0_C) -+#define BXT_PORT_TX_DW3_GRP(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_TX_DW3_GRP_B, \ -+ _PORT_TX_DW3_GRP_C) -+#define SCALE_DCOMP_METHOD (1 << 26) -+#define UNIQUE_TRANGE_EN_METHOD (1 << 27) -+ -+#define _PORT_TX_DW4_LN0_A 0x162510 -+#define _PORT_TX_DW4_LN0_B 0x6C510 -+#define _PORT_TX_DW4_LN0_C 0x6C910 -+#define _PORT_TX_DW4_GRP_A 0x162D10 -+#define _PORT_TX_DW4_GRP_B 0x6CD10 -+#define _PORT_TX_DW4_GRP_C 0x6CF10 -+#define BXT_PORT_TX_DW4_LN0(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_TX_DW4_LN0_B, \ -+ _PORT_TX_DW4_LN0_C) -+#define BXT_PORT_TX_DW4_GRP(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_TX_DW4_GRP_B, \ -+ _PORT_TX_DW4_GRP_C) -+#define DEEMPH_SHIFT 24 -+#define DE_EMPHASIS (0xFF << DEEMPH_SHIFT) -+ -+#define _PORT_TX_DW5_LN0_A 0x162514 -+#define _PORT_TX_DW5_LN0_B 0x6C514 -+#define _PORT_TX_DW5_LN0_C 0x6C914 -+#define _PORT_TX_DW5_GRP_A 0x162D14 -+#define _PORT_TX_DW5_GRP_B 0x6CD14 -+#define _PORT_TX_DW5_GRP_C 0x6CF14 -+#define BXT_PORT_TX_DW5_LN0(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_TX_DW5_LN0_B, \ -+ _PORT_TX_DW5_LN0_C) -+#define BXT_PORT_TX_DW5_GRP(phy, ch) _MMIO_BXT_PHY_CH(phy, ch, \ -+ _PORT_TX_DW5_GRP_B, \ -+ _PORT_TX_DW5_GRP_C) -+#define DCC_DELAY_RANGE_1 (1 << 9) -+#define DCC_DELAY_RANGE_2 (1 << 8) -+ -+#define _PORT_TX_DW14_LN0_A 0x162538 -+#define _PORT_TX_DW14_LN0_B 0x6C538 -+#define _PORT_TX_DW14_LN0_C 0x6C938 -+#define LATENCY_OPTIM_SHIFT 30 -+#define LATENCY_OPTIM (1 << LATENCY_OPTIM_SHIFT) -+#define BXT_PORT_TX_DW14_LN(phy, ch, lane) \ -+ _MMIO(_BXT_PHY_CH(phy, ch, _PORT_TX_DW14_LN0_B, \ -+ _PORT_TX_DW14_LN0_C) + \ -+ _BXT_LANE_OFFSET(lane)) -+ -+/* UAIMI scratch pad register 1 */ -+#define UAIMI_SPR1 _MMIO(0x4F074) -+/* SKL VccIO mask */ -+#define SKL_VCCIO_MASK 0x1 -+/* SKL balance leg register */ -+#define DISPIO_CR_TX_BMU_CR0 _MMIO(0x6C00C) -+/* I_boost values */ -+#define BALANCE_LEG_SHIFT(port) (8 + 3 * (port)) -+#define BALANCE_LEG_MASK(port) (7 << (8 + 3 * (port))) -+/* Balance leg disable bits */ -+#define BALANCE_LEG_DISABLE_SHIFT 23 -+#define BALANCE_LEG_DISABLE(port) (1 << (23 + (port))) -+ -+/* -+ * Fence registers -+ * [0-7] @ 0x2000 gen2,gen3 -+ * [8-15] @ 0x3000 945,g33,pnv -+ * -+ * [0-15] @ 0x3000 gen4,gen5 -+ * -+ * [0-15] @ 0x100000 gen6,vlv,chv -+ * [0-31] @ 0x100000 gen7+ -+ */ -+#define FENCE_REG(i) _MMIO(0x2000 + (((i) & 8) << 9) + ((i) & 7) * 4) -+#define I830_FENCE_START_MASK 0x07f80000 -+#define I830_FENCE_TILING_Y_SHIFT 12 -+#define I830_FENCE_SIZE_BITS(size) ((ffs((size) >> 19) - 1) << 8) -+#define I830_FENCE_PITCH_SHIFT 4 -+#define I830_FENCE_REG_VALID (1 << 0) -+#define I915_FENCE_MAX_PITCH_VAL 4 -+#define I830_FENCE_MAX_PITCH_VAL 6 -+#define I830_FENCE_MAX_SIZE_VAL (1 << 8) -+ -+#define I915_FENCE_START_MASK 0x0ff00000 -+#define I915_FENCE_SIZE_BITS(size) ((ffs((size) >> 20) - 1) << 8) -+ -+#define FENCE_REG_965_LO(i) _MMIO(0x03000 + (i) * 8) -+#define FENCE_REG_965_HI(i) _MMIO(0x03000 + (i) * 8 + 4) -+#define I965_FENCE_PITCH_SHIFT 2 -+#define I965_FENCE_TILING_Y_SHIFT 1 -+#define I965_FENCE_REG_VALID (1 << 0) -+#define I965_FENCE_MAX_PITCH_VAL 0x0400 -+ -+#define FENCE_REG_GEN6_LO(i) _MMIO(0x100000 + (i) * 8) -+#define FENCE_REG_GEN6_HI(i) _MMIO(0x100000 + (i) * 8 + 4) -+#define GEN6_FENCE_PITCH_SHIFT 32 -+#define GEN7_FENCE_MAX_PITCH_VAL 0x0800 -+ -+ -+/* control register for cpu gtt access */ -+#define TILECTL _MMIO(0x101000) -+#define TILECTL_SWZCTL (1 << 0) -+#define TILECTL_TLBPF (1 << 1) -+#define TILECTL_TLB_PREFETCH_DIS (1 << 2) -+#define TILECTL_BACKSNOOP_DIS (1 << 3) -+ -+/* -+ * Instruction and interrupt control regs -+ */ -+#define PGTBL_CTL _MMIO(0x02020) -+#define PGTBL_ADDRESS_LO_MASK 0xfffff000 /* bits [31:12] */ -+#define PGTBL_ADDRESS_HI_MASK 0x000000f0 /* bits [35:32] (gen4) */ -+#define PGTBL_ER _MMIO(0x02024) -+#define PRB0_BASE (0x2030 - 0x30) -+#define PRB1_BASE (0x2040 - 0x30) /* 830,gen3 */ -+#define PRB2_BASE (0x2050 - 0x30) /* gen3 */ -+#define SRB0_BASE (0x2100 - 0x30) /* gen2 */ -+#define SRB1_BASE (0x2110 - 0x30) /* gen2 */ -+#define SRB2_BASE (0x2120 - 0x30) /* 830 */ -+#define SRB3_BASE (0x2130 - 0x30) /* 830 */ -+#define RENDER_RING_BASE 0x02000 -+#define BSD_RING_BASE 0x04000 -+#define GEN6_BSD_RING_BASE 0x12000 -+#define GEN8_BSD2_RING_BASE 0x1c000 -+#define GEN11_BSD_RING_BASE 0x1c0000 -+#define GEN11_BSD2_RING_BASE 0x1c4000 -+#define GEN11_BSD3_RING_BASE 0x1d0000 -+#define GEN11_BSD4_RING_BASE 0x1d4000 -+#define VEBOX_RING_BASE 0x1a000 -+#define GEN11_VEBOX_RING_BASE 0x1c8000 -+#define GEN11_VEBOX2_RING_BASE 0x1d8000 -+#define BLT_RING_BASE 0x22000 -+#define RING_TAIL(base) _MMIO((base) + 0x30) -+#define RING_HEAD(base) _MMIO((base) + 0x34) -+#define RING_START(base) _MMIO((base) + 0x38) -+#define RING_CTL(base) _MMIO((base) + 0x3c) -+#define RING_CTL_SIZE(size) ((size) - PAGE_SIZE) /* in bytes -> pages */ -+#define RING_SYNC_0(base) _MMIO((base) + 0x40) -+#define RING_SYNC_1(base) _MMIO((base) + 0x44) -+#define RING_SYNC_2(base) _MMIO((base) + 0x48) -+#define GEN6_RVSYNC (RING_SYNC_0(RENDER_RING_BASE)) -+#define GEN6_RBSYNC (RING_SYNC_1(RENDER_RING_BASE)) -+#define GEN6_RVESYNC (RING_SYNC_2(RENDER_RING_BASE)) -+#define GEN6_VBSYNC (RING_SYNC_0(GEN6_BSD_RING_BASE)) -+#define GEN6_VRSYNC (RING_SYNC_1(GEN6_BSD_RING_BASE)) -+#define GEN6_VVESYNC (RING_SYNC_2(GEN6_BSD_RING_BASE)) -+#define GEN6_BRSYNC (RING_SYNC_0(BLT_RING_BASE)) -+#define GEN6_BVSYNC (RING_SYNC_1(BLT_RING_BASE)) -+#define GEN6_BVESYNC (RING_SYNC_2(BLT_RING_BASE)) -+#define GEN6_VEBSYNC (RING_SYNC_0(VEBOX_RING_BASE)) -+#define GEN6_VERSYNC (RING_SYNC_1(VEBOX_RING_BASE)) -+#define GEN6_VEVSYNC (RING_SYNC_2(VEBOX_RING_BASE)) -+#define GEN6_NOSYNC INVALID_MMIO_REG -+#define RING_PSMI_CTL(base) _MMIO((base) + 0x50) -+#define RING_MAX_IDLE(base) _MMIO((base) + 0x54) -+#define RING_HWS_PGA(base) _MMIO((base) + 0x80) -+#define RING_HWS_PGA_GEN6(base) _MMIO((base) + 0x2080) -+#define RING_RESET_CTL(base) _MMIO((base) + 0xd0) -+#define RESET_CTL_CAT_ERROR REG_BIT(2) -+#define RESET_CTL_READY_TO_RESET REG_BIT(1) -+#define RESET_CTL_REQUEST_RESET REG_BIT(0) -+ -+#define RING_SEMA_WAIT_POLL(base) _MMIO((base) + 0x24c) -+ -+#define HSW_GTT_CACHE_EN _MMIO(0x4024) -+#define GTT_CACHE_EN_ALL 0xF0007FFF -+#define GEN7_WR_WATERMARK _MMIO(0x4028) -+#define GEN7_GFX_PRIO_CTRL _MMIO(0x402C) -+#define ARB_MODE _MMIO(0x4030) -+#define ARB_MODE_SWIZZLE_SNB (1 << 4) -+#define ARB_MODE_SWIZZLE_IVB (1 << 5) -+#define GEN7_GFX_PEND_TLB0 _MMIO(0x4034) -+#define GEN7_GFX_PEND_TLB1 _MMIO(0x4038) -+/* L3, CVS, ZTLB, RCC, CASC LRA min, max values */ -+#define GEN7_LRA_LIMITS(i) _MMIO(0x403C + (i) * 4) -+#define GEN7_LRA_LIMITS_REG_NUM 13 -+#define GEN7_MEDIA_MAX_REQ_COUNT _MMIO(0x4070) -+#define GEN7_GFX_MAX_REQ_COUNT _MMIO(0x4074) -+ -+#define GAMTARBMODE _MMIO(0x04a08) -+#define ARB_MODE_BWGTLB_DISABLE (1 << 9) -+#define ARB_MODE_SWIZZLE_BDW (1 << 1) -+#define RENDER_HWS_PGA_GEN7 _MMIO(0x04080) -+#define RING_FAULT_REG(engine) _MMIO(0x4094 + 0x100 * (engine)->hw_id) -+#define GEN8_RING_FAULT_REG _MMIO(0x4094) -+#define GEN8_RING_FAULT_ENGINE_ID(x) (((x) >> 12) & 0x7) -+#define RING_FAULT_GTTSEL_MASK (1 << 11) -+#define RING_FAULT_SRCID(x) (((x) >> 3) & 0xff) -+#define RING_FAULT_FAULT_TYPE(x) (((x) >> 1) & 0x3) -+#define RING_FAULT_VALID (1 << 0) -+#define DONE_REG _MMIO(0x40b0) -+#define GEN8_PRIVATE_PAT_LO _MMIO(0x40e0) -+#define GEN8_PRIVATE_PAT_HI _MMIO(0x40e0 + 4) -+#define GEN10_PAT_INDEX(index) _MMIO(0x40e0 + (index) * 4) -+#define BSD_HWS_PGA_GEN7 _MMIO(0x04180) -+#define BLT_HWS_PGA_GEN7 _MMIO(0x04280) -+#define VEBOX_HWS_PGA_GEN7 _MMIO(0x04380) -+#define RING_ACTHD(base) _MMIO((base) + 0x74) -+#define RING_ACTHD_UDW(base) _MMIO((base) + 0x5c) -+#define RING_NOPID(base) _MMIO((base) + 0x94) -+#define RING_IMR(base) _MMIO((base) + 0xa8) -+#define RING_HWSTAM(base) _MMIO((base) + 0x98) -+#define RING_TIMESTAMP(base) _MMIO((base) + 0x358) -+#define RING_TIMESTAMP_UDW(base) _MMIO((base) + 0x358 + 4) -+#define TAIL_ADDR 0x001FFFF8 -+#define HEAD_WRAP_COUNT 0xFFE00000 -+#define HEAD_WRAP_ONE 0x00200000 -+#define HEAD_ADDR 0x001FFFFC -+#define RING_NR_PAGES 0x001FF000 -+#define RING_REPORT_MASK 0x00000006 -+#define RING_REPORT_64K 0x00000002 -+#define RING_REPORT_128K 0x00000004 -+#define RING_NO_REPORT 0x00000000 -+#define RING_VALID_MASK 0x00000001 -+#define RING_VALID 0x00000001 -+#define RING_INVALID 0x00000000 -+#define RING_WAIT_I8XX (1 << 0) /* gen2, PRBx_HEAD */ -+#define RING_WAIT (1 << 11) /* gen3+, PRBx_CTL */ -+#define RING_WAIT_SEMAPHORE (1 << 10) /* gen6+ */ -+ -+#define RING_FORCE_TO_NONPRIV(base, i) _MMIO(((base) + 0x4D0) + (i) * 4) -+#define RING_FORCE_TO_NONPRIV_RW (0 << 28) /* CFL+ & Gen11+ */ -+#define RING_FORCE_TO_NONPRIV_RD (1 << 28) -+#define RING_FORCE_TO_NONPRIV_WR (2 << 28) -+#define RING_FORCE_TO_NONPRIV_RANGE_1 (0 << 0) /* CFL+ & Gen11+ */ -+#define RING_FORCE_TO_NONPRIV_RANGE_4 (1 << 0) -+#define RING_FORCE_TO_NONPRIV_RANGE_16 (2 << 0) -+#define RING_FORCE_TO_NONPRIV_RANGE_64 (3 << 0) -+#define RING_MAX_NONPRIV_SLOTS 12 -+ -+#define GEN7_TLB_RD_ADDR _MMIO(0x4700) -+ -+#define GEN9_GAMT_ECO_REG_RW_IA _MMIO(0x4ab0) -+#define GAMT_ECO_ENABLE_IN_PLACE_DECOMPRESS (1 << 18) -+ -+#define GEN8_GAMW_ECO_DEV_RW_IA _MMIO(0x4080) -+#define GAMW_ECO_ENABLE_64K_IPS_FIELD 0xF -+#define GAMW_ECO_DEV_CTX_RELOAD_DISABLE (1 << 7) -+ -+#define GAMT_CHKN_BIT_REG _MMIO(0x4ab8) -+#define GAMT_CHKN_DISABLE_L3_COH_PIPE (1 << 31) -+#define GAMT_CHKN_DISABLE_DYNAMIC_CREDIT_SHARING (1 << 28) -+#define GAMT_CHKN_DISABLE_I2M_CYCLE_ON_WR_PORT (1 << 24) -+ -+#if 0 -+#define PRB0_TAIL _MMIO(0x2030) -+#define PRB0_HEAD _MMIO(0x2034) -+#define PRB0_START _MMIO(0x2038) -+#define PRB0_CTL _MMIO(0x203c) -+#define PRB1_TAIL _MMIO(0x2040) /* 915+ only */ -+#define PRB1_HEAD _MMIO(0x2044) /* 915+ only */ -+#define PRB1_START _MMIO(0x2048) /* 915+ only */ -+#define PRB1_CTL _MMIO(0x204c) /* 915+ only */ -+#endif -+#define IPEIR_I965 _MMIO(0x2064) -+#define IPEHR_I965 _MMIO(0x2068) -+#define GEN7_SC_INSTDONE _MMIO(0x7100) -+#define GEN7_SAMPLER_INSTDONE _MMIO(0xe160) -+#define GEN7_ROW_INSTDONE _MMIO(0xe164) -+#define GEN8_MCR_SELECTOR _MMIO(0xfdc) -+#define GEN8_MCR_SLICE(slice) (((slice) & 3) << 26) -+#define GEN8_MCR_SLICE_MASK GEN8_MCR_SLICE(3) -+#define GEN8_MCR_SUBSLICE(subslice) (((subslice) & 3) << 24) -+#define GEN8_MCR_SUBSLICE_MASK GEN8_MCR_SUBSLICE(3) -+#define GEN11_MCR_SLICE(slice) (((slice) & 0xf) << 27) -+#define GEN11_MCR_SLICE_MASK GEN11_MCR_SLICE(0xf) -+#define GEN11_MCR_SUBSLICE(subslice) (((subslice) & 0x7) << 24) -+#define GEN11_MCR_SUBSLICE_MASK GEN11_MCR_SUBSLICE(0x7) -+#define RING_IPEIR(base) _MMIO((base) + 0x64) -+#define RING_IPEHR(base) _MMIO((base) + 0x68) -+/* -+ * On GEN4, only the render ring INSTDONE exists and has a different -+ * layout than the GEN7+ version. -+ * The GEN2 counterpart of this register is GEN2_INSTDONE. -+ */ -+#define RING_INSTDONE(base) _MMIO((base) + 0x6c) -+#define RING_INSTPS(base) _MMIO((base) + 0x70) -+#define RING_DMA_FADD(base) _MMIO((base) + 0x78) -+#define RING_DMA_FADD_UDW(base) _MMIO((base) + 0x60) /* gen8+ */ -+#define RING_INSTPM(base) _MMIO((base) + 0xc0) -+#define RING_MI_MODE(base) _MMIO((base) + 0x9c) -+#define INSTPS _MMIO(0x2070) /* 965+ only */ -+#define GEN4_INSTDONE1 _MMIO(0x207c) /* 965+ only, aka INSTDONE_2 on SNB */ -+#define ACTHD_I965 _MMIO(0x2074) -+#define HWS_PGA _MMIO(0x2080) -+#define HWS_ADDRESS_MASK 0xfffff000 -+#define HWS_START_ADDRESS_SHIFT 4 -+#define PWRCTXA _MMIO(0x2088) /* 965GM+ only */ -+#define PWRCTX_EN (1 << 0) -+#define IPEIR(base) _MMIO((base) + 0x88) -+#define IPEHR(base) _MMIO((base) + 0x8c) -+#define GEN2_INSTDONE _MMIO(0x2090) -+#define NOPID _MMIO(0x2094) -+#define HWSTAM _MMIO(0x2098) -+#define DMA_FADD_I8XX(base) _MMIO((base) + 0xd0) -+#define RING_BBSTATE(base) _MMIO((base) + 0x110) -+#define RING_BB_PPGTT (1 << 5) -+#define RING_SBBADDR(base) _MMIO((base) + 0x114) /* hsw+ */ -+#define RING_SBBSTATE(base) _MMIO((base) + 0x118) /* hsw+ */ -+#define RING_SBBADDR_UDW(base) _MMIO((base) + 0x11c) /* gen8+ */ -+#define RING_BBADDR(base) _MMIO((base) + 0x140) -+#define RING_BBADDR_UDW(base) _MMIO((base) + 0x168) /* gen8+ */ -+#define RING_BB_PER_CTX_PTR(base) _MMIO((base) + 0x1c0) /* gen8+ */ -+#define RING_INDIRECT_CTX(base) _MMIO((base) + 0x1c4) /* gen8+ */ -+#define RING_INDIRECT_CTX_OFFSET(base) _MMIO((base) + 0x1c8) /* gen8+ */ -+#define RING_CTX_TIMESTAMP(base) _MMIO((base) + 0x3a8) /* gen8+ */ -+ -+#define ERROR_GEN6 _MMIO(0x40a0) -+#define GEN7_ERR_INT _MMIO(0x44040) -+#define ERR_INT_POISON (1 << 31) -+#define ERR_INT_MMIO_UNCLAIMED (1 << 13) -+#define ERR_INT_PIPE_CRC_DONE_C (1 << 8) -+#define ERR_INT_FIFO_UNDERRUN_C (1 << 6) -+#define ERR_INT_PIPE_CRC_DONE_B (1 << 5) -+#define ERR_INT_FIFO_UNDERRUN_B (1 << 3) -+#define ERR_INT_PIPE_CRC_DONE_A (1 << 2) -+#define ERR_INT_PIPE_CRC_DONE(pipe) (1 << (2 + (pipe) * 3)) -+#define ERR_INT_FIFO_UNDERRUN_A (1 << 0) -+#define ERR_INT_FIFO_UNDERRUN(pipe) (1 << ((pipe) * 3)) -+ -+#define GEN8_FAULT_TLB_DATA0 _MMIO(0x4b10) -+#define GEN8_FAULT_TLB_DATA1 _MMIO(0x4b14) -+#define FAULT_VA_HIGH_BITS (0xf << 0) -+#define FAULT_GTT_SEL (1 << 4) -+ -+#define FPGA_DBG _MMIO(0x42300) -+#define FPGA_DBG_RM_NOCLAIM (1 << 31) -+ -+#define CLAIM_ER _MMIO(VLV_DISPLAY_BASE + 0x2028) -+#define CLAIM_ER_CLR (1 << 31) -+#define CLAIM_ER_OVERFLOW (1 << 16) -+#define CLAIM_ER_CTR_MASK 0xffff -+ -+#define DERRMR _MMIO(0x44050) -+/* Note that HBLANK events are reserved on bdw+ */ -+#define DERRMR_PIPEA_SCANLINE (1 << 0) -+#define DERRMR_PIPEA_PRI_FLIP_DONE (1 << 1) -+#define DERRMR_PIPEA_SPR_FLIP_DONE (1 << 2) -+#define DERRMR_PIPEA_VBLANK (1 << 3) -+#define DERRMR_PIPEA_HBLANK (1 << 5) -+#define DERRMR_PIPEB_SCANLINE (1 << 8) -+#define DERRMR_PIPEB_PRI_FLIP_DONE (1 << 9) -+#define DERRMR_PIPEB_SPR_FLIP_DONE (1 << 10) -+#define DERRMR_PIPEB_VBLANK (1 << 11) -+#define DERRMR_PIPEB_HBLANK (1 << 13) -+/* Note that PIPEC is not a simple translation of PIPEA/PIPEB */ -+#define DERRMR_PIPEC_SCANLINE (1 << 14) -+#define DERRMR_PIPEC_PRI_FLIP_DONE (1 << 15) -+#define DERRMR_PIPEC_SPR_FLIP_DONE (1 << 20) -+#define DERRMR_PIPEC_VBLANK (1 << 21) -+#define DERRMR_PIPEC_HBLANK (1 << 22) -+ -+ -+/* GM45+ chicken bits -- debug workaround bits that may be required -+ * for various sorts of correct behavior. The top 16 bits of each are -+ * the enables for writing to the corresponding low bit. -+ */ -+#define _3D_CHICKEN _MMIO(0x2084) -+#define _3D_CHICKEN_HIZ_PLANE_DISABLE_MSAA_4X_SNB (1 << 10) -+#define _3D_CHICKEN2 _MMIO(0x208c) -+ -+#define FF_SLICE_CHICKEN _MMIO(0x2088) -+#define FF_SLICE_CHICKEN_CL_PROVOKING_VERTEX_FIX (1 << 1) -+ -+/* Disables pipelining of read flushes past the SF-WIZ interface. -+ * Required on all Ironlake steppings according to the B-Spec, but the -+ * particular danger of not doing so is not specified. -+ */ -+# define _3D_CHICKEN2_WM_READ_PIPELINED (1 << 14) -+#define _3D_CHICKEN3 _MMIO(0x2090) -+#define _3D_CHICKEN_SF_PROVOKING_VERTEX_FIX (1 << 12) -+#define _3D_CHICKEN_SF_DISABLE_OBJEND_CULL (1 << 10) -+#define _3D_CHICKEN3_AA_LINE_QUALITY_FIX_ENABLE (1 << 5) -+#define _3D_CHICKEN3_SF_DISABLE_FASTCLIP_CULL (1 << 5) -+#define _3D_CHICKEN_SDE_LIMIT_FIFO_POLY_DEPTH(x) ((x) << 1) /* gen8+ */ -+#define _3D_CHICKEN3_SF_DISABLE_PIPELINED_ATTR_FETCH (1 << 1) /* gen6 */ -+ -+#define MI_MODE _MMIO(0x209c) -+# define VS_TIMER_DISPATCH (1 << 6) -+# define MI_FLUSH_ENABLE (1 << 12) -+# define ASYNC_FLIP_PERF_DISABLE (1 << 14) -+# define MODE_IDLE (1 << 9) -+# define STOP_RING (1 << 8) -+ -+#define GEN6_GT_MODE _MMIO(0x20d0) -+#define GEN7_GT_MODE _MMIO(0x7008) -+#define GEN6_WIZ_HASHING(hi, lo) (((hi) << 9) | ((lo) << 7)) -+#define GEN6_WIZ_HASHING_8x8 GEN6_WIZ_HASHING(0, 0) -+#define GEN6_WIZ_HASHING_8x4 GEN6_WIZ_HASHING(0, 1) -+#define GEN6_WIZ_HASHING_16x4 GEN6_WIZ_HASHING(1, 0) -+#define GEN6_WIZ_HASHING_MASK GEN6_WIZ_HASHING(1, 1) -+#define GEN6_TD_FOUR_ROW_DISPATCH_DISABLE (1 << 5) -+#define GEN9_IZ_HASHING_MASK(slice) (0x3 << ((slice) * 2)) -+#define GEN9_IZ_HASHING(slice, val) ((val) << ((slice) * 2)) -+ -+/* chicken reg for WaConextSwitchWithConcurrentTLBInvalidate */ -+#define GEN9_CSFE_CHICKEN1_RCS _MMIO(0x20D4) -+#define GEN9_PREEMPT_GPGPU_SYNC_SWITCH_DISABLE (1 << 2) -+#define GEN11_ENABLE_32_PLANE_MODE (1 << 7) -+ -+/* WaClearTdlStateAckDirtyBits */ -+#define GEN8_STATE_ACK _MMIO(0x20F0) -+#define GEN9_STATE_ACK_SLICE1 _MMIO(0x20F8) -+#define GEN9_STATE_ACK_SLICE2 _MMIO(0x2100) -+#define GEN9_STATE_ACK_TDL0 (1 << 12) -+#define GEN9_STATE_ACK_TDL1 (1 << 13) -+#define GEN9_STATE_ACK_TDL2 (1 << 14) -+#define GEN9_STATE_ACK_TDL3 (1 << 15) -+#define GEN9_SUBSLICE_TDL_ACK_BITS \ -+ (GEN9_STATE_ACK_TDL3 | GEN9_STATE_ACK_TDL2 | \ -+ GEN9_STATE_ACK_TDL1 | GEN9_STATE_ACK_TDL0) -+ -+#define GFX_MODE _MMIO(0x2520) -+#define GFX_MODE_GEN7 _MMIO(0x229c) -+#define RING_MODE_GEN7(engine) _MMIO((engine)->mmio_base + 0x29c) -+#define GFX_RUN_LIST_ENABLE (1 << 15) -+#define GFX_INTERRUPT_STEERING (1 << 14) -+#define GFX_TLB_INVALIDATE_EXPLICIT (1 << 13) -+#define GFX_SURFACE_FAULT_ENABLE (1 << 12) -+#define GFX_REPLAY_MODE (1 << 11) -+#define GFX_PSMI_GRANULARITY (1 << 10) -+#define GFX_PPGTT_ENABLE (1 << 9) -+#define GEN8_GFX_PPGTT_48B (1 << 7) -+ -+#define GFX_FORWARD_VBLANK_MASK (3 << 5) -+#define GFX_FORWARD_VBLANK_NEVER (0 << 5) -+#define GFX_FORWARD_VBLANK_ALWAYS (1 << 5) -+#define GFX_FORWARD_VBLANK_COND (2 << 5) -+ -+#define GEN11_GFX_DISABLE_LEGACY_MODE (1 << 3) -+ -+#define VLV_GU_CTL0 _MMIO(VLV_DISPLAY_BASE + 0x2030) -+#define VLV_GU_CTL1 _MMIO(VLV_DISPLAY_BASE + 0x2034) -+#define SCPD0 _MMIO(0x209c) /* 915+ only */ -+#define GEN2_IER _MMIO(0x20a0) -+#define GEN2_IIR _MMIO(0x20a4) -+#define GEN2_IMR _MMIO(0x20a8) -+#define GEN2_ISR _MMIO(0x20ac) -+#define VLV_GUNIT_CLOCK_GATE _MMIO(VLV_DISPLAY_BASE + 0x2060) -+#define GINT_DIS (1 << 22) -+#define GCFG_DIS (1 << 8) -+#define VLV_GUNIT_CLOCK_GATE2 _MMIO(VLV_DISPLAY_BASE + 0x2064) -+#define VLV_IIR_RW _MMIO(VLV_DISPLAY_BASE + 0x2084) -+#define VLV_IER _MMIO(VLV_DISPLAY_BASE + 0x20a0) -+#define VLV_IIR _MMIO(VLV_DISPLAY_BASE + 0x20a4) -+#define VLV_IMR _MMIO(VLV_DISPLAY_BASE + 0x20a8) -+#define VLV_ISR _MMIO(VLV_DISPLAY_BASE + 0x20ac) -+#define VLV_PCBR _MMIO(VLV_DISPLAY_BASE + 0x2120) -+#define VLV_PCBR_ADDR_SHIFT 12 -+ -+#define DISPLAY_PLANE_FLIP_PENDING(plane) (1 << (11 - (plane))) /* A and B only */ -+#define EIR _MMIO(0x20b0) -+#define EMR _MMIO(0x20b4) -+#define ESR _MMIO(0x20b8) -+#define GM45_ERROR_PAGE_TABLE (1 << 5) -+#define GM45_ERROR_MEM_PRIV (1 << 4) -+#define I915_ERROR_PAGE_TABLE (1 << 4) -+#define GM45_ERROR_CP_PRIV (1 << 3) -+#define I915_ERROR_MEMORY_REFRESH (1 << 1) -+#define I915_ERROR_INSTRUCTION (1 << 0) -+#define INSTPM _MMIO(0x20c0) -+#define INSTPM_SELF_EN (1 << 12) /* 915GM only */ -+#define INSTPM_AGPBUSY_INT_EN (1 << 11) /* gen3: when disabled, pending interrupts -+ will not assert AGPBUSY# and will only -+ be delivered when out of C3. */ -+#define INSTPM_FORCE_ORDERING (1 << 7) /* GEN6+ */ -+#define INSTPM_TLB_INVALIDATE (1 << 9) -+#define INSTPM_SYNC_FLUSH (1 << 5) -+#define ACTHD(base) _MMIO((base) + 0xc8) -+#define MEM_MODE _MMIO(0x20cc) -+#define MEM_DISPLAY_B_TRICKLE_FEED_DISABLE (1 << 3) /* 830 only */ -+#define MEM_DISPLAY_A_TRICKLE_FEED_DISABLE (1 << 2) /* 830/845 only */ -+#define MEM_DISPLAY_TRICKLE_FEED_DISABLE (1 << 2) /* 85x only */ -+#define FW_BLC _MMIO(0x20d8) -+#define FW_BLC2 _MMIO(0x20dc) -+#define FW_BLC_SELF _MMIO(0x20e0) /* 915+ only */ -+#define FW_BLC_SELF_EN_MASK (1 << 31) -+#define FW_BLC_SELF_FIFO_MASK (1 << 16) /* 945 only */ -+#define FW_BLC_SELF_EN (1 << 15) /* 945 only */ -+#define MM_BURST_LENGTH 0x00700000 -+#define MM_FIFO_WATERMARK 0x0001F000 -+#define LM_BURST_LENGTH 0x00000700 -+#define LM_FIFO_WATERMARK 0x0000001F -+#define MI_ARB_STATE _MMIO(0x20e4) /* 915+ only */ -+ -+#define MBUS_ABOX_CTL _MMIO(0x45038) -+#define MBUS_ABOX_BW_CREDIT_MASK (3 << 20) -+#define MBUS_ABOX_BW_CREDIT(x) ((x) << 20) -+#define MBUS_ABOX_B_CREDIT_MASK (0xF << 16) -+#define MBUS_ABOX_B_CREDIT(x) ((x) << 16) -+#define MBUS_ABOX_BT_CREDIT_POOL2_MASK (0x1F << 8) -+#define MBUS_ABOX_BT_CREDIT_POOL2(x) ((x) << 8) -+#define MBUS_ABOX_BT_CREDIT_POOL1_MASK (0x1F << 0) -+#define MBUS_ABOX_BT_CREDIT_POOL1(x) ((x) << 0) -+ -+#define _PIPEA_MBUS_DBOX_CTL 0x7003C -+#define _PIPEB_MBUS_DBOX_CTL 0x7103C -+#define PIPE_MBUS_DBOX_CTL(pipe) _MMIO_PIPE(pipe, _PIPEA_MBUS_DBOX_CTL, \ -+ _PIPEB_MBUS_DBOX_CTL) -+#define MBUS_DBOX_BW_CREDIT_MASK (3 << 14) -+#define MBUS_DBOX_BW_CREDIT(x) ((x) << 14) -+#define MBUS_DBOX_B_CREDIT_MASK (0x1F << 8) -+#define MBUS_DBOX_B_CREDIT(x) ((x) << 8) -+#define MBUS_DBOX_A_CREDIT_MASK (0xF << 0) -+#define MBUS_DBOX_A_CREDIT(x) ((x) << 0) -+ -+#define MBUS_UBOX_CTL _MMIO(0x4503C) -+#define MBUS_BBOX_CTL_S1 _MMIO(0x45040) -+#define MBUS_BBOX_CTL_S2 _MMIO(0x45044) -+ -+/* Make render/texture TLB fetches lower priorty than associated data -+ * fetches. This is not turned on by default -+ */ -+#define MI_ARB_RENDER_TLB_LOW_PRIORITY (1 << 15) -+ -+/* Isoch request wait on GTT enable (Display A/B/C streams). -+ * Make isoch requests stall on the TLB update. May cause -+ * display underruns (test mode only) -+ */ -+#define MI_ARB_ISOCH_WAIT_GTT (1 << 14) -+ -+/* Block grant count for isoch requests when block count is -+ * set to a finite value. -+ */ -+#define MI_ARB_BLOCK_GRANT_MASK (3 << 12) -+#define MI_ARB_BLOCK_GRANT_8 (0 << 12) /* for 3 display planes */ -+#define MI_ARB_BLOCK_GRANT_4 (1 << 12) /* for 2 display planes */ -+#define MI_ARB_BLOCK_GRANT_2 (2 << 12) /* for 1 display plane */ -+#define MI_ARB_BLOCK_GRANT_0 (3 << 12) /* don't use */ -+ -+/* Enable render writes to complete in C2/C3/C4 power states. -+ * If this isn't enabled, render writes are prevented in low -+ * power states. That seems bad to me. -+ */ -+#define MI_ARB_C3_LP_WRITE_ENABLE (1 << 11) -+ -+/* This acknowledges an async flip immediately instead -+ * of waiting for 2TLB fetches. -+ */ -+#define MI_ARB_ASYNC_FLIP_ACK_IMMEDIATE (1 << 10) -+ -+/* Enables non-sequential data reads through arbiter -+ */ -+#define MI_ARB_DUAL_DATA_PHASE_DISABLE (1 << 9) -+ -+/* Disable FSB snooping of cacheable write cycles from binner/render -+ * command stream -+ */ -+#define MI_ARB_CACHE_SNOOP_DISABLE (1 << 8) -+ -+/* Arbiter time slice for non-isoch streams */ -+#define MI_ARB_TIME_SLICE_MASK (7 << 5) -+#define MI_ARB_TIME_SLICE_1 (0 << 5) -+#define MI_ARB_TIME_SLICE_2 (1 << 5) -+#define MI_ARB_TIME_SLICE_4 (2 << 5) -+#define MI_ARB_TIME_SLICE_6 (3 << 5) -+#define MI_ARB_TIME_SLICE_8 (4 << 5) -+#define MI_ARB_TIME_SLICE_10 (5 << 5) -+#define MI_ARB_TIME_SLICE_14 (6 << 5) -+#define MI_ARB_TIME_SLICE_16 (7 << 5) -+ -+/* Low priority grace period page size */ -+#define MI_ARB_LOW_PRIORITY_GRACE_4KB (0 << 4) /* default */ -+#define MI_ARB_LOW_PRIORITY_GRACE_8KB (1 << 4) -+ -+/* Disable display A/B trickle feed */ -+#define MI_ARB_DISPLAY_TRICKLE_FEED_DISABLE (1 << 2) -+ -+/* Set display plane priority */ -+#define MI_ARB_DISPLAY_PRIORITY_A_B (0 << 0) /* display A > display B */ -+#define MI_ARB_DISPLAY_PRIORITY_B_A (1 << 0) /* display B > display A */ -+ -+#define MI_STATE _MMIO(0x20e4) /* gen2 only */ -+#define MI_AGPBUSY_INT_EN (1 << 1) /* 85x only */ -+#define MI_AGPBUSY_830_MODE (1 << 0) /* 85x only */ -+ -+#define CACHE_MODE_0 _MMIO(0x2120) /* 915+ only */ -+#define CM0_PIPELINED_RENDER_FLUSH_DISABLE (1 << 8) -+#define CM0_IZ_OPT_DISABLE (1 << 6) -+#define CM0_ZR_OPT_DISABLE (1 << 5) -+#define CM0_STC_EVICT_DISABLE_LRA_SNB (1 << 5) -+#define CM0_DEPTH_EVICT_DISABLE (1 << 4) -+#define CM0_COLOR_EVICT_DISABLE (1 << 3) -+#define CM0_DEPTH_WRITE_DISABLE (1 << 1) -+#define CM0_RC_OP_FLUSH_DISABLE (1 << 0) -+#define GFX_FLSH_CNTL _MMIO(0x2170) /* 915+ only */ -+#define GFX_FLSH_CNTL_GEN6 _MMIO(0x101008) -+#define GFX_FLSH_CNTL_EN (1 << 0) -+#define ECOSKPD _MMIO(0x21d0) -+#define ECO_GATING_CX_ONLY (1 << 3) -+#define ECO_FLIP_DONE (1 << 0) -+ -+#define CACHE_MODE_0_GEN7 _MMIO(0x7000) /* IVB+ */ -+#define RC_OP_FLUSH_ENABLE (1 << 0) -+#define HIZ_RAW_STALL_OPT_DISABLE (1 << 2) -+#define CACHE_MODE_1 _MMIO(0x7004) /* IVB+ */ -+#define PIXEL_SUBSPAN_COLLECT_OPT_DISABLE (1 << 6) -+#define GEN8_4x4_STC_OPTIMIZATION_DISABLE (1 << 6) -+#define GEN9_PARTIAL_RESOLVE_IN_VC_DISABLE (1 << 1) -+ -+#define GEN6_BLITTER_ECOSKPD _MMIO(0x221d0) -+#define GEN6_BLITTER_LOCK_SHIFT 16 -+#define GEN6_BLITTER_FBC_NOTIFY (1 << 3) -+ -+#define GEN6_RC_SLEEP_PSMI_CONTROL _MMIO(0x2050) -+#define GEN6_PSMI_SLEEP_MSG_DISABLE (1 << 0) -+#define GEN8_RC_SEMA_IDLE_MSG_DISABLE (1 << 12) -+#define GEN8_FF_DOP_CLOCK_GATE_DISABLE (1 << 10) -+ -+#define GEN6_RCS_PWR_FSM _MMIO(0x22ac) -+#define GEN9_RCS_FE_FSM2 _MMIO(0x22a4) -+ -+#define GEN10_CACHE_MODE_SS _MMIO(0xe420) -+#define FLOAT_BLEND_OPTIMIZATION_ENABLE (1 << 4) -+ -+/* Fuse readout registers for GT */ -+#define HSW_PAVP_FUSE1 _MMIO(0x911C) -+#define HSW_F1_EU_DIS_SHIFT 16 -+#define HSW_F1_EU_DIS_MASK (0x3 << HSW_F1_EU_DIS_SHIFT) -+#define HSW_F1_EU_DIS_10EUS 0 -+#define HSW_F1_EU_DIS_8EUS 1 -+#define HSW_F1_EU_DIS_6EUS 2 -+ -+#define CHV_FUSE_GT _MMIO(VLV_DISPLAY_BASE + 0x2168) -+#define CHV_FGT_DISABLE_SS0 (1 << 10) -+#define CHV_FGT_DISABLE_SS1 (1 << 11) -+#define CHV_FGT_EU_DIS_SS0_R0_SHIFT 16 -+#define CHV_FGT_EU_DIS_SS0_R0_MASK (0xf << CHV_FGT_EU_DIS_SS0_R0_SHIFT) -+#define CHV_FGT_EU_DIS_SS0_R1_SHIFT 20 -+#define CHV_FGT_EU_DIS_SS0_R1_MASK (0xf << CHV_FGT_EU_DIS_SS0_R1_SHIFT) -+#define CHV_FGT_EU_DIS_SS1_R0_SHIFT 24 -+#define CHV_FGT_EU_DIS_SS1_R0_MASK (0xf << CHV_FGT_EU_DIS_SS1_R0_SHIFT) -+#define CHV_FGT_EU_DIS_SS1_R1_SHIFT 28 -+#define CHV_FGT_EU_DIS_SS1_R1_MASK (0xf << CHV_FGT_EU_DIS_SS1_R1_SHIFT) -+ -+#define GEN8_FUSE2 _MMIO(0x9120) -+#define GEN8_F2_SS_DIS_SHIFT 21 -+#define GEN8_F2_SS_DIS_MASK (0x7 << GEN8_F2_SS_DIS_SHIFT) -+#define GEN8_F2_S_ENA_SHIFT 25 -+#define GEN8_F2_S_ENA_MASK (0x7 << GEN8_F2_S_ENA_SHIFT) -+ -+#define GEN9_F2_SS_DIS_SHIFT 20 -+#define GEN9_F2_SS_DIS_MASK (0xf << GEN9_F2_SS_DIS_SHIFT) -+ -+#define GEN10_F2_S_ENA_SHIFT 22 -+#define GEN10_F2_S_ENA_MASK (0x3f << GEN10_F2_S_ENA_SHIFT) -+#define GEN10_F2_SS_DIS_SHIFT 18 -+#define GEN10_F2_SS_DIS_MASK (0xf << GEN10_F2_SS_DIS_SHIFT) -+ -+#define GEN10_MIRROR_FUSE3 _MMIO(0x9118) -+#define GEN10_L3BANK_PAIR_COUNT 4 -+#define GEN10_L3BANK_MASK 0x0F -+ -+#define GEN8_EU_DISABLE0 _MMIO(0x9134) -+#define GEN8_EU_DIS0_S0_MASK 0xffffff -+#define GEN8_EU_DIS0_S1_SHIFT 24 -+#define GEN8_EU_DIS0_S1_MASK (0xff << GEN8_EU_DIS0_S1_SHIFT) -+ -+#define GEN8_EU_DISABLE1 _MMIO(0x9138) -+#define GEN8_EU_DIS1_S1_MASK 0xffff -+#define GEN8_EU_DIS1_S2_SHIFT 16 -+#define GEN8_EU_DIS1_S2_MASK (0xffff << GEN8_EU_DIS1_S2_SHIFT) -+ -+#define GEN8_EU_DISABLE2 _MMIO(0x913c) -+#define GEN8_EU_DIS2_S2_MASK 0xff -+ -+#define GEN9_EU_DISABLE(slice) _MMIO(0x9134 + (slice) * 0x4) -+ -+#define GEN10_EU_DISABLE3 _MMIO(0x9140) -+#define GEN10_EU_DIS_SS_MASK 0xff -+ -+#define GEN11_GT_VEBOX_VDBOX_DISABLE _MMIO(0x9140) -+#define GEN11_GT_VDBOX_DISABLE_MASK 0xff -+#define GEN11_GT_VEBOX_DISABLE_SHIFT 16 -+#define GEN11_GT_VEBOX_DISABLE_MASK (0x0f << GEN11_GT_VEBOX_DISABLE_SHIFT) -+ -+#define GEN11_EU_DISABLE _MMIO(0x9134) -+#define GEN11_EU_DIS_MASK 0xFF -+ -+#define GEN11_GT_SLICE_ENABLE _MMIO(0x9138) -+#define GEN11_GT_S_ENA_MASK 0xFF -+ -+#define GEN11_GT_SUBSLICE_DISABLE _MMIO(0x913C) -+ -+#define GEN6_BSD_SLEEP_PSMI_CONTROL _MMIO(0x12050) -+#define GEN6_BSD_SLEEP_MSG_DISABLE (1 << 0) -+#define GEN6_BSD_SLEEP_FLUSH_DISABLE (1 << 2) -+#define GEN6_BSD_SLEEP_INDICATOR (1 << 3) -+#define GEN6_BSD_GO_INDICATOR (1 << 4) -+ -+/* On modern GEN architectures interrupt control consists of two sets -+ * of registers. The first set pertains to the ring generating the -+ * interrupt. The second control is for the functional block generating the -+ * interrupt. These are PM, GT, DE, etc. -+ * -+ * Luckily *knocks on wood* all the ring interrupt bits match up with the -+ * GT interrupt bits, so we don't need to duplicate the defines. -+ * -+ * These defines should cover us well from SNB->HSW with minor exceptions -+ * it can also work on ILK. -+ */ -+#define GT_BLT_FLUSHDW_NOTIFY_INTERRUPT (1 << 26) -+#define GT_BLT_CS_ERROR_INTERRUPT (1 << 25) -+#define GT_BLT_USER_INTERRUPT (1 << 22) -+#define GT_BSD_CS_ERROR_INTERRUPT (1 << 15) -+#define GT_BSD_USER_INTERRUPT (1 << 12) -+#define GT_RENDER_L3_PARITY_ERROR_INTERRUPT_S1 (1 << 11) /* hsw+; rsvd on snb, ivb, vlv */ -+#define GT_CONTEXT_SWITCH_INTERRUPT (1 << 8) -+#define GT_RENDER_L3_PARITY_ERROR_INTERRUPT (1 << 5) /* !snb */ -+#define GT_RENDER_PIPECTL_NOTIFY_INTERRUPT (1 << 4) -+#define GT_RENDER_CS_MASTER_ERROR_INTERRUPT (1 << 3) -+#define GT_RENDER_SYNC_STATUS_INTERRUPT (1 << 2) -+#define GT_RENDER_DEBUG_INTERRUPT (1 << 1) -+#define GT_RENDER_USER_INTERRUPT (1 << 0) -+ -+#define PM_VEBOX_CS_ERROR_INTERRUPT (1 << 12) /* hsw+ */ -+#define PM_VEBOX_USER_INTERRUPT (1 << 10) /* hsw+ */ -+ -+#define GT_PARITY_ERROR(dev_priv) \ -+ (GT_RENDER_L3_PARITY_ERROR_INTERRUPT | \ -+ (IS_HASWELL(dev_priv) ? GT_RENDER_L3_PARITY_ERROR_INTERRUPT_S1 : 0)) -+ -+/* These are all the "old" interrupts */ -+#define ILK_BSD_USER_INTERRUPT (1 << 5) -+ -+#define I915_PM_INTERRUPT (1 << 31) -+#define I915_ISP_INTERRUPT (1 << 22) -+#define I915_LPE_PIPE_B_INTERRUPT (1 << 21) -+#define I915_LPE_PIPE_A_INTERRUPT (1 << 20) -+#define I915_MIPIC_INTERRUPT (1 << 19) -+#define I915_MIPIA_INTERRUPT (1 << 18) -+#define I915_PIPE_CONTROL_NOTIFY_INTERRUPT (1 << 18) -+#define I915_DISPLAY_PORT_INTERRUPT (1 << 17) -+#define I915_DISPLAY_PIPE_C_HBLANK_INTERRUPT (1 << 16) -+#define I915_MASTER_ERROR_INTERRUPT (1 << 15) -+#define I915_DISPLAY_PIPE_B_HBLANK_INTERRUPT (1 << 14) -+#define I915_GMCH_THERMAL_SENSOR_EVENT_INTERRUPT (1 << 14) /* p-state */ -+#define I915_DISPLAY_PIPE_A_HBLANK_INTERRUPT (1 << 13) -+#define I915_HWB_OOM_INTERRUPT (1 << 13) -+#define I915_LPE_PIPE_C_INTERRUPT (1 << 12) -+#define I915_SYNC_STATUS_INTERRUPT (1 << 12) -+#define I915_MISC_INTERRUPT (1 << 11) -+#define I915_DISPLAY_PLANE_A_FLIP_PENDING_INTERRUPT (1 << 11) -+#define I915_DISPLAY_PIPE_C_VBLANK_INTERRUPT (1 << 10) -+#define I915_DISPLAY_PLANE_B_FLIP_PENDING_INTERRUPT (1 << 10) -+#define I915_DISPLAY_PIPE_C_EVENT_INTERRUPT (1 << 9) -+#define I915_OVERLAY_PLANE_FLIP_PENDING_INTERRUPT (1 << 9) -+#define I915_DISPLAY_PIPE_C_DPBM_INTERRUPT (1 << 8) -+#define I915_DISPLAY_PLANE_C_FLIP_PENDING_INTERRUPT (1 << 8) -+#define I915_DISPLAY_PIPE_A_VBLANK_INTERRUPT (1 << 7) -+#define I915_DISPLAY_PIPE_A_EVENT_INTERRUPT (1 << 6) -+#define I915_DISPLAY_PIPE_B_VBLANK_INTERRUPT (1 << 5) -+#define I915_DISPLAY_PIPE_B_EVENT_INTERRUPT (1 << 4) -+#define I915_DISPLAY_PIPE_A_DPBM_INTERRUPT (1 << 3) -+#define I915_DISPLAY_PIPE_B_DPBM_INTERRUPT (1 << 2) -+#define I915_DEBUG_INTERRUPT (1 << 2) -+#define I915_WINVALID_INTERRUPT (1 << 1) -+#define I915_USER_INTERRUPT (1 << 1) -+#define I915_ASLE_INTERRUPT (1 << 0) -+#define I915_BSD_USER_INTERRUPT (1 << 25) -+ -+#define I915_HDMI_LPE_AUDIO_BASE (VLV_DISPLAY_BASE + 0x65000) -+#define I915_HDMI_LPE_AUDIO_SIZE 0x1000 -+ -+/* DisplayPort Audio w/ LPE */ -+#define VLV_AUD_CHICKEN_BIT_REG _MMIO(VLV_DISPLAY_BASE + 0x62F38) -+#define VLV_CHICKEN_BIT_DBG_ENABLE (1 << 0) -+ -+#define _VLV_AUD_PORT_EN_B_DBG (VLV_DISPLAY_BASE + 0x62F20) -+#define _VLV_AUD_PORT_EN_C_DBG (VLV_DISPLAY_BASE + 0x62F30) -+#define _VLV_AUD_PORT_EN_D_DBG (VLV_DISPLAY_BASE + 0x62F34) -+#define VLV_AUD_PORT_EN_DBG(port) _MMIO_PORT3((port) - PORT_B, \ -+ _VLV_AUD_PORT_EN_B_DBG, \ -+ _VLV_AUD_PORT_EN_C_DBG, \ -+ _VLV_AUD_PORT_EN_D_DBG) -+#define VLV_AMP_MUTE (1 << 1) -+ -+#define GEN6_BSD_RNCID _MMIO(0x12198) -+ -+#define GEN7_FF_THREAD_MODE _MMIO(0x20a0) -+#define GEN7_FF_SCHED_MASK 0x0077070 -+#define GEN8_FF_DS_REF_CNT_FFME (1 << 19) -+#define GEN7_FF_TS_SCHED_HS1 (0x5 << 16) -+#define GEN7_FF_TS_SCHED_HS0 (0x3 << 16) -+#define GEN7_FF_TS_SCHED_LOAD_BALANCE (0x1 << 16) -+#define GEN7_FF_TS_SCHED_HW (0x0 << 16) /* Default */ -+#define GEN7_FF_VS_REF_CNT_FFME (1 << 15) -+#define GEN7_FF_VS_SCHED_HS1 (0x5 << 12) -+#define GEN7_FF_VS_SCHED_HS0 (0x3 << 12) -+#define GEN7_FF_VS_SCHED_LOAD_BALANCE (0x1 << 12) /* Default */ -+#define GEN7_FF_VS_SCHED_HW (0x0 << 12) -+#define GEN7_FF_DS_SCHED_HS1 (0x5 << 4) -+#define GEN7_FF_DS_SCHED_HS0 (0x3 << 4) -+#define GEN7_FF_DS_SCHED_LOAD_BALANCE (0x1 << 4) /* Default */ -+#define GEN7_FF_DS_SCHED_HW (0x0 << 4) -+ -+/* -+ * Framebuffer compression (915+ only) -+ */ -+ -+#define FBC_CFB_BASE _MMIO(0x3200) /* 4k page aligned */ -+#define FBC_LL_BASE _MMIO(0x3204) /* 4k page aligned */ -+#define FBC_CONTROL _MMIO(0x3208) -+#define FBC_CTL_EN (1 << 31) -+#define FBC_CTL_PERIODIC (1 << 30) -+#define FBC_CTL_INTERVAL_SHIFT (16) -+#define FBC_CTL_UNCOMPRESSIBLE (1 << 14) -+#define FBC_CTL_C3_IDLE (1 << 13) -+#define FBC_CTL_STRIDE_SHIFT (5) -+#define FBC_CTL_FENCENO_SHIFT (0) -+#define FBC_COMMAND _MMIO(0x320c) -+#define FBC_CMD_COMPRESS (1 << 0) -+#define FBC_STATUS _MMIO(0x3210) -+#define FBC_STAT_COMPRESSING (1 << 31) -+#define FBC_STAT_COMPRESSED (1 << 30) -+#define FBC_STAT_MODIFIED (1 << 29) -+#define FBC_STAT_CURRENT_LINE_SHIFT (0) -+#define FBC_CONTROL2 _MMIO(0x3214) -+#define FBC_CTL_FENCE_DBL (0 << 4) -+#define FBC_CTL_IDLE_IMM (0 << 2) -+#define FBC_CTL_IDLE_FULL (1 << 2) -+#define FBC_CTL_IDLE_LINE (2 << 2) -+#define FBC_CTL_IDLE_DEBUG (3 << 2) -+#define FBC_CTL_CPU_FENCE (1 << 1) -+#define FBC_CTL_PLANE(plane) ((plane) << 0) -+#define FBC_FENCE_OFF _MMIO(0x3218) /* BSpec typo has 321Bh */ -+#define FBC_TAG(i) _MMIO(0x3300 + (i) * 4) -+ -+#define FBC_LL_SIZE (1536) -+ -+#define FBC_LLC_READ_CTRL _MMIO(0x9044) -+#define FBC_LLC_FULLY_OPEN (1 << 30) -+ -+/* Framebuffer compression for GM45+ */ -+#define DPFC_CB_BASE _MMIO(0x3200) -+#define DPFC_CONTROL _MMIO(0x3208) -+#define DPFC_CTL_EN (1 << 31) -+#define DPFC_CTL_PLANE(plane) ((plane) << 30) -+#define IVB_DPFC_CTL_PLANE(plane) ((plane) << 29) -+#define DPFC_CTL_FENCE_EN (1 << 29) -+#define IVB_DPFC_CTL_FENCE_EN (1 << 28) -+#define DPFC_CTL_PERSISTENT_MODE (1 << 25) -+#define DPFC_SR_EN (1 << 10) -+#define DPFC_CTL_LIMIT_1X (0 << 6) -+#define DPFC_CTL_LIMIT_2X (1 << 6) -+#define DPFC_CTL_LIMIT_4X (2 << 6) -+#define DPFC_RECOMP_CTL _MMIO(0x320c) -+#define DPFC_RECOMP_STALL_EN (1 << 27) -+#define DPFC_RECOMP_STALL_WM_SHIFT (16) -+#define DPFC_RECOMP_STALL_WM_MASK (0x07ff0000) -+#define DPFC_RECOMP_TIMER_COUNT_SHIFT (0) -+#define DPFC_RECOMP_TIMER_COUNT_MASK (0x0000003f) -+#define DPFC_STATUS _MMIO(0x3210) -+#define DPFC_INVAL_SEG_SHIFT (16) -+#define DPFC_INVAL_SEG_MASK (0x07ff0000) -+#define DPFC_COMP_SEG_SHIFT (0) -+#define DPFC_COMP_SEG_MASK (0x000007ff) -+#define DPFC_STATUS2 _MMIO(0x3214) -+#define DPFC_FENCE_YOFF _MMIO(0x3218) -+#define DPFC_CHICKEN _MMIO(0x3224) -+#define DPFC_HT_MODIFY (1 << 31) -+ -+/* Framebuffer compression for Ironlake */ -+#define ILK_DPFC_CB_BASE _MMIO(0x43200) -+#define ILK_DPFC_CONTROL _MMIO(0x43208) -+#define FBC_CTL_FALSE_COLOR (1 << 10) -+/* The bit 28-8 is reserved */ -+#define DPFC_RESERVED (0x1FFFFF00) -+#define ILK_DPFC_RECOMP_CTL _MMIO(0x4320c) -+#define ILK_DPFC_STATUS _MMIO(0x43210) -+#define ILK_DPFC_COMP_SEG_MASK 0x7ff -+#define IVB_FBC_STATUS2 _MMIO(0x43214) -+#define IVB_FBC_COMP_SEG_MASK 0x7ff -+#define BDW_FBC_COMP_SEG_MASK 0xfff -+#define ILK_DPFC_FENCE_YOFF _MMIO(0x43218) -+#define ILK_DPFC_CHICKEN _MMIO(0x43224) -+#define ILK_DPFC_DISABLE_DUMMY0 (1 << 8) -+#define ILK_DPFC_NUKE_ON_ANY_MODIFICATION (1 << 23) -+#define ILK_FBC_RT_BASE _MMIO(0x2128) -+#define ILK_FBC_RT_VALID (1 << 0) -+#define SNB_FBC_FRONT_BUFFER (1 << 1) -+ -+#define ILK_DISPLAY_CHICKEN1 _MMIO(0x42000) -+#define ILK_FBCQ_DIS (1 << 22) -+#define ILK_PABSTRETCH_DIS (1 << 21) -+ -+ -+/* -+ * Framebuffer compression for Sandybridge -+ * -+ * The following two registers are of type GTTMMADR -+ */ -+#define SNB_DPFC_CTL_SA _MMIO(0x100100) -+#define SNB_CPU_FENCE_ENABLE (1 << 29) -+#define DPFC_CPU_FENCE_OFFSET _MMIO(0x100104) -+ -+/* Framebuffer compression for Ivybridge */ -+#define IVB_FBC_RT_BASE _MMIO(0x7020) -+ -+#define IPS_CTL _MMIO(0x43408) -+#define IPS_ENABLE (1 << 31) -+ -+#define MSG_FBC_REND_STATE _MMIO(0x50380) -+#define FBC_REND_NUKE (1 << 2) -+#define FBC_REND_CACHE_CLEAN (1 << 1) -+ -+/* -+ * GPIO regs -+ */ -+#define GPIO(gpio) _MMIO(dev_priv->gpio_mmio_base + 0x5010 + \ -+ 4 * (gpio)) -+ -+# define GPIO_CLOCK_DIR_MASK (1 << 0) -+# define GPIO_CLOCK_DIR_IN (0 << 1) -+# define GPIO_CLOCK_DIR_OUT (1 << 1) -+# define GPIO_CLOCK_VAL_MASK (1 << 2) -+# define GPIO_CLOCK_VAL_OUT (1 << 3) -+# define GPIO_CLOCK_VAL_IN (1 << 4) -+# define GPIO_CLOCK_PULLUP_DISABLE (1 << 5) -+# define GPIO_DATA_DIR_MASK (1 << 8) -+# define GPIO_DATA_DIR_IN (0 << 9) -+# define GPIO_DATA_DIR_OUT (1 << 9) -+# define GPIO_DATA_VAL_MASK (1 << 10) -+# define GPIO_DATA_VAL_OUT (1 << 11) -+# define GPIO_DATA_VAL_IN (1 << 12) -+# define GPIO_DATA_PULLUP_DISABLE (1 << 13) -+ -+#define GMBUS0 _MMIO(dev_priv->gpio_mmio_base + 0x5100) /* clock/port select */ -+#define GMBUS_AKSV_SELECT (1 << 11) -+#define GMBUS_RATE_100KHZ (0 << 8) -+#define GMBUS_RATE_50KHZ (1 << 8) -+#define GMBUS_RATE_400KHZ (2 << 8) /* reserved on Pineview */ -+#define GMBUS_RATE_1MHZ (3 << 8) /* reserved on Pineview */ -+#define GMBUS_HOLD_EXT (1 << 7) /* 300ns hold time, rsvd on Pineview */ -+#define GMBUS_BYTE_CNT_OVERRIDE (1 << 6) -+#define GMBUS_PIN_DISABLED 0 -+#define GMBUS_PIN_SSC 1 -+#define GMBUS_PIN_VGADDC 2 -+#define GMBUS_PIN_PANEL 3 -+#define GMBUS_PIN_DPD_CHV 3 /* HDMID_CHV */ -+#define GMBUS_PIN_DPC 4 /* HDMIC */ -+#define GMBUS_PIN_DPB 5 /* SDVO, HDMIB */ -+#define GMBUS_PIN_DPD 6 /* HDMID */ -+#define GMBUS_PIN_RESERVED 7 /* 7 reserved */ -+#define GMBUS_PIN_1_BXT 1 /* BXT+ (atom) and CNP+ (big core) */ -+#define GMBUS_PIN_2_BXT 2 -+#define GMBUS_PIN_3_BXT 3 -+#define GMBUS_PIN_4_CNP 4 -+#define GMBUS_PIN_9_TC1_ICP 9 -+#define GMBUS_PIN_10_TC2_ICP 10 -+#define GMBUS_PIN_11_TC3_ICP 11 -+#define GMBUS_PIN_12_TC4_ICP 12 -+ -+#define GMBUS_NUM_PINS 13 /* including 0 */ -+#define GMBUS1 _MMIO(dev_priv->gpio_mmio_base + 0x5104) /* command/status */ -+#define GMBUS_SW_CLR_INT (1 << 31) -+#define GMBUS_SW_RDY (1 << 30) -+#define GMBUS_ENT (1 << 29) /* enable timeout */ -+#define GMBUS_CYCLE_NONE (0 << 25) -+#define GMBUS_CYCLE_WAIT (1 << 25) -+#define GMBUS_CYCLE_INDEX (2 << 25) -+#define GMBUS_CYCLE_STOP (4 << 25) -+#define GMBUS_BYTE_COUNT_SHIFT 16 -+#define GMBUS_BYTE_COUNT_MAX 256U -+#define GEN9_GMBUS_BYTE_COUNT_MAX 511U -+#define GMBUS_SLAVE_INDEX_SHIFT 8 -+#define GMBUS_SLAVE_ADDR_SHIFT 1 -+#define GMBUS_SLAVE_READ (1 << 0) -+#define GMBUS_SLAVE_WRITE (0 << 0) -+#define GMBUS2 _MMIO(dev_priv->gpio_mmio_base + 0x5108) /* status */ -+#define GMBUS_INUSE (1 << 15) -+#define GMBUS_HW_WAIT_PHASE (1 << 14) -+#define GMBUS_STALL_TIMEOUT (1 << 13) -+#define GMBUS_INT (1 << 12) -+#define GMBUS_HW_RDY (1 << 11) -+#define GMBUS_SATOER (1 << 10) -+#define GMBUS_ACTIVE (1 << 9) -+#define GMBUS3 _MMIO(dev_priv->gpio_mmio_base + 0x510c) /* data buffer bytes 3-0 */ -+#define GMBUS4 _MMIO(dev_priv->gpio_mmio_base + 0x5110) /* interrupt mask (Pineview+) */ -+#define GMBUS_SLAVE_TIMEOUT_EN (1 << 4) -+#define GMBUS_NAK_EN (1 << 3) -+#define GMBUS_IDLE_EN (1 << 2) -+#define GMBUS_HW_WAIT_EN (1 << 1) -+#define GMBUS_HW_RDY_EN (1 << 0) -+#define GMBUS5 _MMIO(dev_priv->gpio_mmio_base + 0x5120) /* byte index */ -+#define GMBUS_2BYTE_INDEX_EN (1 << 31) -+ -+/* -+ * Clock control & power management -+ */ -+#define _DPLL_A (DISPLAY_MMIO_BASE(dev_priv) + 0x6014) -+#define _DPLL_B (DISPLAY_MMIO_BASE(dev_priv) + 0x6018) -+#define _CHV_DPLL_C (DISPLAY_MMIO_BASE(dev_priv) + 0x6030) -+#define DPLL(pipe) _MMIO_PIPE3((pipe), _DPLL_A, _DPLL_B, _CHV_DPLL_C) -+ -+#define VGA0 _MMIO(0x6000) -+#define VGA1 _MMIO(0x6004) -+#define VGA_PD _MMIO(0x6010) -+#define VGA0_PD_P2_DIV_4 (1 << 7) -+#define VGA0_PD_P1_DIV_2 (1 << 5) -+#define VGA0_PD_P1_SHIFT 0 -+#define VGA0_PD_P1_MASK (0x1f << 0) -+#define VGA1_PD_P2_DIV_4 (1 << 15) -+#define VGA1_PD_P1_DIV_2 (1 << 13) -+#define VGA1_PD_P1_SHIFT 8 -+#define VGA1_PD_P1_MASK (0x1f << 8) -+#define DPLL_VCO_ENABLE (1 << 31) -+#define DPLL_SDVO_HIGH_SPEED (1 << 30) -+#define DPLL_DVO_2X_MODE (1 << 30) -+#define DPLL_EXT_BUFFER_ENABLE_VLV (1 << 30) -+#define DPLL_SYNCLOCK_ENABLE (1 << 29) -+#define DPLL_REF_CLK_ENABLE_VLV (1 << 29) -+#define DPLL_VGA_MODE_DIS (1 << 28) -+#define DPLLB_MODE_DAC_SERIAL (1 << 26) /* i915 */ -+#define DPLLB_MODE_LVDS (2 << 26) /* i915 */ -+#define DPLL_MODE_MASK (3 << 26) -+#define DPLL_DAC_SERIAL_P2_CLOCK_DIV_10 (0 << 24) /* i915 */ -+#define DPLL_DAC_SERIAL_P2_CLOCK_DIV_5 (1 << 24) /* i915 */ -+#define DPLLB_LVDS_P2_CLOCK_DIV_14 (0 << 24) /* i915 */ -+#define DPLLB_LVDS_P2_CLOCK_DIV_7 (1 << 24) /* i915 */ -+#define DPLL_P2_CLOCK_DIV_MASK 0x03000000 /* i915 */ -+#define DPLL_FPA01_P1_POST_DIV_MASK 0x00ff0000 /* i915 */ -+#define DPLL_FPA01_P1_POST_DIV_MASK_PINEVIEW 0x00ff8000 /* Pineview */ -+#define DPLL_LOCK_VLV (1 << 15) -+#define DPLL_INTEGRATED_CRI_CLK_VLV (1 << 14) -+#define DPLL_INTEGRATED_REF_CLK_VLV (1 << 13) -+#define DPLL_SSC_REF_CLK_CHV (1 << 13) -+#define DPLL_PORTC_READY_MASK (0xf << 4) -+#define DPLL_PORTB_READY_MASK (0xf) -+ -+#define DPLL_FPA01_P1_POST_DIV_MASK_I830 0x001f0000 -+ -+/* Additional CHV pll/phy registers */ -+#define DPIO_PHY_STATUS _MMIO(VLV_DISPLAY_BASE + 0x6240) -+#define DPLL_PORTD_READY_MASK (0xf) -+#define DISPLAY_PHY_CONTROL _MMIO(VLV_DISPLAY_BASE + 0x60100) -+#define PHY_CH_POWER_DOWN_OVRD_EN(phy, ch) (1 << (2 * (phy) + (ch) + 27)) -+#define PHY_LDO_DELAY_0NS 0x0 -+#define PHY_LDO_DELAY_200NS 0x1 -+#define PHY_LDO_DELAY_600NS 0x2 -+#define PHY_LDO_SEQ_DELAY(delay, phy) ((delay) << (2 * (phy) + 23)) -+#define PHY_CH_POWER_DOWN_OVRD(mask, phy, ch) ((mask) << (8 * (phy) + 4 * (ch) + 11)) -+#define PHY_CH_SU_PSR 0x1 -+#define PHY_CH_DEEP_PSR 0x7 -+#define PHY_CH_POWER_MODE(mode, phy, ch) ((mode) << (6 * (phy) + 3 * (ch) + 2)) -+#define PHY_COM_LANE_RESET_DEASSERT(phy) (1 << (phy)) -+#define DISPLAY_PHY_STATUS _MMIO(VLV_DISPLAY_BASE + 0x60104) -+#define PHY_POWERGOOD(phy) (((phy) == DPIO_PHY0) ? (1 << 31) : (1 << 30)) -+#define PHY_STATUS_CMN_LDO(phy, ch) (1 << (6 - (6 * (phy) + 3 * (ch)))) -+#define PHY_STATUS_SPLINE_LDO(phy, ch, spline) (1 << (8 - (6 * (phy) + 3 * (ch) + (spline)))) -+ -+/* -+ * The i830 generation, in LVDS mode, defines P1 as the bit number set within -+ * this field (only one bit may be set). -+ */ -+#define DPLL_FPA01_P1_POST_DIV_MASK_I830_LVDS 0x003f0000 -+#define DPLL_FPA01_P1_POST_DIV_SHIFT 16 -+#define DPLL_FPA01_P1_POST_DIV_SHIFT_PINEVIEW 15 -+/* i830, required in DVO non-gang */ -+#define PLL_P2_DIVIDE_BY_4 (1 << 23) -+#define PLL_P1_DIVIDE_BY_TWO (1 << 21) /* i830 */ -+#define PLL_REF_INPUT_DREFCLK (0 << 13) -+#define PLL_REF_INPUT_TVCLKINA (1 << 13) /* i830 */ -+#define PLL_REF_INPUT_TVCLKINBC (2 << 13) /* SDVO TVCLKIN */ -+#define PLLB_REF_INPUT_SPREADSPECTRUMIN (3 << 13) -+#define PLL_REF_INPUT_MASK (3 << 13) -+#define PLL_LOAD_PULSE_PHASE_SHIFT 9 -+/* Ironlake */ -+# define PLL_REF_SDVO_HDMI_MULTIPLIER_SHIFT 9 -+# define PLL_REF_SDVO_HDMI_MULTIPLIER_MASK (7 << 9) -+# define PLL_REF_SDVO_HDMI_MULTIPLIER(x) (((x) - 1) << 9) -+# define DPLL_FPA1_P1_POST_DIV_SHIFT 0 -+# define DPLL_FPA1_P1_POST_DIV_MASK 0xff -+ -+/* -+ * Parallel to Serial Load Pulse phase selection. -+ * Selects the phase for the 10X DPLL clock for the PCIe -+ * digital display port. The range is 4 to 13; 10 or more -+ * is just a flip delay. The default is 6 -+ */ -+#define PLL_LOAD_PULSE_PHASE_MASK (0xf << PLL_LOAD_PULSE_PHASE_SHIFT) -+#define DISPLAY_RATE_SELECT_FPA1 (1 << 8) -+/* -+ * SDVO multiplier for 945G/GM. Not used on 965. -+ */ -+#define SDVO_MULTIPLIER_MASK 0x000000ff -+#define SDVO_MULTIPLIER_SHIFT_HIRES 4 -+#define SDVO_MULTIPLIER_SHIFT_VGA 0 -+ -+#define _DPLL_A_MD (DISPLAY_MMIO_BASE(dev_priv) + 0x601c) -+#define _DPLL_B_MD (DISPLAY_MMIO_BASE(dev_priv) + 0x6020) -+#define _CHV_DPLL_C_MD (DISPLAY_MMIO_BASE(dev_priv) + 0x603c) -+#define DPLL_MD(pipe) _MMIO_PIPE3((pipe), _DPLL_A_MD, _DPLL_B_MD, _CHV_DPLL_C_MD) -+ -+/* -+ * UDI pixel divider, controlling how many pixels are stuffed into a packet. -+ * -+ * Value is pixels minus 1. Must be set to 1 pixel for SDVO. -+ */ -+#define DPLL_MD_UDI_DIVIDER_MASK 0x3f000000 -+#define DPLL_MD_UDI_DIVIDER_SHIFT 24 -+/* UDI pixel divider for VGA, same as DPLL_MD_UDI_DIVIDER_MASK. */ -+#define DPLL_MD_VGA_UDI_DIVIDER_MASK 0x003f0000 -+#define DPLL_MD_VGA_UDI_DIVIDER_SHIFT 16 -+/* -+ * SDVO/UDI pixel multiplier. -+ * -+ * SDVO requires that the bus clock rate be between 1 and 2 Ghz, and the bus -+ * clock rate is 10 times the DPLL clock. At low resolution/refresh rate -+ * modes, the bus rate would be below the limits, so SDVO allows for stuffing -+ * dummy bytes in the datastream at an increased clock rate, with both sides of -+ * the link knowing how many bytes are fill. -+ * -+ * So, for a mode with a dotclock of 65Mhz, we would want to double the clock -+ * rate to 130Mhz to get a bus rate of 1.30Ghz. The DPLL clock rate would be -+ * set to 130Mhz, and the SDVO multiplier set to 2x in this register and -+ * through an SDVO command. -+ * -+ * This register field has values of multiplication factor minus 1, with -+ * a maximum multiplier of 5 for SDVO. -+ */ -+#define DPLL_MD_UDI_MULTIPLIER_MASK 0x00003f00 -+#define DPLL_MD_UDI_MULTIPLIER_SHIFT 8 -+/* -+ * SDVO/UDI pixel multiplier for VGA, same as DPLL_MD_UDI_MULTIPLIER_MASK. -+ * This best be set to the default value (3) or the CRT won't work. No, -+ * I don't entirely understand what this does... -+ */ -+#define DPLL_MD_VGA_UDI_MULTIPLIER_MASK 0x0000003f -+#define DPLL_MD_VGA_UDI_MULTIPLIER_SHIFT 0 -+ -+#define RAWCLK_FREQ_VLV _MMIO(VLV_DISPLAY_BASE + 0x6024) -+ -+#define _FPA0 0x6040 -+#define _FPA1 0x6044 -+#define _FPB0 0x6048 -+#define _FPB1 0x604c -+#define FP0(pipe) _MMIO_PIPE(pipe, _FPA0, _FPB0) -+#define FP1(pipe) _MMIO_PIPE(pipe, _FPA1, _FPB1) -+#define FP_N_DIV_MASK 0x003f0000 -+#define FP_N_PINEVIEW_DIV_MASK 0x00ff0000 -+#define FP_N_DIV_SHIFT 16 -+#define FP_M1_DIV_MASK 0x00003f00 -+#define FP_M1_DIV_SHIFT 8 -+#define FP_M2_DIV_MASK 0x0000003f -+#define FP_M2_PINEVIEW_DIV_MASK 0x000000ff -+#define FP_M2_DIV_SHIFT 0 -+#define DPLL_TEST _MMIO(0x606c) -+#define DPLLB_TEST_SDVO_DIV_1 (0 << 22) -+#define DPLLB_TEST_SDVO_DIV_2 (1 << 22) -+#define DPLLB_TEST_SDVO_DIV_4 (2 << 22) -+#define DPLLB_TEST_SDVO_DIV_MASK (3 << 22) -+#define DPLLB_TEST_N_BYPASS (1 << 19) -+#define DPLLB_TEST_M_BYPASS (1 << 18) -+#define DPLLB_INPUT_BUFFER_ENABLE (1 << 16) -+#define DPLLA_TEST_N_BYPASS (1 << 3) -+#define DPLLA_TEST_M_BYPASS (1 << 2) -+#define DPLLA_INPUT_BUFFER_ENABLE (1 << 0) -+#define D_STATE _MMIO(0x6104) -+#define DSTATE_GFX_RESET_I830 (1 << 6) -+#define DSTATE_PLL_D3_OFF (1 << 3) -+#define DSTATE_GFX_CLOCK_GATING (1 << 1) -+#define DSTATE_DOT_CLOCK_GATING (1 << 0) -+#define DSPCLK_GATE_D _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x6200) -+# define DPUNIT_B_CLOCK_GATE_DISABLE (1 << 30) /* 965 */ -+# define VSUNIT_CLOCK_GATE_DISABLE (1 << 29) /* 965 */ -+# define VRHUNIT_CLOCK_GATE_DISABLE (1 << 28) /* 965 */ -+# define VRDUNIT_CLOCK_GATE_DISABLE (1 << 27) /* 965 */ -+# define AUDUNIT_CLOCK_GATE_DISABLE (1 << 26) /* 965 */ -+# define DPUNIT_A_CLOCK_GATE_DISABLE (1 << 25) /* 965 */ -+# define DPCUNIT_CLOCK_GATE_DISABLE (1 << 24) /* 965 */ -+# define PNV_GMBUSUNIT_CLOCK_GATE_DISABLE (1 << 24) /* pnv */ -+# define TVRUNIT_CLOCK_GATE_DISABLE (1 << 23) /* 915-945 */ -+# define TVCUNIT_CLOCK_GATE_DISABLE (1 << 22) /* 915-945 */ -+# define TVFUNIT_CLOCK_GATE_DISABLE (1 << 21) /* 915-945 */ -+# define TVEUNIT_CLOCK_GATE_DISABLE (1 << 20) /* 915-945 */ -+# define DVSUNIT_CLOCK_GATE_DISABLE (1 << 19) /* 915-945 */ -+# define DSSUNIT_CLOCK_GATE_DISABLE (1 << 18) /* 915-945 */ -+# define DDBUNIT_CLOCK_GATE_DISABLE (1 << 17) /* 915-945 */ -+# define DPRUNIT_CLOCK_GATE_DISABLE (1 << 16) /* 915-945 */ -+# define DPFUNIT_CLOCK_GATE_DISABLE (1 << 15) /* 915-945 */ -+# define DPBMUNIT_CLOCK_GATE_DISABLE (1 << 14) /* 915-945 */ -+# define DPLSUNIT_CLOCK_GATE_DISABLE (1 << 13) /* 915-945 */ -+# define DPLUNIT_CLOCK_GATE_DISABLE (1 << 12) /* 915-945 */ -+# define DPOUNIT_CLOCK_GATE_DISABLE (1 << 11) -+# define DPBUNIT_CLOCK_GATE_DISABLE (1 << 10) -+# define DCUNIT_CLOCK_GATE_DISABLE (1 << 9) -+# define DPUNIT_CLOCK_GATE_DISABLE (1 << 8) -+# define VRUNIT_CLOCK_GATE_DISABLE (1 << 7) /* 915+: reserved */ -+# define OVHUNIT_CLOCK_GATE_DISABLE (1 << 6) /* 830-865 */ -+# define DPIOUNIT_CLOCK_GATE_DISABLE (1 << 6) /* 915-945 */ -+# define OVFUNIT_CLOCK_GATE_DISABLE (1 << 5) -+# define OVBUNIT_CLOCK_GATE_DISABLE (1 << 4) -+/* -+ * This bit must be set on the 830 to prevent hangs when turning off the -+ * overlay scaler. -+ */ -+# define OVRUNIT_CLOCK_GATE_DISABLE (1 << 3) -+# define OVCUNIT_CLOCK_GATE_DISABLE (1 << 2) -+# define OVUUNIT_CLOCK_GATE_DISABLE (1 << 1) -+# define ZVUNIT_CLOCK_GATE_DISABLE (1 << 0) /* 830 */ -+# define OVLUNIT_CLOCK_GATE_DISABLE (1 << 0) /* 845,865 */ -+ -+#define RENCLK_GATE_D1 _MMIO(0x6204) -+# define BLITTER_CLOCK_GATE_DISABLE (1 << 13) /* 945GM only */ -+# define MPEG_CLOCK_GATE_DISABLE (1 << 12) /* 945GM only */ -+# define PC_FE_CLOCK_GATE_DISABLE (1 << 11) -+# define PC_BE_CLOCK_GATE_DISABLE (1 << 10) -+# define WINDOWER_CLOCK_GATE_DISABLE (1 << 9) -+# define INTERPOLATOR_CLOCK_GATE_DISABLE (1 << 8) -+# define COLOR_CALCULATOR_CLOCK_GATE_DISABLE (1 << 7) -+# define MOTION_COMP_CLOCK_GATE_DISABLE (1 << 6) -+# define MAG_CLOCK_GATE_DISABLE (1 << 5) -+/* This bit must be unset on 855,865 */ -+# define MECI_CLOCK_GATE_DISABLE (1 << 4) -+# define DCMP_CLOCK_GATE_DISABLE (1 << 3) -+# define MEC_CLOCK_GATE_DISABLE (1 << 2) -+# define MECO_CLOCK_GATE_DISABLE (1 << 1) -+/* This bit must be set on 855,865. */ -+# define SV_CLOCK_GATE_DISABLE (1 << 0) -+# define I915_MPEG_CLOCK_GATE_DISABLE (1 << 16) -+# define I915_VLD_IP_PR_CLOCK_GATE_DISABLE (1 << 15) -+# define I915_MOTION_COMP_CLOCK_GATE_DISABLE (1 << 14) -+# define I915_BD_BF_CLOCK_GATE_DISABLE (1 << 13) -+# define I915_SF_SE_CLOCK_GATE_DISABLE (1 << 12) -+# define I915_WM_CLOCK_GATE_DISABLE (1 << 11) -+# define I915_IZ_CLOCK_GATE_DISABLE (1 << 10) -+# define I915_PI_CLOCK_GATE_DISABLE (1 << 9) -+# define I915_DI_CLOCK_GATE_DISABLE (1 << 8) -+# define I915_SH_SV_CLOCK_GATE_DISABLE (1 << 7) -+# define I915_PL_DG_QC_FT_CLOCK_GATE_DISABLE (1 << 6) -+# define I915_SC_CLOCK_GATE_DISABLE (1 << 5) -+# define I915_FL_CLOCK_GATE_DISABLE (1 << 4) -+# define I915_DM_CLOCK_GATE_DISABLE (1 << 3) -+# define I915_PS_CLOCK_GATE_DISABLE (1 << 2) -+# define I915_CC_CLOCK_GATE_DISABLE (1 << 1) -+# define I915_BY_CLOCK_GATE_DISABLE (1 << 0) -+ -+# define I965_RCZ_CLOCK_GATE_DISABLE (1 << 30) -+/* This bit must always be set on 965G/965GM */ -+# define I965_RCC_CLOCK_GATE_DISABLE (1 << 29) -+# define I965_RCPB_CLOCK_GATE_DISABLE (1 << 28) -+# define I965_DAP_CLOCK_GATE_DISABLE (1 << 27) -+# define I965_ROC_CLOCK_GATE_DISABLE (1 << 26) -+# define I965_GW_CLOCK_GATE_DISABLE (1 << 25) -+# define I965_TD_CLOCK_GATE_DISABLE (1 << 24) -+/* This bit must always be set on 965G */ -+# define I965_ISC_CLOCK_GATE_DISABLE (1 << 23) -+# define I965_IC_CLOCK_GATE_DISABLE (1 << 22) -+# define I965_EU_CLOCK_GATE_DISABLE (1 << 21) -+# define I965_IF_CLOCK_GATE_DISABLE (1 << 20) -+# define I965_TC_CLOCK_GATE_DISABLE (1 << 19) -+# define I965_SO_CLOCK_GATE_DISABLE (1 << 17) -+# define I965_FBC_CLOCK_GATE_DISABLE (1 << 16) -+# define I965_MARI_CLOCK_GATE_DISABLE (1 << 15) -+# define I965_MASF_CLOCK_GATE_DISABLE (1 << 14) -+# define I965_MAWB_CLOCK_GATE_DISABLE (1 << 13) -+# define I965_EM_CLOCK_GATE_DISABLE (1 << 12) -+# define I965_UC_CLOCK_GATE_DISABLE (1 << 11) -+# define I965_SI_CLOCK_GATE_DISABLE (1 << 6) -+# define I965_MT_CLOCK_GATE_DISABLE (1 << 5) -+# define I965_PL_CLOCK_GATE_DISABLE (1 << 4) -+# define I965_DG_CLOCK_GATE_DISABLE (1 << 3) -+# define I965_QC_CLOCK_GATE_DISABLE (1 << 2) -+# define I965_FT_CLOCK_GATE_DISABLE (1 << 1) -+# define I965_DM_CLOCK_GATE_DISABLE (1 << 0) -+ -+#define RENCLK_GATE_D2 _MMIO(0x6208) -+#define VF_UNIT_CLOCK_GATE_DISABLE (1 << 9) -+#define GS_UNIT_CLOCK_GATE_DISABLE (1 << 7) -+#define CL_UNIT_CLOCK_GATE_DISABLE (1 << 6) -+ -+#define VDECCLK_GATE_D _MMIO(0x620C) /* g4x only */ -+#define VCP_UNIT_CLOCK_GATE_DISABLE (1 << 4) -+ -+#define RAMCLK_GATE_D _MMIO(0x6210) /* CRL only */ -+#define DEUC _MMIO(0x6214) /* CRL only */ -+ -+#define FW_BLC_SELF_VLV _MMIO(VLV_DISPLAY_BASE + 0x6500) -+#define FW_CSPWRDWNEN (1 << 15) -+ -+#define MI_ARB_VLV _MMIO(VLV_DISPLAY_BASE + 0x6504) -+ -+#define CZCLK_CDCLK_FREQ_RATIO _MMIO(VLV_DISPLAY_BASE + 0x6508) -+#define CDCLK_FREQ_SHIFT 4 -+#define CDCLK_FREQ_MASK (0x1f << CDCLK_FREQ_SHIFT) -+#define CZCLK_FREQ_MASK 0xf -+ -+#define GCI_CONTROL _MMIO(VLV_DISPLAY_BASE + 0x650C) -+#define PFI_CREDIT_63 (9 << 28) /* chv only */ -+#define PFI_CREDIT_31 (8 << 28) /* chv only */ -+#define PFI_CREDIT(x) (((x) - 8) << 28) /* 8-15 */ -+#define PFI_CREDIT_RESEND (1 << 27) -+#define VGA_FAST_MODE_DISABLE (1 << 14) -+ -+#define GMBUSFREQ_VLV _MMIO(VLV_DISPLAY_BASE + 0x6510) -+ -+/* -+ * Palette regs -+ */ -+#define _PALETTE_A 0xa000 -+#define _PALETTE_B 0xa800 -+#define _CHV_PALETTE_C 0xc000 -+#define PALETTE(pipe, i) _MMIO(DISPLAY_MMIO_BASE(dev_priv) + \ -+ _PICK((pipe), _PALETTE_A, \ -+ _PALETTE_B, _CHV_PALETTE_C) + \ -+ (i) * 4) -+ -+/* MCH MMIO space */ -+ -+/* -+ * MCHBAR mirror. -+ * -+ * This mirrors the MCHBAR MMIO space whose location is determined by -+ * device 0 function 0's pci config register 0x44 or 0x48 and matches it in -+ * every way. It is not accessible from the CP register read instructions. -+ * -+ * Starting from Haswell, you can't write registers using the MCHBAR mirror, -+ * just read. -+ */ -+#define MCHBAR_MIRROR_BASE 0x10000 -+ -+#define MCHBAR_MIRROR_BASE_SNB 0x140000 -+ -+#define CTG_STOLEN_RESERVED _MMIO(MCHBAR_MIRROR_BASE + 0x34) -+#define ELK_STOLEN_RESERVED _MMIO(MCHBAR_MIRROR_BASE + 0x48) -+#define G4X_STOLEN_RESERVED_ADDR1_MASK (0xFFFF << 16) -+#define G4X_STOLEN_RESERVED_ADDR2_MASK (0xFFF << 4) -+#define G4X_STOLEN_RESERVED_ENABLE (1 << 0) -+ -+/* Memory controller frequency in MCHBAR for Haswell (possible SNB+) */ -+#define DCLK _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x5e04) -+ -+/* 915-945 and GM965 MCH register controlling DRAM channel access */ -+#define DCC _MMIO(MCHBAR_MIRROR_BASE + 0x200) -+#define DCC_ADDRESSING_MODE_SINGLE_CHANNEL (0 << 0) -+#define DCC_ADDRESSING_MODE_DUAL_CHANNEL_ASYMMETRIC (1 << 0) -+#define DCC_ADDRESSING_MODE_DUAL_CHANNEL_INTERLEAVED (2 << 0) -+#define DCC_ADDRESSING_MODE_MASK (3 << 0) -+#define DCC_CHANNEL_XOR_DISABLE (1 << 10) -+#define DCC_CHANNEL_XOR_BIT_17 (1 << 9) -+#define DCC2 _MMIO(MCHBAR_MIRROR_BASE + 0x204) -+#define DCC2_MODIFIED_ENHANCED_DISABLE (1 << 20) -+ -+/* Pineview MCH register contains DDR3 setting */ -+#define CSHRDDR3CTL _MMIO(MCHBAR_MIRROR_BASE + 0x1a8) -+#define CSHRDDR3CTL_DDR3 (1 << 2) -+ -+/* 965 MCH register controlling DRAM channel configuration */ -+#define C0DRB3 _MMIO(MCHBAR_MIRROR_BASE + 0x206) -+#define C1DRB3 _MMIO(MCHBAR_MIRROR_BASE + 0x606) -+ -+/* snb MCH registers for reading the DRAM channel configuration */ -+#define MAD_DIMM_C0 _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x5004) -+#define MAD_DIMM_C1 _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x5008) -+#define MAD_DIMM_C2 _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x500C) -+#define MAD_DIMM_ECC_MASK (0x3 << 24) -+#define MAD_DIMM_ECC_OFF (0x0 << 24) -+#define MAD_DIMM_ECC_IO_ON_LOGIC_OFF (0x1 << 24) -+#define MAD_DIMM_ECC_IO_OFF_LOGIC_ON (0x2 << 24) -+#define MAD_DIMM_ECC_ON (0x3 << 24) -+#define MAD_DIMM_ENH_INTERLEAVE (0x1 << 22) -+#define MAD_DIMM_RANK_INTERLEAVE (0x1 << 21) -+#define MAD_DIMM_B_WIDTH_X16 (0x1 << 20) /* X8 chips if unset */ -+#define MAD_DIMM_A_WIDTH_X16 (0x1 << 19) /* X8 chips if unset */ -+#define MAD_DIMM_B_DUAL_RANK (0x1 << 18) -+#define MAD_DIMM_A_DUAL_RANK (0x1 << 17) -+#define MAD_DIMM_A_SELECT (0x1 << 16) -+/* DIMM sizes are in multiples of 256mb. */ -+#define MAD_DIMM_B_SIZE_SHIFT 8 -+#define MAD_DIMM_B_SIZE_MASK (0xff << MAD_DIMM_B_SIZE_SHIFT) -+#define MAD_DIMM_A_SIZE_SHIFT 0 -+#define MAD_DIMM_A_SIZE_MASK (0xff << MAD_DIMM_A_SIZE_SHIFT) -+ -+/* snb MCH registers for priority tuning */ -+#define MCH_SSKPD _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x5d10) -+#define MCH_SSKPD_WM0_MASK 0x3f -+#define MCH_SSKPD_WM0_VAL 0xc -+ -+#define MCH_SECP_NRG_STTS _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x592c) -+ -+/* Clocking configuration register */ -+#define CLKCFG _MMIO(MCHBAR_MIRROR_BASE + 0xc00) -+#define CLKCFG_FSB_400 (5 << 0) /* hrawclk 100 */ -+#define CLKCFG_FSB_533 (1 << 0) /* hrawclk 133 */ -+#define CLKCFG_FSB_667 (3 << 0) /* hrawclk 166 */ -+#define CLKCFG_FSB_800 (2 << 0) /* hrawclk 200 */ -+#define CLKCFG_FSB_1067 (6 << 0) /* hrawclk 266 */ -+#define CLKCFG_FSB_1067_ALT (0 << 0) /* hrawclk 266 */ -+#define CLKCFG_FSB_1333 (7 << 0) /* hrawclk 333 */ -+/* -+ * Note that on at least on ELK the below value is reported for both -+ * 333 and 400 MHz BIOS FSB setting, but given that the gmch datasheet -+ * lists only 200/266/333 MHz FSB as supported let's decode it as 333 MHz. -+ */ -+#define CLKCFG_FSB_1333_ALT (4 << 0) /* hrawclk 333 */ -+#define CLKCFG_FSB_MASK (7 << 0) -+#define CLKCFG_MEM_533 (1 << 4) -+#define CLKCFG_MEM_667 (2 << 4) -+#define CLKCFG_MEM_800 (3 << 4) -+#define CLKCFG_MEM_MASK (7 << 4) -+ -+#define HPLLVCO _MMIO(MCHBAR_MIRROR_BASE + 0xc38) -+#define HPLLVCO_MOBILE _MMIO(MCHBAR_MIRROR_BASE + 0xc0f) -+ -+#define TSC1 _MMIO(0x11001) -+#define TSE (1 << 0) -+#define TR1 _MMIO(0x11006) -+#define TSFS _MMIO(0x11020) -+#define TSFS_SLOPE_MASK 0x0000ff00 -+#define TSFS_SLOPE_SHIFT 8 -+#define TSFS_INTR_MASK 0x000000ff -+ -+#define CRSTANDVID _MMIO(0x11100) -+#define PXVFREQ(fstart) _MMIO(0x11110 + (fstart) * 4) /* P[0-15]VIDFREQ (0x1114c) (Ironlake) */ -+#define PXVFREQ_PX_MASK 0x7f000000 -+#define PXVFREQ_PX_SHIFT 24 -+#define VIDFREQ_BASE _MMIO(0x11110) -+#define VIDFREQ1 _MMIO(0x11110) /* VIDFREQ1-4 (0x1111c) (Cantiga) */ -+#define VIDFREQ2 _MMIO(0x11114) -+#define VIDFREQ3 _MMIO(0x11118) -+#define VIDFREQ4 _MMIO(0x1111c) -+#define VIDFREQ_P0_MASK 0x1f000000 -+#define VIDFREQ_P0_SHIFT 24 -+#define VIDFREQ_P0_CSCLK_MASK 0x00f00000 -+#define VIDFREQ_P0_CSCLK_SHIFT 20 -+#define VIDFREQ_P0_CRCLK_MASK 0x000f0000 -+#define VIDFREQ_P0_CRCLK_SHIFT 16 -+#define VIDFREQ_P1_MASK 0x00001f00 -+#define VIDFREQ_P1_SHIFT 8 -+#define VIDFREQ_P1_CSCLK_MASK 0x000000f0 -+#define VIDFREQ_P1_CSCLK_SHIFT 4 -+#define VIDFREQ_P1_CRCLK_MASK 0x0000000f -+#define INTTOEXT_BASE_ILK _MMIO(0x11300) -+#define INTTOEXT_BASE _MMIO(0x11120) /* INTTOEXT1-8 (0x1113c) */ -+#define INTTOEXT_MAP3_SHIFT 24 -+#define INTTOEXT_MAP3_MASK (0x1f << INTTOEXT_MAP3_SHIFT) -+#define INTTOEXT_MAP2_SHIFT 16 -+#define INTTOEXT_MAP2_MASK (0x1f << INTTOEXT_MAP2_SHIFT) -+#define INTTOEXT_MAP1_SHIFT 8 -+#define INTTOEXT_MAP1_MASK (0x1f << INTTOEXT_MAP1_SHIFT) -+#define INTTOEXT_MAP0_SHIFT 0 -+#define INTTOEXT_MAP0_MASK (0x1f << INTTOEXT_MAP0_SHIFT) -+#define MEMSWCTL _MMIO(0x11170) /* Ironlake only */ -+#define MEMCTL_CMD_MASK 0xe000 -+#define MEMCTL_CMD_SHIFT 13 -+#define MEMCTL_CMD_RCLK_OFF 0 -+#define MEMCTL_CMD_RCLK_ON 1 -+#define MEMCTL_CMD_CHFREQ 2 -+#define MEMCTL_CMD_CHVID 3 -+#define MEMCTL_CMD_VMMOFF 4 -+#define MEMCTL_CMD_VMMON 5 -+#define MEMCTL_CMD_STS (1 << 12) /* write 1 triggers command, clears -+ when command complete */ -+#define MEMCTL_FREQ_MASK 0x0f00 /* jitter, from 0-15 */ -+#define MEMCTL_FREQ_SHIFT 8 -+#define MEMCTL_SFCAVM (1 << 7) -+#define MEMCTL_TGT_VID_MASK 0x007f -+#define MEMIHYST _MMIO(0x1117c) -+#define MEMINTREN _MMIO(0x11180) /* 16 bits */ -+#define MEMINT_RSEXIT_EN (1 << 8) -+#define MEMINT_CX_SUPR_EN (1 << 7) -+#define MEMINT_CONT_BUSY_EN (1 << 6) -+#define MEMINT_AVG_BUSY_EN (1 << 5) -+#define MEMINT_EVAL_CHG_EN (1 << 4) -+#define MEMINT_MON_IDLE_EN (1 << 3) -+#define MEMINT_UP_EVAL_EN (1 << 2) -+#define MEMINT_DOWN_EVAL_EN (1 << 1) -+#define MEMINT_SW_CMD_EN (1 << 0) -+#define MEMINTRSTR _MMIO(0x11182) /* 16 bits */ -+#define MEM_RSEXIT_MASK 0xc000 -+#define MEM_RSEXIT_SHIFT 14 -+#define MEM_CONT_BUSY_MASK 0x3000 -+#define MEM_CONT_BUSY_SHIFT 12 -+#define MEM_AVG_BUSY_MASK 0x0c00 -+#define MEM_AVG_BUSY_SHIFT 10 -+#define MEM_EVAL_CHG_MASK 0x0300 -+#define MEM_EVAL_BUSY_SHIFT 8 -+#define MEM_MON_IDLE_MASK 0x00c0 -+#define MEM_MON_IDLE_SHIFT 6 -+#define MEM_UP_EVAL_MASK 0x0030 -+#define MEM_UP_EVAL_SHIFT 4 -+#define MEM_DOWN_EVAL_MASK 0x000c -+#define MEM_DOWN_EVAL_SHIFT 2 -+#define MEM_SW_CMD_MASK 0x0003 -+#define MEM_INT_STEER_GFX 0 -+#define MEM_INT_STEER_CMR 1 -+#define MEM_INT_STEER_SMI 2 -+#define MEM_INT_STEER_SCI 3 -+#define MEMINTRSTS _MMIO(0x11184) -+#define MEMINT_RSEXIT (1 << 7) -+#define MEMINT_CONT_BUSY (1 << 6) -+#define MEMINT_AVG_BUSY (1 << 5) -+#define MEMINT_EVAL_CHG (1 << 4) -+#define MEMINT_MON_IDLE (1 << 3) -+#define MEMINT_UP_EVAL (1 << 2) -+#define MEMINT_DOWN_EVAL (1 << 1) -+#define MEMINT_SW_CMD (1 << 0) -+#define MEMMODECTL _MMIO(0x11190) -+#define MEMMODE_BOOST_EN (1 << 31) -+#define MEMMODE_BOOST_FREQ_MASK 0x0f000000 /* jitter for boost, 0-15 */ -+#define MEMMODE_BOOST_FREQ_SHIFT 24 -+#define MEMMODE_IDLE_MODE_MASK 0x00030000 -+#define MEMMODE_IDLE_MODE_SHIFT 16 -+#define MEMMODE_IDLE_MODE_EVAL 0 -+#define MEMMODE_IDLE_MODE_CONT 1 -+#define MEMMODE_HWIDLE_EN (1 << 15) -+#define MEMMODE_SWMODE_EN (1 << 14) -+#define MEMMODE_RCLK_GATE (1 << 13) -+#define MEMMODE_HW_UPDATE (1 << 12) -+#define MEMMODE_FSTART_MASK 0x00000f00 /* starting jitter, 0-15 */ -+#define MEMMODE_FSTART_SHIFT 8 -+#define MEMMODE_FMAX_MASK 0x000000f0 /* max jitter, 0-15 */ -+#define MEMMODE_FMAX_SHIFT 4 -+#define MEMMODE_FMIN_MASK 0x0000000f /* min jitter, 0-15 */ -+#define RCBMAXAVG _MMIO(0x1119c) -+#define MEMSWCTL2 _MMIO(0x1119e) /* Cantiga only */ -+#define SWMEMCMD_RENDER_OFF (0 << 13) -+#define SWMEMCMD_RENDER_ON (1 << 13) -+#define SWMEMCMD_SWFREQ (2 << 13) -+#define SWMEMCMD_TARVID (3 << 13) -+#define SWMEMCMD_VRM_OFF (4 << 13) -+#define SWMEMCMD_VRM_ON (5 << 13) -+#define CMDSTS (1 << 12) -+#define SFCAVM (1 << 11) -+#define SWFREQ_MASK 0x0380 /* P0-7 */ -+#define SWFREQ_SHIFT 7 -+#define TARVID_MASK 0x001f -+#define MEMSTAT_CTG _MMIO(0x111a0) -+#define RCBMINAVG _MMIO(0x111a0) -+#define RCUPEI _MMIO(0x111b0) -+#define RCDNEI _MMIO(0x111b4) -+#define RSTDBYCTL _MMIO(0x111b8) -+#define RS1EN (1 << 31) -+#define RS2EN (1 << 30) -+#define RS3EN (1 << 29) -+#define D3RS3EN (1 << 28) /* Display D3 imlies RS3 */ -+#define SWPROMORSX (1 << 27) /* RSx promotion timers ignored */ -+#define RCWAKERW (1 << 26) /* Resetwarn from PCH causes wakeup */ -+#define DPRSLPVREN (1 << 25) /* Fast voltage ramp enable */ -+#define GFXTGHYST (1 << 24) /* Hysteresis to allow trunk gating */ -+#define RCX_SW_EXIT (1 << 23) /* Leave RSx and prevent re-entry */ -+#define RSX_STATUS_MASK (7 << 20) -+#define RSX_STATUS_ON (0 << 20) -+#define RSX_STATUS_RC1 (1 << 20) -+#define RSX_STATUS_RC1E (2 << 20) -+#define RSX_STATUS_RS1 (3 << 20) -+#define RSX_STATUS_RS2 (4 << 20) /* aka rc6 */ -+#define RSX_STATUS_RSVD (5 << 20) /* deep rc6 unsupported on ilk */ -+#define RSX_STATUS_RS3 (6 << 20) /* rs3 unsupported on ilk */ -+#define RSX_STATUS_RSVD2 (7 << 20) -+#define UWRCRSXE (1 << 19) /* wake counter limit prevents rsx */ -+#define RSCRP (1 << 18) /* rs requests control on rs1/2 reqs */ -+#define JRSC (1 << 17) /* rsx coupled to cpu c-state */ -+#define RS2INC0 (1 << 16) /* allow rs2 in cpu c0 */ -+#define RS1CONTSAV_MASK (3 << 14) -+#define RS1CONTSAV_NO_RS1 (0 << 14) /* rs1 doesn't save/restore context */ -+#define RS1CONTSAV_RSVD (1 << 14) -+#define RS1CONTSAV_SAVE_RS1 (2 << 14) /* rs1 saves context */ -+#define RS1CONTSAV_FULL_RS1 (3 << 14) /* rs1 saves and restores context */ -+#define NORMSLEXLAT_MASK (3 << 12) -+#define SLOW_RS123 (0 << 12) -+#define SLOW_RS23 (1 << 12) -+#define SLOW_RS3 (2 << 12) -+#define NORMAL_RS123 (3 << 12) -+#define RCMODE_TIMEOUT (1 << 11) /* 0 is eval interval method */ -+#define IMPROMOEN (1 << 10) /* promo is immediate or delayed until next idle interval (only for timeout method above) */ -+#define RCENTSYNC (1 << 9) /* rs coupled to cpu c-state (3/6/7) */ -+#define STATELOCK (1 << 7) /* locked to rs_cstate if 0 */ -+#define RS_CSTATE_MASK (3 << 4) -+#define RS_CSTATE_C367_RS1 (0 << 4) -+#define RS_CSTATE_C36_RS1_C7_RS2 (1 << 4) -+#define RS_CSTATE_RSVD (2 << 4) -+#define RS_CSTATE_C367_RS2 (3 << 4) -+#define REDSAVES (1 << 3) /* no context save if was idle during rs0 */ -+#define REDRESTORES (1 << 2) /* no restore if was idle during rs0 */ -+#define VIDCTL _MMIO(0x111c0) -+#define VIDSTS _MMIO(0x111c8) -+#define VIDSTART _MMIO(0x111cc) /* 8 bits */ -+#define MEMSTAT_ILK _MMIO(0x111f8) -+#define MEMSTAT_VID_MASK 0x7f00 -+#define MEMSTAT_VID_SHIFT 8 -+#define MEMSTAT_PSTATE_MASK 0x00f8 -+#define MEMSTAT_PSTATE_SHIFT 3 -+#define MEMSTAT_MON_ACTV (1 << 2) -+#define MEMSTAT_SRC_CTL_MASK 0x0003 -+#define MEMSTAT_SRC_CTL_CORE 0 -+#define MEMSTAT_SRC_CTL_TRB 1 -+#define MEMSTAT_SRC_CTL_THM 2 -+#define MEMSTAT_SRC_CTL_STDBY 3 -+#define RCPREVBSYTUPAVG _MMIO(0x113b8) -+#define RCPREVBSYTDNAVG _MMIO(0x113bc) -+#define PMMISC _MMIO(0x11214) -+#define MCPPCE_EN (1 << 0) /* enable PM_MSG from PCH->MPC */ -+#define SDEW _MMIO(0x1124c) -+#define CSIEW0 _MMIO(0x11250) -+#define CSIEW1 _MMIO(0x11254) -+#define CSIEW2 _MMIO(0x11258) -+#define PEW(i) _MMIO(0x1125c + (i) * 4) /* 5 registers */ -+#define DEW(i) _MMIO(0x11270 + (i) * 4) /* 3 registers */ -+#define MCHAFE _MMIO(0x112c0) -+#define CSIEC _MMIO(0x112e0) -+#define DMIEC _MMIO(0x112e4) -+#define DDREC _MMIO(0x112e8) -+#define PEG0EC _MMIO(0x112ec) -+#define PEG1EC _MMIO(0x112f0) -+#define GFXEC _MMIO(0x112f4) -+#define RPPREVBSYTUPAVG _MMIO(0x113b8) -+#define RPPREVBSYTDNAVG _MMIO(0x113bc) -+#define ECR _MMIO(0x11600) -+#define ECR_GPFE (1 << 31) -+#define ECR_IMONE (1 << 30) -+#define ECR_CAP_MASK 0x0000001f /* Event range, 0-31 */ -+#define OGW0 _MMIO(0x11608) -+#define OGW1 _MMIO(0x1160c) -+#define EG0 _MMIO(0x11610) -+#define EG1 _MMIO(0x11614) -+#define EG2 _MMIO(0x11618) -+#define EG3 _MMIO(0x1161c) -+#define EG4 _MMIO(0x11620) -+#define EG5 _MMIO(0x11624) -+#define EG6 _MMIO(0x11628) -+#define EG7 _MMIO(0x1162c) -+#define PXW(i) _MMIO(0x11664 + (i) * 4) /* 4 registers */ -+#define PXWL(i) _MMIO(0x11680 + (i) * 8) /* 8 registers */ -+#define LCFUSE02 _MMIO(0x116c0) -+#define LCFUSE_HIV_MASK 0x000000ff -+#define CSIPLL0 _MMIO(0x12c10) -+#define DDRMPLL1 _MMIO(0X12c20) -+#define PEG_BAND_GAP_DATA _MMIO(0x14d68) -+ -+#define GEN6_GT_THREAD_STATUS_REG _MMIO(0x13805c) -+#define GEN6_GT_THREAD_STATUS_CORE_MASK 0x7 -+ -+#define GEN6_GT_PERF_STATUS _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x5948) -+#define BXT_GT_PERF_STATUS _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x7070) -+#define GEN6_RP_STATE_LIMITS _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x5994) -+#define GEN6_RP_STATE_CAP _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x5998) -+#define BXT_RP_STATE_CAP _MMIO(0x138170) -+ -+/* -+ * Make these a multiple of magic 25 to avoid SNB (eg. Dell XPS -+ * 8300) freezing up around GPU hangs. Looks as if even -+ * scheduling/timer interrupts start misbehaving if the RPS -+ * EI/thresholds are "bad", leading to a very sluggish or even -+ * frozen machine. -+ */ -+#define INTERVAL_1_28_US(us) roundup(((us) * 100) >> 7, 25) -+#define INTERVAL_1_33_US(us) (((us) * 3) >> 2) -+#define INTERVAL_0_833_US(us) (((us) * 6) / 5) -+#define GT_INTERVAL_FROM_US(dev_priv, us) (INTEL_GEN(dev_priv) >= 9 ? \ -+ (IS_GEN9_LP(dev_priv) ? \ -+ INTERVAL_0_833_US(us) : \ -+ INTERVAL_1_33_US(us)) : \ -+ INTERVAL_1_28_US(us)) -+ -+#define INTERVAL_1_28_TO_US(interval) (((interval) << 7) / 100) -+#define INTERVAL_1_33_TO_US(interval) (((interval) << 2) / 3) -+#define INTERVAL_0_833_TO_US(interval) (((interval) * 5) / 6) -+#define GT_PM_INTERVAL_TO_US(dev_priv, interval) (INTEL_GEN(dev_priv) >= 9 ? \ -+ (IS_GEN9_LP(dev_priv) ? \ -+ INTERVAL_0_833_TO_US(interval) : \ -+ INTERVAL_1_33_TO_US(interval)) : \ -+ INTERVAL_1_28_TO_US(interval)) -+ -+/* -+ * Logical Context regs -+ */ -+#define CCID(base) _MMIO((base) + 0x180) -+#define CCID_EN BIT(0) -+#define CCID_EXTENDED_STATE_RESTORE BIT(2) -+#define CCID_EXTENDED_STATE_SAVE BIT(3) -+/* -+ * Notes on SNB/IVB/VLV context size: -+ * - Power context is saved elsewhere (LLC or stolen) -+ * - Ring/execlist context is saved on SNB, not on IVB -+ * - Extended context size already includes render context size -+ * - We always need to follow the extended context size. -+ * SNB BSpec has comments indicating that we should use the -+ * render context size instead if execlists are disabled, but -+ * based on empirical testing that's just nonsense. -+ * - Pipelined/VF state is saved on SNB/IVB respectively -+ * - GT1 size just indicates how much of render context -+ * doesn't need saving on GT1 -+ */ -+#define CXT_SIZE _MMIO(0x21a0) -+#define GEN6_CXT_POWER_SIZE(cxt_reg) (((cxt_reg) >> 24) & 0x3f) -+#define GEN6_CXT_RING_SIZE(cxt_reg) (((cxt_reg) >> 18) & 0x3f) -+#define GEN6_CXT_RENDER_SIZE(cxt_reg) (((cxt_reg) >> 12) & 0x3f) -+#define GEN6_CXT_EXTENDED_SIZE(cxt_reg) (((cxt_reg) >> 6) & 0x3f) -+#define GEN6_CXT_PIPELINE_SIZE(cxt_reg) (((cxt_reg) >> 0) & 0x3f) -+#define GEN6_CXT_TOTAL_SIZE(cxt_reg) (GEN6_CXT_RING_SIZE(cxt_reg) + \ -+ GEN6_CXT_EXTENDED_SIZE(cxt_reg) + \ -+ GEN6_CXT_PIPELINE_SIZE(cxt_reg)) -+#define GEN7_CXT_SIZE _MMIO(0x21a8) -+#define GEN7_CXT_POWER_SIZE(ctx_reg) (((ctx_reg) >> 25) & 0x7f) -+#define GEN7_CXT_RING_SIZE(ctx_reg) (((ctx_reg) >> 22) & 0x7) -+#define GEN7_CXT_RENDER_SIZE(ctx_reg) (((ctx_reg) >> 16) & 0x3f) -+#define GEN7_CXT_EXTENDED_SIZE(ctx_reg) (((ctx_reg) >> 9) & 0x7f) -+#define GEN7_CXT_GT1_SIZE(ctx_reg) (((ctx_reg) >> 6) & 0x7) -+#define GEN7_CXT_VFSTATE_SIZE(ctx_reg) (((ctx_reg) >> 0) & 0x3f) -+#define GEN7_CXT_TOTAL_SIZE(ctx_reg) (GEN7_CXT_EXTENDED_SIZE(ctx_reg) + \ -+ GEN7_CXT_VFSTATE_SIZE(ctx_reg)) -+ -+enum { -+ INTEL_ADVANCED_CONTEXT = 0, -+ INTEL_LEGACY_32B_CONTEXT, -+ INTEL_ADVANCED_AD_CONTEXT, -+ INTEL_LEGACY_64B_CONTEXT -+}; -+ -+enum { -+ FAULT_AND_HANG = 0, -+ FAULT_AND_HALT, /* Debug only */ -+ FAULT_AND_STREAM, -+ FAULT_AND_CONTINUE /* Unsupported */ -+}; -+ -+#define GEN8_CTX_VALID (1 << 0) -+#define GEN8_CTX_FORCE_PD_RESTORE (1 << 1) -+#define GEN8_CTX_FORCE_RESTORE (1 << 2) -+#define GEN8_CTX_L3LLC_COHERENT (1 << 5) -+#define GEN8_CTX_PRIVILEGE (1 << 8) -+#define GEN8_CTX_ADDRESSING_MODE_SHIFT 3 -+ -+#define GEN8_CTX_ID_SHIFT 32 -+#define GEN8_CTX_ID_WIDTH 21 -+#define GEN11_SW_CTX_ID_SHIFT 37 -+#define GEN11_SW_CTX_ID_WIDTH 11 -+#define GEN11_ENGINE_CLASS_SHIFT 61 -+#define GEN11_ENGINE_CLASS_WIDTH 3 -+#define GEN11_ENGINE_INSTANCE_SHIFT 48 -+#define GEN11_ENGINE_INSTANCE_WIDTH 6 -+ -+#define CHV_CLK_CTL1 _MMIO(0x101100) -+#define VLV_CLK_CTL2 _MMIO(0x101104) -+#define CLK_CTL2_CZCOUNT_30NS_SHIFT 28 -+ -+/* -+ * Overlay regs -+ */ -+ -+#define OVADD _MMIO(0x30000) -+#define DOVSTA _MMIO(0x30008) -+#define OC_BUF (0x3 << 20) -+#define OGAMC5 _MMIO(0x30010) -+#define OGAMC4 _MMIO(0x30014) -+#define OGAMC3 _MMIO(0x30018) -+#define OGAMC2 _MMIO(0x3001c) -+#define OGAMC1 _MMIO(0x30020) -+#define OGAMC0 _MMIO(0x30024) -+ -+/* -+ * GEN9 clock gating regs -+ */ -+#define GEN9_CLKGATE_DIS_0 _MMIO(0x46530) -+#define DARBF_GATING_DIS (1 << 27) -+#define PWM2_GATING_DIS (1 << 14) -+#define PWM1_GATING_DIS (1 << 13) -+ -+#define GEN9_CLKGATE_DIS_4 _MMIO(0x4653C) -+#define BXT_GMBUS_GATING_DIS (1 << 14) -+ -+#define _CLKGATE_DIS_PSL_A 0x46520 -+#define _CLKGATE_DIS_PSL_B 0x46524 -+#define _CLKGATE_DIS_PSL_C 0x46528 -+#define DUPS1_GATING_DIS (1 << 15) -+#define DUPS2_GATING_DIS (1 << 19) -+#define DUPS3_GATING_DIS (1 << 23) -+#define DPF_GATING_DIS (1 << 10) -+#define DPF_RAM_GATING_DIS (1 << 9) -+#define DPFR_GATING_DIS (1 << 8) -+ -+#define CLKGATE_DIS_PSL(pipe) \ -+ _MMIO_PIPE(pipe, _CLKGATE_DIS_PSL_A, _CLKGATE_DIS_PSL_B) -+ -+/* -+ * GEN10 clock gating regs -+ */ -+#define SLICE_UNIT_LEVEL_CLKGATE _MMIO(0x94d4) -+#define SARBUNIT_CLKGATE_DIS (1 << 5) -+#define RCCUNIT_CLKGATE_DIS (1 << 7) -+#define MSCUNIT_CLKGATE_DIS (1 << 10) -+ -+#define SUBSLICE_UNIT_LEVEL_CLKGATE _MMIO(0x9524) -+#define GWUNIT_CLKGATE_DIS (1 << 16) -+ -+#define UNSLICE_UNIT_LEVEL_CLKGATE _MMIO(0x9434) -+#define VFUNIT_CLKGATE_DIS (1 << 20) -+ -+#define INF_UNIT_LEVEL_CLKGATE _MMIO(0x9560) -+#define CGPSF_CLKGATE_DIS (1 << 3) -+ -+/* -+ * Display engine regs -+ */ -+ -+/* Pipe A CRC regs */ -+#define _PIPE_CRC_CTL_A 0x60050 -+#define PIPE_CRC_ENABLE (1 << 31) -+/* skl+ source selection */ -+#define PIPE_CRC_SOURCE_PLANE_1_SKL (0 << 28) -+#define PIPE_CRC_SOURCE_PLANE_2_SKL (2 << 28) -+#define PIPE_CRC_SOURCE_DMUX_SKL (4 << 28) -+#define PIPE_CRC_SOURCE_PLANE_3_SKL (6 << 28) -+#define PIPE_CRC_SOURCE_PLANE_4_SKL (7 << 28) -+#define PIPE_CRC_SOURCE_PLANE_5_SKL (5 << 28) -+#define PIPE_CRC_SOURCE_PLANE_6_SKL (3 << 28) -+#define PIPE_CRC_SOURCE_PLANE_7_SKL (1 << 28) -+/* ivb+ source selection */ -+#define PIPE_CRC_SOURCE_PRIMARY_IVB (0 << 29) -+#define PIPE_CRC_SOURCE_SPRITE_IVB (1 << 29) -+#define PIPE_CRC_SOURCE_PF_IVB (2 << 29) -+/* ilk+ source selection */ -+#define PIPE_CRC_SOURCE_PRIMARY_ILK (0 << 28) -+#define PIPE_CRC_SOURCE_SPRITE_ILK (1 << 28) -+#define PIPE_CRC_SOURCE_PIPE_ILK (2 << 28) -+/* embedded DP port on the north display block, reserved on ivb */ -+#define PIPE_CRC_SOURCE_PORT_A_ILK (4 << 28) -+#define PIPE_CRC_SOURCE_FDI_ILK (5 << 28) /* reserved on ivb */ -+/* vlv source selection */ -+#define PIPE_CRC_SOURCE_PIPE_VLV (0 << 27) -+#define PIPE_CRC_SOURCE_HDMIB_VLV (1 << 27) -+#define PIPE_CRC_SOURCE_HDMIC_VLV (2 << 27) -+/* with DP port the pipe source is invalid */ -+#define PIPE_CRC_SOURCE_DP_D_VLV (3 << 27) -+#define PIPE_CRC_SOURCE_DP_B_VLV (6 << 27) -+#define PIPE_CRC_SOURCE_DP_C_VLV (7 << 27) -+/* gen3+ source selection */ -+#define PIPE_CRC_SOURCE_PIPE_I9XX (0 << 28) -+#define PIPE_CRC_SOURCE_SDVOB_I9XX (1 << 28) -+#define PIPE_CRC_SOURCE_SDVOC_I9XX (2 << 28) -+/* with DP/TV port the pipe source is invalid */ -+#define PIPE_CRC_SOURCE_DP_D_G4X (3 << 28) -+#define PIPE_CRC_SOURCE_TV_PRE (4 << 28) -+#define PIPE_CRC_SOURCE_TV_POST (5 << 28) -+#define PIPE_CRC_SOURCE_DP_B_G4X (6 << 28) -+#define PIPE_CRC_SOURCE_DP_C_G4X (7 << 28) -+/* gen2 doesn't have source selection bits */ -+#define PIPE_CRC_INCLUDE_BORDER_I8XX (1 << 30) -+ -+#define _PIPE_CRC_RES_1_A_IVB 0x60064 -+#define _PIPE_CRC_RES_2_A_IVB 0x60068 -+#define _PIPE_CRC_RES_3_A_IVB 0x6006c -+#define _PIPE_CRC_RES_4_A_IVB 0x60070 -+#define _PIPE_CRC_RES_5_A_IVB 0x60074 -+ -+#define _PIPE_CRC_RES_RED_A 0x60060 -+#define _PIPE_CRC_RES_GREEN_A 0x60064 -+#define _PIPE_CRC_RES_BLUE_A 0x60068 -+#define _PIPE_CRC_RES_RES1_A_I915 0x6006c -+#define _PIPE_CRC_RES_RES2_A_G4X 0x60080 -+ -+/* Pipe B CRC regs */ -+#define _PIPE_CRC_RES_1_B_IVB 0x61064 -+#define _PIPE_CRC_RES_2_B_IVB 0x61068 -+#define _PIPE_CRC_RES_3_B_IVB 0x6106c -+#define _PIPE_CRC_RES_4_B_IVB 0x61070 -+#define _PIPE_CRC_RES_5_B_IVB 0x61074 -+ -+#define PIPE_CRC_CTL(pipe) _MMIO_TRANS2(pipe, _PIPE_CRC_CTL_A) -+#define PIPE_CRC_RES_1_IVB(pipe) _MMIO_TRANS2(pipe, _PIPE_CRC_RES_1_A_IVB) -+#define PIPE_CRC_RES_2_IVB(pipe) _MMIO_TRANS2(pipe, _PIPE_CRC_RES_2_A_IVB) -+#define PIPE_CRC_RES_3_IVB(pipe) _MMIO_TRANS2(pipe, _PIPE_CRC_RES_3_A_IVB) -+#define PIPE_CRC_RES_4_IVB(pipe) _MMIO_TRANS2(pipe, _PIPE_CRC_RES_4_A_IVB) -+#define PIPE_CRC_RES_5_IVB(pipe) _MMIO_TRANS2(pipe, _PIPE_CRC_RES_5_A_IVB) -+ -+#define PIPE_CRC_RES_RED(pipe) _MMIO_TRANS2(pipe, _PIPE_CRC_RES_RED_A) -+#define PIPE_CRC_RES_GREEN(pipe) _MMIO_TRANS2(pipe, _PIPE_CRC_RES_GREEN_A) -+#define PIPE_CRC_RES_BLUE(pipe) _MMIO_TRANS2(pipe, _PIPE_CRC_RES_BLUE_A) -+#define PIPE_CRC_RES_RES1_I915(pipe) _MMIO_TRANS2(pipe, _PIPE_CRC_RES_RES1_A_I915) -+#define PIPE_CRC_RES_RES2_G4X(pipe) _MMIO_TRANS2(pipe, _PIPE_CRC_RES_RES2_A_G4X) -+ -+/* Pipe A timing regs */ -+#define _HTOTAL_A 0x60000 -+#define _HBLANK_A 0x60004 -+#define _HSYNC_A 0x60008 -+#define _VTOTAL_A 0x6000c -+#define _VBLANK_A 0x60010 -+#define _VSYNC_A 0x60014 -+#define _PIPEASRC 0x6001c -+#define _BCLRPAT_A 0x60020 -+#define _VSYNCSHIFT_A 0x60028 -+#define _PIPE_MULT_A 0x6002c -+ -+/* Pipe B timing regs */ -+#define _HTOTAL_B 0x61000 -+#define _HBLANK_B 0x61004 -+#define _HSYNC_B 0x61008 -+#define _VTOTAL_B 0x6100c -+#define _VBLANK_B 0x61010 -+#define _VSYNC_B 0x61014 -+#define _PIPEBSRC 0x6101c -+#define _BCLRPAT_B 0x61020 -+#define _VSYNCSHIFT_B 0x61028 -+#define _PIPE_MULT_B 0x6102c -+ -+/* DSI 0 timing regs */ -+#define _HTOTAL_DSI0 0x6b000 -+#define _HSYNC_DSI0 0x6b008 -+#define _VTOTAL_DSI0 0x6b00c -+#define _VSYNC_DSI0 0x6b014 -+#define _VSYNCSHIFT_DSI0 0x6b028 -+ -+/* DSI 1 timing regs */ -+#define _HTOTAL_DSI1 0x6b800 -+#define _HSYNC_DSI1 0x6b808 -+#define _VTOTAL_DSI1 0x6b80c -+#define _VSYNC_DSI1 0x6b814 -+#define _VSYNCSHIFT_DSI1 0x6b828 -+ -+#define TRANSCODER_A_OFFSET 0x60000 -+#define TRANSCODER_B_OFFSET 0x61000 -+#define TRANSCODER_C_OFFSET 0x62000 -+#define CHV_TRANSCODER_C_OFFSET 0x63000 -+#define TRANSCODER_EDP_OFFSET 0x6f000 -+#define TRANSCODER_DSI0_OFFSET 0x6b000 -+#define TRANSCODER_DSI1_OFFSET 0x6b800 -+ -+#define HTOTAL(trans) _MMIO_TRANS2(trans, _HTOTAL_A) -+#define HBLANK(trans) _MMIO_TRANS2(trans, _HBLANK_A) -+#define HSYNC(trans) _MMIO_TRANS2(trans, _HSYNC_A) -+#define VTOTAL(trans) _MMIO_TRANS2(trans, _VTOTAL_A) -+#define VBLANK(trans) _MMIO_TRANS2(trans, _VBLANK_A) -+#define VSYNC(trans) _MMIO_TRANS2(trans, _VSYNC_A) -+#define BCLRPAT(trans) _MMIO_TRANS2(trans, _BCLRPAT_A) -+#define VSYNCSHIFT(trans) _MMIO_TRANS2(trans, _VSYNCSHIFT_A) -+#define PIPESRC(trans) _MMIO_TRANS2(trans, _PIPEASRC) -+#define PIPE_MULT(trans) _MMIO_TRANS2(trans, _PIPE_MULT_A) -+ -+/* HSW+ eDP PSR registers */ -+#define HSW_EDP_PSR_BASE 0x64800 -+#define BDW_EDP_PSR_BASE 0x6f800 -+#define EDP_PSR_CTL _MMIO(dev_priv->psr_mmio_base + 0) -+#define EDP_PSR_ENABLE (1 << 31) -+#define BDW_PSR_SINGLE_FRAME (1 << 30) -+#define EDP_PSR_RESTORE_PSR_ACTIVE_CTX_MASK (1 << 29) /* SW can't modify */ -+#define EDP_PSR_LINK_STANDBY (1 << 27) -+#define EDP_PSR_MIN_LINK_ENTRY_TIME_MASK (3 << 25) -+#define EDP_PSR_MIN_LINK_ENTRY_TIME_8_LINES (0 << 25) -+#define EDP_PSR_MIN_LINK_ENTRY_TIME_4_LINES (1 << 25) -+#define EDP_PSR_MIN_LINK_ENTRY_TIME_2_LINES (2 << 25) -+#define EDP_PSR_MIN_LINK_ENTRY_TIME_0_LINES (3 << 25) -+#define EDP_PSR_MAX_SLEEP_TIME_SHIFT 20 -+#define EDP_PSR_SKIP_AUX_EXIT (1 << 12) -+#define EDP_PSR_TP1_TP2_SEL (0 << 11) -+#define EDP_PSR_TP1_TP3_SEL (1 << 11) -+#define EDP_PSR_CRC_ENABLE (1 << 10) /* BDW+ */ -+#define EDP_PSR_TP2_TP3_TIME_500us (0 << 8) -+#define EDP_PSR_TP2_TP3_TIME_100us (1 << 8) -+#define EDP_PSR_TP2_TP3_TIME_2500us (2 << 8) -+#define EDP_PSR_TP2_TP3_TIME_0us (3 << 8) -+#define EDP_PSR_TP4_TIME_0US (3 << 6) /* ICL+ */ -+#define EDP_PSR_TP1_TIME_500us (0 << 4) -+#define EDP_PSR_TP1_TIME_100us (1 << 4) -+#define EDP_PSR_TP1_TIME_2500us (2 << 4) -+#define EDP_PSR_TP1_TIME_0us (3 << 4) -+#define EDP_PSR_IDLE_FRAME_SHIFT 0 -+ -+/* Bspec claims those aren't shifted but stay at 0x64800 */ -+#define EDP_PSR_IMR _MMIO(0x64834) -+#define EDP_PSR_IIR _MMIO(0x64838) -+#define EDP_PSR_ERROR(shift) (1 << ((shift) + 2)) -+#define EDP_PSR_POST_EXIT(shift) (1 << ((shift) + 1)) -+#define EDP_PSR_PRE_ENTRY(shift) (1 << (shift)) -+#define EDP_PSR_TRANSCODER_C_SHIFT 24 -+#define EDP_PSR_TRANSCODER_B_SHIFT 16 -+#define EDP_PSR_TRANSCODER_A_SHIFT 8 -+#define EDP_PSR_TRANSCODER_EDP_SHIFT 0 -+ -+#define EDP_PSR_AUX_CTL _MMIO(dev_priv->psr_mmio_base + 0x10) -+#define EDP_PSR_AUX_CTL_TIME_OUT_MASK (3 << 26) -+#define EDP_PSR_AUX_CTL_MESSAGE_SIZE_MASK (0x1f << 20) -+#define EDP_PSR_AUX_CTL_PRECHARGE_2US_MASK (0xf << 16) -+#define EDP_PSR_AUX_CTL_ERROR_INTERRUPT (1 << 11) -+#define EDP_PSR_AUX_CTL_BIT_CLOCK_2X_MASK (0x7ff) -+ -+#define EDP_PSR_AUX_DATA(i) _MMIO(dev_priv->psr_mmio_base + 0x14 + (i) * 4) /* 5 registers */ -+ -+#define EDP_PSR_STATUS _MMIO(dev_priv->psr_mmio_base + 0x40) -+#define EDP_PSR_STATUS_STATE_MASK (7 << 29) -+#define EDP_PSR_STATUS_STATE_SHIFT 29 -+#define EDP_PSR_STATUS_STATE_IDLE (0 << 29) -+#define EDP_PSR_STATUS_STATE_SRDONACK (1 << 29) -+#define EDP_PSR_STATUS_STATE_SRDENT (2 << 29) -+#define EDP_PSR_STATUS_STATE_BUFOFF (3 << 29) -+#define EDP_PSR_STATUS_STATE_BUFON (4 << 29) -+#define EDP_PSR_STATUS_STATE_AUXACK (5 << 29) -+#define EDP_PSR_STATUS_STATE_SRDOFFACK (6 << 29) -+#define EDP_PSR_STATUS_LINK_MASK (3 << 26) -+#define EDP_PSR_STATUS_LINK_FULL_OFF (0 << 26) -+#define EDP_PSR_STATUS_LINK_FULL_ON (1 << 26) -+#define EDP_PSR_STATUS_LINK_STANDBY (2 << 26) -+#define EDP_PSR_STATUS_MAX_SLEEP_TIMER_SHIFT 20 -+#define EDP_PSR_STATUS_MAX_SLEEP_TIMER_MASK 0x1f -+#define EDP_PSR_STATUS_COUNT_SHIFT 16 -+#define EDP_PSR_STATUS_COUNT_MASK 0xf -+#define EDP_PSR_STATUS_AUX_ERROR (1 << 15) -+#define EDP_PSR_STATUS_AUX_SENDING (1 << 12) -+#define EDP_PSR_STATUS_SENDING_IDLE (1 << 9) -+#define EDP_PSR_STATUS_SENDING_TP2_TP3 (1 << 8) -+#define EDP_PSR_STATUS_SENDING_TP1 (1 << 4) -+#define EDP_PSR_STATUS_IDLE_MASK 0xf -+ -+#define EDP_PSR_PERF_CNT _MMIO(dev_priv->psr_mmio_base + 0x44) -+#define EDP_PSR_PERF_CNT_MASK 0xffffff -+ -+#define EDP_PSR_DEBUG _MMIO(dev_priv->psr_mmio_base + 0x60) /* PSR_MASK on SKL+ */ -+#define EDP_PSR_DEBUG_MASK_MAX_SLEEP (1 << 28) -+#define EDP_PSR_DEBUG_MASK_LPSP (1 << 27) -+#define EDP_PSR_DEBUG_MASK_MEMUP (1 << 26) -+#define EDP_PSR_DEBUG_MASK_HPD (1 << 25) -+#define EDP_PSR_DEBUG_MASK_DISP_REG_WRITE (1 << 16) /* Reserved in ICL+ */ -+#define EDP_PSR_DEBUG_EXIT_ON_PIXEL_UNDERRUN (1 << 15) /* SKL+ */ -+ -+#define EDP_PSR2_CTL _MMIO(0x6f900) -+#define EDP_PSR2_ENABLE (1 << 31) -+#define EDP_SU_TRACK_ENABLE (1 << 30) -+#define EDP_Y_COORDINATE_VALID (1 << 26) /* GLK and CNL+ */ -+#define EDP_Y_COORDINATE_ENABLE (1 << 25) /* GLK and CNL+ */ -+#define EDP_MAX_SU_DISABLE_TIME(t) ((t) << 20) -+#define EDP_MAX_SU_DISABLE_TIME_MASK (0x1f << 20) -+#define EDP_PSR2_TP2_TIME_500us (0 << 8) -+#define EDP_PSR2_TP2_TIME_100us (1 << 8) -+#define EDP_PSR2_TP2_TIME_2500us (2 << 8) -+#define EDP_PSR2_TP2_TIME_50us (3 << 8) -+#define EDP_PSR2_TP2_TIME_MASK (3 << 8) -+#define EDP_PSR2_FRAME_BEFORE_SU_SHIFT 4 -+#define EDP_PSR2_FRAME_BEFORE_SU_MASK (0xf << 4) -+#define EDP_PSR2_FRAME_BEFORE_SU(a) ((a) << 4) -+#define EDP_PSR2_IDLE_FRAME_MASK 0xf -+#define EDP_PSR2_IDLE_FRAME_SHIFT 0 -+ -+#define _PSR_EVENT_TRANS_A 0x60848 -+#define _PSR_EVENT_TRANS_B 0x61848 -+#define _PSR_EVENT_TRANS_C 0x62848 -+#define _PSR_EVENT_TRANS_D 0x63848 -+#define _PSR_EVENT_TRANS_EDP 0x6F848 -+#define PSR_EVENT(trans) _MMIO_TRANS2(trans, _PSR_EVENT_TRANS_A) -+#define PSR_EVENT_PSR2_WD_TIMER_EXPIRE (1 << 17) -+#define PSR_EVENT_PSR2_DISABLED (1 << 16) -+#define PSR_EVENT_SU_DIRTY_FIFO_UNDERRUN (1 << 15) -+#define PSR_EVENT_SU_CRC_FIFO_UNDERRUN (1 << 14) -+#define PSR_EVENT_GRAPHICS_RESET (1 << 12) -+#define PSR_EVENT_PCH_INTERRUPT (1 << 11) -+#define PSR_EVENT_MEMORY_UP (1 << 10) -+#define PSR_EVENT_FRONT_BUFFER_MODIFY (1 << 9) -+#define PSR_EVENT_WD_TIMER_EXPIRE (1 << 8) -+#define PSR_EVENT_PIPE_REGISTERS_UPDATE (1 << 6) -+#define PSR_EVENT_REGISTER_UPDATE (1 << 5) /* Reserved in ICL+ */ -+#define PSR_EVENT_HDCP_ENABLE (1 << 4) -+#define PSR_EVENT_KVMR_SESSION_ENABLE (1 << 3) -+#define PSR_EVENT_VBI_ENABLE (1 << 2) -+#define PSR_EVENT_LPSP_MODE_EXIT (1 << 1) -+#define PSR_EVENT_PSR_DISABLE (1 << 0) -+ -+#define EDP_PSR2_STATUS _MMIO(0x6f940) -+#define EDP_PSR2_STATUS_STATE_MASK (0xf << 28) -+#define EDP_PSR2_STATUS_STATE_SHIFT 28 -+ -+#define _PSR2_SU_STATUS_0 0x6F914 -+#define _PSR2_SU_STATUS_1 0x6F918 -+#define _PSR2_SU_STATUS_2 0x6F91C -+#define _PSR2_SU_STATUS(index) _MMIO(_PICK_EVEN((index), _PSR2_SU_STATUS_0, _PSR2_SU_STATUS_1)) -+#define PSR2_SU_STATUS(frame) (_PSR2_SU_STATUS((frame) / 3)) -+#define PSR2_SU_STATUS_SHIFT(frame) (((frame) % 3) * 10) -+#define PSR2_SU_STATUS_MASK(frame) (0x3ff << PSR2_SU_STATUS_SHIFT(frame)) -+#define PSR2_SU_STATUS_FRAMES 8 -+ -+/* VGA port control */ -+#define ADPA _MMIO(0x61100) -+#define PCH_ADPA _MMIO(0xe1100) -+#define VLV_ADPA _MMIO(VLV_DISPLAY_BASE + 0x61100) -+ -+#define ADPA_DAC_ENABLE (1 << 31) -+#define ADPA_DAC_DISABLE 0 -+#define ADPA_PIPE_SEL_SHIFT 30 -+#define ADPA_PIPE_SEL_MASK (1 << 30) -+#define ADPA_PIPE_SEL(pipe) ((pipe) << 30) -+#define ADPA_PIPE_SEL_SHIFT_CPT 29 -+#define ADPA_PIPE_SEL_MASK_CPT (3 << 29) -+#define ADPA_PIPE_SEL_CPT(pipe) ((pipe) << 29) -+#define ADPA_CRT_HOTPLUG_MASK 0x03ff0000 /* bit 25-16 */ -+#define ADPA_CRT_HOTPLUG_MONITOR_NONE (0 << 24) -+#define ADPA_CRT_HOTPLUG_MONITOR_MASK (3 << 24) -+#define ADPA_CRT_HOTPLUG_MONITOR_COLOR (3 << 24) -+#define ADPA_CRT_HOTPLUG_MONITOR_MONO (2 << 24) -+#define ADPA_CRT_HOTPLUG_ENABLE (1 << 23) -+#define ADPA_CRT_HOTPLUG_PERIOD_64 (0 << 22) -+#define ADPA_CRT_HOTPLUG_PERIOD_128 (1 << 22) -+#define ADPA_CRT_HOTPLUG_WARMUP_5MS (0 << 21) -+#define ADPA_CRT_HOTPLUG_WARMUP_10MS (1 << 21) -+#define ADPA_CRT_HOTPLUG_SAMPLE_2S (0 << 20) -+#define ADPA_CRT_HOTPLUG_SAMPLE_4S (1 << 20) -+#define ADPA_CRT_HOTPLUG_VOLTAGE_40 (0 << 18) -+#define ADPA_CRT_HOTPLUG_VOLTAGE_50 (1 << 18) -+#define ADPA_CRT_HOTPLUG_VOLTAGE_60 (2 << 18) -+#define ADPA_CRT_HOTPLUG_VOLTAGE_70 (3 << 18) -+#define ADPA_CRT_HOTPLUG_VOLREF_325MV (0 << 17) -+#define ADPA_CRT_HOTPLUG_VOLREF_475MV (1 << 17) -+#define ADPA_CRT_HOTPLUG_FORCE_TRIGGER (1 << 16) -+#define ADPA_USE_VGA_HVPOLARITY (1 << 15) -+#define ADPA_SETS_HVPOLARITY 0 -+#define ADPA_VSYNC_CNTL_DISABLE (1 << 10) -+#define ADPA_VSYNC_CNTL_ENABLE 0 -+#define ADPA_HSYNC_CNTL_DISABLE (1 << 11) -+#define ADPA_HSYNC_CNTL_ENABLE 0 -+#define ADPA_VSYNC_ACTIVE_HIGH (1 << 4) -+#define ADPA_VSYNC_ACTIVE_LOW 0 -+#define ADPA_HSYNC_ACTIVE_HIGH (1 << 3) -+#define ADPA_HSYNC_ACTIVE_LOW 0 -+#define ADPA_DPMS_MASK (~(3 << 10)) -+#define ADPA_DPMS_ON (0 << 10) -+#define ADPA_DPMS_SUSPEND (1 << 10) -+#define ADPA_DPMS_STANDBY (2 << 10) -+#define ADPA_DPMS_OFF (3 << 10) -+ -+ -+/* Hotplug control (945+ only) */ -+#define PORT_HOTPLUG_EN _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x61110) -+#define PORTB_HOTPLUG_INT_EN (1 << 29) -+#define PORTC_HOTPLUG_INT_EN (1 << 28) -+#define PORTD_HOTPLUG_INT_EN (1 << 27) -+#define SDVOB_HOTPLUG_INT_EN (1 << 26) -+#define SDVOC_HOTPLUG_INT_EN (1 << 25) -+#define TV_HOTPLUG_INT_EN (1 << 18) -+#define CRT_HOTPLUG_INT_EN (1 << 9) -+#define HOTPLUG_INT_EN_MASK (PORTB_HOTPLUG_INT_EN | \ -+ PORTC_HOTPLUG_INT_EN | \ -+ PORTD_HOTPLUG_INT_EN | \ -+ SDVOC_HOTPLUG_INT_EN | \ -+ SDVOB_HOTPLUG_INT_EN | \ -+ CRT_HOTPLUG_INT_EN) -+#define CRT_HOTPLUG_FORCE_DETECT (1 << 3) -+#define CRT_HOTPLUG_ACTIVATION_PERIOD_32 (0 << 8) -+/* must use period 64 on GM45 according to docs */ -+#define CRT_HOTPLUG_ACTIVATION_PERIOD_64 (1 << 8) -+#define CRT_HOTPLUG_DAC_ON_TIME_2M (0 << 7) -+#define CRT_HOTPLUG_DAC_ON_TIME_4M (1 << 7) -+#define CRT_HOTPLUG_VOLTAGE_COMPARE_40 (0 << 5) -+#define CRT_HOTPLUG_VOLTAGE_COMPARE_50 (1 << 5) -+#define CRT_HOTPLUG_VOLTAGE_COMPARE_60 (2 << 5) -+#define CRT_HOTPLUG_VOLTAGE_COMPARE_70 (3 << 5) -+#define CRT_HOTPLUG_VOLTAGE_COMPARE_MASK (3 << 5) -+#define CRT_HOTPLUG_DETECT_DELAY_1G (0 << 4) -+#define CRT_HOTPLUG_DETECT_DELAY_2G (1 << 4) -+#define CRT_HOTPLUG_DETECT_VOLTAGE_325MV (0 << 2) -+#define CRT_HOTPLUG_DETECT_VOLTAGE_475MV (1 << 2) -+ -+#define PORT_HOTPLUG_STAT _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x61114) -+/* -+ * HDMI/DP bits are g4x+ -+ * -+ * WARNING: Bspec for hpd status bits on gen4 seems to be completely confused. -+ * Please check the detailed lore in the commit message for for experimental -+ * evidence. -+ */ -+/* Bspec says GM45 should match G4X/VLV/CHV, but reality disagrees */ -+#define PORTD_HOTPLUG_LIVE_STATUS_GM45 (1 << 29) -+#define PORTC_HOTPLUG_LIVE_STATUS_GM45 (1 << 28) -+#define PORTB_HOTPLUG_LIVE_STATUS_GM45 (1 << 27) -+/* G4X/VLV/CHV DP/HDMI bits again match Bspec */ -+#define PORTD_HOTPLUG_LIVE_STATUS_G4X (1 << 27) -+#define PORTC_HOTPLUG_LIVE_STATUS_G4X (1 << 28) -+#define PORTB_HOTPLUG_LIVE_STATUS_G4X (1 << 29) -+#define PORTD_HOTPLUG_INT_STATUS (3 << 21) -+#define PORTD_HOTPLUG_INT_LONG_PULSE (2 << 21) -+#define PORTD_HOTPLUG_INT_SHORT_PULSE (1 << 21) -+#define PORTC_HOTPLUG_INT_STATUS (3 << 19) -+#define PORTC_HOTPLUG_INT_LONG_PULSE (2 << 19) -+#define PORTC_HOTPLUG_INT_SHORT_PULSE (1 << 19) -+#define PORTB_HOTPLUG_INT_STATUS (3 << 17) -+#define PORTB_HOTPLUG_INT_LONG_PULSE (2 << 17) -+#define PORTB_HOTPLUG_INT_SHORT_PLUSE (1 << 17) -+/* CRT/TV common between gen3+ */ -+#define CRT_HOTPLUG_INT_STATUS (1 << 11) -+#define TV_HOTPLUG_INT_STATUS (1 << 10) -+#define CRT_HOTPLUG_MONITOR_MASK (3 << 8) -+#define CRT_HOTPLUG_MONITOR_COLOR (3 << 8) -+#define CRT_HOTPLUG_MONITOR_MONO (2 << 8) -+#define CRT_HOTPLUG_MONITOR_NONE (0 << 8) -+#define DP_AUX_CHANNEL_D_INT_STATUS_G4X (1 << 6) -+#define DP_AUX_CHANNEL_C_INT_STATUS_G4X (1 << 5) -+#define DP_AUX_CHANNEL_B_INT_STATUS_G4X (1 << 4) -+#define DP_AUX_CHANNEL_MASK_INT_STATUS_G4X (7 << 4) -+ -+/* SDVO is different across gen3/4 */ -+#define SDVOC_HOTPLUG_INT_STATUS_G4X (1 << 3) -+#define SDVOB_HOTPLUG_INT_STATUS_G4X (1 << 2) -+/* -+ * Bspec seems to be seriously misleaded about the SDVO hpd bits on i965g/gm, -+ * since reality corrobates that they're the same as on gen3. But keep these -+ * bits here (and the comment!) to help any other lost wanderers back onto the -+ * right tracks. -+ */ -+#define SDVOC_HOTPLUG_INT_STATUS_I965 (3 << 4) -+#define SDVOB_HOTPLUG_INT_STATUS_I965 (3 << 2) -+#define SDVOC_HOTPLUG_INT_STATUS_I915 (1 << 7) -+#define SDVOB_HOTPLUG_INT_STATUS_I915 (1 << 6) -+#define HOTPLUG_INT_STATUS_G4X (CRT_HOTPLUG_INT_STATUS | \ -+ SDVOB_HOTPLUG_INT_STATUS_G4X | \ -+ SDVOC_HOTPLUG_INT_STATUS_G4X | \ -+ PORTB_HOTPLUG_INT_STATUS | \ -+ PORTC_HOTPLUG_INT_STATUS | \ -+ PORTD_HOTPLUG_INT_STATUS) -+ -+#define HOTPLUG_INT_STATUS_I915 (CRT_HOTPLUG_INT_STATUS | \ -+ SDVOB_HOTPLUG_INT_STATUS_I915 | \ -+ SDVOC_HOTPLUG_INT_STATUS_I915 | \ -+ PORTB_HOTPLUG_INT_STATUS | \ -+ PORTC_HOTPLUG_INT_STATUS | \ -+ PORTD_HOTPLUG_INT_STATUS) -+ -+/* SDVO and HDMI port control. -+ * The same register may be used for SDVO or HDMI */ -+#define _GEN3_SDVOB 0x61140 -+#define _GEN3_SDVOC 0x61160 -+#define GEN3_SDVOB _MMIO(_GEN3_SDVOB) -+#define GEN3_SDVOC _MMIO(_GEN3_SDVOC) -+#define GEN4_HDMIB GEN3_SDVOB -+#define GEN4_HDMIC GEN3_SDVOC -+#define VLV_HDMIB _MMIO(VLV_DISPLAY_BASE + 0x61140) -+#define VLV_HDMIC _MMIO(VLV_DISPLAY_BASE + 0x61160) -+#define CHV_HDMID _MMIO(VLV_DISPLAY_BASE + 0x6116C) -+#define PCH_SDVOB _MMIO(0xe1140) -+#define PCH_HDMIB PCH_SDVOB -+#define PCH_HDMIC _MMIO(0xe1150) -+#define PCH_HDMID _MMIO(0xe1160) -+ -+#define PORT_DFT_I9XX _MMIO(0x61150) -+#define DC_BALANCE_RESET (1 << 25) -+#define PORT_DFT2_G4X _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x61154) -+#define DC_BALANCE_RESET_VLV (1 << 31) -+#define PIPE_SCRAMBLE_RESET_MASK ((1 << 14) | (0x3 << 0)) -+#define PIPE_C_SCRAMBLE_RESET (1 << 14) /* chv */ -+#define PIPE_B_SCRAMBLE_RESET (1 << 1) -+#define PIPE_A_SCRAMBLE_RESET (1 << 0) -+ -+/* Gen 3 SDVO bits: */ -+#define SDVO_ENABLE (1 << 31) -+#define SDVO_PIPE_SEL_SHIFT 30 -+#define SDVO_PIPE_SEL_MASK (1 << 30) -+#define SDVO_PIPE_SEL(pipe) ((pipe) << 30) -+#define SDVO_STALL_SELECT (1 << 29) -+#define SDVO_INTERRUPT_ENABLE (1 << 26) -+/* -+ * 915G/GM SDVO pixel multiplier. -+ * Programmed value is multiplier - 1, up to 5x. -+ * \sa DPLL_MD_UDI_MULTIPLIER_MASK -+ */ -+#define SDVO_PORT_MULTIPLY_MASK (7 << 23) -+#define SDVO_PORT_MULTIPLY_SHIFT 23 -+#define SDVO_PHASE_SELECT_MASK (15 << 19) -+#define SDVO_PHASE_SELECT_DEFAULT (6 << 19) -+#define SDVO_CLOCK_OUTPUT_INVERT (1 << 18) -+#define SDVOC_GANG_MODE (1 << 16) /* Port C only */ -+#define SDVO_BORDER_ENABLE (1 << 7) /* SDVO only */ -+#define SDVOB_PCIE_CONCURRENCY (1 << 3) /* Port B only */ -+#define SDVO_DETECTED (1 << 2) -+/* Bits to be preserved when writing */ -+#define SDVOB_PRESERVE_MASK ((1 << 17) | (1 << 16) | (1 << 14) | \ -+ SDVO_INTERRUPT_ENABLE) -+#define SDVOC_PRESERVE_MASK ((1 << 17) | SDVO_INTERRUPT_ENABLE) -+ -+/* Gen 4 SDVO/HDMI bits: */ -+#define SDVO_COLOR_FORMAT_8bpc (0 << 26) -+#define SDVO_COLOR_FORMAT_MASK (7 << 26) -+#define SDVO_ENCODING_SDVO (0 << 10) -+#define SDVO_ENCODING_HDMI (2 << 10) -+#define HDMI_MODE_SELECT_HDMI (1 << 9) /* HDMI only */ -+#define HDMI_MODE_SELECT_DVI (0 << 9) /* HDMI only */ -+#define HDMI_COLOR_RANGE_16_235 (1 << 8) /* HDMI only */ -+#define SDVO_AUDIO_ENABLE (1 << 6) -+/* VSYNC/HSYNC bits new with 965, default is to be set */ -+#define SDVO_VSYNC_ACTIVE_HIGH (1 << 4) -+#define SDVO_HSYNC_ACTIVE_HIGH (1 << 3) -+ -+/* Gen 5 (IBX) SDVO/HDMI bits: */ -+#define HDMI_COLOR_FORMAT_12bpc (3 << 26) /* HDMI only */ -+#define SDVOB_HOTPLUG_ENABLE (1 << 23) /* SDVO only */ -+ -+/* Gen 6 (CPT) SDVO/HDMI bits: */ -+#define SDVO_PIPE_SEL_SHIFT_CPT 29 -+#define SDVO_PIPE_SEL_MASK_CPT (3 << 29) -+#define SDVO_PIPE_SEL_CPT(pipe) ((pipe) << 29) -+ -+/* CHV SDVO/HDMI bits: */ -+#define SDVO_PIPE_SEL_SHIFT_CHV 24 -+#define SDVO_PIPE_SEL_MASK_CHV (3 << 24) -+#define SDVO_PIPE_SEL_CHV(pipe) ((pipe) << 24) -+ -+ -+/* DVO port control */ -+#define _DVOA 0x61120 -+#define DVOA _MMIO(_DVOA) -+#define _DVOB 0x61140 -+#define DVOB _MMIO(_DVOB) -+#define _DVOC 0x61160 -+#define DVOC _MMIO(_DVOC) -+#define DVO_ENABLE (1 << 31) -+#define DVO_PIPE_SEL_SHIFT 30 -+#define DVO_PIPE_SEL_MASK (1 << 30) -+#define DVO_PIPE_SEL(pipe) ((pipe) << 30) -+#define DVO_PIPE_STALL_UNUSED (0 << 28) -+#define DVO_PIPE_STALL (1 << 28) -+#define DVO_PIPE_STALL_TV (2 << 28) -+#define DVO_PIPE_STALL_MASK (3 << 28) -+#define DVO_USE_VGA_SYNC (1 << 15) -+#define DVO_DATA_ORDER_I740 (0 << 14) -+#define DVO_DATA_ORDER_FP (1 << 14) -+#define DVO_VSYNC_DISABLE (1 << 11) -+#define DVO_HSYNC_DISABLE (1 << 10) -+#define DVO_VSYNC_TRISTATE (1 << 9) -+#define DVO_HSYNC_TRISTATE (1 << 8) -+#define DVO_BORDER_ENABLE (1 << 7) -+#define DVO_DATA_ORDER_GBRG (1 << 6) -+#define DVO_DATA_ORDER_RGGB (0 << 6) -+#define DVO_DATA_ORDER_GBRG_ERRATA (0 << 6) -+#define DVO_DATA_ORDER_RGGB_ERRATA (1 << 6) -+#define DVO_VSYNC_ACTIVE_HIGH (1 << 4) -+#define DVO_HSYNC_ACTIVE_HIGH (1 << 3) -+#define DVO_BLANK_ACTIVE_HIGH (1 << 2) -+#define DVO_OUTPUT_CSTATE_PIXELS (1 << 1) /* SDG only */ -+#define DVO_OUTPUT_SOURCE_SIZE_PIXELS (1 << 0) /* SDG only */ -+#define DVO_PRESERVE_MASK (0x7 << 24) -+#define DVOA_SRCDIM _MMIO(0x61124) -+#define DVOB_SRCDIM _MMIO(0x61144) -+#define DVOC_SRCDIM _MMIO(0x61164) -+#define DVO_SRCDIM_HORIZONTAL_SHIFT 12 -+#define DVO_SRCDIM_VERTICAL_SHIFT 0 -+ -+/* LVDS port control */ -+#define LVDS _MMIO(0x61180) -+/* -+ * Enables the LVDS port. This bit must be set before DPLLs are enabled, as -+ * the DPLL semantics change when the LVDS is assigned to that pipe. -+ */ -+#define LVDS_PORT_EN (1 << 31) -+/* Selects pipe B for LVDS data. Must be set on pre-965. */ -+#define LVDS_PIPE_SEL_SHIFT 30 -+#define LVDS_PIPE_SEL_MASK (1 << 30) -+#define LVDS_PIPE_SEL(pipe) ((pipe) << 30) -+#define LVDS_PIPE_SEL_SHIFT_CPT 29 -+#define LVDS_PIPE_SEL_MASK_CPT (3 << 29) -+#define LVDS_PIPE_SEL_CPT(pipe) ((pipe) << 29) -+/* LVDS dithering flag on 965/g4x platform */ -+#define LVDS_ENABLE_DITHER (1 << 25) -+/* LVDS sync polarity flags. Set to invert (i.e. negative) */ -+#define LVDS_VSYNC_POLARITY (1 << 21) -+#define LVDS_HSYNC_POLARITY (1 << 20) -+ -+/* Enable border for unscaled (or aspect-scaled) display */ -+#define LVDS_BORDER_ENABLE (1 << 15) -+/* -+ * Enables the A0-A2 data pairs and CLKA, containing 18 bits of color data per -+ * pixel. -+ */ -+#define LVDS_A0A2_CLKA_POWER_MASK (3 << 8) -+#define LVDS_A0A2_CLKA_POWER_DOWN (0 << 8) -+#define LVDS_A0A2_CLKA_POWER_UP (3 << 8) -+/* -+ * Controls the A3 data pair, which contains the additional LSBs for 24 bit -+ * mode. Only enabled if LVDS_A0A2_CLKA_POWER_UP also indicates it should be -+ * on. -+ */ -+#define LVDS_A3_POWER_MASK (3 << 6) -+#define LVDS_A3_POWER_DOWN (0 << 6) -+#define LVDS_A3_POWER_UP (3 << 6) -+/* -+ * Controls the CLKB pair. This should only be set when LVDS_B0B3_POWER_UP -+ * is set. -+ */ -+#define LVDS_CLKB_POWER_MASK (3 << 4) -+#define LVDS_CLKB_POWER_DOWN (0 << 4) -+#define LVDS_CLKB_POWER_UP (3 << 4) -+/* -+ * Controls the B0-B3 data pairs. This must be set to match the DPLL p2 -+ * setting for whether we are in dual-channel mode. The B3 pair will -+ * additionally only be powered up when LVDS_A3_POWER_UP is set. -+ */ -+#define LVDS_B0B3_POWER_MASK (3 << 2) -+#define LVDS_B0B3_POWER_DOWN (0 << 2) -+#define LVDS_B0B3_POWER_UP (3 << 2) -+ -+/* Video Data Island Packet control */ -+#define VIDEO_DIP_DATA _MMIO(0x61178) -+/* Read the description of VIDEO_DIP_DATA (before Haswell) or VIDEO_DIP_ECC -+ * (Haswell and newer) to see which VIDEO_DIP_DATA byte corresponds to each byte -+ * of the infoframe structure specified by CEA-861. */ -+#define VIDEO_DIP_DATA_SIZE 32 -+#define VIDEO_DIP_VSC_DATA_SIZE 36 -+#define VIDEO_DIP_PPS_DATA_SIZE 132 -+#define VIDEO_DIP_CTL _MMIO(0x61170) -+/* Pre HSW: */ -+#define VIDEO_DIP_ENABLE (1 << 31) -+#define VIDEO_DIP_PORT(port) ((port) << 29) -+#define VIDEO_DIP_PORT_MASK (3 << 29) -+#define VIDEO_DIP_ENABLE_GCP (1 << 25) /* ilk+ */ -+#define VIDEO_DIP_ENABLE_AVI (1 << 21) -+#define VIDEO_DIP_ENABLE_VENDOR (2 << 21) -+#define VIDEO_DIP_ENABLE_GAMUT (4 << 21) /* ilk+ */ -+#define VIDEO_DIP_ENABLE_SPD (8 << 21) -+#define VIDEO_DIP_SELECT_AVI (0 << 19) -+#define VIDEO_DIP_SELECT_VENDOR (1 << 19) -+#define VIDEO_DIP_SELECT_GAMUT (2 << 19) -+#define VIDEO_DIP_SELECT_SPD (3 << 19) -+#define VIDEO_DIP_SELECT_MASK (3 << 19) -+#define VIDEO_DIP_FREQ_ONCE (0 << 16) -+#define VIDEO_DIP_FREQ_VSYNC (1 << 16) -+#define VIDEO_DIP_FREQ_2VSYNC (2 << 16) -+#define VIDEO_DIP_FREQ_MASK (3 << 16) -+/* HSW and later: */ -+#define DRM_DIP_ENABLE (1 << 28) -+#define PSR_VSC_BIT_7_SET (1 << 27) -+#define VSC_SELECT_MASK (0x3 << 25) -+#define VSC_SELECT_SHIFT 25 -+#define VSC_DIP_HW_HEA_DATA (0 << 25) -+#define VSC_DIP_HW_HEA_SW_DATA (1 << 25) -+#define VSC_DIP_HW_DATA_SW_HEA (2 << 25) -+#define VSC_DIP_SW_HEA_DATA (3 << 25) -+#define VDIP_ENABLE_PPS (1 << 24) -+#define VIDEO_DIP_ENABLE_VSC_HSW (1 << 20) -+#define VIDEO_DIP_ENABLE_GCP_HSW (1 << 16) -+#define VIDEO_DIP_ENABLE_AVI_HSW (1 << 12) -+#define VIDEO_DIP_ENABLE_VS_HSW (1 << 8) -+#define VIDEO_DIP_ENABLE_GMP_HSW (1 << 4) -+#define VIDEO_DIP_ENABLE_SPD_HSW (1 << 0) -+ -+/* Panel power sequencing */ -+#define PPS_BASE 0x61200 -+#define VLV_PPS_BASE (VLV_DISPLAY_BASE + PPS_BASE) -+#define PCH_PPS_BASE 0xC7200 -+ -+#define _MMIO_PPS(pps_idx, reg) _MMIO(dev_priv->pps_mmio_base - \ -+ PPS_BASE + (reg) + \ -+ (pps_idx) * 0x100) -+ -+#define _PP_STATUS 0x61200 -+#define PP_STATUS(pps_idx) _MMIO_PPS(pps_idx, _PP_STATUS) -+#define PP_ON REG_BIT(31) -+ -+#define _PP_CONTROL_1 0xc7204 -+#define _PP_CONTROL_2 0xc7304 -+#define ICP_PP_CONTROL(x) _MMIO(((x) == 1) ? _PP_CONTROL_1 : \ -+ _PP_CONTROL_2) -+#define POWER_CYCLE_DELAY_MASK REG_GENMASK(8, 4) -+#define VDD_OVERRIDE_FORCE REG_BIT(3) -+#define BACKLIGHT_ENABLE REG_BIT(2) -+#define PWR_DOWN_ON_RESET REG_BIT(1) -+#define PWR_STATE_TARGET REG_BIT(0) -+/* -+ * Indicates that all dependencies of the panel are on: -+ * -+ * - PLL enabled -+ * - pipe enabled -+ * - LVDS/DVOB/DVOC on -+ */ -+#define PP_READY REG_BIT(30) -+#define PP_SEQUENCE_MASK REG_GENMASK(29, 28) -+#define PP_SEQUENCE_NONE REG_FIELD_PREP(PP_SEQUENCE_MASK, 0) -+#define PP_SEQUENCE_POWER_UP REG_FIELD_PREP(PP_SEQUENCE_MASK, 1) -+#define PP_SEQUENCE_POWER_DOWN REG_FIELD_PREP(PP_SEQUENCE_MASK, 2) -+#define PP_CYCLE_DELAY_ACTIVE REG_BIT(27) -+#define PP_SEQUENCE_STATE_MASK REG_GENMASK(3, 0) -+#define PP_SEQUENCE_STATE_OFF_IDLE REG_FIELD_PREP(PP_SEQUENCE_STATE_MASK, 0x0) -+#define PP_SEQUENCE_STATE_OFF_S0_1 REG_FIELD_PREP(PP_SEQUENCE_STATE_MASK, 0x1) -+#define PP_SEQUENCE_STATE_OFF_S0_2 REG_FIELD_PREP(PP_SEQUENCE_STATE_MASK, 0x2) -+#define PP_SEQUENCE_STATE_OFF_S0_3 REG_FIELD_PREP(PP_SEQUENCE_STATE_MASK, 0x3) -+#define PP_SEQUENCE_STATE_ON_IDLE REG_FIELD_PREP(PP_SEQUENCE_STATE_MASK, 0x8) -+#define PP_SEQUENCE_STATE_ON_S1_1 REG_FIELD_PREP(PP_SEQUENCE_STATE_MASK, 0x9) -+#define PP_SEQUENCE_STATE_ON_S1_2 REG_FIELD_PREP(PP_SEQUENCE_STATE_MASK, 0xa) -+#define PP_SEQUENCE_STATE_ON_S1_3 REG_FIELD_PREP(PP_SEQUENCE_STATE_MASK, 0xb) -+#define PP_SEQUENCE_STATE_RESET REG_FIELD_PREP(PP_SEQUENCE_STATE_MASK, 0xf) -+ -+#define _PP_CONTROL 0x61204 -+#define PP_CONTROL(pps_idx) _MMIO_PPS(pps_idx, _PP_CONTROL) -+#define PANEL_UNLOCK_MASK REG_GENMASK(31, 16) -+#define PANEL_UNLOCK_REGS REG_FIELD_PREP(PANEL_UNLOCK_MASK, 0xabcd) -+#define BXT_POWER_CYCLE_DELAY_MASK REG_GENMASK(8, 4) -+#define EDP_FORCE_VDD REG_BIT(3) -+#define EDP_BLC_ENABLE REG_BIT(2) -+#define PANEL_POWER_RESET REG_BIT(1) -+#define PANEL_POWER_ON REG_BIT(0) -+ -+#define _PP_ON_DELAYS 0x61208 -+#define PP_ON_DELAYS(pps_idx) _MMIO_PPS(pps_idx, _PP_ON_DELAYS) -+#define PANEL_PORT_SELECT_MASK REG_GENMASK(31, 30) -+#define PANEL_PORT_SELECT_LVDS REG_FIELD_PREP(PANEL_PORT_SELECT_MASK, 0) -+#define PANEL_PORT_SELECT_DPA REG_FIELD_PREP(PANEL_PORT_SELECT_MASK, 1) -+#define PANEL_PORT_SELECT_DPC REG_FIELD_PREP(PANEL_PORT_SELECT_MASK, 2) -+#define PANEL_PORT_SELECT_DPD REG_FIELD_PREP(PANEL_PORT_SELECT_MASK, 3) -+#define PANEL_PORT_SELECT_VLV(port) REG_FIELD_PREP(PANEL_PORT_SELECT_MASK, port) -+#define PANEL_POWER_UP_DELAY_MASK REG_GENMASK(28, 16) -+#define PANEL_LIGHT_ON_DELAY_MASK REG_GENMASK(12, 0) -+ -+#define _PP_OFF_DELAYS 0x6120C -+#define PP_OFF_DELAYS(pps_idx) _MMIO_PPS(pps_idx, _PP_OFF_DELAYS) -+#define PANEL_POWER_DOWN_DELAY_MASK REG_GENMASK(28, 16) -+#define PANEL_LIGHT_OFF_DELAY_MASK REG_GENMASK(12, 0) -+ -+#define _PP_DIVISOR 0x61210 -+#define PP_DIVISOR(pps_idx) _MMIO_PPS(pps_idx, _PP_DIVISOR) -+#define PP_REFERENCE_DIVIDER_MASK REG_GENMASK(31, 8) -+#define PANEL_POWER_CYCLE_DELAY_MASK REG_GENMASK(4, 0) -+ -+/* Panel fitting */ -+#define PFIT_CONTROL _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x61230) -+#define PFIT_ENABLE (1 << 31) -+#define PFIT_PIPE_MASK (3 << 29) -+#define PFIT_PIPE_SHIFT 29 -+#define VERT_INTERP_DISABLE (0 << 10) -+#define VERT_INTERP_BILINEAR (1 << 10) -+#define VERT_INTERP_MASK (3 << 10) -+#define VERT_AUTO_SCALE (1 << 9) -+#define HORIZ_INTERP_DISABLE (0 << 6) -+#define HORIZ_INTERP_BILINEAR (1 << 6) -+#define HORIZ_INTERP_MASK (3 << 6) -+#define HORIZ_AUTO_SCALE (1 << 5) -+#define PANEL_8TO6_DITHER_ENABLE (1 << 3) -+#define PFIT_FILTER_FUZZY (0 << 24) -+#define PFIT_SCALING_AUTO (0 << 26) -+#define PFIT_SCALING_PROGRAMMED (1 << 26) -+#define PFIT_SCALING_PILLAR (2 << 26) -+#define PFIT_SCALING_LETTER (3 << 26) -+#define PFIT_PGM_RATIOS _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x61234) -+/* Pre-965 */ -+#define PFIT_VERT_SCALE_SHIFT 20 -+#define PFIT_VERT_SCALE_MASK 0xfff00000 -+#define PFIT_HORIZ_SCALE_SHIFT 4 -+#define PFIT_HORIZ_SCALE_MASK 0x0000fff0 -+/* 965+ */ -+#define PFIT_VERT_SCALE_SHIFT_965 16 -+#define PFIT_VERT_SCALE_MASK_965 0x1fff0000 -+#define PFIT_HORIZ_SCALE_SHIFT_965 0 -+#define PFIT_HORIZ_SCALE_MASK_965 0x00001fff -+ -+#define PFIT_AUTO_RATIOS _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x61238) -+ -+#define _VLV_BLC_PWM_CTL2_A (DISPLAY_MMIO_BASE(dev_priv) + 0x61250) -+#define _VLV_BLC_PWM_CTL2_B (DISPLAY_MMIO_BASE(dev_priv) + 0x61350) -+#define VLV_BLC_PWM_CTL2(pipe) _MMIO_PIPE(pipe, _VLV_BLC_PWM_CTL2_A, \ -+ _VLV_BLC_PWM_CTL2_B) -+ -+#define _VLV_BLC_PWM_CTL_A (DISPLAY_MMIO_BASE(dev_priv) + 0x61254) -+#define _VLV_BLC_PWM_CTL_B (DISPLAY_MMIO_BASE(dev_priv) + 0x61354) -+#define VLV_BLC_PWM_CTL(pipe) _MMIO_PIPE(pipe, _VLV_BLC_PWM_CTL_A, \ -+ _VLV_BLC_PWM_CTL_B) -+ -+#define _VLV_BLC_HIST_CTL_A (DISPLAY_MMIO_BASE(dev_priv) + 0x61260) -+#define _VLV_BLC_HIST_CTL_B (DISPLAY_MMIO_BASE(dev_priv) + 0x61360) -+#define VLV_BLC_HIST_CTL(pipe) _MMIO_PIPE(pipe, _VLV_BLC_HIST_CTL_A, \ -+ _VLV_BLC_HIST_CTL_B) -+ -+/* Backlight control */ -+#define BLC_PWM_CTL2 _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x61250) /* 965+ only */ -+#define BLM_PWM_ENABLE (1 << 31) -+#define BLM_COMBINATION_MODE (1 << 30) /* gen4 only */ -+#define BLM_PIPE_SELECT (1 << 29) -+#define BLM_PIPE_SELECT_IVB (3 << 29) -+#define BLM_PIPE_A (0 << 29) -+#define BLM_PIPE_B (1 << 29) -+#define BLM_PIPE_C (2 << 29) /* ivb + */ -+#define BLM_TRANSCODER_A BLM_PIPE_A /* hsw */ -+#define BLM_TRANSCODER_B BLM_PIPE_B -+#define BLM_TRANSCODER_C BLM_PIPE_C -+#define BLM_TRANSCODER_EDP (3 << 29) -+#define BLM_PIPE(pipe) ((pipe) << 29) -+#define BLM_POLARITY_I965 (1 << 28) /* gen4 only */ -+#define BLM_PHASE_IN_INTERUPT_STATUS (1 << 26) -+#define BLM_PHASE_IN_ENABLE (1 << 25) -+#define BLM_PHASE_IN_INTERUPT_ENABL (1 << 24) -+#define BLM_PHASE_IN_TIME_BASE_SHIFT (16) -+#define BLM_PHASE_IN_TIME_BASE_MASK (0xff << 16) -+#define BLM_PHASE_IN_COUNT_SHIFT (8) -+#define BLM_PHASE_IN_COUNT_MASK (0xff << 8) -+#define BLM_PHASE_IN_INCR_SHIFT (0) -+#define BLM_PHASE_IN_INCR_MASK (0xff << 0) -+#define BLC_PWM_CTL _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x61254) -+/* -+ * This is the most significant 15 bits of the number of backlight cycles in a -+ * complete cycle of the modulated backlight control. -+ * -+ * The actual value is this field multiplied by two. -+ */ -+#define BACKLIGHT_MODULATION_FREQ_SHIFT (17) -+#define BACKLIGHT_MODULATION_FREQ_MASK (0x7fff << 17) -+#define BLM_LEGACY_MODE (1 << 16) /* gen2 only */ -+/* -+ * This is the number of cycles out of the backlight modulation cycle for which -+ * the backlight is on. -+ * -+ * This field must be no greater than the number of cycles in the complete -+ * backlight modulation cycle. -+ */ -+#define BACKLIGHT_DUTY_CYCLE_SHIFT (0) -+#define BACKLIGHT_DUTY_CYCLE_MASK (0xffff) -+#define BACKLIGHT_DUTY_CYCLE_MASK_PNV (0xfffe) -+#define BLM_POLARITY_PNV (1 << 0) /* pnv only */ -+ -+#define BLC_HIST_CTL _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x61260) -+#define BLM_HISTOGRAM_ENABLE (1 << 31) -+ -+/* New registers for PCH-split platforms. Safe where new bits show up, the -+ * register layout machtes with gen4 BLC_PWM_CTL[12]. */ -+#define BLC_PWM_CPU_CTL2 _MMIO(0x48250) -+#define BLC_PWM_CPU_CTL _MMIO(0x48254) -+ -+#define HSW_BLC_PWM2_CTL _MMIO(0x48350) -+ -+/* PCH CTL1 is totally different, all but the below bits are reserved. CTL2 is -+ * like the normal CTL from gen4 and earlier. Hooray for confusing naming. */ -+#define BLC_PWM_PCH_CTL1 _MMIO(0xc8250) -+#define BLM_PCH_PWM_ENABLE (1 << 31) -+#define BLM_PCH_OVERRIDE_ENABLE (1 << 30) -+#define BLM_PCH_POLARITY (1 << 29) -+#define BLC_PWM_PCH_CTL2 _MMIO(0xc8254) -+ -+#define UTIL_PIN_CTL _MMIO(0x48400) -+#define UTIL_PIN_ENABLE (1 << 31) -+ -+#define UTIL_PIN_PIPE(x) ((x) << 29) -+#define UTIL_PIN_PIPE_MASK (3 << 29) -+#define UTIL_PIN_MODE_PWM (1 << 24) -+#define UTIL_PIN_MODE_MASK (0xf << 24) -+#define UTIL_PIN_POLARITY (1 << 22) -+ -+/* BXT backlight register definition. */ -+#define _BXT_BLC_PWM_CTL1 0xC8250 -+#define BXT_BLC_PWM_ENABLE (1 << 31) -+#define BXT_BLC_PWM_POLARITY (1 << 29) -+#define _BXT_BLC_PWM_FREQ1 0xC8254 -+#define _BXT_BLC_PWM_DUTY1 0xC8258 -+ -+#define _BXT_BLC_PWM_CTL2 0xC8350 -+#define _BXT_BLC_PWM_FREQ2 0xC8354 -+#define _BXT_BLC_PWM_DUTY2 0xC8358 -+ -+#define BXT_BLC_PWM_CTL(controller) _MMIO_PIPE(controller, \ -+ _BXT_BLC_PWM_CTL1, _BXT_BLC_PWM_CTL2) -+#define BXT_BLC_PWM_FREQ(controller) _MMIO_PIPE(controller, \ -+ _BXT_BLC_PWM_FREQ1, _BXT_BLC_PWM_FREQ2) -+#define BXT_BLC_PWM_DUTY(controller) _MMIO_PIPE(controller, \ -+ _BXT_BLC_PWM_DUTY1, _BXT_BLC_PWM_DUTY2) -+ -+#define PCH_GTC_CTL _MMIO(0xe7000) -+#define PCH_GTC_ENABLE (1 << 31) -+ -+/* TV port control */ -+#define TV_CTL _MMIO(0x68000) -+/* Enables the TV encoder */ -+# define TV_ENC_ENABLE (1 << 31) -+/* Sources the TV encoder input from pipe B instead of A. */ -+# define TV_ENC_PIPE_SEL_SHIFT 30 -+# define TV_ENC_PIPE_SEL_MASK (1 << 30) -+# define TV_ENC_PIPE_SEL(pipe) ((pipe) << 30) -+/* Outputs composite video (DAC A only) */ -+# define TV_ENC_OUTPUT_COMPOSITE (0 << 28) -+/* Outputs SVideo video (DAC B/C) */ -+# define TV_ENC_OUTPUT_SVIDEO (1 << 28) -+/* Outputs Component video (DAC A/B/C) */ -+# define TV_ENC_OUTPUT_COMPONENT (2 << 28) -+/* Outputs Composite and SVideo (DAC A/B/C) */ -+# define TV_ENC_OUTPUT_SVIDEO_COMPOSITE (3 << 28) -+# define TV_TRILEVEL_SYNC (1 << 21) -+/* Enables slow sync generation (945GM only) */ -+# define TV_SLOW_SYNC (1 << 20) -+/* Selects 4x oversampling for 480i and 576p */ -+# define TV_OVERSAMPLE_4X (0 << 18) -+/* Selects 2x oversampling for 720p and 1080i */ -+# define TV_OVERSAMPLE_2X (1 << 18) -+/* Selects no oversampling for 1080p */ -+# define TV_OVERSAMPLE_NONE (2 << 18) -+/* Selects 8x oversampling */ -+# define TV_OVERSAMPLE_8X (3 << 18) -+# define TV_OVERSAMPLE_MASK (3 << 18) -+/* Selects progressive mode rather than interlaced */ -+# define TV_PROGRESSIVE (1 << 17) -+/* Sets the colorburst to PAL mode. Required for non-M PAL modes. */ -+# define TV_PAL_BURST (1 << 16) -+/* Field for setting delay of Y compared to C */ -+# define TV_YC_SKEW_MASK (7 << 12) -+/* Enables a fix for 480p/576p standard definition modes on the 915GM only */ -+# define TV_ENC_SDP_FIX (1 << 11) -+/* -+ * Enables a fix for the 915GM only. -+ * -+ * Not sure what it does. -+ */ -+# define TV_ENC_C0_FIX (1 << 10) -+/* Bits that must be preserved by software */ -+# define TV_CTL_SAVE ((1 << 11) | (3 << 9) | (7 << 6) | 0xf) -+# define TV_FUSE_STATE_MASK (3 << 4) -+/* Read-only state that reports all features enabled */ -+# define TV_FUSE_STATE_ENABLED (0 << 4) -+/* Read-only state that reports that Macrovision is disabled in hardware*/ -+# define TV_FUSE_STATE_NO_MACROVISION (1 << 4) -+/* Read-only state that reports that TV-out is disabled in hardware. */ -+# define TV_FUSE_STATE_DISABLED (2 << 4) -+/* Normal operation */ -+# define TV_TEST_MODE_NORMAL (0 << 0) -+/* Encoder test pattern 1 - combo pattern */ -+# define TV_TEST_MODE_PATTERN_1 (1 << 0) -+/* Encoder test pattern 2 - full screen vertical 75% color bars */ -+# define TV_TEST_MODE_PATTERN_2 (2 << 0) -+/* Encoder test pattern 3 - full screen horizontal 75% color bars */ -+# define TV_TEST_MODE_PATTERN_3 (3 << 0) -+/* Encoder test pattern 4 - random noise */ -+# define TV_TEST_MODE_PATTERN_4 (4 << 0) -+/* Encoder test pattern 5 - linear color ramps */ -+# define TV_TEST_MODE_PATTERN_5 (5 << 0) -+/* -+ * This test mode forces the DACs to 50% of full output. -+ * -+ * This is used for load detection in combination with TVDAC_SENSE_MASK -+ */ -+# define TV_TEST_MODE_MONITOR_DETECT (7 << 0) -+# define TV_TEST_MODE_MASK (7 << 0) -+ -+#define TV_DAC _MMIO(0x68004) -+# define TV_DAC_SAVE 0x00ffff00 -+/* -+ * Reports that DAC state change logic has reported change (RO). -+ * -+ * This gets cleared when TV_DAC_STATE_EN is cleared -+*/ -+# define TVDAC_STATE_CHG (1 << 31) -+# define TVDAC_SENSE_MASK (7 << 28) -+/* Reports that DAC A voltage is above the detect threshold */ -+# define TVDAC_A_SENSE (1 << 30) -+/* Reports that DAC B voltage is above the detect threshold */ -+# define TVDAC_B_SENSE (1 << 29) -+/* Reports that DAC C voltage is above the detect threshold */ -+# define TVDAC_C_SENSE (1 << 28) -+/* -+ * Enables DAC state detection logic, for load-based TV detection. -+ * -+ * The PLL of the chosen pipe (in TV_CTL) must be running, and the encoder set -+ * to off, for load detection to work. -+ */ -+# define TVDAC_STATE_CHG_EN (1 << 27) -+/* Sets the DAC A sense value to high */ -+# define TVDAC_A_SENSE_CTL (1 << 26) -+/* Sets the DAC B sense value to high */ -+# define TVDAC_B_SENSE_CTL (1 << 25) -+/* Sets the DAC C sense value to high */ -+# define TVDAC_C_SENSE_CTL (1 << 24) -+/* Overrides the ENC_ENABLE and DAC voltage levels */ -+# define DAC_CTL_OVERRIDE (1 << 7) -+/* Sets the slew rate. Must be preserved in software */ -+# define ENC_TVDAC_SLEW_FAST (1 << 6) -+# define DAC_A_1_3_V (0 << 4) -+# define DAC_A_1_1_V (1 << 4) -+# define DAC_A_0_7_V (2 << 4) -+# define DAC_A_MASK (3 << 4) -+# define DAC_B_1_3_V (0 << 2) -+# define DAC_B_1_1_V (1 << 2) -+# define DAC_B_0_7_V (2 << 2) -+# define DAC_B_MASK (3 << 2) -+# define DAC_C_1_3_V (0 << 0) -+# define DAC_C_1_1_V (1 << 0) -+# define DAC_C_0_7_V (2 << 0) -+# define DAC_C_MASK (3 << 0) -+ -+/* -+ * CSC coefficients are stored in a floating point format with 9 bits of -+ * mantissa and 2 or 3 bits of exponent. The exponent is represented as 2**-n, -+ * where 2-bit exponents are unsigned n, and 3-bit exponents are signed n with -+ * -1 (0x3) being the only legal negative value. -+ */ -+#define TV_CSC_Y _MMIO(0x68010) -+# define TV_RY_MASK 0x07ff0000 -+# define TV_RY_SHIFT 16 -+# define TV_GY_MASK 0x00000fff -+# define TV_GY_SHIFT 0 -+ -+#define TV_CSC_Y2 _MMIO(0x68014) -+# define TV_BY_MASK 0x07ff0000 -+# define TV_BY_SHIFT 16 -+/* -+ * Y attenuation for component video. -+ * -+ * Stored in 1.9 fixed point. -+ */ -+# define TV_AY_MASK 0x000003ff -+# define TV_AY_SHIFT 0 -+ -+#define TV_CSC_U _MMIO(0x68018) -+# define TV_RU_MASK 0x07ff0000 -+# define TV_RU_SHIFT 16 -+# define TV_GU_MASK 0x000007ff -+# define TV_GU_SHIFT 0 -+ -+#define TV_CSC_U2 _MMIO(0x6801c) -+# define TV_BU_MASK 0x07ff0000 -+# define TV_BU_SHIFT 16 -+/* -+ * U attenuation for component video. -+ * -+ * Stored in 1.9 fixed point. -+ */ -+# define TV_AU_MASK 0x000003ff -+# define TV_AU_SHIFT 0 -+ -+#define TV_CSC_V _MMIO(0x68020) -+# define TV_RV_MASK 0x0fff0000 -+# define TV_RV_SHIFT 16 -+# define TV_GV_MASK 0x000007ff -+# define TV_GV_SHIFT 0 -+ -+#define TV_CSC_V2 _MMIO(0x68024) -+# define TV_BV_MASK 0x07ff0000 -+# define TV_BV_SHIFT 16 -+/* -+ * V attenuation for component video. -+ * -+ * Stored in 1.9 fixed point. -+ */ -+# define TV_AV_MASK 0x000007ff -+# define TV_AV_SHIFT 0 -+ -+#define TV_CLR_KNOBS _MMIO(0x68028) -+/* 2s-complement brightness adjustment */ -+# define TV_BRIGHTNESS_MASK 0xff000000 -+# define TV_BRIGHTNESS_SHIFT 24 -+/* Contrast adjustment, as a 2.6 unsigned floating point number */ -+# define TV_CONTRAST_MASK 0x00ff0000 -+# define TV_CONTRAST_SHIFT 16 -+/* Saturation adjustment, as a 2.6 unsigned floating point number */ -+# define TV_SATURATION_MASK 0x0000ff00 -+# define TV_SATURATION_SHIFT 8 -+/* Hue adjustment, as an integer phase angle in degrees */ -+# define TV_HUE_MASK 0x000000ff -+# define TV_HUE_SHIFT 0 -+ -+#define TV_CLR_LEVEL _MMIO(0x6802c) -+/* Controls the DAC level for black */ -+# define TV_BLACK_LEVEL_MASK 0x01ff0000 -+# define TV_BLACK_LEVEL_SHIFT 16 -+/* Controls the DAC level for blanking */ -+# define TV_BLANK_LEVEL_MASK 0x000001ff -+# define TV_BLANK_LEVEL_SHIFT 0 -+ -+#define TV_H_CTL_1 _MMIO(0x68030) -+/* Number of pixels in the hsync. */ -+# define TV_HSYNC_END_MASK 0x1fff0000 -+# define TV_HSYNC_END_SHIFT 16 -+/* Total number of pixels minus one in the line (display and blanking). */ -+# define TV_HTOTAL_MASK 0x00001fff -+# define TV_HTOTAL_SHIFT 0 -+ -+#define TV_H_CTL_2 _MMIO(0x68034) -+/* Enables the colorburst (needed for non-component color) */ -+# define TV_BURST_ENA (1 << 31) -+/* Offset of the colorburst from the start of hsync, in pixels minus one. */ -+# define TV_HBURST_START_SHIFT 16 -+# define TV_HBURST_START_MASK 0x1fff0000 -+/* Length of the colorburst */ -+# define TV_HBURST_LEN_SHIFT 0 -+# define TV_HBURST_LEN_MASK 0x0001fff -+ -+#define TV_H_CTL_3 _MMIO(0x68038) -+/* End of hblank, measured in pixels minus one from start of hsync */ -+# define TV_HBLANK_END_SHIFT 16 -+# define TV_HBLANK_END_MASK 0x1fff0000 -+/* Start of hblank, measured in pixels minus one from start of hsync */ -+# define TV_HBLANK_START_SHIFT 0 -+# define TV_HBLANK_START_MASK 0x0001fff -+ -+#define TV_V_CTL_1 _MMIO(0x6803c) -+/* XXX */ -+# define TV_NBR_END_SHIFT 16 -+# define TV_NBR_END_MASK 0x07ff0000 -+/* XXX */ -+# define TV_VI_END_F1_SHIFT 8 -+# define TV_VI_END_F1_MASK 0x00003f00 -+/* XXX */ -+# define TV_VI_END_F2_SHIFT 0 -+# define TV_VI_END_F2_MASK 0x0000003f -+ -+#define TV_V_CTL_2 _MMIO(0x68040) -+/* Length of vsync, in half lines */ -+# define TV_VSYNC_LEN_MASK 0x07ff0000 -+# define TV_VSYNC_LEN_SHIFT 16 -+/* Offset of the start of vsync in field 1, measured in one less than the -+ * number of half lines. -+ */ -+# define TV_VSYNC_START_F1_MASK 0x00007f00 -+# define TV_VSYNC_START_F1_SHIFT 8 -+/* -+ * Offset of the start of vsync in field 2, measured in one less than the -+ * number of half lines. -+ */ -+# define TV_VSYNC_START_F2_MASK 0x0000007f -+# define TV_VSYNC_START_F2_SHIFT 0 -+ -+#define TV_V_CTL_3 _MMIO(0x68044) -+/* Enables generation of the equalization signal */ -+# define TV_EQUAL_ENA (1 << 31) -+/* Length of vsync, in half lines */ -+# define TV_VEQ_LEN_MASK 0x007f0000 -+# define TV_VEQ_LEN_SHIFT 16 -+/* Offset of the start of equalization in field 1, measured in one less than -+ * the number of half lines. -+ */ -+# define TV_VEQ_START_F1_MASK 0x0007f00 -+# define TV_VEQ_START_F1_SHIFT 8 -+/* -+ * Offset of the start of equalization in field 2, measured in one less than -+ * the number of half lines. -+ */ -+# define TV_VEQ_START_F2_MASK 0x000007f -+# define TV_VEQ_START_F2_SHIFT 0 -+ -+#define TV_V_CTL_4 _MMIO(0x68048) -+/* -+ * Offset to start of vertical colorburst, measured in one less than the -+ * number of lines from vertical start. -+ */ -+# define TV_VBURST_START_F1_MASK 0x003f0000 -+# define TV_VBURST_START_F1_SHIFT 16 -+/* -+ * Offset to the end of vertical colorburst, measured in one less than the -+ * number of lines from the start of NBR. -+ */ -+# define TV_VBURST_END_F1_MASK 0x000000ff -+# define TV_VBURST_END_F1_SHIFT 0 -+ -+#define TV_V_CTL_5 _MMIO(0x6804c) -+/* -+ * Offset to start of vertical colorburst, measured in one less than the -+ * number of lines from vertical start. -+ */ -+# define TV_VBURST_START_F2_MASK 0x003f0000 -+# define TV_VBURST_START_F2_SHIFT 16 -+/* -+ * Offset to the end of vertical colorburst, measured in one less than the -+ * number of lines from the start of NBR. -+ */ -+# define TV_VBURST_END_F2_MASK 0x000000ff -+# define TV_VBURST_END_F2_SHIFT 0 -+ -+#define TV_V_CTL_6 _MMIO(0x68050) -+/* -+ * Offset to start of vertical colorburst, measured in one less than the -+ * number of lines from vertical start. -+ */ -+# define TV_VBURST_START_F3_MASK 0x003f0000 -+# define TV_VBURST_START_F3_SHIFT 16 -+/* -+ * Offset to the end of vertical colorburst, measured in one less than the -+ * number of lines from the start of NBR. -+ */ -+# define TV_VBURST_END_F3_MASK 0x000000ff -+# define TV_VBURST_END_F3_SHIFT 0 -+ -+#define TV_V_CTL_7 _MMIO(0x68054) -+/* -+ * Offset to start of vertical colorburst, measured in one less than the -+ * number of lines from vertical start. -+ */ -+# define TV_VBURST_START_F4_MASK 0x003f0000 -+# define TV_VBURST_START_F4_SHIFT 16 -+/* -+ * Offset to the end of vertical colorburst, measured in one less than the -+ * number of lines from the start of NBR. -+ */ -+# define TV_VBURST_END_F4_MASK 0x000000ff -+# define TV_VBURST_END_F4_SHIFT 0 -+ -+#define TV_SC_CTL_1 _MMIO(0x68060) -+/* Turns on the first subcarrier phase generation DDA */ -+# define TV_SC_DDA1_EN (1 << 31) -+/* Turns on the first subcarrier phase generation DDA */ -+# define TV_SC_DDA2_EN (1 << 30) -+/* Turns on the first subcarrier phase generation DDA */ -+# define TV_SC_DDA3_EN (1 << 29) -+/* Sets the subcarrier DDA to reset frequency every other field */ -+# define TV_SC_RESET_EVERY_2 (0 << 24) -+/* Sets the subcarrier DDA to reset frequency every fourth field */ -+# define TV_SC_RESET_EVERY_4 (1 << 24) -+/* Sets the subcarrier DDA to reset frequency every eighth field */ -+# define TV_SC_RESET_EVERY_8 (2 << 24) -+/* Sets the subcarrier DDA to never reset the frequency */ -+# define TV_SC_RESET_NEVER (3 << 24) -+/* Sets the peak amplitude of the colorburst.*/ -+# define TV_BURST_LEVEL_MASK 0x00ff0000 -+# define TV_BURST_LEVEL_SHIFT 16 -+/* Sets the increment of the first subcarrier phase generation DDA */ -+# define TV_SCDDA1_INC_MASK 0x00000fff -+# define TV_SCDDA1_INC_SHIFT 0 -+ -+#define TV_SC_CTL_2 _MMIO(0x68064) -+/* Sets the rollover for the second subcarrier phase generation DDA */ -+# define TV_SCDDA2_SIZE_MASK 0x7fff0000 -+# define TV_SCDDA2_SIZE_SHIFT 16 -+/* Sets the increent of the second subcarrier phase generation DDA */ -+# define TV_SCDDA2_INC_MASK 0x00007fff -+# define TV_SCDDA2_INC_SHIFT 0 -+ -+#define TV_SC_CTL_3 _MMIO(0x68068) -+/* Sets the rollover for the third subcarrier phase generation DDA */ -+# define TV_SCDDA3_SIZE_MASK 0x7fff0000 -+# define TV_SCDDA3_SIZE_SHIFT 16 -+/* Sets the increent of the third subcarrier phase generation DDA */ -+# define TV_SCDDA3_INC_MASK 0x00007fff -+# define TV_SCDDA3_INC_SHIFT 0 -+ -+#define TV_WIN_POS _MMIO(0x68070) -+/* X coordinate of the display from the start of horizontal active */ -+# define TV_XPOS_MASK 0x1fff0000 -+# define TV_XPOS_SHIFT 16 -+/* Y coordinate of the display from the start of vertical active (NBR) */ -+# define TV_YPOS_MASK 0x00000fff -+# define TV_YPOS_SHIFT 0 -+ -+#define TV_WIN_SIZE _MMIO(0x68074) -+/* Horizontal size of the display window, measured in pixels*/ -+# define TV_XSIZE_MASK 0x1fff0000 -+# define TV_XSIZE_SHIFT 16 -+/* -+ * Vertical size of the display window, measured in pixels. -+ * -+ * Must be even for interlaced modes. -+ */ -+# define TV_YSIZE_MASK 0x00000fff -+# define TV_YSIZE_SHIFT 0 -+ -+#define TV_FILTER_CTL_1 _MMIO(0x68080) -+/* -+ * Enables automatic scaling calculation. -+ * -+ * If set, the rest of the registers are ignored, and the calculated values can -+ * be read back from the register. -+ */ -+# define TV_AUTO_SCALE (1 << 31) -+/* -+ * Disables the vertical filter. -+ * -+ * This is required on modes more than 1024 pixels wide */ -+# define TV_V_FILTER_BYPASS (1 << 29) -+/* Enables adaptive vertical filtering */ -+# define TV_VADAPT (1 << 28) -+# define TV_VADAPT_MODE_MASK (3 << 26) -+/* Selects the least adaptive vertical filtering mode */ -+# define TV_VADAPT_MODE_LEAST (0 << 26) -+/* Selects the moderately adaptive vertical filtering mode */ -+# define TV_VADAPT_MODE_MODERATE (1 << 26) -+/* Selects the most adaptive vertical filtering mode */ -+# define TV_VADAPT_MODE_MOST (3 << 26) -+/* -+ * Sets the horizontal scaling factor. -+ * -+ * This should be the fractional part of the horizontal scaling factor divided -+ * by the oversampling rate. TV_HSCALE should be less than 1, and set to: -+ * -+ * (src width - 1) / ((oversample * dest width) - 1) -+ */ -+# define TV_HSCALE_FRAC_MASK 0x00003fff -+# define TV_HSCALE_FRAC_SHIFT 0 -+ -+#define TV_FILTER_CTL_2 _MMIO(0x68084) -+/* -+ * Sets the integer part of the 3.15 fixed-point vertical scaling factor. -+ * -+ * TV_VSCALE should be (src height - 1) / ((interlace * dest height) - 1) -+ */ -+# define TV_VSCALE_INT_MASK 0x00038000 -+# define TV_VSCALE_INT_SHIFT 15 -+/* -+ * Sets the fractional part of the 3.15 fixed-point vertical scaling factor. -+ * -+ * \sa TV_VSCALE_INT_MASK -+ */ -+# define TV_VSCALE_FRAC_MASK 0x00007fff -+# define TV_VSCALE_FRAC_SHIFT 0 -+ -+#define TV_FILTER_CTL_3 _MMIO(0x68088) -+/* -+ * Sets the integer part of the 3.15 fixed-point vertical scaling factor. -+ * -+ * TV_VSCALE should be (src height - 1) / (1/4 * (dest height - 1)) -+ * -+ * For progressive modes, TV_VSCALE_IP_INT should be set to zeroes. -+ */ -+# define TV_VSCALE_IP_INT_MASK 0x00038000 -+# define TV_VSCALE_IP_INT_SHIFT 15 -+/* -+ * Sets the fractional part of the 3.15 fixed-point vertical scaling factor. -+ * -+ * For progressive modes, TV_VSCALE_IP_INT should be set to zeroes. -+ * -+ * \sa TV_VSCALE_IP_INT_MASK -+ */ -+# define TV_VSCALE_IP_FRAC_MASK 0x00007fff -+# define TV_VSCALE_IP_FRAC_SHIFT 0 -+ -+#define TV_CC_CONTROL _MMIO(0x68090) -+# define TV_CC_ENABLE (1 << 31) -+/* -+ * Specifies which field to send the CC data in. -+ * -+ * CC data is usually sent in field 0. -+ */ -+# define TV_CC_FID_MASK (1 << 27) -+# define TV_CC_FID_SHIFT 27 -+/* Sets the horizontal position of the CC data. Usually 135. */ -+# define TV_CC_HOFF_MASK 0x03ff0000 -+# define TV_CC_HOFF_SHIFT 16 -+/* Sets the vertical position of the CC data. Usually 21 */ -+# define TV_CC_LINE_MASK 0x0000003f -+# define TV_CC_LINE_SHIFT 0 -+ -+#define TV_CC_DATA _MMIO(0x68094) -+# define TV_CC_RDY (1 << 31) -+/* Second word of CC data to be transmitted. */ -+# define TV_CC_DATA_2_MASK 0x007f0000 -+# define TV_CC_DATA_2_SHIFT 16 -+/* First word of CC data to be transmitted. */ -+# define TV_CC_DATA_1_MASK 0x0000007f -+# define TV_CC_DATA_1_SHIFT 0 -+ -+#define TV_H_LUMA(i) _MMIO(0x68100 + (i) * 4) /* 60 registers */ -+#define TV_H_CHROMA(i) _MMIO(0x68200 + (i) * 4) /* 60 registers */ -+#define TV_V_LUMA(i) _MMIO(0x68300 + (i) * 4) /* 43 registers */ -+#define TV_V_CHROMA(i) _MMIO(0x68400 + (i) * 4) /* 43 registers */ -+ -+/* Display Port */ -+#define DP_A _MMIO(0x64000) /* eDP */ -+#define DP_B _MMIO(0x64100) -+#define DP_C _MMIO(0x64200) -+#define DP_D _MMIO(0x64300) -+ -+#define VLV_DP_B _MMIO(VLV_DISPLAY_BASE + 0x64100) -+#define VLV_DP_C _MMIO(VLV_DISPLAY_BASE + 0x64200) -+#define CHV_DP_D _MMIO(VLV_DISPLAY_BASE + 0x64300) -+ -+#define DP_PORT_EN (1 << 31) -+#define DP_PIPE_SEL_SHIFT 30 -+#define DP_PIPE_SEL_MASK (1 << 30) -+#define DP_PIPE_SEL(pipe) ((pipe) << 30) -+#define DP_PIPE_SEL_SHIFT_IVB 29 -+#define DP_PIPE_SEL_MASK_IVB (3 << 29) -+#define DP_PIPE_SEL_IVB(pipe) ((pipe) << 29) -+#define DP_PIPE_SEL_SHIFT_CHV 16 -+#define DP_PIPE_SEL_MASK_CHV (3 << 16) -+#define DP_PIPE_SEL_CHV(pipe) ((pipe) << 16) -+ -+/* Link training mode - select a suitable mode for each stage */ -+#define DP_LINK_TRAIN_PAT_1 (0 << 28) -+#define DP_LINK_TRAIN_PAT_2 (1 << 28) -+#define DP_LINK_TRAIN_PAT_IDLE (2 << 28) -+#define DP_LINK_TRAIN_OFF (3 << 28) -+#define DP_LINK_TRAIN_MASK (3 << 28) -+#define DP_LINK_TRAIN_SHIFT 28 -+ -+/* CPT Link training mode */ -+#define DP_LINK_TRAIN_PAT_1_CPT (0 << 8) -+#define DP_LINK_TRAIN_PAT_2_CPT (1 << 8) -+#define DP_LINK_TRAIN_PAT_IDLE_CPT (2 << 8) -+#define DP_LINK_TRAIN_OFF_CPT (3 << 8) -+#define DP_LINK_TRAIN_MASK_CPT (7 << 8) -+#define DP_LINK_TRAIN_SHIFT_CPT 8 -+ -+/* Signal voltages. These are mostly controlled by the other end */ -+#define DP_VOLTAGE_0_4 (0 << 25) -+#define DP_VOLTAGE_0_6 (1 << 25) -+#define DP_VOLTAGE_0_8 (2 << 25) -+#define DP_VOLTAGE_1_2 (3 << 25) -+#define DP_VOLTAGE_MASK (7 << 25) -+#define DP_VOLTAGE_SHIFT 25 -+ -+/* Signal pre-emphasis levels, like voltages, the other end tells us what -+ * they want -+ */ -+#define DP_PRE_EMPHASIS_0 (0 << 22) -+#define DP_PRE_EMPHASIS_3_5 (1 << 22) -+#define DP_PRE_EMPHASIS_6 (2 << 22) -+#define DP_PRE_EMPHASIS_9_5 (3 << 22) -+#define DP_PRE_EMPHASIS_MASK (7 << 22) -+#define DP_PRE_EMPHASIS_SHIFT 22 -+ -+/* How many wires to use. I guess 3 was too hard */ -+#define DP_PORT_WIDTH(width) (((width) - 1) << 19) -+#define DP_PORT_WIDTH_MASK (7 << 19) -+#define DP_PORT_WIDTH_SHIFT 19 -+ -+/* Mystic DPCD version 1.1 special mode */ -+#define DP_ENHANCED_FRAMING (1 << 18) -+ -+/* eDP */ -+#define DP_PLL_FREQ_270MHZ (0 << 16) -+#define DP_PLL_FREQ_162MHZ (1 << 16) -+#define DP_PLL_FREQ_MASK (3 << 16) -+ -+/* locked once port is enabled */ -+#define DP_PORT_REVERSAL (1 << 15) -+ -+/* eDP */ -+#define DP_PLL_ENABLE (1 << 14) -+ -+/* sends the clock on lane 15 of the PEG for debug */ -+#define DP_CLOCK_OUTPUT_ENABLE (1 << 13) -+ -+#define DP_SCRAMBLING_DISABLE (1 << 12) -+#define DP_SCRAMBLING_DISABLE_IRONLAKE (1 << 7) -+ -+/* limit RGB values to avoid confusing TVs */ -+#define DP_COLOR_RANGE_16_235 (1 << 8) -+ -+/* Turn on the audio link */ -+#define DP_AUDIO_OUTPUT_ENABLE (1 << 6) -+ -+/* vs and hs sync polarity */ -+#define DP_SYNC_VS_HIGH (1 << 4) -+#define DP_SYNC_HS_HIGH (1 << 3) -+ -+/* A fantasy */ -+#define DP_DETECTED (1 << 2) -+ -+/* The aux channel provides a way to talk to the -+ * signal sink for DDC etc. Max packet size supported -+ * is 20 bytes in each direction, hence the 5 fixed -+ * data registers -+ */ -+#define _DPA_AUX_CH_CTL (DISPLAY_MMIO_BASE(dev_priv) + 0x64010) -+#define _DPA_AUX_CH_DATA1 (DISPLAY_MMIO_BASE(dev_priv) + 0x64014) -+#define _DPA_AUX_CH_DATA2 (DISPLAY_MMIO_BASE(dev_priv) + 0x64018) -+#define _DPA_AUX_CH_DATA3 (DISPLAY_MMIO_BASE(dev_priv) + 0x6401c) -+#define _DPA_AUX_CH_DATA4 (DISPLAY_MMIO_BASE(dev_priv) + 0x64020) -+#define _DPA_AUX_CH_DATA5 (DISPLAY_MMIO_BASE(dev_priv) + 0x64024) -+ -+#define _DPB_AUX_CH_CTL (DISPLAY_MMIO_BASE(dev_priv) + 0x64110) -+#define _DPB_AUX_CH_DATA1 (DISPLAY_MMIO_BASE(dev_priv) + 0x64114) -+#define _DPB_AUX_CH_DATA2 (DISPLAY_MMIO_BASE(dev_priv) + 0x64118) -+#define _DPB_AUX_CH_DATA3 (DISPLAY_MMIO_BASE(dev_priv) + 0x6411c) -+#define _DPB_AUX_CH_DATA4 (DISPLAY_MMIO_BASE(dev_priv) + 0x64120) -+#define _DPB_AUX_CH_DATA5 (DISPLAY_MMIO_BASE(dev_priv) + 0x64124) -+ -+#define _DPC_AUX_CH_CTL (DISPLAY_MMIO_BASE(dev_priv) + 0x64210) -+#define _DPC_AUX_CH_DATA1 (DISPLAY_MMIO_BASE(dev_priv) + 0x64214) -+#define _DPC_AUX_CH_DATA2 (DISPLAY_MMIO_BASE(dev_priv) + 0x64218) -+#define _DPC_AUX_CH_DATA3 (DISPLAY_MMIO_BASE(dev_priv) + 0x6421c) -+#define _DPC_AUX_CH_DATA4 (DISPLAY_MMIO_BASE(dev_priv) + 0x64220) -+#define _DPC_AUX_CH_DATA5 (DISPLAY_MMIO_BASE(dev_priv) + 0x64224) -+ -+#define _DPD_AUX_CH_CTL (DISPLAY_MMIO_BASE(dev_priv) + 0x64310) -+#define _DPD_AUX_CH_DATA1 (DISPLAY_MMIO_BASE(dev_priv) + 0x64314) -+#define _DPD_AUX_CH_DATA2 (DISPLAY_MMIO_BASE(dev_priv) + 0x64318) -+#define _DPD_AUX_CH_DATA3 (DISPLAY_MMIO_BASE(dev_priv) + 0x6431c) -+#define _DPD_AUX_CH_DATA4 (DISPLAY_MMIO_BASE(dev_priv) + 0x64320) -+#define _DPD_AUX_CH_DATA5 (DISPLAY_MMIO_BASE(dev_priv) + 0x64324) -+ -+#define _DPE_AUX_CH_CTL (DISPLAY_MMIO_BASE(dev_priv) + 0x64410) -+#define _DPE_AUX_CH_DATA1 (DISPLAY_MMIO_BASE(dev_priv) + 0x64414) -+#define _DPE_AUX_CH_DATA2 (DISPLAY_MMIO_BASE(dev_priv) + 0x64418) -+#define _DPE_AUX_CH_DATA3 (DISPLAY_MMIO_BASE(dev_priv) + 0x6441c) -+#define _DPE_AUX_CH_DATA4 (DISPLAY_MMIO_BASE(dev_priv) + 0x64420) -+#define _DPE_AUX_CH_DATA5 (DISPLAY_MMIO_BASE(dev_priv) + 0x64424) -+ -+#define _DPF_AUX_CH_CTL (DISPLAY_MMIO_BASE(dev_priv) + 0x64510) -+#define _DPF_AUX_CH_DATA1 (DISPLAY_MMIO_BASE(dev_priv) + 0x64514) -+#define _DPF_AUX_CH_DATA2 (DISPLAY_MMIO_BASE(dev_priv) + 0x64518) -+#define _DPF_AUX_CH_DATA3 (DISPLAY_MMIO_BASE(dev_priv) + 0x6451c) -+#define _DPF_AUX_CH_DATA4 (DISPLAY_MMIO_BASE(dev_priv) + 0x64520) -+#define _DPF_AUX_CH_DATA5 (DISPLAY_MMIO_BASE(dev_priv) + 0x64524) -+ -+#define DP_AUX_CH_CTL(aux_ch) _MMIO_PORT(aux_ch, _DPA_AUX_CH_CTL, _DPB_AUX_CH_CTL) -+#define DP_AUX_CH_DATA(aux_ch, i) _MMIO(_PORT(aux_ch, _DPA_AUX_CH_DATA1, _DPB_AUX_CH_DATA1) + (i) * 4) /* 5 registers */ -+ -+#define DP_AUX_CH_CTL_SEND_BUSY (1 << 31) -+#define DP_AUX_CH_CTL_DONE (1 << 30) -+#define DP_AUX_CH_CTL_INTERRUPT (1 << 29) -+#define DP_AUX_CH_CTL_TIME_OUT_ERROR (1 << 28) -+#define DP_AUX_CH_CTL_TIME_OUT_400us (0 << 26) -+#define DP_AUX_CH_CTL_TIME_OUT_600us (1 << 26) -+#define DP_AUX_CH_CTL_TIME_OUT_800us (2 << 26) -+#define DP_AUX_CH_CTL_TIME_OUT_MAX (3 << 26) /* Varies per platform */ -+#define DP_AUX_CH_CTL_TIME_OUT_MASK (3 << 26) -+#define DP_AUX_CH_CTL_RECEIVE_ERROR (1 << 25) -+#define DP_AUX_CH_CTL_MESSAGE_SIZE_MASK (0x1f << 20) -+#define DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT 20 -+#define DP_AUX_CH_CTL_PRECHARGE_2US_MASK (0xf << 16) -+#define DP_AUX_CH_CTL_PRECHARGE_2US_SHIFT 16 -+#define DP_AUX_CH_CTL_AUX_AKSV_SELECT (1 << 15) -+#define DP_AUX_CH_CTL_MANCHESTER_TEST (1 << 14) -+#define DP_AUX_CH_CTL_SYNC_TEST (1 << 13) -+#define DP_AUX_CH_CTL_DEGLITCH_TEST (1 << 12) -+#define DP_AUX_CH_CTL_PRECHARGE_TEST (1 << 11) -+#define DP_AUX_CH_CTL_BIT_CLOCK_2X_MASK (0x7ff) -+#define DP_AUX_CH_CTL_BIT_CLOCK_2X_SHIFT 0 -+#define DP_AUX_CH_CTL_PSR_DATA_AUX_REG_SKL (1 << 14) -+#define DP_AUX_CH_CTL_FS_DATA_AUX_REG_SKL (1 << 13) -+#define DP_AUX_CH_CTL_GTC_DATA_AUX_REG_SKL (1 << 12) -+#define DP_AUX_CH_CTL_TBT_IO (1 << 11) -+#define DP_AUX_CH_CTL_FW_SYNC_PULSE_SKL_MASK (0x1f << 5) -+#define DP_AUX_CH_CTL_FW_SYNC_PULSE_SKL(c) (((c) - 1) << 5) -+#define DP_AUX_CH_CTL_SYNC_PULSE_SKL(c) ((c) - 1) -+ -+/* -+ * Computing GMCH M and N values for the Display Port link -+ * -+ * GMCH M/N = dot clock * bytes per pixel / ls_clk * # of lanes -+ * -+ * ls_clk (we assume) is the DP link clock (1.62 or 2.7 GHz) -+ * -+ * The GMCH value is used internally -+ * -+ * bytes_per_pixel is the number of bytes coming out of the plane, -+ * which is after the LUTs, so we want the bytes for our color format. -+ * For our current usage, this is always 3, one byte for R, G and B. -+ */ -+#define _PIPEA_DATA_M_G4X 0x70050 -+#define _PIPEB_DATA_M_G4X 0x71050 -+ -+/* Transfer unit size for display port - 1, default is 0x3f (for TU size 64) */ -+#define TU_SIZE(x) (((x) - 1) << 25) /* default size 64 */ -+#define TU_SIZE_SHIFT 25 -+#define TU_SIZE_MASK (0x3f << 25) -+ -+#define DATA_LINK_M_N_MASK (0xffffff) -+#define DATA_LINK_N_MAX (0x800000) -+ -+#define _PIPEA_DATA_N_G4X 0x70054 -+#define _PIPEB_DATA_N_G4X 0x71054 -+#define PIPE_GMCH_DATA_N_MASK (0xffffff) -+ -+/* -+ * Computing Link M and N values for the Display Port link -+ * -+ * Link M / N = pixel_clock / ls_clk -+ * -+ * (the DP spec calls pixel_clock the 'strm_clk') -+ * -+ * The Link value is transmitted in the Main Stream -+ * Attributes and VB-ID. -+ */ -+ -+#define _PIPEA_LINK_M_G4X 0x70060 -+#define _PIPEB_LINK_M_G4X 0x71060 -+#define PIPEA_DP_LINK_M_MASK (0xffffff) -+ -+#define _PIPEA_LINK_N_G4X 0x70064 -+#define _PIPEB_LINK_N_G4X 0x71064 -+#define PIPEA_DP_LINK_N_MASK (0xffffff) -+ -+#define PIPE_DATA_M_G4X(pipe) _MMIO_PIPE(pipe, _PIPEA_DATA_M_G4X, _PIPEB_DATA_M_G4X) -+#define PIPE_DATA_N_G4X(pipe) _MMIO_PIPE(pipe, _PIPEA_DATA_N_G4X, _PIPEB_DATA_N_G4X) -+#define PIPE_LINK_M_G4X(pipe) _MMIO_PIPE(pipe, _PIPEA_LINK_M_G4X, _PIPEB_LINK_M_G4X) -+#define PIPE_LINK_N_G4X(pipe) _MMIO_PIPE(pipe, _PIPEA_LINK_N_G4X, _PIPEB_LINK_N_G4X) -+ -+/* Display & cursor control */ -+ -+/* Pipe A */ -+#define _PIPEADSL 0x70000 -+#define DSL_LINEMASK_GEN2 0x00000fff -+#define DSL_LINEMASK_GEN3 0x00001fff -+#define _PIPEACONF 0x70008 -+#define PIPECONF_ENABLE (1 << 31) -+#define PIPECONF_DISABLE 0 -+#define PIPECONF_DOUBLE_WIDE (1 << 30) -+#define I965_PIPECONF_ACTIVE (1 << 30) -+#define PIPECONF_DSI_PLL_LOCKED (1 << 29) /* vlv & pipe A only */ -+#define PIPECONF_FRAME_START_DELAY_MASK (3 << 27) -+#define PIPECONF_SINGLE_WIDE 0 -+#define PIPECONF_PIPE_UNLOCKED 0 -+#define PIPECONF_PIPE_LOCKED (1 << 25) -+#define PIPECONF_FORCE_BORDER (1 << 25) -+#define PIPECONF_GAMMA_MODE_MASK_I9XX (1 << 24) /* gmch */ -+#define PIPECONF_GAMMA_MODE_MASK_ILK (3 << 24) /* ilk-ivb */ -+#define PIPECONF_GAMMA_MODE_8BIT (0 << 24) /* gmch,ilk-ivb */ -+#define PIPECONF_GAMMA_MODE_10BIT (1 << 24) /* gmch,ilk-ivb */ -+#define PIPECONF_GAMMA_MODE_12BIT (2 << 24) /* ilk-ivb */ -+#define PIPECONF_GAMMA_MODE_SPLIT (3 << 24) /* ivb */ -+#define PIPECONF_GAMMA_MODE(x) ((x) << 24) /* pass in GAMMA_MODE_MODE_* */ -+#define PIPECONF_GAMMA_MODE_SHIFT 24 -+#define PIPECONF_INTERLACE_MASK (7 << 21) -+#define PIPECONF_INTERLACE_MASK_HSW (3 << 21) -+/* Note that pre-gen3 does not support interlaced display directly. Panel -+ * fitting must be disabled on pre-ilk for interlaced. */ -+#define PIPECONF_PROGRESSIVE (0 << 21) -+#define PIPECONF_INTERLACE_W_SYNC_SHIFT_PANEL (4 << 21) /* gen4 only */ -+#define PIPECONF_INTERLACE_W_SYNC_SHIFT (5 << 21) /* gen4 only */ -+#define PIPECONF_INTERLACE_W_FIELD_INDICATION (6 << 21) -+#define PIPECONF_INTERLACE_FIELD_0_ONLY (7 << 21) /* gen3 only */ -+/* Ironlake and later have a complete new set of values for interlaced. PFIT -+ * means panel fitter required, PF means progressive fetch, DBL means power -+ * saving pixel doubling. */ -+#define PIPECONF_PFIT_PF_INTERLACED_ILK (1 << 21) -+#define PIPECONF_INTERLACED_ILK (3 << 21) -+#define PIPECONF_INTERLACED_DBL_ILK (4 << 21) /* ilk/snb only */ -+#define PIPECONF_PFIT_PF_INTERLACED_DBL_ILK (5 << 21) /* ilk/snb only */ -+#define PIPECONF_INTERLACE_MODE_MASK (7 << 21) -+#define PIPECONF_EDP_RR_MODE_SWITCH (1 << 20) -+#define PIPECONF_CXSR_DOWNCLOCK (1 << 16) -+#define PIPECONF_EDP_RR_MODE_SWITCH_VLV (1 << 14) -+#define PIPECONF_COLOR_RANGE_SELECT (1 << 13) -+#define PIPECONF_BPC_MASK (0x7 << 5) -+#define PIPECONF_8BPC (0 << 5) -+#define PIPECONF_10BPC (1 << 5) -+#define PIPECONF_6BPC (2 << 5) -+#define PIPECONF_12BPC (3 << 5) -+#define PIPECONF_DITHER_EN (1 << 4) -+#define PIPECONF_DITHER_TYPE_MASK (0x0000000c) -+#define PIPECONF_DITHER_TYPE_SP (0 << 2) -+#define PIPECONF_DITHER_TYPE_ST1 (1 << 2) -+#define PIPECONF_DITHER_TYPE_ST2 (2 << 2) -+#define PIPECONF_DITHER_TYPE_TEMP (3 << 2) -+#define _PIPEASTAT 0x70024 -+#define PIPE_FIFO_UNDERRUN_STATUS (1UL << 31) -+#define SPRITE1_FLIP_DONE_INT_EN_VLV (1UL << 30) -+#define PIPE_CRC_ERROR_ENABLE (1UL << 29) -+#define PIPE_CRC_DONE_ENABLE (1UL << 28) -+#define PERF_COUNTER2_INTERRUPT_EN (1UL << 27) -+#define PIPE_GMBUS_EVENT_ENABLE (1UL << 27) -+#define PLANE_FLIP_DONE_INT_EN_VLV (1UL << 26) -+#define PIPE_HOTPLUG_INTERRUPT_ENABLE (1UL << 26) -+#define PIPE_VSYNC_INTERRUPT_ENABLE (1UL << 25) -+#define PIPE_DISPLAY_LINE_COMPARE_ENABLE (1UL << 24) -+#define PIPE_DPST_EVENT_ENABLE (1UL << 23) -+#define SPRITE0_FLIP_DONE_INT_EN_VLV (1UL << 22) -+#define PIPE_LEGACY_BLC_EVENT_ENABLE (1UL << 22) -+#define PIPE_ODD_FIELD_INTERRUPT_ENABLE (1UL << 21) -+#define PIPE_EVEN_FIELD_INTERRUPT_ENABLE (1UL << 20) -+#define PIPE_B_PSR_INTERRUPT_ENABLE_VLV (1UL << 19) -+#define PERF_COUNTER_INTERRUPT_EN (1UL << 19) -+#define PIPE_HOTPLUG_TV_INTERRUPT_ENABLE (1UL << 18) /* pre-965 */ -+#define PIPE_START_VBLANK_INTERRUPT_ENABLE (1UL << 18) /* 965 or later */ -+#define PIPE_FRAMESTART_INTERRUPT_ENABLE (1UL << 17) -+#define PIPE_VBLANK_INTERRUPT_ENABLE (1UL << 17) -+#define PIPEA_HBLANK_INT_EN_VLV (1UL << 16) -+#define PIPE_OVERLAY_UPDATED_ENABLE (1UL << 16) -+#define SPRITE1_FLIP_DONE_INT_STATUS_VLV (1UL << 15) -+#define SPRITE0_FLIP_DONE_INT_STATUS_VLV (1UL << 14) -+#define PIPE_CRC_ERROR_INTERRUPT_STATUS (1UL << 13) -+#define PIPE_CRC_DONE_INTERRUPT_STATUS (1UL << 12) -+#define PERF_COUNTER2_INTERRUPT_STATUS (1UL << 11) -+#define PIPE_GMBUS_INTERRUPT_STATUS (1UL << 11) -+#define PLANE_FLIP_DONE_INT_STATUS_VLV (1UL << 10) -+#define PIPE_HOTPLUG_INTERRUPT_STATUS (1UL << 10) -+#define PIPE_VSYNC_INTERRUPT_STATUS (1UL << 9) -+#define PIPE_DISPLAY_LINE_COMPARE_STATUS (1UL << 8) -+#define PIPE_DPST_EVENT_STATUS (1UL << 7) -+#define PIPE_A_PSR_STATUS_VLV (1UL << 6) -+#define PIPE_LEGACY_BLC_EVENT_STATUS (1UL << 6) -+#define PIPE_ODD_FIELD_INTERRUPT_STATUS (1UL << 5) -+#define PIPE_EVEN_FIELD_INTERRUPT_STATUS (1UL << 4) -+#define PIPE_B_PSR_STATUS_VLV (1UL << 3) -+#define PERF_COUNTER_INTERRUPT_STATUS (1UL << 3) -+#define PIPE_HOTPLUG_TV_INTERRUPT_STATUS (1UL << 2) /* pre-965 */ -+#define PIPE_START_VBLANK_INTERRUPT_STATUS (1UL << 2) /* 965 or later */ -+#define PIPE_FRAMESTART_INTERRUPT_STATUS (1UL << 1) -+#define PIPE_VBLANK_INTERRUPT_STATUS (1UL << 1) -+#define PIPE_HBLANK_INT_STATUS (1UL << 0) -+#define PIPE_OVERLAY_UPDATED_STATUS (1UL << 0) -+ -+#define PIPESTAT_INT_ENABLE_MASK 0x7fff0000 -+#define PIPESTAT_INT_STATUS_MASK 0x0000ffff -+ -+#define PIPE_A_OFFSET 0x70000 -+#define PIPE_B_OFFSET 0x71000 -+#define PIPE_C_OFFSET 0x72000 -+#define CHV_PIPE_C_OFFSET 0x74000 -+/* -+ * There's actually no pipe EDP. Some pipe registers have -+ * simply shifted from the pipe to the transcoder, while -+ * keeping their original offset. Thus we need PIPE_EDP_OFFSET -+ * to access such registers in transcoder EDP. -+ */ -+#define PIPE_EDP_OFFSET 0x7f000 -+ -+/* ICL DSI 0 and 1 */ -+#define PIPE_DSI0_OFFSET 0x7b000 -+#define PIPE_DSI1_OFFSET 0x7b800 -+ -+#define PIPECONF(pipe) _MMIO_PIPE2(pipe, _PIPEACONF) -+#define PIPEDSL(pipe) _MMIO_PIPE2(pipe, _PIPEADSL) -+#define PIPEFRAME(pipe) _MMIO_PIPE2(pipe, _PIPEAFRAMEHIGH) -+#define PIPEFRAMEPIXEL(pipe) _MMIO_PIPE2(pipe, _PIPEAFRAMEPIXEL) -+#define PIPESTAT(pipe) _MMIO_PIPE2(pipe, _PIPEASTAT) -+ -+#define _PIPEAGCMAX 0x70010 -+#define _PIPEBGCMAX 0x71010 -+#define PIPEGCMAX(pipe, i) _MMIO_PIPE2(pipe, _PIPEAGCMAX + (i) * 4) -+ -+#define _PIPE_MISC_A 0x70030 -+#define _PIPE_MISC_B 0x71030 -+#define PIPEMISC_YUV420_ENABLE (1 << 27) -+#define PIPEMISC_YUV420_MODE_FULL_BLEND (1 << 26) -+#define PIPEMISC_OUTPUT_COLORSPACE_YUV (1 << 11) -+#define PIPEMISC_DITHER_BPC_MASK (7 << 5) -+#define PIPEMISC_DITHER_8_BPC (0 << 5) -+#define PIPEMISC_DITHER_10_BPC (1 << 5) -+#define PIPEMISC_DITHER_6_BPC (2 << 5) -+#define PIPEMISC_DITHER_12_BPC (3 << 5) -+#define PIPEMISC_DITHER_ENABLE (1 << 4) -+#define PIPEMISC_DITHER_TYPE_MASK (3 << 2) -+#define PIPEMISC_DITHER_TYPE_SP (0 << 2) -+#define PIPEMISC(pipe) _MMIO_PIPE2(pipe, _PIPE_MISC_A) -+ -+/* Skylake+ pipe bottom (background) color */ -+#define _SKL_BOTTOM_COLOR_A 0x70034 -+#define SKL_BOTTOM_COLOR_GAMMA_ENABLE (1 << 31) -+#define SKL_BOTTOM_COLOR_CSC_ENABLE (1 << 30) -+#define SKL_BOTTOM_COLOR(pipe) _MMIO_PIPE2(pipe, _SKL_BOTTOM_COLOR_A) -+ -+#define VLV_DPFLIPSTAT _MMIO(VLV_DISPLAY_BASE + 0x70028) -+#define PIPEB_LINE_COMPARE_INT_EN (1 << 29) -+#define PIPEB_HLINE_INT_EN (1 << 28) -+#define PIPEB_VBLANK_INT_EN (1 << 27) -+#define SPRITED_FLIP_DONE_INT_EN (1 << 26) -+#define SPRITEC_FLIP_DONE_INT_EN (1 << 25) -+#define PLANEB_FLIP_DONE_INT_EN (1 << 24) -+#define PIPE_PSR_INT_EN (1 << 22) -+#define PIPEA_LINE_COMPARE_INT_EN (1 << 21) -+#define PIPEA_HLINE_INT_EN (1 << 20) -+#define PIPEA_VBLANK_INT_EN (1 << 19) -+#define SPRITEB_FLIP_DONE_INT_EN (1 << 18) -+#define SPRITEA_FLIP_DONE_INT_EN (1 << 17) -+#define PLANEA_FLIPDONE_INT_EN (1 << 16) -+#define PIPEC_LINE_COMPARE_INT_EN (1 << 13) -+#define PIPEC_HLINE_INT_EN (1 << 12) -+#define PIPEC_VBLANK_INT_EN (1 << 11) -+#define SPRITEF_FLIPDONE_INT_EN (1 << 10) -+#define SPRITEE_FLIPDONE_INT_EN (1 << 9) -+#define PLANEC_FLIPDONE_INT_EN (1 << 8) -+ -+#define DPINVGTT _MMIO(VLV_DISPLAY_BASE + 0x7002c) /* VLV/CHV only */ -+#define SPRITEF_INVALID_GTT_INT_EN (1 << 27) -+#define SPRITEE_INVALID_GTT_INT_EN (1 << 26) -+#define PLANEC_INVALID_GTT_INT_EN (1 << 25) -+#define CURSORC_INVALID_GTT_INT_EN (1 << 24) -+#define CURSORB_INVALID_GTT_INT_EN (1 << 23) -+#define CURSORA_INVALID_GTT_INT_EN (1 << 22) -+#define SPRITED_INVALID_GTT_INT_EN (1 << 21) -+#define SPRITEC_INVALID_GTT_INT_EN (1 << 20) -+#define PLANEB_INVALID_GTT_INT_EN (1 << 19) -+#define SPRITEB_INVALID_GTT_INT_EN (1 << 18) -+#define SPRITEA_INVALID_GTT_INT_EN (1 << 17) -+#define PLANEA_INVALID_GTT_INT_EN (1 << 16) -+#define DPINVGTT_EN_MASK 0xff0000 -+#define DPINVGTT_EN_MASK_CHV 0xfff0000 -+#define SPRITEF_INVALID_GTT_STATUS (1 << 11) -+#define SPRITEE_INVALID_GTT_STATUS (1 << 10) -+#define PLANEC_INVALID_GTT_STATUS (1 << 9) -+#define CURSORC_INVALID_GTT_STATUS (1 << 8) -+#define CURSORB_INVALID_GTT_STATUS (1 << 7) -+#define CURSORA_INVALID_GTT_STATUS (1 << 6) -+#define SPRITED_INVALID_GTT_STATUS (1 << 5) -+#define SPRITEC_INVALID_GTT_STATUS (1 << 4) -+#define PLANEB_INVALID_GTT_STATUS (1 << 3) -+#define SPRITEB_INVALID_GTT_STATUS (1 << 2) -+#define SPRITEA_INVALID_GTT_STATUS (1 << 1) -+#define PLANEA_INVALID_GTT_STATUS (1 << 0) -+#define DPINVGTT_STATUS_MASK 0xff -+#define DPINVGTT_STATUS_MASK_CHV 0xfff -+ -+#define DSPARB _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x70030) -+#define DSPARB_CSTART_MASK (0x7f << 7) -+#define DSPARB_CSTART_SHIFT 7 -+#define DSPARB_BSTART_MASK (0x7f) -+#define DSPARB_BSTART_SHIFT 0 -+#define DSPARB_BEND_SHIFT 9 /* on 855 */ -+#define DSPARB_AEND_SHIFT 0 -+#define DSPARB_SPRITEA_SHIFT_VLV 0 -+#define DSPARB_SPRITEA_MASK_VLV (0xff << 0) -+#define DSPARB_SPRITEB_SHIFT_VLV 8 -+#define DSPARB_SPRITEB_MASK_VLV (0xff << 8) -+#define DSPARB_SPRITEC_SHIFT_VLV 16 -+#define DSPARB_SPRITEC_MASK_VLV (0xff << 16) -+#define DSPARB_SPRITED_SHIFT_VLV 24 -+#define DSPARB_SPRITED_MASK_VLV (0xff << 24) -+#define DSPARB2 _MMIO(VLV_DISPLAY_BASE + 0x70060) /* vlv/chv */ -+#define DSPARB_SPRITEA_HI_SHIFT_VLV 0 -+#define DSPARB_SPRITEA_HI_MASK_VLV (0x1 << 0) -+#define DSPARB_SPRITEB_HI_SHIFT_VLV 4 -+#define DSPARB_SPRITEB_HI_MASK_VLV (0x1 << 4) -+#define DSPARB_SPRITEC_HI_SHIFT_VLV 8 -+#define DSPARB_SPRITEC_HI_MASK_VLV (0x1 << 8) -+#define DSPARB_SPRITED_HI_SHIFT_VLV 12 -+#define DSPARB_SPRITED_HI_MASK_VLV (0x1 << 12) -+#define DSPARB_SPRITEE_HI_SHIFT_VLV 16 -+#define DSPARB_SPRITEE_HI_MASK_VLV (0x1 << 16) -+#define DSPARB_SPRITEF_HI_SHIFT_VLV 20 -+#define DSPARB_SPRITEF_HI_MASK_VLV (0x1 << 20) -+#define DSPARB3 _MMIO(VLV_DISPLAY_BASE + 0x7006c) /* chv */ -+#define DSPARB_SPRITEE_SHIFT_VLV 0 -+#define DSPARB_SPRITEE_MASK_VLV (0xff << 0) -+#define DSPARB_SPRITEF_SHIFT_VLV 8 -+#define DSPARB_SPRITEF_MASK_VLV (0xff << 8) -+ -+/* pnv/gen4/g4x/vlv/chv */ -+#define DSPFW1 _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x70034) -+#define DSPFW_SR_SHIFT 23 -+#define DSPFW_SR_MASK (0x1ff << 23) -+#define DSPFW_CURSORB_SHIFT 16 -+#define DSPFW_CURSORB_MASK (0x3f << 16) -+#define DSPFW_PLANEB_SHIFT 8 -+#define DSPFW_PLANEB_MASK (0x7f << 8) -+#define DSPFW_PLANEB_MASK_VLV (0xff << 8) /* vlv/chv */ -+#define DSPFW_PLANEA_SHIFT 0 -+#define DSPFW_PLANEA_MASK (0x7f << 0) -+#define DSPFW_PLANEA_MASK_VLV (0xff << 0) /* vlv/chv */ -+#define DSPFW2 _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x70038) -+#define DSPFW_FBC_SR_EN (1 << 31) /* g4x */ -+#define DSPFW_FBC_SR_SHIFT 28 -+#define DSPFW_FBC_SR_MASK (0x7 << 28) /* g4x */ -+#define DSPFW_FBC_HPLL_SR_SHIFT 24 -+#define DSPFW_FBC_HPLL_SR_MASK (0xf << 24) /* g4x */ -+#define DSPFW_SPRITEB_SHIFT (16) -+#define DSPFW_SPRITEB_MASK (0x7f << 16) /* g4x */ -+#define DSPFW_SPRITEB_MASK_VLV (0xff << 16) /* vlv/chv */ -+#define DSPFW_CURSORA_SHIFT 8 -+#define DSPFW_CURSORA_MASK (0x3f << 8) -+#define DSPFW_PLANEC_OLD_SHIFT 0 -+#define DSPFW_PLANEC_OLD_MASK (0x7f << 0) /* pre-gen4 sprite C */ -+#define DSPFW_SPRITEA_SHIFT 0 -+#define DSPFW_SPRITEA_MASK (0x7f << 0) /* g4x */ -+#define DSPFW_SPRITEA_MASK_VLV (0xff << 0) /* vlv/chv */ -+#define DSPFW3 _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x7003c) -+#define DSPFW_HPLL_SR_EN (1 << 31) -+#define PINEVIEW_SELF_REFRESH_EN (1 << 30) -+#define DSPFW_CURSOR_SR_SHIFT 24 -+#define DSPFW_CURSOR_SR_MASK (0x3f << 24) -+#define DSPFW_HPLL_CURSOR_SHIFT 16 -+#define DSPFW_HPLL_CURSOR_MASK (0x3f << 16) -+#define DSPFW_HPLL_SR_SHIFT 0 -+#define DSPFW_HPLL_SR_MASK (0x1ff << 0) -+ -+/* vlv/chv */ -+#define DSPFW4 _MMIO(VLV_DISPLAY_BASE + 0x70070) -+#define DSPFW_SPRITEB_WM1_SHIFT 16 -+#define DSPFW_SPRITEB_WM1_MASK (0xff << 16) -+#define DSPFW_CURSORA_WM1_SHIFT 8 -+#define DSPFW_CURSORA_WM1_MASK (0x3f << 8) -+#define DSPFW_SPRITEA_WM1_SHIFT 0 -+#define DSPFW_SPRITEA_WM1_MASK (0xff << 0) -+#define DSPFW5 _MMIO(VLV_DISPLAY_BASE + 0x70074) -+#define DSPFW_PLANEB_WM1_SHIFT 24 -+#define DSPFW_PLANEB_WM1_MASK (0xff << 24) -+#define DSPFW_PLANEA_WM1_SHIFT 16 -+#define DSPFW_PLANEA_WM1_MASK (0xff << 16) -+#define DSPFW_CURSORB_WM1_SHIFT 8 -+#define DSPFW_CURSORB_WM1_MASK (0x3f << 8) -+#define DSPFW_CURSOR_SR_WM1_SHIFT 0 -+#define DSPFW_CURSOR_SR_WM1_MASK (0x3f << 0) -+#define DSPFW6 _MMIO(VLV_DISPLAY_BASE + 0x70078) -+#define DSPFW_SR_WM1_SHIFT 0 -+#define DSPFW_SR_WM1_MASK (0x1ff << 0) -+#define DSPFW7 _MMIO(VLV_DISPLAY_BASE + 0x7007c) -+#define DSPFW7_CHV _MMIO(VLV_DISPLAY_BASE + 0x700b4) /* wtf #1? */ -+#define DSPFW_SPRITED_WM1_SHIFT 24 -+#define DSPFW_SPRITED_WM1_MASK (0xff << 24) -+#define DSPFW_SPRITED_SHIFT 16 -+#define DSPFW_SPRITED_MASK_VLV (0xff << 16) -+#define DSPFW_SPRITEC_WM1_SHIFT 8 -+#define DSPFW_SPRITEC_WM1_MASK (0xff << 8) -+#define DSPFW_SPRITEC_SHIFT 0 -+#define DSPFW_SPRITEC_MASK_VLV (0xff << 0) -+#define DSPFW8_CHV _MMIO(VLV_DISPLAY_BASE + 0x700b8) -+#define DSPFW_SPRITEF_WM1_SHIFT 24 -+#define DSPFW_SPRITEF_WM1_MASK (0xff << 24) -+#define DSPFW_SPRITEF_SHIFT 16 -+#define DSPFW_SPRITEF_MASK_VLV (0xff << 16) -+#define DSPFW_SPRITEE_WM1_SHIFT 8 -+#define DSPFW_SPRITEE_WM1_MASK (0xff << 8) -+#define DSPFW_SPRITEE_SHIFT 0 -+#define DSPFW_SPRITEE_MASK_VLV (0xff << 0) -+#define DSPFW9_CHV _MMIO(VLV_DISPLAY_BASE + 0x7007c) /* wtf #2? */ -+#define DSPFW_PLANEC_WM1_SHIFT 24 -+#define DSPFW_PLANEC_WM1_MASK (0xff << 24) -+#define DSPFW_PLANEC_SHIFT 16 -+#define DSPFW_PLANEC_MASK_VLV (0xff << 16) -+#define DSPFW_CURSORC_WM1_SHIFT 8 -+#define DSPFW_CURSORC_WM1_MASK (0x3f << 16) -+#define DSPFW_CURSORC_SHIFT 0 -+#define DSPFW_CURSORC_MASK (0x3f << 0) -+ -+/* vlv/chv high order bits */ -+#define DSPHOWM _MMIO(VLV_DISPLAY_BASE + 0x70064) -+#define DSPFW_SR_HI_SHIFT 24 -+#define DSPFW_SR_HI_MASK (3 << 24) /* 2 bits for chv, 1 for vlv */ -+#define DSPFW_SPRITEF_HI_SHIFT 23 -+#define DSPFW_SPRITEF_HI_MASK (1 << 23) -+#define DSPFW_SPRITEE_HI_SHIFT 22 -+#define DSPFW_SPRITEE_HI_MASK (1 << 22) -+#define DSPFW_PLANEC_HI_SHIFT 21 -+#define DSPFW_PLANEC_HI_MASK (1 << 21) -+#define DSPFW_SPRITED_HI_SHIFT 20 -+#define DSPFW_SPRITED_HI_MASK (1 << 20) -+#define DSPFW_SPRITEC_HI_SHIFT 16 -+#define DSPFW_SPRITEC_HI_MASK (1 << 16) -+#define DSPFW_PLANEB_HI_SHIFT 12 -+#define DSPFW_PLANEB_HI_MASK (1 << 12) -+#define DSPFW_SPRITEB_HI_SHIFT 8 -+#define DSPFW_SPRITEB_HI_MASK (1 << 8) -+#define DSPFW_SPRITEA_HI_SHIFT 4 -+#define DSPFW_SPRITEA_HI_MASK (1 << 4) -+#define DSPFW_PLANEA_HI_SHIFT 0 -+#define DSPFW_PLANEA_HI_MASK (1 << 0) -+#define DSPHOWM1 _MMIO(VLV_DISPLAY_BASE + 0x70068) -+#define DSPFW_SR_WM1_HI_SHIFT 24 -+#define DSPFW_SR_WM1_HI_MASK (3 << 24) /* 2 bits for chv, 1 for vlv */ -+#define DSPFW_SPRITEF_WM1_HI_SHIFT 23 -+#define DSPFW_SPRITEF_WM1_HI_MASK (1 << 23) -+#define DSPFW_SPRITEE_WM1_HI_SHIFT 22 -+#define DSPFW_SPRITEE_WM1_HI_MASK (1 << 22) -+#define DSPFW_PLANEC_WM1_HI_SHIFT 21 -+#define DSPFW_PLANEC_WM1_HI_MASK (1 << 21) -+#define DSPFW_SPRITED_WM1_HI_SHIFT 20 -+#define DSPFW_SPRITED_WM1_HI_MASK (1 << 20) -+#define DSPFW_SPRITEC_WM1_HI_SHIFT 16 -+#define DSPFW_SPRITEC_WM1_HI_MASK (1 << 16) -+#define DSPFW_PLANEB_WM1_HI_SHIFT 12 -+#define DSPFW_PLANEB_WM1_HI_MASK (1 << 12) -+#define DSPFW_SPRITEB_WM1_HI_SHIFT 8 -+#define DSPFW_SPRITEB_WM1_HI_MASK (1 << 8) -+#define DSPFW_SPRITEA_WM1_HI_SHIFT 4 -+#define DSPFW_SPRITEA_WM1_HI_MASK (1 << 4) -+#define DSPFW_PLANEA_WM1_HI_SHIFT 0 -+#define DSPFW_PLANEA_WM1_HI_MASK (1 << 0) -+ -+/* drain latency register values*/ -+#define VLV_DDL(pipe) _MMIO(VLV_DISPLAY_BASE + 0x70050 + 4 * (pipe)) -+#define DDL_CURSOR_SHIFT 24 -+#define DDL_SPRITE_SHIFT(sprite) (8 + 8 * (sprite)) -+#define DDL_PLANE_SHIFT 0 -+#define DDL_PRECISION_HIGH (1 << 7) -+#define DDL_PRECISION_LOW (0 << 7) -+#define DRAIN_LATENCY_MASK 0x7f -+ -+#define CBR1_VLV _MMIO(VLV_DISPLAY_BASE + 0x70400) -+#define CBR_PND_DEADLINE_DISABLE (1 << 31) -+#define CBR_PWM_CLOCK_MUX_SELECT (1 << 30) -+ -+#define CBR4_VLV _MMIO(VLV_DISPLAY_BASE + 0x70450) -+#define CBR_DPLLBMD_PIPE(pipe) (1 << (7 + (pipe) * 11)) /* pipes B and C */ -+ -+/* FIFO watermark sizes etc */ -+#define G4X_FIFO_LINE_SIZE 64 -+#define I915_FIFO_LINE_SIZE 64 -+#define I830_FIFO_LINE_SIZE 32 -+ -+#define VALLEYVIEW_FIFO_SIZE 255 -+#define G4X_FIFO_SIZE 127 -+#define I965_FIFO_SIZE 512 -+#define I945_FIFO_SIZE 127 -+#define I915_FIFO_SIZE 95 -+#define I855GM_FIFO_SIZE 127 /* In cachelines */ -+#define I830_FIFO_SIZE 95 -+ -+#define VALLEYVIEW_MAX_WM 0xff -+#define G4X_MAX_WM 0x3f -+#define I915_MAX_WM 0x3f -+ -+#define PINEVIEW_DISPLAY_FIFO 512 /* in 64byte unit */ -+#define PINEVIEW_FIFO_LINE_SIZE 64 -+#define PINEVIEW_MAX_WM 0x1ff -+#define PINEVIEW_DFT_WM 0x3f -+#define PINEVIEW_DFT_HPLLOFF_WM 0 -+#define PINEVIEW_GUARD_WM 10 -+#define PINEVIEW_CURSOR_FIFO 64 -+#define PINEVIEW_CURSOR_MAX_WM 0x3f -+#define PINEVIEW_CURSOR_DFT_WM 0 -+#define PINEVIEW_CURSOR_GUARD_WM 5 -+ -+#define VALLEYVIEW_CURSOR_MAX_WM 64 -+#define I965_CURSOR_FIFO 64 -+#define I965_CURSOR_MAX_WM 32 -+#define I965_CURSOR_DFT_WM 8 -+ -+/* Watermark register definitions for SKL */ -+#define _CUR_WM_A_0 0x70140 -+#define _CUR_WM_B_0 0x71140 -+#define _PLANE_WM_1_A_0 0x70240 -+#define _PLANE_WM_1_B_0 0x71240 -+#define _PLANE_WM_2_A_0 0x70340 -+#define _PLANE_WM_2_B_0 0x71340 -+#define _PLANE_WM_TRANS_1_A_0 0x70268 -+#define _PLANE_WM_TRANS_1_B_0 0x71268 -+#define _PLANE_WM_TRANS_2_A_0 0x70368 -+#define _PLANE_WM_TRANS_2_B_0 0x71368 -+#define _CUR_WM_TRANS_A_0 0x70168 -+#define _CUR_WM_TRANS_B_0 0x71168 -+#define PLANE_WM_EN (1 << 31) -+#define PLANE_WM_IGNORE_LINES (1 << 30) -+#define PLANE_WM_LINES_SHIFT 14 -+#define PLANE_WM_LINES_MASK 0x1f -+#define PLANE_WM_BLOCKS_MASK 0x7ff /* skl+: 10 bits, icl+ 11 bits */ -+ -+#define _CUR_WM_0(pipe) _PIPE(pipe, _CUR_WM_A_0, _CUR_WM_B_0) -+#define CUR_WM(pipe, level) _MMIO(_CUR_WM_0(pipe) + ((4) * (level))) -+#define CUR_WM_TRANS(pipe) _MMIO_PIPE(pipe, _CUR_WM_TRANS_A_0, _CUR_WM_TRANS_B_0) -+ -+#define _PLANE_WM_1(pipe) _PIPE(pipe, _PLANE_WM_1_A_0, _PLANE_WM_1_B_0) -+#define _PLANE_WM_2(pipe) _PIPE(pipe, _PLANE_WM_2_A_0, _PLANE_WM_2_B_0) -+#define _PLANE_WM_BASE(pipe, plane) \ -+ _PLANE(plane, _PLANE_WM_1(pipe), _PLANE_WM_2(pipe)) -+#define PLANE_WM(pipe, plane, level) \ -+ _MMIO(_PLANE_WM_BASE(pipe, plane) + ((4) * (level))) -+#define _PLANE_WM_TRANS_1(pipe) \ -+ _PIPE(pipe, _PLANE_WM_TRANS_1_A_0, _PLANE_WM_TRANS_1_B_0) -+#define _PLANE_WM_TRANS_2(pipe) \ -+ _PIPE(pipe, _PLANE_WM_TRANS_2_A_0, _PLANE_WM_TRANS_2_B_0) -+#define PLANE_WM_TRANS(pipe, plane) \ -+ _MMIO(_PLANE(plane, _PLANE_WM_TRANS_1(pipe), _PLANE_WM_TRANS_2(pipe))) -+ -+/* define the Watermark register on Ironlake */ -+#define WM0_PIPEA_ILK _MMIO(0x45100) -+#define WM0_PIPE_PLANE_MASK (0xffff << 16) -+#define WM0_PIPE_PLANE_SHIFT 16 -+#define WM0_PIPE_SPRITE_MASK (0xff << 8) -+#define WM0_PIPE_SPRITE_SHIFT 8 -+#define WM0_PIPE_CURSOR_MASK (0xff) -+ -+#define WM0_PIPEB_ILK _MMIO(0x45104) -+#define WM0_PIPEC_IVB _MMIO(0x45200) -+#define WM1_LP_ILK _MMIO(0x45108) -+#define WM1_LP_SR_EN (1 << 31) -+#define WM1_LP_LATENCY_SHIFT 24 -+#define WM1_LP_LATENCY_MASK (0x7f << 24) -+#define WM1_LP_FBC_MASK (0xf << 20) -+#define WM1_LP_FBC_SHIFT 20 -+#define WM1_LP_FBC_SHIFT_BDW 19 -+#define WM1_LP_SR_MASK (0x7ff << 8) -+#define WM1_LP_SR_SHIFT 8 -+#define WM1_LP_CURSOR_MASK (0xff) -+#define WM2_LP_ILK _MMIO(0x4510c) -+#define WM2_LP_EN (1 << 31) -+#define WM3_LP_ILK _MMIO(0x45110) -+#define WM3_LP_EN (1 << 31) -+#define WM1S_LP_ILK _MMIO(0x45120) -+#define WM2S_LP_IVB _MMIO(0x45124) -+#define WM3S_LP_IVB _MMIO(0x45128) -+#define WM1S_LP_EN (1 << 31) -+ -+#define HSW_WM_LP_VAL(lat, fbc, pri, cur) \ -+ (WM3_LP_EN | ((lat) << WM1_LP_LATENCY_SHIFT) | \ -+ ((fbc) << WM1_LP_FBC_SHIFT) | ((pri) << WM1_LP_SR_SHIFT) | (cur)) -+ -+/* Memory latency timer register */ -+#define MLTR_ILK _MMIO(0x11222) -+#define MLTR_WM1_SHIFT 0 -+#define MLTR_WM2_SHIFT 8 -+/* the unit of memory self-refresh latency time is 0.5us */ -+#define ILK_SRLT_MASK 0x3f -+ -+ -+/* the address where we get all kinds of latency value */ -+#define SSKPD _MMIO(0x5d10) -+#define SSKPD_WM_MASK 0x3f -+#define SSKPD_WM0_SHIFT 0 -+#define SSKPD_WM1_SHIFT 8 -+#define SSKPD_WM2_SHIFT 16 -+#define SSKPD_WM3_SHIFT 24 -+ -+/* -+ * The two pipe frame counter registers are not synchronized, so -+ * reading a stable value is somewhat tricky. The following code -+ * should work: -+ * -+ * do { -+ * high1 = ((INREG(PIPEAFRAMEHIGH) & PIPE_FRAME_HIGH_MASK) >> -+ * PIPE_FRAME_HIGH_SHIFT; -+ * low1 = ((INREG(PIPEAFRAMEPIXEL) & PIPE_FRAME_LOW_MASK) >> -+ * PIPE_FRAME_LOW_SHIFT); -+ * high2 = ((INREG(PIPEAFRAMEHIGH) & PIPE_FRAME_HIGH_MASK) >> -+ * PIPE_FRAME_HIGH_SHIFT); -+ * } while (high1 != high2); -+ * frame = (high1 << 8) | low1; -+ */ -+#define _PIPEAFRAMEHIGH 0x70040 -+#define PIPE_FRAME_HIGH_MASK 0x0000ffff -+#define PIPE_FRAME_HIGH_SHIFT 0 -+#define _PIPEAFRAMEPIXEL 0x70044 -+#define PIPE_FRAME_LOW_MASK 0xff000000 -+#define PIPE_FRAME_LOW_SHIFT 24 -+#define PIPE_PIXEL_MASK 0x00ffffff -+#define PIPE_PIXEL_SHIFT 0 -+/* GM45+ just has to be different */ -+#define _PIPEA_FRMCOUNT_G4X 0x70040 -+#define _PIPEA_FLIPCOUNT_G4X 0x70044 -+#define PIPE_FRMCOUNT_G4X(pipe) _MMIO_PIPE2(pipe, _PIPEA_FRMCOUNT_G4X) -+#define PIPE_FLIPCOUNT_G4X(pipe) _MMIO_PIPE2(pipe, _PIPEA_FLIPCOUNT_G4X) -+ -+/* Cursor A & B regs */ -+#define _CURACNTR 0x70080 -+/* Old style CUR*CNTR flags (desktop 8xx) */ -+#define CURSOR_ENABLE 0x80000000 -+#define CURSOR_GAMMA_ENABLE 0x40000000 -+#define CURSOR_STRIDE_SHIFT 28 -+#define CURSOR_STRIDE(x) ((ffs(x) - 9) << CURSOR_STRIDE_SHIFT) /* 256,512,1k,2k */ -+#define CURSOR_FORMAT_SHIFT 24 -+#define CURSOR_FORMAT_MASK (0x07 << CURSOR_FORMAT_SHIFT) -+#define CURSOR_FORMAT_2C (0x00 << CURSOR_FORMAT_SHIFT) -+#define CURSOR_FORMAT_3C (0x01 << CURSOR_FORMAT_SHIFT) -+#define CURSOR_FORMAT_4C (0x02 << CURSOR_FORMAT_SHIFT) -+#define CURSOR_FORMAT_ARGB (0x04 << CURSOR_FORMAT_SHIFT) -+#define CURSOR_FORMAT_XRGB (0x05 << CURSOR_FORMAT_SHIFT) -+/* New style CUR*CNTR flags */ -+#define MCURSOR_MODE 0x27 -+#define MCURSOR_MODE_DISABLE 0x00 -+#define MCURSOR_MODE_128_32B_AX 0x02 -+#define MCURSOR_MODE_256_32B_AX 0x03 -+#define MCURSOR_MODE_64_32B_AX 0x07 -+#define MCURSOR_MODE_128_ARGB_AX ((1 << 5) | MCURSOR_MODE_128_32B_AX) -+#define MCURSOR_MODE_256_ARGB_AX ((1 << 5) | MCURSOR_MODE_256_32B_AX) -+#define MCURSOR_MODE_64_ARGB_AX ((1 << 5) | MCURSOR_MODE_64_32B_AX) -+#define MCURSOR_PIPE_SELECT_MASK (0x3 << 28) -+#define MCURSOR_PIPE_SELECT_SHIFT 28 -+#define MCURSOR_PIPE_SELECT(pipe) ((pipe) << 28) -+#define MCURSOR_GAMMA_ENABLE (1 << 26) -+#define MCURSOR_PIPE_CSC_ENABLE (1 << 24) /* ilk+ */ -+#define MCURSOR_ROTATE_180 (1 << 15) -+#define MCURSOR_TRICKLE_FEED_DISABLE (1 << 14) -+#define _CURABASE 0x70084 -+#define _CURAPOS 0x70088 -+#define CURSOR_POS_MASK 0x007FF -+#define CURSOR_POS_SIGN 0x8000 -+#define CURSOR_X_SHIFT 0 -+#define CURSOR_Y_SHIFT 16 -+#define CURSIZE _MMIO(0x700a0) /* 845/865 */ -+#define _CUR_FBC_CTL_A 0x700a0 /* ivb+ */ -+#define CUR_FBC_CTL_EN (1 << 31) -+#define _CURASURFLIVE 0x700ac /* g4x+ */ -+#define _CURBCNTR 0x700c0 -+#define _CURBBASE 0x700c4 -+#define _CURBPOS 0x700c8 -+ -+#define _CURBCNTR_IVB 0x71080 -+#define _CURBBASE_IVB 0x71084 -+#define _CURBPOS_IVB 0x71088 -+ -+#define CURCNTR(pipe) _CURSOR2(pipe, _CURACNTR) -+#define CURBASE(pipe) _CURSOR2(pipe, _CURABASE) -+#define CURPOS(pipe) _CURSOR2(pipe, _CURAPOS) -+#define CUR_FBC_CTL(pipe) _CURSOR2(pipe, _CUR_FBC_CTL_A) -+#define CURSURFLIVE(pipe) _CURSOR2(pipe, _CURASURFLIVE) -+ -+#define CURSOR_A_OFFSET 0x70080 -+#define CURSOR_B_OFFSET 0x700c0 -+#define CHV_CURSOR_C_OFFSET 0x700e0 -+#define IVB_CURSOR_B_OFFSET 0x71080 -+#define IVB_CURSOR_C_OFFSET 0x72080 -+ -+/* Display A control */ -+#define _DSPACNTR 0x70180 -+#define DISPLAY_PLANE_ENABLE (1 << 31) -+#define DISPLAY_PLANE_DISABLE 0 -+#define DISPPLANE_GAMMA_ENABLE (1 << 30) -+#define DISPPLANE_GAMMA_DISABLE 0 -+#define DISPPLANE_PIXFORMAT_MASK (0xf << 26) -+#define DISPPLANE_YUV422 (0x0 << 26) -+#define DISPPLANE_8BPP (0x2 << 26) -+#define DISPPLANE_BGRA555 (0x3 << 26) -+#define DISPPLANE_BGRX555 (0x4 << 26) -+#define DISPPLANE_BGRX565 (0x5 << 26) -+#define DISPPLANE_BGRX888 (0x6 << 26) -+#define DISPPLANE_BGRA888 (0x7 << 26) -+#define DISPPLANE_RGBX101010 (0x8 << 26) -+#define DISPPLANE_RGBA101010 (0x9 << 26) -+#define DISPPLANE_BGRX101010 (0xa << 26) -+#define DISPPLANE_RGBX161616 (0xc << 26) -+#define DISPPLANE_RGBX888 (0xe << 26) -+#define DISPPLANE_RGBA888 (0xf << 26) -+#define DISPPLANE_STEREO_ENABLE (1 << 25) -+#define DISPPLANE_STEREO_DISABLE 0 -+#define DISPPLANE_PIPE_CSC_ENABLE (1 << 24) /* ilk+ */ -+#define DISPPLANE_SEL_PIPE_SHIFT 24 -+#define DISPPLANE_SEL_PIPE_MASK (3 << DISPPLANE_SEL_PIPE_SHIFT) -+#define DISPPLANE_SEL_PIPE(pipe) ((pipe) << DISPPLANE_SEL_PIPE_SHIFT) -+#define DISPPLANE_SRC_KEY_ENABLE (1 << 22) -+#define DISPPLANE_SRC_KEY_DISABLE 0 -+#define DISPPLANE_LINE_DOUBLE (1 << 20) -+#define DISPPLANE_NO_LINE_DOUBLE 0 -+#define DISPPLANE_STEREO_POLARITY_FIRST 0 -+#define DISPPLANE_STEREO_POLARITY_SECOND (1 << 18) -+#define DISPPLANE_ALPHA_PREMULTIPLY (1 << 16) /* CHV pipe B */ -+#define DISPPLANE_ROTATE_180 (1 << 15) -+#define DISPPLANE_TRICKLE_FEED_DISABLE (1 << 14) /* Ironlake */ -+#define DISPPLANE_TILED (1 << 10) -+#define DISPPLANE_MIRROR (1 << 8) /* CHV pipe B */ -+#define _DSPAADDR 0x70184 -+#define _DSPASTRIDE 0x70188 -+#define _DSPAPOS 0x7018C /* reserved */ -+#define _DSPASIZE 0x70190 -+#define _DSPASURF 0x7019C /* 965+ only */ -+#define _DSPATILEOFF 0x701A4 /* 965+ only */ -+#define _DSPAOFFSET 0x701A4 /* HSW */ -+#define _DSPASURFLIVE 0x701AC -+ -+#define DSPCNTR(plane) _MMIO_PIPE2(plane, _DSPACNTR) -+#define DSPADDR(plane) _MMIO_PIPE2(plane, _DSPAADDR) -+#define DSPSTRIDE(plane) _MMIO_PIPE2(plane, _DSPASTRIDE) -+#define DSPPOS(plane) _MMIO_PIPE2(plane, _DSPAPOS) -+#define DSPSIZE(plane) _MMIO_PIPE2(plane, _DSPASIZE) -+#define DSPSURF(plane) _MMIO_PIPE2(plane, _DSPASURF) -+#define DSPTILEOFF(plane) _MMIO_PIPE2(plane, _DSPATILEOFF) -+#define DSPLINOFF(plane) DSPADDR(plane) -+#define DSPOFFSET(plane) _MMIO_PIPE2(plane, _DSPAOFFSET) -+#define DSPSURFLIVE(plane) _MMIO_PIPE2(plane, _DSPASURFLIVE) -+ -+/* CHV pipe B blender and primary plane */ -+#define _CHV_BLEND_A 0x60a00 -+#define CHV_BLEND_LEGACY (0 << 30) -+#define CHV_BLEND_ANDROID (1 << 30) -+#define CHV_BLEND_MPO (2 << 30) -+#define CHV_BLEND_MASK (3 << 30) -+#define _CHV_CANVAS_A 0x60a04 -+#define _PRIMPOS_A 0x60a08 -+#define _PRIMSIZE_A 0x60a0c -+#define _PRIMCNSTALPHA_A 0x60a10 -+#define PRIM_CONST_ALPHA_ENABLE (1 << 31) -+ -+#define CHV_BLEND(pipe) _MMIO_TRANS2(pipe, _CHV_BLEND_A) -+#define CHV_CANVAS(pipe) _MMIO_TRANS2(pipe, _CHV_CANVAS_A) -+#define PRIMPOS(plane) _MMIO_TRANS2(plane, _PRIMPOS_A) -+#define PRIMSIZE(plane) _MMIO_TRANS2(plane, _PRIMSIZE_A) -+#define PRIMCNSTALPHA(plane) _MMIO_TRANS2(plane, _PRIMCNSTALPHA_A) -+ -+/* Display/Sprite base address macros */ -+#define DISP_BASEADDR_MASK (0xfffff000) -+#define I915_LO_DISPBASE(val) ((val) & ~DISP_BASEADDR_MASK) -+#define I915_HI_DISPBASE(val) ((val) & DISP_BASEADDR_MASK) -+ -+/* -+ * VBIOS flags -+ * gen2: -+ * [00:06] alm,mgm -+ * [10:16] all -+ * [30:32] alm,mgm -+ * gen3+: -+ * [00:0f] all -+ * [10:1f] all -+ * [30:32] all -+ */ -+#define SWF0(i) _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x70410 + (i) * 4) -+#define SWF1(i) _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x71410 + (i) * 4) -+#define SWF3(i) _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x72414 + (i) * 4) -+#define SWF_ILK(i) _MMIO(0x4F000 + (i) * 4) -+ -+/* Pipe B */ -+#define _PIPEBDSL (DISPLAY_MMIO_BASE(dev_priv) + 0x71000) -+#define _PIPEBCONF (DISPLAY_MMIO_BASE(dev_priv) + 0x71008) -+#define _PIPEBSTAT (DISPLAY_MMIO_BASE(dev_priv) + 0x71024) -+#define _PIPEBFRAMEHIGH 0x71040 -+#define _PIPEBFRAMEPIXEL 0x71044 -+#define _PIPEB_FRMCOUNT_G4X (DISPLAY_MMIO_BASE(dev_priv) + 0x71040) -+#define _PIPEB_FLIPCOUNT_G4X (DISPLAY_MMIO_BASE(dev_priv) + 0x71044) -+ -+ -+/* Display B control */ -+#define _DSPBCNTR (DISPLAY_MMIO_BASE(dev_priv) + 0x71180) -+#define DISPPLANE_ALPHA_TRANS_ENABLE (1 << 15) -+#define DISPPLANE_ALPHA_TRANS_DISABLE 0 -+#define DISPPLANE_SPRITE_ABOVE_DISPLAY 0 -+#define DISPPLANE_SPRITE_ABOVE_OVERLAY (1) -+#define _DSPBADDR (DISPLAY_MMIO_BASE(dev_priv) + 0x71184) -+#define _DSPBSTRIDE (DISPLAY_MMIO_BASE(dev_priv) + 0x71188) -+#define _DSPBPOS (DISPLAY_MMIO_BASE(dev_priv) + 0x7118C) -+#define _DSPBSIZE (DISPLAY_MMIO_BASE(dev_priv) + 0x71190) -+#define _DSPBSURF (DISPLAY_MMIO_BASE(dev_priv) + 0x7119C) -+#define _DSPBTILEOFF (DISPLAY_MMIO_BASE(dev_priv) + 0x711A4) -+#define _DSPBOFFSET (DISPLAY_MMIO_BASE(dev_priv) + 0x711A4) -+#define _DSPBSURFLIVE (DISPLAY_MMIO_BASE(dev_priv) + 0x711AC) -+ -+/* ICL DSI 0 and 1 */ -+#define _PIPEDSI0CONF 0x7b008 -+#define _PIPEDSI1CONF 0x7b808 -+ -+/* Sprite A control */ -+#define _DVSACNTR 0x72180 -+#define DVS_ENABLE (1 << 31) -+#define DVS_GAMMA_ENABLE (1 << 30) -+#define DVS_YUV_RANGE_CORRECTION_DISABLE (1 << 27) -+#define DVS_PIXFORMAT_MASK (3 << 25) -+#define DVS_FORMAT_YUV422 (0 << 25) -+#define DVS_FORMAT_RGBX101010 (1 << 25) -+#define DVS_FORMAT_RGBX888 (2 << 25) -+#define DVS_FORMAT_RGBX161616 (3 << 25) -+#define DVS_PIPE_CSC_ENABLE (1 << 24) -+#define DVS_SOURCE_KEY (1 << 22) -+#define DVS_RGB_ORDER_XBGR (1 << 20) -+#define DVS_YUV_FORMAT_BT709 (1 << 18) -+#define DVS_YUV_BYTE_ORDER_MASK (3 << 16) -+#define DVS_YUV_ORDER_YUYV (0 << 16) -+#define DVS_YUV_ORDER_UYVY (1 << 16) -+#define DVS_YUV_ORDER_YVYU (2 << 16) -+#define DVS_YUV_ORDER_VYUY (3 << 16) -+#define DVS_ROTATE_180 (1 << 15) -+#define DVS_DEST_KEY (1 << 2) -+#define DVS_TRICKLE_FEED_DISABLE (1 << 14) -+#define DVS_TILED (1 << 10) -+#define _DVSALINOFF 0x72184 -+#define _DVSASTRIDE 0x72188 -+#define _DVSAPOS 0x7218c -+#define _DVSASIZE 0x72190 -+#define _DVSAKEYVAL 0x72194 -+#define _DVSAKEYMSK 0x72198 -+#define _DVSASURF 0x7219c -+#define _DVSAKEYMAXVAL 0x721a0 -+#define _DVSATILEOFF 0x721a4 -+#define _DVSASURFLIVE 0x721ac -+#define _DVSASCALE 0x72204 -+#define DVS_SCALE_ENABLE (1 << 31) -+#define DVS_FILTER_MASK (3 << 29) -+#define DVS_FILTER_MEDIUM (0 << 29) -+#define DVS_FILTER_ENHANCING (1 << 29) -+#define DVS_FILTER_SOFTENING (2 << 29) -+#define DVS_VERTICAL_OFFSET_HALF (1 << 28) /* must be enabled below */ -+#define DVS_VERTICAL_OFFSET_ENABLE (1 << 27) -+#define _DVSAGAMC 0x72300 -+ -+#define _DVSBCNTR 0x73180 -+#define _DVSBLINOFF 0x73184 -+#define _DVSBSTRIDE 0x73188 -+#define _DVSBPOS 0x7318c -+#define _DVSBSIZE 0x73190 -+#define _DVSBKEYVAL 0x73194 -+#define _DVSBKEYMSK 0x73198 -+#define _DVSBSURF 0x7319c -+#define _DVSBKEYMAXVAL 0x731a0 -+#define _DVSBTILEOFF 0x731a4 -+#define _DVSBSURFLIVE 0x731ac -+#define _DVSBSCALE 0x73204 -+#define _DVSBGAMC 0x73300 -+ -+#define DVSCNTR(pipe) _MMIO_PIPE(pipe, _DVSACNTR, _DVSBCNTR) -+#define DVSLINOFF(pipe) _MMIO_PIPE(pipe, _DVSALINOFF, _DVSBLINOFF) -+#define DVSSTRIDE(pipe) _MMIO_PIPE(pipe, _DVSASTRIDE, _DVSBSTRIDE) -+#define DVSPOS(pipe) _MMIO_PIPE(pipe, _DVSAPOS, _DVSBPOS) -+#define DVSSURF(pipe) _MMIO_PIPE(pipe, _DVSASURF, _DVSBSURF) -+#define DVSKEYMAX(pipe) _MMIO_PIPE(pipe, _DVSAKEYMAXVAL, _DVSBKEYMAXVAL) -+#define DVSSIZE(pipe) _MMIO_PIPE(pipe, _DVSASIZE, _DVSBSIZE) -+#define DVSSCALE(pipe) _MMIO_PIPE(pipe, _DVSASCALE, _DVSBSCALE) -+#define DVSTILEOFF(pipe) _MMIO_PIPE(pipe, _DVSATILEOFF, _DVSBTILEOFF) -+#define DVSKEYVAL(pipe) _MMIO_PIPE(pipe, _DVSAKEYVAL, _DVSBKEYVAL) -+#define DVSKEYMSK(pipe) _MMIO_PIPE(pipe, _DVSAKEYMSK, _DVSBKEYMSK) -+#define DVSSURFLIVE(pipe) _MMIO_PIPE(pipe, _DVSASURFLIVE, _DVSBSURFLIVE) -+ -+#define _SPRA_CTL 0x70280 -+#define SPRITE_ENABLE (1 << 31) -+#define SPRITE_GAMMA_ENABLE (1 << 30) -+#define SPRITE_YUV_RANGE_CORRECTION_DISABLE (1 << 28) -+#define SPRITE_PIXFORMAT_MASK (7 << 25) -+#define SPRITE_FORMAT_YUV422 (0 << 25) -+#define SPRITE_FORMAT_RGBX101010 (1 << 25) -+#define SPRITE_FORMAT_RGBX888 (2 << 25) -+#define SPRITE_FORMAT_RGBX161616 (3 << 25) -+#define SPRITE_FORMAT_YUV444 (4 << 25) -+#define SPRITE_FORMAT_XR_BGR101010 (5 << 25) /* Extended range */ -+#define SPRITE_PIPE_CSC_ENABLE (1 << 24) -+#define SPRITE_SOURCE_KEY (1 << 22) -+#define SPRITE_RGB_ORDER_RGBX (1 << 20) /* only for 888 and 161616 */ -+#define SPRITE_YUV_TO_RGB_CSC_DISABLE (1 << 19) -+#define SPRITE_YUV_TO_RGB_CSC_FORMAT_BT709 (1 << 18) /* 0 is BT601 */ -+#define SPRITE_YUV_BYTE_ORDER_MASK (3 << 16) -+#define SPRITE_YUV_ORDER_YUYV (0 << 16) -+#define SPRITE_YUV_ORDER_UYVY (1 << 16) -+#define SPRITE_YUV_ORDER_YVYU (2 << 16) -+#define SPRITE_YUV_ORDER_VYUY (3 << 16) -+#define SPRITE_ROTATE_180 (1 << 15) -+#define SPRITE_TRICKLE_FEED_DISABLE (1 << 14) -+#define SPRITE_INT_GAMMA_ENABLE (1 << 13) -+#define SPRITE_TILED (1 << 10) -+#define SPRITE_DEST_KEY (1 << 2) -+#define _SPRA_LINOFF 0x70284 -+#define _SPRA_STRIDE 0x70288 -+#define _SPRA_POS 0x7028c -+#define _SPRA_SIZE 0x70290 -+#define _SPRA_KEYVAL 0x70294 -+#define _SPRA_KEYMSK 0x70298 -+#define _SPRA_SURF 0x7029c -+#define _SPRA_KEYMAX 0x702a0 -+#define _SPRA_TILEOFF 0x702a4 -+#define _SPRA_OFFSET 0x702a4 -+#define _SPRA_SURFLIVE 0x702ac -+#define _SPRA_SCALE 0x70304 -+#define SPRITE_SCALE_ENABLE (1 << 31) -+#define SPRITE_FILTER_MASK (3 << 29) -+#define SPRITE_FILTER_MEDIUM (0 << 29) -+#define SPRITE_FILTER_ENHANCING (1 << 29) -+#define SPRITE_FILTER_SOFTENING (2 << 29) -+#define SPRITE_VERTICAL_OFFSET_HALF (1 << 28) /* must be enabled below */ -+#define SPRITE_VERTICAL_OFFSET_ENABLE (1 << 27) -+#define _SPRA_GAMC 0x70400 -+ -+#define _SPRB_CTL 0x71280 -+#define _SPRB_LINOFF 0x71284 -+#define _SPRB_STRIDE 0x71288 -+#define _SPRB_POS 0x7128c -+#define _SPRB_SIZE 0x71290 -+#define _SPRB_KEYVAL 0x71294 -+#define _SPRB_KEYMSK 0x71298 -+#define _SPRB_SURF 0x7129c -+#define _SPRB_KEYMAX 0x712a0 -+#define _SPRB_TILEOFF 0x712a4 -+#define _SPRB_OFFSET 0x712a4 -+#define _SPRB_SURFLIVE 0x712ac -+#define _SPRB_SCALE 0x71304 -+#define _SPRB_GAMC 0x71400 -+ -+#define SPRCTL(pipe) _MMIO_PIPE(pipe, _SPRA_CTL, _SPRB_CTL) -+#define SPRLINOFF(pipe) _MMIO_PIPE(pipe, _SPRA_LINOFF, _SPRB_LINOFF) -+#define SPRSTRIDE(pipe) _MMIO_PIPE(pipe, _SPRA_STRIDE, _SPRB_STRIDE) -+#define SPRPOS(pipe) _MMIO_PIPE(pipe, _SPRA_POS, _SPRB_POS) -+#define SPRSIZE(pipe) _MMIO_PIPE(pipe, _SPRA_SIZE, _SPRB_SIZE) -+#define SPRKEYVAL(pipe) _MMIO_PIPE(pipe, _SPRA_KEYVAL, _SPRB_KEYVAL) -+#define SPRKEYMSK(pipe) _MMIO_PIPE(pipe, _SPRA_KEYMSK, _SPRB_KEYMSK) -+#define SPRSURF(pipe) _MMIO_PIPE(pipe, _SPRA_SURF, _SPRB_SURF) -+#define SPRKEYMAX(pipe) _MMIO_PIPE(pipe, _SPRA_KEYMAX, _SPRB_KEYMAX) -+#define SPRTILEOFF(pipe) _MMIO_PIPE(pipe, _SPRA_TILEOFF, _SPRB_TILEOFF) -+#define SPROFFSET(pipe) _MMIO_PIPE(pipe, _SPRA_OFFSET, _SPRB_OFFSET) -+#define SPRSCALE(pipe) _MMIO_PIPE(pipe, _SPRA_SCALE, _SPRB_SCALE) -+#define SPRGAMC(pipe) _MMIO_PIPE(pipe, _SPRA_GAMC, _SPRB_GAMC) -+#define SPRSURFLIVE(pipe) _MMIO_PIPE(pipe, _SPRA_SURFLIVE, _SPRB_SURFLIVE) -+ -+#define _SPACNTR (VLV_DISPLAY_BASE + 0x72180) -+#define SP_ENABLE (1 << 31) -+#define SP_GAMMA_ENABLE (1 << 30) -+#define SP_PIXFORMAT_MASK (0xf << 26) -+#define SP_FORMAT_YUV422 (0 << 26) -+#define SP_FORMAT_BGR565 (5 << 26) -+#define SP_FORMAT_BGRX8888 (6 << 26) -+#define SP_FORMAT_BGRA8888 (7 << 26) -+#define SP_FORMAT_RGBX1010102 (8 << 26) -+#define SP_FORMAT_RGBA1010102 (9 << 26) -+#define SP_FORMAT_RGBX8888 (0xe << 26) -+#define SP_FORMAT_RGBA8888 (0xf << 26) -+#define SP_ALPHA_PREMULTIPLY (1 << 23) /* CHV pipe B */ -+#define SP_SOURCE_KEY (1 << 22) -+#define SP_YUV_FORMAT_BT709 (1 << 18) -+#define SP_YUV_BYTE_ORDER_MASK (3 << 16) -+#define SP_YUV_ORDER_YUYV (0 << 16) -+#define SP_YUV_ORDER_UYVY (1 << 16) -+#define SP_YUV_ORDER_YVYU (2 << 16) -+#define SP_YUV_ORDER_VYUY (3 << 16) -+#define SP_ROTATE_180 (1 << 15) -+#define SP_TILED (1 << 10) -+#define SP_MIRROR (1 << 8) /* CHV pipe B */ -+#define _SPALINOFF (VLV_DISPLAY_BASE + 0x72184) -+#define _SPASTRIDE (VLV_DISPLAY_BASE + 0x72188) -+#define _SPAPOS (VLV_DISPLAY_BASE + 0x7218c) -+#define _SPASIZE (VLV_DISPLAY_BASE + 0x72190) -+#define _SPAKEYMINVAL (VLV_DISPLAY_BASE + 0x72194) -+#define _SPAKEYMSK (VLV_DISPLAY_BASE + 0x72198) -+#define _SPASURF (VLV_DISPLAY_BASE + 0x7219c) -+#define _SPAKEYMAXVAL (VLV_DISPLAY_BASE + 0x721a0) -+#define _SPATILEOFF (VLV_DISPLAY_BASE + 0x721a4) -+#define _SPACONSTALPHA (VLV_DISPLAY_BASE + 0x721a8) -+#define SP_CONST_ALPHA_ENABLE (1 << 31) -+#define _SPACLRC0 (VLV_DISPLAY_BASE + 0x721d0) -+#define SP_CONTRAST(x) ((x) << 18) /* u3.6 */ -+#define SP_BRIGHTNESS(x) ((x) & 0xff) /* s8 */ -+#define _SPACLRC1 (VLV_DISPLAY_BASE + 0x721d4) -+#define SP_SH_SIN(x) (((x) & 0x7ff) << 16) /* s4.7 */ -+#define SP_SH_COS(x) (x) /* u3.7 */ -+#define _SPAGAMC (VLV_DISPLAY_BASE + 0x721f4) -+ -+#define _SPBCNTR (VLV_DISPLAY_BASE + 0x72280) -+#define _SPBLINOFF (VLV_DISPLAY_BASE + 0x72284) -+#define _SPBSTRIDE (VLV_DISPLAY_BASE + 0x72288) -+#define _SPBPOS (VLV_DISPLAY_BASE + 0x7228c) -+#define _SPBSIZE (VLV_DISPLAY_BASE + 0x72290) -+#define _SPBKEYMINVAL (VLV_DISPLAY_BASE + 0x72294) -+#define _SPBKEYMSK (VLV_DISPLAY_BASE + 0x72298) -+#define _SPBSURF (VLV_DISPLAY_BASE + 0x7229c) -+#define _SPBKEYMAXVAL (VLV_DISPLAY_BASE + 0x722a0) -+#define _SPBTILEOFF (VLV_DISPLAY_BASE + 0x722a4) -+#define _SPBCONSTALPHA (VLV_DISPLAY_BASE + 0x722a8) -+#define _SPBCLRC0 (VLV_DISPLAY_BASE + 0x722d0) -+#define _SPBCLRC1 (VLV_DISPLAY_BASE + 0x722d4) -+#define _SPBGAMC (VLV_DISPLAY_BASE + 0x722f4) -+ -+#define _MMIO_VLV_SPR(pipe, plane_id, reg_a, reg_b) \ -+ _MMIO_PIPE((pipe) * 2 + (plane_id) - PLANE_SPRITE0, (reg_a), (reg_b)) -+ -+#define SPCNTR(pipe, plane_id) _MMIO_VLV_SPR((pipe), (plane_id), _SPACNTR, _SPBCNTR) -+#define SPLINOFF(pipe, plane_id) _MMIO_VLV_SPR((pipe), (plane_id), _SPALINOFF, _SPBLINOFF) -+#define SPSTRIDE(pipe, plane_id) _MMIO_VLV_SPR((pipe), (plane_id), _SPASTRIDE, _SPBSTRIDE) -+#define SPPOS(pipe, plane_id) _MMIO_VLV_SPR((pipe), (plane_id), _SPAPOS, _SPBPOS) -+#define SPSIZE(pipe, plane_id) _MMIO_VLV_SPR((pipe), (plane_id), _SPASIZE, _SPBSIZE) -+#define SPKEYMINVAL(pipe, plane_id) _MMIO_VLV_SPR((pipe), (plane_id), _SPAKEYMINVAL, _SPBKEYMINVAL) -+#define SPKEYMSK(pipe, plane_id) _MMIO_VLV_SPR((pipe), (plane_id), _SPAKEYMSK, _SPBKEYMSK) -+#define SPSURF(pipe, plane_id) _MMIO_VLV_SPR((pipe), (plane_id), _SPASURF, _SPBSURF) -+#define SPKEYMAXVAL(pipe, plane_id) _MMIO_VLV_SPR((pipe), (plane_id), _SPAKEYMAXVAL, _SPBKEYMAXVAL) -+#define SPTILEOFF(pipe, plane_id) _MMIO_VLV_SPR((pipe), (plane_id), _SPATILEOFF, _SPBTILEOFF) -+#define SPCONSTALPHA(pipe, plane_id) _MMIO_VLV_SPR((pipe), (plane_id), _SPACONSTALPHA, _SPBCONSTALPHA) -+#define SPCLRC0(pipe, plane_id) _MMIO_VLV_SPR((pipe), (plane_id), _SPACLRC0, _SPBCLRC0) -+#define SPCLRC1(pipe, plane_id) _MMIO_VLV_SPR((pipe), (plane_id), _SPACLRC1, _SPBCLRC1) -+#define SPGAMC(pipe, plane_id) _MMIO_VLV_SPR((pipe), (plane_id), _SPAGAMC, _SPBGAMC) -+ -+/* -+ * CHV pipe B sprite CSC -+ * -+ * |cr| |c0 c1 c2| |cr + cr_ioff| |cr_ooff| -+ * |yg| = |c3 c4 c5| x |yg + yg_ioff| + |yg_ooff| -+ * |cb| |c6 c7 c8| |cb + cr_ioff| |cb_ooff| -+ */ -+#define _MMIO_CHV_SPCSC(plane_id, reg) \ -+ _MMIO(VLV_DISPLAY_BASE + ((plane_id) - PLANE_SPRITE0) * 0x1000 + (reg)) -+ -+#define SPCSCYGOFF(plane_id) _MMIO_CHV_SPCSC(plane_id, 0x6d900) -+#define SPCSCCBOFF(plane_id) _MMIO_CHV_SPCSC(plane_id, 0x6d904) -+#define SPCSCCROFF(plane_id) _MMIO_CHV_SPCSC(plane_id, 0x6d908) -+#define SPCSC_OOFF(x) (((x) & 0x7ff) << 16) /* s11 */ -+#define SPCSC_IOFF(x) (((x) & 0x7ff) << 0) /* s11 */ -+ -+#define SPCSCC01(plane_id) _MMIO_CHV_SPCSC(plane_id, 0x6d90c) -+#define SPCSCC23(plane_id) _MMIO_CHV_SPCSC(plane_id, 0x6d910) -+#define SPCSCC45(plane_id) _MMIO_CHV_SPCSC(plane_id, 0x6d914) -+#define SPCSCC67(plane_id) _MMIO_CHV_SPCSC(plane_id, 0x6d918) -+#define SPCSCC8(plane_id) _MMIO_CHV_SPCSC(plane_id, 0x6d91c) -+#define SPCSC_C1(x) (((x) & 0x7fff) << 16) /* s3.12 */ -+#define SPCSC_C0(x) (((x) & 0x7fff) << 0) /* s3.12 */ -+ -+#define SPCSCYGICLAMP(plane_id) _MMIO_CHV_SPCSC(plane_id, 0x6d920) -+#define SPCSCCBICLAMP(plane_id) _MMIO_CHV_SPCSC(plane_id, 0x6d924) -+#define SPCSCCRICLAMP(plane_id) _MMIO_CHV_SPCSC(plane_id, 0x6d928) -+#define SPCSC_IMAX(x) (((x) & 0x7ff) << 16) /* s11 */ -+#define SPCSC_IMIN(x) (((x) & 0x7ff) << 0) /* s11 */ -+ -+#define SPCSCYGOCLAMP(plane_id) _MMIO_CHV_SPCSC(plane_id, 0x6d92c) -+#define SPCSCCBOCLAMP(plane_id) _MMIO_CHV_SPCSC(plane_id, 0x6d930) -+#define SPCSCCROCLAMP(plane_id) _MMIO_CHV_SPCSC(plane_id, 0x6d934) -+#define SPCSC_OMAX(x) ((x) << 16) /* u10 */ -+#define SPCSC_OMIN(x) ((x) << 0) /* u10 */ -+ -+/* Skylake plane registers */ -+ -+#define _PLANE_CTL_1_A 0x70180 -+#define _PLANE_CTL_2_A 0x70280 -+#define _PLANE_CTL_3_A 0x70380 -+#define PLANE_CTL_ENABLE (1 << 31) -+#define PLANE_CTL_PIPE_GAMMA_ENABLE (1 << 30) /* Pre-GLK */ -+#define PLANE_CTL_YUV_RANGE_CORRECTION_DISABLE (1 << 28) -+/* -+ * ICL+ uses the same PLANE_CTL_FORMAT bits, but the field definition -+ * expanded to include bit 23 as well. However, the shift-24 based values -+ * correctly map to the same formats in ICL, as long as bit 23 is set to 0 -+ */ -+#define PLANE_CTL_FORMAT_MASK (0xf << 24) -+#define PLANE_CTL_FORMAT_YUV422 (0 << 24) -+#define PLANE_CTL_FORMAT_NV12 (1 << 24) -+#define PLANE_CTL_FORMAT_XRGB_2101010 (2 << 24) -+#define PLANE_CTL_FORMAT_P010 (3 << 24) -+#define PLANE_CTL_FORMAT_XRGB_8888 (4 << 24) -+#define PLANE_CTL_FORMAT_P012 (5 << 24) -+#define PLANE_CTL_FORMAT_XRGB_16161616F (6 << 24) -+#define PLANE_CTL_FORMAT_P016 (7 << 24) -+#define PLANE_CTL_FORMAT_AYUV (8 << 24) -+#define PLANE_CTL_FORMAT_INDEXED (12 << 24) -+#define PLANE_CTL_FORMAT_RGB_565 (14 << 24) -+#define ICL_PLANE_CTL_FORMAT_MASK (0x1f << 23) -+#define PLANE_CTL_PIPE_CSC_ENABLE (1 << 23) /* Pre-GLK */ -+#define PLANE_CTL_FORMAT_Y210 (1 << 23) -+#define PLANE_CTL_FORMAT_Y212 (3 << 23) -+#define PLANE_CTL_FORMAT_Y216 (5 << 23) -+#define PLANE_CTL_FORMAT_Y410 (7 << 23) -+#define PLANE_CTL_FORMAT_Y412 (9 << 23) -+#define PLANE_CTL_FORMAT_Y416 (0xb << 23) -+#define PLANE_CTL_KEY_ENABLE_MASK (0x3 << 21) -+#define PLANE_CTL_KEY_ENABLE_SOURCE (1 << 21) -+#define PLANE_CTL_KEY_ENABLE_DESTINATION (2 << 21) -+#define PLANE_CTL_ORDER_BGRX (0 << 20) -+#define PLANE_CTL_ORDER_RGBX (1 << 20) -+#define PLANE_CTL_YUV420_Y_PLANE (1 << 19) -+#define PLANE_CTL_YUV_TO_RGB_CSC_FORMAT_BT709 (1 << 18) -+#define PLANE_CTL_YUV422_ORDER_MASK (0x3 << 16) -+#define PLANE_CTL_YUV422_YUYV (0 << 16) -+#define PLANE_CTL_YUV422_UYVY (1 << 16) -+#define PLANE_CTL_YUV422_YVYU (2 << 16) -+#define PLANE_CTL_YUV422_VYUY (3 << 16) -+#define PLANE_CTL_RENDER_DECOMPRESSION_ENABLE (1 << 15) -+#define PLANE_CTL_TRICKLE_FEED_DISABLE (1 << 14) -+#define PLANE_CTL_PLANE_GAMMA_DISABLE (1 << 13) /* Pre-GLK */ -+#define PLANE_CTL_TILED_MASK (0x7 << 10) -+#define PLANE_CTL_TILED_LINEAR (0 << 10) -+#define PLANE_CTL_TILED_X (1 << 10) -+#define PLANE_CTL_TILED_Y (4 << 10) -+#define PLANE_CTL_TILED_YF (5 << 10) -+#define PLANE_CTL_FLIP_HORIZONTAL (1 << 8) -+#define PLANE_CTL_ALPHA_MASK (0x3 << 4) /* Pre-GLK */ -+#define PLANE_CTL_ALPHA_DISABLE (0 << 4) -+#define PLANE_CTL_ALPHA_SW_PREMULTIPLY (2 << 4) -+#define PLANE_CTL_ALPHA_HW_PREMULTIPLY (3 << 4) -+#define PLANE_CTL_ROTATE_MASK 0x3 -+#define PLANE_CTL_ROTATE_0 0x0 -+#define PLANE_CTL_ROTATE_90 0x1 -+#define PLANE_CTL_ROTATE_180 0x2 -+#define PLANE_CTL_ROTATE_270 0x3 -+#define _PLANE_STRIDE_1_A 0x70188 -+#define _PLANE_STRIDE_2_A 0x70288 -+#define _PLANE_STRIDE_3_A 0x70388 -+#define _PLANE_POS_1_A 0x7018c -+#define _PLANE_POS_2_A 0x7028c -+#define _PLANE_POS_3_A 0x7038c -+#define _PLANE_SIZE_1_A 0x70190 -+#define _PLANE_SIZE_2_A 0x70290 -+#define _PLANE_SIZE_3_A 0x70390 -+#define _PLANE_SURF_1_A 0x7019c -+#define _PLANE_SURF_2_A 0x7029c -+#define _PLANE_SURF_3_A 0x7039c -+#define _PLANE_OFFSET_1_A 0x701a4 -+#define _PLANE_OFFSET_2_A 0x702a4 -+#define _PLANE_OFFSET_3_A 0x703a4 -+#define _PLANE_KEYVAL_1_A 0x70194 -+#define _PLANE_KEYVAL_2_A 0x70294 -+#define _PLANE_KEYMSK_1_A 0x70198 -+#define _PLANE_KEYMSK_2_A 0x70298 -+#define PLANE_KEYMSK_ALPHA_ENABLE (1 << 31) -+#define _PLANE_KEYMAX_1_A 0x701a0 -+#define _PLANE_KEYMAX_2_A 0x702a0 -+#define PLANE_KEYMAX_ALPHA(a) ((a) << 24) -+#define _PLANE_AUX_DIST_1_A 0x701c0 -+#define _PLANE_AUX_DIST_2_A 0x702c0 -+#define _PLANE_AUX_OFFSET_1_A 0x701c4 -+#define _PLANE_AUX_OFFSET_2_A 0x702c4 -+#define _PLANE_CUS_CTL_1_A 0x701c8 -+#define _PLANE_CUS_CTL_2_A 0x702c8 -+#define PLANE_CUS_ENABLE (1 << 31) -+#define PLANE_CUS_PLANE_6 (0 << 30) -+#define PLANE_CUS_PLANE_7 (1 << 30) -+#define PLANE_CUS_HPHASE_SIGN_NEGATIVE (1 << 19) -+#define PLANE_CUS_HPHASE_0 (0 << 16) -+#define PLANE_CUS_HPHASE_0_25 (1 << 16) -+#define PLANE_CUS_HPHASE_0_5 (2 << 16) -+#define PLANE_CUS_VPHASE_SIGN_NEGATIVE (1 << 15) -+#define PLANE_CUS_VPHASE_0 (0 << 12) -+#define PLANE_CUS_VPHASE_0_25 (1 << 12) -+#define PLANE_CUS_VPHASE_0_5 (2 << 12) -+#define _PLANE_COLOR_CTL_1_A 0x701CC /* GLK+ */ -+#define _PLANE_COLOR_CTL_2_A 0x702CC /* GLK+ */ -+#define _PLANE_COLOR_CTL_3_A 0x703CC /* GLK+ */ -+#define PLANE_COLOR_PIPE_GAMMA_ENABLE (1 << 30) /* Pre-ICL */ -+#define PLANE_COLOR_YUV_RANGE_CORRECTION_DISABLE (1 << 28) -+#define PLANE_COLOR_INPUT_CSC_ENABLE (1 << 20) /* ICL+ */ -+#define PLANE_COLOR_PIPE_CSC_ENABLE (1 << 23) /* Pre-ICL */ -+#define PLANE_COLOR_CSC_MODE_BYPASS (0 << 17) -+#define PLANE_COLOR_CSC_MODE_YUV601_TO_RGB709 (1 << 17) -+#define PLANE_COLOR_CSC_MODE_YUV709_TO_RGB709 (2 << 17) -+#define PLANE_COLOR_CSC_MODE_YUV2020_TO_RGB2020 (3 << 17) -+#define PLANE_COLOR_CSC_MODE_RGB709_TO_RGB2020 (4 << 17) -+#define PLANE_COLOR_PLANE_GAMMA_DISABLE (1 << 13) -+#define PLANE_COLOR_ALPHA_MASK (0x3 << 4) -+#define PLANE_COLOR_ALPHA_DISABLE (0 << 4) -+#define PLANE_COLOR_ALPHA_SW_PREMULTIPLY (2 << 4) -+#define PLANE_COLOR_ALPHA_HW_PREMULTIPLY (3 << 4) -+#define _PLANE_BUF_CFG_1_A 0x7027c -+#define _PLANE_BUF_CFG_2_A 0x7037c -+#define _PLANE_NV12_BUF_CFG_1_A 0x70278 -+#define _PLANE_NV12_BUF_CFG_2_A 0x70378 -+ -+/* Input CSC Register Definitions */ -+#define _PLANE_INPUT_CSC_RY_GY_1_A 0x701E0 -+#define _PLANE_INPUT_CSC_RY_GY_2_A 0x702E0 -+ -+#define _PLANE_INPUT_CSC_RY_GY_1_B 0x711E0 -+#define _PLANE_INPUT_CSC_RY_GY_2_B 0x712E0 -+ -+#define _PLANE_INPUT_CSC_RY_GY_1(pipe) \ -+ _PIPE(pipe, _PLANE_INPUT_CSC_RY_GY_1_A, \ -+ _PLANE_INPUT_CSC_RY_GY_1_B) -+#define _PLANE_INPUT_CSC_RY_GY_2(pipe) \ -+ _PIPE(pipe, _PLANE_INPUT_CSC_RY_GY_2_A, \ -+ _PLANE_INPUT_CSC_RY_GY_2_B) -+ -+#define PLANE_INPUT_CSC_COEFF(pipe, plane, index) \ -+ _MMIO_PLANE(plane, _PLANE_INPUT_CSC_RY_GY_1(pipe) + (index) * 4, \ -+ _PLANE_INPUT_CSC_RY_GY_2(pipe) + (index) * 4) -+ -+#define _PLANE_INPUT_CSC_PREOFF_HI_1_A 0x701F8 -+#define _PLANE_INPUT_CSC_PREOFF_HI_2_A 0x702F8 -+ -+#define _PLANE_INPUT_CSC_PREOFF_HI_1_B 0x711F8 -+#define _PLANE_INPUT_CSC_PREOFF_HI_2_B 0x712F8 -+ -+#define _PLANE_INPUT_CSC_PREOFF_HI_1(pipe) \ -+ _PIPE(pipe, _PLANE_INPUT_CSC_PREOFF_HI_1_A, \ -+ _PLANE_INPUT_CSC_PREOFF_HI_1_B) -+#define _PLANE_INPUT_CSC_PREOFF_HI_2(pipe) \ -+ _PIPE(pipe, _PLANE_INPUT_CSC_PREOFF_HI_2_A, \ -+ _PLANE_INPUT_CSC_PREOFF_HI_2_B) -+#define PLANE_INPUT_CSC_PREOFF(pipe, plane, index) \ -+ _MMIO_PLANE(plane, _PLANE_INPUT_CSC_PREOFF_HI_1(pipe) + (index) * 4, \ -+ _PLANE_INPUT_CSC_PREOFF_HI_2(pipe) + (index) * 4) -+ -+#define _PLANE_INPUT_CSC_POSTOFF_HI_1_A 0x70204 -+#define _PLANE_INPUT_CSC_POSTOFF_HI_2_A 0x70304 -+ -+#define _PLANE_INPUT_CSC_POSTOFF_HI_1_B 0x71204 -+#define _PLANE_INPUT_CSC_POSTOFF_HI_2_B 0x71304 -+ -+#define _PLANE_INPUT_CSC_POSTOFF_HI_1(pipe) \ -+ _PIPE(pipe, _PLANE_INPUT_CSC_POSTOFF_HI_1_A, \ -+ _PLANE_INPUT_CSC_POSTOFF_HI_1_B) -+#define _PLANE_INPUT_CSC_POSTOFF_HI_2(pipe) \ -+ _PIPE(pipe, _PLANE_INPUT_CSC_POSTOFF_HI_2_A, \ -+ _PLANE_INPUT_CSC_POSTOFF_HI_2_B) -+#define PLANE_INPUT_CSC_POSTOFF(pipe, plane, index) \ -+ _MMIO_PLANE(plane, _PLANE_INPUT_CSC_POSTOFF_HI_1(pipe) + (index) * 4, \ -+ _PLANE_INPUT_CSC_POSTOFF_HI_2(pipe) + (index) * 4) -+ -+#define _PLANE_CTL_1_B 0x71180 -+#define _PLANE_CTL_2_B 0x71280 -+#define _PLANE_CTL_3_B 0x71380 -+#define _PLANE_CTL_1(pipe) _PIPE(pipe, _PLANE_CTL_1_A, _PLANE_CTL_1_B) -+#define _PLANE_CTL_2(pipe) _PIPE(pipe, _PLANE_CTL_2_A, _PLANE_CTL_2_B) -+#define _PLANE_CTL_3(pipe) _PIPE(pipe, _PLANE_CTL_3_A, _PLANE_CTL_3_B) -+#define PLANE_CTL(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_CTL_1(pipe), _PLANE_CTL_2(pipe)) -+ -+#define _PLANE_STRIDE_1_B 0x71188 -+#define _PLANE_STRIDE_2_B 0x71288 -+#define _PLANE_STRIDE_3_B 0x71388 -+#define _PLANE_STRIDE_1(pipe) \ -+ _PIPE(pipe, _PLANE_STRIDE_1_A, _PLANE_STRIDE_1_B) -+#define _PLANE_STRIDE_2(pipe) \ -+ _PIPE(pipe, _PLANE_STRIDE_2_A, _PLANE_STRIDE_2_B) -+#define _PLANE_STRIDE_3(pipe) \ -+ _PIPE(pipe, _PLANE_STRIDE_3_A, _PLANE_STRIDE_3_B) -+#define PLANE_STRIDE(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_STRIDE_1(pipe), _PLANE_STRIDE_2(pipe)) -+ -+#define _PLANE_POS_1_B 0x7118c -+#define _PLANE_POS_2_B 0x7128c -+#define _PLANE_POS_3_B 0x7138c -+#define _PLANE_POS_1(pipe) _PIPE(pipe, _PLANE_POS_1_A, _PLANE_POS_1_B) -+#define _PLANE_POS_2(pipe) _PIPE(pipe, _PLANE_POS_2_A, _PLANE_POS_2_B) -+#define _PLANE_POS_3(pipe) _PIPE(pipe, _PLANE_POS_3_A, _PLANE_POS_3_B) -+#define PLANE_POS(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_POS_1(pipe), _PLANE_POS_2(pipe)) -+ -+#define _PLANE_SIZE_1_B 0x71190 -+#define _PLANE_SIZE_2_B 0x71290 -+#define _PLANE_SIZE_3_B 0x71390 -+#define _PLANE_SIZE_1(pipe) _PIPE(pipe, _PLANE_SIZE_1_A, _PLANE_SIZE_1_B) -+#define _PLANE_SIZE_2(pipe) _PIPE(pipe, _PLANE_SIZE_2_A, _PLANE_SIZE_2_B) -+#define _PLANE_SIZE_3(pipe) _PIPE(pipe, _PLANE_SIZE_3_A, _PLANE_SIZE_3_B) -+#define PLANE_SIZE(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_SIZE_1(pipe), _PLANE_SIZE_2(pipe)) -+ -+#define _PLANE_SURF_1_B 0x7119c -+#define _PLANE_SURF_2_B 0x7129c -+#define _PLANE_SURF_3_B 0x7139c -+#define _PLANE_SURF_1(pipe) _PIPE(pipe, _PLANE_SURF_1_A, _PLANE_SURF_1_B) -+#define _PLANE_SURF_2(pipe) _PIPE(pipe, _PLANE_SURF_2_A, _PLANE_SURF_2_B) -+#define _PLANE_SURF_3(pipe) _PIPE(pipe, _PLANE_SURF_3_A, _PLANE_SURF_3_B) -+#define PLANE_SURF(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_SURF_1(pipe), _PLANE_SURF_2(pipe)) -+ -+#define _PLANE_OFFSET_1_B 0x711a4 -+#define _PLANE_OFFSET_2_B 0x712a4 -+#define _PLANE_OFFSET_1(pipe) _PIPE(pipe, _PLANE_OFFSET_1_A, _PLANE_OFFSET_1_B) -+#define _PLANE_OFFSET_2(pipe) _PIPE(pipe, _PLANE_OFFSET_2_A, _PLANE_OFFSET_2_B) -+#define PLANE_OFFSET(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_OFFSET_1(pipe), _PLANE_OFFSET_2(pipe)) -+ -+#define _PLANE_KEYVAL_1_B 0x71194 -+#define _PLANE_KEYVAL_2_B 0x71294 -+#define _PLANE_KEYVAL_1(pipe) _PIPE(pipe, _PLANE_KEYVAL_1_A, _PLANE_KEYVAL_1_B) -+#define _PLANE_KEYVAL_2(pipe) _PIPE(pipe, _PLANE_KEYVAL_2_A, _PLANE_KEYVAL_2_B) -+#define PLANE_KEYVAL(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_KEYVAL_1(pipe), _PLANE_KEYVAL_2(pipe)) -+ -+#define _PLANE_KEYMSK_1_B 0x71198 -+#define _PLANE_KEYMSK_2_B 0x71298 -+#define _PLANE_KEYMSK_1(pipe) _PIPE(pipe, _PLANE_KEYMSK_1_A, _PLANE_KEYMSK_1_B) -+#define _PLANE_KEYMSK_2(pipe) _PIPE(pipe, _PLANE_KEYMSK_2_A, _PLANE_KEYMSK_2_B) -+#define PLANE_KEYMSK(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_KEYMSK_1(pipe), _PLANE_KEYMSK_2(pipe)) -+ -+#define _PLANE_KEYMAX_1_B 0x711a0 -+#define _PLANE_KEYMAX_2_B 0x712a0 -+#define _PLANE_KEYMAX_1(pipe) _PIPE(pipe, _PLANE_KEYMAX_1_A, _PLANE_KEYMAX_1_B) -+#define _PLANE_KEYMAX_2(pipe) _PIPE(pipe, _PLANE_KEYMAX_2_A, _PLANE_KEYMAX_2_B) -+#define PLANE_KEYMAX(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_KEYMAX_1(pipe), _PLANE_KEYMAX_2(pipe)) -+ -+#define _PLANE_BUF_CFG_1_B 0x7127c -+#define _PLANE_BUF_CFG_2_B 0x7137c -+#define DDB_ENTRY_MASK 0x7FF /* skl+: 10 bits, icl+ 11 bits */ -+#define DDB_ENTRY_END_SHIFT 16 -+#define _PLANE_BUF_CFG_1(pipe) \ -+ _PIPE(pipe, _PLANE_BUF_CFG_1_A, _PLANE_BUF_CFG_1_B) -+#define _PLANE_BUF_CFG_2(pipe) \ -+ _PIPE(pipe, _PLANE_BUF_CFG_2_A, _PLANE_BUF_CFG_2_B) -+#define PLANE_BUF_CFG(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_BUF_CFG_1(pipe), _PLANE_BUF_CFG_2(pipe)) -+ -+#define _PLANE_NV12_BUF_CFG_1_B 0x71278 -+#define _PLANE_NV12_BUF_CFG_2_B 0x71378 -+#define _PLANE_NV12_BUF_CFG_1(pipe) \ -+ _PIPE(pipe, _PLANE_NV12_BUF_CFG_1_A, _PLANE_NV12_BUF_CFG_1_B) -+#define _PLANE_NV12_BUF_CFG_2(pipe) \ -+ _PIPE(pipe, _PLANE_NV12_BUF_CFG_2_A, _PLANE_NV12_BUF_CFG_2_B) -+#define PLANE_NV12_BUF_CFG(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_NV12_BUF_CFG_1(pipe), _PLANE_NV12_BUF_CFG_2(pipe)) -+ -+#define _PLANE_AUX_DIST_1_B 0x711c0 -+#define _PLANE_AUX_DIST_2_B 0x712c0 -+#define _PLANE_AUX_DIST_1(pipe) \ -+ _PIPE(pipe, _PLANE_AUX_DIST_1_A, _PLANE_AUX_DIST_1_B) -+#define _PLANE_AUX_DIST_2(pipe) \ -+ _PIPE(pipe, _PLANE_AUX_DIST_2_A, _PLANE_AUX_DIST_2_B) -+#define PLANE_AUX_DIST(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_AUX_DIST_1(pipe), _PLANE_AUX_DIST_2(pipe)) -+ -+#define _PLANE_AUX_OFFSET_1_B 0x711c4 -+#define _PLANE_AUX_OFFSET_2_B 0x712c4 -+#define _PLANE_AUX_OFFSET_1(pipe) \ -+ _PIPE(pipe, _PLANE_AUX_OFFSET_1_A, _PLANE_AUX_OFFSET_1_B) -+#define _PLANE_AUX_OFFSET_2(pipe) \ -+ _PIPE(pipe, _PLANE_AUX_OFFSET_2_A, _PLANE_AUX_OFFSET_2_B) -+#define PLANE_AUX_OFFSET(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_AUX_OFFSET_1(pipe), _PLANE_AUX_OFFSET_2(pipe)) -+ -+#define _PLANE_CUS_CTL_1_B 0x711c8 -+#define _PLANE_CUS_CTL_2_B 0x712c8 -+#define _PLANE_CUS_CTL_1(pipe) \ -+ _PIPE(pipe, _PLANE_CUS_CTL_1_A, _PLANE_CUS_CTL_1_B) -+#define _PLANE_CUS_CTL_2(pipe) \ -+ _PIPE(pipe, _PLANE_CUS_CTL_2_A, _PLANE_CUS_CTL_2_B) -+#define PLANE_CUS_CTL(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_CUS_CTL_1(pipe), _PLANE_CUS_CTL_2(pipe)) -+ -+#define _PLANE_COLOR_CTL_1_B 0x711CC -+#define _PLANE_COLOR_CTL_2_B 0x712CC -+#define _PLANE_COLOR_CTL_3_B 0x713CC -+#define _PLANE_COLOR_CTL_1(pipe) \ -+ _PIPE(pipe, _PLANE_COLOR_CTL_1_A, _PLANE_COLOR_CTL_1_B) -+#define _PLANE_COLOR_CTL_2(pipe) \ -+ _PIPE(pipe, _PLANE_COLOR_CTL_2_A, _PLANE_COLOR_CTL_2_B) -+#define PLANE_COLOR_CTL(pipe, plane) \ -+ _MMIO_PLANE(plane, _PLANE_COLOR_CTL_1(pipe), _PLANE_COLOR_CTL_2(pipe)) -+ -+#/* SKL new cursor registers */ -+#define _CUR_BUF_CFG_A 0x7017c -+#define _CUR_BUF_CFG_B 0x7117c -+#define CUR_BUF_CFG(pipe) _MMIO_PIPE(pipe, _CUR_BUF_CFG_A, _CUR_BUF_CFG_B) -+ -+/* VBIOS regs */ -+#define VGACNTRL _MMIO(0x71400) -+# define VGA_DISP_DISABLE (1 << 31) -+# define VGA_2X_MODE (1 << 30) -+# define VGA_PIPE_B_SELECT (1 << 29) -+ -+#define VLV_VGACNTRL _MMIO(VLV_DISPLAY_BASE + 0x71400) -+ -+/* Ironlake */ -+ -+#define CPU_VGACNTRL _MMIO(0x41000) -+ -+#define DIGITAL_PORT_HOTPLUG_CNTRL _MMIO(0x44030) -+#define DIGITAL_PORTA_HOTPLUG_ENABLE (1 << 4) -+#define DIGITAL_PORTA_PULSE_DURATION_2ms (0 << 2) /* pre-HSW */ -+#define DIGITAL_PORTA_PULSE_DURATION_4_5ms (1 << 2) /* pre-HSW */ -+#define DIGITAL_PORTA_PULSE_DURATION_6ms (2 << 2) /* pre-HSW */ -+#define DIGITAL_PORTA_PULSE_DURATION_100ms (3 << 2) /* pre-HSW */ -+#define DIGITAL_PORTA_PULSE_DURATION_MASK (3 << 2) /* pre-HSW */ -+#define DIGITAL_PORTA_HOTPLUG_STATUS_MASK (3 << 0) -+#define DIGITAL_PORTA_HOTPLUG_NO_DETECT (0 << 0) -+#define DIGITAL_PORTA_HOTPLUG_SHORT_DETECT (1 << 0) -+#define DIGITAL_PORTA_HOTPLUG_LONG_DETECT (2 << 0) -+ -+/* refresh rate hardware control */ -+#define RR_HW_CTL _MMIO(0x45300) -+#define RR_HW_LOW_POWER_FRAMES_MASK 0xff -+#define RR_HW_HIGH_POWER_FRAMES_MASK 0xff00 -+ -+#define FDI_PLL_BIOS_0 _MMIO(0x46000) -+#define FDI_PLL_FB_CLOCK_MASK 0xff -+#define FDI_PLL_BIOS_1 _MMIO(0x46004) -+#define FDI_PLL_BIOS_2 _MMIO(0x46008) -+#define DISPLAY_PORT_PLL_BIOS_0 _MMIO(0x4600c) -+#define DISPLAY_PORT_PLL_BIOS_1 _MMIO(0x46010) -+#define DISPLAY_PORT_PLL_BIOS_2 _MMIO(0x46014) -+ -+#define PCH_3DCGDIS0 _MMIO(0x46020) -+# define MARIUNIT_CLOCK_GATE_DISABLE (1 << 18) -+# define SVSMUNIT_CLOCK_GATE_DISABLE (1 << 1) -+ -+#define PCH_3DCGDIS1 _MMIO(0x46024) -+# define VFMUNIT_CLOCK_GATE_DISABLE (1 << 11) -+ -+#define FDI_PLL_FREQ_CTL _MMIO(0x46030) -+#define FDI_PLL_FREQ_CHANGE_REQUEST (1 << 24) -+#define FDI_PLL_FREQ_LOCK_LIMIT_MASK 0xfff00 -+#define FDI_PLL_FREQ_DISABLE_COUNT_LIMIT_MASK 0xff -+ -+ -+#define _PIPEA_DATA_M1 0x60030 -+#define PIPE_DATA_M1_OFFSET 0 -+#define _PIPEA_DATA_N1 0x60034 -+#define PIPE_DATA_N1_OFFSET 0 -+ -+#define _PIPEA_DATA_M2 0x60038 -+#define PIPE_DATA_M2_OFFSET 0 -+#define _PIPEA_DATA_N2 0x6003c -+#define PIPE_DATA_N2_OFFSET 0 -+ -+#define _PIPEA_LINK_M1 0x60040 -+#define PIPE_LINK_M1_OFFSET 0 -+#define _PIPEA_LINK_N1 0x60044 -+#define PIPE_LINK_N1_OFFSET 0 -+ -+#define _PIPEA_LINK_M2 0x60048 -+#define PIPE_LINK_M2_OFFSET 0 -+#define _PIPEA_LINK_N2 0x6004c -+#define PIPE_LINK_N2_OFFSET 0 -+ -+/* PIPEB timing regs are same start from 0x61000 */ -+ -+#define _PIPEB_DATA_M1 0x61030 -+#define _PIPEB_DATA_N1 0x61034 -+#define _PIPEB_DATA_M2 0x61038 -+#define _PIPEB_DATA_N2 0x6103c -+#define _PIPEB_LINK_M1 0x61040 -+#define _PIPEB_LINK_N1 0x61044 -+#define _PIPEB_LINK_M2 0x61048 -+#define _PIPEB_LINK_N2 0x6104c -+ -+#define PIPE_DATA_M1(tran) _MMIO_TRANS2(tran, _PIPEA_DATA_M1) -+#define PIPE_DATA_N1(tran) _MMIO_TRANS2(tran, _PIPEA_DATA_N1) -+#define PIPE_DATA_M2(tran) _MMIO_TRANS2(tran, _PIPEA_DATA_M2) -+#define PIPE_DATA_N2(tran) _MMIO_TRANS2(tran, _PIPEA_DATA_N2) -+#define PIPE_LINK_M1(tran) _MMIO_TRANS2(tran, _PIPEA_LINK_M1) -+#define PIPE_LINK_N1(tran) _MMIO_TRANS2(tran, _PIPEA_LINK_N1) -+#define PIPE_LINK_M2(tran) _MMIO_TRANS2(tran, _PIPEA_LINK_M2) -+#define PIPE_LINK_N2(tran) _MMIO_TRANS2(tran, _PIPEA_LINK_N2) -+ -+/* CPU panel fitter */ -+/* IVB+ has 3 fitters, 0 is 7x5 capable, the other two only 3x3 */ -+#define _PFA_CTL_1 0x68080 -+#define _PFB_CTL_1 0x68880 -+#define PF_ENABLE (1 << 31) -+#define PF_PIPE_SEL_MASK_IVB (3 << 29) -+#define PF_PIPE_SEL_IVB(pipe) ((pipe) << 29) -+#define PF_FILTER_MASK (3 << 23) -+#define PF_FILTER_PROGRAMMED (0 << 23) -+#define PF_FILTER_MED_3x3 (1 << 23) -+#define PF_FILTER_EDGE_ENHANCE (2 << 23) -+#define PF_FILTER_EDGE_SOFTEN (3 << 23) -+#define _PFA_WIN_SZ 0x68074 -+#define _PFB_WIN_SZ 0x68874 -+#define _PFA_WIN_POS 0x68070 -+#define _PFB_WIN_POS 0x68870 -+#define _PFA_VSCALE 0x68084 -+#define _PFB_VSCALE 0x68884 -+#define _PFA_HSCALE 0x68090 -+#define _PFB_HSCALE 0x68890 -+ -+#define PF_CTL(pipe) _MMIO_PIPE(pipe, _PFA_CTL_1, _PFB_CTL_1) -+#define PF_WIN_SZ(pipe) _MMIO_PIPE(pipe, _PFA_WIN_SZ, _PFB_WIN_SZ) -+#define PF_WIN_POS(pipe) _MMIO_PIPE(pipe, _PFA_WIN_POS, _PFB_WIN_POS) -+#define PF_VSCALE(pipe) _MMIO_PIPE(pipe, _PFA_VSCALE, _PFB_VSCALE) -+#define PF_HSCALE(pipe) _MMIO_PIPE(pipe, _PFA_HSCALE, _PFB_HSCALE) -+ -+#define _PSA_CTL 0x68180 -+#define _PSB_CTL 0x68980 -+#define PS_ENABLE (1 << 31) -+#define _PSA_WIN_SZ 0x68174 -+#define _PSB_WIN_SZ 0x68974 -+#define _PSA_WIN_POS 0x68170 -+#define _PSB_WIN_POS 0x68970 -+ -+#define PS_CTL(pipe) _MMIO_PIPE(pipe, _PSA_CTL, _PSB_CTL) -+#define PS_WIN_SZ(pipe) _MMIO_PIPE(pipe, _PSA_WIN_SZ, _PSB_WIN_SZ) -+#define PS_WIN_POS(pipe) _MMIO_PIPE(pipe, _PSA_WIN_POS, _PSB_WIN_POS) -+ -+/* -+ * Skylake scalers -+ */ -+#define _PS_1A_CTRL 0x68180 -+#define _PS_2A_CTRL 0x68280 -+#define _PS_1B_CTRL 0x68980 -+#define _PS_2B_CTRL 0x68A80 -+#define _PS_1C_CTRL 0x69180 -+#define PS_SCALER_EN (1 << 31) -+#define SKL_PS_SCALER_MODE_MASK (3 << 28) -+#define SKL_PS_SCALER_MODE_DYN (0 << 28) -+#define SKL_PS_SCALER_MODE_HQ (1 << 28) -+#define SKL_PS_SCALER_MODE_NV12 (2 << 28) -+#define PS_SCALER_MODE_PLANAR (1 << 29) -+#define PS_SCALER_MODE_NORMAL (0 << 29) -+#define PS_PLANE_SEL_MASK (7 << 25) -+#define PS_PLANE_SEL(plane) (((plane) + 1) << 25) -+#define PS_FILTER_MASK (3 << 23) -+#define PS_FILTER_MEDIUM (0 << 23) -+#define PS_FILTER_EDGE_ENHANCE (2 << 23) -+#define PS_FILTER_BILINEAR (3 << 23) -+#define PS_VERT3TAP (1 << 21) -+#define PS_VERT_INT_INVERT_FIELD1 (0 << 20) -+#define PS_VERT_INT_INVERT_FIELD0 (1 << 20) -+#define PS_PWRUP_PROGRESS (1 << 17) -+#define PS_V_FILTER_BYPASS (1 << 8) -+#define PS_VADAPT_EN (1 << 7) -+#define PS_VADAPT_MODE_MASK (3 << 5) -+#define PS_VADAPT_MODE_LEAST_ADAPT (0 << 5) -+#define PS_VADAPT_MODE_MOD_ADAPT (1 << 5) -+#define PS_VADAPT_MODE_MOST_ADAPT (3 << 5) -+#define PS_PLANE_Y_SEL_MASK (7 << 5) -+#define PS_PLANE_Y_SEL(plane) (((plane) + 1) << 5) -+ -+#define _PS_PWR_GATE_1A 0x68160 -+#define _PS_PWR_GATE_2A 0x68260 -+#define _PS_PWR_GATE_1B 0x68960 -+#define _PS_PWR_GATE_2B 0x68A60 -+#define _PS_PWR_GATE_1C 0x69160 -+#define PS_PWR_GATE_DIS_OVERRIDE (1 << 31) -+#define PS_PWR_GATE_SETTLING_TIME_32 (0 << 3) -+#define PS_PWR_GATE_SETTLING_TIME_64 (1 << 3) -+#define PS_PWR_GATE_SETTLING_TIME_96 (2 << 3) -+#define PS_PWR_GATE_SETTLING_TIME_128 (3 << 3) -+#define PS_PWR_GATE_SLPEN_8 0 -+#define PS_PWR_GATE_SLPEN_16 1 -+#define PS_PWR_GATE_SLPEN_24 2 -+#define PS_PWR_GATE_SLPEN_32 3 -+ -+#define _PS_WIN_POS_1A 0x68170 -+#define _PS_WIN_POS_2A 0x68270 -+#define _PS_WIN_POS_1B 0x68970 -+#define _PS_WIN_POS_2B 0x68A70 -+#define _PS_WIN_POS_1C 0x69170 -+ -+#define _PS_WIN_SZ_1A 0x68174 -+#define _PS_WIN_SZ_2A 0x68274 -+#define _PS_WIN_SZ_1B 0x68974 -+#define _PS_WIN_SZ_2B 0x68A74 -+#define _PS_WIN_SZ_1C 0x69174 -+ -+#define _PS_VSCALE_1A 0x68184 -+#define _PS_VSCALE_2A 0x68284 -+#define _PS_VSCALE_1B 0x68984 -+#define _PS_VSCALE_2B 0x68A84 -+#define _PS_VSCALE_1C 0x69184 -+ -+#define _PS_HSCALE_1A 0x68190 -+#define _PS_HSCALE_2A 0x68290 -+#define _PS_HSCALE_1B 0x68990 -+#define _PS_HSCALE_2B 0x68A90 -+#define _PS_HSCALE_1C 0x69190 -+ -+#define _PS_VPHASE_1A 0x68188 -+#define _PS_VPHASE_2A 0x68288 -+#define _PS_VPHASE_1B 0x68988 -+#define _PS_VPHASE_2B 0x68A88 -+#define _PS_VPHASE_1C 0x69188 -+#define PS_Y_PHASE(x) ((x) << 16) -+#define PS_UV_RGB_PHASE(x) ((x) << 0) -+#define PS_PHASE_MASK (0x7fff << 1) /* u2.13 */ -+#define PS_PHASE_TRIP (1 << 0) -+ -+#define _PS_HPHASE_1A 0x68194 -+#define _PS_HPHASE_2A 0x68294 -+#define _PS_HPHASE_1B 0x68994 -+#define _PS_HPHASE_2B 0x68A94 -+#define _PS_HPHASE_1C 0x69194 -+ -+#define _PS_ECC_STAT_1A 0x681D0 -+#define _PS_ECC_STAT_2A 0x682D0 -+#define _PS_ECC_STAT_1B 0x689D0 -+#define _PS_ECC_STAT_2B 0x68AD0 -+#define _PS_ECC_STAT_1C 0x691D0 -+ -+#define _ID(id, a, b) _PICK_EVEN(id, a, b) -+#define SKL_PS_CTRL(pipe, id) _MMIO_PIPE(pipe, \ -+ _ID(id, _PS_1A_CTRL, _PS_2A_CTRL), \ -+ _ID(id, _PS_1B_CTRL, _PS_2B_CTRL)) -+#define SKL_PS_PWR_GATE(pipe, id) _MMIO_PIPE(pipe, \ -+ _ID(id, _PS_PWR_GATE_1A, _PS_PWR_GATE_2A), \ -+ _ID(id, _PS_PWR_GATE_1B, _PS_PWR_GATE_2B)) -+#define SKL_PS_WIN_POS(pipe, id) _MMIO_PIPE(pipe, \ -+ _ID(id, _PS_WIN_POS_1A, _PS_WIN_POS_2A), \ -+ _ID(id, _PS_WIN_POS_1B, _PS_WIN_POS_2B)) -+#define SKL_PS_WIN_SZ(pipe, id) _MMIO_PIPE(pipe, \ -+ _ID(id, _PS_WIN_SZ_1A, _PS_WIN_SZ_2A), \ -+ _ID(id, _PS_WIN_SZ_1B, _PS_WIN_SZ_2B)) -+#define SKL_PS_VSCALE(pipe, id) _MMIO_PIPE(pipe, \ -+ _ID(id, _PS_VSCALE_1A, _PS_VSCALE_2A), \ -+ _ID(id, _PS_VSCALE_1B, _PS_VSCALE_2B)) -+#define SKL_PS_HSCALE(pipe, id) _MMIO_PIPE(pipe, \ -+ _ID(id, _PS_HSCALE_1A, _PS_HSCALE_2A), \ -+ _ID(id, _PS_HSCALE_1B, _PS_HSCALE_2B)) -+#define SKL_PS_VPHASE(pipe, id) _MMIO_PIPE(pipe, \ -+ _ID(id, _PS_VPHASE_1A, _PS_VPHASE_2A), \ -+ _ID(id, _PS_VPHASE_1B, _PS_VPHASE_2B)) -+#define SKL_PS_HPHASE(pipe, id) _MMIO_PIPE(pipe, \ -+ _ID(id, _PS_HPHASE_1A, _PS_HPHASE_2A), \ -+ _ID(id, _PS_HPHASE_1B, _PS_HPHASE_2B)) -+#define SKL_PS_ECC_STAT(pipe, id) _MMIO_PIPE(pipe, \ -+ _ID(id, _PS_ECC_STAT_1A, _PS_ECC_STAT_2A), \ -+ _ID(id, _PS_ECC_STAT_1B, _PS_ECC_STAT_2B)) -+ -+/* legacy palette */ -+#define _LGC_PALETTE_A 0x4a000 -+#define _LGC_PALETTE_B 0x4a800 -+#define LGC_PALETTE(pipe, i) _MMIO(_PIPE(pipe, _LGC_PALETTE_A, _LGC_PALETTE_B) + (i) * 4) -+ -+/* ilk/snb precision palette */ -+#define _PREC_PALETTE_A 0x4b000 -+#define _PREC_PALETTE_B 0x4c000 -+#define PREC_PALETTE(pipe, i) _MMIO(_PIPE(pipe, _PREC_PALETTE_A, _PREC_PALETTE_B) + (i) * 4) -+ -+#define _PREC_PIPEAGCMAX 0x4d000 -+#define _PREC_PIPEBGCMAX 0x4d010 -+#define PREC_PIPEGCMAX(pipe, i) _MMIO(_PIPE(pipe, _PIPEAGCMAX, _PIPEBGCMAX) + (i) * 4) -+ -+#define _GAMMA_MODE_A 0x4a480 -+#define _GAMMA_MODE_B 0x4ac80 -+#define GAMMA_MODE(pipe) _MMIO_PIPE(pipe, _GAMMA_MODE_A, _GAMMA_MODE_B) -+#define PRE_CSC_GAMMA_ENABLE (1 << 31) -+#define POST_CSC_GAMMA_ENABLE (1 << 30) -+#define GAMMA_MODE_MODE_MASK (3 << 0) -+#define GAMMA_MODE_MODE_8BIT (0 << 0) -+#define GAMMA_MODE_MODE_10BIT (1 << 0) -+#define GAMMA_MODE_MODE_12BIT (2 << 0) -+#define GAMMA_MODE_MODE_SPLIT (3 << 0) -+ -+/* DMC/CSR */ -+#define CSR_PROGRAM(i) _MMIO(0x80000 + (i) * 4) -+#define CSR_SSP_BASE_ADDR_GEN9 0x00002FC0 -+#define CSR_HTP_ADDR_SKL 0x00500034 -+#define CSR_SSP_BASE _MMIO(0x8F074) -+#define CSR_HTP_SKL _MMIO(0x8F004) -+#define CSR_LAST_WRITE _MMIO(0x8F034) -+#define CSR_LAST_WRITE_VALUE 0xc003b400 -+/* MMIO address range for CSR program (0x80000 - 0x82FFF) */ -+#define CSR_MMIO_START_RANGE 0x80000 -+#define CSR_MMIO_END_RANGE 0x8FFFF -+#define SKL_CSR_DC3_DC5_COUNT _MMIO(0x80030) -+#define SKL_CSR_DC5_DC6_COUNT _MMIO(0x8002C) -+#define BXT_CSR_DC3_DC5_COUNT _MMIO(0x80038) -+ -+/* interrupts */ -+#define DE_MASTER_IRQ_CONTROL (1 << 31) -+#define DE_SPRITEB_FLIP_DONE (1 << 29) -+#define DE_SPRITEA_FLIP_DONE (1 << 28) -+#define DE_PLANEB_FLIP_DONE (1 << 27) -+#define DE_PLANEA_FLIP_DONE (1 << 26) -+#define DE_PLANE_FLIP_DONE(plane) (1 << (26 + (plane))) -+#define DE_PCU_EVENT (1 << 25) -+#define DE_GTT_FAULT (1 << 24) -+#define DE_POISON (1 << 23) -+#define DE_PERFORM_COUNTER (1 << 22) -+#define DE_PCH_EVENT (1 << 21) -+#define DE_AUX_CHANNEL_A (1 << 20) -+#define DE_DP_A_HOTPLUG (1 << 19) -+#define DE_GSE (1 << 18) -+#define DE_PIPEB_VBLANK (1 << 15) -+#define DE_PIPEB_EVEN_FIELD (1 << 14) -+#define DE_PIPEB_ODD_FIELD (1 << 13) -+#define DE_PIPEB_LINE_COMPARE (1 << 12) -+#define DE_PIPEB_VSYNC (1 << 11) -+#define DE_PIPEB_CRC_DONE (1 << 10) -+#define DE_PIPEB_FIFO_UNDERRUN (1 << 8) -+#define DE_PIPEA_VBLANK (1 << 7) -+#define DE_PIPE_VBLANK(pipe) (1 << (7 + 8 * (pipe))) -+#define DE_PIPEA_EVEN_FIELD (1 << 6) -+#define DE_PIPEA_ODD_FIELD (1 << 5) -+#define DE_PIPEA_LINE_COMPARE (1 << 4) -+#define DE_PIPEA_VSYNC (1 << 3) -+#define DE_PIPEA_CRC_DONE (1 << 2) -+#define DE_PIPE_CRC_DONE(pipe) (1 << (2 + 8 * (pipe))) -+#define DE_PIPEA_FIFO_UNDERRUN (1 << 0) -+#define DE_PIPE_FIFO_UNDERRUN(pipe) (1 << (8 * (pipe))) -+ -+/* More Ivybridge lolz */ -+#define DE_ERR_INT_IVB (1 << 30) -+#define DE_GSE_IVB (1 << 29) -+#define DE_PCH_EVENT_IVB (1 << 28) -+#define DE_DP_A_HOTPLUG_IVB (1 << 27) -+#define DE_AUX_CHANNEL_A_IVB (1 << 26) -+#define DE_EDP_PSR_INT_HSW (1 << 19) -+#define DE_SPRITEC_FLIP_DONE_IVB (1 << 14) -+#define DE_PLANEC_FLIP_DONE_IVB (1 << 13) -+#define DE_PIPEC_VBLANK_IVB (1 << 10) -+#define DE_SPRITEB_FLIP_DONE_IVB (1 << 9) -+#define DE_PLANEB_FLIP_DONE_IVB (1 << 8) -+#define DE_PIPEB_VBLANK_IVB (1 << 5) -+#define DE_SPRITEA_FLIP_DONE_IVB (1 << 4) -+#define DE_PLANEA_FLIP_DONE_IVB (1 << 3) -+#define DE_PLANE_FLIP_DONE_IVB(plane) (1 << (3 + 5 * (plane))) -+#define DE_PIPEA_VBLANK_IVB (1 << 0) -+#define DE_PIPE_VBLANK_IVB(pipe) (1 << ((pipe) * 5)) -+ -+#define VLV_MASTER_IER _MMIO(0x4400c) /* Gunit master IER */ -+#define MASTER_INTERRUPT_ENABLE (1 << 31) -+ -+#define DEISR _MMIO(0x44000) -+#define DEIMR _MMIO(0x44004) -+#define DEIIR _MMIO(0x44008) -+#define DEIER _MMIO(0x4400c) -+ -+#define GTISR _MMIO(0x44010) -+#define GTIMR _MMIO(0x44014) -+#define GTIIR _MMIO(0x44018) -+#define GTIER _MMIO(0x4401c) -+ -+#define GEN8_MASTER_IRQ _MMIO(0x44200) -+#define GEN8_MASTER_IRQ_CONTROL (1 << 31) -+#define GEN8_PCU_IRQ (1 << 30) -+#define GEN8_DE_PCH_IRQ (1 << 23) -+#define GEN8_DE_MISC_IRQ (1 << 22) -+#define GEN8_DE_PORT_IRQ (1 << 20) -+#define GEN8_DE_PIPE_C_IRQ (1 << 18) -+#define GEN8_DE_PIPE_B_IRQ (1 << 17) -+#define GEN8_DE_PIPE_A_IRQ (1 << 16) -+#define GEN8_DE_PIPE_IRQ(pipe) (1 << (16 + (pipe))) -+#define GEN8_GT_VECS_IRQ (1 << 6) -+#define GEN8_GT_GUC_IRQ (1 << 5) -+#define GEN8_GT_PM_IRQ (1 << 4) -+#define GEN8_GT_VCS1_IRQ (1 << 3) /* NB: VCS2 in bspec! */ -+#define GEN8_GT_VCS0_IRQ (1 << 2) /* NB: VCS1 in bpsec! */ -+#define GEN8_GT_BCS_IRQ (1 << 1) -+#define GEN8_GT_RCS_IRQ (1 << 0) -+ -+#define GEN8_GT_ISR(which) _MMIO(0x44300 + (0x10 * (which))) -+#define GEN8_GT_IMR(which) _MMIO(0x44304 + (0x10 * (which))) -+#define GEN8_GT_IIR(which) _MMIO(0x44308 + (0x10 * (which))) -+#define GEN8_GT_IER(which) _MMIO(0x4430c + (0x10 * (which))) -+ -+#define GEN9_GUC_TO_HOST_INT_EVENT (1 << 31) -+#define GEN9_GUC_EXEC_ERROR_EVENT (1 << 30) -+#define GEN9_GUC_DISPLAY_EVENT (1 << 29) -+#define GEN9_GUC_SEMA_SIGNAL_EVENT (1 << 28) -+#define GEN9_GUC_IOMMU_MSG_EVENT (1 << 27) -+#define GEN9_GUC_DB_RING_EVENT (1 << 26) -+#define GEN9_GUC_DMA_DONE_EVENT (1 << 25) -+#define GEN9_GUC_FATAL_ERROR_EVENT (1 << 24) -+#define GEN9_GUC_NOTIFICATION_EVENT (1 << 23) -+ -+#define GEN8_RCS_IRQ_SHIFT 0 -+#define GEN8_BCS_IRQ_SHIFT 16 -+#define GEN8_VCS0_IRQ_SHIFT 0 /* NB: VCS1 in bspec! */ -+#define GEN8_VCS1_IRQ_SHIFT 16 /* NB: VCS2 in bpsec! */ -+#define GEN8_VECS_IRQ_SHIFT 0 -+#define GEN8_WD_IRQ_SHIFT 16 -+ -+#define GEN8_DE_PIPE_ISR(pipe) _MMIO(0x44400 + (0x10 * (pipe))) -+#define GEN8_DE_PIPE_IMR(pipe) _MMIO(0x44404 + (0x10 * (pipe))) -+#define GEN8_DE_PIPE_IIR(pipe) _MMIO(0x44408 + (0x10 * (pipe))) -+#define GEN8_DE_PIPE_IER(pipe) _MMIO(0x4440c + (0x10 * (pipe))) -+#define GEN8_PIPE_FIFO_UNDERRUN (1 << 31) -+#define GEN8_PIPE_CDCLK_CRC_ERROR (1 << 29) -+#define GEN8_PIPE_CDCLK_CRC_DONE (1 << 28) -+#define GEN8_PIPE_CURSOR_FAULT (1 << 10) -+#define GEN8_PIPE_SPRITE_FAULT (1 << 9) -+#define GEN8_PIPE_PRIMARY_FAULT (1 << 8) -+#define GEN8_PIPE_SPRITE_FLIP_DONE (1 << 5) -+#define GEN8_PIPE_PRIMARY_FLIP_DONE (1 << 4) -+#define GEN8_PIPE_SCAN_LINE_EVENT (1 << 2) -+#define GEN8_PIPE_VSYNC (1 << 1) -+#define GEN8_PIPE_VBLANK (1 << 0) -+#define GEN9_PIPE_CURSOR_FAULT (1 << 11) -+#define GEN9_PIPE_PLANE4_FAULT (1 << 10) -+#define GEN9_PIPE_PLANE3_FAULT (1 << 9) -+#define GEN9_PIPE_PLANE2_FAULT (1 << 8) -+#define GEN9_PIPE_PLANE1_FAULT (1 << 7) -+#define GEN9_PIPE_PLANE4_FLIP_DONE (1 << 6) -+#define GEN9_PIPE_PLANE3_FLIP_DONE (1 << 5) -+#define GEN9_PIPE_PLANE2_FLIP_DONE (1 << 4) -+#define GEN9_PIPE_PLANE1_FLIP_DONE (1 << 3) -+#define GEN9_PIPE_PLANE_FLIP_DONE(p) (1 << (3 + (p))) -+#define GEN8_DE_PIPE_IRQ_FAULT_ERRORS \ -+ (GEN8_PIPE_CURSOR_FAULT | \ -+ GEN8_PIPE_SPRITE_FAULT | \ -+ GEN8_PIPE_PRIMARY_FAULT) -+#define GEN9_DE_PIPE_IRQ_FAULT_ERRORS \ -+ (GEN9_PIPE_CURSOR_FAULT | \ -+ GEN9_PIPE_PLANE4_FAULT | \ -+ GEN9_PIPE_PLANE3_FAULT | \ -+ GEN9_PIPE_PLANE2_FAULT | \ -+ GEN9_PIPE_PLANE1_FAULT) -+ -+#define GEN8_DE_PORT_ISR _MMIO(0x44440) -+#define GEN8_DE_PORT_IMR _MMIO(0x44444) -+#define GEN8_DE_PORT_IIR _MMIO(0x44448) -+#define GEN8_DE_PORT_IER _MMIO(0x4444c) -+#define ICL_AUX_CHANNEL_E (1 << 29) -+#define CNL_AUX_CHANNEL_F (1 << 28) -+#define GEN9_AUX_CHANNEL_D (1 << 27) -+#define GEN9_AUX_CHANNEL_C (1 << 26) -+#define GEN9_AUX_CHANNEL_B (1 << 25) -+#define BXT_DE_PORT_HP_DDIC (1 << 5) -+#define BXT_DE_PORT_HP_DDIB (1 << 4) -+#define BXT_DE_PORT_HP_DDIA (1 << 3) -+#define BXT_DE_PORT_HOTPLUG_MASK (BXT_DE_PORT_HP_DDIA | \ -+ BXT_DE_PORT_HP_DDIB | \ -+ BXT_DE_PORT_HP_DDIC) -+#define GEN8_PORT_DP_A_HOTPLUG (1 << 3) -+#define BXT_DE_PORT_GMBUS (1 << 1) -+#define GEN8_AUX_CHANNEL_A (1 << 0) -+ -+#define GEN8_DE_MISC_ISR _MMIO(0x44460) -+#define GEN8_DE_MISC_IMR _MMIO(0x44464) -+#define GEN8_DE_MISC_IIR _MMIO(0x44468) -+#define GEN8_DE_MISC_IER _MMIO(0x4446c) -+#define GEN8_DE_MISC_GSE (1 << 27) -+#define GEN8_DE_EDP_PSR (1 << 19) -+ -+#define GEN8_PCU_ISR _MMIO(0x444e0) -+#define GEN8_PCU_IMR _MMIO(0x444e4) -+#define GEN8_PCU_IIR _MMIO(0x444e8) -+#define GEN8_PCU_IER _MMIO(0x444ec) -+ -+#define GEN11_GU_MISC_ISR _MMIO(0x444f0) -+#define GEN11_GU_MISC_IMR _MMIO(0x444f4) -+#define GEN11_GU_MISC_IIR _MMIO(0x444f8) -+#define GEN11_GU_MISC_IER _MMIO(0x444fc) -+#define GEN11_GU_MISC_GSE (1 << 27) -+ -+#define GEN11_GFX_MSTR_IRQ _MMIO(0x190010) -+#define GEN11_MASTER_IRQ (1 << 31) -+#define GEN11_PCU_IRQ (1 << 30) -+#define GEN11_GU_MISC_IRQ (1 << 29) -+#define GEN11_DISPLAY_IRQ (1 << 16) -+#define GEN11_GT_DW_IRQ(x) (1 << (x)) -+#define GEN11_GT_DW1_IRQ (1 << 1) -+#define GEN11_GT_DW0_IRQ (1 << 0) -+ -+#define GEN11_DISPLAY_INT_CTL _MMIO(0x44200) -+#define GEN11_DISPLAY_IRQ_ENABLE (1 << 31) -+#define GEN11_AUDIO_CODEC_IRQ (1 << 24) -+#define GEN11_DE_PCH_IRQ (1 << 23) -+#define GEN11_DE_MISC_IRQ (1 << 22) -+#define GEN11_DE_HPD_IRQ (1 << 21) -+#define GEN11_DE_PORT_IRQ (1 << 20) -+#define GEN11_DE_PIPE_C (1 << 18) -+#define GEN11_DE_PIPE_B (1 << 17) -+#define GEN11_DE_PIPE_A (1 << 16) -+ -+#define GEN11_DE_HPD_ISR _MMIO(0x44470) -+#define GEN11_DE_HPD_IMR _MMIO(0x44474) -+#define GEN11_DE_HPD_IIR _MMIO(0x44478) -+#define GEN11_DE_HPD_IER _MMIO(0x4447c) -+#define GEN11_TC4_HOTPLUG (1 << 19) -+#define GEN11_TC3_HOTPLUG (1 << 18) -+#define GEN11_TC2_HOTPLUG (1 << 17) -+#define GEN11_TC1_HOTPLUG (1 << 16) -+#define GEN11_TC_HOTPLUG(tc_port) (1 << ((tc_port) + 16)) -+#define GEN11_DE_TC_HOTPLUG_MASK (GEN11_TC4_HOTPLUG | \ -+ GEN11_TC3_HOTPLUG | \ -+ GEN11_TC2_HOTPLUG | \ -+ GEN11_TC1_HOTPLUG) -+#define GEN11_TBT4_HOTPLUG (1 << 3) -+#define GEN11_TBT3_HOTPLUG (1 << 2) -+#define GEN11_TBT2_HOTPLUG (1 << 1) -+#define GEN11_TBT1_HOTPLUG (1 << 0) -+#define GEN11_TBT_HOTPLUG(tc_port) (1 << (tc_port)) -+#define GEN11_DE_TBT_HOTPLUG_MASK (GEN11_TBT4_HOTPLUG | \ -+ GEN11_TBT3_HOTPLUG | \ -+ GEN11_TBT2_HOTPLUG | \ -+ GEN11_TBT1_HOTPLUG) -+ -+#define GEN11_TBT_HOTPLUG_CTL _MMIO(0x44030) -+#define GEN11_TC_HOTPLUG_CTL _MMIO(0x44038) -+#define GEN11_HOTPLUG_CTL_ENABLE(tc_port) (8 << (tc_port) * 4) -+#define GEN11_HOTPLUG_CTL_LONG_DETECT(tc_port) (2 << (tc_port) * 4) -+#define GEN11_HOTPLUG_CTL_SHORT_DETECT(tc_port) (1 << (tc_port) * 4) -+#define GEN11_HOTPLUG_CTL_NO_DETECT(tc_port) (0 << (tc_port) * 4) -+ -+#define GEN11_GT_INTR_DW0 _MMIO(0x190018) -+#define GEN11_CSME (31) -+#define GEN11_GUNIT (28) -+#define GEN11_GUC (25) -+#define GEN11_WDPERF (20) -+#define GEN11_KCR (19) -+#define GEN11_GTPM (16) -+#define GEN11_BCS (15) -+#define GEN11_RCS0 (0) -+ -+#define GEN11_GT_INTR_DW1 _MMIO(0x19001c) -+#define GEN11_VECS(x) (31 - (x)) -+#define GEN11_VCS(x) (x) -+ -+#define GEN11_GT_INTR_DW(x) _MMIO(0x190018 + ((x) * 4)) -+ -+#define GEN11_INTR_IDENTITY_REG0 _MMIO(0x190060) -+#define GEN11_INTR_IDENTITY_REG1 _MMIO(0x190064) -+#define GEN11_INTR_DATA_VALID (1 << 31) -+#define GEN11_INTR_ENGINE_CLASS(x) (((x) & GENMASK(18, 16)) >> 16) -+#define GEN11_INTR_ENGINE_INSTANCE(x) (((x) & GENMASK(25, 20)) >> 20) -+#define GEN11_INTR_ENGINE_INTR(x) ((x) & 0xffff) -+ -+#define GEN11_INTR_IDENTITY_REG(x) _MMIO(0x190060 + ((x) * 4)) -+ -+#define GEN11_IIR_REG0_SELECTOR _MMIO(0x190070) -+#define GEN11_IIR_REG1_SELECTOR _MMIO(0x190074) -+ -+#define GEN11_IIR_REG_SELECTOR(x) _MMIO(0x190070 + ((x) * 4)) -+ -+#define GEN11_RENDER_COPY_INTR_ENABLE _MMIO(0x190030) -+#define GEN11_VCS_VECS_INTR_ENABLE _MMIO(0x190034) -+#define GEN11_GUC_SG_INTR_ENABLE _MMIO(0x190038) -+#define GEN11_GPM_WGBOXPERF_INTR_ENABLE _MMIO(0x19003c) -+#define GEN11_CRYPTO_RSVD_INTR_ENABLE _MMIO(0x190040) -+#define GEN11_GUNIT_CSME_INTR_ENABLE _MMIO(0x190044) -+ -+#define GEN11_RCS0_RSVD_INTR_MASK _MMIO(0x190090) -+#define GEN11_BCS_RSVD_INTR_MASK _MMIO(0x1900a0) -+#define GEN11_VCS0_VCS1_INTR_MASK _MMIO(0x1900a8) -+#define GEN11_VCS2_VCS3_INTR_MASK _MMIO(0x1900ac) -+#define GEN11_VECS0_VECS1_INTR_MASK _MMIO(0x1900d0) -+#define GEN11_GUC_SG_INTR_MASK _MMIO(0x1900e8) -+#define GEN11_GPM_WGBOXPERF_INTR_MASK _MMIO(0x1900ec) -+#define GEN11_CRYPTO_RSVD_INTR_MASK _MMIO(0x1900f0) -+#define GEN11_GUNIT_CSME_INTR_MASK _MMIO(0x1900f4) -+ -+#define ILK_DISPLAY_CHICKEN2 _MMIO(0x42004) -+/* Required on all Ironlake and Sandybridge according to the B-Spec. */ -+#define ILK_ELPIN_409_SELECT (1 << 25) -+#define ILK_DPARB_GATE (1 << 22) -+#define ILK_VSDPFD_FULL (1 << 21) -+#define FUSE_STRAP _MMIO(0x42014) -+#define ILK_INTERNAL_GRAPHICS_DISABLE (1 << 31) -+#define ILK_INTERNAL_DISPLAY_DISABLE (1 << 30) -+#define ILK_DISPLAY_DEBUG_DISABLE (1 << 29) -+#define IVB_PIPE_C_DISABLE (1 << 28) -+#define ILK_HDCP_DISABLE (1 << 25) -+#define ILK_eDP_A_DISABLE (1 << 24) -+#define HSW_CDCLK_LIMIT (1 << 24) -+#define ILK_DESKTOP (1 << 23) -+ -+#define ILK_DSPCLK_GATE_D _MMIO(0x42020) -+#define ILK_VRHUNIT_CLOCK_GATE_DISABLE (1 << 28) -+#define ILK_DPFCUNIT_CLOCK_GATE_DISABLE (1 << 9) -+#define ILK_DPFCRUNIT_CLOCK_GATE_DISABLE (1 << 8) -+#define ILK_DPFDUNIT_CLOCK_GATE_ENABLE (1 << 7) -+#define ILK_DPARBUNIT_CLOCK_GATE_ENABLE (1 << 5) -+ -+#define IVB_CHICKEN3 _MMIO(0x4200c) -+# define CHICKEN3_DGMG_REQ_OUT_FIX_DISABLE (1 << 5) -+# define CHICKEN3_DGMG_DONE_FIX_DISABLE (1 << 2) -+ -+#define CHICKEN_PAR1_1 _MMIO(0x42080) -+#define SKL_DE_COMPRESSED_HASH_MODE (1 << 15) -+#define DPA_MASK_VBLANK_SRD (1 << 15) -+#define FORCE_ARB_IDLE_PLANES (1 << 14) -+#define SKL_EDP_PSR_FIX_RDWRAP (1 << 3) -+ -+#define CHICKEN_PAR2_1 _MMIO(0x42090) -+#define KVM_CONFIG_CHANGE_NOTIFICATION_SELECT (1 << 14) -+ -+#define CHICKEN_MISC_2 _MMIO(0x42084) -+#define CNL_COMP_PWR_DOWN (1 << 23) -+#define GLK_CL2_PWR_DOWN (1 << 12) -+#define GLK_CL1_PWR_DOWN (1 << 11) -+#define GLK_CL0_PWR_DOWN (1 << 10) -+ -+#define CHICKEN_MISC_4 _MMIO(0x4208c) -+#define FBC_STRIDE_OVERRIDE (1 << 13) -+#define FBC_STRIDE_MASK 0x1FFF -+ -+#define _CHICKEN_PIPESL_1_A 0x420b0 -+#define _CHICKEN_PIPESL_1_B 0x420b4 -+#define HSW_FBCQ_DIS (1 << 22) -+#define BDW_DPRS_MASK_VBLANK_SRD (1 << 0) -+#define CHICKEN_PIPESL_1(pipe) _MMIO_PIPE(pipe, _CHICKEN_PIPESL_1_A, _CHICKEN_PIPESL_1_B) -+ -+#define CHICKEN_TRANS_A _MMIO(0x420c0) -+#define CHICKEN_TRANS_B _MMIO(0x420c4) -+#define CHICKEN_TRANS_C _MMIO(0x420c8) -+#define CHICKEN_TRANS_EDP _MMIO(0x420cc) -+#define VSC_DATA_SEL_SOFTWARE_CONTROL (1 << 25) /* GLK and CNL+ */ -+#define DDI_TRAINING_OVERRIDE_ENABLE (1 << 19) -+#define DDI_TRAINING_OVERRIDE_VALUE (1 << 18) -+#define DDIE_TRAINING_OVERRIDE_ENABLE (1 << 17) /* CHICKEN_TRANS_A only */ -+#define DDIE_TRAINING_OVERRIDE_VALUE (1 << 16) /* CHICKEN_TRANS_A only */ -+#define PSR2_ADD_VERTICAL_LINE_COUNT (1 << 15) -+#define PSR2_VSC_ENABLE_PROG_HEADER (1 << 12) -+ -+#define DISP_ARB_CTL _MMIO(0x45000) -+#define DISP_FBC_MEMORY_WAKE (1 << 31) -+#define DISP_TILE_SURFACE_SWIZZLING (1 << 13) -+#define DISP_FBC_WM_DIS (1 << 15) -+#define DISP_ARB_CTL2 _MMIO(0x45004) -+#define DISP_DATA_PARTITION_5_6 (1 << 6) -+#define DISP_IPC_ENABLE (1 << 3) -+#define DBUF_CTL _MMIO(0x45008) -+#define DBUF_CTL_S1 _MMIO(0x45008) -+#define DBUF_CTL_S2 _MMIO(0x44FE8) -+#define DBUF_POWER_REQUEST (1 << 31) -+#define DBUF_POWER_STATE (1 << 30) -+#define GEN7_MSG_CTL _MMIO(0x45010) -+#define WAIT_FOR_PCH_RESET_ACK (1 << 1) -+#define WAIT_FOR_PCH_FLR_ACK (1 << 0) -+#define HSW_NDE_RSTWRN_OPT _MMIO(0x46408) -+#define RESET_PCH_HANDSHAKE_ENABLE (1 << 4) -+ -+#define GEN8_CHICKEN_DCPR_1 _MMIO(0x46430) -+#define SKL_SELECT_ALTERNATE_DC_EXIT (1 << 30) -+#define MASK_WAKEMEM (1 << 13) -+#define CNL_DDI_CLOCK_REG_ACCESS_ON (1 << 7) -+ -+#define SKL_DFSM _MMIO(0x51000) -+#define SKL_DFSM_CDCLK_LIMIT_MASK (3 << 23) -+#define SKL_DFSM_CDCLK_LIMIT_675 (0 << 23) -+#define SKL_DFSM_CDCLK_LIMIT_540 (1 << 23) -+#define SKL_DFSM_CDCLK_LIMIT_450 (2 << 23) -+#define SKL_DFSM_CDCLK_LIMIT_337_5 (3 << 23) -+#define SKL_DFSM_PIPE_A_DISABLE (1 << 30) -+#define SKL_DFSM_PIPE_B_DISABLE (1 << 21) -+#define SKL_DFSM_PIPE_C_DISABLE (1 << 28) -+ -+#define SKL_DSSM _MMIO(0x51004) -+#define CNL_DSSM_CDCLK_PLL_REFCLK_24MHz (1 << 31) -+#define ICL_DSSM_CDCLK_PLL_REFCLK_MASK (7 << 29) -+#define ICL_DSSM_CDCLK_PLL_REFCLK_24MHz (0 << 29) -+#define ICL_DSSM_CDCLK_PLL_REFCLK_19_2MHz (1 << 29) -+#define ICL_DSSM_CDCLK_PLL_REFCLK_38_4MHz (2 << 29) -+ -+#define GEN7_FF_SLICE_CS_CHICKEN1 _MMIO(0x20e0) -+#define GEN9_FFSC_PERCTX_PREEMPT_CTRL (1 << 14) -+ -+#define FF_SLICE_CS_CHICKEN2 _MMIO(0x20e4) -+#define GEN9_TSG_BARRIER_ACK_DISABLE (1 << 8) -+#define GEN9_POOLED_EU_LOAD_BALANCING_FIX_DISABLE (1 << 10) -+ -+#define GEN9_CS_DEBUG_MODE1 _MMIO(0x20ec) -+#define GEN9_CTX_PREEMPT_REG _MMIO(0x2248) -+#define GEN8_CS_CHICKEN1 _MMIO(0x2580) -+#define GEN9_PREEMPT_3D_OBJECT_LEVEL (1 << 0) -+#define GEN9_PREEMPT_GPGPU_LEVEL(hi, lo) (((hi) << 2) | ((lo) << 1)) -+#define GEN9_PREEMPT_GPGPU_MID_THREAD_LEVEL GEN9_PREEMPT_GPGPU_LEVEL(0, 0) -+#define GEN9_PREEMPT_GPGPU_THREAD_GROUP_LEVEL GEN9_PREEMPT_GPGPU_LEVEL(0, 1) -+#define GEN9_PREEMPT_GPGPU_COMMAND_LEVEL GEN9_PREEMPT_GPGPU_LEVEL(1, 0) -+#define GEN9_PREEMPT_GPGPU_LEVEL_MASK GEN9_PREEMPT_GPGPU_LEVEL(1, 1) -+ -+/* GEN7 chicken */ -+#define GEN7_COMMON_SLICE_CHICKEN1 _MMIO(0x7010) -+ #define GEN7_CSC1_RHWO_OPT_DISABLE_IN_RCC ((1 << 10) | (1 << 26)) -+ #define GEN9_RHWO_OPTIMIZATION_DISABLE (1 << 14) -+ -+#define COMMON_SLICE_CHICKEN2 _MMIO(0x7014) -+ #define GEN9_PBE_COMPRESSED_HASH_SELECTION (1 << 13) -+ #define GEN9_DISABLE_GATHER_AT_SET_SHADER_COMMON_SLICE (1 << 12) -+ #define GEN8_SBE_DISABLE_REPLAY_BUF_OPTIMIZATION (1 << 8) -+ #define GEN8_CSC2_SBE_VUE_CACHE_CONSERVATIVE (1 << 0) -+ -+#define GEN8_L3CNTLREG _MMIO(0x7034) -+ #define GEN8_ERRDETBCTRL (1 << 9) -+ -+#define GEN11_COMMON_SLICE_CHICKEN3 _MMIO(0x7304) -+ #define GEN11_BLEND_EMB_FIX_DISABLE_IN_RCC (1 << 11) -+ -+#define HIZ_CHICKEN _MMIO(0x7018) -+# define CHV_HZ_8X8_MODE_IN_1X (1 << 15) -+# define BDW_HIZ_POWER_COMPILER_CLOCK_GATING_DISABLE (1 << 3) -+ -+#define GEN9_SLICE_COMMON_ECO_CHICKEN0 _MMIO(0x7308) -+#define DISABLE_PIXEL_MASK_CAMMING (1 << 14) -+ -+#define GEN9_SLICE_COMMON_ECO_CHICKEN1 _MMIO(0x731c) -+#define GEN11_STATE_CACHE_REDIRECT_TO_CS (1 << 11) -+ -+#define GEN7_SARCHKMD _MMIO(0xB000) -+#define GEN7_DISABLE_DEMAND_PREFETCH (1 << 31) -+#define GEN7_DISABLE_SAMPLER_PREFETCH (1 << 30) -+ -+#define GEN7_L3SQCREG1 _MMIO(0xB010) -+#define VLV_B0_WA_L3SQCREG1_VALUE 0x00D30000 -+ -+#define GEN8_L3SQCREG1 _MMIO(0xB100) -+/* -+ * Note that on CHV the following has an off-by-one error wrt. to BSpec. -+ * Using the formula in BSpec leads to a hang, while the formula here works -+ * fine and matches the formulas for all other platforms. A BSpec change -+ * request has been filed to clarify this. -+ */ -+#define L3_GENERAL_PRIO_CREDITS(x) (((x) >> 1) << 19) -+#define L3_HIGH_PRIO_CREDITS(x) (((x) >> 1) << 14) -+#define L3_PRIO_CREDITS_MASK ((0x1f << 19) | (0x1f << 14)) -+ -+#define GEN7_L3CNTLREG1 _MMIO(0xB01C) -+#define GEN7_WA_FOR_GEN7_L3_CONTROL 0x3C47FF8C -+#define GEN7_L3AGDIS (1 << 19) -+#define GEN7_L3CNTLREG2 _MMIO(0xB020) -+#define GEN7_L3CNTLREG3 _MMIO(0xB024) -+ -+#define GEN7_L3_CHICKEN_MODE_REGISTER _MMIO(0xB030) -+#define GEN7_WA_L3_CHICKEN_MODE 0x20000000 -+#define GEN10_L3_CHICKEN_MODE_REGISTER _MMIO(0xB114) -+#define GEN11_I2M_WRITE_DISABLE (1 << 28) -+ -+#define GEN7_L3SQCREG4 _MMIO(0xb034) -+#define L3SQ_URB_READ_CAM_MATCH_DISABLE (1 << 27) -+ -+#define GEN8_L3SQCREG4 _MMIO(0xb118) -+#define GEN11_LQSC_CLEAN_EVICT_DISABLE (1 << 6) -+#define GEN8_LQSC_RO_PERF_DIS (1 << 27) -+#define GEN8_LQSC_FLUSH_COHERENT_LINES (1 << 21) -+ -+/* GEN8 chicken */ -+#define HDC_CHICKEN0 _MMIO(0x7300) -+#define CNL_HDC_CHICKEN0 _MMIO(0xE5F0) -+#define ICL_HDC_MODE _MMIO(0xE5F4) -+#define HDC_FORCE_CSR_NON_COHERENT_OVR_DISABLE (1 << 15) -+#define HDC_FENCE_DEST_SLM_DISABLE (1 << 14) -+#define HDC_DONOT_FETCH_MEM_WHEN_MASKED (1 << 11) -+#define HDC_FORCE_CONTEXT_SAVE_RESTORE_NON_COHERENT (1 << 5) -+#define HDC_FORCE_NON_COHERENT (1 << 4) -+#define HDC_BARRIER_PERFORMANCE_DISABLE (1 << 10) -+ -+#define GEN8_HDC_CHICKEN1 _MMIO(0x7304) -+ -+/* GEN9 chicken */ -+#define SLICE_ECO_CHICKEN0 _MMIO(0x7308) -+#define PIXEL_MASK_CAMMING_DISABLE (1 << 14) -+ -+#define GEN9_WM_CHICKEN3 _MMIO(0x5588) -+#define GEN9_FACTOR_IN_CLR_VAL_HIZ (1 << 9) -+ -+/* WaCatErrorRejectionIssue */ -+#define GEN7_SQ_CHICKEN_MBCUNIT_CONFIG _MMIO(0x9030) -+#define GEN7_SQ_CHICKEN_MBCUNIT_SQINTMOB (1 << 11) -+ -+#define HSW_SCRATCH1 _MMIO(0xb038) -+#define HSW_SCRATCH1_L3_DATA_ATOMICS_DISABLE (1 << 27) -+ -+#define BDW_SCRATCH1 _MMIO(0xb11c) -+#define GEN9_LBS_SLA_RETRY_TIMER_DECREMENT_ENABLE (1 << 2) -+ -+/*GEN11 chicken */ -+#define _PIPEA_CHICKEN 0x70038 -+#define _PIPEB_CHICKEN 0x71038 -+#define _PIPEC_CHICKEN 0x72038 -+#define PIPE_CHICKEN(pipe) _MMIO_PIPE(pipe, _PIPEA_CHICKEN,\ -+ _PIPEB_CHICKEN) -+#define PIXEL_ROUNDING_TRUNC_FB_PASSTHRU (1 << 15) -+#define PER_PIXEL_ALPHA_BYPASS_EN (1 << 7) -+ -+/* PCH */ -+ -+#define PCH_DISPLAY_BASE 0xc0000u -+ -+/* south display engine interrupt: IBX */ -+#define SDE_AUDIO_POWER_D (1 << 27) -+#define SDE_AUDIO_POWER_C (1 << 26) -+#define SDE_AUDIO_POWER_B (1 << 25) -+#define SDE_AUDIO_POWER_SHIFT (25) -+#define SDE_AUDIO_POWER_MASK (7 << SDE_AUDIO_POWER_SHIFT) -+#define SDE_GMBUS (1 << 24) -+#define SDE_AUDIO_HDCP_TRANSB (1 << 23) -+#define SDE_AUDIO_HDCP_TRANSA (1 << 22) -+#define SDE_AUDIO_HDCP_MASK (3 << 22) -+#define SDE_AUDIO_TRANSB (1 << 21) -+#define SDE_AUDIO_TRANSA (1 << 20) -+#define SDE_AUDIO_TRANS_MASK (3 << 20) -+#define SDE_POISON (1 << 19) -+/* 18 reserved */ -+#define SDE_FDI_RXB (1 << 17) -+#define SDE_FDI_RXA (1 << 16) -+#define SDE_FDI_MASK (3 << 16) -+#define SDE_AUXD (1 << 15) -+#define SDE_AUXC (1 << 14) -+#define SDE_AUXB (1 << 13) -+#define SDE_AUX_MASK (7 << 13) -+/* 12 reserved */ -+#define SDE_CRT_HOTPLUG (1 << 11) -+#define SDE_PORTD_HOTPLUG (1 << 10) -+#define SDE_PORTC_HOTPLUG (1 << 9) -+#define SDE_PORTB_HOTPLUG (1 << 8) -+#define SDE_SDVOB_HOTPLUG (1 << 6) -+#define SDE_HOTPLUG_MASK (SDE_CRT_HOTPLUG | \ -+ SDE_SDVOB_HOTPLUG | \ -+ SDE_PORTB_HOTPLUG | \ -+ SDE_PORTC_HOTPLUG | \ -+ SDE_PORTD_HOTPLUG) -+#define SDE_TRANSB_CRC_DONE (1 << 5) -+#define SDE_TRANSB_CRC_ERR (1 << 4) -+#define SDE_TRANSB_FIFO_UNDER (1 << 3) -+#define SDE_TRANSA_CRC_DONE (1 << 2) -+#define SDE_TRANSA_CRC_ERR (1 << 1) -+#define SDE_TRANSA_FIFO_UNDER (1 << 0) -+#define SDE_TRANS_MASK (0x3f) -+ -+/* south display engine interrupt: CPT - CNP */ -+#define SDE_AUDIO_POWER_D_CPT (1 << 31) -+#define SDE_AUDIO_POWER_C_CPT (1 << 30) -+#define SDE_AUDIO_POWER_B_CPT (1 << 29) -+#define SDE_AUDIO_POWER_SHIFT_CPT 29 -+#define SDE_AUDIO_POWER_MASK_CPT (7 << 29) -+#define SDE_AUXD_CPT (1 << 27) -+#define SDE_AUXC_CPT (1 << 26) -+#define SDE_AUXB_CPT (1 << 25) -+#define SDE_AUX_MASK_CPT (7 << 25) -+#define SDE_PORTE_HOTPLUG_SPT (1 << 25) -+#define SDE_PORTA_HOTPLUG_SPT (1 << 24) -+#define SDE_PORTD_HOTPLUG_CPT (1 << 23) -+#define SDE_PORTC_HOTPLUG_CPT (1 << 22) -+#define SDE_PORTB_HOTPLUG_CPT (1 << 21) -+#define SDE_CRT_HOTPLUG_CPT (1 << 19) -+#define SDE_SDVOB_HOTPLUG_CPT (1 << 18) -+#define SDE_HOTPLUG_MASK_CPT (SDE_CRT_HOTPLUG_CPT | \ -+ SDE_SDVOB_HOTPLUG_CPT | \ -+ SDE_PORTD_HOTPLUG_CPT | \ -+ SDE_PORTC_HOTPLUG_CPT | \ -+ SDE_PORTB_HOTPLUG_CPT) -+#define SDE_HOTPLUG_MASK_SPT (SDE_PORTE_HOTPLUG_SPT | \ -+ SDE_PORTD_HOTPLUG_CPT | \ -+ SDE_PORTC_HOTPLUG_CPT | \ -+ SDE_PORTB_HOTPLUG_CPT | \ -+ SDE_PORTA_HOTPLUG_SPT) -+#define SDE_GMBUS_CPT (1 << 17) -+#define SDE_ERROR_CPT (1 << 16) -+#define SDE_AUDIO_CP_REQ_C_CPT (1 << 10) -+#define SDE_AUDIO_CP_CHG_C_CPT (1 << 9) -+#define SDE_FDI_RXC_CPT (1 << 8) -+#define SDE_AUDIO_CP_REQ_B_CPT (1 << 6) -+#define SDE_AUDIO_CP_CHG_B_CPT (1 << 5) -+#define SDE_FDI_RXB_CPT (1 << 4) -+#define SDE_AUDIO_CP_REQ_A_CPT (1 << 2) -+#define SDE_AUDIO_CP_CHG_A_CPT (1 << 1) -+#define SDE_FDI_RXA_CPT (1 << 0) -+#define SDE_AUDIO_CP_REQ_CPT (SDE_AUDIO_CP_REQ_C_CPT | \ -+ SDE_AUDIO_CP_REQ_B_CPT | \ -+ SDE_AUDIO_CP_REQ_A_CPT) -+#define SDE_AUDIO_CP_CHG_CPT (SDE_AUDIO_CP_CHG_C_CPT | \ -+ SDE_AUDIO_CP_CHG_B_CPT | \ -+ SDE_AUDIO_CP_CHG_A_CPT) -+#define SDE_FDI_MASK_CPT (SDE_FDI_RXC_CPT | \ -+ SDE_FDI_RXB_CPT | \ -+ SDE_FDI_RXA_CPT) -+ -+/* south display engine interrupt: ICP */ -+#define SDE_TC4_HOTPLUG_ICP (1 << 27) -+#define SDE_TC3_HOTPLUG_ICP (1 << 26) -+#define SDE_TC2_HOTPLUG_ICP (1 << 25) -+#define SDE_TC1_HOTPLUG_ICP (1 << 24) -+#define SDE_GMBUS_ICP (1 << 23) -+#define SDE_DDIB_HOTPLUG_ICP (1 << 17) -+#define SDE_DDIA_HOTPLUG_ICP (1 << 16) -+#define SDE_TC_HOTPLUG_ICP(tc_port) (1 << ((tc_port) + 24)) -+#define SDE_DDI_HOTPLUG_ICP(port) (1 << ((port) + 16)) -+#define SDE_DDI_MASK_ICP (SDE_DDIB_HOTPLUG_ICP | \ -+ SDE_DDIA_HOTPLUG_ICP) -+#define SDE_TC_MASK_ICP (SDE_TC4_HOTPLUG_ICP | \ -+ SDE_TC3_HOTPLUG_ICP | \ -+ SDE_TC2_HOTPLUG_ICP | \ -+ SDE_TC1_HOTPLUG_ICP) -+ -+#define SDEISR _MMIO(0xc4000) -+#define SDEIMR _MMIO(0xc4004) -+#define SDEIIR _MMIO(0xc4008) -+#define SDEIER _MMIO(0xc400c) -+ -+#define SERR_INT _MMIO(0xc4040) -+#define SERR_INT_POISON (1 << 31) -+#define SERR_INT_TRANS_FIFO_UNDERRUN(pipe) (1 << ((pipe) * 3)) -+ -+/* digital port hotplug */ -+#define PCH_PORT_HOTPLUG _MMIO(0xc4030) /* SHOTPLUG_CTL */ -+#define PORTA_HOTPLUG_ENABLE (1 << 28) /* LPT:LP+ & BXT */ -+#define BXT_DDIA_HPD_INVERT (1 << 27) -+#define PORTA_HOTPLUG_STATUS_MASK (3 << 24) /* SPT+ & BXT */ -+#define PORTA_HOTPLUG_NO_DETECT (0 << 24) /* SPT+ & BXT */ -+#define PORTA_HOTPLUG_SHORT_DETECT (1 << 24) /* SPT+ & BXT */ -+#define PORTA_HOTPLUG_LONG_DETECT (2 << 24) /* SPT+ & BXT */ -+#define PORTD_HOTPLUG_ENABLE (1 << 20) -+#define PORTD_PULSE_DURATION_2ms (0 << 18) /* pre-LPT */ -+#define PORTD_PULSE_DURATION_4_5ms (1 << 18) /* pre-LPT */ -+#define PORTD_PULSE_DURATION_6ms (2 << 18) /* pre-LPT */ -+#define PORTD_PULSE_DURATION_100ms (3 << 18) /* pre-LPT */ -+#define PORTD_PULSE_DURATION_MASK (3 << 18) /* pre-LPT */ -+#define PORTD_HOTPLUG_STATUS_MASK (3 << 16) -+#define PORTD_HOTPLUG_NO_DETECT (0 << 16) -+#define PORTD_HOTPLUG_SHORT_DETECT (1 << 16) -+#define PORTD_HOTPLUG_LONG_DETECT (2 << 16) -+#define PORTC_HOTPLUG_ENABLE (1 << 12) -+#define BXT_DDIC_HPD_INVERT (1 << 11) -+#define PORTC_PULSE_DURATION_2ms (0 << 10) /* pre-LPT */ -+#define PORTC_PULSE_DURATION_4_5ms (1 << 10) /* pre-LPT */ -+#define PORTC_PULSE_DURATION_6ms (2 << 10) /* pre-LPT */ -+#define PORTC_PULSE_DURATION_100ms (3 << 10) /* pre-LPT */ -+#define PORTC_PULSE_DURATION_MASK (3 << 10) /* pre-LPT */ -+#define PORTC_HOTPLUG_STATUS_MASK (3 << 8) -+#define PORTC_HOTPLUG_NO_DETECT (0 << 8) -+#define PORTC_HOTPLUG_SHORT_DETECT (1 << 8) -+#define PORTC_HOTPLUG_LONG_DETECT (2 << 8) -+#define PORTB_HOTPLUG_ENABLE (1 << 4) -+#define BXT_DDIB_HPD_INVERT (1 << 3) -+#define PORTB_PULSE_DURATION_2ms (0 << 2) /* pre-LPT */ -+#define PORTB_PULSE_DURATION_4_5ms (1 << 2) /* pre-LPT */ -+#define PORTB_PULSE_DURATION_6ms (2 << 2) /* pre-LPT */ -+#define PORTB_PULSE_DURATION_100ms (3 << 2) /* pre-LPT */ -+#define PORTB_PULSE_DURATION_MASK (3 << 2) /* pre-LPT */ -+#define PORTB_HOTPLUG_STATUS_MASK (3 << 0) -+#define PORTB_HOTPLUG_NO_DETECT (0 << 0) -+#define PORTB_HOTPLUG_SHORT_DETECT (1 << 0) -+#define PORTB_HOTPLUG_LONG_DETECT (2 << 0) -+#define BXT_DDI_HPD_INVERT_MASK (BXT_DDIA_HPD_INVERT | \ -+ BXT_DDIB_HPD_INVERT | \ -+ BXT_DDIC_HPD_INVERT) -+ -+#define PCH_PORT_HOTPLUG2 _MMIO(0xc403C) /* SHOTPLUG_CTL2 SPT+ */ -+#define PORTE_HOTPLUG_ENABLE (1 << 4) -+#define PORTE_HOTPLUG_STATUS_MASK (3 << 0) -+#define PORTE_HOTPLUG_NO_DETECT (0 << 0) -+#define PORTE_HOTPLUG_SHORT_DETECT (1 << 0) -+#define PORTE_HOTPLUG_LONG_DETECT (2 << 0) -+ -+/* This register is a reuse of PCH_PORT_HOTPLUG register. The -+ * functionality covered in PCH_PORT_HOTPLUG is split into -+ * SHOTPLUG_CTL_DDI and SHOTPLUG_CTL_TC. -+ */ -+ -+#define SHOTPLUG_CTL_DDI _MMIO(0xc4030) -+#define ICP_DDIB_HPD_ENABLE (1 << 7) -+#define ICP_DDIB_HPD_STATUS_MASK (3 << 4) -+#define ICP_DDIB_HPD_NO_DETECT (0 << 4) -+#define ICP_DDIB_HPD_SHORT_DETECT (1 << 4) -+#define ICP_DDIB_HPD_LONG_DETECT (2 << 4) -+#define ICP_DDIB_HPD_SHORT_LONG_DETECT (3 << 4) -+#define ICP_DDIA_HPD_ENABLE (1 << 3) -+#define ICP_DDIA_HPD_OP_DRIVE_1 (1 << 2) -+#define ICP_DDIA_HPD_STATUS_MASK (3 << 0) -+#define ICP_DDIA_HPD_NO_DETECT (0 << 0) -+#define ICP_DDIA_HPD_SHORT_DETECT (1 << 0) -+#define ICP_DDIA_HPD_LONG_DETECT (2 << 0) -+#define ICP_DDIA_HPD_SHORT_LONG_DETECT (3 << 0) -+ -+#define SHOTPLUG_CTL_TC _MMIO(0xc4034) -+#define ICP_TC_HPD_ENABLE(tc_port) (8 << (tc_port) * 4) -+/* Icelake DSC Rate Control Range Parameter Registers */ -+#define DSCA_RC_RANGE_PARAMETERS_0 _MMIO(0x6B240) -+#define DSCA_RC_RANGE_PARAMETERS_0_UDW _MMIO(0x6B240 + 4) -+#define DSCC_RC_RANGE_PARAMETERS_0 _MMIO(0x6BA40) -+#define DSCC_RC_RANGE_PARAMETERS_0_UDW _MMIO(0x6BA40 + 4) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_0_PB (0x78208) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_0_UDW_PB (0x78208 + 4) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_0_PB (0x78308) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_0_UDW_PB (0x78308 + 4) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_0_PC (0x78408) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_0_UDW_PC (0x78408 + 4) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_0_PC (0x78508) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_0_UDW_PC (0x78508 + 4) -+#define ICL_DSC0_RC_RANGE_PARAMETERS_0(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_0_PB, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_0_PC) -+#define ICL_DSC0_RC_RANGE_PARAMETERS_0_UDW(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_0_UDW_PB, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_0_UDW_PC) -+#define ICL_DSC1_RC_RANGE_PARAMETERS_0(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_0_PB, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_0_PC) -+#define ICL_DSC1_RC_RANGE_PARAMETERS_0_UDW(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_0_UDW_PB, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_0_UDW_PC) -+#define RC_BPG_OFFSET_SHIFT 10 -+#define RC_MAX_QP_SHIFT 5 -+#define RC_MIN_QP_SHIFT 0 -+ -+#define DSCA_RC_RANGE_PARAMETERS_1 _MMIO(0x6B248) -+#define DSCA_RC_RANGE_PARAMETERS_1_UDW _MMIO(0x6B248 + 4) -+#define DSCC_RC_RANGE_PARAMETERS_1 _MMIO(0x6BA48) -+#define DSCC_RC_RANGE_PARAMETERS_1_UDW _MMIO(0x6BA48 + 4) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_1_PB (0x78210) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_1_UDW_PB (0x78210 + 4) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_1_PB (0x78310) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_1_UDW_PB (0x78310 + 4) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_1_PC (0x78410) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_1_UDW_PC (0x78410 + 4) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_1_PC (0x78510) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_1_UDW_PC (0x78510 + 4) -+#define ICL_DSC0_RC_RANGE_PARAMETERS_1(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_1_PB, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_1_PC) -+#define ICL_DSC0_RC_RANGE_PARAMETERS_1_UDW(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_1_UDW_PB, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_1_UDW_PC) -+#define ICL_DSC1_RC_RANGE_PARAMETERS_1(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_1_PB, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_1_PC) -+#define ICL_DSC1_RC_RANGE_PARAMETERS_1_UDW(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_1_UDW_PB, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_1_UDW_PC) -+ -+#define DSCA_RC_RANGE_PARAMETERS_2 _MMIO(0x6B250) -+#define DSCA_RC_RANGE_PARAMETERS_2_UDW _MMIO(0x6B250 + 4) -+#define DSCC_RC_RANGE_PARAMETERS_2 _MMIO(0x6BA50) -+#define DSCC_RC_RANGE_PARAMETERS_2_UDW _MMIO(0x6BA50 + 4) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_2_PB (0x78218) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_2_UDW_PB (0x78218 + 4) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_2_PB (0x78318) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_2_UDW_PB (0x78318 + 4) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_2_PC (0x78418) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_2_UDW_PC (0x78418 + 4) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_2_PC (0x78518) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_2_UDW_PC (0x78518 + 4) -+#define ICL_DSC0_RC_RANGE_PARAMETERS_2(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_2_PB, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_2_PC) -+#define ICL_DSC0_RC_RANGE_PARAMETERS_2_UDW(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_2_UDW_PB, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_2_UDW_PC) -+#define ICL_DSC1_RC_RANGE_PARAMETERS_2(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_2_PB, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_2_PC) -+#define ICL_DSC1_RC_RANGE_PARAMETERS_2_UDW(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_2_UDW_PB, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_2_UDW_PC) -+ -+#define DSCA_RC_RANGE_PARAMETERS_3 _MMIO(0x6B258) -+#define DSCA_RC_RANGE_PARAMETERS_3_UDW _MMIO(0x6B258 + 4) -+#define DSCC_RC_RANGE_PARAMETERS_3 _MMIO(0x6BA58) -+#define DSCC_RC_RANGE_PARAMETERS_3_UDW _MMIO(0x6BA58 + 4) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_3_PB (0x78220) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_3_UDW_PB (0x78220 + 4) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_3_PB (0x78320) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_3_UDW_PB (0x78320 + 4) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_3_PC (0x78420) -+#define _ICL_DSC0_RC_RANGE_PARAMETERS_3_UDW_PC (0x78420 + 4) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_3_PC (0x78520) -+#define _ICL_DSC1_RC_RANGE_PARAMETERS_3_UDW_PC (0x78520 + 4) -+#define ICL_DSC0_RC_RANGE_PARAMETERS_3(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_3_PB, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_3_PC) -+#define ICL_DSC0_RC_RANGE_PARAMETERS_3_UDW(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_3_UDW_PB, \ -+ _ICL_DSC0_RC_RANGE_PARAMETERS_3_UDW_PC) -+#define ICL_DSC1_RC_RANGE_PARAMETERS_3(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_3_PB, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_3_PC) -+#define ICL_DSC1_RC_RANGE_PARAMETERS_3_UDW(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_3_UDW_PB, \ -+ _ICL_DSC1_RC_RANGE_PARAMETERS_3_UDW_PC) -+ -+#define ICP_TC_HPD_LONG_DETECT(tc_port) (2 << (tc_port) * 4) -+#define ICP_TC_HPD_SHORT_DETECT(tc_port) (1 << (tc_port) * 4) -+ -+#define _PCH_DPLL_A 0xc6014 -+#define _PCH_DPLL_B 0xc6018 -+#define PCH_DPLL(pll) _MMIO((pll) == 0 ? _PCH_DPLL_A : _PCH_DPLL_B) -+ -+#define _PCH_FPA0 0xc6040 -+#define FP_CB_TUNE (0x3 << 22) -+#define _PCH_FPA1 0xc6044 -+#define _PCH_FPB0 0xc6048 -+#define _PCH_FPB1 0xc604c -+#define PCH_FP0(pll) _MMIO((pll) == 0 ? _PCH_FPA0 : _PCH_FPB0) -+#define PCH_FP1(pll) _MMIO((pll) == 0 ? _PCH_FPA1 : _PCH_FPB1) -+ -+#define PCH_DPLL_TEST _MMIO(0xc606c) -+ -+#define PCH_DREF_CONTROL _MMIO(0xC6200) -+#define DREF_CONTROL_MASK 0x7fc3 -+#define DREF_CPU_SOURCE_OUTPUT_DISABLE (0 << 13) -+#define DREF_CPU_SOURCE_OUTPUT_DOWNSPREAD (2 << 13) -+#define DREF_CPU_SOURCE_OUTPUT_NONSPREAD (3 << 13) -+#define DREF_CPU_SOURCE_OUTPUT_MASK (3 << 13) -+#define DREF_SSC_SOURCE_DISABLE (0 << 11) -+#define DREF_SSC_SOURCE_ENABLE (2 << 11) -+#define DREF_SSC_SOURCE_MASK (3 << 11) -+#define DREF_NONSPREAD_SOURCE_DISABLE (0 << 9) -+#define DREF_NONSPREAD_CK505_ENABLE (1 << 9) -+#define DREF_NONSPREAD_SOURCE_ENABLE (2 << 9) -+#define DREF_NONSPREAD_SOURCE_MASK (3 << 9) -+#define DREF_SUPERSPREAD_SOURCE_DISABLE (0 << 7) -+#define DREF_SUPERSPREAD_SOURCE_ENABLE (2 << 7) -+#define DREF_SUPERSPREAD_SOURCE_MASK (3 << 7) -+#define DREF_SSC4_DOWNSPREAD (0 << 6) -+#define DREF_SSC4_CENTERSPREAD (1 << 6) -+#define DREF_SSC1_DISABLE (0 << 1) -+#define DREF_SSC1_ENABLE (1 << 1) -+#define DREF_SSC4_DISABLE (0) -+#define DREF_SSC4_ENABLE (1) -+ -+#define PCH_RAWCLK_FREQ _MMIO(0xc6204) -+#define FDL_TP1_TIMER_SHIFT 12 -+#define FDL_TP1_TIMER_MASK (3 << 12) -+#define FDL_TP2_TIMER_SHIFT 10 -+#define FDL_TP2_TIMER_MASK (3 << 10) -+#define RAWCLK_FREQ_MASK 0x3ff -+#define CNP_RAWCLK_DIV_MASK (0x3ff << 16) -+#define CNP_RAWCLK_DIV(div) ((div) << 16) -+#define CNP_RAWCLK_FRAC_MASK (0xf << 26) -+#define CNP_RAWCLK_DEN(den) ((den) << 26) -+#define ICP_RAWCLK_NUM(num) ((num) << 11) -+ -+#define PCH_DPLL_TMR_CFG _MMIO(0xc6208) -+ -+#define PCH_SSC4_PARMS _MMIO(0xc6210) -+#define PCH_SSC4_AUX_PARMS _MMIO(0xc6214) -+ -+#define PCH_DPLL_SEL _MMIO(0xc7000) -+#define TRANS_DPLLB_SEL(pipe) (1 << ((pipe) * 4)) -+#define TRANS_DPLLA_SEL(pipe) 0 -+#define TRANS_DPLL_ENABLE(pipe) (1 << ((pipe) * 4 + 3)) -+ -+/* transcoder */ -+ -+#define _PCH_TRANS_HTOTAL_A 0xe0000 -+#define TRANS_HTOTAL_SHIFT 16 -+#define TRANS_HACTIVE_SHIFT 0 -+#define _PCH_TRANS_HBLANK_A 0xe0004 -+#define TRANS_HBLANK_END_SHIFT 16 -+#define TRANS_HBLANK_START_SHIFT 0 -+#define _PCH_TRANS_HSYNC_A 0xe0008 -+#define TRANS_HSYNC_END_SHIFT 16 -+#define TRANS_HSYNC_START_SHIFT 0 -+#define _PCH_TRANS_VTOTAL_A 0xe000c -+#define TRANS_VTOTAL_SHIFT 16 -+#define TRANS_VACTIVE_SHIFT 0 -+#define _PCH_TRANS_VBLANK_A 0xe0010 -+#define TRANS_VBLANK_END_SHIFT 16 -+#define TRANS_VBLANK_START_SHIFT 0 -+#define _PCH_TRANS_VSYNC_A 0xe0014 -+#define TRANS_VSYNC_END_SHIFT 16 -+#define TRANS_VSYNC_START_SHIFT 0 -+#define _PCH_TRANS_VSYNCSHIFT_A 0xe0028 -+ -+#define _PCH_TRANSA_DATA_M1 0xe0030 -+#define _PCH_TRANSA_DATA_N1 0xe0034 -+#define _PCH_TRANSA_DATA_M2 0xe0038 -+#define _PCH_TRANSA_DATA_N2 0xe003c -+#define _PCH_TRANSA_LINK_M1 0xe0040 -+#define _PCH_TRANSA_LINK_N1 0xe0044 -+#define _PCH_TRANSA_LINK_M2 0xe0048 -+#define _PCH_TRANSA_LINK_N2 0xe004c -+ -+/* Per-transcoder DIP controls (PCH) */ -+#define _VIDEO_DIP_CTL_A 0xe0200 -+#define _VIDEO_DIP_DATA_A 0xe0208 -+#define _VIDEO_DIP_GCP_A 0xe0210 -+#define GCP_COLOR_INDICATION (1 << 2) -+#define GCP_DEFAULT_PHASE_ENABLE (1 << 1) -+#define GCP_AV_MUTE (1 << 0) -+ -+#define _VIDEO_DIP_CTL_B 0xe1200 -+#define _VIDEO_DIP_DATA_B 0xe1208 -+#define _VIDEO_DIP_GCP_B 0xe1210 -+ -+#define TVIDEO_DIP_CTL(pipe) _MMIO_PIPE(pipe, _VIDEO_DIP_CTL_A, _VIDEO_DIP_CTL_B) -+#define TVIDEO_DIP_DATA(pipe) _MMIO_PIPE(pipe, _VIDEO_DIP_DATA_A, _VIDEO_DIP_DATA_B) -+#define TVIDEO_DIP_GCP(pipe) _MMIO_PIPE(pipe, _VIDEO_DIP_GCP_A, _VIDEO_DIP_GCP_B) -+ -+/* Per-transcoder DIP controls (VLV) */ -+#define _VLV_VIDEO_DIP_CTL_A (VLV_DISPLAY_BASE + 0x60200) -+#define _VLV_VIDEO_DIP_DATA_A (VLV_DISPLAY_BASE + 0x60208) -+#define _VLV_VIDEO_DIP_GDCP_PAYLOAD_A (VLV_DISPLAY_BASE + 0x60210) -+ -+#define _VLV_VIDEO_DIP_CTL_B (VLV_DISPLAY_BASE + 0x61170) -+#define _VLV_VIDEO_DIP_DATA_B (VLV_DISPLAY_BASE + 0x61174) -+#define _VLV_VIDEO_DIP_GDCP_PAYLOAD_B (VLV_DISPLAY_BASE + 0x61178) -+ -+#define _CHV_VIDEO_DIP_CTL_C (VLV_DISPLAY_BASE + 0x611f0) -+#define _CHV_VIDEO_DIP_DATA_C (VLV_DISPLAY_BASE + 0x611f4) -+#define _CHV_VIDEO_DIP_GDCP_PAYLOAD_C (VLV_DISPLAY_BASE + 0x611f8) -+ -+#define VLV_TVIDEO_DIP_CTL(pipe) \ -+ _MMIO_PIPE3((pipe), _VLV_VIDEO_DIP_CTL_A, \ -+ _VLV_VIDEO_DIP_CTL_B, _CHV_VIDEO_DIP_CTL_C) -+#define VLV_TVIDEO_DIP_DATA(pipe) \ -+ _MMIO_PIPE3((pipe), _VLV_VIDEO_DIP_DATA_A, \ -+ _VLV_VIDEO_DIP_DATA_B, _CHV_VIDEO_DIP_DATA_C) -+#define VLV_TVIDEO_DIP_GCP(pipe) \ -+ _MMIO_PIPE3((pipe), _VLV_VIDEO_DIP_GDCP_PAYLOAD_A, \ -+ _VLV_VIDEO_DIP_GDCP_PAYLOAD_B, _CHV_VIDEO_DIP_GDCP_PAYLOAD_C) -+ -+/* Haswell DIP controls */ -+ -+#define _HSW_VIDEO_DIP_CTL_A 0x60200 -+#define _HSW_VIDEO_DIP_AVI_DATA_A 0x60220 -+#define _HSW_VIDEO_DIP_VS_DATA_A 0x60260 -+#define _HSW_VIDEO_DIP_SPD_DATA_A 0x602A0 -+#define _HSW_VIDEO_DIP_GMP_DATA_A 0x602E0 -+#define _HSW_VIDEO_DIP_VSC_DATA_A 0x60320 -+#define _HSW_VIDEO_DIP_AVI_ECC_A 0x60240 -+#define _HSW_VIDEO_DIP_VS_ECC_A 0x60280 -+#define _HSW_VIDEO_DIP_SPD_ECC_A 0x602C0 -+#define _HSW_VIDEO_DIP_GMP_ECC_A 0x60300 -+#define _HSW_VIDEO_DIP_VSC_ECC_A 0x60344 -+#define _HSW_VIDEO_DIP_GCP_A 0x60210 -+ -+#define _HSW_VIDEO_DIP_CTL_B 0x61200 -+#define _HSW_VIDEO_DIP_AVI_DATA_B 0x61220 -+#define _HSW_VIDEO_DIP_VS_DATA_B 0x61260 -+#define _HSW_VIDEO_DIP_SPD_DATA_B 0x612A0 -+#define _HSW_VIDEO_DIP_GMP_DATA_B 0x612E0 -+#define _HSW_VIDEO_DIP_VSC_DATA_B 0x61320 -+#define _HSW_VIDEO_DIP_BVI_ECC_B 0x61240 -+#define _HSW_VIDEO_DIP_VS_ECC_B 0x61280 -+#define _HSW_VIDEO_DIP_SPD_ECC_B 0x612C0 -+#define _HSW_VIDEO_DIP_GMP_ECC_B 0x61300 -+#define _HSW_VIDEO_DIP_VSC_ECC_B 0x61344 -+#define _HSW_VIDEO_DIP_GCP_B 0x61210 -+ -+/* Icelake PPS_DATA and _ECC DIP Registers. -+ * These are available for transcoders B,C and eDP. -+ * Adding the _A so as to reuse the _MMIO_TRANS2 -+ * definition, with which it offsets to the right location. -+ */ -+ -+#define _ICL_VIDEO_DIP_PPS_DATA_A 0x60350 -+#define _ICL_VIDEO_DIP_PPS_DATA_B 0x61350 -+#define _ICL_VIDEO_DIP_PPS_ECC_A 0x603D4 -+#define _ICL_VIDEO_DIP_PPS_ECC_B 0x613D4 -+ -+#define HSW_TVIDEO_DIP_CTL(trans) _MMIO_TRANS2(trans, _HSW_VIDEO_DIP_CTL_A) -+#define HSW_TVIDEO_DIP_GCP(trans) _MMIO_TRANS2(trans, _HSW_VIDEO_DIP_GCP_A) -+#define HSW_TVIDEO_DIP_AVI_DATA(trans, i) _MMIO_TRANS2(trans, _HSW_VIDEO_DIP_AVI_DATA_A + (i) * 4) -+#define HSW_TVIDEO_DIP_VS_DATA(trans, i) _MMIO_TRANS2(trans, _HSW_VIDEO_DIP_VS_DATA_A + (i) * 4) -+#define HSW_TVIDEO_DIP_SPD_DATA(trans, i) _MMIO_TRANS2(trans, _HSW_VIDEO_DIP_SPD_DATA_A + (i) * 4) -+#define HSW_TVIDEO_DIP_GMP_DATA(trans, i) _MMIO_TRANS2(trans, _HSW_VIDEO_DIP_GMP_DATA_A + (i) * 4) -+#define HSW_TVIDEO_DIP_VSC_DATA(trans, i) _MMIO_TRANS2(trans, _HSW_VIDEO_DIP_VSC_DATA_A + (i) * 4) -+#define ICL_VIDEO_DIP_PPS_DATA(trans, i) _MMIO_TRANS2(trans, _ICL_VIDEO_DIP_PPS_DATA_A + (i) * 4) -+#define ICL_VIDEO_DIP_PPS_ECC(trans, i) _MMIO_TRANS2(trans, _ICL_VIDEO_DIP_PPS_ECC_A + (i) * 4) -+ -+#define _HSW_STEREO_3D_CTL_A 0x70020 -+#define S3D_ENABLE (1 << 31) -+#define _HSW_STEREO_3D_CTL_B 0x71020 -+ -+#define HSW_STEREO_3D_CTL(trans) _MMIO_PIPE2(trans, _HSW_STEREO_3D_CTL_A) -+ -+#define _PCH_TRANS_HTOTAL_B 0xe1000 -+#define _PCH_TRANS_HBLANK_B 0xe1004 -+#define _PCH_TRANS_HSYNC_B 0xe1008 -+#define _PCH_TRANS_VTOTAL_B 0xe100c -+#define _PCH_TRANS_VBLANK_B 0xe1010 -+#define _PCH_TRANS_VSYNC_B 0xe1014 -+#define _PCH_TRANS_VSYNCSHIFT_B 0xe1028 -+ -+#define PCH_TRANS_HTOTAL(pipe) _MMIO_PIPE(pipe, _PCH_TRANS_HTOTAL_A, _PCH_TRANS_HTOTAL_B) -+#define PCH_TRANS_HBLANK(pipe) _MMIO_PIPE(pipe, _PCH_TRANS_HBLANK_A, _PCH_TRANS_HBLANK_B) -+#define PCH_TRANS_HSYNC(pipe) _MMIO_PIPE(pipe, _PCH_TRANS_HSYNC_A, _PCH_TRANS_HSYNC_B) -+#define PCH_TRANS_VTOTAL(pipe) _MMIO_PIPE(pipe, _PCH_TRANS_VTOTAL_A, _PCH_TRANS_VTOTAL_B) -+#define PCH_TRANS_VBLANK(pipe) _MMIO_PIPE(pipe, _PCH_TRANS_VBLANK_A, _PCH_TRANS_VBLANK_B) -+#define PCH_TRANS_VSYNC(pipe) _MMIO_PIPE(pipe, _PCH_TRANS_VSYNC_A, _PCH_TRANS_VSYNC_B) -+#define PCH_TRANS_VSYNCSHIFT(pipe) _MMIO_PIPE(pipe, _PCH_TRANS_VSYNCSHIFT_A, _PCH_TRANS_VSYNCSHIFT_B) -+ -+#define _PCH_TRANSB_DATA_M1 0xe1030 -+#define _PCH_TRANSB_DATA_N1 0xe1034 -+#define _PCH_TRANSB_DATA_M2 0xe1038 -+#define _PCH_TRANSB_DATA_N2 0xe103c -+#define _PCH_TRANSB_LINK_M1 0xe1040 -+#define _PCH_TRANSB_LINK_N1 0xe1044 -+#define _PCH_TRANSB_LINK_M2 0xe1048 -+#define _PCH_TRANSB_LINK_N2 0xe104c -+ -+#define PCH_TRANS_DATA_M1(pipe) _MMIO_PIPE(pipe, _PCH_TRANSA_DATA_M1, _PCH_TRANSB_DATA_M1) -+#define PCH_TRANS_DATA_N1(pipe) _MMIO_PIPE(pipe, _PCH_TRANSA_DATA_N1, _PCH_TRANSB_DATA_N1) -+#define PCH_TRANS_DATA_M2(pipe) _MMIO_PIPE(pipe, _PCH_TRANSA_DATA_M2, _PCH_TRANSB_DATA_M2) -+#define PCH_TRANS_DATA_N2(pipe) _MMIO_PIPE(pipe, _PCH_TRANSA_DATA_N2, _PCH_TRANSB_DATA_N2) -+#define PCH_TRANS_LINK_M1(pipe) _MMIO_PIPE(pipe, _PCH_TRANSA_LINK_M1, _PCH_TRANSB_LINK_M1) -+#define PCH_TRANS_LINK_N1(pipe) _MMIO_PIPE(pipe, _PCH_TRANSA_LINK_N1, _PCH_TRANSB_LINK_N1) -+#define PCH_TRANS_LINK_M2(pipe) _MMIO_PIPE(pipe, _PCH_TRANSA_LINK_M2, _PCH_TRANSB_LINK_M2) -+#define PCH_TRANS_LINK_N2(pipe) _MMIO_PIPE(pipe, _PCH_TRANSA_LINK_N2, _PCH_TRANSB_LINK_N2) -+ -+#define _PCH_TRANSACONF 0xf0008 -+#define _PCH_TRANSBCONF 0xf1008 -+#define PCH_TRANSCONF(pipe) _MMIO_PIPE(pipe, _PCH_TRANSACONF, _PCH_TRANSBCONF) -+#define LPT_TRANSCONF PCH_TRANSCONF(PIPE_A) /* lpt has only one transcoder */ -+#define TRANS_DISABLE (0 << 31) -+#define TRANS_ENABLE (1 << 31) -+#define TRANS_STATE_MASK (1 << 30) -+#define TRANS_STATE_DISABLE (0 << 30) -+#define TRANS_STATE_ENABLE (1 << 30) -+#define TRANS_FSYNC_DELAY_HB1 (0 << 27) -+#define TRANS_FSYNC_DELAY_HB2 (1 << 27) -+#define TRANS_FSYNC_DELAY_HB3 (2 << 27) -+#define TRANS_FSYNC_DELAY_HB4 (3 << 27) -+#define TRANS_INTERLACE_MASK (7 << 21) -+#define TRANS_PROGRESSIVE (0 << 21) -+#define TRANS_INTERLACED (3 << 21) -+#define TRANS_LEGACY_INTERLACED_ILK (2 << 21) -+#define TRANS_8BPC (0 << 5) -+#define TRANS_10BPC (1 << 5) -+#define TRANS_6BPC (2 << 5) -+#define TRANS_12BPC (3 << 5) -+ -+#define _TRANSA_CHICKEN1 0xf0060 -+#define _TRANSB_CHICKEN1 0xf1060 -+#define TRANS_CHICKEN1(pipe) _MMIO_PIPE(pipe, _TRANSA_CHICKEN1, _TRANSB_CHICKEN1) -+#define TRANS_CHICKEN1_HDMIUNIT_GC_DISABLE (1 << 10) -+#define TRANS_CHICKEN1_DP0UNIT_GC_DISABLE (1 << 4) -+#define _TRANSA_CHICKEN2 0xf0064 -+#define _TRANSB_CHICKEN2 0xf1064 -+#define TRANS_CHICKEN2(pipe) _MMIO_PIPE(pipe, _TRANSA_CHICKEN2, _TRANSB_CHICKEN2) -+#define TRANS_CHICKEN2_TIMING_OVERRIDE (1 << 31) -+#define TRANS_CHICKEN2_FDI_POLARITY_REVERSED (1 << 29) -+#define TRANS_CHICKEN2_FRAME_START_DELAY_MASK (3 << 27) -+#define TRANS_CHICKEN2_DISABLE_DEEP_COLOR_COUNTER (1 << 26) -+#define TRANS_CHICKEN2_DISABLE_DEEP_COLOR_MODESWITCH (1 << 25) -+ -+#define SOUTH_CHICKEN1 _MMIO(0xc2000) -+#define FDIA_PHASE_SYNC_SHIFT_OVR 19 -+#define FDIA_PHASE_SYNC_SHIFT_EN 18 -+#define FDI_PHASE_SYNC_OVR(pipe) (1 << (FDIA_PHASE_SYNC_SHIFT_OVR - ((pipe) * 2))) -+#define FDI_PHASE_SYNC_EN(pipe) (1 << (FDIA_PHASE_SYNC_SHIFT_EN - ((pipe) * 2))) -+#define FDI_BC_BIFURCATION_SELECT (1 << 12) -+#define CHASSIS_CLK_REQ_DURATION_MASK (0xf << 8) -+#define CHASSIS_CLK_REQ_DURATION(x) ((x) << 8) -+#define SPT_PWM_GRANULARITY (1 << 0) -+#define SOUTH_CHICKEN2 _MMIO(0xc2004) -+#define FDI_MPHY_IOSFSB_RESET_STATUS (1 << 13) -+#define FDI_MPHY_IOSFSB_RESET_CTL (1 << 12) -+#define LPT_PWM_GRANULARITY (1 << 5) -+#define DPLS_EDP_PPS_FIX_DIS (1 << 0) -+ -+#define _FDI_RXA_CHICKEN 0xc200c -+#define _FDI_RXB_CHICKEN 0xc2010 -+#define FDI_RX_PHASE_SYNC_POINTER_OVR (1 << 1) -+#define FDI_RX_PHASE_SYNC_POINTER_EN (1 << 0) -+#define FDI_RX_CHICKEN(pipe) _MMIO_PIPE(pipe, _FDI_RXA_CHICKEN, _FDI_RXB_CHICKEN) -+ -+#define SOUTH_DSPCLK_GATE_D _MMIO(0xc2020) -+#define PCH_GMBUSUNIT_CLOCK_GATE_DISABLE (1 << 31) -+#define PCH_DPLUNIT_CLOCK_GATE_DISABLE (1 << 30) -+#define PCH_DPLSUNIT_CLOCK_GATE_DISABLE (1 << 29) -+#define PCH_CPUNIT_CLOCK_GATE_DISABLE (1 << 14) -+#define CNP_PWM_CGE_GATING_DISABLE (1 << 13) -+#define PCH_LP_PARTITION_LEVEL_DISABLE (1 << 12) -+ -+/* CPU: FDI_TX */ -+#define _FDI_TXA_CTL 0x60100 -+#define _FDI_TXB_CTL 0x61100 -+#define FDI_TX_CTL(pipe) _MMIO_PIPE(pipe, _FDI_TXA_CTL, _FDI_TXB_CTL) -+#define FDI_TX_DISABLE (0 << 31) -+#define FDI_TX_ENABLE (1 << 31) -+#define FDI_LINK_TRAIN_PATTERN_1 (0 << 28) -+#define FDI_LINK_TRAIN_PATTERN_2 (1 << 28) -+#define FDI_LINK_TRAIN_PATTERN_IDLE (2 << 28) -+#define FDI_LINK_TRAIN_NONE (3 << 28) -+#define FDI_LINK_TRAIN_VOLTAGE_0_4V (0 << 25) -+#define FDI_LINK_TRAIN_VOLTAGE_0_6V (1 << 25) -+#define FDI_LINK_TRAIN_VOLTAGE_0_8V (2 << 25) -+#define FDI_LINK_TRAIN_VOLTAGE_1_2V (3 << 25) -+#define FDI_LINK_TRAIN_PRE_EMPHASIS_NONE (0 << 22) -+#define FDI_LINK_TRAIN_PRE_EMPHASIS_1_5X (1 << 22) -+#define FDI_LINK_TRAIN_PRE_EMPHASIS_2X (2 << 22) -+#define FDI_LINK_TRAIN_PRE_EMPHASIS_3X (3 << 22) -+/* ILK always use 400mV 0dB for voltage swing and pre-emphasis level. -+ SNB has different settings. */ -+/* SNB A-stepping */ -+#define FDI_LINK_TRAIN_400MV_0DB_SNB_A (0x38 << 22) -+#define FDI_LINK_TRAIN_400MV_6DB_SNB_A (0x02 << 22) -+#define FDI_LINK_TRAIN_600MV_3_5DB_SNB_A (0x01 << 22) -+#define FDI_LINK_TRAIN_800MV_0DB_SNB_A (0x0 << 22) -+/* SNB B-stepping */ -+#define FDI_LINK_TRAIN_400MV_0DB_SNB_B (0x0 << 22) -+#define FDI_LINK_TRAIN_400MV_6DB_SNB_B (0x3a << 22) -+#define FDI_LINK_TRAIN_600MV_3_5DB_SNB_B (0x39 << 22) -+#define FDI_LINK_TRAIN_800MV_0DB_SNB_B (0x38 << 22) -+#define FDI_LINK_TRAIN_VOL_EMP_MASK (0x3f << 22) -+#define FDI_DP_PORT_WIDTH_SHIFT 19 -+#define FDI_DP_PORT_WIDTH_MASK (7 << FDI_DP_PORT_WIDTH_SHIFT) -+#define FDI_DP_PORT_WIDTH(width) (((width) - 1) << FDI_DP_PORT_WIDTH_SHIFT) -+#define FDI_TX_ENHANCE_FRAME_ENABLE (1 << 18) -+/* Ironlake: hardwired to 1 */ -+#define FDI_TX_PLL_ENABLE (1 << 14) -+ -+/* Ivybridge has different bits for lolz */ -+#define FDI_LINK_TRAIN_PATTERN_1_IVB (0 << 8) -+#define FDI_LINK_TRAIN_PATTERN_2_IVB (1 << 8) -+#define FDI_LINK_TRAIN_PATTERN_IDLE_IVB (2 << 8) -+#define FDI_LINK_TRAIN_NONE_IVB (3 << 8) -+ -+/* both Tx and Rx */ -+#define FDI_COMPOSITE_SYNC (1 << 11) -+#define FDI_LINK_TRAIN_AUTO (1 << 10) -+#define FDI_SCRAMBLING_ENABLE (0 << 7) -+#define FDI_SCRAMBLING_DISABLE (1 << 7) -+ -+/* FDI_RX, FDI_X is hard-wired to Transcoder_X */ -+#define _FDI_RXA_CTL 0xf000c -+#define _FDI_RXB_CTL 0xf100c -+#define FDI_RX_CTL(pipe) _MMIO_PIPE(pipe, _FDI_RXA_CTL, _FDI_RXB_CTL) -+#define FDI_RX_ENABLE (1 << 31) -+/* train, dp width same as FDI_TX */ -+#define FDI_FS_ERRC_ENABLE (1 << 27) -+#define FDI_FE_ERRC_ENABLE (1 << 26) -+#define FDI_RX_POLARITY_REVERSED_LPT (1 << 16) -+#define FDI_8BPC (0 << 16) -+#define FDI_10BPC (1 << 16) -+#define FDI_6BPC (2 << 16) -+#define FDI_12BPC (3 << 16) -+#define FDI_RX_LINK_REVERSAL_OVERRIDE (1 << 15) -+#define FDI_DMI_LINK_REVERSE_MASK (1 << 14) -+#define FDI_RX_PLL_ENABLE (1 << 13) -+#define FDI_FS_ERR_CORRECT_ENABLE (1 << 11) -+#define FDI_FE_ERR_CORRECT_ENABLE (1 << 10) -+#define FDI_FS_ERR_REPORT_ENABLE (1 << 9) -+#define FDI_FE_ERR_REPORT_ENABLE (1 << 8) -+#define FDI_RX_ENHANCE_FRAME_ENABLE (1 << 6) -+#define FDI_PCDCLK (1 << 4) -+/* CPT */ -+#define FDI_AUTO_TRAINING (1 << 10) -+#define FDI_LINK_TRAIN_PATTERN_1_CPT (0 << 8) -+#define FDI_LINK_TRAIN_PATTERN_2_CPT (1 << 8) -+#define FDI_LINK_TRAIN_PATTERN_IDLE_CPT (2 << 8) -+#define FDI_LINK_TRAIN_NORMAL_CPT (3 << 8) -+#define FDI_LINK_TRAIN_PATTERN_MASK_CPT (3 << 8) -+ -+#define _FDI_RXA_MISC 0xf0010 -+#define _FDI_RXB_MISC 0xf1010 -+#define FDI_RX_PWRDN_LANE1_MASK (3 << 26) -+#define FDI_RX_PWRDN_LANE1_VAL(x) ((x) << 26) -+#define FDI_RX_PWRDN_LANE0_MASK (3 << 24) -+#define FDI_RX_PWRDN_LANE0_VAL(x) ((x) << 24) -+#define FDI_RX_TP1_TO_TP2_48 (2 << 20) -+#define FDI_RX_TP1_TO_TP2_64 (3 << 20) -+#define FDI_RX_FDI_DELAY_90 (0x90 << 0) -+#define FDI_RX_MISC(pipe) _MMIO_PIPE(pipe, _FDI_RXA_MISC, _FDI_RXB_MISC) -+ -+#define _FDI_RXA_TUSIZE1 0xf0030 -+#define _FDI_RXA_TUSIZE2 0xf0038 -+#define _FDI_RXB_TUSIZE1 0xf1030 -+#define _FDI_RXB_TUSIZE2 0xf1038 -+#define FDI_RX_TUSIZE1(pipe) _MMIO_PIPE(pipe, _FDI_RXA_TUSIZE1, _FDI_RXB_TUSIZE1) -+#define FDI_RX_TUSIZE2(pipe) _MMIO_PIPE(pipe, _FDI_RXA_TUSIZE2, _FDI_RXB_TUSIZE2) -+ -+/* FDI_RX interrupt register format */ -+#define FDI_RX_INTER_LANE_ALIGN (1 << 10) -+#define FDI_RX_SYMBOL_LOCK (1 << 9) /* train 2 */ -+#define FDI_RX_BIT_LOCK (1 << 8) /* train 1 */ -+#define FDI_RX_TRAIN_PATTERN_2_FAIL (1 << 7) -+#define FDI_RX_FS_CODE_ERR (1 << 6) -+#define FDI_RX_FE_CODE_ERR (1 << 5) -+#define FDI_RX_SYMBOL_ERR_RATE_ABOVE (1 << 4) -+#define FDI_RX_HDCP_LINK_FAIL (1 << 3) -+#define FDI_RX_PIXEL_FIFO_OVERFLOW (1 << 2) -+#define FDI_RX_CROSS_CLOCK_OVERFLOW (1 << 1) -+#define FDI_RX_SYMBOL_QUEUE_OVERFLOW (1 << 0) -+ -+#define _FDI_RXA_IIR 0xf0014 -+#define _FDI_RXA_IMR 0xf0018 -+#define _FDI_RXB_IIR 0xf1014 -+#define _FDI_RXB_IMR 0xf1018 -+#define FDI_RX_IIR(pipe) _MMIO_PIPE(pipe, _FDI_RXA_IIR, _FDI_RXB_IIR) -+#define FDI_RX_IMR(pipe) _MMIO_PIPE(pipe, _FDI_RXA_IMR, _FDI_RXB_IMR) -+ -+#define FDI_PLL_CTL_1 _MMIO(0xfe000) -+#define FDI_PLL_CTL_2 _MMIO(0xfe004) -+ -+#define PCH_LVDS _MMIO(0xe1180) -+#define LVDS_DETECTED (1 << 1) -+ -+#define _PCH_DP_B 0xe4100 -+#define PCH_DP_B _MMIO(_PCH_DP_B) -+#define _PCH_DPB_AUX_CH_CTL 0xe4110 -+#define _PCH_DPB_AUX_CH_DATA1 0xe4114 -+#define _PCH_DPB_AUX_CH_DATA2 0xe4118 -+#define _PCH_DPB_AUX_CH_DATA3 0xe411c -+#define _PCH_DPB_AUX_CH_DATA4 0xe4120 -+#define _PCH_DPB_AUX_CH_DATA5 0xe4124 -+ -+#define _PCH_DP_C 0xe4200 -+#define PCH_DP_C _MMIO(_PCH_DP_C) -+#define _PCH_DPC_AUX_CH_CTL 0xe4210 -+#define _PCH_DPC_AUX_CH_DATA1 0xe4214 -+#define _PCH_DPC_AUX_CH_DATA2 0xe4218 -+#define _PCH_DPC_AUX_CH_DATA3 0xe421c -+#define _PCH_DPC_AUX_CH_DATA4 0xe4220 -+#define _PCH_DPC_AUX_CH_DATA5 0xe4224 -+ -+#define _PCH_DP_D 0xe4300 -+#define PCH_DP_D _MMIO(_PCH_DP_D) -+#define _PCH_DPD_AUX_CH_CTL 0xe4310 -+#define _PCH_DPD_AUX_CH_DATA1 0xe4314 -+#define _PCH_DPD_AUX_CH_DATA2 0xe4318 -+#define _PCH_DPD_AUX_CH_DATA3 0xe431c -+#define _PCH_DPD_AUX_CH_DATA4 0xe4320 -+#define _PCH_DPD_AUX_CH_DATA5 0xe4324 -+ -+#define PCH_DP_AUX_CH_CTL(aux_ch) _MMIO_PORT((aux_ch) - AUX_CH_B, _PCH_DPB_AUX_CH_CTL, _PCH_DPC_AUX_CH_CTL) -+#define PCH_DP_AUX_CH_DATA(aux_ch, i) _MMIO(_PORT((aux_ch) - AUX_CH_B, _PCH_DPB_AUX_CH_DATA1, _PCH_DPC_AUX_CH_DATA1) + (i) * 4) /* 5 registers */ -+ -+/* CPT */ -+#define _TRANS_DP_CTL_A 0xe0300 -+#define _TRANS_DP_CTL_B 0xe1300 -+#define _TRANS_DP_CTL_C 0xe2300 -+#define TRANS_DP_CTL(pipe) _MMIO_PIPE(pipe, _TRANS_DP_CTL_A, _TRANS_DP_CTL_B) -+#define TRANS_DP_OUTPUT_ENABLE (1 << 31) -+#define TRANS_DP_PORT_SEL_MASK (3 << 29) -+#define TRANS_DP_PORT_SEL_NONE (3 << 29) -+#define TRANS_DP_PORT_SEL(port) (((port) - PORT_B) << 29) -+#define TRANS_DP_AUDIO_ONLY (1 << 26) -+#define TRANS_DP_ENH_FRAMING (1 << 18) -+#define TRANS_DP_8BPC (0 << 9) -+#define TRANS_DP_10BPC (1 << 9) -+#define TRANS_DP_6BPC (2 << 9) -+#define TRANS_DP_12BPC (3 << 9) -+#define TRANS_DP_BPC_MASK (3 << 9) -+#define TRANS_DP_VSYNC_ACTIVE_HIGH (1 << 4) -+#define TRANS_DP_VSYNC_ACTIVE_LOW 0 -+#define TRANS_DP_HSYNC_ACTIVE_HIGH (1 << 3) -+#define TRANS_DP_HSYNC_ACTIVE_LOW 0 -+#define TRANS_DP_SYNC_MASK (3 << 3) -+ -+/* SNB eDP training params */ -+/* SNB A-stepping */ -+#define EDP_LINK_TRAIN_400MV_0DB_SNB_A (0x38 << 22) -+#define EDP_LINK_TRAIN_400MV_6DB_SNB_A (0x02 << 22) -+#define EDP_LINK_TRAIN_600MV_3_5DB_SNB_A (0x01 << 22) -+#define EDP_LINK_TRAIN_800MV_0DB_SNB_A (0x0 << 22) -+/* SNB B-stepping */ -+#define EDP_LINK_TRAIN_400_600MV_0DB_SNB_B (0x0 << 22) -+#define EDP_LINK_TRAIN_400MV_3_5DB_SNB_B (0x1 << 22) -+#define EDP_LINK_TRAIN_400_600MV_6DB_SNB_B (0x3a << 22) -+#define EDP_LINK_TRAIN_600_800MV_3_5DB_SNB_B (0x39 << 22) -+#define EDP_LINK_TRAIN_800_1200MV_0DB_SNB_B (0x38 << 22) -+#define EDP_LINK_TRAIN_VOL_EMP_MASK_SNB (0x3f << 22) -+ -+/* IVB */ -+#define EDP_LINK_TRAIN_400MV_0DB_IVB (0x24 << 22) -+#define EDP_LINK_TRAIN_400MV_3_5DB_IVB (0x2a << 22) -+#define EDP_LINK_TRAIN_400MV_6DB_IVB (0x2f << 22) -+#define EDP_LINK_TRAIN_600MV_0DB_IVB (0x30 << 22) -+#define EDP_LINK_TRAIN_600MV_3_5DB_IVB (0x36 << 22) -+#define EDP_LINK_TRAIN_800MV_0DB_IVB (0x38 << 22) -+#define EDP_LINK_TRAIN_800MV_3_5DB_IVB (0x3e << 22) -+ -+/* legacy values */ -+#define EDP_LINK_TRAIN_500MV_0DB_IVB (0x00 << 22) -+#define EDP_LINK_TRAIN_1000MV_0DB_IVB (0x20 << 22) -+#define EDP_LINK_TRAIN_500MV_3_5DB_IVB (0x02 << 22) -+#define EDP_LINK_TRAIN_1000MV_3_5DB_IVB (0x22 << 22) -+#define EDP_LINK_TRAIN_1000MV_6DB_IVB (0x23 << 22) -+ -+#define EDP_LINK_TRAIN_VOL_EMP_MASK_IVB (0x3f << 22) -+ -+#define VLV_PMWGICZ _MMIO(0x1300a4) -+ -+#define RC6_LOCATION _MMIO(0xD40) -+#define RC6_CTX_IN_DRAM (1 << 0) -+#define RC6_CTX_BASE _MMIO(0xD48) -+#define RC6_CTX_BASE_MASK 0xFFFFFFF0 -+#define PWRCTX_MAXCNT_RCSUNIT _MMIO(0x2054) -+#define PWRCTX_MAXCNT_VCSUNIT0 _MMIO(0x12054) -+#define PWRCTX_MAXCNT_BCSUNIT _MMIO(0x22054) -+#define PWRCTX_MAXCNT_VECSUNIT _MMIO(0x1A054) -+#define PWRCTX_MAXCNT_VCSUNIT1 _MMIO(0x1C054) -+#define IDLE_TIME_MASK 0xFFFFF -+#define FORCEWAKE _MMIO(0xA18C) -+#define FORCEWAKE_VLV _MMIO(0x1300b0) -+#define FORCEWAKE_ACK_VLV _MMIO(0x1300b4) -+#define FORCEWAKE_MEDIA_VLV _MMIO(0x1300b8) -+#define FORCEWAKE_ACK_MEDIA_VLV _MMIO(0x1300bc) -+#define FORCEWAKE_ACK_HSW _MMIO(0x130044) -+#define FORCEWAKE_ACK _MMIO(0x130090) -+#define VLV_GTLC_WAKE_CTRL _MMIO(0x130090) -+#define VLV_GTLC_RENDER_CTX_EXISTS (1 << 25) -+#define VLV_GTLC_MEDIA_CTX_EXISTS (1 << 24) -+#define VLV_GTLC_ALLOWWAKEREQ (1 << 0) -+ -+#define VLV_GTLC_PW_STATUS _MMIO(0x130094) -+#define VLV_GTLC_ALLOWWAKEACK (1 << 0) -+#define VLV_GTLC_ALLOWWAKEERR (1 << 1) -+#define VLV_GTLC_PW_MEDIA_STATUS_MASK (1 << 5) -+#define VLV_GTLC_PW_RENDER_STATUS_MASK (1 << 7) -+#define FORCEWAKE_MT _MMIO(0xa188) /* multi-threaded */ -+#define FORCEWAKE_MEDIA_GEN9 _MMIO(0xa270) -+#define FORCEWAKE_MEDIA_VDBOX_GEN11(n) _MMIO(0xa540 + (n) * 4) -+#define FORCEWAKE_MEDIA_VEBOX_GEN11(n) _MMIO(0xa560 + (n) * 4) -+#define FORCEWAKE_RENDER_GEN9 _MMIO(0xa278) -+#define FORCEWAKE_BLITTER_GEN9 _MMIO(0xa188) -+#define FORCEWAKE_ACK_MEDIA_GEN9 _MMIO(0x0D88) -+#define FORCEWAKE_ACK_MEDIA_VDBOX_GEN11(n) _MMIO(0x0D50 + (n) * 4) -+#define FORCEWAKE_ACK_MEDIA_VEBOX_GEN11(n) _MMIO(0x0D70 + (n) * 4) -+#define FORCEWAKE_ACK_RENDER_GEN9 _MMIO(0x0D84) -+#define FORCEWAKE_ACK_BLITTER_GEN9 _MMIO(0x130044) -+#define FORCEWAKE_KERNEL BIT(0) -+#define FORCEWAKE_USER BIT(1) -+#define FORCEWAKE_KERNEL_FALLBACK BIT(15) -+#define FORCEWAKE_MT_ACK _MMIO(0x130040) -+#define ECOBUS _MMIO(0xa180) -+#define FORCEWAKE_MT_ENABLE (1 << 5) -+#define VLV_SPAREG2H _MMIO(0xA194) -+#define GEN9_PWRGT_DOMAIN_STATUS _MMIO(0xA2A0) -+#define GEN9_PWRGT_MEDIA_STATUS_MASK (1 << 0) -+#define GEN9_PWRGT_RENDER_STATUS_MASK (1 << 1) -+ -+#define GTFIFODBG _MMIO(0x120000) -+#define GT_FIFO_SBDEDICATE_FREE_ENTRY_CHV (0x1f << 20) -+#define GT_FIFO_FREE_ENTRIES_CHV (0x7f << 13) -+#define GT_FIFO_SBDROPERR (1 << 6) -+#define GT_FIFO_BLOBDROPERR (1 << 5) -+#define GT_FIFO_SB_READ_ABORTERR (1 << 4) -+#define GT_FIFO_DROPERR (1 << 3) -+#define GT_FIFO_OVFERR (1 << 2) -+#define GT_FIFO_IAWRERR (1 << 1) -+#define GT_FIFO_IARDERR (1 << 0) -+ -+#define GTFIFOCTL _MMIO(0x120008) -+#define GT_FIFO_FREE_ENTRIES_MASK 0x7f -+#define GT_FIFO_NUM_RESERVED_ENTRIES 20 -+#define GT_FIFO_CTL_BLOCK_ALL_POLICY_STALL (1 << 12) -+#define GT_FIFO_CTL_RC6_POLICY_STALL (1 << 11) -+ -+#define HSW_IDICR _MMIO(0x9008) -+#define IDIHASHMSK(x) (((x) & 0x3f) << 16) -+#define HSW_EDRAM_CAP _MMIO(0x120010) -+#define EDRAM_ENABLED 0x1 -+#define EDRAM_NUM_BANKS(cap) (((cap) >> 1) & 0xf) -+#define EDRAM_WAYS_IDX(cap) (((cap) >> 5) & 0x7) -+#define EDRAM_SETS_IDX(cap) (((cap) >> 8) & 0x3) -+ -+#define GEN6_UCGCTL1 _MMIO(0x9400) -+# define GEN6_GAMUNIT_CLOCK_GATE_DISABLE (1 << 22) -+# define GEN6_EU_TCUNIT_CLOCK_GATE_DISABLE (1 << 16) -+# define GEN6_BLBUNIT_CLOCK_GATE_DISABLE (1 << 5) -+# define GEN6_CSUNIT_CLOCK_GATE_DISABLE (1 << 7) -+ -+#define GEN6_UCGCTL2 _MMIO(0x9404) -+# define GEN6_VFUNIT_CLOCK_GATE_DISABLE (1 << 31) -+# define GEN7_VDSUNIT_CLOCK_GATE_DISABLE (1 << 30) -+# define GEN7_TDLUNIT_CLOCK_GATE_DISABLE (1 << 22) -+# define GEN6_RCZUNIT_CLOCK_GATE_DISABLE (1 << 13) -+# define GEN6_RCPBUNIT_CLOCK_GATE_DISABLE (1 << 12) -+# define GEN6_RCCUNIT_CLOCK_GATE_DISABLE (1 << 11) -+ -+#define GEN6_UCGCTL3 _MMIO(0x9408) -+# define GEN6_OACSUNIT_CLOCK_GATE_DISABLE (1 << 20) -+ -+#define GEN7_UCGCTL4 _MMIO(0x940c) -+#define GEN7_L3BANK2X_CLOCK_GATE_DISABLE (1 << 25) -+#define GEN8_EU_GAUNIT_CLOCK_GATE_DISABLE (1 << 14) -+ -+#define GEN6_RCGCTL1 _MMIO(0x9410) -+#define GEN6_RCGCTL2 _MMIO(0x9414) -+#define GEN6_RSTCTL _MMIO(0x9420) -+ -+#define GEN8_UCGCTL6 _MMIO(0x9430) -+#define GEN8_GAPSUNIT_CLOCK_GATE_DISABLE (1 << 24) -+#define GEN8_SDEUNIT_CLOCK_GATE_DISABLE (1 << 14) -+#define GEN8_HDCUNIT_CLOCK_GATE_DISABLE_HDCREQ (1 << 28) -+ -+#define GEN6_GFXPAUSE _MMIO(0xA000) -+#define GEN6_RPNSWREQ _MMIO(0xA008) -+#define GEN6_TURBO_DISABLE (1 << 31) -+#define GEN6_FREQUENCY(x) ((x) << 25) -+#define HSW_FREQUENCY(x) ((x) << 24) -+#define GEN9_FREQUENCY(x) ((x) << 23) -+#define GEN6_OFFSET(x) ((x) << 19) -+#define GEN6_AGGRESSIVE_TURBO (0 << 15) -+#define GEN6_RC_VIDEO_FREQ _MMIO(0xA00C) -+#define GEN6_RC_CONTROL _MMIO(0xA090) -+#define GEN6_RC_CTL_RC6pp_ENABLE (1 << 16) -+#define GEN6_RC_CTL_RC6p_ENABLE (1 << 17) -+#define GEN6_RC_CTL_RC6_ENABLE (1 << 18) -+#define GEN6_RC_CTL_RC1e_ENABLE (1 << 20) -+#define GEN6_RC_CTL_RC7_ENABLE (1 << 22) -+#define VLV_RC_CTL_CTX_RST_PARALLEL (1 << 24) -+#define GEN7_RC_CTL_TO_MODE (1 << 28) -+#define GEN6_RC_CTL_EI_MODE(x) ((x) << 27) -+#define GEN6_RC_CTL_HW_ENABLE (1 << 31) -+#define GEN6_RP_DOWN_TIMEOUT _MMIO(0xA010) -+#define GEN6_RP_INTERRUPT_LIMITS _MMIO(0xA014) -+#define GEN6_RPSTAT1 _MMIO(0xA01C) -+#define GEN6_CAGF_SHIFT 8 -+#define HSW_CAGF_SHIFT 7 -+#define GEN9_CAGF_SHIFT 23 -+#define GEN6_CAGF_MASK (0x7f << GEN6_CAGF_SHIFT) -+#define HSW_CAGF_MASK (0x7f << HSW_CAGF_SHIFT) -+#define GEN9_CAGF_MASK (0x1ff << GEN9_CAGF_SHIFT) -+#define GEN6_RP_CONTROL _MMIO(0xA024) -+#define GEN6_RP_MEDIA_TURBO (1 << 11) -+#define GEN6_RP_MEDIA_MODE_MASK (3 << 9) -+#define GEN6_RP_MEDIA_HW_TURBO_MODE (3 << 9) -+#define GEN6_RP_MEDIA_HW_NORMAL_MODE (2 << 9) -+#define GEN6_RP_MEDIA_HW_MODE (1 << 9) -+#define GEN6_RP_MEDIA_SW_MODE (0 << 9) -+#define GEN6_RP_MEDIA_IS_GFX (1 << 8) -+#define GEN6_RP_ENABLE (1 << 7) -+#define GEN6_RP_UP_IDLE_MIN (0x1 << 3) -+#define GEN6_RP_UP_BUSY_AVG (0x2 << 3) -+#define GEN6_RP_UP_BUSY_CONT (0x4 << 3) -+#define GEN6_RP_DOWN_IDLE_AVG (0x2 << 0) -+#define GEN6_RP_DOWN_IDLE_CONT (0x1 << 0) -+#define GEN6_RP_UP_THRESHOLD _MMIO(0xA02C) -+#define GEN6_RP_DOWN_THRESHOLD _MMIO(0xA030) -+#define GEN6_RP_CUR_UP_EI _MMIO(0xA050) -+#define GEN6_RP_EI_MASK 0xffffff -+#define GEN6_CURICONT_MASK GEN6_RP_EI_MASK -+#define GEN6_RP_CUR_UP _MMIO(0xA054) -+#define GEN6_CURBSYTAVG_MASK GEN6_RP_EI_MASK -+#define GEN6_RP_PREV_UP _MMIO(0xA058) -+#define GEN6_RP_CUR_DOWN_EI _MMIO(0xA05C) -+#define GEN6_CURIAVG_MASK GEN6_RP_EI_MASK -+#define GEN6_RP_CUR_DOWN _MMIO(0xA060) -+#define GEN6_RP_PREV_DOWN _MMIO(0xA064) -+#define GEN6_RP_UP_EI _MMIO(0xA068) -+#define GEN6_RP_DOWN_EI _MMIO(0xA06C) -+#define GEN6_RP_IDLE_HYSTERSIS _MMIO(0xA070) -+#define GEN6_RPDEUHWTC _MMIO(0xA080) -+#define GEN6_RPDEUC _MMIO(0xA084) -+#define GEN6_RPDEUCSW _MMIO(0xA088) -+#define GEN6_RC_STATE _MMIO(0xA094) -+#define RC_SW_TARGET_STATE_SHIFT 16 -+#define RC_SW_TARGET_STATE_MASK (7 << RC_SW_TARGET_STATE_SHIFT) -+#define GEN6_RC1_WAKE_RATE_LIMIT _MMIO(0xA098) -+#define GEN6_RC6_WAKE_RATE_LIMIT _MMIO(0xA09C) -+#define GEN6_RC6pp_WAKE_RATE_LIMIT _MMIO(0xA0A0) -+#define GEN10_MEDIA_WAKE_RATE_LIMIT _MMIO(0xA0A0) -+#define GEN6_RC_EVALUATION_INTERVAL _MMIO(0xA0A8) -+#define GEN6_RC_IDLE_HYSTERSIS _MMIO(0xA0AC) -+#define GEN6_RC_SLEEP _MMIO(0xA0B0) -+#define GEN6_RCUBMABDTMR _MMIO(0xA0B0) -+#define GEN6_RC1e_THRESHOLD _MMIO(0xA0B4) -+#define GEN6_RC6_THRESHOLD _MMIO(0xA0B8) -+#define GEN6_RC6p_THRESHOLD _MMIO(0xA0BC) -+#define VLV_RCEDATA _MMIO(0xA0BC) -+#define GEN6_RC6pp_THRESHOLD _MMIO(0xA0C0) -+#define GEN6_PMINTRMSK _MMIO(0xA168) -+#define GEN8_PMINTR_DISABLE_REDIRECT_TO_GUC (1 << 31) -+#define ARAT_EXPIRED_INTRMSK (1 << 9) -+#define GEN8_MISC_CTRL0 _MMIO(0xA180) -+#define VLV_PWRDWNUPCTL _MMIO(0xA294) -+#define GEN9_MEDIA_PG_IDLE_HYSTERESIS _MMIO(0xA0C4) -+#define GEN9_RENDER_PG_IDLE_HYSTERESIS _MMIO(0xA0C8) -+#define GEN9_PG_ENABLE _MMIO(0xA210) -+#define GEN9_RENDER_PG_ENABLE REG_BIT(0) -+#define GEN9_MEDIA_PG_ENABLE REG_BIT(1) -+#define GEN11_MEDIA_SAMPLER_PG_ENABLE REG_BIT(2) -+#define GEN8_PUSHBUS_CONTROL _MMIO(0xA248) -+#define GEN8_PUSHBUS_ENABLE _MMIO(0xA250) -+#define GEN8_PUSHBUS_SHIFT _MMIO(0xA25C) -+ -+#define VLV_CHICKEN_3 _MMIO(VLV_DISPLAY_BASE + 0x7040C) -+#define PIXEL_OVERLAP_CNT_MASK (3 << 30) -+#define PIXEL_OVERLAP_CNT_SHIFT 30 -+ -+#define GEN6_PMISR _MMIO(0x44020) -+#define GEN6_PMIMR _MMIO(0x44024) /* rps_lock */ -+#define GEN6_PMIIR _MMIO(0x44028) -+#define GEN6_PMIER _MMIO(0x4402C) -+#define GEN6_PM_MBOX_EVENT (1 << 25) -+#define GEN6_PM_THERMAL_EVENT (1 << 24) -+ -+/* -+ * For Gen11 these are in the upper word of the GPM_WGBOXPERF -+ * registers. Shifting is handled on accessing the imr and ier. -+ */ -+#define GEN6_PM_RP_DOWN_TIMEOUT (1 << 6) -+#define GEN6_PM_RP_UP_THRESHOLD (1 << 5) -+#define GEN6_PM_RP_DOWN_THRESHOLD (1 << 4) -+#define GEN6_PM_RP_UP_EI_EXPIRED (1 << 2) -+#define GEN6_PM_RP_DOWN_EI_EXPIRED (1 << 1) -+#define GEN6_PM_RPS_EVENTS (GEN6_PM_RP_UP_EI_EXPIRED | \ -+ GEN6_PM_RP_UP_THRESHOLD | \ -+ GEN6_PM_RP_DOWN_EI_EXPIRED | \ -+ GEN6_PM_RP_DOWN_THRESHOLD | \ -+ GEN6_PM_RP_DOWN_TIMEOUT) -+ -+#define GEN7_GT_SCRATCH(i) _MMIO(0x4F100 + (i) * 4) -+#define GEN7_GT_SCRATCH_REG_NUM 8 -+ -+#define VLV_GTLC_SURVIVABILITY_REG _MMIO(0x130098) -+#define VLV_GFX_CLK_STATUS_BIT (1 << 3) -+#define VLV_GFX_CLK_FORCE_ON_BIT (1 << 2) -+ -+#define GEN6_GT_GFX_RC6_LOCKED _MMIO(0x138104) -+#define VLV_COUNTER_CONTROL _MMIO(0x138104) -+#define VLV_COUNT_RANGE_HIGH (1 << 15) -+#define VLV_MEDIA_RC0_COUNT_EN (1 << 5) -+#define VLV_RENDER_RC0_COUNT_EN (1 << 4) -+#define VLV_MEDIA_RC6_COUNT_EN (1 << 1) -+#define VLV_RENDER_RC6_COUNT_EN (1 << 0) -+#define GEN6_GT_GFX_RC6 _MMIO(0x138108) -+#define VLV_GT_RENDER_RC6 _MMIO(0x138108) -+#define VLV_GT_MEDIA_RC6 _MMIO(0x13810C) -+ -+#define GEN6_GT_GFX_RC6p _MMIO(0x13810C) -+#define GEN6_GT_GFX_RC6pp _MMIO(0x138110) -+#define VLV_RENDER_C0_COUNT _MMIO(0x138118) -+#define VLV_MEDIA_C0_COUNT _MMIO(0x13811C) -+ -+#define GEN6_PCODE_MAILBOX _MMIO(0x138124) -+#define GEN6_PCODE_READY (1 << 31) -+#define GEN6_PCODE_ERROR_MASK 0xFF -+#define GEN6_PCODE_SUCCESS 0x0 -+#define GEN6_PCODE_ILLEGAL_CMD 0x1 -+#define GEN6_PCODE_MIN_FREQ_TABLE_GT_RATIO_OUT_OF_RANGE 0x2 -+#define GEN6_PCODE_TIMEOUT 0x3 -+#define GEN6_PCODE_UNIMPLEMENTED_CMD 0xFF -+#define GEN7_PCODE_TIMEOUT 0x2 -+#define GEN7_PCODE_ILLEGAL_DATA 0x3 -+#define GEN7_PCODE_MIN_FREQ_TABLE_GT_RATIO_OUT_OF_RANGE 0x10 -+#define GEN6_PCODE_WRITE_RC6VIDS 0x4 -+#define GEN6_PCODE_READ_RC6VIDS 0x5 -+#define GEN6_ENCODE_RC6_VID(mv) (((mv) - 245) / 5) -+#define GEN6_DECODE_RC6_VID(vids) (((vids) * 5) + 245) -+#define BDW_PCODE_DISPLAY_FREQ_CHANGE_REQ 0x18 -+#define GEN9_PCODE_READ_MEM_LATENCY 0x6 -+#define GEN9_MEM_LATENCY_LEVEL_MASK 0xFF -+#define GEN9_MEM_LATENCY_LEVEL_1_5_SHIFT 8 -+#define GEN9_MEM_LATENCY_LEVEL_2_6_SHIFT 16 -+#define GEN9_MEM_LATENCY_LEVEL_3_7_SHIFT 24 -+#define SKL_PCODE_LOAD_HDCP_KEYS 0x5 -+#define SKL_PCODE_CDCLK_CONTROL 0x7 -+#define SKL_CDCLK_PREPARE_FOR_CHANGE 0x3 -+#define SKL_CDCLK_READY_FOR_CHANGE 0x1 -+#define GEN6_PCODE_WRITE_MIN_FREQ_TABLE 0x8 -+#define GEN6_PCODE_READ_MIN_FREQ_TABLE 0x9 -+#define GEN6_READ_OC_PARAMS 0xc -+#define GEN6_PCODE_READ_D_COMP 0x10 -+#define GEN6_PCODE_WRITE_D_COMP 0x11 -+#define HSW_PCODE_DE_WRITE_FREQ_REQ 0x17 -+#define DISPLAY_IPS_CONTROL 0x19 -+ /* See also IPS_CTL */ -+#define IPS_PCODE_CONTROL (1 << 30) -+#define HSW_PCODE_DYNAMIC_DUTY_CYCLE_CONTROL 0x1A -+#define GEN9_PCODE_SAGV_CONTROL 0x21 -+#define GEN9_SAGV_DISABLE 0x0 -+#define GEN9_SAGV_IS_DISABLED 0x1 -+#define GEN9_SAGV_ENABLE 0x3 -+#define GEN6_PCODE_DATA _MMIO(0x138128) -+#define GEN6_PCODE_FREQ_IA_RATIO_SHIFT 8 -+#define GEN6_PCODE_FREQ_RING_RATIO_SHIFT 16 -+#define GEN6_PCODE_DATA1 _MMIO(0x13812C) -+ -+#define GEN6_GT_CORE_STATUS _MMIO(0x138060) -+#define GEN6_CORE_CPD_STATE_MASK (7 << 4) -+#define GEN6_RCn_MASK 7 -+#define GEN6_RC0 0 -+#define GEN6_RC3 2 -+#define GEN6_RC6 3 -+#define GEN6_RC7 4 -+ -+#define GEN8_GT_SLICE_INFO _MMIO(0x138064) -+#define GEN8_LSLICESTAT_MASK 0x7 -+ -+#define CHV_POWER_SS0_SIG1 _MMIO(0xa720) -+#define CHV_POWER_SS1_SIG1 _MMIO(0xa728) -+#define CHV_SS_PG_ENABLE (1 << 1) -+#define CHV_EU08_PG_ENABLE (1 << 9) -+#define CHV_EU19_PG_ENABLE (1 << 17) -+#define CHV_EU210_PG_ENABLE (1 << 25) -+ -+#define CHV_POWER_SS0_SIG2 _MMIO(0xa724) -+#define CHV_POWER_SS1_SIG2 _MMIO(0xa72c) -+#define CHV_EU311_PG_ENABLE (1 << 1) -+ -+#define GEN9_SLICE_PGCTL_ACK(slice) _MMIO(0x804c + (slice) * 0x4) -+#define GEN10_SLICE_PGCTL_ACK(slice) _MMIO(0x804c + ((slice) / 3) * 0x34 + \ -+ ((slice) % 3) * 0x4) -+#define GEN9_PGCTL_SLICE_ACK (1 << 0) -+#define GEN9_PGCTL_SS_ACK(subslice) (1 << (2 + (subslice) * 2)) -+#define GEN10_PGCTL_VALID_SS_MASK(slice) ((slice) == 0 ? 0x7F : 0x1F) -+ -+#define GEN9_SS01_EU_PGCTL_ACK(slice) _MMIO(0x805c + (slice) * 0x8) -+#define GEN10_SS01_EU_PGCTL_ACK(slice) _MMIO(0x805c + ((slice) / 3) * 0x30 + \ -+ ((slice) % 3) * 0x8) -+#define GEN9_SS23_EU_PGCTL_ACK(slice) _MMIO(0x8060 + (slice) * 0x8) -+#define GEN10_SS23_EU_PGCTL_ACK(slice) _MMIO(0x8060 + ((slice) / 3) * 0x30 + \ -+ ((slice) % 3) * 0x8) -+#define GEN9_PGCTL_SSA_EU08_ACK (1 << 0) -+#define GEN9_PGCTL_SSA_EU19_ACK (1 << 2) -+#define GEN9_PGCTL_SSA_EU210_ACK (1 << 4) -+#define GEN9_PGCTL_SSA_EU311_ACK (1 << 6) -+#define GEN9_PGCTL_SSB_EU08_ACK (1 << 8) -+#define GEN9_PGCTL_SSB_EU19_ACK (1 << 10) -+#define GEN9_PGCTL_SSB_EU210_ACK (1 << 12) -+#define GEN9_PGCTL_SSB_EU311_ACK (1 << 14) -+ -+#define GEN7_MISCCPCTL _MMIO(0x9424) -+#define GEN7_DOP_CLOCK_GATE_ENABLE (1 << 0) -+#define GEN8_DOP_CLOCK_GATE_CFCLK_ENABLE (1 << 2) -+#define GEN8_DOP_CLOCK_GATE_GUC_ENABLE (1 << 4) -+#define GEN8_DOP_CLOCK_GATE_MEDIA_ENABLE (1 << 6) -+ -+#define GEN8_GARBCNTL _MMIO(0xB004) -+#define GEN9_GAPS_TSV_CREDIT_DISABLE (1 << 7) -+#define GEN11_ARBITRATION_PRIO_ORDER_MASK (0x3f << 22) -+#define GEN11_HASH_CTRL_EXCL_MASK (0x7f << 0) -+#define GEN11_HASH_CTRL_EXCL_BIT0 (1 << 0) -+ -+#define GEN11_GLBLINVL _MMIO(0xB404) -+#define GEN11_BANK_HASH_ADDR_EXCL_MASK (0x7f << 5) -+#define GEN11_BANK_HASH_ADDR_EXCL_BIT0 (1 << 5) -+ -+#define GEN10_DFR_RATIO_EN_AND_CHICKEN _MMIO(0x9550) -+#define DFR_DISABLE (1 << 9) -+ -+#define GEN11_GACB_PERF_CTRL _MMIO(0x4B80) -+#define GEN11_HASH_CTRL_MASK (0x3 << 12 | 0xf << 0) -+#define GEN11_HASH_CTRL_BIT0 (1 << 0) -+#define GEN11_HASH_CTRL_BIT4 (1 << 12) -+ -+#define GEN11_LSN_UNSLCVC _MMIO(0xB43C) -+#define GEN11_LSN_UNSLCVC_GAFS_HALF_CL2_MAXALLOC (1 << 9) -+#define GEN11_LSN_UNSLCVC_GAFS_HALF_SF_MAXALLOC (1 << 7) -+ -+#define GEN10_SAMPLER_MODE _MMIO(0xE18C) -+ -+/* IVYBRIDGE DPF */ -+#define GEN7_L3CDERRST1(slice) _MMIO(0xB008 + (slice) * 0x200) /* L3CD Error Status 1 */ -+#define GEN7_L3CDERRST1_ROW_MASK (0x7ff << 14) -+#define GEN7_PARITY_ERROR_VALID (1 << 13) -+#define GEN7_L3CDERRST1_BANK_MASK (3 << 11) -+#define GEN7_L3CDERRST1_SUBBANK_MASK (7 << 8) -+#define GEN7_PARITY_ERROR_ROW(reg) \ -+ (((reg) & GEN7_L3CDERRST1_ROW_MASK) >> 14) -+#define GEN7_PARITY_ERROR_BANK(reg) \ -+ (((reg) & GEN7_L3CDERRST1_BANK_MASK) >> 11) -+#define GEN7_PARITY_ERROR_SUBBANK(reg) \ -+ (((reg) & GEN7_L3CDERRST1_SUBBANK_MASK) >> 8) -+#define GEN7_L3CDERRST1_ENABLE (1 << 7) -+ -+#define GEN7_L3LOG(slice, i) _MMIO(0xB070 + (slice) * 0x200 + (i) * 4) -+#define GEN7_L3LOG_SIZE 0x80 -+ -+#define GEN7_HALF_SLICE_CHICKEN1 _MMIO(0xe100) /* IVB GT1 + VLV */ -+#define GEN7_HALF_SLICE_CHICKEN1_GT2 _MMIO(0xf100) -+#define GEN7_MAX_PS_THREAD_DEP (8 << 12) -+#define GEN7_SINGLE_SUBSCAN_DISPATCH_ENABLE (1 << 10) -+#define GEN7_SBE_SS_CACHE_DISPATCH_PORT_SHARING_DISABLE (1 << 4) -+#define GEN7_PSD_SINGLE_PORT_DISPATCH_ENABLE (1 << 3) -+ -+#define GEN9_HALF_SLICE_CHICKEN5 _MMIO(0xe188) -+#define GEN9_DG_MIRROR_FIX_ENABLE (1 << 5) -+#define GEN9_CCS_TLB_PREFETCH_ENABLE (1 << 3) -+ -+#define GEN8_ROW_CHICKEN _MMIO(0xe4f0) -+#define FLOW_CONTROL_ENABLE (1 << 15) -+#define PARTIAL_INSTRUCTION_SHOOTDOWN_DISABLE (1 << 8) -+#define STALL_DOP_GATING_DISABLE (1 << 5) -+#define THROTTLE_12_5 (7 << 2) -+#define DISABLE_EARLY_EOT (1 << 1) -+ -+#define GEN7_ROW_CHICKEN2 _MMIO(0xe4f4) -+#define GEN7_ROW_CHICKEN2_GT2 _MMIO(0xf4f4) -+#define DOP_CLOCK_GATING_DISABLE (1 << 0) -+#define PUSH_CONSTANT_DEREF_DISABLE (1 << 8) -+#define GEN11_TDL_CLOCK_GATING_FIX_DISABLE (1 << 1) -+ -+#define HSW_ROW_CHICKEN3 _MMIO(0xe49c) -+#define HSW_ROW_CHICKEN3_L3_GLOBAL_ATOMICS_DISABLE (1 << 6) -+ -+#define HALF_SLICE_CHICKEN2 _MMIO(0xe180) -+#define GEN8_ST_PO_DISABLE (1 << 13) -+ -+#define HALF_SLICE_CHICKEN3 _MMIO(0xe184) -+#define HSW_SAMPLE_C_PERFORMANCE (1 << 9) -+#define GEN8_CENTROID_PIXEL_OPT_DIS (1 << 8) -+#define GEN9_DISABLE_OCL_OOB_SUPPRESS_LOGIC (1 << 5) -+#define CNL_FAST_ANISO_L1_BANKING_FIX (1 << 4) -+#define GEN8_SAMPLER_POWER_BYPASS_DIS (1 << 1) -+ -+#define GEN9_HALF_SLICE_CHICKEN7 _MMIO(0xe194) -+#define GEN9_SAMPLER_HASH_COMPRESSED_READ_ADDR (1 << 8) -+#define GEN9_ENABLE_YV12_BUGFIX (1 << 4) -+#define GEN9_ENABLE_GPGPU_PREEMPTION (1 << 2) -+ -+/* Audio */ -+#define G4X_AUD_VID_DID _MMIO(DISPLAY_MMIO_BASE(dev_priv) + 0x62020) -+#define INTEL_AUDIO_DEVCL 0x808629FB -+#define INTEL_AUDIO_DEVBLC 0x80862801 -+#define INTEL_AUDIO_DEVCTG 0x80862802 -+ -+#define G4X_AUD_CNTL_ST _MMIO(0x620B4) -+#define G4X_ELDV_DEVCL_DEVBLC (1 << 13) -+#define G4X_ELDV_DEVCTG (1 << 14) -+#define G4X_ELD_ADDR_MASK (0xf << 5) -+#define G4X_ELD_ACK (1 << 4) -+#define G4X_HDMIW_HDMIEDID _MMIO(0x6210C) -+ -+#define _IBX_HDMIW_HDMIEDID_A 0xE2050 -+#define _IBX_HDMIW_HDMIEDID_B 0xE2150 -+#define IBX_HDMIW_HDMIEDID(pipe) _MMIO_PIPE(pipe, _IBX_HDMIW_HDMIEDID_A, \ -+ _IBX_HDMIW_HDMIEDID_B) -+#define _IBX_AUD_CNTL_ST_A 0xE20B4 -+#define _IBX_AUD_CNTL_ST_B 0xE21B4 -+#define IBX_AUD_CNTL_ST(pipe) _MMIO_PIPE(pipe, _IBX_AUD_CNTL_ST_A, \ -+ _IBX_AUD_CNTL_ST_B) -+#define IBX_ELD_BUFFER_SIZE_MASK (0x1f << 10) -+#define IBX_ELD_ADDRESS_MASK (0x1f << 5) -+#define IBX_ELD_ACK (1 << 4) -+#define IBX_AUD_CNTL_ST2 _MMIO(0xE20C0) -+#define IBX_CP_READY(port) ((1 << 1) << (((port) - 1) * 4)) -+#define IBX_ELD_VALID(port) ((1 << 0) << (((port) - 1) * 4)) -+ -+#define _CPT_HDMIW_HDMIEDID_A 0xE5050 -+#define _CPT_HDMIW_HDMIEDID_B 0xE5150 -+#define CPT_HDMIW_HDMIEDID(pipe) _MMIO_PIPE(pipe, _CPT_HDMIW_HDMIEDID_A, _CPT_HDMIW_HDMIEDID_B) -+#define _CPT_AUD_CNTL_ST_A 0xE50B4 -+#define _CPT_AUD_CNTL_ST_B 0xE51B4 -+#define CPT_AUD_CNTL_ST(pipe) _MMIO_PIPE(pipe, _CPT_AUD_CNTL_ST_A, _CPT_AUD_CNTL_ST_B) -+#define CPT_AUD_CNTRL_ST2 _MMIO(0xE50C0) -+ -+#define _VLV_HDMIW_HDMIEDID_A (VLV_DISPLAY_BASE + 0x62050) -+#define _VLV_HDMIW_HDMIEDID_B (VLV_DISPLAY_BASE + 0x62150) -+#define VLV_HDMIW_HDMIEDID(pipe) _MMIO_PIPE(pipe, _VLV_HDMIW_HDMIEDID_A, _VLV_HDMIW_HDMIEDID_B) -+#define _VLV_AUD_CNTL_ST_A (VLV_DISPLAY_BASE + 0x620B4) -+#define _VLV_AUD_CNTL_ST_B (VLV_DISPLAY_BASE + 0x621B4) -+#define VLV_AUD_CNTL_ST(pipe) _MMIO_PIPE(pipe, _VLV_AUD_CNTL_ST_A, _VLV_AUD_CNTL_ST_B) -+#define VLV_AUD_CNTL_ST2 _MMIO(VLV_DISPLAY_BASE + 0x620C0) -+ -+/* These are the 4 32-bit write offset registers for each stream -+ * output buffer. It determines the offset from the -+ * 3DSTATE_SO_BUFFERs that the next streamed vertex output goes to. -+ */ -+#define GEN7_SO_WRITE_OFFSET(n) _MMIO(0x5280 + (n) * 4) -+ -+#define _IBX_AUD_CONFIG_A 0xe2000 -+#define _IBX_AUD_CONFIG_B 0xe2100 -+#define IBX_AUD_CFG(pipe) _MMIO_PIPE(pipe, _IBX_AUD_CONFIG_A, _IBX_AUD_CONFIG_B) -+#define _CPT_AUD_CONFIG_A 0xe5000 -+#define _CPT_AUD_CONFIG_B 0xe5100 -+#define CPT_AUD_CFG(pipe) _MMIO_PIPE(pipe, _CPT_AUD_CONFIG_A, _CPT_AUD_CONFIG_B) -+#define _VLV_AUD_CONFIG_A (VLV_DISPLAY_BASE + 0x62000) -+#define _VLV_AUD_CONFIG_B (VLV_DISPLAY_BASE + 0x62100) -+#define VLV_AUD_CFG(pipe) _MMIO_PIPE(pipe, _VLV_AUD_CONFIG_A, _VLV_AUD_CONFIG_B) -+ -+#define AUD_CONFIG_N_VALUE_INDEX (1 << 29) -+#define AUD_CONFIG_N_PROG_ENABLE (1 << 28) -+#define AUD_CONFIG_UPPER_N_SHIFT 20 -+#define AUD_CONFIG_UPPER_N_MASK (0xff << 20) -+#define AUD_CONFIG_LOWER_N_SHIFT 4 -+#define AUD_CONFIG_LOWER_N_MASK (0xfff << 4) -+#define AUD_CONFIG_N_MASK (AUD_CONFIG_UPPER_N_MASK | AUD_CONFIG_LOWER_N_MASK) -+#define AUD_CONFIG_N(n) \ -+ (((((n) >> 12) & 0xff) << AUD_CONFIG_UPPER_N_SHIFT) | \ -+ (((n) & 0xfff) << AUD_CONFIG_LOWER_N_SHIFT)) -+#define AUD_CONFIG_PIXEL_CLOCK_HDMI_SHIFT 16 -+#define AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK (0xf << 16) -+#define AUD_CONFIG_PIXEL_CLOCK_HDMI_25175 (0 << 16) -+#define AUD_CONFIG_PIXEL_CLOCK_HDMI_25200 (1 << 16) -+#define AUD_CONFIG_PIXEL_CLOCK_HDMI_27000 (2 << 16) -+#define AUD_CONFIG_PIXEL_CLOCK_HDMI_27027 (3 << 16) -+#define AUD_CONFIG_PIXEL_CLOCK_HDMI_54000 (4 << 16) -+#define AUD_CONFIG_PIXEL_CLOCK_HDMI_54054 (5 << 16) -+#define AUD_CONFIG_PIXEL_CLOCK_HDMI_74176 (6 << 16) -+#define AUD_CONFIG_PIXEL_CLOCK_HDMI_74250 (7 << 16) -+#define AUD_CONFIG_PIXEL_CLOCK_HDMI_148352 (8 << 16) -+#define AUD_CONFIG_PIXEL_CLOCK_HDMI_148500 (9 << 16) -+#define AUD_CONFIG_DISABLE_NCTS (1 << 3) -+ -+/* HSW Audio */ -+#define _HSW_AUD_CONFIG_A 0x65000 -+#define _HSW_AUD_CONFIG_B 0x65100 -+#define HSW_AUD_CFG(pipe) _MMIO_PIPE(pipe, _HSW_AUD_CONFIG_A, _HSW_AUD_CONFIG_B) -+ -+#define _HSW_AUD_MISC_CTRL_A 0x65010 -+#define _HSW_AUD_MISC_CTRL_B 0x65110 -+#define HSW_AUD_MISC_CTRL(pipe) _MMIO_PIPE(pipe, _HSW_AUD_MISC_CTRL_A, _HSW_AUD_MISC_CTRL_B) -+ -+#define _HSW_AUD_M_CTS_ENABLE_A 0x65028 -+#define _HSW_AUD_M_CTS_ENABLE_B 0x65128 -+#define HSW_AUD_M_CTS_ENABLE(pipe) _MMIO_PIPE(pipe, _HSW_AUD_M_CTS_ENABLE_A, _HSW_AUD_M_CTS_ENABLE_B) -+#define AUD_M_CTS_M_VALUE_INDEX (1 << 21) -+#define AUD_M_CTS_M_PROG_ENABLE (1 << 20) -+#define AUD_CONFIG_M_MASK 0xfffff -+ -+#define _HSW_AUD_DIP_ELD_CTRL_ST_A 0x650b4 -+#define _HSW_AUD_DIP_ELD_CTRL_ST_B 0x651b4 -+#define HSW_AUD_DIP_ELD_CTRL(pipe) _MMIO_PIPE(pipe, _HSW_AUD_DIP_ELD_CTRL_ST_A, _HSW_AUD_DIP_ELD_CTRL_ST_B) -+ -+/* Audio Digital Converter */ -+#define _HSW_AUD_DIG_CNVT_1 0x65080 -+#define _HSW_AUD_DIG_CNVT_2 0x65180 -+#define AUD_DIG_CNVT(pipe) _MMIO_PIPE(pipe, _HSW_AUD_DIG_CNVT_1, _HSW_AUD_DIG_CNVT_2) -+#define DIP_PORT_SEL_MASK 0x3 -+ -+#define _HSW_AUD_EDID_DATA_A 0x65050 -+#define _HSW_AUD_EDID_DATA_B 0x65150 -+#define HSW_AUD_EDID_DATA(pipe) _MMIO_PIPE(pipe, _HSW_AUD_EDID_DATA_A, _HSW_AUD_EDID_DATA_B) -+ -+#define HSW_AUD_PIPE_CONV_CFG _MMIO(0x6507c) -+#define HSW_AUD_PIN_ELD_CP_VLD _MMIO(0x650c0) -+#define AUDIO_INACTIVE(trans) ((1 << 3) << ((trans) * 4)) -+#define AUDIO_OUTPUT_ENABLE(trans) ((1 << 2) << ((trans) * 4)) -+#define AUDIO_CP_READY(trans) ((1 << 1) << ((trans) * 4)) -+#define AUDIO_ELD_VALID(trans) ((1 << 0) << ((trans) * 4)) -+ -+#define HSW_AUD_CHICKENBIT _MMIO(0x65f10) -+#define SKL_AUD_CODEC_WAKE_SIGNAL (1 << 15) -+ -+/* -+ * HSW - ICL power wells -+ * -+ * Platforms have up to 3 power well control register sets, each set -+ * controlling up to 16 power wells via a request/status HW flag tuple: -+ * - main (HSW_PWR_WELL_CTL[1-4]) -+ * - AUX (ICL_PWR_WELL_CTL_AUX[1-4]) -+ * - DDI (ICL_PWR_WELL_CTL_DDI[1-4]) -+ * Each control register set consists of up to 4 registers used by different -+ * sources that can request a power well to be enabled: -+ * - BIOS (HSW_PWR_WELL_CTL1/ICL_PWR_WELL_CTL_AUX1/ICL_PWR_WELL_CTL_DDI1) -+ * - DRIVER (HSW_PWR_WELL_CTL2/ICL_PWR_WELL_CTL_AUX2/ICL_PWR_WELL_CTL_DDI2) -+ * - KVMR (HSW_PWR_WELL_CTL3) (only in the main register set) -+ * - DEBUG (HSW_PWR_WELL_CTL4/ICL_PWR_WELL_CTL_AUX4/ICL_PWR_WELL_CTL_DDI4) -+ */ -+#define HSW_PWR_WELL_CTL1 _MMIO(0x45400) -+#define HSW_PWR_WELL_CTL2 _MMIO(0x45404) -+#define HSW_PWR_WELL_CTL3 _MMIO(0x45408) -+#define HSW_PWR_WELL_CTL4 _MMIO(0x4540C) -+#define HSW_PWR_WELL_CTL_REQ(pw_idx) (0x2 << ((pw_idx) * 2)) -+#define HSW_PWR_WELL_CTL_STATE(pw_idx) (0x1 << ((pw_idx) * 2)) -+ -+/* HSW/BDW power well */ -+#define HSW_PW_CTL_IDX_GLOBAL 15 -+ -+/* SKL/BXT/GLK/CNL power wells */ -+#define SKL_PW_CTL_IDX_PW_2 15 -+#define SKL_PW_CTL_IDX_PW_1 14 -+#define CNL_PW_CTL_IDX_AUX_F 12 -+#define CNL_PW_CTL_IDX_AUX_D 11 -+#define GLK_PW_CTL_IDX_AUX_C 10 -+#define GLK_PW_CTL_IDX_AUX_B 9 -+#define GLK_PW_CTL_IDX_AUX_A 8 -+#define CNL_PW_CTL_IDX_DDI_F 6 -+#define SKL_PW_CTL_IDX_DDI_D 4 -+#define SKL_PW_CTL_IDX_DDI_C 3 -+#define SKL_PW_CTL_IDX_DDI_B 2 -+#define SKL_PW_CTL_IDX_DDI_A_E 1 -+#define GLK_PW_CTL_IDX_DDI_A 1 -+#define SKL_PW_CTL_IDX_MISC_IO 0 -+ -+/* ICL - power wells */ -+#define ICL_PW_CTL_IDX_PW_4 3 -+#define ICL_PW_CTL_IDX_PW_3 2 -+#define ICL_PW_CTL_IDX_PW_2 1 -+#define ICL_PW_CTL_IDX_PW_1 0 -+ -+#define ICL_PWR_WELL_CTL_AUX1 _MMIO(0x45440) -+#define ICL_PWR_WELL_CTL_AUX2 _MMIO(0x45444) -+#define ICL_PWR_WELL_CTL_AUX4 _MMIO(0x4544C) -+#define ICL_PW_CTL_IDX_AUX_TBT4 11 -+#define ICL_PW_CTL_IDX_AUX_TBT3 10 -+#define ICL_PW_CTL_IDX_AUX_TBT2 9 -+#define ICL_PW_CTL_IDX_AUX_TBT1 8 -+#define ICL_PW_CTL_IDX_AUX_F 5 -+#define ICL_PW_CTL_IDX_AUX_E 4 -+#define ICL_PW_CTL_IDX_AUX_D 3 -+#define ICL_PW_CTL_IDX_AUX_C 2 -+#define ICL_PW_CTL_IDX_AUX_B 1 -+#define ICL_PW_CTL_IDX_AUX_A 0 -+ -+#define ICL_PWR_WELL_CTL_DDI1 _MMIO(0x45450) -+#define ICL_PWR_WELL_CTL_DDI2 _MMIO(0x45454) -+#define ICL_PWR_WELL_CTL_DDI4 _MMIO(0x4545C) -+#define ICL_PW_CTL_IDX_DDI_F 5 -+#define ICL_PW_CTL_IDX_DDI_E 4 -+#define ICL_PW_CTL_IDX_DDI_D 3 -+#define ICL_PW_CTL_IDX_DDI_C 2 -+#define ICL_PW_CTL_IDX_DDI_B 1 -+#define ICL_PW_CTL_IDX_DDI_A 0 -+ -+/* HSW - power well misc debug registers */ -+#define HSW_PWR_WELL_CTL5 _MMIO(0x45410) -+#define HSW_PWR_WELL_ENABLE_SINGLE_STEP (1 << 31) -+#define HSW_PWR_WELL_PWR_GATE_OVERRIDE (1 << 20) -+#define HSW_PWR_WELL_FORCE_ON (1 << 19) -+#define HSW_PWR_WELL_CTL6 _MMIO(0x45414) -+ -+/* SKL Fuse Status */ -+enum skl_power_gate { -+ SKL_PG0, -+ SKL_PG1, -+ SKL_PG2, -+ ICL_PG3, -+ ICL_PG4, -+}; -+ -+#define SKL_FUSE_STATUS _MMIO(0x42000) -+#define SKL_FUSE_DOWNLOAD_STATUS (1 << 31) -+/* -+ * PG0 is HW controlled, so doesn't have a corresponding power well control knob -+ * SKL_DISP_PW1_IDX..SKL_DISP_PW2_IDX -> PG1..PG2 -+ */ -+#define SKL_PW_CTL_IDX_TO_PG(pw_idx) \ -+ ((pw_idx) - SKL_PW_CTL_IDX_PW_1 + SKL_PG1) -+/* -+ * PG0 is HW controlled, so doesn't have a corresponding power well control knob -+ * ICL_DISP_PW1_IDX..ICL_DISP_PW4_IDX -> PG1..PG4 -+ */ -+#define ICL_PW_CTL_IDX_TO_PG(pw_idx) \ -+ ((pw_idx) - ICL_PW_CTL_IDX_PW_1 + SKL_PG1) -+#define SKL_FUSE_PG_DIST_STATUS(pg) (1 << (27 - (pg))) -+ -+#define _CNL_AUX_REG_IDX(pw_idx) ((pw_idx) - GLK_PW_CTL_IDX_AUX_B) -+#define _CNL_AUX_ANAOVRD1_B 0x162250 -+#define _CNL_AUX_ANAOVRD1_C 0x162210 -+#define _CNL_AUX_ANAOVRD1_D 0x1622D0 -+#define _CNL_AUX_ANAOVRD1_F 0x162A90 -+#define CNL_AUX_ANAOVRD1(pw_idx) _MMIO(_PICK(_CNL_AUX_REG_IDX(pw_idx), \ -+ _CNL_AUX_ANAOVRD1_B, \ -+ _CNL_AUX_ANAOVRD1_C, \ -+ _CNL_AUX_ANAOVRD1_D, \ -+ _CNL_AUX_ANAOVRD1_F)) -+#define CNL_AUX_ANAOVRD1_ENABLE (1 << 16) -+#define CNL_AUX_ANAOVRD1_LDO_BYPASS (1 << 23) -+ -+#define _ICL_AUX_REG_IDX(pw_idx) ((pw_idx) - ICL_PW_CTL_IDX_AUX_A) -+#define _ICL_AUX_ANAOVRD1_A 0x162398 -+#define _ICL_AUX_ANAOVRD1_B 0x6C398 -+#define ICL_AUX_ANAOVRD1(pw_idx) _MMIO(_PICK(_ICL_AUX_REG_IDX(pw_idx), \ -+ _ICL_AUX_ANAOVRD1_A, \ -+ _ICL_AUX_ANAOVRD1_B)) -+#define ICL_AUX_ANAOVRD1_LDO_BYPASS (1 << 7) -+#define ICL_AUX_ANAOVRD1_ENABLE (1 << 0) -+ -+/* HDCP Key Registers */ -+#define HDCP_KEY_CONF _MMIO(0x66c00) -+#define HDCP_AKSV_SEND_TRIGGER BIT(31) -+#define HDCP_CLEAR_KEYS_TRIGGER BIT(30) -+#define HDCP_KEY_LOAD_TRIGGER BIT(8) -+#define HDCP_KEY_STATUS _MMIO(0x66c04) -+#define HDCP_FUSE_IN_PROGRESS BIT(7) -+#define HDCP_FUSE_ERROR BIT(6) -+#define HDCP_FUSE_DONE BIT(5) -+#define HDCP_KEY_LOAD_STATUS BIT(1) -+#define HDCP_KEY_LOAD_DONE BIT(0) -+#define HDCP_AKSV_LO _MMIO(0x66c10) -+#define HDCP_AKSV_HI _MMIO(0x66c14) -+ -+/* HDCP Repeater Registers */ -+#define HDCP_REP_CTL _MMIO(0x66d00) -+#define HDCP_DDIB_REP_PRESENT BIT(30) -+#define HDCP_DDIA_REP_PRESENT BIT(29) -+#define HDCP_DDIC_REP_PRESENT BIT(28) -+#define HDCP_DDID_REP_PRESENT BIT(27) -+#define HDCP_DDIF_REP_PRESENT BIT(26) -+#define HDCP_DDIE_REP_PRESENT BIT(25) -+#define HDCP_DDIB_SHA1_M0 (1 << 20) -+#define HDCP_DDIA_SHA1_M0 (2 << 20) -+#define HDCP_DDIC_SHA1_M0 (3 << 20) -+#define HDCP_DDID_SHA1_M0 (4 << 20) -+#define HDCP_DDIF_SHA1_M0 (5 << 20) -+#define HDCP_DDIE_SHA1_M0 (6 << 20) /* Bspec says 5? */ -+#define HDCP_SHA1_BUSY BIT(16) -+#define HDCP_SHA1_READY BIT(17) -+#define HDCP_SHA1_COMPLETE BIT(18) -+#define HDCP_SHA1_V_MATCH BIT(19) -+#define HDCP_SHA1_TEXT_32 (1 << 1) -+#define HDCP_SHA1_COMPLETE_HASH (2 << 1) -+#define HDCP_SHA1_TEXT_24 (4 << 1) -+#define HDCP_SHA1_TEXT_16 (5 << 1) -+#define HDCP_SHA1_TEXT_8 (6 << 1) -+#define HDCP_SHA1_TEXT_0 (7 << 1) -+#define HDCP_SHA_V_PRIME_H0 _MMIO(0x66d04) -+#define HDCP_SHA_V_PRIME_H1 _MMIO(0x66d08) -+#define HDCP_SHA_V_PRIME_H2 _MMIO(0x66d0C) -+#define HDCP_SHA_V_PRIME_H3 _MMIO(0x66d10) -+#define HDCP_SHA_V_PRIME_H4 _MMIO(0x66d14) -+#define HDCP_SHA_V_PRIME(h) _MMIO((0x66d04 + (h) * 4)) -+#define HDCP_SHA_TEXT _MMIO(0x66d18) -+ -+/* HDCP Auth Registers */ -+#define _PORTA_HDCP_AUTHENC 0x66800 -+#define _PORTB_HDCP_AUTHENC 0x66500 -+#define _PORTC_HDCP_AUTHENC 0x66600 -+#define _PORTD_HDCP_AUTHENC 0x66700 -+#define _PORTE_HDCP_AUTHENC 0x66A00 -+#define _PORTF_HDCP_AUTHENC 0x66900 -+#define _PORT_HDCP_AUTHENC(port, x) _MMIO(_PICK(port, \ -+ _PORTA_HDCP_AUTHENC, \ -+ _PORTB_HDCP_AUTHENC, \ -+ _PORTC_HDCP_AUTHENC, \ -+ _PORTD_HDCP_AUTHENC, \ -+ _PORTE_HDCP_AUTHENC, \ -+ _PORTF_HDCP_AUTHENC) + (x)) -+#define PORT_HDCP_CONF(port) _PORT_HDCP_AUTHENC(port, 0x0) -+#define HDCP_CONF_CAPTURE_AN BIT(0) -+#define HDCP_CONF_AUTH_AND_ENC (BIT(1) | BIT(0)) -+#define PORT_HDCP_ANINIT(port) _PORT_HDCP_AUTHENC(port, 0x4) -+#define PORT_HDCP_ANLO(port) _PORT_HDCP_AUTHENC(port, 0x8) -+#define PORT_HDCP_ANHI(port) _PORT_HDCP_AUTHENC(port, 0xC) -+#define PORT_HDCP_BKSVLO(port) _PORT_HDCP_AUTHENC(port, 0x10) -+#define PORT_HDCP_BKSVHI(port) _PORT_HDCP_AUTHENC(port, 0x14) -+#define PORT_HDCP_RPRIME(port) _PORT_HDCP_AUTHENC(port, 0x18) -+#define PORT_HDCP_STATUS(port) _PORT_HDCP_AUTHENC(port, 0x1C) -+#define HDCP_STATUS_STREAM_A_ENC BIT(31) -+#define HDCP_STATUS_STREAM_B_ENC BIT(30) -+#define HDCP_STATUS_STREAM_C_ENC BIT(29) -+#define HDCP_STATUS_STREAM_D_ENC BIT(28) -+#define HDCP_STATUS_AUTH BIT(21) -+#define HDCP_STATUS_ENC BIT(20) -+#define HDCP_STATUS_RI_MATCH BIT(19) -+#define HDCP_STATUS_R0_READY BIT(18) -+#define HDCP_STATUS_AN_READY BIT(17) -+#define HDCP_STATUS_CIPHER BIT(16) -+#define HDCP_STATUS_FRAME_CNT(x) (((x) >> 8) & 0xff) -+ -+/* HDCP2.2 Registers */ -+#define _PORTA_HDCP2_BASE 0x66800 -+#define _PORTB_HDCP2_BASE 0x66500 -+#define _PORTC_HDCP2_BASE 0x66600 -+#define _PORTD_HDCP2_BASE 0x66700 -+#define _PORTE_HDCP2_BASE 0x66A00 -+#define _PORTF_HDCP2_BASE 0x66900 -+#define _PORT_HDCP2_BASE(port, x) _MMIO(_PICK((port), \ -+ _PORTA_HDCP2_BASE, \ -+ _PORTB_HDCP2_BASE, \ -+ _PORTC_HDCP2_BASE, \ -+ _PORTD_HDCP2_BASE, \ -+ _PORTE_HDCP2_BASE, \ -+ _PORTF_HDCP2_BASE) + (x)) -+ -+#define HDCP2_AUTH_DDI(port) _PORT_HDCP2_BASE(port, 0x98) -+#define AUTH_LINK_AUTHENTICATED BIT(31) -+#define AUTH_LINK_TYPE BIT(30) -+#define AUTH_FORCE_CLR_INPUTCTR BIT(19) -+#define AUTH_CLR_KEYS BIT(18) -+ -+#define HDCP2_CTL_DDI(port) _PORT_HDCP2_BASE(port, 0xB0) -+#define CTL_LINK_ENCRYPTION_REQ BIT(31) -+ -+#define HDCP2_STATUS_DDI(port) _PORT_HDCP2_BASE(port, 0xB4) -+#define STREAM_ENCRYPTION_STATUS_A BIT(31) -+#define STREAM_ENCRYPTION_STATUS_B BIT(30) -+#define STREAM_ENCRYPTION_STATUS_C BIT(29) -+#define LINK_TYPE_STATUS BIT(22) -+#define LINK_AUTH_STATUS BIT(21) -+#define LINK_ENCRYPTION_STATUS BIT(20) -+ -+/* Per-pipe DDI Function Control */ -+#define _TRANS_DDI_FUNC_CTL_A 0x60400 -+#define _TRANS_DDI_FUNC_CTL_B 0x61400 -+#define _TRANS_DDI_FUNC_CTL_C 0x62400 -+#define _TRANS_DDI_FUNC_CTL_EDP 0x6F400 -+#define _TRANS_DDI_FUNC_CTL_DSI0 0x6b400 -+#define _TRANS_DDI_FUNC_CTL_DSI1 0x6bc00 -+#define TRANS_DDI_FUNC_CTL(tran) _MMIO_TRANS2(tran, _TRANS_DDI_FUNC_CTL_A) -+ -+#define TRANS_DDI_FUNC_ENABLE (1 << 31) -+/* Those bits are ignored by pipe EDP since it can only connect to DDI A */ -+#define TRANS_DDI_PORT_MASK (7 << 28) -+#define TRANS_DDI_PORT_SHIFT 28 -+#define TRANS_DDI_SELECT_PORT(x) ((x) << 28) -+#define TRANS_DDI_PORT_NONE (0 << 28) -+#define TRANS_DDI_MODE_SELECT_MASK (7 << 24) -+#define TRANS_DDI_MODE_SELECT_HDMI (0 << 24) -+#define TRANS_DDI_MODE_SELECT_DVI (1 << 24) -+#define TRANS_DDI_MODE_SELECT_DP_SST (2 << 24) -+#define TRANS_DDI_MODE_SELECT_DP_MST (3 << 24) -+#define TRANS_DDI_MODE_SELECT_FDI (4 << 24) -+#define TRANS_DDI_BPC_MASK (7 << 20) -+#define TRANS_DDI_BPC_8 (0 << 20) -+#define TRANS_DDI_BPC_10 (1 << 20) -+#define TRANS_DDI_BPC_6 (2 << 20) -+#define TRANS_DDI_BPC_12 (3 << 20) -+#define TRANS_DDI_PVSYNC (1 << 17) -+#define TRANS_DDI_PHSYNC (1 << 16) -+#define TRANS_DDI_EDP_INPUT_MASK (7 << 12) -+#define TRANS_DDI_EDP_INPUT_A_ON (0 << 12) -+#define TRANS_DDI_EDP_INPUT_A_ONOFF (4 << 12) -+#define TRANS_DDI_EDP_INPUT_B_ONOFF (5 << 12) -+#define TRANS_DDI_EDP_INPUT_C_ONOFF (6 << 12) -+#define TRANS_DDI_HDCP_SIGNALLING (1 << 9) -+#define TRANS_DDI_DP_VC_PAYLOAD_ALLOC (1 << 8) -+#define TRANS_DDI_HDMI_SCRAMBLER_CTS_ENABLE (1 << 7) -+#define TRANS_DDI_HDMI_SCRAMBLER_RESET_FREQ (1 << 6) -+#define TRANS_DDI_BFI_ENABLE (1 << 4) -+#define TRANS_DDI_HIGH_TMDS_CHAR_RATE (1 << 4) -+#define TRANS_DDI_HDMI_SCRAMBLING (1 << 0) -+#define TRANS_DDI_HDMI_SCRAMBLING_MASK (TRANS_DDI_HDMI_SCRAMBLER_CTS_ENABLE \ -+ | TRANS_DDI_HDMI_SCRAMBLER_RESET_FREQ \ -+ | TRANS_DDI_HDMI_SCRAMBLING) -+ -+#define _TRANS_DDI_FUNC_CTL2_A 0x60404 -+#define _TRANS_DDI_FUNC_CTL2_B 0x61404 -+#define _TRANS_DDI_FUNC_CTL2_C 0x62404 -+#define _TRANS_DDI_FUNC_CTL2_EDP 0x6f404 -+#define _TRANS_DDI_FUNC_CTL2_DSI0 0x6b404 -+#define _TRANS_DDI_FUNC_CTL2_DSI1 0x6bc04 -+#define TRANS_DDI_FUNC_CTL2(tran) _MMIO_TRANS2(tran, \ -+ _TRANS_DDI_FUNC_CTL2_A) -+#define PORT_SYNC_MODE_ENABLE (1 << 4) -+#define PORT_SYNC_MODE_MASTER_SELECT(x) ((x) << 0) -+#define PORT_SYNC_MODE_MASTER_SELECT_MASK (0x7 << 0) -+#define PORT_SYNC_MODE_MASTER_SELECT_SHIFT 0 -+ -+/* DisplayPort Transport Control */ -+#define _DP_TP_CTL_A 0x64040 -+#define _DP_TP_CTL_B 0x64140 -+#define DP_TP_CTL(port) _MMIO_PORT(port, _DP_TP_CTL_A, _DP_TP_CTL_B) -+#define DP_TP_CTL_ENABLE (1 << 31) -+#define DP_TP_CTL_FEC_ENABLE (1 << 30) -+#define DP_TP_CTL_MODE_SST (0 << 27) -+#define DP_TP_CTL_MODE_MST (1 << 27) -+#define DP_TP_CTL_FORCE_ACT (1 << 25) -+#define DP_TP_CTL_ENHANCED_FRAME_ENABLE (1 << 18) -+#define DP_TP_CTL_FDI_AUTOTRAIN (1 << 15) -+#define DP_TP_CTL_LINK_TRAIN_MASK (7 << 8) -+#define DP_TP_CTL_LINK_TRAIN_PAT1 (0 << 8) -+#define DP_TP_CTL_LINK_TRAIN_PAT2 (1 << 8) -+#define DP_TP_CTL_LINK_TRAIN_PAT3 (4 << 8) -+#define DP_TP_CTL_LINK_TRAIN_PAT4 (5 << 8) -+#define DP_TP_CTL_LINK_TRAIN_IDLE (2 << 8) -+#define DP_TP_CTL_LINK_TRAIN_NORMAL (3 << 8) -+#define DP_TP_CTL_SCRAMBLE_DISABLE (1 << 7) -+ -+/* DisplayPort Transport Status */ -+#define _DP_TP_STATUS_A 0x64044 -+#define _DP_TP_STATUS_B 0x64144 -+#define DP_TP_STATUS(port) _MMIO_PORT(port, _DP_TP_STATUS_A, _DP_TP_STATUS_B) -+#define DP_TP_STATUS_FEC_ENABLE_LIVE (1 << 28) -+#define DP_TP_STATUS_IDLE_DONE (1 << 25) -+#define DP_TP_STATUS_ACT_SENT (1 << 24) -+#define DP_TP_STATUS_MODE_STATUS_MST (1 << 23) -+#define DP_TP_STATUS_AUTOTRAIN_DONE (1 << 12) -+#define DP_TP_STATUS_PAYLOAD_MAPPING_VC2 (3 << 8) -+#define DP_TP_STATUS_PAYLOAD_MAPPING_VC1 (3 << 4) -+#define DP_TP_STATUS_PAYLOAD_MAPPING_VC0 (3 << 0) -+ -+/* DDI Buffer Control */ -+#define _DDI_BUF_CTL_A 0x64000 -+#define _DDI_BUF_CTL_B 0x64100 -+#define DDI_BUF_CTL(port) _MMIO_PORT(port, _DDI_BUF_CTL_A, _DDI_BUF_CTL_B) -+#define DDI_BUF_CTL_ENABLE (1 << 31) -+#define DDI_BUF_TRANS_SELECT(n) ((n) << 24) -+#define DDI_BUF_EMP_MASK (0xf << 24) -+#define DDI_BUF_PORT_REVERSAL (1 << 16) -+#define DDI_BUF_IS_IDLE (1 << 7) -+#define DDI_A_4_LANES (1 << 4) -+#define DDI_PORT_WIDTH(width) (((width) - 1) << 1) -+#define DDI_PORT_WIDTH_MASK (7 << 1) -+#define DDI_PORT_WIDTH_SHIFT 1 -+#define DDI_INIT_DISPLAY_DETECTED (1 << 0) -+ -+/* DDI Buffer Translations */ -+#define _DDI_BUF_TRANS_A 0x64E00 -+#define _DDI_BUF_TRANS_B 0x64E60 -+#define DDI_BUF_TRANS_LO(port, i) _MMIO(_PORT(port, _DDI_BUF_TRANS_A, _DDI_BUF_TRANS_B) + (i) * 8) -+#define DDI_BUF_BALANCE_LEG_ENABLE (1 << 31) -+#define DDI_BUF_TRANS_HI(port, i) _MMIO(_PORT(port, _DDI_BUF_TRANS_A, _DDI_BUF_TRANS_B) + (i) * 8 + 4) -+ -+/* Sideband Interface (SBI) is programmed indirectly, via -+ * SBI_ADDR, which contains the register offset; and SBI_DATA, -+ * which contains the payload */ -+#define SBI_ADDR _MMIO(0xC6000) -+#define SBI_DATA _MMIO(0xC6004) -+#define SBI_CTL_STAT _MMIO(0xC6008) -+#define SBI_CTL_DEST_ICLK (0x0 << 16) -+#define SBI_CTL_DEST_MPHY (0x1 << 16) -+#define SBI_CTL_OP_IORD (0x2 << 8) -+#define SBI_CTL_OP_IOWR (0x3 << 8) -+#define SBI_CTL_OP_CRRD (0x6 << 8) -+#define SBI_CTL_OP_CRWR (0x7 << 8) -+#define SBI_RESPONSE_FAIL (0x1 << 1) -+#define SBI_RESPONSE_SUCCESS (0x0 << 1) -+#define SBI_BUSY (0x1 << 0) -+#define SBI_READY (0x0 << 0) -+ -+/* SBI offsets */ -+#define SBI_SSCDIVINTPHASE 0x0200 -+#define SBI_SSCDIVINTPHASE6 0x0600 -+#define SBI_SSCDIVINTPHASE_DIVSEL_SHIFT 1 -+#define SBI_SSCDIVINTPHASE_DIVSEL_MASK (0x7f << 1) -+#define SBI_SSCDIVINTPHASE_DIVSEL(x) ((x) << 1) -+#define SBI_SSCDIVINTPHASE_INCVAL_SHIFT 8 -+#define SBI_SSCDIVINTPHASE_INCVAL_MASK (0x7f << 8) -+#define SBI_SSCDIVINTPHASE_INCVAL(x) ((x) << 8) -+#define SBI_SSCDIVINTPHASE_DIR(x) ((x) << 15) -+#define SBI_SSCDIVINTPHASE_PROPAGATE (1 << 0) -+#define SBI_SSCDITHPHASE 0x0204 -+#define SBI_SSCCTL 0x020c -+#define SBI_SSCCTL6 0x060C -+#define SBI_SSCCTL_PATHALT (1 << 3) -+#define SBI_SSCCTL_DISABLE (1 << 0) -+#define SBI_SSCAUXDIV6 0x0610 -+#define SBI_SSCAUXDIV_FINALDIV2SEL_SHIFT 4 -+#define SBI_SSCAUXDIV_FINALDIV2SEL_MASK (1 << 4) -+#define SBI_SSCAUXDIV_FINALDIV2SEL(x) ((x) << 4) -+#define SBI_DBUFF0 0x2a00 -+#define SBI_GEN0 0x1f00 -+#define SBI_GEN0_CFG_BUFFENABLE_DISABLE (1 << 0) -+ -+/* LPT PIXCLK_GATE */ -+#define PIXCLK_GATE _MMIO(0xC6020) -+#define PIXCLK_GATE_UNGATE (1 << 0) -+#define PIXCLK_GATE_GATE (0 << 0) -+ -+/* SPLL */ -+#define SPLL_CTL _MMIO(0x46020) -+#define SPLL_PLL_ENABLE (1 << 31) -+#define SPLL_PLL_SSC (1 << 28) -+#define SPLL_PLL_NON_SSC (2 << 28) -+#define SPLL_PLL_LCPLL (3 << 28) -+#define SPLL_PLL_REF_MASK (3 << 28) -+#define SPLL_PLL_FREQ_810MHz (0 << 26) -+#define SPLL_PLL_FREQ_1350MHz (1 << 26) -+#define SPLL_PLL_FREQ_2700MHz (2 << 26) -+#define SPLL_PLL_FREQ_MASK (3 << 26) -+ -+/* WRPLL */ -+#define _WRPLL_CTL1 0x46040 -+#define _WRPLL_CTL2 0x46060 -+#define WRPLL_CTL(pll) _MMIO_PIPE(pll, _WRPLL_CTL1, _WRPLL_CTL2) -+#define WRPLL_PLL_ENABLE (1 << 31) -+#define WRPLL_PLL_SSC (1 << 28) -+#define WRPLL_PLL_NON_SSC (2 << 28) -+#define WRPLL_PLL_LCPLL (3 << 28) -+#define WRPLL_PLL_REF_MASK (3 << 28) -+/* WRPLL divider programming */ -+#define WRPLL_DIVIDER_REFERENCE(x) ((x) << 0) -+#define WRPLL_DIVIDER_REF_MASK (0xff) -+#define WRPLL_DIVIDER_POST(x) ((x) << 8) -+#define WRPLL_DIVIDER_POST_MASK (0x3f << 8) -+#define WRPLL_DIVIDER_POST_SHIFT 8 -+#define WRPLL_DIVIDER_FEEDBACK(x) ((x) << 16) -+#define WRPLL_DIVIDER_FB_SHIFT 16 -+#define WRPLL_DIVIDER_FB_MASK (0xff << 16) -+ -+/* Port clock selection */ -+#define _PORT_CLK_SEL_A 0x46100 -+#define _PORT_CLK_SEL_B 0x46104 -+#define PORT_CLK_SEL(port) _MMIO_PORT(port, _PORT_CLK_SEL_A, _PORT_CLK_SEL_B) -+#define PORT_CLK_SEL_LCPLL_2700 (0 << 29) -+#define PORT_CLK_SEL_LCPLL_1350 (1 << 29) -+#define PORT_CLK_SEL_LCPLL_810 (2 << 29) -+#define PORT_CLK_SEL_SPLL (3 << 29) -+#define PORT_CLK_SEL_WRPLL(pll) (((pll) + 4) << 29) -+#define PORT_CLK_SEL_WRPLL1 (4 << 29) -+#define PORT_CLK_SEL_WRPLL2 (5 << 29) -+#define PORT_CLK_SEL_NONE (7 << 29) -+#define PORT_CLK_SEL_MASK (7 << 29) -+ -+/* On ICL+ this is the same as PORT_CLK_SEL, but all bits change. */ -+#define DDI_CLK_SEL(port) PORT_CLK_SEL(port) -+#define DDI_CLK_SEL_NONE (0x0 << 28) -+#define DDI_CLK_SEL_MG (0x8 << 28) -+#define DDI_CLK_SEL_TBT_162 (0xC << 28) -+#define DDI_CLK_SEL_TBT_270 (0xD << 28) -+#define DDI_CLK_SEL_TBT_540 (0xE << 28) -+#define DDI_CLK_SEL_TBT_810 (0xF << 28) -+#define DDI_CLK_SEL_MASK (0xF << 28) -+ -+/* Transcoder clock selection */ -+#define _TRANS_CLK_SEL_A 0x46140 -+#define _TRANS_CLK_SEL_B 0x46144 -+#define TRANS_CLK_SEL(tran) _MMIO_TRANS(tran, _TRANS_CLK_SEL_A, _TRANS_CLK_SEL_B) -+/* For each transcoder, we need to select the corresponding port clock */ -+#define TRANS_CLK_SEL_DISABLED (0x0 << 29) -+#define TRANS_CLK_SEL_PORT(x) (((x) + 1) << 29) -+ -+#define CDCLK_FREQ _MMIO(0x46200) -+ -+#define _TRANSA_MSA_MISC 0x60410 -+#define _TRANSB_MSA_MISC 0x61410 -+#define _TRANSC_MSA_MISC 0x62410 -+#define _TRANS_EDP_MSA_MISC 0x6f410 -+#define TRANS_MSA_MISC(tran) _MMIO_TRANS2(tran, _TRANSA_MSA_MISC) -+ -+#define TRANS_MSA_SYNC_CLK (1 << 0) -+#define TRANS_MSA_SAMPLING_444 (2 << 1) -+#define TRANS_MSA_CLRSP_YCBCR (2 << 3) -+#define TRANS_MSA_6_BPC (0 << 5) -+#define TRANS_MSA_8_BPC (1 << 5) -+#define TRANS_MSA_10_BPC (2 << 5) -+#define TRANS_MSA_12_BPC (3 << 5) -+#define TRANS_MSA_16_BPC (4 << 5) -+#define TRANS_MSA_CEA_RANGE (1 << 3) -+ -+/* LCPLL Control */ -+#define LCPLL_CTL _MMIO(0x130040) -+#define LCPLL_PLL_DISABLE (1 << 31) -+#define LCPLL_PLL_LOCK (1 << 30) -+#define LCPLL_CLK_FREQ_MASK (3 << 26) -+#define LCPLL_CLK_FREQ_450 (0 << 26) -+#define LCPLL_CLK_FREQ_54O_BDW (1 << 26) -+#define LCPLL_CLK_FREQ_337_5_BDW (2 << 26) -+#define LCPLL_CLK_FREQ_675_BDW (3 << 26) -+#define LCPLL_CD_CLOCK_DISABLE (1 << 25) -+#define LCPLL_ROOT_CD_CLOCK_DISABLE (1 << 24) -+#define LCPLL_CD2X_CLOCK_DISABLE (1 << 23) -+#define LCPLL_POWER_DOWN_ALLOW (1 << 22) -+#define LCPLL_CD_SOURCE_FCLK (1 << 21) -+#define LCPLL_CD_SOURCE_FCLK_DONE (1 << 19) -+ -+/* -+ * SKL Clocks -+ */ -+ -+/* CDCLK_CTL */ -+#define CDCLK_CTL _MMIO(0x46000) -+#define CDCLK_FREQ_SEL_MASK (3 << 26) -+#define CDCLK_FREQ_450_432 (0 << 26) -+#define CDCLK_FREQ_540 (1 << 26) -+#define CDCLK_FREQ_337_308 (2 << 26) -+#define CDCLK_FREQ_675_617 (3 << 26) -+#define BXT_CDCLK_CD2X_DIV_SEL_MASK (3 << 22) -+#define BXT_CDCLK_CD2X_DIV_SEL_1 (0 << 22) -+#define BXT_CDCLK_CD2X_DIV_SEL_1_5 (1 << 22) -+#define BXT_CDCLK_CD2X_DIV_SEL_2 (2 << 22) -+#define BXT_CDCLK_CD2X_DIV_SEL_4 (3 << 22) -+#define BXT_CDCLK_CD2X_PIPE(pipe) ((pipe) << 20) -+#define CDCLK_DIVMUX_CD_OVERRIDE (1 << 19) -+#define BXT_CDCLK_CD2X_PIPE_NONE BXT_CDCLK_CD2X_PIPE(3) -+#define ICL_CDCLK_CD2X_PIPE_NONE (7 << 19) -+#define BXT_CDCLK_SSA_PRECHARGE_ENABLE (1 << 16) -+#define CDCLK_FREQ_DECIMAL_MASK (0x7ff) -+ -+/* LCPLL_CTL */ -+#define LCPLL1_CTL _MMIO(0x46010) -+#define LCPLL2_CTL _MMIO(0x46014) -+#define LCPLL_PLL_ENABLE (1 << 31) -+ -+/* DPLL control1 */ -+#define DPLL_CTRL1 _MMIO(0x6C058) -+#define DPLL_CTRL1_HDMI_MODE(id) (1 << ((id) * 6 + 5)) -+#define DPLL_CTRL1_SSC(id) (1 << ((id) * 6 + 4)) -+#define DPLL_CTRL1_LINK_RATE_MASK(id) (7 << ((id) * 6 + 1)) -+#define DPLL_CTRL1_LINK_RATE_SHIFT(id) ((id) * 6 + 1) -+#define DPLL_CTRL1_LINK_RATE(linkrate, id) ((linkrate) << ((id) * 6 + 1)) -+#define DPLL_CTRL1_OVERRIDE(id) (1 << ((id) * 6)) -+#define DPLL_CTRL1_LINK_RATE_2700 0 -+#define DPLL_CTRL1_LINK_RATE_1350 1 -+#define DPLL_CTRL1_LINK_RATE_810 2 -+#define DPLL_CTRL1_LINK_RATE_1620 3 -+#define DPLL_CTRL1_LINK_RATE_1080 4 -+#define DPLL_CTRL1_LINK_RATE_2160 5 -+ -+/* DPLL control2 */ -+#define DPLL_CTRL2 _MMIO(0x6C05C) -+#define DPLL_CTRL2_DDI_CLK_OFF(port) (1 << ((port) + 15)) -+#define DPLL_CTRL2_DDI_CLK_SEL_MASK(port) (3 << ((port) * 3 + 1)) -+#define DPLL_CTRL2_DDI_CLK_SEL_SHIFT(port) ((port) * 3 + 1) -+#define DPLL_CTRL2_DDI_CLK_SEL(clk, port) ((clk) << ((port) * 3 + 1)) -+#define DPLL_CTRL2_DDI_SEL_OVERRIDE(port) (1 << ((port) * 3)) -+ -+/* DPLL Status */ -+#define DPLL_STATUS _MMIO(0x6C060) -+#define DPLL_LOCK(id) (1 << ((id) * 8)) -+ -+/* DPLL cfg */ -+#define _DPLL1_CFGCR1 0x6C040 -+#define _DPLL2_CFGCR1 0x6C048 -+#define _DPLL3_CFGCR1 0x6C050 -+#define DPLL_CFGCR1_FREQ_ENABLE (1 << 31) -+#define DPLL_CFGCR1_DCO_FRACTION_MASK (0x7fff << 9) -+#define DPLL_CFGCR1_DCO_FRACTION(x) ((x) << 9) -+#define DPLL_CFGCR1_DCO_INTEGER_MASK (0x1ff) -+ -+#define _DPLL1_CFGCR2 0x6C044 -+#define _DPLL2_CFGCR2 0x6C04C -+#define _DPLL3_CFGCR2 0x6C054 -+#define DPLL_CFGCR2_QDIV_RATIO_MASK (0xff << 8) -+#define DPLL_CFGCR2_QDIV_RATIO(x) ((x) << 8) -+#define DPLL_CFGCR2_QDIV_MODE(x) ((x) << 7) -+#define DPLL_CFGCR2_KDIV_MASK (3 << 5) -+#define DPLL_CFGCR2_KDIV(x) ((x) << 5) -+#define DPLL_CFGCR2_KDIV_5 (0 << 5) -+#define DPLL_CFGCR2_KDIV_2 (1 << 5) -+#define DPLL_CFGCR2_KDIV_3 (2 << 5) -+#define DPLL_CFGCR2_KDIV_1 (3 << 5) -+#define DPLL_CFGCR2_PDIV_MASK (7 << 2) -+#define DPLL_CFGCR2_PDIV(x) ((x) << 2) -+#define DPLL_CFGCR2_PDIV_1 (0 << 2) -+#define DPLL_CFGCR2_PDIV_2 (1 << 2) -+#define DPLL_CFGCR2_PDIV_3 (2 << 2) -+#define DPLL_CFGCR2_PDIV_7 (4 << 2) -+#define DPLL_CFGCR2_CENTRAL_FREQ_MASK (3) -+ -+#define DPLL_CFGCR1(id) _MMIO_PIPE((id) - SKL_DPLL1, _DPLL1_CFGCR1, _DPLL2_CFGCR1) -+#define DPLL_CFGCR2(id) _MMIO_PIPE((id) - SKL_DPLL1, _DPLL1_CFGCR2, _DPLL2_CFGCR2) -+ -+/* -+ * CNL Clocks -+ */ -+#define DPCLKA_CFGCR0 _MMIO(0x6C200) -+#define DPCLKA_CFGCR0_ICL _MMIO(0x164280) -+#define DPCLKA_CFGCR0_DDI_CLK_OFF(port) (1 << ((port) == PORT_F ? 23 : \ -+ (port) + 10)) -+#define ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(port) (1 << ((port) + 10)) -+#define ICL_DPCLKA_CFGCR0_TC_CLK_OFF(tc_port) (1 << ((tc_port) == PORT_TC4 ? \ -+ 21 : (tc_port) + 12)) -+#define DPCLKA_CFGCR0_DDI_CLK_SEL_SHIFT(port) ((port) == PORT_F ? 21 : \ -+ (port) * 2) -+#define DPCLKA_CFGCR0_DDI_CLK_SEL_MASK(port) (3 << DPCLKA_CFGCR0_DDI_CLK_SEL_SHIFT(port)) -+#define DPCLKA_CFGCR0_DDI_CLK_SEL(pll, port) ((pll) << DPCLKA_CFGCR0_DDI_CLK_SEL_SHIFT(port)) -+ -+/* CNL PLL */ -+#define DPLL0_ENABLE 0x46010 -+#define DPLL1_ENABLE 0x46014 -+#define PLL_ENABLE (1 << 31) -+#define PLL_LOCK (1 << 30) -+#define PLL_POWER_ENABLE (1 << 27) -+#define PLL_POWER_STATE (1 << 26) -+#define CNL_DPLL_ENABLE(pll) _MMIO_PLL(pll, DPLL0_ENABLE, DPLL1_ENABLE) -+ -+#define TBT_PLL_ENABLE _MMIO(0x46020) -+ -+#define _MG_PLL1_ENABLE 0x46030 -+#define _MG_PLL2_ENABLE 0x46034 -+#define _MG_PLL3_ENABLE 0x46038 -+#define _MG_PLL4_ENABLE 0x4603C -+/* Bits are the same as DPLL0_ENABLE */ -+#define MG_PLL_ENABLE(tc_port) _MMIO_PORT((tc_port), _MG_PLL1_ENABLE, \ -+ _MG_PLL2_ENABLE) -+ -+#define _MG_REFCLKIN_CTL_PORT1 0x16892C -+#define _MG_REFCLKIN_CTL_PORT2 0x16992C -+#define _MG_REFCLKIN_CTL_PORT3 0x16A92C -+#define _MG_REFCLKIN_CTL_PORT4 0x16B92C -+#define MG_REFCLKIN_CTL_OD_2_MUX(x) ((x) << 8) -+#define MG_REFCLKIN_CTL_OD_2_MUX_MASK (0x7 << 8) -+#define MG_REFCLKIN_CTL(tc_port) _MMIO_PORT((tc_port), \ -+ _MG_REFCLKIN_CTL_PORT1, \ -+ _MG_REFCLKIN_CTL_PORT2) -+ -+#define _MG_CLKTOP2_CORECLKCTL1_PORT1 0x1688D8 -+#define _MG_CLKTOP2_CORECLKCTL1_PORT2 0x1698D8 -+#define _MG_CLKTOP2_CORECLKCTL1_PORT3 0x16A8D8 -+#define _MG_CLKTOP2_CORECLKCTL1_PORT4 0x16B8D8 -+#define MG_CLKTOP2_CORECLKCTL1_B_DIVRATIO(x) ((x) << 16) -+#define MG_CLKTOP2_CORECLKCTL1_B_DIVRATIO_MASK (0xff << 16) -+#define MG_CLKTOP2_CORECLKCTL1_A_DIVRATIO(x) ((x) << 8) -+#define MG_CLKTOP2_CORECLKCTL1_A_DIVRATIO_MASK (0xff << 8) -+#define MG_CLKTOP2_CORECLKCTL1(tc_port) _MMIO_PORT((tc_port), \ -+ _MG_CLKTOP2_CORECLKCTL1_PORT1, \ -+ _MG_CLKTOP2_CORECLKCTL1_PORT2) -+ -+#define _MG_CLKTOP2_HSCLKCTL_PORT1 0x1688D4 -+#define _MG_CLKTOP2_HSCLKCTL_PORT2 0x1698D4 -+#define _MG_CLKTOP2_HSCLKCTL_PORT3 0x16A8D4 -+#define _MG_CLKTOP2_HSCLKCTL_PORT4 0x16B8D4 -+#define MG_CLKTOP2_HSCLKCTL_CORE_INPUTSEL(x) ((x) << 16) -+#define MG_CLKTOP2_HSCLKCTL_CORE_INPUTSEL_MASK (0x1 << 16) -+#define MG_CLKTOP2_HSCLKCTL_TLINEDRV_CLKSEL(x) ((x) << 14) -+#define MG_CLKTOP2_HSCLKCTL_TLINEDRV_CLKSEL_MASK (0x3 << 14) -+#define MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_MASK (0x3 << 12) -+#define MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_2 (0 << 12) -+#define MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_3 (1 << 12) -+#define MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_5 (2 << 12) -+#define MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_7 (3 << 12) -+#define MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO(x) ((x) << 8) -+#define MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO_SHIFT 8 -+#define MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO_MASK (0xf << 8) -+#define MG_CLKTOP2_HSCLKCTL(tc_port) _MMIO_PORT((tc_port), \ -+ _MG_CLKTOP2_HSCLKCTL_PORT1, \ -+ _MG_CLKTOP2_HSCLKCTL_PORT2) -+ -+#define _MG_PLL_DIV0_PORT1 0x168A00 -+#define _MG_PLL_DIV0_PORT2 0x169A00 -+#define _MG_PLL_DIV0_PORT3 0x16AA00 -+#define _MG_PLL_DIV0_PORT4 0x16BA00 -+#define MG_PLL_DIV0_FRACNEN_H (1 << 30) -+#define MG_PLL_DIV0_FBDIV_FRAC_MASK (0x3fffff << 8) -+#define MG_PLL_DIV0_FBDIV_FRAC_SHIFT 8 -+#define MG_PLL_DIV0_FBDIV_FRAC(x) ((x) << 8) -+#define MG_PLL_DIV0_FBDIV_INT_MASK (0xff << 0) -+#define MG_PLL_DIV0_FBDIV_INT(x) ((x) << 0) -+#define MG_PLL_DIV0(tc_port) _MMIO_PORT((tc_port), _MG_PLL_DIV0_PORT1, \ -+ _MG_PLL_DIV0_PORT2) -+ -+#define _MG_PLL_DIV1_PORT1 0x168A04 -+#define _MG_PLL_DIV1_PORT2 0x169A04 -+#define _MG_PLL_DIV1_PORT3 0x16AA04 -+#define _MG_PLL_DIV1_PORT4 0x16BA04 -+#define MG_PLL_DIV1_IREF_NDIVRATIO(x) ((x) << 16) -+#define MG_PLL_DIV1_DITHER_DIV_1 (0 << 12) -+#define MG_PLL_DIV1_DITHER_DIV_2 (1 << 12) -+#define MG_PLL_DIV1_DITHER_DIV_4 (2 << 12) -+#define MG_PLL_DIV1_DITHER_DIV_8 (3 << 12) -+#define MG_PLL_DIV1_NDIVRATIO(x) ((x) << 4) -+#define MG_PLL_DIV1_FBPREDIV_MASK (0xf << 0) -+#define MG_PLL_DIV1_FBPREDIV(x) ((x) << 0) -+#define MG_PLL_DIV1(tc_port) _MMIO_PORT((tc_port), _MG_PLL_DIV1_PORT1, \ -+ _MG_PLL_DIV1_PORT2) -+ -+#define _MG_PLL_LF_PORT1 0x168A08 -+#define _MG_PLL_LF_PORT2 0x169A08 -+#define _MG_PLL_LF_PORT3 0x16AA08 -+#define _MG_PLL_LF_PORT4 0x16BA08 -+#define MG_PLL_LF_TDCTARGETCNT(x) ((x) << 24) -+#define MG_PLL_LF_AFCCNTSEL_256 (0 << 20) -+#define MG_PLL_LF_AFCCNTSEL_512 (1 << 20) -+#define MG_PLL_LF_GAINCTRL(x) ((x) << 16) -+#define MG_PLL_LF_INT_COEFF(x) ((x) << 8) -+#define MG_PLL_LF_PROP_COEFF(x) ((x) << 0) -+#define MG_PLL_LF(tc_port) _MMIO_PORT((tc_port), _MG_PLL_LF_PORT1, \ -+ _MG_PLL_LF_PORT2) -+ -+#define _MG_PLL_FRAC_LOCK_PORT1 0x168A0C -+#define _MG_PLL_FRAC_LOCK_PORT2 0x169A0C -+#define _MG_PLL_FRAC_LOCK_PORT3 0x16AA0C -+#define _MG_PLL_FRAC_LOCK_PORT4 0x16BA0C -+#define MG_PLL_FRAC_LOCK_TRUELOCK_CRIT_32 (1 << 18) -+#define MG_PLL_FRAC_LOCK_EARLYLOCK_CRIT_32 (1 << 16) -+#define MG_PLL_FRAC_LOCK_LOCKTHRESH(x) ((x) << 11) -+#define MG_PLL_FRAC_LOCK_DCODITHEREN (1 << 10) -+#define MG_PLL_FRAC_LOCK_FEEDFWRDCAL_EN (1 << 8) -+#define MG_PLL_FRAC_LOCK_FEEDFWRDGAIN(x) ((x) << 0) -+#define MG_PLL_FRAC_LOCK(tc_port) _MMIO_PORT((tc_port), \ -+ _MG_PLL_FRAC_LOCK_PORT1, \ -+ _MG_PLL_FRAC_LOCK_PORT2) -+ -+#define _MG_PLL_SSC_PORT1 0x168A10 -+#define _MG_PLL_SSC_PORT2 0x169A10 -+#define _MG_PLL_SSC_PORT3 0x16AA10 -+#define _MG_PLL_SSC_PORT4 0x16BA10 -+#define MG_PLL_SSC_EN (1 << 28) -+#define MG_PLL_SSC_TYPE(x) ((x) << 26) -+#define MG_PLL_SSC_STEPLENGTH(x) ((x) << 16) -+#define MG_PLL_SSC_STEPNUM(x) ((x) << 10) -+#define MG_PLL_SSC_FLLEN (1 << 9) -+#define MG_PLL_SSC_STEPSIZE(x) ((x) << 0) -+#define MG_PLL_SSC(tc_port) _MMIO_PORT((tc_port), _MG_PLL_SSC_PORT1, \ -+ _MG_PLL_SSC_PORT2) -+ -+#define _MG_PLL_BIAS_PORT1 0x168A14 -+#define _MG_PLL_BIAS_PORT2 0x169A14 -+#define _MG_PLL_BIAS_PORT3 0x16AA14 -+#define _MG_PLL_BIAS_PORT4 0x16BA14 -+#define MG_PLL_BIAS_BIAS_GB_SEL(x) ((x) << 30) -+#define MG_PLL_BIAS_BIAS_GB_SEL_MASK (0x3 << 30) -+#define MG_PLL_BIAS_INIT_DCOAMP(x) ((x) << 24) -+#define MG_PLL_BIAS_INIT_DCOAMP_MASK (0x3f << 24) -+#define MG_PLL_BIAS_BIAS_BONUS(x) ((x) << 16) -+#define MG_PLL_BIAS_BIAS_BONUS_MASK (0xff << 16) -+#define MG_PLL_BIAS_BIASCAL_EN (1 << 15) -+#define MG_PLL_BIAS_CTRIM(x) ((x) << 8) -+#define MG_PLL_BIAS_CTRIM_MASK (0x1f << 8) -+#define MG_PLL_BIAS_VREF_RDAC(x) ((x) << 5) -+#define MG_PLL_BIAS_VREF_RDAC_MASK (0x7 << 5) -+#define MG_PLL_BIAS_IREFTRIM(x) ((x) << 0) -+#define MG_PLL_BIAS_IREFTRIM_MASK (0x1f << 0) -+#define MG_PLL_BIAS(tc_port) _MMIO_PORT((tc_port), _MG_PLL_BIAS_PORT1, \ -+ _MG_PLL_BIAS_PORT2) -+ -+#define _MG_PLL_TDC_COLDST_BIAS_PORT1 0x168A18 -+#define _MG_PLL_TDC_COLDST_BIAS_PORT2 0x169A18 -+#define _MG_PLL_TDC_COLDST_BIAS_PORT3 0x16AA18 -+#define _MG_PLL_TDC_COLDST_BIAS_PORT4 0x16BA18 -+#define MG_PLL_TDC_COLDST_IREFINT_EN (1 << 27) -+#define MG_PLL_TDC_COLDST_REFBIAS_START_PULSE_W(x) ((x) << 17) -+#define MG_PLL_TDC_COLDST_COLDSTART (1 << 16) -+#define MG_PLL_TDC_TDCOVCCORR_EN (1 << 2) -+#define MG_PLL_TDC_TDCSEL(x) ((x) << 0) -+#define MG_PLL_TDC_COLDST_BIAS(tc_port) _MMIO_PORT((tc_port), \ -+ _MG_PLL_TDC_COLDST_BIAS_PORT1, \ -+ _MG_PLL_TDC_COLDST_BIAS_PORT2) -+ -+#define _CNL_DPLL0_CFGCR0 0x6C000 -+#define _CNL_DPLL1_CFGCR0 0x6C080 -+#define DPLL_CFGCR0_HDMI_MODE (1 << 30) -+#define DPLL_CFGCR0_SSC_ENABLE (1 << 29) -+#define DPLL_CFGCR0_SSC_ENABLE_ICL (1 << 25) -+#define DPLL_CFGCR0_LINK_RATE_MASK (0xf << 25) -+#define DPLL_CFGCR0_LINK_RATE_2700 (0 << 25) -+#define DPLL_CFGCR0_LINK_RATE_1350 (1 << 25) -+#define DPLL_CFGCR0_LINK_RATE_810 (2 << 25) -+#define DPLL_CFGCR0_LINK_RATE_1620 (3 << 25) -+#define DPLL_CFGCR0_LINK_RATE_1080 (4 << 25) -+#define DPLL_CFGCR0_LINK_RATE_2160 (5 << 25) -+#define DPLL_CFGCR0_LINK_RATE_3240 (6 << 25) -+#define DPLL_CFGCR0_LINK_RATE_4050 (7 << 25) -+#define DPLL_CFGCR0_DCO_FRACTION_MASK (0x7fff << 10) -+#define DPLL_CFGCR0_DCO_FRACTION_SHIFT (10) -+#define DPLL_CFGCR0_DCO_FRACTION(x) ((x) << 10) -+#define DPLL_CFGCR0_DCO_INTEGER_MASK (0x3ff) -+#define CNL_DPLL_CFGCR0(pll) _MMIO_PLL(pll, _CNL_DPLL0_CFGCR0, _CNL_DPLL1_CFGCR0) -+ -+#define _CNL_DPLL0_CFGCR1 0x6C004 -+#define _CNL_DPLL1_CFGCR1 0x6C084 -+#define DPLL_CFGCR1_QDIV_RATIO_MASK (0xff << 10) -+#define DPLL_CFGCR1_QDIV_RATIO_SHIFT (10) -+#define DPLL_CFGCR1_QDIV_RATIO(x) ((x) << 10) -+#define DPLL_CFGCR1_QDIV_MODE_SHIFT (9) -+#define DPLL_CFGCR1_QDIV_MODE(x) ((x) << 9) -+#define DPLL_CFGCR1_KDIV_MASK (7 << 6) -+#define DPLL_CFGCR1_KDIV_SHIFT (6) -+#define DPLL_CFGCR1_KDIV(x) ((x) << 6) -+#define DPLL_CFGCR1_KDIV_1 (1 << 6) -+#define DPLL_CFGCR1_KDIV_2 (2 << 6) -+#define DPLL_CFGCR1_KDIV_3 (4 << 6) -+#define DPLL_CFGCR1_PDIV_MASK (0xf << 2) -+#define DPLL_CFGCR1_PDIV_SHIFT (2) -+#define DPLL_CFGCR1_PDIV(x) ((x) << 2) -+#define DPLL_CFGCR1_PDIV_2 (1 << 2) -+#define DPLL_CFGCR1_PDIV_3 (2 << 2) -+#define DPLL_CFGCR1_PDIV_5 (4 << 2) -+#define DPLL_CFGCR1_PDIV_7 (8 << 2) -+#define DPLL_CFGCR1_CENTRAL_FREQ (3 << 0) -+#define DPLL_CFGCR1_CENTRAL_FREQ_8400 (3 << 0) -+#define CNL_DPLL_CFGCR1(pll) _MMIO_PLL(pll, _CNL_DPLL0_CFGCR1, _CNL_DPLL1_CFGCR1) -+ -+#define _ICL_DPLL0_CFGCR0 0x164000 -+#define _ICL_DPLL1_CFGCR0 0x164080 -+#define ICL_DPLL_CFGCR0(pll) _MMIO_PLL(pll, _ICL_DPLL0_CFGCR0, \ -+ _ICL_DPLL1_CFGCR0) -+ -+#define _ICL_DPLL0_CFGCR1 0x164004 -+#define _ICL_DPLL1_CFGCR1 0x164084 -+#define ICL_DPLL_CFGCR1(pll) _MMIO_PLL(pll, _ICL_DPLL0_CFGCR1, \ -+ _ICL_DPLL1_CFGCR1) -+ -+/* BXT display engine PLL */ -+#define BXT_DE_PLL_CTL _MMIO(0x6d000) -+#define BXT_DE_PLL_RATIO(x) (x) /* {60,65,100} * 19.2MHz */ -+#define BXT_DE_PLL_RATIO_MASK 0xff -+ -+#define BXT_DE_PLL_ENABLE _MMIO(0x46070) -+#define BXT_DE_PLL_PLL_ENABLE (1 << 31) -+#define BXT_DE_PLL_LOCK (1 << 30) -+#define CNL_CDCLK_PLL_RATIO(x) (x) -+#define CNL_CDCLK_PLL_RATIO_MASK 0xff -+ -+/* GEN9 DC */ -+#define DC_STATE_EN _MMIO(0x45504) -+#define DC_STATE_DISABLE 0 -+#define DC_STATE_EN_UPTO_DC5 (1 << 0) -+#define DC_STATE_EN_DC9 (1 << 3) -+#define DC_STATE_EN_UPTO_DC6 (2 << 0) -+#define DC_STATE_EN_UPTO_DC5_DC6_MASK 0x3 -+ -+#define DC_STATE_DEBUG _MMIO(0x45520) -+#define DC_STATE_DEBUG_MASK_CORES (1 << 0) -+#define DC_STATE_DEBUG_MASK_MEMORY_UP (1 << 1) -+ -+#define BXT_P_CR_MC_BIOS_REQ_0_0_0 _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x7114) -+#define BXT_REQ_DATA_MASK 0x3F -+#define BXT_DRAM_CHANNEL_ACTIVE_SHIFT 12 -+#define BXT_DRAM_CHANNEL_ACTIVE_MASK (0xF << 12) -+#define BXT_MEMORY_FREQ_MULTIPLIER_HZ 133333333 -+ -+#define BXT_D_CR_DRP0_DUNIT8 0x1000 -+#define BXT_D_CR_DRP0_DUNIT9 0x1200 -+#define BXT_D_CR_DRP0_DUNIT_START 8 -+#define BXT_D_CR_DRP0_DUNIT_END 11 -+#define BXT_D_CR_DRP0_DUNIT(x) _MMIO(MCHBAR_MIRROR_BASE_SNB + \ -+ _PICK_EVEN((x) - 8, BXT_D_CR_DRP0_DUNIT8,\ -+ BXT_D_CR_DRP0_DUNIT9)) -+#define BXT_DRAM_RANK_MASK 0x3 -+#define BXT_DRAM_RANK_SINGLE 0x1 -+#define BXT_DRAM_RANK_DUAL 0x3 -+#define BXT_DRAM_WIDTH_MASK (0x3 << 4) -+#define BXT_DRAM_WIDTH_SHIFT 4 -+#define BXT_DRAM_WIDTH_X8 (0x0 << 4) -+#define BXT_DRAM_WIDTH_X16 (0x1 << 4) -+#define BXT_DRAM_WIDTH_X32 (0x2 << 4) -+#define BXT_DRAM_WIDTH_X64 (0x3 << 4) -+#define BXT_DRAM_SIZE_MASK (0x7 << 6) -+#define BXT_DRAM_SIZE_SHIFT 6 -+#define BXT_DRAM_SIZE_4GBIT (0x0 << 6) -+#define BXT_DRAM_SIZE_6GBIT (0x1 << 6) -+#define BXT_DRAM_SIZE_8GBIT (0x2 << 6) -+#define BXT_DRAM_SIZE_12GBIT (0x3 << 6) -+#define BXT_DRAM_SIZE_16GBIT (0x4 << 6) -+#define BXT_DRAM_TYPE_MASK (0x7 << 22) -+#define BXT_DRAM_TYPE_SHIFT 22 -+#define BXT_DRAM_TYPE_DDR3 (0x0 << 22) -+#define BXT_DRAM_TYPE_LPDDR3 (0x1 << 22) -+#define BXT_DRAM_TYPE_LPDDR4 (0x2 << 22) -+#define BXT_DRAM_TYPE_DDR4 (0x4 << 22) -+ -+#define SKL_MEMORY_FREQ_MULTIPLIER_HZ 266666666 -+#define SKL_MC_BIOS_DATA_0_0_0_MCHBAR_PCU _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x5E04) -+#define SKL_REQ_DATA_MASK (0xF << 0) -+ -+#define SKL_MAD_INTER_CHANNEL_0_0_0_MCHBAR_MCMAIN _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x5000) -+#define SKL_DRAM_DDR_TYPE_MASK (0x3 << 0) -+#define SKL_DRAM_DDR_TYPE_DDR4 (0 << 0) -+#define SKL_DRAM_DDR_TYPE_DDR3 (1 << 0) -+#define SKL_DRAM_DDR_TYPE_LPDDR3 (2 << 0) -+#define SKL_DRAM_DDR_TYPE_LPDDR4 (3 << 0) -+ -+#define SKL_MAD_DIMM_CH0_0_0_0_MCHBAR_MCMAIN _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x500C) -+#define SKL_MAD_DIMM_CH1_0_0_0_MCHBAR_MCMAIN _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x5010) -+#define SKL_DRAM_S_SHIFT 16 -+#define SKL_DRAM_SIZE_MASK 0x3F -+#define SKL_DRAM_WIDTH_MASK (0x3 << 8) -+#define SKL_DRAM_WIDTH_SHIFT 8 -+#define SKL_DRAM_WIDTH_X8 (0x0 << 8) -+#define SKL_DRAM_WIDTH_X16 (0x1 << 8) -+#define SKL_DRAM_WIDTH_X32 (0x2 << 8) -+#define SKL_DRAM_RANK_MASK (0x1 << 10) -+#define SKL_DRAM_RANK_SHIFT 10 -+#define SKL_DRAM_RANK_1 (0x0 << 10) -+#define SKL_DRAM_RANK_2 (0x1 << 10) -+#define SKL_DRAM_RANK_MASK (0x1 << 10) -+#define CNL_DRAM_SIZE_MASK 0x7F -+#define CNL_DRAM_WIDTH_MASK (0x3 << 7) -+#define CNL_DRAM_WIDTH_SHIFT 7 -+#define CNL_DRAM_WIDTH_X8 (0x0 << 7) -+#define CNL_DRAM_WIDTH_X16 (0x1 << 7) -+#define CNL_DRAM_WIDTH_X32 (0x2 << 7) -+#define CNL_DRAM_RANK_MASK (0x3 << 9) -+#define CNL_DRAM_RANK_SHIFT 9 -+#define CNL_DRAM_RANK_1 (0x0 << 9) -+#define CNL_DRAM_RANK_2 (0x1 << 9) -+#define CNL_DRAM_RANK_3 (0x2 << 9) -+#define CNL_DRAM_RANK_4 (0x3 << 9) -+ -+/* Please see hsw_read_dcomp() and hsw_write_dcomp() before using this register, -+ * since on HSW we can't write to it using I915_WRITE. */ -+#define D_COMP_HSW _MMIO(MCHBAR_MIRROR_BASE_SNB + 0x5F0C) -+#define D_COMP_BDW _MMIO(0x138144) -+#define D_COMP_RCOMP_IN_PROGRESS (1 << 9) -+#define D_COMP_COMP_FORCE (1 << 8) -+#define D_COMP_COMP_DISABLE (1 << 0) -+ -+/* Pipe WM_LINETIME - watermark line time */ -+#define _PIPE_WM_LINETIME_A 0x45270 -+#define _PIPE_WM_LINETIME_B 0x45274 -+#define PIPE_WM_LINETIME(pipe) _MMIO_PIPE(pipe, _PIPE_WM_LINETIME_A, _PIPE_WM_LINETIME_B) -+#define PIPE_WM_LINETIME_MASK (0x1ff) -+#define PIPE_WM_LINETIME_TIME(x) ((x)) -+#define PIPE_WM_LINETIME_IPS_LINETIME_MASK (0x1ff << 16) -+#define PIPE_WM_LINETIME_IPS_LINETIME(x) ((x) << 16) -+ -+/* SFUSE_STRAP */ -+#define SFUSE_STRAP _MMIO(0xc2014) -+#define SFUSE_STRAP_FUSE_LOCK (1 << 13) -+#define SFUSE_STRAP_RAW_FREQUENCY (1 << 8) -+#define SFUSE_STRAP_DISPLAY_DISABLED (1 << 7) -+#define SFUSE_STRAP_CRT_DISABLED (1 << 6) -+#define SFUSE_STRAP_DDIF_DETECTED (1 << 3) -+#define SFUSE_STRAP_DDIB_DETECTED (1 << 2) -+#define SFUSE_STRAP_DDIC_DETECTED (1 << 1) -+#define SFUSE_STRAP_DDID_DETECTED (1 << 0) -+ -+#define WM_MISC _MMIO(0x45260) -+#define WM_MISC_DATA_PARTITION_5_6 (1 << 0) -+ -+#define WM_DBG _MMIO(0x45280) -+#define WM_DBG_DISALLOW_MULTIPLE_LP (1 << 0) -+#define WM_DBG_DISALLOW_MAXFIFO (1 << 1) -+#define WM_DBG_DISALLOW_SPRITE (1 << 2) -+ -+/* pipe CSC */ -+#define _PIPE_A_CSC_COEFF_RY_GY 0x49010 -+#define _PIPE_A_CSC_COEFF_BY 0x49014 -+#define _PIPE_A_CSC_COEFF_RU_GU 0x49018 -+#define _PIPE_A_CSC_COEFF_BU 0x4901c -+#define _PIPE_A_CSC_COEFF_RV_GV 0x49020 -+#define _PIPE_A_CSC_COEFF_BV 0x49024 -+ -+#define _PIPE_A_CSC_MODE 0x49028 -+#define ICL_CSC_ENABLE (1 << 31) -+#define ICL_OUTPUT_CSC_ENABLE (1 << 30) -+#define CSC_BLACK_SCREEN_OFFSET (1 << 2) -+#define CSC_POSITION_BEFORE_GAMMA (1 << 1) -+#define CSC_MODE_YUV_TO_RGB (1 << 0) -+ -+#define _PIPE_A_CSC_PREOFF_HI 0x49030 -+#define _PIPE_A_CSC_PREOFF_ME 0x49034 -+#define _PIPE_A_CSC_PREOFF_LO 0x49038 -+#define _PIPE_A_CSC_POSTOFF_HI 0x49040 -+#define _PIPE_A_CSC_POSTOFF_ME 0x49044 -+#define _PIPE_A_CSC_POSTOFF_LO 0x49048 -+ -+#define _PIPE_B_CSC_COEFF_RY_GY 0x49110 -+#define _PIPE_B_CSC_COEFF_BY 0x49114 -+#define _PIPE_B_CSC_COEFF_RU_GU 0x49118 -+#define _PIPE_B_CSC_COEFF_BU 0x4911c -+#define _PIPE_B_CSC_COEFF_RV_GV 0x49120 -+#define _PIPE_B_CSC_COEFF_BV 0x49124 -+#define _PIPE_B_CSC_MODE 0x49128 -+#define _PIPE_B_CSC_PREOFF_HI 0x49130 -+#define _PIPE_B_CSC_PREOFF_ME 0x49134 -+#define _PIPE_B_CSC_PREOFF_LO 0x49138 -+#define _PIPE_B_CSC_POSTOFF_HI 0x49140 -+#define _PIPE_B_CSC_POSTOFF_ME 0x49144 -+#define _PIPE_B_CSC_POSTOFF_LO 0x49148 -+ -+#define PIPE_CSC_COEFF_RY_GY(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_COEFF_RY_GY, _PIPE_B_CSC_COEFF_RY_GY) -+#define PIPE_CSC_COEFF_BY(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_COEFF_BY, _PIPE_B_CSC_COEFF_BY) -+#define PIPE_CSC_COEFF_RU_GU(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_COEFF_RU_GU, _PIPE_B_CSC_COEFF_RU_GU) -+#define PIPE_CSC_COEFF_BU(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_COEFF_BU, _PIPE_B_CSC_COEFF_BU) -+#define PIPE_CSC_COEFF_RV_GV(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_COEFF_RV_GV, _PIPE_B_CSC_COEFF_RV_GV) -+#define PIPE_CSC_COEFF_BV(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_COEFF_BV, _PIPE_B_CSC_COEFF_BV) -+#define PIPE_CSC_MODE(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_MODE, _PIPE_B_CSC_MODE) -+#define PIPE_CSC_PREOFF_HI(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_PREOFF_HI, _PIPE_B_CSC_PREOFF_HI) -+#define PIPE_CSC_PREOFF_ME(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_PREOFF_ME, _PIPE_B_CSC_PREOFF_ME) -+#define PIPE_CSC_PREOFF_LO(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_PREOFF_LO, _PIPE_B_CSC_PREOFF_LO) -+#define PIPE_CSC_POSTOFF_HI(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_POSTOFF_HI, _PIPE_B_CSC_POSTOFF_HI) -+#define PIPE_CSC_POSTOFF_ME(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_POSTOFF_ME, _PIPE_B_CSC_POSTOFF_ME) -+#define PIPE_CSC_POSTOFF_LO(pipe) _MMIO_PIPE(pipe, _PIPE_A_CSC_POSTOFF_LO, _PIPE_B_CSC_POSTOFF_LO) -+ -+/* Pipe Output CSC */ -+#define _PIPE_A_OUTPUT_CSC_COEFF_RY_GY 0x49050 -+#define _PIPE_A_OUTPUT_CSC_COEFF_BY 0x49054 -+#define _PIPE_A_OUTPUT_CSC_COEFF_RU_GU 0x49058 -+#define _PIPE_A_OUTPUT_CSC_COEFF_BU 0x4905c -+#define _PIPE_A_OUTPUT_CSC_COEFF_RV_GV 0x49060 -+#define _PIPE_A_OUTPUT_CSC_COEFF_BV 0x49064 -+#define _PIPE_A_OUTPUT_CSC_PREOFF_HI 0x49068 -+#define _PIPE_A_OUTPUT_CSC_PREOFF_ME 0x4906c -+#define _PIPE_A_OUTPUT_CSC_PREOFF_LO 0x49070 -+#define _PIPE_A_OUTPUT_CSC_POSTOFF_HI 0x49074 -+#define _PIPE_A_OUTPUT_CSC_POSTOFF_ME 0x49078 -+#define _PIPE_A_OUTPUT_CSC_POSTOFF_LO 0x4907c -+ -+#define _PIPE_B_OUTPUT_CSC_COEFF_RY_GY 0x49150 -+#define _PIPE_B_OUTPUT_CSC_COEFF_BY 0x49154 -+#define _PIPE_B_OUTPUT_CSC_COEFF_RU_GU 0x49158 -+#define _PIPE_B_OUTPUT_CSC_COEFF_BU 0x4915c -+#define _PIPE_B_OUTPUT_CSC_COEFF_RV_GV 0x49160 -+#define _PIPE_B_OUTPUT_CSC_COEFF_BV 0x49164 -+#define _PIPE_B_OUTPUT_CSC_PREOFF_HI 0x49168 -+#define _PIPE_B_OUTPUT_CSC_PREOFF_ME 0x4916c -+#define _PIPE_B_OUTPUT_CSC_PREOFF_LO 0x49170 -+#define _PIPE_B_OUTPUT_CSC_POSTOFF_HI 0x49174 -+#define _PIPE_B_OUTPUT_CSC_POSTOFF_ME 0x49178 -+#define _PIPE_B_OUTPUT_CSC_POSTOFF_LO 0x4917c -+ -+#define PIPE_CSC_OUTPUT_COEFF_RY_GY(pipe) _MMIO_PIPE(pipe,\ -+ _PIPE_A_OUTPUT_CSC_COEFF_RY_GY,\ -+ _PIPE_B_OUTPUT_CSC_COEFF_RY_GY) -+#define PIPE_CSC_OUTPUT_COEFF_BY(pipe) _MMIO_PIPE(pipe, \ -+ _PIPE_A_OUTPUT_CSC_COEFF_BY, \ -+ _PIPE_B_OUTPUT_CSC_COEFF_BY) -+#define PIPE_CSC_OUTPUT_COEFF_RU_GU(pipe) _MMIO_PIPE(pipe, \ -+ _PIPE_A_OUTPUT_CSC_COEFF_RU_GU, \ -+ _PIPE_B_OUTPUT_CSC_COEFF_RU_GU) -+#define PIPE_CSC_OUTPUT_COEFF_BU(pipe) _MMIO_PIPE(pipe, \ -+ _PIPE_A_OUTPUT_CSC_COEFF_BU, \ -+ _PIPE_B_OUTPUT_CSC_COEFF_BU) -+#define PIPE_CSC_OUTPUT_COEFF_RV_GV(pipe) _MMIO_PIPE(pipe, \ -+ _PIPE_A_OUTPUT_CSC_COEFF_RV_GV, \ -+ _PIPE_B_OUTPUT_CSC_COEFF_RV_GV) -+#define PIPE_CSC_OUTPUT_COEFF_BV(pipe) _MMIO_PIPE(pipe, \ -+ _PIPE_A_OUTPUT_CSC_COEFF_BV, \ -+ _PIPE_B_OUTPUT_CSC_COEFF_BV) -+#define PIPE_CSC_OUTPUT_PREOFF_HI(pipe) _MMIO_PIPE(pipe, \ -+ _PIPE_A_OUTPUT_CSC_PREOFF_HI, \ -+ _PIPE_B_OUTPUT_CSC_PREOFF_HI) -+#define PIPE_CSC_OUTPUT_PREOFF_ME(pipe) _MMIO_PIPE(pipe, \ -+ _PIPE_A_OUTPUT_CSC_PREOFF_ME, \ -+ _PIPE_B_OUTPUT_CSC_PREOFF_ME) -+#define PIPE_CSC_OUTPUT_PREOFF_LO(pipe) _MMIO_PIPE(pipe, \ -+ _PIPE_A_OUTPUT_CSC_PREOFF_LO, \ -+ _PIPE_B_OUTPUT_CSC_PREOFF_LO) -+#define PIPE_CSC_OUTPUT_POSTOFF_HI(pipe) _MMIO_PIPE(pipe, \ -+ _PIPE_A_OUTPUT_CSC_POSTOFF_HI, \ -+ _PIPE_B_OUTPUT_CSC_POSTOFF_HI) -+#define PIPE_CSC_OUTPUT_POSTOFF_ME(pipe) _MMIO_PIPE(pipe, \ -+ _PIPE_A_OUTPUT_CSC_POSTOFF_ME, \ -+ _PIPE_B_OUTPUT_CSC_POSTOFF_ME) -+#define PIPE_CSC_OUTPUT_POSTOFF_LO(pipe) _MMIO_PIPE(pipe, \ -+ _PIPE_A_OUTPUT_CSC_POSTOFF_LO, \ -+ _PIPE_B_OUTPUT_CSC_POSTOFF_LO) -+ -+/* pipe degamma/gamma LUTs on IVB+ */ -+#define _PAL_PREC_INDEX_A 0x4A400 -+#define _PAL_PREC_INDEX_B 0x4AC00 -+#define _PAL_PREC_INDEX_C 0x4B400 -+#define PAL_PREC_10_12_BIT (0 << 31) -+#define PAL_PREC_SPLIT_MODE (1 << 31) -+#define PAL_PREC_AUTO_INCREMENT (1 << 15) -+#define PAL_PREC_INDEX_VALUE_MASK (0x3ff << 0) -+#define PAL_PREC_INDEX_VALUE(x) ((x) << 0) -+#define _PAL_PREC_DATA_A 0x4A404 -+#define _PAL_PREC_DATA_B 0x4AC04 -+#define _PAL_PREC_DATA_C 0x4B404 -+#define _PAL_PREC_GC_MAX_A 0x4A410 -+#define _PAL_PREC_GC_MAX_B 0x4AC10 -+#define _PAL_PREC_GC_MAX_C 0x4B410 -+#define _PAL_PREC_EXT_GC_MAX_A 0x4A420 -+#define _PAL_PREC_EXT_GC_MAX_B 0x4AC20 -+#define _PAL_PREC_EXT_GC_MAX_C 0x4B420 -+#define _PAL_PREC_EXT2_GC_MAX_A 0x4A430 -+#define _PAL_PREC_EXT2_GC_MAX_B 0x4AC30 -+#define _PAL_PREC_EXT2_GC_MAX_C 0x4B430 -+ -+#define PREC_PAL_INDEX(pipe) _MMIO_PIPE(pipe, _PAL_PREC_INDEX_A, _PAL_PREC_INDEX_B) -+#define PREC_PAL_DATA(pipe) _MMIO_PIPE(pipe, _PAL_PREC_DATA_A, _PAL_PREC_DATA_B) -+#define PREC_PAL_GC_MAX(pipe, i) _MMIO(_PIPE(pipe, _PAL_PREC_GC_MAX_A, _PAL_PREC_GC_MAX_B) + (i) * 4) -+#define PREC_PAL_EXT_GC_MAX(pipe, i) _MMIO(_PIPE(pipe, _PAL_PREC_EXT_GC_MAX_A, _PAL_PREC_EXT_GC_MAX_B) + (i) * 4) -+#define PREC_PAL_EXT2_GC_MAX(pipe, i) _MMIO(_PIPE(pipe, _PAL_PREC_EXT2_GC_MAX_A, _PAL_PREC_EXT2_GC_MAX_B) + (i) * 4) -+ -+#define _PRE_CSC_GAMC_INDEX_A 0x4A484 -+#define _PRE_CSC_GAMC_INDEX_B 0x4AC84 -+#define _PRE_CSC_GAMC_INDEX_C 0x4B484 -+#define PRE_CSC_GAMC_AUTO_INCREMENT (1 << 10) -+#define _PRE_CSC_GAMC_DATA_A 0x4A488 -+#define _PRE_CSC_GAMC_DATA_B 0x4AC88 -+#define _PRE_CSC_GAMC_DATA_C 0x4B488 -+ -+#define PRE_CSC_GAMC_INDEX(pipe) _MMIO_PIPE(pipe, _PRE_CSC_GAMC_INDEX_A, _PRE_CSC_GAMC_INDEX_B) -+#define PRE_CSC_GAMC_DATA(pipe) _MMIO_PIPE(pipe, _PRE_CSC_GAMC_DATA_A, _PRE_CSC_GAMC_DATA_B) -+ -+/* pipe CSC & degamma/gamma LUTs on CHV */ -+#define _CGM_PIPE_A_CSC_COEFF01 (VLV_DISPLAY_BASE + 0x67900) -+#define _CGM_PIPE_A_CSC_COEFF23 (VLV_DISPLAY_BASE + 0x67904) -+#define _CGM_PIPE_A_CSC_COEFF45 (VLV_DISPLAY_BASE + 0x67908) -+#define _CGM_PIPE_A_CSC_COEFF67 (VLV_DISPLAY_BASE + 0x6790C) -+#define _CGM_PIPE_A_CSC_COEFF8 (VLV_DISPLAY_BASE + 0x67910) -+#define _CGM_PIPE_A_DEGAMMA (VLV_DISPLAY_BASE + 0x66000) -+#define _CGM_PIPE_A_GAMMA (VLV_DISPLAY_BASE + 0x67000) -+#define _CGM_PIPE_A_MODE (VLV_DISPLAY_BASE + 0x67A00) -+#define CGM_PIPE_MODE_GAMMA (1 << 2) -+#define CGM_PIPE_MODE_CSC (1 << 1) -+#define CGM_PIPE_MODE_DEGAMMA (1 << 0) -+ -+#define _CGM_PIPE_B_CSC_COEFF01 (VLV_DISPLAY_BASE + 0x69900) -+#define _CGM_PIPE_B_CSC_COEFF23 (VLV_DISPLAY_BASE + 0x69904) -+#define _CGM_PIPE_B_CSC_COEFF45 (VLV_DISPLAY_BASE + 0x69908) -+#define _CGM_PIPE_B_CSC_COEFF67 (VLV_DISPLAY_BASE + 0x6990C) -+#define _CGM_PIPE_B_CSC_COEFF8 (VLV_DISPLAY_BASE + 0x69910) -+#define _CGM_PIPE_B_DEGAMMA (VLV_DISPLAY_BASE + 0x68000) -+#define _CGM_PIPE_B_GAMMA (VLV_DISPLAY_BASE + 0x69000) -+#define _CGM_PIPE_B_MODE (VLV_DISPLAY_BASE + 0x69A00) -+ -+#define CGM_PIPE_CSC_COEFF01(pipe) _MMIO_PIPE(pipe, _CGM_PIPE_A_CSC_COEFF01, _CGM_PIPE_B_CSC_COEFF01) -+#define CGM_PIPE_CSC_COEFF23(pipe) _MMIO_PIPE(pipe, _CGM_PIPE_A_CSC_COEFF23, _CGM_PIPE_B_CSC_COEFF23) -+#define CGM_PIPE_CSC_COEFF45(pipe) _MMIO_PIPE(pipe, _CGM_PIPE_A_CSC_COEFF45, _CGM_PIPE_B_CSC_COEFF45) -+#define CGM_PIPE_CSC_COEFF67(pipe) _MMIO_PIPE(pipe, _CGM_PIPE_A_CSC_COEFF67, _CGM_PIPE_B_CSC_COEFF67) -+#define CGM_PIPE_CSC_COEFF8(pipe) _MMIO_PIPE(pipe, _CGM_PIPE_A_CSC_COEFF8, _CGM_PIPE_B_CSC_COEFF8) -+#define CGM_PIPE_DEGAMMA(pipe, i, w) _MMIO(_PIPE(pipe, _CGM_PIPE_A_DEGAMMA, _CGM_PIPE_B_DEGAMMA) + (i) * 8 + (w) * 4) -+#define CGM_PIPE_GAMMA(pipe, i, w) _MMIO(_PIPE(pipe, _CGM_PIPE_A_GAMMA, _CGM_PIPE_B_GAMMA) + (i) * 8 + (w) * 4) -+#define CGM_PIPE_MODE(pipe) _MMIO_PIPE(pipe, _CGM_PIPE_A_MODE, _CGM_PIPE_B_MODE) -+ -+/* MIPI DSI registers */ -+ -+#define _MIPI_PORT(port, a, c) (((port) == PORT_A) ? a : c) /* ports A and C only */ -+#define _MMIO_MIPI(port, a, c) _MMIO(_MIPI_PORT(port, a, c)) -+ -+/* Gen11 DSI */ -+#define _MMIO_DSI(tc, dsi0, dsi1) _MMIO_TRANS((tc) - TRANSCODER_DSI_0, \ -+ dsi0, dsi1) -+ -+#define MIPIO_TXESC_CLK_DIV1 _MMIO(0x160004) -+#define GLK_TX_ESC_CLK_DIV1_MASK 0x3FF -+#define MIPIO_TXESC_CLK_DIV2 _MMIO(0x160008) -+#define GLK_TX_ESC_CLK_DIV2_MASK 0x3FF -+ -+#define _ICL_DSI_ESC_CLK_DIV0 0x6b090 -+#define _ICL_DSI_ESC_CLK_DIV1 0x6b890 -+#define ICL_DSI_ESC_CLK_DIV(port) _MMIO_PORT((port), \ -+ _ICL_DSI_ESC_CLK_DIV0, \ -+ _ICL_DSI_ESC_CLK_DIV1) -+#define _ICL_DPHY_ESC_CLK_DIV0 0x162190 -+#define _ICL_DPHY_ESC_CLK_DIV1 0x6C190 -+#define ICL_DPHY_ESC_CLK_DIV(port) _MMIO_PORT((port), \ -+ _ICL_DPHY_ESC_CLK_DIV0, \ -+ _ICL_DPHY_ESC_CLK_DIV1) -+#define ICL_BYTE_CLK_PER_ESC_CLK_MASK (0x1f << 16) -+#define ICL_BYTE_CLK_PER_ESC_CLK_SHIFT 16 -+#define ICL_ESC_CLK_DIV_MASK 0x1ff -+#define ICL_ESC_CLK_DIV_SHIFT 0 -+#define DSI_MAX_ESC_CLK 20000 /* in KHz */ -+ -+/* Gen4+ Timestamp and Pipe Frame time stamp registers */ -+#define GEN4_TIMESTAMP _MMIO(0x2358) -+#define ILK_TIMESTAMP_HI _MMIO(0x70070) -+#define IVB_TIMESTAMP_CTR _MMIO(0x44070) -+ -+#define GEN9_TIMESTAMP_OVERRIDE _MMIO(0x44074) -+#define GEN9_TIMESTAMP_OVERRIDE_US_COUNTER_DIVIDER_SHIFT 0 -+#define GEN9_TIMESTAMP_OVERRIDE_US_COUNTER_DIVIDER_MASK 0x3ff -+#define GEN9_TIMESTAMP_OVERRIDE_US_COUNTER_DENOMINATOR_SHIFT 12 -+#define GEN9_TIMESTAMP_OVERRIDE_US_COUNTER_DENOMINATOR_MASK (0xf << 12) -+ -+#define _PIPE_FRMTMSTMP_A 0x70048 -+#define PIPE_FRMTMSTMP(pipe) \ -+ _MMIO_PIPE2(pipe, _PIPE_FRMTMSTMP_A) -+ -+/* BXT MIPI clock controls */ -+#define BXT_MAX_VAR_OUTPUT_KHZ 39500 -+ -+#define BXT_MIPI_CLOCK_CTL _MMIO(0x46090) -+#define BXT_MIPI1_DIV_SHIFT 26 -+#define BXT_MIPI2_DIV_SHIFT 10 -+#define BXT_MIPI_DIV_SHIFT(port) \ -+ _MIPI_PORT(port, BXT_MIPI1_DIV_SHIFT, \ -+ BXT_MIPI2_DIV_SHIFT) -+ -+/* TX control divider to select actual TX clock output from (8x/var) */ -+#define BXT_MIPI1_TX_ESCLK_SHIFT 26 -+#define BXT_MIPI2_TX_ESCLK_SHIFT 10 -+#define BXT_MIPI_TX_ESCLK_SHIFT(port) \ -+ _MIPI_PORT(port, BXT_MIPI1_TX_ESCLK_SHIFT, \ -+ BXT_MIPI2_TX_ESCLK_SHIFT) -+#define BXT_MIPI1_TX_ESCLK_FIXDIV_MASK (0x3F << 26) -+#define BXT_MIPI2_TX_ESCLK_FIXDIV_MASK (0x3F << 10) -+#define BXT_MIPI_TX_ESCLK_FIXDIV_MASK(port) \ -+ _MIPI_PORT(port, BXT_MIPI1_TX_ESCLK_FIXDIV_MASK, \ -+ BXT_MIPI2_TX_ESCLK_FIXDIV_MASK) -+#define BXT_MIPI_TX_ESCLK_DIVIDER(port, val) \ -+ (((val) & 0x3F) << BXT_MIPI_TX_ESCLK_SHIFT(port)) -+/* RX upper control divider to select actual RX clock output from 8x */ -+#define BXT_MIPI1_RX_ESCLK_UPPER_SHIFT 21 -+#define BXT_MIPI2_RX_ESCLK_UPPER_SHIFT 5 -+#define BXT_MIPI_RX_ESCLK_UPPER_SHIFT(port) \ -+ _MIPI_PORT(port, BXT_MIPI1_RX_ESCLK_UPPER_SHIFT, \ -+ BXT_MIPI2_RX_ESCLK_UPPER_SHIFT) -+#define BXT_MIPI1_RX_ESCLK_UPPER_FIXDIV_MASK (3 << 21) -+#define BXT_MIPI2_RX_ESCLK_UPPER_FIXDIV_MASK (3 << 5) -+#define BXT_MIPI_RX_ESCLK_UPPER_FIXDIV_MASK(port) \ -+ _MIPI_PORT(port, BXT_MIPI1_RX_ESCLK_UPPER_FIXDIV_MASK, \ -+ BXT_MIPI2_RX_ESCLK_UPPER_FIXDIV_MASK) -+#define BXT_MIPI_RX_ESCLK_UPPER_DIVIDER(port, val) \ -+ (((val) & 3) << BXT_MIPI_RX_ESCLK_UPPER_SHIFT(port)) -+/* 8/3X divider to select the actual 8/3X clock output from 8x */ -+#define BXT_MIPI1_8X_BY3_SHIFT 19 -+#define BXT_MIPI2_8X_BY3_SHIFT 3 -+#define BXT_MIPI_8X_BY3_SHIFT(port) \ -+ _MIPI_PORT(port, BXT_MIPI1_8X_BY3_SHIFT, \ -+ BXT_MIPI2_8X_BY3_SHIFT) -+#define BXT_MIPI1_8X_BY3_DIVIDER_MASK (3 << 19) -+#define BXT_MIPI2_8X_BY3_DIVIDER_MASK (3 << 3) -+#define BXT_MIPI_8X_BY3_DIVIDER_MASK(port) \ -+ _MIPI_PORT(port, BXT_MIPI1_8X_BY3_DIVIDER_MASK, \ -+ BXT_MIPI2_8X_BY3_DIVIDER_MASK) -+#define BXT_MIPI_8X_BY3_DIVIDER(port, val) \ -+ (((val) & 3) << BXT_MIPI_8X_BY3_SHIFT(port)) -+/* RX lower control divider to select actual RX clock output from 8x */ -+#define BXT_MIPI1_RX_ESCLK_LOWER_SHIFT 16 -+#define BXT_MIPI2_RX_ESCLK_LOWER_SHIFT 0 -+#define BXT_MIPI_RX_ESCLK_LOWER_SHIFT(port) \ -+ _MIPI_PORT(port, BXT_MIPI1_RX_ESCLK_LOWER_SHIFT, \ -+ BXT_MIPI2_RX_ESCLK_LOWER_SHIFT) -+#define BXT_MIPI1_RX_ESCLK_LOWER_FIXDIV_MASK (3 << 16) -+#define BXT_MIPI2_RX_ESCLK_LOWER_FIXDIV_MASK (3 << 0) -+#define BXT_MIPI_RX_ESCLK_LOWER_FIXDIV_MASK(port) \ -+ _MIPI_PORT(port, BXT_MIPI1_RX_ESCLK_LOWER_FIXDIV_MASK, \ -+ BXT_MIPI2_RX_ESCLK_LOWER_FIXDIV_MASK) -+#define BXT_MIPI_RX_ESCLK_LOWER_DIVIDER(port, val) \ -+ (((val) & 3) << BXT_MIPI_RX_ESCLK_LOWER_SHIFT(port)) -+ -+#define RX_DIVIDER_BIT_1_2 0x3 -+#define RX_DIVIDER_BIT_3_4 0xC -+ -+/* BXT MIPI mode configure */ -+#define _BXT_MIPIA_TRANS_HACTIVE 0x6B0F8 -+#define _BXT_MIPIC_TRANS_HACTIVE 0x6B8F8 -+#define BXT_MIPI_TRANS_HACTIVE(tc) _MMIO_MIPI(tc, \ -+ _BXT_MIPIA_TRANS_HACTIVE, _BXT_MIPIC_TRANS_HACTIVE) -+ -+#define _BXT_MIPIA_TRANS_VACTIVE 0x6B0FC -+#define _BXT_MIPIC_TRANS_VACTIVE 0x6B8FC -+#define BXT_MIPI_TRANS_VACTIVE(tc) _MMIO_MIPI(tc, \ -+ _BXT_MIPIA_TRANS_VACTIVE, _BXT_MIPIC_TRANS_VACTIVE) -+ -+#define _BXT_MIPIA_TRANS_VTOTAL 0x6B100 -+#define _BXT_MIPIC_TRANS_VTOTAL 0x6B900 -+#define BXT_MIPI_TRANS_VTOTAL(tc) _MMIO_MIPI(tc, \ -+ _BXT_MIPIA_TRANS_VTOTAL, _BXT_MIPIC_TRANS_VTOTAL) -+ -+#define BXT_DSI_PLL_CTL _MMIO(0x161000) -+#define BXT_DSI_PLL_PVD_RATIO_SHIFT 16 -+#define BXT_DSI_PLL_PVD_RATIO_MASK (3 << BXT_DSI_PLL_PVD_RATIO_SHIFT) -+#define BXT_DSI_PLL_PVD_RATIO_1 (1 << BXT_DSI_PLL_PVD_RATIO_SHIFT) -+#define BXT_DSIC_16X_BY1 (0 << 10) -+#define BXT_DSIC_16X_BY2 (1 << 10) -+#define BXT_DSIC_16X_BY3 (2 << 10) -+#define BXT_DSIC_16X_BY4 (3 << 10) -+#define BXT_DSIC_16X_MASK (3 << 10) -+#define BXT_DSIA_16X_BY1 (0 << 8) -+#define BXT_DSIA_16X_BY2 (1 << 8) -+#define BXT_DSIA_16X_BY3 (2 << 8) -+#define BXT_DSIA_16X_BY4 (3 << 8) -+#define BXT_DSIA_16X_MASK (3 << 8) -+#define BXT_DSI_FREQ_SEL_SHIFT 8 -+#define BXT_DSI_FREQ_SEL_MASK (0xF << BXT_DSI_FREQ_SEL_SHIFT) -+ -+#define BXT_DSI_PLL_RATIO_MAX 0x7D -+#define BXT_DSI_PLL_RATIO_MIN 0x22 -+#define GLK_DSI_PLL_RATIO_MAX 0x6F -+#define GLK_DSI_PLL_RATIO_MIN 0x22 -+#define BXT_DSI_PLL_RATIO_MASK 0xFF -+#define BXT_REF_CLOCK_KHZ 19200 -+ -+#define BXT_DSI_PLL_ENABLE _MMIO(0x46080) -+#define BXT_DSI_PLL_DO_ENABLE (1 << 31) -+#define BXT_DSI_PLL_LOCKED (1 << 30) -+ -+#define _MIPIA_PORT_CTRL (VLV_DISPLAY_BASE + 0x61190) -+#define _MIPIC_PORT_CTRL (VLV_DISPLAY_BASE + 0x61700) -+#define MIPI_PORT_CTRL(port) _MMIO_MIPI(port, _MIPIA_PORT_CTRL, _MIPIC_PORT_CTRL) -+ -+ /* BXT port control */ -+#define _BXT_MIPIA_PORT_CTRL 0x6B0C0 -+#define _BXT_MIPIC_PORT_CTRL 0x6B8C0 -+#define BXT_MIPI_PORT_CTRL(tc) _MMIO_MIPI(tc, _BXT_MIPIA_PORT_CTRL, _BXT_MIPIC_PORT_CTRL) -+ -+/* ICL DSI MODE control */ -+#define _ICL_DSI_IO_MODECTL_0 0x6B094 -+#define _ICL_DSI_IO_MODECTL_1 0x6B894 -+#define ICL_DSI_IO_MODECTL(port) _MMIO_PORT(port, \ -+ _ICL_DSI_IO_MODECTL_0, \ -+ _ICL_DSI_IO_MODECTL_1) -+#define COMBO_PHY_MODE_DSI (1 << 0) -+ -+/* Display Stream Splitter Control */ -+#define DSS_CTL1 _MMIO(0x67400) -+#define SPLITTER_ENABLE (1 << 31) -+#define JOINER_ENABLE (1 << 30) -+#define DUAL_LINK_MODE_INTERLEAVE (1 << 24) -+#define DUAL_LINK_MODE_FRONTBACK (0 << 24) -+#define OVERLAP_PIXELS_MASK (0xf << 16) -+#define OVERLAP_PIXELS(pixels) ((pixels) << 16) -+#define LEFT_DL_BUF_TARGET_DEPTH_MASK (0xfff << 0) -+#define LEFT_DL_BUF_TARGET_DEPTH(pixels) ((pixels) << 0) -+#define MAX_DL_BUFFER_TARGET_DEPTH 0x5a0 -+ -+#define DSS_CTL2 _MMIO(0x67404) -+#define LEFT_BRANCH_VDSC_ENABLE (1 << 31) -+#define RIGHT_BRANCH_VDSC_ENABLE (1 << 15) -+#define RIGHT_DL_BUF_TARGET_DEPTH_MASK (0xfff << 0) -+#define RIGHT_DL_BUF_TARGET_DEPTH(pixels) ((pixels) << 0) -+ -+#define _ICL_PIPE_DSS_CTL1_PB 0x78200 -+#define _ICL_PIPE_DSS_CTL1_PC 0x78400 -+#define ICL_PIPE_DSS_CTL1(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_PIPE_DSS_CTL1_PB, \ -+ _ICL_PIPE_DSS_CTL1_PC) -+#define BIG_JOINER_ENABLE (1 << 29) -+#define MASTER_BIG_JOINER_ENABLE (1 << 28) -+#define VGA_CENTERING_ENABLE (1 << 27) -+ -+#define _ICL_PIPE_DSS_CTL2_PB 0x78204 -+#define _ICL_PIPE_DSS_CTL2_PC 0x78404 -+#define ICL_PIPE_DSS_CTL2(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_PIPE_DSS_CTL2_PB, \ -+ _ICL_PIPE_DSS_CTL2_PC) -+ -+#define BXT_P_DSI_REGULATOR_CFG _MMIO(0x160020) -+#define STAP_SELECT (1 << 0) -+ -+#define BXT_P_DSI_REGULATOR_TX_CTRL _MMIO(0x160054) -+#define HS_IO_CTRL_SELECT (1 << 0) -+ -+#define DPI_ENABLE (1 << 31) /* A + C */ -+#define MIPIA_MIPI4DPHY_DELAY_COUNT_SHIFT 27 -+#define MIPIA_MIPI4DPHY_DELAY_COUNT_MASK (0xf << 27) -+#define DUAL_LINK_MODE_SHIFT 26 -+#define DUAL_LINK_MODE_MASK (1 << 26) -+#define DUAL_LINK_MODE_FRONT_BACK (0 << 26) -+#define DUAL_LINK_MODE_PIXEL_ALTERNATIVE (1 << 26) -+#define DITHERING_ENABLE (1 << 25) /* A + C */ -+#define FLOPPED_HSTX (1 << 23) -+#define DE_INVERT (1 << 19) /* XXX */ -+#define MIPIA_FLISDSI_DELAY_COUNT_SHIFT 18 -+#define MIPIA_FLISDSI_DELAY_COUNT_MASK (0xf << 18) -+#define AFE_LATCHOUT (1 << 17) -+#define LP_OUTPUT_HOLD (1 << 16) -+#define MIPIC_FLISDSI_DELAY_COUNT_HIGH_SHIFT 15 -+#define MIPIC_FLISDSI_DELAY_COUNT_HIGH_MASK (1 << 15) -+#define MIPIC_MIPI4DPHY_DELAY_COUNT_SHIFT 11 -+#define MIPIC_MIPI4DPHY_DELAY_COUNT_MASK (0xf << 11) -+#define CSB_SHIFT 9 -+#define CSB_MASK (3 << 9) -+#define CSB_20MHZ (0 << 9) -+#define CSB_10MHZ (1 << 9) -+#define CSB_40MHZ (2 << 9) -+#define BANDGAP_MASK (1 << 8) -+#define BANDGAP_PNW_CIRCUIT (0 << 8) -+#define BANDGAP_LNC_CIRCUIT (1 << 8) -+#define MIPIC_FLISDSI_DELAY_COUNT_LOW_SHIFT 5 -+#define MIPIC_FLISDSI_DELAY_COUNT_LOW_MASK (7 << 5) -+#define TEARING_EFFECT_DELAY (1 << 4) /* A + C */ -+#define TEARING_EFFECT_SHIFT 2 /* A + C */ -+#define TEARING_EFFECT_MASK (3 << 2) -+#define TEARING_EFFECT_OFF (0 << 2) -+#define TEARING_EFFECT_DSI (1 << 2) -+#define TEARING_EFFECT_GPIO (2 << 2) -+#define LANE_CONFIGURATION_SHIFT 0 -+#define LANE_CONFIGURATION_MASK (3 << 0) -+#define LANE_CONFIGURATION_4LANE (0 << 0) -+#define LANE_CONFIGURATION_DUAL_LINK_A (1 << 0) -+#define LANE_CONFIGURATION_DUAL_LINK_B (2 << 0) -+ -+#define _MIPIA_TEARING_CTRL (VLV_DISPLAY_BASE + 0x61194) -+#define _MIPIC_TEARING_CTRL (VLV_DISPLAY_BASE + 0x61704) -+#define MIPI_TEARING_CTRL(port) _MMIO_MIPI(port, _MIPIA_TEARING_CTRL, _MIPIC_TEARING_CTRL) -+#define TEARING_EFFECT_DELAY_SHIFT 0 -+#define TEARING_EFFECT_DELAY_MASK (0xffff << 0) -+ -+/* XXX: all bits reserved */ -+#define _MIPIA_AUTOPWG (VLV_DISPLAY_BASE + 0x611a0) -+ -+/* MIPI DSI Controller and D-PHY registers */ -+ -+#define _MIPIA_DEVICE_READY (dev_priv->mipi_mmio_base + 0xb000) -+#define _MIPIC_DEVICE_READY (dev_priv->mipi_mmio_base + 0xb800) -+#define MIPI_DEVICE_READY(port) _MMIO_MIPI(port, _MIPIA_DEVICE_READY, _MIPIC_DEVICE_READY) -+#define BUS_POSSESSION (1 << 3) /* set to give bus to receiver */ -+#define ULPS_STATE_MASK (3 << 1) -+#define ULPS_STATE_ENTER (2 << 1) -+#define ULPS_STATE_EXIT (1 << 1) -+#define ULPS_STATE_NORMAL_OPERATION (0 << 1) -+#define DEVICE_READY (1 << 0) -+ -+#define _MIPIA_INTR_STAT (dev_priv->mipi_mmio_base + 0xb004) -+#define _MIPIC_INTR_STAT (dev_priv->mipi_mmio_base + 0xb804) -+#define MIPI_INTR_STAT(port) _MMIO_MIPI(port, _MIPIA_INTR_STAT, _MIPIC_INTR_STAT) -+#define _MIPIA_INTR_EN (dev_priv->mipi_mmio_base + 0xb008) -+#define _MIPIC_INTR_EN (dev_priv->mipi_mmio_base + 0xb808) -+#define MIPI_INTR_EN(port) _MMIO_MIPI(port, _MIPIA_INTR_EN, _MIPIC_INTR_EN) -+#define TEARING_EFFECT (1 << 31) -+#define SPL_PKT_SENT_INTERRUPT (1 << 30) -+#define GEN_READ_DATA_AVAIL (1 << 29) -+#define LP_GENERIC_WR_FIFO_FULL (1 << 28) -+#define HS_GENERIC_WR_FIFO_FULL (1 << 27) -+#define RX_PROT_VIOLATION (1 << 26) -+#define RX_INVALID_TX_LENGTH (1 << 25) -+#define ACK_WITH_NO_ERROR (1 << 24) -+#define TURN_AROUND_ACK_TIMEOUT (1 << 23) -+#define LP_RX_TIMEOUT (1 << 22) -+#define HS_TX_TIMEOUT (1 << 21) -+#define DPI_FIFO_UNDERRUN (1 << 20) -+#define LOW_CONTENTION (1 << 19) -+#define HIGH_CONTENTION (1 << 18) -+#define TXDSI_VC_ID_INVALID (1 << 17) -+#define TXDSI_DATA_TYPE_NOT_RECOGNISED (1 << 16) -+#define TXCHECKSUM_ERROR (1 << 15) -+#define TXECC_MULTIBIT_ERROR (1 << 14) -+#define TXECC_SINGLE_BIT_ERROR (1 << 13) -+#define TXFALSE_CONTROL_ERROR (1 << 12) -+#define RXDSI_VC_ID_INVALID (1 << 11) -+#define RXDSI_DATA_TYPE_NOT_REGOGNISED (1 << 10) -+#define RXCHECKSUM_ERROR (1 << 9) -+#define RXECC_MULTIBIT_ERROR (1 << 8) -+#define RXECC_SINGLE_BIT_ERROR (1 << 7) -+#define RXFALSE_CONTROL_ERROR (1 << 6) -+#define RXHS_RECEIVE_TIMEOUT_ERROR (1 << 5) -+#define RX_LP_TX_SYNC_ERROR (1 << 4) -+#define RXEXCAPE_MODE_ENTRY_ERROR (1 << 3) -+#define RXEOT_SYNC_ERROR (1 << 2) -+#define RXSOT_SYNC_ERROR (1 << 1) -+#define RXSOT_ERROR (1 << 0) -+ -+#define _MIPIA_DSI_FUNC_PRG (dev_priv->mipi_mmio_base + 0xb00c) -+#define _MIPIC_DSI_FUNC_PRG (dev_priv->mipi_mmio_base + 0xb80c) -+#define MIPI_DSI_FUNC_PRG(port) _MMIO_MIPI(port, _MIPIA_DSI_FUNC_PRG, _MIPIC_DSI_FUNC_PRG) -+#define CMD_MODE_DATA_WIDTH_MASK (7 << 13) -+#define CMD_MODE_NOT_SUPPORTED (0 << 13) -+#define CMD_MODE_DATA_WIDTH_16_BIT (1 << 13) -+#define CMD_MODE_DATA_WIDTH_9_BIT (2 << 13) -+#define CMD_MODE_DATA_WIDTH_8_BIT (3 << 13) -+#define CMD_MODE_DATA_WIDTH_OPTION1 (4 << 13) -+#define CMD_MODE_DATA_WIDTH_OPTION2 (5 << 13) -+#define VID_MODE_FORMAT_MASK (0xf << 7) -+#define VID_MODE_NOT_SUPPORTED (0 << 7) -+#define VID_MODE_FORMAT_RGB565 (1 << 7) -+#define VID_MODE_FORMAT_RGB666_PACKED (2 << 7) -+#define VID_MODE_FORMAT_RGB666 (3 << 7) -+#define VID_MODE_FORMAT_RGB888 (4 << 7) -+#define CMD_MODE_CHANNEL_NUMBER_SHIFT 5 -+#define CMD_MODE_CHANNEL_NUMBER_MASK (3 << 5) -+#define VID_MODE_CHANNEL_NUMBER_SHIFT 3 -+#define VID_MODE_CHANNEL_NUMBER_MASK (3 << 3) -+#define DATA_LANES_PRG_REG_SHIFT 0 -+#define DATA_LANES_PRG_REG_MASK (7 << 0) -+ -+#define _MIPIA_HS_TX_TIMEOUT (dev_priv->mipi_mmio_base + 0xb010) -+#define _MIPIC_HS_TX_TIMEOUT (dev_priv->mipi_mmio_base + 0xb810) -+#define MIPI_HS_TX_TIMEOUT(port) _MMIO_MIPI(port, _MIPIA_HS_TX_TIMEOUT, _MIPIC_HS_TX_TIMEOUT) -+#define HIGH_SPEED_TX_TIMEOUT_COUNTER_MASK 0xffffff -+ -+#define _MIPIA_LP_RX_TIMEOUT (dev_priv->mipi_mmio_base + 0xb014) -+#define _MIPIC_LP_RX_TIMEOUT (dev_priv->mipi_mmio_base + 0xb814) -+#define MIPI_LP_RX_TIMEOUT(port) _MMIO_MIPI(port, _MIPIA_LP_RX_TIMEOUT, _MIPIC_LP_RX_TIMEOUT) -+#define LOW_POWER_RX_TIMEOUT_COUNTER_MASK 0xffffff -+ -+#define _MIPIA_TURN_AROUND_TIMEOUT (dev_priv->mipi_mmio_base + 0xb018) -+#define _MIPIC_TURN_AROUND_TIMEOUT (dev_priv->mipi_mmio_base + 0xb818) -+#define MIPI_TURN_AROUND_TIMEOUT(port) _MMIO_MIPI(port, _MIPIA_TURN_AROUND_TIMEOUT, _MIPIC_TURN_AROUND_TIMEOUT) -+#define TURN_AROUND_TIMEOUT_MASK 0x3f -+ -+#define _MIPIA_DEVICE_RESET_TIMER (dev_priv->mipi_mmio_base + 0xb01c) -+#define _MIPIC_DEVICE_RESET_TIMER (dev_priv->mipi_mmio_base + 0xb81c) -+#define MIPI_DEVICE_RESET_TIMER(port) _MMIO_MIPI(port, _MIPIA_DEVICE_RESET_TIMER, _MIPIC_DEVICE_RESET_TIMER) -+#define DEVICE_RESET_TIMER_MASK 0xffff -+ -+#define _MIPIA_DPI_RESOLUTION (dev_priv->mipi_mmio_base + 0xb020) -+#define _MIPIC_DPI_RESOLUTION (dev_priv->mipi_mmio_base + 0xb820) -+#define MIPI_DPI_RESOLUTION(port) _MMIO_MIPI(port, _MIPIA_DPI_RESOLUTION, _MIPIC_DPI_RESOLUTION) -+#define VERTICAL_ADDRESS_SHIFT 16 -+#define VERTICAL_ADDRESS_MASK (0xffff << 16) -+#define HORIZONTAL_ADDRESS_SHIFT 0 -+#define HORIZONTAL_ADDRESS_MASK 0xffff -+ -+#define _MIPIA_DBI_FIFO_THROTTLE (dev_priv->mipi_mmio_base + 0xb024) -+#define _MIPIC_DBI_FIFO_THROTTLE (dev_priv->mipi_mmio_base + 0xb824) -+#define MIPI_DBI_FIFO_THROTTLE(port) _MMIO_MIPI(port, _MIPIA_DBI_FIFO_THROTTLE, _MIPIC_DBI_FIFO_THROTTLE) -+#define DBI_FIFO_EMPTY_HALF (0 << 0) -+#define DBI_FIFO_EMPTY_QUARTER (1 << 0) -+#define DBI_FIFO_EMPTY_7_LOCATIONS (2 << 0) -+ -+/* regs below are bits 15:0 */ -+#define _MIPIA_HSYNC_PADDING_COUNT (dev_priv->mipi_mmio_base + 0xb028) -+#define _MIPIC_HSYNC_PADDING_COUNT (dev_priv->mipi_mmio_base + 0xb828) -+#define MIPI_HSYNC_PADDING_COUNT(port) _MMIO_MIPI(port, _MIPIA_HSYNC_PADDING_COUNT, _MIPIC_HSYNC_PADDING_COUNT) -+ -+#define _MIPIA_HBP_COUNT (dev_priv->mipi_mmio_base + 0xb02c) -+#define _MIPIC_HBP_COUNT (dev_priv->mipi_mmio_base + 0xb82c) -+#define MIPI_HBP_COUNT(port) _MMIO_MIPI(port, _MIPIA_HBP_COUNT, _MIPIC_HBP_COUNT) -+ -+#define _MIPIA_HFP_COUNT (dev_priv->mipi_mmio_base + 0xb030) -+#define _MIPIC_HFP_COUNT (dev_priv->mipi_mmio_base + 0xb830) -+#define MIPI_HFP_COUNT(port) _MMIO_MIPI(port, _MIPIA_HFP_COUNT, _MIPIC_HFP_COUNT) -+ -+#define _MIPIA_HACTIVE_AREA_COUNT (dev_priv->mipi_mmio_base + 0xb034) -+#define _MIPIC_HACTIVE_AREA_COUNT (dev_priv->mipi_mmio_base + 0xb834) -+#define MIPI_HACTIVE_AREA_COUNT(port) _MMIO_MIPI(port, _MIPIA_HACTIVE_AREA_COUNT, _MIPIC_HACTIVE_AREA_COUNT) -+ -+#define _MIPIA_VSYNC_PADDING_COUNT (dev_priv->mipi_mmio_base + 0xb038) -+#define _MIPIC_VSYNC_PADDING_COUNT (dev_priv->mipi_mmio_base + 0xb838) -+#define MIPI_VSYNC_PADDING_COUNT(port) _MMIO_MIPI(port, _MIPIA_VSYNC_PADDING_COUNT, _MIPIC_VSYNC_PADDING_COUNT) -+ -+#define _MIPIA_VBP_COUNT (dev_priv->mipi_mmio_base + 0xb03c) -+#define _MIPIC_VBP_COUNT (dev_priv->mipi_mmio_base + 0xb83c) -+#define MIPI_VBP_COUNT(port) _MMIO_MIPI(port, _MIPIA_VBP_COUNT, _MIPIC_VBP_COUNT) -+ -+#define _MIPIA_VFP_COUNT (dev_priv->mipi_mmio_base + 0xb040) -+#define _MIPIC_VFP_COUNT (dev_priv->mipi_mmio_base + 0xb840) -+#define MIPI_VFP_COUNT(port) _MMIO_MIPI(port, _MIPIA_VFP_COUNT, _MIPIC_VFP_COUNT) -+ -+#define _MIPIA_HIGH_LOW_SWITCH_COUNT (dev_priv->mipi_mmio_base + 0xb044) -+#define _MIPIC_HIGH_LOW_SWITCH_COUNT (dev_priv->mipi_mmio_base + 0xb844) -+#define MIPI_HIGH_LOW_SWITCH_COUNT(port) _MMIO_MIPI(port, _MIPIA_HIGH_LOW_SWITCH_COUNT, _MIPIC_HIGH_LOW_SWITCH_COUNT) -+ -+/* regs above are bits 15:0 */ -+ -+#define _MIPIA_DPI_CONTROL (dev_priv->mipi_mmio_base + 0xb048) -+#define _MIPIC_DPI_CONTROL (dev_priv->mipi_mmio_base + 0xb848) -+#define MIPI_DPI_CONTROL(port) _MMIO_MIPI(port, _MIPIA_DPI_CONTROL, _MIPIC_DPI_CONTROL) -+#define DPI_LP_MODE (1 << 6) -+#define BACKLIGHT_OFF (1 << 5) -+#define BACKLIGHT_ON (1 << 4) -+#define COLOR_MODE_OFF (1 << 3) -+#define COLOR_MODE_ON (1 << 2) -+#define TURN_ON (1 << 1) -+#define SHUTDOWN (1 << 0) -+ -+#define _MIPIA_DPI_DATA (dev_priv->mipi_mmio_base + 0xb04c) -+#define _MIPIC_DPI_DATA (dev_priv->mipi_mmio_base + 0xb84c) -+#define MIPI_DPI_DATA(port) _MMIO_MIPI(port, _MIPIA_DPI_DATA, _MIPIC_DPI_DATA) -+#define COMMAND_BYTE_SHIFT 0 -+#define COMMAND_BYTE_MASK (0x3f << 0) -+ -+#define _MIPIA_INIT_COUNT (dev_priv->mipi_mmio_base + 0xb050) -+#define _MIPIC_INIT_COUNT (dev_priv->mipi_mmio_base + 0xb850) -+#define MIPI_INIT_COUNT(port) _MMIO_MIPI(port, _MIPIA_INIT_COUNT, _MIPIC_INIT_COUNT) -+#define MASTER_INIT_TIMER_SHIFT 0 -+#define MASTER_INIT_TIMER_MASK (0xffff << 0) -+ -+#define _MIPIA_MAX_RETURN_PKT_SIZE (dev_priv->mipi_mmio_base + 0xb054) -+#define _MIPIC_MAX_RETURN_PKT_SIZE (dev_priv->mipi_mmio_base + 0xb854) -+#define MIPI_MAX_RETURN_PKT_SIZE(port) _MMIO_MIPI(port, \ -+ _MIPIA_MAX_RETURN_PKT_SIZE, _MIPIC_MAX_RETURN_PKT_SIZE) -+#define MAX_RETURN_PKT_SIZE_SHIFT 0 -+#define MAX_RETURN_PKT_SIZE_MASK (0x3ff << 0) -+ -+#define _MIPIA_VIDEO_MODE_FORMAT (dev_priv->mipi_mmio_base + 0xb058) -+#define _MIPIC_VIDEO_MODE_FORMAT (dev_priv->mipi_mmio_base + 0xb858) -+#define MIPI_VIDEO_MODE_FORMAT(port) _MMIO_MIPI(port, _MIPIA_VIDEO_MODE_FORMAT, _MIPIC_VIDEO_MODE_FORMAT) -+#define RANDOM_DPI_DISPLAY_RESOLUTION (1 << 4) -+#define DISABLE_VIDEO_BTA (1 << 3) -+#define IP_TG_CONFIG (1 << 2) -+#define VIDEO_MODE_NON_BURST_WITH_SYNC_PULSE (1 << 0) -+#define VIDEO_MODE_NON_BURST_WITH_SYNC_EVENTS (2 << 0) -+#define VIDEO_MODE_BURST (3 << 0) -+ -+#define _MIPIA_EOT_DISABLE (dev_priv->mipi_mmio_base + 0xb05c) -+#define _MIPIC_EOT_DISABLE (dev_priv->mipi_mmio_base + 0xb85c) -+#define MIPI_EOT_DISABLE(port) _MMIO_MIPI(port, _MIPIA_EOT_DISABLE, _MIPIC_EOT_DISABLE) -+#define BXT_DEFEATURE_DPI_FIFO_CTR (1 << 9) -+#define BXT_DPHY_DEFEATURE_EN (1 << 8) -+#define LP_RX_TIMEOUT_ERROR_RECOVERY_DISABLE (1 << 7) -+#define HS_RX_TIMEOUT_ERROR_RECOVERY_DISABLE (1 << 6) -+#define LOW_CONTENTION_RECOVERY_DISABLE (1 << 5) -+#define HIGH_CONTENTION_RECOVERY_DISABLE (1 << 4) -+#define TXDSI_TYPE_NOT_RECOGNISED_ERROR_RECOVERY_DISABLE (1 << 3) -+#define TXECC_MULTIBIT_ERROR_RECOVERY_DISABLE (1 << 2) -+#define CLOCKSTOP (1 << 1) -+#define EOT_DISABLE (1 << 0) -+ -+#define _MIPIA_LP_BYTECLK (dev_priv->mipi_mmio_base + 0xb060) -+#define _MIPIC_LP_BYTECLK (dev_priv->mipi_mmio_base + 0xb860) -+#define MIPI_LP_BYTECLK(port) _MMIO_MIPI(port, _MIPIA_LP_BYTECLK, _MIPIC_LP_BYTECLK) -+#define LP_BYTECLK_SHIFT 0 -+#define LP_BYTECLK_MASK (0xffff << 0) -+ -+#define _MIPIA_TLPX_TIME_COUNT (dev_priv->mipi_mmio_base + 0xb0a4) -+#define _MIPIC_TLPX_TIME_COUNT (dev_priv->mipi_mmio_base + 0xb8a4) -+#define MIPI_TLPX_TIME_COUNT(port) _MMIO_MIPI(port, _MIPIA_TLPX_TIME_COUNT, _MIPIC_TLPX_TIME_COUNT) -+ -+#define _MIPIA_CLK_LANE_TIMING (dev_priv->mipi_mmio_base + 0xb098) -+#define _MIPIC_CLK_LANE_TIMING (dev_priv->mipi_mmio_base + 0xb898) -+#define MIPI_CLK_LANE_TIMING(port) _MMIO_MIPI(port, _MIPIA_CLK_LANE_TIMING, _MIPIC_CLK_LANE_TIMING) -+ -+/* bits 31:0 */ -+#define _MIPIA_LP_GEN_DATA (dev_priv->mipi_mmio_base + 0xb064) -+#define _MIPIC_LP_GEN_DATA (dev_priv->mipi_mmio_base + 0xb864) -+#define MIPI_LP_GEN_DATA(port) _MMIO_MIPI(port, _MIPIA_LP_GEN_DATA, _MIPIC_LP_GEN_DATA) -+ -+/* bits 31:0 */ -+#define _MIPIA_HS_GEN_DATA (dev_priv->mipi_mmio_base + 0xb068) -+#define _MIPIC_HS_GEN_DATA (dev_priv->mipi_mmio_base + 0xb868) -+#define MIPI_HS_GEN_DATA(port) _MMIO_MIPI(port, _MIPIA_HS_GEN_DATA, _MIPIC_HS_GEN_DATA) -+ -+#define _MIPIA_LP_GEN_CTRL (dev_priv->mipi_mmio_base + 0xb06c) -+#define _MIPIC_LP_GEN_CTRL (dev_priv->mipi_mmio_base + 0xb86c) -+#define MIPI_LP_GEN_CTRL(port) _MMIO_MIPI(port, _MIPIA_LP_GEN_CTRL, _MIPIC_LP_GEN_CTRL) -+#define _MIPIA_HS_GEN_CTRL (dev_priv->mipi_mmio_base + 0xb070) -+#define _MIPIC_HS_GEN_CTRL (dev_priv->mipi_mmio_base + 0xb870) -+#define MIPI_HS_GEN_CTRL(port) _MMIO_MIPI(port, _MIPIA_HS_GEN_CTRL, _MIPIC_HS_GEN_CTRL) -+#define LONG_PACKET_WORD_COUNT_SHIFT 8 -+#define LONG_PACKET_WORD_COUNT_MASK (0xffff << 8) -+#define SHORT_PACKET_PARAM_SHIFT 8 -+#define SHORT_PACKET_PARAM_MASK (0xffff << 8) -+#define VIRTUAL_CHANNEL_SHIFT 6 -+#define VIRTUAL_CHANNEL_MASK (3 << 6) -+#define DATA_TYPE_SHIFT 0 -+#define DATA_TYPE_MASK (0x3f << 0) -+/* data type values, see include/video/mipi_display.h */ -+ -+#define _MIPIA_GEN_FIFO_STAT (dev_priv->mipi_mmio_base + 0xb074) -+#define _MIPIC_GEN_FIFO_STAT (dev_priv->mipi_mmio_base + 0xb874) -+#define MIPI_GEN_FIFO_STAT(port) _MMIO_MIPI(port, _MIPIA_GEN_FIFO_STAT, _MIPIC_GEN_FIFO_STAT) -+#define DPI_FIFO_EMPTY (1 << 28) -+#define DBI_FIFO_EMPTY (1 << 27) -+#define LP_CTRL_FIFO_EMPTY (1 << 26) -+#define LP_CTRL_FIFO_HALF_EMPTY (1 << 25) -+#define LP_CTRL_FIFO_FULL (1 << 24) -+#define HS_CTRL_FIFO_EMPTY (1 << 18) -+#define HS_CTRL_FIFO_HALF_EMPTY (1 << 17) -+#define HS_CTRL_FIFO_FULL (1 << 16) -+#define LP_DATA_FIFO_EMPTY (1 << 10) -+#define LP_DATA_FIFO_HALF_EMPTY (1 << 9) -+#define LP_DATA_FIFO_FULL (1 << 8) -+#define HS_DATA_FIFO_EMPTY (1 << 2) -+#define HS_DATA_FIFO_HALF_EMPTY (1 << 1) -+#define HS_DATA_FIFO_FULL (1 << 0) -+ -+#define _MIPIA_HS_LS_DBI_ENABLE (dev_priv->mipi_mmio_base + 0xb078) -+#define _MIPIC_HS_LS_DBI_ENABLE (dev_priv->mipi_mmio_base + 0xb878) -+#define MIPI_HS_LP_DBI_ENABLE(port) _MMIO_MIPI(port, _MIPIA_HS_LS_DBI_ENABLE, _MIPIC_HS_LS_DBI_ENABLE) -+#define DBI_HS_LP_MODE_MASK (1 << 0) -+#define DBI_LP_MODE (1 << 0) -+#define DBI_HS_MODE (0 << 0) -+ -+#define _MIPIA_DPHY_PARAM (dev_priv->mipi_mmio_base + 0xb080) -+#define _MIPIC_DPHY_PARAM (dev_priv->mipi_mmio_base + 0xb880) -+#define MIPI_DPHY_PARAM(port) _MMIO_MIPI(port, _MIPIA_DPHY_PARAM, _MIPIC_DPHY_PARAM) -+#define EXIT_ZERO_COUNT_SHIFT 24 -+#define EXIT_ZERO_COUNT_MASK (0x3f << 24) -+#define TRAIL_COUNT_SHIFT 16 -+#define TRAIL_COUNT_MASK (0x1f << 16) -+#define CLK_ZERO_COUNT_SHIFT 8 -+#define CLK_ZERO_COUNT_MASK (0xff << 8) -+#define PREPARE_COUNT_SHIFT 0 -+#define PREPARE_COUNT_MASK (0x3f << 0) -+ -+#define _ICL_DSI_T_INIT_MASTER_0 0x6b088 -+#define _ICL_DSI_T_INIT_MASTER_1 0x6b888 -+#define ICL_DSI_T_INIT_MASTER(port) _MMIO_PORT(port, \ -+ _ICL_DSI_T_INIT_MASTER_0,\ -+ _ICL_DSI_T_INIT_MASTER_1) -+ -+#define _DPHY_CLK_TIMING_PARAM_0 0x162180 -+#define _DPHY_CLK_TIMING_PARAM_1 0x6c180 -+#define DPHY_CLK_TIMING_PARAM(port) _MMIO_PORT(port, \ -+ _DPHY_CLK_TIMING_PARAM_0,\ -+ _DPHY_CLK_TIMING_PARAM_1) -+#define _DSI_CLK_TIMING_PARAM_0 0x6b080 -+#define _DSI_CLK_TIMING_PARAM_1 0x6b880 -+#define DSI_CLK_TIMING_PARAM(port) _MMIO_PORT(port, \ -+ _DSI_CLK_TIMING_PARAM_0,\ -+ _DSI_CLK_TIMING_PARAM_1) -+#define CLK_PREPARE_OVERRIDE (1 << 31) -+#define CLK_PREPARE(x) ((x) << 28) -+#define CLK_PREPARE_MASK (0x7 << 28) -+#define CLK_PREPARE_SHIFT 28 -+#define CLK_ZERO_OVERRIDE (1 << 27) -+#define CLK_ZERO(x) ((x) << 20) -+#define CLK_ZERO_MASK (0xf << 20) -+#define CLK_ZERO_SHIFT 20 -+#define CLK_PRE_OVERRIDE (1 << 19) -+#define CLK_PRE(x) ((x) << 16) -+#define CLK_PRE_MASK (0x3 << 16) -+#define CLK_PRE_SHIFT 16 -+#define CLK_POST_OVERRIDE (1 << 15) -+#define CLK_POST(x) ((x) << 8) -+#define CLK_POST_MASK (0x7 << 8) -+#define CLK_POST_SHIFT 8 -+#define CLK_TRAIL_OVERRIDE (1 << 7) -+#define CLK_TRAIL(x) ((x) << 0) -+#define CLK_TRAIL_MASK (0xf << 0) -+#define CLK_TRAIL_SHIFT 0 -+ -+#define _DPHY_DATA_TIMING_PARAM_0 0x162184 -+#define _DPHY_DATA_TIMING_PARAM_1 0x6c184 -+#define DPHY_DATA_TIMING_PARAM(port) _MMIO_PORT(port, \ -+ _DPHY_DATA_TIMING_PARAM_0,\ -+ _DPHY_DATA_TIMING_PARAM_1) -+#define _DSI_DATA_TIMING_PARAM_0 0x6B084 -+#define _DSI_DATA_TIMING_PARAM_1 0x6B884 -+#define DSI_DATA_TIMING_PARAM(port) _MMIO_PORT(port, \ -+ _DSI_DATA_TIMING_PARAM_0,\ -+ _DSI_DATA_TIMING_PARAM_1) -+#define HS_PREPARE_OVERRIDE (1 << 31) -+#define HS_PREPARE(x) ((x) << 24) -+#define HS_PREPARE_MASK (0x7 << 24) -+#define HS_PREPARE_SHIFT 24 -+#define HS_ZERO_OVERRIDE (1 << 23) -+#define HS_ZERO(x) ((x) << 16) -+#define HS_ZERO_MASK (0xf << 16) -+#define HS_ZERO_SHIFT 16 -+#define HS_TRAIL_OVERRIDE (1 << 15) -+#define HS_TRAIL(x) ((x) << 8) -+#define HS_TRAIL_MASK (0x7 << 8) -+#define HS_TRAIL_SHIFT 8 -+#define HS_EXIT_OVERRIDE (1 << 7) -+#define HS_EXIT(x) ((x) << 0) -+#define HS_EXIT_MASK (0x7 << 0) -+#define HS_EXIT_SHIFT 0 -+ -+#define _DPHY_TA_TIMING_PARAM_0 0x162188 -+#define _DPHY_TA_TIMING_PARAM_1 0x6c188 -+#define DPHY_TA_TIMING_PARAM(port) _MMIO_PORT(port, \ -+ _DPHY_TA_TIMING_PARAM_0,\ -+ _DPHY_TA_TIMING_PARAM_1) -+#define _DSI_TA_TIMING_PARAM_0 0x6b098 -+#define _DSI_TA_TIMING_PARAM_1 0x6b898 -+#define DSI_TA_TIMING_PARAM(port) _MMIO_PORT(port, \ -+ _DSI_TA_TIMING_PARAM_0,\ -+ _DSI_TA_TIMING_PARAM_1) -+#define TA_SURE_OVERRIDE (1 << 31) -+#define TA_SURE(x) ((x) << 16) -+#define TA_SURE_MASK (0x1f << 16) -+#define TA_SURE_SHIFT 16 -+#define TA_GO_OVERRIDE (1 << 15) -+#define TA_GO(x) ((x) << 8) -+#define TA_GO_MASK (0xf << 8) -+#define TA_GO_SHIFT 8 -+#define TA_GET_OVERRIDE (1 << 7) -+#define TA_GET(x) ((x) << 0) -+#define TA_GET_MASK (0xf << 0) -+#define TA_GET_SHIFT 0 -+ -+/* DSI transcoder configuration */ -+#define _DSI_TRANS_FUNC_CONF_0 0x6b030 -+#define _DSI_TRANS_FUNC_CONF_1 0x6b830 -+#define DSI_TRANS_FUNC_CONF(tc) _MMIO_DSI(tc, \ -+ _DSI_TRANS_FUNC_CONF_0,\ -+ _DSI_TRANS_FUNC_CONF_1) -+#define OP_MODE_MASK (0x3 << 28) -+#define OP_MODE_SHIFT 28 -+#define CMD_MODE_NO_GATE (0x0 << 28) -+#define CMD_MODE_TE_GATE (0x1 << 28) -+#define VIDEO_MODE_SYNC_EVENT (0x2 << 28) -+#define VIDEO_MODE_SYNC_PULSE (0x3 << 28) -+#define LINK_READY (1 << 20) -+#define PIX_FMT_MASK (0x3 << 16) -+#define PIX_FMT_SHIFT 16 -+#define PIX_FMT_RGB565 (0x0 << 16) -+#define PIX_FMT_RGB666_PACKED (0x1 << 16) -+#define PIX_FMT_RGB666_LOOSE (0x2 << 16) -+#define PIX_FMT_RGB888 (0x3 << 16) -+#define PIX_FMT_RGB101010 (0x4 << 16) -+#define PIX_FMT_RGB121212 (0x5 << 16) -+#define PIX_FMT_COMPRESSED (0x6 << 16) -+#define BGR_TRANSMISSION (1 << 15) -+#define PIX_VIRT_CHAN(x) ((x) << 12) -+#define PIX_VIRT_CHAN_MASK (0x3 << 12) -+#define PIX_VIRT_CHAN_SHIFT 12 -+#define PIX_BUF_THRESHOLD_MASK (0x3 << 10) -+#define PIX_BUF_THRESHOLD_SHIFT 10 -+#define PIX_BUF_THRESHOLD_1_4 (0x0 << 10) -+#define PIX_BUF_THRESHOLD_1_2 (0x1 << 10) -+#define PIX_BUF_THRESHOLD_3_4 (0x2 << 10) -+#define PIX_BUF_THRESHOLD_FULL (0x3 << 10) -+#define CONTINUOUS_CLK_MASK (0x3 << 8) -+#define CONTINUOUS_CLK_SHIFT 8 -+#define CLK_ENTER_LP_AFTER_DATA (0x0 << 8) -+#define CLK_HS_OR_LP (0x2 << 8) -+#define CLK_HS_CONTINUOUS (0x3 << 8) -+#define LINK_CALIBRATION_MASK (0x3 << 4) -+#define LINK_CALIBRATION_SHIFT 4 -+#define CALIBRATION_DISABLED (0x0 << 4) -+#define CALIBRATION_ENABLED_INITIAL_ONLY (0x2 << 4) -+#define CALIBRATION_ENABLED_INITIAL_PERIODIC (0x3 << 4) -+#define S3D_ORIENTATION_LANDSCAPE (1 << 1) -+#define EOTP_DISABLED (1 << 0) -+ -+#define _DSI_CMD_RXCTL_0 0x6b0d4 -+#define _DSI_CMD_RXCTL_1 0x6b8d4 -+#define DSI_CMD_RXCTL(tc) _MMIO_DSI(tc, \ -+ _DSI_CMD_RXCTL_0,\ -+ _DSI_CMD_RXCTL_1) -+#define READ_UNLOADS_DW (1 << 16) -+#define RECEIVED_UNASSIGNED_TRIGGER (1 << 15) -+#define RECEIVED_ACKNOWLEDGE_TRIGGER (1 << 14) -+#define RECEIVED_TEAR_EFFECT_TRIGGER (1 << 13) -+#define RECEIVED_RESET_TRIGGER (1 << 12) -+#define RECEIVED_PAYLOAD_WAS_LOST (1 << 11) -+#define RECEIVED_CRC_WAS_LOST (1 << 10) -+#define NUMBER_RX_PLOAD_DW_MASK (0xff << 0) -+#define NUMBER_RX_PLOAD_DW_SHIFT 0 -+ -+#define _DSI_CMD_TXCTL_0 0x6b0d0 -+#define _DSI_CMD_TXCTL_1 0x6b8d0 -+#define DSI_CMD_TXCTL(tc) _MMIO_DSI(tc, \ -+ _DSI_CMD_TXCTL_0,\ -+ _DSI_CMD_TXCTL_1) -+#define KEEP_LINK_IN_HS (1 << 24) -+#define FREE_HEADER_CREDIT_MASK (0x1f << 8) -+#define FREE_HEADER_CREDIT_SHIFT 0x8 -+#define FREE_PLOAD_CREDIT_MASK (0xff << 0) -+#define FREE_PLOAD_CREDIT_SHIFT 0 -+#define MAX_HEADER_CREDIT 0x10 -+#define MAX_PLOAD_CREDIT 0x40 -+ -+#define _DSI_CMD_TXHDR_0 0x6b100 -+#define _DSI_CMD_TXHDR_1 0x6b900 -+#define DSI_CMD_TXHDR(tc) _MMIO_DSI(tc, \ -+ _DSI_CMD_TXHDR_0,\ -+ _DSI_CMD_TXHDR_1) -+#define PAYLOAD_PRESENT (1 << 31) -+#define LP_DATA_TRANSFER (1 << 30) -+#define VBLANK_FENCE (1 << 29) -+#define PARAM_WC_MASK (0xffff << 8) -+#define PARAM_WC_LOWER_SHIFT 8 -+#define PARAM_WC_UPPER_SHIFT 16 -+#define VC_MASK (0x3 << 6) -+#define VC_SHIFT 6 -+#define DT_MASK (0x3f << 0) -+#define DT_SHIFT 0 -+ -+#define _DSI_CMD_TXPYLD_0 0x6b104 -+#define _DSI_CMD_TXPYLD_1 0x6b904 -+#define DSI_CMD_TXPYLD(tc) _MMIO_DSI(tc, \ -+ _DSI_CMD_TXPYLD_0,\ -+ _DSI_CMD_TXPYLD_1) -+ -+#define _DSI_LP_MSG_0 0x6b0d8 -+#define _DSI_LP_MSG_1 0x6b8d8 -+#define DSI_LP_MSG(tc) _MMIO_DSI(tc, \ -+ _DSI_LP_MSG_0,\ -+ _DSI_LP_MSG_1) -+#define LPTX_IN_PROGRESS (1 << 17) -+#define LINK_IN_ULPS (1 << 16) -+#define LINK_ULPS_TYPE_LP11 (1 << 8) -+#define LINK_ENTER_ULPS (1 << 0) -+ -+/* DSI timeout registers */ -+#define _DSI_HSTX_TO_0 0x6b044 -+#define _DSI_HSTX_TO_1 0x6b844 -+#define DSI_HSTX_TO(tc) _MMIO_DSI(tc, \ -+ _DSI_HSTX_TO_0,\ -+ _DSI_HSTX_TO_1) -+#define HSTX_TIMEOUT_VALUE_MASK (0xffff << 16) -+#define HSTX_TIMEOUT_VALUE_SHIFT 16 -+#define HSTX_TIMEOUT_VALUE(x) ((x) << 16) -+#define HSTX_TIMED_OUT (1 << 0) -+ -+#define _DSI_LPRX_HOST_TO_0 0x6b048 -+#define _DSI_LPRX_HOST_TO_1 0x6b848 -+#define DSI_LPRX_HOST_TO(tc) _MMIO_DSI(tc, \ -+ _DSI_LPRX_HOST_TO_0,\ -+ _DSI_LPRX_HOST_TO_1) -+#define LPRX_TIMED_OUT (1 << 16) -+#define LPRX_TIMEOUT_VALUE_MASK (0xffff << 0) -+#define LPRX_TIMEOUT_VALUE_SHIFT 0 -+#define LPRX_TIMEOUT_VALUE(x) ((x) << 0) -+ -+#define _DSI_PWAIT_TO_0 0x6b040 -+#define _DSI_PWAIT_TO_1 0x6b840 -+#define DSI_PWAIT_TO(tc) _MMIO_DSI(tc, \ -+ _DSI_PWAIT_TO_0,\ -+ _DSI_PWAIT_TO_1) -+#define PRESET_TIMEOUT_VALUE_MASK (0xffff << 16) -+#define PRESET_TIMEOUT_VALUE_SHIFT 16 -+#define PRESET_TIMEOUT_VALUE(x) ((x) << 16) -+#define PRESPONSE_TIMEOUT_VALUE_MASK (0xffff << 0) -+#define PRESPONSE_TIMEOUT_VALUE_SHIFT 0 -+#define PRESPONSE_TIMEOUT_VALUE(x) ((x) << 0) -+ -+#define _DSI_TA_TO_0 0x6b04c -+#define _DSI_TA_TO_1 0x6b84c -+#define DSI_TA_TO(tc) _MMIO_DSI(tc, \ -+ _DSI_TA_TO_0,\ -+ _DSI_TA_TO_1) -+#define TA_TIMED_OUT (1 << 16) -+#define TA_TIMEOUT_VALUE_MASK (0xffff << 0) -+#define TA_TIMEOUT_VALUE_SHIFT 0 -+#define TA_TIMEOUT_VALUE(x) ((x) << 0) -+ -+/* bits 31:0 */ -+#define _MIPIA_DBI_BW_CTRL (dev_priv->mipi_mmio_base + 0xb084) -+#define _MIPIC_DBI_BW_CTRL (dev_priv->mipi_mmio_base + 0xb884) -+#define MIPI_DBI_BW_CTRL(port) _MMIO_MIPI(port, _MIPIA_DBI_BW_CTRL, _MIPIC_DBI_BW_CTRL) -+ -+#define _MIPIA_CLK_LANE_SWITCH_TIME_CNT (dev_priv->mipi_mmio_base + 0xb088) -+#define _MIPIC_CLK_LANE_SWITCH_TIME_CNT (dev_priv->mipi_mmio_base + 0xb888) -+#define MIPI_CLK_LANE_SWITCH_TIME_CNT(port) _MMIO_MIPI(port, _MIPIA_CLK_LANE_SWITCH_TIME_CNT, _MIPIC_CLK_LANE_SWITCH_TIME_CNT) -+#define LP_HS_SSW_CNT_SHIFT 16 -+#define LP_HS_SSW_CNT_MASK (0xffff << 16) -+#define HS_LP_PWR_SW_CNT_SHIFT 0 -+#define HS_LP_PWR_SW_CNT_MASK (0xffff << 0) -+ -+#define _MIPIA_STOP_STATE_STALL (dev_priv->mipi_mmio_base + 0xb08c) -+#define _MIPIC_STOP_STATE_STALL (dev_priv->mipi_mmio_base + 0xb88c) -+#define MIPI_STOP_STATE_STALL(port) _MMIO_MIPI(port, _MIPIA_STOP_STATE_STALL, _MIPIC_STOP_STATE_STALL) -+#define STOP_STATE_STALL_COUNTER_SHIFT 0 -+#define STOP_STATE_STALL_COUNTER_MASK (0xff << 0) -+ -+#define _MIPIA_INTR_STAT_REG_1 (dev_priv->mipi_mmio_base + 0xb090) -+#define _MIPIC_INTR_STAT_REG_1 (dev_priv->mipi_mmio_base + 0xb890) -+#define MIPI_INTR_STAT_REG_1(port) _MMIO_MIPI(port, _MIPIA_INTR_STAT_REG_1, _MIPIC_INTR_STAT_REG_1) -+#define _MIPIA_INTR_EN_REG_1 (dev_priv->mipi_mmio_base + 0xb094) -+#define _MIPIC_INTR_EN_REG_1 (dev_priv->mipi_mmio_base + 0xb894) -+#define MIPI_INTR_EN_REG_1(port) _MMIO_MIPI(port, _MIPIA_INTR_EN_REG_1, _MIPIC_INTR_EN_REG_1) -+#define RX_CONTENTION_DETECTED (1 << 0) -+ -+/* XXX: only pipe A ?!? */ -+#define MIPIA_DBI_TYPEC_CTRL (dev_priv->mipi_mmio_base + 0xb100) -+#define DBI_TYPEC_ENABLE (1 << 31) -+#define DBI_TYPEC_WIP (1 << 30) -+#define DBI_TYPEC_OPTION_SHIFT 28 -+#define DBI_TYPEC_OPTION_MASK (3 << 28) -+#define DBI_TYPEC_FREQ_SHIFT 24 -+#define DBI_TYPEC_FREQ_MASK (0xf << 24) -+#define DBI_TYPEC_OVERRIDE (1 << 8) -+#define DBI_TYPEC_OVERRIDE_COUNTER_SHIFT 0 -+#define DBI_TYPEC_OVERRIDE_COUNTER_MASK (0xff << 0) -+ -+ -+/* MIPI adapter registers */ -+ -+#define _MIPIA_CTRL (dev_priv->mipi_mmio_base + 0xb104) -+#define _MIPIC_CTRL (dev_priv->mipi_mmio_base + 0xb904) -+#define MIPI_CTRL(port) _MMIO_MIPI(port, _MIPIA_CTRL, _MIPIC_CTRL) -+#define ESCAPE_CLOCK_DIVIDER_SHIFT 5 /* A only */ -+#define ESCAPE_CLOCK_DIVIDER_MASK (3 << 5) -+#define ESCAPE_CLOCK_DIVIDER_1 (0 << 5) -+#define ESCAPE_CLOCK_DIVIDER_2 (1 << 5) -+#define ESCAPE_CLOCK_DIVIDER_4 (2 << 5) -+#define READ_REQUEST_PRIORITY_SHIFT 3 -+#define READ_REQUEST_PRIORITY_MASK (3 << 3) -+#define READ_REQUEST_PRIORITY_LOW (0 << 3) -+#define READ_REQUEST_PRIORITY_HIGH (3 << 3) -+#define RGB_FLIP_TO_BGR (1 << 2) -+ -+#define BXT_PIPE_SELECT_SHIFT 7 -+#define BXT_PIPE_SELECT_MASK (7 << 7) -+#define BXT_PIPE_SELECT(pipe) ((pipe) << 7) -+#define GLK_PHY_STATUS_PORT_READY (1 << 31) /* RO */ -+#define GLK_ULPS_NOT_ACTIVE (1 << 30) /* RO */ -+#define GLK_MIPIIO_RESET_RELEASED (1 << 28) -+#define GLK_CLOCK_LANE_STOP_STATE (1 << 27) /* RO */ -+#define GLK_DATA_LANE_STOP_STATE (1 << 26) /* RO */ -+#define GLK_LP_WAKE (1 << 22) -+#define GLK_LP11_LOW_PWR_MODE (1 << 21) -+#define GLK_LP00_LOW_PWR_MODE (1 << 20) -+#define GLK_FIREWALL_ENABLE (1 << 16) -+#define BXT_PIXEL_OVERLAP_CNT_MASK (0xf << 10) -+#define BXT_PIXEL_OVERLAP_CNT_SHIFT 10 -+#define BXT_DSC_ENABLE (1 << 3) -+#define BXT_RGB_FLIP (1 << 2) -+#define GLK_MIPIIO_PORT_POWERED (1 << 1) /* RO */ -+#define GLK_MIPIIO_ENABLE (1 << 0) -+ -+#define _MIPIA_DATA_ADDRESS (dev_priv->mipi_mmio_base + 0xb108) -+#define _MIPIC_DATA_ADDRESS (dev_priv->mipi_mmio_base + 0xb908) -+#define MIPI_DATA_ADDRESS(port) _MMIO_MIPI(port, _MIPIA_DATA_ADDRESS, _MIPIC_DATA_ADDRESS) -+#define DATA_MEM_ADDRESS_SHIFT 5 -+#define DATA_MEM_ADDRESS_MASK (0x7ffffff << 5) -+#define DATA_VALID (1 << 0) -+ -+#define _MIPIA_DATA_LENGTH (dev_priv->mipi_mmio_base + 0xb10c) -+#define _MIPIC_DATA_LENGTH (dev_priv->mipi_mmio_base + 0xb90c) -+#define MIPI_DATA_LENGTH(port) _MMIO_MIPI(port, _MIPIA_DATA_LENGTH, _MIPIC_DATA_LENGTH) -+#define DATA_LENGTH_SHIFT 0 -+#define DATA_LENGTH_MASK (0xfffff << 0) -+ -+#define _MIPIA_COMMAND_ADDRESS (dev_priv->mipi_mmio_base + 0xb110) -+#define _MIPIC_COMMAND_ADDRESS (dev_priv->mipi_mmio_base + 0xb910) -+#define MIPI_COMMAND_ADDRESS(port) _MMIO_MIPI(port, _MIPIA_COMMAND_ADDRESS, _MIPIC_COMMAND_ADDRESS) -+#define COMMAND_MEM_ADDRESS_SHIFT 5 -+#define COMMAND_MEM_ADDRESS_MASK (0x7ffffff << 5) -+#define AUTO_PWG_ENABLE (1 << 2) -+#define MEMORY_WRITE_DATA_FROM_PIPE_RENDERING (1 << 1) -+#define COMMAND_VALID (1 << 0) -+ -+#define _MIPIA_COMMAND_LENGTH (dev_priv->mipi_mmio_base + 0xb114) -+#define _MIPIC_COMMAND_LENGTH (dev_priv->mipi_mmio_base + 0xb914) -+#define MIPI_COMMAND_LENGTH(port) _MMIO_MIPI(port, _MIPIA_COMMAND_LENGTH, _MIPIC_COMMAND_LENGTH) -+#define COMMAND_LENGTH_SHIFT(n) (8 * (n)) /* n: 0...3 */ -+#define COMMAND_LENGTH_MASK(n) (0xff << (8 * (n))) -+ -+#define _MIPIA_READ_DATA_RETURN0 (dev_priv->mipi_mmio_base + 0xb118) -+#define _MIPIC_READ_DATA_RETURN0 (dev_priv->mipi_mmio_base + 0xb918) -+#define MIPI_READ_DATA_RETURN(port, n) _MMIO(_MIPI(port, _MIPIA_READ_DATA_RETURN0, _MIPIC_READ_DATA_RETURN0) + 4 * (n)) /* n: 0...7 */ -+ -+#define _MIPIA_READ_DATA_VALID (dev_priv->mipi_mmio_base + 0xb138) -+#define _MIPIC_READ_DATA_VALID (dev_priv->mipi_mmio_base + 0xb938) -+#define MIPI_READ_DATA_VALID(port) _MMIO_MIPI(port, _MIPIA_READ_DATA_VALID, _MIPIC_READ_DATA_VALID) -+#define READ_DATA_VALID(n) (1 << (n)) -+ -+/* MOCS (Memory Object Control State) registers */ -+#define GEN9_LNCFCMOCS(i) _MMIO(0xb020 + (i) * 4) /* L3 Cache Control */ -+ -+#define GEN9_GFX_MOCS(i) _MMIO(0xc800 + (i) * 4) /* Graphics MOCS registers */ -+#define GEN9_MFX0_MOCS(i) _MMIO(0xc900 + (i) * 4) /* Media 0 MOCS registers */ -+#define GEN9_MFX1_MOCS(i) _MMIO(0xca00 + (i) * 4) /* Media 1 MOCS registers */ -+#define GEN9_VEBOX_MOCS(i) _MMIO(0xcb00 + (i) * 4) /* Video MOCS registers */ -+#define GEN9_BLT_MOCS(i) _MMIO(0xcc00 + (i) * 4) /* Blitter MOCS registers */ -+/* Media decoder 2 MOCS registers */ -+#define GEN11_MFX2_MOCS(i) _MMIO(0x10000 + (i) * 4) -+ -+#define GEN10_SCRATCH_LNCF2 _MMIO(0xb0a0) -+#define PMFLUSHDONE_LNICRSDROP (1 << 20) -+#define PMFLUSH_GAPL3UNBLOCK (1 << 21) -+#define PMFLUSHDONE_LNEBLK (1 << 22) -+ -+/* gamt regs */ -+#define GEN8_L3_LRA_1_GPGPU _MMIO(0x4dd4) -+#define GEN8_L3_LRA_1_GPGPU_DEFAULT_VALUE_BDW 0x67F1427F /* max/min for LRA1/2 */ -+#define GEN8_L3_LRA_1_GPGPU_DEFAULT_VALUE_CHV 0x5FF101FF /* max/min for LRA1/2 */ -+#define GEN9_L3_LRA_1_GPGPU_DEFAULT_VALUE_SKL 0x67F1427F /* " " */ -+#define GEN9_L3_LRA_1_GPGPU_DEFAULT_VALUE_BXT 0x5FF101FF /* " " */ -+ -+#define MMCD_MISC_CTRL _MMIO(0x4ddc) /* skl+ */ -+#define MMCD_PCLA (1 << 31) -+#define MMCD_HOTSPOT_EN (1 << 27) -+ -+#define _ICL_PHY_MISC_A 0x64C00 -+#define _ICL_PHY_MISC_B 0x64C04 -+#define ICL_PHY_MISC(port) _MMIO_PORT(port, _ICL_PHY_MISC_A, \ -+ _ICL_PHY_MISC_B) -+#define ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN (1 << 23) -+ -+/* Icelake Display Stream Compression Registers */ -+#define DSCA_PICTURE_PARAMETER_SET_0 _MMIO(0x6B200) -+#define DSCC_PICTURE_PARAMETER_SET_0 _MMIO(0x6BA00) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_0_PB 0x78270 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_0_PB 0x78370 -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_0_PC 0x78470 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_0_PC 0x78570 -+#define ICL_DSC0_PICTURE_PARAMETER_SET_0(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_0_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_0_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_0(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_0_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_0_PC) -+#define DSC_VBR_ENABLE (1 << 19) -+#define DSC_422_ENABLE (1 << 18) -+#define DSC_COLOR_SPACE_CONVERSION (1 << 17) -+#define DSC_BLOCK_PREDICTION (1 << 16) -+#define DSC_LINE_BUF_DEPTH_SHIFT 12 -+#define DSC_BPC_SHIFT 8 -+#define DSC_VER_MIN_SHIFT 4 -+#define DSC_VER_MAJ (0x1 << 0) -+ -+#define DSCA_PICTURE_PARAMETER_SET_1 _MMIO(0x6B204) -+#define DSCC_PICTURE_PARAMETER_SET_1 _MMIO(0x6BA04) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_1_PB 0x78274 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_1_PB 0x78374 -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_1_PC 0x78474 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_1_PC 0x78574 -+#define ICL_DSC0_PICTURE_PARAMETER_SET_1(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_1_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_1_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_1(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_1_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_1_PC) -+#define DSC_BPP(bpp) ((bpp) << 0) -+ -+#define DSCA_PICTURE_PARAMETER_SET_2 _MMIO(0x6B208) -+#define DSCC_PICTURE_PARAMETER_SET_2 _MMIO(0x6BA08) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_2_PB 0x78278 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_2_PB 0x78378 -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_2_PC 0x78478 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_2_PC 0x78578 -+#define ICL_DSC0_PICTURE_PARAMETER_SET_2(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_2_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_2_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_2(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_2_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_2_PC) -+#define DSC_PIC_WIDTH(pic_width) ((pic_width) << 16) -+#define DSC_PIC_HEIGHT(pic_height) ((pic_height) << 0) -+ -+#define DSCA_PICTURE_PARAMETER_SET_3 _MMIO(0x6B20C) -+#define DSCC_PICTURE_PARAMETER_SET_3 _MMIO(0x6BA0C) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_3_PB 0x7827C -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_3_PB 0x7837C -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_3_PC 0x7847C -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_3_PC 0x7857C -+#define ICL_DSC0_PICTURE_PARAMETER_SET_3(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_3_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_3_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_3(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_3_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_3_PC) -+#define DSC_SLICE_WIDTH(slice_width) ((slice_width) << 16) -+#define DSC_SLICE_HEIGHT(slice_height) ((slice_height) << 0) -+ -+#define DSCA_PICTURE_PARAMETER_SET_4 _MMIO(0x6B210) -+#define DSCC_PICTURE_PARAMETER_SET_4 _MMIO(0x6BA10) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_4_PB 0x78280 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_4_PB 0x78380 -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_4_PC 0x78480 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_4_PC 0x78580 -+#define ICL_DSC0_PICTURE_PARAMETER_SET_4(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_4_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_4_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_4(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_4_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_4_PC) -+#define DSC_INITIAL_DEC_DELAY(dec_delay) ((dec_delay) << 16) -+#define DSC_INITIAL_XMIT_DELAY(xmit_delay) ((xmit_delay) << 0) -+ -+#define DSCA_PICTURE_PARAMETER_SET_5 _MMIO(0x6B214) -+#define DSCC_PICTURE_PARAMETER_SET_5 _MMIO(0x6BA14) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_5_PB 0x78284 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_5_PB 0x78384 -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_5_PC 0x78484 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_5_PC 0x78584 -+#define ICL_DSC0_PICTURE_PARAMETER_SET_5(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_5_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_5_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_5(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_5_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_5_PC) -+#define DSC_SCALE_DEC_INT(scale_dec) ((scale_dec) << 16) -+#define DSC_SCALE_INC_INT(scale_inc) ((scale_inc) << 0) -+ -+#define DSCA_PICTURE_PARAMETER_SET_6 _MMIO(0x6B218) -+#define DSCC_PICTURE_PARAMETER_SET_6 _MMIO(0x6BA18) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_6_PB 0x78288 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_6_PB 0x78388 -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_6_PC 0x78488 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_6_PC 0x78588 -+#define ICL_DSC0_PICTURE_PARAMETER_SET_6(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_6_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_6_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_6(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_6_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_6_PC) -+#define DSC_FLATNESS_MAX_QP(max_qp) ((max_qp) << 24) -+#define DSC_FLATNESS_MIN_QP(min_qp) ((min_qp) << 16) -+#define DSC_FIRST_LINE_BPG_OFFSET(offset) ((offset) << 8) -+#define DSC_INITIAL_SCALE_VALUE(value) ((value) << 0) -+ -+#define DSCA_PICTURE_PARAMETER_SET_7 _MMIO(0x6B21C) -+#define DSCC_PICTURE_PARAMETER_SET_7 _MMIO(0x6BA1C) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_7_PB 0x7828C -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_7_PB 0x7838C -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_7_PC 0x7848C -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_7_PC 0x7858C -+#define ICL_DSC0_PICTURE_PARAMETER_SET_7(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_7_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_7_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_7(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_7_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_7_PC) -+#define DSC_NFL_BPG_OFFSET(bpg_offset) ((bpg_offset) << 16) -+#define DSC_SLICE_BPG_OFFSET(bpg_offset) ((bpg_offset) << 0) -+ -+#define DSCA_PICTURE_PARAMETER_SET_8 _MMIO(0x6B220) -+#define DSCC_PICTURE_PARAMETER_SET_8 _MMIO(0x6BA20) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_8_PB 0x78290 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_8_PB 0x78390 -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_8_PC 0x78490 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_8_PC 0x78590 -+#define ICL_DSC0_PICTURE_PARAMETER_SET_8(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_8_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_8_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_8(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_8_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_8_PC) -+#define DSC_INITIAL_OFFSET(initial_offset) ((initial_offset) << 16) -+#define DSC_FINAL_OFFSET(final_offset) ((final_offset) << 0) -+ -+#define DSCA_PICTURE_PARAMETER_SET_9 _MMIO(0x6B224) -+#define DSCC_PICTURE_PARAMETER_SET_9 _MMIO(0x6BA24) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_9_PB 0x78294 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_9_PB 0x78394 -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_9_PC 0x78494 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_9_PC 0x78594 -+#define ICL_DSC0_PICTURE_PARAMETER_SET_9(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_9_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_9_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_9(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_9_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_9_PC) -+#define DSC_RC_EDGE_FACTOR(rc_edge_fact) ((rc_edge_fact) << 16) -+#define DSC_RC_MODEL_SIZE(rc_model_size) ((rc_model_size) << 0) -+ -+#define DSCA_PICTURE_PARAMETER_SET_10 _MMIO(0x6B228) -+#define DSCC_PICTURE_PARAMETER_SET_10 _MMIO(0x6BA28) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_10_PB 0x78298 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_10_PB 0x78398 -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_10_PC 0x78498 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_10_PC 0x78598 -+#define ICL_DSC0_PICTURE_PARAMETER_SET_10(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_10_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_10_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_10(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_10_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_10_PC) -+#define DSC_RC_TARGET_OFF_LOW(rc_tgt_off_low) ((rc_tgt_off_low) << 20) -+#define DSC_RC_TARGET_OFF_HIGH(rc_tgt_off_high) ((rc_tgt_off_high) << 16) -+#define DSC_RC_QUANT_INC_LIMIT1(lim) ((lim) << 8) -+#define DSC_RC_QUANT_INC_LIMIT0(lim) ((lim) << 0) -+ -+#define DSCA_PICTURE_PARAMETER_SET_11 _MMIO(0x6B22C) -+#define DSCC_PICTURE_PARAMETER_SET_11 _MMIO(0x6BA2C) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_11_PB 0x7829C -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_11_PB 0x7839C -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_11_PC 0x7849C -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_11_PC 0x7859C -+#define ICL_DSC0_PICTURE_PARAMETER_SET_11(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_11_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_11_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_11(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_11_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_11_PC) -+ -+#define DSCA_PICTURE_PARAMETER_SET_12 _MMIO(0x6B260) -+#define DSCC_PICTURE_PARAMETER_SET_12 _MMIO(0x6BA60) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_12_PB 0x782A0 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_12_PB 0x783A0 -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_12_PC 0x784A0 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_12_PC 0x785A0 -+#define ICL_DSC0_PICTURE_PARAMETER_SET_12(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_12_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_12_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_12(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_12_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_12_PC) -+ -+#define DSCA_PICTURE_PARAMETER_SET_13 _MMIO(0x6B264) -+#define DSCC_PICTURE_PARAMETER_SET_13 _MMIO(0x6BA64) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_13_PB 0x782A4 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_13_PB 0x783A4 -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_13_PC 0x784A4 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_13_PC 0x785A4 -+#define ICL_DSC0_PICTURE_PARAMETER_SET_13(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_13_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_13_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_13(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_13_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_13_PC) -+ -+#define DSCA_PICTURE_PARAMETER_SET_14 _MMIO(0x6B268) -+#define DSCC_PICTURE_PARAMETER_SET_14 _MMIO(0x6BA68) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_14_PB 0x782A8 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_14_PB 0x783A8 -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_14_PC 0x784A8 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_14_PC 0x785A8 -+#define ICL_DSC0_PICTURE_PARAMETER_SET_14(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_14_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_14_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_14(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_14_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_14_PC) -+ -+#define DSCA_PICTURE_PARAMETER_SET_15 _MMIO(0x6B26C) -+#define DSCC_PICTURE_PARAMETER_SET_15 _MMIO(0x6BA6C) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_15_PB 0x782AC -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_15_PB 0x783AC -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_15_PC 0x784AC -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_15_PC 0x785AC -+#define ICL_DSC0_PICTURE_PARAMETER_SET_15(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_15_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_15_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_15(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_15_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_15_PC) -+ -+#define DSCA_PICTURE_PARAMETER_SET_16 _MMIO(0x6B270) -+#define DSCC_PICTURE_PARAMETER_SET_16 _MMIO(0x6BA70) -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_16_PB 0x782B0 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_16_PB 0x783B0 -+#define _ICL_DSC0_PICTURE_PARAMETER_SET_16_PC 0x784B0 -+#define _ICL_DSC1_PICTURE_PARAMETER_SET_16_PC 0x785B0 -+#define ICL_DSC0_PICTURE_PARAMETER_SET_16(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_16_PB, \ -+ _ICL_DSC0_PICTURE_PARAMETER_SET_16_PC) -+#define ICL_DSC1_PICTURE_PARAMETER_SET_16(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_16_PB, \ -+ _ICL_DSC1_PICTURE_PARAMETER_SET_16_PC) -+#define DSC_SLICE_ROW_PER_FRAME(slice_row_per_frame) ((slice_row_per_frame) << 20) -+#define DSC_SLICE_PER_LINE(slice_per_line) ((slice_per_line) << 16) -+#define DSC_SLICE_CHUNK_SIZE(slice_chunk_size) ((slice_chunk_size) << 0) -+ -+/* Icelake Rate Control Buffer Threshold Registers */ -+#define DSCA_RC_BUF_THRESH_0 _MMIO(0x6B230) -+#define DSCA_RC_BUF_THRESH_0_UDW _MMIO(0x6B230 + 4) -+#define DSCC_RC_BUF_THRESH_0 _MMIO(0x6BA30) -+#define DSCC_RC_BUF_THRESH_0_UDW _MMIO(0x6BA30 + 4) -+#define _ICL_DSC0_RC_BUF_THRESH_0_PB (0x78254) -+#define _ICL_DSC0_RC_BUF_THRESH_0_UDW_PB (0x78254 + 4) -+#define _ICL_DSC1_RC_BUF_THRESH_0_PB (0x78354) -+#define _ICL_DSC1_RC_BUF_THRESH_0_UDW_PB (0x78354 + 4) -+#define _ICL_DSC0_RC_BUF_THRESH_0_PC (0x78454) -+#define _ICL_DSC0_RC_BUF_THRESH_0_UDW_PC (0x78454 + 4) -+#define _ICL_DSC1_RC_BUF_THRESH_0_PC (0x78554) -+#define _ICL_DSC1_RC_BUF_THRESH_0_UDW_PC (0x78554 + 4) -+#define ICL_DSC0_RC_BUF_THRESH_0(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_RC_BUF_THRESH_0_PB, \ -+ _ICL_DSC0_RC_BUF_THRESH_0_PC) -+#define ICL_DSC0_RC_BUF_THRESH_0_UDW(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_RC_BUF_THRESH_0_UDW_PB, \ -+ _ICL_DSC0_RC_BUF_THRESH_0_UDW_PC) -+#define ICL_DSC1_RC_BUF_THRESH_0(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_RC_BUF_THRESH_0_PB, \ -+ _ICL_DSC1_RC_BUF_THRESH_0_PC) -+#define ICL_DSC1_RC_BUF_THRESH_0_UDW(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_RC_BUF_THRESH_0_UDW_PB, \ -+ _ICL_DSC1_RC_BUF_THRESH_0_UDW_PC) -+ -+#define DSCA_RC_BUF_THRESH_1 _MMIO(0x6B238) -+#define DSCA_RC_BUF_THRESH_1_UDW _MMIO(0x6B238 + 4) -+#define DSCC_RC_BUF_THRESH_1 _MMIO(0x6BA38) -+#define DSCC_RC_BUF_THRESH_1_UDW _MMIO(0x6BA38 + 4) -+#define _ICL_DSC0_RC_BUF_THRESH_1_PB (0x7825C) -+#define _ICL_DSC0_RC_BUF_THRESH_1_UDW_PB (0x7825C + 4) -+#define _ICL_DSC1_RC_BUF_THRESH_1_PB (0x7835C) -+#define _ICL_DSC1_RC_BUF_THRESH_1_UDW_PB (0x7835C + 4) -+#define _ICL_DSC0_RC_BUF_THRESH_1_PC (0x7845C) -+#define _ICL_DSC0_RC_BUF_THRESH_1_UDW_PC (0x7845C + 4) -+#define _ICL_DSC1_RC_BUF_THRESH_1_PC (0x7855C) -+#define _ICL_DSC1_RC_BUF_THRESH_1_UDW_PC (0x7855C + 4) -+#define ICL_DSC0_RC_BUF_THRESH_1(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_RC_BUF_THRESH_1_PB, \ -+ _ICL_DSC0_RC_BUF_THRESH_1_PC) -+#define ICL_DSC0_RC_BUF_THRESH_1_UDW(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC0_RC_BUF_THRESH_1_UDW_PB, \ -+ _ICL_DSC0_RC_BUF_THRESH_1_UDW_PC) -+#define ICL_DSC1_RC_BUF_THRESH_1(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_RC_BUF_THRESH_1_PB, \ -+ _ICL_DSC1_RC_BUF_THRESH_1_PC) -+#define ICL_DSC1_RC_BUF_THRESH_1_UDW(pipe) _MMIO_PIPE((pipe) - PIPE_B, \ -+ _ICL_DSC1_RC_BUF_THRESH_1_UDW_PB, \ -+ _ICL_DSC1_RC_BUF_THRESH_1_UDW_PC) -+ -+#define PORT_TX_DFLEXDPSP _MMIO(FIA1_BASE + 0x008A0) -+#define TC_LIVE_STATE_TBT(tc_port) (1 << ((tc_port) * 8 + 6)) -+#define TC_LIVE_STATE_TC(tc_port) (1 << ((tc_port) * 8 + 5)) -+#define DP_LANE_ASSIGNMENT_SHIFT(tc_port) ((tc_port) * 8) -+#define DP_LANE_ASSIGNMENT_MASK(tc_port) (0xf << ((tc_port) * 8)) -+#define DP_LANE_ASSIGNMENT(tc_port, x) ((x) << ((tc_port) * 8)) -+ -+#define PORT_TX_DFLEXDPPMS _MMIO(FIA1_BASE + 0x00890) -+#define DP_PHY_MODE_STATUS_COMPLETED(tc_port) (1 << (tc_port)) -+ -+#define PORT_TX_DFLEXDPCSSS _MMIO(FIA1_BASE + 0x00894) -+#define DP_PHY_MODE_STATUS_NOT_SAFE(tc_port) (1 << (tc_port)) -+ -+#endif /* _I915_REG_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_request.c b/drivers/gpu/drm/i915_legacy/i915_request.c -new file mode 100644 -index 000000000000..81b48e273cbd ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_request.c -@@ -0,0 +1,1511 @@ -+/* -+ * Copyright © 2008-2015 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "i915_active.h" -+#include "i915_drv.h" -+#include "i915_globals.h" -+#include "i915_reset.h" -+#include "intel_pm.h" -+ -+struct execute_cb { -+ struct list_head link; -+ struct irq_work work; -+ struct i915_sw_fence *fence; -+}; -+ -+static struct i915_global_request { -+ struct i915_global base; -+ struct kmem_cache *slab_requests; -+ struct kmem_cache *slab_dependencies; -+ struct kmem_cache *slab_execute_cbs; -+} global; -+ -+static const char *i915_fence_get_driver_name(struct dma_fence *fence) -+{ -+ return "i915"; -+} -+ -+static const char *i915_fence_get_timeline_name(struct dma_fence *fence) -+{ -+ /* -+ * The timeline struct (as part of the ppgtt underneath a context) -+ * may be freed when the request is no longer in use by the GPU. -+ * We could extend the life of a context to beyond that of all -+ * fences, possibly keeping the hw resource around indefinitely, -+ * or we just give them a false name. Since -+ * dma_fence_ops.get_timeline_name is a debug feature, the occasional -+ * lie seems justifiable. -+ */ -+ if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags)) -+ return "signaled"; -+ -+ return to_request(fence)->gem_context->name ?: "[i915]"; -+} -+ -+static bool i915_fence_signaled(struct dma_fence *fence) -+{ -+ return i915_request_completed(to_request(fence)); -+} -+ -+static bool i915_fence_enable_signaling(struct dma_fence *fence) -+{ -+ return i915_request_enable_breadcrumb(to_request(fence)); -+} -+ -+static signed long i915_fence_wait(struct dma_fence *fence, -+ bool interruptible, -+ signed long timeout) -+{ -+ return i915_request_wait(to_request(fence), -+ interruptible | I915_WAIT_PRIORITY, -+ timeout); -+} -+ -+static void i915_fence_release(struct dma_fence *fence) -+{ -+ struct i915_request *rq = to_request(fence); -+ -+ /* -+ * The request is put onto a RCU freelist (i.e. the address -+ * is immediately reused), mark the fences as being freed now. -+ * Otherwise the debugobjects for the fences are only marked as -+ * freed when the slab cache itself is freed, and so we would get -+ * caught trying to reuse dead objects. -+ */ -+ i915_sw_fence_fini(&rq->submit); -+ i915_sw_fence_fini(&rq->semaphore); -+ -+ kmem_cache_free(global.slab_requests, rq); -+} -+ -+const struct dma_fence_ops i915_fence_ops = { -+ .get_driver_name = i915_fence_get_driver_name, -+ .get_timeline_name = i915_fence_get_timeline_name, -+ .enable_signaling = i915_fence_enable_signaling, -+ .signaled = i915_fence_signaled, -+ .wait = i915_fence_wait, -+ .release = i915_fence_release, -+}; -+ -+static inline void -+i915_request_remove_from_client(struct i915_request *request) -+{ -+ struct drm_i915_file_private *file_priv; -+ -+ file_priv = request->file_priv; -+ if (!file_priv) -+ return; -+ -+ spin_lock(&file_priv->mm.lock); -+ if (request->file_priv) { -+ list_del(&request->client_link); -+ request->file_priv = NULL; -+ } -+ spin_unlock(&file_priv->mm.lock); -+} -+ -+static void reserve_gt(struct drm_i915_private *i915) -+{ -+ if (!i915->gt.active_requests++) -+ i915_gem_unpark(i915); -+} -+ -+static void unreserve_gt(struct drm_i915_private *i915) -+{ -+ GEM_BUG_ON(!i915->gt.active_requests); -+ if (!--i915->gt.active_requests) -+ i915_gem_park(i915); -+} -+ -+static void advance_ring(struct i915_request *request) -+{ -+ struct intel_ring *ring = request->ring; -+ unsigned int tail; -+ -+ /* -+ * We know the GPU must have read the request to have -+ * sent us the seqno + interrupt, so use the position -+ * of tail of the request to update the last known position -+ * of the GPU head. -+ * -+ * Note this requires that we are always called in request -+ * completion order. -+ */ -+ GEM_BUG_ON(!list_is_first(&request->ring_link, &ring->request_list)); -+ if (list_is_last(&request->ring_link, &ring->request_list)) { -+ /* -+ * We may race here with execlists resubmitting this request -+ * as we retire it. The resubmission will move the ring->tail -+ * forwards (to request->wa_tail). We either read the -+ * current value that was written to hw, or the value that -+ * is just about to be. Either works, if we miss the last two -+ * noops - they are safe to be replayed on a reset. -+ */ -+ tail = READ_ONCE(request->tail); -+ list_del(&ring->active_link); -+ } else { -+ tail = request->postfix; -+ } -+ list_del_init(&request->ring_link); -+ -+ ring->head = tail; -+} -+ -+static void free_capture_list(struct i915_request *request) -+{ -+ struct i915_capture_list *capture; -+ -+ capture = request->capture_list; -+ while (capture) { -+ struct i915_capture_list *next = capture->next; -+ -+ kfree(capture); -+ capture = next; -+ } -+} -+ -+static void __retire_engine_request(struct intel_engine_cs *engine, -+ struct i915_request *rq) -+{ -+ GEM_TRACE("%s(%s) fence %llx:%lld, current %d\n", -+ __func__, engine->name, -+ rq->fence.context, rq->fence.seqno, -+ hwsp_seqno(rq)); -+ -+ GEM_BUG_ON(!i915_request_completed(rq)); -+ -+ local_irq_disable(); -+ -+ spin_lock(&engine->timeline.lock); -+ GEM_BUG_ON(!list_is_first(&rq->link, &engine->timeline.requests)); -+ list_del_init(&rq->link); -+ spin_unlock(&engine->timeline.lock); -+ -+ spin_lock(&rq->lock); -+ i915_request_mark_complete(rq); -+ if (!i915_request_signaled(rq)) -+ dma_fence_signal_locked(&rq->fence); -+ if (test_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, &rq->fence.flags)) -+ i915_request_cancel_breadcrumb(rq); -+ if (rq->waitboost) { -+ GEM_BUG_ON(!atomic_read(&rq->i915->gt_pm.rps.num_waiters)); -+ atomic_dec(&rq->i915->gt_pm.rps.num_waiters); -+ } -+ spin_unlock(&rq->lock); -+ -+ local_irq_enable(); -+ -+ /* -+ * The backing object for the context is done after switching to the -+ * *next* context. Therefore we cannot retire the previous context until -+ * the next context has already started running. However, since we -+ * cannot take the required locks at i915_request_submit() we -+ * defer the unpinning of the active context to now, retirement of -+ * the subsequent request. -+ */ -+ if (engine->last_retired_context) -+ intel_context_unpin(engine->last_retired_context); -+ engine->last_retired_context = rq->hw_context; -+} -+ -+static void __retire_engine_upto(struct intel_engine_cs *engine, -+ struct i915_request *rq) -+{ -+ struct i915_request *tmp; -+ -+ if (list_empty(&rq->link)) -+ return; -+ -+ do { -+ tmp = list_first_entry(&engine->timeline.requests, -+ typeof(*tmp), link); -+ -+ GEM_BUG_ON(tmp->engine != engine); -+ __retire_engine_request(engine, tmp); -+ } while (tmp != rq); -+} -+ -+static void i915_request_retire(struct i915_request *request) -+{ -+ struct i915_active_request *active, *next; -+ -+ GEM_TRACE("%s fence %llx:%lld, current %d\n", -+ request->engine->name, -+ request->fence.context, request->fence.seqno, -+ hwsp_seqno(request)); -+ -+ lockdep_assert_held(&request->i915->drm.struct_mutex); -+ GEM_BUG_ON(!i915_sw_fence_signaled(&request->submit)); -+ GEM_BUG_ON(!i915_request_completed(request)); -+ -+ trace_i915_request_retire(request); -+ -+ advance_ring(request); -+ free_capture_list(request); -+ -+ /* -+ * Walk through the active list, calling retire on each. This allows -+ * objects to track their GPU activity and mark themselves as idle -+ * when their *last* active request is completed (updating state -+ * tracking lists for eviction, active references for GEM, etc). -+ * -+ * As the ->retire() may free the node, we decouple it first and -+ * pass along the auxiliary information (to avoid dereferencing -+ * the node after the callback). -+ */ -+ list_for_each_entry_safe(active, next, &request->active_list, link) { -+ /* -+ * In microbenchmarks or focusing upon time inside the kernel, -+ * we may spend an inordinate amount of time simply handling -+ * the retirement of requests and processing their callbacks. -+ * Of which, this loop itself is particularly hot due to the -+ * cache misses when jumping around the list of -+ * i915_active_request. So we try to keep this loop as -+ * streamlined as possible and also prefetch the next -+ * i915_active_request to try and hide the likely cache miss. -+ */ -+ prefetchw(next); -+ -+ INIT_LIST_HEAD(&active->link); -+ RCU_INIT_POINTER(active->request, NULL); -+ -+ active->retire(active, request); -+ } -+ -+ i915_request_remove_from_client(request); -+ -+ intel_context_unpin(request->hw_context); -+ -+ __retire_engine_upto(request->engine, request); -+ -+ unreserve_gt(request->i915); -+ -+ i915_sched_node_fini(&request->sched); -+ i915_request_put(request); -+} -+ -+void i915_request_retire_upto(struct i915_request *rq) -+{ -+ struct intel_ring *ring = rq->ring; -+ struct i915_request *tmp; -+ -+ GEM_TRACE("%s fence %llx:%lld, current %d\n", -+ rq->engine->name, -+ rq->fence.context, rq->fence.seqno, -+ hwsp_seqno(rq)); -+ -+ lockdep_assert_held(&rq->i915->drm.struct_mutex); -+ GEM_BUG_ON(!i915_request_completed(rq)); -+ -+ if (list_empty(&rq->ring_link)) -+ return; -+ -+ do { -+ tmp = list_first_entry(&ring->request_list, -+ typeof(*tmp), ring_link); -+ -+ i915_request_retire(tmp); -+ } while (tmp != rq); -+} -+ -+static void irq_execute_cb(struct irq_work *wrk) -+{ -+ struct execute_cb *cb = container_of(wrk, typeof(*cb), work); -+ -+ i915_sw_fence_complete(cb->fence); -+ kmem_cache_free(global.slab_execute_cbs, cb); -+} -+ -+static void __notify_execute_cb(struct i915_request *rq) -+{ -+ struct execute_cb *cb; -+ -+ lockdep_assert_held(&rq->lock); -+ -+ if (list_empty(&rq->execute_cb)) -+ return; -+ -+ list_for_each_entry(cb, &rq->execute_cb, link) -+ irq_work_queue(&cb->work); -+ -+ /* -+ * XXX Rollback on __i915_request_unsubmit() -+ * -+ * In the future, perhaps when we have an active time-slicing scheduler, -+ * it will be interesting to unsubmit parallel execution and remove -+ * busywaits from the GPU until their master is restarted. This is -+ * quite hairy, we have to carefully rollback the fence and do a -+ * preempt-to-idle cycle on the target engine, all the while the -+ * master execute_cb may refire. -+ */ -+ INIT_LIST_HEAD(&rq->execute_cb); -+} -+ -+static int -+i915_request_await_execution(struct i915_request *rq, -+ struct i915_request *signal, -+ gfp_t gfp) -+{ -+ struct execute_cb *cb; -+ -+ if (i915_request_is_active(signal)) -+ return 0; -+ -+ cb = kmem_cache_alloc(global.slab_execute_cbs, gfp); -+ if (!cb) -+ return -ENOMEM; -+ -+ cb->fence = &rq->submit; -+ i915_sw_fence_await(cb->fence); -+ init_irq_work(&cb->work, irq_execute_cb); -+ -+ spin_lock_irq(&signal->lock); -+ if (i915_request_is_active(signal)) { -+ i915_sw_fence_complete(cb->fence); -+ kmem_cache_free(global.slab_execute_cbs, cb); -+ } else { -+ list_add_tail(&cb->link, &signal->execute_cb); -+ } -+ spin_unlock_irq(&signal->lock); -+ -+ return 0; -+} -+ -+static void move_to_timeline(struct i915_request *request, -+ struct i915_timeline *timeline) -+{ -+ GEM_BUG_ON(request->timeline == &request->engine->timeline); -+ lockdep_assert_held(&request->engine->timeline.lock); -+ -+ spin_lock(&request->timeline->lock); -+ list_move_tail(&request->link, &timeline->requests); -+ spin_unlock(&request->timeline->lock); -+} -+ -+void __i915_request_submit(struct i915_request *request) -+{ -+ struct intel_engine_cs *engine = request->engine; -+ -+ GEM_TRACE("%s fence %llx:%lld -> current %d\n", -+ engine->name, -+ request->fence.context, request->fence.seqno, -+ hwsp_seqno(request)); -+ -+ GEM_BUG_ON(!irqs_disabled()); -+ lockdep_assert_held(&engine->timeline.lock); -+ -+ if (i915_gem_context_is_banned(request->gem_context)) -+ i915_request_skip(request, -EIO); -+ -+ /* -+ * Are we using semaphores when the gpu is already saturated? -+ * -+ * Using semaphores incurs a cost in having the GPU poll a -+ * memory location, busywaiting for it to change. The continual -+ * memory reads can have a noticeable impact on the rest of the -+ * system with the extra bus traffic, stalling the cpu as it too -+ * tries to access memory across the bus (perf stat -e bus-cycles). -+ * -+ * If we installed a semaphore on this request and we only submit -+ * the request after the signaler completed, that indicates the -+ * system is overloaded and using semaphores at this time only -+ * increases the amount of work we are doing. If so, we disable -+ * further use of semaphores until we are idle again, whence we -+ * optimistically try again. -+ */ -+ if (request->sched.semaphores && -+ i915_sw_fence_signaled(&request->semaphore)) -+ engine->saturated |= request->sched.semaphores; -+ -+ /* We may be recursing from the signal callback of another i915 fence */ -+ spin_lock_nested(&request->lock, SINGLE_DEPTH_NESTING); -+ -+ GEM_BUG_ON(test_bit(I915_FENCE_FLAG_ACTIVE, &request->fence.flags)); -+ set_bit(I915_FENCE_FLAG_ACTIVE, &request->fence.flags); -+ -+ if (test_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, &request->fence.flags) && -+ !test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &request->fence.flags) && -+ !i915_request_enable_breadcrumb(request)) -+ intel_engine_queue_breadcrumbs(engine); -+ -+ __notify_execute_cb(request); -+ -+ spin_unlock(&request->lock); -+ -+ engine->emit_fini_breadcrumb(request, -+ request->ring->vaddr + request->postfix); -+ -+ /* Transfer from per-context onto the global per-engine timeline */ -+ move_to_timeline(request, &engine->timeline); -+ -+ trace_i915_request_execute(request); -+} -+ -+void i915_request_submit(struct i915_request *request) -+{ -+ struct intel_engine_cs *engine = request->engine; -+ unsigned long flags; -+ -+ /* Will be called from irq-context when using foreign fences. */ -+ spin_lock_irqsave(&engine->timeline.lock, flags); -+ -+ __i915_request_submit(request); -+ -+ spin_unlock_irqrestore(&engine->timeline.lock, flags); -+} -+ -+void __i915_request_unsubmit(struct i915_request *request) -+{ -+ struct intel_engine_cs *engine = request->engine; -+ -+ GEM_TRACE("%s fence %llx:%lld, current %d\n", -+ engine->name, -+ request->fence.context, request->fence.seqno, -+ hwsp_seqno(request)); -+ -+ GEM_BUG_ON(!irqs_disabled()); -+ lockdep_assert_held(&engine->timeline.lock); -+ -+ /* -+ * Only unwind in reverse order, required so that the per-context list -+ * is kept in seqno/ring order. -+ */ -+ -+ /* We may be recursing from the signal callback of another i915 fence */ -+ spin_lock_nested(&request->lock, SINGLE_DEPTH_NESTING); -+ -+ if (test_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, &request->fence.flags)) -+ i915_request_cancel_breadcrumb(request); -+ -+ GEM_BUG_ON(!test_bit(I915_FENCE_FLAG_ACTIVE, &request->fence.flags)); -+ clear_bit(I915_FENCE_FLAG_ACTIVE, &request->fence.flags); -+ -+ spin_unlock(&request->lock); -+ -+ /* Transfer back from the global per-engine timeline to per-context */ -+ move_to_timeline(request, request->timeline); -+ -+ /* -+ * We don't need to wake_up any waiters on request->execute, they -+ * will get woken by any other event or us re-adding this request -+ * to the engine timeline (__i915_request_submit()). The waiters -+ * should be quite adapt at finding that the request now has a new -+ * global_seqno to the one they went to sleep on. -+ */ -+} -+ -+void i915_request_unsubmit(struct i915_request *request) -+{ -+ struct intel_engine_cs *engine = request->engine; -+ unsigned long flags; -+ -+ /* Will be called from irq-context when using foreign fences. */ -+ spin_lock_irqsave(&engine->timeline.lock, flags); -+ -+ __i915_request_unsubmit(request); -+ -+ spin_unlock_irqrestore(&engine->timeline.lock, flags); -+} -+ -+static int __i915_sw_fence_call -+submit_notify(struct i915_sw_fence *fence, enum i915_sw_fence_notify state) -+{ -+ struct i915_request *request = -+ container_of(fence, typeof(*request), submit); -+ -+ switch (state) { -+ case FENCE_COMPLETE: -+ trace_i915_request_submit(request); -+ /* -+ * We need to serialize use of the submit_request() callback -+ * with its hotplugging performed during an emergency -+ * i915_gem_set_wedged(). We use the RCU mechanism to mark the -+ * critical section in order to force i915_gem_set_wedged() to -+ * wait until the submit_request() is completed before -+ * proceeding. -+ */ -+ rcu_read_lock(); -+ request->engine->submit_request(request); -+ rcu_read_unlock(); -+ break; -+ -+ case FENCE_FREE: -+ i915_request_put(request); -+ break; -+ } -+ -+ return NOTIFY_DONE; -+} -+ -+static int __i915_sw_fence_call -+semaphore_notify(struct i915_sw_fence *fence, enum i915_sw_fence_notify state) -+{ -+ struct i915_request *request = -+ container_of(fence, typeof(*request), semaphore); -+ -+ switch (state) { -+ case FENCE_COMPLETE: -+ i915_schedule_bump_priority(request, I915_PRIORITY_NOSEMAPHORE); -+ break; -+ -+ case FENCE_FREE: -+ i915_request_put(request); -+ break; -+ } -+ -+ return NOTIFY_DONE; -+} -+ -+static void ring_retire_requests(struct intel_ring *ring) -+{ -+ struct i915_request *rq, *rn; -+ -+ list_for_each_entry_safe(rq, rn, &ring->request_list, ring_link) { -+ if (!i915_request_completed(rq)) -+ break; -+ -+ i915_request_retire(rq); -+ } -+} -+ -+static noinline struct i915_request * -+i915_request_alloc_slow(struct intel_context *ce) -+{ -+ struct intel_ring *ring = ce->ring; -+ struct i915_request *rq; -+ -+ if (list_empty(&ring->request_list)) -+ goto out; -+ -+ /* Ratelimit ourselves to prevent oom from malicious clients */ -+ rq = list_last_entry(&ring->request_list, typeof(*rq), ring_link); -+ cond_synchronize_rcu(rq->rcustate); -+ -+ /* Retire our old requests in the hope that we free some */ -+ ring_retire_requests(ring); -+ -+out: -+ return kmem_cache_alloc(global.slab_requests, GFP_KERNEL); -+} -+ -+/** -+ * i915_request_alloc - allocate a request structure -+ * -+ * @engine: engine that we wish to issue the request on. -+ * @ctx: context that the request will be associated with. -+ * -+ * Returns a pointer to the allocated request if successful, -+ * or an error code if not. -+ */ -+struct i915_request * -+i915_request_alloc(struct intel_engine_cs *engine, struct i915_gem_context *ctx) -+{ -+ struct drm_i915_private *i915 = engine->i915; -+ struct intel_context *ce; -+ struct i915_timeline *tl; -+ struct i915_request *rq; -+ u32 seqno; -+ int ret; -+ -+ lockdep_assert_held(&i915->drm.struct_mutex); -+ -+ /* -+ * Preempt contexts are reserved for exclusive use to inject a -+ * preemption context switch. They are never to be used for any trivial -+ * request! -+ */ -+ GEM_BUG_ON(ctx == i915->preempt_context); -+ -+ /* -+ * ABI: Before userspace accesses the GPU (e.g. execbuffer), report -+ * EIO if the GPU is already wedged. -+ */ -+ ret = i915_terminally_wedged(i915); -+ if (ret) -+ return ERR_PTR(ret); -+ -+ /* -+ * Pinning the contexts may generate requests in order to acquire -+ * GGTT space, so do this first before we reserve a seqno for -+ * ourselves. -+ */ -+ ce = intel_context_pin(ctx, engine); -+ if (IS_ERR(ce)) -+ return ERR_CAST(ce); -+ -+ reserve_gt(i915); -+ mutex_lock(&ce->ring->timeline->mutex); -+ -+ /* Move our oldest request to the slab-cache (if not in use!) */ -+ rq = list_first_entry(&ce->ring->request_list, typeof(*rq), ring_link); -+ if (!list_is_last(&rq->ring_link, &ce->ring->request_list) && -+ i915_request_completed(rq)) -+ i915_request_retire(rq); -+ -+ /* -+ * Beware: Dragons be flying overhead. -+ * -+ * We use RCU to look up requests in flight. The lookups may -+ * race with the request being allocated from the slab freelist. -+ * That is the request we are writing to here, may be in the process -+ * of being read by __i915_active_request_get_rcu(). As such, -+ * we have to be very careful when overwriting the contents. During -+ * the RCU lookup, we change chase the request->engine pointer, -+ * read the request->global_seqno and increment the reference count. -+ * -+ * The reference count is incremented atomically. If it is zero, -+ * the lookup knows the request is unallocated and complete. Otherwise, -+ * it is either still in use, or has been reallocated and reset -+ * with dma_fence_init(). This increment is safe for release as we -+ * check that the request we have a reference to and matches the active -+ * request. -+ * -+ * Before we increment the refcount, we chase the request->engine -+ * pointer. We must not call kmem_cache_zalloc() or else we set -+ * that pointer to NULL and cause a crash during the lookup. If -+ * we see the request is completed (based on the value of the -+ * old engine and seqno), the lookup is complete and reports NULL. -+ * If we decide the request is not completed (new engine or seqno), -+ * then we grab a reference and double check that it is still the -+ * active request - which it won't be and restart the lookup. -+ * -+ * Do not use kmem_cache_zalloc() here! -+ */ -+ rq = kmem_cache_alloc(global.slab_requests, -+ GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_NOWARN); -+ if (unlikely(!rq)) { -+ rq = i915_request_alloc_slow(ce); -+ if (!rq) { -+ ret = -ENOMEM; -+ goto err_unreserve; -+ } -+ } -+ -+ INIT_LIST_HEAD(&rq->active_list); -+ INIT_LIST_HEAD(&rq->execute_cb); -+ -+ tl = ce->ring->timeline; -+ ret = i915_timeline_get_seqno(tl, rq, &seqno); -+ if (ret) -+ goto err_free; -+ -+ rq->i915 = i915; -+ rq->engine = engine; -+ rq->gem_context = ctx; -+ rq->hw_context = ce; -+ rq->ring = ce->ring; -+ rq->timeline = tl; -+ GEM_BUG_ON(rq->timeline == &engine->timeline); -+ rq->hwsp_seqno = tl->hwsp_seqno; -+ rq->hwsp_cacheline = tl->hwsp_cacheline; -+ rq->rcustate = get_state_synchronize_rcu(); /* acts as smp_mb() */ -+ -+ spin_lock_init(&rq->lock); -+ dma_fence_init(&rq->fence, &i915_fence_ops, &rq->lock, -+ tl->fence_context, seqno); -+ -+ /* We bump the ref for the fence chain */ -+ i915_sw_fence_init(&i915_request_get(rq)->submit, submit_notify); -+ i915_sw_fence_init(&i915_request_get(rq)->semaphore, semaphore_notify); -+ -+ i915_sched_node_init(&rq->sched); -+ -+ /* No zalloc, must clear what we need by hand */ -+ rq->file_priv = NULL; -+ rq->batch = NULL; -+ rq->capture_list = NULL; -+ rq->waitboost = false; -+ -+ /* -+ * Reserve space in the ring buffer for all the commands required to -+ * eventually emit this request. This is to guarantee that the -+ * i915_request_add() call can't fail. Note that the reserve may need -+ * to be redone if the request is not actually submitted straight -+ * away, e.g. because a GPU scheduler has deferred it. -+ * -+ * Note that due to how we add reserved_space to intel_ring_begin() -+ * we need to double our request to ensure that if we need to wrap -+ * around inside i915_request_add() there is sufficient space at -+ * the beginning of the ring as well. -+ */ -+ rq->reserved_space = 2 * engine->emit_fini_breadcrumb_dw * sizeof(u32); -+ -+ /* -+ * Record the position of the start of the request so that -+ * should we detect the updated seqno part-way through the -+ * GPU processing the request, we never over-estimate the -+ * position of the head. -+ */ -+ rq->head = rq->ring->emit; -+ -+ ret = engine->request_alloc(rq); -+ if (ret) -+ goto err_unwind; -+ -+ /* Keep a second pin for the dual retirement along engine and ring */ -+ __intel_context_pin(ce); -+ -+ rq->infix = rq->ring->emit; /* end of header; start of user payload */ -+ -+ /* Check that we didn't interrupt ourselves with a new request */ -+ lockdep_assert_held(&rq->timeline->mutex); -+ GEM_BUG_ON(rq->timeline->seqno != rq->fence.seqno); -+ rq->cookie = lockdep_pin_lock(&rq->timeline->mutex); -+ -+ return rq; -+ -+err_unwind: -+ ce->ring->emit = rq->head; -+ -+ /* Make sure we didn't add ourselves to external state before freeing */ -+ GEM_BUG_ON(!list_empty(&rq->active_list)); -+ GEM_BUG_ON(!list_empty(&rq->sched.signalers_list)); -+ GEM_BUG_ON(!list_empty(&rq->sched.waiters_list)); -+ -+err_free: -+ kmem_cache_free(global.slab_requests, rq); -+err_unreserve: -+ mutex_unlock(&ce->ring->timeline->mutex); -+ unreserve_gt(i915); -+ intel_context_unpin(ce); -+ return ERR_PTR(ret); -+} -+ -+static int -+i915_request_await_start(struct i915_request *rq, struct i915_request *signal) -+{ -+ if (list_is_first(&signal->ring_link, &signal->ring->request_list)) -+ return 0; -+ -+ signal = list_prev_entry(signal, ring_link); -+ if (i915_timeline_sync_is_later(rq->timeline, &signal->fence)) -+ return 0; -+ -+ return i915_sw_fence_await_dma_fence(&rq->submit, -+ &signal->fence, 0, -+ I915_FENCE_GFP); -+} -+ -+static intel_engine_mask_t -+already_busywaiting(struct i915_request *rq) -+{ -+ /* -+ * Polling a semaphore causes bus traffic, delaying other users of -+ * both the GPU and CPU. We want to limit the impact on others, -+ * while taking advantage of early submission to reduce GPU -+ * latency. Therefore we restrict ourselves to not using more -+ * than one semaphore from each source, and not using a semaphore -+ * if we have detected the engine is saturated (i.e. would not be -+ * submitted early and cause bus traffic reading an already passed -+ * semaphore). -+ * -+ * See the are-we-too-late? check in __i915_request_submit(). -+ */ -+ return rq->sched.semaphores | rq->engine->saturated; -+} -+ -+static int -+emit_semaphore_wait(struct i915_request *to, -+ struct i915_request *from, -+ gfp_t gfp) -+{ -+ u32 hwsp_offset; -+ u32 *cs; -+ int err; -+ -+ GEM_BUG_ON(!from->timeline->has_initial_breadcrumb); -+ GEM_BUG_ON(INTEL_GEN(to->i915) < 8); -+ -+ /* Just emit the first semaphore we see as request space is limited. */ -+ if (already_busywaiting(to) & from->engine->mask) -+ return i915_sw_fence_await_dma_fence(&to->submit, -+ &from->fence, 0, -+ I915_FENCE_GFP); -+ -+ err = i915_request_await_start(to, from); -+ if (err < 0) -+ return err; -+ -+ /* We need to pin the signaler's HWSP until we are finished reading. */ -+ err = i915_timeline_read_hwsp(from, to, &hwsp_offset); -+ if (err) -+ return err; -+ -+ /* Only submit our spinner after the signaler is running! */ -+ err = i915_request_await_execution(to, from, gfp); -+ if (err) -+ return err; -+ -+ cs = intel_ring_begin(to, 4); -+ if (IS_ERR(cs)) -+ return PTR_ERR(cs); -+ -+ /* -+ * Using greater-than-or-equal here means we have to worry -+ * about seqno wraparound. To side step that issue, we swap -+ * the timeline HWSP upon wrapping, so that everyone listening -+ * for the old (pre-wrap) values do not see the much smaller -+ * (post-wrap) values than they were expecting (and so wait -+ * forever). -+ */ -+ *cs++ = MI_SEMAPHORE_WAIT | -+ MI_SEMAPHORE_GLOBAL_GTT | -+ MI_SEMAPHORE_POLL | -+ MI_SEMAPHORE_SAD_GTE_SDD; -+ *cs++ = from->fence.seqno; -+ *cs++ = hwsp_offset; -+ *cs++ = 0; -+ -+ intel_ring_advance(to, cs); -+ to->sched.semaphores |= from->engine->mask; -+ to->sched.flags |= I915_SCHED_HAS_SEMAPHORE_CHAIN; -+ return 0; -+} -+ -+static int -+i915_request_await_request(struct i915_request *to, struct i915_request *from) -+{ -+ int ret; -+ -+ GEM_BUG_ON(to == from); -+ GEM_BUG_ON(to->timeline == from->timeline); -+ -+ if (i915_request_completed(from)) -+ return 0; -+ -+ if (to->engine->schedule) { -+ ret = i915_sched_node_add_dependency(&to->sched, &from->sched); -+ if (ret < 0) -+ return ret; -+ } -+ -+ if (to->engine == from->engine) { -+ ret = i915_sw_fence_await_sw_fence_gfp(&to->submit, -+ &from->submit, -+ I915_FENCE_GFP); -+ } else if (intel_engine_has_semaphores(to->engine) && -+ to->gem_context->sched.priority >= I915_PRIORITY_NORMAL) { -+ ret = emit_semaphore_wait(to, from, I915_FENCE_GFP); -+ } else { -+ ret = i915_sw_fence_await_dma_fence(&to->submit, -+ &from->fence, 0, -+ I915_FENCE_GFP); -+ } -+ if (ret < 0) -+ return ret; -+ -+ if (to->sched.flags & I915_SCHED_HAS_SEMAPHORE_CHAIN) { -+ ret = i915_sw_fence_await_dma_fence(&to->semaphore, -+ &from->fence, 0, -+ I915_FENCE_GFP); -+ if (ret < 0) -+ return ret; -+ } -+ -+ return 0; -+} -+ -+int -+i915_request_await_dma_fence(struct i915_request *rq, struct dma_fence *fence) -+{ -+ struct dma_fence **child = &fence; -+ unsigned int nchild = 1; -+ int ret; -+ -+ /* -+ * Note that if the fence-array was created in signal-on-any mode, -+ * we should *not* decompose it into its individual fences. However, -+ * we don't currently store which mode the fence-array is operating -+ * in. Fortunately, the only user of signal-on-any is private to -+ * amdgpu and we should not see any incoming fence-array from -+ * sync-file being in signal-on-any mode. -+ */ -+ if (dma_fence_is_array(fence)) { -+ struct dma_fence_array *array = to_dma_fence_array(fence); -+ -+ child = array->fences; -+ nchild = array->num_fences; -+ GEM_BUG_ON(!nchild); -+ } -+ -+ do { -+ fence = *child++; -+ if (test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags)) -+ continue; -+ -+ /* -+ * Requests on the same timeline are explicitly ordered, along -+ * with their dependencies, by i915_request_add() which ensures -+ * that requests are submitted in-order through each ring. -+ */ -+ if (fence->context == rq->fence.context) -+ continue; -+ -+ /* Squash repeated waits to the same timelines */ -+ if (fence->context != rq->i915->mm.unordered_timeline && -+ i915_timeline_sync_is_later(rq->timeline, fence)) -+ continue; -+ -+ if (dma_fence_is_i915(fence)) -+ ret = i915_request_await_request(rq, to_request(fence)); -+ else -+ ret = i915_sw_fence_await_dma_fence(&rq->submit, fence, -+ I915_FENCE_TIMEOUT, -+ I915_FENCE_GFP); -+ if (ret < 0) -+ return ret; -+ -+ /* Record the latest fence used against each timeline */ -+ if (fence->context != rq->i915->mm.unordered_timeline) -+ i915_timeline_sync_set(rq->timeline, fence); -+ } while (--nchild); -+ -+ return 0; -+} -+ -+/** -+ * i915_request_await_object - set this request to (async) wait upon a bo -+ * @to: request we are wishing to use -+ * @obj: object which may be in use on another ring. -+ * @write: whether the wait is on behalf of a writer -+ * -+ * This code is meant to abstract object synchronization with the GPU. -+ * Conceptually we serialise writes between engines inside the GPU. -+ * We only allow one engine to write into a buffer at any time, but -+ * multiple readers. To ensure each has a coherent view of memory, we must: -+ * -+ * - If there is an outstanding write request to the object, the new -+ * request must wait for it to complete (either CPU or in hw, requests -+ * on the same ring will be naturally ordered). -+ * -+ * - If we are a write request (pending_write_domain is set), the new -+ * request must wait for outstanding read requests to complete. -+ * -+ * Returns 0 if successful, else propagates up the lower layer error. -+ */ -+int -+i915_request_await_object(struct i915_request *to, -+ struct drm_i915_gem_object *obj, -+ bool write) -+{ -+ struct dma_fence *excl; -+ int ret = 0; -+ -+ if (write) { -+ struct dma_fence **shared; -+ unsigned int count, i; -+ -+ ret = reservation_object_get_fences_rcu(obj->resv, -+ &excl, &count, &shared); -+ if (ret) -+ return ret; -+ -+ for (i = 0; i < count; i++) { -+ ret = i915_request_await_dma_fence(to, shared[i]); -+ if (ret) -+ break; -+ -+ dma_fence_put(shared[i]); -+ } -+ -+ for (; i < count; i++) -+ dma_fence_put(shared[i]); -+ kfree(shared); -+ } else { -+ excl = reservation_object_get_excl_rcu(obj->resv); -+ } -+ -+ if (excl) { -+ if (ret == 0) -+ ret = i915_request_await_dma_fence(to, excl); -+ -+ dma_fence_put(excl); -+ } -+ -+ return ret; -+} -+ -+void i915_request_skip(struct i915_request *rq, int error) -+{ -+ void *vaddr = rq->ring->vaddr; -+ u32 head; -+ -+ GEM_BUG_ON(!IS_ERR_VALUE((long)error)); -+ dma_fence_set_error(&rq->fence, error); -+ -+ /* -+ * As this request likely depends on state from the lost -+ * context, clear out all the user operations leaving the -+ * breadcrumb at the end (so we get the fence notifications). -+ */ -+ head = rq->infix; -+ if (rq->postfix < head) { -+ memset(vaddr + head, 0, rq->ring->size - head); -+ head = 0; -+ } -+ memset(vaddr + head, 0, rq->postfix - head); -+} -+ -+static struct i915_request * -+__i915_request_add_to_timeline(struct i915_request *rq) -+{ -+ struct i915_timeline *timeline = rq->timeline; -+ struct i915_request *prev; -+ -+ /* -+ * Dependency tracking and request ordering along the timeline -+ * is special cased so that we can eliminate redundant ordering -+ * operations while building the request (we know that the timeline -+ * itself is ordered, and here we guarantee it). -+ * -+ * As we know we will need to emit tracking along the timeline, -+ * we embed the hooks into our request struct -- at the cost of -+ * having to have specialised no-allocation interfaces (which will -+ * be beneficial elsewhere). -+ * -+ * A second benefit to open-coding i915_request_await_request is -+ * that we can apply a slight variant of the rules specialised -+ * for timelines that jump between engines (such as virtual engines). -+ * If we consider the case of virtual engine, we must emit a dma-fence -+ * to prevent scheduling of the second request until the first is -+ * complete (to maximise our greedy late load balancing) and this -+ * precludes optimising to use semaphores serialisation of a single -+ * timeline across engines. -+ */ -+ prev = i915_active_request_raw(&timeline->last_request, -+ &rq->i915->drm.struct_mutex); -+ if (prev && !i915_request_completed(prev)) { -+ if (is_power_of_2(prev->engine->mask | rq->engine->mask)) -+ i915_sw_fence_await_sw_fence(&rq->submit, -+ &prev->submit, -+ &rq->submitq); -+ else -+ __i915_sw_fence_await_dma_fence(&rq->submit, -+ &prev->fence, -+ &rq->dmaq); -+ if (rq->engine->schedule) -+ __i915_sched_node_add_dependency(&rq->sched, -+ &prev->sched, -+ &rq->dep, -+ 0); -+ } -+ -+ spin_lock_irq(&timeline->lock); -+ list_add_tail(&rq->link, &timeline->requests); -+ spin_unlock_irq(&timeline->lock); -+ -+ GEM_BUG_ON(timeline->seqno != rq->fence.seqno); -+ __i915_active_request_set(&timeline->last_request, rq); -+ -+ return prev; -+} -+ -+/* -+ * NB: This function is not allowed to fail. Doing so would mean the the -+ * request is not being tracked for completion but the work itself is -+ * going to happen on the hardware. This would be a Bad Thing(tm). -+ */ -+void i915_request_add(struct i915_request *request) -+{ -+ struct intel_engine_cs *engine = request->engine; -+ struct i915_timeline *timeline = request->timeline; -+ struct intel_ring *ring = request->ring; -+ struct i915_request *prev; -+ u32 *cs; -+ -+ GEM_TRACE("%s fence %llx:%lld\n", -+ engine->name, request->fence.context, request->fence.seqno); -+ -+ lockdep_assert_held(&request->timeline->mutex); -+ lockdep_unpin_lock(&request->timeline->mutex, request->cookie); -+ -+ trace_i915_request_add(request); -+ -+ /* -+ * Make sure that no request gazumped us - if it was allocated after -+ * our i915_request_alloc() and called __i915_request_add() before -+ * us, the timeline will hold its seqno which is later than ours. -+ */ -+ GEM_BUG_ON(timeline->seqno != request->fence.seqno); -+ -+ /* -+ * To ensure that this call will not fail, space for its emissions -+ * should already have been reserved in the ring buffer. Let the ring -+ * know that it is time to use that space up. -+ */ -+ GEM_BUG_ON(request->reserved_space > request->ring->space); -+ request->reserved_space = 0; -+ -+ /* -+ * Record the position of the start of the breadcrumb so that -+ * should we detect the updated seqno part-way through the -+ * GPU processing the request, we never over-estimate the -+ * position of the ring's HEAD. -+ */ -+ cs = intel_ring_begin(request, engine->emit_fini_breadcrumb_dw); -+ GEM_BUG_ON(IS_ERR(cs)); -+ request->postfix = intel_ring_offset(request, cs); -+ -+ prev = __i915_request_add_to_timeline(request); -+ -+ list_add_tail(&request->ring_link, &ring->request_list); -+ if (list_is_first(&request->ring_link, &ring->request_list)) -+ list_add(&ring->active_link, &request->i915->gt.active_rings); -+ request->i915->gt.active_engines |= request->engine->mask; -+ request->emitted_jiffies = jiffies; -+ -+ /* -+ * Let the backend know a new request has arrived that may need -+ * to adjust the existing execution schedule due to a high priority -+ * request - i.e. we may want to preempt the current request in order -+ * to run a high priority dependency chain *before* we can execute this -+ * request. -+ * -+ * This is called before the request is ready to run so that we can -+ * decide whether to preempt the entire chain so that it is ready to -+ * run at the earliest possible convenience. -+ */ -+ local_bh_disable(); -+ i915_sw_fence_commit(&request->semaphore); -+ rcu_read_lock(); /* RCU serialisation for set-wedged protection */ -+ if (engine->schedule) { -+ struct i915_sched_attr attr = request->gem_context->sched; -+ -+ /* -+ * Boost actual workloads past semaphores! -+ * -+ * With semaphores we spin on one engine waiting for another, -+ * simply to reduce the latency of starting our work when -+ * the signaler completes. However, if there is any other -+ * work that we could be doing on this engine instead, that -+ * is better utilisation and will reduce the overall duration -+ * of the current work. To avoid PI boosting a semaphore -+ * far in the distance past over useful work, we keep a history -+ * of any semaphore use along our dependency chain. -+ */ -+ if (!(request->sched.flags & I915_SCHED_HAS_SEMAPHORE_CHAIN)) -+ attr.priority |= I915_PRIORITY_NOSEMAPHORE; -+ -+ /* -+ * Boost priorities to new clients (new request flows). -+ * -+ * Allow interactive/synchronous clients to jump ahead of -+ * the bulk clients. (FQ_CODEL) -+ */ -+ if (list_empty(&request->sched.signalers_list)) -+ attr.priority |= I915_PRIORITY_WAIT; -+ -+ engine->schedule(request, &attr); -+ } -+ rcu_read_unlock(); -+ i915_sw_fence_commit(&request->submit); -+ local_bh_enable(); /* Kick the execlists tasklet if just scheduled */ -+ -+ /* -+ * In typical scenarios, we do not expect the previous request on -+ * the timeline to be still tracked by timeline->last_request if it -+ * has been completed. If the completed request is still here, that -+ * implies that request retirement is a long way behind submission, -+ * suggesting that we haven't been retiring frequently enough from -+ * the combination of retire-before-alloc, waiters and the background -+ * retirement worker. So if the last request on this timeline was -+ * already completed, do a catch up pass, flushing the retirement queue -+ * up to this client. Since we have now moved the heaviest operations -+ * during retirement onto secondary workers, such as freeing objects -+ * or contexts, retiring a bunch of requests is mostly list management -+ * (and cache misses), and so we should not be overly penalizing this -+ * client by performing excess work, though we may still performing -+ * work on behalf of others -- but instead we should benefit from -+ * improved resource management. (Well, that's the theory at least.) -+ */ -+ if (prev && i915_request_completed(prev)) -+ i915_request_retire_upto(prev); -+ -+ mutex_unlock(&request->timeline->mutex); -+} -+ -+static unsigned long local_clock_us(unsigned int *cpu) -+{ -+ unsigned long t; -+ -+ /* -+ * Cheaply and approximately convert from nanoseconds to microseconds. -+ * The result and subsequent calculations are also defined in the same -+ * approximate microseconds units. The principal source of timing -+ * error here is from the simple truncation. -+ * -+ * Note that local_clock() is only defined wrt to the current CPU; -+ * the comparisons are no longer valid if we switch CPUs. Instead of -+ * blocking preemption for the entire busywait, we can detect the CPU -+ * switch and use that as indicator of system load and a reason to -+ * stop busywaiting, see busywait_stop(). -+ */ -+ *cpu = get_cpu(); -+ t = local_clock() >> 10; -+ put_cpu(); -+ -+ return t; -+} -+ -+static bool busywait_stop(unsigned long timeout, unsigned int cpu) -+{ -+ unsigned int this_cpu; -+ -+ if (time_after(local_clock_us(&this_cpu), timeout)) -+ return true; -+ -+ return this_cpu != cpu; -+} -+ -+static bool __i915_spin_request(const struct i915_request * const rq, -+ int state, unsigned long timeout_us) -+{ -+ unsigned int cpu; -+ -+ /* -+ * Only wait for the request if we know it is likely to complete. -+ * -+ * We don't track the timestamps around requests, nor the average -+ * request length, so we do not have a good indicator that this -+ * request will complete within the timeout. What we do know is the -+ * order in which requests are executed by the context and so we can -+ * tell if the request has been started. If the request is not even -+ * running yet, it is a fair assumption that it will not complete -+ * within our relatively short timeout. -+ */ -+ if (!i915_request_is_running(rq)) -+ return false; -+ -+ /* -+ * When waiting for high frequency requests, e.g. during synchronous -+ * rendering split between the CPU and GPU, the finite amount of time -+ * required to set up the irq and wait upon it limits the response -+ * rate. By busywaiting on the request completion for a short while we -+ * can service the high frequency waits as quick as possible. However, -+ * if it is a slow request, we want to sleep as quickly as possible. -+ * The tradeoff between waiting and sleeping is roughly the time it -+ * takes to sleep on a request, on the order of a microsecond. -+ */ -+ -+ timeout_us += local_clock_us(&cpu); -+ do { -+ if (i915_request_completed(rq)) -+ return true; -+ -+ if (signal_pending_state(state, current)) -+ break; -+ -+ if (busywait_stop(timeout_us, cpu)) -+ break; -+ -+ cpu_relax(); -+ } while (!need_resched()); -+ -+ return false; -+} -+ -+struct request_wait { -+ struct dma_fence_cb cb; -+ struct task_struct *tsk; -+}; -+ -+static void request_wait_wake(struct dma_fence *fence, struct dma_fence_cb *cb) -+{ -+ struct request_wait *wait = container_of(cb, typeof(*wait), cb); -+ -+ wake_up_process(wait->tsk); -+} -+ -+/** -+ * i915_request_wait - wait until execution of request has finished -+ * @rq: the request to wait upon -+ * @flags: how to wait -+ * @timeout: how long to wait in jiffies -+ * -+ * i915_request_wait() waits for the request to be completed, for a -+ * maximum of @timeout jiffies (with MAX_SCHEDULE_TIMEOUT implying an -+ * unbounded wait). -+ * -+ * If the caller holds the struct_mutex, the caller must pass I915_WAIT_LOCKED -+ * in via the flags, and vice versa if the struct_mutex is not held, the caller -+ * must not specify that the wait is locked. -+ * -+ * Returns the remaining time (in jiffies) if the request completed, which may -+ * be zero or -ETIME if the request is unfinished after the timeout expires. -+ * May return -EINTR is called with I915_WAIT_INTERRUPTIBLE and a signal is -+ * pending before the request completes. -+ */ -+long i915_request_wait(struct i915_request *rq, -+ unsigned int flags, -+ long timeout) -+{ -+ const int state = flags & I915_WAIT_INTERRUPTIBLE ? -+ TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE; -+ struct request_wait wait; -+ -+ might_sleep(); -+ GEM_BUG_ON(timeout < 0); -+ -+ if (i915_request_completed(rq)) -+ return timeout; -+ -+ if (!timeout) -+ return -ETIME; -+ -+ trace_i915_request_wait_begin(rq, flags); -+ -+ /* Optimistic short spin before touching IRQs */ -+ if (__i915_spin_request(rq, state, 5)) -+ goto out; -+ -+ /* -+ * This client is about to stall waiting for the GPU. In many cases -+ * this is undesirable and limits the throughput of the system, as -+ * many clients cannot continue processing user input/output whilst -+ * blocked. RPS autotuning may take tens of milliseconds to respond -+ * to the GPU load and thus incurs additional latency for the client. -+ * We can circumvent that by promoting the GPU frequency to maximum -+ * before we sleep. This makes the GPU throttle up much more quickly -+ * (good for benchmarks and user experience, e.g. window animations), -+ * but at a cost of spending more power processing the workload -+ * (bad for battery). -+ */ -+ if (flags & I915_WAIT_PRIORITY) { -+ if (!i915_request_started(rq) && INTEL_GEN(rq->i915) >= 6) -+ gen6_rps_boost(rq); -+ local_bh_disable(); /* suspend tasklets for reprioritisation */ -+ i915_schedule_bump_priority(rq, I915_PRIORITY_WAIT); -+ local_bh_enable(); /* kick tasklets en masse */ -+ } -+ -+ wait.tsk = current; -+ if (dma_fence_add_callback(&rq->fence, &wait.cb, request_wait_wake)) -+ goto out; -+ -+ for (;;) { -+ set_current_state(state); -+ -+ if (i915_request_completed(rq)) -+ break; -+ -+ if (signal_pending_state(state, current)) { -+ timeout = -ERESTARTSYS; -+ break; -+ } -+ -+ if (!timeout) { -+ timeout = -ETIME; -+ break; -+ } -+ -+ timeout = io_schedule_timeout(timeout); -+ } -+ __set_current_state(TASK_RUNNING); -+ -+ dma_fence_remove_callback(&rq->fence, &wait.cb); -+ -+out: -+ trace_i915_request_wait_end(rq); -+ return timeout; -+} -+ -+void i915_retire_requests(struct drm_i915_private *i915) -+{ -+ struct intel_ring *ring, *tmp; -+ -+ lockdep_assert_held(&i915->drm.struct_mutex); -+ -+ if (!i915->gt.active_requests) -+ return; -+ -+ list_for_each_entry_safe(ring, tmp, -+ &i915->gt.active_rings, active_link) { -+ intel_ring_get(ring); /* last rq holds reference! */ -+ ring_retire_requests(ring); -+ intel_ring_put(ring); -+ } -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -+#include "selftests/mock_request.c" -+#include "selftests/i915_request.c" -+#endif -+ -+static void i915_global_request_shrink(void) -+{ -+ kmem_cache_shrink(global.slab_dependencies); -+ kmem_cache_shrink(global.slab_execute_cbs); -+ kmem_cache_shrink(global.slab_requests); -+} -+ -+static void i915_global_request_exit(void) -+{ -+ kmem_cache_destroy(global.slab_dependencies); -+ kmem_cache_destroy(global.slab_execute_cbs); -+ kmem_cache_destroy(global.slab_requests); -+} -+ -+static struct i915_global_request global = { { -+ .shrink = i915_global_request_shrink, -+ .exit = i915_global_request_exit, -+} }; -+ -+int __init i915_global_request_init(void) -+{ -+ global.slab_requests = KMEM_CACHE(i915_request, -+ SLAB_HWCACHE_ALIGN | -+ SLAB_RECLAIM_ACCOUNT | -+ SLAB_TYPESAFE_BY_RCU); -+ if (!global.slab_requests) -+ return -ENOMEM; -+ -+ global.slab_execute_cbs = KMEM_CACHE(execute_cb, -+ SLAB_HWCACHE_ALIGN | -+ SLAB_RECLAIM_ACCOUNT | -+ SLAB_TYPESAFE_BY_RCU); -+ if (!global.slab_execute_cbs) -+ goto err_requests; -+ -+ global.slab_dependencies = KMEM_CACHE(i915_dependency, -+ SLAB_HWCACHE_ALIGN | -+ SLAB_RECLAIM_ACCOUNT); -+ if (!global.slab_dependencies) -+ goto err_execute_cbs; -+ -+ i915_global_register(&global.base); -+ return 0; -+ -+err_execute_cbs: -+ kmem_cache_destroy(global.slab_execute_cbs); -+err_requests: -+ kmem_cache_destroy(global.slab_requests); -+ return -ENOMEM; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_request.h b/drivers/gpu/drm/i915_legacy/i915_request.h -new file mode 100644 -index 000000000000..a982664618c2 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_request.h -@@ -0,0 +1,423 @@ -+/* -+ * Copyright © 2008-2018 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef I915_REQUEST_H -+#define I915_REQUEST_H -+ -+#include -+#include -+ -+#include "i915_gem.h" -+#include "i915_scheduler.h" -+#include "i915_selftest.h" -+#include "i915_sw_fence.h" -+ -+#include -+ -+struct drm_file; -+struct drm_i915_gem_object; -+struct i915_request; -+struct i915_timeline; -+struct i915_timeline_cacheline; -+ -+struct i915_capture_list { -+ struct i915_capture_list *next; -+ struct i915_vma *vma; -+}; -+ -+enum { -+ /* -+ * I915_FENCE_FLAG_ACTIVE - this request is currently submitted to HW. -+ * -+ * Set by __i915_request_submit() on handing over to HW, and cleared -+ * by __i915_request_unsubmit() if we preempt this request. -+ * -+ * Finally cleared for consistency on retiring the request, when -+ * we know the HW is no longer running this request. -+ * -+ * See i915_request_is_active() -+ */ -+ I915_FENCE_FLAG_ACTIVE = DMA_FENCE_FLAG_USER_BITS, -+ -+ /* -+ * I915_FENCE_FLAG_SIGNAL - this request is currently on signal_list -+ * -+ * Internal bookkeeping used by the breadcrumb code to track when -+ * a request is on the various signal_list. -+ */ -+ I915_FENCE_FLAG_SIGNAL, -+}; -+ -+/** -+ * Request queue structure. -+ * -+ * The request queue allows us to note sequence numbers that have been emitted -+ * and may be associated with active buffers to be retired. -+ * -+ * By keeping this list, we can avoid having to do questionable sequence -+ * number comparisons on buffer last_read|write_seqno. It also allows an -+ * emission time to be associated with the request for tracking how far ahead -+ * of the GPU the submission is. -+ * -+ * When modifying this structure be very aware that we perform a lockless -+ * RCU lookup of it that may race against reallocation of the struct -+ * from the slab freelist. We intentionally do not zero the structure on -+ * allocation so that the lookup can use the dangling pointers (and is -+ * cogniscent that those pointers may be wrong). Instead, everything that -+ * needs to be initialised must be done so explicitly. -+ * -+ * The requests are reference counted. -+ */ -+struct i915_request { -+ struct dma_fence fence; -+ spinlock_t lock; -+ -+ /** On Which ring this request was generated */ -+ struct drm_i915_private *i915; -+ -+ /** -+ * Context and ring buffer related to this request -+ * Contexts are refcounted, so when this request is associated with a -+ * context, we must increment the context's refcount, to guarantee that -+ * it persists while any request is linked to it. Requests themselves -+ * are also refcounted, so the request will only be freed when the last -+ * reference to it is dismissed, and the code in -+ * i915_request_free() will then decrement the refcount on the -+ * context. -+ */ -+ struct i915_gem_context *gem_context; -+ struct intel_engine_cs *engine; -+ struct intel_context *hw_context; -+ struct intel_ring *ring; -+ struct i915_timeline *timeline; -+ struct list_head signal_link; -+ -+ /* -+ * The rcu epoch of when this request was allocated. Used to judiciously -+ * apply backpressure on future allocations to ensure that under -+ * mempressure there is sufficient RCU ticks for us to reclaim our -+ * RCU protected slabs. -+ */ -+ unsigned long rcustate; -+ -+ /* -+ * We pin the timeline->mutex while constructing the request to -+ * ensure that no caller accidentally drops it during construction. -+ * The timeline->mutex must be held to ensure that only this caller -+ * can use the ring and manipulate the associated timeline during -+ * construction. -+ */ -+ struct pin_cookie cookie; -+ -+ /* -+ * Fences for the various phases in the request's lifetime. -+ * -+ * The submit fence is used to await upon all of the request's -+ * dependencies. When it is signaled, the request is ready to run. -+ * It is used by the driver to then queue the request for execution. -+ */ -+ struct i915_sw_fence submit; -+ union { -+ wait_queue_entry_t submitq; -+ struct i915_sw_dma_fence_cb dmaq; -+ }; -+ struct list_head execute_cb; -+ struct i915_sw_fence semaphore; -+ -+ /* -+ * A list of everyone we wait upon, and everyone who waits upon us. -+ * Even though we will not be submitted to the hardware before the -+ * submit fence is signaled (it waits for all external events as well -+ * as our own requests), the scheduler still needs to know the -+ * dependency tree for the lifetime of the request (from execbuf -+ * to retirement), i.e. bidirectional dependency information for the -+ * request not tied to individual fences. -+ */ -+ struct i915_sched_node sched; -+ struct i915_dependency dep; -+ -+ /* -+ * A convenience pointer to the current breadcrumb value stored in -+ * the HW status page (or our timeline's local equivalent). The full -+ * path would be rq->hw_context->ring->timeline->hwsp_seqno. -+ */ -+ const u32 *hwsp_seqno; -+ -+ /* -+ * If we need to access the timeline's seqno for this request in -+ * another request, we need to keep a read reference to this associated -+ * cacheline, so that we do not free and recycle it before the foreign -+ * observers have completed. Hence, we keep a pointer to the cacheline -+ * inside the timeline's HWSP vma, but it is only valid while this -+ * request has not completed and guarded by the timeline mutex. -+ */ -+ struct i915_timeline_cacheline *hwsp_cacheline; -+ -+ /** Position in the ring of the start of the request */ -+ u32 head; -+ -+ /** Position in the ring of the start of the user packets */ -+ u32 infix; -+ -+ /** -+ * Position in the ring of the start of the postfix. -+ * This is required to calculate the maximum available ring space -+ * without overwriting the postfix. -+ */ -+ u32 postfix; -+ -+ /** Position in the ring of the end of the whole request */ -+ u32 tail; -+ -+ /** Position in the ring of the end of any workarounds after the tail */ -+ u32 wa_tail; -+ -+ /** Preallocate space in the ring for the emitting the request */ -+ u32 reserved_space; -+ -+ /** Batch buffer related to this request if any (used for -+ * error state dump only). -+ */ -+ struct i915_vma *batch; -+ /** -+ * Additional buffers requested by userspace to be captured upon -+ * a GPU hang. The vma/obj on this list are protected by their -+ * active reference - all objects on this list must also be -+ * on the active_list (of their final request). -+ */ -+ struct i915_capture_list *capture_list; -+ struct list_head active_list; -+ -+ /** Time at which this request was emitted, in jiffies. */ -+ unsigned long emitted_jiffies; -+ -+ bool waitboost; -+ -+ /** engine->request_list entry for this request */ -+ struct list_head link; -+ -+ /** ring->request_list entry for this request */ -+ struct list_head ring_link; -+ -+ struct drm_i915_file_private *file_priv; -+ /** file_priv list entry for this request */ -+ struct list_head client_link; -+ -+ I915_SELFTEST_DECLARE(struct { -+ struct list_head link; -+ unsigned long delay; -+ } mock;) -+}; -+ -+#define I915_FENCE_GFP (GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_NOWARN) -+ -+extern const struct dma_fence_ops i915_fence_ops; -+ -+static inline bool dma_fence_is_i915(const struct dma_fence *fence) -+{ -+ return fence->ops == &i915_fence_ops; -+} -+ -+struct i915_request * __must_check -+i915_request_alloc(struct intel_engine_cs *engine, -+ struct i915_gem_context *ctx); -+void i915_request_retire_upto(struct i915_request *rq); -+ -+static inline struct i915_request * -+to_request(struct dma_fence *fence) -+{ -+ /* We assume that NULL fence/request are interoperable */ -+ BUILD_BUG_ON(offsetof(struct i915_request, fence) != 0); -+ GEM_BUG_ON(fence && !dma_fence_is_i915(fence)); -+ return container_of(fence, struct i915_request, fence); -+} -+ -+static inline struct i915_request * -+i915_request_get(struct i915_request *rq) -+{ -+ return to_request(dma_fence_get(&rq->fence)); -+} -+ -+static inline struct i915_request * -+i915_request_get_rcu(struct i915_request *rq) -+{ -+ return to_request(dma_fence_get_rcu(&rq->fence)); -+} -+ -+static inline void -+i915_request_put(struct i915_request *rq) -+{ -+ dma_fence_put(&rq->fence); -+} -+ -+int i915_request_await_object(struct i915_request *to, -+ struct drm_i915_gem_object *obj, -+ bool write); -+int i915_request_await_dma_fence(struct i915_request *rq, -+ struct dma_fence *fence); -+ -+void i915_request_add(struct i915_request *rq); -+ -+void __i915_request_submit(struct i915_request *request); -+void i915_request_submit(struct i915_request *request); -+ -+void i915_request_skip(struct i915_request *request, int error); -+ -+void __i915_request_unsubmit(struct i915_request *request); -+void i915_request_unsubmit(struct i915_request *request); -+ -+/* Note: part of the intel_breadcrumbs family */ -+bool i915_request_enable_breadcrumb(struct i915_request *request); -+void i915_request_cancel_breadcrumb(struct i915_request *request); -+ -+long i915_request_wait(struct i915_request *rq, -+ unsigned int flags, -+ long timeout) -+ __attribute__((nonnull(1))); -+#define I915_WAIT_INTERRUPTIBLE BIT(0) -+#define I915_WAIT_LOCKED BIT(1) /* struct_mutex held, handle GPU reset */ -+#define I915_WAIT_PRIORITY BIT(2) /* small priority bump for the request */ -+#define I915_WAIT_ALL BIT(3) /* used by i915_gem_object_wait() */ -+#define I915_WAIT_FOR_IDLE_BOOST BIT(4) -+ -+static inline bool i915_request_signaled(const struct i915_request *rq) -+{ -+ /* The request may live longer than its HWSP, so check flags first! */ -+ return test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &rq->fence.flags); -+} -+ -+static inline bool i915_request_is_active(const struct i915_request *rq) -+{ -+ return test_bit(I915_FENCE_FLAG_ACTIVE, &rq->fence.flags); -+} -+ -+/** -+ * Returns true if seq1 is later than seq2. -+ */ -+static inline bool i915_seqno_passed(u32 seq1, u32 seq2) -+{ -+ return (s32)(seq1 - seq2) >= 0; -+} -+ -+static inline u32 __hwsp_seqno(const struct i915_request *rq) -+{ -+ return READ_ONCE(*rq->hwsp_seqno); -+} -+ -+/** -+ * hwsp_seqno - the current breadcrumb value in the HW status page -+ * @rq: the request, to chase the relevant HW status page -+ * -+ * The emphasis in naming here is that hwsp_seqno() is not a property of the -+ * request, but an indication of the current HW state (associated with this -+ * request). Its value will change as the GPU executes more requests. -+ * -+ * Returns the current breadcrumb value in the associated HW status page (or -+ * the local timeline's equivalent) for this request. The request itself -+ * has the associated breadcrumb value of rq->fence.seqno, when the HW -+ * status page has that breadcrumb or later, this request is complete. -+ */ -+static inline u32 hwsp_seqno(const struct i915_request *rq) -+{ -+ u32 seqno; -+ -+ rcu_read_lock(); /* the HWSP may be freed at runtime */ -+ seqno = __hwsp_seqno(rq); -+ rcu_read_unlock(); -+ -+ return seqno; -+} -+ -+static inline bool __i915_request_has_started(const struct i915_request *rq) -+{ -+ return i915_seqno_passed(hwsp_seqno(rq), rq->fence.seqno - 1); -+} -+ -+/** -+ * i915_request_started - check if the request has begun being executed -+ * @rq: the request -+ * -+ * If the timeline is not using initial breadcrumbs, a request is -+ * considered started if the previous request on its timeline (i.e. -+ * context) has been signaled. -+ * -+ * If the timeline is using semaphores, it will also be emitting an -+ * "initial breadcrumb" after the semaphores are complete and just before -+ * it began executing the user payload. A request can therefore be active -+ * on the HW and not yet started as it is still busywaiting on its -+ * dependencies (via HW semaphores). -+ * -+ * If the request has started, its dependencies will have been signaled -+ * (either by fences or by semaphores) and it will have begun processing -+ * the user payload. -+ * -+ * However, even if a request has started, it may have been preempted and -+ * so no longer active, or it may have already completed. -+ * -+ * See also i915_request_is_active(). -+ * -+ * Returns true if the request has begun executing the user payload, or -+ * has completed: -+ */ -+static inline bool i915_request_started(const struct i915_request *rq) -+{ -+ if (i915_request_signaled(rq)) -+ return true; -+ -+ /* Remember: started but may have since been preempted! */ -+ return __i915_request_has_started(rq); -+} -+ -+/** -+ * i915_request_is_running - check if the request may actually be executing -+ * @rq: the request -+ * -+ * Returns true if the request is currently submitted to hardware, has passed -+ * its start point (i.e. the context is setup and not busywaiting). Note that -+ * it may no longer be running by the time the function returns! -+ */ -+static inline bool i915_request_is_running(const struct i915_request *rq) -+{ -+ if (!i915_request_is_active(rq)) -+ return false; -+ -+ return __i915_request_has_started(rq); -+} -+ -+static inline bool i915_request_completed(const struct i915_request *rq) -+{ -+ if (i915_request_signaled(rq)) -+ return true; -+ -+ return i915_seqno_passed(hwsp_seqno(rq), rq->fence.seqno); -+} -+ -+static inline void i915_request_mark_complete(struct i915_request *rq) -+{ -+ rq->hwsp_seqno = (u32 *)&rq->fence.seqno; /* decouple from HWSP */ -+} -+ -+void i915_retire_requests(struct drm_i915_private *i915); -+ -+#endif /* I915_REQUEST_H */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_reset.c b/drivers/gpu/drm/i915_legacy/i915_reset.c -new file mode 100644 -index 000000000000..677d59304e78 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_reset.c -@@ -0,0 +1,1474 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2008-2018 Intel Corporation -+ */ -+ -+#include -+#include -+ -+#include "i915_drv.h" -+#include "i915_gpu_error.h" -+#include "i915_reset.h" -+ -+#include "intel_guc.h" -+ -+#define RESET_MAX_RETRIES 3 -+ -+/* XXX How to handle concurrent GGTT updates using tiling registers? */ -+#define RESET_UNDER_STOP_MACHINE 0 -+ -+static void rmw_set(struct intel_uncore *uncore, i915_reg_t reg, u32 set) -+{ -+ intel_uncore_rmw(uncore, reg, 0, set); -+} -+ -+static void rmw_clear(struct intel_uncore *uncore, i915_reg_t reg, u32 clr) -+{ -+ intel_uncore_rmw(uncore, reg, clr, 0); -+} -+ -+static void rmw_set_fw(struct intel_uncore *uncore, i915_reg_t reg, u32 set) -+{ -+ intel_uncore_rmw_fw(uncore, reg, 0, set); -+} -+ -+static void rmw_clear_fw(struct intel_uncore *uncore, i915_reg_t reg, u32 clr) -+{ -+ intel_uncore_rmw_fw(uncore, reg, clr, 0); -+} -+ -+static void engine_skip_context(struct i915_request *rq) -+{ -+ struct intel_engine_cs *engine = rq->engine; -+ struct i915_gem_context *hung_ctx = rq->gem_context; -+ -+ lockdep_assert_held(&engine->timeline.lock); -+ -+ if (!i915_request_is_active(rq)) -+ return; -+ -+ list_for_each_entry_continue(rq, &engine->timeline.requests, link) -+ if (rq->gem_context == hung_ctx) -+ i915_request_skip(rq, -EIO); -+} -+ -+static void client_mark_guilty(struct drm_i915_file_private *file_priv, -+ const struct i915_gem_context *ctx) -+{ -+ unsigned int score; -+ unsigned long prev_hang; -+ -+ if (i915_gem_context_is_banned(ctx)) -+ score = I915_CLIENT_SCORE_CONTEXT_BAN; -+ else -+ score = 0; -+ -+ prev_hang = xchg(&file_priv->hang_timestamp, jiffies); -+ if (time_before(jiffies, prev_hang + I915_CLIENT_FAST_HANG_JIFFIES)) -+ score += I915_CLIENT_SCORE_HANG_FAST; -+ -+ if (score) { -+ atomic_add(score, &file_priv->ban_score); -+ -+ DRM_DEBUG_DRIVER("client %s: gained %u ban score, now %u\n", -+ ctx->name, score, -+ atomic_read(&file_priv->ban_score)); -+ } -+} -+ -+static bool context_mark_guilty(struct i915_gem_context *ctx) -+{ -+ unsigned long prev_hang; -+ bool banned; -+ int i; -+ -+ atomic_inc(&ctx->guilty_count); -+ -+ /* Cool contexts are too cool to be banned! (Used for reset testing.) */ -+ if (!i915_gem_context_is_bannable(ctx)) -+ return false; -+ -+ /* Record the timestamp for the last N hangs */ -+ prev_hang = ctx->hang_timestamp[0]; -+ for (i = 0; i < ARRAY_SIZE(ctx->hang_timestamp) - 1; i++) -+ ctx->hang_timestamp[i] = ctx->hang_timestamp[i + 1]; -+ ctx->hang_timestamp[i] = jiffies; -+ -+ /* If we have hung N+1 times in rapid succession, we ban the context! */ -+ banned = !i915_gem_context_is_recoverable(ctx); -+ if (time_before(jiffies, prev_hang + CONTEXT_FAST_HANG_JIFFIES)) -+ banned = true; -+ if (banned) { -+ DRM_DEBUG_DRIVER("context %s: guilty %d, banned\n", -+ ctx->name, atomic_read(&ctx->guilty_count)); -+ i915_gem_context_set_banned(ctx); -+ } -+ -+ if (!IS_ERR_OR_NULL(ctx->file_priv)) -+ client_mark_guilty(ctx->file_priv, ctx); -+ -+ return banned; -+} -+ -+static void context_mark_innocent(struct i915_gem_context *ctx) -+{ -+ atomic_inc(&ctx->active_count); -+} -+ -+void i915_reset_request(struct i915_request *rq, bool guilty) -+{ -+ GEM_TRACE("%s rq=%llx:%lld, guilty? %s\n", -+ rq->engine->name, -+ rq->fence.context, -+ rq->fence.seqno, -+ yesno(guilty)); -+ -+ lockdep_assert_held(&rq->engine->timeline.lock); -+ GEM_BUG_ON(i915_request_completed(rq)); -+ -+ if (guilty) { -+ i915_request_skip(rq, -EIO); -+ if (context_mark_guilty(rq->gem_context)) -+ engine_skip_context(rq); -+ } else { -+ dma_fence_set_error(&rq->fence, -EAGAIN); -+ context_mark_innocent(rq->gem_context); -+ } -+} -+ -+static void gen3_stop_engine(struct intel_engine_cs *engine) -+{ -+ struct intel_uncore *uncore = engine->uncore; -+ const u32 base = engine->mmio_base; -+ -+ GEM_TRACE("%s\n", engine->name); -+ -+ if (intel_engine_stop_cs(engine)) -+ GEM_TRACE("%s: timed out on STOP_RING\n", engine->name); -+ -+ intel_uncore_write_fw(uncore, -+ RING_HEAD(base), -+ intel_uncore_read_fw(uncore, RING_TAIL(base))); -+ intel_uncore_posting_read_fw(uncore, RING_HEAD(base)); /* paranoia */ -+ -+ intel_uncore_write_fw(uncore, RING_HEAD(base), 0); -+ intel_uncore_write_fw(uncore, RING_TAIL(base), 0); -+ intel_uncore_posting_read_fw(uncore, RING_TAIL(base)); -+ -+ /* The ring must be empty before it is disabled */ -+ intel_uncore_write_fw(uncore, RING_CTL(base), 0); -+ -+ /* Check acts as a post */ -+ if (intel_uncore_read_fw(uncore, RING_HEAD(base))) -+ GEM_TRACE("%s: ring head [%x] not parked\n", -+ engine->name, -+ intel_uncore_read_fw(uncore, RING_HEAD(base))); -+} -+ -+static void i915_stop_engines(struct drm_i915_private *i915, -+ intel_engine_mask_t engine_mask) -+{ -+ struct intel_engine_cs *engine; -+ intel_engine_mask_t tmp; -+ -+ if (INTEL_GEN(i915) < 3) -+ return; -+ -+ for_each_engine_masked(engine, i915, engine_mask, tmp) -+ gen3_stop_engine(engine); -+} -+ -+static bool i915_in_reset(struct pci_dev *pdev) -+{ -+ u8 gdrst; -+ -+ pci_read_config_byte(pdev, I915_GDRST, &gdrst); -+ return gdrst & GRDOM_RESET_STATUS; -+} -+ -+static int i915_do_reset(struct drm_i915_private *i915, -+ intel_engine_mask_t engine_mask, -+ unsigned int retry) -+{ -+ struct pci_dev *pdev = i915->drm.pdev; -+ int err; -+ -+ /* Assert reset for at least 20 usec, and wait for acknowledgement. */ -+ pci_write_config_byte(pdev, I915_GDRST, GRDOM_RESET_ENABLE); -+ udelay(50); -+ err = wait_for_atomic(i915_in_reset(pdev), 50); -+ -+ /* Clear the reset request. */ -+ pci_write_config_byte(pdev, I915_GDRST, 0); -+ udelay(50); -+ if (!err) -+ err = wait_for_atomic(!i915_in_reset(pdev), 50); -+ -+ return err; -+} -+ -+static bool g4x_reset_complete(struct pci_dev *pdev) -+{ -+ u8 gdrst; -+ -+ pci_read_config_byte(pdev, I915_GDRST, &gdrst); -+ return (gdrst & GRDOM_RESET_ENABLE) == 0; -+} -+ -+static int g33_do_reset(struct drm_i915_private *i915, -+ intel_engine_mask_t engine_mask, -+ unsigned int retry) -+{ -+ struct pci_dev *pdev = i915->drm.pdev; -+ -+ pci_write_config_byte(pdev, I915_GDRST, GRDOM_RESET_ENABLE); -+ return wait_for_atomic(g4x_reset_complete(pdev), 50); -+} -+ -+static int g4x_do_reset(struct drm_i915_private *i915, -+ intel_engine_mask_t engine_mask, -+ unsigned int retry) -+{ -+ struct pci_dev *pdev = i915->drm.pdev; -+ struct intel_uncore *uncore = &i915->uncore; -+ int ret; -+ -+ /* WaVcpClkGateDisableForMediaReset:ctg,elk */ -+ rmw_set_fw(uncore, VDECCLK_GATE_D, VCP_UNIT_CLOCK_GATE_DISABLE); -+ intel_uncore_posting_read_fw(uncore, VDECCLK_GATE_D); -+ -+ pci_write_config_byte(pdev, I915_GDRST, -+ GRDOM_MEDIA | GRDOM_RESET_ENABLE); -+ ret = wait_for_atomic(g4x_reset_complete(pdev), 50); -+ if (ret) { -+ DRM_DEBUG_DRIVER("Wait for media reset failed\n"); -+ goto out; -+ } -+ -+ pci_write_config_byte(pdev, I915_GDRST, -+ GRDOM_RENDER | GRDOM_RESET_ENABLE); -+ ret = wait_for_atomic(g4x_reset_complete(pdev), 50); -+ if (ret) { -+ DRM_DEBUG_DRIVER("Wait for render reset failed\n"); -+ goto out; -+ } -+ -+out: -+ pci_write_config_byte(pdev, I915_GDRST, 0); -+ -+ rmw_clear_fw(uncore, VDECCLK_GATE_D, VCP_UNIT_CLOCK_GATE_DISABLE); -+ intel_uncore_posting_read_fw(uncore, VDECCLK_GATE_D); -+ -+ return ret; -+} -+ -+static int ironlake_do_reset(struct drm_i915_private *i915, -+ intel_engine_mask_t engine_mask, -+ unsigned int retry) -+{ -+ struct intel_uncore *uncore = &i915->uncore; -+ int ret; -+ -+ intel_uncore_write_fw(uncore, ILK_GDSR, -+ ILK_GRDOM_RENDER | ILK_GRDOM_RESET_ENABLE); -+ ret = __intel_wait_for_register_fw(uncore, ILK_GDSR, -+ ILK_GRDOM_RESET_ENABLE, 0, -+ 5000, 0, -+ NULL); -+ if (ret) { -+ DRM_DEBUG_DRIVER("Wait for render reset failed\n"); -+ goto out; -+ } -+ -+ intel_uncore_write_fw(uncore, ILK_GDSR, -+ ILK_GRDOM_MEDIA | ILK_GRDOM_RESET_ENABLE); -+ ret = __intel_wait_for_register_fw(uncore, ILK_GDSR, -+ ILK_GRDOM_RESET_ENABLE, 0, -+ 5000, 0, -+ NULL); -+ if (ret) { -+ DRM_DEBUG_DRIVER("Wait for media reset failed\n"); -+ goto out; -+ } -+ -+out: -+ intel_uncore_write_fw(uncore, ILK_GDSR, 0); -+ intel_uncore_posting_read_fw(uncore, ILK_GDSR); -+ return ret; -+} -+ -+/* Reset the hardware domains (GENX_GRDOM_*) specified by mask */ -+static int gen6_hw_domain_reset(struct drm_i915_private *i915, -+ u32 hw_domain_mask) -+{ -+ struct intel_uncore *uncore = &i915->uncore; -+ int err; -+ -+ /* -+ * GEN6_GDRST is not in the gt power well, no need to check -+ * for fifo space for the write or forcewake the chip for -+ * the read -+ */ -+ intel_uncore_write_fw(uncore, GEN6_GDRST, hw_domain_mask); -+ -+ /* Wait for the device to ack the reset requests */ -+ err = __intel_wait_for_register_fw(uncore, -+ GEN6_GDRST, hw_domain_mask, 0, -+ 500, 0, -+ NULL); -+ if (err) -+ DRM_DEBUG_DRIVER("Wait for 0x%08x engines reset failed\n", -+ hw_domain_mask); -+ -+ return err; -+} -+ -+static int gen6_reset_engines(struct drm_i915_private *i915, -+ intel_engine_mask_t engine_mask, -+ unsigned int retry) -+{ -+ struct intel_engine_cs *engine; -+ const u32 hw_engine_mask[] = { -+ [RCS0] = GEN6_GRDOM_RENDER, -+ [BCS0] = GEN6_GRDOM_BLT, -+ [VCS0] = GEN6_GRDOM_MEDIA, -+ [VCS1] = GEN8_GRDOM_MEDIA2, -+ [VECS0] = GEN6_GRDOM_VECS, -+ }; -+ u32 hw_mask; -+ -+ if (engine_mask == ALL_ENGINES) { -+ hw_mask = GEN6_GRDOM_FULL; -+ } else { -+ intel_engine_mask_t tmp; -+ -+ hw_mask = 0; -+ for_each_engine_masked(engine, i915, engine_mask, tmp) { -+ GEM_BUG_ON(engine->id >= ARRAY_SIZE(hw_engine_mask)); -+ hw_mask |= hw_engine_mask[engine->id]; -+ } -+ } -+ -+ return gen6_hw_domain_reset(i915, hw_mask); -+} -+ -+static u32 gen11_lock_sfc(struct intel_engine_cs *engine) -+{ -+ struct intel_uncore *uncore = engine->uncore; -+ u8 vdbox_sfc_access = RUNTIME_INFO(engine->i915)->vdbox_sfc_access; -+ i915_reg_t sfc_forced_lock, sfc_forced_lock_ack; -+ u32 sfc_forced_lock_bit, sfc_forced_lock_ack_bit; -+ i915_reg_t sfc_usage; -+ u32 sfc_usage_bit; -+ u32 sfc_reset_bit; -+ -+ switch (engine->class) { -+ case VIDEO_DECODE_CLASS: -+ if ((BIT(engine->instance) & vdbox_sfc_access) == 0) -+ return 0; -+ -+ sfc_forced_lock = GEN11_VCS_SFC_FORCED_LOCK(engine); -+ sfc_forced_lock_bit = GEN11_VCS_SFC_FORCED_LOCK_BIT; -+ -+ sfc_forced_lock_ack = GEN11_VCS_SFC_LOCK_STATUS(engine); -+ sfc_forced_lock_ack_bit = GEN11_VCS_SFC_LOCK_ACK_BIT; -+ -+ sfc_usage = GEN11_VCS_SFC_LOCK_STATUS(engine); -+ sfc_usage_bit = GEN11_VCS_SFC_USAGE_BIT; -+ sfc_reset_bit = GEN11_VCS_SFC_RESET_BIT(engine->instance); -+ break; -+ -+ case VIDEO_ENHANCEMENT_CLASS: -+ sfc_forced_lock = GEN11_VECS_SFC_FORCED_LOCK(engine); -+ sfc_forced_lock_bit = GEN11_VECS_SFC_FORCED_LOCK_BIT; -+ -+ sfc_forced_lock_ack = GEN11_VECS_SFC_LOCK_ACK(engine); -+ sfc_forced_lock_ack_bit = GEN11_VECS_SFC_LOCK_ACK_BIT; -+ -+ sfc_usage = GEN11_VECS_SFC_USAGE(engine); -+ sfc_usage_bit = GEN11_VECS_SFC_USAGE_BIT; -+ sfc_reset_bit = GEN11_VECS_SFC_RESET_BIT(engine->instance); -+ break; -+ -+ default: -+ return 0; -+ } -+ -+ /* -+ * Tell the engine that a software reset is going to happen. The engine -+ * will then try to force lock the SFC (if currently locked, it will -+ * remain so until we tell the engine it is safe to unlock; if currently -+ * unlocked, it will ignore this and all new lock requests). If SFC -+ * ends up being locked to the engine we want to reset, we have to reset -+ * it as well (we will unlock it once the reset sequence is completed). -+ */ -+ rmw_set_fw(uncore, sfc_forced_lock, sfc_forced_lock_bit); -+ -+ if (__intel_wait_for_register_fw(uncore, -+ sfc_forced_lock_ack, -+ sfc_forced_lock_ack_bit, -+ sfc_forced_lock_ack_bit, -+ 1000, 0, NULL)) { -+ DRM_DEBUG_DRIVER("Wait for SFC forced lock ack failed\n"); -+ return 0; -+ } -+ -+ if (intel_uncore_read_fw(uncore, sfc_usage) & sfc_usage_bit) -+ return sfc_reset_bit; -+ -+ return 0; -+} -+ -+static void gen11_unlock_sfc(struct intel_engine_cs *engine) -+{ -+ struct intel_uncore *uncore = engine->uncore; -+ u8 vdbox_sfc_access = RUNTIME_INFO(engine->i915)->vdbox_sfc_access; -+ i915_reg_t sfc_forced_lock; -+ u32 sfc_forced_lock_bit; -+ -+ switch (engine->class) { -+ case VIDEO_DECODE_CLASS: -+ if ((BIT(engine->instance) & vdbox_sfc_access) == 0) -+ return; -+ -+ sfc_forced_lock = GEN11_VCS_SFC_FORCED_LOCK(engine); -+ sfc_forced_lock_bit = GEN11_VCS_SFC_FORCED_LOCK_BIT; -+ break; -+ -+ case VIDEO_ENHANCEMENT_CLASS: -+ sfc_forced_lock = GEN11_VECS_SFC_FORCED_LOCK(engine); -+ sfc_forced_lock_bit = GEN11_VECS_SFC_FORCED_LOCK_BIT; -+ break; -+ -+ default: -+ return; -+ } -+ -+ rmw_clear_fw(uncore, sfc_forced_lock, sfc_forced_lock_bit); -+} -+ -+static int gen11_reset_engines(struct drm_i915_private *i915, -+ intel_engine_mask_t engine_mask, -+ unsigned int retry) -+{ -+ const u32 hw_engine_mask[] = { -+ [RCS0] = GEN11_GRDOM_RENDER, -+ [BCS0] = GEN11_GRDOM_BLT, -+ [VCS0] = GEN11_GRDOM_MEDIA, -+ [VCS1] = GEN11_GRDOM_MEDIA2, -+ [VCS2] = GEN11_GRDOM_MEDIA3, -+ [VCS3] = GEN11_GRDOM_MEDIA4, -+ [VECS0] = GEN11_GRDOM_VECS, -+ [VECS1] = GEN11_GRDOM_VECS2, -+ }; -+ struct intel_engine_cs *engine; -+ intel_engine_mask_t tmp; -+ u32 hw_mask; -+ int ret; -+ -+ if (engine_mask == ALL_ENGINES) { -+ hw_mask = GEN11_GRDOM_FULL; -+ } else { -+ hw_mask = 0; -+ for_each_engine_masked(engine, i915, engine_mask, tmp) { -+ GEM_BUG_ON(engine->id >= ARRAY_SIZE(hw_engine_mask)); -+ hw_mask |= hw_engine_mask[engine->id]; -+ hw_mask |= gen11_lock_sfc(engine); -+ } -+ } -+ -+ ret = gen6_hw_domain_reset(i915, hw_mask); -+ -+ if (engine_mask != ALL_ENGINES) -+ for_each_engine_masked(engine, i915, engine_mask, tmp) -+ gen11_unlock_sfc(engine); -+ -+ return ret; -+} -+ -+static int gen8_engine_reset_prepare(struct intel_engine_cs *engine) -+{ -+ struct intel_uncore *uncore = engine->uncore; -+ const i915_reg_t reg = RING_RESET_CTL(engine->mmio_base); -+ u32 request, mask, ack; -+ int ret; -+ -+ ack = intel_uncore_read_fw(uncore, reg); -+ if (ack & RESET_CTL_CAT_ERROR) { -+ /* -+ * For catastrophic errors, ready-for-reset sequence -+ * needs to be bypassed: HAS#396813 -+ */ -+ request = RESET_CTL_CAT_ERROR; -+ mask = RESET_CTL_CAT_ERROR; -+ -+ /* Catastrophic errors need to be cleared by HW */ -+ ack = 0; -+ } else if (!(ack & RESET_CTL_READY_TO_RESET)) { -+ request = RESET_CTL_REQUEST_RESET; -+ mask = RESET_CTL_READY_TO_RESET; -+ ack = RESET_CTL_READY_TO_RESET; -+ } else { -+ return 0; -+ } -+ -+ intel_uncore_write_fw(uncore, reg, _MASKED_BIT_ENABLE(request)); -+ ret = __intel_wait_for_register_fw(uncore, reg, mask, ack, -+ 700, 0, NULL); -+ if (ret) -+ DRM_ERROR("%s reset request timed out: {request: %08x, RESET_CTL: %08x}\n", -+ engine->name, request, -+ intel_uncore_read_fw(uncore, reg)); -+ -+ return ret; -+} -+ -+static void gen8_engine_reset_cancel(struct intel_engine_cs *engine) -+{ -+ intel_uncore_write_fw(engine->uncore, -+ RING_RESET_CTL(engine->mmio_base), -+ _MASKED_BIT_DISABLE(RESET_CTL_REQUEST_RESET)); -+} -+ -+static int gen8_reset_engines(struct drm_i915_private *i915, -+ intel_engine_mask_t engine_mask, -+ unsigned int retry) -+{ -+ struct intel_engine_cs *engine; -+ const bool reset_non_ready = retry >= 1; -+ intel_engine_mask_t tmp; -+ int ret; -+ -+ for_each_engine_masked(engine, i915, engine_mask, tmp) { -+ ret = gen8_engine_reset_prepare(engine); -+ if (ret && !reset_non_ready) -+ goto skip_reset; -+ -+ /* -+ * If this is not the first failed attempt to prepare, -+ * we decide to proceed anyway. -+ * -+ * By doing so we risk context corruption and with -+ * some gens (kbl), possible system hang if reset -+ * happens during active bb execution. -+ * -+ * We rather take context corruption instead of -+ * failed reset with a wedged driver/gpu. And -+ * active bb execution case should be covered by -+ * i915_stop_engines we have before the reset. -+ */ -+ } -+ -+ if (INTEL_GEN(i915) >= 11) -+ ret = gen11_reset_engines(i915, engine_mask, retry); -+ else -+ ret = gen6_reset_engines(i915, engine_mask, retry); -+ -+skip_reset: -+ for_each_engine_masked(engine, i915, engine_mask, tmp) -+ gen8_engine_reset_cancel(engine); -+ -+ return ret; -+} -+ -+typedef int (*reset_func)(struct drm_i915_private *, -+ intel_engine_mask_t engine_mask, -+ unsigned int retry); -+ -+static reset_func intel_get_gpu_reset(struct drm_i915_private *i915) -+{ -+ if (INTEL_GEN(i915) >= 8) -+ return gen8_reset_engines; -+ else if (INTEL_GEN(i915) >= 6) -+ return gen6_reset_engines; -+ else if (INTEL_GEN(i915) >= 5) -+ return ironlake_do_reset; -+ else if (IS_G4X(i915)) -+ return g4x_do_reset; -+ else if (IS_G33(i915) || IS_PINEVIEW(i915)) -+ return g33_do_reset; -+ else if (INTEL_GEN(i915) >= 3) -+ return i915_do_reset; -+ else -+ return NULL; -+} -+ -+int intel_gpu_reset(struct drm_i915_private *i915, -+ intel_engine_mask_t engine_mask) -+{ -+ const int retries = engine_mask == ALL_ENGINES ? RESET_MAX_RETRIES : 1; -+ reset_func reset; -+ int ret = -ETIMEDOUT; -+ int retry; -+ -+ reset = intel_get_gpu_reset(i915); -+ if (!reset) -+ return -ENODEV; -+ -+ /* -+ * If the power well sleeps during the reset, the reset -+ * request may be dropped and never completes (causing -EIO). -+ */ -+ intel_uncore_forcewake_get(&i915->uncore, FORCEWAKE_ALL); -+ for (retry = 0; ret == -ETIMEDOUT && retry < retries; retry++) { -+ /* -+ * We stop engines, otherwise we might get failed reset and a -+ * dead gpu (on elk). Also as modern gpu as kbl can suffer -+ * from system hang if batchbuffer is progressing when -+ * the reset is issued, regardless of READY_TO_RESET ack. -+ * Thus assume it is best to stop engines on all gens -+ * where we have a gpu reset. -+ * -+ * WaKBLVECSSemaphoreWaitPoll:kbl (on ALL_ENGINES) -+ * -+ * WaMediaResetMainRingCleanup:ctg,elk (presumably) -+ * -+ * FIXME: Wa for more modern gens needs to be validated -+ */ -+ if (retry) -+ i915_stop_engines(i915, engine_mask); -+ -+ GEM_TRACE("engine_mask=%x\n", engine_mask); -+ preempt_disable(); -+ ret = reset(i915, engine_mask, retry); -+ preempt_enable(); -+ } -+ intel_uncore_forcewake_put(&i915->uncore, FORCEWAKE_ALL); -+ -+ return ret; -+} -+ -+bool intel_has_gpu_reset(struct drm_i915_private *i915) -+{ -+ if (USES_GUC(i915)) -+ return false; -+ -+ if (!i915_modparams.reset) -+ return NULL; -+ -+ return intel_get_gpu_reset(i915); -+} -+ -+bool intel_has_reset_engine(struct drm_i915_private *i915) -+{ -+ return INTEL_INFO(i915)->has_reset_engine && i915_modparams.reset >= 2; -+} -+ -+int intel_reset_guc(struct drm_i915_private *i915) -+{ -+ u32 guc_domain = -+ INTEL_GEN(i915) >= 11 ? GEN11_GRDOM_GUC : GEN9_GRDOM_GUC; -+ int ret; -+ -+ GEM_BUG_ON(!HAS_GUC(i915)); -+ -+ intel_uncore_forcewake_get(&i915->uncore, FORCEWAKE_ALL); -+ ret = gen6_hw_domain_reset(i915, guc_domain); -+ intel_uncore_forcewake_put(&i915->uncore, FORCEWAKE_ALL); -+ -+ return ret; -+} -+ -+/* -+ * Ensure irq handler finishes, and not run again. -+ * Also return the active request so that we only search for it once. -+ */ -+static void reset_prepare_engine(struct intel_engine_cs *engine) -+{ -+ /* -+ * During the reset sequence, we must prevent the engine from -+ * entering RC6. As the context state is undefined until we restart -+ * the engine, if it does enter RC6 during the reset, the state -+ * written to the powercontext is undefined and so we may lose -+ * GPU state upon resume, i.e. fail to restart after a reset. -+ */ -+ intel_uncore_forcewake_get(engine->uncore, FORCEWAKE_ALL); -+ engine->reset.prepare(engine); -+} -+ -+static void revoke_mmaps(struct drm_i915_private *i915) -+{ -+ int i; -+ -+ for (i = 0; i < i915->num_fence_regs; i++) { -+ struct drm_vma_offset_node *node; -+ struct i915_vma *vma; -+ u64 vma_offset; -+ -+ vma = READ_ONCE(i915->fence_regs[i].vma); -+ if (!vma) -+ continue; -+ -+ if (!i915_vma_has_userfault(vma)) -+ continue; -+ -+ GEM_BUG_ON(vma->fence != &i915->fence_regs[i]); -+ node = &vma->obj->base.vma_node; -+ vma_offset = vma->ggtt_view.partial.offset << PAGE_SHIFT; -+ unmap_mapping_range(i915->drm.anon_inode->i_mapping, -+ drm_vma_node_offset_addr(node) + vma_offset, -+ vma->size, -+ 1); -+ } -+} -+ -+static void reset_prepare(struct drm_i915_private *i915) -+{ -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ -+ for_each_engine(engine, i915, id) -+ reset_prepare_engine(engine); -+ -+ intel_uc_reset_prepare(i915); -+} -+ -+static void gt_revoke(struct drm_i915_private *i915) -+{ -+ revoke_mmaps(i915); -+} -+ -+static int gt_reset(struct drm_i915_private *i915, -+ intel_engine_mask_t stalled_mask) -+{ -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ int err; -+ -+ /* -+ * Everything depends on having the GTT running, so we need to start -+ * there. -+ */ -+ err = i915_ggtt_enable_hw(i915); -+ if (err) -+ return err; -+ -+ for_each_engine(engine, i915, id) -+ intel_engine_reset(engine, stalled_mask & engine->mask); -+ -+ i915_gem_restore_fences(i915); -+ -+ return err; -+} -+ -+static void reset_finish_engine(struct intel_engine_cs *engine) -+{ -+ engine->reset.finish(engine); -+ intel_uncore_forcewake_put(engine->uncore, FORCEWAKE_ALL); -+} -+ -+struct i915_gpu_restart { -+ struct work_struct work; -+ struct drm_i915_private *i915; -+}; -+ -+static void restart_work(struct work_struct *work) -+{ -+ struct i915_gpu_restart *arg = container_of(work, typeof(*arg), work); -+ struct drm_i915_private *i915 = arg->i915; -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ intel_wakeref_t wakeref; -+ -+ wakeref = intel_runtime_pm_get(i915); -+ mutex_lock(&i915->drm.struct_mutex); -+ WRITE_ONCE(i915->gpu_error.restart, NULL); -+ -+ for_each_engine(engine, i915, id) { -+ struct i915_request *rq; -+ -+ /* -+ * Ostensibily, we always want a context loaded for powersaving, -+ * so if the engine is idle after the reset, send a request -+ * to load our scratch kernel_context. -+ */ -+ if (!intel_engine_is_idle(engine)) -+ continue; -+ -+ rq = i915_request_alloc(engine, i915->kernel_context); -+ if (!IS_ERR(rq)) -+ i915_request_add(rq); -+ } -+ -+ mutex_unlock(&i915->drm.struct_mutex); -+ intel_runtime_pm_put(i915, wakeref); -+ -+ kfree(arg); -+} -+ -+static void reset_finish(struct drm_i915_private *i915) -+{ -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ -+ for_each_engine(engine, i915, id) { -+ reset_finish_engine(engine); -+ intel_engine_signal_breadcrumbs(engine); -+ } -+} -+ -+static void reset_restart(struct drm_i915_private *i915) -+{ -+ struct i915_gpu_restart *arg; -+ -+ /* -+ * Following the reset, ensure that we always reload context for -+ * powersaving, and to correct engine->last_retired_context. Since -+ * this requires us to submit a request, queue a worker to do that -+ * task for us to evade any locking here. -+ */ -+ if (READ_ONCE(i915->gpu_error.restart)) -+ return; -+ -+ arg = kmalloc(sizeof(*arg), GFP_KERNEL); -+ if (arg) { -+ arg->i915 = i915; -+ INIT_WORK(&arg->work, restart_work); -+ -+ WRITE_ONCE(i915->gpu_error.restart, arg); -+ queue_work(i915->wq, &arg->work); -+ } -+} -+ -+static void nop_submit_request(struct i915_request *request) -+{ -+ struct intel_engine_cs *engine = request->engine; -+ unsigned long flags; -+ -+ GEM_TRACE("%s fence %llx:%lld -> -EIO\n", -+ engine->name, request->fence.context, request->fence.seqno); -+ dma_fence_set_error(&request->fence, -EIO); -+ -+ spin_lock_irqsave(&engine->timeline.lock, flags); -+ __i915_request_submit(request); -+ i915_request_mark_complete(request); -+ spin_unlock_irqrestore(&engine->timeline.lock, flags); -+ -+ intel_engine_queue_breadcrumbs(engine); -+} -+ -+static void __i915_gem_set_wedged(struct drm_i915_private *i915) -+{ -+ struct i915_gpu_error *error = &i915->gpu_error; -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ -+ if (test_bit(I915_WEDGED, &error->flags)) -+ return; -+ -+ if (GEM_SHOW_DEBUG() && !intel_engines_are_idle(i915)) { -+ struct drm_printer p = drm_debug_printer(__func__); -+ -+ for_each_engine(engine, i915, id) -+ intel_engine_dump(engine, &p, "%s\n", engine->name); -+ } -+ -+ GEM_TRACE("start\n"); -+ -+ /* -+ * First, stop submission to hw, but do not yet complete requests by -+ * rolling the global seqno forward (since this would complete requests -+ * for which we haven't set the fence error to EIO yet). -+ */ -+ reset_prepare(i915); -+ -+ /* Even if the GPU reset fails, it should still stop the engines */ -+ if (!INTEL_INFO(i915)->gpu_reset_clobbers_display) -+ intel_gpu_reset(i915, ALL_ENGINES); -+ -+ for_each_engine(engine, i915, id) { -+ engine->submit_request = nop_submit_request; -+ engine->schedule = NULL; -+ } -+ i915->caps.scheduler = 0; -+ -+ /* -+ * Make sure no request can slip through without getting completed by -+ * either this call here to intel_engine_write_global_seqno, or the one -+ * in nop_submit_request. -+ */ -+ synchronize_rcu_expedited(); -+ -+ /* Mark all executing requests as skipped */ -+ for_each_engine(engine, i915, id) -+ engine->cancel_requests(engine); -+ -+ reset_finish(i915); -+ -+ smp_mb__before_atomic(); -+ set_bit(I915_WEDGED, &error->flags); -+ -+ GEM_TRACE("end\n"); -+} -+ -+void i915_gem_set_wedged(struct drm_i915_private *i915) -+{ -+ struct i915_gpu_error *error = &i915->gpu_error; -+ intel_wakeref_t wakeref; -+ -+ mutex_lock(&error->wedge_mutex); -+ with_intel_runtime_pm(i915, wakeref) -+ __i915_gem_set_wedged(i915); -+ mutex_unlock(&error->wedge_mutex); -+} -+ -+static bool __i915_gem_unset_wedged(struct drm_i915_private *i915) -+{ -+ struct i915_gpu_error *error = &i915->gpu_error; -+ struct i915_timeline *tl; -+ -+ if (!test_bit(I915_WEDGED, &error->flags)) -+ return true; -+ -+ if (!i915->gt.scratch) /* Never full initialised, recovery impossible */ -+ return false; -+ -+ GEM_TRACE("start\n"); -+ -+ /* -+ * Before unwedging, make sure that all pending operations -+ * are flushed and errored out - we may have requests waiting upon -+ * third party fences. We marked all inflight requests as EIO, and -+ * every execbuf since returned EIO, for consistency we want all -+ * the currently pending requests to also be marked as EIO, which -+ * is done inside our nop_submit_request - and so we must wait. -+ * -+ * No more can be submitted until we reset the wedged bit. -+ */ -+ mutex_lock(&i915->gt.timelines.mutex); -+ list_for_each_entry(tl, &i915->gt.timelines.active_list, link) { -+ struct i915_request *rq; -+ -+ rq = i915_active_request_get_unlocked(&tl->last_request); -+ if (!rq) -+ continue; -+ -+ /* -+ * All internal dependencies (i915_requests) will have -+ * been flushed by the set-wedge, but we may be stuck waiting -+ * for external fences. These should all be capped to 10s -+ * (I915_FENCE_TIMEOUT) so this wait should not be unbounded -+ * in the worst case. -+ */ -+ dma_fence_default_wait(&rq->fence, false, MAX_SCHEDULE_TIMEOUT); -+ i915_request_put(rq); -+ } -+ mutex_unlock(&i915->gt.timelines.mutex); -+ -+ intel_engines_sanitize(i915, false); -+ -+ /* -+ * Undo nop_submit_request. We prevent all new i915 requests from -+ * being queued (by disallowing execbuf whilst wedged) so having -+ * waited for all active requests above, we know the system is idle -+ * and do not have to worry about a thread being inside -+ * engine->submit_request() as we swap over. So unlike installing -+ * the nop_submit_request on reset, we can do this from normal -+ * context and do not require stop_machine(). -+ */ -+ intel_engines_reset_default_submission(i915); -+ -+ GEM_TRACE("end\n"); -+ -+ smp_mb__before_atomic(); /* complete takeover before enabling execbuf */ -+ clear_bit(I915_WEDGED, &i915->gpu_error.flags); -+ -+ return true; -+} -+ -+bool i915_gem_unset_wedged(struct drm_i915_private *i915) -+{ -+ struct i915_gpu_error *error = &i915->gpu_error; -+ bool result; -+ -+ mutex_lock(&error->wedge_mutex); -+ result = __i915_gem_unset_wedged(i915); -+ mutex_unlock(&error->wedge_mutex); -+ -+ return result; -+} -+ -+static int do_reset(struct drm_i915_private *i915, -+ intel_engine_mask_t stalled_mask) -+{ -+ int err, i; -+ -+ gt_revoke(i915); -+ -+ err = intel_gpu_reset(i915, ALL_ENGINES); -+ for (i = 0; err && i < RESET_MAX_RETRIES; i++) { -+ msleep(10 * (i + 1)); -+ err = intel_gpu_reset(i915, ALL_ENGINES); -+ } -+ if (err) -+ return err; -+ -+ return gt_reset(i915, stalled_mask); -+} -+ -+/** -+ * i915_reset - reset chip after a hang -+ * @i915: #drm_i915_private to reset -+ * @stalled_mask: mask of the stalled engines with the guilty requests -+ * @reason: user error message for why we are resetting -+ * -+ * Reset the chip. Useful if a hang is detected. Marks the device as wedged -+ * on failure. -+ * -+ * Procedure is fairly simple: -+ * - reset the chip using the reset reg -+ * - re-init context state -+ * - re-init hardware status page -+ * - re-init ring buffer -+ * - re-init interrupt state -+ * - re-init display -+ */ -+void i915_reset(struct drm_i915_private *i915, -+ intel_engine_mask_t stalled_mask, -+ const char *reason) -+{ -+ struct i915_gpu_error *error = &i915->gpu_error; -+ int ret; -+ -+ GEM_TRACE("flags=%lx\n", error->flags); -+ -+ might_sleep(); -+ assert_rpm_wakelock_held(i915); -+ GEM_BUG_ON(!test_bit(I915_RESET_BACKOFF, &error->flags)); -+ -+ /* Clear any previous failed attempts at recovery. Time to try again. */ -+ if (!__i915_gem_unset_wedged(i915)) -+ return; -+ -+ if (reason) -+ dev_notice(i915->drm.dev, "Resetting chip for %s\n", reason); -+ error->reset_count++; -+ -+ reset_prepare(i915); -+ -+ if (!intel_has_gpu_reset(i915)) { -+ if (i915_modparams.reset) -+ dev_err(i915->drm.dev, "GPU reset not supported\n"); -+ else -+ DRM_DEBUG_DRIVER("GPU reset disabled\n"); -+ goto error; -+ } -+ -+ if (INTEL_INFO(i915)->gpu_reset_clobbers_display) -+ intel_runtime_pm_disable_interrupts(i915); -+ -+ if (do_reset(i915, stalled_mask)) { -+ dev_err(i915->drm.dev, "Failed to reset chip\n"); -+ goto taint; -+ } -+ -+ if (INTEL_INFO(i915)->gpu_reset_clobbers_display) -+ intel_runtime_pm_enable_interrupts(i915); -+ -+ intel_overlay_reset(i915); -+ -+ /* -+ * Next we need to restore the context, but we don't use those -+ * yet either... -+ * -+ * Ring buffer needs to be re-initialized in the KMS case, or if X -+ * was running at the time of the reset (i.e. we weren't VT -+ * switched away). -+ */ -+ ret = i915_gem_init_hw(i915); -+ if (ret) { -+ DRM_ERROR("Failed to initialise HW following reset (%d)\n", -+ ret); -+ goto error; -+ } -+ -+ i915_queue_hangcheck(i915); -+ -+finish: -+ reset_finish(i915); -+ if (!__i915_wedged(error)) -+ reset_restart(i915); -+ return; -+ -+taint: -+ /* -+ * History tells us that if we cannot reset the GPU now, we -+ * never will. This then impacts everything that is run -+ * subsequently. On failing the reset, we mark the driver -+ * as wedged, preventing further execution on the GPU. -+ * We also want to go one step further and add a taint to the -+ * kernel so that any subsequent faults can be traced back to -+ * this failure. This is important for CI, where if the -+ * GPU/driver fails we would like to reboot and restart testing -+ * rather than continue on into oblivion. For everyone else, -+ * the system should still plod along, but they have been warned! -+ */ -+ add_taint(TAINT_WARN, LOCKDEP_STILL_OK); -+error: -+ __i915_gem_set_wedged(i915); -+ goto finish; -+} -+ -+static inline int intel_gt_reset_engine(struct drm_i915_private *i915, -+ struct intel_engine_cs *engine) -+{ -+ return intel_gpu_reset(i915, engine->mask); -+} -+ -+/** -+ * i915_reset_engine - reset GPU engine to recover from a hang -+ * @engine: engine to reset -+ * @msg: reason for GPU reset; or NULL for no dev_notice() -+ * -+ * Reset a specific GPU engine. Useful if a hang is detected. -+ * Returns zero on successful reset or otherwise an error code. -+ * -+ * Procedure is: -+ * - identifies the request that caused the hang and it is dropped -+ * - reset engine (which will force the engine to idle) -+ * - re-init/configure engine -+ */ -+int i915_reset_engine(struct intel_engine_cs *engine, const char *msg) -+{ -+ struct i915_gpu_error *error = &engine->i915->gpu_error; -+ int ret; -+ -+ GEM_TRACE("%s flags=%lx\n", engine->name, error->flags); -+ GEM_BUG_ON(!test_bit(I915_RESET_ENGINE + engine->id, &error->flags)); -+ -+ reset_prepare_engine(engine); -+ -+ if (msg) -+ dev_notice(engine->i915->drm.dev, -+ "Resetting %s for %s\n", engine->name, msg); -+ error->reset_engine_count[engine->id]++; -+ -+ if (!engine->i915->guc.execbuf_client) -+ ret = intel_gt_reset_engine(engine->i915, engine); -+ else -+ ret = intel_guc_reset_engine(&engine->i915->guc, engine); -+ if (ret) { -+ /* If we fail here, we expect to fallback to a global reset */ -+ DRM_DEBUG_DRIVER("%sFailed to reset %s, ret=%d\n", -+ engine->i915->guc.execbuf_client ? "GuC " : "", -+ engine->name, ret); -+ goto out; -+ } -+ -+ /* -+ * The request that caused the hang is stuck on elsp, we know the -+ * active request and can drop it, adjust head to skip the offending -+ * request to resume executing remaining requests in the queue. -+ */ -+ intel_engine_reset(engine, true); -+ -+ /* -+ * The engine and its registers (and workarounds in case of render) -+ * have been reset to their default values. Follow the init_ring -+ * process to program RING_MODE, HWSP and re-enable submission. -+ */ -+ ret = engine->init_hw(engine); -+ if (ret) -+ goto out; -+ -+out: -+ intel_engine_cancel_stop_cs(engine); -+ reset_finish_engine(engine); -+ return ret; -+} -+ -+static void i915_reset_device(struct drm_i915_private *i915, -+ u32 engine_mask, -+ const char *reason) -+{ -+ struct i915_gpu_error *error = &i915->gpu_error; -+ struct kobject *kobj = &i915->drm.primary->kdev->kobj; -+ char *error_event[] = { I915_ERROR_UEVENT "=1", NULL }; -+ char *reset_event[] = { I915_RESET_UEVENT "=1", NULL }; -+ char *reset_done_event[] = { I915_ERROR_UEVENT "=0", NULL }; -+ struct i915_wedge_me w; -+ -+ kobject_uevent_env(kobj, KOBJ_CHANGE, error_event); -+ -+ DRM_DEBUG_DRIVER("resetting chip\n"); -+ kobject_uevent_env(kobj, KOBJ_CHANGE, reset_event); -+ -+ /* Use a watchdog to ensure that our reset completes */ -+ i915_wedge_on_timeout(&w, i915, 5 * HZ) { -+ intel_prepare_reset(i915); -+ -+ /* Flush everyone using a resource about to be clobbered */ -+ synchronize_srcu_expedited(&error->reset_backoff_srcu); -+ -+ mutex_lock(&error->wedge_mutex); -+ i915_reset(i915, engine_mask, reason); -+ mutex_unlock(&error->wedge_mutex); -+ -+ intel_finish_reset(i915); -+ } -+ -+ if (!test_bit(I915_WEDGED, &error->flags)) -+ kobject_uevent_env(kobj, KOBJ_CHANGE, reset_done_event); -+} -+ -+static void clear_register(struct intel_uncore *uncore, i915_reg_t reg) -+{ -+ intel_uncore_rmw(uncore, reg, 0, 0); -+} -+ -+void i915_clear_error_registers(struct drm_i915_private *i915) -+{ -+ struct intel_uncore *uncore = &i915->uncore; -+ u32 eir; -+ -+ if (!IS_GEN(i915, 2)) -+ clear_register(uncore, PGTBL_ER); -+ -+ if (INTEL_GEN(i915) < 4) -+ clear_register(uncore, IPEIR(RENDER_RING_BASE)); -+ else -+ clear_register(uncore, IPEIR_I965); -+ -+ clear_register(uncore, EIR); -+ eir = intel_uncore_read(uncore, EIR); -+ if (eir) { -+ /* -+ * some errors might have become stuck, -+ * mask them. -+ */ -+ DRM_DEBUG_DRIVER("EIR stuck: 0x%08x, masking\n", eir); -+ rmw_set(uncore, EMR, eir); -+ intel_uncore_write(uncore, GEN2_IIR, -+ I915_MASTER_ERROR_INTERRUPT); -+ } -+ -+ if (INTEL_GEN(i915) >= 8) { -+ rmw_clear(uncore, GEN8_RING_FAULT_REG, RING_FAULT_VALID); -+ intel_uncore_posting_read(uncore, GEN8_RING_FAULT_REG); -+ } else if (INTEL_GEN(i915) >= 6) { -+ struct intel_engine_cs *engine; -+ enum intel_engine_id id; -+ -+ for_each_engine(engine, i915, id) { -+ rmw_clear(uncore, -+ RING_FAULT_REG(engine), RING_FAULT_VALID); -+ intel_uncore_posting_read(uncore, -+ RING_FAULT_REG(engine)); -+ } -+ } -+} -+ -+/** -+ * i915_handle_error - handle a gpu error -+ * @i915: i915 device private -+ * @engine_mask: mask representing engines that are hung -+ * @flags: control flags -+ * @fmt: Error message format string -+ * -+ * Do some basic checking of register state at error time and -+ * dump it to the syslog. Also call i915_capture_error_state() to make -+ * sure we get a record and make it available in debugfs. Fire a uevent -+ * so userspace knows something bad happened (should trigger collection -+ * of a ring dump etc.). -+ */ -+void i915_handle_error(struct drm_i915_private *i915, -+ intel_engine_mask_t engine_mask, -+ unsigned long flags, -+ const char *fmt, ...) -+{ -+ struct i915_gpu_error *error = &i915->gpu_error; -+ struct intel_engine_cs *engine; -+ intel_wakeref_t wakeref; -+ intel_engine_mask_t tmp; -+ char error_msg[80]; -+ char *msg = NULL; -+ -+ if (fmt) { -+ va_list args; -+ -+ va_start(args, fmt); -+ vscnprintf(error_msg, sizeof(error_msg), fmt, args); -+ va_end(args); -+ -+ msg = error_msg; -+ } -+ -+ /* -+ * In most cases it's guaranteed that we get here with an RPM -+ * reference held, for example because there is a pending GPU -+ * request that won't finish until the reset is done. This -+ * isn't the case at least when we get here by doing a -+ * simulated reset via debugfs, so get an RPM reference. -+ */ -+ wakeref = intel_runtime_pm_get(i915); -+ -+ engine_mask &= INTEL_INFO(i915)->engine_mask; -+ -+ if (flags & I915_ERROR_CAPTURE) { -+ i915_capture_error_state(i915, engine_mask, msg); -+ i915_clear_error_registers(i915); -+ } -+ -+ /* -+ * Try engine reset when available. We fall back to full reset if -+ * single reset fails. -+ */ -+ if (intel_has_reset_engine(i915) && !__i915_wedged(error)) { -+ for_each_engine_masked(engine, i915, engine_mask, tmp) { -+ BUILD_BUG_ON(I915_RESET_MODESET >= I915_RESET_ENGINE); -+ if (test_and_set_bit(I915_RESET_ENGINE + engine->id, -+ &error->flags)) -+ continue; -+ -+ if (i915_reset_engine(engine, msg) == 0) -+ engine_mask &= ~engine->mask; -+ -+ clear_bit(I915_RESET_ENGINE + engine->id, -+ &error->flags); -+ wake_up_bit(&error->flags, -+ I915_RESET_ENGINE + engine->id); -+ } -+ } -+ -+ if (!engine_mask) -+ goto out; -+ -+ /* Full reset needs the mutex, stop any other user trying to do so. */ -+ if (test_and_set_bit(I915_RESET_BACKOFF, &error->flags)) { -+ wait_event(error->reset_queue, -+ !test_bit(I915_RESET_BACKOFF, &error->flags)); -+ goto out; /* piggy-back on the other reset */ -+ } -+ -+ /* Make sure i915_reset_trylock() sees the I915_RESET_BACKOFF */ -+ synchronize_rcu_expedited(); -+ -+ /* Prevent any other reset-engine attempt. */ -+ for_each_engine(engine, i915, tmp) { -+ while (test_and_set_bit(I915_RESET_ENGINE + engine->id, -+ &error->flags)) -+ wait_on_bit(&error->flags, -+ I915_RESET_ENGINE + engine->id, -+ TASK_UNINTERRUPTIBLE); -+ } -+ -+ i915_reset_device(i915, engine_mask, msg); -+ -+ for_each_engine(engine, i915, tmp) { -+ clear_bit(I915_RESET_ENGINE + engine->id, -+ &error->flags); -+ } -+ -+ clear_bit(I915_RESET_BACKOFF, &error->flags); -+ wake_up_all(&error->reset_queue); -+ -+out: -+ intel_runtime_pm_put(i915, wakeref); -+} -+ -+int i915_reset_trylock(struct drm_i915_private *i915) -+{ -+ struct i915_gpu_error *error = &i915->gpu_error; -+ int srcu; -+ -+ might_lock(&error->reset_backoff_srcu); -+ might_sleep(); -+ -+ rcu_read_lock(); -+ while (test_bit(I915_RESET_BACKOFF, &error->flags)) { -+ rcu_read_unlock(); -+ -+ if (wait_event_interruptible(error->reset_queue, -+ !test_bit(I915_RESET_BACKOFF, -+ &error->flags))) -+ return -EINTR; -+ -+ rcu_read_lock(); -+ } -+ srcu = srcu_read_lock(&error->reset_backoff_srcu); -+ rcu_read_unlock(); -+ -+ return srcu; -+} -+ -+void i915_reset_unlock(struct drm_i915_private *i915, int tag) -+__releases(&i915->gpu_error.reset_backoff_srcu) -+{ -+ struct i915_gpu_error *error = &i915->gpu_error; -+ -+ srcu_read_unlock(&error->reset_backoff_srcu, tag); -+} -+ -+int i915_terminally_wedged(struct drm_i915_private *i915) -+{ -+ struct i915_gpu_error *error = &i915->gpu_error; -+ -+ might_sleep(); -+ -+ if (!__i915_wedged(error)) -+ return 0; -+ -+ /* Reset still in progress? Maybe we will recover? */ -+ if (!test_bit(I915_RESET_BACKOFF, &error->flags)) -+ return -EIO; -+ -+ /* XXX intel_reset_finish() still takes struct_mutex!!! */ -+ if (mutex_is_locked(&i915->drm.struct_mutex)) -+ return -EAGAIN; -+ -+ if (wait_event_interruptible(error->reset_queue, -+ !test_bit(I915_RESET_BACKOFF, -+ &error->flags))) -+ return -EINTR; -+ -+ return __i915_wedged(error) ? -EIO : 0; -+} -+ -+bool i915_reset_flush(struct drm_i915_private *i915) -+{ -+ int err; -+ -+ cancel_delayed_work_sync(&i915->gpu_error.hangcheck_work); -+ -+ flush_workqueue(i915->wq); -+ GEM_BUG_ON(READ_ONCE(i915->gpu_error.restart)); -+ -+ mutex_lock(&i915->drm.struct_mutex); -+ err = i915_gem_wait_for_idle(i915, -+ I915_WAIT_LOCKED | -+ I915_WAIT_FOR_IDLE_BOOST, -+ MAX_SCHEDULE_TIMEOUT); -+ mutex_unlock(&i915->drm.struct_mutex); -+ -+ return !err; -+} -+ -+static void i915_wedge_me(struct work_struct *work) -+{ -+ struct i915_wedge_me *w = container_of(work, typeof(*w), work.work); -+ -+ dev_err(w->i915->drm.dev, -+ "%s timed out, cancelling all in-flight rendering.\n", -+ w->name); -+ i915_gem_set_wedged(w->i915); -+} -+ -+void __i915_init_wedge(struct i915_wedge_me *w, -+ struct drm_i915_private *i915, -+ long timeout, -+ const char *name) -+{ -+ w->i915 = i915; -+ w->name = name; -+ -+ INIT_DELAYED_WORK_ONSTACK(&w->work, i915_wedge_me); -+ schedule_delayed_work(&w->work, timeout); -+} -+ -+void __i915_fini_wedge(struct i915_wedge_me *w) -+{ -+ cancel_delayed_work_sync(&w->work); -+ destroy_delayed_work_on_stack(&w->work); -+ w->i915 = NULL; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_reset.h b/drivers/gpu/drm/i915_legacy/i915_reset.h -new file mode 100644 -index 000000000000..3c0450289b8f ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_reset.h -@@ -0,0 +1,69 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2008-2018 Intel Corporation -+ */ -+ -+#ifndef I915_RESET_H -+#define I915_RESET_H -+ -+#include -+#include -+#include -+ -+#include "intel_engine_types.h" -+ -+struct drm_i915_private; -+struct i915_request; -+struct intel_engine_cs; -+struct intel_guc; -+ -+__printf(4, 5) -+void i915_handle_error(struct drm_i915_private *i915, -+ intel_engine_mask_t engine_mask, -+ unsigned long flags, -+ const char *fmt, ...); -+#define I915_ERROR_CAPTURE BIT(0) -+ -+void i915_clear_error_registers(struct drm_i915_private *i915); -+ -+void i915_reset(struct drm_i915_private *i915, -+ intel_engine_mask_t stalled_mask, -+ const char *reason); -+int i915_reset_engine(struct intel_engine_cs *engine, -+ const char *reason); -+ -+void i915_reset_request(struct i915_request *rq, bool guilty); -+bool i915_reset_flush(struct drm_i915_private *i915); -+ -+int __must_check i915_reset_trylock(struct drm_i915_private *i915); -+void i915_reset_unlock(struct drm_i915_private *i915, int tag); -+ -+int i915_terminally_wedged(struct drm_i915_private *i915); -+ -+bool intel_has_gpu_reset(struct drm_i915_private *i915); -+bool intel_has_reset_engine(struct drm_i915_private *i915); -+ -+int intel_gpu_reset(struct drm_i915_private *i915, -+ intel_engine_mask_t engine_mask); -+ -+int intel_reset_guc(struct drm_i915_private *i915); -+ -+struct i915_wedge_me { -+ struct delayed_work work; -+ struct drm_i915_private *i915; -+ const char *name; -+}; -+ -+void __i915_init_wedge(struct i915_wedge_me *w, -+ struct drm_i915_private *i915, -+ long timeout, -+ const char *name); -+void __i915_fini_wedge(struct i915_wedge_me *w); -+ -+#define i915_wedge_on_timeout(W, DEV, TIMEOUT) \ -+ for (__i915_init_wedge((W), (DEV), (TIMEOUT), __func__); \ -+ (W)->i915; \ -+ __i915_fini_wedge((W))) -+ -+#endif /* I915_RESET_H */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_scheduler.c b/drivers/gpu/drm/i915_legacy/i915_scheduler.c -new file mode 100644 -index 000000000000..108f52e1bf35 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_scheduler.c -@@ -0,0 +1,492 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_globals.h" -+#include "i915_request.h" -+#include "i915_scheduler.h" -+ -+static struct i915_global_scheduler { -+ struct i915_global base; -+ struct kmem_cache *slab_dependencies; -+ struct kmem_cache *slab_priorities; -+} global; -+ -+static DEFINE_SPINLOCK(schedule_lock); -+ -+static const struct i915_request * -+node_to_request(const struct i915_sched_node *node) -+{ -+ return container_of(node, const struct i915_request, sched); -+} -+ -+static inline bool node_started(const struct i915_sched_node *node) -+{ -+ return i915_request_started(node_to_request(node)); -+} -+ -+static inline bool node_signaled(const struct i915_sched_node *node) -+{ -+ return i915_request_completed(node_to_request(node)); -+} -+ -+static inline struct i915_priolist *to_priolist(struct rb_node *rb) -+{ -+ return rb_entry(rb, struct i915_priolist, node); -+} -+ -+static void assert_priolists(struct intel_engine_execlists * const execlists) -+{ -+ struct rb_node *rb; -+ long last_prio, i; -+ -+ if (!IS_ENABLED(CONFIG_DRM_I915_DEBUG_GEM)) -+ return; -+ -+ GEM_BUG_ON(rb_first_cached(&execlists->queue) != -+ rb_first(&execlists->queue.rb_root)); -+ -+ last_prio = (INT_MAX >> I915_USER_PRIORITY_SHIFT) + 1; -+ for (rb = rb_first_cached(&execlists->queue); rb; rb = rb_next(rb)) { -+ const struct i915_priolist *p = to_priolist(rb); -+ -+ GEM_BUG_ON(p->priority >= last_prio); -+ last_prio = p->priority; -+ -+ GEM_BUG_ON(!p->used); -+ for (i = 0; i < ARRAY_SIZE(p->requests); i++) { -+ if (list_empty(&p->requests[i])) -+ continue; -+ -+ GEM_BUG_ON(!(p->used & BIT(i))); -+ } -+ } -+} -+ -+struct list_head * -+i915_sched_lookup_priolist(struct intel_engine_cs *engine, int prio) -+{ -+ struct intel_engine_execlists * const execlists = &engine->execlists; -+ struct i915_priolist *p; -+ struct rb_node **parent, *rb; -+ bool first = true; -+ int idx, i; -+ -+ lockdep_assert_held(&engine->timeline.lock); -+ assert_priolists(execlists); -+ -+ /* buckets sorted from highest [in slot 0] to lowest priority */ -+ idx = I915_PRIORITY_COUNT - (prio & I915_PRIORITY_MASK) - 1; -+ prio >>= I915_USER_PRIORITY_SHIFT; -+ if (unlikely(execlists->no_priolist)) -+ prio = I915_PRIORITY_NORMAL; -+ -+find_priolist: -+ /* most positive priority is scheduled first, equal priorities fifo */ -+ rb = NULL; -+ parent = &execlists->queue.rb_root.rb_node; -+ while (*parent) { -+ rb = *parent; -+ p = to_priolist(rb); -+ if (prio > p->priority) { -+ parent = &rb->rb_left; -+ } else if (prio < p->priority) { -+ parent = &rb->rb_right; -+ first = false; -+ } else { -+ goto out; -+ } -+ } -+ -+ if (prio == I915_PRIORITY_NORMAL) { -+ p = &execlists->default_priolist; -+ } else { -+ p = kmem_cache_alloc(global.slab_priorities, GFP_ATOMIC); -+ /* Convert an allocation failure to a priority bump */ -+ if (unlikely(!p)) { -+ prio = I915_PRIORITY_NORMAL; /* recurses just once */ -+ -+ /* To maintain ordering with all rendering, after an -+ * allocation failure we have to disable all scheduling. -+ * Requests will then be executed in fifo, and schedule -+ * will ensure that dependencies are emitted in fifo. -+ * There will be still some reordering with existing -+ * requests, so if userspace lied about their -+ * dependencies that reordering may be visible. -+ */ -+ execlists->no_priolist = true; -+ goto find_priolist; -+ } -+ } -+ -+ p->priority = prio; -+ for (i = 0; i < ARRAY_SIZE(p->requests); i++) -+ INIT_LIST_HEAD(&p->requests[i]); -+ rb_link_node(&p->node, rb, parent); -+ rb_insert_color_cached(&p->node, &execlists->queue, first); -+ p->used = 0; -+ -+out: -+ p->used |= BIT(idx); -+ return &p->requests[idx]; -+} -+ -+void __i915_priolist_free(struct i915_priolist *p) -+{ -+ kmem_cache_free(global.slab_priorities, p); -+} -+ -+struct sched_cache { -+ struct list_head *priolist; -+}; -+ -+static struct intel_engine_cs * -+sched_lock_engine(const struct i915_sched_node *node, -+ struct intel_engine_cs *locked, -+ struct sched_cache *cache) -+{ -+ struct intel_engine_cs *engine = node_to_request(node)->engine; -+ -+ GEM_BUG_ON(!locked); -+ -+ if (engine != locked) { -+ spin_unlock(&locked->timeline.lock); -+ memset(cache, 0, sizeof(*cache)); -+ spin_lock(&engine->timeline.lock); -+ } -+ -+ return engine; -+} -+ -+static bool inflight(const struct i915_request *rq, -+ const struct intel_engine_cs *engine) -+{ -+ const struct i915_request *active; -+ -+ if (!i915_request_is_active(rq)) -+ return false; -+ -+ active = port_request(engine->execlists.port); -+ return active->hw_context == rq->hw_context; -+} -+ -+static void __i915_schedule(struct i915_sched_node *node, -+ const struct i915_sched_attr *attr) -+{ -+ struct intel_engine_cs *engine; -+ struct i915_dependency *dep, *p; -+ struct i915_dependency stack; -+ const int prio = attr->priority; -+ struct sched_cache cache; -+ LIST_HEAD(dfs); -+ -+ /* Needed in order to use the temporary link inside i915_dependency */ -+ lockdep_assert_held(&schedule_lock); -+ GEM_BUG_ON(prio == I915_PRIORITY_INVALID); -+ -+ if (node_signaled(node)) -+ return; -+ -+ if (prio <= READ_ONCE(node->attr.priority)) -+ return; -+ -+ stack.signaler = node; -+ list_add(&stack.dfs_link, &dfs); -+ -+ /* -+ * Recursively bump all dependent priorities to match the new request. -+ * -+ * A naive approach would be to use recursion: -+ * static void update_priorities(struct i915_sched_node *node, prio) { -+ * list_for_each_entry(dep, &node->signalers_list, signal_link) -+ * update_priorities(dep->signal, prio) -+ * queue_request(node); -+ * } -+ * but that may have unlimited recursion depth and so runs a very -+ * real risk of overunning the kernel stack. Instead, we build -+ * a flat list of all dependencies starting with the current request. -+ * As we walk the list of dependencies, we add all of its dependencies -+ * to the end of the list (this may include an already visited -+ * request) and continue to walk onwards onto the new dependencies. The -+ * end result is a topological list of requests in reverse order, the -+ * last element in the list is the request we must execute first. -+ */ -+ list_for_each_entry(dep, &dfs, dfs_link) { -+ struct i915_sched_node *node = dep->signaler; -+ -+ /* If we are already flying, we know we have no signalers */ -+ if (node_started(node)) -+ continue; -+ -+ /* -+ * Within an engine, there can be no cycle, but we may -+ * refer to the same dependency chain multiple times -+ * (redundant dependencies are not eliminated) and across -+ * engines. -+ */ -+ list_for_each_entry(p, &node->signalers_list, signal_link) { -+ GEM_BUG_ON(p == dep); /* no cycles! */ -+ -+ if (node_signaled(p->signaler)) -+ continue; -+ -+ if (prio > READ_ONCE(p->signaler->attr.priority)) -+ list_move_tail(&p->dfs_link, &dfs); -+ } -+ } -+ -+ /* -+ * If we didn't need to bump any existing priorities, and we haven't -+ * yet submitted this request (i.e. there is no potential race with -+ * execlists_submit_request()), we can set our own priority and skip -+ * acquiring the engine locks. -+ */ -+ if (node->attr.priority == I915_PRIORITY_INVALID) { -+ GEM_BUG_ON(!list_empty(&node->link)); -+ node->attr = *attr; -+ -+ if (stack.dfs_link.next == stack.dfs_link.prev) -+ return; -+ -+ __list_del_entry(&stack.dfs_link); -+ } -+ -+ memset(&cache, 0, sizeof(cache)); -+ engine = node_to_request(node)->engine; -+ spin_lock(&engine->timeline.lock); -+ -+ /* Fifo and depth-first replacement ensure our deps execute before us */ -+ list_for_each_entry_safe_reverse(dep, p, &dfs, dfs_link) { -+ INIT_LIST_HEAD(&dep->dfs_link); -+ -+ node = dep->signaler; -+ engine = sched_lock_engine(node, engine, &cache); -+ lockdep_assert_held(&engine->timeline.lock); -+ -+ /* Recheck after acquiring the engine->timeline.lock */ -+ if (prio <= node->attr.priority || node_signaled(node)) -+ continue; -+ -+ node->attr.priority = prio; -+ if (!list_empty(&node->link)) { -+ if (!cache.priolist) -+ cache.priolist = -+ i915_sched_lookup_priolist(engine, -+ prio); -+ list_move_tail(&node->link, cache.priolist); -+ } else { -+ /* -+ * If the request is not in the priolist queue because -+ * it is not yet runnable, then it doesn't contribute -+ * to our preemption decisions. On the other hand, -+ * if the request is on the HW, it too is not in the -+ * queue; but in that case we may still need to reorder -+ * the inflight requests. -+ */ -+ if (!i915_sw_fence_done(&node_to_request(node)->submit)) -+ continue; -+ } -+ -+ if (prio <= engine->execlists.queue_priority_hint) -+ continue; -+ -+ engine->execlists.queue_priority_hint = prio; -+ -+ /* -+ * If we are already the currently executing context, don't -+ * bother evaluating if we should preempt ourselves. -+ */ -+ if (inflight(node_to_request(node), engine)) -+ continue; -+ -+ /* Defer (tasklet) submission until after all of our updates. */ -+ tasklet_hi_schedule(&engine->execlists.tasklet); -+ } -+ -+ spin_unlock(&engine->timeline.lock); -+} -+ -+void i915_schedule(struct i915_request *rq, const struct i915_sched_attr *attr) -+{ -+ spin_lock_irq(&schedule_lock); -+ __i915_schedule(&rq->sched, attr); -+ spin_unlock_irq(&schedule_lock); -+} -+ -+static void __bump_priority(struct i915_sched_node *node, unsigned int bump) -+{ -+ struct i915_sched_attr attr = node->attr; -+ -+ attr.priority |= bump; -+ __i915_schedule(node, &attr); -+} -+ -+void i915_schedule_bump_priority(struct i915_request *rq, unsigned int bump) -+{ -+ unsigned long flags; -+ -+ GEM_BUG_ON(bump & ~I915_PRIORITY_MASK); -+ -+ if (READ_ONCE(rq->sched.attr.priority) == I915_PRIORITY_INVALID) -+ return; -+ -+ spin_lock_irqsave(&schedule_lock, flags); -+ __bump_priority(&rq->sched, bump); -+ spin_unlock_irqrestore(&schedule_lock, flags); -+} -+ -+void i915_sched_node_init(struct i915_sched_node *node) -+{ -+ INIT_LIST_HEAD(&node->signalers_list); -+ INIT_LIST_HEAD(&node->waiters_list); -+ INIT_LIST_HEAD(&node->link); -+ node->attr.priority = I915_PRIORITY_INVALID; -+ node->semaphores = 0; -+ node->flags = 0; -+} -+ -+static struct i915_dependency * -+i915_dependency_alloc(void) -+{ -+ return kmem_cache_alloc(global.slab_dependencies, GFP_KERNEL); -+} -+ -+static void -+i915_dependency_free(struct i915_dependency *dep) -+{ -+ kmem_cache_free(global.slab_dependencies, dep); -+} -+ -+bool __i915_sched_node_add_dependency(struct i915_sched_node *node, -+ struct i915_sched_node *signal, -+ struct i915_dependency *dep, -+ unsigned long flags) -+{ -+ bool ret = false; -+ -+ spin_lock_irq(&schedule_lock); -+ -+ if (!node_signaled(signal)) { -+ INIT_LIST_HEAD(&dep->dfs_link); -+ list_add(&dep->wait_link, &signal->waiters_list); -+ list_add(&dep->signal_link, &node->signalers_list); -+ dep->signaler = signal; -+ dep->flags = flags; -+ -+ /* Keep track of whether anyone on this chain has a semaphore */ -+ if (signal->flags & I915_SCHED_HAS_SEMAPHORE_CHAIN && -+ !node_started(signal)) -+ node->flags |= I915_SCHED_HAS_SEMAPHORE_CHAIN; -+ -+ /* -+ * As we do not allow WAIT to preempt inflight requests, -+ * once we have executed a request, along with triggering -+ * any execution callbacks, we must preserve its ordering -+ * within the non-preemptible FIFO. -+ */ -+ BUILD_BUG_ON(__NO_PREEMPTION & ~I915_PRIORITY_MASK); -+ if (flags & I915_DEPENDENCY_EXTERNAL) -+ __bump_priority(signal, __NO_PREEMPTION); -+ -+ ret = true; -+ } -+ -+ spin_unlock_irq(&schedule_lock); -+ -+ return ret; -+} -+ -+int i915_sched_node_add_dependency(struct i915_sched_node *node, -+ struct i915_sched_node *signal) -+{ -+ struct i915_dependency *dep; -+ -+ dep = i915_dependency_alloc(); -+ if (!dep) -+ return -ENOMEM; -+ -+ if (!__i915_sched_node_add_dependency(node, signal, dep, -+ I915_DEPENDENCY_EXTERNAL | -+ I915_DEPENDENCY_ALLOC)) -+ i915_dependency_free(dep); -+ -+ return 0; -+} -+ -+void i915_sched_node_fini(struct i915_sched_node *node) -+{ -+ struct i915_dependency *dep, *tmp; -+ -+ GEM_BUG_ON(!list_empty(&node->link)); -+ -+ spin_lock_irq(&schedule_lock); -+ -+ /* -+ * Everyone we depended upon (the fences we wait to be signaled) -+ * should retire before us and remove themselves from our list. -+ * However, retirement is run independently on each timeline and -+ * so we may be called out-of-order. -+ */ -+ list_for_each_entry_safe(dep, tmp, &node->signalers_list, signal_link) { -+ GEM_BUG_ON(!node_signaled(dep->signaler)); -+ GEM_BUG_ON(!list_empty(&dep->dfs_link)); -+ -+ list_del(&dep->wait_link); -+ if (dep->flags & I915_DEPENDENCY_ALLOC) -+ i915_dependency_free(dep); -+ } -+ -+ /* Remove ourselves from everyone who depends upon us */ -+ list_for_each_entry_safe(dep, tmp, &node->waiters_list, wait_link) { -+ GEM_BUG_ON(dep->signaler != node); -+ GEM_BUG_ON(!list_empty(&dep->dfs_link)); -+ -+ list_del(&dep->signal_link); -+ if (dep->flags & I915_DEPENDENCY_ALLOC) -+ i915_dependency_free(dep); -+ } -+ -+ spin_unlock_irq(&schedule_lock); -+} -+ -+static void i915_global_scheduler_shrink(void) -+{ -+ kmem_cache_shrink(global.slab_dependencies); -+ kmem_cache_shrink(global.slab_priorities); -+} -+ -+static void i915_global_scheduler_exit(void) -+{ -+ kmem_cache_destroy(global.slab_dependencies); -+ kmem_cache_destroy(global.slab_priorities); -+} -+ -+static struct i915_global_scheduler global = { { -+ .shrink = i915_global_scheduler_shrink, -+ .exit = i915_global_scheduler_exit, -+} }; -+ -+int __init i915_global_scheduler_init(void) -+{ -+ global.slab_dependencies = KMEM_CACHE(i915_dependency, -+ SLAB_HWCACHE_ALIGN); -+ if (!global.slab_dependencies) -+ return -ENOMEM; -+ -+ global.slab_priorities = KMEM_CACHE(i915_priolist, -+ SLAB_HWCACHE_ALIGN); -+ if (!global.slab_priorities) -+ goto err_priorities; -+ -+ i915_global_register(&global.base); -+ return 0; -+ -+err_priorities: -+ kmem_cache_destroy(global.slab_priorities); -+ return -ENOMEM; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_scheduler.h b/drivers/gpu/drm/i915_legacy/i915_scheduler.h -new file mode 100644 -index 000000000000..07d243acf553 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_scheduler.h -@@ -0,0 +1,55 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ */ -+ -+#ifndef _I915_SCHEDULER_H_ -+#define _I915_SCHEDULER_H_ -+ -+#include -+#include -+#include -+ -+#include "i915_scheduler_types.h" -+ -+#define priolist_for_each_request(it, plist, idx) \ -+ for (idx = 0; idx < ARRAY_SIZE((plist)->requests); idx++) \ -+ list_for_each_entry(it, &(plist)->requests[idx], sched.link) -+ -+#define priolist_for_each_request_consume(it, n, plist, idx) \ -+ for (; \ -+ (plist)->used ? (idx = __ffs((plist)->used)), 1 : 0; \ -+ (plist)->used &= ~BIT(idx)) \ -+ list_for_each_entry_safe(it, n, \ -+ &(plist)->requests[idx], \ -+ sched.link) -+ -+void i915_sched_node_init(struct i915_sched_node *node); -+ -+bool __i915_sched_node_add_dependency(struct i915_sched_node *node, -+ struct i915_sched_node *signal, -+ struct i915_dependency *dep, -+ unsigned long flags); -+ -+int i915_sched_node_add_dependency(struct i915_sched_node *node, -+ struct i915_sched_node *signal); -+ -+void i915_sched_node_fini(struct i915_sched_node *node); -+ -+void i915_schedule(struct i915_request *request, -+ const struct i915_sched_attr *attr); -+ -+void i915_schedule_bump_priority(struct i915_request *rq, unsigned int bump); -+ -+struct list_head * -+i915_sched_lookup_priolist(struct intel_engine_cs *engine, int prio); -+ -+void __i915_priolist_free(struct i915_priolist *p); -+static inline void i915_priolist_free(struct i915_priolist *p) -+{ -+ if (p->priority != I915_PRIORITY_NORMAL) -+ __i915_priolist_free(p); -+} -+ -+#endif /* _I915_SCHEDULER_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_scheduler_types.h b/drivers/gpu/drm/i915_legacy/i915_scheduler_types.h -new file mode 100644 -index 000000000000..4f2b2eb7c3e5 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_scheduler_types.h -@@ -0,0 +1,73 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ */ -+ -+#ifndef _I915_SCHEDULER_TYPES_H_ -+#define _I915_SCHEDULER_TYPES_H_ -+ -+#include -+ -+#include "i915_priolist_types.h" -+#include "intel_engine_types.h" -+ -+struct drm_i915_private; -+struct i915_request; -+struct intel_engine_cs; -+ -+struct i915_sched_attr { -+ /** -+ * @priority: execution and service priority -+ * -+ * All clients are equal, but some are more equal than others! -+ * -+ * Requests from a context with a greater (more positive) value of -+ * @priority will be executed before those with a lower @priority -+ * value, forming a simple QoS. -+ * -+ * The &drm_i915_private.kernel_context is assigned the lowest priority. -+ */ -+ int priority; -+}; -+ -+/* -+ * "People assume that time is a strict progression of cause to effect, but -+ * actually, from a nonlinear, non-subjective viewpoint, it's more like a big -+ * ball of wibbly-wobbly, timey-wimey ... stuff." -The Doctor, 2015 -+ * -+ * Requests exist in a complex web of interdependencies. Each request -+ * has to wait for some other request to complete before it is ready to be run -+ * (e.g. we have to wait until the pixels have been rendering into a texture -+ * before we can copy from it). We track the readiness of a request in terms -+ * of fences, but we also need to keep the dependency tree for the lifetime -+ * of the request (beyond the life of an individual fence). We use the tree -+ * at various points to reorder the requests whilst keeping the requests -+ * in order with respect to their various dependencies. -+ * -+ * There is no active component to the "scheduler". As we know the dependency -+ * DAG of each request, we are able to insert it into a sorted queue when it -+ * is ready, and are able to reorder its portion of the graph to accommodate -+ * dynamic priority changes. -+ */ -+struct i915_sched_node { -+ struct list_head signalers_list; /* those before us, we depend upon */ -+ struct list_head waiters_list; /* those after us, they depend upon us */ -+ struct list_head link; -+ struct i915_sched_attr attr; -+ unsigned int flags; -+#define I915_SCHED_HAS_SEMAPHORE_CHAIN BIT(0) -+ intel_engine_mask_t semaphores; -+}; -+ -+struct i915_dependency { -+ struct i915_sched_node *signaler; -+ struct list_head signal_link; -+ struct list_head wait_link; -+ struct list_head dfs_link; -+ unsigned long flags; -+#define I915_DEPENDENCY_ALLOC BIT(0) -+#define I915_DEPENDENCY_EXTERNAL BIT(1) -+}; -+ -+#endif /* _I915_SCHEDULER_TYPES_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_selftest.h b/drivers/gpu/drm/i915_legacy/i915_selftest.h -new file mode 100644 -index 000000000000..207e21b478f2 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_selftest.h -@@ -0,0 +1,105 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#ifndef __I915_SELFTEST_H__ -+#define __I915_SELFTEST_H__ -+ -+struct pci_dev; -+struct drm_i915_private; -+ -+struct i915_selftest { -+ unsigned long timeout_jiffies; -+ unsigned int timeout_ms; -+ unsigned int random_seed; -+ char *filter; -+ int mock; -+ int live; -+}; -+ -+#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -+#include -+ -+extern struct i915_selftest i915_selftest; -+ -+int i915_mock_selftests(void); -+int i915_live_selftests(struct pci_dev *pdev); -+ -+/* We extract the function declarations from i915_mock_selftests.h and -+ * i915_live_selftests.h Add your unit test declarations there! -+ * -+ * Mock unit tests are run very early upon module load, before the driver -+ * is probed. All hardware interactions, as well as other subsystems, must -+ * be "mocked". -+ * -+ * Live unit tests are run after the driver is loaded - all hardware -+ * interactions are real. -+ */ -+#define selftest(name, func) int func(void); -+#include "selftests/i915_mock_selftests.h" -+#undef selftest -+#define selftest(name, func) int func(struct drm_i915_private *i915); -+#include "selftests/i915_live_selftests.h" -+#undef selftest -+ -+struct i915_subtest { -+ int (*func)(void *data); -+ const char *name; -+}; -+ -+int __i915_subtests(const char *caller, -+ const struct i915_subtest *st, -+ unsigned int count, -+ void *data); -+#define i915_subtests(T, data) \ -+ __i915_subtests(__func__, T, ARRAY_SIZE(T), data) -+ -+#define SUBTEST(x) { x, #x } -+ -+#define I915_SELFTEST_DECLARE(x) x -+#define I915_SELFTEST_ONLY(x) unlikely(x) -+ -+#else /* !IS_ENABLED(CONFIG_DRM_I915_SELFTEST) */ -+ -+static inline int i915_mock_selftests(void) { return 0; } -+static inline int i915_live_selftests(struct pci_dev *pdev) { return 0; } -+ -+#define I915_SELFTEST_DECLARE(x) -+#define I915_SELFTEST_ONLY(x) 0 -+ -+#endif -+ -+/* Using the i915_selftest_ prefix becomes a little unwieldy with the helpers. -+ * Instead we use the igt_ shorthand, in reference to the intel-gpu-tools -+ * suite of uabi test cases (which includes a test runner for our selftests). -+ */ -+ -+#define IGT_TIMEOUT(name__) \ -+ unsigned long name__ = jiffies + i915_selftest.timeout_jiffies -+ -+__printf(2, 3) -+bool __igt_timeout(unsigned long timeout, const char *fmt, ...); -+ -+#define igt_timeout(t, fmt, ...) \ -+ __igt_timeout((t), KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) -+ -+#endif /* !__I915_SELFTEST_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_suspend.c b/drivers/gpu/drm/i915_legacy/i915_suspend.c -new file mode 100644 -index 000000000000..95f3dab1b229 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_suspend.c -@@ -0,0 +1,150 @@ -+/* -+ * -+ * Copyright 2008 (c) Intel Corporation -+ * Jesse Barnes -+ * -+ * 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, sub license, 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 (including the -+ * next paragraph) 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 NON-INFRINGEMENT. -+ * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS 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. -+ */ -+ -+#include -+ -+#include "i915_reg.h" -+#include "intel_drv.h" -+#include "intel_fbc.h" -+ -+static void i915_save_display(struct drm_i915_private *dev_priv) -+{ -+ /* Display arbitration control */ -+ if (INTEL_GEN(dev_priv) <= 4) -+ dev_priv->regfile.saveDSPARB = I915_READ(DSPARB); -+ -+ /* save FBC interval */ -+ if (HAS_FBC(dev_priv) && INTEL_GEN(dev_priv) <= 4 && !IS_G4X(dev_priv)) -+ dev_priv->regfile.saveFBC_CONTROL = I915_READ(FBC_CONTROL); -+} -+ -+static void i915_restore_display(struct drm_i915_private *dev_priv) -+{ -+ /* Display arbitration */ -+ if (INTEL_GEN(dev_priv) <= 4) -+ I915_WRITE(DSPARB, dev_priv->regfile.saveDSPARB); -+ -+ /* only restore FBC info on the platform that supports FBC*/ -+ intel_fbc_global_disable(dev_priv); -+ -+ /* restore FBC interval */ -+ if (HAS_FBC(dev_priv) && INTEL_GEN(dev_priv) <= 4 && !IS_G4X(dev_priv)) -+ I915_WRITE(FBC_CONTROL, dev_priv->regfile.saveFBC_CONTROL); -+ -+ i915_redisable_vga(dev_priv); -+} -+ -+int i915_save_state(struct drm_i915_private *dev_priv) -+{ -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ int i; -+ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ -+ i915_save_display(dev_priv); -+ -+ if (IS_GEN(dev_priv, 4)) -+ pci_read_config_word(pdev, GCDGMBUS, -+ &dev_priv->regfile.saveGCDGMBUS); -+ -+ /* Cache mode state */ -+ if (INTEL_GEN(dev_priv) < 7) -+ dev_priv->regfile.saveCACHE_MODE_0 = I915_READ(CACHE_MODE_0); -+ -+ /* Memory Arbitration state */ -+ dev_priv->regfile.saveMI_ARB_STATE = I915_READ(MI_ARB_STATE); -+ -+ /* Scratch space */ -+ if (IS_GEN(dev_priv, 2) && IS_MOBILE(dev_priv)) { -+ for (i = 0; i < 7; i++) { -+ dev_priv->regfile.saveSWF0[i] = I915_READ(SWF0(i)); -+ dev_priv->regfile.saveSWF1[i] = I915_READ(SWF1(i)); -+ } -+ for (i = 0; i < 3; i++) -+ dev_priv->regfile.saveSWF3[i] = I915_READ(SWF3(i)); -+ } else if (IS_GEN(dev_priv, 2)) { -+ for (i = 0; i < 7; i++) -+ dev_priv->regfile.saveSWF1[i] = I915_READ(SWF1(i)); -+ } else if (HAS_GMCH(dev_priv)) { -+ for (i = 0; i < 16; i++) { -+ dev_priv->regfile.saveSWF0[i] = I915_READ(SWF0(i)); -+ dev_priv->regfile.saveSWF1[i] = I915_READ(SWF1(i)); -+ } -+ for (i = 0; i < 3; i++) -+ dev_priv->regfile.saveSWF3[i] = I915_READ(SWF3(i)); -+ } -+ -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ -+ return 0; -+} -+ -+int i915_restore_state(struct drm_i915_private *dev_priv) -+{ -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ int i; -+ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ -+ if (IS_GEN(dev_priv, 4)) -+ pci_write_config_word(pdev, GCDGMBUS, -+ dev_priv->regfile.saveGCDGMBUS); -+ i915_restore_display(dev_priv); -+ -+ /* Cache mode state */ -+ if (INTEL_GEN(dev_priv) < 7) -+ I915_WRITE(CACHE_MODE_0, dev_priv->regfile.saveCACHE_MODE_0 | -+ 0xffff0000); -+ -+ /* Memory arbitration state */ -+ I915_WRITE(MI_ARB_STATE, dev_priv->regfile.saveMI_ARB_STATE | 0xffff0000); -+ -+ /* Scratch space */ -+ if (IS_GEN(dev_priv, 2) && IS_MOBILE(dev_priv)) { -+ for (i = 0; i < 7; i++) { -+ I915_WRITE(SWF0(i), dev_priv->regfile.saveSWF0[i]); -+ I915_WRITE(SWF1(i), dev_priv->regfile.saveSWF1[i]); -+ } -+ for (i = 0; i < 3; i++) -+ I915_WRITE(SWF3(i), dev_priv->regfile.saveSWF3[i]); -+ } else if (IS_GEN(dev_priv, 2)) { -+ for (i = 0; i < 7; i++) -+ I915_WRITE(SWF1(i), dev_priv->regfile.saveSWF1[i]); -+ } else if (HAS_GMCH(dev_priv)) { -+ for (i = 0; i < 16; i++) { -+ I915_WRITE(SWF0(i), dev_priv->regfile.saveSWF0[i]); -+ I915_WRITE(SWF1(i), dev_priv->regfile.saveSWF1[i]); -+ } -+ for (i = 0; i < 3; i++) -+ I915_WRITE(SWF3(i), dev_priv->regfile.saveSWF3[i]); -+ } -+ -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ -+ intel_i2c_reset(dev_priv); -+ -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_sw_fence.c b/drivers/gpu/drm/i915_legacy/i915_sw_fence.c -new file mode 100644 -index 000000000000..5387aafd3424 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_sw_fence.c -@@ -0,0 +1,576 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * (C) Copyright 2016 Intel Corporation -+ */ -+ -+#include -+#include -+#include -+#include -+ -+#include "i915_sw_fence.h" -+#include "i915_selftest.h" -+ -+#define I915_SW_FENCE_FLAG_ALLOC BIT(3) /* after WQ_FLAG_* for safety */ -+ -+static DEFINE_SPINLOCK(i915_sw_fence_lock); -+ -+enum { -+ DEBUG_FENCE_IDLE = 0, -+ DEBUG_FENCE_NOTIFY, -+}; -+ -+static void *i915_sw_fence_debug_hint(void *addr) -+{ -+ return (void *)(((struct i915_sw_fence *)addr)->flags & I915_SW_FENCE_MASK); -+} -+ -+#ifdef CONFIG_DRM_I915_SW_FENCE_DEBUG_OBJECTS -+ -+static struct debug_obj_descr i915_sw_fence_debug_descr = { -+ .name = "i915_sw_fence", -+ .debug_hint = i915_sw_fence_debug_hint, -+}; -+ -+static inline void debug_fence_init(struct i915_sw_fence *fence) -+{ -+ debug_object_init(fence, &i915_sw_fence_debug_descr); -+} -+ -+static inline void debug_fence_init_onstack(struct i915_sw_fence *fence) -+{ -+ debug_object_init_on_stack(fence, &i915_sw_fence_debug_descr); -+} -+ -+static inline void debug_fence_activate(struct i915_sw_fence *fence) -+{ -+ debug_object_activate(fence, &i915_sw_fence_debug_descr); -+} -+ -+static inline void debug_fence_set_state(struct i915_sw_fence *fence, -+ int old, int new) -+{ -+ debug_object_active_state(fence, &i915_sw_fence_debug_descr, old, new); -+} -+ -+static inline void debug_fence_deactivate(struct i915_sw_fence *fence) -+{ -+ debug_object_deactivate(fence, &i915_sw_fence_debug_descr); -+} -+ -+static inline void debug_fence_destroy(struct i915_sw_fence *fence) -+{ -+ debug_object_destroy(fence, &i915_sw_fence_debug_descr); -+} -+ -+static inline void debug_fence_free(struct i915_sw_fence *fence) -+{ -+ debug_object_free(fence, &i915_sw_fence_debug_descr); -+ smp_wmb(); /* flush the change in state before reallocation */ -+} -+ -+static inline void debug_fence_assert(struct i915_sw_fence *fence) -+{ -+ debug_object_assert_init(fence, &i915_sw_fence_debug_descr); -+} -+ -+#else -+ -+static inline void debug_fence_init(struct i915_sw_fence *fence) -+{ -+} -+ -+static inline void debug_fence_init_onstack(struct i915_sw_fence *fence) -+{ -+} -+ -+static inline void debug_fence_activate(struct i915_sw_fence *fence) -+{ -+} -+ -+static inline void debug_fence_set_state(struct i915_sw_fence *fence, -+ int old, int new) -+{ -+} -+ -+static inline void debug_fence_deactivate(struct i915_sw_fence *fence) -+{ -+} -+ -+static inline void debug_fence_destroy(struct i915_sw_fence *fence) -+{ -+} -+ -+static inline void debug_fence_free(struct i915_sw_fence *fence) -+{ -+} -+ -+static inline void debug_fence_assert(struct i915_sw_fence *fence) -+{ -+} -+ -+#endif -+ -+static int __i915_sw_fence_notify(struct i915_sw_fence *fence, -+ enum i915_sw_fence_notify state) -+{ -+ i915_sw_fence_notify_t fn; -+ -+ fn = (i915_sw_fence_notify_t)(fence->flags & I915_SW_FENCE_MASK); -+ return fn(fence, state); -+} -+ -+#ifdef CONFIG_DRM_I915_SW_FENCE_DEBUG_OBJECTS -+void i915_sw_fence_fini(struct i915_sw_fence *fence) -+{ -+ debug_fence_free(fence); -+} -+#endif -+ -+static void __i915_sw_fence_wake_up_all(struct i915_sw_fence *fence, -+ struct list_head *continuation) -+{ -+ wait_queue_head_t *x = &fence->wait; -+ wait_queue_entry_t *pos, *next; -+ unsigned long flags; -+ -+ debug_fence_deactivate(fence); -+ atomic_set_release(&fence->pending, -1); /* 0 -> -1 [done] */ -+ -+ /* -+ * To prevent unbounded recursion as we traverse the graph of -+ * i915_sw_fences, we move the entry list from this, the next ready -+ * fence, to the tail of the original fence's entry list -+ * (and so added to the list to be woken). -+ */ -+ -+ spin_lock_irqsave_nested(&x->lock, flags, 1 + !!continuation); -+ if (continuation) { -+ list_for_each_entry_safe(pos, next, &x->head, entry) { -+ if (pos->func == autoremove_wake_function) -+ pos->func(pos, TASK_NORMAL, 0, continuation); -+ else -+ list_move_tail(&pos->entry, continuation); -+ } -+ } else { -+ LIST_HEAD(extra); -+ -+ do { -+ list_for_each_entry_safe(pos, next, &x->head, entry) -+ pos->func(pos, TASK_NORMAL, 0, &extra); -+ -+ if (list_empty(&extra)) -+ break; -+ -+ list_splice_tail_init(&extra, &x->head); -+ } while (1); -+ } -+ spin_unlock_irqrestore(&x->lock, flags); -+ -+ debug_fence_assert(fence); -+} -+ -+static void __i915_sw_fence_complete(struct i915_sw_fence *fence, -+ struct list_head *continuation) -+{ -+ debug_fence_assert(fence); -+ -+ if (!atomic_dec_and_test(&fence->pending)) -+ return; -+ -+ debug_fence_set_state(fence, DEBUG_FENCE_IDLE, DEBUG_FENCE_NOTIFY); -+ -+ if (__i915_sw_fence_notify(fence, FENCE_COMPLETE) != NOTIFY_DONE) -+ return; -+ -+ debug_fence_set_state(fence, DEBUG_FENCE_NOTIFY, DEBUG_FENCE_IDLE); -+ -+ __i915_sw_fence_wake_up_all(fence, continuation); -+ -+ debug_fence_destroy(fence); -+ __i915_sw_fence_notify(fence, FENCE_FREE); -+} -+ -+void i915_sw_fence_complete(struct i915_sw_fence *fence) -+{ -+ debug_fence_assert(fence); -+ -+ if (WARN_ON(i915_sw_fence_done(fence))) -+ return; -+ -+ __i915_sw_fence_complete(fence, NULL); -+} -+ -+void i915_sw_fence_await(struct i915_sw_fence *fence) -+{ -+ debug_fence_assert(fence); -+ WARN_ON(atomic_inc_return(&fence->pending) <= 1); -+} -+ -+void __i915_sw_fence_init(struct i915_sw_fence *fence, -+ i915_sw_fence_notify_t fn, -+ const char *name, -+ struct lock_class_key *key) -+{ -+ BUG_ON(!fn || (unsigned long)fn & ~I915_SW_FENCE_MASK); -+ -+ debug_fence_init(fence); -+ -+ __init_waitqueue_head(&fence->wait, name, key); -+ atomic_set(&fence->pending, 1); -+ fence->flags = (unsigned long)fn; -+} -+ -+void i915_sw_fence_commit(struct i915_sw_fence *fence) -+{ -+ debug_fence_activate(fence); -+ i915_sw_fence_complete(fence); -+} -+ -+static int i915_sw_fence_wake(wait_queue_entry_t *wq, unsigned mode, int flags, void *key) -+{ -+ list_del(&wq->entry); -+ __i915_sw_fence_complete(wq->private, key); -+ -+ if (wq->flags & I915_SW_FENCE_FLAG_ALLOC) -+ kfree(wq); -+ return 0; -+} -+ -+static bool __i915_sw_fence_check_if_after(struct i915_sw_fence *fence, -+ const struct i915_sw_fence * const signaler) -+{ -+ wait_queue_entry_t *wq; -+ -+ if (__test_and_set_bit(I915_SW_FENCE_CHECKED_BIT, &fence->flags)) -+ return false; -+ -+ if (fence == signaler) -+ return true; -+ -+ list_for_each_entry(wq, &fence->wait.head, entry) { -+ if (wq->func != i915_sw_fence_wake) -+ continue; -+ -+ if (__i915_sw_fence_check_if_after(wq->private, signaler)) -+ return true; -+ } -+ -+ return false; -+} -+ -+static void __i915_sw_fence_clear_checked_bit(struct i915_sw_fence *fence) -+{ -+ wait_queue_entry_t *wq; -+ -+ if (!__test_and_clear_bit(I915_SW_FENCE_CHECKED_BIT, &fence->flags)) -+ return; -+ -+ list_for_each_entry(wq, &fence->wait.head, entry) { -+ if (wq->func != i915_sw_fence_wake) -+ continue; -+ -+ __i915_sw_fence_clear_checked_bit(wq->private); -+ } -+} -+ -+static bool i915_sw_fence_check_if_after(struct i915_sw_fence *fence, -+ const struct i915_sw_fence * const signaler) -+{ -+ unsigned long flags; -+ bool err; -+ -+ if (!IS_ENABLED(CONFIG_DRM_I915_SW_FENCE_CHECK_DAG)) -+ return false; -+ -+ spin_lock_irqsave(&i915_sw_fence_lock, flags); -+ err = __i915_sw_fence_check_if_after(fence, signaler); -+ __i915_sw_fence_clear_checked_bit(fence); -+ spin_unlock_irqrestore(&i915_sw_fence_lock, flags); -+ -+ return err; -+} -+ -+static int __i915_sw_fence_await_sw_fence(struct i915_sw_fence *fence, -+ struct i915_sw_fence *signaler, -+ wait_queue_entry_t *wq, gfp_t gfp) -+{ -+ unsigned long flags; -+ int pending; -+ -+ debug_fence_assert(fence); -+ might_sleep_if(gfpflags_allow_blocking(gfp)); -+ -+ if (i915_sw_fence_done(signaler)) -+ return 0; -+ -+ debug_fence_assert(signaler); -+ -+ /* The dependency graph must be acyclic. */ -+ if (unlikely(i915_sw_fence_check_if_after(fence, signaler))) -+ return -EINVAL; -+ -+ pending = 0; -+ if (!wq) { -+ wq = kmalloc(sizeof(*wq), gfp); -+ if (!wq) { -+ if (!gfpflags_allow_blocking(gfp)) -+ return -ENOMEM; -+ -+ i915_sw_fence_wait(signaler); -+ return 0; -+ } -+ -+ pending |= I915_SW_FENCE_FLAG_ALLOC; -+ } -+ -+ INIT_LIST_HEAD(&wq->entry); -+ wq->flags = pending; -+ wq->func = i915_sw_fence_wake; -+ wq->private = fence; -+ -+ i915_sw_fence_await(fence); -+ -+ spin_lock_irqsave(&signaler->wait.lock, flags); -+ if (likely(!i915_sw_fence_done(signaler))) { -+ __add_wait_queue_entry_tail(&signaler->wait, wq); -+ pending = 1; -+ } else { -+ i915_sw_fence_wake(wq, 0, 0, NULL); -+ pending = 0; -+ } -+ spin_unlock_irqrestore(&signaler->wait.lock, flags); -+ -+ return pending; -+} -+ -+int i915_sw_fence_await_sw_fence(struct i915_sw_fence *fence, -+ struct i915_sw_fence *signaler, -+ wait_queue_entry_t *wq) -+{ -+ return __i915_sw_fence_await_sw_fence(fence, signaler, wq, 0); -+} -+ -+int i915_sw_fence_await_sw_fence_gfp(struct i915_sw_fence *fence, -+ struct i915_sw_fence *signaler, -+ gfp_t gfp) -+{ -+ return __i915_sw_fence_await_sw_fence(fence, signaler, NULL, gfp); -+} -+ -+struct i915_sw_dma_fence_cb_timer { -+ struct i915_sw_dma_fence_cb base; -+ struct dma_fence *dma; -+ struct timer_list timer; -+ struct irq_work work; -+ struct rcu_head rcu; -+}; -+ -+static void dma_i915_sw_fence_wake(struct dma_fence *dma, -+ struct dma_fence_cb *data) -+{ -+ struct i915_sw_dma_fence_cb *cb = container_of(data, typeof(*cb), base); -+ -+ i915_sw_fence_complete(cb->fence); -+ kfree(cb); -+} -+ -+static void timer_i915_sw_fence_wake(struct timer_list *t) -+{ -+ struct i915_sw_dma_fence_cb_timer *cb = from_timer(cb, t, timer); -+ struct i915_sw_fence *fence; -+ -+ fence = xchg(&cb->base.fence, NULL); -+ if (!fence) -+ return; -+ -+ pr_notice("Asynchronous wait on fence %s:%s:%llx timed out (hint:%pS)\n", -+ cb->dma->ops->get_driver_name(cb->dma), -+ cb->dma->ops->get_timeline_name(cb->dma), -+ cb->dma->seqno, -+ i915_sw_fence_debug_hint(fence)); -+ -+ i915_sw_fence_complete(fence); -+} -+ -+static void dma_i915_sw_fence_wake_timer(struct dma_fence *dma, -+ struct dma_fence_cb *data) -+{ -+ struct i915_sw_dma_fence_cb_timer *cb = -+ container_of(data, typeof(*cb), base.base); -+ struct i915_sw_fence *fence; -+ -+ fence = xchg(&cb->base.fence, NULL); -+ if (fence) -+ i915_sw_fence_complete(fence); -+ -+ irq_work_queue(&cb->work); -+} -+ -+static void irq_i915_sw_fence_work(struct irq_work *wrk) -+{ -+ struct i915_sw_dma_fence_cb_timer *cb = -+ container_of(wrk, typeof(*cb), work); -+ -+ del_timer_sync(&cb->timer); -+ dma_fence_put(cb->dma); -+ -+ kfree_rcu(cb, rcu); -+} -+ -+int i915_sw_fence_await_dma_fence(struct i915_sw_fence *fence, -+ struct dma_fence *dma, -+ unsigned long timeout, -+ gfp_t gfp) -+{ -+ struct i915_sw_dma_fence_cb *cb; -+ dma_fence_func_t func; -+ int ret; -+ -+ debug_fence_assert(fence); -+ might_sleep_if(gfpflags_allow_blocking(gfp)); -+ -+ if (dma_fence_is_signaled(dma)) -+ return 0; -+ -+ cb = kmalloc(timeout ? -+ sizeof(struct i915_sw_dma_fence_cb_timer) : -+ sizeof(struct i915_sw_dma_fence_cb), -+ gfp); -+ if (!cb) { -+ if (!gfpflags_allow_blocking(gfp)) -+ return -ENOMEM; -+ -+ return dma_fence_wait(dma, false); -+ } -+ -+ cb->fence = fence; -+ i915_sw_fence_await(fence); -+ -+ func = dma_i915_sw_fence_wake; -+ if (timeout) { -+ struct i915_sw_dma_fence_cb_timer *timer = -+ container_of(cb, typeof(*timer), base); -+ -+ timer->dma = dma_fence_get(dma); -+ init_irq_work(&timer->work, irq_i915_sw_fence_work); -+ -+ timer_setup(&timer->timer, -+ timer_i915_sw_fence_wake, TIMER_IRQSAFE); -+ mod_timer(&timer->timer, round_jiffies_up(jiffies + timeout)); -+ -+ func = dma_i915_sw_fence_wake_timer; -+ } -+ -+ ret = dma_fence_add_callback(dma, &cb->base, func); -+ if (ret == 0) { -+ ret = 1; -+ } else { -+ func(dma, &cb->base); -+ if (ret == -ENOENT) /* fence already signaled */ -+ ret = 0; -+ } -+ -+ return ret; -+} -+ -+static void __dma_i915_sw_fence_wake(struct dma_fence *dma, -+ struct dma_fence_cb *data) -+{ -+ struct i915_sw_dma_fence_cb *cb = container_of(data, typeof(*cb), base); -+ -+ i915_sw_fence_complete(cb->fence); -+} -+ -+int __i915_sw_fence_await_dma_fence(struct i915_sw_fence *fence, -+ struct dma_fence *dma, -+ struct i915_sw_dma_fence_cb *cb) -+{ -+ int ret; -+ -+ debug_fence_assert(fence); -+ -+ if (dma_fence_is_signaled(dma)) -+ return 0; -+ -+ cb->fence = fence; -+ i915_sw_fence_await(fence); -+ -+ ret = dma_fence_add_callback(dma, &cb->base, __dma_i915_sw_fence_wake); -+ if (ret == 0) { -+ ret = 1; -+ } else { -+ i915_sw_fence_complete(fence); -+ if (ret == -ENOENT) /* fence already signaled */ -+ ret = 0; -+ } -+ -+ return ret; -+} -+ -+int i915_sw_fence_await_reservation(struct i915_sw_fence *fence, -+ struct reservation_object *resv, -+ const struct dma_fence_ops *exclude, -+ bool write, -+ unsigned long timeout, -+ gfp_t gfp) -+{ -+ struct dma_fence *excl; -+ int ret = 0, pending; -+ -+ debug_fence_assert(fence); -+ might_sleep_if(gfpflags_allow_blocking(gfp)); -+ -+ if (write) { -+ struct dma_fence **shared; -+ unsigned int count, i; -+ -+ ret = reservation_object_get_fences_rcu(resv, -+ &excl, &count, &shared); -+ if (ret) -+ return ret; -+ -+ for (i = 0; i < count; i++) { -+ if (shared[i]->ops == exclude) -+ continue; -+ -+ pending = i915_sw_fence_await_dma_fence(fence, -+ shared[i], -+ timeout, -+ gfp); -+ if (pending < 0) { -+ ret = pending; -+ break; -+ } -+ -+ ret |= pending; -+ } -+ -+ for (i = 0; i < count; i++) -+ dma_fence_put(shared[i]); -+ kfree(shared); -+ } else { -+ excl = reservation_object_get_excl_rcu(resv); -+ } -+ -+ if (ret >= 0 && excl && excl->ops != exclude) { -+ pending = i915_sw_fence_await_dma_fence(fence, -+ excl, -+ timeout, -+ gfp); -+ if (pending < 0) -+ ret = pending; -+ else -+ ret |= pending; -+ } -+ -+ dma_fence_put(excl); -+ -+ return ret; -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -+#include "selftests/lib_sw_fence.c" -+#include "selftests/i915_sw_fence.c" -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_sw_fence.h b/drivers/gpu/drm/i915_legacy/i915_sw_fence.h -new file mode 100644 -index 000000000000..9cb5c3b307a6 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_sw_fence.h -@@ -0,0 +1,109 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * i915_sw_fence.h - library routines for N:M synchronisation points -+ * -+ * Copyright (C) 2016 Intel Corporation -+ */ -+ -+#ifndef _I915_SW_FENCE_H_ -+#define _I915_SW_FENCE_H_ -+ -+#include -+#include -+#include -+#include /* for NOTIFY_DONE */ -+#include -+ -+struct completion; -+struct reservation_object; -+ -+struct i915_sw_fence { -+ wait_queue_head_t wait; -+ unsigned long flags; -+ atomic_t pending; -+}; -+ -+#define I915_SW_FENCE_CHECKED_BIT 0 /* used internally for DAG checking */ -+#define I915_SW_FENCE_PRIVATE_BIT 1 /* available for use by owner */ -+#define I915_SW_FENCE_MASK (~3) -+ -+enum i915_sw_fence_notify { -+ FENCE_COMPLETE, -+ FENCE_FREE -+}; -+ -+typedef int (*i915_sw_fence_notify_t)(struct i915_sw_fence *, -+ enum i915_sw_fence_notify state); -+#define __i915_sw_fence_call __aligned(4) -+ -+void __i915_sw_fence_init(struct i915_sw_fence *fence, -+ i915_sw_fence_notify_t fn, -+ const char *name, -+ struct lock_class_key *key); -+#ifdef CONFIG_LOCKDEP -+#define i915_sw_fence_init(fence, fn) \ -+do { \ -+ static struct lock_class_key __key; \ -+ \ -+ __i915_sw_fence_init((fence), (fn), #fence, &__key); \ -+} while (0) -+#else -+#define i915_sw_fence_init(fence, fn) \ -+ __i915_sw_fence_init((fence), (fn), NULL, NULL) -+#endif -+ -+#ifdef CONFIG_DRM_I915_SW_FENCE_DEBUG_OBJECTS -+void i915_sw_fence_fini(struct i915_sw_fence *fence); -+#else -+static inline void i915_sw_fence_fini(struct i915_sw_fence *fence) {} -+#endif -+ -+void i915_sw_fence_commit(struct i915_sw_fence *fence); -+ -+int i915_sw_fence_await_sw_fence(struct i915_sw_fence *fence, -+ struct i915_sw_fence *after, -+ wait_queue_entry_t *wq); -+int i915_sw_fence_await_sw_fence_gfp(struct i915_sw_fence *fence, -+ struct i915_sw_fence *after, -+ gfp_t gfp); -+ -+struct i915_sw_dma_fence_cb { -+ struct dma_fence_cb base; -+ struct i915_sw_fence *fence; -+}; -+ -+int __i915_sw_fence_await_dma_fence(struct i915_sw_fence *fence, -+ struct dma_fence *dma, -+ struct i915_sw_dma_fence_cb *cb); -+int i915_sw_fence_await_dma_fence(struct i915_sw_fence *fence, -+ struct dma_fence *dma, -+ unsigned long timeout, -+ gfp_t gfp); -+ -+int i915_sw_fence_await_reservation(struct i915_sw_fence *fence, -+ struct reservation_object *resv, -+ const struct dma_fence_ops *exclude, -+ bool write, -+ unsigned long timeout, -+ gfp_t gfp); -+ -+void i915_sw_fence_await(struct i915_sw_fence *fence); -+void i915_sw_fence_complete(struct i915_sw_fence *fence); -+ -+static inline bool i915_sw_fence_signaled(const struct i915_sw_fence *fence) -+{ -+ return atomic_read(&fence->pending) <= 0; -+} -+ -+static inline bool i915_sw_fence_done(const struct i915_sw_fence *fence) -+{ -+ return atomic_read(&fence->pending) < 0; -+} -+ -+static inline void i915_sw_fence_wait(struct i915_sw_fence *fence) -+{ -+ wait_event(fence->wait, i915_sw_fence_done(fence)); -+} -+ -+#endif /* _I915_SW_FENCE_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_syncmap.c b/drivers/gpu/drm/i915_legacy/i915_syncmap.c -new file mode 100644 -index 000000000000..60404dbb2e9f ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_syncmap.c -@@ -0,0 +1,412 @@ -+/* -+ * Copyright © 2017 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include -+ -+#include "i915_syncmap.h" -+ -+#include "i915_gem.h" /* GEM_BUG_ON() */ -+#include "i915_selftest.h" -+ -+#define SHIFT ilog2(KSYNCMAP) -+#define MASK (KSYNCMAP - 1) -+ -+/* -+ * struct i915_syncmap is a layer of a radixtree that maps a u64 fence -+ * context id to the last u32 fence seqno waited upon from that context. -+ * Unlike lib/radixtree it uses a parent pointer that allows traversal back to -+ * the root. This allows us to access the whole tree via a single pointer -+ * to the most recently used layer. We expect fence contexts to be dense -+ * and most reuse to be on the same i915_gem_context but on neighbouring -+ * engines (i.e. on adjacent contexts) and reuse the same leaf, a very -+ * effective lookup cache. If the new lookup is not on the same leaf, we -+ * expect it to be on the neighbouring branch. -+ * -+ * A leaf holds an array of u32 seqno, and has height 0. The bitmap field -+ * allows us to store whether a particular seqno is valid (i.e. allows us -+ * to distinguish unset from 0). -+ * -+ * A branch holds an array of layer pointers, and has height > 0, and always -+ * has at least 2 layers (either branches or leaves) below it. -+ * -+ * For example, -+ * for x in -+ * 0 1 2 0x10 0x11 0x200 0x201 -+ * 0x500000 0x500001 0x503000 0x503001 -+ * 0xE<<60: -+ * i915_syncmap_set(&sync, x, lower_32_bits(x)); -+ * will build a tree like: -+ * 0xXXXXXXXXXXXXXXXX -+ * 0-> 0x0000000000XXXXXX -+ * | 0-> 0x0000000000000XXX -+ * | | 0-> 0x00000000000000XX -+ * | | | 0-> 0x000000000000000X 0:0, 1:1, 2:2 -+ * | | | 1-> 0x000000000000001X 0:10, 1:11 -+ * | | 2-> 0x000000000000020X 0:200, 1:201 -+ * | 5-> 0x000000000050XXXX -+ * | 0-> 0x000000000050000X 0:500000, 1:500001 -+ * | 3-> 0x000000000050300X 0:503000, 1:503001 -+ * e-> 0xe00000000000000X e:e -+ */ -+ -+struct i915_syncmap { -+ u64 prefix; -+ unsigned int height; -+ unsigned int bitmap; -+ struct i915_syncmap *parent; -+ /* -+ * Following this header is an array of either seqno or child pointers: -+ * union { -+ * u32 seqno[KSYNCMAP]; -+ * struct i915_syncmap *child[KSYNCMAP]; -+ * }; -+ */ -+}; -+ -+/** -+ * i915_syncmap_init -- initialise the #i915_syncmap -+ * @root: pointer to the #i915_syncmap -+ */ -+void i915_syncmap_init(struct i915_syncmap **root) -+{ -+ BUILD_BUG_ON_NOT_POWER_OF_2(KSYNCMAP); -+ BUILD_BUG_ON_NOT_POWER_OF_2(SHIFT); -+ BUILD_BUG_ON(KSYNCMAP > BITS_PER_TYPE((*root)->bitmap)); -+ *root = NULL; -+} -+ -+static inline u32 *__sync_seqno(struct i915_syncmap *p) -+{ -+ GEM_BUG_ON(p->height); -+ return (u32 *)(p + 1); -+} -+ -+static inline struct i915_syncmap **__sync_child(struct i915_syncmap *p) -+{ -+ GEM_BUG_ON(!p->height); -+ return (struct i915_syncmap **)(p + 1); -+} -+ -+static inline unsigned int -+__sync_branch_idx(const struct i915_syncmap *p, u64 id) -+{ -+ return (id >> p->height) & MASK; -+} -+ -+static inline unsigned int -+__sync_leaf_idx(const struct i915_syncmap *p, u64 id) -+{ -+ GEM_BUG_ON(p->height); -+ return id & MASK; -+} -+ -+static inline u64 __sync_branch_prefix(const struct i915_syncmap *p, u64 id) -+{ -+ return id >> p->height >> SHIFT; -+} -+ -+static inline u64 __sync_leaf_prefix(const struct i915_syncmap *p, u64 id) -+{ -+ GEM_BUG_ON(p->height); -+ return id >> SHIFT; -+} -+ -+static inline bool seqno_later(u32 a, u32 b) -+{ -+ return (s32)(a - b) >= 0; -+} -+ -+/** -+ * i915_syncmap_is_later -- compare against the last know sync point -+ * @root: pointer to the #i915_syncmap -+ * @id: the context id (other timeline) we are synchronising to -+ * @seqno: the sequence number along the other timeline -+ * -+ * If we have already synchronised this @root timeline with another (@id) then -+ * we can omit any repeated or earlier synchronisation requests. If the two -+ * timelines are already coupled, we can also omit the dependency between the -+ * two as that is already known via the timeline. -+ * -+ * Returns true if the two timelines are already synchronised wrt to @seqno, -+ * false if not and the synchronisation must be emitted. -+ */ -+bool i915_syncmap_is_later(struct i915_syncmap **root, u64 id, u32 seqno) -+{ -+ struct i915_syncmap *p; -+ unsigned int idx; -+ -+ p = *root; -+ if (!p) -+ return false; -+ -+ if (likely(__sync_leaf_prefix(p, id) == p->prefix)) -+ goto found; -+ -+ /* First climb the tree back to a parent branch */ -+ do { -+ p = p->parent; -+ if (!p) -+ return false; -+ -+ if (__sync_branch_prefix(p, id) == p->prefix) -+ break; -+ } while (1); -+ -+ /* And then descend again until we find our leaf */ -+ do { -+ if (!p->height) -+ break; -+ -+ p = __sync_child(p)[__sync_branch_idx(p, id)]; -+ if (!p) -+ return false; -+ -+ if (__sync_branch_prefix(p, id) != p->prefix) -+ return false; -+ } while (1); -+ -+ *root = p; -+found: -+ idx = __sync_leaf_idx(p, id); -+ if (!(p->bitmap & BIT(idx))) -+ return false; -+ -+ return seqno_later(__sync_seqno(p)[idx], seqno); -+} -+ -+static struct i915_syncmap * -+__sync_alloc_leaf(struct i915_syncmap *parent, u64 id) -+{ -+ struct i915_syncmap *p; -+ -+ p = kmalloc(sizeof(*p) + KSYNCMAP * sizeof(u32), GFP_KERNEL); -+ if (unlikely(!p)) -+ return NULL; -+ -+ p->parent = parent; -+ p->height = 0; -+ p->bitmap = 0; -+ p->prefix = __sync_leaf_prefix(p, id); -+ return p; -+} -+ -+static inline void __sync_set_seqno(struct i915_syncmap *p, u64 id, u32 seqno) -+{ -+ unsigned int idx = __sync_leaf_idx(p, id); -+ -+ p->bitmap |= BIT(idx); -+ __sync_seqno(p)[idx] = seqno; -+} -+ -+static inline void __sync_set_child(struct i915_syncmap *p, -+ unsigned int idx, -+ struct i915_syncmap *child) -+{ -+ p->bitmap |= BIT(idx); -+ __sync_child(p)[idx] = child; -+} -+ -+static noinline int __sync_set(struct i915_syncmap **root, u64 id, u32 seqno) -+{ -+ struct i915_syncmap *p = *root; -+ unsigned int idx; -+ -+ if (!p) { -+ p = __sync_alloc_leaf(NULL, id); -+ if (unlikely(!p)) -+ return -ENOMEM; -+ -+ goto found; -+ } -+ -+ /* Caller handled the likely cached case */ -+ GEM_BUG_ON(__sync_leaf_prefix(p, id) == p->prefix); -+ -+ /* Climb back up the tree until we find a common prefix */ -+ do { -+ if (!p->parent) -+ break; -+ -+ p = p->parent; -+ -+ if (__sync_branch_prefix(p, id) == p->prefix) -+ break; -+ } while (1); -+ -+ /* -+ * No shortcut, we have to descend the tree to find the right layer -+ * containing this fence. -+ * -+ * Each layer in the tree holds 16 (KSYNCMAP) pointers, either fences -+ * or lower layers. Leaf nodes (height = 0) contain the fences, all -+ * other nodes (height > 0) are internal layers that point to a lower -+ * node. Each internal layer has at least 2 descendents. -+ * -+ * Starting at the top, we check whether the current prefix matches. If -+ * it doesn't, we have gone past our target and need to insert a join -+ * into the tree, and a new leaf node for the target as a descendent -+ * of the join, as well as the original layer. -+ * -+ * The matching prefix means we are still following the right branch -+ * of the tree. If it has height 0, we have found our leaf and just -+ * need to replace the fence slot with ourselves. If the height is -+ * not zero, our slot contains the next layer in the tree (unless -+ * it is empty, in which case we can add ourselves as a new leaf). -+ * As descend the tree the prefix grows (and height decreases). -+ */ -+ do { -+ struct i915_syncmap *next; -+ -+ if (__sync_branch_prefix(p, id) != p->prefix) { -+ unsigned int above; -+ -+ /* Insert a join above the current layer */ -+ next = kzalloc(sizeof(*next) + KSYNCMAP * sizeof(next), -+ GFP_KERNEL); -+ if (unlikely(!next)) -+ return -ENOMEM; -+ -+ /* Compute the height at which these two diverge */ -+ above = fls64(__sync_branch_prefix(p, id) ^ p->prefix); -+ above = round_up(above, SHIFT); -+ next->height = above + p->height; -+ next->prefix = __sync_branch_prefix(next, id); -+ -+ /* Insert the join into the parent */ -+ if (p->parent) { -+ idx = __sync_branch_idx(p->parent, id); -+ __sync_child(p->parent)[idx] = next; -+ GEM_BUG_ON(!(p->parent->bitmap & BIT(idx))); -+ } -+ next->parent = p->parent; -+ -+ /* Compute the idx of the other branch, not our id! */ -+ idx = p->prefix >> (above - SHIFT) & MASK; -+ __sync_set_child(next, idx, p); -+ p->parent = next; -+ -+ /* Ascend to the join */ -+ p = next; -+ } else { -+ if (!p->height) -+ break; -+ } -+ -+ /* Descend into the next layer */ -+ GEM_BUG_ON(!p->height); -+ idx = __sync_branch_idx(p, id); -+ next = __sync_child(p)[idx]; -+ if (!next) { -+ next = __sync_alloc_leaf(p, id); -+ if (unlikely(!next)) -+ return -ENOMEM; -+ -+ __sync_set_child(p, idx, next); -+ p = next; -+ break; -+ } -+ -+ p = next; -+ } while (1); -+ -+found: -+ GEM_BUG_ON(p->prefix != __sync_leaf_prefix(p, id)); -+ __sync_set_seqno(p, id, seqno); -+ *root = p; -+ return 0; -+} -+ -+/** -+ * i915_syncmap_set -- mark the most recent syncpoint between contexts -+ * @root: pointer to the #i915_syncmap -+ * @id: the context id (other timeline) we have synchronised to -+ * @seqno: the sequence number along the other timeline -+ * -+ * When we synchronise this @root timeline with another (@id), we also know -+ * that we have synchronized with all previous seqno along that timeline. If -+ * we then have a request to synchronise with the same seqno or older, we can -+ * omit it, see i915_syncmap_is_later() -+ * -+ * Returns 0 on success, or a negative error code. -+ */ -+int i915_syncmap_set(struct i915_syncmap **root, u64 id, u32 seqno) -+{ -+ struct i915_syncmap *p = *root; -+ -+ /* -+ * We expect to be called in sequence following is_later(id), which -+ * should have preloaded the root for us. -+ */ -+ if (likely(p && __sync_leaf_prefix(p, id) == p->prefix)) { -+ __sync_set_seqno(p, id, seqno); -+ return 0; -+ } -+ -+ return __sync_set(root, id, seqno); -+} -+ -+static void __sync_free(struct i915_syncmap *p) -+{ -+ if (p->height) { -+ unsigned int i; -+ -+ while ((i = ffs(p->bitmap))) { -+ p->bitmap &= ~0u << i; -+ __sync_free(__sync_child(p)[i - 1]); -+ } -+ } -+ -+ kfree(p); -+} -+ -+/** -+ * i915_syncmap_free -- free all memory associated with the syncmap -+ * @root: pointer to the #i915_syncmap -+ * -+ * Either when the timeline is to be freed and we no longer need the sync -+ * point tracking, or when the fences are all known to be signaled and the -+ * sync point tracking is redundant, we can free the #i915_syncmap to recover -+ * its allocations. -+ * -+ * Will reinitialise the @root pointer so that the #i915_syncmap is ready for -+ * reuse. -+ */ -+void i915_syncmap_free(struct i915_syncmap **root) -+{ -+ struct i915_syncmap *p; -+ -+ p = *root; -+ if (!p) -+ return; -+ -+ while (p->parent) -+ p = p->parent; -+ -+ __sync_free(p); -+ *root = NULL; -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -+#include "selftests/i915_syncmap.c" -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_syncmap.h b/drivers/gpu/drm/i915_legacy/i915_syncmap.h -new file mode 100644 -index 000000000000..0653f70bee82 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_syncmap.h -@@ -0,0 +1,38 @@ -+/* -+ * Copyright © 2017 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef __I915_SYNCMAP_H__ -+#define __I915_SYNCMAP_H__ -+ -+#include -+ -+struct i915_syncmap; -+#define KSYNCMAP 16 /* radix of the tree, how many slots in each layer */ -+ -+void i915_syncmap_init(struct i915_syncmap **root); -+int i915_syncmap_set(struct i915_syncmap **root, u64 id, u32 seqno); -+bool i915_syncmap_is_later(struct i915_syncmap **root, u64 id, u32 seqno); -+void i915_syncmap_free(struct i915_syncmap **root); -+ -+#endif /* __I915_SYNCMAP_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_sysfs.c b/drivers/gpu/drm/i915_legacy/i915_sysfs.c -new file mode 100644 -index 000000000000..41313005af42 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_sysfs.c -@@ -0,0 +1,644 @@ -+/* -+ * Copyright © 2012 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Ben Widawsky -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include "intel_drv.h" -+#include "i915_drv.h" -+ -+static inline struct drm_i915_private *kdev_minor_to_i915(struct device *kdev) -+{ -+ struct drm_minor *minor = dev_get_drvdata(kdev); -+ return to_i915(minor->dev); -+} -+ -+#ifdef CONFIG_PM -+static u32 calc_residency(struct drm_i915_private *dev_priv, -+ i915_reg_t reg) -+{ -+ intel_wakeref_t wakeref; -+ u64 res = 0; -+ -+ with_intel_runtime_pm(dev_priv, wakeref) -+ res = intel_rc6_residency_us(dev_priv, reg); -+ -+ return DIV_ROUND_CLOSEST_ULL(res, 1000); -+} -+ -+static ssize_t -+show_rc6_mask(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ unsigned int mask; -+ -+ mask = 0; -+ if (HAS_RC6(dev_priv)) -+ mask |= BIT(0); -+ if (HAS_RC6p(dev_priv)) -+ mask |= BIT(1); -+ if (HAS_RC6pp(dev_priv)) -+ mask |= BIT(2); -+ -+ return snprintf(buf, PAGE_SIZE, "%x\n", mask); -+} -+ -+static ssize_t -+show_rc6_ms(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ u32 rc6_residency = calc_residency(dev_priv, GEN6_GT_GFX_RC6); -+ return snprintf(buf, PAGE_SIZE, "%u\n", rc6_residency); -+} -+ -+static ssize_t -+show_rc6p_ms(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ u32 rc6p_residency = calc_residency(dev_priv, GEN6_GT_GFX_RC6p); -+ return snprintf(buf, PAGE_SIZE, "%u\n", rc6p_residency); -+} -+ -+static ssize_t -+show_rc6pp_ms(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ u32 rc6pp_residency = calc_residency(dev_priv, GEN6_GT_GFX_RC6pp); -+ return snprintf(buf, PAGE_SIZE, "%u\n", rc6pp_residency); -+} -+ -+static ssize_t -+show_media_rc6_ms(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ u32 rc6_residency = calc_residency(dev_priv, VLV_GT_MEDIA_RC6); -+ return snprintf(buf, PAGE_SIZE, "%u\n", rc6_residency); -+} -+ -+static DEVICE_ATTR(rc6_enable, S_IRUGO, show_rc6_mask, NULL); -+static DEVICE_ATTR(rc6_residency_ms, S_IRUGO, show_rc6_ms, NULL); -+static DEVICE_ATTR(rc6p_residency_ms, S_IRUGO, show_rc6p_ms, NULL); -+static DEVICE_ATTR(rc6pp_residency_ms, S_IRUGO, show_rc6pp_ms, NULL); -+static DEVICE_ATTR(media_rc6_residency_ms, S_IRUGO, show_media_rc6_ms, NULL); -+ -+static struct attribute *rc6_attrs[] = { -+ &dev_attr_rc6_enable.attr, -+ &dev_attr_rc6_residency_ms.attr, -+ NULL -+}; -+ -+static const struct attribute_group rc6_attr_group = { -+ .name = power_group_name, -+ .attrs = rc6_attrs -+}; -+ -+static struct attribute *rc6p_attrs[] = { -+ &dev_attr_rc6p_residency_ms.attr, -+ &dev_attr_rc6pp_residency_ms.attr, -+ NULL -+}; -+ -+static const struct attribute_group rc6p_attr_group = { -+ .name = power_group_name, -+ .attrs = rc6p_attrs -+}; -+ -+static struct attribute *media_rc6_attrs[] = { -+ &dev_attr_media_rc6_residency_ms.attr, -+ NULL -+}; -+ -+static const struct attribute_group media_rc6_attr_group = { -+ .name = power_group_name, -+ .attrs = media_rc6_attrs -+}; -+#endif -+ -+static int l3_access_valid(struct drm_i915_private *dev_priv, loff_t offset) -+{ -+ if (!HAS_L3_DPF(dev_priv)) -+ return -EPERM; -+ -+ if (offset % 4 != 0) -+ return -EINVAL; -+ -+ if (offset >= GEN7_L3LOG_SIZE) -+ return -ENXIO; -+ -+ return 0; -+} -+ -+static ssize_t -+i915_l3_read(struct file *filp, struct kobject *kobj, -+ struct bin_attribute *attr, char *buf, -+ loff_t offset, size_t count) -+{ -+ struct device *kdev = kobj_to_dev(kobj); -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ struct drm_device *dev = &dev_priv->drm; -+ int slice = (int)(uintptr_t)attr->private; -+ int ret; -+ -+ count = round_down(count, 4); -+ -+ ret = l3_access_valid(dev_priv, offset); -+ if (ret) -+ return ret; -+ -+ count = min_t(size_t, GEN7_L3LOG_SIZE - offset, count); -+ -+ ret = i915_mutex_lock_interruptible(dev); -+ if (ret) -+ return ret; -+ -+ if (dev_priv->l3_parity.remap_info[slice]) -+ memcpy(buf, -+ dev_priv->l3_parity.remap_info[slice] + (offset/4), -+ count); -+ else -+ memset(buf, 0, count); -+ -+ mutex_unlock(&dev->struct_mutex); -+ -+ return count; -+} -+ -+static ssize_t -+i915_l3_write(struct file *filp, struct kobject *kobj, -+ struct bin_attribute *attr, char *buf, -+ loff_t offset, size_t count) -+{ -+ struct device *kdev = kobj_to_dev(kobj); -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ struct drm_device *dev = &dev_priv->drm; -+ struct i915_gem_context *ctx; -+ int slice = (int)(uintptr_t)attr->private; -+ u32 **remap_info; -+ int ret; -+ -+ ret = l3_access_valid(dev_priv, offset); -+ if (ret) -+ return ret; -+ -+ ret = i915_mutex_lock_interruptible(dev); -+ if (ret) -+ return ret; -+ -+ remap_info = &dev_priv->l3_parity.remap_info[slice]; -+ if (!*remap_info) { -+ *remap_info = kzalloc(GEN7_L3LOG_SIZE, GFP_KERNEL); -+ if (!*remap_info) { -+ ret = -ENOMEM; -+ goto out; -+ } -+ } -+ -+ /* TODO: Ideally we really want a GPU reset here to make sure errors -+ * aren't propagated. Since I cannot find a stable way to reset the GPU -+ * at this point it is left as a TODO. -+ */ -+ memcpy(*remap_info + (offset/4), buf, count); -+ -+ /* NB: We defer the remapping until we switch to the context */ -+ list_for_each_entry(ctx, &dev_priv->contexts.list, link) -+ ctx->remap_slice |= (1<struct_mutex); -+ -+ return ret; -+} -+ -+static const struct bin_attribute dpf_attrs = { -+ .attr = {.name = "l3_parity", .mode = (S_IRUSR | S_IWUSR)}, -+ .size = GEN7_L3LOG_SIZE, -+ .read = i915_l3_read, -+ .write = i915_l3_write, -+ .mmap = NULL, -+ .private = (void *)0 -+}; -+ -+static const struct bin_attribute dpf_attrs_1 = { -+ .attr = {.name = "l3_parity_slice_1", .mode = (S_IRUSR | S_IWUSR)}, -+ .size = GEN7_L3LOG_SIZE, -+ .read = i915_l3_read, -+ .write = i915_l3_write, -+ .mmap = NULL, -+ .private = (void *)1 -+}; -+ -+static ssize_t gt_act_freq_mhz_show(struct device *kdev, -+ struct device_attribute *attr, char *buf) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ intel_wakeref_t wakeref; -+ int ret; -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ u32 freq; -+ freq = vlv_punit_read(dev_priv, PUNIT_REG_GPU_FREQ_STS); -+ ret = intel_gpu_freq(dev_priv, (freq >> 8) & 0xff); -+ } else { -+ ret = intel_gpu_freq(dev_priv, -+ intel_get_cagf(dev_priv, -+ I915_READ(GEN6_RPSTAT1))); -+ } -+ mutex_unlock(&dev_priv->pcu_lock); -+ -+ intel_runtime_pm_put(dev_priv, wakeref); -+ -+ return snprintf(buf, PAGE_SIZE, "%d\n", ret); -+} -+ -+static ssize_t gt_cur_freq_mhz_show(struct device *kdev, -+ struct device_attribute *attr, char *buf) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ -+ return snprintf(buf, PAGE_SIZE, "%d\n", -+ intel_gpu_freq(dev_priv, -+ dev_priv->gt_pm.rps.cur_freq)); -+} -+ -+static ssize_t gt_boost_freq_mhz_show(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ -+ return snprintf(buf, PAGE_SIZE, "%d\n", -+ intel_gpu_freq(dev_priv, -+ dev_priv->gt_pm.rps.boost_freq)); -+} -+ -+static ssize_t gt_boost_freq_mhz_store(struct device *kdev, -+ struct device_attribute *attr, -+ const char *buf, size_t count) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ struct intel_rps *rps = &dev_priv->gt_pm.rps; -+ bool boost = false; -+ ssize_t ret; -+ u32 val; -+ -+ ret = kstrtou32(buf, 0, &val); -+ if (ret) -+ return ret; -+ -+ /* Validate against (static) hardware limits */ -+ val = intel_freq_opcode(dev_priv, val); -+ if (val < rps->min_freq || val > rps->max_freq) -+ return -EINVAL; -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ if (val != rps->boost_freq) { -+ rps->boost_freq = val; -+ boost = atomic_read(&rps->num_waiters); -+ } -+ mutex_unlock(&dev_priv->pcu_lock); -+ if (boost) -+ schedule_work(&rps->work); -+ -+ return count; -+} -+ -+static ssize_t vlv_rpe_freq_mhz_show(struct device *kdev, -+ struct device_attribute *attr, char *buf) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ -+ return snprintf(buf, PAGE_SIZE, "%d\n", -+ intel_gpu_freq(dev_priv, -+ dev_priv->gt_pm.rps.efficient_freq)); -+} -+ -+static ssize_t gt_max_freq_mhz_show(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ -+ return snprintf(buf, PAGE_SIZE, "%d\n", -+ intel_gpu_freq(dev_priv, -+ dev_priv->gt_pm.rps.max_freq_softlimit)); -+} -+ -+static ssize_t gt_max_freq_mhz_store(struct device *kdev, -+ struct device_attribute *attr, -+ const char *buf, size_t count) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ struct intel_rps *rps = &dev_priv->gt_pm.rps; -+ intel_wakeref_t wakeref; -+ u32 val; -+ ssize_t ret; -+ -+ ret = kstrtou32(buf, 0, &val); -+ if (ret) -+ return ret; -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ -+ val = intel_freq_opcode(dev_priv, val); -+ -+ if (val < rps->min_freq || -+ val > rps->max_freq || -+ val < rps->min_freq_softlimit) { -+ mutex_unlock(&dev_priv->pcu_lock); -+ intel_runtime_pm_put(dev_priv, wakeref); -+ return -EINVAL; -+ } -+ -+ if (val > rps->rp0_freq) -+ DRM_DEBUG("User requested overclocking to %d\n", -+ intel_gpu_freq(dev_priv, val)); -+ -+ rps->max_freq_softlimit = val; -+ -+ val = clamp_t(int, rps->cur_freq, -+ rps->min_freq_softlimit, -+ rps->max_freq_softlimit); -+ -+ /* We still need *_set_rps to process the new max_delay and -+ * update the interrupt limits and PMINTRMSK even though -+ * frequency request may be unchanged. */ -+ ret = intel_set_rps(dev_priv, val); -+ -+ mutex_unlock(&dev_priv->pcu_lock); -+ -+ intel_runtime_pm_put(dev_priv, wakeref); -+ -+ return ret ?: count; -+} -+ -+static ssize_t gt_min_freq_mhz_show(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ -+ return snprintf(buf, PAGE_SIZE, "%d\n", -+ intel_gpu_freq(dev_priv, -+ dev_priv->gt_pm.rps.min_freq_softlimit)); -+} -+ -+static ssize_t gt_min_freq_mhz_store(struct device *kdev, -+ struct device_attribute *attr, -+ const char *buf, size_t count) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ struct intel_rps *rps = &dev_priv->gt_pm.rps; -+ intel_wakeref_t wakeref; -+ u32 val; -+ ssize_t ret; -+ -+ ret = kstrtou32(buf, 0, &val); -+ if (ret) -+ return ret; -+ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ -+ val = intel_freq_opcode(dev_priv, val); -+ -+ if (val < rps->min_freq || -+ val > rps->max_freq || -+ val > rps->max_freq_softlimit) { -+ mutex_unlock(&dev_priv->pcu_lock); -+ intel_runtime_pm_put(dev_priv, wakeref); -+ return -EINVAL; -+ } -+ -+ rps->min_freq_softlimit = val; -+ -+ val = clamp_t(int, rps->cur_freq, -+ rps->min_freq_softlimit, -+ rps->max_freq_softlimit); -+ -+ /* We still need *_set_rps to process the new min_delay and -+ * update the interrupt limits and PMINTRMSK even though -+ * frequency request may be unchanged. */ -+ ret = intel_set_rps(dev_priv, val); -+ -+ mutex_unlock(&dev_priv->pcu_lock); -+ -+ intel_runtime_pm_put(dev_priv, wakeref); -+ -+ return ret ?: count; -+} -+ -+static DEVICE_ATTR_RO(gt_act_freq_mhz); -+static DEVICE_ATTR_RO(gt_cur_freq_mhz); -+static DEVICE_ATTR_RW(gt_boost_freq_mhz); -+static DEVICE_ATTR_RW(gt_max_freq_mhz); -+static DEVICE_ATTR_RW(gt_min_freq_mhz); -+ -+static DEVICE_ATTR_RO(vlv_rpe_freq_mhz); -+ -+static ssize_t gt_rp_mhz_show(struct device *kdev, struct device_attribute *attr, char *buf); -+static DEVICE_ATTR(gt_RP0_freq_mhz, S_IRUGO, gt_rp_mhz_show, NULL); -+static DEVICE_ATTR(gt_RP1_freq_mhz, S_IRUGO, gt_rp_mhz_show, NULL); -+static DEVICE_ATTR(gt_RPn_freq_mhz, S_IRUGO, gt_rp_mhz_show, NULL); -+ -+/* For now we have a static number of RP states */ -+static ssize_t gt_rp_mhz_show(struct device *kdev, struct device_attribute *attr, char *buf) -+{ -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ struct intel_rps *rps = &dev_priv->gt_pm.rps; -+ u32 val; -+ -+ if (attr == &dev_attr_gt_RP0_freq_mhz) -+ val = intel_gpu_freq(dev_priv, rps->rp0_freq); -+ else if (attr == &dev_attr_gt_RP1_freq_mhz) -+ val = intel_gpu_freq(dev_priv, rps->rp1_freq); -+ else if (attr == &dev_attr_gt_RPn_freq_mhz) -+ val = intel_gpu_freq(dev_priv, rps->min_freq); -+ else -+ BUG(); -+ -+ return snprintf(buf, PAGE_SIZE, "%d\n", val); -+} -+ -+static const struct attribute * const gen6_attrs[] = { -+ &dev_attr_gt_act_freq_mhz.attr, -+ &dev_attr_gt_cur_freq_mhz.attr, -+ &dev_attr_gt_boost_freq_mhz.attr, -+ &dev_attr_gt_max_freq_mhz.attr, -+ &dev_attr_gt_min_freq_mhz.attr, -+ &dev_attr_gt_RP0_freq_mhz.attr, -+ &dev_attr_gt_RP1_freq_mhz.attr, -+ &dev_attr_gt_RPn_freq_mhz.attr, -+ NULL, -+}; -+ -+static const struct attribute * const vlv_attrs[] = { -+ &dev_attr_gt_act_freq_mhz.attr, -+ &dev_attr_gt_cur_freq_mhz.attr, -+ &dev_attr_gt_boost_freq_mhz.attr, -+ &dev_attr_gt_max_freq_mhz.attr, -+ &dev_attr_gt_min_freq_mhz.attr, -+ &dev_attr_gt_RP0_freq_mhz.attr, -+ &dev_attr_gt_RP1_freq_mhz.attr, -+ &dev_attr_gt_RPn_freq_mhz.attr, -+ &dev_attr_vlv_rpe_freq_mhz.attr, -+ NULL, -+}; -+ -+#if IS_ENABLED(CONFIG_DRM_I915_CAPTURE_ERROR) -+ -+static ssize_t error_state_read(struct file *filp, struct kobject *kobj, -+ struct bin_attribute *attr, char *buf, -+ loff_t off, size_t count) -+{ -+ -+ struct device *kdev = kobj_to_dev(kobj); -+ struct drm_i915_private *i915 = kdev_minor_to_i915(kdev); -+ struct i915_gpu_state *gpu; -+ ssize_t ret; -+ -+ gpu = i915_first_error_state(i915); -+ if (IS_ERR(gpu)) { -+ ret = PTR_ERR(gpu); -+ } else if (gpu) { -+ ret = i915_gpu_state_copy_to_buffer(gpu, buf, off, count); -+ i915_gpu_state_put(gpu); -+ } else { -+ const char *str = "No error state collected\n"; -+ size_t len = strlen(str); -+ -+ ret = min_t(size_t, count, len - off); -+ memcpy(buf, str + off, ret); -+ } -+ -+ return ret; -+} -+ -+static ssize_t error_state_write(struct file *file, struct kobject *kobj, -+ struct bin_attribute *attr, char *buf, -+ loff_t off, size_t count) -+{ -+ struct device *kdev = kobj_to_dev(kobj); -+ struct drm_i915_private *dev_priv = kdev_minor_to_i915(kdev); -+ -+ DRM_DEBUG_DRIVER("Resetting error state\n"); -+ i915_reset_error_state(dev_priv); -+ -+ return count; -+} -+ -+static const struct bin_attribute error_state_attr = { -+ .attr.name = "error", -+ .attr.mode = S_IRUSR | S_IWUSR, -+ .size = 0, -+ .read = error_state_read, -+ .write = error_state_write, -+}; -+ -+static void i915_setup_error_capture(struct device *kdev) -+{ -+ if (sysfs_create_bin_file(&kdev->kobj, &error_state_attr)) -+ DRM_ERROR("error_state sysfs setup failed\n"); -+} -+ -+static void i915_teardown_error_capture(struct device *kdev) -+{ -+ sysfs_remove_bin_file(&kdev->kobj, &error_state_attr); -+} -+#else -+static void i915_setup_error_capture(struct device *kdev) {} -+static void i915_teardown_error_capture(struct device *kdev) {} -+#endif -+ -+void i915_setup_sysfs(struct drm_i915_private *dev_priv) -+{ -+ struct device *kdev = dev_priv->drm.primary->kdev; -+ int ret; -+ -+#ifdef CONFIG_PM -+ if (HAS_RC6(dev_priv)) { -+ ret = sysfs_merge_group(&kdev->kobj, -+ &rc6_attr_group); -+ if (ret) -+ DRM_ERROR("RC6 residency sysfs setup failed\n"); -+ } -+ if (HAS_RC6p(dev_priv)) { -+ ret = sysfs_merge_group(&kdev->kobj, -+ &rc6p_attr_group); -+ if (ret) -+ DRM_ERROR("RC6p residency sysfs setup failed\n"); -+ } -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ ret = sysfs_merge_group(&kdev->kobj, -+ &media_rc6_attr_group); -+ if (ret) -+ DRM_ERROR("Media RC6 residency sysfs setup failed\n"); -+ } -+#endif -+ if (HAS_L3_DPF(dev_priv)) { -+ ret = device_create_bin_file(kdev, &dpf_attrs); -+ if (ret) -+ DRM_ERROR("l3 parity sysfs setup failed\n"); -+ -+ if (NUM_L3_SLICES(dev_priv) > 1) { -+ ret = device_create_bin_file(kdev, -+ &dpf_attrs_1); -+ if (ret) -+ DRM_ERROR("l3 parity slice 1 setup failed\n"); -+ } -+ } -+ -+ ret = 0; -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ ret = sysfs_create_files(&kdev->kobj, vlv_attrs); -+ else if (INTEL_GEN(dev_priv) >= 6) -+ ret = sysfs_create_files(&kdev->kobj, gen6_attrs); -+ if (ret) -+ DRM_ERROR("RPS sysfs setup failed\n"); -+ -+ i915_setup_error_capture(kdev); -+} -+ -+void i915_teardown_sysfs(struct drm_i915_private *dev_priv) -+{ -+ struct device *kdev = dev_priv->drm.primary->kdev; -+ -+ i915_teardown_error_capture(kdev); -+ -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ sysfs_remove_files(&kdev->kobj, vlv_attrs); -+ else -+ sysfs_remove_files(&kdev->kobj, gen6_attrs); -+ device_remove_bin_file(kdev, &dpf_attrs_1); -+ device_remove_bin_file(kdev, &dpf_attrs); -+#ifdef CONFIG_PM -+ sysfs_unmerge_group(&kdev->kobj, &rc6_attr_group); -+ sysfs_unmerge_group(&kdev->kobj, &rc6p_attr_group); -+#endif -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_timeline.c b/drivers/gpu/drm/i915_legacy/i915_timeline.c -new file mode 100644 -index 000000000000..5fbea0892f33 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_timeline.c -@@ -0,0 +1,579 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2016-2018 Intel Corporation -+ */ -+ -+#include "i915_drv.h" -+ -+#include "i915_active.h" -+#include "i915_syncmap.h" -+#include "i915_timeline.h" -+ -+#define ptr_set_bit(ptr, bit) ((typeof(ptr))((unsigned long)(ptr) | BIT(bit))) -+#define ptr_test_bit(ptr, bit) ((unsigned long)(ptr) & BIT(bit)) -+ -+struct i915_timeline_hwsp { -+ struct i915_gt_timelines *gt; -+ struct list_head free_link; -+ struct i915_vma *vma; -+ u64 free_bitmap; -+}; -+ -+struct i915_timeline_cacheline { -+ struct i915_active active; -+ struct i915_timeline_hwsp *hwsp; -+ void *vaddr; -+#define CACHELINE_BITS 6 -+#define CACHELINE_FREE CACHELINE_BITS -+}; -+ -+static inline struct drm_i915_private * -+hwsp_to_i915(struct i915_timeline_hwsp *hwsp) -+{ -+ return container_of(hwsp->gt, struct drm_i915_private, gt.timelines); -+} -+ -+static struct i915_vma *__hwsp_alloc(struct drm_i915_private *i915) -+{ -+ struct drm_i915_gem_object *obj; -+ struct i915_vma *vma; -+ -+ obj = i915_gem_object_create_internal(i915, PAGE_SIZE); -+ if (IS_ERR(obj)) -+ return ERR_CAST(obj); -+ -+ i915_gem_object_set_cache_coherency(obj, I915_CACHE_LLC); -+ -+ vma = i915_vma_instance(obj, &i915->ggtt.vm, NULL); -+ if (IS_ERR(vma)) -+ i915_gem_object_put(obj); -+ -+ return vma; -+} -+ -+static struct i915_vma * -+hwsp_alloc(struct i915_timeline *timeline, unsigned int *cacheline) -+{ -+ struct drm_i915_private *i915 = timeline->i915; -+ struct i915_gt_timelines *gt = &i915->gt.timelines; -+ struct i915_timeline_hwsp *hwsp; -+ -+ BUILD_BUG_ON(BITS_PER_TYPE(u64) * CACHELINE_BYTES > PAGE_SIZE); -+ -+ spin_lock(>->hwsp_lock); -+ -+ /* hwsp_free_list only contains HWSP that have available cachelines */ -+ hwsp = list_first_entry_or_null(>->hwsp_free_list, -+ typeof(*hwsp), free_link); -+ if (!hwsp) { -+ struct i915_vma *vma; -+ -+ spin_unlock(>->hwsp_lock); -+ -+ hwsp = kmalloc(sizeof(*hwsp), GFP_KERNEL); -+ if (!hwsp) -+ return ERR_PTR(-ENOMEM); -+ -+ vma = __hwsp_alloc(i915); -+ if (IS_ERR(vma)) { -+ kfree(hwsp); -+ return vma; -+ } -+ -+ vma->private = hwsp; -+ hwsp->vma = vma; -+ hwsp->free_bitmap = ~0ull; -+ hwsp->gt = gt; -+ -+ spin_lock(>->hwsp_lock); -+ list_add(&hwsp->free_link, >->hwsp_free_list); -+ } -+ -+ GEM_BUG_ON(!hwsp->free_bitmap); -+ *cacheline = __ffs64(hwsp->free_bitmap); -+ hwsp->free_bitmap &= ~BIT_ULL(*cacheline); -+ if (!hwsp->free_bitmap) -+ list_del(&hwsp->free_link); -+ -+ spin_unlock(>->hwsp_lock); -+ -+ GEM_BUG_ON(hwsp->vma->private != hwsp); -+ return hwsp->vma; -+} -+ -+static void __idle_hwsp_free(struct i915_timeline_hwsp *hwsp, int cacheline) -+{ -+ struct i915_gt_timelines *gt = hwsp->gt; -+ -+ spin_lock(>->hwsp_lock); -+ -+ /* As a cacheline becomes available, publish the HWSP on the freelist */ -+ if (!hwsp->free_bitmap) -+ list_add_tail(&hwsp->free_link, >->hwsp_free_list); -+ -+ GEM_BUG_ON(cacheline >= BITS_PER_TYPE(hwsp->free_bitmap)); -+ hwsp->free_bitmap |= BIT_ULL(cacheline); -+ -+ /* And if no one is left using it, give the page back to the system */ -+ if (hwsp->free_bitmap == ~0ull) { -+ i915_vma_put(hwsp->vma); -+ list_del(&hwsp->free_link); -+ kfree(hwsp); -+ } -+ -+ spin_unlock(>->hwsp_lock); -+} -+ -+static void __idle_cacheline_free(struct i915_timeline_cacheline *cl) -+{ -+ GEM_BUG_ON(!i915_active_is_idle(&cl->active)); -+ -+ i915_gem_object_unpin_map(cl->hwsp->vma->obj); -+ i915_vma_put(cl->hwsp->vma); -+ __idle_hwsp_free(cl->hwsp, ptr_unmask_bits(cl->vaddr, CACHELINE_BITS)); -+ -+ i915_active_fini(&cl->active); -+ kfree(cl); -+} -+ -+static void __cacheline_retire(struct i915_active *active) -+{ -+ struct i915_timeline_cacheline *cl = -+ container_of(active, typeof(*cl), active); -+ -+ i915_vma_unpin(cl->hwsp->vma); -+ if (ptr_test_bit(cl->vaddr, CACHELINE_FREE)) -+ __idle_cacheline_free(cl); -+} -+ -+static struct i915_timeline_cacheline * -+cacheline_alloc(struct i915_timeline_hwsp *hwsp, unsigned int cacheline) -+{ -+ struct i915_timeline_cacheline *cl; -+ void *vaddr; -+ -+ GEM_BUG_ON(cacheline >= BIT(CACHELINE_BITS)); -+ -+ cl = kmalloc(sizeof(*cl), GFP_KERNEL); -+ if (!cl) -+ return ERR_PTR(-ENOMEM); -+ -+ vaddr = i915_gem_object_pin_map(hwsp->vma->obj, I915_MAP_WB); -+ if (IS_ERR(vaddr)) { -+ kfree(cl); -+ return ERR_CAST(vaddr); -+ } -+ -+ i915_vma_get(hwsp->vma); -+ cl->hwsp = hwsp; -+ cl->vaddr = page_pack_bits(vaddr, cacheline); -+ -+ i915_active_init(hwsp_to_i915(hwsp), &cl->active, __cacheline_retire); -+ -+ return cl; -+} -+ -+static void cacheline_acquire(struct i915_timeline_cacheline *cl) -+{ -+ if (cl && i915_active_acquire(&cl->active)) -+ __i915_vma_pin(cl->hwsp->vma); -+} -+ -+static void cacheline_release(struct i915_timeline_cacheline *cl) -+{ -+ if (cl) -+ i915_active_release(&cl->active); -+} -+ -+static void cacheline_free(struct i915_timeline_cacheline *cl) -+{ -+ GEM_BUG_ON(ptr_test_bit(cl->vaddr, CACHELINE_FREE)); -+ cl->vaddr = ptr_set_bit(cl->vaddr, CACHELINE_FREE); -+ -+ if (i915_active_is_idle(&cl->active)) -+ __idle_cacheline_free(cl); -+} -+ -+int i915_timeline_init(struct drm_i915_private *i915, -+ struct i915_timeline *timeline, -+ struct i915_vma *hwsp) -+{ -+ void *vaddr; -+ -+ /* -+ * Ideally we want a set of engines on a single leaf as we expect -+ * to mostly be tracking synchronisation between engines. It is not -+ * a huge issue if this is not the case, but we may want to mitigate -+ * any page crossing penalties if they become an issue. -+ * -+ * Called during early_init before we know how many engines there are. -+ */ -+ BUILD_BUG_ON(KSYNCMAP < I915_NUM_ENGINES); -+ -+ timeline->i915 = i915; -+ timeline->pin_count = 0; -+ timeline->has_initial_breadcrumb = !hwsp; -+ timeline->hwsp_cacheline = NULL; -+ -+ if (!hwsp) { -+ struct i915_timeline_cacheline *cl; -+ unsigned int cacheline; -+ -+ hwsp = hwsp_alloc(timeline, &cacheline); -+ if (IS_ERR(hwsp)) -+ return PTR_ERR(hwsp); -+ -+ cl = cacheline_alloc(hwsp->private, cacheline); -+ if (IS_ERR(cl)) { -+ __idle_hwsp_free(hwsp->private, cacheline); -+ return PTR_ERR(cl); -+ } -+ -+ timeline->hwsp_cacheline = cl; -+ timeline->hwsp_offset = cacheline * CACHELINE_BYTES; -+ -+ vaddr = page_mask_bits(cl->vaddr); -+ } else { -+ timeline->hwsp_offset = I915_GEM_HWS_SEQNO_ADDR; -+ -+ vaddr = i915_gem_object_pin_map(hwsp->obj, I915_MAP_WB); -+ if (IS_ERR(vaddr)) -+ return PTR_ERR(vaddr); -+ } -+ -+ timeline->hwsp_seqno = -+ memset(vaddr + timeline->hwsp_offset, 0, CACHELINE_BYTES); -+ -+ timeline->hwsp_ggtt = i915_vma_get(hwsp); -+ GEM_BUG_ON(timeline->hwsp_offset >= hwsp->size); -+ -+ timeline->fence_context = dma_fence_context_alloc(1); -+ -+ spin_lock_init(&timeline->lock); -+ mutex_init(&timeline->mutex); -+ -+ INIT_ACTIVE_REQUEST(&timeline->last_request); -+ INIT_LIST_HEAD(&timeline->requests); -+ -+ i915_syncmap_init(&timeline->sync); -+ -+ return 0; -+} -+ -+void i915_timelines_init(struct drm_i915_private *i915) -+{ -+ struct i915_gt_timelines *gt = &i915->gt.timelines; -+ -+ mutex_init(>->mutex); -+ INIT_LIST_HEAD(>->active_list); -+ -+ spin_lock_init(>->hwsp_lock); -+ INIT_LIST_HEAD(>->hwsp_free_list); -+ -+ /* via i915_gem_wait_for_idle() */ -+ i915_gem_shrinker_taints_mutex(i915, >->mutex); -+} -+ -+static void timeline_add_to_active(struct i915_timeline *tl) -+{ -+ struct i915_gt_timelines *gt = &tl->i915->gt.timelines; -+ -+ mutex_lock(>->mutex); -+ list_add(&tl->link, >->active_list); -+ mutex_unlock(>->mutex); -+} -+ -+static void timeline_remove_from_active(struct i915_timeline *tl) -+{ -+ struct i915_gt_timelines *gt = &tl->i915->gt.timelines; -+ -+ mutex_lock(>->mutex); -+ list_del(&tl->link); -+ mutex_unlock(>->mutex); -+} -+ -+/** -+ * i915_timelines_park - called when the driver idles -+ * @i915: the drm_i915_private device -+ * -+ * When the driver is completely idle, we know that all of our sync points -+ * have been signaled and our tracking is then entirely redundant. Any request -+ * to wait upon an older sync point will be completed instantly as we know -+ * the fence is signaled and therefore we will not even look them up in the -+ * sync point map. -+ */ -+void i915_timelines_park(struct drm_i915_private *i915) -+{ -+ struct i915_gt_timelines *gt = &i915->gt.timelines; -+ struct i915_timeline *timeline; -+ -+ mutex_lock(>->mutex); -+ list_for_each_entry(timeline, >->active_list, link) { -+ /* -+ * All known fences are completed so we can scrap -+ * the current sync point tracking and start afresh, -+ * any attempt to wait upon a previous sync point -+ * will be skipped as the fence was signaled. -+ */ -+ i915_syncmap_free(&timeline->sync); -+ } -+ mutex_unlock(>->mutex); -+} -+ -+void i915_timeline_fini(struct i915_timeline *timeline) -+{ -+ GEM_BUG_ON(timeline->pin_count); -+ GEM_BUG_ON(!list_empty(&timeline->requests)); -+ -+ i915_syncmap_free(&timeline->sync); -+ -+ if (timeline->hwsp_cacheline) -+ cacheline_free(timeline->hwsp_cacheline); -+ else -+ i915_gem_object_unpin_map(timeline->hwsp_ggtt->obj); -+ -+ i915_vma_put(timeline->hwsp_ggtt); -+} -+ -+struct i915_timeline * -+i915_timeline_create(struct drm_i915_private *i915, -+ struct i915_vma *global_hwsp) -+{ -+ struct i915_timeline *timeline; -+ int err; -+ -+ timeline = kzalloc(sizeof(*timeline), GFP_KERNEL); -+ if (!timeline) -+ return ERR_PTR(-ENOMEM); -+ -+ err = i915_timeline_init(i915, timeline, global_hwsp); -+ if (err) { -+ kfree(timeline); -+ return ERR_PTR(err); -+ } -+ -+ kref_init(&timeline->kref); -+ -+ return timeline; -+} -+ -+int i915_timeline_pin(struct i915_timeline *tl) -+{ -+ int err; -+ -+ if (tl->pin_count++) -+ return 0; -+ GEM_BUG_ON(!tl->pin_count); -+ -+ err = i915_vma_pin(tl->hwsp_ggtt, 0, 0, PIN_GLOBAL | PIN_HIGH); -+ if (err) -+ goto unpin; -+ -+ tl->hwsp_offset = -+ i915_ggtt_offset(tl->hwsp_ggtt) + -+ offset_in_page(tl->hwsp_offset); -+ -+ cacheline_acquire(tl->hwsp_cacheline); -+ timeline_add_to_active(tl); -+ -+ return 0; -+ -+unpin: -+ tl->pin_count = 0; -+ return err; -+} -+ -+static u32 timeline_advance(struct i915_timeline *tl) -+{ -+ GEM_BUG_ON(!tl->pin_count); -+ GEM_BUG_ON(tl->seqno & tl->has_initial_breadcrumb); -+ -+ return tl->seqno += 1 + tl->has_initial_breadcrumb; -+} -+ -+static void timeline_rollback(struct i915_timeline *tl) -+{ -+ tl->seqno -= 1 + tl->has_initial_breadcrumb; -+} -+ -+static noinline int -+__i915_timeline_get_seqno(struct i915_timeline *tl, -+ struct i915_request *rq, -+ u32 *seqno) -+{ -+ struct i915_timeline_cacheline *cl; -+ unsigned int cacheline; -+ struct i915_vma *vma; -+ void *vaddr; -+ int err; -+ -+ /* -+ * If there is an outstanding GPU reference to this cacheline, -+ * such as it being sampled by a HW semaphore on another timeline, -+ * we cannot wraparound our seqno value (the HW semaphore does -+ * a strict greater-than-or-equals compare, not i915_seqno_passed). -+ * So if the cacheline is still busy, we must detach ourselves -+ * from it and leave it inflight alongside its users. -+ * -+ * However, if nobody is watching and we can guarantee that nobody -+ * will, we could simply reuse the same cacheline. -+ * -+ * if (i915_active_request_is_signaled(&tl->last_request) && -+ * i915_active_is_signaled(&tl->hwsp_cacheline->active)) -+ * return 0; -+ * -+ * That seems unlikely for a busy timeline that needed to wrap in -+ * the first place, so just replace the cacheline. -+ */ -+ -+ vma = hwsp_alloc(tl, &cacheline); -+ if (IS_ERR(vma)) { -+ err = PTR_ERR(vma); -+ goto err_rollback; -+ } -+ -+ err = i915_vma_pin(vma, 0, 0, PIN_GLOBAL | PIN_HIGH); -+ if (err) { -+ __idle_hwsp_free(vma->private, cacheline); -+ goto err_rollback; -+ } -+ -+ cl = cacheline_alloc(vma->private, cacheline); -+ if (IS_ERR(cl)) { -+ err = PTR_ERR(cl); -+ __idle_hwsp_free(vma->private, cacheline); -+ goto err_unpin; -+ } -+ GEM_BUG_ON(cl->hwsp->vma != vma); -+ -+ /* -+ * Attach the old cacheline to the current request, so that we only -+ * free it after the current request is retired, which ensures that -+ * all writes into the cacheline from previous requests are complete. -+ */ -+ err = i915_active_ref(&tl->hwsp_cacheline->active, -+ tl->fence_context, rq); -+ if (err) -+ goto err_cacheline; -+ -+ cacheline_release(tl->hwsp_cacheline); /* ownership now xfered to rq */ -+ cacheline_free(tl->hwsp_cacheline); -+ -+ i915_vma_unpin(tl->hwsp_ggtt); /* binding kept alive by old cacheline */ -+ i915_vma_put(tl->hwsp_ggtt); -+ -+ tl->hwsp_ggtt = i915_vma_get(vma); -+ -+ vaddr = page_mask_bits(cl->vaddr); -+ tl->hwsp_offset = cacheline * CACHELINE_BYTES; -+ tl->hwsp_seqno = -+ memset(vaddr + tl->hwsp_offset, 0, CACHELINE_BYTES); -+ -+ tl->hwsp_offset += i915_ggtt_offset(vma); -+ -+ cacheline_acquire(cl); -+ tl->hwsp_cacheline = cl; -+ -+ *seqno = timeline_advance(tl); -+ GEM_BUG_ON(i915_seqno_passed(*tl->hwsp_seqno, *seqno)); -+ return 0; -+ -+err_cacheline: -+ cacheline_free(cl); -+err_unpin: -+ i915_vma_unpin(vma); -+err_rollback: -+ timeline_rollback(tl); -+ return err; -+} -+ -+int i915_timeline_get_seqno(struct i915_timeline *tl, -+ struct i915_request *rq, -+ u32 *seqno) -+{ -+ *seqno = timeline_advance(tl); -+ -+ /* Replace the HWSP on wraparound for HW semaphores */ -+ if (unlikely(!*seqno && tl->hwsp_cacheline)) -+ return __i915_timeline_get_seqno(tl, rq, seqno); -+ -+ return 0; -+} -+ -+static int cacheline_ref(struct i915_timeline_cacheline *cl, -+ struct i915_request *rq) -+{ -+ return i915_active_ref(&cl->active, rq->fence.context, rq); -+} -+ -+int i915_timeline_read_hwsp(struct i915_request *from, -+ struct i915_request *to, -+ u32 *hwsp) -+{ -+ struct i915_timeline_cacheline *cl = from->hwsp_cacheline; -+ struct i915_timeline *tl = from->timeline; -+ int err; -+ -+ GEM_BUG_ON(to->timeline == tl); -+ -+ mutex_lock_nested(&tl->mutex, SINGLE_DEPTH_NESTING); -+ err = i915_request_completed(from); -+ if (!err) -+ err = cacheline_ref(cl, to); -+ if (!err) { -+ if (likely(cl == tl->hwsp_cacheline)) { -+ *hwsp = tl->hwsp_offset; -+ } else { /* across a seqno wrap, recover the original offset */ -+ *hwsp = i915_ggtt_offset(cl->hwsp->vma) + -+ ptr_unmask_bits(cl->vaddr, CACHELINE_BITS) * -+ CACHELINE_BYTES; -+ } -+ } -+ mutex_unlock(&tl->mutex); -+ -+ return err; -+} -+ -+void i915_timeline_unpin(struct i915_timeline *tl) -+{ -+ GEM_BUG_ON(!tl->pin_count); -+ if (--tl->pin_count) -+ return; -+ -+ timeline_remove_from_active(tl); -+ cacheline_release(tl->hwsp_cacheline); -+ -+ /* -+ * Since this timeline is idle, all bariers upon which we were waiting -+ * must also be complete and so we can discard the last used barriers -+ * without loss of information. -+ */ -+ i915_syncmap_free(&tl->sync); -+ -+ __i915_vma_unpin(tl->hwsp_ggtt); -+} -+ -+void __i915_timeline_free(struct kref *kref) -+{ -+ struct i915_timeline *timeline = -+ container_of(kref, typeof(*timeline), kref); -+ -+ i915_timeline_fini(timeline); -+ kfree(timeline); -+} -+ -+void i915_timelines_fini(struct drm_i915_private *i915) -+{ -+ struct i915_gt_timelines *gt = &i915->gt.timelines; -+ -+ GEM_BUG_ON(!list_empty(>->active_list)); -+ GEM_BUG_ON(!list_empty(>->hwsp_free_list)); -+ -+ mutex_destroy(>->mutex); -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -+#include "selftests/mock_timeline.c" -+#include "selftests/i915_timeline.c" -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_timeline.h b/drivers/gpu/drm/i915_legacy/i915_timeline.h -new file mode 100644 -index 000000000000..27668a1a69a3 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_timeline.h -@@ -0,0 +1,113 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef I915_TIMELINE_H -+#define I915_TIMELINE_H -+ -+#include -+ -+#include "i915_active.h" -+#include "i915_syncmap.h" -+#include "i915_timeline_types.h" -+ -+int i915_timeline_init(struct drm_i915_private *i915, -+ struct i915_timeline *tl, -+ struct i915_vma *hwsp); -+void i915_timeline_fini(struct i915_timeline *tl); -+ -+static inline void -+i915_timeline_set_subclass(struct i915_timeline *timeline, -+ unsigned int subclass) -+{ -+ lockdep_set_subclass(&timeline->lock, subclass); -+ -+ /* -+ * Due to an interesting quirk in lockdep's internal debug tracking, -+ * after setting a subclass we must ensure the lock is used. Otherwise, -+ * nr_unused_locks is incremented once too often. -+ */ -+#ifdef CONFIG_DEBUG_LOCK_ALLOC -+ local_irq_disable(); -+ lock_map_acquire(&timeline->lock.dep_map); -+ lock_map_release(&timeline->lock.dep_map); -+ local_irq_enable(); -+#endif -+} -+ -+struct i915_timeline * -+i915_timeline_create(struct drm_i915_private *i915, -+ struct i915_vma *global_hwsp); -+ -+static inline struct i915_timeline * -+i915_timeline_get(struct i915_timeline *timeline) -+{ -+ kref_get(&timeline->kref); -+ return timeline; -+} -+ -+void __i915_timeline_free(struct kref *kref); -+static inline void i915_timeline_put(struct i915_timeline *timeline) -+{ -+ kref_put(&timeline->kref, __i915_timeline_free); -+} -+ -+static inline int __i915_timeline_sync_set(struct i915_timeline *tl, -+ u64 context, u32 seqno) -+{ -+ return i915_syncmap_set(&tl->sync, context, seqno); -+} -+ -+static inline int i915_timeline_sync_set(struct i915_timeline *tl, -+ const struct dma_fence *fence) -+{ -+ return __i915_timeline_sync_set(tl, fence->context, fence->seqno); -+} -+ -+static inline bool __i915_timeline_sync_is_later(struct i915_timeline *tl, -+ u64 context, u32 seqno) -+{ -+ return i915_syncmap_is_later(&tl->sync, context, seqno); -+} -+ -+static inline bool i915_timeline_sync_is_later(struct i915_timeline *tl, -+ const struct dma_fence *fence) -+{ -+ return __i915_timeline_sync_is_later(tl, fence->context, fence->seqno); -+} -+ -+int i915_timeline_pin(struct i915_timeline *tl); -+int i915_timeline_get_seqno(struct i915_timeline *tl, -+ struct i915_request *rq, -+ u32 *seqno); -+void i915_timeline_unpin(struct i915_timeline *tl); -+ -+int i915_timeline_read_hwsp(struct i915_request *from, -+ struct i915_request *until, -+ u32 *hwsp_offset); -+ -+void i915_timelines_init(struct drm_i915_private *i915); -+void i915_timelines_park(struct drm_i915_private *i915); -+void i915_timelines_fini(struct drm_i915_private *i915); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_timeline_types.h b/drivers/gpu/drm/i915_legacy/i915_timeline_types.h -new file mode 100644 -index 000000000000..5256a0b5c5f7 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_timeline_types.h -@@ -0,0 +1,70 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2016 Intel Corporation -+ */ -+ -+#ifndef __I915_TIMELINE_TYPES_H__ -+#define __I915_TIMELINE_TYPES_H__ -+ -+#include -+#include -+#include -+#include -+ -+#include "i915_active_types.h" -+ -+struct drm_i915_private; -+struct i915_vma; -+struct i915_timeline_cacheline; -+struct i915_syncmap; -+ -+struct i915_timeline { -+ u64 fence_context; -+ u32 seqno; -+ -+ spinlock_t lock; -+#define TIMELINE_CLIENT 0 /* default subclass */ -+#define TIMELINE_ENGINE 1 -+ struct mutex mutex; /* protects the flow of requests */ -+ -+ unsigned int pin_count; -+ const u32 *hwsp_seqno; -+ struct i915_vma *hwsp_ggtt; -+ u32 hwsp_offset; -+ -+ struct i915_timeline_cacheline *hwsp_cacheline; -+ -+ bool has_initial_breadcrumb; -+ -+ /** -+ * List of breadcrumbs associated with GPU requests currently -+ * outstanding. -+ */ -+ struct list_head requests; -+ -+ /* Contains an RCU guarded pointer to the last request. No reference is -+ * held to the request, users must carefully acquire a reference to -+ * the request using i915_active_request_get_request_rcu(), or hold the -+ * struct_mutex. -+ */ -+ struct i915_active_request last_request; -+ -+ /** -+ * We track the most recent seqno that we wait on in every context so -+ * that we only have to emit a new await and dependency on a more -+ * recent sync point. As the contexts may be executed out-of-order, we -+ * have to track each individually and can not rely on an absolute -+ * global_seqno. When we know that all tracked fences are completed -+ * (i.e. when the driver is idle), we know that the syncmap is -+ * redundant and we can discard it without loss of generality. -+ */ -+ struct i915_syncmap *sync; -+ -+ struct list_head link; -+ struct drm_i915_private *i915; -+ -+ struct kref kref; -+}; -+ -+#endif /* __I915_TIMELINE_TYPES_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_trace.h b/drivers/gpu/drm/i915_legacy/i915_trace.h -new file mode 100644 -index 000000000000..ca379eaa3537 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_trace.h -@@ -0,0 +1,1000 @@ -+/* SPDX-License-Identifier: GPL-2.0 */ -+#if !defined(_I915_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ) -+#define _I915_TRACE_H_ -+ -+#include -+#include -+#include -+ -+#include -+ -+#include "i915_drv.h" -+#include "intel_drv.h" -+#include "intel_ringbuffer.h" -+ -+#undef TRACE_SYSTEM -+#define TRACE_SYSTEM i915 -+#define TRACE_INCLUDE_FILE i915_trace -+ -+/* watermark/fifo updates */ -+ -+TRACE_EVENT(intel_pipe_enable, -+ TP_PROTO(struct drm_i915_private *dev_priv, enum pipe pipe), -+ TP_ARGS(dev_priv, pipe), -+ -+ TP_STRUCT__entry( -+ __array(u32, frame, 3) -+ __array(u32, scanline, 3) -+ __field(enum pipe, pipe) -+ ), -+ -+ TP_fast_assign( -+ enum pipe _pipe; -+ for_each_pipe(dev_priv, _pipe) { -+ __entry->frame[_pipe] = -+ dev_priv->drm.driver->get_vblank_counter(&dev_priv->drm, _pipe); -+ __entry->scanline[_pipe] = -+ intel_get_crtc_scanline(intel_get_crtc_for_pipe(dev_priv, _pipe)); -+ } -+ __entry->pipe = pipe; -+ ), -+ -+ TP_printk("pipe %c enable, pipe A: frame=%u, scanline=%u, pipe B: frame=%u, scanline=%u, pipe C: frame=%u, scanline=%u", -+ pipe_name(__entry->pipe), -+ __entry->frame[PIPE_A], __entry->scanline[PIPE_A], -+ __entry->frame[PIPE_B], __entry->scanline[PIPE_B], -+ __entry->frame[PIPE_C], __entry->scanline[PIPE_C]) -+); -+ -+TRACE_EVENT(intel_pipe_disable, -+ TP_PROTO(struct drm_i915_private *dev_priv, enum pipe pipe), -+ TP_ARGS(dev_priv, pipe), -+ -+ TP_STRUCT__entry( -+ __array(u32, frame, 3) -+ __array(u32, scanline, 3) -+ __field(enum pipe, pipe) -+ ), -+ -+ TP_fast_assign( -+ enum pipe _pipe; -+ for_each_pipe(dev_priv, _pipe) { -+ __entry->frame[_pipe] = -+ dev_priv->drm.driver->get_vblank_counter(&dev_priv->drm, _pipe); -+ __entry->scanline[_pipe] = -+ intel_get_crtc_scanline(intel_get_crtc_for_pipe(dev_priv, _pipe)); -+ } -+ __entry->pipe = pipe; -+ ), -+ -+ TP_printk("pipe %c disable, pipe A: frame=%u, scanline=%u, pipe B: frame=%u, scanline=%u, pipe C: frame=%u, scanline=%u", -+ pipe_name(__entry->pipe), -+ __entry->frame[PIPE_A], __entry->scanline[PIPE_A], -+ __entry->frame[PIPE_B], __entry->scanline[PIPE_B], -+ __entry->frame[PIPE_C], __entry->scanline[PIPE_C]) -+); -+ -+TRACE_EVENT(intel_pipe_crc, -+ TP_PROTO(struct intel_crtc *crtc, const u32 *crcs), -+ TP_ARGS(crtc, crcs), -+ -+ TP_STRUCT__entry( -+ __field(enum pipe, pipe) -+ __field(u32, frame) -+ __field(u32, scanline) -+ __array(u32, crcs, 5) -+ ), -+ -+ TP_fast_assign( -+ __entry->pipe = crtc->pipe; -+ __entry->frame = crtc->base.dev->driver->get_vblank_counter(crtc->base.dev, -+ crtc->pipe); -+ __entry->scanline = intel_get_crtc_scanline(crtc); -+ memcpy(__entry->crcs, crcs, sizeof(__entry->crcs)); -+ ), -+ -+ TP_printk("pipe %c, frame=%u, scanline=%u crc=%08x %08x %08x %08x %08x", -+ pipe_name(__entry->pipe), __entry->frame, __entry->scanline, -+ __entry->crcs[0], __entry->crcs[1], __entry->crcs[2], -+ __entry->crcs[3], __entry->crcs[4]) -+); -+ -+TRACE_EVENT(intel_cpu_fifo_underrun, -+ TP_PROTO(struct drm_i915_private *dev_priv, enum pipe pipe), -+ TP_ARGS(dev_priv, pipe), -+ -+ TP_STRUCT__entry( -+ __field(enum pipe, pipe) -+ __field(u32, frame) -+ __field(u32, scanline) -+ ), -+ -+ TP_fast_assign( -+ __entry->pipe = pipe; -+ __entry->frame = dev_priv->drm.driver->get_vblank_counter(&dev_priv->drm, pipe); -+ __entry->scanline = intel_get_crtc_scanline(intel_get_crtc_for_pipe(dev_priv, pipe)); -+ ), -+ -+ TP_printk("pipe %c, frame=%u, scanline=%u", -+ pipe_name(__entry->pipe), -+ __entry->frame, __entry->scanline) -+); -+ -+TRACE_EVENT(intel_pch_fifo_underrun, -+ TP_PROTO(struct drm_i915_private *dev_priv, enum pipe pch_transcoder), -+ TP_ARGS(dev_priv, pch_transcoder), -+ -+ TP_STRUCT__entry( -+ __field(enum pipe, pipe) -+ __field(u32, frame) -+ __field(u32, scanline) -+ ), -+ -+ TP_fast_assign( -+ enum pipe pipe = pch_transcoder; -+ __entry->pipe = pipe; -+ __entry->frame = dev_priv->drm.driver->get_vblank_counter(&dev_priv->drm, pipe); -+ __entry->scanline = intel_get_crtc_scanline(intel_get_crtc_for_pipe(dev_priv, pipe)); -+ ), -+ -+ TP_printk("pch transcoder %c, frame=%u, scanline=%u", -+ pipe_name(__entry->pipe), -+ __entry->frame, __entry->scanline) -+); -+ -+TRACE_EVENT(intel_memory_cxsr, -+ TP_PROTO(struct drm_i915_private *dev_priv, bool old, bool new), -+ TP_ARGS(dev_priv, old, new), -+ -+ TP_STRUCT__entry( -+ __array(u32, frame, 3) -+ __array(u32, scanline, 3) -+ __field(bool, old) -+ __field(bool, new) -+ ), -+ -+ TP_fast_assign( -+ enum pipe pipe; -+ for_each_pipe(dev_priv, pipe) { -+ __entry->frame[pipe] = -+ dev_priv->drm.driver->get_vblank_counter(&dev_priv->drm, pipe); -+ __entry->scanline[pipe] = -+ intel_get_crtc_scanline(intel_get_crtc_for_pipe(dev_priv, pipe)); -+ } -+ __entry->old = old; -+ __entry->new = new; -+ ), -+ -+ TP_printk("%s->%s, pipe A: frame=%u, scanline=%u, pipe B: frame=%u, scanline=%u, pipe C: frame=%u, scanline=%u", -+ onoff(__entry->old), onoff(__entry->new), -+ __entry->frame[PIPE_A], __entry->scanline[PIPE_A], -+ __entry->frame[PIPE_B], __entry->scanline[PIPE_B], -+ __entry->frame[PIPE_C], __entry->scanline[PIPE_C]) -+); -+ -+TRACE_EVENT(g4x_wm, -+ TP_PROTO(struct intel_crtc *crtc, const struct g4x_wm_values *wm), -+ TP_ARGS(crtc, wm), -+ -+ TP_STRUCT__entry( -+ __field(enum pipe, pipe) -+ __field(u32, frame) -+ __field(u32, scanline) -+ __field(u16, primary) -+ __field(u16, sprite) -+ __field(u16, cursor) -+ __field(u16, sr_plane) -+ __field(u16, sr_cursor) -+ __field(u16, sr_fbc) -+ __field(u16, hpll_plane) -+ __field(u16, hpll_cursor) -+ __field(u16, hpll_fbc) -+ __field(bool, cxsr) -+ __field(bool, hpll) -+ __field(bool, fbc) -+ ), -+ -+ TP_fast_assign( -+ __entry->pipe = crtc->pipe; -+ __entry->frame = crtc->base.dev->driver->get_vblank_counter(crtc->base.dev, -+ crtc->pipe); -+ __entry->scanline = intel_get_crtc_scanline(crtc); -+ __entry->primary = wm->pipe[crtc->pipe].plane[PLANE_PRIMARY]; -+ __entry->sprite = wm->pipe[crtc->pipe].plane[PLANE_SPRITE0]; -+ __entry->cursor = wm->pipe[crtc->pipe].plane[PLANE_CURSOR]; -+ __entry->sr_plane = wm->sr.plane; -+ __entry->sr_cursor = wm->sr.cursor; -+ __entry->sr_fbc = wm->sr.fbc; -+ __entry->hpll_plane = wm->hpll.plane; -+ __entry->hpll_cursor = wm->hpll.cursor; -+ __entry->hpll_fbc = wm->hpll.fbc; -+ __entry->cxsr = wm->cxsr; -+ __entry->hpll = wm->hpll_en; -+ __entry->fbc = wm->fbc_en; -+ ), -+ -+ TP_printk("pipe %c, frame=%u, scanline=%u, wm %d/%d/%d, sr %s/%d/%d/%d, hpll %s/%d/%d/%d, fbc %s", -+ pipe_name(__entry->pipe), __entry->frame, __entry->scanline, -+ __entry->primary, __entry->sprite, __entry->cursor, -+ yesno(__entry->cxsr), __entry->sr_plane, __entry->sr_cursor, __entry->sr_fbc, -+ yesno(__entry->hpll), __entry->hpll_plane, __entry->hpll_cursor, __entry->hpll_fbc, -+ yesno(__entry->fbc)) -+); -+ -+TRACE_EVENT(vlv_wm, -+ TP_PROTO(struct intel_crtc *crtc, const struct vlv_wm_values *wm), -+ TP_ARGS(crtc, wm), -+ -+ TP_STRUCT__entry( -+ __field(enum pipe, pipe) -+ __field(u32, frame) -+ __field(u32, scanline) -+ __field(u32, level) -+ __field(u32, cxsr) -+ __field(u32, primary) -+ __field(u32, sprite0) -+ __field(u32, sprite1) -+ __field(u32, cursor) -+ __field(u32, sr_plane) -+ __field(u32, sr_cursor) -+ ), -+ -+ TP_fast_assign( -+ __entry->pipe = crtc->pipe; -+ __entry->frame = crtc->base.dev->driver->get_vblank_counter(crtc->base.dev, -+ crtc->pipe); -+ __entry->scanline = intel_get_crtc_scanline(crtc); -+ __entry->level = wm->level; -+ __entry->cxsr = wm->cxsr; -+ __entry->primary = wm->pipe[crtc->pipe].plane[PLANE_PRIMARY]; -+ __entry->sprite0 = wm->pipe[crtc->pipe].plane[PLANE_SPRITE0]; -+ __entry->sprite1 = wm->pipe[crtc->pipe].plane[PLANE_SPRITE1]; -+ __entry->cursor = wm->pipe[crtc->pipe].plane[PLANE_CURSOR]; -+ __entry->sr_plane = wm->sr.plane; -+ __entry->sr_cursor = wm->sr.cursor; -+ ), -+ -+ TP_printk("pipe %c, frame=%u, scanline=%u, level=%d, cxsr=%d, wm %d/%d/%d/%d, sr %d/%d", -+ pipe_name(__entry->pipe), __entry->frame, -+ __entry->scanline, __entry->level, __entry->cxsr, -+ __entry->primary, __entry->sprite0, __entry->sprite1, __entry->cursor, -+ __entry->sr_plane, __entry->sr_cursor) -+); -+ -+TRACE_EVENT(vlv_fifo_size, -+ TP_PROTO(struct intel_crtc *crtc, u32 sprite0_start, u32 sprite1_start, u32 fifo_size), -+ TP_ARGS(crtc, sprite0_start, sprite1_start, fifo_size), -+ -+ TP_STRUCT__entry( -+ __field(enum pipe, pipe) -+ __field(u32, frame) -+ __field(u32, scanline) -+ __field(u32, sprite0_start) -+ __field(u32, sprite1_start) -+ __field(u32, fifo_size) -+ ), -+ -+ TP_fast_assign( -+ __entry->pipe = crtc->pipe; -+ __entry->frame = crtc->base.dev->driver->get_vblank_counter(crtc->base.dev, -+ crtc->pipe); -+ __entry->scanline = intel_get_crtc_scanline(crtc); -+ __entry->sprite0_start = sprite0_start; -+ __entry->sprite1_start = sprite1_start; -+ __entry->fifo_size = fifo_size; -+ ), -+ -+ TP_printk("pipe %c, frame=%u, scanline=%u, %d/%d/%d", -+ pipe_name(__entry->pipe), __entry->frame, -+ __entry->scanline, __entry->sprite0_start, -+ __entry->sprite1_start, __entry->fifo_size) -+); -+ -+/* plane updates */ -+ -+TRACE_EVENT(intel_update_plane, -+ TP_PROTO(struct drm_plane *plane, struct intel_crtc *crtc), -+ TP_ARGS(plane, crtc), -+ -+ TP_STRUCT__entry( -+ __field(enum pipe, pipe) -+ __field(const char *, name) -+ __field(u32, frame) -+ __field(u32, scanline) -+ __array(int, src, 4) -+ __array(int, dst, 4) -+ ), -+ -+ TP_fast_assign( -+ __entry->pipe = crtc->pipe; -+ __entry->name = plane->name; -+ __entry->frame = crtc->base.dev->driver->get_vblank_counter(crtc->base.dev, -+ crtc->pipe); -+ __entry->scanline = intel_get_crtc_scanline(crtc); -+ memcpy(__entry->src, &plane->state->src, sizeof(__entry->src)); -+ memcpy(__entry->dst, &plane->state->dst, sizeof(__entry->dst)); -+ ), -+ -+ TP_printk("pipe %c, plane %s, frame=%u, scanline=%u, " DRM_RECT_FP_FMT " -> " DRM_RECT_FMT, -+ pipe_name(__entry->pipe), __entry->name, -+ __entry->frame, __entry->scanline, -+ DRM_RECT_FP_ARG((const struct drm_rect *)__entry->src), -+ DRM_RECT_ARG((const struct drm_rect *)__entry->dst)) -+); -+ -+TRACE_EVENT(intel_disable_plane, -+ TP_PROTO(struct drm_plane *plane, struct intel_crtc *crtc), -+ TP_ARGS(plane, crtc), -+ -+ TP_STRUCT__entry( -+ __field(enum pipe, pipe) -+ __field(const char *, name) -+ __field(u32, frame) -+ __field(u32, scanline) -+ ), -+ -+ TP_fast_assign( -+ __entry->pipe = crtc->pipe; -+ __entry->name = plane->name; -+ __entry->frame = crtc->base.dev->driver->get_vblank_counter(crtc->base.dev, -+ crtc->pipe); -+ __entry->scanline = intel_get_crtc_scanline(crtc); -+ ), -+ -+ TP_printk("pipe %c, plane %s, frame=%u, scanline=%u", -+ pipe_name(__entry->pipe), __entry->name, -+ __entry->frame, __entry->scanline) -+); -+ -+/* pipe updates */ -+ -+TRACE_EVENT(i915_pipe_update_start, -+ TP_PROTO(struct intel_crtc *crtc), -+ TP_ARGS(crtc), -+ -+ TP_STRUCT__entry( -+ __field(enum pipe, pipe) -+ __field(u32, frame) -+ __field(u32, scanline) -+ __field(u32, min) -+ __field(u32, max) -+ ), -+ -+ TP_fast_assign( -+ __entry->pipe = crtc->pipe; -+ __entry->frame = crtc->base.dev->driver->get_vblank_counter(crtc->base.dev, -+ crtc->pipe); -+ __entry->scanline = intel_get_crtc_scanline(crtc); -+ __entry->min = crtc->debug.min_vbl; -+ __entry->max = crtc->debug.max_vbl; -+ ), -+ -+ TP_printk("pipe %c, frame=%u, scanline=%u, min=%u, max=%u", -+ pipe_name(__entry->pipe), __entry->frame, -+ __entry->scanline, __entry->min, __entry->max) -+); -+ -+TRACE_EVENT(i915_pipe_update_vblank_evaded, -+ TP_PROTO(struct intel_crtc *crtc), -+ TP_ARGS(crtc), -+ -+ TP_STRUCT__entry( -+ __field(enum pipe, pipe) -+ __field(u32, frame) -+ __field(u32, scanline) -+ __field(u32, min) -+ __field(u32, max) -+ ), -+ -+ TP_fast_assign( -+ __entry->pipe = crtc->pipe; -+ __entry->frame = crtc->debug.start_vbl_count; -+ __entry->scanline = crtc->debug.scanline_start; -+ __entry->min = crtc->debug.min_vbl; -+ __entry->max = crtc->debug.max_vbl; -+ ), -+ -+ TP_printk("pipe %c, frame=%u, scanline=%u, min=%u, max=%u", -+ pipe_name(__entry->pipe), __entry->frame, -+ __entry->scanline, __entry->min, __entry->max) -+); -+ -+TRACE_EVENT(i915_pipe_update_end, -+ TP_PROTO(struct intel_crtc *crtc, u32 frame, int scanline_end), -+ TP_ARGS(crtc, frame, scanline_end), -+ -+ TP_STRUCT__entry( -+ __field(enum pipe, pipe) -+ __field(u32, frame) -+ __field(u32, scanline) -+ ), -+ -+ TP_fast_assign( -+ __entry->pipe = crtc->pipe; -+ __entry->frame = frame; -+ __entry->scanline = scanline_end; -+ ), -+ -+ TP_printk("pipe %c, frame=%u, scanline=%u", -+ pipe_name(__entry->pipe), __entry->frame, -+ __entry->scanline) -+); -+ -+/* object tracking */ -+ -+TRACE_EVENT(i915_gem_object_create, -+ TP_PROTO(struct drm_i915_gem_object *obj), -+ TP_ARGS(obj), -+ -+ TP_STRUCT__entry( -+ __field(struct drm_i915_gem_object *, obj) -+ __field(u64, size) -+ ), -+ -+ TP_fast_assign( -+ __entry->obj = obj; -+ __entry->size = obj->base.size; -+ ), -+ -+ TP_printk("obj=%p, size=0x%llx", __entry->obj, __entry->size) -+); -+ -+TRACE_EVENT(i915_gem_shrink, -+ TP_PROTO(struct drm_i915_private *i915, unsigned long target, unsigned flags), -+ TP_ARGS(i915, target, flags), -+ -+ TP_STRUCT__entry( -+ __field(int, dev) -+ __field(unsigned long, target) -+ __field(unsigned, flags) -+ ), -+ -+ TP_fast_assign( -+ __entry->dev = i915->drm.primary->index; -+ __entry->target = target; -+ __entry->flags = flags; -+ ), -+ -+ TP_printk("dev=%d, target=%lu, flags=%x", -+ __entry->dev, __entry->target, __entry->flags) -+); -+ -+TRACE_EVENT(i915_vma_bind, -+ TP_PROTO(struct i915_vma *vma, unsigned flags), -+ TP_ARGS(vma, flags), -+ -+ TP_STRUCT__entry( -+ __field(struct drm_i915_gem_object *, obj) -+ __field(struct i915_address_space *, vm) -+ __field(u64, offset) -+ __field(u64, size) -+ __field(unsigned, flags) -+ ), -+ -+ TP_fast_assign( -+ __entry->obj = vma->obj; -+ __entry->vm = vma->vm; -+ __entry->offset = vma->node.start; -+ __entry->size = vma->node.size; -+ __entry->flags = flags; -+ ), -+ -+ TP_printk("obj=%p, offset=0x%016llx size=0x%llx%s vm=%p", -+ __entry->obj, __entry->offset, __entry->size, -+ __entry->flags & PIN_MAPPABLE ? ", mappable" : "", -+ __entry->vm) -+); -+ -+TRACE_EVENT(i915_vma_unbind, -+ TP_PROTO(struct i915_vma *vma), -+ TP_ARGS(vma), -+ -+ TP_STRUCT__entry( -+ __field(struct drm_i915_gem_object *, obj) -+ __field(struct i915_address_space *, vm) -+ __field(u64, offset) -+ __field(u64, size) -+ ), -+ -+ TP_fast_assign( -+ __entry->obj = vma->obj; -+ __entry->vm = vma->vm; -+ __entry->offset = vma->node.start; -+ __entry->size = vma->node.size; -+ ), -+ -+ TP_printk("obj=%p, offset=0x%016llx size=0x%llx vm=%p", -+ __entry->obj, __entry->offset, __entry->size, __entry->vm) -+); -+ -+TRACE_EVENT(i915_gem_object_pwrite, -+ TP_PROTO(struct drm_i915_gem_object *obj, u64 offset, u64 len), -+ TP_ARGS(obj, offset, len), -+ -+ TP_STRUCT__entry( -+ __field(struct drm_i915_gem_object *, obj) -+ __field(u64, offset) -+ __field(u64, len) -+ ), -+ -+ TP_fast_assign( -+ __entry->obj = obj; -+ __entry->offset = offset; -+ __entry->len = len; -+ ), -+ -+ TP_printk("obj=%p, offset=0x%llx, len=0x%llx", -+ __entry->obj, __entry->offset, __entry->len) -+); -+ -+TRACE_EVENT(i915_gem_object_pread, -+ TP_PROTO(struct drm_i915_gem_object *obj, u64 offset, u64 len), -+ TP_ARGS(obj, offset, len), -+ -+ TP_STRUCT__entry( -+ __field(struct drm_i915_gem_object *, obj) -+ __field(u64, offset) -+ __field(u64, len) -+ ), -+ -+ TP_fast_assign( -+ __entry->obj = obj; -+ __entry->offset = offset; -+ __entry->len = len; -+ ), -+ -+ TP_printk("obj=%p, offset=0x%llx, len=0x%llx", -+ __entry->obj, __entry->offset, __entry->len) -+); -+ -+TRACE_EVENT(i915_gem_object_fault, -+ TP_PROTO(struct drm_i915_gem_object *obj, u64 index, bool gtt, bool write), -+ TP_ARGS(obj, index, gtt, write), -+ -+ TP_STRUCT__entry( -+ __field(struct drm_i915_gem_object *, obj) -+ __field(u64, index) -+ __field(bool, gtt) -+ __field(bool, write) -+ ), -+ -+ TP_fast_assign( -+ __entry->obj = obj; -+ __entry->index = index; -+ __entry->gtt = gtt; -+ __entry->write = write; -+ ), -+ -+ TP_printk("obj=%p, %s index=%llu %s", -+ __entry->obj, -+ __entry->gtt ? "GTT" : "CPU", -+ __entry->index, -+ __entry->write ? ", writable" : "") -+); -+ -+DECLARE_EVENT_CLASS(i915_gem_object, -+ TP_PROTO(struct drm_i915_gem_object *obj), -+ TP_ARGS(obj), -+ -+ TP_STRUCT__entry( -+ __field(struct drm_i915_gem_object *, obj) -+ ), -+ -+ TP_fast_assign( -+ __entry->obj = obj; -+ ), -+ -+ TP_printk("obj=%p", __entry->obj) -+); -+ -+DEFINE_EVENT(i915_gem_object, i915_gem_object_clflush, -+ TP_PROTO(struct drm_i915_gem_object *obj), -+ TP_ARGS(obj) -+); -+ -+DEFINE_EVENT(i915_gem_object, i915_gem_object_destroy, -+ TP_PROTO(struct drm_i915_gem_object *obj), -+ TP_ARGS(obj) -+); -+ -+TRACE_EVENT(i915_gem_evict, -+ TP_PROTO(struct i915_address_space *vm, u64 size, u64 align, unsigned int flags), -+ TP_ARGS(vm, size, align, flags), -+ -+ TP_STRUCT__entry( -+ __field(u32, dev) -+ __field(struct i915_address_space *, vm) -+ __field(u64, size) -+ __field(u64, align) -+ __field(unsigned int, flags) -+ ), -+ -+ TP_fast_assign( -+ __entry->dev = vm->i915->drm.primary->index; -+ __entry->vm = vm; -+ __entry->size = size; -+ __entry->align = align; -+ __entry->flags = flags; -+ ), -+ -+ TP_printk("dev=%d, vm=%p, size=0x%llx, align=0x%llx %s", -+ __entry->dev, __entry->vm, __entry->size, __entry->align, -+ __entry->flags & PIN_MAPPABLE ? ", mappable" : "") -+); -+ -+TRACE_EVENT(i915_gem_evict_node, -+ TP_PROTO(struct i915_address_space *vm, struct drm_mm_node *node, unsigned int flags), -+ TP_ARGS(vm, node, flags), -+ -+ TP_STRUCT__entry( -+ __field(u32, dev) -+ __field(struct i915_address_space *, vm) -+ __field(u64, start) -+ __field(u64, size) -+ __field(unsigned long, color) -+ __field(unsigned int, flags) -+ ), -+ -+ TP_fast_assign( -+ __entry->dev = vm->i915->drm.primary->index; -+ __entry->vm = vm; -+ __entry->start = node->start; -+ __entry->size = node->size; -+ __entry->color = node->color; -+ __entry->flags = flags; -+ ), -+ -+ TP_printk("dev=%d, vm=%p, start=0x%llx size=0x%llx, color=0x%lx, flags=%x", -+ __entry->dev, __entry->vm, -+ __entry->start, __entry->size, -+ __entry->color, __entry->flags) -+); -+ -+TRACE_EVENT(i915_gem_evict_vm, -+ TP_PROTO(struct i915_address_space *vm), -+ TP_ARGS(vm), -+ -+ TP_STRUCT__entry( -+ __field(u32, dev) -+ __field(struct i915_address_space *, vm) -+ ), -+ -+ TP_fast_assign( -+ __entry->dev = vm->i915->drm.primary->index; -+ __entry->vm = vm; -+ ), -+ -+ TP_printk("dev=%d, vm=%p", __entry->dev, __entry->vm) -+); -+ -+TRACE_EVENT(i915_request_queue, -+ TP_PROTO(struct i915_request *rq, u32 flags), -+ TP_ARGS(rq, flags), -+ -+ TP_STRUCT__entry( -+ __field(u32, dev) -+ __field(u32, hw_id) -+ __field(u64, ctx) -+ __field(u16, class) -+ __field(u16, instance) -+ __field(u32, seqno) -+ __field(u32, flags) -+ ), -+ -+ TP_fast_assign( -+ __entry->dev = rq->i915->drm.primary->index; -+ __entry->hw_id = rq->gem_context->hw_id; -+ __entry->class = rq->engine->uabi_class; -+ __entry->instance = rq->engine->instance; -+ __entry->ctx = rq->fence.context; -+ __entry->seqno = rq->fence.seqno; -+ __entry->flags = flags; -+ ), -+ -+ TP_printk("dev=%u, engine=%u:%u, hw_id=%u, ctx=%llu, seqno=%u, flags=0x%x", -+ __entry->dev, __entry->class, __entry->instance, -+ __entry->hw_id, __entry->ctx, __entry->seqno, -+ __entry->flags) -+); -+ -+DECLARE_EVENT_CLASS(i915_request, -+ TP_PROTO(struct i915_request *rq), -+ TP_ARGS(rq), -+ -+ TP_STRUCT__entry( -+ __field(u32, dev) -+ __field(u32, hw_id) -+ __field(u64, ctx) -+ __field(u16, class) -+ __field(u16, instance) -+ __field(u32, seqno) -+ ), -+ -+ TP_fast_assign( -+ __entry->dev = rq->i915->drm.primary->index; -+ __entry->hw_id = rq->gem_context->hw_id; -+ __entry->class = rq->engine->uabi_class; -+ __entry->instance = rq->engine->instance; -+ __entry->ctx = rq->fence.context; -+ __entry->seqno = rq->fence.seqno; -+ ), -+ -+ TP_printk("dev=%u, engine=%u:%u, hw_id=%u, ctx=%llu, seqno=%u", -+ __entry->dev, __entry->class, __entry->instance, -+ __entry->hw_id, __entry->ctx, __entry->seqno) -+); -+ -+DEFINE_EVENT(i915_request, i915_request_add, -+ TP_PROTO(struct i915_request *rq), -+ TP_ARGS(rq) -+); -+ -+#if defined(CONFIG_DRM_I915_LOW_LEVEL_TRACEPOINTS) -+DEFINE_EVENT(i915_request, i915_request_submit, -+ TP_PROTO(struct i915_request *rq), -+ TP_ARGS(rq) -+); -+ -+DEFINE_EVENT(i915_request, i915_request_execute, -+ TP_PROTO(struct i915_request *rq), -+ TP_ARGS(rq) -+); -+ -+TRACE_EVENT(i915_request_in, -+ TP_PROTO(struct i915_request *rq, unsigned int port), -+ TP_ARGS(rq, port), -+ -+ TP_STRUCT__entry( -+ __field(u32, dev) -+ __field(u32, hw_id) -+ __field(u64, ctx) -+ __field(u16, class) -+ __field(u16, instance) -+ __field(u32, seqno) -+ __field(u32, port) -+ __field(u32, prio) -+ ), -+ -+ TP_fast_assign( -+ __entry->dev = rq->i915->drm.primary->index; -+ __entry->hw_id = rq->gem_context->hw_id; -+ __entry->class = rq->engine->uabi_class; -+ __entry->instance = rq->engine->instance; -+ __entry->ctx = rq->fence.context; -+ __entry->seqno = rq->fence.seqno; -+ __entry->prio = rq->sched.attr.priority; -+ __entry->port = port; -+ ), -+ -+ TP_printk("dev=%u, engine=%u:%u, hw_id=%u, ctx=%llu, seqno=%u, prio=%u, port=%u", -+ __entry->dev, __entry->class, __entry->instance, -+ __entry->hw_id, __entry->ctx, __entry->seqno, -+ __entry->prio, __entry->port) -+); -+ -+TRACE_EVENT(i915_request_out, -+ TP_PROTO(struct i915_request *rq), -+ TP_ARGS(rq), -+ -+ TP_STRUCT__entry( -+ __field(u32, dev) -+ __field(u32, hw_id) -+ __field(u64, ctx) -+ __field(u16, class) -+ __field(u16, instance) -+ __field(u32, seqno) -+ __field(u32, completed) -+ ), -+ -+ TP_fast_assign( -+ __entry->dev = rq->i915->drm.primary->index; -+ __entry->hw_id = rq->gem_context->hw_id; -+ __entry->class = rq->engine->uabi_class; -+ __entry->instance = rq->engine->instance; -+ __entry->ctx = rq->fence.context; -+ __entry->seqno = rq->fence.seqno; -+ __entry->completed = i915_request_completed(rq); -+ ), -+ -+ TP_printk("dev=%u, engine=%u:%u, hw_id=%u, ctx=%llu, seqno=%u, completed?=%u", -+ __entry->dev, __entry->class, __entry->instance, -+ __entry->hw_id, __entry->ctx, __entry->seqno, -+ __entry->completed) -+); -+ -+#else -+#if !defined(TRACE_HEADER_MULTI_READ) -+static inline void -+trace_i915_request_submit(struct i915_request *rq) -+{ -+} -+ -+static inline void -+trace_i915_request_execute(struct i915_request *rq) -+{ -+} -+ -+static inline void -+trace_i915_request_in(struct i915_request *rq, unsigned int port) -+{ -+} -+ -+static inline void -+trace_i915_request_out(struct i915_request *rq) -+{ -+} -+#endif -+#endif -+ -+DEFINE_EVENT(i915_request, i915_request_retire, -+ TP_PROTO(struct i915_request *rq), -+ TP_ARGS(rq) -+); -+ -+TRACE_EVENT(i915_request_wait_begin, -+ TP_PROTO(struct i915_request *rq, unsigned int flags), -+ TP_ARGS(rq, flags), -+ -+ TP_STRUCT__entry( -+ __field(u32, dev) -+ __field(u32, hw_id) -+ __field(u64, ctx) -+ __field(u16, class) -+ __field(u16, instance) -+ __field(u32, seqno) -+ __field(unsigned int, flags) -+ ), -+ -+ /* NB: the blocking information is racy since mutex_is_locked -+ * doesn't check that the current thread holds the lock. The only -+ * other option would be to pass the boolean information of whether -+ * or not the class was blocking down through the stack which is -+ * less desirable. -+ */ -+ TP_fast_assign( -+ __entry->dev = rq->i915->drm.primary->index; -+ __entry->hw_id = rq->gem_context->hw_id; -+ __entry->class = rq->engine->uabi_class; -+ __entry->instance = rq->engine->instance; -+ __entry->ctx = rq->fence.context; -+ __entry->seqno = rq->fence.seqno; -+ __entry->flags = flags; -+ ), -+ -+ TP_printk("dev=%u, engine=%u:%u, hw_id=%u, ctx=%llu, seqno=%u, blocking=%u, flags=0x%x", -+ __entry->dev, __entry->class, __entry->instance, -+ __entry->hw_id, __entry->ctx, __entry->seqno, -+ !!(__entry->flags & I915_WAIT_LOCKED), -+ __entry->flags) -+); -+ -+DEFINE_EVENT(i915_request, i915_request_wait_end, -+ TP_PROTO(struct i915_request *rq), -+ TP_ARGS(rq) -+); -+ -+TRACE_EVENT_CONDITION(i915_reg_rw, -+ TP_PROTO(bool write, i915_reg_t reg, u64 val, int len, bool trace), -+ -+ TP_ARGS(write, reg, val, len, trace), -+ -+ TP_CONDITION(trace), -+ -+ TP_STRUCT__entry( -+ __field(u64, val) -+ __field(u32, reg) -+ __field(u16, write) -+ __field(u16, len) -+ ), -+ -+ TP_fast_assign( -+ __entry->val = (u64)val; -+ __entry->reg = i915_mmio_reg_offset(reg); -+ __entry->write = write; -+ __entry->len = len; -+ ), -+ -+ TP_printk("%s reg=0x%x, len=%d, val=(0x%x, 0x%x)", -+ __entry->write ? "write" : "read", -+ __entry->reg, __entry->len, -+ (u32)(__entry->val & 0xffffffff), -+ (u32)(__entry->val >> 32)) -+); -+ -+TRACE_EVENT(intel_gpu_freq_change, -+ TP_PROTO(u32 freq), -+ TP_ARGS(freq), -+ -+ TP_STRUCT__entry( -+ __field(u32, freq) -+ ), -+ -+ TP_fast_assign( -+ __entry->freq = freq; -+ ), -+ -+ TP_printk("new_freq=%u", __entry->freq) -+); -+ -+/** -+ * DOC: i915_ppgtt_create and i915_ppgtt_release tracepoints -+ * -+ * With full ppgtt enabled each process using drm will allocate at least one -+ * translation table. With these traces it is possible to keep track of the -+ * allocation and of the lifetime of the tables; this can be used during -+ * testing/debug to verify that we are not leaking ppgtts. -+ * These traces identify the ppgtt through the vm pointer, which is also printed -+ * by the i915_vma_bind and i915_vma_unbind tracepoints. -+ */ -+DECLARE_EVENT_CLASS(i915_ppgtt, -+ TP_PROTO(struct i915_address_space *vm), -+ TP_ARGS(vm), -+ -+ TP_STRUCT__entry( -+ __field(struct i915_address_space *, vm) -+ __field(u32, dev) -+ ), -+ -+ TP_fast_assign( -+ __entry->vm = vm; -+ __entry->dev = vm->i915->drm.primary->index; -+ ), -+ -+ TP_printk("dev=%u, vm=%p", __entry->dev, __entry->vm) -+) -+ -+DEFINE_EVENT(i915_ppgtt, i915_ppgtt_create, -+ TP_PROTO(struct i915_address_space *vm), -+ TP_ARGS(vm) -+); -+ -+DEFINE_EVENT(i915_ppgtt, i915_ppgtt_release, -+ TP_PROTO(struct i915_address_space *vm), -+ TP_ARGS(vm) -+); -+ -+/** -+ * DOC: i915_context_create and i915_context_free tracepoints -+ * -+ * These tracepoints are used to track creation and deletion of contexts. -+ * If full ppgtt is enabled, they also print the address of the vm assigned to -+ * the context. -+ */ -+DECLARE_EVENT_CLASS(i915_context, -+ TP_PROTO(struct i915_gem_context *ctx), -+ TP_ARGS(ctx), -+ -+ TP_STRUCT__entry( -+ __field(u32, dev) -+ __field(struct i915_gem_context *, ctx) -+ __field(u32, hw_id) -+ __field(struct i915_address_space *, vm) -+ ), -+ -+ TP_fast_assign( -+ __entry->dev = ctx->i915->drm.primary->index; -+ __entry->ctx = ctx; -+ __entry->hw_id = ctx->hw_id; -+ __entry->vm = ctx->ppgtt ? &ctx->ppgtt->vm : NULL; -+ ), -+ -+ TP_printk("dev=%u, ctx=%p, ctx_vm=%p, hw_id=%u", -+ __entry->dev, __entry->ctx, __entry->vm, __entry->hw_id) -+) -+ -+DEFINE_EVENT(i915_context, i915_context_create, -+ TP_PROTO(struct i915_gem_context *ctx), -+ TP_ARGS(ctx) -+); -+ -+DEFINE_EVENT(i915_context, i915_context_free, -+ TP_PROTO(struct i915_gem_context *ctx), -+ TP_ARGS(ctx) -+); -+ -+#endif /* _I915_TRACE_H_ */ -+ -+/* This part must be outside protection */ -+#undef TRACE_INCLUDE_PATH -+#define TRACE_INCLUDE_PATH ../../drivers/gpu/drm/i915_legacy -+#include -diff --git a/drivers/gpu/drm/i915_legacy/i915_trace_points.c b/drivers/gpu/drm/i915_legacy/i915_trace_points.c -new file mode 100644 -index 000000000000..463a7177997c ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_trace_points.c -@@ -0,0 +1,14 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Copyright © 2009 Intel Corporation -+ * -+ * Authors: -+ * Chris Wilson -+ */ -+ -+#include "i915_drv.h" -+ -+#ifndef __CHECKER__ -+#define CREATE_TRACE_POINTS -+#include "i915_trace.h" -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/i915_user_extensions.c b/drivers/gpu/drm/i915_legacy/i915_user_extensions.c -new file mode 100644 -index 000000000000..c822d0aafd2d ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_user_extensions.c -@@ -0,0 +1,61 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ */ -+ -+#include -+#include -+#include -+ -+#include -+ -+#include "i915_user_extensions.h" -+#include "i915_utils.h" -+ -+int i915_user_extensions(struct i915_user_extension __user *ext, -+ const i915_user_extension_fn *tbl, -+ unsigned int count, -+ void *data) -+{ -+ unsigned int stackdepth = 512; -+ -+ while (ext) { -+ int i, err; -+ u32 name; -+ u64 next; -+ -+ if (!stackdepth--) /* recursion vs useful flexibility */ -+ return -E2BIG; -+ -+ err = check_user_mbz(&ext->flags); -+ if (err) -+ return err; -+ -+ for (i = 0; i < ARRAY_SIZE(ext->rsvd); i++) { -+ err = check_user_mbz(&ext->rsvd[i]); -+ if (err) -+ return err; -+ } -+ -+ if (get_user(name, &ext->name)) -+ return -EFAULT; -+ -+ err = -EINVAL; -+ if (name < count) { -+ name = array_index_nospec(name, count); -+ if (tbl[name]) -+ err = tbl[name](ext, data); -+ } -+ if (err) -+ return err; -+ -+ if (get_user(next, &ext->next_extension) || -+ overflows_type(next, ext)) -+ return -EFAULT; -+ -+ ext = u64_to_user_ptr(next); -+ } -+ -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_user_extensions.h b/drivers/gpu/drm/i915_legacy/i915_user_extensions.h -new file mode 100644 -index 000000000000..a14bf6bba9a1 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_user_extensions.h -@@ -0,0 +1,20 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2018 Intel Corporation -+ */ -+ -+#ifndef I915_USER_EXTENSIONS_H -+#define I915_USER_EXTENSIONS_H -+ -+struct i915_user_extension; -+ -+typedef int (*i915_user_extension_fn)(struct i915_user_extension __user *ext, -+ void *data); -+ -+int i915_user_extensions(struct i915_user_extension __user *ext, -+ const i915_user_extension_fn *tbl, -+ unsigned int count, -+ void *data); -+ -+#endif /* I915_USER_EXTENSIONS_H */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_utils.h b/drivers/gpu/drm/i915_legacy/i915_utils.h -new file mode 100644 -index 000000000000..2dbe8933b50a ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_utils.h -@@ -0,0 +1,192 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef __I915_UTILS_H -+#define __I915_UTILS_H -+ -+#undef WARN_ON -+/* Many gcc seem to no see through this and fall over :( */ -+#if 0 -+#define WARN_ON(x) ({ \ -+ bool __i915_warn_cond = (x); \ -+ if (__builtin_constant_p(__i915_warn_cond)) \ -+ BUILD_BUG_ON(__i915_warn_cond); \ -+ WARN(__i915_warn_cond, "WARN_ON(" #x ")"); }) -+#else -+#define WARN_ON(x) WARN((x), "%s", "WARN_ON(" __stringify(x) ")") -+#endif -+ -+#undef WARN_ON_ONCE -+#define WARN_ON_ONCE(x) WARN_ONCE((x), "%s", "WARN_ON_ONCE(" __stringify(x) ")") -+ -+#define MISSING_CASE(x) WARN(1, "Missing case (%s == %ld)\n", \ -+ __stringify(x), (long)(x)) -+ -+#if defined(GCC_VERSION) && GCC_VERSION >= 70000 -+#define add_overflows_t(T, A, B) \ -+ __builtin_add_overflow_p((A), (B), (T)0) -+#else -+#define add_overflows_t(T, A, B) ({ \ -+ typeof(A) a = (A); \ -+ typeof(B) b = (B); \ -+ (T)(a + b) < a; \ -+}) -+#endif -+ -+#define add_overflows(A, B) \ -+ add_overflows_t(typeof((A) + (B)), (A), (B)) -+ -+#define range_overflows(start, size, max) ({ \ -+ typeof(start) start__ = (start); \ -+ typeof(size) size__ = (size); \ -+ typeof(max) max__ = (max); \ -+ (void)(&start__ == &size__); \ -+ (void)(&start__ == &max__); \ -+ start__ > max__ || size__ > max__ - start__; \ -+}) -+ -+#define range_overflows_t(type, start, size, max) \ -+ range_overflows((type)(start), (type)(size), (type)(max)) -+ -+/* Note we don't consider signbits :| */ -+#define overflows_type(x, T) \ -+ (sizeof(x) > sizeof(T) && (x) >> BITS_PER_TYPE(T)) -+ -+#define ptr_mask_bits(ptr, n) ({ \ -+ unsigned long __v = (unsigned long)(ptr); \ -+ (typeof(ptr))(__v & -BIT(n)); \ -+}) -+ -+#define ptr_unmask_bits(ptr, n) ((unsigned long)(ptr) & (BIT(n) - 1)) -+ -+#define ptr_unpack_bits(ptr, bits, n) ({ \ -+ unsigned long __v = (unsigned long)(ptr); \ -+ *(bits) = __v & (BIT(n) - 1); \ -+ (typeof(ptr))(__v & -BIT(n)); \ -+}) -+ -+#define ptr_pack_bits(ptr, bits, n) ({ \ -+ unsigned long __bits = (bits); \ -+ GEM_BUG_ON(__bits & -BIT(n)); \ -+ ((typeof(ptr))((unsigned long)(ptr) | __bits)); \ -+}) -+ -+#define page_mask_bits(ptr) ptr_mask_bits(ptr, PAGE_SHIFT) -+#define page_unmask_bits(ptr) ptr_unmask_bits(ptr, PAGE_SHIFT) -+#define page_pack_bits(ptr, bits) ptr_pack_bits(ptr, bits, PAGE_SHIFT) -+#define page_unpack_bits(ptr, bits) ptr_unpack_bits(ptr, bits, PAGE_SHIFT) -+ -+#define ptr_offset(ptr, member) offsetof(typeof(*(ptr)), member) -+ -+#define fetch_and_zero(ptr) ({ \ -+ typeof(*ptr) __T = *(ptr); \ -+ *(ptr) = (typeof(*ptr))0; \ -+ __T; \ -+}) -+ -+/* -+ * container_of_user: Extract the superclass from a pointer to a member. -+ * -+ * Exactly like container_of() with the exception that it plays nicely -+ * with sparse for __user @ptr. -+ */ -+#define container_of_user(ptr, type, member) ({ \ -+ void __user *__mptr = (void __user *)(ptr); \ -+ BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \ -+ !__same_type(*(ptr), void), \ -+ "pointer type mismatch in container_of()"); \ -+ ((type __user *)(__mptr - offsetof(type, member))); }) -+ -+/* -+ * check_user_mbz: Check that a user value exists and is zero -+ * -+ * Frequently in our uABI we reserve space for future extensions, and -+ * two ensure that userspace is prepared we enforce that space must -+ * be zero. (Then any future extension can safely assume a default value -+ * of 0.) -+ * -+ * check_user_mbz() combines checking that the user pointer is accessible -+ * and that the contained value is zero. -+ * -+ * Returns: -EFAULT if not accessible, -EINVAL if !zero, or 0 on success. -+ */ -+#define check_user_mbz(U) ({ \ -+ typeof(*(U)) mbz__; \ -+ get_user(mbz__, (U)) ? -EFAULT : mbz__ ? -EINVAL : 0; \ -+}) -+ -+static inline u64 ptr_to_u64(const void *ptr) -+{ -+ return (uintptr_t)ptr; -+} -+ -+#define u64_to_ptr(T, x) ({ \ -+ typecheck(u64, x); \ -+ (T *)(uintptr_t)(x); \ -+}) -+ -+#define __mask_next_bit(mask) ({ \ -+ int __idx = ffs(mask) - 1; \ -+ mask &= ~BIT(__idx); \ -+ __idx; \ -+}) -+ -+#include -+ -+static inline void __list_del_many(struct list_head *head, -+ struct list_head *first) -+{ -+ first->prev = head; -+ WRITE_ONCE(head->next, first); -+} -+ -+/* -+ * Wait until the work is finally complete, even if it tries to postpone -+ * by requeueing itself. Note, that if the worker never cancels itself, -+ * we will spin forever. -+ */ -+static inline void drain_delayed_work(struct delayed_work *dw) -+{ -+ do { -+ while (flush_delayed_work(dw)) -+ ; -+ } while (delayed_work_pending(dw)); -+} -+ -+static inline const char *yesno(bool v) -+{ -+ return v ? "yes" : "no"; -+} -+ -+static inline const char *onoff(bool v) -+{ -+ return v ? "on" : "off"; -+} -+ -+static inline const char *enableddisabled(bool v) -+{ -+ return v ? "enabled" : "disabled"; -+} -+ -+#endif /* !__I915_UTILS_H */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_vgpu.c b/drivers/gpu/drm/i915_legacy/i915_vgpu.c -new file mode 100644 -index 000000000000..724627afdedc ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_vgpu.c -@@ -0,0 +1,279 @@ -+/* -+ * Copyright(c) 2011-2015 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#include "intel_drv.h" -+#include "i915_vgpu.h" -+ -+/** -+ * DOC: Intel GVT-g guest support -+ * -+ * Intel GVT-g is a graphics virtualization technology which shares the -+ * GPU among multiple virtual machines on a time-sharing basis. Each -+ * virtual machine is presented a virtual GPU (vGPU), which has equivalent -+ * features as the underlying physical GPU (pGPU), so i915 driver can run -+ * seamlessly in a virtual machine. This file provides vGPU specific -+ * optimizations when running in a virtual machine, to reduce the complexity -+ * of vGPU emulation and to improve the overall performance. -+ * -+ * A primary function introduced here is so-called "address space ballooning" -+ * technique. Intel GVT-g partitions global graphics memory among multiple VMs, -+ * so each VM can directly access a portion of the memory without hypervisor's -+ * intervention, e.g. filling textures or queuing commands. However with the -+ * partitioning an unmodified i915 driver would assume a smaller graphics -+ * memory starting from address ZERO, then requires vGPU emulation module to -+ * translate the graphics address between 'guest view' and 'host view', for -+ * all registers and command opcodes which contain a graphics memory address. -+ * To reduce the complexity, Intel GVT-g introduces "address space ballooning", -+ * by telling the exact partitioning knowledge to each guest i915 driver, which -+ * then reserves and prevents non-allocated portions from allocation. Thus vGPU -+ * emulation module only needs to scan and validate graphics addresses without -+ * complexity of address translation. -+ * -+ */ -+ -+/** -+ * i915_check_vgpu - detect virtual GPU -+ * @dev_priv: i915 device private -+ * -+ * This function is called at the initialization stage, to detect whether -+ * running on a vGPU. -+ */ -+void i915_check_vgpu(struct drm_i915_private *dev_priv) -+{ -+ struct intel_uncore *uncore = &dev_priv->uncore; -+ u64 magic; -+ u16 version_major; -+ -+ BUILD_BUG_ON(sizeof(struct vgt_if) != VGT_PVINFO_SIZE); -+ -+ magic = __raw_uncore_read64(uncore, vgtif_reg(magic)); -+ if (magic != VGT_MAGIC) -+ return; -+ -+ version_major = __raw_uncore_read16(uncore, vgtif_reg(version_major)); -+ if (version_major < VGT_VERSION_MAJOR) { -+ DRM_INFO("VGT interface version mismatch!\n"); -+ return; -+ } -+ -+ dev_priv->vgpu.caps = __raw_uncore_read32(uncore, vgtif_reg(vgt_caps)); -+ -+ dev_priv->vgpu.active = true; -+ DRM_INFO("Virtual GPU for Intel GVT-g detected.\n"); -+} -+ -+bool intel_vgpu_has_full_ppgtt(struct drm_i915_private *dev_priv) -+{ -+ return dev_priv->vgpu.caps & VGT_CAPS_FULL_PPGTT; -+} -+ -+struct _balloon_info_ { -+ /* -+ * There are up to 2 regions per mappable/unmappable graphic -+ * memory that might be ballooned. Here, index 0/1 is for mappable -+ * graphic memory, 2/3 for unmappable graphic memory. -+ */ -+ struct drm_mm_node space[4]; -+}; -+ -+static struct _balloon_info_ bl_info; -+ -+static void vgt_deballoon_space(struct i915_ggtt *ggtt, -+ struct drm_mm_node *node) -+{ -+ if (!drm_mm_node_allocated(node)) -+ return; -+ -+ DRM_DEBUG_DRIVER("deballoon space: range [0x%llx - 0x%llx] %llu KiB.\n", -+ node->start, -+ node->start + node->size, -+ node->size / 1024); -+ -+ ggtt->vm.reserved -= node->size; -+ drm_mm_remove_node(node); -+} -+ -+/** -+ * intel_vgt_deballoon - deballoon reserved graphics address trunks -+ * @dev_priv: i915 device private data -+ * -+ * This function is called to deallocate the ballooned-out graphic memory, when -+ * driver is unloaded or when ballooning fails. -+ */ -+void intel_vgt_deballoon(struct drm_i915_private *dev_priv) -+{ -+ int i; -+ -+ if (!intel_vgpu_active(dev_priv)) -+ return; -+ -+ DRM_DEBUG("VGT deballoon.\n"); -+ -+ for (i = 0; i < 4; i++) -+ vgt_deballoon_space(&dev_priv->ggtt, &bl_info.space[i]); -+} -+ -+static int vgt_balloon_space(struct i915_ggtt *ggtt, -+ struct drm_mm_node *node, -+ unsigned long start, unsigned long end) -+{ -+ unsigned long size = end - start; -+ int ret; -+ -+ if (start >= end) -+ return -EINVAL; -+ -+ DRM_INFO("balloon space: range [ 0x%lx - 0x%lx ] %lu KiB.\n", -+ start, end, size / 1024); -+ ret = i915_gem_gtt_reserve(&ggtt->vm, node, -+ size, start, I915_COLOR_UNEVICTABLE, -+ 0); -+ if (!ret) -+ ggtt->vm.reserved += size; -+ -+ return ret; -+} -+ -+/** -+ * intel_vgt_balloon - balloon out reserved graphics address trunks -+ * @dev_priv: i915 device private data -+ * -+ * This function is called at the initialization stage, to balloon out the -+ * graphic address space allocated to other vGPUs, by marking these spaces as -+ * reserved. The ballooning related knowledge(starting address and size of -+ * the mappable/unmappable graphic memory) is described in the vgt_if structure -+ * in a reserved mmio range. -+ * -+ * To give an example, the drawing below depicts one typical scenario after -+ * ballooning. Here the vGPU1 has 2 pieces of graphic address spaces ballooned -+ * out each for the mappable and the non-mappable part. From the vGPU1 point of -+ * view, the total size is the same as the physical one, with the start address -+ * of its graphic space being zero. Yet there are some portions ballooned out( -+ * the shadow part, which are marked as reserved by drm allocator). From the -+ * host point of view, the graphic address space is partitioned by multiple -+ * vGPUs in different VMs. :: -+ * -+ * vGPU1 view Host view -+ * 0 ------> +-----------+ +-----------+ -+ * ^ |###########| | vGPU3 | -+ * | |###########| +-----------+ -+ * | |###########| | vGPU2 | -+ * | +-----------+ +-----------+ -+ * mappable GM | available | ==> | vGPU1 | -+ * | +-----------+ +-----------+ -+ * | |###########| | | -+ * v |###########| | Host | -+ * +=======+===========+ +===========+ -+ * ^ |###########| | vGPU3 | -+ * | |###########| +-----------+ -+ * | |###########| | vGPU2 | -+ * | +-----------+ +-----------+ -+ * unmappable GM | available | ==> | vGPU1 | -+ * | +-----------+ +-----------+ -+ * | |###########| | | -+ * | |###########| | Host | -+ * v |###########| | | -+ * total GM size ------> +-----------+ +-----------+ -+ * -+ * Returns: -+ * zero on success, non-zero if configuration invalid or ballooning failed -+ */ -+int intel_vgt_balloon(struct drm_i915_private *dev_priv) -+{ -+ struct i915_ggtt *ggtt = &dev_priv->ggtt; -+ unsigned long ggtt_end = ggtt->vm.total; -+ -+ unsigned long mappable_base, mappable_size, mappable_end; -+ unsigned long unmappable_base, unmappable_size, unmappable_end; -+ int ret; -+ -+ if (!intel_vgpu_active(dev_priv)) -+ return 0; -+ -+ mappable_base = I915_READ(vgtif_reg(avail_rs.mappable_gmadr.base)); -+ mappable_size = I915_READ(vgtif_reg(avail_rs.mappable_gmadr.size)); -+ unmappable_base = I915_READ(vgtif_reg(avail_rs.nonmappable_gmadr.base)); -+ unmappable_size = I915_READ(vgtif_reg(avail_rs.nonmappable_gmadr.size)); -+ -+ mappable_end = mappable_base + mappable_size; -+ unmappable_end = unmappable_base + unmappable_size; -+ -+ DRM_INFO("VGT ballooning configuration:\n"); -+ DRM_INFO("Mappable graphic memory: base 0x%lx size %ldKiB\n", -+ mappable_base, mappable_size / 1024); -+ DRM_INFO("Unmappable graphic memory: base 0x%lx size %ldKiB\n", -+ unmappable_base, unmappable_size / 1024); -+ -+ if (mappable_end > ggtt->mappable_end || -+ unmappable_base < ggtt->mappable_end || -+ unmappable_end > ggtt_end) { -+ DRM_ERROR("Invalid ballooning configuration!\n"); -+ return -EINVAL; -+ } -+ -+ /* Unmappable graphic memory ballooning */ -+ if (unmappable_base > ggtt->mappable_end) { -+ ret = vgt_balloon_space(ggtt, &bl_info.space[2], -+ ggtt->mappable_end, unmappable_base); -+ -+ if (ret) -+ goto err; -+ } -+ -+ if (unmappable_end < ggtt_end) { -+ ret = vgt_balloon_space(ggtt, &bl_info.space[3], -+ unmappable_end, ggtt_end); -+ if (ret) -+ goto err_upon_mappable; -+ } -+ -+ /* Mappable graphic memory ballooning */ -+ if (mappable_base) { -+ ret = vgt_balloon_space(ggtt, &bl_info.space[0], -+ 0, mappable_base); -+ -+ if (ret) -+ goto err_upon_unmappable; -+ } -+ -+ if (mappable_end < ggtt->mappable_end) { -+ ret = vgt_balloon_space(ggtt, &bl_info.space[1], -+ mappable_end, ggtt->mappable_end); -+ -+ if (ret) -+ goto err_below_mappable; -+ } -+ -+ DRM_INFO("VGT balloon successfully\n"); -+ return 0; -+ -+err_below_mappable: -+ vgt_deballoon_space(ggtt, &bl_info.space[0]); -+err_upon_unmappable: -+ vgt_deballoon_space(ggtt, &bl_info.space[3]); -+err_upon_mappable: -+ vgt_deballoon_space(ggtt, &bl_info.space[2]); -+err: -+ DRM_ERROR("VGT balloon fail\n"); -+ return ret; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_vgpu.h b/drivers/gpu/drm/i915_legacy/i915_vgpu.h -new file mode 100644 -index 000000000000..ebe1b7bced98 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_vgpu.h -@@ -0,0 +1,48 @@ -+/* -+ * Copyright(c) 2011-2015 Intel Corporation. All rights reserved. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#ifndef _I915_VGPU_H_ -+#define _I915_VGPU_H_ -+ -+#include "i915_pvinfo.h" -+ -+void i915_check_vgpu(struct drm_i915_private *dev_priv); -+ -+bool intel_vgpu_has_full_ppgtt(struct drm_i915_private *dev_priv); -+ -+static inline bool -+intel_vgpu_has_hwsp_emulation(struct drm_i915_private *dev_priv) -+{ -+ return dev_priv->vgpu.caps & VGT_CAPS_HWSP_EMULATION; -+} -+ -+static inline bool -+intel_vgpu_has_huge_gtt(struct drm_i915_private *dev_priv) -+{ -+ return dev_priv->vgpu.caps & VGT_CAPS_HUGE_GTT; -+} -+ -+int intel_vgt_balloon(struct drm_i915_private *dev_priv); -+void intel_vgt_deballoon(struct drm_i915_private *dev_priv); -+ -+#endif /* _I915_VGPU_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/i915_vma.c b/drivers/gpu/drm/i915_legacy/i915_vma.c -new file mode 100644 -index 000000000000..961268f66c63 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_vma.c -@@ -0,0 +1,1079 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include "i915_vma.h" -+ -+#include "i915_drv.h" -+#include "i915_globals.h" -+#include "intel_ringbuffer.h" -+#include "intel_frontbuffer.h" -+ -+#include -+ -+static struct i915_global_vma { -+ struct i915_global base; -+ struct kmem_cache *slab_vmas; -+} global; -+ -+struct i915_vma *i915_vma_alloc(void) -+{ -+ return kmem_cache_zalloc(global.slab_vmas, GFP_KERNEL); -+} -+ -+void i915_vma_free(struct i915_vma *vma) -+{ -+ return kmem_cache_free(global.slab_vmas, vma); -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_ERRLOG_GEM) && IS_ENABLED(CONFIG_DRM_DEBUG_MM) -+ -+#include -+ -+static void vma_print_allocator(struct i915_vma *vma, const char *reason) -+{ -+ unsigned long *entries; -+ unsigned int nr_entries; -+ char buf[512]; -+ -+ if (!vma->node.stack) { -+ DRM_DEBUG_DRIVER("vma.node [%08llx + %08llx] %s: unknown owner\n", -+ vma->node.start, vma->node.size, reason); -+ return; -+ } -+ -+ nr_entries = stack_depot_fetch(vma->node.stack, &entries); -+ stack_trace_snprint(buf, sizeof(buf), entries, nr_entries, 0); -+ DRM_DEBUG_DRIVER("vma.node [%08llx + %08llx] %s: inserted at %s\n", -+ vma->node.start, vma->node.size, reason, buf); -+} -+ -+#else -+ -+static void vma_print_allocator(struct i915_vma *vma, const char *reason) -+{ -+} -+ -+#endif -+ -+static void obj_bump_mru(struct drm_i915_gem_object *obj) -+{ -+ struct drm_i915_private *i915 = to_i915(obj->base.dev); -+ -+ spin_lock(&i915->mm.obj_lock); -+ if (obj->bind_count) -+ list_move_tail(&obj->mm.link, &i915->mm.bound_list); -+ spin_unlock(&i915->mm.obj_lock); -+ -+ obj->mm.dirty = true; /* be paranoid */ -+} -+ -+static void __i915_vma_retire(struct i915_active *ref) -+{ -+ struct i915_vma *vma = container_of(ref, typeof(*vma), active); -+ struct drm_i915_gem_object *obj = vma->obj; -+ -+ GEM_BUG_ON(!i915_gem_object_is_active(obj)); -+ if (--obj->active_count) -+ return; -+ -+ /* Prune the shared fence arrays iff completely idle (inc. external) */ -+ if (reservation_object_trylock(obj->resv)) { -+ if (reservation_object_test_signaled_rcu(obj->resv, true)) -+ reservation_object_add_excl_fence(obj->resv, NULL); -+ reservation_object_unlock(obj->resv); -+ } -+ -+ /* -+ * Bump our place on the bound list to keep it roughly in LRU order -+ * so that we don't steal from recently used but inactive objects -+ * (unless we are forced to ofc!) -+ */ -+ obj_bump_mru(obj); -+ -+ if (i915_gem_object_has_active_reference(obj)) { -+ i915_gem_object_clear_active_reference(obj); -+ i915_gem_object_put(obj); -+ } -+} -+ -+static struct i915_vma * -+vma_create(struct drm_i915_gem_object *obj, -+ struct i915_address_space *vm, -+ const struct i915_ggtt_view *view) -+{ -+ struct i915_vma *vma; -+ struct rb_node *rb, **p; -+ -+ /* The aliasing_ppgtt should never be used directly! */ -+ GEM_BUG_ON(vm == &vm->i915->mm.aliasing_ppgtt->vm); -+ -+ vma = i915_vma_alloc(); -+ if (vma == NULL) -+ return ERR_PTR(-ENOMEM); -+ -+ i915_active_init(vm->i915, &vma->active, __i915_vma_retire); -+ INIT_ACTIVE_REQUEST(&vma->last_fence); -+ -+ vma->vm = vm; -+ vma->ops = &vm->vma_ops; -+ vma->obj = obj; -+ vma->resv = obj->resv; -+ vma->size = obj->base.size; -+ vma->display_alignment = I915_GTT_MIN_ALIGNMENT; -+ -+ if (view && view->type != I915_GGTT_VIEW_NORMAL) { -+ vma->ggtt_view = *view; -+ if (view->type == I915_GGTT_VIEW_PARTIAL) { -+ GEM_BUG_ON(range_overflows_t(u64, -+ view->partial.offset, -+ view->partial.size, -+ obj->base.size >> PAGE_SHIFT)); -+ vma->size = view->partial.size; -+ vma->size <<= PAGE_SHIFT; -+ GEM_BUG_ON(vma->size > obj->base.size); -+ } else if (view->type == I915_GGTT_VIEW_ROTATED) { -+ vma->size = intel_rotation_info_size(&view->rotated); -+ vma->size <<= PAGE_SHIFT; -+ } -+ } -+ -+ if (unlikely(vma->size > vm->total)) -+ goto err_vma; -+ -+ GEM_BUG_ON(!IS_ALIGNED(vma->size, I915_GTT_PAGE_SIZE)); -+ -+ if (i915_is_ggtt(vm)) { -+ if (unlikely(overflows_type(vma->size, u32))) -+ goto err_vma; -+ -+ vma->fence_size = i915_gem_fence_size(vm->i915, vma->size, -+ i915_gem_object_get_tiling(obj), -+ i915_gem_object_get_stride(obj)); -+ if (unlikely(vma->fence_size < vma->size || /* overflow */ -+ vma->fence_size > vm->total)) -+ goto err_vma; -+ -+ GEM_BUG_ON(!IS_ALIGNED(vma->fence_size, I915_GTT_MIN_ALIGNMENT)); -+ -+ vma->fence_alignment = i915_gem_fence_alignment(vm->i915, vma->size, -+ i915_gem_object_get_tiling(obj), -+ i915_gem_object_get_stride(obj)); -+ GEM_BUG_ON(!is_power_of_2(vma->fence_alignment)); -+ -+ vma->flags |= I915_VMA_GGTT; -+ } -+ -+ spin_lock(&obj->vma.lock); -+ -+ rb = NULL; -+ p = &obj->vma.tree.rb_node; -+ while (*p) { -+ struct i915_vma *pos; -+ long cmp; -+ -+ rb = *p; -+ pos = rb_entry(rb, struct i915_vma, obj_node); -+ -+ /* -+ * If the view already exists in the tree, another thread -+ * already created a matching vma, so return the older instance -+ * and dispose of ours. -+ */ -+ cmp = i915_vma_compare(pos, vm, view); -+ if (cmp == 0) { -+ spin_unlock(&obj->vma.lock); -+ i915_vma_free(vma); -+ return pos; -+ } -+ -+ if (cmp < 0) -+ p = &rb->rb_right; -+ else -+ p = &rb->rb_left; -+ } -+ rb_link_node(&vma->obj_node, rb, p); -+ rb_insert_color(&vma->obj_node, &obj->vma.tree); -+ -+ if (i915_vma_is_ggtt(vma)) -+ /* -+ * We put the GGTT vma at the start of the vma-list, followed -+ * by the ppGGTT vma. This allows us to break early when -+ * iterating over only the GGTT vma for an object, see -+ * for_each_ggtt_vma() -+ */ -+ list_add(&vma->obj_link, &obj->vma.list); -+ else -+ list_add_tail(&vma->obj_link, &obj->vma.list); -+ -+ spin_unlock(&obj->vma.lock); -+ -+ mutex_lock(&vm->mutex); -+ list_add(&vma->vm_link, &vm->unbound_list); -+ mutex_unlock(&vm->mutex); -+ -+ return vma; -+ -+err_vma: -+ i915_vma_free(vma); -+ return ERR_PTR(-E2BIG); -+} -+ -+static struct i915_vma * -+vma_lookup(struct drm_i915_gem_object *obj, -+ struct i915_address_space *vm, -+ const struct i915_ggtt_view *view) -+{ -+ struct rb_node *rb; -+ -+ rb = obj->vma.tree.rb_node; -+ while (rb) { -+ struct i915_vma *vma = rb_entry(rb, struct i915_vma, obj_node); -+ long cmp; -+ -+ cmp = i915_vma_compare(vma, vm, view); -+ if (cmp == 0) -+ return vma; -+ -+ if (cmp < 0) -+ rb = rb->rb_right; -+ else -+ rb = rb->rb_left; -+ } -+ -+ return NULL; -+} -+ -+/** -+ * i915_vma_instance - return the singleton instance of the VMA -+ * @obj: parent &struct drm_i915_gem_object to be mapped -+ * @vm: address space in which the mapping is located -+ * @view: additional mapping requirements -+ * -+ * i915_vma_instance() looks up an existing VMA of the @obj in the @vm with -+ * the same @view characteristics. If a match is not found, one is created. -+ * Once created, the VMA is kept until either the object is freed, or the -+ * address space is closed. -+ * -+ * Must be called with struct_mutex held. -+ * -+ * Returns the vma, or an error pointer. -+ */ -+struct i915_vma * -+i915_vma_instance(struct drm_i915_gem_object *obj, -+ struct i915_address_space *vm, -+ const struct i915_ggtt_view *view) -+{ -+ struct i915_vma *vma; -+ -+ GEM_BUG_ON(view && !i915_is_ggtt(vm)); -+ GEM_BUG_ON(vm->closed); -+ -+ spin_lock(&obj->vma.lock); -+ vma = vma_lookup(obj, vm, view); -+ spin_unlock(&obj->vma.lock); -+ -+ /* vma_create() will resolve the race if another creates the vma */ -+ if (unlikely(!vma)) -+ vma = vma_create(obj, vm, view); -+ -+ GEM_BUG_ON(!IS_ERR(vma) && i915_vma_compare(vma, vm, view)); -+ return vma; -+} -+ -+/** -+ * i915_vma_bind - Sets up PTEs for an VMA in it's corresponding address space. -+ * @vma: VMA to map -+ * @cache_level: mapping cache level -+ * @flags: flags like global or local mapping -+ * -+ * DMA addresses are taken from the scatter-gather table of this object (or of -+ * this VMA in case of non-default GGTT views) and PTE entries set up. -+ * Note that DMA addresses are also the only part of the SG table we care about. -+ */ -+int i915_vma_bind(struct i915_vma *vma, enum i915_cache_level cache_level, -+ u32 flags) -+{ -+ u32 bind_flags; -+ u32 vma_flags; -+ int ret; -+ -+ GEM_BUG_ON(!drm_mm_node_allocated(&vma->node)); -+ GEM_BUG_ON(vma->size > vma->node.size); -+ -+ if (GEM_DEBUG_WARN_ON(range_overflows(vma->node.start, -+ vma->node.size, -+ vma->vm->total))) -+ return -ENODEV; -+ -+ if (GEM_DEBUG_WARN_ON(!flags)) -+ return -EINVAL; -+ -+ bind_flags = 0; -+ if (flags & PIN_GLOBAL) -+ bind_flags |= I915_VMA_GLOBAL_BIND; -+ if (flags & PIN_USER) -+ bind_flags |= I915_VMA_LOCAL_BIND; -+ -+ vma_flags = vma->flags & (I915_VMA_GLOBAL_BIND | I915_VMA_LOCAL_BIND); -+ if (flags & PIN_UPDATE) -+ bind_flags |= vma_flags; -+ else -+ bind_flags &= ~vma_flags; -+ if (bind_flags == 0) -+ return 0; -+ -+ GEM_BUG_ON(!vma->pages); -+ -+ trace_i915_vma_bind(vma, bind_flags); -+ ret = vma->ops->bind_vma(vma, cache_level, bind_flags); -+ if (ret) -+ return ret; -+ -+ vma->flags |= bind_flags; -+ return 0; -+} -+ -+void __iomem *i915_vma_pin_iomap(struct i915_vma *vma) -+{ -+ void __iomem *ptr; -+ int err; -+ -+ /* Access through the GTT requires the device to be awake. */ -+ assert_rpm_wakelock_held(vma->vm->i915); -+ -+ lockdep_assert_held(&vma->vm->i915->drm.struct_mutex); -+ if (WARN_ON(!i915_vma_is_map_and_fenceable(vma))) { -+ err = -ENODEV; -+ goto err; -+ } -+ -+ GEM_BUG_ON(!i915_vma_is_ggtt(vma)); -+ GEM_BUG_ON((vma->flags & I915_VMA_GLOBAL_BIND) == 0); -+ -+ ptr = vma->iomap; -+ if (ptr == NULL) { -+ ptr = io_mapping_map_wc(&i915_vm_to_ggtt(vma->vm)->iomap, -+ vma->node.start, -+ vma->node.size); -+ if (ptr == NULL) { -+ err = -ENOMEM; -+ goto err; -+ } -+ -+ vma->iomap = ptr; -+ } -+ -+ __i915_vma_pin(vma); -+ -+ err = i915_vma_pin_fence(vma); -+ if (err) -+ goto err_unpin; -+ -+ i915_vma_set_ggtt_write(vma); -+ return ptr; -+ -+err_unpin: -+ __i915_vma_unpin(vma); -+err: -+ return IO_ERR_PTR(err); -+} -+ -+void i915_vma_flush_writes(struct i915_vma *vma) -+{ -+ if (!i915_vma_has_ggtt_write(vma)) -+ return; -+ -+ i915_gem_flush_ggtt_writes(vma->vm->i915); -+ -+ i915_vma_unset_ggtt_write(vma); -+} -+ -+void i915_vma_unpin_iomap(struct i915_vma *vma) -+{ -+ lockdep_assert_held(&vma->vm->i915->drm.struct_mutex); -+ -+ GEM_BUG_ON(vma->iomap == NULL); -+ -+ i915_vma_flush_writes(vma); -+ -+ i915_vma_unpin_fence(vma); -+ i915_vma_unpin(vma); -+} -+ -+void i915_vma_unpin_and_release(struct i915_vma **p_vma, unsigned int flags) -+{ -+ struct i915_vma *vma; -+ struct drm_i915_gem_object *obj; -+ -+ vma = fetch_and_zero(p_vma); -+ if (!vma) -+ return; -+ -+ obj = vma->obj; -+ GEM_BUG_ON(!obj); -+ -+ i915_vma_unpin(vma); -+ i915_vma_close(vma); -+ -+ if (flags & I915_VMA_RELEASE_MAP) -+ i915_gem_object_unpin_map(obj); -+ -+ __i915_gem_object_release_unless_active(obj); -+} -+ -+bool i915_vma_misplaced(const struct i915_vma *vma, -+ u64 size, u64 alignment, u64 flags) -+{ -+ if (!drm_mm_node_allocated(&vma->node)) -+ return false; -+ -+ if (vma->node.size < size) -+ return true; -+ -+ GEM_BUG_ON(alignment && !is_power_of_2(alignment)); -+ if (alignment && !IS_ALIGNED(vma->node.start, alignment)) -+ return true; -+ -+ if (flags & PIN_MAPPABLE && !i915_vma_is_map_and_fenceable(vma)) -+ return true; -+ -+ if (flags & PIN_OFFSET_BIAS && -+ vma->node.start < (flags & PIN_OFFSET_MASK)) -+ return true; -+ -+ if (flags & PIN_OFFSET_FIXED && -+ vma->node.start != (flags & PIN_OFFSET_MASK)) -+ return true; -+ -+ return false; -+} -+ -+void __i915_vma_set_map_and_fenceable(struct i915_vma *vma) -+{ -+ bool mappable, fenceable; -+ -+ GEM_BUG_ON(!i915_vma_is_ggtt(vma)); -+ GEM_BUG_ON(!vma->fence_size); -+ -+ /* -+ * Explicitly disable for rotated VMA since the display does not -+ * need the fence and the VMA is not accessible to other users. -+ */ -+ if (vma->ggtt_view.type == I915_GGTT_VIEW_ROTATED) -+ return; -+ -+ fenceable = (vma->node.size >= vma->fence_size && -+ IS_ALIGNED(vma->node.start, vma->fence_alignment)); -+ -+ mappable = vma->node.start + vma->fence_size <= i915_vm_to_ggtt(vma->vm)->mappable_end; -+ -+ if (mappable && fenceable) -+ vma->flags |= I915_VMA_CAN_FENCE; -+ else -+ vma->flags &= ~I915_VMA_CAN_FENCE; -+} -+ -+static bool color_differs(struct drm_mm_node *node, unsigned long color) -+{ -+ return node->allocated && node->color != color; -+} -+ -+bool i915_gem_valid_gtt_space(struct i915_vma *vma, unsigned long cache_level) -+{ -+ struct drm_mm_node *node = &vma->node; -+ struct drm_mm_node *other; -+ -+ /* -+ * On some machines we have to be careful when putting differing types -+ * of snoopable memory together to avoid the prefetcher crossing memory -+ * domains and dying. During vm initialisation, we decide whether or not -+ * these constraints apply and set the drm_mm.color_adjust -+ * appropriately. -+ */ -+ if (vma->vm->mm.color_adjust == NULL) -+ return true; -+ -+ /* Only valid to be called on an already inserted vma */ -+ GEM_BUG_ON(!drm_mm_node_allocated(node)); -+ GEM_BUG_ON(list_empty(&node->node_list)); -+ -+ other = list_prev_entry(node, node_list); -+ if (color_differs(other, cache_level) && !drm_mm_hole_follows(other)) -+ return false; -+ -+ other = list_next_entry(node, node_list); -+ if (color_differs(other, cache_level) && !drm_mm_hole_follows(node)) -+ return false; -+ -+ return true; -+} -+ -+static void assert_bind_count(const struct drm_i915_gem_object *obj) -+{ -+ /* -+ * Combine the assertion that the object is bound and that we have -+ * pinned its pages. But we should never have bound the object -+ * more than we have pinned its pages. (For complete accuracy, we -+ * assume that no else is pinning the pages, but as a rough assertion -+ * that we will not run into problems later, this will do!) -+ */ -+ GEM_BUG_ON(atomic_read(&obj->mm.pages_pin_count) < obj->bind_count); -+} -+ -+/** -+ * i915_vma_insert - finds a slot for the vma in its address space -+ * @vma: the vma -+ * @size: requested size in bytes (can be larger than the VMA) -+ * @alignment: required alignment -+ * @flags: mask of PIN_* flags to use -+ * -+ * First we try to allocate some free space that meets the requirements for -+ * the VMA. Failiing that, if the flags permit, it will evict an old VMA, -+ * preferrably the oldest idle entry to make room for the new VMA. -+ * -+ * Returns: -+ * 0 on success, negative error code otherwise. -+ */ -+static int -+i915_vma_insert(struct i915_vma *vma, u64 size, u64 alignment, u64 flags) -+{ -+ struct drm_i915_private *dev_priv = vma->vm->i915; -+ unsigned int cache_level; -+ u64 start, end; -+ int ret; -+ -+ GEM_BUG_ON(i915_vma_is_closed(vma)); -+ GEM_BUG_ON(vma->flags & (I915_VMA_GLOBAL_BIND | I915_VMA_LOCAL_BIND)); -+ GEM_BUG_ON(drm_mm_node_allocated(&vma->node)); -+ -+ size = max(size, vma->size); -+ alignment = max(alignment, vma->display_alignment); -+ if (flags & PIN_MAPPABLE) { -+ size = max_t(typeof(size), size, vma->fence_size); -+ alignment = max_t(typeof(alignment), -+ alignment, vma->fence_alignment); -+ } -+ -+ GEM_BUG_ON(!IS_ALIGNED(size, I915_GTT_PAGE_SIZE)); -+ GEM_BUG_ON(!IS_ALIGNED(alignment, I915_GTT_MIN_ALIGNMENT)); -+ GEM_BUG_ON(!is_power_of_2(alignment)); -+ -+ start = flags & PIN_OFFSET_BIAS ? flags & PIN_OFFSET_MASK : 0; -+ GEM_BUG_ON(!IS_ALIGNED(start, I915_GTT_PAGE_SIZE)); -+ -+ end = vma->vm->total; -+ if (flags & PIN_MAPPABLE) -+ end = min_t(u64, end, dev_priv->ggtt.mappable_end); -+ if (flags & PIN_ZONE_4G) -+ end = min_t(u64, end, (1ULL << 32) - I915_GTT_PAGE_SIZE); -+ GEM_BUG_ON(!IS_ALIGNED(end, I915_GTT_PAGE_SIZE)); -+ -+ /* If binding the object/GGTT view requires more space than the entire -+ * aperture has, reject it early before evicting everything in a vain -+ * attempt to find space. -+ */ -+ if (size > end) { -+ DRM_DEBUG("Attempting to bind an object larger than the aperture: request=%llu > %s aperture=%llu\n", -+ size, flags & PIN_MAPPABLE ? "mappable" : "total", -+ end); -+ return -ENOSPC; -+ } -+ -+ if (vma->obj) { -+ ret = i915_gem_object_pin_pages(vma->obj); -+ if (ret) -+ return ret; -+ -+ cache_level = vma->obj->cache_level; -+ } else { -+ cache_level = 0; -+ } -+ -+ GEM_BUG_ON(vma->pages); -+ -+ ret = vma->ops->set_pages(vma); -+ if (ret) -+ goto err_unpin; -+ -+ if (flags & PIN_OFFSET_FIXED) { -+ u64 offset = flags & PIN_OFFSET_MASK; -+ if (!IS_ALIGNED(offset, alignment) || -+ range_overflows(offset, size, end)) { -+ ret = -EINVAL; -+ goto err_clear; -+ } -+ -+ ret = i915_gem_gtt_reserve(vma->vm, &vma->node, -+ size, offset, cache_level, -+ flags); -+ if (ret) -+ goto err_clear; -+ } else { -+ /* -+ * We only support huge gtt pages through the 48b PPGTT, -+ * however we also don't want to force any alignment for -+ * objects which need to be tightly packed into the low 32bits. -+ * -+ * Note that we assume that GGTT are limited to 4GiB for the -+ * forseeable future. See also i915_ggtt_offset(). -+ */ -+ if (upper_32_bits(end - 1) && -+ vma->page_sizes.sg > I915_GTT_PAGE_SIZE) { -+ /* -+ * We can't mix 64K and 4K PTEs in the same page-table -+ * (2M block), and so to avoid the ugliness and -+ * complexity of coloring we opt for just aligning 64K -+ * objects to 2M. -+ */ -+ u64 page_alignment = -+ rounddown_pow_of_two(vma->page_sizes.sg | -+ I915_GTT_PAGE_SIZE_2M); -+ -+ /* -+ * Check we don't expand for the limited Global GTT -+ * (mappable aperture is even more precious!). This -+ * also checks that we exclude the aliasing-ppgtt. -+ */ -+ GEM_BUG_ON(i915_vma_is_ggtt(vma)); -+ -+ alignment = max(alignment, page_alignment); -+ -+ if (vma->page_sizes.sg & I915_GTT_PAGE_SIZE_64K) -+ size = round_up(size, I915_GTT_PAGE_SIZE_2M); -+ } -+ -+ ret = i915_gem_gtt_insert(vma->vm, &vma->node, -+ size, alignment, cache_level, -+ start, end, flags); -+ if (ret) -+ goto err_clear; -+ -+ GEM_BUG_ON(vma->node.start < start); -+ GEM_BUG_ON(vma->node.start + vma->node.size > end); -+ } -+ GEM_BUG_ON(!drm_mm_node_allocated(&vma->node)); -+ GEM_BUG_ON(!i915_gem_valid_gtt_space(vma, cache_level)); -+ -+ mutex_lock(&vma->vm->mutex); -+ list_move_tail(&vma->vm_link, &vma->vm->bound_list); -+ mutex_unlock(&vma->vm->mutex); -+ -+ if (vma->obj) { -+ struct drm_i915_gem_object *obj = vma->obj; -+ -+ spin_lock(&dev_priv->mm.obj_lock); -+ list_move_tail(&obj->mm.link, &dev_priv->mm.bound_list); -+ obj->bind_count++; -+ spin_unlock(&dev_priv->mm.obj_lock); -+ -+ assert_bind_count(obj); -+ } -+ -+ return 0; -+ -+err_clear: -+ vma->ops->clear_pages(vma); -+err_unpin: -+ if (vma->obj) -+ i915_gem_object_unpin_pages(vma->obj); -+ return ret; -+} -+ -+static void -+i915_vma_remove(struct i915_vma *vma) -+{ -+ struct drm_i915_private *i915 = vma->vm->i915; -+ -+ GEM_BUG_ON(!drm_mm_node_allocated(&vma->node)); -+ GEM_BUG_ON(vma->flags & (I915_VMA_GLOBAL_BIND | I915_VMA_LOCAL_BIND)); -+ -+ vma->ops->clear_pages(vma); -+ -+ mutex_lock(&vma->vm->mutex); -+ drm_mm_remove_node(&vma->node); -+ list_move_tail(&vma->vm_link, &vma->vm->unbound_list); -+ mutex_unlock(&vma->vm->mutex); -+ -+ /* -+ * Since the unbound list is global, only move to that list if -+ * no more VMAs exist. -+ */ -+ if (vma->obj) { -+ struct drm_i915_gem_object *obj = vma->obj; -+ -+ spin_lock(&i915->mm.obj_lock); -+ if (--obj->bind_count == 0) -+ list_move_tail(&obj->mm.link, &i915->mm.unbound_list); -+ spin_unlock(&i915->mm.obj_lock); -+ -+ /* -+ * And finally now the object is completely decoupled from this -+ * vma, we can drop its hold on the backing storage and allow -+ * it to be reaped by the shrinker. -+ */ -+ i915_gem_object_unpin_pages(obj); -+ assert_bind_count(obj); -+ } -+} -+ -+int __i915_vma_do_pin(struct i915_vma *vma, -+ u64 size, u64 alignment, u64 flags) -+{ -+ const unsigned int bound = vma->flags; -+ int ret; -+ -+ lockdep_assert_held(&vma->vm->i915->drm.struct_mutex); -+ GEM_BUG_ON((flags & (PIN_GLOBAL | PIN_USER)) == 0); -+ GEM_BUG_ON((flags & PIN_GLOBAL) && !i915_vma_is_ggtt(vma)); -+ -+ if (WARN_ON(bound & I915_VMA_PIN_OVERFLOW)) { -+ ret = -EBUSY; -+ goto err_unpin; -+ } -+ -+ if ((bound & I915_VMA_BIND_MASK) == 0) { -+ ret = i915_vma_insert(vma, size, alignment, flags); -+ if (ret) -+ goto err_unpin; -+ } -+ GEM_BUG_ON(!drm_mm_node_allocated(&vma->node)); -+ -+ ret = i915_vma_bind(vma, vma->obj ? vma->obj->cache_level : 0, flags); -+ if (ret) -+ goto err_remove; -+ -+ GEM_BUG_ON((vma->flags & I915_VMA_BIND_MASK) == 0); -+ -+ if ((bound ^ vma->flags) & I915_VMA_GLOBAL_BIND) -+ __i915_vma_set_map_and_fenceable(vma); -+ -+ GEM_BUG_ON(i915_vma_misplaced(vma, size, alignment, flags)); -+ return 0; -+ -+err_remove: -+ if ((bound & I915_VMA_BIND_MASK) == 0) { -+ i915_vma_remove(vma); -+ GEM_BUG_ON(vma->pages); -+ GEM_BUG_ON(vma->flags & I915_VMA_BIND_MASK); -+ } -+err_unpin: -+ __i915_vma_unpin(vma); -+ return ret; -+} -+ -+void i915_vma_close(struct i915_vma *vma) -+{ -+ lockdep_assert_held(&vma->vm->i915->drm.struct_mutex); -+ -+ GEM_BUG_ON(i915_vma_is_closed(vma)); -+ vma->flags |= I915_VMA_CLOSED; -+ -+ /* -+ * We defer actually closing, unbinding and destroying the VMA until -+ * the next idle point, or if the object is freed in the meantime. By -+ * postponing the unbind, we allow for it to be resurrected by the -+ * client, avoiding the work required to rebind the VMA. This is -+ * advantageous for DRI, where the client/server pass objects -+ * between themselves, temporarily opening a local VMA to the -+ * object, and then closing it again. The same object is then reused -+ * on the next frame (or two, depending on the depth of the swap queue) -+ * causing us to rebind the VMA once more. This ends up being a lot -+ * of wasted work for the steady state. -+ */ -+ list_add_tail(&vma->closed_link, &vma->vm->i915->gt.closed_vma); -+} -+ -+void i915_vma_reopen(struct i915_vma *vma) -+{ -+ lockdep_assert_held(&vma->vm->i915->drm.struct_mutex); -+ -+ if (vma->flags & I915_VMA_CLOSED) { -+ vma->flags &= ~I915_VMA_CLOSED; -+ list_del(&vma->closed_link); -+ } -+} -+ -+static void __i915_vma_destroy(struct i915_vma *vma) -+{ -+ GEM_BUG_ON(vma->node.allocated); -+ GEM_BUG_ON(vma->fence); -+ -+ GEM_BUG_ON(i915_active_request_isset(&vma->last_fence)); -+ -+ mutex_lock(&vma->vm->mutex); -+ list_del(&vma->vm_link); -+ mutex_unlock(&vma->vm->mutex); -+ -+ if (vma->obj) { -+ struct drm_i915_gem_object *obj = vma->obj; -+ -+ spin_lock(&obj->vma.lock); -+ list_del(&vma->obj_link); -+ rb_erase(&vma->obj_node, &vma->obj->vma.tree); -+ spin_unlock(&obj->vma.lock); -+ } -+ -+ i915_active_fini(&vma->active); -+ -+ i915_vma_free(vma); -+} -+ -+void i915_vma_destroy(struct i915_vma *vma) -+{ -+ lockdep_assert_held(&vma->vm->i915->drm.struct_mutex); -+ -+ GEM_BUG_ON(i915_vma_is_active(vma)); -+ GEM_BUG_ON(i915_vma_is_pinned(vma)); -+ -+ if (i915_vma_is_closed(vma)) -+ list_del(&vma->closed_link); -+ -+ WARN_ON(i915_vma_unbind(vma)); -+ __i915_vma_destroy(vma); -+} -+ -+void i915_vma_parked(struct drm_i915_private *i915) -+{ -+ struct i915_vma *vma, *next; -+ -+ list_for_each_entry_safe(vma, next, &i915->gt.closed_vma, closed_link) { -+ GEM_BUG_ON(!i915_vma_is_closed(vma)); -+ i915_vma_destroy(vma); -+ } -+ -+ GEM_BUG_ON(!list_empty(&i915->gt.closed_vma)); -+} -+ -+static void __i915_vma_iounmap(struct i915_vma *vma) -+{ -+ GEM_BUG_ON(i915_vma_is_pinned(vma)); -+ -+ if (vma->iomap == NULL) -+ return; -+ -+ io_mapping_unmap(vma->iomap); -+ vma->iomap = NULL; -+} -+ -+void i915_vma_revoke_mmap(struct i915_vma *vma) -+{ -+ struct drm_vma_offset_node *node = &vma->obj->base.vma_node; -+ u64 vma_offset; -+ -+ lockdep_assert_held(&vma->vm->i915->drm.struct_mutex); -+ -+ if (!i915_vma_has_userfault(vma)) -+ return; -+ -+ GEM_BUG_ON(!i915_vma_is_map_and_fenceable(vma)); -+ GEM_BUG_ON(!vma->obj->userfault_count); -+ -+ vma_offset = vma->ggtt_view.partial.offset << PAGE_SHIFT; -+ unmap_mapping_range(vma->vm->i915->drm.anon_inode->i_mapping, -+ drm_vma_node_offset_addr(node) + vma_offset, -+ vma->size, -+ 1); -+ -+ i915_vma_unset_userfault(vma); -+ if (!--vma->obj->userfault_count) -+ list_del(&vma->obj->userfault_link); -+} -+ -+static void export_fence(struct i915_vma *vma, -+ struct i915_request *rq, -+ unsigned int flags) -+{ -+ struct reservation_object *resv = vma->resv; -+ -+ /* -+ * Ignore errors from failing to allocate the new fence, we can't -+ * handle an error right now. Worst case should be missed -+ * synchronisation leading to rendering corruption. -+ */ -+ reservation_object_lock(resv, NULL); -+ if (flags & EXEC_OBJECT_WRITE) -+ reservation_object_add_excl_fence(resv, &rq->fence); -+ else if (reservation_object_reserve_shared(resv, 1) == 0) -+ reservation_object_add_shared_fence(resv, &rq->fence); -+ reservation_object_unlock(resv); -+} -+ -+int i915_vma_move_to_active(struct i915_vma *vma, -+ struct i915_request *rq, -+ unsigned int flags) -+{ -+ struct drm_i915_gem_object *obj = vma->obj; -+ -+ lockdep_assert_held(&rq->i915->drm.struct_mutex); -+ GEM_BUG_ON(!drm_mm_node_allocated(&vma->node)); -+ -+ /* -+ * Add a reference if we're newly entering the active list. -+ * The order in which we add operations to the retirement queue is -+ * vital here: mark_active adds to the start of the callback list, -+ * such that subsequent callbacks are called first. Therefore we -+ * add the active reference first and queue for it to be dropped -+ * *last*. -+ */ -+ if (!vma->active.count) -+ obj->active_count++; -+ -+ if (unlikely(i915_active_ref(&vma->active, rq->fence.context, rq))) { -+ if (!vma->active.count) -+ obj->active_count--; -+ return -ENOMEM; -+ } -+ -+ GEM_BUG_ON(!i915_vma_is_active(vma)); -+ GEM_BUG_ON(!obj->active_count); -+ -+ obj->write_domain = 0; -+ if (flags & EXEC_OBJECT_WRITE) { -+ obj->write_domain = I915_GEM_DOMAIN_RENDER; -+ -+ if (intel_fb_obj_invalidate(obj, ORIGIN_CS)) -+ __i915_active_request_set(&obj->frontbuffer_write, rq); -+ -+ obj->read_domains = 0; -+ } -+ obj->read_domains |= I915_GEM_GPU_DOMAINS; -+ -+ if (flags & EXEC_OBJECT_NEEDS_FENCE) -+ __i915_active_request_set(&vma->last_fence, rq); -+ -+ export_fence(vma, rq, flags); -+ return 0; -+} -+ -+int i915_vma_unbind(struct i915_vma *vma) -+{ -+ int ret; -+ -+ lockdep_assert_held(&vma->vm->i915->drm.struct_mutex); -+ -+ /* -+ * First wait upon any activity as retiring the request may -+ * have side-effects such as unpinning or even unbinding this vma. -+ */ -+ might_sleep(); -+ if (i915_vma_is_active(vma)) { -+ /* -+ * When a closed VMA is retired, it is unbound - eek. -+ * In order to prevent it from being recursively closed, -+ * take a pin on the vma so that the second unbind is -+ * aborted. -+ * -+ * Even more scary is that the retire callback may free -+ * the object (last active vma). To prevent the explosion -+ * we defer the actual object free to a worker that can -+ * only proceed once it acquires the struct_mutex (which -+ * we currently hold, therefore it cannot free this object -+ * before we are finished). -+ */ -+ __i915_vma_pin(vma); -+ -+ ret = i915_active_wait(&vma->active); -+ if (ret) -+ goto unpin; -+ -+ ret = i915_active_request_retire(&vma->last_fence, -+ &vma->vm->i915->drm.struct_mutex); -+unpin: -+ __i915_vma_unpin(vma); -+ if (ret) -+ return ret; -+ } -+ GEM_BUG_ON(i915_vma_is_active(vma)); -+ -+ if (i915_vma_is_pinned(vma)) { -+ vma_print_allocator(vma, "is pinned"); -+ return -EBUSY; -+ } -+ -+ if (!drm_mm_node_allocated(&vma->node)) -+ return 0; -+ -+ if (i915_vma_is_map_and_fenceable(vma)) { -+ /* -+ * Check that we have flushed all writes through the GGTT -+ * before the unbind, other due to non-strict nature of those -+ * indirect writes they may end up referencing the GGTT PTE -+ * after the unbind. -+ */ -+ i915_vma_flush_writes(vma); -+ GEM_BUG_ON(i915_vma_has_ggtt_write(vma)); -+ -+ /* release the fence reg _after_ flushing */ -+ ret = i915_vma_put_fence(vma); -+ if (ret) -+ return ret; -+ -+ /* Force a pagefault for domain tracking on next user access */ -+ i915_vma_revoke_mmap(vma); -+ -+ __i915_vma_iounmap(vma); -+ vma->flags &= ~I915_VMA_CAN_FENCE; -+ } -+ GEM_BUG_ON(vma->fence); -+ GEM_BUG_ON(i915_vma_has_userfault(vma)); -+ -+ if (likely(!vma->vm->closed)) { -+ trace_i915_vma_unbind(vma); -+ vma->ops->unbind_vma(vma); -+ } -+ vma->flags &= ~(I915_VMA_GLOBAL_BIND | I915_VMA_LOCAL_BIND); -+ -+ i915_vma_remove(vma); -+ -+ return 0; -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_SELFTEST) -+#include "selftests/i915_vma.c" -+#endif -+ -+static void i915_global_vma_shrink(void) -+{ -+ kmem_cache_shrink(global.slab_vmas); -+} -+ -+static void i915_global_vma_exit(void) -+{ -+ kmem_cache_destroy(global.slab_vmas); -+} -+ -+static struct i915_global_vma global = { { -+ .shrink = i915_global_vma_shrink, -+ .exit = i915_global_vma_exit, -+} }; -+ -+int __init i915_global_vma_init(void) -+{ -+ global.slab_vmas = KMEM_CACHE(i915_vma, SLAB_HWCACHE_ALIGN); -+ if (!global.slab_vmas) -+ return -ENOMEM; -+ -+ i915_global_register(&global.base); -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/i915_vma.h b/drivers/gpu/drm/i915_legacy/i915_vma.h -new file mode 100644 -index 000000000000..6eab70953a57 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/i915_vma.h -@@ -0,0 +1,446 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef __I915_VMA_H__ -+#define __I915_VMA_H__ -+ -+#include -+#include -+ -+#include -+ -+#include "i915_gem_gtt.h" -+#include "i915_gem_fence_reg.h" -+#include "i915_gem_object.h" -+ -+#include "i915_active.h" -+#include "i915_request.h" -+ -+enum i915_cache_level; -+ -+/** -+ * A VMA represents a GEM BO that is bound into an address space. Therefore, a -+ * VMA's presence cannot be guaranteed before binding, or after unbinding the -+ * object into/from the address space. -+ * -+ * To make things as simple as possible (ie. no refcounting), a VMA's lifetime -+ * will always be <= an objects lifetime. So object refcounting should cover us. -+ */ -+struct i915_vma { -+ struct drm_mm_node node; -+ struct drm_i915_gem_object *obj; -+ struct i915_address_space *vm; -+ const struct i915_vma_ops *ops; -+ struct drm_i915_fence_reg *fence; -+ struct reservation_object *resv; /** Alias of obj->resv */ -+ struct sg_table *pages; -+ void __iomem *iomap; -+ void *private; /* owned by creator */ -+ u64 size; -+ u64 display_alignment; -+ struct i915_page_sizes page_sizes; -+ -+ u32 fence_size; -+ u32 fence_alignment; -+ -+ /** -+ * Count of the number of times this vma has been opened by different -+ * handles (but same file) for execbuf, i.e. the number of aliases -+ * that exist in the ctx->handle_vmas LUT for this vma. -+ */ -+ unsigned int open_count; -+ unsigned long flags; -+ /** -+ * How many users have pinned this object in GTT space. -+ * -+ * This is a tightly bound, fairly small number of users, so we -+ * stuff inside the flags field so that we can both check for overflow -+ * and detect a no-op i915_vma_pin() in a single check, while also -+ * pinning the vma. -+ * -+ * The worst case display setup would have the same vma pinned for -+ * use on each plane on each crtc, while also building the next atomic -+ * state and holding a pin for the length of the cleanup queue. In the -+ * future, the flip queue may be increased from 1. -+ * Estimated worst case: 3 [qlen] * 4 [max crtcs] * 7 [max planes] = 84 -+ * -+ * For GEM, the number of concurrent users for pwrite/pread is -+ * unbounded. For execbuffer, it is currently one but will in future -+ * be extended to allow multiple clients to pin vma concurrently. -+ * -+ * We also use suballocated pages, with each suballocation claiming -+ * its own pin on the shared vma. At present, this is limited to -+ * exclusive cachelines of a single page, so a maximum of 64 possible -+ * users. -+ */ -+#define I915_VMA_PIN_MASK 0xff -+#define I915_VMA_PIN_OVERFLOW BIT(8) -+ -+ /** Flags and address space this VMA is bound to */ -+#define I915_VMA_GLOBAL_BIND BIT(9) -+#define I915_VMA_LOCAL_BIND BIT(10) -+#define I915_VMA_BIND_MASK (I915_VMA_GLOBAL_BIND | I915_VMA_LOCAL_BIND | I915_VMA_PIN_OVERFLOW) -+ -+#define I915_VMA_GGTT BIT(11) -+#define I915_VMA_CAN_FENCE BIT(12) -+#define I915_VMA_CLOSED BIT(13) -+#define I915_VMA_USERFAULT_BIT 14 -+#define I915_VMA_USERFAULT BIT(I915_VMA_USERFAULT_BIT) -+#define I915_VMA_GGTT_WRITE BIT(15) -+ -+ struct i915_active active; -+ struct i915_active_request last_fence; -+ -+ /** -+ * Support different GGTT views into the same object. -+ * This means there can be multiple VMA mappings per object and per VM. -+ * i915_ggtt_view_type is used to distinguish between those entries. -+ * The default one of zero (I915_GGTT_VIEW_NORMAL) is default and also -+ * assumed in GEM functions which take no ggtt view parameter. -+ */ -+ struct i915_ggtt_view ggtt_view; -+ -+ /** This object's place on the active/inactive lists */ -+ struct list_head vm_link; -+ -+ struct list_head obj_link; /* Link in the object's VMA list */ -+ struct rb_node obj_node; -+ struct hlist_node obj_hash; -+ -+ /** This vma's place in the execbuf reservation list */ -+ struct list_head exec_link; -+ struct list_head reloc_link; -+ -+ /** This vma's place in the eviction list */ -+ struct list_head evict_link; -+ -+ struct list_head closed_link; -+ -+ /** -+ * Used for performing relocations during execbuffer insertion. -+ */ -+ unsigned int *exec_flags; -+ struct hlist_node exec_node; -+ u32 exec_handle; -+}; -+ -+struct i915_vma * -+i915_vma_instance(struct drm_i915_gem_object *obj, -+ struct i915_address_space *vm, -+ const struct i915_ggtt_view *view); -+ -+void i915_vma_unpin_and_release(struct i915_vma **p_vma, unsigned int flags); -+#define I915_VMA_RELEASE_MAP BIT(0) -+ -+static inline bool i915_vma_is_active(const struct i915_vma *vma) -+{ -+ return !i915_active_is_idle(&vma->active); -+} -+ -+int __must_check i915_vma_move_to_active(struct i915_vma *vma, -+ struct i915_request *rq, -+ unsigned int flags); -+ -+static inline bool i915_vma_is_ggtt(const struct i915_vma *vma) -+{ -+ return vma->flags & I915_VMA_GGTT; -+} -+ -+static inline bool i915_vma_has_ggtt_write(const struct i915_vma *vma) -+{ -+ return vma->flags & I915_VMA_GGTT_WRITE; -+} -+ -+static inline void i915_vma_set_ggtt_write(struct i915_vma *vma) -+{ -+ GEM_BUG_ON(!i915_vma_is_ggtt(vma)); -+ vma->flags |= I915_VMA_GGTT_WRITE; -+} -+ -+static inline void i915_vma_unset_ggtt_write(struct i915_vma *vma) -+{ -+ vma->flags &= ~I915_VMA_GGTT_WRITE; -+} -+ -+void i915_vma_flush_writes(struct i915_vma *vma); -+ -+static inline bool i915_vma_is_map_and_fenceable(const struct i915_vma *vma) -+{ -+ return vma->flags & I915_VMA_CAN_FENCE; -+} -+ -+static inline bool i915_vma_is_closed(const struct i915_vma *vma) -+{ -+ return vma->flags & I915_VMA_CLOSED; -+} -+ -+static inline bool i915_vma_set_userfault(struct i915_vma *vma) -+{ -+ GEM_BUG_ON(!i915_vma_is_map_and_fenceable(vma)); -+ return __test_and_set_bit(I915_VMA_USERFAULT_BIT, &vma->flags); -+} -+ -+static inline void i915_vma_unset_userfault(struct i915_vma *vma) -+{ -+ return __clear_bit(I915_VMA_USERFAULT_BIT, &vma->flags); -+} -+ -+static inline bool i915_vma_has_userfault(const struct i915_vma *vma) -+{ -+ return test_bit(I915_VMA_USERFAULT_BIT, &vma->flags); -+} -+ -+static inline u32 i915_ggtt_offset(const struct i915_vma *vma) -+{ -+ GEM_BUG_ON(!i915_vma_is_ggtt(vma)); -+ GEM_BUG_ON(!vma->node.allocated); -+ GEM_BUG_ON(upper_32_bits(vma->node.start)); -+ GEM_BUG_ON(upper_32_bits(vma->node.start + vma->node.size - 1)); -+ return lower_32_bits(vma->node.start); -+} -+ -+static inline u32 i915_ggtt_pin_bias(struct i915_vma *vma) -+{ -+ return i915_vm_to_ggtt(vma->vm)->pin_bias; -+} -+ -+static inline struct i915_vma *i915_vma_get(struct i915_vma *vma) -+{ -+ i915_gem_object_get(vma->obj); -+ return vma; -+} -+ -+static inline void i915_vma_put(struct i915_vma *vma) -+{ -+ i915_gem_object_put(vma->obj); -+} -+ -+static __always_inline ptrdiff_t ptrdiff(const void *a, const void *b) -+{ -+ return a - b; -+} -+ -+static inline long -+i915_vma_compare(struct i915_vma *vma, -+ struct i915_address_space *vm, -+ const struct i915_ggtt_view *view) -+{ -+ ptrdiff_t cmp; -+ -+ GEM_BUG_ON(view && !i915_is_ggtt(vm)); -+ -+ cmp = ptrdiff(vma->vm, vm); -+ if (cmp) -+ return cmp; -+ -+ BUILD_BUG_ON(I915_GGTT_VIEW_NORMAL != 0); -+ cmp = vma->ggtt_view.type; -+ if (!view) -+ return cmp; -+ -+ cmp -= view->type; -+ if (cmp) -+ return cmp; -+ -+ assert_i915_gem_gtt_types(); -+ -+ /* ggtt_view.type also encodes its size so that we both distinguish -+ * different views using it as a "type" and also use a compact (no -+ * accessing of uninitialised padding bytes) memcmp without storing -+ * an extra parameter or adding more code. -+ * -+ * To ensure that the memcmp is valid for all branches of the union, -+ * even though the code looks like it is just comparing one branch, -+ * we assert above that all branches have the same address, and that -+ * each branch has a unique type/size. -+ */ -+ BUILD_BUG_ON(I915_GGTT_VIEW_NORMAL >= I915_GGTT_VIEW_PARTIAL); -+ BUILD_BUG_ON(I915_GGTT_VIEW_PARTIAL >= I915_GGTT_VIEW_ROTATED); -+ BUILD_BUG_ON(offsetof(typeof(*view), rotated) != -+ offsetof(typeof(*view), partial)); -+ return memcmp(&vma->ggtt_view.partial, &view->partial, view->type); -+} -+ -+int i915_vma_bind(struct i915_vma *vma, enum i915_cache_level cache_level, -+ u32 flags); -+bool i915_gem_valid_gtt_space(struct i915_vma *vma, unsigned long cache_level); -+bool i915_vma_misplaced(const struct i915_vma *vma, -+ u64 size, u64 alignment, u64 flags); -+void __i915_vma_set_map_and_fenceable(struct i915_vma *vma); -+void i915_vma_revoke_mmap(struct i915_vma *vma); -+int __must_check i915_vma_unbind(struct i915_vma *vma); -+void i915_vma_unlink_ctx(struct i915_vma *vma); -+void i915_vma_close(struct i915_vma *vma); -+void i915_vma_reopen(struct i915_vma *vma); -+void i915_vma_destroy(struct i915_vma *vma); -+ -+int __i915_vma_do_pin(struct i915_vma *vma, -+ u64 size, u64 alignment, u64 flags); -+static inline int __must_check -+i915_vma_pin(struct i915_vma *vma, u64 size, u64 alignment, u64 flags) -+{ -+ BUILD_BUG_ON(PIN_MBZ != I915_VMA_PIN_OVERFLOW); -+ BUILD_BUG_ON(PIN_GLOBAL != I915_VMA_GLOBAL_BIND); -+ BUILD_BUG_ON(PIN_USER != I915_VMA_LOCAL_BIND); -+ -+ /* Pin early to prevent the shrinker/eviction logic from destroying -+ * our vma as we insert and bind. -+ */ -+ if (likely(((++vma->flags ^ flags) & I915_VMA_BIND_MASK) == 0)) { -+ GEM_BUG_ON(!drm_mm_node_allocated(&vma->node)); -+ GEM_BUG_ON(i915_vma_misplaced(vma, size, alignment, flags)); -+ return 0; -+ } -+ -+ return __i915_vma_do_pin(vma, size, alignment, flags); -+} -+ -+static inline int i915_vma_pin_count(const struct i915_vma *vma) -+{ -+ return vma->flags & I915_VMA_PIN_MASK; -+} -+ -+static inline bool i915_vma_is_pinned(const struct i915_vma *vma) -+{ -+ return i915_vma_pin_count(vma); -+} -+ -+static inline void __i915_vma_pin(struct i915_vma *vma) -+{ -+ vma->flags++; -+ GEM_BUG_ON(vma->flags & I915_VMA_PIN_OVERFLOW); -+} -+ -+static inline void __i915_vma_unpin(struct i915_vma *vma) -+{ -+ vma->flags--; -+} -+ -+static inline void i915_vma_unpin(struct i915_vma *vma) -+{ -+ GEM_BUG_ON(!i915_vma_is_pinned(vma)); -+ GEM_BUG_ON(!drm_mm_node_allocated(&vma->node)); -+ __i915_vma_unpin(vma); -+} -+ -+static inline bool i915_vma_is_bound(const struct i915_vma *vma, -+ unsigned int where) -+{ -+ return vma->flags & where; -+} -+ -+/** -+ * i915_vma_pin_iomap - calls ioremap_wc to map the GGTT VMA via the aperture -+ * @vma: VMA to iomap -+ * -+ * The passed in VMA has to be pinned in the global GTT mappable region. -+ * An extra pinning of the VMA is acquired for the return iomapping, -+ * the caller must call i915_vma_unpin_iomap to relinquish the pinning -+ * after the iomapping is no longer required. -+ * -+ * Callers must hold the struct_mutex. -+ * -+ * Returns a valid iomapped pointer or ERR_PTR. -+ */ -+void __iomem *i915_vma_pin_iomap(struct i915_vma *vma); -+#define IO_ERR_PTR(x) ((void __iomem *)ERR_PTR(x)) -+ -+/** -+ * i915_vma_unpin_iomap - unpins the mapping returned from i915_vma_iomap -+ * @vma: VMA to unpin -+ * -+ * Unpins the previously iomapped VMA from i915_vma_pin_iomap(). -+ * -+ * Callers must hold the struct_mutex. This function is only valid to be -+ * called on a VMA previously iomapped by the caller with i915_vma_pin_iomap(). -+ */ -+void i915_vma_unpin_iomap(struct i915_vma *vma); -+ -+static inline struct page *i915_vma_first_page(struct i915_vma *vma) -+{ -+ GEM_BUG_ON(!vma->pages); -+ return sg_page(vma->pages->sgl); -+} -+ -+/** -+ * i915_vma_pin_fence - pin fencing state -+ * @vma: vma to pin fencing for -+ * -+ * This pins the fencing state (whether tiled or untiled) to make sure the -+ * vma (and its object) is ready to be used as a scanout target. Fencing -+ * status must be synchronize first by calling i915_vma_get_fence(): -+ * -+ * The resulting fence pin reference must be released again with -+ * i915_vma_unpin_fence(). -+ * -+ * Returns: -+ * -+ * True if the vma has a fence, false otherwise. -+ */ -+int i915_vma_pin_fence(struct i915_vma *vma); -+int __must_check i915_vma_put_fence(struct i915_vma *vma); -+ -+static inline void __i915_vma_unpin_fence(struct i915_vma *vma) -+{ -+ GEM_BUG_ON(vma->fence->pin_count <= 0); -+ vma->fence->pin_count--; -+} -+ -+/** -+ * i915_vma_unpin_fence - unpin fencing state -+ * @vma: vma to unpin fencing for -+ * -+ * This releases the fence pin reference acquired through -+ * i915_vma_pin_fence. It will handle both objects with and without an -+ * attached fence correctly, callers do not need to distinguish this. -+ */ -+static inline void -+i915_vma_unpin_fence(struct i915_vma *vma) -+{ -+ /* lockdep_assert_held(&vma->vm->i915->drm.struct_mutex); */ -+ if (vma->fence) -+ __i915_vma_unpin_fence(vma); -+} -+ -+void i915_vma_parked(struct drm_i915_private *i915); -+ -+#define for_each_until(cond) if (cond) break; else -+ -+/** -+ * for_each_ggtt_vma - Iterate over the GGTT VMA belonging to an object. -+ * @V: the #i915_vma iterator -+ * @OBJ: the #drm_i915_gem_object -+ * -+ * GGTT VMA are placed at the being of the object's vma_list, see -+ * vma_create(), so we can stop our walk as soon as we see a ppgtt VMA, -+ * or the list is empty ofc. -+ */ -+#define for_each_ggtt_vma(V, OBJ) \ -+ list_for_each_entry(V, &(OBJ)->vma.list, obj_link) \ -+ for_each_until(!i915_vma_is_ggtt(V)) -+ -+struct i915_vma *i915_vma_alloc(void); -+void i915_vma_free(struct i915_vma *vma); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/icl_dsi.c b/drivers/gpu/drm/i915_legacy/icl_dsi.c -new file mode 100644 -index 000000000000..9d962ea1e635 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/icl_dsi.c -@@ -0,0 +1,1464 @@ -+/* -+ * Copyright © 2018 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Madhav Chauhan -+ * Jani Nikula -+ */ -+ -+#include -+#include -+ -+#include "intel_connector.h" -+#include "intel_ddi.h" -+#include "intel_dsi.h" -+#include "intel_panel.h" -+ -+static inline int header_credits_available(struct drm_i915_private *dev_priv, -+ enum transcoder dsi_trans) -+{ -+ return (I915_READ(DSI_CMD_TXCTL(dsi_trans)) & FREE_HEADER_CREDIT_MASK) -+ >> FREE_HEADER_CREDIT_SHIFT; -+} -+ -+static inline int payload_credits_available(struct drm_i915_private *dev_priv, -+ enum transcoder dsi_trans) -+{ -+ return (I915_READ(DSI_CMD_TXCTL(dsi_trans)) & FREE_PLOAD_CREDIT_MASK) -+ >> FREE_PLOAD_CREDIT_SHIFT; -+} -+ -+static void wait_for_header_credits(struct drm_i915_private *dev_priv, -+ enum transcoder dsi_trans) -+{ -+ if (wait_for_us(header_credits_available(dev_priv, dsi_trans) >= -+ MAX_HEADER_CREDIT, 100)) -+ DRM_ERROR("DSI header credits not released\n"); -+} -+ -+static void wait_for_payload_credits(struct drm_i915_private *dev_priv, -+ enum transcoder dsi_trans) -+{ -+ if (wait_for_us(payload_credits_available(dev_priv, dsi_trans) >= -+ MAX_PLOAD_CREDIT, 100)) -+ DRM_ERROR("DSI payload credits not released\n"); -+} -+ -+static enum transcoder dsi_port_to_transcoder(enum port port) -+{ -+ if (port == PORT_A) -+ return TRANSCODER_DSI_0; -+ else -+ return TRANSCODER_DSI_1; -+} -+ -+static void wait_for_cmds_dispatched_to_panel(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ struct mipi_dsi_device *dsi; -+ enum port port; -+ enum transcoder dsi_trans; -+ int ret; -+ -+ /* wait for header/payload credits to be released */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ wait_for_header_credits(dev_priv, dsi_trans); -+ wait_for_payload_credits(dev_priv, dsi_trans); -+ } -+ -+ /* send nop DCS command */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi = intel_dsi->dsi_hosts[port]->device; -+ dsi->mode_flags |= MIPI_DSI_MODE_LPM; -+ dsi->channel = 0; -+ ret = mipi_dsi_dcs_nop(dsi); -+ if (ret < 0) -+ DRM_ERROR("error sending DCS NOP command\n"); -+ } -+ -+ /* wait for header credits to be released */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ wait_for_header_credits(dev_priv, dsi_trans); -+ } -+ -+ /* wait for LP TX in progress bit to be cleared */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ if (wait_for_us(!(I915_READ(DSI_LP_MSG(dsi_trans)) & -+ LPTX_IN_PROGRESS), 20)) -+ DRM_ERROR("LPTX bit not cleared\n"); -+ } -+} -+ -+static bool add_payld_to_queue(struct intel_dsi_host *host, const u8 *data, -+ u32 len) -+{ -+ struct intel_dsi *intel_dsi = host->intel_dsi; -+ struct drm_i915_private *dev_priv = to_i915(intel_dsi->base.base.dev); -+ enum transcoder dsi_trans = dsi_port_to_transcoder(host->port); -+ int free_credits; -+ int i, j; -+ -+ for (i = 0; i < len; i += 4) { -+ u32 tmp = 0; -+ -+ free_credits = payload_credits_available(dev_priv, dsi_trans); -+ if (free_credits < 1) { -+ DRM_ERROR("Payload credit not available\n"); -+ return false; -+ } -+ -+ for (j = 0; j < min_t(u32, len - i, 4); j++) -+ tmp |= *data++ << 8 * j; -+ -+ I915_WRITE(DSI_CMD_TXPYLD(dsi_trans), tmp); -+ } -+ -+ return true; -+} -+ -+static int dsi_send_pkt_hdr(struct intel_dsi_host *host, -+ struct mipi_dsi_packet pkt, bool enable_lpdt) -+{ -+ struct intel_dsi *intel_dsi = host->intel_dsi; -+ struct drm_i915_private *dev_priv = to_i915(intel_dsi->base.base.dev); -+ enum transcoder dsi_trans = dsi_port_to_transcoder(host->port); -+ u32 tmp; -+ int free_credits; -+ -+ /* check if header credit available */ -+ free_credits = header_credits_available(dev_priv, dsi_trans); -+ if (free_credits < 1) { -+ DRM_ERROR("send pkt header failed, not enough hdr credits\n"); -+ return -1; -+ } -+ -+ tmp = I915_READ(DSI_CMD_TXHDR(dsi_trans)); -+ -+ if (pkt.payload) -+ tmp |= PAYLOAD_PRESENT; -+ else -+ tmp &= ~PAYLOAD_PRESENT; -+ -+ tmp &= ~VBLANK_FENCE; -+ -+ if (enable_lpdt) -+ tmp |= LP_DATA_TRANSFER; -+ -+ tmp &= ~(PARAM_WC_MASK | VC_MASK | DT_MASK); -+ tmp |= ((pkt.header[0] & VC_MASK) << VC_SHIFT); -+ tmp |= ((pkt.header[0] & DT_MASK) << DT_SHIFT); -+ tmp |= (pkt.header[1] << PARAM_WC_LOWER_SHIFT); -+ tmp |= (pkt.header[2] << PARAM_WC_UPPER_SHIFT); -+ I915_WRITE(DSI_CMD_TXHDR(dsi_trans), tmp); -+ -+ return 0; -+} -+ -+static int dsi_send_pkt_payld(struct intel_dsi_host *host, -+ struct mipi_dsi_packet pkt) -+{ -+ /* payload queue can accept *256 bytes*, check limit */ -+ if (pkt.payload_length > MAX_PLOAD_CREDIT * 4) { -+ DRM_ERROR("payload size exceeds max queue limit\n"); -+ return -1; -+ } -+ -+ /* load data into command payload queue */ -+ if (!add_payld_to_queue(host, pkt.payload, -+ pkt.payload_length)) { -+ DRM_ERROR("adding payload to queue failed\n"); -+ return -1; -+ } -+ -+ return 0; -+} -+ -+static void dsi_program_swing_and_deemphasis(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ enum port port; -+ u32 tmp; -+ int lane; -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ -+ /* -+ * Program voltage swing and pre-emphasis level values as per -+ * table in BSPEC under DDI buffer programing -+ */ -+ tmp = I915_READ(ICL_PORT_TX_DW5_LN0(port)); -+ tmp &= ~(SCALING_MODE_SEL_MASK | RTERM_SELECT_MASK); -+ tmp |= SCALING_MODE_SEL(0x2); -+ tmp |= TAP2_DISABLE | TAP3_DISABLE; -+ tmp |= RTERM_SELECT(0x6); -+ I915_WRITE(ICL_PORT_TX_DW5_GRP(port), tmp); -+ -+ tmp = I915_READ(ICL_PORT_TX_DW5_AUX(port)); -+ tmp &= ~(SCALING_MODE_SEL_MASK | RTERM_SELECT_MASK); -+ tmp |= SCALING_MODE_SEL(0x2); -+ tmp |= TAP2_DISABLE | TAP3_DISABLE; -+ tmp |= RTERM_SELECT(0x6); -+ I915_WRITE(ICL_PORT_TX_DW5_AUX(port), tmp); -+ -+ tmp = I915_READ(ICL_PORT_TX_DW2_LN0(port)); -+ tmp &= ~(SWING_SEL_LOWER_MASK | SWING_SEL_UPPER_MASK | -+ RCOMP_SCALAR_MASK); -+ tmp |= SWING_SEL_UPPER(0x2); -+ tmp |= SWING_SEL_LOWER(0x2); -+ tmp |= RCOMP_SCALAR(0x98); -+ I915_WRITE(ICL_PORT_TX_DW2_GRP(port), tmp); -+ -+ tmp = I915_READ(ICL_PORT_TX_DW2_AUX(port)); -+ tmp &= ~(SWING_SEL_LOWER_MASK | SWING_SEL_UPPER_MASK | -+ RCOMP_SCALAR_MASK); -+ tmp |= SWING_SEL_UPPER(0x2); -+ tmp |= SWING_SEL_LOWER(0x2); -+ tmp |= RCOMP_SCALAR(0x98); -+ I915_WRITE(ICL_PORT_TX_DW2_AUX(port), tmp); -+ -+ tmp = I915_READ(ICL_PORT_TX_DW4_AUX(port)); -+ tmp &= ~(POST_CURSOR_1_MASK | POST_CURSOR_2_MASK | -+ CURSOR_COEFF_MASK); -+ tmp |= POST_CURSOR_1(0x0); -+ tmp |= POST_CURSOR_2(0x0); -+ tmp |= CURSOR_COEFF(0x3f); -+ I915_WRITE(ICL_PORT_TX_DW4_AUX(port), tmp); -+ -+ for (lane = 0; lane <= 3; lane++) { -+ /* Bspec: must not use GRP register for write */ -+ tmp = I915_READ(ICL_PORT_TX_DW4_LN(lane, port)); -+ tmp &= ~(POST_CURSOR_1_MASK | POST_CURSOR_2_MASK | -+ CURSOR_COEFF_MASK); -+ tmp |= POST_CURSOR_1(0x0); -+ tmp |= POST_CURSOR_2(0x0); -+ tmp |= CURSOR_COEFF(0x3f); -+ I915_WRITE(ICL_PORT_TX_DW4_LN(lane, port), tmp); -+ } -+ } -+} -+ -+static void configure_dual_link_mode(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ u32 dss_ctl1; -+ -+ dss_ctl1 = I915_READ(DSS_CTL1); -+ dss_ctl1 |= SPLITTER_ENABLE; -+ dss_ctl1 &= ~OVERLAP_PIXELS_MASK; -+ dss_ctl1 |= OVERLAP_PIXELS(intel_dsi->pixel_overlap); -+ -+ if (intel_dsi->dual_link == DSI_DUAL_LINK_FRONT_BACK) { -+ const struct drm_display_mode *adjusted_mode = -+ &pipe_config->base.adjusted_mode; -+ u32 dss_ctl2; -+ u16 hactive = adjusted_mode->crtc_hdisplay; -+ u16 dl_buffer_depth; -+ -+ dss_ctl1 &= ~DUAL_LINK_MODE_INTERLEAVE; -+ dl_buffer_depth = hactive / 2 + intel_dsi->pixel_overlap; -+ -+ if (dl_buffer_depth > MAX_DL_BUFFER_TARGET_DEPTH) -+ DRM_ERROR("DL buffer depth exceed max value\n"); -+ -+ dss_ctl1 &= ~LEFT_DL_BUF_TARGET_DEPTH_MASK; -+ dss_ctl1 |= LEFT_DL_BUF_TARGET_DEPTH(dl_buffer_depth); -+ dss_ctl2 = I915_READ(DSS_CTL2); -+ dss_ctl2 &= ~RIGHT_DL_BUF_TARGET_DEPTH_MASK; -+ dss_ctl2 |= RIGHT_DL_BUF_TARGET_DEPTH(dl_buffer_depth); -+ I915_WRITE(DSS_CTL2, dss_ctl2); -+ } else { -+ /* Interleave */ -+ dss_ctl1 |= DUAL_LINK_MODE_INTERLEAVE; -+ } -+ -+ I915_WRITE(DSS_CTL1, dss_ctl1); -+} -+ -+static void gen11_dsi_program_esc_clk_div(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ enum port port; -+ u32 bpp = mipi_dsi_pixel_format_to_bpp(intel_dsi->pixel_format); -+ u32 afe_clk_khz; /* 8X Clock */ -+ u32 esc_clk_div_m; -+ -+ afe_clk_khz = DIV_ROUND_CLOSEST(intel_dsi->pclk * bpp, -+ intel_dsi->lane_count); -+ -+ esc_clk_div_m = DIV_ROUND_UP(afe_clk_khz, DSI_MAX_ESC_CLK); -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ I915_WRITE(ICL_DSI_ESC_CLK_DIV(port), -+ esc_clk_div_m & ICL_ESC_CLK_DIV_MASK); -+ POSTING_READ(ICL_DSI_ESC_CLK_DIV(port)); -+ } -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ I915_WRITE(ICL_DPHY_ESC_CLK_DIV(port), -+ esc_clk_div_m & ICL_ESC_CLK_DIV_MASK); -+ POSTING_READ(ICL_DPHY_ESC_CLK_DIV(port)); -+ } -+} -+ -+static void get_dsi_io_power_domains(struct drm_i915_private *dev_priv, -+ struct intel_dsi *intel_dsi) -+{ -+ enum port port; -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ WARN_ON(intel_dsi->io_wakeref[port]); -+ intel_dsi->io_wakeref[port] = -+ intel_display_power_get(dev_priv, -+ port == PORT_A ? -+ POWER_DOMAIN_PORT_DDI_A_IO : -+ POWER_DOMAIN_PORT_DDI_B_IO); -+ } -+} -+ -+static void gen11_dsi_enable_io_power(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ enum port port; -+ u32 tmp; -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp = I915_READ(ICL_DSI_IO_MODECTL(port)); -+ tmp |= COMBO_PHY_MODE_DSI; -+ I915_WRITE(ICL_DSI_IO_MODECTL(port), tmp); -+ } -+ -+ get_dsi_io_power_domains(dev_priv, intel_dsi); -+} -+ -+static void gen11_dsi_power_up_lanes(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ enum port port; -+ u32 tmp; -+ u32 lane_mask; -+ -+ switch (intel_dsi->lane_count) { -+ case 1: -+ lane_mask = PWR_DOWN_LN_3_1_0; -+ break; -+ case 2: -+ lane_mask = PWR_DOWN_LN_3_1; -+ break; -+ case 3: -+ lane_mask = PWR_DOWN_LN_3; -+ break; -+ case 4: -+ default: -+ lane_mask = PWR_UP_ALL_LANES; -+ break; -+ } -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp = I915_READ(ICL_PORT_CL_DW10(port)); -+ tmp &= ~PWR_DOWN_LN_MASK; -+ I915_WRITE(ICL_PORT_CL_DW10(port), tmp | lane_mask); -+ } -+} -+ -+static void gen11_dsi_config_phy_lanes_sequence(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ enum port port; -+ u32 tmp; -+ int lane; -+ -+ /* Step 4b(i) set loadgen select for transmit and aux lanes */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp = I915_READ(ICL_PORT_TX_DW4_AUX(port)); -+ tmp &= ~LOADGEN_SELECT; -+ I915_WRITE(ICL_PORT_TX_DW4_AUX(port), tmp); -+ for (lane = 0; lane <= 3; lane++) { -+ tmp = I915_READ(ICL_PORT_TX_DW4_LN(lane, port)); -+ tmp &= ~LOADGEN_SELECT; -+ if (lane != 2) -+ tmp |= LOADGEN_SELECT; -+ I915_WRITE(ICL_PORT_TX_DW4_LN(lane, port), tmp); -+ } -+ } -+ -+ /* Step 4b(ii) set latency optimization for transmit and aux lanes */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp = I915_READ(ICL_PORT_TX_DW2_AUX(port)); -+ tmp &= ~FRC_LATENCY_OPTIM_MASK; -+ tmp |= FRC_LATENCY_OPTIM_VAL(0x5); -+ I915_WRITE(ICL_PORT_TX_DW2_AUX(port), tmp); -+ tmp = I915_READ(ICL_PORT_TX_DW2_LN0(port)); -+ tmp &= ~FRC_LATENCY_OPTIM_MASK; -+ tmp |= FRC_LATENCY_OPTIM_VAL(0x5); -+ I915_WRITE(ICL_PORT_TX_DW2_GRP(port), tmp); -+ } -+ -+} -+ -+static void gen11_dsi_voltage_swing_program_seq(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ u32 tmp; -+ enum port port; -+ -+ /* clear common keeper enable bit */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp = I915_READ(ICL_PORT_PCS_DW1_LN0(port)); -+ tmp &= ~COMMON_KEEPER_EN; -+ I915_WRITE(ICL_PORT_PCS_DW1_GRP(port), tmp); -+ tmp = I915_READ(ICL_PORT_PCS_DW1_AUX(port)); -+ tmp &= ~COMMON_KEEPER_EN; -+ I915_WRITE(ICL_PORT_PCS_DW1_AUX(port), tmp); -+ } -+ -+ /* -+ * Set SUS Clock Config bitfield to 11b -+ * Note: loadgen select program is done -+ * as part of lane phy sequence configuration -+ */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp = I915_READ(ICL_PORT_CL_DW5(port)); -+ tmp |= SUS_CLOCK_CONFIG; -+ I915_WRITE(ICL_PORT_CL_DW5(port), tmp); -+ } -+ -+ /* Clear training enable to change swing values */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp = I915_READ(ICL_PORT_TX_DW5_LN0(port)); -+ tmp &= ~TX_TRAINING_EN; -+ I915_WRITE(ICL_PORT_TX_DW5_GRP(port), tmp); -+ tmp = I915_READ(ICL_PORT_TX_DW5_AUX(port)); -+ tmp &= ~TX_TRAINING_EN; -+ I915_WRITE(ICL_PORT_TX_DW5_AUX(port), tmp); -+ } -+ -+ /* Program swing and de-emphasis */ -+ dsi_program_swing_and_deemphasis(encoder); -+ -+ /* Set training enable to trigger update */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp = I915_READ(ICL_PORT_TX_DW5_LN0(port)); -+ tmp |= TX_TRAINING_EN; -+ I915_WRITE(ICL_PORT_TX_DW5_GRP(port), tmp); -+ tmp = I915_READ(ICL_PORT_TX_DW5_AUX(port)); -+ tmp |= TX_TRAINING_EN; -+ I915_WRITE(ICL_PORT_TX_DW5_AUX(port), tmp); -+ } -+} -+ -+static void gen11_dsi_enable_ddi_buffer(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ u32 tmp; -+ enum port port; -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp = I915_READ(DDI_BUF_CTL(port)); -+ tmp |= DDI_BUF_CTL_ENABLE; -+ I915_WRITE(DDI_BUF_CTL(port), tmp); -+ -+ if (wait_for_us(!(I915_READ(DDI_BUF_CTL(port)) & -+ DDI_BUF_IS_IDLE), -+ 500)) -+ DRM_ERROR("DDI port:%c buffer idle\n", port_name(port)); -+ } -+} -+ -+static void gen11_dsi_setup_dphy_timings(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ u32 tmp; -+ enum port port; -+ -+ /* Program T-INIT master registers */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp = I915_READ(ICL_DSI_T_INIT_MASTER(port)); -+ tmp &= ~MASTER_INIT_TIMER_MASK; -+ tmp |= intel_dsi->init_count; -+ I915_WRITE(ICL_DSI_T_INIT_MASTER(port), tmp); -+ } -+ -+ /* Program DPHY clock lanes timings */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ I915_WRITE(DPHY_CLK_TIMING_PARAM(port), intel_dsi->dphy_reg); -+ -+ /* shadow register inside display core */ -+ I915_WRITE(DSI_CLK_TIMING_PARAM(port), intel_dsi->dphy_reg); -+ } -+ -+ /* Program DPHY data lanes timings */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ I915_WRITE(DPHY_DATA_TIMING_PARAM(port), -+ intel_dsi->dphy_data_lane_reg); -+ -+ /* shadow register inside display core */ -+ I915_WRITE(DSI_DATA_TIMING_PARAM(port), -+ intel_dsi->dphy_data_lane_reg); -+ } -+ -+ /* -+ * If DSI link operating at or below an 800 MHz, -+ * TA_SURE should be override and programmed to -+ * a value '0' inside TA_PARAM_REGISTERS otherwise -+ * leave all fields at HW default values. -+ */ -+ if (intel_dsi_bitrate(intel_dsi) <= 800000) { -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp = I915_READ(DPHY_TA_TIMING_PARAM(port)); -+ tmp &= ~TA_SURE_MASK; -+ tmp |= TA_SURE_OVERRIDE | TA_SURE(0); -+ I915_WRITE(DPHY_TA_TIMING_PARAM(port), tmp); -+ -+ /* shadow register inside display core */ -+ tmp = I915_READ(DSI_TA_TIMING_PARAM(port)); -+ tmp &= ~TA_SURE_MASK; -+ tmp |= TA_SURE_OVERRIDE | TA_SURE(0); -+ I915_WRITE(DSI_TA_TIMING_PARAM(port), tmp); -+ } -+ } -+} -+ -+static void gen11_dsi_gate_clocks(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ u32 tmp; -+ enum port port; -+ -+ mutex_lock(&dev_priv->dpll_lock); -+ tmp = I915_READ(DPCLKA_CFGCR0_ICL); -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp |= DPCLKA_CFGCR0_DDI_CLK_OFF(port); -+ } -+ -+ I915_WRITE(DPCLKA_CFGCR0_ICL, tmp); -+ mutex_unlock(&dev_priv->dpll_lock); -+} -+ -+static void gen11_dsi_ungate_clocks(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ u32 tmp; -+ enum port port; -+ -+ mutex_lock(&dev_priv->dpll_lock); -+ tmp = I915_READ(DPCLKA_CFGCR0_ICL); -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp &= ~DPCLKA_CFGCR0_DDI_CLK_OFF(port); -+ } -+ -+ I915_WRITE(DPCLKA_CFGCR0_ICL, tmp); -+ mutex_unlock(&dev_priv->dpll_lock); -+} -+ -+static void gen11_dsi_map_pll(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ struct intel_shared_dpll *pll = crtc_state->shared_dpll; -+ enum port port; -+ u32 val; -+ -+ mutex_lock(&dev_priv->dpll_lock); -+ -+ val = I915_READ(DPCLKA_CFGCR0_ICL); -+ for_each_dsi_port(port, intel_dsi->ports) { -+ val &= ~DPCLKA_CFGCR0_DDI_CLK_SEL_MASK(port); -+ val |= DPCLKA_CFGCR0_DDI_CLK_SEL(pll->info->id, port); -+ } -+ I915_WRITE(DPCLKA_CFGCR0_ICL, val); -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ val &= ~DPCLKA_CFGCR0_DDI_CLK_OFF(port); -+ } -+ I915_WRITE(DPCLKA_CFGCR0_ICL, val); -+ -+ POSTING_READ(DPCLKA_CFGCR0_ICL); -+ -+ mutex_unlock(&dev_priv->dpll_lock); -+} -+ -+static void -+gen11_dsi_configure_transcoder(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ struct intel_crtc *intel_crtc = to_intel_crtc(pipe_config->base.crtc); -+ enum pipe pipe = intel_crtc->pipe; -+ u32 tmp; -+ enum port port; -+ enum transcoder dsi_trans; -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ tmp = I915_READ(DSI_TRANS_FUNC_CONF(dsi_trans)); -+ -+ if (intel_dsi->eotp_pkt) -+ tmp &= ~EOTP_DISABLED; -+ else -+ tmp |= EOTP_DISABLED; -+ -+ /* enable link calibration if freq > 1.5Gbps */ -+ if (intel_dsi_bitrate(intel_dsi) >= 1500 * 1000) { -+ tmp &= ~LINK_CALIBRATION_MASK; -+ tmp |= CALIBRATION_ENABLED_INITIAL_ONLY; -+ } -+ -+ /* configure continuous clock */ -+ tmp &= ~CONTINUOUS_CLK_MASK; -+ if (intel_dsi->clock_stop) -+ tmp |= CLK_ENTER_LP_AFTER_DATA; -+ else -+ tmp |= CLK_HS_CONTINUOUS; -+ -+ /* configure buffer threshold limit to minimum */ -+ tmp &= ~PIX_BUF_THRESHOLD_MASK; -+ tmp |= PIX_BUF_THRESHOLD_1_4; -+ -+ /* set virtual channel to '0' */ -+ tmp &= ~PIX_VIRT_CHAN_MASK; -+ tmp |= PIX_VIRT_CHAN(0); -+ -+ /* program BGR transmission */ -+ if (intel_dsi->bgr_enabled) -+ tmp |= BGR_TRANSMISSION; -+ -+ /* select pixel format */ -+ tmp &= ~PIX_FMT_MASK; -+ switch (intel_dsi->pixel_format) { -+ default: -+ MISSING_CASE(intel_dsi->pixel_format); -+ /* fallthrough */ -+ case MIPI_DSI_FMT_RGB565: -+ tmp |= PIX_FMT_RGB565; -+ break; -+ case MIPI_DSI_FMT_RGB666_PACKED: -+ tmp |= PIX_FMT_RGB666_PACKED; -+ break; -+ case MIPI_DSI_FMT_RGB666: -+ tmp |= PIX_FMT_RGB666_LOOSE; -+ break; -+ case MIPI_DSI_FMT_RGB888: -+ tmp |= PIX_FMT_RGB888; -+ break; -+ } -+ -+ /* program DSI operation mode */ -+ if (is_vid_mode(intel_dsi)) { -+ tmp &= ~OP_MODE_MASK; -+ switch (intel_dsi->video_mode_format) { -+ default: -+ MISSING_CASE(intel_dsi->video_mode_format); -+ /* fallthrough */ -+ case VIDEO_MODE_NON_BURST_WITH_SYNC_EVENTS: -+ tmp |= VIDEO_MODE_SYNC_EVENT; -+ break; -+ case VIDEO_MODE_NON_BURST_WITH_SYNC_PULSE: -+ tmp |= VIDEO_MODE_SYNC_PULSE; -+ break; -+ } -+ } -+ -+ I915_WRITE(DSI_TRANS_FUNC_CONF(dsi_trans), tmp); -+ } -+ -+ /* enable port sync mode if dual link */ -+ if (intel_dsi->dual_link) { -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ tmp = I915_READ(TRANS_DDI_FUNC_CTL2(dsi_trans)); -+ tmp |= PORT_SYNC_MODE_ENABLE; -+ I915_WRITE(TRANS_DDI_FUNC_CTL2(dsi_trans), tmp); -+ } -+ -+ /* configure stream splitting */ -+ configure_dual_link_mode(encoder, pipe_config); -+ } -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ -+ /* select data lane width */ -+ tmp = I915_READ(TRANS_DDI_FUNC_CTL(dsi_trans)); -+ tmp &= ~DDI_PORT_WIDTH_MASK; -+ tmp |= DDI_PORT_WIDTH(intel_dsi->lane_count); -+ -+ /* select input pipe */ -+ tmp &= ~TRANS_DDI_EDP_INPUT_MASK; -+ switch (pipe) { -+ default: -+ MISSING_CASE(pipe); -+ /* fallthrough */ -+ case PIPE_A: -+ tmp |= TRANS_DDI_EDP_INPUT_A_ON; -+ break; -+ case PIPE_B: -+ tmp |= TRANS_DDI_EDP_INPUT_B_ONOFF; -+ break; -+ case PIPE_C: -+ tmp |= TRANS_DDI_EDP_INPUT_C_ONOFF; -+ break; -+ } -+ -+ /* enable DDI buffer */ -+ tmp |= TRANS_DDI_FUNC_ENABLE; -+ I915_WRITE(TRANS_DDI_FUNC_CTL(dsi_trans), tmp); -+ } -+ -+ /* wait for link ready */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ if (wait_for_us((I915_READ(DSI_TRANS_FUNC_CONF(dsi_trans)) & -+ LINK_READY), 2500)) -+ DRM_ERROR("DSI link not ready\n"); -+ } -+} -+ -+static void -+gen11_dsi_set_transcoder_timings(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ const struct drm_display_mode *adjusted_mode = -+ &pipe_config->base.adjusted_mode; -+ enum port port; -+ enum transcoder dsi_trans; -+ /* horizontal timings */ -+ u16 htotal, hactive, hsync_start, hsync_end, hsync_size; -+ u16 hfront_porch, hback_porch; -+ /* vertical timings */ -+ u16 vtotal, vactive, vsync_start, vsync_end, vsync_shift; -+ -+ hactive = adjusted_mode->crtc_hdisplay; -+ htotal = adjusted_mode->crtc_htotal; -+ hsync_start = adjusted_mode->crtc_hsync_start; -+ hsync_end = adjusted_mode->crtc_hsync_end; -+ hsync_size = hsync_end - hsync_start; -+ hfront_porch = (adjusted_mode->crtc_hsync_start - -+ adjusted_mode->crtc_hdisplay); -+ hback_porch = (adjusted_mode->crtc_htotal - -+ adjusted_mode->crtc_hsync_end); -+ vactive = adjusted_mode->crtc_vdisplay; -+ vtotal = adjusted_mode->crtc_vtotal; -+ vsync_start = adjusted_mode->crtc_vsync_start; -+ vsync_end = adjusted_mode->crtc_vsync_end; -+ vsync_shift = hsync_start - htotal / 2; -+ -+ if (intel_dsi->dual_link) { -+ hactive /= 2; -+ if (intel_dsi->dual_link == DSI_DUAL_LINK_FRONT_BACK) -+ hactive += intel_dsi->pixel_overlap; -+ htotal /= 2; -+ } -+ -+ /* minimum hactive as per bspec: 256 pixels */ -+ if (adjusted_mode->crtc_hdisplay < 256) -+ DRM_ERROR("hactive is less then 256 pixels\n"); -+ -+ /* if RGB666 format, then hactive must be multiple of 4 pixels */ -+ if (intel_dsi->pixel_format == MIPI_DSI_FMT_RGB666 && hactive % 4 != 0) -+ DRM_ERROR("hactive pixels are not multiple of 4\n"); -+ -+ /* program TRANS_HTOTAL register */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ I915_WRITE(HTOTAL(dsi_trans), -+ (hactive - 1) | ((htotal - 1) << 16)); -+ } -+ -+ /* TRANS_HSYNC register to be programmed only for video mode */ -+ if (intel_dsi->operation_mode == INTEL_DSI_VIDEO_MODE) { -+ if (intel_dsi->video_mode_format == -+ VIDEO_MODE_NON_BURST_WITH_SYNC_PULSE) { -+ /* BSPEC: hsync size should be atleast 16 pixels */ -+ if (hsync_size < 16) -+ DRM_ERROR("hsync size < 16 pixels\n"); -+ } -+ -+ if (hback_porch < 16) -+ DRM_ERROR("hback porch < 16 pixels\n"); -+ -+ if (intel_dsi->dual_link) { -+ hsync_start /= 2; -+ hsync_end /= 2; -+ } -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ I915_WRITE(HSYNC(dsi_trans), -+ (hsync_start - 1) | ((hsync_end - 1) << 16)); -+ } -+ } -+ -+ /* program TRANS_VTOTAL register */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ /* -+ * FIXME: Programing this by assuming progressive mode, since -+ * non-interlaced info from VBT is not saved inside -+ * struct drm_display_mode. -+ * For interlace mode: program required pixel minus 2 -+ */ -+ I915_WRITE(VTOTAL(dsi_trans), -+ (vactive - 1) | ((vtotal - 1) << 16)); -+ } -+ -+ if (vsync_end < vsync_start || vsync_end > vtotal) -+ DRM_ERROR("Invalid vsync_end value\n"); -+ -+ if (vsync_start < vactive) -+ DRM_ERROR("vsync_start less than vactive\n"); -+ -+ /* program TRANS_VSYNC register */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ I915_WRITE(VSYNC(dsi_trans), -+ (vsync_start - 1) | ((vsync_end - 1) << 16)); -+ } -+ -+ /* -+ * FIXME: It has to be programmed only for interlaced -+ * modes. Put the check condition here once interlaced -+ * info available as described above. -+ * program TRANS_VSYNCSHIFT register -+ */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ I915_WRITE(VSYNCSHIFT(dsi_trans), vsync_shift); -+ } -+} -+ -+static void gen11_dsi_enable_transcoder(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ enum port port; -+ enum transcoder dsi_trans; -+ u32 tmp; -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ tmp = I915_READ(PIPECONF(dsi_trans)); -+ tmp |= PIPECONF_ENABLE; -+ I915_WRITE(PIPECONF(dsi_trans), tmp); -+ -+ /* wait for transcoder to be enabled */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ PIPECONF(dsi_trans), -+ I965_PIPECONF_ACTIVE, -+ I965_PIPECONF_ACTIVE, 10)) -+ DRM_ERROR("DSI transcoder not enabled\n"); -+ } -+} -+ -+static void gen11_dsi_setup_timeouts(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ enum port port; -+ enum transcoder dsi_trans; -+ u32 tmp, hs_tx_timeout, lp_rx_timeout, ta_timeout, divisor, mul; -+ -+ /* -+ * escape clock count calculation: -+ * BYTE_CLK_COUNT = TIME_NS/(8 * UI) -+ * UI (nsec) = (10^6)/Bitrate -+ * TIME_NS = (BYTE_CLK_COUNT * 8 * 10^6)/ Bitrate -+ * ESCAPE_CLK_COUNT = TIME_NS/ESC_CLK_NS -+ */ -+ divisor = intel_dsi_tlpx_ns(intel_dsi) * intel_dsi_bitrate(intel_dsi) * 1000; -+ mul = 8 * 1000000; -+ hs_tx_timeout = DIV_ROUND_UP(intel_dsi->hs_tx_timeout * mul, -+ divisor); -+ lp_rx_timeout = DIV_ROUND_UP(intel_dsi->lp_rx_timeout * mul, divisor); -+ ta_timeout = DIV_ROUND_UP(intel_dsi->turn_arnd_val * mul, divisor); -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ -+ /* program hst_tx_timeout */ -+ tmp = I915_READ(DSI_HSTX_TO(dsi_trans)); -+ tmp &= ~HSTX_TIMEOUT_VALUE_MASK; -+ tmp |= HSTX_TIMEOUT_VALUE(hs_tx_timeout); -+ I915_WRITE(DSI_HSTX_TO(dsi_trans), tmp); -+ -+ /* FIXME: DSI_CALIB_TO */ -+ -+ /* program lp_rx_host timeout */ -+ tmp = I915_READ(DSI_LPRX_HOST_TO(dsi_trans)); -+ tmp &= ~LPRX_TIMEOUT_VALUE_MASK; -+ tmp |= LPRX_TIMEOUT_VALUE(lp_rx_timeout); -+ I915_WRITE(DSI_LPRX_HOST_TO(dsi_trans), tmp); -+ -+ /* FIXME: DSI_PWAIT_TO */ -+ -+ /* program turn around timeout */ -+ tmp = I915_READ(DSI_TA_TO(dsi_trans)); -+ tmp &= ~TA_TIMEOUT_VALUE_MASK; -+ tmp |= TA_TIMEOUT_VALUE(ta_timeout); -+ I915_WRITE(DSI_TA_TO(dsi_trans), tmp); -+ } -+} -+ -+static void -+gen11_dsi_enable_port_and_phy(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config) -+{ -+ /* step 4a: power up all lanes of the DDI used by DSI */ -+ gen11_dsi_power_up_lanes(encoder); -+ -+ /* step 4b: configure lane sequencing of the Combo-PHY transmitters */ -+ gen11_dsi_config_phy_lanes_sequence(encoder); -+ -+ /* step 4c: configure voltage swing and skew */ -+ gen11_dsi_voltage_swing_program_seq(encoder); -+ -+ /* enable DDI buffer */ -+ gen11_dsi_enable_ddi_buffer(encoder); -+ -+ /* setup D-PHY timings */ -+ gen11_dsi_setup_dphy_timings(encoder); -+ -+ /* step 4h: setup DSI protocol timeouts */ -+ gen11_dsi_setup_timeouts(encoder); -+ -+ /* Step (4h, 4i, 4j, 4k): Configure transcoder */ -+ gen11_dsi_configure_transcoder(encoder, pipe_config); -+ -+ /* Step 4l: Gate DDI clocks */ -+ gen11_dsi_gate_clocks(encoder); -+} -+ -+static void gen11_dsi_powerup_panel(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ struct mipi_dsi_device *dsi; -+ enum port port; -+ enum transcoder dsi_trans; -+ u32 tmp; -+ int ret; -+ -+ /* set maximum return packet size */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ -+ /* -+ * FIXME: This uses the number of DW's currently in the payload -+ * receive queue. This is probably not what we want here. -+ */ -+ tmp = I915_READ(DSI_CMD_RXCTL(dsi_trans)); -+ tmp &= NUMBER_RX_PLOAD_DW_MASK; -+ /* multiply "Number Rx Payload DW" by 4 to get max value */ -+ tmp = tmp * 4; -+ dsi = intel_dsi->dsi_hosts[port]->device; -+ ret = mipi_dsi_set_maximum_return_packet_size(dsi, tmp); -+ if (ret < 0) -+ DRM_ERROR("error setting max return pkt size%d\n", tmp); -+ } -+ -+ /* panel power on related mipi dsi vbt sequences */ -+ intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_POWER_ON); -+ intel_dsi_msleep(intel_dsi, intel_dsi->panel_on_delay); -+ intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_DEASSERT_RESET); -+ intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_INIT_OTP); -+ intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_DISPLAY_ON); -+ -+ /* ensure all panel commands dispatched before enabling transcoder */ -+ wait_for_cmds_dispatched_to_panel(encoder); -+} -+ -+static void gen11_dsi_pre_pll_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config, -+ const struct drm_connector_state *conn_state) -+{ -+ /* step2: enable IO power */ -+ gen11_dsi_enable_io_power(encoder); -+ -+ /* step3: enable DSI PLL */ -+ gen11_dsi_program_esc_clk_div(encoder); -+} -+ -+static void gen11_dsi_pre_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config, -+ const struct drm_connector_state *conn_state) -+{ -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ -+ /* step3b */ -+ gen11_dsi_map_pll(encoder, pipe_config); -+ -+ /* step4: enable DSI port and DPHY */ -+ gen11_dsi_enable_port_and_phy(encoder, pipe_config); -+ -+ /* step5: program and powerup panel */ -+ gen11_dsi_powerup_panel(encoder); -+ -+ /* step6c: configure transcoder timings */ -+ gen11_dsi_set_transcoder_timings(encoder, pipe_config); -+ -+ /* step6d: enable dsi transcoder */ -+ gen11_dsi_enable_transcoder(encoder); -+ -+ /* step7: enable backlight */ -+ intel_panel_enable_backlight(pipe_config, conn_state); -+ intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_BACKLIGHT_ON); -+} -+ -+static void gen11_dsi_disable_transcoder(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ enum port port; -+ enum transcoder dsi_trans; -+ u32 tmp; -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ -+ /* disable transcoder */ -+ tmp = I915_READ(PIPECONF(dsi_trans)); -+ tmp &= ~PIPECONF_ENABLE; -+ I915_WRITE(PIPECONF(dsi_trans), tmp); -+ -+ /* wait for transcoder to be disabled */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ PIPECONF(dsi_trans), -+ I965_PIPECONF_ACTIVE, 0, 50)) -+ DRM_ERROR("DSI trancoder not disabled\n"); -+ } -+} -+ -+static void gen11_dsi_powerdown_panel(struct intel_encoder *encoder) -+{ -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ -+ intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_DISPLAY_OFF); -+ intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_ASSERT_RESET); -+ intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_POWER_OFF); -+ -+ /* ensure cmds dispatched to panel */ -+ wait_for_cmds_dispatched_to_panel(encoder); -+} -+ -+static void gen11_dsi_deconfigure_trancoder(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ enum port port; -+ enum transcoder dsi_trans; -+ u32 tmp; -+ -+ /* put dsi link in ULPS */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ tmp = I915_READ(DSI_LP_MSG(dsi_trans)); -+ tmp |= LINK_ENTER_ULPS; -+ tmp &= ~LINK_ULPS_TYPE_LP11; -+ I915_WRITE(DSI_LP_MSG(dsi_trans), tmp); -+ -+ if (wait_for_us((I915_READ(DSI_LP_MSG(dsi_trans)) & -+ LINK_IN_ULPS), -+ 10)) -+ DRM_ERROR("DSI link not in ULPS\n"); -+ } -+ -+ /* disable ddi function */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ tmp = I915_READ(TRANS_DDI_FUNC_CTL(dsi_trans)); -+ tmp &= ~TRANS_DDI_FUNC_ENABLE; -+ I915_WRITE(TRANS_DDI_FUNC_CTL(dsi_trans), tmp); -+ } -+ -+ /* disable port sync mode if dual link */ -+ if (intel_dsi->dual_link) { -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ tmp = I915_READ(TRANS_DDI_FUNC_CTL2(dsi_trans)); -+ tmp &= ~PORT_SYNC_MODE_ENABLE; -+ I915_WRITE(TRANS_DDI_FUNC_CTL2(dsi_trans), tmp); -+ } -+ } -+} -+ -+static void gen11_dsi_disable_port(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ u32 tmp; -+ enum port port; -+ -+ gen11_dsi_ungate_clocks(encoder); -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp = I915_READ(DDI_BUF_CTL(port)); -+ tmp &= ~DDI_BUF_CTL_ENABLE; -+ I915_WRITE(DDI_BUF_CTL(port), tmp); -+ -+ if (wait_for_us((I915_READ(DDI_BUF_CTL(port)) & -+ DDI_BUF_IS_IDLE), -+ 8)) -+ DRM_ERROR("DDI port:%c buffer not idle\n", -+ port_name(port)); -+ } -+ gen11_dsi_gate_clocks(encoder); -+} -+ -+static void gen11_dsi_disable_io_power(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ enum port port; -+ u32 tmp; -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ intel_wakeref_t wakeref; -+ -+ wakeref = fetch_and_zero(&intel_dsi->io_wakeref[port]); -+ intel_display_power_put(dev_priv, -+ port == PORT_A ? -+ POWER_DOMAIN_PORT_DDI_A_IO : -+ POWER_DOMAIN_PORT_DDI_B_IO, -+ wakeref); -+ } -+ -+ /* set mode to DDI */ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ tmp = I915_READ(ICL_DSI_IO_MODECTL(port)); -+ tmp &= ~COMBO_PHY_MODE_DSI; -+ I915_WRITE(ICL_DSI_IO_MODECTL(port), tmp); -+ } -+} -+ -+static void gen11_dsi_disable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ -+ /* step1: turn off backlight */ -+ intel_dsi_vbt_exec_sequence(intel_dsi, MIPI_SEQ_BACKLIGHT_OFF); -+ intel_panel_disable_backlight(old_conn_state); -+ -+ /* step2d,e: disable transcoder and wait */ -+ gen11_dsi_disable_transcoder(encoder); -+ -+ /* step2f,g: powerdown panel */ -+ gen11_dsi_powerdown_panel(encoder); -+ -+ /* step2h,i,j: deconfig trancoder */ -+ gen11_dsi_deconfigure_trancoder(encoder); -+ -+ /* step3: disable port */ -+ gen11_dsi_disable_port(encoder); -+ -+ /* step4: disable IO power */ -+ gen11_dsi_disable_io_power(encoder); -+} -+ -+static void gen11_dsi_get_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ -+ /* FIXME: adapt icl_ddi_clock_get() for DSI and use that? */ -+ pipe_config->port_clock = -+ cnl_calc_wrpll_link(dev_priv, &pipe_config->dpll_hw_state); -+ pipe_config->base.adjusted_mode.crtc_clock = intel_dsi->pclk; -+ pipe_config->output_types |= BIT(INTEL_OUTPUT_DSI); -+} -+ -+static int gen11_dsi_compute_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config, -+ struct drm_connector_state *conn_state) -+{ -+ struct intel_dsi *intel_dsi = container_of(encoder, struct intel_dsi, -+ base); -+ struct intel_connector *intel_connector = intel_dsi->attached_connector; -+ struct intel_crtc *crtc = to_intel_crtc(pipe_config->base.crtc); -+ const struct drm_display_mode *fixed_mode = -+ intel_connector->panel.fixed_mode; -+ struct drm_display_mode *adjusted_mode = -+ &pipe_config->base.adjusted_mode; -+ -+ intel_fixed_panel_mode(fixed_mode, adjusted_mode); -+ intel_pch_panel_fitting(crtc, pipe_config, conn_state->scaling_mode); -+ -+ adjusted_mode->flags = 0; -+ -+ /* Dual link goes to trancoder DSI'0' */ -+ if (intel_dsi->ports == BIT(PORT_B)) -+ pipe_config->cpu_transcoder = TRANSCODER_DSI_1; -+ else -+ pipe_config->cpu_transcoder = TRANSCODER_DSI_0; -+ -+ pipe_config->clock_set = true; -+ pipe_config->port_clock = intel_dsi_bitrate(intel_dsi) / 5; -+ -+ return 0; -+} -+ -+static void gen11_dsi_get_power_domains(struct intel_encoder *encoder, -+ struct intel_crtc_state *crtc_state) -+{ -+ get_dsi_io_power_domains(to_i915(encoder->base.dev), -+ enc_to_intel_dsi(&encoder->base)); -+} -+ -+static bool gen11_dsi_get_hw_state(struct intel_encoder *encoder, -+ enum pipe *pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dsi *intel_dsi = enc_to_intel_dsi(&encoder->base); -+ enum transcoder dsi_trans; -+ intel_wakeref_t wakeref; -+ enum port port; -+ bool ret = false; -+ u32 tmp; -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ encoder->power_domain); -+ if (!wakeref) -+ return false; -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ dsi_trans = dsi_port_to_transcoder(port); -+ tmp = I915_READ(TRANS_DDI_FUNC_CTL(dsi_trans)); -+ switch (tmp & TRANS_DDI_EDP_INPUT_MASK) { -+ case TRANS_DDI_EDP_INPUT_A_ON: -+ *pipe = PIPE_A; -+ break; -+ case TRANS_DDI_EDP_INPUT_B_ONOFF: -+ *pipe = PIPE_B; -+ break; -+ case TRANS_DDI_EDP_INPUT_C_ONOFF: -+ *pipe = PIPE_C; -+ break; -+ default: -+ DRM_ERROR("Invalid PIPE input\n"); -+ goto out; -+ } -+ -+ tmp = I915_READ(PIPECONF(dsi_trans)); -+ ret = tmp & PIPECONF_ENABLE; -+ } -+out: -+ intel_display_power_put(dev_priv, encoder->power_domain, wakeref); -+ return ret; -+} -+ -+static void gen11_dsi_encoder_destroy(struct drm_encoder *encoder) -+{ -+ intel_encoder_destroy(encoder); -+} -+ -+static const struct drm_encoder_funcs gen11_dsi_encoder_funcs = { -+ .destroy = gen11_dsi_encoder_destroy, -+}; -+ -+static const struct drm_connector_funcs gen11_dsi_connector_funcs = { -+ .late_register = intel_connector_register, -+ .early_unregister = intel_connector_unregister, -+ .destroy = intel_connector_destroy, -+ .fill_modes = drm_helper_probe_single_connector_modes, -+ .atomic_get_property = intel_digital_connector_atomic_get_property, -+ .atomic_set_property = intel_digital_connector_atomic_set_property, -+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, -+ .atomic_duplicate_state = intel_digital_connector_duplicate_state, -+}; -+ -+static const struct drm_connector_helper_funcs gen11_dsi_connector_helper_funcs = { -+ .get_modes = intel_dsi_get_modes, -+ .mode_valid = intel_dsi_mode_valid, -+ .atomic_check = intel_digital_connector_atomic_check, -+}; -+ -+static int gen11_dsi_host_attach(struct mipi_dsi_host *host, -+ struct mipi_dsi_device *dsi) -+{ -+ return 0; -+} -+ -+static int gen11_dsi_host_detach(struct mipi_dsi_host *host, -+ struct mipi_dsi_device *dsi) -+{ -+ return 0; -+} -+ -+static ssize_t gen11_dsi_host_transfer(struct mipi_dsi_host *host, -+ const struct mipi_dsi_msg *msg) -+{ -+ struct intel_dsi_host *intel_dsi_host = to_intel_dsi_host(host); -+ struct mipi_dsi_packet dsi_pkt; -+ ssize_t ret; -+ bool enable_lpdt = false; -+ -+ ret = mipi_dsi_create_packet(&dsi_pkt, msg); -+ if (ret < 0) -+ return ret; -+ -+ if (msg->flags & MIPI_DSI_MSG_USE_LPM) -+ enable_lpdt = true; -+ -+ /* send packet header */ -+ ret = dsi_send_pkt_hdr(intel_dsi_host, dsi_pkt, enable_lpdt); -+ if (ret < 0) -+ return ret; -+ -+ /* only long packet contains payload */ -+ if (mipi_dsi_packet_format_is_long(msg->type)) { -+ ret = dsi_send_pkt_payld(intel_dsi_host, dsi_pkt); -+ if (ret < 0) -+ return ret; -+ } -+ -+ //TODO: add payload receive code if needed -+ -+ ret = sizeof(dsi_pkt.header) + dsi_pkt.payload_length; -+ -+ return ret; -+} -+ -+static const struct mipi_dsi_host_ops gen11_dsi_host_ops = { -+ .attach = gen11_dsi_host_attach, -+ .detach = gen11_dsi_host_detach, -+ .transfer = gen11_dsi_host_transfer, -+}; -+ -+void icl_dsi_init(struct drm_i915_private *dev_priv) -+{ -+ struct drm_device *dev = &dev_priv->drm; -+ struct intel_dsi *intel_dsi; -+ struct intel_encoder *encoder; -+ struct intel_connector *intel_connector; -+ struct drm_connector *connector; -+ struct drm_display_mode *fixed_mode; -+ enum port port; -+ -+ if (!intel_bios_is_dsi_present(dev_priv, &port)) -+ return; -+ -+ intel_dsi = kzalloc(sizeof(*intel_dsi), GFP_KERNEL); -+ if (!intel_dsi) -+ return; -+ -+ intel_connector = intel_connector_alloc(); -+ if (!intel_connector) { -+ kfree(intel_dsi); -+ return; -+ } -+ -+ encoder = &intel_dsi->base; -+ intel_dsi->attached_connector = intel_connector; -+ connector = &intel_connector->base; -+ -+ /* register DSI encoder with DRM subsystem */ -+ drm_encoder_init(dev, &encoder->base, &gen11_dsi_encoder_funcs, -+ DRM_MODE_ENCODER_DSI, "DSI %c", port_name(port)); -+ -+ encoder->pre_pll_enable = gen11_dsi_pre_pll_enable; -+ encoder->pre_enable = gen11_dsi_pre_enable; -+ encoder->disable = gen11_dsi_disable; -+ encoder->port = port; -+ encoder->get_config = gen11_dsi_get_config; -+ encoder->update_pipe = intel_panel_update_backlight; -+ encoder->compute_config = gen11_dsi_compute_config; -+ encoder->get_hw_state = gen11_dsi_get_hw_state; -+ encoder->type = INTEL_OUTPUT_DSI; -+ encoder->cloneable = 0; -+ encoder->crtc_mask = BIT(PIPE_A) | BIT(PIPE_B) | BIT(PIPE_C); -+ encoder->power_domain = POWER_DOMAIN_PORT_DSI; -+ encoder->get_power_domains = gen11_dsi_get_power_domains; -+ -+ /* register DSI connector with DRM subsystem */ -+ drm_connector_init(dev, connector, &gen11_dsi_connector_funcs, -+ DRM_MODE_CONNECTOR_DSI); -+ drm_connector_helper_add(connector, &gen11_dsi_connector_helper_funcs); -+ connector->display_info.subpixel_order = SubPixelHorizontalRGB; -+ connector->interlace_allowed = false; -+ connector->doublescan_allowed = false; -+ intel_connector->get_hw_state = intel_connector_get_hw_state; -+ -+ /* attach connector to encoder */ -+ intel_connector_attach_encoder(intel_connector, encoder); -+ -+ mutex_lock(&dev->mode_config.mutex); -+ fixed_mode = intel_panel_vbt_fixed_mode(intel_connector); -+ mutex_unlock(&dev->mode_config.mutex); -+ -+ if (!fixed_mode) { -+ DRM_ERROR("DSI fixed mode info missing\n"); -+ goto err; -+ } -+ -+ intel_panel_init(&intel_connector->panel, fixed_mode, NULL); -+ intel_panel_setup_backlight(connector, INVALID_PIPE); -+ -+ if (dev_priv->vbt.dsi.config->dual_link) -+ intel_dsi->ports = BIT(PORT_A) | BIT(PORT_B); -+ else -+ intel_dsi->ports = BIT(port); -+ -+ intel_dsi->dcs_backlight_ports = dev_priv->vbt.dsi.bl_ports; -+ intel_dsi->dcs_cabc_ports = dev_priv->vbt.dsi.cabc_ports; -+ -+ for_each_dsi_port(port, intel_dsi->ports) { -+ struct intel_dsi_host *host; -+ -+ host = intel_dsi_host_init(intel_dsi, &gen11_dsi_host_ops, port); -+ if (!host) -+ goto err; -+ -+ intel_dsi->dsi_hosts[port] = host; -+ } -+ -+ if (!intel_dsi_vbt_init(intel_dsi, MIPI_DSI_GENERIC_PANEL_ID)) { -+ DRM_DEBUG_KMS("no device found\n"); -+ goto err; -+ } -+ -+ return; -+ -+err: -+ drm_encoder_cleanup(&encoder->base); -+ kfree(intel_dsi); -+ kfree(intel_connector); -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_acpi.c b/drivers/gpu/drm/i915_legacy/intel_acpi.c -new file mode 100644 -index 000000000000..9d142d038a7d ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_acpi.c -@@ -0,0 +1,155 @@ -+// SPDX-License-Identifier: GPL-2.0 -+/* -+ * Intel ACPI functions -+ * -+ * _DSM related code stolen from nouveau_acpi.c. -+ */ -+#include -+#include -+#include "i915_drv.h" -+ -+#define INTEL_DSM_REVISION_ID 1 /* For Calpella anyway... */ -+#define INTEL_DSM_FN_PLATFORM_MUX_INFO 1 /* No args */ -+ -+static const guid_t intel_dsm_guid = -+ GUID_INIT(0x7ed873d3, 0xc2d0, 0x4e4f, -+ 0xa8, 0x54, 0x0f, 0x13, 0x17, 0xb0, 0x1c, 0x2c); -+ -+static char *intel_dsm_port_name(u8 id) -+{ -+ switch (id) { -+ case 0: -+ return "Reserved"; -+ case 1: -+ return "Analog VGA"; -+ case 2: -+ return "LVDS"; -+ case 3: -+ return "Reserved"; -+ case 4: -+ return "HDMI/DVI_B"; -+ case 5: -+ return "HDMI/DVI_C"; -+ case 6: -+ return "HDMI/DVI_D"; -+ case 7: -+ return "DisplayPort_A"; -+ case 8: -+ return "DisplayPort_B"; -+ case 9: -+ return "DisplayPort_C"; -+ case 0xa: -+ return "DisplayPort_D"; -+ case 0xb: -+ case 0xc: -+ case 0xd: -+ return "Reserved"; -+ case 0xe: -+ return "WiDi"; -+ default: -+ return "bad type"; -+ } -+} -+ -+static char *intel_dsm_mux_type(u8 type) -+{ -+ switch (type) { -+ case 0: -+ return "unknown"; -+ case 1: -+ return "No MUX, iGPU only"; -+ case 2: -+ return "No MUX, dGPU only"; -+ case 3: -+ return "MUXed between iGPU and dGPU"; -+ default: -+ return "bad type"; -+ } -+} -+ -+static void intel_dsm_platform_mux_info(acpi_handle dhandle) -+{ -+ int i; -+ union acpi_object *pkg, *connector_count; -+ -+ pkg = acpi_evaluate_dsm_typed(dhandle, &intel_dsm_guid, -+ INTEL_DSM_REVISION_ID, INTEL_DSM_FN_PLATFORM_MUX_INFO, -+ NULL, ACPI_TYPE_PACKAGE); -+ if (!pkg) { -+ DRM_DEBUG_DRIVER("failed to evaluate _DSM\n"); -+ return; -+ } -+ -+ connector_count = &pkg->package.elements[0]; -+ DRM_DEBUG_DRIVER("MUX info connectors: %lld\n", -+ (unsigned long long)connector_count->integer.value); -+ for (i = 1; i < pkg->package.count; i++) { -+ union acpi_object *obj = &pkg->package.elements[i]; -+ union acpi_object *connector_id = &obj->package.elements[0]; -+ union acpi_object *info = &obj->package.elements[1]; -+ DRM_DEBUG_DRIVER("Connector id: 0x%016llx\n", -+ (unsigned long long)connector_id->integer.value); -+ DRM_DEBUG_DRIVER(" port id: %s\n", -+ intel_dsm_port_name(info->buffer.pointer[0])); -+ DRM_DEBUG_DRIVER(" display mux info: %s\n", -+ intel_dsm_mux_type(info->buffer.pointer[1])); -+ DRM_DEBUG_DRIVER(" aux/dc mux info: %s\n", -+ intel_dsm_mux_type(info->buffer.pointer[2])); -+ DRM_DEBUG_DRIVER(" hpd mux info: %s\n", -+ intel_dsm_mux_type(info->buffer.pointer[3])); -+ } -+ -+ ACPI_FREE(pkg); -+} -+ -+static acpi_handle intel_dsm_pci_probe(struct pci_dev *pdev) -+{ -+ acpi_handle dhandle; -+ -+ dhandle = ACPI_HANDLE(&pdev->dev); -+ if (!dhandle) -+ return NULL; -+ -+ if (!acpi_check_dsm(dhandle, &intel_dsm_guid, INTEL_DSM_REVISION_ID, -+ 1 << INTEL_DSM_FN_PLATFORM_MUX_INFO)) { -+ DRM_DEBUG_KMS("no _DSM method for intel device\n"); -+ return NULL; -+ } -+ -+ intel_dsm_platform_mux_info(dhandle); -+ -+ return dhandle; -+} -+ -+static bool intel_dsm_detect(void) -+{ -+ acpi_handle dhandle = NULL; -+ char acpi_method_name[255] = { 0 }; -+ struct acpi_buffer buffer = {sizeof(acpi_method_name), acpi_method_name}; -+ struct pci_dev *pdev = NULL; -+ int vga_count = 0; -+ -+ while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev)) != NULL) { -+ vga_count++; -+ dhandle = intel_dsm_pci_probe(pdev) ?: dhandle; -+ } -+ -+ if (vga_count == 2 && dhandle) { -+ acpi_get_name(dhandle, ACPI_FULL_PATHNAME, &buffer); -+ DRM_DEBUG_DRIVER("vga_switcheroo: detected DSM switching method %s handle\n", -+ acpi_method_name); -+ return true; -+ } -+ -+ return false; -+} -+ -+void intel_register_dsm_handler(void) -+{ -+ if (!intel_dsm_detect()) -+ return; -+} -+ -+void intel_unregister_dsm_handler(void) -+{ -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_atomic.c b/drivers/gpu/drm/i915_legacy/intel_atomic.c -new file mode 100644 -index 000000000000..2986ee1dbf62 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_atomic.c -@@ -0,0 +1,428 @@ -+/* -+ * Copyright © 2015 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+/** -+ * DOC: atomic modeset support -+ * -+ * The functions here implement the state management and hardware programming -+ * dispatch required by the atomic modeset infrastructure. -+ * See intel_atomic_plane.c for the plane-specific atomic functionality. -+ */ -+ -+#include -+#include -+#include -+#include -+ -+#include "intel_drv.h" -+#include "intel_hdcp.h" -+#include "intel_sprite.h" -+ -+/** -+ * intel_digital_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 intel_digital_connector_atomic_get_property(struct drm_connector *connector, -+ const struct drm_connector_state *state, -+ struct drm_property *property, -+ u64 *val) -+{ -+ struct drm_device *dev = connector->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_digital_connector_state *intel_conn_state = -+ to_intel_digital_connector_state(state); -+ -+ if (property == dev_priv->force_audio_property) -+ *val = intel_conn_state->force_audio; -+ else if (property == dev_priv->broadcast_rgb_property) -+ *val = intel_conn_state->broadcast_rgb; -+ else { -+ DRM_DEBUG_ATOMIC("Unknown property [PROP:%d:%s]\n", -+ property->base.id, property->name); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+/** -+ * intel_digital_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 intel_digital_connector_atomic_set_property(struct drm_connector *connector, -+ struct drm_connector_state *state, -+ struct drm_property *property, -+ u64 val) -+{ -+ struct drm_device *dev = connector->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_digital_connector_state *intel_conn_state = -+ to_intel_digital_connector_state(state); -+ -+ if (property == dev_priv->force_audio_property) { -+ intel_conn_state->force_audio = val; -+ return 0; -+ } -+ -+ if (property == dev_priv->broadcast_rgb_property) { -+ intel_conn_state->broadcast_rgb = val; -+ return 0; -+ } -+ -+ DRM_DEBUG_ATOMIC("Unknown property [PROP:%d:%s]\n", -+ property->base.id, property->name); -+ return -EINVAL; -+} -+ -+ -+static bool blob_equal(const struct drm_property_blob *a, -+ const struct drm_property_blob *b) -+{ -+ if (a && b) -+ return a->length == b->length && -+ !memcmp(a->data, b->data, a->length); -+ -+ return !a == !b; -+} -+ -+int intel_digital_connector_atomic_check(struct drm_connector *conn, -+ struct drm_atomic_state *state) -+{ -+ struct drm_connector_state *new_state = -+ drm_atomic_get_new_connector_state(state, conn); -+ struct intel_digital_connector_state *new_conn_state = -+ to_intel_digital_connector_state(new_state); -+ struct drm_connector_state *old_state = -+ drm_atomic_get_old_connector_state(state, conn); -+ struct intel_digital_connector_state *old_conn_state = -+ to_intel_digital_connector_state(old_state); -+ struct drm_crtc_state *crtc_state; -+ -+ intel_hdcp_atomic_check(conn, old_state, new_state); -+ -+ if (!new_state->crtc) -+ return 0; -+ -+ crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc); -+ -+ /* -+ * These properties are handled by fastset, and might not end -+ * up in a modeset. -+ */ -+ if (new_conn_state->force_audio != old_conn_state->force_audio || -+ new_conn_state->broadcast_rgb != old_conn_state->broadcast_rgb || -+ new_conn_state->base.colorspace != old_conn_state->base.colorspace || -+ new_conn_state->base.picture_aspect_ratio != old_conn_state->base.picture_aspect_ratio || -+ new_conn_state->base.content_type != old_conn_state->base.content_type || -+ new_conn_state->base.scaling_mode != old_conn_state->base.scaling_mode || -+ !blob_equal(new_conn_state->base.hdr_output_metadata, -+ old_conn_state->base.hdr_output_metadata)) -+ crtc_state->mode_changed = true; -+ -+ return 0; -+} -+ -+/** -+ * intel_digital_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 * -+intel_digital_connector_duplicate_state(struct drm_connector *connector) -+{ -+ struct intel_digital_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; -+} -+ -+/** -+ * intel_crtc_duplicate_state - duplicate crtc state -+ * @crtc: drm crtc -+ * -+ * Allocates and returns a copy of the crtc state (both common and -+ * Intel-specific) for the specified crtc. -+ * -+ * Returns: The newly allocated crtc state, or NULL on failure. -+ */ -+struct drm_crtc_state * -+intel_crtc_duplicate_state(struct drm_crtc *crtc) -+{ -+ struct intel_crtc_state *crtc_state; -+ -+ crtc_state = kmemdup(crtc->state, sizeof(*crtc_state), GFP_KERNEL); -+ if (!crtc_state) -+ return NULL; -+ -+ __drm_atomic_helper_crtc_duplicate_state(crtc, &crtc_state->base); -+ -+ crtc_state->update_pipe = false; -+ crtc_state->disable_lp_wm = false; -+ crtc_state->disable_cxsr = false; -+ crtc_state->update_wm_pre = false; -+ crtc_state->update_wm_post = false; -+ crtc_state->fb_changed = false; -+ crtc_state->fifo_changed = false; -+ crtc_state->wm.need_postvbl_update = false; -+ crtc_state->fb_bits = 0; -+ crtc_state->update_planes = 0; -+ -+ return &crtc_state->base; -+} -+ -+/** -+ * intel_crtc_destroy_state - destroy crtc state -+ * @crtc: drm crtc -+ * @state: the state to destroy -+ * -+ * Destroys the crtc state (both common and Intel-specific) for the -+ * specified crtc. -+ */ -+void -+intel_crtc_destroy_state(struct drm_crtc *crtc, -+ struct drm_crtc_state *state) -+{ -+ drm_atomic_helper_crtc_destroy_state(crtc, state); -+} -+ -+static void intel_atomic_setup_scaler(struct intel_crtc_scaler_state *scaler_state, -+ int num_scalers_need, struct intel_crtc *intel_crtc, -+ const char *name, int idx, -+ struct intel_plane_state *plane_state, -+ int *scaler_id) -+{ -+ struct drm_i915_private *dev_priv = to_i915(intel_crtc->base.dev); -+ int j; -+ u32 mode; -+ -+ if (*scaler_id < 0) { -+ /* find a free scaler */ -+ for (j = 0; j < intel_crtc->num_scalers; j++) { -+ if (scaler_state->scalers[j].in_use) -+ continue; -+ -+ *scaler_id = j; -+ scaler_state->scalers[*scaler_id].in_use = 1; -+ break; -+ } -+ } -+ -+ if (WARN(*scaler_id < 0, "Cannot find scaler for %s:%d\n", name, idx)) -+ return; -+ -+ /* set scaler mode */ -+ if (plane_state && plane_state->base.fb && -+ plane_state->base.fb->format->is_yuv && -+ plane_state->base.fb->format->num_planes > 1) { -+ struct intel_plane *plane = to_intel_plane(plane_state->base.plane); -+ if (IS_GEN(dev_priv, 9) && -+ !IS_GEMINILAKE(dev_priv)) { -+ mode = SKL_PS_SCALER_MODE_NV12; -+ } else if (icl_is_hdr_plane(dev_priv, plane->id)) { -+ /* -+ * On gen11+'s HDR planes we only use the scaler for -+ * scaling. They have a dedicated chroma upsampler, so -+ * we don't need the scaler to upsample the UV plane. -+ */ -+ mode = PS_SCALER_MODE_NORMAL; -+ } else { -+ mode = PS_SCALER_MODE_PLANAR; -+ -+ if (plane_state->linked_plane) -+ mode |= PS_PLANE_Y_SEL(plane_state->linked_plane->id); -+ } -+ } else if (INTEL_GEN(dev_priv) > 9 || IS_GEMINILAKE(dev_priv)) { -+ mode = PS_SCALER_MODE_NORMAL; -+ } else if (num_scalers_need == 1 && intel_crtc->num_scalers > 1) { -+ /* -+ * when only 1 scaler is in use on a pipe with 2 scalers -+ * scaler 0 operates in high quality (HQ) mode. -+ * In this case use scaler 0 to take advantage of HQ mode -+ */ -+ scaler_state->scalers[*scaler_id].in_use = 0; -+ *scaler_id = 0; -+ scaler_state->scalers[0].in_use = 1; -+ mode = SKL_PS_SCALER_MODE_HQ; -+ } else { -+ mode = SKL_PS_SCALER_MODE_DYN; -+ } -+ -+ DRM_DEBUG_KMS("Attached scaler id %u.%u to %s:%d\n", -+ intel_crtc->pipe, *scaler_id, name, idx); -+ scaler_state->scalers[*scaler_id].mode = mode; -+} -+ -+/** -+ * intel_atomic_setup_scalers() - setup scalers for crtc per staged requests -+ * @dev_priv: i915 device -+ * @intel_crtc: intel crtc -+ * @crtc_state: incoming crtc_state to validate and setup scalers -+ * -+ * This function sets up scalers based on staged scaling requests for -+ * a @crtc and its planes. It is called from crtc level check path. If request -+ * is a supportable request, it attaches scalers to requested planes and crtc. -+ * -+ * This function takes into account the current scaler(s) in use by any planes -+ * not being part of this atomic state -+ * -+ * Returns: -+ * 0 - scalers were setup succesfully -+ * error code - otherwise -+ */ -+int intel_atomic_setup_scalers(struct drm_i915_private *dev_priv, -+ struct intel_crtc *intel_crtc, -+ struct intel_crtc_state *crtc_state) -+{ -+ struct drm_plane *plane = NULL; -+ struct intel_plane *intel_plane; -+ struct intel_plane_state *plane_state = NULL; -+ struct intel_crtc_scaler_state *scaler_state = -+ &crtc_state->scaler_state; -+ struct drm_atomic_state *drm_state = crtc_state->base.state; -+ struct intel_atomic_state *intel_state = to_intel_atomic_state(drm_state); -+ int num_scalers_need; -+ int i; -+ -+ num_scalers_need = hweight32(scaler_state->scaler_users); -+ -+ /* -+ * High level flow: -+ * - staged scaler requests are already in scaler_state->scaler_users -+ * - check whether staged scaling requests can be supported -+ * - add planes using scalers that aren't in current transaction -+ * - assign scalers to requested users -+ * - as part of plane commit, scalers will be committed -+ * (i.e., either attached or detached) to respective planes in hw -+ * - as part of crtc_commit, scaler will be either attached or detached -+ * to crtc in hw -+ */ -+ -+ /* fail if required scalers > available scalers */ -+ if (num_scalers_need > intel_crtc->num_scalers){ -+ DRM_DEBUG_KMS("Too many scaling requests %d > %d\n", -+ num_scalers_need, intel_crtc->num_scalers); -+ return -EINVAL; -+ } -+ -+ /* walkthrough scaler_users bits and start assigning scalers */ -+ for (i = 0; i < sizeof(scaler_state->scaler_users) * 8; i++) { -+ int *scaler_id; -+ const char *name; -+ int idx; -+ -+ /* skip if scaler not required */ -+ if (!(scaler_state->scaler_users & (1 << i))) -+ continue; -+ -+ if (i == SKL_CRTC_INDEX) { -+ name = "CRTC"; -+ idx = intel_crtc->base.base.id; -+ -+ /* panel fitter case: assign as a crtc scaler */ -+ scaler_id = &scaler_state->scaler_id; -+ } else { -+ name = "PLANE"; -+ -+ /* plane scaler case: assign as a plane scaler */ -+ /* find the plane that set the bit as scaler_user */ -+ plane = drm_state->planes[i].ptr; -+ -+ /* -+ * to enable/disable hq mode, add planes that are using scaler -+ * into this transaction -+ */ -+ if (!plane) { -+ struct drm_plane_state *state; -+ plane = drm_plane_from_index(&dev_priv->drm, i); -+ state = drm_atomic_get_plane_state(drm_state, plane); -+ if (IS_ERR(state)) { -+ DRM_DEBUG_KMS("Failed to add [PLANE:%d] to drm_state\n", -+ plane->base.id); -+ return PTR_ERR(state); -+ } -+ -+ /* -+ * the plane is added after plane checks are run, -+ * but since this plane is unchanged just do the -+ * minimum required validation. -+ */ -+ crtc_state->base.planes_changed = true; -+ } -+ -+ intel_plane = to_intel_plane(plane); -+ idx = plane->base.id; -+ -+ /* plane on different crtc cannot be a scaler user of this crtc */ -+ if (WARN_ON(intel_plane->pipe != intel_crtc->pipe)) -+ continue; -+ -+ plane_state = intel_atomic_get_new_plane_state(intel_state, -+ intel_plane); -+ scaler_id = &plane_state->scaler_id; -+ } -+ -+ intel_atomic_setup_scaler(scaler_state, num_scalers_need, -+ intel_crtc, name, idx, -+ plane_state, scaler_id); -+ } -+ -+ return 0; -+} -+ -+struct drm_atomic_state * -+intel_atomic_state_alloc(struct drm_device *dev) -+{ -+ struct intel_atomic_state *state = kzalloc(sizeof(*state), GFP_KERNEL); -+ -+ if (!state || drm_atomic_state_init(dev, &state->base) < 0) { -+ kfree(state); -+ return NULL; -+ } -+ -+ return &state->base; -+} -+ -+void intel_atomic_state_clear(struct drm_atomic_state *s) -+{ -+ struct intel_atomic_state *state = to_intel_atomic_state(s); -+ drm_atomic_state_default_clear(&state->base); -+ state->dpll_set = state->modeset = false; -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_atomic_plane.c b/drivers/gpu/drm/i915_legacy/intel_atomic_plane.c -new file mode 100644 -index 000000000000..d11681d71add ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_atomic_plane.c -@@ -0,0 +1,373 @@ -+/* -+ * Copyright © 2014 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+/** -+ * DOC: atomic plane helpers -+ * -+ * The functions here are used by the atomic plane helper functions to -+ * implement legacy plane updates (i.e., drm_plane->update_plane() and -+ * drm_plane->disable_plane()). This allows plane updates to use the -+ * atomic state infrastructure and perform plane updates as separate -+ * prepare/check/commit/cleanup steps. -+ */ -+ -+#include -+#include -+#include -+ -+#include "intel_atomic_plane.h" -+#include "intel_drv.h" -+#include "intel_pm.h" -+#include "intel_sprite.h" -+ -+struct intel_plane *intel_plane_alloc(void) -+{ -+ struct intel_plane_state *plane_state; -+ struct intel_plane *plane; -+ -+ plane = kzalloc(sizeof(*plane), GFP_KERNEL); -+ if (!plane) -+ return ERR_PTR(-ENOMEM); -+ -+ plane_state = kzalloc(sizeof(*plane_state), GFP_KERNEL); -+ if (!plane_state) { -+ kfree(plane); -+ return ERR_PTR(-ENOMEM); -+ } -+ -+ __drm_atomic_helper_plane_reset(&plane->base, &plane_state->base); -+ plane_state->scaler_id = -1; -+ -+ return plane; -+} -+ -+void intel_plane_free(struct intel_plane *plane) -+{ -+ intel_plane_destroy_state(&plane->base, plane->base.state); -+ kfree(plane); -+} -+ -+/** -+ * intel_plane_duplicate_state - duplicate plane state -+ * @plane: drm plane -+ * -+ * Allocates and returns a copy of the plane state (both common and -+ * Intel-specific) for the specified plane. -+ * -+ * Returns: The newly allocated plane state, or NULL on failure. -+ */ -+struct drm_plane_state * -+intel_plane_duplicate_state(struct drm_plane *plane) -+{ -+ struct drm_plane_state *state; -+ struct intel_plane_state *intel_state; -+ -+ intel_state = kmemdup(plane->state, sizeof(*intel_state), GFP_KERNEL); -+ -+ if (!intel_state) -+ return NULL; -+ -+ state = &intel_state->base; -+ -+ __drm_atomic_helper_plane_duplicate_state(plane, state); -+ -+ intel_state->vma = NULL; -+ intel_state->flags = 0; -+ -+ return state; -+} -+ -+/** -+ * intel_plane_destroy_state - destroy plane state -+ * @plane: drm plane -+ * @state: state object to destroy -+ * -+ * Destroys the plane state (both common and Intel-specific) for the -+ * specified plane. -+ */ -+void -+intel_plane_destroy_state(struct drm_plane *plane, -+ struct drm_plane_state *state) -+{ -+ WARN_ON(to_intel_plane_state(state)->vma); -+ -+ drm_atomic_helper_plane_destroy_state(plane, state); -+} -+ -+int intel_plane_atomic_check_with_state(const struct intel_crtc_state *old_crtc_state, -+ struct intel_crtc_state *new_crtc_state, -+ const struct intel_plane_state *old_plane_state, -+ struct intel_plane_state *new_plane_state) -+{ -+ struct intel_plane *plane = to_intel_plane(new_plane_state->base.plane); -+ int ret; -+ -+ new_crtc_state->active_planes &= ~BIT(plane->id); -+ new_crtc_state->nv12_planes &= ~BIT(plane->id); -+ new_crtc_state->c8_planes &= ~BIT(plane->id); -+ new_plane_state->base.visible = false; -+ -+ if (!new_plane_state->base.crtc && !old_plane_state->base.crtc) -+ return 0; -+ -+ ret = plane->check_plane(new_crtc_state, new_plane_state); -+ if (ret) -+ return ret; -+ -+ /* FIXME pre-g4x don't work like this */ -+ if (new_plane_state->base.visible) -+ new_crtc_state->active_planes |= BIT(plane->id); -+ -+ if (new_plane_state->base.visible && -+ is_planar_yuv_format(new_plane_state->base.fb->format->format)) -+ new_crtc_state->nv12_planes |= BIT(plane->id); -+ -+ if (new_plane_state->base.visible && -+ new_plane_state->base.fb->format->format == DRM_FORMAT_C8) -+ new_crtc_state->c8_planes |= BIT(plane->id); -+ -+ if (new_plane_state->base.visible || old_plane_state->base.visible) -+ new_crtc_state->update_planes |= BIT(plane->id); -+ -+ return intel_plane_atomic_calc_changes(old_crtc_state, -+ &new_crtc_state->base, -+ old_plane_state, -+ &new_plane_state->base); -+} -+ -+static int intel_plane_atomic_check(struct drm_plane *plane, -+ struct drm_plane_state *new_plane_state) -+{ -+ struct drm_atomic_state *state = new_plane_state->state; -+ const struct drm_plane_state *old_plane_state = -+ drm_atomic_get_old_plane_state(state, plane); -+ struct drm_crtc *crtc = new_plane_state->crtc ?: old_plane_state->crtc; -+ const struct drm_crtc_state *old_crtc_state; -+ struct drm_crtc_state *new_crtc_state; -+ -+ new_plane_state->visible = false; -+ if (!crtc) -+ return 0; -+ -+ old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc); -+ new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc); -+ -+ return intel_plane_atomic_check_with_state(to_intel_crtc_state(old_crtc_state), -+ to_intel_crtc_state(new_crtc_state), -+ to_intel_plane_state(old_plane_state), -+ to_intel_plane_state(new_plane_state)); -+} -+ -+static struct intel_plane * -+skl_next_plane_to_commit(struct intel_atomic_state *state, -+ struct intel_crtc *crtc, -+ struct skl_ddb_entry entries_y[I915_MAX_PLANES], -+ struct skl_ddb_entry entries_uv[I915_MAX_PLANES], -+ unsigned int *update_mask) -+{ -+ struct intel_crtc_state *crtc_state = -+ intel_atomic_get_new_crtc_state(state, crtc); -+ struct intel_plane_state *plane_state; -+ struct intel_plane *plane; -+ int i; -+ -+ if (*update_mask == 0) -+ return NULL; -+ -+ for_each_new_intel_plane_in_state(state, plane, plane_state, i) { -+ enum plane_id plane_id = plane->id; -+ -+ if (crtc->pipe != plane->pipe || -+ !(*update_mask & BIT(plane_id))) -+ continue; -+ -+ if (skl_ddb_allocation_overlaps(&crtc_state->wm.skl.plane_ddb_y[plane_id], -+ entries_y, -+ I915_MAX_PLANES, plane_id) || -+ skl_ddb_allocation_overlaps(&crtc_state->wm.skl.plane_ddb_uv[plane_id], -+ entries_uv, -+ I915_MAX_PLANES, plane_id)) -+ continue; -+ -+ *update_mask &= ~BIT(plane_id); -+ entries_y[plane_id] = crtc_state->wm.skl.plane_ddb_y[plane_id]; -+ entries_uv[plane_id] = crtc_state->wm.skl.plane_ddb_uv[plane_id]; -+ -+ return plane; -+ } -+ -+ /* should never happen */ -+ WARN_ON(1); -+ -+ return NULL; -+} -+ -+void intel_update_plane(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ -+ trace_intel_update_plane(&plane->base, crtc); -+ plane->update_plane(plane, crtc_state, plane_state); -+} -+ -+void intel_update_slave(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ -+ trace_intel_update_plane(&plane->base, crtc); -+ plane->update_slave(plane, crtc_state, plane_state); -+} -+ -+void intel_disable_plane(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ -+ trace_intel_disable_plane(&plane->base, crtc); -+ plane->disable_plane(plane, crtc_state); -+} -+ -+void skl_update_planes_on_crtc(struct intel_atomic_state *state, -+ struct intel_crtc *crtc) -+{ -+ struct intel_crtc_state *old_crtc_state = -+ intel_atomic_get_old_crtc_state(state, crtc); -+ struct intel_crtc_state *new_crtc_state = -+ intel_atomic_get_new_crtc_state(state, crtc); -+ struct skl_ddb_entry entries_y[I915_MAX_PLANES]; -+ struct skl_ddb_entry entries_uv[I915_MAX_PLANES]; -+ u32 update_mask = new_crtc_state->update_planes; -+ struct intel_plane *plane; -+ -+ memcpy(entries_y, old_crtc_state->wm.skl.plane_ddb_y, -+ sizeof(old_crtc_state->wm.skl.plane_ddb_y)); -+ memcpy(entries_uv, old_crtc_state->wm.skl.plane_ddb_uv, -+ sizeof(old_crtc_state->wm.skl.plane_ddb_uv)); -+ -+ while ((plane = skl_next_plane_to_commit(state, crtc, -+ entries_y, entries_uv, -+ &update_mask))) { -+ struct intel_plane_state *new_plane_state = -+ intel_atomic_get_new_plane_state(state, plane); -+ -+ if (new_plane_state->base.visible) { -+ intel_update_plane(plane, new_crtc_state, new_plane_state); -+ } else if (new_plane_state->slave) { -+ struct intel_plane *master = -+ new_plane_state->linked_plane; -+ -+ /* -+ * We update the slave plane from this function because -+ * programming it from the master plane's update_plane -+ * callback runs into issues when the Y plane is -+ * reassigned, disabled or used by a different plane. -+ * -+ * The slave plane is updated with the master plane's -+ * plane_state. -+ */ -+ new_plane_state = -+ intel_atomic_get_new_plane_state(state, master); -+ -+ intel_update_slave(plane, new_crtc_state, new_plane_state); -+ } else { -+ intel_disable_plane(plane, new_crtc_state); -+ } -+ } -+} -+ -+void i9xx_update_planes_on_crtc(struct intel_atomic_state *state, -+ struct intel_crtc *crtc) -+{ -+ struct intel_crtc_state *new_crtc_state = -+ intel_atomic_get_new_crtc_state(state, crtc); -+ u32 update_mask = new_crtc_state->update_planes; -+ struct intel_plane_state *new_plane_state; -+ struct intel_plane *plane; -+ int i; -+ -+ for_each_new_intel_plane_in_state(state, plane, new_plane_state, i) { -+ if (crtc->pipe != plane->pipe || -+ !(update_mask & BIT(plane->id))) -+ continue; -+ -+ if (new_plane_state->base.visible) -+ intel_update_plane(plane, new_crtc_state, new_plane_state); -+ else -+ intel_disable_plane(plane, new_crtc_state); -+ } -+} -+ -+const struct drm_plane_helper_funcs intel_plane_helper_funcs = { -+ .prepare_fb = intel_prepare_plane_fb, -+ .cleanup_fb = intel_cleanup_plane_fb, -+ .atomic_check = intel_plane_atomic_check, -+}; -+ -+/** -+ * intel_plane_atomic_get_property - fetch plane property value -+ * @plane: plane to fetch property for -+ * @state: state containing the property value -+ * @property: property to look up -+ * @val: pointer to write property value into -+ * -+ * The DRM core does not store shadow copies of properties for -+ * atomic-capable drivers. This entrypoint is used to fetch -+ * the current value of a driver-specific plane property. -+ */ -+int -+intel_plane_atomic_get_property(struct drm_plane *plane, -+ const struct drm_plane_state *state, -+ struct drm_property *property, -+ u64 *val) -+{ -+ DRM_DEBUG_KMS("Unknown property [PROP:%d:%s]\n", -+ property->base.id, property->name); -+ return -EINVAL; -+} -+ -+/** -+ * intel_plane_atomic_set_property - set plane property value -+ * @plane: plane to set property for -+ * @state: state to update property value in -+ * @property: property to set -+ * @val: value to set property to -+ * -+ * Writes the specified property value for a plane into the provided atomic -+ * state object. -+ * -+ * Returns 0 on success, -EINVAL on unrecognized properties -+ */ -+int -+intel_plane_atomic_set_property(struct drm_plane *plane, -+ struct drm_plane_state *state, -+ struct drm_property *property, -+ u64 val) -+{ -+ DRM_DEBUG_KMS("Unknown property [PROP:%d:%s]\n", -+ property->base.id, property->name); -+ return -EINVAL; -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_atomic_plane.h b/drivers/gpu/drm/i915_legacy/intel_atomic_plane.h -new file mode 100644 -index 000000000000..14678620440f ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_atomic_plane.h -@@ -0,0 +1,40 @@ -+/* SPDX-License-Identifier: MIT */ -+/* -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef __INTEL_ATOMIC_PLANE_H__ -+#define __INTEL_ATOMIC_PLANE_H__ -+ -+struct drm_plane; -+struct intel_atomic_state; -+struct intel_crtc; -+struct intel_crtc_state; -+struct intel_plane; -+struct intel_plane_state; -+ -+extern const struct drm_plane_helper_funcs intel_plane_helper_funcs; -+ -+void intel_update_plane(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state); -+void intel_update_slave(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state); -+void intel_disable_plane(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state); -+struct intel_plane *intel_plane_alloc(void); -+void intel_plane_free(struct intel_plane *plane); -+struct drm_plane_state *intel_plane_duplicate_state(struct drm_plane *plane); -+void intel_plane_destroy_state(struct drm_plane *plane, -+ struct drm_plane_state *state); -+void skl_update_planes_on_crtc(struct intel_atomic_state *state, -+ struct intel_crtc *crtc); -+void i9xx_update_planes_on_crtc(struct intel_atomic_state *state, -+ struct intel_crtc *crtc); -+int intel_plane_atomic_check_with_state(const struct intel_crtc_state *old_crtc_state, -+ struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *old_plane_state, -+ struct intel_plane_state *intel_state); -+ -+#endif /* __INTEL_ATOMIC_PLANE_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_audio.c b/drivers/gpu/drm/i915_legacy/intel_audio.c -new file mode 100644 -index 000000000000..bca4cc025d3d ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_audio.c -@@ -0,0 +1,1105 @@ -+/* -+ * Copyright © 2014 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#include -+#include -+ -+#include -+#include -+#include -+ -+#include "i915_drv.h" -+#include "intel_audio.h" -+#include "intel_drv.h" -+ -+/** -+ * DOC: High Definition Audio over HDMI and Display Port -+ * -+ * The graphics and audio drivers together support High Definition Audio over -+ * HDMI and Display Port. The audio programming sequences are divided into audio -+ * codec and controller enable and disable sequences. The graphics driver -+ * handles the audio codec sequences, while the audio driver handles the audio -+ * controller sequences. -+ * -+ * The disable sequences must be performed before disabling the transcoder or -+ * port. The enable sequences may only be performed after enabling the -+ * transcoder and port, and after completed link training. Therefore the audio -+ * enable/disable sequences are part of the modeset sequence. -+ * -+ * The codec and controller sequences could be done either parallel or serial, -+ * but generally the ELDV/PD change in the codec sequence indicates to the audio -+ * driver that the controller sequence should start. Indeed, most of the -+ * co-operation between the graphics and audio drivers is handled via audio -+ * related registers. (The notable exception is the power management, not -+ * covered here.) -+ * -+ * The struct &i915_audio_component is used to interact between the graphics -+ * and audio drivers. The struct &i915_audio_component_ops @ops in it is -+ * defined in graphics driver and called in audio driver. The -+ * struct &i915_audio_component_audio_ops @audio_ops is called from i915 driver. -+ */ -+ -+/* DP N/M table */ -+#define LC_810M 810000 -+#define LC_540M 540000 -+#define LC_270M 270000 -+#define LC_162M 162000 -+ -+struct dp_aud_n_m { -+ int sample_rate; -+ int clock; -+ u16 m; -+ u16 n; -+}; -+ -+/* Values according to DP 1.4 Table 2-104 */ -+static const struct dp_aud_n_m dp_aud_n_m[] = { -+ { 32000, LC_162M, 1024, 10125 }, -+ { 44100, LC_162M, 784, 5625 }, -+ { 48000, LC_162M, 512, 3375 }, -+ { 64000, LC_162M, 2048, 10125 }, -+ { 88200, LC_162M, 1568, 5625 }, -+ { 96000, LC_162M, 1024, 3375 }, -+ { 128000, LC_162M, 4096, 10125 }, -+ { 176400, LC_162M, 3136, 5625 }, -+ { 192000, LC_162M, 2048, 3375 }, -+ { 32000, LC_270M, 1024, 16875 }, -+ { 44100, LC_270M, 784, 9375 }, -+ { 48000, LC_270M, 512, 5625 }, -+ { 64000, LC_270M, 2048, 16875 }, -+ { 88200, LC_270M, 1568, 9375 }, -+ { 96000, LC_270M, 1024, 5625 }, -+ { 128000, LC_270M, 4096, 16875 }, -+ { 176400, LC_270M, 3136, 9375 }, -+ { 192000, LC_270M, 2048, 5625 }, -+ { 32000, LC_540M, 1024, 33750 }, -+ { 44100, LC_540M, 784, 18750 }, -+ { 48000, LC_540M, 512, 11250 }, -+ { 64000, LC_540M, 2048, 33750 }, -+ { 88200, LC_540M, 1568, 18750 }, -+ { 96000, LC_540M, 1024, 11250 }, -+ { 128000, LC_540M, 4096, 33750 }, -+ { 176400, LC_540M, 3136, 18750 }, -+ { 192000, LC_540M, 2048, 11250 }, -+ { 32000, LC_810M, 1024, 50625 }, -+ { 44100, LC_810M, 784, 28125 }, -+ { 48000, LC_810M, 512, 16875 }, -+ { 64000, LC_810M, 2048, 50625 }, -+ { 88200, LC_810M, 1568, 28125 }, -+ { 96000, LC_810M, 1024, 16875 }, -+ { 128000, LC_810M, 4096, 50625 }, -+ { 176400, LC_810M, 3136, 28125 }, -+ { 192000, LC_810M, 2048, 16875 }, -+}; -+ -+static const struct dp_aud_n_m * -+audio_config_dp_get_n_m(const struct intel_crtc_state *crtc_state, int rate) -+{ -+ int i; -+ -+ for (i = 0; i < ARRAY_SIZE(dp_aud_n_m); i++) { -+ if (rate == dp_aud_n_m[i].sample_rate && -+ crtc_state->port_clock == dp_aud_n_m[i].clock) -+ return &dp_aud_n_m[i]; -+ } -+ -+ return NULL; -+} -+ -+static const struct { -+ int clock; -+ u32 config; -+} hdmi_audio_clock[] = { -+ { 25175, AUD_CONFIG_PIXEL_CLOCK_HDMI_25175 }, -+ { 25200, AUD_CONFIG_PIXEL_CLOCK_HDMI_25200 }, /* default per bspec */ -+ { 27000, AUD_CONFIG_PIXEL_CLOCK_HDMI_27000 }, -+ { 27027, AUD_CONFIG_PIXEL_CLOCK_HDMI_27027 }, -+ { 54000, AUD_CONFIG_PIXEL_CLOCK_HDMI_54000 }, -+ { 54054, AUD_CONFIG_PIXEL_CLOCK_HDMI_54054 }, -+ { 74176, AUD_CONFIG_PIXEL_CLOCK_HDMI_74176 }, -+ { 74250, AUD_CONFIG_PIXEL_CLOCK_HDMI_74250 }, -+ { 148352, AUD_CONFIG_PIXEL_CLOCK_HDMI_148352 }, -+ { 148500, AUD_CONFIG_PIXEL_CLOCK_HDMI_148500 }, -+}; -+ -+/* HDMI N/CTS table */ -+#define TMDS_297M 297000 -+#define TMDS_296M 296703 -+#define TMDS_594M 594000 -+#define TMDS_593M 593407 -+ -+static const struct { -+ int sample_rate; -+ int clock; -+ int n; -+ int cts; -+} hdmi_aud_ncts[] = { -+ { 32000, TMDS_296M, 5824, 421875 }, -+ { 32000, TMDS_297M, 3072, 222750 }, -+ { 32000, TMDS_593M, 5824, 843750 }, -+ { 32000, TMDS_594M, 3072, 445500 }, -+ { 44100, TMDS_296M, 4459, 234375 }, -+ { 44100, TMDS_297M, 4704, 247500 }, -+ { 44100, TMDS_593M, 8918, 937500 }, -+ { 44100, TMDS_594M, 9408, 990000 }, -+ { 88200, TMDS_296M, 8918, 234375 }, -+ { 88200, TMDS_297M, 9408, 247500 }, -+ { 88200, TMDS_593M, 17836, 937500 }, -+ { 88200, TMDS_594M, 18816, 990000 }, -+ { 176400, TMDS_296M, 17836, 234375 }, -+ { 176400, TMDS_297M, 18816, 247500 }, -+ { 176400, TMDS_593M, 35672, 937500 }, -+ { 176400, TMDS_594M, 37632, 990000 }, -+ { 48000, TMDS_296M, 5824, 281250 }, -+ { 48000, TMDS_297M, 5120, 247500 }, -+ { 48000, TMDS_593M, 5824, 562500 }, -+ { 48000, TMDS_594M, 6144, 594000 }, -+ { 96000, TMDS_296M, 11648, 281250 }, -+ { 96000, TMDS_297M, 10240, 247500 }, -+ { 96000, TMDS_593M, 11648, 562500 }, -+ { 96000, TMDS_594M, 12288, 594000 }, -+ { 192000, TMDS_296M, 23296, 281250 }, -+ { 192000, TMDS_297M, 20480, 247500 }, -+ { 192000, TMDS_593M, 23296, 562500 }, -+ { 192000, TMDS_594M, 24576, 594000 }, -+}; -+ -+/* get AUD_CONFIG_PIXEL_CLOCK_HDMI_* value for mode */ -+static u32 audio_config_hdmi_pixel_clock(const struct intel_crtc_state *crtc_state) -+{ -+ const struct drm_display_mode *adjusted_mode = -+ &crtc_state->base.adjusted_mode; -+ int i; -+ -+ for (i = 0; i < ARRAY_SIZE(hdmi_audio_clock); i++) { -+ if (adjusted_mode->crtc_clock == hdmi_audio_clock[i].clock) -+ break; -+ } -+ -+ if (i == ARRAY_SIZE(hdmi_audio_clock)) { -+ DRM_DEBUG_KMS("HDMI audio pixel clock setting for %d not found, falling back to defaults\n", -+ adjusted_mode->crtc_clock); -+ i = 1; -+ } -+ -+ DRM_DEBUG_KMS("Configuring HDMI audio for pixel clock %d (0x%08x)\n", -+ hdmi_audio_clock[i].clock, -+ hdmi_audio_clock[i].config); -+ -+ return hdmi_audio_clock[i].config; -+} -+ -+static int audio_config_hdmi_get_n(const struct intel_crtc_state *crtc_state, -+ int rate) -+{ -+ const struct drm_display_mode *adjusted_mode = -+ &crtc_state->base.adjusted_mode; -+ int i; -+ -+ for (i = 0; i < ARRAY_SIZE(hdmi_aud_ncts); i++) { -+ if (rate == hdmi_aud_ncts[i].sample_rate && -+ adjusted_mode->crtc_clock == hdmi_aud_ncts[i].clock) { -+ return hdmi_aud_ncts[i].n; -+ } -+ } -+ return 0; -+} -+ -+static bool intel_eld_uptodate(struct drm_connector *connector, -+ i915_reg_t reg_eldv, u32 bits_eldv, -+ i915_reg_t reg_elda, u32 bits_elda, -+ i915_reg_t reg_edid) -+{ -+ struct drm_i915_private *dev_priv = to_i915(connector->dev); -+ const u8 *eld = connector->eld; -+ u32 tmp; -+ int i; -+ -+ tmp = I915_READ(reg_eldv); -+ tmp &= bits_eldv; -+ -+ if (!tmp) -+ return false; -+ -+ tmp = I915_READ(reg_elda); -+ tmp &= ~bits_elda; -+ I915_WRITE(reg_elda, tmp); -+ -+ for (i = 0; i < drm_eld_size(eld) / 4; i++) -+ if (I915_READ(reg_edid) != *((const u32 *)eld + i)) -+ return false; -+ -+ return true; -+} -+ -+static void g4x_audio_codec_disable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ u32 eldv, tmp; -+ -+ DRM_DEBUG_KMS("Disable audio codec\n"); -+ -+ tmp = I915_READ(G4X_AUD_VID_DID); -+ if (tmp == INTEL_AUDIO_DEVBLC || tmp == INTEL_AUDIO_DEVCL) -+ eldv = G4X_ELDV_DEVCL_DEVBLC; -+ else -+ eldv = G4X_ELDV_DEVCTG; -+ -+ /* Invalidate ELD */ -+ tmp = I915_READ(G4X_AUD_CNTL_ST); -+ tmp &= ~eldv; -+ I915_WRITE(G4X_AUD_CNTL_ST, tmp); -+} -+ -+static void g4x_audio_codec_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct drm_connector *connector = conn_state->connector; -+ const u8 *eld = connector->eld; -+ u32 eldv; -+ u32 tmp; -+ int len, i; -+ -+ DRM_DEBUG_KMS("Enable audio codec, %u bytes ELD\n", drm_eld_size(eld)); -+ -+ tmp = I915_READ(G4X_AUD_VID_DID); -+ if (tmp == INTEL_AUDIO_DEVBLC || tmp == INTEL_AUDIO_DEVCL) -+ eldv = G4X_ELDV_DEVCL_DEVBLC; -+ else -+ eldv = G4X_ELDV_DEVCTG; -+ -+ if (intel_eld_uptodate(connector, -+ G4X_AUD_CNTL_ST, eldv, -+ G4X_AUD_CNTL_ST, G4X_ELD_ADDR_MASK, -+ G4X_HDMIW_HDMIEDID)) -+ return; -+ -+ tmp = I915_READ(G4X_AUD_CNTL_ST); -+ tmp &= ~(eldv | G4X_ELD_ADDR_MASK); -+ len = (tmp >> 9) & 0x1f; /* ELD buffer size */ -+ I915_WRITE(G4X_AUD_CNTL_ST, tmp); -+ -+ len = min(drm_eld_size(eld) / 4, len); -+ DRM_DEBUG_DRIVER("ELD size %d\n", len); -+ for (i = 0; i < len; i++) -+ I915_WRITE(G4X_HDMIW_HDMIEDID, *((const u32 *)eld + i)); -+ -+ tmp = I915_READ(G4X_AUD_CNTL_ST); -+ tmp |= eldv; -+ I915_WRITE(G4X_AUD_CNTL_ST, tmp); -+} -+ -+static void -+hsw_dp_audio_config_update(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct i915_audio_component *acomp = dev_priv->audio_component; -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ enum port port = encoder->port; -+ enum pipe pipe = crtc->pipe; -+ const struct dp_aud_n_m *nm; -+ int rate; -+ u32 tmp; -+ -+ rate = acomp ? acomp->aud_sample_rate[port] : 0; -+ nm = audio_config_dp_get_n_m(crtc_state, rate); -+ if (nm) -+ DRM_DEBUG_KMS("using Maud %u, Naud %u\n", nm->m, nm->n); -+ else -+ DRM_DEBUG_KMS("using automatic Maud, Naud\n"); -+ -+ tmp = I915_READ(HSW_AUD_CFG(pipe)); -+ tmp &= ~AUD_CONFIG_N_VALUE_INDEX; -+ tmp &= ~AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK; -+ tmp &= ~AUD_CONFIG_N_PROG_ENABLE; -+ tmp |= AUD_CONFIG_N_VALUE_INDEX; -+ -+ if (nm) { -+ tmp &= ~AUD_CONFIG_N_MASK; -+ tmp |= AUD_CONFIG_N(nm->n); -+ tmp |= AUD_CONFIG_N_PROG_ENABLE; -+ } -+ -+ I915_WRITE(HSW_AUD_CFG(pipe), tmp); -+ -+ tmp = I915_READ(HSW_AUD_M_CTS_ENABLE(pipe)); -+ tmp &= ~AUD_CONFIG_M_MASK; -+ tmp &= ~AUD_M_CTS_M_VALUE_INDEX; -+ tmp &= ~AUD_M_CTS_M_PROG_ENABLE; -+ -+ if (nm) { -+ tmp |= nm->m; -+ tmp |= AUD_M_CTS_M_VALUE_INDEX; -+ tmp |= AUD_M_CTS_M_PROG_ENABLE; -+ } -+ -+ I915_WRITE(HSW_AUD_M_CTS_ENABLE(pipe), tmp); -+} -+ -+static void -+hsw_hdmi_audio_config_update(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct i915_audio_component *acomp = dev_priv->audio_component; -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ enum port port = encoder->port; -+ enum pipe pipe = crtc->pipe; -+ int n, rate; -+ u32 tmp; -+ -+ rate = acomp ? acomp->aud_sample_rate[port] : 0; -+ -+ tmp = I915_READ(HSW_AUD_CFG(pipe)); -+ tmp &= ~AUD_CONFIG_N_VALUE_INDEX; -+ tmp &= ~AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK; -+ tmp &= ~AUD_CONFIG_N_PROG_ENABLE; -+ tmp |= audio_config_hdmi_pixel_clock(crtc_state); -+ -+ n = audio_config_hdmi_get_n(crtc_state, rate); -+ if (n != 0) { -+ DRM_DEBUG_KMS("using N %d\n", n); -+ -+ tmp &= ~AUD_CONFIG_N_MASK; -+ tmp |= AUD_CONFIG_N(n); -+ tmp |= AUD_CONFIG_N_PROG_ENABLE; -+ } else { -+ DRM_DEBUG_KMS("using automatic N\n"); -+ } -+ -+ I915_WRITE(HSW_AUD_CFG(pipe), tmp); -+ -+ /* -+ * Let's disable "Enable CTS or M Prog bit" -+ * and let HW calculate the value -+ */ -+ tmp = I915_READ(HSW_AUD_M_CTS_ENABLE(pipe)); -+ tmp &= ~AUD_M_CTS_M_PROG_ENABLE; -+ tmp &= ~AUD_M_CTS_M_VALUE_INDEX; -+ I915_WRITE(HSW_AUD_M_CTS_ENABLE(pipe), tmp); -+} -+ -+static void -+hsw_audio_config_update(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ if (intel_crtc_has_dp_encoder(crtc_state)) -+ hsw_dp_audio_config_update(encoder, crtc_state); -+ else -+ hsw_hdmi_audio_config_update(encoder, crtc_state); -+} -+ -+static void hsw_audio_codec_disable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->base.crtc); -+ enum pipe pipe = crtc->pipe; -+ u32 tmp; -+ -+ DRM_DEBUG_KMS("Disable audio codec on pipe %c\n", pipe_name(pipe)); -+ -+ mutex_lock(&dev_priv->av_mutex); -+ -+ /* Disable timestamps */ -+ tmp = I915_READ(HSW_AUD_CFG(pipe)); -+ tmp &= ~AUD_CONFIG_N_VALUE_INDEX; -+ tmp |= AUD_CONFIG_N_PROG_ENABLE; -+ tmp &= ~AUD_CONFIG_UPPER_N_MASK; -+ tmp &= ~AUD_CONFIG_LOWER_N_MASK; -+ if (intel_crtc_has_dp_encoder(old_crtc_state)) -+ tmp |= AUD_CONFIG_N_VALUE_INDEX; -+ I915_WRITE(HSW_AUD_CFG(pipe), tmp); -+ -+ /* Invalidate ELD */ -+ tmp = I915_READ(HSW_AUD_PIN_ELD_CP_VLD); -+ tmp &= ~AUDIO_ELD_VALID(pipe); -+ tmp &= ~AUDIO_OUTPUT_ENABLE(pipe); -+ I915_WRITE(HSW_AUD_PIN_ELD_CP_VLD, tmp); -+ -+ mutex_unlock(&dev_priv->av_mutex); -+} -+ -+static void hsw_audio_codec_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_connector *connector = conn_state->connector; -+ enum pipe pipe = crtc->pipe; -+ const u8 *eld = connector->eld; -+ u32 tmp; -+ int len, i; -+ -+ DRM_DEBUG_KMS("Enable audio codec on pipe %c, %u bytes ELD\n", -+ pipe_name(pipe), drm_eld_size(eld)); -+ -+ mutex_lock(&dev_priv->av_mutex); -+ -+ /* Enable audio presence detect, invalidate ELD */ -+ tmp = I915_READ(HSW_AUD_PIN_ELD_CP_VLD); -+ tmp |= AUDIO_OUTPUT_ENABLE(pipe); -+ tmp &= ~AUDIO_ELD_VALID(pipe); -+ I915_WRITE(HSW_AUD_PIN_ELD_CP_VLD, tmp); -+ -+ /* -+ * FIXME: We're supposed to wait for vblank here, but we have vblanks -+ * disabled during the mode set. The proper fix would be to push the -+ * rest of the setup into a vblank work item, queued here, but the -+ * infrastructure is not there yet. -+ */ -+ -+ /* Reset ELD write address */ -+ tmp = I915_READ(HSW_AUD_DIP_ELD_CTRL(pipe)); -+ tmp &= ~IBX_ELD_ADDRESS_MASK; -+ I915_WRITE(HSW_AUD_DIP_ELD_CTRL(pipe), tmp); -+ -+ /* Up to 84 bytes of hw ELD buffer */ -+ len = min(drm_eld_size(eld), 84); -+ for (i = 0; i < len / 4; i++) -+ I915_WRITE(HSW_AUD_EDID_DATA(pipe), *((const u32 *)eld + i)); -+ -+ /* ELD valid */ -+ tmp = I915_READ(HSW_AUD_PIN_ELD_CP_VLD); -+ tmp |= AUDIO_ELD_VALID(pipe); -+ I915_WRITE(HSW_AUD_PIN_ELD_CP_VLD, tmp); -+ -+ /* Enable timestamps */ -+ hsw_audio_config_update(encoder, crtc_state); -+ -+ mutex_unlock(&dev_priv->av_mutex); -+} -+ -+static void ilk_audio_codec_disable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->base.crtc); -+ enum pipe pipe = crtc->pipe; -+ enum port port = encoder->port; -+ u32 tmp, eldv; -+ i915_reg_t aud_config, aud_cntrl_st2; -+ -+ DRM_DEBUG_KMS("Disable audio codec on port %c, pipe %c\n", -+ port_name(port), pipe_name(pipe)); -+ -+ if (WARN_ON(port == PORT_A)) -+ return; -+ -+ if (HAS_PCH_IBX(dev_priv)) { -+ aud_config = IBX_AUD_CFG(pipe); -+ aud_cntrl_st2 = IBX_AUD_CNTL_ST2; -+ } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ aud_config = VLV_AUD_CFG(pipe); -+ aud_cntrl_st2 = VLV_AUD_CNTL_ST2; -+ } else { -+ aud_config = CPT_AUD_CFG(pipe); -+ aud_cntrl_st2 = CPT_AUD_CNTRL_ST2; -+ } -+ -+ /* Disable timestamps */ -+ tmp = I915_READ(aud_config); -+ tmp &= ~AUD_CONFIG_N_VALUE_INDEX; -+ tmp |= AUD_CONFIG_N_PROG_ENABLE; -+ tmp &= ~AUD_CONFIG_UPPER_N_MASK; -+ tmp &= ~AUD_CONFIG_LOWER_N_MASK; -+ if (intel_crtc_has_dp_encoder(old_crtc_state)) -+ tmp |= AUD_CONFIG_N_VALUE_INDEX; -+ I915_WRITE(aud_config, tmp); -+ -+ eldv = IBX_ELD_VALID(port); -+ -+ /* Invalidate ELD */ -+ tmp = I915_READ(aud_cntrl_st2); -+ tmp &= ~eldv; -+ I915_WRITE(aud_cntrl_st2, tmp); -+} -+ -+static void ilk_audio_codec_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_connector *connector = conn_state->connector; -+ enum pipe pipe = crtc->pipe; -+ enum port port = encoder->port; -+ const u8 *eld = connector->eld; -+ u32 tmp, eldv; -+ int len, i; -+ i915_reg_t hdmiw_hdmiedid, aud_config, aud_cntl_st, aud_cntrl_st2; -+ -+ DRM_DEBUG_KMS("Enable audio codec on port %c, pipe %c, %u bytes ELD\n", -+ port_name(port), pipe_name(pipe), drm_eld_size(eld)); -+ -+ if (WARN_ON(port == PORT_A)) -+ return; -+ -+ /* -+ * FIXME: We're supposed to wait for vblank here, but we have vblanks -+ * disabled during the mode set. The proper fix would be to push the -+ * rest of the setup into a vblank work item, queued here, but the -+ * infrastructure is not there yet. -+ */ -+ -+ if (HAS_PCH_IBX(dev_priv)) { -+ hdmiw_hdmiedid = IBX_HDMIW_HDMIEDID(pipe); -+ aud_config = IBX_AUD_CFG(pipe); -+ aud_cntl_st = IBX_AUD_CNTL_ST(pipe); -+ aud_cntrl_st2 = IBX_AUD_CNTL_ST2; -+ } else if (IS_VALLEYVIEW(dev_priv) || -+ IS_CHERRYVIEW(dev_priv)) { -+ hdmiw_hdmiedid = VLV_HDMIW_HDMIEDID(pipe); -+ aud_config = VLV_AUD_CFG(pipe); -+ aud_cntl_st = VLV_AUD_CNTL_ST(pipe); -+ aud_cntrl_st2 = VLV_AUD_CNTL_ST2; -+ } else { -+ hdmiw_hdmiedid = CPT_HDMIW_HDMIEDID(pipe); -+ aud_config = CPT_AUD_CFG(pipe); -+ aud_cntl_st = CPT_AUD_CNTL_ST(pipe); -+ aud_cntrl_st2 = CPT_AUD_CNTRL_ST2; -+ } -+ -+ eldv = IBX_ELD_VALID(port); -+ -+ /* Invalidate ELD */ -+ tmp = I915_READ(aud_cntrl_st2); -+ tmp &= ~eldv; -+ I915_WRITE(aud_cntrl_st2, tmp); -+ -+ /* Reset ELD write address */ -+ tmp = I915_READ(aud_cntl_st); -+ tmp &= ~IBX_ELD_ADDRESS_MASK; -+ I915_WRITE(aud_cntl_st, tmp); -+ -+ /* Up to 84 bytes of hw ELD buffer */ -+ len = min(drm_eld_size(eld), 84); -+ for (i = 0; i < len / 4; i++) -+ I915_WRITE(hdmiw_hdmiedid, *((const u32 *)eld + i)); -+ -+ /* ELD valid */ -+ tmp = I915_READ(aud_cntrl_st2); -+ tmp |= eldv; -+ I915_WRITE(aud_cntrl_st2, tmp); -+ -+ /* Enable timestamps */ -+ tmp = I915_READ(aud_config); -+ tmp &= ~AUD_CONFIG_N_VALUE_INDEX; -+ tmp &= ~AUD_CONFIG_N_PROG_ENABLE; -+ tmp &= ~AUD_CONFIG_PIXEL_CLOCK_HDMI_MASK; -+ if (intel_crtc_has_dp_encoder(crtc_state)) -+ tmp |= AUD_CONFIG_N_VALUE_INDEX; -+ else -+ tmp |= audio_config_hdmi_pixel_clock(crtc_state); -+ I915_WRITE(aud_config, tmp); -+} -+ -+/** -+ * intel_audio_codec_enable - Enable the audio codec for HD audio -+ * @encoder: encoder on which to enable audio -+ * @crtc_state: pointer to the current crtc state. -+ * @conn_state: pointer to the current connector state. -+ * -+ * The enable sequences may only be performed after enabling the transcoder and -+ * port, and after completed link training. -+ */ -+void intel_audio_codec_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct i915_audio_component *acomp = dev_priv->audio_component; -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_connector *connector = conn_state->connector; -+ const struct drm_display_mode *adjusted_mode = -+ &crtc_state->base.adjusted_mode; -+ enum port port = encoder->port; -+ enum pipe pipe = crtc->pipe; -+ -+ if (!connector->eld[0]) -+ return; -+ -+ DRM_DEBUG_DRIVER("ELD on [CONNECTOR:%d:%s], [ENCODER:%d:%s]\n", -+ connector->base.id, -+ connector->name, -+ connector->encoder->base.id, -+ connector->encoder->name); -+ -+ connector->eld[6] = drm_av_sync_delay(connector, adjusted_mode) / 2; -+ -+ if (dev_priv->display.audio_codec_enable) -+ dev_priv->display.audio_codec_enable(encoder, -+ crtc_state, -+ conn_state); -+ -+ mutex_lock(&dev_priv->av_mutex); -+ encoder->audio_connector = connector; -+ -+ /* referred in audio callbacks */ -+ dev_priv->av_enc_map[pipe] = encoder; -+ mutex_unlock(&dev_priv->av_mutex); -+ -+ if (acomp && acomp->base.audio_ops && -+ acomp->base.audio_ops->pin_eld_notify) { -+ /* audio drivers expect pipe = -1 to indicate Non-MST cases */ -+ if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST)) -+ pipe = -1; -+ acomp->base.audio_ops->pin_eld_notify(acomp->base.audio_ops->audio_ptr, -+ (int) port, (int) pipe); -+ } -+ -+ intel_lpe_audio_notify(dev_priv, pipe, port, connector->eld, -+ crtc_state->port_clock, -+ intel_crtc_has_dp_encoder(crtc_state)); -+} -+ -+/** -+ * intel_audio_codec_disable - Disable the audio codec for HD audio -+ * @encoder: encoder on which to disable audio -+ * @old_crtc_state: pointer to the old crtc state. -+ * @old_conn_state: pointer to the old connector state. -+ * -+ * The disable sequences must be performed before disabling the transcoder or -+ * port. -+ */ -+void intel_audio_codec_disable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct i915_audio_component *acomp = dev_priv->audio_component; -+ struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->base.crtc); -+ enum port port = encoder->port; -+ enum pipe pipe = crtc->pipe; -+ -+ if (dev_priv->display.audio_codec_disable) -+ dev_priv->display.audio_codec_disable(encoder, -+ old_crtc_state, -+ old_conn_state); -+ -+ mutex_lock(&dev_priv->av_mutex); -+ encoder->audio_connector = NULL; -+ dev_priv->av_enc_map[pipe] = NULL; -+ mutex_unlock(&dev_priv->av_mutex); -+ -+ if (acomp && acomp->base.audio_ops && -+ acomp->base.audio_ops->pin_eld_notify) { -+ /* audio drivers expect pipe = -1 to indicate Non-MST cases */ -+ if (!intel_crtc_has_type(old_crtc_state, INTEL_OUTPUT_DP_MST)) -+ pipe = -1; -+ acomp->base.audio_ops->pin_eld_notify(acomp->base.audio_ops->audio_ptr, -+ (int) port, (int) pipe); -+ } -+ -+ intel_lpe_audio_notify(dev_priv, pipe, port, NULL, 0, false); -+} -+ -+/** -+ * intel_init_audio_hooks - Set up chip specific audio hooks -+ * @dev_priv: device private -+ */ -+void intel_init_audio_hooks(struct drm_i915_private *dev_priv) -+{ -+ if (IS_G4X(dev_priv)) { -+ dev_priv->display.audio_codec_enable = g4x_audio_codec_enable; -+ dev_priv->display.audio_codec_disable = g4x_audio_codec_disable; -+ } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ dev_priv->display.audio_codec_enable = ilk_audio_codec_enable; -+ dev_priv->display.audio_codec_disable = ilk_audio_codec_disable; -+ } else if (IS_HASWELL(dev_priv) || INTEL_GEN(dev_priv) >= 8) { -+ dev_priv->display.audio_codec_enable = hsw_audio_codec_enable; -+ dev_priv->display.audio_codec_disable = hsw_audio_codec_disable; -+ } else if (HAS_PCH_SPLIT(dev_priv)) { -+ dev_priv->display.audio_codec_enable = ilk_audio_codec_enable; -+ dev_priv->display.audio_codec_disable = ilk_audio_codec_disable; -+ } -+} -+ -+static void glk_force_audio_cdclk(struct drm_i915_private *dev_priv, -+ bool enable) -+{ -+ struct drm_modeset_acquire_ctx ctx; -+ struct drm_atomic_state *state; -+ int ret; -+ -+ drm_modeset_acquire_init(&ctx, 0); -+ state = drm_atomic_state_alloc(&dev_priv->drm); -+ if (WARN_ON(!state)) -+ return; -+ -+ state->acquire_ctx = &ctx; -+ -+retry: -+ to_intel_atomic_state(state)->cdclk.force_min_cdclk_changed = true; -+ to_intel_atomic_state(state)->cdclk.force_min_cdclk = -+ enable ? 2 * 96000 : 0; -+ -+ /* -+ * Protects dev_priv->cdclk.force_min_cdclk -+ * Need to lock this here in case we have no active pipes -+ * and thus wouldn't lock it during the commit otherwise. -+ */ -+ ret = drm_modeset_lock(&dev_priv->drm.mode_config.connection_mutex, -+ &ctx); -+ if (!ret) -+ ret = drm_atomic_commit(state); -+ -+ if (ret == -EDEADLK) { -+ drm_atomic_state_clear(state); -+ drm_modeset_backoff(&ctx); -+ goto retry; -+ } -+ -+ WARN_ON(ret); -+ -+ drm_atomic_state_put(state); -+ -+ drm_modeset_drop_locks(&ctx); -+ drm_modeset_acquire_fini(&ctx); -+} -+ -+static unsigned long i915_audio_component_get_power(struct device *kdev) -+{ -+ struct drm_i915_private *dev_priv = kdev_to_i915(kdev); -+ intel_wakeref_t ret; -+ -+ /* Catch potential impedance mismatches before they occur! */ -+ BUILD_BUG_ON(sizeof(intel_wakeref_t) > sizeof(unsigned long)); -+ -+ ret = intel_display_power_get(dev_priv, POWER_DOMAIN_AUDIO); -+ -+ /* Force CDCLK to 2*BCLK as long as we need audio to be powered. */ -+ if (dev_priv->audio_power_refcount++ == 0) -+ if (IS_CANNONLAKE(dev_priv) || IS_GEMINILAKE(dev_priv)) -+ glk_force_audio_cdclk(dev_priv, true); -+ -+ return ret; -+} -+ -+static void i915_audio_component_put_power(struct device *kdev, -+ unsigned long cookie) -+{ -+ struct drm_i915_private *dev_priv = kdev_to_i915(kdev); -+ -+ /* Stop forcing CDCLK to 2*BCLK if no need for audio to be powered. */ -+ if (--dev_priv->audio_power_refcount == 0) -+ if (IS_CANNONLAKE(dev_priv) || IS_GEMINILAKE(dev_priv)) -+ glk_force_audio_cdclk(dev_priv, false); -+ -+ intel_display_power_put(dev_priv, POWER_DOMAIN_AUDIO, cookie); -+} -+ -+static void i915_audio_component_codec_wake_override(struct device *kdev, -+ bool enable) -+{ -+ struct drm_i915_private *dev_priv = kdev_to_i915(kdev); -+ unsigned long cookie; -+ u32 tmp; -+ -+ if (!IS_GEN(dev_priv, 9)) -+ return; -+ -+ cookie = i915_audio_component_get_power(kdev); -+ -+ /* -+ * Enable/disable generating the codec wake signal, overriding the -+ * internal logic to generate the codec wake to controller. -+ */ -+ tmp = I915_READ(HSW_AUD_CHICKENBIT); -+ tmp &= ~SKL_AUD_CODEC_WAKE_SIGNAL; -+ I915_WRITE(HSW_AUD_CHICKENBIT, tmp); -+ usleep_range(1000, 1500); -+ -+ if (enable) { -+ tmp = I915_READ(HSW_AUD_CHICKENBIT); -+ tmp |= SKL_AUD_CODEC_WAKE_SIGNAL; -+ I915_WRITE(HSW_AUD_CHICKENBIT, tmp); -+ usleep_range(1000, 1500); -+ } -+ -+ i915_audio_component_put_power(kdev, cookie); -+} -+ -+/* Get CDCLK in kHz */ -+static int i915_audio_component_get_cdclk_freq(struct device *kdev) -+{ -+ struct drm_i915_private *dev_priv = kdev_to_i915(kdev); -+ -+ if (WARN_ON_ONCE(!HAS_DDI(dev_priv))) -+ return -ENODEV; -+ -+ return dev_priv->cdclk.hw.cdclk; -+} -+ -+/* -+ * get the intel_encoder according to the parameter port and pipe -+ * intel_encoder is saved by the index of pipe -+ * MST & (pipe >= 0): return the av_enc_map[pipe], -+ * when port is matched -+ * MST & (pipe < 0): this is invalid -+ * Non-MST & (pipe >= 0): only pipe = 0 (the first device entry) -+ * will get the right intel_encoder with port matched -+ * Non-MST & (pipe < 0): get the right intel_encoder with port matched -+ */ -+static struct intel_encoder *get_saved_enc(struct drm_i915_private *dev_priv, -+ int port, int pipe) -+{ -+ struct intel_encoder *encoder; -+ -+ /* MST */ -+ if (pipe >= 0) { -+ if (WARN_ON(pipe >= ARRAY_SIZE(dev_priv->av_enc_map))) -+ return NULL; -+ -+ encoder = dev_priv->av_enc_map[pipe]; -+ /* -+ * when bootup, audio driver may not know it is -+ * MST or not. So it will poll all the port & pipe -+ * combinations -+ */ -+ if (encoder != NULL && encoder->port == port && -+ encoder->type == INTEL_OUTPUT_DP_MST) -+ return encoder; -+ } -+ -+ /* Non-MST */ -+ if (pipe > 0) -+ return NULL; -+ -+ for_each_pipe(dev_priv, pipe) { -+ encoder = dev_priv->av_enc_map[pipe]; -+ if (encoder == NULL) -+ continue; -+ -+ if (encoder->type == INTEL_OUTPUT_DP_MST) -+ continue; -+ -+ if (port == encoder->port) -+ return encoder; -+ } -+ -+ return NULL; -+} -+ -+static int i915_audio_component_sync_audio_rate(struct device *kdev, int port, -+ int pipe, int rate) -+{ -+ struct drm_i915_private *dev_priv = kdev_to_i915(kdev); -+ struct i915_audio_component *acomp = dev_priv->audio_component; -+ struct intel_encoder *encoder; -+ struct intel_crtc *crtc; -+ unsigned long cookie; -+ int err = 0; -+ -+ if (!HAS_DDI(dev_priv)) -+ return 0; -+ -+ cookie = i915_audio_component_get_power(kdev); -+ mutex_lock(&dev_priv->av_mutex); -+ -+ /* 1. get the pipe */ -+ encoder = get_saved_enc(dev_priv, port, pipe); -+ if (!encoder || !encoder->base.crtc) { -+ DRM_DEBUG_KMS("Not valid for port %c\n", port_name(port)); -+ err = -ENODEV; -+ goto unlock; -+ } -+ -+ crtc = to_intel_crtc(encoder->base.crtc); -+ -+ /* port must be valid now, otherwise the pipe will be invalid */ -+ acomp->aud_sample_rate[port] = rate; -+ -+ hsw_audio_config_update(encoder, crtc->config); -+ -+ unlock: -+ mutex_unlock(&dev_priv->av_mutex); -+ i915_audio_component_put_power(kdev, cookie); -+ return err; -+} -+ -+static int i915_audio_component_get_eld(struct device *kdev, int port, -+ int pipe, bool *enabled, -+ unsigned char *buf, int max_bytes) -+{ -+ struct drm_i915_private *dev_priv = kdev_to_i915(kdev); -+ struct intel_encoder *intel_encoder; -+ const u8 *eld; -+ int ret = -EINVAL; -+ -+ mutex_lock(&dev_priv->av_mutex); -+ -+ intel_encoder = get_saved_enc(dev_priv, port, pipe); -+ if (!intel_encoder) { -+ DRM_DEBUG_KMS("Not valid for port %c\n", port_name(port)); -+ mutex_unlock(&dev_priv->av_mutex); -+ return ret; -+ } -+ -+ ret = 0; -+ *enabled = intel_encoder->audio_connector != NULL; -+ if (*enabled) { -+ eld = intel_encoder->audio_connector->eld; -+ ret = drm_eld_size(eld); -+ memcpy(buf, eld, min(max_bytes, ret)); -+ } -+ -+ mutex_unlock(&dev_priv->av_mutex); -+ return ret; -+} -+ -+static const struct drm_audio_component_ops i915_audio_component_ops = { -+ .owner = THIS_MODULE, -+ .get_power = i915_audio_component_get_power, -+ .put_power = i915_audio_component_put_power, -+ .codec_wake_override = i915_audio_component_codec_wake_override, -+ .get_cdclk_freq = i915_audio_component_get_cdclk_freq, -+ .sync_audio_rate = i915_audio_component_sync_audio_rate, -+ .get_eld = i915_audio_component_get_eld, -+}; -+ -+static int i915_audio_component_bind(struct device *i915_kdev, -+ struct device *hda_kdev, void *data) -+{ -+ struct i915_audio_component *acomp = data; -+ struct drm_i915_private *dev_priv = kdev_to_i915(i915_kdev); -+ int i; -+ -+ if (WARN_ON(acomp->base.ops || acomp->base.dev)) -+ return -EEXIST; -+ -+ if (WARN_ON(!device_link_add(hda_kdev, i915_kdev, DL_FLAG_STATELESS))) -+ return -ENOMEM; -+ -+ drm_modeset_lock_all(&dev_priv->drm); -+ acomp->base.ops = &i915_audio_component_ops; -+ acomp->base.dev = i915_kdev; -+ BUILD_BUG_ON(MAX_PORTS != I915_MAX_PORTS); -+ for (i = 0; i < ARRAY_SIZE(acomp->aud_sample_rate); i++) -+ acomp->aud_sample_rate[i] = 0; -+ dev_priv->audio_component = acomp; -+ drm_modeset_unlock_all(&dev_priv->drm); -+ -+ return 0; -+} -+ -+static void i915_audio_component_unbind(struct device *i915_kdev, -+ struct device *hda_kdev, void *data) -+{ -+ struct i915_audio_component *acomp = data; -+ struct drm_i915_private *dev_priv = kdev_to_i915(i915_kdev); -+ -+ drm_modeset_lock_all(&dev_priv->drm); -+ acomp->base.ops = NULL; -+ acomp->base.dev = NULL; -+ dev_priv->audio_component = NULL; -+ drm_modeset_unlock_all(&dev_priv->drm); -+ -+ device_link_remove(hda_kdev, i915_kdev); -+} -+ -+static const struct component_ops i915_audio_component_bind_ops = { -+ .bind = i915_audio_component_bind, -+ .unbind = i915_audio_component_unbind, -+}; -+ -+/** -+ * i915_audio_component_init - initialize and register the audio component -+ * @dev_priv: i915 device instance -+ * -+ * This will register with the component framework a child component which -+ * will bind dynamically to the snd_hda_intel driver's corresponding master -+ * component when the latter is registered. During binding the child -+ * initializes an instance of struct i915_audio_component which it receives -+ * from the master. The master can then start to use the interface defined by -+ * this struct. Each side can break the binding at any point by deregistering -+ * its own component after which each side's component unbind callback is -+ * called. -+ * -+ * We ignore any error during registration and continue with reduced -+ * functionality (i.e. without HDMI audio). -+ */ -+static void i915_audio_component_init(struct drm_i915_private *dev_priv) -+{ -+ int ret; -+ -+ ret = component_add_typed(dev_priv->drm.dev, -+ &i915_audio_component_bind_ops, -+ I915_COMPONENT_AUDIO); -+ if (ret < 0) { -+ DRM_ERROR("failed to add audio component (%d)\n", ret); -+ /* continue with reduced functionality */ -+ return; -+ } -+ -+ dev_priv->audio_component_registered = true; -+} -+ -+/** -+ * i915_audio_component_cleanup - deregister the audio component -+ * @dev_priv: i915 device instance -+ * -+ * Deregisters the audio component, breaking any existing binding to the -+ * corresponding snd_hda_intel driver's master component. -+ */ -+static void i915_audio_component_cleanup(struct drm_i915_private *dev_priv) -+{ -+ if (!dev_priv->audio_component_registered) -+ return; -+ -+ component_del(dev_priv->drm.dev, &i915_audio_component_bind_ops); -+ dev_priv->audio_component_registered = false; -+} -+ -+/** -+ * intel_audio_init() - Initialize the audio driver either using -+ * component framework or using lpe audio bridge -+ * @dev_priv: the i915 drm device private data -+ * -+ */ -+void intel_audio_init(struct drm_i915_private *dev_priv) -+{ -+ if (intel_lpe_audio_init(dev_priv) < 0) -+ i915_audio_component_init(dev_priv); -+} -+ -+/** -+ * intel_audio_deinit() - deinitialize the audio driver -+ * @dev_priv: the i915 drm device private data -+ * -+ */ -+void intel_audio_deinit(struct drm_i915_private *dev_priv) -+{ -+ if ((dev_priv)->lpe_audio.platdev != NULL) -+ intel_lpe_audio_teardown(dev_priv); -+ else -+ i915_audio_component_cleanup(dev_priv); -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_audio.h b/drivers/gpu/drm/i915_legacy/intel_audio.h -new file mode 100644 -index 000000000000..a3657c7a7ba2 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_audio.h -@@ -0,0 +1,24 @@ -+/* SPDX-License-Identifier: MIT */ -+/* -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef __INTEL_AUDIO_H__ -+#define __INTEL_AUDIO_H__ -+ -+struct drm_connector_state; -+struct drm_i915_private; -+struct intel_crtc_state; -+struct intel_encoder; -+ -+void intel_init_audio_hooks(struct drm_i915_private *dev_priv); -+void intel_audio_codec_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state); -+void intel_audio_codec_disable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state); -+void intel_audio_init(struct drm_i915_private *dev_priv); -+void intel_audio_deinit(struct drm_i915_private *dev_priv); -+ -+#endif /* __INTEL_AUDIO_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_bios.c b/drivers/gpu/drm/i915_legacy/intel_bios.c -new file mode 100644 -index 000000000000..ee6fa75d65a2 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_bios.c -@@ -0,0 +1,2298 @@ -+/* -+ * Copyright © 2006 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eric Anholt -+ * -+ */ -+ -+#include -+#include -+#include "i915_drv.h" -+ -+#define _INTEL_BIOS_PRIVATE -+#include "intel_vbt_defs.h" -+ -+/** -+ * DOC: Video BIOS Table (VBT) -+ * -+ * The Video BIOS Table, or VBT, provides platform and board specific -+ * configuration information to the driver that is not discoverable or available -+ * through other means. The configuration is mostly related to display -+ * hardware. The VBT is available via the ACPI OpRegion or, on older systems, in -+ * the PCI ROM. -+ * -+ * The VBT consists of a VBT Header (defined as &struct vbt_header), a BDB -+ * Header (&struct bdb_header), and a number of BIOS Data Blocks (BDB) that -+ * contain the actual configuration information. The VBT Header, and thus the -+ * VBT, begins with "$VBT" signature. The VBT Header contains the offset of the -+ * BDB Header. The data blocks are concatenated after the BDB Header. The data -+ * blocks have a 1-byte Block ID, 2-byte Block Size, and Block Size bytes of -+ * data. (Block 53, the MIPI Sequence Block is an exception.) -+ * -+ * The driver parses the VBT during load. The relevant information is stored in -+ * driver private data for ease of use, and the actual VBT is not read after -+ * that. -+ */ -+ -+#define SLAVE_ADDR1 0x70 -+#define SLAVE_ADDR2 0x72 -+ -+/* Get BDB block size given a pointer to Block ID. */ -+static u32 _get_blocksize(const u8 *block_base) -+{ -+ /* The MIPI Sequence Block v3+ has a separate size field. */ -+ if (*block_base == BDB_MIPI_SEQUENCE && *(block_base + 3) >= 3) -+ return *((const u32 *)(block_base + 4)); -+ else -+ return *((const u16 *)(block_base + 1)); -+} -+ -+/* Get BDB block size give a pointer to data after Block ID and Block Size. */ -+static u32 get_blocksize(const void *block_data) -+{ -+ return _get_blocksize(block_data - 3); -+} -+ -+static const void * -+find_section(const void *_bdb, int section_id) -+{ -+ const struct bdb_header *bdb = _bdb; -+ const u8 *base = _bdb; -+ int index = 0; -+ u32 total, current_size; -+ u8 current_id; -+ -+ /* skip to first section */ -+ index += bdb->header_size; -+ total = bdb->bdb_size; -+ -+ /* walk the sections looking for section_id */ -+ while (index + 3 < total) { -+ current_id = *(base + index); -+ current_size = _get_blocksize(base + index); -+ index += 3; -+ -+ if (index + current_size > total) -+ return NULL; -+ -+ if (current_id == section_id) -+ return base + index; -+ -+ index += current_size; -+ } -+ -+ return NULL; -+} -+ -+static void -+fill_detail_timing_data(struct drm_display_mode *panel_fixed_mode, -+ const struct lvds_dvo_timing *dvo_timing) -+{ -+ panel_fixed_mode->hdisplay = (dvo_timing->hactive_hi << 8) | -+ dvo_timing->hactive_lo; -+ panel_fixed_mode->hsync_start = panel_fixed_mode->hdisplay + -+ ((dvo_timing->hsync_off_hi << 8) | dvo_timing->hsync_off_lo); -+ panel_fixed_mode->hsync_end = panel_fixed_mode->hsync_start + -+ ((dvo_timing->hsync_pulse_width_hi << 8) | -+ dvo_timing->hsync_pulse_width_lo); -+ panel_fixed_mode->htotal = panel_fixed_mode->hdisplay + -+ ((dvo_timing->hblank_hi << 8) | dvo_timing->hblank_lo); -+ -+ panel_fixed_mode->vdisplay = (dvo_timing->vactive_hi << 8) | -+ dvo_timing->vactive_lo; -+ panel_fixed_mode->vsync_start = panel_fixed_mode->vdisplay + -+ ((dvo_timing->vsync_off_hi << 4) | dvo_timing->vsync_off_lo); -+ panel_fixed_mode->vsync_end = panel_fixed_mode->vsync_start + -+ ((dvo_timing->vsync_pulse_width_hi << 4) | -+ dvo_timing->vsync_pulse_width_lo); -+ panel_fixed_mode->vtotal = panel_fixed_mode->vdisplay + -+ ((dvo_timing->vblank_hi << 8) | dvo_timing->vblank_lo); -+ panel_fixed_mode->clock = dvo_timing->clock * 10; -+ panel_fixed_mode->type = DRM_MODE_TYPE_PREFERRED; -+ -+ if (dvo_timing->hsync_positive) -+ panel_fixed_mode->flags |= DRM_MODE_FLAG_PHSYNC; -+ else -+ panel_fixed_mode->flags |= DRM_MODE_FLAG_NHSYNC; -+ -+ if (dvo_timing->vsync_positive) -+ panel_fixed_mode->flags |= DRM_MODE_FLAG_PVSYNC; -+ else -+ panel_fixed_mode->flags |= DRM_MODE_FLAG_NVSYNC; -+ -+ panel_fixed_mode->width_mm = (dvo_timing->himage_hi << 8) | -+ dvo_timing->himage_lo; -+ panel_fixed_mode->height_mm = (dvo_timing->vimage_hi << 8) | -+ dvo_timing->vimage_lo; -+ -+ /* Some VBTs have bogus h/vtotal values */ -+ if (panel_fixed_mode->hsync_end > panel_fixed_mode->htotal) -+ panel_fixed_mode->htotal = panel_fixed_mode->hsync_end + 1; -+ if (panel_fixed_mode->vsync_end > panel_fixed_mode->vtotal) -+ panel_fixed_mode->vtotal = panel_fixed_mode->vsync_end + 1; -+ -+ drm_mode_set_name(panel_fixed_mode); -+} -+ -+static const struct lvds_dvo_timing * -+get_lvds_dvo_timing(const struct bdb_lvds_lfp_data *lvds_lfp_data, -+ const struct bdb_lvds_lfp_data_ptrs *lvds_lfp_data_ptrs, -+ int index) -+{ -+ /* -+ * the size of fp_timing varies on the different platform. -+ * So calculate the DVO timing relative offset in LVDS data -+ * entry to get the DVO timing entry -+ */ -+ -+ int lfp_data_size = -+ lvds_lfp_data_ptrs->ptr[1].dvo_timing_offset - -+ lvds_lfp_data_ptrs->ptr[0].dvo_timing_offset; -+ int dvo_timing_offset = -+ lvds_lfp_data_ptrs->ptr[0].dvo_timing_offset - -+ lvds_lfp_data_ptrs->ptr[0].fp_timing_offset; -+ char *entry = (char *)lvds_lfp_data->data + lfp_data_size * index; -+ -+ return (struct lvds_dvo_timing *)(entry + dvo_timing_offset); -+} -+ -+/* get lvds_fp_timing entry -+ * this function may return NULL if the corresponding entry is invalid -+ */ -+static const struct lvds_fp_timing * -+get_lvds_fp_timing(const struct bdb_header *bdb, -+ const struct bdb_lvds_lfp_data *data, -+ const struct bdb_lvds_lfp_data_ptrs *ptrs, -+ int index) -+{ -+ size_t data_ofs = (const u8 *)data - (const u8 *)bdb; -+ u16 data_size = ((const u16 *)data)[-1]; /* stored in header */ -+ size_t ofs; -+ -+ if (index >= ARRAY_SIZE(ptrs->ptr)) -+ return NULL; -+ ofs = ptrs->ptr[index].fp_timing_offset; -+ if (ofs < data_ofs || -+ ofs + sizeof(struct lvds_fp_timing) > data_ofs + data_size) -+ return NULL; -+ return (const struct lvds_fp_timing *)((const u8 *)bdb + ofs); -+} -+ -+/* Try to find integrated panel data */ -+static void -+parse_lfp_panel_data(struct drm_i915_private *dev_priv, -+ const struct bdb_header *bdb) -+{ -+ const struct bdb_lvds_options *lvds_options; -+ const struct bdb_lvds_lfp_data *lvds_lfp_data; -+ const struct bdb_lvds_lfp_data_ptrs *lvds_lfp_data_ptrs; -+ const struct lvds_dvo_timing *panel_dvo_timing; -+ const struct lvds_fp_timing *fp_timing; -+ struct drm_display_mode *panel_fixed_mode; -+ int panel_type; -+ int drrs_mode; -+ int ret; -+ -+ lvds_options = find_section(bdb, BDB_LVDS_OPTIONS); -+ if (!lvds_options) -+ return; -+ -+ dev_priv->vbt.lvds_dither = lvds_options->pixel_dither; -+ -+ ret = intel_opregion_get_panel_type(dev_priv); -+ if (ret >= 0) { -+ WARN_ON(ret > 0xf); -+ panel_type = ret; -+ DRM_DEBUG_KMS("Panel type: %d (OpRegion)\n", panel_type); -+ } else { -+ if (lvds_options->panel_type > 0xf) { -+ DRM_DEBUG_KMS("Invalid VBT panel type 0x%x\n", -+ lvds_options->panel_type); -+ return; -+ } -+ panel_type = lvds_options->panel_type; -+ DRM_DEBUG_KMS("Panel type: %d (VBT)\n", panel_type); -+ } -+ -+ dev_priv->vbt.panel_type = panel_type; -+ -+ drrs_mode = (lvds_options->dps_panel_type_bits -+ >> (panel_type * 2)) & MODE_MASK; -+ /* -+ * VBT has static DRRS = 0 and seamless DRRS = 2. -+ * The below piece of code is required to adjust vbt.drrs_type -+ * to match the enum drrs_support_type. -+ */ -+ switch (drrs_mode) { -+ case 0: -+ dev_priv->vbt.drrs_type = STATIC_DRRS_SUPPORT; -+ DRM_DEBUG_KMS("DRRS supported mode is static\n"); -+ break; -+ case 2: -+ dev_priv->vbt.drrs_type = SEAMLESS_DRRS_SUPPORT; -+ DRM_DEBUG_KMS("DRRS supported mode is seamless\n"); -+ break; -+ default: -+ dev_priv->vbt.drrs_type = DRRS_NOT_SUPPORTED; -+ DRM_DEBUG_KMS("DRRS not supported (VBT input)\n"); -+ break; -+ } -+ -+ lvds_lfp_data = find_section(bdb, BDB_LVDS_LFP_DATA); -+ if (!lvds_lfp_data) -+ return; -+ -+ lvds_lfp_data_ptrs = find_section(bdb, BDB_LVDS_LFP_DATA_PTRS); -+ if (!lvds_lfp_data_ptrs) -+ return; -+ -+ panel_dvo_timing = get_lvds_dvo_timing(lvds_lfp_data, -+ lvds_lfp_data_ptrs, -+ panel_type); -+ -+ panel_fixed_mode = kzalloc(sizeof(*panel_fixed_mode), GFP_KERNEL); -+ if (!panel_fixed_mode) -+ return; -+ -+ fill_detail_timing_data(panel_fixed_mode, panel_dvo_timing); -+ -+ dev_priv->vbt.lfp_lvds_vbt_mode = panel_fixed_mode; -+ -+ DRM_DEBUG_KMS("Found panel mode in BIOS VBT tables:\n"); -+ drm_mode_debug_printmodeline(panel_fixed_mode); -+ -+ fp_timing = get_lvds_fp_timing(bdb, lvds_lfp_data, -+ lvds_lfp_data_ptrs, -+ panel_type); -+ if (fp_timing) { -+ /* check the resolution, just to be sure */ -+ if (fp_timing->x_res == panel_fixed_mode->hdisplay && -+ fp_timing->y_res == panel_fixed_mode->vdisplay) { -+ dev_priv->vbt.bios_lvds_val = fp_timing->lvds_reg_val; -+ DRM_DEBUG_KMS("VBT initial LVDS value %x\n", -+ dev_priv->vbt.bios_lvds_val); -+ } -+ } -+} -+ -+static void -+parse_lfp_backlight(struct drm_i915_private *dev_priv, -+ const struct bdb_header *bdb) -+{ -+ const struct bdb_lfp_backlight_data *backlight_data; -+ const struct bdb_lfp_backlight_data_entry *entry; -+ int panel_type = dev_priv->vbt.panel_type; -+ -+ backlight_data = find_section(bdb, BDB_LVDS_BACKLIGHT); -+ if (!backlight_data) -+ return; -+ -+ if (backlight_data->entry_size != sizeof(backlight_data->data[0])) { -+ DRM_DEBUG_KMS("Unsupported backlight data entry size %u\n", -+ backlight_data->entry_size); -+ return; -+ } -+ -+ entry = &backlight_data->data[panel_type]; -+ -+ dev_priv->vbt.backlight.present = entry->type == BDB_BACKLIGHT_TYPE_PWM; -+ if (!dev_priv->vbt.backlight.present) { -+ DRM_DEBUG_KMS("PWM backlight not present in VBT (type %u)\n", -+ entry->type); -+ return; -+ } -+ -+ dev_priv->vbt.backlight.type = INTEL_BACKLIGHT_DISPLAY_DDI; -+ if (bdb->version >= 191 && -+ get_blocksize(backlight_data) >= sizeof(*backlight_data)) { -+ const struct bdb_lfp_backlight_control_method *method; -+ -+ method = &backlight_data->backlight_control[panel_type]; -+ dev_priv->vbt.backlight.type = method->type; -+ dev_priv->vbt.backlight.controller = method->controller; -+ } -+ -+ dev_priv->vbt.backlight.pwm_freq_hz = entry->pwm_freq_hz; -+ dev_priv->vbt.backlight.active_low_pwm = entry->active_low_pwm; -+ dev_priv->vbt.backlight.min_brightness = entry->min_brightness; -+ DRM_DEBUG_KMS("VBT backlight PWM modulation frequency %u Hz, " -+ "active %s, min brightness %u, level %u, controller %u\n", -+ dev_priv->vbt.backlight.pwm_freq_hz, -+ dev_priv->vbt.backlight.active_low_pwm ? "low" : "high", -+ dev_priv->vbt.backlight.min_brightness, -+ backlight_data->level[panel_type], -+ dev_priv->vbt.backlight.controller); -+} -+ -+/* Try to find sdvo panel data */ -+static void -+parse_sdvo_panel_data(struct drm_i915_private *dev_priv, -+ const struct bdb_header *bdb) -+{ -+ const struct lvds_dvo_timing *dvo_timing; -+ struct drm_display_mode *panel_fixed_mode; -+ int index; -+ -+ index = i915_modparams.vbt_sdvo_panel_type; -+ if (index == -2) { -+ DRM_DEBUG_KMS("Ignore SDVO panel mode from BIOS VBT tables.\n"); -+ return; -+ } -+ -+ if (index == -1) { -+ const struct bdb_sdvo_lvds_options *sdvo_lvds_options; -+ -+ sdvo_lvds_options = find_section(bdb, BDB_SDVO_LVDS_OPTIONS); -+ if (!sdvo_lvds_options) -+ return; -+ -+ index = sdvo_lvds_options->panel_type; -+ } -+ -+ dvo_timing = find_section(bdb, BDB_SDVO_PANEL_DTDS); -+ if (!dvo_timing) -+ return; -+ -+ panel_fixed_mode = kzalloc(sizeof(*panel_fixed_mode), GFP_KERNEL); -+ if (!panel_fixed_mode) -+ return; -+ -+ fill_detail_timing_data(panel_fixed_mode, dvo_timing + index); -+ -+ dev_priv->vbt.sdvo_lvds_vbt_mode = panel_fixed_mode; -+ -+ DRM_DEBUG_KMS("Found SDVO panel mode in BIOS VBT tables:\n"); -+ drm_mode_debug_printmodeline(panel_fixed_mode); -+} -+ -+static int intel_bios_ssc_frequency(struct drm_i915_private *dev_priv, -+ bool alternate) -+{ -+ switch (INTEL_GEN(dev_priv)) { -+ case 2: -+ return alternate ? 66667 : 48000; -+ case 3: -+ case 4: -+ return alternate ? 100000 : 96000; -+ default: -+ return alternate ? 100000 : 120000; -+ } -+} -+ -+static void -+parse_general_features(struct drm_i915_private *dev_priv, -+ const struct bdb_header *bdb) -+{ -+ const struct bdb_general_features *general; -+ -+ general = find_section(bdb, BDB_GENERAL_FEATURES); -+ if (!general) -+ return; -+ -+ dev_priv->vbt.int_tv_support = general->int_tv_support; -+ /* int_crt_support can't be trusted on earlier platforms */ -+ if (bdb->version >= 155 && -+ (HAS_DDI(dev_priv) || IS_VALLEYVIEW(dev_priv))) -+ dev_priv->vbt.int_crt_support = general->int_crt_support; -+ dev_priv->vbt.lvds_use_ssc = general->enable_ssc; -+ dev_priv->vbt.lvds_ssc_freq = -+ intel_bios_ssc_frequency(dev_priv, general->ssc_freq); -+ dev_priv->vbt.display_clock_mode = general->display_clock_mode; -+ dev_priv->vbt.fdi_rx_polarity_inverted = general->fdi_rx_polarity_inverted; -+ if (bdb->version >= 181) { -+ dev_priv->vbt.orientation = general->rotate_180 ? -+ DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP : -+ DRM_MODE_PANEL_ORIENTATION_NORMAL; -+ } else { -+ dev_priv->vbt.orientation = DRM_MODE_PANEL_ORIENTATION_UNKNOWN; -+ } -+ DRM_DEBUG_KMS("BDB_GENERAL_FEATURES int_tv_support %d int_crt_support %d lvds_use_ssc %d lvds_ssc_freq %d display_clock_mode %d fdi_rx_polarity_inverted %d\n", -+ dev_priv->vbt.int_tv_support, -+ dev_priv->vbt.int_crt_support, -+ dev_priv->vbt.lvds_use_ssc, -+ dev_priv->vbt.lvds_ssc_freq, -+ dev_priv->vbt.display_clock_mode, -+ dev_priv->vbt.fdi_rx_polarity_inverted); -+} -+ -+static const struct child_device_config * -+child_device_ptr(const struct bdb_general_definitions *defs, int i) -+{ -+ return (const void *) &defs->devices[i * defs->child_dev_size]; -+} -+ -+static void -+parse_sdvo_device_mapping(struct drm_i915_private *dev_priv, u8 bdb_version) -+{ -+ struct sdvo_device_mapping *mapping; -+ const struct child_device_config *child; -+ int i, count = 0; -+ -+ /* -+ * Only parse SDVO mappings on gens that could have SDVO. This isn't -+ * accurate and doesn't have to be, as long as it's not too strict. -+ */ -+ if (!IS_GEN_RANGE(dev_priv, 3, 7)) { -+ DRM_DEBUG_KMS("Skipping SDVO device mapping\n"); -+ return; -+ } -+ -+ for (i = 0, count = 0; i < dev_priv->vbt.child_dev_num; i++) { -+ child = dev_priv->vbt.child_dev + i; -+ -+ if (child->slave_addr != SLAVE_ADDR1 && -+ child->slave_addr != SLAVE_ADDR2) { -+ /* -+ * If the slave address is neither 0x70 nor 0x72, -+ * it is not a SDVO device. Skip it. -+ */ -+ continue; -+ } -+ if (child->dvo_port != DEVICE_PORT_DVOB && -+ child->dvo_port != DEVICE_PORT_DVOC) { -+ /* skip the incorrect SDVO port */ -+ DRM_DEBUG_KMS("Incorrect SDVO port. Skip it\n"); -+ continue; -+ } -+ DRM_DEBUG_KMS("the SDVO device with slave addr %2x is found on" -+ " %s port\n", -+ child->slave_addr, -+ (child->dvo_port == DEVICE_PORT_DVOB) ? -+ "SDVOB" : "SDVOC"); -+ mapping = &dev_priv->vbt.sdvo_mappings[child->dvo_port - 1]; -+ if (!mapping->initialized) { -+ mapping->dvo_port = child->dvo_port; -+ mapping->slave_addr = child->slave_addr; -+ mapping->dvo_wiring = child->dvo_wiring; -+ mapping->ddc_pin = child->ddc_pin; -+ mapping->i2c_pin = child->i2c_pin; -+ mapping->initialized = 1; -+ DRM_DEBUG_KMS("SDVO device: dvo=%x, addr=%x, wiring=%d, ddc_pin=%d, i2c_pin=%d\n", -+ mapping->dvo_port, -+ mapping->slave_addr, -+ mapping->dvo_wiring, -+ mapping->ddc_pin, -+ mapping->i2c_pin); -+ } else { -+ DRM_DEBUG_KMS("Maybe one SDVO port is shared by " -+ "two SDVO device.\n"); -+ } -+ if (child->slave2_addr) { -+ /* Maybe this is a SDVO device with multiple inputs */ -+ /* And the mapping info is not added */ -+ DRM_DEBUG_KMS("there exists the slave2_addr. Maybe this" -+ " is a SDVO device with multiple inputs.\n"); -+ } -+ count++; -+ } -+ -+ if (!count) { -+ /* No SDVO device info is found */ -+ DRM_DEBUG_KMS("No SDVO device info is found in VBT\n"); -+ } -+} -+ -+static void -+parse_driver_features(struct drm_i915_private *dev_priv, -+ const struct bdb_header *bdb) -+{ -+ const struct bdb_driver_features *driver; -+ -+ driver = find_section(bdb, BDB_DRIVER_FEATURES); -+ if (!driver) -+ return; -+ -+ if (INTEL_GEN(dev_priv) >= 5) { -+ /* -+ * Note that we consider BDB_DRIVER_FEATURE_INT_SDVO_LVDS -+ * to mean "eDP". The VBT spec doesn't agree with that -+ * interpretation, but real world VBTs seem to. -+ */ -+ if (driver->lvds_config != BDB_DRIVER_FEATURE_INT_LVDS) -+ dev_priv->vbt.int_lvds_support = 0; -+ } else { -+ /* -+ * FIXME it's not clear which BDB version has the LVDS config -+ * bits defined. Revision history in the VBT spec says: -+ * "0.92 | Add two definitions for VBT value of LVDS Active -+ * Config (00b and 11b values defined) | 06/13/2005" -+ * but does not the specify the BDB version. -+ * -+ * So far version 134 (on i945gm) is the oldest VBT observed -+ * in the wild with the bits correctly populated. Version -+ * 108 (on i85x) does not have the bits correctly populated. -+ */ -+ if (bdb->version >= 134 && -+ driver->lvds_config != BDB_DRIVER_FEATURE_INT_LVDS && -+ driver->lvds_config != BDB_DRIVER_FEATURE_INT_SDVO_LVDS) -+ dev_priv->vbt.int_lvds_support = 0; -+ } -+ -+ DRM_DEBUG_KMS("DRRS State Enabled:%d\n", driver->drrs_enabled); -+ /* -+ * If DRRS is not supported, drrs_type has to be set to 0. -+ * This is because, VBT is configured in such a way that -+ * static DRRS is 0 and DRRS not supported is represented by -+ * driver->drrs_enabled=false -+ */ -+ if (!driver->drrs_enabled) -+ dev_priv->vbt.drrs_type = DRRS_NOT_SUPPORTED; -+ dev_priv->vbt.psr.enable = driver->psr_enabled; -+} -+ -+static void -+parse_edp(struct drm_i915_private *dev_priv, const struct bdb_header *bdb) -+{ -+ const struct bdb_edp *edp; -+ const struct edp_power_seq *edp_pps; -+ const struct edp_fast_link_params *edp_link_params; -+ int panel_type = dev_priv->vbt.panel_type; -+ -+ edp = find_section(bdb, BDB_EDP); -+ if (!edp) -+ return; -+ -+ switch ((edp->color_depth >> (panel_type * 2)) & 3) { -+ case EDP_18BPP: -+ dev_priv->vbt.edp.bpp = 18; -+ break; -+ case EDP_24BPP: -+ dev_priv->vbt.edp.bpp = 24; -+ break; -+ case EDP_30BPP: -+ dev_priv->vbt.edp.bpp = 30; -+ break; -+ } -+ -+ /* Get the eDP sequencing and link info */ -+ edp_pps = &edp->power_seqs[panel_type]; -+ edp_link_params = &edp->fast_link_params[panel_type]; -+ -+ dev_priv->vbt.edp.pps = *edp_pps; -+ -+ switch (edp_link_params->rate) { -+ case EDP_RATE_1_62: -+ dev_priv->vbt.edp.rate = DP_LINK_BW_1_62; -+ break; -+ case EDP_RATE_2_7: -+ dev_priv->vbt.edp.rate = DP_LINK_BW_2_7; -+ break; -+ default: -+ DRM_DEBUG_KMS("VBT has unknown eDP link rate value %u\n", -+ edp_link_params->rate); -+ break; -+ } -+ -+ switch (edp_link_params->lanes) { -+ case EDP_LANE_1: -+ dev_priv->vbt.edp.lanes = 1; -+ break; -+ case EDP_LANE_2: -+ dev_priv->vbt.edp.lanes = 2; -+ break; -+ case EDP_LANE_4: -+ dev_priv->vbt.edp.lanes = 4; -+ break; -+ default: -+ DRM_DEBUG_KMS("VBT has unknown eDP lane count value %u\n", -+ edp_link_params->lanes); -+ break; -+ } -+ -+ switch (edp_link_params->preemphasis) { -+ case EDP_PREEMPHASIS_NONE: -+ dev_priv->vbt.edp.preemphasis = DP_TRAIN_PRE_EMPH_LEVEL_0; -+ break; -+ case EDP_PREEMPHASIS_3_5dB: -+ dev_priv->vbt.edp.preemphasis = DP_TRAIN_PRE_EMPH_LEVEL_1; -+ break; -+ case EDP_PREEMPHASIS_6dB: -+ dev_priv->vbt.edp.preemphasis = DP_TRAIN_PRE_EMPH_LEVEL_2; -+ break; -+ case EDP_PREEMPHASIS_9_5dB: -+ dev_priv->vbt.edp.preemphasis = DP_TRAIN_PRE_EMPH_LEVEL_3; -+ break; -+ default: -+ DRM_DEBUG_KMS("VBT has unknown eDP pre-emphasis value %u\n", -+ edp_link_params->preemphasis); -+ break; -+ } -+ -+ switch (edp_link_params->vswing) { -+ case EDP_VSWING_0_4V: -+ dev_priv->vbt.edp.vswing = DP_TRAIN_VOLTAGE_SWING_LEVEL_0; -+ break; -+ case EDP_VSWING_0_6V: -+ dev_priv->vbt.edp.vswing = DP_TRAIN_VOLTAGE_SWING_LEVEL_1; -+ break; -+ case EDP_VSWING_0_8V: -+ dev_priv->vbt.edp.vswing = DP_TRAIN_VOLTAGE_SWING_LEVEL_2; -+ break; -+ case EDP_VSWING_1_2V: -+ dev_priv->vbt.edp.vswing = DP_TRAIN_VOLTAGE_SWING_LEVEL_3; -+ break; -+ default: -+ DRM_DEBUG_KMS("VBT has unknown eDP voltage swing value %u\n", -+ edp_link_params->vswing); -+ break; -+ } -+ -+ if (bdb->version >= 173) { -+ u8 vswing; -+ -+ /* Don't read from VBT if module parameter has valid value*/ -+ if (i915_modparams.edp_vswing) { -+ dev_priv->vbt.edp.low_vswing = -+ i915_modparams.edp_vswing == 1; -+ } else { -+ vswing = (edp->edp_vswing_preemph >> (panel_type * 4)) & 0xF; -+ dev_priv->vbt.edp.low_vswing = vswing == 0; -+ } -+ } -+} -+ -+static void -+parse_psr(struct drm_i915_private *dev_priv, const struct bdb_header *bdb) -+{ -+ const struct bdb_psr *psr; -+ const struct psr_table *psr_table; -+ int panel_type = dev_priv->vbt.panel_type; -+ -+ psr = find_section(bdb, BDB_PSR); -+ if (!psr) { -+ DRM_DEBUG_KMS("No PSR BDB found.\n"); -+ return; -+ } -+ -+ psr_table = &psr->psr_table[panel_type]; -+ -+ dev_priv->vbt.psr.full_link = psr_table->full_link; -+ dev_priv->vbt.psr.require_aux_wakeup = psr_table->require_aux_to_wakeup; -+ -+ /* Allowed VBT values goes from 0 to 15 */ -+ dev_priv->vbt.psr.idle_frames = psr_table->idle_frames < 0 ? 0 : -+ psr_table->idle_frames > 15 ? 15 : psr_table->idle_frames; -+ -+ switch (psr_table->lines_to_wait) { -+ case 0: -+ dev_priv->vbt.psr.lines_to_wait = PSR_0_LINES_TO_WAIT; -+ break; -+ case 1: -+ dev_priv->vbt.psr.lines_to_wait = PSR_1_LINE_TO_WAIT; -+ break; -+ case 2: -+ dev_priv->vbt.psr.lines_to_wait = PSR_4_LINES_TO_WAIT; -+ break; -+ case 3: -+ dev_priv->vbt.psr.lines_to_wait = PSR_8_LINES_TO_WAIT; -+ break; -+ default: -+ DRM_DEBUG_KMS("VBT has unknown PSR lines to wait %u\n", -+ psr_table->lines_to_wait); -+ break; -+ } -+ -+ /* -+ * New psr options 0=500us, 1=100us, 2=2500us, 3=0us -+ * Old decimal value is wake up time in multiples of 100 us. -+ */ -+ if (bdb->version >= 205 && -+ (IS_GEN9_BC(dev_priv) || IS_GEMINILAKE(dev_priv) || -+ INTEL_GEN(dev_priv) >= 10)) { -+ switch (psr_table->tp1_wakeup_time) { -+ case 0: -+ dev_priv->vbt.psr.tp1_wakeup_time_us = 500; -+ break; -+ case 1: -+ dev_priv->vbt.psr.tp1_wakeup_time_us = 100; -+ break; -+ case 3: -+ dev_priv->vbt.psr.tp1_wakeup_time_us = 0; -+ break; -+ default: -+ DRM_DEBUG_KMS("VBT tp1 wakeup time value %d is outside range[0-3], defaulting to max value 2500us\n", -+ psr_table->tp1_wakeup_time); -+ /* fallthrough */ -+ case 2: -+ dev_priv->vbt.psr.tp1_wakeup_time_us = 2500; -+ break; -+ } -+ -+ switch (psr_table->tp2_tp3_wakeup_time) { -+ case 0: -+ dev_priv->vbt.psr.tp2_tp3_wakeup_time_us = 500; -+ break; -+ case 1: -+ dev_priv->vbt.psr.tp2_tp3_wakeup_time_us = 100; -+ break; -+ case 3: -+ dev_priv->vbt.psr.tp2_tp3_wakeup_time_us = 0; -+ break; -+ default: -+ DRM_DEBUG_KMS("VBT tp2_tp3 wakeup time value %d is outside range[0-3], defaulting to max value 2500us\n", -+ psr_table->tp2_tp3_wakeup_time); -+ /* fallthrough */ -+ case 2: -+ dev_priv->vbt.psr.tp2_tp3_wakeup_time_us = 2500; -+ break; -+ } -+ } else { -+ dev_priv->vbt.psr.tp1_wakeup_time_us = psr_table->tp1_wakeup_time * 100; -+ dev_priv->vbt.psr.tp2_tp3_wakeup_time_us = psr_table->tp2_tp3_wakeup_time * 100; -+ } -+ -+ if (bdb->version >= 226) { -+ u32 wakeup_time = psr->psr2_tp2_tp3_wakeup_time; -+ -+ wakeup_time = (wakeup_time >> (2 * panel_type)) & 0x3; -+ switch (wakeup_time) { -+ case 0: -+ wakeup_time = 500; -+ break; -+ case 1: -+ wakeup_time = 100; -+ break; -+ case 3: -+ wakeup_time = 50; -+ break; -+ default: -+ case 2: -+ wakeup_time = 2500; -+ break; -+ } -+ dev_priv->vbt.psr.psr2_tp2_tp3_wakeup_time_us = wakeup_time; -+ } else { -+ /* Reusing PSR1 wakeup time for PSR2 in older VBTs */ -+ dev_priv->vbt.psr.psr2_tp2_tp3_wakeup_time_us = dev_priv->vbt.psr.tp2_tp3_wakeup_time_us; -+ } -+} -+ -+static void parse_dsi_backlight_ports(struct drm_i915_private *dev_priv, -+ u16 version, enum port port) -+{ -+ if (!dev_priv->vbt.dsi.config->dual_link || version < 197) { -+ dev_priv->vbt.dsi.bl_ports = BIT(port); -+ if (dev_priv->vbt.dsi.config->cabc_supported) -+ dev_priv->vbt.dsi.cabc_ports = BIT(port); -+ -+ return; -+ } -+ -+ switch (dev_priv->vbt.dsi.config->dl_dcs_backlight_ports) { -+ case DL_DCS_PORT_A: -+ dev_priv->vbt.dsi.bl_ports = BIT(PORT_A); -+ break; -+ case DL_DCS_PORT_C: -+ dev_priv->vbt.dsi.bl_ports = BIT(PORT_C); -+ break; -+ default: -+ case DL_DCS_PORT_A_AND_C: -+ dev_priv->vbt.dsi.bl_ports = BIT(PORT_A) | BIT(PORT_C); -+ break; -+ } -+ -+ if (!dev_priv->vbt.dsi.config->cabc_supported) -+ return; -+ -+ switch (dev_priv->vbt.dsi.config->dl_dcs_cabc_ports) { -+ case DL_DCS_PORT_A: -+ dev_priv->vbt.dsi.cabc_ports = BIT(PORT_A); -+ break; -+ case DL_DCS_PORT_C: -+ dev_priv->vbt.dsi.cabc_ports = BIT(PORT_C); -+ break; -+ default: -+ case DL_DCS_PORT_A_AND_C: -+ dev_priv->vbt.dsi.cabc_ports = -+ BIT(PORT_A) | BIT(PORT_C); -+ break; -+ } -+} -+ -+static void -+parse_mipi_config(struct drm_i915_private *dev_priv, -+ const struct bdb_header *bdb) -+{ -+ const struct bdb_mipi_config *start; -+ const struct mipi_config *config; -+ const struct mipi_pps_data *pps; -+ int panel_type = dev_priv->vbt.panel_type; -+ enum port port; -+ -+ /* parse MIPI blocks only if LFP type is MIPI */ -+ if (!intel_bios_is_dsi_present(dev_priv, &port)) -+ return; -+ -+ /* Initialize this to undefined indicating no generic MIPI support */ -+ dev_priv->vbt.dsi.panel_id = MIPI_DSI_UNDEFINED_PANEL_ID; -+ -+ /* Block #40 is already parsed and panel_fixed_mode is -+ * stored in dev_priv->lfp_lvds_vbt_mode -+ * resuse this when needed -+ */ -+ -+ /* Parse #52 for panel index used from panel_type already -+ * parsed -+ */ -+ start = find_section(bdb, BDB_MIPI_CONFIG); -+ if (!start) { -+ DRM_DEBUG_KMS("No MIPI config BDB found"); -+ return; -+ } -+ -+ DRM_DEBUG_DRIVER("Found MIPI Config block, panel index = %d\n", -+ panel_type); -+ -+ /* -+ * get hold of the correct configuration block and pps data as per -+ * the panel_type as index -+ */ -+ config = &start->config[panel_type]; -+ pps = &start->pps[panel_type]; -+ -+ /* store as of now full data. Trim when we realise all is not needed */ -+ dev_priv->vbt.dsi.config = kmemdup(config, sizeof(struct mipi_config), GFP_KERNEL); -+ if (!dev_priv->vbt.dsi.config) -+ return; -+ -+ dev_priv->vbt.dsi.pps = kmemdup(pps, sizeof(struct mipi_pps_data), GFP_KERNEL); -+ if (!dev_priv->vbt.dsi.pps) { -+ kfree(dev_priv->vbt.dsi.config); -+ return; -+ } -+ -+ parse_dsi_backlight_ports(dev_priv, bdb->version, port); -+ -+ /* FIXME is the 90 vs. 270 correct? */ -+ switch (config->rotation) { -+ case ENABLE_ROTATION_0: -+ /* -+ * Most (all?) VBTs claim 0 degrees despite having -+ * an upside down panel, thus we do not trust this. -+ */ -+ dev_priv->vbt.dsi.orientation = -+ DRM_MODE_PANEL_ORIENTATION_UNKNOWN; -+ break; -+ case ENABLE_ROTATION_90: -+ dev_priv->vbt.dsi.orientation = -+ DRM_MODE_PANEL_ORIENTATION_RIGHT_UP; -+ break; -+ case ENABLE_ROTATION_180: -+ dev_priv->vbt.dsi.orientation = -+ DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP; -+ break; -+ case ENABLE_ROTATION_270: -+ dev_priv->vbt.dsi.orientation = -+ DRM_MODE_PANEL_ORIENTATION_LEFT_UP; -+ break; -+ } -+ -+ /* We have mandatory mipi config blocks. Initialize as generic panel */ -+ dev_priv->vbt.dsi.panel_id = MIPI_DSI_GENERIC_PANEL_ID; -+} -+ -+/* Find the sequence block and size for the given panel. */ -+static const u8 * -+find_panel_sequence_block(const struct bdb_mipi_sequence *sequence, -+ u16 panel_id, u32 *seq_size) -+{ -+ u32 total = get_blocksize(sequence); -+ const u8 *data = &sequence->data[0]; -+ u8 current_id; -+ u32 current_size; -+ int header_size = sequence->version >= 3 ? 5 : 3; -+ int index = 0; -+ int i; -+ -+ /* skip new block size */ -+ if (sequence->version >= 3) -+ data += 4; -+ -+ for (i = 0; i < MAX_MIPI_CONFIGURATIONS && index < total; i++) { -+ if (index + header_size > total) { -+ DRM_ERROR("Invalid sequence block (header)\n"); -+ return NULL; -+ } -+ -+ current_id = *(data + index); -+ if (sequence->version >= 3) -+ current_size = *((const u32 *)(data + index + 1)); -+ else -+ current_size = *((const u16 *)(data + index + 1)); -+ -+ index += header_size; -+ -+ if (index + current_size > total) { -+ DRM_ERROR("Invalid sequence block\n"); -+ return NULL; -+ } -+ -+ if (current_id == panel_id) { -+ *seq_size = current_size; -+ return data + index; -+ } -+ -+ index += current_size; -+ } -+ -+ DRM_ERROR("Sequence block detected but no valid configuration\n"); -+ -+ return NULL; -+} -+ -+static int goto_next_sequence(const u8 *data, int index, int total) -+{ -+ u16 len; -+ -+ /* Skip Sequence Byte. */ -+ for (index = index + 1; index < total; index += len) { -+ u8 operation_byte = *(data + index); -+ index++; -+ -+ switch (operation_byte) { -+ case MIPI_SEQ_ELEM_END: -+ return index; -+ case MIPI_SEQ_ELEM_SEND_PKT: -+ if (index + 4 > total) -+ return 0; -+ -+ len = *((const u16 *)(data + index + 2)) + 4; -+ break; -+ case MIPI_SEQ_ELEM_DELAY: -+ len = 4; -+ break; -+ case MIPI_SEQ_ELEM_GPIO: -+ len = 2; -+ break; -+ case MIPI_SEQ_ELEM_I2C: -+ if (index + 7 > total) -+ return 0; -+ len = *(data + index + 6) + 7; -+ break; -+ default: -+ DRM_ERROR("Unknown operation byte\n"); -+ return 0; -+ } -+ } -+ -+ return 0; -+} -+ -+static int goto_next_sequence_v3(const u8 *data, int index, int total) -+{ -+ int seq_end; -+ u16 len; -+ u32 size_of_sequence; -+ -+ /* -+ * Could skip sequence based on Size of Sequence alone, but also do some -+ * checking on the structure. -+ */ -+ if (total < 5) { -+ DRM_ERROR("Too small sequence size\n"); -+ return 0; -+ } -+ -+ /* Skip Sequence Byte. */ -+ index++; -+ -+ /* -+ * Size of Sequence. Excludes the Sequence Byte and the size itself, -+ * includes MIPI_SEQ_ELEM_END byte, excludes the final MIPI_SEQ_END -+ * byte. -+ */ -+ size_of_sequence = *((const u32 *)(data + index)); -+ index += 4; -+ -+ seq_end = index + size_of_sequence; -+ if (seq_end > total) { -+ DRM_ERROR("Invalid sequence size\n"); -+ return 0; -+ } -+ -+ for (; index < total; index += len) { -+ u8 operation_byte = *(data + index); -+ index++; -+ -+ if (operation_byte == MIPI_SEQ_ELEM_END) { -+ if (index != seq_end) { -+ DRM_ERROR("Invalid element structure\n"); -+ return 0; -+ } -+ return index; -+ } -+ -+ len = *(data + index); -+ index++; -+ -+ /* -+ * FIXME: Would be nice to check elements like for v1/v2 in -+ * goto_next_sequence() above. -+ */ -+ switch (operation_byte) { -+ case MIPI_SEQ_ELEM_SEND_PKT: -+ case MIPI_SEQ_ELEM_DELAY: -+ case MIPI_SEQ_ELEM_GPIO: -+ case MIPI_SEQ_ELEM_I2C: -+ case MIPI_SEQ_ELEM_SPI: -+ case MIPI_SEQ_ELEM_PMIC: -+ break; -+ default: -+ DRM_ERROR("Unknown operation byte %u\n", -+ operation_byte); -+ break; -+ } -+ } -+ -+ return 0; -+} -+ -+/* -+ * Get len of pre-fixed deassert fragment from a v1 init OTP sequence, -+ * skip all delay + gpio operands and stop at the first DSI packet op. -+ */ -+static int get_init_otp_deassert_fragment_len(struct drm_i915_private *dev_priv) -+{ -+ const u8 *data = dev_priv->vbt.dsi.sequence[MIPI_SEQ_INIT_OTP]; -+ int index, len; -+ -+ if (WARN_ON(!data || dev_priv->vbt.dsi.seq_version != 1)) -+ return 0; -+ -+ /* index = 1 to skip sequence byte */ -+ for (index = 1; data[index] != MIPI_SEQ_ELEM_END; index += len) { -+ switch (data[index]) { -+ case MIPI_SEQ_ELEM_SEND_PKT: -+ return index == 1 ? 0 : index; -+ case MIPI_SEQ_ELEM_DELAY: -+ len = 5; /* 1 byte for operand + uint32 */ -+ break; -+ case MIPI_SEQ_ELEM_GPIO: -+ len = 3; /* 1 byte for op, 1 for gpio_nr, 1 for value */ -+ break; -+ default: -+ return 0; -+ } -+ } -+ -+ return 0; -+} -+ -+/* -+ * Some v1 VBT MIPI sequences do the deassert in the init OTP sequence. -+ * The deassert must be done before calling intel_dsi_device_ready, so for -+ * these devices we split the init OTP sequence into a deassert sequence and -+ * the actual init OTP part. -+ */ -+static void fixup_mipi_sequences(struct drm_i915_private *dev_priv) -+{ -+ u8 *init_otp; -+ int len; -+ -+ /* Limit this to VLV for now. */ -+ if (!IS_VALLEYVIEW(dev_priv)) -+ return; -+ -+ /* Limit this to v1 vid-mode sequences */ -+ if (dev_priv->vbt.dsi.config->is_cmd_mode || -+ dev_priv->vbt.dsi.seq_version != 1) -+ return; -+ -+ /* Only do this if there are otp and assert seqs and no deassert seq */ -+ if (!dev_priv->vbt.dsi.sequence[MIPI_SEQ_INIT_OTP] || -+ !dev_priv->vbt.dsi.sequence[MIPI_SEQ_ASSERT_RESET] || -+ dev_priv->vbt.dsi.sequence[MIPI_SEQ_DEASSERT_RESET]) -+ return; -+ -+ /* The deassert-sequence ends at the first DSI packet */ -+ len = get_init_otp_deassert_fragment_len(dev_priv); -+ if (!len) -+ return; -+ -+ DRM_DEBUG_KMS("Using init OTP fragment to deassert reset\n"); -+ -+ /* Copy the fragment, update seq byte and terminate it */ -+ init_otp = (u8 *)dev_priv->vbt.dsi.sequence[MIPI_SEQ_INIT_OTP]; -+ dev_priv->vbt.dsi.deassert_seq = kmemdup(init_otp, len + 1, GFP_KERNEL); -+ if (!dev_priv->vbt.dsi.deassert_seq) -+ return; -+ dev_priv->vbt.dsi.deassert_seq[0] = MIPI_SEQ_DEASSERT_RESET; -+ dev_priv->vbt.dsi.deassert_seq[len] = MIPI_SEQ_ELEM_END; -+ /* Use the copy for deassert */ -+ dev_priv->vbt.dsi.sequence[MIPI_SEQ_DEASSERT_RESET] = -+ dev_priv->vbt.dsi.deassert_seq; -+ /* Replace the last byte of the fragment with init OTP seq byte */ -+ init_otp[len - 1] = MIPI_SEQ_INIT_OTP; -+ /* And make MIPI_MIPI_SEQ_INIT_OTP point to it */ -+ dev_priv->vbt.dsi.sequence[MIPI_SEQ_INIT_OTP] = init_otp + len - 1; -+} -+ -+static void -+parse_mipi_sequence(struct drm_i915_private *dev_priv, -+ const struct bdb_header *bdb) -+{ -+ int panel_type = dev_priv->vbt.panel_type; -+ const struct bdb_mipi_sequence *sequence; -+ const u8 *seq_data; -+ u32 seq_size; -+ u8 *data; -+ int index = 0; -+ -+ /* Only our generic panel driver uses the sequence block. */ -+ if (dev_priv->vbt.dsi.panel_id != MIPI_DSI_GENERIC_PANEL_ID) -+ return; -+ -+ sequence = find_section(bdb, BDB_MIPI_SEQUENCE); -+ if (!sequence) { -+ DRM_DEBUG_KMS("No MIPI Sequence found, parsing complete\n"); -+ return; -+ } -+ -+ /* Fail gracefully for forward incompatible sequence block. */ -+ if (sequence->version >= 4) { -+ DRM_ERROR("Unable to parse MIPI Sequence Block v%u\n", -+ sequence->version); -+ return; -+ } -+ -+ DRM_DEBUG_DRIVER("Found MIPI sequence block v%u\n", sequence->version); -+ -+ seq_data = find_panel_sequence_block(sequence, panel_type, &seq_size); -+ if (!seq_data) -+ return; -+ -+ data = kmemdup(seq_data, seq_size, GFP_KERNEL); -+ if (!data) -+ return; -+ -+ /* Parse the sequences, store pointers to each sequence. */ -+ for (;;) { -+ u8 seq_id = *(data + index); -+ if (seq_id == MIPI_SEQ_END) -+ break; -+ -+ if (seq_id >= MIPI_SEQ_MAX) { -+ DRM_ERROR("Unknown sequence %u\n", seq_id); -+ goto err; -+ } -+ -+ /* Log about presence of sequences we won't run. */ -+ if (seq_id == MIPI_SEQ_TEAR_ON || seq_id == MIPI_SEQ_TEAR_OFF) -+ DRM_DEBUG_KMS("Unsupported sequence %u\n", seq_id); -+ -+ dev_priv->vbt.dsi.sequence[seq_id] = data + index; -+ -+ if (sequence->version >= 3) -+ index = goto_next_sequence_v3(data, index, seq_size); -+ else -+ index = goto_next_sequence(data, index, seq_size); -+ if (!index) { -+ DRM_ERROR("Invalid sequence %u\n", seq_id); -+ goto err; -+ } -+ } -+ -+ dev_priv->vbt.dsi.data = data; -+ dev_priv->vbt.dsi.size = seq_size; -+ dev_priv->vbt.dsi.seq_version = sequence->version; -+ -+ fixup_mipi_sequences(dev_priv); -+ -+ DRM_DEBUG_DRIVER("MIPI related VBT parsing complete\n"); -+ return; -+ -+err: -+ kfree(data); -+ memset(dev_priv->vbt.dsi.sequence, 0, sizeof(dev_priv->vbt.dsi.sequence)); -+} -+ -+static u8 translate_iboost(u8 val) -+{ -+ static const u8 mapping[] = { 1, 3, 7 }; /* See VBT spec */ -+ -+ if (val >= ARRAY_SIZE(mapping)) { -+ DRM_DEBUG_KMS("Unsupported I_boost value found in VBT (%d), display may not work properly\n", val); -+ return 0; -+ } -+ return mapping[val]; -+} -+ -+static void sanitize_ddc_pin(struct drm_i915_private *dev_priv, -+ enum port port) -+{ -+ const struct ddi_vbt_port_info *info = -+ &dev_priv->vbt.ddi_port_info[port]; -+ enum port p; -+ -+ if (!info->alternate_ddc_pin) -+ return; -+ -+ for (p = PORT_A; p < I915_MAX_PORTS; p++) { -+ struct ddi_vbt_port_info *i = &dev_priv->vbt.ddi_port_info[p]; -+ -+ if (p == port || !i->present || -+ info->alternate_ddc_pin != i->alternate_ddc_pin) -+ continue; -+ -+ DRM_DEBUG_KMS("port %c trying to use the same DDC pin (0x%x) as port %c, " -+ "disabling port %c DVI/HDMI support\n", -+ port_name(p), i->alternate_ddc_pin, -+ port_name(port), port_name(p)); -+ -+ /* -+ * If we have multiple ports supposedly sharing the -+ * pin, then dvi/hdmi couldn't exist on the shared -+ * port. Otherwise they share the same ddc bin and -+ * system couldn't communicate with them separately. -+ * -+ * Due to parsing the ports in child device order, -+ * a later device will always clobber an earlier one. -+ */ -+ i->supports_dvi = false; -+ i->supports_hdmi = false; -+ i->alternate_ddc_pin = 0; -+ } -+} -+ -+static void sanitize_aux_ch(struct drm_i915_private *dev_priv, -+ enum port port) -+{ -+ const struct ddi_vbt_port_info *info = -+ &dev_priv->vbt.ddi_port_info[port]; -+ enum port p; -+ -+ if (!info->alternate_aux_channel) -+ return; -+ -+ for (p = PORT_A; p < I915_MAX_PORTS; p++) { -+ struct ddi_vbt_port_info *i = &dev_priv->vbt.ddi_port_info[p]; -+ -+ if (p == port || !i->present || -+ info->alternate_aux_channel != i->alternate_aux_channel) -+ continue; -+ -+ DRM_DEBUG_KMS("port %c trying to use the same AUX CH (0x%x) as port %c, " -+ "disabling port %c DP support\n", -+ port_name(p), i->alternate_aux_channel, -+ port_name(port), port_name(p)); -+ -+ /* -+ * If we have multiple ports supposedlt sharing the -+ * aux channel, then DP couldn't exist on the shared -+ * port. Otherwise they share the same aux channel -+ * and system couldn't communicate with them separately. -+ * -+ * Due to parsing the ports in child device order, -+ * a later device will always clobber an earlier one. -+ */ -+ i->supports_dp = false; -+ i->alternate_aux_channel = 0; -+ } -+} -+ -+static const u8 cnp_ddc_pin_map[] = { -+ [0] = 0, /* N/A */ -+ [DDC_BUS_DDI_B] = GMBUS_PIN_1_BXT, -+ [DDC_BUS_DDI_C] = GMBUS_PIN_2_BXT, -+ [DDC_BUS_DDI_D] = GMBUS_PIN_4_CNP, /* sic */ -+ [DDC_BUS_DDI_F] = GMBUS_PIN_3_BXT, /* sic */ -+}; -+ -+static const u8 icp_ddc_pin_map[] = { -+ [ICL_DDC_BUS_DDI_A] = GMBUS_PIN_1_BXT, -+ [ICL_DDC_BUS_DDI_B] = GMBUS_PIN_2_BXT, -+ [ICL_DDC_BUS_PORT_1] = GMBUS_PIN_9_TC1_ICP, -+ [ICL_DDC_BUS_PORT_2] = GMBUS_PIN_10_TC2_ICP, -+ [ICL_DDC_BUS_PORT_3] = GMBUS_PIN_11_TC3_ICP, -+ [ICL_DDC_BUS_PORT_4] = GMBUS_PIN_12_TC4_ICP, -+}; -+ -+static u8 map_ddc_pin(struct drm_i915_private *dev_priv, u8 vbt_pin) -+{ -+ const u8 *ddc_pin_map; -+ int n_entries; -+ -+ if (HAS_PCH_ICP(dev_priv)) { -+ ddc_pin_map = icp_ddc_pin_map; -+ n_entries = ARRAY_SIZE(icp_ddc_pin_map); -+ } else if (HAS_PCH_CNP(dev_priv)) { -+ ddc_pin_map = cnp_ddc_pin_map; -+ n_entries = ARRAY_SIZE(cnp_ddc_pin_map); -+ } else { -+ /* Assuming direct map */ -+ return vbt_pin; -+ } -+ -+ if (vbt_pin < n_entries && ddc_pin_map[vbt_pin] != 0) -+ return ddc_pin_map[vbt_pin]; -+ -+ DRM_DEBUG_KMS("Ignoring alternate pin: VBT claims DDC pin %d, which is not valid for this platform\n", -+ vbt_pin); -+ return 0; -+} -+ -+static enum port dvo_port_to_port(u8 dvo_port) -+{ -+ /* -+ * Each DDI port can have more than one value on the "DVO Port" field, -+ * so look for all the possible values for each port. -+ */ -+ static const int dvo_ports[][3] = { -+ [PORT_A] = { DVO_PORT_HDMIA, DVO_PORT_DPA, -1}, -+ [PORT_B] = { DVO_PORT_HDMIB, DVO_PORT_DPB, -1}, -+ [PORT_C] = { DVO_PORT_HDMIC, DVO_PORT_DPC, -1}, -+ [PORT_D] = { DVO_PORT_HDMID, DVO_PORT_DPD, -1}, -+ [PORT_E] = { DVO_PORT_CRT, DVO_PORT_HDMIE, DVO_PORT_DPE}, -+ [PORT_F] = { DVO_PORT_HDMIF, DVO_PORT_DPF, -1}, -+ }; -+ enum port port; -+ int i; -+ -+ for (port = PORT_A; port < ARRAY_SIZE(dvo_ports); port++) { -+ for (i = 0; i < ARRAY_SIZE(dvo_ports[port]); i++) { -+ if (dvo_ports[port][i] == -1) -+ break; -+ -+ if (dvo_port == dvo_ports[port][i]) -+ return port; -+ } -+ } -+ -+ return PORT_NONE; -+} -+ -+static void parse_ddi_port(struct drm_i915_private *dev_priv, -+ const struct child_device_config *child, -+ u8 bdb_version) -+{ -+ struct ddi_vbt_port_info *info; -+ bool is_dvi, is_hdmi, is_dp, is_edp, is_crt; -+ enum port port; -+ -+ port = dvo_port_to_port(child->dvo_port); -+ if (port == PORT_NONE) -+ return; -+ -+ info = &dev_priv->vbt.ddi_port_info[port]; -+ -+ if (info->present) { -+ DRM_DEBUG_KMS("More than one child device for port %c in VBT, using the first.\n", -+ port_name(port)); -+ return; -+ } -+ -+ info->present = true; -+ -+ is_dvi = child->device_type & DEVICE_TYPE_TMDS_DVI_SIGNALING; -+ is_dp = child->device_type & DEVICE_TYPE_DISPLAYPORT_OUTPUT; -+ is_crt = child->device_type & DEVICE_TYPE_ANALOG_OUTPUT; -+ is_hdmi = is_dvi && (child->device_type & DEVICE_TYPE_NOT_HDMI_OUTPUT) == 0; -+ is_edp = is_dp && (child->device_type & DEVICE_TYPE_INTERNAL_CONNECTOR); -+ -+ if (port == PORT_A && is_dvi) { -+ DRM_DEBUG_KMS("VBT claims port A supports DVI%s, ignoring\n", -+ is_hdmi ? "/HDMI" : ""); -+ is_dvi = false; -+ is_hdmi = false; -+ } -+ -+ info->supports_dvi = is_dvi; -+ info->supports_hdmi = is_hdmi; -+ info->supports_dp = is_dp; -+ info->supports_edp = is_edp; -+ -+ if (bdb_version >= 195) -+ info->supports_typec_usb = child->dp_usb_type_c; -+ -+ if (bdb_version >= 209) -+ info->supports_tbt = child->tbt; -+ -+ DRM_DEBUG_KMS("Port %c VBT info: DP:%d HDMI:%d DVI:%d EDP:%d CRT:%d TCUSB:%d TBT:%d\n", -+ port_name(port), is_dp, is_hdmi, is_dvi, is_edp, is_crt, -+ info->supports_typec_usb, info->supports_tbt); -+ -+ if (is_edp && is_dvi) -+ DRM_DEBUG_KMS("Internal DP port %c is TMDS compatible\n", -+ port_name(port)); -+ if (is_crt && port != PORT_E) -+ DRM_DEBUG_KMS("Port %c is analog\n", port_name(port)); -+ if (is_crt && (is_dvi || is_dp)) -+ DRM_DEBUG_KMS("Analog port %c is also DP or TMDS compatible\n", -+ port_name(port)); -+ if (is_dvi && (port == PORT_A || port == PORT_E)) -+ DRM_DEBUG_KMS("Port %c is TMDS compatible\n", port_name(port)); -+ if (!is_dvi && !is_dp && !is_crt) -+ DRM_DEBUG_KMS("Port %c is not DP/TMDS/CRT compatible\n", -+ port_name(port)); -+ if (is_edp && (port == PORT_B || port == PORT_C || port == PORT_E)) -+ DRM_DEBUG_KMS("Port %c is internal DP\n", port_name(port)); -+ -+ if (is_dvi) { -+ u8 ddc_pin; -+ -+ ddc_pin = map_ddc_pin(dev_priv, child->ddc_pin); -+ if (intel_gmbus_is_valid_pin(dev_priv, ddc_pin)) { -+ info->alternate_ddc_pin = ddc_pin; -+ sanitize_ddc_pin(dev_priv, port); -+ } else { -+ DRM_DEBUG_KMS("Port %c has invalid DDC pin %d, " -+ "sticking to defaults\n", -+ port_name(port), ddc_pin); -+ } -+ } -+ -+ if (is_dp) { -+ info->alternate_aux_channel = child->aux_channel; -+ -+ sanitize_aux_ch(dev_priv, port); -+ } -+ -+ if (bdb_version >= 158) { -+ /* The VBT HDMI level shift values match the table we have. */ -+ u8 hdmi_level_shift = child->hdmi_level_shifter_value; -+ DRM_DEBUG_KMS("VBT HDMI level shift for port %c: %d\n", -+ port_name(port), -+ hdmi_level_shift); -+ info->hdmi_level_shift = hdmi_level_shift; -+ } -+ -+ if (bdb_version >= 204) { -+ int max_tmds_clock; -+ -+ switch (child->hdmi_max_data_rate) { -+ default: -+ MISSING_CASE(child->hdmi_max_data_rate); -+ /* fall through */ -+ case HDMI_MAX_DATA_RATE_PLATFORM: -+ max_tmds_clock = 0; -+ break; -+ case HDMI_MAX_DATA_RATE_297: -+ max_tmds_clock = 297000; -+ break; -+ case HDMI_MAX_DATA_RATE_165: -+ max_tmds_clock = 165000; -+ break; -+ } -+ -+ if (max_tmds_clock) -+ DRM_DEBUG_KMS("VBT HDMI max TMDS clock for port %c: %d kHz\n", -+ port_name(port), max_tmds_clock); -+ info->max_tmds_clock = max_tmds_clock; -+ } -+ -+ /* Parse the I_boost config for SKL and above */ -+ if (bdb_version >= 196 && child->iboost) { -+ info->dp_boost_level = translate_iboost(child->dp_iboost_level); -+ DRM_DEBUG_KMS("VBT (e)DP boost level for port %c: %d\n", -+ port_name(port), info->dp_boost_level); -+ info->hdmi_boost_level = translate_iboost(child->hdmi_iboost_level); -+ DRM_DEBUG_KMS("VBT HDMI boost level for port %c: %d\n", -+ port_name(port), info->hdmi_boost_level); -+ } -+ -+ /* DP max link rate for CNL+ */ -+ if (bdb_version >= 216) { -+ switch (child->dp_max_link_rate) { -+ default: -+ case VBT_DP_MAX_LINK_RATE_HBR3: -+ info->dp_max_link_rate = 810000; -+ break; -+ case VBT_DP_MAX_LINK_RATE_HBR2: -+ info->dp_max_link_rate = 540000; -+ break; -+ case VBT_DP_MAX_LINK_RATE_HBR: -+ info->dp_max_link_rate = 270000; -+ break; -+ case VBT_DP_MAX_LINK_RATE_LBR: -+ info->dp_max_link_rate = 162000; -+ break; -+ } -+ DRM_DEBUG_KMS("VBT DP max link rate for port %c: %d\n", -+ port_name(port), info->dp_max_link_rate); -+ } -+} -+ -+static void parse_ddi_ports(struct drm_i915_private *dev_priv, u8 bdb_version) -+{ -+ const struct child_device_config *child; -+ int i; -+ -+ if (!HAS_DDI(dev_priv) && !IS_CHERRYVIEW(dev_priv)) -+ return; -+ -+ if (bdb_version < 155) -+ return; -+ -+ for (i = 0; i < dev_priv->vbt.child_dev_num; i++) { -+ child = dev_priv->vbt.child_dev + i; -+ -+ parse_ddi_port(dev_priv, child, bdb_version); -+ } -+} -+ -+static void -+parse_general_definitions(struct drm_i915_private *dev_priv, -+ const struct bdb_header *bdb) -+{ -+ const struct bdb_general_definitions *defs; -+ const struct child_device_config *child; -+ int i, child_device_num, count; -+ u8 expected_size; -+ u16 block_size; -+ int bus_pin; -+ -+ defs = find_section(bdb, BDB_GENERAL_DEFINITIONS); -+ if (!defs) { -+ DRM_DEBUG_KMS("No general definition block is found, no devices defined.\n"); -+ return; -+ } -+ -+ block_size = get_blocksize(defs); -+ if (block_size < sizeof(*defs)) { -+ DRM_DEBUG_KMS("General definitions block too small (%u)\n", -+ block_size); -+ return; -+ } -+ -+ bus_pin = defs->crt_ddc_gmbus_pin; -+ DRM_DEBUG_KMS("crt_ddc_bus_pin: %d\n", bus_pin); -+ if (intel_gmbus_is_valid_pin(dev_priv, bus_pin)) -+ dev_priv->vbt.crt_ddc_pin = bus_pin; -+ -+ if (bdb->version < 106) { -+ expected_size = 22; -+ } else if (bdb->version < 111) { -+ expected_size = 27; -+ } else if (bdb->version < 195) { -+ expected_size = LEGACY_CHILD_DEVICE_CONFIG_SIZE; -+ } else if (bdb->version == 195) { -+ expected_size = 37; -+ } else if (bdb->version <= 215) { -+ expected_size = 38; -+ } else if (bdb->version <= 216) { -+ expected_size = 39; -+ } else { -+ expected_size = sizeof(*child); -+ BUILD_BUG_ON(sizeof(*child) < 39); -+ DRM_DEBUG_DRIVER("Expected child device config size for VBT version %u not known; assuming %u\n", -+ bdb->version, expected_size); -+ } -+ -+ /* Flag an error for unexpected size, but continue anyway. */ -+ if (defs->child_dev_size != expected_size) -+ DRM_ERROR("Unexpected child device config size %u (expected %u for VBT version %u)\n", -+ defs->child_dev_size, expected_size, bdb->version); -+ -+ /* The legacy sized child device config is the minimum we need. */ -+ if (defs->child_dev_size < LEGACY_CHILD_DEVICE_CONFIG_SIZE) { -+ DRM_DEBUG_KMS("Child device config size %u is too small.\n", -+ defs->child_dev_size); -+ return; -+ } -+ -+ /* get the number of child device */ -+ child_device_num = (block_size - sizeof(*defs)) / defs->child_dev_size; -+ count = 0; -+ /* get the number of child device that is present */ -+ for (i = 0; i < child_device_num; i++) { -+ child = child_device_ptr(defs, i); -+ if (!child->device_type) -+ continue; -+ count++; -+ } -+ if (!count) { -+ DRM_DEBUG_KMS("no child dev is parsed from VBT\n"); -+ return; -+ } -+ dev_priv->vbt.child_dev = kcalloc(count, sizeof(*child), GFP_KERNEL); -+ if (!dev_priv->vbt.child_dev) { -+ DRM_DEBUG_KMS("No memory space for child device\n"); -+ return; -+ } -+ -+ dev_priv->vbt.child_dev_num = count; -+ count = 0; -+ for (i = 0; i < child_device_num; i++) { -+ child = child_device_ptr(defs, i); -+ if (!child->device_type) -+ continue; -+ -+ /* -+ * Copy as much as we know (sizeof) and is available -+ * (child_dev_size) of the child device. Accessing the data must -+ * depend on VBT version. -+ */ -+ memcpy(dev_priv->vbt.child_dev + count, child, -+ min_t(size_t, defs->child_dev_size, sizeof(*child))); -+ count++; -+ } -+} -+ -+/* Common defaults which may be overridden by VBT. */ -+static void -+init_vbt_defaults(struct drm_i915_private *dev_priv) -+{ -+ enum port port; -+ -+ dev_priv->vbt.crt_ddc_pin = GMBUS_PIN_VGADDC; -+ -+ /* Default to having backlight */ -+ dev_priv->vbt.backlight.present = true; -+ -+ /* LFP panel data */ -+ dev_priv->vbt.lvds_dither = 1; -+ -+ /* SDVO panel data */ -+ dev_priv->vbt.sdvo_lvds_vbt_mode = NULL; -+ -+ /* general features */ -+ dev_priv->vbt.int_tv_support = 1; -+ dev_priv->vbt.int_crt_support = 1; -+ -+ /* driver features */ -+ dev_priv->vbt.int_lvds_support = 1; -+ -+ /* Default to using SSC */ -+ dev_priv->vbt.lvds_use_ssc = 1; -+ /* -+ * Core/SandyBridge/IvyBridge use alternative (120MHz) reference -+ * clock for LVDS. -+ */ -+ dev_priv->vbt.lvds_ssc_freq = intel_bios_ssc_frequency(dev_priv, -+ !HAS_PCH_SPLIT(dev_priv)); -+ DRM_DEBUG_KMS("Set default to SSC at %d kHz\n", dev_priv->vbt.lvds_ssc_freq); -+ -+ for (port = PORT_A; port < I915_MAX_PORTS; port++) { -+ struct ddi_vbt_port_info *info = -+ &dev_priv->vbt.ddi_port_info[port]; -+ -+ info->hdmi_level_shift = HDMI_LEVEL_SHIFT_UNKNOWN; -+ } -+} -+ -+/* Defaults to initialize only if there is no VBT. */ -+static void -+init_vbt_missing_defaults(struct drm_i915_private *dev_priv) -+{ -+ enum port port; -+ -+ for (port = PORT_A; port < I915_MAX_PORTS; port++) { -+ struct ddi_vbt_port_info *info = -+ &dev_priv->vbt.ddi_port_info[port]; -+ -+ /* -+ * VBT has the TypeC mode (native,TBT/USB) and we don't want -+ * to detect it. -+ */ -+ if (intel_port_is_tc(dev_priv, port)) -+ continue; -+ -+ info->supports_dvi = (port != PORT_A && port != PORT_E); -+ info->supports_hdmi = info->supports_dvi; -+ info->supports_dp = (port != PORT_E); -+ info->supports_edp = (port == PORT_A); -+ } -+} -+ -+static const struct bdb_header *get_bdb_header(const struct vbt_header *vbt) -+{ -+ const void *_vbt = vbt; -+ -+ return _vbt + vbt->bdb_offset; -+} -+ -+/** -+ * intel_bios_is_valid_vbt - does the given buffer contain a valid VBT -+ * @buf: pointer to a buffer to validate -+ * @size: size of the buffer -+ * -+ * Returns true on valid VBT. -+ */ -+bool intel_bios_is_valid_vbt(const void *buf, size_t size) -+{ -+ const struct vbt_header *vbt = buf; -+ const struct bdb_header *bdb; -+ -+ if (!vbt) -+ return false; -+ -+ if (sizeof(struct vbt_header) > size) { -+ DRM_DEBUG_DRIVER("VBT header incomplete\n"); -+ return false; -+ } -+ -+ if (memcmp(vbt->signature, "$VBT", 4)) { -+ DRM_DEBUG_DRIVER("VBT invalid signature\n"); -+ return false; -+ } -+ -+ if (range_overflows_t(size_t, -+ vbt->bdb_offset, -+ sizeof(struct bdb_header), -+ size)) { -+ DRM_DEBUG_DRIVER("BDB header incomplete\n"); -+ return false; -+ } -+ -+ bdb = get_bdb_header(vbt); -+ if (range_overflows_t(size_t, vbt->bdb_offset, bdb->bdb_size, size)) { -+ DRM_DEBUG_DRIVER("BDB incomplete\n"); -+ return false; -+ } -+ -+ return vbt; -+} -+ -+static const struct vbt_header *find_vbt(void __iomem *bios, size_t size) -+{ -+ size_t i; -+ -+ /* Scour memory looking for the VBT signature. */ -+ for (i = 0; i + 4 < size; i++) { -+ void *vbt; -+ -+ if (ioread32(bios + i) != *((const u32 *) "$VBT")) -+ continue; -+ -+ /* -+ * This is the one place where we explicitly discard the address -+ * space (__iomem) of the BIOS/VBT. -+ */ -+ vbt = (void __force *) bios + i; -+ if (intel_bios_is_valid_vbt(vbt, size - i)) -+ return vbt; -+ -+ break; -+ } -+ -+ return NULL; -+} -+ -+/** -+ * intel_bios_init - find VBT and initialize settings from the BIOS -+ * @dev_priv: i915 device instance -+ * -+ * Parse and initialize settings from the Video BIOS Tables (VBT). If the VBT -+ * was not found in ACPI OpRegion, try to find it in PCI ROM first. Also -+ * initialize some defaults if the VBT is not present at all. -+ */ -+void intel_bios_init(struct drm_i915_private *dev_priv) -+{ -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ const struct vbt_header *vbt = dev_priv->opregion.vbt; -+ const struct bdb_header *bdb; -+ u8 __iomem *bios = NULL; -+ -+ if (!HAS_DISPLAY(dev_priv)) { -+ DRM_DEBUG_KMS("Skipping VBT init due to disabled display.\n"); -+ return; -+ } -+ -+ init_vbt_defaults(dev_priv); -+ -+ /* If the OpRegion does not have VBT, look in PCI ROM. */ -+ if (!vbt) { -+ size_t size; -+ -+ bios = pci_map_rom(pdev, &size); -+ if (!bios) -+ goto out; -+ -+ vbt = find_vbt(bios, size); -+ if (!vbt) -+ goto out; -+ -+ DRM_DEBUG_KMS("Found valid VBT in PCI ROM\n"); -+ } -+ -+ bdb = get_bdb_header(vbt); -+ -+ DRM_DEBUG_KMS("VBT signature \"%.*s\", BDB version %d\n", -+ (int)sizeof(vbt->signature), vbt->signature, bdb->version); -+ -+ /* Grab useful general definitions */ -+ parse_general_features(dev_priv, bdb); -+ parse_general_definitions(dev_priv, bdb); -+ parse_lfp_panel_data(dev_priv, bdb); -+ parse_lfp_backlight(dev_priv, bdb); -+ parse_sdvo_panel_data(dev_priv, bdb); -+ parse_driver_features(dev_priv, bdb); -+ parse_edp(dev_priv, bdb); -+ parse_psr(dev_priv, bdb); -+ parse_mipi_config(dev_priv, bdb); -+ parse_mipi_sequence(dev_priv, bdb); -+ -+ /* Further processing on pre-parsed data */ -+ parse_sdvo_device_mapping(dev_priv, bdb->version); -+ parse_ddi_ports(dev_priv, bdb->version); -+ -+out: -+ if (!vbt) { -+ DRM_INFO("Failed to find VBIOS tables (VBT)\n"); -+ init_vbt_missing_defaults(dev_priv); -+ } -+ -+ if (bios) -+ pci_unmap_rom(pdev, bios); -+} -+ -+/** -+ * intel_bios_cleanup - Free any resources allocated by intel_bios_init() -+ * @dev_priv: i915 device instance -+ */ -+void intel_bios_cleanup(struct drm_i915_private *dev_priv) -+{ -+ kfree(dev_priv->vbt.child_dev); -+ dev_priv->vbt.child_dev = NULL; -+ dev_priv->vbt.child_dev_num = 0; -+ kfree(dev_priv->vbt.sdvo_lvds_vbt_mode); -+ dev_priv->vbt.sdvo_lvds_vbt_mode = NULL; -+ kfree(dev_priv->vbt.lfp_lvds_vbt_mode); -+ dev_priv->vbt.lfp_lvds_vbt_mode = NULL; -+ kfree(dev_priv->vbt.dsi.data); -+ dev_priv->vbt.dsi.data = NULL; -+ kfree(dev_priv->vbt.dsi.pps); -+ dev_priv->vbt.dsi.pps = NULL; -+ kfree(dev_priv->vbt.dsi.config); -+ dev_priv->vbt.dsi.config = NULL; -+ kfree(dev_priv->vbt.dsi.deassert_seq); -+ dev_priv->vbt.dsi.deassert_seq = NULL; -+} -+ -+/** -+ * intel_bios_is_tv_present - is integrated TV present in VBT -+ * @dev_priv: i915 device instance -+ * -+ * Return true if TV is present. If no child devices were parsed from VBT, -+ * assume TV is present. -+ */ -+bool intel_bios_is_tv_present(struct drm_i915_private *dev_priv) -+{ -+ const struct child_device_config *child; -+ int i; -+ -+ if (!dev_priv->vbt.int_tv_support) -+ return false; -+ -+ if (!dev_priv->vbt.child_dev_num) -+ return true; -+ -+ for (i = 0; i < dev_priv->vbt.child_dev_num; i++) { -+ child = dev_priv->vbt.child_dev + i; -+ /* -+ * If the device type is not TV, continue. -+ */ -+ switch (child->device_type) { -+ case DEVICE_TYPE_INT_TV: -+ case DEVICE_TYPE_TV: -+ case DEVICE_TYPE_TV_SVIDEO_COMPOSITE: -+ break; -+ default: -+ continue; -+ } -+ /* Only when the addin_offset is non-zero, it is regarded -+ * as present. -+ */ -+ if (child->addin_offset) -+ return true; -+ } -+ -+ return false; -+} -+ -+/** -+ * intel_bios_is_lvds_present - is LVDS present in VBT -+ * @dev_priv: i915 device instance -+ * @i2c_pin: i2c pin for LVDS if present -+ * -+ * Return true if LVDS is present. If no child devices were parsed from VBT, -+ * assume LVDS is present. -+ */ -+bool intel_bios_is_lvds_present(struct drm_i915_private *dev_priv, u8 *i2c_pin) -+{ -+ const struct child_device_config *child; -+ int i; -+ -+ if (!dev_priv->vbt.child_dev_num) -+ return true; -+ -+ for (i = 0; i < dev_priv->vbt.child_dev_num; i++) { -+ child = dev_priv->vbt.child_dev + i; -+ -+ /* If the device type is not LFP, continue. -+ * We have to check both the new identifiers as well as the -+ * old for compatibility with some BIOSes. -+ */ -+ if (child->device_type != DEVICE_TYPE_INT_LFP && -+ child->device_type != DEVICE_TYPE_LFP) -+ continue; -+ -+ if (intel_gmbus_is_valid_pin(dev_priv, child->i2c_pin)) -+ *i2c_pin = child->i2c_pin; -+ -+ /* However, we cannot trust the BIOS writers to populate -+ * the VBT correctly. Since LVDS requires additional -+ * information from AIM blocks, a non-zero addin offset is -+ * a good indicator that the LVDS is actually present. -+ */ -+ if (child->addin_offset) -+ return true; -+ -+ /* But even then some BIOS writers perform some black magic -+ * and instantiate the device without reference to any -+ * additional data. Trust that if the VBT was written into -+ * the OpRegion then they have validated the LVDS's existence. -+ */ -+ if (dev_priv->opregion.vbt) -+ return true; -+ } -+ -+ return false; -+} -+ -+/** -+ * intel_bios_is_port_present - is the specified digital port present -+ * @dev_priv: i915 device instance -+ * @port: port to check -+ * -+ * Return true if the device in %port is present. -+ */ -+bool intel_bios_is_port_present(struct drm_i915_private *dev_priv, enum port port) -+{ -+ const struct child_device_config *child; -+ static const struct { -+ u16 dp, hdmi; -+ } port_mapping[] = { -+ [PORT_B] = { DVO_PORT_DPB, DVO_PORT_HDMIB, }, -+ [PORT_C] = { DVO_PORT_DPC, DVO_PORT_HDMIC, }, -+ [PORT_D] = { DVO_PORT_DPD, DVO_PORT_HDMID, }, -+ [PORT_E] = { DVO_PORT_DPE, DVO_PORT_HDMIE, }, -+ [PORT_F] = { DVO_PORT_DPF, DVO_PORT_HDMIF, }, -+ }; -+ int i; -+ -+ if (HAS_DDI(dev_priv)) { -+ const struct ddi_vbt_port_info *port_info = -+ &dev_priv->vbt.ddi_port_info[port]; -+ -+ return port_info->supports_dp || -+ port_info->supports_dvi || -+ port_info->supports_hdmi; -+ } -+ -+ /* FIXME maybe deal with port A as well? */ -+ if (WARN_ON(port == PORT_A) || port >= ARRAY_SIZE(port_mapping)) -+ return false; -+ -+ if (!dev_priv->vbt.child_dev_num) -+ return false; -+ -+ for (i = 0; i < dev_priv->vbt.child_dev_num; i++) { -+ child = dev_priv->vbt.child_dev + i; -+ -+ if ((child->dvo_port == port_mapping[port].dp || -+ child->dvo_port == port_mapping[port].hdmi) && -+ (child->device_type & (DEVICE_TYPE_TMDS_DVI_SIGNALING | -+ DEVICE_TYPE_DISPLAYPORT_OUTPUT))) -+ return true; -+ } -+ -+ return false; -+} -+ -+/** -+ * intel_bios_is_port_edp - is the device in given port eDP -+ * @dev_priv: i915 device instance -+ * @port: port to check -+ * -+ * Return true if the device in %port is eDP. -+ */ -+bool intel_bios_is_port_edp(struct drm_i915_private *dev_priv, enum port port) -+{ -+ const struct child_device_config *child; -+ static const short port_mapping[] = { -+ [PORT_B] = DVO_PORT_DPB, -+ [PORT_C] = DVO_PORT_DPC, -+ [PORT_D] = DVO_PORT_DPD, -+ [PORT_E] = DVO_PORT_DPE, -+ [PORT_F] = DVO_PORT_DPF, -+ }; -+ int i; -+ -+ if (HAS_DDI(dev_priv)) -+ return dev_priv->vbt.ddi_port_info[port].supports_edp; -+ -+ if (!dev_priv->vbt.child_dev_num) -+ return false; -+ -+ for (i = 0; i < dev_priv->vbt.child_dev_num; i++) { -+ child = dev_priv->vbt.child_dev + i; -+ -+ if (child->dvo_port == port_mapping[port] && -+ (child->device_type & DEVICE_TYPE_eDP_BITS) == -+ (DEVICE_TYPE_eDP & DEVICE_TYPE_eDP_BITS)) -+ return true; -+ } -+ -+ return false; -+} -+ -+static bool child_dev_is_dp_dual_mode(const struct child_device_config *child, -+ enum port port) -+{ -+ static const struct { -+ u16 dp, hdmi; -+ } port_mapping[] = { -+ /* -+ * Buggy VBTs may declare DP ports as having -+ * HDMI type dvo_port :( So let's check both. -+ */ -+ [PORT_B] = { DVO_PORT_DPB, DVO_PORT_HDMIB, }, -+ [PORT_C] = { DVO_PORT_DPC, DVO_PORT_HDMIC, }, -+ [PORT_D] = { DVO_PORT_DPD, DVO_PORT_HDMID, }, -+ [PORT_E] = { DVO_PORT_DPE, DVO_PORT_HDMIE, }, -+ [PORT_F] = { DVO_PORT_DPF, DVO_PORT_HDMIF, }, -+ }; -+ -+ if (port == PORT_A || port >= ARRAY_SIZE(port_mapping)) -+ return false; -+ -+ if ((child->device_type & DEVICE_TYPE_DP_DUAL_MODE_BITS) != -+ (DEVICE_TYPE_DP_DUAL_MODE & DEVICE_TYPE_DP_DUAL_MODE_BITS)) -+ return false; -+ -+ if (child->dvo_port == port_mapping[port].dp) -+ return true; -+ -+ /* Only accept a HDMI dvo_port as DP++ if it has an AUX channel */ -+ if (child->dvo_port == port_mapping[port].hdmi && -+ child->aux_channel != 0) -+ return true; -+ -+ return false; -+} -+ -+bool intel_bios_is_port_dp_dual_mode(struct drm_i915_private *dev_priv, -+ enum port port) -+{ -+ const struct child_device_config *child; -+ int i; -+ -+ for (i = 0; i < dev_priv->vbt.child_dev_num; i++) { -+ child = dev_priv->vbt.child_dev + i; -+ -+ if (child_dev_is_dp_dual_mode(child, port)) -+ return true; -+ } -+ -+ return false; -+} -+ -+/** -+ * intel_bios_is_dsi_present - is DSI present in VBT -+ * @dev_priv: i915 device instance -+ * @port: port for DSI if present -+ * -+ * Return true if DSI is present, and return the port in %port. -+ */ -+bool intel_bios_is_dsi_present(struct drm_i915_private *dev_priv, -+ enum port *port) -+{ -+ const struct child_device_config *child; -+ u8 dvo_port; -+ int i; -+ -+ for (i = 0; i < dev_priv->vbt.child_dev_num; i++) { -+ child = dev_priv->vbt.child_dev + i; -+ -+ if (!(child->device_type & DEVICE_TYPE_MIPI_OUTPUT)) -+ continue; -+ -+ dvo_port = child->dvo_port; -+ -+ if (dvo_port == DVO_PORT_MIPIA || -+ (dvo_port == DVO_PORT_MIPIB && INTEL_GEN(dev_priv) >= 11) || -+ (dvo_port == DVO_PORT_MIPIC && INTEL_GEN(dev_priv) < 11)) { -+ if (port) -+ *port = dvo_port - DVO_PORT_MIPIA; -+ return true; -+ } else if (dvo_port == DVO_PORT_MIPIB || -+ dvo_port == DVO_PORT_MIPIC || -+ dvo_port == DVO_PORT_MIPID) { -+ DRM_DEBUG_KMS("VBT has unsupported DSI port %c\n", -+ port_name(dvo_port - DVO_PORT_MIPIA)); -+ } -+ } -+ -+ return false; -+} -+ -+/** -+ * intel_bios_is_port_hpd_inverted - is HPD inverted for %port -+ * @dev_priv: i915 device instance -+ * @port: port to check -+ * -+ * Return true if HPD should be inverted for %port. -+ */ -+bool -+intel_bios_is_port_hpd_inverted(struct drm_i915_private *dev_priv, -+ enum port port) -+{ -+ const struct child_device_config *child; -+ int i; -+ -+ if (WARN_ON_ONCE(!IS_GEN9_LP(dev_priv))) -+ return false; -+ -+ for (i = 0; i < dev_priv->vbt.child_dev_num; i++) { -+ child = dev_priv->vbt.child_dev + i; -+ -+ if (!child->hpd_invert) -+ continue; -+ -+ switch (child->dvo_port) { -+ case DVO_PORT_DPA: -+ case DVO_PORT_HDMIA: -+ if (port == PORT_A) -+ return true; -+ break; -+ case DVO_PORT_DPB: -+ case DVO_PORT_HDMIB: -+ if (port == PORT_B) -+ return true; -+ break; -+ case DVO_PORT_DPC: -+ case DVO_PORT_HDMIC: -+ if (port == PORT_C) -+ return true; -+ break; -+ default: -+ break; -+ } -+ } -+ -+ return false; -+} -+ -+/** -+ * intel_bios_is_lspcon_present - if LSPCON is attached on %port -+ * @dev_priv: i915 device instance -+ * @port: port to check -+ * -+ * Return true if LSPCON is present on this port -+ */ -+bool -+intel_bios_is_lspcon_present(struct drm_i915_private *dev_priv, -+ enum port port) -+{ -+ const struct child_device_config *child; -+ int i; -+ -+ if (!HAS_LSPCON(dev_priv)) -+ return false; -+ -+ for (i = 0; i < dev_priv->vbt.child_dev_num; i++) { -+ child = dev_priv->vbt.child_dev + i; -+ -+ if (!child->lspcon) -+ continue; -+ -+ switch (child->dvo_port) { -+ case DVO_PORT_DPA: -+ case DVO_PORT_HDMIA: -+ if (port == PORT_A) -+ return true; -+ break; -+ case DVO_PORT_DPB: -+ case DVO_PORT_HDMIB: -+ if (port == PORT_B) -+ return true; -+ break; -+ case DVO_PORT_DPC: -+ case DVO_PORT_HDMIC: -+ if (port == PORT_C) -+ return true; -+ break; -+ case DVO_PORT_DPD: -+ case DVO_PORT_HDMID: -+ if (port == PORT_D) -+ return true; -+ break; -+ case DVO_PORT_DPF: -+ case DVO_PORT_HDMIF: -+ if (port == PORT_F) -+ return true; -+ break; -+ default: -+ break; -+ } -+ } -+ -+ return false; -+} -+ -+enum aux_ch intel_bios_port_aux_ch(struct drm_i915_private *dev_priv, -+ enum port port) -+{ -+ const struct ddi_vbt_port_info *info = -+ &dev_priv->vbt.ddi_port_info[port]; -+ enum aux_ch aux_ch; -+ -+ if (!info->alternate_aux_channel) { -+ aux_ch = (enum aux_ch)port; -+ -+ DRM_DEBUG_KMS("using AUX %c for port %c (platform default)\n", -+ aux_ch_name(aux_ch), port_name(port)); -+ return aux_ch; -+ } -+ -+ switch (info->alternate_aux_channel) { -+ case DP_AUX_A: -+ aux_ch = AUX_CH_A; -+ break; -+ case DP_AUX_B: -+ aux_ch = AUX_CH_B; -+ break; -+ case DP_AUX_C: -+ aux_ch = AUX_CH_C; -+ break; -+ case DP_AUX_D: -+ aux_ch = AUX_CH_D; -+ break; -+ case DP_AUX_E: -+ aux_ch = AUX_CH_E; -+ break; -+ case DP_AUX_F: -+ aux_ch = AUX_CH_F; -+ break; -+ default: -+ MISSING_CASE(info->alternate_aux_channel); -+ aux_ch = AUX_CH_A; -+ break; -+ } -+ -+ DRM_DEBUG_KMS("using AUX %c for port %c (VBT)\n", -+ aux_ch_name(aux_ch), port_name(port)); -+ -+ return aux_ch; -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_bios.h b/drivers/gpu/drm/i915_legacy/intel_bios.h -new file mode 100644 -index 000000000000..7e3545f65257 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_bios.h -@@ -0,0 +1,223 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+/* -+ * Please use intel_vbt_defs.h for VBT private data, to hide and abstract away -+ * the VBT from the rest of the driver. Add the parsed, clean data to struct -+ * intel_vbt_data within struct drm_i915_private. -+ */ -+ -+#ifndef _INTEL_BIOS_H_ -+#define _INTEL_BIOS_H_ -+ -+enum intel_backlight_type { -+ INTEL_BACKLIGHT_PMIC, -+ INTEL_BACKLIGHT_LPSS, -+ INTEL_BACKLIGHT_DISPLAY_DDI, -+ INTEL_BACKLIGHT_DSI_DCS, -+ INTEL_BACKLIGHT_PANEL_DRIVER_INTERFACE, -+}; -+ -+struct edp_power_seq { -+ u16 t1_t3; -+ u16 t8; -+ u16 t9; -+ u16 t10; -+ u16 t11_t12; -+} __packed; -+ -+/* -+ * MIPI Sequence Block definitions -+ * -+ * Note the VBT spec has AssertReset / DeassertReset swapped from their -+ * usual naming, we use the proper names here to avoid confusion when -+ * reading the code. -+ */ -+enum mipi_seq { -+ MIPI_SEQ_END = 0, -+ MIPI_SEQ_DEASSERT_RESET, /* Spec says MipiAssertResetPin */ -+ MIPI_SEQ_INIT_OTP, -+ MIPI_SEQ_DISPLAY_ON, -+ MIPI_SEQ_DISPLAY_OFF, -+ MIPI_SEQ_ASSERT_RESET, /* Spec says MipiDeassertResetPin */ -+ MIPI_SEQ_BACKLIGHT_ON, /* sequence block v2+ */ -+ MIPI_SEQ_BACKLIGHT_OFF, /* sequence block v2+ */ -+ MIPI_SEQ_TEAR_ON, /* sequence block v2+ */ -+ MIPI_SEQ_TEAR_OFF, /* sequence block v3+ */ -+ MIPI_SEQ_POWER_ON, /* sequence block v3+ */ -+ MIPI_SEQ_POWER_OFF, /* sequence block v3+ */ -+ MIPI_SEQ_MAX -+}; -+ -+enum mipi_seq_element { -+ MIPI_SEQ_ELEM_END = 0, -+ MIPI_SEQ_ELEM_SEND_PKT, -+ MIPI_SEQ_ELEM_DELAY, -+ MIPI_SEQ_ELEM_GPIO, -+ MIPI_SEQ_ELEM_I2C, /* sequence block v2+ */ -+ MIPI_SEQ_ELEM_SPI, /* sequence block v3+ */ -+ MIPI_SEQ_ELEM_PMIC, /* sequence block v3+ */ -+ MIPI_SEQ_ELEM_MAX -+}; -+ -+#define MIPI_DSI_UNDEFINED_PANEL_ID 0 -+#define MIPI_DSI_GENERIC_PANEL_ID 1 -+ -+struct mipi_config { -+ u16 panel_id; -+ -+ /* General Params */ -+ u32 enable_dithering:1; -+ u32 rsvd1:1; -+ u32 is_bridge:1; -+ -+ u32 panel_arch_type:2; -+ u32 is_cmd_mode:1; -+ -+#define NON_BURST_SYNC_PULSE 0x1 -+#define NON_BURST_SYNC_EVENTS 0x2 -+#define BURST_MODE 0x3 -+ u32 video_transfer_mode:2; -+ -+ u32 cabc_supported:1; -+#define PPS_BLC_PMIC 0 -+#define PPS_BLC_SOC 1 -+ u32 pwm_blc:1; -+ -+ /* Bit 13:10 */ -+#define PIXEL_FORMAT_RGB565 0x1 -+#define PIXEL_FORMAT_RGB666 0x2 -+#define PIXEL_FORMAT_RGB666_LOOSELY_PACKED 0x3 -+#define PIXEL_FORMAT_RGB888 0x4 -+ u32 videomode_color_format:4; -+ -+ /* Bit 15:14 */ -+#define ENABLE_ROTATION_0 0x0 -+#define ENABLE_ROTATION_90 0x1 -+#define ENABLE_ROTATION_180 0x2 -+#define ENABLE_ROTATION_270 0x3 -+ u32 rotation:2; -+ u32 bta_enabled:1; -+ u32 rsvd2:15; -+ -+ /* 2 byte Port Description */ -+#define DUAL_LINK_NOT_SUPPORTED 0 -+#define DUAL_LINK_FRONT_BACK 1 -+#define DUAL_LINK_PIXEL_ALT 2 -+ u16 dual_link:2; -+ u16 lane_cnt:2; -+ u16 pixel_overlap:3; -+ u16 rgb_flip:1; -+#define DL_DCS_PORT_A 0x00 -+#define DL_DCS_PORT_C 0x01 -+#define DL_DCS_PORT_A_AND_C 0x02 -+ u16 dl_dcs_cabc_ports:2; -+ u16 dl_dcs_backlight_ports:2; -+ u16 rsvd3:4; -+ -+ u16 rsvd4; -+ -+ u8 rsvd5; -+ u32 target_burst_mode_freq; -+ u32 dsi_ddr_clk; -+ u32 bridge_ref_clk; -+ -+#define BYTE_CLK_SEL_20MHZ 0 -+#define BYTE_CLK_SEL_10MHZ 1 -+#define BYTE_CLK_SEL_5MHZ 2 -+ u8 byte_clk_sel:2; -+ -+ u8 rsvd6:6; -+ -+ /* DPHY Flags */ -+ u16 dphy_param_valid:1; -+ u16 eot_pkt_disabled:1; -+ u16 enable_clk_stop:1; -+ u16 rsvd7:13; -+ -+ u32 hs_tx_timeout; -+ u32 lp_rx_timeout; -+ u32 turn_around_timeout; -+ u32 device_reset_timer; -+ u32 master_init_timer; -+ u32 dbi_bw_timer; -+ u32 lp_byte_clk_val; -+ -+ /* 4 byte Dphy Params */ -+ u32 prepare_cnt:6; -+ u32 rsvd8:2; -+ u32 clk_zero_cnt:8; -+ u32 trail_cnt:5; -+ u32 rsvd9:3; -+ u32 exit_zero_cnt:6; -+ u32 rsvd10:2; -+ -+ u32 clk_lane_switch_cnt; -+ u32 hl_switch_cnt; -+ -+ u32 rsvd11[6]; -+ -+ /* timings based on dphy spec */ -+ u8 tclk_miss; -+ u8 tclk_post; -+ u8 rsvd12; -+ u8 tclk_pre; -+ u8 tclk_prepare; -+ u8 tclk_settle; -+ u8 tclk_term_enable; -+ u8 tclk_trail; -+ u16 tclk_prepare_clkzero; -+ u8 rsvd13; -+ u8 td_term_enable; -+ u8 teot; -+ u8 ths_exit; -+ u8 ths_prepare; -+ u16 ths_prepare_hszero; -+ u8 rsvd14; -+ u8 ths_settle; -+ u8 ths_skip; -+ u8 ths_trail; -+ u8 tinit; -+ u8 tlpx; -+ u8 rsvd15[3]; -+ -+ /* GPIOs */ -+ u8 panel_enable; -+ u8 bl_enable; -+ u8 pwm_enable; -+ u8 reset_r_n; -+ u8 pwr_down_r; -+ u8 stdby_r_n; -+ -+} __packed; -+ -+/* all delays have a unit of 100us */ -+struct mipi_pps_data { -+ u16 panel_on_delay; -+ u16 bl_enable_delay; -+ u16 bl_disable_delay; -+ u16 panel_off_delay; -+ u16 panel_power_cycle_delay; -+} __packed; -+ -+#endif /* _INTEL_BIOS_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_breadcrumbs.c b/drivers/gpu/drm/i915_legacy/intel_breadcrumbs.c -new file mode 100644 -index 000000000000..832cb6b1e9bd ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_breadcrumbs.c -@@ -0,0 +1,373 @@ -+/* -+ * Copyright © 2015 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include -+#include -+#include -+ -+#include "i915_drv.h" -+ -+static void irq_enable(struct intel_engine_cs *engine) -+{ -+ if (!engine->irq_enable) -+ return; -+ -+ /* Caller disables interrupts */ -+ spin_lock(&engine->i915->irq_lock); -+ engine->irq_enable(engine); -+ spin_unlock(&engine->i915->irq_lock); -+} -+ -+static void irq_disable(struct intel_engine_cs *engine) -+{ -+ if (!engine->irq_disable) -+ return; -+ -+ /* Caller disables interrupts */ -+ spin_lock(&engine->i915->irq_lock); -+ engine->irq_disable(engine); -+ spin_unlock(&engine->i915->irq_lock); -+} -+ -+static void __intel_breadcrumbs_disarm_irq(struct intel_breadcrumbs *b) -+{ -+ lockdep_assert_held(&b->irq_lock); -+ -+ GEM_BUG_ON(!b->irq_enabled); -+ if (!--b->irq_enabled) -+ irq_disable(container_of(b, -+ struct intel_engine_cs, -+ breadcrumbs)); -+ -+ b->irq_armed = false; -+} -+ -+void intel_engine_disarm_breadcrumbs(struct intel_engine_cs *engine) -+{ -+ struct intel_breadcrumbs *b = &engine->breadcrumbs; -+ -+ if (!b->irq_armed) -+ return; -+ -+ spin_lock_irq(&b->irq_lock); -+ if (b->irq_armed) -+ __intel_breadcrumbs_disarm_irq(b); -+ spin_unlock_irq(&b->irq_lock); -+} -+ -+static inline bool __request_completed(const struct i915_request *rq) -+{ -+ return i915_seqno_passed(__hwsp_seqno(rq), rq->fence.seqno); -+} -+ -+static bool -+__dma_fence_signal(struct dma_fence *fence) -+{ -+ return !test_and_set_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags); -+} -+ -+static void -+__dma_fence_signal__timestamp(struct dma_fence *fence, ktime_t timestamp) -+{ -+ fence->timestamp = timestamp; -+ set_bit(DMA_FENCE_FLAG_TIMESTAMP_BIT, &fence->flags); -+ trace_dma_fence_signaled(fence); -+} -+ -+static void -+__dma_fence_signal__notify(struct dma_fence *fence) -+{ -+ struct dma_fence_cb *cur, *tmp; -+ -+ lockdep_assert_held(fence->lock); -+ lockdep_assert_irqs_disabled(); -+ -+ list_for_each_entry_safe(cur, tmp, &fence->cb_list, node) { -+ INIT_LIST_HEAD(&cur->node); -+ cur->func(fence, cur); -+ } -+ INIT_LIST_HEAD(&fence->cb_list); -+} -+ -+void intel_engine_breadcrumbs_irq(struct intel_engine_cs *engine) -+{ -+ struct intel_breadcrumbs *b = &engine->breadcrumbs; -+ const ktime_t timestamp = ktime_get(); -+ struct intel_context *ce, *cn; -+ struct list_head *pos, *next; -+ LIST_HEAD(signal); -+ -+ spin_lock(&b->irq_lock); -+ -+ if (b->irq_armed && list_empty(&b->signalers)) -+ __intel_breadcrumbs_disarm_irq(b); -+ -+ list_for_each_entry_safe(ce, cn, &b->signalers, signal_link) { -+ GEM_BUG_ON(list_empty(&ce->signals)); -+ -+ list_for_each_safe(pos, next, &ce->signals) { -+ struct i915_request *rq = -+ list_entry(pos, typeof(*rq), signal_link); -+ -+ if (!__request_completed(rq)) -+ break; -+ -+ GEM_BUG_ON(!test_bit(I915_FENCE_FLAG_SIGNAL, -+ &rq->fence.flags)); -+ clear_bit(I915_FENCE_FLAG_SIGNAL, &rq->fence.flags); -+ -+ if (!__dma_fence_signal(&rq->fence)) -+ continue; -+ -+ /* -+ * Queue for execution after dropping the signaling -+ * spinlock as the callback chain may end up adding -+ * more signalers to the same context or engine. -+ */ -+ i915_request_get(rq); -+ list_add_tail(&rq->signal_link, &signal); -+ } -+ -+ /* -+ * We process the list deletion in bulk, only using a list_add -+ * (not list_move) above but keeping the status of -+ * rq->signal_link known with the I915_FENCE_FLAG_SIGNAL bit. -+ */ -+ if (!list_is_first(pos, &ce->signals)) { -+ /* Advance the list to the first incomplete request */ -+ __list_del_many(&ce->signals, pos); -+ if (&ce->signals == pos) /* now empty */ -+ list_del_init(&ce->signal_link); -+ } -+ } -+ -+ spin_unlock(&b->irq_lock); -+ -+ list_for_each_safe(pos, next, &signal) { -+ struct i915_request *rq = -+ list_entry(pos, typeof(*rq), signal_link); -+ -+ __dma_fence_signal__timestamp(&rq->fence, timestamp); -+ -+ spin_lock(&rq->lock); -+ __dma_fence_signal__notify(&rq->fence); -+ spin_unlock(&rq->lock); -+ -+ i915_request_put(rq); -+ } -+} -+ -+void intel_engine_signal_breadcrumbs(struct intel_engine_cs *engine) -+{ -+ local_irq_disable(); -+ intel_engine_breadcrumbs_irq(engine); -+ local_irq_enable(); -+} -+ -+static void signal_irq_work(struct irq_work *work) -+{ -+ struct intel_engine_cs *engine = -+ container_of(work, typeof(*engine), breadcrumbs.irq_work); -+ -+ intel_engine_breadcrumbs_irq(engine); -+} -+ -+void intel_engine_pin_breadcrumbs_irq(struct intel_engine_cs *engine) -+{ -+ struct intel_breadcrumbs *b = &engine->breadcrumbs; -+ -+ spin_lock_irq(&b->irq_lock); -+ if (!b->irq_enabled++) -+ irq_enable(engine); -+ GEM_BUG_ON(!b->irq_enabled); /* no overflow! */ -+ spin_unlock_irq(&b->irq_lock); -+} -+ -+void intel_engine_unpin_breadcrumbs_irq(struct intel_engine_cs *engine) -+{ -+ struct intel_breadcrumbs *b = &engine->breadcrumbs; -+ -+ spin_lock_irq(&b->irq_lock); -+ GEM_BUG_ON(!b->irq_enabled); /* no underflow! */ -+ if (!--b->irq_enabled) -+ irq_disable(engine); -+ spin_unlock_irq(&b->irq_lock); -+} -+ -+static void __intel_breadcrumbs_arm_irq(struct intel_breadcrumbs *b) -+{ -+ struct intel_engine_cs *engine = -+ container_of(b, struct intel_engine_cs, breadcrumbs); -+ -+ lockdep_assert_held(&b->irq_lock); -+ if (b->irq_armed) -+ return; -+ -+ /* -+ * The breadcrumb irq will be disarmed on the interrupt after the -+ * waiters are signaled. This gives us a single interrupt window in -+ * which we can add a new waiter and avoid the cost of re-enabling -+ * the irq. -+ */ -+ b->irq_armed = true; -+ -+ /* -+ * Since we are waiting on a request, the GPU should be busy -+ * and should have its own rpm reference. This is tracked -+ * by i915->gt.awake, we can forgo holding our own wakref -+ * for the interrupt as before i915->gt.awake is released (when -+ * the driver is idle) we disarm the breadcrumbs. -+ */ -+ -+ if (!b->irq_enabled++) -+ irq_enable(engine); -+} -+ -+void intel_engine_init_breadcrumbs(struct intel_engine_cs *engine) -+{ -+ struct intel_breadcrumbs *b = &engine->breadcrumbs; -+ -+ spin_lock_init(&b->irq_lock); -+ INIT_LIST_HEAD(&b->signalers); -+ -+ init_irq_work(&b->irq_work, signal_irq_work); -+} -+ -+void intel_engine_reset_breadcrumbs(struct intel_engine_cs *engine) -+{ -+ struct intel_breadcrumbs *b = &engine->breadcrumbs; -+ unsigned long flags; -+ -+ spin_lock_irqsave(&b->irq_lock, flags); -+ -+ if (b->irq_enabled) -+ irq_enable(engine); -+ else -+ irq_disable(engine); -+ -+ spin_unlock_irqrestore(&b->irq_lock, flags); -+} -+ -+void intel_engine_fini_breadcrumbs(struct intel_engine_cs *engine) -+{ -+} -+ -+bool i915_request_enable_breadcrumb(struct i915_request *rq) -+{ -+ lockdep_assert_held(&rq->lock); -+ lockdep_assert_irqs_disabled(); -+ -+ if (test_bit(I915_FENCE_FLAG_ACTIVE, &rq->fence.flags)) { -+ struct intel_breadcrumbs *b = &rq->engine->breadcrumbs; -+ struct intel_context *ce = rq->hw_context; -+ struct list_head *pos; -+ -+ spin_lock(&b->irq_lock); -+ GEM_BUG_ON(test_bit(I915_FENCE_FLAG_SIGNAL, &rq->fence.flags)); -+ -+ __intel_breadcrumbs_arm_irq(b); -+ -+ /* -+ * We keep the seqno in retirement order, so we can break -+ * inside intel_engine_breadcrumbs_irq as soon as we've passed -+ * the last completed request (or seen a request that hasn't -+ * event started). We could iterate the timeline->requests list, -+ * but keeping a separate signalers_list has the advantage of -+ * hopefully being much smaller than the full list and so -+ * provides faster iteration and detection when there are no -+ * more interrupts required for this context. -+ * -+ * We typically expect to add new signalers in order, so we -+ * start looking for our insertion point from the tail of -+ * the list. -+ */ -+ list_for_each_prev(pos, &ce->signals) { -+ struct i915_request *it = -+ list_entry(pos, typeof(*it), signal_link); -+ -+ if (i915_seqno_passed(rq->fence.seqno, it->fence.seqno)) -+ break; -+ } -+ list_add(&rq->signal_link, pos); -+ if (pos == &ce->signals) /* catch transitions from empty list */ -+ list_move_tail(&ce->signal_link, &b->signalers); -+ -+ set_bit(I915_FENCE_FLAG_SIGNAL, &rq->fence.flags); -+ spin_unlock(&b->irq_lock); -+ } -+ -+ return !__request_completed(rq); -+} -+ -+void i915_request_cancel_breadcrumb(struct i915_request *rq) -+{ -+ struct intel_breadcrumbs *b = &rq->engine->breadcrumbs; -+ -+ lockdep_assert_held(&rq->lock); -+ lockdep_assert_irqs_disabled(); -+ -+ /* -+ * We must wait for b->irq_lock so that we know the interrupt handler -+ * has released its reference to the intel_context and has completed -+ * the DMA_FENCE_FLAG_SIGNALED_BIT/I915_FENCE_FLAG_SIGNAL dance (if -+ * required). -+ */ -+ spin_lock(&b->irq_lock); -+ if (test_bit(I915_FENCE_FLAG_SIGNAL, &rq->fence.flags)) { -+ struct intel_context *ce = rq->hw_context; -+ -+ list_del(&rq->signal_link); -+ if (list_empty(&ce->signals)) -+ list_del_init(&ce->signal_link); -+ -+ clear_bit(I915_FENCE_FLAG_SIGNAL, &rq->fence.flags); -+ } -+ spin_unlock(&b->irq_lock); -+} -+ -+void intel_engine_print_breadcrumbs(struct intel_engine_cs *engine, -+ struct drm_printer *p) -+{ -+ struct intel_breadcrumbs *b = &engine->breadcrumbs; -+ struct intel_context *ce; -+ struct i915_request *rq; -+ -+ if (list_empty(&b->signalers)) -+ return; -+ -+ drm_printf(p, "Signals:\n"); -+ -+ spin_lock_irq(&b->irq_lock); -+ list_for_each_entry(ce, &b->signalers, signal_link) { -+ list_for_each_entry(rq, &ce->signals, signal_link) { -+ drm_printf(p, "\t[%llx:%llx%s] @ %dms\n", -+ rq->fence.context, rq->fence.seqno, -+ i915_request_completed(rq) ? "!" : -+ i915_request_started(rq) ? "*" : -+ "", -+ jiffies_to_msecs(jiffies - rq->emitted_jiffies)); -+ } -+ } -+ spin_unlock_irq(&b->irq_lock); -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_cdclk.c b/drivers/gpu/drm/i915_legacy/intel_cdclk.c -new file mode 100644 -index 000000000000..fd5236da039f ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_cdclk.c -@@ -0,0 +1,2904 @@ -+/* -+ * Copyright © 2006-2017 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#include "intel_cdclk.h" -+#include "intel_drv.h" -+ -+/** -+ * DOC: CDCLK / RAWCLK -+ * -+ * The display engine uses several different clocks to do its work. There -+ * are two main clocks involved that aren't directly related to the actual -+ * pixel clock or any symbol/bit clock of the actual output port. These -+ * are the core display clock (CDCLK) and RAWCLK. -+ * -+ * CDCLK clocks most of the display pipe logic, and thus its frequency -+ * must be high enough to support the rate at which pixels are flowing -+ * through the pipes. Downscaling must also be accounted as that increases -+ * the effective pixel rate. -+ * -+ * On several platforms the CDCLK frequency can be changed dynamically -+ * to minimize power consumption for a given display configuration. -+ * Typically changes to the CDCLK frequency require all the display pipes -+ * to be shut down while the frequency is being changed. -+ * -+ * On SKL+ the DMC will toggle the CDCLK off/on during DC5/6 entry/exit. -+ * DMC will not change the active CDCLK frequency however, so that part -+ * will still be performed by the driver directly. -+ * -+ * RAWCLK is a fixed frequency clock, often used by various auxiliary -+ * blocks such as AUX CH or backlight PWM. Hence the only thing we -+ * really need to know about RAWCLK is its frequency so that various -+ * dividers can be programmed correctly. -+ */ -+ -+static void fixed_133mhz_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ cdclk_state->cdclk = 133333; -+} -+ -+static void fixed_200mhz_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ cdclk_state->cdclk = 200000; -+} -+ -+static void fixed_266mhz_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ cdclk_state->cdclk = 266667; -+} -+ -+static void fixed_333mhz_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ cdclk_state->cdclk = 333333; -+} -+ -+static void fixed_400mhz_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ cdclk_state->cdclk = 400000; -+} -+ -+static void fixed_450mhz_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ cdclk_state->cdclk = 450000; -+} -+ -+static void i85x_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ u16 hpllcc = 0; -+ -+ /* -+ * 852GM/852GMV only supports 133 MHz and the HPLLCC -+ * encoding is different :( -+ * FIXME is this the right way to detect 852GM/852GMV? -+ */ -+ if (pdev->revision == 0x1) { -+ cdclk_state->cdclk = 133333; -+ return; -+ } -+ -+ pci_bus_read_config_word(pdev->bus, -+ PCI_DEVFN(0, 3), HPLLCC, &hpllcc); -+ -+ /* Assume that the hardware is in the high speed state. This -+ * should be the default. -+ */ -+ switch (hpllcc & GC_CLOCK_CONTROL_MASK) { -+ case GC_CLOCK_133_200: -+ case GC_CLOCK_133_200_2: -+ case GC_CLOCK_100_200: -+ cdclk_state->cdclk = 200000; -+ break; -+ case GC_CLOCK_166_250: -+ cdclk_state->cdclk = 250000; -+ break; -+ case GC_CLOCK_100_133: -+ cdclk_state->cdclk = 133333; -+ break; -+ case GC_CLOCK_133_266: -+ case GC_CLOCK_133_266_2: -+ case GC_CLOCK_166_266: -+ cdclk_state->cdclk = 266667; -+ break; -+ } -+} -+ -+static void i915gm_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ u16 gcfgc = 0; -+ -+ pci_read_config_word(pdev, GCFGC, &gcfgc); -+ -+ if (gcfgc & GC_LOW_FREQUENCY_ENABLE) { -+ cdclk_state->cdclk = 133333; -+ return; -+ } -+ -+ switch (gcfgc & GC_DISPLAY_CLOCK_MASK) { -+ case GC_DISPLAY_CLOCK_333_320_MHZ: -+ cdclk_state->cdclk = 333333; -+ break; -+ default: -+ case GC_DISPLAY_CLOCK_190_200_MHZ: -+ cdclk_state->cdclk = 190000; -+ break; -+ } -+} -+ -+static void i945gm_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ u16 gcfgc = 0; -+ -+ pci_read_config_word(pdev, GCFGC, &gcfgc); -+ -+ if (gcfgc & GC_LOW_FREQUENCY_ENABLE) { -+ cdclk_state->cdclk = 133333; -+ return; -+ } -+ -+ switch (gcfgc & GC_DISPLAY_CLOCK_MASK) { -+ case GC_DISPLAY_CLOCK_333_320_MHZ: -+ cdclk_state->cdclk = 320000; -+ break; -+ default: -+ case GC_DISPLAY_CLOCK_190_200_MHZ: -+ cdclk_state->cdclk = 200000; -+ break; -+ } -+} -+ -+static unsigned int intel_hpll_vco(struct drm_i915_private *dev_priv) -+{ -+ static const unsigned int blb_vco[8] = { -+ [0] = 3200000, -+ [1] = 4000000, -+ [2] = 5333333, -+ [3] = 4800000, -+ [4] = 6400000, -+ }; -+ static const unsigned int pnv_vco[8] = { -+ [0] = 3200000, -+ [1] = 4000000, -+ [2] = 5333333, -+ [3] = 4800000, -+ [4] = 2666667, -+ }; -+ static const unsigned int cl_vco[8] = { -+ [0] = 3200000, -+ [1] = 4000000, -+ [2] = 5333333, -+ [3] = 6400000, -+ [4] = 3333333, -+ [5] = 3566667, -+ [6] = 4266667, -+ }; -+ static const unsigned int elk_vco[8] = { -+ [0] = 3200000, -+ [1] = 4000000, -+ [2] = 5333333, -+ [3] = 4800000, -+ }; -+ static const unsigned int ctg_vco[8] = { -+ [0] = 3200000, -+ [1] = 4000000, -+ [2] = 5333333, -+ [3] = 6400000, -+ [4] = 2666667, -+ [5] = 4266667, -+ }; -+ const unsigned int *vco_table; -+ unsigned int vco; -+ u8 tmp = 0; -+ -+ /* FIXME other chipsets? */ -+ if (IS_GM45(dev_priv)) -+ vco_table = ctg_vco; -+ else if (IS_G45(dev_priv)) -+ vco_table = elk_vco; -+ else if (IS_I965GM(dev_priv)) -+ vco_table = cl_vco; -+ else if (IS_PINEVIEW(dev_priv)) -+ vco_table = pnv_vco; -+ else if (IS_G33(dev_priv)) -+ vco_table = blb_vco; -+ else -+ return 0; -+ -+ tmp = I915_READ(IS_PINEVIEW(dev_priv) || IS_MOBILE(dev_priv) ? -+ HPLLVCO_MOBILE : HPLLVCO); -+ -+ vco = vco_table[tmp & 0x7]; -+ if (vco == 0) -+ DRM_ERROR("Bad HPLL VCO (HPLLVCO=0x%02x)\n", tmp); -+ else -+ DRM_DEBUG_KMS("HPLL VCO %u kHz\n", vco); -+ -+ return vco; -+} -+ -+static void g33_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ static const u8 div_3200[] = { 12, 10, 8, 7, 5, 16 }; -+ static const u8 div_4000[] = { 14, 12, 10, 8, 6, 20 }; -+ static const u8 div_4800[] = { 20, 14, 12, 10, 8, 24 }; -+ static const u8 div_5333[] = { 20, 16, 12, 12, 8, 28 }; -+ const u8 *div_table; -+ unsigned int cdclk_sel; -+ u16 tmp = 0; -+ -+ cdclk_state->vco = intel_hpll_vco(dev_priv); -+ -+ pci_read_config_word(pdev, GCFGC, &tmp); -+ -+ cdclk_sel = (tmp >> 4) & 0x7; -+ -+ if (cdclk_sel >= ARRAY_SIZE(div_3200)) -+ goto fail; -+ -+ switch (cdclk_state->vco) { -+ case 3200000: -+ div_table = div_3200; -+ break; -+ case 4000000: -+ div_table = div_4000; -+ break; -+ case 4800000: -+ div_table = div_4800; -+ break; -+ case 5333333: -+ div_table = div_5333; -+ break; -+ default: -+ goto fail; -+ } -+ -+ cdclk_state->cdclk = DIV_ROUND_CLOSEST(cdclk_state->vco, -+ div_table[cdclk_sel]); -+ return; -+ -+fail: -+ DRM_ERROR("Unable to determine CDCLK. HPLL VCO=%u kHz, CFGC=0x%08x\n", -+ cdclk_state->vco, tmp); -+ cdclk_state->cdclk = 190476; -+} -+ -+static void pnv_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ u16 gcfgc = 0; -+ -+ pci_read_config_word(pdev, GCFGC, &gcfgc); -+ -+ switch (gcfgc & GC_DISPLAY_CLOCK_MASK) { -+ case GC_DISPLAY_CLOCK_267_MHZ_PNV: -+ cdclk_state->cdclk = 266667; -+ break; -+ case GC_DISPLAY_CLOCK_333_MHZ_PNV: -+ cdclk_state->cdclk = 333333; -+ break; -+ case GC_DISPLAY_CLOCK_444_MHZ_PNV: -+ cdclk_state->cdclk = 444444; -+ break; -+ case GC_DISPLAY_CLOCK_200_MHZ_PNV: -+ cdclk_state->cdclk = 200000; -+ break; -+ default: -+ DRM_ERROR("Unknown pnv display core clock 0x%04x\n", gcfgc); -+ /* fall through */ -+ case GC_DISPLAY_CLOCK_133_MHZ_PNV: -+ cdclk_state->cdclk = 133333; -+ break; -+ case GC_DISPLAY_CLOCK_167_MHZ_PNV: -+ cdclk_state->cdclk = 166667; -+ break; -+ } -+} -+ -+static void i965gm_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ static const u8 div_3200[] = { 16, 10, 8 }; -+ static const u8 div_4000[] = { 20, 12, 10 }; -+ static const u8 div_5333[] = { 24, 16, 14 }; -+ const u8 *div_table; -+ unsigned int cdclk_sel; -+ u16 tmp = 0; -+ -+ cdclk_state->vco = intel_hpll_vco(dev_priv); -+ -+ pci_read_config_word(pdev, GCFGC, &tmp); -+ -+ cdclk_sel = ((tmp >> 8) & 0x1f) - 1; -+ -+ if (cdclk_sel >= ARRAY_SIZE(div_3200)) -+ goto fail; -+ -+ switch (cdclk_state->vco) { -+ case 3200000: -+ div_table = div_3200; -+ break; -+ case 4000000: -+ div_table = div_4000; -+ break; -+ case 5333333: -+ div_table = div_5333; -+ break; -+ default: -+ goto fail; -+ } -+ -+ cdclk_state->cdclk = DIV_ROUND_CLOSEST(cdclk_state->vco, -+ div_table[cdclk_sel]); -+ return; -+ -+fail: -+ DRM_ERROR("Unable to determine CDCLK. HPLL VCO=%u kHz, CFGC=0x%04x\n", -+ cdclk_state->vco, tmp); -+ cdclk_state->cdclk = 200000; -+} -+ -+static void gm45_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ unsigned int cdclk_sel; -+ u16 tmp = 0; -+ -+ cdclk_state->vco = intel_hpll_vco(dev_priv); -+ -+ pci_read_config_word(pdev, GCFGC, &tmp); -+ -+ cdclk_sel = (tmp >> 12) & 0x1; -+ -+ switch (cdclk_state->vco) { -+ case 2666667: -+ case 4000000: -+ case 5333333: -+ cdclk_state->cdclk = cdclk_sel ? 333333 : 222222; -+ break; -+ case 3200000: -+ cdclk_state->cdclk = cdclk_sel ? 320000 : 228571; -+ break; -+ default: -+ DRM_ERROR("Unable to determine CDCLK. HPLL VCO=%u, CFGC=0x%04x\n", -+ cdclk_state->vco, tmp); -+ cdclk_state->cdclk = 222222; -+ break; -+ } -+} -+ -+static void hsw_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ u32 lcpll = I915_READ(LCPLL_CTL); -+ u32 freq = lcpll & LCPLL_CLK_FREQ_MASK; -+ -+ if (lcpll & LCPLL_CD_SOURCE_FCLK) -+ cdclk_state->cdclk = 800000; -+ else if (I915_READ(FUSE_STRAP) & HSW_CDCLK_LIMIT) -+ cdclk_state->cdclk = 450000; -+ else if (freq == LCPLL_CLK_FREQ_450) -+ cdclk_state->cdclk = 450000; -+ else if (IS_HSW_ULT(dev_priv)) -+ cdclk_state->cdclk = 337500; -+ else -+ cdclk_state->cdclk = 540000; -+} -+ -+static int vlv_calc_cdclk(struct drm_i915_private *dev_priv, int min_cdclk) -+{ -+ int freq_320 = (dev_priv->hpll_freq << 1) % 320000 != 0 ? -+ 333333 : 320000; -+ -+ /* -+ * We seem to get an unstable or solid color picture at 200MHz. -+ * Not sure what's wrong. For now use 200MHz only when all pipes -+ * are off. -+ */ -+ if (IS_VALLEYVIEW(dev_priv) && min_cdclk > freq_320) -+ return 400000; -+ else if (min_cdclk > 266667) -+ return freq_320; -+ else if (min_cdclk > 0) -+ return 266667; -+ else -+ return 200000; -+} -+ -+static u8 vlv_calc_voltage_level(struct drm_i915_private *dev_priv, int cdclk) -+{ -+ if (IS_VALLEYVIEW(dev_priv)) { -+ if (cdclk >= 320000) /* jump to highest voltage for 400MHz too */ -+ return 2; -+ else if (cdclk >= 266667) -+ return 1; -+ else -+ return 0; -+ } else { -+ /* -+ * Specs are full of misinformation, but testing on actual -+ * hardware has shown that we just need to write the desired -+ * CCK divider into the Punit register. -+ */ -+ return DIV_ROUND_CLOSEST(dev_priv->hpll_freq << 1, cdclk) - 1; -+ } -+} -+ -+static void vlv_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ u32 val; -+ -+ cdclk_state->vco = vlv_get_hpll_vco(dev_priv); -+ cdclk_state->cdclk = vlv_get_cck_clock(dev_priv, "cdclk", -+ CCK_DISPLAY_CLOCK_CONTROL, -+ cdclk_state->vco); -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ val = vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM); -+ mutex_unlock(&dev_priv->pcu_lock); -+ -+ if (IS_VALLEYVIEW(dev_priv)) -+ cdclk_state->voltage_level = (val & DSPFREQGUAR_MASK) >> -+ DSPFREQGUAR_SHIFT; -+ else -+ cdclk_state->voltage_level = (val & DSPFREQGUAR_MASK_CHV) >> -+ DSPFREQGUAR_SHIFT_CHV; -+} -+ -+static void vlv_program_pfi_credits(struct drm_i915_private *dev_priv) -+{ -+ unsigned int credits, default_credits; -+ -+ if (IS_CHERRYVIEW(dev_priv)) -+ default_credits = PFI_CREDIT(12); -+ else -+ default_credits = PFI_CREDIT(8); -+ -+ if (dev_priv->cdclk.hw.cdclk >= dev_priv->czclk_freq) { -+ /* CHV suggested value is 31 or 63 */ -+ if (IS_CHERRYVIEW(dev_priv)) -+ credits = PFI_CREDIT_63; -+ else -+ credits = PFI_CREDIT(15); -+ } else { -+ credits = default_credits; -+ } -+ -+ /* -+ * WA - write default credits before re-programming -+ * FIXME: should we also set the resend bit here? -+ */ -+ I915_WRITE(GCI_CONTROL, VGA_FAST_MODE_DISABLE | -+ default_credits); -+ -+ I915_WRITE(GCI_CONTROL, VGA_FAST_MODE_DISABLE | -+ credits | PFI_CREDIT_RESEND); -+ -+ /* -+ * FIXME is this guaranteed to clear -+ * immediately or should we poll for it? -+ */ -+ WARN_ON(I915_READ(GCI_CONTROL) & PFI_CREDIT_RESEND); -+} -+ -+static void vlv_set_cdclk(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *cdclk_state, -+ enum pipe pipe) -+{ -+ int cdclk = cdclk_state->cdclk; -+ u32 val, cmd = cdclk_state->voltage_level; -+ intel_wakeref_t wakeref; -+ -+ switch (cdclk) { -+ case 400000: -+ case 333333: -+ case 320000: -+ case 266667: -+ case 200000: -+ break; -+ default: -+ MISSING_CASE(cdclk); -+ return; -+ } -+ -+ /* There are cases where we can end up here with power domains -+ * off and a CDCLK frequency other than the minimum, like when -+ * issuing a modeset without actually changing any display after -+ * a system suspend. So grab the PIPE-A domain, which covers -+ * the HW blocks needed for the following programming. -+ */ -+ wakeref = intel_display_power_get(dev_priv, POWER_DOMAIN_PIPE_A); -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ val = vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM); -+ val &= ~DSPFREQGUAR_MASK; -+ val |= (cmd << DSPFREQGUAR_SHIFT); -+ vlv_punit_write(dev_priv, PUNIT_REG_DSPSSPM, val); -+ if (wait_for((vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM) & -+ DSPFREQSTAT_MASK) == (cmd << DSPFREQSTAT_SHIFT), -+ 50)) { -+ DRM_ERROR("timed out waiting for CDclk change\n"); -+ } -+ mutex_unlock(&dev_priv->pcu_lock); -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ if (cdclk == 400000) { -+ u32 divider; -+ -+ divider = DIV_ROUND_CLOSEST(dev_priv->hpll_freq << 1, -+ cdclk) - 1; -+ -+ /* adjust cdclk divider */ -+ val = vlv_cck_read(dev_priv, CCK_DISPLAY_CLOCK_CONTROL); -+ val &= ~CCK_FREQUENCY_VALUES; -+ val |= divider; -+ vlv_cck_write(dev_priv, CCK_DISPLAY_CLOCK_CONTROL, val); -+ -+ if (wait_for((vlv_cck_read(dev_priv, CCK_DISPLAY_CLOCK_CONTROL) & -+ CCK_FREQUENCY_STATUS) == (divider << CCK_FREQUENCY_STATUS_SHIFT), -+ 50)) -+ DRM_ERROR("timed out waiting for CDclk change\n"); -+ } -+ -+ /* adjust self-refresh exit latency value */ -+ val = vlv_bunit_read(dev_priv, BUNIT_REG_BISOC); -+ val &= ~0x7f; -+ -+ /* -+ * For high bandwidth configs, we set a higher latency in the bunit -+ * so that the core display fetch happens in time to avoid underruns. -+ */ -+ if (cdclk == 400000) -+ val |= 4500 / 250; /* 4.5 usec */ -+ else -+ val |= 3000 / 250; /* 3.0 usec */ -+ vlv_bunit_write(dev_priv, BUNIT_REG_BISOC, val); -+ -+ mutex_unlock(&dev_priv->sb_lock); -+ -+ intel_update_cdclk(dev_priv); -+ -+ vlv_program_pfi_credits(dev_priv); -+ -+ intel_display_power_put(dev_priv, POWER_DOMAIN_PIPE_A, wakeref); -+} -+ -+static void chv_set_cdclk(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *cdclk_state, -+ enum pipe pipe) -+{ -+ int cdclk = cdclk_state->cdclk; -+ u32 val, cmd = cdclk_state->voltage_level; -+ intel_wakeref_t wakeref; -+ -+ switch (cdclk) { -+ case 333333: -+ case 320000: -+ case 266667: -+ case 200000: -+ break; -+ default: -+ MISSING_CASE(cdclk); -+ return; -+ } -+ -+ /* There are cases where we can end up here with power domains -+ * off and a CDCLK frequency other than the minimum, like when -+ * issuing a modeset without actually changing any display after -+ * a system suspend. So grab the PIPE-A domain, which covers -+ * the HW blocks needed for the following programming. -+ */ -+ wakeref = intel_display_power_get(dev_priv, POWER_DOMAIN_PIPE_A); -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ val = vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM); -+ val &= ~DSPFREQGUAR_MASK_CHV; -+ val |= (cmd << DSPFREQGUAR_SHIFT_CHV); -+ vlv_punit_write(dev_priv, PUNIT_REG_DSPSSPM, val); -+ if (wait_for((vlv_punit_read(dev_priv, PUNIT_REG_DSPSSPM) & -+ DSPFREQSTAT_MASK_CHV) == (cmd << DSPFREQSTAT_SHIFT_CHV), -+ 50)) { -+ DRM_ERROR("timed out waiting for CDclk change\n"); -+ } -+ mutex_unlock(&dev_priv->pcu_lock); -+ -+ intel_update_cdclk(dev_priv); -+ -+ vlv_program_pfi_credits(dev_priv); -+ -+ intel_display_power_put(dev_priv, POWER_DOMAIN_PIPE_A, wakeref); -+} -+ -+static int bdw_calc_cdclk(int min_cdclk) -+{ -+ if (min_cdclk > 540000) -+ return 675000; -+ else if (min_cdclk > 450000) -+ return 540000; -+ else if (min_cdclk > 337500) -+ return 450000; -+ else -+ return 337500; -+} -+ -+static u8 bdw_calc_voltage_level(int cdclk) -+{ -+ switch (cdclk) { -+ default: -+ case 337500: -+ return 2; -+ case 450000: -+ return 0; -+ case 540000: -+ return 1; -+ case 675000: -+ return 3; -+ } -+} -+ -+static void bdw_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ u32 lcpll = I915_READ(LCPLL_CTL); -+ u32 freq = lcpll & LCPLL_CLK_FREQ_MASK; -+ -+ if (lcpll & LCPLL_CD_SOURCE_FCLK) -+ cdclk_state->cdclk = 800000; -+ else if (I915_READ(FUSE_STRAP) & HSW_CDCLK_LIMIT) -+ cdclk_state->cdclk = 450000; -+ else if (freq == LCPLL_CLK_FREQ_450) -+ cdclk_state->cdclk = 450000; -+ else if (freq == LCPLL_CLK_FREQ_54O_BDW) -+ cdclk_state->cdclk = 540000; -+ else if (freq == LCPLL_CLK_FREQ_337_5_BDW) -+ cdclk_state->cdclk = 337500; -+ else -+ cdclk_state->cdclk = 675000; -+ -+ /* -+ * Can't read this out :( Let's assume it's -+ * at least what the CDCLK frequency requires. -+ */ -+ cdclk_state->voltage_level = -+ bdw_calc_voltage_level(cdclk_state->cdclk); -+} -+ -+static void bdw_set_cdclk(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *cdclk_state, -+ enum pipe pipe) -+{ -+ int cdclk = cdclk_state->cdclk; -+ u32 val; -+ int ret; -+ -+ if (WARN((I915_READ(LCPLL_CTL) & -+ (LCPLL_PLL_DISABLE | LCPLL_PLL_LOCK | -+ LCPLL_CD_CLOCK_DISABLE | LCPLL_ROOT_CD_CLOCK_DISABLE | -+ LCPLL_CD2X_CLOCK_DISABLE | LCPLL_POWER_DOWN_ALLOW | -+ LCPLL_CD_SOURCE_FCLK)) != LCPLL_PLL_LOCK, -+ "trying to change cdclk frequency with cdclk not enabled\n")) -+ return; -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ ret = sandybridge_pcode_write(dev_priv, -+ BDW_PCODE_DISPLAY_FREQ_CHANGE_REQ, 0x0); -+ mutex_unlock(&dev_priv->pcu_lock); -+ if (ret) { -+ DRM_ERROR("failed to inform pcode about cdclk change\n"); -+ return; -+ } -+ -+ val = I915_READ(LCPLL_CTL); -+ val |= LCPLL_CD_SOURCE_FCLK; -+ I915_WRITE(LCPLL_CTL, val); -+ -+ /* -+ * According to the spec, it should be enough to poll for this 1 us. -+ * However, extensive testing shows that this can take longer. -+ */ -+ if (wait_for_us(I915_READ(LCPLL_CTL) & -+ LCPLL_CD_SOURCE_FCLK_DONE, 100)) -+ DRM_ERROR("Switching to FCLK failed\n"); -+ -+ val = I915_READ(LCPLL_CTL); -+ val &= ~LCPLL_CLK_FREQ_MASK; -+ -+ switch (cdclk) { -+ default: -+ MISSING_CASE(cdclk); -+ /* fall through */ -+ case 337500: -+ val |= LCPLL_CLK_FREQ_337_5_BDW; -+ break; -+ case 450000: -+ val |= LCPLL_CLK_FREQ_450; -+ break; -+ case 540000: -+ val |= LCPLL_CLK_FREQ_54O_BDW; -+ break; -+ case 675000: -+ val |= LCPLL_CLK_FREQ_675_BDW; -+ break; -+ } -+ -+ I915_WRITE(LCPLL_CTL, val); -+ -+ val = I915_READ(LCPLL_CTL); -+ val &= ~LCPLL_CD_SOURCE_FCLK; -+ I915_WRITE(LCPLL_CTL, val); -+ -+ if (wait_for_us((I915_READ(LCPLL_CTL) & -+ LCPLL_CD_SOURCE_FCLK_DONE) == 0, 1)) -+ DRM_ERROR("Switching back to LCPLL failed\n"); -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ sandybridge_pcode_write(dev_priv, HSW_PCODE_DE_WRITE_FREQ_REQ, -+ cdclk_state->voltage_level); -+ mutex_unlock(&dev_priv->pcu_lock); -+ -+ I915_WRITE(CDCLK_FREQ, DIV_ROUND_CLOSEST(cdclk, 1000) - 1); -+ -+ intel_update_cdclk(dev_priv); -+} -+ -+static int skl_calc_cdclk(int min_cdclk, int vco) -+{ -+ if (vco == 8640000) { -+ if (min_cdclk > 540000) -+ return 617143; -+ else if (min_cdclk > 432000) -+ return 540000; -+ else if (min_cdclk > 308571) -+ return 432000; -+ else -+ return 308571; -+ } else { -+ if (min_cdclk > 540000) -+ return 675000; -+ else if (min_cdclk > 450000) -+ return 540000; -+ else if (min_cdclk > 337500) -+ return 450000; -+ else -+ return 337500; -+ } -+} -+ -+static u8 skl_calc_voltage_level(int cdclk) -+{ -+ switch (cdclk) { -+ default: -+ case 308571: -+ case 337500: -+ return 0; -+ case 450000: -+ case 432000: -+ return 1; -+ case 540000: -+ return 2; -+ case 617143: -+ case 675000: -+ return 3; -+ } -+} -+ -+static void skl_dpll0_update(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ u32 val; -+ -+ cdclk_state->ref = 24000; -+ cdclk_state->vco = 0; -+ -+ val = I915_READ(LCPLL1_CTL); -+ if ((val & LCPLL_PLL_ENABLE) == 0) -+ return; -+ -+ if (WARN_ON((val & LCPLL_PLL_LOCK) == 0)) -+ return; -+ -+ val = I915_READ(DPLL_CTRL1); -+ -+ if (WARN_ON((val & (DPLL_CTRL1_HDMI_MODE(SKL_DPLL0) | -+ DPLL_CTRL1_SSC(SKL_DPLL0) | -+ DPLL_CTRL1_OVERRIDE(SKL_DPLL0))) != -+ DPLL_CTRL1_OVERRIDE(SKL_DPLL0))) -+ return; -+ -+ switch (val & DPLL_CTRL1_LINK_RATE_MASK(SKL_DPLL0)) { -+ case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_810, SKL_DPLL0): -+ case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1350, SKL_DPLL0): -+ case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1620, SKL_DPLL0): -+ case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_2700, SKL_DPLL0): -+ cdclk_state->vco = 8100000; -+ break; -+ case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1080, SKL_DPLL0): -+ case DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_2160, SKL_DPLL0): -+ cdclk_state->vco = 8640000; -+ break; -+ default: -+ MISSING_CASE(val & DPLL_CTRL1_LINK_RATE_MASK(SKL_DPLL0)); -+ break; -+ } -+} -+ -+static void skl_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ u32 cdctl; -+ -+ skl_dpll0_update(dev_priv, cdclk_state); -+ -+ cdclk_state->cdclk = cdclk_state->bypass = cdclk_state->ref; -+ -+ if (cdclk_state->vco == 0) -+ goto out; -+ -+ cdctl = I915_READ(CDCLK_CTL); -+ -+ if (cdclk_state->vco == 8640000) { -+ switch (cdctl & CDCLK_FREQ_SEL_MASK) { -+ case CDCLK_FREQ_450_432: -+ cdclk_state->cdclk = 432000; -+ break; -+ case CDCLK_FREQ_337_308: -+ cdclk_state->cdclk = 308571; -+ break; -+ case CDCLK_FREQ_540: -+ cdclk_state->cdclk = 540000; -+ break; -+ case CDCLK_FREQ_675_617: -+ cdclk_state->cdclk = 617143; -+ break; -+ default: -+ MISSING_CASE(cdctl & CDCLK_FREQ_SEL_MASK); -+ break; -+ } -+ } else { -+ switch (cdctl & CDCLK_FREQ_SEL_MASK) { -+ case CDCLK_FREQ_450_432: -+ cdclk_state->cdclk = 450000; -+ break; -+ case CDCLK_FREQ_337_308: -+ cdclk_state->cdclk = 337500; -+ break; -+ case CDCLK_FREQ_540: -+ cdclk_state->cdclk = 540000; -+ break; -+ case CDCLK_FREQ_675_617: -+ cdclk_state->cdclk = 675000; -+ break; -+ default: -+ MISSING_CASE(cdctl & CDCLK_FREQ_SEL_MASK); -+ break; -+ } -+ } -+ -+ out: -+ /* -+ * Can't read this out :( Let's assume it's -+ * at least what the CDCLK frequency requires. -+ */ -+ cdclk_state->voltage_level = -+ skl_calc_voltage_level(cdclk_state->cdclk); -+} -+ -+/* convert from kHz to .1 fixpoint MHz with -1MHz offset */ -+static int skl_cdclk_decimal(int cdclk) -+{ -+ return DIV_ROUND_CLOSEST(cdclk - 1000, 500); -+} -+ -+static void skl_set_preferred_cdclk_vco(struct drm_i915_private *dev_priv, -+ int vco) -+{ -+ bool changed = dev_priv->skl_preferred_vco_freq != vco; -+ -+ dev_priv->skl_preferred_vco_freq = vco; -+ -+ if (changed) -+ intel_update_max_cdclk(dev_priv); -+} -+ -+static void skl_dpll0_enable(struct drm_i915_private *dev_priv, int vco) -+{ -+ u32 val; -+ -+ WARN_ON(vco != 8100000 && vco != 8640000); -+ -+ /* -+ * We always enable DPLL0 with the lowest link rate possible, but still -+ * taking into account the VCO required to operate the eDP panel at the -+ * desired frequency. The usual DP link rates operate with a VCO of -+ * 8100 while the eDP 1.4 alternate link rates need a VCO of 8640. -+ * The modeset code is responsible for the selection of the exact link -+ * rate later on, with the constraint of choosing a frequency that -+ * works with vco. -+ */ -+ val = I915_READ(DPLL_CTRL1); -+ -+ val &= ~(DPLL_CTRL1_HDMI_MODE(SKL_DPLL0) | DPLL_CTRL1_SSC(SKL_DPLL0) | -+ DPLL_CTRL1_LINK_RATE_MASK(SKL_DPLL0)); -+ val |= DPLL_CTRL1_OVERRIDE(SKL_DPLL0); -+ if (vco == 8640000) -+ val |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1080, -+ SKL_DPLL0); -+ else -+ val |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_810, -+ SKL_DPLL0); -+ -+ I915_WRITE(DPLL_CTRL1, val); -+ POSTING_READ(DPLL_CTRL1); -+ -+ I915_WRITE(LCPLL1_CTL, I915_READ(LCPLL1_CTL) | LCPLL_PLL_ENABLE); -+ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ LCPLL1_CTL, LCPLL_PLL_LOCK, LCPLL_PLL_LOCK, -+ 5)) -+ DRM_ERROR("DPLL0 not locked\n"); -+ -+ dev_priv->cdclk.hw.vco = vco; -+ -+ /* We'll want to keep using the current vco from now on. */ -+ skl_set_preferred_cdclk_vco(dev_priv, vco); -+} -+ -+static void skl_dpll0_disable(struct drm_i915_private *dev_priv) -+{ -+ I915_WRITE(LCPLL1_CTL, I915_READ(LCPLL1_CTL) & ~LCPLL_PLL_ENABLE); -+ if (intel_wait_for_register(&dev_priv->uncore, -+ LCPLL1_CTL, LCPLL_PLL_LOCK, 0, -+ 1)) -+ DRM_ERROR("Couldn't disable DPLL0\n"); -+ -+ dev_priv->cdclk.hw.vco = 0; -+} -+ -+static void skl_set_cdclk(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *cdclk_state, -+ enum pipe pipe) -+{ -+ int cdclk = cdclk_state->cdclk; -+ int vco = cdclk_state->vco; -+ u32 freq_select, cdclk_ctl; -+ int ret; -+ -+ /* -+ * Based on WA#1183 CDCLK rates 308 and 617MHz CDCLK rates are -+ * unsupported on SKL. In theory this should never happen since only -+ * the eDP1.4 2.16 and 4.32Gbps rates require it, but eDP1.4 is not -+ * supported on SKL either, see the above WA. WARN whenever trying to -+ * use the corresponding VCO freq as that always leads to using the -+ * minimum 308MHz CDCLK. -+ */ -+ WARN_ON_ONCE(IS_SKYLAKE(dev_priv) && vco == 8640000); -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ ret = skl_pcode_request(dev_priv, SKL_PCODE_CDCLK_CONTROL, -+ SKL_CDCLK_PREPARE_FOR_CHANGE, -+ SKL_CDCLK_READY_FOR_CHANGE, -+ SKL_CDCLK_READY_FOR_CHANGE, 3); -+ mutex_unlock(&dev_priv->pcu_lock); -+ if (ret) { -+ DRM_ERROR("Failed to inform PCU about cdclk change (%d)\n", -+ ret); -+ return; -+ } -+ -+ /* Choose frequency for this cdclk */ -+ switch (cdclk) { -+ default: -+ WARN_ON(cdclk != dev_priv->cdclk.hw.bypass); -+ WARN_ON(vco != 0); -+ /* fall through */ -+ case 308571: -+ case 337500: -+ freq_select = CDCLK_FREQ_337_308; -+ break; -+ case 450000: -+ case 432000: -+ freq_select = CDCLK_FREQ_450_432; -+ break; -+ case 540000: -+ freq_select = CDCLK_FREQ_540; -+ break; -+ case 617143: -+ case 675000: -+ freq_select = CDCLK_FREQ_675_617; -+ break; -+ } -+ -+ if (dev_priv->cdclk.hw.vco != 0 && -+ dev_priv->cdclk.hw.vco != vco) -+ skl_dpll0_disable(dev_priv); -+ -+ cdclk_ctl = I915_READ(CDCLK_CTL); -+ -+ if (dev_priv->cdclk.hw.vco != vco) { -+ /* Wa Display #1183: skl,kbl,cfl */ -+ cdclk_ctl &= ~(CDCLK_FREQ_SEL_MASK | CDCLK_FREQ_DECIMAL_MASK); -+ cdclk_ctl |= freq_select | skl_cdclk_decimal(cdclk); -+ I915_WRITE(CDCLK_CTL, cdclk_ctl); -+ } -+ -+ /* Wa Display #1183: skl,kbl,cfl */ -+ cdclk_ctl |= CDCLK_DIVMUX_CD_OVERRIDE; -+ I915_WRITE(CDCLK_CTL, cdclk_ctl); -+ POSTING_READ(CDCLK_CTL); -+ -+ if (dev_priv->cdclk.hw.vco != vco) -+ skl_dpll0_enable(dev_priv, vco); -+ -+ /* Wa Display #1183: skl,kbl,cfl */ -+ cdclk_ctl &= ~(CDCLK_FREQ_SEL_MASK | CDCLK_FREQ_DECIMAL_MASK); -+ I915_WRITE(CDCLK_CTL, cdclk_ctl); -+ -+ cdclk_ctl |= freq_select | skl_cdclk_decimal(cdclk); -+ I915_WRITE(CDCLK_CTL, cdclk_ctl); -+ -+ /* Wa Display #1183: skl,kbl,cfl */ -+ cdclk_ctl &= ~CDCLK_DIVMUX_CD_OVERRIDE; -+ I915_WRITE(CDCLK_CTL, cdclk_ctl); -+ POSTING_READ(CDCLK_CTL); -+ -+ /* inform PCU of the change */ -+ mutex_lock(&dev_priv->pcu_lock); -+ sandybridge_pcode_write(dev_priv, SKL_PCODE_CDCLK_CONTROL, -+ cdclk_state->voltage_level); -+ mutex_unlock(&dev_priv->pcu_lock); -+ -+ intel_update_cdclk(dev_priv); -+} -+ -+static void skl_sanitize_cdclk(struct drm_i915_private *dev_priv) -+{ -+ u32 cdctl, expected; -+ -+ /* -+ * check if the pre-os initialized the display -+ * There is SWF18 scratchpad register defined which is set by the -+ * pre-os which can be used by the OS drivers to check the status -+ */ -+ if ((I915_READ(SWF_ILK(0x18)) & 0x00FFFFFF) == 0) -+ goto sanitize; -+ -+ intel_update_cdclk(dev_priv); -+ intel_dump_cdclk_state(&dev_priv->cdclk.hw, "Current CDCLK"); -+ -+ /* Is PLL enabled and locked ? */ -+ if (dev_priv->cdclk.hw.vco == 0 || -+ dev_priv->cdclk.hw.cdclk == dev_priv->cdclk.hw.bypass) -+ goto sanitize; -+ -+ /* DPLL okay; verify the cdclock -+ * -+ * Noticed in some instances that the freq selection is correct but -+ * decimal part is programmed wrong from BIOS where pre-os does not -+ * enable display. Verify the same as well. -+ */ -+ cdctl = I915_READ(CDCLK_CTL); -+ expected = (cdctl & CDCLK_FREQ_SEL_MASK) | -+ skl_cdclk_decimal(dev_priv->cdclk.hw.cdclk); -+ if (cdctl == expected) -+ /* All well; nothing to sanitize */ -+ return; -+ -+sanitize: -+ DRM_DEBUG_KMS("Sanitizing cdclk programmed by pre-os\n"); -+ -+ /* force cdclk programming */ -+ dev_priv->cdclk.hw.cdclk = 0; -+ /* force full PLL disable + enable */ -+ dev_priv->cdclk.hw.vco = -1; -+} -+ -+static void skl_init_cdclk(struct drm_i915_private *dev_priv) -+{ -+ struct intel_cdclk_state cdclk_state; -+ -+ skl_sanitize_cdclk(dev_priv); -+ -+ if (dev_priv->cdclk.hw.cdclk != 0 && -+ dev_priv->cdclk.hw.vco != 0) { -+ /* -+ * Use the current vco as our initial -+ * guess as to what the preferred vco is. -+ */ -+ if (dev_priv->skl_preferred_vco_freq == 0) -+ skl_set_preferred_cdclk_vco(dev_priv, -+ dev_priv->cdclk.hw.vco); -+ return; -+ } -+ -+ cdclk_state = dev_priv->cdclk.hw; -+ -+ cdclk_state.vco = dev_priv->skl_preferred_vco_freq; -+ if (cdclk_state.vco == 0) -+ cdclk_state.vco = 8100000; -+ cdclk_state.cdclk = skl_calc_cdclk(0, cdclk_state.vco); -+ cdclk_state.voltage_level = skl_calc_voltage_level(cdclk_state.cdclk); -+ -+ skl_set_cdclk(dev_priv, &cdclk_state, INVALID_PIPE); -+} -+ -+static void skl_uninit_cdclk(struct drm_i915_private *dev_priv) -+{ -+ struct intel_cdclk_state cdclk_state = dev_priv->cdclk.hw; -+ -+ cdclk_state.cdclk = cdclk_state.bypass; -+ cdclk_state.vco = 0; -+ cdclk_state.voltage_level = skl_calc_voltage_level(cdclk_state.cdclk); -+ -+ skl_set_cdclk(dev_priv, &cdclk_state, INVALID_PIPE); -+} -+ -+static int bxt_calc_cdclk(int min_cdclk) -+{ -+ if (min_cdclk > 576000) -+ return 624000; -+ else if (min_cdclk > 384000) -+ return 576000; -+ else if (min_cdclk > 288000) -+ return 384000; -+ else if (min_cdclk > 144000) -+ return 288000; -+ else -+ return 144000; -+} -+ -+static int glk_calc_cdclk(int min_cdclk) -+{ -+ if (min_cdclk > 158400) -+ return 316800; -+ else if (min_cdclk > 79200) -+ return 158400; -+ else -+ return 79200; -+} -+ -+static u8 bxt_calc_voltage_level(int cdclk) -+{ -+ return DIV_ROUND_UP(cdclk, 25000); -+} -+ -+static int bxt_de_pll_vco(struct drm_i915_private *dev_priv, int cdclk) -+{ -+ int ratio; -+ -+ if (cdclk == dev_priv->cdclk.hw.bypass) -+ return 0; -+ -+ switch (cdclk) { -+ default: -+ MISSING_CASE(cdclk); -+ /* fall through */ -+ case 144000: -+ case 288000: -+ case 384000: -+ case 576000: -+ ratio = 60; -+ break; -+ case 624000: -+ ratio = 65; -+ break; -+ } -+ -+ return dev_priv->cdclk.hw.ref * ratio; -+} -+ -+static int glk_de_pll_vco(struct drm_i915_private *dev_priv, int cdclk) -+{ -+ int ratio; -+ -+ if (cdclk == dev_priv->cdclk.hw.bypass) -+ return 0; -+ -+ switch (cdclk) { -+ default: -+ MISSING_CASE(cdclk); -+ /* fall through */ -+ case 79200: -+ case 158400: -+ case 316800: -+ ratio = 33; -+ break; -+ } -+ -+ return dev_priv->cdclk.hw.ref * ratio; -+} -+ -+static void bxt_de_pll_update(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ u32 val; -+ -+ cdclk_state->ref = 19200; -+ cdclk_state->vco = 0; -+ -+ val = I915_READ(BXT_DE_PLL_ENABLE); -+ if ((val & BXT_DE_PLL_PLL_ENABLE) == 0) -+ return; -+ -+ if (WARN_ON((val & BXT_DE_PLL_LOCK) == 0)) -+ return; -+ -+ val = I915_READ(BXT_DE_PLL_CTL); -+ cdclk_state->vco = (val & BXT_DE_PLL_RATIO_MASK) * cdclk_state->ref; -+} -+ -+static void bxt_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ u32 divider; -+ int div; -+ -+ bxt_de_pll_update(dev_priv, cdclk_state); -+ -+ cdclk_state->cdclk = cdclk_state->bypass = cdclk_state->ref; -+ -+ if (cdclk_state->vco == 0) -+ goto out; -+ -+ divider = I915_READ(CDCLK_CTL) & BXT_CDCLK_CD2X_DIV_SEL_MASK; -+ -+ switch (divider) { -+ case BXT_CDCLK_CD2X_DIV_SEL_1: -+ div = 2; -+ break; -+ case BXT_CDCLK_CD2X_DIV_SEL_1_5: -+ WARN(IS_GEMINILAKE(dev_priv), "Unsupported divider\n"); -+ div = 3; -+ break; -+ case BXT_CDCLK_CD2X_DIV_SEL_2: -+ div = 4; -+ break; -+ case BXT_CDCLK_CD2X_DIV_SEL_4: -+ div = 8; -+ break; -+ default: -+ MISSING_CASE(divider); -+ return; -+ } -+ -+ cdclk_state->cdclk = DIV_ROUND_CLOSEST(cdclk_state->vco, div); -+ -+ out: -+ /* -+ * Can't read this out :( Let's assume it's -+ * at least what the CDCLK frequency requires. -+ */ -+ cdclk_state->voltage_level = -+ bxt_calc_voltage_level(cdclk_state->cdclk); -+} -+ -+static void bxt_de_pll_disable(struct drm_i915_private *dev_priv) -+{ -+ I915_WRITE(BXT_DE_PLL_ENABLE, 0); -+ -+ /* Timeout 200us */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ BXT_DE_PLL_ENABLE, BXT_DE_PLL_LOCK, 0, -+ 1)) -+ DRM_ERROR("timeout waiting for DE PLL unlock\n"); -+ -+ dev_priv->cdclk.hw.vco = 0; -+} -+ -+static void bxt_de_pll_enable(struct drm_i915_private *dev_priv, int vco) -+{ -+ int ratio = DIV_ROUND_CLOSEST(vco, dev_priv->cdclk.hw.ref); -+ u32 val; -+ -+ val = I915_READ(BXT_DE_PLL_CTL); -+ val &= ~BXT_DE_PLL_RATIO_MASK; -+ val |= BXT_DE_PLL_RATIO(ratio); -+ I915_WRITE(BXT_DE_PLL_CTL, val); -+ -+ I915_WRITE(BXT_DE_PLL_ENABLE, BXT_DE_PLL_PLL_ENABLE); -+ -+ /* Timeout 200us */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ BXT_DE_PLL_ENABLE, -+ BXT_DE_PLL_LOCK, -+ BXT_DE_PLL_LOCK, -+ 1)) -+ DRM_ERROR("timeout waiting for DE PLL lock\n"); -+ -+ dev_priv->cdclk.hw.vco = vco; -+} -+ -+static void bxt_set_cdclk(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *cdclk_state, -+ enum pipe pipe) -+{ -+ int cdclk = cdclk_state->cdclk; -+ int vco = cdclk_state->vco; -+ u32 val, divider; -+ int ret; -+ -+ /* cdclk = vco / 2 / div{1,1.5,2,4} */ -+ switch (DIV_ROUND_CLOSEST(vco, cdclk)) { -+ default: -+ WARN_ON(cdclk != dev_priv->cdclk.hw.bypass); -+ WARN_ON(vco != 0); -+ /* fall through */ -+ case 2: -+ divider = BXT_CDCLK_CD2X_DIV_SEL_1; -+ break; -+ case 3: -+ WARN(IS_GEMINILAKE(dev_priv), "Unsupported divider\n"); -+ divider = BXT_CDCLK_CD2X_DIV_SEL_1_5; -+ break; -+ case 4: -+ divider = BXT_CDCLK_CD2X_DIV_SEL_2; -+ break; -+ case 8: -+ divider = BXT_CDCLK_CD2X_DIV_SEL_4; -+ break; -+ } -+ -+ /* -+ * Inform power controller of upcoming frequency change. BSpec -+ * requires us to wait up to 150usec, but that leads to timeouts; -+ * the 2ms used here is based on experiment. -+ */ -+ mutex_lock(&dev_priv->pcu_lock); -+ ret = sandybridge_pcode_write_timeout(dev_priv, -+ HSW_PCODE_DE_WRITE_FREQ_REQ, -+ 0x80000000, 150, 2); -+ mutex_unlock(&dev_priv->pcu_lock); -+ -+ if (ret) { -+ DRM_ERROR("PCode CDCLK freq change notify failed (err %d, freq %d)\n", -+ ret, cdclk); -+ return; -+ } -+ -+ if (dev_priv->cdclk.hw.vco != 0 && -+ dev_priv->cdclk.hw.vco != vco) -+ bxt_de_pll_disable(dev_priv); -+ -+ if (dev_priv->cdclk.hw.vco != vco) -+ bxt_de_pll_enable(dev_priv, vco); -+ -+ val = divider | skl_cdclk_decimal(cdclk); -+ if (pipe == INVALID_PIPE) -+ val |= BXT_CDCLK_CD2X_PIPE_NONE; -+ else -+ val |= BXT_CDCLK_CD2X_PIPE(pipe); -+ /* -+ * Disable SSA Precharge when CD clock frequency < 500 MHz, -+ * enable otherwise. -+ */ -+ if (cdclk >= 500000) -+ val |= BXT_CDCLK_SSA_PRECHARGE_ENABLE; -+ I915_WRITE(CDCLK_CTL, val); -+ -+ if (pipe != INVALID_PIPE) -+ intel_wait_for_vblank(dev_priv, pipe); -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ /* -+ * The timeout isn't specified, the 2ms used here is based on -+ * experiment. -+ * FIXME: Waiting for the request completion could be delayed until -+ * the next PCODE request based on BSpec. -+ */ -+ ret = sandybridge_pcode_write_timeout(dev_priv, -+ HSW_PCODE_DE_WRITE_FREQ_REQ, -+ cdclk_state->voltage_level, 150, 2); -+ mutex_unlock(&dev_priv->pcu_lock); -+ -+ if (ret) { -+ DRM_ERROR("PCode CDCLK freq set failed, (err %d, freq %d)\n", -+ ret, cdclk); -+ return; -+ } -+ -+ intel_update_cdclk(dev_priv); -+} -+ -+static void bxt_sanitize_cdclk(struct drm_i915_private *dev_priv) -+{ -+ u32 cdctl, expected; -+ -+ intel_update_cdclk(dev_priv); -+ intel_dump_cdclk_state(&dev_priv->cdclk.hw, "Current CDCLK"); -+ -+ if (dev_priv->cdclk.hw.vco == 0 || -+ dev_priv->cdclk.hw.cdclk == dev_priv->cdclk.hw.bypass) -+ goto sanitize; -+ -+ /* DPLL okay; verify the cdclock -+ * -+ * Some BIOS versions leave an incorrect decimal frequency value and -+ * set reserved MBZ bits in CDCLK_CTL at least during exiting from S4, -+ * so sanitize this register. -+ */ -+ cdctl = I915_READ(CDCLK_CTL); -+ /* -+ * Let's ignore the pipe field, since BIOS could have configured the -+ * dividers both synching to an active pipe, or asynchronously -+ * (PIPE_NONE). -+ */ -+ cdctl &= ~BXT_CDCLK_CD2X_PIPE_NONE; -+ -+ expected = (cdctl & BXT_CDCLK_CD2X_DIV_SEL_MASK) | -+ skl_cdclk_decimal(dev_priv->cdclk.hw.cdclk); -+ /* -+ * Disable SSA Precharge when CD clock frequency < 500 MHz, -+ * enable otherwise. -+ */ -+ if (dev_priv->cdclk.hw.cdclk >= 500000) -+ expected |= BXT_CDCLK_SSA_PRECHARGE_ENABLE; -+ -+ if (cdctl == expected) -+ /* All well; nothing to sanitize */ -+ return; -+ -+sanitize: -+ DRM_DEBUG_KMS("Sanitizing cdclk programmed by pre-os\n"); -+ -+ /* force cdclk programming */ -+ dev_priv->cdclk.hw.cdclk = 0; -+ -+ /* force full PLL disable + enable */ -+ dev_priv->cdclk.hw.vco = -1; -+} -+ -+static void bxt_init_cdclk(struct drm_i915_private *dev_priv) -+{ -+ struct intel_cdclk_state cdclk_state; -+ -+ bxt_sanitize_cdclk(dev_priv); -+ -+ if (dev_priv->cdclk.hw.cdclk != 0 && -+ dev_priv->cdclk.hw.vco != 0) -+ return; -+ -+ cdclk_state = dev_priv->cdclk.hw; -+ -+ /* -+ * FIXME: -+ * - The initial CDCLK needs to be read from VBT. -+ * Need to make this change after VBT has changes for BXT. -+ */ -+ if (IS_GEMINILAKE(dev_priv)) { -+ cdclk_state.cdclk = glk_calc_cdclk(0); -+ cdclk_state.vco = glk_de_pll_vco(dev_priv, cdclk_state.cdclk); -+ } else { -+ cdclk_state.cdclk = bxt_calc_cdclk(0); -+ cdclk_state.vco = bxt_de_pll_vco(dev_priv, cdclk_state.cdclk); -+ } -+ cdclk_state.voltage_level = bxt_calc_voltage_level(cdclk_state.cdclk); -+ -+ bxt_set_cdclk(dev_priv, &cdclk_state, INVALID_PIPE); -+} -+ -+static void bxt_uninit_cdclk(struct drm_i915_private *dev_priv) -+{ -+ struct intel_cdclk_state cdclk_state = dev_priv->cdclk.hw; -+ -+ cdclk_state.cdclk = cdclk_state.bypass; -+ cdclk_state.vco = 0; -+ cdclk_state.voltage_level = bxt_calc_voltage_level(cdclk_state.cdclk); -+ -+ bxt_set_cdclk(dev_priv, &cdclk_state, INVALID_PIPE); -+} -+ -+static int cnl_calc_cdclk(int min_cdclk) -+{ -+ if (min_cdclk > 336000) -+ return 528000; -+ else if (min_cdclk > 168000) -+ return 336000; -+ else -+ return 168000; -+} -+ -+static u8 cnl_calc_voltage_level(int cdclk) -+{ -+ switch (cdclk) { -+ default: -+ case 168000: -+ return 0; -+ case 336000: -+ return 1; -+ case 528000: -+ return 2; -+ } -+} -+ -+static void cnl_cdclk_pll_update(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ u32 val; -+ -+ if (I915_READ(SKL_DSSM) & CNL_DSSM_CDCLK_PLL_REFCLK_24MHz) -+ cdclk_state->ref = 24000; -+ else -+ cdclk_state->ref = 19200; -+ -+ cdclk_state->vco = 0; -+ -+ val = I915_READ(BXT_DE_PLL_ENABLE); -+ if ((val & BXT_DE_PLL_PLL_ENABLE) == 0) -+ return; -+ -+ if (WARN_ON((val & BXT_DE_PLL_LOCK) == 0)) -+ return; -+ -+ cdclk_state->vco = (val & CNL_CDCLK_PLL_RATIO_MASK) * cdclk_state->ref; -+} -+ -+static void cnl_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ u32 divider; -+ int div; -+ -+ cnl_cdclk_pll_update(dev_priv, cdclk_state); -+ -+ cdclk_state->cdclk = cdclk_state->bypass = cdclk_state->ref; -+ -+ if (cdclk_state->vco == 0) -+ goto out; -+ -+ divider = I915_READ(CDCLK_CTL) & BXT_CDCLK_CD2X_DIV_SEL_MASK; -+ -+ switch (divider) { -+ case BXT_CDCLK_CD2X_DIV_SEL_1: -+ div = 2; -+ break; -+ case BXT_CDCLK_CD2X_DIV_SEL_2: -+ div = 4; -+ break; -+ default: -+ MISSING_CASE(divider); -+ return; -+ } -+ -+ cdclk_state->cdclk = DIV_ROUND_CLOSEST(cdclk_state->vco, div); -+ -+ out: -+ /* -+ * Can't read this out :( Let's assume it's -+ * at least what the CDCLK frequency requires. -+ */ -+ cdclk_state->voltage_level = -+ cnl_calc_voltage_level(cdclk_state->cdclk); -+} -+ -+static void cnl_cdclk_pll_disable(struct drm_i915_private *dev_priv) -+{ -+ u32 val; -+ -+ val = I915_READ(BXT_DE_PLL_ENABLE); -+ val &= ~BXT_DE_PLL_PLL_ENABLE; -+ I915_WRITE(BXT_DE_PLL_ENABLE, val); -+ -+ /* Timeout 200us */ -+ if (wait_for((I915_READ(BXT_DE_PLL_ENABLE) & BXT_DE_PLL_LOCK) == 0, 1)) -+ DRM_ERROR("timeout waiting for CDCLK PLL unlock\n"); -+ -+ dev_priv->cdclk.hw.vco = 0; -+} -+ -+static void cnl_cdclk_pll_enable(struct drm_i915_private *dev_priv, int vco) -+{ -+ int ratio = DIV_ROUND_CLOSEST(vco, dev_priv->cdclk.hw.ref); -+ u32 val; -+ -+ val = CNL_CDCLK_PLL_RATIO(ratio); -+ I915_WRITE(BXT_DE_PLL_ENABLE, val); -+ -+ val |= BXT_DE_PLL_PLL_ENABLE; -+ I915_WRITE(BXT_DE_PLL_ENABLE, val); -+ -+ /* Timeout 200us */ -+ if (wait_for((I915_READ(BXT_DE_PLL_ENABLE) & BXT_DE_PLL_LOCK) != 0, 1)) -+ DRM_ERROR("timeout waiting for CDCLK PLL lock\n"); -+ -+ dev_priv->cdclk.hw.vco = vco; -+} -+ -+static void cnl_set_cdclk(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *cdclk_state, -+ enum pipe pipe) -+{ -+ int cdclk = cdclk_state->cdclk; -+ int vco = cdclk_state->vco; -+ u32 val, divider; -+ int ret; -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ ret = skl_pcode_request(dev_priv, SKL_PCODE_CDCLK_CONTROL, -+ SKL_CDCLK_PREPARE_FOR_CHANGE, -+ SKL_CDCLK_READY_FOR_CHANGE, -+ SKL_CDCLK_READY_FOR_CHANGE, 3); -+ mutex_unlock(&dev_priv->pcu_lock); -+ if (ret) { -+ DRM_ERROR("Failed to inform PCU about cdclk change (%d)\n", -+ ret); -+ return; -+ } -+ -+ /* cdclk = vco / 2 / div{1,2} */ -+ switch (DIV_ROUND_CLOSEST(vco, cdclk)) { -+ default: -+ WARN_ON(cdclk != dev_priv->cdclk.hw.bypass); -+ WARN_ON(vco != 0); -+ /* fall through */ -+ case 2: -+ divider = BXT_CDCLK_CD2X_DIV_SEL_1; -+ break; -+ case 4: -+ divider = BXT_CDCLK_CD2X_DIV_SEL_2; -+ break; -+ } -+ -+ if (dev_priv->cdclk.hw.vco != 0 && -+ dev_priv->cdclk.hw.vco != vco) -+ cnl_cdclk_pll_disable(dev_priv); -+ -+ if (dev_priv->cdclk.hw.vco != vco) -+ cnl_cdclk_pll_enable(dev_priv, vco); -+ -+ val = divider | skl_cdclk_decimal(cdclk); -+ if (pipe == INVALID_PIPE) -+ val |= BXT_CDCLK_CD2X_PIPE_NONE; -+ else -+ val |= BXT_CDCLK_CD2X_PIPE(pipe); -+ I915_WRITE(CDCLK_CTL, val); -+ -+ if (pipe != INVALID_PIPE) -+ intel_wait_for_vblank(dev_priv, pipe); -+ -+ /* inform PCU of the change */ -+ mutex_lock(&dev_priv->pcu_lock); -+ sandybridge_pcode_write(dev_priv, SKL_PCODE_CDCLK_CONTROL, -+ cdclk_state->voltage_level); -+ mutex_unlock(&dev_priv->pcu_lock); -+ -+ intel_update_cdclk(dev_priv); -+ -+ /* -+ * Can't read out the voltage level :( -+ * Let's just assume everything is as expected. -+ */ -+ dev_priv->cdclk.hw.voltage_level = cdclk_state->voltage_level; -+} -+ -+static int cnl_cdclk_pll_vco(struct drm_i915_private *dev_priv, int cdclk) -+{ -+ int ratio; -+ -+ if (cdclk == dev_priv->cdclk.hw.bypass) -+ return 0; -+ -+ switch (cdclk) { -+ default: -+ MISSING_CASE(cdclk); -+ /* fall through */ -+ case 168000: -+ case 336000: -+ ratio = dev_priv->cdclk.hw.ref == 19200 ? 35 : 28; -+ break; -+ case 528000: -+ ratio = dev_priv->cdclk.hw.ref == 19200 ? 55 : 44; -+ break; -+ } -+ -+ return dev_priv->cdclk.hw.ref * ratio; -+} -+ -+static void cnl_sanitize_cdclk(struct drm_i915_private *dev_priv) -+{ -+ u32 cdctl, expected; -+ -+ intel_update_cdclk(dev_priv); -+ intel_dump_cdclk_state(&dev_priv->cdclk.hw, "Current CDCLK"); -+ -+ if (dev_priv->cdclk.hw.vco == 0 || -+ dev_priv->cdclk.hw.cdclk == dev_priv->cdclk.hw.bypass) -+ goto sanitize; -+ -+ /* DPLL okay; verify the cdclock -+ * -+ * Some BIOS versions leave an incorrect decimal frequency value and -+ * set reserved MBZ bits in CDCLK_CTL at least during exiting from S4, -+ * so sanitize this register. -+ */ -+ cdctl = I915_READ(CDCLK_CTL); -+ /* -+ * Let's ignore the pipe field, since BIOS could have configured the -+ * dividers both synching to an active pipe, or asynchronously -+ * (PIPE_NONE). -+ */ -+ cdctl &= ~BXT_CDCLK_CD2X_PIPE_NONE; -+ -+ expected = (cdctl & BXT_CDCLK_CD2X_DIV_SEL_MASK) | -+ skl_cdclk_decimal(dev_priv->cdclk.hw.cdclk); -+ -+ if (cdctl == expected) -+ /* All well; nothing to sanitize */ -+ return; -+ -+sanitize: -+ DRM_DEBUG_KMS("Sanitizing cdclk programmed by pre-os\n"); -+ -+ /* force cdclk programming */ -+ dev_priv->cdclk.hw.cdclk = 0; -+ -+ /* force full PLL disable + enable */ -+ dev_priv->cdclk.hw.vco = -1; -+} -+ -+static int icl_calc_cdclk(int min_cdclk, unsigned int ref) -+{ -+ int ranges_24[] = { 312000, 552000, 648000 }; -+ int ranges_19_38[] = { 307200, 556800, 652800 }; -+ int *ranges; -+ -+ switch (ref) { -+ default: -+ MISSING_CASE(ref); -+ /* fall through */ -+ case 24000: -+ ranges = ranges_24; -+ break; -+ case 19200: -+ case 38400: -+ ranges = ranges_19_38; -+ break; -+ } -+ -+ if (min_cdclk > ranges[1]) -+ return ranges[2]; -+ else if (min_cdclk > ranges[0]) -+ return ranges[1]; -+ else -+ return ranges[0]; -+} -+ -+static int icl_calc_cdclk_pll_vco(struct drm_i915_private *dev_priv, int cdclk) -+{ -+ int ratio; -+ -+ if (cdclk == dev_priv->cdclk.hw.bypass) -+ return 0; -+ -+ switch (cdclk) { -+ default: -+ MISSING_CASE(cdclk); -+ /* fall through */ -+ case 307200: -+ case 556800: -+ case 652800: -+ WARN_ON(dev_priv->cdclk.hw.ref != 19200 && -+ dev_priv->cdclk.hw.ref != 38400); -+ break; -+ case 312000: -+ case 552000: -+ case 648000: -+ WARN_ON(dev_priv->cdclk.hw.ref != 24000); -+ } -+ -+ ratio = cdclk / (dev_priv->cdclk.hw.ref / 2); -+ -+ return dev_priv->cdclk.hw.ref * ratio; -+} -+ -+static void icl_set_cdclk(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *cdclk_state, -+ enum pipe pipe) -+{ -+ unsigned int cdclk = cdclk_state->cdclk; -+ unsigned int vco = cdclk_state->vco; -+ int ret; -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ ret = skl_pcode_request(dev_priv, SKL_PCODE_CDCLK_CONTROL, -+ SKL_CDCLK_PREPARE_FOR_CHANGE, -+ SKL_CDCLK_READY_FOR_CHANGE, -+ SKL_CDCLK_READY_FOR_CHANGE, 3); -+ mutex_unlock(&dev_priv->pcu_lock); -+ if (ret) { -+ DRM_ERROR("Failed to inform PCU about cdclk change (%d)\n", -+ ret); -+ return; -+ } -+ -+ if (dev_priv->cdclk.hw.vco != 0 && -+ dev_priv->cdclk.hw.vco != vco) -+ cnl_cdclk_pll_disable(dev_priv); -+ -+ if (dev_priv->cdclk.hw.vco != vco) -+ cnl_cdclk_pll_enable(dev_priv, vco); -+ -+ /* -+ * On ICL CD2X_DIV can only be 1, so we'll never end up changing the -+ * divider here synchronized to a pipe while CDCLK is on, nor will we -+ * need the corresponding vblank wait. -+ */ -+ I915_WRITE(CDCLK_CTL, ICL_CDCLK_CD2X_PIPE_NONE | -+ skl_cdclk_decimal(cdclk)); -+ -+ mutex_lock(&dev_priv->pcu_lock); -+ sandybridge_pcode_write(dev_priv, SKL_PCODE_CDCLK_CONTROL, -+ cdclk_state->voltage_level); -+ mutex_unlock(&dev_priv->pcu_lock); -+ -+ intel_update_cdclk(dev_priv); -+ -+ /* -+ * Can't read out the voltage level :( -+ * Let's just assume everything is as expected. -+ */ -+ dev_priv->cdclk.hw.voltage_level = cdclk_state->voltage_level; -+} -+ -+static u8 icl_calc_voltage_level(int cdclk) -+{ -+ switch (cdclk) { -+ case 50000: -+ case 307200: -+ case 312000: -+ return 0; -+ case 556800: -+ case 552000: -+ return 1; -+ default: -+ MISSING_CASE(cdclk); -+ /* fall through */ -+ case 652800: -+ case 648000: -+ return 2; -+ } -+} -+ -+static void icl_get_cdclk(struct drm_i915_private *dev_priv, -+ struct intel_cdclk_state *cdclk_state) -+{ -+ u32 val; -+ -+ cdclk_state->bypass = 50000; -+ -+ val = I915_READ(SKL_DSSM); -+ switch (val & ICL_DSSM_CDCLK_PLL_REFCLK_MASK) { -+ default: -+ MISSING_CASE(val); -+ /* fall through */ -+ case ICL_DSSM_CDCLK_PLL_REFCLK_24MHz: -+ cdclk_state->ref = 24000; -+ break; -+ case ICL_DSSM_CDCLK_PLL_REFCLK_19_2MHz: -+ cdclk_state->ref = 19200; -+ break; -+ case ICL_DSSM_CDCLK_PLL_REFCLK_38_4MHz: -+ cdclk_state->ref = 38400; -+ break; -+ } -+ -+ val = I915_READ(BXT_DE_PLL_ENABLE); -+ if ((val & BXT_DE_PLL_PLL_ENABLE) == 0 || -+ (val & BXT_DE_PLL_LOCK) == 0) { -+ /* -+ * CDCLK PLL is disabled, the VCO/ratio doesn't matter, but -+ * setting it to zero is a way to signal that. -+ */ -+ cdclk_state->vco = 0; -+ cdclk_state->cdclk = cdclk_state->bypass; -+ goto out; -+ } -+ -+ cdclk_state->vco = (val & BXT_DE_PLL_RATIO_MASK) * cdclk_state->ref; -+ -+ val = I915_READ(CDCLK_CTL); -+ WARN_ON((val & BXT_CDCLK_CD2X_DIV_SEL_MASK) != 0); -+ -+ cdclk_state->cdclk = cdclk_state->vco / 2; -+ -+out: -+ /* -+ * Can't read this out :( Let's assume it's -+ * at least what the CDCLK frequency requires. -+ */ -+ cdclk_state->voltage_level = -+ icl_calc_voltage_level(cdclk_state->cdclk); -+} -+ -+static void icl_init_cdclk(struct drm_i915_private *dev_priv) -+{ -+ struct intel_cdclk_state sanitized_state; -+ u32 val; -+ -+ /* This sets dev_priv->cdclk.hw. */ -+ intel_update_cdclk(dev_priv); -+ intel_dump_cdclk_state(&dev_priv->cdclk.hw, "Current CDCLK"); -+ -+ /* This means CDCLK disabled. */ -+ if (dev_priv->cdclk.hw.cdclk == dev_priv->cdclk.hw.bypass) -+ goto sanitize; -+ -+ val = I915_READ(CDCLK_CTL); -+ -+ if ((val & BXT_CDCLK_CD2X_DIV_SEL_MASK) != 0) -+ goto sanitize; -+ -+ if ((val & CDCLK_FREQ_DECIMAL_MASK) != -+ skl_cdclk_decimal(dev_priv->cdclk.hw.cdclk)) -+ goto sanitize; -+ -+ return; -+ -+sanitize: -+ DRM_DEBUG_KMS("Sanitizing cdclk programmed by pre-os\n"); -+ -+ sanitized_state.ref = dev_priv->cdclk.hw.ref; -+ sanitized_state.cdclk = icl_calc_cdclk(0, sanitized_state.ref); -+ sanitized_state.vco = icl_calc_cdclk_pll_vco(dev_priv, -+ sanitized_state.cdclk); -+ sanitized_state.voltage_level = -+ icl_calc_voltage_level(sanitized_state.cdclk); -+ -+ icl_set_cdclk(dev_priv, &sanitized_state, INVALID_PIPE); -+} -+ -+static void icl_uninit_cdclk(struct drm_i915_private *dev_priv) -+{ -+ struct intel_cdclk_state cdclk_state = dev_priv->cdclk.hw; -+ -+ cdclk_state.cdclk = cdclk_state.bypass; -+ cdclk_state.vco = 0; -+ cdclk_state.voltage_level = icl_calc_voltage_level(cdclk_state.cdclk); -+ -+ icl_set_cdclk(dev_priv, &cdclk_state, INVALID_PIPE); -+} -+ -+static void cnl_init_cdclk(struct drm_i915_private *dev_priv) -+{ -+ struct intel_cdclk_state cdclk_state; -+ -+ cnl_sanitize_cdclk(dev_priv); -+ -+ if (dev_priv->cdclk.hw.cdclk != 0 && -+ dev_priv->cdclk.hw.vco != 0) -+ return; -+ -+ cdclk_state = dev_priv->cdclk.hw; -+ -+ cdclk_state.cdclk = cnl_calc_cdclk(0); -+ cdclk_state.vco = cnl_cdclk_pll_vco(dev_priv, cdclk_state.cdclk); -+ cdclk_state.voltage_level = cnl_calc_voltage_level(cdclk_state.cdclk); -+ -+ cnl_set_cdclk(dev_priv, &cdclk_state, INVALID_PIPE); -+} -+ -+static void cnl_uninit_cdclk(struct drm_i915_private *dev_priv) -+{ -+ struct intel_cdclk_state cdclk_state = dev_priv->cdclk.hw; -+ -+ cdclk_state.cdclk = cdclk_state.bypass; -+ cdclk_state.vco = 0; -+ cdclk_state.voltage_level = cnl_calc_voltage_level(cdclk_state.cdclk); -+ -+ cnl_set_cdclk(dev_priv, &cdclk_state, INVALID_PIPE); -+} -+ -+/** -+ * intel_cdclk_init - Initialize CDCLK -+ * @i915: i915 device -+ * -+ * Initialize CDCLK. This consists mainly of initializing dev_priv->cdclk.hw and -+ * sanitizing the state of the hardware if needed. This is generally done only -+ * during the display core initialization sequence, after which the DMC will -+ * take care of turning CDCLK off/on as needed. -+ */ -+void intel_cdclk_init(struct drm_i915_private *i915) -+{ -+ if (INTEL_GEN(i915) >= 11) -+ icl_init_cdclk(i915); -+ else if (IS_CANNONLAKE(i915)) -+ cnl_init_cdclk(i915); -+ else if (IS_GEN9_BC(i915)) -+ skl_init_cdclk(i915); -+ else if (IS_GEN9_LP(i915)) -+ bxt_init_cdclk(i915); -+} -+ -+/** -+ * intel_cdclk_uninit - Uninitialize CDCLK -+ * @i915: i915 device -+ * -+ * Uninitialize CDCLK. This is done only during the display core -+ * uninitialization sequence. -+ */ -+void intel_cdclk_uninit(struct drm_i915_private *i915) -+{ -+ if (INTEL_GEN(i915) >= 11) -+ icl_uninit_cdclk(i915); -+ else if (IS_CANNONLAKE(i915)) -+ cnl_uninit_cdclk(i915); -+ else if (IS_GEN9_BC(i915)) -+ skl_uninit_cdclk(i915); -+ else if (IS_GEN9_LP(i915)) -+ bxt_uninit_cdclk(i915); -+} -+ -+/** -+ * intel_cdclk_needs_modeset - Determine if two CDCLK states require a modeset on all pipes -+ * @a: first CDCLK state -+ * @b: second CDCLK state -+ * -+ * Returns: -+ * True if the CDCLK states require pipes to be off during reprogramming, false if not. -+ */ -+bool intel_cdclk_needs_modeset(const struct intel_cdclk_state *a, -+ const struct intel_cdclk_state *b) -+{ -+ return a->cdclk != b->cdclk || -+ a->vco != b->vco || -+ a->ref != b->ref; -+} -+ -+/** -+ * intel_cdclk_needs_cd2x_update - Determine if two CDCLK states require a cd2x divider update -+ * @dev_priv: Not a CDCLK state, it's the drm_i915_private! -+ * @a: first CDCLK state -+ * @b: second CDCLK state -+ * -+ * Returns: -+ * True if the CDCLK states require just a cd2x divider update, false if not. -+ */ -+bool intel_cdclk_needs_cd2x_update(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *a, -+ const struct intel_cdclk_state *b) -+{ -+ /* Older hw doesn't have the capability */ -+ if (INTEL_GEN(dev_priv) < 10 && !IS_GEN9_LP(dev_priv)) -+ return false; -+ -+ return a->cdclk != b->cdclk && -+ a->vco == b->vco && -+ a->ref == b->ref; -+} -+ -+/** -+ * intel_cdclk_changed - Determine if two CDCLK states are different -+ * @a: first CDCLK state -+ * @b: second CDCLK state -+ * -+ * Returns: -+ * True if the CDCLK states don't match, false if they do. -+ */ -+bool intel_cdclk_changed(const struct intel_cdclk_state *a, -+ const struct intel_cdclk_state *b) -+{ -+ return intel_cdclk_needs_modeset(a, b) || -+ a->voltage_level != b->voltage_level; -+} -+ -+/** -+ * intel_cdclk_swap_state - make atomic CDCLK configuration effective -+ * @state: atomic state -+ * -+ * This is the CDCLK version of drm_atomic_helper_swap_state() since the -+ * helper does not handle driver-specific global state. -+ * -+ * Similarly to the atomic helpers this function does a complete swap, -+ * i.e. it also puts the old state into @state. This is used by the commit -+ * code to determine how CDCLK has changed (for instance did it increase or -+ * decrease). -+ */ -+void intel_cdclk_swap_state(struct intel_atomic_state *state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(state->base.dev); -+ -+ swap(state->cdclk.logical, dev_priv->cdclk.logical); -+ swap(state->cdclk.actual, dev_priv->cdclk.actual); -+} -+ -+void intel_dump_cdclk_state(const struct intel_cdclk_state *cdclk_state, -+ const char *context) -+{ -+ DRM_DEBUG_DRIVER("%s %d kHz, VCO %d kHz, ref %d kHz, bypass %d kHz, voltage level %d\n", -+ context, cdclk_state->cdclk, cdclk_state->vco, -+ cdclk_state->ref, cdclk_state->bypass, -+ cdclk_state->voltage_level); -+} -+ -+/** -+ * intel_set_cdclk - Push the CDCLK state to the hardware -+ * @dev_priv: i915 device -+ * @cdclk_state: new CDCLK state -+ * @pipe: pipe with which to synchronize the update -+ * -+ * Program the hardware based on the passed in CDCLK state, -+ * if necessary. -+ */ -+static void intel_set_cdclk(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *cdclk_state, -+ enum pipe pipe) -+{ -+ if (!intel_cdclk_changed(&dev_priv->cdclk.hw, cdclk_state)) -+ return; -+ -+ if (WARN_ON_ONCE(!dev_priv->display.set_cdclk)) -+ return; -+ -+ intel_dump_cdclk_state(cdclk_state, "Changing CDCLK to"); -+ -+ dev_priv->display.set_cdclk(dev_priv, cdclk_state, pipe); -+ -+ if (WARN(intel_cdclk_changed(&dev_priv->cdclk.hw, cdclk_state), -+ "cdclk state doesn't match!\n")) { -+ intel_dump_cdclk_state(&dev_priv->cdclk.hw, "[hw state]"); -+ intel_dump_cdclk_state(cdclk_state, "[sw state]"); -+ } -+} -+ -+/** -+ * intel_set_cdclk_pre_plane_update - Push the CDCLK state to the hardware -+ * @dev_priv: i915 device -+ * @old_state: old CDCLK state -+ * @new_state: new CDCLK state -+ * @pipe: pipe with which to synchronize the update -+ * -+ * Program the hardware before updating the HW plane state based on the passed -+ * in CDCLK state, if necessary. -+ */ -+void -+intel_set_cdclk_pre_plane_update(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *old_state, -+ const struct intel_cdclk_state *new_state, -+ enum pipe pipe) -+{ -+ if (pipe == INVALID_PIPE || old_state->cdclk <= new_state->cdclk) -+ intel_set_cdclk(dev_priv, new_state, pipe); -+} -+ -+/** -+ * intel_set_cdclk_post_plane_update - Push the CDCLK state to the hardware -+ * @dev_priv: i915 device -+ * @old_state: old CDCLK state -+ * @new_state: new CDCLK state -+ * @pipe: pipe with which to synchronize the update -+ * -+ * Program the hardware after updating the HW plane state based on the passed -+ * in CDCLK state, if necessary. -+ */ -+void -+intel_set_cdclk_post_plane_update(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *old_state, -+ const struct intel_cdclk_state *new_state, -+ enum pipe pipe) -+{ -+ if (pipe != INVALID_PIPE && old_state->cdclk > new_state->cdclk) -+ intel_set_cdclk(dev_priv, new_state, pipe); -+} -+ -+static int intel_pixel_rate_to_cdclk(struct drm_i915_private *dev_priv, -+ int pixel_rate) -+{ -+ if (INTEL_GEN(dev_priv) >= 10 || IS_GEMINILAKE(dev_priv)) -+ return DIV_ROUND_UP(pixel_rate, 2); -+ else if (IS_GEN(dev_priv, 9) || -+ IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) -+ return pixel_rate; -+ else if (IS_CHERRYVIEW(dev_priv)) -+ return DIV_ROUND_UP(pixel_rate * 100, 95); -+ else -+ return DIV_ROUND_UP(pixel_rate * 100, 90); -+} -+ -+int intel_crtc_compute_min_cdclk(const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = -+ to_i915(crtc_state->base.crtc->dev); -+ int min_cdclk; -+ -+ if (!crtc_state->base.enable) -+ return 0; -+ -+ min_cdclk = intel_pixel_rate_to_cdclk(dev_priv, crtc_state->pixel_rate); -+ -+ /* pixel rate mustn't exceed 95% of cdclk with IPS on BDW */ -+ if (IS_BROADWELL(dev_priv) && hsw_crtc_state_ips_capable(crtc_state)) -+ min_cdclk = DIV_ROUND_UP(min_cdclk * 100, 95); -+ -+ /* BSpec says "Do not use DisplayPort with CDCLK less than 432 MHz, -+ * audio enabled, port width x4, and link rate HBR2 (5.4 GHz), or else -+ * there may be audio corruption or screen corruption." This cdclk -+ * restriction for GLK is 316.8 MHz. -+ */ -+ if (intel_crtc_has_dp_encoder(crtc_state) && -+ crtc_state->has_audio && -+ crtc_state->port_clock >= 540000 && -+ crtc_state->lane_count == 4) { -+ if (IS_CANNONLAKE(dev_priv) || IS_GEMINILAKE(dev_priv)) { -+ /* Display WA #1145: glk,cnl */ -+ min_cdclk = max(316800, min_cdclk); -+ } else if (IS_GEN(dev_priv, 9) || IS_BROADWELL(dev_priv)) { -+ /* Display WA #1144: skl,bxt */ -+ min_cdclk = max(432000, min_cdclk); -+ } -+ } -+ -+ /* -+ * According to BSpec, "The CD clock frequency must be at least twice -+ * the frequency of the Azalia BCLK." and BCLK is 96 MHz by default. -+ */ -+ if (crtc_state->has_audio && INTEL_GEN(dev_priv) >= 9) -+ min_cdclk = max(2 * 96000, min_cdclk); -+ -+ /* -+ * "For DP audio configuration, cdclk frequency shall be set to -+ * meet the following requirements: -+ * DP Link Frequency(MHz) | Cdclk frequency(MHz) -+ * 270 | 320 or higher -+ * 162 | 200 or higher" -+ */ -+ if ((IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) && -+ intel_crtc_has_dp_encoder(crtc_state) && crtc_state->has_audio) -+ min_cdclk = max(crtc_state->port_clock, min_cdclk); -+ -+ /* -+ * On Valleyview some DSI panels lose (v|h)sync when the clock is lower -+ * than 320000KHz. -+ */ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI) && -+ IS_VALLEYVIEW(dev_priv)) -+ min_cdclk = max(320000, min_cdclk); -+ -+ if (min_cdclk > dev_priv->max_cdclk_freq) { -+ DRM_DEBUG_KMS("required cdclk (%d kHz) exceeds max (%d kHz)\n", -+ min_cdclk, dev_priv->max_cdclk_freq); -+ return -EINVAL; -+ } -+ -+ return min_cdclk; -+} -+ -+static int intel_compute_min_cdclk(struct drm_atomic_state *state) -+{ -+ struct intel_atomic_state *intel_state = to_intel_atomic_state(state); -+ struct drm_i915_private *dev_priv = to_i915(state->dev); -+ struct intel_crtc *crtc; -+ struct intel_crtc_state *crtc_state; -+ int min_cdclk, i; -+ enum pipe pipe; -+ -+ memcpy(intel_state->min_cdclk, dev_priv->min_cdclk, -+ sizeof(intel_state->min_cdclk)); -+ -+ for_each_new_intel_crtc_in_state(intel_state, crtc, crtc_state, i) { -+ min_cdclk = intel_crtc_compute_min_cdclk(crtc_state); -+ if (min_cdclk < 0) -+ return min_cdclk; -+ -+ intel_state->min_cdclk[i] = min_cdclk; -+ } -+ -+ min_cdclk = intel_state->cdclk.force_min_cdclk; -+ for_each_pipe(dev_priv, pipe) -+ min_cdclk = max(intel_state->min_cdclk[pipe], min_cdclk); -+ -+ return min_cdclk; -+} -+ -+/* -+ * Note that this functions assumes that 0 is -+ * the lowest voltage value, and higher values -+ * correspond to increasingly higher voltages. -+ * -+ * Should that relationship no longer hold on -+ * future platforms this code will need to be -+ * adjusted. -+ */ -+static u8 cnl_compute_min_voltage_level(struct intel_atomic_state *state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(state->base.dev); -+ struct intel_crtc *crtc; -+ struct intel_crtc_state *crtc_state; -+ u8 min_voltage_level; -+ int i; -+ enum pipe pipe; -+ -+ memcpy(state->min_voltage_level, dev_priv->min_voltage_level, -+ sizeof(state->min_voltage_level)); -+ -+ for_each_new_intel_crtc_in_state(state, crtc, crtc_state, i) { -+ if (crtc_state->base.enable) -+ state->min_voltage_level[i] = -+ crtc_state->min_voltage_level; -+ else -+ state->min_voltage_level[i] = 0; -+ } -+ -+ min_voltage_level = 0; -+ for_each_pipe(dev_priv, pipe) -+ min_voltage_level = max(state->min_voltage_level[pipe], -+ min_voltage_level); -+ -+ return min_voltage_level; -+} -+ -+static int vlv_modeset_calc_cdclk(struct drm_atomic_state *state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(state->dev); -+ struct intel_atomic_state *intel_state = to_intel_atomic_state(state); -+ int min_cdclk, cdclk; -+ -+ min_cdclk = intel_compute_min_cdclk(state); -+ if (min_cdclk < 0) -+ return min_cdclk; -+ -+ cdclk = vlv_calc_cdclk(dev_priv, min_cdclk); -+ -+ intel_state->cdclk.logical.cdclk = cdclk; -+ intel_state->cdclk.logical.voltage_level = -+ vlv_calc_voltage_level(dev_priv, cdclk); -+ -+ if (!intel_state->active_crtcs) { -+ cdclk = vlv_calc_cdclk(dev_priv, -+ intel_state->cdclk.force_min_cdclk); -+ -+ intel_state->cdclk.actual.cdclk = cdclk; -+ intel_state->cdclk.actual.voltage_level = -+ vlv_calc_voltage_level(dev_priv, cdclk); -+ } else { -+ intel_state->cdclk.actual = -+ intel_state->cdclk.logical; -+ } -+ -+ return 0; -+} -+ -+static int bdw_modeset_calc_cdclk(struct drm_atomic_state *state) -+{ -+ struct intel_atomic_state *intel_state = to_intel_atomic_state(state); -+ int min_cdclk, cdclk; -+ -+ min_cdclk = intel_compute_min_cdclk(state); -+ if (min_cdclk < 0) -+ return min_cdclk; -+ -+ /* -+ * FIXME should also account for plane ratio -+ * once 64bpp pixel formats are supported. -+ */ -+ cdclk = bdw_calc_cdclk(min_cdclk); -+ -+ intel_state->cdclk.logical.cdclk = cdclk; -+ intel_state->cdclk.logical.voltage_level = -+ bdw_calc_voltage_level(cdclk); -+ -+ if (!intel_state->active_crtcs) { -+ cdclk = bdw_calc_cdclk(intel_state->cdclk.force_min_cdclk); -+ -+ intel_state->cdclk.actual.cdclk = cdclk; -+ intel_state->cdclk.actual.voltage_level = -+ bdw_calc_voltage_level(cdclk); -+ } else { -+ intel_state->cdclk.actual = -+ intel_state->cdclk.logical; -+ } -+ -+ return 0; -+} -+ -+static int skl_dpll0_vco(struct intel_atomic_state *intel_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(intel_state->base.dev); -+ struct intel_crtc *crtc; -+ struct intel_crtc_state *crtc_state; -+ int vco, i; -+ -+ vco = intel_state->cdclk.logical.vco; -+ if (!vco) -+ vco = dev_priv->skl_preferred_vco_freq; -+ -+ for_each_new_intel_crtc_in_state(intel_state, crtc, crtc_state, i) { -+ if (!crtc_state->base.enable) -+ continue; -+ -+ if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP)) -+ continue; -+ -+ /* -+ * DPLL0 VCO may need to be adjusted to get the correct -+ * clock for eDP. This will affect cdclk as well. -+ */ -+ switch (crtc_state->port_clock / 2) { -+ case 108000: -+ case 216000: -+ vco = 8640000; -+ break; -+ default: -+ vco = 8100000; -+ break; -+ } -+ } -+ -+ return vco; -+} -+ -+static int skl_modeset_calc_cdclk(struct drm_atomic_state *state) -+{ -+ struct intel_atomic_state *intel_state = to_intel_atomic_state(state); -+ int min_cdclk, cdclk, vco; -+ -+ min_cdclk = intel_compute_min_cdclk(state); -+ if (min_cdclk < 0) -+ return min_cdclk; -+ -+ vco = skl_dpll0_vco(intel_state); -+ -+ /* -+ * FIXME should also account for plane ratio -+ * once 64bpp pixel formats are supported. -+ */ -+ cdclk = skl_calc_cdclk(min_cdclk, vco); -+ -+ intel_state->cdclk.logical.vco = vco; -+ intel_state->cdclk.logical.cdclk = cdclk; -+ intel_state->cdclk.logical.voltage_level = -+ skl_calc_voltage_level(cdclk); -+ -+ if (!intel_state->active_crtcs) { -+ cdclk = skl_calc_cdclk(intel_state->cdclk.force_min_cdclk, vco); -+ -+ intel_state->cdclk.actual.vco = vco; -+ intel_state->cdclk.actual.cdclk = cdclk; -+ intel_state->cdclk.actual.voltage_level = -+ skl_calc_voltage_level(cdclk); -+ } else { -+ intel_state->cdclk.actual = -+ intel_state->cdclk.logical; -+ } -+ -+ return 0; -+} -+ -+static int bxt_modeset_calc_cdclk(struct drm_atomic_state *state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(state->dev); -+ struct intel_atomic_state *intel_state = to_intel_atomic_state(state); -+ int min_cdclk, cdclk, vco; -+ -+ min_cdclk = intel_compute_min_cdclk(state); -+ if (min_cdclk < 0) -+ return min_cdclk; -+ -+ if (IS_GEMINILAKE(dev_priv)) { -+ cdclk = glk_calc_cdclk(min_cdclk); -+ vco = glk_de_pll_vco(dev_priv, cdclk); -+ } else { -+ cdclk = bxt_calc_cdclk(min_cdclk); -+ vco = bxt_de_pll_vco(dev_priv, cdclk); -+ } -+ -+ intel_state->cdclk.logical.vco = vco; -+ intel_state->cdclk.logical.cdclk = cdclk; -+ intel_state->cdclk.logical.voltage_level = -+ bxt_calc_voltage_level(cdclk); -+ -+ if (!intel_state->active_crtcs) { -+ if (IS_GEMINILAKE(dev_priv)) { -+ cdclk = glk_calc_cdclk(intel_state->cdclk.force_min_cdclk); -+ vco = glk_de_pll_vco(dev_priv, cdclk); -+ } else { -+ cdclk = bxt_calc_cdclk(intel_state->cdclk.force_min_cdclk); -+ vco = bxt_de_pll_vco(dev_priv, cdclk); -+ } -+ -+ intel_state->cdclk.actual.vco = vco; -+ intel_state->cdclk.actual.cdclk = cdclk; -+ intel_state->cdclk.actual.voltage_level = -+ bxt_calc_voltage_level(cdclk); -+ } else { -+ intel_state->cdclk.actual = -+ intel_state->cdclk.logical; -+ } -+ -+ return 0; -+} -+ -+static int cnl_modeset_calc_cdclk(struct drm_atomic_state *state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(state->dev); -+ struct intel_atomic_state *intel_state = to_intel_atomic_state(state); -+ int min_cdclk, cdclk, vco; -+ -+ min_cdclk = intel_compute_min_cdclk(state); -+ if (min_cdclk < 0) -+ return min_cdclk; -+ -+ cdclk = cnl_calc_cdclk(min_cdclk); -+ vco = cnl_cdclk_pll_vco(dev_priv, cdclk); -+ -+ intel_state->cdclk.logical.vco = vco; -+ intel_state->cdclk.logical.cdclk = cdclk; -+ intel_state->cdclk.logical.voltage_level = -+ max(cnl_calc_voltage_level(cdclk), -+ cnl_compute_min_voltage_level(intel_state)); -+ -+ if (!intel_state->active_crtcs) { -+ cdclk = cnl_calc_cdclk(intel_state->cdclk.force_min_cdclk); -+ vco = cnl_cdclk_pll_vco(dev_priv, cdclk); -+ -+ intel_state->cdclk.actual.vco = vco; -+ intel_state->cdclk.actual.cdclk = cdclk; -+ intel_state->cdclk.actual.voltage_level = -+ cnl_calc_voltage_level(cdclk); -+ } else { -+ intel_state->cdclk.actual = -+ intel_state->cdclk.logical; -+ } -+ -+ return 0; -+} -+ -+static int icl_modeset_calc_cdclk(struct drm_atomic_state *state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(state->dev); -+ struct intel_atomic_state *intel_state = to_intel_atomic_state(state); -+ unsigned int ref = intel_state->cdclk.logical.ref; -+ int min_cdclk, cdclk, vco; -+ -+ min_cdclk = intel_compute_min_cdclk(state); -+ if (min_cdclk < 0) -+ return min_cdclk; -+ -+ cdclk = icl_calc_cdclk(min_cdclk, ref); -+ vco = icl_calc_cdclk_pll_vco(dev_priv, cdclk); -+ -+ intel_state->cdclk.logical.vco = vco; -+ intel_state->cdclk.logical.cdclk = cdclk; -+ intel_state->cdclk.logical.voltage_level = -+ max(icl_calc_voltage_level(cdclk), -+ cnl_compute_min_voltage_level(intel_state)); -+ -+ if (!intel_state->active_crtcs) { -+ cdclk = icl_calc_cdclk(intel_state->cdclk.force_min_cdclk, ref); -+ vco = icl_calc_cdclk_pll_vco(dev_priv, cdclk); -+ -+ intel_state->cdclk.actual.vco = vco; -+ intel_state->cdclk.actual.cdclk = cdclk; -+ intel_state->cdclk.actual.voltage_level = -+ icl_calc_voltage_level(cdclk); -+ } else { -+ intel_state->cdclk.actual = intel_state->cdclk.logical; -+ } -+ -+ return 0; -+} -+ -+static int intel_compute_max_dotclk(struct drm_i915_private *dev_priv) -+{ -+ int max_cdclk_freq = dev_priv->max_cdclk_freq; -+ -+ if (INTEL_GEN(dev_priv) >= 10 || IS_GEMINILAKE(dev_priv)) -+ return 2 * max_cdclk_freq; -+ else if (IS_GEN(dev_priv, 9) || -+ IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) -+ return max_cdclk_freq; -+ else if (IS_CHERRYVIEW(dev_priv)) -+ return max_cdclk_freq*95/100; -+ else if (INTEL_GEN(dev_priv) < 4) -+ return 2*max_cdclk_freq*90/100; -+ else -+ return max_cdclk_freq*90/100; -+} -+ -+/** -+ * intel_update_max_cdclk - Determine the maximum support CDCLK frequency -+ * @dev_priv: i915 device -+ * -+ * Determine the maximum CDCLK frequency the platform supports, and also -+ * derive the maximum dot clock frequency the maximum CDCLK frequency -+ * allows. -+ */ -+void intel_update_max_cdclk(struct drm_i915_private *dev_priv) -+{ -+ if (INTEL_GEN(dev_priv) >= 11) { -+ if (dev_priv->cdclk.hw.ref == 24000) -+ dev_priv->max_cdclk_freq = 648000; -+ else -+ dev_priv->max_cdclk_freq = 652800; -+ } else if (IS_CANNONLAKE(dev_priv)) { -+ dev_priv->max_cdclk_freq = 528000; -+ } else if (IS_GEN9_BC(dev_priv)) { -+ u32 limit = I915_READ(SKL_DFSM) & SKL_DFSM_CDCLK_LIMIT_MASK; -+ int max_cdclk, vco; -+ -+ vco = dev_priv->skl_preferred_vco_freq; -+ WARN_ON(vco != 8100000 && vco != 8640000); -+ -+ /* -+ * Use the lower (vco 8640) cdclk values as a -+ * first guess. skl_calc_cdclk() will correct it -+ * if the preferred vco is 8100 instead. -+ */ -+ if (limit == SKL_DFSM_CDCLK_LIMIT_675) -+ max_cdclk = 617143; -+ else if (limit == SKL_DFSM_CDCLK_LIMIT_540) -+ max_cdclk = 540000; -+ else if (limit == SKL_DFSM_CDCLK_LIMIT_450) -+ max_cdclk = 432000; -+ else -+ max_cdclk = 308571; -+ -+ dev_priv->max_cdclk_freq = skl_calc_cdclk(max_cdclk, vco); -+ } else if (IS_GEMINILAKE(dev_priv)) { -+ dev_priv->max_cdclk_freq = 316800; -+ } else if (IS_BROXTON(dev_priv)) { -+ dev_priv->max_cdclk_freq = 624000; -+ } else if (IS_BROADWELL(dev_priv)) { -+ /* -+ * FIXME with extra cooling we can allow -+ * 540 MHz for ULX and 675 Mhz for ULT. -+ * How can we know if extra cooling is -+ * available? PCI ID, VTB, something else? -+ */ -+ if (I915_READ(FUSE_STRAP) & HSW_CDCLK_LIMIT) -+ dev_priv->max_cdclk_freq = 450000; -+ else if (IS_BDW_ULX(dev_priv)) -+ dev_priv->max_cdclk_freq = 450000; -+ else if (IS_BDW_ULT(dev_priv)) -+ dev_priv->max_cdclk_freq = 540000; -+ else -+ dev_priv->max_cdclk_freq = 675000; -+ } else if (IS_CHERRYVIEW(dev_priv)) { -+ dev_priv->max_cdclk_freq = 320000; -+ } else if (IS_VALLEYVIEW(dev_priv)) { -+ dev_priv->max_cdclk_freq = 400000; -+ } else { -+ /* otherwise assume cdclk is fixed */ -+ dev_priv->max_cdclk_freq = dev_priv->cdclk.hw.cdclk; -+ } -+ -+ dev_priv->max_dotclk_freq = intel_compute_max_dotclk(dev_priv); -+ -+ DRM_DEBUG_DRIVER("Max CD clock rate: %d kHz\n", -+ dev_priv->max_cdclk_freq); -+ -+ DRM_DEBUG_DRIVER("Max dotclock rate: %d kHz\n", -+ dev_priv->max_dotclk_freq); -+} -+ -+/** -+ * intel_update_cdclk - Determine the current CDCLK frequency -+ * @dev_priv: i915 device -+ * -+ * Determine the current CDCLK frequency. -+ */ -+void intel_update_cdclk(struct drm_i915_private *dev_priv) -+{ -+ dev_priv->display.get_cdclk(dev_priv, &dev_priv->cdclk.hw); -+ -+ /* -+ * 9:0 CMBUS [sic] CDCLK frequency (cdfreq): -+ * Programmng [sic] note: bit[9:2] should be programmed to the number -+ * of cdclk that generates 4MHz reference clock freq which is used to -+ * generate GMBus clock. This will vary with the cdclk freq. -+ */ -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ I915_WRITE(GMBUSFREQ_VLV, -+ DIV_ROUND_UP(dev_priv->cdclk.hw.cdclk, 1000)); -+} -+ -+static int cnp_rawclk(struct drm_i915_private *dev_priv) -+{ -+ u32 rawclk; -+ int divider, fraction; -+ -+ if (I915_READ(SFUSE_STRAP) & SFUSE_STRAP_RAW_FREQUENCY) { -+ /* 24 MHz */ -+ divider = 24000; -+ fraction = 0; -+ } else { -+ /* 19.2 MHz */ -+ divider = 19000; -+ fraction = 200; -+ } -+ -+ rawclk = CNP_RAWCLK_DIV(divider / 1000); -+ if (fraction) { -+ int numerator = 1; -+ -+ rawclk |= CNP_RAWCLK_DEN(DIV_ROUND_CLOSEST(numerator * 1000, -+ fraction) - 1); -+ if (INTEL_PCH_TYPE(dev_priv) >= PCH_ICP) -+ rawclk |= ICP_RAWCLK_NUM(numerator); -+ } -+ -+ I915_WRITE(PCH_RAWCLK_FREQ, rawclk); -+ return divider + fraction; -+} -+ -+static int pch_rawclk(struct drm_i915_private *dev_priv) -+{ -+ return (I915_READ(PCH_RAWCLK_FREQ) & RAWCLK_FREQ_MASK) * 1000; -+} -+ -+static int vlv_hrawclk(struct drm_i915_private *dev_priv) -+{ -+ /* RAWCLK_FREQ_VLV register updated from power well code */ -+ return vlv_get_cck_clock_hpll(dev_priv, "hrawclk", -+ CCK_DISPLAY_REF_CLOCK_CONTROL); -+} -+ -+static int g4x_hrawclk(struct drm_i915_private *dev_priv) -+{ -+ u32 clkcfg; -+ -+ /* hrawclock is 1/4 the FSB frequency */ -+ clkcfg = I915_READ(CLKCFG); -+ switch (clkcfg & CLKCFG_FSB_MASK) { -+ case CLKCFG_FSB_400: -+ return 100000; -+ case CLKCFG_FSB_533: -+ return 133333; -+ case CLKCFG_FSB_667: -+ return 166667; -+ case CLKCFG_FSB_800: -+ return 200000; -+ case CLKCFG_FSB_1067: -+ case CLKCFG_FSB_1067_ALT: -+ return 266667; -+ case CLKCFG_FSB_1333: -+ case CLKCFG_FSB_1333_ALT: -+ return 333333; -+ default: -+ return 133333; -+ } -+} -+ -+/** -+ * intel_update_rawclk - Determine the current RAWCLK frequency -+ * @dev_priv: i915 device -+ * -+ * Determine the current RAWCLK frequency. RAWCLK is a fixed -+ * frequency clock so this needs to done only once. -+ */ -+void intel_update_rawclk(struct drm_i915_private *dev_priv) -+{ -+ if (INTEL_PCH_TYPE(dev_priv) >= PCH_CNP) -+ dev_priv->rawclk_freq = cnp_rawclk(dev_priv); -+ else if (HAS_PCH_SPLIT(dev_priv)) -+ dev_priv->rawclk_freq = pch_rawclk(dev_priv); -+ else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ dev_priv->rawclk_freq = vlv_hrawclk(dev_priv); -+ else if (IS_G4X(dev_priv) || IS_PINEVIEW(dev_priv)) -+ dev_priv->rawclk_freq = g4x_hrawclk(dev_priv); -+ else -+ /* no rawclk on other platforms, or no need to know it */ -+ return; -+ -+ DRM_DEBUG_DRIVER("rawclk rate: %d kHz\n", dev_priv->rawclk_freq); -+} -+ -+/** -+ * intel_init_cdclk_hooks - Initialize CDCLK related modesetting hooks -+ * @dev_priv: i915 device -+ */ -+void intel_init_cdclk_hooks(struct drm_i915_private *dev_priv) -+{ -+ if (INTEL_GEN(dev_priv) >= 11) { -+ dev_priv->display.set_cdclk = icl_set_cdclk; -+ dev_priv->display.modeset_calc_cdclk = icl_modeset_calc_cdclk; -+ } else if (IS_CANNONLAKE(dev_priv)) { -+ dev_priv->display.set_cdclk = cnl_set_cdclk; -+ dev_priv->display.modeset_calc_cdclk = -+ cnl_modeset_calc_cdclk; -+ } else if (IS_GEN9_LP(dev_priv)) { -+ dev_priv->display.set_cdclk = bxt_set_cdclk; -+ dev_priv->display.modeset_calc_cdclk = -+ bxt_modeset_calc_cdclk; -+ } else if (IS_GEN9_BC(dev_priv)) { -+ dev_priv->display.set_cdclk = skl_set_cdclk; -+ dev_priv->display.modeset_calc_cdclk = -+ skl_modeset_calc_cdclk; -+ } else if (IS_BROADWELL(dev_priv)) { -+ dev_priv->display.set_cdclk = bdw_set_cdclk; -+ dev_priv->display.modeset_calc_cdclk = -+ bdw_modeset_calc_cdclk; -+ } else if (IS_CHERRYVIEW(dev_priv)) { -+ dev_priv->display.set_cdclk = chv_set_cdclk; -+ dev_priv->display.modeset_calc_cdclk = -+ vlv_modeset_calc_cdclk; -+ } else if (IS_VALLEYVIEW(dev_priv)) { -+ dev_priv->display.set_cdclk = vlv_set_cdclk; -+ dev_priv->display.modeset_calc_cdclk = -+ vlv_modeset_calc_cdclk; -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ dev_priv->display.get_cdclk = icl_get_cdclk; -+ else if (IS_CANNONLAKE(dev_priv)) -+ dev_priv->display.get_cdclk = cnl_get_cdclk; -+ else if (IS_GEN9_LP(dev_priv)) -+ dev_priv->display.get_cdclk = bxt_get_cdclk; -+ else if (IS_GEN9_BC(dev_priv)) -+ dev_priv->display.get_cdclk = skl_get_cdclk; -+ else if (IS_BROADWELL(dev_priv)) -+ dev_priv->display.get_cdclk = bdw_get_cdclk; -+ else if (IS_HASWELL(dev_priv)) -+ dev_priv->display.get_cdclk = hsw_get_cdclk; -+ else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ dev_priv->display.get_cdclk = vlv_get_cdclk; -+ else if (IS_GEN(dev_priv, 6) || IS_IVYBRIDGE(dev_priv)) -+ dev_priv->display.get_cdclk = fixed_400mhz_get_cdclk; -+ else if (IS_GEN(dev_priv, 5)) -+ dev_priv->display.get_cdclk = fixed_450mhz_get_cdclk; -+ else if (IS_GM45(dev_priv)) -+ dev_priv->display.get_cdclk = gm45_get_cdclk; -+ else if (IS_G45(dev_priv)) -+ dev_priv->display.get_cdclk = g33_get_cdclk; -+ else if (IS_I965GM(dev_priv)) -+ dev_priv->display.get_cdclk = i965gm_get_cdclk; -+ else if (IS_I965G(dev_priv)) -+ dev_priv->display.get_cdclk = fixed_400mhz_get_cdclk; -+ else if (IS_PINEVIEW(dev_priv)) -+ dev_priv->display.get_cdclk = pnv_get_cdclk; -+ else if (IS_G33(dev_priv)) -+ dev_priv->display.get_cdclk = g33_get_cdclk; -+ else if (IS_I945GM(dev_priv)) -+ dev_priv->display.get_cdclk = i945gm_get_cdclk; -+ else if (IS_I945G(dev_priv)) -+ dev_priv->display.get_cdclk = fixed_400mhz_get_cdclk; -+ else if (IS_I915GM(dev_priv)) -+ dev_priv->display.get_cdclk = i915gm_get_cdclk; -+ else if (IS_I915G(dev_priv)) -+ dev_priv->display.get_cdclk = fixed_333mhz_get_cdclk; -+ else if (IS_I865G(dev_priv)) -+ dev_priv->display.get_cdclk = fixed_266mhz_get_cdclk; -+ else if (IS_I85X(dev_priv)) -+ dev_priv->display.get_cdclk = i85x_get_cdclk; -+ else if (IS_I845G(dev_priv)) -+ dev_priv->display.get_cdclk = fixed_200mhz_get_cdclk; -+ else { /* 830 */ -+ WARN(!IS_I830(dev_priv), -+ "Unknown platform. Assuming 133 MHz CDCLK\n"); -+ dev_priv->display.get_cdclk = fixed_133mhz_get_cdclk; -+ } -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_cdclk.h b/drivers/gpu/drm/i915_legacy/intel_cdclk.h -new file mode 100644 -index 000000000000..4d6f7f5f8930 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_cdclk.h -@@ -0,0 +1,46 @@ -+/* SPDX-License-Identifier: MIT */ -+/* -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef __INTEL_CDCLK_H__ -+#define __INTEL_CDCLK_H__ -+ -+#include -+ -+#include "intel_display.h" -+ -+struct drm_i915_private; -+struct intel_atomic_state; -+struct intel_cdclk_state; -+struct intel_crtc_state; -+ -+int intel_crtc_compute_min_cdclk(const struct intel_crtc_state *crtc_state); -+void intel_cdclk_init(struct drm_i915_private *i915); -+void intel_cdclk_uninit(struct drm_i915_private *i915); -+void intel_init_cdclk_hooks(struct drm_i915_private *dev_priv); -+void intel_update_max_cdclk(struct drm_i915_private *dev_priv); -+void intel_update_cdclk(struct drm_i915_private *dev_priv); -+void intel_update_rawclk(struct drm_i915_private *dev_priv); -+bool intel_cdclk_needs_cd2x_update(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *a, -+ const struct intel_cdclk_state *b); -+bool intel_cdclk_needs_modeset(const struct intel_cdclk_state *a, -+ const struct intel_cdclk_state *b); -+bool intel_cdclk_changed(const struct intel_cdclk_state *a, -+ const struct intel_cdclk_state *b); -+void intel_cdclk_swap_state(struct intel_atomic_state *state); -+void -+intel_set_cdclk_pre_plane_update(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *old_state, -+ const struct intel_cdclk_state *new_state, -+ enum pipe pipe); -+void -+intel_set_cdclk_post_plane_update(struct drm_i915_private *dev_priv, -+ const struct intel_cdclk_state *old_state, -+ const struct intel_cdclk_state *new_state, -+ enum pipe pipe); -+void intel_dump_cdclk_state(const struct intel_cdclk_state *cdclk_state, -+ const char *context); -+ -+#endif /* __INTEL_CDCLK_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_color.c b/drivers/gpu/drm/i915_legacy/intel_color.c -new file mode 100644 -index 000000000000..9093daabc290 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_color.c -@@ -0,0 +1,1278 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include "intel_color.h" -+#include "intel_drv.h" -+ -+#define CTM_COEFF_SIGN (1ULL << 63) -+ -+#define CTM_COEFF_1_0 (1ULL << 32) -+#define CTM_COEFF_2_0 (CTM_COEFF_1_0 << 1) -+#define CTM_COEFF_4_0 (CTM_COEFF_2_0 << 1) -+#define CTM_COEFF_8_0 (CTM_COEFF_4_0 << 1) -+#define CTM_COEFF_0_5 (CTM_COEFF_1_0 >> 1) -+#define CTM_COEFF_0_25 (CTM_COEFF_0_5 >> 1) -+#define CTM_COEFF_0_125 (CTM_COEFF_0_25 >> 1) -+ -+#define CTM_COEFF_LIMITED_RANGE ((235ULL - 16ULL) * CTM_COEFF_1_0 / 255) -+ -+#define CTM_COEFF_NEGATIVE(coeff) (((coeff) & CTM_COEFF_SIGN) != 0) -+#define CTM_COEFF_ABS(coeff) ((coeff) & (CTM_COEFF_SIGN - 1)) -+ -+#define LEGACY_LUT_LENGTH 256 -+/* -+ * Extract the CSC coefficient from a CTM coefficient (in U32.32 fixed point -+ * format). This macro takes the coefficient we want transformed and the -+ * number of fractional bits. -+ * -+ * We only have a 9 bits precision window which slides depending on the value -+ * of the CTM coefficient and we write the value from bit 3. We also round the -+ * value. -+ */ -+#define ILK_CSC_COEFF_FP(coeff, fbits) \ -+ (clamp_val(((coeff) >> (32 - (fbits) - 3)) + 4, 0, 0xfff) & 0xff8) -+ -+#define ILK_CSC_COEFF_LIMITED_RANGE 0x0dc0 -+#define ILK_CSC_COEFF_1_0 0x7800 -+ -+#define ILK_CSC_POSTOFF_LIMITED_RANGE (16 * (1 << 12) / 255) -+ -+static const u16 ilk_csc_off_zero[3] = {}; -+ -+static const u16 ilk_csc_coeff_identity[9] = { -+ ILK_CSC_COEFF_1_0, 0, 0, -+ 0, ILK_CSC_COEFF_1_0, 0, -+ 0, 0, ILK_CSC_COEFF_1_0, -+}; -+ -+static const u16 ilk_csc_postoff_limited_range[3] = { -+ ILK_CSC_POSTOFF_LIMITED_RANGE, -+ ILK_CSC_POSTOFF_LIMITED_RANGE, -+ ILK_CSC_POSTOFF_LIMITED_RANGE, -+}; -+ -+static const u16 ilk_csc_coeff_limited_range[9] = { -+ ILK_CSC_COEFF_LIMITED_RANGE, 0, 0, -+ 0, ILK_CSC_COEFF_LIMITED_RANGE, 0, -+ 0, 0, ILK_CSC_COEFF_LIMITED_RANGE, -+}; -+ -+/* -+ * These values are direct register values specified in the Bspec, -+ * for RGB->YUV conversion matrix (colorspace BT709) -+ */ -+static const u16 ilk_csc_coeff_rgb_to_ycbcr[9] = { -+ 0x1e08, 0x9cc0, 0xb528, -+ 0x2ba8, 0x09d8, 0x37e8, -+ 0xbce8, 0x9ad8, 0x1e08, -+}; -+ -+/* Post offset values for RGB->YCBCR conversion */ -+static const u16 ilk_csc_postoff_rgb_to_ycbcr[3] = { -+ 0x0800, 0x0100, 0x0800, -+}; -+ -+static bool lut_is_legacy(const struct drm_property_blob *lut) -+{ -+ return drm_color_lut_size(lut) == LEGACY_LUT_LENGTH; -+} -+ -+static bool crtc_state_is_legacy_gamma(const struct intel_crtc_state *crtc_state) -+{ -+ return !crtc_state->base.degamma_lut && -+ !crtc_state->base.ctm && -+ crtc_state->base.gamma_lut && -+ lut_is_legacy(crtc_state->base.gamma_lut); -+} -+ -+/* -+ * When using limited range, multiply the matrix given by userspace by -+ * the matrix that we would use for the limited range. -+ */ -+static u64 *ctm_mult_by_limited(u64 *result, const u64 *input) -+{ -+ int i; -+ -+ for (i = 0; i < 9; i++) { -+ u64 user_coeff = input[i]; -+ u32 limited_coeff = CTM_COEFF_LIMITED_RANGE; -+ u32 abs_coeff = clamp_val(CTM_COEFF_ABS(user_coeff), 0, -+ CTM_COEFF_4_0 - 1) >> 2; -+ -+ /* -+ * By scaling every co-efficient with limited range (16-235) -+ * vs full range (0-255) the final o/p will be scaled down to -+ * fit in the limited range supported by the panel. -+ */ -+ result[i] = mul_u32_u32(limited_coeff, abs_coeff) >> 30; -+ result[i] |= user_coeff & CTM_COEFF_SIGN; -+ } -+ -+ return result; -+} -+ -+static void ilk_update_pipe_csc(struct intel_crtc *crtc, -+ const u16 preoff[3], -+ const u16 coeff[9], -+ const u16 postoff[3]) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ -+ I915_WRITE(PIPE_CSC_PREOFF_HI(pipe), preoff[0]); -+ I915_WRITE(PIPE_CSC_PREOFF_ME(pipe), preoff[1]); -+ I915_WRITE(PIPE_CSC_PREOFF_LO(pipe), preoff[2]); -+ -+ I915_WRITE(PIPE_CSC_COEFF_RY_GY(pipe), coeff[0] << 16 | coeff[1]); -+ I915_WRITE(PIPE_CSC_COEFF_BY(pipe), coeff[2] << 16); -+ -+ I915_WRITE(PIPE_CSC_COEFF_RU_GU(pipe), coeff[3] << 16 | coeff[4]); -+ I915_WRITE(PIPE_CSC_COEFF_BU(pipe), coeff[5] << 16); -+ -+ I915_WRITE(PIPE_CSC_COEFF_RV_GV(pipe), coeff[6] << 16 | coeff[7]); -+ I915_WRITE(PIPE_CSC_COEFF_BV(pipe), coeff[8] << 16); -+ -+ if (INTEL_GEN(dev_priv) >= 7) { -+ I915_WRITE(PIPE_CSC_POSTOFF_HI(pipe), postoff[0]); -+ I915_WRITE(PIPE_CSC_POSTOFF_ME(pipe), postoff[1]); -+ I915_WRITE(PIPE_CSC_POSTOFF_LO(pipe), postoff[2]); -+ } -+} -+ -+static void icl_update_output_csc(struct intel_crtc *crtc, -+ const u16 preoff[3], -+ const u16 coeff[9], -+ const u16 postoff[3]) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ -+ I915_WRITE(PIPE_CSC_OUTPUT_PREOFF_HI(pipe), preoff[0]); -+ I915_WRITE(PIPE_CSC_OUTPUT_PREOFF_ME(pipe), preoff[1]); -+ I915_WRITE(PIPE_CSC_OUTPUT_PREOFF_LO(pipe), preoff[2]); -+ -+ I915_WRITE(PIPE_CSC_OUTPUT_COEFF_RY_GY(pipe), coeff[0] << 16 | coeff[1]); -+ I915_WRITE(PIPE_CSC_OUTPUT_COEFF_BY(pipe), coeff[2] << 16); -+ -+ I915_WRITE(PIPE_CSC_OUTPUT_COEFF_RU_GU(pipe), coeff[3] << 16 | coeff[4]); -+ I915_WRITE(PIPE_CSC_OUTPUT_COEFF_BU(pipe), coeff[5] << 16); -+ -+ I915_WRITE(PIPE_CSC_OUTPUT_COEFF_RV_GV(pipe), coeff[6] << 16 | coeff[7]); -+ I915_WRITE(PIPE_CSC_OUTPUT_COEFF_BV(pipe), coeff[8] << 16); -+ -+ I915_WRITE(PIPE_CSC_OUTPUT_POSTOFF_HI(pipe), postoff[0]); -+ I915_WRITE(PIPE_CSC_OUTPUT_POSTOFF_ME(pipe), postoff[1]); -+ I915_WRITE(PIPE_CSC_OUTPUT_POSTOFF_LO(pipe), postoff[2]); -+} -+ -+static bool ilk_csc_limited_range(const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ -+ /* -+ * FIXME if there's a gamma LUT after the CSC, we should -+ * do the range compression using the gamma LUT instead. -+ */ -+ return crtc_state->limited_color_range && -+ (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv) || -+ IS_GEN_RANGE(dev_priv, 9, 10)); -+} -+ -+static void ilk_csc_convert_ctm(const struct intel_crtc_state *crtc_state, -+ u16 coeffs[9]) -+{ -+ const struct drm_color_ctm *ctm = crtc_state->base.ctm->data; -+ const u64 *input; -+ u64 temp[9]; -+ int i; -+ -+ if (ilk_csc_limited_range(crtc_state)) -+ input = ctm_mult_by_limited(temp, ctm->matrix); -+ else -+ input = ctm->matrix; -+ -+ /* -+ * Convert fixed point S31.32 input to format supported by the -+ * hardware. -+ */ -+ for (i = 0; i < 9; i++) { -+ u64 abs_coeff = ((1ULL << 63) - 1) & input[i]; -+ -+ /* -+ * Clamp input value to min/max supported by -+ * hardware. -+ */ -+ abs_coeff = clamp_val(abs_coeff, 0, CTM_COEFF_4_0 - 1); -+ -+ coeffs[i] = 0; -+ -+ /* sign bit */ -+ if (CTM_COEFF_NEGATIVE(input[i])) -+ coeffs[i] |= 1 << 15; -+ -+ if (abs_coeff < CTM_COEFF_0_125) -+ coeffs[i] |= (3 << 12) | -+ ILK_CSC_COEFF_FP(abs_coeff, 12); -+ else if (abs_coeff < CTM_COEFF_0_25) -+ coeffs[i] |= (2 << 12) | -+ ILK_CSC_COEFF_FP(abs_coeff, 11); -+ else if (abs_coeff < CTM_COEFF_0_5) -+ coeffs[i] |= (1 << 12) | -+ ILK_CSC_COEFF_FP(abs_coeff, 10); -+ else if (abs_coeff < CTM_COEFF_1_0) -+ coeffs[i] |= ILK_CSC_COEFF_FP(abs_coeff, 9); -+ else if (abs_coeff < CTM_COEFF_2_0) -+ coeffs[i] |= (7 << 12) | -+ ILK_CSC_COEFF_FP(abs_coeff, 8); -+ else -+ coeffs[i] |= (6 << 12) | -+ ILK_CSC_COEFF_FP(abs_coeff, 7); -+ } -+} -+ -+static void ilk_load_csc_matrix(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ bool limited_color_range = ilk_csc_limited_range(crtc_state); -+ -+ if (crtc_state->base.ctm) { -+ u16 coeff[9]; -+ -+ ilk_csc_convert_ctm(crtc_state, coeff); -+ ilk_update_pipe_csc(crtc, ilk_csc_off_zero, coeff, -+ limited_color_range ? -+ ilk_csc_postoff_limited_range : -+ ilk_csc_off_zero); -+ } else if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB) { -+ ilk_update_pipe_csc(crtc, ilk_csc_off_zero, -+ ilk_csc_coeff_rgb_to_ycbcr, -+ ilk_csc_postoff_rgb_to_ycbcr); -+ } else if (limited_color_range) { -+ ilk_update_pipe_csc(crtc, ilk_csc_off_zero, -+ ilk_csc_coeff_limited_range, -+ ilk_csc_postoff_limited_range); -+ } else if (crtc_state->csc_enable) { -+ /* -+ * On GLK+ both pipe CSC and degamma LUT are controlled -+ * by csc_enable. Hence for the cases where the degama -+ * LUT is needed but CSC is not we need to load an -+ * identity matrix. -+ */ -+ WARN_ON(!IS_CANNONLAKE(dev_priv) && !IS_GEMINILAKE(dev_priv)); -+ -+ ilk_update_pipe_csc(crtc, ilk_csc_off_zero, -+ ilk_csc_coeff_identity, -+ ilk_csc_off_zero); -+ } -+ -+ I915_WRITE(PIPE_CSC_MODE(crtc->pipe), crtc_state->csc_mode); -+} -+ -+static void icl_load_csc_matrix(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ if (crtc_state->base.ctm) { -+ u16 coeff[9]; -+ -+ ilk_csc_convert_ctm(crtc_state, coeff); -+ ilk_update_pipe_csc(crtc, ilk_csc_off_zero, -+ coeff, ilk_csc_off_zero); -+ } -+ -+ if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB) { -+ icl_update_output_csc(crtc, ilk_csc_off_zero, -+ ilk_csc_coeff_rgb_to_ycbcr, -+ ilk_csc_postoff_rgb_to_ycbcr); -+ } else if (crtc_state->limited_color_range) { -+ icl_update_output_csc(crtc, ilk_csc_off_zero, -+ ilk_csc_coeff_limited_range, -+ ilk_csc_postoff_limited_range); -+ } -+ -+ I915_WRITE(PIPE_CSC_MODE(crtc->pipe), crtc_state->csc_mode); -+} -+ -+/* -+ * Set up the pipe CSC unit on CherryView. -+ */ -+static void cherryview_load_csc_matrix(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ -+ if (crtc_state->base.ctm) { -+ const struct drm_color_ctm *ctm = crtc_state->base.ctm->data; -+ u16 coeffs[9] = {}; -+ int i; -+ -+ for (i = 0; i < ARRAY_SIZE(coeffs); i++) { -+ u64 abs_coeff = -+ ((1ULL << 63) - 1) & ctm->matrix[i]; -+ -+ /* Round coefficient. */ -+ abs_coeff += 1 << (32 - 13); -+ /* Clamp to hardware limits. */ -+ abs_coeff = clamp_val(abs_coeff, 0, CTM_COEFF_8_0 - 1); -+ -+ /* Write coefficients in S3.12 format. */ -+ if (ctm->matrix[i] & (1ULL << 63)) -+ coeffs[i] = 1 << 15; -+ coeffs[i] |= ((abs_coeff >> 32) & 7) << 12; -+ coeffs[i] |= (abs_coeff >> 20) & 0xfff; -+ } -+ -+ I915_WRITE(CGM_PIPE_CSC_COEFF01(pipe), -+ coeffs[1] << 16 | coeffs[0]); -+ I915_WRITE(CGM_PIPE_CSC_COEFF23(pipe), -+ coeffs[3] << 16 | coeffs[2]); -+ I915_WRITE(CGM_PIPE_CSC_COEFF45(pipe), -+ coeffs[5] << 16 | coeffs[4]); -+ I915_WRITE(CGM_PIPE_CSC_COEFF67(pipe), -+ coeffs[7] << 16 | coeffs[6]); -+ I915_WRITE(CGM_PIPE_CSC_COEFF8(pipe), coeffs[8]); -+ } -+ -+ I915_WRITE(CGM_PIPE_MODE(pipe), crtc_state->cgm_mode); -+} -+ -+/* i965+ "10.6" bit interpolated format "even DW" (low 8 bits) */ -+static u32 i965_lut_10p6_ldw(const struct drm_color_lut *color) -+{ -+ return (color->red & 0xff) << 16 | -+ (color->green & 0xff) << 8 | -+ (color->blue & 0xff); -+} -+ -+/* i965+ "10.6" interpolated format "odd DW" (high 8 bits) */ -+static u32 i965_lut_10p6_udw(const struct drm_color_lut *color) -+{ -+ return (color->red >> 8) << 16 | -+ (color->green >> 8) << 8 | -+ (color->blue >> 8); -+} -+ -+static u32 ilk_lut_10(const struct drm_color_lut *color) -+{ -+ return drm_color_lut_extract(color->red, 10) << 20 | -+ drm_color_lut_extract(color->green, 10) << 10 | -+ drm_color_lut_extract(color->blue, 10); -+} -+ -+/* Loads the legacy palette/gamma unit for the CRTC. */ -+static void i9xx_load_luts_internal(const struct intel_crtc_state *crtc_state, -+ const struct drm_property_blob *blob) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ int i; -+ -+ if (HAS_GMCH(dev_priv)) { -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI)) -+ assert_dsi_pll_enabled(dev_priv); -+ else -+ assert_pll_enabled(dev_priv, pipe); -+ } -+ -+ if (blob) { -+ const struct drm_color_lut *lut = blob->data; -+ -+ for (i = 0; i < 256; i++) { -+ u32 word = -+ (drm_color_lut_extract(lut[i].red, 8) << 16) | -+ (drm_color_lut_extract(lut[i].green, 8) << 8) | -+ drm_color_lut_extract(lut[i].blue, 8); -+ -+ if (HAS_GMCH(dev_priv)) -+ I915_WRITE(PALETTE(pipe, i), word); -+ else -+ I915_WRITE(LGC_PALETTE(pipe, i), word); -+ } -+ } -+} -+ -+static void i9xx_load_luts(const struct intel_crtc_state *crtc_state) -+{ -+ i9xx_load_luts_internal(crtc_state, crtc_state->base.gamma_lut); -+} -+ -+static void i9xx_color_commit(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ u32 val; -+ -+ val = I915_READ(PIPECONF(pipe)); -+ val &= ~PIPECONF_GAMMA_MODE_MASK_I9XX; -+ val |= PIPECONF_GAMMA_MODE(crtc_state->gamma_mode); -+ I915_WRITE(PIPECONF(pipe), val); -+} -+ -+static void ilk_color_commit(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ u32 val; -+ -+ val = I915_READ(PIPECONF(pipe)); -+ val &= ~PIPECONF_GAMMA_MODE_MASK_ILK; -+ val |= PIPECONF_GAMMA_MODE(crtc_state->gamma_mode); -+ I915_WRITE(PIPECONF(pipe), val); -+ -+ ilk_load_csc_matrix(crtc_state); -+} -+ -+static void hsw_color_commit(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ I915_WRITE(GAMMA_MODE(crtc->pipe), crtc_state->gamma_mode); -+ -+ ilk_load_csc_matrix(crtc_state); -+} -+ -+static void skl_color_commit(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ u32 val = 0; -+ -+ /* -+ * We don't (yet) allow userspace to control the pipe background color, -+ * so force it to black, but apply pipe gamma and CSC appropriately -+ * so that its handling will match how we program our planes. -+ */ -+ if (crtc_state->gamma_enable) -+ val |= SKL_BOTTOM_COLOR_GAMMA_ENABLE; -+ if (crtc_state->csc_enable) -+ val |= SKL_BOTTOM_COLOR_CSC_ENABLE; -+ I915_WRITE(SKL_BOTTOM_COLOR(pipe), val); -+ -+ I915_WRITE(GAMMA_MODE(crtc->pipe), crtc_state->gamma_mode); -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ icl_load_csc_matrix(crtc_state); -+ else -+ ilk_load_csc_matrix(crtc_state); -+} -+ -+static void i965_load_lut_10p6(struct intel_crtc *crtc, -+ const struct drm_property_blob *blob) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ const struct drm_color_lut *lut = blob->data; -+ int i, lut_size = drm_color_lut_size(blob); -+ enum pipe pipe = crtc->pipe; -+ -+ for (i = 0; i < lut_size - 1; i++) { -+ I915_WRITE(PALETTE(pipe, 2 * i + 0), -+ i965_lut_10p6_ldw(&lut[i])); -+ I915_WRITE(PALETTE(pipe, 2 * i + 1), -+ i965_lut_10p6_udw(&lut[i])); -+ } -+ -+ I915_WRITE(PIPEGCMAX(pipe, 0), lut[i].red); -+ I915_WRITE(PIPEGCMAX(pipe, 1), lut[i].green); -+ I915_WRITE(PIPEGCMAX(pipe, 2), lut[i].blue); -+} -+ -+static void i965_load_luts(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ const struct drm_property_blob *gamma_lut = crtc_state->base.gamma_lut; -+ -+ if (crtc_state->gamma_mode == GAMMA_MODE_MODE_8BIT) -+ i9xx_load_luts(crtc_state); -+ else -+ i965_load_lut_10p6(crtc, gamma_lut); -+} -+ -+static void ilk_load_lut_10(struct intel_crtc *crtc, -+ const struct drm_property_blob *blob) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ const struct drm_color_lut *lut = blob->data; -+ int i, lut_size = drm_color_lut_size(blob); -+ enum pipe pipe = crtc->pipe; -+ -+ for (i = 0; i < lut_size; i++) -+ I915_WRITE(PREC_PALETTE(pipe, i), ilk_lut_10(&lut[i])); -+} -+ -+static void ilk_load_luts(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ const struct drm_property_blob *gamma_lut = crtc_state->base.gamma_lut; -+ -+ if (crtc_state->gamma_mode == GAMMA_MODE_MODE_8BIT) -+ i9xx_load_luts(crtc_state); -+ else -+ ilk_load_lut_10(crtc, gamma_lut); -+} -+ -+static int ivb_lut_10_size(u32 prec_index) -+{ -+ if (prec_index & PAL_PREC_SPLIT_MODE) -+ return 512; -+ else -+ return 1024; -+} -+ -+/* -+ * IVB/HSW Bspec / PAL_PREC_INDEX: -+ * "Restriction : Index auto increment mode is not -+ * supported and must not be enabled." -+ */ -+static void ivb_load_lut_10(struct intel_crtc *crtc, -+ const struct drm_property_blob *blob, -+ u32 prec_index) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ int hw_lut_size = ivb_lut_10_size(prec_index); -+ const struct drm_color_lut *lut = blob->data; -+ int i, lut_size = drm_color_lut_size(blob); -+ enum pipe pipe = crtc->pipe; -+ -+ for (i = 0; i < hw_lut_size; i++) { -+ /* We discard half the user entries in split gamma mode */ -+ const struct drm_color_lut *entry = -+ &lut[i * (lut_size - 1) / (hw_lut_size - 1)]; -+ -+ I915_WRITE(PREC_PAL_INDEX(pipe), prec_index++); -+ I915_WRITE(PREC_PAL_DATA(pipe), ilk_lut_10(entry)); -+ } -+ -+ /* -+ * Reset the index, otherwise it prevents the legacy palette to be -+ * written properly. -+ */ -+ I915_WRITE(PREC_PAL_INDEX(pipe), 0); -+} -+ -+/* On BDW+ the index auto increment mode actually works */ -+static void bdw_load_lut_10(struct intel_crtc *crtc, -+ const struct drm_property_blob *blob, -+ u32 prec_index) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ int hw_lut_size = ivb_lut_10_size(prec_index); -+ const struct drm_color_lut *lut = blob->data; -+ int i, lut_size = drm_color_lut_size(blob); -+ enum pipe pipe = crtc->pipe; -+ -+ I915_WRITE(PREC_PAL_INDEX(pipe), prec_index | -+ PAL_PREC_AUTO_INCREMENT); -+ -+ for (i = 0; i < hw_lut_size; i++) { -+ /* We discard half the user entries in split gamma mode */ -+ const struct drm_color_lut *entry = -+ &lut[i * (lut_size - 1) / (hw_lut_size - 1)]; -+ -+ I915_WRITE(PREC_PAL_DATA(pipe), ilk_lut_10(entry)); -+ } -+ -+ /* -+ * Reset the index, otherwise it prevents the legacy palette to be -+ * written properly. -+ */ -+ I915_WRITE(PREC_PAL_INDEX(pipe), 0); -+} -+ -+static void ivb_load_lut_10_max(struct intel_crtc *crtc) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ -+ /* Program the max register to clamp values > 1.0. */ -+ I915_WRITE(PREC_PAL_EXT_GC_MAX(pipe, 0), 1 << 16); -+ I915_WRITE(PREC_PAL_EXT_GC_MAX(pipe, 1), 1 << 16); -+ I915_WRITE(PREC_PAL_EXT_GC_MAX(pipe, 2), 1 << 16); -+ -+ /* -+ * Program the gc max 2 register to clamp values > 1.0. -+ * ToDo: Extend the ABI to be able to program values -+ * from 3.0 to 7.0 -+ */ -+ if (INTEL_GEN(dev_priv) >= 10 || IS_GEMINILAKE(dev_priv)) { -+ I915_WRITE(PREC_PAL_EXT2_GC_MAX(pipe, 0), 1 << 16); -+ I915_WRITE(PREC_PAL_EXT2_GC_MAX(pipe, 1), 1 << 16); -+ I915_WRITE(PREC_PAL_EXT2_GC_MAX(pipe, 2), 1 << 16); -+ } -+} -+ -+static void ivb_load_luts(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ const struct drm_property_blob *gamma_lut = crtc_state->base.gamma_lut; -+ const struct drm_property_blob *degamma_lut = crtc_state->base.degamma_lut; -+ -+ if (crtc_state->gamma_mode == GAMMA_MODE_MODE_8BIT) { -+ i9xx_load_luts(crtc_state); -+ } else if (crtc_state->gamma_mode == GAMMA_MODE_MODE_SPLIT) { -+ ivb_load_lut_10(crtc, degamma_lut, PAL_PREC_SPLIT_MODE | -+ PAL_PREC_INDEX_VALUE(0)); -+ ivb_load_lut_10_max(crtc); -+ ivb_load_lut_10(crtc, gamma_lut, PAL_PREC_SPLIT_MODE | -+ PAL_PREC_INDEX_VALUE(512)); -+ } else { -+ const struct drm_property_blob *blob = gamma_lut ?: degamma_lut; -+ -+ ivb_load_lut_10(crtc, blob, -+ PAL_PREC_INDEX_VALUE(0)); -+ ivb_load_lut_10_max(crtc); -+ } -+} -+ -+static void bdw_load_luts(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ const struct drm_property_blob *gamma_lut = crtc_state->base.gamma_lut; -+ const struct drm_property_blob *degamma_lut = crtc_state->base.degamma_lut; -+ -+ if (crtc_state->gamma_mode == GAMMA_MODE_MODE_8BIT) { -+ i9xx_load_luts(crtc_state); -+ } else if (crtc_state->gamma_mode == GAMMA_MODE_MODE_SPLIT) { -+ bdw_load_lut_10(crtc, degamma_lut, PAL_PREC_SPLIT_MODE | -+ PAL_PREC_INDEX_VALUE(0)); -+ ivb_load_lut_10_max(crtc); -+ bdw_load_lut_10(crtc, gamma_lut, PAL_PREC_SPLIT_MODE | -+ PAL_PREC_INDEX_VALUE(512)); -+ } else { -+ const struct drm_property_blob *blob = gamma_lut ?: degamma_lut; -+ -+ bdw_load_lut_10(crtc, blob, -+ PAL_PREC_INDEX_VALUE(0)); -+ ivb_load_lut_10_max(crtc); -+ } -+} -+ -+static void glk_load_degamma_lut(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ const u32 lut_size = INTEL_INFO(dev_priv)->color.degamma_lut_size; -+ const struct drm_color_lut *lut = crtc_state->base.degamma_lut->data; -+ u32 i; -+ -+ /* -+ * When setting the auto-increment bit, the hardware seems to -+ * ignore the index bits, so we need to reset it to index 0 -+ * separately. -+ */ -+ I915_WRITE(PRE_CSC_GAMC_INDEX(pipe), 0); -+ I915_WRITE(PRE_CSC_GAMC_INDEX(pipe), PRE_CSC_GAMC_AUTO_INCREMENT); -+ -+ for (i = 0; i < lut_size; i++) { -+ /* -+ * First 33 entries represent range from 0 to 1.0 -+ * 34th and 35th entry will represent extended range -+ * inputs 3.0 and 7.0 respectively, currently clamped -+ * at 1.0. Since the precision is 16bit, the user -+ * value can be directly filled to register. -+ * The pipe degamma table in GLK+ onwards doesn't -+ * support different values per channel, so this just -+ * programs green value which will be equal to Red and -+ * Blue into the lut registers. -+ * ToDo: Extend to max 7.0. Enable 32 bit input value -+ * as compared to just 16 to achieve this. -+ */ -+ I915_WRITE(PRE_CSC_GAMC_DATA(pipe), lut[i].green); -+ } -+ -+ /* Clamp values > 1.0. */ -+ while (i++ < 35) -+ I915_WRITE(PRE_CSC_GAMC_DATA(pipe), 1 << 16); -+} -+ -+static void glk_load_degamma_lut_linear(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ const u32 lut_size = INTEL_INFO(dev_priv)->color.degamma_lut_size; -+ u32 i; -+ -+ /* -+ * When setting the auto-increment bit, the hardware seems to -+ * ignore the index bits, so we need to reset it to index 0 -+ * separately. -+ */ -+ I915_WRITE(PRE_CSC_GAMC_INDEX(pipe), 0); -+ I915_WRITE(PRE_CSC_GAMC_INDEX(pipe), PRE_CSC_GAMC_AUTO_INCREMENT); -+ -+ for (i = 0; i < lut_size; i++) { -+ u32 v = (i << 16) / (lut_size - 1); -+ -+ I915_WRITE(PRE_CSC_GAMC_DATA(pipe), v); -+ } -+ -+ /* Clamp values > 1.0. */ -+ while (i++ < 35) -+ I915_WRITE(PRE_CSC_GAMC_DATA(pipe), 1 << 16); -+} -+ -+static void glk_load_luts(const struct intel_crtc_state *crtc_state) -+{ -+ const struct drm_property_blob *gamma_lut = crtc_state->base.gamma_lut; -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ -+ /* -+ * On GLK+ both pipe CSC and degamma LUT are controlled -+ * by csc_enable. Hence for the cases where the CSC is -+ * needed but degamma LUT is not we need to load a -+ * linear degamma LUT. In fact we'll just always load -+ * the degama LUT so that we don't have to reload -+ * it every time the pipe CSC is being enabled. -+ */ -+ if (crtc_state->base.degamma_lut) -+ glk_load_degamma_lut(crtc_state); -+ else -+ glk_load_degamma_lut_linear(crtc_state); -+ -+ if (crtc_state->gamma_mode == GAMMA_MODE_MODE_8BIT) { -+ i9xx_load_luts(crtc_state); -+ } else { -+ bdw_load_lut_10(crtc, gamma_lut, PAL_PREC_INDEX_VALUE(0)); -+ ivb_load_lut_10_max(crtc); -+ } -+} -+ -+static void icl_load_luts(const struct intel_crtc_state *crtc_state) -+{ -+ const struct drm_property_blob *gamma_lut = crtc_state->base.gamma_lut; -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ -+ if (crtc_state->base.degamma_lut) -+ glk_load_degamma_lut(crtc_state); -+ -+ if ((crtc_state->gamma_mode & GAMMA_MODE_MODE_MASK) == -+ GAMMA_MODE_MODE_8BIT) { -+ i9xx_load_luts(crtc_state); -+ } else { -+ bdw_load_lut_10(crtc, gamma_lut, PAL_PREC_INDEX_VALUE(0)); -+ ivb_load_lut_10_max(crtc); -+ } -+} -+ -+static void cherryview_load_luts(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ const struct drm_property_blob *gamma_lut = crtc_state->base.gamma_lut; -+ const struct drm_property_blob *degamma_lut = crtc_state->base.degamma_lut; -+ enum pipe pipe = crtc->pipe; -+ -+ cherryview_load_csc_matrix(crtc_state); -+ -+ if (crtc_state_is_legacy_gamma(crtc_state)) { -+ i9xx_load_luts(crtc_state); -+ return; -+ } -+ -+ if (degamma_lut) { -+ const struct drm_color_lut *lut = degamma_lut->data; -+ int i, lut_size = INTEL_INFO(dev_priv)->color.degamma_lut_size; -+ -+ for (i = 0; i < lut_size; i++) { -+ u32 word0, word1; -+ -+ /* Write LUT in U0.14 format. */ -+ word0 = -+ (drm_color_lut_extract(lut[i].green, 14) << 16) | -+ drm_color_lut_extract(lut[i].blue, 14); -+ word1 = drm_color_lut_extract(lut[i].red, 14); -+ -+ I915_WRITE(CGM_PIPE_DEGAMMA(pipe, i, 0), word0); -+ I915_WRITE(CGM_PIPE_DEGAMMA(pipe, i, 1), word1); -+ } -+ } -+ -+ if (gamma_lut) { -+ const struct drm_color_lut *lut = gamma_lut->data; -+ int i, lut_size = INTEL_INFO(dev_priv)->color.gamma_lut_size; -+ -+ for (i = 0; i < lut_size; i++) { -+ u32 word0, word1; -+ -+ /* Write LUT in U0.10 format. */ -+ word0 = -+ (drm_color_lut_extract(lut[i].green, 10) << 16) | -+ drm_color_lut_extract(lut[i].blue, 10); -+ word1 = drm_color_lut_extract(lut[i].red, 10); -+ -+ I915_WRITE(CGM_PIPE_GAMMA(pipe, i, 0), word0); -+ I915_WRITE(CGM_PIPE_GAMMA(pipe, i, 1), word1); -+ } -+ } -+} -+ -+void intel_color_load_luts(const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ -+ dev_priv->display.load_luts(crtc_state); -+} -+ -+void intel_color_commit(const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ -+ dev_priv->display.color_commit(crtc_state); -+} -+ -+int intel_color_check(struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ -+ return dev_priv->display.color_check(crtc_state); -+} -+ -+static bool need_plane_update(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(plane->base.dev); -+ -+ /* -+ * On pre-SKL the pipe gamma enable and pipe csc enable for -+ * the pipe bottom color are configured via the primary plane. -+ * We have to reconfigure that even if the plane is inactive. -+ */ -+ return crtc_state->active_planes & BIT(plane->id) || -+ (INTEL_GEN(dev_priv) < 9 && -+ plane->id == PLANE_PRIMARY); -+} -+ -+static int -+intel_color_add_affected_planes(struct intel_crtc_state *new_crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct intel_atomic_state *state = -+ to_intel_atomic_state(new_crtc_state->base.state); -+ const struct intel_crtc_state *old_crtc_state = -+ intel_atomic_get_old_crtc_state(state, crtc); -+ struct intel_plane *plane; -+ -+ if (!new_crtc_state->base.active || -+ drm_atomic_crtc_needs_modeset(&new_crtc_state->base)) -+ return 0; -+ -+ if (new_crtc_state->gamma_enable == old_crtc_state->gamma_enable && -+ new_crtc_state->csc_enable == old_crtc_state->csc_enable) -+ return 0; -+ -+ for_each_intel_plane_on_crtc(&dev_priv->drm, crtc, plane) { -+ struct intel_plane_state *plane_state; -+ -+ if (!need_plane_update(plane, new_crtc_state)) -+ continue; -+ -+ plane_state = intel_atomic_get_plane_state(state, plane); -+ if (IS_ERR(plane_state)) -+ return PTR_ERR(plane_state); -+ -+ new_crtc_state->update_planes |= BIT(plane->id); -+ } -+ -+ return 0; -+} -+ -+static int check_lut_size(const struct drm_property_blob *lut, int expected) -+{ -+ int len; -+ -+ if (!lut) -+ return 0; -+ -+ len = drm_color_lut_size(lut); -+ if (len != expected) { -+ DRM_DEBUG_KMS("Invalid LUT size; got %d, expected %d\n", -+ len, expected); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int check_luts(const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ const struct drm_property_blob *gamma_lut = crtc_state->base.gamma_lut; -+ const struct drm_property_blob *degamma_lut = crtc_state->base.degamma_lut; -+ int gamma_length, degamma_length; -+ u32 gamma_tests, degamma_tests; -+ -+ /* Always allow legacy gamma LUT with no further checking. */ -+ if (crtc_state_is_legacy_gamma(crtc_state)) -+ return 0; -+ -+ /* C8 relies on its palette being stored in the legacy LUT */ -+ if (crtc_state->c8_planes) -+ return -EINVAL; -+ -+ degamma_length = INTEL_INFO(dev_priv)->color.degamma_lut_size; -+ gamma_length = INTEL_INFO(dev_priv)->color.gamma_lut_size; -+ degamma_tests = INTEL_INFO(dev_priv)->color.degamma_lut_tests; -+ gamma_tests = INTEL_INFO(dev_priv)->color.gamma_lut_tests; -+ -+ if (check_lut_size(degamma_lut, degamma_length) || -+ check_lut_size(gamma_lut, gamma_length)) -+ return -EINVAL; -+ -+ if (drm_color_lut_check(degamma_lut, degamma_tests) || -+ drm_color_lut_check(gamma_lut, gamma_tests)) -+ return -EINVAL; -+ -+ return 0; -+} -+ -+static u32 i9xx_gamma_mode(struct intel_crtc_state *crtc_state) -+{ -+ if (!crtc_state->gamma_enable || -+ crtc_state_is_legacy_gamma(crtc_state)) -+ return GAMMA_MODE_MODE_8BIT; -+ else -+ return GAMMA_MODE_MODE_10BIT; /* i965+ only */ -+} -+ -+static int i9xx_color_check(struct intel_crtc_state *crtc_state) -+{ -+ int ret; -+ -+ ret = check_luts(crtc_state); -+ if (ret) -+ return ret; -+ -+ crtc_state->gamma_enable = -+ crtc_state->base.gamma_lut && -+ !crtc_state->c8_planes; -+ -+ crtc_state->gamma_mode = i9xx_gamma_mode(crtc_state); -+ -+ ret = intel_color_add_affected_planes(crtc_state); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static u32 chv_cgm_mode(const struct intel_crtc_state *crtc_state) -+{ -+ u32 cgm_mode = 0; -+ -+ if (crtc_state_is_legacy_gamma(crtc_state)) -+ return 0; -+ -+ if (crtc_state->base.degamma_lut) -+ cgm_mode |= CGM_PIPE_MODE_DEGAMMA; -+ if (crtc_state->base.ctm) -+ cgm_mode |= CGM_PIPE_MODE_CSC; -+ if (crtc_state->base.gamma_lut) -+ cgm_mode |= CGM_PIPE_MODE_GAMMA; -+ -+ return cgm_mode; -+} -+ -+/* -+ * CHV color pipeline: -+ * u0.10 -> CGM degamma -> u0.14 -> CGM csc -> u0.14 -> CGM gamma -> -+ * u0.10 -> WGC csc -> u0.10 -> pipe gamma -> u0.10 -+ * -+ * We always bypass the WGC csc and use the CGM csc -+ * instead since it has degamma and better precision. -+ */ -+static int chv_color_check(struct intel_crtc_state *crtc_state) -+{ -+ int ret; -+ -+ ret = check_luts(crtc_state); -+ if (ret) -+ return ret; -+ -+ /* -+ * Pipe gamma will be used only for the legacy LUT. -+ * Otherwise we bypass it and use the CGM gamma instead. -+ */ -+ crtc_state->gamma_enable = -+ crtc_state_is_legacy_gamma(crtc_state) && -+ !crtc_state->c8_planes; -+ -+ crtc_state->gamma_mode = GAMMA_MODE_MODE_8BIT; -+ -+ crtc_state->cgm_mode = chv_cgm_mode(crtc_state); -+ -+ ret = intel_color_add_affected_planes(crtc_state); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static u32 ilk_gamma_mode(const struct intel_crtc_state *crtc_state) -+{ -+ if (!crtc_state->gamma_enable || -+ crtc_state_is_legacy_gamma(crtc_state)) -+ return GAMMA_MODE_MODE_8BIT; -+ else -+ return GAMMA_MODE_MODE_10BIT; -+} -+ -+static int ilk_color_check(struct intel_crtc_state *crtc_state) -+{ -+ int ret; -+ -+ ret = check_luts(crtc_state); -+ if (ret) -+ return ret; -+ -+ crtc_state->gamma_enable = -+ crtc_state->base.gamma_lut && -+ !crtc_state->c8_planes; -+ -+ /* -+ * We don't expose the ctm on ilk/snb currently, -+ * nor do we enable YCbCr output. Also RGB limited -+ * range output is handled by the hw automagically. -+ */ -+ crtc_state->csc_enable = false; -+ -+ crtc_state->gamma_mode = ilk_gamma_mode(crtc_state); -+ -+ crtc_state->csc_mode = 0; -+ -+ ret = intel_color_add_affected_planes(crtc_state); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static u32 ivb_gamma_mode(const struct intel_crtc_state *crtc_state) -+{ -+ if (!crtc_state->gamma_enable || -+ crtc_state_is_legacy_gamma(crtc_state)) -+ return GAMMA_MODE_MODE_8BIT; -+ else if (crtc_state->base.gamma_lut && -+ crtc_state->base.degamma_lut) -+ return GAMMA_MODE_MODE_SPLIT; -+ else -+ return GAMMA_MODE_MODE_10BIT; -+} -+ -+static u32 ivb_csc_mode(const struct intel_crtc_state *crtc_state) -+{ -+ bool limited_color_range = ilk_csc_limited_range(crtc_state); -+ -+ /* -+ * CSC comes after the LUT in degamma, RGB->YCbCr, -+ * and RGB full->limited range mode. -+ */ -+ if (crtc_state->base.degamma_lut || -+ crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB || -+ limited_color_range) -+ return 0; -+ -+ return CSC_POSITION_BEFORE_GAMMA; -+} -+ -+static int ivb_color_check(struct intel_crtc_state *crtc_state) -+{ -+ bool limited_color_range = ilk_csc_limited_range(crtc_state); -+ int ret; -+ -+ ret = check_luts(crtc_state); -+ if (ret) -+ return ret; -+ -+ crtc_state->gamma_enable = -+ (crtc_state->base.gamma_lut || -+ crtc_state->base.degamma_lut) && -+ !crtc_state->c8_planes; -+ -+ crtc_state->csc_enable = -+ crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB || -+ crtc_state->base.ctm || limited_color_range; -+ -+ crtc_state->gamma_mode = ivb_gamma_mode(crtc_state); -+ -+ crtc_state->csc_mode = ivb_csc_mode(crtc_state); -+ -+ ret = intel_color_add_affected_planes(crtc_state); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static u32 glk_gamma_mode(const struct intel_crtc_state *crtc_state) -+{ -+ if (!crtc_state->gamma_enable || -+ crtc_state_is_legacy_gamma(crtc_state)) -+ return GAMMA_MODE_MODE_8BIT; -+ else -+ return GAMMA_MODE_MODE_10BIT; -+} -+ -+static int glk_color_check(struct intel_crtc_state *crtc_state) -+{ -+ int ret; -+ -+ ret = check_luts(crtc_state); -+ if (ret) -+ return ret; -+ -+ crtc_state->gamma_enable = -+ crtc_state->base.gamma_lut && -+ !crtc_state->c8_planes; -+ -+ /* On GLK+ degamma LUT is controlled by csc_enable */ -+ crtc_state->csc_enable = -+ crtc_state->base.degamma_lut || -+ crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB || -+ crtc_state->base.ctm || crtc_state->limited_color_range; -+ -+ crtc_state->gamma_mode = glk_gamma_mode(crtc_state); -+ -+ crtc_state->csc_mode = 0; -+ -+ ret = intel_color_add_affected_planes(crtc_state); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static u32 icl_gamma_mode(const struct intel_crtc_state *crtc_state) -+{ -+ u32 gamma_mode = 0; -+ -+ if (crtc_state->base.degamma_lut) -+ gamma_mode |= PRE_CSC_GAMMA_ENABLE; -+ -+ if (crtc_state->base.gamma_lut && -+ !crtc_state->c8_planes) -+ gamma_mode |= POST_CSC_GAMMA_ENABLE; -+ -+ if (!crtc_state->base.gamma_lut || -+ crtc_state_is_legacy_gamma(crtc_state)) -+ gamma_mode |= GAMMA_MODE_MODE_8BIT; -+ else -+ gamma_mode |= GAMMA_MODE_MODE_10BIT; -+ -+ return gamma_mode; -+} -+ -+static u32 icl_csc_mode(const struct intel_crtc_state *crtc_state) -+{ -+ u32 csc_mode = 0; -+ -+ if (crtc_state->base.ctm) -+ csc_mode |= ICL_CSC_ENABLE; -+ -+ if (crtc_state->output_format != INTEL_OUTPUT_FORMAT_RGB || -+ crtc_state->limited_color_range) -+ csc_mode |= ICL_OUTPUT_CSC_ENABLE; -+ -+ return csc_mode; -+} -+ -+static int icl_color_check(struct intel_crtc_state *crtc_state) -+{ -+ int ret; -+ -+ ret = check_luts(crtc_state); -+ if (ret) -+ return ret; -+ -+ crtc_state->gamma_mode = icl_gamma_mode(crtc_state); -+ -+ crtc_state->csc_mode = icl_csc_mode(crtc_state); -+ -+ return 0; -+} -+ -+void intel_color_init(struct intel_crtc *crtc) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ bool has_ctm = INTEL_INFO(dev_priv)->color.degamma_lut_size != 0; -+ -+ drm_mode_crtc_set_gamma_size(&crtc->base, 256); -+ -+ if (HAS_GMCH(dev_priv)) { -+ if (IS_CHERRYVIEW(dev_priv)) { -+ dev_priv->display.color_check = chv_color_check; -+ dev_priv->display.color_commit = i9xx_color_commit; -+ dev_priv->display.load_luts = cherryview_load_luts; -+ } else if (INTEL_GEN(dev_priv) >= 4) { -+ dev_priv->display.color_check = i9xx_color_check; -+ dev_priv->display.color_commit = i9xx_color_commit; -+ dev_priv->display.load_luts = i965_load_luts; -+ } else { -+ dev_priv->display.color_check = i9xx_color_check; -+ dev_priv->display.color_commit = i9xx_color_commit; -+ dev_priv->display.load_luts = i9xx_load_luts; -+ } -+ } else { -+ if (INTEL_GEN(dev_priv) >= 11) -+ dev_priv->display.color_check = icl_color_check; -+ else if (INTEL_GEN(dev_priv) >= 10 || IS_GEMINILAKE(dev_priv)) -+ dev_priv->display.color_check = glk_color_check; -+ else if (INTEL_GEN(dev_priv) >= 7) -+ dev_priv->display.color_check = ivb_color_check; -+ else -+ dev_priv->display.color_check = ilk_color_check; -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ dev_priv->display.color_commit = skl_color_commit; -+ else if (IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) -+ dev_priv->display.color_commit = hsw_color_commit; -+ else -+ dev_priv->display.color_commit = ilk_color_commit; -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ dev_priv->display.load_luts = icl_load_luts; -+ else if (IS_CANNONLAKE(dev_priv) || IS_GEMINILAKE(dev_priv)) -+ dev_priv->display.load_luts = glk_load_luts; -+ else if (INTEL_GEN(dev_priv) >= 8) -+ dev_priv->display.load_luts = bdw_load_luts; -+ else if (INTEL_GEN(dev_priv) >= 7) -+ dev_priv->display.load_luts = ivb_load_luts; -+ else -+ dev_priv->display.load_luts = ilk_load_luts; -+ } -+ -+ drm_crtc_enable_color_mgmt(&crtc->base, -+ INTEL_INFO(dev_priv)->color.degamma_lut_size, -+ has_ctm, -+ INTEL_INFO(dev_priv)->color.gamma_lut_size); -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_color.h b/drivers/gpu/drm/i915_legacy/intel_color.h -new file mode 100644 -index 000000000000..b8a3ce609587 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_color.h -@@ -0,0 +1,17 @@ -+/* SPDX-License-Identifier: MIT */ -+/* -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef __INTEL_COLOR_H__ -+#define __INTEL_COLOR_H__ -+ -+struct intel_crtc_state; -+struct intel_crtc; -+ -+void intel_color_init(struct intel_crtc *crtc); -+int intel_color_check(struct intel_crtc_state *crtc_state); -+void intel_color_commit(const struct intel_crtc_state *crtc_state); -+void intel_color_load_luts(const struct intel_crtc_state *crtc_state); -+ -+#endif /* __INTEL_COLOR_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_combo_phy.c b/drivers/gpu/drm/i915_legacy/intel_combo_phy.c -new file mode 100644 -index 000000000000..2bf4359d7e41 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_combo_phy.c -@@ -0,0 +1,255 @@ -+// SPDX-License-Identifier: MIT -+/* -+ * Copyright © 2018 Intel Corporation -+ */ -+ -+#include "intel_drv.h" -+ -+#define for_each_combo_port(__dev_priv, __port) \ -+ for ((__port) = PORT_A; (__port) < I915_MAX_PORTS; (__port)++) \ -+ for_each_if(intel_port_is_combophy(__dev_priv, __port)) -+ -+#define for_each_combo_port_reverse(__dev_priv, __port) \ -+ for ((__port) = I915_MAX_PORTS; (__port)-- > PORT_A;) \ -+ for_each_if(intel_port_is_combophy(__dev_priv, __port)) -+ -+enum { -+ PROCMON_0_85V_DOT_0, -+ PROCMON_0_95V_DOT_0, -+ PROCMON_0_95V_DOT_1, -+ PROCMON_1_05V_DOT_0, -+ PROCMON_1_05V_DOT_1, -+}; -+ -+static const struct cnl_procmon { -+ u32 dw1, dw9, dw10; -+} cnl_procmon_values[] = { -+ [PROCMON_0_85V_DOT_0] = -+ { .dw1 = 0x00000000, .dw9 = 0x62AB67BB, .dw10 = 0x51914F96, }, -+ [PROCMON_0_95V_DOT_0] = -+ { .dw1 = 0x00000000, .dw9 = 0x86E172C7, .dw10 = 0x77CA5EAB, }, -+ [PROCMON_0_95V_DOT_1] = -+ { .dw1 = 0x00000000, .dw9 = 0x93F87FE1, .dw10 = 0x8AE871C5, }, -+ [PROCMON_1_05V_DOT_0] = -+ { .dw1 = 0x00000000, .dw9 = 0x98FA82DD, .dw10 = 0x89E46DC1, }, -+ [PROCMON_1_05V_DOT_1] = -+ { .dw1 = 0x00440000, .dw9 = 0x9A00AB25, .dw10 = 0x8AE38FF1, }, -+}; -+ -+/* -+ * CNL has just one set of registers, while ICL has two sets: one for port A and -+ * the other for port B. The CNL registers are equivalent to the ICL port A -+ * registers, that's why we call the ICL macros even though the function has CNL -+ * on its name. -+ */ -+static const struct cnl_procmon * -+cnl_get_procmon_ref_values(struct drm_i915_private *dev_priv, enum port port) -+{ -+ const struct cnl_procmon *procmon; -+ u32 val; -+ -+ val = I915_READ(ICL_PORT_COMP_DW3(port)); -+ switch (val & (PROCESS_INFO_MASK | VOLTAGE_INFO_MASK)) { -+ default: -+ MISSING_CASE(val); -+ /* fall through */ -+ case VOLTAGE_INFO_0_85V | PROCESS_INFO_DOT_0: -+ procmon = &cnl_procmon_values[PROCMON_0_85V_DOT_0]; -+ break; -+ case VOLTAGE_INFO_0_95V | PROCESS_INFO_DOT_0: -+ procmon = &cnl_procmon_values[PROCMON_0_95V_DOT_0]; -+ break; -+ case VOLTAGE_INFO_0_95V | PROCESS_INFO_DOT_1: -+ procmon = &cnl_procmon_values[PROCMON_0_95V_DOT_1]; -+ break; -+ case VOLTAGE_INFO_1_05V | PROCESS_INFO_DOT_0: -+ procmon = &cnl_procmon_values[PROCMON_1_05V_DOT_0]; -+ break; -+ case VOLTAGE_INFO_1_05V | PROCESS_INFO_DOT_1: -+ procmon = &cnl_procmon_values[PROCMON_1_05V_DOT_1]; -+ break; -+ } -+ -+ return procmon; -+} -+ -+static void cnl_set_procmon_ref_values(struct drm_i915_private *dev_priv, -+ enum port port) -+{ -+ const struct cnl_procmon *procmon; -+ u32 val; -+ -+ procmon = cnl_get_procmon_ref_values(dev_priv, port); -+ -+ val = I915_READ(ICL_PORT_COMP_DW1(port)); -+ val &= ~((0xff << 16) | 0xff); -+ val |= procmon->dw1; -+ I915_WRITE(ICL_PORT_COMP_DW1(port), val); -+ -+ I915_WRITE(ICL_PORT_COMP_DW9(port), procmon->dw9); -+ I915_WRITE(ICL_PORT_COMP_DW10(port), procmon->dw10); -+} -+ -+static bool check_phy_reg(struct drm_i915_private *dev_priv, -+ enum port port, i915_reg_t reg, u32 mask, -+ u32 expected_val) -+{ -+ u32 val = I915_READ(reg); -+ -+ if ((val & mask) != expected_val) { -+ DRM_DEBUG_DRIVER("Port %c combo PHY reg %08x state mismatch: " -+ "current %08x mask %08x expected %08x\n", -+ port_name(port), -+ reg.reg, val, mask, expected_val); -+ return false; -+ } -+ -+ return true; -+} -+ -+static bool cnl_verify_procmon_ref_values(struct drm_i915_private *dev_priv, -+ enum port port) -+{ -+ const struct cnl_procmon *procmon; -+ bool ret; -+ -+ procmon = cnl_get_procmon_ref_values(dev_priv, port); -+ -+ ret = check_phy_reg(dev_priv, port, ICL_PORT_COMP_DW1(port), -+ (0xff << 16) | 0xff, procmon->dw1); -+ ret &= check_phy_reg(dev_priv, port, ICL_PORT_COMP_DW9(port), -+ -1U, procmon->dw9); -+ ret &= check_phy_reg(dev_priv, port, ICL_PORT_COMP_DW10(port), -+ -1U, procmon->dw10); -+ -+ return ret; -+} -+ -+static bool cnl_combo_phy_enabled(struct drm_i915_private *dev_priv) -+{ -+ return !(I915_READ(CHICKEN_MISC_2) & CNL_COMP_PWR_DOWN) && -+ (I915_READ(CNL_PORT_COMP_DW0) & COMP_INIT); -+} -+ -+static bool cnl_combo_phy_verify_state(struct drm_i915_private *dev_priv) -+{ -+ enum port port = PORT_A; -+ bool ret; -+ -+ if (!cnl_combo_phy_enabled(dev_priv)) -+ return false; -+ -+ ret = cnl_verify_procmon_ref_values(dev_priv, port); -+ -+ ret &= check_phy_reg(dev_priv, port, CNL_PORT_CL1CM_DW5, -+ CL_POWER_DOWN_ENABLE, CL_POWER_DOWN_ENABLE); -+ -+ return ret; -+} -+ -+void cnl_combo_phys_init(struct drm_i915_private *dev_priv) -+{ -+ u32 val; -+ -+ val = I915_READ(CHICKEN_MISC_2); -+ val &= ~CNL_COMP_PWR_DOWN; -+ I915_WRITE(CHICKEN_MISC_2, val); -+ -+ /* Dummy PORT_A to get the correct CNL register from the ICL macro */ -+ cnl_set_procmon_ref_values(dev_priv, PORT_A); -+ -+ val = I915_READ(CNL_PORT_COMP_DW0); -+ val |= COMP_INIT; -+ I915_WRITE(CNL_PORT_COMP_DW0, val); -+ -+ val = I915_READ(CNL_PORT_CL1CM_DW5); -+ val |= CL_POWER_DOWN_ENABLE; -+ I915_WRITE(CNL_PORT_CL1CM_DW5, val); -+} -+ -+void cnl_combo_phys_uninit(struct drm_i915_private *dev_priv) -+{ -+ u32 val; -+ -+ if (!cnl_combo_phy_verify_state(dev_priv)) -+ DRM_WARN("Combo PHY HW state changed unexpectedly.\n"); -+ -+ val = I915_READ(CHICKEN_MISC_2); -+ val |= CNL_COMP_PWR_DOWN; -+ I915_WRITE(CHICKEN_MISC_2, val); -+} -+ -+static bool icl_combo_phy_enabled(struct drm_i915_private *dev_priv, -+ enum port port) -+{ -+ return !(I915_READ(ICL_PHY_MISC(port)) & -+ ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN) && -+ (I915_READ(ICL_PORT_COMP_DW0(port)) & COMP_INIT); -+} -+ -+static bool icl_combo_phy_verify_state(struct drm_i915_private *dev_priv, -+ enum port port) -+{ -+ bool ret; -+ -+ if (!icl_combo_phy_enabled(dev_priv, port)) -+ return false; -+ -+ ret = cnl_verify_procmon_ref_values(dev_priv, port); -+ -+ ret &= check_phy_reg(dev_priv, port, ICL_PORT_CL_DW5(port), -+ CL_POWER_DOWN_ENABLE, CL_POWER_DOWN_ENABLE); -+ -+ return ret; -+} -+ -+void icl_combo_phys_init(struct drm_i915_private *dev_priv) -+{ -+ enum port port; -+ -+ for_each_combo_port(dev_priv, port) { -+ u32 val; -+ -+ if (icl_combo_phy_verify_state(dev_priv, port)) { -+ DRM_DEBUG_DRIVER("Port %c combo PHY already enabled, won't reprogram it.\n", -+ port_name(port)); -+ continue; -+ } -+ -+ val = I915_READ(ICL_PHY_MISC(port)); -+ val &= ~ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN; -+ I915_WRITE(ICL_PHY_MISC(port), val); -+ -+ cnl_set_procmon_ref_values(dev_priv, port); -+ -+ val = I915_READ(ICL_PORT_COMP_DW0(port)); -+ val |= COMP_INIT; -+ I915_WRITE(ICL_PORT_COMP_DW0(port), val); -+ -+ val = I915_READ(ICL_PORT_CL_DW5(port)); -+ val |= CL_POWER_DOWN_ENABLE; -+ I915_WRITE(ICL_PORT_CL_DW5(port), val); -+ } -+} -+ -+void icl_combo_phys_uninit(struct drm_i915_private *dev_priv) -+{ -+ enum port port; -+ -+ for_each_combo_port_reverse(dev_priv, port) { -+ u32 val; -+ -+ if (port == PORT_A && -+ !icl_combo_phy_verify_state(dev_priv, port)) -+ DRM_WARN("Port %c combo PHY HW state changed unexpectedly\n", -+ port_name(port)); -+ -+ val = I915_READ(ICL_PHY_MISC(port)); -+ val |= ICL_PHY_MISC_DE_IO_COMP_PWR_DOWN; -+ I915_WRITE(ICL_PHY_MISC(port), val); -+ -+ val = I915_READ(ICL_PORT_COMP_DW0(port)); -+ val &= ~COMP_INIT; -+ I915_WRITE(ICL_PORT_COMP_DW0(port), val); -+ } -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_connector.c b/drivers/gpu/drm/i915_legacy/intel_connector.c -new file mode 100644 -index 000000000000..073b6c3ab7cc ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_connector.c -@@ -0,0 +1,282 @@ -+/* -+ * Copyright (c) 2007 Dave Airlie -+ * Copyright (c) 2007, 2010 Intel Corporation -+ * Jesse Barnes -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#include -+#include -+ -+#include -+#include -+ -+#include "i915_drv.h" -+#include "intel_connector.h" -+#include "intel_drv.h" -+#include "intel_hdcp.h" -+#include "intel_panel.h" -+ -+int intel_connector_init(struct intel_connector *connector) -+{ -+ struct intel_digital_connector_state *conn_state; -+ -+ /* -+ * Allocate enough memory to hold intel_digital_connector_state, -+ * This might be a few bytes too many, but for connectors that don't -+ * need it we'll free the state and allocate a smaller one on the first -+ * successful commit anyway. -+ */ -+ conn_state = kzalloc(sizeof(*conn_state), GFP_KERNEL); -+ if (!conn_state) -+ return -ENOMEM; -+ -+ __drm_atomic_helper_connector_reset(&connector->base, -+ &conn_state->base); -+ -+ return 0; -+} -+ -+struct intel_connector *intel_connector_alloc(void) -+{ -+ struct intel_connector *connector; -+ -+ connector = kzalloc(sizeof(*connector), GFP_KERNEL); -+ if (!connector) -+ return NULL; -+ -+ if (intel_connector_init(connector) < 0) { -+ kfree(connector); -+ return NULL; -+ } -+ -+ return connector; -+} -+ -+/* -+ * Free the bits allocated by intel_connector_alloc. -+ * This should only be used after intel_connector_alloc has returned -+ * successfully, and before drm_connector_init returns successfully. -+ * Otherwise the destroy callbacks for the connector and the state should -+ * take care of proper cleanup/free (see intel_connector_destroy). -+ */ -+void intel_connector_free(struct intel_connector *connector) -+{ -+ kfree(to_intel_digital_connector_state(connector->base.state)); -+ kfree(connector); -+} -+ -+/* -+ * Connector type independent destroy hook for drm_connector_funcs. -+ */ -+void intel_connector_destroy(struct drm_connector *connector) -+{ -+ struct intel_connector *intel_connector = to_intel_connector(connector); -+ -+ kfree(intel_connector->detect_edid); -+ -+ intel_hdcp_cleanup(intel_connector); -+ -+ if (!IS_ERR_OR_NULL(intel_connector->edid)) -+ kfree(intel_connector->edid); -+ -+ intel_panel_fini(&intel_connector->panel); -+ -+ drm_connector_cleanup(connector); -+ -+ if (intel_connector->port) -+ drm_dp_mst_put_port_malloc(intel_connector->port); -+ -+ kfree(connector); -+} -+ -+int intel_connector_register(struct drm_connector *connector) -+{ -+ struct intel_connector *intel_connector = to_intel_connector(connector); -+ int ret; -+ -+ ret = intel_backlight_device_register(intel_connector); -+ if (ret) -+ goto err; -+ -+ if (i915_inject_load_failure()) { -+ ret = -EFAULT; -+ goto err_backlight; -+ } -+ -+ return 0; -+ -+err_backlight: -+ intel_backlight_device_unregister(intel_connector); -+err: -+ return ret; -+} -+ -+void intel_connector_unregister(struct drm_connector *connector) -+{ -+ struct intel_connector *intel_connector = to_intel_connector(connector); -+ -+ intel_backlight_device_unregister(intel_connector); -+} -+ -+void intel_connector_attach_encoder(struct intel_connector *connector, -+ struct intel_encoder *encoder) -+{ -+ connector->encoder = encoder; -+ drm_connector_attach_encoder(&connector->base, &encoder->base); -+} -+ -+/* -+ * Simple connector->get_hw_state implementation for encoders that support only -+ * one connector and no cloning and hence the encoder state determines the state -+ * of the connector. -+ */ -+bool intel_connector_get_hw_state(struct intel_connector *connector) -+{ -+ enum pipe pipe = 0; -+ struct intel_encoder *encoder = connector->encoder; -+ -+ return encoder->get_hw_state(encoder, &pipe); -+} -+ -+enum pipe intel_connector_get_pipe(struct intel_connector *connector) -+{ -+ struct drm_device *dev = connector->base.dev; -+ -+ WARN_ON(!drm_modeset_is_locked(&dev->mode_config.connection_mutex)); -+ -+ if (!connector->base.state->crtc) -+ return INVALID_PIPE; -+ -+ return to_intel_crtc(connector->base.state->crtc)->pipe; -+} -+ -+/** -+ * intel_connector_update_modes - update connector from edid -+ * @connector: DRM connector device to use -+ * @edid: previously read EDID information -+ */ -+int intel_connector_update_modes(struct drm_connector *connector, -+ struct edid *edid) -+{ -+ int ret; -+ -+ drm_connector_update_edid_property(connector, edid); -+ ret = drm_add_edid_modes(connector, edid); -+ -+ return ret; -+} -+ -+/** -+ * intel_ddc_get_modes - get modelist from monitor -+ * @connector: DRM connector device to use -+ * @adapter: i2c adapter -+ * -+ * Fetch the EDID information from @connector using the DDC bus. -+ */ -+int intel_ddc_get_modes(struct drm_connector *connector, -+ struct i2c_adapter *adapter) -+{ -+ struct edid *edid; -+ int ret; -+ -+ edid = drm_get_edid(connector, adapter); -+ if (!edid) -+ return 0; -+ -+ ret = intel_connector_update_modes(connector, edid); -+ kfree(edid); -+ -+ return ret; -+} -+ -+static const struct drm_prop_enum_list force_audio_names[] = { -+ { HDMI_AUDIO_OFF_DVI, "force-dvi" }, -+ { HDMI_AUDIO_OFF, "off" }, -+ { HDMI_AUDIO_AUTO, "auto" }, -+ { HDMI_AUDIO_ON, "on" }, -+}; -+ -+void -+intel_attach_force_audio_property(struct drm_connector *connector) -+{ -+ struct drm_device *dev = connector->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_property *prop; -+ -+ prop = dev_priv->force_audio_property; -+ if (prop == NULL) { -+ prop = drm_property_create_enum(dev, 0, -+ "audio", -+ force_audio_names, -+ ARRAY_SIZE(force_audio_names)); -+ if (prop == NULL) -+ return; -+ -+ dev_priv->force_audio_property = prop; -+ } -+ drm_object_attach_property(&connector->base, prop, 0); -+} -+ -+static const struct drm_prop_enum_list broadcast_rgb_names[] = { -+ { INTEL_BROADCAST_RGB_AUTO, "Automatic" }, -+ { INTEL_BROADCAST_RGB_FULL, "Full" }, -+ { INTEL_BROADCAST_RGB_LIMITED, "Limited 16:235" }, -+}; -+ -+void -+intel_attach_broadcast_rgb_property(struct drm_connector *connector) -+{ -+ struct drm_device *dev = connector->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_property *prop; -+ -+ prop = dev_priv->broadcast_rgb_property; -+ if (prop == NULL) { -+ prop = drm_property_create_enum(dev, DRM_MODE_PROP_ENUM, -+ "Broadcast RGB", -+ broadcast_rgb_names, -+ ARRAY_SIZE(broadcast_rgb_names)); -+ if (prop == NULL) -+ return; -+ -+ dev_priv->broadcast_rgb_property = prop; -+ } -+ -+ drm_object_attach_property(&connector->base, prop, 0); -+} -+ -+void -+intel_attach_aspect_ratio_property(struct drm_connector *connector) -+{ -+ if (!drm_mode_create_aspect_ratio_property(connector->dev)) -+ drm_object_attach_property(&connector->base, -+ connector->dev->mode_config.aspect_ratio_property, -+ DRM_MODE_PICTURE_ASPECT_NONE); -+} -+ -+void -+intel_attach_colorspace_property(struct drm_connector *connector) -+{ -+ if (!drm_mode_create_colorspace_property(connector)) -+ drm_object_attach_property(&connector->base, -+ connector->colorspace_property, 0); -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_connector.h b/drivers/gpu/drm/i915_legacy/intel_connector.h -new file mode 100644 -index 000000000000..93a7375c8196 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_connector.h -@@ -0,0 +1,35 @@ -+/* SPDX-License-Identifier: MIT */ -+/* -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef __INTEL_CONNECTOR_H__ -+#define __INTEL_CONNECTOR_H__ -+ -+#include "intel_display.h" -+ -+struct drm_connector; -+struct edid; -+struct i2c_adapter; -+struct intel_connector; -+struct intel_encoder; -+ -+int intel_connector_init(struct intel_connector *connector); -+struct intel_connector *intel_connector_alloc(void); -+void intel_connector_free(struct intel_connector *connector); -+void intel_connector_destroy(struct drm_connector *connector); -+int intel_connector_register(struct drm_connector *connector); -+void intel_connector_unregister(struct drm_connector *connector); -+void intel_connector_attach_encoder(struct intel_connector *connector, -+ struct intel_encoder *encoder); -+bool intel_connector_get_hw_state(struct intel_connector *connector); -+enum pipe intel_connector_get_pipe(struct intel_connector *connector); -+int intel_connector_update_modes(struct drm_connector *connector, -+ struct edid *edid); -+int intel_ddc_get_modes(struct drm_connector *c, struct i2c_adapter *adapter); -+void intel_attach_force_audio_property(struct drm_connector *connector); -+void intel_attach_broadcast_rgb_property(struct drm_connector *connector); -+void intel_attach_aspect_ratio_property(struct drm_connector *connector); -+void intel_attach_colorspace_property(struct drm_connector *connector); -+ -+#endif /* __INTEL_CONNECTOR_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_context.c b/drivers/gpu/drm/i915_legacy/intel_context.c -new file mode 100644 -index 000000000000..8931e0fee873 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_context.c -@@ -0,0 +1,269 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#include "i915_drv.h" -+#include "i915_gem_context.h" -+#include "i915_globals.h" -+#include "intel_context.h" -+#include "intel_ringbuffer.h" -+ -+static struct i915_global_context { -+ struct i915_global base; -+ struct kmem_cache *slab_ce; -+} global; -+ -+struct intel_context *intel_context_alloc(void) -+{ -+ return kmem_cache_zalloc(global.slab_ce, GFP_KERNEL); -+} -+ -+void intel_context_free(struct intel_context *ce) -+{ -+ kmem_cache_free(global.slab_ce, ce); -+} -+ -+struct intel_context * -+intel_context_lookup(struct i915_gem_context *ctx, -+ struct intel_engine_cs *engine) -+{ -+ struct intel_context *ce = NULL; -+ struct rb_node *p; -+ -+ spin_lock(&ctx->hw_contexts_lock); -+ p = ctx->hw_contexts.rb_node; -+ while (p) { -+ struct intel_context *this = -+ rb_entry(p, struct intel_context, node); -+ -+ if (this->engine == engine) { -+ GEM_BUG_ON(this->gem_context != ctx); -+ ce = this; -+ break; -+ } -+ -+ if (this->engine < engine) -+ p = p->rb_right; -+ else -+ p = p->rb_left; -+ } -+ spin_unlock(&ctx->hw_contexts_lock); -+ -+ return ce; -+} -+ -+struct intel_context * -+__intel_context_insert(struct i915_gem_context *ctx, -+ struct intel_engine_cs *engine, -+ struct intel_context *ce) -+{ -+ struct rb_node **p, *parent; -+ int err = 0; -+ -+ spin_lock(&ctx->hw_contexts_lock); -+ -+ parent = NULL; -+ p = &ctx->hw_contexts.rb_node; -+ while (*p) { -+ struct intel_context *this; -+ -+ parent = *p; -+ this = rb_entry(parent, struct intel_context, node); -+ -+ if (this->engine == engine) { -+ err = -EEXIST; -+ ce = this; -+ break; -+ } -+ -+ if (this->engine < engine) -+ p = &parent->rb_right; -+ else -+ p = &parent->rb_left; -+ } -+ if (!err) { -+ rb_link_node(&ce->node, parent, p); -+ rb_insert_color(&ce->node, &ctx->hw_contexts); -+ } -+ -+ spin_unlock(&ctx->hw_contexts_lock); -+ -+ return ce; -+} -+ -+void __intel_context_remove(struct intel_context *ce) -+{ -+ struct i915_gem_context *ctx = ce->gem_context; -+ -+ spin_lock(&ctx->hw_contexts_lock); -+ rb_erase(&ce->node, &ctx->hw_contexts); -+ spin_unlock(&ctx->hw_contexts_lock); -+} -+ -+static struct intel_context * -+intel_context_instance(struct i915_gem_context *ctx, -+ struct intel_engine_cs *engine) -+{ -+ struct intel_context *ce, *pos; -+ -+ ce = intel_context_lookup(ctx, engine); -+ if (likely(ce)) -+ return ce; -+ -+ ce = intel_context_alloc(); -+ if (!ce) -+ return ERR_PTR(-ENOMEM); -+ -+ intel_context_init(ce, ctx, engine); -+ -+ pos = __intel_context_insert(ctx, engine, ce); -+ if (unlikely(pos != ce)) /* Beaten! Use their HW context instead */ -+ intel_context_free(ce); -+ -+ GEM_BUG_ON(intel_context_lookup(ctx, engine) != pos); -+ return pos; -+} -+ -+struct intel_context * -+intel_context_pin_lock(struct i915_gem_context *ctx, -+ struct intel_engine_cs *engine) -+ __acquires(ce->pin_mutex) -+{ -+ struct intel_context *ce; -+ -+ ce = intel_context_instance(ctx, engine); -+ if (IS_ERR(ce)) -+ return ce; -+ -+ if (mutex_lock_interruptible(&ce->pin_mutex)) -+ return ERR_PTR(-EINTR); -+ -+ return ce; -+} -+ -+struct intel_context * -+intel_context_pin(struct i915_gem_context *ctx, -+ struct intel_engine_cs *engine) -+{ -+ struct intel_context *ce; -+ int err; -+ -+ ce = intel_context_instance(ctx, engine); -+ if (IS_ERR(ce)) -+ return ce; -+ -+ if (likely(atomic_inc_not_zero(&ce->pin_count))) -+ return ce; -+ -+ if (mutex_lock_interruptible(&ce->pin_mutex)) -+ return ERR_PTR(-EINTR); -+ -+ if (likely(!atomic_read(&ce->pin_count))) { -+ err = ce->ops->pin(ce); -+ if (err) -+ goto err; -+ -+ i915_gem_context_get(ctx); -+ GEM_BUG_ON(ce->gem_context != ctx); -+ -+ mutex_lock(&ctx->mutex); -+ list_add(&ce->active_link, &ctx->active_engines); -+ mutex_unlock(&ctx->mutex); -+ -+ intel_context_get(ce); -+ smp_mb__before_atomic(); /* flush pin before it is visible */ -+ } -+ -+ atomic_inc(&ce->pin_count); -+ GEM_BUG_ON(!intel_context_is_pinned(ce)); /* no overflow! */ -+ -+ mutex_unlock(&ce->pin_mutex); -+ return ce; -+ -+err: -+ mutex_unlock(&ce->pin_mutex); -+ return ERR_PTR(err); -+} -+ -+void intel_context_unpin(struct intel_context *ce) -+{ -+ if (likely(atomic_add_unless(&ce->pin_count, -1, 1))) -+ return; -+ -+ /* We may be called from inside intel_context_pin() to evict another */ -+ intel_context_get(ce); -+ mutex_lock_nested(&ce->pin_mutex, SINGLE_DEPTH_NESTING); -+ -+ if (likely(atomic_dec_and_test(&ce->pin_count))) { -+ ce->ops->unpin(ce); -+ -+ mutex_lock(&ce->gem_context->mutex); -+ list_del(&ce->active_link); -+ mutex_unlock(&ce->gem_context->mutex); -+ -+ i915_gem_context_put(ce->gem_context); -+ intel_context_put(ce); -+ } -+ -+ mutex_unlock(&ce->pin_mutex); -+ intel_context_put(ce); -+} -+ -+static void intel_context_retire(struct i915_active_request *active, -+ struct i915_request *rq) -+{ -+ struct intel_context *ce = -+ container_of(active, typeof(*ce), active_tracker); -+ -+ intel_context_unpin(ce); -+} -+ -+void -+intel_context_init(struct intel_context *ce, -+ struct i915_gem_context *ctx, -+ struct intel_engine_cs *engine) -+{ -+ kref_init(&ce->ref); -+ -+ ce->gem_context = ctx; -+ ce->engine = engine; -+ ce->ops = engine->cops; -+ -+ INIT_LIST_HEAD(&ce->signal_link); -+ INIT_LIST_HEAD(&ce->signals); -+ -+ mutex_init(&ce->pin_mutex); -+ -+ /* Use the whole device by default */ -+ ce->sseu = intel_device_default_sseu(ctx->i915); -+ -+ i915_active_request_init(&ce->active_tracker, -+ NULL, intel_context_retire); -+} -+ -+static void i915_global_context_shrink(void) -+{ -+ kmem_cache_shrink(global.slab_ce); -+} -+ -+static void i915_global_context_exit(void) -+{ -+ kmem_cache_destroy(global.slab_ce); -+} -+ -+static struct i915_global_context global = { { -+ .shrink = i915_global_context_shrink, -+ .exit = i915_global_context_exit, -+} }; -+ -+int __init i915_global_context_init(void) -+{ -+ global.slab_ce = KMEM_CACHE(intel_context, SLAB_HWCACHE_ALIGN); -+ if (!global.slab_ce) -+ return -ENOMEM; -+ -+ i915_global_register(&global.base); -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_context.h b/drivers/gpu/drm/i915_legacy/intel_context.h -new file mode 100644 -index 000000000000..ebc861b1a49e ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_context.h -@@ -0,0 +1,87 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef __INTEL_CONTEXT_H__ -+#define __INTEL_CONTEXT_H__ -+ -+#include -+ -+#include "intel_context_types.h" -+#include "intel_engine_types.h" -+ -+struct intel_context *intel_context_alloc(void); -+void intel_context_free(struct intel_context *ce); -+ -+void intel_context_init(struct intel_context *ce, -+ struct i915_gem_context *ctx, -+ struct intel_engine_cs *engine); -+ -+/** -+ * intel_context_lookup - Find the matching HW context for this (ctx, engine) -+ * @ctx - the parent GEM context -+ * @engine - the target HW engine -+ * -+ * May return NULL if the HW context hasn't been instantiated (i.e. unused). -+ */ -+struct intel_context * -+intel_context_lookup(struct i915_gem_context *ctx, -+ struct intel_engine_cs *engine); -+ -+/** -+ * intel_context_pin_lock - Stablises the 'pinned' status of the HW context -+ * @ctx - the parent GEM context -+ * @engine - the target HW engine -+ * -+ * Acquire a lock on the pinned status of the HW context, such that the context -+ * can neither be bound to the GPU or unbound whilst the lock is held, i.e. -+ * intel_context_is_pinned() remains stable. -+ */ -+struct intel_context * -+intel_context_pin_lock(struct i915_gem_context *ctx, -+ struct intel_engine_cs *engine); -+ -+static inline bool -+intel_context_is_pinned(struct intel_context *ce) -+{ -+ return atomic_read(&ce->pin_count); -+} -+ -+static inline void intel_context_pin_unlock(struct intel_context *ce) -+__releases(ce->pin_mutex) -+{ -+ mutex_unlock(&ce->pin_mutex); -+} -+ -+struct intel_context * -+__intel_context_insert(struct i915_gem_context *ctx, -+ struct intel_engine_cs *engine, -+ struct intel_context *ce); -+void -+__intel_context_remove(struct intel_context *ce); -+ -+struct intel_context * -+intel_context_pin(struct i915_gem_context *ctx, struct intel_engine_cs *engine); -+ -+static inline void __intel_context_pin(struct intel_context *ce) -+{ -+ GEM_BUG_ON(!intel_context_is_pinned(ce)); -+ atomic_inc(&ce->pin_count); -+} -+ -+void intel_context_unpin(struct intel_context *ce); -+ -+static inline struct intel_context *intel_context_get(struct intel_context *ce) -+{ -+ kref_get(&ce->ref); -+ return ce; -+} -+ -+static inline void intel_context_put(struct intel_context *ce) -+{ -+ kref_put(&ce->ref, ce->ops->destroy); -+} -+ -+#endif /* __INTEL_CONTEXT_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_context_types.h b/drivers/gpu/drm/i915_legacy/intel_context_types.h -new file mode 100644 -index 000000000000..fd47b9d49e09 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_context_types.h -@@ -0,0 +1,75 @@ -+/* -+ * SPDX-License-Identifier: MIT -+ * -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef __INTEL_CONTEXT_TYPES__ -+#define __INTEL_CONTEXT_TYPES__ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "i915_active_types.h" -+#include "intel_engine_types.h" -+ -+struct i915_gem_context; -+struct i915_vma; -+struct intel_context; -+struct intel_ring; -+ -+struct intel_context_ops { -+ int (*pin)(struct intel_context *ce); -+ void (*unpin)(struct intel_context *ce); -+ -+ void (*reset)(struct intel_context *ce); -+ void (*destroy)(struct kref *kref); -+}; -+ -+/* -+ * Powergating configuration for a particular (context,engine). -+ */ -+struct intel_sseu { -+ u8 slice_mask; -+ u8 subslice_mask; -+ u8 min_eus_per_subslice; -+ u8 max_eus_per_subslice; -+}; -+ -+struct intel_context { -+ struct kref ref; -+ -+ struct i915_gem_context *gem_context; -+ struct intel_engine_cs *engine; -+ struct intel_engine_cs *active; -+ -+ struct list_head active_link; -+ struct list_head signal_link; -+ struct list_head signals; -+ -+ struct i915_vma *state; -+ struct intel_ring *ring; -+ -+ u32 *lrc_reg_state; -+ u64 lrc_desc; -+ -+ atomic_t pin_count; -+ struct mutex pin_mutex; /* guards pinning and associated on-gpuing */ -+ -+ /** -+ * active_tracker: Active tracker for the external rq activity -+ * on this intel_context object. -+ */ -+ struct i915_active_request active_tracker; -+ -+ const struct intel_context_ops *ops; -+ struct rb_node node; -+ -+ /** sseu: Control eu/slice partitioning */ -+ struct intel_sseu sseu; -+}; -+ -+#endif /* __INTEL_CONTEXT_TYPES__ */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_crt.c b/drivers/gpu/drm/i915_legacy/intel_crt.c -new file mode 100644 -index 000000000000..b665c370111b ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_crt.c -@@ -0,0 +1,1061 @@ -+/* -+ * Copyright © 2006-2007 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eric Anholt -+ */ -+ -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "i915_drv.h" -+#include "intel_connector.h" -+#include "intel_crt.h" -+#include "intel_ddi.h" -+#include "intel_drv.h" -+ -+/* Here's the desired hotplug mode */ -+#define ADPA_HOTPLUG_BITS (ADPA_CRT_HOTPLUG_PERIOD_128 | \ -+ ADPA_CRT_HOTPLUG_WARMUP_10MS | \ -+ ADPA_CRT_HOTPLUG_SAMPLE_4S | \ -+ ADPA_CRT_HOTPLUG_VOLTAGE_50 | \ -+ ADPA_CRT_HOTPLUG_VOLREF_325MV | \ -+ ADPA_CRT_HOTPLUG_ENABLE) -+ -+struct intel_crt { -+ struct intel_encoder base; -+ /* DPMS state is stored in the connector, which we need in the -+ * encoder's enable/disable callbacks */ -+ struct intel_connector *connector; -+ bool force_hotplug_required; -+ i915_reg_t adpa_reg; -+}; -+ -+static struct intel_crt *intel_encoder_to_crt(struct intel_encoder *encoder) -+{ -+ return container_of(encoder, struct intel_crt, base); -+} -+ -+static struct intel_crt *intel_attached_crt(struct drm_connector *connector) -+{ -+ return intel_encoder_to_crt(intel_attached_encoder(connector)); -+} -+ -+bool intel_crt_port_enabled(struct drm_i915_private *dev_priv, -+ i915_reg_t adpa_reg, enum pipe *pipe) -+{ -+ u32 val; -+ -+ val = I915_READ(adpa_reg); -+ -+ /* asserts want to know the pipe even if the port is disabled */ -+ if (HAS_PCH_CPT(dev_priv)) -+ *pipe = (val & ADPA_PIPE_SEL_MASK_CPT) >> ADPA_PIPE_SEL_SHIFT_CPT; -+ else -+ *pipe = (val & ADPA_PIPE_SEL_MASK) >> ADPA_PIPE_SEL_SHIFT; -+ -+ return val & ADPA_DAC_ENABLE; -+} -+ -+static bool intel_crt_get_hw_state(struct intel_encoder *encoder, -+ enum pipe *pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crt *crt = intel_encoder_to_crt(encoder); -+ intel_wakeref_t wakeref; -+ bool ret; -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ encoder->power_domain); -+ if (!wakeref) -+ return false; -+ -+ ret = intel_crt_port_enabled(dev_priv, crt->adpa_reg, pipe); -+ -+ intel_display_power_put(dev_priv, encoder->power_domain, wakeref); -+ -+ return ret; -+} -+ -+static unsigned int intel_crt_get_flags(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crt *crt = intel_encoder_to_crt(encoder); -+ u32 tmp, flags = 0; -+ -+ tmp = I915_READ(crt->adpa_reg); -+ -+ if (tmp & ADPA_HSYNC_ACTIVE_HIGH) -+ flags |= DRM_MODE_FLAG_PHSYNC; -+ else -+ flags |= DRM_MODE_FLAG_NHSYNC; -+ -+ if (tmp & ADPA_VSYNC_ACTIVE_HIGH) -+ flags |= DRM_MODE_FLAG_PVSYNC; -+ else -+ flags |= DRM_MODE_FLAG_NVSYNC; -+ -+ return flags; -+} -+ -+static void intel_crt_get_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config) -+{ -+ pipe_config->output_types |= BIT(INTEL_OUTPUT_ANALOG); -+ -+ pipe_config->base.adjusted_mode.flags |= intel_crt_get_flags(encoder); -+ -+ pipe_config->base.adjusted_mode.crtc_clock = pipe_config->port_clock; -+} -+ -+static void hsw_crt_get_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ -+ intel_ddi_get_config(encoder, pipe_config); -+ -+ pipe_config->base.adjusted_mode.flags &= ~(DRM_MODE_FLAG_PHSYNC | -+ DRM_MODE_FLAG_NHSYNC | -+ DRM_MODE_FLAG_PVSYNC | -+ DRM_MODE_FLAG_NVSYNC); -+ pipe_config->base.adjusted_mode.flags |= intel_crt_get_flags(encoder); -+ -+ pipe_config->base.adjusted_mode.crtc_clock = lpt_get_iclkip(dev_priv); -+} -+ -+/* Note: The caller is required to filter out dpms modes not supported by the -+ * platform. */ -+static void intel_crt_set_dpms(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ int mode) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crt *crt = intel_encoder_to_crt(encoder); -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ const struct drm_display_mode *adjusted_mode = &crtc_state->base.adjusted_mode; -+ u32 adpa; -+ -+ if (INTEL_GEN(dev_priv) >= 5) -+ adpa = ADPA_HOTPLUG_BITS; -+ else -+ adpa = 0; -+ -+ if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) -+ adpa |= ADPA_HSYNC_ACTIVE_HIGH; -+ if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) -+ adpa |= ADPA_VSYNC_ACTIVE_HIGH; -+ -+ /* For CPT allow 3 pipe config, for others just use A or B */ -+ if (HAS_PCH_LPT(dev_priv)) -+ ; /* Those bits don't exist here */ -+ else if (HAS_PCH_CPT(dev_priv)) -+ adpa |= ADPA_PIPE_SEL_CPT(crtc->pipe); -+ else -+ adpa |= ADPA_PIPE_SEL(crtc->pipe); -+ -+ if (!HAS_PCH_SPLIT(dev_priv)) -+ I915_WRITE(BCLRPAT(crtc->pipe), 0); -+ -+ switch (mode) { -+ case DRM_MODE_DPMS_ON: -+ adpa |= ADPA_DAC_ENABLE; -+ break; -+ case DRM_MODE_DPMS_STANDBY: -+ adpa |= ADPA_DAC_ENABLE | ADPA_HSYNC_CNTL_DISABLE; -+ break; -+ case DRM_MODE_DPMS_SUSPEND: -+ adpa |= ADPA_DAC_ENABLE | ADPA_VSYNC_CNTL_DISABLE; -+ break; -+ case DRM_MODE_DPMS_OFF: -+ adpa |= ADPA_HSYNC_CNTL_DISABLE | ADPA_VSYNC_CNTL_DISABLE; -+ break; -+ } -+ -+ I915_WRITE(crt->adpa_reg, adpa); -+} -+ -+static void intel_disable_crt(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ intel_crt_set_dpms(encoder, old_crtc_state, DRM_MODE_DPMS_OFF); -+} -+ -+static void pch_disable_crt(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+} -+ -+static void pch_post_disable_crt(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ intel_disable_crt(encoder, old_crtc_state, old_conn_state); -+} -+ -+static void hsw_disable_crt(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ -+ WARN_ON(!old_crtc_state->has_pch_encoder); -+ -+ intel_set_pch_fifo_underrun_reporting(dev_priv, PIPE_A, false); -+} -+ -+static void hsw_post_disable_crt(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ -+ intel_ddi_disable_pipe_clock(old_crtc_state); -+ -+ pch_post_disable_crt(encoder, old_crtc_state, old_conn_state); -+ -+ lpt_disable_pch_transcoder(dev_priv); -+ lpt_disable_iclkip(dev_priv); -+ -+ intel_ddi_fdi_post_disable(encoder, old_crtc_state, old_conn_state); -+ -+ WARN_ON(!old_crtc_state->has_pch_encoder); -+ -+ intel_set_pch_fifo_underrun_reporting(dev_priv, PIPE_A, true); -+} -+ -+static void hsw_pre_pll_enable_crt(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ -+ WARN_ON(!crtc_state->has_pch_encoder); -+ -+ intel_set_pch_fifo_underrun_reporting(dev_priv, PIPE_A, false); -+} -+ -+static void hsw_pre_enable_crt(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ enum pipe pipe = crtc->pipe; -+ -+ WARN_ON(!crtc_state->has_pch_encoder); -+ -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, false); -+ -+ dev_priv->display.fdi_link_train(crtc, crtc_state); -+ -+ intel_ddi_enable_pipe_clock(crtc_state); -+} -+ -+static void hsw_enable_crt(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ enum pipe pipe = crtc->pipe; -+ -+ WARN_ON(!crtc_state->has_pch_encoder); -+ -+ intel_crt_set_dpms(encoder, crtc_state, DRM_MODE_DPMS_ON); -+ -+ intel_wait_for_vblank(dev_priv, pipe); -+ intel_wait_for_vblank(dev_priv, pipe); -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, true); -+ intel_set_pch_fifo_underrun_reporting(dev_priv, PIPE_A, true); -+} -+ -+static void intel_enable_crt(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ intel_crt_set_dpms(encoder, crtc_state, DRM_MODE_DPMS_ON); -+} -+ -+static enum drm_mode_status -+intel_crt_mode_valid(struct drm_connector *connector, -+ struct drm_display_mode *mode) -+{ -+ struct drm_device *dev = connector->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int max_dotclk = dev_priv->max_dotclk_freq; -+ int max_clock; -+ -+ if (mode->flags & DRM_MODE_FLAG_DBLSCAN) -+ return MODE_NO_DBLESCAN; -+ -+ if (mode->clock < 25000) -+ return MODE_CLOCK_LOW; -+ -+ if (HAS_PCH_LPT(dev_priv)) -+ max_clock = 180000; -+ else if (IS_VALLEYVIEW(dev_priv)) -+ /* -+ * 270 MHz due to current DPLL limits, -+ * DAC limit supposedly 355 MHz. -+ */ -+ max_clock = 270000; -+ else if (IS_GEN_RANGE(dev_priv, 3, 4)) -+ max_clock = 400000; -+ else -+ max_clock = 350000; -+ if (mode->clock > max_clock) -+ return MODE_CLOCK_HIGH; -+ -+ if (mode->clock > max_dotclk) -+ return MODE_CLOCK_HIGH; -+ -+ /* The FDI receiver on LPT only supports 8bpc and only has 2 lanes. */ -+ if (HAS_PCH_LPT(dev_priv) && -+ (ironlake_get_lanes_required(mode->clock, 270000, 24) > 2)) -+ return MODE_CLOCK_HIGH; -+ -+ /* HSW/BDW FDI limited to 4k */ -+ if (mode->hdisplay > 4096) -+ return MODE_H_ILLEGAL; -+ -+ return MODE_OK; -+} -+ -+static int intel_crt_compute_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config, -+ struct drm_connector_state *conn_state) -+{ -+ struct drm_display_mode *adjusted_mode = -+ &pipe_config->base.adjusted_mode; -+ -+ if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN) -+ return -EINVAL; -+ -+ pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; -+ -+ return 0; -+} -+ -+static int pch_crt_compute_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config, -+ struct drm_connector_state *conn_state) -+{ -+ struct drm_display_mode *adjusted_mode = -+ &pipe_config->base.adjusted_mode; -+ -+ if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN) -+ return -EINVAL; -+ -+ pipe_config->has_pch_encoder = true; -+ pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; -+ -+ return 0; -+} -+ -+static int hsw_crt_compute_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config, -+ struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct drm_display_mode *adjusted_mode = -+ &pipe_config->base.adjusted_mode; -+ -+ if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN) -+ return -EINVAL; -+ -+ /* HSW/BDW FDI limited to 4k */ -+ if (adjusted_mode->crtc_hdisplay > 4096 || -+ adjusted_mode->crtc_hblank_start > 4096) -+ return -EINVAL; -+ -+ pipe_config->has_pch_encoder = true; -+ pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; -+ -+ /* LPT FDI RX only supports 8bpc. */ -+ if (HAS_PCH_LPT(dev_priv)) { -+ if (pipe_config->bw_constrained && pipe_config->pipe_bpp < 24) { -+ DRM_DEBUG_KMS("LPT only supports 24bpp\n"); -+ return -EINVAL; -+ } -+ -+ pipe_config->pipe_bpp = 24; -+ } -+ -+ /* FDI must always be 2.7 GHz */ -+ pipe_config->port_clock = 135000 * 2; -+ -+ return 0; -+} -+ -+static bool intel_ironlake_crt_detect_hotplug(struct drm_connector *connector) -+{ -+ struct drm_device *dev = connector->dev; -+ struct intel_crt *crt = intel_attached_crt(connector); -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ u32 adpa; -+ bool ret; -+ -+ /* The first time through, trigger an explicit detection cycle */ -+ if (crt->force_hotplug_required) { -+ bool turn_off_dac = HAS_PCH_SPLIT(dev_priv); -+ u32 save_adpa; -+ -+ crt->force_hotplug_required = 0; -+ -+ save_adpa = adpa = I915_READ(crt->adpa_reg); -+ DRM_DEBUG_KMS("trigger hotplug detect cycle: adpa=0x%x\n", adpa); -+ -+ adpa |= ADPA_CRT_HOTPLUG_FORCE_TRIGGER; -+ if (turn_off_dac) -+ adpa &= ~ADPA_DAC_ENABLE; -+ -+ I915_WRITE(crt->adpa_reg, adpa); -+ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ crt->adpa_reg, -+ ADPA_CRT_HOTPLUG_FORCE_TRIGGER, 0, -+ 1000)) -+ DRM_DEBUG_KMS("timed out waiting for FORCE_TRIGGER"); -+ -+ if (turn_off_dac) { -+ I915_WRITE(crt->adpa_reg, save_adpa); -+ POSTING_READ(crt->adpa_reg); -+ } -+ } -+ -+ /* Check the status to see if both blue and green are on now */ -+ adpa = I915_READ(crt->adpa_reg); -+ if ((adpa & ADPA_CRT_HOTPLUG_MONITOR_MASK) != 0) -+ ret = true; -+ else -+ ret = false; -+ DRM_DEBUG_KMS("ironlake hotplug adpa=0x%x, result %d\n", adpa, ret); -+ -+ return ret; -+} -+ -+static bool valleyview_crt_detect_hotplug(struct drm_connector *connector) -+{ -+ struct drm_device *dev = connector->dev; -+ struct intel_crt *crt = intel_attached_crt(connector); -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ bool reenable_hpd; -+ u32 adpa; -+ bool ret; -+ u32 save_adpa; -+ -+ /* -+ * Doing a force trigger causes a hpd interrupt to get sent, which can -+ * get us stuck in a loop if we're polling: -+ * - We enable power wells and reset the ADPA -+ * - output_poll_exec does force probe on VGA, triggering a hpd -+ * - HPD handler waits for poll to unlock dev->mode_config.mutex -+ * - output_poll_exec shuts off the ADPA, unlocks -+ * dev->mode_config.mutex -+ * - HPD handler runs, resets ADPA and brings us back to the start -+ * -+ * Just disable HPD interrupts here to prevent this -+ */ -+ reenable_hpd = intel_hpd_disable(dev_priv, crt->base.hpd_pin); -+ -+ save_adpa = adpa = I915_READ(crt->adpa_reg); -+ DRM_DEBUG_KMS("trigger hotplug detect cycle: adpa=0x%x\n", adpa); -+ -+ adpa |= ADPA_CRT_HOTPLUG_FORCE_TRIGGER; -+ -+ I915_WRITE(crt->adpa_reg, adpa); -+ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ crt->adpa_reg, -+ ADPA_CRT_HOTPLUG_FORCE_TRIGGER, 0, -+ 1000)) { -+ DRM_DEBUG_KMS("timed out waiting for FORCE_TRIGGER"); -+ I915_WRITE(crt->adpa_reg, save_adpa); -+ } -+ -+ /* Check the status to see if both blue and green are on now */ -+ adpa = I915_READ(crt->adpa_reg); -+ if ((adpa & ADPA_CRT_HOTPLUG_MONITOR_MASK) != 0) -+ ret = true; -+ else -+ ret = false; -+ -+ DRM_DEBUG_KMS("valleyview hotplug adpa=0x%x, result %d\n", adpa, ret); -+ -+ if (reenable_hpd) -+ intel_hpd_enable(dev_priv, crt->base.hpd_pin); -+ -+ return ret; -+} -+ -+static bool intel_crt_detect_hotplug(struct drm_connector *connector) -+{ -+ struct drm_device *dev = connector->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ u32 stat; -+ bool ret = false; -+ int i, tries = 0; -+ -+ if (HAS_PCH_SPLIT(dev_priv)) -+ return intel_ironlake_crt_detect_hotplug(connector); -+ -+ if (IS_VALLEYVIEW(dev_priv)) -+ return valleyview_crt_detect_hotplug(connector); -+ -+ /* -+ * On 4 series desktop, CRT detect sequence need to be done twice -+ * to get a reliable result. -+ */ -+ -+ if (IS_G45(dev_priv)) -+ tries = 2; -+ else -+ tries = 1; -+ -+ for (i = 0; i < tries ; i++) { -+ /* turn on the FORCE_DETECT */ -+ i915_hotplug_interrupt_update(dev_priv, -+ CRT_HOTPLUG_FORCE_DETECT, -+ CRT_HOTPLUG_FORCE_DETECT); -+ /* wait for FORCE_DETECT to go off */ -+ if (intel_wait_for_register(&dev_priv->uncore, PORT_HOTPLUG_EN, -+ CRT_HOTPLUG_FORCE_DETECT, 0, -+ 1000)) -+ DRM_DEBUG_KMS("timed out waiting for FORCE_DETECT to go off"); -+ } -+ -+ stat = I915_READ(PORT_HOTPLUG_STAT); -+ if ((stat & CRT_HOTPLUG_MONITOR_MASK) != CRT_HOTPLUG_MONITOR_NONE) -+ ret = true; -+ -+ /* clear the interrupt we just generated, if any */ -+ I915_WRITE(PORT_HOTPLUG_STAT, CRT_HOTPLUG_INT_STATUS); -+ -+ i915_hotplug_interrupt_update(dev_priv, CRT_HOTPLUG_FORCE_DETECT, 0); -+ -+ return ret; -+} -+ -+static struct edid *intel_crt_get_edid(struct drm_connector *connector, -+ struct i2c_adapter *i2c) -+{ -+ struct edid *edid; -+ -+ edid = drm_get_edid(connector, i2c); -+ -+ if (!edid && !intel_gmbus_is_forced_bit(i2c)) { -+ DRM_DEBUG_KMS("CRT GMBUS EDID read failed, retry using GPIO bit-banging\n"); -+ intel_gmbus_force_bit(i2c, true); -+ edid = drm_get_edid(connector, i2c); -+ intel_gmbus_force_bit(i2c, false); -+ } -+ -+ return edid; -+} -+ -+/* local version of intel_ddc_get_modes() to use intel_crt_get_edid() */ -+static int intel_crt_ddc_get_modes(struct drm_connector *connector, -+ struct i2c_adapter *adapter) -+{ -+ struct edid *edid; -+ int ret; -+ -+ edid = intel_crt_get_edid(connector, adapter); -+ if (!edid) -+ return 0; -+ -+ ret = intel_connector_update_modes(connector, edid); -+ kfree(edid); -+ -+ return ret; -+} -+ -+static bool intel_crt_detect_ddc(struct drm_connector *connector) -+{ -+ struct intel_crt *crt = intel_attached_crt(connector); -+ struct drm_i915_private *dev_priv = to_i915(crt->base.base.dev); -+ struct edid *edid; -+ struct i2c_adapter *i2c; -+ bool ret = false; -+ -+ BUG_ON(crt->base.type != INTEL_OUTPUT_ANALOG); -+ -+ i2c = intel_gmbus_get_adapter(dev_priv, dev_priv->vbt.crt_ddc_pin); -+ edid = intel_crt_get_edid(connector, i2c); -+ -+ if (edid) { -+ bool is_digital = edid->input & DRM_EDID_INPUT_DIGITAL; -+ -+ /* -+ * This may be a DVI-I connector with a shared DDC -+ * link between analog and digital outputs, so we -+ * have to check the EDID input spec of the attached device. -+ */ -+ if (!is_digital) { -+ DRM_DEBUG_KMS("CRT detected via DDC:0x50 [EDID]\n"); -+ ret = true; -+ } else { -+ DRM_DEBUG_KMS("CRT not detected via DDC:0x50 [EDID reports a digital panel]\n"); -+ } -+ } else { -+ DRM_DEBUG_KMS("CRT not detected via DDC:0x50 [no valid EDID found]\n"); -+ } -+ -+ kfree(edid); -+ -+ return ret; -+} -+ -+static enum drm_connector_status -+intel_crt_load_detect(struct intel_crt *crt, u32 pipe) -+{ -+ struct drm_device *dev = crt->base.base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ u32 save_bclrpat; -+ u32 save_vtotal; -+ u32 vtotal, vactive; -+ u32 vsample; -+ u32 vblank, vblank_start, vblank_end; -+ u32 dsl; -+ i915_reg_t bclrpat_reg, vtotal_reg, -+ vblank_reg, vsync_reg, pipeconf_reg, pipe_dsl_reg; -+ u8 st00; -+ enum drm_connector_status status; -+ -+ DRM_DEBUG_KMS("starting load-detect on CRT\n"); -+ -+ bclrpat_reg = BCLRPAT(pipe); -+ vtotal_reg = VTOTAL(pipe); -+ vblank_reg = VBLANK(pipe); -+ vsync_reg = VSYNC(pipe); -+ pipeconf_reg = PIPECONF(pipe); -+ pipe_dsl_reg = PIPEDSL(pipe); -+ -+ save_bclrpat = I915_READ(bclrpat_reg); -+ save_vtotal = I915_READ(vtotal_reg); -+ vblank = I915_READ(vblank_reg); -+ -+ vtotal = ((save_vtotal >> 16) & 0xfff) + 1; -+ vactive = (save_vtotal & 0x7ff) + 1; -+ -+ vblank_start = (vblank & 0xfff) + 1; -+ vblank_end = ((vblank >> 16) & 0xfff) + 1; -+ -+ /* Set the border color to purple. */ -+ I915_WRITE(bclrpat_reg, 0x500050); -+ -+ if (!IS_GEN(dev_priv, 2)) { -+ u32 pipeconf = I915_READ(pipeconf_reg); -+ I915_WRITE(pipeconf_reg, pipeconf | PIPECONF_FORCE_BORDER); -+ POSTING_READ(pipeconf_reg); -+ /* Wait for next Vblank to substitue -+ * border color for Color info */ -+ intel_wait_for_vblank(dev_priv, pipe); -+ st00 = I915_READ8(_VGA_MSR_WRITE); -+ status = ((st00 & (1 << 4)) != 0) ? -+ connector_status_connected : -+ connector_status_disconnected; -+ -+ I915_WRITE(pipeconf_reg, pipeconf); -+ } else { -+ bool restore_vblank = false; -+ int count, detect; -+ -+ /* -+ * If there isn't any border, add some. -+ * Yes, this will flicker -+ */ -+ if (vblank_start <= vactive && vblank_end >= vtotal) { -+ u32 vsync = I915_READ(vsync_reg); -+ u32 vsync_start = (vsync & 0xffff) + 1; -+ -+ vblank_start = vsync_start; -+ I915_WRITE(vblank_reg, -+ (vblank_start - 1) | -+ ((vblank_end - 1) << 16)); -+ restore_vblank = true; -+ } -+ /* sample in the vertical border, selecting the larger one */ -+ if (vblank_start - vactive >= vtotal - vblank_end) -+ vsample = (vblank_start + vactive) >> 1; -+ else -+ vsample = (vtotal + vblank_end) >> 1; -+ -+ /* -+ * Wait for the border to be displayed -+ */ -+ while (I915_READ(pipe_dsl_reg) >= vactive) -+ ; -+ while ((dsl = I915_READ(pipe_dsl_reg)) <= vsample) -+ ; -+ /* -+ * Watch ST00 for an entire scanline -+ */ -+ detect = 0; -+ count = 0; -+ do { -+ count++; -+ /* Read the ST00 VGA status register */ -+ st00 = I915_READ8(_VGA_MSR_WRITE); -+ if (st00 & (1 << 4)) -+ detect++; -+ } while ((I915_READ(pipe_dsl_reg) == dsl)); -+ -+ /* restore vblank if necessary */ -+ if (restore_vblank) -+ I915_WRITE(vblank_reg, vblank); -+ /* -+ * If more than 3/4 of the scanline detected a monitor, -+ * then it is assumed to be present. This works even on i830, -+ * where there isn't any way to force the border color across -+ * the screen -+ */ -+ status = detect * 4 > count * 3 ? -+ connector_status_connected : -+ connector_status_disconnected; -+ } -+ -+ /* Restore previous settings */ -+ I915_WRITE(bclrpat_reg, save_bclrpat); -+ -+ return status; -+} -+ -+static int intel_spurious_crt_detect_dmi_callback(const struct dmi_system_id *id) -+{ -+ DRM_DEBUG_DRIVER("Skipping CRT detection for %s\n", id->ident); -+ return 1; -+} -+ -+static const struct dmi_system_id intel_spurious_crt_detect[] = { -+ { -+ .callback = intel_spurious_crt_detect_dmi_callback, -+ .ident = "ACER ZGB", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "ACER"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "ZGB"), -+ }, -+ }, -+ { -+ .callback = intel_spurious_crt_detect_dmi_callback, -+ .ident = "Intel DZ77BH-55K", -+ .matches = { -+ DMI_MATCH(DMI_BOARD_VENDOR, "Intel Corporation"), -+ DMI_MATCH(DMI_BOARD_NAME, "DZ77BH-55K"), -+ }, -+ }, -+ { } -+}; -+ -+static int -+intel_crt_detect(struct drm_connector *connector, -+ struct drm_modeset_acquire_ctx *ctx, -+ bool force) -+{ -+ struct drm_i915_private *dev_priv = to_i915(connector->dev); -+ struct intel_crt *crt = intel_attached_crt(connector); -+ struct intel_encoder *intel_encoder = &crt->base; -+ intel_wakeref_t wakeref; -+ int status, ret; -+ struct intel_load_detect_pipe tmp; -+ -+ DRM_DEBUG_KMS("[CONNECTOR:%d:%s] force=%d\n", -+ connector->base.id, connector->name, -+ force); -+ -+ if (i915_modparams.load_detect_test) { -+ wakeref = intel_display_power_get(dev_priv, -+ intel_encoder->power_domain); -+ goto load_detect; -+ } -+ -+ /* Skip machines without VGA that falsely report hotplug events */ -+ if (dmi_check_system(intel_spurious_crt_detect)) -+ return connector_status_disconnected; -+ -+ wakeref = intel_display_power_get(dev_priv, -+ intel_encoder->power_domain); -+ -+ if (I915_HAS_HOTPLUG(dev_priv)) { -+ /* We can not rely on the HPD pin always being correctly wired -+ * up, for example many KVM do not pass it through, and so -+ * only trust an assertion that the monitor is connected. -+ */ -+ if (intel_crt_detect_hotplug(connector)) { -+ DRM_DEBUG_KMS("CRT detected via hotplug\n"); -+ status = connector_status_connected; -+ goto out; -+ } else -+ DRM_DEBUG_KMS("CRT not detected via hotplug\n"); -+ } -+ -+ if (intel_crt_detect_ddc(connector)) { -+ status = connector_status_connected; -+ goto out; -+ } -+ -+ /* Load detection is broken on HPD capable machines. Whoever wants a -+ * broken monitor (without edid) to work behind a broken kvm (that fails -+ * to have the right resistors for HP detection) needs to fix this up. -+ * For now just bail out. */ -+ if (I915_HAS_HOTPLUG(dev_priv)) { -+ status = connector_status_disconnected; -+ goto out; -+ } -+ -+load_detect: -+ if (!force) { -+ status = connector->status; -+ goto out; -+ } -+ -+ /* for pre-945g platforms use load detect */ -+ ret = intel_get_load_detect_pipe(connector, NULL, &tmp, ctx); -+ if (ret > 0) { -+ if (intel_crt_detect_ddc(connector)) -+ status = connector_status_connected; -+ else if (INTEL_GEN(dev_priv) < 4) -+ status = intel_crt_load_detect(crt, -+ to_intel_crtc(connector->state->crtc)->pipe); -+ else if (i915_modparams.load_detect_test) -+ status = connector_status_disconnected; -+ else -+ status = connector_status_unknown; -+ intel_release_load_detect_pipe(connector, &tmp, ctx); -+ } else if (ret == 0) { -+ status = connector_status_unknown; -+ } else { -+ status = ret; -+ } -+ -+out: -+ intel_display_power_put(dev_priv, intel_encoder->power_domain, wakeref); -+ return status; -+} -+ -+static int intel_crt_get_modes(struct drm_connector *connector) -+{ -+ struct drm_device *dev = connector->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crt *crt = intel_attached_crt(connector); -+ struct intel_encoder *intel_encoder = &crt->base; -+ intel_wakeref_t wakeref; -+ struct i2c_adapter *i2c; -+ int ret; -+ -+ wakeref = intel_display_power_get(dev_priv, -+ intel_encoder->power_domain); -+ -+ i2c = intel_gmbus_get_adapter(dev_priv, dev_priv->vbt.crt_ddc_pin); -+ ret = intel_crt_ddc_get_modes(connector, i2c); -+ if (ret || !IS_G4X(dev_priv)) -+ goto out; -+ -+ /* Try to probe digital port for output in DVI-I -> VGA mode. */ -+ i2c = intel_gmbus_get_adapter(dev_priv, GMBUS_PIN_DPB); -+ ret = intel_crt_ddc_get_modes(connector, i2c); -+ -+out: -+ intel_display_power_put(dev_priv, intel_encoder->power_domain, wakeref); -+ -+ return ret; -+} -+ -+void intel_crt_reset(struct drm_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->dev); -+ struct intel_crt *crt = intel_encoder_to_crt(to_intel_encoder(encoder)); -+ -+ if (INTEL_GEN(dev_priv) >= 5) { -+ u32 adpa; -+ -+ adpa = I915_READ(crt->adpa_reg); -+ adpa &= ~ADPA_CRT_HOTPLUG_MASK; -+ adpa |= ADPA_HOTPLUG_BITS; -+ I915_WRITE(crt->adpa_reg, adpa); -+ POSTING_READ(crt->adpa_reg); -+ -+ DRM_DEBUG_KMS("crt adpa set to 0x%x\n", adpa); -+ crt->force_hotplug_required = 1; -+ } -+ -+} -+ -+/* -+ * Routines for controlling stuff on the analog port -+ */ -+ -+static const struct drm_connector_funcs intel_crt_connector_funcs = { -+ .fill_modes = drm_helper_probe_single_connector_modes, -+ .late_register = intel_connector_register, -+ .early_unregister = intel_connector_unregister, -+ .destroy = intel_connector_destroy, -+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, -+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, -+}; -+ -+static const struct drm_connector_helper_funcs intel_crt_connector_helper_funcs = { -+ .detect_ctx = intel_crt_detect, -+ .mode_valid = intel_crt_mode_valid, -+ .get_modes = intel_crt_get_modes, -+}; -+ -+static const struct drm_encoder_funcs intel_crt_enc_funcs = { -+ .reset = intel_crt_reset, -+ .destroy = intel_encoder_destroy, -+}; -+ -+void intel_crt_init(struct drm_i915_private *dev_priv) -+{ -+ struct drm_connector *connector; -+ struct intel_crt *crt; -+ struct intel_connector *intel_connector; -+ i915_reg_t adpa_reg; -+ u32 adpa; -+ -+ if (HAS_PCH_SPLIT(dev_priv)) -+ adpa_reg = PCH_ADPA; -+ else if (IS_VALLEYVIEW(dev_priv)) -+ adpa_reg = VLV_ADPA; -+ else -+ adpa_reg = ADPA; -+ -+ adpa = I915_READ(adpa_reg); -+ if ((adpa & ADPA_DAC_ENABLE) == 0) { -+ /* -+ * On some machines (some IVB at least) CRT can be -+ * fused off, but there's no known fuse bit to -+ * indicate that. On these machine the ADPA register -+ * works normally, except the DAC enable bit won't -+ * take. So the only way to tell is attempt to enable -+ * it and see what happens. -+ */ -+ I915_WRITE(adpa_reg, adpa | ADPA_DAC_ENABLE | -+ ADPA_HSYNC_CNTL_DISABLE | ADPA_VSYNC_CNTL_DISABLE); -+ if ((I915_READ(adpa_reg) & ADPA_DAC_ENABLE) == 0) -+ return; -+ I915_WRITE(adpa_reg, adpa); -+ } -+ -+ crt = kzalloc(sizeof(struct intel_crt), GFP_KERNEL); -+ if (!crt) -+ return; -+ -+ intel_connector = intel_connector_alloc(); -+ if (!intel_connector) { -+ kfree(crt); -+ return; -+ } -+ -+ connector = &intel_connector->base; -+ crt->connector = intel_connector; -+ drm_connector_init(&dev_priv->drm, &intel_connector->base, -+ &intel_crt_connector_funcs, DRM_MODE_CONNECTOR_VGA); -+ -+ drm_encoder_init(&dev_priv->drm, &crt->base.base, &intel_crt_enc_funcs, -+ DRM_MODE_ENCODER_DAC, "CRT"); -+ -+ intel_connector_attach_encoder(intel_connector, &crt->base); -+ -+ crt->base.type = INTEL_OUTPUT_ANALOG; -+ crt->base.cloneable = (1 << INTEL_OUTPUT_DVO) | (1 << INTEL_OUTPUT_HDMI); -+ if (IS_I830(dev_priv)) -+ crt->base.crtc_mask = (1 << 0); -+ else -+ crt->base.crtc_mask = (1 << 0) | (1 << 1) | (1 << 2); -+ -+ if (IS_GEN(dev_priv, 2)) -+ connector->interlace_allowed = 0; -+ else -+ connector->interlace_allowed = 1; -+ connector->doublescan_allowed = 0; -+ -+ crt->adpa_reg = adpa_reg; -+ -+ crt->base.power_domain = POWER_DOMAIN_PORT_CRT; -+ -+ if (I915_HAS_HOTPLUG(dev_priv) && -+ !dmi_check_system(intel_spurious_crt_detect)) { -+ crt->base.hpd_pin = HPD_CRT; -+ crt->base.hotplug = intel_encoder_hotplug; -+ } -+ -+ if (HAS_DDI(dev_priv)) { -+ crt->base.port = PORT_E; -+ crt->base.get_config = hsw_crt_get_config; -+ crt->base.get_hw_state = intel_ddi_get_hw_state; -+ crt->base.compute_config = hsw_crt_compute_config; -+ crt->base.pre_pll_enable = hsw_pre_pll_enable_crt; -+ crt->base.pre_enable = hsw_pre_enable_crt; -+ crt->base.enable = hsw_enable_crt; -+ crt->base.disable = hsw_disable_crt; -+ crt->base.post_disable = hsw_post_disable_crt; -+ } else { -+ if (HAS_PCH_SPLIT(dev_priv)) { -+ crt->base.compute_config = pch_crt_compute_config; -+ crt->base.disable = pch_disable_crt; -+ crt->base.post_disable = pch_post_disable_crt; -+ } else { -+ crt->base.compute_config = intel_crt_compute_config; -+ crt->base.disable = intel_disable_crt; -+ } -+ crt->base.port = PORT_NONE; -+ crt->base.get_config = intel_crt_get_config; -+ crt->base.get_hw_state = intel_crt_get_hw_state; -+ crt->base.enable = intel_enable_crt; -+ } -+ intel_connector->get_hw_state = intel_connector_get_hw_state; -+ -+ drm_connector_helper_add(connector, &intel_crt_connector_helper_funcs); -+ -+ if (!I915_HAS_HOTPLUG(dev_priv)) -+ intel_connector->polled = DRM_CONNECTOR_POLL_CONNECT; -+ -+ /* -+ * Configure the automatic hotplug detection stuff -+ */ -+ crt->force_hotplug_required = 0; -+ -+ /* -+ * TODO: find a proper way to discover whether we need to set the the -+ * polarity and link reversal bits or not, instead of relying on the -+ * BIOS. -+ */ -+ if (HAS_PCH_LPT(dev_priv)) { -+ u32 fdi_config = FDI_RX_POLARITY_REVERSED_LPT | -+ FDI_RX_LINK_REVERSAL_OVERRIDE; -+ -+ dev_priv->fdi_rx_config = I915_READ(FDI_RX_CTL(PIPE_A)) & fdi_config; -+ } -+ -+ intel_crt_reset(&crt->base.base); -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_crt.h b/drivers/gpu/drm/i915_legacy/intel_crt.h -new file mode 100644 -index 000000000000..1b3fba359efc ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_crt.h -@@ -0,0 +1,21 @@ -+/* SPDX-License-Identifier: MIT */ -+/* -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef __INTEL_CRT_H__ -+#define __INTEL_CRT_H__ -+ -+#include "i915_reg.h" -+ -+enum pipe; -+struct drm_encoder; -+struct drm_i915_private; -+struct drm_i915_private; -+ -+bool intel_crt_port_enabled(struct drm_i915_private *dev_priv, -+ i915_reg_t adpa_reg, enum pipe *pipe); -+void intel_crt_init(struct drm_i915_private *dev_priv); -+void intel_crt_reset(struct drm_encoder *encoder); -+ -+#endif /* __INTEL_CRT_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_csr.c b/drivers/gpu/drm/i915_legacy/intel_csr.c -new file mode 100644 -index 000000000000..96618af47088 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_csr.c -@@ -0,0 +1,615 @@ -+/* -+ * Copyright © 2014 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "i915_reg.h" -+#include "intel_csr.h" -+ -+/** -+ * DOC: csr support for dmc -+ * -+ * Display Context Save and Restore (CSR) firmware support added from gen9 -+ * onwards to drive newly added DMC (Display microcontroller) in display -+ * engine to save and restore the state of display engine when it enter into -+ * low-power state and comes back to normal. -+ */ -+ -+#define GEN12_CSR_MAX_FW_SIZE ICL_CSR_MAX_FW_SIZE -+ -+#define ICL_CSR_PATH "i915/icl_dmc_ver1_07.bin" -+#define ICL_CSR_VERSION_REQUIRED CSR_VERSION(1, 7) -+#define ICL_CSR_MAX_FW_SIZE 0x6000 -+MODULE_FIRMWARE(ICL_CSR_PATH); -+ -+#define CNL_CSR_PATH "i915/cnl_dmc_ver1_07.bin" -+#define CNL_CSR_VERSION_REQUIRED CSR_VERSION(1, 7) -+#define CNL_CSR_MAX_FW_SIZE GLK_CSR_MAX_FW_SIZE -+MODULE_FIRMWARE(CNL_CSR_PATH); -+ -+#define GLK_CSR_PATH "i915/glk_dmc_ver1_04.bin" -+#define GLK_CSR_VERSION_REQUIRED CSR_VERSION(1, 4) -+#define GLK_CSR_MAX_FW_SIZE 0x4000 -+MODULE_FIRMWARE(GLK_CSR_PATH); -+ -+#define KBL_CSR_PATH "i915/kbl_dmc_ver1_04.bin" -+#define KBL_CSR_VERSION_REQUIRED CSR_VERSION(1, 4) -+#define KBL_CSR_MAX_FW_SIZE BXT_CSR_MAX_FW_SIZE -+MODULE_FIRMWARE(KBL_CSR_PATH); -+ -+#define SKL_CSR_PATH "i915/skl_dmc_ver1_27.bin" -+#define SKL_CSR_VERSION_REQUIRED CSR_VERSION(1, 27) -+#define SKL_CSR_MAX_FW_SIZE BXT_CSR_MAX_FW_SIZE -+MODULE_FIRMWARE(SKL_CSR_PATH); -+ -+#define BXT_CSR_PATH "i915/bxt_dmc_ver1_07.bin" -+#define BXT_CSR_VERSION_REQUIRED CSR_VERSION(1, 7) -+#define BXT_CSR_MAX_FW_SIZE 0x3000 -+MODULE_FIRMWARE(BXT_CSR_PATH); -+ -+#define CSR_DEFAULT_FW_OFFSET 0xFFFFFFFF -+ -+struct intel_css_header { -+ /* 0x09 for DMC */ -+ u32 module_type; -+ -+ /* Includes the DMC specific header in dwords */ -+ u32 header_len; -+ -+ /* always value would be 0x10000 */ -+ u32 header_ver; -+ -+ /* Not used */ -+ u32 module_id; -+ -+ /* Not used */ -+ u32 module_vendor; -+ -+ /* in YYYYMMDD format */ -+ u32 date; -+ -+ /* Size in dwords (CSS_Headerlen + PackageHeaderLen + dmc FWsLen)/4 */ -+ u32 size; -+ -+ /* Not used */ -+ u32 key_size; -+ -+ /* Not used */ -+ u32 modulus_size; -+ -+ /* Not used */ -+ u32 exponent_size; -+ -+ /* Not used */ -+ u32 reserved1[12]; -+ -+ /* Major Minor */ -+ u32 version; -+ -+ /* Not used */ -+ u32 reserved2[8]; -+ -+ /* Not used */ -+ u32 kernel_header_info; -+} __packed; -+ -+struct intel_fw_info { -+ u16 reserved1; -+ -+ /* Stepping (A, B, C, ..., *). * is a wildcard */ -+ char stepping; -+ -+ /* Sub-stepping (0, 1, ..., *). * is a wildcard */ -+ char substepping; -+ -+ u32 offset; -+ u32 reserved2; -+} __packed; -+ -+struct intel_package_header { -+ /* DMC container header length in dwords */ -+ unsigned char header_len; -+ -+ /* always value would be 0x01 */ -+ unsigned char header_ver; -+ -+ unsigned char reserved[10]; -+ -+ /* Number of valid entries in the FWInfo array below */ -+ u32 num_entries; -+ -+ struct intel_fw_info fw_info[20]; -+} __packed; -+ -+struct intel_dmc_header { -+ /* always value would be 0x40403E3E */ -+ u32 signature; -+ -+ /* DMC binary header length */ -+ unsigned char header_len; -+ -+ /* 0x01 */ -+ unsigned char header_ver; -+ -+ /* Reserved */ -+ u16 dmcc_ver; -+ -+ /* Major, Minor */ -+ u32 project; -+ -+ /* Firmware program size (excluding header) in dwords */ -+ u32 fw_size; -+ -+ /* Major Minor version */ -+ u32 fw_version; -+ -+ /* Number of valid MMIO cycles present. */ -+ u32 mmio_count; -+ -+ /* MMIO address */ -+ u32 mmioaddr[8]; -+ -+ /* MMIO data */ -+ u32 mmiodata[8]; -+ -+ /* FW filename */ -+ unsigned char dfile[32]; -+ -+ u32 reserved1[2]; -+} __packed; -+ -+struct stepping_info { -+ char stepping; -+ char substepping; -+}; -+ -+static const struct stepping_info skl_stepping_info[] = { -+ {'A', '0'}, {'B', '0'}, {'C', '0'}, -+ {'D', '0'}, {'E', '0'}, {'F', '0'}, -+ {'G', '0'}, {'H', '0'}, {'I', '0'}, -+ {'J', '0'}, {'K', '0'} -+}; -+ -+static const struct stepping_info bxt_stepping_info[] = { -+ {'A', '0'}, {'A', '1'}, {'A', '2'}, -+ {'B', '0'}, {'B', '1'}, {'B', '2'} -+}; -+ -+static const struct stepping_info icl_stepping_info[] = { -+ {'A', '0'}, {'A', '1'}, {'A', '2'}, -+ {'B', '0'}, {'B', '2'}, -+ {'C', '0'} -+}; -+ -+static const struct stepping_info no_stepping_info = { '*', '*' }; -+ -+static const struct stepping_info * -+intel_get_stepping_info(struct drm_i915_private *dev_priv) -+{ -+ const struct stepping_info *si; -+ unsigned int size; -+ -+ if (IS_ICELAKE(dev_priv)) { -+ size = ARRAY_SIZE(icl_stepping_info); -+ si = icl_stepping_info; -+ } else if (IS_SKYLAKE(dev_priv)) { -+ size = ARRAY_SIZE(skl_stepping_info); -+ si = skl_stepping_info; -+ } else if (IS_BROXTON(dev_priv)) { -+ size = ARRAY_SIZE(bxt_stepping_info); -+ si = bxt_stepping_info; -+ } else { -+ size = 0; -+ si = NULL; -+ } -+ -+ if (INTEL_REVID(dev_priv) < size) -+ return si + INTEL_REVID(dev_priv); -+ -+ return &no_stepping_info; -+} -+ -+static void gen9_set_dc_state_debugmask(struct drm_i915_private *dev_priv) -+{ -+ u32 val, mask; -+ -+ mask = DC_STATE_DEBUG_MASK_MEMORY_UP; -+ -+ if (IS_GEN9_LP(dev_priv)) -+ mask |= DC_STATE_DEBUG_MASK_CORES; -+ -+ /* The below bit doesn't need to be cleared ever afterwards */ -+ val = I915_READ(DC_STATE_DEBUG); -+ if ((val & mask) != mask) { -+ val |= mask; -+ I915_WRITE(DC_STATE_DEBUG, val); -+ POSTING_READ(DC_STATE_DEBUG); -+ } -+} -+ -+/** -+ * intel_csr_load_program() - write the firmware from memory to register. -+ * @dev_priv: i915 drm device. -+ * -+ * CSR firmware is read from a .bin file and kept in internal memory one time. -+ * Everytime display comes back from low power state this function is called to -+ * copy the firmware from internal memory to registers. -+ */ -+void intel_csr_load_program(struct drm_i915_private *dev_priv) -+{ -+ u32 *payload = dev_priv->csr.dmc_payload; -+ u32 i, fw_size; -+ -+ if (!HAS_CSR(dev_priv)) { -+ DRM_ERROR("No CSR support available for this platform\n"); -+ return; -+ } -+ -+ if (!dev_priv->csr.dmc_payload) { -+ DRM_ERROR("Tried to program CSR with empty payload\n"); -+ return; -+ } -+ -+ fw_size = dev_priv->csr.dmc_fw_size; -+ assert_rpm_wakelock_held(dev_priv); -+ -+ preempt_disable(); -+ -+ for (i = 0; i < fw_size; i++) -+ I915_WRITE_FW(CSR_PROGRAM(i), payload[i]); -+ -+ preempt_enable(); -+ -+ for (i = 0; i < dev_priv->csr.mmio_count; i++) { -+ I915_WRITE(dev_priv->csr.mmioaddr[i], -+ dev_priv->csr.mmiodata[i]); -+ } -+ -+ dev_priv->csr.dc_state = 0; -+ -+ gen9_set_dc_state_debugmask(dev_priv); -+} -+ -+static u32 *parse_csr_fw(struct drm_i915_private *dev_priv, -+ const struct firmware *fw) -+{ -+ struct intel_css_header *css_header; -+ struct intel_package_header *package_header; -+ struct intel_dmc_header *dmc_header; -+ struct intel_csr *csr = &dev_priv->csr; -+ const struct stepping_info *si = intel_get_stepping_info(dev_priv); -+ u32 dmc_offset = CSR_DEFAULT_FW_OFFSET, readcount = 0, nbytes; -+ u32 i; -+ u32 *dmc_payload; -+ size_t fsize; -+ -+ if (!fw) -+ return NULL; -+ -+ fsize = sizeof(struct intel_css_header) + -+ sizeof(struct intel_package_header) + -+ sizeof(struct intel_dmc_header); -+ if (fsize > fw->size) -+ goto error_truncated; -+ -+ /* Extract CSS Header information*/ -+ css_header = (struct intel_css_header *)fw->data; -+ if (sizeof(struct intel_css_header) != -+ (css_header->header_len * 4)) { -+ DRM_ERROR("DMC firmware has wrong CSS header length " -+ "(%u bytes)\n", -+ (css_header->header_len * 4)); -+ return NULL; -+ } -+ -+ if (csr->required_version && -+ css_header->version != csr->required_version) { -+ DRM_INFO("Refusing to load DMC firmware v%u.%u," -+ " please use v%u.%u\n", -+ CSR_VERSION_MAJOR(css_header->version), -+ CSR_VERSION_MINOR(css_header->version), -+ CSR_VERSION_MAJOR(csr->required_version), -+ CSR_VERSION_MINOR(csr->required_version)); -+ return NULL; -+ } -+ -+ csr->version = css_header->version; -+ -+ readcount += sizeof(struct intel_css_header); -+ -+ /* Extract Package Header information*/ -+ package_header = (struct intel_package_header *) -+ &fw->data[readcount]; -+ if (sizeof(struct intel_package_header) != -+ (package_header->header_len * 4)) { -+ DRM_ERROR("DMC firmware has wrong package header length " -+ "(%u bytes)\n", -+ (package_header->header_len * 4)); -+ return NULL; -+ } -+ readcount += sizeof(struct intel_package_header); -+ -+ /* Search for dmc_offset to find firware binary. */ -+ for (i = 0; i < package_header->num_entries; i++) { -+ if (package_header->fw_info[i].substepping == '*' && -+ si->stepping == package_header->fw_info[i].stepping) { -+ dmc_offset = package_header->fw_info[i].offset; -+ break; -+ } else if (si->stepping == package_header->fw_info[i].stepping && -+ si->substepping == package_header->fw_info[i].substepping) { -+ dmc_offset = package_header->fw_info[i].offset; -+ break; -+ } else if (package_header->fw_info[i].stepping == '*' && -+ package_header->fw_info[i].substepping == '*') -+ dmc_offset = package_header->fw_info[i].offset; -+ } -+ if (dmc_offset == CSR_DEFAULT_FW_OFFSET) { -+ DRM_ERROR("DMC firmware not supported for %c stepping\n", -+ si->stepping); -+ return NULL; -+ } -+ /* Convert dmc_offset into number of bytes. By default it is in dwords*/ -+ dmc_offset *= 4; -+ readcount += dmc_offset; -+ fsize += dmc_offset; -+ if (fsize > fw->size) -+ goto error_truncated; -+ -+ /* Extract dmc_header information. */ -+ dmc_header = (struct intel_dmc_header *)&fw->data[readcount]; -+ if (sizeof(struct intel_dmc_header) != (dmc_header->header_len)) { -+ DRM_ERROR("DMC firmware has wrong dmc header length " -+ "(%u bytes)\n", -+ (dmc_header->header_len)); -+ return NULL; -+ } -+ readcount += sizeof(struct intel_dmc_header); -+ -+ /* Cache the dmc header info. */ -+ if (dmc_header->mmio_count > ARRAY_SIZE(csr->mmioaddr)) { -+ DRM_ERROR("DMC firmware has wrong mmio count %u\n", -+ dmc_header->mmio_count); -+ return NULL; -+ } -+ csr->mmio_count = dmc_header->mmio_count; -+ for (i = 0; i < dmc_header->mmio_count; i++) { -+ if (dmc_header->mmioaddr[i] < CSR_MMIO_START_RANGE || -+ dmc_header->mmioaddr[i] > CSR_MMIO_END_RANGE) { -+ DRM_ERROR("DMC firmware has wrong mmio address 0x%x\n", -+ dmc_header->mmioaddr[i]); -+ return NULL; -+ } -+ csr->mmioaddr[i] = _MMIO(dmc_header->mmioaddr[i]); -+ csr->mmiodata[i] = dmc_header->mmiodata[i]; -+ } -+ -+ /* fw_size is in dwords, so multiplied by 4 to convert into bytes. */ -+ nbytes = dmc_header->fw_size * 4; -+ fsize += nbytes; -+ if (fsize > fw->size) -+ goto error_truncated; -+ -+ if (nbytes > csr->max_fw_size) { -+ DRM_ERROR("DMC FW too big (%u bytes)\n", nbytes); -+ return NULL; -+ } -+ csr->dmc_fw_size = dmc_header->fw_size; -+ -+ dmc_payload = kmalloc(nbytes, GFP_KERNEL); -+ if (!dmc_payload) { -+ DRM_ERROR("Memory allocation failed for dmc payload\n"); -+ return NULL; -+ } -+ -+ return memcpy(dmc_payload, &fw->data[readcount], nbytes); -+ -+error_truncated: -+ DRM_ERROR("Truncated DMC firmware, rejecting.\n"); -+ return NULL; -+} -+ -+static void intel_csr_runtime_pm_get(struct drm_i915_private *dev_priv) -+{ -+ WARN_ON(dev_priv->csr.wakeref); -+ dev_priv->csr.wakeref = -+ intel_display_power_get(dev_priv, POWER_DOMAIN_INIT); -+} -+ -+static void intel_csr_runtime_pm_put(struct drm_i915_private *dev_priv) -+{ -+ intel_wakeref_t wakeref __maybe_unused = -+ fetch_and_zero(&dev_priv->csr.wakeref); -+ -+ intel_display_power_put(dev_priv, POWER_DOMAIN_INIT, wakeref); -+} -+ -+static void csr_load_work_fn(struct work_struct *work) -+{ -+ struct drm_i915_private *dev_priv; -+ struct intel_csr *csr; -+ const struct firmware *fw = NULL; -+ -+ dev_priv = container_of(work, typeof(*dev_priv), csr.work); -+ csr = &dev_priv->csr; -+ -+ request_firmware(&fw, dev_priv->csr.fw_path, &dev_priv->drm.pdev->dev); -+ if (fw) -+ dev_priv->csr.dmc_payload = parse_csr_fw(dev_priv, fw); -+ -+ if (dev_priv->csr.dmc_payload) { -+ intel_csr_load_program(dev_priv); -+ intel_csr_runtime_pm_put(dev_priv); -+ -+ DRM_INFO("Finished loading DMC firmware %s (v%u.%u)\n", -+ dev_priv->csr.fw_path, -+ CSR_VERSION_MAJOR(csr->version), -+ CSR_VERSION_MINOR(csr->version)); -+ } else { -+ dev_notice(dev_priv->drm.dev, -+ "Failed to load DMC firmware %s." -+ " Disabling runtime power management.\n", -+ csr->fw_path); -+ dev_notice(dev_priv->drm.dev, "DMC firmware homepage: %s", -+ INTEL_UC_FIRMWARE_URL); -+ } -+ -+ release_firmware(fw); -+} -+ -+/** -+ * intel_csr_ucode_init() - initialize the firmware loading. -+ * @dev_priv: i915 drm device. -+ * -+ * This function is called at the time of loading the display driver to read -+ * firmware from a .bin file and copied into a internal memory. -+ */ -+void intel_csr_ucode_init(struct drm_i915_private *dev_priv) -+{ -+ struct intel_csr *csr = &dev_priv->csr; -+ -+ INIT_WORK(&dev_priv->csr.work, csr_load_work_fn); -+ -+ if (!HAS_CSR(dev_priv)) -+ return; -+ -+ /* -+ * Obtain a runtime pm reference, until CSR is loaded, to avoid entering -+ * runtime-suspend. -+ * -+ * On error, we return with the rpm wakeref held to prevent runtime -+ * suspend as runtime suspend *requires* a working CSR for whatever -+ * reason. -+ */ -+ intel_csr_runtime_pm_get(dev_priv); -+ -+ if (INTEL_GEN(dev_priv) >= 12) { -+ /* Allow to load fw via parameter using the last known size */ -+ csr->max_fw_size = GEN12_CSR_MAX_FW_SIZE; -+ } else if (IS_GEN(dev_priv, 11)) { -+ csr->fw_path = ICL_CSR_PATH; -+ csr->required_version = ICL_CSR_VERSION_REQUIRED; -+ csr->max_fw_size = ICL_CSR_MAX_FW_SIZE; -+ } else if (IS_CANNONLAKE(dev_priv)) { -+ csr->fw_path = CNL_CSR_PATH; -+ csr->required_version = CNL_CSR_VERSION_REQUIRED; -+ csr->max_fw_size = CNL_CSR_MAX_FW_SIZE; -+ } else if (IS_GEMINILAKE(dev_priv)) { -+ csr->fw_path = GLK_CSR_PATH; -+ csr->required_version = GLK_CSR_VERSION_REQUIRED; -+ csr->max_fw_size = GLK_CSR_MAX_FW_SIZE; -+ } else if (IS_KABYLAKE(dev_priv) || IS_COFFEELAKE(dev_priv)) { -+ csr->fw_path = KBL_CSR_PATH; -+ csr->required_version = KBL_CSR_VERSION_REQUIRED; -+ csr->max_fw_size = KBL_CSR_MAX_FW_SIZE; -+ } else if (IS_SKYLAKE(dev_priv)) { -+ csr->fw_path = SKL_CSR_PATH; -+ csr->required_version = SKL_CSR_VERSION_REQUIRED; -+ csr->max_fw_size = SKL_CSR_MAX_FW_SIZE; -+ } else if (IS_BROXTON(dev_priv)) { -+ csr->fw_path = BXT_CSR_PATH; -+ csr->required_version = BXT_CSR_VERSION_REQUIRED; -+ csr->max_fw_size = BXT_CSR_MAX_FW_SIZE; -+ } -+ -+ if (i915_modparams.dmc_firmware_path) { -+ if (strlen(i915_modparams.dmc_firmware_path) == 0) { -+ csr->fw_path = NULL; -+ DRM_INFO("Disabling CSR firmware and runtime PM\n"); -+ return; -+ } -+ -+ csr->fw_path = i915_modparams.dmc_firmware_path; -+ /* Bypass version check for firmware override. */ -+ csr->required_version = 0; -+ } -+ -+ if (csr->fw_path == NULL) { -+ DRM_DEBUG_KMS("No known CSR firmware for platform, disabling runtime PM\n"); -+ WARN_ON(!IS_ALPHA_SUPPORT(INTEL_INFO(dev_priv))); -+ -+ return; -+ } -+ -+ DRM_DEBUG_KMS("Loading %s\n", csr->fw_path); -+ schedule_work(&dev_priv->csr.work); -+} -+ -+/** -+ * intel_csr_ucode_suspend() - prepare CSR firmware before system suspend -+ * @dev_priv: i915 drm device -+ * -+ * Prepare the DMC firmware before entering system suspend. This includes -+ * flushing pending work items and releasing any resources acquired during -+ * init. -+ */ -+void intel_csr_ucode_suspend(struct drm_i915_private *dev_priv) -+{ -+ if (!HAS_CSR(dev_priv)) -+ return; -+ -+ flush_work(&dev_priv->csr.work); -+ -+ /* Drop the reference held in case DMC isn't loaded. */ -+ if (!dev_priv->csr.dmc_payload) -+ intel_csr_runtime_pm_put(dev_priv); -+} -+ -+/** -+ * intel_csr_ucode_resume() - init CSR firmware during system resume -+ * @dev_priv: i915 drm device -+ * -+ * Reinitialize the DMC firmware during system resume, reacquiring any -+ * resources released in intel_csr_ucode_suspend(). -+ */ -+void intel_csr_ucode_resume(struct drm_i915_private *dev_priv) -+{ -+ if (!HAS_CSR(dev_priv)) -+ return; -+ -+ /* -+ * Reacquire the reference to keep RPM disabled in case DMC isn't -+ * loaded. -+ */ -+ if (!dev_priv->csr.dmc_payload) -+ intel_csr_runtime_pm_get(dev_priv); -+} -+ -+/** -+ * intel_csr_ucode_fini() - unload the CSR firmware. -+ * @dev_priv: i915 drm device. -+ * -+ * Firmmware unloading includes freeing the internal memory and reset the -+ * firmware loading status. -+ */ -+void intel_csr_ucode_fini(struct drm_i915_private *dev_priv) -+{ -+ if (!HAS_CSR(dev_priv)) -+ return; -+ -+ intel_csr_ucode_suspend(dev_priv); -+ WARN_ON(dev_priv->csr.wakeref); -+ -+ kfree(dev_priv->csr.dmc_payload); -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_csr.h b/drivers/gpu/drm/i915_legacy/intel_csr.h -new file mode 100644 -index 000000000000..17a32c1e8a35 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_csr.h -@@ -0,0 +1,17 @@ -+/* SPDX-License-Identifier: MIT */ -+/* -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef __INTEL_CSR_H__ -+#define __INTEL_CSR_H__ -+ -+struct drm_i915_private; -+ -+void intel_csr_ucode_init(struct drm_i915_private *i915); -+void intel_csr_load_program(struct drm_i915_private *i915); -+void intel_csr_ucode_fini(struct drm_i915_private *i915); -+void intel_csr_ucode_suspend(struct drm_i915_private *i915); -+void intel_csr_ucode_resume(struct drm_i915_private *i915); -+ -+#endif /* __INTEL_CSR_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_ddi.c b/drivers/gpu/drm/i915_legacy/intel_ddi.c -new file mode 100644 -index 000000000000..f181c26f62fd ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_ddi.c -@@ -0,0 +1,4286 @@ -+/* -+ * Copyright © 2012 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eugeni Dodonov -+ * -+ */ -+ -+#include -+ -+#include "i915_drv.h" -+#include "intel_audio.h" -+#include "intel_connector.h" -+#include "intel_ddi.h" -+#include "intel_dp.h" -+#include "intel_drv.h" -+#include "intel_dsi.h" -+#include "intel_hdcp.h" -+#include "intel_hdmi.h" -+#include "intel_lspcon.h" -+#include "intel_panel.h" -+#include "intel_psr.h" -+ -+struct ddi_buf_trans { -+ u32 trans1; /* balance leg enable, de-emph level */ -+ u32 trans2; /* vref sel, vswing */ -+ u8 i_boost; /* SKL: I_boost; valid: 0x0, 0x1, 0x3, 0x7 */ -+}; -+ -+static const u8 index_to_dp_signal_levels[] = { -+ [0] = DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_0, -+ [1] = DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_1, -+ [2] = DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_2, -+ [3] = DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_3, -+ [4] = DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_0, -+ [5] = DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_1, -+ [6] = DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_2, -+ [7] = DP_TRAIN_VOLTAGE_SWING_LEVEL_2 | DP_TRAIN_PRE_EMPH_LEVEL_0, -+ [8] = DP_TRAIN_VOLTAGE_SWING_LEVEL_2 | DP_TRAIN_PRE_EMPH_LEVEL_1, -+ [9] = DP_TRAIN_VOLTAGE_SWING_LEVEL_3 | DP_TRAIN_PRE_EMPH_LEVEL_0, -+}; -+ -+/* HDMI/DVI modes ignore everything but the last 2 items. So we share -+ * them for both DP and FDI transports, allowing those ports to -+ * automatically adapt to HDMI connections as well -+ */ -+static const struct ddi_buf_trans hsw_ddi_translations_dp[] = { -+ { 0x00FFFFFF, 0x0006000E, 0x0 }, -+ { 0x00D75FFF, 0x0005000A, 0x0 }, -+ { 0x00C30FFF, 0x00040006, 0x0 }, -+ { 0x80AAAFFF, 0x000B0000, 0x0 }, -+ { 0x00FFFFFF, 0x0005000A, 0x0 }, -+ { 0x00D75FFF, 0x000C0004, 0x0 }, -+ { 0x80C30FFF, 0x000B0000, 0x0 }, -+ { 0x00FFFFFF, 0x00040006, 0x0 }, -+ { 0x80D75FFF, 0x000B0000, 0x0 }, -+}; -+ -+static const struct ddi_buf_trans hsw_ddi_translations_fdi[] = { -+ { 0x00FFFFFF, 0x0007000E, 0x0 }, -+ { 0x00D75FFF, 0x000F000A, 0x0 }, -+ { 0x00C30FFF, 0x00060006, 0x0 }, -+ { 0x00AAAFFF, 0x001E0000, 0x0 }, -+ { 0x00FFFFFF, 0x000F000A, 0x0 }, -+ { 0x00D75FFF, 0x00160004, 0x0 }, -+ { 0x00C30FFF, 0x001E0000, 0x0 }, -+ { 0x00FFFFFF, 0x00060006, 0x0 }, -+ { 0x00D75FFF, 0x001E0000, 0x0 }, -+}; -+ -+static const struct ddi_buf_trans hsw_ddi_translations_hdmi[] = { -+ /* Idx NT mV d T mV d db */ -+ { 0x00FFFFFF, 0x0006000E, 0x0 },/* 0: 400 400 0 */ -+ { 0x00E79FFF, 0x000E000C, 0x0 },/* 1: 400 500 2 */ -+ { 0x00D75FFF, 0x0005000A, 0x0 },/* 2: 400 600 3.5 */ -+ { 0x00FFFFFF, 0x0005000A, 0x0 },/* 3: 600 600 0 */ -+ { 0x00E79FFF, 0x001D0007, 0x0 },/* 4: 600 750 2 */ -+ { 0x00D75FFF, 0x000C0004, 0x0 },/* 5: 600 900 3.5 */ -+ { 0x00FFFFFF, 0x00040006, 0x0 },/* 6: 800 800 0 */ -+ { 0x80E79FFF, 0x00030002, 0x0 },/* 7: 800 1000 2 */ -+ { 0x00FFFFFF, 0x00140005, 0x0 },/* 8: 850 850 0 */ -+ { 0x00FFFFFF, 0x000C0004, 0x0 },/* 9: 900 900 0 */ -+ { 0x00FFFFFF, 0x001C0003, 0x0 },/* 10: 950 950 0 */ -+ { 0x80FFFFFF, 0x00030002, 0x0 },/* 11: 1000 1000 0 */ -+}; -+ -+static const struct ddi_buf_trans bdw_ddi_translations_edp[] = { -+ { 0x00FFFFFF, 0x00000012, 0x0 }, -+ { 0x00EBAFFF, 0x00020011, 0x0 }, -+ { 0x00C71FFF, 0x0006000F, 0x0 }, -+ { 0x00AAAFFF, 0x000E000A, 0x0 }, -+ { 0x00FFFFFF, 0x00020011, 0x0 }, -+ { 0x00DB6FFF, 0x0005000F, 0x0 }, -+ { 0x00BEEFFF, 0x000A000C, 0x0 }, -+ { 0x00FFFFFF, 0x0005000F, 0x0 }, -+ { 0x00DB6FFF, 0x000A000C, 0x0 }, -+}; -+ -+static const struct ddi_buf_trans bdw_ddi_translations_dp[] = { -+ { 0x00FFFFFF, 0x0007000E, 0x0 }, -+ { 0x00D75FFF, 0x000E000A, 0x0 }, -+ { 0x00BEFFFF, 0x00140006, 0x0 }, -+ { 0x80B2CFFF, 0x001B0002, 0x0 }, -+ { 0x00FFFFFF, 0x000E000A, 0x0 }, -+ { 0x00DB6FFF, 0x00160005, 0x0 }, -+ { 0x80C71FFF, 0x001A0002, 0x0 }, -+ { 0x00F7DFFF, 0x00180004, 0x0 }, -+ { 0x80D75FFF, 0x001B0002, 0x0 }, -+}; -+ -+static const struct ddi_buf_trans bdw_ddi_translations_fdi[] = { -+ { 0x00FFFFFF, 0x0001000E, 0x0 }, -+ { 0x00D75FFF, 0x0004000A, 0x0 }, -+ { 0x00C30FFF, 0x00070006, 0x0 }, -+ { 0x00AAAFFF, 0x000C0000, 0x0 }, -+ { 0x00FFFFFF, 0x0004000A, 0x0 }, -+ { 0x00D75FFF, 0x00090004, 0x0 }, -+ { 0x00C30FFF, 0x000C0000, 0x0 }, -+ { 0x00FFFFFF, 0x00070006, 0x0 }, -+ { 0x00D75FFF, 0x000C0000, 0x0 }, -+}; -+ -+static const struct ddi_buf_trans bdw_ddi_translations_hdmi[] = { -+ /* Idx NT mV d T mV df db */ -+ { 0x00FFFFFF, 0x0007000E, 0x0 },/* 0: 400 400 0 */ -+ { 0x00D75FFF, 0x000E000A, 0x0 },/* 1: 400 600 3.5 */ -+ { 0x00BEFFFF, 0x00140006, 0x0 },/* 2: 400 800 6 */ -+ { 0x00FFFFFF, 0x0009000D, 0x0 },/* 3: 450 450 0 */ -+ { 0x00FFFFFF, 0x000E000A, 0x0 },/* 4: 600 600 0 */ -+ { 0x00D7FFFF, 0x00140006, 0x0 },/* 5: 600 800 2.5 */ -+ { 0x80CB2FFF, 0x001B0002, 0x0 },/* 6: 600 1000 4.5 */ -+ { 0x00FFFFFF, 0x00140006, 0x0 },/* 7: 800 800 0 */ -+ { 0x80E79FFF, 0x001B0002, 0x0 },/* 8: 800 1000 2 */ -+ { 0x80FFFFFF, 0x001B0002, 0x0 },/* 9: 1000 1000 0 */ -+}; -+ -+/* Skylake H and S */ -+static const struct ddi_buf_trans skl_ddi_translations_dp[] = { -+ { 0x00002016, 0x000000A0, 0x0 }, -+ { 0x00005012, 0x0000009B, 0x0 }, -+ { 0x00007011, 0x00000088, 0x0 }, -+ { 0x80009010, 0x000000C0, 0x1 }, -+ { 0x00002016, 0x0000009B, 0x0 }, -+ { 0x00005012, 0x00000088, 0x0 }, -+ { 0x80007011, 0x000000C0, 0x1 }, -+ { 0x00002016, 0x000000DF, 0x0 }, -+ { 0x80005012, 0x000000C0, 0x1 }, -+}; -+ -+/* Skylake U */ -+static const struct ddi_buf_trans skl_u_ddi_translations_dp[] = { -+ { 0x0000201B, 0x000000A2, 0x0 }, -+ { 0x00005012, 0x00000088, 0x0 }, -+ { 0x80007011, 0x000000CD, 0x1 }, -+ { 0x80009010, 0x000000C0, 0x1 }, -+ { 0x0000201B, 0x0000009D, 0x0 }, -+ { 0x80005012, 0x000000C0, 0x1 }, -+ { 0x80007011, 0x000000C0, 0x1 }, -+ { 0x00002016, 0x00000088, 0x0 }, -+ { 0x80005012, 0x000000C0, 0x1 }, -+}; -+ -+/* Skylake Y */ -+static const struct ddi_buf_trans skl_y_ddi_translations_dp[] = { -+ { 0x00000018, 0x000000A2, 0x0 }, -+ { 0x00005012, 0x00000088, 0x0 }, -+ { 0x80007011, 0x000000CD, 0x3 }, -+ { 0x80009010, 0x000000C0, 0x3 }, -+ { 0x00000018, 0x0000009D, 0x0 }, -+ { 0x80005012, 0x000000C0, 0x3 }, -+ { 0x80007011, 0x000000C0, 0x3 }, -+ { 0x00000018, 0x00000088, 0x0 }, -+ { 0x80005012, 0x000000C0, 0x3 }, -+}; -+ -+/* Kabylake H and S */ -+static const struct ddi_buf_trans kbl_ddi_translations_dp[] = { -+ { 0x00002016, 0x000000A0, 0x0 }, -+ { 0x00005012, 0x0000009B, 0x0 }, -+ { 0x00007011, 0x00000088, 0x0 }, -+ { 0x80009010, 0x000000C0, 0x1 }, -+ { 0x00002016, 0x0000009B, 0x0 }, -+ { 0x00005012, 0x00000088, 0x0 }, -+ { 0x80007011, 0x000000C0, 0x1 }, -+ { 0x00002016, 0x00000097, 0x0 }, -+ { 0x80005012, 0x000000C0, 0x1 }, -+}; -+ -+/* Kabylake U */ -+static const struct ddi_buf_trans kbl_u_ddi_translations_dp[] = { -+ { 0x0000201B, 0x000000A1, 0x0 }, -+ { 0x00005012, 0x00000088, 0x0 }, -+ { 0x80007011, 0x000000CD, 0x3 }, -+ { 0x80009010, 0x000000C0, 0x3 }, -+ { 0x0000201B, 0x0000009D, 0x0 }, -+ { 0x80005012, 0x000000C0, 0x3 }, -+ { 0x80007011, 0x000000C0, 0x3 }, -+ { 0x00002016, 0x0000004F, 0x0 }, -+ { 0x80005012, 0x000000C0, 0x3 }, -+}; -+ -+/* Kabylake Y */ -+static const struct ddi_buf_trans kbl_y_ddi_translations_dp[] = { -+ { 0x00001017, 0x000000A1, 0x0 }, -+ { 0x00005012, 0x00000088, 0x0 }, -+ { 0x80007011, 0x000000CD, 0x3 }, -+ { 0x8000800F, 0x000000C0, 0x3 }, -+ { 0x00001017, 0x0000009D, 0x0 }, -+ { 0x80005012, 0x000000C0, 0x3 }, -+ { 0x80007011, 0x000000C0, 0x3 }, -+ { 0x00001017, 0x0000004C, 0x0 }, -+ { 0x80005012, 0x000000C0, 0x3 }, -+}; -+ -+/* -+ * Skylake/Kabylake H and S -+ * eDP 1.4 low vswing translation parameters -+ */ -+static const struct ddi_buf_trans skl_ddi_translations_edp[] = { -+ { 0x00000018, 0x000000A8, 0x0 }, -+ { 0x00004013, 0x000000A9, 0x0 }, -+ { 0x00007011, 0x000000A2, 0x0 }, -+ { 0x00009010, 0x0000009C, 0x0 }, -+ { 0x00000018, 0x000000A9, 0x0 }, -+ { 0x00006013, 0x000000A2, 0x0 }, -+ { 0x00007011, 0x000000A6, 0x0 }, -+ { 0x00000018, 0x000000AB, 0x0 }, -+ { 0x00007013, 0x0000009F, 0x0 }, -+ { 0x00000018, 0x000000DF, 0x0 }, -+}; -+ -+/* -+ * Skylake/Kabylake U -+ * eDP 1.4 low vswing translation parameters -+ */ -+static const struct ddi_buf_trans skl_u_ddi_translations_edp[] = { -+ { 0x00000018, 0x000000A8, 0x0 }, -+ { 0x00004013, 0x000000A9, 0x0 }, -+ { 0x00007011, 0x000000A2, 0x0 }, -+ { 0x00009010, 0x0000009C, 0x0 }, -+ { 0x00000018, 0x000000A9, 0x0 }, -+ { 0x00006013, 0x000000A2, 0x0 }, -+ { 0x00007011, 0x000000A6, 0x0 }, -+ { 0x00002016, 0x000000AB, 0x0 }, -+ { 0x00005013, 0x0000009F, 0x0 }, -+ { 0x00000018, 0x000000DF, 0x0 }, -+}; -+ -+/* -+ * Skylake/Kabylake Y -+ * eDP 1.4 low vswing translation parameters -+ */ -+static const struct ddi_buf_trans skl_y_ddi_translations_edp[] = { -+ { 0x00000018, 0x000000A8, 0x0 }, -+ { 0x00004013, 0x000000AB, 0x0 }, -+ { 0x00007011, 0x000000A4, 0x0 }, -+ { 0x00009010, 0x000000DF, 0x0 }, -+ { 0x00000018, 0x000000AA, 0x0 }, -+ { 0x00006013, 0x000000A4, 0x0 }, -+ { 0x00007011, 0x0000009D, 0x0 }, -+ { 0x00000018, 0x000000A0, 0x0 }, -+ { 0x00006012, 0x000000DF, 0x0 }, -+ { 0x00000018, 0x0000008A, 0x0 }, -+}; -+ -+/* Skylake/Kabylake U, H and S */ -+static const struct ddi_buf_trans skl_ddi_translations_hdmi[] = { -+ { 0x00000018, 0x000000AC, 0x0 }, -+ { 0x00005012, 0x0000009D, 0x0 }, -+ { 0x00007011, 0x00000088, 0x0 }, -+ { 0x00000018, 0x000000A1, 0x0 }, -+ { 0x00000018, 0x00000098, 0x0 }, -+ { 0x00004013, 0x00000088, 0x0 }, -+ { 0x80006012, 0x000000CD, 0x1 }, -+ { 0x00000018, 0x000000DF, 0x0 }, -+ { 0x80003015, 0x000000CD, 0x1 }, /* Default */ -+ { 0x80003015, 0x000000C0, 0x1 }, -+ { 0x80000018, 0x000000C0, 0x1 }, -+}; -+ -+/* Skylake/Kabylake Y */ -+static const struct ddi_buf_trans skl_y_ddi_translations_hdmi[] = { -+ { 0x00000018, 0x000000A1, 0x0 }, -+ { 0x00005012, 0x000000DF, 0x0 }, -+ { 0x80007011, 0x000000CB, 0x3 }, -+ { 0x00000018, 0x000000A4, 0x0 }, -+ { 0x00000018, 0x0000009D, 0x0 }, -+ { 0x00004013, 0x00000080, 0x0 }, -+ { 0x80006013, 0x000000C0, 0x3 }, -+ { 0x00000018, 0x0000008A, 0x0 }, -+ { 0x80003015, 0x000000C0, 0x3 }, /* Default */ -+ { 0x80003015, 0x000000C0, 0x3 }, -+ { 0x80000018, 0x000000C0, 0x3 }, -+}; -+ -+struct bxt_ddi_buf_trans { -+ u8 margin; /* swing value */ -+ u8 scale; /* scale value */ -+ u8 enable; /* scale enable */ -+ u8 deemphasis; -+}; -+ -+static const struct bxt_ddi_buf_trans bxt_ddi_translations_dp[] = { -+ /* Idx NT mV diff db */ -+ { 52, 0x9A, 0, 128, }, /* 0: 400 0 */ -+ { 78, 0x9A, 0, 85, }, /* 1: 400 3.5 */ -+ { 104, 0x9A, 0, 64, }, /* 2: 400 6 */ -+ { 154, 0x9A, 0, 43, }, /* 3: 400 9.5 */ -+ { 77, 0x9A, 0, 128, }, /* 4: 600 0 */ -+ { 116, 0x9A, 0, 85, }, /* 5: 600 3.5 */ -+ { 154, 0x9A, 0, 64, }, /* 6: 600 6 */ -+ { 102, 0x9A, 0, 128, }, /* 7: 800 0 */ -+ { 154, 0x9A, 0, 85, }, /* 8: 800 3.5 */ -+ { 154, 0x9A, 1, 128, }, /* 9: 1200 0 */ -+}; -+ -+static const struct bxt_ddi_buf_trans bxt_ddi_translations_edp[] = { -+ /* Idx NT mV diff db */ -+ { 26, 0, 0, 128, }, /* 0: 200 0 */ -+ { 38, 0, 0, 112, }, /* 1: 200 1.5 */ -+ { 48, 0, 0, 96, }, /* 2: 200 4 */ -+ { 54, 0, 0, 69, }, /* 3: 200 6 */ -+ { 32, 0, 0, 128, }, /* 4: 250 0 */ -+ { 48, 0, 0, 104, }, /* 5: 250 1.5 */ -+ { 54, 0, 0, 85, }, /* 6: 250 4 */ -+ { 43, 0, 0, 128, }, /* 7: 300 0 */ -+ { 54, 0, 0, 101, }, /* 8: 300 1.5 */ -+ { 48, 0, 0, 128, }, /* 9: 300 0 */ -+}; -+ -+/* BSpec has 2 recommended values - entries 0 and 8. -+ * Using the entry with higher vswing. -+ */ -+static const struct bxt_ddi_buf_trans bxt_ddi_translations_hdmi[] = { -+ /* Idx NT mV diff db */ -+ { 52, 0x9A, 0, 128, }, /* 0: 400 0 */ -+ { 52, 0x9A, 0, 85, }, /* 1: 400 3.5 */ -+ { 52, 0x9A, 0, 64, }, /* 2: 400 6 */ -+ { 42, 0x9A, 0, 43, }, /* 3: 400 9.5 */ -+ { 77, 0x9A, 0, 128, }, /* 4: 600 0 */ -+ { 77, 0x9A, 0, 85, }, /* 5: 600 3.5 */ -+ { 77, 0x9A, 0, 64, }, /* 6: 600 6 */ -+ { 102, 0x9A, 0, 128, }, /* 7: 800 0 */ -+ { 102, 0x9A, 0, 85, }, /* 8: 800 3.5 */ -+ { 154, 0x9A, 1, 128, }, /* 9: 1200 0 */ -+}; -+ -+struct cnl_ddi_buf_trans { -+ u8 dw2_swing_sel; -+ u8 dw7_n_scalar; -+ u8 dw4_cursor_coeff; -+ u8 dw4_post_cursor_2; -+ u8 dw4_post_cursor_1; -+}; -+ -+/* Voltage Swing Programming for VccIO 0.85V for DP */ -+static const struct cnl_ddi_buf_trans cnl_ddi_translations_dp_0_85V[] = { -+ /* NT mV Trans mV db */ -+ { 0xA, 0x5D, 0x3F, 0x00, 0x00 }, /* 350 350 0.0 */ -+ { 0xA, 0x6A, 0x38, 0x00, 0x07 }, /* 350 500 3.1 */ -+ { 0xB, 0x7A, 0x32, 0x00, 0x0D }, /* 350 700 6.0 */ -+ { 0x6, 0x7C, 0x2D, 0x00, 0x12 }, /* 350 900 8.2 */ -+ { 0xA, 0x69, 0x3F, 0x00, 0x00 }, /* 500 500 0.0 */ -+ { 0xB, 0x7A, 0x36, 0x00, 0x09 }, /* 500 700 2.9 */ -+ { 0x6, 0x7C, 0x30, 0x00, 0x0F }, /* 500 900 5.1 */ -+ { 0xB, 0x7D, 0x3C, 0x00, 0x03 }, /* 650 725 0.9 */ -+ { 0x6, 0x7C, 0x34, 0x00, 0x0B }, /* 600 900 3.5 */ -+ { 0x6, 0x7B, 0x3F, 0x00, 0x00 }, /* 900 900 0.0 */ -+}; -+ -+/* Voltage Swing Programming for VccIO 0.85V for HDMI */ -+static const struct cnl_ddi_buf_trans cnl_ddi_translations_hdmi_0_85V[] = { -+ /* NT mV Trans mV db */ -+ { 0xA, 0x60, 0x3F, 0x00, 0x00 }, /* 450 450 0.0 */ -+ { 0xB, 0x73, 0x36, 0x00, 0x09 }, /* 450 650 3.2 */ -+ { 0x6, 0x7F, 0x31, 0x00, 0x0E }, /* 450 850 5.5 */ -+ { 0xB, 0x73, 0x3F, 0x00, 0x00 }, /* 650 650 0.0 */ -+ { 0x6, 0x7F, 0x37, 0x00, 0x08 }, /* 650 850 2.3 */ -+ { 0x6, 0x7F, 0x3F, 0x00, 0x00 }, /* 850 850 0.0 */ -+ { 0x6, 0x7F, 0x35, 0x00, 0x0A }, /* 600 850 3.0 */ -+}; -+ -+/* Voltage Swing Programming for VccIO 0.85V for eDP */ -+static const struct cnl_ddi_buf_trans cnl_ddi_translations_edp_0_85V[] = { -+ /* NT mV Trans mV db */ -+ { 0xA, 0x66, 0x3A, 0x00, 0x05 }, /* 384 500 2.3 */ -+ { 0x0, 0x7F, 0x38, 0x00, 0x07 }, /* 153 200 2.3 */ -+ { 0x8, 0x7F, 0x38, 0x00, 0x07 }, /* 192 250 2.3 */ -+ { 0x1, 0x7F, 0x38, 0x00, 0x07 }, /* 230 300 2.3 */ -+ { 0x9, 0x7F, 0x38, 0x00, 0x07 }, /* 269 350 2.3 */ -+ { 0xA, 0x66, 0x3C, 0x00, 0x03 }, /* 446 500 1.0 */ -+ { 0xB, 0x70, 0x3C, 0x00, 0x03 }, /* 460 600 2.3 */ -+ { 0xC, 0x75, 0x3C, 0x00, 0x03 }, /* 537 700 2.3 */ -+ { 0x2, 0x7F, 0x3F, 0x00, 0x00 }, /* 400 400 0.0 */ -+}; -+ -+/* Voltage Swing Programming for VccIO 0.95V for DP */ -+static const struct cnl_ddi_buf_trans cnl_ddi_translations_dp_0_95V[] = { -+ /* NT mV Trans mV db */ -+ { 0xA, 0x5D, 0x3F, 0x00, 0x00 }, /* 350 350 0.0 */ -+ { 0xA, 0x6A, 0x38, 0x00, 0x07 }, /* 350 500 3.1 */ -+ { 0xB, 0x7A, 0x32, 0x00, 0x0D }, /* 350 700 6.0 */ -+ { 0x6, 0x7C, 0x2D, 0x00, 0x12 }, /* 350 900 8.2 */ -+ { 0xA, 0x69, 0x3F, 0x00, 0x00 }, /* 500 500 0.0 */ -+ { 0xB, 0x7A, 0x36, 0x00, 0x09 }, /* 500 700 2.9 */ -+ { 0x6, 0x7C, 0x30, 0x00, 0x0F }, /* 500 900 5.1 */ -+ { 0xB, 0x7D, 0x3C, 0x00, 0x03 }, /* 650 725 0.9 */ -+ { 0x6, 0x7C, 0x34, 0x00, 0x0B }, /* 600 900 3.5 */ -+ { 0x6, 0x7B, 0x3F, 0x00, 0x00 }, /* 900 900 0.0 */ -+}; -+ -+/* Voltage Swing Programming for VccIO 0.95V for HDMI */ -+static const struct cnl_ddi_buf_trans cnl_ddi_translations_hdmi_0_95V[] = { -+ /* NT mV Trans mV db */ -+ { 0xA, 0x5C, 0x3F, 0x00, 0x00 }, /* 400 400 0.0 */ -+ { 0xB, 0x69, 0x37, 0x00, 0x08 }, /* 400 600 3.5 */ -+ { 0x5, 0x76, 0x31, 0x00, 0x0E }, /* 400 800 6.0 */ -+ { 0xA, 0x5E, 0x3F, 0x00, 0x00 }, /* 450 450 0.0 */ -+ { 0xB, 0x69, 0x3F, 0x00, 0x00 }, /* 600 600 0.0 */ -+ { 0xB, 0x79, 0x35, 0x00, 0x0A }, /* 600 850 3.0 */ -+ { 0x6, 0x7D, 0x32, 0x00, 0x0D }, /* 600 1000 4.4 */ -+ { 0x5, 0x76, 0x3F, 0x00, 0x00 }, /* 800 800 0.0 */ -+ { 0x6, 0x7D, 0x39, 0x00, 0x06 }, /* 800 1000 1.9 */ -+ { 0x6, 0x7F, 0x39, 0x00, 0x06 }, /* 850 1050 1.8 */ -+ { 0x6, 0x7F, 0x3F, 0x00, 0x00 }, /* 1050 1050 0.0 */ -+}; -+ -+/* Voltage Swing Programming for VccIO 0.95V for eDP */ -+static const struct cnl_ddi_buf_trans cnl_ddi_translations_edp_0_95V[] = { -+ /* NT mV Trans mV db */ -+ { 0xA, 0x61, 0x3A, 0x00, 0x05 }, /* 384 500 2.3 */ -+ { 0x0, 0x7F, 0x38, 0x00, 0x07 }, /* 153 200 2.3 */ -+ { 0x8, 0x7F, 0x38, 0x00, 0x07 }, /* 192 250 2.3 */ -+ { 0x1, 0x7F, 0x38, 0x00, 0x07 }, /* 230 300 2.3 */ -+ { 0x9, 0x7F, 0x38, 0x00, 0x07 }, /* 269 350 2.3 */ -+ { 0xA, 0x61, 0x3C, 0x00, 0x03 }, /* 446 500 1.0 */ -+ { 0xB, 0x68, 0x39, 0x00, 0x06 }, /* 460 600 2.3 */ -+ { 0xC, 0x6E, 0x39, 0x00, 0x06 }, /* 537 700 2.3 */ -+ { 0x4, 0x7F, 0x3A, 0x00, 0x05 }, /* 460 600 2.3 */ -+ { 0x2, 0x7F, 0x3F, 0x00, 0x00 }, /* 400 400 0.0 */ -+}; -+ -+/* Voltage Swing Programming for VccIO 1.05V for DP */ -+static const struct cnl_ddi_buf_trans cnl_ddi_translations_dp_1_05V[] = { -+ /* NT mV Trans mV db */ -+ { 0xA, 0x58, 0x3F, 0x00, 0x00 }, /* 400 400 0.0 */ -+ { 0xB, 0x64, 0x37, 0x00, 0x08 }, /* 400 600 3.5 */ -+ { 0x5, 0x70, 0x31, 0x00, 0x0E }, /* 400 800 6.0 */ -+ { 0x6, 0x7F, 0x2C, 0x00, 0x13 }, /* 400 1050 8.4 */ -+ { 0xB, 0x64, 0x3F, 0x00, 0x00 }, /* 600 600 0.0 */ -+ { 0x5, 0x73, 0x35, 0x00, 0x0A }, /* 600 850 3.0 */ -+ { 0x6, 0x7F, 0x30, 0x00, 0x0F }, /* 550 1050 5.6 */ -+ { 0x5, 0x76, 0x3E, 0x00, 0x01 }, /* 850 900 0.5 */ -+ { 0x6, 0x7F, 0x36, 0x00, 0x09 }, /* 750 1050 2.9 */ -+ { 0x6, 0x7F, 0x3F, 0x00, 0x00 }, /* 1050 1050 0.0 */ -+}; -+ -+/* Voltage Swing Programming for VccIO 1.05V for HDMI */ -+static const struct cnl_ddi_buf_trans cnl_ddi_translations_hdmi_1_05V[] = { -+ /* NT mV Trans mV db */ -+ { 0xA, 0x58, 0x3F, 0x00, 0x00 }, /* 400 400 0.0 */ -+ { 0xB, 0x64, 0x37, 0x00, 0x08 }, /* 400 600 3.5 */ -+ { 0x5, 0x70, 0x31, 0x00, 0x0E }, /* 400 800 6.0 */ -+ { 0xA, 0x5B, 0x3F, 0x00, 0x00 }, /* 450 450 0.0 */ -+ { 0xB, 0x64, 0x3F, 0x00, 0x00 }, /* 600 600 0.0 */ -+ { 0x5, 0x73, 0x35, 0x00, 0x0A }, /* 600 850 3.0 */ -+ { 0x6, 0x7C, 0x32, 0x00, 0x0D }, /* 600 1000 4.4 */ -+ { 0x5, 0x70, 0x3F, 0x00, 0x00 }, /* 800 800 0.0 */ -+ { 0x6, 0x7C, 0x39, 0x00, 0x06 }, /* 800 1000 1.9 */ -+ { 0x6, 0x7F, 0x39, 0x00, 0x06 }, /* 850 1050 1.8 */ -+ { 0x6, 0x7F, 0x3F, 0x00, 0x00 }, /* 1050 1050 0.0 */ -+}; -+ -+/* Voltage Swing Programming for VccIO 1.05V for eDP */ -+static const struct cnl_ddi_buf_trans cnl_ddi_translations_edp_1_05V[] = { -+ /* NT mV Trans mV db */ -+ { 0xA, 0x5E, 0x3A, 0x00, 0x05 }, /* 384 500 2.3 */ -+ { 0x0, 0x7F, 0x38, 0x00, 0x07 }, /* 153 200 2.3 */ -+ { 0x8, 0x7F, 0x38, 0x00, 0x07 }, /* 192 250 2.3 */ -+ { 0x1, 0x7F, 0x38, 0x00, 0x07 }, /* 230 300 2.3 */ -+ { 0x9, 0x7F, 0x38, 0x00, 0x07 }, /* 269 350 2.3 */ -+ { 0xA, 0x5E, 0x3C, 0x00, 0x03 }, /* 446 500 1.0 */ -+ { 0xB, 0x64, 0x39, 0x00, 0x06 }, /* 460 600 2.3 */ -+ { 0xE, 0x6A, 0x39, 0x00, 0x06 }, /* 537 700 2.3 */ -+ { 0x2, 0x7F, 0x3F, 0x00, 0x00 }, /* 400 400 0.0 */ -+}; -+ -+/* icl_combo_phy_ddi_translations */ -+static const struct cnl_ddi_buf_trans icl_combo_phy_ddi_translations_dp_hbr2[] = { -+ /* NT mV Trans mV db */ -+ { 0xA, 0x35, 0x3F, 0x00, 0x00 }, /* 350 350 0.0 */ -+ { 0xA, 0x4F, 0x37, 0x00, 0x08 }, /* 350 500 3.1 */ -+ { 0xC, 0x71, 0x2F, 0x00, 0x10 }, /* 350 700 6.0 */ -+ { 0x6, 0x7F, 0x2B, 0x00, 0x14 }, /* 350 900 8.2 */ -+ { 0xA, 0x4C, 0x3F, 0x00, 0x00 }, /* 500 500 0.0 */ -+ { 0xC, 0x73, 0x34, 0x00, 0x0B }, /* 500 700 2.9 */ -+ { 0x6, 0x7F, 0x2F, 0x00, 0x10 }, /* 500 900 5.1 */ -+ { 0xC, 0x6C, 0x3C, 0x00, 0x03 }, /* 650 700 0.6 */ -+ { 0x6, 0x7F, 0x35, 0x00, 0x0A }, /* 600 900 3.5 */ -+ { 0x6, 0x7F, 0x3F, 0x00, 0x00 }, /* 900 900 0.0 */ -+}; -+ -+static const struct cnl_ddi_buf_trans icl_combo_phy_ddi_translations_edp_hbr2[] = { -+ /* NT mV Trans mV db */ -+ { 0x0, 0x7F, 0x3F, 0x00, 0x00 }, /* 200 200 0.0 */ -+ { 0x8, 0x7F, 0x38, 0x00, 0x07 }, /* 200 250 1.9 */ -+ { 0x1, 0x7F, 0x33, 0x00, 0x0C }, /* 200 300 3.5 */ -+ { 0x9, 0x7F, 0x31, 0x00, 0x0E }, /* 200 350 4.9 */ -+ { 0x8, 0x7F, 0x3F, 0x00, 0x00 }, /* 250 250 0.0 */ -+ { 0x1, 0x7F, 0x38, 0x00, 0x07 }, /* 250 300 1.6 */ -+ { 0x9, 0x7F, 0x35, 0x00, 0x0A }, /* 250 350 2.9 */ -+ { 0x1, 0x7F, 0x3F, 0x00, 0x00 }, /* 300 300 0.0 */ -+ { 0x9, 0x7F, 0x38, 0x00, 0x07 }, /* 300 350 1.3 */ -+ { 0x9, 0x7F, 0x3F, 0x00, 0x00 }, /* 350 350 0.0 */ -+}; -+ -+static const struct cnl_ddi_buf_trans icl_combo_phy_ddi_translations_edp_hbr3[] = { -+ /* NT mV Trans mV db */ -+ { 0xA, 0x35, 0x3F, 0x00, 0x00 }, /* 350 350 0.0 */ -+ { 0xA, 0x4F, 0x37, 0x00, 0x08 }, /* 350 500 3.1 */ -+ { 0xC, 0x71, 0x2F, 0x00, 0x10 }, /* 350 700 6.0 */ -+ { 0x6, 0x7F, 0x2B, 0x00, 0x14 }, /* 350 900 8.2 */ -+ { 0xA, 0x4C, 0x3F, 0x00, 0x00 }, /* 500 500 0.0 */ -+ { 0xC, 0x73, 0x34, 0x00, 0x0B }, /* 500 700 2.9 */ -+ { 0x6, 0x7F, 0x2F, 0x00, 0x10 }, /* 500 900 5.1 */ -+ { 0xC, 0x6C, 0x3C, 0x00, 0x03 }, /* 650 700 0.6 */ -+ { 0x6, 0x7F, 0x35, 0x00, 0x0A }, /* 600 900 3.5 */ -+ { 0x6, 0x7F, 0x3F, 0x00, 0x00 }, /* 900 900 0.0 */ -+}; -+ -+static const struct cnl_ddi_buf_trans icl_combo_phy_ddi_translations_hdmi[] = { -+ /* NT mV Trans mV db */ -+ { 0xA, 0x60, 0x3F, 0x00, 0x00 }, /* 450 450 0.0 */ -+ { 0xB, 0x73, 0x36, 0x00, 0x09 }, /* 450 650 3.2 */ -+ { 0x6, 0x7F, 0x31, 0x00, 0x0E }, /* 450 850 5.5 */ -+ { 0xB, 0x73, 0x3F, 0x00, 0x00 }, /* 650 650 0.0 ALS */ -+ { 0x6, 0x7F, 0x37, 0x00, 0x08 }, /* 650 850 2.3 */ -+ { 0x6, 0x7F, 0x3F, 0x00, 0x00 }, /* 850 850 0.0 */ -+ { 0x6, 0x7F, 0x35, 0x00, 0x0A }, /* 600 850 3.0 */ -+}; -+ -+struct icl_mg_phy_ddi_buf_trans { -+ u32 cri_txdeemph_override_5_0; -+ u32 cri_txdeemph_override_11_6; -+ u32 cri_txdeemph_override_17_12; -+}; -+ -+static const struct icl_mg_phy_ddi_buf_trans icl_mg_phy_ddi_translations[] = { -+ /* Voltage swing pre-emphasis */ -+ { 0x0, 0x1B, 0x00 }, /* 0 0 */ -+ { 0x0, 0x23, 0x08 }, /* 0 1 */ -+ { 0x0, 0x2D, 0x12 }, /* 0 2 */ -+ { 0x0, 0x00, 0x00 }, /* 0 3 */ -+ { 0x0, 0x23, 0x00 }, /* 1 0 */ -+ { 0x0, 0x2B, 0x09 }, /* 1 1 */ -+ { 0x0, 0x2E, 0x11 }, /* 1 2 */ -+ { 0x0, 0x2F, 0x00 }, /* 2 0 */ -+ { 0x0, 0x33, 0x0C }, /* 2 1 */ -+ { 0x0, 0x00, 0x00 }, /* 3 0 */ -+}; -+ -+static const struct ddi_buf_trans * -+bdw_get_buf_trans_edp(struct drm_i915_private *dev_priv, int *n_entries) -+{ -+ if (dev_priv->vbt.edp.low_vswing) { -+ *n_entries = ARRAY_SIZE(bdw_ddi_translations_edp); -+ return bdw_ddi_translations_edp; -+ } else { -+ *n_entries = ARRAY_SIZE(bdw_ddi_translations_dp); -+ return bdw_ddi_translations_dp; -+ } -+} -+ -+static const struct ddi_buf_trans * -+skl_get_buf_trans_dp(struct drm_i915_private *dev_priv, int *n_entries) -+{ -+ if (IS_SKL_ULX(dev_priv)) { -+ *n_entries = ARRAY_SIZE(skl_y_ddi_translations_dp); -+ return skl_y_ddi_translations_dp; -+ } else if (IS_SKL_ULT(dev_priv)) { -+ *n_entries = ARRAY_SIZE(skl_u_ddi_translations_dp); -+ return skl_u_ddi_translations_dp; -+ } else { -+ *n_entries = ARRAY_SIZE(skl_ddi_translations_dp); -+ return skl_ddi_translations_dp; -+ } -+} -+ -+static const struct ddi_buf_trans * -+kbl_get_buf_trans_dp(struct drm_i915_private *dev_priv, int *n_entries) -+{ -+ if (IS_KBL_ULX(dev_priv) || IS_AML_ULX(dev_priv)) { -+ *n_entries = ARRAY_SIZE(kbl_y_ddi_translations_dp); -+ return kbl_y_ddi_translations_dp; -+ } else if (IS_KBL_ULT(dev_priv) || IS_CFL_ULT(dev_priv)) { -+ *n_entries = ARRAY_SIZE(kbl_u_ddi_translations_dp); -+ return kbl_u_ddi_translations_dp; -+ } else { -+ *n_entries = ARRAY_SIZE(kbl_ddi_translations_dp); -+ return kbl_ddi_translations_dp; -+ } -+} -+ -+static const struct ddi_buf_trans * -+skl_get_buf_trans_edp(struct drm_i915_private *dev_priv, int *n_entries) -+{ -+ if (dev_priv->vbt.edp.low_vswing) { -+ if (IS_SKL_ULX(dev_priv) || IS_KBL_ULX(dev_priv) || IS_AML_ULX(dev_priv)) { -+ *n_entries = ARRAY_SIZE(skl_y_ddi_translations_edp); -+ return skl_y_ddi_translations_edp; -+ } else if (IS_SKL_ULT(dev_priv) || IS_KBL_ULT(dev_priv) || -+ IS_CFL_ULT(dev_priv)) { -+ *n_entries = ARRAY_SIZE(skl_u_ddi_translations_edp); -+ return skl_u_ddi_translations_edp; -+ } else { -+ *n_entries = ARRAY_SIZE(skl_ddi_translations_edp); -+ return skl_ddi_translations_edp; -+ } -+ } -+ -+ if (IS_KABYLAKE(dev_priv) || IS_COFFEELAKE(dev_priv)) -+ return kbl_get_buf_trans_dp(dev_priv, n_entries); -+ else -+ return skl_get_buf_trans_dp(dev_priv, n_entries); -+} -+ -+static const struct ddi_buf_trans * -+skl_get_buf_trans_hdmi(struct drm_i915_private *dev_priv, int *n_entries) -+{ -+ if (IS_SKL_ULX(dev_priv) || IS_KBL_ULX(dev_priv) || IS_AML_ULX(dev_priv)) { -+ *n_entries = ARRAY_SIZE(skl_y_ddi_translations_hdmi); -+ return skl_y_ddi_translations_hdmi; -+ } else { -+ *n_entries = ARRAY_SIZE(skl_ddi_translations_hdmi); -+ return skl_ddi_translations_hdmi; -+ } -+} -+ -+static int skl_buf_trans_num_entries(enum port port, int n_entries) -+{ -+ /* Only DDIA and DDIE can select the 10th register with DP */ -+ if (port == PORT_A || port == PORT_E) -+ return min(n_entries, 10); -+ else -+ return min(n_entries, 9); -+} -+ -+static const struct ddi_buf_trans * -+intel_ddi_get_buf_trans_dp(struct drm_i915_private *dev_priv, -+ enum port port, int *n_entries) -+{ -+ if (IS_KABYLAKE(dev_priv) || IS_COFFEELAKE(dev_priv)) { -+ const struct ddi_buf_trans *ddi_translations = -+ kbl_get_buf_trans_dp(dev_priv, n_entries); -+ *n_entries = skl_buf_trans_num_entries(port, *n_entries); -+ return ddi_translations; -+ } else if (IS_SKYLAKE(dev_priv)) { -+ const struct ddi_buf_trans *ddi_translations = -+ skl_get_buf_trans_dp(dev_priv, n_entries); -+ *n_entries = skl_buf_trans_num_entries(port, *n_entries); -+ return ddi_translations; -+ } else if (IS_BROADWELL(dev_priv)) { -+ *n_entries = ARRAY_SIZE(bdw_ddi_translations_dp); -+ return bdw_ddi_translations_dp; -+ } else if (IS_HASWELL(dev_priv)) { -+ *n_entries = ARRAY_SIZE(hsw_ddi_translations_dp); -+ return hsw_ddi_translations_dp; -+ } -+ -+ *n_entries = 0; -+ return NULL; -+} -+ -+static const struct ddi_buf_trans * -+intel_ddi_get_buf_trans_edp(struct drm_i915_private *dev_priv, -+ enum port port, int *n_entries) -+{ -+ if (IS_GEN9_BC(dev_priv)) { -+ const struct ddi_buf_trans *ddi_translations = -+ skl_get_buf_trans_edp(dev_priv, n_entries); -+ *n_entries = skl_buf_trans_num_entries(port, *n_entries); -+ return ddi_translations; -+ } else if (IS_BROADWELL(dev_priv)) { -+ return bdw_get_buf_trans_edp(dev_priv, n_entries); -+ } else if (IS_HASWELL(dev_priv)) { -+ *n_entries = ARRAY_SIZE(hsw_ddi_translations_dp); -+ return hsw_ddi_translations_dp; -+ } -+ -+ *n_entries = 0; -+ return NULL; -+} -+ -+static const struct ddi_buf_trans * -+intel_ddi_get_buf_trans_fdi(struct drm_i915_private *dev_priv, -+ int *n_entries) -+{ -+ if (IS_BROADWELL(dev_priv)) { -+ *n_entries = ARRAY_SIZE(bdw_ddi_translations_fdi); -+ return bdw_ddi_translations_fdi; -+ } else if (IS_HASWELL(dev_priv)) { -+ *n_entries = ARRAY_SIZE(hsw_ddi_translations_fdi); -+ return hsw_ddi_translations_fdi; -+ } -+ -+ *n_entries = 0; -+ return NULL; -+} -+ -+static const struct ddi_buf_trans * -+intel_ddi_get_buf_trans_hdmi(struct drm_i915_private *dev_priv, -+ int *n_entries) -+{ -+ if (IS_GEN9_BC(dev_priv)) { -+ return skl_get_buf_trans_hdmi(dev_priv, n_entries); -+ } else if (IS_BROADWELL(dev_priv)) { -+ *n_entries = ARRAY_SIZE(bdw_ddi_translations_hdmi); -+ return bdw_ddi_translations_hdmi; -+ } else if (IS_HASWELL(dev_priv)) { -+ *n_entries = ARRAY_SIZE(hsw_ddi_translations_hdmi); -+ return hsw_ddi_translations_hdmi; -+ } -+ -+ *n_entries = 0; -+ return NULL; -+} -+ -+static const struct bxt_ddi_buf_trans * -+bxt_get_buf_trans_dp(struct drm_i915_private *dev_priv, int *n_entries) -+{ -+ *n_entries = ARRAY_SIZE(bxt_ddi_translations_dp); -+ return bxt_ddi_translations_dp; -+} -+ -+static const struct bxt_ddi_buf_trans * -+bxt_get_buf_trans_edp(struct drm_i915_private *dev_priv, int *n_entries) -+{ -+ if (dev_priv->vbt.edp.low_vswing) { -+ *n_entries = ARRAY_SIZE(bxt_ddi_translations_edp); -+ return bxt_ddi_translations_edp; -+ } -+ -+ return bxt_get_buf_trans_dp(dev_priv, n_entries); -+} -+ -+static const struct bxt_ddi_buf_trans * -+bxt_get_buf_trans_hdmi(struct drm_i915_private *dev_priv, int *n_entries) -+{ -+ *n_entries = ARRAY_SIZE(bxt_ddi_translations_hdmi); -+ return bxt_ddi_translations_hdmi; -+} -+ -+static const struct cnl_ddi_buf_trans * -+cnl_get_buf_trans_hdmi(struct drm_i915_private *dev_priv, int *n_entries) -+{ -+ u32 voltage = I915_READ(CNL_PORT_COMP_DW3) & VOLTAGE_INFO_MASK; -+ -+ if (voltage == VOLTAGE_INFO_0_85V) { -+ *n_entries = ARRAY_SIZE(cnl_ddi_translations_hdmi_0_85V); -+ return cnl_ddi_translations_hdmi_0_85V; -+ } else if (voltage == VOLTAGE_INFO_0_95V) { -+ *n_entries = ARRAY_SIZE(cnl_ddi_translations_hdmi_0_95V); -+ return cnl_ddi_translations_hdmi_0_95V; -+ } else if (voltage == VOLTAGE_INFO_1_05V) { -+ *n_entries = ARRAY_SIZE(cnl_ddi_translations_hdmi_1_05V); -+ return cnl_ddi_translations_hdmi_1_05V; -+ } else { -+ *n_entries = 1; /* shut up gcc */ -+ MISSING_CASE(voltage); -+ } -+ return NULL; -+} -+ -+static const struct cnl_ddi_buf_trans * -+cnl_get_buf_trans_dp(struct drm_i915_private *dev_priv, int *n_entries) -+{ -+ u32 voltage = I915_READ(CNL_PORT_COMP_DW3) & VOLTAGE_INFO_MASK; -+ -+ if (voltage == VOLTAGE_INFO_0_85V) { -+ *n_entries = ARRAY_SIZE(cnl_ddi_translations_dp_0_85V); -+ return cnl_ddi_translations_dp_0_85V; -+ } else if (voltage == VOLTAGE_INFO_0_95V) { -+ *n_entries = ARRAY_SIZE(cnl_ddi_translations_dp_0_95V); -+ return cnl_ddi_translations_dp_0_95V; -+ } else if (voltage == VOLTAGE_INFO_1_05V) { -+ *n_entries = ARRAY_SIZE(cnl_ddi_translations_dp_1_05V); -+ return cnl_ddi_translations_dp_1_05V; -+ } else { -+ *n_entries = 1; /* shut up gcc */ -+ MISSING_CASE(voltage); -+ } -+ return NULL; -+} -+ -+static const struct cnl_ddi_buf_trans * -+cnl_get_buf_trans_edp(struct drm_i915_private *dev_priv, int *n_entries) -+{ -+ u32 voltage = I915_READ(CNL_PORT_COMP_DW3) & VOLTAGE_INFO_MASK; -+ -+ if (dev_priv->vbt.edp.low_vswing) { -+ if (voltage == VOLTAGE_INFO_0_85V) { -+ *n_entries = ARRAY_SIZE(cnl_ddi_translations_edp_0_85V); -+ return cnl_ddi_translations_edp_0_85V; -+ } else if (voltage == VOLTAGE_INFO_0_95V) { -+ *n_entries = ARRAY_SIZE(cnl_ddi_translations_edp_0_95V); -+ return cnl_ddi_translations_edp_0_95V; -+ } else if (voltage == VOLTAGE_INFO_1_05V) { -+ *n_entries = ARRAY_SIZE(cnl_ddi_translations_edp_1_05V); -+ return cnl_ddi_translations_edp_1_05V; -+ } else { -+ *n_entries = 1; /* shut up gcc */ -+ MISSING_CASE(voltage); -+ } -+ return NULL; -+ } else { -+ return cnl_get_buf_trans_dp(dev_priv, n_entries); -+ } -+} -+ -+static const struct cnl_ddi_buf_trans * -+icl_get_combo_buf_trans(struct drm_i915_private *dev_priv, enum port port, -+ int type, int rate, int *n_entries) -+{ -+ if (type == INTEL_OUTPUT_HDMI) { -+ *n_entries = ARRAY_SIZE(icl_combo_phy_ddi_translations_hdmi); -+ return icl_combo_phy_ddi_translations_hdmi; -+ } else if (rate > 540000 && type == INTEL_OUTPUT_EDP) { -+ *n_entries = ARRAY_SIZE(icl_combo_phy_ddi_translations_edp_hbr3); -+ return icl_combo_phy_ddi_translations_edp_hbr3; -+ } else if (type == INTEL_OUTPUT_EDP && dev_priv->vbt.edp.low_vswing) { -+ *n_entries = ARRAY_SIZE(icl_combo_phy_ddi_translations_edp_hbr2); -+ return icl_combo_phy_ddi_translations_edp_hbr2; -+ } -+ -+ *n_entries = ARRAY_SIZE(icl_combo_phy_ddi_translations_dp_hbr2); -+ return icl_combo_phy_ddi_translations_dp_hbr2; -+} -+ -+static int intel_ddi_hdmi_level(struct drm_i915_private *dev_priv, enum port port) -+{ -+ int n_entries, level, default_entry; -+ -+ level = dev_priv->vbt.ddi_port_info[port].hdmi_level_shift; -+ -+ if (INTEL_GEN(dev_priv) >= 11) { -+ if (intel_port_is_combophy(dev_priv, port)) -+ icl_get_combo_buf_trans(dev_priv, port, INTEL_OUTPUT_HDMI, -+ 0, &n_entries); -+ else -+ n_entries = ARRAY_SIZE(icl_mg_phy_ddi_translations); -+ default_entry = n_entries - 1; -+ } else if (IS_CANNONLAKE(dev_priv)) { -+ cnl_get_buf_trans_hdmi(dev_priv, &n_entries); -+ default_entry = n_entries - 1; -+ } else if (IS_GEN9_LP(dev_priv)) { -+ bxt_get_buf_trans_hdmi(dev_priv, &n_entries); -+ default_entry = n_entries - 1; -+ } else if (IS_GEN9_BC(dev_priv)) { -+ intel_ddi_get_buf_trans_hdmi(dev_priv, &n_entries); -+ default_entry = 8; -+ } else if (IS_BROADWELL(dev_priv)) { -+ intel_ddi_get_buf_trans_hdmi(dev_priv, &n_entries); -+ default_entry = 7; -+ } else if (IS_HASWELL(dev_priv)) { -+ intel_ddi_get_buf_trans_hdmi(dev_priv, &n_entries); -+ default_entry = 6; -+ } else { -+ WARN(1, "ddi translation table missing\n"); -+ return 0; -+ } -+ -+ /* Choose a good default if VBT is badly populated */ -+ if (level == HDMI_LEVEL_SHIFT_UNKNOWN || level >= n_entries) -+ level = default_entry; -+ -+ if (WARN_ON_ONCE(n_entries == 0)) -+ return 0; -+ if (WARN_ON_ONCE(level >= n_entries)) -+ level = n_entries - 1; -+ -+ return level; -+} -+ -+/* -+ * Starting with Haswell, DDI port buffers must be programmed with correct -+ * values in advance. This function programs the correct values for -+ * DP/eDP/FDI use cases. -+ */ -+static void intel_prepare_dp_ddi_buffers(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ u32 iboost_bit = 0; -+ int i, n_entries; -+ enum port port = encoder->port; -+ const struct ddi_buf_trans *ddi_translations; -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG)) -+ ddi_translations = intel_ddi_get_buf_trans_fdi(dev_priv, -+ &n_entries); -+ else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP)) -+ ddi_translations = intel_ddi_get_buf_trans_edp(dev_priv, port, -+ &n_entries); -+ else -+ ddi_translations = intel_ddi_get_buf_trans_dp(dev_priv, port, -+ &n_entries); -+ -+ /* If we're boosting the current, set bit 31 of trans1 */ -+ if (IS_GEN9_BC(dev_priv) && -+ dev_priv->vbt.ddi_port_info[port].dp_boost_level) -+ iboost_bit = DDI_BUF_BALANCE_LEG_ENABLE; -+ -+ for (i = 0; i < n_entries; i++) { -+ I915_WRITE(DDI_BUF_TRANS_LO(port, i), -+ ddi_translations[i].trans1 | iboost_bit); -+ I915_WRITE(DDI_BUF_TRANS_HI(port, i), -+ ddi_translations[i].trans2); -+ } -+} -+ -+/* -+ * Starting with Haswell, DDI port buffers must be programmed with correct -+ * values in advance. This function programs the correct values for -+ * HDMI/DVI use cases. -+ */ -+static void intel_prepare_hdmi_ddi_buffers(struct intel_encoder *encoder, -+ int level) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ u32 iboost_bit = 0; -+ int n_entries; -+ enum port port = encoder->port; -+ const struct ddi_buf_trans *ddi_translations; -+ -+ ddi_translations = intel_ddi_get_buf_trans_hdmi(dev_priv, &n_entries); -+ -+ if (WARN_ON_ONCE(!ddi_translations)) -+ return; -+ if (WARN_ON_ONCE(level >= n_entries)) -+ level = n_entries - 1; -+ -+ /* If we're boosting the current, set bit 31 of trans1 */ -+ if (IS_GEN9_BC(dev_priv) && -+ dev_priv->vbt.ddi_port_info[port].hdmi_boost_level) -+ iboost_bit = DDI_BUF_BALANCE_LEG_ENABLE; -+ -+ /* Entry 9 is for HDMI: */ -+ I915_WRITE(DDI_BUF_TRANS_LO(port, 9), -+ ddi_translations[level].trans1 | iboost_bit); -+ I915_WRITE(DDI_BUF_TRANS_HI(port, 9), -+ ddi_translations[level].trans2); -+} -+ -+static void intel_wait_ddi_buf_idle(struct drm_i915_private *dev_priv, -+ enum port port) -+{ -+ i915_reg_t reg = DDI_BUF_CTL(port); -+ int i; -+ -+ for (i = 0; i < 16; i++) { -+ udelay(1); -+ if (I915_READ(reg) & DDI_BUF_IS_IDLE) -+ return; -+ } -+ DRM_ERROR("Timeout waiting for DDI BUF %c idle bit\n", port_name(port)); -+} -+ -+static u32 hsw_pll_to_ddi_pll_sel(const struct intel_shared_dpll *pll) -+{ -+ switch (pll->info->id) { -+ case DPLL_ID_WRPLL1: -+ return PORT_CLK_SEL_WRPLL1; -+ case DPLL_ID_WRPLL2: -+ return PORT_CLK_SEL_WRPLL2; -+ case DPLL_ID_SPLL: -+ return PORT_CLK_SEL_SPLL; -+ case DPLL_ID_LCPLL_810: -+ return PORT_CLK_SEL_LCPLL_810; -+ case DPLL_ID_LCPLL_1350: -+ return PORT_CLK_SEL_LCPLL_1350; -+ case DPLL_ID_LCPLL_2700: -+ return PORT_CLK_SEL_LCPLL_2700; -+ default: -+ MISSING_CASE(pll->info->id); -+ return PORT_CLK_SEL_NONE; -+ } -+} -+ -+static u32 icl_pll_to_ddi_clk_sel(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ const struct intel_shared_dpll *pll = crtc_state->shared_dpll; -+ int clock = crtc_state->port_clock; -+ const enum intel_dpll_id id = pll->info->id; -+ -+ switch (id) { -+ default: -+ /* -+ * DPLL_ID_ICL_DPLL0 and DPLL_ID_ICL_DPLL1 should not be used -+ * here, so do warn if this get passed in -+ */ -+ MISSING_CASE(id); -+ return DDI_CLK_SEL_NONE; -+ case DPLL_ID_ICL_TBTPLL: -+ switch (clock) { -+ case 162000: -+ return DDI_CLK_SEL_TBT_162; -+ case 270000: -+ return DDI_CLK_SEL_TBT_270; -+ case 540000: -+ return DDI_CLK_SEL_TBT_540; -+ case 810000: -+ return DDI_CLK_SEL_TBT_810; -+ default: -+ MISSING_CASE(clock); -+ return DDI_CLK_SEL_NONE; -+ } -+ case DPLL_ID_ICL_MGPLL1: -+ case DPLL_ID_ICL_MGPLL2: -+ case DPLL_ID_ICL_MGPLL3: -+ case DPLL_ID_ICL_MGPLL4: -+ return DDI_CLK_SEL_MG; -+ } -+} -+ -+/* Starting with Haswell, different DDI ports can work in FDI mode for -+ * connection to the PCH-located connectors. For this, it is necessary to train -+ * both the DDI port and PCH receiver for the desired DDI buffer settings. -+ * -+ * The recommended port to work in FDI mode is DDI E, which we use here. Also, -+ * please note that when FDI mode is active on DDI E, it shares 2 lines with -+ * DDI A (which is used for eDP) -+ */ -+ -+void hsw_fdi_link_train(struct intel_crtc *crtc, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_encoder *encoder; -+ u32 temp, i, rx_ctl_val, ddi_pll_sel; -+ -+ for_each_encoder_on_crtc(dev, &crtc->base, encoder) { -+ WARN_ON(encoder->type != INTEL_OUTPUT_ANALOG); -+ intel_prepare_dp_ddi_buffers(encoder, crtc_state); -+ } -+ -+ /* Set the FDI_RX_MISC pwrdn lanes and the 2 workarounds listed at the -+ * mode set "sequence for CRT port" document: -+ * - TP1 to TP2 time with the default value -+ * - FDI delay to 90h -+ * -+ * WaFDIAutoLinkSetTimingOverrride:hsw -+ */ -+ I915_WRITE(FDI_RX_MISC(PIPE_A), FDI_RX_PWRDN_LANE1_VAL(2) | -+ FDI_RX_PWRDN_LANE0_VAL(2) | -+ FDI_RX_TP1_TO_TP2_48 | FDI_RX_FDI_DELAY_90); -+ -+ /* Enable the PCH Receiver FDI PLL */ -+ rx_ctl_val = dev_priv->fdi_rx_config | FDI_RX_ENHANCE_FRAME_ENABLE | -+ FDI_RX_PLL_ENABLE | -+ FDI_DP_PORT_WIDTH(crtc_state->fdi_lanes); -+ I915_WRITE(FDI_RX_CTL(PIPE_A), rx_ctl_val); -+ POSTING_READ(FDI_RX_CTL(PIPE_A)); -+ udelay(220); -+ -+ /* Switch from Rawclk to PCDclk */ -+ rx_ctl_val |= FDI_PCDCLK; -+ I915_WRITE(FDI_RX_CTL(PIPE_A), rx_ctl_val); -+ -+ /* Configure Port Clock Select */ -+ ddi_pll_sel = hsw_pll_to_ddi_pll_sel(crtc_state->shared_dpll); -+ I915_WRITE(PORT_CLK_SEL(PORT_E), ddi_pll_sel); -+ WARN_ON(ddi_pll_sel != PORT_CLK_SEL_SPLL); -+ -+ /* Start the training iterating through available voltages and emphasis, -+ * testing each value twice. */ -+ for (i = 0; i < ARRAY_SIZE(hsw_ddi_translations_fdi) * 2; i++) { -+ /* Configure DP_TP_CTL with auto-training */ -+ I915_WRITE(DP_TP_CTL(PORT_E), -+ DP_TP_CTL_FDI_AUTOTRAIN | -+ DP_TP_CTL_ENHANCED_FRAME_ENABLE | -+ DP_TP_CTL_LINK_TRAIN_PAT1 | -+ DP_TP_CTL_ENABLE); -+ -+ /* Configure and enable DDI_BUF_CTL for DDI E with next voltage. -+ * DDI E does not support port reversal, the functionality is -+ * achieved on the PCH side in FDI_RX_CTL, so no need to set the -+ * port reversal bit */ -+ I915_WRITE(DDI_BUF_CTL(PORT_E), -+ DDI_BUF_CTL_ENABLE | -+ ((crtc_state->fdi_lanes - 1) << 1) | -+ DDI_BUF_TRANS_SELECT(i / 2)); -+ POSTING_READ(DDI_BUF_CTL(PORT_E)); -+ -+ udelay(600); -+ -+ /* Program PCH FDI Receiver TU */ -+ I915_WRITE(FDI_RX_TUSIZE1(PIPE_A), TU_SIZE(64)); -+ -+ /* Enable PCH FDI Receiver with auto-training */ -+ rx_ctl_val |= FDI_RX_ENABLE | FDI_LINK_TRAIN_AUTO; -+ I915_WRITE(FDI_RX_CTL(PIPE_A), rx_ctl_val); -+ POSTING_READ(FDI_RX_CTL(PIPE_A)); -+ -+ /* Wait for FDI receiver lane calibration */ -+ udelay(30); -+ -+ /* Unset FDI_RX_MISC pwrdn lanes */ -+ temp = I915_READ(FDI_RX_MISC(PIPE_A)); -+ temp &= ~(FDI_RX_PWRDN_LANE1_MASK | FDI_RX_PWRDN_LANE0_MASK); -+ I915_WRITE(FDI_RX_MISC(PIPE_A), temp); -+ POSTING_READ(FDI_RX_MISC(PIPE_A)); -+ -+ /* Wait for FDI auto training time */ -+ udelay(5); -+ -+ temp = I915_READ(DP_TP_STATUS(PORT_E)); -+ if (temp & DP_TP_STATUS_AUTOTRAIN_DONE) { -+ DRM_DEBUG_KMS("FDI link training done on step %d\n", i); -+ break; -+ } -+ -+ /* -+ * Leave things enabled even if we failed to train FDI. -+ * Results in less fireworks from the state checker. -+ */ -+ if (i == ARRAY_SIZE(hsw_ddi_translations_fdi) * 2 - 1) { -+ DRM_ERROR("FDI link training failed!\n"); -+ break; -+ } -+ -+ rx_ctl_val &= ~FDI_RX_ENABLE; -+ I915_WRITE(FDI_RX_CTL(PIPE_A), rx_ctl_val); -+ POSTING_READ(FDI_RX_CTL(PIPE_A)); -+ -+ temp = I915_READ(DDI_BUF_CTL(PORT_E)); -+ temp &= ~DDI_BUF_CTL_ENABLE; -+ I915_WRITE(DDI_BUF_CTL(PORT_E), temp); -+ POSTING_READ(DDI_BUF_CTL(PORT_E)); -+ -+ /* Disable DP_TP_CTL and FDI_RX_CTL and retry */ -+ temp = I915_READ(DP_TP_CTL(PORT_E)); -+ temp &= ~(DP_TP_CTL_ENABLE | DP_TP_CTL_LINK_TRAIN_MASK); -+ temp |= DP_TP_CTL_LINK_TRAIN_PAT1; -+ I915_WRITE(DP_TP_CTL(PORT_E), temp); -+ POSTING_READ(DP_TP_CTL(PORT_E)); -+ -+ intel_wait_ddi_buf_idle(dev_priv, PORT_E); -+ -+ /* Reset FDI_RX_MISC pwrdn lanes */ -+ temp = I915_READ(FDI_RX_MISC(PIPE_A)); -+ temp &= ~(FDI_RX_PWRDN_LANE1_MASK | FDI_RX_PWRDN_LANE0_MASK); -+ temp |= FDI_RX_PWRDN_LANE1_VAL(2) | FDI_RX_PWRDN_LANE0_VAL(2); -+ I915_WRITE(FDI_RX_MISC(PIPE_A), temp); -+ POSTING_READ(FDI_RX_MISC(PIPE_A)); -+ } -+ -+ /* Enable normal pixel sending for FDI */ -+ I915_WRITE(DP_TP_CTL(PORT_E), -+ DP_TP_CTL_FDI_AUTOTRAIN | -+ DP_TP_CTL_LINK_TRAIN_NORMAL | -+ DP_TP_CTL_ENHANCED_FRAME_ENABLE | -+ DP_TP_CTL_ENABLE); -+} -+ -+static void intel_ddi_init_dp_buf_reg(struct intel_encoder *encoder) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ struct intel_digital_port *intel_dig_port = -+ enc_to_dig_port(&encoder->base); -+ -+ intel_dp->DP = intel_dig_port->saved_port_bits | -+ DDI_BUF_CTL_ENABLE | DDI_BUF_TRANS_SELECT(0); -+ intel_dp->DP |= DDI_PORT_WIDTH(intel_dp->lane_count); -+} -+ -+static struct intel_encoder * -+intel_ddi_get_crtc_encoder(struct intel_crtc *crtc) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct intel_encoder *encoder, *ret = NULL; -+ int num_encoders = 0; -+ -+ for_each_encoder_on_crtc(dev, &crtc->base, encoder) { -+ ret = encoder; -+ num_encoders++; -+ } -+ -+ if (num_encoders != 1) -+ WARN(1, "%d encoders on crtc for pipe %c\n", num_encoders, -+ pipe_name(crtc->pipe)); -+ -+ BUG_ON(ret == NULL); -+ return ret; -+} -+ -+#define LC_FREQ 2700 -+ -+static int hsw_ddi_calc_wrpll_link(struct drm_i915_private *dev_priv, -+ i915_reg_t reg) -+{ -+ int refclk = LC_FREQ; -+ int n, p, r; -+ u32 wrpll; -+ -+ wrpll = I915_READ(reg); -+ switch (wrpll & WRPLL_PLL_REF_MASK) { -+ case WRPLL_PLL_SSC: -+ case WRPLL_PLL_NON_SSC: -+ /* -+ * We could calculate spread here, but our checking -+ * code only cares about 5% accuracy, and spread is a max of -+ * 0.5% downspread. -+ */ -+ refclk = 135; -+ break; -+ case WRPLL_PLL_LCPLL: -+ refclk = LC_FREQ; -+ break; -+ default: -+ WARN(1, "bad wrpll refclk\n"); -+ return 0; -+ } -+ -+ r = wrpll & WRPLL_DIVIDER_REF_MASK; -+ p = (wrpll & WRPLL_DIVIDER_POST_MASK) >> WRPLL_DIVIDER_POST_SHIFT; -+ n = (wrpll & WRPLL_DIVIDER_FB_MASK) >> WRPLL_DIVIDER_FB_SHIFT; -+ -+ /* Convert to KHz, p & r have a fixed point portion */ -+ return (refclk * n * 100) / (p * r); -+} -+ -+static int skl_calc_wrpll_link(const struct intel_dpll_hw_state *pll_state) -+{ -+ u32 p0, p1, p2, dco_freq; -+ -+ p0 = pll_state->cfgcr2 & DPLL_CFGCR2_PDIV_MASK; -+ p2 = pll_state->cfgcr2 & DPLL_CFGCR2_KDIV_MASK; -+ -+ if (pll_state->cfgcr2 & DPLL_CFGCR2_QDIV_MODE(1)) -+ p1 = (pll_state->cfgcr2 & DPLL_CFGCR2_QDIV_RATIO_MASK) >> 8; -+ else -+ p1 = 1; -+ -+ -+ switch (p0) { -+ case DPLL_CFGCR2_PDIV_1: -+ p0 = 1; -+ break; -+ case DPLL_CFGCR2_PDIV_2: -+ p0 = 2; -+ break; -+ case DPLL_CFGCR2_PDIV_3: -+ p0 = 3; -+ break; -+ case DPLL_CFGCR2_PDIV_7: -+ p0 = 7; -+ break; -+ } -+ -+ switch (p2) { -+ case DPLL_CFGCR2_KDIV_5: -+ p2 = 5; -+ break; -+ case DPLL_CFGCR2_KDIV_2: -+ p2 = 2; -+ break; -+ case DPLL_CFGCR2_KDIV_3: -+ p2 = 3; -+ break; -+ case DPLL_CFGCR2_KDIV_1: -+ p2 = 1; -+ break; -+ } -+ -+ dco_freq = (pll_state->cfgcr1 & DPLL_CFGCR1_DCO_INTEGER_MASK) -+ * 24 * 1000; -+ -+ dco_freq += (((pll_state->cfgcr1 & DPLL_CFGCR1_DCO_FRACTION_MASK) >> 9) -+ * 24 * 1000) / 0x8000; -+ -+ if (WARN_ON(p0 == 0 || p1 == 0 || p2 == 0)) -+ return 0; -+ -+ return dco_freq / (p0 * p1 * p2 * 5); -+} -+ -+int cnl_calc_wrpll_link(struct drm_i915_private *dev_priv, -+ struct intel_dpll_hw_state *pll_state) -+{ -+ u32 p0, p1, p2, dco_freq, ref_clock; -+ -+ p0 = pll_state->cfgcr1 & DPLL_CFGCR1_PDIV_MASK; -+ p2 = pll_state->cfgcr1 & DPLL_CFGCR1_KDIV_MASK; -+ -+ if (pll_state->cfgcr1 & DPLL_CFGCR1_QDIV_MODE(1)) -+ p1 = (pll_state->cfgcr1 & DPLL_CFGCR1_QDIV_RATIO_MASK) >> -+ DPLL_CFGCR1_QDIV_RATIO_SHIFT; -+ else -+ p1 = 1; -+ -+ -+ switch (p0) { -+ case DPLL_CFGCR1_PDIV_2: -+ p0 = 2; -+ break; -+ case DPLL_CFGCR1_PDIV_3: -+ p0 = 3; -+ break; -+ case DPLL_CFGCR1_PDIV_5: -+ p0 = 5; -+ break; -+ case DPLL_CFGCR1_PDIV_7: -+ p0 = 7; -+ break; -+ } -+ -+ switch (p2) { -+ case DPLL_CFGCR1_KDIV_1: -+ p2 = 1; -+ break; -+ case DPLL_CFGCR1_KDIV_2: -+ p2 = 2; -+ break; -+ case DPLL_CFGCR1_KDIV_3: -+ p2 = 3; -+ break; -+ } -+ -+ ref_clock = cnl_hdmi_pll_ref_clock(dev_priv); -+ -+ dco_freq = (pll_state->cfgcr0 & DPLL_CFGCR0_DCO_INTEGER_MASK) -+ * ref_clock; -+ -+ dco_freq += (((pll_state->cfgcr0 & DPLL_CFGCR0_DCO_FRACTION_MASK) >> -+ DPLL_CFGCR0_DCO_FRACTION_SHIFT) * ref_clock) / 0x8000; -+ -+ if (WARN_ON(p0 == 0 || p1 == 0 || p2 == 0)) -+ return 0; -+ -+ return dco_freq / (p0 * p1 * p2 * 5); -+} -+ -+static int icl_calc_tbt_pll_link(struct drm_i915_private *dev_priv, -+ enum port port) -+{ -+ u32 val = I915_READ(DDI_CLK_SEL(port)) & DDI_CLK_SEL_MASK; -+ -+ switch (val) { -+ case DDI_CLK_SEL_NONE: -+ return 0; -+ case DDI_CLK_SEL_TBT_162: -+ return 162000; -+ case DDI_CLK_SEL_TBT_270: -+ return 270000; -+ case DDI_CLK_SEL_TBT_540: -+ return 540000; -+ case DDI_CLK_SEL_TBT_810: -+ return 810000; -+ default: -+ MISSING_CASE(val); -+ return 0; -+ } -+} -+ -+static int icl_calc_mg_pll_link(struct drm_i915_private *dev_priv, -+ const struct intel_dpll_hw_state *pll_state) -+{ -+ u32 m1, m2_int, m2_frac, div1, div2, ref_clock; -+ u64 tmp; -+ -+ ref_clock = dev_priv->cdclk.hw.ref; -+ -+ m1 = pll_state->mg_pll_div1 & MG_PLL_DIV1_FBPREDIV_MASK; -+ m2_int = pll_state->mg_pll_div0 & MG_PLL_DIV0_FBDIV_INT_MASK; -+ m2_frac = (pll_state->mg_pll_div0 & MG_PLL_DIV0_FRACNEN_H) ? -+ (pll_state->mg_pll_div0 & MG_PLL_DIV0_FBDIV_FRAC_MASK) >> -+ MG_PLL_DIV0_FBDIV_FRAC_SHIFT : 0; -+ -+ switch (pll_state->mg_clktop2_hsclkctl & -+ MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_MASK) { -+ case MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_2: -+ div1 = 2; -+ break; -+ case MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_3: -+ div1 = 3; -+ break; -+ case MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_5: -+ div1 = 5; -+ break; -+ case MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_7: -+ div1 = 7; -+ break; -+ default: -+ MISSING_CASE(pll_state->mg_clktop2_hsclkctl); -+ return 0; -+ } -+ -+ div2 = (pll_state->mg_clktop2_hsclkctl & -+ MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO_MASK) >> -+ MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO_SHIFT; -+ -+ /* div2 value of 0 is same as 1 means no div */ -+ if (div2 == 0) -+ div2 = 1; -+ -+ /* -+ * Adjust the original formula to delay the division by 2^22 in order to -+ * minimize possible rounding errors. -+ */ -+ tmp = (u64)m1 * m2_int * ref_clock + -+ (((u64)m1 * m2_frac * ref_clock) >> 22); -+ tmp = div_u64(tmp, 5 * div1 * div2); -+ -+ return tmp; -+} -+ -+static void ddi_dotclock_get(struct intel_crtc_state *pipe_config) -+{ -+ int dotclock; -+ -+ if (pipe_config->has_pch_encoder) -+ dotclock = intel_dotclock_calculate(pipe_config->port_clock, -+ &pipe_config->fdi_m_n); -+ else if (intel_crtc_has_dp_encoder(pipe_config)) -+ dotclock = intel_dotclock_calculate(pipe_config->port_clock, -+ &pipe_config->dp_m_n); -+ else if (pipe_config->has_hdmi_sink && pipe_config->pipe_bpp == 36) -+ dotclock = pipe_config->port_clock * 2 / 3; -+ else -+ dotclock = pipe_config->port_clock; -+ -+ if (pipe_config->output_format == INTEL_OUTPUT_FORMAT_YCBCR420) -+ dotclock *= 2; -+ -+ if (pipe_config->pixel_multiplier) -+ dotclock /= pipe_config->pixel_multiplier; -+ -+ pipe_config->base.adjusted_mode.crtc_clock = dotclock; -+} -+ -+static void icl_ddi_clock_get(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dpll_hw_state *pll_state = &pipe_config->dpll_hw_state; -+ enum port port = encoder->port; -+ int link_clock; -+ -+ if (intel_port_is_combophy(dev_priv, port)) { -+ link_clock = cnl_calc_wrpll_link(dev_priv, pll_state); -+ } else { -+ enum intel_dpll_id pll_id = intel_get_shared_dpll_id(dev_priv, -+ pipe_config->shared_dpll); -+ -+ if (pll_id == DPLL_ID_ICL_TBTPLL) -+ link_clock = icl_calc_tbt_pll_link(dev_priv, port); -+ else -+ link_clock = icl_calc_mg_pll_link(dev_priv, pll_state); -+ } -+ -+ pipe_config->port_clock = link_clock; -+ -+ ddi_dotclock_get(pipe_config); -+} -+ -+static void cnl_ddi_clock_get(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dpll_hw_state *pll_state = &pipe_config->dpll_hw_state; -+ int link_clock; -+ -+ if (pll_state->cfgcr0 & DPLL_CFGCR0_HDMI_MODE) { -+ link_clock = cnl_calc_wrpll_link(dev_priv, pll_state); -+ } else { -+ link_clock = pll_state->cfgcr0 & DPLL_CFGCR0_LINK_RATE_MASK; -+ -+ switch (link_clock) { -+ case DPLL_CFGCR0_LINK_RATE_810: -+ link_clock = 81000; -+ break; -+ case DPLL_CFGCR0_LINK_RATE_1080: -+ link_clock = 108000; -+ break; -+ case DPLL_CFGCR0_LINK_RATE_1350: -+ link_clock = 135000; -+ break; -+ case DPLL_CFGCR0_LINK_RATE_1620: -+ link_clock = 162000; -+ break; -+ case DPLL_CFGCR0_LINK_RATE_2160: -+ link_clock = 216000; -+ break; -+ case DPLL_CFGCR0_LINK_RATE_2700: -+ link_clock = 270000; -+ break; -+ case DPLL_CFGCR0_LINK_RATE_3240: -+ link_clock = 324000; -+ break; -+ case DPLL_CFGCR0_LINK_RATE_4050: -+ link_clock = 405000; -+ break; -+ default: -+ WARN(1, "Unsupported link rate\n"); -+ break; -+ } -+ link_clock *= 2; -+ } -+ -+ pipe_config->port_clock = link_clock; -+ -+ ddi_dotclock_get(pipe_config); -+} -+ -+static void skl_ddi_clock_get(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct intel_dpll_hw_state *pll_state = &pipe_config->dpll_hw_state; -+ int link_clock; -+ -+ /* -+ * ctrl1 register is already shifted for each pll, just use 0 to get -+ * the internal shift for each field -+ */ -+ if (pll_state->ctrl1 & DPLL_CTRL1_HDMI_MODE(0)) { -+ link_clock = skl_calc_wrpll_link(pll_state); -+ } else { -+ link_clock = pll_state->ctrl1 & DPLL_CTRL1_LINK_RATE_MASK(0); -+ link_clock >>= DPLL_CTRL1_LINK_RATE_SHIFT(0); -+ -+ switch (link_clock) { -+ case DPLL_CTRL1_LINK_RATE_810: -+ link_clock = 81000; -+ break; -+ case DPLL_CTRL1_LINK_RATE_1080: -+ link_clock = 108000; -+ break; -+ case DPLL_CTRL1_LINK_RATE_1350: -+ link_clock = 135000; -+ break; -+ case DPLL_CTRL1_LINK_RATE_1620: -+ link_clock = 162000; -+ break; -+ case DPLL_CTRL1_LINK_RATE_2160: -+ link_clock = 216000; -+ break; -+ case DPLL_CTRL1_LINK_RATE_2700: -+ link_clock = 270000; -+ break; -+ default: -+ WARN(1, "Unsupported link rate\n"); -+ break; -+ } -+ link_clock *= 2; -+ } -+ -+ pipe_config->port_clock = link_clock; -+ -+ ddi_dotclock_get(pipe_config); -+} -+ -+static void hsw_ddi_clock_get(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ int link_clock = 0; -+ u32 val, pll; -+ -+ val = hsw_pll_to_ddi_pll_sel(pipe_config->shared_dpll); -+ switch (val & PORT_CLK_SEL_MASK) { -+ case PORT_CLK_SEL_LCPLL_810: -+ link_clock = 81000; -+ break; -+ case PORT_CLK_SEL_LCPLL_1350: -+ link_clock = 135000; -+ break; -+ case PORT_CLK_SEL_LCPLL_2700: -+ link_clock = 270000; -+ break; -+ case PORT_CLK_SEL_WRPLL1: -+ link_clock = hsw_ddi_calc_wrpll_link(dev_priv, WRPLL_CTL(0)); -+ break; -+ case PORT_CLK_SEL_WRPLL2: -+ link_clock = hsw_ddi_calc_wrpll_link(dev_priv, WRPLL_CTL(1)); -+ break; -+ case PORT_CLK_SEL_SPLL: -+ pll = I915_READ(SPLL_CTL) & SPLL_PLL_FREQ_MASK; -+ if (pll == SPLL_PLL_FREQ_810MHz) -+ link_clock = 81000; -+ else if (pll == SPLL_PLL_FREQ_1350MHz) -+ link_clock = 135000; -+ else if (pll == SPLL_PLL_FREQ_2700MHz) -+ link_clock = 270000; -+ else { -+ WARN(1, "bad spll freq\n"); -+ return; -+ } -+ break; -+ default: -+ WARN(1, "bad port clock sel\n"); -+ return; -+ } -+ -+ pipe_config->port_clock = link_clock * 2; -+ -+ ddi_dotclock_get(pipe_config); -+} -+ -+static int bxt_calc_pll_link(const struct intel_dpll_hw_state *pll_state) -+{ -+ struct dpll clock; -+ -+ clock.m1 = 2; -+ clock.m2 = (pll_state->pll0 & PORT_PLL_M2_MASK) << 22; -+ if (pll_state->pll3 & PORT_PLL_M2_FRAC_ENABLE) -+ clock.m2 |= pll_state->pll2 & PORT_PLL_M2_FRAC_MASK; -+ clock.n = (pll_state->pll1 & PORT_PLL_N_MASK) >> PORT_PLL_N_SHIFT; -+ clock.p1 = (pll_state->ebb0 & PORT_PLL_P1_MASK) >> PORT_PLL_P1_SHIFT; -+ clock.p2 = (pll_state->ebb0 & PORT_PLL_P2_MASK) >> PORT_PLL_P2_SHIFT; -+ -+ return chv_calc_dpll_params(100000, &clock); -+} -+ -+static void bxt_ddi_clock_get(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config) -+{ -+ pipe_config->port_clock = -+ bxt_calc_pll_link(&pipe_config->dpll_hw_state); -+ -+ ddi_dotclock_get(pipe_config); -+} -+ -+static void intel_ddi_clock_get(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ icl_ddi_clock_get(encoder, pipe_config); -+ else if (IS_CANNONLAKE(dev_priv)) -+ cnl_ddi_clock_get(encoder, pipe_config); -+ else if (IS_GEN9_LP(dev_priv)) -+ bxt_ddi_clock_get(encoder, pipe_config); -+ else if (IS_GEN9_BC(dev_priv)) -+ skl_ddi_clock_get(encoder, pipe_config); -+ else if (INTEL_GEN(dev_priv) <= 8) -+ hsw_ddi_clock_get(encoder, pipe_config); -+} -+ -+void intel_ddi_set_pipe_settings(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; -+ u32 temp; -+ -+ if (!intel_crtc_has_dp_encoder(crtc_state)) -+ return; -+ -+ WARN_ON(transcoder_is_dsi(cpu_transcoder)); -+ -+ temp = TRANS_MSA_SYNC_CLK; -+ -+ if (crtc_state->limited_color_range) -+ temp |= TRANS_MSA_CEA_RANGE; -+ -+ switch (crtc_state->pipe_bpp) { -+ case 18: -+ temp |= TRANS_MSA_6_BPC; -+ break; -+ case 24: -+ temp |= TRANS_MSA_8_BPC; -+ break; -+ case 30: -+ temp |= TRANS_MSA_10_BPC; -+ break; -+ case 36: -+ temp |= TRANS_MSA_12_BPC; -+ break; -+ default: -+ MISSING_CASE(crtc_state->pipe_bpp); -+ break; -+ } -+ -+ /* -+ * As per DP 1.2 spec section 2.3.4.3 while sending -+ * YCBCR 444 signals we should program MSA MISC1/0 fields with -+ * colorspace information. The output colorspace encoding is BT601. -+ */ -+ if (crtc_state->output_format == INTEL_OUTPUT_FORMAT_YCBCR444) -+ temp |= TRANS_MSA_SAMPLING_444 | TRANS_MSA_CLRSP_YCBCR; -+ I915_WRITE(TRANS_MSA_MISC(cpu_transcoder), temp); -+} -+ -+void intel_ddi_set_vc_payload_alloc(const struct intel_crtc_state *crtc_state, -+ bool state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; -+ u32 temp; -+ -+ temp = I915_READ(TRANS_DDI_FUNC_CTL(cpu_transcoder)); -+ if (state == true) -+ temp |= TRANS_DDI_DP_VC_PAYLOAD_ALLOC; -+ else -+ temp &= ~TRANS_DDI_DP_VC_PAYLOAD_ALLOC; -+ I915_WRITE(TRANS_DDI_FUNC_CTL(cpu_transcoder), temp); -+} -+ -+void intel_ddi_enable_transcoder_func(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct intel_encoder *encoder = intel_ddi_get_crtc_encoder(crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; -+ enum port port = encoder->port; -+ u32 temp; -+ -+ /* Enable TRANS_DDI_FUNC_CTL for the pipe to work in HDMI mode */ -+ temp = TRANS_DDI_FUNC_ENABLE; -+ temp |= TRANS_DDI_SELECT_PORT(port); -+ -+ switch (crtc_state->pipe_bpp) { -+ case 18: -+ temp |= TRANS_DDI_BPC_6; -+ break; -+ case 24: -+ temp |= TRANS_DDI_BPC_8; -+ break; -+ case 30: -+ temp |= TRANS_DDI_BPC_10; -+ break; -+ case 36: -+ temp |= TRANS_DDI_BPC_12; -+ break; -+ default: -+ BUG(); -+ } -+ -+ if (crtc_state->base.adjusted_mode.flags & DRM_MODE_FLAG_PVSYNC) -+ temp |= TRANS_DDI_PVSYNC; -+ if (crtc_state->base.adjusted_mode.flags & DRM_MODE_FLAG_PHSYNC) -+ temp |= TRANS_DDI_PHSYNC; -+ -+ if (cpu_transcoder == TRANSCODER_EDP) { -+ switch (pipe) { -+ case PIPE_A: -+ /* On Haswell, can only use the always-on power well for -+ * eDP when not using the panel fitter, and when not -+ * using motion blur mitigation (which we don't -+ * support). */ -+ if (IS_HASWELL(dev_priv) && -+ (crtc_state->pch_pfit.enabled || -+ crtc_state->pch_pfit.force_thru)) -+ temp |= TRANS_DDI_EDP_INPUT_A_ONOFF; -+ else -+ temp |= TRANS_DDI_EDP_INPUT_A_ON; -+ break; -+ case PIPE_B: -+ temp |= TRANS_DDI_EDP_INPUT_B_ONOFF; -+ break; -+ case PIPE_C: -+ temp |= TRANS_DDI_EDP_INPUT_C_ONOFF; -+ break; -+ default: -+ BUG(); -+ break; -+ } -+ } -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) { -+ if (crtc_state->has_hdmi_sink) -+ temp |= TRANS_DDI_MODE_SELECT_HDMI; -+ else -+ temp |= TRANS_DDI_MODE_SELECT_DVI; -+ -+ if (crtc_state->hdmi_scrambling) -+ temp |= TRANS_DDI_HDMI_SCRAMBLING; -+ if (crtc_state->hdmi_high_tmds_clock_ratio) -+ temp |= TRANS_DDI_HIGH_TMDS_CHAR_RATE; -+ } else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG)) { -+ temp |= TRANS_DDI_MODE_SELECT_FDI; -+ temp |= (crtc_state->fdi_lanes - 1) << 1; -+ } else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST)) { -+ temp |= TRANS_DDI_MODE_SELECT_DP_MST; -+ temp |= DDI_PORT_WIDTH(crtc_state->lane_count); -+ } else { -+ temp |= TRANS_DDI_MODE_SELECT_DP_SST; -+ temp |= DDI_PORT_WIDTH(crtc_state->lane_count); -+ } -+ -+ I915_WRITE(TRANS_DDI_FUNC_CTL(cpu_transcoder), temp); -+} -+ -+void intel_ddi_disable_transcoder_func(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; -+ i915_reg_t reg = TRANS_DDI_FUNC_CTL(cpu_transcoder); -+ u32 val = I915_READ(reg); -+ -+ val &= ~(TRANS_DDI_FUNC_ENABLE | TRANS_DDI_PORT_MASK | TRANS_DDI_DP_VC_PAYLOAD_ALLOC); -+ val |= TRANS_DDI_PORT_NONE; -+ I915_WRITE(reg, val); -+ -+ if (dev_priv->quirks & QUIRK_INCREASE_DDI_DISABLED_TIME && -+ intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) { -+ DRM_DEBUG_KMS("Quirk Increase DDI disabled time\n"); -+ /* Quirk time at 100ms for reliable operation */ -+ msleep(100); -+ } -+} -+ -+int intel_ddi_toggle_hdcp_signalling(struct intel_encoder *intel_encoder, -+ bool enable) -+{ -+ struct drm_device *dev = intel_encoder->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ intel_wakeref_t wakeref; -+ enum pipe pipe = 0; -+ int ret = 0; -+ u32 tmp; -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ intel_encoder->power_domain); -+ if (WARN_ON(!wakeref)) -+ return -ENXIO; -+ -+ if (WARN_ON(!intel_encoder->get_hw_state(intel_encoder, &pipe))) { -+ ret = -EIO; -+ goto out; -+ } -+ -+ tmp = I915_READ(TRANS_DDI_FUNC_CTL(pipe)); -+ if (enable) -+ tmp |= TRANS_DDI_HDCP_SIGNALLING; -+ else -+ tmp &= ~TRANS_DDI_HDCP_SIGNALLING; -+ I915_WRITE(TRANS_DDI_FUNC_CTL(pipe), tmp); -+out: -+ intel_display_power_put(dev_priv, intel_encoder->power_domain, wakeref); -+ return ret; -+} -+ -+bool intel_ddi_connector_get_hw_state(struct intel_connector *intel_connector) -+{ -+ struct drm_device *dev = intel_connector->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_encoder *encoder = intel_connector->encoder; -+ int type = intel_connector->base.connector_type; -+ enum port port = encoder->port; -+ enum transcoder cpu_transcoder; -+ intel_wakeref_t wakeref; -+ enum pipe pipe = 0; -+ u32 tmp; -+ bool ret; -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ encoder->power_domain); -+ if (!wakeref) -+ return false; -+ -+ if (!encoder->get_hw_state(encoder, &pipe)) { -+ ret = false; -+ goto out; -+ } -+ -+ if (HAS_TRANSCODER_EDP(dev_priv) && port == PORT_A) -+ cpu_transcoder = TRANSCODER_EDP; -+ else -+ cpu_transcoder = (enum transcoder) pipe; -+ -+ tmp = I915_READ(TRANS_DDI_FUNC_CTL(cpu_transcoder)); -+ -+ switch (tmp & TRANS_DDI_MODE_SELECT_MASK) { -+ case TRANS_DDI_MODE_SELECT_HDMI: -+ case TRANS_DDI_MODE_SELECT_DVI: -+ ret = type == DRM_MODE_CONNECTOR_HDMIA; -+ break; -+ -+ case TRANS_DDI_MODE_SELECT_DP_SST: -+ ret = type == DRM_MODE_CONNECTOR_eDP || -+ type == DRM_MODE_CONNECTOR_DisplayPort; -+ break; -+ -+ case TRANS_DDI_MODE_SELECT_DP_MST: -+ /* if the transcoder is in MST state then -+ * connector isn't connected */ -+ ret = false; -+ break; -+ -+ case TRANS_DDI_MODE_SELECT_FDI: -+ ret = type == DRM_MODE_CONNECTOR_VGA; -+ break; -+ -+ default: -+ ret = false; -+ break; -+ } -+ -+out: -+ intel_display_power_put(dev_priv, encoder->power_domain, wakeref); -+ -+ return ret; -+} -+ -+static void intel_ddi_get_encoder_pipes(struct intel_encoder *encoder, -+ u8 *pipe_mask, bool *is_dp_mst) -+{ -+ struct drm_device *dev = encoder->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ enum port port = encoder->port; -+ intel_wakeref_t wakeref; -+ enum pipe p; -+ u32 tmp; -+ u8 mst_pipe_mask; -+ -+ *pipe_mask = 0; -+ *is_dp_mst = false; -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ encoder->power_domain); -+ if (!wakeref) -+ return; -+ -+ tmp = I915_READ(DDI_BUF_CTL(port)); -+ if (!(tmp & DDI_BUF_CTL_ENABLE)) -+ goto out; -+ -+ if (HAS_TRANSCODER_EDP(dev_priv) && port == PORT_A) { -+ tmp = I915_READ(TRANS_DDI_FUNC_CTL(TRANSCODER_EDP)); -+ -+ switch (tmp & TRANS_DDI_EDP_INPUT_MASK) { -+ default: -+ MISSING_CASE(tmp & TRANS_DDI_EDP_INPUT_MASK); -+ /* fallthrough */ -+ case TRANS_DDI_EDP_INPUT_A_ON: -+ case TRANS_DDI_EDP_INPUT_A_ONOFF: -+ *pipe_mask = BIT(PIPE_A); -+ break; -+ case TRANS_DDI_EDP_INPUT_B_ONOFF: -+ *pipe_mask = BIT(PIPE_B); -+ break; -+ case TRANS_DDI_EDP_INPUT_C_ONOFF: -+ *pipe_mask = BIT(PIPE_C); -+ break; -+ } -+ -+ goto out; -+ } -+ -+ mst_pipe_mask = 0; -+ for_each_pipe(dev_priv, p) { -+ enum transcoder cpu_transcoder = (enum transcoder)p; -+ -+ tmp = I915_READ(TRANS_DDI_FUNC_CTL(cpu_transcoder)); -+ -+ if ((tmp & TRANS_DDI_PORT_MASK) != TRANS_DDI_SELECT_PORT(port)) -+ continue; -+ -+ if ((tmp & TRANS_DDI_MODE_SELECT_MASK) == -+ TRANS_DDI_MODE_SELECT_DP_MST) -+ mst_pipe_mask |= BIT(p); -+ -+ *pipe_mask |= BIT(p); -+ } -+ -+ if (!*pipe_mask) -+ DRM_DEBUG_KMS("No pipe for ddi port %c found\n", -+ port_name(port)); -+ -+ if (!mst_pipe_mask && hweight8(*pipe_mask) > 1) { -+ DRM_DEBUG_KMS("Multiple pipes for non DP-MST port %c (pipe_mask %02x)\n", -+ port_name(port), *pipe_mask); -+ *pipe_mask = BIT(ffs(*pipe_mask) - 1); -+ } -+ -+ if (mst_pipe_mask && mst_pipe_mask != *pipe_mask) -+ DRM_DEBUG_KMS("Conflicting MST and non-MST encoders for port %c (pipe_mask %02x mst_pipe_mask %02x)\n", -+ port_name(port), *pipe_mask, mst_pipe_mask); -+ else -+ *is_dp_mst = mst_pipe_mask; -+ -+out: -+ if (*pipe_mask && IS_GEN9_LP(dev_priv)) { -+ tmp = I915_READ(BXT_PHY_CTL(port)); -+ if ((tmp & (BXT_PHY_CMNLANE_POWERDOWN_ACK | -+ BXT_PHY_LANE_POWERDOWN_ACK | -+ BXT_PHY_LANE_ENABLED)) != BXT_PHY_LANE_ENABLED) -+ DRM_ERROR("Port %c enabled but PHY powered down? " -+ "(PHY_CTL %08x)\n", port_name(port), tmp); -+ } -+ -+ intel_display_power_put(dev_priv, encoder->power_domain, wakeref); -+} -+ -+bool intel_ddi_get_hw_state(struct intel_encoder *encoder, -+ enum pipe *pipe) -+{ -+ u8 pipe_mask; -+ bool is_mst; -+ -+ intel_ddi_get_encoder_pipes(encoder, &pipe_mask, &is_mst); -+ -+ if (is_mst || !pipe_mask) -+ return false; -+ -+ *pipe = ffs(pipe_mask) - 1; -+ -+ return true; -+} -+ -+static inline enum intel_display_power_domain -+intel_ddi_main_link_aux_domain(struct intel_digital_port *dig_port) -+{ -+ /* CNL+ HW requires corresponding AUX IOs to be powered up for PSR with -+ * DC states enabled at the same time, while for driver initiated AUX -+ * transfers we need the same AUX IOs to be powered but with DC states -+ * disabled. Accordingly use the AUX power domain here which leaves DC -+ * states enabled. -+ * However, for non-A AUX ports the corresponding non-EDP transcoders -+ * would have already enabled power well 2 and DC_OFF. This means we can -+ * acquire a wider POWER_DOMAIN_AUX_{B,C,D,F} reference instead of a -+ * specific AUX_IO reference without powering up any extra wells. -+ * Note that PSR is enabled only on Port A even though this function -+ * returns the correct domain for other ports too. -+ */ -+ return dig_port->aux_ch == AUX_CH_A ? POWER_DOMAIN_AUX_IO_A : -+ intel_aux_power_domain(dig_port); -+} -+ -+static void intel_ddi_get_power_domains(struct intel_encoder *encoder, -+ struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_digital_port *dig_port; -+ -+ /* -+ * TODO: Add support for MST encoders. Atm, the following should never -+ * happen since fake-MST encoders don't set their get_power_domains() -+ * hook. -+ */ -+ if (WARN_ON(intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST))) -+ return; -+ -+ dig_port = enc_to_dig_port(&encoder->base); -+ intel_display_power_get(dev_priv, dig_port->ddi_io_power_domain); -+ -+ /* -+ * AUX power is only needed for (e)DP mode, and for HDMI mode on TC -+ * ports. -+ */ -+ if (intel_crtc_has_dp_encoder(crtc_state) || -+ intel_port_is_tc(dev_priv, encoder->port)) -+ intel_display_power_get(dev_priv, -+ intel_ddi_main_link_aux_domain(dig_port)); -+ -+ /* -+ * VDSC power is needed when DSC is enabled -+ */ -+ if (crtc_state->dsc_params.compression_enable) -+ intel_display_power_get(dev_priv, -+ intel_dsc_power_domain(crtc_state)); -+} -+ -+void intel_ddi_enable_pipe_clock(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct intel_encoder *encoder = intel_ddi_get_crtc_encoder(crtc); -+ enum port port = encoder->port; -+ enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; -+ -+ if (cpu_transcoder != TRANSCODER_EDP) -+ I915_WRITE(TRANS_CLK_SEL(cpu_transcoder), -+ TRANS_CLK_SEL_PORT(port)); -+} -+ -+void intel_ddi_disable_pipe_clock(const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; -+ -+ if (cpu_transcoder != TRANSCODER_EDP) -+ I915_WRITE(TRANS_CLK_SEL(cpu_transcoder), -+ TRANS_CLK_SEL_DISABLED); -+} -+ -+static void _skl_ddi_set_iboost(struct drm_i915_private *dev_priv, -+ enum port port, u8 iboost) -+{ -+ u32 tmp; -+ -+ tmp = I915_READ(DISPIO_CR_TX_BMU_CR0); -+ tmp &= ~(BALANCE_LEG_MASK(port) | BALANCE_LEG_DISABLE(port)); -+ if (iboost) -+ tmp |= iboost << BALANCE_LEG_SHIFT(port); -+ else -+ tmp |= BALANCE_LEG_DISABLE(port); -+ I915_WRITE(DISPIO_CR_TX_BMU_CR0, tmp); -+} -+ -+static void skl_ddi_set_iboost(struct intel_encoder *encoder, -+ int level, enum intel_output_type type) -+{ -+ struct intel_digital_port *intel_dig_port = enc_to_dig_port(&encoder->base); -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ u8 iboost; -+ -+ if (type == INTEL_OUTPUT_HDMI) -+ iboost = dev_priv->vbt.ddi_port_info[port].hdmi_boost_level; -+ else -+ iboost = dev_priv->vbt.ddi_port_info[port].dp_boost_level; -+ -+ if (iboost == 0) { -+ const struct ddi_buf_trans *ddi_translations; -+ int n_entries; -+ -+ if (type == INTEL_OUTPUT_HDMI) -+ ddi_translations = intel_ddi_get_buf_trans_hdmi(dev_priv, &n_entries); -+ else if (type == INTEL_OUTPUT_EDP) -+ ddi_translations = intel_ddi_get_buf_trans_edp(dev_priv, port, &n_entries); -+ else -+ ddi_translations = intel_ddi_get_buf_trans_dp(dev_priv, port, &n_entries); -+ -+ if (WARN_ON_ONCE(!ddi_translations)) -+ return; -+ if (WARN_ON_ONCE(level >= n_entries)) -+ level = n_entries - 1; -+ -+ iboost = ddi_translations[level].i_boost; -+ } -+ -+ /* Make sure that the requested I_boost is valid */ -+ if (iboost && iboost != 0x1 && iboost != 0x3 && iboost != 0x7) { -+ DRM_ERROR("Invalid I_boost value %u\n", iboost); -+ return; -+ } -+ -+ _skl_ddi_set_iboost(dev_priv, port, iboost); -+ -+ if (port == PORT_A && intel_dig_port->max_lanes == 4) -+ _skl_ddi_set_iboost(dev_priv, PORT_E, iboost); -+} -+ -+static void bxt_ddi_vswing_sequence(struct intel_encoder *encoder, -+ int level, enum intel_output_type type) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ const struct bxt_ddi_buf_trans *ddi_translations; -+ enum port port = encoder->port; -+ int n_entries; -+ -+ if (type == INTEL_OUTPUT_HDMI) -+ ddi_translations = bxt_get_buf_trans_hdmi(dev_priv, &n_entries); -+ else if (type == INTEL_OUTPUT_EDP) -+ ddi_translations = bxt_get_buf_trans_edp(dev_priv, &n_entries); -+ else -+ ddi_translations = bxt_get_buf_trans_dp(dev_priv, &n_entries); -+ -+ if (WARN_ON_ONCE(!ddi_translations)) -+ return; -+ if (WARN_ON_ONCE(level >= n_entries)) -+ level = n_entries - 1; -+ -+ bxt_ddi_phy_set_signal_level(dev_priv, port, -+ ddi_translations[level].margin, -+ ddi_translations[level].scale, -+ ddi_translations[level].enable, -+ ddi_translations[level].deemphasis); -+} -+ -+u8 intel_ddi_dp_voltage_max(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ enum port port = encoder->port; -+ int n_entries; -+ -+ if (INTEL_GEN(dev_priv) >= 11) { -+ if (intel_port_is_combophy(dev_priv, port)) -+ icl_get_combo_buf_trans(dev_priv, port, encoder->type, -+ intel_dp->link_rate, &n_entries); -+ else -+ n_entries = ARRAY_SIZE(icl_mg_phy_ddi_translations); -+ } else if (IS_CANNONLAKE(dev_priv)) { -+ if (encoder->type == INTEL_OUTPUT_EDP) -+ cnl_get_buf_trans_edp(dev_priv, &n_entries); -+ else -+ cnl_get_buf_trans_dp(dev_priv, &n_entries); -+ } else if (IS_GEN9_LP(dev_priv)) { -+ if (encoder->type == INTEL_OUTPUT_EDP) -+ bxt_get_buf_trans_edp(dev_priv, &n_entries); -+ else -+ bxt_get_buf_trans_dp(dev_priv, &n_entries); -+ } else { -+ if (encoder->type == INTEL_OUTPUT_EDP) -+ intel_ddi_get_buf_trans_edp(dev_priv, port, &n_entries); -+ else -+ intel_ddi_get_buf_trans_dp(dev_priv, port, &n_entries); -+ } -+ -+ if (WARN_ON(n_entries < 1)) -+ n_entries = 1; -+ if (WARN_ON(n_entries > ARRAY_SIZE(index_to_dp_signal_levels))) -+ n_entries = ARRAY_SIZE(index_to_dp_signal_levels); -+ -+ return index_to_dp_signal_levels[n_entries - 1] & -+ DP_TRAIN_VOLTAGE_SWING_MASK; -+} -+ -+/* -+ * We assume that the full set of pre-emphasis values can be -+ * used on all DDI platforms. Should that change we need to -+ * rethink this code. -+ */ -+u8 intel_ddi_dp_pre_emphasis_max(struct intel_encoder *encoder, u8 voltage_swing) -+{ -+ switch (voltage_swing & DP_TRAIN_VOLTAGE_SWING_MASK) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: -+ return DP_TRAIN_PRE_EMPH_LEVEL_3; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: -+ return DP_TRAIN_PRE_EMPH_LEVEL_2; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: -+ return DP_TRAIN_PRE_EMPH_LEVEL_1; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_3: -+ default: -+ return DP_TRAIN_PRE_EMPH_LEVEL_0; -+ } -+} -+ -+static void cnl_ddi_vswing_program(struct intel_encoder *encoder, -+ int level, enum intel_output_type type) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ const struct cnl_ddi_buf_trans *ddi_translations; -+ enum port port = encoder->port; -+ int n_entries, ln; -+ u32 val; -+ -+ if (type == INTEL_OUTPUT_HDMI) -+ ddi_translations = cnl_get_buf_trans_hdmi(dev_priv, &n_entries); -+ else if (type == INTEL_OUTPUT_EDP) -+ ddi_translations = cnl_get_buf_trans_edp(dev_priv, &n_entries); -+ else -+ ddi_translations = cnl_get_buf_trans_dp(dev_priv, &n_entries); -+ -+ if (WARN_ON_ONCE(!ddi_translations)) -+ return; -+ if (WARN_ON_ONCE(level >= n_entries)) -+ level = n_entries - 1; -+ -+ /* Set PORT_TX_DW5 Scaling Mode Sel to 010b. */ -+ val = I915_READ(CNL_PORT_TX_DW5_LN0(port)); -+ val &= ~SCALING_MODE_SEL_MASK; -+ val |= SCALING_MODE_SEL(2); -+ I915_WRITE(CNL_PORT_TX_DW5_GRP(port), val); -+ -+ /* Program PORT_TX_DW2 */ -+ val = I915_READ(CNL_PORT_TX_DW2_LN0(port)); -+ val &= ~(SWING_SEL_LOWER_MASK | SWING_SEL_UPPER_MASK | -+ RCOMP_SCALAR_MASK); -+ val |= SWING_SEL_UPPER(ddi_translations[level].dw2_swing_sel); -+ val |= SWING_SEL_LOWER(ddi_translations[level].dw2_swing_sel); -+ /* Rcomp scalar is fixed as 0x98 for every table entry */ -+ val |= RCOMP_SCALAR(0x98); -+ I915_WRITE(CNL_PORT_TX_DW2_GRP(port), val); -+ -+ /* Program PORT_TX_DW4 */ -+ /* We cannot write to GRP. It would overrite individual loadgen */ -+ for (ln = 0; ln < 4; ln++) { -+ val = I915_READ(CNL_PORT_TX_DW4_LN(ln, port)); -+ val &= ~(POST_CURSOR_1_MASK | POST_CURSOR_2_MASK | -+ CURSOR_COEFF_MASK); -+ val |= POST_CURSOR_1(ddi_translations[level].dw4_post_cursor_1); -+ val |= POST_CURSOR_2(ddi_translations[level].dw4_post_cursor_2); -+ val |= CURSOR_COEFF(ddi_translations[level].dw4_cursor_coeff); -+ I915_WRITE(CNL_PORT_TX_DW4_LN(ln, port), val); -+ } -+ -+ /* Program PORT_TX_DW5 */ -+ /* All DW5 values are fixed for every table entry */ -+ val = I915_READ(CNL_PORT_TX_DW5_LN0(port)); -+ val &= ~RTERM_SELECT_MASK; -+ val |= RTERM_SELECT(6); -+ val |= TAP3_DISABLE; -+ I915_WRITE(CNL_PORT_TX_DW5_GRP(port), val); -+ -+ /* Program PORT_TX_DW7 */ -+ val = I915_READ(CNL_PORT_TX_DW7_LN0(port)); -+ val &= ~N_SCALAR_MASK; -+ val |= N_SCALAR(ddi_translations[level].dw7_n_scalar); -+ I915_WRITE(CNL_PORT_TX_DW7_GRP(port), val); -+} -+ -+static void cnl_ddi_vswing_sequence(struct intel_encoder *encoder, -+ int level, enum intel_output_type type) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ int width, rate, ln; -+ u32 val; -+ -+ if (type == INTEL_OUTPUT_HDMI) { -+ width = 4; -+ rate = 0; /* Rate is always < than 6GHz for HDMI */ -+ } else { -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ -+ width = intel_dp->lane_count; -+ rate = intel_dp->link_rate; -+ } -+ -+ /* -+ * 1. If port type is eDP or DP, -+ * set PORT_PCS_DW1 cmnkeeper_enable to 1b, -+ * else clear to 0b. -+ */ -+ val = I915_READ(CNL_PORT_PCS_DW1_LN0(port)); -+ if (type != INTEL_OUTPUT_HDMI) -+ val |= COMMON_KEEPER_EN; -+ else -+ val &= ~COMMON_KEEPER_EN; -+ I915_WRITE(CNL_PORT_PCS_DW1_GRP(port), val); -+ -+ /* 2. Program loadgen select */ -+ /* -+ * Program PORT_TX_DW4_LN depending on Bit rate and used lanes -+ * <= 6 GHz and 4 lanes (LN0=0, LN1=1, LN2=1, LN3=1) -+ * <= 6 GHz and 1,2 lanes (LN0=0, LN1=1, LN2=1, LN3=0) -+ * > 6 GHz (LN0=0, LN1=0, LN2=0, LN3=0) -+ */ -+ for (ln = 0; ln <= 3; ln++) { -+ val = I915_READ(CNL_PORT_TX_DW4_LN(ln, port)); -+ val &= ~LOADGEN_SELECT; -+ -+ if ((rate <= 600000 && width == 4 && ln >= 1) || -+ (rate <= 600000 && width < 4 && (ln == 1 || ln == 2))) { -+ val |= LOADGEN_SELECT; -+ } -+ I915_WRITE(CNL_PORT_TX_DW4_LN(ln, port), val); -+ } -+ -+ /* 3. Set PORT_CL_DW5 SUS Clock Config to 11b */ -+ val = I915_READ(CNL_PORT_CL1CM_DW5); -+ val |= SUS_CLOCK_CONFIG; -+ I915_WRITE(CNL_PORT_CL1CM_DW5, val); -+ -+ /* 4. Clear training enable to change swing values */ -+ val = I915_READ(CNL_PORT_TX_DW5_LN0(port)); -+ val &= ~TX_TRAINING_EN; -+ I915_WRITE(CNL_PORT_TX_DW5_GRP(port), val); -+ -+ /* 5. Program swing and de-emphasis */ -+ cnl_ddi_vswing_program(encoder, level, type); -+ -+ /* 6. Set training enable to trigger update */ -+ val = I915_READ(CNL_PORT_TX_DW5_LN0(port)); -+ val |= TX_TRAINING_EN; -+ I915_WRITE(CNL_PORT_TX_DW5_GRP(port), val); -+} -+ -+static void icl_ddi_combo_vswing_program(struct drm_i915_private *dev_priv, -+ u32 level, enum port port, int type, -+ int rate) -+{ -+ const struct cnl_ddi_buf_trans *ddi_translations = NULL; -+ u32 n_entries, val; -+ int ln; -+ -+ ddi_translations = icl_get_combo_buf_trans(dev_priv, port, type, -+ rate, &n_entries); -+ if (!ddi_translations) -+ return; -+ -+ if (level >= n_entries) { -+ DRM_DEBUG_KMS("DDI translation not found for level %d. Using %d instead.", level, n_entries - 1); -+ level = n_entries - 1; -+ } -+ -+ /* Set PORT_TX_DW5 */ -+ val = I915_READ(ICL_PORT_TX_DW5_LN0(port)); -+ val &= ~(SCALING_MODE_SEL_MASK | RTERM_SELECT_MASK | -+ TAP2_DISABLE | TAP3_DISABLE); -+ val |= SCALING_MODE_SEL(0x2); -+ val |= RTERM_SELECT(0x6); -+ val |= TAP3_DISABLE; -+ I915_WRITE(ICL_PORT_TX_DW5_GRP(port), val); -+ -+ /* Program PORT_TX_DW2 */ -+ val = I915_READ(ICL_PORT_TX_DW2_LN0(port)); -+ val &= ~(SWING_SEL_LOWER_MASK | SWING_SEL_UPPER_MASK | -+ RCOMP_SCALAR_MASK); -+ val |= SWING_SEL_UPPER(ddi_translations[level].dw2_swing_sel); -+ val |= SWING_SEL_LOWER(ddi_translations[level].dw2_swing_sel); -+ /* Program Rcomp scalar for every table entry */ -+ val |= RCOMP_SCALAR(0x98); -+ I915_WRITE(ICL_PORT_TX_DW2_GRP(port), val); -+ -+ /* Program PORT_TX_DW4 */ -+ /* We cannot write to GRP. It would overwrite individual loadgen. */ -+ for (ln = 0; ln <= 3; ln++) { -+ val = I915_READ(ICL_PORT_TX_DW4_LN(ln, port)); -+ val &= ~(POST_CURSOR_1_MASK | POST_CURSOR_2_MASK | -+ CURSOR_COEFF_MASK); -+ val |= POST_CURSOR_1(ddi_translations[level].dw4_post_cursor_1); -+ val |= POST_CURSOR_2(ddi_translations[level].dw4_post_cursor_2); -+ val |= CURSOR_COEFF(ddi_translations[level].dw4_cursor_coeff); -+ I915_WRITE(ICL_PORT_TX_DW4_LN(ln, port), val); -+ } -+ -+ /* Program PORT_TX_DW7 */ -+ val = I915_READ(ICL_PORT_TX_DW7_LN0(port)); -+ val &= ~N_SCALAR_MASK; -+ val |= N_SCALAR(ddi_translations[level].dw7_n_scalar); -+ I915_WRITE(ICL_PORT_TX_DW7_GRP(port), val); -+} -+ -+static void icl_combo_phy_ddi_vswing_sequence(struct intel_encoder *encoder, -+ u32 level, -+ enum intel_output_type type) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ int width = 0; -+ int rate = 0; -+ u32 val; -+ int ln = 0; -+ -+ if (type == INTEL_OUTPUT_HDMI) { -+ width = 4; -+ /* Rate is always < than 6GHz for HDMI */ -+ } else { -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ -+ width = intel_dp->lane_count; -+ rate = intel_dp->link_rate; -+ } -+ -+ /* -+ * 1. If port type is eDP or DP, -+ * set PORT_PCS_DW1 cmnkeeper_enable to 1b, -+ * else clear to 0b. -+ */ -+ val = I915_READ(ICL_PORT_PCS_DW1_LN0(port)); -+ if (type == INTEL_OUTPUT_HDMI) -+ val &= ~COMMON_KEEPER_EN; -+ else -+ val |= COMMON_KEEPER_EN; -+ I915_WRITE(ICL_PORT_PCS_DW1_GRP(port), val); -+ -+ /* 2. Program loadgen select */ -+ /* -+ * Program PORT_TX_DW4_LN depending on Bit rate and used lanes -+ * <= 6 GHz and 4 lanes (LN0=0, LN1=1, LN2=1, LN3=1) -+ * <= 6 GHz and 1,2 lanes (LN0=0, LN1=1, LN2=1, LN3=0) -+ * > 6 GHz (LN0=0, LN1=0, LN2=0, LN3=0) -+ */ -+ for (ln = 0; ln <= 3; ln++) { -+ val = I915_READ(ICL_PORT_TX_DW4_LN(ln, port)); -+ val &= ~LOADGEN_SELECT; -+ -+ if ((rate <= 600000 && width == 4 && ln >= 1) || -+ (rate <= 600000 && width < 4 && (ln == 1 || ln == 2))) { -+ val |= LOADGEN_SELECT; -+ } -+ I915_WRITE(ICL_PORT_TX_DW4_LN(ln, port), val); -+ } -+ -+ /* 3. Set PORT_CL_DW5 SUS Clock Config to 11b */ -+ val = I915_READ(ICL_PORT_CL_DW5(port)); -+ val |= SUS_CLOCK_CONFIG; -+ I915_WRITE(ICL_PORT_CL_DW5(port), val); -+ -+ /* 4. Clear training enable to change swing values */ -+ val = I915_READ(ICL_PORT_TX_DW5_LN0(port)); -+ val &= ~TX_TRAINING_EN; -+ I915_WRITE(ICL_PORT_TX_DW5_GRP(port), val); -+ -+ /* 5. Program swing and de-emphasis */ -+ icl_ddi_combo_vswing_program(dev_priv, level, port, type, rate); -+ -+ /* 6. Set training enable to trigger update */ -+ val = I915_READ(ICL_PORT_TX_DW5_LN0(port)); -+ val |= TX_TRAINING_EN; -+ I915_WRITE(ICL_PORT_TX_DW5_GRP(port), val); -+} -+ -+static void icl_mg_phy_ddi_vswing_sequence(struct intel_encoder *encoder, -+ int link_clock, -+ u32 level) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ const struct icl_mg_phy_ddi_buf_trans *ddi_translations; -+ u32 n_entries, val; -+ int ln; -+ -+ n_entries = ARRAY_SIZE(icl_mg_phy_ddi_translations); -+ ddi_translations = icl_mg_phy_ddi_translations; -+ /* The table does not have values for level 3 and level 9. */ -+ if (level >= n_entries || level == 3 || level == 9) { -+ DRM_DEBUG_KMS("DDI translation not found for level %d. Using %d instead.", -+ level, n_entries - 2); -+ level = n_entries - 2; -+ } -+ -+ /* Set MG_TX_LINK_PARAMS cri_use_fs32 to 0. */ -+ for (ln = 0; ln < 2; ln++) { -+ val = I915_READ(MG_TX1_LINK_PARAMS(ln, port)); -+ val &= ~CRI_USE_FS32; -+ I915_WRITE(MG_TX1_LINK_PARAMS(ln, port), val); -+ -+ val = I915_READ(MG_TX2_LINK_PARAMS(ln, port)); -+ val &= ~CRI_USE_FS32; -+ I915_WRITE(MG_TX2_LINK_PARAMS(ln, port), val); -+ } -+ -+ /* Program MG_TX_SWINGCTRL with values from vswing table */ -+ for (ln = 0; ln < 2; ln++) { -+ val = I915_READ(MG_TX1_SWINGCTRL(ln, port)); -+ val &= ~CRI_TXDEEMPH_OVERRIDE_17_12_MASK; -+ val |= CRI_TXDEEMPH_OVERRIDE_17_12( -+ ddi_translations[level].cri_txdeemph_override_17_12); -+ I915_WRITE(MG_TX1_SWINGCTRL(ln, port), val); -+ -+ val = I915_READ(MG_TX2_SWINGCTRL(ln, port)); -+ val &= ~CRI_TXDEEMPH_OVERRIDE_17_12_MASK; -+ val |= CRI_TXDEEMPH_OVERRIDE_17_12( -+ ddi_translations[level].cri_txdeemph_override_17_12); -+ I915_WRITE(MG_TX2_SWINGCTRL(ln, port), val); -+ } -+ -+ /* Program MG_TX_DRVCTRL with values from vswing table */ -+ for (ln = 0; ln < 2; ln++) { -+ val = I915_READ(MG_TX1_DRVCTRL(ln, port)); -+ val &= ~(CRI_TXDEEMPH_OVERRIDE_11_6_MASK | -+ CRI_TXDEEMPH_OVERRIDE_5_0_MASK); -+ val |= CRI_TXDEEMPH_OVERRIDE_5_0( -+ ddi_translations[level].cri_txdeemph_override_5_0) | -+ CRI_TXDEEMPH_OVERRIDE_11_6( -+ ddi_translations[level].cri_txdeemph_override_11_6) | -+ CRI_TXDEEMPH_OVERRIDE_EN; -+ I915_WRITE(MG_TX1_DRVCTRL(ln, port), val); -+ -+ val = I915_READ(MG_TX2_DRVCTRL(ln, port)); -+ val &= ~(CRI_TXDEEMPH_OVERRIDE_11_6_MASK | -+ CRI_TXDEEMPH_OVERRIDE_5_0_MASK); -+ val |= CRI_TXDEEMPH_OVERRIDE_5_0( -+ ddi_translations[level].cri_txdeemph_override_5_0) | -+ CRI_TXDEEMPH_OVERRIDE_11_6( -+ ddi_translations[level].cri_txdeemph_override_11_6) | -+ CRI_TXDEEMPH_OVERRIDE_EN; -+ I915_WRITE(MG_TX2_DRVCTRL(ln, port), val); -+ -+ /* FIXME: Program CRI_LOADGEN_SEL after the spec is updated */ -+ } -+ -+ /* -+ * Program MG_CLKHUB with value from frequency table -+ * In case of Legacy mode on MG PHY, both TX1 and TX2 enabled so use the -+ * values from table for which TX1 and TX2 enabled. -+ */ -+ for (ln = 0; ln < 2; ln++) { -+ val = I915_READ(MG_CLKHUB(ln, port)); -+ if (link_clock < 300000) -+ val |= CFG_LOW_RATE_LKREN_EN; -+ else -+ val &= ~CFG_LOW_RATE_LKREN_EN; -+ I915_WRITE(MG_CLKHUB(ln, port), val); -+ } -+ -+ /* Program the MG_TX_DCC based on the link frequency */ -+ for (ln = 0; ln < 2; ln++) { -+ val = I915_READ(MG_TX1_DCC(ln, port)); -+ val &= ~CFG_AMI_CK_DIV_OVERRIDE_VAL_MASK; -+ if (link_clock <= 500000) { -+ val &= ~CFG_AMI_CK_DIV_OVERRIDE_EN; -+ } else { -+ val |= CFG_AMI_CK_DIV_OVERRIDE_EN | -+ CFG_AMI_CK_DIV_OVERRIDE_VAL(1); -+ } -+ I915_WRITE(MG_TX1_DCC(ln, port), val); -+ -+ val = I915_READ(MG_TX2_DCC(ln, port)); -+ val &= ~CFG_AMI_CK_DIV_OVERRIDE_VAL_MASK; -+ if (link_clock <= 500000) { -+ val &= ~CFG_AMI_CK_DIV_OVERRIDE_EN; -+ } else { -+ val |= CFG_AMI_CK_DIV_OVERRIDE_EN | -+ CFG_AMI_CK_DIV_OVERRIDE_VAL(1); -+ } -+ I915_WRITE(MG_TX2_DCC(ln, port), val); -+ } -+ -+ /* Program MG_TX_PISO_READLOAD with values from vswing table */ -+ for (ln = 0; ln < 2; ln++) { -+ val = I915_READ(MG_TX1_PISO_READLOAD(ln, port)); -+ val |= CRI_CALCINIT; -+ I915_WRITE(MG_TX1_PISO_READLOAD(ln, port), val); -+ -+ val = I915_READ(MG_TX2_PISO_READLOAD(ln, port)); -+ val |= CRI_CALCINIT; -+ I915_WRITE(MG_TX2_PISO_READLOAD(ln, port), val); -+ } -+} -+ -+static void icl_ddi_vswing_sequence(struct intel_encoder *encoder, -+ int link_clock, -+ u32 level, -+ enum intel_output_type type) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ -+ if (intel_port_is_combophy(dev_priv, port)) -+ icl_combo_phy_ddi_vswing_sequence(encoder, level, type); -+ else -+ icl_mg_phy_ddi_vswing_sequence(encoder, link_clock, level); -+} -+ -+static u32 translate_signal_level(int signal_levels) -+{ -+ int i; -+ -+ for (i = 0; i < ARRAY_SIZE(index_to_dp_signal_levels); i++) { -+ if (index_to_dp_signal_levels[i] == signal_levels) -+ return i; -+ } -+ -+ WARN(1, "Unsupported voltage swing/pre-emphasis level: 0x%x\n", -+ signal_levels); -+ -+ return 0; -+} -+ -+static u32 intel_ddi_dp_level(struct intel_dp *intel_dp) -+{ -+ u8 train_set = intel_dp->train_set[0]; -+ int signal_levels = train_set & (DP_TRAIN_VOLTAGE_SWING_MASK | -+ DP_TRAIN_PRE_EMPHASIS_MASK); -+ -+ return translate_signal_level(signal_levels); -+} -+ -+u32 bxt_signal_levels(struct intel_dp *intel_dp) -+{ -+ struct intel_digital_port *dport = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = to_i915(dport->base.base.dev); -+ struct intel_encoder *encoder = &dport->base; -+ int level = intel_ddi_dp_level(intel_dp); -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ icl_ddi_vswing_sequence(encoder, intel_dp->link_rate, -+ level, encoder->type); -+ else if (IS_CANNONLAKE(dev_priv)) -+ cnl_ddi_vswing_sequence(encoder, level, encoder->type); -+ else -+ bxt_ddi_vswing_sequence(encoder, level, encoder->type); -+ -+ return 0; -+} -+ -+u32 ddi_signal_levels(struct intel_dp *intel_dp) -+{ -+ struct intel_digital_port *dport = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = to_i915(dport->base.base.dev); -+ struct intel_encoder *encoder = &dport->base; -+ int level = intel_ddi_dp_level(intel_dp); -+ -+ if (IS_GEN9_BC(dev_priv)) -+ skl_ddi_set_iboost(encoder, level, encoder->type); -+ -+ return DDI_BUF_TRANS_SELECT(level); -+} -+ -+static inline -+u32 icl_dpclka_cfgcr0_clk_off(struct drm_i915_private *dev_priv, -+ enum port port) -+{ -+ if (intel_port_is_combophy(dev_priv, port)) { -+ return ICL_DPCLKA_CFGCR0_DDI_CLK_OFF(port); -+ } else if (intel_port_is_tc(dev_priv, port)) { -+ enum tc_port tc_port = intel_port_to_tc(dev_priv, port); -+ -+ return ICL_DPCLKA_CFGCR0_TC_CLK_OFF(tc_port); -+ } -+ -+ return 0; -+} -+ -+static void icl_map_plls_to_ports(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_shared_dpll *pll = crtc_state->shared_dpll; -+ enum port port = encoder->port; -+ u32 val; -+ -+ mutex_lock(&dev_priv->dpll_lock); -+ -+ val = I915_READ(DPCLKA_CFGCR0_ICL); -+ WARN_ON((val & icl_dpclka_cfgcr0_clk_off(dev_priv, port)) == 0); -+ -+ if (intel_port_is_combophy(dev_priv, port)) { -+ val &= ~DPCLKA_CFGCR0_DDI_CLK_SEL_MASK(port); -+ val |= DPCLKA_CFGCR0_DDI_CLK_SEL(pll->info->id, port); -+ I915_WRITE(DPCLKA_CFGCR0_ICL, val); -+ POSTING_READ(DPCLKA_CFGCR0_ICL); -+ } -+ -+ val &= ~icl_dpclka_cfgcr0_clk_off(dev_priv, port); -+ I915_WRITE(DPCLKA_CFGCR0_ICL, val); -+ -+ mutex_unlock(&dev_priv->dpll_lock); -+} -+ -+static void icl_unmap_plls_to_ports(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ u32 val; -+ -+ mutex_lock(&dev_priv->dpll_lock); -+ -+ val = I915_READ(DPCLKA_CFGCR0_ICL); -+ val |= icl_dpclka_cfgcr0_clk_off(dev_priv, port); -+ I915_WRITE(DPCLKA_CFGCR0_ICL, val); -+ -+ mutex_unlock(&dev_priv->dpll_lock); -+} -+ -+void icl_sanitize_encoder_pll_mapping(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ u32 val; -+ enum port port; -+ u32 port_mask; -+ bool ddi_clk_needed; -+ -+ /* -+ * In case of DP MST, we sanitize the primary encoder only, not the -+ * virtual ones. -+ */ -+ if (encoder->type == INTEL_OUTPUT_DP_MST) -+ return; -+ -+ if (!encoder->base.crtc && intel_encoder_is_dp(encoder)) { -+ u8 pipe_mask; -+ bool is_mst; -+ -+ intel_ddi_get_encoder_pipes(encoder, &pipe_mask, &is_mst); -+ /* -+ * In the unlikely case that BIOS enables DP in MST mode, just -+ * warn since our MST HW readout is incomplete. -+ */ -+ if (WARN_ON(is_mst)) -+ return; -+ } -+ -+ port_mask = BIT(encoder->port); -+ ddi_clk_needed = encoder->base.crtc; -+ -+ if (encoder->type == INTEL_OUTPUT_DSI) { -+ struct intel_encoder *other_encoder; -+ -+ port_mask = intel_dsi_encoder_ports(encoder); -+ /* -+ * Sanity check that we haven't incorrectly registered another -+ * encoder using any of the ports of this DSI encoder. -+ */ -+ for_each_intel_encoder(&dev_priv->drm, other_encoder) { -+ if (other_encoder == encoder) -+ continue; -+ -+ if (WARN_ON(port_mask & BIT(other_encoder->port))) -+ return; -+ } -+ /* -+ * For DSI we keep the ddi clocks gated -+ * except during enable/disable sequence. -+ */ -+ ddi_clk_needed = false; -+ } -+ -+ val = I915_READ(DPCLKA_CFGCR0_ICL); -+ for_each_port_masked(port, port_mask) { -+ bool ddi_clk_ungated = !(val & -+ icl_dpclka_cfgcr0_clk_off(dev_priv, -+ port)); -+ -+ if (ddi_clk_needed == ddi_clk_ungated) -+ continue; -+ -+ /* -+ * Punt on the case now where clock is gated, but it would -+ * be needed by the port. Something else is really broken then. -+ */ -+ if (WARN_ON(ddi_clk_needed)) -+ continue; -+ -+ DRM_NOTE("Port %c is disabled/in DSI mode with an ungated DDI clock, gate it\n", -+ port_name(port)); -+ val |= icl_dpclka_cfgcr0_clk_off(dev_priv, port); -+ I915_WRITE(DPCLKA_CFGCR0_ICL, val); -+ } -+} -+ -+static void intel_ddi_clk_select(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ u32 val; -+ const struct intel_shared_dpll *pll = crtc_state->shared_dpll; -+ -+ if (WARN_ON(!pll)) -+ return; -+ -+ mutex_lock(&dev_priv->dpll_lock); -+ -+ if (INTEL_GEN(dev_priv) >= 11) { -+ if (!intel_port_is_combophy(dev_priv, port)) -+ I915_WRITE(DDI_CLK_SEL(port), -+ icl_pll_to_ddi_clk_sel(encoder, crtc_state)); -+ } else if (IS_CANNONLAKE(dev_priv)) { -+ /* Configure DPCLKA_CFGCR0 to map the DPLL to the DDI. */ -+ val = I915_READ(DPCLKA_CFGCR0); -+ val &= ~DPCLKA_CFGCR0_DDI_CLK_SEL_MASK(port); -+ val |= DPCLKA_CFGCR0_DDI_CLK_SEL(pll->info->id, port); -+ I915_WRITE(DPCLKA_CFGCR0, val); -+ -+ /* -+ * Configure DPCLKA_CFGCR0 to turn on the clock for the DDI. -+ * This step and the step before must be done with separate -+ * register writes. -+ */ -+ val = I915_READ(DPCLKA_CFGCR0); -+ val &= ~DPCLKA_CFGCR0_DDI_CLK_OFF(port); -+ I915_WRITE(DPCLKA_CFGCR0, val); -+ } else if (IS_GEN9_BC(dev_priv)) { -+ /* DDI -> PLL mapping */ -+ val = I915_READ(DPLL_CTRL2); -+ -+ val &= ~(DPLL_CTRL2_DDI_CLK_OFF(port) | -+ DPLL_CTRL2_DDI_CLK_SEL_MASK(port)); -+ val |= (DPLL_CTRL2_DDI_CLK_SEL(pll->info->id, port) | -+ DPLL_CTRL2_DDI_SEL_OVERRIDE(port)); -+ -+ I915_WRITE(DPLL_CTRL2, val); -+ -+ } else if (INTEL_GEN(dev_priv) < 9) { -+ I915_WRITE(PORT_CLK_SEL(port), hsw_pll_to_ddi_pll_sel(pll)); -+ } -+ -+ mutex_unlock(&dev_priv->dpll_lock); -+} -+ -+static void intel_ddi_clk_disable(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ -+ if (INTEL_GEN(dev_priv) >= 11) { -+ if (!intel_port_is_combophy(dev_priv, port)) -+ I915_WRITE(DDI_CLK_SEL(port), DDI_CLK_SEL_NONE); -+ } else if (IS_CANNONLAKE(dev_priv)) { -+ I915_WRITE(DPCLKA_CFGCR0, I915_READ(DPCLKA_CFGCR0) | -+ DPCLKA_CFGCR0_DDI_CLK_OFF(port)); -+ } else if (IS_GEN9_BC(dev_priv)) { -+ I915_WRITE(DPLL_CTRL2, I915_READ(DPLL_CTRL2) | -+ DPLL_CTRL2_DDI_CLK_OFF(port)); -+ } else if (INTEL_GEN(dev_priv) < 9) { -+ I915_WRITE(PORT_CLK_SEL(port), PORT_CLK_SEL_NONE); -+ } -+} -+ -+static void icl_enable_phy_clock_gating(struct intel_digital_port *dig_port) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); -+ enum port port = dig_port->base.port; -+ enum tc_port tc_port = intel_port_to_tc(dev_priv, port); -+ u32 val; -+ int ln; -+ -+ if (tc_port == PORT_TC_NONE) -+ return; -+ -+ for (ln = 0; ln < 2; ln++) { -+ val = I915_READ(MG_DP_MODE(ln, port)); -+ val |= MG_DP_MODE_CFG_TR2PWR_GATING | -+ MG_DP_MODE_CFG_TRPWR_GATING | -+ MG_DP_MODE_CFG_CLNPWR_GATING | -+ MG_DP_MODE_CFG_DIGPWR_GATING | -+ MG_DP_MODE_CFG_GAONPWR_GATING; -+ I915_WRITE(MG_DP_MODE(ln, port), val); -+ } -+ -+ val = I915_READ(MG_MISC_SUS0(tc_port)); -+ val |= MG_MISC_SUS0_SUSCLK_DYNCLKGATE_MODE(3) | -+ MG_MISC_SUS0_CFG_TR2PWR_GATING | -+ MG_MISC_SUS0_CFG_CL2PWR_GATING | -+ MG_MISC_SUS0_CFG_GAONPWR_GATING | -+ MG_MISC_SUS0_CFG_TRPWR_GATING | -+ MG_MISC_SUS0_CFG_CL1PWR_GATING | -+ MG_MISC_SUS0_CFG_DGPWR_GATING; -+ I915_WRITE(MG_MISC_SUS0(tc_port), val); -+} -+ -+static void icl_disable_phy_clock_gating(struct intel_digital_port *dig_port) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); -+ enum port port = dig_port->base.port; -+ enum tc_port tc_port = intel_port_to_tc(dev_priv, port); -+ u32 val; -+ int ln; -+ -+ if (tc_port == PORT_TC_NONE) -+ return; -+ -+ for (ln = 0; ln < 2; ln++) { -+ val = I915_READ(MG_DP_MODE(ln, port)); -+ val &= ~(MG_DP_MODE_CFG_TR2PWR_GATING | -+ MG_DP_MODE_CFG_TRPWR_GATING | -+ MG_DP_MODE_CFG_CLNPWR_GATING | -+ MG_DP_MODE_CFG_DIGPWR_GATING | -+ MG_DP_MODE_CFG_GAONPWR_GATING); -+ I915_WRITE(MG_DP_MODE(ln, port), val); -+ } -+ -+ val = I915_READ(MG_MISC_SUS0(tc_port)); -+ val &= ~(MG_MISC_SUS0_SUSCLK_DYNCLKGATE_MODE_MASK | -+ MG_MISC_SUS0_CFG_TR2PWR_GATING | -+ MG_MISC_SUS0_CFG_CL2PWR_GATING | -+ MG_MISC_SUS0_CFG_GAONPWR_GATING | -+ MG_MISC_SUS0_CFG_TRPWR_GATING | -+ MG_MISC_SUS0_CFG_CL1PWR_GATING | -+ MG_MISC_SUS0_CFG_DGPWR_GATING); -+ I915_WRITE(MG_MISC_SUS0(tc_port), val); -+} -+ -+static void icl_program_mg_dp_mode(struct intel_digital_port *intel_dig_port) -+{ -+ struct drm_i915_private *dev_priv = to_i915(intel_dig_port->base.base.dev); -+ enum port port = intel_dig_port->base.port; -+ enum tc_port tc_port = intel_port_to_tc(dev_priv, port); -+ u32 ln0, ln1, lane_info; -+ -+ if (tc_port == PORT_TC_NONE || intel_dig_port->tc_type == TC_PORT_TBT) -+ return; -+ -+ ln0 = I915_READ(MG_DP_MODE(0, port)); -+ ln1 = I915_READ(MG_DP_MODE(1, port)); -+ -+ switch (intel_dig_port->tc_type) { -+ case TC_PORT_TYPEC: -+ ln0 &= ~(MG_DP_MODE_CFG_DP_X1_MODE | MG_DP_MODE_CFG_DP_X2_MODE); -+ ln1 &= ~(MG_DP_MODE_CFG_DP_X1_MODE | MG_DP_MODE_CFG_DP_X2_MODE); -+ -+ lane_info = (I915_READ(PORT_TX_DFLEXDPSP) & -+ DP_LANE_ASSIGNMENT_MASK(tc_port)) >> -+ DP_LANE_ASSIGNMENT_SHIFT(tc_port); -+ -+ switch (lane_info) { -+ case 0x1: -+ case 0x4: -+ break; -+ case 0x2: -+ ln0 |= MG_DP_MODE_CFG_DP_X1_MODE; -+ break; -+ case 0x3: -+ ln0 |= MG_DP_MODE_CFG_DP_X1_MODE | -+ MG_DP_MODE_CFG_DP_X2_MODE; -+ break; -+ case 0x8: -+ ln1 |= MG_DP_MODE_CFG_DP_X1_MODE; -+ break; -+ case 0xC: -+ ln1 |= MG_DP_MODE_CFG_DP_X1_MODE | -+ MG_DP_MODE_CFG_DP_X2_MODE; -+ break; -+ case 0xF: -+ ln0 |= MG_DP_MODE_CFG_DP_X1_MODE | -+ MG_DP_MODE_CFG_DP_X2_MODE; -+ ln1 |= MG_DP_MODE_CFG_DP_X1_MODE | -+ MG_DP_MODE_CFG_DP_X2_MODE; -+ break; -+ default: -+ MISSING_CASE(lane_info); -+ } -+ break; -+ -+ case TC_PORT_LEGACY: -+ ln0 |= MG_DP_MODE_CFG_DP_X1_MODE | MG_DP_MODE_CFG_DP_X2_MODE; -+ ln1 |= MG_DP_MODE_CFG_DP_X1_MODE | MG_DP_MODE_CFG_DP_X2_MODE; -+ break; -+ -+ default: -+ MISSING_CASE(intel_dig_port->tc_type); -+ return; -+ } -+ -+ I915_WRITE(MG_DP_MODE(0, port), ln0); -+ I915_WRITE(MG_DP_MODE(1, port), ln1); -+} -+ -+static void intel_dp_sink_set_fec_ready(struct intel_dp *intel_dp, -+ const struct intel_crtc_state *crtc_state) -+{ -+ if (!crtc_state->fec_enable) -+ return; -+ -+ if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_FEC_CONFIGURATION, DP_FEC_READY) <= 0) -+ DRM_DEBUG_KMS("Failed to set FEC_READY in the sink\n"); -+} -+ -+static void intel_ddi_enable_fec(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ u32 val; -+ -+ if (!crtc_state->fec_enable) -+ return; -+ -+ val = I915_READ(DP_TP_CTL(port)); -+ val |= DP_TP_CTL_FEC_ENABLE; -+ I915_WRITE(DP_TP_CTL(port), val); -+ -+ if (intel_wait_for_register(&dev_priv->uncore, DP_TP_STATUS(port), -+ DP_TP_STATUS_FEC_ENABLE_LIVE, -+ DP_TP_STATUS_FEC_ENABLE_LIVE, -+ 1)) -+ DRM_ERROR("Timed out waiting for FEC Enable Status\n"); -+} -+ -+static void intel_ddi_disable_fec_state(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ u32 val; -+ -+ if (!crtc_state->fec_enable) -+ return; -+ -+ val = I915_READ(DP_TP_CTL(port)); -+ val &= ~DP_TP_CTL_FEC_ENABLE; -+ I915_WRITE(DP_TP_CTL(port), val); -+ POSTING_READ(DP_TP_CTL(port)); -+} -+ -+static void intel_ddi_pre_enable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ struct intel_digital_port *dig_port = enc_to_dig_port(&encoder->base); -+ bool is_mst = intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DP_MST); -+ int level = intel_ddi_dp_level(intel_dp); -+ -+ WARN_ON(is_mst && (port == PORT_A || port == PORT_E)); -+ -+ intel_dp_set_link_params(intel_dp, crtc_state->port_clock, -+ crtc_state->lane_count, is_mst); -+ -+ intel_edp_panel_on(intel_dp); -+ -+ intel_ddi_clk_select(encoder, crtc_state); -+ -+ intel_display_power_get(dev_priv, dig_port->ddi_io_power_domain); -+ -+ icl_program_mg_dp_mode(dig_port); -+ icl_disable_phy_clock_gating(dig_port); -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ icl_ddi_vswing_sequence(encoder, crtc_state->port_clock, -+ level, encoder->type); -+ else if (IS_CANNONLAKE(dev_priv)) -+ cnl_ddi_vswing_sequence(encoder, level, encoder->type); -+ else if (IS_GEN9_LP(dev_priv)) -+ bxt_ddi_vswing_sequence(encoder, level, encoder->type); -+ else -+ intel_prepare_dp_ddi_buffers(encoder, crtc_state); -+ -+ intel_ddi_init_dp_buf_reg(encoder); -+ if (!is_mst) -+ intel_dp_sink_dpms(intel_dp, DRM_MODE_DPMS_ON); -+ intel_dp_sink_set_decompression_state(intel_dp, crtc_state, -+ true); -+ intel_dp_sink_set_fec_ready(intel_dp, crtc_state); -+ intel_dp_start_link_train(intel_dp); -+ if (port != PORT_A || INTEL_GEN(dev_priv) >= 9) -+ intel_dp_stop_link_train(intel_dp); -+ -+ intel_ddi_enable_fec(encoder, crtc_state); -+ -+ icl_enable_phy_clock_gating(dig_port); -+ -+ if (!is_mst) -+ intel_ddi_enable_pipe_clock(crtc_state); -+ -+ intel_dsc_enable(encoder, crtc_state); -+} -+ -+static void intel_ddi_pre_enable_hdmi(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct intel_digital_port *intel_dig_port = enc_to_dig_port(&encoder->base); -+ struct intel_hdmi *intel_hdmi = &intel_dig_port->hdmi; -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ int level = intel_ddi_hdmi_level(dev_priv, port); -+ struct intel_digital_port *dig_port = enc_to_dig_port(&encoder->base); -+ -+ intel_dp_dual_mode_set_tmds_output(intel_hdmi, true); -+ intel_ddi_clk_select(encoder, crtc_state); -+ -+ intel_display_power_get(dev_priv, dig_port->ddi_io_power_domain); -+ -+ icl_program_mg_dp_mode(dig_port); -+ icl_disable_phy_clock_gating(dig_port); -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ icl_ddi_vswing_sequence(encoder, crtc_state->port_clock, -+ level, INTEL_OUTPUT_HDMI); -+ else if (IS_CANNONLAKE(dev_priv)) -+ cnl_ddi_vswing_sequence(encoder, level, INTEL_OUTPUT_HDMI); -+ else if (IS_GEN9_LP(dev_priv)) -+ bxt_ddi_vswing_sequence(encoder, level, INTEL_OUTPUT_HDMI); -+ else -+ intel_prepare_hdmi_ddi_buffers(encoder, level); -+ -+ icl_enable_phy_clock_gating(dig_port); -+ -+ if (IS_GEN9_BC(dev_priv)) -+ skl_ddi_set_iboost(encoder, level, INTEL_OUTPUT_HDMI); -+ -+ intel_ddi_enable_pipe_clock(crtc_state); -+ -+ intel_dig_port->set_infoframes(encoder, -+ crtc_state->has_infoframe, -+ crtc_state, conn_state); -+} -+ -+static void intel_ddi_pre_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ -+ /* -+ * When called from DP MST code: -+ * - conn_state will be NULL -+ * - encoder will be the main encoder (ie. mst->primary) -+ * - the main connector associated with this port -+ * won't be active or linked to a crtc -+ * - crtc_state will be the state of the first stream to -+ * be activated on this port, and it may not be the same -+ * stream that will be deactivated last, but each stream -+ * should have a state that is identical when it comes to -+ * the DP link parameteres -+ */ -+ -+ WARN_ON(crtc_state->has_pch_encoder); -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ icl_map_plls_to_ports(encoder, crtc_state); -+ -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, true); -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) { -+ intel_ddi_pre_enable_hdmi(encoder, crtc_state, conn_state); -+ } else { -+ struct intel_lspcon *lspcon = -+ enc_to_intel_lspcon(&encoder->base); -+ -+ intel_ddi_pre_enable_dp(encoder, crtc_state, conn_state); -+ if (lspcon->active) { -+ struct intel_digital_port *dig_port = -+ enc_to_dig_port(&encoder->base); -+ -+ dig_port->set_infoframes(encoder, -+ crtc_state->has_infoframe, -+ crtc_state, conn_state); -+ } -+ } -+} -+ -+static void intel_disable_ddi_buf(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ bool wait = false; -+ u32 val; -+ -+ val = I915_READ(DDI_BUF_CTL(port)); -+ if (val & DDI_BUF_CTL_ENABLE) { -+ val &= ~DDI_BUF_CTL_ENABLE; -+ I915_WRITE(DDI_BUF_CTL(port), val); -+ wait = true; -+ } -+ -+ val = I915_READ(DP_TP_CTL(port)); -+ val &= ~(DP_TP_CTL_ENABLE | DP_TP_CTL_LINK_TRAIN_MASK); -+ val |= DP_TP_CTL_LINK_TRAIN_PAT1; -+ I915_WRITE(DP_TP_CTL(port), val); -+ -+ /* Disable FEC in DP Sink */ -+ intel_ddi_disable_fec_state(encoder, crtc_state); -+ -+ if (wait) -+ intel_wait_ddi_buf_idle(dev_priv, port); -+} -+ -+static void intel_ddi_post_disable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_digital_port *dig_port = enc_to_dig_port(&encoder->base); -+ struct intel_dp *intel_dp = &dig_port->dp; -+ bool is_mst = intel_crtc_has_type(old_crtc_state, -+ INTEL_OUTPUT_DP_MST); -+ -+ if (!is_mst) { -+ intel_ddi_disable_pipe_clock(old_crtc_state); -+ /* -+ * Power down sink before disabling the port, otherwise we end -+ * up getting interrupts from the sink on detecting link loss. -+ */ -+ intel_dp_sink_dpms(intel_dp, DRM_MODE_DPMS_OFF); -+ } -+ -+ intel_disable_ddi_buf(encoder, old_crtc_state); -+ -+ intel_edp_panel_vdd_on(intel_dp); -+ intel_edp_panel_off(intel_dp); -+ -+ intel_display_power_put_unchecked(dev_priv, -+ dig_port->ddi_io_power_domain); -+ -+ intel_ddi_clk_disable(encoder); -+} -+ -+static void intel_ddi_post_disable_hdmi(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_digital_port *dig_port = enc_to_dig_port(&encoder->base); -+ struct intel_hdmi *intel_hdmi = &dig_port->hdmi; -+ -+ dig_port->set_infoframes(encoder, false, -+ old_crtc_state, old_conn_state); -+ -+ intel_ddi_disable_pipe_clock(old_crtc_state); -+ -+ intel_disable_ddi_buf(encoder, old_crtc_state); -+ -+ intel_display_power_put_unchecked(dev_priv, -+ dig_port->ddi_io_power_domain); -+ -+ intel_ddi_clk_disable(encoder); -+ -+ intel_dp_dual_mode_set_tmds_output(intel_hdmi, false); -+} -+ -+static void intel_ddi_post_disable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ -+ /* -+ * When called from DP MST code: -+ * - old_conn_state will be NULL -+ * - encoder will be the main encoder (ie. mst->primary) -+ * - the main connector associated with this port -+ * won't be active or linked to a crtc -+ * - old_crtc_state will be the state of the last stream to -+ * be deactivated on this port, and it may not be the same -+ * stream that was activated last, but each stream -+ * should have a state that is identical when it comes to -+ * the DP link parameteres -+ */ -+ -+ if (intel_crtc_has_type(old_crtc_state, INTEL_OUTPUT_HDMI)) -+ intel_ddi_post_disable_hdmi(encoder, -+ old_crtc_state, old_conn_state); -+ else -+ intel_ddi_post_disable_dp(encoder, -+ old_crtc_state, old_conn_state); -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ icl_unmap_plls_to_ports(encoder); -+} -+ -+void intel_ddi_fdi_post_disable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ u32 val; -+ -+ /* -+ * Bspec lists this as both step 13 (before DDI_BUF_CTL disable) -+ * and step 18 (after clearing PORT_CLK_SEL). Based on a BUN, -+ * step 13 is the correct place for it. Step 18 is where it was -+ * originally before the BUN. -+ */ -+ val = I915_READ(FDI_RX_CTL(PIPE_A)); -+ val &= ~FDI_RX_ENABLE; -+ I915_WRITE(FDI_RX_CTL(PIPE_A), val); -+ -+ intel_disable_ddi_buf(encoder, old_crtc_state); -+ intel_ddi_clk_disable(encoder); -+ -+ val = I915_READ(FDI_RX_MISC(PIPE_A)); -+ val &= ~(FDI_RX_PWRDN_LANE1_MASK | FDI_RX_PWRDN_LANE0_MASK); -+ val |= FDI_RX_PWRDN_LANE1_VAL(2) | FDI_RX_PWRDN_LANE0_VAL(2); -+ I915_WRITE(FDI_RX_MISC(PIPE_A), val); -+ -+ val = I915_READ(FDI_RX_CTL(PIPE_A)); -+ val &= ~FDI_PCDCLK; -+ I915_WRITE(FDI_RX_CTL(PIPE_A), val); -+ -+ val = I915_READ(FDI_RX_CTL(PIPE_A)); -+ val &= ~FDI_RX_PLL_ENABLE; -+ I915_WRITE(FDI_RX_CTL(PIPE_A), val); -+} -+ -+static void intel_enable_ddi_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ enum port port = encoder->port; -+ -+ if (port == PORT_A && INTEL_GEN(dev_priv) < 9) -+ intel_dp_stop_link_train(intel_dp); -+ -+ intel_edp_backlight_on(crtc_state, conn_state); -+ intel_psr_enable(intel_dp, crtc_state); -+ intel_edp_drrs_enable(intel_dp, crtc_state); -+ -+ if (crtc_state->has_audio) -+ intel_audio_codec_enable(encoder, crtc_state, conn_state); -+} -+ -+static i915_reg_t -+gen9_chicken_trans_reg_by_port(struct drm_i915_private *dev_priv, -+ enum port port) -+{ -+ static const i915_reg_t regs[] = { -+ [PORT_A] = CHICKEN_TRANS_EDP, -+ [PORT_B] = CHICKEN_TRANS_A, -+ [PORT_C] = CHICKEN_TRANS_B, -+ [PORT_D] = CHICKEN_TRANS_C, -+ [PORT_E] = CHICKEN_TRANS_A, -+ }; -+ -+ WARN_ON(INTEL_GEN(dev_priv) < 9); -+ -+ if (WARN_ON(port < PORT_A || port > PORT_E)) -+ port = PORT_A; -+ -+ return regs[port]; -+} -+ -+static void intel_enable_ddi_hdmi(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_digital_port *dig_port = enc_to_dig_port(&encoder->base); -+ struct drm_connector *connector = conn_state->connector; -+ enum port port = encoder->port; -+ -+ if (!intel_hdmi_handle_sink_scrambling(encoder, connector, -+ crtc_state->hdmi_high_tmds_clock_ratio, -+ crtc_state->hdmi_scrambling)) -+ DRM_ERROR("[CONNECTOR:%d:%s] Failed to configure sink scrambling/TMDS bit clock ratio\n", -+ connector->base.id, connector->name); -+ -+ /* Display WA #1143: skl,kbl,cfl */ -+ if (IS_GEN9_BC(dev_priv)) { -+ /* -+ * For some reason these chicken bits have been -+ * stuffed into a transcoder register, event though -+ * the bits affect a specific DDI port rather than -+ * a specific transcoder. -+ */ -+ i915_reg_t reg = gen9_chicken_trans_reg_by_port(dev_priv, port); -+ u32 val; -+ -+ val = I915_READ(reg); -+ -+ if (port == PORT_E) -+ val |= DDIE_TRAINING_OVERRIDE_ENABLE | -+ DDIE_TRAINING_OVERRIDE_VALUE; -+ else -+ val |= DDI_TRAINING_OVERRIDE_ENABLE | -+ DDI_TRAINING_OVERRIDE_VALUE; -+ -+ I915_WRITE(reg, val); -+ POSTING_READ(reg); -+ -+ udelay(1); -+ -+ if (port == PORT_E) -+ val &= ~(DDIE_TRAINING_OVERRIDE_ENABLE | -+ DDIE_TRAINING_OVERRIDE_VALUE); -+ else -+ val &= ~(DDI_TRAINING_OVERRIDE_ENABLE | -+ DDI_TRAINING_OVERRIDE_VALUE); -+ -+ I915_WRITE(reg, val); -+ } -+ -+ /* In HDMI/DVI mode, the port width, and swing/emphasis values -+ * are ignored so nothing special needs to be done besides -+ * enabling the port. -+ */ -+ I915_WRITE(DDI_BUF_CTL(port), -+ dig_port->saved_port_bits | DDI_BUF_CTL_ENABLE); -+ -+ if (crtc_state->has_audio) -+ intel_audio_codec_enable(encoder, crtc_state, conn_state); -+} -+ -+static void intel_enable_ddi(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) -+ intel_enable_ddi_hdmi(encoder, crtc_state, conn_state); -+ else -+ intel_enable_ddi_dp(encoder, crtc_state, conn_state); -+ -+ /* Enable hdcp if it's desired */ -+ if (conn_state->content_protection == -+ DRM_MODE_CONTENT_PROTECTION_DESIRED) -+ intel_hdcp_enable(to_intel_connector(conn_state->connector)); -+} -+ -+static void intel_disable_ddi_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ -+ intel_dp->link_trained = false; -+ -+ if (old_crtc_state->has_audio) -+ intel_audio_codec_disable(encoder, -+ old_crtc_state, old_conn_state); -+ -+ intel_edp_drrs_disable(intel_dp, old_crtc_state); -+ intel_psr_disable(intel_dp, old_crtc_state); -+ intel_edp_backlight_off(old_conn_state); -+ /* Disable the decompression in DP Sink */ -+ intel_dp_sink_set_decompression_state(intel_dp, old_crtc_state, -+ false); -+} -+ -+static void intel_disable_ddi_hdmi(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct drm_connector *connector = old_conn_state->connector; -+ -+ if (old_crtc_state->has_audio) -+ intel_audio_codec_disable(encoder, -+ old_crtc_state, old_conn_state); -+ -+ if (!intel_hdmi_handle_sink_scrambling(encoder, connector, -+ false, false)) -+ DRM_DEBUG_KMS("[CONNECTOR:%d:%s] Failed to reset sink scrambling/TMDS bit clock ratio\n", -+ connector->base.id, connector->name); -+} -+ -+static void intel_disable_ddi(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ intel_hdcp_disable(to_intel_connector(old_conn_state->connector)); -+ -+ if (intel_crtc_has_type(old_crtc_state, INTEL_OUTPUT_HDMI)) -+ intel_disable_ddi_hdmi(encoder, old_crtc_state, old_conn_state); -+ else -+ intel_disable_ddi_dp(encoder, old_crtc_state, old_conn_state); -+} -+ -+static void intel_ddi_update_pipe_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ -+ intel_ddi_set_pipe_settings(crtc_state); -+ -+ intel_psr_update(intel_dp, crtc_state); -+ intel_edp_drrs_enable(intel_dp, crtc_state); -+ -+ intel_panel_update_backlight(encoder, crtc_state, conn_state); -+} -+ -+static void intel_ddi_update_pipe(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) -+ intel_ddi_update_pipe_dp(encoder, crtc_state, conn_state); -+ -+ if (conn_state->content_protection == -+ DRM_MODE_CONTENT_PROTECTION_DESIRED) -+ intel_hdcp_enable(to_intel_connector(conn_state->connector)); -+ else if (conn_state->content_protection == -+ DRM_MODE_CONTENT_PROTECTION_UNDESIRED) -+ intel_hdcp_disable(to_intel_connector(conn_state->connector)); -+} -+ -+static void intel_ddi_set_fia_lane_count(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config, -+ enum port port) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_digital_port *dig_port = enc_to_dig_port(&encoder->base); -+ enum tc_port tc_port = intel_port_to_tc(dev_priv, port); -+ u32 val = I915_READ(PORT_TX_DFLEXDPMLE1); -+ bool lane_reversal = dig_port->saved_port_bits & DDI_BUF_PORT_REVERSAL; -+ -+ val &= ~DFLEXDPMLE1_DPMLETC_MASK(tc_port); -+ switch (pipe_config->lane_count) { -+ case 1: -+ val |= (lane_reversal) ? DFLEXDPMLE1_DPMLETC_ML3(tc_port) : -+ DFLEXDPMLE1_DPMLETC_ML0(tc_port); -+ break; -+ case 2: -+ val |= (lane_reversal) ? DFLEXDPMLE1_DPMLETC_ML3_2(tc_port) : -+ DFLEXDPMLE1_DPMLETC_ML1_0(tc_port); -+ break; -+ case 4: -+ val |= DFLEXDPMLE1_DPMLETC_ML3_0(tc_port); -+ break; -+ default: -+ MISSING_CASE(pipe_config->lane_count); -+ } -+ I915_WRITE(PORT_TX_DFLEXDPMLE1, val); -+} -+ -+static void -+intel_ddi_pre_pll_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_digital_port *dig_port = enc_to_dig_port(&encoder->base); -+ enum port port = encoder->port; -+ -+ if (intel_crtc_has_dp_encoder(crtc_state) || -+ intel_port_is_tc(dev_priv, encoder->port)) -+ intel_display_power_get(dev_priv, -+ intel_ddi_main_link_aux_domain(dig_port)); -+ -+ if (IS_GEN9_LP(dev_priv)) -+ bxt_ddi_phy_set_lane_optim_mask(encoder, -+ crtc_state->lane_lat_optim_mask); -+ -+ /* -+ * Program the lane count for static/dynamic connections on Type-C ports. -+ * Skip this step for TBT. -+ */ -+ if (dig_port->tc_type == TC_PORT_UNKNOWN || -+ dig_port->tc_type == TC_PORT_TBT) -+ return; -+ -+ intel_ddi_set_fia_lane_count(encoder, crtc_state, port); -+} -+ -+static void -+intel_ddi_post_pll_disable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_digital_port *dig_port = enc_to_dig_port(&encoder->base); -+ -+ if (intel_crtc_has_dp_encoder(crtc_state) || -+ intel_port_is_tc(dev_priv, encoder->port)) -+ intel_display_power_put_unchecked(dev_priv, -+ intel_ddi_main_link_aux_domain(dig_port)); -+} -+ -+void intel_ddi_prepare_link_retrain(struct intel_dp *intel_dp) -+{ -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = -+ to_i915(intel_dig_port->base.base.dev); -+ enum port port = intel_dig_port->base.port; -+ u32 val; -+ bool wait = false; -+ -+ if (I915_READ(DP_TP_CTL(port)) & DP_TP_CTL_ENABLE) { -+ val = I915_READ(DDI_BUF_CTL(port)); -+ if (val & DDI_BUF_CTL_ENABLE) { -+ val &= ~DDI_BUF_CTL_ENABLE; -+ I915_WRITE(DDI_BUF_CTL(port), val); -+ wait = true; -+ } -+ -+ val = I915_READ(DP_TP_CTL(port)); -+ val &= ~(DP_TP_CTL_ENABLE | DP_TP_CTL_LINK_TRAIN_MASK); -+ val |= DP_TP_CTL_LINK_TRAIN_PAT1; -+ I915_WRITE(DP_TP_CTL(port), val); -+ POSTING_READ(DP_TP_CTL(port)); -+ -+ if (wait) -+ intel_wait_ddi_buf_idle(dev_priv, port); -+ } -+ -+ val = DP_TP_CTL_ENABLE | -+ DP_TP_CTL_LINK_TRAIN_PAT1 | DP_TP_CTL_SCRAMBLE_DISABLE; -+ if (intel_dp->link_mst) -+ val |= DP_TP_CTL_MODE_MST; -+ else { -+ val |= DP_TP_CTL_MODE_SST; -+ if (drm_dp_enhanced_frame_cap(intel_dp->dpcd)) -+ val |= DP_TP_CTL_ENHANCED_FRAME_ENABLE; -+ } -+ I915_WRITE(DP_TP_CTL(port), val); -+ POSTING_READ(DP_TP_CTL(port)); -+ -+ intel_dp->DP |= DDI_BUF_CTL_ENABLE; -+ I915_WRITE(DDI_BUF_CTL(port), intel_dp->DP); -+ POSTING_READ(DDI_BUF_CTL(port)); -+ -+ udelay(600); -+} -+ -+static bool intel_ddi_is_audio_enabled(struct drm_i915_private *dev_priv, -+ enum transcoder cpu_transcoder) -+{ -+ if (cpu_transcoder == TRANSCODER_EDP) -+ return false; -+ -+ if (!intel_display_power_is_enabled(dev_priv, POWER_DOMAIN_AUDIO)) -+ return false; -+ -+ return I915_READ(HSW_AUD_PIN_ELD_CP_VLD) & -+ AUDIO_OUTPUT_ENABLE(cpu_transcoder); -+} -+ -+void intel_ddi_compute_min_voltage_level(struct drm_i915_private *dev_priv, -+ struct intel_crtc_state *crtc_state) -+{ -+ if (INTEL_GEN(dev_priv) >= 11 && crtc_state->port_clock > 594000) -+ crtc_state->min_voltage_level = 1; -+ else if (IS_CANNONLAKE(dev_priv) && crtc_state->port_clock > 594000) -+ crtc_state->min_voltage_level = 2; -+} -+ -+void intel_ddi_get_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(pipe_config->base.crtc); -+ enum transcoder cpu_transcoder = pipe_config->cpu_transcoder; -+ struct intel_digital_port *intel_dig_port; -+ u32 temp, flags = 0; -+ -+ /* XXX: DSI transcoder paranoia */ -+ if (WARN_ON(transcoder_is_dsi(cpu_transcoder))) -+ return; -+ -+ temp = I915_READ(TRANS_DDI_FUNC_CTL(cpu_transcoder)); -+ if (temp & TRANS_DDI_PHSYNC) -+ flags |= DRM_MODE_FLAG_PHSYNC; -+ else -+ flags |= DRM_MODE_FLAG_NHSYNC; -+ if (temp & TRANS_DDI_PVSYNC) -+ flags |= DRM_MODE_FLAG_PVSYNC; -+ else -+ flags |= DRM_MODE_FLAG_NVSYNC; -+ -+ pipe_config->base.adjusted_mode.flags |= flags; -+ -+ switch (temp & TRANS_DDI_BPC_MASK) { -+ case TRANS_DDI_BPC_6: -+ pipe_config->pipe_bpp = 18; -+ break; -+ case TRANS_DDI_BPC_8: -+ pipe_config->pipe_bpp = 24; -+ break; -+ case TRANS_DDI_BPC_10: -+ pipe_config->pipe_bpp = 30; -+ break; -+ case TRANS_DDI_BPC_12: -+ pipe_config->pipe_bpp = 36; -+ break; -+ default: -+ break; -+ } -+ -+ switch (temp & TRANS_DDI_MODE_SELECT_MASK) { -+ case TRANS_DDI_MODE_SELECT_HDMI: -+ pipe_config->has_hdmi_sink = true; -+ intel_dig_port = enc_to_dig_port(&encoder->base); -+ -+ pipe_config->infoframes.enable |= -+ intel_hdmi_infoframes_enabled(encoder, pipe_config); -+ -+ if (pipe_config->infoframes.enable) -+ pipe_config->has_infoframe = true; -+ -+ if (temp & TRANS_DDI_HDMI_SCRAMBLING) -+ pipe_config->hdmi_scrambling = true; -+ if (temp & TRANS_DDI_HIGH_TMDS_CHAR_RATE) -+ pipe_config->hdmi_high_tmds_clock_ratio = true; -+ /* fall through */ -+ case TRANS_DDI_MODE_SELECT_DVI: -+ pipe_config->output_types |= BIT(INTEL_OUTPUT_HDMI); -+ pipe_config->lane_count = 4; -+ break; -+ case TRANS_DDI_MODE_SELECT_FDI: -+ pipe_config->output_types |= BIT(INTEL_OUTPUT_ANALOG); -+ break; -+ case TRANS_DDI_MODE_SELECT_DP_SST: -+ if (encoder->type == INTEL_OUTPUT_EDP) -+ pipe_config->output_types |= BIT(INTEL_OUTPUT_EDP); -+ else -+ pipe_config->output_types |= BIT(INTEL_OUTPUT_DP); -+ pipe_config->lane_count = -+ ((temp & DDI_PORT_WIDTH_MASK) >> DDI_PORT_WIDTH_SHIFT) + 1; -+ intel_dp_get_m_n(intel_crtc, pipe_config); -+ break; -+ case TRANS_DDI_MODE_SELECT_DP_MST: -+ pipe_config->output_types |= BIT(INTEL_OUTPUT_DP_MST); -+ pipe_config->lane_count = -+ ((temp & DDI_PORT_WIDTH_MASK) >> DDI_PORT_WIDTH_SHIFT) + 1; -+ intel_dp_get_m_n(intel_crtc, pipe_config); -+ break; -+ default: -+ break; -+ } -+ -+ pipe_config->has_audio = -+ intel_ddi_is_audio_enabled(dev_priv, cpu_transcoder); -+ -+ if (encoder->type == INTEL_OUTPUT_EDP && dev_priv->vbt.edp.bpp && -+ pipe_config->pipe_bpp > dev_priv->vbt.edp.bpp) { -+ /* -+ * This is a big fat ugly hack. -+ * -+ * Some machines in UEFI boot mode provide us a VBT that has 18 -+ * bpp and 1.62 GHz link bandwidth for eDP, which for reasons -+ * unknown we fail to light up. Yet the same BIOS boots up with -+ * 24 bpp and 2.7 GHz link. Use the same bpp as the BIOS uses as -+ * max, not what it tells us to use. -+ * -+ * Note: This will still be broken if the eDP panel is not lit -+ * up by the BIOS, and thus we can't get the mode at module -+ * load. -+ */ -+ DRM_DEBUG_KMS("pipe has %d bpp for eDP panel, overriding BIOS-provided max %d bpp\n", -+ pipe_config->pipe_bpp, dev_priv->vbt.edp.bpp); -+ dev_priv->vbt.edp.bpp = pipe_config->pipe_bpp; -+ } -+ -+ intel_ddi_clock_get(encoder, pipe_config); -+ -+ if (IS_GEN9_LP(dev_priv)) -+ pipe_config->lane_lat_optim_mask = -+ bxt_ddi_phy_get_lane_lat_optim_mask(encoder); -+ -+ intel_ddi_compute_min_voltage_level(dev_priv, pipe_config); -+ -+ intel_hdmi_read_gcp_infoframe(encoder, pipe_config); -+ -+ intel_read_infoframe(encoder, pipe_config, -+ HDMI_INFOFRAME_TYPE_AVI, -+ &pipe_config->infoframes.avi); -+ intel_read_infoframe(encoder, pipe_config, -+ HDMI_INFOFRAME_TYPE_SPD, -+ &pipe_config->infoframes.spd); -+ intel_read_infoframe(encoder, pipe_config, -+ HDMI_INFOFRAME_TYPE_VENDOR, -+ &pipe_config->infoframes.hdmi); -+} -+ -+static enum intel_output_type -+intel_ddi_compute_output_type(struct intel_encoder *encoder, -+ struct intel_crtc_state *crtc_state, -+ struct drm_connector_state *conn_state) -+{ -+ switch (conn_state->connector->connector_type) { -+ case DRM_MODE_CONNECTOR_HDMIA: -+ return INTEL_OUTPUT_HDMI; -+ case DRM_MODE_CONNECTOR_eDP: -+ return INTEL_OUTPUT_EDP; -+ case DRM_MODE_CONNECTOR_DisplayPort: -+ return INTEL_OUTPUT_DP; -+ default: -+ MISSING_CASE(conn_state->connector->connector_type); -+ return INTEL_OUTPUT_UNUSED; -+ } -+} -+ -+static int intel_ddi_compute_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config, -+ struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ int ret; -+ -+ if (HAS_TRANSCODER_EDP(dev_priv) && port == PORT_A) -+ pipe_config->cpu_transcoder = TRANSCODER_EDP; -+ -+ if (intel_crtc_has_type(pipe_config, INTEL_OUTPUT_HDMI)) -+ ret = intel_hdmi_compute_config(encoder, pipe_config, conn_state); -+ else -+ ret = intel_dp_compute_config(encoder, pipe_config, conn_state); -+ if (ret) -+ return ret; -+ -+ if (IS_GEN9_LP(dev_priv)) -+ pipe_config->lane_lat_optim_mask = -+ bxt_ddi_phy_calc_lane_lat_optim_mask(pipe_config->lane_count); -+ -+ intel_ddi_compute_min_voltage_level(dev_priv, pipe_config); -+ -+ return 0; -+ -+} -+ -+static void intel_ddi_encoder_suspend(struct intel_encoder *encoder) -+{ -+ struct intel_digital_port *dig_port = enc_to_dig_port(&encoder->base); -+ struct drm_i915_private *i915 = to_i915(encoder->base.dev); -+ -+ intel_dp_encoder_suspend(encoder); -+ -+ /* -+ * TODO: disconnect also from USB DP alternate mode once we have a -+ * way to handle the modeset restore in that mode during resume -+ * even if the sink has disappeared while being suspended. -+ */ -+ if (dig_port->tc_legacy_port) -+ icl_tc_phy_disconnect(i915, dig_port); -+} -+ -+static void intel_ddi_encoder_reset(struct drm_encoder *drm_encoder) -+{ -+ struct intel_digital_port *dig_port = enc_to_dig_port(drm_encoder); -+ struct drm_i915_private *i915 = to_i915(drm_encoder->dev); -+ -+ if (intel_port_is_tc(i915, dig_port->base.port)) -+ intel_digital_port_connected(&dig_port->base); -+ -+ intel_dp_encoder_reset(drm_encoder); -+} -+ -+static void intel_ddi_encoder_destroy(struct drm_encoder *encoder) -+{ -+ struct intel_digital_port *dig_port = enc_to_dig_port(encoder); -+ struct drm_i915_private *i915 = to_i915(encoder->dev); -+ -+ intel_dp_encoder_flush_work(encoder); -+ -+ if (intel_port_is_tc(i915, dig_port->base.port)) -+ icl_tc_phy_disconnect(i915, dig_port); -+ -+ drm_encoder_cleanup(encoder); -+ kfree(dig_port); -+} -+ -+static const struct drm_encoder_funcs intel_ddi_funcs = { -+ .reset = intel_ddi_encoder_reset, -+ .destroy = intel_ddi_encoder_destroy, -+}; -+ -+static struct intel_connector * -+intel_ddi_init_dp_connector(struct intel_digital_port *intel_dig_port) -+{ -+ struct intel_connector *connector; -+ enum port port = intel_dig_port->base.port; -+ -+ connector = intel_connector_alloc(); -+ if (!connector) -+ return NULL; -+ -+ intel_dig_port->dp.output_reg = DDI_BUF_CTL(port); -+ if (!intel_dp_init_connector(intel_dig_port, connector)) { -+ kfree(connector); -+ return NULL; -+ } -+ -+ return connector; -+} -+ -+static int modeset_pipe(struct drm_crtc *crtc, -+ struct drm_modeset_acquire_ctx *ctx) -+{ -+ struct drm_atomic_state *state; -+ struct drm_crtc_state *crtc_state; -+ int ret; -+ -+ state = drm_atomic_state_alloc(crtc->dev); -+ if (!state) -+ return -ENOMEM; -+ -+ state->acquire_ctx = ctx; -+ -+ crtc_state = drm_atomic_get_crtc_state(state, crtc); -+ if (IS_ERR(crtc_state)) { -+ ret = PTR_ERR(crtc_state); -+ goto out; -+ } -+ -+ crtc_state->connectors_changed = true; -+ -+ ret = drm_atomic_commit(state); -+out: -+ drm_atomic_state_put(state); -+ -+ return ret; -+} -+ -+static int intel_hdmi_reset_link(struct intel_encoder *encoder, -+ struct drm_modeset_acquire_ctx *ctx) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_hdmi *hdmi = enc_to_intel_hdmi(&encoder->base); -+ struct intel_connector *connector = hdmi->attached_connector; -+ struct i2c_adapter *adapter = -+ intel_gmbus_get_adapter(dev_priv, hdmi->ddc_bus); -+ struct drm_connector_state *conn_state; -+ struct intel_crtc_state *crtc_state; -+ struct intel_crtc *crtc; -+ u8 config; -+ int ret; -+ -+ if (!connector || connector->base.status != connector_status_connected) -+ return 0; -+ -+ ret = drm_modeset_lock(&dev_priv->drm.mode_config.connection_mutex, -+ ctx); -+ if (ret) -+ return ret; -+ -+ conn_state = connector->base.state; -+ -+ crtc = to_intel_crtc(conn_state->crtc); -+ if (!crtc) -+ return 0; -+ -+ ret = drm_modeset_lock(&crtc->base.mutex, ctx); -+ if (ret) -+ return ret; -+ -+ crtc_state = to_intel_crtc_state(crtc->base.state); -+ -+ WARN_ON(!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)); -+ -+ if (!crtc_state->base.active) -+ return 0; -+ -+ if (!crtc_state->hdmi_high_tmds_clock_ratio && -+ !crtc_state->hdmi_scrambling) -+ return 0; -+ -+ if (conn_state->commit && -+ !try_wait_for_completion(&conn_state->commit->hw_done)) -+ return 0; -+ -+ ret = drm_scdc_readb(adapter, SCDC_TMDS_CONFIG, &config); -+ if (ret < 0) { -+ DRM_ERROR("Failed to read TMDS config: %d\n", ret); -+ return 0; -+ } -+ -+ if (!!(config & SCDC_TMDS_BIT_CLOCK_RATIO_BY_40) == -+ crtc_state->hdmi_high_tmds_clock_ratio && -+ !!(config & SCDC_SCRAMBLING_ENABLE) == -+ crtc_state->hdmi_scrambling) -+ return 0; -+ -+ /* -+ * HDMI 2.0 says that one should not send scrambled data -+ * prior to configuring the sink scrambling, and that -+ * TMDS clock/data transmission should be suspended when -+ * changing the TMDS clock rate in the sink. So let's -+ * just do a full modeset here, even though some sinks -+ * would be perfectly happy if were to just reconfigure -+ * the SCDC settings on the fly. -+ */ -+ return modeset_pipe(&crtc->base, ctx); -+} -+ -+static bool intel_ddi_hotplug(struct intel_encoder *encoder, -+ struct intel_connector *connector) -+{ -+ struct drm_modeset_acquire_ctx ctx; -+ bool changed; -+ int ret; -+ -+ changed = intel_encoder_hotplug(encoder, connector); -+ -+ drm_modeset_acquire_init(&ctx, 0); -+ -+ for (;;) { -+ if (connector->base.connector_type == DRM_MODE_CONNECTOR_HDMIA) -+ ret = intel_hdmi_reset_link(encoder, &ctx); -+ else -+ ret = intel_dp_retrain_link(encoder, &ctx); -+ -+ if (ret == -EDEADLK) { -+ drm_modeset_backoff(&ctx); -+ continue; -+ } -+ -+ break; -+ } -+ -+ drm_modeset_drop_locks(&ctx); -+ drm_modeset_acquire_fini(&ctx); -+ WARN(ret, "Acquiring modeset locks failed with %i\n", ret); -+ -+ return changed; -+} -+ -+static struct intel_connector * -+intel_ddi_init_hdmi_connector(struct intel_digital_port *intel_dig_port) -+{ -+ struct intel_connector *connector; -+ enum port port = intel_dig_port->base.port; -+ -+ connector = intel_connector_alloc(); -+ if (!connector) -+ return NULL; -+ -+ intel_dig_port->hdmi.hdmi_reg = DDI_BUF_CTL(port); -+ intel_hdmi_init_connector(intel_dig_port, connector); -+ -+ return connector; -+} -+ -+static bool intel_ddi_a_force_4_lanes(struct intel_digital_port *dport) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dport->base.base.dev); -+ -+ if (dport->base.port != PORT_A) -+ return false; -+ -+ if (dport->saved_port_bits & DDI_A_4_LANES) -+ return false; -+ -+ /* Broxton/Geminilake: Bspec says that DDI_A_4_LANES is the only -+ * supported configuration -+ */ -+ if (IS_GEN9_LP(dev_priv)) -+ return true; -+ -+ /* Cannonlake: Most of SKUs don't support DDI_E, and the only -+ * one who does also have a full A/E split called -+ * DDI_F what makes DDI_E useless. However for this -+ * case let's trust VBT info. -+ */ -+ if (IS_CANNONLAKE(dev_priv) && -+ !intel_bios_is_port_present(dev_priv, PORT_E)) -+ return true; -+ -+ return false; -+} -+ -+static int -+intel_ddi_max_lanes(struct intel_digital_port *intel_dport) -+{ -+ struct drm_i915_private *dev_priv = to_i915(intel_dport->base.base.dev); -+ enum port port = intel_dport->base.port; -+ int max_lanes = 4; -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ return max_lanes; -+ -+ if (port == PORT_A || port == PORT_E) { -+ if (I915_READ(DDI_BUF_CTL(PORT_A)) & DDI_A_4_LANES) -+ max_lanes = port == PORT_A ? 4 : 0; -+ else -+ /* Both A and E share 2 lanes */ -+ max_lanes = 2; -+ } -+ -+ /* -+ * Some BIOS might fail to set this bit on port A if eDP -+ * wasn't lit up at boot. Force this bit set when needed -+ * so we use the proper lane count for our calculations. -+ */ -+ if (intel_ddi_a_force_4_lanes(intel_dport)) { -+ DRM_DEBUG_KMS("Forcing DDI_A_4_LANES for port A\n"); -+ intel_dport->saved_port_bits |= DDI_A_4_LANES; -+ max_lanes = 4; -+ } -+ -+ return max_lanes; -+} -+ -+void intel_ddi_init(struct drm_i915_private *dev_priv, enum port port) -+{ -+ struct ddi_vbt_port_info *port_info = -+ &dev_priv->vbt.ddi_port_info[port]; -+ struct intel_digital_port *intel_dig_port; -+ struct intel_encoder *intel_encoder; -+ struct drm_encoder *encoder; -+ bool init_hdmi, init_dp, init_lspcon = false; -+ enum pipe pipe; -+ -+ init_hdmi = port_info->supports_dvi || port_info->supports_hdmi; -+ init_dp = port_info->supports_dp; -+ -+ if (intel_bios_is_lspcon_present(dev_priv, port)) { -+ /* -+ * Lspcon device needs to be driven with DP connector -+ * with special detection sequence. So make sure DP -+ * is initialized before lspcon. -+ */ -+ init_dp = true; -+ init_lspcon = true; -+ init_hdmi = false; -+ DRM_DEBUG_KMS("VBT says port %c has lspcon\n", port_name(port)); -+ } -+ -+ if (!init_dp && !init_hdmi) { -+ DRM_DEBUG_KMS("VBT says port %c is not DVI/HDMI/DP compatible, respect it\n", -+ port_name(port)); -+ return; -+ } -+ -+ intel_dig_port = kzalloc(sizeof(*intel_dig_port), GFP_KERNEL); -+ if (!intel_dig_port) -+ return; -+ -+ intel_encoder = &intel_dig_port->base; -+ encoder = &intel_encoder->base; -+ -+ drm_encoder_init(&dev_priv->drm, encoder, &intel_ddi_funcs, -+ DRM_MODE_ENCODER_TMDS, "DDI %c", port_name(port)); -+ -+ intel_encoder->hotplug = intel_ddi_hotplug; -+ intel_encoder->compute_output_type = intel_ddi_compute_output_type; -+ intel_encoder->compute_config = intel_ddi_compute_config; -+ intel_encoder->enable = intel_enable_ddi; -+ intel_encoder->pre_pll_enable = intel_ddi_pre_pll_enable; -+ intel_encoder->post_pll_disable = intel_ddi_post_pll_disable; -+ intel_encoder->pre_enable = intel_ddi_pre_enable; -+ intel_encoder->disable = intel_disable_ddi; -+ intel_encoder->post_disable = intel_ddi_post_disable; -+ intel_encoder->update_pipe = intel_ddi_update_pipe; -+ intel_encoder->get_hw_state = intel_ddi_get_hw_state; -+ intel_encoder->get_config = intel_ddi_get_config; -+ intel_encoder->suspend = intel_ddi_encoder_suspend; -+ intel_encoder->get_power_domains = intel_ddi_get_power_domains; -+ intel_encoder->type = INTEL_OUTPUT_DDI; -+ intel_encoder->power_domain = intel_port_to_power_domain(port); -+ intel_encoder->port = port; -+ intel_encoder->cloneable = 0; -+ for_each_pipe(dev_priv, pipe) -+ intel_encoder->crtc_mask |= BIT(pipe); -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ intel_dig_port->saved_port_bits = I915_READ(DDI_BUF_CTL(port)) & -+ DDI_BUF_PORT_REVERSAL; -+ else -+ intel_dig_port->saved_port_bits = I915_READ(DDI_BUF_CTL(port)) & -+ (DDI_BUF_PORT_REVERSAL | DDI_A_4_LANES); -+ intel_dig_port->dp.output_reg = INVALID_MMIO_REG; -+ intel_dig_port->max_lanes = intel_ddi_max_lanes(intel_dig_port); -+ intel_dig_port->aux_ch = intel_bios_port_aux_ch(dev_priv, port); -+ -+ intel_dig_port->tc_legacy_port = intel_port_is_tc(dev_priv, port) && -+ !port_info->supports_typec_usb && -+ !port_info->supports_tbt; -+ -+ switch (port) { -+ case PORT_A: -+ intel_dig_port->ddi_io_power_domain = -+ POWER_DOMAIN_PORT_DDI_A_IO; -+ break; -+ case PORT_B: -+ intel_dig_port->ddi_io_power_domain = -+ POWER_DOMAIN_PORT_DDI_B_IO; -+ break; -+ case PORT_C: -+ intel_dig_port->ddi_io_power_domain = -+ POWER_DOMAIN_PORT_DDI_C_IO; -+ break; -+ case PORT_D: -+ intel_dig_port->ddi_io_power_domain = -+ POWER_DOMAIN_PORT_DDI_D_IO; -+ break; -+ case PORT_E: -+ intel_dig_port->ddi_io_power_domain = -+ POWER_DOMAIN_PORT_DDI_E_IO; -+ break; -+ case PORT_F: -+ intel_dig_port->ddi_io_power_domain = -+ POWER_DOMAIN_PORT_DDI_F_IO; -+ break; -+ default: -+ MISSING_CASE(port); -+ } -+ -+ if (init_dp) { -+ if (!intel_ddi_init_dp_connector(intel_dig_port)) -+ goto err; -+ -+ intel_dig_port->hpd_pulse = intel_dp_hpd_pulse; -+ } -+ -+ /* In theory we don't need the encoder->type check, but leave it just in -+ * case we have some really bad VBTs... */ -+ if (intel_encoder->type != INTEL_OUTPUT_EDP && init_hdmi) { -+ if (!intel_ddi_init_hdmi_connector(intel_dig_port)) -+ goto err; -+ } -+ -+ if (init_lspcon) { -+ if (lspcon_init(intel_dig_port)) -+ /* TODO: handle hdmi info frame part */ -+ DRM_DEBUG_KMS("LSPCON init success on port %c\n", -+ port_name(port)); -+ else -+ /* -+ * LSPCON init faied, but DP init was success, so -+ * lets try to drive as DP++ port. -+ */ -+ DRM_ERROR("LSPCON init failed on port %c\n", -+ port_name(port)); -+ } -+ -+ intel_infoframe_init(intel_dig_port); -+ -+ if (intel_port_is_tc(dev_priv, port)) -+ intel_digital_port_connected(intel_encoder); -+ -+ return; -+ -+err: -+ drm_encoder_cleanup(encoder); -+ kfree(intel_dig_port); -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_ddi.h b/drivers/gpu/drm/i915_legacy/intel_ddi.h -new file mode 100644 -index 000000000000..9cf69175942e ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_ddi.h -@@ -0,0 +1,53 @@ -+/* SPDX-License-Identifier: MIT */ -+/* -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef __INTEL_DDI_H__ -+#define __INTEL_DDI_H__ -+ -+#include -+ -+#include "intel_display.h" -+ -+struct drm_connector_state; -+struct drm_i915_private; -+struct intel_connector; -+struct intel_crtc; -+struct intel_crtc_state; -+struct intel_dp; -+struct intel_dpll_hw_state; -+struct intel_encoder; -+ -+void intel_ddi_fdi_post_disable(struct intel_encoder *intel_encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state); -+void hsw_fdi_link_train(struct intel_crtc *crtc, -+ const struct intel_crtc_state *crtc_state); -+void intel_ddi_init(struct drm_i915_private *dev_priv, enum port port); -+bool intel_ddi_get_hw_state(struct intel_encoder *encoder, enum pipe *pipe); -+void intel_ddi_enable_transcoder_func(const struct intel_crtc_state *crtc_state); -+void intel_ddi_disable_transcoder_func(const struct intel_crtc_state *crtc_state); -+void intel_ddi_enable_pipe_clock(const struct intel_crtc_state *crtc_state); -+void intel_ddi_disable_pipe_clock(const struct intel_crtc_state *crtc_state); -+void intel_ddi_set_pipe_settings(const struct intel_crtc_state *crtc_state); -+void intel_ddi_prepare_link_retrain(struct intel_dp *intel_dp); -+bool intel_ddi_connector_get_hw_state(struct intel_connector *intel_connector); -+void intel_ddi_get_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config); -+void intel_ddi_set_vc_payload_alloc(const struct intel_crtc_state *crtc_state, -+ bool state); -+void intel_ddi_compute_min_voltage_level(struct drm_i915_private *dev_priv, -+ struct intel_crtc_state *crtc_state); -+u32 bxt_signal_levels(struct intel_dp *intel_dp); -+u32 ddi_signal_levels(struct intel_dp *intel_dp); -+u8 intel_ddi_dp_voltage_max(struct intel_encoder *encoder); -+u8 intel_ddi_dp_pre_emphasis_max(struct intel_encoder *encoder, -+ u8 voltage_swing); -+int intel_ddi_toggle_hdcp_signalling(struct intel_encoder *intel_encoder, -+ bool enable); -+void icl_sanitize_encoder_pll_mapping(struct intel_encoder *encoder); -+int cnl_calc_wrpll_link(struct drm_i915_private *dev_priv, -+ struct intel_dpll_hw_state *state); -+ -+#endif /* __INTEL_DDI_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_device_info.c b/drivers/gpu/drm/i915_legacy/intel_device_info.c -new file mode 100644 -index 000000000000..6af480b95bc6 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_device_info.c -@@ -0,0 +1,1019 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include -+ -+#include "intel_device_info.h" -+#include "i915_drv.h" -+ -+#define PLATFORM_NAME(x) [INTEL_##x] = #x -+static const char * const platform_names[] = { -+ PLATFORM_NAME(I830), -+ PLATFORM_NAME(I845G), -+ PLATFORM_NAME(I85X), -+ PLATFORM_NAME(I865G), -+ PLATFORM_NAME(I915G), -+ PLATFORM_NAME(I915GM), -+ PLATFORM_NAME(I945G), -+ PLATFORM_NAME(I945GM), -+ PLATFORM_NAME(G33), -+ PLATFORM_NAME(PINEVIEW), -+ PLATFORM_NAME(I965G), -+ PLATFORM_NAME(I965GM), -+ PLATFORM_NAME(G45), -+ PLATFORM_NAME(GM45), -+ PLATFORM_NAME(IRONLAKE), -+ PLATFORM_NAME(SANDYBRIDGE), -+ PLATFORM_NAME(IVYBRIDGE), -+ PLATFORM_NAME(VALLEYVIEW), -+ PLATFORM_NAME(HASWELL), -+ PLATFORM_NAME(BROADWELL), -+ PLATFORM_NAME(CHERRYVIEW), -+ PLATFORM_NAME(SKYLAKE), -+ PLATFORM_NAME(BROXTON), -+ PLATFORM_NAME(KABYLAKE), -+ PLATFORM_NAME(GEMINILAKE), -+ PLATFORM_NAME(COFFEELAKE), -+ PLATFORM_NAME(CANNONLAKE), -+ PLATFORM_NAME(ICELAKE), -+ PLATFORM_NAME(ELKHARTLAKE), -+}; -+#undef PLATFORM_NAME -+ -+const char *intel_platform_name(enum intel_platform platform) -+{ -+ BUILD_BUG_ON(ARRAY_SIZE(platform_names) != INTEL_MAX_PLATFORMS); -+ -+ if (WARN_ON_ONCE(platform >= ARRAY_SIZE(platform_names) || -+ platform_names[platform] == NULL)) -+ return ""; -+ -+ return platform_names[platform]; -+} -+ -+void intel_device_info_dump_flags(const struct intel_device_info *info, -+ struct drm_printer *p) -+{ -+#define PRINT_FLAG(name) drm_printf(p, "%s: %s\n", #name, yesno(info->name)); -+ DEV_INFO_FOR_EACH_FLAG(PRINT_FLAG); -+#undef PRINT_FLAG -+ -+#define PRINT_FLAG(name) drm_printf(p, "%s: %s\n", #name, yesno(info->display.name)); -+ DEV_INFO_DISPLAY_FOR_EACH_FLAG(PRINT_FLAG); -+#undef PRINT_FLAG -+} -+ -+static void sseu_dump(const struct sseu_dev_info *sseu, struct drm_printer *p) -+{ -+ int s; -+ -+ drm_printf(p, "slice total: %u, mask=%04x\n", -+ hweight8(sseu->slice_mask), sseu->slice_mask); -+ drm_printf(p, "subslice total: %u\n", sseu_subslice_total(sseu)); -+ for (s = 0; s < sseu->max_slices; s++) { -+ drm_printf(p, "slice%d: %u subslices, mask=%04x\n", -+ s, hweight8(sseu->subslice_mask[s]), -+ sseu->subslice_mask[s]); -+ } -+ drm_printf(p, "EU total: %u\n", sseu->eu_total); -+ drm_printf(p, "EU per subslice: %u\n", sseu->eu_per_subslice); -+ drm_printf(p, "has slice power gating: %s\n", -+ yesno(sseu->has_slice_pg)); -+ drm_printf(p, "has subslice power gating: %s\n", -+ yesno(sseu->has_subslice_pg)); -+ drm_printf(p, "has EU power gating: %s\n", yesno(sseu->has_eu_pg)); -+} -+ -+void intel_device_info_dump_runtime(const struct intel_runtime_info *info, -+ struct drm_printer *p) -+{ -+ sseu_dump(&info->sseu, p); -+ -+ drm_printf(p, "CS timestamp frequency: %u kHz\n", -+ info->cs_timestamp_frequency_khz); -+} -+ -+void intel_device_info_dump_topology(const struct sseu_dev_info *sseu, -+ struct drm_printer *p) -+{ -+ int s, ss; -+ -+ if (sseu->max_slices == 0) { -+ drm_printf(p, "Unavailable\n"); -+ return; -+ } -+ -+ for (s = 0; s < sseu->max_slices; s++) { -+ drm_printf(p, "slice%d: %u subslice(s) (0x%hhx):\n", -+ s, hweight8(sseu->subslice_mask[s]), -+ sseu->subslice_mask[s]); -+ -+ for (ss = 0; ss < sseu->max_subslices; ss++) { -+ u16 enabled_eus = sseu_get_eus(sseu, s, ss); -+ -+ drm_printf(p, "\tsubslice%d: %u EUs (0x%hx)\n", -+ ss, hweight16(enabled_eus), enabled_eus); -+ } -+ } -+} -+ -+static u16 compute_eu_total(const struct sseu_dev_info *sseu) -+{ -+ u16 i, total = 0; -+ -+ for (i = 0; i < ARRAY_SIZE(sseu->eu_mask); i++) -+ total += hweight8(sseu->eu_mask[i]); -+ -+ return total; -+} -+ -+static void gen11_sseu_info_init(struct drm_i915_private *dev_priv) -+{ -+ struct sseu_dev_info *sseu = &RUNTIME_INFO(dev_priv)->sseu; -+ u8 s_en; -+ u32 ss_en, ss_en_mask; -+ u8 eu_en; -+ int s; -+ -+ if (IS_ELKHARTLAKE(dev_priv)) { -+ sseu->max_slices = 1; -+ sseu->max_subslices = 4; -+ sseu->max_eus_per_subslice = 8; -+ } else { -+ sseu->max_slices = 1; -+ sseu->max_subslices = 8; -+ sseu->max_eus_per_subslice = 8; -+ } -+ -+ s_en = I915_READ(GEN11_GT_SLICE_ENABLE) & GEN11_GT_S_ENA_MASK; -+ ss_en = ~I915_READ(GEN11_GT_SUBSLICE_DISABLE); -+ ss_en_mask = BIT(sseu->max_subslices) - 1; -+ eu_en = ~(I915_READ(GEN11_EU_DISABLE) & GEN11_EU_DIS_MASK); -+ -+ for (s = 0; s < sseu->max_slices; s++) { -+ if (s_en & BIT(s)) { -+ int ss_idx = sseu->max_subslices * s; -+ int ss; -+ -+ sseu->slice_mask |= BIT(s); -+ sseu->subslice_mask[s] = (ss_en >> ss_idx) & ss_en_mask; -+ for (ss = 0; ss < sseu->max_subslices; ss++) { -+ if (sseu->subslice_mask[s] & BIT(ss)) -+ sseu_set_eus(sseu, s, ss, eu_en); -+ } -+ } -+ } -+ sseu->eu_per_subslice = hweight8(eu_en); -+ sseu->eu_total = compute_eu_total(sseu); -+ -+ /* ICL has no power gating restrictions. */ -+ sseu->has_slice_pg = 1; -+ sseu->has_subslice_pg = 1; -+ sseu->has_eu_pg = 1; -+} -+ -+static void gen10_sseu_info_init(struct drm_i915_private *dev_priv) -+{ -+ struct sseu_dev_info *sseu = &RUNTIME_INFO(dev_priv)->sseu; -+ const u32 fuse2 = I915_READ(GEN8_FUSE2); -+ int s, ss; -+ const int eu_mask = 0xff; -+ u32 subslice_mask, eu_en; -+ -+ sseu->slice_mask = (fuse2 & GEN10_F2_S_ENA_MASK) >> -+ GEN10_F2_S_ENA_SHIFT; -+ sseu->max_slices = 6; -+ sseu->max_subslices = 4; -+ sseu->max_eus_per_subslice = 8; -+ -+ subslice_mask = (1 << 4) - 1; -+ subslice_mask &= ~((fuse2 & GEN10_F2_SS_DIS_MASK) >> -+ GEN10_F2_SS_DIS_SHIFT); -+ -+ /* -+ * Slice0 can have up to 3 subslices, but there are only 2 in -+ * slice1/2. -+ */ -+ sseu->subslice_mask[0] = subslice_mask; -+ for (s = 1; s < sseu->max_slices; s++) -+ sseu->subslice_mask[s] = subslice_mask & 0x3; -+ -+ /* Slice0 */ -+ eu_en = ~I915_READ(GEN8_EU_DISABLE0); -+ for (ss = 0; ss < sseu->max_subslices; ss++) -+ sseu_set_eus(sseu, 0, ss, (eu_en >> (8 * ss)) & eu_mask); -+ /* Slice1 */ -+ sseu_set_eus(sseu, 1, 0, (eu_en >> 24) & eu_mask); -+ eu_en = ~I915_READ(GEN8_EU_DISABLE1); -+ sseu_set_eus(sseu, 1, 1, eu_en & eu_mask); -+ /* Slice2 */ -+ sseu_set_eus(sseu, 2, 0, (eu_en >> 8) & eu_mask); -+ sseu_set_eus(sseu, 2, 1, (eu_en >> 16) & eu_mask); -+ /* Slice3 */ -+ sseu_set_eus(sseu, 3, 0, (eu_en >> 24) & eu_mask); -+ eu_en = ~I915_READ(GEN8_EU_DISABLE2); -+ sseu_set_eus(sseu, 3, 1, eu_en & eu_mask); -+ /* Slice4 */ -+ sseu_set_eus(sseu, 4, 0, (eu_en >> 8) & eu_mask); -+ sseu_set_eus(sseu, 4, 1, (eu_en >> 16) & eu_mask); -+ /* Slice5 */ -+ sseu_set_eus(sseu, 5, 0, (eu_en >> 24) & eu_mask); -+ eu_en = ~I915_READ(GEN10_EU_DISABLE3); -+ sseu_set_eus(sseu, 5, 1, eu_en & eu_mask); -+ -+ /* Do a second pass where we mark the subslices disabled if all their -+ * eus are off. -+ */ -+ for (s = 0; s < sseu->max_slices; s++) { -+ for (ss = 0; ss < sseu->max_subslices; ss++) { -+ if (sseu_get_eus(sseu, s, ss) == 0) -+ sseu->subslice_mask[s] &= ~BIT(ss); -+ } -+ } -+ -+ sseu->eu_total = compute_eu_total(sseu); -+ -+ /* -+ * CNL is expected to always have a uniform distribution -+ * of EU across subslices with the exception that any one -+ * EU in any one subslice may be fused off for die -+ * recovery. -+ */ -+ sseu->eu_per_subslice = sseu_subslice_total(sseu) ? -+ DIV_ROUND_UP(sseu->eu_total, -+ sseu_subslice_total(sseu)) : 0; -+ -+ /* No restrictions on Power Gating */ -+ sseu->has_slice_pg = 1; -+ sseu->has_subslice_pg = 1; -+ sseu->has_eu_pg = 1; -+} -+ -+static void cherryview_sseu_info_init(struct drm_i915_private *dev_priv) -+{ -+ struct sseu_dev_info *sseu = &RUNTIME_INFO(dev_priv)->sseu; -+ u32 fuse; -+ -+ fuse = I915_READ(CHV_FUSE_GT); -+ -+ sseu->slice_mask = BIT(0); -+ sseu->max_slices = 1; -+ sseu->max_subslices = 2; -+ sseu->max_eus_per_subslice = 8; -+ -+ if (!(fuse & CHV_FGT_DISABLE_SS0)) { -+ u8 disabled_mask = -+ ((fuse & CHV_FGT_EU_DIS_SS0_R0_MASK) >> -+ CHV_FGT_EU_DIS_SS0_R0_SHIFT) | -+ (((fuse & CHV_FGT_EU_DIS_SS0_R1_MASK) >> -+ CHV_FGT_EU_DIS_SS0_R1_SHIFT) << 4); -+ -+ sseu->subslice_mask[0] |= BIT(0); -+ sseu_set_eus(sseu, 0, 0, ~disabled_mask); -+ } -+ -+ if (!(fuse & CHV_FGT_DISABLE_SS1)) { -+ u8 disabled_mask = -+ ((fuse & CHV_FGT_EU_DIS_SS1_R0_MASK) >> -+ CHV_FGT_EU_DIS_SS1_R0_SHIFT) | -+ (((fuse & CHV_FGT_EU_DIS_SS1_R1_MASK) >> -+ CHV_FGT_EU_DIS_SS1_R1_SHIFT) << 4); -+ -+ sseu->subslice_mask[0] |= BIT(1); -+ sseu_set_eus(sseu, 0, 1, ~disabled_mask); -+ } -+ -+ sseu->eu_total = compute_eu_total(sseu); -+ -+ /* -+ * CHV expected to always have a uniform distribution of EU -+ * across subslices. -+ */ -+ sseu->eu_per_subslice = sseu_subslice_total(sseu) ? -+ sseu->eu_total / sseu_subslice_total(sseu) : -+ 0; -+ /* -+ * CHV supports subslice power gating on devices with more than -+ * one subslice, and supports EU power gating on devices with -+ * more than one EU pair per subslice. -+ */ -+ sseu->has_slice_pg = 0; -+ sseu->has_subslice_pg = sseu_subslice_total(sseu) > 1; -+ sseu->has_eu_pg = (sseu->eu_per_subslice > 2); -+} -+ -+static void gen9_sseu_info_init(struct drm_i915_private *dev_priv) -+{ -+ struct intel_device_info *info = mkwrite_device_info(dev_priv); -+ struct sseu_dev_info *sseu = &RUNTIME_INFO(dev_priv)->sseu; -+ int s, ss; -+ u32 fuse2, eu_disable, subslice_mask; -+ const u8 eu_mask = 0xff; -+ -+ fuse2 = I915_READ(GEN8_FUSE2); -+ sseu->slice_mask = (fuse2 & GEN8_F2_S_ENA_MASK) >> GEN8_F2_S_ENA_SHIFT; -+ -+ /* BXT has a single slice and at most 3 subslices. */ -+ sseu->max_slices = IS_GEN9_LP(dev_priv) ? 1 : 3; -+ sseu->max_subslices = IS_GEN9_LP(dev_priv) ? 3 : 4; -+ sseu->max_eus_per_subslice = 8; -+ -+ /* -+ * The subslice disable field is global, i.e. it applies -+ * to each of the enabled slices. -+ */ -+ subslice_mask = (1 << sseu->max_subslices) - 1; -+ subslice_mask &= ~((fuse2 & GEN9_F2_SS_DIS_MASK) >> -+ GEN9_F2_SS_DIS_SHIFT); -+ -+ /* -+ * Iterate through enabled slices and subslices to -+ * count the total enabled EU. -+ */ -+ for (s = 0; s < sseu->max_slices; s++) { -+ if (!(sseu->slice_mask & BIT(s))) -+ /* skip disabled slice */ -+ continue; -+ -+ sseu->subslice_mask[s] = subslice_mask; -+ -+ eu_disable = I915_READ(GEN9_EU_DISABLE(s)); -+ for (ss = 0; ss < sseu->max_subslices; ss++) { -+ int eu_per_ss; -+ u8 eu_disabled_mask; -+ -+ if (!(sseu->subslice_mask[s] & BIT(ss))) -+ /* skip disabled subslice */ -+ continue; -+ -+ eu_disabled_mask = (eu_disable >> (ss * 8)) & eu_mask; -+ -+ sseu_set_eus(sseu, s, ss, ~eu_disabled_mask); -+ -+ eu_per_ss = sseu->max_eus_per_subslice - -+ hweight8(eu_disabled_mask); -+ -+ /* -+ * Record which subslice(s) has(have) 7 EUs. we -+ * can tune the hash used to spread work among -+ * subslices if they are unbalanced. -+ */ -+ if (eu_per_ss == 7) -+ sseu->subslice_7eu[s] |= BIT(ss); -+ } -+ } -+ -+ sseu->eu_total = compute_eu_total(sseu); -+ -+ /* -+ * SKL is expected to always have a uniform distribution -+ * of EU across subslices with the exception that any one -+ * EU in any one subslice may be fused off for die -+ * recovery. BXT is expected to be perfectly uniform in EU -+ * distribution. -+ */ -+ sseu->eu_per_subslice = sseu_subslice_total(sseu) ? -+ DIV_ROUND_UP(sseu->eu_total, -+ sseu_subslice_total(sseu)) : 0; -+ /* -+ * SKL+ supports slice power gating on devices with more than -+ * one slice, and supports EU power gating on devices with -+ * more than one EU pair per subslice. BXT+ supports subslice -+ * power gating on devices with more than one subslice, and -+ * supports EU power gating on devices with more than one EU -+ * pair per subslice. -+ */ -+ sseu->has_slice_pg = -+ !IS_GEN9_LP(dev_priv) && hweight8(sseu->slice_mask) > 1; -+ sseu->has_subslice_pg = -+ IS_GEN9_LP(dev_priv) && sseu_subslice_total(sseu) > 1; -+ sseu->has_eu_pg = sseu->eu_per_subslice > 2; -+ -+ if (IS_GEN9_LP(dev_priv)) { -+#define IS_SS_DISABLED(ss) (!(sseu->subslice_mask[0] & BIT(ss))) -+ info->has_pooled_eu = hweight8(sseu->subslice_mask[0]) == 3; -+ -+ sseu->min_eu_in_pool = 0; -+ if (info->has_pooled_eu) { -+ if (IS_SS_DISABLED(2) || IS_SS_DISABLED(0)) -+ sseu->min_eu_in_pool = 3; -+ else if (IS_SS_DISABLED(1)) -+ sseu->min_eu_in_pool = 6; -+ else -+ sseu->min_eu_in_pool = 9; -+ } -+#undef IS_SS_DISABLED -+ } -+} -+ -+static void broadwell_sseu_info_init(struct drm_i915_private *dev_priv) -+{ -+ struct sseu_dev_info *sseu = &RUNTIME_INFO(dev_priv)->sseu; -+ int s, ss; -+ u32 fuse2, subslice_mask, eu_disable[3]; /* s_max */ -+ -+ fuse2 = I915_READ(GEN8_FUSE2); -+ sseu->slice_mask = (fuse2 & GEN8_F2_S_ENA_MASK) >> GEN8_F2_S_ENA_SHIFT; -+ sseu->max_slices = 3; -+ sseu->max_subslices = 3; -+ sseu->max_eus_per_subslice = 8; -+ -+ /* -+ * The subslice disable field is global, i.e. it applies -+ * to each of the enabled slices. -+ */ -+ subslice_mask = GENMASK(sseu->max_subslices - 1, 0); -+ subslice_mask &= ~((fuse2 & GEN8_F2_SS_DIS_MASK) >> -+ GEN8_F2_SS_DIS_SHIFT); -+ -+ eu_disable[0] = I915_READ(GEN8_EU_DISABLE0) & GEN8_EU_DIS0_S0_MASK; -+ eu_disable[1] = (I915_READ(GEN8_EU_DISABLE0) >> GEN8_EU_DIS0_S1_SHIFT) | -+ ((I915_READ(GEN8_EU_DISABLE1) & GEN8_EU_DIS1_S1_MASK) << -+ (32 - GEN8_EU_DIS0_S1_SHIFT)); -+ eu_disable[2] = (I915_READ(GEN8_EU_DISABLE1) >> GEN8_EU_DIS1_S2_SHIFT) | -+ ((I915_READ(GEN8_EU_DISABLE2) & GEN8_EU_DIS2_S2_MASK) << -+ (32 - GEN8_EU_DIS1_S2_SHIFT)); -+ -+ /* -+ * Iterate through enabled slices and subslices to -+ * count the total enabled EU. -+ */ -+ for (s = 0; s < sseu->max_slices; s++) { -+ if (!(sseu->slice_mask & BIT(s))) -+ /* skip disabled slice */ -+ continue; -+ -+ sseu->subslice_mask[s] = subslice_mask; -+ -+ for (ss = 0; ss < sseu->max_subslices; ss++) { -+ u8 eu_disabled_mask; -+ u32 n_disabled; -+ -+ if (!(sseu->subslice_mask[s] & BIT(ss))) -+ /* skip disabled subslice */ -+ continue; -+ -+ eu_disabled_mask = -+ eu_disable[s] >> (ss * sseu->max_eus_per_subslice); -+ -+ sseu_set_eus(sseu, s, ss, ~eu_disabled_mask); -+ -+ n_disabled = hweight8(eu_disabled_mask); -+ -+ /* -+ * Record which subslices have 7 EUs. -+ */ -+ if (sseu->max_eus_per_subslice - n_disabled == 7) -+ sseu->subslice_7eu[s] |= 1 << ss; -+ } -+ } -+ -+ sseu->eu_total = compute_eu_total(sseu); -+ -+ /* -+ * BDW is expected to always have a uniform distribution of EU across -+ * subslices with the exception that any one EU in any one subslice may -+ * be fused off for die recovery. -+ */ -+ sseu->eu_per_subslice = sseu_subslice_total(sseu) ? -+ DIV_ROUND_UP(sseu->eu_total, -+ sseu_subslice_total(sseu)) : 0; -+ -+ /* -+ * BDW supports slice power gating on devices with more than -+ * one slice. -+ */ -+ sseu->has_slice_pg = hweight8(sseu->slice_mask) > 1; -+ sseu->has_subslice_pg = 0; -+ sseu->has_eu_pg = 0; -+} -+ -+static void haswell_sseu_info_init(struct drm_i915_private *dev_priv) -+{ -+ struct sseu_dev_info *sseu = &RUNTIME_INFO(dev_priv)->sseu; -+ u32 fuse1; -+ int s, ss; -+ -+ /* -+ * There isn't a register to tell us how many slices/subslices. We -+ * work off the PCI-ids here. -+ */ -+ switch (INTEL_INFO(dev_priv)->gt) { -+ default: -+ MISSING_CASE(INTEL_INFO(dev_priv)->gt); -+ /* fall through */ -+ case 1: -+ sseu->slice_mask = BIT(0); -+ sseu->subslice_mask[0] = BIT(0); -+ break; -+ case 2: -+ sseu->slice_mask = BIT(0); -+ sseu->subslice_mask[0] = BIT(0) | BIT(1); -+ break; -+ case 3: -+ sseu->slice_mask = BIT(0) | BIT(1); -+ sseu->subslice_mask[0] = BIT(0) | BIT(1); -+ sseu->subslice_mask[1] = BIT(0) | BIT(1); -+ break; -+ } -+ -+ sseu->max_slices = hweight8(sseu->slice_mask); -+ sseu->max_subslices = hweight8(sseu->subslice_mask[0]); -+ -+ fuse1 = I915_READ(HSW_PAVP_FUSE1); -+ switch ((fuse1 & HSW_F1_EU_DIS_MASK) >> HSW_F1_EU_DIS_SHIFT) { -+ default: -+ MISSING_CASE((fuse1 & HSW_F1_EU_DIS_MASK) >> -+ HSW_F1_EU_DIS_SHIFT); -+ /* fall through */ -+ case HSW_F1_EU_DIS_10EUS: -+ sseu->eu_per_subslice = 10; -+ break; -+ case HSW_F1_EU_DIS_8EUS: -+ sseu->eu_per_subslice = 8; -+ break; -+ case HSW_F1_EU_DIS_6EUS: -+ sseu->eu_per_subslice = 6; -+ break; -+ } -+ sseu->max_eus_per_subslice = sseu->eu_per_subslice; -+ -+ for (s = 0; s < sseu->max_slices; s++) { -+ for (ss = 0; ss < sseu->max_subslices; ss++) { -+ sseu_set_eus(sseu, s, ss, -+ (1UL << sseu->eu_per_subslice) - 1); -+ } -+ } -+ -+ sseu->eu_total = compute_eu_total(sseu); -+ -+ /* No powergating for you. */ -+ sseu->has_slice_pg = 0; -+ sseu->has_subslice_pg = 0; -+ sseu->has_eu_pg = 0; -+} -+ -+static u32 read_reference_ts_freq(struct drm_i915_private *dev_priv) -+{ -+ u32 ts_override = I915_READ(GEN9_TIMESTAMP_OVERRIDE); -+ u32 base_freq, frac_freq; -+ -+ base_freq = ((ts_override & GEN9_TIMESTAMP_OVERRIDE_US_COUNTER_DIVIDER_MASK) >> -+ GEN9_TIMESTAMP_OVERRIDE_US_COUNTER_DIVIDER_SHIFT) + 1; -+ base_freq *= 1000; -+ -+ frac_freq = ((ts_override & -+ GEN9_TIMESTAMP_OVERRIDE_US_COUNTER_DENOMINATOR_MASK) >> -+ GEN9_TIMESTAMP_OVERRIDE_US_COUNTER_DENOMINATOR_SHIFT); -+ frac_freq = 1000 / (frac_freq + 1); -+ -+ return base_freq + frac_freq; -+} -+ -+static u32 gen10_get_crystal_clock_freq(struct drm_i915_private *dev_priv, -+ u32 rpm_config_reg) -+{ -+ u32 f19_2_mhz = 19200; -+ u32 f24_mhz = 24000; -+ u32 crystal_clock = (rpm_config_reg & -+ GEN9_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_MASK) >> -+ GEN9_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_SHIFT; -+ -+ switch (crystal_clock) { -+ case GEN9_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_19_2_MHZ: -+ return f19_2_mhz; -+ case GEN9_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_24_MHZ: -+ return f24_mhz; -+ default: -+ MISSING_CASE(crystal_clock); -+ return 0; -+ } -+} -+ -+static u32 gen11_get_crystal_clock_freq(struct drm_i915_private *dev_priv, -+ u32 rpm_config_reg) -+{ -+ u32 f19_2_mhz = 19200; -+ u32 f24_mhz = 24000; -+ u32 f25_mhz = 25000; -+ u32 f38_4_mhz = 38400; -+ u32 crystal_clock = (rpm_config_reg & -+ GEN11_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_MASK) >> -+ GEN11_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_SHIFT; -+ -+ switch (crystal_clock) { -+ case GEN11_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_24_MHZ: -+ return f24_mhz; -+ case GEN11_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_19_2_MHZ: -+ return f19_2_mhz; -+ case GEN11_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_38_4_MHZ: -+ return f38_4_mhz; -+ case GEN11_RPM_CONFIG0_CRYSTAL_CLOCK_FREQ_25_MHZ: -+ return f25_mhz; -+ default: -+ MISSING_CASE(crystal_clock); -+ return 0; -+ } -+} -+ -+static u32 read_timestamp_frequency(struct drm_i915_private *dev_priv) -+{ -+ u32 f12_5_mhz = 12500; -+ u32 f19_2_mhz = 19200; -+ u32 f24_mhz = 24000; -+ -+ if (INTEL_GEN(dev_priv) <= 4) { -+ /* PRMs say: -+ * -+ * "The value in this register increments once every 16 -+ * hclks." (through the “Clocking Configuration” -+ * (“CLKCFG”) MCHBAR register) -+ */ -+ return dev_priv->rawclk_freq / 16; -+ } else if (INTEL_GEN(dev_priv) <= 8) { -+ /* PRMs say: -+ * -+ * "The PCU TSC counts 10ns increments; this timestamp -+ * reflects bits 38:3 of the TSC (i.e. 80ns granularity, -+ * rolling over every 1.5 hours). -+ */ -+ return f12_5_mhz; -+ } else if (INTEL_GEN(dev_priv) <= 9) { -+ u32 ctc_reg = I915_READ(CTC_MODE); -+ u32 freq = 0; -+ -+ if ((ctc_reg & CTC_SOURCE_PARAMETER_MASK) == CTC_SOURCE_DIVIDE_LOGIC) { -+ freq = read_reference_ts_freq(dev_priv); -+ } else { -+ freq = IS_GEN9_LP(dev_priv) ? f19_2_mhz : f24_mhz; -+ -+ /* Now figure out how the command stream's timestamp -+ * register increments from this frequency (it might -+ * increment only every few clock cycle). -+ */ -+ freq >>= 3 - ((ctc_reg & CTC_SHIFT_PARAMETER_MASK) >> -+ CTC_SHIFT_PARAMETER_SHIFT); -+ } -+ -+ return freq; -+ } else if (INTEL_GEN(dev_priv) <= 11) { -+ u32 ctc_reg = I915_READ(CTC_MODE); -+ u32 freq = 0; -+ -+ /* First figure out the reference frequency. There are 2 ways -+ * we can compute the frequency, either through the -+ * TIMESTAMP_OVERRIDE register or through RPM_CONFIG. CTC_MODE -+ * tells us which one we should use. -+ */ -+ if ((ctc_reg & CTC_SOURCE_PARAMETER_MASK) == CTC_SOURCE_DIVIDE_LOGIC) { -+ freq = read_reference_ts_freq(dev_priv); -+ } else { -+ u32 rpm_config_reg = I915_READ(RPM_CONFIG0); -+ -+ if (INTEL_GEN(dev_priv) <= 10) -+ freq = gen10_get_crystal_clock_freq(dev_priv, -+ rpm_config_reg); -+ else -+ freq = gen11_get_crystal_clock_freq(dev_priv, -+ rpm_config_reg); -+ -+ /* Now figure out how the command stream's timestamp -+ * register increments from this frequency (it might -+ * increment only every few clock cycle). -+ */ -+ freq >>= 3 - ((rpm_config_reg & -+ GEN10_RPM_CONFIG0_CTC_SHIFT_PARAMETER_MASK) >> -+ GEN10_RPM_CONFIG0_CTC_SHIFT_PARAMETER_SHIFT); -+ } -+ -+ return freq; -+ } -+ -+ MISSING_CASE("Unknown gen, unable to read command streamer timestamp frequency\n"); -+ return 0; -+} -+ -+#undef INTEL_VGA_DEVICE -+#define INTEL_VGA_DEVICE(id, info) (id) -+ -+static const u16 subplatform_ult_ids[] = { -+ INTEL_HSW_ULT_GT1_IDS(0), -+ INTEL_HSW_ULT_GT2_IDS(0), -+ INTEL_HSW_ULT_GT3_IDS(0), -+ INTEL_BDW_ULT_GT1_IDS(0), -+ INTEL_BDW_ULT_GT2_IDS(0), -+ INTEL_BDW_ULT_GT3_IDS(0), -+ INTEL_BDW_ULT_RSVD_IDS(0), -+ INTEL_SKL_ULT_GT1_IDS(0), -+ INTEL_SKL_ULT_GT2_IDS(0), -+ INTEL_SKL_ULT_GT3_IDS(0), -+ INTEL_KBL_ULT_GT1_IDS(0), -+ INTEL_KBL_ULT_GT2_IDS(0), -+ INTEL_KBL_ULT_GT3_IDS(0), -+ INTEL_CFL_U_GT2_IDS(0), -+ INTEL_CFL_U_GT3_IDS(0), -+ INTEL_WHL_U_GT1_IDS(0), -+ INTEL_WHL_U_GT2_IDS(0), -+ INTEL_WHL_U_GT3_IDS(0) -+}; -+ -+static const u16 subplatform_ulx_ids[] = { -+ INTEL_HSW_ULX_GT1_IDS(0), -+ INTEL_HSW_ULX_GT2_IDS(0), -+ INTEL_BDW_ULX_GT1_IDS(0), -+ INTEL_BDW_ULX_GT2_IDS(0), -+ INTEL_BDW_ULX_GT3_IDS(0), -+ INTEL_BDW_ULX_RSVD_IDS(0), -+ INTEL_SKL_ULX_GT1_IDS(0), -+ INTEL_SKL_ULX_GT2_IDS(0), -+ INTEL_KBL_ULX_GT1_IDS(0), -+ INTEL_KBL_ULX_GT2_IDS(0) -+}; -+ -+static const u16 subplatform_aml_ids[] = { -+ INTEL_AML_KBL_GT2_IDS(0), -+ INTEL_AML_CFL_GT2_IDS(0) -+}; -+ -+static const u16 subplatform_portf_ids[] = { -+ INTEL_CNL_PORT_F_IDS(0), -+ INTEL_ICL_PORT_F_IDS(0) -+}; -+ -+static bool find_devid(u16 id, const u16 *p, unsigned int num) -+{ -+ for (; num; num--, p++) { -+ if (*p == id) -+ return true; -+ } -+ -+ return false; -+} -+ -+void intel_device_info_subplatform_init(struct drm_i915_private *i915) -+{ -+ const struct intel_device_info *info = INTEL_INFO(i915); -+ const struct intel_runtime_info *rinfo = RUNTIME_INFO(i915); -+ const unsigned int pi = __platform_mask_index(rinfo, info->platform); -+ const unsigned int pb = __platform_mask_bit(rinfo, info->platform); -+ u16 devid = INTEL_DEVID(i915); -+ u32 mask = 0; -+ -+ /* Make sure IS_ checks are working. */ -+ RUNTIME_INFO(i915)->platform_mask[pi] = BIT(pb); -+ -+ /* Find and mark subplatform bits based on the PCI device id. */ -+ if (find_devid(devid, subplatform_ult_ids, -+ ARRAY_SIZE(subplatform_ult_ids))) { -+ mask = BIT(INTEL_SUBPLATFORM_ULT); -+ } else if (find_devid(devid, subplatform_ulx_ids, -+ ARRAY_SIZE(subplatform_ulx_ids))) { -+ mask = BIT(INTEL_SUBPLATFORM_ULX); -+ if (IS_HASWELL(i915) || IS_BROADWELL(i915)) { -+ /* ULX machines are also considered ULT. */ -+ mask |= BIT(INTEL_SUBPLATFORM_ULT); -+ } -+ } else if (find_devid(devid, subplatform_aml_ids, -+ ARRAY_SIZE(subplatform_aml_ids))) { -+ mask = BIT(INTEL_SUBPLATFORM_AML); -+ } else if (find_devid(devid, subplatform_portf_ids, -+ ARRAY_SIZE(subplatform_portf_ids))) { -+ mask = BIT(INTEL_SUBPLATFORM_PORTF); -+ } -+ -+ GEM_BUG_ON(mask & ~INTEL_SUBPLATFORM_BITS); -+ -+ RUNTIME_INFO(i915)->platform_mask[pi] |= mask; -+} -+ -+/** -+ * intel_device_info_runtime_init - initialize runtime info -+ * @dev_priv: the i915 device -+ * -+ * Determine various intel_device_info fields at runtime. -+ * -+ * Use it when either: -+ * - it's judged too laborious to fill n static structures with the limit -+ * when a simple if statement does the job, -+ * - run-time checks (eg read fuse/strap registers) are needed. -+ * -+ * This function needs to be called: -+ * - after the MMIO has been setup as we are reading registers, -+ * - after the PCH has been detected, -+ * - before the first usage of the fields it can tweak. -+ */ -+void intel_device_info_runtime_init(struct drm_i915_private *dev_priv) -+{ -+ struct intel_device_info *info = mkwrite_device_info(dev_priv); -+ struct intel_runtime_info *runtime = RUNTIME_INFO(dev_priv); -+ enum pipe pipe; -+ -+ if (INTEL_GEN(dev_priv) >= 10) { -+ for_each_pipe(dev_priv, pipe) -+ runtime->num_scalers[pipe] = 2; -+ } else if (IS_GEN(dev_priv, 9)) { -+ runtime->num_scalers[PIPE_A] = 2; -+ runtime->num_scalers[PIPE_B] = 2; -+ runtime->num_scalers[PIPE_C] = 1; -+ } -+ -+ BUILD_BUG_ON(BITS_PER_TYPE(intel_engine_mask_t) < I915_NUM_ENGINES); -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ for_each_pipe(dev_priv, pipe) -+ runtime->num_sprites[pipe] = 6; -+ else if (IS_GEN(dev_priv, 10) || IS_GEMINILAKE(dev_priv)) -+ for_each_pipe(dev_priv, pipe) -+ runtime->num_sprites[pipe] = 3; -+ else if (IS_BROXTON(dev_priv)) { -+ /* -+ * Skylake and Broxton currently don't expose the topmost plane as its -+ * use is exclusive with the legacy cursor and we only want to expose -+ * one of those, not both. Until we can safely expose the topmost plane -+ * as a DRM_PLANE_TYPE_CURSOR with all the features exposed/supported, -+ * we don't expose the topmost plane at all to prevent ABI breakage -+ * down the line. -+ */ -+ -+ runtime->num_sprites[PIPE_A] = 2; -+ runtime->num_sprites[PIPE_B] = 2; -+ runtime->num_sprites[PIPE_C] = 1; -+ } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ for_each_pipe(dev_priv, pipe) -+ runtime->num_sprites[pipe] = 2; -+ } else if (INTEL_GEN(dev_priv) >= 5 || IS_G4X(dev_priv)) { -+ for_each_pipe(dev_priv, pipe) -+ runtime->num_sprites[pipe] = 1; -+ } -+ -+ if (i915_modparams.disable_display) { -+ DRM_INFO("Display disabled (module parameter)\n"); -+ info->num_pipes = 0; -+ } else if (HAS_DISPLAY(dev_priv) && -+ (IS_GEN_RANGE(dev_priv, 7, 8)) && -+ HAS_PCH_SPLIT(dev_priv)) { -+ u32 fuse_strap = I915_READ(FUSE_STRAP); -+ u32 sfuse_strap = I915_READ(SFUSE_STRAP); -+ -+ /* -+ * SFUSE_STRAP is supposed to have a bit signalling the display -+ * is fused off. Unfortunately it seems that, at least in -+ * certain cases, fused off display means that PCH display -+ * reads don't land anywhere. In that case, we read 0s. -+ * -+ * On CPT/PPT, we can detect this case as SFUSE_STRAP_FUSE_LOCK -+ * should be set when taking over after the firmware. -+ */ -+ if (fuse_strap & ILK_INTERNAL_DISPLAY_DISABLE || -+ sfuse_strap & SFUSE_STRAP_DISPLAY_DISABLED || -+ (HAS_PCH_CPT(dev_priv) && -+ !(sfuse_strap & SFUSE_STRAP_FUSE_LOCK))) { -+ DRM_INFO("Display fused off, disabling\n"); -+ info->num_pipes = 0; -+ } else if (fuse_strap & IVB_PIPE_C_DISABLE) { -+ DRM_INFO("PipeC fused off\n"); -+ info->num_pipes -= 1; -+ } -+ } else if (HAS_DISPLAY(dev_priv) && INTEL_GEN(dev_priv) >= 9) { -+ u32 dfsm = I915_READ(SKL_DFSM); -+ u8 disabled_mask = 0; -+ bool invalid; -+ int num_bits; -+ -+ if (dfsm & SKL_DFSM_PIPE_A_DISABLE) -+ disabled_mask |= BIT(PIPE_A); -+ if (dfsm & SKL_DFSM_PIPE_B_DISABLE) -+ disabled_mask |= BIT(PIPE_B); -+ if (dfsm & SKL_DFSM_PIPE_C_DISABLE) -+ disabled_mask |= BIT(PIPE_C); -+ -+ num_bits = hweight8(disabled_mask); -+ -+ switch (disabled_mask) { -+ case BIT(PIPE_A): -+ case BIT(PIPE_B): -+ case BIT(PIPE_A) | BIT(PIPE_B): -+ case BIT(PIPE_A) | BIT(PIPE_C): -+ invalid = true; -+ break; -+ default: -+ invalid = false; -+ } -+ -+ if (num_bits > info->num_pipes || invalid) -+ DRM_ERROR("invalid pipe fuse configuration: 0x%x\n", -+ disabled_mask); -+ else -+ info->num_pipes -= num_bits; -+ } -+ -+ /* Initialize slice/subslice/EU info */ -+ if (IS_HASWELL(dev_priv)) -+ haswell_sseu_info_init(dev_priv); -+ else if (IS_CHERRYVIEW(dev_priv)) -+ cherryview_sseu_info_init(dev_priv); -+ else if (IS_BROADWELL(dev_priv)) -+ broadwell_sseu_info_init(dev_priv); -+ else if (IS_GEN(dev_priv, 9)) -+ gen9_sseu_info_init(dev_priv); -+ else if (IS_GEN(dev_priv, 10)) -+ gen10_sseu_info_init(dev_priv); -+ else if (INTEL_GEN(dev_priv) >= 11) -+ gen11_sseu_info_init(dev_priv); -+ -+ if (IS_GEN(dev_priv, 6) && intel_vtd_active()) { -+ DRM_INFO("Disabling ppGTT for VT-d support\n"); -+ info->ppgtt_type = INTEL_PPGTT_NONE; -+ } -+ -+ /* Initialize command stream timestamp frequency */ -+ runtime->cs_timestamp_frequency_khz = read_timestamp_frequency(dev_priv); -+} -+ -+void intel_driver_caps_print(const struct intel_driver_caps *caps, -+ struct drm_printer *p) -+{ -+ drm_printf(p, "Has logical contexts? %s\n", -+ yesno(caps->has_logical_contexts)); -+ drm_printf(p, "scheduler: %x\n", caps->scheduler); -+} -+ -+/* -+ * Determine which engines are fused off in our particular hardware. Since the -+ * fuse register is in the blitter powerwell, we need forcewake to be ready at -+ * this point (but later we need to prune the forcewake domains for engines that -+ * are indeed fused off). -+ */ -+void intel_device_info_init_mmio(struct drm_i915_private *dev_priv) -+{ -+ struct intel_device_info *info = mkwrite_device_info(dev_priv); -+ unsigned int logical_vdbox = 0; -+ unsigned int i; -+ u32 media_fuse; -+ u16 vdbox_mask; -+ u16 vebox_mask; -+ -+ if (INTEL_GEN(dev_priv) < 11) -+ return; -+ -+ media_fuse = ~I915_READ(GEN11_GT_VEBOX_VDBOX_DISABLE); -+ -+ vdbox_mask = media_fuse & GEN11_GT_VDBOX_DISABLE_MASK; -+ vebox_mask = (media_fuse & GEN11_GT_VEBOX_DISABLE_MASK) >> -+ GEN11_GT_VEBOX_DISABLE_SHIFT; -+ -+ for (i = 0; i < I915_MAX_VCS; i++) { -+ if (!HAS_ENGINE(dev_priv, _VCS(i))) -+ continue; -+ -+ if (!(BIT(i) & vdbox_mask)) { -+ info->engine_mask &= ~BIT(_VCS(i)); -+ DRM_DEBUG_DRIVER("vcs%u fused off\n", i); -+ continue; -+ } -+ -+ /* -+ * In Gen11, only even numbered logical VDBOXes are -+ * hooked up to an SFC (Scaler & Format Converter) unit. -+ */ -+ if (logical_vdbox++ % 2 == 0) -+ RUNTIME_INFO(dev_priv)->vdbox_sfc_access |= BIT(i); -+ } -+ DRM_DEBUG_DRIVER("vdbox enable: %04x, instances: %04lx\n", -+ vdbox_mask, VDBOX_MASK(dev_priv)); -+ GEM_BUG_ON(vdbox_mask != VDBOX_MASK(dev_priv)); -+ -+ for (i = 0; i < I915_MAX_VECS; i++) { -+ if (!HAS_ENGINE(dev_priv, _VECS(i))) -+ continue; -+ -+ if (!(BIT(i) & vebox_mask)) { -+ info->engine_mask &= ~BIT(_VECS(i)); -+ DRM_DEBUG_DRIVER("vecs%u fused off\n", i); -+ } -+ } -+ DRM_DEBUG_DRIVER("vebox enable: %04x, instances: %04lx\n", -+ vebox_mask, VEBOX_MASK(dev_priv)); -+ GEM_BUG_ON(vebox_mask != VEBOX_MASK(dev_priv)); -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_device_info.h b/drivers/gpu/drm/i915_legacy/intel_device_info.h -new file mode 100644 -index 000000000000..0e579f158016 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_device_info.h -@@ -0,0 +1,307 @@ -+/* -+ * Copyright © 2014-2017 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef _INTEL_DEVICE_INFO_H_ -+#define _INTEL_DEVICE_INFO_H_ -+ -+#include -+ -+#include "intel_engine_types.h" -+#include "intel_display.h" -+ -+struct drm_printer; -+struct drm_i915_private; -+ -+/* Keep in gen based order, and chronological order within a gen */ -+enum intel_platform { -+ INTEL_PLATFORM_UNINITIALIZED = 0, -+ /* gen2 */ -+ INTEL_I830, -+ INTEL_I845G, -+ INTEL_I85X, -+ INTEL_I865G, -+ /* gen3 */ -+ INTEL_I915G, -+ INTEL_I915GM, -+ INTEL_I945G, -+ INTEL_I945GM, -+ INTEL_G33, -+ INTEL_PINEVIEW, -+ /* gen4 */ -+ INTEL_I965G, -+ INTEL_I965GM, -+ INTEL_G45, -+ INTEL_GM45, -+ /* gen5 */ -+ INTEL_IRONLAKE, -+ /* gen6 */ -+ INTEL_SANDYBRIDGE, -+ /* gen7 */ -+ INTEL_IVYBRIDGE, -+ INTEL_VALLEYVIEW, -+ INTEL_HASWELL, -+ /* gen8 */ -+ INTEL_BROADWELL, -+ INTEL_CHERRYVIEW, -+ /* gen9 */ -+ INTEL_SKYLAKE, -+ INTEL_BROXTON, -+ INTEL_KABYLAKE, -+ INTEL_GEMINILAKE, -+ INTEL_COFFEELAKE, -+ /* gen10 */ -+ INTEL_CANNONLAKE, -+ /* gen11 */ -+ INTEL_ICELAKE, -+ INTEL_ELKHARTLAKE, -+ INTEL_MAX_PLATFORMS -+}; -+ -+/* -+ * Subplatform bits share the same namespace per parent platform. In other words -+ * it is fine for the same bit to be used on multiple parent platforms. -+ */ -+ -+#define INTEL_SUBPLATFORM_BITS (3) -+ -+/* HSW/BDW/SKL/KBL/CFL */ -+#define INTEL_SUBPLATFORM_ULT (0) -+#define INTEL_SUBPLATFORM_ULX (1) -+#define INTEL_SUBPLATFORM_AML (2) -+ -+/* CNL/ICL */ -+#define INTEL_SUBPLATFORM_PORTF (0) -+ -+enum intel_ppgtt_type { -+ INTEL_PPGTT_NONE = I915_GEM_PPGTT_NONE, -+ INTEL_PPGTT_ALIASING = I915_GEM_PPGTT_ALIASING, -+ INTEL_PPGTT_FULL = I915_GEM_PPGTT_FULL, -+}; -+ -+#define DEV_INFO_FOR_EACH_FLAG(func) \ -+ func(is_mobile); \ -+ func(is_lp); \ -+ func(is_alpha_support); \ -+ /* Keep has_* in alphabetical order */ \ -+ func(has_64bit_reloc); \ -+ func(gpu_reset_clobbers_display); \ -+ func(has_reset_engine); \ -+ func(has_fpga_dbg); \ -+ func(has_guc); \ -+ func(has_guc_ct); \ -+ func(has_l3_dpf); \ -+ func(has_llc); \ -+ func(has_logical_ring_contexts); \ -+ func(has_logical_ring_elsq); \ -+ func(has_logical_ring_preemption); \ -+ func(has_pooled_eu); \ -+ func(has_rc6); \ -+ func(has_rc6p); \ -+ func(has_runtime_pm); \ -+ func(has_snoop); \ -+ func(has_coherent_ggtt); \ -+ func(unfenced_needs_alignment); \ -+ func(hws_needs_physical); -+ -+#define DEV_INFO_DISPLAY_FOR_EACH_FLAG(func) \ -+ /* Keep in alphabetical order */ \ -+ func(cursor_needs_physical); \ -+ func(has_csr); \ -+ func(has_ddi); \ -+ func(has_dp_mst); \ -+ func(has_fbc); \ -+ func(has_gmch); \ -+ func(has_hotplug); \ -+ func(has_ipc); \ -+ func(has_overlay); \ -+ func(has_psr); \ -+ func(overlay_needs_physical); \ -+ func(supports_tv); -+ -+#define GEN_MAX_SLICES (6) /* CNL upper bound */ -+#define GEN_MAX_SUBSLICES (8) /* ICL upper bound */ -+ -+struct sseu_dev_info { -+ u8 slice_mask; -+ u8 subslice_mask[GEN_MAX_SLICES]; -+ u16 eu_total; -+ u8 eu_per_subslice; -+ u8 min_eu_in_pool; -+ /* For each slice, which subslice(s) has(have) 7 EUs (bitfield)? */ -+ u8 subslice_7eu[3]; -+ u8 has_slice_pg:1; -+ u8 has_subslice_pg:1; -+ u8 has_eu_pg:1; -+ -+ /* Topology fields */ -+ u8 max_slices; -+ u8 max_subslices; -+ u8 max_eus_per_subslice; -+ -+ /* We don't have more than 8 eus per subslice at the moment and as we -+ * store eus enabled using bits, no need to multiply by eus per -+ * subslice. -+ */ -+ u8 eu_mask[GEN_MAX_SLICES * GEN_MAX_SUBSLICES]; -+}; -+ -+struct intel_device_info { -+ u16 gen_mask; -+ -+ u8 gen; -+ u8 gt; /* GT number, 0 if undefined */ -+ intel_engine_mask_t engine_mask; /* Engines supported by the HW */ -+ -+ enum intel_platform platform; -+ -+ enum intel_ppgtt_type ppgtt_type; -+ unsigned int ppgtt_size; /* log2, e.g. 31/32/48 bits */ -+ -+ unsigned int page_sizes; /* page sizes supported by the HW */ -+ -+ u32 display_mmio_offset; -+ -+ u8 num_pipes; -+ -+#define DEFINE_FLAG(name) u8 name:1 -+ DEV_INFO_FOR_EACH_FLAG(DEFINE_FLAG); -+#undef DEFINE_FLAG -+ -+ struct { -+#define DEFINE_FLAG(name) u8 name:1 -+ DEV_INFO_DISPLAY_FOR_EACH_FLAG(DEFINE_FLAG); -+#undef DEFINE_FLAG -+ } display; -+ -+ u16 ddb_size; /* in blocks */ -+ -+ /* Register offsets for the various display pipes and transcoders */ -+ int pipe_offsets[I915_MAX_TRANSCODERS]; -+ int trans_offsets[I915_MAX_TRANSCODERS]; -+ int cursor_offsets[I915_MAX_PIPES]; -+ -+ struct color_luts { -+ u16 degamma_lut_size; -+ u16 gamma_lut_size; -+ u32 degamma_lut_tests; -+ u32 gamma_lut_tests; -+ } color; -+}; -+ -+struct intel_runtime_info { -+ /* -+ * Platform mask is used for optimizing or-ed IS_PLATFORM calls into -+ * into single runtime conditionals, and also to provide groundwork -+ * for future per platform, or per SKU build optimizations. -+ * -+ * Array can be extended when necessary if the corresponding -+ * BUILD_BUG_ON is hit. -+ */ -+ u32 platform_mask[2]; -+ -+ u16 device_id; -+ -+ u8 num_sprites[I915_MAX_PIPES]; -+ u8 num_scalers[I915_MAX_PIPES]; -+ -+ u8 num_engines; -+ -+ /* Slice/subslice/EU info */ -+ struct sseu_dev_info sseu; -+ -+ u32 cs_timestamp_frequency_khz; -+ -+ /* Media engine access to SFC per instance */ -+ u8 vdbox_sfc_access; -+}; -+ -+struct intel_driver_caps { -+ unsigned int scheduler; -+ bool has_logical_contexts:1; -+}; -+ -+static inline unsigned int sseu_subslice_total(const struct sseu_dev_info *sseu) -+{ -+ unsigned int i, total = 0; -+ -+ for (i = 0; i < ARRAY_SIZE(sseu->subslice_mask); i++) -+ total += hweight8(sseu->subslice_mask[i]); -+ -+ return total; -+} -+ -+static inline int sseu_eu_idx(const struct sseu_dev_info *sseu, -+ int slice, int subslice) -+{ -+ int subslice_stride = DIV_ROUND_UP(sseu->max_eus_per_subslice, -+ BITS_PER_BYTE); -+ int slice_stride = sseu->max_subslices * subslice_stride; -+ -+ return slice * slice_stride + subslice * subslice_stride; -+} -+ -+static inline u16 sseu_get_eus(const struct sseu_dev_info *sseu, -+ int slice, int subslice) -+{ -+ int i, offset = sseu_eu_idx(sseu, slice, subslice); -+ u16 eu_mask = 0; -+ -+ for (i = 0; -+ i < DIV_ROUND_UP(sseu->max_eus_per_subslice, BITS_PER_BYTE); i++) { -+ eu_mask |= ((u16) sseu->eu_mask[offset + i]) << -+ (i * BITS_PER_BYTE); -+ } -+ -+ return eu_mask; -+} -+ -+static inline void sseu_set_eus(struct sseu_dev_info *sseu, -+ int slice, int subslice, u16 eu_mask) -+{ -+ int i, offset = sseu_eu_idx(sseu, slice, subslice); -+ -+ for (i = 0; -+ i < DIV_ROUND_UP(sseu->max_eus_per_subslice, BITS_PER_BYTE); i++) { -+ sseu->eu_mask[offset + i] = -+ (eu_mask >> (BITS_PER_BYTE * i)) & 0xff; -+ } -+} -+ -+const char *intel_platform_name(enum intel_platform platform); -+ -+void intel_device_info_subplatform_init(struct drm_i915_private *dev_priv); -+void intel_device_info_runtime_init(struct drm_i915_private *dev_priv); -+void intel_device_info_dump_flags(const struct intel_device_info *info, -+ struct drm_printer *p); -+void intel_device_info_dump_runtime(const struct intel_runtime_info *info, -+ struct drm_printer *p); -+void intel_device_info_dump_topology(const struct sseu_dev_info *sseu, -+ struct drm_printer *p); -+ -+void intel_device_info_init_mmio(struct drm_i915_private *dev_priv); -+ -+void intel_driver_caps_print(const struct intel_driver_caps *caps, -+ struct drm_printer *p); -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/intel_display.c b/drivers/gpu/drm/i915_legacy/intel_display.c -new file mode 100644 -index 000000000000..75105a2c59ea ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_display.c -@@ -0,0 +1,16814 @@ -+/* -+ * Copyright © 2006-2007 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Eric Anholt -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "i915_drv.h" -+#include "i915_gem_clflush.h" -+#include "i915_reset.h" -+#include "i915_trace.h" -+#include "intel_atomic_plane.h" -+#include "intel_color.h" -+#include "intel_cdclk.h" -+#include "intel_crt.h" -+#include "intel_ddi.h" -+#include "intel_dp.h" -+#include "intel_drv.h" -+#include "intel_dsi.h" -+#include "intel_dvo.h" -+#include "intel_fbc.h" -+#include "intel_fbdev.h" -+#include "intel_frontbuffer.h" -+#include "intel_hdcp.h" -+#include "intel_hdmi.h" -+#include "intel_lvds.h" -+#include "intel_pipe_crc.h" -+#include "intel_pm.h" -+#include "intel_psr.h" -+#include "intel_sdvo.h" -+#include "intel_sprite.h" -+#include "intel_tv.h" -+ -+/* Primary plane formats for gen <= 3 */ -+static const u32 i8xx_primary_formats[] = { -+ DRM_FORMAT_C8, -+ DRM_FORMAT_RGB565, -+ DRM_FORMAT_XRGB1555, -+ DRM_FORMAT_XRGB8888, -+}; -+ -+/* Primary plane formats for gen >= 4 */ -+static const u32 i965_primary_formats[] = { -+ DRM_FORMAT_C8, -+ DRM_FORMAT_RGB565, -+ DRM_FORMAT_XRGB8888, -+ DRM_FORMAT_XBGR8888, -+ DRM_FORMAT_XRGB2101010, -+ DRM_FORMAT_XBGR2101010, -+}; -+ -+static const u64 i9xx_format_modifiers[] = { -+ I915_FORMAT_MOD_X_TILED, -+ DRM_FORMAT_MOD_LINEAR, -+ DRM_FORMAT_MOD_INVALID -+}; -+ -+/* Cursor formats */ -+static const u32 intel_cursor_formats[] = { -+ DRM_FORMAT_ARGB8888, -+}; -+ -+static const u64 cursor_format_modifiers[] = { -+ DRM_FORMAT_MOD_LINEAR, -+ DRM_FORMAT_MOD_INVALID -+}; -+ -+static void i9xx_crtc_clock_get(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config); -+static void ironlake_pch_clock_get(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config); -+ -+static int intel_framebuffer_init(struct intel_framebuffer *ifb, -+ struct drm_i915_gem_object *obj, -+ struct drm_mode_fb_cmd2 *mode_cmd); -+static void intel_set_pipe_timings(const struct intel_crtc_state *crtc_state); -+static void intel_set_pipe_src_size(const struct intel_crtc_state *crtc_state); -+static void intel_cpu_transcoder_set_m_n(const struct intel_crtc_state *crtc_state, -+ const struct intel_link_m_n *m_n, -+ const struct intel_link_m_n *m2_n2); -+static void i9xx_set_pipeconf(const struct intel_crtc_state *crtc_state); -+static void ironlake_set_pipeconf(const struct intel_crtc_state *crtc_state); -+static void haswell_set_pipeconf(const struct intel_crtc_state *crtc_state); -+static void haswell_set_pipemisc(const struct intel_crtc_state *crtc_state); -+static void vlv_prepare_pll(struct intel_crtc *crtc, -+ const struct intel_crtc_state *pipe_config); -+static void chv_prepare_pll(struct intel_crtc *crtc, -+ const struct intel_crtc_state *pipe_config); -+static void intel_begin_crtc_commit(struct intel_atomic_state *, struct intel_crtc *); -+static void intel_finish_crtc_commit(struct intel_atomic_state *, struct intel_crtc *); -+static void intel_crtc_init_scalers(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state); -+static void skylake_pfit_enable(const struct intel_crtc_state *crtc_state); -+static void ironlake_pfit_disable(const struct intel_crtc_state *old_crtc_state); -+static void ironlake_pfit_enable(const struct intel_crtc_state *crtc_state); -+static void intel_modeset_setup_hw_state(struct drm_device *dev, -+ struct drm_modeset_acquire_ctx *ctx); -+static void intel_pre_disable_primary_noatomic(struct drm_crtc *crtc); -+ -+struct intel_limit { -+ struct { -+ int min, max; -+ } dot, vco, n, m, m1, m2, p, p1; -+ -+ struct { -+ int dot_limit; -+ int p2_slow, p2_fast; -+ } p2; -+}; -+ -+/* returns HPLL frequency in kHz */ -+int vlv_get_hpll_vco(struct drm_i915_private *dev_priv) -+{ -+ int hpll_freq, vco_freq[] = { 800, 1600, 2000, 2400 }; -+ -+ /* Obtain SKU information */ -+ mutex_lock(&dev_priv->sb_lock); -+ hpll_freq = vlv_cck_read(dev_priv, CCK_FUSE_REG) & -+ CCK_FUSE_HPLL_FREQ_MASK; -+ mutex_unlock(&dev_priv->sb_lock); -+ -+ return vco_freq[hpll_freq] * 1000; -+} -+ -+int vlv_get_cck_clock(struct drm_i915_private *dev_priv, -+ const char *name, u32 reg, int ref_freq) -+{ -+ u32 val; -+ int divider; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ val = vlv_cck_read(dev_priv, reg); -+ mutex_unlock(&dev_priv->sb_lock); -+ -+ divider = val & CCK_FREQUENCY_VALUES; -+ -+ WARN((val & CCK_FREQUENCY_STATUS) != -+ (divider << CCK_FREQUENCY_STATUS_SHIFT), -+ "%s change in progress\n", name); -+ -+ return DIV_ROUND_CLOSEST(ref_freq << 1, divider + 1); -+} -+ -+int vlv_get_cck_clock_hpll(struct drm_i915_private *dev_priv, -+ const char *name, u32 reg) -+{ -+ if (dev_priv->hpll_freq == 0) -+ dev_priv->hpll_freq = vlv_get_hpll_vco(dev_priv); -+ -+ return vlv_get_cck_clock(dev_priv, name, reg, -+ dev_priv->hpll_freq); -+} -+ -+static void intel_update_czclk(struct drm_i915_private *dev_priv) -+{ -+ if (!(IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv))) -+ return; -+ -+ dev_priv->czclk_freq = vlv_get_cck_clock_hpll(dev_priv, "czclk", -+ CCK_CZ_CLOCK_CONTROL); -+ -+ DRM_DEBUG_DRIVER("CZ clock rate: %d kHz\n", dev_priv->czclk_freq); -+} -+ -+static inline u32 /* units of 100MHz */ -+intel_fdi_link_freq(struct drm_i915_private *dev_priv, -+ const struct intel_crtc_state *pipe_config) -+{ -+ if (HAS_DDI(dev_priv)) -+ return pipe_config->port_clock; /* SPLL */ -+ else -+ return dev_priv->fdi_pll_freq; -+} -+ -+static const struct intel_limit intel_limits_i8xx_dac = { -+ .dot = { .min = 25000, .max = 350000 }, -+ .vco = { .min = 908000, .max = 1512000 }, -+ .n = { .min = 2, .max = 16 }, -+ .m = { .min = 96, .max = 140 }, -+ .m1 = { .min = 18, .max = 26 }, -+ .m2 = { .min = 6, .max = 16 }, -+ .p = { .min = 4, .max = 128 }, -+ .p1 = { .min = 2, .max = 33 }, -+ .p2 = { .dot_limit = 165000, -+ .p2_slow = 4, .p2_fast = 2 }, -+}; -+ -+static const struct intel_limit intel_limits_i8xx_dvo = { -+ .dot = { .min = 25000, .max = 350000 }, -+ .vco = { .min = 908000, .max = 1512000 }, -+ .n = { .min = 2, .max = 16 }, -+ .m = { .min = 96, .max = 140 }, -+ .m1 = { .min = 18, .max = 26 }, -+ .m2 = { .min = 6, .max = 16 }, -+ .p = { .min = 4, .max = 128 }, -+ .p1 = { .min = 2, .max = 33 }, -+ .p2 = { .dot_limit = 165000, -+ .p2_slow = 4, .p2_fast = 4 }, -+}; -+ -+static const struct intel_limit intel_limits_i8xx_lvds = { -+ .dot = { .min = 25000, .max = 350000 }, -+ .vco = { .min = 908000, .max = 1512000 }, -+ .n = { .min = 2, .max = 16 }, -+ .m = { .min = 96, .max = 140 }, -+ .m1 = { .min = 18, .max = 26 }, -+ .m2 = { .min = 6, .max = 16 }, -+ .p = { .min = 4, .max = 128 }, -+ .p1 = { .min = 1, .max = 6 }, -+ .p2 = { .dot_limit = 165000, -+ .p2_slow = 14, .p2_fast = 7 }, -+}; -+ -+static const struct intel_limit intel_limits_i9xx_sdvo = { -+ .dot = { .min = 20000, .max = 400000 }, -+ .vco = { .min = 1400000, .max = 2800000 }, -+ .n = { .min = 1, .max = 6 }, -+ .m = { .min = 70, .max = 120 }, -+ .m1 = { .min = 8, .max = 18 }, -+ .m2 = { .min = 3, .max = 7 }, -+ .p = { .min = 5, .max = 80 }, -+ .p1 = { .min = 1, .max = 8 }, -+ .p2 = { .dot_limit = 200000, -+ .p2_slow = 10, .p2_fast = 5 }, -+}; -+ -+static const struct intel_limit intel_limits_i9xx_lvds = { -+ .dot = { .min = 20000, .max = 400000 }, -+ .vco = { .min = 1400000, .max = 2800000 }, -+ .n = { .min = 1, .max = 6 }, -+ .m = { .min = 70, .max = 120 }, -+ .m1 = { .min = 8, .max = 18 }, -+ .m2 = { .min = 3, .max = 7 }, -+ .p = { .min = 7, .max = 98 }, -+ .p1 = { .min = 1, .max = 8 }, -+ .p2 = { .dot_limit = 112000, -+ .p2_slow = 14, .p2_fast = 7 }, -+}; -+ -+ -+static const struct intel_limit intel_limits_g4x_sdvo = { -+ .dot = { .min = 25000, .max = 270000 }, -+ .vco = { .min = 1750000, .max = 3500000}, -+ .n = { .min = 1, .max = 4 }, -+ .m = { .min = 104, .max = 138 }, -+ .m1 = { .min = 17, .max = 23 }, -+ .m2 = { .min = 5, .max = 11 }, -+ .p = { .min = 10, .max = 30 }, -+ .p1 = { .min = 1, .max = 3}, -+ .p2 = { .dot_limit = 270000, -+ .p2_slow = 10, -+ .p2_fast = 10 -+ }, -+}; -+ -+static const struct intel_limit intel_limits_g4x_hdmi = { -+ .dot = { .min = 22000, .max = 400000 }, -+ .vco = { .min = 1750000, .max = 3500000}, -+ .n = { .min = 1, .max = 4 }, -+ .m = { .min = 104, .max = 138 }, -+ .m1 = { .min = 16, .max = 23 }, -+ .m2 = { .min = 5, .max = 11 }, -+ .p = { .min = 5, .max = 80 }, -+ .p1 = { .min = 1, .max = 8}, -+ .p2 = { .dot_limit = 165000, -+ .p2_slow = 10, .p2_fast = 5 }, -+}; -+ -+static const struct intel_limit intel_limits_g4x_single_channel_lvds = { -+ .dot = { .min = 20000, .max = 115000 }, -+ .vco = { .min = 1750000, .max = 3500000 }, -+ .n = { .min = 1, .max = 3 }, -+ .m = { .min = 104, .max = 138 }, -+ .m1 = { .min = 17, .max = 23 }, -+ .m2 = { .min = 5, .max = 11 }, -+ .p = { .min = 28, .max = 112 }, -+ .p1 = { .min = 2, .max = 8 }, -+ .p2 = { .dot_limit = 0, -+ .p2_slow = 14, .p2_fast = 14 -+ }, -+}; -+ -+static const struct intel_limit intel_limits_g4x_dual_channel_lvds = { -+ .dot = { .min = 80000, .max = 224000 }, -+ .vco = { .min = 1750000, .max = 3500000 }, -+ .n = { .min = 1, .max = 3 }, -+ .m = { .min = 104, .max = 138 }, -+ .m1 = { .min = 17, .max = 23 }, -+ .m2 = { .min = 5, .max = 11 }, -+ .p = { .min = 14, .max = 42 }, -+ .p1 = { .min = 2, .max = 6 }, -+ .p2 = { .dot_limit = 0, -+ .p2_slow = 7, .p2_fast = 7 -+ }, -+}; -+ -+static const struct intel_limit intel_limits_pineview_sdvo = { -+ .dot = { .min = 20000, .max = 400000}, -+ .vco = { .min = 1700000, .max = 3500000 }, -+ /* Pineview's Ncounter is a ring counter */ -+ .n = { .min = 3, .max = 6 }, -+ .m = { .min = 2, .max = 256 }, -+ /* Pineview only has one combined m divider, which we treat as m2. */ -+ .m1 = { .min = 0, .max = 0 }, -+ .m2 = { .min = 0, .max = 254 }, -+ .p = { .min = 5, .max = 80 }, -+ .p1 = { .min = 1, .max = 8 }, -+ .p2 = { .dot_limit = 200000, -+ .p2_slow = 10, .p2_fast = 5 }, -+}; -+ -+static const struct intel_limit intel_limits_pineview_lvds = { -+ .dot = { .min = 20000, .max = 400000 }, -+ .vco = { .min = 1700000, .max = 3500000 }, -+ .n = { .min = 3, .max = 6 }, -+ .m = { .min = 2, .max = 256 }, -+ .m1 = { .min = 0, .max = 0 }, -+ .m2 = { .min = 0, .max = 254 }, -+ .p = { .min = 7, .max = 112 }, -+ .p1 = { .min = 1, .max = 8 }, -+ .p2 = { .dot_limit = 112000, -+ .p2_slow = 14, .p2_fast = 14 }, -+}; -+ -+/* Ironlake / Sandybridge -+ * -+ * We calculate clock using (register_value + 2) for N/M1/M2, so here -+ * the range value for them is (actual_value - 2). -+ */ -+static const struct intel_limit intel_limits_ironlake_dac = { -+ .dot = { .min = 25000, .max = 350000 }, -+ .vco = { .min = 1760000, .max = 3510000 }, -+ .n = { .min = 1, .max = 5 }, -+ .m = { .min = 79, .max = 127 }, -+ .m1 = { .min = 12, .max = 22 }, -+ .m2 = { .min = 5, .max = 9 }, -+ .p = { .min = 5, .max = 80 }, -+ .p1 = { .min = 1, .max = 8 }, -+ .p2 = { .dot_limit = 225000, -+ .p2_slow = 10, .p2_fast = 5 }, -+}; -+ -+static const struct intel_limit intel_limits_ironlake_single_lvds = { -+ .dot = { .min = 25000, .max = 350000 }, -+ .vco = { .min = 1760000, .max = 3510000 }, -+ .n = { .min = 1, .max = 3 }, -+ .m = { .min = 79, .max = 118 }, -+ .m1 = { .min = 12, .max = 22 }, -+ .m2 = { .min = 5, .max = 9 }, -+ .p = { .min = 28, .max = 112 }, -+ .p1 = { .min = 2, .max = 8 }, -+ .p2 = { .dot_limit = 225000, -+ .p2_slow = 14, .p2_fast = 14 }, -+}; -+ -+static const struct intel_limit intel_limits_ironlake_dual_lvds = { -+ .dot = { .min = 25000, .max = 350000 }, -+ .vco = { .min = 1760000, .max = 3510000 }, -+ .n = { .min = 1, .max = 3 }, -+ .m = { .min = 79, .max = 127 }, -+ .m1 = { .min = 12, .max = 22 }, -+ .m2 = { .min = 5, .max = 9 }, -+ .p = { .min = 14, .max = 56 }, -+ .p1 = { .min = 2, .max = 8 }, -+ .p2 = { .dot_limit = 225000, -+ .p2_slow = 7, .p2_fast = 7 }, -+}; -+ -+/* LVDS 100mhz refclk limits. */ -+static const struct intel_limit intel_limits_ironlake_single_lvds_100m = { -+ .dot = { .min = 25000, .max = 350000 }, -+ .vco = { .min = 1760000, .max = 3510000 }, -+ .n = { .min = 1, .max = 2 }, -+ .m = { .min = 79, .max = 126 }, -+ .m1 = { .min = 12, .max = 22 }, -+ .m2 = { .min = 5, .max = 9 }, -+ .p = { .min = 28, .max = 112 }, -+ .p1 = { .min = 2, .max = 8 }, -+ .p2 = { .dot_limit = 225000, -+ .p2_slow = 14, .p2_fast = 14 }, -+}; -+ -+static const struct intel_limit intel_limits_ironlake_dual_lvds_100m = { -+ .dot = { .min = 25000, .max = 350000 }, -+ .vco = { .min = 1760000, .max = 3510000 }, -+ .n = { .min = 1, .max = 3 }, -+ .m = { .min = 79, .max = 126 }, -+ .m1 = { .min = 12, .max = 22 }, -+ .m2 = { .min = 5, .max = 9 }, -+ .p = { .min = 14, .max = 42 }, -+ .p1 = { .min = 2, .max = 6 }, -+ .p2 = { .dot_limit = 225000, -+ .p2_slow = 7, .p2_fast = 7 }, -+}; -+ -+static const struct intel_limit intel_limits_vlv = { -+ /* -+ * These are the data rate limits (measured in fast clocks) -+ * since those are the strictest limits we have. The fast -+ * clock and actual rate limits are more relaxed, so checking -+ * them would make no difference. -+ */ -+ .dot = { .min = 25000 * 5, .max = 270000 * 5 }, -+ .vco = { .min = 4000000, .max = 6000000 }, -+ .n = { .min = 1, .max = 7 }, -+ .m1 = { .min = 2, .max = 3 }, -+ .m2 = { .min = 11, .max = 156 }, -+ .p1 = { .min = 2, .max = 3 }, -+ .p2 = { .p2_slow = 2, .p2_fast = 20 }, /* slow=min, fast=max */ -+}; -+ -+static const struct intel_limit intel_limits_chv = { -+ /* -+ * These are the data rate limits (measured in fast clocks) -+ * since those are the strictest limits we have. The fast -+ * clock and actual rate limits are more relaxed, so checking -+ * them would make no difference. -+ */ -+ .dot = { .min = 25000 * 5, .max = 540000 * 5}, -+ .vco = { .min = 4800000, .max = 6480000 }, -+ .n = { .min = 1, .max = 1 }, -+ .m1 = { .min = 2, .max = 2 }, -+ .m2 = { .min = 24 << 22, .max = 175 << 22 }, -+ .p1 = { .min = 2, .max = 4 }, -+ .p2 = { .p2_slow = 1, .p2_fast = 14 }, -+}; -+ -+static const struct intel_limit intel_limits_bxt = { -+ /* FIXME: find real dot limits */ -+ .dot = { .min = 0, .max = INT_MAX }, -+ .vco = { .min = 4800000, .max = 6700000 }, -+ .n = { .min = 1, .max = 1 }, -+ .m1 = { .min = 2, .max = 2 }, -+ /* FIXME: find real m2 limits */ -+ .m2 = { .min = 2 << 22, .max = 255 << 22 }, -+ .p1 = { .min = 2, .max = 4 }, -+ .p2 = { .p2_slow = 1, .p2_fast = 20 }, -+}; -+ -+static void -+skl_wa_827(struct drm_i915_private *dev_priv, int pipe, bool enable) -+{ -+ if (enable) -+ I915_WRITE(CLKGATE_DIS_PSL(pipe), -+ I915_READ(CLKGATE_DIS_PSL(pipe)) | -+ DUPS1_GATING_DIS | DUPS2_GATING_DIS); -+ else -+ I915_WRITE(CLKGATE_DIS_PSL(pipe), -+ I915_READ(CLKGATE_DIS_PSL(pipe)) & -+ ~(DUPS1_GATING_DIS | DUPS2_GATING_DIS)); -+} -+ -+static bool -+needs_modeset(const struct drm_crtc_state *state) -+{ -+ return drm_atomic_crtc_needs_modeset(state); -+} -+ -+/* -+ * Platform specific helpers to calculate the port PLL loopback- (clock.m), -+ * and post-divider (clock.p) values, pre- (clock.vco) and post-divided fast -+ * (clock.dot) clock rates. This fast dot clock is fed to the port's IO logic. -+ * The helpers' return value is the rate of the clock that is fed to the -+ * display engine's pipe which can be the above fast dot clock rate or a -+ * divided-down version of it. -+ */ -+/* m1 is reserved as 0 in Pineview, n is a ring counter */ -+static int pnv_calc_dpll_params(int refclk, struct dpll *clock) -+{ -+ clock->m = clock->m2 + 2; -+ clock->p = clock->p1 * clock->p2; -+ if (WARN_ON(clock->n == 0 || clock->p == 0)) -+ return 0; -+ clock->vco = DIV_ROUND_CLOSEST(refclk * clock->m, clock->n); -+ clock->dot = DIV_ROUND_CLOSEST(clock->vco, clock->p); -+ -+ return clock->dot; -+} -+ -+static u32 i9xx_dpll_compute_m(struct dpll *dpll) -+{ -+ return 5 * (dpll->m1 + 2) + (dpll->m2 + 2); -+} -+ -+static int i9xx_calc_dpll_params(int refclk, struct dpll *clock) -+{ -+ clock->m = i9xx_dpll_compute_m(clock); -+ clock->p = clock->p1 * clock->p2; -+ if (WARN_ON(clock->n + 2 == 0 || clock->p == 0)) -+ return 0; -+ clock->vco = DIV_ROUND_CLOSEST(refclk * clock->m, clock->n + 2); -+ clock->dot = DIV_ROUND_CLOSEST(clock->vco, clock->p); -+ -+ return clock->dot; -+} -+ -+static int vlv_calc_dpll_params(int refclk, struct dpll *clock) -+{ -+ clock->m = clock->m1 * clock->m2; -+ clock->p = clock->p1 * clock->p2; -+ if (WARN_ON(clock->n == 0 || clock->p == 0)) -+ return 0; -+ clock->vco = DIV_ROUND_CLOSEST(refclk * clock->m, clock->n); -+ clock->dot = DIV_ROUND_CLOSEST(clock->vco, clock->p); -+ -+ return clock->dot / 5; -+} -+ -+int chv_calc_dpll_params(int refclk, struct dpll *clock) -+{ -+ clock->m = clock->m1 * clock->m2; -+ clock->p = clock->p1 * clock->p2; -+ if (WARN_ON(clock->n == 0 || clock->p == 0)) -+ return 0; -+ clock->vco = DIV_ROUND_CLOSEST_ULL((u64)refclk * clock->m, -+ clock->n << 22); -+ clock->dot = DIV_ROUND_CLOSEST(clock->vco, clock->p); -+ -+ return clock->dot / 5; -+} -+ -+#define INTELPllInvalid(s) do { /* DRM_DEBUG(s); */ return false; } while (0) -+ -+/* -+ * Returns whether the given set of divisors are valid for a given refclk with -+ * the given connectors. -+ */ -+static bool intel_PLL_is_valid(struct drm_i915_private *dev_priv, -+ const struct intel_limit *limit, -+ const struct dpll *clock) -+{ -+ if (clock->n < limit->n.min || limit->n.max < clock->n) -+ INTELPllInvalid("n out of range\n"); -+ if (clock->p1 < limit->p1.min || limit->p1.max < clock->p1) -+ INTELPllInvalid("p1 out of range\n"); -+ if (clock->m2 < limit->m2.min || limit->m2.max < clock->m2) -+ INTELPllInvalid("m2 out of range\n"); -+ if (clock->m1 < limit->m1.min || limit->m1.max < clock->m1) -+ INTELPllInvalid("m1 out of range\n"); -+ -+ if (!IS_PINEVIEW(dev_priv) && !IS_VALLEYVIEW(dev_priv) && -+ !IS_CHERRYVIEW(dev_priv) && !IS_GEN9_LP(dev_priv)) -+ if (clock->m1 <= clock->m2) -+ INTELPllInvalid("m1 <= m2\n"); -+ -+ if (!IS_VALLEYVIEW(dev_priv) && !IS_CHERRYVIEW(dev_priv) && -+ !IS_GEN9_LP(dev_priv)) { -+ if (clock->p < limit->p.min || limit->p.max < clock->p) -+ INTELPllInvalid("p out of range\n"); -+ if (clock->m < limit->m.min || limit->m.max < clock->m) -+ INTELPllInvalid("m out of range\n"); -+ } -+ -+ if (clock->vco < limit->vco.min || limit->vco.max < clock->vco) -+ INTELPllInvalid("vco out of range\n"); -+ /* XXX: We may need to be checking "Dot clock" depending on the multiplier, -+ * connector, etc., rather than just a single range. -+ */ -+ if (clock->dot < limit->dot.min || limit->dot.max < clock->dot) -+ INTELPllInvalid("dot out of range\n"); -+ -+ return true; -+} -+ -+static int -+i9xx_select_p2_div(const struct intel_limit *limit, -+ const struct intel_crtc_state *crtc_state, -+ int target) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { -+ /* -+ * For LVDS just rely on its current settings for dual-channel. -+ * We haven't figured out how to reliably set up different -+ * single/dual channel state, if we even can. -+ */ -+ if (intel_is_dual_link_lvds(dev_priv)) -+ return limit->p2.p2_fast; -+ else -+ return limit->p2.p2_slow; -+ } else { -+ if (target < limit->p2.dot_limit) -+ return limit->p2.p2_slow; -+ else -+ return limit->p2.p2_fast; -+ } -+} -+ -+/* -+ * Returns a set of divisors for the desired target clock with the given -+ * refclk, or FALSE. The returned values represent the clock equation: -+ * reflck * (5 * (m1 + 2) + (m2 + 2)) / (n + 2) / p1 / p2. -+ * -+ * Target and reference clocks are specified in kHz. -+ * -+ * If match_clock is provided, then best_clock P divider must match the P -+ * divider from @match_clock used for LVDS downclocking. -+ */ -+static bool -+i9xx_find_best_dpll(const struct intel_limit *limit, -+ struct intel_crtc_state *crtc_state, -+ int target, int refclk, struct dpll *match_clock, -+ struct dpll *best_clock) -+{ -+ struct drm_device *dev = crtc_state->base.crtc->dev; -+ struct dpll clock; -+ int err = target; -+ -+ memset(best_clock, 0, sizeof(*best_clock)); -+ -+ clock.p2 = i9xx_select_p2_div(limit, crtc_state, target); -+ -+ for (clock.m1 = limit->m1.min; clock.m1 <= limit->m1.max; -+ clock.m1++) { -+ for (clock.m2 = limit->m2.min; -+ clock.m2 <= limit->m2.max; clock.m2++) { -+ if (clock.m2 >= clock.m1) -+ break; -+ for (clock.n = limit->n.min; -+ clock.n <= limit->n.max; clock.n++) { -+ for (clock.p1 = limit->p1.min; -+ clock.p1 <= limit->p1.max; clock.p1++) { -+ int this_err; -+ -+ i9xx_calc_dpll_params(refclk, &clock); -+ if (!intel_PLL_is_valid(to_i915(dev), -+ limit, -+ &clock)) -+ continue; -+ if (match_clock && -+ clock.p != match_clock->p) -+ continue; -+ -+ this_err = abs(clock.dot - target); -+ if (this_err < err) { -+ *best_clock = clock; -+ err = this_err; -+ } -+ } -+ } -+ } -+ } -+ -+ return (err != target); -+} -+ -+/* -+ * Returns a set of divisors for the desired target clock with the given -+ * refclk, or FALSE. The returned values represent the clock equation: -+ * reflck * (5 * (m1 + 2) + (m2 + 2)) / (n + 2) / p1 / p2. -+ * -+ * Target and reference clocks are specified in kHz. -+ * -+ * If match_clock is provided, then best_clock P divider must match the P -+ * divider from @match_clock used for LVDS downclocking. -+ */ -+static bool -+pnv_find_best_dpll(const struct intel_limit *limit, -+ struct intel_crtc_state *crtc_state, -+ int target, int refclk, struct dpll *match_clock, -+ struct dpll *best_clock) -+{ -+ struct drm_device *dev = crtc_state->base.crtc->dev; -+ struct dpll clock; -+ int err = target; -+ -+ memset(best_clock, 0, sizeof(*best_clock)); -+ -+ clock.p2 = i9xx_select_p2_div(limit, crtc_state, target); -+ -+ for (clock.m1 = limit->m1.min; clock.m1 <= limit->m1.max; -+ clock.m1++) { -+ for (clock.m2 = limit->m2.min; -+ clock.m2 <= limit->m2.max; clock.m2++) { -+ for (clock.n = limit->n.min; -+ clock.n <= limit->n.max; clock.n++) { -+ for (clock.p1 = limit->p1.min; -+ clock.p1 <= limit->p1.max; clock.p1++) { -+ int this_err; -+ -+ pnv_calc_dpll_params(refclk, &clock); -+ if (!intel_PLL_is_valid(to_i915(dev), -+ limit, -+ &clock)) -+ continue; -+ if (match_clock && -+ clock.p != match_clock->p) -+ continue; -+ -+ this_err = abs(clock.dot - target); -+ if (this_err < err) { -+ *best_clock = clock; -+ err = this_err; -+ } -+ } -+ } -+ } -+ } -+ -+ return (err != target); -+} -+ -+/* -+ * Returns a set of divisors for the desired target clock with the given -+ * refclk, or FALSE. The returned values represent the clock equation: -+ * reflck * (5 * (m1 + 2) + (m2 + 2)) / (n + 2) / p1 / p2. -+ * -+ * Target and reference clocks are specified in kHz. -+ * -+ * If match_clock is provided, then best_clock P divider must match the P -+ * divider from @match_clock used for LVDS downclocking. -+ */ -+static bool -+g4x_find_best_dpll(const struct intel_limit *limit, -+ struct intel_crtc_state *crtc_state, -+ int target, int refclk, struct dpll *match_clock, -+ struct dpll *best_clock) -+{ -+ struct drm_device *dev = crtc_state->base.crtc->dev; -+ struct dpll clock; -+ int max_n; -+ bool found = false; -+ /* approximately equals target * 0.00585 */ -+ int err_most = (target >> 8) + (target >> 9); -+ -+ memset(best_clock, 0, sizeof(*best_clock)); -+ -+ clock.p2 = i9xx_select_p2_div(limit, crtc_state, target); -+ -+ max_n = limit->n.max; -+ /* based on hardware requirement, prefer smaller n to precision */ -+ for (clock.n = limit->n.min; clock.n <= max_n; clock.n++) { -+ /* based on hardware requirement, prefere larger m1,m2 */ -+ for (clock.m1 = limit->m1.max; -+ clock.m1 >= limit->m1.min; clock.m1--) { -+ for (clock.m2 = limit->m2.max; -+ clock.m2 >= limit->m2.min; clock.m2--) { -+ for (clock.p1 = limit->p1.max; -+ clock.p1 >= limit->p1.min; clock.p1--) { -+ int this_err; -+ -+ i9xx_calc_dpll_params(refclk, &clock); -+ if (!intel_PLL_is_valid(to_i915(dev), -+ limit, -+ &clock)) -+ continue; -+ -+ this_err = abs(clock.dot - target); -+ if (this_err < err_most) { -+ *best_clock = clock; -+ err_most = this_err; -+ max_n = clock.n; -+ found = true; -+ } -+ } -+ } -+ } -+ } -+ return found; -+} -+ -+/* -+ * Check if the calculated PLL configuration is more optimal compared to the -+ * best configuration and error found so far. Return the calculated error. -+ */ -+static bool vlv_PLL_is_optimal(struct drm_device *dev, int target_freq, -+ const struct dpll *calculated_clock, -+ const struct dpll *best_clock, -+ unsigned int best_error_ppm, -+ unsigned int *error_ppm) -+{ -+ /* -+ * For CHV ignore the error and consider only the P value. -+ * Prefer a bigger P value based on HW requirements. -+ */ -+ if (IS_CHERRYVIEW(to_i915(dev))) { -+ *error_ppm = 0; -+ -+ return calculated_clock->p > best_clock->p; -+ } -+ -+ if (WARN_ON_ONCE(!target_freq)) -+ return false; -+ -+ *error_ppm = div_u64(1000000ULL * -+ abs(target_freq - calculated_clock->dot), -+ target_freq); -+ /* -+ * Prefer a better P value over a better (smaller) error if the error -+ * is small. Ensure this preference for future configurations too by -+ * setting the error to 0. -+ */ -+ if (*error_ppm < 100 && calculated_clock->p > best_clock->p) { -+ *error_ppm = 0; -+ -+ return true; -+ } -+ -+ return *error_ppm + 10 < best_error_ppm; -+} -+ -+/* -+ * Returns a set of divisors for the desired target clock with the given -+ * refclk, or FALSE. The returned values represent the clock equation: -+ * reflck * (5 * (m1 + 2) + (m2 + 2)) / (n + 2) / p1 / p2. -+ */ -+static bool -+vlv_find_best_dpll(const struct intel_limit *limit, -+ struct intel_crtc_state *crtc_state, -+ int target, int refclk, struct dpll *match_clock, -+ struct dpll *best_clock) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_device *dev = crtc->base.dev; -+ struct dpll clock; -+ unsigned int bestppm = 1000000; -+ /* min update 19.2 MHz */ -+ int max_n = min(limit->n.max, refclk / 19200); -+ bool found = false; -+ -+ target *= 5; /* fast clock */ -+ -+ memset(best_clock, 0, sizeof(*best_clock)); -+ -+ /* based on hardware requirement, prefer smaller n to precision */ -+ for (clock.n = limit->n.min; clock.n <= max_n; clock.n++) { -+ for (clock.p1 = limit->p1.max; clock.p1 >= limit->p1.min; clock.p1--) { -+ for (clock.p2 = limit->p2.p2_fast; clock.p2 >= limit->p2.p2_slow; -+ clock.p2 -= clock.p2 > 10 ? 2 : 1) { -+ clock.p = clock.p1 * clock.p2; -+ /* based on hardware requirement, prefer bigger m1,m2 values */ -+ for (clock.m1 = limit->m1.min; clock.m1 <= limit->m1.max; clock.m1++) { -+ unsigned int ppm; -+ -+ clock.m2 = DIV_ROUND_CLOSEST(target * clock.p * clock.n, -+ refclk * clock.m1); -+ -+ vlv_calc_dpll_params(refclk, &clock); -+ -+ if (!intel_PLL_is_valid(to_i915(dev), -+ limit, -+ &clock)) -+ continue; -+ -+ if (!vlv_PLL_is_optimal(dev, target, -+ &clock, -+ best_clock, -+ bestppm, &ppm)) -+ continue; -+ -+ *best_clock = clock; -+ bestppm = ppm; -+ found = true; -+ } -+ } -+ } -+ } -+ -+ return found; -+} -+ -+/* -+ * Returns a set of divisors for the desired target clock with the given -+ * refclk, or FALSE. The returned values represent the clock equation: -+ * reflck * (5 * (m1 + 2) + (m2 + 2)) / (n + 2) / p1 / p2. -+ */ -+static bool -+chv_find_best_dpll(const struct intel_limit *limit, -+ struct intel_crtc_state *crtc_state, -+ int target, int refclk, struct dpll *match_clock, -+ struct dpll *best_clock) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_device *dev = crtc->base.dev; -+ unsigned int best_error_ppm; -+ struct dpll clock; -+ u64 m2; -+ int found = false; -+ -+ memset(best_clock, 0, sizeof(*best_clock)); -+ best_error_ppm = 1000000; -+ -+ /* -+ * Based on hardware doc, the n always set to 1, and m1 always -+ * set to 2. If requires to support 200Mhz refclk, we need to -+ * revisit this because n may not 1 anymore. -+ */ -+ clock.n = 1, clock.m1 = 2; -+ target *= 5; /* fast clock */ -+ -+ for (clock.p1 = limit->p1.max; clock.p1 >= limit->p1.min; clock.p1--) { -+ for (clock.p2 = limit->p2.p2_fast; -+ clock.p2 >= limit->p2.p2_slow; -+ clock.p2 -= clock.p2 > 10 ? 2 : 1) { -+ unsigned int error_ppm; -+ -+ clock.p = clock.p1 * clock.p2; -+ -+ m2 = DIV_ROUND_CLOSEST_ULL(((u64)target * clock.p * -+ clock.n) << 22, refclk * clock.m1); -+ -+ if (m2 > INT_MAX/clock.m1) -+ continue; -+ -+ clock.m2 = m2; -+ -+ chv_calc_dpll_params(refclk, &clock); -+ -+ if (!intel_PLL_is_valid(to_i915(dev), limit, &clock)) -+ continue; -+ -+ if (!vlv_PLL_is_optimal(dev, target, &clock, best_clock, -+ best_error_ppm, &error_ppm)) -+ continue; -+ -+ *best_clock = clock; -+ best_error_ppm = error_ppm; -+ found = true; -+ } -+ } -+ -+ return found; -+} -+ -+bool bxt_find_best_dpll(struct intel_crtc_state *crtc_state, -+ struct dpll *best_clock) -+{ -+ int refclk = 100000; -+ const struct intel_limit *limit = &intel_limits_bxt; -+ -+ return chv_find_best_dpll(limit, crtc_state, -+ crtc_state->port_clock, refclk, -+ NULL, best_clock); -+} -+ -+bool intel_crtc_active(struct intel_crtc *crtc) -+{ -+ /* Be paranoid as we can arrive here with only partial -+ * state retrieved from the hardware during setup. -+ * -+ * We can ditch the adjusted_mode.crtc_clock check as soon -+ * as Haswell has gained clock readout/fastboot support. -+ * -+ * We can ditch the crtc->primary->state->fb check as soon as we can -+ * properly reconstruct framebuffers. -+ * -+ * FIXME: The intel_crtc->active here should be switched to -+ * crtc->state->active once we have proper CRTC states wired up -+ * for atomic. -+ */ -+ return crtc->active && crtc->base.primary->state->fb && -+ crtc->config->base.adjusted_mode.crtc_clock; -+} -+ -+enum transcoder intel_pipe_to_cpu_transcoder(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ struct intel_crtc *crtc = intel_get_crtc_for_pipe(dev_priv, pipe); -+ -+ return crtc->config->cpu_transcoder; -+} -+ -+static bool pipe_scanline_is_moving(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ i915_reg_t reg = PIPEDSL(pipe); -+ u32 line1, line2; -+ u32 line_mask; -+ -+ if (IS_GEN(dev_priv, 2)) -+ line_mask = DSL_LINEMASK_GEN2; -+ else -+ line_mask = DSL_LINEMASK_GEN3; -+ -+ line1 = I915_READ(reg) & line_mask; -+ msleep(5); -+ line2 = I915_READ(reg) & line_mask; -+ -+ return line1 != line2; -+} -+ -+static void wait_for_pipe_scanline_moving(struct intel_crtc *crtc, bool state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ -+ /* Wait for the display line to settle/start moving */ -+ if (wait_for(pipe_scanline_is_moving(dev_priv, pipe) == state, 100)) -+ DRM_ERROR("pipe %c scanline %s wait timed out\n", -+ pipe_name(pipe), onoff(state)); -+} -+ -+static void intel_wait_for_pipe_scanline_stopped(struct intel_crtc *crtc) -+{ -+ wait_for_pipe_scanline_moving(crtc, false); -+} -+ -+static void intel_wait_for_pipe_scanline_moving(struct intel_crtc *crtc) -+{ -+ wait_for_pipe_scanline_moving(crtc, true); -+} -+ -+static void -+intel_wait_for_pipe_off(const struct intel_crtc_state *old_crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ if (INTEL_GEN(dev_priv) >= 4) { -+ enum transcoder cpu_transcoder = old_crtc_state->cpu_transcoder; -+ i915_reg_t reg = PIPECONF(cpu_transcoder); -+ -+ /* Wait for the Pipe State to go off */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ reg, I965_PIPECONF_ACTIVE, 0, -+ 100)) -+ WARN(1, "pipe_off wait timed out\n"); -+ } else { -+ intel_wait_for_pipe_scanline_stopped(crtc); -+ } -+} -+ -+/* Only for pre-ILK configs */ -+void assert_pll(struct drm_i915_private *dev_priv, -+ enum pipe pipe, bool state) -+{ -+ u32 val; -+ bool cur_state; -+ -+ val = I915_READ(DPLL(pipe)); -+ cur_state = !!(val & DPLL_VCO_ENABLE); -+ I915_STATE_WARN(cur_state != state, -+ "PLL state assertion failure (expected %s, current %s)\n", -+ onoff(state), onoff(cur_state)); -+} -+ -+/* XXX: the dsi pll is shared between MIPI DSI ports */ -+void assert_dsi_pll(struct drm_i915_private *dev_priv, bool state) -+{ -+ u32 val; -+ bool cur_state; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ val = vlv_cck_read(dev_priv, CCK_REG_DSI_PLL_CONTROL); -+ mutex_unlock(&dev_priv->sb_lock); -+ -+ cur_state = val & DSI_PLL_VCO_EN; -+ I915_STATE_WARN(cur_state != state, -+ "DSI PLL state assertion failure (expected %s, current %s)\n", -+ onoff(state), onoff(cur_state)); -+} -+ -+static void assert_fdi_tx(struct drm_i915_private *dev_priv, -+ enum pipe pipe, bool state) -+{ -+ bool cur_state; -+ enum transcoder cpu_transcoder = intel_pipe_to_cpu_transcoder(dev_priv, -+ pipe); -+ -+ if (HAS_DDI(dev_priv)) { -+ /* DDI does not have a specific FDI_TX register */ -+ u32 val = I915_READ(TRANS_DDI_FUNC_CTL(cpu_transcoder)); -+ cur_state = !!(val & TRANS_DDI_FUNC_ENABLE); -+ } else { -+ u32 val = I915_READ(FDI_TX_CTL(pipe)); -+ cur_state = !!(val & FDI_TX_ENABLE); -+ } -+ I915_STATE_WARN(cur_state != state, -+ "FDI TX state assertion failure (expected %s, current %s)\n", -+ onoff(state), onoff(cur_state)); -+} -+#define assert_fdi_tx_enabled(d, p) assert_fdi_tx(d, p, true) -+#define assert_fdi_tx_disabled(d, p) assert_fdi_tx(d, p, false) -+ -+static void assert_fdi_rx(struct drm_i915_private *dev_priv, -+ enum pipe pipe, bool state) -+{ -+ u32 val; -+ bool cur_state; -+ -+ val = I915_READ(FDI_RX_CTL(pipe)); -+ cur_state = !!(val & FDI_RX_ENABLE); -+ I915_STATE_WARN(cur_state != state, -+ "FDI RX state assertion failure (expected %s, current %s)\n", -+ onoff(state), onoff(cur_state)); -+} -+#define assert_fdi_rx_enabled(d, p) assert_fdi_rx(d, p, true) -+#define assert_fdi_rx_disabled(d, p) assert_fdi_rx(d, p, false) -+ -+static void assert_fdi_tx_pll_enabled(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ u32 val; -+ -+ /* ILK FDI PLL is always enabled */ -+ if (IS_GEN(dev_priv, 5)) -+ return; -+ -+ /* On Haswell, DDI ports are responsible for the FDI PLL setup */ -+ if (HAS_DDI(dev_priv)) -+ return; -+ -+ val = I915_READ(FDI_TX_CTL(pipe)); -+ I915_STATE_WARN(!(val & FDI_TX_PLL_ENABLE), "FDI TX PLL assertion failure, should be active but is disabled\n"); -+} -+ -+void assert_fdi_rx_pll(struct drm_i915_private *dev_priv, -+ enum pipe pipe, bool state) -+{ -+ u32 val; -+ bool cur_state; -+ -+ val = I915_READ(FDI_RX_CTL(pipe)); -+ cur_state = !!(val & FDI_RX_PLL_ENABLE); -+ I915_STATE_WARN(cur_state != state, -+ "FDI RX PLL assertion failure (expected %s, current %s)\n", -+ onoff(state), onoff(cur_state)); -+} -+ -+void assert_panel_unlocked(struct drm_i915_private *dev_priv, enum pipe pipe) -+{ -+ i915_reg_t pp_reg; -+ u32 val; -+ enum pipe panel_pipe = INVALID_PIPE; -+ bool locked = true; -+ -+ if (WARN_ON(HAS_DDI(dev_priv))) -+ return; -+ -+ if (HAS_PCH_SPLIT(dev_priv)) { -+ u32 port_sel; -+ -+ pp_reg = PP_CONTROL(0); -+ port_sel = I915_READ(PP_ON_DELAYS(0)) & PANEL_PORT_SELECT_MASK; -+ -+ switch (port_sel) { -+ case PANEL_PORT_SELECT_LVDS: -+ intel_lvds_port_enabled(dev_priv, PCH_LVDS, &panel_pipe); -+ break; -+ case PANEL_PORT_SELECT_DPA: -+ intel_dp_port_enabled(dev_priv, DP_A, PORT_A, &panel_pipe); -+ break; -+ case PANEL_PORT_SELECT_DPC: -+ intel_dp_port_enabled(dev_priv, PCH_DP_C, PORT_C, &panel_pipe); -+ break; -+ case PANEL_PORT_SELECT_DPD: -+ intel_dp_port_enabled(dev_priv, PCH_DP_D, PORT_D, &panel_pipe); -+ break; -+ default: -+ MISSING_CASE(port_sel); -+ break; -+ } -+ } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ /* presumably write lock depends on pipe, not port select */ -+ pp_reg = PP_CONTROL(pipe); -+ panel_pipe = pipe; -+ } else { -+ u32 port_sel; -+ -+ pp_reg = PP_CONTROL(0); -+ port_sel = I915_READ(PP_ON_DELAYS(0)) & PANEL_PORT_SELECT_MASK; -+ -+ WARN_ON(port_sel != PANEL_PORT_SELECT_LVDS); -+ intel_lvds_port_enabled(dev_priv, LVDS, &panel_pipe); -+ } -+ -+ val = I915_READ(pp_reg); -+ if (!(val & PANEL_POWER_ON) || -+ ((val & PANEL_UNLOCK_MASK) == PANEL_UNLOCK_REGS)) -+ locked = false; -+ -+ I915_STATE_WARN(panel_pipe == pipe && locked, -+ "panel assertion failure, pipe %c regs locked\n", -+ pipe_name(pipe)); -+} -+ -+void assert_pipe(struct drm_i915_private *dev_priv, -+ enum pipe pipe, bool state) -+{ -+ bool cur_state; -+ enum transcoder cpu_transcoder = intel_pipe_to_cpu_transcoder(dev_priv, -+ pipe); -+ enum intel_display_power_domain power_domain; -+ intel_wakeref_t wakeref; -+ -+ /* we keep both pipes enabled on 830 */ -+ if (IS_I830(dev_priv)) -+ state = true; -+ -+ power_domain = POWER_DOMAIN_TRANSCODER(cpu_transcoder); -+ wakeref = intel_display_power_get_if_enabled(dev_priv, power_domain); -+ if (wakeref) { -+ u32 val = I915_READ(PIPECONF(cpu_transcoder)); -+ cur_state = !!(val & PIPECONF_ENABLE); -+ -+ intel_display_power_put(dev_priv, power_domain, wakeref); -+ } else { -+ cur_state = false; -+ } -+ -+ I915_STATE_WARN(cur_state != state, -+ "pipe %c assertion failure (expected %s, current %s)\n", -+ pipe_name(pipe), onoff(state), onoff(cur_state)); -+} -+ -+static void assert_plane(struct intel_plane *plane, bool state) -+{ -+ enum pipe pipe; -+ bool cur_state; -+ -+ cur_state = plane->get_hw_state(plane, &pipe); -+ -+ I915_STATE_WARN(cur_state != state, -+ "%s assertion failure (expected %s, current %s)\n", -+ plane->base.name, onoff(state), onoff(cur_state)); -+} -+ -+#define assert_plane_enabled(p) assert_plane(p, true) -+#define assert_plane_disabled(p) assert_plane(p, false) -+ -+static void assert_planes_disabled(struct intel_crtc *crtc) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct intel_plane *plane; -+ -+ for_each_intel_plane_on_crtc(&dev_priv->drm, crtc, plane) -+ assert_plane_disabled(plane); -+} -+ -+static void assert_vblank_disabled(struct drm_crtc *crtc) -+{ -+ if (I915_STATE_WARN_ON(drm_crtc_vblank_get(crtc) == 0)) -+ drm_crtc_vblank_put(crtc); -+} -+ -+void assert_pch_transcoder_disabled(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ u32 val; -+ bool enabled; -+ -+ val = I915_READ(PCH_TRANSCONF(pipe)); -+ enabled = !!(val & TRANS_ENABLE); -+ I915_STATE_WARN(enabled, -+ "transcoder assertion failed, should be off on pipe %c but is still active\n", -+ pipe_name(pipe)); -+} -+ -+static void assert_pch_dp_disabled(struct drm_i915_private *dev_priv, -+ enum pipe pipe, enum port port, -+ i915_reg_t dp_reg) -+{ -+ enum pipe port_pipe; -+ bool state; -+ -+ state = intel_dp_port_enabled(dev_priv, dp_reg, port, &port_pipe); -+ -+ I915_STATE_WARN(state && port_pipe == pipe, -+ "PCH DP %c enabled on transcoder %c, should be disabled\n", -+ port_name(port), pipe_name(pipe)); -+ -+ I915_STATE_WARN(HAS_PCH_IBX(dev_priv) && !state && port_pipe == PIPE_B, -+ "IBX PCH DP %c still using transcoder B\n", -+ port_name(port)); -+} -+ -+static void assert_pch_hdmi_disabled(struct drm_i915_private *dev_priv, -+ enum pipe pipe, enum port port, -+ i915_reg_t hdmi_reg) -+{ -+ enum pipe port_pipe; -+ bool state; -+ -+ state = intel_sdvo_port_enabled(dev_priv, hdmi_reg, &port_pipe); -+ -+ I915_STATE_WARN(state && port_pipe == pipe, -+ "PCH HDMI %c enabled on transcoder %c, should be disabled\n", -+ port_name(port), pipe_name(pipe)); -+ -+ I915_STATE_WARN(HAS_PCH_IBX(dev_priv) && !state && port_pipe == PIPE_B, -+ "IBX PCH HDMI %c still using transcoder B\n", -+ port_name(port)); -+} -+ -+static void assert_pch_ports_disabled(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ enum pipe port_pipe; -+ -+ assert_pch_dp_disabled(dev_priv, pipe, PORT_B, PCH_DP_B); -+ assert_pch_dp_disabled(dev_priv, pipe, PORT_C, PCH_DP_C); -+ assert_pch_dp_disabled(dev_priv, pipe, PORT_D, PCH_DP_D); -+ -+ I915_STATE_WARN(intel_crt_port_enabled(dev_priv, PCH_ADPA, &port_pipe) && -+ port_pipe == pipe, -+ "PCH VGA enabled on transcoder %c, should be disabled\n", -+ pipe_name(pipe)); -+ -+ I915_STATE_WARN(intel_lvds_port_enabled(dev_priv, PCH_LVDS, &port_pipe) && -+ port_pipe == pipe, -+ "PCH LVDS enabled on transcoder %c, should be disabled\n", -+ pipe_name(pipe)); -+ -+ /* PCH SDVOB multiplex with HDMIB */ -+ assert_pch_hdmi_disabled(dev_priv, pipe, PORT_B, PCH_HDMIB); -+ assert_pch_hdmi_disabled(dev_priv, pipe, PORT_C, PCH_HDMIC); -+ assert_pch_hdmi_disabled(dev_priv, pipe, PORT_D, PCH_HDMID); -+} -+ -+static void _vlv_enable_pll(struct intel_crtc *crtc, -+ const struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ -+ I915_WRITE(DPLL(pipe), pipe_config->dpll_hw_state.dpll); -+ POSTING_READ(DPLL(pipe)); -+ udelay(150); -+ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ DPLL(pipe), -+ DPLL_LOCK_VLV, -+ DPLL_LOCK_VLV, -+ 1)) -+ DRM_ERROR("DPLL %d failed to lock\n", pipe); -+} -+ -+static void vlv_enable_pll(struct intel_crtc *crtc, -+ const struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ -+ assert_pipe_disabled(dev_priv, pipe); -+ -+ /* PLL is protected by panel, make sure we can write it */ -+ assert_panel_unlocked(dev_priv, pipe); -+ -+ if (pipe_config->dpll_hw_state.dpll & DPLL_VCO_ENABLE) -+ _vlv_enable_pll(crtc, pipe_config); -+ -+ I915_WRITE(DPLL_MD(pipe), pipe_config->dpll_hw_state.dpll_md); -+ POSTING_READ(DPLL_MD(pipe)); -+} -+ -+ -+static void _chv_enable_pll(struct intel_crtc *crtc, -+ const struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ enum dpio_channel port = vlv_pipe_to_channel(pipe); -+ u32 tmp; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ /* Enable back the 10bit clock to display controller */ -+ tmp = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW14(port)); -+ tmp |= DPIO_DCLKP_EN; -+ vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW14(port), tmp); -+ -+ mutex_unlock(&dev_priv->sb_lock); -+ -+ /* -+ * Need to wait > 100ns between dclkp clock enable bit and PLL enable. -+ */ -+ udelay(1); -+ -+ /* Enable PLL */ -+ I915_WRITE(DPLL(pipe), pipe_config->dpll_hw_state.dpll); -+ -+ /* Check PLL is locked */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ DPLL(pipe), DPLL_LOCK_VLV, DPLL_LOCK_VLV, -+ 1)) -+ DRM_ERROR("PLL %d failed to lock\n", pipe); -+} -+ -+static void chv_enable_pll(struct intel_crtc *crtc, -+ const struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ -+ assert_pipe_disabled(dev_priv, pipe); -+ -+ /* PLL is protected by panel, make sure we can write it */ -+ assert_panel_unlocked(dev_priv, pipe); -+ -+ if (pipe_config->dpll_hw_state.dpll & DPLL_VCO_ENABLE) -+ _chv_enable_pll(crtc, pipe_config); -+ -+ if (pipe != PIPE_A) { -+ /* -+ * WaPixelRepeatModeFixForC0:chv -+ * -+ * DPLLCMD is AWOL. Use chicken bits to propagate -+ * the value from DPLLBMD to either pipe B or C. -+ */ -+ I915_WRITE(CBR4_VLV, CBR_DPLLBMD_PIPE(pipe)); -+ I915_WRITE(DPLL_MD(PIPE_B), pipe_config->dpll_hw_state.dpll_md); -+ I915_WRITE(CBR4_VLV, 0); -+ dev_priv->chv_dpll_md[pipe] = pipe_config->dpll_hw_state.dpll_md; -+ -+ /* -+ * DPLLB VGA mode also seems to cause problems. -+ * We should always have it disabled. -+ */ -+ WARN_ON((I915_READ(DPLL(PIPE_B)) & DPLL_VGA_MODE_DIS) == 0); -+ } else { -+ I915_WRITE(DPLL_MD(pipe), pipe_config->dpll_hw_state.dpll_md); -+ POSTING_READ(DPLL_MD(pipe)); -+ } -+} -+ -+static bool i9xx_has_pps(struct drm_i915_private *dev_priv) -+{ -+ if (IS_I830(dev_priv)) -+ return false; -+ -+ return IS_PINEVIEW(dev_priv) || IS_MOBILE(dev_priv); -+} -+ -+static void i9xx_enable_pll(struct intel_crtc *crtc, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ i915_reg_t reg = DPLL(crtc->pipe); -+ u32 dpll = crtc_state->dpll_hw_state.dpll; -+ int i; -+ -+ assert_pipe_disabled(dev_priv, crtc->pipe); -+ -+ /* PLL is protected by panel, make sure we can write it */ -+ if (i9xx_has_pps(dev_priv)) -+ assert_panel_unlocked(dev_priv, crtc->pipe); -+ -+ /* -+ * Apparently we need to have VGA mode enabled prior to changing -+ * the P1/P2 dividers. Otherwise the DPLL will keep using the old -+ * dividers, even though the register value does change. -+ */ -+ I915_WRITE(reg, dpll & ~DPLL_VGA_MODE_DIS); -+ I915_WRITE(reg, dpll); -+ -+ /* Wait for the clocks to stabilize. */ -+ POSTING_READ(reg); -+ udelay(150); -+ -+ if (INTEL_GEN(dev_priv) >= 4) { -+ I915_WRITE(DPLL_MD(crtc->pipe), -+ crtc_state->dpll_hw_state.dpll_md); -+ } else { -+ /* The pixel multiplier can only be updated once the -+ * DPLL is enabled and the clocks are stable. -+ * -+ * So write it again. -+ */ -+ I915_WRITE(reg, dpll); -+ } -+ -+ /* We do this three times for luck */ -+ for (i = 0; i < 3; i++) { -+ I915_WRITE(reg, dpll); -+ POSTING_READ(reg); -+ udelay(150); /* wait for warmup */ -+ } -+} -+ -+static void i9xx_disable_pll(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ -+ /* Don't disable pipe or pipe PLLs if needed */ -+ if (IS_I830(dev_priv)) -+ return; -+ -+ /* Make sure the pipe isn't still relying on us */ -+ assert_pipe_disabled(dev_priv, pipe); -+ -+ I915_WRITE(DPLL(pipe), DPLL_VGA_MODE_DIS); -+ POSTING_READ(DPLL(pipe)); -+} -+ -+static void vlv_disable_pll(struct drm_i915_private *dev_priv, enum pipe pipe) -+{ -+ u32 val; -+ -+ /* Make sure the pipe isn't still relying on us */ -+ assert_pipe_disabled(dev_priv, pipe); -+ -+ val = DPLL_INTEGRATED_REF_CLK_VLV | -+ DPLL_REF_CLK_ENABLE_VLV | DPLL_VGA_MODE_DIS; -+ if (pipe != PIPE_A) -+ val |= DPLL_INTEGRATED_CRI_CLK_VLV; -+ -+ I915_WRITE(DPLL(pipe), val); -+ POSTING_READ(DPLL(pipe)); -+} -+ -+static void chv_disable_pll(struct drm_i915_private *dev_priv, enum pipe pipe) -+{ -+ enum dpio_channel port = vlv_pipe_to_channel(pipe); -+ u32 val; -+ -+ /* Make sure the pipe isn't still relying on us */ -+ assert_pipe_disabled(dev_priv, pipe); -+ -+ val = DPLL_SSC_REF_CLK_CHV | -+ DPLL_REF_CLK_ENABLE_VLV | DPLL_VGA_MODE_DIS; -+ if (pipe != PIPE_A) -+ val |= DPLL_INTEGRATED_CRI_CLK_VLV; -+ -+ I915_WRITE(DPLL(pipe), val); -+ POSTING_READ(DPLL(pipe)); -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ /* Disable 10bit clock to display controller */ -+ val = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW14(port)); -+ val &= ~DPIO_DCLKP_EN; -+ vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW14(port), val); -+ -+ mutex_unlock(&dev_priv->sb_lock); -+} -+ -+void vlv_wait_port_ready(struct drm_i915_private *dev_priv, -+ struct intel_digital_port *dport, -+ unsigned int expected_mask) -+{ -+ u32 port_mask; -+ i915_reg_t dpll_reg; -+ -+ switch (dport->base.port) { -+ case PORT_B: -+ port_mask = DPLL_PORTB_READY_MASK; -+ dpll_reg = DPLL(0); -+ break; -+ case PORT_C: -+ port_mask = DPLL_PORTC_READY_MASK; -+ dpll_reg = DPLL(0); -+ expected_mask <<= 4; -+ break; -+ case PORT_D: -+ port_mask = DPLL_PORTD_READY_MASK; -+ dpll_reg = DPIO_PHY_STATUS; -+ break; -+ default: -+ BUG(); -+ } -+ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ dpll_reg, port_mask, expected_mask, -+ 1000)) -+ WARN(1, "timed out waiting for port %c ready: got 0x%x, expected 0x%x\n", -+ port_name(dport->base.port), -+ I915_READ(dpll_reg) & port_mask, expected_mask); -+} -+ -+static void ironlake_enable_pch_transcoder(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ i915_reg_t reg; -+ u32 val, pipeconf_val; -+ -+ /* Make sure PCH DPLL is enabled */ -+ assert_shared_dpll_enabled(dev_priv, crtc_state->shared_dpll); -+ -+ /* FDI must be feeding us bits for PCH ports */ -+ assert_fdi_tx_enabled(dev_priv, pipe); -+ assert_fdi_rx_enabled(dev_priv, pipe); -+ -+ if (HAS_PCH_CPT(dev_priv)) { -+ /* Workaround: Set the timing override bit before enabling the -+ * pch transcoder. */ -+ reg = TRANS_CHICKEN2(pipe); -+ val = I915_READ(reg); -+ val |= TRANS_CHICKEN2_TIMING_OVERRIDE; -+ I915_WRITE(reg, val); -+ } -+ -+ reg = PCH_TRANSCONF(pipe); -+ val = I915_READ(reg); -+ pipeconf_val = I915_READ(PIPECONF(pipe)); -+ -+ if (HAS_PCH_IBX(dev_priv)) { -+ /* -+ * Make the BPC in transcoder be consistent with -+ * that in pipeconf reg. For HDMI we must use 8bpc -+ * here for both 8bpc and 12bpc. -+ */ -+ val &= ~PIPECONF_BPC_MASK; -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) -+ val |= PIPECONF_8BPC; -+ else -+ val |= pipeconf_val & PIPECONF_BPC_MASK; -+ } -+ -+ val &= ~TRANS_INTERLACE_MASK; -+ if ((pipeconf_val & PIPECONF_INTERLACE_MASK) == PIPECONF_INTERLACED_ILK) { -+ if (HAS_PCH_IBX(dev_priv) && -+ intel_crtc_has_type(crtc_state, INTEL_OUTPUT_SDVO)) -+ val |= TRANS_LEGACY_INTERLACED_ILK; -+ else -+ val |= TRANS_INTERLACED; -+ } else { -+ val |= TRANS_PROGRESSIVE; -+ } -+ -+ I915_WRITE(reg, val | TRANS_ENABLE); -+ if (intel_wait_for_register(&dev_priv->uncore, -+ reg, TRANS_STATE_ENABLE, TRANS_STATE_ENABLE, -+ 100)) -+ DRM_ERROR("failed to enable transcoder %c\n", pipe_name(pipe)); -+} -+ -+static void lpt_enable_pch_transcoder(struct drm_i915_private *dev_priv, -+ enum transcoder cpu_transcoder) -+{ -+ u32 val, pipeconf_val; -+ -+ /* FDI must be feeding us bits for PCH ports */ -+ assert_fdi_tx_enabled(dev_priv, (enum pipe) cpu_transcoder); -+ assert_fdi_rx_enabled(dev_priv, PIPE_A); -+ -+ /* Workaround: set timing override bit. */ -+ val = I915_READ(TRANS_CHICKEN2(PIPE_A)); -+ val |= TRANS_CHICKEN2_TIMING_OVERRIDE; -+ I915_WRITE(TRANS_CHICKEN2(PIPE_A), val); -+ -+ val = TRANS_ENABLE; -+ pipeconf_val = I915_READ(PIPECONF(cpu_transcoder)); -+ -+ if ((pipeconf_val & PIPECONF_INTERLACE_MASK_HSW) == -+ PIPECONF_INTERLACED_ILK) -+ val |= TRANS_INTERLACED; -+ else -+ val |= TRANS_PROGRESSIVE; -+ -+ I915_WRITE(LPT_TRANSCONF, val); -+ if (intel_wait_for_register(&dev_priv->uncore, -+ LPT_TRANSCONF, -+ TRANS_STATE_ENABLE, -+ TRANS_STATE_ENABLE, -+ 100)) -+ DRM_ERROR("Failed to enable PCH transcoder\n"); -+} -+ -+static void ironlake_disable_pch_transcoder(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ i915_reg_t reg; -+ u32 val; -+ -+ /* FDI relies on the transcoder */ -+ assert_fdi_tx_disabled(dev_priv, pipe); -+ assert_fdi_rx_disabled(dev_priv, pipe); -+ -+ /* Ports must be off as well */ -+ assert_pch_ports_disabled(dev_priv, pipe); -+ -+ reg = PCH_TRANSCONF(pipe); -+ val = I915_READ(reg); -+ val &= ~TRANS_ENABLE; -+ I915_WRITE(reg, val); -+ /* wait for PCH transcoder off, transcoder state */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ reg, TRANS_STATE_ENABLE, 0, -+ 50)) -+ DRM_ERROR("failed to disable transcoder %c\n", pipe_name(pipe)); -+ -+ if (HAS_PCH_CPT(dev_priv)) { -+ /* Workaround: Clear the timing override chicken bit again. */ -+ reg = TRANS_CHICKEN2(pipe); -+ val = I915_READ(reg); -+ val &= ~TRANS_CHICKEN2_TIMING_OVERRIDE; -+ I915_WRITE(reg, val); -+ } -+} -+ -+void lpt_disable_pch_transcoder(struct drm_i915_private *dev_priv) -+{ -+ u32 val; -+ -+ val = I915_READ(LPT_TRANSCONF); -+ val &= ~TRANS_ENABLE; -+ I915_WRITE(LPT_TRANSCONF, val); -+ /* wait for PCH transcoder off, transcoder state */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ LPT_TRANSCONF, TRANS_STATE_ENABLE, 0, -+ 50)) -+ DRM_ERROR("Failed to disable PCH transcoder\n"); -+ -+ /* Workaround: clear timing override bit. */ -+ val = I915_READ(TRANS_CHICKEN2(PIPE_A)); -+ val &= ~TRANS_CHICKEN2_TIMING_OVERRIDE; -+ I915_WRITE(TRANS_CHICKEN2(PIPE_A), val); -+} -+ -+enum pipe intel_crtc_pch_transcoder(struct intel_crtc *crtc) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ if (HAS_PCH_LPT(dev_priv)) -+ return PIPE_A; -+ else -+ return crtc->pipe; -+} -+ -+static u32 intel_crtc_max_vblank_count(const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ -+ /* -+ * On i965gm the hardware frame counter reads -+ * zero when the TV encoder is enabled :( -+ */ -+ if (IS_I965GM(dev_priv) && -+ (crtc_state->output_types & BIT(INTEL_OUTPUT_TVOUT))) -+ return 0; -+ -+ if (INTEL_GEN(dev_priv) >= 5 || IS_G4X(dev_priv)) -+ return 0xffffffff; /* full 32 bit counter */ -+ else if (INTEL_GEN(dev_priv) >= 3) -+ return 0xffffff; /* only 24 bits of frame count */ -+ else -+ return 0; /* Gen2 doesn't have a hardware frame counter */ -+} -+ -+static void intel_crtc_vblank_on(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ -+ drm_crtc_set_max_vblank_count(&crtc->base, -+ intel_crtc_max_vblank_count(crtc_state)); -+ drm_crtc_vblank_on(&crtc->base); -+} -+ -+static void intel_enable_pipe(const struct intel_crtc_state *new_crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum transcoder cpu_transcoder = new_crtc_state->cpu_transcoder; -+ enum pipe pipe = crtc->pipe; -+ i915_reg_t reg; -+ u32 val; -+ -+ DRM_DEBUG_KMS("enabling pipe %c\n", pipe_name(pipe)); -+ -+ assert_planes_disabled(crtc); -+ -+ /* -+ * A pipe without a PLL won't actually be able to drive bits from -+ * a plane. On ILK+ the pipe PLLs are integrated, so we don't -+ * need the check. -+ */ -+ if (HAS_GMCH(dev_priv)) { -+ if (intel_crtc_has_type(new_crtc_state, INTEL_OUTPUT_DSI)) -+ assert_dsi_pll_enabled(dev_priv); -+ else -+ assert_pll_enabled(dev_priv, pipe); -+ } else { -+ if (new_crtc_state->has_pch_encoder) { -+ /* if driving the PCH, we need FDI enabled */ -+ assert_fdi_rx_pll_enabled(dev_priv, -+ intel_crtc_pch_transcoder(crtc)); -+ assert_fdi_tx_pll_enabled(dev_priv, -+ (enum pipe) cpu_transcoder); -+ } -+ /* FIXME: assert CPU port conditions for SNB+ */ -+ } -+ -+ trace_intel_pipe_enable(dev_priv, pipe); -+ -+ reg = PIPECONF(cpu_transcoder); -+ val = I915_READ(reg); -+ if (val & PIPECONF_ENABLE) { -+ /* we keep both pipes enabled on 830 */ -+ WARN_ON(!IS_I830(dev_priv)); -+ return; -+ } -+ -+ I915_WRITE(reg, val | PIPECONF_ENABLE); -+ POSTING_READ(reg); -+ -+ /* -+ * Until the pipe starts PIPEDSL reads will return a stale value, -+ * which causes an apparent vblank timestamp jump when PIPEDSL -+ * resets to its proper value. That also messes up the frame count -+ * when it's derived from the timestamps. So let's wait for the -+ * pipe to start properly before we call drm_crtc_vblank_on() -+ */ -+ if (intel_crtc_max_vblank_count(new_crtc_state) == 0) -+ intel_wait_for_pipe_scanline_moving(crtc); -+} -+ -+static void intel_disable_pipe(const struct intel_crtc_state *old_crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum transcoder cpu_transcoder = old_crtc_state->cpu_transcoder; -+ enum pipe pipe = crtc->pipe; -+ i915_reg_t reg; -+ u32 val; -+ -+ DRM_DEBUG_KMS("disabling pipe %c\n", pipe_name(pipe)); -+ -+ /* -+ * Make sure planes won't keep trying to pump pixels to us, -+ * or we might hang the display. -+ */ -+ assert_planes_disabled(crtc); -+ -+ trace_intel_pipe_disable(dev_priv, pipe); -+ -+ reg = PIPECONF(cpu_transcoder); -+ val = I915_READ(reg); -+ if ((val & PIPECONF_ENABLE) == 0) -+ return; -+ -+ /* -+ * Double wide has implications for planes -+ * so best keep it disabled when not needed. -+ */ -+ if (old_crtc_state->double_wide) -+ val &= ~PIPECONF_DOUBLE_WIDE; -+ -+ /* Don't disable pipe or pipe PLLs if needed */ -+ if (!IS_I830(dev_priv)) -+ val &= ~PIPECONF_ENABLE; -+ -+ I915_WRITE(reg, val); -+ if ((val & PIPECONF_ENABLE) == 0) -+ intel_wait_for_pipe_off(old_crtc_state); -+} -+ -+static unsigned int intel_tile_size(const struct drm_i915_private *dev_priv) -+{ -+ return IS_GEN(dev_priv, 2) ? 2048 : 4096; -+} -+ -+static unsigned int -+intel_tile_width_bytes(const struct drm_framebuffer *fb, int color_plane) -+{ -+ struct drm_i915_private *dev_priv = to_i915(fb->dev); -+ unsigned int cpp = fb->format->cpp[color_plane]; -+ -+ switch (fb->modifier) { -+ case DRM_FORMAT_MOD_LINEAR: -+ return cpp; -+ case I915_FORMAT_MOD_X_TILED: -+ if (IS_GEN(dev_priv, 2)) -+ return 128; -+ else -+ return 512; -+ case I915_FORMAT_MOD_Y_TILED_CCS: -+ if (color_plane == 1) -+ return 128; -+ /* fall through */ -+ case I915_FORMAT_MOD_Y_TILED: -+ if (IS_GEN(dev_priv, 2) || HAS_128_BYTE_Y_TILING(dev_priv)) -+ return 128; -+ else -+ return 512; -+ case I915_FORMAT_MOD_Yf_TILED_CCS: -+ if (color_plane == 1) -+ return 128; -+ /* fall through */ -+ case I915_FORMAT_MOD_Yf_TILED: -+ switch (cpp) { -+ case 1: -+ return 64; -+ case 2: -+ case 4: -+ return 128; -+ case 8: -+ case 16: -+ return 256; -+ default: -+ MISSING_CASE(cpp); -+ return cpp; -+ } -+ break; -+ default: -+ MISSING_CASE(fb->modifier); -+ return cpp; -+ } -+} -+ -+static unsigned int -+intel_tile_height(const struct drm_framebuffer *fb, int color_plane) -+{ -+ if (fb->modifier == DRM_FORMAT_MOD_LINEAR) -+ return 1; -+ else -+ return intel_tile_size(to_i915(fb->dev)) / -+ intel_tile_width_bytes(fb, color_plane); -+} -+ -+/* Return the tile dimensions in pixel units */ -+static void intel_tile_dims(const struct drm_framebuffer *fb, int color_plane, -+ unsigned int *tile_width, -+ unsigned int *tile_height) -+{ -+ unsigned int tile_width_bytes = intel_tile_width_bytes(fb, color_plane); -+ unsigned int cpp = fb->format->cpp[color_plane]; -+ -+ *tile_width = tile_width_bytes / cpp; -+ *tile_height = intel_tile_size(to_i915(fb->dev)) / tile_width_bytes; -+} -+ -+unsigned int -+intel_fb_align_height(const struct drm_framebuffer *fb, -+ int color_plane, unsigned int height) -+{ -+ unsigned int tile_height = intel_tile_height(fb, color_plane); -+ -+ return ALIGN(height, tile_height); -+} -+ -+unsigned int intel_rotation_info_size(const struct intel_rotation_info *rot_info) -+{ -+ unsigned int size = 0; -+ int i; -+ -+ for (i = 0 ; i < ARRAY_SIZE(rot_info->plane); i++) -+ size += rot_info->plane[i].width * rot_info->plane[i].height; -+ -+ return size; -+} -+ -+static void -+intel_fill_fb_ggtt_view(struct i915_ggtt_view *view, -+ const struct drm_framebuffer *fb, -+ unsigned int rotation) -+{ -+ view->type = I915_GGTT_VIEW_NORMAL; -+ if (drm_rotation_90_or_270(rotation)) { -+ view->type = I915_GGTT_VIEW_ROTATED; -+ view->rotated = to_intel_framebuffer(fb)->rot_info; -+ } -+} -+ -+static unsigned int intel_cursor_alignment(const struct drm_i915_private *dev_priv) -+{ -+ if (IS_I830(dev_priv)) -+ return 16 * 1024; -+ else if (IS_I85X(dev_priv)) -+ return 256; -+ else if (IS_I845G(dev_priv) || IS_I865G(dev_priv)) -+ return 32; -+ else -+ return 4 * 1024; -+} -+ -+static unsigned int intel_linear_alignment(const struct drm_i915_private *dev_priv) -+{ -+ if (INTEL_GEN(dev_priv) >= 9) -+ return 256 * 1024; -+ else if (IS_I965G(dev_priv) || IS_I965GM(dev_priv) || -+ IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ return 128 * 1024; -+ else if (INTEL_GEN(dev_priv) >= 4) -+ return 4 * 1024; -+ else -+ return 0; -+} -+ -+static unsigned int intel_surf_alignment(const struct drm_framebuffer *fb, -+ int color_plane) -+{ -+ struct drm_i915_private *dev_priv = to_i915(fb->dev); -+ -+ /* AUX_DIST needs only 4K alignment */ -+ if (color_plane == 1) -+ return 4096; -+ -+ switch (fb->modifier) { -+ case DRM_FORMAT_MOD_LINEAR: -+ return intel_linear_alignment(dev_priv); -+ case I915_FORMAT_MOD_X_TILED: -+ if (INTEL_GEN(dev_priv) >= 9) -+ return 256 * 1024; -+ return 0; -+ case I915_FORMAT_MOD_Y_TILED_CCS: -+ case I915_FORMAT_MOD_Yf_TILED_CCS: -+ case I915_FORMAT_MOD_Y_TILED: -+ case I915_FORMAT_MOD_Yf_TILED: -+ return 1 * 1024 * 1024; -+ default: -+ MISSING_CASE(fb->modifier); -+ return 0; -+ } -+} -+ -+static bool intel_plane_uses_fence(const struct intel_plane_state *plane_state) -+{ -+ struct intel_plane *plane = to_intel_plane(plane_state->base.plane); -+ struct drm_i915_private *dev_priv = to_i915(plane->base.dev); -+ -+ return INTEL_GEN(dev_priv) < 4 || plane->has_fbc; -+} -+ -+struct i915_vma * -+intel_pin_and_fence_fb_obj(struct drm_framebuffer *fb, -+ const struct i915_ggtt_view *view, -+ bool uses_fence, -+ unsigned long *out_flags) -+{ -+ struct drm_device *dev = fb->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_i915_gem_object *obj = intel_fb_obj(fb); -+ intel_wakeref_t wakeref; -+ struct i915_vma *vma; -+ unsigned int pinctl; -+ u32 alignment; -+ -+ WARN_ON(!mutex_is_locked(&dev->struct_mutex)); -+ -+ alignment = intel_surf_alignment(fb, 0); -+ -+ /* Note that the w/a also requires 64 PTE of padding following the -+ * bo. We currently fill all unused PTE with the shadow page and so -+ * we should always have valid PTE following the scanout preventing -+ * the VT-d warning. -+ */ -+ if (intel_scanout_needs_vtd_wa(dev_priv) && alignment < 256 * 1024) -+ alignment = 256 * 1024; -+ -+ /* -+ * Global gtt pte registers are special registers which actually forward -+ * writes to a chunk of system memory. Which means that there is no risk -+ * that the register values disappear as soon as we call -+ * intel_runtime_pm_put(), so it is correct to wrap only the -+ * pin/unpin/fence and not more. -+ */ -+ wakeref = intel_runtime_pm_get(dev_priv); -+ -+ atomic_inc(&dev_priv->gpu_error.pending_fb_pin); -+ -+ pinctl = 0; -+ -+ /* Valleyview is definitely limited to scanning out the first -+ * 512MiB. Lets presume this behaviour was inherited from the -+ * g4x display engine and that all earlier gen are similarly -+ * limited. Testing suggests that it is a little more -+ * complicated than this. For example, Cherryview appears quite -+ * happy to scanout from anywhere within its global aperture. -+ */ -+ if (HAS_GMCH(dev_priv)) -+ pinctl |= PIN_MAPPABLE; -+ -+ vma = i915_gem_object_pin_to_display_plane(obj, -+ alignment, view, pinctl); -+ if (IS_ERR(vma)) -+ goto err; -+ -+ if (uses_fence && i915_vma_is_map_and_fenceable(vma)) { -+ int ret; -+ -+ /* Install a fence for tiled scan-out. Pre-i965 always needs a -+ * fence, whereas 965+ only requires a fence if using -+ * framebuffer compression. For simplicity, we always, when -+ * possible, install a fence as the cost is not that onerous. -+ * -+ * If we fail to fence the tiled scanout, then either the -+ * modeset will reject the change (which is highly unlikely as -+ * the affected systems, all but one, do not have unmappable -+ * space) or we will not be able to enable full powersaving -+ * techniques (also likely not to apply due to various limits -+ * FBC and the like impose on the size of the buffer, which -+ * presumably we violated anyway with this unmappable buffer). -+ * Anyway, it is presumably better to stumble onwards with -+ * something and try to run the system in a "less than optimal" -+ * mode that matches the user configuration. -+ */ -+ ret = i915_vma_pin_fence(vma); -+ if (ret != 0 && INTEL_GEN(dev_priv) < 4) { -+ i915_gem_object_unpin_from_display_plane(vma); -+ vma = ERR_PTR(ret); -+ goto err; -+ } -+ -+ if (ret == 0 && vma->fence) -+ *out_flags |= PLANE_HAS_FENCE; -+ } -+ -+ i915_vma_get(vma); -+err: -+ atomic_dec(&dev_priv->gpu_error.pending_fb_pin); -+ -+ intel_runtime_pm_put(dev_priv, wakeref); -+ return vma; -+} -+ -+void intel_unpin_fb_vma(struct i915_vma *vma, unsigned long flags) -+{ -+ lockdep_assert_held(&vma->vm->i915->drm.struct_mutex); -+ -+ if (flags & PLANE_HAS_FENCE) -+ i915_vma_unpin_fence(vma); -+ i915_gem_object_unpin_from_display_plane(vma); -+ i915_vma_put(vma); -+} -+ -+static int intel_fb_pitch(const struct drm_framebuffer *fb, int color_plane, -+ unsigned int rotation) -+{ -+ if (drm_rotation_90_or_270(rotation)) -+ return to_intel_framebuffer(fb)->rotated[color_plane].pitch; -+ else -+ return fb->pitches[color_plane]; -+} -+ -+/* -+ * Convert the x/y offsets into a linear offset. -+ * Only valid with 0/180 degree rotation, which is fine since linear -+ * offset is only used with linear buffers on pre-hsw and tiled buffers -+ * with gen2/3, and 90/270 degree rotations isn't supported on any of them. -+ */ -+u32 intel_fb_xy_to_linear(int x, int y, -+ const struct intel_plane_state *state, -+ int color_plane) -+{ -+ const struct drm_framebuffer *fb = state->base.fb; -+ unsigned int cpp = fb->format->cpp[color_plane]; -+ unsigned int pitch = state->color_plane[color_plane].stride; -+ -+ return y * pitch + x * cpp; -+} -+ -+/* -+ * Add the x/y offsets derived from fb->offsets[] to the user -+ * specified plane src x/y offsets. The resulting x/y offsets -+ * specify the start of scanout from the beginning of the gtt mapping. -+ */ -+void intel_add_fb_offsets(int *x, int *y, -+ const struct intel_plane_state *state, -+ int color_plane) -+ -+{ -+ const struct intel_framebuffer *intel_fb = to_intel_framebuffer(state->base.fb); -+ unsigned int rotation = state->base.rotation; -+ -+ if (drm_rotation_90_or_270(rotation)) { -+ *x += intel_fb->rotated[color_plane].x; -+ *y += intel_fb->rotated[color_plane].y; -+ } else { -+ *x += intel_fb->normal[color_plane].x; -+ *y += intel_fb->normal[color_plane].y; -+ } -+} -+ -+static u32 intel_adjust_tile_offset(int *x, int *y, -+ unsigned int tile_width, -+ unsigned int tile_height, -+ unsigned int tile_size, -+ unsigned int pitch_tiles, -+ u32 old_offset, -+ u32 new_offset) -+{ -+ unsigned int pitch_pixels = pitch_tiles * tile_width; -+ unsigned int tiles; -+ -+ WARN_ON(old_offset & (tile_size - 1)); -+ WARN_ON(new_offset & (tile_size - 1)); -+ WARN_ON(new_offset > old_offset); -+ -+ tiles = (old_offset - new_offset) / tile_size; -+ -+ *y += tiles / pitch_tiles * tile_height; -+ *x += tiles % pitch_tiles * tile_width; -+ -+ /* minimize x in case it got needlessly big */ -+ *y += *x / pitch_pixels * tile_height; -+ *x %= pitch_pixels; -+ -+ return new_offset; -+} -+ -+static bool is_surface_linear(u64 modifier, int color_plane) -+{ -+ return modifier == DRM_FORMAT_MOD_LINEAR; -+} -+ -+static u32 intel_adjust_aligned_offset(int *x, int *y, -+ const struct drm_framebuffer *fb, -+ int color_plane, -+ unsigned int rotation, -+ unsigned int pitch, -+ u32 old_offset, u32 new_offset) -+{ -+ struct drm_i915_private *dev_priv = to_i915(fb->dev); -+ unsigned int cpp = fb->format->cpp[color_plane]; -+ -+ WARN_ON(new_offset > old_offset); -+ -+ if (!is_surface_linear(fb->modifier, color_plane)) { -+ unsigned int tile_size, tile_width, tile_height; -+ unsigned int pitch_tiles; -+ -+ tile_size = intel_tile_size(dev_priv); -+ intel_tile_dims(fb, color_plane, &tile_width, &tile_height); -+ -+ if (drm_rotation_90_or_270(rotation)) { -+ pitch_tiles = pitch / tile_height; -+ swap(tile_width, tile_height); -+ } else { -+ pitch_tiles = pitch / (tile_width * cpp); -+ } -+ -+ intel_adjust_tile_offset(x, y, tile_width, tile_height, -+ tile_size, pitch_tiles, -+ old_offset, new_offset); -+ } else { -+ old_offset += *y * pitch + *x * cpp; -+ -+ *y = (old_offset - new_offset) / pitch; -+ *x = ((old_offset - new_offset) - *y * pitch) / cpp; -+ } -+ -+ return new_offset; -+} -+ -+/* -+ * Adjust the tile offset by moving the difference into -+ * the x/y offsets. -+ */ -+static u32 intel_plane_adjust_aligned_offset(int *x, int *y, -+ const struct intel_plane_state *state, -+ int color_plane, -+ u32 old_offset, u32 new_offset) -+{ -+ return intel_adjust_aligned_offset(x, y, state->base.fb, color_plane, -+ state->base.rotation, -+ state->color_plane[color_plane].stride, -+ old_offset, new_offset); -+} -+ -+/* -+ * Computes the aligned offset to the base tile and adjusts -+ * x, y. bytes per pixel is assumed to be a power-of-two. -+ * -+ * In the 90/270 rotated case, x and y are assumed -+ * to be already rotated to match the rotated GTT view, and -+ * pitch is the tile_height aligned framebuffer height. -+ * -+ * This function is used when computing the derived information -+ * under intel_framebuffer, so using any of that information -+ * here is not allowed. Anything under drm_framebuffer can be -+ * used. This is why the user has to pass in the pitch since it -+ * is specified in the rotated orientation. -+ */ -+static u32 intel_compute_aligned_offset(struct drm_i915_private *dev_priv, -+ int *x, int *y, -+ const struct drm_framebuffer *fb, -+ int color_plane, -+ unsigned int pitch, -+ unsigned int rotation, -+ u32 alignment) -+{ -+ unsigned int cpp = fb->format->cpp[color_plane]; -+ u32 offset, offset_aligned; -+ -+ if (alignment) -+ alignment--; -+ -+ if (!is_surface_linear(fb->modifier, color_plane)) { -+ unsigned int tile_size, tile_width, tile_height; -+ unsigned int tile_rows, tiles, pitch_tiles; -+ -+ tile_size = intel_tile_size(dev_priv); -+ intel_tile_dims(fb, color_plane, &tile_width, &tile_height); -+ -+ if (drm_rotation_90_or_270(rotation)) { -+ pitch_tiles = pitch / tile_height; -+ swap(tile_width, tile_height); -+ } else { -+ pitch_tiles = pitch / (tile_width * cpp); -+ } -+ -+ tile_rows = *y / tile_height; -+ *y %= tile_height; -+ -+ tiles = *x / tile_width; -+ *x %= tile_width; -+ -+ offset = (tile_rows * pitch_tiles + tiles) * tile_size; -+ offset_aligned = offset & ~alignment; -+ -+ intel_adjust_tile_offset(x, y, tile_width, tile_height, -+ tile_size, pitch_tiles, -+ offset, offset_aligned); -+ } else { -+ offset = *y * pitch + *x * cpp; -+ offset_aligned = offset & ~alignment; -+ -+ *y = (offset & alignment) / pitch; -+ *x = ((offset & alignment) - *y * pitch) / cpp; -+ } -+ -+ return offset_aligned; -+} -+ -+static u32 intel_plane_compute_aligned_offset(int *x, int *y, -+ const struct intel_plane_state *state, -+ int color_plane) -+{ -+ struct intel_plane *intel_plane = to_intel_plane(state->base.plane); -+ struct drm_i915_private *dev_priv = to_i915(intel_plane->base.dev); -+ const struct drm_framebuffer *fb = state->base.fb; -+ unsigned int rotation = state->base.rotation; -+ int pitch = state->color_plane[color_plane].stride; -+ u32 alignment; -+ -+ if (intel_plane->id == PLANE_CURSOR) -+ alignment = intel_cursor_alignment(dev_priv); -+ else -+ alignment = intel_surf_alignment(fb, color_plane); -+ -+ return intel_compute_aligned_offset(dev_priv, x, y, fb, color_plane, -+ pitch, rotation, alignment); -+} -+ -+/* Convert the fb->offset[] into x/y offsets */ -+static int intel_fb_offset_to_xy(int *x, int *y, -+ const struct drm_framebuffer *fb, -+ int color_plane) -+{ -+ struct drm_i915_private *dev_priv = to_i915(fb->dev); -+ unsigned int height; -+ -+ if (fb->modifier != DRM_FORMAT_MOD_LINEAR && -+ fb->offsets[color_plane] % intel_tile_size(dev_priv)) { -+ DRM_DEBUG_KMS("Misaligned offset 0x%08x for color plane %d\n", -+ fb->offsets[color_plane], color_plane); -+ return -EINVAL; -+ } -+ -+ height = drm_framebuffer_plane_height(fb->height, fb, color_plane); -+ height = ALIGN(height, intel_tile_height(fb, color_plane)); -+ -+ /* Catch potential overflows early */ -+ if (add_overflows_t(u32, mul_u32_u32(height, fb->pitches[color_plane]), -+ fb->offsets[color_plane])) { -+ DRM_DEBUG_KMS("Bad offset 0x%08x or pitch %d for color plane %d\n", -+ fb->offsets[color_plane], fb->pitches[color_plane], -+ color_plane); -+ return -ERANGE; -+ } -+ -+ *x = 0; -+ *y = 0; -+ -+ intel_adjust_aligned_offset(x, y, -+ fb, color_plane, DRM_MODE_ROTATE_0, -+ fb->pitches[color_plane], -+ fb->offsets[color_plane], 0); -+ -+ return 0; -+} -+ -+static unsigned int intel_fb_modifier_to_tiling(u64 fb_modifier) -+{ -+ switch (fb_modifier) { -+ case I915_FORMAT_MOD_X_TILED: -+ return I915_TILING_X; -+ case I915_FORMAT_MOD_Y_TILED: -+ case I915_FORMAT_MOD_Y_TILED_CCS: -+ return I915_TILING_Y; -+ default: -+ return I915_TILING_NONE; -+ } -+} -+ -+/* -+ * From the Sky Lake PRM: -+ * "The Color Control Surface (CCS) contains the compression status of -+ * the cache-line pairs. The compression state of the cache-line pair -+ * is specified by 2 bits in the CCS. Each CCS cache-line represents -+ * an area on the main surface of 16 x16 sets of 128 byte Y-tiled -+ * cache-line-pairs. CCS is always Y tiled." -+ * -+ * Since cache line pairs refers to horizontally adjacent cache lines, -+ * each cache line in the CCS corresponds to an area of 32x16 cache -+ * lines on the main surface. Since each pixel is 4 bytes, this gives -+ * us a ratio of one byte in the CCS for each 8x16 pixels in the -+ * main surface. -+ */ -+static const struct drm_format_info ccs_formats[] = { -+ { .format = DRM_FORMAT_XRGB8888, .depth = 24, .num_planes = 2, -+ .cpp = { 4, 1, }, .hsub = 8, .vsub = 16, }, -+ { .format = DRM_FORMAT_XBGR8888, .depth = 24, .num_planes = 2, -+ .cpp = { 4, 1, }, .hsub = 8, .vsub = 16, }, -+ { .format = DRM_FORMAT_ARGB8888, .depth = 32, .num_planes = 2, -+ .cpp = { 4, 1, }, .hsub = 8, .vsub = 16, .has_alpha = true, }, -+ { .format = DRM_FORMAT_ABGR8888, .depth = 32, .num_planes = 2, -+ .cpp = { 4, 1, }, .hsub = 8, .vsub = 16, .has_alpha = true, }, -+}; -+ -+static const struct drm_format_info * -+lookup_format_info(const struct drm_format_info formats[], -+ int num_formats, u32 format) -+{ -+ int i; -+ -+ for (i = 0; i < num_formats; i++) { -+ if (formats[i].format == format) -+ return &formats[i]; -+ } -+ -+ return NULL; -+} -+ -+static const struct drm_format_info * -+intel_get_format_info(const struct drm_mode_fb_cmd2 *cmd) -+{ -+ switch (cmd->modifier[0]) { -+ case I915_FORMAT_MOD_Y_TILED_CCS: -+ case I915_FORMAT_MOD_Yf_TILED_CCS: -+ return lookup_format_info(ccs_formats, -+ ARRAY_SIZE(ccs_formats), -+ cmd->pixel_format); -+ default: -+ return NULL; -+ } -+} -+ -+bool is_ccs_modifier(u64 modifier) -+{ -+ return modifier == I915_FORMAT_MOD_Y_TILED_CCS || -+ modifier == I915_FORMAT_MOD_Yf_TILED_CCS; -+} -+ -+static int -+intel_fill_fb_info(struct drm_i915_private *dev_priv, -+ struct drm_framebuffer *fb) -+{ -+ struct intel_framebuffer *intel_fb = to_intel_framebuffer(fb); -+ struct intel_rotation_info *rot_info = &intel_fb->rot_info; -+ struct drm_i915_gem_object *obj = intel_fb_obj(fb); -+ u32 gtt_offset_rotated = 0; -+ unsigned int max_size = 0; -+ int i, num_planes = fb->format->num_planes; -+ unsigned int tile_size = intel_tile_size(dev_priv); -+ -+ for (i = 0; i < num_planes; i++) { -+ unsigned int width, height; -+ unsigned int cpp, size; -+ u32 offset; -+ int x, y; -+ int ret; -+ -+ cpp = fb->format->cpp[i]; -+ width = drm_framebuffer_plane_width(fb->width, fb, i); -+ height = drm_framebuffer_plane_height(fb->height, fb, i); -+ -+ ret = intel_fb_offset_to_xy(&x, &y, fb, i); -+ if (ret) { -+ DRM_DEBUG_KMS("bad fb plane %d offset: 0x%x\n", -+ i, fb->offsets[i]); -+ return ret; -+ } -+ -+ if (is_ccs_modifier(fb->modifier) && i == 1) { -+ int hsub = fb->format->hsub; -+ int vsub = fb->format->vsub; -+ int tile_width, tile_height; -+ int main_x, main_y; -+ int ccs_x, ccs_y; -+ -+ intel_tile_dims(fb, i, &tile_width, &tile_height); -+ tile_width *= hsub; -+ tile_height *= vsub; -+ -+ ccs_x = (x * hsub) % tile_width; -+ ccs_y = (y * vsub) % tile_height; -+ main_x = intel_fb->normal[0].x % tile_width; -+ main_y = intel_fb->normal[0].y % tile_height; -+ -+ /* -+ * CCS doesn't have its own x/y offset register, so the intra CCS tile -+ * x/y offsets must match between CCS and the main surface. -+ */ -+ if (main_x != ccs_x || main_y != ccs_y) { -+ DRM_DEBUG_KMS("Bad CCS x/y (main %d,%d ccs %d,%d) full (main %d,%d ccs %d,%d)\n", -+ main_x, main_y, -+ ccs_x, ccs_y, -+ intel_fb->normal[0].x, -+ intel_fb->normal[0].y, -+ x, y); -+ return -EINVAL; -+ } -+ } -+ -+ /* -+ * The fence (if used) is aligned to the start of the object -+ * so having the framebuffer wrap around across the edge of the -+ * fenced region doesn't really work. We have no API to configure -+ * the fence start offset within the object (nor could we probably -+ * on gen2/3). So it's just easier if we just require that the -+ * fb layout agrees with the fence layout. We already check that the -+ * fb stride matches the fence stride elsewhere. -+ */ -+ if (i == 0 && i915_gem_object_is_tiled(obj) && -+ (x + width) * cpp > fb->pitches[i]) { -+ DRM_DEBUG_KMS("bad fb plane %d offset: 0x%x\n", -+ i, fb->offsets[i]); -+ return -EINVAL; -+ } -+ -+ /* -+ * First pixel of the framebuffer from -+ * the start of the normal gtt mapping. -+ */ -+ intel_fb->normal[i].x = x; -+ intel_fb->normal[i].y = y; -+ -+ offset = intel_compute_aligned_offset(dev_priv, &x, &y, fb, i, -+ fb->pitches[i], -+ DRM_MODE_ROTATE_0, -+ tile_size); -+ offset /= tile_size; -+ -+ if (!is_surface_linear(fb->modifier, i)) { -+ unsigned int tile_width, tile_height; -+ unsigned int pitch_tiles; -+ struct drm_rect r; -+ -+ intel_tile_dims(fb, i, &tile_width, &tile_height); -+ -+ rot_info->plane[i].offset = offset; -+ rot_info->plane[i].stride = DIV_ROUND_UP(fb->pitches[i], tile_width * cpp); -+ rot_info->plane[i].width = DIV_ROUND_UP(x + width, tile_width); -+ rot_info->plane[i].height = DIV_ROUND_UP(y + height, tile_height); -+ -+ intel_fb->rotated[i].pitch = -+ rot_info->plane[i].height * tile_height; -+ -+ /* how many tiles does this plane need */ -+ size = rot_info->plane[i].stride * rot_info->plane[i].height; -+ /* -+ * If the plane isn't horizontally tile aligned, -+ * we need one more tile. -+ */ -+ if (x != 0) -+ size++; -+ -+ /* rotate the x/y offsets to match the GTT view */ -+ r.x1 = x; -+ r.y1 = y; -+ r.x2 = x + width; -+ r.y2 = y + height; -+ drm_rect_rotate(&r, -+ rot_info->plane[i].width * tile_width, -+ rot_info->plane[i].height * tile_height, -+ DRM_MODE_ROTATE_270); -+ x = r.x1; -+ y = r.y1; -+ -+ /* rotate the tile dimensions to match the GTT view */ -+ pitch_tiles = intel_fb->rotated[i].pitch / tile_height; -+ swap(tile_width, tile_height); -+ -+ /* -+ * We only keep the x/y offsets, so push all of the -+ * gtt offset into the x/y offsets. -+ */ -+ intel_adjust_tile_offset(&x, &y, -+ tile_width, tile_height, -+ tile_size, pitch_tiles, -+ gtt_offset_rotated * tile_size, 0); -+ -+ gtt_offset_rotated += rot_info->plane[i].width * rot_info->plane[i].height; -+ -+ /* -+ * First pixel of the framebuffer from -+ * the start of the rotated gtt mapping. -+ */ -+ intel_fb->rotated[i].x = x; -+ intel_fb->rotated[i].y = y; -+ } else { -+ size = DIV_ROUND_UP((y + height) * fb->pitches[i] + -+ x * cpp, tile_size); -+ } -+ -+ /* how many tiles in total needed in the bo */ -+ max_size = max(max_size, offset + size); -+ } -+ -+ if (mul_u32_u32(max_size, tile_size) > obj->base.size) { -+ DRM_DEBUG_KMS("fb too big for bo (need %llu bytes, have %zu bytes)\n", -+ mul_u32_u32(max_size, tile_size), obj->base.size); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static int i9xx_format_to_fourcc(int format) -+{ -+ switch (format) { -+ case DISPPLANE_8BPP: -+ return DRM_FORMAT_C8; -+ case DISPPLANE_BGRX555: -+ return DRM_FORMAT_XRGB1555; -+ case DISPPLANE_BGRX565: -+ return DRM_FORMAT_RGB565; -+ default: -+ case DISPPLANE_BGRX888: -+ return DRM_FORMAT_XRGB8888; -+ case DISPPLANE_RGBX888: -+ return DRM_FORMAT_XBGR8888; -+ case DISPPLANE_BGRX101010: -+ return DRM_FORMAT_XRGB2101010; -+ case DISPPLANE_RGBX101010: -+ return DRM_FORMAT_XBGR2101010; -+ } -+} -+ -+int skl_format_to_fourcc(int format, bool rgb_order, bool alpha) -+{ -+ switch (format) { -+ case PLANE_CTL_FORMAT_RGB_565: -+ return DRM_FORMAT_RGB565; -+ case PLANE_CTL_FORMAT_NV12: -+ return DRM_FORMAT_NV12; -+ case PLANE_CTL_FORMAT_P010: -+ return DRM_FORMAT_P010; -+ case PLANE_CTL_FORMAT_P012: -+ return DRM_FORMAT_P012; -+ case PLANE_CTL_FORMAT_P016: -+ return DRM_FORMAT_P016; -+ case PLANE_CTL_FORMAT_Y210: -+ return DRM_FORMAT_Y210; -+ case PLANE_CTL_FORMAT_Y212: -+ return DRM_FORMAT_Y212; -+ case PLANE_CTL_FORMAT_Y216: -+ return DRM_FORMAT_Y216; -+ case PLANE_CTL_FORMAT_Y410: -+ return DRM_FORMAT_XVYU2101010; -+ case PLANE_CTL_FORMAT_Y412: -+ return DRM_FORMAT_XVYU12_16161616; -+ case PLANE_CTL_FORMAT_Y416: -+ return DRM_FORMAT_XVYU16161616; -+ default: -+ case PLANE_CTL_FORMAT_XRGB_8888: -+ if (rgb_order) { -+ if (alpha) -+ return DRM_FORMAT_ABGR8888; -+ else -+ return DRM_FORMAT_XBGR8888; -+ } else { -+ if (alpha) -+ return DRM_FORMAT_ARGB8888; -+ else -+ return DRM_FORMAT_XRGB8888; -+ } -+ case PLANE_CTL_FORMAT_XRGB_2101010: -+ if (rgb_order) -+ return DRM_FORMAT_XBGR2101010; -+ else -+ return DRM_FORMAT_XRGB2101010; -+ case PLANE_CTL_FORMAT_XRGB_16161616F: -+ if (rgb_order) { -+ if (alpha) -+ return DRM_FORMAT_ABGR16161616F; -+ else -+ return DRM_FORMAT_XBGR16161616F; -+ } else { -+ if (alpha) -+ return DRM_FORMAT_ARGB16161616F; -+ else -+ return DRM_FORMAT_XRGB16161616F; -+ } -+ } -+} -+ -+static bool -+intel_alloc_initial_plane_obj(struct intel_crtc *crtc, -+ struct intel_initial_plane_config *plane_config) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_i915_gem_object *obj = NULL; -+ struct drm_mode_fb_cmd2 mode_cmd = { 0 }; -+ struct drm_framebuffer *fb = &plane_config->fb->base; -+ u32 base_aligned = round_down(plane_config->base, PAGE_SIZE); -+ u32 size_aligned = round_up(plane_config->base + plane_config->size, -+ PAGE_SIZE); -+ -+ size_aligned -= base_aligned; -+ -+ if (plane_config->size == 0) -+ return false; -+ -+ /* If the FB is too big, just don't use it since fbdev is not very -+ * important and we should probably use that space with FBC or other -+ * features. */ -+ if (size_aligned * 2 > dev_priv->stolen_usable_size) -+ return false; -+ -+ switch (fb->modifier) { -+ case DRM_FORMAT_MOD_LINEAR: -+ case I915_FORMAT_MOD_X_TILED: -+ case I915_FORMAT_MOD_Y_TILED: -+ break; -+ default: -+ DRM_DEBUG_DRIVER("Unsupported modifier for initial FB: 0x%llx\n", -+ fb->modifier); -+ return false; -+ } -+ -+ mutex_lock(&dev->struct_mutex); -+ obj = i915_gem_object_create_stolen_for_preallocated(dev_priv, -+ base_aligned, -+ base_aligned, -+ size_aligned); -+ mutex_unlock(&dev->struct_mutex); -+ if (!obj) -+ return false; -+ -+ switch (plane_config->tiling) { -+ case I915_TILING_NONE: -+ break; -+ case I915_TILING_X: -+ case I915_TILING_Y: -+ obj->tiling_and_stride = fb->pitches[0] | plane_config->tiling; -+ break; -+ default: -+ MISSING_CASE(plane_config->tiling); -+ return false; -+ } -+ -+ mode_cmd.pixel_format = fb->format->format; -+ mode_cmd.width = fb->width; -+ mode_cmd.height = fb->height; -+ mode_cmd.pitches[0] = fb->pitches[0]; -+ mode_cmd.modifier[0] = fb->modifier; -+ mode_cmd.flags = DRM_MODE_FB_MODIFIERS; -+ -+ if (intel_framebuffer_init(to_intel_framebuffer(fb), obj, &mode_cmd)) { -+ DRM_DEBUG_KMS("intel fb init failed\n"); -+ goto out_unref_obj; -+ } -+ -+ -+ DRM_DEBUG_KMS("initial plane fb obj %p\n", obj); -+ return true; -+ -+out_unref_obj: -+ i915_gem_object_put(obj); -+ return false; -+} -+ -+static void -+intel_set_plane_visible(struct intel_crtc_state *crtc_state, -+ struct intel_plane_state *plane_state, -+ bool visible) -+{ -+ struct intel_plane *plane = to_intel_plane(plane_state->base.plane); -+ -+ plane_state->base.visible = visible; -+ -+ if (visible) -+ crtc_state->base.plane_mask |= drm_plane_mask(&plane->base); -+ else -+ crtc_state->base.plane_mask &= ~drm_plane_mask(&plane->base); -+} -+ -+static void fixup_active_planes(struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ struct drm_plane *plane; -+ -+ /* -+ * Active_planes aliases if multiple "primary" or cursor planes -+ * have been used on the same (or wrong) pipe. plane_mask uses -+ * unique ids, hence we can use that to reconstruct active_planes. -+ */ -+ crtc_state->active_planes = 0; -+ -+ drm_for_each_plane_mask(plane, &dev_priv->drm, -+ crtc_state->base.plane_mask) -+ crtc_state->active_planes |= BIT(to_intel_plane(plane)->id); -+} -+ -+static void intel_plane_disable_noatomic(struct intel_crtc *crtc, -+ struct intel_plane *plane) -+{ -+ struct intel_crtc_state *crtc_state = -+ to_intel_crtc_state(crtc->base.state); -+ struct intel_plane_state *plane_state = -+ to_intel_plane_state(plane->base.state); -+ -+ DRM_DEBUG_KMS("Disabling [PLANE:%d:%s] on [CRTC:%d:%s]\n", -+ plane->base.base.id, plane->base.name, -+ crtc->base.base.id, crtc->base.name); -+ -+ intel_set_plane_visible(crtc_state, plane_state, false); -+ fixup_active_planes(crtc_state); -+ -+ if (plane->id == PLANE_PRIMARY) -+ intel_pre_disable_primary_noatomic(&crtc->base); -+ -+ intel_disable_plane(plane, crtc_state); -+} -+ -+static void -+intel_find_initial_plane_obj(struct intel_crtc *intel_crtc, -+ struct intel_initial_plane_config *plane_config) -+{ -+ struct drm_device *dev = intel_crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_crtc *c; -+ struct drm_i915_gem_object *obj; -+ struct drm_plane *primary = intel_crtc->base.primary; -+ struct drm_plane_state *plane_state = primary->state; -+ struct intel_plane *intel_plane = to_intel_plane(primary); -+ struct intel_plane_state *intel_state = -+ to_intel_plane_state(plane_state); -+ struct drm_framebuffer *fb; -+ -+ if (!plane_config->fb) -+ return; -+ -+ if (intel_alloc_initial_plane_obj(intel_crtc, plane_config)) { -+ fb = &plane_config->fb->base; -+ goto valid_fb; -+ } -+ -+ kfree(plane_config->fb); -+ -+ /* -+ * Failed to alloc the obj, check to see if we should share -+ * an fb with another CRTC instead -+ */ -+ for_each_crtc(dev, c) { -+ struct intel_plane_state *state; -+ -+ if (c == &intel_crtc->base) -+ continue; -+ -+ if (!to_intel_crtc(c)->active) -+ continue; -+ -+ state = to_intel_plane_state(c->primary->state); -+ if (!state->vma) -+ continue; -+ -+ if (intel_plane_ggtt_offset(state) == plane_config->base) { -+ fb = state->base.fb; -+ drm_framebuffer_get(fb); -+ goto valid_fb; -+ } -+ } -+ -+ /* -+ * We've failed to reconstruct the BIOS FB. Current display state -+ * indicates that the primary plane is visible, but has a NULL FB, -+ * which will lead to problems later if we don't fix it up. The -+ * simplest solution is to just disable the primary plane now and -+ * pretend the BIOS never had it enabled. -+ */ -+ intel_plane_disable_noatomic(intel_crtc, intel_plane); -+ -+ return; -+ -+valid_fb: -+ intel_state->base.rotation = plane_config->rotation; -+ intel_fill_fb_ggtt_view(&intel_state->view, fb, -+ intel_state->base.rotation); -+ intel_state->color_plane[0].stride = -+ intel_fb_pitch(fb, 0, intel_state->base.rotation); -+ -+ mutex_lock(&dev->struct_mutex); -+ intel_state->vma = -+ intel_pin_and_fence_fb_obj(fb, -+ &intel_state->view, -+ intel_plane_uses_fence(intel_state), -+ &intel_state->flags); -+ mutex_unlock(&dev->struct_mutex); -+ if (IS_ERR(intel_state->vma)) { -+ DRM_ERROR("failed to pin boot fb on pipe %d: %li\n", -+ intel_crtc->pipe, PTR_ERR(intel_state->vma)); -+ -+ intel_state->vma = NULL; -+ drm_framebuffer_put(fb); -+ return; -+ } -+ -+ obj = intel_fb_obj(fb); -+ intel_fb_obj_flush(obj, ORIGIN_DIRTYFB); -+ -+ plane_state->src_x = 0; -+ plane_state->src_y = 0; -+ plane_state->src_w = fb->width << 16; -+ plane_state->src_h = fb->height << 16; -+ -+ plane_state->crtc_x = 0; -+ plane_state->crtc_y = 0; -+ plane_state->crtc_w = fb->width; -+ plane_state->crtc_h = fb->height; -+ -+ intel_state->base.src = drm_plane_state_src(plane_state); -+ intel_state->base.dst = drm_plane_state_dest(plane_state); -+ -+ if (i915_gem_object_is_tiled(obj)) -+ dev_priv->preserve_bios_swizzle = true; -+ -+ plane_state->fb = fb; -+ plane_state->crtc = &intel_crtc->base; -+ -+ atomic_or(to_intel_plane(primary)->frontbuffer_bit, -+ &obj->frontbuffer_bits); -+} -+ -+static int skl_max_plane_width(const struct drm_framebuffer *fb, -+ int color_plane, -+ unsigned int rotation) -+{ -+ int cpp = fb->format->cpp[color_plane]; -+ -+ switch (fb->modifier) { -+ case DRM_FORMAT_MOD_LINEAR: -+ case I915_FORMAT_MOD_X_TILED: -+ switch (cpp) { -+ case 8: -+ return 4096; -+ case 4: -+ case 2: -+ case 1: -+ return 8192; -+ default: -+ MISSING_CASE(cpp); -+ break; -+ } -+ break; -+ case I915_FORMAT_MOD_Y_TILED_CCS: -+ case I915_FORMAT_MOD_Yf_TILED_CCS: -+ /* FIXME AUX plane? */ -+ case I915_FORMAT_MOD_Y_TILED: -+ case I915_FORMAT_MOD_Yf_TILED: -+ switch (cpp) { -+ case 8: -+ return 2048; -+ case 4: -+ return 4096; -+ case 2: -+ case 1: -+ return 8192; -+ default: -+ MISSING_CASE(cpp); -+ break; -+ } -+ break; -+ default: -+ MISSING_CASE(fb->modifier); -+ } -+ -+ return 2048; -+} -+ -+static bool skl_check_main_ccs_coordinates(struct intel_plane_state *plane_state, -+ int main_x, int main_y, u32 main_offset) -+{ -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ int hsub = fb->format->hsub; -+ int vsub = fb->format->vsub; -+ int aux_x = plane_state->color_plane[1].x; -+ int aux_y = plane_state->color_plane[1].y; -+ u32 aux_offset = plane_state->color_plane[1].offset; -+ u32 alignment = intel_surf_alignment(fb, 1); -+ -+ while (aux_offset >= main_offset && aux_y <= main_y) { -+ int x, y; -+ -+ if (aux_x == main_x && aux_y == main_y) -+ break; -+ -+ if (aux_offset == 0) -+ break; -+ -+ x = aux_x / hsub; -+ y = aux_y / vsub; -+ aux_offset = intel_plane_adjust_aligned_offset(&x, &y, plane_state, 1, -+ aux_offset, aux_offset - alignment); -+ aux_x = x * hsub + aux_x % hsub; -+ aux_y = y * vsub + aux_y % vsub; -+ } -+ -+ if (aux_x != main_x || aux_y != main_y) -+ return false; -+ -+ plane_state->color_plane[1].offset = aux_offset; -+ plane_state->color_plane[1].x = aux_x; -+ plane_state->color_plane[1].y = aux_y; -+ -+ return true; -+} -+ -+static int skl_check_main_surface(struct intel_plane_state *plane_state) -+{ -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ unsigned int rotation = plane_state->base.rotation; -+ int x = plane_state->base.src.x1 >> 16; -+ int y = plane_state->base.src.y1 >> 16; -+ int w = drm_rect_width(&plane_state->base.src) >> 16; -+ int h = drm_rect_height(&plane_state->base.src) >> 16; -+ int max_width = skl_max_plane_width(fb, 0, rotation); -+ int max_height = 4096; -+ u32 alignment, offset, aux_offset = plane_state->color_plane[1].offset; -+ -+ if (w > max_width || h > max_height) { -+ DRM_DEBUG_KMS("requested Y/RGB source size %dx%d too big (limit %dx%d)\n", -+ w, h, max_width, max_height); -+ return -EINVAL; -+ } -+ -+ intel_add_fb_offsets(&x, &y, plane_state, 0); -+ offset = intel_plane_compute_aligned_offset(&x, &y, plane_state, 0); -+ alignment = intel_surf_alignment(fb, 0); -+ -+ /* -+ * AUX surface offset is specified as the distance from the -+ * main surface offset, and it must be non-negative. Make -+ * sure that is what we will get. -+ */ -+ if (offset > aux_offset) -+ offset = intel_plane_adjust_aligned_offset(&x, &y, plane_state, 0, -+ offset, aux_offset & ~(alignment - 1)); -+ -+ /* -+ * When using an X-tiled surface, the plane blows up -+ * if the x offset + width exceed the stride. -+ * -+ * TODO: linear and Y-tiled seem fine, Yf untested, -+ */ -+ if (fb->modifier == I915_FORMAT_MOD_X_TILED) { -+ int cpp = fb->format->cpp[0]; -+ -+ while ((x + w) * cpp > plane_state->color_plane[0].stride) { -+ if (offset == 0) { -+ DRM_DEBUG_KMS("Unable to find suitable display surface offset due to X-tiling\n"); -+ return -EINVAL; -+ } -+ -+ offset = intel_plane_adjust_aligned_offset(&x, &y, plane_state, 0, -+ offset, offset - alignment); -+ } -+ } -+ -+ /* -+ * CCS AUX surface doesn't have its own x/y offsets, we must make sure -+ * they match with the main surface x/y offsets. -+ */ -+ if (is_ccs_modifier(fb->modifier)) { -+ while (!skl_check_main_ccs_coordinates(plane_state, x, y, offset)) { -+ if (offset == 0) -+ break; -+ -+ offset = intel_plane_adjust_aligned_offset(&x, &y, plane_state, 0, -+ offset, offset - alignment); -+ } -+ -+ if (x != plane_state->color_plane[1].x || y != plane_state->color_plane[1].y) { -+ DRM_DEBUG_KMS("Unable to find suitable display surface offset due to CCS\n"); -+ return -EINVAL; -+ } -+ } -+ -+ plane_state->color_plane[0].offset = offset; -+ plane_state->color_plane[0].x = x; -+ plane_state->color_plane[0].y = y; -+ -+ return 0; -+} -+ -+static int skl_check_nv12_aux_surface(struct intel_plane_state *plane_state) -+{ -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ unsigned int rotation = plane_state->base.rotation; -+ int max_width = skl_max_plane_width(fb, 1, rotation); -+ int max_height = 4096; -+ int x = plane_state->base.src.x1 >> 17; -+ int y = plane_state->base.src.y1 >> 17; -+ int w = drm_rect_width(&plane_state->base.src) >> 17; -+ int h = drm_rect_height(&plane_state->base.src) >> 17; -+ u32 offset; -+ -+ intel_add_fb_offsets(&x, &y, plane_state, 1); -+ offset = intel_plane_compute_aligned_offset(&x, &y, plane_state, 1); -+ -+ /* FIXME not quite sure how/if these apply to the chroma plane */ -+ if (w > max_width || h > max_height) { -+ DRM_DEBUG_KMS("CbCr source size %dx%d too big (limit %dx%d)\n", -+ w, h, max_width, max_height); -+ return -EINVAL; -+ } -+ -+ plane_state->color_plane[1].offset = offset; -+ plane_state->color_plane[1].x = x; -+ plane_state->color_plane[1].y = y; -+ -+ return 0; -+} -+ -+static int skl_check_ccs_aux_surface(struct intel_plane_state *plane_state) -+{ -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ int src_x = plane_state->base.src.x1 >> 16; -+ int src_y = plane_state->base.src.y1 >> 16; -+ int hsub = fb->format->hsub; -+ int vsub = fb->format->vsub; -+ int x = src_x / hsub; -+ int y = src_y / vsub; -+ u32 offset; -+ -+ intel_add_fb_offsets(&x, &y, plane_state, 1); -+ offset = intel_plane_compute_aligned_offset(&x, &y, plane_state, 1); -+ -+ plane_state->color_plane[1].offset = offset; -+ plane_state->color_plane[1].x = x * hsub + src_x % hsub; -+ plane_state->color_plane[1].y = y * vsub + src_y % vsub; -+ -+ return 0; -+} -+ -+int skl_check_plane_surface(struct intel_plane_state *plane_state) -+{ -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ unsigned int rotation = plane_state->base.rotation; -+ int ret; -+ -+ intel_fill_fb_ggtt_view(&plane_state->view, fb, rotation); -+ plane_state->color_plane[0].stride = intel_fb_pitch(fb, 0, rotation); -+ plane_state->color_plane[1].stride = intel_fb_pitch(fb, 1, rotation); -+ -+ ret = intel_plane_check_stride(plane_state); -+ if (ret) -+ return ret; -+ -+ if (!plane_state->base.visible) -+ return 0; -+ -+ /* Rotate src coordinates to match rotated GTT view */ -+ if (drm_rotation_90_or_270(rotation)) -+ drm_rect_rotate(&plane_state->base.src, -+ fb->width << 16, fb->height << 16, -+ DRM_MODE_ROTATE_270); -+ -+ /* -+ * Handle the AUX surface first since -+ * the main surface setup depends on it. -+ */ -+ if (is_planar_yuv_format(fb->format->format)) { -+ ret = skl_check_nv12_aux_surface(plane_state); -+ if (ret) -+ return ret; -+ } else if (is_ccs_modifier(fb->modifier)) { -+ ret = skl_check_ccs_aux_surface(plane_state); -+ if (ret) -+ return ret; -+ } else { -+ plane_state->color_plane[1].offset = ~0xfff; -+ plane_state->color_plane[1].x = 0; -+ plane_state->color_plane[1].y = 0; -+ } -+ -+ ret = skl_check_main_surface(plane_state); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+unsigned int -+i9xx_plane_max_stride(struct intel_plane *plane, -+ u32 pixel_format, u64 modifier, -+ unsigned int rotation) -+{ -+ struct drm_i915_private *dev_priv = to_i915(plane->base.dev); -+ -+ if (!HAS_GMCH(dev_priv)) { -+ return 32*1024; -+ } else if (INTEL_GEN(dev_priv) >= 4) { -+ if (modifier == I915_FORMAT_MOD_X_TILED) -+ return 16*1024; -+ else -+ return 32*1024; -+ } else if (INTEL_GEN(dev_priv) >= 3) { -+ if (modifier == I915_FORMAT_MOD_X_TILED) -+ return 8*1024; -+ else -+ return 16*1024; -+ } else { -+ if (plane->i9xx_plane == PLANE_C) -+ return 4*1024; -+ else -+ return 8*1024; -+ } -+} -+ -+static u32 i9xx_plane_ctl_crtc(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ u32 dspcntr = 0; -+ -+ if (crtc_state->gamma_enable) -+ dspcntr |= DISPPLANE_GAMMA_ENABLE; -+ -+ if (crtc_state->csc_enable) -+ dspcntr |= DISPPLANE_PIPE_CSC_ENABLE; -+ -+ if (INTEL_GEN(dev_priv) < 5) -+ dspcntr |= DISPPLANE_SEL_PIPE(crtc->pipe); -+ -+ return dspcntr; -+} -+ -+static u32 i9xx_plane_ctl(const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state) -+{ -+ struct drm_i915_private *dev_priv = -+ to_i915(plane_state->base.plane->dev); -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ unsigned int rotation = plane_state->base.rotation; -+ u32 dspcntr; -+ -+ dspcntr = DISPLAY_PLANE_ENABLE; -+ -+ if (IS_G4X(dev_priv) || IS_GEN(dev_priv, 5) || -+ IS_GEN(dev_priv, 6) || IS_IVYBRIDGE(dev_priv)) -+ dspcntr |= DISPPLANE_TRICKLE_FEED_DISABLE; -+ -+ switch (fb->format->format) { -+ case DRM_FORMAT_C8: -+ dspcntr |= DISPPLANE_8BPP; -+ break; -+ case DRM_FORMAT_XRGB1555: -+ dspcntr |= DISPPLANE_BGRX555; -+ break; -+ case DRM_FORMAT_RGB565: -+ dspcntr |= DISPPLANE_BGRX565; -+ break; -+ case DRM_FORMAT_XRGB8888: -+ dspcntr |= DISPPLANE_BGRX888; -+ break; -+ case DRM_FORMAT_XBGR8888: -+ dspcntr |= DISPPLANE_RGBX888; -+ break; -+ case DRM_FORMAT_XRGB2101010: -+ dspcntr |= DISPPLANE_BGRX101010; -+ break; -+ case DRM_FORMAT_XBGR2101010: -+ dspcntr |= DISPPLANE_RGBX101010; -+ break; -+ default: -+ MISSING_CASE(fb->format->format); -+ return 0; -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 4 && -+ fb->modifier == I915_FORMAT_MOD_X_TILED) -+ dspcntr |= DISPPLANE_TILED; -+ -+ if (rotation & DRM_MODE_ROTATE_180) -+ dspcntr |= DISPPLANE_ROTATE_180; -+ -+ if (rotation & DRM_MODE_REFLECT_X) -+ dspcntr |= DISPPLANE_MIRROR; -+ -+ return dspcntr; -+} -+ -+int i9xx_check_plane_surface(struct intel_plane_state *plane_state) -+{ -+ struct drm_i915_private *dev_priv = -+ to_i915(plane_state->base.plane->dev); -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ unsigned int rotation = plane_state->base.rotation; -+ int src_x = plane_state->base.src.x1 >> 16; -+ int src_y = plane_state->base.src.y1 >> 16; -+ u32 offset; -+ int ret; -+ -+ intel_fill_fb_ggtt_view(&plane_state->view, fb, rotation); -+ plane_state->color_plane[0].stride = intel_fb_pitch(fb, 0, rotation); -+ -+ ret = intel_plane_check_stride(plane_state); -+ if (ret) -+ return ret; -+ -+ intel_add_fb_offsets(&src_x, &src_y, plane_state, 0); -+ -+ if (INTEL_GEN(dev_priv) >= 4) -+ offset = intel_plane_compute_aligned_offset(&src_x, &src_y, -+ plane_state, 0); -+ else -+ offset = 0; -+ -+ /* HSW/BDW do this automagically in hardware */ -+ if (!IS_HASWELL(dev_priv) && !IS_BROADWELL(dev_priv)) { -+ int src_w = drm_rect_width(&plane_state->base.src) >> 16; -+ int src_h = drm_rect_height(&plane_state->base.src) >> 16; -+ -+ if (rotation & DRM_MODE_ROTATE_180) { -+ src_x += src_w - 1; -+ src_y += src_h - 1; -+ } else if (rotation & DRM_MODE_REFLECT_X) { -+ src_x += src_w - 1; -+ } -+ } -+ -+ plane_state->color_plane[0].offset = offset; -+ plane_state->color_plane[0].x = src_x; -+ plane_state->color_plane[0].y = src_y; -+ -+ return 0; -+} -+ -+static int -+i9xx_plane_check(struct intel_crtc_state *crtc_state, -+ struct intel_plane_state *plane_state) -+{ -+ int ret; -+ -+ ret = chv_plane_check_rotation(plane_state); -+ if (ret) -+ return ret; -+ -+ ret = drm_atomic_helper_check_plane_state(&plane_state->base, -+ &crtc_state->base, -+ DRM_PLANE_HELPER_NO_SCALING, -+ DRM_PLANE_HELPER_NO_SCALING, -+ false, true); -+ if (ret) -+ return ret; -+ -+ if (!plane_state->base.visible) -+ return 0; -+ -+ ret = intel_plane_check_src_coordinates(plane_state); -+ if (ret) -+ return ret; -+ -+ ret = i9xx_check_plane_surface(plane_state); -+ if (ret) -+ return ret; -+ -+ plane_state->ctl = i9xx_plane_ctl(crtc_state, plane_state); -+ -+ return 0; -+} -+ -+static void i9xx_update_plane(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(plane->base.dev); -+ enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; -+ u32 linear_offset; -+ int x = plane_state->color_plane[0].x; -+ int y = plane_state->color_plane[0].y; -+ unsigned long irqflags; -+ u32 dspaddr_offset; -+ u32 dspcntr; -+ -+ dspcntr = plane_state->ctl | i9xx_plane_ctl_crtc(crtc_state); -+ -+ linear_offset = intel_fb_xy_to_linear(x, y, plane_state, 0); -+ -+ if (INTEL_GEN(dev_priv) >= 4) -+ dspaddr_offset = plane_state->color_plane[0].offset; -+ else -+ dspaddr_offset = linear_offset; -+ -+ spin_lock_irqsave(&dev_priv->uncore.lock, irqflags); -+ -+ I915_WRITE_FW(DSPSTRIDE(i9xx_plane), plane_state->color_plane[0].stride); -+ -+ if (INTEL_GEN(dev_priv) < 4) { -+ /* pipesrc and dspsize control the size that is scaled from, -+ * which should always be the user's requested size. -+ */ -+ I915_WRITE_FW(DSPPOS(i9xx_plane), 0); -+ I915_WRITE_FW(DSPSIZE(i9xx_plane), -+ ((crtc_state->pipe_src_h - 1) << 16) | -+ (crtc_state->pipe_src_w - 1)); -+ } else if (IS_CHERRYVIEW(dev_priv) && i9xx_plane == PLANE_B) { -+ I915_WRITE_FW(PRIMPOS(i9xx_plane), 0); -+ I915_WRITE_FW(PRIMSIZE(i9xx_plane), -+ ((crtc_state->pipe_src_h - 1) << 16) | -+ (crtc_state->pipe_src_w - 1)); -+ I915_WRITE_FW(PRIMCNSTALPHA(i9xx_plane), 0); -+ } -+ -+ if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) { -+ I915_WRITE_FW(DSPOFFSET(i9xx_plane), (y << 16) | x); -+ } else if (INTEL_GEN(dev_priv) >= 4) { -+ I915_WRITE_FW(DSPLINOFF(i9xx_plane), linear_offset); -+ I915_WRITE_FW(DSPTILEOFF(i9xx_plane), (y << 16) | x); -+ } -+ -+ /* -+ * The control register self-arms if the plane was previously -+ * disabled. Try to make the plane enable atomic by writing -+ * the control register just before the surface register. -+ */ -+ I915_WRITE_FW(DSPCNTR(i9xx_plane), dspcntr); -+ if (INTEL_GEN(dev_priv) >= 4) -+ I915_WRITE_FW(DSPSURF(i9xx_plane), -+ intel_plane_ggtt_offset(plane_state) + -+ dspaddr_offset); -+ else -+ I915_WRITE_FW(DSPADDR(i9xx_plane), -+ intel_plane_ggtt_offset(plane_state) + -+ dspaddr_offset); -+ -+ spin_unlock_irqrestore(&dev_priv->uncore.lock, irqflags); -+} -+ -+static void i9xx_disable_plane(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(plane->base.dev); -+ enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; -+ unsigned long irqflags; -+ u32 dspcntr; -+ -+ /* -+ * DSPCNTR pipe gamma enable on g4x+ and pipe csc -+ * enable on ilk+ affect the pipe bottom color as -+ * well, so we must configure them even if the plane -+ * is disabled. -+ * -+ * On pre-g4x there is no way to gamma correct the -+ * pipe bottom color but we'll keep on doing this -+ * anyway so that the crtc state readout works correctly. -+ */ -+ dspcntr = i9xx_plane_ctl_crtc(crtc_state); -+ -+ spin_lock_irqsave(&dev_priv->uncore.lock, irqflags); -+ -+ I915_WRITE_FW(DSPCNTR(i9xx_plane), dspcntr); -+ if (INTEL_GEN(dev_priv) >= 4) -+ I915_WRITE_FW(DSPSURF(i9xx_plane), 0); -+ else -+ I915_WRITE_FW(DSPADDR(i9xx_plane), 0); -+ -+ spin_unlock_irqrestore(&dev_priv->uncore.lock, irqflags); -+} -+ -+static bool i9xx_plane_get_hw_state(struct intel_plane *plane, -+ enum pipe *pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(plane->base.dev); -+ enum intel_display_power_domain power_domain; -+ enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; -+ intel_wakeref_t wakeref; -+ bool ret; -+ u32 val; -+ -+ /* -+ * Not 100% correct for planes that can move between pipes, -+ * but that's only the case for gen2-4 which don't have any -+ * display power wells. -+ */ -+ power_domain = POWER_DOMAIN_PIPE(plane->pipe); -+ wakeref = intel_display_power_get_if_enabled(dev_priv, power_domain); -+ if (!wakeref) -+ return false; -+ -+ val = I915_READ(DSPCNTR(i9xx_plane)); -+ -+ ret = val & DISPLAY_PLANE_ENABLE; -+ -+ if (INTEL_GEN(dev_priv) >= 5) -+ *pipe = plane->pipe; -+ else -+ *pipe = (val & DISPPLANE_SEL_PIPE_MASK) >> -+ DISPPLANE_SEL_PIPE_SHIFT; -+ -+ intel_display_power_put(dev_priv, power_domain, wakeref); -+ -+ return ret; -+} -+ -+static u32 -+intel_fb_stride_alignment(const struct drm_framebuffer *fb, int color_plane) -+{ -+ if (fb->modifier == DRM_FORMAT_MOD_LINEAR) -+ return 64; -+ else -+ return intel_tile_width_bytes(fb, color_plane); -+} -+ -+static void skl_detach_scaler(struct intel_crtc *intel_crtc, int id) -+{ -+ struct drm_device *dev = intel_crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ I915_WRITE(SKL_PS_CTRL(intel_crtc->pipe, id), 0); -+ I915_WRITE(SKL_PS_WIN_POS(intel_crtc->pipe, id), 0); -+ I915_WRITE(SKL_PS_WIN_SZ(intel_crtc->pipe, id), 0); -+} -+ -+/* -+ * This function detaches (aka. unbinds) unused scalers in hardware -+ */ -+static void skl_detach_scalers(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc_state->base.crtc); -+ const struct intel_crtc_scaler_state *scaler_state = -+ &crtc_state->scaler_state; -+ int i; -+ -+ /* loop through and disable scalers that aren't in use */ -+ for (i = 0; i < intel_crtc->num_scalers; i++) { -+ if (!scaler_state->scalers[i].in_use) -+ skl_detach_scaler(intel_crtc, i); -+ } -+} -+ -+static unsigned int skl_plane_stride_mult(const struct drm_framebuffer *fb, -+ int color_plane, unsigned int rotation) -+{ -+ /* -+ * The stride is either expressed as a multiple of 64 bytes chunks for -+ * linear buffers or in number of tiles for tiled buffers. -+ */ -+ if (fb->modifier == DRM_FORMAT_MOD_LINEAR) -+ return 64; -+ else if (drm_rotation_90_or_270(rotation)) -+ return intel_tile_height(fb, color_plane); -+ else -+ return intel_tile_width_bytes(fb, color_plane); -+} -+ -+u32 skl_plane_stride(const struct intel_plane_state *plane_state, -+ int color_plane) -+{ -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ unsigned int rotation = plane_state->base.rotation; -+ u32 stride = plane_state->color_plane[color_plane].stride; -+ -+ if (color_plane >= fb->format->num_planes) -+ return 0; -+ -+ return stride / skl_plane_stride_mult(fb, color_plane, rotation); -+} -+ -+static u32 skl_plane_ctl_format(u32 pixel_format) -+{ -+ switch (pixel_format) { -+ case DRM_FORMAT_C8: -+ return PLANE_CTL_FORMAT_INDEXED; -+ case DRM_FORMAT_RGB565: -+ return PLANE_CTL_FORMAT_RGB_565; -+ case DRM_FORMAT_XBGR8888: -+ case DRM_FORMAT_ABGR8888: -+ return PLANE_CTL_FORMAT_XRGB_8888 | PLANE_CTL_ORDER_RGBX; -+ case DRM_FORMAT_XRGB8888: -+ case DRM_FORMAT_ARGB8888: -+ return PLANE_CTL_FORMAT_XRGB_8888; -+ case DRM_FORMAT_XRGB2101010: -+ return PLANE_CTL_FORMAT_XRGB_2101010; -+ case DRM_FORMAT_XBGR2101010: -+ return PLANE_CTL_ORDER_RGBX | PLANE_CTL_FORMAT_XRGB_2101010; -+ case DRM_FORMAT_XBGR16161616F: -+ case DRM_FORMAT_ABGR16161616F: -+ return PLANE_CTL_FORMAT_XRGB_16161616F | PLANE_CTL_ORDER_RGBX; -+ case DRM_FORMAT_XRGB16161616F: -+ case DRM_FORMAT_ARGB16161616F: -+ return PLANE_CTL_FORMAT_XRGB_16161616F; -+ case DRM_FORMAT_YUYV: -+ return PLANE_CTL_FORMAT_YUV422 | PLANE_CTL_YUV422_YUYV; -+ case DRM_FORMAT_YVYU: -+ return PLANE_CTL_FORMAT_YUV422 | PLANE_CTL_YUV422_YVYU; -+ case DRM_FORMAT_UYVY: -+ return PLANE_CTL_FORMAT_YUV422 | PLANE_CTL_YUV422_UYVY; -+ case DRM_FORMAT_VYUY: -+ return PLANE_CTL_FORMAT_YUV422 | PLANE_CTL_YUV422_VYUY; -+ case DRM_FORMAT_NV12: -+ return PLANE_CTL_FORMAT_NV12; -+ case DRM_FORMAT_P010: -+ return PLANE_CTL_FORMAT_P010; -+ case DRM_FORMAT_P012: -+ return PLANE_CTL_FORMAT_P012; -+ case DRM_FORMAT_P016: -+ return PLANE_CTL_FORMAT_P016; -+ case DRM_FORMAT_Y210: -+ return PLANE_CTL_FORMAT_Y210; -+ case DRM_FORMAT_Y212: -+ return PLANE_CTL_FORMAT_Y212; -+ case DRM_FORMAT_Y216: -+ return PLANE_CTL_FORMAT_Y216; -+ case DRM_FORMAT_XVYU2101010: -+ return PLANE_CTL_FORMAT_Y410; -+ case DRM_FORMAT_XVYU12_16161616: -+ return PLANE_CTL_FORMAT_Y412; -+ case DRM_FORMAT_XVYU16161616: -+ return PLANE_CTL_FORMAT_Y416; -+ default: -+ MISSING_CASE(pixel_format); -+ } -+ -+ return 0; -+} -+ -+static u32 skl_plane_ctl_alpha(const struct intel_plane_state *plane_state) -+{ -+ if (!plane_state->base.fb->format->has_alpha) -+ return PLANE_CTL_ALPHA_DISABLE; -+ -+ switch (plane_state->base.pixel_blend_mode) { -+ case DRM_MODE_BLEND_PIXEL_NONE: -+ return PLANE_CTL_ALPHA_DISABLE; -+ case DRM_MODE_BLEND_PREMULTI: -+ return PLANE_CTL_ALPHA_SW_PREMULTIPLY; -+ case DRM_MODE_BLEND_COVERAGE: -+ return PLANE_CTL_ALPHA_HW_PREMULTIPLY; -+ default: -+ MISSING_CASE(plane_state->base.pixel_blend_mode); -+ return PLANE_CTL_ALPHA_DISABLE; -+ } -+} -+ -+static u32 glk_plane_color_ctl_alpha(const struct intel_plane_state *plane_state) -+{ -+ if (!plane_state->base.fb->format->has_alpha) -+ return PLANE_COLOR_ALPHA_DISABLE; -+ -+ switch (plane_state->base.pixel_blend_mode) { -+ case DRM_MODE_BLEND_PIXEL_NONE: -+ return PLANE_COLOR_ALPHA_DISABLE; -+ case DRM_MODE_BLEND_PREMULTI: -+ return PLANE_COLOR_ALPHA_SW_PREMULTIPLY; -+ case DRM_MODE_BLEND_COVERAGE: -+ return PLANE_COLOR_ALPHA_HW_PREMULTIPLY; -+ default: -+ MISSING_CASE(plane_state->base.pixel_blend_mode); -+ return PLANE_COLOR_ALPHA_DISABLE; -+ } -+} -+ -+static u32 skl_plane_ctl_tiling(u64 fb_modifier) -+{ -+ switch (fb_modifier) { -+ case DRM_FORMAT_MOD_LINEAR: -+ break; -+ case I915_FORMAT_MOD_X_TILED: -+ return PLANE_CTL_TILED_X; -+ case I915_FORMAT_MOD_Y_TILED: -+ return PLANE_CTL_TILED_Y; -+ case I915_FORMAT_MOD_Y_TILED_CCS: -+ return PLANE_CTL_TILED_Y | PLANE_CTL_RENDER_DECOMPRESSION_ENABLE; -+ case I915_FORMAT_MOD_Yf_TILED: -+ return PLANE_CTL_TILED_YF; -+ case I915_FORMAT_MOD_Yf_TILED_CCS: -+ return PLANE_CTL_TILED_YF | PLANE_CTL_RENDER_DECOMPRESSION_ENABLE; -+ default: -+ MISSING_CASE(fb_modifier); -+ } -+ -+ return 0; -+} -+ -+static u32 skl_plane_ctl_rotate(unsigned int rotate) -+{ -+ switch (rotate) { -+ case DRM_MODE_ROTATE_0: -+ break; -+ /* -+ * DRM_MODE_ROTATE_ is counter clockwise to stay compatible with Xrandr -+ * while i915 HW rotation is clockwise, thats why this swapping. -+ */ -+ case DRM_MODE_ROTATE_90: -+ return PLANE_CTL_ROTATE_270; -+ case DRM_MODE_ROTATE_180: -+ return PLANE_CTL_ROTATE_180; -+ case DRM_MODE_ROTATE_270: -+ return PLANE_CTL_ROTATE_90; -+ default: -+ MISSING_CASE(rotate); -+ } -+ -+ return 0; -+} -+ -+static u32 cnl_plane_ctl_flip(unsigned int reflect) -+{ -+ switch (reflect) { -+ case 0: -+ break; -+ case DRM_MODE_REFLECT_X: -+ return PLANE_CTL_FLIP_HORIZONTAL; -+ case DRM_MODE_REFLECT_Y: -+ default: -+ MISSING_CASE(reflect); -+ } -+ -+ return 0; -+} -+ -+u32 skl_plane_ctl_crtc(const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ u32 plane_ctl = 0; -+ -+ if (INTEL_GEN(dev_priv) >= 10 || IS_GEMINILAKE(dev_priv)) -+ return plane_ctl; -+ -+ if (crtc_state->gamma_enable) -+ plane_ctl |= PLANE_CTL_PIPE_GAMMA_ENABLE; -+ -+ if (crtc_state->csc_enable) -+ plane_ctl |= PLANE_CTL_PIPE_CSC_ENABLE; -+ -+ return plane_ctl; -+} -+ -+u32 skl_plane_ctl(const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state) -+{ -+ struct drm_i915_private *dev_priv = -+ to_i915(plane_state->base.plane->dev); -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ unsigned int rotation = plane_state->base.rotation; -+ const struct drm_intel_sprite_colorkey *key = &plane_state->ckey; -+ u32 plane_ctl; -+ -+ plane_ctl = PLANE_CTL_ENABLE; -+ -+ if (INTEL_GEN(dev_priv) < 10 && !IS_GEMINILAKE(dev_priv)) { -+ plane_ctl |= skl_plane_ctl_alpha(plane_state); -+ plane_ctl |= PLANE_CTL_PLANE_GAMMA_DISABLE; -+ -+ if (plane_state->base.color_encoding == DRM_COLOR_YCBCR_BT709) -+ plane_ctl |= PLANE_CTL_YUV_TO_RGB_CSC_FORMAT_BT709; -+ -+ if (plane_state->base.color_range == DRM_COLOR_YCBCR_FULL_RANGE) -+ plane_ctl |= PLANE_CTL_YUV_RANGE_CORRECTION_DISABLE; -+ } -+ -+ plane_ctl |= skl_plane_ctl_format(fb->format->format); -+ plane_ctl |= skl_plane_ctl_tiling(fb->modifier); -+ plane_ctl |= skl_plane_ctl_rotate(rotation & DRM_MODE_ROTATE_MASK); -+ -+ if (INTEL_GEN(dev_priv) >= 10) -+ plane_ctl |= cnl_plane_ctl_flip(rotation & -+ DRM_MODE_REFLECT_MASK); -+ -+ if (key->flags & I915_SET_COLORKEY_DESTINATION) -+ plane_ctl |= PLANE_CTL_KEY_ENABLE_DESTINATION; -+ else if (key->flags & I915_SET_COLORKEY_SOURCE) -+ plane_ctl |= PLANE_CTL_KEY_ENABLE_SOURCE; -+ -+ return plane_ctl; -+} -+ -+u32 glk_plane_color_ctl_crtc(const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ u32 plane_color_ctl = 0; -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ return plane_color_ctl; -+ -+ if (crtc_state->gamma_enable) -+ plane_color_ctl |= PLANE_COLOR_PIPE_GAMMA_ENABLE; -+ -+ if (crtc_state->csc_enable) -+ plane_color_ctl |= PLANE_COLOR_PIPE_CSC_ENABLE; -+ -+ return plane_color_ctl; -+} -+ -+u32 glk_plane_color_ctl(const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state) -+{ -+ struct drm_i915_private *dev_priv = -+ to_i915(plane_state->base.plane->dev); -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ struct intel_plane *plane = to_intel_plane(plane_state->base.plane); -+ u32 plane_color_ctl = 0; -+ -+ plane_color_ctl |= PLANE_COLOR_PLANE_GAMMA_DISABLE; -+ plane_color_ctl |= glk_plane_color_ctl_alpha(plane_state); -+ -+ if (fb->format->is_yuv && !icl_is_hdr_plane(dev_priv, plane->id)) { -+ if (plane_state->base.color_encoding == DRM_COLOR_YCBCR_BT709) -+ plane_color_ctl |= PLANE_COLOR_CSC_MODE_YUV709_TO_RGB709; -+ else -+ plane_color_ctl |= PLANE_COLOR_CSC_MODE_YUV601_TO_RGB709; -+ -+ if (plane_state->base.color_range == DRM_COLOR_YCBCR_FULL_RANGE) -+ plane_color_ctl |= PLANE_COLOR_YUV_RANGE_CORRECTION_DISABLE; -+ } else if (fb->format->is_yuv) { -+ plane_color_ctl |= PLANE_COLOR_INPUT_CSC_ENABLE; -+ } -+ -+ return plane_color_ctl; -+} -+ -+static int -+__intel_display_resume(struct drm_device *dev, -+ struct drm_atomic_state *state, -+ struct drm_modeset_acquire_ctx *ctx) -+{ -+ struct drm_crtc_state *crtc_state; -+ struct drm_crtc *crtc; -+ int i, ret; -+ -+ intel_modeset_setup_hw_state(dev, ctx); -+ i915_redisable_vga(to_i915(dev)); -+ -+ if (!state) -+ return 0; -+ -+ /* -+ * We've duplicated the state, pointers to the old state are invalid. -+ * -+ * Don't attempt to use the old state until we commit the duplicated state. -+ */ -+ for_each_new_crtc_in_state(state, crtc, crtc_state, i) { -+ /* -+ * Force recalculation even if we restore -+ * current state. With fast modeset this may not result -+ * in a modeset when the state is compatible. -+ */ -+ crtc_state->mode_changed = true; -+ } -+ -+ /* ignore any reset values/BIOS leftovers in the WM registers */ -+ if (!HAS_GMCH(to_i915(dev))) -+ to_intel_atomic_state(state)->skip_intermediate_wm = true; -+ -+ ret = drm_atomic_helper_commit_duplicated_state(state, ctx); -+ -+ WARN_ON(ret == -EDEADLK); -+ return ret; -+} -+ -+static bool gpu_reset_clobbers_display(struct drm_i915_private *dev_priv) -+{ -+ return (INTEL_INFO(dev_priv)->gpu_reset_clobbers_display && -+ intel_has_gpu_reset(dev_priv)); -+} -+ -+void intel_prepare_reset(struct drm_i915_private *dev_priv) -+{ -+ struct drm_device *dev = &dev_priv->drm; -+ struct drm_modeset_acquire_ctx *ctx = &dev_priv->reset_ctx; -+ struct drm_atomic_state *state; -+ int ret; -+ -+ /* reset doesn't touch the display */ -+ if (!i915_modparams.force_reset_modeset_test && -+ !gpu_reset_clobbers_display(dev_priv)) -+ return; -+ -+ /* We have a modeset vs reset deadlock, defensively unbreak it. */ -+ set_bit(I915_RESET_MODESET, &dev_priv->gpu_error.flags); -+ wake_up_all(&dev_priv->gpu_error.wait_queue); -+ -+ if (atomic_read(&dev_priv->gpu_error.pending_fb_pin)) { -+ DRM_DEBUG_KMS("Modeset potentially stuck, unbreaking through wedging\n"); -+ i915_gem_set_wedged(dev_priv); -+ } -+ -+ /* -+ * Need mode_config.mutex so that we don't -+ * trample ongoing ->detect() and whatnot. -+ */ -+ mutex_lock(&dev->mode_config.mutex); -+ drm_modeset_acquire_init(ctx, 0); -+ while (1) { -+ ret = drm_modeset_lock_all_ctx(dev, ctx); -+ if (ret != -EDEADLK) -+ break; -+ -+ drm_modeset_backoff(ctx); -+ } -+ /* -+ * Disabling the crtcs gracefully seems nicer. Also the -+ * g33 docs say we should at least disable all the planes. -+ */ -+ state = drm_atomic_helper_duplicate_state(dev, ctx); -+ if (IS_ERR(state)) { -+ ret = PTR_ERR(state); -+ DRM_ERROR("Duplicating state failed with %i\n", ret); -+ return; -+ } -+ -+ ret = drm_atomic_helper_disable_all(dev, ctx); -+ if (ret) { -+ DRM_ERROR("Suspending crtc's failed with %i\n", ret); -+ drm_atomic_state_put(state); -+ return; -+ } -+ -+ dev_priv->modeset_restore_state = state; -+ state->acquire_ctx = ctx; -+} -+ -+void intel_finish_reset(struct drm_i915_private *dev_priv) -+{ -+ struct drm_device *dev = &dev_priv->drm; -+ struct drm_modeset_acquire_ctx *ctx = &dev_priv->reset_ctx; -+ struct drm_atomic_state *state; -+ int ret; -+ -+ /* reset doesn't touch the display */ -+ if (!test_bit(I915_RESET_MODESET, &dev_priv->gpu_error.flags)) -+ return; -+ -+ state = fetch_and_zero(&dev_priv->modeset_restore_state); -+ if (!state) -+ goto unlock; -+ -+ /* reset doesn't touch the display */ -+ if (!gpu_reset_clobbers_display(dev_priv)) { -+ /* for testing only restore the display */ -+ ret = __intel_display_resume(dev, state, ctx); -+ if (ret) -+ DRM_ERROR("Restoring old state failed with %i\n", ret); -+ } else { -+ /* -+ * The display has been reset as well, -+ * so need a full re-initialization. -+ */ -+ intel_pps_unlock_regs_wa(dev_priv); -+ intel_modeset_init_hw(dev); -+ intel_init_clock_gating(dev_priv); -+ -+ spin_lock_irq(&dev_priv->irq_lock); -+ if (dev_priv->display.hpd_irq_setup) -+ dev_priv->display.hpd_irq_setup(dev_priv); -+ spin_unlock_irq(&dev_priv->irq_lock); -+ -+ ret = __intel_display_resume(dev, state, ctx); -+ if (ret) -+ DRM_ERROR("Restoring old state failed with %i\n", ret); -+ -+ intel_hpd_init(dev_priv); -+ } -+ -+ drm_atomic_state_put(state); -+unlock: -+ drm_modeset_drop_locks(ctx); -+ drm_modeset_acquire_fini(ctx); -+ mutex_unlock(&dev->mode_config.mutex); -+ -+ clear_bit(I915_RESET_MODESET, &dev_priv->gpu_error.flags); -+} -+ -+static void icl_set_pipe_chicken(struct intel_crtc *crtc) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ u32 tmp; -+ -+ tmp = I915_READ(PIPE_CHICKEN(pipe)); -+ -+ /* -+ * Display WA #1153: icl -+ * enable hardware to bypass the alpha math -+ * and rounding for per-pixel values 00 and 0xff -+ */ -+ tmp |= PER_PIXEL_ALPHA_BYPASS_EN; -+ /* -+ * Display WA # 1605353570: icl -+ * Set the pixel rounding bit to 1 for allowing -+ * passthrough of Frame buffer pixels unmodified -+ * across pipe -+ */ -+ tmp |= PIXEL_ROUNDING_TRUNC_FB_PASSTHRU; -+ I915_WRITE(PIPE_CHICKEN(pipe), tmp); -+} -+ -+static void intel_update_pipe_config(const struct intel_crtc_state *old_crtc_state, -+ const struct intel_crtc_state *new_crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ /* drm_atomic_helper_update_legacy_modeset_state might not be called. */ -+ crtc->base.mode = new_crtc_state->base.mode; -+ -+ /* -+ * Update pipe size and adjust fitter if needed: the reason for this is -+ * that in compute_mode_changes we check the native mode (not the pfit -+ * mode) to see if we can flip rather than do a full mode set. In the -+ * fastboot case, we'll flip, but if we don't update the pipesrc and -+ * pfit state, we'll end up with a big fb scanned out into the wrong -+ * sized surface. -+ */ -+ -+ I915_WRITE(PIPESRC(crtc->pipe), -+ ((new_crtc_state->pipe_src_w - 1) << 16) | -+ (new_crtc_state->pipe_src_h - 1)); -+ -+ /* on skylake this is done by detaching scalers */ -+ if (INTEL_GEN(dev_priv) >= 9) { -+ skl_detach_scalers(new_crtc_state); -+ -+ if (new_crtc_state->pch_pfit.enabled) -+ skylake_pfit_enable(new_crtc_state); -+ } else if (HAS_PCH_SPLIT(dev_priv)) { -+ if (new_crtc_state->pch_pfit.enabled) -+ ironlake_pfit_enable(new_crtc_state); -+ else if (old_crtc_state->pch_pfit.enabled) -+ ironlake_pfit_disable(old_crtc_state); -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ icl_set_pipe_chicken(crtc); -+} -+ -+static void intel_fdi_normal_train(struct intel_crtc *crtc) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int pipe = crtc->pipe; -+ i915_reg_t reg; -+ u32 temp; -+ -+ /* enable normal train */ -+ reg = FDI_TX_CTL(pipe); -+ temp = I915_READ(reg); -+ if (IS_IVYBRIDGE(dev_priv)) { -+ temp &= ~FDI_LINK_TRAIN_NONE_IVB; -+ temp |= FDI_LINK_TRAIN_NONE_IVB | FDI_TX_ENHANCE_FRAME_ENABLE; -+ } else { -+ temp &= ~FDI_LINK_TRAIN_NONE; -+ temp |= FDI_LINK_TRAIN_NONE | FDI_TX_ENHANCE_FRAME_ENABLE; -+ } -+ I915_WRITE(reg, temp); -+ -+ reg = FDI_RX_CTL(pipe); -+ temp = I915_READ(reg); -+ if (HAS_PCH_CPT(dev_priv)) { -+ temp &= ~FDI_LINK_TRAIN_PATTERN_MASK_CPT; -+ temp |= FDI_LINK_TRAIN_NORMAL_CPT; -+ } else { -+ temp &= ~FDI_LINK_TRAIN_NONE; -+ temp |= FDI_LINK_TRAIN_NONE; -+ } -+ I915_WRITE(reg, temp | FDI_RX_ENHANCE_FRAME_ENABLE); -+ -+ /* wait one idle pattern time */ -+ POSTING_READ(reg); -+ udelay(1000); -+ -+ /* IVB wants error correction enabled */ -+ if (IS_IVYBRIDGE(dev_priv)) -+ I915_WRITE(reg, I915_READ(reg) | FDI_FS_ERRC_ENABLE | -+ FDI_FE_ERRC_ENABLE); -+} -+ -+/* The FDI link training functions for ILK/Ibexpeak. */ -+static void ironlake_fdi_link_train(struct intel_crtc *crtc, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int pipe = crtc->pipe; -+ i915_reg_t reg; -+ u32 temp, tries; -+ -+ /* FDI needs bits from pipe first */ -+ assert_pipe_enabled(dev_priv, pipe); -+ -+ /* Train 1: umask FDI RX Interrupt symbol_lock and bit_lock bit -+ for train result */ -+ reg = FDI_RX_IMR(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_RX_SYMBOL_LOCK; -+ temp &= ~FDI_RX_BIT_LOCK; -+ I915_WRITE(reg, temp); -+ I915_READ(reg); -+ udelay(150); -+ -+ /* enable CPU FDI TX and PCH FDI RX */ -+ reg = FDI_TX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_DP_PORT_WIDTH_MASK; -+ temp |= FDI_DP_PORT_WIDTH(crtc_state->fdi_lanes); -+ temp &= ~FDI_LINK_TRAIN_NONE; -+ temp |= FDI_LINK_TRAIN_PATTERN_1; -+ I915_WRITE(reg, temp | FDI_TX_ENABLE); -+ -+ reg = FDI_RX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_LINK_TRAIN_NONE; -+ temp |= FDI_LINK_TRAIN_PATTERN_1; -+ I915_WRITE(reg, temp | FDI_RX_ENABLE); -+ -+ POSTING_READ(reg); -+ udelay(150); -+ -+ /* Ironlake workaround, enable clock pointer after FDI enable*/ -+ I915_WRITE(FDI_RX_CHICKEN(pipe), FDI_RX_PHASE_SYNC_POINTER_OVR); -+ I915_WRITE(FDI_RX_CHICKEN(pipe), FDI_RX_PHASE_SYNC_POINTER_OVR | -+ FDI_RX_PHASE_SYNC_POINTER_EN); -+ -+ reg = FDI_RX_IIR(pipe); -+ for (tries = 0; tries < 5; tries++) { -+ temp = I915_READ(reg); -+ DRM_DEBUG_KMS("FDI_RX_IIR 0x%x\n", temp); -+ -+ if ((temp & FDI_RX_BIT_LOCK)) { -+ DRM_DEBUG_KMS("FDI train 1 done.\n"); -+ I915_WRITE(reg, temp | FDI_RX_BIT_LOCK); -+ break; -+ } -+ } -+ if (tries == 5) -+ DRM_ERROR("FDI train 1 fail!\n"); -+ -+ /* Train 2 */ -+ reg = FDI_TX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_LINK_TRAIN_NONE; -+ temp |= FDI_LINK_TRAIN_PATTERN_2; -+ I915_WRITE(reg, temp); -+ -+ reg = FDI_RX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_LINK_TRAIN_NONE; -+ temp |= FDI_LINK_TRAIN_PATTERN_2; -+ I915_WRITE(reg, temp); -+ -+ POSTING_READ(reg); -+ udelay(150); -+ -+ reg = FDI_RX_IIR(pipe); -+ for (tries = 0; tries < 5; tries++) { -+ temp = I915_READ(reg); -+ DRM_DEBUG_KMS("FDI_RX_IIR 0x%x\n", temp); -+ -+ if (temp & FDI_RX_SYMBOL_LOCK) { -+ I915_WRITE(reg, temp | FDI_RX_SYMBOL_LOCK); -+ DRM_DEBUG_KMS("FDI train 2 done.\n"); -+ break; -+ } -+ } -+ if (tries == 5) -+ DRM_ERROR("FDI train 2 fail!\n"); -+ -+ DRM_DEBUG_KMS("FDI train done\n"); -+ -+} -+ -+static const int snb_b_fdi_train_param[] = { -+ FDI_LINK_TRAIN_400MV_0DB_SNB_B, -+ FDI_LINK_TRAIN_400MV_6DB_SNB_B, -+ FDI_LINK_TRAIN_600MV_3_5DB_SNB_B, -+ FDI_LINK_TRAIN_800MV_0DB_SNB_B, -+}; -+ -+/* The FDI link training functions for SNB/Cougarpoint. */ -+static void gen6_fdi_link_train(struct intel_crtc *crtc, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int pipe = crtc->pipe; -+ i915_reg_t reg; -+ u32 temp, i, retry; -+ -+ /* Train 1: umask FDI RX Interrupt symbol_lock and bit_lock bit -+ for train result */ -+ reg = FDI_RX_IMR(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_RX_SYMBOL_LOCK; -+ temp &= ~FDI_RX_BIT_LOCK; -+ I915_WRITE(reg, temp); -+ -+ POSTING_READ(reg); -+ udelay(150); -+ -+ /* enable CPU FDI TX and PCH FDI RX */ -+ reg = FDI_TX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_DP_PORT_WIDTH_MASK; -+ temp |= FDI_DP_PORT_WIDTH(crtc_state->fdi_lanes); -+ temp &= ~FDI_LINK_TRAIN_NONE; -+ temp |= FDI_LINK_TRAIN_PATTERN_1; -+ temp &= ~FDI_LINK_TRAIN_VOL_EMP_MASK; -+ /* SNB-B */ -+ temp |= FDI_LINK_TRAIN_400MV_0DB_SNB_B; -+ I915_WRITE(reg, temp | FDI_TX_ENABLE); -+ -+ I915_WRITE(FDI_RX_MISC(pipe), -+ FDI_RX_TP1_TO_TP2_48 | FDI_RX_FDI_DELAY_90); -+ -+ reg = FDI_RX_CTL(pipe); -+ temp = I915_READ(reg); -+ if (HAS_PCH_CPT(dev_priv)) { -+ temp &= ~FDI_LINK_TRAIN_PATTERN_MASK_CPT; -+ temp |= FDI_LINK_TRAIN_PATTERN_1_CPT; -+ } else { -+ temp &= ~FDI_LINK_TRAIN_NONE; -+ temp |= FDI_LINK_TRAIN_PATTERN_1; -+ } -+ I915_WRITE(reg, temp | FDI_RX_ENABLE); -+ -+ POSTING_READ(reg); -+ udelay(150); -+ -+ for (i = 0; i < 4; i++) { -+ reg = FDI_TX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_LINK_TRAIN_VOL_EMP_MASK; -+ temp |= snb_b_fdi_train_param[i]; -+ I915_WRITE(reg, temp); -+ -+ POSTING_READ(reg); -+ udelay(500); -+ -+ for (retry = 0; retry < 5; retry++) { -+ reg = FDI_RX_IIR(pipe); -+ temp = I915_READ(reg); -+ DRM_DEBUG_KMS("FDI_RX_IIR 0x%x\n", temp); -+ if (temp & FDI_RX_BIT_LOCK) { -+ I915_WRITE(reg, temp | FDI_RX_BIT_LOCK); -+ DRM_DEBUG_KMS("FDI train 1 done.\n"); -+ break; -+ } -+ udelay(50); -+ } -+ if (retry < 5) -+ break; -+ } -+ if (i == 4) -+ DRM_ERROR("FDI train 1 fail!\n"); -+ -+ /* Train 2 */ -+ reg = FDI_TX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_LINK_TRAIN_NONE; -+ temp |= FDI_LINK_TRAIN_PATTERN_2; -+ if (IS_GEN(dev_priv, 6)) { -+ temp &= ~FDI_LINK_TRAIN_VOL_EMP_MASK; -+ /* SNB-B */ -+ temp |= FDI_LINK_TRAIN_400MV_0DB_SNB_B; -+ } -+ I915_WRITE(reg, temp); -+ -+ reg = FDI_RX_CTL(pipe); -+ temp = I915_READ(reg); -+ if (HAS_PCH_CPT(dev_priv)) { -+ temp &= ~FDI_LINK_TRAIN_PATTERN_MASK_CPT; -+ temp |= FDI_LINK_TRAIN_PATTERN_2_CPT; -+ } else { -+ temp &= ~FDI_LINK_TRAIN_NONE; -+ temp |= FDI_LINK_TRAIN_PATTERN_2; -+ } -+ I915_WRITE(reg, temp); -+ -+ POSTING_READ(reg); -+ udelay(150); -+ -+ for (i = 0; i < 4; i++) { -+ reg = FDI_TX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_LINK_TRAIN_VOL_EMP_MASK; -+ temp |= snb_b_fdi_train_param[i]; -+ I915_WRITE(reg, temp); -+ -+ POSTING_READ(reg); -+ udelay(500); -+ -+ for (retry = 0; retry < 5; retry++) { -+ reg = FDI_RX_IIR(pipe); -+ temp = I915_READ(reg); -+ DRM_DEBUG_KMS("FDI_RX_IIR 0x%x\n", temp); -+ if (temp & FDI_RX_SYMBOL_LOCK) { -+ I915_WRITE(reg, temp | FDI_RX_SYMBOL_LOCK); -+ DRM_DEBUG_KMS("FDI train 2 done.\n"); -+ break; -+ } -+ udelay(50); -+ } -+ if (retry < 5) -+ break; -+ } -+ if (i == 4) -+ DRM_ERROR("FDI train 2 fail!\n"); -+ -+ DRM_DEBUG_KMS("FDI train done.\n"); -+} -+ -+/* Manual link training for Ivy Bridge A0 parts */ -+static void ivb_manual_fdi_link_train(struct intel_crtc *crtc, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int pipe = crtc->pipe; -+ i915_reg_t reg; -+ u32 temp, i, j; -+ -+ /* Train 1: umask FDI RX Interrupt symbol_lock and bit_lock bit -+ for train result */ -+ reg = FDI_RX_IMR(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_RX_SYMBOL_LOCK; -+ temp &= ~FDI_RX_BIT_LOCK; -+ I915_WRITE(reg, temp); -+ -+ POSTING_READ(reg); -+ udelay(150); -+ -+ DRM_DEBUG_KMS("FDI_RX_IIR before link train 0x%x\n", -+ I915_READ(FDI_RX_IIR(pipe))); -+ -+ /* Try each vswing and preemphasis setting twice before moving on */ -+ for (j = 0; j < ARRAY_SIZE(snb_b_fdi_train_param) * 2; j++) { -+ /* disable first in case we need to retry */ -+ reg = FDI_TX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~(FDI_LINK_TRAIN_AUTO | FDI_LINK_TRAIN_NONE_IVB); -+ temp &= ~FDI_TX_ENABLE; -+ I915_WRITE(reg, temp); -+ -+ reg = FDI_RX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_LINK_TRAIN_AUTO; -+ temp &= ~FDI_LINK_TRAIN_PATTERN_MASK_CPT; -+ temp &= ~FDI_RX_ENABLE; -+ I915_WRITE(reg, temp); -+ -+ /* enable CPU FDI TX and PCH FDI RX */ -+ reg = FDI_TX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_DP_PORT_WIDTH_MASK; -+ temp |= FDI_DP_PORT_WIDTH(crtc_state->fdi_lanes); -+ temp |= FDI_LINK_TRAIN_PATTERN_1_IVB; -+ temp &= ~FDI_LINK_TRAIN_VOL_EMP_MASK; -+ temp |= snb_b_fdi_train_param[j/2]; -+ temp |= FDI_COMPOSITE_SYNC; -+ I915_WRITE(reg, temp | FDI_TX_ENABLE); -+ -+ I915_WRITE(FDI_RX_MISC(pipe), -+ FDI_RX_TP1_TO_TP2_48 | FDI_RX_FDI_DELAY_90); -+ -+ reg = FDI_RX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp |= FDI_LINK_TRAIN_PATTERN_1_CPT; -+ temp |= FDI_COMPOSITE_SYNC; -+ I915_WRITE(reg, temp | FDI_RX_ENABLE); -+ -+ POSTING_READ(reg); -+ udelay(1); /* should be 0.5us */ -+ -+ for (i = 0; i < 4; i++) { -+ reg = FDI_RX_IIR(pipe); -+ temp = I915_READ(reg); -+ DRM_DEBUG_KMS("FDI_RX_IIR 0x%x\n", temp); -+ -+ if (temp & FDI_RX_BIT_LOCK || -+ (I915_READ(reg) & FDI_RX_BIT_LOCK)) { -+ I915_WRITE(reg, temp | FDI_RX_BIT_LOCK); -+ DRM_DEBUG_KMS("FDI train 1 done, level %i.\n", -+ i); -+ break; -+ } -+ udelay(1); /* should be 0.5us */ -+ } -+ if (i == 4) { -+ DRM_DEBUG_KMS("FDI train 1 fail on vswing %d\n", j / 2); -+ continue; -+ } -+ -+ /* Train 2 */ -+ reg = FDI_TX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_LINK_TRAIN_NONE_IVB; -+ temp |= FDI_LINK_TRAIN_PATTERN_2_IVB; -+ I915_WRITE(reg, temp); -+ -+ reg = FDI_RX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_LINK_TRAIN_PATTERN_MASK_CPT; -+ temp |= FDI_LINK_TRAIN_PATTERN_2_CPT; -+ I915_WRITE(reg, temp); -+ -+ POSTING_READ(reg); -+ udelay(2); /* should be 1.5us */ -+ -+ for (i = 0; i < 4; i++) { -+ reg = FDI_RX_IIR(pipe); -+ temp = I915_READ(reg); -+ DRM_DEBUG_KMS("FDI_RX_IIR 0x%x\n", temp); -+ -+ if (temp & FDI_RX_SYMBOL_LOCK || -+ (I915_READ(reg) & FDI_RX_SYMBOL_LOCK)) { -+ I915_WRITE(reg, temp | FDI_RX_SYMBOL_LOCK); -+ DRM_DEBUG_KMS("FDI train 2 done, level %i.\n", -+ i); -+ goto train_done; -+ } -+ udelay(2); /* should be 1.5us */ -+ } -+ if (i == 4) -+ DRM_DEBUG_KMS("FDI train 2 fail on vswing %d\n", j / 2); -+ } -+ -+train_done: -+ DRM_DEBUG_KMS("FDI train done.\n"); -+} -+ -+static void ironlake_fdi_pll_enable(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(intel_crtc->base.dev); -+ int pipe = intel_crtc->pipe; -+ i915_reg_t reg; -+ u32 temp; -+ -+ /* enable PCH FDI RX PLL, wait warmup plus DMI latency */ -+ reg = FDI_RX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~(FDI_DP_PORT_WIDTH_MASK | (0x7 << 16)); -+ temp |= FDI_DP_PORT_WIDTH(crtc_state->fdi_lanes); -+ temp |= (I915_READ(PIPECONF(pipe)) & PIPECONF_BPC_MASK) << 11; -+ I915_WRITE(reg, temp | FDI_RX_PLL_ENABLE); -+ -+ POSTING_READ(reg); -+ udelay(200); -+ -+ /* Switch from Rawclk to PCDclk */ -+ temp = I915_READ(reg); -+ I915_WRITE(reg, temp | FDI_PCDCLK); -+ -+ POSTING_READ(reg); -+ udelay(200); -+ -+ /* Enable CPU FDI TX PLL, always on for Ironlake */ -+ reg = FDI_TX_CTL(pipe); -+ temp = I915_READ(reg); -+ if ((temp & FDI_TX_PLL_ENABLE) == 0) { -+ I915_WRITE(reg, temp | FDI_TX_PLL_ENABLE); -+ -+ POSTING_READ(reg); -+ udelay(100); -+ } -+} -+ -+static void ironlake_fdi_pll_disable(struct intel_crtc *intel_crtc) -+{ -+ struct drm_device *dev = intel_crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int pipe = intel_crtc->pipe; -+ i915_reg_t reg; -+ u32 temp; -+ -+ /* Switch from PCDclk to Rawclk */ -+ reg = FDI_RX_CTL(pipe); -+ temp = I915_READ(reg); -+ I915_WRITE(reg, temp & ~FDI_PCDCLK); -+ -+ /* Disable CPU FDI TX PLL */ -+ reg = FDI_TX_CTL(pipe); -+ temp = I915_READ(reg); -+ I915_WRITE(reg, temp & ~FDI_TX_PLL_ENABLE); -+ -+ POSTING_READ(reg); -+ udelay(100); -+ -+ reg = FDI_RX_CTL(pipe); -+ temp = I915_READ(reg); -+ I915_WRITE(reg, temp & ~FDI_RX_PLL_ENABLE); -+ -+ /* Wait for the clocks to turn off. */ -+ POSTING_READ(reg); -+ udelay(100); -+} -+ -+static void ironlake_fdi_disable(struct drm_crtc *crtc) -+{ -+ struct drm_device *dev = crtc->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ int pipe = intel_crtc->pipe; -+ i915_reg_t reg; -+ u32 temp; -+ -+ /* disable CPU FDI tx and PCH FDI rx */ -+ reg = FDI_TX_CTL(pipe); -+ temp = I915_READ(reg); -+ I915_WRITE(reg, temp & ~FDI_TX_ENABLE); -+ POSTING_READ(reg); -+ -+ reg = FDI_RX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~(0x7 << 16); -+ temp |= (I915_READ(PIPECONF(pipe)) & PIPECONF_BPC_MASK) << 11; -+ I915_WRITE(reg, temp & ~FDI_RX_ENABLE); -+ -+ POSTING_READ(reg); -+ udelay(100); -+ -+ /* Ironlake workaround, disable clock pointer after downing FDI */ -+ if (HAS_PCH_IBX(dev_priv)) -+ I915_WRITE(FDI_RX_CHICKEN(pipe), FDI_RX_PHASE_SYNC_POINTER_OVR); -+ -+ /* still set train pattern 1 */ -+ reg = FDI_TX_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~FDI_LINK_TRAIN_NONE; -+ temp |= FDI_LINK_TRAIN_PATTERN_1; -+ I915_WRITE(reg, temp); -+ -+ reg = FDI_RX_CTL(pipe); -+ temp = I915_READ(reg); -+ if (HAS_PCH_CPT(dev_priv)) { -+ temp &= ~FDI_LINK_TRAIN_PATTERN_MASK_CPT; -+ temp |= FDI_LINK_TRAIN_PATTERN_1_CPT; -+ } else { -+ temp &= ~FDI_LINK_TRAIN_NONE; -+ temp |= FDI_LINK_TRAIN_PATTERN_1; -+ } -+ /* BPC in FDI rx is consistent with that in PIPECONF */ -+ temp &= ~(0x07 << 16); -+ temp |= (I915_READ(PIPECONF(pipe)) & PIPECONF_BPC_MASK) << 11; -+ I915_WRITE(reg, temp); -+ -+ POSTING_READ(reg); -+ udelay(100); -+} -+ -+bool intel_has_pending_fb_unpin(struct drm_i915_private *dev_priv) -+{ -+ struct drm_crtc *crtc; -+ bool cleanup_done; -+ -+ drm_for_each_crtc(crtc, &dev_priv->drm) { -+ struct drm_crtc_commit *commit; -+ spin_lock(&crtc->commit_lock); -+ commit = list_first_entry_or_null(&crtc->commit_list, -+ struct drm_crtc_commit, commit_entry); -+ cleanup_done = commit ? -+ try_wait_for_completion(&commit->cleanup_done) : true; -+ spin_unlock(&crtc->commit_lock); -+ -+ if (cleanup_done) -+ continue; -+ -+ drm_crtc_wait_one_vblank(crtc); -+ -+ return true; -+ } -+ -+ return false; -+} -+ -+void lpt_disable_iclkip(struct drm_i915_private *dev_priv) -+{ -+ u32 temp; -+ -+ I915_WRITE(PIXCLK_GATE, PIXCLK_GATE_GATE); -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ temp = intel_sbi_read(dev_priv, SBI_SSCCTL6, SBI_ICLK); -+ temp |= SBI_SSCCTL_DISABLE; -+ intel_sbi_write(dev_priv, SBI_SSCCTL6, temp, SBI_ICLK); -+ -+ mutex_unlock(&dev_priv->sb_lock); -+} -+ -+/* Program iCLKIP clock to the desired frequency */ -+static void lpt_program_iclkip(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ int clock = crtc_state->base.adjusted_mode.crtc_clock; -+ u32 divsel, phaseinc, auxdiv, phasedir = 0; -+ u32 temp; -+ -+ lpt_disable_iclkip(dev_priv); -+ -+ /* The iCLK virtual clock root frequency is in MHz, -+ * but the adjusted_mode->crtc_clock in in KHz. To get the -+ * divisors, it is necessary to divide one by another, so we -+ * convert the virtual clock precision to KHz here for higher -+ * precision. -+ */ -+ for (auxdiv = 0; auxdiv < 2; auxdiv++) { -+ u32 iclk_virtual_root_freq = 172800 * 1000; -+ u32 iclk_pi_range = 64; -+ u32 desired_divisor; -+ -+ desired_divisor = DIV_ROUND_CLOSEST(iclk_virtual_root_freq, -+ clock << auxdiv); -+ divsel = (desired_divisor / iclk_pi_range) - 2; -+ phaseinc = desired_divisor % iclk_pi_range; -+ -+ /* -+ * Near 20MHz is a corner case which is -+ * out of range for the 7-bit divisor -+ */ -+ if (divsel <= 0x7f) -+ break; -+ } -+ -+ /* This should not happen with any sane values */ -+ WARN_ON(SBI_SSCDIVINTPHASE_DIVSEL(divsel) & -+ ~SBI_SSCDIVINTPHASE_DIVSEL_MASK); -+ WARN_ON(SBI_SSCDIVINTPHASE_DIR(phasedir) & -+ ~SBI_SSCDIVINTPHASE_INCVAL_MASK); -+ -+ DRM_DEBUG_KMS("iCLKIP clock: found settings for %dKHz refresh rate: auxdiv=%x, divsel=%x, phasedir=%x, phaseinc=%x\n", -+ clock, -+ auxdiv, -+ divsel, -+ phasedir, -+ phaseinc); -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ /* Program SSCDIVINTPHASE6 */ -+ temp = intel_sbi_read(dev_priv, SBI_SSCDIVINTPHASE6, SBI_ICLK); -+ temp &= ~SBI_SSCDIVINTPHASE_DIVSEL_MASK; -+ temp |= SBI_SSCDIVINTPHASE_DIVSEL(divsel); -+ temp &= ~SBI_SSCDIVINTPHASE_INCVAL_MASK; -+ temp |= SBI_SSCDIVINTPHASE_INCVAL(phaseinc); -+ temp |= SBI_SSCDIVINTPHASE_DIR(phasedir); -+ temp |= SBI_SSCDIVINTPHASE_PROPAGATE; -+ intel_sbi_write(dev_priv, SBI_SSCDIVINTPHASE6, temp, SBI_ICLK); -+ -+ /* Program SSCAUXDIV */ -+ temp = intel_sbi_read(dev_priv, SBI_SSCAUXDIV6, SBI_ICLK); -+ temp &= ~SBI_SSCAUXDIV_FINALDIV2SEL(1); -+ temp |= SBI_SSCAUXDIV_FINALDIV2SEL(auxdiv); -+ intel_sbi_write(dev_priv, SBI_SSCAUXDIV6, temp, SBI_ICLK); -+ -+ /* Enable modulator and associated divider */ -+ temp = intel_sbi_read(dev_priv, SBI_SSCCTL6, SBI_ICLK); -+ temp &= ~SBI_SSCCTL_DISABLE; -+ intel_sbi_write(dev_priv, SBI_SSCCTL6, temp, SBI_ICLK); -+ -+ mutex_unlock(&dev_priv->sb_lock); -+ -+ /* Wait for initialization time */ -+ udelay(24); -+ -+ I915_WRITE(PIXCLK_GATE, PIXCLK_GATE_UNGATE); -+} -+ -+int lpt_get_iclkip(struct drm_i915_private *dev_priv) -+{ -+ u32 divsel, phaseinc, auxdiv; -+ u32 iclk_virtual_root_freq = 172800 * 1000; -+ u32 iclk_pi_range = 64; -+ u32 desired_divisor; -+ u32 temp; -+ -+ if ((I915_READ(PIXCLK_GATE) & PIXCLK_GATE_UNGATE) == 0) -+ return 0; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ temp = intel_sbi_read(dev_priv, SBI_SSCCTL6, SBI_ICLK); -+ if (temp & SBI_SSCCTL_DISABLE) { -+ mutex_unlock(&dev_priv->sb_lock); -+ return 0; -+ } -+ -+ temp = intel_sbi_read(dev_priv, SBI_SSCDIVINTPHASE6, SBI_ICLK); -+ divsel = (temp & SBI_SSCDIVINTPHASE_DIVSEL_MASK) >> -+ SBI_SSCDIVINTPHASE_DIVSEL_SHIFT; -+ phaseinc = (temp & SBI_SSCDIVINTPHASE_INCVAL_MASK) >> -+ SBI_SSCDIVINTPHASE_INCVAL_SHIFT; -+ -+ temp = intel_sbi_read(dev_priv, SBI_SSCAUXDIV6, SBI_ICLK); -+ auxdiv = (temp & SBI_SSCAUXDIV_FINALDIV2SEL_MASK) >> -+ SBI_SSCAUXDIV_FINALDIV2SEL_SHIFT; -+ -+ mutex_unlock(&dev_priv->sb_lock); -+ -+ desired_divisor = (divsel + 2) * iclk_pi_range + phaseinc; -+ -+ return DIV_ROUND_CLOSEST(iclk_virtual_root_freq, -+ desired_divisor << auxdiv); -+} -+ -+static void ironlake_pch_transcoder_set_timings(const struct intel_crtc_state *crtc_state, -+ enum pipe pch_transcoder) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; -+ -+ I915_WRITE(PCH_TRANS_HTOTAL(pch_transcoder), -+ I915_READ(HTOTAL(cpu_transcoder))); -+ I915_WRITE(PCH_TRANS_HBLANK(pch_transcoder), -+ I915_READ(HBLANK(cpu_transcoder))); -+ I915_WRITE(PCH_TRANS_HSYNC(pch_transcoder), -+ I915_READ(HSYNC(cpu_transcoder))); -+ -+ I915_WRITE(PCH_TRANS_VTOTAL(pch_transcoder), -+ I915_READ(VTOTAL(cpu_transcoder))); -+ I915_WRITE(PCH_TRANS_VBLANK(pch_transcoder), -+ I915_READ(VBLANK(cpu_transcoder))); -+ I915_WRITE(PCH_TRANS_VSYNC(pch_transcoder), -+ I915_READ(VSYNC(cpu_transcoder))); -+ I915_WRITE(PCH_TRANS_VSYNCSHIFT(pch_transcoder), -+ I915_READ(VSYNCSHIFT(cpu_transcoder))); -+} -+ -+static void cpt_set_fdi_bc_bifurcation(struct drm_i915_private *dev_priv, bool enable) -+{ -+ u32 temp; -+ -+ temp = I915_READ(SOUTH_CHICKEN1); -+ if (!!(temp & FDI_BC_BIFURCATION_SELECT) == enable) -+ return; -+ -+ WARN_ON(I915_READ(FDI_RX_CTL(PIPE_B)) & FDI_RX_ENABLE); -+ WARN_ON(I915_READ(FDI_RX_CTL(PIPE_C)) & FDI_RX_ENABLE); -+ -+ temp &= ~FDI_BC_BIFURCATION_SELECT; -+ if (enable) -+ temp |= FDI_BC_BIFURCATION_SELECT; -+ -+ DRM_DEBUG_KMS("%sabling fdi C rx\n", enable ? "en" : "dis"); -+ I915_WRITE(SOUTH_CHICKEN1, temp); -+ POSTING_READ(SOUTH_CHICKEN1); -+} -+ -+static void ivybridge_update_fdi_bc_bifurcation(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ switch (crtc->pipe) { -+ case PIPE_A: -+ break; -+ case PIPE_B: -+ if (crtc_state->fdi_lanes > 2) -+ cpt_set_fdi_bc_bifurcation(dev_priv, false); -+ else -+ cpt_set_fdi_bc_bifurcation(dev_priv, true); -+ -+ break; -+ case PIPE_C: -+ cpt_set_fdi_bc_bifurcation(dev_priv, true); -+ -+ break; -+ default: -+ BUG(); -+ } -+} -+ -+/* -+ * Finds the encoder associated with the given CRTC. This can only be -+ * used when we know that the CRTC isn't feeding multiple encoders! -+ */ -+static struct intel_encoder * -+intel_get_crtc_new_encoder(const struct intel_atomic_state *state, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ const struct drm_connector_state *connector_state; -+ const struct drm_connector *connector; -+ struct intel_encoder *encoder = NULL; -+ int num_encoders = 0; -+ int i; -+ -+ for_each_new_connector_in_state(&state->base, connector, connector_state, i) { -+ if (connector_state->crtc != &crtc->base) -+ continue; -+ -+ encoder = to_intel_encoder(connector_state->best_encoder); -+ num_encoders++; -+ } -+ -+ WARN(num_encoders != 1, "%d encoders for pipe %c\n", -+ num_encoders, pipe_name(crtc->pipe)); -+ -+ return encoder; -+} -+ -+/* -+ * Enable PCH resources required for PCH ports: -+ * - PCH PLLs -+ * - FDI training & RX/TX -+ * - update transcoder timings -+ * - DP transcoding bits -+ * - transcoder -+ */ -+static void ironlake_pch_enable(const struct intel_atomic_state *state, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int pipe = crtc->pipe; -+ u32 temp; -+ -+ assert_pch_transcoder_disabled(dev_priv, pipe); -+ -+ if (IS_IVYBRIDGE(dev_priv)) -+ ivybridge_update_fdi_bc_bifurcation(crtc_state); -+ -+ /* Write the TU size bits before fdi link training, so that error -+ * detection works. */ -+ I915_WRITE(FDI_RX_TUSIZE1(pipe), -+ I915_READ(PIPE_DATA_M1(pipe)) & TU_SIZE_MASK); -+ -+ /* For PCH output, training FDI link */ -+ dev_priv->display.fdi_link_train(crtc, crtc_state); -+ -+ /* We need to program the right clock selection before writing the pixel -+ * mutliplier into the DPLL. */ -+ if (HAS_PCH_CPT(dev_priv)) { -+ u32 sel; -+ -+ temp = I915_READ(PCH_DPLL_SEL); -+ temp |= TRANS_DPLL_ENABLE(pipe); -+ sel = TRANS_DPLLB_SEL(pipe); -+ if (crtc_state->shared_dpll == -+ intel_get_shared_dpll_by_id(dev_priv, DPLL_ID_PCH_PLL_B)) -+ temp |= sel; -+ else -+ temp &= ~sel; -+ I915_WRITE(PCH_DPLL_SEL, temp); -+ } -+ -+ /* XXX: pch pll's can be enabled any time before we enable the PCH -+ * transcoder, and we actually should do this to not upset any PCH -+ * transcoder that already use the clock when we share it. -+ * -+ * Note that enable_shared_dpll tries to do the right thing, but -+ * get_shared_dpll unconditionally resets the pll - we need that to have -+ * the right LVDS enable sequence. */ -+ intel_enable_shared_dpll(crtc_state); -+ -+ /* set transcoder timing, panel must allow it */ -+ assert_panel_unlocked(dev_priv, pipe); -+ ironlake_pch_transcoder_set_timings(crtc_state, pipe); -+ -+ intel_fdi_normal_train(crtc); -+ -+ /* For PCH DP, enable TRANS_DP_CTL */ -+ if (HAS_PCH_CPT(dev_priv) && -+ intel_crtc_has_dp_encoder(crtc_state)) { -+ const struct drm_display_mode *adjusted_mode = -+ &crtc_state->base.adjusted_mode; -+ u32 bpc = (I915_READ(PIPECONF(pipe)) & PIPECONF_BPC_MASK) >> 5; -+ i915_reg_t reg = TRANS_DP_CTL(pipe); -+ enum port port; -+ -+ temp = I915_READ(reg); -+ temp &= ~(TRANS_DP_PORT_SEL_MASK | -+ TRANS_DP_SYNC_MASK | -+ TRANS_DP_BPC_MASK); -+ temp |= TRANS_DP_OUTPUT_ENABLE; -+ temp |= bpc << 9; /* same format but at 11:9 */ -+ -+ if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) -+ temp |= TRANS_DP_HSYNC_ACTIVE_HIGH; -+ if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) -+ temp |= TRANS_DP_VSYNC_ACTIVE_HIGH; -+ -+ port = intel_get_crtc_new_encoder(state, crtc_state)->port; -+ WARN_ON(port < PORT_B || port > PORT_D); -+ temp |= TRANS_DP_PORT_SEL(port); -+ -+ I915_WRITE(reg, temp); -+ } -+ -+ ironlake_enable_pch_transcoder(crtc_state); -+} -+ -+static void lpt_pch_enable(const struct intel_atomic_state *state, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; -+ -+ assert_pch_transcoder_disabled(dev_priv, PIPE_A); -+ -+ lpt_program_iclkip(crtc_state); -+ -+ /* Set transcoder timing. */ -+ ironlake_pch_transcoder_set_timings(crtc_state, PIPE_A); -+ -+ lpt_enable_pch_transcoder(dev_priv, cpu_transcoder); -+} -+ -+static void cpt_verify_modeset(struct drm_device *dev, int pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ i915_reg_t dslreg = PIPEDSL(pipe); -+ u32 temp; -+ -+ temp = I915_READ(dslreg); -+ udelay(500); -+ if (wait_for(I915_READ(dslreg) != temp, 5)) { -+ if (wait_for(I915_READ(dslreg) != temp, 5)) -+ DRM_ERROR("mode set failed: pipe %c stuck\n", pipe_name(pipe)); -+ } -+} -+ -+/* -+ * The hardware phase 0.0 refers to the center of the pixel. -+ * We want to start from the top/left edge which is phase -+ * -0.5. That matches how the hardware calculates the scaling -+ * factors (from top-left of the first pixel to bottom-right -+ * of the last pixel, as opposed to the pixel centers). -+ * -+ * For 4:2:0 subsampled chroma planes we obviously have to -+ * adjust that so that the chroma sample position lands in -+ * the right spot. -+ * -+ * Note that for packed YCbCr 4:2:2 formats there is no way to -+ * control chroma siting. The hardware simply replicates the -+ * chroma samples for both of the luma samples, and thus we don't -+ * actually get the expected MPEG2 chroma siting convention :( -+ * The same behaviour is observed on pre-SKL platforms as well. -+ * -+ * Theory behind the formula (note that we ignore sub-pixel -+ * source coordinates): -+ * s = source sample position -+ * d = destination sample position -+ * -+ * Downscaling 4:1: -+ * -0.5 -+ * | 0.0 -+ * | | 1.5 (initial phase) -+ * | | | -+ * v v v -+ * | s | s | s | s | -+ * | d | -+ * -+ * Upscaling 1:4: -+ * -0.5 -+ * | -0.375 (initial phase) -+ * | | 0.0 -+ * | | | -+ * v v v -+ * | s | -+ * | d | d | d | d | -+ */ -+u16 skl_scaler_calc_phase(int sub, int scale, bool chroma_cosited) -+{ -+ int phase = -0x8000; -+ u16 trip = 0; -+ -+ if (chroma_cosited) -+ phase += (sub - 1) * 0x8000 / sub; -+ -+ phase += scale / (2 * sub); -+ -+ /* -+ * Hardware initial phase limited to [-0.5:1.5]. -+ * Since the max hardware scale factor is 3.0, we -+ * should never actually excdeed 1.0 here. -+ */ -+ WARN_ON(phase < -0x8000 || phase > 0x18000); -+ -+ if (phase < 0) -+ phase = 0x10000 + phase; -+ else -+ trip = PS_PHASE_TRIP; -+ -+ return ((phase >> 2) & PS_PHASE_MASK) | trip; -+} -+ -+static int -+skl_update_scaler(struct intel_crtc_state *crtc_state, bool force_detach, -+ unsigned int scaler_user, int *scaler_id, -+ int src_w, int src_h, int dst_w, int dst_h, -+ const struct drm_format_info *format, bool need_scaler) -+{ -+ struct intel_crtc_scaler_state *scaler_state = -+ &crtc_state->scaler_state; -+ struct intel_crtc *intel_crtc = -+ to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(intel_crtc->base.dev); -+ const struct drm_display_mode *adjusted_mode = -+ &crtc_state->base.adjusted_mode; -+ -+ /* -+ * Src coordinates are already rotated by 270 degrees for -+ * the 90/270 degree plane rotation cases (to match the -+ * GTT mapping), hence no need to account for rotation here. -+ */ -+ if (src_w != dst_w || src_h != dst_h) -+ need_scaler = true; -+ -+ /* -+ * Scaling/fitting not supported in IF-ID mode in GEN9+ -+ * TODO: Interlace fetch mode doesn't support YUV420 planar formats. -+ * Once NV12 is enabled, handle it here while allocating scaler -+ * for NV12. -+ */ -+ if (INTEL_GEN(dev_priv) >= 9 && crtc_state->base.enable && -+ need_scaler && adjusted_mode->flags & DRM_MODE_FLAG_INTERLACE) { -+ DRM_DEBUG_KMS("Pipe/Plane scaling not supported with IF-ID mode\n"); -+ return -EINVAL; -+ } -+ -+ /* -+ * if plane is being disabled or scaler is no more required or force detach -+ * - free scaler binded to this plane/crtc -+ * - in order to do this, update crtc->scaler_usage -+ * -+ * Here scaler state in crtc_state is set free so that -+ * scaler can be assigned to other user. Actual register -+ * update to free the scaler is done in plane/panel-fit programming. -+ * For this purpose crtc/plane_state->scaler_id isn't reset here. -+ */ -+ if (force_detach || !need_scaler) { -+ if (*scaler_id >= 0) { -+ scaler_state->scaler_users &= ~(1 << scaler_user); -+ scaler_state->scalers[*scaler_id].in_use = 0; -+ -+ DRM_DEBUG_KMS("scaler_user index %u.%u: " -+ "Staged freeing scaler id %d scaler_users = 0x%x\n", -+ intel_crtc->pipe, scaler_user, *scaler_id, -+ scaler_state->scaler_users); -+ *scaler_id = -1; -+ } -+ return 0; -+ } -+ -+ if (format && is_planar_yuv_format(format->format) && -+ (src_h < SKL_MIN_YUV_420_SRC_H || src_w < SKL_MIN_YUV_420_SRC_W)) { -+ DRM_DEBUG_KMS("Planar YUV: src dimensions not met\n"); -+ return -EINVAL; -+ } -+ -+ /* range checks */ -+ if (src_w < SKL_MIN_SRC_W || src_h < SKL_MIN_SRC_H || -+ dst_w < SKL_MIN_DST_W || dst_h < SKL_MIN_DST_H || -+ (INTEL_GEN(dev_priv) >= 11 && -+ (src_w > ICL_MAX_SRC_W || src_h > ICL_MAX_SRC_H || -+ dst_w > ICL_MAX_DST_W || dst_h > ICL_MAX_DST_H)) || -+ (INTEL_GEN(dev_priv) < 11 && -+ (src_w > SKL_MAX_SRC_W || src_h > SKL_MAX_SRC_H || -+ dst_w > SKL_MAX_DST_W || dst_h > SKL_MAX_DST_H))) { -+ DRM_DEBUG_KMS("scaler_user index %u.%u: src %ux%u dst %ux%u " -+ "size is out of scaler range\n", -+ intel_crtc->pipe, scaler_user, src_w, src_h, dst_w, dst_h); -+ return -EINVAL; -+ } -+ -+ /* mark this plane as a scaler user in crtc_state */ -+ scaler_state->scaler_users |= (1 << scaler_user); -+ DRM_DEBUG_KMS("scaler_user index %u.%u: " -+ "staged scaling request for %ux%u->%ux%u scaler_users = 0x%x\n", -+ intel_crtc->pipe, scaler_user, src_w, src_h, dst_w, dst_h, -+ scaler_state->scaler_users); -+ -+ return 0; -+} -+ -+/** -+ * skl_update_scaler_crtc - Stages update to scaler state for a given crtc. -+ * -+ * @state: crtc's scaler state -+ * -+ * Return -+ * 0 - scaler_usage updated successfully -+ * error - requested scaling cannot be supported or other error condition -+ */ -+int skl_update_scaler_crtc(struct intel_crtc_state *state) -+{ -+ const struct drm_display_mode *adjusted_mode = &state->base.adjusted_mode; -+ bool need_scaler = false; -+ -+ if (state->output_format == INTEL_OUTPUT_FORMAT_YCBCR420) -+ need_scaler = true; -+ -+ return skl_update_scaler(state, !state->base.active, SKL_CRTC_INDEX, -+ &state->scaler_state.scaler_id, -+ state->pipe_src_w, state->pipe_src_h, -+ adjusted_mode->crtc_hdisplay, -+ adjusted_mode->crtc_vdisplay, NULL, need_scaler); -+} -+ -+/** -+ * skl_update_scaler_plane - Stages update to scaler state for a given plane. -+ * @crtc_state: crtc's scaler state -+ * @plane_state: atomic plane state to update -+ * -+ * Return -+ * 0 - scaler_usage updated successfully -+ * error - requested scaling cannot be supported or other error condition -+ */ -+static int skl_update_scaler_plane(struct intel_crtc_state *crtc_state, -+ struct intel_plane_state *plane_state) -+{ -+ struct intel_plane *intel_plane = -+ to_intel_plane(plane_state->base.plane); -+ struct drm_i915_private *dev_priv = to_i915(intel_plane->base.dev); -+ struct drm_framebuffer *fb = plane_state->base.fb; -+ int ret; -+ bool force_detach = !fb || !plane_state->base.visible; -+ bool need_scaler = false; -+ -+ /* Pre-gen11 and SDR planes always need a scaler for planar formats. */ -+ if (!icl_is_hdr_plane(dev_priv, intel_plane->id) && -+ fb && is_planar_yuv_format(fb->format->format)) -+ need_scaler = true; -+ -+ ret = skl_update_scaler(crtc_state, force_detach, -+ drm_plane_index(&intel_plane->base), -+ &plane_state->scaler_id, -+ drm_rect_width(&plane_state->base.src) >> 16, -+ drm_rect_height(&plane_state->base.src) >> 16, -+ drm_rect_width(&plane_state->base.dst), -+ drm_rect_height(&plane_state->base.dst), -+ fb ? fb->format : NULL, need_scaler); -+ -+ if (ret || plane_state->scaler_id < 0) -+ return ret; -+ -+ /* check colorkey */ -+ if (plane_state->ckey.flags) { -+ DRM_DEBUG_KMS("[PLANE:%d:%s] scaling with color key not allowed", -+ intel_plane->base.base.id, -+ intel_plane->base.name); -+ return -EINVAL; -+ } -+ -+ /* Check src format */ -+ switch (fb->format->format) { -+ case DRM_FORMAT_RGB565: -+ case DRM_FORMAT_XBGR8888: -+ case DRM_FORMAT_XRGB8888: -+ case DRM_FORMAT_ABGR8888: -+ case DRM_FORMAT_ARGB8888: -+ case DRM_FORMAT_XRGB2101010: -+ case DRM_FORMAT_XBGR2101010: -+ case DRM_FORMAT_XBGR16161616F: -+ case DRM_FORMAT_ABGR16161616F: -+ case DRM_FORMAT_XRGB16161616F: -+ case DRM_FORMAT_ARGB16161616F: -+ case DRM_FORMAT_YUYV: -+ case DRM_FORMAT_YVYU: -+ case DRM_FORMAT_UYVY: -+ case DRM_FORMAT_VYUY: -+ case DRM_FORMAT_NV12: -+ case DRM_FORMAT_P010: -+ case DRM_FORMAT_P012: -+ case DRM_FORMAT_P016: -+ case DRM_FORMAT_Y210: -+ case DRM_FORMAT_Y212: -+ case DRM_FORMAT_Y216: -+ case DRM_FORMAT_XVYU2101010: -+ case DRM_FORMAT_XVYU12_16161616: -+ case DRM_FORMAT_XVYU16161616: -+ break; -+ default: -+ DRM_DEBUG_KMS("[PLANE:%d:%s] FB:%d unsupported scaling format 0x%x\n", -+ intel_plane->base.base.id, intel_plane->base.name, -+ fb->base.id, fb->format->format); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static void skylake_scaler_disable(struct intel_crtc *crtc) -+{ -+ int i; -+ -+ for (i = 0; i < crtc->num_scalers; i++) -+ skl_detach_scaler(crtc, i); -+} -+ -+static void skylake_pfit_enable(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ const struct intel_crtc_scaler_state *scaler_state = -+ &crtc_state->scaler_state; -+ -+ if (crtc_state->pch_pfit.enabled) { -+ u16 uv_rgb_hphase, uv_rgb_vphase; -+ int pfit_w, pfit_h, hscale, vscale; -+ int id; -+ -+ if (WARN_ON(crtc_state->scaler_state.scaler_id < 0)) -+ return; -+ -+ pfit_w = (crtc_state->pch_pfit.size >> 16) & 0xFFFF; -+ pfit_h = crtc_state->pch_pfit.size & 0xFFFF; -+ -+ hscale = (crtc_state->pipe_src_w << 16) / pfit_w; -+ vscale = (crtc_state->pipe_src_h << 16) / pfit_h; -+ -+ uv_rgb_hphase = skl_scaler_calc_phase(1, hscale, false); -+ uv_rgb_vphase = skl_scaler_calc_phase(1, vscale, false); -+ -+ id = scaler_state->scaler_id; -+ I915_WRITE(SKL_PS_CTRL(pipe, id), PS_SCALER_EN | -+ PS_FILTER_MEDIUM | scaler_state->scalers[id].mode); -+ I915_WRITE_FW(SKL_PS_VPHASE(pipe, id), -+ PS_Y_PHASE(0) | PS_UV_RGB_PHASE(uv_rgb_vphase)); -+ I915_WRITE_FW(SKL_PS_HPHASE(pipe, id), -+ PS_Y_PHASE(0) | PS_UV_RGB_PHASE(uv_rgb_hphase)); -+ I915_WRITE(SKL_PS_WIN_POS(pipe, id), crtc_state->pch_pfit.pos); -+ I915_WRITE(SKL_PS_WIN_SZ(pipe, id), crtc_state->pch_pfit.size); -+ } -+} -+ -+static void ironlake_pfit_enable(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ int pipe = crtc->pipe; -+ -+ if (crtc_state->pch_pfit.enabled) { -+ /* Force use of hard-coded filter coefficients -+ * as some pre-programmed values are broken, -+ * e.g. x201. -+ */ -+ if (IS_IVYBRIDGE(dev_priv) || IS_HASWELL(dev_priv)) -+ I915_WRITE(PF_CTL(pipe), PF_ENABLE | PF_FILTER_MED_3x3 | -+ PF_PIPE_SEL_IVB(pipe)); -+ else -+ I915_WRITE(PF_CTL(pipe), PF_ENABLE | PF_FILTER_MED_3x3); -+ I915_WRITE(PF_WIN_POS(pipe), crtc_state->pch_pfit.pos); -+ I915_WRITE(PF_WIN_SZ(pipe), crtc_state->pch_pfit.size); -+ } -+} -+ -+void hsw_enable_ips(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ if (!crtc_state->ips_enabled) -+ return; -+ -+ /* -+ * We can only enable IPS after we enable a plane and wait for a vblank -+ * This function is called from post_plane_update, which is run after -+ * a vblank wait. -+ */ -+ WARN_ON(!(crtc_state->active_planes & ~BIT(PLANE_CURSOR))); -+ -+ if (IS_BROADWELL(dev_priv)) { -+ mutex_lock(&dev_priv->pcu_lock); -+ WARN_ON(sandybridge_pcode_write(dev_priv, DISPLAY_IPS_CONTROL, -+ IPS_ENABLE | IPS_PCODE_CONTROL)); -+ mutex_unlock(&dev_priv->pcu_lock); -+ /* Quoting Art Runyan: "its not safe to expect any particular -+ * value in IPS_CTL bit 31 after enabling IPS through the -+ * mailbox." Moreover, the mailbox may return a bogus state, -+ * so we need to just enable it and continue on. -+ */ -+ } else { -+ I915_WRITE(IPS_CTL, IPS_ENABLE); -+ /* The bit only becomes 1 in the next vblank, so this wait here -+ * is essentially intel_wait_for_vblank. If we don't have this -+ * and don't wait for vblanks until the end of crtc_enable, then -+ * the HW state readout code will complain that the expected -+ * IPS_CTL value is not the one we read. */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ IPS_CTL, IPS_ENABLE, IPS_ENABLE, -+ 50)) -+ DRM_ERROR("Timed out waiting for IPS enable\n"); -+ } -+} -+ -+void hsw_disable_ips(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ if (!crtc_state->ips_enabled) -+ return; -+ -+ if (IS_BROADWELL(dev_priv)) { -+ mutex_lock(&dev_priv->pcu_lock); -+ WARN_ON(sandybridge_pcode_write(dev_priv, DISPLAY_IPS_CONTROL, 0)); -+ mutex_unlock(&dev_priv->pcu_lock); -+ /* -+ * Wait for PCODE to finish disabling IPS. The BSpec specified -+ * 42ms timeout value leads to occasional timeouts so use 100ms -+ * instead. -+ */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ IPS_CTL, IPS_ENABLE, 0, -+ 100)) -+ DRM_ERROR("Timed out waiting for IPS disable\n"); -+ } else { -+ I915_WRITE(IPS_CTL, 0); -+ POSTING_READ(IPS_CTL); -+ } -+ -+ /* We need to wait for a vblank before we can disable the plane. */ -+ intel_wait_for_vblank(dev_priv, crtc->pipe); -+} -+ -+static void intel_crtc_dpms_overlay_disable(struct intel_crtc *intel_crtc) -+{ -+ if (intel_crtc->overlay) { -+ struct drm_device *dev = intel_crtc->base.dev; -+ -+ mutex_lock(&dev->struct_mutex); -+ (void) intel_overlay_switch_off(intel_crtc->overlay); -+ mutex_unlock(&dev->struct_mutex); -+ } -+ -+ /* Let userspace switch the overlay on again. In most cases userspace -+ * has to recompute where to put it anyway. -+ */ -+} -+ -+/** -+ * intel_post_enable_primary - Perform operations after enabling primary plane -+ * @crtc: the CRTC whose primary plane was just enabled -+ * @new_crtc_state: the enabling state -+ * -+ * Performs potentially sleeping operations that must be done after the primary -+ * plane is enabled, such as updating FBC and IPS. Note that this may be -+ * called due to an explicit primary plane update, or due to an implicit -+ * re-enable that is caused when a sprite plane is updated to no longer -+ * completely hide the primary plane. -+ */ -+static void -+intel_post_enable_primary(struct drm_crtc *crtc, -+ const struct intel_crtc_state *new_crtc_state) -+{ -+ struct drm_device *dev = crtc->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ int pipe = intel_crtc->pipe; -+ -+ /* -+ * Gen2 reports pipe underruns whenever all planes are disabled. -+ * So don't enable underrun reporting before at least some planes -+ * are enabled. -+ * FIXME: Need to fix the logic to work when we turn off all planes -+ * but leave the pipe running. -+ */ -+ if (IS_GEN(dev_priv, 2)) -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, true); -+ -+ /* Underruns don't always raise interrupts, so check manually. */ -+ intel_check_cpu_fifo_underruns(dev_priv); -+ intel_check_pch_fifo_underruns(dev_priv); -+} -+ -+/* FIXME get rid of this and use pre_plane_update */ -+static void -+intel_pre_disable_primary_noatomic(struct drm_crtc *crtc) -+{ -+ struct drm_device *dev = crtc->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ int pipe = intel_crtc->pipe; -+ -+ /* -+ * Gen2 reports pipe underruns whenever all planes are disabled. -+ * So disable underrun reporting before all the planes get disabled. -+ */ -+ if (IS_GEN(dev_priv, 2)) -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, false); -+ -+ hsw_disable_ips(to_intel_crtc_state(crtc->state)); -+ -+ /* -+ * Vblank time updates from the shadow to live plane control register -+ * are blocked if the memory self-refresh mode is active at that -+ * moment. So to make sure the plane gets truly disabled, disable -+ * first the self-refresh mode. The self-refresh enable bit in turn -+ * will be checked/applied by the HW only at the next frame start -+ * event which is after the vblank start event, so we need to have a -+ * wait-for-vblank between disabling the plane and the pipe. -+ */ -+ if (HAS_GMCH(dev_priv) && -+ intel_set_memory_cxsr(dev_priv, false)) -+ intel_wait_for_vblank(dev_priv, pipe); -+} -+ -+static bool hsw_pre_update_disable_ips(const struct intel_crtc_state *old_crtc_state, -+ const struct intel_crtc_state *new_crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ if (!old_crtc_state->ips_enabled) -+ return false; -+ -+ if (needs_modeset(&new_crtc_state->base)) -+ return true; -+ -+ /* -+ * Workaround : Do not read or write the pipe palette/gamma data while -+ * GAMMA_MODE is configured for split gamma and IPS_CTL has IPS enabled. -+ * -+ * Disable IPS before we program the LUT. -+ */ -+ if (IS_HASWELL(dev_priv) && -+ (new_crtc_state->base.color_mgmt_changed || -+ new_crtc_state->update_pipe) && -+ new_crtc_state->gamma_mode == GAMMA_MODE_MODE_SPLIT) -+ return true; -+ -+ return !new_crtc_state->ips_enabled; -+} -+ -+static bool hsw_post_update_enable_ips(const struct intel_crtc_state *old_crtc_state, -+ const struct intel_crtc_state *new_crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(new_crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ if (!new_crtc_state->ips_enabled) -+ return false; -+ -+ if (needs_modeset(&new_crtc_state->base)) -+ return true; -+ -+ /* -+ * Workaround : Do not read or write the pipe palette/gamma data while -+ * GAMMA_MODE is configured for split gamma and IPS_CTL has IPS enabled. -+ * -+ * Re-enable IPS after the LUT has been programmed. -+ */ -+ if (IS_HASWELL(dev_priv) && -+ (new_crtc_state->base.color_mgmt_changed || -+ new_crtc_state->update_pipe) && -+ new_crtc_state->gamma_mode == GAMMA_MODE_MODE_SPLIT) -+ return true; -+ -+ /* -+ * We can't read out IPS on broadwell, assume the worst and -+ * forcibly enable IPS on the first fastset. -+ */ -+ if (new_crtc_state->update_pipe && -+ old_crtc_state->base.adjusted_mode.private_flags & I915_MODE_FLAG_INHERITED) -+ return true; -+ -+ return !old_crtc_state->ips_enabled; -+} -+ -+static bool needs_nv12_wa(struct drm_i915_private *dev_priv, -+ const struct intel_crtc_state *crtc_state) -+{ -+ if (!crtc_state->nv12_planes) -+ return false; -+ -+ /* WA Display #0827: Gen9:all */ -+ if (IS_GEN(dev_priv, 9) && !IS_GEMINILAKE(dev_priv)) -+ return true; -+ -+ return false; -+} -+ -+static void intel_post_plane_update(struct intel_crtc_state *old_crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->base.crtc); -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_atomic_state *old_state = old_crtc_state->base.state; -+ struct intel_crtc_state *pipe_config = -+ intel_atomic_get_new_crtc_state(to_intel_atomic_state(old_state), -+ crtc); -+ struct drm_plane *primary = crtc->base.primary; -+ struct drm_plane_state *old_primary_state = -+ drm_atomic_get_old_plane_state(old_state, primary); -+ -+ intel_frontbuffer_flip(to_i915(crtc->base.dev), pipe_config->fb_bits); -+ -+ if (pipe_config->update_wm_post && pipe_config->base.active) -+ intel_update_watermarks(crtc); -+ -+ if (hsw_post_update_enable_ips(old_crtc_state, pipe_config)) -+ hsw_enable_ips(pipe_config); -+ -+ if (old_primary_state) { -+ struct drm_plane_state *new_primary_state = -+ drm_atomic_get_new_plane_state(old_state, primary); -+ -+ intel_fbc_post_update(crtc); -+ -+ if (new_primary_state->visible && -+ (needs_modeset(&pipe_config->base) || -+ !old_primary_state->visible)) -+ intel_post_enable_primary(&crtc->base, pipe_config); -+ } -+ -+ /* Display WA 827 */ -+ if (needs_nv12_wa(dev_priv, old_crtc_state) && -+ !needs_nv12_wa(dev_priv, pipe_config)) { -+ skl_wa_827(dev_priv, crtc->pipe, false); -+ } -+} -+ -+static void intel_pre_plane_update(struct intel_crtc_state *old_crtc_state, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->base.crtc); -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_atomic_state *old_state = old_crtc_state->base.state; -+ struct drm_plane *primary = crtc->base.primary; -+ struct drm_plane_state *old_primary_state = -+ drm_atomic_get_old_plane_state(old_state, primary); -+ bool modeset = needs_modeset(&pipe_config->base); -+ struct intel_atomic_state *old_intel_state = -+ to_intel_atomic_state(old_state); -+ -+ if (hsw_pre_update_disable_ips(old_crtc_state, pipe_config)) -+ hsw_disable_ips(old_crtc_state); -+ -+ if (old_primary_state) { -+ struct intel_plane_state *new_primary_state = -+ intel_atomic_get_new_plane_state(old_intel_state, -+ to_intel_plane(primary)); -+ -+ intel_fbc_pre_update(crtc, pipe_config, new_primary_state); -+ /* -+ * Gen2 reports pipe underruns whenever all planes are disabled. -+ * So disable underrun reporting before all the planes get disabled. -+ */ -+ if (IS_GEN(dev_priv, 2) && old_primary_state->visible && -+ (modeset || !new_primary_state->base.visible)) -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, false); -+ } -+ -+ /* Display WA 827 */ -+ if (!needs_nv12_wa(dev_priv, old_crtc_state) && -+ needs_nv12_wa(dev_priv, pipe_config)) { -+ skl_wa_827(dev_priv, crtc->pipe, true); -+ } -+ -+ /* -+ * Vblank time updates from the shadow to live plane control register -+ * are blocked if the memory self-refresh mode is active at that -+ * moment. So to make sure the plane gets truly disabled, disable -+ * first the self-refresh mode. The self-refresh enable bit in turn -+ * will be checked/applied by the HW only at the next frame start -+ * event which is after the vblank start event, so we need to have a -+ * wait-for-vblank between disabling the plane and the pipe. -+ */ -+ if (HAS_GMCH(dev_priv) && old_crtc_state->base.active && -+ pipe_config->disable_cxsr && intel_set_memory_cxsr(dev_priv, false)) -+ intel_wait_for_vblank(dev_priv, crtc->pipe); -+ -+ /* -+ * IVB workaround: must disable low power watermarks for at least -+ * one frame before enabling scaling. LP watermarks can be re-enabled -+ * when scaling is disabled. -+ * -+ * WaCxSRDisabledForSpriteScaling:ivb -+ */ -+ if (pipe_config->disable_lp_wm && ilk_disable_lp_wm(dev) && -+ old_crtc_state->base.active) -+ intel_wait_for_vblank(dev_priv, crtc->pipe); -+ -+ /* -+ * If we're doing a modeset, we're done. No need to do any pre-vblank -+ * watermark programming here. -+ */ -+ if (needs_modeset(&pipe_config->base)) -+ return; -+ -+ /* -+ * For platforms that support atomic watermarks, program the -+ * 'intermediate' watermarks immediately. On pre-gen9 platforms, these -+ * will be the intermediate values that are safe for both pre- and -+ * post- vblank; when vblank happens, the 'active' values will be set -+ * to the final 'target' values and we'll do this again to get the -+ * optimal watermarks. For gen9+ platforms, the values we program here -+ * will be the final target values which will get automatically latched -+ * at vblank time; no further programming will be necessary. -+ * -+ * If a platform hasn't been transitioned to atomic watermarks yet, -+ * we'll continue to update watermarks the old way, if flags tell -+ * us to. -+ */ -+ if (dev_priv->display.initial_watermarks != NULL) -+ dev_priv->display.initial_watermarks(old_intel_state, -+ pipe_config); -+ else if (pipe_config->update_wm_pre) -+ intel_update_watermarks(crtc); -+} -+ -+static void intel_crtc_disable_planes(struct intel_atomic_state *state, -+ struct intel_crtc *crtc) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ const struct intel_crtc_state *new_crtc_state = -+ intel_atomic_get_new_crtc_state(state, crtc); -+ unsigned int update_mask = new_crtc_state->update_planes; -+ const struct intel_plane_state *old_plane_state; -+ struct intel_plane *plane; -+ unsigned fb_bits = 0; -+ int i; -+ -+ intel_crtc_dpms_overlay_disable(crtc); -+ -+ for_each_old_intel_plane_in_state(state, plane, old_plane_state, i) { -+ if (crtc->pipe != plane->pipe || -+ !(update_mask & BIT(plane->id))) -+ continue; -+ -+ intel_disable_plane(plane, new_crtc_state); -+ -+ if (old_plane_state->base.visible) -+ fb_bits |= plane->frontbuffer_bit; -+ } -+ -+ intel_frontbuffer_flip(dev_priv, fb_bits); -+} -+ -+static void intel_encoders_pre_pll_enable(struct drm_crtc *crtc, -+ struct intel_crtc_state *crtc_state, -+ struct drm_atomic_state *old_state) -+{ -+ struct drm_connector_state *conn_state; -+ struct drm_connector *conn; -+ int i; -+ -+ for_each_new_connector_in_state(old_state, conn, conn_state, i) { -+ struct intel_encoder *encoder = -+ to_intel_encoder(conn_state->best_encoder); -+ -+ if (conn_state->crtc != crtc) -+ continue; -+ -+ if (encoder->pre_pll_enable) -+ encoder->pre_pll_enable(encoder, crtc_state, conn_state); -+ } -+} -+ -+static void intel_encoders_pre_enable(struct drm_crtc *crtc, -+ struct intel_crtc_state *crtc_state, -+ struct drm_atomic_state *old_state) -+{ -+ struct drm_connector_state *conn_state; -+ struct drm_connector *conn; -+ int i; -+ -+ for_each_new_connector_in_state(old_state, conn, conn_state, i) { -+ struct intel_encoder *encoder = -+ to_intel_encoder(conn_state->best_encoder); -+ -+ if (conn_state->crtc != crtc) -+ continue; -+ -+ if (encoder->pre_enable) -+ encoder->pre_enable(encoder, crtc_state, conn_state); -+ } -+} -+ -+static void intel_encoders_enable(struct drm_crtc *crtc, -+ struct intel_crtc_state *crtc_state, -+ struct drm_atomic_state *old_state) -+{ -+ struct drm_connector_state *conn_state; -+ struct drm_connector *conn; -+ int i; -+ -+ for_each_new_connector_in_state(old_state, conn, conn_state, i) { -+ struct intel_encoder *encoder = -+ to_intel_encoder(conn_state->best_encoder); -+ -+ if (conn_state->crtc != crtc) -+ continue; -+ -+ if (encoder->enable) -+ encoder->enable(encoder, crtc_state, conn_state); -+ intel_opregion_notify_encoder(encoder, true); -+ } -+} -+ -+static void intel_encoders_disable(struct drm_crtc *crtc, -+ struct intel_crtc_state *old_crtc_state, -+ struct drm_atomic_state *old_state) -+{ -+ struct drm_connector_state *old_conn_state; -+ struct drm_connector *conn; -+ int i; -+ -+ for_each_old_connector_in_state(old_state, conn, old_conn_state, i) { -+ struct intel_encoder *encoder = -+ to_intel_encoder(old_conn_state->best_encoder); -+ -+ if (old_conn_state->crtc != crtc) -+ continue; -+ -+ intel_opregion_notify_encoder(encoder, false); -+ if (encoder->disable) -+ encoder->disable(encoder, old_crtc_state, old_conn_state); -+ } -+} -+ -+static void intel_encoders_post_disable(struct drm_crtc *crtc, -+ struct intel_crtc_state *old_crtc_state, -+ struct drm_atomic_state *old_state) -+{ -+ struct drm_connector_state *old_conn_state; -+ struct drm_connector *conn; -+ int i; -+ -+ for_each_old_connector_in_state(old_state, conn, old_conn_state, i) { -+ struct intel_encoder *encoder = -+ to_intel_encoder(old_conn_state->best_encoder); -+ -+ if (old_conn_state->crtc != crtc) -+ continue; -+ -+ if (encoder->post_disable) -+ encoder->post_disable(encoder, old_crtc_state, old_conn_state); -+ } -+} -+ -+static void intel_encoders_post_pll_disable(struct drm_crtc *crtc, -+ struct intel_crtc_state *old_crtc_state, -+ struct drm_atomic_state *old_state) -+{ -+ struct drm_connector_state *old_conn_state; -+ struct drm_connector *conn; -+ int i; -+ -+ for_each_old_connector_in_state(old_state, conn, old_conn_state, i) { -+ struct intel_encoder *encoder = -+ to_intel_encoder(old_conn_state->best_encoder); -+ -+ if (old_conn_state->crtc != crtc) -+ continue; -+ -+ if (encoder->post_pll_disable) -+ encoder->post_pll_disable(encoder, old_crtc_state, old_conn_state); -+ } -+} -+ -+static void intel_encoders_update_pipe(struct drm_crtc *crtc, -+ struct intel_crtc_state *crtc_state, -+ struct drm_atomic_state *old_state) -+{ -+ struct drm_connector_state *conn_state; -+ struct drm_connector *conn; -+ int i; -+ -+ for_each_new_connector_in_state(old_state, conn, conn_state, i) { -+ struct intel_encoder *encoder = -+ to_intel_encoder(conn_state->best_encoder); -+ -+ if (conn_state->crtc != crtc) -+ continue; -+ -+ if (encoder->update_pipe) -+ encoder->update_pipe(encoder, crtc_state, conn_state); -+ } -+} -+ -+static void intel_disable_primary_plane(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct intel_plane *plane = to_intel_plane(crtc->base.primary); -+ -+ plane->disable_plane(plane, crtc_state); -+} -+ -+static void ironlake_crtc_enable(struct intel_crtc_state *pipe_config, -+ struct drm_atomic_state *old_state) -+{ -+ struct drm_crtc *crtc = pipe_config->base.crtc; -+ struct drm_device *dev = crtc->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ int pipe = intel_crtc->pipe; -+ struct intel_atomic_state *old_intel_state = -+ to_intel_atomic_state(old_state); -+ -+ if (WARN_ON(intel_crtc->active)) -+ return; -+ -+ /* -+ * Sometimes spurious CPU pipe underruns happen during FDI -+ * training, at least with VGA+HDMI cloning. Suppress them. -+ * -+ * On ILK we get an occasional spurious CPU pipe underruns -+ * between eDP port A enable and vdd enable. Also PCH port -+ * enable seems to result in the occasional CPU pipe underrun. -+ * -+ * Spurious PCH underruns also occur during PCH enabling. -+ */ -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, false); -+ intel_set_pch_fifo_underrun_reporting(dev_priv, pipe, false); -+ -+ if (pipe_config->has_pch_encoder) -+ intel_prepare_shared_dpll(pipe_config); -+ -+ if (intel_crtc_has_dp_encoder(pipe_config)) -+ intel_dp_set_m_n(pipe_config, M1_N1); -+ -+ intel_set_pipe_timings(pipe_config); -+ intel_set_pipe_src_size(pipe_config); -+ -+ if (pipe_config->has_pch_encoder) { -+ intel_cpu_transcoder_set_m_n(pipe_config, -+ &pipe_config->fdi_m_n, NULL); -+ } -+ -+ ironlake_set_pipeconf(pipe_config); -+ -+ intel_crtc->active = true; -+ -+ intel_encoders_pre_enable(crtc, pipe_config, old_state); -+ -+ if (pipe_config->has_pch_encoder) { -+ /* Note: FDI PLL enabling _must_ be done before we enable the -+ * cpu pipes, hence this is separate from all the other fdi/pch -+ * enabling. */ -+ ironlake_fdi_pll_enable(pipe_config); -+ } else { -+ assert_fdi_tx_disabled(dev_priv, pipe); -+ assert_fdi_rx_disabled(dev_priv, pipe); -+ } -+ -+ ironlake_pfit_enable(pipe_config); -+ -+ /* -+ * On ILK+ LUT must be loaded before the pipe is running but with -+ * clocks enabled -+ */ -+ intel_color_load_luts(pipe_config); -+ intel_color_commit(pipe_config); -+ /* update DSPCNTR to configure gamma for pipe bottom color */ -+ intel_disable_primary_plane(pipe_config); -+ -+ if (dev_priv->display.initial_watermarks != NULL) -+ dev_priv->display.initial_watermarks(old_intel_state, pipe_config); -+ intel_enable_pipe(pipe_config); -+ -+ if (pipe_config->has_pch_encoder) -+ ironlake_pch_enable(old_intel_state, pipe_config); -+ -+ assert_vblank_disabled(crtc); -+ intel_crtc_vblank_on(pipe_config); -+ -+ intel_encoders_enable(crtc, pipe_config, old_state); -+ -+ if (HAS_PCH_CPT(dev_priv)) -+ cpt_verify_modeset(dev, intel_crtc->pipe); -+ -+ /* -+ * Must wait for vblank to avoid spurious PCH FIFO underruns. -+ * And a second vblank wait is needed at least on ILK with -+ * some interlaced HDMI modes. Let's do the double wait always -+ * in case there are more corner cases we don't know about. -+ */ -+ if (pipe_config->has_pch_encoder) { -+ intel_wait_for_vblank(dev_priv, pipe); -+ intel_wait_for_vblank(dev_priv, pipe); -+ } -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, true); -+ intel_set_pch_fifo_underrun_reporting(dev_priv, pipe, true); -+} -+ -+/* IPS only exists on ULT machines and is tied to pipe A. */ -+static bool hsw_crtc_supports_ips(struct intel_crtc *crtc) -+{ -+ return HAS_IPS(to_i915(crtc->base.dev)) && crtc->pipe == PIPE_A; -+} -+ -+static void glk_pipe_scaler_clock_gating_wa(struct drm_i915_private *dev_priv, -+ enum pipe pipe, bool apply) -+{ -+ u32 val = I915_READ(CLKGATE_DIS_PSL(pipe)); -+ u32 mask = DPF_GATING_DIS | DPF_RAM_GATING_DIS | DPFR_GATING_DIS; -+ -+ if (apply) -+ val |= mask; -+ else -+ val &= ~mask; -+ -+ I915_WRITE(CLKGATE_DIS_PSL(pipe), val); -+} -+ -+static void icl_pipe_mbus_enable(struct intel_crtc *crtc) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ u32 val; -+ -+ val = MBUS_DBOX_A_CREDIT(2); -+ val |= MBUS_DBOX_BW_CREDIT(1); -+ val |= MBUS_DBOX_B_CREDIT(8); -+ -+ I915_WRITE(PIPE_MBUS_DBOX_CTL(pipe), val); -+} -+ -+static void haswell_crtc_enable(struct intel_crtc_state *pipe_config, -+ struct drm_atomic_state *old_state) -+{ -+ struct drm_crtc *crtc = pipe_config->base.crtc; -+ struct drm_i915_private *dev_priv = to_i915(crtc->dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ int pipe = intel_crtc->pipe, hsw_workaround_pipe; -+ enum transcoder cpu_transcoder = pipe_config->cpu_transcoder; -+ struct intel_atomic_state *old_intel_state = -+ to_intel_atomic_state(old_state); -+ bool psl_clkgate_wa; -+ -+ if (WARN_ON(intel_crtc->active)) -+ return; -+ -+ intel_encoders_pre_pll_enable(crtc, pipe_config, old_state); -+ -+ if (pipe_config->shared_dpll) -+ intel_enable_shared_dpll(pipe_config); -+ -+ intel_encoders_pre_enable(crtc, pipe_config, old_state); -+ -+ if (intel_crtc_has_dp_encoder(pipe_config)) -+ intel_dp_set_m_n(pipe_config, M1_N1); -+ -+ if (!transcoder_is_dsi(cpu_transcoder)) -+ intel_set_pipe_timings(pipe_config); -+ -+ intel_set_pipe_src_size(pipe_config); -+ -+ if (cpu_transcoder != TRANSCODER_EDP && -+ !transcoder_is_dsi(cpu_transcoder)) { -+ I915_WRITE(PIPE_MULT(cpu_transcoder), -+ pipe_config->pixel_multiplier - 1); -+ } -+ -+ if (pipe_config->has_pch_encoder) { -+ intel_cpu_transcoder_set_m_n(pipe_config, -+ &pipe_config->fdi_m_n, NULL); -+ } -+ -+ if (!transcoder_is_dsi(cpu_transcoder)) -+ haswell_set_pipeconf(pipe_config); -+ -+ haswell_set_pipemisc(pipe_config); -+ -+ intel_crtc->active = true; -+ -+ /* Display WA #1180: WaDisableScalarClockGating: glk, cnl */ -+ psl_clkgate_wa = (IS_GEMINILAKE(dev_priv) || IS_CANNONLAKE(dev_priv)) && -+ pipe_config->pch_pfit.enabled; -+ if (psl_clkgate_wa) -+ glk_pipe_scaler_clock_gating_wa(dev_priv, pipe, true); -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ skylake_pfit_enable(pipe_config); -+ else -+ ironlake_pfit_enable(pipe_config); -+ -+ /* -+ * On ILK+ LUT must be loaded before the pipe is running but with -+ * clocks enabled -+ */ -+ intel_color_load_luts(pipe_config); -+ intel_color_commit(pipe_config); -+ /* update DSPCNTR to configure gamma/csc for pipe bottom color */ -+ if (INTEL_GEN(dev_priv) < 9) -+ intel_disable_primary_plane(pipe_config); -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ icl_set_pipe_chicken(intel_crtc); -+ -+ intel_ddi_set_pipe_settings(pipe_config); -+ if (!transcoder_is_dsi(cpu_transcoder)) -+ intel_ddi_enable_transcoder_func(pipe_config); -+ -+ if (dev_priv->display.initial_watermarks != NULL) -+ dev_priv->display.initial_watermarks(old_intel_state, pipe_config); -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ icl_pipe_mbus_enable(intel_crtc); -+ -+ /* XXX: Do the pipe assertions at the right place for BXT DSI. */ -+ if (!transcoder_is_dsi(cpu_transcoder)) -+ intel_enable_pipe(pipe_config); -+ -+ if (pipe_config->has_pch_encoder) -+ lpt_pch_enable(old_intel_state, pipe_config); -+ -+ if (intel_crtc_has_type(pipe_config, INTEL_OUTPUT_DP_MST)) -+ intel_ddi_set_vc_payload_alloc(pipe_config, true); -+ -+ assert_vblank_disabled(crtc); -+ intel_crtc_vblank_on(pipe_config); -+ -+ intel_encoders_enable(crtc, pipe_config, old_state); -+ -+ if (psl_clkgate_wa) { -+ intel_wait_for_vblank(dev_priv, pipe); -+ glk_pipe_scaler_clock_gating_wa(dev_priv, pipe, false); -+ } -+ -+ /* If we change the relative order between pipe/planes enabling, we need -+ * to change the workaround. */ -+ hsw_workaround_pipe = pipe_config->hsw_workaround_pipe; -+ if (IS_HASWELL(dev_priv) && hsw_workaround_pipe != INVALID_PIPE) { -+ intel_wait_for_vblank(dev_priv, hsw_workaround_pipe); -+ intel_wait_for_vblank(dev_priv, hsw_workaround_pipe); -+ } -+} -+ -+static void ironlake_pfit_disable(const struct intel_crtc_state *old_crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ -+ /* To avoid upsetting the power well on haswell only disable the pfit if -+ * it's in use. The hw state code will make sure we get this right. */ -+ if (old_crtc_state->pch_pfit.enabled) { -+ I915_WRITE(PF_CTL(pipe), 0); -+ I915_WRITE(PF_WIN_POS(pipe), 0); -+ I915_WRITE(PF_WIN_SZ(pipe), 0); -+ } -+} -+ -+static void ironlake_crtc_disable(struct intel_crtc_state *old_crtc_state, -+ struct drm_atomic_state *old_state) -+{ -+ struct drm_crtc *crtc = old_crtc_state->base.crtc; -+ struct drm_device *dev = crtc->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ int pipe = intel_crtc->pipe; -+ -+ /* -+ * Sometimes spurious CPU pipe underruns happen when the -+ * pipe is already disabled, but FDI RX/TX is still enabled. -+ * Happens at least with VGA+HDMI cloning. Suppress them. -+ */ -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, false); -+ intel_set_pch_fifo_underrun_reporting(dev_priv, pipe, false); -+ -+ intel_encoders_disable(crtc, old_crtc_state, old_state); -+ -+ drm_crtc_vblank_off(crtc); -+ assert_vblank_disabled(crtc); -+ -+ intel_disable_pipe(old_crtc_state); -+ -+ ironlake_pfit_disable(old_crtc_state); -+ -+ if (old_crtc_state->has_pch_encoder) -+ ironlake_fdi_disable(crtc); -+ -+ intel_encoders_post_disable(crtc, old_crtc_state, old_state); -+ -+ if (old_crtc_state->has_pch_encoder) { -+ ironlake_disable_pch_transcoder(dev_priv, pipe); -+ -+ if (HAS_PCH_CPT(dev_priv)) { -+ i915_reg_t reg; -+ u32 temp; -+ -+ /* disable TRANS_DP_CTL */ -+ reg = TRANS_DP_CTL(pipe); -+ temp = I915_READ(reg); -+ temp &= ~(TRANS_DP_OUTPUT_ENABLE | -+ TRANS_DP_PORT_SEL_MASK); -+ temp |= TRANS_DP_PORT_SEL_NONE; -+ I915_WRITE(reg, temp); -+ -+ /* disable DPLL_SEL */ -+ temp = I915_READ(PCH_DPLL_SEL); -+ temp &= ~(TRANS_DPLL_ENABLE(pipe) | TRANS_DPLLB_SEL(pipe)); -+ I915_WRITE(PCH_DPLL_SEL, temp); -+ } -+ -+ ironlake_fdi_pll_disable(intel_crtc); -+ } -+ -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, true); -+ intel_set_pch_fifo_underrun_reporting(dev_priv, pipe, true); -+} -+ -+static void haswell_crtc_disable(struct intel_crtc_state *old_crtc_state, -+ struct drm_atomic_state *old_state) -+{ -+ struct drm_crtc *crtc = old_crtc_state->base.crtc; -+ struct drm_i915_private *dev_priv = to_i915(crtc->dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ enum transcoder cpu_transcoder = old_crtc_state->cpu_transcoder; -+ -+ intel_encoders_disable(crtc, old_crtc_state, old_state); -+ -+ drm_crtc_vblank_off(crtc); -+ assert_vblank_disabled(crtc); -+ -+ /* XXX: Do the pipe assertions at the right place for BXT DSI. */ -+ if (!transcoder_is_dsi(cpu_transcoder)) -+ intel_disable_pipe(old_crtc_state); -+ -+ if (intel_crtc_has_type(old_crtc_state, INTEL_OUTPUT_DP_MST)) -+ intel_ddi_set_vc_payload_alloc(old_crtc_state, false); -+ -+ if (!transcoder_is_dsi(cpu_transcoder)) -+ intel_ddi_disable_transcoder_func(old_crtc_state); -+ -+ intel_dsc_disable(old_crtc_state); -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ skylake_scaler_disable(intel_crtc); -+ else -+ ironlake_pfit_disable(old_crtc_state); -+ -+ intel_encoders_post_disable(crtc, old_crtc_state, old_state); -+ -+ intel_encoders_post_pll_disable(crtc, old_crtc_state, old_state); -+} -+ -+static void i9xx_pfit_enable(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ if (!crtc_state->gmch_pfit.control) -+ return; -+ -+ /* -+ * The panel fitter should only be adjusted whilst the pipe is disabled, -+ * according to register description and PRM. -+ */ -+ WARN_ON(I915_READ(PFIT_CONTROL) & PFIT_ENABLE); -+ assert_pipe_disabled(dev_priv, crtc->pipe); -+ -+ I915_WRITE(PFIT_PGM_RATIOS, crtc_state->gmch_pfit.pgm_ratios); -+ I915_WRITE(PFIT_CONTROL, crtc_state->gmch_pfit.control); -+ -+ /* Border color in case we don't scale up to the full screen. Black by -+ * default, change to something else for debugging. */ -+ I915_WRITE(BCLRPAT(crtc->pipe), 0); -+} -+ -+bool intel_port_is_combophy(struct drm_i915_private *dev_priv, enum port port) -+{ -+ if (port == PORT_NONE) -+ return false; -+ -+ if (IS_ELKHARTLAKE(dev_priv)) -+ return port <= PORT_C; -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ return port <= PORT_B; -+ -+ return false; -+} -+ -+bool intel_port_is_tc(struct drm_i915_private *dev_priv, enum port port) -+{ -+ if (INTEL_GEN(dev_priv) >= 11 && !IS_ELKHARTLAKE(dev_priv)) -+ return port >= PORT_C && port <= PORT_F; -+ -+ return false; -+} -+ -+enum tc_port intel_port_to_tc(struct drm_i915_private *dev_priv, enum port port) -+{ -+ if (!intel_port_is_tc(dev_priv, port)) -+ return PORT_TC_NONE; -+ -+ return port - PORT_C; -+} -+ -+enum intel_display_power_domain intel_port_to_power_domain(enum port port) -+{ -+ switch (port) { -+ case PORT_A: -+ return POWER_DOMAIN_PORT_DDI_A_LANES; -+ case PORT_B: -+ return POWER_DOMAIN_PORT_DDI_B_LANES; -+ case PORT_C: -+ return POWER_DOMAIN_PORT_DDI_C_LANES; -+ case PORT_D: -+ return POWER_DOMAIN_PORT_DDI_D_LANES; -+ case PORT_E: -+ return POWER_DOMAIN_PORT_DDI_E_LANES; -+ case PORT_F: -+ return POWER_DOMAIN_PORT_DDI_F_LANES; -+ default: -+ MISSING_CASE(port); -+ return POWER_DOMAIN_PORT_OTHER; -+ } -+} -+ -+enum intel_display_power_domain -+intel_aux_power_domain(struct intel_digital_port *dig_port) -+{ -+ switch (dig_port->aux_ch) { -+ case AUX_CH_A: -+ return POWER_DOMAIN_AUX_A; -+ case AUX_CH_B: -+ return POWER_DOMAIN_AUX_B; -+ case AUX_CH_C: -+ return POWER_DOMAIN_AUX_C; -+ case AUX_CH_D: -+ return POWER_DOMAIN_AUX_D; -+ case AUX_CH_E: -+ return POWER_DOMAIN_AUX_E; -+ case AUX_CH_F: -+ return POWER_DOMAIN_AUX_F; -+ default: -+ MISSING_CASE(dig_port->aux_ch); -+ return POWER_DOMAIN_AUX_A; -+ } -+} -+ -+static u64 get_crtc_power_domains(struct drm_crtc *crtc, -+ struct intel_crtc_state *crtc_state) -+{ -+ struct drm_device *dev = crtc->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_encoder *encoder; -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ enum pipe pipe = intel_crtc->pipe; -+ u64 mask; -+ enum transcoder transcoder = crtc_state->cpu_transcoder; -+ -+ if (!crtc_state->base.active) -+ return 0; -+ -+ mask = BIT_ULL(POWER_DOMAIN_PIPE(pipe)); -+ mask |= BIT_ULL(POWER_DOMAIN_TRANSCODER(transcoder)); -+ if (crtc_state->pch_pfit.enabled || -+ crtc_state->pch_pfit.force_thru) -+ mask |= BIT_ULL(POWER_DOMAIN_PIPE_PANEL_FITTER(pipe)); -+ -+ drm_for_each_encoder_mask(encoder, dev, crtc_state->base.encoder_mask) { -+ struct intel_encoder *intel_encoder = to_intel_encoder(encoder); -+ -+ mask |= BIT_ULL(intel_encoder->power_domain); -+ } -+ -+ if (HAS_DDI(dev_priv) && crtc_state->has_audio) -+ mask |= BIT_ULL(POWER_DOMAIN_AUDIO); -+ -+ if (crtc_state->shared_dpll) -+ mask |= BIT_ULL(POWER_DOMAIN_PLLS); -+ -+ return mask; -+} -+ -+static u64 -+modeset_get_crtc_power_domains(struct drm_crtc *crtc, -+ struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ enum intel_display_power_domain domain; -+ u64 domains, new_domains, old_domains; -+ -+ old_domains = intel_crtc->enabled_power_domains; -+ intel_crtc->enabled_power_domains = new_domains = -+ get_crtc_power_domains(crtc, crtc_state); -+ -+ domains = new_domains & ~old_domains; -+ -+ for_each_power_domain(domain, domains) -+ intel_display_power_get(dev_priv, domain); -+ -+ return old_domains & ~new_domains; -+} -+ -+static void modeset_put_power_domains(struct drm_i915_private *dev_priv, -+ u64 domains) -+{ -+ enum intel_display_power_domain domain; -+ -+ for_each_power_domain(domain, domains) -+ intel_display_power_put_unchecked(dev_priv, domain); -+} -+ -+static void valleyview_crtc_enable(struct intel_crtc_state *pipe_config, -+ struct drm_atomic_state *old_state) -+{ -+ struct intel_atomic_state *old_intel_state = -+ to_intel_atomic_state(old_state); -+ struct drm_crtc *crtc = pipe_config->base.crtc; -+ struct drm_device *dev = crtc->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ int pipe = intel_crtc->pipe; -+ -+ if (WARN_ON(intel_crtc->active)) -+ return; -+ -+ if (intel_crtc_has_dp_encoder(pipe_config)) -+ intel_dp_set_m_n(pipe_config, M1_N1); -+ -+ intel_set_pipe_timings(pipe_config); -+ intel_set_pipe_src_size(pipe_config); -+ -+ if (IS_CHERRYVIEW(dev_priv) && pipe == PIPE_B) { -+ I915_WRITE(CHV_BLEND(pipe), CHV_BLEND_LEGACY); -+ I915_WRITE(CHV_CANVAS(pipe), 0); -+ } -+ -+ i9xx_set_pipeconf(pipe_config); -+ -+ intel_crtc->active = true; -+ -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, true); -+ -+ intel_encoders_pre_pll_enable(crtc, pipe_config, old_state); -+ -+ if (IS_CHERRYVIEW(dev_priv)) { -+ chv_prepare_pll(intel_crtc, pipe_config); -+ chv_enable_pll(intel_crtc, pipe_config); -+ } else { -+ vlv_prepare_pll(intel_crtc, pipe_config); -+ vlv_enable_pll(intel_crtc, pipe_config); -+ } -+ -+ intel_encoders_pre_enable(crtc, pipe_config, old_state); -+ -+ i9xx_pfit_enable(pipe_config); -+ -+ intel_color_load_luts(pipe_config); -+ intel_color_commit(pipe_config); -+ /* update DSPCNTR to configure gamma for pipe bottom color */ -+ intel_disable_primary_plane(pipe_config); -+ -+ dev_priv->display.initial_watermarks(old_intel_state, -+ pipe_config); -+ intel_enable_pipe(pipe_config); -+ -+ assert_vblank_disabled(crtc); -+ intel_crtc_vblank_on(pipe_config); -+ -+ intel_encoders_enable(crtc, pipe_config, old_state); -+} -+ -+static void i9xx_set_pll_dividers(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ I915_WRITE(FP0(crtc->pipe), crtc_state->dpll_hw_state.fp0); -+ I915_WRITE(FP1(crtc->pipe), crtc_state->dpll_hw_state.fp1); -+} -+ -+static void i9xx_crtc_enable(struct intel_crtc_state *pipe_config, -+ struct drm_atomic_state *old_state) -+{ -+ struct intel_atomic_state *old_intel_state = -+ to_intel_atomic_state(old_state); -+ struct drm_crtc *crtc = pipe_config->base.crtc; -+ struct drm_device *dev = crtc->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ enum pipe pipe = intel_crtc->pipe; -+ -+ if (WARN_ON(intel_crtc->active)) -+ return; -+ -+ i9xx_set_pll_dividers(pipe_config); -+ -+ if (intel_crtc_has_dp_encoder(pipe_config)) -+ intel_dp_set_m_n(pipe_config, M1_N1); -+ -+ intel_set_pipe_timings(pipe_config); -+ intel_set_pipe_src_size(pipe_config); -+ -+ i9xx_set_pipeconf(pipe_config); -+ -+ intel_crtc->active = true; -+ -+ if (!IS_GEN(dev_priv, 2)) -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, true); -+ -+ intel_encoders_pre_enable(crtc, pipe_config, old_state); -+ -+ i9xx_enable_pll(intel_crtc, pipe_config); -+ -+ i9xx_pfit_enable(pipe_config); -+ -+ intel_color_load_luts(pipe_config); -+ intel_color_commit(pipe_config); -+ /* update DSPCNTR to configure gamma for pipe bottom color */ -+ intel_disable_primary_plane(pipe_config); -+ -+ if (dev_priv->display.initial_watermarks != NULL) -+ dev_priv->display.initial_watermarks(old_intel_state, -+ pipe_config); -+ else -+ intel_update_watermarks(intel_crtc); -+ intel_enable_pipe(pipe_config); -+ -+ assert_vblank_disabled(crtc); -+ intel_crtc_vblank_on(pipe_config); -+ -+ intel_encoders_enable(crtc, pipe_config, old_state); -+} -+ -+static void i9xx_pfit_disable(const struct intel_crtc_state *old_crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ if (!old_crtc_state->gmch_pfit.control) -+ return; -+ -+ assert_pipe_disabled(dev_priv, crtc->pipe); -+ -+ DRM_DEBUG_KMS("disabling pfit, current: 0x%08x\n", -+ I915_READ(PFIT_CONTROL)); -+ I915_WRITE(PFIT_CONTROL, 0); -+} -+ -+static void i9xx_crtc_disable(struct intel_crtc_state *old_crtc_state, -+ struct drm_atomic_state *old_state) -+{ -+ struct drm_crtc *crtc = old_crtc_state->base.crtc; -+ struct drm_device *dev = crtc->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ int pipe = intel_crtc->pipe; -+ -+ /* -+ * On gen2 planes are double buffered but the pipe isn't, so we must -+ * wait for planes to fully turn off before disabling the pipe. -+ */ -+ if (IS_GEN(dev_priv, 2)) -+ intel_wait_for_vblank(dev_priv, pipe); -+ -+ intel_encoders_disable(crtc, old_crtc_state, old_state); -+ -+ drm_crtc_vblank_off(crtc); -+ assert_vblank_disabled(crtc); -+ -+ intel_disable_pipe(old_crtc_state); -+ -+ i9xx_pfit_disable(old_crtc_state); -+ -+ intel_encoders_post_disable(crtc, old_crtc_state, old_state); -+ -+ if (!intel_crtc_has_type(old_crtc_state, INTEL_OUTPUT_DSI)) { -+ if (IS_CHERRYVIEW(dev_priv)) -+ chv_disable_pll(dev_priv, pipe); -+ else if (IS_VALLEYVIEW(dev_priv)) -+ vlv_disable_pll(dev_priv, pipe); -+ else -+ i9xx_disable_pll(old_crtc_state); -+ } -+ -+ intel_encoders_post_pll_disable(crtc, old_crtc_state, old_state); -+ -+ if (!IS_GEN(dev_priv, 2)) -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, pipe, false); -+ -+ if (!dev_priv->display.initial_watermarks) -+ intel_update_watermarks(intel_crtc); -+ -+ /* clock the pipe down to 640x480@60 to potentially save power */ -+ if (IS_I830(dev_priv)) -+ i830_enable_pipe(dev_priv, pipe); -+} -+ -+static void intel_crtc_disable_noatomic(struct drm_crtc *crtc, -+ struct drm_modeset_acquire_ctx *ctx) -+{ -+ struct intel_encoder *encoder; -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->dev); -+ enum intel_display_power_domain domain; -+ struct intel_plane *plane; -+ u64 domains; -+ struct drm_atomic_state *state; -+ struct intel_crtc_state *crtc_state; -+ int ret; -+ -+ if (!intel_crtc->active) -+ return; -+ -+ for_each_intel_plane_on_crtc(&dev_priv->drm, intel_crtc, plane) { -+ const struct intel_plane_state *plane_state = -+ to_intel_plane_state(plane->base.state); -+ -+ if (plane_state->base.visible) -+ intel_plane_disable_noatomic(intel_crtc, plane); -+ } -+ -+ state = drm_atomic_state_alloc(crtc->dev); -+ if (!state) { -+ DRM_DEBUG_KMS("failed to disable [CRTC:%d:%s], out of memory", -+ crtc->base.id, crtc->name); -+ return; -+ } -+ -+ state->acquire_ctx = ctx; -+ -+ /* Everything's already locked, -EDEADLK can't happen. */ -+ crtc_state = intel_atomic_get_crtc_state(state, intel_crtc); -+ ret = drm_atomic_add_affected_connectors(state, crtc); -+ -+ WARN_ON(IS_ERR(crtc_state) || ret); -+ -+ dev_priv->display.crtc_disable(crtc_state, state); -+ -+ drm_atomic_state_put(state); -+ -+ DRM_DEBUG_KMS("[CRTC:%d:%s] hw state adjusted, was enabled, now disabled\n", -+ crtc->base.id, crtc->name); -+ -+ WARN_ON(drm_atomic_set_mode_for_crtc(crtc->state, NULL) < 0); -+ crtc->state->active = false; -+ intel_crtc->active = false; -+ crtc->enabled = false; -+ crtc->state->connector_mask = 0; -+ crtc->state->encoder_mask = 0; -+ -+ for_each_encoder_on_crtc(crtc->dev, crtc, encoder) -+ encoder->base.crtc = NULL; -+ -+ intel_fbc_disable(intel_crtc); -+ intel_update_watermarks(intel_crtc); -+ intel_disable_shared_dpll(to_intel_crtc_state(crtc->state)); -+ -+ domains = intel_crtc->enabled_power_domains; -+ for_each_power_domain(domain, domains) -+ intel_display_power_put_unchecked(dev_priv, domain); -+ intel_crtc->enabled_power_domains = 0; -+ -+ dev_priv->active_crtcs &= ~(1 << intel_crtc->pipe); -+ dev_priv->min_cdclk[intel_crtc->pipe] = 0; -+ dev_priv->min_voltage_level[intel_crtc->pipe] = 0; -+} -+ -+/* -+ * turn all crtc's off, but do not adjust state -+ * This has to be paired with a call to intel_modeset_setup_hw_state. -+ */ -+int intel_display_suspend(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_atomic_state *state; -+ int ret; -+ -+ state = drm_atomic_helper_suspend(dev); -+ ret = PTR_ERR_OR_ZERO(state); -+ if (ret) -+ DRM_ERROR("Suspending crtc's failed with %i\n", ret); -+ else -+ dev_priv->modeset_restore_state = state; -+ return ret; -+} -+ -+void intel_encoder_destroy(struct drm_encoder *encoder) -+{ -+ struct intel_encoder *intel_encoder = to_intel_encoder(encoder); -+ -+ drm_encoder_cleanup(encoder); -+ kfree(intel_encoder); -+} -+ -+/* Cross check the actual hw state with our own modeset state tracking (and it's -+ * internal consistency). */ -+static void intel_connector_verify_state(struct drm_crtc_state *crtc_state, -+ struct drm_connector_state *conn_state) -+{ -+ struct intel_connector *connector = to_intel_connector(conn_state->connector); -+ -+ DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", -+ connector->base.base.id, -+ connector->base.name); -+ -+ if (connector->get_hw_state(connector)) { -+ struct intel_encoder *encoder = connector->encoder; -+ -+ I915_STATE_WARN(!crtc_state, -+ "connector enabled without attached crtc\n"); -+ -+ if (!crtc_state) -+ return; -+ -+ I915_STATE_WARN(!crtc_state->active, -+ "connector is active, but attached crtc isn't\n"); -+ -+ if (!encoder || encoder->type == INTEL_OUTPUT_DP_MST) -+ return; -+ -+ I915_STATE_WARN(conn_state->best_encoder != &encoder->base, -+ "atomic encoder doesn't match attached encoder\n"); -+ -+ I915_STATE_WARN(conn_state->crtc != encoder->base.crtc, -+ "attached encoder crtc differs from connector crtc\n"); -+ } else { -+ I915_STATE_WARN(crtc_state && crtc_state->active, -+ "attached crtc is active, but connector isn't\n"); -+ I915_STATE_WARN(!crtc_state && conn_state->best_encoder, -+ "best encoder set without crtc!\n"); -+ } -+} -+ -+static int pipe_required_fdi_lanes(struct intel_crtc_state *crtc_state) -+{ -+ if (crtc_state->base.enable && crtc_state->has_pch_encoder) -+ return crtc_state->fdi_lanes; -+ -+ return 0; -+} -+ -+static int ironlake_check_fdi_lanes(struct drm_device *dev, enum pipe pipe, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_atomic_state *state = pipe_config->base.state; -+ struct intel_crtc *other_crtc; -+ struct intel_crtc_state *other_crtc_state; -+ -+ DRM_DEBUG_KMS("checking fdi config on pipe %c, lanes %i\n", -+ pipe_name(pipe), pipe_config->fdi_lanes); -+ if (pipe_config->fdi_lanes > 4) { -+ DRM_DEBUG_KMS("invalid fdi lane config on pipe %c: %i lanes\n", -+ pipe_name(pipe), pipe_config->fdi_lanes); -+ return -EINVAL; -+ } -+ -+ if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) { -+ if (pipe_config->fdi_lanes > 2) { -+ DRM_DEBUG_KMS("only 2 lanes on haswell, required: %i lanes\n", -+ pipe_config->fdi_lanes); -+ return -EINVAL; -+ } else { -+ return 0; -+ } -+ } -+ -+ if (INTEL_INFO(dev_priv)->num_pipes == 2) -+ return 0; -+ -+ /* Ivybridge 3 pipe is really complicated */ -+ switch (pipe) { -+ case PIPE_A: -+ return 0; -+ case PIPE_B: -+ if (pipe_config->fdi_lanes <= 2) -+ return 0; -+ -+ other_crtc = intel_get_crtc_for_pipe(dev_priv, PIPE_C); -+ other_crtc_state = -+ intel_atomic_get_crtc_state(state, other_crtc); -+ if (IS_ERR(other_crtc_state)) -+ return PTR_ERR(other_crtc_state); -+ -+ if (pipe_required_fdi_lanes(other_crtc_state) > 0) { -+ DRM_DEBUG_KMS("invalid shared fdi lane config on pipe %c: %i lanes\n", -+ pipe_name(pipe), pipe_config->fdi_lanes); -+ return -EINVAL; -+ } -+ return 0; -+ case PIPE_C: -+ if (pipe_config->fdi_lanes > 2) { -+ DRM_DEBUG_KMS("only 2 lanes on pipe %c: required %i lanes\n", -+ pipe_name(pipe), pipe_config->fdi_lanes); -+ return -EINVAL; -+ } -+ -+ other_crtc = intel_get_crtc_for_pipe(dev_priv, PIPE_B); -+ other_crtc_state = -+ intel_atomic_get_crtc_state(state, other_crtc); -+ if (IS_ERR(other_crtc_state)) -+ return PTR_ERR(other_crtc_state); -+ -+ if (pipe_required_fdi_lanes(other_crtc_state) > 2) { -+ DRM_DEBUG_KMS("fdi link B uses too many lanes to enable link C\n"); -+ return -EINVAL; -+ } -+ return 0; -+ default: -+ BUG(); -+ } -+} -+ -+#define RETRY 1 -+static int ironlake_fdi_compute_config(struct intel_crtc *intel_crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_device *dev = intel_crtc->base.dev; -+ const struct drm_display_mode *adjusted_mode = &pipe_config->base.adjusted_mode; -+ int lane, link_bw, fdi_dotclock, ret; -+ bool needs_recompute = false; -+ -+retry: -+ /* FDI is a binary signal running at ~2.7GHz, encoding -+ * each output octet as 10 bits. The actual frequency -+ * is stored as a divider into a 100MHz clock, and the -+ * mode pixel clock is stored in units of 1KHz. -+ * Hence the bw of each lane in terms of the mode signal -+ * is: -+ */ -+ link_bw = intel_fdi_link_freq(to_i915(dev), pipe_config); -+ -+ fdi_dotclock = adjusted_mode->crtc_clock; -+ -+ lane = ironlake_get_lanes_required(fdi_dotclock, link_bw, -+ pipe_config->pipe_bpp); -+ -+ pipe_config->fdi_lanes = lane; -+ -+ intel_link_compute_m_n(pipe_config->pipe_bpp, lane, fdi_dotclock, -+ link_bw, &pipe_config->fdi_m_n, false); -+ -+ ret = ironlake_check_fdi_lanes(dev, intel_crtc->pipe, pipe_config); -+ if (ret == -EDEADLK) -+ return ret; -+ -+ if (ret == -EINVAL && pipe_config->pipe_bpp > 6*3) { -+ pipe_config->pipe_bpp -= 2*3; -+ DRM_DEBUG_KMS("fdi link bw constraint, reducing pipe bpp to %i\n", -+ pipe_config->pipe_bpp); -+ needs_recompute = true; -+ pipe_config->bw_constrained = true; -+ -+ goto retry; -+ } -+ -+ if (needs_recompute) -+ return RETRY; -+ -+ return ret; -+} -+ -+bool hsw_crtc_state_ips_capable(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ /* IPS only exists on ULT machines and is tied to pipe A. */ -+ if (!hsw_crtc_supports_ips(crtc)) -+ return false; -+ -+ if (!i915_modparams.enable_ips) -+ return false; -+ -+ if (crtc_state->pipe_bpp > 24) -+ return false; -+ -+ /* -+ * We compare against max which means we must take -+ * the increased cdclk requirement into account when -+ * calculating the new cdclk. -+ * -+ * Should measure whether using a lower cdclk w/o IPS -+ */ -+ if (IS_BROADWELL(dev_priv) && -+ crtc_state->pixel_rate > dev_priv->max_cdclk_freq * 95 / 100) -+ return false; -+ -+ return true; -+} -+ -+static bool hsw_compute_ips_config(struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = -+ to_i915(crtc_state->base.crtc->dev); -+ struct intel_atomic_state *intel_state = -+ to_intel_atomic_state(crtc_state->base.state); -+ -+ if (!hsw_crtc_state_ips_capable(crtc_state)) -+ return false; -+ -+ /* -+ * When IPS gets enabled, the pipe CRC changes. Since IPS gets -+ * enabled and disabled dynamically based on package C states, -+ * user space can't make reliable use of the CRCs, so let's just -+ * completely disable it. -+ */ -+ if (crtc_state->crc_enabled) -+ return false; -+ -+ /* IPS should be fine as long as at least one plane is enabled. */ -+ if (!(crtc_state->active_planes & ~BIT(PLANE_CURSOR))) -+ return false; -+ -+ /* pixel rate mustn't exceed 95% of cdclk with IPS on BDW */ -+ if (IS_BROADWELL(dev_priv) && -+ crtc_state->pixel_rate > intel_state->cdclk.logical.cdclk * 95 / 100) -+ return false; -+ -+ return true; -+} -+ -+static bool intel_crtc_supports_double_wide(const struct intel_crtc *crtc) -+{ -+ const struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ /* GDG double wide on either pipe, otherwise pipe A only */ -+ return INTEL_GEN(dev_priv) < 4 && -+ (crtc->pipe == PIPE_A || IS_I915G(dev_priv)); -+} -+ -+static u32 ilk_pipe_pixel_rate(const struct intel_crtc_state *pipe_config) -+{ -+ u32 pixel_rate; -+ -+ pixel_rate = pipe_config->base.adjusted_mode.crtc_clock; -+ -+ /* -+ * We only use IF-ID interlacing. If we ever use -+ * PF-ID we'll need to adjust the pixel_rate here. -+ */ -+ -+ if (pipe_config->pch_pfit.enabled) { -+ u64 pipe_w, pipe_h, pfit_w, pfit_h; -+ u32 pfit_size = pipe_config->pch_pfit.size; -+ -+ pipe_w = pipe_config->pipe_src_w; -+ pipe_h = pipe_config->pipe_src_h; -+ -+ pfit_w = (pfit_size >> 16) & 0xFFFF; -+ pfit_h = pfit_size & 0xFFFF; -+ if (pipe_w < pfit_w) -+ pipe_w = pfit_w; -+ if (pipe_h < pfit_h) -+ pipe_h = pfit_h; -+ -+ if (WARN_ON(!pfit_w || !pfit_h)) -+ return pixel_rate; -+ -+ pixel_rate = div_u64((u64)pixel_rate * pipe_w * pipe_h, -+ pfit_w * pfit_h); -+ } -+ -+ return pixel_rate; -+} -+ -+static void intel_crtc_compute_pixel_rate(struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ -+ if (HAS_GMCH(dev_priv)) -+ /* FIXME calculate proper pipe pixel rate for GMCH pfit */ -+ crtc_state->pixel_rate = -+ crtc_state->base.adjusted_mode.crtc_clock; -+ else -+ crtc_state->pixel_rate = -+ ilk_pipe_pixel_rate(crtc_state); -+} -+ -+static int intel_crtc_compute_config(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ const struct drm_display_mode *adjusted_mode = &pipe_config->base.adjusted_mode; -+ int clock_limit = dev_priv->max_dotclk_freq; -+ -+ if (INTEL_GEN(dev_priv) < 4) { -+ clock_limit = dev_priv->max_cdclk_freq * 9 / 10; -+ -+ /* -+ * Enable double wide mode when the dot clock -+ * is > 90% of the (display) core speed. -+ */ -+ if (intel_crtc_supports_double_wide(crtc) && -+ adjusted_mode->crtc_clock > clock_limit) { -+ clock_limit = dev_priv->max_dotclk_freq; -+ pipe_config->double_wide = true; -+ } -+ } -+ -+ if (adjusted_mode->crtc_clock > clock_limit) { -+ DRM_DEBUG_KMS("requested pixel clock (%d kHz) too high (max: %d kHz, double wide: %s)\n", -+ adjusted_mode->crtc_clock, clock_limit, -+ yesno(pipe_config->double_wide)); -+ return -EINVAL; -+ } -+ -+ if ((pipe_config->output_format == INTEL_OUTPUT_FORMAT_YCBCR420 || -+ pipe_config->output_format == INTEL_OUTPUT_FORMAT_YCBCR444) && -+ pipe_config->base.ctm) { -+ /* -+ * There is only one pipe CSC unit per pipe, and we need that -+ * for output conversion from RGB->YCBCR. So if CTM is already -+ * applied we can't support YCBCR420 output. -+ */ -+ DRM_DEBUG_KMS("YCBCR420 and CTM together are not possible\n"); -+ return -EINVAL; -+ } -+ -+ /* -+ * Pipe horizontal size must be even in: -+ * - DVO ganged mode -+ * - LVDS dual channel mode -+ * - Double wide pipe -+ */ -+ if (pipe_config->pipe_src_w & 1) { -+ if (pipe_config->double_wide) { -+ DRM_DEBUG_KMS("Odd pipe source width not supported with double wide pipe\n"); -+ return -EINVAL; -+ } -+ -+ if (intel_crtc_has_type(pipe_config, INTEL_OUTPUT_LVDS) && -+ intel_is_dual_link_lvds(dev_priv)) { -+ DRM_DEBUG_KMS("Odd pipe source width not supported with dual link LVDS\n"); -+ return -EINVAL; -+ } -+ } -+ -+ /* Cantiga+ cannot handle modes with a hsync front porch of 0. -+ * WaPruneModeWithIncorrectHsyncOffset:ctg,elk,ilk,snb,ivb,vlv,hsw. -+ */ -+ if ((INTEL_GEN(dev_priv) > 4 || IS_G4X(dev_priv)) && -+ adjusted_mode->crtc_hsync_start == adjusted_mode->crtc_hdisplay) -+ return -EINVAL; -+ -+ intel_crtc_compute_pixel_rate(pipe_config); -+ -+ if (pipe_config->has_pch_encoder) -+ return ironlake_fdi_compute_config(crtc, pipe_config); -+ -+ return 0; -+} -+ -+static void -+intel_reduce_m_n_ratio(u32 *num, u32 *den) -+{ -+ while (*num > DATA_LINK_M_N_MASK || -+ *den > DATA_LINK_M_N_MASK) { -+ *num >>= 1; -+ *den >>= 1; -+ } -+} -+ -+static void compute_m_n(unsigned int m, unsigned int n, -+ u32 *ret_m, u32 *ret_n, -+ bool constant_n) -+{ -+ /* -+ * Several DP dongles in particular seem to be fussy about -+ * too large link M/N values. Give N value as 0x8000 that -+ * should be acceptable by specific devices. 0x8000 is the -+ * specified fixed N value for asynchronous clock mode, -+ * which the devices expect also in synchronous clock mode. -+ */ -+ if (constant_n) -+ *ret_n = 0x8000; -+ else -+ *ret_n = min_t(unsigned int, roundup_pow_of_two(n), DATA_LINK_N_MAX); -+ -+ *ret_m = div_u64((u64)m * *ret_n, n); -+ intel_reduce_m_n_ratio(ret_m, ret_n); -+} -+ -+void -+intel_link_compute_m_n(u16 bits_per_pixel, int nlanes, -+ int pixel_clock, int link_clock, -+ struct intel_link_m_n *m_n, -+ bool constant_n) -+{ -+ m_n->tu = 64; -+ -+ compute_m_n(bits_per_pixel * pixel_clock, -+ link_clock * nlanes * 8, -+ &m_n->gmch_m, &m_n->gmch_n, -+ constant_n); -+ -+ compute_m_n(pixel_clock, link_clock, -+ &m_n->link_m, &m_n->link_n, -+ constant_n); -+} -+ -+static inline bool intel_panel_use_ssc(struct drm_i915_private *dev_priv) -+{ -+ if (i915_modparams.panel_use_ssc >= 0) -+ return i915_modparams.panel_use_ssc != 0; -+ return dev_priv->vbt.lvds_use_ssc -+ && !(dev_priv->quirks & QUIRK_LVDS_SSC_DISABLE); -+} -+ -+static u32 pnv_dpll_compute_fp(struct dpll *dpll) -+{ -+ return (1 << dpll->n) << 16 | dpll->m2; -+} -+ -+static u32 i9xx_dpll_compute_fp(struct dpll *dpll) -+{ -+ return dpll->n << 16 | dpll->m1 << 8 | dpll->m2; -+} -+ -+static void i9xx_update_pll_dividers(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state, -+ struct dpll *reduced_clock) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ u32 fp, fp2 = 0; -+ -+ if (IS_PINEVIEW(dev_priv)) { -+ fp = pnv_dpll_compute_fp(&crtc_state->dpll); -+ if (reduced_clock) -+ fp2 = pnv_dpll_compute_fp(reduced_clock); -+ } else { -+ fp = i9xx_dpll_compute_fp(&crtc_state->dpll); -+ if (reduced_clock) -+ fp2 = i9xx_dpll_compute_fp(reduced_clock); -+ } -+ -+ crtc_state->dpll_hw_state.fp0 = fp; -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS) && -+ reduced_clock) { -+ crtc_state->dpll_hw_state.fp1 = fp2; -+ } else { -+ crtc_state->dpll_hw_state.fp1 = fp; -+ } -+} -+ -+static void vlv_pllb_recal_opamp(struct drm_i915_private *dev_priv, enum pipe -+ pipe) -+{ -+ u32 reg_val; -+ -+ /* -+ * PLLB opamp always calibrates to max value of 0x3f, force enable it -+ * and set it to a reasonable value instead. -+ */ -+ reg_val = vlv_dpio_read(dev_priv, pipe, VLV_PLL_DW9(1)); -+ reg_val &= 0xffffff00; -+ reg_val |= 0x00000030; -+ vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW9(1), reg_val); -+ -+ reg_val = vlv_dpio_read(dev_priv, pipe, VLV_REF_DW13); -+ reg_val &= 0x00ffffff; -+ reg_val |= 0x8c000000; -+ vlv_dpio_write(dev_priv, pipe, VLV_REF_DW13, reg_val); -+ -+ reg_val = vlv_dpio_read(dev_priv, pipe, VLV_PLL_DW9(1)); -+ reg_val &= 0xffffff00; -+ vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW9(1), reg_val); -+ -+ reg_val = vlv_dpio_read(dev_priv, pipe, VLV_REF_DW13); -+ reg_val &= 0x00ffffff; -+ reg_val |= 0xb0000000; -+ vlv_dpio_write(dev_priv, pipe, VLV_REF_DW13, reg_val); -+} -+ -+static void intel_pch_transcoder_set_m_n(const struct intel_crtc_state *crtc_state, -+ const struct intel_link_m_n *m_n) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ -+ I915_WRITE(PCH_TRANS_DATA_M1(pipe), TU_SIZE(m_n->tu) | m_n->gmch_m); -+ I915_WRITE(PCH_TRANS_DATA_N1(pipe), m_n->gmch_n); -+ I915_WRITE(PCH_TRANS_LINK_M1(pipe), m_n->link_m); -+ I915_WRITE(PCH_TRANS_LINK_N1(pipe), m_n->link_n); -+} -+ -+static bool transcoder_has_m2_n2(struct drm_i915_private *dev_priv, -+ enum transcoder transcoder) -+{ -+ if (IS_HASWELL(dev_priv)) -+ return transcoder == TRANSCODER_EDP; -+ -+ /* -+ * Strictly speaking some registers are available before -+ * gen7, but we only support DRRS on gen7+ -+ */ -+ return IS_GEN(dev_priv, 7) || IS_CHERRYVIEW(dev_priv); -+} -+ -+static void intel_cpu_transcoder_set_m_n(const struct intel_crtc_state *crtc_state, -+ const struct intel_link_m_n *m_n, -+ const struct intel_link_m_n *m2_n2) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ enum transcoder transcoder = crtc_state->cpu_transcoder; -+ -+ if (INTEL_GEN(dev_priv) >= 5) { -+ I915_WRITE(PIPE_DATA_M1(transcoder), TU_SIZE(m_n->tu) | m_n->gmch_m); -+ I915_WRITE(PIPE_DATA_N1(transcoder), m_n->gmch_n); -+ I915_WRITE(PIPE_LINK_M1(transcoder), m_n->link_m); -+ I915_WRITE(PIPE_LINK_N1(transcoder), m_n->link_n); -+ /* -+ * M2_N2 registers are set only if DRRS is supported -+ * (to make sure the registers are not unnecessarily accessed). -+ */ -+ if (m2_n2 && crtc_state->has_drrs && -+ transcoder_has_m2_n2(dev_priv, transcoder)) { -+ I915_WRITE(PIPE_DATA_M2(transcoder), -+ TU_SIZE(m2_n2->tu) | m2_n2->gmch_m); -+ I915_WRITE(PIPE_DATA_N2(transcoder), m2_n2->gmch_n); -+ I915_WRITE(PIPE_LINK_M2(transcoder), m2_n2->link_m); -+ I915_WRITE(PIPE_LINK_N2(transcoder), m2_n2->link_n); -+ } -+ } else { -+ I915_WRITE(PIPE_DATA_M_G4X(pipe), TU_SIZE(m_n->tu) | m_n->gmch_m); -+ I915_WRITE(PIPE_DATA_N_G4X(pipe), m_n->gmch_n); -+ I915_WRITE(PIPE_LINK_M_G4X(pipe), m_n->link_m); -+ I915_WRITE(PIPE_LINK_N_G4X(pipe), m_n->link_n); -+ } -+} -+ -+void intel_dp_set_m_n(const struct intel_crtc_state *crtc_state, enum link_m_n_set m_n) -+{ -+ const struct intel_link_m_n *dp_m_n, *dp_m2_n2 = NULL; -+ -+ if (m_n == M1_N1) { -+ dp_m_n = &crtc_state->dp_m_n; -+ dp_m2_n2 = &crtc_state->dp_m2_n2; -+ } else if (m_n == M2_N2) { -+ -+ /* -+ * M2_N2 registers are not supported. Hence m2_n2 divider value -+ * needs to be programmed into M1_N1. -+ */ -+ dp_m_n = &crtc_state->dp_m2_n2; -+ } else { -+ DRM_ERROR("Unsupported divider value\n"); -+ return; -+ } -+ -+ if (crtc_state->has_pch_encoder) -+ intel_pch_transcoder_set_m_n(crtc_state, &crtc_state->dp_m_n); -+ else -+ intel_cpu_transcoder_set_m_n(crtc_state, dp_m_n, dp_m2_n2); -+} -+ -+static void vlv_compute_dpll(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ pipe_config->dpll_hw_state.dpll = DPLL_INTEGRATED_REF_CLK_VLV | -+ DPLL_REF_CLK_ENABLE_VLV | DPLL_VGA_MODE_DIS; -+ if (crtc->pipe != PIPE_A) -+ pipe_config->dpll_hw_state.dpll |= DPLL_INTEGRATED_CRI_CLK_VLV; -+ -+ /* DPLL not used with DSI, but still need the rest set up */ -+ if (!intel_crtc_has_type(pipe_config, INTEL_OUTPUT_DSI)) -+ pipe_config->dpll_hw_state.dpll |= DPLL_VCO_ENABLE | -+ DPLL_EXT_BUFFER_ENABLE_VLV; -+ -+ pipe_config->dpll_hw_state.dpll_md = -+ (pipe_config->pixel_multiplier - 1) << DPLL_MD_UDI_MULTIPLIER_SHIFT; -+} -+ -+static void chv_compute_dpll(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ pipe_config->dpll_hw_state.dpll = DPLL_SSC_REF_CLK_CHV | -+ DPLL_REF_CLK_ENABLE_VLV | DPLL_VGA_MODE_DIS; -+ if (crtc->pipe != PIPE_A) -+ pipe_config->dpll_hw_state.dpll |= DPLL_INTEGRATED_CRI_CLK_VLV; -+ -+ /* DPLL not used with DSI, but still need the rest set up */ -+ if (!intel_crtc_has_type(pipe_config, INTEL_OUTPUT_DSI)) -+ pipe_config->dpll_hw_state.dpll |= DPLL_VCO_ENABLE; -+ -+ pipe_config->dpll_hw_state.dpll_md = -+ (pipe_config->pixel_multiplier - 1) << DPLL_MD_UDI_MULTIPLIER_SHIFT; -+} -+ -+static void vlv_prepare_pll(struct intel_crtc *crtc, -+ const struct intel_crtc_state *pipe_config) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ enum pipe pipe = crtc->pipe; -+ u32 mdiv; -+ u32 bestn, bestm1, bestm2, bestp1, bestp2; -+ u32 coreclk, reg_val; -+ -+ /* Enable Refclk */ -+ I915_WRITE(DPLL(pipe), -+ pipe_config->dpll_hw_state.dpll & -+ ~(DPLL_VCO_ENABLE | DPLL_EXT_BUFFER_ENABLE_VLV)); -+ -+ /* No need to actually set up the DPLL with DSI */ -+ if ((pipe_config->dpll_hw_state.dpll & DPLL_VCO_ENABLE) == 0) -+ return; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ bestn = pipe_config->dpll.n; -+ bestm1 = pipe_config->dpll.m1; -+ bestm2 = pipe_config->dpll.m2; -+ bestp1 = pipe_config->dpll.p1; -+ bestp2 = pipe_config->dpll.p2; -+ -+ /* See eDP HDMI DPIO driver vbios notes doc */ -+ -+ /* PLL B needs special handling */ -+ if (pipe == PIPE_B) -+ vlv_pllb_recal_opamp(dev_priv, pipe); -+ -+ /* Set up Tx target for periodic Rcomp update */ -+ vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW9_BCAST, 0x0100000f); -+ -+ /* Disable target IRef on PLL */ -+ reg_val = vlv_dpio_read(dev_priv, pipe, VLV_PLL_DW8(pipe)); -+ reg_val &= 0x00ffffff; -+ vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW8(pipe), reg_val); -+ -+ /* Disable fast lock */ -+ vlv_dpio_write(dev_priv, pipe, VLV_CMN_DW0, 0x610); -+ -+ /* Set idtafcrecal before PLL is enabled */ -+ mdiv = ((bestm1 << DPIO_M1DIV_SHIFT) | (bestm2 & DPIO_M2DIV_MASK)); -+ mdiv |= ((bestp1 << DPIO_P1_SHIFT) | (bestp2 << DPIO_P2_SHIFT)); -+ mdiv |= ((bestn << DPIO_N_SHIFT)); -+ mdiv |= (1 << DPIO_K_SHIFT); -+ -+ /* -+ * Post divider depends on pixel clock rate, DAC vs digital (and LVDS, -+ * but we don't support that). -+ * Note: don't use the DAC post divider as it seems unstable. -+ */ -+ mdiv |= (DPIO_POST_DIV_HDMIDP << DPIO_POST_DIV_SHIFT); -+ vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW3(pipe), mdiv); -+ -+ mdiv |= DPIO_ENABLE_CALIBRATION; -+ vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW3(pipe), mdiv); -+ -+ /* Set HBR and RBR LPF coefficients */ -+ if (pipe_config->port_clock == 162000 || -+ intel_crtc_has_type(pipe_config, INTEL_OUTPUT_ANALOG) || -+ intel_crtc_has_type(pipe_config, INTEL_OUTPUT_HDMI)) -+ vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW10(pipe), -+ 0x009f0003); -+ else -+ vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW10(pipe), -+ 0x00d0000f); -+ -+ if (intel_crtc_has_dp_encoder(pipe_config)) { -+ /* Use SSC source */ -+ if (pipe == PIPE_A) -+ vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW5(pipe), -+ 0x0df40000); -+ else -+ vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW5(pipe), -+ 0x0df70000); -+ } else { /* HDMI or VGA */ -+ /* Use bend source */ -+ if (pipe == PIPE_A) -+ vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW5(pipe), -+ 0x0df70000); -+ else -+ vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW5(pipe), -+ 0x0df40000); -+ } -+ -+ coreclk = vlv_dpio_read(dev_priv, pipe, VLV_PLL_DW7(pipe)); -+ coreclk = (coreclk & 0x0000ff00) | 0x01c00000; -+ if (intel_crtc_has_dp_encoder(pipe_config)) -+ coreclk |= 0x01000000; -+ vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW7(pipe), coreclk); -+ -+ vlv_dpio_write(dev_priv, pipe, VLV_PLL_DW11(pipe), 0x87871000); -+ mutex_unlock(&dev_priv->sb_lock); -+} -+ -+static void chv_prepare_pll(struct intel_crtc *crtc, -+ const struct intel_crtc_state *pipe_config) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ enum pipe pipe = crtc->pipe; -+ enum dpio_channel port = vlv_pipe_to_channel(pipe); -+ u32 loopfilter, tribuf_calcntr; -+ u32 bestn, bestm1, bestm2, bestp1, bestp2, bestm2_frac; -+ u32 dpio_val; -+ int vco; -+ -+ /* Enable Refclk and SSC */ -+ I915_WRITE(DPLL(pipe), -+ pipe_config->dpll_hw_state.dpll & ~DPLL_VCO_ENABLE); -+ -+ /* No need to actually set up the DPLL with DSI */ -+ if ((pipe_config->dpll_hw_state.dpll & DPLL_VCO_ENABLE) == 0) -+ return; -+ -+ bestn = pipe_config->dpll.n; -+ bestm2_frac = pipe_config->dpll.m2 & 0x3fffff; -+ bestm1 = pipe_config->dpll.m1; -+ bestm2 = pipe_config->dpll.m2 >> 22; -+ bestp1 = pipe_config->dpll.p1; -+ bestp2 = pipe_config->dpll.p2; -+ vco = pipe_config->dpll.vco; -+ dpio_val = 0; -+ loopfilter = 0; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ /* p1 and p2 divider */ -+ vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW13(port), -+ 5 << DPIO_CHV_S1_DIV_SHIFT | -+ bestp1 << DPIO_CHV_P1_DIV_SHIFT | -+ bestp2 << DPIO_CHV_P2_DIV_SHIFT | -+ 1 << DPIO_CHV_K_DIV_SHIFT); -+ -+ /* Feedback post-divider - m2 */ -+ vlv_dpio_write(dev_priv, pipe, CHV_PLL_DW0(port), bestm2); -+ -+ /* Feedback refclk divider - n and m1 */ -+ vlv_dpio_write(dev_priv, pipe, CHV_PLL_DW1(port), -+ DPIO_CHV_M1_DIV_BY_2 | -+ 1 << DPIO_CHV_N_DIV_SHIFT); -+ -+ /* M2 fraction division */ -+ vlv_dpio_write(dev_priv, pipe, CHV_PLL_DW2(port), bestm2_frac); -+ -+ /* M2 fraction division enable */ -+ dpio_val = vlv_dpio_read(dev_priv, pipe, CHV_PLL_DW3(port)); -+ dpio_val &= ~(DPIO_CHV_FEEDFWD_GAIN_MASK | DPIO_CHV_FRAC_DIV_EN); -+ dpio_val |= (2 << DPIO_CHV_FEEDFWD_GAIN_SHIFT); -+ if (bestm2_frac) -+ dpio_val |= DPIO_CHV_FRAC_DIV_EN; -+ vlv_dpio_write(dev_priv, pipe, CHV_PLL_DW3(port), dpio_val); -+ -+ /* Program digital lock detect threshold */ -+ dpio_val = vlv_dpio_read(dev_priv, pipe, CHV_PLL_DW9(port)); -+ dpio_val &= ~(DPIO_CHV_INT_LOCK_THRESHOLD_MASK | -+ DPIO_CHV_INT_LOCK_THRESHOLD_SEL_COARSE); -+ dpio_val |= (0x5 << DPIO_CHV_INT_LOCK_THRESHOLD_SHIFT); -+ if (!bestm2_frac) -+ dpio_val |= DPIO_CHV_INT_LOCK_THRESHOLD_SEL_COARSE; -+ vlv_dpio_write(dev_priv, pipe, CHV_PLL_DW9(port), dpio_val); -+ -+ /* Loop filter */ -+ if (vco == 5400000) { -+ loopfilter |= (0x3 << DPIO_CHV_PROP_COEFF_SHIFT); -+ loopfilter |= (0x8 << DPIO_CHV_INT_COEFF_SHIFT); -+ loopfilter |= (0x1 << DPIO_CHV_GAIN_CTRL_SHIFT); -+ tribuf_calcntr = 0x9; -+ } else if (vco <= 6200000) { -+ loopfilter |= (0x5 << DPIO_CHV_PROP_COEFF_SHIFT); -+ loopfilter |= (0xB << DPIO_CHV_INT_COEFF_SHIFT); -+ loopfilter |= (0x3 << DPIO_CHV_GAIN_CTRL_SHIFT); -+ tribuf_calcntr = 0x9; -+ } else if (vco <= 6480000) { -+ loopfilter |= (0x4 << DPIO_CHV_PROP_COEFF_SHIFT); -+ loopfilter |= (0x9 << DPIO_CHV_INT_COEFF_SHIFT); -+ loopfilter |= (0x3 << DPIO_CHV_GAIN_CTRL_SHIFT); -+ tribuf_calcntr = 0x8; -+ } else { -+ /* Not supported. Apply the same limits as in the max case */ -+ loopfilter |= (0x4 << DPIO_CHV_PROP_COEFF_SHIFT); -+ loopfilter |= (0x9 << DPIO_CHV_INT_COEFF_SHIFT); -+ loopfilter |= (0x3 << DPIO_CHV_GAIN_CTRL_SHIFT); -+ tribuf_calcntr = 0; -+ } -+ vlv_dpio_write(dev_priv, pipe, CHV_PLL_DW6(port), loopfilter); -+ -+ dpio_val = vlv_dpio_read(dev_priv, pipe, CHV_PLL_DW8(port)); -+ dpio_val &= ~DPIO_CHV_TDC_TARGET_CNT_MASK; -+ dpio_val |= (tribuf_calcntr << DPIO_CHV_TDC_TARGET_CNT_SHIFT); -+ vlv_dpio_write(dev_priv, pipe, CHV_PLL_DW8(port), dpio_val); -+ -+ /* AFC Recal */ -+ vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW14(port), -+ vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW14(port)) | -+ DPIO_AFC_RECAL); -+ -+ mutex_unlock(&dev_priv->sb_lock); -+} -+ -+/** -+ * vlv_force_pll_on - forcibly enable just the PLL -+ * @dev_priv: i915 private structure -+ * @pipe: pipe PLL to enable -+ * @dpll: PLL configuration -+ * -+ * Enable the PLL for @pipe using the supplied @dpll config. To be used -+ * in cases where we need the PLL enabled even when @pipe is not going to -+ * be enabled. -+ */ -+int vlv_force_pll_on(struct drm_i915_private *dev_priv, enum pipe pipe, -+ const struct dpll *dpll) -+{ -+ struct intel_crtc *crtc = intel_get_crtc_for_pipe(dev_priv, pipe); -+ struct intel_crtc_state *pipe_config; -+ -+ pipe_config = kzalloc(sizeof(*pipe_config), GFP_KERNEL); -+ if (!pipe_config) -+ return -ENOMEM; -+ -+ pipe_config->base.crtc = &crtc->base; -+ pipe_config->pixel_multiplier = 1; -+ pipe_config->dpll = *dpll; -+ -+ if (IS_CHERRYVIEW(dev_priv)) { -+ chv_compute_dpll(crtc, pipe_config); -+ chv_prepare_pll(crtc, pipe_config); -+ chv_enable_pll(crtc, pipe_config); -+ } else { -+ vlv_compute_dpll(crtc, pipe_config); -+ vlv_prepare_pll(crtc, pipe_config); -+ vlv_enable_pll(crtc, pipe_config); -+ } -+ -+ kfree(pipe_config); -+ -+ return 0; -+} -+ -+/** -+ * vlv_force_pll_off - forcibly disable just the PLL -+ * @dev_priv: i915 private structure -+ * @pipe: pipe PLL to disable -+ * -+ * Disable the PLL for @pipe. To be used in cases where we need -+ * the PLL enabled even when @pipe is not going to be enabled. -+ */ -+void vlv_force_pll_off(struct drm_i915_private *dev_priv, enum pipe pipe) -+{ -+ if (IS_CHERRYVIEW(dev_priv)) -+ chv_disable_pll(dev_priv, pipe); -+ else -+ vlv_disable_pll(dev_priv, pipe); -+} -+ -+static void i9xx_compute_dpll(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state, -+ struct dpll *reduced_clock) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ u32 dpll; -+ struct dpll *clock = &crtc_state->dpll; -+ -+ i9xx_update_pll_dividers(crtc, crtc_state, reduced_clock); -+ -+ dpll = DPLL_VGA_MODE_DIS; -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) -+ dpll |= DPLLB_MODE_LVDS; -+ else -+ dpll |= DPLLB_MODE_DAC_SERIAL; -+ -+ if (IS_I945G(dev_priv) || IS_I945GM(dev_priv) || -+ IS_G33(dev_priv) || IS_PINEVIEW(dev_priv)) { -+ dpll |= (crtc_state->pixel_multiplier - 1) -+ << SDVO_MULTIPLIER_SHIFT_HIRES; -+ } -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_SDVO) || -+ intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) -+ dpll |= DPLL_SDVO_HIGH_SPEED; -+ -+ if (intel_crtc_has_dp_encoder(crtc_state)) -+ dpll |= DPLL_SDVO_HIGH_SPEED; -+ -+ /* compute bitmask from p1 value */ -+ if (IS_PINEVIEW(dev_priv)) -+ dpll |= (1 << (clock->p1 - 1)) << DPLL_FPA01_P1_POST_DIV_SHIFT_PINEVIEW; -+ else { -+ dpll |= (1 << (clock->p1 - 1)) << DPLL_FPA01_P1_POST_DIV_SHIFT; -+ if (IS_G4X(dev_priv) && reduced_clock) -+ dpll |= (1 << (reduced_clock->p1 - 1)) << DPLL_FPA1_P1_POST_DIV_SHIFT; -+ } -+ switch (clock->p2) { -+ case 5: -+ dpll |= DPLL_DAC_SERIAL_P2_CLOCK_DIV_5; -+ break; -+ case 7: -+ dpll |= DPLLB_LVDS_P2_CLOCK_DIV_7; -+ break; -+ case 10: -+ dpll |= DPLL_DAC_SERIAL_P2_CLOCK_DIV_10; -+ break; -+ case 14: -+ dpll |= DPLLB_LVDS_P2_CLOCK_DIV_14; -+ break; -+ } -+ if (INTEL_GEN(dev_priv) >= 4) -+ dpll |= (6 << PLL_LOAD_PULSE_PHASE_SHIFT); -+ -+ if (crtc_state->sdvo_tv_clock) -+ dpll |= PLL_REF_INPUT_TVCLKINBC; -+ else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS) && -+ intel_panel_use_ssc(dev_priv)) -+ dpll |= PLLB_REF_INPUT_SPREADSPECTRUMIN; -+ else -+ dpll |= PLL_REF_INPUT_DREFCLK; -+ -+ dpll |= DPLL_VCO_ENABLE; -+ crtc_state->dpll_hw_state.dpll = dpll; -+ -+ if (INTEL_GEN(dev_priv) >= 4) { -+ u32 dpll_md = (crtc_state->pixel_multiplier - 1) -+ << DPLL_MD_UDI_MULTIPLIER_SHIFT; -+ crtc_state->dpll_hw_state.dpll_md = dpll_md; -+ } -+} -+ -+static void i8xx_compute_dpll(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state, -+ struct dpll *reduced_clock) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ u32 dpll; -+ struct dpll *clock = &crtc_state->dpll; -+ -+ i9xx_update_pll_dividers(crtc, crtc_state, reduced_clock); -+ -+ dpll = DPLL_VGA_MODE_DIS; -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { -+ dpll |= (1 << (clock->p1 - 1)) << DPLL_FPA01_P1_POST_DIV_SHIFT; -+ } else { -+ if (clock->p1 == 2) -+ dpll |= PLL_P1_DIVIDE_BY_TWO; -+ else -+ dpll |= (clock->p1 - 2) << DPLL_FPA01_P1_POST_DIV_SHIFT; -+ if (clock->p2 == 4) -+ dpll |= PLL_P2_DIVIDE_BY_4; -+ } -+ -+ /* -+ * Bspec: -+ * "[Almador Errata}: For the correct operation of the muxed DVO pins -+ * (GDEVSELB/I2Cdata, GIRDBY/I2CClk) and (GFRAMEB/DVI_Data, -+ * GTRDYB/DVI_Clk): Bit 31 (DPLL VCO Enable) and Bit 30 (2X Clock -+ * Enable) must be set to “1” in both the DPLL A Control Register -+ * (06014h-06017h) and DPLL B Control Register (06018h-0601Bh)." -+ * -+ * For simplicity We simply keep both bits always enabled in -+ * both DPLLS. The spec says we should disable the DVO 2X clock -+ * when not needed, but this seems to work fine in practice. -+ */ -+ if (IS_I830(dev_priv) || -+ intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DVO)) -+ dpll |= DPLL_DVO_2X_MODE; -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS) && -+ intel_panel_use_ssc(dev_priv)) -+ dpll |= PLLB_REF_INPUT_SPREADSPECTRUMIN; -+ else -+ dpll |= PLL_REF_INPUT_DREFCLK; -+ -+ dpll |= DPLL_VCO_ENABLE; -+ crtc_state->dpll_hw_state.dpll = dpll; -+} -+ -+static void intel_set_pipe_timings(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; -+ const struct drm_display_mode *adjusted_mode = &crtc_state->base.adjusted_mode; -+ u32 crtc_vtotal, crtc_vblank_end; -+ int vsyncshift = 0; -+ -+ /* We need to be careful not to changed the adjusted mode, for otherwise -+ * the hw state checker will get angry at the mismatch. */ -+ crtc_vtotal = adjusted_mode->crtc_vtotal; -+ crtc_vblank_end = adjusted_mode->crtc_vblank_end; -+ -+ if (adjusted_mode->flags & DRM_MODE_FLAG_INTERLACE) { -+ /* the chip adds 2 halflines automatically */ -+ crtc_vtotal -= 1; -+ crtc_vblank_end -= 1; -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_SDVO)) -+ vsyncshift = (adjusted_mode->crtc_htotal - 1) / 2; -+ else -+ vsyncshift = adjusted_mode->crtc_hsync_start - -+ adjusted_mode->crtc_htotal / 2; -+ if (vsyncshift < 0) -+ vsyncshift += adjusted_mode->crtc_htotal; -+ } -+ -+ if (INTEL_GEN(dev_priv) > 3) -+ I915_WRITE(VSYNCSHIFT(cpu_transcoder), vsyncshift); -+ -+ I915_WRITE(HTOTAL(cpu_transcoder), -+ (adjusted_mode->crtc_hdisplay - 1) | -+ ((adjusted_mode->crtc_htotal - 1) << 16)); -+ I915_WRITE(HBLANK(cpu_transcoder), -+ (adjusted_mode->crtc_hblank_start - 1) | -+ ((adjusted_mode->crtc_hblank_end - 1) << 16)); -+ I915_WRITE(HSYNC(cpu_transcoder), -+ (adjusted_mode->crtc_hsync_start - 1) | -+ ((adjusted_mode->crtc_hsync_end - 1) << 16)); -+ -+ I915_WRITE(VTOTAL(cpu_transcoder), -+ (adjusted_mode->crtc_vdisplay - 1) | -+ ((crtc_vtotal - 1) << 16)); -+ I915_WRITE(VBLANK(cpu_transcoder), -+ (adjusted_mode->crtc_vblank_start - 1) | -+ ((crtc_vblank_end - 1) << 16)); -+ I915_WRITE(VSYNC(cpu_transcoder), -+ (adjusted_mode->crtc_vsync_start - 1) | -+ ((adjusted_mode->crtc_vsync_end - 1) << 16)); -+ -+ /* Workaround: when the EDP input selection is B, the VTOTAL_B must be -+ * programmed with the VTOTAL_EDP value. Same for VTOTAL_C. This is -+ * documented on the DDI_FUNC_CTL register description, EDP Input Select -+ * bits. */ -+ if (IS_HASWELL(dev_priv) && cpu_transcoder == TRANSCODER_EDP && -+ (pipe == PIPE_B || pipe == PIPE_C)) -+ I915_WRITE(VTOTAL(pipe), I915_READ(VTOTAL(cpu_transcoder))); -+ -+} -+ -+static void intel_set_pipe_src_size(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ -+ /* pipesrc controls the size that is scaled from, which should -+ * always be the user's requested size. -+ */ -+ I915_WRITE(PIPESRC(pipe), -+ ((crtc_state->pipe_src_w - 1) << 16) | -+ (crtc_state->pipe_src_h - 1)); -+} -+ -+static void intel_get_pipe_timings(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ enum transcoder cpu_transcoder = pipe_config->cpu_transcoder; -+ u32 tmp; -+ -+ tmp = I915_READ(HTOTAL(cpu_transcoder)); -+ pipe_config->base.adjusted_mode.crtc_hdisplay = (tmp & 0xffff) + 1; -+ pipe_config->base.adjusted_mode.crtc_htotal = ((tmp >> 16) & 0xffff) + 1; -+ tmp = I915_READ(HBLANK(cpu_transcoder)); -+ pipe_config->base.adjusted_mode.crtc_hblank_start = (tmp & 0xffff) + 1; -+ pipe_config->base.adjusted_mode.crtc_hblank_end = ((tmp >> 16) & 0xffff) + 1; -+ tmp = I915_READ(HSYNC(cpu_transcoder)); -+ pipe_config->base.adjusted_mode.crtc_hsync_start = (tmp & 0xffff) + 1; -+ pipe_config->base.adjusted_mode.crtc_hsync_end = ((tmp >> 16) & 0xffff) + 1; -+ -+ tmp = I915_READ(VTOTAL(cpu_transcoder)); -+ pipe_config->base.adjusted_mode.crtc_vdisplay = (tmp & 0xffff) + 1; -+ pipe_config->base.adjusted_mode.crtc_vtotal = ((tmp >> 16) & 0xffff) + 1; -+ tmp = I915_READ(VBLANK(cpu_transcoder)); -+ pipe_config->base.adjusted_mode.crtc_vblank_start = (tmp & 0xffff) + 1; -+ pipe_config->base.adjusted_mode.crtc_vblank_end = ((tmp >> 16) & 0xffff) + 1; -+ tmp = I915_READ(VSYNC(cpu_transcoder)); -+ pipe_config->base.adjusted_mode.crtc_vsync_start = (tmp & 0xffff) + 1; -+ pipe_config->base.adjusted_mode.crtc_vsync_end = ((tmp >> 16) & 0xffff) + 1; -+ -+ if (I915_READ(PIPECONF(cpu_transcoder)) & PIPECONF_INTERLACE_MASK) { -+ pipe_config->base.adjusted_mode.flags |= DRM_MODE_FLAG_INTERLACE; -+ pipe_config->base.adjusted_mode.crtc_vtotal += 1; -+ pipe_config->base.adjusted_mode.crtc_vblank_end += 1; -+ } -+} -+ -+static void intel_get_pipe_src_size(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ u32 tmp; -+ -+ tmp = I915_READ(PIPESRC(crtc->pipe)); -+ pipe_config->pipe_src_h = (tmp & 0xffff) + 1; -+ pipe_config->pipe_src_w = ((tmp >> 16) & 0xffff) + 1; -+ -+ pipe_config->base.mode.vdisplay = pipe_config->pipe_src_h; -+ pipe_config->base.mode.hdisplay = pipe_config->pipe_src_w; -+} -+ -+void intel_mode_from_pipe_config(struct drm_display_mode *mode, -+ struct intel_crtc_state *pipe_config) -+{ -+ mode->hdisplay = pipe_config->base.adjusted_mode.crtc_hdisplay; -+ mode->htotal = pipe_config->base.adjusted_mode.crtc_htotal; -+ mode->hsync_start = pipe_config->base.adjusted_mode.crtc_hsync_start; -+ mode->hsync_end = pipe_config->base.adjusted_mode.crtc_hsync_end; -+ -+ mode->vdisplay = pipe_config->base.adjusted_mode.crtc_vdisplay; -+ mode->vtotal = pipe_config->base.adjusted_mode.crtc_vtotal; -+ mode->vsync_start = pipe_config->base.adjusted_mode.crtc_vsync_start; -+ mode->vsync_end = pipe_config->base.adjusted_mode.crtc_vsync_end; -+ -+ mode->flags = pipe_config->base.adjusted_mode.flags; -+ mode->type = DRM_MODE_TYPE_DRIVER; -+ -+ mode->clock = pipe_config->base.adjusted_mode.crtc_clock; -+ -+ mode->hsync = drm_mode_hsync(mode); -+ mode->vrefresh = drm_mode_vrefresh(mode); -+ drm_mode_set_name(mode); -+} -+ -+static void i9xx_set_pipeconf(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ u32 pipeconf; -+ -+ pipeconf = 0; -+ -+ /* we keep both pipes enabled on 830 */ -+ if (IS_I830(dev_priv)) -+ pipeconf |= I915_READ(PIPECONF(crtc->pipe)) & PIPECONF_ENABLE; -+ -+ if (crtc_state->double_wide) -+ pipeconf |= PIPECONF_DOUBLE_WIDE; -+ -+ /* only g4x and later have fancy bpc/dither controls */ -+ if (IS_G4X(dev_priv) || IS_VALLEYVIEW(dev_priv) || -+ IS_CHERRYVIEW(dev_priv)) { -+ /* Bspec claims that we can't use dithering for 30bpp pipes. */ -+ if (crtc_state->dither && crtc_state->pipe_bpp != 30) -+ pipeconf |= PIPECONF_DITHER_EN | -+ PIPECONF_DITHER_TYPE_SP; -+ -+ switch (crtc_state->pipe_bpp) { -+ case 18: -+ pipeconf |= PIPECONF_6BPC; -+ break; -+ case 24: -+ pipeconf |= PIPECONF_8BPC; -+ break; -+ case 30: -+ pipeconf |= PIPECONF_10BPC; -+ break; -+ default: -+ /* Case prevented by intel_choose_pipe_bpp_dither. */ -+ BUG(); -+ } -+ } -+ -+ if (crtc_state->base.adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) { -+ if (INTEL_GEN(dev_priv) < 4 || -+ intel_crtc_has_type(crtc_state, INTEL_OUTPUT_SDVO)) -+ pipeconf |= PIPECONF_INTERLACE_W_FIELD_INDICATION; -+ else -+ pipeconf |= PIPECONF_INTERLACE_W_SYNC_SHIFT; -+ } else { -+ pipeconf |= PIPECONF_PROGRESSIVE; -+ } -+ -+ if ((IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) && -+ crtc_state->limited_color_range) -+ pipeconf |= PIPECONF_COLOR_RANGE_SELECT; -+ -+ pipeconf |= PIPECONF_GAMMA_MODE(crtc_state->gamma_mode); -+ -+ I915_WRITE(PIPECONF(crtc->pipe), pipeconf); -+ POSTING_READ(PIPECONF(crtc->pipe)); -+} -+ -+static int i8xx_crtc_compute_clock(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ const struct intel_limit *limit; -+ int refclk = 48000; -+ -+ memset(&crtc_state->dpll_hw_state, 0, -+ sizeof(crtc_state->dpll_hw_state)); -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { -+ if (intel_panel_use_ssc(dev_priv)) { -+ refclk = dev_priv->vbt.lvds_ssc_freq; -+ DRM_DEBUG_KMS("using SSC reference clock of %d kHz\n", refclk); -+ } -+ -+ limit = &intel_limits_i8xx_lvds; -+ } else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DVO)) { -+ limit = &intel_limits_i8xx_dvo; -+ } else { -+ limit = &intel_limits_i8xx_dac; -+ } -+ -+ if (!crtc_state->clock_set && -+ !i9xx_find_best_dpll(limit, crtc_state, crtc_state->port_clock, -+ refclk, NULL, &crtc_state->dpll)) { -+ DRM_ERROR("Couldn't find PLL settings for mode!\n"); -+ return -EINVAL; -+ } -+ -+ i8xx_compute_dpll(crtc, crtc_state, NULL); -+ -+ return 0; -+} -+ -+static int g4x_crtc_compute_clock(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ const struct intel_limit *limit; -+ int refclk = 96000; -+ -+ memset(&crtc_state->dpll_hw_state, 0, -+ sizeof(crtc_state->dpll_hw_state)); -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { -+ if (intel_panel_use_ssc(dev_priv)) { -+ refclk = dev_priv->vbt.lvds_ssc_freq; -+ DRM_DEBUG_KMS("using SSC reference clock of %d kHz\n", refclk); -+ } -+ -+ if (intel_is_dual_link_lvds(dev_priv)) -+ limit = &intel_limits_g4x_dual_channel_lvds; -+ else -+ limit = &intel_limits_g4x_single_channel_lvds; -+ } else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI) || -+ intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG)) { -+ limit = &intel_limits_g4x_hdmi; -+ } else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_SDVO)) { -+ limit = &intel_limits_g4x_sdvo; -+ } else { -+ /* The option is for other outputs */ -+ limit = &intel_limits_i9xx_sdvo; -+ } -+ -+ if (!crtc_state->clock_set && -+ !g4x_find_best_dpll(limit, crtc_state, crtc_state->port_clock, -+ refclk, NULL, &crtc_state->dpll)) { -+ DRM_ERROR("Couldn't find PLL settings for mode!\n"); -+ return -EINVAL; -+ } -+ -+ i9xx_compute_dpll(crtc, crtc_state, NULL); -+ -+ return 0; -+} -+ -+static int pnv_crtc_compute_clock(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ const struct intel_limit *limit; -+ int refclk = 96000; -+ -+ memset(&crtc_state->dpll_hw_state, 0, -+ sizeof(crtc_state->dpll_hw_state)); -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { -+ if (intel_panel_use_ssc(dev_priv)) { -+ refclk = dev_priv->vbt.lvds_ssc_freq; -+ DRM_DEBUG_KMS("using SSC reference clock of %d kHz\n", refclk); -+ } -+ -+ limit = &intel_limits_pineview_lvds; -+ } else { -+ limit = &intel_limits_pineview_sdvo; -+ } -+ -+ if (!crtc_state->clock_set && -+ !pnv_find_best_dpll(limit, crtc_state, crtc_state->port_clock, -+ refclk, NULL, &crtc_state->dpll)) { -+ DRM_ERROR("Couldn't find PLL settings for mode!\n"); -+ return -EINVAL; -+ } -+ -+ i9xx_compute_dpll(crtc, crtc_state, NULL); -+ -+ return 0; -+} -+ -+static int i9xx_crtc_compute_clock(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ const struct intel_limit *limit; -+ int refclk = 96000; -+ -+ memset(&crtc_state->dpll_hw_state, 0, -+ sizeof(crtc_state->dpll_hw_state)); -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { -+ if (intel_panel_use_ssc(dev_priv)) { -+ refclk = dev_priv->vbt.lvds_ssc_freq; -+ DRM_DEBUG_KMS("using SSC reference clock of %d kHz\n", refclk); -+ } -+ -+ limit = &intel_limits_i9xx_lvds; -+ } else { -+ limit = &intel_limits_i9xx_sdvo; -+ } -+ -+ if (!crtc_state->clock_set && -+ !i9xx_find_best_dpll(limit, crtc_state, crtc_state->port_clock, -+ refclk, NULL, &crtc_state->dpll)) { -+ DRM_ERROR("Couldn't find PLL settings for mode!\n"); -+ return -EINVAL; -+ } -+ -+ i9xx_compute_dpll(crtc, crtc_state, NULL); -+ -+ return 0; -+} -+ -+static int chv_crtc_compute_clock(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state) -+{ -+ int refclk = 100000; -+ const struct intel_limit *limit = &intel_limits_chv; -+ -+ memset(&crtc_state->dpll_hw_state, 0, -+ sizeof(crtc_state->dpll_hw_state)); -+ -+ if (!crtc_state->clock_set && -+ !chv_find_best_dpll(limit, crtc_state, crtc_state->port_clock, -+ refclk, NULL, &crtc_state->dpll)) { -+ DRM_ERROR("Couldn't find PLL settings for mode!\n"); -+ return -EINVAL; -+ } -+ -+ chv_compute_dpll(crtc, crtc_state); -+ -+ return 0; -+} -+ -+static int vlv_crtc_compute_clock(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state) -+{ -+ int refclk = 100000; -+ const struct intel_limit *limit = &intel_limits_vlv; -+ -+ memset(&crtc_state->dpll_hw_state, 0, -+ sizeof(crtc_state->dpll_hw_state)); -+ -+ if (!crtc_state->clock_set && -+ !vlv_find_best_dpll(limit, crtc_state, crtc_state->port_clock, -+ refclk, NULL, &crtc_state->dpll)) { -+ DRM_ERROR("Couldn't find PLL settings for mode!\n"); -+ return -EINVAL; -+ } -+ -+ vlv_compute_dpll(crtc, crtc_state); -+ -+ return 0; -+} -+ -+static bool i9xx_has_pfit(struct drm_i915_private *dev_priv) -+{ -+ if (IS_I830(dev_priv)) -+ return false; -+ -+ return INTEL_GEN(dev_priv) >= 4 || -+ IS_PINEVIEW(dev_priv) || IS_MOBILE(dev_priv); -+} -+ -+static void i9xx_get_pfit_config(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ u32 tmp; -+ -+ if (!i9xx_has_pfit(dev_priv)) -+ return; -+ -+ tmp = I915_READ(PFIT_CONTROL); -+ if (!(tmp & PFIT_ENABLE)) -+ return; -+ -+ /* Check whether the pfit is attached to our pipe. */ -+ if (INTEL_GEN(dev_priv) < 4) { -+ if (crtc->pipe != PIPE_B) -+ return; -+ } else { -+ if ((tmp & PFIT_PIPE_MASK) != (crtc->pipe << PFIT_PIPE_SHIFT)) -+ return; -+ } -+ -+ pipe_config->gmch_pfit.control = tmp; -+ pipe_config->gmch_pfit.pgm_ratios = I915_READ(PFIT_PGM_RATIOS); -+} -+ -+static void vlv_crtc_clock_get(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int pipe = pipe_config->cpu_transcoder; -+ struct dpll clock; -+ u32 mdiv; -+ int refclk = 100000; -+ -+ /* In case of DSI, DPLL will not be used */ -+ if ((pipe_config->dpll_hw_state.dpll & DPLL_VCO_ENABLE) == 0) -+ return; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ mdiv = vlv_dpio_read(dev_priv, pipe, VLV_PLL_DW3(pipe)); -+ mutex_unlock(&dev_priv->sb_lock); -+ -+ clock.m1 = (mdiv >> DPIO_M1DIV_SHIFT) & 7; -+ clock.m2 = mdiv & DPIO_M2DIV_MASK; -+ clock.n = (mdiv >> DPIO_N_SHIFT) & 0xf; -+ clock.p1 = (mdiv >> DPIO_P1_SHIFT) & 7; -+ clock.p2 = (mdiv >> DPIO_P2_SHIFT) & 0x1f; -+ -+ pipe_config->port_clock = vlv_calc_dpll_params(refclk, &clock); -+} -+ -+static void -+i9xx_get_initial_plane_config(struct intel_crtc *crtc, -+ struct intel_initial_plane_config *plane_config) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_plane *plane = to_intel_plane(crtc->base.primary); -+ enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; -+ enum pipe pipe; -+ u32 val, base, offset; -+ int fourcc, pixel_format; -+ unsigned int aligned_height; -+ struct drm_framebuffer *fb; -+ struct intel_framebuffer *intel_fb; -+ -+ if (!plane->get_hw_state(plane, &pipe)) -+ return; -+ -+ WARN_ON(pipe != crtc->pipe); -+ -+ intel_fb = kzalloc(sizeof(*intel_fb), GFP_KERNEL); -+ if (!intel_fb) { -+ DRM_DEBUG_KMS("failed to alloc fb\n"); -+ return; -+ } -+ -+ fb = &intel_fb->base; -+ -+ fb->dev = dev; -+ -+ val = I915_READ(DSPCNTR(i9xx_plane)); -+ -+ if (INTEL_GEN(dev_priv) >= 4) { -+ if (val & DISPPLANE_TILED) { -+ plane_config->tiling = I915_TILING_X; -+ fb->modifier = I915_FORMAT_MOD_X_TILED; -+ } -+ -+ if (val & DISPPLANE_ROTATE_180) -+ plane_config->rotation = DRM_MODE_ROTATE_180; -+ } -+ -+ if (IS_CHERRYVIEW(dev_priv) && pipe == PIPE_B && -+ val & DISPPLANE_MIRROR) -+ plane_config->rotation |= DRM_MODE_REFLECT_X; -+ -+ pixel_format = val & DISPPLANE_PIXFORMAT_MASK; -+ fourcc = i9xx_format_to_fourcc(pixel_format); -+ fb->format = drm_format_info(fourcc); -+ -+ if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) { -+ offset = I915_READ(DSPOFFSET(i9xx_plane)); -+ base = I915_READ(DSPSURF(i9xx_plane)) & 0xfffff000; -+ } else if (INTEL_GEN(dev_priv) >= 4) { -+ if (plane_config->tiling) -+ offset = I915_READ(DSPTILEOFF(i9xx_plane)); -+ else -+ offset = I915_READ(DSPLINOFF(i9xx_plane)); -+ base = I915_READ(DSPSURF(i9xx_plane)) & 0xfffff000; -+ } else { -+ base = I915_READ(DSPADDR(i9xx_plane)); -+ } -+ plane_config->base = base; -+ -+ val = I915_READ(PIPESRC(pipe)); -+ fb->width = ((val >> 16) & 0xfff) + 1; -+ fb->height = ((val >> 0) & 0xfff) + 1; -+ -+ val = I915_READ(DSPSTRIDE(i9xx_plane)); -+ fb->pitches[0] = val & 0xffffffc0; -+ -+ aligned_height = intel_fb_align_height(fb, 0, fb->height); -+ -+ plane_config->size = fb->pitches[0] * aligned_height; -+ -+ DRM_DEBUG_KMS("%s/%s with fb: size=%dx%d@%d, offset=%x, pitch %d, size 0x%x\n", -+ crtc->base.name, plane->base.name, fb->width, fb->height, -+ fb->format->cpp[0] * 8, base, fb->pitches[0], -+ plane_config->size); -+ -+ plane_config->fb = intel_fb; -+} -+ -+static void chv_crtc_clock_get(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int pipe = pipe_config->cpu_transcoder; -+ enum dpio_channel port = vlv_pipe_to_channel(pipe); -+ struct dpll clock; -+ u32 cmn_dw13, pll_dw0, pll_dw1, pll_dw2, pll_dw3; -+ int refclk = 100000; -+ -+ /* In case of DSI, DPLL will not be used */ -+ if ((pipe_config->dpll_hw_state.dpll & DPLL_VCO_ENABLE) == 0) -+ return; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ cmn_dw13 = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW13(port)); -+ pll_dw0 = vlv_dpio_read(dev_priv, pipe, CHV_PLL_DW0(port)); -+ pll_dw1 = vlv_dpio_read(dev_priv, pipe, CHV_PLL_DW1(port)); -+ pll_dw2 = vlv_dpio_read(dev_priv, pipe, CHV_PLL_DW2(port)); -+ pll_dw3 = vlv_dpio_read(dev_priv, pipe, CHV_PLL_DW3(port)); -+ mutex_unlock(&dev_priv->sb_lock); -+ -+ clock.m1 = (pll_dw1 & 0x7) == DPIO_CHV_M1_DIV_BY_2 ? 2 : 0; -+ clock.m2 = (pll_dw0 & 0xff) << 22; -+ if (pll_dw3 & DPIO_CHV_FRAC_DIV_EN) -+ clock.m2 |= pll_dw2 & 0x3fffff; -+ clock.n = (pll_dw1 >> DPIO_CHV_N_DIV_SHIFT) & 0xf; -+ clock.p1 = (cmn_dw13 >> DPIO_CHV_P1_DIV_SHIFT) & 0x7; -+ clock.p2 = (cmn_dw13 >> DPIO_CHV_P2_DIV_SHIFT) & 0x1f; -+ -+ pipe_config->port_clock = chv_calc_dpll_params(refclk, &clock); -+} -+ -+static void intel_get_crtc_ycbcr_config(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum intel_output_format output = INTEL_OUTPUT_FORMAT_RGB; -+ -+ pipe_config->lspcon_downsampling = false; -+ -+ if (IS_BROADWELL(dev_priv) || INTEL_GEN(dev_priv) >= 9) { -+ u32 tmp = I915_READ(PIPEMISC(crtc->pipe)); -+ -+ if (tmp & PIPEMISC_OUTPUT_COLORSPACE_YUV) { -+ bool ycbcr420_enabled = tmp & PIPEMISC_YUV420_ENABLE; -+ bool blend = tmp & PIPEMISC_YUV420_MODE_FULL_BLEND; -+ -+ if (ycbcr420_enabled) { -+ /* We support 4:2:0 in full blend mode only */ -+ if (!blend) -+ output = INTEL_OUTPUT_FORMAT_INVALID; -+ else if (!(IS_GEMINILAKE(dev_priv) || -+ INTEL_GEN(dev_priv) >= 10)) -+ output = INTEL_OUTPUT_FORMAT_INVALID; -+ else -+ output = INTEL_OUTPUT_FORMAT_YCBCR420; -+ } else { -+ /* -+ * Currently there is no interface defined to -+ * check user preference between RGB/YCBCR444 -+ * or YCBCR420. So the only possible case for -+ * YCBCR444 usage is driving YCBCR420 output -+ * with LSPCON, when pipe is configured for -+ * YCBCR444 output and LSPCON takes care of -+ * downsampling it. -+ */ -+ pipe_config->lspcon_downsampling = true; -+ output = INTEL_OUTPUT_FORMAT_YCBCR444; -+ } -+ } -+ } -+ -+ pipe_config->output_format = output; -+} -+ -+static void i9xx_get_pipe_color_config(struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct intel_plane *plane = to_intel_plane(crtc->base.primary); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum i9xx_plane_id i9xx_plane = plane->i9xx_plane; -+ u32 tmp; -+ -+ tmp = I915_READ(DSPCNTR(i9xx_plane)); -+ -+ if (tmp & DISPPLANE_GAMMA_ENABLE) -+ crtc_state->gamma_enable = true; -+ -+ if (!HAS_GMCH(dev_priv) && -+ tmp & DISPPLANE_PIPE_CSC_ENABLE) -+ crtc_state->csc_enable = true; -+} -+ -+static bool i9xx_get_pipe_config(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum intel_display_power_domain power_domain; -+ intel_wakeref_t wakeref; -+ u32 tmp; -+ bool ret; -+ -+ power_domain = POWER_DOMAIN_PIPE(crtc->pipe); -+ wakeref = intel_display_power_get_if_enabled(dev_priv, power_domain); -+ if (!wakeref) -+ return false; -+ -+ pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; -+ pipe_config->cpu_transcoder = (enum transcoder) crtc->pipe; -+ pipe_config->shared_dpll = NULL; -+ -+ ret = false; -+ -+ tmp = I915_READ(PIPECONF(crtc->pipe)); -+ if (!(tmp & PIPECONF_ENABLE)) -+ goto out; -+ -+ if (IS_G4X(dev_priv) || IS_VALLEYVIEW(dev_priv) || -+ IS_CHERRYVIEW(dev_priv)) { -+ switch (tmp & PIPECONF_BPC_MASK) { -+ case PIPECONF_6BPC: -+ pipe_config->pipe_bpp = 18; -+ break; -+ case PIPECONF_8BPC: -+ pipe_config->pipe_bpp = 24; -+ break; -+ case PIPECONF_10BPC: -+ pipe_config->pipe_bpp = 30; -+ break; -+ default: -+ break; -+ } -+ } -+ -+ if ((IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) && -+ (tmp & PIPECONF_COLOR_RANGE_SELECT)) -+ pipe_config->limited_color_range = true; -+ -+ pipe_config->gamma_mode = (tmp & PIPECONF_GAMMA_MODE_MASK_I9XX) >> -+ PIPECONF_GAMMA_MODE_SHIFT; -+ -+ if (IS_CHERRYVIEW(dev_priv)) -+ pipe_config->cgm_mode = I915_READ(CGM_PIPE_MODE(crtc->pipe)); -+ -+ i9xx_get_pipe_color_config(pipe_config); -+ -+ if (INTEL_GEN(dev_priv) < 4) -+ pipe_config->double_wide = tmp & PIPECONF_DOUBLE_WIDE; -+ -+ intel_get_pipe_timings(crtc, pipe_config); -+ intel_get_pipe_src_size(crtc, pipe_config); -+ -+ i9xx_get_pfit_config(crtc, pipe_config); -+ -+ if (INTEL_GEN(dev_priv) >= 4) { -+ /* No way to read it out on pipes B and C */ -+ if (IS_CHERRYVIEW(dev_priv) && crtc->pipe != PIPE_A) -+ tmp = dev_priv->chv_dpll_md[crtc->pipe]; -+ else -+ tmp = I915_READ(DPLL_MD(crtc->pipe)); -+ pipe_config->pixel_multiplier = -+ ((tmp & DPLL_MD_UDI_MULTIPLIER_MASK) -+ >> DPLL_MD_UDI_MULTIPLIER_SHIFT) + 1; -+ pipe_config->dpll_hw_state.dpll_md = tmp; -+ } else if (IS_I945G(dev_priv) || IS_I945GM(dev_priv) || -+ IS_G33(dev_priv) || IS_PINEVIEW(dev_priv)) { -+ tmp = I915_READ(DPLL(crtc->pipe)); -+ pipe_config->pixel_multiplier = -+ ((tmp & SDVO_MULTIPLIER_MASK) -+ >> SDVO_MULTIPLIER_SHIFT_HIRES) + 1; -+ } else { -+ /* Note that on i915G/GM the pixel multiplier is in the sdvo -+ * port and will be fixed up in the encoder->get_config -+ * function. */ -+ pipe_config->pixel_multiplier = 1; -+ } -+ pipe_config->dpll_hw_state.dpll = I915_READ(DPLL(crtc->pipe)); -+ if (!IS_VALLEYVIEW(dev_priv) && !IS_CHERRYVIEW(dev_priv)) { -+ pipe_config->dpll_hw_state.fp0 = I915_READ(FP0(crtc->pipe)); -+ pipe_config->dpll_hw_state.fp1 = I915_READ(FP1(crtc->pipe)); -+ } else { -+ /* Mask out read-only status bits. */ -+ pipe_config->dpll_hw_state.dpll &= ~(DPLL_LOCK_VLV | -+ DPLL_PORTC_READY_MASK | -+ DPLL_PORTB_READY_MASK); -+ } -+ -+ if (IS_CHERRYVIEW(dev_priv)) -+ chv_crtc_clock_get(crtc, pipe_config); -+ else if (IS_VALLEYVIEW(dev_priv)) -+ vlv_crtc_clock_get(crtc, pipe_config); -+ else -+ i9xx_crtc_clock_get(crtc, pipe_config); -+ -+ /* -+ * Normally the dotclock is filled in by the encoder .get_config() -+ * but in case the pipe is enabled w/o any ports we need a sane -+ * default. -+ */ -+ pipe_config->base.adjusted_mode.crtc_clock = -+ pipe_config->port_clock / pipe_config->pixel_multiplier; -+ -+ ret = true; -+ -+out: -+ intel_display_power_put(dev_priv, power_domain, wakeref); -+ -+ return ret; -+} -+ -+static void ironlake_init_pch_refclk(struct drm_i915_private *dev_priv) -+{ -+ struct intel_encoder *encoder; -+ int i; -+ u32 val, final; -+ bool has_lvds = false; -+ bool has_cpu_edp = false; -+ bool has_panel = false; -+ bool has_ck505 = false; -+ bool can_ssc = false; -+ bool using_ssc_source = false; -+ -+ /* We need to take the global config into account */ -+ for_each_intel_encoder(&dev_priv->drm, encoder) { -+ switch (encoder->type) { -+ case INTEL_OUTPUT_LVDS: -+ has_panel = true; -+ has_lvds = true; -+ break; -+ case INTEL_OUTPUT_EDP: -+ has_panel = true; -+ if (encoder->port == PORT_A) -+ has_cpu_edp = true; -+ break; -+ default: -+ break; -+ } -+ } -+ -+ if (HAS_PCH_IBX(dev_priv)) { -+ has_ck505 = dev_priv->vbt.display_clock_mode; -+ can_ssc = has_ck505; -+ } else { -+ has_ck505 = false; -+ can_ssc = true; -+ } -+ -+ /* Check if any DPLLs are using the SSC source */ -+ for (i = 0; i < dev_priv->num_shared_dpll; i++) { -+ u32 temp = I915_READ(PCH_DPLL(i)); -+ -+ if (!(temp & DPLL_VCO_ENABLE)) -+ continue; -+ -+ if ((temp & PLL_REF_INPUT_MASK) == -+ PLLB_REF_INPUT_SPREADSPECTRUMIN) { -+ using_ssc_source = true; -+ break; -+ } -+ } -+ -+ DRM_DEBUG_KMS("has_panel %d has_lvds %d has_ck505 %d using_ssc_source %d\n", -+ has_panel, has_lvds, has_ck505, using_ssc_source); -+ -+ /* Ironlake: try to setup display ref clock before DPLL -+ * enabling. This is only under driver's control after -+ * PCH B stepping, previous chipset stepping should be -+ * ignoring this setting. -+ */ -+ val = I915_READ(PCH_DREF_CONTROL); -+ -+ /* As we must carefully and slowly disable/enable each source in turn, -+ * compute the final state we want first and check if we need to -+ * make any changes at all. -+ */ -+ final = val; -+ final &= ~DREF_NONSPREAD_SOURCE_MASK; -+ if (has_ck505) -+ final |= DREF_NONSPREAD_CK505_ENABLE; -+ else -+ final |= DREF_NONSPREAD_SOURCE_ENABLE; -+ -+ final &= ~DREF_SSC_SOURCE_MASK; -+ final &= ~DREF_CPU_SOURCE_OUTPUT_MASK; -+ final &= ~DREF_SSC1_ENABLE; -+ -+ if (has_panel) { -+ final |= DREF_SSC_SOURCE_ENABLE; -+ -+ if (intel_panel_use_ssc(dev_priv) && can_ssc) -+ final |= DREF_SSC1_ENABLE; -+ -+ if (has_cpu_edp) { -+ if (intel_panel_use_ssc(dev_priv) && can_ssc) -+ final |= DREF_CPU_SOURCE_OUTPUT_DOWNSPREAD; -+ else -+ final |= DREF_CPU_SOURCE_OUTPUT_NONSPREAD; -+ } else -+ final |= DREF_CPU_SOURCE_OUTPUT_DISABLE; -+ } else if (using_ssc_source) { -+ final |= DREF_SSC_SOURCE_ENABLE; -+ final |= DREF_SSC1_ENABLE; -+ } -+ -+ if (final == val) -+ return; -+ -+ /* Always enable nonspread source */ -+ val &= ~DREF_NONSPREAD_SOURCE_MASK; -+ -+ if (has_ck505) -+ val |= DREF_NONSPREAD_CK505_ENABLE; -+ else -+ val |= DREF_NONSPREAD_SOURCE_ENABLE; -+ -+ if (has_panel) { -+ val &= ~DREF_SSC_SOURCE_MASK; -+ val |= DREF_SSC_SOURCE_ENABLE; -+ -+ /* SSC must be turned on before enabling the CPU output */ -+ if (intel_panel_use_ssc(dev_priv) && can_ssc) { -+ DRM_DEBUG_KMS("Using SSC on panel\n"); -+ val |= DREF_SSC1_ENABLE; -+ } else -+ val &= ~DREF_SSC1_ENABLE; -+ -+ /* Get SSC going before enabling the outputs */ -+ I915_WRITE(PCH_DREF_CONTROL, val); -+ POSTING_READ(PCH_DREF_CONTROL); -+ udelay(200); -+ -+ val &= ~DREF_CPU_SOURCE_OUTPUT_MASK; -+ -+ /* Enable CPU source on CPU attached eDP */ -+ if (has_cpu_edp) { -+ if (intel_panel_use_ssc(dev_priv) && can_ssc) { -+ DRM_DEBUG_KMS("Using SSC on eDP\n"); -+ val |= DREF_CPU_SOURCE_OUTPUT_DOWNSPREAD; -+ } else -+ val |= DREF_CPU_SOURCE_OUTPUT_NONSPREAD; -+ } else -+ val |= DREF_CPU_SOURCE_OUTPUT_DISABLE; -+ -+ I915_WRITE(PCH_DREF_CONTROL, val); -+ POSTING_READ(PCH_DREF_CONTROL); -+ udelay(200); -+ } else { -+ DRM_DEBUG_KMS("Disabling CPU source output\n"); -+ -+ val &= ~DREF_CPU_SOURCE_OUTPUT_MASK; -+ -+ /* Turn off CPU output */ -+ val |= DREF_CPU_SOURCE_OUTPUT_DISABLE; -+ -+ I915_WRITE(PCH_DREF_CONTROL, val); -+ POSTING_READ(PCH_DREF_CONTROL); -+ udelay(200); -+ -+ if (!using_ssc_source) { -+ DRM_DEBUG_KMS("Disabling SSC source\n"); -+ -+ /* Turn off the SSC source */ -+ val &= ~DREF_SSC_SOURCE_MASK; -+ val |= DREF_SSC_SOURCE_DISABLE; -+ -+ /* Turn off SSC1 */ -+ val &= ~DREF_SSC1_ENABLE; -+ -+ I915_WRITE(PCH_DREF_CONTROL, val); -+ POSTING_READ(PCH_DREF_CONTROL); -+ udelay(200); -+ } -+ } -+ -+ BUG_ON(val != final); -+} -+ -+static void lpt_reset_fdi_mphy(struct drm_i915_private *dev_priv) -+{ -+ u32 tmp; -+ -+ tmp = I915_READ(SOUTH_CHICKEN2); -+ tmp |= FDI_MPHY_IOSFSB_RESET_CTL; -+ I915_WRITE(SOUTH_CHICKEN2, tmp); -+ -+ if (wait_for_us(I915_READ(SOUTH_CHICKEN2) & -+ FDI_MPHY_IOSFSB_RESET_STATUS, 100)) -+ DRM_ERROR("FDI mPHY reset assert timeout\n"); -+ -+ tmp = I915_READ(SOUTH_CHICKEN2); -+ tmp &= ~FDI_MPHY_IOSFSB_RESET_CTL; -+ I915_WRITE(SOUTH_CHICKEN2, tmp); -+ -+ if (wait_for_us((I915_READ(SOUTH_CHICKEN2) & -+ FDI_MPHY_IOSFSB_RESET_STATUS) == 0, 100)) -+ DRM_ERROR("FDI mPHY reset de-assert timeout\n"); -+} -+ -+/* WaMPhyProgramming:hsw */ -+static void lpt_program_fdi_mphy(struct drm_i915_private *dev_priv) -+{ -+ u32 tmp; -+ -+ tmp = intel_sbi_read(dev_priv, 0x8008, SBI_MPHY); -+ tmp &= ~(0xFF << 24); -+ tmp |= (0x12 << 24); -+ intel_sbi_write(dev_priv, 0x8008, tmp, SBI_MPHY); -+ -+ tmp = intel_sbi_read(dev_priv, 0x2008, SBI_MPHY); -+ tmp |= (1 << 11); -+ intel_sbi_write(dev_priv, 0x2008, tmp, SBI_MPHY); -+ -+ tmp = intel_sbi_read(dev_priv, 0x2108, SBI_MPHY); -+ tmp |= (1 << 11); -+ intel_sbi_write(dev_priv, 0x2108, tmp, SBI_MPHY); -+ -+ tmp = intel_sbi_read(dev_priv, 0x206C, SBI_MPHY); -+ tmp |= (1 << 24) | (1 << 21) | (1 << 18); -+ intel_sbi_write(dev_priv, 0x206C, tmp, SBI_MPHY); -+ -+ tmp = intel_sbi_read(dev_priv, 0x216C, SBI_MPHY); -+ tmp |= (1 << 24) | (1 << 21) | (1 << 18); -+ intel_sbi_write(dev_priv, 0x216C, tmp, SBI_MPHY); -+ -+ tmp = intel_sbi_read(dev_priv, 0x2080, SBI_MPHY); -+ tmp &= ~(7 << 13); -+ tmp |= (5 << 13); -+ intel_sbi_write(dev_priv, 0x2080, tmp, SBI_MPHY); -+ -+ tmp = intel_sbi_read(dev_priv, 0x2180, SBI_MPHY); -+ tmp &= ~(7 << 13); -+ tmp |= (5 << 13); -+ intel_sbi_write(dev_priv, 0x2180, tmp, SBI_MPHY); -+ -+ tmp = intel_sbi_read(dev_priv, 0x208C, SBI_MPHY); -+ tmp &= ~0xFF; -+ tmp |= 0x1C; -+ intel_sbi_write(dev_priv, 0x208C, tmp, SBI_MPHY); -+ -+ tmp = intel_sbi_read(dev_priv, 0x218C, SBI_MPHY); -+ tmp &= ~0xFF; -+ tmp |= 0x1C; -+ intel_sbi_write(dev_priv, 0x218C, tmp, SBI_MPHY); -+ -+ tmp = intel_sbi_read(dev_priv, 0x2098, SBI_MPHY); -+ tmp &= ~(0xFF << 16); -+ tmp |= (0x1C << 16); -+ intel_sbi_write(dev_priv, 0x2098, tmp, SBI_MPHY); -+ -+ tmp = intel_sbi_read(dev_priv, 0x2198, SBI_MPHY); -+ tmp &= ~(0xFF << 16); -+ tmp |= (0x1C << 16); -+ intel_sbi_write(dev_priv, 0x2198, tmp, SBI_MPHY); -+ -+ tmp = intel_sbi_read(dev_priv, 0x20C4, SBI_MPHY); -+ tmp |= (1 << 27); -+ intel_sbi_write(dev_priv, 0x20C4, tmp, SBI_MPHY); -+ -+ tmp = intel_sbi_read(dev_priv, 0x21C4, SBI_MPHY); -+ tmp |= (1 << 27); -+ intel_sbi_write(dev_priv, 0x21C4, tmp, SBI_MPHY); -+ -+ tmp = intel_sbi_read(dev_priv, 0x20EC, SBI_MPHY); -+ tmp &= ~(0xF << 28); -+ tmp |= (4 << 28); -+ intel_sbi_write(dev_priv, 0x20EC, tmp, SBI_MPHY); -+ -+ tmp = intel_sbi_read(dev_priv, 0x21EC, SBI_MPHY); -+ tmp &= ~(0xF << 28); -+ tmp |= (4 << 28); -+ intel_sbi_write(dev_priv, 0x21EC, tmp, SBI_MPHY); -+} -+ -+/* Implements 3 different sequences from BSpec chapter "Display iCLK -+ * Programming" based on the parameters passed: -+ * - Sequence to enable CLKOUT_DP -+ * - Sequence to enable CLKOUT_DP without spread -+ * - Sequence to enable CLKOUT_DP for FDI usage and configure PCH FDI I/O -+ */ -+static void lpt_enable_clkout_dp(struct drm_i915_private *dev_priv, -+ bool with_spread, bool with_fdi) -+{ -+ u32 reg, tmp; -+ -+ if (WARN(with_fdi && !with_spread, "FDI requires downspread\n")) -+ with_spread = true; -+ if (WARN(HAS_PCH_LPT_LP(dev_priv) && -+ with_fdi, "LP PCH doesn't have FDI\n")) -+ with_fdi = false; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ tmp = intel_sbi_read(dev_priv, SBI_SSCCTL, SBI_ICLK); -+ tmp &= ~SBI_SSCCTL_DISABLE; -+ tmp |= SBI_SSCCTL_PATHALT; -+ intel_sbi_write(dev_priv, SBI_SSCCTL, tmp, SBI_ICLK); -+ -+ udelay(24); -+ -+ if (with_spread) { -+ tmp = intel_sbi_read(dev_priv, SBI_SSCCTL, SBI_ICLK); -+ tmp &= ~SBI_SSCCTL_PATHALT; -+ intel_sbi_write(dev_priv, SBI_SSCCTL, tmp, SBI_ICLK); -+ -+ if (with_fdi) { -+ lpt_reset_fdi_mphy(dev_priv); -+ lpt_program_fdi_mphy(dev_priv); -+ } -+ } -+ -+ reg = HAS_PCH_LPT_LP(dev_priv) ? SBI_GEN0 : SBI_DBUFF0; -+ tmp = intel_sbi_read(dev_priv, reg, SBI_ICLK); -+ tmp |= SBI_GEN0_CFG_BUFFENABLE_DISABLE; -+ intel_sbi_write(dev_priv, reg, tmp, SBI_ICLK); -+ -+ mutex_unlock(&dev_priv->sb_lock); -+} -+ -+/* Sequence to disable CLKOUT_DP */ -+static void lpt_disable_clkout_dp(struct drm_i915_private *dev_priv) -+{ -+ u32 reg, tmp; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ reg = HAS_PCH_LPT_LP(dev_priv) ? SBI_GEN0 : SBI_DBUFF0; -+ tmp = intel_sbi_read(dev_priv, reg, SBI_ICLK); -+ tmp &= ~SBI_GEN0_CFG_BUFFENABLE_DISABLE; -+ intel_sbi_write(dev_priv, reg, tmp, SBI_ICLK); -+ -+ tmp = intel_sbi_read(dev_priv, SBI_SSCCTL, SBI_ICLK); -+ if (!(tmp & SBI_SSCCTL_DISABLE)) { -+ if (!(tmp & SBI_SSCCTL_PATHALT)) { -+ tmp |= SBI_SSCCTL_PATHALT; -+ intel_sbi_write(dev_priv, SBI_SSCCTL, tmp, SBI_ICLK); -+ udelay(32); -+ } -+ tmp |= SBI_SSCCTL_DISABLE; -+ intel_sbi_write(dev_priv, SBI_SSCCTL, tmp, SBI_ICLK); -+ } -+ -+ mutex_unlock(&dev_priv->sb_lock); -+} -+ -+#define BEND_IDX(steps) ((50 + (steps)) / 5) -+ -+static const u16 sscdivintphase[] = { -+ [BEND_IDX( 50)] = 0x3B23, -+ [BEND_IDX( 45)] = 0x3B23, -+ [BEND_IDX( 40)] = 0x3C23, -+ [BEND_IDX( 35)] = 0x3C23, -+ [BEND_IDX( 30)] = 0x3D23, -+ [BEND_IDX( 25)] = 0x3D23, -+ [BEND_IDX( 20)] = 0x3E23, -+ [BEND_IDX( 15)] = 0x3E23, -+ [BEND_IDX( 10)] = 0x3F23, -+ [BEND_IDX( 5)] = 0x3F23, -+ [BEND_IDX( 0)] = 0x0025, -+ [BEND_IDX( -5)] = 0x0025, -+ [BEND_IDX(-10)] = 0x0125, -+ [BEND_IDX(-15)] = 0x0125, -+ [BEND_IDX(-20)] = 0x0225, -+ [BEND_IDX(-25)] = 0x0225, -+ [BEND_IDX(-30)] = 0x0325, -+ [BEND_IDX(-35)] = 0x0325, -+ [BEND_IDX(-40)] = 0x0425, -+ [BEND_IDX(-45)] = 0x0425, -+ [BEND_IDX(-50)] = 0x0525, -+}; -+ -+/* -+ * Bend CLKOUT_DP -+ * steps -50 to 50 inclusive, in steps of 5 -+ * < 0 slow down the clock, > 0 speed up the clock, 0 == no bend (135MHz) -+ * change in clock period = -(steps / 10) * 5.787 ps -+ */ -+static void lpt_bend_clkout_dp(struct drm_i915_private *dev_priv, int steps) -+{ -+ u32 tmp; -+ int idx = BEND_IDX(steps); -+ -+ if (WARN_ON(steps % 5 != 0)) -+ return; -+ -+ if (WARN_ON(idx >= ARRAY_SIZE(sscdivintphase))) -+ return; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ if (steps % 10 != 0) -+ tmp = 0xAAAAAAAB; -+ else -+ tmp = 0x00000000; -+ intel_sbi_write(dev_priv, SBI_SSCDITHPHASE, tmp, SBI_ICLK); -+ -+ tmp = intel_sbi_read(dev_priv, SBI_SSCDIVINTPHASE, SBI_ICLK); -+ tmp &= 0xffff0000; -+ tmp |= sscdivintphase[idx]; -+ intel_sbi_write(dev_priv, SBI_SSCDIVINTPHASE, tmp, SBI_ICLK); -+ -+ mutex_unlock(&dev_priv->sb_lock); -+} -+ -+#undef BEND_IDX -+ -+static void lpt_init_pch_refclk(struct drm_i915_private *dev_priv) -+{ -+ struct intel_encoder *encoder; -+ bool has_vga = false; -+ -+ for_each_intel_encoder(&dev_priv->drm, encoder) { -+ switch (encoder->type) { -+ case INTEL_OUTPUT_ANALOG: -+ has_vga = true; -+ break; -+ default: -+ break; -+ } -+ } -+ -+ if (has_vga) { -+ lpt_bend_clkout_dp(dev_priv, 0); -+ lpt_enable_clkout_dp(dev_priv, true, true); -+ } else { -+ lpt_disable_clkout_dp(dev_priv); -+ } -+} -+ -+/* -+ * Initialize reference clocks when the driver loads -+ */ -+void intel_init_pch_refclk(struct drm_i915_private *dev_priv) -+{ -+ if (HAS_PCH_IBX(dev_priv) || HAS_PCH_CPT(dev_priv)) -+ ironlake_init_pch_refclk(dev_priv); -+ else if (HAS_PCH_LPT(dev_priv)) -+ lpt_init_pch_refclk(dev_priv); -+} -+ -+static void ironlake_set_pipeconf(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ u32 val; -+ -+ val = 0; -+ -+ switch (crtc_state->pipe_bpp) { -+ case 18: -+ val |= PIPECONF_6BPC; -+ break; -+ case 24: -+ val |= PIPECONF_8BPC; -+ break; -+ case 30: -+ val |= PIPECONF_10BPC; -+ break; -+ case 36: -+ val |= PIPECONF_12BPC; -+ break; -+ default: -+ /* Case prevented by intel_choose_pipe_bpp_dither. */ -+ BUG(); -+ } -+ -+ if (crtc_state->dither) -+ val |= (PIPECONF_DITHER_EN | PIPECONF_DITHER_TYPE_SP); -+ -+ if (crtc_state->base.adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) -+ val |= PIPECONF_INTERLACED_ILK; -+ else -+ val |= PIPECONF_PROGRESSIVE; -+ -+ if (crtc_state->limited_color_range) -+ val |= PIPECONF_COLOR_RANGE_SELECT; -+ -+ val |= PIPECONF_GAMMA_MODE(crtc_state->gamma_mode); -+ -+ I915_WRITE(PIPECONF(pipe), val); -+ POSTING_READ(PIPECONF(pipe)); -+} -+ -+static void haswell_set_pipeconf(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; -+ u32 val = 0; -+ -+ if (IS_HASWELL(dev_priv) && crtc_state->dither) -+ val |= (PIPECONF_DITHER_EN | PIPECONF_DITHER_TYPE_SP); -+ -+ if (crtc_state->base.adjusted_mode.flags & DRM_MODE_FLAG_INTERLACE) -+ val |= PIPECONF_INTERLACED_ILK; -+ else -+ val |= PIPECONF_PROGRESSIVE; -+ -+ I915_WRITE(PIPECONF(cpu_transcoder), val); -+ POSTING_READ(PIPECONF(cpu_transcoder)); -+} -+ -+static void haswell_set_pipemisc(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(intel_crtc->base.dev); -+ -+ if (IS_BROADWELL(dev_priv) || INTEL_GEN(dev_priv) >= 9) { -+ u32 val = 0; -+ -+ switch (crtc_state->pipe_bpp) { -+ case 18: -+ val |= PIPEMISC_DITHER_6_BPC; -+ break; -+ case 24: -+ val |= PIPEMISC_DITHER_8_BPC; -+ break; -+ case 30: -+ val |= PIPEMISC_DITHER_10_BPC; -+ break; -+ case 36: -+ val |= PIPEMISC_DITHER_12_BPC; -+ break; -+ default: -+ /* Case prevented by pipe_config_set_bpp. */ -+ BUG(); -+ } -+ -+ if (crtc_state->dither) -+ val |= PIPEMISC_DITHER_ENABLE | PIPEMISC_DITHER_TYPE_SP; -+ -+ if (crtc_state->output_format == INTEL_OUTPUT_FORMAT_YCBCR420 || -+ crtc_state->output_format == INTEL_OUTPUT_FORMAT_YCBCR444) -+ val |= PIPEMISC_OUTPUT_COLORSPACE_YUV; -+ -+ if (crtc_state->output_format == INTEL_OUTPUT_FORMAT_YCBCR420) -+ val |= PIPEMISC_YUV420_ENABLE | -+ PIPEMISC_YUV420_MODE_FULL_BLEND; -+ -+ I915_WRITE(PIPEMISC(intel_crtc->pipe), val); -+ } -+} -+ -+int ironlake_get_lanes_required(int target_clock, int link_bw, int bpp) -+{ -+ /* -+ * Account for spread spectrum to avoid -+ * oversubscribing the link. Max center spread -+ * is 2.5%; use 5% for safety's sake. -+ */ -+ u32 bps = target_clock * bpp * 21 / 20; -+ return DIV_ROUND_UP(bps, link_bw * 8); -+} -+ -+static bool ironlake_needs_fb_cb_tune(struct dpll *dpll, int factor) -+{ -+ return i9xx_dpll_compute_m(dpll) < factor * dpll->n; -+} -+ -+static void ironlake_compute_dpll(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state, -+ struct dpll *reduced_clock) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ u32 dpll, fp, fp2; -+ int factor; -+ -+ /* Enable autotuning of the PLL clock (if permissible) */ -+ factor = 21; -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { -+ if ((intel_panel_use_ssc(dev_priv) && -+ dev_priv->vbt.lvds_ssc_freq == 100000) || -+ (HAS_PCH_IBX(dev_priv) && -+ intel_is_dual_link_lvds(dev_priv))) -+ factor = 25; -+ } else if (crtc_state->sdvo_tv_clock) { -+ factor = 20; -+ } -+ -+ fp = i9xx_dpll_compute_fp(&crtc_state->dpll); -+ -+ if (ironlake_needs_fb_cb_tune(&crtc_state->dpll, factor)) -+ fp |= FP_CB_TUNE; -+ -+ if (reduced_clock) { -+ fp2 = i9xx_dpll_compute_fp(reduced_clock); -+ -+ if (reduced_clock->m < factor * reduced_clock->n) -+ fp2 |= FP_CB_TUNE; -+ } else { -+ fp2 = fp; -+ } -+ -+ dpll = 0; -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) -+ dpll |= DPLLB_MODE_LVDS; -+ else -+ dpll |= DPLLB_MODE_DAC_SERIAL; -+ -+ dpll |= (crtc_state->pixel_multiplier - 1) -+ << PLL_REF_SDVO_HDMI_MULTIPLIER_SHIFT; -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_SDVO) || -+ intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) -+ dpll |= DPLL_SDVO_HIGH_SPEED; -+ -+ if (intel_crtc_has_dp_encoder(crtc_state)) -+ dpll |= DPLL_SDVO_HIGH_SPEED; -+ -+ /* -+ * The high speed IO clock is only really required for -+ * SDVO/HDMI/DP, but we also enable it for CRT to make it -+ * possible to share the DPLL between CRT and HDMI. Enabling -+ * the clock needlessly does no real harm, except use up a -+ * bit of power potentially. -+ * -+ * We'll limit this to IVB with 3 pipes, since it has only two -+ * DPLLs and so DPLL sharing is the only way to get three pipes -+ * driving PCH ports at the same time. On SNB we could do this, -+ * and potentially avoid enabling the second DPLL, but it's not -+ * clear if it''s a win or loss power wise. No point in doing -+ * this on ILK at all since it has a fixed DPLL<->pipe mapping. -+ */ -+ if (INTEL_INFO(dev_priv)->num_pipes == 3 && -+ intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG)) -+ dpll |= DPLL_SDVO_HIGH_SPEED; -+ -+ /* compute bitmask from p1 value */ -+ dpll |= (1 << (crtc_state->dpll.p1 - 1)) << DPLL_FPA01_P1_POST_DIV_SHIFT; -+ /* also FPA1 */ -+ dpll |= (1 << (crtc_state->dpll.p1 - 1)) << DPLL_FPA1_P1_POST_DIV_SHIFT; -+ -+ switch (crtc_state->dpll.p2) { -+ case 5: -+ dpll |= DPLL_DAC_SERIAL_P2_CLOCK_DIV_5; -+ break; -+ case 7: -+ dpll |= DPLLB_LVDS_P2_CLOCK_DIV_7; -+ break; -+ case 10: -+ dpll |= DPLL_DAC_SERIAL_P2_CLOCK_DIV_10; -+ break; -+ case 14: -+ dpll |= DPLLB_LVDS_P2_CLOCK_DIV_14; -+ break; -+ } -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS) && -+ intel_panel_use_ssc(dev_priv)) -+ dpll |= PLLB_REF_INPUT_SPREADSPECTRUMIN; -+ else -+ dpll |= PLL_REF_INPUT_DREFCLK; -+ -+ dpll |= DPLL_VCO_ENABLE; -+ -+ crtc_state->dpll_hw_state.dpll = dpll; -+ crtc_state->dpll_hw_state.fp0 = fp; -+ crtc_state->dpll_hw_state.fp1 = fp2; -+} -+ -+static int ironlake_crtc_compute_clock(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ const struct intel_limit *limit; -+ int refclk = 120000; -+ -+ memset(&crtc_state->dpll_hw_state, 0, -+ sizeof(crtc_state->dpll_hw_state)); -+ -+ /* CPU eDP is the only output that doesn't need a PCH PLL of its own. */ -+ if (!crtc_state->has_pch_encoder) -+ return 0; -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_LVDS)) { -+ if (intel_panel_use_ssc(dev_priv)) { -+ DRM_DEBUG_KMS("using SSC reference clock of %d kHz\n", -+ dev_priv->vbt.lvds_ssc_freq); -+ refclk = dev_priv->vbt.lvds_ssc_freq; -+ } -+ -+ if (intel_is_dual_link_lvds(dev_priv)) { -+ if (refclk == 100000) -+ limit = &intel_limits_ironlake_dual_lvds_100m; -+ else -+ limit = &intel_limits_ironlake_dual_lvds; -+ } else { -+ if (refclk == 100000) -+ limit = &intel_limits_ironlake_single_lvds_100m; -+ else -+ limit = &intel_limits_ironlake_single_lvds; -+ } -+ } else { -+ limit = &intel_limits_ironlake_dac; -+ } -+ -+ if (!crtc_state->clock_set && -+ !g4x_find_best_dpll(limit, crtc_state, crtc_state->port_clock, -+ refclk, NULL, &crtc_state->dpll)) { -+ DRM_ERROR("Couldn't find PLL settings for mode!\n"); -+ return -EINVAL; -+ } -+ -+ ironlake_compute_dpll(crtc, crtc_state, NULL); -+ -+ if (!intel_get_shared_dpll(crtc_state, NULL)) { -+ DRM_DEBUG_KMS("failed to find PLL for pipe %c\n", -+ pipe_name(crtc->pipe)); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static void intel_pch_transcoder_get_m_n(struct intel_crtc *crtc, -+ struct intel_link_m_n *m_n) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ enum pipe pipe = crtc->pipe; -+ -+ m_n->link_m = I915_READ(PCH_TRANS_LINK_M1(pipe)); -+ m_n->link_n = I915_READ(PCH_TRANS_LINK_N1(pipe)); -+ m_n->gmch_m = I915_READ(PCH_TRANS_DATA_M1(pipe)) -+ & ~TU_SIZE_MASK; -+ m_n->gmch_n = I915_READ(PCH_TRANS_DATA_N1(pipe)); -+ m_n->tu = ((I915_READ(PCH_TRANS_DATA_M1(pipe)) -+ & TU_SIZE_MASK) >> TU_SIZE_SHIFT) + 1; -+} -+ -+static void intel_cpu_transcoder_get_m_n(struct intel_crtc *crtc, -+ enum transcoder transcoder, -+ struct intel_link_m_n *m_n, -+ struct intel_link_m_n *m2_n2) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ enum pipe pipe = crtc->pipe; -+ -+ if (INTEL_GEN(dev_priv) >= 5) { -+ m_n->link_m = I915_READ(PIPE_LINK_M1(transcoder)); -+ m_n->link_n = I915_READ(PIPE_LINK_N1(transcoder)); -+ m_n->gmch_m = I915_READ(PIPE_DATA_M1(transcoder)) -+ & ~TU_SIZE_MASK; -+ m_n->gmch_n = I915_READ(PIPE_DATA_N1(transcoder)); -+ m_n->tu = ((I915_READ(PIPE_DATA_M1(transcoder)) -+ & TU_SIZE_MASK) >> TU_SIZE_SHIFT) + 1; -+ -+ if (m2_n2 && transcoder_has_m2_n2(dev_priv, transcoder)) { -+ m2_n2->link_m = I915_READ(PIPE_LINK_M2(transcoder)); -+ m2_n2->link_n = I915_READ(PIPE_LINK_N2(transcoder)); -+ m2_n2->gmch_m = I915_READ(PIPE_DATA_M2(transcoder)) -+ & ~TU_SIZE_MASK; -+ m2_n2->gmch_n = I915_READ(PIPE_DATA_N2(transcoder)); -+ m2_n2->tu = ((I915_READ(PIPE_DATA_M2(transcoder)) -+ & TU_SIZE_MASK) >> TU_SIZE_SHIFT) + 1; -+ } -+ } else { -+ m_n->link_m = I915_READ(PIPE_LINK_M_G4X(pipe)); -+ m_n->link_n = I915_READ(PIPE_LINK_N_G4X(pipe)); -+ m_n->gmch_m = I915_READ(PIPE_DATA_M_G4X(pipe)) -+ & ~TU_SIZE_MASK; -+ m_n->gmch_n = I915_READ(PIPE_DATA_N_G4X(pipe)); -+ m_n->tu = ((I915_READ(PIPE_DATA_M_G4X(pipe)) -+ & TU_SIZE_MASK) >> TU_SIZE_SHIFT) + 1; -+ } -+} -+ -+void intel_dp_get_m_n(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ if (pipe_config->has_pch_encoder) -+ intel_pch_transcoder_get_m_n(crtc, &pipe_config->dp_m_n); -+ else -+ intel_cpu_transcoder_get_m_n(crtc, pipe_config->cpu_transcoder, -+ &pipe_config->dp_m_n, -+ &pipe_config->dp_m2_n2); -+} -+ -+static void ironlake_get_fdi_m_n_config(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ intel_cpu_transcoder_get_m_n(crtc, pipe_config->cpu_transcoder, -+ &pipe_config->fdi_m_n, NULL); -+} -+ -+static void skylake_get_pfit_config(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crtc_scaler_state *scaler_state = &pipe_config->scaler_state; -+ u32 ps_ctrl = 0; -+ int id = -1; -+ int i; -+ -+ /* find scaler attached to this pipe */ -+ for (i = 0; i < crtc->num_scalers; i++) { -+ ps_ctrl = I915_READ(SKL_PS_CTRL(crtc->pipe, i)); -+ if (ps_ctrl & PS_SCALER_EN && !(ps_ctrl & PS_PLANE_SEL_MASK)) { -+ id = i; -+ pipe_config->pch_pfit.enabled = true; -+ pipe_config->pch_pfit.pos = I915_READ(SKL_PS_WIN_POS(crtc->pipe, i)); -+ pipe_config->pch_pfit.size = I915_READ(SKL_PS_WIN_SZ(crtc->pipe, i)); -+ scaler_state->scalers[i].in_use = true; -+ break; -+ } -+ } -+ -+ scaler_state->scaler_id = id; -+ if (id >= 0) { -+ scaler_state->scaler_users |= (1 << SKL_CRTC_INDEX); -+ } else { -+ scaler_state->scaler_users &= ~(1 << SKL_CRTC_INDEX); -+ } -+} -+ -+static void -+skylake_get_initial_plane_config(struct intel_crtc *crtc, -+ struct intel_initial_plane_config *plane_config) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_plane *plane = to_intel_plane(crtc->base.primary); -+ enum plane_id plane_id = plane->id; -+ enum pipe pipe; -+ u32 val, base, offset, stride_mult, tiling, alpha; -+ int fourcc, pixel_format; -+ unsigned int aligned_height; -+ struct drm_framebuffer *fb; -+ struct intel_framebuffer *intel_fb; -+ -+ if (!plane->get_hw_state(plane, &pipe)) -+ return; -+ -+ WARN_ON(pipe != crtc->pipe); -+ -+ intel_fb = kzalloc(sizeof(*intel_fb), GFP_KERNEL); -+ if (!intel_fb) { -+ DRM_DEBUG_KMS("failed to alloc fb\n"); -+ return; -+ } -+ -+ fb = &intel_fb->base; -+ -+ fb->dev = dev; -+ -+ val = I915_READ(PLANE_CTL(pipe, plane_id)); -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ pixel_format = val & ICL_PLANE_CTL_FORMAT_MASK; -+ else -+ pixel_format = val & PLANE_CTL_FORMAT_MASK; -+ -+ if (INTEL_GEN(dev_priv) >= 10 || IS_GEMINILAKE(dev_priv)) { -+ alpha = I915_READ(PLANE_COLOR_CTL(pipe, plane_id)); -+ alpha &= PLANE_COLOR_ALPHA_MASK; -+ } else { -+ alpha = val & PLANE_CTL_ALPHA_MASK; -+ } -+ -+ fourcc = skl_format_to_fourcc(pixel_format, -+ val & PLANE_CTL_ORDER_RGBX, alpha); -+ fb->format = drm_format_info(fourcc); -+ -+ tiling = val & PLANE_CTL_TILED_MASK; -+ switch (tiling) { -+ case PLANE_CTL_TILED_LINEAR: -+ fb->modifier = DRM_FORMAT_MOD_LINEAR; -+ break; -+ case PLANE_CTL_TILED_X: -+ plane_config->tiling = I915_TILING_X; -+ fb->modifier = I915_FORMAT_MOD_X_TILED; -+ break; -+ case PLANE_CTL_TILED_Y: -+ plane_config->tiling = I915_TILING_Y; -+ if (val & PLANE_CTL_RENDER_DECOMPRESSION_ENABLE) -+ fb->modifier = I915_FORMAT_MOD_Y_TILED_CCS; -+ else -+ fb->modifier = I915_FORMAT_MOD_Y_TILED; -+ break; -+ case PLANE_CTL_TILED_YF: -+ if (val & PLANE_CTL_RENDER_DECOMPRESSION_ENABLE) -+ fb->modifier = I915_FORMAT_MOD_Yf_TILED_CCS; -+ else -+ fb->modifier = I915_FORMAT_MOD_Yf_TILED; -+ break; -+ default: -+ MISSING_CASE(tiling); -+ goto error; -+ } -+ -+ /* -+ * DRM_MODE_ROTATE_ is counter clockwise to stay compatible with Xrandr -+ * while i915 HW rotation is clockwise, thats why this swapping. -+ */ -+ switch (val & PLANE_CTL_ROTATE_MASK) { -+ case PLANE_CTL_ROTATE_0: -+ plane_config->rotation = DRM_MODE_ROTATE_0; -+ break; -+ case PLANE_CTL_ROTATE_90: -+ plane_config->rotation = DRM_MODE_ROTATE_270; -+ break; -+ case PLANE_CTL_ROTATE_180: -+ plane_config->rotation = DRM_MODE_ROTATE_180; -+ break; -+ case PLANE_CTL_ROTATE_270: -+ plane_config->rotation = DRM_MODE_ROTATE_90; -+ break; -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 10 && -+ val & PLANE_CTL_FLIP_HORIZONTAL) -+ plane_config->rotation |= DRM_MODE_REFLECT_X; -+ -+ base = I915_READ(PLANE_SURF(pipe, plane_id)) & 0xfffff000; -+ plane_config->base = base; -+ -+ offset = I915_READ(PLANE_OFFSET(pipe, plane_id)); -+ -+ val = I915_READ(PLANE_SIZE(pipe, plane_id)); -+ fb->height = ((val >> 16) & 0xfff) + 1; -+ fb->width = ((val >> 0) & 0x1fff) + 1; -+ -+ val = I915_READ(PLANE_STRIDE(pipe, plane_id)); -+ stride_mult = skl_plane_stride_mult(fb, 0, DRM_MODE_ROTATE_0); -+ fb->pitches[0] = (val & 0x3ff) * stride_mult; -+ -+ aligned_height = intel_fb_align_height(fb, 0, fb->height); -+ -+ plane_config->size = fb->pitches[0] * aligned_height; -+ -+ DRM_DEBUG_KMS("%s/%s with fb: size=%dx%d@%d, offset=%x, pitch %d, size 0x%x\n", -+ crtc->base.name, plane->base.name, fb->width, fb->height, -+ fb->format->cpp[0] * 8, base, fb->pitches[0], -+ plane_config->size); -+ -+ plane_config->fb = intel_fb; -+ return; -+ -+error: -+ kfree(intel_fb); -+} -+ -+static void ironlake_get_pfit_config(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ u32 tmp; -+ -+ tmp = I915_READ(PF_CTL(crtc->pipe)); -+ -+ if (tmp & PF_ENABLE) { -+ pipe_config->pch_pfit.enabled = true; -+ pipe_config->pch_pfit.pos = I915_READ(PF_WIN_POS(crtc->pipe)); -+ pipe_config->pch_pfit.size = I915_READ(PF_WIN_SZ(crtc->pipe)); -+ -+ /* We currently do not free assignements of panel fitters on -+ * ivb/hsw (since we don't use the higher upscaling modes which -+ * differentiates them) so just WARN about this case for now. */ -+ if (IS_GEN(dev_priv, 7)) { -+ WARN_ON((tmp & PF_PIPE_SEL_MASK_IVB) != -+ PF_PIPE_SEL_IVB(crtc->pipe)); -+ } -+ } -+} -+ -+static bool ironlake_get_pipe_config(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ enum intel_display_power_domain power_domain; -+ intel_wakeref_t wakeref; -+ u32 tmp; -+ bool ret; -+ -+ power_domain = POWER_DOMAIN_PIPE(crtc->pipe); -+ wakeref = intel_display_power_get_if_enabled(dev_priv, power_domain); -+ if (!wakeref) -+ return false; -+ -+ pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; -+ pipe_config->cpu_transcoder = (enum transcoder) crtc->pipe; -+ pipe_config->shared_dpll = NULL; -+ -+ ret = false; -+ tmp = I915_READ(PIPECONF(crtc->pipe)); -+ if (!(tmp & PIPECONF_ENABLE)) -+ goto out; -+ -+ switch (tmp & PIPECONF_BPC_MASK) { -+ case PIPECONF_6BPC: -+ pipe_config->pipe_bpp = 18; -+ break; -+ case PIPECONF_8BPC: -+ pipe_config->pipe_bpp = 24; -+ break; -+ case PIPECONF_10BPC: -+ pipe_config->pipe_bpp = 30; -+ break; -+ case PIPECONF_12BPC: -+ pipe_config->pipe_bpp = 36; -+ break; -+ default: -+ break; -+ } -+ -+ if (tmp & PIPECONF_COLOR_RANGE_SELECT) -+ pipe_config->limited_color_range = true; -+ -+ pipe_config->gamma_mode = (tmp & PIPECONF_GAMMA_MODE_MASK_ILK) >> -+ PIPECONF_GAMMA_MODE_SHIFT; -+ -+ pipe_config->csc_mode = I915_READ(PIPE_CSC_MODE(crtc->pipe)); -+ -+ i9xx_get_pipe_color_config(pipe_config); -+ -+ if (I915_READ(PCH_TRANSCONF(crtc->pipe)) & TRANS_ENABLE) { -+ struct intel_shared_dpll *pll; -+ enum intel_dpll_id pll_id; -+ -+ pipe_config->has_pch_encoder = true; -+ -+ tmp = I915_READ(FDI_RX_CTL(crtc->pipe)); -+ pipe_config->fdi_lanes = ((FDI_DP_PORT_WIDTH_MASK & tmp) >> -+ FDI_DP_PORT_WIDTH_SHIFT) + 1; -+ -+ ironlake_get_fdi_m_n_config(crtc, pipe_config); -+ -+ if (HAS_PCH_IBX(dev_priv)) { -+ /* -+ * The pipe->pch transcoder and pch transcoder->pll -+ * mapping is fixed. -+ */ -+ pll_id = (enum intel_dpll_id) crtc->pipe; -+ } else { -+ tmp = I915_READ(PCH_DPLL_SEL); -+ if (tmp & TRANS_DPLLB_SEL(crtc->pipe)) -+ pll_id = DPLL_ID_PCH_PLL_B; -+ else -+ pll_id= DPLL_ID_PCH_PLL_A; -+ } -+ -+ pipe_config->shared_dpll = -+ intel_get_shared_dpll_by_id(dev_priv, pll_id); -+ pll = pipe_config->shared_dpll; -+ -+ WARN_ON(!pll->info->funcs->get_hw_state(dev_priv, pll, -+ &pipe_config->dpll_hw_state)); -+ -+ tmp = pipe_config->dpll_hw_state.dpll; -+ pipe_config->pixel_multiplier = -+ ((tmp & PLL_REF_SDVO_HDMI_MULTIPLIER_MASK) -+ >> PLL_REF_SDVO_HDMI_MULTIPLIER_SHIFT) + 1; -+ -+ ironlake_pch_clock_get(crtc, pipe_config); -+ } else { -+ pipe_config->pixel_multiplier = 1; -+ } -+ -+ intel_get_pipe_timings(crtc, pipe_config); -+ intel_get_pipe_src_size(crtc, pipe_config); -+ -+ ironlake_get_pfit_config(crtc, pipe_config); -+ -+ ret = true; -+ -+out: -+ intel_display_power_put(dev_priv, power_domain, wakeref); -+ -+ return ret; -+} -+ -+static void assert_can_disable_lcpll(struct drm_i915_private *dev_priv) -+{ -+ struct drm_device *dev = &dev_priv->drm; -+ struct intel_crtc *crtc; -+ -+ for_each_intel_crtc(dev, crtc) -+ I915_STATE_WARN(crtc->active, "CRTC for pipe %c enabled\n", -+ pipe_name(crtc->pipe)); -+ -+ I915_STATE_WARN(I915_READ(HSW_PWR_WELL_CTL2), -+ "Display power well on\n"); -+ I915_STATE_WARN(I915_READ(SPLL_CTL) & SPLL_PLL_ENABLE, "SPLL enabled\n"); -+ I915_STATE_WARN(I915_READ(WRPLL_CTL(0)) & WRPLL_PLL_ENABLE, "WRPLL1 enabled\n"); -+ I915_STATE_WARN(I915_READ(WRPLL_CTL(1)) & WRPLL_PLL_ENABLE, "WRPLL2 enabled\n"); -+ I915_STATE_WARN(I915_READ(PP_STATUS(0)) & PP_ON, "Panel power on\n"); -+ I915_STATE_WARN(I915_READ(BLC_PWM_CPU_CTL2) & BLM_PWM_ENABLE, -+ "CPU PWM1 enabled\n"); -+ if (IS_HASWELL(dev_priv)) -+ I915_STATE_WARN(I915_READ(HSW_BLC_PWM2_CTL) & BLM_PWM_ENABLE, -+ "CPU PWM2 enabled\n"); -+ I915_STATE_WARN(I915_READ(BLC_PWM_PCH_CTL1) & BLM_PCH_PWM_ENABLE, -+ "PCH PWM1 enabled\n"); -+ I915_STATE_WARN(I915_READ(UTIL_PIN_CTL) & UTIL_PIN_ENABLE, -+ "Utility pin enabled\n"); -+ I915_STATE_WARN(I915_READ(PCH_GTC_CTL) & PCH_GTC_ENABLE, "PCH GTC enabled\n"); -+ -+ /* -+ * In theory we can still leave IRQs enabled, as long as only the HPD -+ * interrupts remain enabled. We used to check for that, but since it's -+ * gen-specific and since we only disable LCPLL after we fully disable -+ * the interrupts, the check below should be enough. -+ */ -+ I915_STATE_WARN(intel_irqs_enabled(dev_priv), "IRQs enabled\n"); -+} -+ -+static u32 hsw_read_dcomp(struct drm_i915_private *dev_priv) -+{ -+ if (IS_HASWELL(dev_priv)) -+ return I915_READ(D_COMP_HSW); -+ else -+ return I915_READ(D_COMP_BDW); -+} -+ -+static void hsw_write_dcomp(struct drm_i915_private *dev_priv, u32 val) -+{ -+ if (IS_HASWELL(dev_priv)) { -+ mutex_lock(&dev_priv->pcu_lock); -+ if (sandybridge_pcode_write(dev_priv, GEN6_PCODE_WRITE_D_COMP, -+ val)) -+ DRM_DEBUG_KMS("Failed to write to D_COMP\n"); -+ mutex_unlock(&dev_priv->pcu_lock); -+ } else { -+ I915_WRITE(D_COMP_BDW, val); -+ POSTING_READ(D_COMP_BDW); -+ } -+} -+ -+/* -+ * This function implements pieces of two sequences from BSpec: -+ * - Sequence for display software to disable LCPLL -+ * - Sequence for display software to allow package C8+ -+ * The steps implemented here are just the steps that actually touch the LCPLL -+ * register. Callers should take care of disabling all the display engine -+ * functions, doing the mode unset, fixing interrupts, etc. -+ */ -+static void hsw_disable_lcpll(struct drm_i915_private *dev_priv, -+ bool switch_to_fclk, bool allow_power_down) -+{ -+ u32 val; -+ -+ assert_can_disable_lcpll(dev_priv); -+ -+ val = I915_READ(LCPLL_CTL); -+ -+ if (switch_to_fclk) { -+ val |= LCPLL_CD_SOURCE_FCLK; -+ I915_WRITE(LCPLL_CTL, val); -+ -+ if (wait_for_us(I915_READ(LCPLL_CTL) & -+ LCPLL_CD_SOURCE_FCLK_DONE, 1)) -+ DRM_ERROR("Switching to FCLK failed\n"); -+ -+ val = I915_READ(LCPLL_CTL); -+ } -+ -+ val |= LCPLL_PLL_DISABLE; -+ I915_WRITE(LCPLL_CTL, val); -+ POSTING_READ(LCPLL_CTL); -+ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ LCPLL_CTL, LCPLL_PLL_LOCK, 0, 1)) -+ DRM_ERROR("LCPLL still locked\n"); -+ -+ val = hsw_read_dcomp(dev_priv); -+ val |= D_COMP_COMP_DISABLE; -+ hsw_write_dcomp(dev_priv, val); -+ ndelay(100); -+ -+ if (wait_for((hsw_read_dcomp(dev_priv) & D_COMP_RCOMP_IN_PROGRESS) == 0, -+ 1)) -+ DRM_ERROR("D_COMP RCOMP still in progress\n"); -+ -+ if (allow_power_down) { -+ val = I915_READ(LCPLL_CTL); -+ val |= LCPLL_POWER_DOWN_ALLOW; -+ I915_WRITE(LCPLL_CTL, val); -+ POSTING_READ(LCPLL_CTL); -+ } -+} -+ -+/* -+ * Fully restores LCPLL, disallowing power down and switching back to LCPLL -+ * source. -+ */ -+static void hsw_restore_lcpll(struct drm_i915_private *dev_priv) -+{ -+ u32 val; -+ -+ val = I915_READ(LCPLL_CTL); -+ -+ if ((val & (LCPLL_PLL_LOCK | LCPLL_PLL_DISABLE | LCPLL_CD_SOURCE_FCLK | -+ LCPLL_POWER_DOWN_ALLOW)) == LCPLL_PLL_LOCK) -+ return; -+ -+ /* -+ * Make sure we're not on PC8 state before disabling PC8, otherwise -+ * we'll hang the machine. To prevent PC8 state, just enable force_wake. -+ */ -+ intel_uncore_forcewake_get(&dev_priv->uncore, FORCEWAKE_ALL); -+ -+ if (val & LCPLL_POWER_DOWN_ALLOW) { -+ val &= ~LCPLL_POWER_DOWN_ALLOW; -+ I915_WRITE(LCPLL_CTL, val); -+ POSTING_READ(LCPLL_CTL); -+ } -+ -+ val = hsw_read_dcomp(dev_priv); -+ val |= D_COMP_COMP_FORCE; -+ val &= ~D_COMP_COMP_DISABLE; -+ hsw_write_dcomp(dev_priv, val); -+ -+ val = I915_READ(LCPLL_CTL); -+ val &= ~LCPLL_PLL_DISABLE; -+ I915_WRITE(LCPLL_CTL, val); -+ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ LCPLL_CTL, LCPLL_PLL_LOCK, LCPLL_PLL_LOCK, -+ 5)) -+ DRM_ERROR("LCPLL not locked yet\n"); -+ -+ if (val & LCPLL_CD_SOURCE_FCLK) { -+ val = I915_READ(LCPLL_CTL); -+ val &= ~LCPLL_CD_SOURCE_FCLK; -+ I915_WRITE(LCPLL_CTL, val); -+ -+ if (wait_for_us((I915_READ(LCPLL_CTL) & -+ LCPLL_CD_SOURCE_FCLK_DONE) == 0, 1)) -+ DRM_ERROR("Switching back to LCPLL failed\n"); -+ } -+ -+ intel_uncore_forcewake_put(&dev_priv->uncore, FORCEWAKE_ALL); -+ -+ intel_update_cdclk(dev_priv); -+ intel_dump_cdclk_state(&dev_priv->cdclk.hw, "Current CDCLK"); -+} -+ -+/* -+ * Package states C8 and deeper are really deep PC states that can only be -+ * reached when all the devices on the system allow it, so even if the graphics -+ * device allows PC8+, it doesn't mean the system will actually get to these -+ * states. Our driver only allows PC8+ when going into runtime PM. -+ * -+ * The requirements for PC8+ are that all the outputs are disabled, the power -+ * well is disabled and most interrupts are disabled, and these are also -+ * requirements for runtime PM. When these conditions are met, we manually do -+ * the other conditions: disable the interrupts, clocks and switch LCPLL refclk -+ * to Fclk. If we're in PC8+ and we get an non-hotplug interrupt, we can hard -+ * hang the machine. -+ * -+ * When we really reach PC8 or deeper states (not just when we allow it) we lose -+ * the state of some registers, so when we come back from PC8+ we need to -+ * restore this state. We don't get into PC8+ if we're not in RC6, so we don't -+ * need to take care of the registers kept by RC6. Notice that this happens even -+ * if we don't put the device in PCI D3 state (which is what currently happens -+ * because of the runtime PM support). -+ * -+ * For more, read "Display Sequences for Package C8" on the hardware -+ * documentation. -+ */ -+void hsw_enable_pc8(struct drm_i915_private *dev_priv) -+{ -+ u32 val; -+ -+ DRM_DEBUG_KMS("Enabling package C8+\n"); -+ -+ if (HAS_PCH_LPT_LP(dev_priv)) { -+ val = I915_READ(SOUTH_DSPCLK_GATE_D); -+ val &= ~PCH_LP_PARTITION_LEVEL_DISABLE; -+ I915_WRITE(SOUTH_DSPCLK_GATE_D, val); -+ } -+ -+ lpt_disable_clkout_dp(dev_priv); -+ hsw_disable_lcpll(dev_priv, true, true); -+} -+ -+void hsw_disable_pc8(struct drm_i915_private *dev_priv) -+{ -+ u32 val; -+ -+ DRM_DEBUG_KMS("Disabling package C8+\n"); -+ -+ hsw_restore_lcpll(dev_priv); -+ lpt_init_pch_refclk(dev_priv); -+ -+ if (HAS_PCH_LPT_LP(dev_priv)) { -+ val = I915_READ(SOUTH_DSPCLK_GATE_D); -+ val |= PCH_LP_PARTITION_LEVEL_DISABLE; -+ I915_WRITE(SOUTH_DSPCLK_GATE_D, val); -+ } -+} -+ -+static int haswell_crtc_compute_clock(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct intel_atomic_state *state = -+ to_intel_atomic_state(crtc_state->base.state); -+ -+ if (!intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI) || -+ INTEL_GEN(dev_priv) >= 11) { -+ struct intel_encoder *encoder = -+ intel_get_crtc_new_encoder(state, crtc_state); -+ -+ if (!intel_get_shared_dpll(crtc_state, encoder)) { -+ DRM_DEBUG_KMS("failed to find PLL for pipe %c\n", -+ pipe_name(crtc->pipe)); -+ return -EINVAL; -+ } -+ } -+ -+ return 0; -+} -+ -+static void cannonlake_get_ddi_pll(struct drm_i915_private *dev_priv, -+ enum port port, -+ struct intel_crtc_state *pipe_config) -+{ -+ enum intel_dpll_id id; -+ u32 temp; -+ -+ temp = I915_READ(DPCLKA_CFGCR0) & DPCLKA_CFGCR0_DDI_CLK_SEL_MASK(port); -+ id = temp >> DPCLKA_CFGCR0_DDI_CLK_SEL_SHIFT(port); -+ -+ if (WARN_ON(id < SKL_DPLL0 || id > SKL_DPLL2)) -+ return; -+ -+ pipe_config->shared_dpll = intel_get_shared_dpll_by_id(dev_priv, id); -+} -+ -+static void icelake_get_ddi_pll(struct drm_i915_private *dev_priv, -+ enum port port, -+ struct intel_crtc_state *pipe_config) -+{ -+ enum intel_dpll_id id; -+ u32 temp; -+ -+ /* TODO: TBT pll not implemented. */ -+ if (intel_port_is_combophy(dev_priv, port)) { -+ temp = I915_READ(DPCLKA_CFGCR0_ICL) & -+ DPCLKA_CFGCR0_DDI_CLK_SEL_MASK(port); -+ id = temp >> DPCLKA_CFGCR0_DDI_CLK_SEL_SHIFT(port); -+ } else if (intel_port_is_tc(dev_priv, port)) { -+ id = icl_tc_port_to_pll_id(intel_port_to_tc(dev_priv, port)); -+ } else { -+ WARN(1, "Invalid port %x\n", port); -+ return; -+ } -+ -+ pipe_config->shared_dpll = intel_get_shared_dpll_by_id(dev_priv, id); -+} -+ -+static void bxt_get_ddi_pll(struct drm_i915_private *dev_priv, -+ enum port port, -+ struct intel_crtc_state *pipe_config) -+{ -+ enum intel_dpll_id id; -+ -+ switch (port) { -+ case PORT_A: -+ id = DPLL_ID_SKL_DPLL0; -+ break; -+ case PORT_B: -+ id = DPLL_ID_SKL_DPLL1; -+ break; -+ case PORT_C: -+ id = DPLL_ID_SKL_DPLL2; -+ break; -+ default: -+ DRM_ERROR("Incorrect port type\n"); -+ return; -+ } -+ -+ pipe_config->shared_dpll = intel_get_shared_dpll_by_id(dev_priv, id); -+} -+ -+static void skylake_get_ddi_pll(struct drm_i915_private *dev_priv, -+ enum port port, -+ struct intel_crtc_state *pipe_config) -+{ -+ enum intel_dpll_id id; -+ u32 temp; -+ -+ temp = I915_READ(DPLL_CTRL2) & DPLL_CTRL2_DDI_CLK_SEL_MASK(port); -+ id = temp >> (port * 3 + 1); -+ -+ if (WARN_ON(id < SKL_DPLL0 || id > SKL_DPLL3)) -+ return; -+ -+ pipe_config->shared_dpll = intel_get_shared_dpll_by_id(dev_priv, id); -+} -+ -+static void haswell_get_ddi_pll(struct drm_i915_private *dev_priv, -+ enum port port, -+ struct intel_crtc_state *pipe_config) -+{ -+ enum intel_dpll_id id; -+ u32 ddi_pll_sel = I915_READ(PORT_CLK_SEL(port)); -+ -+ switch (ddi_pll_sel) { -+ case PORT_CLK_SEL_WRPLL1: -+ id = DPLL_ID_WRPLL1; -+ break; -+ case PORT_CLK_SEL_WRPLL2: -+ id = DPLL_ID_WRPLL2; -+ break; -+ case PORT_CLK_SEL_SPLL: -+ id = DPLL_ID_SPLL; -+ break; -+ case PORT_CLK_SEL_LCPLL_810: -+ id = DPLL_ID_LCPLL_810; -+ break; -+ case PORT_CLK_SEL_LCPLL_1350: -+ id = DPLL_ID_LCPLL_1350; -+ break; -+ case PORT_CLK_SEL_LCPLL_2700: -+ id = DPLL_ID_LCPLL_2700; -+ break; -+ default: -+ MISSING_CASE(ddi_pll_sel); -+ /* fall through */ -+ case PORT_CLK_SEL_NONE: -+ return; -+ } -+ -+ pipe_config->shared_dpll = intel_get_shared_dpll_by_id(dev_priv, id); -+} -+ -+static bool hsw_get_transcoder_state(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config, -+ u64 *power_domain_mask, -+ intel_wakeref_t *wakerefs) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ enum intel_display_power_domain power_domain; -+ unsigned long panel_transcoder_mask = 0; -+ unsigned long enabled_panel_transcoders = 0; -+ enum transcoder panel_transcoder; -+ intel_wakeref_t wf; -+ u32 tmp; -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ panel_transcoder_mask |= -+ BIT(TRANSCODER_DSI_0) | BIT(TRANSCODER_DSI_1); -+ -+ if (HAS_TRANSCODER_EDP(dev_priv)) -+ panel_transcoder_mask |= BIT(TRANSCODER_EDP); -+ -+ /* -+ * The pipe->transcoder mapping is fixed with the exception of the eDP -+ * and DSI transcoders handled below. -+ */ -+ pipe_config->cpu_transcoder = (enum transcoder) crtc->pipe; -+ -+ /* -+ * XXX: Do intel_display_power_get_if_enabled before reading this (for -+ * consistency and less surprising code; it's in always on power). -+ */ -+ for_each_set_bit(panel_transcoder, -+ &panel_transcoder_mask, -+ ARRAY_SIZE(INTEL_INFO(dev_priv)->trans_offsets)) { -+ enum pipe trans_pipe; -+ -+ tmp = I915_READ(TRANS_DDI_FUNC_CTL(panel_transcoder)); -+ if (!(tmp & TRANS_DDI_FUNC_ENABLE)) -+ continue; -+ -+ /* -+ * Log all enabled ones, only use the first one. -+ * -+ * FIXME: This won't work for two separate DSI displays. -+ */ -+ enabled_panel_transcoders |= BIT(panel_transcoder); -+ if (enabled_panel_transcoders != BIT(panel_transcoder)) -+ continue; -+ -+ switch (tmp & TRANS_DDI_EDP_INPUT_MASK) { -+ default: -+ WARN(1, "unknown pipe linked to transcoder %s\n", -+ transcoder_name(panel_transcoder)); -+ /* fall through */ -+ case TRANS_DDI_EDP_INPUT_A_ONOFF: -+ case TRANS_DDI_EDP_INPUT_A_ON: -+ trans_pipe = PIPE_A; -+ break; -+ case TRANS_DDI_EDP_INPUT_B_ONOFF: -+ trans_pipe = PIPE_B; -+ break; -+ case TRANS_DDI_EDP_INPUT_C_ONOFF: -+ trans_pipe = PIPE_C; -+ break; -+ } -+ -+ if (trans_pipe == crtc->pipe) -+ pipe_config->cpu_transcoder = panel_transcoder; -+ } -+ -+ /* -+ * Valid combos: none, eDP, DSI0, DSI1, DSI0+DSI1 -+ */ -+ WARN_ON((enabled_panel_transcoders & BIT(TRANSCODER_EDP)) && -+ enabled_panel_transcoders != BIT(TRANSCODER_EDP)); -+ -+ power_domain = POWER_DOMAIN_TRANSCODER(pipe_config->cpu_transcoder); -+ WARN_ON(*power_domain_mask & BIT_ULL(power_domain)); -+ -+ wf = intel_display_power_get_if_enabled(dev_priv, power_domain); -+ if (!wf) -+ return false; -+ -+ wakerefs[power_domain] = wf; -+ *power_domain_mask |= BIT_ULL(power_domain); -+ -+ tmp = I915_READ(PIPECONF(pipe_config->cpu_transcoder)); -+ -+ return tmp & PIPECONF_ENABLE; -+} -+ -+static bool bxt_get_dsi_transcoder_state(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config, -+ u64 *power_domain_mask, -+ intel_wakeref_t *wakerefs) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ enum intel_display_power_domain power_domain; -+ enum transcoder cpu_transcoder; -+ intel_wakeref_t wf; -+ enum port port; -+ u32 tmp; -+ -+ for_each_port_masked(port, BIT(PORT_A) | BIT(PORT_C)) { -+ if (port == PORT_A) -+ cpu_transcoder = TRANSCODER_DSI_A; -+ else -+ cpu_transcoder = TRANSCODER_DSI_C; -+ -+ power_domain = POWER_DOMAIN_TRANSCODER(cpu_transcoder); -+ WARN_ON(*power_domain_mask & BIT_ULL(power_domain)); -+ -+ wf = intel_display_power_get_if_enabled(dev_priv, power_domain); -+ if (!wf) -+ continue; -+ -+ wakerefs[power_domain] = wf; -+ *power_domain_mask |= BIT_ULL(power_domain); -+ -+ /* -+ * The PLL needs to be enabled with a valid divider -+ * configuration, otherwise accessing DSI registers will hang -+ * the machine. See BSpec North Display Engine -+ * registers/MIPI[BXT]. We can break out here early, since we -+ * need the same DSI PLL to be enabled for both DSI ports. -+ */ -+ if (!bxt_dsi_pll_is_enabled(dev_priv)) -+ break; -+ -+ /* XXX: this works for video mode only */ -+ tmp = I915_READ(BXT_MIPI_PORT_CTRL(port)); -+ if (!(tmp & DPI_ENABLE)) -+ continue; -+ -+ tmp = I915_READ(MIPI_CTRL(port)); -+ if ((tmp & BXT_PIPE_SELECT_MASK) != BXT_PIPE_SELECT(crtc->pipe)) -+ continue; -+ -+ pipe_config->cpu_transcoder = cpu_transcoder; -+ break; -+ } -+ -+ return transcoder_is_dsi(pipe_config->cpu_transcoder); -+} -+ -+static void haswell_get_ddi_port_state(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct intel_shared_dpll *pll; -+ enum port port; -+ u32 tmp; -+ -+ tmp = I915_READ(TRANS_DDI_FUNC_CTL(pipe_config->cpu_transcoder)); -+ -+ port = (tmp & TRANS_DDI_PORT_MASK) >> TRANS_DDI_PORT_SHIFT; -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ icelake_get_ddi_pll(dev_priv, port, pipe_config); -+ else if (IS_CANNONLAKE(dev_priv)) -+ cannonlake_get_ddi_pll(dev_priv, port, pipe_config); -+ else if (IS_GEN9_BC(dev_priv)) -+ skylake_get_ddi_pll(dev_priv, port, pipe_config); -+ else if (IS_GEN9_LP(dev_priv)) -+ bxt_get_ddi_pll(dev_priv, port, pipe_config); -+ else -+ haswell_get_ddi_pll(dev_priv, port, pipe_config); -+ -+ pll = pipe_config->shared_dpll; -+ if (pll) { -+ WARN_ON(!pll->info->funcs->get_hw_state(dev_priv, pll, -+ &pipe_config->dpll_hw_state)); -+ } -+ -+ /* -+ * Haswell has only FDI/PCH transcoder A. It is which is connected to -+ * DDI E. So just check whether this pipe is wired to DDI E and whether -+ * the PCH transcoder is on. -+ */ -+ if (INTEL_GEN(dev_priv) < 9 && -+ (port == PORT_E) && I915_READ(LPT_TRANSCONF) & TRANS_ENABLE) { -+ pipe_config->has_pch_encoder = true; -+ -+ tmp = I915_READ(FDI_RX_CTL(PIPE_A)); -+ pipe_config->fdi_lanes = ((FDI_DP_PORT_WIDTH_MASK & tmp) >> -+ FDI_DP_PORT_WIDTH_SHIFT) + 1; -+ -+ ironlake_get_fdi_m_n_config(crtc, pipe_config); -+ } -+} -+ -+static bool haswell_get_pipe_config(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ intel_wakeref_t wakerefs[POWER_DOMAIN_NUM], wf; -+ enum intel_display_power_domain power_domain; -+ u64 power_domain_mask; -+ bool active; -+ -+ intel_crtc_init_scalers(crtc, pipe_config); -+ -+ power_domain = POWER_DOMAIN_PIPE(crtc->pipe); -+ wf = intel_display_power_get_if_enabled(dev_priv, power_domain); -+ if (!wf) -+ return false; -+ -+ wakerefs[power_domain] = wf; -+ power_domain_mask = BIT_ULL(power_domain); -+ -+ pipe_config->shared_dpll = NULL; -+ -+ active = hsw_get_transcoder_state(crtc, pipe_config, -+ &power_domain_mask, wakerefs); -+ -+ if (IS_GEN9_LP(dev_priv) && -+ bxt_get_dsi_transcoder_state(crtc, pipe_config, -+ &power_domain_mask, wakerefs)) { -+ WARN_ON(active); -+ active = true; -+ } -+ -+ if (!active) -+ goto out; -+ -+ if (!transcoder_is_dsi(pipe_config->cpu_transcoder) || -+ INTEL_GEN(dev_priv) >= 11) { -+ haswell_get_ddi_port_state(crtc, pipe_config); -+ intel_get_pipe_timings(crtc, pipe_config); -+ } -+ -+ intel_get_pipe_src_size(crtc, pipe_config); -+ intel_get_crtc_ycbcr_config(crtc, pipe_config); -+ -+ pipe_config->gamma_mode = I915_READ(GAMMA_MODE(crtc->pipe)); -+ -+ pipe_config->csc_mode = I915_READ(PIPE_CSC_MODE(crtc->pipe)); -+ -+ if (INTEL_GEN(dev_priv) >= 9) { -+ u32 tmp = I915_READ(SKL_BOTTOM_COLOR(crtc->pipe)); -+ -+ if (tmp & SKL_BOTTOM_COLOR_GAMMA_ENABLE) -+ pipe_config->gamma_enable = true; -+ -+ if (tmp & SKL_BOTTOM_COLOR_CSC_ENABLE) -+ pipe_config->csc_enable = true; -+ } else { -+ i9xx_get_pipe_color_config(pipe_config); -+ } -+ -+ power_domain = POWER_DOMAIN_PIPE_PANEL_FITTER(crtc->pipe); -+ WARN_ON(power_domain_mask & BIT_ULL(power_domain)); -+ -+ wf = intel_display_power_get_if_enabled(dev_priv, power_domain); -+ if (wf) { -+ wakerefs[power_domain] = wf; -+ power_domain_mask |= BIT_ULL(power_domain); -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ skylake_get_pfit_config(crtc, pipe_config); -+ else -+ ironlake_get_pfit_config(crtc, pipe_config); -+ } -+ -+ if (hsw_crtc_supports_ips(crtc)) { -+ if (IS_HASWELL(dev_priv)) -+ pipe_config->ips_enabled = I915_READ(IPS_CTL) & IPS_ENABLE; -+ else { -+ /* -+ * We cannot readout IPS state on broadwell, set to -+ * true so we can set it to a defined state on first -+ * commit. -+ */ -+ pipe_config->ips_enabled = true; -+ } -+ } -+ -+ if (pipe_config->cpu_transcoder != TRANSCODER_EDP && -+ !transcoder_is_dsi(pipe_config->cpu_transcoder)) { -+ pipe_config->pixel_multiplier = -+ I915_READ(PIPE_MULT(pipe_config->cpu_transcoder)) + 1; -+ } else { -+ pipe_config->pixel_multiplier = 1; -+ } -+ -+out: -+ for_each_power_domain(power_domain, power_domain_mask) -+ intel_display_power_put(dev_priv, -+ power_domain, wakerefs[power_domain]); -+ -+ return active; -+} -+ -+static u32 intel_cursor_base(const struct intel_plane_state *plane_state) -+{ -+ struct drm_i915_private *dev_priv = -+ to_i915(plane_state->base.plane->dev); -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ const struct drm_i915_gem_object *obj = intel_fb_obj(fb); -+ u32 base; -+ -+ if (INTEL_INFO(dev_priv)->display.cursor_needs_physical) -+ base = obj->phys_handle->busaddr; -+ else -+ base = intel_plane_ggtt_offset(plane_state); -+ -+ base += plane_state->color_plane[0].offset; -+ -+ /* ILK+ do this automagically */ -+ if (HAS_GMCH(dev_priv) && -+ plane_state->base.rotation & DRM_MODE_ROTATE_180) -+ base += (plane_state->base.crtc_h * -+ plane_state->base.crtc_w - 1) * fb->format->cpp[0]; -+ -+ return base; -+} -+ -+static u32 intel_cursor_position(const struct intel_plane_state *plane_state) -+{ -+ int x = plane_state->base.crtc_x; -+ int y = plane_state->base.crtc_y; -+ u32 pos = 0; -+ -+ if (x < 0) { -+ pos |= CURSOR_POS_SIGN << CURSOR_X_SHIFT; -+ x = -x; -+ } -+ pos |= x << CURSOR_X_SHIFT; -+ -+ if (y < 0) { -+ pos |= CURSOR_POS_SIGN << CURSOR_Y_SHIFT; -+ y = -y; -+ } -+ pos |= y << CURSOR_Y_SHIFT; -+ -+ return pos; -+} -+ -+static bool intel_cursor_size_ok(const struct intel_plane_state *plane_state) -+{ -+ const struct drm_mode_config *config = -+ &plane_state->base.plane->dev->mode_config; -+ int width = plane_state->base.crtc_w; -+ int height = plane_state->base.crtc_h; -+ -+ return width > 0 && width <= config->cursor_width && -+ height > 0 && height <= config->cursor_height; -+} -+ -+static int intel_cursor_check_surface(struct intel_plane_state *plane_state) -+{ -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ unsigned int rotation = plane_state->base.rotation; -+ int src_x, src_y; -+ u32 offset; -+ int ret; -+ -+ intel_fill_fb_ggtt_view(&plane_state->view, fb, rotation); -+ plane_state->color_plane[0].stride = intel_fb_pitch(fb, 0, rotation); -+ -+ ret = intel_plane_check_stride(plane_state); -+ if (ret) -+ return ret; -+ -+ src_x = plane_state->base.src_x >> 16; -+ src_y = plane_state->base.src_y >> 16; -+ -+ intel_add_fb_offsets(&src_x, &src_y, plane_state, 0); -+ offset = intel_plane_compute_aligned_offset(&src_x, &src_y, -+ plane_state, 0); -+ -+ if (src_x != 0 || src_y != 0) { -+ DRM_DEBUG_KMS("Arbitrary cursor panning not supported\n"); -+ return -EINVAL; -+ } -+ -+ plane_state->color_plane[0].offset = offset; -+ -+ return 0; -+} -+ -+static int intel_check_cursor(struct intel_crtc_state *crtc_state, -+ struct intel_plane_state *plane_state) -+{ -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ int ret; -+ -+ if (fb && fb->modifier != DRM_FORMAT_MOD_LINEAR) { -+ DRM_DEBUG_KMS("cursor cannot be tiled\n"); -+ return -EINVAL; -+ } -+ -+ ret = drm_atomic_helper_check_plane_state(&plane_state->base, -+ &crtc_state->base, -+ DRM_PLANE_HELPER_NO_SCALING, -+ DRM_PLANE_HELPER_NO_SCALING, -+ true, true); -+ if (ret) -+ return ret; -+ -+ if (!plane_state->base.visible) -+ return 0; -+ -+ ret = intel_plane_check_src_coordinates(plane_state); -+ if (ret) -+ return ret; -+ -+ ret = intel_cursor_check_surface(plane_state); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static unsigned int -+i845_cursor_max_stride(struct intel_plane *plane, -+ u32 pixel_format, u64 modifier, -+ unsigned int rotation) -+{ -+ return 2048; -+} -+ -+static u32 i845_cursor_ctl_crtc(const struct intel_crtc_state *crtc_state) -+{ -+ u32 cntl = 0; -+ -+ if (crtc_state->gamma_enable) -+ cntl |= CURSOR_GAMMA_ENABLE; -+ -+ return cntl; -+} -+ -+static u32 i845_cursor_ctl(const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state) -+{ -+ return CURSOR_ENABLE | -+ CURSOR_FORMAT_ARGB | -+ CURSOR_STRIDE(plane_state->color_plane[0].stride); -+} -+ -+static bool i845_cursor_size_ok(const struct intel_plane_state *plane_state) -+{ -+ int width = plane_state->base.crtc_w; -+ -+ /* -+ * 845g/865g are only limited by the width of their cursors, -+ * the height is arbitrary up to the precision of the register. -+ */ -+ return intel_cursor_size_ok(plane_state) && IS_ALIGNED(width, 64); -+} -+ -+static int i845_check_cursor(struct intel_crtc_state *crtc_state, -+ struct intel_plane_state *plane_state) -+{ -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ int ret; -+ -+ ret = intel_check_cursor(crtc_state, plane_state); -+ if (ret) -+ return ret; -+ -+ /* if we want to turn off the cursor ignore width and height */ -+ if (!fb) -+ return 0; -+ -+ /* Check for which cursor types we support */ -+ if (!i845_cursor_size_ok(plane_state)) { -+ DRM_DEBUG("Cursor dimension %dx%d not supported\n", -+ plane_state->base.crtc_w, -+ plane_state->base.crtc_h); -+ return -EINVAL; -+ } -+ -+ WARN_ON(plane_state->base.visible && -+ plane_state->color_plane[0].stride != fb->pitches[0]); -+ -+ switch (fb->pitches[0]) { -+ case 256: -+ case 512: -+ case 1024: -+ case 2048: -+ break; -+ default: -+ DRM_DEBUG_KMS("Invalid cursor stride (%u)\n", -+ fb->pitches[0]); -+ return -EINVAL; -+ } -+ -+ plane_state->ctl = i845_cursor_ctl(crtc_state, plane_state); -+ -+ return 0; -+} -+ -+static void i845_update_cursor(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(plane->base.dev); -+ u32 cntl = 0, base = 0, pos = 0, size = 0; -+ unsigned long irqflags; -+ -+ if (plane_state && plane_state->base.visible) { -+ unsigned int width = plane_state->base.crtc_w; -+ unsigned int height = plane_state->base.crtc_h; -+ -+ cntl = plane_state->ctl | -+ i845_cursor_ctl_crtc(crtc_state); -+ -+ size = (height << 12) | width; -+ -+ base = intel_cursor_base(plane_state); -+ pos = intel_cursor_position(plane_state); -+ } -+ -+ spin_lock_irqsave(&dev_priv->uncore.lock, irqflags); -+ -+ /* On these chipsets we can only modify the base/size/stride -+ * whilst the cursor is disabled. -+ */ -+ if (plane->cursor.base != base || -+ plane->cursor.size != size || -+ plane->cursor.cntl != cntl) { -+ I915_WRITE_FW(CURCNTR(PIPE_A), 0); -+ I915_WRITE_FW(CURBASE(PIPE_A), base); -+ I915_WRITE_FW(CURSIZE, size); -+ I915_WRITE_FW(CURPOS(PIPE_A), pos); -+ I915_WRITE_FW(CURCNTR(PIPE_A), cntl); -+ -+ plane->cursor.base = base; -+ plane->cursor.size = size; -+ plane->cursor.cntl = cntl; -+ } else { -+ I915_WRITE_FW(CURPOS(PIPE_A), pos); -+ } -+ -+ spin_unlock_irqrestore(&dev_priv->uncore.lock, irqflags); -+} -+ -+static void i845_disable_cursor(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state) -+{ -+ i845_update_cursor(plane, crtc_state, NULL); -+} -+ -+static bool i845_cursor_get_hw_state(struct intel_plane *plane, -+ enum pipe *pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(plane->base.dev); -+ enum intel_display_power_domain power_domain; -+ intel_wakeref_t wakeref; -+ bool ret; -+ -+ power_domain = POWER_DOMAIN_PIPE(PIPE_A); -+ wakeref = intel_display_power_get_if_enabled(dev_priv, power_domain); -+ if (!wakeref) -+ return false; -+ -+ ret = I915_READ(CURCNTR(PIPE_A)) & CURSOR_ENABLE; -+ -+ *pipe = PIPE_A; -+ -+ intel_display_power_put(dev_priv, power_domain, wakeref); -+ -+ return ret; -+} -+ -+static unsigned int -+i9xx_cursor_max_stride(struct intel_plane *plane, -+ u32 pixel_format, u64 modifier, -+ unsigned int rotation) -+{ -+ return plane->base.dev->mode_config.cursor_width * 4; -+} -+ -+static u32 i9xx_cursor_ctl_crtc(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ u32 cntl = 0; -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ return cntl; -+ -+ if (crtc_state->gamma_enable) -+ cntl = MCURSOR_GAMMA_ENABLE; -+ -+ if (crtc_state->csc_enable) -+ cntl |= MCURSOR_PIPE_CSC_ENABLE; -+ -+ if (INTEL_GEN(dev_priv) < 5 && !IS_G4X(dev_priv)) -+ cntl |= MCURSOR_PIPE_SELECT(crtc->pipe); -+ -+ return cntl; -+} -+ -+static u32 i9xx_cursor_ctl(const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state) -+{ -+ struct drm_i915_private *dev_priv = -+ to_i915(plane_state->base.plane->dev); -+ u32 cntl = 0; -+ -+ if (IS_GEN(dev_priv, 6) || IS_IVYBRIDGE(dev_priv)) -+ cntl |= MCURSOR_TRICKLE_FEED_DISABLE; -+ -+ switch (plane_state->base.crtc_w) { -+ case 64: -+ cntl |= MCURSOR_MODE_64_ARGB_AX; -+ break; -+ case 128: -+ cntl |= MCURSOR_MODE_128_ARGB_AX; -+ break; -+ case 256: -+ cntl |= MCURSOR_MODE_256_ARGB_AX; -+ break; -+ default: -+ MISSING_CASE(plane_state->base.crtc_w); -+ return 0; -+ } -+ -+ if (plane_state->base.rotation & DRM_MODE_ROTATE_180) -+ cntl |= MCURSOR_ROTATE_180; -+ -+ return cntl; -+} -+ -+static bool i9xx_cursor_size_ok(const struct intel_plane_state *plane_state) -+{ -+ struct drm_i915_private *dev_priv = -+ to_i915(plane_state->base.plane->dev); -+ int width = plane_state->base.crtc_w; -+ int height = plane_state->base.crtc_h; -+ -+ if (!intel_cursor_size_ok(plane_state)) -+ return false; -+ -+ /* Cursor width is limited to a few power-of-two sizes */ -+ switch (width) { -+ case 256: -+ case 128: -+ case 64: -+ break; -+ default: -+ return false; -+ } -+ -+ /* -+ * IVB+ have CUR_FBC_CTL which allows an arbitrary cursor -+ * height from 8 lines up to the cursor width, when the -+ * cursor is not rotated. Everything else requires square -+ * cursors. -+ */ -+ if (HAS_CUR_FBC(dev_priv) && -+ plane_state->base.rotation & DRM_MODE_ROTATE_0) { -+ if (height < 8 || height > width) -+ return false; -+ } else { -+ if (height != width) -+ return false; -+ } -+ -+ return true; -+} -+ -+static int i9xx_check_cursor(struct intel_crtc_state *crtc_state, -+ struct intel_plane_state *plane_state) -+{ -+ struct intel_plane *plane = to_intel_plane(plane_state->base.plane); -+ struct drm_i915_private *dev_priv = to_i915(plane->base.dev); -+ const struct drm_framebuffer *fb = plane_state->base.fb; -+ enum pipe pipe = plane->pipe; -+ int ret; -+ -+ ret = intel_check_cursor(crtc_state, plane_state); -+ if (ret) -+ return ret; -+ -+ /* if we want to turn off the cursor ignore width and height */ -+ if (!fb) -+ return 0; -+ -+ /* Check for which cursor types we support */ -+ if (!i9xx_cursor_size_ok(plane_state)) { -+ DRM_DEBUG("Cursor dimension %dx%d not supported\n", -+ plane_state->base.crtc_w, -+ plane_state->base.crtc_h); -+ return -EINVAL; -+ } -+ -+ WARN_ON(plane_state->base.visible && -+ plane_state->color_plane[0].stride != fb->pitches[0]); -+ -+ if (fb->pitches[0] != plane_state->base.crtc_w * fb->format->cpp[0]) { -+ DRM_DEBUG_KMS("Invalid cursor stride (%u) (cursor width %d)\n", -+ fb->pitches[0], plane_state->base.crtc_w); -+ return -EINVAL; -+ } -+ -+ /* -+ * There's something wrong with the cursor on CHV pipe C. -+ * If it straddles the left edge of the screen then -+ * moving it away from the edge or disabling it often -+ * results in a pipe underrun, and often that can lead to -+ * dead pipe (constant underrun reported, and it scans -+ * out just a solid color). To recover from that, the -+ * display power well must be turned off and on again. -+ * Refuse the put the cursor into that compromised position. -+ */ -+ if (IS_CHERRYVIEW(dev_priv) && pipe == PIPE_C && -+ plane_state->base.visible && plane_state->base.crtc_x < 0) { -+ DRM_DEBUG_KMS("CHV cursor C not allowed to straddle the left screen edge\n"); -+ return -EINVAL; -+ } -+ -+ plane_state->ctl = i9xx_cursor_ctl(crtc_state, plane_state); -+ -+ return 0; -+} -+ -+static void i9xx_update_cursor(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(plane->base.dev); -+ enum pipe pipe = plane->pipe; -+ u32 cntl = 0, base = 0, pos = 0, fbc_ctl = 0; -+ unsigned long irqflags; -+ -+ if (plane_state && plane_state->base.visible) { -+ cntl = plane_state->ctl | -+ i9xx_cursor_ctl_crtc(crtc_state); -+ -+ if (plane_state->base.crtc_h != plane_state->base.crtc_w) -+ fbc_ctl = CUR_FBC_CTL_EN | (plane_state->base.crtc_h - 1); -+ -+ base = intel_cursor_base(plane_state); -+ pos = intel_cursor_position(plane_state); -+ } -+ -+ spin_lock_irqsave(&dev_priv->uncore.lock, irqflags); -+ -+ /* -+ * On some platforms writing CURCNTR first will also -+ * cause CURPOS to be armed by the CURBASE write. -+ * Without the CURCNTR write the CURPOS write would -+ * arm itself. Thus we always update CURCNTR before -+ * CURPOS. -+ * -+ * On other platforms CURPOS always requires the -+ * CURBASE write to arm the update. Additonally -+ * a write to any of the cursor register will cancel -+ * an already armed cursor update. Thus leaving out -+ * the CURBASE write after CURPOS could lead to a -+ * cursor that doesn't appear to move, or even change -+ * shape. Thus we always write CURBASE. -+ * -+ * The other registers are armed by by the CURBASE write -+ * except when the plane is getting enabled at which time -+ * the CURCNTR write arms the update. -+ */ -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ skl_write_cursor_wm(plane, crtc_state); -+ -+ if (plane->cursor.base != base || -+ plane->cursor.size != fbc_ctl || -+ plane->cursor.cntl != cntl) { -+ if (HAS_CUR_FBC(dev_priv)) -+ I915_WRITE_FW(CUR_FBC_CTL(pipe), fbc_ctl); -+ I915_WRITE_FW(CURCNTR(pipe), cntl); -+ I915_WRITE_FW(CURPOS(pipe), pos); -+ I915_WRITE_FW(CURBASE(pipe), base); -+ -+ plane->cursor.base = base; -+ plane->cursor.size = fbc_ctl; -+ plane->cursor.cntl = cntl; -+ } else { -+ I915_WRITE_FW(CURPOS(pipe), pos); -+ I915_WRITE_FW(CURBASE(pipe), base); -+ } -+ -+ spin_unlock_irqrestore(&dev_priv->uncore.lock, irqflags); -+} -+ -+static void i9xx_disable_cursor(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state) -+{ -+ i9xx_update_cursor(plane, crtc_state, NULL); -+} -+ -+static bool i9xx_cursor_get_hw_state(struct intel_plane *plane, -+ enum pipe *pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(plane->base.dev); -+ enum intel_display_power_domain power_domain; -+ intel_wakeref_t wakeref; -+ bool ret; -+ u32 val; -+ -+ /* -+ * Not 100% correct for planes that can move between pipes, -+ * but that's only the case for gen2-3 which don't have any -+ * display power wells. -+ */ -+ power_domain = POWER_DOMAIN_PIPE(plane->pipe); -+ wakeref = intel_display_power_get_if_enabled(dev_priv, power_domain); -+ if (!wakeref) -+ return false; -+ -+ val = I915_READ(CURCNTR(plane->pipe)); -+ -+ ret = val & MCURSOR_MODE; -+ -+ if (INTEL_GEN(dev_priv) >= 5 || IS_G4X(dev_priv)) -+ *pipe = plane->pipe; -+ else -+ *pipe = (val & MCURSOR_PIPE_SELECT_MASK) >> -+ MCURSOR_PIPE_SELECT_SHIFT; -+ -+ intel_display_power_put(dev_priv, power_domain, wakeref); -+ -+ return ret; -+} -+ -+/* VESA 640x480x72Hz mode to set on the pipe */ -+static const struct drm_display_mode load_detect_mode = { -+ DRM_MODE("640x480", DRM_MODE_TYPE_DEFAULT, 31500, 640, 664, -+ 704, 832, 0, 480, 489, 491, 520, 0, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), -+}; -+ -+struct drm_framebuffer * -+intel_framebuffer_create(struct drm_i915_gem_object *obj, -+ struct drm_mode_fb_cmd2 *mode_cmd) -+{ -+ struct intel_framebuffer *intel_fb; -+ int ret; -+ -+ intel_fb = kzalloc(sizeof(*intel_fb), GFP_KERNEL); -+ if (!intel_fb) -+ return ERR_PTR(-ENOMEM); -+ -+ ret = intel_framebuffer_init(intel_fb, obj, mode_cmd); -+ if (ret) -+ goto err; -+ -+ return &intel_fb->base; -+ -+err: -+ kfree(intel_fb); -+ return ERR_PTR(ret); -+} -+ -+static int intel_modeset_disable_planes(struct drm_atomic_state *state, -+ struct drm_crtc *crtc) -+{ -+ struct drm_plane *plane; -+ struct drm_plane_state *plane_state; -+ int ret, i; -+ -+ ret = drm_atomic_add_affected_planes(state, crtc); -+ if (ret) -+ return ret; -+ -+ for_each_new_plane_in_state(state, plane, plane_state, i) { -+ if (plane_state->crtc != crtc) -+ continue; -+ -+ ret = drm_atomic_set_crtc_for_plane(plane_state, NULL); -+ if (ret) -+ return ret; -+ -+ drm_atomic_set_fb_for_plane(plane_state, NULL); -+ } -+ -+ return 0; -+} -+ -+int intel_get_load_detect_pipe(struct drm_connector *connector, -+ const struct drm_display_mode *mode, -+ struct intel_load_detect_pipe *old, -+ struct drm_modeset_acquire_ctx *ctx) -+{ -+ struct intel_crtc *intel_crtc; -+ struct intel_encoder *intel_encoder = -+ intel_attached_encoder(connector); -+ struct drm_crtc *possible_crtc; -+ struct drm_encoder *encoder = &intel_encoder->base; -+ struct drm_crtc *crtc = NULL; -+ struct drm_device *dev = encoder->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_mode_config *config = &dev->mode_config; -+ struct drm_atomic_state *state = NULL, *restore_state = NULL; -+ struct drm_connector_state *connector_state; -+ struct intel_crtc_state *crtc_state; -+ int ret, i = -1; -+ -+ DRM_DEBUG_KMS("[CONNECTOR:%d:%s], [ENCODER:%d:%s]\n", -+ connector->base.id, connector->name, -+ encoder->base.id, encoder->name); -+ -+ old->restore_state = NULL; -+ -+ WARN_ON(!drm_modeset_is_locked(&config->connection_mutex)); -+ -+ /* -+ * Algorithm gets a little messy: -+ * -+ * - if the connector already has an assigned crtc, use it (but make -+ * sure it's on first) -+ * -+ * - try to find the first unused crtc that can drive this connector, -+ * and use that if we find one -+ */ -+ -+ /* See if we already have a CRTC for this connector */ -+ if (connector->state->crtc) { -+ crtc = connector->state->crtc; -+ -+ ret = drm_modeset_lock(&crtc->mutex, ctx); -+ if (ret) -+ goto fail; -+ -+ /* Make sure the crtc and connector are running */ -+ goto found; -+ } -+ -+ /* Find an unused one (if possible) */ -+ for_each_crtc(dev, possible_crtc) { -+ i++; -+ if (!(encoder->possible_crtcs & (1 << i))) -+ continue; -+ -+ ret = drm_modeset_lock(&possible_crtc->mutex, ctx); -+ if (ret) -+ goto fail; -+ -+ if (possible_crtc->state->enable) { -+ drm_modeset_unlock(&possible_crtc->mutex); -+ continue; -+ } -+ -+ crtc = possible_crtc; -+ break; -+ } -+ -+ /* -+ * If we didn't find an unused CRTC, don't use any. -+ */ -+ if (!crtc) { -+ DRM_DEBUG_KMS("no pipe available for load-detect\n"); -+ ret = -ENODEV; -+ goto fail; -+ } -+ -+found: -+ intel_crtc = to_intel_crtc(crtc); -+ -+ state = drm_atomic_state_alloc(dev); -+ restore_state = drm_atomic_state_alloc(dev); -+ if (!state || !restore_state) { -+ ret = -ENOMEM; -+ goto fail; -+ } -+ -+ state->acquire_ctx = ctx; -+ restore_state->acquire_ctx = ctx; -+ -+ connector_state = drm_atomic_get_connector_state(state, connector); -+ if (IS_ERR(connector_state)) { -+ ret = PTR_ERR(connector_state); -+ goto fail; -+ } -+ -+ ret = drm_atomic_set_crtc_for_connector(connector_state, crtc); -+ if (ret) -+ goto fail; -+ -+ crtc_state = intel_atomic_get_crtc_state(state, intel_crtc); -+ if (IS_ERR(crtc_state)) { -+ ret = PTR_ERR(crtc_state); -+ goto fail; -+ } -+ -+ crtc_state->base.active = crtc_state->base.enable = true; -+ -+ if (!mode) -+ mode = &load_detect_mode; -+ -+ ret = drm_atomic_set_mode_for_crtc(&crtc_state->base, mode); -+ if (ret) -+ goto fail; -+ -+ ret = intel_modeset_disable_planes(state, crtc); -+ if (ret) -+ goto fail; -+ -+ ret = PTR_ERR_OR_ZERO(drm_atomic_get_connector_state(restore_state, connector)); -+ if (!ret) -+ ret = PTR_ERR_OR_ZERO(drm_atomic_get_crtc_state(restore_state, crtc)); -+ if (!ret) -+ ret = drm_atomic_add_affected_planes(restore_state, crtc); -+ if (ret) { -+ DRM_DEBUG_KMS("Failed to create a copy of old state to restore: %i\n", ret); -+ goto fail; -+ } -+ -+ ret = drm_atomic_commit(state); -+ if (ret) { -+ DRM_DEBUG_KMS("failed to set mode on load-detect pipe\n"); -+ goto fail; -+ } -+ -+ old->restore_state = restore_state; -+ drm_atomic_state_put(state); -+ -+ /* let the connector get through one full cycle before testing */ -+ intel_wait_for_vblank(dev_priv, intel_crtc->pipe); -+ return true; -+ -+fail: -+ if (state) { -+ drm_atomic_state_put(state); -+ state = NULL; -+ } -+ if (restore_state) { -+ drm_atomic_state_put(restore_state); -+ restore_state = NULL; -+ } -+ -+ if (ret == -EDEADLK) -+ return ret; -+ -+ return false; -+} -+ -+void intel_release_load_detect_pipe(struct drm_connector *connector, -+ struct intel_load_detect_pipe *old, -+ struct drm_modeset_acquire_ctx *ctx) -+{ -+ struct intel_encoder *intel_encoder = -+ intel_attached_encoder(connector); -+ struct drm_encoder *encoder = &intel_encoder->base; -+ struct drm_atomic_state *state = old->restore_state; -+ int ret; -+ -+ DRM_DEBUG_KMS("[CONNECTOR:%d:%s], [ENCODER:%d:%s]\n", -+ connector->base.id, connector->name, -+ encoder->base.id, encoder->name); -+ -+ if (!state) -+ return; -+ -+ ret = drm_atomic_helper_commit_duplicated_state(state, ctx); -+ if (ret) -+ DRM_DEBUG_KMS("Couldn't release load detect pipe: %i\n", ret); -+ drm_atomic_state_put(state); -+} -+ -+static int i9xx_pll_refclk(struct drm_device *dev, -+ const struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ u32 dpll = pipe_config->dpll_hw_state.dpll; -+ -+ if ((dpll & PLL_REF_INPUT_MASK) == PLLB_REF_INPUT_SPREADSPECTRUMIN) -+ return dev_priv->vbt.lvds_ssc_freq; -+ else if (HAS_PCH_SPLIT(dev_priv)) -+ return 120000; -+ else if (!IS_GEN(dev_priv, 2)) -+ return 96000; -+ else -+ return 48000; -+} -+ -+/* Returns the clock of the currently programmed mode of the given pipe. */ -+static void i9xx_crtc_clock_get(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int pipe = pipe_config->cpu_transcoder; -+ u32 dpll = pipe_config->dpll_hw_state.dpll; -+ u32 fp; -+ struct dpll clock; -+ int port_clock; -+ int refclk = i9xx_pll_refclk(dev, pipe_config); -+ -+ if ((dpll & DISPLAY_RATE_SELECT_FPA1) == 0) -+ fp = pipe_config->dpll_hw_state.fp0; -+ else -+ fp = pipe_config->dpll_hw_state.fp1; -+ -+ clock.m1 = (fp & FP_M1_DIV_MASK) >> FP_M1_DIV_SHIFT; -+ if (IS_PINEVIEW(dev_priv)) { -+ clock.n = ffs((fp & FP_N_PINEVIEW_DIV_MASK) >> FP_N_DIV_SHIFT) - 1; -+ clock.m2 = (fp & FP_M2_PINEVIEW_DIV_MASK) >> FP_M2_DIV_SHIFT; -+ } else { -+ clock.n = (fp & FP_N_DIV_MASK) >> FP_N_DIV_SHIFT; -+ clock.m2 = (fp & FP_M2_DIV_MASK) >> FP_M2_DIV_SHIFT; -+ } -+ -+ if (!IS_GEN(dev_priv, 2)) { -+ if (IS_PINEVIEW(dev_priv)) -+ clock.p1 = ffs((dpll & DPLL_FPA01_P1_POST_DIV_MASK_PINEVIEW) >> -+ DPLL_FPA01_P1_POST_DIV_SHIFT_PINEVIEW); -+ else -+ clock.p1 = ffs((dpll & DPLL_FPA01_P1_POST_DIV_MASK) >> -+ DPLL_FPA01_P1_POST_DIV_SHIFT); -+ -+ switch (dpll & DPLL_MODE_MASK) { -+ case DPLLB_MODE_DAC_SERIAL: -+ clock.p2 = dpll & DPLL_DAC_SERIAL_P2_CLOCK_DIV_5 ? -+ 5 : 10; -+ break; -+ case DPLLB_MODE_LVDS: -+ clock.p2 = dpll & DPLLB_LVDS_P2_CLOCK_DIV_7 ? -+ 7 : 14; -+ break; -+ default: -+ DRM_DEBUG_KMS("Unknown DPLL mode %08x in programmed " -+ "mode\n", (int)(dpll & DPLL_MODE_MASK)); -+ return; -+ } -+ -+ if (IS_PINEVIEW(dev_priv)) -+ port_clock = pnv_calc_dpll_params(refclk, &clock); -+ else -+ port_clock = i9xx_calc_dpll_params(refclk, &clock); -+ } else { -+ u32 lvds = IS_I830(dev_priv) ? 0 : I915_READ(LVDS); -+ bool is_lvds = (pipe == 1) && (lvds & LVDS_PORT_EN); -+ -+ if (is_lvds) { -+ clock.p1 = ffs((dpll & DPLL_FPA01_P1_POST_DIV_MASK_I830_LVDS) >> -+ DPLL_FPA01_P1_POST_DIV_SHIFT); -+ -+ if (lvds & LVDS_CLKB_POWER_UP) -+ clock.p2 = 7; -+ else -+ clock.p2 = 14; -+ } else { -+ if (dpll & PLL_P1_DIVIDE_BY_TWO) -+ clock.p1 = 2; -+ else { -+ clock.p1 = ((dpll & DPLL_FPA01_P1_POST_DIV_MASK_I830) >> -+ DPLL_FPA01_P1_POST_DIV_SHIFT) + 2; -+ } -+ if (dpll & PLL_P2_DIVIDE_BY_4) -+ clock.p2 = 4; -+ else -+ clock.p2 = 2; -+ } -+ -+ port_clock = i9xx_calc_dpll_params(refclk, &clock); -+ } -+ -+ /* -+ * This value includes pixel_multiplier. We will use -+ * port_clock to compute adjusted_mode.crtc_clock in the -+ * encoder's get_config() function. -+ */ -+ pipe_config->port_clock = port_clock; -+} -+ -+int intel_dotclock_calculate(int link_freq, -+ const struct intel_link_m_n *m_n) -+{ -+ /* -+ * The calculation for the data clock is: -+ * pixel_clock = ((m/n)*(link_clock * nr_lanes))/bpp -+ * But we want to avoid losing precison if possible, so: -+ * pixel_clock = ((m * link_clock * nr_lanes)/(n*bpp)) -+ * -+ * and the link clock is simpler: -+ * link_clock = (m * link_clock) / n -+ */ -+ -+ if (!m_n->link_n) -+ return 0; -+ -+ return div_u64(mul_u32_u32(m_n->link_m, link_freq), m_n->link_n); -+} -+ -+static void ironlake_pch_clock_get(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ /* read out port_clock from the DPLL */ -+ i9xx_crtc_clock_get(crtc, pipe_config); -+ -+ /* -+ * In case there is an active pipe without active ports, -+ * we may need some idea for the dotclock anyway. -+ * Calculate one based on the FDI configuration. -+ */ -+ pipe_config->base.adjusted_mode.crtc_clock = -+ intel_dotclock_calculate(intel_fdi_link_freq(dev_priv, pipe_config), -+ &pipe_config->fdi_m_n); -+} -+ -+/* Returns the currently programmed mode of the given encoder. */ -+struct drm_display_mode * -+intel_encoder_current_mode(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crtc_state *crtc_state; -+ struct drm_display_mode *mode; -+ struct intel_crtc *crtc; -+ enum pipe pipe; -+ -+ if (!encoder->get_hw_state(encoder, &pipe)) -+ return NULL; -+ -+ crtc = intel_get_crtc_for_pipe(dev_priv, pipe); -+ -+ mode = kzalloc(sizeof(*mode), GFP_KERNEL); -+ if (!mode) -+ return NULL; -+ -+ crtc_state = kzalloc(sizeof(*crtc_state), GFP_KERNEL); -+ if (!crtc_state) { -+ kfree(mode); -+ return NULL; -+ } -+ -+ crtc_state->base.crtc = &crtc->base; -+ -+ if (!dev_priv->display.get_pipe_config(crtc, crtc_state)) { -+ kfree(crtc_state); -+ kfree(mode); -+ return NULL; -+ } -+ -+ encoder->get_config(encoder, crtc_state); -+ -+ intel_mode_from_pipe_config(mode, crtc_state); -+ -+ kfree(crtc_state); -+ -+ return mode; -+} -+ -+static void intel_crtc_destroy(struct drm_crtc *crtc) -+{ -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ -+ drm_crtc_cleanup(crtc); -+ kfree(intel_crtc); -+} -+ -+/** -+ * intel_wm_need_update - Check whether watermarks need updating -+ * @cur: current plane state -+ * @new: new plane state -+ * -+ * Check current plane state versus the new one to determine whether -+ * watermarks need to be recalculated. -+ * -+ * Returns true or false. -+ */ -+static bool intel_wm_need_update(struct intel_plane_state *cur, -+ struct intel_plane_state *new) -+{ -+ /* Update watermarks on tiling or size changes. */ -+ if (new->base.visible != cur->base.visible) -+ return true; -+ -+ if (!cur->base.fb || !new->base.fb) -+ return false; -+ -+ if (cur->base.fb->modifier != new->base.fb->modifier || -+ cur->base.rotation != new->base.rotation || -+ drm_rect_width(&new->base.src) != drm_rect_width(&cur->base.src) || -+ drm_rect_height(&new->base.src) != drm_rect_height(&cur->base.src) || -+ drm_rect_width(&new->base.dst) != drm_rect_width(&cur->base.dst) || -+ drm_rect_height(&new->base.dst) != drm_rect_height(&cur->base.dst)) -+ return true; -+ -+ return false; -+} -+ -+static bool needs_scaling(const struct intel_plane_state *state) -+{ -+ int src_w = drm_rect_width(&state->base.src) >> 16; -+ int src_h = drm_rect_height(&state->base.src) >> 16; -+ int dst_w = drm_rect_width(&state->base.dst); -+ int dst_h = drm_rect_height(&state->base.dst); -+ -+ return (src_w != dst_w || src_h != dst_h); -+} -+ -+int intel_plane_atomic_calc_changes(const struct intel_crtc_state *old_crtc_state, -+ struct drm_crtc_state *crtc_state, -+ const struct intel_plane_state *old_plane_state, -+ struct drm_plane_state *plane_state) -+{ -+ struct intel_crtc_state *pipe_config = to_intel_crtc_state(crtc_state); -+ struct drm_crtc *crtc = crtc_state->crtc; -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ struct intel_plane *plane = to_intel_plane(plane_state->plane); -+ struct drm_device *dev = crtc->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ bool mode_changed = needs_modeset(crtc_state); -+ bool was_crtc_enabled = old_crtc_state->base.active; -+ bool is_crtc_enabled = crtc_state->active; -+ bool turn_off, turn_on, visible, was_visible; -+ struct drm_framebuffer *fb = plane_state->fb; -+ int ret; -+ -+ if (INTEL_GEN(dev_priv) >= 9 && plane->id != PLANE_CURSOR) { -+ ret = skl_update_scaler_plane( -+ to_intel_crtc_state(crtc_state), -+ to_intel_plane_state(plane_state)); -+ if (ret) -+ return ret; -+ } -+ -+ was_visible = old_plane_state->base.visible; -+ visible = plane_state->visible; -+ -+ if (!was_crtc_enabled && WARN_ON(was_visible)) -+ was_visible = false; -+ -+ /* -+ * Visibility is calculated as if the crtc was on, but -+ * after scaler setup everything depends on it being off -+ * when the crtc isn't active. -+ * -+ * FIXME this is wrong for watermarks. Watermarks should also -+ * be computed as if the pipe would be active. Perhaps move -+ * per-plane wm computation to the .check_plane() hook, and -+ * only combine the results from all planes in the current place? -+ */ -+ if (!is_crtc_enabled) { -+ plane_state->visible = visible = false; -+ to_intel_crtc_state(crtc_state)->active_planes &= ~BIT(plane->id); -+ } -+ -+ if (!was_visible && !visible) -+ return 0; -+ -+ if (fb != old_plane_state->base.fb) -+ pipe_config->fb_changed = true; -+ -+ turn_off = was_visible && (!visible || mode_changed); -+ turn_on = visible && (!was_visible || mode_changed); -+ -+ DRM_DEBUG_ATOMIC("[CRTC:%d:%s] has [PLANE:%d:%s] with fb %i\n", -+ intel_crtc->base.base.id, intel_crtc->base.name, -+ plane->base.base.id, plane->base.name, -+ fb ? fb->base.id : -1); -+ -+ DRM_DEBUG_ATOMIC("[PLANE:%d:%s] visible %i -> %i, off %i, on %i, ms %i\n", -+ plane->base.base.id, plane->base.name, -+ was_visible, visible, -+ turn_off, turn_on, mode_changed); -+ -+ if (turn_on) { -+ if (INTEL_GEN(dev_priv) < 5 && !IS_G4X(dev_priv)) -+ pipe_config->update_wm_pre = true; -+ -+ /* must disable cxsr around plane enable/disable */ -+ if (plane->id != PLANE_CURSOR) -+ pipe_config->disable_cxsr = true; -+ } else if (turn_off) { -+ if (INTEL_GEN(dev_priv) < 5 && !IS_G4X(dev_priv)) -+ pipe_config->update_wm_post = true; -+ -+ /* must disable cxsr around plane enable/disable */ -+ if (plane->id != PLANE_CURSOR) -+ pipe_config->disable_cxsr = true; -+ } else if (intel_wm_need_update(to_intel_plane_state(plane->base.state), -+ to_intel_plane_state(plane_state))) { -+ if (INTEL_GEN(dev_priv) < 5 && !IS_G4X(dev_priv)) { -+ /* FIXME bollocks */ -+ pipe_config->update_wm_pre = true; -+ pipe_config->update_wm_post = true; -+ } -+ } -+ -+ if (visible || was_visible) -+ pipe_config->fb_bits |= plane->frontbuffer_bit; -+ -+ /* -+ * ILK/SNB DVSACNTR/Sprite Enable -+ * IVB SPR_CTL/Sprite Enable -+ * "When in Self Refresh Big FIFO mode, a write to enable the -+ * plane will be internally buffered and delayed while Big FIFO -+ * mode is exiting." -+ * -+ * Which means that enabling the sprite can take an extra frame -+ * when we start in big FIFO mode (LP1+). Thus we need to drop -+ * down to LP0 and wait for vblank in order to make sure the -+ * sprite gets enabled on the next vblank after the register write. -+ * Doing otherwise would risk enabling the sprite one frame after -+ * we've already signalled flip completion. We can resume LP1+ -+ * once the sprite has been enabled. -+ * -+ * -+ * WaCxSRDisabledForSpriteScaling:ivb -+ * IVB SPR_SCALE/Scaling Enable -+ * "Low Power watermarks must be disabled for at least one -+ * frame before enabling sprite scaling, and kept disabled -+ * until sprite scaling is disabled." -+ * -+ * ILK/SNB DVSASCALE/Scaling Enable -+ * "When in Self Refresh Big FIFO mode, scaling enable will be -+ * masked off while Big FIFO mode is exiting." -+ * -+ * Despite the w/a only being listed for IVB we assume that -+ * the ILK/SNB note has similar ramifications, hence we apply -+ * the w/a on all three platforms. -+ * -+ * With experimental results seems this is needed also for primary -+ * plane, not only sprite plane. -+ */ -+ if (plane->id != PLANE_CURSOR && -+ (IS_GEN_RANGE(dev_priv, 5, 6) || -+ IS_IVYBRIDGE(dev_priv)) && -+ (turn_on || (!needs_scaling(old_plane_state) && -+ needs_scaling(to_intel_plane_state(plane_state))))) -+ pipe_config->disable_lp_wm = true; -+ -+ return 0; -+} -+ -+static bool encoders_cloneable(const struct intel_encoder *a, -+ const struct intel_encoder *b) -+{ -+ /* masks could be asymmetric, so check both ways */ -+ return a == b || (a->cloneable & (1 << b->type) && -+ b->cloneable & (1 << a->type)); -+} -+ -+static bool check_single_encoder_cloning(struct drm_atomic_state *state, -+ struct intel_crtc *crtc, -+ struct intel_encoder *encoder) -+{ -+ struct intel_encoder *source_encoder; -+ struct drm_connector *connector; -+ struct drm_connector_state *connector_state; -+ int i; -+ -+ for_each_new_connector_in_state(state, connector, connector_state, i) { -+ if (connector_state->crtc != &crtc->base) -+ continue; -+ -+ source_encoder = -+ to_intel_encoder(connector_state->best_encoder); -+ if (!encoders_cloneable(encoder, source_encoder)) -+ return false; -+ } -+ -+ return true; -+} -+ -+static int icl_add_linked_planes(struct intel_atomic_state *state) -+{ -+ struct intel_plane *plane, *linked; -+ struct intel_plane_state *plane_state, *linked_plane_state; -+ int i; -+ -+ for_each_new_intel_plane_in_state(state, plane, plane_state, i) { -+ linked = plane_state->linked_plane; -+ -+ if (!linked) -+ continue; -+ -+ linked_plane_state = intel_atomic_get_plane_state(state, linked); -+ if (IS_ERR(linked_plane_state)) -+ return PTR_ERR(linked_plane_state); -+ -+ WARN_ON(linked_plane_state->linked_plane != plane); -+ WARN_ON(linked_plane_state->slave == plane_state->slave); -+ } -+ -+ return 0; -+} -+ -+static int icl_check_nv12_planes(struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct intel_atomic_state *state = to_intel_atomic_state(crtc_state->base.state); -+ struct intel_plane *plane, *linked; -+ struct intel_plane_state *plane_state; -+ int i; -+ -+ if (INTEL_GEN(dev_priv) < 11) -+ return 0; -+ -+ /* -+ * Destroy all old plane links and make the slave plane invisible -+ * in the crtc_state->active_planes mask. -+ */ -+ for_each_new_intel_plane_in_state(state, plane, plane_state, i) { -+ if (plane->pipe != crtc->pipe || !plane_state->linked_plane) -+ continue; -+ -+ plane_state->linked_plane = NULL; -+ if (plane_state->slave && !plane_state->base.visible) { -+ crtc_state->active_planes &= ~BIT(plane->id); -+ crtc_state->update_planes |= BIT(plane->id); -+ } -+ -+ plane_state->slave = false; -+ } -+ -+ if (!crtc_state->nv12_planes) -+ return 0; -+ -+ for_each_new_intel_plane_in_state(state, plane, plane_state, i) { -+ struct intel_plane_state *linked_state = NULL; -+ -+ if (plane->pipe != crtc->pipe || -+ !(crtc_state->nv12_planes & BIT(plane->id))) -+ continue; -+ -+ for_each_intel_plane_on_crtc(&dev_priv->drm, crtc, linked) { -+ if (!icl_is_nv12_y_plane(linked->id)) -+ continue; -+ -+ if (crtc_state->active_planes & BIT(linked->id)) -+ continue; -+ -+ linked_state = intel_atomic_get_plane_state(state, linked); -+ if (IS_ERR(linked_state)) -+ return PTR_ERR(linked_state); -+ -+ break; -+ } -+ -+ if (!linked_state) { -+ DRM_DEBUG_KMS("Need %d free Y planes for planar YUV\n", -+ hweight8(crtc_state->nv12_planes)); -+ -+ return -EINVAL; -+ } -+ -+ plane_state->linked_plane = linked; -+ -+ linked_state->slave = true; -+ linked_state->linked_plane = plane; -+ crtc_state->active_planes |= BIT(linked->id); -+ crtc_state->update_planes |= BIT(linked->id); -+ DRM_DEBUG_KMS("Using %s as Y plane for %s\n", linked->base.name, plane->base.name); -+ } -+ -+ return 0; -+} -+ -+static int intel_crtc_atomic_check(struct drm_crtc *crtc, -+ struct drm_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ struct intel_crtc_state *pipe_config = -+ to_intel_crtc_state(crtc_state); -+ int ret; -+ bool mode_changed = needs_modeset(crtc_state); -+ -+ if (INTEL_GEN(dev_priv) < 5 && !IS_G4X(dev_priv) && -+ mode_changed && !crtc_state->active) -+ pipe_config->update_wm_post = true; -+ -+ if (mode_changed && crtc_state->enable && -+ dev_priv->display.crtc_compute_clock && -+ !WARN_ON(pipe_config->shared_dpll)) { -+ ret = dev_priv->display.crtc_compute_clock(intel_crtc, -+ pipe_config); -+ if (ret) -+ return ret; -+ } -+ -+ if (mode_changed || pipe_config->update_pipe || -+ crtc_state->color_mgmt_changed) { -+ ret = intel_color_check(pipe_config); -+ if (ret) -+ return ret; -+ } -+ -+ ret = 0; -+ if (dev_priv->display.compute_pipe_wm) { -+ ret = dev_priv->display.compute_pipe_wm(pipe_config); -+ if (ret) { -+ DRM_DEBUG_KMS("Target pipe watermarks are invalid\n"); -+ return ret; -+ } -+ } -+ -+ if (dev_priv->display.compute_intermediate_wm) { -+ if (WARN_ON(!dev_priv->display.compute_pipe_wm)) -+ return 0; -+ -+ /* -+ * Calculate 'intermediate' watermarks that satisfy both the -+ * old state and the new state. We can program these -+ * immediately. -+ */ -+ ret = dev_priv->display.compute_intermediate_wm(pipe_config); -+ if (ret) { -+ DRM_DEBUG_KMS("No valid intermediate pipe watermarks are possible\n"); -+ return ret; -+ } -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 9) { -+ if (mode_changed || pipe_config->update_pipe) -+ ret = skl_update_scaler_crtc(pipe_config); -+ -+ if (!ret) -+ ret = icl_check_nv12_planes(pipe_config); -+ if (!ret) -+ ret = skl_check_pipe_max_pixel_rate(intel_crtc, -+ pipe_config); -+ if (!ret) -+ ret = intel_atomic_setup_scalers(dev_priv, intel_crtc, -+ pipe_config); -+ } -+ -+ if (HAS_IPS(dev_priv)) -+ pipe_config->ips_enabled = hsw_compute_ips_config(pipe_config); -+ -+ return ret; -+} -+ -+static const struct drm_crtc_helper_funcs intel_helper_funcs = { -+ .atomic_check = intel_crtc_atomic_check, -+}; -+ -+static void intel_modeset_update_connector_atomic_state(struct drm_device *dev) -+{ -+ struct intel_connector *connector; -+ struct drm_connector_list_iter conn_iter; -+ -+ drm_connector_list_iter_begin(dev, &conn_iter); -+ for_each_intel_connector_iter(connector, &conn_iter) { -+ if (connector->base.state->crtc) -+ drm_connector_put(&connector->base); -+ -+ if (connector->base.encoder) { -+ connector->base.state->best_encoder = -+ connector->base.encoder; -+ connector->base.state->crtc = -+ connector->base.encoder->crtc; -+ -+ drm_connector_get(&connector->base); -+ } else { -+ connector->base.state->best_encoder = NULL; -+ connector->base.state->crtc = NULL; -+ } -+ } -+ drm_connector_list_iter_end(&conn_iter); -+} -+ -+static int -+compute_sink_pipe_bpp(const struct drm_connector_state *conn_state, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_connector *connector = conn_state->connector; -+ const struct drm_display_info *info = &connector->display_info; -+ int bpp; -+ -+ switch (conn_state->max_bpc) { -+ case 6 ... 7: -+ bpp = 6 * 3; -+ break; -+ case 8 ... 9: -+ bpp = 8 * 3; -+ break; -+ case 10 ... 11: -+ bpp = 10 * 3; -+ break; -+ case 12: -+ bpp = 12 * 3; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ if (bpp < pipe_config->pipe_bpp) { -+ DRM_DEBUG_KMS("[CONNECTOR:%d:%s] Limiting display bpp to %d instead of " -+ "EDID bpp %d, requested bpp %d, max platform bpp %d\n", -+ connector->base.id, connector->name, -+ bpp, 3 * info->bpc, 3 * conn_state->max_requested_bpc, -+ pipe_config->pipe_bpp); -+ -+ pipe_config->pipe_bpp = bpp; -+ } -+ -+ return 0; -+} -+ -+static int -+compute_baseline_pipe_bpp(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct drm_atomic_state *state = pipe_config->base.state; -+ struct drm_connector *connector; -+ struct drm_connector_state *connector_state; -+ int bpp, i; -+ -+ if ((IS_G4X(dev_priv) || IS_VALLEYVIEW(dev_priv) || -+ IS_CHERRYVIEW(dev_priv))) -+ bpp = 10*3; -+ else if (INTEL_GEN(dev_priv) >= 5) -+ bpp = 12*3; -+ else -+ bpp = 8*3; -+ -+ pipe_config->pipe_bpp = bpp; -+ -+ /* Clamp display bpp to connector max bpp */ -+ for_each_new_connector_in_state(state, connector, connector_state, i) { -+ int ret; -+ -+ if (connector_state->crtc != &crtc->base) -+ continue; -+ -+ ret = compute_sink_pipe_bpp(connector_state, pipe_config); -+ if (ret) -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static void intel_dump_crtc_timings(const struct drm_display_mode *mode) -+{ -+ DRM_DEBUG_KMS("crtc timings: %d %d %d %d %d %d %d %d %d, " -+ "type: 0x%x flags: 0x%x\n", -+ mode->crtc_clock, -+ mode->crtc_hdisplay, mode->crtc_hsync_start, -+ mode->crtc_hsync_end, mode->crtc_htotal, -+ mode->crtc_vdisplay, mode->crtc_vsync_start, -+ mode->crtc_vsync_end, mode->crtc_vtotal, mode->type, mode->flags); -+} -+ -+static inline void -+intel_dump_m_n_config(struct intel_crtc_state *pipe_config, char *id, -+ unsigned int lane_count, struct intel_link_m_n *m_n) -+{ -+ DRM_DEBUG_KMS("%s: lanes: %i; gmch_m: %u, gmch_n: %u, link_m: %u, link_n: %u, tu: %u\n", -+ id, lane_count, -+ m_n->gmch_m, m_n->gmch_n, -+ m_n->link_m, m_n->link_n, m_n->tu); -+} -+ -+static void -+intel_dump_infoframe(struct drm_i915_private *dev_priv, -+ const union hdmi_infoframe *frame) -+{ -+ if ((drm_debug & DRM_UT_KMS) == 0) -+ return; -+ -+ hdmi_infoframe_log(KERN_DEBUG, dev_priv->drm.dev, frame); -+} -+ -+#define OUTPUT_TYPE(x) [INTEL_OUTPUT_ ## x] = #x -+ -+static const char * const output_type_str[] = { -+ OUTPUT_TYPE(UNUSED), -+ OUTPUT_TYPE(ANALOG), -+ OUTPUT_TYPE(DVO), -+ OUTPUT_TYPE(SDVO), -+ OUTPUT_TYPE(LVDS), -+ OUTPUT_TYPE(TVOUT), -+ OUTPUT_TYPE(HDMI), -+ OUTPUT_TYPE(DP), -+ OUTPUT_TYPE(EDP), -+ OUTPUT_TYPE(DSI), -+ OUTPUT_TYPE(DDI), -+ OUTPUT_TYPE(DP_MST), -+}; -+ -+#undef OUTPUT_TYPE -+ -+static void snprintf_output_types(char *buf, size_t len, -+ unsigned int output_types) -+{ -+ char *str = buf; -+ int i; -+ -+ str[0] = '\0'; -+ -+ for (i = 0; i < ARRAY_SIZE(output_type_str); i++) { -+ int r; -+ -+ if ((output_types & BIT(i)) == 0) -+ continue; -+ -+ r = snprintf(str, len, "%s%s", -+ str != buf ? "," : "", output_type_str[i]); -+ if (r >= len) -+ break; -+ str += r; -+ len -= r; -+ -+ output_types &= ~BIT(i); -+ } -+ -+ WARN_ON_ONCE(output_types != 0); -+} -+ -+static const char * const output_format_str[] = { -+ [INTEL_OUTPUT_FORMAT_INVALID] = "Invalid", -+ [INTEL_OUTPUT_FORMAT_RGB] = "RGB", -+ [INTEL_OUTPUT_FORMAT_YCBCR420] = "YCBCR4:2:0", -+ [INTEL_OUTPUT_FORMAT_YCBCR444] = "YCBCR4:4:4", -+}; -+ -+static const char *output_formats(enum intel_output_format format) -+{ -+ if (format >= ARRAY_SIZE(output_format_str)) -+ format = INTEL_OUTPUT_FORMAT_INVALID; -+ return output_format_str[format]; -+} -+ -+static void intel_dump_pipe_config(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config, -+ const char *context) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_plane *plane; -+ struct intel_plane *intel_plane; -+ struct intel_plane_state *state; -+ struct drm_framebuffer *fb; -+ char buf[64]; -+ -+ DRM_DEBUG_KMS("[CRTC:%d:%s]%s\n", -+ crtc->base.base.id, crtc->base.name, context); -+ -+ snprintf_output_types(buf, sizeof(buf), pipe_config->output_types); -+ DRM_DEBUG_KMS("output_types: %s (0x%x)\n", -+ buf, pipe_config->output_types); -+ -+ DRM_DEBUG_KMS("output format: %s\n", -+ output_formats(pipe_config->output_format)); -+ -+ DRM_DEBUG_KMS("cpu_transcoder: %s, pipe bpp: %i, dithering: %i\n", -+ transcoder_name(pipe_config->cpu_transcoder), -+ pipe_config->pipe_bpp, pipe_config->dither); -+ -+ if (pipe_config->has_pch_encoder) -+ intel_dump_m_n_config(pipe_config, "fdi", -+ pipe_config->fdi_lanes, -+ &pipe_config->fdi_m_n); -+ -+ if (intel_crtc_has_dp_encoder(pipe_config)) { -+ intel_dump_m_n_config(pipe_config, "dp m_n", -+ pipe_config->lane_count, &pipe_config->dp_m_n); -+ if (pipe_config->has_drrs) -+ intel_dump_m_n_config(pipe_config, "dp m2_n2", -+ pipe_config->lane_count, -+ &pipe_config->dp_m2_n2); -+ } -+ -+ DRM_DEBUG_KMS("audio: %i, infoframes: %i\n", -+ pipe_config->has_audio, pipe_config->has_infoframe); -+ -+ DRM_DEBUG_KMS("infoframes enabled: 0x%x\n", -+ pipe_config->infoframes.enable); -+ -+ if (pipe_config->infoframes.enable & -+ intel_hdmi_infoframe_enable(HDMI_PACKET_TYPE_GENERAL_CONTROL)) -+ DRM_DEBUG_KMS("GCP: 0x%x\n", pipe_config->infoframes.gcp); -+ if (pipe_config->infoframes.enable & -+ intel_hdmi_infoframe_enable(HDMI_INFOFRAME_TYPE_AVI)) -+ intel_dump_infoframe(dev_priv, &pipe_config->infoframes.avi); -+ if (pipe_config->infoframes.enable & -+ intel_hdmi_infoframe_enable(HDMI_INFOFRAME_TYPE_SPD)) -+ intel_dump_infoframe(dev_priv, &pipe_config->infoframes.spd); -+ if (pipe_config->infoframes.enable & -+ intel_hdmi_infoframe_enable(HDMI_INFOFRAME_TYPE_VENDOR)) -+ intel_dump_infoframe(dev_priv, &pipe_config->infoframes.hdmi); -+ -+ DRM_DEBUG_KMS("requested mode:\n"); -+ drm_mode_debug_printmodeline(&pipe_config->base.mode); -+ DRM_DEBUG_KMS("adjusted mode:\n"); -+ drm_mode_debug_printmodeline(&pipe_config->base.adjusted_mode); -+ intel_dump_crtc_timings(&pipe_config->base.adjusted_mode); -+ DRM_DEBUG_KMS("port clock: %d, pipe src size: %dx%d, pixel rate %d\n", -+ pipe_config->port_clock, -+ pipe_config->pipe_src_w, pipe_config->pipe_src_h, -+ pipe_config->pixel_rate); -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ DRM_DEBUG_KMS("num_scalers: %d, scaler_users: 0x%x, scaler_id: %d\n", -+ crtc->num_scalers, -+ pipe_config->scaler_state.scaler_users, -+ pipe_config->scaler_state.scaler_id); -+ -+ if (HAS_GMCH(dev_priv)) -+ DRM_DEBUG_KMS("gmch pfit: control: 0x%08x, ratios: 0x%08x, lvds border: 0x%08x\n", -+ pipe_config->gmch_pfit.control, -+ pipe_config->gmch_pfit.pgm_ratios, -+ pipe_config->gmch_pfit.lvds_border_bits); -+ else -+ DRM_DEBUG_KMS("pch pfit: pos: 0x%08x, size: 0x%08x, %s\n", -+ pipe_config->pch_pfit.pos, -+ pipe_config->pch_pfit.size, -+ enableddisabled(pipe_config->pch_pfit.enabled)); -+ -+ DRM_DEBUG_KMS("ips: %i, double wide: %i\n", -+ pipe_config->ips_enabled, pipe_config->double_wide); -+ -+ intel_dpll_dump_hw_state(dev_priv, &pipe_config->dpll_hw_state); -+ -+ DRM_DEBUG_KMS("planes on this crtc\n"); -+ list_for_each_entry(plane, &dev->mode_config.plane_list, head) { -+ struct drm_format_name_buf format_name; -+ intel_plane = to_intel_plane(plane); -+ if (intel_plane->pipe != crtc->pipe) -+ continue; -+ -+ state = to_intel_plane_state(plane->state); -+ fb = state->base.fb; -+ if (!fb) { -+ DRM_DEBUG_KMS("[PLANE:%d:%s] disabled, scaler_id = %d\n", -+ plane->base.id, plane->name, state->scaler_id); -+ continue; -+ } -+ -+ DRM_DEBUG_KMS("[PLANE:%d:%s] FB:%d, fb = %ux%u format = %s\n", -+ plane->base.id, plane->name, -+ fb->base.id, fb->width, fb->height, -+ drm_get_format_name(fb->format->format, &format_name)); -+ if (INTEL_GEN(dev_priv) >= 9) -+ DRM_DEBUG_KMS("\tscaler:%d src %dx%d+%d+%d dst %dx%d+%d+%d\n", -+ state->scaler_id, -+ state->base.src.x1 >> 16, -+ state->base.src.y1 >> 16, -+ drm_rect_width(&state->base.src) >> 16, -+ drm_rect_height(&state->base.src) >> 16, -+ state->base.dst.x1, state->base.dst.y1, -+ drm_rect_width(&state->base.dst), -+ drm_rect_height(&state->base.dst)); -+ } -+} -+ -+static bool check_digital_port_conflicts(struct drm_atomic_state *state) -+{ -+ struct drm_device *dev = state->dev; -+ struct drm_connector *connector; -+ struct drm_connector_list_iter conn_iter; -+ unsigned int used_ports = 0; -+ unsigned int used_mst_ports = 0; -+ bool ret = true; -+ -+ /* -+ * Walk the connector list instead of the encoder -+ * list to detect the problem on ddi platforms -+ * where there's just one encoder per digital port. -+ */ -+ drm_connector_list_iter_begin(dev, &conn_iter); -+ drm_for_each_connector_iter(connector, &conn_iter) { -+ struct drm_connector_state *connector_state; -+ struct intel_encoder *encoder; -+ -+ connector_state = drm_atomic_get_new_connector_state(state, connector); -+ if (!connector_state) -+ connector_state = connector->state; -+ -+ if (!connector_state->best_encoder) -+ continue; -+ -+ encoder = to_intel_encoder(connector_state->best_encoder); -+ -+ WARN_ON(!connector_state->crtc); -+ -+ switch (encoder->type) { -+ unsigned int port_mask; -+ case INTEL_OUTPUT_DDI: -+ if (WARN_ON(!HAS_DDI(to_i915(dev)))) -+ break; -+ /* else: fall through */ -+ case INTEL_OUTPUT_DP: -+ case INTEL_OUTPUT_HDMI: -+ case INTEL_OUTPUT_EDP: -+ port_mask = 1 << encoder->port; -+ -+ /* the same port mustn't appear more than once */ -+ if (used_ports & port_mask) -+ ret = false; -+ -+ used_ports |= port_mask; -+ break; -+ case INTEL_OUTPUT_DP_MST: -+ used_mst_ports |= -+ 1 << encoder->port; -+ break; -+ default: -+ break; -+ } -+ } -+ drm_connector_list_iter_end(&conn_iter); -+ -+ /* can't mix MST and SST/HDMI on the same port */ -+ if (used_ports & used_mst_ports) -+ return false; -+ -+ return ret; -+} -+ -+static int -+clear_intel_crtc_state(struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = -+ to_i915(crtc_state->base.crtc->dev); -+ struct intel_crtc_state *saved_state; -+ -+ saved_state = kzalloc(sizeof(*saved_state), GFP_KERNEL); -+ if (!saved_state) -+ return -ENOMEM; -+ -+ /* FIXME: before the switch to atomic started, a new pipe_config was -+ * kzalloc'd. Code that depends on any field being zero should be -+ * fixed, so that the crtc_state can be safely duplicated. For now, -+ * only fields that are know to not cause problems are preserved. */ -+ -+ saved_state->scaler_state = crtc_state->scaler_state; -+ saved_state->shared_dpll = crtc_state->shared_dpll; -+ saved_state->dpll_hw_state = crtc_state->dpll_hw_state; -+ saved_state->pch_pfit.force_thru = crtc_state->pch_pfit.force_thru; -+ saved_state->crc_enabled = crtc_state->crc_enabled; -+ if (IS_G4X(dev_priv) || -+ IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ saved_state->wm = crtc_state->wm; -+ -+ /* Keep base drm_crtc_state intact, only clear our extended struct */ -+ BUILD_BUG_ON(offsetof(struct intel_crtc_state, base)); -+ memcpy(&crtc_state->base + 1, &saved_state->base + 1, -+ sizeof(*crtc_state) - sizeof(crtc_state->base)); -+ -+ kfree(saved_state); -+ return 0; -+} -+ -+static int -+intel_modeset_pipe_config(struct drm_crtc *crtc, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_atomic_state *state = pipe_config->base.state; -+ struct intel_encoder *encoder; -+ struct drm_connector *connector; -+ struct drm_connector_state *connector_state; -+ int base_bpp, ret; -+ int i; -+ bool retry = true; -+ -+ ret = clear_intel_crtc_state(pipe_config); -+ if (ret) -+ return ret; -+ -+ pipe_config->cpu_transcoder = -+ (enum transcoder) to_intel_crtc(crtc)->pipe; -+ -+ /* -+ * Sanitize sync polarity flags based on requested ones. If neither -+ * positive or negative polarity is requested, treat this as meaning -+ * negative polarity. -+ */ -+ if (!(pipe_config->base.adjusted_mode.flags & -+ (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC))) -+ pipe_config->base.adjusted_mode.flags |= DRM_MODE_FLAG_NHSYNC; -+ -+ if (!(pipe_config->base.adjusted_mode.flags & -+ (DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC))) -+ pipe_config->base.adjusted_mode.flags |= DRM_MODE_FLAG_NVSYNC; -+ -+ ret = compute_baseline_pipe_bpp(to_intel_crtc(crtc), -+ pipe_config); -+ if (ret) -+ return ret; -+ -+ base_bpp = pipe_config->pipe_bpp; -+ -+ /* -+ * Determine the real pipe dimensions. Note that stereo modes can -+ * increase the actual pipe size due to the frame doubling and -+ * insertion of additional space for blanks between the frame. This -+ * is stored in the crtc timings. We use the requested mode to do this -+ * computation to clearly distinguish it from the adjusted mode, which -+ * can be changed by the connectors in the below retry loop. -+ */ -+ drm_mode_get_hv_timing(&pipe_config->base.mode, -+ &pipe_config->pipe_src_w, -+ &pipe_config->pipe_src_h); -+ -+ for_each_new_connector_in_state(state, connector, connector_state, i) { -+ if (connector_state->crtc != crtc) -+ continue; -+ -+ encoder = to_intel_encoder(connector_state->best_encoder); -+ -+ if (!check_single_encoder_cloning(state, to_intel_crtc(crtc), encoder)) { -+ DRM_DEBUG_KMS("rejecting invalid cloning configuration\n"); -+ return -EINVAL; -+ } -+ -+ /* -+ * Determine output_types before calling the .compute_config() -+ * hooks so that the hooks can use this information safely. -+ */ -+ if (encoder->compute_output_type) -+ pipe_config->output_types |= -+ BIT(encoder->compute_output_type(encoder, pipe_config, -+ connector_state)); -+ else -+ pipe_config->output_types |= BIT(encoder->type); -+ } -+ -+encoder_retry: -+ /* Ensure the port clock defaults are reset when retrying. */ -+ pipe_config->port_clock = 0; -+ pipe_config->pixel_multiplier = 1; -+ -+ /* Fill in default crtc timings, allow encoders to overwrite them. */ -+ drm_mode_set_crtcinfo(&pipe_config->base.adjusted_mode, -+ CRTC_STEREO_DOUBLE); -+ -+ /* Pass our mode to the connectors and the CRTC to give them a chance to -+ * adjust it according to limitations or connector properties, and also -+ * a chance to reject the mode entirely. -+ */ -+ for_each_new_connector_in_state(state, connector, connector_state, i) { -+ if (connector_state->crtc != crtc) -+ continue; -+ -+ encoder = to_intel_encoder(connector_state->best_encoder); -+ ret = encoder->compute_config(encoder, pipe_config, -+ connector_state); -+ if (ret < 0) { -+ if (ret != -EDEADLK) -+ DRM_DEBUG_KMS("Encoder config failure: %d\n", -+ ret); -+ return ret; -+ } -+ } -+ -+ /* Set default port clock if not overwritten by the encoder. Needs to be -+ * done afterwards in case the encoder adjusts the mode. */ -+ if (!pipe_config->port_clock) -+ pipe_config->port_clock = pipe_config->base.adjusted_mode.crtc_clock -+ * pipe_config->pixel_multiplier; -+ -+ ret = intel_crtc_compute_config(to_intel_crtc(crtc), pipe_config); -+ if (ret == -EDEADLK) -+ return ret; -+ if (ret < 0) { -+ DRM_DEBUG_KMS("CRTC fixup failed\n"); -+ return ret; -+ } -+ -+ if (ret == RETRY) { -+ if (WARN(!retry, "loop in pipe configuration computation\n")) -+ return -EINVAL; -+ -+ DRM_DEBUG_KMS("CRTC bw constrained, retrying\n"); -+ retry = false; -+ goto encoder_retry; -+ } -+ -+ /* Dithering seems to not pass-through bits correctly when it should, so -+ * only enable it on 6bpc panels and when its not a compliance -+ * test requesting 6bpc video pattern. -+ */ -+ pipe_config->dither = (pipe_config->pipe_bpp == 6*3) && -+ !pipe_config->dither_force_disable; -+ DRM_DEBUG_KMS("hw max bpp: %i, pipe bpp: %i, dithering: %i\n", -+ base_bpp, pipe_config->pipe_bpp, pipe_config->dither); -+ -+ return 0; -+} -+ -+bool intel_fuzzy_clock_check(int clock1, int clock2) -+{ -+ int diff; -+ -+ if (clock1 == clock2) -+ return true; -+ -+ if (!clock1 || !clock2) -+ return false; -+ -+ diff = abs(clock1 - clock2); -+ -+ if (((((diff + clock1 + clock2) * 100)) / (clock1 + clock2)) < 105) -+ return true; -+ -+ return false; -+} -+ -+static bool -+intel_compare_m_n(unsigned int m, unsigned int n, -+ unsigned int m2, unsigned int n2, -+ bool exact) -+{ -+ if (m == m2 && n == n2) -+ return true; -+ -+ if (exact || !m || !n || !m2 || !n2) -+ return false; -+ -+ BUILD_BUG_ON(DATA_LINK_M_N_MASK > INT_MAX); -+ -+ if (n > n2) { -+ while (n > n2) { -+ m2 <<= 1; -+ n2 <<= 1; -+ } -+ } else if (n < n2) { -+ while (n < n2) { -+ m <<= 1; -+ n <<= 1; -+ } -+ } -+ -+ if (n != n2) -+ return false; -+ -+ return intel_fuzzy_clock_check(m, m2); -+} -+ -+static bool -+intel_compare_link_m_n(const struct intel_link_m_n *m_n, -+ struct intel_link_m_n *m2_n2, -+ bool adjust) -+{ -+ if (m_n->tu == m2_n2->tu && -+ intel_compare_m_n(m_n->gmch_m, m_n->gmch_n, -+ m2_n2->gmch_m, m2_n2->gmch_n, !adjust) && -+ intel_compare_m_n(m_n->link_m, m_n->link_n, -+ m2_n2->link_m, m2_n2->link_n, !adjust)) { -+ return true; -+ } -+ -+ return false; -+} -+ -+static bool -+intel_compare_infoframe(const union hdmi_infoframe *a, -+ const union hdmi_infoframe *b) -+{ -+ return memcmp(a, b, sizeof(*a)) == 0; -+} -+ -+static void -+pipe_config_infoframe_err(struct drm_i915_private *dev_priv, -+ bool adjust, const char *name, -+ const union hdmi_infoframe *a, -+ const union hdmi_infoframe *b) -+{ -+ if (adjust) { -+ if ((drm_debug & DRM_UT_KMS) == 0) -+ return; -+ -+ drm_dbg(DRM_UT_KMS, "mismatch in %s infoframe", name); -+ drm_dbg(DRM_UT_KMS, "expected:"); -+ hdmi_infoframe_log(KERN_DEBUG, dev_priv->drm.dev, a); -+ drm_dbg(DRM_UT_KMS, "found"); -+ hdmi_infoframe_log(KERN_DEBUG, dev_priv->drm.dev, b); -+ } else { -+ drm_err("mismatch in %s infoframe", name); -+ drm_err("expected:"); -+ hdmi_infoframe_log(KERN_ERR, dev_priv->drm.dev, a); -+ drm_err("found"); -+ hdmi_infoframe_log(KERN_ERR, dev_priv->drm.dev, b); -+ } -+} -+ -+static void __printf(3, 4) -+pipe_config_err(bool adjust, const char *name, const char *format, ...) -+{ -+ struct va_format vaf; -+ va_list args; -+ -+ va_start(args, format); -+ vaf.fmt = format; -+ vaf.va = &args; -+ -+ if (adjust) -+ drm_dbg(DRM_UT_KMS, "mismatch in %s %pV", name, &vaf); -+ else -+ drm_err("mismatch in %s %pV", name, &vaf); -+ -+ va_end(args); -+} -+ -+static bool fastboot_enabled(struct drm_i915_private *dev_priv) -+{ -+ if (i915_modparams.fastboot != -1) -+ return i915_modparams.fastboot; -+ -+ /* Enable fastboot by default on Skylake and newer */ -+ if (INTEL_GEN(dev_priv) >= 9) -+ return true; -+ -+ /* Enable fastboot by default on VLV and CHV */ -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ return true; -+ -+ /* Disabled by default on all others */ -+ return false; -+} -+ -+static bool -+intel_pipe_config_compare(struct drm_i915_private *dev_priv, -+ struct intel_crtc_state *current_config, -+ struct intel_crtc_state *pipe_config, -+ bool adjust) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(current_config->base.crtc); -+ bool ret = true; -+ bool fixup_inherited = adjust && -+ (current_config->base.mode.private_flags & I915_MODE_FLAG_INHERITED) && -+ !(pipe_config->base.mode.private_flags & I915_MODE_FLAG_INHERITED); -+ -+ if (fixup_inherited && !fastboot_enabled(dev_priv)) { -+ DRM_DEBUG_KMS("initial modeset and fastboot not set\n"); -+ ret = false; -+ } -+ -+#define PIPE_CONF_CHECK_X(name) do { \ -+ if (current_config->name != pipe_config->name) { \ -+ pipe_config_err(adjust, __stringify(name), \ -+ "(expected 0x%08x, found 0x%08x)\n", \ -+ current_config->name, \ -+ pipe_config->name); \ -+ ret = false; \ -+ } \ -+} while (0) -+ -+#define PIPE_CONF_CHECK_I(name) do { \ -+ if (current_config->name != pipe_config->name) { \ -+ pipe_config_err(adjust, __stringify(name), \ -+ "(expected %i, found %i)\n", \ -+ current_config->name, \ -+ pipe_config->name); \ -+ ret = false; \ -+ } \ -+} while (0) -+ -+#define PIPE_CONF_CHECK_BOOL(name) do { \ -+ if (current_config->name != pipe_config->name) { \ -+ pipe_config_err(adjust, __stringify(name), \ -+ "(expected %s, found %s)\n", \ -+ yesno(current_config->name), \ -+ yesno(pipe_config->name)); \ -+ ret = false; \ -+ } \ -+} while (0) -+ -+/* -+ * Checks state where we only read out the enabling, but not the entire -+ * state itself (like full infoframes or ELD for audio). These states -+ * require a full modeset on bootup to fix up. -+ */ -+#define PIPE_CONF_CHECK_BOOL_INCOMPLETE(name) do { \ -+ if (!fixup_inherited || (!current_config->name && !pipe_config->name)) { \ -+ PIPE_CONF_CHECK_BOOL(name); \ -+ } else { \ -+ pipe_config_err(adjust, __stringify(name), \ -+ "unable to verify whether state matches exactly, forcing modeset (expected %s, found %s)\n", \ -+ yesno(current_config->name), \ -+ yesno(pipe_config->name)); \ -+ ret = false; \ -+ } \ -+} while (0) -+ -+#define PIPE_CONF_CHECK_P(name) do { \ -+ if (current_config->name != pipe_config->name) { \ -+ pipe_config_err(adjust, __stringify(name), \ -+ "(expected %p, found %p)\n", \ -+ current_config->name, \ -+ pipe_config->name); \ -+ ret = false; \ -+ } \ -+} while (0) -+ -+#define PIPE_CONF_CHECK_M_N(name) do { \ -+ if (!intel_compare_link_m_n(¤t_config->name, \ -+ &pipe_config->name,\ -+ adjust)) { \ -+ pipe_config_err(adjust, __stringify(name), \ -+ "(expected tu %i gmch %i/%i link %i/%i, " \ -+ "found tu %i, gmch %i/%i link %i/%i)\n", \ -+ current_config->name.tu, \ -+ current_config->name.gmch_m, \ -+ current_config->name.gmch_n, \ -+ current_config->name.link_m, \ -+ current_config->name.link_n, \ -+ pipe_config->name.tu, \ -+ pipe_config->name.gmch_m, \ -+ pipe_config->name.gmch_n, \ -+ pipe_config->name.link_m, \ -+ pipe_config->name.link_n); \ -+ ret = false; \ -+ } \ -+} while (0) -+ -+/* This is required for BDW+ where there is only one set of registers for -+ * switching between high and low RR. -+ * This macro can be used whenever a comparison has to be made between one -+ * hw state and multiple sw state variables. -+ */ -+#define PIPE_CONF_CHECK_M_N_ALT(name, alt_name) do { \ -+ if (!intel_compare_link_m_n(¤t_config->name, \ -+ &pipe_config->name, adjust) && \ -+ !intel_compare_link_m_n(¤t_config->alt_name, \ -+ &pipe_config->name, adjust)) { \ -+ pipe_config_err(adjust, __stringify(name), \ -+ "(expected tu %i gmch %i/%i link %i/%i, " \ -+ "or tu %i gmch %i/%i link %i/%i, " \ -+ "found tu %i, gmch %i/%i link %i/%i)\n", \ -+ current_config->name.tu, \ -+ current_config->name.gmch_m, \ -+ current_config->name.gmch_n, \ -+ current_config->name.link_m, \ -+ current_config->name.link_n, \ -+ current_config->alt_name.tu, \ -+ current_config->alt_name.gmch_m, \ -+ current_config->alt_name.gmch_n, \ -+ current_config->alt_name.link_m, \ -+ current_config->alt_name.link_n, \ -+ pipe_config->name.tu, \ -+ pipe_config->name.gmch_m, \ -+ pipe_config->name.gmch_n, \ -+ pipe_config->name.link_m, \ -+ pipe_config->name.link_n); \ -+ ret = false; \ -+ } \ -+} while (0) -+ -+#define PIPE_CONF_CHECK_FLAGS(name, mask) do { \ -+ if ((current_config->name ^ pipe_config->name) & (mask)) { \ -+ pipe_config_err(adjust, __stringify(name), \ -+ "(%x) (expected %i, found %i)\n", \ -+ (mask), \ -+ current_config->name & (mask), \ -+ pipe_config->name & (mask)); \ -+ ret = false; \ -+ } \ -+} while (0) -+ -+#define PIPE_CONF_CHECK_CLOCK_FUZZY(name) do { \ -+ if (!intel_fuzzy_clock_check(current_config->name, pipe_config->name)) { \ -+ pipe_config_err(adjust, __stringify(name), \ -+ "(expected %i, found %i)\n", \ -+ current_config->name, \ -+ pipe_config->name); \ -+ ret = false; \ -+ } \ -+} while (0) -+ -+#define PIPE_CONF_CHECK_INFOFRAME(name) do { \ -+ if (!intel_compare_infoframe(¤t_config->infoframes.name, \ -+ &pipe_config->infoframes.name)) { \ -+ pipe_config_infoframe_err(dev_priv, adjust, __stringify(name), \ -+ ¤t_config->infoframes.name, \ -+ &pipe_config->infoframes.name); \ -+ ret = false; \ -+ } \ -+} while (0) -+ -+#define PIPE_CONF_QUIRK(quirk) \ -+ ((current_config->quirks | pipe_config->quirks) & (quirk)) -+ -+ PIPE_CONF_CHECK_I(cpu_transcoder); -+ -+ PIPE_CONF_CHECK_BOOL(has_pch_encoder); -+ PIPE_CONF_CHECK_I(fdi_lanes); -+ PIPE_CONF_CHECK_M_N(fdi_m_n); -+ -+ PIPE_CONF_CHECK_I(lane_count); -+ PIPE_CONF_CHECK_X(lane_lat_optim_mask); -+ -+ if (INTEL_GEN(dev_priv) < 8) { -+ PIPE_CONF_CHECK_M_N(dp_m_n); -+ -+ if (current_config->has_drrs) -+ PIPE_CONF_CHECK_M_N(dp_m2_n2); -+ } else -+ PIPE_CONF_CHECK_M_N_ALT(dp_m_n, dp_m2_n2); -+ -+ PIPE_CONF_CHECK_X(output_types); -+ -+ PIPE_CONF_CHECK_I(base.adjusted_mode.crtc_hdisplay); -+ PIPE_CONF_CHECK_I(base.adjusted_mode.crtc_htotal); -+ PIPE_CONF_CHECK_I(base.adjusted_mode.crtc_hblank_start); -+ PIPE_CONF_CHECK_I(base.adjusted_mode.crtc_hblank_end); -+ PIPE_CONF_CHECK_I(base.adjusted_mode.crtc_hsync_start); -+ PIPE_CONF_CHECK_I(base.adjusted_mode.crtc_hsync_end); -+ -+ PIPE_CONF_CHECK_I(base.adjusted_mode.crtc_vdisplay); -+ PIPE_CONF_CHECK_I(base.adjusted_mode.crtc_vtotal); -+ PIPE_CONF_CHECK_I(base.adjusted_mode.crtc_vblank_start); -+ PIPE_CONF_CHECK_I(base.adjusted_mode.crtc_vblank_end); -+ PIPE_CONF_CHECK_I(base.adjusted_mode.crtc_vsync_start); -+ PIPE_CONF_CHECK_I(base.adjusted_mode.crtc_vsync_end); -+ -+ PIPE_CONF_CHECK_I(pixel_multiplier); -+ PIPE_CONF_CHECK_I(output_format); -+ PIPE_CONF_CHECK_BOOL(has_hdmi_sink); -+ if ((INTEL_GEN(dev_priv) < 8 && !IS_HASWELL(dev_priv)) || -+ IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ PIPE_CONF_CHECK_BOOL(limited_color_range); -+ -+ PIPE_CONF_CHECK_BOOL(hdmi_scrambling); -+ PIPE_CONF_CHECK_BOOL(hdmi_high_tmds_clock_ratio); -+ PIPE_CONF_CHECK_BOOL_INCOMPLETE(has_infoframe); -+ -+ PIPE_CONF_CHECK_BOOL_INCOMPLETE(has_audio); -+ -+ PIPE_CONF_CHECK_FLAGS(base.adjusted_mode.flags, -+ DRM_MODE_FLAG_INTERLACE); -+ -+ if (!PIPE_CONF_QUIRK(PIPE_CONFIG_QUIRK_MODE_SYNC_FLAGS)) { -+ PIPE_CONF_CHECK_FLAGS(base.adjusted_mode.flags, -+ DRM_MODE_FLAG_PHSYNC); -+ PIPE_CONF_CHECK_FLAGS(base.adjusted_mode.flags, -+ DRM_MODE_FLAG_NHSYNC); -+ PIPE_CONF_CHECK_FLAGS(base.adjusted_mode.flags, -+ DRM_MODE_FLAG_PVSYNC); -+ PIPE_CONF_CHECK_FLAGS(base.adjusted_mode.flags, -+ DRM_MODE_FLAG_NVSYNC); -+ } -+ -+ PIPE_CONF_CHECK_X(gmch_pfit.control); -+ /* pfit ratios are autocomputed by the hw on gen4+ */ -+ if (INTEL_GEN(dev_priv) < 4) -+ PIPE_CONF_CHECK_X(gmch_pfit.pgm_ratios); -+ PIPE_CONF_CHECK_X(gmch_pfit.lvds_border_bits); -+ -+ /* -+ * Changing the EDP transcoder input mux -+ * (A_ONOFF vs. A_ON) requires a full modeset. -+ */ -+ if (IS_HASWELL(dev_priv) && crtc->pipe == PIPE_A && -+ current_config->cpu_transcoder == TRANSCODER_EDP) -+ PIPE_CONF_CHECK_BOOL(pch_pfit.enabled); -+ -+ if (!adjust) { -+ PIPE_CONF_CHECK_I(pipe_src_w); -+ PIPE_CONF_CHECK_I(pipe_src_h); -+ -+ PIPE_CONF_CHECK_BOOL(pch_pfit.enabled); -+ if (current_config->pch_pfit.enabled) { -+ PIPE_CONF_CHECK_X(pch_pfit.pos); -+ PIPE_CONF_CHECK_X(pch_pfit.size); -+ } -+ -+ PIPE_CONF_CHECK_I(scaler_state.scaler_id); -+ PIPE_CONF_CHECK_CLOCK_FUZZY(pixel_rate); -+ -+ PIPE_CONF_CHECK_X(gamma_mode); -+ if (IS_CHERRYVIEW(dev_priv)) -+ PIPE_CONF_CHECK_X(cgm_mode); -+ else -+ PIPE_CONF_CHECK_X(csc_mode); -+ PIPE_CONF_CHECK_BOOL(gamma_enable); -+ PIPE_CONF_CHECK_BOOL(csc_enable); -+ } -+ -+ PIPE_CONF_CHECK_BOOL(double_wide); -+ -+ PIPE_CONF_CHECK_P(shared_dpll); -+ PIPE_CONF_CHECK_X(dpll_hw_state.dpll); -+ PIPE_CONF_CHECK_X(dpll_hw_state.dpll_md); -+ PIPE_CONF_CHECK_X(dpll_hw_state.fp0); -+ PIPE_CONF_CHECK_X(dpll_hw_state.fp1); -+ PIPE_CONF_CHECK_X(dpll_hw_state.wrpll); -+ PIPE_CONF_CHECK_X(dpll_hw_state.spll); -+ PIPE_CONF_CHECK_X(dpll_hw_state.ctrl1); -+ PIPE_CONF_CHECK_X(dpll_hw_state.cfgcr1); -+ PIPE_CONF_CHECK_X(dpll_hw_state.cfgcr2); -+ PIPE_CONF_CHECK_X(dpll_hw_state.cfgcr0); -+ PIPE_CONF_CHECK_X(dpll_hw_state.ebb0); -+ PIPE_CONF_CHECK_X(dpll_hw_state.ebb4); -+ PIPE_CONF_CHECK_X(dpll_hw_state.pll0); -+ PIPE_CONF_CHECK_X(dpll_hw_state.pll1); -+ PIPE_CONF_CHECK_X(dpll_hw_state.pll2); -+ PIPE_CONF_CHECK_X(dpll_hw_state.pll3); -+ PIPE_CONF_CHECK_X(dpll_hw_state.pll6); -+ PIPE_CONF_CHECK_X(dpll_hw_state.pll8); -+ PIPE_CONF_CHECK_X(dpll_hw_state.pll9); -+ PIPE_CONF_CHECK_X(dpll_hw_state.pll10); -+ PIPE_CONF_CHECK_X(dpll_hw_state.pcsdw12); -+ PIPE_CONF_CHECK_X(dpll_hw_state.mg_refclkin_ctl); -+ PIPE_CONF_CHECK_X(dpll_hw_state.mg_clktop2_coreclkctl1); -+ PIPE_CONF_CHECK_X(dpll_hw_state.mg_clktop2_hsclkctl); -+ PIPE_CONF_CHECK_X(dpll_hw_state.mg_pll_div0); -+ PIPE_CONF_CHECK_X(dpll_hw_state.mg_pll_div1); -+ PIPE_CONF_CHECK_X(dpll_hw_state.mg_pll_lf); -+ PIPE_CONF_CHECK_X(dpll_hw_state.mg_pll_frac_lock); -+ PIPE_CONF_CHECK_X(dpll_hw_state.mg_pll_ssc); -+ PIPE_CONF_CHECK_X(dpll_hw_state.mg_pll_bias); -+ PIPE_CONF_CHECK_X(dpll_hw_state.mg_pll_tdc_coldst_bias); -+ -+ PIPE_CONF_CHECK_X(dsi_pll.ctrl); -+ PIPE_CONF_CHECK_X(dsi_pll.div); -+ -+ if (IS_G4X(dev_priv) || INTEL_GEN(dev_priv) >= 5) -+ PIPE_CONF_CHECK_I(pipe_bpp); -+ -+ PIPE_CONF_CHECK_CLOCK_FUZZY(base.adjusted_mode.crtc_clock); -+ PIPE_CONF_CHECK_CLOCK_FUZZY(port_clock); -+ -+ PIPE_CONF_CHECK_I(min_voltage_level); -+ -+ PIPE_CONF_CHECK_X(infoframes.enable); -+ PIPE_CONF_CHECK_X(infoframes.gcp); -+ PIPE_CONF_CHECK_INFOFRAME(avi); -+ PIPE_CONF_CHECK_INFOFRAME(spd); -+ PIPE_CONF_CHECK_INFOFRAME(hdmi); -+ -+#undef PIPE_CONF_CHECK_X -+#undef PIPE_CONF_CHECK_I -+#undef PIPE_CONF_CHECK_BOOL -+#undef PIPE_CONF_CHECK_BOOL_INCOMPLETE -+#undef PIPE_CONF_CHECK_P -+#undef PIPE_CONF_CHECK_FLAGS -+#undef PIPE_CONF_CHECK_CLOCK_FUZZY -+#undef PIPE_CONF_QUIRK -+ -+ return ret; -+} -+ -+static void intel_pipe_config_sanity_check(struct drm_i915_private *dev_priv, -+ const struct intel_crtc_state *pipe_config) -+{ -+ if (pipe_config->has_pch_encoder) { -+ int fdi_dotclock = intel_dotclock_calculate(intel_fdi_link_freq(dev_priv, pipe_config), -+ &pipe_config->fdi_m_n); -+ int dotclock = pipe_config->base.adjusted_mode.crtc_clock; -+ -+ /* -+ * FDI already provided one idea for the dotclock. -+ * Yell if the encoder disagrees. -+ */ -+ WARN(!intel_fuzzy_clock_check(fdi_dotclock, dotclock), -+ "FDI dotclock and encoder dotclock mismatch, fdi: %i, encoder: %i\n", -+ fdi_dotclock, dotclock); -+ } -+} -+ -+static void verify_wm_state(struct drm_crtc *crtc, -+ struct drm_crtc_state *new_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->dev); -+ struct skl_hw_state { -+ struct skl_ddb_entry ddb_y[I915_MAX_PLANES]; -+ struct skl_ddb_entry ddb_uv[I915_MAX_PLANES]; -+ struct skl_ddb_allocation ddb; -+ struct skl_pipe_wm wm; -+ } *hw; -+ struct skl_ddb_allocation *sw_ddb; -+ struct skl_pipe_wm *sw_wm; -+ struct skl_ddb_entry *hw_ddb_entry, *sw_ddb_entry; -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ const enum pipe pipe = intel_crtc->pipe; -+ int plane, level, max_level = ilk_wm_max_level(dev_priv); -+ -+ if (INTEL_GEN(dev_priv) < 9 || !new_state->active) -+ return; -+ -+ hw = kzalloc(sizeof(*hw), GFP_KERNEL); -+ if (!hw) -+ return; -+ -+ skl_pipe_wm_get_hw_state(intel_crtc, &hw->wm); -+ sw_wm = &to_intel_crtc_state(new_state)->wm.skl.optimal; -+ -+ skl_pipe_ddb_get_hw_state(intel_crtc, hw->ddb_y, hw->ddb_uv); -+ -+ skl_ddb_get_hw_state(dev_priv, &hw->ddb); -+ sw_ddb = &dev_priv->wm.skl_hw.ddb; -+ -+ if (INTEL_GEN(dev_priv) >= 11 && -+ hw->ddb.enabled_slices != sw_ddb->enabled_slices) -+ DRM_ERROR("mismatch in DBUF Slices (expected %u, got %u)\n", -+ sw_ddb->enabled_slices, -+ hw->ddb.enabled_slices); -+ -+ /* planes */ -+ for_each_universal_plane(dev_priv, pipe, plane) { -+ struct skl_plane_wm *hw_plane_wm, *sw_plane_wm; -+ -+ hw_plane_wm = &hw->wm.planes[plane]; -+ sw_plane_wm = &sw_wm->planes[plane]; -+ -+ /* Watermarks */ -+ for (level = 0; level <= max_level; level++) { -+ if (skl_wm_level_equals(&hw_plane_wm->wm[level], -+ &sw_plane_wm->wm[level])) -+ continue; -+ -+ DRM_ERROR("mismatch in WM pipe %c plane %d level %d (expected e=%d b=%u l=%u, got e=%d b=%u l=%u)\n", -+ pipe_name(pipe), plane + 1, level, -+ sw_plane_wm->wm[level].plane_en, -+ sw_plane_wm->wm[level].plane_res_b, -+ sw_plane_wm->wm[level].plane_res_l, -+ hw_plane_wm->wm[level].plane_en, -+ hw_plane_wm->wm[level].plane_res_b, -+ hw_plane_wm->wm[level].plane_res_l); -+ } -+ -+ if (!skl_wm_level_equals(&hw_plane_wm->trans_wm, -+ &sw_plane_wm->trans_wm)) { -+ DRM_ERROR("mismatch in trans WM pipe %c plane %d (expected e=%d b=%u l=%u, got e=%d b=%u l=%u)\n", -+ pipe_name(pipe), plane + 1, -+ sw_plane_wm->trans_wm.plane_en, -+ sw_plane_wm->trans_wm.plane_res_b, -+ sw_plane_wm->trans_wm.plane_res_l, -+ hw_plane_wm->trans_wm.plane_en, -+ hw_plane_wm->trans_wm.plane_res_b, -+ hw_plane_wm->trans_wm.plane_res_l); -+ } -+ -+ /* DDB */ -+ hw_ddb_entry = &hw->ddb_y[plane]; -+ sw_ddb_entry = &to_intel_crtc_state(new_state)->wm.skl.plane_ddb_y[plane]; -+ -+ if (!skl_ddb_entry_equal(hw_ddb_entry, sw_ddb_entry)) { -+ DRM_ERROR("mismatch in DDB state pipe %c plane %d (expected (%u,%u), found (%u,%u))\n", -+ pipe_name(pipe), plane + 1, -+ sw_ddb_entry->start, sw_ddb_entry->end, -+ hw_ddb_entry->start, hw_ddb_entry->end); -+ } -+ } -+ -+ /* -+ * cursor -+ * If the cursor plane isn't active, we may not have updated it's ddb -+ * allocation. In that case since the ddb allocation will be updated -+ * once the plane becomes visible, we can skip this check -+ */ -+ if (1) { -+ struct skl_plane_wm *hw_plane_wm, *sw_plane_wm; -+ -+ hw_plane_wm = &hw->wm.planes[PLANE_CURSOR]; -+ sw_plane_wm = &sw_wm->planes[PLANE_CURSOR]; -+ -+ /* Watermarks */ -+ for (level = 0; level <= max_level; level++) { -+ if (skl_wm_level_equals(&hw_plane_wm->wm[level], -+ &sw_plane_wm->wm[level])) -+ continue; -+ -+ DRM_ERROR("mismatch in WM pipe %c cursor level %d (expected e=%d b=%u l=%u, got e=%d b=%u l=%u)\n", -+ pipe_name(pipe), level, -+ sw_plane_wm->wm[level].plane_en, -+ sw_plane_wm->wm[level].plane_res_b, -+ sw_plane_wm->wm[level].plane_res_l, -+ hw_plane_wm->wm[level].plane_en, -+ hw_plane_wm->wm[level].plane_res_b, -+ hw_plane_wm->wm[level].plane_res_l); -+ } -+ -+ if (!skl_wm_level_equals(&hw_plane_wm->trans_wm, -+ &sw_plane_wm->trans_wm)) { -+ DRM_ERROR("mismatch in trans WM pipe %c cursor (expected e=%d b=%u l=%u, got e=%d b=%u l=%u)\n", -+ pipe_name(pipe), -+ sw_plane_wm->trans_wm.plane_en, -+ sw_plane_wm->trans_wm.plane_res_b, -+ sw_plane_wm->trans_wm.plane_res_l, -+ hw_plane_wm->trans_wm.plane_en, -+ hw_plane_wm->trans_wm.plane_res_b, -+ hw_plane_wm->trans_wm.plane_res_l); -+ } -+ -+ /* DDB */ -+ hw_ddb_entry = &hw->ddb_y[PLANE_CURSOR]; -+ sw_ddb_entry = &to_intel_crtc_state(new_state)->wm.skl.plane_ddb_y[PLANE_CURSOR]; -+ -+ if (!skl_ddb_entry_equal(hw_ddb_entry, sw_ddb_entry)) { -+ DRM_ERROR("mismatch in DDB state pipe %c cursor (expected (%u,%u), found (%u,%u))\n", -+ pipe_name(pipe), -+ sw_ddb_entry->start, sw_ddb_entry->end, -+ hw_ddb_entry->start, hw_ddb_entry->end); -+ } -+ } -+ -+ kfree(hw); -+} -+ -+static void -+verify_connector_state(struct drm_device *dev, -+ struct drm_atomic_state *state, -+ struct drm_crtc *crtc) -+{ -+ struct drm_connector *connector; -+ struct drm_connector_state *new_conn_state; -+ int i; -+ -+ for_each_new_connector_in_state(state, connector, new_conn_state, i) { -+ struct drm_encoder *encoder = connector->encoder; -+ struct drm_crtc_state *crtc_state = NULL; -+ -+ if (new_conn_state->crtc != crtc) -+ continue; -+ -+ if (crtc) -+ crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc); -+ -+ intel_connector_verify_state(crtc_state, new_conn_state); -+ -+ I915_STATE_WARN(new_conn_state->best_encoder != encoder, -+ "connector's atomic encoder doesn't match legacy encoder\n"); -+ } -+} -+ -+static void -+verify_encoder_state(struct drm_device *dev, struct drm_atomic_state *state) -+{ -+ struct intel_encoder *encoder; -+ struct drm_connector *connector; -+ struct drm_connector_state *old_conn_state, *new_conn_state; -+ int i; -+ -+ for_each_intel_encoder(dev, encoder) { -+ bool enabled = false, found = false; -+ enum pipe pipe; -+ -+ DRM_DEBUG_KMS("[ENCODER:%d:%s]\n", -+ encoder->base.base.id, -+ encoder->base.name); -+ -+ for_each_oldnew_connector_in_state(state, connector, old_conn_state, -+ new_conn_state, i) { -+ if (old_conn_state->best_encoder == &encoder->base) -+ found = true; -+ -+ if (new_conn_state->best_encoder != &encoder->base) -+ continue; -+ found = enabled = true; -+ -+ I915_STATE_WARN(new_conn_state->crtc != -+ encoder->base.crtc, -+ "connector's crtc doesn't match encoder crtc\n"); -+ } -+ -+ if (!found) -+ continue; -+ -+ I915_STATE_WARN(!!encoder->base.crtc != enabled, -+ "encoder's enabled state mismatch " -+ "(expected %i, found %i)\n", -+ !!encoder->base.crtc, enabled); -+ -+ if (!encoder->base.crtc) { -+ bool active; -+ -+ active = encoder->get_hw_state(encoder, &pipe); -+ I915_STATE_WARN(active, -+ "encoder detached but still enabled on pipe %c.\n", -+ pipe_name(pipe)); -+ } -+ } -+} -+ -+static void -+verify_crtc_state(struct drm_crtc *crtc, -+ struct drm_crtc_state *old_crtc_state, -+ struct drm_crtc_state *new_crtc_state) -+{ -+ struct drm_device *dev = crtc->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_encoder *encoder; -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ struct intel_crtc_state *pipe_config, *sw_config; -+ struct drm_atomic_state *old_state; -+ bool active; -+ -+ old_state = old_crtc_state->state; -+ __drm_atomic_helper_crtc_destroy_state(old_crtc_state); -+ pipe_config = to_intel_crtc_state(old_crtc_state); -+ memset(pipe_config, 0, sizeof(*pipe_config)); -+ pipe_config->base.crtc = crtc; -+ pipe_config->base.state = old_state; -+ -+ DRM_DEBUG_KMS("[CRTC:%d:%s]\n", crtc->base.id, crtc->name); -+ -+ active = dev_priv->display.get_pipe_config(intel_crtc, pipe_config); -+ -+ /* we keep both pipes enabled on 830 */ -+ if (IS_I830(dev_priv)) -+ active = new_crtc_state->active; -+ -+ I915_STATE_WARN(new_crtc_state->active != active, -+ "crtc active state doesn't match with hw state " -+ "(expected %i, found %i)\n", new_crtc_state->active, active); -+ -+ I915_STATE_WARN(intel_crtc->active != new_crtc_state->active, -+ "transitional active state does not match atomic hw state " -+ "(expected %i, found %i)\n", new_crtc_state->active, intel_crtc->active); -+ -+ for_each_encoder_on_crtc(dev, crtc, encoder) { -+ enum pipe pipe; -+ -+ active = encoder->get_hw_state(encoder, &pipe); -+ I915_STATE_WARN(active != new_crtc_state->active, -+ "[ENCODER:%i] active %i with crtc active %i\n", -+ encoder->base.base.id, active, new_crtc_state->active); -+ -+ I915_STATE_WARN(active && intel_crtc->pipe != pipe, -+ "Encoder connected to wrong pipe %c\n", -+ pipe_name(pipe)); -+ -+ if (active) -+ encoder->get_config(encoder, pipe_config); -+ } -+ -+ intel_crtc_compute_pixel_rate(pipe_config); -+ -+ if (!new_crtc_state->active) -+ return; -+ -+ intel_pipe_config_sanity_check(dev_priv, pipe_config); -+ -+ sw_config = to_intel_crtc_state(new_crtc_state); -+ if (!intel_pipe_config_compare(dev_priv, sw_config, -+ pipe_config, false)) { -+ I915_STATE_WARN(1, "pipe state doesn't match!\n"); -+ intel_dump_pipe_config(intel_crtc, pipe_config, -+ "[hw state]"); -+ intel_dump_pipe_config(intel_crtc, sw_config, -+ "[sw state]"); -+ } -+} -+ -+static void -+intel_verify_planes(struct intel_atomic_state *state) -+{ -+ struct intel_plane *plane; -+ const struct intel_plane_state *plane_state; -+ int i; -+ -+ for_each_new_intel_plane_in_state(state, plane, -+ plane_state, i) -+ assert_plane(plane, plane_state->slave || -+ plane_state->base.visible); -+} -+ -+static void -+verify_single_dpll_state(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ struct drm_crtc *crtc, -+ struct drm_crtc_state *new_state) -+{ -+ struct intel_dpll_hw_state dpll_hw_state; -+ unsigned int crtc_mask; -+ bool active; -+ -+ memset(&dpll_hw_state, 0, sizeof(dpll_hw_state)); -+ -+ DRM_DEBUG_KMS("%s\n", pll->info->name); -+ -+ active = pll->info->funcs->get_hw_state(dev_priv, pll, &dpll_hw_state); -+ -+ if (!(pll->info->flags & INTEL_DPLL_ALWAYS_ON)) { -+ I915_STATE_WARN(!pll->on && pll->active_mask, -+ "pll in active use but not on in sw tracking\n"); -+ I915_STATE_WARN(pll->on && !pll->active_mask, -+ "pll is on but not used by any active crtc\n"); -+ I915_STATE_WARN(pll->on != active, -+ "pll on state mismatch (expected %i, found %i)\n", -+ pll->on, active); -+ } -+ -+ if (!crtc) { -+ I915_STATE_WARN(pll->active_mask & ~pll->state.crtc_mask, -+ "more active pll users than references: %x vs %x\n", -+ pll->active_mask, pll->state.crtc_mask); -+ -+ return; -+ } -+ -+ crtc_mask = drm_crtc_mask(crtc); -+ -+ if (new_state->active) -+ I915_STATE_WARN(!(pll->active_mask & crtc_mask), -+ "pll active mismatch (expected pipe %c in active mask 0x%02x)\n", -+ pipe_name(drm_crtc_index(crtc)), pll->active_mask); -+ else -+ I915_STATE_WARN(pll->active_mask & crtc_mask, -+ "pll active mismatch (didn't expect pipe %c in active mask 0x%02x)\n", -+ pipe_name(drm_crtc_index(crtc)), pll->active_mask); -+ -+ I915_STATE_WARN(!(pll->state.crtc_mask & crtc_mask), -+ "pll enabled crtcs mismatch (expected 0x%x in 0x%02x)\n", -+ crtc_mask, pll->state.crtc_mask); -+ -+ I915_STATE_WARN(pll->on && memcmp(&pll->state.hw_state, -+ &dpll_hw_state, -+ sizeof(dpll_hw_state)), -+ "pll hw state mismatch\n"); -+} -+ -+static void -+verify_shared_dpll_state(struct drm_device *dev, struct drm_crtc *crtc, -+ struct drm_crtc_state *old_crtc_state, -+ struct drm_crtc_state *new_crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crtc_state *old_state = to_intel_crtc_state(old_crtc_state); -+ struct intel_crtc_state *new_state = to_intel_crtc_state(new_crtc_state); -+ -+ if (new_state->shared_dpll) -+ verify_single_dpll_state(dev_priv, new_state->shared_dpll, crtc, new_crtc_state); -+ -+ if (old_state->shared_dpll && -+ old_state->shared_dpll != new_state->shared_dpll) { -+ unsigned int crtc_mask = drm_crtc_mask(crtc); -+ struct intel_shared_dpll *pll = old_state->shared_dpll; -+ -+ I915_STATE_WARN(pll->active_mask & crtc_mask, -+ "pll active mismatch (didn't expect pipe %c in active mask)\n", -+ pipe_name(drm_crtc_index(crtc))); -+ I915_STATE_WARN(pll->state.crtc_mask & crtc_mask, -+ "pll enabled crtcs mismatch (found %x in enabled mask)\n", -+ pipe_name(drm_crtc_index(crtc))); -+ } -+} -+ -+static void -+intel_modeset_verify_crtc(struct drm_crtc *crtc, -+ struct drm_atomic_state *state, -+ struct drm_crtc_state *old_state, -+ struct drm_crtc_state *new_state) -+{ -+ if (!needs_modeset(new_state) && -+ !to_intel_crtc_state(new_state)->update_pipe) -+ return; -+ -+ verify_wm_state(crtc, new_state); -+ verify_connector_state(crtc->dev, state, crtc); -+ verify_crtc_state(crtc, old_state, new_state); -+ verify_shared_dpll_state(crtc->dev, crtc, old_state, new_state); -+} -+ -+static void -+verify_disabled_dpll_state(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int i; -+ -+ for (i = 0; i < dev_priv->num_shared_dpll; i++) -+ verify_single_dpll_state(dev_priv, &dev_priv->shared_dplls[i], NULL, NULL); -+} -+ -+static void -+intel_modeset_verify_disabled(struct drm_device *dev, -+ struct drm_atomic_state *state) -+{ -+ verify_encoder_state(dev, state); -+ verify_connector_state(dev, state, NULL); -+ verify_disabled_dpll_state(dev); -+} -+ -+static void update_scanline_offset(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ /* -+ * The scanline counter increments at the leading edge of hsync. -+ * -+ * On most platforms it starts counting from vtotal-1 on the -+ * first active line. That means the scanline counter value is -+ * always one less than what we would expect. Ie. just after -+ * start of vblank, which also occurs at start of hsync (on the -+ * last active line), the scanline counter will read vblank_start-1. -+ * -+ * On gen2 the scanline counter starts counting from 1 instead -+ * of vtotal-1, so we have to subtract one (or rather add vtotal-1 -+ * to keep the value positive), instead of adding one. -+ * -+ * On HSW+ the behaviour of the scanline counter depends on the output -+ * type. For DP ports it behaves like most other platforms, but on HDMI -+ * there's an extra 1 line difference. So we need to add two instead of -+ * one to the value. -+ * -+ * On VLV/CHV DSI the scanline counter would appear to increment -+ * approx. 1/3 of a scanline before start of vblank. Unfortunately -+ * that means we can't tell whether we're in vblank or not while -+ * we're on that particular line. We must still set scanline_offset -+ * to 1 so that the vblank timestamps come out correct when we query -+ * the scanline counter from within the vblank interrupt handler. -+ * However if queried just before the start of vblank we'll get an -+ * answer that's slightly in the future. -+ */ -+ if (IS_GEN(dev_priv, 2)) { -+ const struct drm_display_mode *adjusted_mode = &crtc_state->base.adjusted_mode; -+ int vtotal; -+ -+ vtotal = adjusted_mode->crtc_vtotal; -+ if (adjusted_mode->flags & DRM_MODE_FLAG_INTERLACE) -+ vtotal /= 2; -+ -+ crtc->scanline_offset = vtotal - 1; -+ } else if (HAS_DDI(dev_priv) && -+ intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) { -+ crtc->scanline_offset = 2; -+ } else -+ crtc->scanline_offset = 1; -+} -+ -+static void intel_modeset_clear_plls(struct drm_atomic_state *state) -+{ -+ struct drm_device *dev = state->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_crtc *crtc; -+ struct drm_crtc_state *old_crtc_state, *new_crtc_state; -+ int i; -+ -+ if (!dev_priv->display.crtc_compute_clock) -+ return; -+ -+ for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ struct intel_shared_dpll *old_dpll = -+ to_intel_crtc_state(old_crtc_state)->shared_dpll; -+ -+ if (!needs_modeset(new_crtc_state)) -+ continue; -+ -+ to_intel_crtc_state(new_crtc_state)->shared_dpll = NULL; -+ -+ if (!old_dpll) -+ continue; -+ -+ intel_release_shared_dpll(old_dpll, intel_crtc, state); -+ } -+} -+ -+/* -+ * This implements the workaround described in the "notes" section of the mode -+ * set sequence documentation. When going from no pipes or single pipe to -+ * multiple pipes, and planes are enabled after the pipe, we need to wait at -+ * least 2 vblanks on the first pipe before enabling planes on the second pipe. -+ */ -+static int haswell_mode_set_planes_workaround(struct drm_atomic_state *state) -+{ -+ struct drm_crtc_state *crtc_state; -+ struct intel_crtc *intel_crtc; -+ struct drm_crtc *crtc; -+ struct intel_crtc_state *first_crtc_state = NULL; -+ struct intel_crtc_state *other_crtc_state = NULL; -+ enum pipe first_pipe = INVALID_PIPE, enabled_pipe = INVALID_PIPE; -+ int i; -+ -+ /* look at all crtc's that are going to be enabled in during modeset */ -+ for_each_new_crtc_in_state(state, crtc, crtc_state, i) { -+ intel_crtc = to_intel_crtc(crtc); -+ -+ if (!crtc_state->active || !needs_modeset(crtc_state)) -+ continue; -+ -+ if (first_crtc_state) { -+ other_crtc_state = to_intel_crtc_state(crtc_state); -+ break; -+ } else { -+ first_crtc_state = to_intel_crtc_state(crtc_state); -+ first_pipe = intel_crtc->pipe; -+ } -+ } -+ -+ /* No workaround needed? */ -+ if (!first_crtc_state) -+ return 0; -+ -+ /* w/a possibly needed, check how many crtc's are already enabled. */ -+ for_each_intel_crtc(state->dev, intel_crtc) { -+ struct intel_crtc_state *pipe_config; -+ -+ pipe_config = intel_atomic_get_crtc_state(state, intel_crtc); -+ if (IS_ERR(pipe_config)) -+ return PTR_ERR(pipe_config); -+ -+ pipe_config->hsw_workaround_pipe = INVALID_PIPE; -+ -+ if (!pipe_config->base.active || -+ needs_modeset(&pipe_config->base)) -+ continue; -+ -+ /* 2 or more enabled crtcs means no need for w/a */ -+ if (enabled_pipe != INVALID_PIPE) -+ return 0; -+ -+ enabled_pipe = intel_crtc->pipe; -+ } -+ -+ if (enabled_pipe != INVALID_PIPE) -+ first_crtc_state->hsw_workaround_pipe = enabled_pipe; -+ else if (other_crtc_state) -+ other_crtc_state->hsw_workaround_pipe = first_pipe; -+ -+ return 0; -+} -+ -+static int intel_lock_all_pipes(struct drm_atomic_state *state) -+{ -+ struct drm_crtc *crtc; -+ -+ /* Add all pipes to the state */ -+ for_each_crtc(state->dev, crtc) { -+ 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); -+ } -+ -+ return 0; -+} -+ -+static int intel_modeset_all_pipes(struct drm_atomic_state *state) -+{ -+ struct drm_crtc *crtc; -+ -+ /* -+ * Add all pipes to the state, and force -+ * a modeset on all the active ones. -+ */ -+ for_each_crtc(state->dev, crtc) { -+ struct drm_crtc_state *crtc_state; -+ int ret; -+ -+ crtc_state = drm_atomic_get_crtc_state(state, crtc); -+ if (IS_ERR(crtc_state)) -+ return PTR_ERR(crtc_state); -+ -+ if (!crtc_state->active || needs_modeset(crtc_state)) -+ continue; -+ -+ crtc_state->mode_changed = true; -+ -+ ret = drm_atomic_add_affected_connectors(state, crtc); -+ if (ret) -+ return ret; -+ -+ ret = drm_atomic_add_affected_planes(state, crtc); -+ if (ret) -+ return ret; -+ } -+ -+ return 0; -+} -+ -+static int intel_modeset_checks(struct drm_atomic_state *state) -+{ -+ struct intel_atomic_state *intel_state = to_intel_atomic_state(state); -+ struct drm_i915_private *dev_priv = to_i915(state->dev); -+ struct drm_crtc *crtc; -+ struct drm_crtc_state *old_crtc_state, *new_crtc_state; -+ int ret = 0, i; -+ -+ if (!check_digital_port_conflicts(state)) { -+ DRM_DEBUG_KMS("rejecting conflicting digital port configuration\n"); -+ return -EINVAL; -+ } -+ -+ /* keep the current setting */ -+ if (!intel_state->cdclk.force_min_cdclk_changed) -+ intel_state->cdclk.force_min_cdclk = -+ dev_priv->cdclk.force_min_cdclk; -+ -+ intel_state->modeset = true; -+ intel_state->active_crtcs = dev_priv->active_crtcs; -+ intel_state->cdclk.logical = dev_priv->cdclk.logical; -+ intel_state->cdclk.actual = dev_priv->cdclk.actual; -+ intel_state->cdclk.pipe = INVALID_PIPE; -+ -+ for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { -+ if (new_crtc_state->active) -+ intel_state->active_crtcs |= 1 << i; -+ else -+ intel_state->active_crtcs &= ~(1 << i); -+ -+ if (old_crtc_state->active != new_crtc_state->active) -+ intel_state->active_pipe_changes |= drm_crtc_mask(crtc); -+ } -+ -+ /* -+ * See if the config requires any additional preparation, e.g. -+ * to adjust global state with pipes off. We need to do this -+ * here so we can get the modeset_pipe updated config for the new -+ * mode set on this crtc. For other crtcs we need to use the -+ * adjusted_mode bits in the crtc directly. -+ */ -+ if (dev_priv->display.modeset_calc_cdclk) { -+ enum pipe pipe; -+ -+ ret = dev_priv->display.modeset_calc_cdclk(state); -+ if (ret < 0) -+ return ret; -+ -+ /* -+ * Writes to dev_priv->cdclk.logical must protected by -+ * holding all the crtc locks, even if we don't end up -+ * touching the hardware -+ */ -+ if (intel_cdclk_changed(&dev_priv->cdclk.logical, -+ &intel_state->cdclk.logical)) { -+ ret = intel_lock_all_pipes(state); -+ if (ret < 0) -+ return ret; -+ } -+ -+ if (is_power_of_2(intel_state->active_crtcs)) { -+ struct drm_crtc *crtc; -+ struct drm_crtc_state *crtc_state; -+ -+ pipe = ilog2(intel_state->active_crtcs); -+ crtc = &intel_get_crtc_for_pipe(dev_priv, pipe)->base; -+ crtc_state = drm_atomic_get_new_crtc_state(state, crtc); -+ if (crtc_state && needs_modeset(crtc_state)) -+ pipe = INVALID_PIPE; -+ } else { -+ pipe = INVALID_PIPE; -+ } -+ -+ /* All pipes must be switched off while we change the cdclk. */ -+ if (pipe != INVALID_PIPE && -+ intel_cdclk_needs_cd2x_update(dev_priv, -+ &dev_priv->cdclk.actual, -+ &intel_state->cdclk.actual)) { -+ ret = intel_lock_all_pipes(state); -+ if (ret < 0) -+ return ret; -+ -+ intel_state->cdclk.pipe = pipe; -+ } else if (intel_cdclk_needs_modeset(&dev_priv->cdclk.actual, -+ &intel_state->cdclk.actual)) { -+ ret = intel_modeset_all_pipes(state); -+ if (ret < 0) -+ return ret; -+ -+ intel_state->cdclk.pipe = INVALID_PIPE; -+ } -+ -+ DRM_DEBUG_KMS("New cdclk calculated to be logical %u kHz, actual %u kHz\n", -+ intel_state->cdclk.logical.cdclk, -+ intel_state->cdclk.actual.cdclk); -+ DRM_DEBUG_KMS("New voltage level calculated to be logical %u, actual %u\n", -+ intel_state->cdclk.logical.voltage_level, -+ intel_state->cdclk.actual.voltage_level); -+ } -+ -+ intel_modeset_clear_plls(state); -+ -+ if (IS_HASWELL(dev_priv)) -+ return haswell_mode_set_planes_workaround(state); -+ -+ return 0; -+} -+ -+/* -+ * Handle calculation of various watermark data at the end of the atomic check -+ * phase. The code here should be run after the per-crtc and per-plane 'check' -+ * handlers to ensure that all derived state has been updated. -+ */ -+static int calc_watermark_data(struct intel_atomic_state *state) -+{ -+ struct drm_device *dev = state->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ /* Is there platform-specific watermark information to calculate? */ -+ if (dev_priv->display.compute_global_watermarks) -+ return dev_priv->display.compute_global_watermarks(state); -+ -+ return 0; -+} -+ -+static void intel_crtc_check_fastset(struct intel_crtc_state *old_crtc_state, -+ struct intel_crtc_state *new_crtc_state) -+{ -+ struct drm_i915_private *dev_priv = -+ to_i915(new_crtc_state->base.crtc->dev); -+ -+ if (!intel_pipe_config_compare(dev_priv, old_crtc_state, -+ new_crtc_state, true)) -+ return; -+ -+ new_crtc_state->base.mode_changed = false; -+ new_crtc_state->update_pipe = true; -+ -+ /* -+ * If we're not doing the full modeset we want to -+ * keep the current M/N values as they may be -+ * sufficiently different to the computed values -+ * to cause problems. -+ * -+ * FIXME: should really copy more fuzzy state here -+ */ -+ new_crtc_state->fdi_m_n = old_crtc_state->fdi_m_n; -+ new_crtc_state->dp_m_n = old_crtc_state->dp_m_n; -+ new_crtc_state->dp_m2_n2 = old_crtc_state->dp_m2_n2; -+ new_crtc_state->has_drrs = old_crtc_state->has_drrs; -+} -+ -+/** -+ * intel_atomic_check - validate state object -+ * @dev: drm device -+ * @state: state to validate -+ */ -+static int intel_atomic_check(struct drm_device *dev, -+ struct drm_atomic_state *state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_atomic_state *intel_state = to_intel_atomic_state(state); -+ struct drm_crtc *crtc; -+ struct drm_crtc_state *old_crtc_state, *crtc_state; -+ int ret, i; -+ bool any_ms = intel_state->cdclk.force_min_cdclk_changed; -+ -+ /* Catch I915_MODE_FLAG_INHERITED */ -+ for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, -+ crtc_state, i) { -+ if (crtc_state->mode.private_flags != -+ old_crtc_state->mode.private_flags) -+ crtc_state->mode_changed = true; -+ } -+ -+ ret = drm_atomic_helper_check_modeset(dev, state); -+ if (ret) -+ return ret; -+ -+ for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, crtc_state, i) { -+ struct intel_crtc_state *pipe_config = -+ to_intel_crtc_state(crtc_state); -+ -+ if (!needs_modeset(crtc_state)) -+ continue; -+ -+ if (!crtc_state->enable) { -+ any_ms = true; -+ continue; -+ } -+ -+ ret = intel_modeset_pipe_config(crtc, pipe_config); -+ if (ret == -EDEADLK) -+ return ret; -+ if (ret) { -+ intel_dump_pipe_config(to_intel_crtc(crtc), -+ pipe_config, "[failed]"); -+ return ret; -+ } -+ -+ intel_crtc_check_fastset(to_intel_crtc_state(old_crtc_state), -+ pipe_config); -+ -+ if (needs_modeset(crtc_state)) -+ any_ms = true; -+ -+ intel_dump_pipe_config(to_intel_crtc(crtc), pipe_config, -+ needs_modeset(crtc_state) ? -+ "[modeset]" : "[fastset]"); -+ } -+ -+ ret = drm_dp_mst_atomic_check(state); -+ if (ret) -+ return ret; -+ -+ if (any_ms) { -+ ret = intel_modeset_checks(state); -+ -+ if (ret) -+ return ret; -+ } else { -+ intel_state->cdclk.logical = dev_priv->cdclk.logical; -+ } -+ -+ ret = icl_add_linked_planes(intel_state); -+ if (ret) -+ return ret; -+ -+ ret = drm_atomic_helper_check_planes(dev, state); -+ if (ret) -+ return ret; -+ -+ intel_fbc_choose_crtc(dev_priv, intel_state); -+ return calc_watermark_data(intel_state); -+} -+ -+static int intel_atomic_prepare_commit(struct drm_device *dev, -+ struct drm_atomic_state *state) -+{ -+ return drm_atomic_helper_prepare_planes(dev, state); -+} -+ -+u32 intel_crtc_get_vblank_counter(struct intel_crtc *crtc) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_vblank_crtc *vblank = &dev->vblank[drm_crtc_index(&crtc->base)]; -+ -+ if (!vblank->max_vblank_count) -+ return (u32)drm_crtc_accurate_vblank_count(&crtc->base); -+ -+ return dev->driver->get_vblank_counter(dev, crtc->pipe); -+} -+ -+static void intel_update_crtc(struct drm_crtc *crtc, -+ struct drm_atomic_state *state, -+ struct drm_crtc_state *old_crtc_state, -+ struct drm_crtc_state *new_crtc_state) -+{ -+ struct drm_device *dev = crtc->dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc); -+ struct intel_crtc_state *pipe_config = to_intel_crtc_state(new_crtc_state); -+ bool modeset = needs_modeset(new_crtc_state); -+ struct intel_plane_state *new_plane_state = -+ intel_atomic_get_new_plane_state(to_intel_atomic_state(state), -+ to_intel_plane(crtc->primary)); -+ -+ if (modeset) { -+ update_scanline_offset(pipe_config); -+ dev_priv->display.crtc_enable(pipe_config, state); -+ -+ /* vblanks work again, re-enable pipe CRC. */ -+ intel_crtc_enable_pipe_crc(intel_crtc); -+ } else { -+ intel_pre_plane_update(to_intel_crtc_state(old_crtc_state), -+ pipe_config); -+ -+ if (pipe_config->update_pipe) -+ intel_encoders_update_pipe(crtc, pipe_config, state); -+ } -+ -+ if (pipe_config->update_pipe && !pipe_config->enable_fbc) -+ intel_fbc_disable(intel_crtc); -+ else if (new_plane_state) -+ intel_fbc_enable(intel_crtc, pipe_config, new_plane_state); -+ -+ intel_begin_crtc_commit(to_intel_atomic_state(state), intel_crtc); -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ skl_update_planes_on_crtc(to_intel_atomic_state(state), intel_crtc); -+ else -+ i9xx_update_planes_on_crtc(to_intel_atomic_state(state), intel_crtc); -+ -+ intel_finish_crtc_commit(to_intel_atomic_state(state), intel_crtc); -+} -+ -+static void intel_update_crtcs(struct drm_atomic_state *state) -+{ -+ struct drm_crtc *crtc; -+ struct drm_crtc_state *old_crtc_state, *new_crtc_state; -+ int i; -+ -+ for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { -+ if (!new_crtc_state->active) -+ continue; -+ -+ intel_update_crtc(crtc, state, old_crtc_state, -+ new_crtc_state); -+ } -+} -+ -+static void skl_update_crtcs(struct drm_atomic_state *state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(state->dev); -+ struct intel_atomic_state *intel_state = to_intel_atomic_state(state); -+ struct drm_crtc *crtc; -+ struct intel_crtc *intel_crtc; -+ struct drm_crtc_state *old_crtc_state, *new_crtc_state; -+ struct intel_crtc_state *cstate; -+ unsigned int updated = 0; -+ bool progress; -+ enum pipe pipe; -+ int i; -+ u8 hw_enabled_slices = dev_priv->wm.skl_hw.ddb.enabled_slices; -+ u8 required_slices = intel_state->wm_results.ddb.enabled_slices; -+ struct skl_ddb_entry entries[I915_MAX_PIPES] = {}; -+ -+ for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) -+ /* ignore allocations for crtc's that have been turned off. */ -+ if (new_crtc_state->active) -+ entries[i] = to_intel_crtc_state(old_crtc_state)->wm.skl.ddb; -+ -+ /* If 2nd DBuf slice required, enable it here */ -+ if (INTEL_GEN(dev_priv) >= 11 && required_slices > hw_enabled_slices) -+ icl_dbuf_slices_update(dev_priv, required_slices); -+ -+ /* -+ * Whenever the number of active pipes changes, we need to make sure we -+ * update the pipes in the right order so that their ddb allocations -+ * never overlap with eachother inbetween CRTC updates. Otherwise we'll -+ * cause pipe underruns and other bad stuff. -+ */ -+ do { -+ progress = false; -+ -+ for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { -+ bool vbl_wait = false; -+ unsigned int cmask = drm_crtc_mask(crtc); -+ -+ intel_crtc = to_intel_crtc(crtc); -+ cstate = to_intel_crtc_state(new_crtc_state); -+ pipe = intel_crtc->pipe; -+ -+ if (updated & cmask || !cstate->base.active) -+ continue; -+ -+ if (skl_ddb_allocation_overlaps(&cstate->wm.skl.ddb, -+ entries, -+ INTEL_INFO(dev_priv)->num_pipes, i)) -+ continue; -+ -+ updated |= cmask; -+ entries[i] = cstate->wm.skl.ddb; -+ -+ /* -+ * If this is an already active pipe, it's DDB changed, -+ * and this isn't the last pipe that needs updating -+ * then we need to wait for a vblank to pass for the -+ * new ddb allocation to take effect. -+ */ -+ if (!skl_ddb_entry_equal(&cstate->wm.skl.ddb, -+ &to_intel_crtc_state(old_crtc_state)->wm.skl.ddb) && -+ !new_crtc_state->active_changed && -+ intel_state->wm_results.dirty_pipes != updated) -+ vbl_wait = true; -+ -+ intel_update_crtc(crtc, state, old_crtc_state, -+ new_crtc_state); -+ -+ if (vbl_wait) -+ intel_wait_for_vblank(dev_priv, pipe); -+ -+ progress = true; -+ } -+ } while (progress); -+ -+ /* If 2nd DBuf slice is no more required disable it */ -+ if (INTEL_GEN(dev_priv) >= 11 && required_slices < hw_enabled_slices) -+ icl_dbuf_slices_update(dev_priv, required_slices); -+} -+ -+static void intel_atomic_helper_free_state(struct drm_i915_private *dev_priv) -+{ -+ struct intel_atomic_state *state, *next; -+ struct llist_node *freed; -+ -+ freed = llist_del_all(&dev_priv->atomic_helper.free_list); -+ llist_for_each_entry_safe(state, next, freed, freed) -+ drm_atomic_state_put(&state->base); -+} -+ -+static void intel_atomic_helper_free_state_worker(struct work_struct *work) -+{ -+ struct drm_i915_private *dev_priv = -+ container_of(work, typeof(*dev_priv), atomic_helper.free_work); -+ -+ intel_atomic_helper_free_state(dev_priv); -+} -+ -+static void intel_atomic_commit_fence_wait(struct intel_atomic_state *intel_state) -+{ -+ struct wait_queue_entry wait_fence, wait_reset; -+ struct drm_i915_private *dev_priv = to_i915(intel_state->base.dev); -+ -+ init_wait_entry(&wait_fence, 0); -+ init_wait_entry(&wait_reset, 0); -+ for (;;) { -+ prepare_to_wait(&intel_state->commit_ready.wait, -+ &wait_fence, TASK_UNINTERRUPTIBLE); -+ prepare_to_wait(&dev_priv->gpu_error.wait_queue, -+ &wait_reset, TASK_UNINTERRUPTIBLE); -+ -+ -+ if (i915_sw_fence_done(&intel_state->commit_ready) -+ || test_bit(I915_RESET_MODESET, &dev_priv->gpu_error.flags)) -+ break; -+ -+ schedule(); -+ } -+ finish_wait(&intel_state->commit_ready.wait, &wait_fence); -+ finish_wait(&dev_priv->gpu_error.wait_queue, &wait_reset); -+} -+ -+static void intel_atomic_cleanup_work(struct work_struct *work) -+{ -+ struct drm_atomic_state *state = -+ container_of(work, struct drm_atomic_state, commit_work); -+ struct drm_i915_private *i915 = to_i915(state->dev); -+ -+ drm_atomic_helper_cleanup_planes(&i915->drm, state); -+ drm_atomic_helper_commit_cleanup_done(state); -+ drm_atomic_state_put(state); -+ -+ intel_atomic_helper_free_state(i915); -+} -+ -+static void intel_atomic_commit_tail(struct drm_atomic_state *state) -+{ -+ struct drm_device *dev = state->dev; -+ struct intel_atomic_state *intel_state = to_intel_atomic_state(state); -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_crtc_state *old_crtc_state, *new_crtc_state; -+ struct intel_crtc_state *new_intel_crtc_state, *old_intel_crtc_state; -+ struct drm_crtc *crtc; -+ struct intel_crtc *intel_crtc; -+ u64 put_domains[I915_MAX_PIPES] = {}; -+ intel_wakeref_t wakeref = 0; -+ int i; -+ -+ intel_atomic_commit_fence_wait(intel_state); -+ -+ drm_atomic_helper_wait_for_dependencies(state); -+ -+ if (intel_state->modeset) -+ wakeref = intel_display_power_get(dev_priv, POWER_DOMAIN_MODESET); -+ -+ for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { -+ old_intel_crtc_state = to_intel_crtc_state(old_crtc_state); -+ new_intel_crtc_state = to_intel_crtc_state(new_crtc_state); -+ intel_crtc = to_intel_crtc(crtc); -+ -+ if (needs_modeset(new_crtc_state) || -+ to_intel_crtc_state(new_crtc_state)->update_pipe) { -+ -+ put_domains[intel_crtc->pipe] = -+ modeset_get_crtc_power_domains(crtc, -+ new_intel_crtc_state); -+ } -+ -+ if (!needs_modeset(new_crtc_state)) -+ continue; -+ -+ intel_pre_plane_update(old_intel_crtc_state, new_intel_crtc_state); -+ -+ if (old_crtc_state->active) { -+ intel_crtc_disable_planes(intel_state, intel_crtc); -+ -+ /* -+ * We need to disable pipe CRC before disabling the pipe, -+ * or we race against vblank off. -+ */ -+ intel_crtc_disable_pipe_crc(intel_crtc); -+ -+ dev_priv->display.crtc_disable(old_intel_crtc_state, state); -+ intel_crtc->active = false; -+ intel_fbc_disable(intel_crtc); -+ intel_disable_shared_dpll(old_intel_crtc_state); -+ -+ /* -+ * Underruns don't always raise -+ * interrupts, so check manually. -+ */ -+ intel_check_cpu_fifo_underruns(dev_priv); -+ intel_check_pch_fifo_underruns(dev_priv); -+ -+ /* FIXME unify this for all platforms */ -+ if (!new_crtc_state->active && -+ !HAS_GMCH(dev_priv) && -+ dev_priv->display.initial_watermarks) -+ dev_priv->display.initial_watermarks(intel_state, -+ new_intel_crtc_state); -+ } -+ } -+ -+ /* FIXME: Eventually get rid of our intel_crtc->config pointer */ -+ for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) -+ to_intel_crtc(crtc)->config = to_intel_crtc_state(new_crtc_state); -+ -+ if (intel_state->modeset) { -+ drm_atomic_helper_update_legacy_modeset_state(state->dev, state); -+ -+ intel_set_cdclk_pre_plane_update(dev_priv, -+ &intel_state->cdclk.actual, -+ &dev_priv->cdclk.actual, -+ intel_state->cdclk.pipe); -+ -+ /* -+ * SKL workaround: bspec recommends we disable the SAGV when we -+ * have more then one pipe enabled -+ */ -+ if (!intel_can_enable_sagv(state)) -+ intel_disable_sagv(dev_priv); -+ -+ intel_modeset_verify_disabled(dev, state); -+ } -+ -+ /* Complete the events for pipes that have now been disabled */ -+ for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { -+ bool modeset = needs_modeset(new_crtc_state); -+ -+ /* Complete events for now disable pipes here. */ -+ if (modeset && !new_crtc_state->active && new_crtc_state->event) { -+ spin_lock_irq(&dev->event_lock); -+ drm_crtc_send_vblank_event(crtc, new_crtc_state->event); -+ spin_unlock_irq(&dev->event_lock); -+ -+ new_crtc_state->event = NULL; -+ } -+ } -+ -+ /* Now enable the clocks, plane, pipe, and connectors that we set up. */ -+ dev_priv->display.update_crtcs(state); -+ -+ if (intel_state->modeset) -+ intel_set_cdclk_post_plane_update(dev_priv, -+ &intel_state->cdclk.actual, -+ &dev_priv->cdclk.actual, -+ intel_state->cdclk.pipe); -+ -+ /* FIXME: We should call drm_atomic_helper_commit_hw_done() here -+ * already, but still need the state for the delayed optimization. To -+ * fix this: -+ * - wrap the optimization/post_plane_update stuff into a per-crtc work. -+ * - schedule that vblank worker _before_ calling hw_done -+ * - at the start of commit_tail, cancel it _synchrously -+ * - switch over to the vblank wait helper in the core after that since -+ * we don't need out special handling any more. -+ */ -+ drm_atomic_helper_wait_for_flip_done(dev, state); -+ -+ for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { -+ new_intel_crtc_state = to_intel_crtc_state(new_crtc_state); -+ -+ if (new_crtc_state->active && -+ !needs_modeset(new_crtc_state) && -+ (new_intel_crtc_state->base.color_mgmt_changed || -+ new_intel_crtc_state->update_pipe)) -+ intel_color_load_luts(new_intel_crtc_state); -+ } -+ -+ /* -+ * Now that the vblank has passed, we can go ahead and program the -+ * optimal watermarks on platforms that need two-step watermark -+ * programming. -+ * -+ * TODO: Move this (and other cleanup) to an async worker eventually. -+ */ -+ for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) { -+ new_intel_crtc_state = to_intel_crtc_state(new_crtc_state); -+ -+ if (dev_priv->display.optimize_watermarks) -+ dev_priv->display.optimize_watermarks(intel_state, -+ new_intel_crtc_state); -+ } -+ -+ for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { -+ intel_post_plane_update(to_intel_crtc_state(old_crtc_state)); -+ -+ if (put_domains[i]) -+ modeset_put_power_domains(dev_priv, put_domains[i]); -+ -+ intel_modeset_verify_crtc(crtc, state, old_crtc_state, new_crtc_state); -+ } -+ -+ if (intel_state->modeset) -+ intel_verify_planes(intel_state); -+ -+ if (intel_state->modeset && intel_can_enable_sagv(state)) -+ intel_enable_sagv(dev_priv); -+ -+ drm_atomic_helper_commit_hw_done(state); -+ -+ if (intel_state->modeset) { -+ /* As one of the primary mmio accessors, KMS has a high -+ * likelihood of triggering bugs in unclaimed access. After we -+ * finish modesetting, see if an error has been flagged, and if -+ * so enable debugging for the next modeset - and hope we catch -+ * the culprit. -+ */ -+ intel_uncore_arm_unclaimed_mmio_detection(&dev_priv->uncore); -+ intel_display_power_put(dev_priv, POWER_DOMAIN_MODESET, wakeref); -+ } -+ -+ /* -+ * Defer the cleanup of the old state to a separate worker to not -+ * impede the current task (userspace for blocking modesets) that -+ * are executed inline. For out-of-line asynchronous modesets/flips, -+ * deferring to a new worker seems overkill, but we would place a -+ * schedule point (cond_resched()) here anyway to keep latencies -+ * down. -+ */ -+ INIT_WORK(&state->commit_work, intel_atomic_cleanup_work); -+ queue_work(system_highpri_wq, &state->commit_work); -+} -+ -+static void intel_atomic_commit_work(struct work_struct *work) -+{ -+ struct drm_atomic_state *state = -+ container_of(work, struct drm_atomic_state, commit_work); -+ -+ intel_atomic_commit_tail(state); -+} -+ -+static int __i915_sw_fence_call -+intel_atomic_commit_ready(struct i915_sw_fence *fence, -+ enum i915_sw_fence_notify notify) -+{ -+ struct intel_atomic_state *state = -+ container_of(fence, struct intel_atomic_state, commit_ready); -+ -+ switch (notify) { -+ case FENCE_COMPLETE: -+ /* we do blocking waits in the worker, nothing to do here */ -+ break; -+ case FENCE_FREE: -+ { -+ struct intel_atomic_helper *helper = -+ &to_i915(state->base.dev)->atomic_helper; -+ -+ if (llist_add(&state->freed, &helper->free_list)) -+ schedule_work(&helper->free_work); -+ break; -+ } -+ } -+ -+ return NOTIFY_DONE; -+} -+ -+static void intel_atomic_track_fbs(struct drm_atomic_state *state) -+{ -+ struct drm_plane_state *old_plane_state, *new_plane_state; -+ struct drm_plane *plane; -+ int i; -+ -+ for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) -+ i915_gem_track_fb(intel_fb_obj(old_plane_state->fb), -+ intel_fb_obj(new_plane_state->fb), -+ to_intel_plane(plane)->frontbuffer_bit); -+} -+ -+/** -+ * intel_atomic_commit - commit validated state object -+ * @dev: DRM device -+ * @state: the top-level driver state object -+ * @nonblock: nonblocking commit -+ * -+ * This function commits a top-level state object that has been validated -+ * with drm_atomic_helper_check(). -+ * -+ * RETURNS -+ * Zero for success or -errno. -+ */ -+static int intel_atomic_commit(struct drm_device *dev, -+ struct drm_atomic_state *state, -+ bool nonblock) -+{ -+ struct intel_atomic_state *intel_state = to_intel_atomic_state(state); -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int ret = 0; -+ -+ drm_atomic_state_get(state); -+ i915_sw_fence_init(&intel_state->commit_ready, -+ intel_atomic_commit_ready); -+ -+ /* -+ * The intel_legacy_cursor_update() fast path takes care -+ * of avoiding the vblank waits for simple cursor -+ * movement and flips. For cursor on/off and size changes, -+ * we want to perform the vblank waits so that watermark -+ * updates happen during the correct frames. Gen9+ have -+ * double buffered watermarks and so shouldn't need this. -+ * -+ * Unset state->legacy_cursor_update before the call to -+ * drm_atomic_helper_setup_commit() because otherwise -+ * drm_atomic_helper_wait_for_flip_done() is a noop and -+ * we get FIFO underruns because we didn't wait -+ * for vblank. -+ * -+ * FIXME doing watermarks and fb cleanup from a vblank worker -+ * (assuming we had any) would solve these problems. -+ */ -+ if (INTEL_GEN(dev_priv) < 9 && state->legacy_cursor_update) { -+ struct intel_crtc_state *new_crtc_state; -+ struct intel_crtc *crtc; -+ int i; -+ -+ for_each_new_intel_crtc_in_state(intel_state, crtc, new_crtc_state, i) -+ if (new_crtc_state->wm.need_postvbl_update || -+ new_crtc_state->update_wm_post) -+ state->legacy_cursor_update = false; -+ } -+ -+ ret = intel_atomic_prepare_commit(dev, state); -+ if (ret) { -+ DRM_DEBUG_ATOMIC("Preparing state failed with %i\n", ret); -+ i915_sw_fence_commit(&intel_state->commit_ready); -+ return ret; -+ } -+ -+ ret = drm_atomic_helper_setup_commit(state, nonblock); -+ if (!ret) -+ ret = drm_atomic_helper_swap_state(state, true); -+ -+ if (ret) { -+ i915_sw_fence_commit(&intel_state->commit_ready); -+ -+ drm_atomic_helper_cleanup_planes(dev, state); -+ return ret; -+ } -+ dev_priv->wm.distrust_bios_wm = false; -+ intel_shared_dpll_swap_state(state); -+ intel_atomic_track_fbs(state); -+ -+ if (intel_state->modeset) { -+ memcpy(dev_priv->min_cdclk, intel_state->min_cdclk, -+ sizeof(intel_state->min_cdclk)); -+ memcpy(dev_priv->min_voltage_level, -+ intel_state->min_voltage_level, -+ sizeof(intel_state->min_voltage_level)); -+ dev_priv->active_crtcs = intel_state->active_crtcs; -+ dev_priv->cdclk.force_min_cdclk = -+ intel_state->cdclk.force_min_cdclk; -+ -+ intel_cdclk_swap_state(intel_state); -+ } -+ -+ drm_atomic_state_get(state); -+ INIT_WORK(&state->commit_work, intel_atomic_commit_work); -+ -+ i915_sw_fence_commit(&intel_state->commit_ready); -+ if (nonblock && intel_state->modeset) { -+ queue_work(dev_priv->modeset_wq, &state->commit_work); -+ } else if (nonblock) { -+ queue_work(system_unbound_wq, &state->commit_work); -+ } else { -+ if (intel_state->modeset) -+ flush_workqueue(dev_priv->modeset_wq); -+ intel_atomic_commit_tail(state); -+ } -+ -+ return 0; -+} -+ -+static const struct drm_crtc_funcs intel_crtc_funcs = { -+ .gamma_set = drm_atomic_helper_legacy_gamma_set, -+ .set_config = drm_atomic_helper_set_config, -+ .destroy = intel_crtc_destroy, -+ .page_flip = drm_atomic_helper_page_flip, -+ .atomic_duplicate_state = intel_crtc_duplicate_state, -+ .atomic_destroy_state = intel_crtc_destroy_state, -+ .set_crc_source = intel_crtc_set_crc_source, -+ .verify_crc_source = intel_crtc_verify_crc_source, -+ .get_crc_sources = intel_crtc_get_crc_sources, -+}; -+ -+struct wait_rps_boost { -+ struct wait_queue_entry wait; -+ -+ struct drm_crtc *crtc; -+ struct i915_request *request; -+}; -+ -+static int do_rps_boost(struct wait_queue_entry *_wait, -+ unsigned mode, int sync, void *key) -+{ -+ struct wait_rps_boost *wait = container_of(_wait, typeof(*wait), wait); -+ struct i915_request *rq = wait->request; -+ -+ /* -+ * If we missed the vblank, but the request is already running it -+ * is reasonable to assume that it will complete before the next -+ * vblank without our intervention, so leave RPS alone. -+ */ -+ if (!i915_request_started(rq)) -+ gen6_rps_boost(rq); -+ i915_request_put(rq); -+ -+ drm_crtc_vblank_put(wait->crtc); -+ -+ list_del(&wait->wait.entry); -+ kfree(wait); -+ return 1; -+} -+ -+static void add_rps_boost_after_vblank(struct drm_crtc *crtc, -+ struct dma_fence *fence) -+{ -+ struct wait_rps_boost *wait; -+ -+ if (!dma_fence_is_i915(fence)) -+ return; -+ -+ if (INTEL_GEN(to_i915(crtc->dev)) < 6) -+ return; -+ -+ if (drm_crtc_vblank_get(crtc)) -+ return; -+ -+ wait = kmalloc(sizeof(*wait), GFP_KERNEL); -+ if (!wait) { -+ drm_crtc_vblank_put(crtc); -+ return; -+ } -+ -+ wait->request = to_request(dma_fence_get(fence)); -+ wait->crtc = crtc; -+ -+ wait->wait.func = do_rps_boost; -+ wait->wait.flags = 0; -+ -+ add_wait_queue(drm_crtc_vblank_waitqueue(crtc), &wait->wait); -+} -+ -+static int intel_plane_pin_fb(struct intel_plane_state *plane_state) -+{ -+ struct intel_plane *plane = to_intel_plane(plane_state->base.plane); -+ struct drm_i915_private *dev_priv = to_i915(plane->base.dev); -+ struct drm_framebuffer *fb = plane_state->base.fb; -+ struct i915_vma *vma; -+ -+ if (plane->id == PLANE_CURSOR && -+ INTEL_INFO(dev_priv)->display.cursor_needs_physical) { -+ struct drm_i915_gem_object *obj = intel_fb_obj(fb); -+ const int align = intel_cursor_alignment(dev_priv); -+ int err; -+ -+ err = i915_gem_object_attach_phys(obj, align); -+ if (err) -+ return err; -+ } -+ -+ vma = intel_pin_and_fence_fb_obj(fb, -+ &plane_state->view, -+ intel_plane_uses_fence(plane_state), -+ &plane_state->flags); -+ if (IS_ERR(vma)) -+ return PTR_ERR(vma); -+ -+ plane_state->vma = vma; -+ -+ return 0; -+} -+ -+static void intel_plane_unpin_fb(struct intel_plane_state *old_plane_state) -+{ -+ struct i915_vma *vma; -+ -+ vma = fetch_and_zero(&old_plane_state->vma); -+ if (vma) -+ intel_unpin_fb_vma(vma, old_plane_state->flags); -+} -+ -+static void fb_obj_bump_render_priority(struct drm_i915_gem_object *obj) -+{ -+ struct i915_sched_attr attr = { -+ .priority = I915_PRIORITY_DISPLAY, -+ }; -+ -+ i915_gem_object_wait_priority(obj, 0, &attr); -+} -+ -+/** -+ * intel_prepare_plane_fb - Prepare fb for usage on plane -+ * @plane: drm plane to prepare for -+ * @new_state: the plane state being prepared -+ * -+ * Prepares a framebuffer for usage on a display plane. Generally this -+ * involves pinning the underlying object and updating the frontbuffer tracking -+ * bits. Some older platforms need special physical address handling for -+ * cursor planes. -+ * -+ * Must be called with struct_mutex held. -+ * -+ * Returns 0 on success, negative error code on failure. -+ */ -+int -+intel_prepare_plane_fb(struct drm_plane *plane, -+ struct drm_plane_state *new_state) -+{ -+ struct intel_atomic_state *intel_state = -+ to_intel_atomic_state(new_state->state); -+ struct drm_i915_private *dev_priv = to_i915(plane->dev); -+ struct drm_framebuffer *fb = new_state->fb; -+ struct drm_i915_gem_object *obj = intel_fb_obj(fb); -+ struct drm_i915_gem_object *old_obj = intel_fb_obj(plane->state->fb); -+ int ret; -+ -+ if (old_obj) { -+ struct drm_crtc_state *crtc_state = -+ drm_atomic_get_new_crtc_state(new_state->state, -+ plane->state->crtc); -+ -+ /* Big Hammer, we also need to ensure that any pending -+ * MI_WAIT_FOR_EVENT inside a user batch buffer on the -+ * current scanout is retired before unpinning the old -+ * framebuffer. Note that we rely on userspace rendering -+ * into the buffer attached to the pipe they are waiting -+ * on. If not, userspace generates a GPU hang with IPEHR -+ * point to the MI_WAIT_FOR_EVENT. -+ * -+ * This should only fail upon a hung GPU, in which case we -+ * can safely continue. -+ */ -+ if (needs_modeset(crtc_state)) { -+ ret = i915_sw_fence_await_reservation(&intel_state->commit_ready, -+ old_obj->resv, NULL, -+ false, 0, -+ GFP_KERNEL); -+ if (ret < 0) -+ return ret; -+ } -+ } -+ -+ if (new_state->fence) { /* explicit fencing */ -+ ret = i915_sw_fence_await_dma_fence(&intel_state->commit_ready, -+ new_state->fence, -+ I915_FENCE_TIMEOUT, -+ GFP_KERNEL); -+ if (ret < 0) -+ return ret; -+ } -+ -+ if (!obj) -+ return 0; -+ -+ ret = i915_gem_object_pin_pages(obj); -+ if (ret) -+ return ret; -+ -+ ret = mutex_lock_interruptible(&dev_priv->drm.struct_mutex); -+ if (ret) { -+ i915_gem_object_unpin_pages(obj); -+ return ret; -+ } -+ -+ ret = intel_plane_pin_fb(to_intel_plane_state(new_state)); -+ -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+ i915_gem_object_unpin_pages(obj); -+ if (ret) -+ return ret; -+ -+ fb_obj_bump_render_priority(obj); -+ intel_fb_obj_flush(obj, ORIGIN_DIRTYFB); -+ -+ if (!new_state->fence) { /* implicit fencing */ -+ struct dma_fence *fence; -+ -+ ret = i915_sw_fence_await_reservation(&intel_state->commit_ready, -+ obj->resv, NULL, -+ false, I915_FENCE_TIMEOUT, -+ GFP_KERNEL); -+ if (ret < 0) -+ return ret; -+ -+ fence = reservation_object_get_excl_rcu(obj->resv); -+ if (fence) { -+ add_rps_boost_after_vblank(new_state->crtc, fence); -+ dma_fence_put(fence); -+ } -+ } else { -+ add_rps_boost_after_vblank(new_state->crtc, new_state->fence); -+ } -+ -+ /* -+ * We declare pageflips to be interactive and so merit a small bias -+ * towards upclocking to deliver the frame on time. By only changing -+ * the RPS thresholds to sample more regularly and aim for higher -+ * clocks we can hopefully deliver low power workloads (like kodi) -+ * that are not quite steady state without resorting to forcing -+ * maximum clocks following a vblank miss (see do_rps_boost()). -+ */ -+ if (!intel_state->rps_interactive) { -+ intel_rps_mark_interactive(dev_priv, true); -+ intel_state->rps_interactive = true; -+ } -+ -+ return 0; -+} -+ -+/** -+ * intel_cleanup_plane_fb - Cleans up an fb after plane use -+ * @plane: drm plane to clean up for -+ * @old_state: the state from the previous modeset -+ * -+ * Cleans up a framebuffer that has just been removed from a plane. -+ * -+ * Must be called with struct_mutex held. -+ */ -+void -+intel_cleanup_plane_fb(struct drm_plane *plane, -+ struct drm_plane_state *old_state) -+{ -+ struct intel_atomic_state *intel_state = -+ to_intel_atomic_state(old_state->state); -+ struct drm_i915_private *dev_priv = to_i915(plane->dev); -+ -+ if (intel_state->rps_interactive) { -+ intel_rps_mark_interactive(dev_priv, false); -+ intel_state->rps_interactive = false; -+ } -+ -+ /* Should only be called after a successful intel_prepare_plane_fb()! */ -+ mutex_lock(&dev_priv->drm.struct_mutex); -+ intel_plane_unpin_fb(to_intel_plane_state(old_state)); -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+} -+ -+int -+skl_max_scale(const struct intel_crtc_state *crtc_state, -+ u32 pixel_format) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ int max_scale, mult; -+ int crtc_clock, max_dotclk, tmpclk1, tmpclk2; -+ -+ if (!crtc_state->base.enable) -+ return DRM_PLANE_HELPER_NO_SCALING; -+ -+ crtc_clock = crtc_state->base.adjusted_mode.crtc_clock; -+ max_dotclk = to_intel_atomic_state(crtc_state->base.state)->cdclk.logical.cdclk; -+ -+ if (IS_GEMINILAKE(dev_priv) || INTEL_GEN(dev_priv) >= 10) -+ max_dotclk *= 2; -+ -+ if (WARN_ON_ONCE(!crtc_clock || max_dotclk < crtc_clock)) -+ return DRM_PLANE_HELPER_NO_SCALING; -+ -+ /* -+ * skl max scale is lower of: -+ * close to 3 but not 3, -1 is for that purpose -+ * or -+ * cdclk/crtc_clock -+ */ -+ mult = is_planar_yuv_format(pixel_format) ? 2 : 3; -+ tmpclk1 = (1 << 16) * mult - 1; -+ tmpclk2 = (1 << 8) * ((max_dotclk << 8) / crtc_clock); -+ max_scale = min(tmpclk1, tmpclk2); -+ -+ return max_scale; -+} -+ -+static void intel_begin_crtc_commit(struct intel_atomic_state *state, -+ struct intel_crtc *crtc) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct intel_crtc_state *old_crtc_state = -+ intel_atomic_get_old_crtc_state(state, crtc); -+ struct intel_crtc_state *new_crtc_state = -+ intel_atomic_get_new_crtc_state(state, crtc); -+ bool modeset = needs_modeset(&new_crtc_state->base); -+ -+ /* Perform vblank evasion around commit operation */ -+ intel_pipe_update_start(new_crtc_state); -+ -+ if (modeset) -+ goto out; -+ -+ if (new_crtc_state->base.color_mgmt_changed || -+ new_crtc_state->update_pipe) -+ intel_color_commit(new_crtc_state); -+ -+ if (new_crtc_state->update_pipe) -+ intel_update_pipe_config(old_crtc_state, new_crtc_state); -+ else if (INTEL_GEN(dev_priv) >= 9) -+ skl_detach_scalers(new_crtc_state); -+ -+out: -+ if (dev_priv->display.atomic_update_watermarks) -+ dev_priv->display.atomic_update_watermarks(state, -+ new_crtc_state); -+} -+ -+void intel_crtc_arm_fifo_underrun(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ if (!IS_GEN(dev_priv, 2)) -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, true); -+ -+ if (crtc_state->has_pch_encoder) { -+ enum pipe pch_transcoder = -+ intel_crtc_pch_transcoder(crtc); -+ -+ intel_set_pch_fifo_underrun_reporting(dev_priv, pch_transcoder, true); -+ } -+} -+ -+static void intel_finish_crtc_commit(struct intel_atomic_state *state, -+ struct intel_crtc *crtc) -+{ -+ struct intel_crtc_state *old_crtc_state = -+ intel_atomic_get_old_crtc_state(state, crtc); -+ struct intel_crtc_state *new_crtc_state = -+ intel_atomic_get_new_crtc_state(state, crtc); -+ -+ intel_pipe_update_end(new_crtc_state); -+ -+ if (new_crtc_state->update_pipe && -+ !needs_modeset(&new_crtc_state->base) && -+ old_crtc_state->base.mode.private_flags & I915_MODE_FLAG_INHERITED) -+ intel_crtc_arm_fifo_underrun(crtc, new_crtc_state); -+} -+ -+/** -+ * intel_plane_destroy - destroy a plane -+ * @plane: plane to destroy -+ * -+ * Common destruction function for all types of planes (primary, cursor, -+ * sprite). -+ */ -+void intel_plane_destroy(struct drm_plane *plane) -+{ -+ drm_plane_cleanup(plane); -+ kfree(to_intel_plane(plane)); -+} -+ -+static bool i8xx_plane_format_mod_supported(struct drm_plane *_plane, -+ u32 format, u64 modifier) -+{ -+ switch (modifier) { -+ case DRM_FORMAT_MOD_LINEAR: -+ case I915_FORMAT_MOD_X_TILED: -+ break; -+ default: -+ return false; -+ } -+ -+ switch (format) { -+ case DRM_FORMAT_C8: -+ case DRM_FORMAT_RGB565: -+ case DRM_FORMAT_XRGB1555: -+ case DRM_FORMAT_XRGB8888: -+ return modifier == DRM_FORMAT_MOD_LINEAR || -+ modifier == I915_FORMAT_MOD_X_TILED; -+ default: -+ return false; -+ } -+} -+ -+static bool i965_plane_format_mod_supported(struct drm_plane *_plane, -+ u32 format, u64 modifier) -+{ -+ switch (modifier) { -+ case DRM_FORMAT_MOD_LINEAR: -+ case I915_FORMAT_MOD_X_TILED: -+ break; -+ default: -+ return false; -+ } -+ -+ switch (format) { -+ case DRM_FORMAT_C8: -+ case DRM_FORMAT_RGB565: -+ case DRM_FORMAT_XRGB8888: -+ case DRM_FORMAT_XBGR8888: -+ case DRM_FORMAT_XRGB2101010: -+ case DRM_FORMAT_XBGR2101010: -+ return modifier == DRM_FORMAT_MOD_LINEAR || -+ modifier == I915_FORMAT_MOD_X_TILED; -+ default: -+ return false; -+ } -+} -+ -+static bool intel_cursor_format_mod_supported(struct drm_plane *_plane, -+ u32 format, u64 modifier) -+{ -+ return modifier == DRM_FORMAT_MOD_LINEAR && -+ format == DRM_FORMAT_ARGB8888; -+} -+ -+static const struct drm_plane_funcs i965_plane_funcs = { -+ .update_plane = drm_atomic_helper_update_plane, -+ .disable_plane = drm_atomic_helper_disable_plane, -+ .destroy = intel_plane_destroy, -+ .atomic_get_property = intel_plane_atomic_get_property, -+ .atomic_set_property = intel_plane_atomic_set_property, -+ .atomic_duplicate_state = intel_plane_duplicate_state, -+ .atomic_destroy_state = intel_plane_destroy_state, -+ .format_mod_supported = i965_plane_format_mod_supported, -+}; -+ -+static const struct drm_plane_funcs i8xx_plane_funcs = { -+ .update_plane = drm_atomic_helper_update_plane, -+ .disable_plane = drm_atomic_helper_disable_plane, -+ .destroy = intel_plane_destroy, -+ .atomic_get_property = intel_plane_atomic_get_property, -+ .atomic_set_property = intel_plane_atomic_set_property, -+ .atomic_duplicate_state = intel_plane_duplicate_state, -+ .atomic_destroy_state = intel_plane_destroy_state, -+ .format_mod_supported = i8xx_plane_format_mod_supported, -+}; -+ -+static int -+intel_legacy_cursor_update(struct drm_plane *plane, -+ struct drm_crtc *crtc, -+ struct drm_framebuffer *fb, -+ int crtc_x, int crtc_y, -+ unsigned int crtc_w, unsigned int crtc_h, -+ u32 src_x, u32 src_y, -+ u32 src_w, u32 src_h, -+ struct drm_modeset_acquire_ctx *ctx) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc->dev); -+ int ret; -+ struct drm_plane_state *old_plane_state, *new_plane_state; -+ struct intel_plane *intel_plane = to_intel_plane(plane); -+ struct drm_framebuffer *old_fb; -+ struct intel_crtc_state *crtc_state = -+ to_intel_crtc_state(crtc->state); -+ struct intel_crtc_state *new_crtc_state; -+ -+ /* -+ * When crtc is inactive or there is a modeset pending, -+ * wait for it to complete in the slowpath -+ */ -+ if (!crtc_state->base.active || needs_modeset(&crtc_state->base) || -+ crtc_state->update_pipe) -+ goto slow; -+ -+ old_plane_state = plane->state; -+ /* -+ * Don't do an async update if there is an outstanding commit modifying -+ * the plane. This prevents our async update's changes from getting -+ * overridden by a previous synchronous update's state. -+ */ -+ if (old_plane_state->commit && -+ !try_wait_for_completion(&old_plane_state->commit->hw_done)) -+ goto slow; -+ -+ /* -+ * If any parameters change that may affect watermarks, -+ * take the slowpath. Only changing fb or position should be -+ * in the fastpath. -+ */ -+ if (old_plane_state->crtc != crtc || -+ old_plane_state->src_w != src_w || -+ old_plane_state->src_h != src_h || -+ old_plane_state->crtc_w != crtc_w || -+ old_plane_state->crtc_h != crtc_h || -+ !old_plane_state->fb != !fb) -+ goto slow; -+ -+ new_plane_state = intel_plane_duplicate_state(plane); -+ if (!new_plane_state) -+ return -ENOMEM; -+ -+ new_crtc_state = to_intel_crtc_state(intel_crtc_duplicate_state(crtc)); -+ if (!new_crtc_state) { -+ ret = -ENOMEM; -+ goto out_free; -+ } -+ -+ drm_atomic_set_fb_for_plane(new_plane_state, fb); -+ -+ new_plane_state->src_x = src_x; -+ new_plane_state->src_y = src_y; -+ new_plane_state->src_w = src_w; -+ new_plane_state->src_h = src_h; -+ new_plane_state->crtc_x = crtc_x; -+ new_plane_state->crtc_y = crtc_y; -+ new_plane_state->crtc_w = crtc_w; -+ new_plane_state->crtc_h = crtc_h; -+ -+ ret = intel_plane_atomic_check_with_state(crtc_state, new_crtc_state, -+ to_intel_plane_state(old_plane_state), -+ to_intel_plane_state(new_plane_state)); -+ if (ret) -+ goto out_free; -+ -+ ret = mutex_lock_interruptible(&dev_priv->drm.struct_mutex); -+ if (ret) -+ goto out_free; -+ -+ ret = intel_plane_pin_fb(to_intel_plane_state(new_plane_state)); -+ if (ret) -+ goto out_unlock; -+ -+ intel_fb_obj_flush(intel_fb_obj(fb), ORIGIN_FLIP); -+ -+ old_fb = old_plane_state->fb; -+ i915_gem_track_fb(intel_fb_obj(old_fb), intel_fb_obj(fb), -+ intel_plane->frontbuffer_bit); -+ -+ /* Swap plane state */ -+ plane->state = new_plane_state; -+ -+ /* -+ * We cannot swap crtc_state as it may be in use by an atomic commit or -+ * page flip that's running simultaneously. If we swap crtc_state and -+ * destroy the old state, we will cause a use-after-free there. -+ * -+ * Only update active_planes, which is needed for our internal -+ * bookkeeping. Either value will do the right thing when updating -+ * planes atomically. If the cursor was part of the atomic update then -+ * we would have taken the slowpath. -+ */ -+ crtc_state->active_planes = new_crtc_state->active_planes; -+ -+ if (plane->state->visible) -+ intel_update_plane(intel_plane, crtc_state, -+ to_intel_plane_state(plane->state)); -+ else -+ intel_disable_plane(intel_plane, crtc_state); -+ -+ intel_plane_unpin_fb(to_intel_plane_state(old_plane_state)); -+ -+out_unlock: -+ mutex_unlock(&dev_priv->drm.struct_mutex); -+out_free: -+ if (new_crtc_state) -+ intel_crtc_destroy_state(crtc, &new_crtc_state->base); -+ if (ret) -+ intel_plane_destroy_state(plane, new_plane_state); -+ else -+ intel_plane_destroy_state(plane, old_plane_state); -+ return ret; -+ -+slow: -+ return drm_atomic_helper_update_plane(plane, crtc, fb, -+ crtc_x, crtc_y, crtc_w, crtc_h, -+ src_x, src_y, src_w, src_h, ctx); -+} -+ -+static const struct drm_plane_funcs intel_cursor_plane_funcs = { -+ .update_plane = intel_legacy_cursor_update, -+ .disable_plane = drm_atomic_helper_disable_plane, -+ .destroy = intel_plane_destroy, -+ .atomic_get_property = intel_plane_atomic_get_property, -+ .atomic_set_property = intel_plane_atomic_set_property, -+ .atomic_duplicate_state = intel_plane_duplicate_state, -+ .atomic_destroy_state = intel_plane_destroy_state, -+ .format_mod_supported = intel_cursor_format_mod_supported, -+}; -+ -+static bool i9xx_plane_has_fbc(struct drm_i915_private *dev_priv, -+ enum i9xx_plane_id i9xx_plane) -+{ -+ if (!HAS_FBC(dev_priv)) -+ return false; -+ -+ if (IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) -+ return i9xx_plane == PLANE_A; /* tied to pipe A */ -+ else if (IS_IVYBRIDGE(dev_priv)) -+ return i9xx_plane == PLANE_A || i9xx_plane == PLANE_B || -+ i9xx_plane == PLANE_C; -+ else if (INTEL_GEN(dev_priv) >= 4) -+ return i9xx_plane == PLANE_A || i9xx_plane == PLANE_B; -+ else -+ return i9xx_plane == PLANE_A; -+} -+ -+static struct intel_plane * -+intel_primary_plane_create(struct drm_i915_private *dev_priv, enum pipe pipe) -+{ -+ struct intel_plane *plane; -+ const struct drm_plane_funcs *plane_funcs; -+ unsigned int supported_rotations; -+ unsigned int possible_crtcs; -+ const u64 *modifiers; -+ const u32 *formats; -+ int num_formats; -+ int ret; -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ return skl_universal_plane_create(dev_priv, pipe, -+ PLANE_PRIMARY); -+ -+ plane = intel_plane_alloc(); -+ if (IS_ERR(plane)) -+ return plane; -+ -+ plane->pipe = pipe; -+ /* -+ * On gen2/3 only plane A can do FBC, but the panel fitter and LVDS -+ * port is hooked to pipe B. Hence we want plane A feeding pipe B. -+ */ -+ if (HAS_FBC(dev_priv) && INTEL_GEN(dev_priv) < 4) -+ plane->i9xx_plane = (enum i9xx_plane_id) !pipe; -+ else -+ plane->i9xx_plane = (enum i9xx_plane_id) pipe; -+ plane->id = PLANE_PRIMARY; -+ plane->frontbuffer_bit = INTEL_FRONTBUFFER(pipe, plane->id); -+ -+ plane->has_fbc = i9xx_plane_has_fbc(dev_priv, plane->i9xx_plane); -+ if (plane->has_fbc) { -+ struct intel_fbc *fbc = &dev_priv->fbc; -+ -+ fbc->possible_framebuffer_bits |= plane->frontbuffer_bit; -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 4) { -+ formats = i965_primary_formats; -+ num_formats = ARRAY_SIZE(i965_primary_formats); -+ modifiers = i9xx_format_modifiers; -+ -+ plane->max_stride = i9xx_plane_max_stride; -+ plane->update_plane = i9xx_update_plane; -+ plane->disable_plane = i9xx_disable_plane; -+ plane->get_hw_state = i9xx_plane_get_hw_state; -+ plane->check_plane = i9xx_plane_check; -+ -+ plane_funcs = &i965_plane_funcs; -+ } else { -+ formats = i8xx_primary_formats; -+ num_formats = ARRAY_SIZE(i8xx_primary_formats); -+ modifiers = i9xx_format_modifiers; -+ -+ plane->max_stride = i9xx_plane_max_stride; -+ plane->update_plane = i9xx_update_plane; -+ plane->disable_plane = i9xx_disable_plane; -+ plane->get_hw_state = i9xx_plane_get_hw_state; -+ plane->check_plane = i9xx_plane_check; -+ -+ plane_funcs = &i8xx_plane_funcs; -+ } -+ -+ possible_crtcs = BIT(pipe); -+ -+ if (INTEL_GEN(dev_priv) >= 5 || IS_G4X(dev_priv)) -+ ret = drm_universal_plane_init(&dev_priv->drm, &plane->base, -+ possible_crtcs, plane_funcs, -+ formats, num_formats, modifiers, -+ DRM_PLANE_TYPE_PRIMARY, -+ "primary %c", pipe_name(pipe)); -+ else -+ ret = drm_universal_plane_init(&dev_priv->drm, &plane->base, -+ possible_crtcs, plane_funcs, -+ formats, num_formats, modifiers, -+ DRM_PLANE_TYPE_PRIMARY, -+ "plane %c", -+ plane_name(plane->i9xx_plane)); -+ if (ret) -+ goto fail; -+ -+ if (IS_CHERRYVIEW(dev_priv) && pipe == PIPE_B) { -+ supported_rotations = -+ DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180 | -+ DRM_MODE_REFLECT_X; -+ } else if (INTEL_GEN(dev_priv) >= 4) { -+ supported_rotations = -+ DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180; -+ } else { -+ supported_rotations = DRM_MODE_ROTATE_0; -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 4) -+ drm_plane_create_rotation_property(&plane->base, -+ DRM_MODE_ROTATE_0, -+ supported_rotations); -+ -+ drm_plane_helper_add(&plane->base, &intel_plane_helper_funcs); -+ -+ return plane; -+ -+fail: -+ intel_plane_free(plane); -+ -+ return ERR_PTR(ret); -+} -+ -+static struct intel_plane * -+intel_cursor_plane_create(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ unsigned int possible_crtcs; -+ struct intel_plane *cursor; -+ int ret; -+ -+ cursor = intel_plane_alloc(); -+ if (IS_ERR(cursor)) -+ return cursor; -+ -+ cursor->pipe = pipe; -+ cursor->i9xx_plane = (enum i9xx_plane_id) pipe; -+ cursor->id = PLANE_CURSOR; -+ cursor->frontbuffer_bit = INTEL_FRONTBUFFER(pipe, cursor->id); -+ -+ if (IS_I845G(dev_priv) || IS_I865G(dev_priv)) { -+ cursor->max_stride = i845_cursor_max_stride; -+ cursor->update_plane = i845_update_cursor; -+ cursor->disable_plane = i845_disable_cursor; -+ cursor->get_hw_state = i845_cursor_get_hw_state; -+ cursor->check_plane = i845_check_cursor; -+ } else { -+ cursor->max_stride = i9xx_cursor_max_stride; -+ cursor->update_plane = i9xx_update_cursor; -+ cursor->disable_plane = i9xx_disable_cursor; -+ cursor->get_hw_state = i9xx_cursor_get_hw_state; -+ cursor->check_plane = i9xx_check_cursor; -+ } -+ -+ cursor->cursor.base = ~0; -+ cursor->cursor.cntl = ~0; -+ -+ if (IS_I845G(dev_priv) || IS_I865G(dev_priv) || HAS_CUR_FBC(dev_priv)) -+ cursor->cursor.size = ~0; -+ -+ possible_crtcs = BIT(pipe); -+ -+ ret = drm_universal_plane_init(&dev_priv->drm, &cursor->base, -+ possible_crtcs, &intel_cursor_plane_funcs, -+ intel_cursor_formats, -+ ARRAY_SIZE(intel_cursor_formats), -+ cursor_format_modifiers, -+ DRM_PLANE_TYPE_CURSOR, -+ "cursor %c", pipe_name(pipe)); -+ if (ret) -+ goto fail; -+ -+ if (INTEL_GEN(dev_priv) >= 4) -+ drm_plane_create_rotation_property(&cursor->base, -+ DRM_MODE_ROTATE_0, -+ DRM_MODE_ROTATE_0 | -+ DRM_MODE_ROTATE_180); -+ -+ drm_plane_helper_add(&cursor->base, &intel_plane_helper_funcs); -+ -+ return cursor; -+ -+fail: -+ intel_plane_free(cursor); -+ -+ return ERR_PTR(ret); -+} -+ -+static void intel_crtc_init_scalers(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc_scaler_state *scaler_state = -+ &crtc_state->scaler_state; -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ int i; -+ -+ crtc->num_scalers = RUNTIME_INFO(dev_priv)->num_scalers[crtc->pipe]; -+ if (!crtc->num_scalers) -+ return; -+ -+ for (i = 0; i < crtc->num_scalers; i++) { -+ struct intel_scaler *scaler = &scaler_state->scalers[i]; -+ -+ scaler->in_use = 0; -+ scaler->mode = 0; -+ } -+ -+ scaler_state->scaler_id = -1; -+} -+ -+static int intel_crtc_init(struct drm_i915_private *dev_priv, enum pipe pipe) -+{ -+ struct intel_crtc *intel_crtc; -+ struct intel_crtc_state *crtc_state = NULL; -+ struct intel_plane *primary = NULL; -+ struct intel_plane *cursor = NULL; -+ int sprite, ret; -+ -+ intel_crtc = kzalloc(sizeof(*intel_crtc), GFP_KERNEL); -+ if (!intel_crtc) -+ return -ENOMEM; -+ -+ crtc_state = kzalloc(sizeof(*crtc_state), GFP_KERNEL); -+ if (!crtc_state) { -+ ret = -ENOMEM; -+ goto fail; -+ } -+ intel_crtc->config = crtc_state; -+ intel_crtc->base.state = &crtc_state->base; -+ crtc_state->base.crtc = &intel_crtc->base; -+ -+ primary = intel_primary_plane_create(dev_priv, pipe); -+ if (IS_ERR(primary)) { -+ ret = PTR_ERR(primary); -+ goto fail; -+ } -+ intel_crtc->plane_ids_mask |= BIT(primary->id); -+ -+ for_each_sprite(dev_priv, pipe, sprite) { -+ struct intel_plane *plane; -+ -+ plane = intel_sprite_plane_create(dev_priv, pipe, sprite); -+ if (IS_ERR(plane)) { -+ ret = PTR_ERR(plane); -+ goto fail; -+ } -+ intel_crtc->plane_ids_mask |= BIT(plane->id); -+ } -+ -+ cursor = intel_cursor_plane_create(dev_priv, pipe); -+ if (IS_ERR(cursor)) { -+ ret = PTR_ERR(cursor); -+ goto fail; -+ } -+ intel_crtc->plane_ids_mask |= BIT(cursor->id); -+ -+ ret = drm_crtc_init_with_planes(&dev_priv->drm, &intel_crtc->base, -+ &primary->base, &cursor->base, -+ &intel_crtc_funcs, -+ "pipe %c", pipe_name(pipe)); -+ if (ret) -+ goto fail; -+ -+ intel_crtc->pipe = pipe; -+ -+ /* initialize shared scalers */ -+ intel_crtc_init_scalers(intel_crtc, crtc_state); -+ -+ BUG_ON(pipe >= ARRAY_SIZE(dev_priv->pipe_to_crtc_mapping) || -+ dev_priv->pipe_to_crtc_mapping[pipe] != NULL); -+ dev_priv->pipe_to_crtc_mapping[pipe] = intel_crtc; -+ -+ if (INTEL_GEN(dev_priv) < 9) { -+ enum i9xx_plane_id i9xx_plane = primary->i9xx_plane; -+ -+ BUG_ON(i9xx_plane >= ARRAY_SIZE(dev_priv->plane_to_crtc_mapping) || -+ dev_priv->plane_to_crtc_mapping[i9xx_plane] != NULL); -+ dev_priv->plane_to_crtc_mapping[i9xx_plane] = intel_crtc; -+ } -+ -+ drm_crtc_helper_add(&intel_crtc->base, &intel_helper_funcs); -+ -+ intel_color_init(intel_crtc); -+ -+ WARN_ON(drm_crtc_index(&intel_crtc->base) != intel_crtc->pipe); -+ -+ return 0; -+ -+fail: -+ /* -+ * drm_mode_config_cleanup() will free up any -+ * crtcs/planes already initialized. -+ */ -+ kfree(crtc_state); -+ kfree(intel_crtc); -+ -+ return ret; -+} -+ -+int intel_get_pipe_from_crtc_id_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file) -+{ -+ struct drm_i915_get_pipe_from_crtc_id *pipe_from_crtc_id = data; -+ struct drm_crtc *drmmode_crtc; -+ struct intel_crtc *crtc; -+ -+ drmmode_crtc = drm_crtc_find(dev, file, pipe_from_crtc_id->crtc_id); -+ if (!drmmode_crtc) -+ return -ENOENT; -+ -+ crtc = to_intel_crtc(drmmode_crtc); -+ pipe_from_crtc_id->pipe = crtc->pipe; -+ -+ return 0; -+} -+ -+static int intel_encoder_clones(struct intel_encoder *encoder) -+{ -+ struct drm_device *dev = encoder->base.dev; -+ struct intel_encoder *source_encoder; -+ int index_mask = 0; -+ int entry = 0; -+ -+ for_each_intel_encoder(dev, source_encoder) { -+ if (encoders_cloneable(encoder, source_encoder)) -+ index_mask |= (1 << entry); -+ -+ entry++; -+ } -+ -+ return index_mask; -+} -+ -+static bool ilk_has_edp_a(struct drm_i915_private *dev_priv) -+{ -+ if (!IS_MOBILE(dev_priv)) -+ return false; -+ -+ if ((I915_READ(DP_A) & DP_DETECTED) == 0) -+ return false; -+ -+ if (IS_GEN(dev_priv, 5) && (I915_READ(FUSE_STRAP) & ILK_eDP_A_DISABLE)) -+ return false; -+ -+ return true; -+} -+ -+static bool intel_ddi_crt_present(struct drm_i915_private *dev_priv) -+{ -+ if (INTEL_GEN(dev_priv) >= 9) -+ return false; -+ -+ if (IS_HSW_ULT(dev_priv) || IS_BDW_ULT(dev_priv)) -+ return false; -+ -+ if (HAS_PCH_LPT_H(dev_priv) && -+ I915_READ(SFUSE_STRAP) & SFUSE_STRAP_CRT_DISABLED) -+ return false; -+ -+ /* DDI E can't be used if DDI A requires 4 lanes */ -+ if (I915_READ(DDI_BUF_CTL(PORT_A)) & DDI_A_4_LANES) -+ return false; -+ -+ if (!dev_priv->vbt.int_crt_support) -+ return false; -+ -+ return true; -+} -+ -+void intel_pps_unlock_regs_wa(struct drm_i915_private *dev_priv) -+{ -+ int pps_num; -+ int pps_idx; -+ -+ if (HAS_DDI(dev_priv)) -+ return; -+ /* -+ * This w/a is needed at least on CPT/PPT, but to be sure apply it -+ * everywhere where registers can be write protected. -+ */ -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ pps_num = 2; -+ else -+ pps_num = 1; -+ -+ for (pps_idx = 0; pps_idx < pps_num; pps_idx++) { -+ u32 val = I915_READ(PP_CONTROL(pps_idx)); -+ -+ val = (val & ~PANEL_UNLOCK_MASK) | PANEL_UNLOCK_REGS; -+ I915_WRITE(PP_CONTROL(pps_idx), val); -+ } -+} -+ -+static void intel_pps_init(struct drm_i915_private *dev_priv) -+{ -+ if (HAS_PCH_SPLIT(dev_priv) || IS_GEN9_LP(dev_priv)) -+ dev_priv->pps_mmio_base = PCH_PPS_BASE; -+ else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ dev_priv->pps_mmio_base = VLV_PPS_BASE; -+ else -+ dev_priv->pps_mmio_base = PPS_BASE; -+ -+ intel_pps_unlock_regs_wa(dev_priv); -+} -+ -+static void intel_setup_outputs(struct drm_i915_private *dev_priv) -+{ -+ struct intel_encoder *encoder; -+ bool dpd_is_edp = false; -+ -+ intel_pps_init(dev_priv); -+ -+ if (!HAS_DISPLAY(dev_priv)) -+ return; -+ -+ if (IS_ELKHARTLAKE(dev_priv)) { -+ intel_ddi_init(dev_priv, PORT_A); -+ intel_ddi_init(dev_priv, PORT_B); -+ intel_ddi_init(dev_priv, PORT_C); -+ icl_dsi_init(dev_priv); -+ } else if (INTEL_GEN(dev_priv) >= 11) { -+ intel_ddi_init(dev_priv, PORT_A); -+ intel_ddi_init(dev_priv, PORT_B); -+ intel_ddi_init(dev_priv, PORT_C); -+ intel_ddi_init(dev_priv, PORT_D); -+ intel_ddi_init(dev_priv, PORT_E); -+ /* -+ * On some ICL SKUs port F is not present. No strap bits for -+ * this, so rely on VBT. -+ * Work around broken VBTs on SKUs known to have no port F. -+ */ -+ if (IS_ICL_WITH_PORT_F(dev_priv) && -+ intel_bios_is_port_present(dev_priv, PORT_F)) -+ intel_ddi_init(dev_priv, PORT_F); -+ -+ icl_dsi_init(dev_priv); -+ } else if (IS_GEN9_LP(dev_priv)) { -+ /* -+ * FIXME: Broxton doesn't support port detection via the -+ * DDI_BUF_CTL_A or SFUSE_STRAP registers, find another way to -+ * detect the ports. -+ */ -+ intel_ddi_init(dev_priv, PORT_A); -+ intel_ddi_init(dev_priv, PORT_B); -+ intel_ddi_init(dev_priv, PORT_C); -+ -+ vlv_dsi_init(dev_priv); -+ } else if (HAS_DDI(dev_priv)) { -+ int found; -+ -+ if (intel_ddi_crt_present(dev_priv)) -+ intel_crt_init(dev_priv); -+ -+ /* -+ * Haswell uses DDI functions to detect digital outputs. -+ * On SKL pre-D0 the strap isn't connected, so we assume -+ * it's there. -+ */ -+ found = I915_READ(DDI_BUF_CTL(PORT_A)) & DDI_INIT_DISPLAY_DETECTED; -+ /* WaIgnoreDDIAStrap: skl */ -+ if (found || IS_GEN9_BC(dev_priv)) -+ intel_ddi_init(dev_priv, PORT_A); -+ -+ /* DDI B, C, D, and F detection is indicated by the SFUSE_STRAP -+ * register */ -+ found = I915_READ(SFUSE_STRAP); -+ -+ if (found & SFUSE_STRAP_DDIB_DETECTED) -+ intel_ddi_init(dev_priv, PORT_B); -+ if (found & SFUSE_STRAP_DDIC_DETECTED) -+ intel_ddi_init(dev_priv, PORT_C); -+ if (found & SFUSE_STRAP_DDID_DETECTED) -+ intel_ddi_init(dev_priv, PORT_D); -+ if (found & SFUSE_STRAP_DDIF_DETECTED) -+ intel_ddi_init(dev_priv, PORT_F); -+ /* -+ * On SKL we don't have a way to detect DDI-E so we rely on VBT. -+ */ -+ if (IS_GEN9_BC(dev_priv) && -+ intel_bios_is_port_present(dev_priv, PORT_E)) -+ intel_ddi_init(dev_priv, PORT_E); -+ -+ } else if (HAS_PCH_SPLIT(dev_priv)) { -+ int found; -+ -+ /* -+ * intel_edp_init_connector() depends on this completing first, -+ * to prevent the registration of both eDP and LVDS and the -+ * incorrect sharing of the PPS. -+ */ -+ intel_lvds_init(dev_priv); -+ intel_crt_init(dev_priv); -+ -+ dpd_is_edp = intel_dp_is_port_edp(dev_priv, PORT_D); -+ -+ if (ilk_has_edp_a(dev_priv)) -+ intel_dp_init(dev_priv, DP_A, PORT_A); -+ -+ if (I915_READ(PCH_HDMIB) & SDVO_DETECTED) { -+ /* PCH SDVOB multiplex with HDMIB */ -+ found = intel_sdvo_init(dev_priv, PCH_SDVOB, PORT_B); -+ if (!found) -+ intel_hdmi_init(dev_priv, PCH_HDMIB, PORT_B); -+ if (!found && (I915_READ(PCH_DP_B) & DP_DETECTED)) -+ intel_dp_init(dev_priv, PCH_DP_B, PORT_B); -+ } -+ -+ if (I915_READ(PCH_HDMIC) & SDVO_DETECTED) -+ intel_hdmi_init(dev_priv, PCH_HDMIC, PORT_C); -+ -+ if (!dpd_is_edp && I915_READ(PCH_HDMID) & SDVO_DETECTED) -+ intel_hdmi_init(dev_priv, PCH_HDMID, PORT_D); -+ -+ if (I915_READ(PCH_DP_C) & DP_DETECTED) -+ intel_dp_init(dev_priv, PCH_DP_C, PORT_C); -+ -+ if (I915_READ(PCH_DP_D) & DP_DETECTED) -+ intel_dp_init(dev_priv, PCH_DP_D, PORT_D); -+ } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ bool has_edp, has_port; -+ -+ if (IS_VALLEYVIEW(dev_priv) && dev_priv->vbt.int_crt_support) -+ intel_crt_init(dev_priv); -+ -+ /* -+ * The DP_DETECTED bit is the latched state of the DDC -+ * SDA pin at boot. However since eDP doesn't require DDC -+ * (no way to plug in a DP->HDMI dongle) the DDC pins for -+ * eDP ports may have been muxed to an alternate function. -+ * Thus we can't rely on the DP_DETECTED bit alone to detect -+ * eDP ports. Consult the VBT as well as DP_DETECTED to -+ * detect eDP ports. -+ * -+ * Sadly the straps seem to be missing sometimes even for HDMI -+ * ports (eg. on Voyo V3 - CHT x7-Z8700), so check both strap -+ * and VBT for the presence of the port. Additionally we can't -+ * trust the port type the VBT declares as we've seen at least -+ * HDMI ports that the VBT claim are DP or eDP. -+ */ -+ has_edp = intel_dp_is_port_edp(dev_priv, PORT_B); -+ has_port = intel_bios_is_port_present(dev_priv, PORT_B); -+ if (I915_READ(VLV_DP_B) & DP_DETECTED || has_port) -+ has_edp &= intel_dp_init(dev_priv, VLV_DP_B, PORT_B); -+ if ((I915_READ(VLV_HDMIB) & SDVO_DETECTED || has_port) && !has_edp) -+ intel_hdmi_init(dev_priv, VLV_HDMIB, PORT_B); -+ -+ has_edp = intel_dp_is_port_edp(dev_priv, PORT_C); -+ has_port = intel_bios_is_port_present(dev_priv, PORT_C); -+ if (I915_READ(VLV_DP_C) & DP_DETECTED || has_port) -+ has_edp &= intel_dp_init(dev_priv, VLV_DP_C, PORT_C); -+ if ((I915_READ(VLV_HDMIC) & SDVO_DETECTED || has_port) && !has_edp) -+ intel_hdmi_init(dev_priv, VLV_HDMIC, PORT_C); -+ -+ if (IS_CHERRYVIEW(dev_priv)) { -+ /* -+ * eDP not supported on port D, -+ * so no need to worry about it -+ */ -+ has_port = intel_bios_is_port_present(dev_priv, PORT_D); -+ if (I915_READ(CHV_DP_D) & DP_DETECTED || has_port) -+ intel_dp_init(dev_priv, CHV_DP_D, PORT_D); -+ if (I915_READ(CHV_HDMID) & SDVO_DETECTED || has_port) -+ intel_hdmi_init(dev_priv, CHV_HDMID, PORT_D); -+ } -+ -+ vlv_dsi_init(dev_priv); -+ } else if (IS_PINEVIEW(dev_priv)) { -+ intel_lvds_init(dev_priv); -+ intel_crt_init(dev_priv); -+ } else if (IS_GEN_RANGE(dev_priv, 3, 4)) { -+ bool found = false; -+ -+ if (IS_MOBILE(dev_priv)) -+ intel_lvds_init(dev_priv); -+ -+ intel_crt_init(dev_priv); -+ -+ if (I915_READ(GEN3_SDVOB) & SDVO_DETECTED) { -+ DRM_DEBUG_KMS("probing SDVOB\n"); -+ found = intel_sdvo_init(dev_priv, GEN3_SDVOB, PORT_B); -+ if (!found && IS_G4X(dev_priv)) { -+ DRM_DEBUG_KMS("probing HDMI on SDVOB\n"); -+ intel_hdmi_init(dev_priv, GEN4_HDMIB, PORT_B); -+ } -+ -+ if (!found && IS_G4X(dev_priv)) -+ intel_dp_init(dev_priv, DP_B, PORT_B); -+ } -+ -+ /* Before G4X SDVOC doesn't have its own detect register */ -+ -+ if (I915_READ(GEN3_SDVOB) & SDVO_DETECTED) { -+ DRM_DEBUG_KMS("probing SDVOC\n"); -+ found = intel_sdvo_init(dev_priv, GEN3_SDVOC, PORT_C); -+ } -+ -+ if (!found && (I915_READ(GEN3_SDVOC) & SDVO_DETECTED)) { -+ -+ if (IS_G4X(dev_priv)) { -+ DRM_DEBUG_KMS("probing HDMI on SDVOC\n"); -+ intel_hdmi_init(dev_priv, GEN4_HDMIC, PORT_C); -+ } -+ if (IS_G4X(dev_priv)) -+ intel_dp_init(dev_priv, DP_C, PORT_C); -+ } -+ -+ if (IS_G4X(dev_priv) && (I915_READ(DP_D) & DP_DETECTED)) -+ intel_dp_init(dev_priv, DP_D, PORT_D); -+ -+ if (SUPPORTS_TV(dev_priv)) -+ intel_tv_init(dev_priv); -+ } else if (IS_GEN(dev_priv, 2)) { -+ if (IS_I85X(dev_priv)) -+ intel_lvds_init(dev_priv); -+ -+ intel_crt_init(dev_priv); -+ intel_dvo_init(dev_priv); -+ } -+ -+ intel_psr_init(dev_priv); -+ -+ for_each_intel_encoder(&dev_priv->drm, encoder) { -+ encoder->base.possible_crtcs = encoder->crtc_mask; -+ encoder->base.possible_clones = -+ intel_encoder_clones(encoder); -+ } -+ -+ intel_init_pch_refclk(dev_priv); -+ -+ drm_helper_move_panel_connectors_to_head(&dev_priv->drm); -+} -+ -+static void intel_user_framebuffer_destroy(struct drm_framebuffer *fb) -+{ -+ struct intel_framebuffer *intel_fb = to_intel_framebuffer(fb); -+ struct drm_i915_gem_object *obj = intel_fb_obj(fb); -+ -+ drm_framebuffer_cleanup(fb); -+ -+ i915_gem_object_lock(obj); -+ WARN_ON(!obj->framebuffer_references--); -+ i915_gem_object_unlock(obj); -+ -+ i915_gem_object_put(obj); -+ -+ kfree(intel_fb); -+} -+ -+static int intel_user_framebuffer_create_handle(struct drm_framebuffer *fb, -+ struct drm_file *file, -+ unsigned int *handle) -+{ -+ struct drm_i915_gem_object *obj = intel_fb_obj(fb); -+ -+ if (obj->userptr.mm) { -+ DRM_DEBUG("attempting to use a userptr for a framebuffer, denied\n"); -+ return -EINVAL; -+ } -+ -+ return drm_gem_handle_create(file, &obj->base, handle); -+} -+ -+static int intel_user_framebuffer_dirty(struct drm_framebuffer *fb, -+ struct drm_file *file, -+ unsigned flags, unsigned color, -+ struct drm_clip_rect *clips, -+ unsigned num_clips) -+{ -+ struct drm_i915_gem_object *obj = intel_fb_obj(fb); -+ -+ i915_gem_object_flush_if_display(obj); -+ intel_fb_obj_flush(obj, ORIGIN_DIRTYFB); -+ -+ return 0; -+} -+ -+static const struct drm_framebuffer_funcs intel_fb_funcs = { -+ .destroy = intel_user_framebuffer_destroy, -+ .create_handle = intel_user_framebuffer_create_handle, -+ .dirty = intel_user_framebuffer_dirty, -+}; -+ -+static -+u32 intel_fb_pitch_limit(struct drm_i915_private *dev_priv, -+ u32 pixel_format, u64 fb_modifier) -+{ -+ struct intel_crtc *crtc; -+ struct intel_plane *plane; -+ -+ /* -+ * We assume the primary plane for pipe A has -+ * the highest stride limits of them all. -+ */ -+ crtc = intel_get_crtc_for_pipe(dev_priv, PIPE_A); -+ plane = to_intel_plane(crtc->base.primary); -+ -+ return plane->max_stride(plane, pixel_format, fb_modifier, -+ DRM_MODE_ROTATE_0); -+} -+ -+static int intel_framebuffer_init(struct intel_framebuffer *intel_fb, -+ struct drm_i915_gem_object *obj, -+ struct drm_mode_fb_cmd2 *mode_cmd) -+{ -+ struct drm_i915_private *dev_priv = to_i915(obj->base.dev); -+ struct drm_framebuffer *fb = &intel_fb->base; -+ u32 pitch_limit; -+ unsigned int tiling, stride; -+ int ret = -EINVAL; -+ int i; -+ -+ i915_gem_object_lock(obj); -+ obj->framebuffer_references++; -+ tiling = i915_gem_object_get_tiling(obj); -+ stride = i915_gem_object_get_stride(obj); -+ i915_gem_object_unlock(obj); -+ -+ if (mode_cmd->flags & DRM_MODE_FB_MODIFIERS) { -+ /* -+ * If there's a fence, enforce that -+ * the fb modifier and tiling mode match. -+ */ -+ if (tiling != I915_TILING_NONE && -+ tiling != intel_fb_modifier_to_tiling(mode_cmd->modifier[0])) { -+ DRM_DEBUG_KMS("tiling_mode doesn't match fb modifier\n"); -+ goto err; -+ } -+ } else { -+ if (tiling == I915_TILING_X) { -+ mode_cmd->modifier[0] = I915_FORMAT_MOD_X_TILED; -+ } else if (tiling == I915_TILING_Y) { -+ DRM_DEBUG_KMS("No Y tiling for legacy addfb\n"); -+ goto err; -+ } -+ } -+ -+ if (!drm_any_plane_has_format(&dev_priv->drm, -+ mode_cmd->pixel_format, -+ mode_cmd->modifier[0])) { -+ struct drm_format_name_buf format_name; -+ -+ DRM_DEBUG_KMS("unsupported pixel format %s / modifier 0x%llx\n", -+ drm_get_format_name(mode_cmd->pixel_format, -+ &format_name), -+ mode_cmd->modifier[0]); -+ goto err; -+ } -+ -+ /* -+ * gen2/3 display engine uses the fence if present, -+ * so the tiling mode must match the fb modifier exactly. -+ */ -+ if (INTEL_GEN(dev_priv) < 4 && -+ tiling != intel_fb_modifier_to_tiling(mode_cmd->modifier[0])) { -+ DRM_DEBUG_KMS("tiling_mode must match fb modifier exactly on gen2/3\n"); -+ goto err; -+ } -+ -+ pitch_limit = intel_fb_pitch_limit(dev_priv, mode_cmd->pixel_format, -+ mode_cmd->modifier[0]); -+ if (mode_cmd->pitches[0] > pitch_limit) { -+ DRM_DEBUG_KMS("%s pitch (%u) must be at most %d\n", -+ mode_cmd->modifier[0] != DRM_FORMAT_MOD_LINEAR ? -+ "tiled" : "linear", -+ mode_cmd->pitches[0], pitch_limit); -+ goto err; -+ } -+ -+ /* -+ * If there's a fence, enforce that -+ * the fb pitch and fence stride match. -+ */ -+ if (tiling != I915_TILING_NONE && mode_cmd->pitches[0] != stride) { -+ DRM_DEBUG_KMS("pitch (%d) must match tiling stride (%d)\n", -+ mode_cmd->pitches[0], stride); -+ goto err; -+ } -+ -+ /* FIXME need to adjust LINOFF/TILEOFF accordingly. */ -+ if (mode_cmd->offsets[0] != 0) -+ goto err; -+ -+ drm_helper_mode_fill_fb_struct(&dev_priv->drm, fb, mode_cmd); -+ -+ for (i = 0; i < fb->format->num_planes; i++) { -+ u32 stride_alignment; -+ -+ if (mode_cmd->handles[i] != mode_cmd->handles[0]) { -+ DRM_DEBUG_KMS("bad plane %d handle\n", i); -+ goto err; -+ } -+ -+ stride_alignment = intel_fb_stride_alignment(fb, i); -+ -+ /* -+ * Display WA #0531: skl,bxt,kbl,glk -+ * -+ * Render decompression and plane width > 3840 -+ * combined with horizontal panning requires the -+ * plane stride to be a multiple of 4. We'll just -+ * require the entire fb to accommodate that to avoid -+ * potential runtime errors at plane configuration time. -+ */ -+ if (IS_GEN(dev_priv, 9) && i == 0 && fb->width > 3840 && -+ is_ccs_modifier(fb->modifier)) -+ stride_alignment *= 4; -+ -+ if (fb->pitches[i] & (stride_alignment - 1)) { -+ DRM_DEBUG_KMS("plane %d pitch (%d) must be at least %u byte aligned\n", -+ i, fb->pitches[i], stride_alignment); -+ goto err; -+ } -+ -+ fb->obj[i] = &obj->base; -+ } -+ -+ ret = intel_fill_fb_info(dev_priv, fb); -+ if (ret) -+ goto err; -+ -+ ret = drm_framebuffer_init(&dev_priv->drm, fb, &intel_fb_funcs); -+ if (ret) { -+ DRM_ERROR("framebuffer init failed %d\n", ret); -+ goto err; -+ } -+ -+ return 0; -+ -+err: -+ i915_gem_object_lock(obj); -+ obj->framebuffer_references--; -+ i915_gem_object_unlock(obj); -+ return ret; -+} -+ -+static struct drm_framebuffer * -+intel_user_framebuffer_create(struct drm_device *dev, -+ struct drm_file *filp, -+ const struct drm_mode_fb_cmd2 *user_mode_cmd) -+{ -+ struct drm_framebuffer *fb; -+ struct drm_i915_gem_object *obj; -+ struct drm_mode_fb_cmd2 mode_cmd = *user_mode_cmd; -+ -+ obj = i915_gem_object_lookup(filp, mode_cmd.handles[0]); -+ if (!obj) -+ return ERR_PTR(-ENOENT); -+ -+ fb = intel_framebuffer_create(obj, &mode_cmd); -+ if (IS_ERR(fb)) -+ i915_gem_object_put(obj); -+ -+ return fb; -+} -+ -+static void intel_atomic_state_free(struct drm_atomic_state *state) -+{ -+ struct intel_atomic_state *intel_state = to_intel_atomic_state(state); -+ -+ drm_atomic_state_default_release(state); -+ -+ i915_sw_fence_fini(&intel_state->commit_ready); -+ -+ kfree(state); -+} -+ -+static enum drm_mode_status -+intel_mode_valid(struct drm_device *dev, -+ const struct drm_display_mode *mode) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ int hdisplay_max, htotal_max; -+ int vdisplay_max, vtotal_max; -+ -+ /* -+ * Can't reject DBLSCAN here because Xorg ddxen can add piles -+ * of DBLSCAN modes to the output's mode list when they detect -+ * the scaling mode property on the connector. And they don't -+ * ask the kernel to validate those modes in any way until -+ * modeset time at which point the client gets a protocol error. -+ * So in order to not upset those clients we silently ignore the -+ * DBLSCAN flag on such connectors. For other connectors we will -+ * reject modes with the DBLSCAN flag in encoder->compute_config(). -+ * And we always reject DBLSCAN modes in connector->mode_valid() -+ * as we never want such modes on the connector's mode list. -+ */ -+ -+ if (mode->vscan > 1) -+ return MODE_NO_VSCAN; -+ -+ if (mode->flags & DRM_MODE_FLAG_HSKEW) -+ return MODE_H_ILLEGAL; -+ -+ if (mode->flags & (DRM_MODE_FLAG_CSYNC | -+ DRM_MODE_FLAG_NCSYNC | -+ DRM_MODE_FLAG_PCSYNC)) -+ return MODE_HSYNC; -+ -+ if (mode->flags & (DRM_MODE_FLAG_BCAST | -+ DRM_MODE_FLAG_PIXMUX | -+ DRM_MODE_FLAG_CLKDIV2)) -+ return MODE_BAD; -+ -+ if (INTEL_GEN(dev_priv) >= 9 || -+ IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) { -+ hdisplay_max = 8192; /* FDI max 4096 handled elsewhere */ -+ vdisplay_max = 4096; -+ htotal_max = 8192; -+ vtotal_max = 8192; -+ } else if (INTEL_GEN(dev_priv) >= 3) { -+ hdisplay_max = 4096; -+ vdisplay_max = 4096; -+ htotal_max = 8192; -+ vtotal_max = 8192; -+ } else { -+ hdisplay_max = 2048; -+ vdisplay_max = 2048; -+ htotal_max = 4096; -+ vtotal_max = 4096; -+ } -+ -+ if (mode->hdisplay > hdisplay_max || -+ mode->hsync_start > htotal_max || -+ mode->hsync_end > htotal_max || -+ mode->htotal > htotal_max) -+ return MODE_H_ILLEGAL; -+ -+ if (mode->vdisplay > vdisplay_max || -+ mode->vsync_start > vtotal_max || -+ mode->vsync_end > vtotal_max || -+ mode->vtotal > vtotal_max) -+ return MODE_V_ILLEGAL; -+ -+ return MODE_OK; -+} -+ -+static const struct drm_mode_config_funcs intel_mode_funcs = { -+ .fb_create = intel_user_framebuffer_create, -+ .get_format_info = intel_get_format_info, -+ .output_poll_changed = intel_fbdev_output_poll_changed, -+ .mode_valid = intel_mode_valid, -+ .atomic_check = intel_atomic_check, -+ .atomic_commit = intel_atomic_commit, -+ .atomic_state_alloc = intel_atomic_state_alloc, -+ .atomic_state_clear = intel_atomic_state_clear, -+ .atomic_state_free = intel_atomic_state_free, -+}; -+ -+/** -+ * intel_init_display_hooks - initialize the display modesetting hooks -+ * @dev_priv: device private -+ */ -+void intel_init_display_hooks(struct drm_i915_private *dev_priv) -+{ -+ intel_init_cdclk_hooks(dev_priv); -+ -+ if (INTEL_GEN(dev_priv) >= 9) { -+ dev_priv->display.get_pipe_config = haswell_get_pipe_config; -+ dev_priv->display.get_initial_plane_config = -+ skylake_get_initial_plane_config; -+ dev_priv->display.crtc_compute_clock = -+ haswell_crtc_compute_clock; -+ dev_priv->display.crtc_enable = haswell_crtc_enable; -+ dev_priv->display.crtc_disable = haswell_crtc_disable; -+ } else if (HAS_DDI(dev_priv)) { -+ dev_priv->display.get_pipe_config = haswell_get_pipe_config; -+ dev_priv->display.get_initial_plane_config = -+ i9xx_get_initial_plane_config; -+ dev_priv->display.crtc_compute_clock = -+ haswell_crtc_compute_clock; -+ dev_priv->display.crtc_enable = haswell_crtc_enable; -+ dev_priv->display.crtc_disable = haswell_crtc_disable; -+ } else if (HAS_PCH_SPLIT(dev_priv)) { -+ dev_priv->display.get_pipe_config = ironlake_get_pipe_config; -+ dev_priv->display.get_initial_plane_config = -+ i9xx_get_initial_plane_config; -+ dev_priv->display.crtc_compute_clock = -+ ironlake_crtc_compute_clock; -+ dev_priv->display.crtc_enable = ironlake_crtc_enable; -+ dev_priv->display.crtc_disable = ironlake_crtc_disable; -+ } else if (IS_CHERRYVIEW(dev_priv)) { -+ dev_priv->display.get_pipe_config = i9xx_get_pipe_config; -+ dev_priv->display.get_initial_plane_config = -+ i9xx_get_initial_plane_config; -+ dev_priv->display.crtc_compute_clock = chv_crtc_compute_clock; -+ dev_priv->display.crtc_enable = valleyview_crtc_enable; -+ dev_priv->display.crtc_disable = i9xx_crtc_disable; -+ } else if (IS_VALLEYVIEW(dev_priv)) { -+ dev_priv->display.get_pipe_config = i9xx_get_pipe_config; -+ dev_priv->display.get_initial_plane_config = -+ i9xx_get_initial_plane_config; -+ dev_priv->display.crtc_compute_clock = vlv_crtc_compute_clock; -+ dev_priv->display.crtc_enable = valleyview_crtc_enable; -+ dev_priv->display.crtc_disable = i9xx_crtc_disable; -+ } else if (IS_G4X(dev_priv)) { -+ dev_priv->display.get_pipe_config = i9xx_get_pipe_config; -+ dev_priv->display.get_initial_plane_config = -+ i9xx_get_initial_plane_config; -+ dev_priv->display.crtc_compute_clock = g4x_crtc_compute_clock; -+ dev_priv->display.crtc_enable = i9xx_crtc_enable; -+ dev_priv->display.crtc_disable = i9xx_crtc_disable; -+ } else if (IS_PINEVIEW(dev_priv)) { -+ dev_priv->display.get_pipe_config = i9xx_get_pipe_config; -+ dev_priv->display.get_initial_plane_config = -+ i9xx_get_initial_plane_config; -+ dev_priv->display.crtc_compute_clock = pnv_crtc_compute_clock; -+ dev_priv->display.crtc_enable = i9xx_crtc_enable; -+ dev_priv->display.crtc_disable = i9xx_crtc_disable; -+ } else if (!IS_GEN(dev_priv, 2)) { -+ dev_priv->display.get_pipe_config = i9xx_get_pipe_config; -+ dev_priv->display.get_initial_plane_config = -+ i9xx_get_initial_plane_config; -+ dev_priv->display.crtc_compute_clock = i9xx_crtc_compute_clock; -+ dev_priv->display.crtc_enable = i9xx_crtc_enable; -+ dev_priv->display.crtc_disable = i9xx_crtc_disable; -+ } else { -+ dev_priv->display.get_pipe_config = i9xx_get_pipe_config; -+ dev_priv->display.get_initial_plane_config = -+ i9xx_get_initial_plane_config; -+ dev_priv->display.crtc_compute_clock = i8xx_crtc_compute_clock; -+ dev_priv->display.crtc_enable = i9xx_crtc_enable; -+ dev_priv->display.crtc_disable = i9xx_crtc_disable; -+ } -+ -+ if (IS_GEN(dev_priv, 5)) { -+ dev_priv->display.fdi_link_train = ironlake_fdi_link_train; -+ } else if (IS_GEN(dev_priv, 6)) { -+ dev_priv->display.fdi_link_train = gen6_fdi_link_train; -+ } else if (IS_IVYBRIDGE(dev_priv)) { -+ /* FIXME: detect B0+ stepping and use auto training */ -+ dev_priv->display.fdi_link_train = ivb_manual_fdi_link_train; -+ } else if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) { -+ dev_priv->display.fdi_link_train = hsw_fdi_link_train; -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ dev_priv->display.update_crtcs = skl_update_crtcs; -+ else -+ dev_priv->display.update_crtcs = intel_update_crtcs; -+} -+ -+/* Disable the VGA plane that we never use */ -+static void i915_disable_vga(struct drm_i915_private *dev_priv) -+{ -+ struct pci_dev *pdev = dev_priv->drm.pdev; -+ u8 sr1; -+ i915_reg_t vga_reg = i915_vgacntrl_reg(dev_priv); -+ -+ /* WaEnableVGAAccessThroughIOPort:ctg,elk,ilk,snb,ivb,vlv,hsw */ -+ vga_get_uninterruptible(pdev, VGA_RSRC_LEGACY_IO); -+ outb(SR01, VGA_SR_INDEX); -+ sr1 = inb(VGA_SR_DATA); -+ outb(sr1 | 1<<5, VGA_SR_DATA); -+ vga_put(pdev, VGA_RSRC_LEGACY_IO); -+ udelay(300); -+ -+ I915_WRITE(vga_reg, VGA_DISP_DISABLE); -+ POSTING_READ(vga_reg); -+} -+ -+void intel_modeset_init_hw(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ intel_update_cdclk(dev_priv); -+ intel_dump_cdclk_state(&dev_priv->cdclk.hw, "Current CDCLK"); -+ dev_priv->cdclk.logical = dev_priv->cdclk.actual = dev_priv->cdclk.hw; -+} -+ -+/* -+ * Calculate what we think the watermarks should be for the state we've read -+ * out of the hardware and then immediately program those watermarks so that -+ * we ensure the hardware settings match our internal state. -+ * -+ * We can calculate what we think WM's should be by creating a duplicate of the -+ * current state (which was constructed during hardware readout) and running it -+ * through the atomic check code to calculate new watermark values in the -+ * state object. -+ */ -+static void sanitize_watermarks(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_atomic_state *state; -+ struct intel_atomic_state *intel_state; -+ struct drm_crtc *crtc; -+ struct drm_crtc_state *cstate; -+ struct drm_modeset_acquire_ctx ctx; -+ int ret; -+ int i; -+ -+ /* Only supported on platforms that use atomic watermark design */ -+ if (!dev_priv->display.optimize_watermarks) -+ return; -+ -+ /* -+ * We need to hold connection_mutex before calling duplicate_state so -+ * that the connector loop is protected. -+ */ -+ drm_modeset_acquire_init(&ctx, 0); -+retry: -+ ret = drm_modeset_lock_all_ctx(dev, &ctx); -+ if (ret == -EDEADLK) { -+ drm_modeset_backoff(&ctx); -+ goto retry; -+ } else if (WARN_ON(ret)) { -+ goto fail; -+ } -+ -+ state = drm_atomic_helper_duplicate_state(dev, &ctx); -+ if (WARN_ON(IS_ERR(state))) -+ goto fail; -+ -+ intel_state = to_intel_atomic_state(state); -+ -+ /* -+ * Hardware readout is the only time we don't want to calculate -+ * intermediate watermarks (since we don't trust the current -+ * watermarks). -+ */ -+ if (!HAS_GMCH(dev_priv)) -+ intel_state->skip_intermediate_wm = true; -+ -+ ret = intel_atomic_check(dev, state); -+ if (ret) { -+ /* -+ * If we fail here, it means that the hardware appears to be -+ * programmed in a way that shouldn't be possible, given our -+ * understanding of watermark requirements. This might mean a -+ * mistake in the hardware readout code or a mistake in the -+ * watermark calculations for a given platform. Raise a WARN -+ * so that this is noticeable. -+ * -+ * If this actually happens, we'll have to just leave the -+ * BIOS-programmed watermarks untouched and hope for the best. -+ */ -+ WARN(true, "Could not determine valid watermarks for inherited state\n"); -+ goto put_state; -+ } -+ -+ /* Write calculated watermark values back */ -+ for_each_new_crtc_in_state(state, crtc, cstate, i) { -+ struct intel_crtc_state *cs = to_intel_crtc_state(cstate); -+ -+ cs->wm.need_postvbl_update = true; -+ dev_priv->display.optimize_watermarks(intel_state, cs); -+ -+ to_intel_crtc_state(crtc->state)->wm = cs->wm; -+ } -+ -+put_state: -+ drm_atomic_state_put(state); -+fail: -+ drm_modeset_drop_locks(&ctx); -+ drm_modeset_acquire_fini(&ctx); -+} -+ -+static void intel_update_fdi_pll_freq(struct drm_i915_private *dev_priv) -+{ -+ if (IS_GEN(dev_priv, 5)) { -+ u32 fdi_pll_clk = -+ I915_READ(FDI_PLL_BIOS_0) & FDI_PLL_FB_CLOCK_MASK; -+ -+ dev_priv->fdi_pll_freq = (fdi_pll_clk + 2) * 10000; -+ } else if (IS_GEN(dev_priv, 6) || IS_IVYBRIDGE(dev_priv)) { -+ dev_priv->fdi_pll_freq = 270000; -+ } else { -+ return; -+ } -+ -+ DRM_DEBUG_DRIVER("FDI PLL freq=%d\n", dev_priv->fdi_pll_freq); -+} -+ -+static int intel_initial_commit(struct drm_device *dev) -+{ -+ struct drm_atomic_state *state = NULL; -+ struct drm_modeset_acquire_ctx ctx; -+ struct drm_crtc *crtc; -+ struct drm_crtc_state *crtc_state; -+ int ret = 0; -+ -+ state = drm_atomic_state_alloc(dev); -+ if (!state) -+ return -ENOMEM; -+ -+ drm_modeset_acquire_init(&ctx, 0); -+ -+retry: -+ state->acquire_ctx = &ctx; -+ -+ drm_for_each_crtc(crtc, dev) { -+ crtc_state = drm_atomic_get_crtc_state(state, crtc); -+ if (IS_ERR(crtc_state)) { -+ ret = PTR_ERR(crtc_state); -+ goto out; -+ } -+ -+ if (crtc_state->active) { -+ ret = drm_atomic_add_affected_planes(state, crtc); -+ if (ret) -+ goto out; -+ -+ /* -+ * FIXME hack to force a LUT update to avoid the -+ * plane update forcing the pipe gamma on without -+ * having a proper LUT loaded. Remove once we -+ * have readout for pipe gamma enable. -+ */ -+ crtc_state->color_mgmt_changed = true; -+ } -+ } -+ -+ ret = drm_atomic_commit(state); -+ -+out: -+ if (ret == -EDEADLK) { -+ drm_atomic_state_clear(state); -+ drm_modeset_backoff(&ctx); -+ goto retry; -+ } -+ -+ drm_atomic_state_put(state); -+ -+ drm_modeset_drop_locks(&ctx); -+ drm_modeset_acquire_fini(&ctx); -+ -+ return ret; -+} -+ -+int intel_modeset_init(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct i915_ggtt *ggtt = &dev_priv->ggtt; -+ enum pipe pipe; -+ struct intel_crtc *crtc; -+ int ret; -+ -+ dev_priv->modeset_wq = alloc_ordered_workqueue("i915_modeset", 0); -+ -+ drm_mode_config_init(dev); -+ -+ dev->mode_config.min_width = 0; -+ dev->mode_config.min_height = 0; -+ -+ dev->mode_config.preferred_depth = 24; -+ dev->mode_config.prefer_shadow = 1; -+ -+ dev->mode_config.allow_fb_modifiers = true; -+ -+ dev->mode_config.funcs = &intel_mode_funcs; -+ -+ init_llist_head(&dev_priv->atomic_helper.free_list); -+ INIT_WORK(&dev_priv->atomic_helper.free_work, -+ intel_atomic_helper_free_state_worker); -+ -+ intel_init_quirks(dev_priv); -+ -+ intel_fbc_init(dev_priv); -+ -+ intel_init_pm(dev_priv); -+ -+ /* -+ * There may be no VBT; and if the BIOS enabled SSC we can -+ * just keep using it to avoid unnecessary flicker. Whereas if the -+ * BIOS isn't using it, don't assume it will work even if the VBT -+ * indicates as much. -+ */ -+ if (HAS_PCH_IBX(dev_priv) || HAS_PCH_CPT(dev_priv)) { -+ bool bios_lvds_use_ssc = !!(I915_READ(PCH_DREF_CONTROL) & -+ DREF_SSC1_ENABLE); -+ -+ if (dev_priv->vbt.lvds_use_ssc != bios_lvds_use_ssc) { -+ DRM_DEBUG_KMS("SSC %sabled by BIOS, overriding VBT which says %sabled\n", -+ bios_lvds_use_ssc ? "en" : "dis", -+ dev_priv->vbt.lvds_use_ssc ? "en" : "dis"); -+ dev_priv->vbt.lvds_use_ssc = bios_lvds_use_ssc; -+ } -+ } -+ -+ /* maximum framebuffer dimensions */ -+ if (IS_GEN(dev_priv, 2)) { -+ dev->mode_config.max_width = 2048; -+ dev->mode_config.max_height = 2048; -+ } else if (IS_GEN(dev_priv, 3)) { -+ dev->mode_config.max_width = 4096; -+ dev->mode_config.max_height = 4096; -+ } else { -+ dev->mode_config.max_width = 8192; -+ dev->mode_config.max_height = 8192; -+ } -+ -+ if (IS_I845G(dev_priv) || IS_I865G(dev_priv)) { -+ dev->mode_config.cursor_width = IS_I845G(dev_priv) ? 64 : 512; -+ dev->mode_config.cursor_height = 1023; -+ } else if (IS_GEN(dev_priv, 2)) { -+ dev->mode_config.cursor_width = 64; -+ dev->mode_config.cursor_height = 64; -+ } else { -+ dev->mode_config.cursor_width = 256; -+ dev->mode_config.cursor_height = 256; -+ } -+ -+ dev->mode_config.fb_base = ggtt->gmadr.start; -+ -+ DRM_DEBUG_KMS("%d display pipe%s available.\n", -+ INTEL_INFO(dev_priv)->num_pipes, -+ INTEL_INFO(dev_priv)->num_pipes > 1 ? "s" : ""); -+ -+ for_each_pipe(dev_priv, pipe) { -+ ret = intel_crtc_init(dev_priv, pipe); -+ if (ret) { -+ drm_mode_config_cleanup(dev); -+ return ret; -+ } -+ } -+ -+ intel_shared_dpll_init(dev); -+ intel_update_fdi_pll_freq(dev_priv); -+ -+ intel_update_czclk(dev_priv); -+ intel_modeset_init_hw(dev); -+ -+ intel_hdcp_component_init(dev_priv); -+ -+ if (dev_priv->max_cdclk_freq == 0) -+ intel_update_max_cdclk(dev_priv); -+ -+ /* Just disable it once at startup */ -+ i915_disable_vga(dev_priv); -+ intel_setup_outputs(dev_priv); -+ -+ drm_modeset_lock_all(dev); -+ intel_modeset_setup_hw_state(dev, dev->mode_config.acquire_ctx); -+ drm_modeset_unlock_all(dev); -+ -+ for_each_intel_crtc(dev, crtc) { -+ struct intel_initial_plane_config plane_config = {}; -+ -+ if (!crtc->active) -+ continue; -+ -+ /* -+ * Note that reserving the BIOS fb up front prevents us -+ * from stuffing other stolen allocations like the ring -+ * on top. This prevents some ugliness at boot time, and -+ * can even allow for smooth boot transitions if the BIOS -+ * fb is large enough for the active pipe configuration. -+ */ -+ dev_priv->display.get_initial_plane_config(crtc, -+ &plane_config); -+ -+ /* -+ * If the fb is shared between multiple heads, we'll -+ * just get the first one. -+ */ -+ intel_find_initial_plane_obj(crtc, &plane_config); -+ } -+ -+ /* -+ * Make sure hardware watermarks really match the state we read out. -+ * Note that we need to do this after reconstructing the BIOS fb's -+ * since the watermark calculation done here will use pstate->fb. -+ */ -+ if (!HAS_GMCH(dev_priv)) -+ sanitize_watermarks(dev); -+ -+ /* -+ * Force all active planes to recompute their states. So that on -+ * mode_setcrtc after probe, all the intel_plane_state variables -+ * are already calculated and there is no assert_plane warnings -+ * during bootup. -+ */ -+ ret = intel_initial_commit(dev); -+ if (ret) -+ DRM_DEBUG_KMS("Initial commit in probe failed.\n"); -+ -+ return 0; -+} -+ -+void i830_enable_pipe(struct drm_i915_private *dev_priv, enum pipe pipe) -+{ -+ struct intel_crtc *crtc = intel_get_crtc_for_pipe(dev_priv, pipe); -+ /* 640x480@60Hz, ~25175 kHz */ -+ struct dpll clock = { -+ .m1 = 18, -+ .m2 = 7, -+ .p1 = 13, -+ .p2 = 4, -+ .n = 2, -+ }; -+ u32 dpll, fp; -+ int i; -+ -+ WARN_ON(i9xx_calc_dpll_params(48000, &clock) != 25154); -+ -+ DRM_DEBUG_KMS("enabling pipe %c due to force quirk (vco=%d dot=%d)\n", -+ pipe_name(pipe), clock.vco, clock.dot); -+ -+ fp = i9xx_dpll_compute_fp(&clock); -+ dpll = DPLL_DVO_2X_MODE | -+ DPLL_VGA_MODE_DIS | -+ ((clock.p1 - 2) << DPLL_FPA01_P1_POST_DIV_SHIFT) | -+ PLL_P2_DIVIDE_BY_4 | -+ PLL_REF_INPUT_DREFCLK | -+ DPLL_VCO_ENABLE; -+ -+ I915_WRITE(FP0(pipe), fp); -+ I915_WRITE(FP1(pipe), fp); -+ -+ I915_WRITE(HTOTAL(pipe), (640 - 1) | ((800 - 1) << 16)); -+ I915_WRITE(HBLANK(pipe), (640 - 1) | ((800 - 1) << 16)); -+ I915_WRITE(HSYNC(pipe), (656 - 1) | ((752 - 1) << 16)); -+ I915_WRITE(VTOTAL(pipe), (480 - 1) | ((525 - 1) << 16)); -+ I915_WRITE(VBLANK(pipe), (480 - 1) | ((525 - 1) << 16)); -+ I915_WRITE(VSYNC(pipe), (490 - 1) | ((492 - 1) << 16)); -+ I915_WRITE(PIPESRC(pipe), ((640 - 1) << 16) | (480 - 1)); -+ -+ /* -+ * Apparently we need to have VGA mode enabled prior to changing -+ * the P1/P2 dividers. Otherwise the DPLL will keep using the old -+ * dividers, even though the register value does change. -+ */ -+ I915_WRITE(DPLL(pipe), dpll & ~DPLL_VGA_MODE_DIS); -+ I915_WRITE(DPLL(pipe), dpll); -+ -+ /* Wait for the clocks to stabilize. */ -+ POSTING_READ(DPLL(pipe)); -+ udelay(150); -+ -+ /* The pixel multiplier can only be updated once the -+ * DPLL is enabled and the clocks are stable. -+ * -+ * So write it again. -+ */ -+ I915_WRITE(DPLL(pipe), dpll); -+ -+ /* We do this three times for luck */ -+ for (i = 0; i < 3 ; i++) { -+ I915_WRITE(DPLL(pipe), dpll); -+ POSTING_READ(DPLL(pipe)); -+ udelay(150); /* wait for warmup */ -+ } -+ -+ I915_WRITE(PIPECONF(pipe), PIPECONF_ENABLE | PIPECONF_PROGRESSIVE); -+ POSTING_READ(PIPECONF(pipe)); -+ -+ intel_wait_for_pipe_scanline_moving(crtc); -+} -+ -+void i830_disable_pipe(struct drm_i915_private *dev_priv, enum pipe pipe) -+{ -+ struct intel_crtc *crtc = intel_get_crtc_for_pipe(dev_priv, pipe); -+ -+ DRM_DEBUG_KMS("disabling pipe %c due to force quirk\n", -+ pipe_name(pipe)); -+ -+ WARN_ON(I915_READ(DSPCNTR(PLANE_A)) & DISPLAY_PLANE_ENABLE); -+ WARN_ON(I915_READ(DSPCNTR(PLANE_B)) & DISPLAY_PLANE_ENABLE); -+ WARN_ON(I915_READ(DSPCNTR(PLANE_C)) & DISPLAY_PLANE_ENABLE); -+ WARN_ON(I915_READ(CURCNTR(PIPE_A)) & MCURSOR_MODE); -+ WARN_ON(I915_READ(CURCNTR(PIPE_B)) & MCURSOR_MODE); -+ -+ I915_WRITE(PIPECONF(pipe), 0); -+ POSTING_READ(PIPECONF(pipe)); -+ -+ intel_wait_for_pipe_scanline_stopped(crtc); -+ -+ I915_WRITE(DPLL(pipe), DPLL_VGA_MODE_DIS); -+ POSTING_READ(DPLL(pipe)); -+} -+ -+static void -+intel_sanitize_plane_mapping(struct drm_i915_private *dev_priv) -+{ -+ struct intel_crtc *crtc; -+ -+ if (INTEL_GEN(dev_priv) >= 4) -+ return; -+ -+ for_each_intel_crtc(&dev_priv->drm, crtc) { -+ struct intel_plane *plane = -+ to_intel_plane(crtc->base.primary); -+ struct intel_crtc *plane_crtc; -+ enum pipe pipe; -+ -+ if (!plane->get_hw_state(plane, &pipe)) -+ continue; -+ -+ if (pipe == crtc->pipe) -+ continue; -+ -+ DRM_DEBUG_KMS("[PLANE:%d:%s] attached to the wrong pipe, disabling plane\n", -+ plane->base.base.id, plane->base.name); -+ -+ plane_crtc = intel_get_crtc_for_pipe(dev_priv, pipe); -+ intel_plane_disable_noatomic(plane_crtc, plane); -+ } -+} -+ -+static bool intel_crtc_has_encoders(struct intel_crtc *crtc) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct intel_encoder *encoder; -+ -+ for_each_encoder_on_crtc(dev, &crtc->base, encoder) -+ return true; -+ -+ return false; -+} -+ -+static struct intel_connector *intel_encoder_find_connector(struct intel_encoder *encoder) -+{ -+ struct drm_device *dev = encoder->base.dev; -+ struct intel_connector *connector; -+ -+ for_each_connector_on_encoder(dev, &encoder->base, connector) -+ return connector; -+ -+ return NULL; -+} -+ -+static bool has_pch_trancoder(struct drm_i915_private *dev_priv, -+ enum pipe pch_transcoder) -+{ -+ return HAS_PCH_IBX(dev_priv) || HAS_PCH_CPT(dev_priv) || -+ (HAS_PCH_LPT_H(dev_priv) && pch_transcoder == PIPE_A); -+} -+ -+static void intel_sanitize_crtc(struct intel_crtc *crtc, -+ struct drm_modeset_acquire_ctx *ctx) -+{ -+ struct drm_device *dev = crtc->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crtc_state *crtc_state = to_intel_crtc_state(crtc->base.state); -+ enum transcoder cpu_transcoder = crtc_state->cpu_transcoder; -+ -+ /* Clear any frame start delays used for debugging left by the BIOS */ -+ if (crtc->active && !transcoder_is_dsi(cpu_transcoder)) { -+ i915_reg_t reg = PIPECONF(cpu_transcoder); -+ -+ I915_WRITE(reg, -+ I915_READ(reg) & ~PIPECONF_FRAME_START_DELAY_MASK); -+ } -+ -+ if (crtc_state->base.active) { -+ struct intel_plane *plane; -+ -+ /* Disable everything but the primary plane */ -+ for_each_intel_plane_on_crtc(dev, crtc, plane) { -+ const struct intel_plane_state *plane_state = -+ to_intel_plane_state(plane->base.state); -+ -+ if (plane_state->base.visible && -+ plane->base.type != DRM_PLANE_TYPE_PRIMARY) -+ intel_plane_disable_noatomic(crtc, plane); -+ } -+ -+ /* -+ * Disable any background color set by the BIOS, but enable the -+ * gamma and CSC to match how we program our planes. -+ */ -+ if (INTEL_GEN(dev_priv) >= 9) -+ I915_WRITE(SKL_BOTTOM_COLOR(crtc->pipe), -+ SKL_BOTTOM_COLOR_GAMMA_ENABLE | -+ SKL_BOTTOM_COLOR_CSC_ENABLE); -+ } -+ -+ /* Adjust the state of the output pipe according to whether we -+ * have active connectors/encoders. */ -+ if (crtc_state->base.active && !intel_crtc_has_encoders(crtc)) -+ intel_crtc_disable_noatomic(&crtc->base, ctx); -+ -+ if (crtc_state->base.active || HAS_GMCH(dev_priv)) { -+ /* -+ * We start out with underrun reporting disabled to avoid races. -+ * For correct bookkeeping mark this on active crtcs. -+ * -+ * Also on gmch platforms we dont have any hardware bits to -+ * disable the underrun reporting. Which means we need to start -+ * out with underrun reporting disabled also on inactive pipes, -+ * since otherwise we'll complain about the garbage we read when -+ * e.g. coming up after runtime pm. -+ * -+ * No protection against concurrent access is required - at -+ * worst a fifo underrun happens which also sets this to false. -+ */ -+ crtc->cpu_fifo_underrun_disabled = true; -+ /* -+ * We track the PCH trancoder underrun reporting state -+ * within the crtc. With crtc for pipe A housing the underrun -+ * reporting state for PCH transcoder A, crtc for pipe B housing -+ * it for PCH transcoder B, etc. LPT-H has only PCH transcoder A, -+ * and marking underrun reporting as disabled for the non-existing -+ * PCH transcoders B and C would prevent enabling the south -+ * error interrupt (see cpt_can_enable_serr_int()). -+ */ -+ if (has_pch_trancoder(dev_priv, crtc->pipe)) -+ crtc->pch_fifo_underrun_disabled = true; -+ } -+} -+ -+static bool has_bogus_dpll_config(const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ -+ /* -+ * Some SNB BIOSen (eg. ASUS K53SV) are known to misprogram -+ * the hardware when a high res displays plugged in. DPLL P -+ * divider is zero, and the pipe timings are bonkers. We'll -+ * try to disable everything in that case. -+ * -+ * FIXME would be nice to be able to sanitize this state -+ * without several WARNs, but for now let's take the easy -+ * road. -+ */ -+ return IS_GEN(dev_priv, 6) && -+ crtc_state->base.active && -+ crtc_state->shared_dpll && -+ crtc_state->port_clock == 0; -+} -+ -+static void intel_sanitize_encoder(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_connector *connector; -+ struct intel_crtc *crtc = to_intel_crtc(encoder->base.crtc); -+ struct intel_crtc_state *crtc_state = crtc ? -+ to_intel_crtc_state(crtc->base.state) : NULL; -+ -+ /* We need to check both for a crtc link (meaning that the -+ * encoder is active and trying to read from a pipe) and the -+ * pipe itself being active. */ -+ bool has_active_crtc = crtc_state && -+ crtc_state->base.active; -+ -+ if (crtc_state && has_bogus_dpll_config(crtc_state)) { -+ DRM_DEBUG_KMS("BIOS has misprogrammed the hardware. Disabling pipe %c\n", -+ pipe_name(crtc->pipe)); -+ has_active_crtc = false; -+ } -+ -+ connector = intel_encoder_find_connector(encoder); -+ if (connector && !has_active_crtc) { -+ DRM_DEBUG_KMS("[ENCODER:%d:%s] has active connectors but no active pipe!\n", -+ encoder->base.base.id, -+ encoder->base.name); -+ -+ /* Connector is active, but has no active pipe. This is -+ * fallout from our resume register restoring. Disable -+ * the encoder manually again. */ -+ if (crtc_state) { -+ struct drm_encoder *best_encoder; -+ -+ DRM_DEBUG_KMS("[ENCODER:%d:%s] manually disabled\n", -+ encoder->base.base.id, -+ encoder->base.name); -+ -+ /* avoid oopsing in case the hooks consult best_encoder */ -+ best_encoder = connector->base.state->best_encoder; -+ connector->base.state->best_encoder = &encoder->base; -+ -+ if (encoder->disable) -+ encoder->disable(encoder, crtc_state, -+ connector->base.state); -+ if (encoder->post_disable) -+ encoder->post_disable(encoder, crtc_state, -+ connector->base.state); -+ -+ connector->base.state->best_encoder = best_encoder; -+ } -+ encoder->base.crtc = NULL; -+ -+ /* Inconsistent output/port/pipe state happens presumably due to -+ * a bug in one of the get_hw_state functions. Or someplace else -+ * in our code, like the register restore mess on resume. Clamp -+ * things to off as a safer default. */ -+ -+ connector->base.dpms = DRM_MODE_DPMS_OFF; -+ connector->base.encoder = NULL; -+ } -+ -+ /* notify opregion of the sanitized encoder state */ -+ intel_opregion_notify_encoder(encoder, connector && has_active_crtc); -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ icl_sanitize_encoder_pll_mapping(encoder); -+} -+ -+void i915_redisable_vga_power_on(struct drm_i915_private *dev_priv) -+{ -+ i915_reg_t vga_reg = i915_vgacntrl_reg(dev_priv); -+ -+ if (!(I915_READ(vga_reg) & VGA_DISP_DISABLE)) { -+ DRM_DEBUG_KMS("Something enabled VGA plane, disabling it\n"); -+ i915_disable_vga(dev_priv); -+ } -+} -+ -+void i915_redisable_vga(struct drm_i915_private *dev_priv) -+{ -+ intel_wakeref_t wakeref; -+ -+ /* -+ * This function can be called both from intel_modeset_setup_hw_state or -+ * at a very early point in our resume sequence, where the power well -+ * structures are not yet restored. Since this function is at a very -+ * paranoid "someone might have enabled VGA while we were not looking" -+ * level, just check if the power well is enabled instead of trying to -+ * follow the "don't touch the power well if we don't need it" policy -+ * the rest of the driver uses. -+ */ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ POWER_DOMAIN_VGA); -+ if (!wakeref) -+ return; -+ -+ i915_redisable_vga_power_on(dev_priv); -+ -+ intel_display_power_put(dev_priv, POWER_DOMAIN_VGA, wakeref); -+} -+ -+/* FIXME read out full plane state for all planes */ -+static void readout_plane_state(struct drm_i915_private *dev_priv) -+{ -+ struct intel_plane *plane; -+ struct intel_crtc *crtc; -+ -+ for_each_intel_plane(&dev_priv->drm, plane) { -+ struct intel_plane_state *plane_state = -+ to_intel_plane_state(plane->base.state); -+ struct intel_crtc_state *crtc_state; -+ enum pipe pipe = PIPE_A; -+ bool visible; -+ -+ visible = plane->get_hw_state(plane, &pipe); -+ -+ crtc = intel_get_crtc_for_pipe(dev_priv, pipe); -+ crtc_state = to_intel_crtc_state(crtc->base.state); -+ -+ intel_set_plane_visible(crtc_state, plane_state, visible); -+ -+ DRM_DEBUG_KMS("[PLANE:%d:%s] hw state readout: %s, pipe %c\n", -+ plane->base.base.id, plane->base.name, -+ enableddisabled(visible), pipe_name(pipe)); -+ } -+ -+ for_each_intel_crtc(&dev_priv->drm, crtc) { -+ struct intel_crtc_state *crtc_state = -+ to_intel_crtc_state(crtc->base.state); -+ -+ fixup_active_planes(crtc_state); -+ } -+} -+ -+static void intel_modeset_readout_hw_state(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ enum pipe pipe; -+ struct intel_crtc *crtc; -+ struct intel_encoder *encoder; -+ struct intel_connector *connector; -+ struct drm_connector_list_iter conn_iter; -+ int i; -+ -+ dev_priv->active_crtcs = 0; -+ -+ for_each_intel_crtc(dev, crtc) { -+ struct intel_crtc_state *crtc_state = -+ to_intel_crtc_state(crtc->base.state); -+ -+ __drm_atomic_helper_crtc_destroy_state(&crtc_state->base); -+ memset(crtc_state, 0, sizeof(*crtc_state)); -+ crtc_state->base.crtc = &crtc->base; -+ -+ crtc_state->base.active = crtc_state->base.enable = -+ dev_priv->display.get_pipe_config(crtc, crtc_state); -+ -+ crtc->base.enabled = crtc_state->base.enable; -+ crtc->active = crtc_state->base.active; -+ -+ if (crtc_state->base.active) -+ dev_priv->active_crtcs |= 1 << crtc->pipe; -+ -+ DRM_DEBUG_KMS("[CRTC:%d:%s] hw state readout: %s\n", -+ crtc->base.base.id, crtc->base.name, -+ enableddisabled(crtc_state->base.active)); -+ } -+ -+ readout_plane_state(dev_priv); -+ -+ for (i = 0; i < dev_priv->num_shared_dpll; i++) { -+ struct intel_shared_dpll *pll = &dev_priv->shared_dplls[i]; -+ -+ pll->on = pll->info->funcs->get_hw_state(dev_priv, pll, -+ &pll->state.hw_state); -+ pll->state.crtc_mask = 0; -+ for_each_intel_crtc(dev, crtc) { -+ struct intel_crtc_state *crtc_state = -+ to_intel_crtc_state(crtc->base.state); -+ -+ if (crtc_state->base.active && -+ crtc_state->shared_dpll == pll) -+ pll->state.crtc_mask |= 1 << crtc->pipe; -+ } -+ pll->active_mask = pll->state.crtc_mask; -+ -+ DRM_DEBUG_KMS("%s hw state readout: crtc_mask 0x%08x, on %i\n", -+ pll->info->name, pll->state.crtc_mask, pll->on); -+ } -+ -+ for_each_intel_encoder(dev, encoder) { -+ pipe = 0; -+ -+ if (encoder->get_hw_state(encoder, &pipe)) { -+ struct intel_crtc_state *crtc_state; -+ -+ crtc = intel_get_crtc_for_pipe(dev_priv, pipe); -+ crtc_state = to_intel_crtc_state(crtc->base.state); -+ -+ encoder->base.crtc = &crtc->base; -+ encoder->get_config(encoder, crtc_state); -+ } else { -+ encoder->base.crtc = NULL; -+ } -+ -+ DRM_DEBUG_KMS("[ENCODER:%d:%s] hw state readout: %s, pipe %c\n", -+ encoder->base.base.id, encoder->base.name, -+ enableddisabled(encoder->base.crtc), -+ pipe_name(pipe)); -+ } -+ -+ drm_connector_list_iter_begin(dev, &conn_iter); -+ for_each_intel_connector_iter(connector, &conn_iter) { -+ if (connector->get_hw_state(connector)) { -+ connector->base.dpms = DRM_MODE_DPMS_ON; -+ -+ encoder = connector->encoder; -+ connector->base.encoder = &encoder->base; -+ -+ if (encoder->base.crtc && -+ encoder->base.crtc->state->active) { -+ /* -+ * This has to be done during hardware readout -+ * because anything calling .crtc_disable may -+ * rely on the connector_mask being accurate. -+ */ -+ encoder->base.crtc->state->connector_mask |= -+ drm_connector_mask(&connector->base); -+ encoder->base.crtc->state->encoder_mask |= -+ drm_encoder_mask(&encoder->base); -+ } -+ -+ } else { -+ connector->base.dpms = DRM_MODE_DPMS_OFF; -+ connector->base.encoder = NULL; -+ } -+ DRM_DEBUG_KMS("[CONNECTOR:%d:%s] hw state readout: %s\n", -+ connector->base.base.id, connector->base.name, -+ enableddisabled(connector->base.encoder)); -+ } -+ drm_connector_list_iter_end(&conn_iter); -+ -+ for_each_intel_crtc(dev, crtc) { -+ struct intel_crtc_state *crtc_state = -+ to_intel_crtc_state(crtc->base.state); -+ int min_cdclk = 0; -+ -+ memset(&crtc->base.mode, 0, sizeof(crtc->base.mode)); -+ if (crtc_state->base.active) { -+ intel_mode_from_pipe_config(&crtc->base.mode, crtc_state); -+ crtc->base.mode.hdisplay = crtc_state->pipe_src_w; -+ crtc->base.mode.vdisplay = crtc_state->pipe_src_h; -+ intel_mode_from_pipe_config(&crtc_state->base.adjusted_mode, crtc_state); -+ WARN_ON(drm_atomic_set_mode_for_crtc(crtc->base.state, &crtc->base.mode)); -+ -+ /* -+ * The initial mode needs to be set in order to keep -+ * the atomic core happy. It wants a valid mode if the -+ * crtc's enabled, so we do the above call. -+ * -+ * But we don't set all the derived state fully, hence -+ * set a flag to indicate that a full recalculation is -+ * needed on the next commit. -+ */ -+ crtc_state->base.mode.private_flags = I915_MODE_FLAG_INHERITED; -+ -+ intel_crtc_compute_pixel_rate(crtc_state); -+ -+ if (dev_priv->display.modeset_calc_cdclk) { -+ min_cdclk = intel_crtc_compute_min_cdclk(crtc_state); -+ if (WARN_ON(min_cdclk < 0)) -+ min_cdclk = 0; -+ } -+ -+ drm_calc_timestamping_constants(&crtc->base, -+ &crtc_state->base.adjusted_mode); -+ update_scanline_offset(crtc_state); -+ } -+ -+ dev_priv->min_cdclk[crtc->pipe] = min_cdclk; -+ dev_priv->min_voltage_level[crtc->pipe] = -+ crtc_state->min_voltage_level; -+ -+ intel_pipe_config_sanity_check(dev_priv, crtc_state); -+ } -+} -+ -+static void -+get_encoder_power_domains(struct drm_i915_private *dev_priv) -+{ -+ struct intel_encoder *encoder; -+ -+ for_each_intel_encoder(&dev_priv->drm, encoder) { -+ struct intel_crtc_state *crtc_state; -+ -+ if (!encoder->get_power_domains) -+ continue; -+ -+ /* -+ * MST-primary and inactive encoders don't have a crtc state -+ * and neither of these require any power domain references. -+ */ -+ if (!encoder->base.crtc) -+ continue; -+ -+ crtc_state = to_intel_crtc_state(encoder->base.crtc->state); -+ encoder->get_power_domains(encoder, crtc_state); -+ } -+} -+ -+static void intel_early_display_was(struct drm_i915_private *dev_priv) -+{ -+ /* Display WA #1185 WaDisableDARBFClkGating:cnl,glk */ -+ if (IS_CANNONLAKE(dev_priv) || IS_GEMINILAKE(dev_priv)) -+ I915_WRITE(GEN9_CLKGATE_DIS_0, I915_READ(GEN9_CLKGATE_DIS_0) | -+ DARBF_GATING_DIS); -+ -+ if (IS_HASWELL(dev_priv)) { -+ /* -+ * WaRsPkgCStateDisplayPMReq:hsw -+ * System hang if this isn't done before disabling all planes! -+ */ -+ I915_WRITE(CHICKEN_PAR1_1, -+ I915_READ(CHICKEN_PAR1_1) | FORCE_ARB_IDLE_PLANES); -+ } -+} -+ -+static void ibx_sanitize_pch_hdmi_port(struct drm_i915_private *dev_priv, -+ enum port port, i915_reg_t hdmi_reg) -+{ -+ u32 val = I915_READ(hdmi_reg); -+ -+ if (val & SDVO_ENABLE || -+ (val & SDVO_PIPE_SEL_MASK) == SDVO_PIPE_SEL(PIPE_A)) -+ return; -+ -+ DRM_DEBUG_KMS("Sanitizing transcoder select for HDMI %c\n", -+ port_name(port)); -+ -+ val &= ~SDVO_PIPE_SEL_MASK; -+ val |= SDVO_PIPE_SEL(PIPE_A); -+ -+ I915_WRITE(hdmi_reg, val); -+} -+ -+static void ibx_sanitize_pch_dp_port(struct drm_i915_private *dev_priv, -+ enum port port, i915_reg_t dp_reg) -+{ -+ u32 val = I915_READ(dp_reg); -+ -+ if (val & DP_PORT_EN || -+ (val & DP_PIPE_SEL_MASK) == DP_PIPE_SEL(PIPE_A)) -+ return; -+ -+ DRM_DEBUG_KMS("Sanitizing transcoder select for DP %c\n", -+ port_name(port)); -+ -+ val &= ~DP_PIPE_SEL_MASK; -+ val |= DP_PIPE_SEL(PIPE_A); -+ -+ I915_WRITE(dp_reg, val); -+} -+ -+static void ibx_sanitize_pch_ports(struct drm_i915_private *dev_priv) -+{ -+ /* -+ * The BIOS may select transcoder B on some of the PCH -+ * ports even it doesn't enable the port. This would trip -+ * assert_pch_dp_disabled() and assert_pch_hdmi_disabled(). -+ * Sanitize the transcoder select bits to prevent that. We -+ * assume that the BIOS never actually enabled the port, -+ * because if it did we'd actually have to toggle the port -+ * on and back off to make the transcoder A select stick -+ * (see. intel_dp_link_down(), intel_disable_hdmi(), -+ * intel_disable_sdvo()). -+ */ -+ ibx_sanitize_pch_dp_port(dev_priv, PORT_B, PCH_DP_B); -+ ibx_sanitize_pch_dp_port(dev_priv, PORT_C, PCH_DP_C); -+ ibx_sanitize_pch_dp_port(dev_priv, PORT_D, PCH_DP_D); -+ -+ /* PCH SDVOB multiplex with HDMIB */ -+ ibx_sanitize_pch_hdmi_port(dev_priv, PORT_B, PCH_HDMIB); -+ ibx_sanitize_pch_hdmi_port(dev_priv, PORT_C, PCH_HDMIC); -+ ibx_sanitize_pch_hdmi_port(dev_priv, PORT_D, PCH_HDMID); -+} -+ -+/* Scan out the current hw modeset state, -+ * and sanitizes it to the current state -+ */ -+static void -+intel_modeset_setup_hw_state(struct drm_device *dev, -+ struct drm_modeset_acquire_ctx *ctx) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_crtc_state *crtc_state; -+ struct intel_encoder *encoder; -+ struct intel_crtc *crtc; -+ intel_wakeref_t wakeref; -+ int i; -+ -+ wakeref = intel_display_power_get(dev_priv, POWER_DOMAIN_INIT); -+ -+ intel_early_display_was(dev_priv); -+ intel_modeset_readout_hw_state(dev); -+ -+ /* HW state is read out, now we need to sanitize this mess. */ -+ get_encoder_power_domains(dev_priv); -+ -+ if (HAS_PCH_IBX(dev_priv)) -+ ibx_sanitize_pch_ports(dev_priv); -+ -+ /* -+ * intel_sanitize_plane_mapping() may need to do vblank -+ * waits, so we need vblank interrupts restored beforehand. -+ */ -+ for_each_intel_crtc(&dev_priv->drm, crtc) { -+ crtc_state = to_intel_crtc_state(crtc->base.state); -+ -+ drm_crtc_vblank_reset(&crtc->base); -+ -+ if (crtc_state->base.active) -+ intel_crtc_vblank_on(crtc_state); -+ } -+ -+ intel_sanitize_plane_mapping(dev_priv); -+ -+ for_each_intel_encoder(dev, encoder) -+ intel_sanitize_encoder(encoder); -+ -+ for_each_intel_crtc(&dev_priv->drm, crtc) { -+ crtc_state = to_intel_crtc_state(crtc->base.state); -+ intel_sanitize_crtc(crtc, ctx); -+ intel_dump_pipe_config(crtc, crtc_state, -+ "[setup_hw_state]"); -+ } -+ -+ intel_modeset_update_connector_atomic_state(dev); -+ -+ for (i = 0; i < dev_priv->num_shared_dpll; i++) { -+ struct intel_shared_dpll *pll = &dev_priv->shared_dplls[i]; -+ -+ if (!pll->on || pll->active_mask) -+ continue; -+ -+ DRM_DEBUG_KMS("%s enabled but not in use, disabling\n", -+ pll->info->name); -+ -+ pll->info->funcs->disable(dev_priv, pll); -+ pll->on = false; -+ } -+ -+ if (IS_G4X(dev_priv)) { -+ g4x_wm_get_hw_state(dev_priv); -+ g4x_wm_sanitize(dev_priv); -+ } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ vlv_wm_get_hw_state(dev_priv); -+ vlv_wm_sanitize(dev_priv); -+ } else if (INTEL_GEN(dev_priv) >= 9) { -+ skl_wm_get_hw_state(dev_priv); -+ } else if (HAS_PCH_SPLIT(dev_priv)) { -+ ilk_wm_get_hw_state(dev_priv); -+ } -+ -+ for_each_intel_crtc(dev, crtc) { -+ u64 put_domains; -+ -+ crtc_state = to_intel_crtc_state(crtc->base.state); -+ put_domains = modeset_get_crtc_power_domains(&crtc->base, crtc_state); -+ if (WARN_ON(put_domains)) -+ modeset_put_power_domains(dev_priv, put_domains); -+ } -+ -+ intel_display_power_put(dev_priv, POWER_DOMAIN_INIT, wakeref); -+ -+ intel_fbc_init_pipe_state(dev_priv); -+} -+ -+void intel_display_resume(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct drm_atomic_state *state = dev_priv->modeset_restore_state; -+ struct drm_modeset_acquire_ctx ctx; -+ int ret; -+ -+ dev_priv->modeset_restore_state = NULL; -+ if (state) -+ state->acquire_ctx = &ctx; -+ -+ drm_modeset_acquire_init(&ctx, 0); -+ -+ while (1) { -+ ret = drm_modeset_lock_all_ctx(dev, &ctx); -+ if (ret != -EDEADLK) -+ break; -+ -+ drm_modeset_backoff(&ctx); -+ } -+ -+ if (!ret) -+ ret = __intel_display_resume(dev, state, &ctx); -+ -+ intel_enable_ipc(dev_priv); -+ drm_modeset_drop_locks(&ctx); -+ drm_modeset_acquire_fini(&ctx); -+ -+ if (ret) -+ DRM_ERROR("Restoring old state failed with %i\n", ret); -+ if (state) -+ drm_atomic_state_put(state); -+} -+ -+static void intel_hpd_poll_fini(struct drm_device *dev) -+{ -+ struct intel_connector *connector; -+ struct drm_connector_list_iter conn_iter; -+ -+ /* Kill all the work that may have been queued by hpd. */ -+ drm_connector_list_iter_begin(dev, &conn_iter); -+ for_each_intel_connector_iter(connector, &conn_iter) { -+ if (connector->modeset_retry_work.func) -+ cancel_work_sync(&connector->modeset_retry_work); -+ if (connector->hdcp.shim) { -+ cancel_delayed_work_sync(&connector->hdcp.check_work); -+ cancel_work_sync(&connector->hdcp.prop_work); -+ } -+ } -+ drm_connector_list_iter_end(&conn_iter); -+} -+ -+void intel_modeset_cleanup(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ flush_workqueue(dev_priv->modeset_wq); -+ -+ flush_work(&dev_priv->atomic_helper.free_work); -+ WARN_ON(!llist_empty(&dev_priv->atomic_helper.free_list)); -+ -+ /* -+ * Interrupts and polling as the first thing to avoid creating havoc. -+ * Too much stuff here (turning of connectors, ...) would -+ * experience fancy races otherwise. -+ */ -+ intel_irq_uninstall(dev_priv); -+ -+ /* -+ * Due to the hpd irq storm handling the hotplug work can re-arm the -+ * poll handlers. Hence disable polling after hpd handling is shut down. -+ */ -+ intel_hpd_poll_fini(dev); -+ -+ /* poll work can call into fbdev, hence clean that up afterwards */ -+ intel_fbdev_fini(dev_priv); -+ -+ intel_unregister_dsm_handler(); -+ -+ intel_fbc_global_disable(dev_priv); -+ -+ /* flush any delayed tasks or pending work */ -+ flush_scheduled_work(); -+ -+ intel_hdcp_component_fini(dev_priv); -+ -+ drm_mode_config_cleanup(dev); -+ -+ intel_overlay_cleanup(dev_priv); -+ -+ intel_teardown_gmbus(dev_priv); -+ -+ destroy_workqueue(dev_priv->modeset_wq); -+ -+ intel_fbc_cleanup_cfb(dev_priv); -+} -+ -+/* -+ * set vga decode state - true == enable VGA decode -+ */ -+int intel_modeset_vga_set_state(struct drm_i915_private *dev_priv, bool state) -+{ -+ unsigned reg = INTEL_GEN(dev_priv) >= 6 ? SNB_GMCH_CTRL : INTEL_GMCH_CTRL; -+ u16 gmch_ctrl; -+ -+ if (pci_read_config_word(dev_priv->bridge_dev, reg, &gmch_ctrl)) { -+ DRM_ERROR("failed to read control word\n"); -+ return -EIO; -+ } -+ -+ if (!!(gmch_ctrl & INTEL_GMCH_VGA_DISABLE) == !state) -+ return 0; -+ -+ if (state) -+ gmch_ctrl &= ~INTEL_GMCH_VGA_DISABLE; -+ else -+ gmch_ctrl |= INTEL_GMCH_VGA_DISABLE; -+ -+ if (pci_write_config_word(dev_priv->bridge_dev, reg, gmch_ctrl)) { -+ DRM_ERROR("failed to write control word\n"); -+ return -EIO; -+ } -+ -+ return 0; -+} -+ -+#if IS_ENABLED(CONFIG_DRM_I915_CAPTURE_ERROR) -+ -+struct intel_display_error_state { -+ -+ u32 power_well_driver; -+ -+ struct intel_cursor_error_state { -+ u32 control; -+ u32 position; -+ u32 base; -+ u32 size; -+ } cursor[I915_MAX_PIPES]; -+ -+ struct intel_pipe_error_state { -+ bool power_domain_on; -+ u32 source; -+ u32 stat; -+ } pipe[I915_MAX_PIPES]; -+ -+ struct intel_plane_error_state { -+ u32 control; -+ u32 stride; -+ u32 size; -+ u32 pos; -+ u32 addr; -+ u32 surface; -+ u32 tile_offset; -+ } plane[I915_MAX_PIPES]; -+ -+ struct intel_transcoder_error_state { -+ bool available; -+ bool power_domain_on; -+ enum transcoder cpu_transcoder; -+ -+ u32 conf; -+ -+ u32 htotal; -+ u32 hblank; -+ u32 hsync; -+ u32 vtotal; -+ u32 vblank; -+ u32 vsync; -+ } transcoder[4]; -+}; -+ -+struct intel_display_error_state * -+intel_display_capture_error_state(struct drm_i915_private *dev_priv) -+{ -+ struct intel_display_error_state *error; -+ int transcoders[] = { -+ TRANSCODER_A, -+ TRANSCODER_B, -+ TRANSCODER_C, -+ TRANSCODER_EDP, -+ }; -+ int i; -+ -+ BUILD_BUG_ON(ARRAY_SIZE(transcoders) != ARRAY_SIZE(error->transcoder)); -+ -+ if (!HAS_DISPLAY(dev_priv)) -+ return NULL; -+ -+ error = kzalloc(sizeof(*error), GFP_ATOMIC); -+ if (error == NULL) -+ return NULL; -+ -+ if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) -+ error->power_well_driver = I915_READ(HSW_PWR_WELL_CTL2); -+ -+ for_each_pipe(dev_priv, i) { -+ error->pipe[i].power_domain_on = -+ __intel_display_power_is_enabled(dev_priv, -+ POWER_DOMAIN_PIPE(i)); -+ if (!error->pipe[i].power_domain_on) -+ continue; -+ -+ error->cursor[i].control = I915_READ(CURCNTR(i)); -+ error->cursor[i].position = I915_READ(CURPOS(i)); -+ error->cursor[i].base = I915_READ(CURBASE(i)); -+ -+ error->plane[i].control = I915_READ(DSPCNTR(i)); -+ error->plane[i].stride = I915_READ(DSPSTRIDE(i)); -+ if (INTEL_GEN(dev_priv) <= 3) { -+ error->plane[i].size = I915_READ(DSPSIZE(i)); -+ error->plane[i].pos = I915_READ(DSPPOS(i)); -+ } -+ if (INTEL_GEN(dev_priv) <= 7 && !IS_HASWELL(dev_priv)) -+ error->plane[i].addr = I915_READ(DSPADDR(i)); -+ if (INTEL_GEN(dev_priv) >= 4) { -+ error->plane[i].surface = I915_READ(DSPSURF(i)); -+ error->plane[i].tile_offset = I915_READ(DSPTILEOFF(i)); -+ } -+ -+ error->pipe[i].source = I915_READ(PIPESRC(i)); -+ -+ if (HAS_GMCH(dev_priv)) -+ error->pipe[i].stat = I915_READ(PIPESTAT(i)); -+ } -+ -+ for (i = 0; i < ARRAY_SIZE(error->transcoder); i++) { -+ enum transcoder cpu_transcoder = transcoders[i]; -+ -+ if (!INTEL_INFO(dev_priv)->trans_offsets[cpu_transcoder]) -+ continue; -+ -+ error->transcoder[i].available = true; -+ error->transcoder[i].power_domain_on = -+ __intel_display_power_is_enabled(dev_priv, -+ POWER_DOMAIN_TRANSCODER(cpu_transcoder)); -+ if (!error->transcoder[i].power_domain_on) -+ continue; -+ -+ error->transcoder[i].cpu_transcoder = cpu_transcoder; -+ -+ error->transcoder[i].conf = I915_READ(PIPECONF(cpu_transcoder)); -+ error->transcoder[i].htotal = I915_READ(HTOTAL(cpu_transcoder)); -+ error->transcoder[i].hblank = I915_READ(HBLANK(cpu_transcoder)); -+ error->transcoder[i].hsync = I915_READ(HSYNC(cpu_transcoder)); -+ error->transcoder[i].vtotal = I915_READ(VTOTAL(cpu_transcoder)); -+ error->transcoder[i].vblank = I915_READ(VBLANK(cpu_transcoder)); -+ error->transcoder[i].vsync = I915_READ(VSYNC(cpu_transcoder)); -+ } -+ -+ return error; -+} -+ -+#define err_printf(e, ...) i915_error_printf(e, __VA_ARGS__) -+ -+void -+intel_display_print_error_state(struct drm_i915_error_state_buf *m, -+ struct intel_display_error_state *error) -+{ -+ struct drm_i915_private *dev_priv = m->i915; -+ int i; -+ -+ if (!error) -+ return; -+ -+ err_printf(m, "Num Pipes: %d\n", INTEL_INFO(dev_priv)->num_pipes); -+ if (IS_HASWELL(dev_priv) || IS_BROADWELL(dev_priv)) -+ err_printf(m, "PWR_WELL_CTL2: %08x\n", -+ error->power_well_driver); -+ for_each_pipe(dev_priv, i) { -+ err_printf(m, "Pipe [%d]:\n", i); -+ err_printf(m, " Power: %s\n", -+ onoff(error->pipe[i].power_domain_on)); -+ err_printf(m, " SRC: %08x\n", error->pipe[i].source); -+ err_printf(m, " STAT: %08x\n", error->pipe[i].stat); -+ -+ err_printf(m, "Plane [%d]:\n", i); -+ err_printf(m, " CNTR: %08x\n", error->plane[i].control); -+ err_printf(m, " STRIDE: %08x\n", error->plane[i].stride); -+ if (INTEL_GEN(dev_priv) <= 3) { -+ err_printf(m, " SIZE: %08x\n", error->plane[i].size); -+ err_printf(m, " POS: %08x\n", error->plane[i].pos); -+ } -+ if (INTEL_GEN(dev_priv) <= 7 && !IS_HASWELL(dev_priv)) -+ err_printf(m, " ADDR: %08x\n", error->plane[i].addr); -+ if (INTEL_GEN(dev_priv) >= 4) { -+ err_printf(m, " SURF: %08x\n", error->plane[i].surface); -+ err_printf(m, " TILEOFF: %08x\n", error->plane[i].tile_offset); -+ } -+ -+ err_printf(m, "Cursor [%d]:\n", i); -+ err_printf(m, " CNTR: %08x\n", error->cursor[i].control); -+ err_printf(m, " POS: %08x\n", error->cursor[i].position); -+ err_printf(m, " BASE: %08x\n", error->cursor[i].base); -+ } -+ -+ for (i = 0; i < ARRAY_SIZE(error->transcoder); i++) { -+ if (!error->transcoder[i].available) -+ continue; -+ -+ err_printf(m, "CPU transcoder: %s\n", -+ transcoder_name(error->transcoder[i].cpu_transcoder)); -+ err_printf(m, " Power: %s\n", -+ onoff(error->transcoder[i].power_domain_on)); -+ err_printf(m, " CONF: %08x\n", error->transcoder[i].conf); -+ err_printf(m, " HTOTAL: %08x\n", error->transcoder[i].htotal); -+ err_printf(m, " HBLANK: %08x\n", error->transcoder[i].hblank); -+ err_printf(m, " HSYNC: %08x\n", error->transcoder[i].hsync); -+ err_printf(m, " VTOTAL: %08x\n", error->transcoder[i].vtotal); -+ err_printf(m, " VBLANK: %08x\n", error->transcoder[i].vblank); -+ err_printf(m, " VSYNC: %08x\n", error->transcoder[i].vsync); -+ } -+} -+ -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/intel_display.h b/drivers/gpu/drm/i915_legacy/intel_display.h -new file mode 100644 -index 000000000000..2220588e86ac ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_display.h -@@ -0,0 +1,435 @@ -+/* -+ * Copyright © 2006-2017 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef _INTEL_DISPLAY_H_ -+#define _INTEL_DISPLAY_H_ -+ -+#include -+#include -+ -+enum i915_gpio { -+ GPIOA, -+ GPIOB, -+ GPIOC, -+ GPIOD, -+ GPIOE, -+ GPIOF, -+ GPIOG, -+ GPIOH, -+ __GPIOI_UNUSED, -+ GPIOJ, -+ GPIOK, -+ GPIOL, -+ GPIOM, -+}; -+ -+/* -+ * Keep the pipe enum values fixed: the code assumes that PIPE_A=0, the -+ * rest have consecutive values and match the enum values of transcoders -+ * with a 1:1 transcoder -> pipe mapping. -+ */ -+enum pipe { -+ INVALID_PIPE = -1, -+ -+ PIPE_A = 0, -+ PIPE_B, -+ PIPE_C, -+ _PIPE_EDP, -+ -+ I915_MAX_PIPES = _PIPE_EDP -+}; -+ -+#define pipe_name(p) ((p) + 'A') -+ -+enum transcoder { -+ /* -+ * The following transcoders have a 1:1 transcoder -> pipe mapping, -+ * keep their values fixed: the code assumes that TRANSCODER_A=0, the -+ * rest have consecutive values and match the enum values of the pipes -+ * they map to. -+ */ -+ TRANSCODER_A = PIPE_A, -+ TRANSCODER_B = PIPE_B, -+ TRANSCODER_C = PIPE_C, -+ -+ /* -+ * The following transcoders can map to any pipe, their enum value -+ * doesn't need to stay fixed. -+ */ -+ TRANSCODER_EDP, -+ TRANSCODER_DSI_0, -+ TRANSCODER_DSI_1, -+ TRANSCODER_DSI_A = TRANSCODER_DSI_0, /* legacy DSI */ -+ TRANSCODER_DSI_C = TRANSCODER_DSI_1, /* legacy DSI */ -+ -+ I915_MAX_TRANSCODERS -+}; -+ -+static inline const char *transcoder_name(enum transcoder transcoder) -+{ -+ switch (transcoder) { -+ case TRANSCODER_A: -+ return "A"; -+ case TRANSCODER_B: -+ return "B"; -+ case TRANSCODER_C: -+ return "C"; -+ case TRANSCODER_EDP: -+ return "EDP"; -+ case TRANSCODER_DSI_A: -+ return "DSI A"; -+ case TRANSCODER_DSI_C: -+ return "DSI C"; -+ default: -+ return ""; -+ } -+} -+ -+static inline bool transcoder_is_dsi(enum transcoder transcoder) -+{ -+ return transcoder == TRANSCODER_DSI_A || transcoder == TRANSCODER_DSI_C; -+} -+ -+/* -+ * Global legacy plane identifier. Valid only for primary/sprite -+ * planes on pre-g4x, and only for primary planes on g4x-bdw. -+ */ -+enum i9xx_plane_id { -+ PLANE_A, -+ PLANE_B, -+ PLANE_C, -+}; -+ -+#define plane_name(p) ((p) + 'A') -+#define sprite_name(p, s) ((p) * RUNTIME_INFO(dev_priv)->num_sprites[(p)] + (s) + 'A') -+ -+/* -+ * Per-pipe plane identifier. -+ * I915_MAX_PLANES in the enum below is the maximum (across all platforms) -+ * number of planes per CRTC. Not all platforms really have this many planes, -+ * which means some arrays of size I915_MAX_PLANES may have unused entries -+ * between the topmost sprite plane and the cursor plane. -+ * -+ * This is expected to be passed to various register macros -+ * (eg. PLANE_CTL(), PS_PLANE_SEL(), etc.) so adjust with care. -+ */ -+enum plane_id { -+ PLANE_PRIMARY, -+ PLANE_SPRITE0, -+ PLANE_SPRITE1, -+ PLANE_SPRITE2, -+ PLANE_SPRITE3, -+ PLANE_SPRITE4, -+ PLANE_SPRITE5, -+ PLANE_CURSOR, -+ -+ I915_MAX_PLANES, -+}; -+ -+#define for_each_plane_id_on_crtc(__crtc, __p) \ -+ for ((__p) = PLANE_PRIMARY; (__p) < I915_MAX_PLANES; (__p)++) \ -+ for_each_if((__crtc)->plane_ids_mask & BIT(__p)) -+ -+/* -+ * Ports identifier referenced from other drivers. -+ * Expected to remain stable over time -+ */ -+static inline const char *port_identifier(enum port port) -+{ -+ switch (port) { -+ case PORT_A: -+ return "Port A"; -+ case PORT_B: -+ return "Port B"; -+ case PORT_C: -+ return "Port C"; -+ case PORT_D: -+ return "Port D"; -+ case PORT_E: -+ return "Port E"; -+ case PORT_F: -+ return "Port F"; -+ default: -+ return ""; -+ } -+} -+ -+enum tc_port { -+ PORT_TC_NONE = -1, -+ -+ PORT_TC1 = 0, -+ PORT_TC2, -+ PORT_TC3, -+ PORT_TC4, -+ -+ I915_MAX_TC_PORTS -+}; -+ -+enum tc_port_type { -+ TC_PORT_UNKNOWN = 0, -+ TC_PORT_TYPEC, -+ TC_PORT_TBT, -+ TC_PORT_LEGACY, -+}; -+ -+enum dpio_channel { -+ DPIO_CH0, -+ DPIO_CH1 -+}; -+ -+enum dpio_phy { -+ DPIO_PHY0, -+ DPIO_PHY1, -+ DPIO_PHY2, -+}; -+ -+#define I915_NUM_PHYS_VLV 2 -+ -+enum aux_ch { -+ AUX_CH_A, -+ AUX_CH_B, -+ AUX_CH_C, -+ AUX_CH_D, -+ AUX_CH_E, /* ICL+ */ -+ AUX_CH_F, -+}; -+ -+#define aux_ch_name(a) ((a) + 'A') -+ -+enum intel_display_power_domain { -+ POWER_DOMAIN_PIPE_A, -+ POWER_DOMAIN_PIPE_B, -+ POWER_DOMAIN_PIPE_C, -+ POWER_DOMAIN_PIPE_A_PANEL_FITTER, -+ POWER_DOMAIN_PIPE_B_PANEL_FITTER, -+ POWER_DOMAIN_PIPE_C_PANEL_FITTER, -+ POWER_DOMAIN_TRANSCODER_A, -+ POWER_DOMAIN_TRANSCODER_B, -+ POWER_DOMAIN_TRANSCODER_C, -+ POWER_DOMAIN_TRANSCODER_EDP, -+ POWER_DOMAIN_TRANSCODER_EDP_VDSC, -+ POWER_DOMAIN_TRANSCODER_DSI_A, -+ POWER_DOMAIN_TRANSCODER_DSI_C, -+ POWER_DOMAIN_PORT_DDI_A_LANES, -+ POWER_DOMAIN_PORT_DDI_B_LANES, -+ POWER_DOMAIN_PORT_DDI_C_LANES, -+ POWER_DOMAIN_PORT_DDI_D_LANES, -+ POWER_DOMAIN_PORT_DDI_E_LANES, -+ POWER_DOMAIN_PORT_DDI_F_LANES, -+ POWER_DOMAIN_PORT_DDI_A_IO, -+ POWER_DOMAIN_PORT_DDI_B_IO, -+ POWER_DOMAIN_PORT_DDI_C_IO, -+ POWER_DOMAIN_PORT_DDI_D_IO, -+ POWER_DOMAIN_PORT_DDI_E_IO, -+ POWER_DOMAIN_PORT_DDI_F_IO, -+ POWER_DOMAIN_PORT_DSI, -+ POWER_DOMAIN_PORT_CRT, -+ POWER_DOMAIN_PORT_OTHER, -+ POWER_DOMAIN_VGA, -+ POWER_DOMAIN_AUDIO, -+ POWER_DOMAIN_PLLS, -+ POWER_DOMAIN_AUX_A, -+ POWER_DOMAIN_AUX_B, -+ POWER_DOMAIN_AUX_C, -+ POWER_DOMAIN_AUX_D, -+ POWER_DOMAIN_AUX_E, -+ POWER_DOMAIN_AUX_F, -+ POWER_DOMAIN_AUX_IO_A, -+ POWER_DOMAIN_AUX_TBT1, -+ POWER_DOMAIN_AUX_TBT2, -+ POWER_DOMAIN_AUX_TBT3, -+ POWER_DOMAIN_AUX_TBT4, -+ POWER_DOMAIN_GMBUS, -+ POWER_DOMAIN_MODESET, -+ POWER_DOMAIN_GT_IRQ, -+ POWER_DOMAIN_INIT, -+ -+ POWER_DOMAIN_NUM, -+}; -+ -+#define POWER_DOMAIN_PIPE(pipe) ((pipe) + POWER_DOMAIN_PIPE_A) -+#define POWER_DOMAIN_PIPE_PANEL_FITTER(pipe) \ -+ ((pipe) + POWER_DOMAIN_PIPE_A_PANEL_FITTER) -+#define POWER_DOMAIN_TRANSCODER(tran) \ -+ ((tran) == TRANSCODER_EDP ? POWER_DOMAIN_TRANSCODER_EDP : \ -+ (tran) + POWER_DOMAIN_TRANSCODER_A) -+ -+/* Used by dp and fdi links */ -+struct intel_link_m_n { -+ u32 tu; -+ u32 gmch_m; -+ u32 gmch_n; -+ u32 link_m; -+ u32 link_n; -+}; -+ -+#define for_each_pipe(__dev_priv, __p) \ -+ for ((__p) = 0; (__p) < INTEL_INFO(__dev_priv)->num_pipes; (__p)++) -+ -+#define for_each_pipe_masked(__dev_priv, __p, __mask) \ -+ for ((__p) = 0; (__p) < INTEL_INFO(__dev_priv)->num_pipes; (__p)++) \ -+ for_each_if((__mask) & BIT(__p)) -+ -+#define for_each_cpu_transcoder_masked(__dev_priv, __t, __mask) \ -+ for ((__t) = 0; (__t) < I915_MAX_TRANSCODERS; (__t)++) \ -+ for_each_if ((__mask) & (1 << (__t))) -+ -+#define for_each_universal_plane(__dev_priv, __pipe, __p) \ -+ for ((__p) = 0; \ -+ (__p) < RUNTIME_INFO(__dev_priv)->num_sprites[(__pipe)] + 1; \ -+ (__p)++) -+ -+#define for_each_sprite(__dev_priv, __p, __s) \ -+ for ((__s) = 0; \ -+ (__s) < RUNTIME_INFO(__dev_priv)->num_sprites[(__p)]; \ -+ (__s)++) -+ -+#define for_each_port_masked(__port, __ports_mask) \ -+ for ((__port) = PORT_A; (__port) < I915_MAX_PORTS; (__port)++) \ -+ for_each_if((__ports_mask) & BIT(__port)) -+ -+#define for_each_crtc(dev, crtc) \ -+ list_for_each_entry(crtc, &(dev)->mode_config.crtc_list, head) -+ -+#define for_each_intel_plane(dev, intel_plane) \ -+ list_for_each_entry(intel_plane, \ -+ &(dev)->mode_config.plane_list, \ -+ base.head) -+ -+#define for_each_intel_plane_mask(dev, intel_plane, plane_mask) \ -+ list_for_each_entry(intel_plane, \ -+ &(dev)->mode_config.plane_list, \ -+ base.head) \ -+ for_each_if((plane_mask) & \ -+ drm_plane_mask(&intel_plane->base))) -+ -+#define for_each_intel_plane_on_crtc(dev, intel_crtc, intel_plane) \ -+ list_for_each_entry(intel_plane, \ -+ &(dev)->mode_config.plane_list, \ -+ base.head) \ -+ for_each_if((intel_plane)->pipe == (intel_crtc)->pipe) -+ -+#define for_each_intel_crtc(dev, intel_crtc) \ -+ list_for_each_entry(intel_crtc, \ -+ &(dev)->mode_config.crtc_list, \ -+ base.head) -+ -+#define for_each_intel_crtc_mask(dev, intel_crtc, crtc_mask) \ -+ list_for_each_entry(intel_crtc, \ -+ &(dev)->mode_config.crtc_list, \ -+ base.head) \ -+ for_each_if((crtc_mask) & drm_crtc_mask(&intel_crtc->base)) -+ -+#define for_each_intel_encoder(dev, intel_encoder) \ -+ list_for_each_entry(intel_encoder, \ -+ &(dev)->mode_config.encoder_list, \ -+ base.head) -+ -+#define for_each_intel_dp(dev, intel_encoder) \ -+ for_each_intel_encoder(dev, intel_encoder) \ -+ for_each_if(intel_encoder_is_dp(intel_encoder)) -+ -+#define for_each_intel_connector_iter(intel_connector, iter) \ -+ while ((intel_connector = to_intel_connector(drm_connector_list_iter_next(iter)))) -+ -+#define for_each_encoder_on_crtc(dev, __crtc, intel_encoder) \ -+ list_for_each_entry((intel_encoder), &(dev)->mode_config.encoder_list, base.head) \ -+ for_each_if((intel_encoder)->base.crtc == (__crtc)) -+ -+#define for_each_connector_on_encoder(dev, __encoder, intel_connector) \ -+ list_for_each_entry((intel_connector), &(dev)->mode_config.connector_list, base.head) \ -+ for_each_if((intel_connector)->base.encoder == (__encoder)) -+ -+#define for_each_power_domain(domain, mask) \ -+ for ((domain) = 0; (domain) < POWER_DOMAIN_NUM; (domain)++) \ -+ for_each_if(BIT_ULL(domain) & (mask)) -+ -+#define for_each_power_well(__dev_priv, __power_well) \ -+ for ((__power_well) = (__dev_priv)->power_domains.power_wells; \ -+ (__power_well) - (__dev_priv)->power_domains.power_wells < \ -+ (__dev_priv)->power_domains.power_well_count; \ -+ (__power_well)++) -+ -+#define for_each_power_well_reverse(__dev_priv, __power_well) \ -+ for ((__power_well) = (__dev_priv)->power_domains.power_wells + \ -+ (__dev_priv)->power_domains.power_well_count - 1; \ -+ (__power_well) - (__dev_priv)->power_domains.power_wells >= 0; \ -+ (__power_well)--) -+ -+#define for_each_power_domain_well(__dev_priv, __power_well, __domain_mask) \ -+ for_each_power_well(__dev_priv, __power_well) \ -+ for_each_if((__power_well)->desc->domains & (__domain_mask)) -+ -+#define for_each_power_domain_well_reverse(__dev_priv, __power_well, __domain_mask) \ -+ for_each_power_well_reverse(__dev_priv, __power_well) \ -+ for_each_if((__power_well)->desc->domains & (__domain_mask)) -+ -+#define for_each_old_intel_plane_in_state(__state, plane, old_plane_state, __i) \ -+ for ((__i) = 0; \ -+ (__i) < (__state)->base.dev->mode_config.num_total_plane && \ -+ ((plane) = to_intel_plane((__state)->base.planes[__i].ptr), \ -+ (old_plane_state) = to_intel_plane_state((__state)->base.planes[__i].old_state), 1); \ -+ (__i)++) \ -+ for_each_if(plane) -+ -+#define for_each_new_intel_plane_in_state(__state, plane, new_plane_state, __i) \ -+ for ((__i) = 0; \ -+ (__i) < (__state)->base.dev->mode_config.num_total_plane && \ -+ ((plane) = to_intel_plane((__state)->base.planes[__i].ptr), \ -+ (new_plane_state) = to_intel_plane_state((__state)->base.planes[__i].new_state), 1); \ -+ (__i)++) \ -+ for_each_if(plane) -+ -+#define for_each_new_intel_crtc_in_state(__state, crtc, new_crtc_state, __i) \ -+ for ((__i) = 0; \ -+ (__i) < (__state)->base.dev->mode_config.num_crtc && \ -+ ((crtc) = to_intel_crtc((__state)->base.crtcs[__i].ptr), \ -+ (new_crtc_state) = to_intel_crtc_state((__state)->base.crtcs[__i].new_state), 1); \ -+ (__i)++) \ -+ for_each_if(crtc) -+ -+#define for_each_oldnew_intel_plane_in_state(__state, plane, old_plane_state, new_plane_state, __i) \ -+ for ((__i) = 0; \ -+ (__i) < (__state)->base.dev->mode_config.num_total_plane && \ -+ ((plane) = to_intel_plane((__state)->base.planes[__i].ptr), \ -+ (old_plane_state) = to_intel_plane_state((__state)->base.planes[__i].old_state), \ -+ (new_plane_state) = to_intel_plane_state((__state)->base.planes[__i].new_state), 1); \ -+ (__i)++) \ -+ for_each_if(plane) -+ -+#define for_each_oldnew_intel_crtc_in_state(__state, crtc, old_crtc_state, new_crtc_state, __i) \ -+ for ((__i) = 0; \ -+ (__i) < (__state)->base.dev->mode_config.num_crtc && \ -+ ((crtc) = to_intel_crtc((__state)->base.crtcs[__i].ptr), \ -+ (old_crtc_state) = to_intel_crtc_state((__state)->base.crtcs[__i].old_state), \ -+ (new_crtc_state) = to_intel_crtc_state((__state)->base.crtcs[__i].new_state), 1); \ -+ (__i)++) \ -+ for_each_if(crtc) -+ -+void intel_link_compute_m_n(u16 bpp, int nlanes, -+ int pixel_clock, int link_clock, -+ struct intel_link_m_n *m_n, -+ bool constant_n); -+bool is_ccs_modifier(u64 modifier); -+#endif -diff --git a/drivers/gpu/drm/i915_legacy/intel_dp.c b/drivers/gpu/drm/i915_legacy/intel_dp.c -new file mode 100644 -index 000000000000..560274d1c50b ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_dp.c -@@ -0,0 +1,7405 @@ -+/* -+ * Copyright © 2008 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Authors: -+ * Keith Packard -+ * -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "i915_drv.h" -+#include "intel_audio.h" -+#include "intel_connector.h" -+#include "intel_ddi.h" -+#include "intel_dp.h" -+#include "intel_drv.h" -+#include "intel_hdcp.h" -+#include "intel_hdmi.h" -+#include "intel_lspcon.h" -+#include "intel_lvds.h" -+#include "intel_panel.h" -+#include "intel_psr.h" -+ -+#define DP_DPRX_ESI_LEN 14 -+ -+/* DP DSC small joiner has 2 FIFOs each of 640 x 6 bytes */ -+#define DP_DSC_MAX_SMALL_JOINER_RAM_BUFFER 61440 -+#define DP_DSC_MIN_SUPPORTED_BPC 8 -+#define DP_DSC_MAX_SUPPORTED_BPC 10 -+ -+/* DP DSC throughput values used for slice count calculations KPixels/s */ -+#define DP_DSC_PEAK_PIXEL_RATE 2720000 -+#define DP_DSC_MAX_ENC_THROUGHPUT_0 340000 -+#define DP_DSC_MAX_ENC_THROUGHPUT_1 400000 -+ -+/* DP DSC FEC Overhead factor = (100 - 2.4)/100 */ -+#define DP_DSC_FEC_OVERHEAD_FACTOR 976 -+ -+/* Compliance test status bits */ -+#define INTEL_DP_RESOLUTION_SHIFT_MASK 0 -+#define INTEL_DP_RESOLUTION_PREFERRED (1 << INTEL_DP_RESOLUTION_SHIFT_MASK) -+#define INTEL_DP_RESOLUTION_STANDARD (2 << INTEL_DP_RESOLUTION_SHIFT_MASK) -+#define INTEL_DP_RESOLUTION_FAILSAFE (3 << INTEL_DP_RESOLUTION_SHIFT_MASK) -+ -+struct dp_link_dpll { -+ int clock; -+ struct dpll dpll; -+}; -+ -+static const struct dp_link_dpll g4x_dpll[] = { -+ { 162000, -+ { .p1 = 2, .p2 = 10, .n = 2, .m1 = 23, .m2 = 8 } }, -+ { 270000, -+ { .p1 = 1, .p2 = 10, .n = 1, .m1 = 14, .m2 = 2 } } -+}; -+ -+static const struct dp_link_dpll pch_dpll[] = { -+ { 162000, -+ { .p1 = 2, .p2 = 10, .n = 1, .m1 = 12, .m2 = 9 } }, -+ { 270000, -+ { .p1 = 1, .p2 = 10, .n = 2, .m1 = 14, .m2 = 8 } } -+}; -+ -+static const struct dp_link_dpll vlv_dpll[] = { -+ { 162000, -+ { .p1 = 3, .p2 = 2, .n = 5, .m1 = 3, .m2 = 81 } }, -+ { 270000, -+ { .p1 = 2, .p2 = 2, .n = 1, .m1 = 2, .m2 = 27 } } -+}; -+ -+/* -+ * CHV supports eDP 1.4 that have more link rates. -+ * Below only provides the fixed rate but exclude variable rate. -+ */ -+static const struct dp_link_dpll chv_dpll[] = { -+ /* -+ * CHV requires to program fractional division for m2. -+ * m2 is stored in fixed point format using formula below -+ * (m2_int << 22) | m2_fraction -+ */ -+ { 162000, /* m2_int = 32, m2_fraction = 1677722 */ -+ { .p1 = 4, .p2 = 2, .n = 1, .m1 = 2, .m2 = 0x819999a } }, -+ { 270000, /* m2_int = 27, m2_fraction = 0 */ -+ { .p1 = 4, .p2 = 1, .n = 1, .m1 = 2, .m2 = 0x6c00000 } }, -+}; -+ -+/* Constants for DP DSC configurations */ -+static const u8 valid_dsc_bpp[] = {6, 8, 10, 12, 15}; -+ -+/* With Single pipe configuration, HW is capable of supporting maximum -+ * of 4 slices per line. -+ */ -+static const u8 valid_dsc_slicecount[] = {1, 2, 4}; -+ -+/** -+ * intel_dp_is_edp - is the given port attached to an eDP panel (either CPU or PCH) -+ * @intel_dp: DP struct -+ * -+ * If a CPU or PCH DP output is attached to an eDP panel, this function -+ * will return true, and false otherwise. -+ */ -+bool intel_dp_is_edp(struct intel_dp *intel_dp) -+{ -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ -+ return intel_dig_port->base.type == INTEL_OUTPUT_EDP; -+} -+ -+static struct intel_dp *intel_attached_dp(struct drm_connector *connector) -+{ -+ return enc_to_intel_dp(&intel_attached_encoder(connector)->base); -+} -+ -+static void intel_dp_link_down(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state); -+static bool edp_panel_vdd_on(struct intel_dp *intel_dp); -+static void edp_panel_vdd_off(struct intel_dp *intel_dp, bool sync); -+static void vlv_init_panel_power_sequencer(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state); -+static void vlv_steal_power_sequencer(struct drm_i915_private *dev_priv, -+ enum pipe pipe); -+static void intel_dp_unset_edid(struct intel_dp *intel_dp); -+ -+/* update sink rates from dpcd */ -+static void intel_dp_set_sink_rates(struct intel_dp *intel_dp) -+{ -+ static const int dp_rates[] = { -+ 162000, 270000, 540000, 810000 -+ }; -+ int i, max_rate; -+ -+ max_rate = drm_dp_bw_code_to_link_rate(intel_dp->dpcd[DP_MAX_LINK_RATE]); -+ -+ for (i = 0; i < ARRAY_SIZE(dp_rates); i++) { -+ if (dp_rates[i] > max_rate) -+ break; -+ intel_dp->sink_rates[i] = dp_rates[i]; -+ } -+ -+ intel_dp->num_sink_rates = i; -+} -+ -+/* Get length of rates array potentially limited by max_rate. */ -+static int intel_dp_rate_limit_len(const int *rates, int len, int max_rate) -+{ -+ int i; -+ -+ /* Limit results by potentially reduced max rate */ -+ for (i = 0; i < len; i++) { -+ if (rates[len - i - 1] <= max_rate) -+ return len - i; -+ } -+ -+ return 0; -+} -+ -+/* Get length of common rates array potentially limited by max_rate. */ -+static int intel_dp_common_len_rate_limit(const struct intel_dp *intel_dp, -+ int max_rate) -+{ -+ return intel_dp_rate_limit_len(intel_dp->common_rates, -+ intel_dp->num_common_rates, max_rate); -+} -+ -+/* Theoretical max between source and sink */ -+static int intel_dp_max_common_rate(struct intel_dp *intel_dp) -+{ -+ return intel_dp->common_rates[intel_dp->num_common_rates - 1]; -+} -+ -+static int intel_dp_get_fia_supported_lane_count(struct intel_dp *intel_dp) -+{ -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); -+ enum tc_port tc_port = intel_port_to_tc(dev_priv, dig_port->base.port); -+ u32 lane_info; -+ -+ if (tc_port == PORT_TC_NONE || dig_port->tc_type != TC_PORT_TYPEC) -+ return 4; -+ -+ lane_info = (I915_READ(PORT_TX_DFLEXDPSP) & -+ DP_LANE_ASSIGNMENT_MASK(tc_port)) >> -+ DP_LANE_ASSIGNMENT_SHIFT(tc_port); -+ -+ switch (lane_info) { -+ default: -+ MISSING_CASE(lane_info); -+ case 1: -+ case 2: -+ case 4: -+ case 8: -+ return 1; -+ case 3: -+ case 12: -+ return 2; -+ case 15: -+ return 4; -+ } -+} -+ -+/* Theoretical max between source and sink */ -+static int intel_dp_max_common_lane_count(struct intel_dp *intel_dp) -+{ -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ int source_max = intel_dig_port->max_lanes; -+ int sink_max = drm_dp_max_lane_count(intel_dp->dpcd); -+ int fia_max = intel_dp_get_fia_supported_lane_count(intel_dp); -+ -+ return min3(source_max, sink_max, fia_max); -+} -+ -+int intel_dp_max_lane_count(struct intel_dp *intel_dp) -+{ -+ return intel_dp->max_link_lane_count; -+} -+ -+int -+intel_dp_link_required(int pixel_clock, int bpp) -+{ -+ /* pixel_clock is in kHz, divide bpp by 8 for bit to Byte conversion */ -+ return DIV_ROUND_UP(pixel_clock * bpp, 8); -+} -+ -+int -+intel_dp_max_data_rate(int max_link_clock, int max_lanes) -+{ -+ /* max_link_clock is the link symbol clock (LS_Clk) in kHz and not the -+ * link rate that is generally expressed in Gbps. Since, 8 bits of data -+ * is transmitted every LS_Clk per lane, there is no need to account for -+ * the channel encoding that is done in the PHY layer here. -+ */ -+ -+ return max_link_clock * max_lanes; -+} -+ -+static int -+intel_dp_downstream_max_dotclock(struct intel_dp *intel_dp) -+{ -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ struct intel_encoder *encoder = &intel_dig_port->base; -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ int max_dotclk = dev_priv->max_dotclk_freq; -+ int ds_max_dotclk; -+ -+ int type = intel_dp->downstream_ports[0] & DP_DS_PORT_TYPE_MASK; -+ -+ if (type != DP_DS_PORT_TYPE_VGA) -+ return max_dotclk; -+ -+ ds_max_dotclk = drm_dp_downstream_max_clock(intel_dp->dpcd, -+ intel_dp->downstream_ports); -+ -+ if (ds_max_dotclk != 0) -+ max_dotclk = min(max_dotclk, ds_max_dotclk); -+ -+ return max_dotclk; -+} -+ -+static int cnl_max_source_rate(struct intel_dp *intel_dp) -+{ -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); -+ enum port port = dig_port->base.port; -+ -+ u32 voltage = I915_READ(CNL_PORT_COMP_DW3) & VOLTAGE_INFO_MASK; -+ -+ /* Low voltage SKUs are limited to max of 5.4G */ -+ if (voltage == VOLTAGE_INFO_0_85V) -+ return 540000; -+ -+ /* For this SKU 8.1G is supported in all ports */ -+ if (IS_CNL_WITH_PORT_F(dev_priv)) -+ return 810000; -+ -+ /* For other SKUs, max rate on ports A and D is 5.4G */ -+ if (port == PORT_A || port == PORT_D) -+ return 540000; -+ -+ return 810000; -+} -+ -+static int icl_max_source_rate(struct intel_dp *intel_dp) -+{ -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); -+ enum port port = dig_port->base.port; -+ -+ if (intel_port_is_combophy(dev_priv, port) && -+ !intel_dp_is_edp(intel_dp)) -+ return 540000; -+ -+ return 810000; -+} -+ -+static void -+intel_dp_set_source_rates(struct intel_dp *intel_dp) -+{ -+ /* The values must be in increasing order */ -+ static const int cnl_rates[] = { -+ 162000, 216000, 270000, 324000, 432000, 540000, 648000, 810000 -+ }; -+ static const int bxt_rates[] = { -+ 162000, 216000, 243000, 270000, 324000, 432000, 540000 -+ }; -+ static const int skl_rates[] = { -+ 162000, 216000, 270000, 324000, 432000, 540000 -+ }; -+ static const int hsw_rates[] = { -+ 162000, 270000, 540000 -+ }; -+ static const int g4x_rates[] = { -+ 162000, 270000 -+ }; -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); -+ const struct ddi_vbt_port_info *info = -+ &dev_priv->vbt.ddi_port_info[dig_port->base.port]; -+ const int *source_rates; -+ int size, max_rate = 0, vbt_max_rate = info->dp_max_link_rate; -+ -+ /* This should only be done once */ -+ WARN_ON(intel_dp->source_rates || intel_dp->num_source_rates); -+ -+ if (INTEL_GEN(dev_priv) >= 10) { -+ source_rates = cnl_rates; -+ size = ARRAY_SIZE(cnl_rates); -+ if (IS_GEN(dev_priv, 10)) -+ max_rate = cnl_max_source_rate(intel_dp); -+ else -+ max_rate = icl_max_source_rate(intel_dp); -+ } else if (IS_GEN9_LP(dev_priv)) { -+ source_rates = bxt_rates; -+ size = ARRAY_SIZE(bxt_rates); -+ } else if (IS_GEN9_BC(dev_priv)) { -+ source_rates = skl_rates; -+ size = ARRAY_SIZE(skl_rates); -+ } else if ((IS_HASWELL(dev_priv) && !IS_HSW_ULX(dev_priv)) || -+ IS_BROADWELL(dev_priv)) { -+ source_rates = hsw_rates; -+ size = ARRAY_SIZE(hsw_rates); -+ } else { -+ source_rates = g4x_rates; -+ size = ARRAY_SIZE(g4x_rates); -+ } -+ -+ if (max_rate && vbt_max_rate) -+ max_rate = min(max_rate, vbt_max_rate); -+ else if (vbt_max_rate) -+ max_rate = vbt_max_rate; -+ -+ if (max_rate) -+ size = intel_dp_rate_limit_len(source_rates, size, max_rate); -+ -+ intel_dp->source_rates = source_rates; -+ intel_dp->num_source_rates = size; -+} -+ -+static int intersect_rates(const int *source_rates, int source_len, -+ const int *sink_rates, int sink_len, -+ int *common_rates) -+{ -+ int i = 0, j = 0, k = 0; -+ -+ while (i < source_len && j < sink_len) { -+ if (source_rates[i] == sink_rates[j]) { -+ if (WARN_ON(k >= DP_MAX_SUPPORTED_RATES)) -+ return k; -+ common_rates[k] = source_rates[i]; -+ ++k; -+ ++i; -+ ++j; -+ } else if (source_rates[i] < sink_rates[j]) { -+ ++i; -+ } else { -+ ++j; -+ } -+ } -+ return k; -+} -+ -+/* return index of rate in rates array, or -1 if not found */ -+static int intel_dp_rate_index(const int *rates, int len, int rate) -+{ -+ int i; -+ -+ for (i = 0; i < len; i++) -+ if (rate == rates[i]) -+ return i; -+ -+ return -1; -+} -+ -+static void intel_dp_set_common_rates(struct intel_dp *intel_dp) -+{ -+ WARN_ON(!intel_dp->num_source_rates || !intel_dp->num_sink_rates); -+ -+ intel_dp->num_common_rates = intersect_rates(intel_dp->source_rates, -+ intel_dp->num_source_rates, -+ intel_dp->sink_rates, -+ intel_dp->num_sink_rates, -+ intel_dp->common_rates); -+ -+ /* Paranoia, there should always be something in common. */ -+ if (WARN_ON(intel_dp->num_common_rates == 0)) { -+ intel_dp->common_rates[0] = 162000; -+ intel_dp->num_common_rates = 1; -+ } -+} -+ -+static bool intel_dp_link_params_valid(struct intel_dp *intel_dp, int link_rate, -+ u8 lane_count) -+{ -+ /* -+ * FIXME: we need to synchronize the current link parameters with -+ * hardware readout. Currently fast link training doesn't work on -+ * boot-up. -+ */ -+ if (link_rate == 0 || -+ link_rate > intel_dp->max_link_rate) -+ return false; -+ -+ if (lane_count == 0 || -+ lane_count > intel_dp_max_lane_count(intel_dp)) -+ return false; -+ -+ return true; -+} -+ -+static bool intel_dp_can_link_train_fallback_for_edp(struct intel_dp *intel_dp, -+ int link_rate, -+ u8 lane_count) -+{ -+ const struct drm_display_mode *fixed_mode = -+ intel_dp->attached_connector->panel.fixed_mode; -+ int mode_rate, max_rate; -+ -+ mode_rate = intel_dp_link_required(fixed_mode->clock, 18); -+ max_rate = intel_dp_max_data_rate(link_rate, lane_count); -+ if (mode_rate > max_rate) -+ return false; -+ -+ return true; -+} -+ -+int intel_dp_get_link_train_fallback_values(struct intel_dp *intel_dp, -+ int link_rate, u8 lane_count) -+{ -+ int index; -+ -+ index = intel_dp_rate_index(intel_dp->common_rates, -+ intel_dp->num_common_rates, -+ link_rate); -+ if (index > 0) { -+ if (intel_dp_is_edp(intel_dp) && -+ !intel_dp_can_link_train_fallback_for_edp(intel_dp, -+ intel_dp->common_rates[index - 1], -+ lane_count)) { -+ DRM_DEBUG_KMS("Retrying Link training for eDP with same parameters\n"); -+ return 0; -+ } -+ intel_dp->max_link_rate = intel_dp->common_rates[index - 1]; -+ intel_dp->max_link_lane_count = lane_count; -+ } else if (lane_count > 1) { -+ if (intel_dp_is_edp(intel_dp) && -+ !intel_dp_can_link_train_fallback_for_edp(intel_dp, -+ intel_dp_max_common_rate(intel_dp), -+ lane_count >> 1)) { -+ DRM_DEBUG_KMS("Retrying Link training for eDP with same parameters\n"); -+ return 0; -+ } -+ intel_dp->max_link_rate = intel_dp_max_common_rate(intel_dp); -+ intel_dp->max_link_lane_count = lane_count >> 1; -+ } else { -+ DRM_ERROR("Link Training Unsuccessful\n"); -+ return -1; -+ } -+ -+ return 0; -+} -+ -+static enum drm_mode_status -+intel_dp_mode_valid(struct drm_connector *connector, -+ struct drm_display_mode *mode) -+{ -+ struct intel_dp *intel_dp = intel_attached_dp(connector); -+ struct intel_connector *intel_connector = to_intel_connector(connector); -+ struct drm_display_mode *fixed_mode = intel_connector->panel.fixed_mode; -+ struct drm_i915_private *dev_priv = to_i915(connector->dev); -+ int target_clock = mode->clock; -+ int max_rate, mode_rate, max_lanes, max_link_clock; -+ int max_dotclk; -+ u16 dsc_max_output_bpp = 0; -+ u8 dsc_slice_count = 0; -+ -+ if (mode->flags & DRM_MODE_FLAG_DBLSCAN) -+ return MODE_NO_DBLESCAN; -+ -+ max_dotclk = intel_dp_downstream_max_dotclock(intel_dp); -+ -+ if (intel_dp_is_edp(intel_dp) && fixed_mode) { -+ if (mode->hdisplay > fixed_mode->hdisplay) -+ return MODE_PANEL; -+ -+ if (mode->vdisplay > fixed_mode->vdisplay) -+ return MODE_PANEL; -+ -+ target_clock = fixed_mode->clock; -+ } -+ -+ max_link_clock = intel_dp_max_link_rate(intel_dp); -+ max_lanes = intel_dp_max_lane_count(intel_dp); -+ -+ max_rate = intel_dp_max_data_rate(max_link_clock, max_lanes); -+ mode_rate = intel_dp_link_required(target_clock, 18); -+ -+ /* -+ * Output bpp is stored in 6.4 format so right shift by 4 to get the -+ * integer value since we support only integer values of bpp. -+ */ -+ if ((INTEL_GEN(dev_priv) >= 10 || IS_GEMINILAKE(dev_priv)) && -+ drm_dp_sink_supports_dsc(intel_dp->dsc_dpcd)) { -+ if (intel_dp_is_edp(intel_dp)) { -+ dsc_max_output_bpp = -+ drm_edp_dsc_sink_output_bpp(intel_dp->dsc_dpcd) >> 4; -+ dsc_slice_count = -+ drm_dp_dsc_sink_max_slice_count(intel_dp->dsc_dpcd, -+ true); -+ } else if (drm_dp_sink_supports_fec(intel_dp->fec_capable)) { -+ dsc_max_output_bpp = -+ intel_dp_dsc_get_output_bpp(max_link_clock, -+ max_lanes, -+ target_clock, -+ mode->hdisplay) >> 4; -+ dsc_slice_count = -+ intel_dp_dsc_get_slice_count(intel_dp, -+ target_clock, -+ mode->hdisplay); -+ } -+ } -+ -+ if ((mode_rate > max_rate && !(dsc_max_output_bpp && dsc_slice_count)) || -+ target_clock > max_dotclk) -+ return MODE_CLOCK_HIGH; -+ -+ if (mode->clock < 10000) -+ return MODE_CLOCK_LOW; -+ -+ if (mode->flags & DRM_MODE_FLAG_DBLCLK) -+ return MODE_H_ILLEGAL; -+ -+ return MODE_OK; -+} -+ -+u32 intel_dp_pack_aux(const u8 *src, int src_bytes) -+{ -+ int i; -+ u32 v = 0; -+ -+ if (src_bytes > 4) -+ src_bytes = 4; -+ for (i = 0; i < src_bytes; i++) -+ v |= ((u32)src[i]) << ((3 - i) * 8); -+ return v; -+} -+ -+static void intel_dp_unpack_aux(u32 src, u8 *dst, int dst_bytes) -+{ -+ int i; -+ if (dst_bytes > 4) -+ dst_bytes = 4; -+ for (i = 0; i < dst_bytes; i++) -+ dst[i] = src >> ((3-i) * 8); -+} -+ -+static void -+intel_dp_init_panel_power_sequencer(struct intel_dp *intel_dp); -+static void -+intel_dp_init_panel_power_sequencer_registers(struct intel_dp *intel_dp, -+ bool force_disable_vdd); -+static void -+intel_dp_pps_init(struct intel_dp *intel_dp); -+ -+static intel_wakeref_t -+pps_lock(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ intel_wakeref_t wakeref; -+ -+ /* -+ * See intel_power_sequencer_reset() why we need -+ * a power domain reference here. -+ */ -+ wakeref = intel_display_power_get(dev_priv, -+ intel_aux_power_domain(dp_to_dig_port(intel_dp))); -+ -+ mutex_lock(&dev_priv->pps_mutex); -+ -+ return wakeref; -+} -+ -+static intel_wakeref_t -+pps_unlock(struct intel_dp *intel_dp, intel_wakeref_t wakeref) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ -+ mutex_unlock(&dev_priv->pps_mutex); -+ intel_display_power_put(dev_priv, -+ intel_aux_power_domain(dp_to_dig_port(intel_dp)), -+ wakeref); -+ return 0; -+} -+ -+#define with_pps_lock(dp, wf) \ -+ for ((wf) = pps_lock(dp); (wf); (wf) = pps_unlock((dp), (wf))) -+ -+static void -+vlv_power_sequencer_kick(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ enum pipe pipe = intel_dp->pps_pipe; -+ bool pll_enabled, release_cl_override = false; -+ enum dpio_phy phy = DPIO_PHY(pipe); -+ enum dpio_channel ch = vlv_pipe_to_channel(pipe); -+ u32 DP; -+ -+ if (WARN(I915_READ(intel_dp->output_reg) & DP_PORT_EN, -+ "skipping pipe %c power sequencer kick due to port %c being active\n", -+ pipe_name(pipe), port_name(intel_dig_port->base.port))) -+ return; -+ -+ DRM_DEBUG_KMS("kicking pipe %c power sequencer for port %c\n", -+ pipe_name(pipe), port_name(intel_dig_port->base.port)); -+ -+ /* Preserve the BIOS-computed detected bit. This is -+ * supposed to be read-only. -+ */ -+ DP = I915_READ(intel_dp->output_reg) & DP_DETECTED; -+ DP |= DP_VOLTAGE_0_4 | DP_PRE_EMPHASIS_0; -+ DP |= DP_PORT_WIDTH(1); -+ DP |= DP_LINK_TRAIN_PAT_1; -+ -+ if (IS_CHERRYVIEW(dev_priv)) -+ DP |= DP_PIPE_SEL_CHV(pipe); -+ else -+ DP |= DP_PIPE_SEL(pipe); -+ -+ pll_enabled = I915_READ(DPLL(pipe)) & DPLL_VCO_ENABLE; -+ -+ /* -+ * The DPLL for the pipe must be enabled for this to work. -+ * So enable temporarily it if it's not already enabled. -+ */ -+ if (!pll_enabled) { -+ release_cl_override = IS_CHERRYVIEW(dev_priv) && -+ !chv_phy_powergate_ch(dev_priv, phy, ch, true); -+ -+ if (vlv_force_pll_on(dev_priv, pipe, IS_CHERRYVIEW(dev_priv) ? -+ &chv_dpll[0].dpll : &vlv_dpll[0].dpll)) { -+ DRM_ERROR("Failed to force on pll for pipe %c!\n", -+ pipe_name(pipe)); -+ return; -+ } -+ } -+ -+ /* -+ * Similar magic as in intel_dp_enable_port(). -+ * We _must_ do this port enable + disable trick -+ * to make this power sequencer lock onto the port. -+ * Otherwise even VDD force bit won't work. -+ */ -+ I915_WRITE(intel_dp->output_reg, DP); -+ POSTING_READ(intel_dp->output_reg); -+ -+ I915_WRITE(intel_dp->output_reg, DP | DP_PORT_EN); -+ POSTING_READ(intel_dp->output_reg); -+ -+ I915_WRITE(intel_dp->output_reg, DP & ~DP_PORT_EN); -+ POSTING_READ(intel_dp->output_reg); -+ -+ if (!pll_enabled) { -+ vlv_force_pll_off(dev_priv, pipe); -+ -+ if (release_cl_override) -+ chv_phy_powergate_ch(dev_priv, phy, ch, false); -+ } -+} -+ -+static enum pipe vlv_find_free_pps(struct drm_i915_private *dev_priv) -+{ -+ struct intel_encoder *encoder; -+ unsigned int pipes = (1 << PIPE_A) | (1 << PIPE_B); -+ -+ /* -+ * We don't have power sequencer currently. -+ * Pick one that's not used by other ports. -+ */ -+ for_each_intel_dp(&dev_priv->drm, encoder) { -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ -+ if (encoder->type == INTEL_OUTPUT_EDP) { -+ WARN_ON(intel_dp->active_pipe != INVALID_PIPE && -+ intel_dp->active_pipe != intel_dp->pps_pipe); -+ -+ if (intel_dp->pps_pipe != INVALID_PIPE) -+ pipes &= ~(1 << intel_dp->pps_pipe); -+ } else { -+ WARN_ON(intel_dp->pps_pipe != INVALID_PIPE); -+ -+ if (intel_dp->active_pipe != INVALID_PIPE) -+ pipes &= ~(1 << intel_dp->active_pipe); -+ } -+ } -+ -+ if (pipes == 0) -+ return INVALID_PIPE; -+ -+ return ffs(pipes) - 1; -+} -+ -+static enum pipe -+vlv_power_sequencer_pipe(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ enum pipe pipe; -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ /* We should never land here with regular DP ports */ -+ WARN_ON(!intel_dp_is_edp(intel_dp)); -+ -+ WARN_ON(intel_dp->active_pipe != INVALID_PIPE && -+ intel_dp->active_pipe != intel_dp->pps_pipe); -+ -+ if (intel_dp->pps_pipe != INVALID_PIPE) -+ return intel_dp->pps_pipe; -+ -+ pipe = vlv_find_free_pps(dev_priv); -+ -+ /* -+ * Didn't find one. This should not happen since there -+ * are two power sequencers and up to two eDP ports. -+ */ -+ if (WARN_ON(pipe == INVALID_PIPE)) -+ pipe = PIPE_A; -+ -+ vlv_steal_power_sequencer(dev_priv, pipe); -+ intel_dp->pps_pipe = pipe; -+ -+ DRM_DEBUG_KMS("picked pipe %c power sequencer for port %c\n", -+ pipe_name(intel_dp->pps_pipe), -+ port_name(intel_dig_port->base.port)); -+ -+ /* init power sequencer on this pipe and port */ -+ intel_dp_init_panel_power_sequencer(intel_dp); -+ intel_dp_init_panel_power_sequencer_registers(intel_dp, true); -+ -+ /* -+ * Even vdd force doesn't work until we've made -+ * the power sequencer lock in on the port. -+ */ -+ vlv_power_sequencer_kick(intel_dp); -+ -+ return intel_dp->pps_pipe; -+} -+ -+static int -+bxt_power_sequencer_idx(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ int backlight_controller = dev_priv->vbt.backlight.controller; -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ /* We should never land here with regular DP ports */ -+ WARN_ON(!intel_dp_is_edp(intel_dp)); -+ -+ if (!intel_dp->pps_reset) -+ return backlight_controller; -+ -+ intel_dp->pps_reset = false; -+ -+ /* -+ * Only the HW needs to be reprogrammed, the SW state is fixed and -+ * has been setup during connector init. -+ */ -+ intel_dp_init_panel_power_sequencer_registers(intel_dp, false); -+ -+ return backlight_controller; -+} -+ -+typedef bool (*vlv_pipe_check)(struct drm_i915_private *dev_priv, -+ enum pipe pipe); -+ -+static bool vlv_pipe_has_pp_on(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ return I915_READ(PP_STATUS(pipe)) & PP_ON; -+} -+ -+static bool vlv_pipe_has_vdd_on(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ return I915_READ(PP_CONTROL(pipe)) & EDP_FORCE_VDD; -+} -+ -+static bool vlv_pipe_any(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ return true; -+} -+ -+static enum pipe -+vlv_initial_pps_pipe(struct drm_i915_private *dev_priv, -+ enum port port, -+ vlv_pipe_check pipe_check) -+{ -+ enum pipe pipe; -+ -+ for (pipe = PIPE_A; pipe <= PIPE_B; pipe++) { -+ u32 port_sel = I915_READ(PP_ON_DELAYS(pipe)) & -+ PANEL_PORT_SELECT_MASK; -+ -+ if (port_sel != PANEL_PORT_SELECT_VLV(port)) -+ continue; -+ -+ if (!pipe_check(dev_priv, pipe)) -+ continue; -+ -+ return pipe; -+ } -+ -+ return INVALID_PIPE; -+} -+ -+static void -+vlv_initial_power_sequencer_setup(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ enum port port = intel_dig_port->base.port; -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ /* try to find a pipe with this port selected */ -+ /* first pick one where the panel is on */ -+ intel_dp->pps_pipe = vlv_initial_pps_pipe(dev_priv, port, -+ vlv_pipe_has_pp_on); -+ /* didn't find one? pick one where vdd is on */ -+ if (intel_dp->pps_pipe == INVALID_PIPE) -+ intel_dp->pps_pipe = vlv_initial_pps_pipe(dev_priv, port, -+ vlv_pipe_has_vdd_on); -+ /* didn't find one? pick one with just the correct port */ -+ if (intel_dp->pps_pipe == INVALID_PIPE) -+ intel_dp->pps_pipe = vlv_initial_pps_pipe(dev_priv, port, -+ vlv_pipe_any); -+ -+ /* didn't find one? just let vlv_power_sequencer_pipe() pick one when needed */ -+ if (intel_dp->pps_pipe == INVALID_PIPE) { -+ DRM_DEBUG_KMS("no initial power sequencer for port %c\n", -+ port_name(port)); -+ return; -+ } -+ -+ DRM_DEBUG_KMS("initial power sequencer for port %c: pipe %c\n", -+ port_name(port), pipe_name(intel_dp->pps_pipe)); -+ -+ intel_dp_init_panel_power_sequencer(intel_dp); -+ intel_dp_init_panel_power_sequencer_registers(intel_dp, false); -+} -+ -+void intel_power_sequencer_reset(struct drm_i915_private *dev_priv) -+{ -+ struct intel_encoder *encoder; -+ -+ if (WARN_ON(!IS_VALLEYVIEW(dev_priv) && !IS_CHERRYVIEW(dev_priv) && -+ !IS_GEN9_LP(dev_priv))) -+ return; -+ -+ /* -+ * We can't grab pps_mutex here due to deadlock with power_domain -+ * mutex when power_domain functions are called while holding pps_mutex. -+ * That also means that in order to use pps_pipe the code needs to -+ * hold both a power domain reference and pps_mutex, and the power domain -+ * reference get/put must be done while _not_ holding pps_mutex. -+ * pps_{lock,unlock}() do these steps in the correct order, so one -+ * should use them always. -+ */ -+ -+ for_each_intel_dp(&dev_priv->drm, encoder) { -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ -+ WARN_ON(intel_dp->active_pipe != INVALID_PIPE); -+ -+ if (encoder->type != INTEL_OUTPUT_EDP) -+ continue; -+ -+ if (IS_GEN9_LP(dev_priv)) -+ intel_dp->pps_reset = true; -+ else -+ intel_dp->pps_pipe = INVALID_PIPE; -+ } -+} -+ -+struct pps_registers { -+ i915_reg_t pp_ctrl; -+ i915_reg_t pp_stat; -+ i915_reg_t pp_on; -+ i915_reg_t pp_off; -+ i915_reg_t pp_div; -+}; -+ -+static void intel_pps_get_registers(struct intel_dp *intel_dp, -+ struct pps_registers *regs) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ int pps_idx = 0; -+ -+ memset(regs, 0, sizeof(*regs)); -+ -+ if (IS_GEN9_LP(dev_priv)) -+ pps_idx = bxt_power_sequencer_idx(intel_dp); -+ else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ pps_idx = vlv_power_sequencer_pipe(intel_dp); -+ -+ regs->pp_ctrl = PP_CONTROL(pps_idx); -+ regs->pp_stat = PP_STATUS(pps_idx); -+ regs->pp_on = PP_ON_DELAYS(pps_idx); -+ regs->pp_off = PP_OFF_DELAYS(pps_idx); -+ -+ /* Cycle delay moved from PP_DIVISOR to PP_CONTROL */ -+ if (IS_GEN9_LP(dev_priv) || INTEL_PCH_TYPE(dev_priv) >= PCH_CNP) -+ regs->pp_div = INVALID_MMIO_REG; -+ else -+ regs->pp_div = PP_DIVISOR(pps_idx); -+} -+ -+static i915_reg_t -+_pp_ctrl_reg(struct intel_dp *intel_dp) -+{ -+ struct pps_registers regs; -+ -+ intel_pps_get_registers(intel_dp, ®s); -+ -+ return regs.pp_ctrl; -+} -+ -+static i915_reg_t -+_pp_stat_reg(struct intel_dp *intel_dp) -+{ -+ struct pps_registers regs; -+ -+ intel_pps_get_registers(intel_dp, ®s); -+ -+ return regs.pp_stat; -+} -+ -+/* Reboot notifier handler to shutdown panel power to guarantee T12 timing -+ This function only applicable when panel PM state is not to be tracked */ -+static int edp_notify_handler(struct notifier_block *this, unsigned long code, -+ void *unused) -+{ -+ struct intel_dp *intel_dp = container_of(this, typeof(* intel_dp), -+ edp_notifier); -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ intel_wakeref_t wakeref; -+ -+ if (!intel_dp_is_edp(intel_dp) || code != SYS_RESTART) -+ return 0; -+ -+ with_pps_lock(intel_dp, wakeref) { -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ enum pipe pipe = vlv_power_sequencer_pipe(intel_dp); -+ i915_reg_t pp_ctrl_reg, pp_div_reg; -+ u32 pp_div; -+ -+ pp_ctrl_reg = PP_CONTROL(pipe); -+ pp_div_reg = PP_DIVISOR(pipe); -+ pp_div = I915_READ(pp_div_reg); -+ pp_div &= PP_REFERENCE_DIVIDER_MASK; -+ -+ /* 0x1F write to PP_DIV_REG sets max cycle delay */ -+ I915_WRITE(pp_div_reg, pp_div | 0x1F); -+ I915_WRITE(pp_ctrl_reg, PANEL_UNLOCK_REGS); -+ msleep(intel_dp->panel_power_cycle_delay); -+ } -+ } -+ -+ return 0; -+} -+ -+static bool edp_have_panel_power(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ if ((IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) && -+ intel_dp->pps_pipe == INVALID_PIPE) -+ return false; -+ -+ return (I915_READ(_pp_stat_reg(intel_dp)) & PP_ON) != 0; -+} -+ -+static bool edp_have_panel_vdd(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ if ((IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) && -+ intel_dp->pps_pipe == INVALID_PIPE) -+ return false; -+ -+ return I915_READ(_pp_ctrl_reg(intel_dp)) & EDP_FORCE_VDD; -+} -+ -+static void -+intel_dp_check_edp(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ -+ if (!intel_dp_is_edp(intel_dp)) -+ return; -+ -+ if (!edp_have_panel_power(intel_dp) && !edp_have_panel_vdd(intel_dp)) { -+ WARN(1, "eDP powered off while attempting aux channel communication.\n"); -+ DRM_DEBUG_KMS("Status 0x%08x Control 0x%08x\n", -+ I915_READ(_pp_stat_reg(intel_dp)), -+ I915_READ(_pp_ctrl_reg(intel_dp))); -+ } -+} -+ -+static u32 -+intel_dp_aux_wait_done(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ i915_reg_t ch_ctl = intel_dp->aux_ch_ctl_reg(intel_dp); -+ u32 status; -+ bool done; -+ -+#define C (((status = I915_READ_NOTRACE(ch_ctl)) & DP_AUX_CH_CTL_SEND_BUSY) == 0) -+ done = wait_event_timeout(dev_priv->gmbus_wait_queue, C, -+ msecs_to_jiffies_timeout(10)); -+ -+ /* just trace the final value */ -+ trace_i915_reg_rw(false, ch_ctl, status, sizeof(status), true); -+ -+ if (!done) -+ DRM_ERROR("dp aux hw did not signal timeout!\n"); -+#undef C -+ -+ return status; -+} -+ -+static u32 g4x_get_aux_clock_divider(struct intel_dp *intel_dp, int index) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ -+ if (index) -+ return 0; -+ -+ /* -+ * The clock divider is based off the hrawclk, and would like to run at -+ * 2MHz. So, take the hrawclk value and divide by 2000 and use that -+ */ -+ return DIV_ROUND_CLOSEST(dev_priv->rawclk_freq, 2000); -+} -+ -+static u32 ilk_get_aux_clock_divider(struct intel_dp *intel_dp, int index) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ -+ if (index) -+ return 0; -+ -+ /* -+ * The clock divider is based off the cdclk or PCH rawclk, and would -+ * like to run at 2MHz. So, take the cdclk or PCH rawclk value and -+ * divide by 2000 and use that -+ */ -+ if (dig_port->aux_ch == AUX_CH_A) -+ return DIV_ROUND_CLOSEST(dev_priv->cdclk.hw.cdclk, 2000); -+ else -+ return DIV_ROUND_CLOSEST(dev_priv->rawclk_freq, 2000); -+} -+ -+static u32 hsw_get_aux_clock_divider(struct intel_dp *intel_dp, int index) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ -+ if (dig_port->aux_ch != AUX_CH_A && HAS_PCH_LPT_H(dev_priv)) { -+ /* Workaround for non-ULT HSW */ -+ switch (index) { -+ case 0: return 63; -+ case 1: return 72; -+ default: return 0; -+ } -+ } -+ -+ return ilk_get_aux_clock_divider(intel_dp, index); -+} -+ -+static u32 skl_get_aux_clock_divider(struct intel_dp *intel_dp, int index) -+{ -+ /* -+ * SKL doesn't need us to program the AUX clock divider (Hardware will -+ * derive the clock from CDCLK automatically). We still implement the -+ * get_aux_clock_divider vfunc to plug-in into the existing code. -+ */ -+ return index ? 0 : 1; -+} -+ -+static u32 g4x_get_aux_send_ctl(struct intel_dp *intel_dp, -+ int send_bytes, -+ u32 aux_clock_divider) -+{ -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = -+ to_i915(intel_dig_port->base.base.dev); -+ u32 precharge, timeout; -+ -+ if (IS_GEN(dev_priv, 6)) -+ precharge = 3; -+ else -+ precharge = 5; -+ -+ if (IS_BROADWELL(dev_priv)) -+ timeout = DP_AUX_CH_CTL_TIME_OUT_600us; -+ else -+ timeout = DP_AUX_CH_CTL_TIME_OUT_400us; -+ -+ return DP_AUX_CH_CTL_SEND_BUSY | -+ DP_AUX_CH_CTL_DONE | -+ DP_AUX_CH_CTL_INTERRUPT | -+ DP_AUX_CH_CTL_TIME_OUT_ERROR | -+ timeout | -+ DP_AUX_CH_CTL_RECEIVE_ERROR | -+ (send_bytes << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT) | -+ (precharge << DP_AUX_CH_CTL_PRECHARGE_2US_SHIFT) | -+ (aux_clock_divider << DP_AUX_CH_CTL_BIT_CLOCK_2X_SHIFT); -+} -+ -+static u32 skl_get_aux_send_ctl(struct intel_dp *intel_dp, -+ int send_bytes, -+ u32 unused) -+{ -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ u32 ret; -+ -+ ret = DP_AUX_CH_CTL_SEND_BUSY | -+ DP_AUX_CH_CTL_DONE | -+ DP_AUX_CH_CTL_INTERRUPT | -+ DP_AUX_CH_CTL_TIME_OUT_ERROR | -+ DP_AUX_CH_CTL_TIME_OUT_MAX | -+ DP_AUX_CH_CTL_RECEIVE_ERROR | -+ (send_bytes << DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT) | -+ DP_AUX_CH_CTL_FW_SYNC_PULSE_SKL(32) | -+ DP_AUX_CH_CTL_SYNC_PULSE_SKL(32); -+ -+ if (intel_dig_port->tc_type == TC_PORT_TBT) -+ ret |= DP_AUX_CH_CTL_TBT_IO; -+ -+ return ret; -+} -+ -+static int -+intel_dp_aux_xfer(struct intel_dp *intel_dp, -+ const u8 *send, int send_bytes, -+ u8 *recv, int recv_size, -+ u32 aux_send_ctl_flags) -+{ -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = -+ to_i915(intel_dig_port->base.base.dev); -+ i915_reg_t ch_ctl, ch_data[5]; -+ u32 aux_clock_divider; -+ intel_wakeref_t wakeref; -+ int i, ret, recv_bytes; -+ int try, clock = 0; -+ u32 status; -+ bool vdd; -+ -+ ch_ctl = intel_dp->aux_ch_ctl_reg(intel_dp); -+ for (i = 0; i < ARRAY_SIZE(ch_data); i++) -+ ch_data[i] = intel_dp->aux_ch_data_reg(intel_dp, i); -+ -+ wakeref = pps_lock(intel_dp); -+ -+ /* -+ * We will be called with VDD already enabled for dpcd/edid/oui reads. -+ * In such cases we want to leave VDD enabled and it's up to upper layers -+ * to turn it off. But for eg. i2c-dev access we need to turn it on/off -+ * ourselves. -+ */ -+ vdd = edp_panel_vdd_on(intel_dp); -+ -+ /* dp aux is extremely sensitive to irq latency, hence request the -+ * lowest possible wakeup latency and so prevent the cpu from going into -+ * deep sleep states. -+ */ -+ pm_qos_update_request(&dev_priv->pm_qos, 0); -+ -+ intel_dp_check_edp(intel_dp); -+ -+ /* Try to wait for any previous AUX channel activity */ -+ for (try = 0; try < 3; try++) { -+ status = I915_READ_NOTRACE(ch_ctl); -+ if ((status & DP_AUX_CH_CTL_SEND_BUSY) == 0) -+ break; -+ msleep(1); -+ } -+ /* just trace the final value */ -+ trace_i915_reg_rw(false, ch_ctl, status, sizeof(status), true); -+ -+ if (try == 3) { -+ static u32 last_status = -1; -+ const u32 status = I915_READ(ch_ctl); -+ -+ if (status != last_status) { -+ WARN(1, "dp_aux_ch not started status 0x%08x\n", -+ status); -+ last_status = status; -+ } -+ -+ ret = -EBUSY; -+ goto out; -+ } -+ -+ /* Only 5 data registers! */ -+ if (WARN_ON(send_bytes > 20 || recv_size > 20)) { -+ ret = -E2BIG; -+ goto out; -+ } -+ -+ while ((aux_clock_divider = intel_dp->get_aux_clock_divider(intel_dp, clock++))) { -+ u32 send_ctl = intel_dp->get_aux_send_ctl(intel_dp, -+ send_bytes, -+ aux_clock_divider); -+ -+ send_ctl |= aux_send_ctl_flags; -+ -+ /* Must try at least 3 times according to DP spec */ -+ for (try = 0; try < 5; try++) { -+ /* Load the send data into the aux channel data registers */ -+ for (i = 0; i < send_bytes; i += 4) -+ I915_WRITE(ch_data[i >> 2], -+ intel_dp_pack_aux(send + i, -+ send_bytes - i)); -+ -+ /* Send the command and wait for it to complete */ -+ I915_WRITE(ch_ctl, send_ctl); -+ -+ status = intel_dp_aux_wait_done(intel_dp); -+ -+ /* Clear done status and any errors */ -+ I915_WRITE(ch_ctl, -+ status | -+ DP_AUX_CH_CTL_DONE | -+ DP_AUX_CH_CTL_TIME_OUT_ERROR | -+ DP_AUX_CH_CTL_RECEIVE_ERROR); -+ -+ /* DP CTS 1.2 Core Rev 1.1, 4.2.1.1 & 4.2.1.2 -+ * 400us delay required for errors and timeouts -+ * Timeout errors from the HW already meet this -+ * requirement so skip to next iteration -+ */ -+ if (status & DP_AUX_CH_CTL_TIME_OUT_ERROR) -+ continue; -+ -+ if (status & DP_AUX_CH_CTL_RECEIVE_ERROR) { -+ usleep_range(400, 500); -+ continue; -+ } -+ if (status & DP_AUX_CH_CTL_DONE) -+ goto done; -+ } -+ } -+ -+ if ((status & DP_AUX_CH_CTL_DONE) == 0) { -+ DRM_ERROR("dp_aux_ch not done status 0x%08x\n", status); -+ ret = -EBUSY; -+ goto out; -+ } -+ -+done: -+ /* Check for timeout or receive error. -+ * Timeouts occur when the sink is not connected -+ */ -+ if (status & DP_AUX_CH_CTL_RECEIVE_ERROR) { -+ DRM_ERROR("dp_aux_ch receive error status 0x%08x\n", status); -+ ret = -EIO; -+ goto out; -+ } -+ -+ /* Timeouts occur when the device isn't connected, so they're -+ * "normal" -- don't fill the kernel log with these */ -+ if (status & DP_AUX_CH_CTL_TIME_OUT_ERROR) { -+ DRM_DEBUG_KMS("dp_aux_ch timeout status 0x%08x\n", status); -+ ret = -ETIMEDOUT; -+ goto out; -+ } -+ -+ /* Unload any bytes sent back from the other side */ -+ recv_bytes = ((status & DP_AUX_CH_CTL_MESSAGE_SIZE_MASK) >> -+ DP_AUX_CH_CTL_MESSAGE_SIZE_SHIFT); -+ -+ /* -+ * By BSpec: "Message sizes of 0 or >20 are not allowed." -+ * We have no idea of what happened so we return -EBUSY so -+ * drm layer takes care for the necessary retries. -+ */ -+ if (recv_bytes == 0 || recv_bytes > 20) { -+ DRM_DEBUG_KMS("Forbidden recv_bytes = %d on aux transaction\n", -+ recv_bytes); -+ ret = -EBUSY; -+ goto out; -+ } -+ -+ if (recv_bytes > recv_size) -+ recv_bytes = recv_size; -+ -+ for (i = 0; i < recv_bytes; i += 4) -+ intel_dp_unpack_aux(I915_READ(ch_data[i >> 2]), -+ recv + i, recv_bytes - i); -+ -+ ret = recv_bytes; -+out: -+ pm_qos_update_request(&dev_priv->pm_qos, PM_QOS_DEFAULT_VALUE); -+ -+ if (vdd) -+ edp_panel_vdd_off(intel_dp, false); -+ -+ pps_unlock(intel_dp, wakeref); -+ -+ return ret; -+} -+ -+#define BARE_ADDRESS_SIZE 3 -+#define HEADER_SIZE (BARE_ADDRESS_SIZE + 1) -+ -+static void -+intel_dp_aux_header(u8 txbuf[HEADER_SIZE], -+ const struct drm_dp_aux_msg *msg) -+{ -+ txbuf[0] = (msg->request << 4) | ((msg->address >> 16) & 0xf); -+ txbuf[1] = (msg->address >> 8) & 0xff; -+ txbuf[2] = msg->address & 0xff; -+ txbuf[3] = msg->size - 1; -+} -+ -+static ssize_t -+intel_dp_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) -+{ -+ struct intel_dp *intel_dp = container_of(aux, struct intel_dp, aux); -+ u8 txbuf[20], rxbuf[20]; -+ size_t txsize, rxsize; -+ int ret; -+ -+ intel_dp_aux_header(txbuf, msg); -+ -+ switch (msg->request & ~DP_AUX_I2C_MOT) { -+ case DP_AUX_NATIVE_WRITE: -+ case DP_AUX_I2C_WRITE: -+ case DP_AUX_I2C_WRITE_STATUS_UPDATE: -+ txsize = msg->size ? HEADER_SIZE + msg->size : BARE_ADDRESS_SIZE; -+ rxsize = 2; /* 0 or 1 data bytes */ -+ -+ if (WARN_ON(txsize > 20)) -+ return -E2BIG; -+ -+ WARN_ON(!msg->buffer != !msg->size); -+ -+ if (msg->buffer) -+ memcpy(txbuf + HEADER_SIZE, msg->buffer, msg->size); -+ -+ ret = intel_dp_aux_xfer(intel_dp, txbuf, txsize, -+ rxbuf, rxsize, 0); -+ if (ret > 0) { -+ msg->reply = rxbuf[0] >> 4; -+ -+ if (ret > 1) { -+ /* Number of bytes written in a short write. */ -+ ret = clamp_t(int, rxbuf[1], 0, msg->size); -+ } else { -+ /* Return payload size. */ -+ ret = msg->size; -+ } -+ } -+ break; -+ -+ case DP_AUX_NATIVE_READ: -+ case DP_AUX_I2C_READ: -+ txsize = msg->size ? HEADER_SIZE : BARE_ADDRESS_SIZE; -+ rxsize = msg->size + 1; -+ -+ if (WARN_ON(rxsize > 20)) -+ return -E2BIG; -+ -+ ret = intel_dp_aux_xfer(intel_dp, txbuf, txsize, -+ rxbuf, rxsize, 0); -+ if (ret > 0) { -+ msg->reply = rxbuf[0] >> 4; -+ /* -+ * Assume happy day, and copy the data. The caller is -+ * expected to check msg->reply before touching it. -+ * -+ * Return payload size. -+ */ -+ ret--; -+ memcpy(msg->buffer, rxbuf + 1, ret); -+ } -+ break; -+ -+ default: -+ ret = -EINVAL; -+ break; -+ } -+ -+ return ret; -+} -+ -+ -+static i915_reg_t g4x_aux_ctl_reg(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ enum aux_ch aux_ch = dig_port->aux_ch; -+ -+ switch (aux_ch) { -+ case AUX_CH_B: -+ case AUX_CH_C: -+ case AUX_CH_D: -+ return DP_AUX_CH_CTL(aux_ch); -+ default: -+ MISSING_CASE(aux_ch); -+ return DP_AUX_CH_CTL(AUX_CH_B); -+ } -+} -+ -+static i915_reg_t g4x_aux_data_reg(struct intel_dp *intel_dp, int index) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ enum aux_ch aux_ch = dig_port->aux_ch; -+ -+ switch (aux_ch) { -+ case AUX_CH_B: -+ case AUX_CH_C: -+ case AUX_CH_D: -+ return DP_AUX_CH_DATA(aux_ch, index); -+ default: -+ MISSING_CASE(aux_ch); -+ return DP_AUX_CH_DATA(AUX_CH_B, index); -+ } -+} -+ -+static i915_reg_t ilk_aux_ctl_reg(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ enum aux_ch aux_ch = dig_port->aux_ch; -+ -+ switch (aux_ch) { -+ case AUX_CH_A: -+ return DP_AUX_CH_CTL(aux_ch); -+ case AUX_CH_B: -+ case AUX_CH_C: -+ case AUX_CH_D: -+ return PCH_DP_AUX_CH_CTL(aux_ch); -+ default: -+ MISSING_CASE(aux_ch); -+ return DP_AUX_CH_CTL(AUX_CH_A); -+ } -+} -+ -+static i915_reg_t ilk_aux_data_reg(struct intel_dp *intel_dp, int index) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ enum aux_ch aux_ch = dig_port->aux_ch; -+ -+ switch (aux_ch) { -+ case AUX_CH_A: -+ return DP_AUX_CH_DATA(aux_ch, index); -+ case AUX_CH_B: -+ case AUX_CH_C: -+ case AUX_CH_D: -+ return PCH_DP_AUX_CH_DATA(aux_ch, index); -+ default: -+ MISSING_CASE(aux_ch); -+ return DP_AUX_CH_DATA(AUX_CH_A, index); -+ } -+} -+ -+static i915_reg_t skl_aux_ctl_reg(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ enum aux_ch aux_ch = dig_port->aux_ch; -+ -+ switch (aux_ch) { -+ case AUX_CH_A: -+ case AUX_CH_B: -+ case AUX_CH_C: -+ case AUX_CH_D: -+ case AUX_CH_E: -+ case AUX_CH_F: -+ return DP_AUX_CH_CTL(aux_ch); -+ default: -+ MISSING_CASE(aux_ch); -+ return DP_AUX_CH_CTL(AUX_CH_A); -+ } -+} -+ -+static i915_reg_t skl_aux_data_reg(struct intel_dp *intel_dp, int index) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ enum aux_ch aux_ch = dig_port->aux_ch; -+ -+ switch (aux_ch) { -+ case AUX_CH_A: -+ case AUX_CH_B: -+ case AUX_CH_C: -+ case AUX_CH_D: -+ case AUX_CH_E: -+ case AUX_CH_F: -+ return DP_AUX_CH_DATA(aux_ch, index); -+ default: -+ MISSING_CASE(aux_ch); -+ return DP_AUX_CH_DATA(AUX_CH_A, index); -+ } -+} -+ -+static void -+intel_dp_aux_fini(struct intel_dp *intel_dp) -+{ -+ kfree(intel_dp->aux.name); -+} -+ -+static void -+intel_dp_aux_init(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ struct intel_encoder *encoder = &dig_port->base; -+ -+ if (INTEL_GEN(dev_priv) >= 9) { -+ intel_dp->aux_ch_ctl_reg = skl_aux_ctl_reg; -+ intel_dp->aux_ch_data_reg = skl_aux_data_reg; -+ } else if (HAS_PCH_SPLIT(dev_priv)) { -+ intel_dp->aux_ch_ctl_reg = ilk_aux_ctl_reg; -+ intel_dp->aux_ch_data_reg = ilk_aux_data_reg; -+ } else { -+ intel_dp->aux_ch_ctl_reg = g4x_aux_ctl_reg; -+ intel_dp->aux_ch_data_reg = g4x_aux_data_reg; -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ intel_dp->get_aux_clock_divider = skl_get_aux_clock_divider; -+ else if (IS_BROADWELL(dev_priv) || IS_HASWELL(dev_priv)) -+ intel_dp->get_aux_clock_divider = hsw_get_aux_clock_divider; -+ else if (HAS_PCH_SPLIT(dev_priv)) -+ intel_dp->get_aux_clock_divider = ilk_get_aux_clock_divider; -+ else -+ intel_dp->get_aux_clock_divider = g4x_get_aux_clock_divider; -+ -+ if (INTEL_GEN(dev_priv) >= 9) -+ intel_dp->get_aux_send_ctl = skl_get_aux_send_ctl; -+ else -+ intel_dp->get_aux_send_ctl = g4x_get_aux_send_ctl; -+ -+ drm_dp_aux_init(&intel_dp->aux); -+ -+ /* Failure to allocate our preferred name is not critical */ -+ intel_dp->aux.name = kasprintf(GFP_KERNEL, "DPDDC-%c", -+ port_name(encoder->port)); -+ intel_dp->aux.transfer = intel_dp_aux_transfer; -+} -+ -+bool intel_dp_source_supports_hbr2(struct intel_dp *intel_dp) -+{ -+ int max_rate = intel_dp->source_rates[intel_dp->num_source_rates - 1]; -+ -+ return max_rate >= 540000; -+} -+ -+bool intel_dp_source_supports_hbr3(struct intel_dp *intel_dp) -+{ -+ int max_rate = intel_dp->source_rates[intel_dp->num_source_rates - 1]; -+ -+ return max_rate >= 810000; -+} -+ -+static void -+intel_dp_set_clock(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ const struct dp_link_dpll *divisor = NULL; -+ int i, count = 0; -+ -+ if (IS_G4X(dev_priv)) { -+ divisor = g4x_dpll; -+ count = ARRAY_SIZE(g4x_dpll); -+ } else if (HAS_PCH_SPLIT(dev_priv)) { -+ divisor = pch_dpll; -+ count = ARRAY_SIZE(pch_dpll); -+ } else if (IS_CHERRYVIEW(dev_priv)) { -+ divisor = chv_dpll; -+ count = ARRAY_SIZE(chv_dpll); -+ } else if (IS_VALLEYVIEW(dev_priv)) { -+ divisor = vlv_dpll; -+ count = ARRAY_SIZE(vlv_dpll); -+ } -+ -+ if (divisor && count) { -+ for (i = 0; i < count; i++) { -+ if (pipe_config->port_clock == divisor[i].clock) { -+ pipe_config->dpll = divisor[i].dpll; -+ pipe_config->clock_set = true; -+ break; -+ } -+ } -+ } -+} -+ -+static void snprintf_int_array(char *str, size_t len, -+ const int *array, int nelem) -+{ -+ int i; -+ -+ str[0] = '\0'; -+ -+ for (i = 0; i < nelem; i++) { -+ int r = snprintf(str, len, "%s%d", i ? ", " : "", array[i]); -+ if (r >= len) -+ return; -+ str += r; -+ len -= r; -+ } -+} -+ -+static void intel_dp_print_rates(struct intel_dp *intel_dp) -+{ -+ char str[128]; /* FIXME: too big for stack? */ -+ -+ if ((drm_debug & DRM_UT_KMS) == 0) -+ return; -+ -+ snprintf_int_array(str, sizeof(str), -+ intel_dp->source_rates, intel_dp->num_source_rates); -+ DRM_DEBUG_KMS("source rates: %s\n", str); -+ -+ snprintf_int_array(str, sizeof(str), -+ intel_dp->sink_rates, intel_dp->num_sink_rates); -+ DRM_DEBUG_KMS("sink rates: %s\n", str); -+ -+ snprintf_int_array(str, sizeof(str), -+ intel_dp->common_rates, intel_dp->num_common_rates); -+ DRM_DEBUG_KMS("common rates: %s\n", str); -+} -+ -+int -+intel_dp_max_link_rate(struct intel_dp *intel_dp) -+{ -+ int len; -+ -+ len = intel_dp_common_len_rate_limit(intel_dp, intel_dp->max_link_rate); -+ if (WARN_ON(len <= 0)) -+ return 162000; -+ -+ return intel_dp->common_rates[len - 1]; -+} -+ -+int intel_dp_rate_select(struct intel_dp *intel_dp, int rate) -+{ -+ int i = intel_dp_rate_index(intel_dp->sink_rates, -+ intel_dp->num_sink_rates, rate); -+ -+ if (WARN_ON(i < 0)) -+ i = 0; -+ -+ return i; -+} -+ -+void intel_dp_compute_rate(struct intel_dp *intel_dp, int port_clock, -+ u8 *link_bw, u8 *rate_select) -+{ -+ /* eDP 1.4 rate select method. */ -+ if (intel_dp->use_rate_select) { -+ *link_bw = 0; -+ *rate_select = -+ intel_dp_rate_select(intel_dp, port_clock); -+ } else { -+ *link_bw = drm_dp_link_rate_to_bw_code(port_clock); -+ *rate_select = 0; -+ } -+} -+ -+static bool intel_dp_source_supports_fec(struct intel_dp *intel_dp, -+ const struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ -+ return INTEL_GEN(dev_priv) >= 11 && -+ pipe_config->cpu_transcoder != TRANSCODER_A; -+} -+ -+static bool intel_dp_supports_fec(struct intel_dp *intel_dp, -+ const struct intel_crtc_state *pipe_config) -+{ -+ return intel_dp_source_supports_fec(intel_dp, pipe_config) && -+ drm_dp_sink_supports_fec(intel_dp->fec_capable); -+} -+ -+static bool intel_dp_source_supports_dsc(struct intel_dp *intel_dp, -+ const struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ -+ return INTEL_GEN(dev_priv) >= 10 && -+ pipe_config->cpu_transcoder != TRANSCODER_A; -+} -+ -+static bool intel_dp_supports_dsc(struct intel_dp *intel_dp, -+ const struct intel_crtc_state *pipe_config) -+{ -+ if (!intel_dp_is_edp(intel_dp) && !pipe_config->fec_enable) -+ return false; -+ -+ return intel_dp_source_supports_dsc(intel_dp, pipe_config) && -+ drm_dp_sink_supports_dsc(intel_dp->dsc_dpcd); -+} -+ -+static int intel_dp_compute_bpp(struct intel_dp *intel_dp, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_connector *intel_connector = intel_dp->attached_connector; -+ int bpp, bpc; -+ -+ bpp = pipe_config->pipe_bpp; -+ bpc = drm_dp_downstream_max_bpc(intel_dp->dpcd, intel_dp->downstream_ports); -+ -+ if (bpc > 0) -+ bpp = min(bpp, 3*bpc); -+ -+ if (intel_dp_is_edp(intel_dp)) { -+ /* Get bpp from vbt only for panels that dont have bpp in edid */ -+ if (intel_connector->base.display_info.bpc == 0 && -+ dev_priv->vbt.edp.bpp && dev_priv->vbt.edp.bpp < bpp) { -+ DRM_DEBUG_KMS("clamping bpp for eDP panel to BIOS-provided %i\n", -+ dev_priv->vbt.edp.bpp); -+ bpp = dev_priv->vbt.edp.bpp; -+ } -+ } -+ -+ return bpp; -+} -+ -+/* Adjust link config limits based on compliance test requests. */ -+void -+intel_dp_adjust_compliance_config(struct intel_dp *intel_dp, -+ struct intel_crtc_state *pipe_config, -+ struct link_config_limits *limits) -+{ -+ /* For DP Compliance we override the computed bpp for the pipe */ -+ if (intel_dp->compliance.test_data.bpc != 0) { -+ int bpp = 3 * intel_dp->compliance.test_data.bpc; -+ -+ limits->min_bpp = limits->max_bpp = bpp; -+ pipe_config->dither_force_disable = bpp == 6 * 3; -+ -+ DRM_DEBUG_KMS("Setting pipe_bpp to %d\n", bpp); -+ } -+ -+ /* Use values requested by Compliance Test Request */ -+ if (intel_dp->compliance.test_type == DP_TEST_LINK_TRAINING) { -+ int index; -+ -+ /* Validate the compliance test data since max values -+ * might have changed due to link train fallback. -+ */ -+ if (intel_dp_link_params_valid(intel_dp, intel_dp->compliance.test_link_rate, -+ intel_dp->compliance.test_lane_count)) { -+ index = intel_dp_rate_index(intel_dp->common_rates, -+ intel_dp->num_common_rates, -+ intel_dp->compliance.test_link_rate); -+ if (index >= 0) -+ limits->min_clock = limits->max_clock = index; -+ limits->min_lane_count = limits->max_lane_count = -+ intel_dp->compliance.test_lane_count; -+ } -+ } -+} -+ -+/* Optimize link config in order: max bpp, min clock, min lanes */ -+static int -+intel_dp_compute_link_config_wide(struct intel_dp *intel_dp, -+ struct intel_crtc_state *pipe_config, -+ const struct link_config_limits *limits) -+{ -+ struct drm_display_mode *adjusted_mode = &pipe_config->base.adjusted_mode; -+ int bpp, clock, lane_count; -+ int mode_rate, link_clock, link_avail; -+ -+ for (bpp = limits->max_bpp; bpp >= limits->min_bpp; bpp -= 2 * 3) { -+ mode_rate = intel_dp_link_required(adjusted_mode->crtc_clock, -+ bpp); -+ -+ for (clock = limits->min_clock; clock <= limits->max_clock; clock++) { -+ for (lane_count = limits->min_lane_count; -+ lane_count <= limits->max_lane_count; -+ lane_count <<= 1) { -+ link_clock = intel_dp->common_rates[clock]; -+ link_avail = intel_dp_max_data_rate(link_clock, -+ lane_count); -+ -+ if (mode_rate <= link_avail) { -+ pipe_config->lane_count = lane_count; -+ pipe_config->pipe_bpp = bpp; -+ pipe_config->port_clock = link_clock; -+ -+ return 0; -+ } -+ } -+ } -+ } -+ -+ return -EINVAL; -+} -+ -+static int intel_dp_dsc_compute_bpp(struct intel_dp *intel_dp, u8 dsc_max_bpc) -+{ -+ int i, num_bpc; -+ u8 dsc_bpc[3] = {0}; -+ -+ num_bpc = drm_dp_dsc_sink_supported_input_bpcs(intel_dp->dsc_dpcd, -+ dsc_bpc); -+ for (i = 0; i < num_bpc; i++) { -+ if (dsc_max_bpc >= dsc_bpc[i]) -+ return dsc_bpc[i] * 3; -+ } -+ -+ return 0; -+} -+ -+static int intel_dp_dsc_compute_config(struct intel_dp *intel_dp, -+ struct intel_crtc_state *pipe_config, -+ struct drm_connector_state *conn_state, -+ struct link_config_limits *limits) -+{ -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); -+ struct drm_display_mode *adjusted_mode = &pipe_config->base.adjusted_mode; -+ u8 dsc_max_bpc; -+ int pipe_bpp; -+ int ret; -+ -+ pipe_config->fec_enable = !intel_dp_is_edp(intel_dp) && -+ intel_dp_supports_fec(intel_dp, pipe_config); -+ -+ if (!intel_dp_supports_dsc(intel_dp, pipe_config)) -+ return -EINVAL; -+ -+ dsc_max_bpc = min_t(u8, DP_DSC_MAX_SUPPORTED_BPC, -+ conn_state->max_requested_bpc); -+ -+ pipe_bpp = intel_dp_dsc_compute_bpp(intel_dp, dsc_max_bpc); -+ if (pipe_bpp < DP_DSC_MIN_SUPPORTED_BPC * 3) { -+ DRM_DEBUG_KMS("No DSC support for less than 8bpc\n"); -+ return -EINVAL; -+ } -+ -+ /* -+ * For now enable DSC for max bpp, max link rate, max lane count. -+ * Optimize this later for the minimum possible link rate/lane count -+ * with DSC enabled for the requested mode. -+ */ -+ pipe_config->pipe_bpp = pipe_bpp; -+ pipe_config->port_clock = intel_dp->common_rates[limits->max_clock]; -+ pipe_config->lane_count = limits->max_lane_count; -+ -+ if (intel_dp_is_edp(intel_dp)) { -+ pipe_config->dsc_params.compressed_bpp = -+ min_t(u16, drm_edp_dsc_sink_output_bpp(intel_dp->dsc_dpcd) >> 4, -+ pipe_config->pipe_bpp); -+ pipe_config->dsc_params.slice_count = -+ drm_dp_dsc_sink_max_slice_count(intel_dp->dsc_dpcd, -+ true); -+ } else { -+ u16 dsc_max_output_bpp; -+ u8 dsc_dp_slice_count; -+ -+ dsc_max_output_bpp = -+ intel_dp_dsc_get_output_bpp(pipe_config->port_clock, -+ pipe_config->lane_count, -+ adjusted_mode->crtc_clock, -+ adjusted_mode->crtc_hdisplay); -+ dsc_dp_slice_count = -+ intel_dp_dsc_get_slice_count(intel_dp, -+ adjusted_mode->crtc_clock, -+ adjusted_mode->crtc_hdisplay); -+ if (!dsc_max_output_bpp || !dsc_dp_slice_count) { -+ DRM_DEBUG_KMS("Compressed BPP/Slice Count not supported\n"); -+ return -EINVAL; -+ } -+ pipe_config->dsc_params.compressed_bpp = min_t(u16, -+ dsc_max_output_bpp >> 4, -+ pipe_config->pipe_bpp); -+ pipe_config->dsc_params.slice_count = dsc_dp_slice_count; -+ } -+ /* -+ * VDSC engine operates at 1 Pixel per clock, so if peak pixel rate -+ * is greater than the maximum Cdclock and if slice count is even -+ * then we need to use 2 VDSC instances. -+ */ -+ if (adjusted_mode->crtc_clock > dev_priv->max_cdclk_freq) { -+ if (pipe_config->dsc_params.slice_count > 1) { -+ pipe_config->dsc_params.dsc_split = true; -+ } else { -+ DRM_DEBUG_KMS("Cannot split stream to use 2 VDSC instances\n"); -+ return -EINVAL; -+ } -+ } -+ -+ ret = intel_dp_compute_dsc_params(intel_dp, pipe_config); -+ if (ret < 0) { -+ DRM_DEBUG_KMS("Cannot compute valid DSC parameters for Input Bpp = %d " -+ "Compressed BPP = %d\n", -+ pipe_config->pipe_bpp, -+ pipe_config->dsc_params.compressed_bpp); -+ return ret; -+ } -+ -+ pipe_config->dsc_params.compression_enable = true; -+ DRM_DEBUG_KMS("DP DSC computed with Input Bpp = %d " -+ "Compressed Bpp = %d Slice Count = %d\n", -+ pipe_config->pipe_bpp, -+ pipe_config->dsc_params.compressed_bpp, -+ pipe_config->dsc_params.slice_count); -+ -+ return 0; -+} -+ -+int intel_dp_min_bpp(const struct intel_crtc_state *crtc_state) -+{ -+ if (crtc_state->output_format == INTEL_OUTPUT_FORMAT_RGB) -+ return 6 * 3; -+ else -+ return 8 * 3; -+} -+ -+static int -+intel_dp_compute_link_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config, -+ struct drm_connector_state *conn_state) -+{ -+ struct drm_display_mode *adjusted_mode = &pipe_config->base.adjusted_mode; -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ struct link_config_limits limits; -+ int common_len; -+ int ret; -+ -+ common_len = intel_dp_common_len_rate_limit(intel_dp, -+ intel_dp->max_link_rate); -+ -+ /* No common link rates between source and sink */ -+ WARN_ON(common_len <= 0); -+ -+ limits.min_clock = 0; -+ limits.max_clock = common_len - 1; -+ -+ limits.min_lane_count = 1; -+ limits.max_lane_count = intel_dp_max_lane_count(intel_dp); -+ -+ limits.min_bpp = intel_dp_min_bpp(pipe_config); -+ limits.max_bpp = intel_dp_compute_bpp(intel_dp, pipe_config); -+ -+ if (intel_dp_is_edp(intel_dp)) { -+ /* -+ * Use the maximum clock and number of lanes the eDP panel -+ * advertizes being capable of. The panels are generally -+ * designed to support only a single clock and lane -+ * configuration, and typically these values correspond to the -+ * native resolution of the panel. -+ */ -+ limits.min_lane_count = limits.max_lane_count; -+ limits.min_clock = limits.max_clock; -+ } -+ -+ intel_dp_adjust_compliance_config(intel_dp, pipe_config, &limits); -+ -+ DRM_DEBUG_KMS("DP link computation with max lane count %i " -+ "max rate %d max bpp %d pixel clock %iKHz\n", -+ limits.max_lane_count, -+ intel_dp->common_rates[limits.max_clock], -+ limits.max_bpp, adjusted_mode->crtc_clock); -+ -+ /* -+ * Optimize for slow and wide. This is the place to add alternative -+ * optimization policy. -+ */ -+ ret = intel_dp_compute_link_config_wide(intel_dp, pipe_config, &limits); -+ -+ /* enable compression if the mode doesn't fit available BW */ -+ DRM_DEBUG_KMS("Force DSC en = %d\n", intel_dp->force_dsc_en); -+ if (ret || intel_dp->force_dsc_en) { -+ ret = intel_dp_dsc_compute_config(intel_dp, pipe_config, -+ conn_state, &limits); -+ if (ret < 0) -+ return ret; -+ } -+ -+ if (pipe_config->dsc_params.compression_enable) { -+ DRM_DEBUG_KMS("DP lane count %d clock %d Input bpp %d Compressed bpp %d\n", -+ pipe_config->lane_count, pipe_config->port_clock, -+ pipe_config->pipe_bpp, -+ pipe_config->dsc_params.compressed_bpp); -+ -+ DRM_DEBUG_KMS("DP link rate required %i available %i\n", -+ intel_dp_link_required(adjusted_mode->crtc_clock, -+ pipe_config->dsc_params.compressed_bpp), -+ intel_dp_max_data_rate(pipe_config->port_clock, -+ pipe_config->lane_count)); -+ } else { -+ DRM_DEBUG_KMS("DP lane count %d clock %d bpp %d\n", -+ pipe_config->lane_count, pipe_config->port_clock, -+ pipe_config->pipe_bpp); -+ -+ DRM_DEBUG_KMS("DP link rate required %i available %i\n", -+ intel_dp_link_required(adjusted_mode->crtc_clock, -+ pipe_config->pipe_bpp), -+ intel_dp_max_data_rate(pipe_config->port_clock, -+ pipe_config->lane_count)); -+ } -+ return 0; -+} -+ -+bool intel_dp_limited_color_range(const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ const struct intel_digital_connector_state *intel_conn_state = -+ to_intel_digital_connector_state(conn_state); -+ const struct drm_display_mode *adjusted_mode = -+ &crtc_state->base.adjusted_mode; -+ -+ if (intel_conn_state->broadcast_rgb == INTEL_BROADCAST_RGB_AUTO) { -+ /* -+ * See: -+ * CEA-861-E - 5.1 Default Encoding Parameters -+ * VESA DisplayPort Ver.1.2a - 5.1.1.1 Video Colorimetry -+ */ -+ return crtc_state->pipe_bpp != 18 && -+ drm_default_rgb_quant_range(adjusted_mode) == -+ HDMI_QUANTIZATION_RANGE_LIMITED; -+ } else { -+ return intel_conn_state->broadcast_rgb == -+ INTEL_BROADCAST_RGB_LIMITED; -+ } -+} -+ -+int -+intel_dp_compute_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config, -+ struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct drm_display_mode *adjusted_mode = &pipe_config->base.adjusted_mode; -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ struct intel_lspcon *lspcon = enc_to_intel_lspcon(&encoder->base); -+ enum port port = encoder->port; -+ struct intel_crtc *intel_crtc = to_intel_crtc(pipe_config->base.crtc); -+ struct intel_connector *intel_connector = intel_dp->attached_connector; -+ struct intel_digital_connector_state *intel_conn_state = -+ to_intel_digital_connector_state(conn_state); -+ bool constant_n = drm_dp_has_quirk(&intel_dp->desc, -+ DP_DPCD_QUIRK_CONSTANT_N); -+ int ret, output_bpp; -+ -+ if (HAS_PCH_SPLIT(dev_priv) && !HAS_DDI(dev_priv) && port != PORT_A) -+ pipe_config->has_pch_encoder = true; -+ -+ pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; -+ if (lspcon->active) -+ lspcon_ycbcr420_config(&intel_connector->base, pipe_config); -+ -+ pipe_config->has_drrs = false; -+ if (IS_G4X(dev_priv) || port == PORT_A) -+ pipe_config->has_audio = false; -+ else if (intel_conn_state->force_audio == HDMI_AUDIO_AUTO) -+ pipe_config->has_audio = intel_dp->has_audio; -+ else -+ pipe_config->has_audio = intel_conn_state->force_audio == HDMI_AUDIO_ON; -+ -+ if (intel_dp_is_edp(intel_dp) && intel_connector->panel.fixed_mode) { -+ intel_fixed_panel_mode(intel_connector->panel.fixed_mode, -+ adjusted_mode); -+ -+ if (INTEL_GEN(dev_priv) >= 9) { -+ ret = skl_update_scaler_crtc(pipe_config); -+ if (ret) -+ return ret; -+ } -+ -+ if (HAS_GMCH(dev_priv)) -+ intel_gmch_panel_fitting(intel_crtc, pipe_config, -+ conn_state->scaling_mode); -+ else -+ intel_pch_panel_fitting(intel_crtc, pipe_config, -+ conn_state->scaling_mode); -+ } -+ -+ if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN) -+ return -EINVAL; -+ -+ if (HAS_GMCH(dev_priv) && -+ adjusted_mode->flags & DRM_MODE_FLAG_INTERLACE) -+ return -EINVAL; -+ -+ if (adjusted_mode->flags & DRM_MODE_FLAG_DBLCLK) -+ return -EINVAL; -+ -+ ret = intel_dp_compute_link_config(encoder, pipe_config, conn_state); -+ if (ret < 0) -+ return ret; -+ -+ pipe_config->limited_color_range = -+ intel_dp_limited_color_range(pipe_config, conn_state); -+ -+ if (pipe_config->dsc_params.compression_enable) -+ output_bpp = pipe_config->dsc_params.compressed_bpp; -+ else -+ output_bpp = pipe_config->pipe_bpp; -+ -+ intel_link_compute_m_n(output_bpp, -+ pipe_config->lane_count, -+ adjusted_mode->crtc_clock, -+ pipe_config->port_clock, -+ &pipe_config->dp_m_n, -+ constant_n); -+ -+ if (intel_connector->panel.downclock_mode != NULL && -+ dev_priv->drrs.type == SEAMLESS_DRRS_SUPPORT) { -+ pipe_config->has_drrs = true; -+ intel_link_compute_m_n(output_bpp, -+ pipe_config->lane_count, -+ intel_connector->panel.downclock_mode->clock, -+ pipe_config->port_clock, -+ &pipe_config->dp_m2_n2, -+ constant_n); -+ } -+ -+ if (!HAS_DDI(dev_priv)) -+ intel_dp_set_clock(encoder, pipe_config); -+ -+ intel_psr_compute_config(intel_dp, pipe_config); -+ -+ return 0; -+} -+ -+void intel_dp_set_link_params(struct intel_dp *intel_dp, -+ int link_rate, u8 lane_count, -+ bool link_mst) -+{ -+ intel_dp->link_trained = false; -+ intel_dp->link_rate = link_rate; -+ intel_dp->lane_count = lane_count; -+ intel_dp->link_mst = link_mst; -+} -+ -+static void intel_dp_prepare(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ enum port port = encoder->port; -+ struct intel_crtc *crtc = to_intel_crtc(pipe_config->base.crtc); -+ const struct drm_display_mode *adjusted_mode = &pipe_config->base.adjusted_mode; -+ -+ intel_dp_set_link_params(intel_dp, pipe_config->port_clock, -+ pipe_config->lane_count, -+ intel_crtc_has_type(pipe_config, -+ INTEL_OUTPUT_DP_MST)); -+ -+ /* -+ * There are four kinds of DP registers: -+ * -+ * IBX PCH -+ * SNB CPU -+ * IVB CPU -+ * CPT PCH -+ * -+ * IBX PCH and CPU are the same for almost everything, -+ * except that the CPU DP PLL is configured in this -+ * register -+ * -+ * CPT PCH is quite different, having many bits moved -+ * to the TRANS_DP_CTL register instead. That -+ * configuration happens (oddly) in ironlake_pch_enable -+ */ -+ -+ /* Preserve the BIOS-computed detected bit. This is -+ * supposed to be read-only. -+ */ -+ intel_dp->DP = I915_READ(intel_dp->output_reg) & DP_DETECTED; -+ -+ /* Handle DP bits in common between all three register formats */ -+ intel_dp->DP |= DP_VOLTAGE_0_4 | DP_PRE_EMPHASIS_0; -+ intel_dp->DP |= DP_PORT_WIDTH(pipe_config->lane_count); -+ -+ /* Split out the IBX/CPU vs CPT settings */ -+ -+ if (IS_IVYBRIDGE(dev_priv) && port == PORT_A) { -+ if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) -+ intel_dp->DP |= DP_SYNC_HS_HIGH; -+ if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) -+ intel_dp->DP |= DP_SYNC_VS_HIGH; -+ intel_dp->DP |= DP_LINK_TRAIN_OFF_CPT; -+ -+ if (drm_dp_enhanced_frame_cap(intel_dp->dpcd)) -+ intel_dp->DP |= DP_ENHANCED_FRAMING; -+ -+ intel_dp->DP |= DP_PIPE_SEL_IVB(crtc->pipe); -+ } else if (HAS_PCH_CPT(dev_priv) && port != PORT_A) { -+ u32 trans_dp; -+ -+ intel_dp->DP |= DP_LINK_TRAIN_OFF_CPT; -+ -+ trans_dp = I915_READ(TRANS_DP_CTL(crtc->pipe)); -+ if (drm_dp_enhanced_frame_cap(intel_dp->dpcd)) -+ trans_dp |= TRANS_DP_ENH_FRAMING; -+ else -+ trans_dp &= ~TRANS_DP_ENH_FRAMING; -+ I915_WRITE(TRANS_DP_CTL(crtc->pipe), trans_dp); -+ } else { -+ if (IS_G4X(dev_priv) && pipe_config->limited_color_range) -+ intel_dp->DP |= DP_COLOR_RANGE_16_235; -+ -+ if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) -+ intel_dp->DP |= DP_SYNC_HS_HIGH; -+ if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) -+ intel_dp->DP |= DP_SYNC_VS_HIGH; -+ intel_dp->DP |= DP_LINK_TRAIN_OFF; -+ -+ if (drm_dp_enhanced_frame_cap(intel_dp->dpcd)) -+ intel_dp->DP |= DP_ENHANCED_FRAMING; -+ -+ if (IS_CHERRYVIEW(dev_priv)) -+ intel_dp->DP |= DP_PIPE_SEL_CHV(crtc->pipe); -+ else -+ intel_dp->DP |= DP_PIPE_SEL(crtc->pipe); -+ } -+} -+ -+#define IDLE_ON_MASK (PP_ON | PP_SEQUENCE_MASK | 0 | PP_SEQUENCE_STATE_MASK) -+#define IDLE_ON_VALUE (PP_ON | PP_SEQUENCE_NONE | 0 | PP_SEQUENCE_STATE_ON_IDLE) -+ -+#define IDLE_OFF_MASK (PP_ON | PP_SEQUENCE_MASK | 0 | 0) -+#define IDLE_OFF_VALUE (0 | PP_SEQUENCE_NONE | 0 | 0) -+ -+#define IDLE_CYCLE_MASK (PP_ON | PP_SEQUENCE_MASK | PP_CYCLE_DELAY_ACTIVE | PP_SEQUENCE_STATE_MASK) -+#define IDLE_CYCLE_VALUE (0 | PP_SEQUENCE_NONE | 0 | PP_SEQUENCE_STATE_OFF_IDLE) -+ -+static void intel_pps_verify_state(struct intel_dp *intel_dp); -+ -+static void wait_panel_status(struct intel_dp *intel_dp, -+ u32 mask, -+ u32 value) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ i915_reg_t pp_stat_reg, pp_ctrl_reg; -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ intel_pps_verify_state(intel_dp); -+ -+ pp_stat_reg = _pp_stat_reg(intel_dp); -+ pp_ctrl_reg = _pp_ctrl_reg(intel_dp); -+ -+ DRM_DEBUG_KMS("mask %08x value %08x status %08x control %08x\n", -+ mask, value, -+ I915_READ(pp_stat_reg), -+ I915_READ(pp_ctrl_reg)); -+ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ pp_stat_reg, mask, value, -+ 5000)) -+ DRM_ERROR("Panel status timeout: status %08x control %08x\n", -+ I915_READ(pp_stat_reg), -+ I915_READ(pp_ctrl_reg)); -+ -+ DRM_DEBUG_KMS("Wait complete\n"); -+} -+ -+static void wait_panel_on(struct intel_dp *intel_dp) -+{ -+ DRM_DEBUG_KMS("Wait for panel power on\n"); -+ wait_panel_status(intel_dp, IDLE_ON_MASK, IDLE_ON_VALUE); -+} -+ -+static void wait_panel_off(struct intel_dp *intel_dp) -+{ -+ DRM_DEBUG_KMS("Wait for panel power off time\n"); -+ wait_panel_status(intel_dp, IDLE_OFF_MASK, IDLE_OFF_VALUE); -+} -+ -+static void wait_panel_power_cycle(struct intel_dp *intel_dp) -+{ -+ ktime_t panel_power_on_time; -+ s64 panel_power_off_duration; -+ -+ DRM_DEBUG_KMS("Wait for panel power cycle\n"); -+ -+ /* take the difference of currrent time and panel power off time -+ * and then make panel wait for t11_t12 if needed. */ -+ panel_power_on_time = ktime_get_boottime(); -+ panel_power_off_duration = ktime_ms_delta(panel_power_on_time, intel_dp->panel_power_off_time); -+ -+ /* When we disable the VDD override bit last we have to do the manual -+ * wait. */ -+ if (panel_power_off_duration < (s64)intel_dp->panel_power_cycle_delay) -+ wait_remaining_ms_from_jiffies(jiffies, -+ intel_dp->panel_power_cycle_delay - panel_power_off_duration); -+ -+ wait_panel_status(intel_dp, IDLE_CYCLE_MASK, IDLE_CYCLE_VALUE); -+} -+ -+static void wait_backlight_on(struct intel_dp *intel_dp) -+{ -+ wait_remaining_ms_from_jiffies(intel_dp->last_power_on, -+ intel_dp->backlight_on_delay); -+} -+ -+static void edp_wait_backlight_off(struct intel_dp *intel_dp) -+{ -+ wait_remaining_ms_from_jiffies(intel_dp->last_backlight_off, -+ intel_dp->backlight_off_delay); -+} -+ -+/* Read the current pp_control value, unlocking the register if it -+ * is locked -+ */ -+ -+static u32 ironlake_get_pp_control(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ u32 control; -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ control = I915_READ(_pp_ctrl_reg(intel_dp)); -+ if (WARN_ON(!HAS_DDI(dev_priv) && -+ (control & PANEL_UNLOCK_MASK) != PANEL_UNLOCK_REGS)) { -+ control &= ~PANEL_UNLOCK_MASK; -+ control |= PANEL_UNLOCK_REGS; -+ } -+ return control; -+} -+ -+/* -+ * Must be paired with edp_panel_vdd_off(). -+ * Must hold pps_mutex around the whole on/off sequence. -+ * Can be nested with intel_edp_panel_vdd_{on,off}() calls. -+ */ -+static bool edp_panel_vdd_on(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ u32 pp; -+ i915_reg_t pp_stat_reg, pp_ctrl_reg; -+ bool need_to_disable = !intel_dp->want_panel_vdd; -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ if (!intel_dp_is_edp(intel_dp)) -+ return false; -+ -+ cancel_delayed_work(&intel_dp->panel_vdd_work); -+ intel_dp->want_panel_vdd = true; -+ -+ if (edp_have_panel_vdd(intel_dp)) -+ return need_to_disable; -+ -+ intel_display_power_get(dev_priv, -+ intel_aux_power_domain(intel_dig_port)); -+ -+ DRM_DEBUG_KMS("Turning eDP port %c VDD on\n", -+ port_name(intel_dig_port->base.port)); -+ -+ if (!edp_have_panel_power(intel_dp)) -+ wait_panel_power_cycle(intel_dp); -+ -+ pp = ironlake_get_pp_control(intel_dp); -+ pp |= EDP_FORCE_VDD; -+ -+ pp_stat_reg = _pp_stat_reg(intel_dp); -+ pp_ctrl_reg = _pp_ctrl_reg(intel_dp); -+ -+ I915_WRITE(pp_ctrl_reg, pp); -+ POSTING_READ(pp_ctrl_reg); -+ DRM_DEBUG_KMS("PP_STATUS: 0x%08x PP_CONTROL: 0x%08x\n", -+ I915_READ(pp_stat_reg), I915_READ(pp_ctrl_reg)); -+ /* -+ * If the panel wasn't on, delay before accessing aux channel -+ */ -+ if (!edp_have_panel_power(intel_dp)) { -+ DRM_DEBUG_KMS("eDP port %c panel power wasn't enabled\n", -+ port_name(intel_dig_port->base.port)); -+ msleep(intel_dp->panel_power_up_delay); -+ } -+ -+ return need_to_disable; -+} -+ -+/* -+ * Must be paired with intel_edp_panel_vdd_off() or -+ * intel_edp_panel_off(). -+ * Nested calls to these functions are not allowed since -+ * we drop the lock. Caller must use some higher level -+ * locking to prevent nested calls from other threads. -+ */ -+void intel_edp_panel_vdd_on(struct intel_dp *intel_dp) -+{ -+ intel_wakeref_t wakeref; -+ bool vdd; -+ -+ if (!intel_dp_is_edp(intel_dp)) -+ return; -+ -+ vdd = false; -+ with_pps_lock(intel_dp, wakeref) -+ vdd = edp_panel_vdd_on(intel_dp); -+ I915_STATE_WARN(!vdd, "eDP port %c VDD already requested on\n", -+ port_name(dp_to_dig_port(intel_dp)->base.port)); -+} -+ -+static void edp_panel_vdd_off_sync(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *intel_dig_port = -+ dp_to_dig_port(intel_dp); -+ u32 pp; -+ i915_reg_t pp_stat_reg, pp_ctrl_reg; -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ WARN_ON(intel_dp->want_panel_vdd); -+ -+ if (!edp_have_panel_vdd(intel_dp)) -+ return; -+ -+ DRM_DEBUG_KMS("Turning eDP port %c VDD off\n", -+ port_name(intel_dig_port->base.port)); -+ -+ pp = ironlake_get_pp_control(intel_dp); -+ pp &= ~EDP_FORCE_VDD; -+ -+ pp_ctrl_reg = _pp_ctrl_reg(intel_dp); -+ pp_stat_reg = _pp_stat_reg(intel_dp); -+ -+ I915_WRITE(pp_ctrl_reg, pp); -+ POSTING_READ(pp_ctrl_reg); -+ -+ /* Make sure sequencer is idle before allowing subsequent activity */ -+ DRM_DEBUG_KMS("PP_STATUS: 0x%08x PP_CONTROL: 0x%08x\n", -+ I915_READ(pp_stat_reg), I915_READ(pp_ctrl_reg)); -+ -+ if ((pp & PANEL_POWER_ON) == 0) -+ intel_dp->panel_power_off_time = ktime_get_boottime(); -+ -+ intel_display_power_put_unchecked(dev_priv, -+ intel_aux_power_domain(intel_dig_port)); -+} -+ -+static void edp_panel_vdd_work(struct work_struct *__work) -+{ -+ struct intel_dp *intel_dp = -+ container_of(to_delayed_work(__work), -+ struct intel_dp, panel_vdd_work); -+ intel_wakeref_t wakeref; -+ -+ with_pps_lock(intel_dp, wakeref) { -+ if (!intel_dp->want_panel_vdd) -+ edp_panel_vdd_off_sync(intel_dp); -+ } -+} -+ -+static void edp_panel_vdd_schedule_off(struct intel_dp *intel_dp) -+{ -+ unsigned long delay; -+ -+ /* -+ * Queue the timer to fire a long time from now (relative to the power -+ * down delay) to keep the panel power up across a sequence of -+ * operations. -+ */ -+ delay = msecs_to_jiffies(intel_dp->panel_power_cycle_delay * 5); -+ schedule_delayed_work(&intel_dp->panel_vdd_work, delay); -+} -+ -+/* -+ * Must be paired with edp_panel_vdd_on(). -+ * Must hold pps_mutex around the whole on/off sequence. -+ * Can be nested with intel_edp_panel_vdd_{on,off}() calls. -+ */ -+static void edp_panel_vdd_off(struct intel_dp *intel_dp, bool sync) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ if (!intel_dp_is_edp(intel_dp)) -+ return; -+ -+ I915_STATE_WARN(!intel_dp->want_panel_vdd, "eDP port %c VDD not forced on", -+ port_name(dp_to_dig_port(intel_dp)->base.port)); -+ -+ intel_dp->want_panel_vdd = false; -+ -+ if (sync) -+ edp_panel_vdd_off_sync(intel_dp); -+ else -+ edp_panel_vdd_schedule_off(intel_dp); -+} -+ -+static void edp_panel_on(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ u32 pp; -+ i915_reg_t pp_ctrl_reg; -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ if (!intel_dp_is_edp(intel_dp)) -+ return; -+ -+ DRM_DEBUG_KMS("Turn eDP port %c panel power on\n", -+ port_name(dp_to_dig_port(intel_dp)->base.port)); -+ -+ if (WARN(edp_have_panel_power(intel_dp), -+ "eDP port %c panel power already on\n", -+ port_name(dp_to_dig_port(intel_dp)->base.port))) -+ return; -+ -+ wait_panel_power_cycle(intel_dp); -+ -+ pp_ctrl_reg = _pp_ctrl_reg(intel_dp); -+ pp = ironlake_get_pp_control(intel_dp); -+ if (IS_GEN(dev_priv, 5)) { -+ /* ILK workaround: disable reset around power sequence */ -+ pp &= ~PANEL_POWER_RESET; -+ I915_WRITE(pp_ctrl_reg, pp); -+ POSTING_READ(pp_ctrl_reg); -+ } -+ -+ pp |= PANEL_POWER_ON; -+ if (!IS_GEN(dev_priv, 5)) -+ pp |= PANEL_POWER_RESET; -+ -+ I915_WRITE(pp_ctrl_reg, pp); -+ POSTING_READ(pp_ctrl_reg); -+ -+ wait_panel_on(intel_dp); -+ intel_dp->last_power_on = jiffies; -+ -+ if (IS_GEN(dev_priv, 5)) { -+ pp |= PANEL_POWER_RESET; /* restore panel reset bit */ -+ I915_WRITE(pp_ctrl_reg, pp); -+ POSTING_READ(pp_ctrl_reg); -+ } -+} -+ -+void intel_edp_panel_on(struct intel_dp *intel_dp) -+{ -+ intel_wakeref_t wakeref; -+ -+ if (!intel_dp_is_edp(intel_dp)) -+ return; -+ -+ with_pps_lock(intel_dp, wakeref) -+ edp_panel_on(intel_dp); -+} -+ -+ -+static void edp_panel_off(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ u32 pp; -+ i915_reg_t pp_ctrl_reg; -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ if (!intel_dp_is_edp(intel_dp)) -+ return; -+ -+ DRM_DEBUG_KMS("Turn eDP port %c panel power off\n", -+ port_name(dig_port->base.port)); -+ -+ WARN(!intel_dp->want_panel_vdd, "Need eDP port %c VDD to turn off panel\n", -+ port_name(dig_port->base.port)); -+ -+ pp = ironlake_get_pp_control(intel_dp); -+ /* We need to switch off panel power _and_ force vdd, for otherwise some -+ * panels get very unhappy and cease to work. */ -+ pp &= ~(PANEL_POWER_ON | PANEL_POWER_RESET | EDP_FORCE_VDD | -+ EDP_BLC_ENABLE); -+ -+ pp_ctrl_reg = _pp_ctrl_reg(intel_dp); -+ -+ intel_dp->want_panel_vdd = false; -+ -+ I915_WRITE(pp_ctrl_reg, pp); -+ POSTING_READ(pp_ctrl_reg); -+ -+ wait_panel_off(intel_dp); -+ intel_dp->panel_power_off_time = ktime_get_boottime(); -+ -+ /* We got a reference when we enabled the VDD. */ -+ intel_display_power_put_unchecked(dev_priv, intel_aux_power_domain(dig_port)); -+} -+ -+void intel_edp_panel_off(struct intel_dp *intel_dp) -+{ -+ intel_wakeref_t wakeref; -+ -+ if (!intel_dp_is_edp(intel_dp)) -+ return; -+ -+ with_pps_lock(intel_dp, wakeref) -+ edp_panel_off(intel_dp); -+} -+ -+/* Enable backlight in the panel power control. */ -+static void _intel_edp_backlight_on(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ intel_wakeref_t wakeref; -+ -+ /* -+ * If we enable the backlight right away following a panel power -+ * on, we may see slight flicker as the panel syncs with the eDP -+ * link. So delay a bit to make sure the image is solid before -+ * allowing it to appear. -+ */ -+ wait_backlight_on(intel_dp); -+ -+ with_pps_lock(intel_dp, wakeref) { -+ i915_reg_t pp_ctrl_reg = _pp_ctrl_reg(intel_dp); -+ u32 pp; -+ -+ pp = ironlake_get_pp_control(intel_dp); -+ pp |= EDP_BLC_ENABLE; -+ -+ I915_WRITE(pp_ctrl_reg, pp); -+ POSTING_READ(pp_ctrl_reg); -+ } -+} -+ -+/* Enable backlight PWM and backlight PP control. */ -+void intel_edp_backlight_on(const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(conn_state->best_encoder); -+ -+ if (!intel_dp_is_edp(intel_dp)) -+ return; -+ -+ DRM_DEBUG_KMS("\n"); -+ -+ intel_panel_enable_backlight(crtc_state, conn_state); -+ _intel_edp_backlight_on(intel_dp); -+} -+ -+/* Disable backlight in the panel power control. */ -+static void _intel_edp_backlight_off(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ intel_wakeref_t wakeref; -+ -+ if (!intel_dp_is_edp(intel_dp)) -+ return; -+ -+ with_pps_lock(intel_dp, wakeref) { -+ i915_reg_t pp_ctrl_reg = _pp_ctrl_reg(intel_dp); -+ u32 pp; -+ -+ pp = ironlake_get_pp_control(intel_dp); -+ pp &= ~EDP_BLC_ENABLE; -+ -+ I915_WRITE(pp_ctrl_reg, pp); -+ POSTING_READ(pp_ctrl_reg); -+ } -+ -+ intel_dp->last_backlight_off = jiffies; -+ edp_wait_backlight_off(intel_dp); -+} -+ -+/* Disable backlight PP control and backlight PWM. */ -+void intel_edp_backlight_off(const struct drm_connector_state *old_conn_state) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(old_conn_state->best_encoder); -+ -+ if (!intel_dp_is_edp(intel_dp)) -+ return; -+ -+ DRM_DEBUG_KMS("\n"); -+ -+ _intel_edp_backlight_off(intel_dp); -+ intel_panel_disable_backlight(old_conn_state); -+} -+ -+/* -+ * Hook for controlling the panel power control backlight through the bl_power -+ * sysfs attribute. Take care to handle multiple calls. -+ */ -+static void intel_edp_backlight_power(struct intel_connector *connector, -+ bool enable) -+{ -+ struct intel_dp *intel_dp = intel_attached_dp(&connector->base); -+ intel_wakeref_t wakeref; -+ bool is_enabled; -+ -+ is_enabled = false; -+ with_pps_lock(intel_dp, wakeref) -+ is_enabled = ironlake_get_pp_control(intel_dp) & EDP_BLC_ENABLE; -+ if (is_enabled == enable) -+ return; -+ -+ DRM_DEBUG_KMS("panel power control backlight %s\n", -+ enable ? "enable" : "disable"); -+ -+ if (enable) -+ _intel_edp_backlight_on(intel_dp); -+ else -+ _intel_edp_backlight_off(intel_dp); -+} -+ -+static void assert_dp_port(struct intel_dp *intel_dp, bool state) -+{ -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = to_i915(dig_port->base.base.dev); -+ bool cur_state = I915_READ(intel_dp->output_reg) & DP_PORT_EN; -+ -+ I915_STATE_WARN(cur_state != state, -+ "DP port %c state assertion failure (expected %s, current %s)\n", -+ port_name(dig_port->base.port), -+ onoff(state), onoff(cur_state)); -+} -+#define assert_dp_port_disabled(d) assert_dp_port((d), false) -+ -+static void assert_edp_pll(struct drm_i915_private *dev_priv, bool state) -+{ -+ bool cur_state = I915_READ(DP_A) & DP_PLL_ENABLE; -+ -+ I915_STATE_WARN(cur_state != state, -+ "eDP PLL state assertion failure (expected %s, current %s)\n", -+ onoff(state), onoff(cur_state)); -+} -+#define assert_edp_pll_enabled(d) assert_edp_pll((d), true) -+#define assert_edp_pll_disabled(d) assert_edp_pll((d), false) -+ -+static void ironlake_edp_pll_on(struct intel_dp *intel_dp, -+ const struct intel_crtc_state *pipe_config) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(pipe_config->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ assert_pipe_disabled(dev_priv, crtc->pipe); -+ assert_dp_port_disabled(intel_dp); -+ assert_edp_pll_disabled(dev_priv); -+ -+ DRM_DEBUG_KMS("enabling eDP PLL for clock %d\n", -+ pipe_config->port_clock); -+ -+ intel_dp->DP &= ~DP_PLL_FREQ_MASK; -+ -+ if (pipe_config->port_clock == 162000) -+ intel_dp->DP |= DP_PLL_FREQ_162MHZ; -+ else -+ intel_dp->DP |= DP_PLL_FREQ_270MHZ; -+ -+ I915_WRITE(DP_A, intel_dp->DP); -+ POSTING_READ(DP_A); -+ udelay(500); -+ -+ /* -+ * [DevILK] Work around required when enabling DP PLL -+ * while a pipe is enabled going to FDI: -+ * 1. Wait for the start of vertical blank on the enabled pipe going to FDI -+ * 2. Program DP PLL enable -+ */ -+ if (IS_GEN(dev_priv, 5)) -+ intel_wait_for_vblank_if_active(dev_priv, !crtc->pipe); -+ -+ intel_dp->DP |= DP_PLL_ENABLE; -+ -+ I915_WRITE(DP_A, intel_dp->DP); -+ POSTING_READ(DP_A); -+ udelay(200); -+} -+ -+static void ironlake_edp_pll_off(struct intel_dp *intel_dp, -+ const struct intel_crtc_state *old_crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ -+ assert_pipe_disabled(dev_priv, crtc->pipe); -+ assert_dp_port_disabled(intel_dp); -+ assert_edp_pll_enabled(dev_priv); -+ -+ DRM_DEBUG_KMS("disabling eDP PLL\n"); -+ -+ intel_dp->DP &= ~DP_PLL_ENABLE; -+ -+ I915_WRITE(DP_A, intel_dp->DP); -+ POSTING_READ(DP_A); -+ udelay(200); -+} -+ -+static bool downstream_hpd_needs_d0(struct intel_dp *intel_dp) -+{ -+ /* -+ * DPCD 1.2+ should support BRANCH_DEVICE_CTRL, and thus -+ * be capable of signalling downstream hpd with a long pulse. -+ * Whether or not that means D3 is safe to use is not clear, -+ * but let's assume so until proven otherwise. -+ * -+ * FIXME should really check all downstream ports... -+ */ -+ return intel_dp->dpcd[DP_DPCD_REV] == 0x11 && -+ intel_dp->dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT && -+ intel_dp->downstream_ports[0] & DP_DS_PORT_HPD; -+} -+ -+void intel_dp_sink_set_decompression_state(struct intel_dp *intel_dp, -+ const struct intel_crtc_state *crtc_state, -+ bool enable) -+{ -+ int ret; -+ -+ if (!crtc_state->dsc_params.compression_enable) -+ return; -+ -+ ret = drm_dp_dpcd_writeb(&intel_dp->aux, DP_DSC_ENABLE, -+ enable ? DP_DECOMPRESSION_EN : 0); -+ if (ret < 0) -+ DRM_DEBUG_KMS("Failed to %s sink decompression state\n", -+ enable ? "enable" : "disable"); -+} -+ -+/* If the sink supports it, try to set the power state appropriately */ -+void intel_dp_sink_dpms(struct intel_dp *intel_dp, int mode) -+{ -+ int ret, i; -+ -+ /* Should have a valid DPCD by this point */ -+ if (intel_dp->dpcd[DP_DPCD_REV] < 0x11) -+ return; -+ -+ if (mode != DRM_MODE_DPMS_ON) { -+ if (downstream_hpd_needs_d0(intel_dp)) -+ return; -+ -+ ret = drm_dp_dpcd_writeb(&intel_dp->aux, DP_SET_POWER, -+ DP_SET_POWER_D3); -+ } else { -+ struct intel_lspcon *lspcon = dp_to_lspcon(intel_dp); -+ -+ /* -+ * When turning on, we need to retry for 1ms to give the sink -+ * time to wake up. -+ */ -+ for (i = 0; i < 3; i++) { -+ ret = drm_dp_dpcd_writeb(&intel_dp->aux, DP_SET_POWER, -+ DP_SET_POWER_D0); -+ if (ret == 1) -+ break; -+ msleep(1); -+ } -+ -+ if (ret == 1 && lspcon->active) -+ lspcon_wait_pcon_mode(lspcon); -+ } -+ -+ if (ret != 1) -+ DRM_DEBUG_KMS("failed to %s sink power state\n", -+ mode == DRM_MODE_DPMS_ON ? "enable" : "disable"); -+} -+ -+static bool cpt_dp_port_selected(struct drm_i915_private *dev_priv, -+ enum port port, enum pipe *pipe) -+{ -+ enum pipe p; -+ -+ for_each_pipe(dev_priv, p) { -+ u32 val = I915_READ(TRANS_DP_CTL(p)); -+ -+ if ((val & TRANS_DP_PORT_SEL_MASK) == TRANS_DP_PORT_SEL(port)) { -+ *pipe = p; -+ return true; -+ } -+ } -+ -+ DRM_DEBUG_KMS("No pipe for DP port %c found\n", port_name(port)); -+ -+ /* must initialize pipe to something for the asserts */ -+ *pipe = PIPE_A; -+ -+ return false; -+} -+ -+bool intel_dp_port_enabled(struct drm_i915_private *dev_priv, -+ i915_reg_t dp_reg, enum port port, -+ enum pipe *pipe) -+{ -+ bool ret; -+ u32 val; -+ -+ val = I915_READ(dp_reg); -+ -+ ret = val & DP_PORT_EN; -+ -+ /* asserts want to know the pipe even if the port is disabled */ -+ if (IS_IVYBRIDGE(dev_priv) && port == PORT_A) -+ *pipe = (val & DP_PIPE_SEL_MASK_IVB) >> DP_PIPE_SEL_SHIFT_IVB; -+ else if (HAS_PCH_CPT(dev_priv) && port != PORT_A) -+ ret &= cpt_dp_port_selected(dev_priv, port, pipe); -+ else if (IS_CHERRYVIEW(dev_priv)) -+ *pipe = (val & DP_PIPE_SEL_MASK_CHV) >> DP_PIPE_SEL_SHIFT_CHV; -+ else -+ *pipe = (val & DP_PIPE_SEL_MASK) >> DP_PIPE_SEL_SHIFT; -+ -+ return ret; -+} -+ -+static bool intel_dp_get_hw_state(struct intel_encoder *encoder, -+ enum pipe *pipe) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ intel_wakeref_t wakeref; -+ bool ret; -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ encoder->power_domain); -+ if (!wakeref) -+ return false; -+ -+ ret = intel_dp_port_enabled(dev_priv, intel_dp->output_reg, -+ encoder->port, pipe); -+ -+ intel_display_power_put(dev_priv, encoder->power_domain, wakeref); -+ -+ return ret; -+} -+ -+static void intel_dp_get_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ u32 tmp, flags = 0; -+ enum port port = encoder->port; -+ struct intel_crtc *crtc = to_intel_crtc(pipe_config->base.crtc); -+ -+ if (encoder->type == INTEL_OUTPUT_EDP) -+ pipe_config->output_types |= BIT(INTEL_OUTPUT_EDP); -+ else -+ pipe_config->output_types |= BIT(INTEL_OUTPUT_DP); -+ -+ tmp = I915_READ(intel_dp->output_reg); -+ -+ pipe_config->has_audio = tmp & DP_AUDIO_OUTPUT_ENABLE && port != PORT_A; -+ -+ if (HAS_PCH_CPT(dev_priv) && port != PORT_A) { -+ u32 trans_dp = I915_READ(TRANS_DP_CTL(crtc->pipe)); -+ -+ if (trans_dp & TRANS_DP_HSYNC_ACTIVE_HIGH) -+ flags |= DRM_MODE_FLAG_PHSYNC; -+ else -+ flags |= DRM_MODE_FLAG_NHSYNC; -+ -+ if (trans_dp & TRANS_DP_VSYNC_ACTIVE_HIGH) -+ flags |= DRM_MODE_FLAG_PVSYNC; -+ else -+ flags |= DRM_MODE_FLAG_NVSYNC; -+ } else { -+ if (tmp & DP_SYNC_HS_HIGH) -+ flags |= DRM_MODE_FLAG_PHSYNC; -+ else -+ flags |= DRM_MODE_FLAG_NHSYNC; -+ -+ if (tmp & DP_SYNC_VS_HIGH) -+ flags |= DRM_MODE_FLAG_PVSYNC; -+ else -+ flags |= DRM_MODE_FLAG_NVSYNC; -+ } -+ -+ pipe_config->base.adjusted_mode.flags |= flags; -+ -+ if (IS_G4X(dev_priv) && tmp & DP_COLOR_RANGE_16_235) -+ pipe_config->limited_color_range = true; -+ -+ pipe_config->lane_count = -+ ((tmp & DP_PORT_WIDTH_MASK) >> DP_PORT_WIDTH_SHIFT) + 1; -+ -+ intel_dp_get_m_n(crtc, pipe_config); -+ -+ if (port == PORT_A) { -+ if ((I915_READ(DP_A) & DP_PLL_FREQ_MASK) == DP_PLL_FREQ_162MHZ) -+ pipe_config->port_clock = 162000; -+ else -+ pipe_config->port_clock = 270000; -+ } -+ -+ pipe_config->base.adjusted_mode.crtc_clock = -+ intel_dotclock_calculate(pipe_config->port_clock, -+ &pipe_config->dp_m_n); -+ -+ if (intel_dp_is_edp(intel_dp) && dev_priv->vbt.edp.bpp && -+ pipe_config->pipe_bpp > dev_priv->vbt.edp.bpp) { -+ /* -+ * This is a big fat ugly hack. -+ * -+ * Some machines in UEFI boot mode provide us a VBT that has 18 -+ * bpp and 1.62 GHz link bandwidth for eDP, which for reasons -+ * unknown we fail to light up. Yet the same BIOS boots up with -+ * 24 bpp and 2.7 GHz link. Use the same bpp as the BIOS uses as -+ * max, not what it tells us to use. -+ * -+ * Note: This will still be broken if the eDP panel is not lit -+ * up by the BIOS, and thus we can't get the mode at module -+ * load. -+ */ -+ DRM_DEBUG_KMS("pipe has %d bpp for eDP panel, overriding BIOS-provided max %d bpp\n", -+ pipe_config->pipe_bpp, dev_priv->vbt.edp.bpp); -+ dev_priv->vbt.edp.bpp = pipe_config->pipe_bpp; -+ } -+} -+ -+static void intel_disable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ -+ intel_dp->link_trained = false; -+ -+ if (old_crtc_state->has_audio) -+ intel_audio_codec_disable(encoder, -+ old_crtc_state, old_conn_state); -+ -+ /* Make sure the panel is off before trying to change the mode. But also -+ * ensure that we have vdd while we switch off the panel. */ -+ intel_edp_panel_vdd_on(intel_dp); -+ intel_edp_backlight_off(old_conn_state); -+ intel_dp_sink_dpms(intel_dp, DRM_MODE_DPMS_OFF); -+ intel_edp_panel_off(intel_dp); -+} -+ -+static void g4x_disable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ intel_disable_dp(encoder, old_crtc_state, old_conn_state); -+} -+ -+static void vlv_disable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ intel_disable_dp(encoder, old_crtc_state, old_conn_state); -+} -+ -+static void g4x_post_disable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ enum port port = encoder->port; -+ -+ /* -+ * Bspec does not list a specific disable sequence for g4x DP. -+ * Follow the ilk+ sequence (disable pipe before the port) for -+ * g4x DP as it does not suffer from underruns like the normal -+ * g4x modeset sequence (disable pipe after the port). -+ */ -+ intel_dp_link_down(encoder, old_crtc_state); -+ -+ /* Only ilk+ has port A */ -+ if (port == PORT_A) -+ ironlake_edp_pll_off(intel_dp, old_crtc_state); -+} -+ -+static void vlv_post_disable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ intel_dp_link_down(encoder, old_crtc_state); -+} -+ -+static void chv_post_disable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ -+ intel_dp_link_down(encoder, old_crtc_state); -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ /* Assert data lane reset */ -+ chv_data_lane_soft_reset(encoder, old_crtc_state, true); -+ -+ mutex_unlock(&dev_priv->sb_lock); -+} -+ -+static void -+_intel_dp_set_link_train(struct intel_dp *intel_dp, -+ u32 *DP, -+ u8 dp_train_pat) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ enum port port = intel_dig_port->base.port; -+ u8 train_pat_mask = drm_dp_training_pattern_mask(intel_dp->dpcd); -+ -+ if (dp_train_pat & train_pat_mask) -+ DRM_DEBUG_KMS("Using DP training pattern TPS%d\n", -+ dp_train_pat & train_pat_mask); -+ -+ if (HAS_DDI(dev_priv)) { -+ u32 temp = I915_READ(DP_TP_CTL(port)); -+ -+ if (dp_train_pat & DP_LINK_SCRAMBLING_DISABLE) -+ temp |= DP_TP_CTL_SCRAMBLE_DISABLE; -+ else -+ temp &= ~DP_TP_CTL_SCRAMBLE_DISABLE; -+ -+ temp &= ~DP_TP_CTL_LINK_TRAIN_MASK; -+ switch (dp_train_pat & train_pat_mask) { -+ case DP_TRAINING_PATTERN_DISABLE: -+ temp |= DP_TP_CTL_LINK_TRAIN_NORMAL; -+ -+ break; -+ case DP_TRAINING_PATTERN_1: -+ temp |= DP_TP_CTL_LINK_TRAIN_PAT1; -+ break; -+ case DP_TRAINING_PATTERN_2: -+ temp |= DP_TP_CTL_LINK_TRAIN_PAT2; -+ break; -+ case DP_TRAINING_PATTERN_3: -+ temp |= DP_TP_CTL_LINK_TRAIN_PAT3; -+ break; -+ case DP_TRAINING_PATTERN_4: -+ temp |= DP_TP_CTL_LINK_TRAIN_PAT4; -+ break; -+ } -+ I915_WRITE(DP_TP_CTL(port), temp); -+ -+ } else if ((IS_IVYBRIDGE(dev_priv) && port == PORT_A) || -+ (HAS_PCH_CPT(dev_priv) && port != PORT_A)) { -+ *DP &= ~DP_LINK_TRAIN_MASK_CPT; -+ -+ switch (dp_train_pat & DP_TRAINING_PATTERN_MASK) { -+ case DP_TRAINING_PATTERN_DISABLE: -+ *DP |= DP_LINK_TRAIN_OFF_CPT; -+ break; -+ case DP_TRAINING_PATTERN_1: -+ *DP |= DP_LINK_TRAIN_PAT_1_CPT; -+ break; -+ case DP_TRAINING_PATTERN_2: -+ *DP |= DP_LINK_TRAIN_PAT_2_CPT; -+ break; -+ case DP_TRAINING_PATTERN_3: -+ DRM_DEBUG_KMS("TPS3 not supported, using TPS2 instead\n"); -+ *DP |= DP_LINK_TRAIN_PAT_2_CPT; -+ break; -+ } -+ -+ } else { -+ *DP &= ~DP_LINK_TRAIN_MASK; -+ -+ switch (dp_train_pat & DP_TRAINING_PATTERN_MASK) { -+ case DP_TRAINING_PATTERN_DISABLE: -+ *DP |= DP_LINK_TRAIN_OFF; -+ break; -+ case DP_TRAINING_PATTERN_1: -+ *DP |= DP_LINK_TRAIN_PAT_1; -+ break; -+ case DP_TRAINING_PATTERN_2: -+ *DP |= DP_LINK_TRAIN_PAT_2; -+ break; -+ case DP_TRAINING_PATTERN_3: -+ DRM_DEBUG_KMS("TPS3 not supported, using TPS2 instead\n"); -+ *DP |= DP_LINK_TRAIN_PAT_2; -+ break; -+ } -+ } -+} -+ -+static void intel_dp_enable_port(struct intel_dp *intel_dp, -+ const struct intel_crtc_state *old_crtc_state) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ -+ /* enable with pattern 1 (as per spec) */ -+ -+ intel_dp_program_link_training_pattern(intel_dp, DP_TRAINING_PATTERN_1); -+ -+ /* -+ * Magic for VLV/CHV. We _must_ first set up the register -+ * without actually enabling the port, and then do another -+ * write to enable the port. Otherwise link training will -+ * fail when the power sequencer is freshly used for this port. -+ */ -+ intel_dp->DP |= DP_PORT_EN; -+ if (old_crtc_state->has_audio) -+ intel_dp->DP |= DP_AUDIO_OUTPUT_ENABLE; -+ -+ I915_WRITE(intel_dp->output_reg, intel_dp->DP); -+ POSTING_READ(intel_dp->output_reg); -+} -+ -+static void intel_enable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config, -+ const struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ struct intel_crtc *crtc = to_intel_crtc(pipe_config->base.crtc); -+ u32 dp_reg = I915_READ(intel_dp->output_reg); -+ enum pipe pipe = crtc->pipe; -+ intel_wakeref_t wakeref; -+ -+ if (WARN_ON(dp_reg & DP_PORT_EN)) -+ return; -+ -+ with_pps_lock(intel_dp, wakeref) { -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ vlv_init_panel_power_sequencer(encoder, pipe_config); -+ -+ intel_dp_enable_port(intel_dp, pipe_config); -+ -+ edp_panel_vdd_on(intel_dp); -+ edp_panel_on(intel_dp); -+ edp_panel_vdd_off(intel_dp, true); -+ } -+ -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ unsigned int lane_mask = 0x0; -+ -+ if (IS_CHERRYVIEW(dev_priv)) -+ lane_mask = intel_dp_unused_lane_mask(pipe_config->lane_count); -+ -+ vlv_wait_port_ready(dev_priv, dp_to_dig_port(intel_dp), -+ lane_mask); -+ } -+ -+ intel_dp_sink_dpms(intel_dp, DRM_MODE_DPMS_ON); -+ intel_dp_start_link_train(intel_dp); -+ intel_dp_stop_link_train(intel_dp); -+ -+ if (pipe_config->has_audio) { -+ DRM_DEBUG_DRIVER("Enabling DP audio on pipe %c\n", -+ pipe_name(pipe)); -+ intel_audio_codec_enable(encoder, pipe_config, conn_state); -+ } -+} -+ -+static void g4x_enable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config, -+ const struct drm_connector_state *conn_state) -+{ -+ intel_enable_dp(encoder, pipe_config, conn_state); -+ intel_edp_backlight_on(pipe_config, conn_state); -+} -+ -+static void vlv_enable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config, -+ const struct drm_connector_state *conn_state) -+{ -+ intel_edp_backlight_on(pipe_config, conn_state); -+} -+ -+static void g4x_pre_enable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config, -+ const struct drm_connector_state *conn_state) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ enum port port = encoder->port; -+ -+ intel_dp_prepare(encoder, pipe_config); -+ -+ /* Only ilk+ has port A */ -+ if (port == PORT_A) -+ ironlake_edp_pll_on(intel_dp, pipe_config); -+} -+ -+static void vlv_detach_power_sequencer(struct intel_dp *intel_dp) -+{ -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = to_i915(intel_dig_port->base.base.dev); -+ enum pipe pipe = intel_dp->pps_pipe; -+ i915_reg_t pp_on_reg = PP_ON_DELAYS(pipe); -+ -+ WARN_ON(intel_dp->active_pipe != INVALID_PIPE); -+ -+ if (WARN_ON(pipe != PIPE_A && pipe != PIPE_B)) -+ return; -+ -+ edp_panel_vdd_off_sync(intel_dp); -+ -+ /* -+ * VLV seems to get confused when multiple power sequencers -+ * have the same port selected (even if only one has power/vdd -+ * enabled). The failure manifests as vlv_wait_port_ready() failing -+ * CHV on the other hand doesn't seem to mind having the same port -+ * selected in multiple power sequencers, but let's clear the -+ * port select always when logically disconnecting a power sequencer -+ * from a port. -+ */ -+ DRM_DEBUG_KMS("detaching pipe %c power sequencer from port %c\n", -+ pipe_name(pipe), port_name(intel_dig_port->base.port)); -+ I915_WRITE(pp_on_reg, 0); -+ POSTING_READ(pp_on_reg); -+ -+ intel_dp->pps_pipe = INVALID_PIPE; -+} -+ -+static void vlv_steal_power_sequencer(struct drm_i915_private *dev_priv, -+ enum pipe pipe) -+{ -+ struct intel_encoder *encoder; -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ for_each_intel_dp(&dev_priv->drm, encoder) { -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ enum port port = encoder->port; -+ -+ WARN(intel_dp->active_pipe == pipe, -+ "stealing pipe %c power sequencer from active (e)DP port %c\n", -+ pipe_name(pipe), port_name(port)); -+ -+ if (intel_dp->pps_pipe != pipe) -+ continue; -+ -+ DRM_DEBUG_KMS("stealing pipe %c power sequencer from port %c\n", -+ pipe_name(pipe), port_name(port)); -+ -+ /* make sure vdd is off before we steal it */ -+ vlv_detach_power_sequencer(intel_dp); -+ } -+} -+ -+static void vlv_init_panel_power_sequencer(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ WARN_ON(intel_dp->active_pipe != INVALID_PIPE); -+ -+ if (intel_dp->pps_pipe != INVALID_PIPE && -+ intel_dp->pps_pipe != crtc->pipe) { -+ /* -+ * If another power sequencer was being used on this -+ * port previously make sure to turn off vdd there while -+ * we still have control of it. -+ */ -+ vlv_detach_power_sequencer(intel_dp); -+ } -+ -+ /* -+ * We may be stealing the power -+ * sequencer from another port. -+ */ -+ vlv_steal_power_sequencer(dev_priv, crtc->pipe); -+ -+ intel_dp->active_pipe = crtc->pipe; -+ -+ if (!intel_dp_is_edp(intel_dp)) -+ return; -+ -+ /* now it's all ours */ -+ intel_dp->pps_pipe = crtc->pipe; -+ -+ DRM_DEBUG_KMS("initializing pipe %c power sequencer for port %c\n", -+ pipe_name(intel_dp->pps_pipe), port_name(encoder->port)); -+ -+ /* init power sequencer on this pipe and port */ -+ intel_dp_init_panel_power_sequencer(intel_dp); -+ intel_dp_init_panel_power_sequencer_registers(intel_dp, true); -+} -+ -+static void vlv_pre_enable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config, -+ const struct drm_connector_state *conn_state) -+{ -+ vlv_phy_pre_encoder_enable(encoder, pipe_config); -+ -+ intel_enable_dp(encoder, pipe_config, conn_state); -+} -+ -+static void vlv_dp_pre_pll_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config, -+ const struct drm_connector_state *conn_state) -+{ -+ intel_dp_prepare(encoder, pipe_config); -+ -+ vlv_phy_pre_pll_enable(encoder, pipe_config); -+} -+ -+static void chv_pre_enable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config, -+ const struct drm_connector_state *conn_state) -+{ -+ chv_phy_pre_encoder_enable(encoder, pipe_config); -+ -+ intel_enable_dp(encoder, pipe_config, conn_state); -+ -+ /* Second common lane will stay alive on its own now */ -+ chv_phy_release_cl2_override(encoder); -+} -+ -+static void chv_dp_pre_pll_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config, -+ const struct drm_connector_state *conn_state) -+{ -+ intel_dp_prepare(encoder, pipe_config); -+ -+ chv_phy_pre_pll_enable(encoder, pipe_config); -+} -+ -+static void chv_dp_post_pll_disable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ chv_phy_post_pll_disable(encoder, old_crtc_state); -+} -+ -+/* -+ * Fetch AUX CH registers 0x202 - 0x207 which contain -+ * link status information -+ */ -+bool -+intel_dp_get_link_status(struct intel_dp *intel_dp, u8 link_status[DP_LINK_STATUS_SIZE]) -+{ -+ return drm_dp_dpcd_read(&intel_dp->aux, DP_LANE0_1_STATUS, link_status, -+ DP_LINK_STATUS_SIZE) == DP_LINK_STATUS_SIZE; -+} -+ -+/* These are source-specific values. */ -+u8 -+intel_dp_voltage_max(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; -+ enum port port = encoder->port; -+ -+ if (HAS_DDI(dev_priv)) -+ return intel_ddi_dp_voltage_max(encoder); -+ else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ return DP_TRAIN_VOLTAGE_SWING_LEVEL_3; -+ else if (IS_IVYBRIDGE(dev_priv) && port == PORT_A) -+ return DP_TRAIN_VOLTAGE_SWING_LEVEL_2; -+ else if (HAS_PCH_CPT(dev_priv) && port != PORT_A) -+ return DP_TRAIN_VOLTAGE_SWING_LEVEL_3; -+ else -+ return DP_TRAIN_VOLTAGE_SWING_LEVEL_2; -+} -+ -+u8 -+intel_dp_pre_emphasis_max(struct intel_dp *intel_dp, u8 voltage_swing) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; -+ enum port port = encoder->port; -+ -+ if (HAS_DDI(dev_priv)) { -+ return intel_ddi_dp_pre_emphasis_max(encoder, voltage_swing); -+ } else if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ switch (voltage_swing & DP_TRAIN_VOLTAGE_SWING_MASK) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: -+ return DP_TRAIN_PRE_EMPH_LEVEL_3; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: -+ return DP_TRAIN_PRE_EMPH_LEVEL_2; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: -+ return DP_TRAIN_PRE_EMPH_LEVEL_1; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_3: -+ default: -+ return DP_TRAIN_PRE_EMPH_LEVEL_0; -+ } -+ } else if (IS_IVYBRIDGE(dev_priv) && port == PORT_A) { -+ switch (voltage_swing & DP_TRAIN_VOLTAGE_SWING_MASK) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: -+ return DP_TRAIN_PRE_EMPH_LEVEL_2; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: -+ return DP_TRAIN_PRE_EMPH_LEVEL_1; -+ default: -+ return DP_TRAIN_PRE_EMPH_LEVEL_0; -+ } -+ } else { -+ switch (voltage_swing & DP_TRAIN_VOLTAGE_SWING_MASK) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: -+ return DP_TRAIN_PRE_EMPH_LEVEL_2; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: -+ return DP_TRAIN_PRE_EMPH_LEVEL_2; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: -+ return DP_TRAIN_PRE_EMPH_LEVEL_1; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_3: -+ default: -+ return DP_TRAIN_PRE_EMPH_LEVEL_0; -+ } -+ } -+} -+ -+static u32 vlv_signal_levels(struct intel_dp *intel_dp) -+{ -+ struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; -+ unsigned long demph_reg_value, preemph_reg_value, -+ uniqtranscale_reg_value; -+ u8 train_set = intel_dp->train_set[0]; -+ -+ switch (train_set & DP_TRAIN_PRE_EMPHASIS_MASK) { -+ case DP_TRAIN_PRE_EMPH_LEVEL_0: -+ preemph_reg_value = 0x0004000; -+ switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: -+ demph_reg_value = 0x2B405555; -+ uniqtranscale_reg_value = 0x552AB83A; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: -+ demph_reg_value = 0x2B404040; -+ uniqtranscale_reg_value = 0x5548B83A; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: -+ demph_reg_value = 0x2B245555; -+ uniqtranscale_reg_value = 0x5560B83A; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_3: -+ demph_reg_value = 0x2B405555; -+ uniqtranscale_reg_value = 0x5598DA3A; -+ break; -+ default: -+ return 0; -+ } -+ break; -+ case DP_TRAIN_PRE_EMPH_LEVEL_1: -+ preemph_reg_value = 0x0002000; -+ switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: -+ demph_reg_value = 0x2B404040; -+ uniqtranscale_reg_value = 0x5552B83A; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: -+ demph_reg_value = 0x2B404848; -+ uniqtranscale_reg_value = 0x5580B83A; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: -+ demph_reg_value = 0x2B404040; -+ uniqtranscale_reg_value = 0x55ADDA3A; -+ break; -+ default: -+ return 0; -+ } -+ break; -+ case DP_TRAIN_PRE_EMPH_LEVEL_2: -+ preemph_reg_value = 0x0000000; -+ switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: -+ demph_reg_value = 0x2B305555; -+ uniqtranscale_reg_value = 0x5570B83A; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: -+ demph_reg_value = 0x2B2B4040; -+ uniqtranscale_reg_value = 0x55ADDA3A; -+ break; -+ default: -+ return 0; -+ } -+ break; -+ case DP_TRAIN_PRE_EMPH_LEVEL_3: -+ preemph_reg_value = 0x0006000; -+ switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: -+ demph_reg_value = 0x1B405555; -+ uniqtranscale_reg_value = 0x55ADDA3A; -+ break; -+ default: -+ return 0; -+ } -+ break; -+ default: -+ return 0; -+ } -+ -+ vlv_set_phy_signal_level(encoder, demph_reg_value, preemph_reg_value, -+ uniqtranscale_reg_value, 0); -+ -+ return 0; -+} -+ -+static u32 chv_signal_levels(struct intel_dp *intel_dp) -+{ -+ struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; -+ u32 deemph_reg_value, margin_reg_value; -+ bool uniq_trans_scale = false; -+ u8 train_set = intel_dp->train_set[0]; -+ -+ switch (train_set & DP_TRAIN_PRE_EMPHASIS_MASK) { -+ case DP_TRAIN_PRE_EMPH_LEVEL_0: -+ switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: -+ deemph_reg_value = 128; -+ margin_reg_value = 52; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: -+ deemph_reg_value = 128; -+ margin_reg_value = 77; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: -+ deemph_reg_value = 128; -+ margin_reg_value = 102; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_3: -+ deemph_reg_value = 128; -+ margin_reg_value = 154; -+ uniq_trans_scale = true; -+ break; -+ default: -+ return 0; -+ } -+ break; -+ case DP_TRAIN_PRE_EMPH_LEVEL_1: -+ switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: -+ deemph_reg_value = 85; -+ margin_reg_value = 78; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: -+ deemph_reg_value = 85; -+ margin_reg_value = 116; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: -+ deemph_reg_value = 85; -+ margin_reg_value = 154; -+ break; -+ default: -+ return 0; -+ } -+ break; -+ case DP_TRAIN_PRE_EMPH_LEVEL_2: -+ switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: -+ deemph_reg_value = 64; -+ margin_reg_value = 104; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: -+ deemph_reg_value = 64; -+ margin_reg_value = 154; -+ break; -+ default: -+ return 0; -+ } -+ break; -+ case DP_TRAIN_PRE_EMPH_LEVEL_3: -+ switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: -+ deemph_reg_value = 43; -+ margin_reg_value = 154; -+ break; -+ default: -+ return 0; -+ } -+ break; -+ default: -+ return 0; -+ } -+ -+ chv_set_phy_signal_level(encoder, deemph_reg_value, -+ margin_reg_value, uniq_trans_scale); -+ -+ return 0; -+} -+ -+static u32 -+g4x_signal_levels(u8 train_set) -+{ -+ u32 signal_levels = 0; -+ -+ switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0: -+ default: -+ signal_levels |= DP_VOLTAGE_0_4; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1: -+ signal_levels |= DP_VOLTAGE_0_6; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_2: -+ signal_levels |= DP_VOLTAGE_0_8; -+ break; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_3: -+ signal_levels |= DP_VOLTAGE_1_2; -+ break; -+ } -+ switch (train_set & DP_TRAIN_PRE_EMPHASIS_MASK) { -+ case DP_TRAIN_PRE_EMPH_LEVEL_0: -+ default: -+ signal_levels |= DP_PRE_EMPHASIS_0; -+ break; -+ case DP_TRAIN_PRE_EMPH_LEVEL_1: -+ signal_levels |= DP_PRE_EMPHASIS_3_5; -+ break; -+ case DP_TRAIN_PRE_EMPH_LEVEL_2: -+ signal_levels |= DP_PRE_EMPHASIS_6; -+ break; -+ case DP_TRAIN_PRE_EMPH_LEVEL_3: -+ signal_levels |= DP_PRE_EMPHASIS_9_5; -+ break; -+ } -+ return signal_levels; -+} -+ -+/* SNB CPU eDP voltage swing and pre-emphasis control */ -+static u32 -+snb_cpu_edp_signal_levels(u8 train_set) -+{ -+ int signal_levels = train_set & (DP_TRAIN_VOLTAGE_SWING_MASK | -+ DP_TRAIN_PRE_EMPHASIS_MASK); -+ switch (signal_levels) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_0: -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_0: -+ return EDP_LINK_TRAIN_400_600MV_0DB_SNB_B; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_1: -+ return EDP_LINK_TRAIN_400MV_3_5DB_SNB_B; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_2: -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_2: -+ return EDP_LINK_TRAIN_400_600MV_6DB_SNB_B; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_1: -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_2 | DP_TRAIN_PRE_EMPH_LEVEL_1: -+ return EDP_LINK_TRAIN_600_800MV_3_5DB_SNB_B; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_2 | DP_TRAIN_PRE_EMPH_LEVEL_0: -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_3 | DP_TRAIN_PRE_EMPH_LEVEL_0: -+ return EDP_LINK_TRAIN_800_1200MV_0DB_SNB_B; -+ default: -+ DRM_DEBUG_KMS("Unsupported voltage swing/pre-emphasis level:" -+ "0x%x\n", signal_levels); -+ return EDP_LINK_TRAIN_400_600MV_0DB_SNB_B; -+ } -+} -+ -+/* IVB CPU eDP voltage swing and pre-emphasis control */ -+static u32 -+ivb_cpu_edp_signal_levels(u8 train_set) -+{ -+ int signal_levels = train_set & (DP_TRAIN_VOLTAGE_SWING_MASK | -+ DP_TRAIN_PRE_EMPHASIS_MASK); -+ switch (signal_levels) { -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_0: -+ return EDP_LINK_TRAIN_400MV_0DB_IVB; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_1: -+ return EDP_LINK_TRAIN_400MV_3_5DB_IVB; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | DP_TRAIN_PRE_EMPH_LEVEL_2: -+ return EDP_LINK_TRAIN_400MV_6DB_IVB; -+ -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_0: -+ return EDP_LINK_TRAIN_600MV_0DB_IVB; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_1 | DP_TRAIN_PRE_EMPH_LEVEL_1: -+ return EDP_LINK_TRAIN_600MV_3_5DB_IVB; -+ -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_2 | DP_TRAIN_PRE_EMPH_LEVEL_0: -+ return EDP_LINK_TRAIN_800MV_0DB_IVB; -+ case DP_TRAIN_VOLTAGE_SWING_LEVEL_2 | DP_TRAIN_PRE_EMPH_LEVEL_1: -+ return EDP_LINK_TRAIN_800MV_3_5DB_IVB; -+ -+ default: -+ DRM_DEBUG_KMS("Unsupported voltage swing/pre-emphasis level:" -+ "0x%x\n", signal_levels); -+ return EDP_LINK_TRAIN_500MV_0DB_IVB; -+ } -+} -+ -+void -+intel_dp_set_signal_levels(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ enum port port = intel_dig_port->base.port; -+ u32 signal_levels, mask = 0; -+ u8 train_set = intel_dp->train_set[0]; -+ -+ if (IS_GEN9_LP(dev_priv) || INTEL_GEN(dev_priv) >= 10) { -+ signal_levels = bxt_signal_levels(intel_dp); -+ } else if (HAS_DDI(dev_priv)) { -+ signal_levels = ddi_signal_levels(intel_dp); -+ mask = DDI_BUF_EMP_MASK; -+ } else if (IS_CHERRYVIEW(dev_priv)) { -+ signal_levels = chv_signal_levels(intel_dp); -+ } else if (IS_VALLEYVIEW(dev_priv)) { -+ signal_levels = vlv_signal_levels(intel_dp); -+ } else if (IS_IVYBRIDGE(dev_priv) && port == PORT_A) { -+ signal_levels = ivb_cpu_edp_signal_levels(train_set); -+ mask = EDP_LINK_TRAIN_VOL_EMP_MASK_IVB; -+ } else if (IS_GEN(dev_priv, 6) && port == PORT_A) { -+ signal_levels = snb_cpu_edp_signal_levels(train_set); -+ mask = EDP_LINK_TRAIN_VOL_EMP_MASK_SNB; -+ } else { -+ signal_levels = g4x_signal_levels(train_set); -+ mask = DP_VOLTAGE_MASK | DP_PRE_EMPHASIS_MASK; -+ } -+ -+ if (mask) -+ DRM_DEBUG_KMS("Using signal levels %08x\n", signal_levels); -+ -+ DRM_DEBUG_KMS("Using vswing level %d\n", -+ train_set & DP_TRAIN_VOLTAGE_SWING_MASK); -+ DRM_DEBUG_KMS("Using pre-emphasis level %d\n", -+ (train_set & DP_TRAIN_PRE_EMPHASIS_MASK) >> -+ DP_TRAIN_PRE_EMPHASIS_SHIFT); -+ -+ intel_dp->DP = (intel_dp->DP & ~mask) | signal_levels; -+ -+ I915_WRITE(intel_dp->output_reg, intel_dp->DP); -+ POSTING_READ(intel_dp->output_reg); -+} -+ -+void -+intel_dp_program_link_training_pattern(struct intel_dp *intel_dp, -+ u8 dp_train_pat) -+{ -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = -+ to_i915(intel_dig_port->base.base.dev); -+ -+ _intel_dp_set_link_train(intel_dp, &intel_dp->DP, dp_train_pat); -+ -+ I915_WRITE(intel_dp->output_reg, intel_dp->DP); -+ POSTING_READ(intel_dp->output_reg); -+} -+ -+void intel_dp_set_idle_link_train(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ enum port port = intel_dig_port->base.port; -+ u32 val; -+ -+ if (!HAS_DDI(dev_priv)) -+ return; -+ -+ val = I915_READ(DP_TP_CTL(port)); -+ val &= ~DP_TP_CTL_LINK_TRAIN_MASK; -+ val |= DP_TP_CTL_LINK_TRAIN_IDLE; -+ I915_WRITE(DP_TP_CTL(port), val); -+ -+ /* -+ * On PORT_A we can have only eDP in SST mode. There the only reason -+ * we need to set idle transmission mode is to work around a HW issue -+ * where we enable the pipe while not in idle link-training mode. -+ * In this case there is requirement to wait for a minimum number of -+ * idle patterns to be sent. -+ */ -+ if (port == PORT_A) -+ return; -+ -+ if (intel_wait_for_register(&dev_priv->uncore, DP_TP_STATUS(port), -+ DP_TP_STATUS_IDLE_DONE, -+ DP_TP_STATUS_IDLE_DONE, -+ 1)) -+ DRM_ERROR("Timed out waiting for DP idle patterns\n"); -+} -+ -+static void -+intel_dp_link_down(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->base.crtc); -+ enum port port = encoder->port; -+ u32 DP = intel_dp->DP; -+ -+ if (WARN_ON(HAS_DDI(dev_priv))) -+ return; -+ -+ if (WARN_ON((I915_READ(intel_dp->output_reg) & DP_PORT_EN) == 0)) -+ return; -+ -+ DRM_DEBUG_KMS("\n"); -+ -+ if ((IS_IVYBRIDGE(dev_priv) && port == PORT_A) || -+ (HAS_PCH_CPT(dev_priv) && port != PORT_A)) { -+ DP &= ~DP_LINK_TRAIN_MASK_CPT; -+ DP |= DP_LINK_TRAIN_PAT_IDLE_CPT; -+ } else { -+ DP &= ~DP_LINK_TRAIN_MASK; -+ DP |= DP_LINK_TRAIN_PAT_IDLE; -+ } -+ I915_WRITE(intel_dp->output_reg, DP); -+ POSTING_READ(intel_dp->output_reg); -+ -+ DP &= ~(DP_PORT_EN | DP_AUDIO_OUTPUT_ENABLE); -+ I915_WRITE(intel_dp->output_reg, DP); -+ POSTING_READ(intel_dp->output_reg); -+ -+ /* -+ * HW workaround for IBX, we need to move the port -+ * to transcoder A after disabling it to allow the -+ * matching HDMI port to be enabled on transcoder A. -+ */ -+ if (HAS_PCH_IBX(dev_priv) && crtc->pipe == PIPE_B && port != PORT_A) { -+ /* -+ * We get CPU/PCH FIFO underruns on the other pipe when -+ * doing the workaround. Sweep them under the rug. -+ */ -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, PIPE_A, false); -+ intel_set_pch_fifo_underrun_reporting(dev_priv, PIPE_A, false); -+ -+ /* always enable with pattern 1 (as per spec) */ -+ DP &= ~(DP_PIPE_SEL_MASK | DP_LINK_TRAIN_MASK); -+ DP |= DP_PORT_EN | DP_PIPE_SEL(PIPE_A) | -+ DP_LINK_TRAIN_PAT_1; -+ I915_WRITE(intel_dp->output_reg, DP); -+ POSTING_READ(intel_dp->output_reg); -+ -+ DP &= ~DP_PORT_EN; -+ I915_WRITE(intel_dp->output_reg, DP); -+ POSTING_READ(intel_dp->output_reg); -+ -+ intel_wait_for_vblank_if_active(dev_priv, PIPE_A); -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, PIPE_A, true); -+ intel_set_pch_fifo_underrun_reporting(dev_priv, PIPE_A, true); -+ } -+ -+ msleep(intel_dp->panel_power_down_delay); -+ -+ intel_dp->DP = DP; -+ -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ intel_wakeref_t wakeref; -+ -+ with_pps_lock(intel_dp, wakeref) -+ intel_dp->active_pipe = INVALID_PIPE; -+ } -+} -+ -+static void -+intel_dp_extended_receiver_capabilities(struct intel_dp *intel_dp) -+{ -+ u8 dpcd_ext[6]; -+ -+ /* -+ * Prior to DP1.3 the bit represented by -+ * DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT was reserved. -+ * if it is set DP_DPCD_REV at 0000h could be at a value less than -+ * the true capability of the panel. The only way to check is to -+ * then compare 0000h and 2200h. -+ */ -+ if (!(intel_dp->dpcd[DP_TRAINING_AUX_RD_INTERVAL] & -+ DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT)) -+ return; -+ -+ if (drm_dp_dpcd_read(&intel_dp->aux, DP_DP13_DPCD_REV, -+ &dpcd_ext, sizeof(dpcd_ext)) != sizeof(dpcd_ext)) { -+ DRM_ERROR("DPCD failed read at extended capabilities\n"); -+ return; -+ } -+ -+ if (intel_dp->dpcd[DP_DPCD_REV] > dpcd_ext[DP_DPCD_REV]) { -+ DRM_DEBUG_KMS("DPCD extended DPCD rev less than base DPCD rev\n"); -+ return; -+ } -+ -+ if (!memcmp(intel_dp->dpcd, dpcd_ext, sizeof(dpcd_ext))) -+ return; -+ -+ DRM_DEBUG_KMS("Base DPCD: %*ph\n", -+ (int)sizeof(intel_dp->dpcd), intel_dp->dpcd); -+ -+ memcpy(intel_dp->dpcd, dpcd_ext, sizeof(dpcd_ext)); -+} -+ -+bool -+intel_dp_read_dpcd(struct intel_dp *intel_dp) -+{ -+ if (drm_dp_dpcd_read(&intel_dp->aux, 0x000, intel_dp->dpcd, -+ sizeof(intel_dp->dpcd)) < 0) -+ return false; /* aux transfer failed */ -+ -+ intel_dp_extended_receiver_capabilities(intel_dp); -+ -+ DRM_DEBUG_KMS("DPCD: %*ph\n", (int) sizeof(intel_dp->dpcd), intel_dp->dpcd); -+ -+ return intel_dp->dpcd[DP_DPCD_REV] != 0; -+} -+ -+static void intel_dp_get_dsc_sink_cap(struct intel_dp *intel_dp) -+{ -+ /* -+ * Clear the cached register set to avoid using stale values -+ * for the sinks that do not support DSC. -+ */ -+ memset(intel_dp->dsc_dpcd, 0, sizeof(intel_dp->dsc_dpcd)); -+ -+ /* Clear fec_capable to avoid using stale values */ -+ intel_dp->fec_capable = 0; -+ -+ /* Cache the DSC DPCD if eDP or DP rev >= 1.4 */ -+ if (intel_dp->dpcd[DP_DPCD_REV] >= 0x14 || -+ intel_dp->edp_dpcd[0] >= DP_EDP_14) { -+ if (drm_dp_dpcd_read(&intel_dp->aux, DP_DSC_SUPPORT, -+ intel_dp->dsc_dpcd, -+ sizeof(intel_dp->dsc_dpcd)) < 0) -+ DRM_ERROR("Failed to read DPCD register 0x%x\n", -+ DP_DSC_SUPPORT); -+ -+ DRM_DEBUG_KMS("DSC DPCD: %*ph\n", -+ (int)sizeof(intel_dp->dsc_dpcd), -+ intel_dp->dsc_dpcd); -+ -+ /* FEC is supported only on DP 1.4 */ -+ if (!intel_dp_is_edp(intel_dp) && -+ drm_dp_dpcd_readb(&intel_dp->aux, DP_FEC_CAPABILITY, -+ &intel_dp->fec_capable) < 0) -+ DRM_ERROR("Failed to read FEC DPCD register\n"); -+ -+ DRM_DEBUG_KMS("FEC CAPABILITY: %x\n", intel_dp->fec_capable); -+ } -+} -+ -+static bool -+intel_edp_init_dpcd(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = -+ to_i915(dp_to_dig_port(intel_dp)->base.base.dev); -+ -+ /* this function is meant to be called only once */ -+ WARN_ON(intel_dp->dpcd[DP_DPCD_REV] != 0); -+ -+ if (!intel_dp_read_dpcd(intel_dp)) -+ return false; -+ -+ drm_dp_read_desc(&intel_dp->aux, &intel_dp->desc, -+ drm_dp_is_branch(intel_dp->dpcd)); -+ -+ if (intel_dp->dpcd[DP_DPCD_REV] >= 0x11) -+ dev_priv->no_aux_handshake = intel_dp->dpcd[DP_MAX_DOWNSPREAD] & -+ DP_NO_AUX_HANDSHAKE_LINK_TRAINING; -+ -+ /* -+ * Read the eDP display control registers. -+ * -+ * Do this independent of DP_DPCD_DISPLAY_CONTROL_CAPABLE bit in -+ * DP_EDP_CONFIGURATION_CAP, because some buggy displays do not have it -+ * set, but require eDP 1.4+ detection (e.g. for supported link rates -+ * method). The display control registers should read zero if they're -+ * not supported anyway. -+ */ -+ if (drm_dp_dpcd_read(&intel_dp->aux, DP_EDP_DPCD_REV, -+ intel_dp->edp_dpcd, sizeof(intel_dp->edp_dpcd)) == -+ sizeof(intel_dp->edp_dpcd)) -+ DRM_DEBUG_KMS("eDP DPCD: %*ph\n", (int) sizeof(intel_dp->edp_dpcd), -+ intel_dp->edp_dpcd); -+ -+ /* -+ * This has to be called after intel_dp->edp_dpcd is filled, PSR checks -+ * for SET_POWER_CAPABLE bit in intel_dp->edp_dpcd[1] -+ */ -+ intel_psr_init_dpcd(intel_dp); -+ -+ /* Read the eDP 1.4+ supported link rates. */ -+ if (intel_dp->edp_dpcd[0] >= DP_EDP_14) { -+ __le16 sink_rates[DP_MAX_SUPPORTED_RATES]; -+ int i; -+ -+ drm_dp_dpcd_read(&intel_dp->aux, DP_SUPPORTED_LINK_RATES, -+ sink_rates, sizeof(sink_rates)); -+ -+ for (i = 0; i < ARRAY_SIZE(sink_rates); i++) { -+ int val = le16_to_cpu(sink_rates[i]); -+ -+ if (val == 0) -+ break; -+ -+ /* Value read multiplied by 200kHz gives the per-lane -+ * link rate in kHz. The source rates are, however, -+ * stored in terms of LS_Clk kHz. The full conversion -+ * back to symbols is -+ * (val * 200kHz)*(8/10 ch. encoding)*(1/8 bit to Byte) -+ */ -+ intel_dp->sink_rates[i] = (val * 200) / 10; -+ } -+ intel_dp->num_sink_rates = i; -+ } -+ -+ /* -+ * Use DP_LINK_RATE_SET if DP_SUPPORTED_LINK_RATES are available, -+ * default to DP_MAX_LINK_RATE and DP_LINK_BW_SET otherwise. -+ */ -+ if (intel_dp->num_sink_rates) -+ intel_dp->use_rate_select = true; -+ else -+ intel_dp_set_sink_rates(intel_dp); -+ -+ intel_dp_set_common_rates(intel_dp); -+ -+ /* Read the eDP DSC DPCD registers */ -+ if (INTEL_GEN(dev_priv) >= 10 || IS_GEMINILAKE(dev_priv)) -+ intel_dp_get_dsc_sink_cap(intel_dp); -+ -+ return true; -+} -+ -+ -+static bool -+intel_dp_get_dpcd(struct intel_dp *intel_dp) -+{ -+ if (!intel_dp_read_dpcd(intel_dp)) -+ return false; -+ -+ /* Don't clobber cached eDP rates. */ -+ if (!intel_dp_is_edp(intel_dp)) { -+ intel_dp_set_sink_rates(intel_dp); -+ intel_dp_set_common_rates(intel_dp); -+ } -+ -+ /* -+ * Some eDP panels do not set a valid value for sink count, that is why -+ * it don't care about read it here and in intel_edp_init_dpcd(). -+ */ -+ if (!intel_dp_is_edp(intel_dp)) { -+ u8 count; -+ ssize_t r; -+ -+ r = drm_dp_dpcd_readb(&intel_dp->aux, DP_SINK_COUNT, &count); -+ if (r < 1) -+ return false; -+ -+ /* -+ * Sink count can change between short pulse hpd hence -+ * a member variable in intel_dp will track any changes -+ * between short pulse interrupts. -+ */ -+ intel_dp->sink_count = DP_GET_SINK_COUNT(count); -+ -+ /* -+ * SINK_COUNT == 0 and DOWNSTREAM_PORT_PRESENT == 1 implies that -+ * a dongle is present but no display. Unless we require to know -+ * if a dongle is present or not, we don't need to update -+ * downstream port information. So, an early return here saves -+ * time from performing other operations which are not required. -+ */ -+ if (!intel_dp->sink_count) -+ return false; -+ } -+ -+ if (!drm_dp_is_branch(intel_dp->dpcd)) -+ return true; /* native DP sink */ -+ -+ if (intel_dp->dpcd[DP_DPCD_REV] == 0x10) -+ return true; /* no per-port downstream info */ -+ -+ if (drm_dp_dpcd_read(&intel_dp->aux, DP_DOWNSTREAM_PORT_0, -+ intel_dp->downstream_ports, -+ DP_MAX_DOWNSTREAM_PORTS) < 0) -+ return false; /* downstream port status fetch failed */ -+ -+ return true; -+} -+ -+static bool -+intel_dp_sink_can_mst(struct intel_dp *intel_dp) -+{ -+ u8 mstm_cap; -+ -+ if (intel_dp->dpcd[DP_DPCD_REV] < 0x12) -+ return false; -+ -+ if (drm_dp_dpcd_readb(&intel_dp->aux, DP_MSTM_CAP, &mstm_cap) != 1) -+ return false; -+ -+ return mstm_cap & DP_MST_CAP; -+} -+ -+static bool -+intel_dp_can_mst(struct intel_dp *intel_dp) -+{ -+ return i915_modparams.enable_dp_mst && -+ intel_dp->can_mst && -+ intel_dp_sink_can_mst(intel_dp); -+} -+ -+static void -+intel_dp_configure_mst(struct intel_dp *intel_dp) -+{ -+ struct intel_encoder *encoder = -+ &dp_to_dig_port(intel_dp)->base; -+ bool sink_can_mst = intel_dp_sink_can_mst(intel_dp); -+ -+ DRM_DEBUG_KMS("MST support? port %c: %s, sink: %s, modparam: %s\n", -+ port_name(encoder->port), yesno(intel_dp->can_mst), -+ yesno(sink_can_mst), yesno(i915_modparams.enable_dp_mst)); -+ -+ if (!intel_dp->can_mst) -+ return; -+ -+ intel_dp->is_mst = sink_can_mst && -+ i915_modparams.enable_dp_mst; -+ -+ drm_dp_mst_topology_mgr_set_mst(&intel_dp->mst_mgr, -+ intel_dp->is_mst); -+} -+ -+static bool -+intel_dp_get_sink_irq_esi(struct intel_dp *intel_dp, u8 *sink_irq_vector) -+{ -+ return drm_dp_dpcd_read(&intel_dp->aux, DP_SINK_COUNT_ESI, -+ sink_irq_vector, DP_DPRX_ESI_LEN) == -+ DP_DPRX_ESI_LEN; -+} -+ -+u16 intel_dp_dsc_get_output_bpp(int link_clock, u8 lane_count, -+ int mode_clock, int mode_hdisplay) -+{ -+ u16 bits_per_pixel, max_bpp_small_joiner_ram; -+ int i; -+ -+ /* -+ * Available Link Bandwidth(Kbits/sec) = (NumberOfLanes)* -+ * (LinkSymbolClock)* 8 * ((100-FECOverhead)/100)*(TimeSlotsPerMTP) -+ * FECOverhead = 2.4%, for SST -> TimeSlotsPerMTP is 1, -+ * for MST -> TimeSlotsPerMTP has to be calculated -+ */ -+ bits_per_pixel = (link_clock * lane_count * 8 * -+ DP_DSC_FEC_OVERHEAD_FACTOR) / -+ mode_clock; -+ -+ /* Small Joiner Check: output bpp <= joiner RAM (bits) / Horiz. width */ -+ max_bpp_small_joiner_ram = DP_DSC_MAX_SMALL_JOINER_RAM_BUFFER / -+ mode_hdisplay; -+ -+ /* -+ * Greatest allowed DSC BPP = MIN (output BPP from avaialble Link BW -+ * check, output bpp from small joiner RAM check) -+ */ -+ bits_per_pixel = min(bits_per_pixel, max_bpp_small_joiner_ram); -+ -+ /* Error out if the max bpp is less than smallest allowed valid bpp */ -+ if (bits_per_pixel < valid_dsc_bpp[0]) { -+ DRM_DEBUG_KMS("Unsupported BPP %d\n", bits_per_pixel); -+ return 0; -+ } -+ -+ /* Find the nearest match in the array of known BPPs from VESA */ -+ for (i = 0; i < ARRAY_SIZE(valid_dsc_bpp) - 1; i++) { -+ if (bits_per_pixel < valid_dsc_bpp[i + 1]) -+ break; -+ } -+ bits_per_pixel = valid_dsc_bpp[i]; -+ -+ /* -+ * Compressed BPP in U6.4 format so multiply by 16, for Gen 11, -+ * fractional part is 0 -+ */ -+ return bits_per_pixel << 4; -+} -+ -+u8 intel_dp_dsc_get_slice_count(struct intel_dp *intel_dp, -+ int mode_clock, -+ int mode_hdisplay) -+{ -+ u8 min_slice_count, i; -+ int max_slice_width; -+ -+ if (mode_clock <= DP_DSC_PEAK_PIXEL_RATE) -+ min_slice_count = DIV_ROUND_UP(mode_clock, -+ DP_DSC_MAX_ENC_THROUGHPUT_0); -+ else -+ min_slice_count = DIV_ROUND_UP(mode_clock, -+ DP_DSC_MAX_ENC_THROUGHPUT_1); -+ -+ max_slice_width = drm_dp_dsc_sink_max_slice_width(intel_dp->dsc_dpcd); -+ if (max_slice_width < DP_DSC_MIN_SLICE_WIDTH_VALUE) { -+ DRM_DEBUG_KMS("Unsupported slice width %d by DP DSC Sink device\n", -+ max_slice_width); -+ return 0; -+ } -+ /* Also take into account max slice width */ -+ min_slice_count = min_t(u8, min_slice_count, -+ DIV_ROUND_UP(mode_hdisplay, -+ max_slice_width)); -+ -+ /* Find the closest match to the valid slice count values */ -+ for (i = 0; i < ARRAY_SIZE(valid_dsc_slicecount); i++) { -+ if (valid_dsc_slicecount[i] > -+ drm_dp_dsc_sink_max_slice_count(intel_dp->dsc_dpcd, -+ false)) -+ break; -+ if (min_slice_count <= valid_dsc_slicecount[i]) -+ return valid_dsc_slicecount[i]; -+ } -+ -+ DRM_DEBUG_KMS("Unsupported Slice Count %d\n", min_slice_count); -+ return 0; -+} -+ -+static u8 intel_dp_autotest_link_training(struct intel_dp *intel_dp) -+{ -+ int status = 0; -+ int test_link_rate; -+ u8 test_lane_count, test_link_bw; -+ /* (DP CTS 1.2) -+ * 4.3.1.11 -+ */ -+ /* Read the TEST_LANE_COUNT and TEST_LINK_RTAE fields (DP CTS 3.1.4) */ -+ status = drm_dp_dpcd_readb(&intel_dp->aux, DP_TEST_LANE_COUNT, -+ &test_lane_count); -+ -+ if (status <= 0) { -+ DRM_DEBUG_KMS("Lane count read failed\n"); -+ return DP_TEST_NAK; -+ } -+ test_lane_count &= DP_MAX_LANE_COUNT_MASK; -+ -+ status = drm_dp_dpcd_readb(&intel_dp->aux, DP_TEST_LINK_RATE, -+ &test_link_bw); -+ if (status <= 0) { -+ DRM_DEBUG_KMS("Link Rate read failed\n"); -+ return DP_TEST_NAK; -+ } -+ test_link_rate = drm_dp_bw_code_to_link_rate(test_link_bw); -+ -+ /* Validate the requested link rate and lane count */ -+ if (!intel_dp_link_params_valid(intel_dp, test_link_rate, -+ test_lane_count)) -+ return DP_TEST_NAK; -+ -+ intel_dp->compliance.test_lane_count = test_lane_count; -+ intel_dp->compliance.test_link_rate = test_link_rate; -+ -+ return DP_TEST_ACK; -+} -+ -+static u8 intel_dp_autotest_video_pattern(struct intel_dp *intel_dp) -+{ -+ u8 test_pattern; -+ u8 test_misc; -+ __be16 h_width, v_height; -+ int status = 0; -+ -+ /* Read the TEST_PATTERN (DP CTS 3.1.5) */ -+ status = drm_dp_dpcd_readb(&intel_dp->aux, DP_TEST_PATTERN, -+ &test_pattern); -+ if (status <= 0) { -+ DRM_DEBUG_KMS("Test pattern read failed\n"); -+ return DP_TEST_NAK; -+ } -+ if (test_pattern != DP_COLOR_RAMP) -+ return DP_TEST_NAK; -+ -+ status = drm_dp_dpcd_read(&intel_dp->aux, DP_TEST_H_WIDTH_HI, -+ &h_width, 2); -+ if (status <= 0) { -+ DRM_DEBUG_KMS("H Width read failed\n"); -+ return DP_TEST_NAK; -+ } -+ -+ status = drm_dp_dpcd_read(&intel_dp->aux, DP_TEST_V_HEIGHT_HI, -+ &v_height, 2); -+ if (status <= 0) { -+ DRM_DEBUG_KMS("V Height read failed\n"); -+ return DP_TEST_NAK; -+ } -+ -+ status = drm_dp_dpcd_readb(&intel_dp->aux, DP_TEST_MISC0, -+ &test_misc); -+ if (status <= 0) { -+ DRM_DEBUG_KMS("TEST MISC read failed\n"); -+ return DP_TEST_NAK; -+ } -+ if ((test_misc & DP_TEST_COLOR_FORMAT_MASK) != DP_COLOR_FORMAT_RGB) -+ return DP_TEST_NAK; -+ if (test_misc & DP_TEST_DYNAMIC_RANGE_CEA) -+ return DP_TEST_NAK; -+ switch (test_misc & DP_TEST_BIT_DEPTH_MASK) { -+ case DP_TEST_BIT_DEPTH_6: -+ intel_dp->compliance.test_data.bpc = 6; -+ break; -+ case DP_TEST_BIT_DEPTH_8: -+ intel_dp->compliance.test_data.bpc = 8; -+ break; -+ default: -+ return DP_TEST_NAK; -+ } -+ -+ intel_dp->compliance.test_data.video_pattern = test_pattern; -+ intel_dp->compliance.test_data.hdisplay = be16_to_cpu(h_width); -+ intel_dp->compliance.test_data.vdisplay = be16_to_cpu(v_height); -+ /* Set test active flag here so userspace doesn't interrupt things */ -+ intel_dp->compliance.test_active = 1; -+ -+ return DP_TEST_ACK; -+} -+ -+static u8 intel_dp_autotest_edid(struct intel_dp *intel_dp) -+{ -+ u8 test_result = DP_TEST_ACK; -+ struct intel_connector *intel_connector = intel_dp->attached_connector; -+ struct drm_connector *connector = &intel_connector->base; -+ -+ if (intel_connector->detect_edid == NULL || -+ connector->edid_corrupt || -+ intel_dp->aux.i2c_defer_count > 6) { -+ /* Check EDID read for NACKs, DEFERs and corruption -+ * (DP CTS 1.2 Core r1.1) -+ * 4.2.2.4 : Failed EDID read, I2C_NAK -+ * 4.2.2.5 : Failed EDID read, I2C_DEFER -+ * 4.2.2.6 : EDID corruption detected -+ * Use failsafe mode for all cases -+ */ -+ if (intel_dp->aux.i2c_nack_count > 0 || -+ intel_dp->aux.i2c_defer_count > 0) -+ DRM_DEBUG_KMS("EDID read had %d NACKs, %d DEFERs\n", -+ intel_dp->aux.i2c_nack_count, -+ intel_dp->aux.i2c_defer_count); -+ intel_dp->compliance.test_data.edid = INTEL_DP_RESOLUTION_FAILSAFE; -+ } else { -+ struct edid *block = intel_connector->detect_edid; -+ -+ /* We have to write the checksum -+ * of the last block read -+ */ -+ block += intel_connector->detect_edid->extensions; -+ -+ if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_TEST_EDID_CHECKSUM, -+ block->checksum) <= 0) -+ DRM_DEBUG_KMS("Failed to write EDID checksum\n"); -+ -+ test_result = DP_TEST_ACK | DP_TEST_EDID_CHECKSUM_WRITE; -+ intel_dp->compliance.test_data.edid = INTEL_DP_RESOLUTION_PREFERRED; -+ } -+ -+ /* Set test active flag here so userspace doesn't interrupt things */ -+ intel_dp->compliance.test_active = 1; -+ -+ return test_result; -+} -+ -+static u8 intel_dp_autotest_phy_pattern(struct intel_dp *intel_dp) -+{ -+ u8 test_result = DP_TEST_NAK; -+ return test_result; -+} -+ -+static void intel_dp_handle_test_request(struct intel_dp *intel_dp) -+{ -+ u8 response = DP_TEST_NAK; -+ u8 request = 0; -+ int status; -+ -+ status = drm_dp_dpcd_readb(&intel_dp->aux, DP_TEST_REQUEST, &request); -+ if (status <= 0) { -+ DRM_DEBUG_KMS("Could not read test request from sink\n"); -+ goto update_status; -+ } -+ -+ switch (request) { -+ case DP_TEST_LINK_TRAINING: -+ DRM_DEBUG_KMS("LINK_TRAINING test requested\n"); -+ response = intel_dp_autotest_link_training(intel_dp); -+ break; -+ case DP_TEST_LINK_VIDEO_PATTERN: -+ DRM_DEBUG_KMS("TEST_PATTERN test requested\n"); -+ response = intel_dp_autotest_video_pattern(intel_dp); -+ break; -+ case DP_TEST_LINK_EDID_READ: -+ DRM_DEBUG_KMS("EDID test requested\n"); -+ response = intel_dp_autotest_edid(intel_dp); -+ break; -+ case DP_TEST_LINK_PHY_TEST_PATTERN: -+ DRM_DEBUG_KMS("PHY_PATTERN test requested\n"); -+ response = intel_dp_autotest_phy_pattern(intel_dp); -+ break; -+ default: -+ DRM_DEBUG_KMS("Invalid test request '%02x'\n", request); -+ break; -+ } -+ -+ if (response & DP_TEST_ACK) -+ intel_dp->compliance.test_type = request; -+ -+update_status: -+ status = drm_dp_dpcd_writeb(&intel_dp->aux, DP_TEST_RESPONSE, response); -+ if (status <= 0) -+ DRM_DEBUG_KMS("Could not write test response to sink\n"); -+} -+ -+static int -+intel_dp_check_mst_status(struct intel_dp *intel_dp) -+{ -+ bool bret; -+ -+ if (intel_dp->is_mst) { -+ u8 esi[DP_DPRX_ESI_LEN] = { 0 }; -+ int ret = 0; -+ int retry; -+ bool handled; -+ -+ WARN_ON_ONCE(intel_dp->active_mst_links < 0); -+ bret = intel_dp_get_sink_irq_esi(intel_dp, esi); -+go_again: -+ if (bret == true) { -+ -+ /* check link status - esi[10] = 0x200c */ -+ if (intel_dp->active_mst_links > 0 && -+ !drm_dp_channel_eq_ok(&esi[10], intel_dp->lane_count)) { -+ DRM_DEBUG_KMS("channel EQ not ok, retraining\n"); -+ intel_dp_start_link_train(intel_dp); -+ intel_dp_stop_link_train(intel_dp); -+ } -+ -+ DRM_DEBUG_KMS("got esi %3ph\n", esi); -+ ret = drm_dp_mst_hpd_irq(&intel_dp->mst_mgr, esi, &handled); -+ -+ if (handled) { -+ for (retry = 0; retry < 3; retry++) { -+ int wret; -+ wret = drm_dp_dpcd_write(&intel_dp->aux, -+ DP_SINK_COUNT_ESI+1, -+ &esi[1], 3); -+ if (wret == 3) { -+ break; -+ } -+ } -+ -+ bret = intel_dp_get_sink_irq_esi(intel_dp, esi); -+ if (bret == true) { -+ DRM_DEBUG_KMS("got esi2 %3ph\n", esi); -+ goto go_again; -+ } -+ } else -+ ret = 0; -+ -+ return ret; -+ } else { -+ DRM_DEBUG_KMS("failed to get ESI - device may have failed\n"); -+ intel_dp->is_mst = false; -+ drm_dp_mst_topology_mgr_set_mst(&intel_dp->mst_mgr, -+ intel_dp->is_mst); -+ } -+ } -+ return -EINVAL; -+} -+ -+static bool -+intel_dp_needs_link_retrain(struct intel_dp *intel_dp) -+{ -+ u8 link_status[DP_LINK_STATUS_SIZE]; -+ -+ if (!intel_dp->link_trained) -+ return false; -+ -+ /* -+ * While PSR source HW is enabled, it will control main-link sending -+ * frames, enabling and disabling it so trying to do a retrain will fail -+ * as the link would or not be on or it could mix training patterns -+ * and frame data at the same time causing retrain to fail. -+ * Also when exiting PSR, HW will retrain the link anyways fixing -+ * any link status error. -+ */ -+ if (intel_psr_enabled(intel_dp)) -+ return false; -+ -+ if (!intel_dp_get_link_status(intel_dp, link_status)) -+ return false; -+ -+ /* -+ * Validate the cached values of intel_dp->link_rate and -+ * intel_dp->lane_count before attempting to retrain. -+ */ -+ if (!intel_dp_link_params_valid(intel_dp, intel_dp->link_rate, -+ intel_dp->lane_count)) -+ return false; -+ -+ /* Retrain if Channel EQ or CR not ok */ -+ return !drm_dp_channel_eq_ok(link_status, intel_dp->lane_count); -+} -+ -+int intel_dp_retrain_link(struct intel_encoder *encoder, -+ struct drm_modeset_acquire_ctx *ctx) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ struct intel_connector *connector = intel_dp->attached_connector; -+ struct drm_connector_state *conn_state; -+ struct intel_crtc_state *crtc_state; -+ struct intel_crtc *crtc; -+ int ret; -+ -+ /* FIXME handle the MST connectors as well */ -+ -+ if (!connector || connector->base.status != connector_status_connected) -+ return 0; -+ -+ ret = drm_modeset_lock(&dev_priv->drm.mode_config.connection_mutex, -+ ctx); -+ if (ret) -+ return ret; -+ -+ conn_state = connector->base.state; -+ -+ crtc = to_intel_crtc(conn_state->crtc); -+ if (!crtc) -+ return 0; -+ -+ ret = drm_modeset_lock(&crtc->base.mutex, ctx); -+ if (ret) -+ return ret; -+ -+ crtc_state = to_intel_crtc_state(crtc->base.state); -+ -+ WARN_ON(!intel_crtc_has_dp_encoder(crtc_state)); -+ -+ if (!crtc_state->base.active) -+ return 0; -+ -+ if (conn_state->commit && -+ !try_wait_for_completion(&conn_state->commit->hw_done)) -+ return 0; -+ -+ if (!intel_dp_needs_link_retrain(intel_dp)) -+ return 0; -+ -+ /* Suppress underruns caused by re-training */ -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, false); -+ if (crtc_state->has_pch_encoder) -+ intel_set_pch_fifo_underrun_reporting(dev_priv, -+ intel_crtc_pch_transcoder(crtc), false); -+ -+ intel_dp_start_link_train(intel_dp); -+ intel_dp_stop_link_train(intel_dp); -+ -+ /* Keep underrun reporting disabled until things are stable */ -+ intel_wait_for_vblank(dev_priv, crtc->pipe); -+ -+ intel_set_cpu_fifo_underrun_reporting(dev_priv, crtc->pipe, true); -+ if (crtc_state->has_pch_encoder) -+ intel_set_pch_fifo_underrun_reporting(dev_priv, -+ intel_crtc_pch_transcoder(crtc), true); -+ -+ return 0; -+} -+ -+/* -+ * If display is now connected check links status, -+ * there has been known issues of link loss triggering -+ * long pulse. -+ * -+ * Some sinks (eg. ASUS PB287Q) seem to perform some -+ * weird HPD ping pong during modesets. So we can apparently -+ * end up with HPD going low during a modeset, and then -+ * going back up soon after. And once that happens we must -+ * retrain the link to get a picture. That's in case no -+ * userspace component reacted to intermittent HPD dip. -+ */ -+static bool intel_dp_hotplug(struct intel_encoder *encoder, -+ struct intel_connector *connector) -+{ -+ struct drm_modeset_acquire_ctx ctx; -+ bool changed; -+ int ret; -+ -+ changed = intel_encoder_hotplug(encoder, connector); -+ -+ drm_modeset_acquire_init(&ctx, 0); -+ -+ for (;;) { -+ ret = intel_dp_retrain_link(encoder, &ctx); -+ -+ if (ret == -EDEADLK) { -+ drm_modeset_backoff(&ctx); -+ continue; -+ } -+ -+ break; -+ } -+ -+ drm_modeset_drop_locks(&ctx); -+ drm_modeset_acquire_fini(&ctx); -+ WARN(ret, "Acquiring modeset locks failed with %i\n", ret); -+ -+ return changed; -+} -+ -+static void intel_dp_check_service_irq(struct intel_dp *intel_dp) -+{ -+ u8 val; -+ -+ if (intel_dp->dpcd[DP_DPCD_REV] < 0x11) -+ return; -+ -+ if (drm_dp_dpcd_readb(&intel_dp->aux, -+ DP_DEVICE_SERVICE_IRQ_VECTOR, &val) != 1 || !val) -+ return; -+ -+ drm_dp_dpcd_writeb(&intel_dp->aux, DP_DEVICE_SERVICE_IRQ_VECTOR, val); -+ -+ if (val & DP_AUTOMATED_TEST_REQUEST) -+ intel_dp_handle_test_request(intel_dp); -+ -+ if (val & DP_CP_IRQ) -+ intel_hdcp_handle_cp_irq(intel_dp->attached_connector); -+ -+ if (val & DP_SINK_SPECIFIC_IRQ) -+ DRM_DEBUG_DRIVER("Sink specific irq unhandled\n"); -+} -+ -+/* -+ * According to DP spec -+ * 5.1.2: -+ * 1. Read DPCD -+ * 2. Configure link according to Receiver Capabilities -+ * 3. Use Link Training from 2.5.3.3 and 3.5.1.3 -+ * 4. Check link status on receipt of hot-plug interrupt -+ * -+ * intel_dp_short_pulse - handles short pulse interrupts -+ * when full detection is not required. -+ * Returns %true if short pulse is handled and full detection -+ * is NOT required and %false otherwise. -+ */ -+static bool -+intel_dp_short_pulse(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ u8 old_sink_count = intel_dp->sink_count; -+ bool ret; -+ -+ /* -+ * Clearing compliance test variables to allow capturing -+ * of values for next automated test request. -+ */ -+ memset(&intel_dp->compliance, 0, sizeof(intel_dp->compliance)); -+ -+ /* -+ * Now read the DPCD to see if it's actually running -+ * If the current value of sink count doesn't match with -+ * the value that was stored earlier or dpcd read failed -+ * we need to do full detection -+ */ -+ ret = intel_dp_get_dpcd(intel_dp); -+ -+ if ((old_sink_count != intel_dp->sink_count) || !ret) { -+ /* No need to proceed if we are going to do full detect */ -+ return false; -+ } -+ -+ intel_dp_check_service_irq(intel_dp); -+ -+ /* Handle CEC interrupts, if any */ -+ drm_dp_cec_irq(&intel_dp->aux); -+ -+ /* defer to the hotplug work for link retraining if needed */ -+ if (intel_dp_needs_link_retrain(intel_dp)) -+ return false; -+ -+ intel_psr_short_pulse(intel_dp); -+ -+ if (intel_dp->compliance.test_type == DP_TEST_LINK_TRAINING) { -+ DRM_DEBUG_KMS("Link Training Compliance Test requested\n"); -+ /* Send a Hotplug Uevent to userspace to start modeset */ -+ drm_kms_helper_hotplug_event(&dev_priv->drm); -+ } -+ -+ return true; -+} -+ -+/* XXX this is probably wrong for multiple downstream ports */ -+static enum drm_connector_status -+intel_dp_detect_dpcd(struct intel_dp *intel_dp) -+{ -+ struct intel_lspcon *lspcon = dp_to_lspcon(intel_dp); -+ u8 *dpcd = intel_dp->dpcd; -+ u8 type; -+ -+ if (lspcon->active) -+ lspcon_resume(lspcon); -+ -+ if (!intel_dp_get_dpcd(intel_dp)) -+ return connector_status_disconnected; -+ -+ if (intel_dp_is_edp(intel_dp)) -+ return connector_status_connected; -+ -+ /* if there's no downstream port, we're done */ -+ if (!drm_dp_is_branch(dpcd)) -+ return connector_status_connected; -+ -+ /* If we're HPD-aware, SINK_COUNT changes dynamically */ -+ if (intel_dp->dpcd[DP_DPCD_REV] >= 0x11 && -+ intel_dp->downstream_ports[0] & DP_DS_PORT_HPD) { -+ -+ return intel_dp->sink_count ? -+ connector_status_connected : connector_status_disconnected; -+ } -+ -+ if (intel_dp_can_mst(intel_dp)) -+ return connector_status_connected; -+ -+ /* If no HPD, poke DDC gently */ -+ if (drm_probe_ddc(&intel_dp->aux.ddc)) -+ return connector_status_connected; -+ -+ /* Well we tried, say unknown for unreliable port types */ -+ if (intel_dp->dpcd[DP_DPCD_REV] >= 0x11) { -+ type = intel_dp->downstream_ports[0] & DP_DS_PORT_TYPE_MASK; -+ if (type == DP_DS_PORT_TYPE_VGA || -+ type == DP_DS_PORT_TYPE_NON_EDID) -+ return connector_status_unknown; -+ } else { -+ type = intel_dp->dpcd[DP_DOWNSTREAMPORT_PRESENT] & -+ DP_DWN_STRM_PORT_TYPE_MASK; -+ if (type == DP_DWN_STRM_PORT_TYPE_ANALOG || -+ type == DP_DWN_STRM_PORT_TYPE_OTHER) -+ return connector_status_unknown; -+ } -+ -+ /* Anything else is out of spec, warn and ignore */ -+ DRM_DEBUG_KMS("Broken DP branch device, ignoring\n"); -+ return connector_status_disconnected; -+} -+ -+static enum drm_connector_status -+edp_detect(struct intel_dp *intel_dp) -+{ -+ return connector_status_connected; -+} -+ -+static bool ibx_digital_port_connected(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ u32 bit; -+ -+ switch (encoder->hpd_pin) { -+ case HPD_PORT_B: -+ bit = SDE_PORTB_HOTPLUG; -+ break; -+ case HPD_PORT_C: -+ bit = SDE_PORTC_HOTPLUG; -+ break; -+ case HPD_PORT_D: -+ bit = SDE_PORTD_HOTPLUG; -+ break; -+ default: -+ MISSING_CASE(encoder->hpd_pin); -+ return false; -+ } -+ -+ return I915_READ(SDEISR) & bit; -+} -+ -+static bool cpt_digital_port_connected(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ u32 bit; -+ -+ switch (encoder->hpd_pin) { -+ case HPD_PORT_B: -+ bit = SDE_PORTB_HOTPLUG_CPT; -+ break; -+ case HPD_PORT_C: -+ bit = SDE_PORTC_HOTPLUG_CPT; -+ break; -+ case HPD_PORT_D: -+ bit = SDE_PORTD_HOTPLUG_CPT; -+ break; -+ default: -+ MISSING_CASE(encoder->hpd_pin); -+ return false; -+ } -+ -+ return I915_READ(SDEISR) & bit; -+} -+ -+static bool spt_digital_port_connected(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ u32 bit; -+ -+ switch (encoder->hpd_pin) { -+ case HPD_PORT_A: -+ bit = SDE_PORTA_HOTPLUG_SPT; -+ break; -+ case HPD_PORT_E: -+ bit = SDE_PORTE_HOTPLUG_SPT; -+ break; -+ default: -+ return cpt_digital_port_connected(encoder); -+ } -+ -+ return I915_READ(SDEISR) & bit; -+} -+ -+static bool g4x_digital_port_connected(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ u32 bit; -+ -+ switch (encoder->hpd_pin) { -+ case HPD_PORT_B: -+ bit = PORTB_HOTPLUG_LIVE_STATUS_G4X; -+ break; -+ case HPD_PORT_C: -+ bit = PORTC_HOTPLUG_LIVE_STATUS_G4X; -+ break; -+ case HPD_PORT_D: -+ bit = PORTD_HOTPLUG_LIVE_STATUS_G4X; -+ break; -+ default: -+ MISSING_CASE(encoder->hpd_pin); -+ return false; -+ } -+ -+ return I915_READ(PORT_HOTPLUG_STAT) & bit; -+} -+ -+static bool gm45_digital_port_connected(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ u32 bit; -+ -+ switch (encoder->hpd_pin) { -+ case HPD_PORT_B: -+ bit = PORTB_HOTPLUG_LIVE_STATUS_GM45; -+ break; -+ case HPD_PORT_C: -+ bit = PORTC_HOTPLUG_LIVE_STATUS_GM45; -+ break; -+ case HPD_PORT_D: -+ bit = PORTD_HOTPLUG_LIVE_STATUS_GM45; -+ break; -+ default: -+ MISSING_CASE(encoder->hpd_pin); -+ return false; -+ } -+ -+ return I915_READ(PORT_HOTPLUG_STAT) & bit; -+} -+ -+static bool ilk_digital_port_connected(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ -+ if (encoder->hpd_pin == HPD_PORT_A) -+ return I915_READ(DEISR) & DE_DP_A_HOTPLUG; -+ else -+ return ibx_digital_port_connected(encoder); -+} -+ -+static bool snb_digital_port_connected(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ -+ if (encoder->hpd_pin == HPD_PORT_A) -+ return I915_READ(DEISR) & DE_DP_A_HOTPLUG; -+ else -+ return cpt_digital_port_connected(encoder); -+} -+ -+static bool ivb_digital_port_connected(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ -+ if (encoder->hpd_pin == HPD_PORT_A) -+ return I915_READ(DEISR) & DE_DP_A_HOTPLUG_IVB; -+ else -+ return cpt_digital_port_connected(encoder); -+} -+ -+static bool bdw_digital_port_connected(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ -+ if (encoder->hpd_pin == HPD_PORT_A) -+ return I915_READ(GEN8_DE_PORT_ISR) & GEN8_PORT_DP_A_HOTPLUG; -+ else -+ return cpt_digital_port_connected(encoder); -+} -+ -+static bool bxt_digital_port_connected(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ u32 bit; -+ -+ switch (encoder->hpd_pin) { -+ case HPD_PORT_A: -+ bit = BXT_DE_PORT_HP_DDIA; -+ break; -+ case HPD_PORT_B: -+ bit = BXT_DE_PORT_HP_DDIB; -+ break; -+ case HPD_PORT_C: -+ bit = BXT_DE_PORT_HP_DDIC; -+ break; -+ default: -+ MISSING_CASE(encoder->hpd_pin); -+ return false; -+ } -+ -+ return I915_READ(GEN8_DE_PORT_ISR) & bit; -+} -+ -+static bool icl_combo_port_connected(struct drm_i915_private *dev_priv, -+ struct intel_digital_port *intel_dig_port) -+{ -+ enum port port = intel_dig_port->base.port; -+ -+ return I915_READ(SDEISR) & SDE_DDI_HOTPLUG_ICP(port); -+} -+ -+static const char *tc_type_name(enum tc_port_type type) -+{ -+ static const char * const names[] = { -+ [TC_PORT_UNKNOWN] = "unknown", -+ [TC_PORT_LEGACY] = "legacy", -+ [TC_PORT_TYPEC] = "typec", -+ [TC_PORT_TBT] = "tbt", -+ }; -+ -+ if (WARN_ON(type >= ARRAY_SIZE(names))) -+ type = TC_PORT_UNKNOWN; -+ -+ return names[type]; -+} -+ -+static void icl_update_tc_port_type(struct drm_i915_private *dev_priv, -+ struct intel_digital_port *intel_dig_port, -+ bool is_legacy, bool is_typec, bool is_tbt) -+{ -+ enum port port = intel_dig_port->base.port; -+ enum tc_port_type old_type = intel_dig_port->tc_type; -+ -+ WARN_ON(is_legacy + is_typec + is_tbt != 1); -+ -+ if (is_legacy) -+ intel_dig_port->tc_type = TC_PORT_LEGACY; -+ else if (is_typec) -+ intel_dig_port->tc_type = TC_PORT_TYPEC; -+ else if (is_tbt) -+ intel_dig_port->tc_type = TC_PORT_TBT; -+ else -+ return; -+ -+ /* Types are not supposed to be changed at runtime. */ -+ WARN_ON(old_type != TC_PORT_UNKNOWN && -+ old_type != intel_dig_port->tc_type); -+ -+ if (old_type != intel_dig_port->tc_type) -+ DRM_DEBUG_KMS("Port %c has TC type %s\n", port_name(port), -+ tc_type_name(intel_dig_port->tc_type)); -+} -+ -+/* -+ * This function implements the first part of the Connect Flow described by our -+ * specification, Gen11 TypeC Programming chapter. The rest of the flow (reading -+ * lanes, EDID, etc) is done as needed in the typical places. -+ * -+ * Unlike the other ports, type-C ports are not available to use as soon as we -+ * get a hotplug. The type-C PHYs can be shared between multiple controllers: -+ * display, USB, etc. As a result, handshaking through FIA is required around -+ * connect and disconnect to cleanly transfer ownership with the controller and -+ * set the type-C power state. -+ * -+ * We could opt to only do the connect flow when we actually try to use the AUX -+ * channels or do a modeset, then immediately run the disconnect flow after -+ * usage, but there are some implications on this for a dynamic environment: -+ * things may go away or change behind our backs. So for now our driver is -+ * always trying to acquire ownership of the controller as soon as it gets an -+ * interrupt (or polls state and sees a port is connected) and only gives it -+ * back when it sees a disconnect. Implementation of a more fine-grained model -+ * will require a lot of coordination with user space and thorough testing for -+ * the extra possible cases. -+ */ -+static bool icl_tc_phy_connect(struct drm_i915_private *dev_priv, -+ struct intel_digital_port *dig_port) -+{ -+ enum tc_port tc_port = intel_port_to_tc(dev_priv, dig_port->base.port); -+ u32 val; -+ -+ if (dig_port->tc_type != TC_PORT_LEGACY && -+ dig_port->tc_type != TC_PORT_TYPEC) -+ return true; -+ -+ val = I915_READ(PORT_TX_DFLEXDPPMS); -+ if (!(val & DP_PHY_MODE_STATUS_COMPLETED(tc_port))) { -+ DRM_DEBUG_KMS("DP PHY for TC port %d not ready\n", tc_port); -+ WARN_ON(dig_port->tc_legacy_port); -+ return false; -+ } -+ -+ /* -+ * This function may be called many times in a row without an HPD event -+ * in between, so try to avoid the write when we can. -+ */ -+ val = I915_READ(PORT_TX_DFLEXDPCSSS); -+ if (!(val & DP_PHY_MODE_STATUS_NOT_SAFE(tc_port))) { -+ val |= DP_PHY_MODE_STATUS_NOT_SAFE(tc_port); -+ I915_WRITE(PORT_TX_DFLEXDPCSSS, val); -+ } -+ -+ /* -+ * Now we have to re-check the live state, in case the port recently -+ * became disconnected. Not necessary for legacy mode. -+ */ -+ if (dig_port->tc_type == TC_PORT_TYPEC && -+ !(I915_READ(PORT_TX_DFLEXDPSP) & TC_LIVE_STATE_TC(tc_port))) { -+ DRM_DEBUG_KMS("TC PHY %d sudden disconnect.\n", tc_port); -+ icl_tc_phy_disconnect(dev_priv, dig_port); -+ return false; -+ } -+ -+ return true; -+} -+ -+/* -+ * See the comment at the connect function. This implements the Disconnect -+ * Flow. -+ */ -+void icl_tc_phy_disconnect(struct drm_i915_private *dev_priv, -+ struct intel_digital_port *dig_port) -+{ -+ enum tc_port tc_port = intel_port_to_tc(dev_priv, dig_port->base.port); -+ -+ if (dig_port->tc_type == TC_PORT_UNKNOWN) -+ return; -+ -+ /* -+ * TBT disconnection flow is read the live status, what was done in -+ * caller. -+ */ -+ if (dig_port->tc_type == TC_PORT_TYPEC || -+ dig_port->tc_type == TC_PORT_LEGACY) { -+ u32 val; -+ -+ val = I915_READ(PORT_TX_DFLEXDPCSSS); -+ val &= ~DP_PHY_MODE_STATUS_NOT_SAFE(tc_port); -+ I915_WRITE(PORT_TX_DFLEXDPCSSS, val); -+ } -+ -+ DRM_DEBUG_KMS("Port %c TC type %s disconnected\n", -+ port_name(dig_port->base.port), -+ tc_type_name(dig_port->tc_type)); -+ -+ dig_port->tc_type = TC_PORT_UNKNOWN; -+} -+ -+/* -+ * The type-C ports are different because even when they are connected, they may -+ * not be available/usable by the graphics driver: see the comment on -+ * icl_tc_phy_connect(). So in our driver instead of adding the additional -+ * concept of "usable" and make everything check for "connected and usable" we -+ * define a port as "connected" when it is not only connected, but also when it -+ * is usable by the rest of the driver. That maintains the old assumption that -+ * connected ports are usable, and avoids exposing to the users objects they -+ * can't really use. -+ */ -+static bool icl_tc_port_connected(struct drm_i915_private *dev_priv, -+ struct intel_digital_port *intel_dig_port) -+{ -+ enum port port = intel_dig_port->base.port; -+ enum tc_port tc_port = intel_port_to_tc(dev_priv, port); -+ bool is_legacy, is_typec, is_tbt; -+ u32 dpsp; -+ -+ /* -+ * WARN if we got a legacy port HPD, but VBT didn't mark the port as -+ * legacy. Treat the port as legacy from now on. -+ */ -+ if (WARN_ON(!intel_dig_port->tc_legacy_port && -+ I915_READ(SDEISR) & SDE_TC_HOTPLUG_ICP(tc_port))) -+ intel_dig_port->tc_legacy_port = true; -+ is_legacy = intel_dig_port->tc_legacy_port; -+ -+ /* -+ * The spec says we shouldn't be using the ISR bits for detecting -+ * between TC and TBT. We should use DFLEXDPSP. -+ */ -+ dpsp = I915_READ(PORT_TX_DFLEXDPSP); -+ is_typec = dpsp & TC_LIVE_STATE_TC(tc_port); -+ is_tbt = dpsp & TC_LIVE_STATE_TBT(tc_port); -+ -+ if (!is_legacy && !is_typec && !is_tbt) { -+ icl_tc_phy_disconnect(dev_priv, intel_dig_port); -+ -+ return false; -+ } -+ -+ icl_update_tc_port_type(dev_priv, intel_dig_port, is_legacy, is_typec, -+ is_tbt); -+ -+ if (!icl_tc_phy_connect(dev_priv, intel_dig_port)) -+ return false; -+ -+ return true; -+} -+ -+static bool icl_digital_port_connected(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_digital_port *dig_port = enc_to_dig_port(&encoder->base); -+ -+ if (intel_port_is_combophy(dev_priv, encoder->port)) -+ return icl_combo_port_connected(dev_priv, dig_port); -+ else if (intel_port_is_tc(dev_priv, encoder->port)) -+ return icl_tc_port_connected(dev_priv, dig_port); -+ else -+ MISSING_CASE(encoder->hpd_pin); -+ -+ return false; -+} -+ -+/* -+ * intel_digital_port_connected - is the specified port connected? -+ * @encoder: intel_encoder -+ * -+ * In cases where there's a connector physically connected but it can't be used -+ * by our hardware we also return false, since the rest of the driver should -+ * pretty much treat the port as disconnected. This is relevant for type-C -+ * (starting on ICL) where there's ownership involved. -+ * -+ * Return %true if port is connected, %false otherwise. -+ */ -+bool intel_digital_port_connected(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ -+ if (HAS_GMCH(dev_priv)) { -+ if (IS_GM45(dev_priv)) -+ return gm45_digital_port_connected(encoder); -+ else -+ return g4x_digital_port_connected(encoder); -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 11) -+ return icl_digital_port_connected(encoder); -+ else if (IS_GEN(dev_priv, 10) || IS_GEN9_BC(dev_priv)) -+ return spt_digital_port_connected(encoder); -+ else if (IS_GEN9_LP(dev_priv)) -+ return bxt_digital_port_connected(encoder); -+ else if (IS_GEN(dev_priv, 8)) -+ return bdw_digital_port_connected(encoder); -+ else if (IS_GEN(dev_priv, 7)) -+ return ivb_digital_port_connected(encoder); -+ else if (IS_GEN(dev_priv, 6)) -+ return snb_digital_port_connected(encoder); -+ else if (IS_GEN(dev_priv, 5)) -+ return ilk_digital_port_connected(encoder); -+ -+ MISSING_CASE(INTEL_GEN(dev_priv)); -+ return false; -+} -+ -+static struct edid * -+intel_dp_get_edid(struct intel_dp *intel_dp) -+{ -+ struct intel_connector *intel_connector = intel_dp->attached_connector; -+ -+ /* use cached edid if we have one */ -+ if (intel_connector->edid) { -+ /* invalid edid */ -+ if (IS_ERR(intel_connector->edid)) -+ return NULL; -+ -+ return drm_edid_duplicate(intel_connector->edid); -+ } else -+ return drm_get_edid(&intel_connector->base, -+ &intel_dp->aux.ddc); -+} -+ -+static void -+intel_dp_set_edid(struct intel_dp *intel_dp) -+{ -+ struct intel_connector *intel_connector = intel_dp->attached_connector; -+ struct edid *edid; -+ -+ intel_dp_unset_edid(intel_dp); -+ edid = intel_dp_get_edid(intel_dp); -+ intel_connector->detect_edid = edid; -+ -+ intel_dp->has_audio = drm_detect_monitor_audio(edid); -+ drm_dp_cec_set_edid(&intel_dp->aux, edid); -+} -+ -+static void -+intel_dp_unset_edid(struct intel_dp *intel_dp) -+{ -+ struct intel_connector *intel_connector = intel_dp->attached_connector; -+ -+ drm_dp_cec_unset_edid(&intel_dp->aux); -+ kfree(intel_connector->detect_edid); -+ intel_connector->detect_edid = NULL; -+ -+ intel_dp->has_audio = false; -+} -+ -+static int -+intel_dp_detect(struct drm_connector *connector, -+ struct drm_modeset_acquire_ctx *ctx, -+ bool force) -+{ -+ struct drm_i915_private *dev_priv = to_i915(connector->dev); -+ struct intel_dp *intel_dp = intel_attached_dp(connector); -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ struct intel_encoder *encoder = &dig_port->base; -+ enum drm_connector_status status; -+ enum intel_display_power_domain aux_domain = -+ intel_aux_power_domain(dig_port); -+ intel_wakeref_t wakeref; -+ -+ DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", -+ connector->base.id, connector->name); -+ WARN_ON(!drm_modeset_is_locked(&dev_priv->drm.mode_config.connection_mutex)); -+ -+ wakeref = intel_display_power_get(dev_priv, aux_domain); -+ -+ /* Can't disconnect eDP */ -+ if (intel_dp_is_edp(intel_dp)) -+ status = edp_detect(intel_dp); -+ else if (intel_digital_port_connected(encoder)) -+ status = intel_dp_detect_dpcd(intel_dp); -+ else -+ status = connector_status_disconnected; -+ -+ if (status == connector_status_disconnected) { -+ memset(&intel_dp->compliance, 0, sizeof(intel_dp->compliance)); -+ memset(intel_dp->dsc_dpcd, 0, sizeof(intel_dp->dsc_dpcd)); -+ -+ if (intel_dp->is_mst) { -+ DRM_DEBUG_KMS("MST device may have disappeared %d vs %d\n", -+ intel_dp->is_mst, -+ intel_dp->mst_mgr.mst_state); -+ intel_dp->is_mst = false; -+ drm_dp_mst_topology_mgr_set_mst(&intel_dp->mst_mgr, -+ intel_dp->is_mst); -+ } -+ -+ goto out; -+ } -+ -+ if (intel_dp->reset_link_params) { -+ /* Initial max link lane count */ -+ intel_dp->max_link_lane_count = intel_dp_max_common_lane_count(intel_dp); -+ -+ /* Initial max link rate */ -+ intel_dp->max_link_rate = intel_dp_max_common_rate(intel_dp); -+ -+ intel_dp->reset_link_params = false; -+ } -+ -+ intel_dp_print_rates(intel_dp); -+ -+ /* Read DP Sink DSC Cap DPCD regs for DP v1.4 */ -+ if (INTEL_GEN(dev_priv) >= 11) -+ intel_dp_get_dsc_sink_cap(intel_dp); -+ -+ drm_dp_read_desc(&intel_dp->aux, &intel_dp->desc, -+ drm_dp_is_branch(intel_dp->dpcd)); -+ -+ intel_dp_configure_mst(intel_dp); -+ -+ if (intel_dp->is_mst) { -+ /* -+ * If we are in MST mode then this connector -+ * won't appear connected or have anything -+ * with EDID on it -+ */ -+ status = connector_status_disconnected; -+ goto out; -+ } -+ -+ /* -+ * Some external monitors do not signal loss of link synchronization -+ * with an IRQ_HPD, so force a link status check. -+ */ -+ if (!intel_dp_is_edp(intel_dp)) { -+ int ret; -+ -+ ret = intel_dp_retrain_link(encoder, ctx); -+ if (ret) { -+ intel_display_power_put(dev_priv, aux_domain, wakeref); -+ return ret; -+ } -+ } -+ -+ /* -+ * Clearing NACK and defer counts to get their exact values -+ * while reading EDID which are required by Compliance tests -+ * 4.2.2.4 and 4.2.2.5 -+ */ -+ intel_dp->aux.i2c_nack_count = 0; -+ intel_dp->aux.i2c_defer_count = 0; -+ -+ intel_dp_set_edid(intel_dp); -+ if (intel_dp_is_edp(intel_dp) || -+ to_intel_connector(connector)->detect_edid) -+ status = connector_status_connected; -+ -+ intel_dp_check_service_irq(intel_dp); -+ -+out: -+ if (status != connector_status_connected && !intel_dp->is_mst) -+ intel_dp_unset_edid(intel_dp); -+ -+ intel_display_power_put(dev_priv, aux_domain, wakeref); -+ return status; -+} -+ -+static void -+intel_dp_force(struct drm_connector *connector) -+{ -+ struct intel_dp *intel_dp = intel_attached_dp(connector); -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ struct intel_encoder *intel_encoder = &dig_port->base; -+ struct drm_i915_private *dev_priv = to_i915(intel_encoder->base.dev); -+ enum intel_display_power_domain aux_domain = -+ intel_aux_power_domain(dig_port); -+ intel_wakeref_t wakeref; -+ -+ DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", -+ connector->base.id, connector->name); -+ intel_dp_unset_edid(intel_dp); -+ -+ if (connector->status != connector_status_connected) -+ return; -+ -+ wakeref = intel_display_power_get(dev_priv, aux_domain); -+ -+ intel_dp_set_edid(intel_dp); -+ -+ intel_display_power_put(dev_priv, aux_domain, wakeref); -+} -+ -+static int intel_dp_get_modes(struct drm_connector *connector) -+{ -+ struct intel_connector *intel_connector = to_intel_connector(connector); -+ struct edid *edid; -+ -+ edid = intel_connector->detect_edid; -+ if (edid) { -+ int ret = intel_connector_update_modes(connector, edid); -+ if (ret) -+ return ret; -+ } -+ -+ /* if eDP has no EDID, fall back to fixed mode */ -+ if (intel_dp_is_edp(intel_attached_dp(connector)) && -+ intel_connector->panel.fixed_mode) { -+ struct drm_display_mode *mode; -+ -+ mode = drm_mode_duplicate(connector->dev, -+ intel_connector->panel.fixed_mode); -+ if (mode) { -+ drm_mode_probed_add(connector, mode); -+ return 1; -+ } -+ } -+ -+ return 0; -+} -+ -+static int -+intel_dp_connector_register(struct drm_connector *connector) -+{ -+ struct intel_dp *intel_dp = intel_attached_dp(connector); -+ struct drm_device *dev = connector->dev; -+ int ret; -+ -+ ret = intel_connector_register(connector); -+ if (ret) -+ return ret; -+ -+ i915_debugfs_connector_add(connector); -+ -+ DRM_DEBUG_KMS("registering %s bus for %s\n", -+ intel_dp->aux.name, connector->kdev->kobj.name); -+ -+ intel_dp->aux.dev = connector->kdev; -+ ret = drm_dp_aux_register(&intel_dp->aux); -+ if (!ret) -+ drm_dp_cec_register_connector(&intel_dp->aux, -+ connector->name, dev->dev); -+ return ret; -+} -+ -+static void -+intel_dp_connector_unregister(struct drm_connector *connector) -+{ -+ struct intel_dp *intel_dp = intel_attached_dp(connector); -+ -+ drm_dp_cec_unregister_connector(&intel_dp->aux); -+ drm_dp_aux_unregister(&intel_dp->aux); -+ intel_connector_unregister(connector); -+} -+ -+void intel_dp_encoder_flush_work(struct drm_encoder *encoder) -+{ -+ struct intel_digital_port *intel_dig_port = enc_to_dig_port(encoder); -+ struct intel_dp *intel_dp = &intel_dig_port->dp; -+ -+ intel_dp_mst_encoder_cleanup(intel_dig_port); -+ if (intel_dp_is_edp(intel_dp)) { -+ intel_wakeref_t wakeref; -+ -+ cancel_delayed_work_sync(&intel_dp->panel_vdd_work); -+ /* -+ * vdd might still be enabled do to the delayed vdd off. -+ * Make sure vdd is actually turned off here. -+ */ -+ with_pps_lock(intel_dp, wakeref) -+ edp_panel_vdd_off_sync(intel_dp); -+ -+ if (intel_dp->edp_notifier.notifier_call) { -+ unregister_reboot_notifier(&intel_dp->edp_notifier); -+ intel_dp->edp_notifier.notifier_call = NULL; -+ } -+ } -+ -+ intel_dp_aux_fini(intel_dp); -+} -+ -+static void intel_dp_encoder_destroy(struct drm_encoder *encoder) -+{ -+ intel_dp_encoder_flush_work(encoder); -+ -+ drm_encoder_cleanup(encoder); -+ kfree(enc_to_dig_port(encoder)); -+} -+ -+void intel_dp_encoder_suspend(struct intel_encoder *intel_encoder) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(&intel_encoder->base); -+ intel_wakeref_t wakeref; -+ -+ if (!intel_dp_is_edp(intel_dp)) -+ return; -+ -+ /* -+ * vdd might still be enabled do to the delayed vdd off. -+ * Make sure vdd is actually turned off here. -+ */ -+ cancel_delayed_work_sync(&intel_dp->panel_vdd_work); -+ with_pps_lock(intel_dp, wakeref) -+ edp_panel_vdd_off_sync(intel_dp); -+} -+ -+static void intel_dp_hdcp_wait_for_cp_irq(struct intel_hdcp *hdcp, int timeout) -+{ -+ long ret; -+ -+#define C (hdcp->cp_irq_count_cached != atomic_read(&hdcp->cp_irq_count)) -+ ret = wait_event_interruptible_timeout(hdcp->cp_irq_queue, C, -+ msecs_to_jiffies(timeout)); -+ -+ if (!ret) -+ DRM_DEBUG_KMS("Timedout at waiting for CP_IRQ\n"); -+} -+ -+static -+int intel_dp_hdcp_write_an_aksv(struct intel_digital_port *intel_dig_port, -+ u8 *an) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(&intel_dig_port->base.base); -+ static const struct drm_dp_aux_msg msg = { -+ .request = DP_AUX_NATIVE_WRITE, -+ .address = DP_AUX_HDCP_AKSV, -+ .size = DRM_HDCP_KSV_LEN, -+ }; -+ u8 txbuf[HEADER_SIZE + DRM_HDCP_KSV_LEN] = {}, rxbuf[2], reply = 0; -+ ssize_t dpcd_ret; -+ int ret; -+ -+ /* Output An first, that's easy */ -+ dpcd_ret = drm_dp_dpcd_write(&intel_dig_port->dp.aux, DP_AUX_HDCP_AN, -+ an, DRM_HDCP_AN_LEN); -+ if (dpcd_ret != DRM_HDCP_AN_LEN) { -+ DRM_DEBUG_KMS("Failed to write An over DP/AUX (%zd)\n", -+ dpcd_ret); -+ return dpcd_ret >= 0 ? -EIO : dpcd_ret; -+ } -+ -+ /* -+ * Since Aksv is Oh-So-Secret, we can't access it in software. So in -+ * order to get it on the wire, we need to create the AUX header as if -+ * we were writing the data, and then tickle the hardware to output the -+ * data once the header is sent out. -+ */ -+ intel_dp_aux_header(txbuf, &msg); -+ -+ ret = intel_dp_aux_xfer(intel_dp, txbuf, HEADER_SIZE + msg.size, -+ rxbuf, sizeof(rxbuf), -+ DP_AUX_CH_CTL_AUX_AKSV_SELECT); -+ if (ret < 0) { -+ DRM_DEBUG_KMS("Write Aksv over DP/AUX failed (%d)\n", ret); -+ return ret; -+ } else if (ret == 0) { -+ DRM_DEBUG_KMS("Aksv write over DP/AUX was empty\n"); -+ return -EIO; -+ } -+ -+ reply = (rxbuf[0] >> 4) & DP_AUX_NATIVE_REPLY_MASK; -+ if (reply != DP_AUX_NATIVE_REPLY_ACK) { -+ DRM_DEBUG_KMS("Aksv write: no DP_AUX_NATIVE_REPLY_ACK %x\n", -+ reply); -+ return -EIO; -+ } -+ return 0; -+} -+ -+static int intel_dp_hdcp_read_bksv(struct intel_digital_port *intel_dig_port, -+ u8 *bksv) -+{ -+ ssize_t ret; -+ ret = drm_dp_dpcd_read(&intel_dig_port->dp.aux, DP_AUX_HDCP_BKSV, bksv, -+ DRM_HDCP_KSV_LEN); -+ if (ret != DRM_HDCP_KSV_LEN) { -+ DRM_DEBUG_KMS("Read Bksv from DP/AUX failed (%zd)\n", ret); -+ return ret >= 0 ? -EIO : ret; -+ } -+ return 0; -+} -+ -+static int intel_dp_hdcp_read_bstatus(struct intel_digital_port *intel_dig_port, -+ u8 *bstatus) -+{ -+ ssize_t ret; -+ /* -+ * For some reason the HDMI and DP HDCP specs call this register -+ * definition by different names. In the HDMI spec, it's called BSTATUS, -+ * but in DP it's called BINFO. -+ */ -+ ret = drm_dp_dpcd_read(&intel_dig_port->dp.aux, DP_AUX_HDCP_BINFO, -+ bstatus, DRM_HDCP_BSTATUS_LEN); -+ if (ret != DRM_HDCP_BSTATUS_LEN) { -+ DRM_DEBUG_KMS("Read bstatus from DP/AUX failed (%zd)\n", ret); -+ return ret >= 0 ? -EIO : ret; -+ } -+ return 0; -+} -+ -+static -+int intel_dp_hdcp_read_bcaps(struct intel_digital_port *intel_dig_port, -+ u8 *bcaps) -+{ -+ ssize_t ret; -+ -+ ret = drm_dp_dpcd_read(&intel_dig_port->dp.aux, DP_AUX_HDCP_BCAPS, -+ bcaps, 1); -+ if (ret != 1) { -+ DRM_DEBUG_KMS("Read bcaps from DP/AUX failed (%zd)\n", ret); -+ return ret >= 0 ? -EIO : ret; -+ } -+ -+ return 0; -+} -+ -+static -+int intel_dp_hdcp_repeater_present(struct intel_digital_port *intel_dig_port, -+ bool *repeater_present) -+{ -+ ssize_t ret; -+ u8 bcaps; -+ -+ ret = intel_dp_hdcp_read_bcaps(intel_dig_port, &bcaps); -+ if (ret) -+ return ret; -+ -+ *repeater_present = bcaps & DP_BCAPS_REPEATER_PRESENT; -+ return 0; -+} -+ -+static -+int intel_dp_hdcp_read_ri_prime(struct intel_digital_port *intel_dig_port, -+ u8 *ri_prime) -+{ -+ ssize_t ret; -+ ret = drm_dp_dpcd_read(&intel_dig_port->dp.aux, DP_AUX_HDCP_RI_PRIME, -+ ri_prime, DRM_HDCP_RI_LEN); -+ if (ret != DRM_HDCP_RI_LEN) { -+ DRM_DEBUG_KMS("Read Ri' from DP/AUX failed (%zd)\n", ret); -+ return ret >= 0 ? -EIO : ret; -+ } -+ return 0; -+} -+ -+static -+int intel_dp_hdcp_read_ksv_ready(struct intel_digital_port *intel_dig_port, -+ bool *ksv_ready) -+{ -+ ssize_t ret; -+ u8 bstatus; -+ ret = drm_dp_dpcd_read(&intel_dig_port->dp.aux, DP_AUX_HDCP_BSTATUS, -+ &bstatus, 1); -+ if (ret != 1) { -+ DRM_DEBUG_KMS("Read bstatus from DP/AUX failed (%zd)\n", ret); -+ return ret >= 0 ? -EIO : ret; -+ } -+ *ksv_ready = bstatus & DP_BSTATUS_READY; -+ return 0; -+} -+ -+static -+int intel_dp_hdcp_read_ksv_fifo(struct intel_digital_port *intel_dig_port, -+ int num_downstream, u8 *ksv_fifo) -+{ -+ ssize_t ret; -+ int i; -+ -+ /* KSV list is read via 15 byte window (3 entries @ 5 bytes each) */ -+ for (i = 0; i < num_downstream; i += 3) { -+ size_t len = min(num_downstream - i, 3) * DRM_HDCP_KSV_LEN; -+ ret = drm_dp_dpcd_read(&intel_dig_port->dp.aux, -+ DP_AUX_HDCP_KSV_FIFO, -+ ksv_fifo + i * DRM_HDCP_KSV_LEN, -+ len); -+ if (ret != len) { -+ DRM_DEBUG_KMS("Read ksv[%d] from DP/AUX failed (%zd)\n", -+ i, ret); -+ return ret >= 0 ? -EIO : ret; -+ } -+ } -+ return 0; -+} -+ -+static -+int intel_dp_hdcp_read_v_prime_part(struct intel_digital_port *intel_dig_port, -+ int i, u32 *part) -+{ -+ ssize_t ret; -+ -+ if (i >= DRM_HDCP_V_PRIME_NUM_PARTS) -+ return -EINVAL; -+ -+ ret = drm_dp_dpcd_read(&intel_dig_port->dp.aux, -+ DP_AUX_HDCP_V_PRIME(i), part, -+ DRM_HDCP_V_PRIME_PART_LEN); -+ if (ret != DRM_HDCP_V_PRIME_PART_LEN) { -+ DRM_DEBUG_KMS("Read v'[%d] from DP/AUX failed (%zd)\n", i, ret); -+ return ret >= 0 ? -EIO : ret; -+ } -+ return 0; -+} -+ -+static -+int intel_dp_hdcp_toggle_signalling(struct intel_digital_port *intel_dig_port, -+ bool enable) -+{ -+ /* Not used for single stream DisplayPort setups */ -+ return 0; -+} -+ -+static -+bool intel_dp_hdcp_check_link(struct intel_digital_port *intel_dig_port) -+{ -+ ssize_t ret; -+ u8 bstatus; -+ -+ ret = drm_dp_dpcd_read(&intel_dig_port->dp.aux, DP_AUX_HDCP_BSTATUS, -+ &bstatus, 1); -+ if (ret != 1) { -+ DRM_DEBUG_KMS("Read bstatus from DP/AUX failed (%zd)\n", ret); -+ return false; -+ } -+ -+ return !(bstatus & (DP_BSTATUS_LINK_FAILURE | DP_BSTATUS_REAUTH_REQ)); -+} -+ -+static -+int intel_dp_hdcp_capable(struct intel_digital_port *intel_dig_port, -+ bool *hdcp_capable) -+{ -+ ssize_t ret; -+ u8 bcaps; -+ -+ ret = intel_dp_hdcp_read_bcaps(intel_dig_port, &bcaps); -+ if (ret) -+ return ret; -+ -+ *hdcp_capable = bcaps & DP_BCAPS_HDCP_CAPABLE; -+ return 0; -+} -+ -+struct hdcp2_dp_errata_stream_type { -+ u8 msg_id; -+ u8 stream_type; -+} __packed; -+ -+static struct hdcp2_dp_msg_data { -+ u8 msg_id; -+ u32 offset; -+ bool msg_detectable; -+ u32 timeout; -+ u32 timeout2; /* Added for non_paired situation */ -+ } hdcp2_msg_data[] = { -+ {HDCP_2_2_AKE_INIT, DP_HDCP_2_2_AKE_INIT_OFFSET, false, 0, 0}, -+ {HDCP_2_2_AKE_SEND_CERT, DP_HDCP_2_2_AKE_SEND_CERT_OFFSET, -+ false, HDCP_2_2_CERT_TIMEOUT_MS, 0}, -+ {HDCP_2_2_AKE_NO_STORED_KM, DP_HDCP_2_2_AKE_NO_STORED_KM_OFFSET, -+ false, 0, 0}, -+ {HDCP_2_2_AKE_STORED_KM, DP_HDCP_2_2_AKE_STORED_KM_OFFSET, -+ false, 0, 0}, -+ {HDCP_2_2_AKE_SEND_HPRIME, DP_HDCP_2_2_AKE_SEND_HPRIME_OFFSET, -+ true, HDCP_2_2_HPRIME_PAIRED_TIMEOUT_MS, -+ HDCP_2_2_HPRIME_NO_PAIRED_TIMEOUT_MS}, -+ {HDCP_2_2_AKE_SEND_PAIRING_INFO, -+ DP_HDCP_2_2_AKE_SEND_PAIRING_INFO_OFFSET, true, -+ HDCP_2_2_PAIRING_TIMEOUT_MS, 0}, -+ {HDCP_2_2_LC_INIT, DP_HDCP_2_2_LC_INIT_OFFSET, false, 0, 0}, -+ {HDCP_2_2_LC_SEND_LPRIME, DP_HDCP_2_2_LC_SEND_LPRIME_OFFSET, -+ false, HDCP_2_2_DP_LPRIME_TIMEOUT_MS, 0}, -+ {HDCP_2_2_SKE_SEND_EKS, DP_HDCP_2_2_SKE_SEND_EKS_OFFSET, false, -+ 0, 0}, -+ {HDCP_2_2_REP_SEND_RECVID_LIST, -+ DP_HDCP_2_2_REP_SEND_RECVID_LIST_OFFSET, true, -+ HDCP_2_2_RECVID_LIST_TIMEOUT_MS, 0}, -+ {HDCP_2_2_REP_SEND_ACK, DP_HDCP_2_2_REP_SEND_ACK_OFFSET, false, -+ 0, 0}, -+ {HDCP_2_2_REP_STREAM_MANAGE, -+ DP_HDCP_2_2_REP_STREAM_MANAGE_OFFSET, false, -+ 0, 0}, -+ {HDCP_2_2_REP_STREAM_READY, DP_HDCP_2_2_REP_STREAM_READY_OFFSET, -+ false, HDCP_2_2_STREAM_READY_TIMEOUT_MS, 0}, -+/* local define to shovel this through the write_2_2 interface */ -+#define HDCP_2_2_ERRATA_DP_STREAM_TYPE 50 -+ {HDCP_2_2_ERRATA_DP_STREAM_TYPE, -+ DP_HDCP_2_2_REG_STREAM_TYPE_OFFSET, false, -+ 0, 0}, -+ }; -+ -+static inline -+int intel_dp_hdcp2_read_rx_status(struct intel_digital_port *intel_dig_port, -+ u8 *rx_status) -+{ -+ ssize_t ret; -+ -+ ret = drm_dp_dpcd_read(&intel_dig_port->dp.aux, -+ DP_HDCP_2_2_REG_RXSTATUS_OFFSET, rx_status, -+ HDCP_2_2_DP_RXSTATUS_LEN); -+ if (ret != HDCP_2_2_DP_RXSTATUS_LEN) { -+ DRM_DEBUG_KMS("Read bstatus from DP/AUX failed (%zd)\n", ret); -+ return ret >= 0 ? -EIO : ret; -+ } -+ -+ return 0; -+} -+ -+static -+int hdcp2_detect_msg_availability(struct intel_digital_port *intel_dig_port, -+ u8 msg_id, bool *msg_ready) -+{ -+ u8 rx_status; -+ int ret; -+ -+ *msg_ready = false; -+ ret = intel_dp_hdcp2_read_rx_status(intel_dig_port, &rx_status); -+ if (ret < 0) -+ return ret; -+ -+ switch (msg_id) { -+ case HDCP_2_2_AKE_SEND_HPRIME: -+ if (HDCP_2_2_DP_RXSTATUS_H_PRIME(rx_status)) -+ *msg_ready = true; -+ break; -+ case HDCP_2_2_AKE_SEND_PAIRING_INFO: -+ if (HDCP_2_2_DP_RXSTATUS_PAIRING(rx_status)) -+ *msg_ready = true; -+ break; -+ case HDCP_2_2_REP_SEND_RECVID_LIST: -+ if (HDCP_2_2_DP_RXSTATUS_READY(rx_status)) -+ *msg_ready = true; -+ break; -+ default: -+ DRM_ERROR("Unidentified msg_id: %d\n", msg_id); -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+static ssize_t -+intel_dp_hdcp2_wait_for_msg(struct intel_digital_port *intel_dig_port, -+ struct hdcp2_dp_msg_data *hdcp2_msg_data) -+{ -+ struct intel_dp *dp = &intel_dig_port->dp; -+ struct intel_hdcp *hdcp = &dp->attached_connector->hdcp; -+ u8 msg_id = hdcp2_msg_data->msg_id; -+ int ret, timeout; -+ bool msg_ready = false; -+ -+ if (msg_id == HDCP_2_2_AKE_SEND_HPRIME && !hdcp->is_paired) -+ timeout = hdcp2_msg_data->timeout2; -+ else -+ timeout = hdcp2_msg_data->timeout; -+ -+ /* -+ * There is no way to detect the CERT, LPRIME and STREAM_READY -+ * availability. So Wait for timeout and read the msg. -+ */ -+ if (!hdcp2_msg_data->msg_detectable) { -+ mdelay(timeout); -+ ret = 0; -+ } else { -+ /* -+ * As we want to check the msg availability at timeout, Ignoring -+ * the timeout at wait for CP_IRQ. -+ */ -+ intel_dp_hdcp_wait_for_cp_irq(hdcp, timeout); -+ ret = hdcp2_detect_msg_availability(intel_dig_port, -+ msg_id, &msg_ready); -+ if (!msg_ready) -+ ret = -ETIMEDOUT; -+ } -+ -+ if (ret) -+ DRM_DEBUG_KMS("msg_id %d, ret %d, timeout(mSec): %d\n", -+ hdcp2_msg_data->msg_id, ret, timeout); -+ -+ return ret; -+} -+ -+static struct hdcp2_dp_msg_data *get_hdcp2_dp_msg_data(u8 msg_id) -+{ -+ int i; -+ -+ for (i = 0; i < ARRAY_SIZE(hdcp2_msg_data); i++) -+ if (hdcp2_msg_data[i].msg_id == msg_id) -+ return &hdcp2_msg_data[i]; -+ -+ return NULL; -+} -+ -+static -+int intel_dp_hdcp2_write_msg(struct intel_digital_port *intel_dig_port, -+ void *buf, size_t size) -+{ -+ struct intel_dp *dp = &intel_dig_port->dp; -+ struct intel_hdcp *hdcp = &dp->attached_connector->hdcp; -+ unsigned int offset; -+ u8 *byte = buf; -+ ssize_t ret, bytes_to_write, len; -+ struct hdcp2_dp_msg_data *hdcp2_msg_data; -+ -+ hdcp2_msg_data = get_hdcp2_dp_msg_data(*byte); -+ if (!hdcp2_msg_data) -+ return -EINVAL; -+ -+ offset = hdcp2_msg_data->offset; -+ -+ /* No msg_id in DP HDCP2.2 msgs */ -+ bytes_to_write = size - 1; -+ byte++; -+ -+ hdcp->cp_irq_count_cached = atomic_read(&hdcp->cp_irq_count); -+ -+ while (bytes_to_write) { -+ len = bytes_to_write > DP_AUX_MAX_PAYLOAD_BYTES ? -+ DP_AUX_MAX_PAYLOAD_BYTES : bytes_to_write; -+ -+ ret = drm_dp_dpcd_write(&intel_dig_port->dp.aux, -+ offset, (void *)byte, len); -+ if (ret < 0) -+ return ret; -+ -+ bytes_to_write -= ret; -+ byte += ret; -+ offset += ret; -+ } -+ -+ return size; -+} -+ -+static -+ssize_t get_receiver_id_list_size(struct intel_digital_port *intel_dig_port) -+{ -+ u8 rx_info[HDCP_2_2_RXINFO_LEN]; -+ u32 dev_cnt; -+ ssize_t ret; -+ -+ ret = drm_dp_dpcd_read(&intel_dig_port->dp.aux, -+ DP_HDCP_2_2_REG_RXINFO_OFFSET, -+ (void *)rx_info, HDCP_2_2_RXINFO_LEN); -+ if (ret != HDCP_2_2_RXINFO_LEN) -+ return ret >= 0 ? -EIO : ret; -+ -+ dev_cnt = (HDCP_2_2_DEV_COUNT_HI(rx_info[0]) << 4 | -+ HDCP_2_2_DEV_COUNT_LO(rx_info[1])); -+ -+ if (dev_cnt > HDCP_2_2_MAX_DEVICE_COUNT) -+ dev_cnt = HDCP_2_2_MAX_DEVICE_COUNT; -+ -+ ret = sizeof(struct hdcp2_rep_send_receiverid_list) - -+ HDCP_2_2_RECEIVER_IDS_MAX_LEN + -+ (dev_cnt * HDCP_2_2_RECEIVER_ID_LEN); -+ -+ return ret; -+} -+ -+static -+int intel_dp_hdcp2_read_msg(struct intel_digital_port *intel_dig_port, -+ u8 msg_id, void *buf, size_t size) -+{ -+ unsigned int offset; -+ u8 *byte = buf; -+ ssize_t ret, bytes_to_recv, len; -+ struct hdcp2_dp_msg_data *hdcp2_msg_data; -+ -+ hdcp2_msg_data = get_hdcp2_dp_msg_data(msg_id); -+ if (!hdcp2_msg_data) -+ return -EINVAL; -+ offset = hdcp2_msg_data->offset; -+ -+ ret = intel_dp_hdcp2_wait_for_msg(intel_dig_port, hdcp2_msg_data); -+ if (ret < 0) -+ return ret; -+ -+ if (msg_id == HDCP_2_2_REP_SEND_RECVID_LIST) { -+ ret = get_receiver_id_list_size(intel_dig_port); -+ if (ret < 0) -+ return ret; -+ -+ size = ret; -+ } -+ bytes_to_recv = size - 1; -+ -+ /* DP adaptation msgs has no msg_id */ -+ byte++; -+ -+ while (bytes_to_recv) { -+ len = bytes_to_recv > DP_AUX_MAX_PAYLOAD_BYTES ? -+ DP_AUX_MAX_PAYLOAD_BYTES : bytes_to_recv; -+ -+ ret = drm_dp_dpcd_read(&intel_dig_port->dp.aux, offset, -+ (void *)byte, len); -+ if (ret < 0) { -+ DRM_DEBUG_KMS("msg_id %d, ret %zd\n", msg_id, ret); -+ return ret; -+ } -+ -+ bytes_to_recv -= ret; -+ byte += ret; -+ offset += ret; -+ } -+ byte = buf; -+ *byte = msg_id; -+ -+ return size; -+} -+ -+static -+int intel_dp_hdcp2_config_stream_type(struct intel_digital_port *intel_dig_port, -+ bool is_repeater, u8 content_type) -+{ -+ struct hdcp2_dp_errata_stream_type stream_type_msg; -+ -+ if (is_repeater) -+ return 0; -+ -+ /* -+ * Errata for DP: As Stream type is used for encryption, Receiver -+ * should be communicated with stream type for the decryption of the -+ * content. -+ * Repeater will be communicated with stream type as a part of it's -+ * auth later in time. -+ */ -+ stream_type_msg.msg_id = HDCP_2_2_ERRATA_DP_STREAM_TYPE; -+ stream_type_msg.stream_type = content_type; -+ -+ return intel_dp_hdcp2_write_msg(intel_dig_port, &stream_type_msg, -+ sizeof(stream_type_msg)); -+} -+ -+static -+int intel_dp_hdcp2_check_link(struct intel_digital_port *intel_dig_port) -+{ -+ u8 rx_status; -+ int ret; -+ -+ ret = intel_dp_hdcp2_read_rx_status(intel_dig_port, &rx_status); -+ if (ret) -+ return ret; -+ -+ if (HDCP_2_2_DP_RXSTATUS_REAUTH_REQ(rx_status)) -+ ret = HDCP_REAUTH_REQUEST; -+ else if (HDCP_2_2_DP_RXSTATUS_LINK_FAILED(rx_status)) -+ ret = HDCP_LINK_INTEGRITY_FAILURE; -+ else if (HDCP_2_2_DP_RXSTATUS_READY(rx_status)) -+ ret = HDCP_TOPOLOGY_CHANGE; -+ -+ return ret; -+} -+ -+static -+int intel_dp_hdcp2_capable(struct intel_digital_port *intel_dig_port, -+ bool *capable) -+{ -+ u8 rx_caps[3]; -+ int ret; -+ -+ *capable = false; -+ ret = drm_dp_dpcd_read(&intel_dig_port->dp.aux, -+ DP_HDCP_2_2_REG_RX_CAPS_OFFSET, -+ rx_caps, HDCP_2_2_RXCAPS_LEN); -+ if (ret != HDCP_2_2_RXCAPS_LEN) -+ return ret >= 0 ? -EIO : ret; -+ -+ if (rx_caps[0] == HDCP_2_2_RX_CAPS_VERSION_VAL && -+ HDCP_2_2_DP_HDCP_CAPABLE(rx_caps[2])) -+ *capable = true; -+ -+ return 0; -+} -+ -+static const struct intel_hdcp_shim intel_dp_hdcp_shim = { -+ .write_an_aksv = intel_dp_hdcp_write_an_aksv, -+ .read_bksv = intel_dp_hdcp_read_bksv, -+ .read_bstatus = intel_dp_hdcp_read_bstatus, -+ .repeater_present = intel_dp_hdcp_repeater_present, -+ .read_ri_prime = intel_dp_hdcp_read_ri_prime, -+ .read_ksv_ready = intel_dp_hdcp_read_ksv_ready, -+ .read_ksv_fifo = intel_dp_hdcp_read_ksv_fifo, -+ .read_v_prime_part = intel_dp_hdcp_read_v_prime_part, -+ .toggle_signalling = intel_dp_hdcp_toggle_signalling, -+ .check_link = intel_dp_hdcp_check_link, -+ .hdcp_capable = intel_dp_hdcp_capable, -+ .write_2_2_msg = intel_dp_hdcp2_write_msg, -+ .read_2_2_msg = intel_dp_hdcp2_read_msg, -+ .config_stream_type = intel_dp_hdcp2_config_stream_type, -+ .check_2_2_link = intel_dp_hdcp2_check_link, -+ .hdcp_2_2_capable = intel_dp_hdcp2_capable, -+ .protocol = HDCP_PROTOCOL_DP, -+}; -+ -+static void intel_edp_panel_vdd_sanitize(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_digital_port *dig_port = dp_to_dig_port(intel_dp); -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ if (!edp_have_panel_vdd(intel_dp)) -+ return; -+ -+ /* -+ * The VDD bit needs a power domain reference, so if the bit is -+ * already enabled when we boot or resume, grab this reference and -+ * schedule a vdd off, so we don't hold on to the reference -+ * indefinitely. -+ */ -+ DRM_DEBUG_KMS("VDD left on by BIOS, adjusting state tracking\n"); -+ intel_display_power_get(dev_priv, intel_aux_power_domain(dig_port)); -+ -+ edp_panel_vdd_schedule_off(intel_dp); -+} -+ -+static enum pipe vlv_active_pipe(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct intel_encoder *encoder = &dp_to_dig_port(intel_dp)->base; -+ enum pipe pipe; -+ -+ if (intel_dp_port_enabled(dev_priv, intel_dp->output_reg, -+ encoder->port, &pipe)) -+ return pipe; -+ -+ return INVALID_PIPE; -+} -+ -+void intel_dp_encoder_reset(struct drm_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->dev); -+ struct intel_dp *intel_dp = enc_to_intel_dp(encoder); -+ struct intel_lspcon *lspcon = dp_to_lspcon(intel_dp); -+ intel_wakeref_t wakeref; -+ -+ if (!HAS_DDI(dev_priv)) -+ intel_dp->DP = I915_READ(intel_dp->output_reg); -+ -+ if (lspcon->active) -+ lspcon_resume(lspcon); -+ -+ intel_dp->reset_link_params = true; -+ -+ with_pps_lock(intel_dp, wakeref) { -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ intel_dp->active_pipe = vlv_active_pipe(intel_dp); -+ -+ if (intel_dp_is_edp(intel_dp)) { -+ /* -+ * Reinit the power sequencer, in case BIOS did -+ * something nasty with it. -+ */ -+ intel_dp_pps_init(intel_dp); -+ intel_edp_panel_vdd_sanitize(intel_dp); -+ } -+ } -+} -+ -+static const struct drm_connector_funcs intel_dp_connector_funcs = { -+ .force = intel_dp_force, -+ .fill_modes = drm_helper_probe_single_connector_modes, -+ .atomic_get_property = intel_digital_connector_atomic_get_property, -+ .atomic_set_property = intel_digital_connector_atomic_set_property, -+ .late_register = intel_dp_connector_register, -+ .early_unregister = intel_dp_connector_unregister, -+ .destroy = intel_connector_destroy, -+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, -+ .atomic_duplicate_state = intel_digital_connector_duplicate_state, -+}; -+ -+static const struct drm_connector_helper_funcs intel_dp_connector_helper_funcs = { -+ .detect_ctx = intel_dp_detect, -+ .get_modes = intel_dp_get_modes, -+ .mode_valid = intel_dp_mode_valid, -+ .atomic_check = intel_digital_connector_atomic_check, -+}; -+ -+static const struct drm_encoder_funcs intel_dp_enc_funcs = { -+ .reset = intel_dp_encoder_reset, -+ .destroy = intel_dp_encoder_destroy, -+}; -+ -+enum irqreturn -+intel_dp_hpd_pulse(struct intel_digital_port *intel_dig_port, bool long_hpd) -+{ -+ struct intel_dp *intel_dp = &intel_dig_port->dp; -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ enum irqreturn ret = IRQ_NONE; -+ intel_wakeref_t wakeref; -+ -+ if (long_hpd && intel_dig_port->base.type == INTEL_OUTPUT_EDP) { -+ /* -+ * vdd off can generate a long pulse on eDP which -+ * would require vdd on to handle it, and thus we -+ * would end up in an endless cycle of -+ * "vdd off -> long hpd -> vdd on -> detect -> vdd off -> ..." -+ */ -+ DRM_DEBUG_KMS("ignoring long hpd on eDP port %c\n", -+ port_name(intel_dig_port->base.port)); -+ return IRQ_HANDLED; -+ } -+ -+ DRM_DEBUG_KMS("got hpd irq on port %c - %s\n", -+ port_name(intel_dig_port->base.port), -+ long_hpd ? "long" : "short"); -+ -+ if (long_hpd) { -+ intel_dp->reset_link_params = true; -+ return IRQ_NONE; -+ } -+ -+ wakeref = intel_display_power_get(dev_priv, -+ intel_aux_power_domain(intel_dig_port)); -+ -+ if (intel_dp->is_mst) { -+ if (intel_dp_check_mst_status(intel_dp) == -EINVAL) { -+ /* -+ * If we were in MST mode, and device is not -+ * there, get out of MST mode -+ */ -+ DRM_DEBUG_KMS("MST device may have disappeared %d vs %d\n", -+ intel_dp->is_mst, intel_dp->mst_mgr.mst_state); -+ intel_dp->is_mst = false; -+ drm_dp_mst_topology_mgr_set_mst(&intel_dp->mst_mgr, -+ intel_dp->is_mst); -+ goto put_power; -+ } -+ } -+ -+ if (!intel_dp->is_mst) { -+ bool handled; -+ -+ handled = intel_dp_short_pulse(intel_dp); -+ -+ if (!handled) -+ goto put_power; -+ } -+ -+ ret = IRQ_HANDLED; -+ -+put_power: -+ intel_display_power_put(dev_priv, -+ intel_aux_power_domain(intel_dig_port), -+ wakeref); -+ -+ return ret; -+} -+ -+/* check the VBT to see whether the eDP is on another port */ -+bool intel_dp_is_port_edp(struct drm_i915_private *dev_priv, enum port port) -+{ -+ /* -+ * eDP not supported on g4x. so bail out early just -+ * for a bit extra safety in case the VBT is bonkers. -+ */ -+ if (INTEL_GEN(dev_priv) < 5) -+ return false; -+ -+ if (INTEL_GEN(dev_priv) < 9 && port == PORT_A) -+ return true; -+ -+ return intel_bios_is_port_edp(dev_priv, port); -+} -+ -+static void -+intel_dp_add_properties(struct intel_dp *intel_dp, struct drm_connector *connector) -+{ -+ struct drm_i915_private *dev_priv = to_i915(connector->dev); -+ enum port port = dp_to_dig_port(intel_dp)->base.port; -+ -+ if (!IS_G4X(dev_priv) && port != PORT_A) -+ intel_attach_force_audio_property(connector); -+ -+ intel_attach_broadcast_rgb_property(connector); -+ if (HAS_GMCH(dev_priv)) -+ drm_connector_attach_max_bpc_property(connector, 6, 10); -+ else if (INTEL_GEN(dev_priv) >= 5) -+ drm_connector_attach_max_bpc_property(connector, 6, 12); -+ -+ if (intel_dp_is_edp(intel_dp)) { -+ u32 allowed_scalers; -+ -+ allowed_scalers = BIT(DRM_MODE_SCALE_ASPECT) | BIT(DRM_MODE_SCALE_FULLSCREEN); -+ if (!HAS_GMCH(dev_priv)) -+ allowed_scalers |= BIT(DRM_MODE_SCALE_CENTER); -+ -+ drm_connector_attach_scaling_mode_property(connector, allowed_scalers); -+ -+ connector->state->scaling_mode = DRM_MODE_SCALE_ASPECT; -+ -+ } -+} -+ -+static void intel_dp_init_panel_power_timestamps(struct intel_dp *intel_dp) -+{ -+ intel_dp->panel_power_off_time = ktime_get_boottime(); -+ intel_dp->last_power_on = jiffies; -+ intel_dp->last_backlight_off = jiffies; -+} -+ -+static void -+intel_pps_readout_hw_state(struct intel_dp *intel_dp, struct edp_power_seq *seq) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ u32 pp_on, pp_off, pp_ctl; -+ struct pps_registers regs; -+ -+ intel_pps_get_registers(intel_dp, ®s); -+ -+ pp_ctl = ironlake_get_pp_control(intel_dp); -+ -+ /* Ensure PPS is unlocked */ -+ if (!HAS_DDI(dev_priv)) -+ I915_WRITE(regs.pp_ctrl, pp_ctl); -+ -+ pp_on = I915_READ(regs.pp_on); -+ pp_off = I915_READ(regs.pp_off); -+ -+ /* Pull timing values out of registers */ -+ seq->t1_t3 = REG_FIELD_GET(PANEL_POWER_UP_DELAY_MASK, pp_on); -+ seq->t8 = REG_FIELD_GET(PANEL_LIGHT_ON_DELAY_MASK, pp_on); -+ seq->t9 = REG_FIELD_GET(PANEL_LIGHT_OFF_DELAY_MASK, pp_off); -+ seq->t10 = REG_FIELD_GET(PANEL_POWER_DOWN_DELAY_MASK, pp_off); -+ -+ if (i915_mmio_reg_valid(regs.pp_div)) { -+ u32 pp_div; -+ -+ pp_div = I915_READ(regs.pp_div); -+ -+ seq->t11_t12 = REG_FIELD_GET(PANEL_POWER_CYCLE_DELAY_MASK, pp_div) * 1000; -+ } else { -+ seq->t11_t12 = REG_FIELD_GET(BXT_POWER_CYCLE_DELAY_MASK, pp_ctl) * 1000; -+ } -+} -+ -+static void -+intel_pps_dump_state(const char *state_name, const struct edp_power_seq *seq) -+{ -+ DRM_DEBUG_KMS("%s t1_t3 %d t8 %d t9 %d t10 %d t11_t12 %d\n", -+ state_name, -+ seq->t1_t3, seq->t8, seq->t9, seq->t10, seq->t11_t12); -+} -+ -+static void -+intel_pps_verify_state(struct intel_dp *intel_dp) -+{ -+ struct edp_power_seq hw; -+ struct edp_power_seq *sw = &intel_dp->pps_delays; -+ -+ intel_pps_readout_hw_state(intel_dp, &hw); -+ -+ if (hw.t1_t3 != sw->t1_t3 || hw.t8 != sw->t8 || hw.t9 != sw->t9 || -+ hw.t10 != sw->t10 || hw.t11_t12 != sw->t11_t12) { -+ DRM_ERROR("PPS state mismatch\n"); -+ intel_pps_dump_state("sw", sw); -+ intel_pps_dump_state("hw", &hw); -+ } -+} -+ -+static void -+intel_dp_init_panel_power_sequencer(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct edp_power_seq cur, vbt, spec, -+ *final = &intel_dp->pps_delays; -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ /* already initialized? */ -+ if (final->t11_t12 != 0) -+ return; -+ -+ intel_pps_readout_hw_state(intel_dp, &cur); -+ -+ intel_pps_dump_state("cur", &cur); -+ -+ vbt = dev_priv->vbt.edp.pps; -+ /* On Toshiba Satellite P50-C-18C system the VBT T12 delay -+ * of 500ms appears to be too short. Ocassionally the panel -+ * just fails to power back on. Increasing the delay to 800ms -+ * seems sufficient to avoid this problem. -+ */ -+ if (dev_priv->quirks & QUIRK_INCREASE_T12_DELAY) { -+ vbt.t11_t12 = max_t(u16, vbt.t11_t12, 1300 * 10); -+ DRM_DEBUG_KMS("Increasing T12 panel delay as per the quirk to %d\n", -+ vbt.t11_t12); -+ } -+ /* T11_T12 delay is special and actually in units of 100ms, but zero -+ * based in the hw (so we need to add 100 ms). But the sw vbt -+ * table multiplies it with 1000 to make it in units of 100usec, -+ * too. */ -+ vbt.t11_t12 += 100 * 10; -+ -+ /* Upper limits from eDP 1.3 spec. Note that we use the clunky units of -+ * our hw here, which are all in 100usec. */ -+ spec.t1_t3 = 210 * 10; -+ spec.t8 = 50 * 10; /* no limit for t8, use t7 instead */ -+ spec.t9 = 50 * 10; /* no limit for t9, make it symmetric with t8 */ -+ spec.t10 = 500 * 10; -+ /* This one is special and actually in units of 100ms, but zero -+ * based in the hw (so we need to add 100 ms). But the sw vbt -+ * table multiplies it with 1000 to make it in units of 100usec, -+ * too. */ -+ spec.t11_t12 = (510 + 100) * 10; -+ -+ intel_pps_dump_state("vbt", &vbt); -+ -+ /* Use the max of the register settings and vbt. If both are -+ * unset, fall back to the spec limits. */ -+#define assign_final(field) final->field = (max(cur.field, vbt.field) == 0 ? \ -+ spec.field : \ -+ max(cur.field, vbt.field)) -+ assign_final(t1_t3); -+ assign_final(t8); -+ assign_final(t9); -+ assign_final(t10); -+ assign_final(t11_t12); -+#undef assign_final -+ -+#define get_delay(field) (DIV_ROUND_UP(final->field, 10)) -+ intel_dp->panel_power_up_delay = get_delay(t1_t3); -+ intel_dp->backlight_on_delay = get_delay(t8); -+ intel_dp->backlight_off_delay = get_delay(t9); -+ intel_dp->panel_power_down_delay = get_delay(t10); -+ intel_dp->panel_power_cycle_delay = get_delay(t11_t12); -+#undef get_delay -+ -+ DRM_DEBUG_KMS("panel power up delay %d, power down delay %d, power cycle delay %d\n", -+ intel_dp->panel_power_up_delay, intel_dp->panel_power_down_delay, -+ intel_dp->panel_power_cycle_delay); -+ -+ DRM_DEBUG_KMS("backlight on delay %d, off delay %d\n", -+ intel_dp->backlight_on_delay, intel_dp->backlight_off_delay); -+ -+ /* -+ * We override the HW backlight delays to 1 because we do manual waits -+ * on them. For T8, even BSpec recommends doing it. For T9, if we -+ * don't do this, we'll end up waiting for the backlight off delay -+ * twice: once when we do the manual sleep, and once when we disable -+ * the panel and wait for the PP_STATUS bit to become zero. -+ */ -+ final->t8 = 1; -+ final->t9 = 1; -+ -+ /* -+ * HW has only a 100msec granularity for t11_t12 so round it up -+ * accordingly. -+ */ -+ final->t11_t12 = roundup(final->t11_t12, 100 * 10); -+} -+ -+static void -+intel_dp_init_panel_power_sequencer_registers(struct intel_dp *intel_dp, -+ bool force_disable_vdd) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ u32 pp_on, pp_off, port_sel = 0; -+ int div = dev_priv->rawclk_freq / 1000; -+ struct pps_registers regs; -+ enum port port = dp_to_dig_port(intel_dp)->base.port; -+ const struct edp_power_seq *seq = &intel_dp->pps_delays; -+ -+ lockdep_assert_held(&dev_priv->pps_mutex); -+ -+ intel_pps_get_registers(intel_dp, ®s); -+ -+ /* -+ * On some VLV machines the BIOS can leave the VDD -+ * enabled even on power sequencers which aren't -+ * hooked up to any port. This would mess up the -+ * power domain tracking the first time we pick -+ * one of these power sequencers for use since -+ * edp_panel_vdd_on() would notice that the VDD was -+ * already on and therefore wouldn't grab the power -+ * domain reference. Disable VDD first to avoid this. -+ * This also avoids spuriously turning the VDD on as -+ * soon as the new power sequencer gets initialized. -+ */ -+ if (force_disable_vdd) { -+ u32 pp = ironlake_get_pp_control(intel_dp); -+ -+ WARN(pp & PANEL_POWER_ON, "Panel power already on\n"); -+ -+ if (pp & EDP_FORCE_VDD) -+ DRM_DEBUG_KMS("VDD already on, disabling first\n"); -+ -+ pp &= ~EDP_FORCE_VDD; -+ -+ I915_WRITE(regs.pp_ctrl, pp); -+ } -+ -+ pp_on = REG_FIELD_PREP(PANEL_POWER_UP_DELAY_MASK, seq->t1_t3) | -+ REG_FIELD_PREP(PANEL_LIGHT_ON_DELAY_MASK, seq->t8); -+ pp_off = REG_FIELD_PREP(PANEL_LIGHT_OFF_DELAY_MASK, seq->t9) | -+ REG_FIELD_PREP(PANEL_POWER_DOWN_DELAY_MASK, seq->t10); -+ -+ /* Haswell doesn't have any port selection bits for the panel -+ * power sequencer any more. */ -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ port_sel = PANEL_PORT_SELECT_VLV(port); -+ } else if (HAS_PCH_IBX(dev_priv) || HAS_PCH_CPT(dev_priv)) { -+ switch (port) { -+ case PORT_A: -+ port_sel = PANEL_PORT_SELECT_DPA; -+ break; -+ case PORT_C: -+ port_sel = PANEL_PORT_SELECT_DPC; -+ break; -+ case PORT_D: -+ port_sel = PANEL_PORT_SELECT_DPD; -+ break; -+ default: -+ MISSING_CASE(port); -+ break; -+ } -+ } -+ -+ pp_on |= port_sel; -+ -+ I915_WRITE(regs.pp_on, pp_on); -+ I915_WRITE(regs.pp_off, pp_off); -+ -+ /* -+ * Compute the divisor for the pp clock, simply match the Bspec formula. -+ */ -+ if (i915_mmio_reg_valid(regs.pp_div)) { -+ I915_WRITE(regs.pp_div, -+ REG_FIELD_PREP(PP_REFERENCE_DIVIDER_MASK, (100 * div) / 2 - 1) | -+ REG_FIELD_PREP(PANEL_POWER_CYCLE_DELAY_MASK, DIV_ROUND_UP(seq->t11_t12, 1000))); -+ } else { -+ u32 pp_ctl; -+ -+ pp_ctl = I915_READ(regs.pp_ctrl); -+ pp_ctl &= ~BXT_POWER_CYCLE_DELAY_MASK; -+ pp_ctl |= REG_FIELD_PREP(BXT_POWER_CYCLE_DELAY_MASK, DIV_ROUND_UP(seq->t11_t12, 1000)); -+ I915_WRITE(regs.pp_ctrl, pp_ctl); -+ } -+ -+ DRM_DEBUG_KMS("panel power sequencer register settings: PP_ON %#x, PP_OFF %#x, PP_DIV %#x\n", -+ I915_READ(regs.pp_on), -+ I915_READ(regs.pp_off), -+ i915_mmio_reg_valid(regs.pp_div) ? -+ I915_READ(regs.pp_div) : -+ (I915_READ(regs.pp_ctrl) & BXT_POWER_CYCLE_DELAY_MASK)); -+} -+ -+static void intel_dp_pps_init(struct intel_dp *intel_dp) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ vlv_initial_power_sequencer_setup(intel_dp); -+ } else { -+ intel_dp_init_panel_power_sequencer(intel_dp); -+ intel_dp_init_panel_power_sequencer_registers(intel_dp, false); -+ } -+} -+ -+/** -+ * intel_dp_set_drrs_state - program registers for RR switch to take effect -+ * @dev_priv: i915 device -+ * @crtc_state: a pointer to the active intel_crtc_state -+ * @refresh_rate: RR to be programmed -+ * -+ * This function gets called when refresh rate (RR) has to be changed from -+ * one frequency to another. Switches can be between high and low RR -+ * supported by the panel or to any other RR based on media playback (in -+ * this case, RR value needs to be passed from user space). -+ * -+ * The caller of this function needs to take a lock on dev_priv->drrs. -+ */ -+static void intel_dp_set_drrs_state(struct drm_i915_private *dev_priv, -+ const struct intel_crtc_state *crtc_state, -+ int refresh_rate) -+{ -+ struct intel_encoder *encoder; -+ struct intel_digital_port *dig_port = NULL; -+ struct intel_dp *intel_dp = dev_priv->drrs.dp; -+ struct intel_crtc *intel_crtc = to_intel_crtc(crtc_state->base.crtc); -+ enum drrs_refresh_rate_type index = DRRS_HIGH_RR; -+ -+ if (refresh_rate <= 0) { -+ DRM_DEBUG_KMS("Refresh rate should be positive non-zero.\n"); -+ return; -+ } -+ -+ if (intel_dp == NULL) { -+ DRM_DEBUG_KMS("DRRS not supported.\n"); -+ return; -+ } -+ -+ dig_port = dp_to_dig_port(intel_dp); -+ encoder = &dig_port->base; -+ -+ if (!intel_crtc) { -+ DRM_DEBUG_KMS("DRRS: intel_crtc not initialized\n"); -+ return; -+ } -+ -+ if (dev_priv->drrs.type < SEAMLESS_DRRS_SUPPORT) { -+ DRM_DEBUG_KMS("Only Seamless DRRS supported.\n"); -+ return; -+ } -+ -+ if (intel_dp->attached_connector->panel.downclock_mode->vrefresh == -+ refresh_rate) -+ index = DRRS_LOW_RR; -+ -+ if (index == dev_priv->drrs.refresh_rate_type) { -+ DRM_DEBUG_KMS( -+ "DRRS requested for previously set RR...ignoring\n"); -+ return; -+ } -+ -+ if (!crtc_state->base.active) { -+ DRM_DEBUG_KMS("eDP encoder disabled. CRTC not Active\n"); -+ return; -+ } -+ -+ if (INTEL_GEN(dev_priv) >= 8 && !IS_CHERRYVIEW(dev_priv)) { -+ switch (index) { -+ case DRRS_HIGH_RR: -+ intel_dp_set_m_n(crtc_state, M1_N1); -+ break; -+ case DRRS_LOW_RR: -+ intel_dp_set_m_n(crtc_state, M2_N2); -+ break; -+ case DRRS_MAX_RR: -+ default: -+ DRM_ERROR("Unsupported refreshrate type\n"); -+ } -+ } else if (INTEL_GEN(dev_priv) > 6) { -+ i915_reg_t reg = PIPECONF(crtc_state->cpu_transcoder); -+ u32 val; -+ -+ val = I915_READ(reg); -+ if (index > DRRS_HIGH_RR) { -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ val |= PIPECONF_EDP_RR_MODE_SWITCH_VLV; -+ else -+ val |= PIPECONF_EDP_RR_MODE_SWITCH; -+ } else { -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ val &= ~PIPECONF_EDP_RR_MODE_SWITCH_VLV; -+ else -+ val &= ~PIPECONF_EDP_RR_MODE_SWITCH; -+ } -+ I915_WRITE(reg, val); -+ } -+ -+ dev_priv->drrs.refresh_rate_type = index; -+ -+ DRM_DEBUG_KMS("eDP Refresh Rate set to : %dHz\n", refresh_rate); -+} -+ -+/** -+ * intel_edp_drrs_enable - init drrs struct if supported -+ * @intel_dp: DP struct -+ * @crtc_state: A pointer to the active crtc state. -+ * -+ * Initializes frontbuffer_bits and drrs.dp -+ */ -+void intel_edp_drrs_enable(struct intel_dp *intel_dp, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ -+ if (!crtc_state->has_drrs) { -+ DRM_DEBUG_KMS("Panel doesn't support DRRS\n"); -+ return; -+ } -+ -+ if (dev_priv->psr.enabled) { -+ DRM_DEBUG_KMS("PSR enabled. Not enabling DRRS.\n"); -+ return; -+ } -+ -+ mutex_lock(&dev_priv->drrs.mutex); -+ if (dev_priv->drrs.dp) { -+ DRM_DEBUG_KMS("DRRS already enabled\n"); -+ goto unlock; -+ } -+ -+ dev_priv->drrs.busy_frontbuffer_bits = 0; -+ -+ dev_priv->drrs.dp = intel_dp; -+ -+unlock: -+ mutex_unlock(&dev_priv->drrs.mutex); -+} -+ -+/** -+ * intel_edp_drrs_disable - Disable DRRS -+ * @intel_dp: DP struct -+ * @old_crtc_state: Pointer to old crtc_state. -+ * -+ */ -+void intel_edp_drrs_disable(struct intel_dp *intel_dp, -+ const struct intel_crtc_state *old_crtc_state) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ -+ if (!old_crtc_state->has_drrs) -+ return; -+ -+ mutex_lock(&dev_priv->drrs.mutex); -+ if (!dev_priv->drrs.dp) { -+ mutex_unlock(&dev_priv->drrs.mutex); -+ return; -+ } -+ -+ if (dev_priv->drrs.refresh_rate_type == DRRS_LOW_RR) -+ intel_dp_set_drrs_state(dev_priv, old_crtc_state, -+ intel_dp->attached_connector->panel.fixed_mode->vrefresh); -+ -+ dev_priv->drrs.dp = NULL; -+ mutex_unlock(&dev_priv->drrs.mutex); -+ -+ cancel_delayed_work_sync(&dev_priv->drrs.work); -+} -+ -+static void intel_edp_drrs_downclock_work(struct work_struct *work) -+{ -+ struct drm_i915_private *dev_priv = -+ container_of(work, typeof(*dev_priv), drrs.work.work); -+ struct intel_dp *intel_dp; -+ -+ mutex_lock(&dev_priv->drrs.mutex); -+ -+ intel_dp = dev_priv->drrs.dp; -+ -+ if (!intel_dp) -+ goto unlock; -+ -+ /* -+ * The delayed work can race with an invalidate hence we need to -+ * recheck. -+ */ -+ -+ if (dev_priv->drrs.busy_frontbuffer_bits) -+ goto unlock; -+ -+ if (dev_priv->drrs.refresh_rate_type != DRRS_LOW_RR) { -+ struct drm_crtc *crtc = dp_to_dig_port(intel_dp)->base.base.crtc; -+ -+ intel_dp_set_drrs_state(dev_priv, to_intel_crtc(crtc)->config, -+ intel_dp->attached_connector->panel.downclock_mode->vrefresh); -+ } -+ -+unlock: -+ mutex_unlock(&dev_priv->drrs.mutex); -+} -+ -+/** -+ * intel_edp_drrs_invalidate - Disable Idleness DRRS -+ * @dev_priv: i915 device -+ * @frontbuffer_bits: frontbuffer plane tracking bits -+ * -+ * This function gets called everytime rendering on the given planes start. -+ * Hence DRRS needs to be Upclocked, i.e. (LOW_RR -> HIGH_RR). -+ * -+ * Dirty frontbuffers relevant to DRRS are tracked in busy_frontbuffer_bits. -+ */ -+void intel_edp_drrs_invalidate(struct drm_i915_private *dev_priv, -+ unsigned int frontbuffer_bits) -+{ -+ struct drm_crtc *crtc; -+ enum pipe pipe; -+ -+ if (dev_priv->drrs.type == DRRS_NOT_SUPPORTED) -+ return; -+ -+ cancel_delayed_work(&dev_priv->drrs.work); -+ -+ mutex_lock(&dev_priv->drrs.mutex); -+ if (!dev_priv->drrs.dp) { -+ mutex_unlock(&dev_priv->drrs.mutex); -+ return; -+ } -+ -+ crtc = dp_to_dig_port(dev_priv->drrs.dp)->base.base.crtc; -+ pipe = to_intel_crtc(crtc)->pipe; -+ -+ frontbuffer_bits &= INTEL_FRONTBUFFER_ALL_MASK(pipe); -+ dev_priv->drrs.busy_frontbuffer_bits |= frontbuffer_bits; -+ -+ /* invalidate means busy screen hence upclock */ -+ if (frontbuffer_bits && dev_priv->drrs.refresh_rate_type == DRRS_LOW_RR) -+ intel_dp_set_drrs_state(dev_priv, to_intel_crtc(crtc)->config, -+ dev_priv->drrs.dp->attached_connector->panel.fixed_mode->vrefresh); -+ -+ mutex_unlock(&dev_priv->drrs.mutex); -+} -+ -+/** -+ * intel_edp_drrs_flush - Restart Idleness DRRS -+ * @dev_priv: i915 device -+ * @frontbuffer_bits: frontbuffer plane tracking bits -+ * -+ * This function gets called every time rendering on the given planes has -+ * completed or flip on a crtc is completed. So DRRS should be upclocked -+ * (LOW_RR -> HIGH_RR). And also Idleness detection should be started again, -+ * if no other planes are dirty. -+ * -+ * Dirty frontbuffers relevant to DRRS are tracked in busy_frontbuffer_bits. -+ */ -+void intel_edp_drrs_flush(struct drm_i915_private *dev_priv, -+ unsigned int frontbuffer_bits) -+{ -+ struct drm_crtc *crtc; -+ enum pipe pipe; -+ -+ if (dev_priv->drrs.type == DRRS_NOT_SUPPORTED) -+ return; -+ -+ cancel_delayed_work(&dev_priv->drrs.work); -+ -+ mutex_lock(&dev_priv->drrs.mutex); -+ if (!dev_priv->drrs.dp) { -+ mutex_unlock(&dev_priv->drrs.mutex); -+ return; -+ } -+ -+ crtc = dp_to_dig_port(dev_priv->drrs.dp)->base.base.crtc; -+ pipe = to_intel_crtc(crtc)->pipe; -+ -+ frontbuffer_bits &= INTEL_FRONTBUFFER_ALL_MASK(pipe); -+ dev_priv->drrs.busy_frontbuffer_bits &= ~frontbuffer_bits; -+ -+ /* flush means busy screen hence upclock */ -+ if (frontbuffer_bits && dev_priv->drrs.refresh_rate_type == DRRS_LOW_RR) -+ intel_dp_set_drrs_state(dev_priv, to_intel_crtc(crtc)->config, -+ dev_priv->drrs.dp->attached_connector->panel.fixed_mode->vrefresh); -+ -+ /* -+ * flush also means no more activity hence schedule downclock, if all -+ * other fbs are quiescent too -+ */ -+ if (!dev_priv->drrs.busy_frontbuffer_bits) -+ schedule_delayed_work(&dev_priv->drrs.work, -+ msecs_to_jiffies(1000)); -+ mutex_unlock(&dev_priv->drrs.mutex); -+} -+ -+/** -+ * DOC: Display Refresh Rate Switching (DRRS) -+ * -+ * Display Refresh Rate Switching (DRRS) is a power conservation feature -+ * which enables swtching between low and high refresh rates, -+ * dynamically, based on the usage scenario. This feature is applicable -+ * for internal panels. -+ * -+ * Indication that the panel supports DRRS is given by the panel EDID, which -+ * would list multiple refresh rates for one resolution. -+ * -+ * DRRS is of 2 types - static and seamless. -+ * Static DRRS involves changing refresh rate (RR) by doing a full modeset -+ * (may appear as a blink on screen) and is used in dock-undock scenario. -+ * Seamless DRRS involves changing RR without any visual effect to the user -+ * and can be used during normal system usage. This is done by programming -+ * certain registers. -+ * -+ * Support for static/seamless DRRS may be indicated in the VBT based on -+ * inputs from the panel spec. -+ * -+ * DRRS saves power by switching to low RR based on usage scenarios. -+ * -+ * The implementation is based on frontbuffer tracking implementation. When -+ * there is a disturbance on the screen triggered by user activity or a periodic -+ * system activity, DRRS is disabled (RR is changed to high RR). When there is -+ * no movement on screen, after a timeout of 1 second, a switch to low RR is -+ * made. -+ * -+ * For integration with frontbuffer tracking code, intel_edp_drrs_invalidate() -+ * and intel_edp_drrs_flush() are called. -+ * -+ * DRRS can be further extended to support other internal panels and also -+ * the scenario of video playback wherein RR is set based on the rate -+ * requested by userspace. -+ */ -+ -+/** -+ * intel_dp_drrs_init - Init basic DRRS work and mutex. -+ * @connector: eDP connector -+ * @fixed_mode: preferred mode of panel -+ * -+ * This function is called only once at driver load to initialize basic -+ * DRRS stuff. -+ * -+ * Returns: -+ * Downclock mode if panel supports it, else return NULL. -+ * DRRS support is determined by the presence of downclock mode (apart -+ * from VBT setting). -+ */ -+static struct drm_display_mode * -+intel_dp_drrs_init(struct intel_connector *connector, -+ struct drm_display_mode *fixed_mode) -+{ -+ struct drm_i915_private *dev_priv = to_i915(connector->base.dev); -+ struct drm_display_mode *downclock_mode = NULL; -+ -+ INIT_DELAYED_WORK(&dev_priv->drrs.work, intel_edp_drrs_downclock_work); -+ mutex_init(&dev_priv->drrs.mutex); -+ -+ if (INTEL_GEN(dev_priv) <= 6) { -+ DRM_DEBUG_KMS("DRRS supported for Gen7 and above\n"); -+ return NULL; -+ } -+ -+ if (dev_priv->vbt.drrs_type != SEAMLESS_DRRS_SUPPORT) { -+ DRM_DEBUG_KMS("VBT doesn't support DRRS\n"); -+ return NULL; -+ } -+ -+ downclock_mode = intel_panel_edid_downclock_mode(connector, fixed_mode); -+ if (!downclock_mode) { -+ DRM_DEBUG_KMS("Downclock mode is not found. DRRS not supported\n"); -+ return NULL; -+ } -+ -+ dev_priv->drrs.type = dev_priv->vbt.drrs_type; -+ -+ dev_priv->drrs.refresh_rate_type = DRRS_HIGH_RR; -+ DRM_DEBUG_KMS("seamless DRRS supported for eDP panel.\n"); -+ return downclock_mode; -+} -+ -+static bool intel_edp_init_connector(struct intel_dp *intel_dp, -+ struct intel_connector *intel_connector) -+{ -+ struct drm_i915_private *dev_priv = dp_to_i915(intel_dp); -+ struct drm_device *dev = &dev_priv->drm; -+ struct drm_connector *connector = &intel_connector->base; -+ struct drm_display_mode *fixed_mode = NULL; -+ struct drm_display_mode *downclock_mode = NULL; -+ bool has_dpcd; -+ enum pipe pipe = INVALID_PIPE; -+ intel_wakeref_t wakeref; -+ struct edid *edid; -+ -+ if (!intel_dp_is_edp(intel_dp)) -+ return true; -+ -+ INIT_DELAYED_WORK(&intel_dp->panel_vdd_work, edp_panel_vdd_work); -+ -+ /* -+ * On IBX/CPT we may get here with LVDS already registered. Since the -+ * driver uses the only internal power sequencer available for both -+ * eDP and LVDS bail out early in this case to prevent interfering -+ * with an already powered-on LVDS power sequencer. -+ */ -+ if (intel_get_lvds_encoder(dev_priv)) { -+ WARN_ON(!(HAS_PCH_IBX(dev_priv) || HAS_PCH_CPT(dev_priv))); -+ DRM_INFO("LVDS was detected, not registering eDP\n"); -+ -+ return false; -+ } -+ -+ with_pps_lock(intel_dp, wakeref) { -+ intel_dp_init_panel_power_timestamps(intel_dp); -+ intel_dp_pps_init(intel_dp); -+ intel_edp_panel_vdd_sanitize(intel_dp); -+ } -+ -+ /* Cache DPCD and EDID for edp. */ -+ has_dpcd = intel_edp_init_dpcd(intel_dp); -+ -+ if (!has_dpcd) { -+ /* if this fails, presume the device is a ghost */ -+ DRM_INFO("failed to retrieve link info, disabling eDP\n"); -+ goto out_vdd_off; -+ } -+ -+ mutex_lock(&dev->mode_config.mutex); -+ edid = drm_get_edid(connector, &intel_dp->aux.ddc); -+ if (edid) { -+ if (drm_add_edid_modes(connector, edid)) { -+ drm_connector_update_edid_property(connector, -+ edid); -+ } else { -+ kfree(edid); -+ edid = ERR_PTR(-EINVAL); -+ } -+ } else { -+ edid = ERR_PTR(-ENOENT); -+ } -+ intel_connector->edid = edid; -+ -+ fixed_mode = intel_panel_edid_fixed_mode(intel_connector); -+ if (fixed_mode) -+ downclock_mode = intel_dp_drrs_init(intel_connector, fixed_mode); -+ -+ /* fallback to VBT if available for eDP */ -+ if (!fixed_mode) -+ fixed_mode = intel_panel_vbt_fixed_mode(intel_connector); -+ mutex_unlock(&dev->mode_config.mutex); -+ -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) { -+ intel_dp->edp_notifier.notifier_call = edp_notify_handler; -+ register_reboot_notifier(&intel_dp->edp_notifier); -+ -+ /* -+ * Figure out the current pipe for the initial backlight setup. -+ * If the current pipe isn't valid, try the PPS pipe, and if that -+ * fails just assume pipe A. -+ */ -+ pipe = vlv_active_pipe(intel_dp); -+ -+ if (pipe != PIPE_A && pipe != PIPE_B) -+ pipe = intel_dp->pps_pipe; -+ -+ if (pipe != PIPE_A && pipe != PIPE_B) -+ pipe = PIPE_A; -+ -+ DRM_DEBUG_KMS("using pipe %c for initial backlight setup\n", -+ pipe_name(pipe)); -+ } -+ -+ intel_panel_init(&intel_connector->panel, fixed_mode, downclock_mode); -+ intel_connector->panel.backlight.power = intel_edp_backlight_power; -+ intel_panel_setup_backlight(connector, pipe); -+ -+ if (fixed_mode) -+ drm_connector_init_panel_orientation_property( -+ connector, fixed_mode->hdisplay, fixed_mode->vdisplay); -+ -+ return true; -+ -+out_vdd_off: -+ cancel_delayed_work_sync(&intel_dp->panel_vdd_work); -+ /* -+ * vdd might still be enabled do to the delayed vdd off. -+ * Make sure vdd is actually turned off here. -+ */ -+ with_pps_lock(intel_dp, wakeref) -+ edp_panel_vdd_off_sync(intel_dp); -+ -+ return false; -+} -+ -+static void intel_dp_modeset_retry_work_fn(struct work_struct *work) -+{ -+ struct intel_connector *intel_connector; -+ struct drm_connector *connector; -+ -+ intel_connector = container_of(work, typeof(*intel_connector), -+ modeset_retry_work); -+ connector = &intel_connector->base; -+ DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id, -+ connector->name); -+ -+ /* Grab the locks before changing connector property*/ -+ mutex_lock(&connector->dev->mode_config.mutex); -+ /* Set connector link status to BAD and send a Uevent to notify -+ * userspace to do a modeset. -+ */ -+ drm_connector_set_link_status_property(connector, -+ DRM_MODE_LINK_STATUS_BAD); -+ mutex_unlock(&connector->dev->mode_config.mutex); -+ /* Send Hotplug uevent so userspace can reprobe */ -+ drm_kms_helper_hotplug_event(connector->dev); -+} -+ -+bool -+intel_dp_init_connector(struct intel_digital_port *intel_dig_port, -+ struct intel_connector *intel_connector) -+{ -+ struct drm_connector *connector = &intel_connector->base; -+ struct intel_dp *intel_dp = &intel_dig_port->dp; -+ struct intel_encoder *intel_encoder = &intel_dig_port->base; -+ struct drm_device *dev = intel_encoder->base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ enum port port = intel_encoder->port; -+ int type; -+ -+ /* Initialize the work for modeset in case of link train failure */ -+ INIT_WORK(&intel_connector->modeset_retry_work, -+ intel_dp_modeset_retry_work_fn); -+ -+ if (WARN(intel_dig_port->max_lanes < 1, -+ "Not enough lanes (%d) for DP on port %c\n", -+ intel_dig_port->max_lanes, port_name(port))) -+ return false; -+ -+ intel_dp_set_source_rates(intel_dp); -+ -+ intel_dp->reset_link_params = true; -+ intel_dp->pps_pipe = INVALID_PIPE; -+ intel_dp->active_pipe = INVALID_PIPE; -+ -+ /* intel_dp vfuncs */ -+ if (HAS_DDI(dev_priv)) -+ intel_dp->prepare_link_retrain = intel_ddi_prepare_link_retrain; -+ -+ /* Preserve the current hw state. */ -+ intel_dp->DP = I915_READ(intel_dp->output_reg); -+ intel_dp->attached_connector = intel_connector; -+ -+ if (intel_dp_is_port_edp(dev_priv, port)) -+ type = DRM_MODE_CONNECTOR_eDP; -+ else -+ type = DRM_MODE_CONNECTOR_DisplayPort; -+ -+ if (IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) -+ intel_dp->active_pipe = vlv_active_pipe(intel_dp); -+ -+ /* -+ * For eDP we always set the encoder type to INTEL_OUTPUT_EDP, but -+ * for DP the encoder type can be set by the caller to -+ * INTEL_OUTPUT_UNKNOWN for DDI, so don't rewrite it. -+ */ -+ if (type == DRM_MODE_CONNECTOR_eDP) -+ intel_encoder->type = INTEL_OUTPUT_EDP; -+ -+ /* eDP only on port B and/or C on vlv/chv */ -+ if (WARN_ON((IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv)) && -+ intel_dp_is_edp(intel_dp) && -+ port != PORT_B && port != PORT_C)) -+ return false; -+ -+ DRM_DEBUG_KMS("Adding %s connector on port %c\n", -+ type == DRM_MODE_CONNECTOR_eDP ? "eDP" : "DP", -+ port_name(port)); -+ -+ drm_connector_init(dev, connector, &intel_dp_connector_funcs, type); -+ drm_connector_helper_add(connector, &intel_dp_connector_helper_funcs); -+ -+ if (!HAS_GMCH(dev_priv)) -+ connector->interlace_allowed = true; -+ connector->doublescan_allowed = 0; -+ -+ intel_encoder->hpd_pin = intel_hpd_pin_default(dev_priv, port); -+ -+ intel_dp_aux_init(intel_dp); -+ -+ intel_connector_attach_encoder(intel_connector, intel_encoder); -+ -+ if (HAS_DDI(dev_priv)) -+ intel_connector->get_hw_state = intel_ddi_connector_get_hw_state; -+ else -+ intel_connector->get_hw_state = intel_connector_get_hw_state; -+ -+ /* init MST on ports that can support it */ -+ if (HAS_DP_MST(dev_priv) && !intel_dp_is_edp(intel_dp) && -+ (port == PORT_B || port == PORT_C || -+ port == PORT_D || port == PORT_F)) -+ intel_dp_mst_encoder_init(intel_dig_port, -+ intel_connector->base.base.id); -+ -+ if (!intel_edp_init_connector(intel_dp, intel_connector)) { -+ intel_dp_aux_fini(intel_dp); -+ intel_dp_mst_encoder_cleanup(intel_dig_port); -+ goto fail; -+ } -+ -+ intel_dp_add_properties(intel_dp, connector); -+ -+ if (is_hdcp_supported(dev_priv, port) && !intel_dp_is_edp(intel_dp)) { -+ int ret = intel_hdcp_init(intel_connector, &intel_dp_hdcp_shim); -+ if (ret) -+ DRM_DEBUG_KMS("HDCP init failed, skipping.\n"); -+ } -+ -+ /* For G4X desktop chip, PEG_BAND_GAP_DATA 3:0 must first be written -+ * 0xd. Failure to do so will result in spurious interrupts being -+ * generated on the port when a cable is not attached. -+ */ -+ if (IS_G45(dev_priv)) { -+ u32 temp = I915_READ(PEG_BAND_GAP_DATA); -+ I915_WRITE(PEG_BAND_GAP_DATA, (temp & ~0xf) | 0xd); -+ } -+ -+ return true; -+ -+fail: -+ drm_connector_cleanup(connector); -+ -+ return false; -+} -+ -+bool intel_dp_init(struct drm_i915_private *dev_priv, -+ i915_reg_t output_reg, -+ enum port port) -+{ -+ struct intel_digital_port *intel_dig_port; -+ struct intel_encoder *intel_encoder; -+ struct drm_encoder *encoder; -+ struct intel_connector *intel_connector; -+ -+ intel_dig_port = kzalloc(sizeof(*intel_dig_port), GFP_KERNEL); -+ if (!intel_dig_port) -+ return false; -+ -+ intel_connector = intel_connector_alloc(); -+ if (!intel_connector) -+ goto err_connector_alloc; -+ -+ intel_encoder = &intel_dig_port->base; -+ encoder = &intel_encoder->base; -+ -+ if (drm_encoder_init(&dev_priv->drm, &intel_encoder->base, -+ &intel_dp_enc_funcs, DRM_MODE_ENCODER_TMDS, -+ "DP %c", port_name(port))) -+ goto err_encoder_init; -+ -+ intel_encoder->hotplug = intel_dp_hotplug; -+ intel_encoder->compute_config = intel_dp_compute_config; -+ intel_encoder->get_hw_state = intel_dp_get_hw_state; -+ intel_encoder->get_config = intel_dp_get_config; -+ intel_encoder->update_pipe = intel_panel_update_backlight; -+ intel_encoder->suspend = intel_dp_encoder_suspend; -+ if (IS_CHERRYVIEW(dev_priv)) { -+ intel_encoder->pre_pll_enable = chv_dp_pre_pll_enable; -+ intel_encoder->pre_enable = chv_pre_enable_dp; -+ intel_encoder->enable = vlv_enable_dp; -+ intel_encoder->disable = vlv_disable_dp; -+ intel_encoder->post_disable = chv_post_disable_dp; -+ intel_encoder->post_pll_disable = chv_dp_post_pll_disable; -+ } else if (IS_VALLEYVIEW(dev_priv)) { -+ intel_encoder->pre_pll_enable = vlv_dp_pre_pll_enable; -+ intel_encoder->pre_enable = vlv_pre_enable_dp; -+ intel_encoder->enable = vlv_enable_dp; -+ intel_encoder->disable = vlv_disable_dp; -+ intel_encoder->post_disable = vlv_post_disable_dp; -+ } else { -+ intel_encoder->pre_enable = g4x_pre_enable_dp; -+ intel_encoder->enable = g4x_enable_dp; -+ intel_encoder->disable = g4x_disable_dp; -+ intel_encoder->post_disable = g4x_post_disable_dp; -+ } -+ -+ intel_dig_port->dp.output_reg = output_reg; -+ intel_dig_port->max_lanes = 4; -+ -+ intel_encoder->type = INTEL_OUTPUT_DP; -+ intel_encoder->power_domain = intel_port_to_power_domain(port); -+ if (IS_CHERRYVIEW(dev_priv)) { -+ if (port == PORT_D) -+ intel_encoder->crtc_mask = 1 << 2; -+ else -+ intel_encoder->crtc_mask = (1 << 0) | (1 << 1); -+ } else { -+ intel_encoder->crtc_mask = (1 << 0) | (1 << 1) | (1 << 2); -+ } -+ intel_encoder->cloneable = 0; -+ intel_encoder->port = port; -+ -+ intel_dig_port->hpd_pulse = intel_dp_hpd_pulse; -+ -+ if (port != PORT_A) -+ intel_infoframe_init(intel_dig_port); -+ -+ intel_dig_port->aux_ch = intel_bios_port_aux_ch(dev_priv, port); -+ if (!intel_dp_init_connector(intel_dig_port, intel_connector)) -+ goto err_init_connector; -+ -+ return true; -+ -+err_init_connector: -+ drm_encoder_cleanup(encoder); -+err_encoder_init: -+ kfree(intel_connector); -+err_connector_alloc: -+ kfree(intel_dig_port); -+ return false; -+} -+ -+void intel_dp_mst_suspend(struct drm_i915_private *dev_priv) -+{ -+ struct intel_encoder *encoder; -+ -+ for_each_intel_encoder(&dev_priv->drm, encoder) { -+ struct intel_dp *intel_dp; -+ -+ if (encoder->type != INTEL_OUTPUT_DDI) -+ continue; -+ -+ intel_dp = enc_to_intel_dp(&encoder->base); -+ -+ if (!intel_dp->can_mst) -+ continue; -+ -+ if (intel_dp->is_mst) -+ drm_dp_mst_topology_mgr_suspend(&intel_dp->mst_mgr); -+ } -+} -+ -+void intel_dp_mst_resume(struct drm_i915_private *dev_priv) -+{ -+ struct intel_encoder *encoder; -+ -+ for_each_intel_encoder(&dev_priv->drm, encoder) { -+ struct intel_dp *intel_dp; -+ int ret; -+ -+ if (encoder->type != INTEL_OUTPUT_DDI) -+ continue; -+ -+ intel_dp = enc_to_intel_dp(&encoder->base); -+ -+ if (!intel_dp->can_mst) -+ continue; -+ -+ ret = drm_dp_mst_topology_mgr_resume(&intel_dp->mst_mgr); -+ if (ret) { -+ intel_dp->is_mst = false; -+ drm_dp_mst_topology_mgr_set_mst(&intel_dp->mst_mgr, -+ false); -+ } -+ } -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_dp.h b/drivers/gpu/drm/i915_legacy/intel_dp.h -new file mode 100644 -index 000000000000..5e9e8d13de6e ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_dp.h -@@ -0,0 +1,122 @@ -+/* SPDX-License-Identifier: MIT */ -+/* -+ * Copyright © 2019 Intel Corporation -+ */ -+ -+#ifndef __INTEL_DP_H__ -+#define __INTEL_DP_H__ -+ -+#include -+ -+#include -+ -+#include "i915_reg.h" -+ -+enum pipe; -+struct drm_connector_state; -+struct drm_encoder; -+struct drm_i915_private; -+struct drm_modeset_acquire_ctx; -+struct intel_connector; -+struct intel_crtc_state; -+struct intel_digital_port; -+struct intel_dp; -+struct intel_encoder; -+ -+struct link_config_limits { -+ int min_clock, max_clock; -+ int min_lane_count, max_lane_count; -+ int min_bpp, max_bpp; -+}; -+ -+void intel_dp_adjust_compliance_config(struct intel_dp *intel_dp, -+ struct intel_crtc_state *pipe_config, -+ struct link_config_limits *limits); -+bool intel_dp_limited_color_range(const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state); -+int intel_dp_min_bpp(const struct intel_crtc_state *crtc_state); -+bool intel_dp_port_enabled(struct drm_i915_private *dev_priv, -+ i915_reg_t dp_reg, enum port port, -+ enum pipe *pipe); -+bool intel_dp_init(struct drm_i915_private *dev_priv, i915_reg_t output_reg, -+ enum port port); -+bool intel_dp_init_connector(struct intel_digital_port *intel_dig_port, -+ struct intel_connector *intel_connector); -+void intel_dp_set_link_params(struct intel_dp *intel_dp, -+ int link_rate, u8 lane_count, -+ bool link_mst); -+int intel_dp_get_link_train_fallback_values(struct intel_dp *intel_dp, -+ int link_rate, u8 lane_count); -+int intel_dp_retrain_link(struct intel_encoder *encoder, -+ struct drm_modeset_acquire_ctx *ctx); -+void intel_dp_sink_dpms(struct intel_dp *intel_dp, int mode); -+void intel_dp_sink_set_decompression_state(struct intel_dp *intel_dp, -+ const struct intel_crtc_state *crtc_state, -+ bool enable); -+void intel_dp_encoder_reset(struct drm_encoder *encoder); -+void intel_dp_encoder_suspend(struct intel_encoder *intel_encoder); -+void intel_dp_encoder_flush_work(struct drm_encoder *encoder); -+int intel_dp_compute_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config, -+ struct drm_connector_state *conn_state); -+bool intel_dp_is_edp(struct intel_dp *intel_dp); -+bool intel_dp_is_port_edp(struct drm_i915_private *dev_priv, enum port port); -+enum irqreturn intel_dp_hpd_pulse(struct intel_digital_port *intel_dig_port, -+ bool long_hpd); -+void intel_edp_backlight_on(const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state); -+void intel_edp_backlight_off(const struct drm_connector_state *conn_state); -+void intel_edp_panel_vdd_on(struct intel_dp *intel_dp); -+void intel_edp_panel_on(struct intel_dp *intel_dp); -+void intel_edp_panel_off(struct intel_dp *intel_dp); -+void intel_dp_mst_suspend(struct drm_i915_private *dev_priv); -+void intel_dp_mst_resume(struct drm_i915_private *dev_priv); -+int intel_dp_max_link_rate(struct intel_dp *intel_dp); -+int intel_dp_max_lane_count(struct intel_dp *intel_dp); -+int intel_dp_rate_select(struct intel_dp *intel_dp, int rate); -+void intel_power_sequencer_reset(struct drm_i915_private *dev_priv); -+u32 intel_dp_pack_aux(const u8 *src, int src_bytes); -+ -+void intel_edp_drrs_enable(struct intel_dp *intel_dp, -+ const struct intel_crtc_state *crtc_state); -+void intel_edp_drrs_disable(struct intel_dp *intel_dp, -+ const struct intel_crtc_state *crtc_state); -+void intel_edp_drrs_invalidate(struct drm_i915_private *dev_priv, -+ unsigned int frontbuffer_bits); -+void intel_edp_drrs_flush(struct drm_i915_private *dev_priv, -+ unsigned int frontbuffer_bits); -+ -+void -+intel_dp_program_link_training_pattern(struct intel_dp *intel_dp, -+ u8 dp_train_pat); -+void -+intel_dp_set_signal_levels(struct intel_dp *intel_dp); -+void intel_dp_set_idle_link_train(struct intel_dp *intel_dp); -+u8 -+intel_dp_voltage_max(struct intel_dp *intel_dp); -+u8 -+intel_dp_pre_emphasis_max(struct intel_dp *intel_dp, u8 voltage_swing); -+void intel_dp_compute_rate(struct intel_dp *intel_dp, int port_clock, -+ u8 *link_bw, u8 *rate_select); -+bool intel_dp_source_supports_hbr2(struct intel_dp *intel_dp); -+bool intel_dp_source_supports_hbr3(struct intel_dp *intel_dp); -+bool -+intel_dp_get_link_status(struct intel_dp *intel_dp, u8 *link_status); -+u16 intel_dp_dsc_get_output_bpp(int link_clock, u8 lane_count, -+ int mode_clock, int mode_hdisplay); -+u8 intel_dp_dsc_get_slice_count(struct intel_dp *intel_dp, int mode_clock, -+ int mode_hdisplay); -+ -+bool intel_dp_read_dpcd(struct intel_dp *intel_dp); -+int intel_dp_link_required(int pixel_clock, int bpp); -+int intel_dp_max_data_rate(int max_link_clock, int max_lanes); -+bool intel_digital_port_connected(struct intel_encoder *encoder); -+void icl_tc_phy_disconnect(struct drm_i915_private *dev_priv, -+ struct intel_digital_port *dig_port); -+ -+static inline unsigned int intel_dp_unused_lane_mask(int lane_count) -+{ -+ return ~((1 << lane_count) - 1) & 0xf; -+} -+ -+#endif /* __INTEL_DP_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_dp_aux_backlight.c b/drivers/gpu/drm/i915_legacy/intel_dp_aux_backlight.c -new file mode 100644 -index 000000000000..357136f17f85 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_dp_aux_backlight.c -@@ -0,0 +1,280 @@ -+/* -+ * Copyright © 2015 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include "intel_drv.h" -+ -+static void set_aux_backlight_enable(struct intel_dp *intel_dp, bool enable) -+{ -+ u8 reg_val = 0; -+ -+ /* Early return when display use other mechanism to enable backlight. */ -+ if (!(intel_dp->edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP)) -+ return; -+ -+ if (drm_dp_dpcd_readb(&intel_dp->aux, DP_EDP_DISPLAY_CONTROL_REGISTER, -+ ®_val) < 0) { -+ DRM_DEBUG_KMS("Failed to read DPCD register 0x%x\n", -+ DP_EDP_DISPLAY_CONTROL_REGISTER); -+ return; -+ } -+ if (enable) -+ reg_val |= DP_EDP_BACKLIGHT_ENABLE; -+ else -+ reg_val &= ~(DP_EDP_BACKLIGHT_ENABLE); -+ -+ if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_EDP_DISPLAY_CONTROL_REGISTER, -+ reg_val) != 1) { -+ DRM_DEBUG_KMS("Failed to %s aux backlight\n", -+ enable ? "enable" : "disable"); -+ } -+} -+ -+/* -+ * Read the current backlight value from DPCD register(s) based -+ * on if 8-bit(MSB) or 16-bit(MSB and LSB) values are supported -+ */ -+static u32 intel_dp_aux_get_backlight(struct intel_connector *connector) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(&connector->encoder->base); -+ u8 read_val[2] = { 0x0 }; -+ u16 level = 0; -+ -+ if (drm_dp_dpcd_read(&intel_dp->aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, -+ &read_val, sizeof(read_val)) < 0) { -+ DRM_DEBUG_KMS("Failed to read DPCD register 0x%x\n", -+ DP_EDP_BACKLIGHT_BRIGHTNESS_MSB); -+ return 0; -+ } -+ level = read_val[0]; -+ if (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) -+ level = (read_val[0] << 8 | read_val[1]); -+ -+ return level; -+} -+ -+/* -+ * Sends the current backlight level over the aux channel, checking if its using -+ * 8-bit or 16 bit value (MSB and LSB) -+ */ -+static void -+intel_dp_aux_set_backlight(const struct drm_connector_state *conn_state, u32 level) -+{ -+ struct intel_connector *connector = to_intel_connector(conn_state->connector); -+ struct intel_dp *intel_dp = enc_to_intel_dp(&connector->encoder->base); -+ u8 vals[2] = { 0x0 }; -+ -+ vals[0] = level; -+ -+ /* Write the MSB and/or LSB */ -+ if (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) { -+ vals[0] = (level & 0xFF00) >> 8; -+ vals[1] = (level & 0xFF); -+ } -+ if (drm_dp_dpcd_write(&intel_dp->aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, -+ vals, sizeof(vals)) < 0) { -+ DRM_DEBUG_KMS("Failed to write aux backlight level\n"); -+ return; -+ } -+} -+ -+/* -+ * Set PWM Frequency divider to match desired frequency in vbt. -+ * The PWM Frequency is calculated as 27Mhz / (F x P). -+ * - Where F = PWM Frequency Pre-Divider value programmed by field 7:0 of the -+ * EDP_BACKLIGHT_FREQ_SET register (DPCD Address 00728h) -+ * - Where P = 2^Pn, where Pn is the value programmed by field 4:0 of the -+ * EDP_PWMGEN_BIT_COUNT register (DPCD Address 00724h) -+ */ -+static bool intel_dp_aux_set_pwm_freq(struct intel_connector *connector) -+{ -+ struct drm_i915_private *dev_priv = to_i915(connector->base.dev); -+ struct intel_dp *intel_dp = enc_to_intel_dp(&connector->encoder->base); -+ int freq, fxp, fxp_min, fxp_max, fxp_actual, f = 1; -+ u8 pn, pn_min, pn_max; -+ -+ /* Find desired value of (F x P) -+ * Note that, if F x P is out of supported range, the maximum value or -+ * minimum value will applied automatically. So no need to check that. -+ */ -+ freq = dev_priv->vbt.backlight.pwm_freq_hz; -+ DRM_DEBUG_KMS("VBT defined backlight frequency %u Hz\n", freq); -+ if (!freq) { -+ DRM_DEBUG_KMS("Use panel default backlight frequency\n"); -+ return false; -+ } -+ -+ fxp = DIV_ROUND_CLOSEST(KHz(DP_EDP_BACKLIGHT_FREQ_BASE_KHZ), freq); -+ -+ /* Use highest possible value of Pn for more granularity of brightness -+ * adjustment while satifying the conditions below. -+ * - Pn is in the range of Pn_min and Pn_max -+ * - F is in the range of 1 and 255 -+ * - FxP is within 25% of desired value. -+ * Note: 25% is arbitrary value and may need some tweak. -+ */ -+ if (drm_dp_dpcd_readb(&intel_dp->aux, -+ DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min) != 1) { -+ DRM_DEBUG_KMS("Failed to read pwmgen bit count cap min\n"); -+ return false; -+ } -+ if (drm_dp_dpcd_readb(&intel_dp->aux, -+ DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max) != 1) { -+ DRM_DEBUG_KMS("Failed to read pwmgen bit count cap max\n"); -+ return false; -+ } -+ pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; -+ pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; -+ -+ fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4); -+ fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4); -+ if (fxp_min < (1 << pn_min) || (255 << pn_max) < fxp_max) { -+ DRM_DEBUG_KMS("VBT defined backlight frequency out of range\n"); -+ return false; -+ } -+ -+ for (pn = pn_max; pn >= pn_min; pn--) { -+ f = clamp(DIV_ROUND_CLOSEST(fxp, 1 << pn), 1, 255); -+ fxp_actual = f << pn; -+ if (fxp_min <= fxp_actual && fxp_actual <= fxp_max) -+ break; -+ } -+ -+ if (drm_dp_dpcd_writeb(&intel_dp->aux, -+ DP_EDP_PWMGEN_BIT_COUNT, pn) < 0) { -+ DRM_DEBUG_KMS("Failed to write aux pwmgen bit count\n"); -+ return false; -+ } -+ if (drm_dp_dpcd_writeb(&intel_dp->aux, -+ DP_EDP_BACKLIGHT_FREQ_SET, (u8) f) < 0) { -+ DRM_DEBUG_KMS("Failed to write aux backlight freq\n"); -+ return false; -+ } -+ return true; -+} -+ -+static void intel_dp_aux_enable_backlight(const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state) -+{ -+ struct intel_connector *connector = to_intel_connector(conn_state->connector); -+ struct intel_dp *intel_dp = enc_to_intel_dp(&connector->encoder->base); -+ u8 dpcd_buf, new_dpcd_buf, edp_backlight_mode; -+ -+ if (drm_dp_dpcd_readb(&intel_dp->aux, -+ DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &dpcd_buf) != 1) { -+ DRM_DEBUG_KMS("Failed to read DPCD register 0x%x\n", -+ DP_EDP_BACKLIGHT_MODE_SET_REGISTER); -+ return; -+ } -+ -+ new_dpcd_buf = dpcd_buf; -+ edp_backlight_mode = dpcd_buf & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK; -+ -+ switch (edp_backlight_mode) { -+ case DP_EDP_BACKLIGHT_CONTROL_MODE_PWM: -+ case DP_EDP_BACKLIGHT_CONTROL_MODE_PRESET: -+ case DP_EDP_BACKLIGHT_CONTROL_MODE_PRODUCT: -+ new_dpcd_buf &= ~DP_EDP_BACKLIGHT_CONTROL_MODE_MASK; -+ new_dpcd_buf |= DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD; -+ break; -+ -+ /* Do nothing when it is already DPCD mode */ -+ case DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD: -+ default: -+ break; -+ } -+ -+ if (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_FREQ_AUX_SET_CAP) -+ if (intel_dp_aux_set_pwm_freq(connector)) -+ new_dpcd_buf |= DP_EDP_BACKLIGHT_FREQ_AUX_SET_ENABLE; -+ -+ if (new_dpcd_buf != dpcd_buf) { -+ if (drm_dp_dpcd_writeb(&intel_dp->aux, -+ DP_EDP_BACKLIGHT_MODE_SET_REGISTER, new_dpcd_buf) < 0) { -+ DRM_DEBUG_KMS("Failed to write aux backlight mode\n"); -+ } -+ } -+ -+ set_aux_backlight_enable(intel_dp, true); -+ intel_dp_aux_set_backlight(conn_state, connector->panel.backlight.level); -+} -+ -+static void intel_dp_aux_disable_backlight(const struct drm_connector_state *old_conn_state) -+{ -+ set_aux_backlight_enable(enc_to_intel_dp(old_conn_state->best_encoder), false); -+} -+ -+static int intel_dp_aux_setup_backlight(struct intel_connector *connector, -+ enum pipe pipe) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(&connector->encoder->base); -+ struct intel_panel *panel = &connector->panel; -+ -+ if (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) -+ panel->backlight.max = 0xFFFF; -+ else -+ panel->backlight.max = 0xFF; -+ -+ panel->backlight.min = 0; -+ panel->backlight.level = intel_dp_aux_get_backlight(connector); -+ -+ panel->backlight.enabled = panel->backlight.level != 0; -+ -+ return 0; -+} -+ -+static bool -+intel_dp_aux_display_control_capable(struct intel_connector *connector) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(&connector->encoder->base); -+ -+ /* Check the eDP Display control capabilities registers to determine if -+ * the panel can support backlight control over the aux channel -+ */ -+ if (intel_dp->edp_dpcd[1] & DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP && -+ (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP) && -+ !(intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_PWM_PIN_CAP)) { -+ DRM_DEBUG_KMS("AUX Backlight Control Supported!\n"); -+ return true; -+ } -+ return false; -+} -+ -+int intel_dp_aux_init_backlight_funcs(struct intel_connector *intel_connector) -+{ -+ struct intel_panel *panel = &intel_connector->panel; -+ -+ if (!i915_modparams.enable_dpcd_backlight) -+ return -ENODEV; -+ -+ if (!intel_dp_aux_display_control_capable(intel_connector)) -+ return -ENODEV; -+ -+ panel->backlight.setup = intel_dp_aux_setup_backlight; -+ panel->backlight.enable = intel_dp_aux_enable_backlight; -+ panel->backlight.disable = intel_dp_aux_disable_backlight; -+ panel->backlight.set = intel_dp_aux_set_backlight; -+ panel->backlight.get = intel_dp_aux_get_backlight; -+ -+ return 0; -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_dp_link_training.c b/drivers/gpu/drm/i915_legacy/intel_dp_link_training.c -new file mode 100644 -index 000000000000..54b069333e2f ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_dp_link_training.c -@@ -0,0 +1,381 @@ -+/* -+ * Copyright © 2008-2015 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#include "intel_dp.h" -+#include "intel_drv.h" -+ -+static void -+intel_dp_dump_link_status(const u8 link_status[DP_LINK_STATUS_SIZE]) -+{ -+ -+ DRM_DEBUG_KMS("ln0_1:0x%x ln2_3:0x%x align:0x%x sink:0x%x adj_req0_1:0x%x adj_req2_3:0x%x", -+ link_status[0], link_status[1], link_status[2], -+ link_status[3], link_status[4], link_status[5]); -+} -+ -+static void -+intel_get_adjust_train(struct intel_dp *intel_dp, -+ const u8 link_status[DP_LINK_STATUS_SIZE]) -+{ -+ u8 v = 0; -+ u8 p = 0; -+ int lane; -+ u8 voltage_max; -+ u8 preemph_max; -+ -+ for (lane = 0; lane < intel_dp->lane_count; lane++) { -+ u8 this_v = drm_dp_get_adjust_request_voltage(link_status, lane); -+ u8 this_p = drm_dp_get_adjust_request_pre_emphasis(link_status, lane); -+ -+ if (this_v > v) -+ v = this_v; -+ if (this_p > p) -+ p = this_p; -+ } -+ -+ voltage_max = intel_dp_voltage_max(intel_dp); -+ if (v >= voltage_max) -+ v = voltage_max | DP_TRAIN_MAX_SWING_REACHED; -+ -+ preemph_max = intel_dp_pre_emphasis_max(intel_dp, v); -+ if (p >= preemph_max) -+ p = preemph_max | DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; -+ -+ for (lane = 0; lane < 4; lane++) -+ intel_dp->train_set[lane] = v | p; -+} -+ -+static bool -+intel_dp_set_link_train(struct intel_dp *intel_dp, -+ u8 dp_train_pat) -+{ -+ u8 buf[sizeof(intel_dp->train_set) + 1]; -+ int ret, len; -+ -+ intel_dp_program_link_training_pattern(intel_dp, dp_train_pat); -+ -+ buf[0] = dp_train_pat; -+ if ((dp_train_pat & DP_TRAINING_PATTERN_MASK) == -+ DP_TRAINING_PATTERN_DISABLE) { -+ /* don't write DP_TRAINING_LANEx_SET on disable */ -+ len = 1; -+ } else { -+ /* DP_TRAINING_LANEx_SET follow DP_TRAINING_PATTERN_SET */ -+ memcpy(buf + 1, intel_dp->train_set, intel_dp->lane_count); -+ len = intel_dp->lane_count + 1; -+ } -+ -+ ret = drm_dp_dpcd_write(&intel_dp->aux, DP_TRAINING_PATTERN_SET, -+ buf, len); -+ -+ return ret == len; -+} -+ -+static bool -+intel_dp_reset_link_train(struct intel_dp *intel_dp, -+ u8 dp_train_pat) -+{ -+ memset(intel_dp->train_set, 0, sizeof(intel_dp->train_set)); -+ intel_dp_set_signal_levels(intel_dp); -+ return intel_dp_set_link_train(intel_dp, dp_train_pat); -+} -+ -+static bool -+intel_dp_update_link_train(struct intel_dp *intel_dp) -+{ -+ int ret; -+ -+ intel_dp_set_signal_levels(intel_dp); -+ -+ ret = drm_dp_dpcd_write(&intel_dp->aux, DP_TRAINING_LANE0_SET, -+ intel_dp->train_set, intel_dp->lane_count); -+ -+ return ret == intel_dp->lane_count; -+} -+ -+static bool intel_dp_link_max_vswing_reached(struct intel_dp *intel_dp) -+{ -+ int lane; -+ -+ for (lane = 0; lane < intel_dp->lane_count; lane++) -+ if ((intel_dp->train_set[lane] & -+ DP_TRAIN_MAX_SWING_REACHED) == 0) -+ return false; -+ -+ return true; -+} -+ -+/* Enable corresponding port and start training pattern 1 */ -+static bool -+intel_dp_link_training_clock_recovery(struct intel_dp *intel_dp) -+{ -+ u8 voltage; -+ int voltage_tries, cr_tries, max_cr_tries; -+ bool max_vswing_reached = false; -+ u8 link_config[2]; -+ u8 link_bw, rate_select; -+ -+ if (intel_dp->prepare_link_retrain) -+ intel_dp->prepare_link_retrain(intel_dp); -+ -+ intel_dp_compute_rate(intel_dp, intel_dp->link_rate, -+ &link_bw, &rate_select); -+ -+ if (link_bw) -+ DRM_DEBUG_KMS("Using LINK_BW_SET value %02x\n", link_bw); -+ else -+ DRM_DEBUG_KMS("Using LINK_RATE_SET value %02x\n", rate_select); -+ -+ /* Write the link configuration data */ -+ link_config[0] = link_bw; -+ link_config[1] = intel_dp->lane_count; -+ if (drm_dp_enhanced_frame_cap(intel_dp->dpcd)) -+ link_config[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; -+ drm_dp_dpcd_write(&intel_dp->aux, DP_LINK_BW_SET, link_config, 2); -+ -+ /* eDP 1.4 rate select method. */ -+ if (!link_bw) -+ drm_dp_dpcd_write(&intel_dp->aux, DP_LINK_RATE_SET, -+ &rate_select, 1); -+ -+ link_config[0] = 0; -+ link_config[1] = DP_SET_ANSI_8B10B; -+ drm_dp_dpcd_write(&intel_dp->aux, DP_DOWNSPREAD_CTRL, link_config, 2); -+ -+ intel_dp->DP |= DP_PORT_EN; -+ -+ /* clock recovery */ -+ if (!intel_dp_reset_link_train(intel_dp, -+ DP_TRAINING_PATTERN_1 | -+ DP_LINK_SCRAMBLING_DISABLE)) { -+ DRM_ERROR("failed to enable link training\n"); -+ return false; -+ } -+ -+ /* -+ * The DP 1.4 spec defines the max clock recovery retries value -+ * as 10 but for pre-DP 1.4 devices we set a very tolerant -+ * retry limit of 80 (4 voltage levels x 4 preemphasis levels x -+ * x 5 identical voltage retries). Since the previous specs didn't -+ * define a limit and created the possibility of an infinite loop -+ * we want to prevent any sync from triggering that corner case. -+ */ -+ if (intel_dp->dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14) -+ max_cr_tries = 10; -+ else -+ max_cr_tries = 80; -+ -+ voltage_tries = 1; -+ for (cr_tries = 0; cr_tries < max_cr_tries; ++cr_tries) { -+ u8 link_status[DP_LINK_STATUS_SIZE]; -+ -+ drm_dp_link_train_clock_recovery_delay(intel_dp->dpcd); -+ -+ if (!intel_dp_get_link_status(intel_dp, link_status)) { -+ DRM_ERROR("failed to get link status\n"); -+ return false; -+ } -+ -+ if (drm_dp_clock_recovery_ok(link_status, intel_dp->lane_count)) { -+ DRM_DEBUG_KMS("clock recovery OK\n"); -+ return true; -+ } -+ -+ if (voltage_tries == 5) { -+ DRM_DEBUG_KMS("Same voltage tried 5 times\n"); -+ return false; -+ } -+ -+ if (max_vswing_reached) { -+ DRM_DEBUG_KMS("Max Voltage Swing reached\n"); -+ return false; -+ } -+ -+ voltage = intel_dp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK; -+ -+ /* Update training set as requested by target */ -+ intel_get_adjust_train(intel_dp, link_status); -+ if (!intel_dp_update_link_train(intel_dp)) { -+ DRM_ERROR("failed to update link training\n"); -+ return false; -+ } -+ -+ if ((intel_dp->train_set[0] & DP_TRAIN_VOLTAGE_SWING_MASK) == -+ voltage) -+ ++voltage_tries; -+ else -+ voltage_tries = 1; -+ -+ if (intel_dp_link_max_vswing_reached(intel_dp)) -+ max_vswing_reached = true; -+ -+ } -+ DRM_ERROR("Failed clock recovery %d times, giving up!\n", max_cr_tries); -+ return false; -+} -+ -+/* -+ * Pick training pattern for channel equalization. Training pattern 4 for HBR3 -+ * or for 1.4 devices that support it, training Pattern 3 for HBR2 -+ * or 1.2 devices that support it, Training Pattern 2 otherwise. -+ */ -+static u32 intel_dp_training_pattern(struct intel_dp *intel_dp) -+{ -+ bool source_tps3, sink_tps3, source_tps4, sink_tps4; -+ -+ /* -+ * Intel platforms that support HBR3 also support TPS4. It is mandatory -+ * for all downstream devices that support HBR3. There are no known eDP -+ * panels that support TPS4 as of Feb 2018 as per VESA eDP_v1.4b_E1 -+ * specification. -+ */ -+ source_tps4 = intel_dp_source_supports_hbr3(intel_dp); -+ sink_tps4 = drm_dp_tps4_supported(intel_dp->dpcd); -+ if (source_tps4 && sink_tps4) { -+ return DP_TRAINING_PATTERN_4; -+ } else if (intel_dp->link_rate == 810000) { -+ if (!source_tps4) -+ DRM_DEBUG_KMS("8.1 Gbps link rate without source HBR3/TPS4 support\n"); -+ if (!sink_tps4) -+ DRM_DEBUG_KMS("8.1 Gbps link rate without sink TPS4 support\n"); -+ } -+ /* -+ * Intel platforms that support HBR2 also support TPS3. TPS3 support is -+ * also mandatory for downstream devices that support HBR2. However, not -+ * all sinks follow the spec. -+ */ -+ source_tps3 = intel_dp_source_supports_hbr2(intel_dp); -+ sink_tps3 = drm_dp_tps3_supported(intel_dp->dpcd); -+ if (source_tps3 && sink_tps3) { -+ return DP_TRAINING_PATTERN_3; -+ } else if (intel_dp->link_rate >= 540000) { -+ if (!source_tps3) -+ DRM_DEBUG_KMS(">=5.4/6.48 Gbps link rate without source HBR2/TPS3 support\n"); -+ if (!sink_tps3) -+ DRM_DEBUG_KMS(">=5.4/6.48 Gbps link rate without sink TPS3 support\n"); -+ } -+ -+ return DP_TRAINING_PATTERN_2; -+} -+ -+static bool -+intel_dp_link_training_channel_equalization(struct intel_dp *intel_dp) -+{ -+ int tries; -+ u32 training_pattern; -+ u8 link_status[DP_LINK_STATUS_SIZE]; -+ bool channel_eq = false; -+ -+ training_pattern = intel_dp_training_pattern(intel_dp); -+ /* Scrambling is disabled for TPS2/3 and enabled for TPS4 */ -+ if (training_pattern != DP_TRAINING_PATTERN_4) -+ training_pattern |= DP_LINK_SCRAMBLING_DISABLE; -+ -+ /* channel equalization */ -+ if (!intel_dp_set_link_train(intel_dp, -+ training_pattern)) { -+ DRM_ERROR("failed to start channel equalization\n"); -+ return false; -+ } -+ -+ for (tries = 0; tries < 5; tries++) { -+ -+ drm_dp_link_train_channel_eq_delay(intel_dp->dpcd); -+ if (!intel_dp_get_link_status(intel_dp, link_status)) { -+ DRM_ERROR("failed to get link status\n"); -+ break; -+ } -+ -+ /* Make sure clock is still ok */ -+ if (!drm_dp_clock_recovery_ok(link_status, -+ intel_dp->lane_count)) { -+ intel_dp_dump_link_status(link_status); -+ DRM_DEBUG_KMS("Clock recovery check failed, cannot " -+ "continue channel equalization\n"); -+ break; -+ } -+ -+ if (drm_dp_channel_eq_ok(link_status, -+ intel_dp->lane_count)) { -+ channel_eq = true; -+ DRM_DEBUG_KMS("Channel EQ done. DP Training " -+ "successful\n"); -+ break; -+ } -+ -+ /* Update training set as requested by target */ -+ intel_get_adjust_train(intel_dp, link_status); -+ if (!intel_dp_update_link_train(intel_dp)) { -+ DRM_ERROR("failed to update link training\n"); -+ break; -+ } -+ } -+ -+ /* Try 5 times, else fail and try at lower BW */ -+ if (tries == 5) { -+ intel_dp_dump_link_status(link_status); -+ DRM_DEBUG_KMS("Channel equalization failed 5 times\n"); -+ } -+ -+ intel_dp_set_idle_link_train(intel_dp); -+ -+ return channel_eq; -+ -+} -+ -+void intel_dp_stop_link_train(struct intel_dp *intel_dp) -+{ -+ intel_dp->link_trained = true; -+ -+ intel_dp_set_link_train(intel_dp, -+ DP_TRAINING_PATTERN_DISABLE); -+} -+ -+void -+intel_dp_start_link_train(struct intel_dp *intel_dp) -+{ -+ struct intel_connector *intel_connector = intel_dp->attached_connector; -+ -+ if (!intel_dp_link_training_clock_recovery(intel_dp)) -+ goto failure_handling; -+ if (!intel_dp_link_training_channel_equalization(intel_dp)) -+ goto failure_handling; -+ -+ DRM_DEBUG_KMS("[CONNECTOR:%d:%s] Link Training Passed at Link Rate = %d, Lane count = %d", -+ intel_connector->base.base.id, -+ intel_connector->base.name, -+ intel_dp->link_rate, intel_dp->lane_count); -+ return; -+ -+ failure_handling: -+ DRM_DEBUG_KMS("[CONNECTOR:%d:%s] Link Training failed at link rate = %d, lane count = %d", -+ intel_connector->base.base.id, -+ intel_connector->base.name, -+ intel_dp->link_rate, intel_dp->lane_count); -+ if (!intel_dp_get_link_train_fallback_values(intel_dp, -+ intel_dp->link_rate, -+ intel_dp->lane_count)) -+ /* Schedule a Hotplug Uevent to userspace to start modeset */ -+ schedule_work(&intel_connector->modeset_retry_work); -+ return; -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_dp_mst.c b/drivers/gpu/drm/i915_legacy/intel_dp_mst.c -new file mode 100644 -index 000000000000..ffdc80bfde15 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_dp_mst.c -@@ -0,0 +1,678 @@ -+/* -+ * Copyright © 2008 Intel Corporation -+ * 2014 Red Hat Inc. -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#include -+#include -+#include -+ -+#include "i915_drv.h" -+#include "intel_audio.h" -+#include "intel_connector.h" -+#include "intel_ddi.h" -+#include "intel_dp.h" -+#include "intel_drv.h" -+ -+static int intel_dp_mst_compute_link_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *crtc_state, -+ struct drm_connector_state *conn_state, -+ struct link_config_limits *limits) -+{ -+ struct drm_atomic_state *state = crtc_state->base.state; -+ struct intel_dp_mst_encoder *intel_mst = enc_to_mst(&encoder->base); -+ struct intel_dp *intel_dp = &intel_mst->primary->dp; -+ struct intel_connector *connector = -+ to_intel_connector(conn_state->connector); -+ const struct drm_display_mode *adjusted_mode = -+ &crtc_state->base.adjusted_mode; -+ void *port = connector->port; -+ bool constant_n = drm_dp_has_quirk(&intel_dp->desc, -+ DP_DPCD_QUIRK_CONSTANT_N); -+ int bpp, slots = -EINVAL; -+ -+ crtc_state->lane_count = limits->max_lane_count; -+ crtc_state->port_clock = limits->max_clock; -+ -+ for (bpp = limits->max_bpp; bpp >= limits->min_bpp; bpp -= 2 * 3) { -+ crtc_state->pipe_bpp = bpp; -+ -+ crtc_state->pbn = drm_dp_calc_pbn_mode(adjusted_mode->crtc_clock, -+ crtc_state->pipe_bpp); -+ -+ slots = drm_dp_atomic_find_vcpi_slots(state, &intel_dp->mst_mgr, -+ port, crtc_state->pbn); -+ if (slots == -EDEADLK) -+ return slots; -+ if (slots >= 0) -+ break; -+ } -+ -+ if (slots < 0) { -+ DRM_DEBUG_KMS("failed finding vcpi slots:%d\n", slots); -+ return slots; -+ } -+ -+ intel_link_compute_m_n(crtc_state->pipe_bpp, -+ crtc_state->lane_count, -+ adjusted_mode->crtc_clock, -+ crtc_state->port_clock, -+ &crtc_state->dp_m_n, -+ constant_n); -+ crtc_state->dp_m_n.tu = slots; -+ -+ return 0; -+} -+ -+static int intel_dp_mst_compute_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config, -+ struct drm_connector_state *conn_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_dp_mst_encoder *intel_mst = enc_to_mst(&encoder->base); -+ struct intel_dp *intel_dp = &intel_mst->primary->dp; -+ struct intel_connector *connector = -+ to_intel_connector(conn_state->connector); -+ struct intel_digital_connector_state *intel_conn_state = -+ to_intel_digital_connector_state(conn_state); -+ const struct drm_display_mode *adjusted_mode = -+ &pipe_config->base.adjusted_mode; -+ void *port = connector->port; -+ struct link_config_limits limits; -+ int ret; -+ -+ if (adjusted_mode->flags & DRM_MODE_FLAG_DBLSCAN) -+ return -EINVAL; -+ -+ pipe_config->output_format = INTEL_OUTPUT_FORMAT_RGB; -+ pipe_config->has_pch_encoder = false; -+ -+ if (intel_conn_state->force_audio == HDMI_AUDIO_AUTO) -+ pipe_config->has_audio = -+ drm_dp_mst_port_has_audio(&intel_dp->mst_mgr, port); -+ else -+ pipe_config->has_audio = -+ intel_conn_state->force_audio == HDMI_AUDIO_ON; -+ -+ /* -+ * for MST we always configure max link bw - the spec doesn't -+ * seem to suggest we should do otherwise. -+ */ -+ limits.min_clock = -+ limits.max_clock = intel_dp_max_link_rate(intel_dp); -+ -+ limits.min_lane_count = -+ limits.max_lane_count = intel_dp_max_lane_count(intel_dp); -+ -+ limits.min_bpp = intel_dp_min_bpp(pipe_config); -+ /* -+ * FIXME: If all the streams can't fit into the link with -+ * their current pipe_bpp we should reduce pipe_bpp across -+ * the board until things start to fit. Until then we -+ * limit to <= 8bpc since that's what was hardcoded for all -+ * MST streams previously. This hack should be removed once -+ * we have the proper retry logic in place. -+ */ -+ limits.max_bpp = min(pipe_config->pipe_bpp, 24); -+ -+ intel_dp_adjust_compliance_config(intel_dp, pipe_config, &limits); -+ -+ ret = intel_dp_mst_compute_link_config(encoder, pipe_config, -+ conn_state, &limits); -+ if (ret) -+ return ret; -+ -+ pipe_config->limited_color_range = -+ intel_dp_limited_color_range(pipe_config, conn_state); -+ -+ if (IS_GEN9_LP(dev_priv)) -+ pipe_config->lane_lat_optim_mask = -+ bxt_ddi_phy_calc_lane_lat_optim_mask(pipe_config->lane_count); -+ -+ intel_ddi_compute_min_voltage_level(dev_priv, pipe_config); -+ -+ return 0; -+} -+ -+static int -+intel_dp_mst_atomic_check(struct drm_connector *connector, -+ struct drm_atomic_state *state) -+{ -+ struct drm_connector_state *new_conn_state = -+ drm_atomic_get_new_connector_state(state, connector); -+ struct drm_connector_state *old_conn_state = -+ drm_atomic_get_old_connector_state(state, connector); -+ struct intel_connector *intel_connector = -+ to_intel_connector(connector); -+ struct drm_crtc *new_crtc = new_conn_state->crtc; -+ struct drm_crtc_state *crtc_state; -+ struct drm_dp_mst_topology_mgr *mgr; -+ int ret; -+ -+ ret = intel_digital_connector_atomic_check(connector, state); -+ if (ret) -+ return ret; -+ -+ if (!old_conn_state->crtc) -+ return 0; -+ -+ /* We only want to free VCPI if this state disables the CRTC on this -+ * connector -+ */ -+ if (new_crtc) { -+ crtc_state = drm_atomic_get_new_crtc_state(state, new_crtc); -+ -+ if (!crtc_state || -+ !drm_atomic_crtc_needs_modeset(crtc_state) || -+ crtc_state->enable) -+ return 0; -+ } -+ -+ mgr = &enc_to_mst(old_conn_state->best_encoder)->primary->dp.mst_mgr; -+ ret = drm_dp_atomic_release_vcpi_slots(state, mgr, -+ intel_connector->port); -+ -+ return ret; -+} -+ -+static void intel_mst_disable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct intel_dp_mst_encoder *intel_mst = enc_to_mst(&encoder->base); -+ struct intel_digital_port *intel_dig_port = intel_mst->primary; -+ struct intel_dp *intel_dp = &intel_dig_port->dp; -+ struct intel_connector *connector = -+ to_intel_connector(old_conn_state->connector); -+ int ret; -+ -+ DRM_DEBUG_KMS("active links %d\n", intel_dp->active_mst_links); -+ -+ drm_dp_mst_reset_vcpi_slots(&intel_dp->mst_mgr, connector->port); -+ -+ ret = drm_dp_update_payload_part1(&intel_dp->mst_mgr); -+ if (ret) { -+ DRM_ERROR("failed to update payload %d\n", ret); -+ } -+ if (old_crtc_state->has_audio) -+ intel_audio_codec_disable(encoder, -+ old_crtc_state, old_conn_state); -+} -+ -+static void intel_mst_post_disable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct intel_dp_mst_encoder *intel_mst = enc_to_mst(&encoder->base); -+ struct intel_digital_port *intel_dig_port = intel_mst->primary; -+ struct intel_dp *intel_dp = &intel_dig_port->dp; -+ struct intel_connector *connector = -+ to_intel_connector(old_conn_state->connector); -+ -+ intel_ddi_disable_pipe_clock(old_crtc_state); -+ -+ /* this can fail */ -+ drm_dp_check_act_status(&intel_dp->mst_mgr); -+ /* and this can also fail */ -+ drm_dp_update_payload_part2(&intel_dp->mst_mgr); -+ -+ drm_dp_mst_deallocate_vcpi(&intel_dp->mst_mgr, connector->port); -+ -+ /* -+ * Power down mst path before disabling the port, otherwise we end -+ * up getting interrupts from the sink upon detecting link loss. -+ */ -+ drm_dp_send_power_updown_phy(&intel_dp->mst_mgr, connector->port, -+ false); -+ -+ intel_dp->active_mst_links--; -+ -+ intel_mst->connector = NULL; -+ if (intel_dp->active_mst_links == 0) { -+ intel_dp_sink_dpms(intel_dp, DRM_MODE_DPMS_OFF); -+ intel_dig_port->base.post_disable(&intel_dig_port->base, -+ old_crtc_state, NULL); -+ } -+ -+ DRM_DEBUG_KMS("active links %d\n", intel_dp->active_mst_links); -+} -+ -+static void intel_mst_pre_pll_enable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config, -+ const struct drm_connector_state *conn_state) -+{ -+ struct intel_dp_mst_encoder *intel_mst = enc_to_mst(&encoder->base); -+ struct intel_digital_port *intel_dig_port = intel_mst->primary; -+ struct intel_dp *intel_dp = &intel_dig_port->dp; -+ -+ if (intel_dp->active_mst_links == 0) -+ intel_dig_port->base.pre_pll_enable(&intel_dig_port->base, -+ pipe_config, NULL); -+} -+ -+static void intel_mst_post_pll_disable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state, -+ const struct drm_connector_state *old_conn_state) -+{ -+ struct intel_dp_mst_encoder *intel_mst = enc_to_mst(&encoder->base); -+ struct intel_digital_port *intel_dig_port = intel_mst->primary; -+ struct intel_dp *intel_dp = &intel_dig_port->dp; -+ -+ if (intel_dp->active_mst_links == 0) -+ intel_dig_port->base.post_pll_disable(&intel_dig_port->base, -+ old_crtc_state, -+ old_conn_state); -+} -+ -+static void intel_mst_pre_enable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config, -+ const struct drm_connector_state *conn_state) -+{ -+ struct intel_dp_mst_encoder *intel_mst = enc_to_mst(&encoder->base); -+ struct intel_digital_port *intel_dig_port = intel_mst->primary; -+ struct intel_dp *intel_dp = &intel_dig_port->dp; -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = intel_dig_port->base.port; -+ struct intel_connector *connector = -+ to_intel_connector(conn_state->connector); -+ int ret; -+ u32 temp; -+ -+ /* MST encoders are bound to a crtc, not to a connector, -+ * force the mapping here for get_hw_state. -+ */ -+ connector->encoder = encoder; -+ intel_mst->connector = connector; -+ -+ DRM_DEBUG_KMS("active links %d\n", intel_dp->active_mst_links); -+ -+ if (intel_dp->active_mst_links == 0) -+ intel_dp_sink_dpms(intel_dp, DRM_MODE_DPMS_ON); -+ -+ drm_dp_send_power_updown_phy(&intel_dp->mst_mgr, connector->port, true); -+ -+ if (intel_dp->active_mst_links == 0) -+ intel_dig_port->base.pre_enable(&intel_dig_port->base, -+ pipe_config, NULL); -+ -+ ret = drm_dp_mst_allocate_vcpi(&intel_dp->mst_mgr, -+ connector->port, -+ pipe_config->pbn, -+ pipe_config->dp_m_n.tu); -+ if (!ret) -+ DRM_ERROR("failed to allocate vcpi\n"); -+ -+ intel_dp->active_mst_links++; -+ temp = I915_READ(DP_TP_STATUS(port)); -+ I915_WRITE(DP_TP_STATUS(port), temp); -+ -+ ret = drm_dp_update_payload_part1(&intel_dp->mst_mgr); -+ -+ intel_ddi_enable_pipe_clock(pipe_config); -+} -+ -+static void intel_mst_enable_dp(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config, -+ const struct drm_connector_state *conn_state) -+{ -+ struct intel_dp_mst_encoder *intel_mst = enc_to_mst(&encoder->base); -+ struct intel_digital_port *intel_dig_port = intel_mst->primary; -+ struct intel_dp *intel_dp = &intel_dig_port->dp; -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = intel_dig_port->base.port; -+ -+ DRM_DEBUG_KMS("active links %d\n", intel_dp->active_mst_links); -+ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ DP_TP_STATUS(port), -+ DP_TP_STATUS_ACT_SENT, -+ DP_TP_STATUS_ACT_SENT, -+ 1)) -+ DRM_ERROR("Timed out waiting for ACT sent\n"); -+ -+ drm_dp_check_act_status(&intel_dp->mst_mgr); -+ -+ drm_dp_update_payload_part2(&intel_dp->mst_mgr); -+ if (pipe_config->has_audio) -+ intel_audio_codec_enable(encoder, pipe_config, conn_state); -+} -+ -+static bool intel_dp_mst_enc_get_hw_state(struct intel_encoder *encoder, -+ enum pipe *pipe) -+{ -+ struct intel_dp_mst_encoder *intel_mst = enc_to_mst(&encoder->base); -+ *pipe = intel_mst->pipe; -+ if (intel_mst->connector) -+ return true; -+ return false; -+} -+ -+static void intel_dp_mst_enc_get_config(struct intel_encoder *encoder, -+ struct intel_crtc_state *pipe_config) -+{ -+ struct intel_dp_mst_encoder *intel_mst = enc_to_mst(&encoder->base); -+ struct intel_digital_port *intel_dig_port = intel_mst->primary; -+ -+ intel_ddi_get_config(&intel_dig_port->base, pipe_config); -+} -+ -+static int intel_dp_mst_get_ddc_modes(struct drm_connector *connector) -+{ -+ struct intel_connector *intel_connector = to_intel_connector(connector); -+ struct intel_dp *intel_dp = intel_connector->mst_port; -+ struct edid *edid; -+ int ret; -+ -+ if (drm_connector_is_unregistered(connector)) -+ return intel_connector_update_modes(connector, NULL); -+ -+ edid = drm_dp_mst_get_edid(connector, &intel_dp->mst_mgr, intel_connector->port); -+ ret = intel_connector_update_modes(connector, edid); -+ kfree(edid); -+ -+ return ret; -+} -+ -+static enum drm_connector_status -+intel_dp_mst_detect(struct drm_connector *connector, bool force) -+{ -+ struct intel_connector *intel_connector = to_intel_connector(connector); -+ struct intel_dp *intel_dp = intel_connector->mst_port; -+ -+ if (drm_connector_is_unregistered(connector)) -+ return connector_status_disconnected; -+ return drm_dp_mst_detect_port(connector, &intel_dp->mst_mgr, -+ intel_connector->port); -+} -+ -+static const struct drm_connector_funcs intel_dp_mst_connector_funcs = { -+ .detect = intel_dp_mst_detect, -+ .fill_modes = drm_helper_probe_single_connector_modes, -+ .atomic_get_property = intel_digital_connector_atomic_get_property, -+ .atomic_set_property = intel_digital_connector_atomic_set_property, -+ .late_register = intel_connector_register, -+ .early_unregister = intel_connector_unregister, -+ .destroy = intel_connector_destroy, -+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, -+ .atomic_duplicate_state = intel_digital_connector_duplicate_state, -+}; -+ -+static int intel_dp_mst_get_modes(struct drm_connector *connector) -+{ -+ return intel_dp_mst_get_ddc_modes(connector); -+} -+ -+static enum drm_mode_status -+intel_dp_mst_mode_valid(struct drm_connector *connector, -+ struct drm_display_mode *mode) -+{ -+ struct intel_connector *intel_connector = to_intel_connector(connector); -+ struct intel_dp *intel_dp = intel_connector->mst_port; -+ int max_dotclk = to_i915(connector->dev)->max_dotclk_freq; -+ int max_rate, mode_rate, max_lanes, max_link_clock; -+ -+ if (drm_connector_is_unregistered(connector)) -+ return MODE_ERROR; -+ -+ if (mode->flags & DRM_MODE_FLAG_DBLSCAN) -+ return MODE_NO_DBLESCAN; -+ -+ max_link_clock = intel_dp_max_link_rate(intel_dp); -+ max_lanes = intel_dp_max_lane_count(intel_dp); -+ -+ max_rate = intel_dp_max_data_rate(max_link_clock, max_lanes); -+ mode_rate = intel_dp_link_required(mode->clock, 18); -+ -+ /* TODO - validate mode against available PBN for link */ -+ if (mode->clock < 10000) -+ return MODE_CLOCK_LOW; -+ -+ if (mode->flags & DRM_MODE_FLAG_DBLCLK) -+ return MODE_H_ILLEGAL; -+ -+ if (mode_rate > max_rate || mode->clock > max_dotclk) -+ return MODE_CLOCK_HIGH; -+ -+ return MODE_OK; -+} -+ -+static struct drm_encoder *intel_mst_atomic_best_encoder(struct drm_connector *connector, -+ struct drm_connector_state *state) -+{ -+ struct intel_connector *intel_connector = to_intel_connector(connector); -+ struct intel_dp *intel_dp = intel_connector->mst_port; -+ struct intel_crtc *crtc = to_intel_crtc(state->crtc); -+ -+ return &intel_dp->mst_encoders[crtc->pipe]->base.base; -+} -+ -+static const struct drm_connector_helper_funcs intel_dp_mst_connector_helper_funcs = { -+ .get_modes = intel_dp_mst_get_modes, -+ .mode_valid = intel_dp_mst_mode_valid, -+ .atomic_best_encoder = intel_mst_atomic_best_encoder, -+ .atomic_check = intel_dp_mst_atomic_check, -+}; -+ -+static void intel_dp_mst_encoder_destroy(struct drm_encoder *encoder) -+{ -+ struct intel_dp_mst_encoder *intel_mst = enc_to_mst(encoder); -+ -+ drm_encoder_cleanup(encoder); -+ kfree(intel_mst); -+} -+ -+static const struct drm_encoder_funcs intel_dp_mst_enc_funcs = { -+ .destroy = intel_dp_mst_encoder_destroy, -+}; -+ -+static bool intel_dp_mst_get_hw_state(struct intel_connector *connector) -+{ -+ if (connector->encoder && connector->base.state->crtc) { -+ enum pipe pipe; -+ if (!connector->encoder->get_hw_state(connector->encoder, &pipe)) -+ return false; -+ return true; -+ } -+ return false; -+} -+ -+static struct drm_connector *intel_dp_add_mst_connector(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port, const char *pathprop) -+{ -+ struct intel_dp *intel_dp = container_of(mgr, struct intel_dp, mst_mgr); -+ struct intel_digital_port *intel_dig_port = dp_to_dig_port(intel_dp); -+ struct drm_device *dev = intel_dig_port->base.base.dev; -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ struct intel_connector *intel_connector; -+ struct drm_connector *connector; -+ enum pipe pipe; -+ int ret; -+ -+ intel_connector = intel_connector_alloc(); -+ if (!intel_connector) -+ return NULL; -+ -+ intel_connector->get_hw_state = intel_dp_mst_get_hw_state; -+ intel_connector->mst_port = intel_dp; -+ intel_connector->port = port; -+ drm_dp_mst_get_port_malloc(port); -+ -+ connector = &intel_connector->base; -+ ret = drm_connector_init(dev, connector, &intel_dp_mst_connector_funcs, -+ DRM_MODE_CONNECTOR_DisplayPort); -+ if (ret) { -+ intel_connector_free(intel_connector); -+ return NULL; -+ } -+ -+ drm_connector_helper_add(connector, &intel_dp_mst_connector_helper_funcs); -+ -+ for_each_pipe(dev_priv, pipe) { -+ struct drm_encoder *enc = -+ &intel_dp->mst_encoders[pipe]->base.base; -+ -+ ret = drm_connector_attach_encoder(&intel_connector->base, enc); -+ if (ret) -+ goto err; -+ } -+ -+ drm_object_attach_property(&connector->base, dev->mode_config.path_property, 0); -+ drm_object_attach_property(&connector->base, dev->mode_config.tile_property, 0); -+ -+ ret = drm_connector_set_path_property(connector, pathprop); -+ if (ret) -+ goto err; -+ -+ intel_attach_force_audio_property(connector); -+ intel_attach_broadcast_rgb_property(connector); -+ -+ /* -+ * Reuse the prop from the SST connector because we're -+ * not allowed to create new props after device registration. -+ */ -+ connector->max_bpc_property = -+ intel_dp->attached_connector->base.max_bpc_property; -+ if (connector->max_bpc_property) -+ drm_connector_attach_max_bpc_property(connector, 6, 12); -+ -+ return connector; -+ -+err: -+ drm_connector_cleanup(connector); -+ return NULL; -+} -+ -+static void intel_dp_register_mst_connector(struct drm_connector *connector) -+{ -+ struct drm_i915_private *dev_priv = to_i915(connector->dev); -+ -+ if (dev_priv->fbdev) -+ drm_fb_helper_add_one_connector(&dev_priv->fbdev->helper, -+ connector); -+ -+ drm_connector_register(connector); -+} -+ -+static void intel_dp_destroy_mst_connector(struct drm_dp_mst_topology_mgr *mgr, -+ struct drm_connector *connector) -+{ -+ struct drm_i915_private *dev_priv = to_i915(connector->dev); -+ -+ DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id, connector->name); -+ drm_connector_unregister(connector); -+ -+ if (dev_priv->fbdev) -+ drm_fb_helper_remove_one_connector(&dev_priv->fbdev->helper, -+ connector); -+ -+ drm_connector_put(connector); -+} -+ -+static const struct drm_dp_mst_topology_cbs mst_cbs = { -+ .add_connector = intel_dp_add_mst_connector, -+ .register_connector = intel_dp_register_mst_connector, -+ .destroy_connector = intel_dp_destroy_mst_connector, -+}; -+ -+static struct intel_dp_mst_encoder * -+intel_dp_create_fake_mst_encoder(struct intel_digital_port *intel_dig_port, enum pipe pipe) -+{ -+ struct intel_dp_mst_encoder *intel_mst; -+ struct intel_encoder *intel_encoder; -+ struct drm_device *dev = intel_dig_port->base.base.dev; -+ -+ intel_mst = kzalloc(sizeof(*intel_mst), GFP_KERNEL); -+ -+ if (!intel_mst) -+ return NULL; -+ -+ intel_mst->pipe = pipe; -+ intel_encoder = &intel_mst->base; -+ intel_mst->primary = intel_dig_port; -+ -+ drm_encoder_init(dev, &intel_encoder->base, &intel_dp_mst_enc_funcs, -+ DRM_MODE_ENCODER_DPMST, "DP-MST %c", pipe_name(pipe)); -+ -+ intel_encoder->type = INTEL_OUTPUT_DP_MST; -+ intel_encoder->power_domain = intel_dig_port->base.power_domain; -+ intel_encoder->port = intel_dig_port->base.port; -+ intel_encoder->crtc_mask = 0x7; -+ intel_encoder->cloneable = 0; -+ -+ intel_encoder->compute_config = intel_dp_mst_compute_config; -+ intel_encoder->disable = intel_mst_disable_dp; -+ intel_encoder->post_disable = intel_mst_post_disable_dp; -+ intel_encoder->pre_pll_enable = intel_mst_pre_pll_enable_dp; -+ intel_encoder->post_pll_disable = intel_mst_post_pll_disable_dp; -+ intel_encoder->pre_enable = intel_mst_pre_enable_dp; -+ intel_encoder->enable = intel_mst_enable_dp; -+ intel_encoder->get_hw_state = intel_dp_mst_enc_get_hw_state; -+ intel_encoder->get_config = intel_dp_mst_enc_get_config; -+ -+ return intel_mst; -+ -+} -+ -+static bool -+intel_dp_create_fake_mst_encoders(struct intel_digital_port *intel_dig_port) -+{ -+ struct intel_dp *intel_dp = &intel_dig_port->dp; -+ struct drm_i915_private *dev_priv = to_i915(intel_dig_port->base.base.dev); -+ enum pipe pipe; -+ -+ for_each_pipe(dev_priv, pipe) -+ intel_dp->mst_encoders[pipe] = intel_dp_create_fake_mst_encoder(intel_dig_port, pipe); -+ return true; -+} -+ -+int -+intel_dp_mst_encoder_init(struct intel_digital_port *intel_dig_port, int conn_base_id) -+{ -+ struct intel_dp *intel_dp = &intel_dig_port->dp; -+ struct drm_device *dev = intel_dig_port->base.base.dev; -+ int ret; -+ -+ intel_dp->can_mst = true; -+ intel_dp->mst_mgr.cbs = &mst_cbs; -+ -+ /* create encoders */ -+ intel_dp_create_fake_mst_encoders(intel_dig_port); -+ ret = drm_dp_mst_topology_mgr_init(&intel_dp->mst_mgr, dev, -+ &intel_dp->aux, 16, 3, conn_base_id); -+ if (ret) { -+ intel_dp->can_mst = false; -+ return ret; -+ } -+ return 0; -+} -+ -+void -+intel_dp_mst_encoder_cleanup(struct intel_digital_port *intel_dig_port) -+{ -+ struct intel_dp *intel_dp = &intel_dig_port->dp; -+ -+ if (!intel_dp->can_mst) -+ return; -+ -+ drm_dp_mst_topology_mgr_destroy(&intel_dp->mst_mgr); -+ /* encoders will get killed by normal cleanup */ -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_dpio_phy.c b/drivers/gpu/drm/i915_legacy/intel_dpio_phy.c -new file mode 100644 -index 000000000000..ab4ac7158b79 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_dpio_phy.c -@@ -0,0 +1,1082 @@ -+/* -+ * Copyright © 2014-2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#include "intel_dp.h" -+#include "intel_drv.h" -+ -+/** -+ * DOC: DPIO -+ * -+ * VLV, CHV and BXT have slightly peculiar display PHYs for driving DP/HDMI -+ * ports. DPIO is the name given to such a display PHY. These PHYs -+ * don't follow the standard programming model using direct MMIO -+ * registers, and instead their registers must be accessed trough IOSF -+ * sideband. VLV has one such PHY for driving ports B and C, and CHV -+ * adds another PHY for driving port D. Each PHY responds to specific -+ * IOSF-SB port. -+ * -+ * Each display PHY is made up of one or two channels. Each channel -+ * houses a common lane part which contains the PLL and other common -+ * logic. CH0 common lane also contains the IOSF-SB logic for the -+ * Common Register Interface (CRI) ie. the DPIO registers. CRI clock -+ * must be running when any DPIO registers are accessed. -+ * -+ * In addition to having their own registers, the PHYs are also -+ * controlled through some dedicated signals from the display -+ * controller. These include PLL reference clock enable, PLL enable, -+ * and CRI clock selection, for example. -+ * -+ * Eeach channel also has two splines (also called data lanes), and -+ * each spline is made up of one Physical Access Coding Sub-Layer -+ * (PCS) block and two TX lanes. So each channel has two PCS blocks -+ * and four TX lanes. The TX lanes are used as DP lanes or TMDS -+ * data/clock pairs depending on the output type. -+ * -+ * Additionally the PHY also contains an AUX lane with AUX blocks -+ * for each channel. This is used for DP AUX communication, but -+ * this fact isn't really relevant for the driver since AUX is -+ * controlled from the display controller side. No DPIO registers -+ * need to be accessed during AUX communication, -+ * -+ * Generally on VLV/CHV the common lane corresponds to the pipe and -+ * the spline (PCS/TX) corresponds to the port. -+ * -+ * For dual channel PHY (VLV/CHV): -+ * -+ * pipe A == CMN/PLL/REF CH0 -+ * -+ * pipe B == CMN/PLL/REF CH1 -+ * -+ * port B == PCS/TX CH0 -+ * -+ * port C == PCS/TX CH1 -+ * -+ * This is especially important when we cross the streams -+ * ie. drive port B with pipe B, or port C with pipe A. -+ * -+ * For single channel PHY (CHV): -+ * -+ * pipe C == CMN/PLL/REF CH0 -+ * -+ * port D == PCS/TX CH0 -+ * -+ * On BXT the entire PHY channel corresponds to the port. That means -+ * the PLL is also now associated with the port rather than the pipe, -+ * and so the clock needs to be routed to the appropriate transcoder. -+ * Port A PLL is directly connected to transcoder EDP and port B/C -+ * PLLs can be routed to any transcoder A/B/C. -+ * -+ * Note: DDI0 is digital port B, DD1 is digital port C, and DDI2 is -+ * digital port D (CHV) or port A (BXT). :: -+ * -+ * -+ * Dual channel PHY (VLV/CHV/BXT) -+ * --------------------------------- -+ * | CH0 | CH1 | -+ * | CMN/PLL/REF | CMN/PLL/REF | -+ * |---------------|---------------| Display PHY -+ * | PCS01 | PCS23 | PCS01 | PCS23 | -+ * |-------|-------|-------|-------| -+ * |TX0|TX1|TX2|TX3|TX0|TX1|TX2|TX3| -+ * --------------------------------- -+ * | DDI0 | DDI1 | DP/HDMI ports -+ * --------------------------------- -+ * -+ * Single channel PHY (CHV/BXT) -+ * ----------------- -+ * | CH0 | -+ * | CMN/PLL/REF | -+ * |---------------| Display PHY -+ * | PCS01 | PCS23 | -+ * |-------|-------| -+ * |TX0|TX1|TX2|TX3| -+ * ----------------- -+ * | DDI2 | DP/HDMI port -+ * ----------------- -+ */ -+ -+/** -+ * struct bxt_ddi_phy_info - Hold info for a broxton DDI phy -+ */ -+struct bxt_ddi_phy_info { -+ /** -+ * @dual_channel: true if this phy has a second channel. -+ */ -+ bool dual_channel; -+ -+ /** -+ * @rcomp_phy: If -1, indicates this phy has its own rcomp resistor. -+ * Otherwise the GRC value will be copied from the phy indicated by -+ * this field. -+ */ -+ enum dpio_phy rcomp_phy; -+ -+ /** -+ * @reset_delay: delay in us to wait before setting the common reset -+ * bit in BXT_PHY_CTL_FAMILY, which effectively enables the phy. -+ */ -+ int reset_delay; -+ -+ /** -+ * @pwron_mask: Mask with the appropriate bit set that would cause the -+ * punit to power this phy if written to BXT_P_CR_GT_DISP_PWRON. -+ */ -+ u32 pwron_mask; -+ -+ /** -+ * @channel: struct containing per channel information. -+ */ -+ struct { -+ /** -+ * @channel.port: which port maps to this channel. -+ */ -+ enum port port; -+ } channel[2]; -+}; -+ -+static const struct bxt_ddi_phy_info bxt_ddi_phy_info[] = { -+ [DPIO_PHY0] = { -+ .dual_channel = true, -+ .rcomp_phy = DPIO_PHY1, -+ .pwron_mask = BIT(0), -+ -+ .channel = { -+ [DPIO_CH0] = { .port = PORT_B }, -+ [DPIO_CH1] = { .port = PORT_C }, -+ } -+ }, -+ [DPIO_PHY1] = { -+ .dual_channel = false, -+ .rcomp_phy = -1, -+ .pwron_mask = BIT(1), -+ -+ .channel = { -+ [DPIO_CH0] = { .port = PORT_A }, -+ } -+ }, -+}; -+ -+static const struct bxt_ddi_phy_info glk_ddi_phy_info[] = { -+ [DPIO_PHY0] = { -+ .dual_channel = false, -+ .rcomp_phy = DPIO_PHY1, -+ .pwron_mask = BIT(0), -+ .reset_delay = 20, -+ -+ .channel = { -+ [DPIO_CH0] = { .port = PORT_B }, -+ } -+ }, -+ [DPIO_PHY1] = { -+ .dual_channel = false, -+ .rcomp_phy = -1, -+ .pwron_mask = BIT(3), -+ .reset_delay = 20, -+ -+ .channel = { -+ [DPIO_CH0] = { .port = PORT_A }, -+ } -+ }, -+ [DPIO_PHY2] = { -+ .dual_channel = false, -+ .rcomp_phy = DPIO_PHY1, -+ .pwron_mask = BIT(1), -+ .reset_delay = 20, -+ -+ .channel = { -+ [DPIO_CH0] = { .port = PORT_C }, -+ } -+ }, -+}; -+ -+static const struct bxt_ddi_phy_info * -+bxt_get_phy_list(struct drm_i915_private *dev_priv, int *count) -+{ -+ if (IS_GEMINILAKE(dev_priv)) { -+ *count = ARRAY_SIZE(glk_ddi_phy_info); -+ return glk_ddi_phy_info; -+ } else { -+ *count = ARRAY_SIZE(bxt_ddi_phy_info); -+ return bxt_ddi_phy_info; -+ } -+} -+ -+static const struct bxt_ddi_phy_info * -+bxt_get_phy_info(struct drm_i915_private *dev_priv, enum dpio_phy phy) -+{ -+ int count; -+ const struct bxt_ddi_phy_info *phy_list = -+ bxt_get_phy_list(dev_priv, &count); -+ -+ return &phy_list[phy]; -+} -+ -+void bxt_port_to_phy_channel(struct drm_i915_private *dev_priv, enum port port, -+ enum dpio_phy *phy, enum dpio_channel *ch) -+{ -+ const struct bxt_ddi_phy_info *phy_info, *phys; -+ int i, count; -+ -+ phys = bxt_get_phy_list(dev_priv, &count); -+ -+ for (i = 0; i < count; i++) { -+ phy_info = &phys[i]; -+ -+ if (port == phy_info->channel[DPIO_CH0].port) { -+ *phy = i; -+ *ch = DPIO_CH0; -+ return; -+ } -+ -+ if (phy_info->dual_channel && -+ port == phy_info->channel[DPIO_CH1].port) { -+ *phy = i; -+ *ch = DPIO_CH1; -+ return; -+ } -+ } -+ -+ WARN(1, "PHY not found for PORT %c", port_name(port)); -+ *phy = DPIO_PHY0; -+ *ch = DPIO_CH0; -+} -+ -+void bxt_ddi_phy_set_signal_level(struct drm_i915_private *dev_priv, -+ enum port port, u32 margin, u32 scale, -+ u32 enable, u32 deemphasis) -+{ -+ u32 val; -+ enum dpio_phy phy; -+ enum dpio_channel ch; -+ -+ bxt_port_to_phy_channel(dev_priv, port, &phy, &ch); -+ -+ /* -+ * While we write to the group register to program all lanes at once we -+ * can read only lane registers and we pick lanes 0/1 for that. -+ */ -+ val = I915_READ(BXT_PORT_PCS_DW10_LN01(phy, ch)); -+ val &= ~(TX2_SWING_CALC_INIT | TX1_SWING_CALC_INIT); -+ I915_WRITE(BXT_PORT_PCS_DW10_GRP(phy, ch), val); -+ -+ val = I915_READ(BXT_PORT_TX_DW2_LN0(phy, ch)); -+ val &= ~(MARGIN_000 | UNIQ_TRANS_SCALE); -+ val |= margin << MARGIN_000_SHIFT | scale << UNIQ_TRANS_SCALE_SHIFT; -+ I915_WRITE(BXT_PORT_TX_DW2_GRP(phy, ch), val); -+ -+ val = I915_READ(BXT_PORT_TX_DW3_LN0(phy, ch)); -+ val &= ~SCALE_DCOMP_METHOD; -+ if (enable) -+ val |= SCALE_DCOMP_METHOD; -+ -+ if ((val & UNIQUE_TRANGE_EN_METHOD) && !(val & SCALE_DCOMP_METHOD)) -+ DRM_ERROR("Disabled scaling while ouniqetrangenmethod was set"); -+ -+ I915_WRITE(BXT_PORT_TX_DW3_GRP(phy, ch), val); -+ -+ val = I915_READ(BXT_PORT_TX_DW4_LN0(phy, ch)); -+ val &= ~DE_EMPHASIS; -+ val |= deemphasis << DEEMPH_SHIFT; -+ I915_WRITE(BXT_PORT_TX_DW4_GRP(phy, ch), val); -+ -+ val = I915_READ(BXT_PORT_PCS_DW10_LN01(phy, ch)); -+ val |= TX2_SWING_CALC_INIT | TX1_SWING_CALC_INIT; -+ I915_WRITE(BXT_PORT_PCS_DW10_GRP(phy, ch), val); -+} -+ -+bool bxt_ddi_phy_is_enabled(struct drm_i915_private *dev_priv, -+ enum dpio_phy phy) -+{ -+ const struct bxt_ddi_phy_info *phy_info; -+ -+ phy_info = bxt_get_phy_info(dev_priv, phy); -+ -+ if (!(I915_READ(BXT_P_CR_GT_DISP_PWRON) & phy_info->pwron_mask)) -+ return false; -+ -+ if ((I915_READ(BXT_PORT_CL1CM_DW0(phy)) & -+ (PHY_POWER_GOOD | PHY_RESERVED)) != PHY_POWER_GOOD) { -+ DRM_DEBUG_DRIVER("DDI PHY %d powered, but power hasn't settled\n", -+ phy); -+ -+ return false; -+ } -+ -+ if (!(I915_READ(BXT_PHY_CTL_FAMILY(phy)) & COMMON_RESET_DIS)) { -+ DRM_DEBUG_DRIVER("DDI PHY %d powered, but still in reset\n", -+ phy); -+ -+ return false; -+ } -+ -+ return true; -+} -+ -+static u32 bxt_get_grc(struct drm_i915_private *dev_priv, enum dpio_phy phy) -+{ -+ u32 val = I915_READ(BXT_PORT_REF_DW6(phy)); -+ -+ return (val & GRC_CODE_MASK) >> GRC_CODE_SHIFT; -+} -+ -+static void bxt_phy_wait_grc_done(struct drm_i915_private *dev_priv, -+ enum dpio_phy phy) -+{ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ BXT_PORT_REF_DW3(phy), -+ GRC_DONE, GRC_DONE, -+ 10)) -+ DRM_ERROR("timeout waiting for PHY%d GRC\n", phy); -+} -+ -+static void _bxt_ddi_phy_init(struct drm_i915_private *dev_priv, -+ enum dpio_phy phy) -+{ -+ const struct bxt_ddi_phy_info *phy_info; -+ u32 val; -+ -+ phy_info = bxt_get_phy_info(dev_priv, phy); -+ -+ if (bxt_ddi_phy_is_enabled(dev_priv, phy)) { -+ /* Still read out the GRC value for state verification */ -+ if (phy_info->rcomp_phy != -1) -+ dev_priv->bxt_phy_grc = bxt_get_grc(dev_priv, phy); -+ -+ if (bxt_ddi_phy_verify_state(dev_priv, phy)) { -+ DRM_DEBUG_DRIVER("DDI PHY %d already enabled, " -+ "won't reprogram it\n", phy); -+ return; -+ } -+ -+ DRM_DEBUG_DRIVER("DDI PHY %d enabled with invalid state, " -+ "force reprogramming it\n", phy); -+ } -+ -+ val = I915_READ(BXT_P_CR_GT_DISP_PWRON); -+ val |= phy_info->pwron_mask; -+ I915_WRITE(BXT_P_CR_GT_DISP_PWRON, val); -+ -+ /* -+ * The PHY registers start out inaccessible and respond to reads with -+ * all 1s. Eventually they become accessible as they power up, then -+ * the reserved bit will give the default 0. Poll on the reserved bit -+ * becoming 0 to find when the PHY is accessible. -+ * The flag should get set in 100us according to the HW team, but -+ * use 1ms due to occasional timeouts observed with that. -+ */ -+ if (intel_wait_for_register_fw(&dev_priv->uncore, -+ BXT_PORT_CL1CM_DW0(phy), -+ PHY_RESERVED | PHY_POWER_GOOD, -+ PHY_POWER_GOOD, -+ 1)) -+ DRM_ERROR("timeout during PHY%d power on\n", phy); -+ -+ /* Program PLL Rcomp code offset */ -+ val = I915_READ(BXT_PORT_CL1CM_DW9(phy)); -+ val &= ~IREF0RC_OFFSET_MASK; -+ val |= 0xE4 << IREF0RC_OFFSET_SHIFT; -+ I915_WRITE(BXT_PORT_CL1CM_DW9(phy), val); -+ -+ val = I915_READ(BXT_PORT_CL1CM_DW10(phy)); -+ val &= ~IREF1RC_OFFSET_MASK; -+ val |= 0xE4 << IREF1RC_OFFSET_SHIFT; -+ I915_WRITE(BXT_PORT_CL1CM_DW10(phy), val); -+ -+ /* Program power gating */ -+ val = I915_READ(BXT_PORT_CL1CM_DW28(phy)); -+ val |= OCL1_POWER_DOWN_EN | DW28_OLDO_DYN_PWR_DOWN_EN | -+ SUS_CLK_CONFIG; -+ I915_WRITE(BXT_PORT_CL1CM_DW28(phy), val); -+ -+ if (phy_info->dual_channel) { -+ val = I915_READ(BXT_PORT_CL2CM_DW6(phy)); -+ val |= DW6_OLDO_DYN_PWR_DOWN_EN; -+ I915_WRITE(BXT_PORT_CL2CM_DW6(phy), val); -+ } -+ -+ if (phy_info->rcomp_phy != -1) { -+ u32 grc_code; -+ -+ bxt_phy_wait_grc_done(dev_priv, phy_info->rcomp_phy); -+ -+ /* -+ * PHY0 isn't connected to an RCOMP resistor so copy over -+ * the corresponding calibrated value from PHY1, and disable -+ * the automatic calibration on PHY0. -+ */ -+ val = dev_priv->bxt_phy_grc = bxt_get_grc(dev_priv, -+ phy_info->rcomp_phy); -+ grc_code = val << GRC_CODE_FAST_SHIFT | -+ val << GRC_CODE_SLOW_SHIFT | -+ val; -+ I915_WRITE(BXT_PORT_REF_DW6(phy), grc_code); -+ -+ val = I915_READ(BXT_PORT_REF_DW8(phy)); -+ val |= GRC_DIS | GRC_RDY_OVRD; -+ I915_WRITE(BXT_PORT_REF_DW8(phy), val); -+ } -+ -+ if (phy_info->reset_delay) -+ udelay(phy_info->reset_delay); -+ -+ val = I915_READ(BXT_PHY_CTL_FAMILY(phy)); -+ val |= COMMON_RESET_DIS; -+ I915_WRITE(BXT_PHY_CTL_FAMILY(phy), val); -+} -+ -+void bxt_ddi_phy_uninit(struct drm_i915_private *dev_priv, enum dpio_phy phy) -+{ -+ const struct bxt_ddi_phy_info *phy_info; -+ u32 val; -+ -+ phy_info = bxt_get_phy_info(dev_priv, phy); -+ -+ val = I915_READ(BXT_PHY_CTL_FAMILY(phy)); -+ val &= ~COMMON_RESET_DIS; -+ I915_WRITE(BXT_PHY_CTL_FAMILY(phy), val); -+ -+ val = I915_READ(BXT_P_CR_GT_DISP_PWRON); -+ val &= ~phy_info->pwron_mask; -+ I915_WRITE(BXT_P_CR_GT_DISP_PWRON, val); -+} -+ -+void bxt_ddi_phy_init(struct drm_i915_private *dev_priv, enum dpio_phy phy) -+{ -+ const struct bxt_ddi_phy_info *phy_info = -+ bxt_get_phy_info(dev_priv, phy); -+ enum dpio_phy rcomp_phy = phy_info->rcomp_phy; -+ bool was_enabled; -+ -+ lockdep_assert_held(&dev_priv->power_domains.lock); -+ -+ was_enabled = true; -+ if (rcomp_phy != -1) -+ was_enabled = bxt_ddi_phy_is_enabled(dev_priv, rcomp_phy); -+ -+ /* -+ * We need to copy the GRC calibration value from rcomp_phy, -+ * so make sure it's powered up. -+ */ -+ if (!was_enabled) -+ _bxt_ddi_phy_init(dev_priv, rcomp_phy); -+ -+ _bxt_ddi_phy_init(dev_priv, phy); -+ -+ if (!was_enabled) -+ bxt_ddi_phy_uninit(dev_priv, rcomp_phy); -+} -+ -+static bool __printf(6, 7) -+__phy_reg_verify_state(struct drm_i915_private *dev_priv, enum dpio_phy phy, -+ i915_reg_t reg, u32 mask, u32 expected, -+ const char *reg_fmt, ...) -+{ -+ struct va_format vaf; -+ va_list args; -+ u32 val; -+ -+ val = I915_READ(reg); -+ if ((val & mask) == expected) -+ return true; -+ -+ va_start(args, reg_fmt); -+ vaf.fmt = reg_fmt; -+ vaf.va = &args; -+ -+ DRM_DEBUG_DRIVER("DDI PHY %d reg %pV [%08x] state mismatch: " -+ "current %08x, expected %08x (mask %08x)\n", -+ phy, &vaf, reg.reg, val, (val & ~mask) | expected, -+ mask); -+ -+ va_end(args); -+ -+ return false; -+} -+ -+bool bxt_ddi_phy_verify_state(struct drm_i915_private *dev_priv, -+ enum dpio_phy phy) -+{ -+ const struct bxt_ddi_phy_info *phy_info; -+ u32 mask; -+ bool ok; -+ -+ phy_info = bxt_get_phy_info(dev_priv, phy); -+ -+#define _CHK(reg, mask, exp, fmt, ...) \ -+ __phy_reg_verify_state(dev_priv, phy, reg, mask, exp, fmt, \ -+ ## __VA_ARGS__) -+ -+ if (!bxt_ddi_phy_is_enabled(dev_priv, phy)) -+ return false; -+ -+ ok = true; -+ -+ /* PLL Rcomp code offset */ -+ ok &= _CHK(BXT_PORT_CL1CM_DW9(phy), -+ IREF0RC_OFFSET_MASK, 0xe4 << IREF0RC_OFFSET_SHIFT, -+ "BXT_PORT_CL1CM_DW9(%d)", phy); -+ ok &= _CHK(BXT_PORT_CL1CM_DW10(phy), -+ IREF1RC_OFFSET_MASK, 0xe4 << IREF1RC_OFFSET_SHIFT, -+ "BXT_PORT_CL1CM_DW10(%d)", phy); -+ -+ /* Power gating */ -+ mask = OCL1_POWER_DOWN_EN | DW28_OLDO_DYN_PWR_DOWN_EN | SUS_CLK_CONFIG; -+ ok &= _CHK(BXT_PORT_CL1CM_DW28(phy), mask, mask, -+ "BXT_PORT_CL1CM_DW28(%d)", phy); -+ -+ if (phy_info->dual_channel) -+ ok &= _CHK(BXT_PORT_CL2CM_DW6(phy), -+ DW6_OLDO_DYN_PWR_DOWN_EN, DW6_OLDO_DYN_PWR_DOWN_EN, -+ "BXT_PORT_CL2CM_DW6(%d)", phy); -+ -+ if (phy_info->rcomp_phy != -1) { -+ u32 grc_code = dev_priv->bxt_phy_grc; -+ -+ grc_code = grc_code << GRC_CODE_FAST_SHIFT | -+ grc_code << GRC_CODE_SLOW_SHIFT | -+ grc_code; -+ mask = GRC_CODE_FAST_MASK | GRC_CODE_SLOW_MASK | -+ GRC_CODE_NOM_MASK; -+ ok &= _CHK(BXT_PORT_REF_DW6(phy), mask, grc_code, -+ "BXT_PORT_REF_DW6(%d)", phy); -+ -+ mask = GRC_DIS | GRC_RDY_OVRD; -+ ok &= _CHK(BXT_PORT_REF_DW8(phy), mask, mask, -+ "BXT_PORT_REF_DW8(%d)", phy); -+ } -+ -+ return ok; -+#undef _CHK -+} -+ -+u8 -+bxt_ddi_phy_calc_lane_lat_optim_mask(u8 lane_count) -+{ -+ switch (lane_count) { -+ case 1: -+ return 0; -+ case 2: -+ return BIT(2) | BIT(0); -+ case 4: -+ return BIT(3) | BIT(2) | BIT(0); -+ default: -+ MISSING_CASE(lane_count); -+ -+ return 0; -+ } -+} -+ -+void bxt_ddi_phy_set_lane_optim_mask(struct intel_encoder *encoder, -+ u8 lane_lat_optim_mask) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ enum dpio_phy phy; -+ enum dpio_channel ch; -+ int lane; -+ -+ bxt_port_to_phy_channel(dev_priv, port, &phy, &ch); -+ -+ for (lane = 0; lane < 4; lane++) { -+ u32 val = I915_READ(BXT_PORT_TX_DW14_LN(phy, ch, lane)); -+ -+ /* -+ * Note that on CHV this flag is called UPAR, but has -+ * the same function. -+ */ -+ val &= ~LATENCY_OPTIM; -+ if (lane_lat_optim_mask & BIT(lane)) -+ val |= LATENCY_OPTIM; -+ -+ I915_WRITE(BXT_PORT_TX_DW14_LN(phy, ch, lane), val); -+ } -+} -+ -+u8 -+bxt_ddi_phy_get_lane_lat_optim_mask(struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum port port = encoder->port; -+ enum dpio_phy phy; -+ enum dpio_channel ch; -+ int lane; -+ u8 mask; -+ -+ bxt_port_to_phy_channel(dev_priv, port, &phy, &ch); -+ -+ mask = 0; -+ for (lane = 0; lane < 4; lane++) { -+ u32 val = I915_READ(BXT_PORT_TX_DW14_LN(phy, ch, lane)); -+ -+ if (val & LATENCY_OPTIM) -+ mask |= BIT(lane); -+ } -+ -+ return mask; -+} -+ -+ -+void chv_set_phy_signal_level(struct intel_encoder *encoder, -+ u32 deemph_reg_value, u32 margin_reg_value, -+ bool uniq_trans_scale) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_digital_port *dport = enc_to_dig_port(&encoder->base); -+ struct intel_crtc *intel_crtc = to_intel_crtc(encoder->base.crtc); -+ enum dpio_channel ch = vlv_dport_to_channel(dport); -+ enum pipe pipe = intel_crtc->pipe; -+ u32 val; -+ int i; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ /* Clear calc init */ -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW10(ch)); -+ val &= ~(DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3); -+ val &= ~(DPIO_PCS_TX1DEEMP_MASK | DPIO_PCS_TX2DEEMP_MASK); -+ val |= DPIO_PCS_TX1DEEMP_9P5 | DPIO_PCS_TX2DEEMP_9P5; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW10(ch), val); -+ -+ if (intel_crtc->config->lane_count > 2) { -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW10(ch)); -+ val &= ~(DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3); -+ val &= ~(DPIO_PCS_TX1DEEMP_MASK | DPIO_PCS_TX2DEEMP_MASK); -+ val |= DPIO_PCS_TX1DEEMP_9P5 | DPIO_PCS_TX2DEEMP_9P5; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW10(ch), val); -+ } -+ -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW9(ch)); -+ val &= ~(DPIO_PCS_TX1MARGIN_MASK | DPIO_PCS_TX2MARGIN_MASK); -+ val |= DPIO_PCS_TX1MARGIN_000 | DPIO_PCS_TX2MARGIN_000; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW9(ch), val); -+ -+ if (intel_crtc->config->lane_count > 2) { -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW9(ch)); -+ val &= ~(DPIO_PCS_TX1MARGIN_MASK | DPIO_PCS_TX2MARGIN_MASK); -+ val |= DPIO_PCS_TX1MARGIN_000 | DPIO_PCS_TX2MARGIN_000; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW9(ch), val); -+ } -+ -+ /* Program swing deemph */ -+ for (i = 0; i < intel_crtc->config->lane_count; i++) { -+ val = vlv_dpio_read(dev_priv, pipe, CHV_TX_DW4(ch, i)); -+ val &= ~DPIO_SWING_DEEMPH9P5_MASK; -+ val |= deemph_reg_value << DPIO_SWING_DEEMPH9P5_SHIFT; -+ vlv_dpio_write(dev_priv, pipe, CHV_TX_DW4(ch, i), val); -+ } -+ -+ /* Program swing margin */ -+ for (i = 0; i < intel_crtc->config->lane_count; i++) { -+ val = vlv_dpio_read(dev_priv, pipe, CHV_TX_DW2(ch, i)); -+ -+ val &= ~DPIO_SWING_MARGIN000_MASK; -+ val |= margin_reg_value << DPIO_SWING_MARGIN000_SHIFT; -+ -+ /* -+ * Supposedly this value shouldn't matter when unique transition -+ * scale is disabled, but in fact it does matter. Let's just -+ * always program the same value and hope it's OK. -+ */ -+ val &= ~(0xff << DPIO_UNIQ_TRANS_SCALE_SHIFT); -+ val |= 0x9a << DPIO_UNIQ_TRANS_SCALE_SHIFT; -+ -+ vlv_dpio_write(dev_priv, pipe, CHV_TX_DW2(ch, i), val); -+ } -+ -+ /* -+ * The document said it needs to set bit 27 for ch0 and bit 26 -+ * for ch1. Might be a typo in the doc. -+ * For now, for this unique transition scale selection, set bit -+ * 27 for ch0 and ch1. -+ */ -+ for (i = 0; i < intel_crtc->config->lane_count; i++) { -+ val = vlv_dpio_read(dev_priv, pipe, CHV_TX_DW3(ch, i)); -+ if (uniq_trans_scale) -+ val |= DPIO_TX_UNIQ_TRANS_SCALE_EN; -+ else -+ val &= ~DPIO_TX_UNIQ_TRANS_SCALE_EN; -+ vlv_dpio_write(dev_priv, pipe, CHV_TX_DW3(ch, i), val); -+ } -+ -+ /* Start swing calculation */ -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW10(ch)); -+ val |= DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW10(ch), val); -+ -+ if (intel_crtc->config->lane_count > 2) { -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW10(ch)); -+ val |= DPIO_PCS_SWING_CALC_TX0_TX2 | DPIO_PCS_SWING_CALC_TX1_TX3; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW10(ch), val); -+ } -+ -+ mutex_unlock(&dev_priv->sb_lock); -+ -+} -+ -+void chv_data_lane_soft_reset(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ bool reset) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum dpio_channel ch = vlv_dport_to_channel(enc_to_dig_port(&encoder->base)); -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ enum pipe pipe = crtc->pipe; -+ u32 val; -+ -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW0(ch)); -+ if (reset) -+ val &= ~(DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET); -+ else -+ val |= DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW0(ch), val); -+ -+ if (crtc_state->lane_count > 2) { -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW0(ch)); -+ if (reset) -+ val &= ~(DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET); -+ else -+ val |= DPIO_PCS_TX_LANE2_RESET | DPIO_PCS_TX_LANE1_RESET; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW0(ch), val); -+ } -+ -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW1(ch)); -+ val |= CHV_PCS_REQ_SOFTRESET_EN; -+ if (reset) -+ val &= ~DPIO_PCS_CLK_SOFT_RESET; -+ else -+ val |= DPIO_PCS_CLK_SOFT_RESET; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW1(ch), val); -+ -+ if (crtc_state->lane_count > 2) { -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW1(ch)); -+ val |= CHV_PCS_REQ_SOFTRESET_EN; -+ if (reset) -+ val &= ~DPIO_PCS_CLK_SOFT_RESET; -+ else -+ val |= DPIO_PCS_CLK_SOFT_RESET; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW1(ch), val); -+ } -+} -+ -+void chv_phy_pre_pll_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_digital_port *dport = enc_to_dig_port(&encoder->base); -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ enum dpio_channel ch = vlv_dport_to_channel(dport); -+ enum pipe pipe = crtc->pipe; -+ unsigned int lane_mask = -+ intel_dp_unused_lane_mask(crtc_state->lane_count); -+ u32 val; -+ -+ /* -+ * Must trick the second common lane into life. -+ * Otherwise we can't even access the PLL. -+ */ -+ if (ch == DPIO_CH0 && pipe == PIPE_B) -+ dport->release_cl2_override = -+ !chv_phy_powergate_ch(dev_priv, DPIO_PHY0, DPIO_CH1, true); -+ -+ chv_phy_powergate_lanes(encoder, true, lane_mask); -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ /* Assert data lane reset */ -+ chv_data_lane_soft_reset(encoder, crtc_state, true); -+ -+ /* program left/right clock distribution */ -+ if (pipe != PIPE_B) { -+ val = vlv_dpio_read(dev_priv, pipe, _CHV_CMN_DW5_CH0); -+ val &= ~(CHV_BUFLEFTENA1_MASK | CHV_BUFRIGHTENA1_MASK); -+ if (ch == DPIO_CH0) -+ val |= CHV_BUFLEFTENA1_FORCE; -+ if (ch == DPIO_CH1) -+ val |= CHV_BUFRIGHTENA1_FORCE; -+ vlv_dpio_write(dev_priv, pipe, _CHV_CMN_DW5_CH0, val); -+ } else { -+ val = vlv_dpio_read(dev_priv, pipe, _CHV_CMN_DW1_CH1); -+ val &= ~(CHV_BUFLEFTENA2_MASK | CHV_BUFRIGHTENA2_MASK); -+ if (ch == DPIO_CH0) -+ val |= CHV_BUFLEFTENA2_FORCE; -+ if (ch == DPIO_CH1) -+ val |= CHV_BUFRIGHTENA2_FORCE; -+ vlv_dpio_write(dev_priv, pipe, _CHV_CMN_DW1_CH1, val); -+ } -+ -+ /* program clock channel usage */ -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW8(ch)); -+ val |= CHV_PCS_USEDCLKCHANNEL_OVRRIDE; -+ if (pipe != PIPE_B) -+ val &= ~CHV_PCS_USEDCLKCHANNEL; -+ else -+ val |= CHV_PCS_USEDCLKCHANNEL; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW8(ch), val); -+ -+ if (crtc_state->lane_count > 2) { -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW8(ch)); -+ val |= CHV_PCS_USEDCLKCHANNEL_OVRRIDE; -+ if (pipe != PIPE_B) -+ val &= ~CHV_PCS_USEDCLKCHANNEL; -+ else -+ val |= CHV_PCS_USEDCLKCHANNEL; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW8(ch), val); -+ } -+ -+ /* -+ * This a a bit weird since generally CL -+ * matches the pipe, but here we need to -+ * pick the CL based on the port. -+ */ -+ val = vlv_dpio_read(dev_priv, pipe, CHV_CMN_DW19(ch)); -+ if (pipe != PIPE_B) -+ val &= ~CHV_CMN_USEDCLKCHANNEL; -+ else -+ val |= CHV_CMN_USEDCLKCHANNEL; -+ vlv_dpio_write(dev_priv, pipe, CHV_CMN_DW19(ch), val); -+ -+ mutex_unlock(&dev_priv->sb_lock); -+} -+ -+void chv_phy_pre_encoder_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ struct intel_digital_port *dport = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ enum dpio_channel ch = vlv_dport_to_channel(dport); -+ enum pipe pipe = crtc->pipe; -+ int data, i, stagger; -+ u32 val; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ /* allow hardware to manage TX FIFO reset source */ -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW11(ch)); -+ val &= ~DPIO_LANEDESKEW_STRAP_OVRD; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW11(ch), val); -+ -+ if (crtc_state->lane_count > 2) { -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW11(ch)); -+ val &= ~DPIO_LANEDESKEW_STRAP_OVRD; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW11(ch), val); -+ } -+ -+ /* Program Tx lane latency optimal setting*/ -+ for (i = 0; i < crtc_state->lane_count; i++) { -+ /* Set the upar bit */ -+ if (crtc_state->lane_count == 1) -+ data = 0x0; -+ else -+ data = (i == 1) ? 0x0 : 0x1; -+ vlv_dpio_write(dev_priv, pipe, CHV_TX_DW14(ch, i), -+ data << DPIO_UPAR_SHIFT); -+ } -+ -+ /* Data lane stagger programming */ -+ if (crtc_state->port_clock > 270000) -+ stagger = 0x18; -+ else if (crtc_state->port_clock > 135000) -+ stagger = 0xd; -+ else if (crtc_state->port_clock > 67500) -+ stagger = 0x7; -+ else if (crtc_state->port_clock > 33750) -+ stagger = 0x4; -+ else -+ stagger = 0x2; -+ -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW11(ch)); -+ val |= DPIO_TX2_STAGGER_MASK(0x1f); -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW11(ch), val); -+ -+ if (crtc_state->lane_count > 2) { -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS23_DW11(ch)); -+ val |= DPIO_TX2_STAGGER_MASK(0x1f); -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW11(ch), val); -+ } -+ -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS01_DW12(ch), -+ DPIO_LANESTAGGER_STRAP(stagger) | -+ DPIO_LANESTAGGER_STRAP_OVRD | -+ DPIO_TX1_STAGGER_MASK(0x1f) | -+ DPIO_TX1_STAGGER_MULT(6) | -+ DPIO_TX2_STAGGER_MULT(0)); -+ -+ if (crtc_state->lane_count > 2) { -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS23_DW12(ch), -+ DPIO_LANESTAGGER_STRAP(stagger) | -+ DPIO_LANESTAGGER_STRAP_OVRD | -+ DPIO_TX1_STAGGER_MASK(0x1f) | -+ DPIO_TX1_STAGGER_MULT(7) | -+ DPIO_TX2_STAGGER_MULT(5)); -+ } -+ -+ /* Deassert data lane reset */ -+ chv_data_lane_soft_reset(encoder, crtc_state, false); -+ -+ mutex_unlock(&dev_priv->sb_lock); -+} -+ -+void chv_phy_release_cl2_override(struct intel_encoder *encoder) -+{ -+ struct intel_digital_port *dport = enc_to_dig_port(&encoder->base); -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ -+ if (dport->release_cl2_override) { -+ chv_phy_powergate_ch(dev_priv, DPIO_PHY0, DPIO_CH1, false); -+ dport->release_cl2_override = false; -+ } -+} -+ -+void chv_phy_post_pll_disable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ enum pipe pipe = to_intel_crtc(old_crtc_state->base.crtc)->pipe; -+ u32 val; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ /* disable left/right clock distribution */ -+ if (pipe != PIPE_B) { -+ val = vlv_dpio_read(dev_priv, pipe, _CHV_CMN_DW5_CH0); -+ val &= ~(CHV_BUFLEFTENA1_MASK | CHV_BUFRIGHTENA1_MASK); -+ vlv_dpio_write(dev_priv, pipe, _CHV_CMN_DW5_CH0, val); -+ } else { -+ val = vlv_dpio_read(dev_priv, pipe, _CHV_CMN_DW1_CH1); -+ val &= ~(CHV_BUFLEFTENA2_MASK | CHV_BUFRIGHTENA2_MASK); -+ vlv_dpio_write(dev_priv, pipe, _CHV_CMN_DW1_CH1, val); -+ } -+ -+ mutex_unlock(&dev_priv->sb_lock); -+ -+ /* -+ * Leave the power down bit cleared for at least one -+ * lane so that chv_powergate_phy_ch() will power -+ * on something when the channel is otherwise unused. -+ * When the port is off and the override is removed -+ * the lanes power down anyway, so otherwise it doesn't -+ * really matter what the state of power down bits is -+ * after this. -+ */ -+ chv_phy_powergate_lanes(encoder, false, 0x0); -+} -+ -+void vlv_set_phy_signal_level(struct intel_encoder *encoder, -+ u32 demph_reg_value, u32 preemph_reg_value, -+ u32 uniqtranscale_reg_value, u32 tx3_demph) -+{ -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crtc *intel_crtc = to_intel_crtc(encoder->base.crtc); -+ struct intel_digital_port *dport = enc_to_dig_port(&encoder->base); -+ enum dpio_channel port = vlv_dport_to_channel(dport); -+ enum pipe pipe = intel_crtc->pipe; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ vlv_dpio_write(dev_priv, pipe, VLV_TX_DW5(port), 0x00000000); -+ vlv_dpio_write(dev_priv, pipe, VLV_TX_DW4(port), demph_reg_value); -+ vlv_dpio_write(dev_priv, pipe, VLV_TX_DW2(port), -+ uniqtranscale_reg_value); -+ vlv_dpio_write(dev_priv, pipe, VLV_TX_DW3(port), 0x0C782040); -+ -+ if (tx3_demph) -+ vlv_dpio_write(dev_priv, pipe, VLV_TX3_DW4(port), tx3_demph); -+ -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW11(port), 0x00030000); -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW9(port), preemph_reg_value); -+ vlv_dpio_write(dev_priv, pipe, VLV_TX_DW5(port), DPIO_TX_OCALINIT_EN); -+ mutex_unlock(&dev_priv->sb_lock); -+} -+ -+void vlv_phy_pre_pll_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_digital_port *dport = enc_to_dig_port(&encoder->base); -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ enum dpio_channel port = vlv_dport_to_channel(dport); -+ enum pipe pipe = crtc->pipe; -+ -+ /* Program Tx lane resets to default */ -+ mutex_lock(&dev_priv->sb_lock); -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW0(port), -+ DPIO_PCS_TX_LANE2_RESET | -+ DPIO_PCS_TX_LANE1_RESET); -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW1(port), -+ DPIO_PCS_CLK_CRI_RXEB_EIOS_EN | -+ DPIO_PCS_CLK_CRI_RXDIGFILTSG_EN | -+ (1<sb_lock); -+} -+ -+void vlv_phy_pre_encoder_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_dp *intel_dp = enc_to_intel_dp(&encoder->base); -+ struct intel_digital_port *dport = dp_to_dig_port(intel_dp); -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ enum dpio_channel port = vlv_dport_to_channel(dport); -+ enum pipe pipe = crtc->pipe; -+ u32 val; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ -+ /* Enable clock channels for this port */ -+ val = vlv_dpio_read(dev_priv, pipe, VLV_PCS01_DW8(port)); -+ val = 0; -+ if (pipe) -+ val |= (1<<21); -+ else -+ val &= ~(1<<21); -+ val |= 0x001000c4; -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW8(port), val); -+ -+ /* Program lane clock */ -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW14(port), 0x00760018); -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW23(port), 0x00400888); -+ -+ mutex_unlock(&dev_priv->sb_lock); -+} -+ -+void vlv_phy_reset_lanes(struct intel_encoder *encoder, -+ const struct intel_crtc_state *old_crtc_state) -+{ -+ struct intel_digital_port *dport = enc_to_dig_port(&encoder->base); -+ struct drm_i915_private *dev_priv = to_i915(encoder->base.dev); -+ struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->base.crtc); -+ enum dpio_channel port = vlv_dport_to_channel(dport); -+ enum pipe pipe = crtc->pipe; -+ -+ mutex_lock(&dev_priv->sb_lock); -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW0(port), 0x00000000); -+ vlv_dpio_write(dev_priv, pipe, VLV_PCS_DW1(port), 0x00e00060); -+ mutex_unlock(&dev_priv->sb_lock); -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_dpll_mgr.c b/drivers/gpu/drm/i915_legacy/intel_dpll_mgr.c -new file mode 100644 -index 000000000000..e01c057ce50b ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_dpll_mgr.c -@@ -0,0 +1,3382 @@ -+/* -+ * Copyright © 2006-2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#include "intel_drv.h" -+ -+/** -+ * DOC: Display PLLs -+ * -+ * Display PLLs used for driving outputs vary by platform. While some have -+ * per-pipe or per-encoder dedicated PLLs, others allow the use of any PLL -+ * from a pool. In the latter scenario, it is possible that multiple pipes -+ * share a PLL if their configurations match. -+ * -+ * This file provides an abstraction over display PLLs. The function -+ * intel_shared_dpll_init() initializes the PLLs for the given platform. The -+ * users of a PLL are tracked and that tracking is integrated with the atomic -+ * modest interface. During an atomic operation, a PLL can be requested for a -+ * given CRTC and encoder configuration by calling intel_get_shared_dpll() and -+ * a previously used PLL can be released with intel_release_shared_dpll(). -+ * Changes to the users are first staged in the atomic state, and then made -+ * effective by calling intel_shared_dpll_swap_state() during the atomic -+ * commit phase. -+ */ -+ -+static void -+intel_atomic_duplicate_dpll_state(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll_state *shared_dpll) -+{ -+ enum intel_dpll_id i; -+ -+ /* Copy shared dpll state */ -+ for (i = 0; i < dev_priv->num_shared_dpll; i++) { -+ struct intel_shared_dpll *pll = &dev_priv->shared_dplls[i]; -+ -+ shared_dpll[i] = pll->state; -+ } -+} -+ -+static struct intel_shared_dpll_state * -+intel_atomic_get_shared_dpll_state(struct drm_atomic_state *s) -+{ -+ struct intel_atomic_state *state = to_intel_atomic_state(s); -+ -+ WARN_ON(!drm_modeset_is_locked(&s->dev->mode_config.connection_mutex)); -+ -+ if (!state->dpll_set) { -+ state->dpll_set = true; -+ -+ intel_atomic_duplicate_dpll_state(to_i915(s->dev), -+ state->shared_dpll); -+ } -+ -+ return state->shared_dpll; -+} -+ -+/** -+ * intel_get_shared_dpll_by_id - get a DPLL given its id -+ * @dev_priv: i915 device instance -+ * @id: pll id -+ * -+ * Returns: -+ * A pointer to the DPLL with @id -+ */ -+struct intel_shared_dpll * -+intel_get_shared_dpll_by_id(struct drm_i915_private *dev_priv, -+ enum intel_dpll_id id) -+{ -+ return &dev_priv->shared_dplls[id]; -+} -+ -+/** -+ * intel_get_shared_dpll_id - get the id of a DPLL -+ * @dev_priv: i915 device instance -+ * @pll: the DPLL -+ * -+ * Returns: -+ * The id of @pll -+ */ -+enum intel_dpll_id -+intel_get_shared_dpll_id(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ if (WARN_ON(pll < dev_priv->shared_dplls|| -+ pll > &dev_priv->shared_dplls[dev_priv->num_shared_dpll])) -+ return -1; -+ -+ return (enum intel_dpll_id) (pll - dev_priv->shared_dplls); -+} -+ -+/* For ILK+ */ -+void assert_shared_dpll(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ bool state) -+{ -+ bool cur_state; -+ struct intel_dpll_hw_state hw_state; -+ -+ if (WARN(!pll, "asserting DPLL %s with no DPLL\n", onoff(state))) -+ return; -+ -+ cur_state = pll->info->funcs->get_hw_state(dev_priv, pll, &hw_state); -+ I915_STATE_WARN(cur_state != state, -+ "%s assertion failure (expected %s, current %s)\n", -+ pll->info->name, onoff(state), onoff(cur_state)); -+} -+ -+/** -+ * intel_prepare_shared_dpll - call a dpll's prepare hook -+ * @crtc_state: CRTC, and its state, which has a shared dpll -+ * -+ * This calls the PLL's prepare hook if it has one and if the PLL is not -+ * already enabled. The prepare hook is platform specific. -+ */ -+void intel_prepare_shared_dpll(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct intel_shared_dpll *pll = crtc_state->shared_dpll; -+ -+ if (WARN_ON(pll == NULL)) -+ return; -+ -+ mutex_lock(&dev_priv->dpll_lock); -+ WARN_ON(!pll->state.crtc_mask); -+ if (!pll->active_mask) { -+ DRM_DEBUG_DRIVER("setting up %s\n", pll->info->name); -+ WARN_ON(pll->on); -+ assert_shared_dpll_disabled(dev_priv, pll); -+ -+ pll->info->funcs->prepare(dev_priv, pll); -+ } -+ mutex_unlock(&dev_priv->dpll_lock); -+} -+ -+/** -+ * intel_enable_shared_dpll - enable a CRTC's shared DPLL -+ * @crtc_state: CRTC, and its state, which has a shared DPLL -+ * -+ * Enable the shared DPLL used by @crtc. -+ */ -+void intel_enable_shared_dpll(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct intel_shared_dpll *pll = crtc_state->shared_dpll; -+ unsigned int crtc_mask = drm_crtc_mask(&crtc->base); -+ unsigned int old_mask; -+ -+ if (WARN_ON(pll == NULL)) -+ return; -+ -+ mutex_lock(&dev_priv->dpll_lock); -+ old_mask = pll->active_mask; -+ -+ if (WARN_ON(!(pll->state.crtc_mask & crtc_mask)) || -+ WARN_ON(pll->active_mask & crtc_mask)) -+ goto out; -+ -+ pll->active_mask |= crtc_mask; -+ -+ DRM_DEBUG_KMS("enable %s (active %x, on? %d) for crtc %d\n", -+ pll->info->name, pll->active_mask, pll->on, -+ crtc->base.base.id); -+ -+ if (old_mask) { -+ WARN_ON(!pll->on); -+ assert_shared_dpll_enabled(dev_priv, pll); -+ goto out; -+ } -+ WARN_ON(pll->on); -+ -+ DRM_DEBUG_KMS("enabling %s\n", pll->info->name); -+ pll->info->funcs->enable(dev_priv, pll); -+ pll->on = true; -+ -+out: -+ mutex_unlock(&dev_priv->dpll_lock); -+} -+ -+/** -+ * intel_disable_shared_dpll - disable a CRTC's shared DPLL -+ * @crtc_state: CRTC, and its state, which has a shared DPLL -+ * -+ * Disable the shared DPLL used by @crtc. -+ */ -+void intel_disable_shared_dpll(const struct intel_crtc_state *crtc_state) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct intel_shared_dpll *pll = crtc_state->shared_dpll; -+ unsigned int crtc_mask = drm_crtc_mask(&crtc->base); -+ -+ /* PCH only available on ILK+ */ -+ if (INTEL_GEN(dev_priv) < 5) -+ return; -+ -+ if (pll == NULL) -+ return; -+ -+ mutex_lock(&dev_priv->dpll_lock); -+ if (WARN_ON(!(pll->active_mask & crtc_mask))) -+ goto out; -+ -+ DRM_DEBUG_KMS("disable %s (active %x, on? %d) for crtc %d\n", -+ pll->info->name, pll->active_mask, pll->on, -+ crtc->base.base.id); -+ -+ assert_shared_dpll_enabled(dev_priv, pll); -+ WARN_ON(!pll->on); -+ -+ pll->active_mask &= ~crtc_mask; -+ if (pll->active_mask) -+ goto out; -+ -+ DRM_DEBUG_KMS("disabling %s\n", pll->info->name); -+ pll->info->funcs->disable(dev_priv, pll); -+ pll->on = false; -+ -+out: -+ mutex_unlock(&dev_priv->dpll_lock); -+} -+ -+static struct intel_shared_dpll * -+intel_find_shared_dpll(struct intel_crtc_state *crtc_state, -+ enum intel_dpll_id range_min, -+ enum intel_dpll_id range_max) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct intel_shared_dpll *pll, *unused_pll = NULL; -+ struct intel_shared_dpll_state *shared_dpll; -+ enum intel_dpll_id i; -+ -+ shared_dpll = intel_atomic_get_shared_dpll_state(crtc_state->base.state); -+ -+ for (i = range_min; i <= range_max; i++) { -+ pll = &dev_priv->shared_dplls[i]; -+ -+ /* Only want to check enabled timings first */ -+ if (shared_dpll[i].crtc_mask == 0) { -+ if (!unused_pll) -+ unused_pll = pll; -+ continue; -+ } -+ -+ if (memcmp(&crtc_state->dpll_hw_state, -+ &shared_dpll[i].hw_state, -+ sizeof(crtc_state->dpll_hw_state)) == 0) { -+ DRM_DEBUG_KMS("[CRTC:%d:%s] sharing existing %s (crtc mask 0x%08x, active %x)\n", -+ crtc->base.base.id, crtc->base.name, -+ pll->info->name, -+ shared_dpll[i].crtc_mask, -+ pll->active_mask); -+ return pll; -+ } -+ } -+ -+ /* Ok no matching timings, maybe there's a free one? */ -+ if (unused_pll) { -+ DRM_DEBUG_KMS("[CRTC:%d:%s] allocated %s\n", -+ crtc->base.base.id, crtc->base.name, -+ unused_pll->info->name); -+ return unused_pll; -+ } -+ -+ return NULL; -+} -+ -+static void -+intel_reference_shared_dpll(struct intel_shared_dpll *pll, -+ struct intel_crtc_state *crtc_state) -+{ -+ struct intel_shared_dpll_state *shared_dpll; -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ const enum intel_dpll_id id = pll->info->id; -+ -+ shared_dpll = intel_atomic_get_shared_dpll_state(crtc_state->base.state); -+ -+ if (shared_dpll[id].crtc_mask == 0) -+ shared_dpll[id].hw_state = -+ crtc_state->dpll_hw_state; -+ -+ crtc_state->shared_dpll = pll; -+ DRM_DEBUG_DRIVER("using %s for pipe %c\n", pll->info->name, -+ pipe_name(crtc->pipe)); -+ -+ shared_dpll[id].crtc_mask |= 1 << crtc->pipe; -+} -+ -+/** -+ * intel_shared_dpll_swap_state - make atomic DPLL configuration effective -+ * @state: atomic state -+ * -+ * This is the dpll version of drm_atomic_helper_swap_state() since the -+ * helper does not handle driver-specific global state. -+ * -+ * For consistency with atomic helpers this function does a complete swap, -+ * i.e. it also puts the current state into @state, even though there is no -+ * need for that at this moment. -+ */ -+void intel_shared_dpll_swap_state(struct drm_atomic_state *state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(state->dev); -+ struct intel_shared_dpll_state *shared_dpll; -+ struct intel_shared_dpll *pll; -+ enum intel_dpll_id i; -+ -+ if (!to_intel_atomic_state(state)->dpll_set) -+ return; -+ -+ shared_dpll = to_intel_atomic_state(state)->shared_dpll; -+ for (i = 0; i < dev_priv->num_shared_dpll; i++) { -+ struct intel_shared_dpll_state tmp; -+ -+ pll = &dev_priv->shared_dplls[i]; -+ -+ tmp = pll->state; -+ pll->state = shared_dpll[i]; -+ shared_dpll[i] = tmp; -+ } -+} -+ -+static bool ibx_pch_dpll_get_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ const enum intel_dpll_id id = pll->info->id; -+ intel_wakeref_t wakeref; -+ u32 val; -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ POWER_DOMAIN_PLLS); -+ if (!wakeref) -+ return false; -+ -+ val = I915_READ(PCH_DPLL(id)); -+ hw_state->dpll = val; -+ hw_state->fp0 = I915_READ(PCH_FP0(id)); -+ hw_state->fp1 = I915_READ(PCH_FP1(id)); -+ -+ intel_display_power_put(dev_priv, POWER_DOMAIN_PLLS, wakeref); -+ -+ return val & DPLL_VCO_ENABLE; -+} -+ -+static void ibx_pch_dpll_prepare(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ const enum intel_dpll_id id = pll->info->id; -+ -+ I915_WRITE(PCH_FP0(id), pll->state.hw_state.fp0); -+ I915_WRITE(PCH_FP1(id), pll->state.hw_state.fp1); -+} -+ -+static void ibx_assert_pch_refclk_enabled(struct drm_i915_private *dev_priv) -+{ -+ u32 val; -+ bool enabled; -+ -+ I915_STATE_WARN_ON(!(HAS_PCH_IBX(dev_priv) || HAS_PCH_CPT(dev_priv))); -+ -+ val = I915_READ(PCH_DREF_CONTROL); -+ enabled = !!(val & (DREF_SSC_SOURCE_MASK | DREF_NONSPREAD_SOURCE_MASK | -+ DREF_SUPERSPREAD_SOURCE_MASK)); -+ I915_STATE_WARN(!enabled, "PCH refclk assertion failure, should be active but is disabled\n"); -+} -+ -+static void ibx_pch_dpll_enable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ const enum intel_dpll_id id = pll->info->id; -+ -+ /* PCH refclock must be enabled first */ -+ ibx_assert_pch_refclk_enabled(dev_priv); -+ -+ I915_WRITE(PCH_DPLL(id), pll->state.hw_state.dpll); -+ -+ /* Wait for the clocks to stabilize. */ -+ POSTING_READ(PCH_DPLL(id)); -+ udelay(150); -+ -+ /* The pixel multiplier can only be updated once the -+ * DPLL is enabled and the clocks are stable. -+ * -+ * So write it again. -+ */ -+ I915_WRITE(PCH_DPLL(id), pll->state.hw_state.dpll); -+ POSTING_READ(PCH_DPLL(id)); -+ udelay(200); -+} -+ -+static void ibx_pch_dpll_disable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ const enum intel_dpll_id id = pll->info->id; -+ -+ I915_WRITE(PCH_DPLL(id), 0); -+ POSTING_READ(PCH_DPLL(id)); -+ udelay(200); -+} -+ -+static struct intel_shared_dpll * -+ibx_get_dpll(struct intel_crtc_state *crtc_state, -+ struct intel_encoder *encoder) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct intel_shared_dpll *pll; -+ enum intel_dpll_id i; -+ -+ if (HAS_PCH_IBX(dev_priv)) { -+ /* Ironlake PCH has a fixed PLL->PCH pipe mapping. */ -+ i = (enum intel_dpll_id) crtc->pipe; -+ pll = &dev_priv->shared_dplls[i]; -+ -+ DRM_DEBUG_KMS("[CRTC:%d:%s] using pre-allocated %s\n", -+ crtc->base.base.id, crtc->base.name, -+ pll->info->name); -+ } else { -+ pll = intel_find_shared_dpll(crtc_state, -+ DPLL_ID_PCH_PLL_A, -+ DPLL_ID_PCH_PLL_B); -+ } -+ -+ if (!pll) -+ return NULL; -+ -+ /* reference the pll */ -+ intel_reference_shared_dpll(pll, crtc_state); -+ -+ return pll; -+} -+ -+static void ibx_dump_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ DRM_DEBUG_KMS("dpll_hw_state: dpll: 0x%x, dpll_md: 0x%x, " -+ "fp0: 0x%x, fp1: 0x%x\n", -+ hw_state->dpll, -+ hw_state->dpll_md, -+ hw_state->fp0, -+ hw_state->fp1); -+} -+ -+static const struct intel_shared_dpll_funcs ibx_pch_dpll_funcs = { -+ .prepare = ibx_pch_dpll_prepare, -+ .enable = ibx_pch_dpll_enable, -+ .disable = ibx_pch_dpll_disable, -+ .get_hw_state = ibx_pch_dpll_get_hw_state, -+}; -+ -+static void hsw_ddi_wrpll_enable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ const enum intel_dpll_id id = pll->info->id; -+ -+ I915_WRITE(WRPLL_CTL(id), pll->state.hw_state.wrpll); -+ POSTING_READ(WRPLL_CTL(id)); -+ udelay(20); -+} -+ -+static void hsw_ddi_spll_enable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ I915_WRITE(SPLL_CTL, pll->state.hw_state.spll); -+ POSTING_READ(SPLL_CTL); -+ udelay(20); -+} -+ -+static void hsw_ddi_wrpll_disable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ const enum intel_dpll_id id = pll->info->id; -+ u32 val; -+ -+ val = I915_READ(WRPLL_CTL(id)); -+ I915_WRITE(WRPLL_CTL(id), val & ~WRPLL_PLL_ENABLE); -+ POSTING_READ(WRPLL_CTL(id)); -+} -+ -+static void hsw_ddi_spll_disable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ u32 val; -+ -+ val = I915_READ(SPLL_CTL); -+ I915_WRITE(SPLL_CTL, val & ~SPLL_PLL_ENABLE); -+ POSTING_READ(SPLL_CTL); -+} -+ -+static bool hsw_ddi_wrpll_get_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ const enum intel_dpll_id id = pll->info->id; -+ intel_wakeref_t wakeref; -+ u32 val; -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ POWER_DOMAIN_PLLS); -+ if (!wakeref) -+ return false; -+ -+ val = I915_READ(WRPLL_CTL(id)); -+ hw_state->wrpll = val; -+ -+ intel_display_power_put(dev_priv, POWER_DOMAIN_PLLS, wakeref); -+ -+ return val & WRPLL_PLL_ENABLE; -+} -+ -+static bool hsw_ddi_spll_get_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ intel_wakeref_t wakeref; -+ u32 val; -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ POWER_DOMAIN_PLLS); -+ if (!wakeref) -+ return false; -+ -+ val = I915_READ(SPLL_CTL); -+ hw_state->spll = val; -+ -+ intel_display_power_put(dev_priv, POWER_DOMAIN_PLLS, wakeref); -+ -+ return val & SPLL_PLL_ENABLE; -+} -+ -+#define LC_FREQ 2700 -+#define LC_FREQ_2K U64_C(LC_FREQ * 2000) -+ -+#define P_MIN 2 -+#define P_MAX 64 -+#define P_INC 2 -+ -+/* Constraints for PLL good behavior */ -+#define REF_MIN 48 -+#define REF_MAX 400 -+#define VCO_MIN 2400 -+#define VCO_MAX 4800 -+ -+struct hsw_wrpll_rnp { -+ unsigned p, n2, r2; -+}; -+ -+static unsigned hsw_wrpll_get_budget_for_freq(int clock) -+{ -+ unsigned budget; -+ -+ switch (clock) { -+ case 25175000: -+ case 25200000: -+ case 27000000: -+ case 27027000: -+ case 37762500: -+ case 37800000: -+ case 40500000: -+ case 40541000: -+ case 54000000: -+ case 54054000: -+ case 59341000: -+ case 59400000: -+ case 72000000: -+ case 74176000: -+ case 74250000: -+ case 81000000: -+ case 81081000: -+ case 89012000: -+ case 89100000: -+ case 108000000: -+ case 108108000: -+ case 111264000: -+ case 111375000: -+ case 148352000: -+ case 148500000: -+ case 162000000: -+ case 162162000: -+ case 222525000: -+ case 222750000: -+ case 296703000: -+ case 297000000: -+ budget = 0; -+ break; -+ case 233500000: -+ case 245250000: -+ case 247750000: -+ case 253250000: -+ case 298000000: -+ budget = 1500; -+ break; -+ case 169128000: -+ case 169500000: -+ case 179500000: -+ case 202000000: -+ budget = 2000; -+ break; -+ case 256250000: -+ case 262500000: -+ case 270000000: -+ case 272500000: -+ case 273750000: -+ case 280750000: -+ case 281250000: -+ case 286000000: -+ case 291750000: -+ budget = 4000; -+ break; -+ case 267250000: -+ case 268500000: -+ budget = 5000; -+ break; -+ default: -+ budget = 1000; -+ break; -+ } -+ -+ return budget; -+} -+ -+static void hsw_wrpll_update_rnp(u64 freq2k, unsigned int budget, -+ unsigned int r2, unsigned int n2, -+ unsigned int p, -+ struct hsw_wrpll_rnp *best) -+{ -+ u64 a, b, c, d, diff, diff_best; -+ -+ /* No best (r,n,p) yet */ -+ if (best->p == 0) { -+ best->p = p; -+ best->n2 = n2; -+ best->r2 = r2; -+ return; -+ } -+ -+ /* -+ * Output clock is (LC_FREQ_2K / 2000) * N / (P * R), which compares to -+ * freq2k. -+ * -+ * delta = 1e6 * -+ * abs(freq2k - (LC_FREQ_2K * n2/(p * r2))) / -+ * freq2k; -+ * -+ * and we would like delta <= budget. -+ * -+ * If the discrepancy is above the PPM-based budget, always prefer to -+ * improve upon the previous solution. However, if you're within the -+ * budget, try to maximize Ref * VCO, that is N / (P * R^2). -+ */ -+ a = freq2k * budget * p * r2; -+ b = freq2k * budget * best->p * best->r2; -+ diff = abs_diff(freq2k * p * r2, LC_FREQ_2K * n2); -+ diff_best = abs_diff(freq2k * best->p * best->r2, -+ LC_FREQ_2K * best->n2); -+ c = 1000000 * diff; -+ d = 1000000 * diff_best; -+ -+ if (a < c && b < d) { -+ /* If both are above the budget, pick the closer */ -+ if (best->p * best->r2 * diff < p * r2 * diff_best) { -+ best->p = p; -+ best->n2 = n2; -+ best->r2 = r2; -+ } -+ } else if (a >= c && b < d) { -+ /* If A is below the threshold but B is above it? Update. */ -+ best->p = p; -+ best->n2 = n2; -+ best->r2 = r2; -+ } else if (a >= c && b >= d) { -+ /* Both are below the limit, so pick the higher n2/(r2*r2) */ -+ if (n2 * best->r2 * best->r2 > best->n2 * r2 * r2) { -+ best->p = p; -+ best->n2 = n2; -+ best->r2 = r2; -+ } -+ } -+ /* Otherwise a < c && b >= d, do nothing */ -+} -+ -+static void -+hsw_ddi_calculate_wrpll(int clock /* in Hz */, -+ unsigned *r2_out, unsigned *n2_out, unsigned *p_out) -+{ -+ u64 freq2k; -+ unsigned p, n2, r2; -+ struct hsw_wrpll_rnp best = { 0, 0, 0 }; -+ unsigned budget; -+ -+ freq2k = clock / 100; -+ -+ budget = hsw_wrpll_get_budget_for_freq(clock); -+ -+ /* Special case handling for 540 pixel clock: bypass WR PLL entirely -+ * and directly pass the LC PLL to it. */ -+ if (freq2k == 5400000) { -+ *n2_out = 2; -+ *p_out = 1; -+ *r2_out = 2; -+ return; -+ } -+ -+ /* -+ * Ref = LC_FREQ / R, where Ref is the actual reference input seen by -+ * the WR PLL. -+ * -+ * We want R so that REF_MIN <= Ref <= REF_MAX. -+ * Injecting R2 = 2 * R gives: -+ * REF_MAX * r2 > LC_FREQ * 2 and -+ * REF_MIN * r2 < LC_FREQ * 2 -+ * -+ * Which means the desired boundaries for r2 are: -+ * LC_FREQ * 2 / REF_MAX < r2 < LC_FREQ * 2 / REF_MIN -+ * -+ */ -+ for (r2 = LC_FREQ * 2 / REF_MAX + 1; -+ r2 <= LC_FREQ * 2 / REF_MIN; -+ r2++) { -+ -+ /* -+ * VCO = N * Ref, that is: VCO = N * LC_FREQ / R -+ * -+ * Once again we want VCO_MIN <= VCO <= VCO_MAX. -+ * Injecting R2 = 2 * R and N2 = 2 * N, we get: -+ * VCO_MAX * r2 > n2 * LC_FREQ and -+ * VCO_MIN * r2 < n2 * LC_FREQ) -+ * -+ * Which means the desired boundaries for n2 are: -+ * VCO_MIN * r2 / LC_FREQ < n2 < VCO_MAX * r2 / LC_FREQ -+ */ -+ for (n2 = VCO_MIN * r2 / LC_FREQ + 1; -+ n2 <= VCO_MAX * r2 / LC_FREQ; -+ n2++) { -+ -+ for (p = P_MIN; p <= P_MAX; p += P_INC) -+ hsw_wrpll_update_rnp(freq2k, budget, -+ r2, n2, p, &best); -+ } -+ } -+ -+ *n2_out = best.n2; -+ *p_out = best.p; -+ *r2_out = best.r2; -+} -+ -+static struct intel_shared_dpll *hsw_ddi_hdmi_get_dpll(struct intel_crtc_state *crtc_state) -+{ -+ struct intel_shared_dpll *pll; -+ u32 val; -+ unsigned int p, n2, r2; -+ -+ hsw_ddi_calculate_wrpll(crtc_state->port_clock * 1000, &r2, &n2, &p); -+ -+ val = WRPLL_PLL_ENABLE | WRPLL_PLL_LCPLL | -+ WRPLL_DIVIDER_REFERENCE(r2) | WRPLL_DIVIDER_FEEDBACK(n2) | -+ WRPLL_DIVIDER_POST(p); -+ -+ crtc_state->dpll_hw_state.wrpll = val; -+ -+ pll = intel_find_shared_dpll(crtc_state, -+ DPLL_ID_WRPLL1, DPLL_ID_WRPLL2); -+ -+ if (!pll) -+ return NULL; -+ -+ return pll; -+} -+ -+static struct intel_shared_dpll * -+hsw_ddi_dp_get_dpll(struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ struct intel_shared_dpll *pll; -+ enum intel_dpll_id pll_id; -+ int clock = crtc_state->port_clock; -+ -+ switch (clock / 2) { -+ case 81000: -+ pll_id = DPLL_ID_LCPLL_810; -+ break; -+ case 135000: -+ pll_id = DPLL_ID_LCPLL_1350; -+ break; -+ case 270000: -+ pll_id = DPLL_ID_LCPLL_2700; -+ break; -+ default: -+ DRM_DEBUG_KMS("Invalid clock for DP: %d\n", clock); -+ return NULL; -+ } -+ -+ pll = intel_get_shared_dpll_by_id(dev_priv, pll_id); -+ -+ if (!pll) -+ return NULL; -+ -+ return pll; -+} -+ -+static struct intel_shared_dpll * -+hsw_get_dpll(struct intel_crtc_state *crtc_state, -+ struct intel_encoder *encoder) -+{ -+ struct intel_shared_dpll *pll; -+ -+ memset(&crtc_state->dpll_hw_state, 0, -+ sizeof(crtc_state->dpll_hw_state)); -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) { -+ pll = hsw_ddi_hdmi_get_dpll(crtc_state); -+ } else if (intel_crtc_has_dp_encoder(crtc_state)) { -+ pll = hsw_ddi_dp_get_dpll(crtc_state); -+ } else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_ANALOG)) { -+ if (WARN_ON(crtc_state->port_clock / 2 != 135000)) -+ return NULL; -+ -+ crtc_state->dpll_hw_state.spll = -+ SPLL_PLL_ENABLE | SPLL_PLL_FREQ_1350MHz | SPLL_PLL_SSC; -+ -+ pll = intel_find_shared_dpll(crtc_state, -+ DPLL_ID_SPLL, DPLL_ID_SPLL); -+ } else { -+ return NULL; -+ } -+ -+ if (!pll) -+ return NULL; -+ -+ intel_reference_shared_dpll(pll, crtc_state); -+ -+ return pll; -+} -+ -+static void hsw_dump_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ DRM_DEBUG_KMS("dpll_hw_state: wrpll: 0x%x spll: 0x%x\n", -+ hw_state->wrpll, hw_state->spll); -+} -+ -+static const struct intel_shared_dpll_funcs hsw_ddi_wrpll_funcs = { -+ .enable = hsw_ddi_wrpll_enable, -+ .disable = hsw_ddi_wrpll_disable, -+ .get_hw_state = hsw_ddi_wrpll_get_hw_state, -+}; -+ -+static const struct intel_shared_dpll_funcs hsw_ddi_spll_funcs = { -+ .enable = hsw_ddi_spll_enable, -+ .disable = hsw_ddi_spll_disable, -+ .get_hw_state = hsw_ddi_spll_get_hw_state, -+}; -+ -+static void hsw_ddi_lcpll_enable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+} -+ -+static void hsw_ddi_lcpll_disable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+} -+ -+static bool hsw_ddi_lcpll_get_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ return true; -+} -+ -+static const struct intel_shared_dpll_funcs hsw_ddi_lcpll_funcs = { -+ .enable = hsw_ddi_lcpll_enable, -+ .disable = hsw_ddi_lcpll_disable, -+ .get_hw_state = hsw_ddi_lcpll_get_hw_state, -+}; -+ -+struct skl_dpll_regs { -+ i915_reg_t ctl, cfgcr1, cfgcr2; -+}; -+ -+/* this array is indexed by the *shared* pll id */ -+static const struct skl_dpll_regs skl_dpll_regs[4] = { -+ { -+ /* DPLL 0 */ -+ .ctl = LCPLL1_CTL, -+ /* DPLL 0 doesn't support HDMI mode */ -+ }, -+ { -+ /* DPLL 1 */ -+ .ctl = LCPLL2_CTL, -+ .cfgcr1 = DPLL_CFGCR1(SKL_DPLL1), -+ .cfgcr2 = DPLL_CFGCR2(SKL_DPLL1), -+ }, -+ { -+ /* DPLL 2 */ -+ .ctl = WRPLL_CTL(0), -+ .cfgcr1 = DPLL_CFGCR1(SKL_DPLL2), -+ .cfgcr2 = DPLL_CFGCR2(SKL_DPLL2), -+ }, -+ { -+ /* DPLL 3 */ -+ .ctl = WRPLL_CTL(1), -+ .cfgcr1 = DPLL_CFGCR1(SKL_DPLL3), -+ .cfgcr2 = DPLL_CFGCR2(SKL_DPLL3), -+ }, -+}; -+ -+static void skl_ddi_pll_write_ctrl1(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ const enum intel_dpll_id id = pll->info->id; -+ u32 val; -+ -+ val = I915_READ(DPLL_CTRL1); -+ -+ val &= ~(DPLL_CTRL1_HDMI_MODE(id) | -+ DPLL_CTRL1_SSC(id) | -+ DPLL_CTRL1_LINK_RATE_MASK(id)); -+ val |= pll->state.hw_state.ctrl1 << (id * 6); -+ -+ I915_WRITE(DPLL_CTRL1, val); -+ POSTING_READ(DPLL_CTRL1); -+} -+ -+static void skl_ddi_pll_enable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ const struct skl_dpll_regs *regs = skl_dpll_regs; -+ const enum intel_dpll_id id = pll->info->id; -+ -+ skl_ddi_pll_write_ctrl1(dev_priv, pll); -+ -+ I915_WRITE(regs[id].cfgcr1, pll->state.hw_state.cfgcr1); -+ I915_WRITE(regs[id].cfgcr2, pll->state.hw_state.cfgcr2); -+ POSTING_READ(regs[id].cfgcr1); -+ POSTING_READ(regs[id].cfgcr2); -+ -+ /* the enable bit is always bit 31 */ -+ I915_WRITE(regs[id].ctl, -+ I915_READ(regs[id].ctl) | LCPLL_PLL_ENABLE); -+ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ DPLL_STATUS, -+ DPLL_LOCK(id), -+ DPLL_LOCK(id), -+ 5)) -+ DRM_ERROR("DPLL %d not locked\n", id); -+} -+ -+static void skl_ddi_dpll0_enable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ skl_ddi_pll_write_ctrl1(dev_priv, pll); -+} -+ -+static void skl_ddi_pll_disable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ const struct skl_dpll_regs *regs = skl_dpll_regs; -+ const enum intel_dpll_id id = pll->info->id; -+ -+ /* the enable bit is always bit 31 */ -+ I915_WRITE(regs[id].ctl, -+ I915_READ(regs[id].ctl) & ~LCPLL_PLL_ENABLE); -+ POSTING_READ(regs[id].ctl); -+} -+ -+static void skl_ddi_dpll0_disable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+} -+ -+static bool skl_ddi_pll_get_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ u32 val; -+ const struct skl_dpll_regs *regs = skl_dpll_regs; -+ const enum intel_dpll_id id = pll->info->id; -+ intel_wakeref_t wakeref; -+ bool ret; -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ POWER_DOMAIN_PLLS); -+ if (!wakeref) -+ return false; -+ -+ ret = false; -+ -+ val = I915_READ(regs[id].ctl); -+ if (!(val & LCPLL_PLL_ENABLE)) -+ goto out; -+ -+ val = I915_READ(DPLL_CTRL1); -+ hw_state->ctrl1 = (val >> (id * 6)) & 0x3f; -+ -+ /* avoid reading back stale values if HDMI mode is not enabled */ -+ if (val & DPLL_CTRL1_HDMI_MODE(id)) { -+ hw_state->cfgcr1 = I915_READ(regs[id].cfgcr1); -+ hw_state->cfgcr2 = I915_READ(regs[id].cfgcr2); -+ } -+ ret = true; -+ -+out: -+ intel_display_power_put(dev_priv, POWER_DOMAIN_PLLS, wakeref); -+ -+ return ret; -+} -+ -+static bool skl_ddi_dpll0_get_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ const struct skl_dpll_regs *regs = skl_dpll_regs; -+ const enum intel_dpll_id id = pll->info->id; -+ intel_wakeref_t wakeref; -+ u32 val; -+ bool ret; -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ POWER_DOMAIN_PLLS); -+ if (!wakeref) -+ return false; -+ -+ ret = false; -+ -+ /* DPLL0 is always enabled since it drives CDCLK */ -+ val = I915_READ(regs[id].ctl); -+ if (WARN_ON(!(val & LCPLL_PLL_ENABLE))) -+ goto out; -+ -+ val = I915_READ(DPLL_CTRL1); -+ hw_state->ctrl1 = (val >> (id * 6)) & 0x3f; -+ -+ ret = true; -+ -+out: -+ intel_display_power_put(dev_priv, POWER_DOMAIN_PLLS, wakeref); -+ -+ return ret; -+} -+ -+struct skl_wrpll_context { -+ u64 min_deviation; /* current minimal deviation */ -+ u64 central_freq; /* chosen central freq */ -+ u64 dco_freq; /* chosen dco freq */ -+ unsigned int p; /* chosen divider */ -+}; -+ -+static void skl_wrpll_context_init(struct skl_wrpll_context *ctx) -+{ -+ memset(ctx, 0, sizeof(*ctx)); -+ -+ ctx->min_deviation = U64_MAX; -+} -+ -+/* DCO freq must be within +1%/-6% of the DCO central freq */ -+#define SKL_DCO_MAX_PDEVIATION 100 -+#define SKL_DCO_MAX_NDEVIATION 600 -+ -+static void skl_wrpll_try_divider(struct skl_wrpll_context *ctx, -+ u64 central_freq, -+ u64 dco_freq, -+ unsigned int divider) -+{ -+ u64 deviation; -+ -+ deviation = div64_u64(10000 * abs_diff(dco_freq, central_freq), -+ central_freq); -+ -+ /* positive deviation */ -+ if (dco_freq >= central_freq) { -+ if (deviation < SKL_DCO_MAX_PDEVIATION && -+ deviation < ctx->min_deviation) { -+ ctx->min_deviation = deviation; -+ ctx->central_freq = central_freq; -+ ctx->dco_freq = dco_freq; -+ ctx->p = divider; -+ } -+ /* negative deviation */ -+ } else if (deviation < SKL_DCO_MAX_NDEVIATION && -+ deviation < ctx->min_deviation) { -+ ctx->min_deviation = deviation; -+ ctx->central_freq = central_freq; -+ ctx->dco_freq = dco_freq; -+ ctx->p = divider; -+ } -+} -+ -+static void skl_wrpll_get_multipliers(unsigned int p, -+ unsigned int *p0 /* out */, -+ unsigned int *p1 /* out */, -+ unsigned int *p2 /* out */) -+{ -+ /* even dividers */ -+ if (p % 2 == 0) { -+ unsigned int half = p / 2; -+ -+ if (half == 1 || half == 2 || half == 3 || half == 5) { -+ *p0 = 2; -+ *p1 = 1; -+ *p2 = half; -+ } else if (half % 2 == 0) { -+ *p0 = 2; -+ *p1 = half / 2; -+ *p2 = 2; -+ } else if (half % 3 == 0) { -+ *p0 = 3; -+ *p1 = half / 3; -+ *p2 = 2; -+ } else if (half % 7 == 0) { -+ *p0 = 7; -+ *p1 = half / 7; -+ *p2 = 2; -+ } -+ } else if (p == 3 || p == 9) { /* 3, 5, 7, 9, 15, 21, 35 */ -+ *p0 = 3; -+ *p1 = 1; -+ *p2 = p / 3; -+ } else if (p == 5 || p == 7) { -+ *p0 = p; -+ *p1 = 1; -+ *p2 = 1; -+ } else if (p == 15) { -+ *p0 = 3; -+ *p1 = 1; -+ *p2 = 5; -+ } else if (p == 21) { -+ *p0 = 7; -+ *p1 = 1; -+ *p2 = 3; -+ } else if (p == 35) { -+ *p0 = 7; -+ *p1 = 1; -+ *p2 = 5; -+ } -+} -+ -+struct skl_wrpll_params { -+ u32 dco_fraction; -+ u32 dco_integer; -+ u32 qdiv_ratio; -+ u32 qdiv_mode; -+ u32 kdiv; -+ u32 pdiv; -+ u32 central_freq; -+}; -+ -+static void skl_wrpll_params_populate(struct skl_wrpll_params *params, -+ u64 afe_clock, -+ u64 central_freq, -+ u32 p0, u32 p1, u32 p2) -+{ -+ u64 dco_freq; -+ -+ switch (central_freq) { -+ case 9600000000ULL: -+ params->central_freq = 0; -+ break; -+ case 9000000000ULL: -+ params->central_freq = 1; -+ break; -+ case 8400000000ULL: -+ params->central_freq = 3; -+ } -+ -+ switch (p0) { -+ case 1: -+ params->pdiv = 0; -+ break; -+ case 2: -+ params->pdiv = 1; -+ break; -+ case 3: -+ params->pdiv = 2; -+ break; -+ case 7: -+ params->pdiv = 4; -+ break; -+ default: -+ WARN(1, "Incorrect PDiv\n"); -+ } -+ -+ switch (p2) { -+ case 5: -+ params->kdiv = 0; -+ break; -+ case 2: -+ params->kdiv = 1; -+ break; -+ case 3: -+ params->kdiv = 2; -+ break; -+ case 1: -+ params->kdiv = 3; -+ break; -+ default: -+ WARN(1, "Incorrect KDiv\n"); -+ } -+ -+ params->qdiv_ratio = p1; -+ params->qdiv_mode = (params->qdiv_ratio == 1) ? 0 : 1; -+ -+ dco_freq = p0 * p1 * p2 * afe_clock; -+ -+ /* -+ * Intermediate values are in Hz. -+ * Divide by MHz to match bsepc -+ */ -+ params->dco_integer = div_u64(dco_freq, 24 * MHz(1)); -+ params->dco_fraction = -+ div_u64((div_u64(dco_freq, 24) - -+ params->dco_integer * MHz(1)) * 0x8000, MHz(1)); -+} -+ -+static bool -+skl_ddi_calculate_wrpll(int clock /* in Hz */, -+ struct skl_wrpll_params *wrpll_params) -+{ -+ u64 afe_clock = clock * 5; /* AFE Clock is 5x Pixel clock */ -+ u64 dco_central_freq[3] = { 8400000000ULL, -+ 9000000000ULL, -+ 9600000000ULL }; -+ static const int even_dividers[] = { 4, 6, 8, 10, 12, 14, 16, 18, 20, -+ 24, 28, 30, 32, 36, 40, 42, 44, -+ 48, 52, 54, 56, 60, 64, 66, 68, -+ 70, 72, 76, 78, 80, 84, 88, 90, -+ 92, 96, 98 }; -+ static const int odd_dividers[] = { 3, 5, 7, 9, 15, 21, 35 }; -+ static const struct { -+ const int *list; -+ int n_dividers; -+ } dividers[] = { -+ { even_dividers, ARRAY_SIZE(even_dividers) }, -+ { odd_dividers, ARRAY_SIZE(odd_dividers) }, -+ }; -+ struct skl_wrpll_context ctx; -+ unsigned int dco, d, i; -+ unsigned int p0, p1, p2; -+ -+ skl_wrpll_context_init(&ctx); -+ -+ for (d = 0; d < ARRAY_SIZE(dividers); d++) { -+ for (dco = 0; dco < ARRAY_SIZE(dco_central_freq); dco++) { -+ for (i = 0; i < dividers[d].n_dividers; i++) { -+ unsigned int p = dividers[d].list[i]; -+ u64 dco_freq = p * afe_clock; -+ -+ skl_wrpll_try_divider(&ctx, -+ dco_central_freq[dco], -+ dco_freq, -+ p); -+ /* -+ * Skip the remaining dividers if we're sure to -+ * have found the definitive divider, we can't -+ * improve a 0 deviation. -+ */ -+ if (ctx.min_deviation == 0) -+ goto skip_remaining_dividers; -+ } -+ } -+ -+skip_remaining_dividers: -+ /* -+ * If a solution is found with an even divider, prefer -+ * this one. -+ */ -+ if (d == 0 && ctx.p) -+ break; -+ } -+ -+ if (!ctx.p) { -+ DRM_DEBUG_DRIVER("No valid divider found for %dHz\n", clock); -+ return false; -+ } -+ -+ /* -+ * gcc incorrectly analyses that these can be used without being -+ * initialized. To be fair, it's hard to guess. -+ */ -+ p0 = p1 = p2 = 0; -+ skl_wrpll_get_multipliers(ctx.p, &p0, &p1, &p2); -+ skl_wrpll_params_populate(wrpll_params, afe_clock, ctx.central_freq, -+ p0, p1, p2); -+ -+ return true; -+} -+ -+static bool skl_ddi_hdmi_pll_dividers(struct intel_crtc_state *crtc_state) -+{ -+ u32 ctrl1, cfgcr1, cfgcr2; -+ struct skl_wrpll_params wrpll_params = { 0, }; -+ -+ /* -+ * See comment in intel_dpll_hw_state to understand why we always use 0 -+ * as the DPLL id in this function. -+ */ -+ ctrl1 = DPLL_CTRL1_OVERRIDE(0); -+ -+ ctrl1 |= DPLL_CTRL1_HDMI_MODE(0); -+ -+ if (!skl_ddi_calculate_wrpll(crtc_state->port_clock * 1000, -+ &wrpll_params)) -+ return false; -+ -+ cfgcr1 = DPLL_CFGCR1_FREQ_ENABLE | -+ DPLL_CFGCR1_DCO_FRACTION(wrpll_params.dco_fraction) | -+ wrpll_params.dco_integer; -+ -+ cfgcr2 = DPLL_CFGCR2_QDIV_RATIO(wrpll_params.qdiv_ratio) | -+ DPLL_CFGCR2_QDIV_MODE(wrpll_params.qdiv_mode) | -+ DPLL_CFGCR2_KDIV(wrpll_params.kdiv) | -+ DPLL_CFGCR2_PDIV(wrpll_params.pdiv) | -+ wrpll_params.central_freq; -+ -+ memset(&crtc_state->dpll_hw_state, 0, -+ sizeof(crtc_state->dpll_hw_state)); -+ -+ crtc_state->dpll_hw_state.ctrl1 = ctrl1; -+ crtc_state->dpll_hw_state.cfgcr1 = cfgcr1; -+ crtc_state->dpll_hw_state.cfgcr2 = cfgcr2; -+ return true; -+} -+ -+static bool -+skl_ddi_dp_set_dpll_hw_state(struct intel_crtc_state *crtc_state) -+{ -+ u32 ctrl1; -+ -+ /* -+ * See comment in intel_dpll_hw_state to understand why we always use 0 -+ * as the DPLL id in this function. -+ */ -+ ctrl1 = DPLL_CTRL1_OVERRIDE(0); -+ switch (crtc_state->port_clock / 2) { -+ case 81000: -+ ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_810, 0); -+ break; -+ case 135000: -+ ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1350, 0); -+ break; -+ case 270000: -+ ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_2700, 0); -+ break; -+ /* eDP 1.4 rates */ -+ case 162000: -+ ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1620, 0); -+ break; -+ case 108000: -+ ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_1080, 0); -+ break; -+ case 216000: -+ ctrl1 |= DPLL_CTRL1_LINK_RATE(DPLL_CTRL1_LINK_RATE_2160, 0); -+ break; -+ } -+ -+ memset(&crtc_state->dpll_hw_state, 0, -+ sizeof(crtc_state->dpll_hw_state)); -+ -+ crtc_state->dpll_hw_state.ctrl1 = ctrl1; -+ -+ return true; -+} -+ -+static struct intel_shared_dpll * -+skl_get_dpll(struct intel_crtc_state *crtc_state, -+ struct intel_encoder *encoder) -+{ -+ struct intel_shared_dpll *pll; -+ bool bret; -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) { -+ bret = skl_ddi_hdmi_pll_dividers(crtc_state); -+ if (!bret) { -+ DRM_DEBUG_KMS("Could not get HDMI pll dividers.\n"); -+ return NULL; -+ } -+ } else if (intel_crtc_has_dp_encoder(crtc_state)) { -+ bret = skl_ddi_dp_set_dpll_hw_state(crtc_state); -+ if (!bret) { -+ DRM_DEBUG_KMS("Could not set DP dpll HW state.\n"); -+ return NULL; -+ } -+ } else { -+ return NULL; -+ } -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_EDP)) -+ pll = intel_find_shared_dpll(crtc_state, -+ DPLL_ID_SKL_DPLL0, -+ DPLL_ID_SKL_DPLL0); -+ else -+ pll = intel_find_shared_dpll(crtc_state, -+ DPLL_ID_SKL_DPLL1, -+ DPLL_ID_SKL_DPLL3); -+ if (!pll) -+ return NULL; -+ -+ intel_reference_shared_dpll(pll, crtc_state); -+ -+ return pll; -+} -+ -+static void skl_dump_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ DRM_DEBUG_KMS("dpll_hw_state: " -+ "ctrl1: 0x%x, cfgcr1: 0x%x, cfgcr2: 0x%x\n", -+ hw_state->ctrl1, -+ hw_state->cfgcr1, -+ hw_state->cfgcr2); -+} -+ -+static const struct intel_shared_dpll_funcs skl_ddi_pll_funcs = { -+ .enable = skl_ddi_pll_enable, -+ .disable = skl_ddi_pll_disable, -+ .get_hw_state = skl_ddi_pll_get_hw_state, -+}; -+ -+static const struct intel_shared_dpll_funcs skl_ddi_dpll0_funcs = { -+ .enable = skl_ddi_dpll0_enable, -+ .disable = skl_ddi_dpll0_disable, -+ .get_hw_state = skl_ddi_dpll0_get_hw_state, -+}; -+ -+static void bxt_ddi_pll_enable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ u32 temp; -+ enum port port = (enum port)pll->info->id; /* 1:1 port->PLL mapping */ -+ enum dpio_phy phy; -+ enum dpio_channel ch; -+ -+ bxt_port_to_phy_channel(dev_priv, port, &phy, &ch); -+ -+ /* Non-SSC reference */ -+ temp = I915_READ(BXT_PORT_PLL_ENABLE(port)); -+ temp |= PORT_PLL_REF_SEL; -+ I915_WRITE(BXT_PORT_PLL_ENABLE(port), temp); -+ -+ if (IS_GEMINILAKE(dev_priv)) { -+ temp = I915_READ(BXT_PORT_PLL_ENABLE(port)); -+ temp |= PORT_PLL_POWER_ENABLE; -+ I915_WRITE(BXT_PORT_PLL_ENABLE(port), temp); -+ -+ if (wait_for_us((I915_READ(BXT_PORT_PLL_ENABLE(port)) & -+ PORT_PLL_POWER_STATE), 200)) -+ DRM_ERROR("Power state not set for PLL:%d\n", port); -+ } -+ -+ /* Disable 10 bit clock */ -+ temp = I915_READ(BXT_PORT_PLL_EBB_4(phy, ch)); -+ temp &= ~PORT_PLL_10BIT_CLK_ENABLE; -+ I915_WRITE(BXT_PORT_PLL_EBB_4(phy, ch), temp); -+ -+ /* Write P1 & P2 */ -+ temp = I915_READ(BXT_PORT_PLL_EBB_0(phy, ch)); -+ temp &= ~(PORT_PLL_P1_MASK | PORT_PLL_P2_MASK); -+ temp |= pll->state.hw_state.ebb0; -+ I915_WRITE(BXT_PORT_PLL_EBB_0(phy, ch), temp); -+ -+ /* Write M2 integer */ -+ temp = I915_READ(BXT_PORT_PLL(phy, ch, 0)); -+ temp &= ~PORT_PLL_M2_MASK; -+ temp |= pll->state.hw_state.pll0; -+ I915_WRITE(BXT_PORT_PLL(phy, ch, 0), temp); -+ -+ /* Write N */ -+ temp = I915_READ(BXT_PORT_PLL(phy, ch, 1)); -+ temp &= ~PORT_PLL_N_MASK; -+ temp |= pll->state.hw_state.pll1; -+ I915_WRITE(BXT_PORT_PLL(phy, ch, 1), temp); -+ -+ /* Write M2 fraction */ -+ temp = I915_READ(BXT_PORT_PLL(phy, ch, 2)); -+ temp &= ~PORT_PLL_M2_FRAC_MASK; -+ temp |= pll->state.hw_state.pll2; -+ I915_WRITE(BXT_PORT_PLL(phy, ch, 2), temp); -+ -+ /* Write M2 fraction enable */ -+ temp = I915_READ(BXT_PORT_PLL(phy, ch, 3)); -+ temp &= ~PORT_PLL_M2_FRAC_ENABLE; -+ temp |= pll->state.hw_state.pll3; -+ I915_WRITE(BXT_PORT_PLL(phy, ch, 3), temp); -+ -+ /* Write coeff */ -+ temp = I915_READ(BXT_PORT_PLL(phy, ch, 6)); -+ temp &= ~PORT_PLL_PROP_COEFF_MASK; -+ temp &= ~PORT_PLL_INT_COEFF_MASK; -+ temp &= ~PORT_PLL_GAIN_CTL_MASK; -+ temp |= pll->state.hw_state.pll6; -+ I915_WRITE(BXT_PORT_PLL(phy, ch, 6), temp); -+ -+ /* Write calibration val */ -+ temp = I915_READ(BXT_PORT_PLL(phy, ch, 8)); -+ temp &= ~PORT_PLL_TARGET_CNT_MASK; -+ temp |= pll->state.hw_state.pll8; -+ I915_WRITE(BXT_PORT_PLL(phy, ch, 8), temp); -+ -+ temp = I915_READ(BXT_PORT_PLL(phy, ch, 9)); -+ temp &= ~PORT_PLL_LOCK_THRESHOLD_MASK; -+ temp |= pll->state.hw_state.pll9; -+ I915_WRITE(BXT_PORT_PLL(phy, ch, 9), temp); -+ -+ temp = I915_READ(BXT_PORT_PLL(phy, ch, 10)); -+ temp &= ~PORT_PLL_DCO_AMP_OVR_EN_H; -+ temp &= ~PORT_PLL_DCO_AMP_MASK; -+ temp |= pll->state.hw_state.pll10; -+ I915_WRITE(BXT_PORT_PLL(phy, ch, 10), temp); -+ -+ /* Recalibrate with new settings */ -+ temp = I915_READ(BXT_PORT_PLL_EBB_4(phy, ch)); -+ temp |= PORT_PLL_RECALIBRATE; -+ I915_WRITE(BXT_PORT_PLL_EBB_4(phy, ch), temp); -+ temp &= ~PORT_PLL_10BIT_CLK_ENABLE; -+ temp |= pll->state.hw_state.ebb4; -+ I915_WRITE(BXT_PORT_PLL_EBB_4(phy, ch), temp); -+ -+ /* Enable PLL */ -+ temp = I915_READ(BXT_PORT_PLL_ENABLE(port)); -+ temp |= PORT_PLL_ENABLE; -+ I915_WRITE(BXT_PORT_PLL_ENABLE(port), temp); -+ POSTING_READ(BXT_PORT_PLL_ENABLE(port)); -+ -+ if (wait_for_us((I915_READ(BXT_PORT_PLL_ENABLE(port)) & PORT_PLL_LOCK), -+ 200)) -+ DRM_ERROR("PLL %d not locked\n", port); -+ -+ if (IS_GEMINILAKE(dev_priv)) { -+ temp = I915_READ(BXT_PORT_TX_DW5_LN0(phy, ch)); -+ temp |= DCC_DELAY_RANGE_2; -+ I915_WRITE(BXT_PORT_TX_DW5_GRP(phy, ch), temp); -+ } -+ -+ /* -+ * While we write to the group register to program all lanes at once we -+ * can read only lane registers and we pick lanes 0/1 for that. -+ */ -+ temp = I915_READ(BXT_PORT_PCS_DW12_LN01(phy, ch)); -+ temp &= ~LANE_STAGGER_MASK; -+ temp &= ~LANESTAGGER_STRAP_OVRD; -+ temp |= pll->state.hw_state.pcsdw12; -+ I915_WRITE(BXT_PORT_PCS_DW12_GRP(phy, ch), temp); -+} -+ -+static void bxt_ddi_pll_disable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ enum port port = (enum port)pll->info->id; /* 1:1 port->PLL mapping */ -+ u32 temp; -+ -+ temp = I915_READ(BXT_PORT_PLL_ENABLE(port)); -+ temp &= ~PORT_PLL_ENABLE; -+ I915_WRITE(BXT_PORT_PLL_ENABLE(port), temp); -+ POSTING_READ(BXT_PORT_PLL_ENABLE(port)); -+ -+ if (IS_GEMINILAKE(dev_priv)) { -+ temp = I915_READ(BXT_PORT_PLL_ENABLE(port)); -+ temp &= ~PORT_PLL_POWER_ENABLE; -+ I915_WRITE(BXT_PORT_PLL_ENABLE(port), temp); -+ -+ if (wait_for_us(!(I915_READ(BXT_PORT_PLL_ENABLE(port)) & -+ PORT_PLL_POWER_STATE), 200)) -+ DRM_ERROR("Power state not reset for PLL:%d\n", port); -+ } -+} -+ -+static bool bxt_ddi_pll_get_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ enum port port = (enum port)pll->info->id; /* 1:1 port->PLL mapping */ -+ intel_wakeref_t wakeref; -+ enum dpio_phy phy; -+ enum dpio_channel ch; -+ u32 val; -+ bool ret; -+ -+ bxt_port_to_phy_channel(dev_priv, port, &phy, &ch); -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ POWER_DOMAIN_PLLS); -+ if (!wakeref) -+ return false; -+ -+ ret = false; -+ -+ val = I915_READ(BXT_PORT_PLL_ENABLE(port)); -+ if (!(val & PORT_PLL_ENABLE)) -+ goto out; -+ -+ hw_state->ebb0 = I915_READ(BXT_PORT_PLL_EBB_0(phy, ch)); -+ hw_state->ebb0 &= PORT_PLL_P1_MASK | PORT_PLL_P2_MASK; -+ -+ hw_state->ebb4 = I915_READ(BXT_PORT_PLL_EBB_4(phy, ch)); -+ hw_state->ebb4 &= PORT_PLL_10BIT_CLK_ENABLE; -+ -+ hw_state->pll0 = I915_READ(BXT_PORT_PLL(phy, ch, 0)); -+ hw_state->pll0 &= PORT_PLL_M2_MASK; -+ -+ hw_state->pll1 = I915_READ(BXT_PORT_PLL(phy, ch, 1)); -+ hw_state->pll1 &= PORT_PLL_N_MASK; -+ -+ hw_state->pll2 = I915_READ(BXT_PORT_PLL(phy, ch, 2)); -+ hw_state->pll2 &= PORT_PLL_M2_FRAC_MASK; -+ -+ hw_state->pll3 = I915_READ(BXT_PORT_PLL(phy, ch, 3)); -+ hw_state->pll3 &= PORT_PLL_M2_FRAC_ENABLE; -+ -+ hw_state->pll6 = I915_READ(BXT_PORT_PLL(phy, ch, 6)); -+ hw_state->pll6 &= PORT_PLL_PROP_COEFF_MASK | -+ PORT_PLL_INT_COEFF_MASK | -+ PORT_PLL_GAIN_CTL_MASK; -+ -+ hw_state->pll8 = I915_READ(BXT_PORT_PLL(phy, ch, 8)); -+ hw_state->pll8 &= PORT_PLL_TARGET_CNT_MASK; -+ -+ hw_state->pll9 = I915_READ(BXT_PORT_PLL(phy, ch, 9)); -+ hw_state->pll9 &= PORT_PLL_LOCK_THRESHOLD_MASK; -+ -+ hw_state->pll10 = I915_READ(BXT_PORT_PLL(phy, ch, 10)); -+ hw_state->pll10 &= PORT_PLL_DCO_AMP_OVR_EN_H | -+ PORT_PLL_DCO_AMP_MASK; -+ -+ /* -+ * While we write to the group register to program all lanes at once we -+ * can read only lane registers. We configure all lanes the same way, so -+ * here just read out lanes 0/1 and output a note if lanes 2/3 differ. -+ */ -+ hw_state->pcsdw12 = I915_READ(BXT_PORT_PCS_DW12_LN01(phy, ch)); -+ if (I915_READ(BXT_PORT_PCS_DW12_LN23(phy, ch)) != hw_state->pcsdw12) -+ DRM_DEBUG_DRIVER("lane stagger config different for lane 01 (%08x) and 23 (%08x)\n", -+ hw_state->pcsdw12, -+ I915_READ(BXT_PORT_PCS_DW12_LN23(phy, ch))); -+ hw_state->pcsdw12 &= LANE_STAGGER_MASK | LANESTAGGER_STRAP_OVRD; -+ -+ ret = true; -+ -+out: -+ intel_display_power_put(dev_priv, POWER_DOMAIN_PLLS, wakeref); -+ -+ return ret; -+} -+ -+/* bxt clock parameters */ -+struct bxt_clk_div { -+ int clock; -+ u32 p1; -+ u32 p2; -+ u32 m2_int; -+ u32 m2_frac; -+ bool m2_frac_en; -+ u32 n; -+ -+ int vco; -+}; -+ -+/* pre-calculated values for DP linkrates */ -+static const struct bxt_clk_div bxt_dp_clk_val[] = { -+ {162000, 4, 2, 32, 1677722, 1, 1}, -+ {270000, 4, 1, 27, 0, 0, 1}, -+ {540000, 2, 1, 27, 0, 0, 1}, -+ {216000, 3, 2, 32, 1677722, 1, 1}, -+ {243000, 4, 1, 24, 1258291, 1, 1}, -+ {324000, 4, 1, 32, 1677722, 1, 1}, -+ {432000, 3, 1, 32, 1677722, 1, 1} -+}; -+ -+static bool -+bxt_ddi_hdmi_pll_dividers(struct intel_crtc_state *crtc_state, -+ struct bxt_clk_div *clk_div) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct dpll best_clock; -+ -+ /* Calculate HDMI div */ -+ /* -+ * FIXME: tie the following calculation into -+ * i9xx_crtc_compute_clock -+ */ -+ if (!bxt_find_best_dpll(crtc_state, &best_clock)) { -+ DRM_DEBUG_DRIVER("no PLL dividers found for clock %d pipe %c\n", -+ crtc_state->port_clock, -+ pipe_name(crtc->pipe)); -+ return false; -+ } -+ -+ clk_div->p1 = best_clock.p1; -+ clk_div->p2 = best_clock.p2; -+ WARN_ON(best_clock.m1 != 2); -+ clk_div->n = best_clock.n; -+ clk_div->m2_int = best_clock.m2 >> 22; -+ clk_div->m2_frac = best_clock.m2 & ((1 << 22) - 1); -+ clk_div->m2_frac_en = clk_div->m2_frac != 0; -+ -+ clk_div->vco = best_clock.vco; -+ -+ return true; -+} -+ -+static void bxt_ddi_dp_pll_dividers(struct intel_crtc_state *crtc_state, -+ struct bxt_clk_div *clk_div) -+{ -+ int clock = crtc_state->port_clock; -+ int i; -+ -+ *clk_div = bxt_dp_clk_val[0]; -+ for (i = 0; i < ARRAY_SIZE(bxt_dp_clk_val); ++i) { -+ if (bxt_dp_clk_val[i].clock == clock) { -+ *clk_div = bxt_dp_clk_val[i]; -+ break; -+ } -+ } -+ -+ clk_div->vco = clock * 10 / 2 * clk_div->p1 * clk_div->p2; -+} -+ -+static bool bxt_ddi_set_dpll_hw_state(struct intel_crtc_state *crtc_state, -+ const struct bxt_clk_div *clk_div) -+{ -+ struct intel_dpll_hw_state *dpll_hw_state = &crtc_state->dpll_hw_state; -+ int clock = crtc_state->port_clock; -+ int vco = clk_div->vco; -+ u32 prop_coef, int_coef, gain_ctl, targ_cnt; -+ u32 lanestagger; -+ -+ memset(dpll_hw_state, 0, sizeof(*dpll_hw_state)); -+ -+ if (vco >= 6200000 && vco <= 6700000) { -+ prop_coef = 4; -+ int_coef = 9; -+ gain_ctl = 3; -+ targ_cnt = 8; -+ } else if ((vco > 5400000 && vco < 6200000) || -+ (vco >= 4800000 && vco < 5400000)) { -+ prop_coef = 5; -+ int_coef = 11; -+ gain_ctl = 3; -+ targ_cnt = 9; -+ } else if (vco == 5400000) { -+ prop_coef = 3; -+ int_coef = 8; -+ gain_ctl = 1; -+ targ_cnt = 9; -+ } else { -+ DRM_ERROR("Invalid VCO\n"); -+ return false; -+ } -+ -+ if (clock > 270000) -+ lanestagger = 0x18; -+ else if (clock > 135000) -+ lanestagger = 0x0d; -+ else if (clock > 67000) -+ lanestagger = 0x07; -+ else if (clock > 33000) -+ lanestagger = 0x04; -+ else -+ lanestagger = 0x02; -+ -+ dpll_hw_state->ebb0 = PORT_PLL_P1(clk_div->p1) | PORT_PLL_P2(clk_div->p2); -+ dpll_hw_state->pll0 = clk_div->m2_int; -+ dpll_hw_state->pll1 = PORT_PLL_N(clk_div->n); -+ dpll_hw_state->pll2 = clk_div->m2_frac; -+ -+ if (clk_div->m2_frac_en) -+ dpll_hw_state->pll3 = PORT_PLL_M2_FRAC_ENABLE; -+ -+ dpll_hw_state->pll6 = prop_coef | PORT_PLL_INT_COEFF(int_coef); -+ dpll_hw_state->pll6 |= PORT_PLL_GAIN_CTL(gain_ctl); -+ -+ dpll_hw_state->pll8 = targ_cnt; -+ -+ dpll_hw_state->pll9 = 5 << PORT_PLL_LOCK_THRESHOLD_SHIFT; -+ -+ dpll_hw_state->pll10 = -+ PORT_PLL_DCO_AMP(PORT_PLL_DCO_AMP_DEFAULT) -+ | PORT_PLL_DCO_AMP_OVR_EN_H; -+ -+ dpll_hw_state->ebb4 = PORT_PLL_10BIT_CLK_ENABLE; -+ -+ dpll_hw_state->pcsdw12 = LANESTAGGER_STRAP_OVRD | lanestagger; -+ -+ return true; -+} -+ -+static bool -+bxt_ddi_dp_set_dpll_hw_state(struct intel_crtc_state *crtc_state) -+{ -+ struct bxt_clk_div clk_div = {}; -+ -+ bxt_ddi_dp_pll_dividers(crtc_state, &clk_div); -+ -+ return bxt_ddi_set_dpll_hw_state(crtc_state, &clk_div); -+} -+ -+static bool -+bxt_ddi_hdmi_set_dpll_hw_state(struct intel_crtc_state *crtc_state) -+{ -+ struct bxt_clk_div clk_div = {}; -+ -+ bxt_ddi_hdmi_pll_dividers(crtc_state, &clk_div); -+ -+ return bxt_ddi_set_dpll_hw_state(crtc_state, &clk_div); -+} -+ -+static struct intel_shared_dpll * -+bxt_get_dpll(struct intel_crtc_state *crtc_state, -+ struct intel_encoder *encoder) -+{ -+ struct intel_crtc *crtc = to_intel_crtc(crtc_state->base.crtc); -+ struct drm_i915_private *dev_priv = to_i915(crtc->base.dev); -+ struct intel_shared_dpll *pll; -+ enum intel_dpll_id id; -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI) && -+ !bxt_ddi_hdmi_set_dpll_hw_state(crtc_state)) -+ return NULL; -+ -+ if (intel_crtc_has_dp_encoder(crtc_state) && -+ !bxt_ddi_dp_set_dpll_hw_state(crtc_state)) -+ return NULL; -+ -+ /* 1:1 mapping between ports and PLLs */ -+ id = (enum intel_dpll_id) encoder->port; -+ pll = intel_get_shared_dpll_by_id(dev_priv, id); -+ -+ DRM_DEBUG_KMS("[CRTC:%d:%s] using pre-allocated %s\n", -+ crtc->base.base.id, crtc->base.name, pll->info->name); -+ -+ intel_reference_shared_dpll(pll, crtc_state); -+ -+ return pll; -+} -+ -+static void bxt_dump_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ DRM_DEBUG_KMS("dpll_hw_state: ebb0: 0x%x, ebb4: 0x%x," -+ "pll0: 0x%x, pll1: 0x%x, pll2: 0x%x, pll3: 0x%x, " -+ "pll6: 0x%x, pll8: 0x%x, pll9: 0x%x, pll10: 0x%x, pcsdw12: 0x%x\n", -+ hw_state->ebb0, -+ hw_state->ebb4, -+ hw_state->pll0, -+ hw_state->pll1, -+ hw_state->pll2, -+ hw_state->pll3, -+ hw_state->pll6, -+ hw_state->pll8, -+ hw_state->pll9, -+ hw_state->pll10, -+ hw_state->pcsdw12); -+} -+ -+static const struct intel_shared_dpll_funcs bxt_ddi_pll_funcs = { -+ .enable = bxt_ddi_pll_enable, -+ .disable = bxt_ddi_pll_disable, -+ .get_hw_state = bxt_ddi_pll_get_hw_state, -+}; -+ -+static void intel_ddi_pll_init(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ -+ if (INTEL_GEN(dev_priv) < 9) { -+ u32 val = I915_READ(LCPLL_CTL); -+ -+ /* -+ * The LCPLL register should be turned on by the BIOS. For now -+ * let's just check its state and print errors in case -+ * something is wrong. Don't even try to turn it on. -+ */ -+ -+ if (val & LCPLL_CD_SOURCE_FCLK) -+ DRM_ERROR("CDCLK source is not LCPLL\n"); -+ -+ if (val & LCPLL_PLL_DISABLE) -+ DRM_ERROR("LCPLL is disabled\n"); -+ } -+} -+ -+struct intel_dpll_mgr { -+ const struct dpll_info *dpll_info; -+ -+ struct intel_shared_dpll *(*get_dpll)(struct intel_crtc_state *crtc_state, -+ struct intel_encoder *encoder); -+ -+ void (*dump_hw_state)(struct drm_i915_private *dev_priv, -+ struct intel_dpll_hw_state *hw_state); -+}; -+ -+static const struct dpll_info pch_plls[] = { -+ { "PCH DPLL A", &ibx_pch_dpll_funcs, DPLL_ID_PCH_PLL_A, 0 }, -+ { "PCH DPLL B", &ibx_pch_dpll_funcs, DPLL_ID_PCH_PLL_B, 0 }, -+ { }, -+}; -+ -+static const struct intel_dpll_mgr pch_pll_mgr = { -+ .dpll_info = pch_plls, -+ .get_dpll = ibx_get_dpll, -+ .dump_hw_state = ibx_dump_hw_state, -+}; -+ -+static const struct dpll_info hsw_plls[] = { -+ { "WRPLL 1", &hsw_ddi_wrpll_funcs, DPLL_ID_WRPLL1, 0 }, -+ { "WRPLL 2", &hsw_ddi_wrpll_funcs, DPLL_ID_WRPLL2, 0 }, -+ { "SPLL", &hsw_ddi_spll_funcs, DPLL_ID_SPLL, 0 }, -+ { "LCPLL 810", &hsw_ddi_lcpll_funcs, DPLL_ID_LCPLL_810, INTEL_DPLL_ALWAYS_ON }, -+ { "LCPLL 1350", &hsw_ddi_lcpll_funcs, DPLL_ID_LCPLL_1350, INTEL_DPLL_ALWAYS_ON }, -+ { "LCPLL 2700", &hsw_ddi_lcpll_funcs, DPLL_ID_LCPLL_2700, INTEL_DPLL_ALWAYS_ON }, -+ { }, -+}; -+ -+static const struct intel_dpll_mgr hsw_pll_mgr = { -+ .dpll_info = hsw_plls, -+ .get_dpll = hsw_get_dpll, -+ .dump_hw_state = hsw_dump_hw_state, -+}; -+ -+static const struct dpll_info skl_plls[] = { -+ { "DPLL 0", &skl_ddi_dpll0_funcs, DPLL_ID_SKL_DPLL0, INTEL_DPLL_ALWAYS_ON }, -+ { "DPLL 1", &skl_ddi_pll_funcs, DPLL_ID_SKL_DPLL1, 0 }, -+ { "DPLL 2", &skl_ddi_pll_funcs, DPLL_ID_SKL_DPLL2, 0 }, -+ { "DPLL 3", &skl_ddi_pll_funcs, DPLL_ID_SKL_DPLL3, 0 }, -+ { }, -+}; -+ -+static const struct intel_dpll_mgr skl_pll_mgr = { -+ .dpll_info = skl_plls, -+ .get_dpll = skl_get_dpll, -+ .dump_hw_state = skl_dump_hw_state, -+}; -+ -+static const struct dpll_info bxt_plls[] = { -+ { "PORT PLL A", &bxt_ddi_pll_funcs, DPLL_ID_SKL_DPLL0, 0 }, -+ { "PORT PLL B", &bxt_ddi_pll_funcs, DPLL_ID_SKL_DPLL1, 0 }, -+ { "PORT PLL C", &bxt_ddi_pll_funcs, DPLL_ID_SKL_DPLL2, 0 }, -+ { }, -+}; -+ -+static const struct intel_dpll_mgr bxt_pll_mgr = { -+ .dpll_info = bxt_plls, -+ .get_dpll = bxt_get_dpll, -+ .dump_hw_state = bxt_dump_hw_state, -+}; -+ -+static void cnl_ddi_pll_enable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ const enum intel_dpll_id id = pll->info->id; -+ u32 val; -+ -+ /* 1. Enable DPLL power in DPLL_ENABLE. */ -+ val = I915_READ(CNL_DPLL_ENABLE(id)); -+ val |= PLL_POWER_ENABLE; -+ I915_WRITE(CNL_DPLL_ENABLE(id), val); -+ -+ /* 2. Wait for DPLL power state enabled in DPLL_ENABLE. */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ CNL_DPLL_ENABLE(id), -+ PLL_POWER_STATE, -+ PLL_POWER_STATE, -+ 5)) -+ DRM_ERROR("PLL %d Power not enabled\n", id); -+ -+ /* -+ * 3. Configure DPLL_CFGCR0 to set SSC enable/disable, -+ * select DP mode, and set DP link rate. -+ */ -+ val = pll->state.hw_state.cfgcr0; -+ I915_WRITE(CNL_DPLL_CFGCR0(id), val); -+ -+ /* 4. Reab back to ensure writes completed */ -+ POSTING_READ(CNL_DPLL_CFGCR0(id)); -+ -+ /* 3. Configure DPLL_CFGCR0 */ -+ /* Avoid touch CFGCR1 if HDMI mode is not enabled */ -+ if (pll->state.hw_state.cfgcr0 & DPLL_CFGCR0_HDMI_MODE) { -+ val = pll->state.hw_state.cfgcr1; -+ I915_WRITE(CNL_DPLL_CFGCR1(id), val); -+ /* 4. Reab back to ensure writes completed */ -+ POSTING_READ(CNL_DPLL_CFGCR1(id)); -+ } -+ -+ /* -+ * 5. If the frequency will result in a change to the voltage -+ * requirement, follow the Display Voltage Frequency Switching -+ * Sequence Before Frequency Change -+ * -+ * Note: DVFS is actually handled via the cdclk code paths, -+ * hence we do nothing here. -+ */ -+ -+ /* 6. Enable DPLL in DPLL_ENABLE. */ -+ val = I915_READ(CNL_DPLL_ENABLE(id)); -+ val |= PLL_ENABLE; -+ I915_WRITE(CNL_DPLL_ENABLE(id), val); -+ -+ /* 7. Wait for PLL lock status in DPLL_ENABLE. */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ CNL_DPLL_ENABLE(id), -+ PLL_LOCK, -+ PLL_LOCK, -+ 5)) -+ DRM_ERROR("PLL %d not locked\n", id); -+ -+ /* -+ * 8. If the frequency will result in a change to the voltage -+ * requirement, follow the Display Voltage Frequency Switching -+ * Sequence After Frequency Change -+ * -+ * Note: DVFS is actually handled via the cdclk code paths, -+ * hence we do nothing here. -+ */ -+ -+ /* -+ * 9. turn on the clock for the DDI and map the DPLL to the DDI -+ * Done at intel_ddi_clk_select -+ */ -+} -+ -+static void cnl_ddi_pll_disable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ const enum intel_dpll_id id = pll->info->id; -+ u32 val; -+ -+ /* -+ * 1. Configure DPCLKA_CFGCR0 to turn off the clock for the DDI. -+ * Done at intel_ddi_post_disable -+ */ -+ -+ /* -+ * 2. If the frequency will result in a change to the voltage -+ * requirement, follow the Display Voltage Frequency Switching -+ * Sequence Before Frequency Change -+ * -+ * Note: DVFS is actually handled via the cdclk code paths, -+ * hence we do nothing here. -+ */ -+ -+ /* 3. Disable DPLL through DPLL_ENABLE. */ -+ val = I915_READ(CNL_DPLL_ENABLE(id)); -+ val &= ~PLL_ENABLE; -+ I915_WRITE(CNL_DPLL_ENABLE(id), val); -+ -+ /* 4. Wait for PLL not locked status in DPLL_ENABLE. */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ CNL_DPLL_ENABLE(id), -+ PLL_LOCK, -+ 0, -+ 5)) -+ DRM_ERROR("PLL %d locked\n", id); -+ -+ /* -+ * 5. If the frequency will result in a change to the voltage -+ * requirement, follow the Display Voltage Frequency Switching -+ * Sequence After Frequency Change -+ * -+ * Note: DVFS is actually handled via the cdclk code paths, -+ * hence we do nothing here. -+ */ -+ -+ /* 6. Disable DPLL power in DPLL_ENABLE. */ -+ val = I915_READ(CNL_DPLL_ENABLE(id)); -+ val &= ~PLL_POWER_ENABLE; -+ I915_WRITE(CNL_DPLL_ENABLE(id), val); -+ -+ /* 7. Wait for DPLL power state disabled in DPLL_ENABLE. */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ CNL_DPLL_ENABLE(id), -+ PLL_POWER_STATE, -+ 0, -+ 5)) -+ DRM_ERROR("PLL %d Power not disabled\n", id); -+} -+ -+static bool cnl_ddi_pll_get_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ const enum intel_dpll_id id = pll->info->id; -+ intel_wakeref_t wakeref; -+ u32 val; -+ bool ret; -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ POWER_DOMAIN_PLLS); -+ if (!wakeref) -+ return false; -+ -+ ret = false; -+ -+ val = I915_READ(CNL_DPLL_ENABLE(id)); -+ if (!(val & PLL_ENABLE)) -+ goto out; -+ -+ val = I915_READ(CNL_DPLL_CFGCR0(id)); -+ hw_state->cfgcr0 = val; -+ -+ /* avoid reading back stale values if HDMI mode is not enabled */ -+ if (val & DPLL_CFGCR0_HDMI_MODE) { -+ hw_state->cfgcr1 = I915_READ(CNL_DPLL_CFGCR1(id)); -+ } -+ ret = true; -+ -+out: -+ intel_display_power_put(dev_priv, POWER_DOMAIN_PLLS, wakeref); -+ -+ return ret; -+} -+ -+static void cnl_wrpll_get_multipliers(int bestdiv, int *pdiv, -+ int *qdiv, int *kdiv) -+{ -+ /* even dividers */ -+ if (bestdiv % 2 == 0) { -+ if (bestdiv == 2) { -+ *pdiv = 2; -+ *qdiv = 1; -+ *kdiv = 1; -+ } else if (bestdiv % 4 == 0) { -+ *pdiv = 2; -+ *qdiv = bestdiv / 4; -+ *kdiv = 2; -+ } else if (bestdiv % 6 == 0) { -+ *pdiv = 3; -+ *qdiv = bestdiv / 6; -+ *kdiv = 2; -+ } else if (bestdiv % 5 == 0) { -+ *pdiv = 5; -+ *qdiv = bestdiv / 10; -+ *kdiv = 2; -+ } else if (bestdiv % 14 == 0) { -+ *pdiv = 7; -+ *qdiv = bestdiv / 14; -+ *kdiv = 2; -+ } -+ } else { -+ if (bestdiv == 3 || bestdiv == 5 || bestdiv == 7) { -+ *pdiv = bestdiv; -+ *qdiv = 1; -+ *kdiv = 1; -+ } else { /* 9, 15, 21 */ -+ *pdiv = bestdiv / 3; -+ *qdiv = 1; -+ *kdiv = 3; -+ } -+ } -+} -+ -+static void cnl_wrpll_params_populate(struct skl_wrpll_params *params, -+ u32 dco_freq, u32 ref_freq, -+ int pdiv, int qdiv, int kdiv) -+{ -+ u32 dco; -+ -+ switch (kdiv) { -+ case 1: -+ params->kdiv = 1; -+ break; -+ case 2: -+ params->kdiv = 2; -+ break; -+ case 3: -+ params->kdiv = 4; -+ break; -+ default: -+ WARN(1, "Incorrect KDiv\n"); -+ } -+ -+ switch (pdiv) { -+ case 2: -+ params->pdiv = 1; -+ break; -+ case 3: -+ params->pdiv = 2; -+ break; -+ case 5: -+ params->pdiv = 4; -+ break; -+ case 7: -+ params->pdiv = 8; -+ break; -+ default: -+ WARN(1, "Incorrect PDiv\n"); -+ } -+ -+ WARN_ON(kdiv != 2 && qdiv != 1); -+ -+ params->qdiv_ratio = qdiv; -+ params->qdiv_mode = (qdiv == 1) ? 0 : 1; -+ -+ dco = div_u64((u64)dco_freq << 15, ref_freq); -+ -+ params->dco_integer = dco >> 15; -+ params->dco_fraction = dco & 0x7fff; -+} -+ -+int cnl_hdmi_pll_ref_clock(struct drm_i915_private *dev_priv) -+{ -+ int ref_clock = dev_priv->cdclk.hw.ref; -+ -+ /* -+ * For ICL+, the spec states: if reference frequency is 38.4, -+ * use 19.2 because the DPLL automatically divides that by 2. -+ */ -+ if (INTEL_GEN(dev_priv) >= 11 && ref_clock == 38400) -+ ref_clock = 19200; -+ -+ return ref_clock; -+} -+ -+static bool -+cnl_ddi_calculate_wrpll(struct intel_crtc_state *crtc_state, -+ struct skl_wrpll_params *wrpll_params) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ u32 afe_clock = crtc_state->port_clock * 5; -+ u32 ref_clock; -+ u32 dco_min = 7998000; -+ u32 dco_max = 10000000; -+ u32 dco_mid = (dco_min + dco_max) / 2; -+ static const int dividers[] = { 2, 4, 6, 8, 10, 12, 14, 16, -+ 18, 20, 24, 28, 30, 32, 36, 40, -+ 42, 44, 48, 50, 52, 54, 56, 60, -+ 64, 66, 68, 70, 72, 76, 78, 80, -+ 84, 88, 90, 92, 96, 98, 100, 102, -+ 3, 5, 7, 9, 15, 21 }; -+ u32 dco, best_dco = 0, dco_centrality = 0; -+ u32 best_dco_centrality = U32_MAX; /* Spec meaning of 999999 MHz */ -+ int d, best_div = 0, pdiv = 0, qdiv = 0, kdiv = 0; -+ -+ for (d = 0; d < ARRAY_SIZE(dividers); d++) { -+ dco = afe_clock * dividers[d]; -+ -+ if ((dco <= dco_max) && (dco >= dco_min)) { -+ dco_centrality = abs(dco - dco_mid); -+ -+ if (dco_centrality < best_dco_centrality) { -+ best_dco_centrality = dco_centrality; -+ best_div = dividers[d]; -+ best_dco = dco; -+ } -+ } -+ } -+ -+ if (best_div == 0) -+ return false; -+ -+ cnl_wrpll_get_multipliers(best_div, &pdiv, &qdiv, &kdiv); -+ -+ ref_clock = cnl_hdmi_pll_ref_clock(dev_priv); -+ -+ cnl_wrpll_params_populate(wrpll_params, best_dco, ref_clock, -+ pdiv, qdiv, kdiv); -+ -+ return true; -+} -+ -+static bool cnl_ddi_hdmi_pll_dividers(struct intel_crtc_state *crtc_state) -+{ -+ u32 cfgcr0, cfgcr1; -+ struct skl_wrpll_params wrpll_params = { 0, }; -+ -+ cfgcr0 = DPLL_CFGCR0_HDMI_MODE; -+ -+ if (!cnl_ddi_calculate_wrpll(crtc_state, &wrpll_params)) -+ return false; -+ -+ cfgcr0 |= DPLL_CFGCR0_DCO_FRACTION(wrpll_params.dco_fraction) | -+ wrpll_params.dco_integer; -+ -+ cfgcr1 = DPLL_CFGCR1_QDIV_RATIO(wrpll_params.qdiv_ratio) | -+ DPLL_CFGCR1_QDIV_MODE(wrpll_params.qdiv_mode) | -+ DPLL_CFGCR1_KDIV(wrpll_params.kdiv) | -+ DPLL_CFGCR1_PDIV(wrpll_params.pdiv) | -+ DPLL_CFGCR1_CENTRAL_FREQ; -+ -+ memset(&crtc_state->dpll_hw_state, 0, -+ sizeof(crtc_state->dpll_hw_state)); -+ -+ crtc_state->dpll_hw_state.cfgcr0 = cfgcr0; -+ crtc_state->dpll_hw_state.cfgcr1 = cfgcr1; -+ return true; -+} -+ -+static bool -+cnl_ddi_dp_set_dpll_hw_state(struct intel_crtc_state *crtc_state) -+{ -+ u32 cfgcr0; -+ -+ cfgcr0 = DPLL_CFGCR0_SSC_ENABLE; -+ -+ switch (crtc_state->port_clock / 2) { -+ case 81000: -+ cfgcr0 |= DPLL_CFGCR0_LINK_RATE_810; -+ break; -+ case 135000: -+ cfgcr0 |= DPLL_CFGCR0_LINK_RATE_1350; -+ break; -+ case 270000: -+ cfgcr0 |= DPLL_CFGCR0_LINK_RATE_2700; -+ break; -+ /* eDP 1.4 rates */ -+ case 162000: -+ cfgcr0 |= DPLL_CFGCR0_LINK_RATE_1620; -+ break; -+ case 108000: -+ cfgcr0 |= DPLL_CFGCR0_LINK_RATE_1080; -+ break; -+ case 216000: -+ cfgcr0 |= DPLL_CFGCR0_LINK_RATE_2160; -+ break; -+ case 324000: -+ /* Some SKUs may require elevated I/O voltage to support this */ -+ cfgcr0 |= DPLL_CFGCR0_LINK_RATE_3240; -+ break; -+ case 405000: -+ /* Some SKUs may require elevated I/O voltage to support this */ -+ cfgcr0 |= DPLL_CFGCR0_LINK_RATE_4050; -+ break; -+ } -+ -+ memset(&crtc_state->dpll_hw_state, 0, -+ sizeof(crtc_state->dpll_hw_state)); -+ -+ crtc_state->dpll_hw_state.cfgcr0 = cfgcr0; -+ -+ return true; -+} -+ -+static struct intel_shared_dpll * -+cnl_get_dpll(struct intel_crtc_state *crtc_state, -+ struct intel_encoder *encoder) -+{ -+ struct intel_shared_dpll *pll; -+ bool bret; -+ -+ if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI)) { -+ bret = cnl_ddi_hdmi_pll_dividers(crtc_state); -+ if (!bret) { -+ DRM_DEBUG_KMS("Could not get HDMI pll dividers.\n"); -+ return NULL; -+ } -+ } else if (intel_crtc_has_dp_encoder(crtc_state)) { -+ bret = cnl_ddi_dp_set_dpll_hw_state(crtc_state); -+ if (!bret) { -+ DRM_DEBUG_KMS("Could not set DP dpll HW state.\n"); -+ return NULL; -+ } -+ } else { -+ DRM_DEBUG_KMS("Skip DPLL setup for output_types 0x%x\n", -+ crtc_state->output_types); -+ return NULL; -+ } -+ -+ pll = intel_find_shared_dpll(crtc_state, -+ DPLL_ID_SKL_DPLL0, -+ DPLL_ID_SKL_DPLL2); -+ if (!pll) { -+ DRM_DEBUG_KMS("No PLL selected\n"); -+ return NULL; -+ } -+ -+ intel_reference_shared_dpll(pll, crtc_state); -+ -+ return pll; -+} -+ -+static void cnl_dump_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ DRM_DEBUG_KMS("dpll_hw_state: " -+ "cfgcr0: 0x%x, cfgcr1: 0x%x\n", -+ hw_state->cfgcr0, -+ hw_state->cfgcr1); -+} -+ -+static const struct intel_shared_dpll_funcs cnl_ddi_pll_funcs = { -+ .enable = cnl_ddi_pll_enable, -+ .disable = cnl_ddi_pll_disable, -+ .get_hw_state = cnl_ddi_pll_get_hw_state, -+}; -+ -+static const struct dpll_info cnl_plls[] = { -+ { "DPLL 0", &cnl_ddi_pll_funcs, DPLL_ID_SKL_DPLL0, 0 }, -+ { "DPLL 1", &cnl_ddi_pll_funcs, DPLL_ID_SKL_DPLL1, 0 }, -+ { "DPLL 2", &cnl_ddi_pll_funcs, DPLL_ID_SKL_DPLL2, 0 }, -+ { }, -+}; -+ -+static const struct intel_dpll_mgr cnl_pll_mgr = { -+ .dpll_info = cnl_plls, -+ .get_dpll = cnl_get_dpll, -+ .dump_hw_state = cnl_dump_hw_state, -+}; -+ -+struct icl_combo_pll_params { -+ int clock; -+ struct skl_wrpll_params wrpll; -+}; -+ -+/* -+ * These values alrea already adjusted: they're the bits we write to the -+ * registers, not the logical values. -+ */ -+static const struct icl_combo_pll_params icl_dp_combo_pll_24MHz_values[] = { -+ { 540000, -+ { .dco_integer = 0x151, .dco_fraction = 0x4000, /* [0]: 5.4 */ -+ .pdiv = 0x2 /* 3 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, -+ { 270000, -+ { .dco_integer = 0x151, .dco_fraction = 0x4000, /* [1]: 2.7 */ -+ .pdiv = 0x2 /* 3 */, .kdiv = 2, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, -+ { 162000, -+ { .dco_integer = 0x151, .dco_fraction = 0x4000, /* [2]: 1.62 */ -+ .pdiv = 0x4 /* 5 */, .kdiv = 2, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, -+ { 324000, -+ { .dco_integer = 0x151, .dco_fraction = 0x4000, /* [3]: 3.24 */ -+ .pdiv = 0x4 /* 5 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, -+ { 216000, -+ { .dco_integer = 0x168, .dco_fraction = 0x0000, /* [4]: 2.16 */ -+ .pdiv = 0x1 /* 2 */, .kdiv = 2, .qdiv_mode = 1, .qdiv_ratio = 2, }, }, -+ { 432000, -+ { .dco_integer = 0x168, .dco_fraction = 0x0000, /* [5]: 4.32 */ -+ .pdiv = 0x1 /* 2 */, .kdiv = 2, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, -+ { 648000, -+ { .dco_integer = 0x195, .dco_fraction = 0x0000, /* [6]: 6.48 */ -+ .pdiv = 0x2 /* 3 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, -+ { 810000, -+ { .dco_integer = 0x151, .dco_fraction = 0x4000, /* [7]: 8.1 */ -+ .pdiv = 0x1 /* 2 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, -+}; -+ -+ -+/* Also used for 38.4 MHz values. */ -+static const struct icl_combo_pll_params icl_dp_combo_pll_19_2MHz_values[] = { -+ { 540000, -+ { .dco_integer = 0x1A5, .dco_fraction = 0x7000, /* [0]: 5.4 */ -+ .pdiv = 0x2 /* 3 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, -+ { 270000, -+ { .dco_integer = 0x1A5, .dco_fraction = 0x7000, /* [1]: 2.7 */ -+ .pdiv = 0x2 /* 3 */, .kdiv = 2, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, -+ { 162000, -+ { .dco_integer = 0x1A5, .dco_fraction = 0x7000, /* [2]: 1.62 */ -+ .pdiv = 0x4 /* 5 */, .kdiv = 2, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, -+ { 324000, -+ { .dco_integer = 0x1A5, .dco_fraction = 0x7000, /* [3]: 3.24 */ -+ .pdiv = 0x4 /* 5 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, -+ { 216000, -+ { .dco_integer = 0x1C2, .dco_fraction = 0x0000, /* [4]: 2.16 */ -+ .pdiv = 0x1 /* 2 */, .kdiv = 2, .qdiv_mode = 1, .qdiv_ratio = 2, }, }, -+ { 432000, -+ { .dco_integer = 0x1C2, .dco_fraction = 0x0000, /* [5]: 4.32 */ -+ .pdiv = 0x1 /* 2 */, .kdiv = 2, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, -+ { 648000, -+ { .dco_integer = 0x1FA, .dco_fraction = 0x2000, /* [6]: 6.48 */ -+ .pdiv = 0x2 /* 3 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, -+ { 810000, -+ { .dco_integer = 0x1A5, .dco_fraction = 0x7000, /* [7]: 8.1 */ -+ .pdiv = 0x1 /* 2 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, }, }, -+}; -+ -+static const struct skl_wrpll_params icl_tbt_pll_24MHz_values = { -+ .dco_integer = 0x151, .dco_fraction = 0x4000, -+ .pdiv = 0x4 /* 5 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, -+}; -+ -+static const struct skl_wrpll_params icl_tbt_pll_19_2MHz_values = { -+ .dco_integer = 0x1A5, .dco_fraction = 0x7000, -+ .pdiv = 0x4 /* 5 */, .kdiv = 1, .qdiv_mode = 0, .qdiv_ratio = 0, -+}; -+ -+static bool icl_calc_dp_combo_pll(struct intel_crtc_state *crtc_state, -+ struct skl_wrpll_params *pll_params) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ const struct icl_combo_pll_params *params = -+ dev_priv->cdclk.hw.ref == 24000 ? -+ icl_dp_combo_pll_24MHz_values : -+ icl_dp_combo_pll_19_2MHz_values; -+ int clock = crtc_state->port_clock; -+ int i; -+ -+ for (i = 0; i < ARRAY_SIZE(icl_dp_combo_pll_24MHz_values); i++) { -+ if (clock == params[i].clock) { -+ *pll_params = params[i].wrpll; -+ return true; -+ } -+ } -+ -+ MISSING_CASE(clock); -+ return false; -+} -+ -+static bool icl_calc_tbt_pll(struct intel_crtc_state *crtc_state, -+ struct skl_wrpll_params *pll_params) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ -+ *pll_params = dev_priv->cdclk.hw.ref == 24000 ? -+ icl_tbt_pll_24MHz_values : icl_tbt_pll_19_2MHz_values; -+ return true; -+} -+ -+static bool icl_calc_dpll_state(struct intel_crtc_state *crtc_state, -+ struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ u32 cfgcr0, cfgcr1; -+ struct skl_wrpll_params pll_params = { 0 }; -+ bool ret; -+ -+ if (intel_port_is_tc(dev_priv, encoder->port)) -+ ret = icl_calc_tbt_pll(crtc_state, &pll_params); -+ else if (intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI) || -+ intel_crtc_has_type(crtc_state, INTEL_OUTPUT_DSI)) -+ ret = cnl_ddi_calculate_wrpll(crtc_state, &pll_params); -+ else -+ ret = icl_calc_dp_combo_pll(crtc_state, &pll_params); -+ -+ if (!ret) -+ return false; -+ -+ cfgcr0 = DPLL_CFGCR0_DCO_FRACTION(pll_params.dco_fraction) | -+ pll_params.dco_integer; -+ -+ cfgcr1 = DPLL_CFGCR1_QDIV_RATIO(pll_params.qdiv_ratio) | -+ DPLL_CFGCR1_QDIV_MODE(pll_params.qdiv_mode) | -+ DPLL_CFGCR1_KDIV(pll_params.kdiv) | -+ DPLL_CFGCR1_PDIV(pll_params.pdiv) | -+ DPLL_CFGCR1_CENTRAL_FREQ_8400; -+ -+ memset(&crtc_state->dpll_hw_state, 0, -+ sizeof(crtc_state->dpll_hw_state)); -+ -+ crtc_state->dpll_hw_state.cfgcr0 = cfgcr0; -+ crtc_state->dpll_hw_state.cfgcr1 = cfgcr1; -+ -+ return true; -+} -+ -+ -+static enum tc_port icl_pll_id_to_tc_port(enum intel_dpll_id id) -+{ -+ return id - DPLL_ID_ICL_MGPLL1; -+} -+ -+enum intel_dpll_id icl_tc_port_to_pll_id(enum tc_port tc_port) -+{ -+ return tc_port + DPLL_ID_ICL_MGPLL1; -+} -+ -+static bool icl_mg_pll_find_divisors(int clock_khz, bool is_dp, bool use_ssc, -+ u32 *target_dco_khz, -+ struct intel_dpll_hw_state *state) -+{ -+ u32 dco_min_freq, dco_max_freq; -+ int div1_vals[] = {7, 5, 3, 2}; -+ unsigned int i; -+ int div2; -+ -+ dco_min_freq = is_dp ? 8100000 : use_ssc ? 8000000 : 7992000; -+ dco_max_freq = is_dp ? 8100000 : 10000000; -+ -+ for (i = 0; i < ARRAY_SIZE(div1_vals); i++) { -+ int div1 = div1_vals[i]; -+ -+ for (div2 = 10; div2 > 0; div2--) { -+ int dco = div1 * div2 * clock_khz * 5; -+ int a_divratio, tlinedrv, inputsel; -+ u32 hsdiv; -+ -+ if (dco < dco_min_freq || dco > dco_max_freq) -+ continue; -+ -+ if (div2 >= 2) { -+ a_divratio = is_dp ? 10 : 5; -+ tlinedrv = 2; -+ } else { -+ a_divratio = 5; -+ tlinedrv = 0; -+ } -+ inputsel = is_dp ? 0 : 1; -+ -+ switch (div1) { -+ default: -+ MISSING_CASE(div1); -+ /* fall through */ -+ case 2: -+ hsdiv = MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_2; -+ break; -+ case 3: -+ hsdiv = MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_3; -+ break; -+ case 5: -+ hsdiv = MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_5; -+ break; -+ case 7: -+ hsdiv = MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_7; -+ break; -+ } -+ -+ *target_dco_khz = dco; -+ -+ state->mg_refclkin_ctl = MG_REFCLKIN_CTL_OD_2_MUX(1); -+ -+ state->mg_clktop2_coreclkctl1 = -+ MG_CLKTOP2_CORECLKCTL1_A_DIVRATIO(a_divratio); -+ -+ state->mg_clktop2_hsclkctl = -+ MG_CLKTOP2_HSCLKCTL_TLINEDRV_CLKSEL(tlinedrv) | -+ MG_CLKTOP2_HSCLKCTL_CORE_INPUTSEL(inputsel) | -+ hsdiv | -+ MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO(div2); -+ -+ return true; -+ } -+ } -+ -+ return false; -+} -+ -+/* -+ * The specification for this function uses real numbers, so the math had to be -+ * adapted to integer-only calculation, that's why it looks so different. -+ */ -+static bool icl_calc_mg_pll_state(struct intel_crtc_state *crtc_state) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ struct intel_dpll_hw_state *pll_state = &crtc_state->dpll_hw_state; -+ int refclk_khz = dev_priv->cdclk.hw.ref; -+ int clock = crtc_state->port_clock; -+ u32 dco_khz, m1div, m2div_int, m2div_rem, m2div_frac; -+ u32 iref_ndiv, iref_trim, iref_pulse_w; -+ u32 prop_coeff, int_coeff; -+ u32 tdc_targetcnt, feedfwgain; -+ u64 ssc_stepsize, ssc_steplen, ssc_steplog; -+ u64 tmp; -+ bool use_ssc = false; -+ bool is_dp = !intel_crtc_has_type(crtc_state, INTEL_OUTPUT_HDMI); -+ -+ memset(pll_state, 0, sizeof(*pll_state)); -+ -+ if (!icl_mg_pll_find_divisors(clock, is_dp, use_ssc, &dco_khz, -+ pll_state)) { -+ DRM_DEBUG_KMS("Failed to find divisors for clock %d\n", clock); -+ return false; -+ } -+ -+ m1div = 2; -+ m2div_int = dco_khz / (refclk_khz * m1div); -+ if (m2div_int > 255) { -+ m1div = 4; -+ m2div_int = dco_khz / (refclk_khz * m1div); -+ if (m2div_int > 255) { -+ DRM_DEBUG_KMS("Failed to find mdiv for clock %d\n", -+ clock); -+ return false; -+ } -+ } -+ m2div_rem = dco_khz % (refclk_khz * m1div); -+ -+ tmp = (u64)m2div_rem * (1 << 22); -+ do_div(tmp, refclk_khz * m1div); -+ m2div_frac = tmp; -+ -+ switch (refclk_khz) { -+ case 19200: -+ iref_ndiv = 1; -+ iref_trim = 28; -+ iref_pulse_w = 1; -+ break; -+ case 24000: -+ iref_ndiv = 1; -+ iref_trim = 25; -+ iref_pulse_w = 2; -+ break; -+ case 38400: -+ iref_ndiv = 2; -+ iref_trim = 28; -+ iref_pulse_w = 1; -+ break; -+ default: -+ MISSING_CASE(refclk_khz); -+ return false; -+ } -+ -+ /* -+ * tdc_res = 0.000003 -+ * tdc_targetcnt = int(2 / (tdc_res * 8 * 50 * 1.1) / refclk_mhz + 0.5) -+ * -+ * The multiplication by 1000 is due to refclk MHz to KHz conversion. It -+ * was supposed to be a division, but we rearranged the operations of -+ * the formula to avoid early divisions so we don't multiply the -+ * rounding errors. -+ * -+ * 0.000003 * 8 * 50 * 1.1 = 0.00132, also known as 132 / 100000, which -+ * we also rearrange to work with integers. -+ * -+ * The 0.5 transformed to 5 results in a multiplication by 10 and the -+ * last division by 10. -+ */ -+ tdc_targetcnt = (2 * 1000 * 100000 * 10 / (132 * refclk_khz) + 5) / 10; -+ -+ /* -+ * Here we divide dco_khz by 10 in order to allow the dividend to fit in -+ * 32 bits. That's not a problem since we round the division down -+ * anyway. -+ */ -+ feedfwgain = (use_ssc || m2div_rem > 0) ? -+ m1div * 1000000 * 100 / (dco_khz * 3 / 10) : 0; -+ -+ if (dco_khz >= 9000000) { -+ prop_coeff = 5; -+ int_coeff = 10; -+ } else { -+ prop_coeff = 4; -+ int_coeff = 8; -+ } -+ -+ if (use_ssc) { -+ tmp = (u64)dco_khz * 47 * 32; -+ do_div(tmp, refclk_khz * m1div * 10000); -+ ssc_stepsize = tmp; -+ -+ tmp = (u64)dco_khz * 1000; -+ ssc_steplen = DIV_ROUND_UP_ULL(tmp, 32 * 2 * 32); -+ } else { -+ ssc_stepsize = 0; -+ ssc_steplen = 0; -+ } -+ ssc_steplog = 4; -+ -+ pll_state->mg_pll_div0 = (m2div_rem > 0 ? MG_PLL_DIV0_FRACNEN_H : 0) | -+ MG_PLL_DIV0_FBDIV_FRAC(m2div_frac) | -+ MG_PLL_DIV0_FBDIV_INT(m2div_int); -+ -+ pll_state->mg_pll_div1 = MG_PLL_DIV1_IREF_NDIVRATIO(iref_ndiv) | -+ MG_PLL_DIV1_DITHER_DIV_2 | -+ MG_PLL_DIV1_NDIVRATIO(1) | -+ MG_PLL_DIV1_FBPREDIV(m1div); -+ -+ pll_state->mg_pll_lf = MG_PLL_LF_TDCTARGETCNT(tdc_targetcnt) | -+ MG_PLL_LF_AFCCNTSEL_512 | -+ MG_PLL_LF_GAINCTRL(1) | -+ MG_PLL_LF_INT_COEFF(int_coeff) | -+ MG_PLL_LF_PROP_COEFF(prop_coeff); -+ -+ pll_state->mg_pll_frac_lock = MG_PLL_FRAC_LOCK_TRUELOCK_CRIT_32 | -+ MG_PLL_FRAC_LOCK_EARLYLOCK_CRIT_32 | -+ MG_PLL_FRAC_LOCK_LOCKTHRESH(10) | -+ MG_PLL_FRAC_LOCK_DCODITHEREN | -+ MG_PLL_FRAC_LOCK_FEEDFWRDGAIN(feedfwgain); -+ if (use_ssc || m2div_rem > 0) -+ pll_state->mg_pll_frac_lock |= MG_PLL_FRAC_LOCK_FEEDFWRDCAL_EN; -+ -+ pll_state->mg_pll_ssc = (use_ssc ? MG_PLL_SSC_EN : 0) | -+ MG_PLL_SSC_TYPE(2) | -+ MG_PLL_SSC_STEPLENGTH(ssc_steplen) | -+ MG_PLL_SSC_STEPNUM(ssc_steplog) | -+ MG_PLL_SSC_FLLEN | -+ MG_PLL_SSC_STEPSIZE(ssc_stepsize); -+ -+ pll_state->mg_pll_tdc_coldst_bias = MG_PLL_TDC_COLDST_COLDSTART | -+ MG_PLL_TDC_COLDST_IREFINT_EN | -+ MG_PLL_TDC_COLDST_REFBIAS_START_PULSE_W(iref_pulse_w) | -+ MG_PLL_TDC_TDCOVCCORR_EN | -+ MG_PLL_TDC_TDCSEL(3); -+ -+ pll_state->mg_pll_bias = MG_PLL_BIAS_BIAS_GB_SEL(3) | -+ MG_PLL_BIAS_INIT_DCOAMP(0x3F) | -+ MG_PLL_BIAS_BIAS_BONUS(10) | -+ MG_PLL_BIAS_BIASCAL_EN | -+ MG_PLL_BIAS_CTRIM(12) | -+ MG_PLL_BIAS_VREF_RDAC(4) | -+ MG_PLL_BIAS_IREFTRIM(iref_trim); -+ -+ if (refclk_khz == 38400) { -+ pll_state->mg_pll_tdc_coldst_bias_mask = MG_PLL_TDC_COLDST_COLDSTART; -+ pll_state->mg_pll_bias_mask = 0; -+ } else { -+ pll_state->mg_pll_tdc_coldst_bias_mask = -1U; -+ pll_state->mg_pll_bias_mask = -1U; -+ } -+ -+ pll_state->mg_pll_tdc_coldst_bias &= pll_state->mg_pll_tdc_coldst_bias_mask; -+ pll_state->mg_pll_bias &= pll_state->mg_pll_bias_mask; -+ -+ return true; -+} -+ -+static struct intel_shared_dpll * -+icl_get_dpll(struct intel_crtc_state *crtc_state, -+ struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ struct intel_digital_port *intel_dig_port; -+ struct intel_shared_dpll *pll; -+ enum port port = encoder->port; -+ enum intel_dpll_id min, max; -+ bool ret; -+ -+ if (intel_port_is_combophy(dev_priv, port)) { -+ min = DPLL_ID_ICL_DPLL0; -+ max = DPLL_ID_ICL_DPLL1; -+ ret = icl_calc_dpll_state(crtc_state, encoder); -+ } else if (intel_port_is_tc(dev_priv, port)) { -+ if (encoder->type == INTEL_OUTPUT_DP_MST) { -+ struct intel_dp_mst_encoder *mst_encoder; -+ -+ mst_encoder = enc_to_mst(&encoder->base); -+ intel_dig_port = mst_encoder->primary; -+ } else { -+ intel_dig_port = enc_to_dig_port(&encoder->base); -+ } -+ -+ if (intel_dig_port->tc_type == TC_PORT_TBT) { -+ min = DPLL_ID_ICL_TBTPLL; -+ max = min; -+ ret = icl_calc_dpll_state(crtc_state, encoder); -+ } else { -+ enum tc_port tc_port; -+ -+ tc_port = intel_port_to_tc(dev_priv, port); -+ min = icl_tc_port_to_pll_id(tc_port); -+ max = min; -+ ret = icl_calc_mg_pll_state(crtc_state); -+ } -+ } else { -+ MISSING_CASE(port); -+ return NULL; -+ } -+ -+ if (!ret) { -+ DRM_DEBUG_KMS("Could not calculate PLL state.\n"); -+ return NULL; -+ } -+ -+ -+ pll = intel_find_shared_dpll(crtc_state, min, max); -+ if (!pll) { -+ DRM_DEBUG_KMS("No PLL selected\n"); -+ return NULL; -+ } -+ -+ intel_reference_shared_dpll(pll, crtc_state); -+ -+ return pll; -+} -+ -+static bool mg_pll_get_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ const enum intel_dpll_id id = pll->info->id; -+ enum tc_port tc_port = icl_pll_id_to_tc_port(id); -+ intel_wakeref_t wakeref; -+ bool ret = false; -+ u32 val; -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ POWER_DOMAIN_PLLS); -+ if (!wakeref) -+ return false; -+ -+ val = I915_READ(MG_PLL_ENABLE(tc_port)); -+ if (!(val & PLL_ENABLE)) -+ goto out; -+ -+ hw_state->mg_refclkin_ctl = I915_READ(MG_REFCLKIN_CTL(tc_port)); -+ hw_state->mg_refclkin_ctl &= MG_REFCLKIN_CTL_OD_2_MUX_MASK; -+ -+ hw_state->mg_clktop2_coreclkctl1 = -+ I915_READ(MG_CLKTOP2_CORECLKCTL1(tc_port)); -+ hw_state->mg_clktop2_coreclkctl1 &= -+ MG_CLKTOP2_CORECLKCTL1_A_DIVRATIO_MASK; -+ -+ hw_state->mg_clktop2_hsclkctl = -+ I915_READ(MG_CLKTOP2_HSCLKCTL(tc_port)); -+ hw_state->mg_clktop2_hsclkctl &= -+ MG_CLKTOP2_HSCLKCTL_TLINEDRV_CLKSEL_MASK | -+ MG_CLKTOP2_HSCLKCTL_CORE_INPUTSEL_MASK | -+ MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_MASK | -+ MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO_MASK; -+ -+ hw_state->mg_pll_div0 = I915_READ(MG_PLL_DIV0(tc_port)); -+ hw_state->mg_pll_div1 = I915_READ(MG_PLL_DIV1(tc_port)); -+ hw_state->mg_pll_lf = I915_READ(MG_PLL_LF(tc_port)); -+ hw_state->mg_pll_frac_lock = I915_READ(MG_PLL_FRAC_LOCK(tc_port)); -+ hw_state->mg_pll_ssc = I915_READ(MG_PLL_SSC(tc_port)); -+ -+ hw_state->mg_pll_bias = I915_READ(MG_PLL_BIAS(tc_port)); -+ hw_state->mg_pll_tdc_coldst_bias = -+ I915_READ(MG_PLL_TDC_COLDST_BIAS(tc_port)); -+ -+ if (dev_priv->cdclk.hw.ref == 38400) { -+ hw_state->mg_pll_tdc_coldst_bias_mask = MG_PLL_TDC_COLDST_COLDSTART; -+ hw_state->mg_pll_bias_mask = 0; -+ } else { -+ hw_state->mg_pll_tdc_coldst_bias_mask = -1U; -+ hw_state->mg_pll_bias_mask = -1U; -+ } -+ -+ hw_state->mg_pll_tdc_coldst_bias &= hw_state->mg_pll_tdc_coldst_bias_mask; -+ hw_state->mg_pll_bias &= hw_state->mg_pll_bias_mask; -+ -+ ret = true; -+out: -+ intel_display_power_put(dev_priv, POWER_DOMAIN_PLLS, wakeref); -+ return ret; -+} -+ -+static bool icl_pll_get_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ struct intel_dpll_hw_state *hw_state, -+ i915_reg_t enable_reg) -+{ -+ const enum intel_dpll_id id = pll->info->id; -+ intel_wakeref_t wakeref; -+ bool ret = false; -+ u32 val; -+ -+ wakeref = intel_display_power_get_if_enabled(dev_priv, -+ POWER_DOMAIN_PLLS); -+ if (!wakeref) -+ return false; -+ -+ val = I915_READ(enable_reg); -+ if (!(val & PLL_ENABLE)) -+ goto out; -+ -+ hw_state->cfgcr0 = I915_READ(ICL_DPLL_CFGCR0(id)); -+ hw_state->cfgcr1 = I915_READ(ICL_DPLL_CFGCR1(id)); -+ -+ ret = true; -+out: -+ intel_display_power_put(dev_priv, POWER_DOMAIN_PLLS, wakeref); -+ return ret; -+} -+ -+static bool combo_pll_get_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ return icl_pll_get_hw_state(dev_priv, pll, hw_state, -+ CNL_DPLL_ENABLE(pll->info->id)); -+} -+ -+static bool tbt_pll_get_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ return icl_pll_get_hw_state(dev_priv, pll, hw_state, TBT_PLL_ENABLE); -+} -+ -+static void icl_dpll_write(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ struct intel_dpll_hw_state *hw_state = &pll->state.hw_state; -+ const enum intel_dpll_id id = pll->info->id; -+ -+ I915_WRITE(ICL_DPLL_CFGCR0(id), hw_state->cfgcr0); -+ I915_WRITE(ICL_DPLL_CFGCR1(id), hw_state->cfgcr1); -+ POSTING_READ(ICL_DPLL_CFGCR1(id)); -+} -+ -+static void icl_mg_pll_write(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ struct intel_dpll_hw_state *hw_state = &pll->state.hw_state; -+ enum tc_port tc_port = icl_pll_id_to_tc_port(pll->info->id); -+ u32 val; -+ -+ /* -+ * Some of the following registers have reserved fields, so program -+ * these with RMW based on a mask. The mask can be fixed or generated -+ * during the calc/readout phase if the mask depends on some other HW -+ * state like refclk, see icl_calc_mg_pll_state(). -+ */ -+ val = I915_READ(MG_REFCLKIN_CTL(tc_port)); -+ val &= ~MG_REFCLKIN_CTL_OD_2_MUX_MASK; -+ val |= hw_state->mg_refclkin_ctl; -+ I915_WRITE(MG_REFCLKIN_CTL(tc_port), val); -+ -+ val = I915_READ(MG_CLKTOP2_CORECLKCTL1(tc_port)); -+ val &= ~MG_CLKTOP2_CORECLKCTL1_A_DIVRATIO_MASK; -+ val |= hw_state->mg_clktop2_coreclkctl1; -+ I915_WRITE(MG_CLKTOP2_CORECLKCTL1(tc_port), val); -+ -+ val = I915_READ(MG_CLKTOP2_HSCLKCTL(tc_port)); -+ val &= ~(MG_CLKTOP2_HSCLKCTL_TLINEDRV_CLKSEL_MASK | -+ MG_CLKTOP2_HSCLKCTL_CORE_INPUTSEL_MASK | -+ MG_CLKTOP2_HSCLKCTL_HSDIV_RATIO_MASK | -+ MG_CLKTOP2_HSCLKCTL_DSDIV_RATIO_MASK); -+ val |= hw_state->mg_clktop2_hsclkctl; -+ I915_WRITE(MG_CLKTOP2_HSCLKCTL(tc_port), val); -+ -+ I915_WRITE(MG_PLL_DIV0(tc_port), hw_state->mg_pll_div0); -+ I915_WRITE(MG_PLL_DIV1(tc_port), hw_state->mg_pll_div1); -+ I915_WRITE(MG_PLL_LF(tc_port), hw_state->mg_pll_lf); -+ I915_WRITE(MG_PLL_FRAC_LOCK(tc_port), hw_state->mg_pll_frac_lock); -+ I915_WRITE(MG_PLL_SSC(tc_port), hw_state->mg_pll_ssc); -+ -+ val = I915_READ(MG_PLL_BIAS(tc_port)); -+ val &= ~hw_state->mg_pll_bias_mask; -+ val |= hw_state->mg_pll_bias; -+ I915_WRITE(MG_PLL_BIAS(tc_port), val); -+ -+ val = I915_READ(MG_PLL_TDC_COLDST_BIAS(tc_port)); -+ val &= ~hw_state->mg_pll_tdc_coldst_bias_mask; -+ val |= hw_state->mg_pll_tdc_coldst_bias; -+ I915_WRITE(MG_PLL_TDC_COLDST_BIAS(tc_port), val); -+ -+ POSTING_READ(MG_PLL_TDC_COLDST_BIAS(tc_port)); -+} -+ -+static void icl_pll_power_enable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ i915_reg_t enable_reg) -+{ -+ u32 val; -+ -+ val = I915_READ(enable_reg); -+ val |= PLL_POWER_ENABLE; -+ I915_WRITE(enable_reg, val); -+ -+ /* -+ * The spec says we need to "wait" but it also says it should be -+ * immediate. -+ */ -+ if (intel_wait_for_register(&dev_priv->uncore, enable_reg, -+ PLL_POWER_STATE, PLL_POWER_STATE, 1)) -+ DRM_ERROR("PLL %d Power not enabled\n", pll->info->id); -+} -+ -+static void icl_pll_enable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ i915_reg_t enable_reg) -+{ -+ u32 val; -+ -+ val = I915_READ(enable_reg); -+ val |= PLL_ENABLE; -+ I915_WRITE(enable_reg, val); -+ -+ /* Timeout is actually 600us. */ -+ if (intel_wait_for_register(&dev_priv->uncore, enable_reg, -+ PLL_LOCK, PLL_LOCK, 1)) -+ DRM_ERROR("PLL %d not locked\n", pll->info->id); -+} -+ -+static void combo_pll_enable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ i915_reg_t enable_reg = CNL_DPLL_ENABLE(pll->info->id); -+ -+ icl_pll_power_enable(dev_priv, pll, enable_reg); -+ -+ icl_dpll_write(dev_priv, pll); -+ -+ /* -+ * DVFS pre sequence would be here, but in our driver the cdclk code -+ * paths should already be setting the appropriate voltage, hence we do -+ * nothing here. -+ */ -+ -+ icl_pll_enable(dev_priv, pll, enable_reg); -+ -+ /* DVFS post sequence would be here. See the comment above. */ -+} -+ -+static void tbt_pll_enable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ icl_pll_power_enable(dev_priv, pll, TBT_PLL_ENABLE); -+ -+ icl_dpll_write(dev_priv, pll); -+ -+ /* -+ * DVFS pre sequence would be here, but in our driver the cdclk code -+ * paths should already be setting the appropriate voltage, hence we do -+ * nothing here. -+ */ -+ -+ icl_pll_enable(dev_priv, pll, TBT_PLL_ENABLE); -+ -+ /* DVFS post sequence would be here. See the comment above. */ -+} -+ -+static void mg_pll_enable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ i915_reg_t enable_reg = -+ MG_PLL_ENABLE(icl_pll_id_to_tc_port(pll->info->id)); -+ -+ icl_pll_power_enable(dev_priv, pll, enable_reg); -+ -+ icl_mg_pll_write(dev_priv, pll); -+ -+ /* -+ * DVFS pre sequence would be here, but in our driver the cdclk code -+ * paths should already be setting the appropriate voltage, hence we do -+ * nothing here. -+ */ -+ -+ icl_pll_enable(dev_priv, pll, enable_reg); -+ -+ /* DVFS post sequence would be here. See the comment above. */ -+} -+ -+static void icl_pll_disable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ i915_reg_t enable_reg) -+{ -+ u32 val; -+ -+ /* The first steps are done by intel_ddi_post_disable(). */ -+ -+ /* -+ * DVFS pre sequence would be here, but in our driver the cdclk code -+ * paths should already be setting the appropriate voltage, hence we do -+ * nothign here. -+ */ -+ -+ val = I915_READ(enable_reg); -+ val &= ~PLL_ENABLE; -+ I915_WRITE(enable_reg, val); -+ -+ /* Timeout is actually 1us. */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ enable_reg, PLL_LOCK, 0, 1)) -+ DRM_ERROR("PLL %d locked\n", pll->info->id); -+ -+ /* DVFS post sequence would be here. See the comment above. */ -+ -+ val = I915_READ(enable_reg); -+ val &= ~PLL_POWER_ENABLE; -+ I915_WRITE(enable_reg, val); -+ -+ /* -+ * The spec says we need to "wait" but it also says it should be -+ * immediate. -+ */ -+ if (intel_wait_for_register(&dev_priv->uncore, -+ enable_reg, PLL_POWER_STATE, 0, 1)) -+ DRM_ERROR("PLL %d Power not disabled\n", pll->info->id); -+} -+ -+static void combo_pll_disable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ icl_pll_disable(dev_priv, pll, CNL_DPLL_ENABLE(pll->info->id)); -+} -+ -+static void tbt_pll_disable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ icl_pll_disable(dev_priv, pll, TBT_PLL_ENABLE); -+} -+ -+static void mg_pll_disable(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll) -+{ -+ i915_reg_t enable_reg = -+ MG_PLL_ENABLE(icl_pll_id_to_tc_port(pll->info->id)); -+ -+ icl_pll_disable(dev_priv, pll, enable_reg); -+} -+ -+static void icl_dump_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ DRM_DEBUG_KMS("dpll_hw_state: cfgcr0: 0x%x, cfgcr1: 0x%x, " -+ "mg_refclkin_ctl: 0x%x, hg_clktop2_coreclkctl1: 0x%x, " -+ "mg_clktop2_hsclkctl: 0x%x, mg_pll_div0: 0x%x, " -+ "mg_pll_div2: 0x%x, mg_pll_lf: 0x%x, " -+ "mg_pll_frac_lock: 0x%x, mg_pll_ssc: 0x%x, " -+ "mg_pll_bias: 0x%x, mg_pll_tdc_coldst_bias: 0x%x\n", -+ hw_state->cfgcr0, hw_state->cfgcr1, -+ hw_state->mg_refclkin_ctl, -+ hw_state->mg_clktop2_coreclkctl1, -+ hw_state->mg_clktop2_hsclkctl, -+ hw_state->mg_pll_div0, -+ hw_state->mg_pll_div1, -+ hw_state->mg_pll_lf, -+ hw_state->mg_pll_frac_lock, -+ hw_state->mg_pll_ssc, -+ hw_state->mg_pll_bias, -+ hw_state->mg_pll_tdc_coldst_bias); -+} -+ -+static const struct intel_shared_dpll_funcs combo_pll_funcs = { -+ .enable = combo_pll_enable, -+ .disable = combo_pll_disable, -+ .get_hw_state = combo_pll_get_hw_state, -+}; -+ -+static const struct intel_shared_dpll_funcs tbt_pll_funcs = { -+ .enable = tbt_pll_enable, -+ .disable = tbt_pll_disable, -+ .get_hw_state = tbt_pll_get_hw_state, -+}; -+ -+static const struct intel_shared_dpll_funcs mg_pll_funcs = { -+ .enable = mg_pll_enable, -+ .disable = mg_pll_disable, -+ .get_hw_state = mg_pll_get_hw_state, -+}; -+ -+static const struct dpll_info icl_plls[] = { -+ { "DPLL 0", &combo_pll_funcs, DPLL_ID_ICL_DPLL0, 0 }, -+ { "DPLL 1", &combo_pll_funcs, DPLL_ID_ICL_DPLL1, 0 }, -+ { "TBT PLL", &tbt_pll_funcs, DPLL_ID_ICL_TBTPLL, 0 }, -+ { "MG PLL 1", &mg_pll_funcs, DPLL_ID_ICL_MGPLL1, 0 }, -+ { "MG PLL 2", &mg_pll_funcs, DPLL_ID_ICL_MGPLL2, 0 }, -+ { "MG PLL 3", &mg_pll_funcs, DPLL_ID_ICL_MGPLL3, 0 }, -+ { "MG PLL 4", &mg_pll_funcs, DPLL_ID_ICL_MGPLL4, 0 }, -+ { }, -+}; -+ -+static const struct intel_dpll_mgr icl_pll_mgr = { -+ .dpll_info = icl_plls, -+ .get_dpll = icl_get_dpll, -+ .dump_hw_state = icl_dump_hw_state, -+}; -+ -+static const struct dpll_info ehl_plls[] = { -+ { "DPLL 0", &combo_pll_funcs, DPLL_ID_ICL_DPLL0, 0 }, -+ { "DPLL 1", &combo_pll_funcs, DPLL_ID_ICL_DPLL1, 0 }, -+ { }, -+}; -+ -+static const struct intel_dpll_mgr ehl_pll_mgr = { -+ .dpll_info = ehl_plls, -+ .get_dpll = icl_get_dpll, -+ .dump_hw_state = icl_dump_hw_state, -+}; -+ -+/** -+ * intel_shared_dpll_init - Initialize shared DPLLs -+ * @dev: drm device -+ * -+ * Initialize shared DPLLs for @dev. -+ */ -+void intel_shared_dpll_init(struct drm_device *dev) -+{ -+ struct drm_i915_private *dev_priv = to_i915(dev); -+ const struct intel_dpll_mgr *dpll_mgr = NULL; -+ const struct dpll_info *dpll_info; -+ int i; -+ -+ if (IS_ELKHARTLAKE(dev_priv)) -+ dpll_mgr = &ehl_pll_mgr; -+ else if (INTEL_GEN(dev_priv) >= 11) -+ dpll_mgr = &icl_pll_mgr; -+ else if (IS_CANNONLAKE(dev_priv)) -+ dpll_mgr = &cnl_pll_mgr; -+ else if (IS_GEN9_BC(dev_priv)) -+ dpll_mgr = &skl_pll_mgr; -+ else if (IS_GEN9_LP(dev_priv)) -+ dpll_mgr = &bxt_pll_mgr; -+ else if (HAS_DDI(dev_priv)) -+ dpll_mgr = &hsw_pll_mgr; -+ else if (HAS_PCH_IBX(dev_priv) || HAS_PCH_CPT(dev_priv)) -+ dpll_mgr = &pch_pll_mgr; -+ -+ if (!dpll_mgr) { -+ dev_priv->num_shared_dpll = 0; -+ return; -+ } -+ -+ dpll_info = dpll_mgr->dpll_info; -+ -+ for (i = 0; dpll_info[i].name; i++) { -+ WARN_ON(i != dpll_info[i].id); -+ dev_priv->shared_dplls[i].info = &dpll_info[i]; -+ } -+ -+ dev_priv->dpll_mgr = dpll_mgr; -+ dev_priv->num_shared_dpll = i; -+ mutex_init(&dev_priv->dpll_lock); -+ -+ BUG_ON(dev_priv->num_shared_dpll > I915_NUM_PLLS); -+ -+ /* FIXME: Move this to a more suitable place */ -+ if (HAS_DDI(dev_priv)) -+ intel_ddi_pll_init(dev); -+} -+ -+/** -+ * intel_get_shared_dpll - get a shared DPLL for CRTC and encoder combination -+ * @crtc_state: atomic state for the crtc -+ * @encoder: encoder -+ * -+ * Find an appropriate DPLL for the given CRTC and encoder combination. A -+ * reference from the @crtc_state to the returned pll is registered in the -+ * atomic state. That configuration is made effective by calling -+ * intel_shared_dpll_swap_state(). The reference should be released by calling -+ * intel_release_shared_dpll(). -+ * -+ * Returns: -+ * A shared DPLL to be used by @crtc_state and @encoder. -+ */ -+struct intel_shared_dpll * -+intel_get_shared_dpll(struct intel_crtc_state *crtc_state, -+ struct intel_encoder *encoder) -+{ -+ struct drm_i915_private *dev_priv = to_i915(crtc_state->base.crtc->dev); -+ const struct intel_dpll_mgr *dpll_mgr = dev_priv->dpll_mgr; -+ -+ if (WARN_ON(!dpll_mgr)) -+ return NULL; -+ -+ return dpll_mgr->get_dpll(crtc_state, encoder); -+} -+ -+/** -+ * intel_release_shared_dpll - end use of DPLL by CRTC in atomic state -+ * @dpll: dpll in use by @crtc -+ * @crtc: crtc -+ * @state: atomic state -+ * -+ * This function releases the reference from @crtc to @dpll from the -+ * atomic @state. The new configuration is made effective by calling -+ * intel_shared_dpll_swap_state(). -+ */ -+void intel_release_shared_dpll(struct intel_shared_dpll *dpll, -+ struct intel_crtc *crtc, -+ struct drm_atomic_state *state) -+{ -+ struct intel_shared_dpll_state *shared_dpll_state; -+ -+ shared_dpll_state = intel_atomic_get_shared_dpll_state(state); -+ shared_dpll_state[dpll->info->id].crtc_mask &= ~(1 << crtc->pipe); -+} -+ -+/** -+ * intel_shared_dpll_dump_hw_state - write hw_state to dmesg -+ * @dev_priv: i915 drm device -+ * @hw_state: hw state to be written to the log -+ * -+ * Write the relevant values in @hw_state to dmesg using DRM_DEBUG_KMS. -+ */ -+void intel_dpll_dump_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_dpll_hw_state *hw_state) -+{ -+ if (dev_priv->dpll_mgr) { -+ dev_priv->dpll_mgr->dump_hw_state(dev_priv, hw_state); -+ } else { -+ /* fallback for platforms that don't use the shared dpll -+ * infrastructure -+ */ -+ DRM_DEBUG_KMS("dpll_hw_state: dpll: 0x%x, dpll_md: 0x%x, " -+ "fp0: 0x%x, fp1: 0x%x\n", -+ hw_state->dpll, -+ hw_state->dpll_md, -+ hw_state->fp0, -+ hw_state->fp1); -+ } -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_dpll_mgr.h b/drivers/gpu/drm/i915_legacy/intel_dpll_mgr.h -new file mode 100644 -index 000000000000..bd8124cc81ed ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_dpll_mgr.h -@@ -0,0 +1,347 @@ -+/* -+ * Copyright © 2012-2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ */ -+ -+#ifndef _INTEL_DPLL_MGR_H_ -+#define _INTEL_DPLL_MGR_H_ -+ -+/*FIXME: Move this to a more appropriate place. */ -+#define abs_diff(a, b) ({ \ -+ typeof(a) __a = (a); \ -+ typeof(b) __b = (b); \ -+ (void) (&__a == &__b); \ -+ __a > __b ? (__a - __b) : (__b - __a); }) -+ -+struct drm_i915_private; -+struct intel_crtc; -+struct intel_crtc_state; -+struct intel_encoder; -+ -+struct intel_shared_dpll; -+struct intel_dpll_mgr; -+ -+/** -+ * enum intel_dpll_id - possible DPLL ids -+ * -+ * Enumeration of possible IDs for a DPLL. Real shared dpll ids must be >= 0. -+ */ -+enum intel_dpll_id { -+ /** -+ * @DPLL_ID_PRIVATE: non-shared dpll in use -+ */ -+ DPLL_ID_PRIVATE = -1, -+ -+ /** -+ * @DPLL_ID_PCH_PLL_A: DPLL A in ILK, SNB and IVB -+ */ -+ DPLL_ID_PCH_PLL_A = 0, -+ /** -+ * @DPLL_ID_PCH_PLL_B: DPLL B in ILK, SNB and IVB -+ */ -+ DPLL_ID_PCH_PLL_B = 1, -+ -+ -+ /** -+ * @DPLL_ID_WRPLL1: HSW and BDW WRPLL1 -+ */ -+ DPLL_ID_WRPLL1 = 0, -+ /** -+ * @DPLL_ID_WRPLL2: HSW and BDW WRPLL2 -+ */ -+ DPLL_ID_WRPLL2 = 1, -+ /** -+ * @DPLL_ID_SPLL: HSW and BDW SPLL -+ */ -+ DPLL_ID_SPLL = 2, -+ /** -+ * @DPLL_ID_LCPLL_810: HSW and BDW 0.81 GHz LCPLL -+ */ -+ DPLL_ID_LCPLL_810 = 3, -+ /** -+ * @DPLL_ID_LCPLL_1350: HSW and BDW 1.35 GHz LCPLL -+ */ -+ DPLL_ID_LCPLL_1350 = 4, -+ /** -+ * @DPLL_ID_LCPLL_2700: HSW and BDW 2.7 GHz LCPLL -+ */ -+ DPLL_ID_LCPLL_2700 = 5, -+ -+ -+ /** -+ * @DPLL_ID_SKL_DPLL0: SKL and later DPLL0 -+ */ -+ DPLL_ID_SKL_DPLL0 = 0, -+ /** -+ * @DPLL_ID_SKL_DPLL1: SKL and later DPLL1 -+ */ -+ DPLL_ID_SKL_DPLL1 = 1, -+ /** -+ * @DPLL_ID_SKL_DPLL2: SKL and later DPLL2 -+ */ -+ DPLL_ID_SKL_DPLL2 = 2, -+ /** -+ * @DPLL_ID_SKL_DPLL3: SKL and later DPLL3 -+ */ -+ DPLL_ID_SKL_DPLL3 = 3, -+ -+ -+ /** -+ * @DPLL_ID_ICL_DPLL0: ICL combo PHY DPLL0 -+ */ -+ DPLL_ID_ICL_DPLL0 = 0, -+ /** -+ * @DPLL_ID_ICL_DPLL1: ICL combo PHY DPLL1 -+ */ -+ DPLL_ID_ICL_DPLL1 = 1, -+ /** -+ * @DPLL_ID_ICL_TBTPLL: ICL TBT PLL -+ */ -+ DPLL_ID_ICL_TBTPLL = 2, -+ /** -+ * @DPLL_ID_ICL_MGPLL1: ICL MG PLL 1 port 1 (C) -+ */ -+ DPLL_ID_ICL_MGPLL1 = 3, -+ /** -+ * @DPLL_ID_ICL_MGPLL2: ICL MG PLL 1 port 2 (D) -+ */ -+ DPLL_ID_ICL_MGPLL2 = 4, -+ /** -+ * @DPLL_ID_ICL_MGPLL3: ICL MG PLL 1 port 3 (E) -+ */ -+ DPLL_ID_ICL_MGPLL3 = 5, -+ /** -+ * @DPLL_ID_ICL_MGPLL4: ICL MG PLL 1 port 4 (F) -+ */ -+ DPLL_ID_ICL_MGPLL4 = 6, -+}; -+#define I915_NUM_PLLS 7 -+ -+struct intel_dpll_hw_state { -+ /* i9xx, pch plls */ -+ u32 dpll; -+ u32 dpll_md; -+ u32 fp0; -+ u32 fp1; -+ -+ /* hsw, bdw */ -+ u32 wrpll; -+ u32 spll; -+ -+ /* skl */ -+ /* -+ * DPLL_CTRL1 has 6 bits for each each this DPLL. We store those in -+ * lower part of ctrl1 and they get shifted into position when writing -+ * the register. This allows us to easily compare the state to share -+ * the DPLL. -+ */ -+ u32 ctrl1; -+ /* HDMI only, 0 when used for DP */ -+ u32 cfgcr1, cfgcr2; -+ -+ /* cnl */ -+ u32 cfgcr0; -+ /* CNL also uses cfgcr1 */ -+ -+ /* bxt */ -+ u32 ebb0, ebb4, pll0, pll1, pll2, pll3, pll6, pll8, pll9, pll10, pcsdw12; -+ -+ /* -+ * ICL uses the following, already defined: -+ * u32 cfgcr0, cfgcr1; -+ */ -+ u32 mg_refclkin_ctl; -+ u32 mg_clktop2_coreclkctl1; -+ u32 mg_clktop2_hsclkctl; -+ u32 mg_pll_div0; -+ u32 mg_pll_div1; -+ u32 mg_pll_lf; -+ u32 mg_pll_frac_lock; -+ u32 mg_pll_ssc; -+ u32 mg_pll_bias; -+ u32 mg_pll_tdc_coldst_bias; -+ u32 mg_pll_bias_mask; -+ u32 mg_pll_tdc_coldst_bias_mask; -+}; -+ -+/** -+ * struct intel_shared_dpll_state - hold the DPLL atomic state -+ * -+ * This structure holds an atomic state for the DPLL, that can represent -+ * either its current state (in struct &intel_shared_dpll) or a desired -+ * future state which would be applied by an atomic mode set (stored in -+ * a struct &intel_atomic_state). -+ * -+ * See also intel_get_shared_dpll() and intel_release_shared_dpll(). -+ */ -+struct intel_shared_dpll_state { -+ /** -+ * @crtc_mask: mask of CRTC using this DPLL, active or not -+ */ -+ unsigned crtc_mask; -+ -+ /** -+ * @hw_state: hardware configuration for the DPLL stored in -+ * struct &intel_dpll_hw_state. -+ */ -+ struct intel_dpll_hw_state hw_state; -+}; -+ -+/** -+ * struct intel_shared_dpll_funcs - platform specific hooks for managing DPLLs -+ */ -+struct intel_shared_dpll_funcs { -+ /** -+ * @prepare: -+ * -+ * Optional hook to perform operations prior to enabling the PLL. -+ * Called from intel_prepare_shared_dpll() function unless the PLL -+ * is already enabled. -+ */ -+ void (*prepare)(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll); -+ -+ /** -+ * @enable: -+ * -+ * Hook for enabling the pll, called from intel_enable_shared_dpll() -+ * if the pll is not already enabled. -+ */ -+ void (*enable)(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll); -+ -+ /** -+ * @disable: -+ * -+ * Hook for disabling the pll, called from intel_disable_shared_dpll() -+ * only when it is safe to disable the pll, i.e., there are no more -+ * tracked users for it. -+ */ -+ void (*disable)(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll); -+ -+ /** -+ * @get_hw_state: -+ * -+ * Hook for reading the values currently programmed to the DPLL -+ * registers. This is used for initial hw state readout and state -+ * verification after a mode set. -+ */ -+ bool (*get_hw_state)(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ struct intel_dpll_hw_state *hw_state); -+}; -+ -+/** -+ * struct dpll_info - display PLL platform specific info -+ */ -+struct dpll_info { -+ /** -+ * @name: DPLL name; used for logging -+ */ -+ const char *name; -+ -+ /** -+ * @funcs: platform specific hooks -+ */ -+ const struct intel_shared_dpll_funcs *funcs; -+ -+ /** -+ * @id: unique indentifier for this DPLL; should match the index in the -+ * dev_priv->shared_dplls array -+ */ -+ enum intel_dpll_id id; -+ -+#define INTEL_DPLL_ALWAYS_ON (1 << 0) -+ /** -+ * @flags: -+ * -+ * INTEL_DPLL_ALWAYS_ON -+ * Inform the state checker that the DPLL is kept enabled even if -+ * not in use by any CRTC. -+ */ -+ u32 flags; -+}; -+ -+/** -+ * struct intel_shared_dpll - display PLL with tracked state and users -+ */ -+struct intel_shared_dpll { -+ /** -+ * @state: -+ * -+ * Store the state for the pll, including the its hw state -+ * and CRTCs using it. -+ */ -+ struct intel_shared_dpll_state state; -+ -+ /** -+ * @active_mask: mask of active CRTCs (i.e. DPMS on) using this DPLL -+ */ -+ unsigned active_mask; -+ -+ /** -+ * @on: is the PLL actually active? Disabled during modeset -+ */ -+ bool on; -+ -+ /** -+ * @info: platform specific info -+ */ -+ const struct dpll_info *info; -+}; -+ -+#define SKL_DPLL0 0 -+#define SKL_DPLL1 1 -+#define SKL_DPLL2 2 -+#define SKL_DPLL3 3 -+ -+/* shared dpll functions */ -+struct intel_shared_dpll * -+intel_get_shared_dpll_by_id(struct drm_i915_private *dev_priv, -+ enum intel_dpll_id id); -+enum intel_dpll_id -+intel_get_shared_dpll_id(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll); -+void assert_shared_dpll(struct drm_i915_private *dev_priv, -+ struct intel_shared_dpll *pll, -+ bool state); -+#define assert_shared_dpll_enabled(d, p) assert_shared_dpll(d, p, true) -+#define assert_shared_dpll_disabled(d, p) assert_shared_dpll(d, p, false) -+struct intel_shared_dpll *intel_get_shared_dpll(struct intel_crtc_state *state, -+ struct intel_encoder *encoder); -+void intel_release_shared_dpll(struct intel_shared_dpll *dpll, -+ struct intel_crtc *crtc, -+ struct drm_atomic_state *state); -+void intel_prepare_shared_dpll(const struct intel_crtc_state *crtc_state); -+void intel_enable_shared_dpll(const struct intel_crtc_state *crtc_state); -+void intel_disable_shared_dpll(const struct intel_crtc_state *crtc_state); -+void intel_shared_dpll_swap_state(struct drm_atomic_state *state); -+void intel_shared_dpll_init(struct drm_device *dev); -+ -+void intel_dpll_dump_hw_state(struct drm_i915_private *dev_priv, -+ struct intel_dpll_hw_state *hw_state); -+int cnl_hdmi_pll_ref_clock(struct drm_i915_private *dev_priv); -+enum intel_dpll_id icl_tc_port_to_pll_id(enum tc_port tc_port); -+bool intel_dpll_is_combophy(enum intel_dpll_id id); -+ -+#endif /* _INTEL_DPLL_MGR_H_ */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_drv.h b/drivers/gpu/drm/i915_legacy/intel_drv.h -new file mode 100644 -index 000000000000..2db759e8e454 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_drv.h -@@ -0,0 +1,2045 @@ -+/* -+ * Copyright (c) 2006 Dave Airlie -+ * Copyright (c) 2007-2008 Intel Corporation -+ * Jesse Barnes -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+#ifndef __INTEL_DRV_H__ -+#define __INTEL_DRV_H__ -+ -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "i915_drv.h" -+ -+struct drm_printer; -+ -+/** -+ * __wait_for - magic wait macro -+ * -+ * Macro to help avoid open coding check/wait/timeout patterns. Note that it's -+ * important that we check the condition again after having timed out, since the -+ * timeout could be due to preemption or similar and we've never had a chance to -+ * check the condition before the timeout. -+ */ -+#define __wait_for(OP, COND, US, Wmin, Wmax) ({ \ -+ const ktime_t end__ = ktime_add_ns(ktime_get_raw(), 1000ll * (US)); \ -+ long wait__ = (Wmin); /* recommended min for usleep is 10 us */ \ -+ int ret__; \ -+ might_sleep(); \ -+ for (;;) { \ -+ const bool expired__ = ktime_after(ktime_get_raw(), end__); \ -+ OP; \ -+ /* Guarantee COND check prior to timeout */ \ -+ barrier(); \ -+ if (COND) { \ -+ ret__ = 0; \ -+ break; \ -+ } \ -+ if (expired__) { \ -+ ret__ = -ETIMEDOUT; \ -+ break; \ -+ } \ -+ usleep_range(wait__, wait__ * 2); \ -+ if (wait__ < (Wmax)) \ -+ wait__ <<= 1; \ -+ } \ -+ ret__; \ -+}) -+ -+#define _wait_for(COND, US, Wmin, Wmax) __wait_for(, (COND), (US), (Wmin), \ -+ (Wmax)) -+#define wait_for(COND, MS) _wait_for((COND), (MS) * 1000, 10, 1000) -+ -+/* If CONFIG_PREEMPT_COUNT is disabled, in_atomic() always reports false. */ -+#if defined(CONFIG_DRM_I915_DEBUG) && defined(CONFIG_PREEMPT_COUNT) -+# define _WAIT_FOR_ATOMIC_CHECK(ATOMIC) WARN_ON_ONCE((ATOMIC) && !in_atomic()) -+#else -+# define _WAIT_FOR_ATOMIC_CHECK(ATOMIC) do { } while (0) -+#endif -+ -+#define _wait_for_atomic(COND, US, ATOMIC) \ -+({ \ -+ int cpu, ret, timeout = (US) * 1000; \ -+ u64 base; \ -+ _WAIT_FOR_ATOMIC_CHECK(ATOMIC); \ -+ if (!(ATOMIC)) { \ -+ preempt_disable(); \ -+ cpu = smp_processor_id(); \ -+ } \ -+ base = local_clock(); \ -+ for (;;) { \ -+ u64 now = local_clock(); \ -+ if (!(ATOMIC)) \ -+ preempt_enable(); \ -+ /* Guarantee COND check prior to timeout */ \ -+ barrier(); \ -+ if (COND) { \ -+ ret = 0; \ -+ break; \ -+ } \ -+ if (now - base >= timeout) { \ -+ ret = -ETIMEDOUT; \ -+ break; \ -+ } \ -+ cpu_relax(); \ -+ if (!(ATOMIC)) { \ -+ preempt_disable(); \ -+ if (unlikely(cpu != smp_processor_id())) { \ -+ timeout -= now - base; \ -+ cpu = smp_processor_id(); \ -+ base = local_clock(); \ -+ } \ -+ } \ -+ } \ -+ ret; \ -+}) -+ -+#define wait_for_us(COND, US) \ -+({ \ -+ int ret__; \ -+ BUILD_BUG_ON(!__builtin_constant_p(US)); \ -+ if ((US) > 10) \ -+ ret__ = _wait_for((COND), (US), 10, 10); \ -+ else \ -+ ret__ = _wait_for_atomic((COND), (US), 0); \ -+ ret__; \ -+}) -+ -+#define wait_for_atomic_us(COND, US) \ -+({ \ -+ BUILD_BUG_ON(!__builtin_constant_p(US)); \ -+ BUILD_BUG_ON((US) > 50000); \ -+ _wait_for_atomic((COND), (US), 1); \ -+}) -+ -+#define wait_for_atomic(COND, MS) wait_for_atomic_us((COND), (MS) * 1000) -+ -+#define KHz(x) (1000 * (x)) -+#define MHz(x) KHz(1000 * (x)) -+ -+#define KBps(x) (1000 * (x)) -+#define MBps(x) KBps(1000 * (x)) -+#define GBps(x) ((u64)1000 * MBps((x))) -+ -+/* -+ * Display related stuff -+ */ -+ -+/* store information about an Ixxx DVO */ -+/* The i830->i865 use multiple DVOs with multiple i2cs */ -+/* the i915, i945 have a single sDVO i2c bus - which is different */ -+#define MAX_OUTPUTS 6 -+/* maximum connectors per crtcs in the mode set */ -+ -+#define INTEL_I2C_BUS_DVO 1 -+#define INTEL_I2C_BUS_SDVO 2 -+ -+/* these are outputs from the chip - integrated only -+ external chips are via DVO or SDVO output */ -+enum intel_output_type { -+ INTEL_OUTPUT_UNUSED = 0, -+ INTEL_OUTPUT_ANALOG = 1, -+ INTEL_OUTPUT_DVO = 2, -+ INTEL_OUTPUT_SDVO = 3, -+ INTEL_OUTPUT_LVDS = 4, -+ INTEL_OUTPUT_TVOUT = 5, -+ INTEL_OUTPUT_HDMI = 6, -+ INTEL_OUTPUT_DP = 7, -+ INTEL_OUTPUT_EDP = 8, -+ INTEL_OUTPUT_DSI = 9, -+ INTEL_OUTPUT_DDI = 10, -+ INTEL_OUTPUT_DP_MST = 11, -+}; -+ -+#define INTEL_DVO_CHIP_NONE 0 -+#define INTEL_DVO_CHIP_LVDS 1 -+#define INTEL_DVO_CHIP_TMDS 2 -+#define INTEL_DVO_CHIP_TVOUT 4 -+ -+#define INTEL_DSI_VIDEO_MODE 0 -+#define INTEL_DSI_COMMAND_MODE 1 -+ -+struct intel_framebuffer { -+ struct drm_framebuffer base; -+ struct intel_rotation_info rot_info; -+ -+ /* for each plane in the normal GTT view */ -+ struct { -+ unsigned int x, y; -+ } normal[2]; -+ /* for each plane in the rotated GTT view */ -+ struct { -+ unsigned int x, y; -+ unsigned int pitch; /* pixels */ -+ } rotated[2]; -+}; -+ -+struct intel_fbdev { -+ struct drm_fb_helper helper; -+ struct intel_framebuffer *fb; -+ struct i915_vma *vma; -+ unsigned long vma_flags; -+ async_cookie_t cookie; -+ int preferred_bpp; -+ -+ /* Whether or not fbdev hpd processing is temporarily suspended */ -+ bool hpd_suspended : 1; -+ /* Set when a hotplug was received while HPD processing was -+ * suspended -+ */ -+ bool hpd_waiting : 1; -+ -+ /* Protects hpd_suspended */ -+ struct mutex hpd_lock; -+}; -+ -+struct intel_encoder { -+ struct drm_encoder base; -+ -+ enum intel_output_type type; -+ enum port port; -+ unsigned int cloneable; -+ bool (*hotplug)(struct intel_encoder *encoder, -+ struct intel_connector *connector); -+ enum intel_output_type (*compute_output_type)(struct intel_encoder *, -+ struct intel_crtc_state *, -+ struct drm_connector_state *); -+ int (*compute_config)(struct intel_encoder *, -+ struct intel_crtc_state *, -+ struct drm_connector_state *); -+ void (*pre_pll_enable)(struct intel_encoder *, -+ const struct intel_crtc_state *, -+ const struct drm_connector_state *); -+ void (*pre_enable)(struct intel_encoder *, -+ const struct intel_crtc_state *, -+ const struct drm_connector_state *); -+ void (*enable)(struct intel_encoder *, -+ const struct intel_crtc_state *, -+ const struct drm_connector_state *); -+ void (*disable)(struct intel_encoder *, -+ const struct intel_crtc_state *, -+ const struct drm_connector_state *); -+ void (*post_disable)(struct intel_encoder *, -+ const struct intel_crtc_state *, -+ const struct drm_connector_state *); -+ void (*post_pll_disable)(struct intel_encoder *, -+ const struct intel_crtc_state *, -+ const struct drm_connector_state *); -+ void (*update_pipe)(struct intel_encoder *, -+ const struct intel_crtc_state *, -+ const struct drm_connector_state *); -+ /* Read out the current hw state of this connector, returning true if -+ * the encoder is active. If the encoder is enabled it also set the pipe -+ * it is connected to in the pipe parameter. */ -+ bool (*get_hw_state)(struct intel_encoder *, enum pipe *pipe); -+ /* Reconstructs the equivalent mode flags for the current hardware -+ * state. This must be called _after_ display->get_pipe_config has -+ * pre-filled the pipe config. Note that intel_encoder->base.crtc must -+ * be set correctly before calling this function. */ -+ void (*get_config)(struct intel_encoder *, -+ struct intel_crtc_state *pipe_config); -+ /* -+ * Acquires the power domains needed for an active encoder during -+ * hardware state readout. -+ */ -+ void (*get_power_domains)(struct intel_encoder *encoder, -+ struct intel_crtc_state *crtc_state); -+ /* -+ * Called during system suspend after all pending requests for the -+ * encoder are flushed (for example for DP AUX transactions) and -+ * device interrupts are disabled. -+ */ -+ void (*suspend)(struct intel_encoder *); -+ int crtc_mask; -+ enum hpd_pin hpd_pin; -+ enum intel_display_power_domain power_domain; -+ /* for communication with audio component; protected by av_mutex */ -+ const struct drm_connector *audio_connector; -+}; -+ -+struct intel_panel { -+ struct drm_display_mode *fixed_mode; -+ struct drm_display_mode *downclock_mode; -+ -+ /* backlight */ -+ struct { -+ bool present; -+ u32 level; -+ u32 min; -+ u32 max; -+ bool enabled; -+ bool combination_mode; /* gen 2/4 only */ -+ bool active_low_pwm; -+ bool alternate_pwm_increment; /* lpt+ */ -+ -+ /* PWM chip */ -+ bool util_pin_active_low; /* bxt+ */ -+ u8 controller; /* bxt+ only */ -+ struct pwm_device *pwm; -+ -+ struct backlight_device *device; -+ -+ /* Connector and platform specific backlight functions */ -+ int (*setup)(struct intel_connector *connector, enum pipe pipe); -+ u32 (*get)(struct intel_connector *connector); -+ void (*set)(const struct drm_connector_state *conn_state, u32 level); -+ void (*disable)(const struct drm_connector_state *conn_state); -+ void (*enable)(const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state); -+ u32 (*hz_to_pwm)(struct intel_connector *connector, u32 hz); -+ void (*power)(struct intel_connector *, bool enable); -+ } backlight; -+}; -+ -+struct intel_digital_port; -+ -+enum check_link_response { -+ HDCP_LINK_PROTECTED = 0, -+ HDCP_TOPOLOGY_CHANGE, -+ HDCP_LINK_INTEGRITY_FAILURE, -+ HDCP_REAUTH_REQUEST -+}; -+ -+/* -+ * This structure serves as a translation layer between the generic HDCP code -+ * and the bus-specific code. What that means is that HDCP over HDMI differs -+ * from HDCP over DP, so to account for these differences, we need to -+ * communicate with the receiver through this shim. -+ * -+ * For completeness, the 2 buses differ in the following ways: -+ * - DP AUX vs. DDC -+ * HDCP registers on the receiver are set via DP AUX for DP, and -+ * they are set via DDC for HDMI. -+ * - Receiver register offsets -+ * The offsets of the registers are different for DP vs. HDMI -+ * - Receiver register masks/offsets -+ * For instance, the ready bit for the KSV fifo is in a different -+ * place on DP vs HDMI -+ * - Receiver register names -+ * Seriously. In the DP spec, the 16-bit register containing -+ * downstream information is called BINFO, on HDMI it's called -+ * BSTATUS. To confuse matters further, DP has a BSTATUS register -+ * with a completely different definition. -+ * - KSV FIFO -+ * On HDMI, the ksv fifo is read all at once, whereas on DP it must -+ * be read 3 keys at a time -+ * - Aksv output -+ * Since Aksv is hidden in hardware, there's different procedures -+ * to send it over DP AUX vs DDC -+ */ -+struct intel_hdcp_shim { -+ /* Outputs the transmitter's An and Aksv values to the receiver. */ -+ int (*write_an_aksv)(struct intel_digital_port *intel_dig_port, u8 *an); -+ -+ /* Reads the receiver's key selection vector */ -+ int (*read_bksv)(struct intel_digital_port *intel_dig_port, u8 *bksv); -+ -+ /* -+ * Reads BINFO from DP receivers and BSTATUS from HDMI receivers. The -+ * definitions are the same in the respective specs, but the names are -+ * different. Call it BSTATUS since that's the name the HDMI spec -+ * uses and it was there first. -+ */ -+ int (*read_bstatus)(struct intel_digital_port *intel_dig_port, -+ u8 *bstatus); -+ -+ /* Determines whether a repeater is present downstream */ -+ int (*repeater_present)(struct intel_digital_port *intel_dig_port, -+ bool *repeater_present); -+ -+ /* Reads the receiver's Ri' value */ -+ int (*read_ri_prime)(struct intel_digital_port *intel_dig_port, u8 *ri); -+ -+ /* Determines if the receiver's KSV FIFO is ready for consumption */ -+ int (*read_ksv_ready)(struct intel_digital_port *intel_dig_port, -+ bool *ksv_ready); -+ -+ /* Reads the ksv fifo for num_downstream devices */ -+ int (*read_ksv_fifo)(struct intel_digital_port *intel_dig_port, -+ int num_downstream, u8 *ksv_fifo); -+ -+ /* Reads a 32-bit part of V' from the receiver */ -+ int (*read_v_prime_part)(struct intel_digital_port *intel_dig_port, -+ int i, u32 *part); -+ -+ /* Enables HDCP signalling on the port */ -+ int (*toggle_signalling)(struct intel_digital_port *intel_dig_port, -+ bool enable); -+ -+ /* Ensures the link is still protected */ -+ bool (*check_link)(struct intel_digital_port *intel_dig_port); -+ -+ /* Detects panel's hdcp capability. This is optional for HDMI. */ -+ int (*hdcp_capable)(struct intel_digital_port *intel_dig_port, -+ bool *hdcp_capable); -+ -+ /* HDCP adaptation(DP/HDMI) required on the port */ -+ enum hdcp_wired_protocol protocol; -+ -+ /* Detects whether sink is HDCP2.2 capable */ -+ int (*hdcp_2_2_capable)(struct intel_digital_port *intel_dig_port, -+ bool *capable); -+ -+ /* Write HDCP2.2 messages */ -+ int (*write_2_2_msg)(struct intel_digital_port *intel_dig_port, -+ void *buf, size_t size); -+ -+ /* Read HDCP2.2 messages */ -+ int (*read_2_2_msg)(struct intel_digital_port *intel_dig_port, -+ u8 msg_id, void *buf, size_t size); -+ -+ /* -+ * Implementation of DP HDCP2.2 Errata for the communication of stream -+ * type to Receivers. In DP HDCP2.2 Stream type is one of the input to -+ * the HDCP2.2 Cipher for En/De-Cryption. Not applicable for HDMI. -+ */ -+ int (*config_stream_type)(struct intel_digital_port *intel_dig_port, -+ bool is_repeater, u8 type); -+ -+ /* HDCP2.2 Link Integrity Check */ -+ int (*check_2_2_link)(struct intel_digital_port *intel_dig_port); -+}; -+ -+struct intel_hdcp { -+ const struct intel_hdcp_shim *shim; -+ /* Mutex for hdcp state of the connector */ -+ struct mutex mutex; -+ u64 value; -+ struct delayed_work check_work; -+ struct work_struct prop_work; -+ -+ /* HDCP1.4 Encryption status */ -+ bool hdcp_encrypted; -+ -+ /* HDCP2.2 related definitions */ -+ /* Flag indicates whether this connector supports HDCP2.2 or not. */ -+ bool hdcp2_supported; -+ -+ /* HDCP2.2 Encryption status */ -+ bool hdcp2_encrypted; -+ -+ /* -+ * Content Stream Type defined by content owner. TYPE0(0x0) content can -+ * flow in the link protected by HDCP2.2 or HDCP1.4, where as TYPE1(0x1) -+ * content can flow only through a link protected by HDCP2.2. -+ */ -+ u8 content_type; -+ struct hdcp_port_data port_data; -+ -+ bool is_paired; -+ bool is_repeater; -+ -+ /* -+ * Count of ReceiverID_List received. Initialized to 0 at AKE_INIT. -+ * Incremented after processing the RepeaterAuth_Send_ReceiverID_List. -+ * When it rolls over re-auth has to be triggered. -+ */ -+ u32 seq_num_v; -+ -+ /* -+ * Count of RepeaterAuth_Stream_Manage msg propagated. -+ * Initialized to 0 on AKE_INIT. Incremented after every successful -+ * transmission of RepeaterAuth_Stream_Manage message. When it rolls -+ * over re-Auth has to be triggered. -+ */ -+ u32 seq_num_m; -+ -+ /* -+ * Work queue to signal the CP_IRQ. Used for the waiters to read the -+ * available information from HDCP DP sink. -+ */ -+ wait_queue_head_t cp_irq_queue; -+ atomic_t cp_irq_count; -+ int cp_irq_count_cached; -+}; -+ -+struct intel_connector { -+ struct drm_connector base; -+ /* -+ * The fixed encoder this connector is connected to. -+ */ -+ struct intel_encoder *encoder; -+ -+ /* ACPI device id for ACPI and driver cooperation */ -+ u32 acpi_device_id; -+ -+ /* Reads out the current hw, returning true if the connector is enabled -+ * and active (i.e. dpms ON state). */ -+ bool (*get_hw_state)(struct intel_connector *); -+ -+ /* Panel info for eDP and LVDS */ -+ struct intel_panel panel; -+ -+ /* Cached EDID for eDP and LVDS. May hold ERR_PTR for invalid EDID. */ -+ struct edid *edid; -+ struct edid *detect_edid; -+ -+ /* since POLL and HPD connectors may use the same HPD line keep the native -+ state of connector->polled in case hotplug storm detection changes it */ -+ u8 polled; -+ -+ void *port; /* store this opaque as its illegal to dereference it */ -+ -+ struct intel_dp *mst_port; -+ -+ /* Work struct to schedule a uevent on link train failure */ -+ struct work_struct modeset_retry_work; -+ -+ struct intel_hdcp hdcp; -+}; -+ -+struct intel_digital_connector_state { -+ struct drm_connector_state base; -+ -+ enum hdmi_force_audio force_audio; -+ int broadcast_rgb; -+}; -+ -+#define to_intel_digital_connector_state(x) container_of(x, struct intel_digital_connector_state, base) -+ -+struct dpll { -+ /* given values */ -+ int n; -+ int m1, m2; -+ int p1, p2; -+ /* derived values */ -+ int dot; -+ int vco; -+ int m; -+ int p; -+}; -+ -+struct intel_atomic_state { -+ struct drm_atomic_state base; -+ -+ struct { -+ /* -+ * Logical state of cdclk (used for all scaling, watermark, -+ * etc. calculations and checks). This is computed as if all -+ * enabled crtcs were active. -+ */ -+ struct intel_cdclk_state logical; -+ -+ /* -+ * Actual state of cdclk, can be different from the logical -+ * state only when all crtc's are DPMS off. -+ */ -+ struct intel_cdclk_state actual; -+ -+ int force_min_cdclk; -+ bool force_min_cdclk_changed; -+ /* pipe to which cd2x update is synchronized */ -+ enum pipe pipe; -+ } cdclk; -+ -+ bool dpll_set, modeset; -+ -+ /* -+ * Does this transaction change the pipes that are active? This mask -+ * tracks which CRTC's have changed their active state at the end of -+ * the transaction (not counting the temporary disable during modesets). -+ * This mask should only be non-zero when intel_state->modeset is true, -+ * but the converse is not necessarily true; simply changing a mode may -+ * not flip the final active status of any CRTC's -+ */ -+ unsigned int active_pipe_changes; -+ -+ unsigned int active_crtcs; -+ /* minimum acceptable cdclk for each pipe */ -+ int min_cdclk[I915_MAX_PIPES]; -+ /* minimum acceptable voltage level for each pipe */ -+ u8 min_voltage_level[I915_MAX_PIPES]; -+ -+ struct intel_shared_dpll_state shared_dpll[I915_NUM_PLLS]; -+ -+ /* -+ * Current watermarks can't be trusted during hardware readout, so -+ * don't bother calculating intermediate watermarks. -+ */ -+ bool skip_intermediate_wm; -+ -+ bool rps_interactive; -+ -+ /* Gen9+ only */ -+ struct skl_ddb_values wm_results; -+ -+ struct i915_sw_fence commit_ready; -+ -+ struct llist_node freed; -+}; -+ -+struct intel_plane_state { -+ struct drm_plane_state base; -+ struct i915_ggtt_view view; -+ struct i915_vma *vma; -+ unsigned long flags; -+#define PLANE_HAS_FENCE BIT(0) -+ -+ struct { -+ u32 offset; -+ /* -+ * Plane stride in: -+ * bytes for 0/180 degree rotation -+ * pixels for 90/270 degree rotation -+ */ -+ u32 stride; -+ int x, y; -+ } color_plane[2]; -+ -+ /* plane control register */ -+ u32 ctl; -+ -+ /* plane color control register */ -+ u32 color_ctl; -+ -+ /* -+ * scaler_id -+ * = -1 : not using a scaler -+ * >= 0 : using a scalers -+ * -+ * plane requiring a scaler: -+ * - During check_plane, its bit is set in -+ * crtc_state->scaler_state.scaler_users by calling helper function -+ * update_scaler_plane. -+ * - scaler_id indicates the scaler it got assigned. -+ * -+ * plane doesn't require a scaler: -+ * - this can happen when scaling is no more required or plane simply -+ * got disabled. -+ * - During check_plane, corresponding bit is reset in -+ * crtc_state->scaler_state.scaler_users by calling helper function -+ * update_scaler_plane. -+ */ -+ int scaler_id; -+ -+ /* -+ * linked_plane: -+ * -+ * ICL planar formats require 2 planes that are updated as pairs. -+ * This member is used to make sure the other plane is also updated -+ * when required, and for update_slave() to find the correct -+ * plane_state to pass as argument. -+ */ -+ struct intel_plane *linked_plane; -+ -+ /* -+ * slave: -+ * If set don't update use the linked plane's state for updating -+ * this plane during atomic commit with the update_slave() callback. -+ * -+ * It's also used by the watermark code to ignore wm calculations on -+ * this plane. They're calculated by the linked plane's wm code. -+ */ -+ u32 slave; -+ -+ struct drm_intel_sprite_colorkey ckey; -+}; -+ -+struct intel_initial_plane_config { -+ struct intel_framebuffer *fb; -+ unsigned int tiling; -+ int size; -+ u32 base; -+ u8 rotation; -+}; -+ -+#define SKL_MIN_SRC_W 8 -+#define SKL_MAX_SRC_W 4096 -+#define SKL_MIN_SRC_H 8 -+#define SKL_MAX_SRC_H 4096 -+#define SKL_MIN_DST_W 8 -+#define SKL_MAX_DST_W 4096 -+#define SKL_MIN_DST_H 8 -+#define SKL_MAX_DST_H 4096 -+#define ICL_MAX_SRC_W 5120 -+#define ICL_MAX_SRC_H 4096 -+#define ICL_MAX_DST_W 5120 -+#define ICL_MAX_DST_H 4096 -+#define SKL_MIN_YUV_420_SRC_W 16 -+#define SKL_MIN_YUV_420_SRC_H 16 -+ -+struct intel_scaler { -+ int in_use; -+ u32 mode; -+}; -+ -+struct intel_crtc_scaler_state { -+#define SKL_NUM_SCALERS 2 -+ struct intel_scaler scalers[SKL_NUM_SCALERS]; -+ -+ /* -+ * scaler_users: keeps track of users requesting scalers on this crtc. -+ * -+ * If a bit is set, a user is using a scaler. -+ * Here user can be a plane or crtc as defined below: -+ * bits 0-30 - plane (bit position is index from drm_plane_index) -+ * bit 31 - crtc -+ * -+ * Instead of creating a new index to cover planes and crtc, using -+ * existing drm_plane_index for planes which is well less than 31 -+ * planes and bit 31 for crtc. This should be fine to cover all -+ * our platforms. -+ * -+ * intel_atomic_setup_scalers will setup available scalers to users -+ * requesting scalers. It will gracefully fail if request exceeds -+ * avilability. -+ */ -+#define SKL_CRTC_INDEX 31 -+ unsigned scaler_users; -+ -+ /* scaler used by crtc for panel fitting purpose */ -+ int scaler_id; -+}; -+ -+/* drm_mode->private_flags */ -+#define I915_MODE_FLAG_INHERITED (1<<0) -+/* Flag to get scanline using frame time stamps */ -+#define I915_MODE_FLAG_GET_SCANLINE_FROM_TIMESTAMP (1<<1) -+/* Flag to use the scanline counter instead of the pixel counter */ -+#define I915_MODE_FLAG_USE_SCANLINE_COUNTER (1<<2) -+ -+struct intel_pipe_wm { -+ struct intel_wm_level wm[5]; -+ u32 linetime; -+ bool fbc_wm_enabled; -+ bool pipe_enabled; -+ bool sprites_enabled; -+ bool sprites_scaled; -+}; -+ -+struct skl_plane_wm { -+ struct skl_wm_level wm[8]; -+ struct skl_wm_level uv_wm[8]; -+ struct skl_wm_level trans_wm; -+ bool is_planar; -+}; -+ -+struct skl_pipe_wm { -+ struct skl_plane_wm planes[I915_MAX_PLANES]; -+ u32 linetime; -+}; -+ -+enum vlv_wm_level { -+ VLV_WM_LEVEL_PM2, -+ VLV_WM_LEVEL_PM5, -+ VLV_WM_LEVEL_DDR_DVFS, -+ NUM_VLV_WM_LEVELS, -+}; -+ -+struct vlv_wm_state { -+ struct g4x_pipe_wm wm[NUM_VLV_WM_LEVELS]; -+ struct g4x_sr_wm sr[NUM_VLV_WM_LEVELS]; -+ u8 num_levels; -+ bool cxsr; -+}; -+ -+struct vlv_fifo_state { -+ u16 plane[I915_MAX_PLANES]; -+}; -+ -+enum g4x_wm_level { -+ G4X_WM_LEVEL_NORMAL, -+ G4X_WM_LEVEL_SR, -+ G4X_WM_LEVEL_HPLL, -+ NUM_G4X_WM_LEVELS, -+}; -+ -+struct g4x_wm_state { -+ struct g4x_pipe_wm wm; -+ struct g4x_sr_wm sr; -+ struct g4x_sr_wm hpll; -+ bool cxsr; -+ bool hpll_en; -+ bool fbc_en; -+}; -+ -+struct intel_crtc_wm_state { -+ union { -+ struct { -+ /* -+ * Intermediate watermarks; these can be -+ * programmed immediately since they satisfy -+ * both the current configuration we're -+ * switching away from and the new -+ * configuration we're switching to. -+ */ -+ struct intel_pipe_wm intermediate; -+ -+ /* -+ * Optimal watermarks, programmed post-vblank -+ * when this state is committed. -+ */ -+ struct intel_pipe_wm optimal; -+ } ilk; -+ -+ struct { -+ /* gen9+ only needs 1-step wm programming */ -+ struct skl_pipe_wm optimal; -+ struct skl_ddb_entry ddb; -+ struct skl_ddb_entry plane_ddb_y[I915_MAX_PLANES]; -+ struct skl_ddb_entry plane_ddb_uv[I915_MAX_PLANES]; -+ } skl; -+ -+ struct { -+ /* "raw" watermarks (not inverted) */ -+ struct g4x_pipe_wm raw[NUM_VLV_WM_LEVELS]; -+ /* intermediate watermarks (inverted) */ -+ struct vlv_wm_state intermediate; -+ /* optimal watermarks (inverted) */ -+ struct vlv_wm_state optimal; -+ /* display FIFO split */ -+ struct vlv_fifo_state fifo_state; -+ } vlv; -+ -+ struct { -+ /* "raw" watermarks */ -+ struct g4x_pipe_wm raw[NUM_G4X_WM_LEVELS]; -+ /* intermediate watermarks */ -+ struct g4x_wm_state intermediate; -+ /* optimal watermarks */ -+ struct g4x_wm_state optimal; -+ } g4x; -+ }; -+ -+ /* -+ * Platforms with two-step watermark programming will need to -+ * update watermark programming post-vblank to switch from the -+ * safe intermediate watermarks to the optimal final -+ * watermarks. -+ */ -+ bool need_postvbl_update; -+}; -+ -+enum intel_output_format { -+ INTEL_OUTPUT_FORMAT_INVALID, -+ INTEL_OUTPUT_FORMAT_RGB, -+ INTEL_OUTPUT_FORMAT_YCBCR420, -+ INTEL_OUTPUT_FORMAT_YCBCR444, -+}; -+ -+struct intel_crtc_state { -+ struct drm_crtc_state base; -+ -+ /** -+ * quirks - bitfield with hw state readout quirks -+ * -+ * For various reasons the hw state readout code might not be able to -+ * completely faithfully read out the current state. These cases are -+ * tracked with quirk flags so that fastboot and state checker can act -+ * accordingly. -+ */ -+#define PIPE_CONFIG_QUIRK_MODE_SYNC_FLAGS (1<<0) /* unreliable sync mode.flags */ -+ unsigned long quirks; -+ -+ unsigned fb_bits; /* framebuffers to flip */ -+ bool update_pipe; /* can a fast modeset be performed? */ -+ bool disable_cxsr; -+ bool update_wm_pre, update_wm_post; /* watermarks are updated */ -+ bool fb_changed; /* fb on any of the planes is changed */ -+ bool fifo_changed; /* FIFO split is changed */ -+ -+ /* Pipe source size (ie. panel fitter input size) -+ * All planes will be positioned inside this space, -+ * and get clipped at the edges. */ -+ int pipe_src_w, pipe_src_h; -+ -+ /* -+ * Pipe pixel rate, adjusted for -+ * panel fitter/pipe scaler downscaling. -+ */ -+ unsigned int pixel_rate; -+ -+ /* Whether to set up the PCH/FDI. Note that we never allow sharing -+ * between pch encoders and cpu encoders. */ -+ bool has_pch_encoder; -+ -+ /* Are we sending infoframes on the attached port */ -+ bool has_infoframe; -+ -+ /* CPU Transcoder for the pipe. Currently this can only differ from the -+ * pipe on Haswell and later (where we have a special eDP transcoder) -+ * and Broxton (where we have special DSI transcoders). */ -+ enum transcoder cpu_transcoder; -+ -+ /* -+ * Use reduced/limited/broadcast rbg range, compressing from the full -+ * range fed into the crtcs. -+ */ -+ bool limited_color_range; -+ -+ /* Bitmask of encoder types (enum intel_output_type) -+ * driven by the pipe. -+ */ -+ unsigned int output_types; -+ -+ /* Whether we should send NULL infoframes. Required for audio. */ -+ bool has_hdmi_sink; -+ -+ /* Audio enabled on this pipe. Only valid if either has_hdmi_sink or -+ * has_dp_encoder is set. */ -+ bool has_audio; -+ -+ /* -+ * Enable dithering, used when the selected pipe bpp doesn't match the -+ * plane bpp. -+ */ -+ bool dither; -+ -+ /* -+ * Dither gets enabled for 18bpp which causes CRC mismatch errors for -+ * compliance video pattern tests. -+ * Disable dither only if it is a compliance test request for -+ * 18bpp. -+ */ -+ bool dither_force_disable; -+ -+ /* Controls for the clock computation, to override various stages. */ -+ bool clock_set; -+ -+ /* SDVO TV has a bunch of special case. To make multifunction encoders -+ * work correctly, we need to track this at runtime.*/ -+ bool sdvo_tv_clock; -+ -+ /* -+ * crtc bandwidth limit, don't increase pipe bpp or clock if not really -+ * required. This is set in the 2nd loop of calling encoder's -+ * ->compute_config if the first pick doesn't work out. -+ */ -+ bool bw_constrained; -+ -+ /* Settings for the intel dpll used on pretty much everything but -+ * haswell. */ -+ struct dpll dpll; -+ -+ /* Selected dpll when shared or NULL. */ -+ struct intel_shared_dpll *shared_dpll; -+ -+ /* Actual register state of the dpll, for shared dpll cross-checking. */ -+ struct intel_dpll_hw_state dpll_hw_state; -+ -+ /* DSI PLL registers */ -+ struct { -+ u32 ctrl, div; -+ } dsi_pll; -+ -+ int pipe_bpp; -+ struct intel_link_m_n dp_m_n; -+ -+ /* m2_n2 for eDP downclock */ -+ struct intel_link_m_n dp_m2_n2; -+ bool has_drrs; -+ -+ bool has_psr; -+ bool has_psr2; -+ -+ /* -+ * Frequence the dpll for the port should run at. Differs from the -+ * adjusted dotclock e.g. for DP or 12bpc hdmi mode. This is also -+ * already multiplied by pixel_multiplier. -+ */ -+ int port_clock; -+ -+ /* Used by SDVO (and if we ever fix it, HDMI). */ -+ unsigned pixel_multiplier; -+ -+ u8 lane_count; -+ -+ /* -+ * Used by platforms having DP/HDMI PHY with programmable lane -+ * latency optimization. -+ */ -+ u8 lane_lat_optim_mask; -+ -+ /* minimum acceptable voltage level */ -+ u8 min_voltage_level; -+ -+ /* Panel fitter controls for gen2-gen4 + VLV */ -+ struct { -+ u32 control; -+ u32 pgm_ratios; -+ u32 lvds_border_bits; -+ } gmch_pfit; -+ -+ /* Panel fitter placement and size for Ironlake+ */ -+ struct { -+ u32 pos; -+ u32 size; -+ bool enabled; -+ bool force_thru; -+ } pch_pfit; -+ -+ /* FDI configuration, only valid if has_pch_encoder is set. */ -+ int fdi_lanes; -+ struct intel_link_m_n fdi_m_n; -+ -+ bool ips_enabled; -+ -+ bool crc_enabled; -+ -+ bool enable_fbc; -+ -+ bool double_wide; -+ -+ int pbn; -+ -+ struct intel_crtc_scaler_state scaler_state; -+ -+ /* w/a for waiting 2 vblanks during crtc enable */ -+ enum pipe hsw_workaround_pipe; -+ -+ /* IVB sprite scaling w/a (WaCxSRDisabledForSpriteScaling:ivb) */ -+ bool disable_lp_wm; -+ -+ struct intel_crtc_wm_state wm; -+ -+ /* Gamma mode programmed on the pipe */ -+ u32 gamma_mode; -+ -+ union { -+ /* CSC mode programmed on the pipe */ -+ u32 csc_mode; -+ -+ /* CHV CGM mode */ -+ u32 cgm_mode; -+ }; -+ -+ /* bitmask of visible planes (enum plane_id) */ -+ u8 active_planes; -+ u8 nv12_planes; -+ u8 c8_planes; -+ -+ /* bitmask of planes that will be updated during the commit */ -+ u8 update_planes; -+ -+ struct { -+ u32 enable; -+ u32 gcp; -+ union hdmi_infoframe avi; -+ union hdmi_infoframe spd; -+ union hdmi_infoframe hdmi; -+ } infoframes; -+ -+ /* HDMI scrambling status */ -+ bool hdmi_scrambling; -+ -+ /* HDMI High TMDS char rate ratio */ -+ bool hdmi_high_tmds_clock_ratio; -+ -+ /* Output format RGB/YCBCR etc */ -+ enum intel_output_format output_format; -+ -+ /* Output down scaling is done in LSPCON device */ -+ bool lspcon_downsampling; -+ -+ /* enable pipe gamma? */ -+ bool gamma_enable; -+ -+ /* enable pipe csc? */ -+ bool csc_enable; -+ -+ /* Display Stream compression state */ -+ struct { -+ bool compression_enable; -+ bool dsc_split; -+ u16 compressed_bpp; -+ u8 slice_count; -+ } dsc_params; -+ struct drm_dsc_config dp_dsc_cfg; -+ -+ /* Forward Error correction State */ -+ bool fec_enable; -+}; -+ -+struct intel_crtc { -+ struct drm_crtc base; -+ enum pipe pipe; -+ /* -+ * Whether the crtc and the connected output pipeline is active. Implies -+ * that crtc->enabled is set, i.e. the current mode configuration has -+ * some outputs connected to this crtc. -+ */ -+ bool active; -+ u8 plane_ids_mask; -+ unsigned long long enabled_power_domains; -+ struct intel_overlay *overlay; -+ -+ struct intel_crtc_state *config; -+ -+ /* Access to these should be protected by dev_priv->irq_lock. */ -+ bool cpu_fifo_underrun_disabled; -+ bool pch_fifo_underrun_disabled; -+ -+ /* per-pipe watermark state */ -+ struct { -+ /* watermarks currently being used */ -+ union { -+ struct intel_pipe_wm ilk; -+ struct vlv_wm_state vlv; -+ struct g4x_wm_state g4x; -+ } active; -+ } wm; -+ -+ int scanline_offset; -+ -+ struct { -+ unsigned start_vbl_count; -+ ktime_t start_vbl_time; -+ int min_vbl, max_vbl; -+ int scanline_start; -+ } debug; -+ -+ /* scalers available on this crtc */ -+ int num_scalers; -+}; -+ -+struct intel_plane { -+ struct drm_plane base; -+ enum i9xx_plane_id i9xx_plane; -+ enum plane_id id; -+ enum pipe pipe; -+ bool has_fbc; -+ bool has_ccs; -+ u32 frontbuffer_bit; -+ -+ struct { -+ u32 base, cntl, size; -+ } cursor; -+ -+ /* -+ * NOTE: Do not place new plane state fields here (e.g., when adding -+ * new plane properties). New runtime state should now be placed in -+ * the intel_plane_state structure and accessed via plane_state. -+ */ -+ -+ unsigned int (*max_stride)(struct intel_plane *plane, -+ u32 pixel_format, u64 modifier, -+ unsigned int rotation); -+ void (*update_plane)(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state); -+ void (*update_slave)(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state); -+ void (*disable_plane)(struct intel_plane *plane, -+ const struct intel_crtc_state *crtc_state); -+ bool (*get_hw_state)(struct intel_plane *plane, enum pipe *pipe); -+ int (*check_plane)(struct intel_crtc_state *crtc_state, -+ struct intel_plane_state *plane_state); -+}; -+ -+struct intel_watermark_params { -+ u16 fifo_size; -+ u16 max_wm; -+ u8 default_wm; -+ u8 guard_size; -+ u8 cacheline_size; -+}; -+ -+struct cxsr_latency { -+ bool is_desktop : 1; -+ bool is_ddr3 : 1; -+ u16 fsb_freq; -+ u16 mem_freq; -+ u16 display_sr; -+ u16 display_hpll_disable; -+ u16 cursor_sr; -+ u16 cursor_hpll_disable; -+}; -+ -+#define to_intel_atomic_state(x) container_of(x, struct intel_atomic_state, base) -+#define to_intel_crtc(x) container_of(x, struct intel_crtc, base) -+#define to_intel_crtc_state(x) container_of(x, struct intel_crtc_state, base) -+#define to_intel_connector(x) container_of(x, struct intel_connector, base) -+#define to_intel_encoder(x) container_of(x, struct intel_encoder, base) -+#define to_intel_framebuffer(x) container_of(x, struct intel_framebuffer, base) -+#define to_intel_plane(x) container_of(x, struct intel_plane, base) -+#define to_intel_plane_state(x) container_of(x, struct intel_plane_state, base) -+#define intel_fb_obj(x) ((x) ? to_intel_bo((x)->obj[0]) : NULL) -+ -+struct intel_hdmi { -+ i915_reg_t hdmi_reg; -+ int ddc_bus; -+ struct { -+ enum drm_dp_dual_mode_type type; -+ int max_tmds_clock; -+ } dp_dual_mode; -+ bool has_hdmi_sink; -+ bool has_audio; -+ struct intel_connector *attached_connector; -+ struct cec_notifier *cec_notifier; -+}; -+ -+struct intel_dp_mst_encoder; -+#define DP_MAX_DOWNSTREAM_PORTS 0x10 -+ -+/* -+ * enum link_m_n_set: -+ * When platform provides two set of M_N registers for dp, we can -+ * program them and switch between them incase of DRRS. -+ * But When only one such register is provided, we have to program the -+ * required divider value on that registers itself based on the DRRS state. -+ * -+ * M1_N1 : Program dp_m_n on M1_N1 registers -+ * dp_m2_n2 on M2_N2 registers (If supported) -+ * -+ * M2_N2 : Program dp_m2_n2 on M1_N1 registers -+ * M2_N2 registers are not supported -+ */ -+ -+enum link_m_n_set { -+ /* Sets the m1_n1 and m2_n2 */ -+ M1_N1 = 0, -+ M2_N2 -+}; -+ -+struct intel_dp_compliance_data { -+ unsigned long edid; -+ u8 video_pattern; -+ u16 hdisplay, vdisplay; -+ u8 bpc; -+}; -+ -+struct intel_dp_compliance { -+ unsigned long test_type; -+ struct intel_dp_compliance_data test_data; -+ bool test_active; -+ int test_link_rate; -+ u8 test_lane_count; -+}; -+ -+struct intel_dp { -+ i915_reg_t output_reg; -+ u32 DP; -+ int link_rate; -+ u8 lane_count; -+ u8 sink_count; -+ bool link_mst; -+ bool link_trained; -+ bool has_audio; -+ bool reset_link_params; -+ u8 dpcd[DP_RECEIVER_CAP_SIZE]; -+ u8 psr_dpcd[EDP_PSR_RECEIVER_CAP_SIZE]; -+ u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS]; -+ u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]; -+ u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]; -+ u8 fec_capable; -+ /* source rates */ -+ int num_source_rates; -+ const int *source_rates; -+ /* sink rates as reported by DP_MAX_LINK_RATE/DP_SUPPORTED_LINK_RATES */ -+ int num_sink_rates; -+ int sink_rates[DP_MAX_SUPPORTED_RATES]; -+ bool use_rate_select; -+ /* intersection of source and sink rates */ -+ int num_common_rates; -+ int common_rates[DP_MAX_SUPPORTED_RATES]; -+ /* Max lane count for the current link */ -+ int max_link_lane_count; -+ /* Max rate for the current link */ -+ int max_link_rate; -+ /* sink or branch descriptor */ -+ struct drm_dp_desc desc; -+ struct drm_dp_aux aux; -+ u8 train_set[4]; -+ int panel_power_up_delay; -+ int panel_power_down_delay; -+ int panel_power_cycle_delay; -+ int backlight_on_delay; -+ int backlight_off_delay; -+ struct delayed_work panel_vdd_work; -+ bool want_panel_vdd; -+ unsigned long last_power_on; -+ unsigned long last_backlight_off; -+ ktime_t panel_power_off_time; -+ -+ struct notifier_block edp_notifier; -+ -+ /* -+ * Pipe whose power sequencer is currently locked into -+ * this port. Only relevant on VLV/CHV. -+ */ -+ enum pipe pps_pipe; -+ /* -+ * Pipe currently driving the port. Used for preventing -+ * the use of the PPS for any pipe currentrly driving -+ * external DP as that will mess things up on VLV. -+ */ -+ enum pipe active_pipe; -+ /* -+ * Set if the sequencer may be reset due to a power transition, -+ * requiring a reinitialization. Only relevant on BXT. -+ */ -+ bool pps_reset; -+ struct edp_power_seq pps_delays; -+ -+ bool can_mst; /* this port supports mst */ -+ bool is_mst; -+ int active_mst_links; -+ /* connector directly attached - won't be use for modeset in mst world */ -+ struct intel_connector *attached_connector; -+ -+ /* mst connector list */ -+ struct intel_dp_mst_encoder *mst_encoders[I915_MAX_PIPES]; -+ struct drm_dp_mst_topology_mgr mst_mgr; -+ -+ u32 (*get_aux_clock_divider)(struct intel_dp *dp, int index); -+ /* -+ * This function returns the value we have to program the AUX_CTL -+ * register with to kick off an AUX transaction. -+ */ -+ u32 (*get_aux_send_ctl)(struct intel_dp *dp, int send_bytes, -+ u32 aux_clock_divider); -+ -+ i915_reg_t (*aux_ch_ctl_reg)(struct intel_dp *dp); -+ i915_reg_t (*aux_ch_data_reg)(struct intel_dp *dp, int index); -+ -+ /* This is called before a link training is starterd */ -+ void (*prepare_link_retrain)(struct intel_dp *intel_dp); -+ -+ /* Displayport compliance testing */ -+ struct intel_dp_compliance compliance; -+ -+ /* Display stream compression testing */ -+ bool force_dsc_en; -+}; -+ -+enum lspcon_vendor { -+ LSPCON_VENDOR_MCA, -+ LSPCON_VENDOR_PARADE -+}; -+ -+struct intel_lspcon { -+ bool active; -+ enum drm_lspcon_mode mode; -+ enum lspcon_vendor vendor; -+}; -+ -+struct intel_digital_port { -+ struct intel_encoder base; -+ u32 saved_port_bits; -+ struct intel_dp dp; -+ struct intel_hdmi hdmi; -+ struct intel_lspcon lspcon; -+ enum irqreturn (*hpd_pulse)(struct intel_digital_port *, bool); -+ bool release_cl2_override; -+ u8 max_lanes; -+ /* Used for DP and ICL+ TypeC/DP and TypeC/HDMI ports. */ -+ enum aux_ch aux_ch; -+ enum intel_display_power_domain ddi_io_power_domain; -+ bool tc_legacy_port:1; -+ enum tc_port_type tc_type; -+ -+ void (*write_infoframe)(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ unsigned int type, -+ const void *frame, ssize_t len); -+ void (*read_infoframe)(struct intel_encoder *encoder, -+ const struct intel_crtc_state *crtc_state, -+ unsigned int type, -+ void *frame, ssize_t len); -+ void (*set_infoframes)(struct intel_encoder *encoder, -+ bool enable, -+ const struct intel_crtc_state *crtc_state, -+ const struct drm_connector_state *conn_state); -+ u32 (*infoframes_enabled)(struct intel_encoder *encoder, -+ const struct intel_crtc_state *pipe_config); -+}; -+ -+struct intel_dp_mst_encoder { -+ struct intel_encoder base; -+ enum pipe pipe; -+ struct intel_digital_port *primary; -+ struct intel_connector *connector; -+}; -+ -+static inline enum dpio_channel -+vlv_dport_to_channel(struct intel_digital_port *dport) -+{ -+ switch (dport->base.port) { -+ case PORT_B: -+ case PORT_D: -+ return DPIO_CH0; -+ case PORT_C: -+ return DPIO_CH1; -+ default: -+ BUG(); -+ } -+} -+ -+static inline enum dpio_phy -+vlv_dport_to_phy(struct intel_digital_port *dport) -+{ -+ switch (dport->base.port) { -+ case PORT_B: -+ case PORT_C: -+ return DPIO_PHY0; -+ case PORT_D: -+ return DPIO_PHY1; -+ default: -+ BUG(); -+ } -+} -+ -+static inline enum dpio_channel -+vlv_pipe_to_channel(enum pipe pipe) -+{ -+ switch (pipe) { -+ case PIPE_A: -+ case PIPE_C: -+ return DPIO_CH0; -+ case PIPE_B: -+ return DPIO_CH1; -+ default: -+ BUG(); -+ } -+} -+ -+static inline struct intel_crtc * -+intel_get_crtc_for_pipe(struct drm_i915_private *dev_priv, enum pipe pipe) -+{ -+ return dev_priv->pipe_to_crtc_mapping[pipe]; -+} -+ -+static inline struct intel_crtc * -+intel_get_crtc_for_plane(struct drm_i915_private *dev_priv, enum i9xx_plane_id plane) -+{ -+ return dev_priv->plane_to_crtc_mapping[plane]; -+} -+ -+struct intel_load_detect_pipe { -+ struct drm_atomic_state *restore_state; -+}; -+ -+static inline struct intel_encoder * -+intel_attached_encoder(struct drm_connector *connector) -+{ -+ return to_intel_connector(connector)->encoder; -+} -+ -+static inline bool intel_encoder_is_dig_port(struct intel_encoder *encoder) -+{ -+ switch (encoder->type) { -+ case INTEL_OUTPUT_DDI: -+ case INTEL_OUTPUT_DP: -+ case INTEL_OUTPUT_EDP: -+ case INTEL_OUTPUT_HDMI: -+ return true; -+ default: -+ return false; -+ } -+} -+ -+static inline struct intel_digital_port * -+enc_to_dig_port(struct drm_encoder *encoder) -+{ -+ struct intel_encoder *intel_encoder = to_intel_encoder(encoder); -+ -+ if (intel_encoder_is_dig_port(intel_encoder)) -+ return container_of(encoder, struct intel_digital_port, -+ base.base); -+ else -+ return NULL; -+} -+ -+static inline struct intel_digital_port * -+conn_to_dig_port(struct intel_connector *connector) -+{ -+ return enc_to_dig_port(&intel_attached_encoder(&connector->base)->base); -+} -+ -+static inline struct intel_dp_mst_encoder * -+enc_to_mst(struct drm_encoder *encoder) -+{ -+ return container_of(encoder, struct intel_dp_mst_encoder, base.base); -+} -+ -+static inline struct intel_dp *enc_to_intel_dp(struct drm_encoder *encoder) -+{ -+ return &enc_to_dig_port(encoder)->dp; -+} -+ -+static inline bool intel_encoder_is_dp(struct intel_encoder *encoder) -+{ -+ switch (encoder->type) { -+ case INTEL_OUTPUT_DP: -+ case INTEL_OUTPUT_EDP: -+ return true; -+ case INTEL_OUTPUT_DDI: -+ /* Skip pure HDMI/DVI DDI encoders */ -+ return i915_mmio_reg_valid(enc_to_intel_dp(&encoder->base)->output_reg); -+ default: -+ return false; -+ } -+} -+ -+static inline struct intel_lspcon * -+enc_to_intel_lspcon(struct drm_encoder *encoder) -+{ -+ return &enc_to_dig_port(encoder)->lspcon; -+} -+ -+static inline struct intel_digital_port * -+dp_to_dig_port(struct intel_dp *intel_dp) -+{ -+ return container_of(intel_dp, struct intel_digital_port, dp); -+} -+ -+static inline struct intel_lspcon * -+dp_to_lspcon(struct intel_dp *intel_dp) -+{ -+ return &dp_to_dig_port(intel_dp)->lspcon; -+} -+ -+static inline struct drm_i915_private * -+dp_to_i915(struct intel_dp *intel_dp) -+{ -+ return to_i915(dp_to_dig_port(intel_dp)->base.base.dev); -+} -+ -+static inline struct intel_digital_port * -+hdmi_to_dig_port(struct intel_hdmi *intel_hdmi) -+{ -+ return container_of(intel_hdmi, struct intel_digital_port, hdmi); -+} -+ -+static inline struct intel_plane_state * -+intel_atomic_get_plane_state(struct intel_atomic_state *state, -+ struct intel_plane *plane) -+{ -+ struct drm_plane_state *ret = -+ drm_atomic_get_plane_state(&state->base, &plane->base); -+ -+ if (IS_ERR(ret)) -+ return ERR_CAST(ret); -+ -+ return to_intel_plane_state(ret); -+} -+ -+static inline struct intel_plane_state * -+intel_atomic_get_old_plane_state(struct intel_atomic_state *state, -+ struct intel_plane *plane) -+{ -+ return to_intel_plane_state(drm_atomic_get_old_plane_state(&state->base, -+ &plane->base)); -+} -+ -+static inline struct intel_plane_state * -+intel_atomic_get_new_plane_state(struct intel_atomic_state *state, -+ struct intel_plane *plane) -+{ -+ return to_intel_plane_state(drm_atomic_get_new_plane_state(&state->base, -+ &plane->base)); -+} -+ -+static inline struct intel_crtc_state * -+intel_atomic_get_old_crtc_state(struct intel_atomic_state *state, -+ struct intel_crtc *crtc) -+{ -+ return to_intel_crtc_state(drm_atomic_get_old_crtc_state(&state->base, -+ &crtc->base)); -+} -+ -+static inline struct intel_crtc_state * -+intel_atomic_get_new_crtc_state(struct intel_atomic_state *state, -+ struct intel_crtc *crtc) -+{ -+ return to_intel_crtc_state(drm_atomic_get_new_crtc_state(&state->base, -+ &crtc->base)); -+} -+ -+/* intel_fifo_underrun.c */ -+bool intel_set_cpu_fifo_underrun_reporting(struct drm_i915_private *dev_priv, -+ enum pipe pipe, bool enable); -+bool intel_set_pch_fifo_underrun_reporting(struct drm_i915_private *dev_priv, -+ enum pipe pch_transcoder, -+ bool enable); -+void intel_cpu_fifo_underrun_irq_handler(struct drm_i915_private *dev_priv, -+ enum pipe pipe); -+void intel_pch_fifo_underrun_irq_handler(struct drm_i915_private *dev_priv, -+ enum pipe pch_transcoder); -+void intel_check_cpu_fifo_underruns(struct drm_i915_private *dev_priv); -+void intel_check_pch_fifo_underruns(struct drm_i915_private *dev_priv); -+ -+/* i915_irq.c */ -+void gen5_enable_gt_irq(struct drm_i915_private *dev_priv, u32 mask); -+void gen5_disable_gt_irq(struct drm_i915_private *dev_priv, u32 mask); -+void gen6_mask_pm_irq(struct drm_i915_private *dev_priv, u32 mask); -+void gen6_unmask_pm_irq(struct drm_i915_private *dev_priv, u32 mask); -+void gen11_reset_rps_interrupts(struct drm_i915_private *dev_priv); -+void gen6_reset_rps_interrupts(struct drm_i915_private *dev_priv); -+void gen6_enable_rps_interrupts(struct drm_i915_private *dev_priv); -+void gen6_disable_rps_interrupts(struct drm_i915_private *dev_priv); -+void gen6_rps_reset_ei(struct drm_i915_private *dev_priv); -+ -+static inline u32 gen6_sanitize_rps_pm_mask(const struct drm_i915_private *i915, -+ u32 mask) -+{ -+ return mask & ~i915->gt_pm.rps.pm_intrmsk_mbz; -+} -+ -+void intel_runtime_pm_disable_interrupts(struct drm_i915_private *dev_priv); -+void intel_runtime_pm_enable_interrupts(struct drm_i915_private *dev_priv); -+static inline bool intel_irqs_enabled(struct drm_i915_private *dev_priv) -+{ -+ /* -+ * We only use drm_irq_uninstall() at unload and VT switch, so -+ * this is the only thing we need to check. -+ */ -+ return dev_priv->runtime_pm.irqs_enabled; -+} -+ -+int intel_get_crtc_scanline(struct intel_crtc *crtc); -+void gen8_irq_power_well_post_enable(struct drm_i915_private *dev_priv, -+ u8 pipe_mask); -+void gen8_irq_power_well_pre_disable(struct drm_i915_private *dev_priv, -+ u8 pipe_mask); -+void gen9_reset_guc_interrupts(struct drm_i915_private *dev_priv); -+void gen9_enable_guc_interrupts(struct drm_i915_private *dev_priv); -+void gen9_disable_guc_interrupts(struct drm_i915_private *dev_priv); -+ -+/* intel_display.c */ -+void intel_plane_destroy(struct drm_plane *plane); -+void i830_enable_pipe(struct drm_i915_private *dev_priv, enum pipe pipe); -+void i830_disable_pipe(struct drm_i915_private *dev_priv, enum pipe pipe); -+enum pipe intel_crtc_pch_transcoder(struct intel_crtc *crtc); -+int vlv_get_hpll_vco(struct drm_i915_private *dev_priv); -+int vlv_get_cck_clock(struct drm_i915_private *dev_priv, -+ const char *name, u32 reg, int ref_freq); -+int vlv_get_cck_clock_hpll(struct drm_i915_private *dev_priv, -+ const char *name, u32 reg); -+void lpt_disable_pch_transcoder(struct drm_i915_private *dev_priv); -+void lpt_disable_iclkip(struct drm_i915_private *dev_priv); -+void intel_init_display_hooks(struct drm_i915_private *dev_priv); -+unsigned int intel_fb_xy_to_linear(int x, int y, -+ const struct intel_plane_state *state, -+ int plane); -+unsigned int intel_fb_align_height(const struct drm_framebuffer *fb, -+ int color_plane, unsigned int height); -+void intel_add_fb_offsets(int *x, int *y, -+ const struct intel_plane_state *state, int plane); -+unsigned int intel_rotation_info_size(const struct intel_rotation_info *rot_info); -+bool intel_has_pending_fb_unpin(struct drm_i915_private *dev_priv); -+void intel_mark_busy(struct drm_i915_private *dev_priv); -+void intel_mark_idle(struct drm_i915_private *dev_priv); -+int intel_display_suspend(struct drm_device *dev); -+void intel_pps_unlock_regs_wa(struct drm_i915_private *dev_priv); -+void intel_encoder_destroy(struct drm_encoder *encoder); -+struct drm_display_mode * -+intel_encoder_current_mode(struct intel_encoder *encoder); -+bool intel_port_is_combophy(struct drm_i915_private *dev_priv, enum port port); -+bool intel_port_is_tc(struct drm_i915_private *dev_priv, enum port port); -+enum tc_port intel_port_to_tc(struct drm_i915_private *dev_priv, -+ enum port port); -+int intel_get_pipe_from_crtc_id_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+enum transcoder intel_pipe_to_cpu_transcoder(struct drm_i915_private *dev_priv, -+ enum pipe pipe); -+static inline bool -+intel_crtc_has_type(const struct intel_crtc_state *crtc_state, -+ enum intel_output_type type) -+{ -+ return crtc_state->output_types & (1 << type); -+} -+static inline bool -+intel_crtc_has_dp_encoder(const struct intel_crtc_state *crtc_state) -+{ -+ return crtc_state->output_types & -+ ((1 << INTEL_OUTPUT_DP) | -+ (1 << INTEL_OUTPUT_DP_MST) | -+ (1 << INTEL_OUTPUT_EDP)); -+} -+static inline void -+intel_wait_for_vblank(struct drm_i915_private *dev_priv, enum pipe pipe) -+{ -+ drm_wait_one_vblank(&dev_priv->drm, pipe); -+} -+static inline void -+intel_wait_for_vblank_if_active(struct drm_i915_private *dev_priv, int pipe) -+{ -+ const struct intel_crtc *crtc = intel_get_crtc_for_pipe(dev_priv, pipe); -+ -+ if (crtc->active) -+ intel_wait_for_vblank(dev_priv, pipe); -+} -+ -+u32 intel_crtc_get_vblank_counter(struct intel_crtc *crtc); -+ -+int ironlake_get_lanes_required(int target_clock, int link_bw, int bpp); -+void vlv_wait_port_ready(struct drm_i915_private *dev_priv, -+ struct intel_digital_port *dport, -+ unsigned int expected_mask); -+int intel_get_load_detect_pipe(struct drm_connector *connector, -+ const struct drm_display_mode *mode, -+ struct intel_load_detect_pipe *old, -+ struct drm_modeset_acquire_ctx *ctx); -+void intel_release_load_detect_pipe(struct drm_connector *connector, -+ struct intel_load_detect_pipe *old, -+ struct drm_modeset_acquire_ctx *ctx); -+struct i915_vma * -+intel_pin_and_fence_fb_obj(struct drm_framebuffer *fb, -+ const struct i915_ggtt_view *view, -+ bool uses_fence, -+ unsigned long *out_flags); -+void intel_unpin_fb_vma(struct i915_vma *vma, unsigned long flags); -+struct drm_framebuffer * -+intel_framebuffer_create(struct drm_i915_gem_object *obj, -+ struct drm_mode_fb_cmd2 *mode_cmd); -+int intel_prepare_plane_fb(struct drm_plane *plane, -+ struct drm_plane_state *new_state); -+void intel_cleanup_plane_fb(struct drm_plane *plane, -+ struct drm_plane_state *old_state); -+int intel_plane_atomic_get_property(struct drm_plane *plane, -+ const struct drm_plane_state *state, -+ struct drm_property *property, -+ u64 *val); -+int intel_plane_atomic_set_property(struct drm_plane *plane, -+ struct drm_plane_state *state, -+ struct drm_property *property, -+ u64 val); -+int intel_plane_atomic_calc_changes(const struct intel_crtc_state *old_crtc_state, -+ struct drm_crtc_state *crtc_state, -+ const struct intel_plane_state *old_plane_state, -+ struct drm_plane_state *plane_state); -+ -+void assert_pch_transcoder_disabled(struct drm_i915_private *dev_priv, -+ enum pipe pipe); -+ -+int vlv_force_pll_on(struct drm_i915_private *dev_priv, enum pipe pipe, -+ const struct dpll *dpll); -+void vlv_force_pll_off(struct drm_i915_private *dev_priv, enum pipe pipe); -+int lpt_get_iclkip(struct drm_i915_private *dev_priv); -+bool intel_fuzzy_clock_check(int clock1, int clock2); -+ -+/* modesetting asserts */ -+void assert_panel_unlocked(struct drm_i915_private *dev_priv, -+ enum pipe pipe); -+void assert_pll(struct drm_i915_private *dev_priv, -+ enum pipe pipe, bool state); -+#define assert_pll_enabled(d, p) assert_pll(d, p, true) -+#define assert_pll_disabled(d, p) assert_pll(d, p, false) -+void assert_dsi_pll(struct drm_i915_private *dev_priv, bool state); -+#define assert_dsi_pll_enabled(d) assert_dsi_pll(d, true) -+#define assert_dsi_pll_disabled(d) assert_dsi_pll(d, false) -+void assert_fdi_rx_pll(struct drm_i915_private *dev_priv, -+ enum pipe pipe, bool state); -+#define assert_fdi_rx_pll_enabled(d, p) assert_fdi_rx_pll(d, p, true) -+#define assert_fdi_rx_pll_disabled(d, p) assert_fdi_rx_pll(d, p, false) -+void assert_pipe(struct drm_i915_private *dev_priv, enum pipe pipe, bool state); -+#define assert_pipe_enabled(d, p) assert_pipe(d, p, true) -+#define assert_pipe_disabled(d, p) assert_pipe(d, p, false) -+void intel_prepare_reset(struct drm_i915_private *dev_priv); -+void intel_finish_reset(struct drm_i915_private *dev_priv); -+void hsw_enable_pc8(struct drm_i915_private *dev_priv); -+void hsw_disable_pc8(struct drm_i915_private *dev_priv); -+void gen9_sanitize_dc_state(struct drm_i915_private *dev_priv); -+void bxt_enable_dc9(struct drm_i915_private *dev_priv); -+void bxt_disable_dc9(struct drm_i915_private *dev_priv); -+void gen9_enable_dc5(struct drm_i915_private *dev_priv); -+unsigned int skl_cdclk_get_vco(unsigned int freq); -+void skl_enable_dc6(struct drm_i915_private *dev_priv); -+void intel_dp_get_m_n(struct intel_crtc *crtc, -+ struct intel_crtc_state *pipe_config); -+void intel_dp_set_m_n(const struct intel_crtc_state *crtc_state, -+ enum link_m_n_set m_n); -+int intel_dotclock_calculate(int link_freq, const struct intel_link_m_n *m_n); -+bool bxt_find_best_dpll(struct intel_crtc_state *crtc_state, -+ struct dpll *best_clock); -+int chv_calc_dpll_params(int refclk, struct dpll *pll_clock); -+ -+bool intel_crtc_active(struct intel_crtc *crtc); -+bool hsw_crtc_state_ips_capable(const struct intel_crtc_state *crtc_state); -+void hsw_enable_ips(const struct intel_crtc_state *crtc_state); -+void hsw_disable_ips(const struct intel_crtc_state *crtc_state); -+enum intel_display_power_domain intel_port_to_power_domain(enum port port); -+enum intel_display_power_domain -+intel_aux_power_domain(struct intel_digital_port *dig_port); -+void intel_mode_from_pipe_config(struct drm_display_mode *mode, -+ struct intel_crtc_state *pipe_config); -+void intel_crtc_arm_fifo_underrun(struct intel_crtc *crtc, -+ struct intel_crtc_state *crtc_state); -+ -+u16 skl_scaler_calc_phase(int sub, int scale, bool chroma_center); -+int skl_update_scaler_crtc(struct intel_crtc_state *crtc_state); -+int skl_max_scale(const struct intel_crtc_state *crtc_state, -+ u32 pixel_format); -+ -+static inline u32 intel_plane_ggtt_offset(const struct intel_plane_state *state) -+{ -+ return i915_ggtt_offset(state->vma); -+} -+ -+u32 glk_plane_color_ctl(const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state); -+u32 glk_plane_color_ctl_crtc(const struct intel_crtc_state *crtc_state); -+u32 skl_plane_ctl(const struct intel_crtc_state *crtc_state, -+ const struct intel_plane_state *plane_state); -+u32 skl_plane_ctl_crtc(const struct intel_crtc_state *crtc_state); -+u32 skl_plane_stride(const struct intel_plane_state *plane_state, -+ int plane); -+int skl_check_plane_surface(struct intel_plane_state *plane_state); -+int i9xx_check_plane_surface(struct intel_plane_state *plane_state); -+int skl_format_to_fourcc(int format, bool rgb_order, bool alpha); -+unsigned int i9xx_plane_max_stride(struct intel_plane *plane, -+ u32 pixel_format, u64 modifier, -+ unsigned int rotation); -+ -+/* intel_dp_link_training.c */ -+void intel_dp_start_link_train(struct intel_dp *intel_dp); -+void intel_dp_stop_link_train(struct intel_dp *intel_dp); -+ -+/* intel_vdsc.c */ -+int intel_dp_compute_dsc_params(struct intel_dp *intel_dp, -+ struct intel_crtc_state *pipe_config); -+enum intel_display_power_domain -+intel_dsc_power_domain(const struct intel_crtc_state *crtc_state); -+ -+/* intel_dp_aux_backlight.c */ -+int intel_dp_aux_init_backlight_funcs(struct intel_connector *intel_connector); -+ -+/* intel_dp_mst.c */ -+int intel_dp_mst_encoder_init(struct intel_digital_port *intel_dig_port, int conn_id); -+void intel_dp_mst_encoder_cleanup(struct intel_digital_port *intel_dig_port); -+/* vlv_dsi.c */ -+void vlv_dsi_init(struct drm_i915_private *dev_priv); -+ -+/* icl_dsi.c */ -+void icl_dsi_init(struct drm_i915_private *dev_priv); -+ -+/* intel_dsi_dcs_backlight.c */ -+int intel_dsi_dcs_init_backlight_funcs(struct intel_connector *intel_connector); -+ -+/* intel_hotplug.c */ -+void intel_hpd_poll_init(struct drm_i915_private *dev_priv); -+bool intel_encoder_hotplug(struct intel_encoder *encoder, -+ struct intel_connector *connector); -+ -+/* intel_overlay.c */ -+void intel_overlay_setup(struct drm_i915_private *dev_priv); -+void intel_overlay_cleanup(struct drm_i915_private *dev_priv); -+int intel_overlay_switch_off(struct intel_overlay *overlay); -+int intel_overlay_put_image_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+int intel_overlay_attrs_ioctl(struct drm_device *dev, void *data, -+ struct drm_file *file_priv); -+void intel_overlay_reset(struct drm_i915_private *dev_priv); -+ -+/* intel_quirks.c */ -+void intel_init_quirks(struct drm_i915_private *dev_priv); -+ -+/* intel_runtime_pm.c */ -+void intel_runtime_pm_init_early(struct drm_i915_private *dev_priv); -+int intel_power_domains_init(struct drm_i915_private *); -+void intel_power_domains_cleanup(struct drm_i915_private *dev_priv); -+void intel_power_domains_init_hw(struct drm_i915_private *dev_priv, bool resume); -+void intel_power_domains_fini_hw(struct drm_i915_private *dev_priv); -+void icl_display_core_init(struct drm_i915_private *dev_priv, bool resume); -+void icl_display_core_uninit(struct drm_i915_private *dev_priv); -+void intel_power_domains_enable(struct drm_i915_private *dev_priv); -+void intel_power_domains_disable(struct drm_i915_private *dev_priv); -+ -+enum i915_drm_suspend_mode { -+ I915_DRM_SUSPEND_IDLE, -+ I915_DRM_SUSPEND_MEM, -+ I915_DRM_SUSPEND_HIBERNATE, -+}; -+ -+void intel_power_domains_suspend(struct drm_i915_private *dev_priv, -+ enum i915_drm_suspend_mode); -+void intel_power_domains_resume(struct drm_i915_private *dev_priv); -+void bxt_display_core_init(struct drm_i915_private *dev_priv, bool resume); -+void bxt_display_core_uninit(struct drm_i915_private *dev_priv); -+void intel_runtime_pm_enable(struct drm_i915_private *dev_priv); -+void intel_runtime_pm_disable(struct drm_i915_private *dev_priv); -+void intel_runtime_pm_cleanup(struct drm_i915_private *dev_priv); -+const char * -+intel_display_power_domain_str(enum intel_display_power_domain domain); -+ -+bool intel_display_power_is_enabled(struct drm_i915_private *dev_priv, -+ enum intel_display_power_domain domain); -+bool __intel_display_power_is_enabled(struct drm_i915_private *dev_priv, -+ enum intel_display_power_domain domain); -+intel_wakeref_t intel_display_power_get(struct drm_i915_private *dev_priv, -+ enum intel_display_power_domain domain); -+intel_wakeref_t -+intel_display_power_get_if_enabled(struct drm_i915_private *dev_priv, -+ enum intel_display_power_domain domain); -+void intel_display_power_put_unchecked(struct drm_i915_private *dev_priv, -+ enum intel_display_power_domain domain); -+#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_RUNTIME_PM) -+void intel_display_power_put(struct drm_i915_private *dev_priv, -+ enum intel_display_power_domain domain, -+ intel_wakeref_t wakeref); -+#else -+#define intel_display_power_put(i915, domain, wakeref) \ -+ intel_display_power_put_unchecked(i915, domain) -+#endif -+void icl_dbuf_slices_update(struct drm_i915_private *dev_priv, -+ u8 req_slices); -+ -+static inline void -+assert_rpm_device_not_suspended(struct i915_runtime_pm *rpm) -+{ -+ WARN_ONCE(rpm->suspended, -+ "Device suspended during HW access\n"); -+} -+ -+static inline void -+__assert_rpm_wakelock_held(struct i915_runtime_pm *rpm) -+{ -+ assert_rpm_device_not_suspended(rpm); -+ WARN_ONCE(!atomic_read(&rpm->wakeref_count), -+ "RPM wakelock ref not held during HW access"); -+} -+ -+static inline void -+assert_rpm_wakelock_held(struct drm_i915_private *i915) -+{ -+ __assert_rpm_wakelock_held(&i915->runtime_pm); -+} -+ -+/** -+ * disable_rpm_wakeref_asserts - disable the RPM assert checks -+ * @i915: i915 device instance -+ * -+ * This function disable asserts that check if we hold an RPM wakelock -+ * reference, while keeping the device-not-suspended checks still enabled. -+ * It's meant to be used only in special circumstances where our rule about -+ * the wakelock refcount wrt. the device power state doesn't hold. According -+ * to this rule at any point where we access the HW or want to keep the HW in -+ * an active state we must hold an RPM wakelock reference acquired via one of -+ * the intel_runtime_pm_get() helpers. Currently there are a few special spots -+ * where this rule doesn't hold: the IRQ and suspend/resume handlers, the -+ * forcewake release timer, and the GPU RPS and hangcheck works. All other -+ * users should avoid using this function. -+ * -+ * Any calls to this function must have a symmetric call to -+ * enable_rpm_wakeref_asserts(). -+ */ -+static inline void -+disable_rpm_wakeref_asserts(struct drm_i915_private *i915) -+{ -+ atomic_inc(&i915->runtime_pm.wakeref_count); -+} -+ -+/** -+ * enable_rpm_wakeref_asserts - re-enable the RPM assert checks -+ * @i915: i915 device instance -+ * -+ * This function re-enables the RPM assert checks after disabling them with -+ * disable_rpm_wakeref_asserts. It's meant to be used only in special -+ * circumstances otherwise its use should be avoided. -+ * -+ * Any calls to this function must have a symmetric call to -+ * disable_rpm_wakeref_asserts(). -+ */ -+static inline void -+enable_rpm_wakeref_asserts(struct drm_i915_private *i915) -+{ -+ atomic_dec(&i915->runtime_pm.wakeref_count); -+} -+ -+intel_wakeref_t intel_runtime_pm_get(struct drm_i915_private *i915); -+intel_wakeref_t intel_runtime_pm_get_if_in_use(struct drm_i915_private *i915); -+intel_wakeref_t intel_runtime_pm_get_noresume(struct drm_i915_private *i915); -+ -+#define with_intel_runtime_pm(i915, wf) \ -+ for ((wf) = intel_runtime_pm_get(i915); (wf); \ -+ intel_runtime_pm_put((i915), (wf)), (wf) = 0) -+ -+#define with_intel_runtime_pm_if_in_use(i915, wf) \ -+ for ((wf) = intel_runtime_pm_get_if_in_use(i915); (wf); \ -+ intel_runtime_pm_put((i915), (wf)), (wf) = 0) -+ -+void intel_runtime_pm_put_unchecked(struct drm_i915_private *i915); -+#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_RUNTIME_PM) -+void intel_runtime_pm_put(struct drm_i915_private *i915, intel_wakeref_t wref); -+#else -+#define intel_runtime_pm_put(i915, wref) intel_runtime_pm_put_unchecked(i915) -+#endif -+ -+#if IS_ENABLED(CONFIG_DRM_I915_DEBUG_RUNTIME_PM) -+void print_intel_runtime_pm_wakeref(struct drm_i915_private *i915, -+ struct drm_printer *p); -+#else -+static inline void print_intel_runtime_pm_wakeref(struct drm_i915_private *i915, -+ struct drm_printer *p) -+{ -+} -+#endif -+ -+void chv_phy_powergate_lanes(struct intel_encoder *encoder, -+ bool override, unsigned int mask); -+bool chv_phy_powergate_ch(struct drm_i915_private *dev_priv, enum dpio_phy phy, -+ enum dpio_channel ch, bool override); -+ -+/* intel_atomic.c */ -+int intel_digital_connector_atomic_get_property(struct drm_connector *connector, -+ const struct drm_connector_state *state, -+ struct drm_property *property, -+ u64 *val); -+int intel_digital_connector_atomic_set_property(struct drm_connector *connector, -+ struct drm_connector_state *state, -+ struct drm_property *property, -+ u64 val); -+int intel_digital_connector_atomic_check(struct drm_connector *conn, -+ struct drm_atomic_state *state); -+struct drm_connector_state * -+intel_digital_connector_duplicate_state(struct drm_connector *connector); -+ -+struct drm_crtc_state *intel_crtc_duplicate_state(struct drm_crtc *crtc); -+void intel_crtc_destroy_state(struct drm_crtc *crtc, -+ struct drm_crtc_state *state); -+struct drm_atomic_state *intel_atomic_state_alloc(struct drm_device *dev); -+void intel_atomic_state_clear(struct drm_atomic_state *); -+ -+static inline struct intel_crtc_state * -+intel_atomic_get_crtc_state(struct drm_atomic_state *state, -+ struct intel_crtc *crtc) -+{ -+ struct drm_crtc_state *crtc_state; -+ crtc_state = drm_atomic_get_crtc_state(state, &crtc->base); -+ if (IS_ERR(crtc_state)) -+ return ERR_CAST(crtc_state); -+ -+ return to_intel_crtc_state(crtc_state); -+} -+ -+int intel_atomic_setup_scalers(struct drm_i915_private *dev_priv, -+ struct intel_crtc *intel_crtc, -+ struct intel_crtc_state *crtc_state); -+ -+#endif /* __INTEL_DRV_H__ */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_dsi.c b/drivers/gpu/drm/i915_legacy/intel_dsi.c -new file mode 100644 -index 000000000000..5fec02aceaed ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_dsi.c -@@ -0,0 +1,128 @@ -+// SPDX-License-Identifier: MIT -+/* -+ * Copyright © 2018 Intel Corporation -+ */ -+ -+#include -+#include "intel_dsi.h" -+ -+int intel_dsi_bitrate(const struct intel_dsi *intel_dsi) -+{ -+ int bpp = mipi_dsi_pixel_format_to_bpp(intel_dsi->pixel_format); -+ -+ if (WARN_ON(bpp < 0)) -+ bpp = 16; -+ -+ return intel_dsi->pclk * bpp / intel_dsi->lane_count; -+} -+ -+int intel_dsi_tlpx_ns(const struct intel_dsi *intel_dsi) -+{ -+ switch (intel_dsi->escape_clk_div) { -+ default: -+ case 0: -+ return 50; -+ case 1: -+ return 100; -+ case 2: -+ return 200; -+ } -+} -+ -+int intel_dsi_get_modes(struct drm_connector *connector) -+{ -+ struct intel_connector *intel_connector = to_intel_connector(connector); -+ struct drm_display_mode *mode; -+ -+ DRM_DEBUG_KMS("\n"); -+ -+ if (!intel_connector->panel.fixed_mode) { -+ DRM_DEBUG_KMS("no fixed mode\n"); -+ return 0; -+ } -+ -+ mode = drm_mode_duplicate(connector->dev, -+ intel_connector->panel.fixed_mode); -+ if (!mode) { -+ DRM_DEBUG_KMS("drm_mode_duplicate failed\n"); -+ return 0; -+ } -+ -+ drm_mode_probed_add(connector, mode); -+ return 1; -+} -+ -+enum drm_mode_status intel_dsi_mode_valid(struct drm_connector *connector, -+ struct drm_display_mode *mode) -+{ -+ struct intel_connector *intel_connector = to_intel_connector(connector); -+ const struct drm_display_mode *fixed_mode = intel_connector->panel.fixed_mode; -+ int max_dotclk = to_i915(connector->dev)->max_dotclk_freq; -+ -+ DRM_DEBUG_KMS("\n"); -+ -+ if (mode->flags & DRM_MODE_FLAG_DBLSCAN) -+ return MODE_NO_DBLESCAN; -+ -+ if (fixed_mode) { -+ if (mode->hdisplay > fixed_mode->hdisplay) -+ return MODE_PANEL; -+ if (mode->vdisplay > fixed_mode->vdisplay) -+ return MODE_PANEL; -+ if (fixed_mode->clock > max_dotclk) -+ return MODE_CLOCK_HIGH; -+ } -+ -+ return MODE_OK; -+} -+ -+struct intel_dsi_host *intel_dsi_host_init(struct intel_dsi *intel_dsi, -+ const struct mipi_dsi_host_ops *funcs, -+ enum port port) -+{ -+ struct intel_dsi_host *host; -+ struct mipi_dsi_device *device; -+ -+ host = kzalloc(sizeof(*host), GFP_KERNEL); -+ if (!host) -+ return NULL; -+ -+ host->base.ops = funcs; -+ host->intel_dsi = intel_dsi; -+ host->port = port; -+ -+ /* -+ * We should call mipi_dsi_host_register(&host->base) here, but we don't -+ * have a host->dev, and we don't have OF stuff either. So just use the -+ * dsi framework as a library and hope for the best. Create the dsi -+ * devices by ourselves here too. Need to be careful though, because we -+ * don't initialize any of the driver model devices here. -+ */ -+ device = kzalloc(sizeof(*device), GFP_KERNEL); -+ if (!device) { -+ kfree(host); -+ return NULL; -+ } -+ -+ device->host = &host->base; -+ host->device = device; -+ -+ return host; -+} -+ -+enum drm_panel_orientation -+intel_dsi_get_panel_orientation(struct intel_connector *connector) -+{ -+ struct drm_i915_private *dev_priv = to_i915(connector->base.dev); -+ enum drm_panel_orientation orientation; -+ -+ orientation = dev_priv->vbt.dsi.orientation; -+ if (orientation != DRM_MODE_PANEL_ORIENTATION_UNKNOWN) -+ return orientation; -+ -+ orientation = dev_priv->vbt.orientation; -+ if (orientation != DRM_MODE_PANEL_ORIENTATION_UNKNOWN) -+ return orientation; -+ -+ return DRM_MODE_PANEL_ORIENTATION_NORMAL; -+} -diff --git a/drivers/gpu/drm/i915_legacy/intel_dsi.h b/drivers/gpu/drm/i915_legacy/intel_dsi.h -new file mode 100644 -index 000000000000..705a609050c0 ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_dsi.h -@@ -0,0 +1,196 @@ -+/* -+ * Copyright © 2013 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ */ -+ -+#ifndef _INTEL_DSI_H -+#define _INTEL_DSI_H -+ -+#include -+#include -+#include "intel_drv.h" -+ -+/* Dual Link support */ -+#define DSI_DUAL_LINK_NONE 0 -+#define DSI_DUAL_LINK_FRONT_BACK 1 -+#define DSI_DUAL_LINK_PIXEL_ALT 2 -+ -+struct intel_dsi_host; -+ -+struct intel_dsi { -+ struct intel_encoder base; -+ -+ struct intel_dsi_host *dsi_hosts[I915_MAX_PORTS]; -+ intel_wakeref_t io_wakeref[I915_MAX_PORTS]; -+ -+ /* GPIO Desc for CRC based Panel control */ -+ struct gpio_desc *gpio_panel; -+ -+ struct intel_connector *attached_connector; -+ -+ /* bit mask of ports being driven */ -+ u16 ports; -+ -+ /* if true, use HS mode, otherwise LP */ -+ bool hs; -+ -+ /* virtual channel */ -+ int channel; -+ -+ /* Video mode or command mode */ -+ u16 operation_mode; -+ -+ /* number of DSI lanes */ -+ unsigned int lane_count; -+ -+ /* -+ * video mode pixel format -+ * -+ * XXX: consolidate on .format in struct mipi_dsi_device. -+ */ -+ enum mipi_dsi_pixel_format pixel_format; -+ -+ /* video mode format for MIPI_VIDEO_MODE_FORMAT register */ -+ u32 video_mode_format; -+ -+ /* eot for MIPI_EOT_DISABLE register */ -+ u8 eotp_pkt; -+ u8 clock_stop; -+ -+ u8 escape_clk_div; -+ u8 dual_link; -+ -+ u16 dcs_backlight_ports; -+ u16 dcs_cabc_ports; -+ -+ /* RGB or BGR */ -+ bool bgr_enabled; -+ -+ u8 pixel_overlap; -+ u32 port_bits; -+ u32 bw_timer; -+ u32 dphy_reg; -+ -+ /* data lanes dphy timing */ -+ u32 dphy_data_lane_reg; -+ u32 video_frmt_cfg_bits; -+ u16 lp_byte_clk; -+ -+ /* timeouts in byte clocks */ -+ u16 hs_tx_timeout; -+ u16 lp_rx_timeout; -+ u16 turn_arnd_val; -+ u16 rst_timer_val; -+ u16 hs_to_lp_count; -+ u16 clk_lp_to_hs_count; -+ u16 clk_hs_to_lp_count; -+ -+ u16 init_count; -+ u32 pclk; -+ u16 burst_mode_ratio; -+ -+ /* all delays in ms */ -+ u16 backlight_off_delay; -+ u16 backlight_on_delay; -+ u16 panel_on_delay; -+ u16 panel_off_delay; -+ u16 panel_pwr_cycle_delay; -+}; -+ -+struct intel_dsi_host { -+ struct mipi_dsi_host base; -+ struct intel_dsi *intel_dsi; -+ enum port port; -+ -+ /* our little hack */ -+ struct mipi_dsi_device *device; -+}; -+ -+static inline struct intel_dsi_host *to_intel_dsi_host(struct mipi_dsi_host *h) -+{ -+ return container_of(h, struct intel_dsi_host, base); -+} -+ -+#define for_each_dsi_port(__port, __ports_mask) for_each_port_masked(__port, __ports_mask) -+ -+static inline struct intel_dsi *enc_to_intel_dsi(struct drm_encoder *encoder) -+{ -+ return container_of(encoder, struct intel_dsi, base.base); -+} -+ -+static inline bool is_vid_mode(struct intel_dsi *intel_dsi) -+{ -+ return intel_dsi->operation_mode == INTEL_DSI_VIDEO_MODE; -+} -+ -+static inline bool is_cmd_mode(struct intel_dsi *intel_dsi) -+{ -+ return intel_dsi->operation_mode == INTEL_DSI_COMMAND_MODE; -+} -+ -+static inline u16 intel_dsi_encoder_ports(struct intel_encoder *encoder) -+{ -+ return enc_to_intel_dsi(&encoder->base)->ports; -+} -+ -+/* intel_dsi.c */ -+int intel_dsi_bitrate(const struct intel_dsi *intel_dsi); -+int intel_dsi_tlpx_ns(const struct intel_dsi *intel_dsi); -+enum drm_panel_orientation -+intel_dsi_get_panel_orientation(struct intel_connector *connector); -+ -+/* vlv_dsi.c */ -+void vlv_dsi_wait_for_fifo_empty(struct intel_dsi *intel_dsi, enum port port); -+enum mipi_dsi_pixel_format pixel_format_from_register_bits(u32 fmt); -+int intel_dsi_get_modes(struct drm_connector *connector); -+enum drm_mode_status intel_dsi_mode_valid(struct drm_connector *connector, -+ struct drm_display_mode *mode); -+struct intel_dsi_host *intel_dsi_host_init(struct intel_dsi *intel_dsi, -+ const struct mipi_dsi_host_ops *funcs, -+ enum port port); -+ -+/* vlv_dsi_pll.c */ -+int vlv_dsi_pll_compute(struct intel_encoder *encoder, -+ struct intel_crtc_state *config); -+void vlv_dsi_pll_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *config); -+void vlv_dsi_pll_disable(struct intel_encoder *encoder); -+u32 vlv_dsi_get_pclk(struct intel_encoder *encoder, -+ struct intel_crtc_state *config); -+void vlv_dsi_reset_clocks(struct intel_encoder *encoder, enum port port); -+ -+bool bxt_dsi_pll_is_enabled(struct drm_i915_private *dev_priv); -+int bxt_dsi_pll_compute(struct intel_encoder *encoder, -+ struct intel_crtc_state *config); -+void bxt_dsi_pll_enable(struct intel_encoder *encoder, -+ const struct intel_crtc_state *config); -+void bxt_dsi_pll_disable(struct intel_encoder *encoder); -+u32 bxt_dsi_get_pclk(struct intel_encoder *encoder, -+ struct intel_crtc_state *config); -+void bxt_dsi_reset_clocks(struct intel_encoder *encoder, enum port port); -+ -+/* intel_dsi_vbt.c */ -+bool intel_dsi_vbt_init(struct intel_dsi *intel_dsi, u16 panel_id); -+void intel_dsi_vbt_exec_sequence(struct intel_dsi *intel_dsi, -+ enum mipi_seq seq_id); -+void intel_dsi_msleep(struct intel_dsi *intel_dsi, int msec); -+ -+#endif /* _INTEL_DSI_H */ -diff --git a/drivers/gpu/drm/i915_legacy/intel_dsi_dcs_backlight.c b/drivers/gpu/drm/i915_legacy/intel_dsi_dcs_backlight.c -new file mode 100644 -index 000000000000..150a156f3b1e ---- /dev/null -+++ b/drivers/gpu/drm/i915_legacy/intel_dsi_dcs_backlight.c -@@ -0,0 +1,177 @@ -+/* -+ * Copyright © 2016 Intel Corporation -+ * -+ * 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 (including the next -+ * paragraph) 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 AUTHORS OR 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. -+ * -+ * Author: Deepak M -+ */ -+ -+#include "intel_drv.h" -+#include "intel_dsi.h" -+#include "i915_drv.h" -+#include